commit 0e7bfc1fe29fd595df0b982e40f94c30befb1ec7 Author: satire6 <66761962+satire6@users.noreply.github.com> Date: Fri Sep 11 12:58:04 2020 -0400 Initial commit diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..758c1ff --- /dev/null +++ b/README.txt @@ -0,0 +1,3 @@ +Toontown Online is a MMORPG created by Disney Interactive, shut down in 2013. + +This is a collection of various source code for the game. \ No newline at end of file diff --git a/otp/.cvsignore b/otp/.cvsignore new file mode 100644 index 0000000..6f5a0ae --- /dev/null +++ b/otp/.cvsignore @@ -0,0 +1,5 @@ +.cvsignore +__init__.py +Makefile +pp.dep +built diff --git a/otp/.project b/otp/.project new file mode 100644 index 0000000..9ceab4a --- /dev/null +++ b/otp/.project @@ -0,0 +1,11 @@ + + + otp + + + + + + + + diff --git a/otp/Package.pp b/otp/Package.pp new file mode 100644 index 0000000..3b7e0b9 --- /dev/null +++ b/otp/Package.pp @@ -0,0 +1,63 @@ +// +// Package.pp +// +// This file defines certain configuration variables that are to be +// written into the various make scripts. It is processed by ppremake +// (along with the Sources.pp files in each of the various +// directories) to generate build scripts appropriate to each +// environment. +// +// This is the package-specific file, which should be at the top of +// every source hierarchy. It generally gets the ball rolling, and is +// responsible for explicitly including all of the relevent Config.pp +// files. + + + +// What is the name and version of this source tree? +#if $[eq $[PACKAGE],] + #define PACKAGE otp + #define VERSION 0.80 +#endif + + +// Where should we find the DIRECT source directory? +#if $[DIRECT_SOURCE] + #define DIRECT_SOURCE $[unixfilename $[DIRECT_SOURCE]] +#elif $[or $[CTPROJS],$[DIRECT]] + // If we are presently attached, use the environment variable. + #define DIRECT_SOURCE $[unixfilename $[DIRECT]] + #if $[eq $[DIRECT],] + #error You seem to be attached to some trees, but not DIRECT! + #endif +#else + // Otherwise, if we are not attached, we guess that the source is a + // sibling directory to this source root. + #define DIRECT_SOURCE $[standardize $[TOPDIR]/../direct] +#endif + +// Where should we install OTP? +#if $[OTP_INSTALL] + #define OTP_INSTALL $[unixfilename $[OTP_INSTALL]] +#elif $[CTPROJS] + #set OTP $[unixfilename $[OTP]] + #define OTP_INSTALL $[OTP]/built + #if $[eq $[OTP],] + #error You seem to be attached to some trees, but not OTP! + #endif +#else + #defer OTP_INSTALL $[unixfilename $[INSTALL_DIR]] +#endif + + +// Also get the DIRECT Package file and everything that includes. +#if $[not $[isfile $[DIRECT_SOURCE]/Package.pp]] + #printvar DIRECT_SOURCE + #error DIRECT source directory not found from otp! Are you attached properly? +#endif + +#include $[DIRECT_SOURCE]/Package.pp + +// Define the inter-tree dependencies. +#define NEEDS_TREES direct $[NEEDS_TREES] +#define DEPENDABLE_HEADER_DIRS $[DEPENDABLE_HEADER_DIRS] $[DIRECT_INSTALL]/include diff --git a/otp/Sources.pp b/otp/Sources.pp new file mode 100644 index 0000000..a22276f --- /dev/null +++ b/otp/Sources.pp @@ -0,0 +1,10 @@ +// This is the toplevel directory for a package. + +#define DIR_TYPE toplevel + +#define REQUIRED_TREES dtool panda direct + +#define EXTRA_DIST \ + Sources.pp Config.pp Package.pp + +#define PYTHON_PACKAGE 1 diff --git a/otp/metalibs/Sources.pp b/otp/metalibs/Sources.pp new file mode 100644 index 0000000..93e28f9 --- /dev/null +++ b/otp/metalibs/Sources.pp @@ -0,0 +1,7 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group + +// The metalibs directory always depends on the src directory. +#define DEPENDS src diff --git a/otp/metalibs/otp/.cvsignore b/otp/metalibs/otp/.cvsignore new file mode 100644 index 0000000..7bda5c8 --- /dev/null +++ b/otp/metalibs/otp/.cvsignore @@ -0,0 +1,2 @@ +.cvsignore +Makefile diff --git a/otp/metalibs/otp/Sources.pp b/otp/metalibs/otp/Sources.pp new file mode 100644 index 0000000..bd506ea --- /dev/null +++ b/otp/metalibs/otp/Sources.pp @@ -0,0 +1,33 @@ +// DIR_TYPE "metalib" indicates we are building a shared library that +// consists mostly of references to other shared libraries. Under +// Windows, this directly produces a DLL (as opposed to the regular +// src libraries, which don't produce anything but a pile of OBJ files +// under Windows). + +#define DIR_TYPE metalib +#define BUILDING_DLL BUILDING_OTP + +#define COMPONENT_LIBS \ + otpbase settings nametag movement secure navigation + +#define OTHER_LIBS direct panda pandaexpress dtoolconfig dtool \ + express:c prc:c event:c pgraph:c pgraphnodes:c linmath:c gobj:c lerp:c \ + char:c putil:c mathutil:c downloader:c mathutil:c chan:c \ + pandabase:c recorder:c grutil:c chan:c collide:c device:c \ + dgraph:c display:c gsgbase:c parametrics:c text:c pnmimage:c \ + dtoolutil:c interrogatedb:c interval:c dtoolbase:c \ + dconfig:c pipeline:c pstatclient:c cull:c pnmimagetypes:c \ + tform:c audio:c pgui:c directbase:c dcparser:c showbase:c \ + deadrec:c distributed:c motiontrail:c movies:c \ + $[if $[HAVE_NET],net:c] $[if $[WANT_NATIVE_NET],nativenet:c] + +#if $[HAVE_FREETYPE] + #define OTHER_LIBS $[OTHER_LIBS] pnmtext:c + #endif + +#begin metalib_target + #define TARGET otp + + #define SOURCES otp.cxx +#end metalib_target + diff --git a/otp/metalibs/otp/otp.cxx b/otp/metalibs/otp/otp.cxx new file mode 100644 index 0000000..22c17b0 --- /dev/null +++ b/otp/metalibs/otp/otp.cxx @@ -0,0 +1,9 @@ +// Filename: otp.cxx +// Created by: drose (18May00) +// + + +// This is a dummy file whose sole purpose is to give the compiler +// something to compile when making libtoontown.so in NO_DEFER mode, +// which generates an empty library that itself links with all the +// other shared libraries that make up libtoontown. diff --git a/otp/src/.cvsignore b/otp/src/.cvsignore new file mode 100644 index 0000000..7bda5c8 --- /dev/null +++ b/otp/src/.cvsignore @@ -0,0 +1,2 @@ +.cvsignore +Makefile diff --git a/otp/src/Sources.pp b/otp/src/Sources.pp new file mode 100644 index 0000000..fb8681d --- /dev/null +++ b/otp/src/Sources.pp @@ -0,0 +1,4 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group diff --git a/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.inf b/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.inf new file mode 100644 index 0000000..25ee01a --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.inf @@ -0,0 +1,12 @@ +[Add.Code] +DisneyOnlineGames.ocx=DisneyOnlineGames.ocx +DisneyOnlineGames.inf=DisneyOnlineGames.inf + +[DisneyOnlineGames.ocx] +file=thiscab +clsid={3DCEC959-378A-4922-AD7E-FD5C925D927F} +RegisterServer=yes +FileVersion=7,6,6,1549 + +[DisneyOnlineGames.inf] +file=thiscab diff --git a/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.ocx b/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.ocx new file mode 100644 index 0000000..a93ca24 Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/built/DisneyOnlineGames.ocx differ diff --git a/otp/src/activex/DisneyOnlineGames/built/buildCAB.cmd b/otp/src/activex/DisneyOnlineGames/built/buildCAB.cmd new file mode 100644 index 0000000..3d44760 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/built/buildCAB.cmd @@ -0,0 +1,3 @@ +signcode -spc \\sesrccdfile01\Verisign_Credentials\WaltDisneyCompany\mycredentials.spc -v \\sesrccdfile01\Verisign_Credentials\WaltDisneyCompany\myprivatekey.pvk -n "Disney Online Games ActiveX Control" -t http://timestamp.verisign.com/scripts/timstamp.dll DisneyOnlineGames.ocx +cabarc -s 6144 n DisneyOnlineGames.cab DisneyOnlineGames.ocx DisneyOnlineGames.inf +signcode -spc \\sesrccdfile01\Verisign_Credentials\WaltDisneyCompany\mycredentials.spc -v \\sesrccdfile01\Verisign_Credentials\WaltDisneyCompany\myprivatekey.pvk -n "Disney Online Games ActiveX Control" -t http://timestamp.verisign.com/scripts/timstamp.dll DisneyOnlineGames.cab diff --git a/otp/src/activex/DisneyOnlineGames/built/signed/DisneyOnlineGames.cab b/otp/src/activex/DisneyOnlineGames/built/signed/DisneyOnlineGames.cab new file mode 100644 index 0000000..f2fee36 Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/built/signed/DisneyOnlineGames.cab differ diff --git a/otp/src/activex/DisneyOnlineGames/doc/Build-Instructions.doc b/otp/src/activex/DisneyOnlineGames/doc/Build-Instructions.doc new file mode 100644 index 0000000..96e66c7 Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/doc/Build-Instructions.doc differ diff --git a/otp/src/activex/DisneyOnlineGames/doc/DisneyOnlineGames-ActiveX.doc b/otp/src/activex/DisneyOnlineGames/doc/DisneyOnlineGames-ActiveX.doc new file mode 100644 index 0000000..4019012 Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/doc/DisneyOnlineGames-ActiveX.doc differ diff --git a/otp/src/activex/DisneyOnlineGames/doc/FlowDiagram.vsd b/otp/src/activex/DisneyOnlineGames/doc/FlowDiagram.vsd new file mode 100644 index 0000000..696c9e3 Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/doc/FlowDiagram.vsd differ diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.sln b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.sln new file mode 100644 index 0000000..5b57b46 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DisneyOnlineGames", "DisneyOnlineGames.Visual Studio 2003.vcproj", "{230D7CA9-17C1-4FF6-92E0-E620C7314D77}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Debug.ActiveCfg = Debug|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Debug.Build.0 = Debug|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Release.ActiveCfg = Release|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.vcproj b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.vcproj new file mode 100644 index 0000000..c8914d7 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2003.vcproj @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.sln b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.sln new file mode 100644 index 0000000..4f363bd --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DisneyOnlineGames", "DisneyOnlineGames.Visual Studio 2005.vcproj", "{230D7CA9-17C1-4FF6-92E0-E620C7314D77}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Debug|Win32.ActiveCfg = Debug|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Debug|Win32.Build.0 = Debug|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Release|Win32.ActiveCfg = Release|Win32 + {230D7CA9-17C1-4FF6-92E0-E620C7314D77}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.vcproj b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.vcproj new file mode 100644 index 0000000..e74a151 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.Visual Studio 2005.vcproj @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.cpp b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.cpp new file mode 100644 index 0000000..a7461c4 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.cpp @@ -0,0 +1,201 @@ +#include "stdafx.h" +#include "DisneyOnlineGames.h" +#include "comcat.h" +#include "strsafe.h" +#include "objsafe.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +// CLSID_SafeItem - Necessary for safe ActiveX control +// Taken from IMPLEMENT_OLECREATE_EX function in DisneyOnlineGamesCtrl.cpp + +const CATID CLSID_SafeItem = +{ 0x3dcec959, 0x378a, 0x4922,{ 0xad, 0x7e, 0xfd, 0x5c, 0x92, 0x5d, 0x92, 0x7f}}; + +HRESULT CreateComponentCategory(CATID catid, WCHAR *catDescription) +{ + ICatRegister *pcr = NULL ; + HRESULT hr = S_OK ; + + hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, + NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); + if (FAILED(hr)) + return hr; + + // Make sure the HKCR\Component Categories\{..catid...} + // key is registered. + CATEGORYINFO catinfo; + catinfo.catid = catid; + catinfo.lcid = 0x0409 ; // english + size_t len; + // Make sure the provided description is not too long. + // Only copy the first 127 characters if it is. + // The second parameter of StringCchLength is the maximum + // number of characters that may be read into catDescription. + // There must be room for a NULL-terminator. The third parameter + // contains the number of characters excluding the NULL-terminator. + hr = StringCchLengthW(catDescription, STRSAFE_MAX_CCH, &len); + if (SUCCEEDED(hr)) + { + if (len>127) + { + len = 127; + } + } + else + { + // TODO: Write an error handler; + } + // The second parameter of StringCchCopy is 128 because you need + // room for a NULL-terminator. + hr = StringCchCopyW(catinfo.szDescription, len + 1, catDescription); + // Make sure the description is null terminated. + catinfo.szDescription[len + 1] = '\0'; + + hr = pcr->RegisterCategories(1, &catinfo); + pcr->Release(); + + return hr; +} + +// HRESULT RegisterCLSIDInCategory - +// Register your component categories information + +HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid) +{ +// Register your component categories information. + ICatRegister *pcr = NULL ; + HRESULT hr = S_OK ; + hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, + NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); + if (SUCCEEDED(hr)) + { + // Register this category as being "implemented" by the class. + CATID rgcatid[1] ; + rgcatid[0] = catid; + hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid); + } + + if (pcr != NULL) + pcr->Release(); + + return hr; +} + +// HRESULT UnRegisterCLSIDInCategory - Remove entries from the registry + +HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid) +{ + ICatRegister *pcr = NULL ; + HRESULT hr = S_OK ; + + hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, + NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); + if (SUCCEEDED(hr)) + { + // Unregister this category as being "implemented" by the class. + CATID rgcatid[1] ; + rgcatid[0] = catid; + hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid); + } + + if (pcr != NULL) + pcr->Release(); + + return hr; +} + +CDisneyOnlineGamesApp theApp; + +const GUID CDECL BASED_CODE _tlid = + { 0x5F6C8F0A, 0x6FE5, 0x4546, { 0x82, 0xF1, 0x9B, 0x50, 0x37, 0x3A, 0x8E, 0xBD } }; +const WORD _wVerMajor = 3; +const WORD _wVerMinor = 1; + +BOOL CDisneyOnlineGamesApp::InitInstance() +// DLL initialization +{ + BOOL bInit = COleControlModule::InitInstance(); + if (bInit) + { + // TODO: Add your own module initialization code here. + } + return bInit; +} + +int CDisneyOnlineGamesApp::ExitInstance() +// DLL termination +{ + // TODO: Add your own module termination code here. + return COleControlModule::ExitInstance(); +} + +STDAPI DllRegisterServer(void) +// Adds entries to the system registry +{ + HRESULT hr; // HResult used by Safety Functions + + AFX_MANAGE_STATE(_afxModuleAddrThis); + + if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) + return ResultFromScode(SELFREG_E_TYPELIB); + + if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE)) + return ResultFromScode(SELFREG_E_CLASS); + + // Mark the control as safe for initializing. + + hr = CreateComponentCategory(CATID_SafeForInitializing, + L"Controls safely initializable from persistent data!"); + if (FAILED(hr)) + return hr; + + hr = RegisterCLSIDInCategory(CLSID_SafeItem, + CATID_SafeForInitializing); + if (FAILED(hr)) + return hr; + + // Mark the control as safe for scripting. + + hr = CreateComponentCategory(CATID_SafeForScripting, + L"Controls safely scriptable!"); + if (FAILED(hr)) + return hr; + + hr = RegisterCLSIDInCategory(CLSID_SafeItem, + CATID_SafeForScripting); + if (FAILED(hr)) + return hr; + + return NOERROR; +} + +STDAPI DllUnregisterServer(void) +// Removes entries from the system registry +{ + HRESULT hr; // HResult used by Safety Functions + + AFX_MANAGE_STATE(_afxModuleAddrThis); + + if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) + return ResultFromScode(SELFREG_E_TYPELIB); + + if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE)) + return ResultFromScode(SELFREG_E_CLASS); + + // Remove entries from the registry. + + hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, + CATID_SafeForInitializing); + if (FAILED(hr)) + return hr; + + hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, + CATID_SafeForScripting); + if (FAILED(hr)) + return hr; + + return NOERROR; +} diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.def b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.def new file mode 100644 index 0000000..d287d55 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.def @@ -0,0 +1,9 @@ +; DisneyOnlineGames.def : Declares the module parameters. + +LIBRARY "DisneyOnlineGames.OCX" + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.h b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.h new file mode 100644 index 0000000..92dae12 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.h @@ -0,0 +1,20 @@ +#pragma once + +#if !defined( __AFXCTL_H__ ) +#error "include 'afxctl.h' before including this file" +#endif + +#include "resource.h" // main symbols + + +class CDisneyOnlineGamesApp : public COleControlModule +{ +public: + BOOL InitInstance(); + int ExitInstance(); +}; + +extern const GUID CDECL _tlid; +extern const WORD _wVerMajor; +extern const WORD _wVerMinor; + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.idl b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.idl new file mode 100644 index 0000000..22126b6 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.idl @@ -0,0 +1,55 @@ +// DisneyOnlineGames.idl : type library source for ActiveX Control project. + +// This file will be processed by the MIDL compiler tool to +// produce the type library (DisneyOnlineGames.tlb) that will become a resource in +// DisneyOnlineGames.ocx. + +#include +#include + +[ uuid(5F6C8F0A-6FE5-4546-82F1-9B50373A8EBD), version(1.0), + helpfile("DisneyOnlineGames.hlp"), + helpstring("Disney Online Games ActiveX Control module"), + control ] +library DisneyOnlineGamesLib +{ + importlib(STDOLE_TLB); + + // Primary dispatch interface for CDisneyOnlineGamesCtrl + + [ uuid(33BDF503-F6F7-456C-B3C9-F74F294C8EB7), + helpstring("Dispatch interface for Disney Online Games Control")] + dispinterface _DDisneyOnlineGames + { + properties: + [id(2), helpstring("property ModeId")] BSTR ModeId; + [id(9) , helpstring("property ResponseCode")] ULONG ResponseCode; + [id(10) , helpstring("property Token")] BSTR Token; +methods: + [id(5), helpstring("method runPiratesOnline")] void runPiratesOnline(void); + }; + + // Event dispatch interface for CDisneyOnlineGamesCtrl + + [ uuid(CAC95DCF-C37B-4173-901A-ED2D6EDC5177), + helpstring("Event interface for Disney Online Games Control") ] + dispinterface _DDisneyOnlineGamesEvents + { + properties: + // Event interface has no properties + + methods: + [id(1)] void onRunPiratesOnlineComplete(void); + }; + + // Class information for CDisneyOnlineGamesCtrl + + [ uuid(3DCEC959-378A-4922-AD7E-FD5C925D927F), + helpstring("Disney Online Games Control"), control ] + coclass DisneyOnlineGames + { + [default] dispinterface _DDisneyOnlineGames; + [default, source] dispinterface _DDisneyOnlineGamesEvents; + }; + +}; diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.rc b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.rc new file mode 100644 index 0000000..6973cd2 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames.rc @@ -0,0 +1,159 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "1 TYPELIB ""DisneyOnlineGames.tlb""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 7,6,6,1549 + PRODUCTVERSION 3,0,1,5 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "Walt Disney Co." + VALUE "FileDescription", "Disney Online Games ActiveX Control" + VALUE "FileVersion", "7, 6, 6, 1549" + VALUE "InternalName", "DisneyOnlineGames.ocx" + VALUE "LegalCopyright", "Copyright 2007 Walt Disney Co." + VALUE "OriginalFilename", "DisneyOnlineGames.ocx" + VALUE "ProductName", "DisneyOnlineGames" + VALUE "ProductVersion", "3, 0, 1, 5" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_DISNEYONLINEGAMES BITMAP "DisneyOnlineGamesCtrl.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROPPAGE_DISNEYONLINEGAMES DIALOG 0, 0, 250, 62 +STYLE DS_SETFONT | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "TODO: Place controls to manipulate properties of DisneyOnlineGames Control on this dialog.", + IDC_STATIC,7,25,229,16 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROPPAGE_DISNEYONLINEGAMES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_DISNEYONLINEGAMES "Disney Online Games ActiveX Control" + IDS_DISNEYONLINEGAMES_PPG "Disney Online Games ActiveX Property Page" +END + +STRINGTABLE +BEGIN + IDS_DISNEYONLINEGAMES_PPG_CAPTION "General" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +1 TYPELIB "DisneyOnlineGames.tlb" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.bmp b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.bmp new file mode 100644 index 0000000..627f93d Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.bmp differ diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.cpp b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.cpp new file mode 100644 index 0000000..84c7699 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.cpp @@ -0,0 +1,236 @@ +#include "stdafx.h" +#include "DisneyOnlineGames.h" +#include "DisneyOnlineGamesCtrl.h" +#include "DisneyOnlineGamesPropPage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +IMPLEMENT_DYNCREATE(CDisneyOnlineGamesCtrl, COleControl) + +// Message map + +BEGIN_MESSAGE_MAP(CDisneyOnlineGamesCtrl, COleControl) + ON_OLEVERB(AFX_IDS_VERB_EDIT, OnEdit) + ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties) +END_MESSAGE_MAP() + +// Dispatch map + +BEGIN_DISPATCH_MAP(CDisneyOnlineGamesCtrl, COleControl) + DISP_PROPERTY_NOTIFY_ID(CDisneyOnlineGamesCtrl, "ModeId", dispidModeId, m_ModeId, OnModeIdChanged, VT_BSTR) + DISP_FUNCTION_ID(CDisneyOnlineGamesCtrl, "runPiratesOnline", dispidrunPiratesOnline, exportedmethodrunPiratesOnline, VT_EMPTY, VTS_NONE) + DISP_PROPERTY_NOTIFY_ID(CDisneyOnlineGamesCtrl, "ResponseCode", dispidResponseCode, m_ResponseCode, OnResponseCodeChanged, VT_UI4) + DISP_PROPERTY_NOTIFY_ID(CDisneyOnlineGamesCtrl, "Token", dispidToken, m_Token, OnTokenChanged, VT_BSTR) +END_DISPATCH_MAP() + +// Event map + +BEGIN_EVENT_MAP(CDisneyOnlineGamesCtrl, COleControl) + EVENT_CUSTOM_ID("onRunPiratesOnlineComplete", eventidonRunPiratesOnlineComplete, Fire_onRunPiratesOnlineComplete, VTS_NONE) +END_EVENT_MAP() + +// Property pages + +// TODO: Add more property pages as needed. Remember to increase the count! +BEGIN_PROPPAGEIDS(CDisneyOnlineGamesCtrl, 1) + PROPPAGEID(CDisneyOnlineGamesPropPage::guid) +END_PROPPAGEIDS(CDisneyOnlineGamesCtrl) + +// Initialize class factory and guid + +IMPLEMENT_OLECREATE_EX(CDisneyOnlineGamesCtrl, "DISNEYONLINEGAMES.DisneyOnlineGamesCtrl.1", + 0x3dcec959, 0x378a, 0x4922, 0xad, 0x7e, 0xfd, 0x5c, 0x92, 0x5d, 0x92, 0x7f) + +// Type library ID and version + +IMPLEMENT_OLETYPELIB(CDisneyOnlineGamesCtrl, _tlid, _wVerMajor, _wVerMinor) + +// Interface IDs + +const IID BASED_CODE IID_DDisneyOnlineGames = + { 0x33BDF503, 0xF6F7, 0x456C, { 0xB3, 0xC9, 0xF7, 0x4F, 0x29, 0x4C, 0x8E, 0xB7 } }; +const IID BASED_CODE IID_DDisneyOnlineGamesEvents = + { 0xCAC95DCF, 0xC37B, 0x4173, { 0x90, 0x1A, 0xED, 0x2D, 0x6E, 0xDC, 0x51, 0x77 } }; + +// Control type information + +static const DWORD BASED_CODE _dwDisneyOnlineGamesOleMisc = + OLEMISC_ALWAYSRUN | + OLEMISC_SETCLIENTSITEFIRST | + OLEMISC_INSIDEOUT | + OLEMISC_CANTLINKINSIDE | + OLEMISC_ACTIVATEWHENVISIBLE + ; + +IMPLEMENT_OLECTLTYPE(CDisneyOnlineGamesCtrl, IDS_DISNEYONLINEGAMES, _dwDisneyOnlineGamesOleMisc) + +// CDisneyOnlineGamesCtrl::CDisneyOnlineGamesCtrlFactory::UpdateRegistry - +// Adds or removes system registry entries for CDisneyOnlineGamesCtrl + +BOOL CDisneyOnlineGamesCtrl::CDisneyOnlineGamesCtrlFactory::UpdateRegistry(BOOL bRegister) +{ + // TODO: Verify that your control follows apartment-model threading rules. + // Refer to MFC TechNote 64 for more information. + // If your control does not conform to the apartment-model rules, then + // you must modify the code below, changing the 6th parameter from + // afxRegInsertable | afxRegApartmentThreading to afxRegInsertable. + + if (bRegister) + return AfxOleRegisterControlClass( + AfxGetInstanceHandle(), + m_clsid, + m_lpszProgID, + IDS_DISNEYONLINEGAMES, + IDB_DISNEYONLINEGAMES, + afxRegInsertable | afxRegApartmentThreading | afxRegFreeThreading, + _dwDisneyOnlineGamesOleMisc, + _tlid, + _wVerMajor, + _wVerMinor); + else + return AfxOleUnregisterClass(m_clsid, m_lpszProgID); +} + +// CDisneyOnlineGamesCtrl::CDisneyOnlineGamesCtrl - Constructor + +CDisneyOnlineGamesCtrl::CDisneyOnlineGamesCtrl() +{ + InitializeIIDs(&IID_DDisneyOnlineGames, &IID_DDisneyOnlineGamesEvents); +} + +// CDisneyOnlineGamesCtrl::~CDisneyOnlineGamesCtrl - Destructor + +CDisneyOnlineGamesCtrl::~CDisneyOnlineGamesCtrl() +{ + // TODO: Cleanup your control's instance data here. +} + +// CDisneyOnlineGamesCtrl::OnDraw - Drawing function + +void CDisneyOnlineGamesCtrl::OnDraw( + CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) +{ + if (!pdc) + return; +} + +// CDisneyOnlineGamesCtrl::DoPropExchange - Persistence support + +void CDisneyOnlineGamesCtrl::DoPropExchange(CPropExchange* pPX) +{ + ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)); + COleControl::DoPropExchange(pPX); + + // TODO: Call PX_ functions for each persistent custom property. +} + +// CDisneyOnlineGamesCtrl::GetControlFlags - +// Flags to customize MFC's implementation of ActiveX controls. +// +DWORD CDisneyOnlineGamesCtrl::GetControlFlags() +{ + DWORD dwFlags = COleControl::GetControlFlags(); + return dwFlags; +} + +// CDisneyOnlineGamesCtrl::OnResetState - Reset control to default state + +void CDisneyOnlineGamesCtrl::OnResetState() +{ + COleControl::OnResetState(); // Resets defaults found in DoPropExchange + + // TODO: Reset any other control state here. +} + +// CDisneyOnlineGamesCtrl message handlers + +void CDisneyOnlineGamesCtrl::OnModeIdChanged(void) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + // TODO: Add your property handler code here + + SetModifiedFlag(); +} + +void CDisneyOnlineGamesCtrl::exportedmethodrunPiratesOnline (void) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + +#if defined(_DEBUG_TOKEN_VALUE_) + { + // If the request to obtain the path to temporary directory failed + char szTempPath [MAX_PATH]; + if (GetTempPath (MAX_PATH, szTempPath)) + { + // If the request to obtain a unique temporary filename failed + char szInstallerFullPathname [MAX_PATH]; + if (GetTempFileName (szTempPath, NULL, 0, szInstallerFullPathname)) + { + // If the temporary file was not successfully opened for writing + HANDLE hFile; + if (INVALID_HANDLE_VALUE != (hFile = CreateFile ( + szInstallerFullPathname, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ))) + { + char szPrefix [] = "m_Token=\""; + char szSuffix [] = "\""; + char * pszBuf = (char *)malloc (lstrlen (szPrefix) + lstrlen (m_Token) + lstrlen (szSuffix) + 1); + strcpy (pszBuf, szPrefix); + strcat (pszBuf, m_Token); + strcat (pszBuf, szSuffix); + // + DWORD dwNumberOfBytesWritten; + WriteFile (hFile, pszBuf, lstrlen (pszBuf), &dwNumberOfBytesWritten, NULL); + // + CloseHandle (hFile); + } + } + } + } +#endif + + CRunPiratesOnline * pRunPiratesOnline = new CRunPiratesOnline (); + m_ResponseCode = pRunPiratesOnline->Run (atoi (m_ModeId), m_Token); + delete pRunPiratesOnline; + + // If our attempt to run Pirates Online was successful + if (RESPONSE_CODE__SUCCESS == m_ResponseCode) + { + CTopLevelWindowIterator itlw (GetCurrentProcessId ()); + for (HWND hWndTopLevelWindow = itlw.First(); hWndTopLevelWindow; hWndTopLevelWindow = itlw.Next()) + { + // Minimize the top level window that was created by this process + ::ShowWindow (hWndTopLevelWindow, SW_MINIMIZE); + } + } + + // Fire response to webpage + Fire_onRunPiratesOnlineComplete (); +} + +void CDisneyOnlineGamesCtrl::OnResponseCodeChanged(void) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + // TODO: Add your property handler code here + + SetModifiedFlag(); +} + +void CDisneyOnlineGamesCtrl::OnTokenChanged(void) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + // TODO: Add your property handler code here + + SetModifiedFlag(); +} diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.h b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.h new file mode 100644 index 0000000..1261b4c --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesCtrl.h @@ -0,0 +1,58 @@ +#pragma once + +class CDisneyOnlineGamesCtrl : public COleControl +{ + DECLARE_DYNCREATE(CDisneyOnlineGamesCtrl) + +// Constructor +public: + CDisneyOnlineGamesCtrl(); + +// Overrides +public: + virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid); + virtual void DoPropExchange(CPropExchange* pPX); + virtual void OnResetState(); + virtual DWORD GetControlFlags(); + +// Implementation +protected: + ~CDisneyOnlineGamesCtrl(); + + DECLARE_OLECREATE_EX(CDisneyOnlineGamesCtrl) // Class factory and guid + DECLARE_OLETYPELIB(CDisneyOnlineGamesCtrl) // GetTypeInfo + DECLARE_PROPPAGEIDS(CDisneyOnlineGamesCtrl) // Property page IDs + DECLARE_OLECTLTYPE(CDisneyOnlineGamesCtrl) // Type name and misc status + +// Message maps + DECLARE_MESSAGE_MAP() + +// Dispatch maps + DECLARE_DISPATCH_MAP() + +// Event maps + DECLARE_EVENT_MAP() + +// Dispatch and event IDs +public: + enum { + dispidToken = 10, + eventidonRunPiratesOnlineComplete = 1L, + dispidResponseCode = 9, + dispidrunPiratesOnline = 5L, + dispidModeId = 2, + }; +protected: + void OnModeIdChanged(void); + CString m_ModeId; + void exportedmethodrunPiratesOnline(void); + void OnResponseCodeChanged(void); + ULONG m_ResponseCode; + void OnTokenChanged(void); + CString m_Token; + + void Fire_onRunPiratesOnlineComplete(void) + { + FireEvent(eventidonRunPiratesOnlineComplete, EVENT_PARAM(VTS_NONE)); + } +}; diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.cpp b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.cpp new file mode 100644 index 0000000..702d817 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.cpp @@ -0,0 +1,60 @@ +#include "stdafx.h" +#include "DisneyOnlineGames.h" +#include "DisneyOnlineGamesPropPage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + + +IMPLEMENT_DYNCREATE(CDisneyOnlineGamesPropPage, COlePropertyPage) + + + +// Message map + +BEGIN_MESSAGE_MAP(CDisneyOnlineGamesPropPage, COlePropertyPage) +END_MESSAGE_MAP() + + + +// Initialize class factory and guid + +IMPLEMENT_OLECREATE_EX(CDisneyOnlineGamesPropPage, "DISNEYONLINEGAMES.DisneyOnlineGamesPropPage.1", + 0xe314aa7, 0x9a00, 0x4db3, 0xb0, 0x23, 0x7a, 0xda, 0x8d, 0xdf, 0x7d, 0x26) + + + +// CDisneyOnlineGamesPropPage::CDisneyOnlineGamesPropPageFactory::UpdateRegistry - +// Adds or removes system registry entries for CDisneyOnlineGamesPropPage + +BOOL CDisneyOnlineGamesPropPage::CDisneyOnlineGamesPropPageFactory::UpdateRegistry(BOOL bRegister) +{ + if (bRegister) + return AfxOleRegisterPropertyPageClass(AfxGetInstanceHandle(), + m_clsid, IDS_DISNEYONLINEGAMES_PPG); + else + return AfxOleUnregisterClass(m_clsid, NULL); +} + + + +// CDisneyOnlineGamesPropPage::CDisneyOnlineGamesPropPage - Constructor + +CDisneyOnlineGamesPropPage::CDisneyOnlineGamesPropPage() : + COlePropertyPage(IDD, IDS_DISNEYONLINEGAMES_PPG_CAPTION) +{ +} + + + +// CDisneyOnlineGamesPropPage::DoDataExchange - Moves data between page and properties + +void CDisneyOnlineGamesPropPage::DoDataExchange(CDataExchange* pDX) +{ + DDP_PostProcessing(pDX); +} + + + +// CDisneyOnlineGamesPropPage message handlers diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.h b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.h new file mode 100644 index 0000000..5275917 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesPropPage.h @@ -0,0 +1,23 @@ +#pragma once + +class CDisneyOnlineGamesPropPage : public COlePropertyPage +{ + DECLARE_DYNCREATE(CDisneyOnlineGamesPropPage) + DECLARE_OLECREATE_EX(CDisneyOnlineGamesPropPage) + +// Constructor +public: + CDisneyOnlineGamesPropPage(); + +// Dialog Data + enum { IDD = IDD_PROPPAGE_DISNEYONLINEGAMES }; + +// Implementation +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + +// Message maps +protected: + DECLARE_MESSAGE_MAP() +}; + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames_i.c b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames_i.c new file mode 100644 index 0000000..4bde7e6 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGames_i.c @@ -0,0 +1,93 @@ + + +/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ + +/* link this file in with the server and any clients */ + + + /* File created by MIDL compiler version 6.00.0361 */ +/* at Wed Jun 06 15:59:25 2007 + */ +/* Compiler settings for .\DisneyOnlineGames.idl: + Oicf, W1, Zp8, env=Win32 (32b run) + protocol : dce , ms_ext, c_ext, robust + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +//@@MIDL_FILE_HEADING( ) + +#if !defined(_M_IA64) && !defined(_M_AMD64) + + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + + +#ifdef __cplusplus +extern "C"{ +#endif + + +#include +#include + +#ifdef _MIDL_USE_GUIDDEF_ + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) + +#else // !_MIDL_USE_GUIDDEF_ + +#ifndef __IID_DEFINED__ +#define __IID_DEFINED__ + +typedef struct _IID +{ + unsigned long x; + unsigned short s1; + unsigned short s2; + unsigned char c[8]; +} IID; + +#endif // __IID_DEFINED__ + +#ifndef CLSID_DEFINED +#define CLSID_DEFINED +typedef IID CLSID; +#endif // CLSID_DEFINED + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +#endif !_MIDL_USE_GUIDDEF_ + +MIDL_DEFINE_GUID(IID, LIBID_DisneyOnlineGamesLib,0x5F6C8F0A,0x6FE5,0x4546,0x82,0xF1,0x9B,0x50,0x37,0x3A,0x8E,0xBD); + + +MIDL_DEFINE_GUID(IID, DIID__DDisneyOnlineGames,0x33BDF503,0xF6F7,0x456C,0xB3,0xC9,0xF7,0x4F,0x29,0x4C,0x8E,0xB7); + + +MIDL_DEFINE_GUID(IID, DIID__DDisneyOnlineGamesEvents,0xCAC95DCF,0xC37B,0x4173,0x90,0x1A,0xED,0x2D,0x6E,0xDC,0x51,0x77); + + +MIDL_DEFINE_GUID(CLSID, CLSID_DisneyOnlineGames,0x3DCEC959,0x378A,0x4922,0xAD,0x7E,0xFD,0x5C,0x92,0x5D,0x92,0x7F); + +#undef MIDL_DEFINE_GUID + +#ifdef __cplusplus +} +#endif + + + +#endif /* !defined(_M_IA64) && !defined(_M_AMD64)*/ + diff --git a/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesidl.h b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesidl.h new file mode 100644 index 0000000..9832754 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/DisneyOnlineGamesidl.h @@ -0,0 +1,316 @@ + + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + + /* File created by MIDL compiler version 6.00.0361 */ +/* at Wed Jun 06 15:59:25 2007 + */ +/* Compiler settings for .\DisneyOnlineGames.idl: + Oicf, W1, Zp8, env=Win32 (32b run) + protocol : dce , ms_ext, c_ext, robust + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +//@@MIDL_FILE_HEADING( ) + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 475 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif // __RPCNDR_H_VERSION__ + + +#ifndef __DisneyOnlineGamesidl_h__ +#define __DisneyOnlineGamesidl_h__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +/* Forward Declarations */ + +#ifndef ___DDisneyOnlineGames_FWD_DEFINED__ +#define ___DDisneyOnlineGames_FWD_DEFINED__ +typedef interface _DDisneyOnlineGames _DDisneyOnlineGames; +#endif /* ___DDisneyOnlineGames_FWD_DEFINED__ */ + + +#ifndef ___DDisneyOnlineGamesEvents_FWD_DEFINED__ +#define ___DDisneyOnlineGamesEvents_FWD_DEFINED__ +typedef interface _DDisneyOnlineGamesEvents _DDisneyOnlineGamesEvents; +#endif /* ___DDisneyOnlineGamesEvents_FWD_DEFINED__ */ + + +#ifndef __DisneyOnlineGames_FWD_DEFINED__ +#define __DisneyOnlineGames_FWD_DEFINED__ + +#ifdef __cplusplus +typedef class DisneyOnlineGames DisneyOnlineGames; +#else +typedef struct DisneyOnlineGames DisneyOnlineGames; +#endif /* __cplusplus */ + +#endif /* __DisneyOnlineGames_FWD_DEFINED__ */ + + +#ifdef __cplusplus +extern "C"{ +#endif + +void * __RPC_USER MIDL_user_allocate(size_t); +void __RPC_USER MIDL_user_free( void * ); + + +#ifndef __DisneyOnlineGamesLib_LIBRARY_DEFINED__ +#define __DisneyOnlineGamesLib_LIBRARY_DEFINED__ + +/* library DisneyOnlineGamesLib */ +/* [control][helpstring][helpfile][version][uuid] */ + + +EXTERN_C const IID LIBID_DisneyOnlineGamesLib; + +#ifndef ___DDisneyOnlineGames_DISPINTERFACE_DEFINED__ +#define ___DDisneyOnlineGames_DISPINTERFACE_DEFINED__ + +/* dispinterface _DDisneyOnlineGames */ +/* [helpstring][uuid] */ + + +EXTERN_C const IID DIID__DDisneyOnlineGames; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("33BDF503-F6F7-456C-B3C9-F74F294C8EB7") + _DDisneyOnlineGames : public IDispatch + { + }; + +#else /* C style interface */ + + typedef struct _DDisneyOnlineGamesVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + _DDisneyOnlineGames * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + _DDisneyOnlineGames * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + _DDisneyOnlineGames * This); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( + _DDisneyOnlineGames * This, + /* [out] */ UINT *pctinfo); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( + _DDisneyOnlineGames * This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo **ppTInfo); + + HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( + _DDisneyOnlineGames * This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR *rgszNames, + /* [in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID *rgDispId); + + /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( + _DDisneyOnlineGames * This, + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS *pDispParams, + /* [out] */ VARIANT *pVarResult, + /* [out] */ EXCEPINFO *pExcepInfo, + /* [out] */ UINT *puArgErr); + + END_INTERFACE + } _DDisneyOnlineGamesVtbl; + + interface _DDisneyOnlineGames + { + CONST_VTBL struct _DDisneyOnlineGamesVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define _DDisneyOnlineGames_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define _DDisneyOnlineGames_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define _DDisneyOnlineGames_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define _DDisneyOnlineGames_GetTypeInfoCount(This,pctinfo) \ + (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) + +#define _DDisneyOnlineGames_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ + (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) + +#define _DDisneyOnlineGames_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ + (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) + +#define _DDisneyOnlineGames_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ + (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + +#endif /* ___DDisneyOnlineGames_DISPINTERFACE_DEFINED__ */ + + +#ifndef ___DDisneyOnlineGamesEvents_DISPINTERFACE_DEFINED__ +#define ___DDisneyOnlineGamesEvents_DISPINTERFACE_DEFINED__ + +/* dispinterface _DDisneyOnlineGamesEvents */ +/* [helpstring][uuid] */ + + +EXTERN_C const IID DIID__DDisneyOnlineGamesEvents; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("CAC95DCF-C37B-4173-901A-ED2D6EDC5177") + _DDisneyOnlineGamesEvents : public IDispatch + { + }; + +#else /* C style interface */ + + typedef struct _DDisneyOnlineGamesEventsVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + _DDisneyOnlineGamesEvents * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + _DDisneyOnlineGamesEvents * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + _DDisneyOnlineGamesEvents * This); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( + _DDisneyOnlineGamesEvents * This, + /* [out] */ UINT *pctinfo); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( + _DDisneyOnlineGamesEvents * This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo **ppTInfo); + + HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( + _DDisneyOnlineGamesEvents * This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR *rgszNames, + /* [in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID *rgDispId); + + /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( + _DDisneyOnlineGamesEvents * This, + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS *pDispParams, + /* [out] */ VARIANT *pVarResult, + /* [out] */ EXCEPINFO *pExcepInfo, + /* [out] */ UINT *puArgErr); + + END_INTERFACE + } _DDisneyOnlineGamesEventsVtbl; + + interface _DDisneyOnlineGamesEvents + { + CONST_VTBL struct _DDisneyOnlineGamesEventsVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define _DDisneyOnlineGamesEvents_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define _DDisneyOnlineGamesEvents_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define _DDisneyOnlineGamesEvents_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define _DDisneyOnlineGamesEvents_GetTypeInfoCount(This,pctinfo) \ + (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) + +#define _DDisneyOnlineGamesEvents_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ + (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) + +#define _DDisneyOnlineGamesEvents_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ + (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) + +#define _DDisneyOnlineGamesEvents_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ + (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + +#endif /* ___DDisneyOnlineGamesEvents_DISPINTERFACE_DEFINED__ */ + + +EXTERN_C const CLSID CLSID_DisneyOnlineGames; + +#ifdef __cplusplus + +class DECLSPEC_UUID("3DCEC959-378A-4922-AD7E-FD5C925D927F") +DisneyOnlineGames; +#endif +#endif /* __DisneyOnlineGamesLib_LIBRARY_DEFINED__ */ + +/* Additional Prototypes for ALL interfaces */ + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/otp/src/activex/DisneyOnlineGames/src/EnvBlock.cpp b/otp/src/activex/DisneyOnlineGames/src/EnvBlock.cpp new file mode 100644 index 0000000..afbdcf5 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/EnvBlock.cpp @@ -0,0 +1,94 @@ +#include "stdafx.h" + +void CEnvBlock::insert_env_block_key_value_pair (CString & strEnvForChildProcess, + int * pInsertIdx, + char * lpszVariable) +{ + // Insert the next environment block key=value pair, accounting for the following facts: + // 1. There must be a terminating NULL byte ('\0') between each key=value pair + // 2. Two NULL bytes indicate environment block end + //--------------------------------------------------------------------------------------------- + // In other words, an environment block consists of a null-terminated block of null-terminated + // strings (meaning there are two null bytes at the end of the block), where each string is of + // the form + // key=value + //--------------------------------------------------------------------------------------------- + strEnvForChildProcess.Insert (*pInsertIdx, lpszVariable); + (*pInsertIdx) += (int)strlen (lpszVariable); + (*pInsertIdx)++; + strEnvForChildProcess.GetBufferSetLength (*pInsertIdx); +} + +void CEnvBlock::Create (const char * pszEnvKeyToAddToChildProcessEnvBlock, + CString & strEnvForChildProcess) +{ + // Set environment key=value pair that the executable will look for + static const char szEnvKeyValuePairSeparator [] = "="; + CString strEnvKeyValuePairToAddToChildProcessEnvBlock; + strEnvKeyValuePairToAddToChildProcessEnvBlock.Format ( + "%s%s%s", + pszEnvKeyToAddToChildProcessEnvBlock, + szEnvKeyValuePairSeparator, + m.pszToken); + // Ensure the string to hold the environment block for the child process is empty + strEnvForChildProcess.GetBufferSetLength (0); + // Initilize the child process environment block insert index + int InsertIdx = 0; + // Indicate that the key=value pair that the executable will look for has NOT yet been inserted + bool fAddedEnvKeyToChildProcessEnvBlock = false; + // If a pointer to the environment block for this process was returned + LPVOID lpvEnv; + if (lpvEnv = GetEnvironmentStrings ()) + { + // Variable strings are separated by NULL byte, and the block is terminated by a NULL byte + LPTSTR lpszVariable; + for (lpszVariable = (LPTSTR)lpvEnv; *lpszVariable; lpszVariable += lstrlen (lpszVariable) + 1) + { + // If the key=value pair that the executable will look for has NOT yet been inserted + if (!fAddedEnvKeyToChildProcessEnvBlock) + { + // If the current key=value pair from this process' environment block is greater + // than the key=value pair that the executable will look for + //--------------------------------------------------------------------------------- + // NOTE: All strings in the environment block must be sorted alphabetically by name. + // The sort is case-insensitive, Unicode order, without regard to locale. + // Because the equal sign is a separator, it must not be used in the name of + // an environment variable. + //--------------------------------------------------------------------------------- + if (0 > strEnvKeyValuePairToAddToChildProcessEnvBlock.CompareNoCase (lpszVariable)) + { + // Indicate that the key=value pair that the executable will look for has been + // inserted into the environment block for the child process + fAddedEnvKeyToChildProcessEnvBlock = true; + // Insert the next environment block key=value pair + insert_env_block_key_value_pair ( + strEnvForChildProcess, + &InsertIdx, + strEnvKeyValuePairToAddToChildProcessEnvBlock.GetBuffer ()); + } + } + // Insert the next environment block key=value pair + insert_env_block_key_value_pair ( + strEnvForChildProcess, + &InsertIdx, + lpszVariable); + } + // Free the environment block + FreeEnvironmentStrings ((LPTCH)lpvEnv); + } + // If the key=value pair that the executable will look for has NOT yet been inserted + if (!fAddedEnvKeyToChildProcessEnvBlock) + { + // Insert the next environment block key=value pair + insert_env_block_key_value_pair ( + strEnvForChildProcess, + &InsertIdx, + strEnvKeyValuePairToAddToChildProcessEnvBlock.GetBuffer ()); + } + // Insert the next environment block key=value pair + insert_env_block_key_value_pair ( + strEnvForChildProcess, + &InsertIdx, + "\0"); +} + diff --git a/otp/src/activex/DisneyOnlineGames/src/EnvBlock.h b/otp/src/activex/DisneyOnlineGames/src/EnvBlock.h new file mode 100644 index 0000000..1d2121a --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/EnvBlock.h @@ -0,0 +1,23 @@ +class CEnvBlock +{ +public: + CEnvBlock (const char * pszToken) + { + memset (&m, 0, sizeof (m)); + m.pszToken = pszToken; + } + ~CEnvBlock (void) + { + } + void Create (const char * pszEnvKeyToAddToChildProcessEnvBlock, + CString & strEnvForChildProcess); +private: + struct + { + const char * pszToken; + } m; +private: + void insert_env_block_key_value_pair (CString & strEnvForChildProcess, + int * pInsertIdx, + char * lpszVariable); +}; diff --git a/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.cpp b/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.cpp new file mode 100644 index 0000000..4c04326 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.cpp @@ -0,0 +1,877 @@ +#include "stdafx.h" + +static const char szEnvKeyToAddToChildProcessEnvBlock [] = "DisneyOnlineGamesToken"; +static const char szInstallerCommandLineOptionPrefix [] = "/"; +static const char szInstallerSilentRunModeOption [] = "S"; + +typedef struct _tag_MMOG_FLAVOR +{ + const int mode; + const char * pszInstallerURL; + const char * pszLauncherCSIDL; + const char * pszLauncherPathname; +} MMOG_FLAVOR, *PMMOG_FLAVOR; + +static const MMOG_FLAVOR gsc_mmogFlavor [] = { + { + // Development + 1, + "http://build64.online.disney.com:3120/english/currentVersion/dev/PotC-setup_DEV.exe", + "PROGRAM_FILES", + "\\Disney\\Disney Online\\PiratesOnline_DEV\\Launcher1.exe", + } + , + { // QA + 2, + "http://pirate143b.starwave.com:1420/english/currentVersion/qa/PotC-setup_QA.exe", + "PROGRAM_FILES", + "\\Disney\\Disney Online\\PiratesOnline_QA\\Launcher1.exe", + } + , + { // Test + 3, + "http://download.test.piratesonline.com/english/currentVersion/PotC-setup_TEST.exe", + "PROGRAM_FILES", + "\\Disney\\Disney Online\\PiratesOnline_TEST\\Launcher1.exe", + } + , + { // Live + 4, + "http://download.piratesonline.com/english/currentVersion/PotC-setup.exe", + "PROGRAM_FILES", + "\\Disney\\Disney Online\\PiratesOnline\\Launcher1.exe", + } +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const char c_szFolderNameDelimiter_DOS [] = "\\"; +static const char c_chFolderNameDelimiter_UNIX = '/'; + +#if defined(_DEBUG) +#define ODS(s) { printf (s); printf ("\r\n"); } +#endif + +static char *g_paszAcceptTypes [] = { "*/*", NULL }; + +#if defined(_DEBUG) +PSZ CRunPiratesOnline::get_http_specific_error_description (DWORD dwError) +{ + switch (dwError) + { + case 12001: + return "Out of handles"; + case 12002: + return "Timeout"; + case 12004: + return "Internal Error"; + case 12005: + return "Invalid URL"; + case 12007: + return "Service Name Not Resolved"; + case 12008: + return "Protocol Not Found"; + case 12013: + return "Incorrect User Name"; + case 12014: + return "Incorrect Password"; + case 12015: + return "Login Failure"; + case 12016: + return "Invalid Operation"; + case 12017: + return "Operation Canceled"; + case 12020: + return "Not Proxy Request"; + case 12023: + return "No Direct Access"; + case 12026: + return "Request Pending"; + case 12027: + return "Incorrect Format"; + case 12028: + return "Item not found"; + case 12029: + return "Cannot connect"; + case 12030: + return "Connection Aborted"; + case 12031: + return "Connection Reset"; + case 12033: + return "Invalid Proxy Request"; + case 12034: + return "Need UI"; + case 12035: + return "Sec Cert Date Invalid"; + case 12038: + return "Sec Cert CN Invalid"; + case 12044: + return "Client Auth Cert Needed"; + case 12045: + return "Invalid CA Cert"; + case 12046: + return "Client Auth Not Setup"; + case 12150: + return "HTTP Header Not Found"; + case 12152: + return "Invalid HTTP Server Response"; + case 12153: + return "Invalid HTTP Header"; + case 120154: + return "Invalid Query Request"; + case 120156: + return "Redirect Failed"; + case 120159: + return "TCP/IP not installed"; + default: + return "Error"; + } +} +#endif + +ENUM_RESPONSE_CODE CRunPiratesOnline::response_data_available_and_successfully_read (HINTERNET hRequest, + char * pszHttpResponseData, + DWORD dwNumHttpResponseDataBufferBytes, + DWORD * pdwNumBytesResponseDataRead) +{ + // If we can NOT query the server to determine the amount of data available + DWORD dwNumberOfBytesAvailableToBeRead = 0; + if (!InternetQueryDataAvailable ( + hRequest, + &dwNumberOfBytesAvailableToBeRead, + 0, + 0)) + { +#if defined(_DEBUG) + { + DWORD dwLastError; + dwLastError = GetLastError (); + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\t%s: %u=%s", + __FUNCTION__, + "ERROR: InternetQueryDataAvailable", + dwLastError, + get_http_specific_error_description (dwLastError)); + ODS(szMsgBuf) + } +#endif + return RESPONSE_CODE__CANNOT_QUERY_SERVER; + } + // If no data is available + if (0 == dwNumberOfBytesAvailableToBeRead) + { +#if defined(_DEBUG) + { + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\t%s: Indicates that zero (0) bytes are available to be read", + __FUNCTION__, + "InternetQueryDataAvailable"); + ODS(szMsgBuf) + } +#endif + return RESPONSE_CODE__SUCCESS; + } + // If the number of bytes available to be read is more than the size of our in-memory buffer + if (dwNumberOfBytesAvailableToBeRead > dwNumHttpResponseDataBufferBytes) + { + // Clamp the number of bytes to be read to the size of our in-memory buffer + dwNumberOfBytesAvailableToBeRead = dwNumHttpResponseDataBufferBytes; + } + // If the number of bytes available to be read will require a read past the end of the content + if ((dwNumberOfBytesAvailableToBeRead + m.dwNumBytesResponseDataReadTotal) > m.dwContentLen) + { + // Clip the number of bytes to be read to ensure exactly m.dwContentLen bytes are read + dwNumberOfBytesAvailableToBeRead = m.dwContentLen - m.dwNumBytesResponseDataReadTotal; + } + // If we can NOT read data from the handle opened by function InternetOpenUrl + if (!InternetReadFile ( + hRequest, + pszHttpResponseData, + dwNumberOfBytesAvailableToBeRead, + pdwNumBytesResponseDataRead)) + { +#if defined(_DEBUG) + { + DWORD dwLastError; + dwLastError = GetLastError (); + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\t%s: %u=%s", + __FUNCTION__, + "ERROR: InternetReadFile", + dwLastError, + get_http_specific_error_description (dwLastError)); + ODS(szMsgBuf) + } +#endif + return RESPONSE_CODE__INTERNET_READ_FAILURE; + } + // If the number of byte available to be read is NOT equal to the number of bytes read + if (dwNumberOfBytesAvailableToBeRead != *pdwNumBytesResponseDataRead) + { +#if defined(_DEBUG) + { + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\t%s: The number of bytes available to be read = <%u>, but <%u> bytes were successfully read", + __FUNCTION__, + "ERROR: InternetReadFile", + dwNumberOfBytesAvailableToBeRead, + *pdwNumBytesResponseDataRead); + ODS(szMsgBuf) + } +#endif + return RESPONSE_CODE__INVALID_NUMBER_OF_BYTES_READ; + } +#if defined(_DEBUG) + { + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\tSuccessfully read <%d> bytes", + __FUNCTION__, + *pdwNumBytesResponseDataRead); + ODS(szMsgBuf) + } +#endif + // Indicate success + return RESPONSE_CODE__SUCCESS; +} + +ENUM_RESPONSE_CODE CRunPiratesOnline::download_installer (HANDLE hFile) +{ +#if defined(_DEBUG) + { char szMsgBuf [1024];wsprintf (szMsgBuf,"ENTER: " + "%s",__FUNCTION__);ODS(szMsgBuf)} +#endif + ENUM_RESPONSE_CODE enumResponseCode = RESPONSE_CODE__SUCCESS; + // + char hostnamebuf [512]; + memset (hostnamebuf, 0, sizeof (hostnamebuf)); + // + char urlstrbuf [1024]; + // + URL_COMPONENTS url_comp; + memset (&url_comp, 0, sizeof (url_comp)); + url_comp.dwStructSize = sizeof (url_comp); + url_comp.lpszHostName = hostnamebuf; + url_comp.dwHostNameLength = sizeof (hostnamebuf); + url_comp.lpszUrlPath = urlstrbuf; + url_comp.dwUrlPathLength = sizeof (urlstrbuf); + // If the source URL cannot be cracked into its component parts +#if defined(_DEBUG) + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call InternetCrackUrl",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (!InternetCrackUrl (gsc_mmogFlavor[m.immogFlavorIdx].pszInstallerURL, 0, 0x0, &url_comp)) + { +#if defined(_DEBUG) + { + DWORD dwLastError; + dwLastError = GetLastError (); + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s\t" + "%s: %u=%s", + __FUNCTION__, + "ERROR: InternetCrackUrl", + dwLastError, + get_http_specific_error_description (dwLastError)); + ODS(szMsgBuf) + } +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_CRACK_SOURCE_URL; + } + else + if ((url_comp.nScheme != INTERNET_SCHEME_HTTP) + && + (url_comp.nScheme != INTERNET_SCHEME_HTTPS) + ) + { +#if defined(_DEBUG) + { + char szMsgBuf [1024]; + wsprintf ( + szMsgBuf, + "%s" + "\t" + "ERROR: Only HTTP and HTTPS are supported", + __FUNCTION__); + ODS(szMsgBuf) + } +#endif + enumResponseCode = RESPONSE_CODE__UNSUPPORTED_INTERNET_PROTOCOL_SCHEME; + } + + // Initialize the HttpOpenRequest bit flags + // + DWORD dwHttpOpenRequestBitFlags = + INTERNET_FLAG_RELOAD // 0x80000000 // retrieve the original item + | + INTERNET_FLAG_NO_CACHE_WRITE // 0x04000000 // don't write this item to the cache + | + INTERNET_FLAG_KEEP_CONNECTION // 0x00400000 // use keep-alive semantics + | + INTERNET_FLAG_PRAGMA_NOCACHE // 0x00000100 // asking wininet to add "pragma: no-cache" + ; + + // Set the server port number + // + if (INTERNET_SCHEME_HTTPS == url_comp.nScheme) + { + dwHttpOpenRequestBitFlags |= ( + INTERNET_FLAG_SECURE // 0x00800000 // use PCT/SSL if applicable (HTTP) + | + INTERNET_FLAG_IGNORE_CERT_CN_INVALID // 0x00001000 // bad common name in X509 Cert. + | + INTERNET_FLAG_IGNORE_CERT_DATE_INVALID); // 0x00002000 // expired X509 Cert. + } + + HINTERNET hSession; + HINTERNET hConnection = NULL; + HINTERNET hRequest = NULL; + // +#define MAX_HEADER_SIZE 8192 + char * pRequestHdrBuf = new char [MAX_HEADER_SIZE]; + DWORD dwBufLen = MAX_HEADER_SIZE; + // + DWORD HTTP_StatusCode = 0; + DWORD dwSizeOfStatusCode = sizeof (HTTP_StatusCode); + // + DWORD dwSizeOf_ContentLen = sizeof (m.dwContentLen); + + // Initialize this apps use of the WinINet functions +#if defined(_DEBUG) + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call InternetOpen",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (NULL == (hSession = InternetOpen ( + "DisneyOnlineGames", + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0))) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: InternetOpen",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_OPEN_SESSION; + } + // Open an HTTP session for the specified site + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - url_comp.lpszHostName=[%s]",__FUNCTION__,url_comp.lpszHostName);ODS(szMsgBuf)} + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - url_comp.nPort=[%d]",__FUNCTION__,url_comp.nPort);ODS(szMsgBuf)} + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - urlstrbuf=[%s]",__FUNCTION__,urlstrbuf);ODS(szMsgBuf)} + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call InternetConnect",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (NULL == (hConnection = InternetConnect ( + hSession, + url_comp.lpszHostName, + url_comp.nPort, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + INTERNET_FLAG_NO_CACHE_WRITE, + 0))) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: InternetConnect",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_CONNECT; + } + // Open an HTTP request handle + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call HttpOpenRequest",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (NULL == (hRequest = HttpOpenRequest ( + hConnection, + "GET", + url_comp.lpszUrlPath, + "HTTP/1.1", + NULL, + (LPCTSTR *)g_paszAcceptTypes, + dwHttpOpenRequestBitFlags, + 0))) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpOpenRequest",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_OPEN_REQUEST; + } + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call HttpQueryInfo",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (!HttpQueryInfo ( + hRequest, + HTTP_QUERY_RAW_HEADERS_CRLF | HTTP_QUERY_FLAG_REQUEST_HEADERS, + (LPVOID)pRequestHdrBuf, + &dwBufLen, + 0)) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpQueryInfo",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_OBTAIN_HEADERS_RETURNED_BY_SERVER; + } + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call HttpSendRequest",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (!HttpSendRequest ( + hRequest, + NULL, // No extra headers + 0, // No extra Header length + NULL, // Not sending a POST + 0)) // Not sending a POST + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpQueryInfo",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__HTTP_SERVER_REQUEST_FAILURE; + } + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call HttpQueryInfo",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (!HttpQueryInfo ( + hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, // HTTP_QUERY_FLAG_NUMBER tells it to return a dword, not a string + (LPVOID)&HTTP_StatusCode, + &dwSizeOfStatusCode, + NULL)) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpQueryInfo",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_OBTAIN_STATUS_CODE; + } + else if (HTTP_STATUS_OK != HTTP_StatusCode) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpQueryInfo",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__HTTP_STATUS_ERROR; + } + else +#if defined(_DEBUG) + { + { char szMsgBuf [1024];wsprintf (szMsgBuf,"%s" + " - about to call HttpQueryInfo",__FUNCTION__);ODS(szMsgBuf)} +#endif + if (!HttpQueryInfo ( + hRequest, + HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, + (LPVOID)&(m.dwContentLen), + &dwSizeOf_ContentLen, + NULL)) + { +#if defined(_DEBUG) + {DWORD dwLastError;dwLastError = GetLastError ();char szMsgBuf [1024];wsprintf (szMsgBuf,"%s\t" + "%s: %u=%s",__FUNCTION__,"ERROR: HttpQueryInfo",dwLastError,get_http_specific_error_description (dwLastError));ODS(szMsgBuf)} +#endif + enumResponseCode = RESPONSE_CODE__CANNOT_OBTAIN_CONTENT_LENGTH; + } + else + { + // While response data is available is being successfully read + // + static const int NUM_HTTP_RESPONSE_DATA_BUFFER_BYTES = 8192; + char szHttpResponseData [NUM_HTTP_RESPONSE_DATA_BUFFER_BYTES]; + DWORD dwNumBytesResponseDataRead = 0; + ENUM_RESPONSE_CODE enumResponseCode; + while ( + (m.dwNumBytesResponseDataReadTotal < m.dwContentLen) + && + (RESPONSE_CODE__SUCCESS == (enumResponseCode = response_data_available_and_successfully_read ( + hRequest, + szHttpResponseData, + NUM_HTTP_RESPONSE_DATA_BUFFER_BYTES, + &dwNumBytesResponseDataRead))) + ) + { + // Update the total number of response data bytes read + m.dwNumBytesResponseDataReadTotal += dwNumBytesResponseDataRead; + // If the destination file write fails + DWORD dwNumberOfBytesWritten; + if (!WriteFile ( + hFile, + szHttpResponseData, + dwNumBytesResponseDataRead, + &dwNumberOfBytesWritten, + NULL)) + { + enumResponseCode = RESPONSE_CODE__FILE_WRITE_FAILURE; + break; + } + if (dwNumBytesResponseDataRead != dwNumberOfBytesWritten) + { + enumResponseCode = RESPONSE_CODE__UNEXPECTED_NUMBER_OF_BYTES_WRITTEN_TO_FILE; + break; + } + } + } +#if defined(_DEBUG) + } + } + } + } + } + } +#endif + + // Close the handle opened by the call to function HttpOpenRequest + // + if (hRequest) + { + InternetCloseHandle (hRequest); + } + + // Close the handle opened by the call to function InternetConnect + // + if (hConnection) + { + InternetCloseHandle (hConnection); + } + + // Close the Internet handle opened by the call to function InternetOpen + // + if (hSession) + { + InternetCloseHandle (hSession); + } + +#if defined(_DEBUG) + { char szMsgBuf [1024];wsprintf (szMsgBuf,"LEAVE: " + "%s",__FUNCTION__);ODS(szMsgBuf)} +#endif + return enumResponseCode; +} + +ENUM_RESPONSE_CODE CRunPiratesOnline::download_and_run_installer (const char * pszInstallerURL) +{ + // If the request to obtain the path to temporary directory failed + char szTempPath [MAX_PATH]; + if (0 == GetTempPath (MAX_PATH, szTempPath)) + { + return RESPONSE_CODE__UNABLE_TO_OBTAIN_TEMP_PATHNAME; + } + // If the request to obtain a unique temporary filename failed + char szInstallerFullPathname [MAX_PATH]; + if (0 == GetTempFileName (szTempPath, NULL, 0, szInstallerFullPathname)) + { + return RESPONSE_CODE__UNABLE_TO_OBTAIN_TEMP_FILENAME; + } + // If the temporary file was not successfully opened for writing + HANDLE hFile; + if (INVALID_HANDLE_VALUE == (hFile = CreateFile ( + szInstallerFullPathname, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ))) + { + return RESPONSE_CODE__UNABLE_TO_CREATE_DESTINATION_FILENAME; + } + // + ENUM_RESPONSE_CODE enumResponseCode; + enumResponseCode = download_installer (hFile); + // + if (!CloseHandle (hFile)) + { + enumResponseCode = RESPONSE_CODE__CANNOT_CLOSE_DESTINATION_FILE; + } + // + if (RESPONSE_CODE__SUCCESS != enumResponseCode) + { + DeleteFile (szInstallerFullPathname); + return enumResponseCode; + } + // Register file szInstallerFullPathname to be deleted when the system restarts + // NOTES: + // 1) The system moves the file immediately after AUTOCHK is executed, but before creating any + // paging files. + // 2) Parameter value MOVEFILE_DELAY_UNTIL_REBOOT can be used only if the process is in the + // context of a user who belongs to the administrator group or the LocalSystem account. + MoveFileEx (szInstallerFullPathname, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); + // Set command line + CString strCommandLine (szInstallerFullPathname); + strCommandLine.Append (" "); + strCommandLine.Append (szInstallerCommandLineOptionPrefix); + strCommandLine.Append (szInstallerSilentRunModeOption); + strCommandLine.Append (" "); + strCommandLine.Append (szInstallerCommandLineOptionPrefix); + strCommandLine.Append (szEnvKeyToAddToChildProcessEnvBlock); + strCommandLine.Append (" "); + strCommandLine.Append (m.pszToken); + // Attempt to run the installer executable + return create_process (strCommandLine); +} + +ENUM_RESPONSE_CODE CRunPiratesOnline::create_process (CString & strCommandLine) +{ + // Create security attributes + SECURITY_DESCRIPTOR sd; + InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl (&sd, TRUE, 0, FALSE); + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), &sd, true }; + // Create child process environment block + CString strEnvForChildProcess; + CEnvBlock EnvBlock (m.pszToken); + EnvBlock.Create (szEnvKeyToAddToChildProcessEnvBlock, strEnvForChildProcess); + // Set startup information + STARTUPINFO si; + memset (&si, 0, sizeof (si)); + si.cb = sizeof (si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + // Initialize process information structure + PROCESS_INFORMATION pi; + ZeroMemory (&pi, sizeof (pi)); + // Attempt to run the executable + if (!CreateProcess ( + NULL, + strCommandLine.GetBuffer (), + &sa, + &sa, + true, + 0, + strEnvForChildProcess.GetBuffer (), + NULL, + &si, + &pi)) + { + return RESPONSE_CODE__CREATE_PROCESS_FAILED; + } + return RESPONSE_CODE__SUCCESS; +} + +bool CRunPiratesOnline::map_folder_to_csidl (const char * pszCSIDL, + int * pcsidl) +{ + bool fRet = true; + // + *pcsidl = 0; + if (0 == _strcmpi (pszCSIDL /* .GetBuffer () */ , "ADMINTOOLS")) + { + // The file system directory that is used to store administrative tools for an individual user. + // The Microsoft Management Console (MMC) will save customized consoles to this directory, and + // it will roam with the user. + *pcsidl |= CSIDL_ADMINTOOLS; + } + else + if (0 == _strcmpi (pszCSIDL, "COMMON_ADMINTOOLS")) + { + // The file system directory containing administrative tools for all users of the computer. + *pcsidl |= CSIDL_COMMON_ADMINTOOLS; + } + else + if (0 == _strcmpi (pszCSIDL, "APPDATA")) + { + // The file system directory that serves as a common repository for application-specific data. + // A typical path is C:\Documents and Settings\username\Application Data. + // This CSIDL is supported by the redistributable Shfolder.dll for systems that do not have + // the Microsoft Internet Explorer 4.0 integrated Shell installed. + *pcsidl |= CSIDL_APPDATA; + } + else + if (0 == _strcmpi (pszCSIDL, "COMMON_APPDATA")) + { + // The file system directory containing application data for all users. + // A typical path is C:\Documents and Settings\All Users\Application Data. + *pcsidl |= CSIDL_COMMON_APPDATA; + } + else + if (0 == _strcmpi (pszCSIDL, "COMMON_DOCUMENTS")) + { + // The file system directory that contains documents that are common to all users. + // A typical paths is C:\Documents and Settings\All Users\Documents. + // Valid for Windows NT systems and Microsoft Windows 95 and Windows 98 systems with + // Shfolder.dll installed. + *pcsidl |= CSIDL_COMMON_DOCUMENTS; + } + else + if (0 == _strcmpi (pszCSIDL, "COOKIES")) + { + // The file system directory that serves as a common repository for Internet cookies. + // A typical path is C:\Documents and Settings\username\Cookies. + *pcsidl |= CSIDL_COOKIES; + } + else + if (0 == _strcmpi (pszCSIDL, "HISTORY")) + { + // The file system directory that serves as a common repository for Internet history items. + *pcsidl |= CSIDL_HISTORY; + } + else + if (0 == _strcmpi (pszCSIDL, "INTERNET_CACHE")) + { + // The file system directory that serves as a common repository for temporary Internet files. + // A typical path is C:\Documents and Settings\username\Local Settings\Temporary Internet Files. + *pcsidl |= CSIDL_INTERNET_CACHE; + } + else + if (0 == _strcmpi (pszCSIDL, "LOCAL_APPDATA")) + { + // The file system directory that serves as a data repository for local (nonroaming) applications. + // A typical path is C:\Documents and Settings\username\Local Settings\Application Data. + *pcsidl |= CSIDL_LOCAL_APPDATA; + } + else + if (0 == _strcmpi (pszCSIDL, "MYPICTURES")) + { + // The file system directory that serves as a common repository for image files. + // A typical path is C:\Documents and Settings\username\My Documents\My Pictures. + *pcsidl |= CSIDL_MYPICTURES; + } + else + if (0 == _strcmpi (pszCSIDL, "PERSONAL")) + { + // The virtual folder representing the My Documents desktop item. + // This is equivalent to CSIDL_MYDOCUMENTS. + *pcsidl |= CSIDL_PERSONAL; + } + else + if (0 == _strcmpi (pszCSIDL, "PROGRAM_FILES")) + { + // The Program Files folder. + // A typical path is C:\Program Files. + *pcsidl |= CSIDL_PROGRAM_FILES; + } + else + if (0 == _strcmpi (pszCSIDL, "PROGRAM_FILES_COMMON")) + { + // A folder for components that are shared across applications. + // A typical path is C:\Program Files\Common. + // Valid only for Windows NT, Windows 2000, and Windows XP systems. + // Not valid for Windows Millennium Edition (Windows Me). + *pcsidl |= CSIDL_PROGRAM_FILES_COMMON; + } + else + if (0 == _strcmpi (pszCSIDL, "SYSTEM")) + { + // The Windows System folder. + // A typical path is C:\Windows\System32. + *pcsidl |= CSIDL_SYSTEM; + } + else + if (0 == _strcmpi (pszCSIDL, "WINDOWS")) + { + // The Windows directory or SYSROOT. + // This corresponds to the %windir% or %SYSTEMROOT% environment variables. + // A typical path is C:\Windows. + *pcsidl |= CSIDL_WINDOWS; + } + else + { + fRet = false; + } + return fRet; +} + +ENUM_RESPONSE_CODE CRunPiratesOnline::run_launcher (const char * pszLauncherCSIDL, + const char * pszLauncherPathname) +{ + // Given a CSIDL as a string, if the corresponding pathname cannot be obtained + int csidl = 0; + if (!map_folder_to_csidl (pszLauncherCSIDL, &csidl)) + { + return RESPONSE_CODE__UNRECOGNIZED_DESTINATION_FOLDER; + } + // Given a CSIDL of a folder, if a path was NOT returned + char szPathFromCSIDL [MAX_PATH]; + if (!(SUCCEEDED(SHGetFolderPath ( + NULL, // handle to an owner window + csidl, // CSIDL value that identifies the folder whose path is to be retrieved + NULL, // access token that can be used to represent a particular user + SHGFP_TYPE_CURRENT, // flags to specify which path is to be returned + szPathFromCSIDL // pointer to a null-terminated string of length MAX_PATH which will receive the path + )))) + { + return RESPONSE_CODE__CANNOT_OBTAIN_SPECIAL_ROOT_FOLDER_PATHNAME; + } + // Obtain string lengths + size_t stPath_Len = strlen (szPathFromCSIDL); + size_t stLancherPathname_Len = strlen (pszLauncherPathname); + // If launcher executable full pathname will be too long + if (MAX_PATH <= (stPath_Len + stLancherPathname_Len)) + { + return RESPONSE_CODE__LAUNCHER_FULL_PATHNAME_TOO_LONG; + } + // Set launcher executable full pathname + char szLauncherFullPathname [MAX_PATH]; + strcpy (szLauncherFullPathname, szPathFromCSIDL); + strcat (szLauncherFullPathname, pszLauncherPathname); + // Set command line + CString strCommandLine (szLauncherFullPathname); + strCommandLine.Append (" "); + strCommandLine.Append (szEnvKeyToAddToChildProcessEnvBlock); + strCommandLine.Append ("="); + strCommandLine.Append (m.pszToken); + // Return indication of whether or not the launcher process was successfully created + return create_process (strCommandLine); +} + +ENUM_RESPONSE_CODE CRunPiratesOnline::validate_inputs (const int ModeId) +{ + ENUM_RESPONSE_CODE enumResponseCode = RESPONSE_CODE__INVALID_MODE; + for (m.immogFlavorIdx = 0; m.immogFlavorIdx < (sizeof (gsc_mmogFlavor) / sizeof (MMOG_FLAVOR)); m.immogFlavorIdx++) + { + if (gsc_mmogFlavor[m.immogFlavorIdx].mode == ModeId) + { + enumResponseCode = RESPONSE_CODE__SUCCESS; + break; + } + } + return enumResponseCode; +} + +ULONG CRunPiratesOnline::Run (const int ModeId, + const char * pszToken) +{ + m.pszToken = pszToken; + ENUM_RESPONSE_CODE ResponseCode; + if (RESPONSE_CODE__SUCCESS == (ResponseCode = validate_inputs (ModeId))) + { + if (RESPONSE_CODE__SUCCESS != (ResponseCode = run_launcher ( + gsc_mmogFlavor[m.immogFlavorIdx].pszLauncherCSIDL, + gsc_mmogFlavor[m.immogFlavorIdx].pszLauncherPathname))) + { + ResponseCode = download_and_run_installer ( + gsc_mmogFlavor[m.immogFlavorIdx].pszInstallerURL); + } + } + return ResponseCode; +} diff --git a/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.h b/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.h new file mode 100644 index 0000000..7f7cc51 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/RunPiratesOnline.h @@ -0,0 +1,130 @@ +#pragma once + +enum ENUM_RESPONSE_CODE +{ + RESPONSE_CODE__SUCCESS = 0, + // Success + RESPONSE_CODE__INVALID_MODE = 1, + // Mode input is invalid + RESPONSE_CODE__UNRECOGNIZED_DESTINATION_FOLDER = 2, + // Unrecognized Destination Folder argument + // Please reference [Valid Destination Folder Parameter Values] below + RESPONSE_CODE__CANNOT_OBTAIN_SPECIAL_ROOT_FOLDER_PATHNAME = 3, + // Cannot obtain special root folder pathname + RESPONSE_CODE__LAUNCHER_FULL_PATHNAME_TOO_LONG = 4, + // Full pathname to launcher executable is too long + RESPONSE_CODE__BAD_PATHNAME = 5, + // Possible issue: Relative paths are not allowed + RESPONSE_CODE__FILENAME_EXCED_RANGE = 6, + // Destination Folder + Destination Sub-folder + Destination Filename > 256 characters + RESPONSE_CODE__PATH_NOT_FOUND = 7, + // The system cannot find the path + // Possible issue: The path contains an invalid entry + RESPONSE_CODE__CANCELLED = 8, + // The user canceled the operation + RESPONSE_CODE__SHCDEX_UNRECOGNIZED_RET_CODE = 9, + // Call to create Destination Folder failed with an unrecognized reason code + RESPONSE_CODE__DESTINATION_FILENAME_TOO_LONG = 10, + // Destination filename too long + RESPONSE_CODE__DESTINATION_FILENAME_IS_A_DIRECTORY = 11, + // A directory exists with the same full pathname as the requested destination + RESPONSE_CODE__UNABLE_TO_CREATE_DESTINATION_FILENAME = 12, + // Unable to create destination file on user's HDD + RESPONSE_CODE__CANNOT_CRACK_SOURCE_URL = 13, + // Cannot crack source URL + RESPONSE_CODE__UNSUPPORTED_INTERNET_PROTOCOL_SCHEME = 14, + // Internet protocol scheme not supported + // Supported Internet protocol schemes: HTTP, HTTPS + RESPONSE_CODE__CANNOT_OBTAIN_CONTENT_LENGTH = 15, + // Cannot obtain content length from server + RESPONSE_CODE__CANNOT_OPEN_SESSION = 16, + // Call to open protocol session failed + RESPONSE_CODE__CANNOT_CONNECT = 17, + // Attempt to connect to server failed + RESPONSE_CODE__CANNOT_OPEN_REQUEST = 18, + // Protocol request handle creation failure + RESPONSE_CODE__CANNOT_OBTAIN_HEADERS_RETURNED_BY_SERVER = 19, + // Unable to retrieve header information associated with server request + RESPONSE_CODE__HTTP_SERVER_REQUEST_FAILURE = 20, + // Server request failure + RESPONSE_CODE__CANNOT_OBTAIN_STATUS_CODE = 21, + // Server request could not complete + RESPONSE_CODE__HTTP_STATUS_ERROR = 22, + // Status code returned from server indicates error + RESPONSE_CODE__CANNOT_QUERY_SERVER = 23, + // Cannot query server + // Failure occurred when querying server to determine data size + RESPONSE_CODE__INTERNET_READ_FAILURE = 24, + // Call to read data from server failed + RESPONSE_CODE__INVALID_NUMBER_OF_BYTES_READ = 25, + // The number of bytes requested to be read is not equal to the number of bytes read from + // the server + RESPONSE_CODE__FILE_WRITE_FAILURE = 26, + // HDD write failure + // Users hard drive may be out of space + RESPONSE_CODE__UNEXPECTED_NUMBER_OF_BYTES_WRITTEN_TO_FILE = 27, + // Number of bytes written to HDD does not match the number of bytes requested to be written + RESPONSE_CODE__CANNOT_CLOSE_DESTINATION_FILE = 28, + // Call to close the Destination Filename failed + RESPONSE_CODE__CREATE_PROCESS_FAILED = 29, + // Unable to run downloaded executable + // + // + RESPONSE_CODE__UNABLE_TO_OBTAIN_TEMP_PATHNAME = 51, + RESPONSE_CODE__UNABLE_TO_OBTAIN_TEMP_FILENAME = 52, + // + // + RESPONSE_CODE__FAILURE = 99, + // Non-specific failure +}; + +class CRunPiratesOnline +{ +public: + CRunPiratesOnline (void) + { + memset (&m, 0, sizeof (m)); + } + ~CRunPiratesOnline (void) + { + } + ULONG Run (const int ModeId, + const char * pszToken); +private: + struct + { + const char * pszToken; + int immogFlavorIdx; + DWORD dwContentLen; + DWORD dwNumBytesResponseDataReadTotal; + } m; +private: +#if defined(_DEBUG) + PSZ get_http_specific_error_description (DWORD dwError); +#endif + // + ENUM_RESPONSE_CODE response_data_available_and_successfully_read (HINTERNET hRequest, + char * pszHttpResponseData, + DWORD dwNumHttpResponseDataBufferBytes, + DWORD * pdwNumBytesResponseDataRead); + // + ENUM_RESPONSE_CODE download_installer (HANDLE hFile); + // + ENUM_RESPONSE_CODE download_and_run_installer (const char * pszInstallerURL); + // + void insert_env_block_key_value_pair (CString & strEnvForChildProcess, + int * pInsertIdx, + char * lpszVariable); + // + void create_child_process_environment_block (CString & strEnvForChildProcess); + // + ENUM_RESPONSE_CODE create_process (CString & strCommandLine); + // + ENUM_RESPONSE_CODE validate_inputs (const int ModeId); + // + ENUM_RESPONSE_CODE run_launcher (const char * pszLauncherCSIDL, + const char * pszLauncherPathname); + // + bool map_folder_to_csidl (const char * pszCSIDL, + int * pcsidl); +}; diff --git a/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.cpp b/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.cpp new file mode 100644 index 0000000..22b2cb3 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.cpp @@ -0,0 +1,69 @@ +#include "StdAfx.h" + +#define INITIAL_NUM_WINDOW_HANDLES 10 +#define NUM_WINDOW_HANDLE_ARRAY_INCREMENT 5 + +CTopLevelWindowIterator::CTopLevelWindowIterator (DWORD dwProcessId) +{ + ZeroMemory (&m, sizeof (m)); + m.dwProcessId = dwProcessId; + m.dwNumAlloced = INITIAL_NUM_WINDOW_HANDLES; + m.paHWND = new HWND [m.dwNumAlloced]; +} + +CTopLevelWindowIterator::~CTopLevelWindowIterator() +{ + delete [] m.paHWND; +} + +HWND CTopLevelWindowIterator::First() +{ + // Enumerate all top-level windows + ::EnumWindows(EnumProc, (LPARAM)this); + + // Reset the HWND array current index + m.dwCurrIdx = 0; + + // Return the first top level window created by the specified process + return Next(); +} + +HWND CTopLevelWindowIterator::Next() +{ + if (m.paHWND && (m.dwCurrIdx < m.dwNumUsed)) + { + return m.paHWND[m.dwCurrIdx++]; + } + return NULL; +} + +BOOL CALLBACK CTopLevelWindowIterator::EnumProc (HWND hwnd, LPARAM lp) +{ + return ((CTopLevelWindowIterator*)lp)->OnEnumProc(hwnd); +} + +BOOL CTopLevelWindowIterator::OnEnumProc (HWND hwnd) +{ + // If the given top level window is visible + if (WS_VISIBLE & GetWindowLong (hwnd, GWL_STYLE)) + { + // Retrieve the identifier of the process that created the given top level window + DWORD dwWindowThreadProcessId; + GetWindowThreadProcessId (hwnd, &dwWindowThreadProcessId); + + // If the given top level window was created by the specified process + if (dwWindowThreadProcessId == m.dwProcessId) + { + if (m.dwNumUsed >= m.dwNumAlloced) + { + HWND * paHWND = new HWND [m.dwNumAlloced + NUM_WINDOW_HANDLE_ARRAY_INCREMENT]; + memcpy (paHWND, m.paHWND, sizeof (HWND) * m.dwNumAlloced); + delete [] m.paHWND; + m.paHWND = paHWND; + m.dwNumAlloced += NUM_WINDOW_HANDLE_ARRAY_INCREMENT; + } + m.paHWND[m.dwNumUsed++] = hwnd; + } + } + return TRUE; // keep looking +} diff --git a/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.h b/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.h new file mode 100644 index 0000000..cf1cbaf --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/TopLevelWindowIterator.h @@ -0,0 +1,23 @@ +class CTopLevelWindowIterator +{ +public: + CTopLevelWindowIterator(DWORD dwProcessId); + ~CTopLevelWindowIterator(); + + HWND First(); + HWND Next(); + +protected: + static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lp); + + BOOL OnEnumProc(HWND hwnd); + + struct + { + DWORD dwProcessId; // process id + DWORD dwNumAlloced; // number of HWND array elements allocated + DWORD dwNumUsed; // number of HWND array elements used + DWORD dwCurrIdx; // HWND array current index + HWND * paHWND; // pointer to the array of top level window handles + } m; +}; diff --git a/otp/src/activex/DisneyOnlineGames/src/resource.h b/otp/src/activex/DisneyOnlineGames/src/resource.h new file mode 100644 index 0000000..663e5e8 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/resource.h @@ -0,0 +1,20 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DisneyOnlineGames.rc +// + +#define IDS_DISNEYONLINEGAMES 1 +#define IDS_DISNEYONLINEGAMES_PPG 2 + +#define IDS_DISNEYONLINEGAMES_PPG_CAPTION 200 + +#define IDD_PROPPAGE_DISNEYONLINEGAMES 200 + + +#define IDB_DISNEYONLINEGAMES 1 + + +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 32768 diff --git a/otp/src/activex/DisneyOnlineGames/src/stdafx.cpp b/otp/src/activex/DisneyOnlineGames/src/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/otp/src/activex/DisneyOnlineGames/src/stdafx.h b/otp/src/activex/DisneyOnlineGames/src/stdafx.h new file mode 100644 index 0000000..ea43956 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/src/stdafx.h @@ -0,0 +1,55 @@ +#pragma once + +#define _CRT_SECURE_NO_WARNINGS + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +#ifndef WINVER +#define WINVER 0x0501 +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#ifndef _WIN32_IE +#define _WIN32_IE 0x0600 +#endif + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +#include // MFC support for ActiveX Controls +#include // MFC extensions +#ifndef _AFX_NO_OLE_SUPPORT +#include // MFC support for Internet Explorer 4 Comon Controls +#endif +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +// Delete the two includes below if you do not wish to use the MFC +// database classes +#ifndef _WIN64 + +#ifndef _AFX_NO_DB_SUPPORT +#include // MFC ODBC database classes +#endif // _AFX_NO_DB_SUPPORT + +#ifndef _AFX_NO_DAO_SUPPORT +#include // MFC DAO database classes +#endif // _AFX_NO_DAO_SUPPORT + +#endif // _WIN64 + +#include + +#include + +#include +#include + +#include "EnvBlock.h" +#include "RunPiratesOnline.h" +#include "TopLevelWindowIterator.h" diff --git a/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.sln b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.sln new file mode 100644 index 0000000..9f6aa26 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testCoreFunctionality", "testCoreFunctionality.Visual Studio 2005.vcproj", "{72EC3673-7D59-4A82-8BFA-ABF504A3DA87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {72EC3673-7D59-4A82-8BFA-ABF504A3DA87}.Debug|Win32.ActiveCfg = Debug|Win32 + {72EC3673-7D59-4A82-8BFA-ABF504A3DA87}.Debug|Win32.Build.0 = Debug|Win32 + {72EC3673-7D59-4A82-8BFA-ABF504A3DA87}.Release|Win32.ActiveCfg = Release|Win32 + {72EC3673-7D59-4A82-8BFA-ABF504A3DA87}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.vcproj b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.vcproj new file mode 100644 index 0000000..456a9bd --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.Visual Studio 2005.vcproj @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.cpp b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.cpp new file mode 100644 index 0000000..a2515db --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testCoreFunctionality/testCoreFunctionality.cpp @@ -0,0 +1,18 @@ +#include "stdafx.h" + +int _tmain(int argc, _TCHAR* argv[]) +{ + CRunPiratesOnline * pRunPiratesOnline = new CRunPiratesOnline (); + ULONG ResponseCode = pRunPiratesOnline->Run ( + 3, + "UserPassiveToken"); + delete pRunPiratesOnline; + + // Wait for user to read output + fflush (stdin); + printf ("ResponseCode=[%u]\r\n",ResponseCode); + printf ("Press ENTER key to exit: "); + while ('\n' != fgetc (stdin)) + // + return 1; +} diff --git a/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.cpp b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.cpp new file mode 100644 index 0000000..7c4ab36 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.cpp @@ -0,0 +1,99 @@ +#include "stdafx.h" + +static void dump_env_block (void) +{ + printf ("%s: ENTER\n\n", __FUNCTION__); + // If a pointer to the environment block for this process was returned + LPVOID lpvEnv; + if (lpvEnv = GetEnvironmentStrings ()) + { + // Variable strings are separated by NULL byte, and the block is terminated by a NULL byte + LPTSTR lpszVariable; + for (lpszVariable = (LPTSTR)lpvEnv; *lpszVariable; lpszVariable += lstrlen (lpszVariable) + 1) + { + printf ("%s\n", lpszVariable); + } + // Free the environment block + FreeEnvironmentStrings ((LPTCH)lpvEnv); + } + printf ("%s: LEAVE\n\n", __FUNCTION__); +} + +#if defined(_DEBUG) +static bool create_process (char * pszProcessFullPathname, + const char * pszCommandLineArgs = NULL) +{ + CString strCommandLine (pszProcessFullPathname); + if (pszCommandLineArgs) + { + strCommandLine.Append (pszCommandLineArgs); + } + // Create security attributes + SECURITY_DESCRIPTOR sd; + InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl (&sd, TRUE, 0, FALSE); + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), &sd, true }; + // + dump_env_block (); + // + // Create child process environment block + CString strEnvForChildProcess; + CEnvBlock EnvBlock ("U2FsdGVkX19W1vR0iRQVBG4VBp%2BW%2FAqGiGri5SHhJQK3NSA3yrMbrKPXp7PpBiQiRR5njCGoyRsK1f6xRlPFqMI3iSk%2B5MhrkZ6ycQOCazyNcKCc9ZtS13MEsia8cKFwjybX6%2F4BV09dGIjB47jHDD5VCs7K6bLxUZXTnLD88OU7v8FeLaI3ds%2B25bzD3k%2FGYFethATY6Fr6P6EsWKlN8FFVkPsD6Rmfm9hsGxK52cBzsZHCsJ%2BvV9Z7GQZqIoV43XwtAOeeIgC%2FeKJZ%2BTdxxWuby3pS4YEAOf2o37d68CeMLZTeQQR2Ul1APEv19erfvBAg%2FT1DXbJH78CEkxe9x99dOsDrb%2BWd91kuDy9baIkEXtFAcP6DMsP%2FUyCoC%2B2taZD5vIBMOuxIUWcdTulB3A%3D%3D"); + EnvBlock.Create ("BobJones", strEnvForChildProcess); + // + printf ("%s: About to write strEnvForChildProcess\n\n", __FUNCTION__); + LPTSTR lpszVar; + for (lpszVar = strEnvForChildProcess.GetBuffer (); *lpszVar; lpszVar += lstrlen (lpszVar) + 1) + { + printf ("%s\n", lpszVar); + } + printf ("%s: Wrote strEnvForChildProcess\n\n", __FUNCTION__); + // + // Set startup information + STARTUPINFO si; + memset (&si, 0, sizeof (si)); + si.cb = sizeof (si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + // Initialize process information structure + PROCESS_INFORMATION pi; + ZeroMemory (&pi, sizeof (pi)); + // Attempt to run the executable + if (!CreateProcess ( + NULL, + strCommandLine.GetBuffer (), + &sa, + &sa, + true, + 0, + strEnvForChildProcess.GetBuffer (), + NULL, + &si, + &pi)) + { + return false; + } + return true; +} +#endif + +int main(int argc, char * argv[]) +{ + printf ("%s: ENTER\n\n", __FUNCTION__); +#if defined(_DEBUG) + char szProcessToCreate [] = "Release\\testEnvBlock.exe"; + if (!create_process (szProcessToCreate)) + { + printf ("Unable to create process \"%s\"", szProcessToCreate); + } +#else + dump_env_block (); +#endif + // Wait for user to read output + fflush (stdin); + printf ("\n\nPress ENTER key to exit: "); + while ('\n' != fgetc (stdin)) + // + printf ("%s: LEAVE\n\n", __FUNCTION__); + return 1; +} diff --git a/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.sln b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.sln new file mode 100644 index 0000000..fabd3fc --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testEnvBlock", "testEnvBlock.vcproj", "{2582CF1E-D27B-4824-B200-9F5EDC1836BA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2582CF1E-D27B-4824-B200-9F5EDC1836BA}.Debug|Win32.ActiveCfg = Debug|Win32 + {2582CF1E-D27B-4824-B200-9F5EDC1836BA}.Debug|Win32.Build.0 = Debug|Win32 + {2582CF1E-D27B-4824-B200-9F5EDC1836BA}.Release|Win32.ActiveCfg = Release|Win32 + {2582CF1E-D27B-4824-B200-9F5EDC1836BA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.vcproj b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.vcproj new file mode 100644 index 0000000..1d8f026 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testEnvBlock/testEnvBlock.vcproj @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.cpp b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.h b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.h new file mode 100644 index 0000000..61089b2 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/stdafx.h @@ -0,0 +1,9 @@ +#pragma once + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +#include + +#include "..\src\TopLevelWindowIterator.h" diff --git a/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.sln b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.sln new file mode 100644 index 0000000..fc5def8 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testTopLevelWindowIterator", "testTopLevelWindowIterator.VS2005.vcproj", "{C275C319-F39C-4728-81C4-B16279EEC848}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C275C319-F39C-4728-81C4-B16279EEC848}.Debug|Win32.ActiveCfg = Debug|Win32 + {C275C319-F39C-4728-81C4-B16279EEC848}.Debug|Win32.Build.0 = Debug|Win32 + {C275C319-F39C-4728-81C4-B16279EEC848}.Release|Win32.ActiveCfg = Release|Win32 + {C275C319-F39C-4728-81C4-B16279EEC848}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.vcproj b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.vcproj new file mode 100644 index 0000000..8ffbfce --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.VS2005.vcproj @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.cpp b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.cpp new file mode 100644 index 0000000..6925e25 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/testTopLevelWindowIterator/testTopLevelWindowIterator.cpp @@ -0,0 +1,12 @@ +#include "stdafx.h" + +int main(int argc, char* argv[]) +{ + DWORD dwProcessId = 0; + CTopLevelWindowIterator itlw (dwProcessId); + for (HWND hwndTopLevelWindow = itlw.First (); hwndTopLevelWindow; hwndTopLevelWindow = itlw.Next()) + { + ::ShowWindow (hwndTopLevelWindow, SW_MINIMIZE); + } + return 0; +} diff --git a/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup.gif b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup.gif new file mode 100644 index 0000000..a92387d Binary files /dev/null and b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup.gif differ diff --git a/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onDisneyBlast.htm b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onDisneyBlast.htm new file mode 100644 index 0000000..83f1c57 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onDisneyBlast.htm @@ -0,0 +1,34 @@ + + +Pirates of the Carribean Online + + + + + +
+

+ + + + +

+ + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onLocalMachine.htm b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onLocalMachine.htm new file mode 100644 index 0000000..545d4f3 --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onLocalMachine.htm @@ -0,0 +1,34 @@ + + +Pirates of the Carribean Online + + + + + +
+

+ + + + +

+ + + + + + + diff --git a/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onSoundMine.htm b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onSoundMine.htm new file mode 100644 index 0000000..4beca6b --- /dev/null +++ b/otp/src/activex/DisneyOnlineGames/wwwroot/PiratesSetup_onSoundMine.htm @@ -0,0 +1,34 @@ + + +Pirates of the Carribean Online + + + + + +
+

+ + + + +

+ + + + + + + diff --git a/otp/src/ai/.cvsignore b/otp/src/ai/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/ai/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/ai/AIBase.py b/otp/src/ai/AIBase.py new file mode 100644 index 0000000..2eaa530 --- /dev/null +++ b/otp/src/ai/AIBase.py @@ -0,0 +1,278 @@ +from pandac.PandaModules import * +from direct.directnotify.DirectNotifyGlobal import * +from direct.showbase.MessengerGlobal import * +from direct.showbase.BulletinBoardGlobal import * +from direct.task.TaskManagerGlobal import * +from direct.showbase.JobManagerGlobal import * +from direct.showbase.EventManagerGlobal import * +from direct.showbase.PythonUtil import * +from direct.showbase import PythonUtil +from direct.interval.IntervalManager import ivalMgr + +from direct.task import Task +from direct.showbase import EventManager +from direct.showbase import ExceptionVarDump +import math +import sys +import time +import gc + +## assert game.process == 'ai', "Are you intentionally running ai code on %s"%(game.process,) + +class AIBase: + notify = directNotify.newCategory("AIBase") + + def __init__(self): + # Get the dconfig object + self.config = getConfigShowbase() + __builtins__["__dev__"] = self.config.GetBool('want-dev', 0) + if self.config.GetBool('want-variable-dump', 0): + ExceptionVarDump.install() + + if self.config.GetBool('use-vfs', 1): + vfs = VirtualFileSystem.getGlobalPtr() + else: + vfs = None + + # Store dconfig variables + self.wantTk = self.config.GetBool('want-tk', 0) + + # How long should the AI sleep between frames to keep CPU usage down + 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') + # each zone has its own render + #self.render = NodePath('render') + + # This graphics engine is not intended to ever draw anything, it + # advanced clocks and clears pstats state, just like on the client. + self.graphicsEngine = GraphicsEngine() + + # Get a pointer to Panda's global ClockObject, used for + # synchronizing events between Python and C. + # object is exactly in sync with the TrueClock. + globalClock = ClockObject.getGlobalClock() + + # Since we have already started up a TaskManager, and probably + # a number of tasks; and since the TaskManager had to use the + # TrueClock to tell time until this moment, make sure the + # globalClock + self.trueClock = TrueClock.getGlobalPtr() + globalClock.setRealTime(self.trueClock.getShortTime()) + # set the amount of time used to compute average frame rate + globalClock.setAverageFrameRateInterval(30.) + globalClock.tick() + + # Now we can make the TaskManager start using the new globalClock. + taskMgr.globalClock = globalClock + + __builtins__["ostream"] = Notify.out() + __builtins__["globalClock"] = globalClock + __builtins__["vfs"] = vfs + __builtins__["hidden"] = self.hidden + #__builtins__["render"] = self.render + + AIBase.notify.info('__dev__ == %s' % __dev__) + + # set up recording of Functor creation stacks in __dev__ + PythonUtil.recordFunctorCreationStacks() + + # This is temporary: + __builtins__["wantTestObject"] = self.config.GetBool('want-test-object', 0) + + + self.wantStats = self.config.GetBool('want-pstats', 0) + Task.TaskManager.pStatsTasks = self.config.GetBool('pstats-tasks', 0) + # Set up the TaskManager to reset the PStats clock back + # whenever we resume from a pause. This callback function is + # a little hacky, but we can't call it directly from within + # the TaskManager because he doesn't know about PStats (and + # has to run before libpanda is even loaded). + taskMgr.resumeFunc = PStatClient.resumeAfterPause + + # in production, we want to use fake textures. + defaultValue = 1 + if __dev__: + defaultValue = 0 + wantFakeTextures = self.config.GetBool('want-fake-textures-ai', + defaultValue) + + if wantFakeTextures: + # Setting textures-header-only is a little better than + # using fake-texture-image. The textures' headers are + # read to check their number of channels, etc., and then a + # 1x1 blue texture is created. It loads quickly, consumes + # very little memory, and doesn't require a bogus texture + # to be loaded repeatedly. + loadPrcFileData('aibase', 'textures-header-only 1') + + # If there's a Toontown-specific AIBase, that's where the following + # config flags should be. + # I tried putting this logic in ToontownAIRepository, but wantPets is + # needed during the import of ToontownAIRepository.py + self.wantPets = self.config.GetBool('want-pets', 1) + if self.wantPets: + if game.name == 'toontown': + from toontown.pets import PetConstants + self.petMoodTimescale = self.config.GetFloat( + 'pet-mood-timescale', 1.) + 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('wantKarts', 1) + + self.newDBRequestGen = self.config.GetBool( + 'new-database-request-generate', 1) + + self.waitShardDelete = self.config.GetBool('wait-shard-delete', 1) + self.blinkTrolley = self.config.GetBool('blink-trolley', 0) + self.fakeDistrictPopulations = self.config.GetBool('fake-district-populations', 0) + + self.wantSwitchboard = self.config.GetBool('want-switchboard', 0) + self.wantSwitchboardHacks = self.config.GetBool('want-switchboard-hacks', 0) + self.GEMdemoWhisperRecipientDoid = self.config.GetBool('gem-demo-whisper-recipient-doid', 0) + self.sqlAvailable = self.config.GetBool('sql-available', 1) + + self.createStats() + + self.restart() + + ## ok lets over ride the time yieldFunction + #self.MaxEpockSpeed = 1.0/60.0; + #taskMgr.doYield = self.taskManagerDoYield; + + + def setupCpuAffinities(self, minChannel): + if game.name == 'uberDog': + affinityMask = self.config.GetInt('uberdog-cpu-affinity-mask', -1) + else: + affinityMask = self.config.GetInt('ai-cpu-affinity-mask', -1) + if affinityMask != -1: + TrueClock.getGlobalPtr().setCpuAffinity(affinityMask) + else: + # this is useful on machines that perform better with each process + # assigned to a single CPU + autoAffinity = self.config.GetBool('auto-single-cpu-affinity', 0) + if game.name == 'uberDog': + affinity = self.config.GetInt('uberdog-cpu-affinity', -1) + if autoAffinity and (affinity == -1): + affinity = 2 + else: + affinity = self.config.GetInt('ai-cpu-affinity', -1) + if autoAffinity and (affinity == -1): + affinity = 1 + if affinity != -1: + TrueClock.getGlobalPtr().setCpuAffinity(1 << affinity) + elif autoAffinity: + if game.name == 'uberDog': + # set the affinity based on our channel range + channelSet = int(minChannel / 1000000) + channelSet -= 240 + # add an offset so that the default uberdog affinity is 2 + affinity = channelSet + 3 + # this could be better if we know how many CPUs we have + # for now spread the uberdogs across 4 processors + TrueClock.getGlobalPtr().setCpuAffinity(1 << (affinity % 4)) + + ######################################################################### + # This is the yield function for simple timing based .. no consideration for Network and such.. + ########################################################################### + def taskManagerDoYield(self , frameStartTime, nextScheuledTaksTime): + minFinTime = frameStartTime + self.MaxEpockSpeed + if nextScheuledTaksTime > 0 and nextScheuledTaksTime < minFinTime: + minFinTime = nextScheuledTaksTime; + + delta = minFinTime - globalClock.getRealTime(); + while(delta > 0.002): + time.sleep(delta) + delta = minFinTime - globalClock.getRealTime(); + + + def createStats(self, hostname=None, port=None): + # You can specify pstats-host in your Config.prc or use ~pstats/~aipstats + # The default is localhost + if not self.wantStats: + return False + + if PStatClient.isConnected(): + PStatClient.disconnect() + # these default values match the C++ default values + if hostname is None: + hostname = '' + if port is None: + port = -1 + PStatClient.connect(hostname, port) + return PStatClient.isConnected() + + def __sleepCycleTask(self, task): + # To keep the AI task from running too fast, we sleep a bit here + time.sleep(self.AISleep) + return Task.cont + + def __resetPrevTransform(self, state): + # Clear out the previous velocity deltas now, after we have + # rendered (the previous frame). We do this after the render, + # so that we have a chance to draw a representation of spheres + # along with their velocities. At the beginning of the frame + # really means after the command prompt, which allows the user + # to interactively query these deltas meaningfully. + + PandaNode.resetAllPrevTransform() + return Task.cont + + def __ivalLoop(self, state): + # Execute all intervals in the global ivalMgr. + ivalMgr.step() + return Task.cont + + def __igLoop(self, state): + # This advances the clocks and clears pstats 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() + # __resetPrevTransform goes at the very beginning of the frame. + self.taskMgr.add( + self.__resetPrevTransform, 'resetPrevTransform', priority = -51) + # spawn the ivalLoop with a later priority, so that it will + # run after most tasks, but before igLoop. + self.taskMgr.add(self.__ivalLoop, 'ivalLoop', priority = 20) + self.taskMgr.add(self.__igLoop, 'igLoop', priority = 50) + if self.AISleep >= 0 and (not self.AIRunningNetYield or self.AIForceSleep): + self.taskMgr.add(self.__sleepCycleTask, 'aiSleep', priority = 55) + + self.eventMgr.restart() + + def getRepository(self): + return self.air + + def run(self): + self.taskMgr.run() diff --git a/otp/src/ai/AIBaseGlobal.py b/otp/src/ai/AIBaseGlobal.py new file mode 100644 index 0000000..3fee730 --- /dev/null +++ b/otp/src/ai/AIBaseGlobal.py @@ -0,0 +1,44 @@ +"""instantiate global ShowBase object""" + + +from AIBase import * + +# guard against AI files being imported on the client +assert game.process != 'client' + +__builtins__["simbase"] = AIBase() + +# Make some global aliases for convenience +__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 + +# we don't use ToontownLoader because it just adds progress bar +# functionality to Loader +from direct.showbase import Loader + +simbase.loader = Loader.Loader(simbase) +__builtins__["loader"] = simbase.loader + +# Set direct notify categories now that we have config +directNotify.setDconfigLevels() + +def inspect(anObject): + from direct.tkpanels import Inspector + Inspector.inspect(anObject) + +__builtins__["inspect"] = inspect +# this also appears in ShowBaseGlobal +if (not __debug__) and __dev__: + notify = directNotify.newCategory('ShowBaseGlobal') + notify.error("You must set 'want-dev' to false in non-debug mode.") + + +# Now the builtins are filled in. +taskMgr.finalInit() diff --git a/otp/src/ai/AIDistrict.py b/otp/src/ai/AIDistrict.py new file mode 100644 index 0000000..021fe2d --- /dev/null +++ b/otp/src/ai/AIDistrict.py @@ -0,0 +1,483 @@ +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from AIMsgTypes import * +from direct.showbase.PythonUtil import Functor, randUint32 +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.task import Task +from direct.distributed import ParentMgr +from otp.ai.AIRepository import AIRepository +from otp.ai.AIZoneData import AIZoneData, AIZoneDataStore +import sys +import os +import copy +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator + +if __debug__: + import pdb + +class AIDistrict(AIRepository): + notify = DirectNotifyGlobal.directNotify.newCategory("AIDistrict") + + def __init__( + self, mdip, mdport, esip, esport, dcFileNames, + districtId, districtName, districtType, serverId, + minChannel, maxChannel, dcSuffix = 'AI'): + assert self.notify.debugStateCall(self) + + # Save the district Id (needed for calculations in AIRepository code) + self.districtId = districtId + self.districtName = districtName + self.districtType = districtType + + AIRepository.__init__( + self, mdip, mdport, esip, esport, dcFileNames, + serverId, + minChannel, maxChannel, dcSuffix) + self.setClientDatagram(0) + assert minChannel > districtId + if hasattr(self, 'setVerbose'): + if self.config.GetBool('verbose-airepository'): + self.setVerbose(1) + + # Save the state server id + self.serverId = serverId + + # Record the reason each client leaves the shard, according to + # the client. + self._avatarDisconnectReasons = {} + + # A list of avIds to pretend to disconnect at the next poll + # cycle, for debugging purposes only. + self._debugDisconnectIds = [] + + # player avatars will increment and decrement this count + self._population = 0 + + # The AI State machine + self.fsm = ClassicFSM.ClassicFSM('AIDistrict', + [State.State('off', + self.enterOff, + self.exitOff, + ['connect']), + State.State('connect', + self.enterConnect, + self.exitConnect, + ['districtReset', 'noConnection', + # I added this because Skyler removed the transition to + # districtReset -- Joe + 'playGame', + ]), + State.State('districtReset', + self.enterDistrictReset, + self.exitDistrictReset, + ['playGame','noConnection']), + State.State('playGame', + self.enterPlayGame, + self.exitPlayGame, + ['noConnection']), + State.State('noConnection', + self.enterNoConnection, + self.exitNoConnection, + ['connect'])], + # initial state + 'off', + # final state + 'off', + ) + + self.fsm.enterInitialState() + + self.fsm.request("connect") + + def uniqueName(self, desc): + return desc+"-"+str(self.districtId) + + def getGameDoId(self): + self.notify.error('derived must override') + + def incrementPopulation(self): + self._population += 1 + def decrementPopulation(self): + if __dev__: + assert self._population > 0 + self._population = max(0, self._population - 1) + + def getPopulation(self): + if simbase.fakeDistrictPopulations: + if not hasattr(self, '_fakePopulation'): + import random + self._fakePopulation = random.randrange(1000) + return self._fakePopulation + return self._population + + def printPopulationToLog(self, task): + self.notify.info("district-name %s | district-id %s | population %s" % (self.districtName, self.districtId, self._population)) + return Task.again + + # check if this is a player avatar in a location where they should not be + def _isValidPlayerLocation(self, parentId, zoneId): + return True + + #### Init #### + + def writeServerEvent(self, eventType, who, description): + AIRepository.writeServerEvent(self, eventType, who, description, + serverId=self.districtId) + + #### DistrictReset #### + def enterDistrictReset(self): + self.handler = self.handleDistrictReset + self.deleteDistrict(self.districtId) + + def exitDistrictReset(self): + self.handler = None + + def handleDistrictReset(self, msgType, di): + if msgType == STATESERVER_OBJECT_DELETE_RAM: + doId = di.getUint32() + self.notify.info("Got request to delete doId: " +str(doId)) + if(doId == self.districtId): + self.fsm.request("playGame") + elif msgType == STATESERVER_OBJECT_NOTFOUND: + doId = di.getUint32() + self.notify.info("Got Not Found For doId: " +str(doId)) + if(doId == self.districtId): + self.fsm.request("playGame") + else: + self.handleMessageType(msgType, di) + + #### DistrictReset #### + def enterPlayGame(self): + self._zoneDataStore = AIZoneDataStore() + AIRepository.enterPlayGame(self) + if simbase.config.GetBool('game-server-tests', 0): + from otp.distributed import DistributedTestObjectAI + self.testObject = DistributedTestObjectAI.DistributedTestObjectAI(self) + self.testObject.generateOtpObject(self.getGameDoId(), 3) + + taskMgr.doMethodLater(300, self.printPopulationToLog, self.uniqueName("printPopulationTask")) + + + def getZoneDataStore(self): + """This will crash (as designed) if called outside of the PlayGame state.""" + return self._zoneDataStore + + def getRender(self, parentId, zoneId): + # distributed objects should call getRender on themselves rather than + # call this function. Only call this for zones that are actively being + # used, otherwise the zone data will be destroyed before this function + # returns + zd = AIZoneData(self, parentId, zoneId) + render = zd.getRender() + zd.destroy() + return render + + def getNonCollidableParent(self, parentId, zoneId): + # distributed objects should call getNonCollidableParent on themselves rather than + # call this function. Only call this for zones that are actively being + # used, otherwise the zone data will be destroyed before this function + # returns + zd = AIZoneData(self, parentId, zoneId) + ncParent = zd.getNonCollidableParent() + zd.destroy() + return ncParent + + def getCollTrav(self, parentId, zoneId, *args, **kArgs): + # see comment in getRender + zd = AIZoneData(self, parentId, zoneId) + collTrav = zd.getCollTrav(*args, **kArgs) + zd.destroy() + return collTrav + + def getParentMgr(self, parentId, zoneId): + # see comment in getRender + zd = AIZoneData(self, parentId, zoneId) + parentMgr = zd.getParentMgr() + zd.destroy() + return parentMgr + + def exitPlayGame(self): + if simbase.config.GetBool('game-server-tests', 0): + self.testObject.requestDelete() + del self.testObject + self._zoneDataStore.destroy() + del self._zoneDataStore + taskMgr.remove(self.uniqueName("printPopulationTask")) + AIRepository.exitPlayGame(self) + + #### connect ##### + def enterConnect(self): + self.handler = self.handleConnect + self.lastMessageTime = 0 + + self.connect([self.mdurl], + successCallback = self._connected, + failureCallback = self._failedToConnect) + + def _failedToConnect(self, statusCode, statusString): + self.fsm.request("noConnection") + + def _connected(self): + # Register our channel + self.setConnectionName(self.districtName) + AIRepository._connected(self) + self.registerShardDownMessage(self.serverId) + if self.districtType is not None: + self.fsm.request("districtReset") + + def _handleValidDistrictDown(self, msgType, di): + downDistrictId = di.getUint32() + if (downDistrictId != self.districtId): + self.notify.error("Tried to bring down " + + str(self.districtId) + + " but " + + str(downDistrictId) + + " came down instead!") + else: + # We don't really need to do anything here. + pass + + def _handleIgnorableObjectDelete(self, msgType, di): + doId = di.getUint32() + self.notify.debug("Ignoring request to delete doId: " + + str(doId)) + + def _handleValidDistrictUp(self, msgType, di): + if msgType == STATESERVER_DISTRICT_UP: + upDistrictId = di.getUint32() + if (upDistrictId != self.districtId): + self.notify.error("Tried to bring up " + + str(self.districtId) + + " but " + + str(downDistrictId) + + " came up instead!") + else: + self.notify.info("District %s %s is up. Creating objects..." % + (self.districtId, self.districtName)) + self.fsm.request("playGame") + + def exitConnect(self): + self.handler = None + # Clean up the create district tasks + taskMgr.remove(self.uniqueName("newDistrictWait")) + del self.lastMessageTime + + def readerPollUntilEmpty(self, task): + # This overrides AIRepository.readerPollUntilEmpty() + # to provide an additional debugging hook. + + while self._debugDisconnectIds: + avId = self._debugDisconnectIds.pop() + self._doDebugDisconnectAvatar(avId) + + try: + return AIRepository.readerPollUntilEmpty(self, task) + except Exception, e: + appendStr(e, '\nSENDER ID: %s' % self.getAvatarIdFromSender()) + raise + + def handleReaderOverflow(self): + # may as well delete the shard at this point + self.deleteDistrict(self.districtId) + raise StandardError, ("incoming-datagram buffer overflowed, " + "aborting AI process") + + ##### General Purpose functions ##### + + def getAvatarExitEvent(self, avId): + return ("districtExit-" + str(avId)) + + def debugDisconnectAvatar(self, avId): + # This function will pretend to disconnect the indicated + # avatar at the next poll cycle, as if the avatar suddenly + # disconnected. This is for the purposes of debugging only. + # It makes the AI totally forget who this avatar is, but the + # avatar is still connected to the server. + self._debugDisconnectIds.append(avId) + + def _doDebugDisconnectAvatar(self, avId): + obj = self.doId2do.get(avId) + if obj: + self.deleteDistObject(obj) + self._announceDistObjExit(avId) + + def _announceDistObjExit(self, avId): + # This announces the exiting of this particular avatar + messenger.send(self.getAvatarExitEvent(avId)) + + # This announces generally that an avatar has left. + #messenger.send("avatarExited") + + # Now we don't need to store the disconnect reason any more. + try: + del self._avatarDisconnectReasons[avId] + except: + pass + + def setAvatarDisconnectReason(self, avId, disconnectReason): + # This is told us by the client just before he disconnects. + self._avatarDisconnectReasons[avId] = disconnectReason + + def getAvatarDisconnectReason(self, avId): + # Returns the reason (as reported by the client) for an + # avatar's unexpected exit, or 0 if the reason is unknown. It + # is only valid to query this during the handler for the + # avatar's unexpected-exit event. + return self._avatarDisconnectReasons.get(avId, 0) + + def _handleUnexpectedDistrictDown(self, di): + # Get the district Id + downDistrict = di.getUint32() + if downDistrict == self.districtId: + self.notify.warning("Somebody brought my district(" + + str(self.districtId) + + ") down! I'm shutting down!") + sys.exit() + else: + self.notify.warning("Weird... My district is " + + str(self.districtId) + + " and I just got a message that district " + + str(downDistrict) + + " is going down. I'm ignoring it." + ) + + def _handleUnexpectedDistrictUp(self, di): + # Get the district Id + upDistrict = di.getUint32() + if upDistrict == self.districtId: + self.notify.warning("Somebody brought my district(" + + str(self.districtId) + + ") up! I'm shutting down!") + sys.exit() + else: + self.notify.warning("Weird... My district is " + + str(self.districtId) + + " and I just got a message that district " + + str(upDistrict) + + " is coming up. I'm ignoring it." + ) + + def _handleMakeFriendsReply(self, di): + result = di.getUint8() + context = di.getUint32() + messenger.send("makeFriendsReply", [result, context]) + + def _handleRequestSecretReply(self, di): + result = di.getUint8() + secret = di.getString() + requesterId = di.getUint32() + messenger.send("requestSecretReply", [result, secret, requesterId]) + + def _handleSubmitSecretReply(self, di): + result = di.getUint8() + secret = di.getString() + requesterId = di.getUint32() + avId = di.getUint32() + self.writeServerEvent('entered-secret', requesterId, '%s|%s|%s' % (result, secret, avId)) + messenger.send("submitSecretReply", [result, secret, requesterId, avId]) + + def registerShardDownMessage(self, stateserverid): + datagram = PyDatagram() + datagram.addServerHeader( + stateserverid, self.ourChannel, STATESERVER_SHARD_REST) + datagram.addChannel(self.ourChannel) + # schedule for execution on socket close + self.addPostSocketClose(datagram) + + def sendSetZone(self, distobj, zoneId): + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + # Add the zone parent id + # HACK: + parentId = oldParentId = self.districtId + datagram.addUint32(parentId) + # Put in the zone id + datagram.addUint32(zoneId) + # Send it + self.send(datagram) + # The servers don't inform us of this zone change, because we're the + # one that requested it. Update immediately. + # TODO: pass in the old parent and old zone + distobj.setLocation(parentId, zoneId) #, oldParentId, distobj.zoneId) + + def deleteDistrict(self, districtId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.serverId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(districtId) + # Send the message + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def makeFriends(self, avatarAId, avatarBId, flags, context): + """ + Requests to make a friendship between avatarA and avatarB with + the indicated flags (or upgrade an existing friendship with + the indicated flags). The context is any arbitrary 32-bit + integer. When the friendship is made, or the operation fails, + the "makeFriendsReply" event is generated, with two + parameters: an integer result code, and the supplied context. + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID, self.ourChannel, DBSERVER_MAKE_FRIENDS) + + # Indicate the two avatars who are making friends + datagram.addUint32(avatarAId) + datagram.addUint32(avatarBId) + datagram.addUint8(flags) + datagram.addUint32(context) + self.send(datagram) + + def requestSecret(self, requesterId): + """ + Requests a "secret" from the database server. This is a + unique string that will be associated with the indicated + requesterId, for the purposes of authenticating true-life + friends. + + When the secret is ready, a "requestSecretReply" message will + be thrown with three parameters: the result code (0 or 1, + indicating failure or success), the generated secret, and the + requesterId again. + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID,self.ourChannel,DBSERVER_REQUEST_SECRET) + + # Indicate the number we want to associate with the new secret. + datagram.addUint32(requesterId) + # Send it off! + self.send(datagram) + + def submitSecret(self, requesterId, secret): + """ + Submits a "secret" back to the database server for validation. + This attempts to match the indicated string, entered by the + user, to a string returned by a previous call to + requestSecret(). + + When the response comes back from the server, a + "submitSecretReply" message will be thrown with four + parameters: the result code (0 or 1, indicating failure or + success), the secret again, the requesterId again, and the + number associated with the original secret (that is, the + original requesterId). + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID, self.ourChannel, DBSERVER_SUBMIT_SECRET) + # Pass in our identifying number, and the string. + datagram.addUint32(requesterId) + datagram.addString(secret) + self.send(datagram) + + def replaceMethod(self, oldMethod, newFunction): + return 0 diff --git a/otp/src/ai/AIInterestHandles.py b/otp/src/ai/AIInterestHandles.py new file mode 100644 index 0000000..72ad745 --- /dev/null +++ b/otp/src/ai/AIInterestHandles.py @@ -0,0 +1,19 @@ + +# AIInterestHandles +# +# These are reserved interest handle id's to be used with calls +# to AIRepository.addInterestToConnection(). The high bit (16th) is set +# on all handle id's assigned by this function call so as not to collide +# with handles assigned by the Client. + +# Pirates +PIRATES_CARDGAME = 1 +PIRATES_CREW = 2 +PIRATES_GUILD = 3 +PIRATES_FRIENDS = 4 + +PIRATES_BAND = 5 +PIRATES_PVP_RESPAWN = 6 +PIRATES_TREASUREMAP = 7 + +PIRATES_SHIPPVP = 8 diff --git a/otp/src/ai/AIMsgTypes.py b/otp/src/ai/AIMsgTypes.py new file mode 100644 index 0000000..18de7d6 --- /dev/null +++ b/otp/src/ai/AIMsgTypes.py @@ -0,0 +1,118 @@ +""" +AIMsgTypes module: +Contains AI specific network message types +""" + +from otp.distributed.OtpDoGlobals import * +from direct.showbase.PythonUtil import invertDictLossless + +# top level object (root): +OTP_SERVER_ROOT_DO_ID = 4007 + +CHANNEL_CLIENT_BROADCAST = 4014 + +BAD_CHANNEL_ID = 0 # 0xffffffffffffffff # -1 +BAD_ZONE_ID = 0 # 0xffffffff # -1 +BAD_DO_ID = 0 # 0xffffffff # -1 + +# Control Transactions +CONTROL_MESSAGE = 4001 +CONTROL_SET_CHANNEL = 2001 +CONTROL_REMOVE_CHANNEL = 2002 + +CONTROL_SET_CON_NAME = 2004 +CONTROL_SET_CON_URL = 2005 + +CONTROL_ADD_RANGE = 2008 +CONTROL_REMOVE_RANGE = 2009 +CONTROL_ADD_POST_REMOVE = 2010 # ADD A MESSAGE TO THE CLOSING EVENT ON A DIRECTOR SOCKET +CONTROL_CLEAR_POST_REMOVE = 2011 # CLEAR ALL THE EVENTS.. + +AIMsgName2Id = { + # State Server Transactions + 'STATESERVER_OBJECT_GENERATE_WITH_REQUIRED': 2001, + 'STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER': 2003, + 'STATESERVER_OBJECT_UPDATE_FIELD': 2004, + 'STATESERVER_OBJECT_UPDATE_FIELD_MULTIPLE': 2005, + 'STATESERVER_OBJECT_DELETE_RAM': 2007, + 'STATESERVER_OBJECT_SET_ZONE': 2008, + 'STATESERVER_OBJECT_CHANGE_ZONE': 2009, + 'STATESERVER_OBJECT_NOTFOUND': 2015, + + 'STATESERVER_QUERY_OBJECT_ALL': 2020, + 'STATESERVER_QUERY_ZONE_OBJECT_ALL': 2021, + 'STATESERVER_OBJECT_LOCATE': 2022, + 'STATESERVER_OBJECT_LOCATE_RESP': 2023, + 'STATESERVER_OBJECT_QUERY_FIELD': 2024, # See 2062 + 'STATESERVER_QUERY_OBJECT_ALL_RESP': 2030, + + 'STATESERVER_SHARD_REST': 2061, + 'STATESERVER_ADD_AI_RECV': 2045, + 'STATESERVER_QUERY_ZONE_OBJECT_ALL_DONE': 2046, + 'STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT': 2050, + 'STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT': 2051, + 'STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT_RESP': 2052, + 'STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT_RESP': 2053, + 'STATESERVER_OBJECT_DELETE_DISK': 2060, + 'STATESERVER_OBJECT_QUERY_FIELD_RESP': 2062, # See 2024 + + 'STATESERVER_OBJECT_ENTERZONE_WITH_REQUIRED_OTHER': 2066, + 'STATESERVER_OBJECT_ENTER_AI_RECV': 2067, + 'STATESERVER_OBJECT_LEAVING_AI_INTEREST': 2033, # This is the new name for 2033 + + 'STATESERVER_OBJECT_ENTER_OWNER_RECV': 2068, # new obj with owner + 'STATESERVER_OBJECT_CHANGE_OWNER_RECV': 2069, # obj has new owner + 'STATESERVER_OBJECT_SET_OWNER_RECV': 2070, # ??? + + 'STATESERVER_OBJECT_QUERY_FIELDS': 2080, + 'STATESERVER_OBJECT_QUERY_FIELDS_RESP': 2081, + 'STATESERVER_OBJECT_QUERY_FIELDS_STRING': 2082, + 'STATESERVER_OBJECT_QUERY_MANAGING_AI': 2083, # Should not be received by python code (it's for roger's server) + 'STATESERVER_BOUNCE_MESSAGE': 2086, + + 'STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL': 2087, + 'STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL_DONE': 2089, + 'STATESERVER_QUERY_OBJECT_CHILDREN_RESP': 2087, + + 'ACCOUNT_AVATAR_USAGE': 3005, # Avatar online or offline + 'ACCOUNT_ACCOUNT_USAGE': 3006, # Account login or log off + + 'CLIENT_AGENT_OPEN_CHANNEL': 3104, + 'CLIENT_AGENT_CLOSE_CHANNEL': 3105, + 'CLIENT_AGENT_SET_INTEREST': 3106, + 'CLIENT_AGENT_REMOVE_INTEREST': 3107, + + + 'CHANNEL_PUPPET_ACTION': 4004, # Account and Avatar online or offline + + # direct-to-database-server transactions + 'DBSERVER_MAKE_FRIENDS': 1017, + 'DBSERVER_MAKE_FRIENDS_RESP': 1031, + 'DBSERVER_REQUEST_SECRET': 1025, + 'DBSERVER_REQUEST_SECRET_RESP': 1026, + 'DBSERVER_SUBMIT_SECRET': 1027, + 'DBSERVER_SUBMIT_SECRET_RESP': 1028, + + 'DBSERVER_CREATE_STORED_OBJECT': 1003, + 'DBSERVER_CREATE_STORED_OBJECT_RESP': 1004, + 'DBSERVER_DELETE_STORED_OBJECT': 1008, + + 'DBSERVER_GET_STORED_VALUES': 1012, + 'DBSERVER_GET_STORED_VALUES_RESP': 1013, + 'DBSERVER_SET_STORED_VALUES': 1014, + + 'SERVER_PING': 5002, + } + +# create id->name table for debugging +AIMsgId2Names = invertDictLossless(AIMsgName2Id) + +# put msg names in module scope, assigned to msg value +for name, value in AIMsgName2Id.items(): + exec '%s = %s' % (name, value) +del name, value + +# The ID number of the database server. The above direct-to-dbserver +# transactions are sent to this ID. +DBSERVER_ID = 4003 + diff --git a/otp/src/ai/AIRepository.py b/otp/src/ai/AIRepository.py new file mode 100644 index 0000000..4bbce7e --- /dev/null +++ b/otp/src/ai/AIRepository.py @@ -0,0 +1,1913 @@ +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from AIMsgTypes import * +from direct.showbase.PythonUtil import Functor +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.task import Task +from direct.distributed.AsyncRequest import cleanupAsyncRequests +from direct.distributed import ParentMgr +from direct.distributed.ConnectionRepository import ConnectionRepository +import sys +import os +import copy +import types +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from direct.distributed.NetMessenger import NetMessenger +from direct.showbase.ContainerLeakDetector import ContainerLeakDetector +from direct.showbase import MessengerLeakDetector +from direct.showbase import LeakDetectors +from direct.showbase.GarbageReportScheduler import GarbageReportScheduler +from otp.avatar.DistributedPlayerAI import DistributedPlayerAI +from otp.distributed import OtpDoGlobals +from otp.ai.GarbageLeakServerEventAggregatorAI import GarbageLeakServerEventAggregatorAI +import time +import gc + +class AIRepository(ConnectionRepository): + """ + The new AIRepository base class + + It does not have: + - district or shard code (see AIDistrict.py) + - friends or secret friends code + - a collision traverser + + of course, a derived class may add those. + + It does have: + + object creation code + + channel listening code + + context allocator/manager + """ + notify = directNotify.newCategory("AIRepository") + + InitialContext = 100000 + + def __init__( + self, mdip, mdport, esip, esport, dcFileNames, + serverId, + minChannel, maxChannel, dcSuffix = 'AI'): + assert self.notify.debugStateCall(self) + self._channels={} + self.AIRunningNetYield = simbase.config.GetBool('ai-running-net-yield', 0) + + self._msgBundleNames = [] + + # doId->requestDeleted object + self._requestDeletedDOs = {} + + ConnectionRepository.__init__( + self, ConnectionRepository.CM_NATIVE, simbase.config) + self.dcSuffix = dcSuffix + + simbase.setupCpuAffinities(minChannel) + + self.distributedObjectRequests=set() + + self.context=self.InitialContext + self.contextToClassName={} + self.setClientDatagram(0) + + self.readDCFile(dcFileNames = dcFileNames) + + # Save the state server id + self.serverId = serverId + + # Save the connect info + self.mdurl = URLSpec(mdip, 1) + self.esurl = URLSpec(esip, 1) + + if not self.mdurl.hasPort(): + self.mdurl.setPort(mdport) + + if not self.esurl.empty() and not self.esurl.hasPort(): + self.esurl.setPort(esport) + + self.notify.info("event server at %s." % (repr(self.esurl))) + + # UDP socket for sending events to the event server. + self.udpSock = None + + if not self.esurl.empty(): + udpEventServer = SocketAddress() + if not udpEventServer.setHost(self.esurl.getServer(), self.esurl.getPort()): + self.notify.warning("Invalid host for event server: %s" % (self.esurl)) + + self.udpSock = SocketUDPOutgoing() + self.udpSock.InitToAddress(udpEventServer) + + # Save the ranges of channels that the AI controls + self.minChannel = minChannel + self.maxChannel = maxChannel + self.notify.info("dynamic doIds in range [%s, %s], total %s" % ( + minChannel, maxChannel, maxChannel - minChannel + 1)) + assert maxChannel >= minChannel + + # initialize the channel allocation + self.channelAllocator = UniqueIdAllocator(minChannel, maxChannel) + + # Define the ranges of zones. + self.minZone = self.getMinDynamicZone() + self.maxZone = self.getMaxDynamicZone() + self.notify.info("dynamic zoneIds in range [%s, %s], total %s" % ( + self.minZone, self.maxZone, self.maxZone - self.minZone + 1)) + assert self.maxZone >= self.minZone + self.zoneAllocator = UniqueIdAllocator(self.minZone, self.maxZone) + + if config.GetBool('detect-leaks', 0) or config.GetBool('ai-detect-leaks', 0): + self.startLeakDetector() + + if config.GetBool('detect-messenger-leaks', 0) or config.GetBool('ai-detect-messenger-leaks', 0): + self.messengerLeakDetector = MessengerLeakDetector.MessengerLeakDetector( + 'AI messenger leak detector') + if config.GetBool('leak-messages', 0): + MessengerLeakDetector._leakMessengerObject() + + if config.GetBool('run-garbage-reports', 0) or config.GetBool('ai-run-garbage-reports', 0): + noneValue = -1. + reportWait = config.GetFloat('garbage-report-wait', noneValue) + reportWaitScale = config.GetFloat('garbage-report-wait-scale', noneValue) + if reportWait == noneValue: + reportWait = None + if reportWaitScale == noneValue: + reportWaitScale = None + self.garbageReportScheduler = GarbageReportScheduler(waitBetween=reportWait, + waitScale=reportWaitScale) + + self._proactiveLeakChecks = (config.GetBool('proactive-leak-checks', 1) and + config.GetBool('ai-proactive-leak-checks', 1)) + self._crashOnProactiveLeakDetect = config.GetBool('crash-on-proactive-leak-detect', 1) + + # Give ourselves the first channel in the range + self.ourChannel = self.allocateChannel() + + # These are used to query database objects directly; currently + # used only for offline utilities. + self.dbObjContext = 0 + self.dbObjMap = {} + + # The UtilityAIRepository sets this to 0 to indicate we should + # not do things like issue new catalogs to toons that we load + # in. However, in the normal AI repository, we should do + # these things. + self.doLiveUpdates = 1 + + #for generating unqiue names for non-dos, manly used for tasks + self.keyCounter = 0 + + self.MaxEpockSpeed = self.config.GetFloat('ai-net-yield-epoch', 1.0/30.0) + + if self.AIRunningNetYield : + taskMgr.doYield =self.taskManagerDoYieldNetwork + + # use this for time yields without sleeps + #taskMgr.doYield = self.taskManagerDoYield + + # Used for moderation of report-a-player feature + self.centralLogger = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, + "CentralLogger") + + self.garbageLeakLogger = GarbageLeakServerEventAggregatorAI(self) + + taskMgr.add(self._checkBundledMsgs, 'checkBundledMsgs', priority=-100) + + # skip a bit so we miss the startup sequence (it has very long frames as things are set up) + taskMgr.doMethodLater(2 * 60., self._startPerformanceLogging, + 'startPerformanceLogging') + + self.connectionName = None + self.connectionURL = None + + def _startPerformanceLogging(self, task=None): + period = self.config.GetFloat( + 'ai-performance-log-period', + self.config.GetFloat('server-performance-log-period', + choice(__dev__, 60. * 10., 60.) + ) + ) + self._sampledMaxFrameDuration = 0. + self._sampleMaxFrameDuration() + self._numPyObjs = None + self._getNumObjCounterLimit = int(max(1, (60 * 60.) / period)) + self._getNumObjCounter = 0 + taskMgr.doMethodLater(period, self._logPerformanceData, 'logPerformanceData') + return Task.done + + def _sampleMaxFrameDuration(self, task=None): + self._sampledMaxFrameDuration = max(self._sampledMaxFrameDuration, + globalClock.getMaxFrameDuration()) + # call this at a higher frequency than the frequency at which the global + # clock completely replaces its frame duration samples. that should ensure that + # we don't miss a slow frame in the performance logging + taskMgr.doMethodLater(globalClock.getAverageFrameRateInterval() * .75, + self._sampleMaxFrameDuration, 'sampleMaxFrameDuration') + return Task.done + + def _logPerformanceData(self, task=None): + avgFrameDur = 1. / globalClock.getAverageFrameRate() + maxFrameDur = max(self._sampledMaxFrameDuration, + globalClock.getMaxFrameDuration()) + self._sampledMaxFrameDuration = 0. + # gc.get_objects can be slow. only sample object count every once in a while + self._getNumObjCounter -= 1 + if self._getNumObjCounter <= 0: + self._numPyObjs = len(gc.get_objects()) + self._getNumObjCounter = self._getNumObjCounterLimit + self.notify.info( + 'avg frame duration=%fs, max frame duration=%fs, num Python objects=%s' % ( + avgFrameDur, maxFrameDur, self._numPyObjs)) + return Task.again + + def startLeakDetector(self): + if hasattr(self, 'leakDetector'): + return False + firstCheckDelay = config.GetFloat('leak-detector-first-check-delay', -1) + if firstCheckDelay == -1: + firstCheckDelay = None + self.leakDetector = ContainerLeakDetector( + 'AI container leak detector', firstCheckDelay = firstCheckDelay) + self.accept(self.leakDetector.getLeakEvent(), self._handleLeak) + self.objectTypesLeakDetector = LeakDetectors.ObjectTypesLeakDetector() + self.garbageLeakDetector = LeakDetectors.GarbageLeakDetector() + self.cppMemoryUsageLeakDetector = LeakDetectors.CppMemoryUsage() + self.taskLeakDetector = LeakDetectors.TaskLeakDetector() + self.messageListenerTypesLeakDetector = LeakDetectors.MessageListenerTypesLeakDetector() + # this isn't necessary with the current messenger implementation + #self.messageTypesLeakDetector = LeakDetectors.MessageTypesLeakDetector() + return True + + def _getMsgName(self, msgId): + # we might get a list of message names, use the first one + return makeList(AIMsgId2Names.get(msgId, 'UNKNOWN MESSAGE: %s' % msgId))[0] + + def _handleLeak(self, container, containerName): + # TODO: send email with warning + self.notify.info('sending memory leak server event') + self.writeServerEvent('memoryLeak', self.ourChannel, '%s|%s|%s|%s' % ( + containerName, len(container), itype(container), + fastRepr(container, maxLen=1, strFactor=50))) + + def getPlayerAvatars(self): + return [i for i in self.doId2do.values() + if isinstance(i, DistributedPlayerAI)] + + def uniqueName(self, desc): + return desc+"-"+str(self.serverId) + + def trueUniqueName(self, desc): + self.keyCounter += 1 + return desc+"-"+str(self.serverId)+"-"+str(self.keyCounter) + + def allocateContext(self): + self.context+=1 + if self.context >= (1<<32): + self.context=self.InitialContext + return self.context + + #### Init #### + + def writeServerEvent(self, eventType, who, description, serverId=None): + """ + Sends the indicated event data to the event server via UDP for + recording and/or statistics gathering. All related events + should be given the same eventType (some arbitrary string). + The who field should be the numeric or alphanumeric + representation of who generated this activity; generally this + will be an avatarId or the doId of the AI or something. The + description is a one-line description of the event, possibly + with embedded vertical bars as separators. + """ + if not self.udpSock: + self.notify.debug("Unable to log server event: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + who = str(who) + + # log the access string for avatar-related messages + doId = None + try: + doId = int(who) + except: + pass + if doId is not None and doId in self.doId2do: + av = self.getDo(doId) + if hasattr(av, 'getAccess'): + description = '%s|%s' % (description, av.getAccess()) + + # break it up into chunks that will fit in a UDP packet (max 1024 bytes) + # leave some room for the header/eventType/who + maxLen = 900 + breakCount = 0 + # always run this loop at least once + while True: + if breakCount > 0: + eventType = '%s-continued%s' % (eventType, breakCount) + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + 2 + len(eventType) + 2 + len(who) + 2 + len(description) + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(1) # message type 1: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(eventType) + dg.addString(who) + dg.addString(description[:maxLen]) + description = description[maxLen:] + + self.notify.debug('%s|AIevent:%s|%s|%s' % (eventType, self.serverId, who, description)) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server event: %s" % (self.udpSock.GetLastError())) + + if len(description) == 0: + break + breakCount += 1 + + + def writeServerStatus(self, who, avatar_count, object_count): + """ + Sends the Status Packet to the event server via UDP for + recording. Used to monito the health of a AI Server. + """ + if not self.udpSock: + self.notify.debug("Unable to log server Status: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + #who = str(who) + who=""; + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + (len(who)+2) + 4 +4 + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(2) # message type 2: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(who) + dg.addUint32(avatar_count) + dg.addUint32(object_count) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server status: %s" % (self.udpSock.GetLastError())) + + def writeServerStatus2(self, who, avatar_count, object_count): + """ + Sends the Status Packet to the event server via UDP for + recording. Used to monito the health of a AI Server. + ServerStatus2 has an additional channel code added for a ping message + We can consolidate the two writeServerStatus messages when toontown can + adopt the new otp_server + """ + if not self.udpSock: + self.notify.debug("Unable to log server Status: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + #who = str(who) + who=""; + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + (len(who)+2) + 8 + 4 + 4 + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(3) # message type 2: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(who) + dg.addUint64(self.ourChannel) + dg.addUint32(avatar_count) + dg.addUint32(object_count) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server status: %s" % (self.udpSock.GetLastError())) + + ##### Off ##### + def enterOff(self): + self.handler = None + + def exitOff(self): + self.handler = None + + #### connect ##### + def enterConnect(self): + self.handler = self.handleConnect + self.lastMessageTime = 0 + + self.connect([self.mdurl], + successCallback = self._connected, + failureCallback = self._failedToConnect) + + def _failedToConnect(self, statusCode, statusString): + self.fsm.request("noConnection") + + def _connected(self): + # Register our channel + self.registerForChannel(self.ourChannel) + self.netMessenger = NetMessenger(self, (OTP_NET_MSGR_CHANNEL_ID_ALL_AI,)) + ## self.netMessenger.accept("transferDo", self, self.handleTransferDo) + + def _handleIgnorableObjectDelete(self, msgType, di): + doId = di.getUint32() + self.notify.debug("Ignoring request to delete doId: " + + str(doId)) + + def exitConnect(self): + self.handler = None + # Clean up the create district tasks + taskMgr.remove(self.uniqueName("newDistrictWait")) + del self.lastMessageTime + + ##### PlayGame ##### + + def enterPlayGame(self): + self.handler = self.handlePlayGame + self.createObjects() + + def handleConnect(self, msgType, di): + self.lastMessageTime = globalClock.getRealTime() + + if msgType == STATESERVER_DISTRICT_DOWN: + self._handleValidDistrictDown(msgType, di) + elif msgType == STATESERVER_DISTRICT_UP: + self._handleValidDistrictUp(msgType, di) + elif msgType == STATESERVER_OBJECT_DELETE_RAM: + self._handleIgnorableObjectDelete(msgType, di) + else: + self.handleMessageType(msgType, di) + + def handlePlayGame(self, msgType, di): + # NOTE: Inheritors may override this to check their + # own message types before calling this handler + # See ToontownAIRepository.py for example. + if msgType == STATESERVER_OBJECT_UPDATE_FIELD: + self._handleUpdateField(di) + elif msgType == STATESERVER_OBJECT_CHANGE_ZONE: + self._handleObjectChangeZone(di) + elif msgType == STATESERVER_OBJECT_DELETE_RAM: + self._handleDeleteObject(di) + elif msgType == DBSERVER_MAKE_FRIENDS_RESP: + self._handleMakeFriendsReply(di) + elif msgType == DBSERVER_REQUEST_SECRET_RESP: + self._handleRequestSecretReply(di) + elif msgType == DBSERVER_SUBMIT_SECRET_RESP: + self._handleSubmitSecretReply(di) + else: + self.handleMessageType(msgType, di) + + def handleAvatarUsage(self, di): + """ + Should only be handled by the UD process containing the AvatarManagerUD + """ + pass + + def handleAccountUsage(self, di): + """ + Should only be handled by the UD process containing the AvatarManagerUD + """ + pass + + def handleObjectDeleteDisk(self, di): + pass + + def handleObjectQueryField(self, di): + assert self.notify.debugStateCall(self) + + doId = di.getUint32() + fieldId = di.getUint16() + context = di.getUint32() + success = di.getUint8() + if success and context: + className = self.contextToClassName.pop(context, None) + # This prevents a crash that occurs when an AI sends a query, + # crashes, comes back up, then receives the response + if className: + dclass = self.dclassesByName.get(className) + interface = dclass.getFieldByIndex(fieldId) + packer = DCPacker() + packer.setUnpackData(di.getRemainingBytes()) + packer.beginUnpack(interface) + value = packer.unpackObject() + messenger.send( + "doFieldResponse-%s"%(context,), [context, value]) + + def handleObjectQueryFields(self, di): + assert self.notify.debugStateCall(self) + + doId = di.getUint32() + context = di.getUint32() + success = di.getUint8() + if success and context: + className = self.contextToClassName.pop(context) + # This prevents a crash that occurs when an AI sends a query, + # crashes, comes back up, then receives the response + if className: + dclass = self.dclassesByName.get(className) + packer = DCPacker() + objData = {} + + while di.getRemainingSize() > 0: + fieldId = di.getUint16() + interface = dclass.getFieldByIndex(fieldId) + packer.setUnpackData(di.getRemainingBytes()) + packer.beginUnpack(interface) + value = packer.unpackObject() + packer.endUnpack() + objData[interface.getName()] = value + di.skipBytes(packer.getNumUnpackedBytes()) + + messenger.send("doFieldResponse-%s"%(context,),[context,objData]) + else: + self.notify.warning("STATESERVER_OBJECT_QUERY_FIELDS_RESP received with invalid context: %s" % context) + messenger.send("doFieldQueryFailed-%s"%(context),[context]) + else: + messenger.send("doFieldQueryFailed-%s"%(context),[context]) + + def handleServerPing(self, di): + assert self.notify.debugStateCall(self) + # Deconstruct ping message from stateserver + sec = di.getUint32() + usec = di.getUint32() + url = di.getString() + channel = di.getUint32() + + # Send ping back to state server + datagram = PyDatagram() + sender=self.getMsgSender() + datagram.addServerHeader( + sender, self.ourChannel, SERVER_PING) + # A context that can be used to index the response if needed + datagram.addUint32(sec) + datagram.addUint32(usec) + datagram.addString(url) + datagram.addUint32(channel) + self.send(datagram) + + + + def handleMessageType(self, msgType, di): + if msgType == CLIENT_GET_STATE_RESP: + # This one comes back after we sendSetAvatarIdMsg() + pass + elif msgType == DBSERVER_GET_STORED_VALUES_RESP: + self._handleDatabaseGetStoredValuesResp(di) + elif msgType == DBSERVER_CREATE_STORED_OBJECT_RESP: + self._handleDatabaseCreateStoredObjectResp(di) + elif msgType == STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT_RESP: + self._handleDatabaseGenerateResponse(di) + elif msgType == STATESERVER_OBJECT_SET_ZONE: + # import pdb;pdb.set_trace() + self.handleSetLocation(di) + elif msgType == STATESERVER_OBJECT_LEAVING_AI_INTEREST: + # import pdb;pdb.set_trace() + self.handleDistObjExit(di) + elif msgType == STATESERVER_QUERY_OBJECT_ALL_RESP: + self.handleDistObjRequestResponse(di) + elif msgType == STATESERVER_OBJECT_ENTER_AI_RECV: + self.handleDistObjEnter(di) + elif msgType == STATESERVER_OBJECT_ENTERZONE_WITH_REQUIRED_OTHER: + self.handleDistObjEnterZone(di) + elif msgType == STATESERVER_QUERY_OBJECT_CHILDREN_RESP: + # This acts much like a generate with required and other + self.handleDistObjEnter(di) + elif msgType == STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL_DONE: + # This acts much like a generate with required and other + self.handleQueryObjectChildrenLocalDone(di) + elif msgType == STATESERVER_OBJECT_ENTER_OWNER_RECV: + # This message is sent at some stage during avatar creation. + self.handleDistObjEnterOwner(di) + elif msgType == STATESERVER_OBJECT_CHANGE_OWNER_RECV: + self.handleDistObjChangeOwner(di) + elif msgType == ACCOUNT_AVATAR_USAGE: + self.handleAvatarUsage(di) + elif msgType == ACCOUNT_ACCOUNT_USAGE: + self.handleAccountUsage(di) + elif msgType == STATESERVER_OBJECT_DELETE_DISK: + self.handleObjectDeleteDisk(di) + elif msgType == STATESERVER_OBJECT_QUERY_FIELD_RESP: + self.handleObjectQueryField(di) + elif msgType == STATESERVER_OBJECT_QUERY_FIELDS_RESP: + self.handleObjectQueryFields(di) + elif msgType == STATESERVER_OBJECT_QUERY_MANAGING_AI: + pass + elif msgType == SERVER_PING: + self.handleServerPing(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected message type: %s in state: %s" % + (msgType, self.fsm.getCurrentState().getName())) + if __dev__: + import pdb + pdb.set_trace() + + def exitPlayGame(self): + self.handler = None + self.stopReaderPollTask() + + self.deleteDistributedObjects() + cleanupAsyncRequests() + + # Make sure there are no leftover tasks that shouldn't be here. + for task in taskMgr.getTasks(): + if (task.name in ("igLoop", + "aiSleep", + "doLaterProcessor", + "eventManager", + "tkLoop", + )): + # These tasks are ok + continue + else: + print taskMgr + self.notify.error("You can't leave otp until you clean up your tasks.") + + # Make sure there are no event hooks still hanging. + if not messenger.isEmpty(): + print messenger + self.notify.error("Messenger should not have any events in it.") + + ##### NoConnection ##### + + def enterNoConnection(self): + self.handler = self.handleMessageType + + AIRepository.notify.warning( + "Failed to connect to message director at %s." % (repr(self.mdurl))) + # Wait five seconds, then try to reconnect + taskMgr.doMethodLater(5, self.reconnect, self.uniqueName("waitToReconnect")) + + def reconnect(self, task): + self.fsm.request("connect") + return Task.done + + def exitNoConnection(self): + self.handler = None + taskMgr.remove(self.uniqueName("waitToReconnect")) + + ##### General Purpose functions ##### + + def createObjects(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. This is where you should create + # DistributedObjectAI's (and generate them), create the objects + # that manage them, as well as spawn + # any tasks that will create DistributedObjectAI's in the future. + pass + + def getHandleClassNames(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. + + # This function should return a tuple or list of string names + # that represent distributed object classes for which we want + # to make a special 'handle' class available. For instance, + # to make the DistributedToonHandleAI class available, this + # function should return ('DistributedToon',). + return () + + def handleDistObjRequestResponse(self, di): + assert self.notify.debugStateCall(self) + context = di.getUint32() + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # if there's no context, there's no point in doing anything else + if context: + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di, addToTables=False) + #distObj.isQueryAllResponse = True + + self.writeServerEvent('doRequestResponse', doId, '%s'%(context,)) + messenger.send("doRequestResponse-%s"%(context,), [context, distObj]) + + def postGenerate(self, context, distObj): + parentId = distObj.parentId + zoneId = distObj.zoneId + doId = distObj.doId + self.distributedObjectRequests.discard(doId) + distObj.setLocation(parentId, zoneId) + self.writeServerEvent('distObjEnter', doId, '') + + def handleDistObjEnter(self, di): + assert self.notify.debugStateCall(self) + context = di.getUint32() + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Is it in our dictionary? + if self.doId2do.has_key(doId): + self.notify.warning("Object Entered " + str(doId) + + " re-entered without exiting") + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di) + self.postGenerate(context, distObj) + + # Put it in the dictionary - Is it already in our dictionary? Not + # sure why or how this happens, but it does come up from time to + # time in production + # Asad Todo take out this security check for now don't publish to toontown!!! +## if self.doId2do.has_key(doId): +## self.writeServerEvent('suspicious', doId, 'Avatar re-entered without exiting') +## # Note: until we figure out what is causing this bug, it will be an error +## # This is listed as bug 50608 in the production remarks db +## self.notify.error("Avatar %s re-entered without exiting" % doId) + + + def handleDistObjEnterZone(self, di): + assert self.notify.debugStateCall(self) + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Is it in our dictionary? + if self.doId2do.has_key(doId): + self.notify.warning("Object Entered " + str(doId) + + " re-entered without exiting") + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di) + # self.postGenerate(context, distObj) + + # Put it in the dictionary - Is it already in our dictionary? Not + # sure why or how this happens, but it does come up from time to + # time in production + # Asad Todo take out this security check for now don't publish to toontown!!! +## if self.doId2do.has_key(doId): +## self.writeServerEvent('suspicious', doId, 'Avatar re-entered without exiting') +## # Note: until we figure out what is causing this bug, it will be an error +## # This is listed as bug 50608 in the production remarks db +## self.notify.error("Avatar %s re-entered without exiting" % doId) + + + def handleDistObjEnterOwner(self, di): + # TEMP + return self.handleDistObjEnter(di) + """ + assert self.notify.debugStateCall(self) + # The context is bogus + context = di.getUint32() + # The zone the avatar is in + parentId = di.getUint32() + zoneId = di.getUint32() + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + self.notify.info( + 'ignoring owner create, context=%s, parentId=%s, ' + 'zoneId=%s, doId=%s, dclass=%s' % ( + context, parentId, zoneId, doId, dclass.getName())) + """ + + def handleDistObjChangeOwner(self, di): + assert self.notify.debugStateCall(self) + doId = di.getUint32() + newOwnerId = di.getUint32() + oldOwnerId = di.getUint32() + self.notify.info( + 'ignoring owner change, ' + 'doId=%s, newOwnerId=%s, oldOwnerID=%s' % ( + doId, newOwnerId, oldOwnerId)) + + def getDeleteDoIdEvent(self, doId): + # this event is sent after the object is deleted, + # and is sent even if the object was not in the tables. + return 'AIRDeleteDoId-%s' % doId + + def handleDistObjExit(self, di): + # Get the distributed object id + doId = di.getUint32() + self.notify.debug("handleDistObjExit %s" % doId) + + # If it is in the dictionary, remove it. + obj = self.doId2do.get(doId) + if obj: + self.deleteDistObject(obj) + else: + self.notify.warning("DistObj " + str(doId) + + " exited, but never entered.") + + # announce delete event for this doId, even if obj doesn't exist + # send it after we delete any existing object + messenger.send(self.getDeleteDoIdEvent(doId)) + + # TODO: remove this in favor of getDeleteDoIdEvent + # Throw an event telling everyone that the distObj is gone + self._announceDistObjExit(doId) + + def _announceDistObjExit(self, doId): + pass + + def _generateFromDatagram(self, parentId, zoneId, dclass, doId, di, addToTables=True): + if (self.doId2do.has_key(doId)): + # added to prevent objects already generated from being generated again (was + # happening with some traded inventory objects, quests specfically) + return self.doId2do[doId] + # We got a datagram telling us to create a new DO instance + classDef = dclass.getClassDef() + try: + distObj = classDef(self) + except TypeError, e: + self.notify.error('%s (class %s, parentId %d, zoneId %d, doId %d)' % \ + (e, dclass.getName(), parentId, zoneId, doId)) + distObj.dclass = dclass + # Assign it an Id + distObj.doId = doId + # Since the distObj has been created explicitly from the + # server, we do not own its doId, and hence we shouldn't try + # to deallocate it. + distObj.doNotDeallocateChannel = 1 + + # init the parentId and zoneId + if addToTables: + # add the new DO to the tables + # let addDoToTables set the parentId and zoneId, it ignores + # the location if it matches what's already on the object + distObj.parentId = None + distObj.zoneId = None + self.addDOToTables(distObj, location = (parentId,zoneId)) + else: + distObj.parentId = parentId + distObj.zoneId = zoneId + + # Generate this Object + distObj.generate() + + # Update the required fields + distObj.updateAllRequiredOtherFields(dclass, di) + + return distObj + + def _handleUpdateField(self, di): + # Get the Do Id + doId = di.getUint32() + # Find the do + do = self.doId2do[doId] + # Let the dclass finish the job + do.dclass.receiveUpdate(do, di) + + def _handleObjectChangeZone(self, di): + # Get the Do Id + doId = di.getUint32() + newParentId = di.getUint32() + newZoneId = di.getUint32() + oldParentId = di.getUint32() + oldZoneId = di.getUint32() + # Find the do + do = self.doId2do.get(doId) + if do is None: + self.notify.warning( + 'handleObjectChangeZone: (NOT PRESENT) %s:(%s, %s)->(%s, %s)' %( + doId, oldParentId, oldZoneId, newParentId, newZoneId)) + else: + self.notify.debug('handleObjectChangeZone: %s:(%s, %s)->(%s, %s)' %( + doId, oldParentId, oldZoneId, newParentId, newZoneId)) + # TODO: pass in the old parent and old zone + do.setLocation(newParentId, newZoneId) #, oldParentId, oldZoneId) + + def _handleDeleteObject(self, di): + # Get the DO Id + doId = di.getUint32() + obj = self.doId2do.get(doId) + if obj: + # If it is in the dictionary, remove it. + self.deleteDistObject(obj) + else: + # Otherwise ignore it + AIRepository.notify.warning( + "Asked to delete non-existent DistObjAI " + str(doId)) + + # announce delete event for this doId, even if obj doesn't exist + messenger.send(self.getDeleteDoIdEvent(doId)) + + def sendUpdate(self, do, fieldName, args): + #print "---------------sendUpdate--" + #print do + #print do.doId + #print fieldName + #print args + dg = do.dclass.aiFormatUpdate( + fieldName, do.doId, do.doId, self.ourChannel, args) + self.sendDatagram(dg) + + def sendUpdateToDoId(self, dclassName, fieldName, doId, args, + channelId=None): + """ + channelId can be used as a recipient if you want to bypass the normal + airecv, ownrecv, broadcast, etc. If you don't include a channelId + or if channelId == doId, then the normal broadcast options will + be used. + + See Also: def queryObjectField + """ + dclass=self.dclassesByName.get(dclassName+self.dcSuffix) + assert dclass is not None + if channelId is None: + channelId=doId + if dclass is not None: + dg = dclass.aiFormatUpdate( + fieldName, doId, channelId, self.ourChannel, args) + self.send(dg) + + def createDgUpdateToDoId(self, dclassName, fieldName, doId, args, + channelId=None): + """ + channelId can be used as a recipient if you want to bypass the normal + airecv, ownrecv, broadcast, etc. If you don't include a channelId + or if channelId == doId, then the normal broadcast options will + be used. + + This is just like sendUpdateToDoId, but just returns + the datagram instead of immediately sending it. + """ + result = None + dclass=self.dclassesByName.get(dclassName+self.dcSuffix) + assert dclass is not None + if channelId is None: + channelId=doId + if dclass is not None: + dg = dclass.aiFormatUpdate( + fieldName, doId, channelId, self.ourChannel, args) + result = dg + return result + + def sendUpdateToGlobalDoId(self, dclassName, fieldName, doId, args): + """ + Used for sending messages from an AI directly to an + uber object. + """ + dclass = self.dclassesByName.get(dclassName) + assert dclass, 'dclass %s not found in DC files' % dclassName + dg = dclass.aiFormatUpdate( + fieldName, doId, doId, self.ourChannel, args) + self.send(dg) + + def sendUpdateToChannel(self, do, channelId, fieldName, args): + dg = do.dclass.aiFormatUpdate( + fieldName, do.doId, channelId, self.ourChannel, args) + self.sendDatagram(dg) + + def sendUpdateToChannelFrom(self, do, channelId, fieldName, fromid, args): + dg = do.dclass.aiFormatUpdate(fieldName, do.doId, channelId, + fromid, args) + self.send(dg) + + def startMessageBundle(self, name): + # start bundling messages together. Use this for instance if you want to + # make sure that location and position of an object are processed atomically + # on the state server, to prevent clients from getting the new location and an + # old position (relative to old location) on client generate of the object + self._msgBundleNames.append(name) + ConnectionRepository.startMessageBundle(self) + def sendMessageBundle(self, senderChannel): + # stop bundling messages and send the bundle + # senderChannel is typically the doId of the object affected by the messages + ConnectionRepository.sendMessageBundle(self, self.districtId, senderChannel) + self._msgBundleNames.pop() + + def _checkBundledMsgs(self, task=None): + # message bundles should not last across frames + num = len(self._msgBundleNames) + while len(self._msgBundleNames): + self.notify.warning('abandoning message bundle: %s' % self._msgBundleNames.pop()) + if num > 0: + self.abandonMessageBundles() + self.notify.error('message bundling leak, see warnings above (most recent first)') + return task.cont + + def registerForChannel(self, channelNumber): + if self._channels.get(channelNumber): + # We are already registered for this channel. + return + self._channels[channelNumber]=1 + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_SET_CHANNEL) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CHANNEL) + + datagram.addChannel(channelNumber) + self.send(datagram) + + def addPostSocketClose(self, themessage): + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_ADD_POST_REMOVE) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_ADD_POST_REMOVE) + + datagram.addString(themessage.getMessage()) + self.send(datagram) + + def addPostSocketCloseUD(self, dclassName, fieldName, doId, args): + dclass = self.dclassesByName.get(dclassName) + assert dclass, 'dclass %s not found in DC files' % dclassName + dg = dclass.aiFormatUpdate( + fieldName, doId, doId, self.ourChannel, args) + self.addPostSocketClose(dg) + + def unregisterForChannel(self, channelNumber): + if self._channels.get(channelNumber) is None: + # We are already unregistered for this channel. + return + del self._channels[channelNumber] + # Time to send a unregister for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_REMOVE_CHANNEL) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_REMOVE_CHANNEL) + + datagram.addChannel(channelNumber) + self.send(datagram) + + #---------------------------------- + + def addAvatarToChannels(self, avatarId, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + self.addConnectionToChannels((1L<<32)+avatarId, listOfChannels) + + def removeAvatarFromChannels(self, avatarId, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + self.removeConnectionToChannels((1L<<32)+avatarId, listOfChannels) + + def addConnectionToChannels(self, targetConnection, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + targetConnection, self.serverId, CLIENT_AGENT_OPEN_CHANNEL) + for i in listOfChannels: + dg.addUint64(i) + self.send(dg) + + def removeConnectionToChannels(self, targetConnection, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + targetConnection, self.serverId, CLIENT_AGENT_CLOSE_CHANNEL) + for i in listOfChannels: + dg.addUint64(i) + self.send(dg) + + def addInterestToConnection(self, targetConnection, interestId, + contextId, parentDoId, zoneIdList): + """ + Allows the AIRepository to initiate interest on the client. + See otp.ai.AIInterestHandles for a list of interestId's to use. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+targetConnection, self.serverId, + CLIENT_AGENT_SET_INTEREST) + + # Set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint16((1<<15)+interestId) + dg.addUint32(contextId) + dg.addUint32(parentDoId) + dg.addUint32(contextId) + if isinstance(zoneIdList, types.ListType): + # sort and remove repeated entries + zIdSet = set(zoneIdList) + for zoneId in zIdSet: + dg.addUint32(zoneId) + else: + dg.addUint32(zoneIdList) + self.send(dg) + + def removeInterestFromConnection(self, targetConnection, interestId, + contextId=0): + """ + Allows the AIRepository to remove interest on the client. + See otp.ai.AIInterestHandles for a list of interestId's to use. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+targetConnection, self.serverId, + CLIENT_AGENT_REMOVE_INTEREST) + # set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint16((1<<15)+interestId) + dg.addUint32(contextId) + self.send(dg) + + def setAllowClientSend(self, avatarId, + dObject, fieldNameList = []): + """ + Allow an AI to temporarily give a client 'clsend' privileges + on a particular fields on a particular object. This should + be used on fields that are 'ownsend' by default. When you want + to revoke these privileges, use clearAllowClientSend() to end + these privileges. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+avatarId, self.serverId, + CLIENT_SET_FIELD_SENDABLE) + + # Set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint32(dObject.doId) + assert isinstance(fieldNameList, types.ListType) + + dclass = dObject.dclass + # sort and remove repeated entries as we discover the field + # ids for the specified names + fieldIdSet = set(dclass.getFieldByName(name).getNumber() \ + for name in fieldNameList) + + # insert the fieldIds into the datagram + for fieldId in sorted(fieldIdSet): + dg.addUint16(fieldId) + self.send(dg) + + def clearAllowClientSend(self, avatarId, dObject): + self.setAllowClientSend(avatarId, dObject) + + # ---------------------------------- + + def setConnectionName(self, name): + self.connectionName = name + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() + # datagram.addServerControlHeader(CONTROL_SET_CON_NAME) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CON_NAME) + + datagram.addString(name) + self.send(datagram) + + def setConnectionURL(self, url): + self.connectionURL = url + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() + # datagram.addServerControlHeader(CONTROL_SET_CON_NAME) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CON_URL) + + datagram.addString(url) + self.send(datagram) + + def deleteObjects(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. + # This function is where objects that manage DistributedObjectAI's + # should be cleaned up. Since this is only called during a district + # shutdown of some kind, it is not useful to delete the existing + # DistributedObjectAI's, but rather to just make sure that they + # are no longer referenced, and no new ones are created. + pass + + def allocateChannel(self): + channel=self.channelAllocator.allocate() + if channel==-1: + raise RuntimeError, "channelAllocator.allocate() is out of channels" + if self.channelAllocator.fractionUsed()>0.75: + # There is some debate about how bad it is to run out of + # channels. Being ignorant about what exactly will happen + # if a channel is reused too quickly, we decided to bail + # out if we got low on channels. By the way, being low on + # channels is not the real problem. The problem is reusing + # a channel too soon after it's freed. There is an assumption + # here that if there are a lot of free channels and the + # channels are reused in the same order that they are freed, + # then there is a good chance that any allocated channel has + # "aged" properly. + # Schuyler has daydreamed about a system that will track the age + # of freed ids and sleep or flag an error as apropriate. See him + # for details (esp. if you want to write a cross-platform version + # of said feature). + raise RuntimeError, "Dangerously low on channels." + # Sanity check + assert (channel >= self.minChannel) and (channel <= self.maxChannel) + + if 0: ## used to debug the channel code + import traceback + if not hasattr(self, "debug_dictionary"): + self.debug_dictionary = {} + __builtins__["debug_dictionary"] = self.debug_dictionary + + for id in self.debug_dictionary.keys(): + if not self.doId2do.has_key(id): + print "--------------------- Not In DOID table" + print id + #traceback.print_list(self.debug_dictionary[id]) + print self.debug_dictionary[id] + del self.debug_dictionary[id] # never report it again .. + + self.debug_dictionary[channel] = traceback.extract_stack(None,7) + + return channel + + def deallocateChannel(self, channel): + if 0: ## used to debug the channel code .. see above + del self.debug_dictionary[channel] + + self.channelAllocator.free(channel) + + def getMinDynamicZone(self): + # Override this to return the minimum allowable value for a + # dynamically-allocated zone id. + return 0 + + def getMaxDynamicZone(self): + # Override this to return the maximum allowable value for a + # dynamically-allocated zone id. + return 0 + + def allocateZone(self): + zoneId=self.zoneAllocator.allocate() + if zoneId==-1: + raise RuntimeError, "zoneAllocator.allocate() is out of zoneIds" + # Sanity check + assert (zoneId >= self.minZone) and (zoneId <= self.maxZone) + return zoneId + + def deallocateZone(self, zoneId): + self.zoneAllocator.free(zoneId) + + # NOTE: the public API for this is in DistributedObjectAI.b_setLocation() + # @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetLocation(self, distobj, parentId, zoneId, owner=None): + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + datagram.addUint32(parentId) + datagram.addUint32(zoneId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetLocationDoId(self, doId, parentId, zoneId, owner=None): + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + datagram.addUint32(parentId) + datagram.addUint32(zoneId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetOwnerDoId(self, doId, ownerId): + # allowed channels are (from http://aspen.online.disney.com/mediawiki/index.php/P2P%2C_OWNERSHIP_STUFF): + # account<<32 + avatar + # 1<<32 + avatar + # 1<<32 + account + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_OWNER_RECV) + datagram.addChannel((1<<32) + ownerId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendClearOwnerDoId(self, doId): + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_OWNER_RECV) + datagram.addChannel(0) + self.send(datagram) + + def sendSetZone(self, distobj, zoneId): + self.notify.error("non-district types should not call sendSetZone") + + def startTrackRequestDeletedDO(self, obj): + if obj.doId in self._requestDeletedDOs: + self.notify.warning('duplicate requestDelete for %s %s' % (obj.__class__.__name__, obj.doId)) + # store object and time of requestDelete + self._requestDeletedDOs[obj.doId] = (obj, globalClock.getRealTime()) + + def stopTrackRequestDeletedDO(self, obj): + # sometimes objects are deleted without having requested a delete + #assert obj.doId in self._requestDeletedDOs + if (hasattr(obj,'doId')) and (obj.doId in self._requestDeletedDOs): + del self._requestDeletedDOs[obj.doId] + + def getRequestDeletedDOs(self): + # returns list of (obj, age of delete request), sorted by descending age + response = [] + now = globalClock.getRealTime() + for obj, requestTime in self._requestDeletedDOs.values(): + # calculate how long it has been since delete was requested + age = now - requestTime + index = 0 + while index < len(response): + if age > response[index][1]: + break + index += 1 + response.insert(index, (obj, age)) + return response + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def requestDelete(self, distobj): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(distobj.doId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def requestDeleteDoId(self, doId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(doId) + self.send(datagram) + + def requestDeleteDoIdFromDisk(self, doId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_DELETE_DISK) + # The Id of the object in question + datagram.addUint32(doId) + self.send(datagram) + + def getDatabaseGenerateResponseEvent(self, context): + # handler must accept (doId) + return 'DBGenResponse-%s' % context + + def _handleDatabaseGenerateResponse(self, di): + assert self.notify.debugCall(self) + context = di.getUint32() + doId = di.getUint32() + self.notify.debug( + '_handleDatabaseGenerateResponse, context=%s, doId=%s' % + (context, doId)) + messenger.send( + self.getDatabaseGenerateResponseEvent(context), [doId]) + + def getDatabaseIdForClassName(self, className): + assert 0 + # You probably want to override this to return something better. + return 0 + + def requestDatabaseGenerate( + self, classId, context, + parentId=0, zoneId=0, + ownerChannel=0, ownerAvId=None, + databaseId=None, values = None): + """ + context is any 32 bit integer to be used a reference + for this message (and its reply). + parentId may be 0 or a valid distributed object ID. + zoneId may be 0 or a valid zone ID (int32). + ownerChannel may be 0 or a valid owner channel (int64) + OR + ownerAvId may be None or a player doId + databaseId is a distributed object ID for the database + (maybe a constant). + values is a dictionary of other member values (or None) + + To get doId of new object, listen for this event: + air.getDatabaseGenerateResponseEvent(context) + If object location was provided, and the location is on this + district, the object will be generated shortly after the + above message is sent. + """ + AIRepository.notify.debugCall() + #self.notify.info('requestDatabaseGenerate, class=%s, context=%s' % + # (classId, context)) + if ownerChannel == 0 and ownerAvId is not None: + ownerChannel = (1<<32) + ownerAvId + if self.dclassesByNumber.has_key(classId): + dclass = self.dclassesByNumber[classId] + else: + if self.dclassesByName.has_key(classId): + dclass = self.dclassesByName[classId] + elif self.dclassesByName.has_key(classId+self.dcSuffix): + dclass = self.dclassesByName[classId+self.dcSuffix] + elif self.dclassesByName.has_key(classId+'AI'): + dclass = self.dclassesByName[classId+'AI'] + else: + self.notify.warning("dclass not found %s"%(classId,)) + if __dev__: + import pdb; pdb.set_trace() + if databaseId is None: + databaseId=self.getDatabaseIdForClassName(dclass.getName()) + if values is None: + if simbase.newDBRequestGen: + dg = dclass.aiDatabaseGenerateContext( + context, parentId, zoneId, ownerChannel, + databaseId, self.ourChannel) + else: + dg = dclass.aiDatabaseGenerateContextOld( + context, parentId, zoneId, + databaseId, self.ourChannel) + self.send(dg) + else: + packer = DCPacker() + packer.rawPackUint8(1) + packer.rawPackUint64(databaseId) + packer.rawPackUint64(self.ourChannel) + packer.rawPackUint16( + STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT) + ## packer.rawPackUint16( + ## STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT) + packer.rawPackUint32(parentId) + packer.rawPackUint32(zoneId) + if simbase.newDBRequestGen: + packer.rawPackUint64(ownerChannel) + packer.rawPackUint16(dclass.getNumber()) + packer.rawPackUint32(context) + + optionalFields = [] + + for i in range(dclass.getNumInheritedFields()): + field = dclass.getInheritedField(i) + if field.asMolecularField() == None: + if field.isRequired(): + # Packs Required Fields + value = values.get(field.getName(), None) + packer.beginPack(field) + + if value == None: + packer.packDefaultValue() + else: + if not field.packArgs(packer, value): + raise StandardError + packer.endPack() + else: + value = values.get(field.getName(), None) + + if value != None: + fieldDec = {} + fieldDec['field'] = field + fieldDec['value'] = value + optionalFields.append(fieldDec) + + packer.rawPackUint16(len(optionalFields)) + + # Packs Optional Fields + for i in optionalFields: + field = i['field'] + value = i['value'] + + packer.rawPackUint16(field.getNumber()) + packer.beginPack(field) + field.packArgs(packer, value) + packer.endPack() + + if packer.hadError(): + raise StandardError + + dg = Datagram(packer.getString()) + self.send(dg) + + def lostConnection(self): + ConnectionRepository.lostConnection(self) + sys.exit() + + def handleDatagram(self, di): + if self.notify.getDebug(): + print "AIRepository received datagram:" + di.getDatagram().dumpHex(ostream) + + channel=self.getMsgChannel() + if channel in self.netMessenger.channels: + self.netMessenger.handle(di.getString()) + else: + self.handler(self.getMsgType(), di) + + def deleteDistObject(self, do): + assert self.notify.debugCall() + + # if not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse: + do.sendDeleteEvent() + # Remove it from the dictionary + self.removeDOFromTables(do) + # Delete the object itself + do.delete() + if self._proactiveLeakChecks: + # make sure we're not leaking + do.detectLeaks() + + def sendAnotherGenerate(self, distObj, toChannel): + assert self.notify.debugCall() + dg = distObj.dclass.aiFormatGenerate( + distObj, distObj.doId, distObj.parentId, distObj.zoneId, + toChannel, self.ourChannel, []) + self.send(dg) + + def generateWithRequired(self, distObj, parentId, zoneId, optionalFields=[]): + assert self.notify.debugStateCall(self) + # Assign it an id + distObj.doId = self.allocateChannel() + # Put the new DO in the dictionaries + self.addDOToTables(distObj, location = (parentId,zoneId)) + # Send a generate message + distObj.sendGenerateWithRequired(self, parentId, zoneId, optionalFields) + + + # this is a special generate used for estates, or anything else that + # needs to have a hard coded doId as assigned by the server + def generateWithRequiredAndId( + self, distObj, doId, parentId, zoneId, optionalFields=[]): + assert self.notify.debugStateCall(self) + # Assign it an id + distObj.doId = doId + # Since the distObj has been created explicitly from the + # server, we do not own its doId, and hence we shouldn't try + # to deallocate it. + distObj.doNotDeallocateChannel = 1 + # Put the new DO in the dictionaries + self.addDOToTables(distObj, location = (parentId,zoneId)) + # Send a generate message + distObj.sendGenerateWithRequired(self, parentId, zoneId, optionalFields) + + def queryObjectAll(self, doId, context=0): + """ + Get a one-time snapshot look at the object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_QUERY_OBJECT_ALL) + # A context that can be used to index the response if needed + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def queryObjectZoneIds(self, stateServerId, obj2ZoneDict): + # obj2ZoneDict should be a dict looking like this: + # { objId : [zoneId, zoneId, ...], + # objId : [zoneId, zoneId, ...], + # } + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + stateServerId, self.ourChannel, STATESERVER_QUERY_ZONE_OBJECT_ALL) + numObjs = len(obj2ZoneDict.keys()) + datagram.addUint16(numObjs) + for objId, zoneIds in obj2ZoneDict.values(): + datagram.addUint32(objId) + datagram.addUint16(len(zoneIds)) + for zoneId in zoneIds: + datagram.addUint32(zoneId) + # This is for a forwarding system that I did not hook up. + # Ask Roger for details. + datagram.addUint16(0) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def queryObjectChildrenLocal(self, parentId, context=0): + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.serverId, self.ourChannel, STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL) + datagram.addUint32(parentId) + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def handleQueryObjectChildrenLocalDone(self, di): + # We asked to get generates for all objects that are children of + # us. This is the callback that says the server is done sending all + # those generates. + assert self.notify.debugCall() + parentId = di.getUint16() + context = di.getUint32() + parent = self.doId2do.get(parentId) + if parent: + parent.handleQueryObjectChildrenLocalDone(context) + else: + self.notify.warning('handleQueryObjectChildrenLocalDone: parentId %s not found' % + parentId) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFieldId(self, doId, fieldId, context=0): + """ + Get a one-time snapshot look at the object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELD) + datagram.addUint32(doId) + datagram.addUint16(fieldId) + # A context that can be used to index the response if needed + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFieldIds(self, doId, fieldIds, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field IDs from the same object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELDS) + datagram.addUint32(doId) + datagram.addUint32(context) + for x in fieldIds: + datagram.addUint16(x) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectStringFieldIds(self, dbId, objString, fieldIds, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field IDs from the same object, by object string. + """ + assert self.notify.debugStateCall(self) + # Create a message + dg = PyDatagram() + dg.addServerHeader( + dbId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELDS_STRING) + dg.addString(objString) + dg.addUint32(context) + for x in fieldIds: + dg.addUint16(x) + self.send(dg) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectStringFields( + self, dbId, dclassName, objString, fieldNames, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field names from the same object, by object string. + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + for fn in fieldNames: + assert len(fn) > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectStringFields invalid dclassName %s"%(dclassName)) + return + if dclass is not None: + fieldIds = [] + for fn in fieldNames: + id = dclass.getFieldByName(fn).getNumber() + assert id + if not id: + self.notify.error( + "queryObjectStrongFields invalid field %s, %s"%(doId,fn)) + return + fieldIds.append(id) + self.queryObjectStringFieldIds(dbId,objString,fieldIds,context) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectField(self, dclassName, fieldName, doId, context=0): + """ + See Also: def sendUpdateToDoId + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + assert len(fieldName) > 0 + assert doId > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectField invalid dclassName %s, %s"%(doId, fieldName)) + return + if dclass is not None: + fieldId = dclass.getFieldByName(fieldName).getNumber() + assert fieldId # is 0 a valid value? + if not fieldId: + self.notify.error( + "queryObjectField invalid field %s, %s"%(doId, fieldName)) + return + self.queryObjectFieldId(doId, fieldId, context) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFields(self, dclassName, fieldNames, doId, context=0): + """ + See Also: def sendUpdateToDoId + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + assert len(fieldNames) > 0 + for fieldName in fieldNames: + assert len(fieldName) > 0 + assert doId > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectField invalid dclassName %s, %s"%(doId, fieldName)) + return + if dclass is not None: + fieldIds = [dclass.getFieldByName(fieldName).getNumber() \ + for fieldName in fieldNames] + # is 0 a valid value? + assert 0 not in fieldIds + if 0 not in fieldIds: + self.queryObjectFieldIds(doId, fieldIds, context) + else: + assert self.notify.error( + "queryObjectFields invalid field in %s, %s"%(doId, `fieldNames`)) + + + def requestDistributedObject(self, doId): + """ + Ask for the object to be added to the private + distributed object cache. + + Normally a query object all does not actually enter the object + returned into the doId2do table. This function will change a query + request to a distributed object that is part of the normal set of + objects on this server. + """ + assert self.notify.debugCall() + distObj = self.doId2do.get(doId) + if distObj is not None: + # Already have it + return + if doId in self.distributedObjectRequests: + # Already requested it + return + #todo: add timeout for to remove request + self.distributedObjectRequests.add(doId) + context=self.allocateContext() + self.acceptOnce( + "doRequestResponse-%s"%(context,), + self.postGenerate, []) + self.registerForChannel(doId) + self.queryObjectAll(doId, context) + + # If you want to set the airecv you should set the location + def setAIReceiver(self, objectId, aiChannel=None): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.ourChannel, self.ourChannel, STATESERVER_ADD_AI_RECV) + # The Id of the object in question + datagram.addUint32(objectId) + if aiChannel is None: + aiChannel = self.ourChannel + datagram.addUint32(aiChannel) + # Send the message + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def replaceMethod(self, oldMethod, newFunction): + return 0 + + def sendUpdate(self, distObj, fieldName, args): + dg = distObj.dclass.aiFormatUpdate( + fieldName, distObj.doId, distObj.doId, self.ourChannel, args) + self.sendDatagram(dg) + + def _handleDatabaseGetStoredValuesResp(self, di): + context = di.getUint32() + dbObj = self.dbObjMap.get(context) + if dbObj: + del self.dbObjMap[context] + dbObj.getFieldsResponse(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected context %d for DBSERVER_GET_STORED_VALUES" % + context) + + def _handleDatabaseCreateStoredObjectResp(self, di): + context = di.getUint32() + dbObj = self.dbObjMap.get(context) + if dbObj: + del self.dbObjMap[context] + dbObj.handleCreateObjectResponse(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected context %d for DBSERVER_CREATE_STORED_OBJECT" % + context) + + # LEADERBOARD + def setLeaderboardValue(self, category, whoId, whoName, value, senderId=None): + self.writeServerEvent('setLeaderboardValue', whoId, + '%s|%s|%s' % (whoName, category, value)) + dcfile = self.getDcFile() + dclass = dcfile.getClassByName('LeaderBoard') + if senderId is None: + senderId = self.districtId + dg = dclass.aiFormatUpdate('setValue', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + senderId, + [[category], whoId, whoName, value]) + self.send(dg) + + ################################################################## + # msgsend is set by the C code on the repository.. These Functions + # will let you parse the special encoded sender id if you want to + # know the AvatarID or The AccountID of the Sender.. + + def getAccountIdFromSender(self): + """ + only works on the dc updates from the client agent + """ + return self.getMsgSender() >> 32 + + def getAvatarIdFromSender(self): + """ + only works on the dc updates from the client agent + """ + return self.getMsgSender() & 0xffffffffL + + def getSenderReturnChannel(self): + return self.getMsgSender() + + + ######################################## + # Network reading and time device.. for ai's + def taskManagerDoYieldNetwork(self , frameStartTime, nextScheuledTaksTime): + minFinTime = frameStartTime + self.MaxEpockSpeed + if nextScheuledTaksTime > 0 and nextScheuledTaksTime < minFinTime: + minFinTime = nextScheuledTaksTime + + self.networkBasedReaderAndYielder(self.handleDatagram,globalClock,minFinTime) + + if not self.isConnected(): + self.stopReaderPollTask() + self.lostConnection() + + ############################################################### + # Optimized version of old behavior.. + def readerPollUntilEmpty(self, task): + self.checkDatagramAi(self.handleDatagram) + if not self.isConnected(): + self.stopReaderPollTask() + self.lostConnection() + + return Task.cont + + + ############################################################### + # This can be used to do time based yielding instead of the sleep task. + def taskManagerDoYield(self , frameStartTime, nextScheuledTaksTime): + minFinTime = frameStartTime + self.MaxEpockSpeed + if nextScheuledTaksTime > 0 and nextScheuledTaksTime < minFinTime: + minFinTime = nextScheuledTaksTime + + delta = minFinTime - globalClock.getRealTime() + while(delta > 0.002): + time.sleep(delta) + delta = minFinTime - globalClock.getRealTime() + + + ############################################################### + # This can be used to do time based yielding instead of the sleep task. + def startReaderPollTask(self): + if not self.AIRunningNetYield: + ConnectionRepository.startReaderPollTask(self) + else: + print '########## startReaderPollTask New ' + self.stopReaderPollTask() + self.accept(CConnectionRepository.getOverflowEventName(),self.handleReaderOverflow) diff --git a/otp/src/ai/AIZoneData.py b/otp/src/ai/AIZoneData.py new file mode 100644 index 0000000..1d29965 --- /dev/null +++ b/otp/src/ai/AIZoneData.py @@ -0,0 +1,245 @@ +from pandac.PandaModules import * +from direct.distributed import ParentMgr +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.task import Task +from direct.showbase import LeakDetectors +from otp.otpbase import OTPGlobals +import random + +class AIZoneData: + """This is a proxy to the AIZoneDataObj for a particular zone. When all + AIZoneData objects for a zone have been destroyed, the AIZoneDataObj + object for that zone is destroyed as well.""" + 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): + # provide direct access to AIZoneDataStore methods on this object + return getattr(self._data, attr) + +class AIZoneDataObj: + """ + This class stores per-zone information on an AI district. Only one of these + exists for a particular zone, and it only exists if somebody requested it. + """ + notify = directNotify.newCategory('AIZoneDataObj') + + DefaultCTravName = 'default' + + def __init__(self, parentId, zoneId): + assert self.notify.debug( + 'AIZoneDataObj.__init__(%s, %s)' % (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): + assert self.notify.debug( + 'AIZoneDataObj.destroy(%s, %s)' % (self._parentId, self._zoneId)) + 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'): + if hasattr(self, '_renderLeakDetector'): + self._renderLeakDetector.destroy() + del self._renderLeakDetector + 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)) + if config.GetBool('leak-scene-graph', 0): + self._renderLeakDetector = LeakDetectors.SceneGraphLeakDetector(self._render) + return self._render + + def getNonCollidableParent(self): + # this is a node for things that can't be collided against, so that that collision traverser + # only has to check once against this node. It's up to the show code to make sure that + # no collidables are put under this node + if not hasattr(self, '_nonCollidableParent'): + render = self.getRender() + self._nonCollidableParent = render.attachNewNode('nonCollidables') + if __dev__: + assert self._nonCollidableParent.getCollideMask() == BitMask32().allOff(),\ + "collidable geometry under non-collidable parent node for location "\ + "(%s,%s)" % (self._parentId, self._zoneId) + 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): + #assert self.notify.debug('getCollTrav(%s, %s)' % (self._parentId, self._zoneId)) + 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 (self._collTravs.has_key(name)): + 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: + # make sure the topNode for collision traversal is a descendant of our render node + 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): + # call this method to do a one-shot collision traversal (instead of starting up a task + # to traverse every frame) + assert self.notify.debug('doCollTrav(%s, %s)' % (self._parentId, self._zoneId)) + self.getCollTrav(cTravName) + self._doCollisions(topNode=topNode, cTravName=cTravName) + + def startCollTrav(self, respectPrevTransform=1, cTravName=None): + """sets up and starts collision traverser for this zone. + Pass in zero for 'respectPrevTransform' to disable support for + tunneling/trailing sphere support. This will allow objects to + break through collision barriers, but may run faster -- see + drose for more info. + """ + if cTravName is None: + cTravName = AIZoneDataObj.DefaultCTravName + assert self.notify.debug( + 'startCollTrav(%s, (%s, %s), %s)' % (cTravName, self._parentId, self._zoneId, respectPrevTransform)) + if not cTravName in self._collTravsStarted: + # make sure we've got a collision traverser + self.getCollTrav(name=cTravName) + taskMgr.add(self._doCollisions, self._getCTravTaskName(name=cTravName), + priority=OTPGlobals.AICollisionPriority, + extraArgs=[self._zoneId]) + self._collTravsStarted.add(cTravName) + assert self.notify.debug( + 'adding %s collision traversal for (%s, %s)' % (cTravName, self._parentId, self._zoneId)) + self.setRespectPrevTransform(respectPrevTransform, cTravName=cTravName) + + def stopCollTrav(self, cTravName=None): + """frees resources used by collision traverser for this zone""" + assert self.notify.debugStateCall(self) + 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) + + def setRespectPrevTransform(self, flag, cTravName=None): + if cTravName is None: + cTravName = AIZoneDataObj.DefaultCTravName + self._collTravs[cTravName].setRespectPrevTransform(flag) + def getRespectPrevTransform(self, cTravName=None): + if cTravName is None: + cTravName = AIZoneDataObj.DefaultCTravName + return self._collTravs[cTravName].getRespectPrevTransform() + +class AIZoneDataStore: + """This class holds all of the AIZoneDataObj objects for a district.""" + notify = directNotify.newCategory('AIZoneDataStore') + + def __init__(self): + # table of (parentId, zoneId) -> AIZoneDataObj + 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() + assert (refCount >= 0) + 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)) + diff --git a/otp/src/ai/BanManagerAI.py b/otp/src/ai/BanManagerAI.py new file mode 100644 index 0000000..8dea8f5 --- /dev/null +++ b/otp/src/ai/BanManagerAI.py @@ -0,0 +1,97 @@ +################################################################# +# File: BanManagerAI.py +# Purpose: Module to ban avatars while inside the game and kick +# them out of the game +################################################################# +import urllib +import os +from pandac.PandaModules import HTTPClient, Ramfile +from direct.directnotify import DirectNotifyGlobal + +class BanManagerAI: + notify = DirectNotifyGlobal.directNotify.newCategory("BanManagerAI") + + # These URLS came from Alper Akture + # dev instance (but as of 7/1/2010 hooked to live disl) + # http://dnhdosapp01.wdig.com:8005/dis-hold/action/event + # qa instance + # http://qnhspapp02.wdig.com:8005/dis-hold/action/event + # live + # https://vapps.disl.starwave.com:8005/dis-hold/action/event + BanUrl = simbase.config.GetString("ban-base-url", "https://vapps.disl.starwave.com:8005/dis-hold/action/event") + App = simbase.config.GetString("ban-app-name", "TTWorldAI") + Product = simbase.config.GetString("ban-product", "Toontown") + EventName = simbase.config.GetString("ban-event-name", "tthackattempt") + + def __init__(self): + """Construct and initialize members.""" + self.curBanRequestNum = 0 + self.channels = {} + self.ramFiles = {} + + def ban(self, avatarId, dislid, comment): + """Ban the player""" + + parameters = "" + parameters += "app=%s" % self.App + parameters += "&product=%s" % self.Product + parameters += "&user_id=%s" % dislid + parameters += "&event_name=%s" % self.EventName + commentWithAvatarId = "avId-%s " % avatarId + commentWithAvatarId += comment + parameters += "&comments=%s" % urllib.quote(str(commentWithAvatarId)) + + # get the base ban url from the environment variable first + baseUrlToUse = self.BanUrl + osBaseUrl = os.getenv("BAN_URL") + if osBaseUrl: + baseUrlToUse = osBaseUrl + fullUrl = baseUrlToUse + "?" + parameters + + self.notify.info ("ban request %s dislid=%s comment=%s fullUrl=%s" % (self.curBanRequestNum, dislid, comment, fullUrl)) + simbase.air.writeServerEvent('ban_request', avatarId, "%s|%s|%s" % (dislid, comment, fullUrl)) + + if simbase.config.GetBool('do-actual-ban',False): + newTaskName = "ban-task-%d" % self.curBanRequestNum + newTask = taskMgr.add(self.doBanUrlTask, newTaskName) + newTask.banRequestNum = self.curBanRequestNum + http = HTTPClient.getGlobalPtr() + channel = http.makeChannel(False) # hmm should we make true for a persistent connection? + self.channels [ self.curBanRequestNum] = channel + rf = Ramfile() + self.ramFiles [ self.curBanRequestNum] = rf + + channel.beginGetDocument(fullUrl) + channel.downloadToRam(rf) + + self.curBanRequestNum += 1 + + def cleanupBanReq(self, banReq): + """Cleanup stuff that's associated with this ban request.""" + channel = self.channels.get(banReq) + if channel: + del self.channels[banReq] + ramfile = self.ramFiles.get(banReq) + if ramfile: + del self.ramFiles[banReq] + + + def doBanUrlTask(self, task): + """Continue downloading the ban url if needed.""" + banReq = task.banRequestNum + channel = self.channels.get(banReq) + if channel: + if channel.run(): + return task.cont + else: + self.notify.warning("no channel for ban req %s" % banReq) + self.cleanupBanReq(banReq) + return task.done + + result = "" + ramfile = self.ramFiles.get(banReq) + if ramfile: + result = ramfile.getData() + self.notify.info("done processing ban request %s, ramFile=%s" % (banReq, result)) + self.cleanupBanReq(banReq) + return task.done diff --git a/otp/src/ai/Barrier.py b/otp/src/ai/Barrier.py new file mode 100644 index 0000000..87ea666 --- /dev/null +++ b/otp/src/ai/Barrier.py @@ -0,0 +1,138 @@ +""" Barrier.py: contains the Barrier class: utility class for AI objects that must wait for a message from each of a list of Avatars """ + +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): + """ + name: a context name that should be used in common with the + client code. + uniqueName: should be a unique name for this Barrier, used + for timeout doLater + avIdList: list of avatars from which we'll expect responses + timeout: how long to wait before giving up + clearedFunc: func to call when all avatars have cleared the barrier; + takes no arguments + timeoutFunc: func to call when the timeout has expired; + takes list of avIds of avatars that did not + clear the barrier + doneFunc: func to call when the the barrier is complete for + either reason; takes list of avIds of avatars that + successfully cleared the barrier + + Call Barrier.clear(avId) when you get a response from + each avatar. + + If you need to have additional parameters passed to your + callback funcs, see PythonUtil.Functor + """ + 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: + # If we are initialized with an empty list of avatars to + # wait for, we consider ourselves cleared immediately. + 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 + + # choose a name for the timeout task + self.taskName = self.uniqueName + '-Timeout' + # this shouldn't be necessary, and it's kind of ugly; + # in any case, it's better than bringing down the AI server + # with an assert + origTaskName = self.taskName + while taskMgr.hasTaskNamed(self.taskName): + self.taskName = origTaskName + '-' + str(random.randint(0, 10000)) + + # start the timeout + taskMgr.doMethodLater(self.timeout, + self.__timerExpired, + self.taskName) + + # Hang hooks for each avatar to disappear. + 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): + """ + call this if you're abandoning the barrier condition and + discarding this object + """ + if self.active: + taskMgr.remove(self.taskName) + self.active = 0 + self.ignoreAll() + + def clear(self, avId): + if not (avId 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() + # report which avatars have not responded + 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) diff --git a/otp/src/ai/GarbageLeakServerEventAggregator.py b/otp/src/ai/GarbageLeakServerEventAggregator.py new file mode 100644 index 0000000..4a30759 --- /dev/null +++ b/otp/src/ai/GarbageLeakServerEventAggregator.py @@ -0,0 +1,44 @@ +from direct.showbase.DirectObject import DirectObject +from direct.showbase import GarbageReport + +class GarbageLeakServerEventAggregator(DirectObject): + def __init__(self, cr): + self.cr = cr + self._doLaterName = None + self._sentLeakDesc2num = {} + self._curLeakDesc2num = {} + self.accept(GarbageReport.GarbageCycleCountAnnounceEvent, + self._handleCycleCounts) + + def destroy(self): + self._stopSending() + self.ignoreAll() + del self.cr + + def _handleCycleCounts(self, desc2num): + self._curLeakDesc2num = desc2num + self._startSending() + + def _startSending(self): + if not self._doLaterName: + self._sendLeaks() + self._doLaterName = uniqueName('%s-sendGarbageLeakInfo' % self.__class__.__name__) + self.doMethodLater(60 * 60., self._sendLeaks, self._doLaterName) + + def _stopSending(self): + if self._doLaterName: + self.removeTask(self._doLaterName) + self._doLaterName = None + + def _sendLeaks(self, task=None): + for desc, curNum in self._curLeakDesc2num.iteritems(): + # only send the number of occurrences of each leak that + # we haven't already sent + self._sentLeakDesc2num.setdefault(desc, 0) + num = curNum - self._sentLeakDesc2num[desc] + if num > 0: + self.cr.timeManager.d_setClientGarbageLeak(num, desc) + self._sentLeakDesc2num[desc] = curNum + if task: + return task.again + diff --git a/otp/src/ai/GarbageLeakServerEventAggregatorAI.py b/otp/src/ai/GarbageLeakServerEventAggregatorAI.py new file mode 100644 index 0000000..b00ad33 --- /dev/null +++ b/otp/src/ai/GarbageLeakServerEventAggregatorAI.py @@ -0,0 +1,89 @@ +from direct.showbase.DirectObject import DirectObject +from direct.showbase import GarbageReport + +class GarbageLeakServerEventAggregatorAI(DirectObject): + ClientLeakEvent = 'LeakAggregator-ClientGarbageLeakReceived' + def __init__(self, air): + self.air = air + self._eventFreq = config.GetFloat('garbage-leak-server-event-frequency', 60 * 60.) + self._doLaterName = None + self._sentLeakDesc2num = {} + self._curLeakDesc2num = {} + self.accept(GarbageReport.GarbageCycleCountAnnounceEvent, + self._handleCycleCounts) + self._clientStartFDC = None + self._doLaterNameClient = None + self._sentClientDesc2num = {} + self._curClientDesc2num = {} + self.accept(self.ClientLeakEvent, self._handleClientCycleCount) + + def destroy(self): + self.ignoreAll() + self._clientStartFDC.destroy() + self._stopSending() + self._stopSendingClientLeaks() + del self.air + + def _handleCycleCounts(self, desc2num): + self._curLeakDesc2num = desc2num + self._startSending() + + def _handleClientCycleCount(self, num, description): + self._curClientDesc2num.setdefault(description, 0) + self._curClientDesc2num[description] += num + if not self._clientStartFDC: + # do an FDC to allow other concurrent client events to make it in, for dev/testing + self._clientStartFDC = FrameDelayedCall( + uniqueName('%s-startClientSend' % self.__class__.__name__), + self._startSendingClientLeaks) + + def _startSending(self): + if not self._doLaterName: + self._sendLeaks() + self._doLaterName = uniqueName('%s-sendGarbageServerEvents' % self.__class__.__name__) + self.doMethodLater(self._eventFreq, self._sendLeaks, self._doLaterName) + + def _stopSending(self): + self.removeTask(self._doLaterName) + self._doLaterName = None + + def _sendLeaks(self, task=None): + # only send the number of occurences of each leak that + # we haven't already sent + for desc, curNum in self._curLeakDesc2num.iteritems(): + self._sentLeakDesc2num.setdefault(desc, 0) + num = curNum - self._sentLeakDesc2num[desc] + if num > 0: + if hasattr(self.air, 'districtId'): + who = self.air.districtId + eventName = 'ai-garbage' + else: + who = self.air.ourChannel + eventName = 'ud-garbage' + self.air.writeServerEvent(eventName, who, '%s|%s' % (num, desc)) + self._sentLeakDesc2num[desc] = curNum + if task: + return task.again + + def _startSendingClientLeaks(self): + if not self._doLaterNameClient: + self._sendClientLeaks() + self._doLaterNameClient = uniqueName( + '%s-sendClientGarbageServerEvents' % self.__class__.__name__) + self.doMethodLater(self._eventFreq, self._sendClientLeaks, self._doLaterNameClient) + + def _stopSendingClientLeaks(self): + self.removeTask(self._doLaterNameClient) + self._doLaterNameClient = None + + def _sendClientLeaks(self, task=None): + # only send the number of occurences of each leak that + # we haven't already sent + for desc, curNum in self._curClientDesc2num.iteritems(): + self._sentClientDesc2num.setdefault(desc, 0) + num = curNum - self._sentClientDesc2num[desc] + if num > 0: + self.air.writeServerEvent('client-garbage', self.air.districtId, '%s|%s' % (num, desc)) + self._sentClientDesc2num[desc] = curNum + if task: + return task.again diff --git a/otp/src/ai/GlobalDistributedClassAI.py b/otp/src/ai/GlobalDistributedClassAI.py new file mode 100644 index 0000000..9ce7731 --- /dev/null +++ b/otp/src/ai/GlobalDistributedClassAI.py @@ -0,0 +1,44 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task import Task +from direct.showbase import DirectObject +#from direct.directnotify import DirectNotifyGlobal +import time + +class GlobalDistributedClassAI(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory("GlobalDistributedClassAI") + + def __init__(self, air, doid, className): + self.doId = doid + self.air = air + # Setting teh Zone to None means No zone logic for this object + self.zoneId = None + self.dclass = air.dclassesByName[className] + # upside registration + self.air.addDOToTables(self) + self.air.registerForChannel(self.doId) + + def remove(): + self.air.unregisterForChannel(do.doId) + # This is a race, you may have messages pending delivery that are already + # on there way to or in the local queue: + self.air.removeDOFromTables(self) + del self.air + del sel.doId + + ############################# + ## Support Functions + ############################# + + def sendUpdateToAvatarIdFromDOID(self, avId, fieldName, args): + channelId = self.GetPuppetConnectionChannel(avId) + self.sendUpdateToChannelFromDOID(channelId, fieldName, args) + + ######## + # Special Function to set return address to the DOID + ######## + def sendUpdateToChannelFromDOID(self, channelId, fieldName, args): + self.air.sendUpdateToChannelFrom(self, channelId, fieldName,self.doId, args) + + diff --git a/otp/src/ai/MagicWordManager.py b/otp/src/ai/MagicWordManager.py new file mode 100644 index 0000000..3254227 --- /dev/null +++ b/otp/src/ai/MagicWordManager.py @@ -0,0 +1,1116 @@ +from pandac.PandaModules import * +from direct.showbase import GarbageReport, ContainerReport, MessengerLeakDetector +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.showbase.InputStateGlobal import inputState +from direct.task import Task +from direct.task.TaskProfiler import TaskProfiler +from otp.avatar import Avatar +import string +from direct.showbase import PythonUtil +from direct.showbase.PythonUtil import Functor, DelayedCall, ScratchPad +from otp.otpbase import OTPGlobals +from direct.distributed.ClockDelta import * +from direct.showutil.TexViewer import TexViewer + +class MagicWordManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("MagicWordManager") + neverDisable = 1 + + GameAvatarClass = None + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + self.shownFontNode = None + self.csShown = 0 + self.guiPopupShown = 0 + self.texViewer = None + + def generate(self): + DistributedObject.DistributedObject.generate(self) + self.accept("magicWord", self.b_setMagicWord) + self.autoMagicWordEvent = localAvatar.getArrivedOnDistrictEvent() + if localAvatar.isGeneratedOnDistrict(): + self.doLoginMagicWords() + else: + self.accept(self.autoMagicWordEvent, self.doLoginMagicWords) + + def doLoginMagicWords(self): + # MPG - we probably want generic versions of these + """ + if base.config.GetBool('want-chat', 0): + # Automatically send ~chat if want-chat is true. + self.d_setMagicWord('~chat', base.localAvatar.doId, 0) + if base.config.GetBool('want-run', 0): + self.toggleRun() + if base.config.GetBool('immortal-mode', 0): + self.d_setMagicWord('~immortal', base.localAvatar.doId, 0) + """ + pass + + def disable(self): + self.ignore(self.autoMagicWordEvent) + del self.autoMagicWordEvent + self.ignore("magicWord") + #if self.dbg_running_fast: + # self.toggleRun() + self.hidefont() + DistributedObject.DistributedObject.disable(self) + + def setMagicWord(self, word, avId, zoneId): + try: + self.doMagicWord(word, avId, zoneId) + except: + response = PythonUtil.describeException(backTrace = 1) + self.notify.warning("Ignoring error in magic word:\n%s" % response) + self.setMagicWordResponse(response) + + def wordIs(self, word, w): + return word == w or word[:(len(w)+1)] == ('%s ' % w) + + def getWordIs(self, word): + # bind a word to self.wordIs and return a callable obj + return Functor(self.wordIs, word) + + def doMagicWord(self, word, avId, zoneId): + wordIs = self.getWordIs(word) + + print word + if wordIs("~oobe"): + base.oobe() + elif wordIs("~oobeCull"): + base.oobeCull() + elif wordIs("~tex"): + self.doTex(word) + elif wordIs("~texmem"): + base.toggleTexMem() + elif wordIs("~verts"): + base.toggleShowVertices() + elif wordIs("~wire"): + base.toggleWireframe() + elif wordIs("~stereo"): + base.toggleStereo() + elif wordIs("~showfont"): + self.showfont(word[9:]) + elif wordIs("~hidefont"): + self.hidefont() + elif wordIs("~guiPopup"): + self.toggleGuiPopup() + + elif wordIs("~showCS") or wordIs("~showcs"): + bitmask = self.getCSBitmask(word[7:]) + render.showCS(bitmask) + self.csShown = 1 + + elif wordIs("~hideCS") or wordIs("~hidecs"): + bitmask = self.getCSBitmask(word[7:]) + render.hideCS(bitmask) + self.csShown = 0 + + elif wordIs("~cs"): + # Toggle hide/show collision solids: + # (Also a shorthand for ~hideCS and ~showCS). + bitmask = self.getCSBitmask(word[3:]) + if self.csShown: + render.hideCS(bitmask) + self.csShown = 0 + else: + render.showCS(bitmask) + self.csShown = 1 + + elif wordIs("~showShadowCollisions"): + self.showShadowCollisions() + + elif wordIs("~hideShadowCollisions"): + self.hideShadowCollisions() + + elif wordIs("~showCollisions"): + self.showCollisions() + + elif wordIs("~hideCollisions"): + self.hideCollisions() + + elif wordIs("~showCameraCollisions"): + self.showCameraCollisions() + + elif wordIs("~hideCameraCollisions"): + self.hideCameraCollisions() + + elif wordIs("~collidespam"): + n = Notify.ptr().getCategory(':collide') + if hasattr(self, '_collideSpamSeverity'): + n.setSeverity(self._collideSpamSeverity) + del self._collideSpamSeverity + else: + self._collideSpamSeverity = n.getSeverity() + n.setSeverity(NSSpam) + + elif wordIs("~notify"): + args = word.split() + n = Notify.ptr().getCategory(args[1]) + n.setSeverity( + {'error': NSError, + 'warning': NSWarning, + 'info': NSInfo, + 'debug': NSDebug, + 'spam': NSSpam,}[args[2]]) + + # MPG we probably need generic versions of these + #elif wordIs("~listen"): + # base.localAvatar.garbleChat = 0 + + #elif wordIs("~nochat") or wordIs("~chat") or wordIs("~superchat"): + # base.localAvatar.garbleChat = 1 + + elif wordIs("~stress"): + factor = word[7:] + if factor: + factor = float(factor) + LOD.setStressFactor(factor) + response = "Set LOD stress factor to %s" % (factor) + else: + factor = LOD.getStressFactor() + response = "LOD stress factor is %s" % (factor) + + self.setMagicWordResponse(response) + + elif wordIs("~for"): + self.forAnother(word, avId, zoneId) + + elif wordIs("~badname"): + # ~badname with an argument becomes ~for ... ~badname + word = "~for %s ~badname" % (word[9:]) + print "word is %s" % (word) + self.forAnother(word, avId, zoneId) + + elif wordIs('~avId'): + self.setMagicWordResponse(str(localAvatar.doId)) + + elif wordIs("~doId"): + name = string.strip(word[6:]) + + objs = self.identifyDistributedObjects(name) + if (len(objs) == 0): + response = "%s is unknown." % (name) + else: + response = "" + for name, obj in objs: + response += "\n%s %d" % (name, obj.doId) + response = response[1:] + + self.setMagicWordResponse(response) + + # MPG - need generic versions of these + #elif wordIs("~collisions_on"): + # base.localAvatar.collisionsOn() + + #elif wordIs("~collisions_off"): + # base.localAvatar.collisionsOff() + + #elif wordIs('~addCameraPosition'): + # base.localAvatar.addCameraPosition() + + #elif wordIs('~removeCameraPosition'): + # base.localAvatar.removeCameraPosition() + + #elif wordIs('~printCameraPosition'): + # base.localAvatar.printCameraPosition( + # base.localAvatar.cameraIndex) + + #elif wordIs('~printCameraPositions'): + # base.localAvatar.printCameraPositions() + + elif wordIs("~exec"): + # Enable execChat. + from otp.chat import ChatManager + ChatManager.ChatManager.execChat = 1 + + elif wordIs("~run"): + self.toggleRun() + + elif wordIs("~runFaster"): + if(config.GetBool("want-running",1)): + args = word.split() + if(len(args)>1): + base.debugRunningMultiplier = float(args[1]) + else: + base.debugRunningMultiplier = 10 + inputState.set("debugRunning", True) + + elif wordIs("~who"): + # Get all the nearby avIds and send them to the AI. + avIds = [] + for av in Avatar.Avatar.ActiveAvatars: + # If the avatar has a friends list, it's probably a + # real avatar and not an NPC. + if hasattr(av, "getFriendsList"): + avIds.append(av.doId) + self.d_setWho(avIds) + + elif wordIs("~sync"): + # Sync with the AI, like F6, but rather than accumulating + # sync informatoin, throw away whatever information was + # there from before. If a second parameter is supplied, + # it is a number of seconds of temporary extra skew to + # apply; the default is 0. + + tm = self.cr.timeManager + if tm == None: + response = "No TimeManager." + self.setMagicWordResponse(response) + else: + tm.extraSkew = 0.0 + skew = string.strip(word[5:]) + if skew != "": + tm.extraSkew = float(skew) + globalClockDelta.clear() + tm.handleHotkey() + + elif wordIs("~period"): + # Reset the period timer to expire in the indicated number + # of seconds, or with no parameter, report the number of + # seconds remaining. + + timeout = string.strip(word[7:]) + if timeout != "": + seconds = int(timeout) + self.cr.stopPeriodTimer() + self.cr.resetPeriodTimer(seconds) + self.cr.startPeriodTimer() + + # Now report the number of seconds remaining. + if self.cr.periodTimerExpired: + response = "Period timer has expired." + + elif self.cr.periodTimerStarted: + elapsed = globalClock.getFrameTime() - self.cr.periodTimerStarted + secondsRemaining = self.cr.periodTimerSecondsRemaining - elapsed + response = "Period timer expires in %s seconds." % (int(secondsRemaining)) + else: + response = "Period timer not set." + + self.setMagicWordResponse(response) + + elif wordIs("~DIRECT"): + args = word.split() + fEnableLight = 0 + if len(args) > 1: + if direct and (args[1] == 'CAM'): + direct.enable() + taskMgr.removeTasksMatching('updateSmartCamera*') + camera.wrtReparentTo(render) + direct.cameraControl.enableMouseFly() + self.setMagicWordResponse("Enabled DIRECT camera") + return + elif args[1] == 'LIGHT': + fEnableLight = 1 + # Start up DIRECT + base.startTk() + from direct.directtools import DirectSession + if fEnableLight: + direct.enableLight() + else: + direct.enable() + self.setMagicWordResponse("Enabled DIRECT") + + elif wordIs("~TT"): + if not direct: + return + args = word.split() + if len(args) > 1: + if (args[1] == 'CAM'): + direct.cameraControl.disableMouseFly() + camera.wrtReparentTo(base.localAvatar) + base.localAvatar.startUpdateSmartCamera() + self.setMagicWordResponse("Disabled DIRECT camera") + return + # Return to toontown mode + direct.disable() + camera.wrtReparentTo(base.localAvatar) + base.localAvatar.startUpdateSmartCamera() + self.setMagicWordResponse("Disabled DIRECT") + + elif wordIs("~net"): + # Simulate pulling or restoring the network plug. + if self.cr.networkPlugPulled(): + self.cr.restoreNetworkPlug() + self.cr.startHeartbeat() + response = "Network restored." + else: + self.cr.pullNetworkPlug() + self.cr.stopHeartbeat() + response = "Network disconnected." + self.setMagicWordResponse(response) + + elif wordIs('~disconnect'): + # force a simulated disconnect + # you can also do this from the OTP webpage + base.cr.distributedDistrict.sendUpdate('broadcastMessage') + + elif wordIs("~model"): + # load a model into the scene graph at the location of localAvatar + args = word.split() + path = args[1] + model = loader.loadModel(path) + model.reparentTo(localAvatar) + model.wrtReparentTo(render) + self.setMagicWordResponse('loaded %s' % path) + + elif wordIs("~axis"): + # Show a 10 foot and 100 foot axis at the spot of the avatar + # axis aligned to render + axis = loader.loadModel("models/misc/xyzAxis.bam") + axis.reparentTo(render) + axis.setPos(base.localAvatar, 0,0,0) + axis.setHpr(render, 0,0,0) + axis10 = loader.loadModel("models/misc/xyzAxis.bam") + axis10.reparentTo(render) + axis10.setPos(base.localAvatar, 0,0,0) + axis10.setScale(10) + axis10.setHpr(render, 0,0,0) + axis10.setColorScale(1,1,1,0.4) + axis10.setTransparency(1) + + elif (wordIs("~clearAxes") or wordIs("~clearAxis")): + # Remove the effects of ~axis calls + render.findAllMatches("**/xyzAxis.egg").detach() + + elif wordIs("~myAxis"): + if hasattr(self, 'myAxis'): + self.myAxis.detachNode() + del self.myAxis + else: + self.myAxis = loader.loadModel("models/misc/xyzAxis.bam") + self.myAxis.reparentTo(localAvatar) + + elif (wordIs("~osd")): + onScreenDebug.enabled = not onScreenDebug.enabled + + elif wordIs("~osdScale"): + args = word.split() + defScale = .05 + if len(args) > 1: + scale = float(args[1]) + else: + scale = 1. + onScreenDebug.onScreenText.setScale(defScale * scale) + + elif wordIs('~osdTaskMgr'): + if taskMgr.osdEnabled(): + taskMgr.stopOsd() + else: + if not onScreenDebug.enabled: + onScreenDebug.enabled = True + taskMgr.startOsd() + + elif wordIs("~fps"): + self.doFps(word, avId, zoneId) + + elif wordIs("~sleep"): + args = word.split() + if len(args) > 1: + s = float(args[1]) + base.setSleep(s) + response = 'sleeping %s' % s + else: + base.setSleep(0.0) + response = 'not sleeping' + self.setMagicWordResponse(response) + + elif wordIs('~objects'): + args = word.split() + from direct.showbase import ObjectReport + report = ObjectReport.ObjectReport('client ~objects') + + if 'all' in args: + self.notify.info('printing full object set...') + report.getObjectPool().printObjsByType(printReferrers='ref' in args) + + if hasattr(self, 'baselineObjReport'): + self.notify.info('calculating diff from baseline ObjectReport...') + self.lastDiff = self.baselineObjReport.diff(report) + self.lastDiff.printOut(full=('diff' in args or 'dif' in args)) + + if 'baseline' in args or not hasattr(self, 'baselineObjReport'): + self.notify.info('recording baseline ObjectReport...') + if hasattr(self, 'baselineObjReport'): + self.baselineObjReport.destroy() + self.baselineObjReport = report + + self.setMagicWordResponse('objects logged') + + elif wordIs('~objecthg'): + import gc + objs = gc.get_objects() + type2count = {} + for obj in objs: + tn = safeTypeName(obj) + type2count.setdefault(tn, 0) + type2count[tn] += 1 + count2type = invertDictLossless(type2count) + counts = count2type.keys() + counts.sort() + counts.reverse() + for count in counts: + print '%s: %s' % (count, count2type[count]) + self.setMagicWordResponse('~aiobjecthg complete') + + elif wordIs('~containers'): + args = word.split() + limit = 30 + if 'full' in args: + limit = None + ContainerReport.ContainerReport('~containers', log=True, limit=limit, threaded=True) + + elif wordIs('~garbage'): + args = word.split() + # it can take a LOOONG time to print out the garbage referrers and referents + # by reference (as opposed to by number) + full = 'full' in args + safeMode = 'safe' in args + delOnly = 'delonly' in args + # This does a garbage collection and dumps the list of leaked (uncollectable) objects to the log. + GarbageReport.GarbageLogger('~garbage', fullReport=full, threaded=True, + safeMode=safeMode, delOnly=delOnly, + doneCallback=self.garbageReportDone) + # this is coming back from the AI + #self.setMagicWordResponse('garbage logged') + + elif wordIs('~guicreates'): + base.printGuiCreates = True + self.setMagicWordResponse('printing gui creation stacks') + + elif wordIs("~creategarbage"): + GarbageReport._createGarbage() + # this is coming back from the AI + #self.setMagicWordResponse(senderId, 'leaked garbage created') + + elif wordIs('~leakTask'): + def leakTask(task): + return task.cont + taskMgr.add(leakTask, uniqueName('leakedTask')) + leakTask = None + # this is coming back from the AI + #self.setMagicWordResponse(senderId, 'leaked task created') + + elif wordIs('~leakmessage'): + MessengerLeakDetector._leakMessengerObject() + self.down_setMagicWordResponse(senderId, 'messenger leak object created') + + elif wordIs('~pstats'): + args = word.split() + hostname = None + port = None + if len(args) > 1: + hostname = args[1] + if len(args) > 2: + port = int(args[2]) + # make sure pstats is enabled + base.wantStats = 1 + Task.TaskManager.pStatsTasks = 1 + result = base.createStats(hostname, port) + connectionName = '%s' % hostname + if port is not None: + connectionName += ':%s' % port + if result: + response = 'connected client pstats to %s' % connectionName + else: + response = 'could not connect pstats to %s' % connectionName + self.setMagicWordResponse(response) + + elif wordIs('~profile'): + args = word.split() + if len(args) > 1: + num = int(args[1]) + else: + num = 5 + session = taskMgr.getProfileSession('~profile') + session.setLogAfterProfile(True) + taskMgr.profileFrames(num, session) + self.setMagicWordResponse('profiling %s client frames...' % num) + + elif wordIs('~frameprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileFrames()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileFrames(setting) + self.setMagicWordResponse( + 'frame profiling %s%s' % (choice(setting, 'ON', 'OFF'), + choice(wasOn == setting, ' already', ''))) + + elif wordIs('~taskprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileTasks()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileTasks(setting) + self.setMagicWordResponse( + 'task profiling %s%s' % (choice(setting, 'ON', 'OFF'), + choice(wasOn == setting, ' already', ''))) + + elif wordIs('~taskspikethreshold'): + args = word.split() + if len(args) > 1: + threshold = float(args[1]) + response = 'task spike threshold set to %ss' % threshold + else: + threshold = TaskProfiler.GetDefaultSpikeThreshold() + response = 'task spike threshold reset to %ss' % threshold + TaskProfiler.SetSpikeThreshold(threshold) + self.setMagicWordResponse(response) + + elif wordIs('~logtaskprofiles'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.logTaskProfiles(name) + response = 'logged task profiles%s' % choice(name, ' for %s' % name, '') + self.setMagicWordResponse(response) + + elif wordIs('~taskprofileflush'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.flushTaskProfiles(name) + response = 'flushed AI task profiles%s' % choice(name, ' for %s' % name, '') + self.setMagicWordResponse(response) + + elif wordIs('~objectcount'): + base.cr.printObjectCount() + self.setMagicWordResponse('logging client distributed object count...') + + elif wordIs('~taskmgr'): + print taskMgr + self.setMagicWordResponse('logging client taskMgr...') + + elif wordIs('~jobmgr'): + print jobMgr + self.setMagicWordResponse('logging client jobMgr...') + + elif wordIs('~jobtime'): + args = word.split() + if len(args) > 1: + time = float(args[1]) + else: + time = None + response = '' + if time is None: + time = jobMgr.getDefaultTimeslice() + response = 'reset client jobMgr timeslice to %s ms' % time + else: + response = 'set client jobMgr timeslice to %s ms' % time + time = time / 1000. + jobMgr.setTimeslice(time) + self.setMagicWordResponse(response) + + elif wordIs('~detectleaks'): + started = self.cr.startLeakDetector() + self.setMagicWordResponse( + choice(started, + 'leak detector started', + 'leak detector already started', + )) + + elif wordIs('~taskthreshold'): + args = word.split() + if len(args) > 1.: + threshold = float(args[1]) + else: + threshold = None + response = '' + if threshold is None: + threshold = taskMgr.DefTaskDurationWarningThreshold + response = 'reset task duration warning threshold to %s' % threshold + else: + response = 'set task duration warning threshold to %s' % threshold + taskMgr.setTaskDurationWarningThreshold(threshold) + self.setMagicWordResponse(response) + + elif wordIs('~messenger'): + print messenger + self.setMagicWordResponse('logging client messenger...') + + elif wordIs('~clientcrash'): + # if we call notify.error directly, the magic word mgr will catch it + # self.notify.error doesn't seem to work either + DelayedCall(Functor(self.notify.error, '~clientcrash: simulating a client crash')) + + elif wordIs('~badDelete'): + doId = 0 + while doId in base.cr.doId2do: + doId += 1 + # location (0,0) is special, pass in (1,1) + # deleteObjectLocation expects a DO, pass in a ScratchPad instead + # we must delay the call because magicWordMgr is in a big try/except block + DelayedCall(Functor(base.cr.deleteObjectLocation, ScratchPad(doId=doId), 1, 1)) + self.setMagicWordResponse('doing bad delete') + + elif wordIs("~idTags"): + messenger.send('nameTagShowAvId', []) + base.idTags = 1 + + elif wordIs("~nameTags"): + messenger.send('nameTagShowName', []) + base.idTags = 0 + + elif wordIs("~hideNames"): + # note do ~hideNames before ~hideGui if you want both off + if NametagGlobals.getMasterNametagsVisible(): + NametagGlobals.setMasterNametagsVisible(0) + else: + NametagGlobals.setMasterNametagsVisible(1) + + elif wordIs("~hideGui"): + if aspect2d.isHidden(): + aspect2d.show() + else: + aspect2d.hide() + + elif wordIs('~flush'): + base.cr.doDataCache.flush() + base.cr.cache.flush() + self.setMagicWordResponse('client object and data caches flushed') + + elif wordIs('~prof'): + import time + + ### set up ### + name = 'default' + p = Point3() + ############## + + ts = time.time() + for i in xrange(1000000): + + ### code to be timed ### + p.set(1,2,3) + ######################## + + tf = time.time() + dt = tf - ts + response = 'prof(%s): %s secs' % (name, dt) + print response + self.setMagicWordResponse(response) + + elif wordIs('~gptc'): + args = word.split() + if len(args) > 1. and hasattr(self.cr, 'leakDetector'): + gptcJob = self.cr.leakDetector.getPathsToContainers( + '~gptc', args[1], Functor(self._handleGPTCfinished, args[1])) + else: + self.setMagicWordResponse('error') + + elif wordIs('~gptcn'): + args = word.split() + if len(args) > 1. and hasattr(self.cr, 'leakDetector'): + gptcnJob = self.cr.leakDetector.getPathsToContainersNamed( + '~gptcn', args[1], Functor(self._handleGPTCNfinished, args[1])) + else: + self.setMagicWordResponse('error') + + else: + # Not a magic word I know! + return 0 + + return 1 + + # MPG need a generic version of this + def toggleRun(self): + if(config.GetBool("want-running",1)): + inputState.set("debugRunning", + inputState.isSet("debugRunning") != True) + + def d_setMagicWord(self, magicWord, avId, zoneId): + self.sendUpdate("setMagicWord", [magicWord, avId, zoneId, + base.cr.userSignature]) + + def b_setMagicWord(self, magicWord, avId = None, zoneId = None): + if self.cr.wantMagicWords: + if avId == None: + avId = base.localAvatar.doId + + if zoneId == None: + # Try to figure out what zone the avatar is in. Some + # magic words find this useful. + try: + zoneId = self.cr.playGame.getPlace().getZoneId() + except: + pass + if zoneId == None: + zoneId = 0 + + self.d_setMagicWord(magicWord, avId, zoneId) + + # We handle this as a special case, before we get into + # setMagicWord, since that function will trap exceptions. + if (magicWord.count('~crash')): + args = magicWord.split() + errorCode = 12 # 12 = general python exception + if len(args) > 1: + errorCode = int(args[1]) + + self.notify.info("Simulating client crash: exit error = %s" % (errorCode)) + base.exitShow(errorCode) + + self.setMagicWord(magicWord, avId, zoneId) + + + def setMagicWordResponse(self, response): + """setMagicWordResponse(self, string response) + + The AI might send a formatted string response to certain magic + words. + """ + base.localAvatar.setChatAbsolute(response, CFSpeech | CFTimeout) + base.talkAssistant.receiveDeveloperMessage(response) + + def d_setWho(self, avIds): + self.sendUpdate("setWho", [avIds]) + + def _handleGPTCfinished(self, ct, gptcJob): + self.setMagicWordResponse('gptc(%s) finished' % ct) + + def _handleGPTCNfinished(self, cn, gptcnJob): + self.setMagicWordResponse('gptcn(%s) finished' % cn) + + def forAnother(self, word, avId, zoneId): + # The magic word ~for was spoken, which means to execute the + # rest of the string as a magic word on behalf on another + # avatar. + + # Separate out the next magic word and the name. + b = 5 + while word[b:b+2] != ' ~': + b += 1 + if b >= len(word): + self.setMagicWordResponse("No next magic word!") + return + + nextWord = word[b+1:] + name = string.strip(word[5:b]) + + id = self.identifyAvatar(name) + if (id == None): + self.setMagicWordResponse("Don't know who %s is." % (name)) + return + + self.d_setMagicWord(nextWord, id, zoneId) + + def identifyAvatar(self, name): + self.notify.error("Pure virtual - please override me.") + + def identifyDistributedObjects(self, name): + # Find the nearby distributed object or objects (of any type) + # with the given name. Returns a list of (name, obj) pairs. + + result = [] + lowerName = string.lower(name) + + for obj in self.cr.doId2do.values(): + className = obj.__class__.__name__ + try: + name = obj.getName() + except: + name = className + + if string.lower(name) == lowerName or \ + string.lower(className) == lowerName or \ + string.lower(className) == "distributed" + lowerName: + result.append((name, obj)) + + return result + + def doTex(self, word): + """ Toggles texturing (with no parameters) or shows the named + texture (with a parameter). """ + + args = word.split() + if len(args) <= 1: + # No parameters: clean up the old texture viewer. + if self.texViewer: + self.texViewer.cleanup() + self.texViewer = None + return + + # No parameters, and now texture viewer: toggle texture. + base.toggleTexture() + return + + # At least one parameter: show the named texture. + if self.texViewer: + self.texViewer.cleanup() + self.texViewer = None + + tex = TexturePool.findTexture(args[1]) + if not tex: + # Try it with stars on both ends. + tex = TexturePool.findTexture('*%s*' % (args[1])) + + if not tex: + # Couldn't find that texture. + self.setMagicWordResponse("Unknown texture: %s" % (args[1])) + return + + self.texViewer = TexViewer(tex) + + def getCSBitmask(self, str): + # Decompose the string into keywords, and return the + # corresponding collision bitmask, suitable for passing to + # NodePath.showCS() or hideCS(). + words = string.lower(str).split() + if len(words) == 0: + return None + + invalid = "" + + bitmask = BitMask32.allOff() + for w in words: + if w == "wall": + bitmask |= OTPGlobals.WallBitmask + elif w == "floor": + bitmask |= OTPGlobals.FloorBitmask + elif w == "cam": + bitmask |= OTPGlobals.CameraBitmask + elif w == "catch": + bitmask |= OTPGlobals.CatchBitmask + elif w == "ghost": + bitmask |= OTPGlobals.GhostBitmask + elif w == "pet": + bitmask |= OTPGlobals.PetBitmask + elif w == "furniture": + bitmask |= (OTPGlobals.FurnitureSideBitmask | + OTPGlobals.FurnitureTopBitmask | + OTPGlobals.FurnitureDragBitmask) + elif w == "furnitureside": + bitmask |= OTPGlobals.FurnitureSideBitmask + elif w == "furnituretop": + bitmask |= OTPGlobals.FurnitureTopBitmask + elif w == "furnituredrag": + bitmask |= OTPGlobals.FurnitureDragBitmask + elif w == "pie": + bitmask |= OTPGlobals.PieBitmask + else: + try: + # Convert the string to an integer and use that + # to spec a bitmask. Use try/except because w + # might not be an integer. Is there a better way + # to check? + bitmask |= BitMask32.bit(int(w)) + print bitmask + except ValueError: + invalid += " " + w + if invalid: + self.setMagicWordResponse("Unknown CS keyword(s): %s" % invalid) + + return bitmask + + def getFontByName(self, fontname): + if fontname == "default": + return TextNode.getDefaultFont() + elif fontname == "interface": + return OTPGlobals.getInterfaceFont() + elif fontname == "sign": + return OTPGlobals.getSignFont() + else: + return None + + def showfont(self, fontname): + fontname = string.strip(string.lower(fontname)) + font = self.getFontByName(fontname) + if font == None: + self.setMagicWordResponse("Unknown font: %s" % (fontname)) + return + + if not isinstance(font, DynamicTextFont): + self.setMagicWordResponse("Font %s is not dynamic." % (fontname)) + return + + # Hide the previously shown font, if any + self.hidefont() + + # Show the new font. + self.shownFontNode = aspect2d.attachNewNode('shownFont') + + # We use a TextNode just to create big square polygons. + tn = TextNode('square') + tn.setCardActual(0.0, 1.0, -1.0, 0.0) + tn.setFrameActual(0.0, 1.0, -1.0, 0.0) + tn.setCardColor(1, 1, 1, 0.5) + tn.setFrameColor(1, 1, 1, 1) + tn.setFont(font) + tn.setText(' ') + + numXPages = 2 + numYPages = 2 + pageScale = 0.8 + pageMargin = 0.1 + + numPages = font.getNumPages() + x = 0 + y = 0 + for pi in range(numPages): + page = font.getPage(pi) + tn.setCardTexture(page) + np = self.shownFontNode.attachNewNode(tn.generate()) + np.setScale(pageScale) + np.setPos(float(x) / numXPages * 2 - 1 + pageMargin, 0, + 1 - float(y) / numYPages * 2 - pageMargin), + x += 1 + if x >= numXPages: + y += 1 + x = 0 + + def hidefont(self): + if self.shownFontNode != None: + self.shownFontNode.removeNode() + self.shownFontNode = None + + def showShadowCollisions(self): + try: + base.shadowTrav.showCollisions(render) + except: + self.setMagicWordResponse("CollisionVisualizer is not compiled in.") + + def hideShadowCollisions(self): + base.shadowTrav.hideCollisions() + + def showCollisions(self): + try: + base.cTrav.showCollisions(render) + except: + self.setMagicWordResponse("CollisionVisualizer is not compiled in.") + + def hideCollisions(self): + base.cTrav.hideCollisions() + + def showCameraCollisions(self): + try: + localAvatar.ccTrav.showCollisions(render) + except: + self.setMagicWordResponse("CollisionVisualizer is not compiled in.") + + def hideCameraCollisions(self): + localAvatar.ccTrav.hideCollisions() + + def doFps(self, word, avId, zoneId): + # The ~fps magic word: play with the frame rate meter and the + # funny clock modes. + + args = word.split() + + response = None + if len(args) == 1 or args[1] == "normal": + # ~fps or ~fps normal: toggle the frame rate meter and/or + # return the clock to normal mode. + + if globalClock.getMode() != ClockObject.MNormal: + globalClock.setMode(ClockObject.MNormal) + response = "Normal frame rate set." + else: + base.setFrameRateMeter(not base.frameRateMeter) + + elif args[1] == "forced": + # ~fps forced fps: force a particular frame rate. + fps = float(args[2]) + globalClock.setMode(ClockObject.MForced) + globalClock.setDt(1.0/fps) + response = "Frame rate forced to %s fps." % (fps) + base.setFrameRateMeter(1) + + elif args[1] == "degrade": + # ~fps degrade factor: degrade the frame rate by the + # indicated factor. + factor = float(args[2]) + globalClock.setMode(ClockObject.MDegrade) + globalClock.setDegradeFactor(factor) + response = "Frame rate degraded by factor of %s." % (factor) + base.setFrameRateMeter(1) + + elif args[1][-1] == '%': + # ~fps %: degrade the frame rate to the indicated + # percentage. + percent = float(args[1][:-1]) + if (percent == 100): + globalClock.setMode(ClockObject.MNormal) + response = "Normal frame rate set." + else: + globalClock.setMode(ClockObject.MDegrade) + globalClock.setDegradeFactor(100.0/percent) + response = "Frame rate degraded to %s percent." % (percent) + base.setFrameRateMeter(1) + + else: + try: + fps = float(args[1]) + except: + fps = None + if fps != None: + # ~fps : force a particular frame rate. + globalClock.setMode(ClockObject.MForced) + globalClock.setDt(1.0/fps) + response = "Frame rate forced to %s fps." % (fps) + base.setFrameRateMeter(1) + else: + response = "Unknown fps command: ~s" % (args[1]) + + if base.frameRateMeter: + # Restore the average frame rate interval to a sensible + # value for display the fps meter. This might have been + # set too high by the client-fps mechanism. + globalClock.setAverageFrameRateInterval(ConfigVariableDouble('average-frame-rate-interval').getValue()) + + if response != None: + self.setMagicWordResponse(response) + + + def identifyAvatar(self, name): + # Find the nearby avatar with the given name. + + # First, try an exact name match. + for av in Avatar.Avatar.ActiveAvatars: + if isinstance(av, self.GameAvatarClass) and \ + av.getName() == name: + return av.doId + + # No good; try a case-insensitive match. + lowerName = string.lower(name) + for av in Avatar.Avatar.ActiveAvatars: + if isinstance(av, self.GameAvatarClass) and \ + string.strip(string.lower(av.getName())) == lowerName: + return av.doId + + # Is it a doId anyway? + try: + avId = int(name) + return avId + except: + pass + + # Oh well. + return None + + def toggleGuiPopup(self): + if self.guiPopupShown: + base.mouseWatcherNode.hideRegions() + self.guiPopupShown = 0 + else: + base.mouseWatcherNode.showRegions(render2d, 'gui-popup', 0) + self.guiPopupShown = 1 + + def garbageReportDone(self, garbageReport): + self.setMagicWordResponse('%s garbage cycles' % garbageReport.getNumCycles()) + +def magicWord(mw): + messenger.send('magicWord', [mw]) + +import __builtin__ +__builtin__.magicWord = magicWord diff --git a/otp/src/ai/MagicWordManagerAI.py b/otp/src/ai/MagicWordManagerAI.py new file mode 100644 index 0000000..9cdbab0 --- /dev/null +++ b/otp/src/ai/MagicWordManagerAI.py @@ -0,0 +1,597 @@ +from AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import OTPGlobals +from direct.showbase import PythonUtil, GarbageReport, ContainerReport, MessengerLeakDetector +from direct.showbase import ContainerLeakDetector +from direct.showbase.PythonUtil import Functor, DelayedCall, formatTimeCompact +import fpformat +import string +import time +import re +from direct.task import Task + +class MagicWordManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("MagicWordManagerAI") + + supportSuperchat = simbase.config.GetBool('support-superchat', 0) + supportRename = simbase.config.GetBool('support-rename', 0) + + # Fill in by subclass + GameAvatarClass = None + + # This will hold the local namespace we evaluate '~ai' messages + # within. + ExecNamespace = { } + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + def setMagicWord(self, word, avId, zoneId, signature): + senderId = self.air.getAvatarIdFromSender() + + sender = self.air.doId2do.get(senderId, None) + if sender: + if senderId == avId: + sender = "%s/%s(%s)" % (sender.accountName, sender.name, senderId) + else: + sender = "%s/%s(%s) (for %d)" % (sender.accountName, sender.name, senderId, avId) + else: + sender = "Unknown avatar %d" % (senderId) + + self.notify.info("%s (%s) just said the magic word: %s" % (sender, signature, word)) + self.air.writeServerEvent('magic-word', senderId, "%s|%s|%s" % (sender, signature, word)) + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + + try: + self.doMagicWord(word, av, zoneId, senderId) + except: + response = PythonUtil.describeException(backTrace = 1) + self.notify.warning("Ignoring error in magic word:\n%s" % response) + self.down_setMagicWordResponse(senderId, response) + else: + self.notify.info("Don't know avatar %d." % (avId)) + + def wordIs(self, word, w): + return word == w or word[:(len(w)+1)] == ('%s ' % w) + + def getWordIs(self, word): + # bind a word to self.wordIs and return a callable obj + return Functor(self.wordIs, word) + + def doMagicWord(self, word, av, zoneId, senderId): + wordIs = self.getWordIs(word) + + if wordIs("~rename"): + if (not self.supportRename): + self.notify.warning("Rename is not supported for %s, requested by %d" % (av.name, senderId)) + else: + name = string.strip(word[8:]) + if name == "": + response = "No name." + else: + av.d_setName(name) + + elif wordIs("~badname"): + self.notify.warning("Renaming inappropriately named toon %s (doId %d)." % (av.name, av.doId)) + name = "toon%d" % (av.doId % 1000000) + av.d_setName(name) + + elif wordIs("~chat"): + if (not self.supportSuperchat) and (senderId != av.doId): + self.notify.warning("Super chat is not supported for %s, requested by %d" % (av.name, senderId)) + else: + av.d_setCommonChatFlags(OTPGlobals.CommonChat) + self.notify.debug("Giving common chat permission to " + av.name) + elif wordIs("~superchat"): + if not self.supportSuperchat: + self.notify.warning("Super chat is not supported for " + av.name) + else: + av.d_setCommonChatFlags(OTPGlobals.SuperChat) + self.notify.debug("Giving super chat permission to " + av.name) + elif wordIs("~nochat"): + av.d_setCommonChatFlags(0) + self.notify.debug("Removing special chat permissions for " + av.name) + + elif wordIs("~listen"): + if (not self.supportSuperchat) and (senderId != av.doId): + self.notify.warning("Listen is not supported for %s, requested by %d" % (av.name, senderId)) + else: + # This is a client-side word. + if (senderId != av.doId): + self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId]) + + elif wordIs("~fix"): + anyChanged = av.fixAvatar() + if anyChanged: + response = "avatar fixed." + else: + response = "avatar does not need fixing." + self.down_setMagicWordResponse(senderId, response) + + self.down_setMagicWordResponse(senderId, response) + + elif wordIs("~who all"): + str = '' + for obj in self.air.doId2do.values(): + if hasattr(obj, "accountName"): + str += '%s %s\n' % (obj.accountName, obj.name) + if not str: + str = "No avatars." + self.down_setMagicWordResponse(senderId, str) + + elif wordIs("~ouch"): + if av.hp < 1: + av.b_setHp(0) + av.toonUp(1) + else: + av.b_setHp(1) + self.notify.debug("Only 1 hp for " + av.name) + elif wordIs("~sad"): + av.b_setHp(0) + self.notify.debug("Only 0 hp for " + av.name) + elif wordIs("~dead"): + av.takeDamage(av.hp) + self.notify.debug(av.name + " is dead") + elif wordIs("~waydead"): + av.takeDamage(av.hp) + av.b_setHp(-100) + self.notify.debug(av.name + " is way dead") + elif wordIs("~toonup"): + av.toonUp(av.maxHp) + self.notify.debug("Full heal for " + av.name) + elif wordIs('~hp'): + args = word.split() + hp = int(args[1]) + av.b_setHp(hp) + self.notify.debug('Set hp to %s for %s' % (hp, av.name)) + + elif wordIs("~ainotify"): + args = word.split() + n = Notify.ptr().getCategory(args[1]) + n.setSeverity( + {'error': NSError, + 'warning': NSWarning, + 'info': NSInfo, + 'debug': NSDebug, + 'spam': NSSpam,}[args[2]]) + + elif wordIs("~ghost"): + # Toggle ghost mode. Ghost mode == 2 indicates a magic + # word was the source. + if av.ghostMode: + av.b_setGhostMode(0) + else: + av.b_setGhostMode(2) + + elif wordIs('~immortal'): + # ~immortal toggles immortal mode on and off + # ~immortal 0/1 and ~immortal on/off sets the mode explicitly + args = word.split() + invalid = False + if len(args) > 1 and args[1] in ('0', 'off'): + immortal = False + elif len(args) > 1 and args[1] in ('1', 'on'): + immortal = True + elif len(args) > 1: + invalid = True + else: + immortal = not av.immortalMode + + if invalid: + self.down_setMagicWordResponse(senderId, 'unknown argument %s' % args[1]) + else: + # immortality + av.setImmortalMode(immortal) + if av.immortalMode: + response = 'immortality ON' + else: + response = 'immortality OFF' + self.down_setMagicWordResponse(senderId, response) + + elif wordIs("~dna"): + # Fiddle with your dna. + self.doDna(word, av, zoneId, senderId) + + elif wordIs('~ai'): + # Execute an arbitrary Python command on the AI. + command = string.strip(word[3:]) + self.notify.warning("Executing command '%s' from %s" % (command, senderId)) + text = self.__execMessage(command)[:simbase.config.GetInt("ai-debug-length",300)] + self.down_setMagicWordResponse( + senderId, text) + + elif wordIs('~ud'): + # Execute an arbitrary Python command on the ud. + print word + channel,command = re.match("~ud ([0-9]+) (.+)", word).groups() + channel = int(channel) + if(simbase.air.doId2do.get(channel)): + self.notify.warning("Passing command '%s' to %s from %s" % (command, channel, senderId)) + + try: + simbase.air.doId2do[channel].sendUpdate("execCommand", [command, self.doId, senderId, zoneId]) + except: + pass + + elif wordIs('~aiobjects'): + args = word.split() + from direct.showbase import ObjectReport + report = ObjectReport.ObjectReport('AI ~objects') + + if 'all' in args: + self.notify.info('printing full object set...') + report.getObjectPool().printObjsByType(printReferrers='ref' in args) + + if hasattr(self, 'baselineObjReport'): + self.notify.info('calculating diff from baseline ObjectReport...') + self.lastDiff = self.baselineObjReport.diff(report) + self.lastDiff.printOut(full=('diff' in args or 'dif' in args)) + + if 'baseline' in args or not hasattr(self, 'baselineObjReport'): + self.notify.info('recording baseline ObjectReport...') + if hasattr(self, 'baselineObjReport'): + self.baselineObjReport.destroy() + self.baselineObjReport = report + + self.down_setMagicWordResponse(senderId, 'objects logged') + + elif wordIs('~aiobjecthg'): + import gc + objs = gc.get_objects() + type2count = {} + for obj in objs: + tn = safeTypeName(obj) + type2count.setdefault(tn, 0) + type2count[tn] += 1 + count2type = invertDictLossless(type2count) + counts = count2type.keys() + counts.sort() + counts.reverse() + for count in counts: + print '%s: %s' % (count, count2type[count]) + self.down_setMagicWordResponse(senderId, '~aiobjecthg complete') + + elif wordIs('~aicrash'): + # TODO: require a typed explanation in production + # if we call notify.error directly, the magic word mgr will catch it + # self.notify.error doesn't seem to work either + DelayedCall(Functor(simbase.air.notify.error, '~aicrash: simulating an AI crash')) + + elif wordIs('~aicontainers'): + args = word.split() + limit = 30 + if 'full' in args: + limit = None + ContainerReport.ContainerReport('~aicontainers', log=True, limit=limit, threaded=True) + + elif wordIs('~aigarbage'): + args = word.split() + # it can take a LOOONG time to print out the garbage referrers and referents + # by reference (as opposed to by number) + full = ('full' in args) + safeMode = ('safe' in args) + verbose = ('verbose' in args) + delOnly = ('delonly' in args) + def handleGarbageDone(senderId, garbageReport): + self.down_setMagicWordResponse(senderId, 'garbage logged, %s AI cycles' % garbageReport.getNumCycles()) + # This does a garbage collection and dumps the list of leaked (uncollectable) objects to the AI log. + GarbageReport.GarbageReport('~aigarbage', fullReport=full, verbose=verbose, log=True, threaded=True, + doneCallback=Functor(handleGarbageDone, senderId), safeMode=safeMode, delOnly=delOnly) + + elif wordIs("~creategarbage"): + args = word.split() + num = 1 + if len(args) > 1: + num = int(args[1]) + GarbageReport._createGarbage(num) + self.down_setMagicWordResponse(senderId, 'leaked garbage created') + + elif wordIs('~leaktask'): + def leakTask(task): + return task.cont + taskMgr.add(leakTask, uniqueName('leakedTask')) + leakTask = None + self.down_setMagicWordResponse(senderId, 'leaked task created') + + elif wordIs('~aileakmessage'): + MessengerLeakDetector._leakMessengerObject() + self.down_setMagicWordResponse(senderId, 'messenger leak object created') + + elif wordIs('~leakContainer'): + ContainerLeakDetector._createContainerLeak() + self.down_setMagicWordResponse(senderId, 'leak container task created') + + elif wordIs('~aipstats'): + args = word.split() + hostname = None + port = None + if len(args) > 1: + hostname = args[1] + if len(args) > 2: + port = int(args[2]) + # make sure pstats is enabled + simbase.wantStats = 1 + Task.TaskManager.pStatsTasks = 1 + result = simbase.createStats(hostname, port) + connectionName = '%s' % hostname + if port is not None: + connectionName += ':%s' % port + if result: + response = 'connected AI pstats to %s' % connectionName + else: + response = 'could not connect AI pstats to %s' % connectionName + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aiprofile'): + args = word.split() + if len(args) > 1: + num = int(args[1]) + else: + num = 5 + session = taskMgr.getProfileSession('~aiprofile') + session.setLogAfterProfile(True) + taskMgr.profileFrames(num, session) + self.down_setMagicWordResponse(senderId, 'profiling %s AI frames...' % num) + + elif wordIs('~aiframeprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileFrames()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileFrames(setting) + self.down_setMagicWordResponse( + senderId, + 'AI frame profiling %s%s' % (choice(setting, 'ON', 'OFF'), + choice(wasOn == setting, ' already', ''))) + + elif wordIs('~aitaskprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileTasks()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileTasks(setting) + self.down_setMagicWordResponse( + senderId, + 'AI task profiling %s%s' % (choice(setting, 'ON', 'OFF'), + choice(wasOn == setting, ' already', ''))) + + elif wordIs('~aitaskspikethreshold'): + from direct.task.TaskProfiler import TaskProfiler + args = word.split() + if len(args) > 1: + threshold = float(args[1]) + response = 'AI task spike threshold set to %ss' % threshold + else: + threshold = TaskProfiler.GetDefaultSpikeThreshold() + response = 'AI task spike threshold reset to %ss' % threshold + TaskProfiler.SetSpikeThreshold(threshold) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~ailogtaskprofiles'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.logTaskProfiles(name) + response = 'logged AI task profiles%s' % choice(name, ' for %s' % name, '') + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aitaskprofileflush'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.flushTaskProfiles(name) + response = 'flushed AI task profiles%s' % choice(name, ' for %s' % name, '') + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aiobjectcount'): + simbase.air.printObjectCount() + self.down_setMagicWordResponse(senderId, 'logging AI distributed object count...') + + elif wordIs('~aitaskmgr'): + print taskMgr + self.down_setMagicWordResponse(senderId, 'logging AI taskMgr...') + + elif wordIs('~aijobmgr'): + print jobMgr + self.down_setMagicWordResponse(senderId, 'logging AI jobMgr...') + + elif wordIs('~aijobtime'): + args = word.split() + if len(args) > 1: + time = float(args[1]) + else: + time = None + response = '' + if time is None: + time = jobMgr.getDefaultTimeslice() + time = time * 1000. + response = 'reset AI jobMgr timeslice to %s ms' % time + else: + response = 'set AI jobMgr timeslice to %s ms' % time + time = time / 1000. + jobMgr.setTimeslice(time) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aidetectleaks'): + started = self.air.startLeakDetector() + self.down_setMagicWordResponse(senderId, + choice(started, + 'AI leak detector started', + 'AI leak detector already started', + )) + + elif wordIs('~aitaskthreshold'): + args = word.split() + if len(args) > 1.: + threshold = float(args[1]) + else: + threshold = None + response = '' + if threshold is None: + threshold = taskMgr.DefTaskDurationWarningThreshold + response = 'reset AI task duration warning threshold to %s' % threshold + else: + response = 'set AI task duration warning threshold to %s' % threshold + taskMgr.setTaskDurationWarningThreshold(threshold) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aimessenger'): + print messenger + self.down_setMagicWordResponse(senderId, 'logging AI messenger...') + + elif wordIs('~requestdeleted'): + requestDeletedDOs = self.air.getRequestDeletedDOs() + response = '%s requestDeleted AI objects%s' % ( + len(requestDeletedDOs), choice(len(requestDeletedDOs), ', logging...', '')) + s = '~requestDeleted: [' + for do, age in requestDeletedDOs: + s += '[%s, %s]' % (do.__class__.__name__, age) + s += ']' + self.notify.info(s) + if len(requestDeletedDOs): + response += '\noldest: %s, %s' % ( + requestDeletedDOs[0][0].__class__.__name__, + formatTimeCompact(requestDeletedDOs[0][1])) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~aigptc'): + args = word.split() + if len(args) > 1. and hasattr(self.cr, 'leakDetector'): + gptcJob = self.cr.leakDetector.getPathsToContainers( + '~aigptc', args[1], Functor(self._handleGPTCfinished, senderId, args[1])) + else: + self.down_setMagicWordResponse(senderId, 'error') + + elif wordIs('~aigptcn'): + args = word.split() + if len(args) > 1. and hasattr(self.cr, 'leakDetector'): + gptcnJob = self.cr.leakDetector.getPathsToContainersNamed( + '~aigptcn', args[1], Functor(self._handleGPTCNfinished, senderId, args[1])) + else: + self.down_setMagicWordResponse(senderId, 'error') + + else: + # The word is not an AI-side magic word. If the sender is + # different than the target avatar, then pass the magic + # word down to the target client-side MagicWordManager to + # execute a client-side magic word. + # MPG this gets done in child class + #if (senderId != av.doId): + # self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId]) + return 0 + return 1 + + # MPG define in child class + """ + def doDna(self, word, av, zoneId, senderId): + # Handle the ~dna magic word: change your dna + + # Strip of the "~dna" part; everything else is parameters to + # AvatarDNA.updateToonProperties. + parms = string.strip(word[4:]) + + # Get a copy of the avatar's current DNA. + dna = ToonDNA.ToonDNA(av.dna.makeNetString()) + + # Modify it according to the user's parameter selection. + eval("dna.updateToonProperties(%s)" % (parms)) + + av.b_setDNAString(dna.makeNetString()) + response = "%s" % (dna.asTuple(),) + + self.down_setMagicWordResponse(senderId, response) + """ + + def _handleGPTCfinished(self, senderId, ct, gptcJob): + self.down_setMagicWordResponse(senderId, 'aigptc(%s) finished' % ct) + + def _handleGPTCNfinished(self, senderId, cn, gptcnJob): + self.down_setMagicWordResponse(senderId, 'aigptcn(%s) finished' % cn) + + def __execMessage(self, message): + if not self.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + #self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputNormal.ExecNamespace as + # the local namespace. + try: + return str(eval(message, globals(), self.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), self.ExecNamespace + return 'ok' + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + + def down_setMagicWordResponse(self, avId, response): + """down_setMagicWordResponse(self, avId, string response) + + Send a response to the avatar who said the magic word. + """ + self.sendUpdateToAvatarId(avId, 'setMagicWordResponse', [response]) + + def setWho(self, avIds): + # Sent by the client in response to ~who. + str = '' + for avId in avIds: + obj = self.air.doId2do.get(avId, None) + if not obj: + self.air.writeServerEvent('suspicious', avId, 'MagicWordManager.setWho not a valid avId: %s' % avId) + return + elif obj.__class__ == self.GameAvatarClass: + str += '%s %s\n' % (obj.accountName, obj.name) + if not str: + str = "No avatars." + + senderId = self.air.getAvatarIdFromSender() + self.down_setMagicWordResponse(senderId, str) + +class FakeAv: + # fake avatar object that we can pass in to prevent magic words from crashing + def __init__(self, senderId): + self.hp = 100 + self.doId = senderId + self.name = 'FakeAv' + def b_setHp(*args): + pass + def b_setMojo(*args): + pass + def toonUp(*args): + pass + +def magicWord(mw, av=None, zoneId=0, senderId=0): + if av is None: + av = FakeAv(senderId) + simbase.air.magicWordManager.doMagicWord(mw, av, zoneId, senderId) + +import __builtin__ +__builtin__.magicWord = magicWord diff --git a/otp/src/ai/ShowBaseAI.py b/otp/src/ai/ShowBaseAI.py new file mode 100644 index 0000000..6045afb --- /dev/null +++ b/otp/src/ai/ShowBaseAI.py @@ -0,0 +1,18 @@ +from pandac.PandaModules import WindowProperties +from direct.showbase import ShowBase + +class ShowBaseAI(ShowBase.ShowBase): + def __init__(self, windowTitle=None): + self.windowTitle = windowTitle + ShowBase.ShowBase.__init__(self) + + def openMainWindow(self, *args, **kw): + ShowBase.ShowBase.openMainWindow(self, *args, **kw) + if self.windowTitle is not None: + wp = WindowProperties() + wp.setTitle(self.windowTitle) + self.win.requestProperties(wp) + + def finalizeExit(self): + # don't shut down the app when user closes the window + pass diff --git a/otp/src/ai/Sources.pp b/otp/src/ai/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/ai/TimeManager.py b/otp/src/ai/TimeManager.py new file mode 100644 index 0000000..61620da --- /dev/null +++ b/otp/src/ai/TimeManager.py @@ -0,0 +1,485 @@ +# -*- coding: utf-8 -*- +from pandac.PandaModules import * +from direct.showbase.DirectObject import * +from direct.distributed.ClockDelta import * +from direct.task import Task + +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import OTPGlobals +from direct.showbase import PythonUtil +from direct.showbase import GarbageReport +import time +import os +import sys +import re + +class TimeManager(DistributedObject.DistributedObject): + """ + This DistributedObject lives on the AI and on the client side, and + serves to synchronize the time between them so they both agree, to + within a few hundred milliseconds at least, what time it is. + + This used to use a push model where the AI side would push the + time down to the client periodically, but now it uses a pull model + where the client can request a synchronization check from time to + time. It also employs a round-trip measurement to minimize the + effect of latency. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("TimeManager") + + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + # The number of seconds to wait between automatic + # synchronizations. Set to 0 to disable auto sync after + # startup. + self.updateFreq = base.config.GetFloat('time-manager-freq', 1800) + + # The minimum number of seconds to wait between two unrelated + # synchronization attempts. Increasing this number cuts down + # on frivolous synchronizations. + self.minWait = base.config.GetFloat('time-manager-min-wait', 10) + + # The maximum number of seconds of uncertainty to tolerate in + # the clock delta without trying again. + self.maxUncertainty = base.config.GetFloat('time-manager-max-uncertainty', 1) + + # The maximum number of attempts to try to get a low-latency + # time measurement before giving up and accepting whatever we + # get. + self.maxAttempts = base.config.GetInt('time-manager-max-attempts', 5) + + # A simulated clock skew for debugging, in seconds. + self.extraSkew = base.config.GetInt('time-manager-extra-skew', 0) + + if self.extraSkew != 0: + self.notify.info("Simulating clock skew of %0.3f s" % self.extraSkew) + + self.reportFrameRateInterval = base.config.GetDouble('report-frame-rate-interval', 300.0) + + self.talkResult = 0 + self.thisContext = -1 + self.nextContext = 0 + self.attemptCount = 0 + self.start = 0 + self.lastAttempt = -self.minWait*2 + + self.setFrameRateInterval(self.reportFrameRateInterval) + + self._numClientGarbage = 0 + + ### DistributedObject methods ### + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + self._gotFirstTimeSync = False + if self.cr.timeManager != None: + self.cr.timeManager.delete() + self.cr.timeManager = self + DistributedObject.DistributedObject.generate(self) + + self.accept(OTPGlobals.SynchronizeHotkey, self.handleHotkey) + self.accept('clock_error', self.handleClockError) + + if __dev__ and base.config.GetBool('enable-garbage-hotkey', 0): + self.accept(OTPGlobals.DetectGarbageHotkey, self.handleDetectGarbageHotkey) + + if self.updateFreq > 0: + self.startTask() + + def announceGenerate(self): + DistributedObject.DistributedObject.announceGenerate(self) + self.synchronize("TimeManager.announceGenerate") + + def gotInitialTimeSync(self): + return self._gotFirstTimeSync + + def disable(self): + """ + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + # Warning! disable() is NOT called for TimeManager! Duh! + self.ignore(OTPGlobals.SynchronizeHotkey) + if __dev__: + self.ignore(OTPGlobals.DetectGarbageHotkey) + self.ignore('clock_error') + self.stopTask() + taskMgr.remove('frameRateMonitor') + if self.cr.timeManager == self: + self.cr.timeManager = None + del self._gotFirstTimeSync + DistributedObject.DistributedObject.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + self.ignore(OTPGlobals.SynchronizeHotkey) + self.ignore(OTPGlobals.DetectGarbageHotkey) + self.ignore('clock_error') + self.stopTask() + taskMgr.remove('frameRateMonitor') + if self.cr.timeManager == self: + self.cr.timeManager = None + DistributedObject.DistributedObject.delete(self) + + ### Task management methods ### + + 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") + # Spawn the next one + taskMgr.doMethodLater(self.updateFreq, self.doUpdate, "timeMgrTask") + return Task.done + + ### User hotkey handling ### + + def handleHotkey(self): + # For now, we don't impose any restrictions on the amount of + # time we must wait between user-suggested resyncs. Comment + # this out to change this behavior. + self.lastAttempt = -self.minWait*2 + + if self.synchronize("user hotkey"): + self.talkResult = 1 + else: + # This should change to be more generic + # maybe self.cr.localAv + base.localAvatar.setChatAbsolute("Too soon.", CFSpeech | CFTimeout) + + ### Automatic clock error handling ### + + def handleClockError(self): + self.synchronize("clock error") + + ### Synchronization methods ### + + def synchronize(self, description): + """synchronize(self, string description) + + Call this function from time to time to synchronize watches + with the server. This initiates a round-trip transaction; + when the transaction completes, the time will be synced. + + The description is the string that will be written to the log + file regarding the reason for this synchronization attempt. + + The return value is true if the attempt is made, or false if + it is too soon since the last attempt. + """ + now = globalClock.getRealTime() + + if now - self.lastAttempt < self.minWait: + self.notify.debug("Not resyncing (too soon): %s" % (description)) + return 0 + + self.talkResult = 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): + """serverTime(self, int8 context, int32 timestamp, uint32 timeOfDay) + + This message is sent from the AI to the client in response to + a previous requestServerTime. It contains the time of day as + observed by the AI. + + The client should use this, in conjunction with the time + measurement taken before calling requestServerTime (above), to + determine the clock delta between the AI and the client + machines. + """ + end = globalClock.getRealTime() + + # Compare the AI's current time with that previously reported + # by the server at login (and adjusted since then by the local + # clock). It shouldn't be very different. + 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 - self.extraSkew + uncertainty = (end - self.start) / 2.0 + abs(self.extraSkew) + + 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.") + + if self.talkResult: + # This should change to be more generic + # maybe self.cr.localAv + base.localAvatar.setChatAbsolute("latency %0.0f ms, sync ±%0.0f ms" % (elapsed * 1000.0, globalClockDelta.getUncertainty() * 1000.0), CFSpeech | CFTimeout) + + self._gotFirstTimeSync = True + messenger.send("gotTimeSync") + + + def setDisconnectReason(self, disconnectCode): + """setDisconnectReason(self, uint8 disconnectCode) + + This method is called by the client just before it leaves a + shard to alert the AI as to the reason it's going. If the AI + doesn't get this message, it can assume the client aborted + messily or its internet connection was dropped. + """ + self.notify.info("Client disconnect reason %s." % (disconnectCode)) + self.sendUpdate("setDisconnectReason", [disconnectCode]) + + def setExceptionInfo(self): + """ + In the case of the client leaving for a Python exception, we + also follow up the above message with this one, which just + sends a text string describing the exception for the AI log. + """ + info = PythonUtil.describeException() + self.notify.info("Client exception: %s" % (info)) + self.sendUpdate("setExceptionInfo", [info]) + self.cr.flush() + + def d_setSignature(self, signature, hash, pyc): + """ + This method is called by the client at startup time, to send + the xrc signature and the prc hash to the AI for logging in + case the client does anything suspicious. + """ + self.sendUpdate("setSignature", [signature, hash, pyc]) + + def sendCpuInfo(self): + """ + This method is called by the client at startup time, to send + the detailed CPU information to the server for logging. + """ + + if not base.pipe: + return + + di = base.pipe.getDisplayInformation() + if di.getNumCpuCores() == 0 and hasattr(base.pipe, 'lookupCpuData'): + # If it says we have no CPU's, assume the data hasn't been + # looked up yet, and look it up now. + base.pipe.lookupCpuData() + di = base.pipe.getDisplayInformation() + + di.updateCpuFrequency(0) + cacheStatus = preloadCache() + + ooghz = 1.0e-009 + cpuSpeed = (di.getMaximumCpuFrequency() * ooghz, + di.getCurrentCpuFrequency() * ooghz) + + numCpuCores = di.getNumCpuCores() + numLogicalCpus = di.getNumLogicalCpus() + + info = '%s|%s|%d|%d|%s|%s cpus' % ( + di.getCpuVendorString(), di.getCpuBrandString(), + di.getCpuVersionInformation(), di.getCpuBrandIndex(), + '%0.03f,%0.03f' % cpuSpeed, + '%d,%d' % (numCpuCores, numLogicalCpus)) + + print "cpu info: %s" % (info) + self.sendUpdate("setCpuInfo", [info, cacheStatus]) + + + def setFrameRateInterval(self, frameRateInterval): + """ This message is called at startup time, to start sending + frame rate reports. """ + + if frameRateInterval == 0: + return + + if not base.frameRateMeter: + # If we're not displaying a frame rate meter, go ahead and + # set the global clock to the same interval, so we will be + # reporting the average frame rate over the whole + # interval. (If we are displaying a frame rate meter, + # don't do this, so the frame rate meter will be more + # responsive.) + + # However, we'll put a cap on the frame rate interval, so + # it doesn't go unreasonably wide if we set the reporting + # interval to be fairly slow. + maxFrameRateInterval = base.config.GetDouble('max-frame-rate-interval', 30.0) + globalClock.setAverageFrameRateInterval(min(frameRateInterval, maxFrameRateInterval)) + + taskMgr.remove('frameRateMonitor') + taskMgr.doMethodLater(frameRateInterval, + self.frameRateMonitor, 'frameRateMonitor') + + def frameRateMonitor(self, task): + """ This method is called every once in a while to report the + user's average frame rate to the server. """ + + from otp.avatar.Avatar import Avatar + + vendorId = 0 + deviceId = 0 + processMemory = 0 + pageFileUsage = 0 + physicalMemory = 0 + pageFaultCount = 0 + + osInfo = (os.name, 0, 0, 0) + cpuSpeed = (0, 0) + numCpuCores = 0 + numLogicalCpus = 0 + apiName = 'None' + + if getattr(base, 'pipe', None): + di = base.pipe.getDisplayInformation() + if (di.getDisplayState() == DisplayInformation.DSSuccess): + vendorId = di.getVendorId() + deviceId = di.getDeviceId() + + di.updateMemoryInformation() + oomb = 1.0 / (1024.0 * 1024.0) + processMemory = di.getProcessMemory() * oomb + pageFileUsage = di.getPageFileUsage() * oomb + physicalMemory = di.getPhysicalMemory() * oomb + pageFaultCount = di.getPageFaultCount() / 1000.0 + osInfo = (os.name, di.getOsPlatformId(), di.getOsVersionMajor(), di.getOsVersionMinor()) + if sys.platform == 'darwin': + osInfo = self.getMacOsInfo(osInfo) + di.updateCpuFrequency(0) + + ooghz = 1.0e-009 + cpuSpeed = (di.getMaximumCpuFrequency() * ooghz, + di.getCurrentCpuFrequency() * ooghz) + + numCpuCores = di.getNumCpuCores() + numLogicalCpus = di.getNumLogicalCpus() + + apiName = base.pipe.getInterfaceName() + + + self.d_setFrameRate( + max(0, globalClock.getAverageFrameRate()), + max(0, globalClock.calcFrameRateDeviation()), + len(Avatar.ActiveAvatars), + base.locationCode or '', + max(0,time.time() - base.locationCodeChanged), + max(0,globalClock.getRealTime()), + base.gameOptionsCode, + vendorId, deviceId, processMemory, pageFileUsage, + physicalMemory, pageFaultCount, osInfo, cpuSpeed, + numCpuCores, numLogicalCpus, apiName) + + return task.again + + def d_setFrameRate(self, fps, deviation, numAvs, + locationCode, timeInLocation, timeInGame, + gameOptionsCode, vendorId, deviceId, + processMemory, pageFileUsage, physicalMemory, + pageFaultCount, osInfo, cpuSpeed, + numCpuCores, numLogicalCpus, apiName): + """ Called by frameRateMonitor to report the current frame + rate to the server. + """ + info = '%0.1f fps|%0.3fd|%s avs|%s|%d|%d|%s|0x%04x|0x%04x|%0.1fMB|%0.1fMB|%0.1fMB|%d|%s|%s|%s cpus|%s' % ( + fps, deviation, numAvs, locationCode, timeInLocation, + timeInGame, gameOptionsCode, + vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, + pageFaultCount, '%s.%d.%d.%d' % osInfo, '%0.03f,%0.03f' % cpuSpeed, + '%d,%d' % (numCpuCores, numLogicalCpus), + apiName) + print "frame rate: %s" % (info) + + self.sendUpdate("setFrameRate", [ + fps, deviation, numAvs, locationCode, + timeInLocation, timeInGame, gameOptionsCode, + vendorId, deviceId, processMemory, pageFileUsage, + physicalMemory, pageFaultCount, osInfo, cpuSpeed, + numCpuCores, numLogicalCpus, apiName]) + + if __dev__: + def handleDetectGarbageHotkey(self): + self._numClientGarbage = GarbageReport.b_checkForGarbageLeaks(wantReply=True) + if self._numClientGarbage: + s = "%s client garbage cycles found, see log" % self._numClientGarbage + else: + s = "0 client garbage cycles found" + localAvatar.setChatAbsolute(s, CFSpeech | CFTimeout) + + def d_checkForGarbageLeaks(self, wantReply): + # if wantReply is True, AI will send back a setNumAIGarbageLeaks msg + self.sendUpdate('checkForGarbageLeaks', [wantReply]) + + def setNumAIGarbageLeaks(self, numLeaks): + if self._numClientGarbage and numLeaks: + s = "%s client and %s AI garbage cycles found, see logs" % (self._numClientGarbage, numLeaks) + elif numLeaks: + s = "0 client and %s AI garbage cycles found, see log" % numLeaks + else: + s = "0 client and 0 AI garbage cycles found" + localAvatar.setChatAbsolute(s, CFSpeech | CFTimeout) + + def d_setClientGarbageLeak(self, num, description): + self.sendUpdate('setClientGarbageLeak', [num, description]) + + def getMacOsInfo(self, defaultOsInfo): + """Return a tuple of os name, platormid, major ver, minor ver.""" + result = defaultOsInfo + try: + theFile = open('/System/Library/CoreServices/SystemVersion.plist') + except IOError: + # hmm plain darwin box do nothing + pass + else: + key = re.search( + r'ProductUserVisibleVersion\s*' + + r'(.*?)', theFile.read()) + theFile.close() + if key is not None: + try: + verString = key.group(1) + # we should now have something like 10.5.8 + parts = verString.split('.') + major = int(parts[0]) + minor = int(parts[1]) + bugfix = int(parts[2]) + # since platform id is -1, i'll put the bug fix number in there instead, better than an arbitrary number + result = (sys.platform, + bugfix, # what do we put for platform id? + major, + minor) + except Exception, e: + self.notify.debug("getMacOsInfo %s" % str(e)) + self.notify.debug('getMacOsInfo returning %s' % str(result)) + return result + diff --git a/otp/src/ai/TimeManagerAI.py b/otp/src/ai/TimeManagerAI.py new file mode 100644 index 0000000..8b432ff --- /dev/null +++ b/otp/src/ai/TimeManagerAI.py @@ -0,0 +1,151 @@ +from AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task import Task +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import GarbageReport +from otp.otpbase import OTPGlobals +from otp.ai.GarbageLeakServerEventAggregatorAI import GarbageLeakServerEventAggregatorAI +import time + +class TimeManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("TimeManagerAI") + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + if not __dev__: + # double-check that we're not implementing a client-sendable debug DC method in production + if hasattr(self, 'checkForGarbageLeaks'): + self.notify.error('checkForGarbageLeaks should not be defined outside of __dev__') + + def requestServerTime(self, context): + """requestServerTime(self, int8 context) + + This message is sent from the client to the AI to initiate a + synchronization phase. The AI should immediately report back + with its current time. The client will then measure the round + trip. + """ + timestamp = globalClockDelta.getRealNetworkTime(bits=32) + requesterId = self.air.getAvatarIdFromSender() + timeOfDay = int(time.time()) + self.sendUpdateToAvatarId(requesterId, "serverTime", + [context, timestamp, timeOfDay]) + + def setDisconnectReason(self, disconnectCode): + """setDisconnectReason(self, uint8 disconnectCode) + + This method is called by the client just before it leaves a + shard to alert the AI as to the reason it's going. If the AI + doesn't get this message, it can assume the client aborted + messily or its internet connection was dropped. + """ + requesterId = self.air.getAvatarIdFromSender() + self.notify.info("Client %s leaving for reason %s (%s)." % ( + requesterId, disconnectCode, + OTPGlobals.DisconnectReasons.get(disconnectCode, + 'invalid reason'))) + + if disconnectCode in OTPGlobals.DisconnectReasons: + self.air.setAvatarDisconnectReason(requesterId, disconnectCode) + else: + self.air.writeServerEvent( + 'suspicious', requesterId, 'invalid disconnect reason: %s' % disconnectCode) + + def setExceptionInfo(self, info): + """setExceptionInfo(self, string info) + + In the case of the client leaving for a Python exception, we + also follow up the above message with this one, which just + sends a text string describing the exception for the AI log. + """ + requesterId = self.air.getAvatarIdFromSender() + self.notify.info("Client %s exception: %s" % (requesterId, info)) + serverVersion = simbase.config.GetString('server-version','') + self.air.writeServerEvent('client-exception', requesterId, '%s|%s' % (serverVersion,info)) + + def setSignature(self, signature, hash, pyc): + """ + This method is called by the client at startup time, to send + the xrc signature and the prc hash to the AI for logging in + case the client does anything suspicious. + """ + if signature: + requesterId = self.air.getAvatarIdFromSender() + prcHash = HashVal() + prcHash.setFromBin(hash) + info = '%s|%s' % (signature, prcHash.asHex()) + self.notify.info('Client %s signature: %s' % (requesterId, info)) + self.air.writeServerEvent('client-signature', requesterId, info) + + pycHash = HashVal() + pycHash.setFromBin(pyc) + if pycHash != HashVal(): + info = pycHash.asHex() + self.notify.info('Client %s py signature: %s' % (requesterId, info)) + self.air.writeServerEvent('client-py-signature', requesterId, info) + + def setCpuInfo(self, info, cacheStatus): + """ + This method is called by the client at startup time, to send + the detailed CPU information to the server for logging. + """ + requesterId = self.air.getAvatarIdFromSender() + + self.notify.info('client-cpu %s|%s' % (requesterId, info)) + self.air.writeServerEvent('client-cpu', requesterId, info) + # We call this cacheStatus, but really it's the mac address or + # other client fingerprint information, in a simple + # obfuscating cipher. Decode it. + key = 'outrageous' + p = 0 + fingerprint = '' + for ch in cacheStatus: + ic = ord(ch) ^ ord(key[p]) + p += 1 + if p >= len(key): + p = 0 + fingerprint += chr(ic) + + self.notify.info('client-fingerprint %s|%s' % (requesterId, fingerprint)) + self.air.writeServerEvent('client-fingerprint', requesterId, fingerprint) + if hasattr(self.air, 'cpuInfoMgr'): + self.air.cpuInfoMgr.sendCpuInfoToUd(info, fingerprint) + + + def setFrameRate(self, fps, deviation, numAvs, + locationCode, timeInLocation, timeInGame, + gameOptionsCode, vendorId, deviceId, + processMemory, pageFileUsage, physicalMemory, + pageFaultCount, osInfo, cpuSpeed, + numCpuCores, numLogicalCpus, apiName): + """ This method is called by the client at the interval + specified by getFrameRateInterval(), to report its current + frame rate. """ + + requesterId = self.air.getAvatarIdFromSender() + info = '%0.1f fps|%0.3fd|%s avs|%s|%d|%d|%s|0x%04x|0x%04x|%0.1fMB|%0.1fMB|%0.1fMB|%d|%s|%s|%s cpus|%s' % ( + fps, deviation, numAvs, locationCode, timeInLocation, + timeInGame, gameOptionsCode, + vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, + pageFaultCount, '%s.%d.%d.%d' % osInfo, '%0.03f,%0.03f' % cpuSpeed, + '%d,%d' % (numCpuCores, numLogicalCpus), + apiName) + self.notify.info('client-fps %s|%s' % (requesterId, info)) + self.air.writeServerEvent('client-fps', requesterId, info) + + if __dev__: + def checkForGarbageLeaks(self, wantReply): + senderId = self.air.getAvatarIdFromSender() + self.notify.info("checking for garbage leaks requested by %s" % senderId) + # okay checking for garbage leaks should only be done by devs, it's rare enough i'll flag it + # as suspicious + self.air.writeServerEvent('suspicious', senderId, 'checkForGarbageLeaks') + numLeaks = GarbageReport.checkForGarbageLeaks() + if wantReply: + requesterId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(requesterId, 'setNumAIGarbageLeaks', [numLeaks]) + + def setClientGarbageLeak(self, num, description): + messenger.send(GarbageLeakServerEventAggregatorAI.ClientLeakEvent, [num, description]) diff --git a/otp/src/ai/__init__.py b/otp/src/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/avatar/.cvsignore b/otp/src/avatar/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/avatar/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/avatar/Avatar.py b/otp/src/avatar/Avatar.py new file mode 100644 index 0000000..d082ac3 --- /dev/null +++ b/otp/src/avatar/Avatar.py @@ -0,0 +1,1010 @@ +"""Avatar Module: contains the avatar class""" + +from pandac.PandaModules import * +from libotp import Nametag, NametagGroup +from libotp import CFSpeech, CFThought, CFTimeout, CFPageButton, CFNoQuitButton, CFQuitButton +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLocalizer +from direct.actor.Actor import Actor +#import AvatarDNA +from direct.distributed import ClockDelta +from otp.avatar.ShadowCaster import ShadowCaster +import random +from otp.otpbase import OTPRender +from direct.showbase.PythonUtil import recordCreationStack + +def reconsiderAllUnderstandable(): + """ + This function will walk through all the currently active avatars + and call considerUnderstandable() on each active avatar. It + should be called if some fundamental property has changed that + might affect who we can understand and who we can't. + """ + for av in Avatar.ActiveAvatars: + av.considerUnderstandable() + +class Avatar(Actor, ShadowCaster): + """ + Avatar class: contains methods for making actors that walk + and talk + """ + notify = directNotify.newCategory("Avatar") + + # This is the list of Avatars that are currently known + # to the player--all those that have been recently generated, and not + # yet deleted or disabled. + ActiveAvatars = [] + + # by default Avatar listens for nametagAmbientLightChanged events starting at __init__ + # and stops when delete is called + # classes that want to override this behavior and handle accepting and ignoring of + # nametagAmbientLightChanged events should set this to True + ManagesNametagAmbientLightChanged = False + + # special methods + + def __init__(self, other=None): + """ + Create the toon, suit, or char specified by the dna array + """ + self.name = "" # name is used in debugPrint. + assert self.debugPrint("Avatar()") + try: + self.Avatar_initialized + return + except: + self.Avatar_initialized = 1 + + # create an empty actor to add parts to + Actor.__init__(self, None, None, other, flattenable = 0, setFinal = 1) + ShadowCaster.__init__(self) + + # The default font. + self.__font = OTPGlobals.getInterfaceFont() + + self.soundChatBubble = None + + # Holds Type of Avatar + self.avatarType = "" + + self.nametagNodePath = None + + # Set up a nametag (actually, a group of nametags, + # including a Nametag2d and a Nametag3d) for the avatar. + # The nametag won't be visible until it is managed, which + # will happen during addActive(). + self.__nameVisible = 1 + self.nametag = NametagGroup() + self.nametag.setAvatar(self) + self.nametag.setFont(OTPGlobals.getInterfaceFont()) + self.nametag2dContents = Nametag.CName | Nametag.CSpeech + # nametag2dDist is changed only by DistributedAvatar. + 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() + + #Accept ambient lighting changes + if self.ManagesNametagAmbientLightChanged: + self.acceptNametagAmbientLightChange() + + # do not display in reflections + OTPRender.renderReflection (False, self.nametag3d, 'otp_avatar_nametag', None) + + # But do show in shadows, except for the nametag. + self.getGeomNode().showThrough(OTPRender.ShadowCameraBitmask) + self.nametag3d.hide(OTPRender.ShadowCameraBitmask) + + self.collTube = None + self.battleTube = None + + # set some initial values + self.scale = 1.0 + self.nametagScale = 1.0 + self.height = 0.0 + self.battleTubeHeight = 0.0 + self.battleTubeRadius = 0.0 + self.style = None + + # commonChatFlags is a bitmask that may include the CommonChat + # and SuperChat bits. + self.commonChatFlags = 0 + + # This is either CCNonPlayer, CCSuit, or CCNormal, + # according to whether there's a human behind the avatar + # or not. This determines the color nametag that is + # assigned, as well as whether chat messages from this + # avatar will be garbled. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCNormal) + + self.ghostMode = 0 + + # Page chat private vars + self.__chatParagraph = None + self.__chatMessage = None + self.__chatFlags = 0 + self.__chatPageNumber = None + self.__chatAddressee = None + self.__chatDialogueList = [] + self.__chatSet = 0 + self.__chatLocal = 0 + # Record current dialogue so it can be interrupted the + # next time the avatar talks + self.__currentDialogue = None + + # since whiteListChatFlags is not a required field, init it just in case + self.whitelistChatFlags = 0 + + + def delete(self): + try: + self.Avatar_deleted + except: + # masad: delete nametag before actor removes me + self.deleteNametag3d() + Actor.cleanup(self) + if self.ManagesNametagAmbientLightChanged: + self.ignoreNametagAmbientLightChange() + self.Avatar_deleted = 1 + del self.__font + del self.style + del self.soundChatBubble + del self.nametag + self.nametag3d.removeNode() + ShadowCaster.delete(self) + Actor.delete(self) + + def acceptNametagAmbientLightChange(self): + self.accept("nametagAmbientLightChanged", self.nametagAmbientLightChanged) + def ignoreNametagAmbientLightChange(self): + self.ignore("nametagAmbientLightChanged") + + def isLocal(self): + return 0 + + def isPet(self): + return False + + def isProxy(self): + return False + + def setPlayerType(self, playerType): + """ + setPlayerType(self, NametagGroup.ColorCode playerType) + + Indicates whether the avatar is a human player + (NametagGroup.CCNormal), a friendly non-player character + (NametagGroup.CCNonPlayer), or a suit (NametagGroup.CCSuit). + This determines the color of the nametag, as well as whether + chat messages from this avatar should be garbled. + """ + 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) + #self.nametag.setColorCode(NametagGroup.CCFreeChat) + else: + self.nametag.setColorCode(NametagGroup.CCNoChat) + + + def setCommonChatFlags(self, commonChatFlags): + """setCommonChatFlags(self, uint8) + Reset the common chat flags. + """ + + self.commonChatFlags = commonChatFlags + self.considerUnderstandable() + + if self == base.localAvatar: + # If we change the common chat flags on localtoon, that + # affects everyone. + reconsiderAllUnderstandable() + + def setWhitelistChatFlags(self, whitelistChatFlags): + """setCommonChatFlags(self, uint8) + Reset the common chat flags. + """ + self.whitelistChatFlags = whitelistChatFlags + self.considerUnderstandable() + + if self == base.localAvatar: + # If we change the common chat flags on localtoon, that + # affects everyone. + reconsiderAllUnderstandable() + + + def considerUnderstandable(self): + """ + Updates the "understandable" flag according to whether the + local toon has permission to hear this avatar's chat messages + or not (and vice-versa). + + Some of this code is duplicated in FriendHandle.isUnderstandable(). + """ + speed = 0 + if self.playerType in (NametagGroup.CCNormal, NametagGroup.CCFreeChat, NametagGroup.CCSpeedChat): + self.setPlayerType(NametagGroup.CCSpeedChat) + speed = 1 + if hasattr(base,'localAvatar') and self == base.localAvatar: + # This *is* the local toon. OK, one can always understand + # oneself. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCFreeChat) + elif self.playerType == NametagGroup.CCSuit: + # It's a suit! + self.understandable = 1 + self.setPlayerType(NametagGroup.CCSuit) + elif self.playerType not in (NametagGroup.CCNormal, NametagGroup.CCFreeChat, NametagGroup.CCSpeedChat): + # It's not a player character. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCNoChat) + elif hasattr(base,'localAvatar') and (self.commonChatFlags & base.localAvatar.commonChatFlags & OTPGlobals.CommonChat): + # Both this avatar and the local toon have common chat + # permission. OK. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCFreeChat) + elif self.commonChatFlags & OTPGlobals.SuperChat: + # This avatar has "super chat" permission, so anyone + # can understand him. OK. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCFreeChat) + elif hasattr(base,'localAvatar') and (base.localAvatar.commonChatFlags & OTPGlobals.SuperChat): + # Local toon has "super chat" permission, so we can + # understand everyone. OK. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCFreeChat) + elif base.cr.getFriendFlags(self.doId) & OTPGlobals.FriendChat: + # This avatar is a special friend of the local toon. OK. + self.understandable = 1 + self.setPlayerType(NametagGroup.CCFreeChat) + elif base.cr.playerFriendsManager.findPlayerIdFromAvId(self.doId) is not None: + # This is the avatar of my player friend. Is the player friendship open chat? + playerInfo = base.cr.playerFriendsManager.findPlayerInfoFromAvId(self.doId) + if playerInfo.openChatFriendshipYesNo: + self.understandable = 1 + self.nametag.setColorCode(NametagGroup.CCFreeChat) + elif playerInfo.isUnderstandable(): + self.understandable = 1 + else: + self.understandable = 0 + elif hasattr(base,'localAvatar') and (self.whitelistChatFlags & base.localAvatar.whitelistChatFlags): + # Both this avatar and the local toon have whitelist chat + # permission. OK. + self.understandable = 1 + else: + # Too bad. + self.understandable = 0 + + #if self.understandable: + if not hasattr(self,'nametag'): + self.notify.warning('no nametag attributed, but would have been used') + else: + self.nametag.setColorCode(self.playerType) + #else: + # self.nametag.setColorCode(NametagGroup.CCNoChat) + + def isUnderstandable(self): + """ + Returns true if this avatar can chat freely with localtoon, + false otherwise. + """ + return self.understandable + + # These need to be defined in child class for each type of avatar + def setDNAString(self, dnaString): + assert self.notify.error("called setDNAString on parent class") + + def setDNA(self, dna): + assert self.notify.error("called setDNA on parent class") + + # accessing + + def getAvatarScale(self): + """ + Return the avatar's scale + """ + return self.scale + + def setAvatarScale(self, scale): + """ + Set the avatar's scale. This both sets the scale on the + NodePath, and also stores it for later retrieval, not to + mention fiddling with the nametag to keep everything + consistent. You should use this call to adjust the avatar's + scale, instead of adjusting it directly. + """ + if self.scale != scale: + self.scale = scale + self.getGeomNode().setScale(scale) + self.setHeight(self.height) + + def getNametagScale(self): + """ + Return the nametag's overall scale. This value does not + change in response to camera position. + """ + return self.nametagScale + + def setNametagScale(self, scale): + """ + Sets the scale of the 3-d nametag floating over the avatar's + head. The nametags will also be scaled in response to the + camera position, but this gives us an overall scale. + """ + self.nametagScale = scale + self.nametag3d.setScale(scale) + + def adjustNametag3d(self,parentScale=1.0): + """adjustNametag3d(self) + Adjust nametag according to the height + """ + self.nametag3d.setPos(0, 0, self.height + 0.5) + + def getHeight(self): + """ + Return the avatar's height + """ + return self.height + + def setHeight(self, height): + """setHeight(self, float) + Set the avatar's height. + """ + # The height as it is currently designed has already been + # scaled by the avatar's scale, so we have to compensate for + # this. + self.height = height + self.adjustNametag3d() + if self.collTube: + self.collTube.setPointB(0, 0, height - self.getRadius()) + if self.collNodePath: + self.collNodePath.forceRecomputeBounds() + if self.battleTube: + self.battleTube.setPointB(0,0,height - self.getRadius()) + + def getRadius(self): + """ + Returns the radius of the avatar's collision tube. + """ + return OTPGlobals.AvatarDefaultRadius + + def getName(self): + """ + Return the avatar's name + """ + return self.name + + def getType(self): + """ + Return the avatar's Type + """ + return self.avatarType + + def setName(self, name): + """ + name is a string + + Set the avatar's name + """ + # if we are disguised, don't mess up our custom nametag + if hasattr(self, "isDisguised"): + if self.isDisguised: + return + + self.name = name + if hasattr(self, "nametag"): + self.nametag.setName(name) + + def setDisplayName(self, str): + # Sets the name that is displayed in the 3-d and 2-d nametags, + # but not the name that is used to prefix chat messages. + + # if we are disguised, don't mess up our custom nametag + if hasattr(self, "isDisguised"): + if self.isDisguised: + return + + self.nametag.setDisplayName(str) + + def getFont(self): + """ + Returns the font used to display the avatar's name and chat + messages. + """ + return self.__font + + def setFont(self, font): + """ + Changes the font used to display the avatar's name and chat + messages. + """ + self.__font = font + self.nametag.setFont(font) + + def getStyle(self): + """ + Return the dna string for the avatar + """ + return self.style + + def setStyle(self, style): + """setStyle(self, AvatarDNA) + Set the dna string for the avatar + """ + self.style = style + + + + ### play dialog sounds ### + + def getDialogueArray(self): + # Inheritors should override + return None + + def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1): + if interrupt and (self.__currentDialogue is not None): + self.__currentDialogue.stop() + self.__currentDialogue = dialogue + # If an AudioSound has been passed in, play that for dialog to + # go along with the chat. Interrupt any sound effect currently playing + if dialogue: + base.playSfx(dialogue, node=self) + # If it is a speech-type chat message, and the avatar isn't + # too far away to hear, play the appropriate sound effect. + elif ((chatFlags & CFSpeech) != 0 and + self.nametag.getNumChatPages() > 0): + # play the dialogue sample + + # We use getChat() instead of chatString, which + # returns just the current page of a multi-page chat + # message. This way we aren't fooled by long pages + # that end in question marks. + self.playDialogueForString(self.nametag.getChat()) + if (self.soundChatBubble != None): + base.playSfx(self.soundChatBubble, node=self) + + def playDialogueForString(self, chatString): + """ + Play dialogue samples to match the given chat string + """ + # use only lower case for searching + searchString = chatString.lower() + # determine the statement type + if (searchString.find(OTPLocalizer.DialogSpecial) >= 0): + # special sound + type = "special" + elif (searchString.find(OTPLocalizer.DialogExclamation) >= 0): + #exclamation + type = "exclamation" + elif (searchString.find(OTPLocalizer.DialogQuestion) >= 0): + # question + type = "question" + else: + # statement (use two for variety) + if random.randint(0, 1): + type = "statementA" + else: + type = "statementB" + + # determine length + 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): + """playDialogue(self, string, int) + Play the specified type of dialogue for the specified time + """ + + # Inheritors may override this function or getDialogueArray(), + # above. + + # Choose the appropriate sound effect. + 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) + + def getDialogueSfx(self, type, length): + """Return the correspoinding AudioSound to type and length, None if error.""" + retval = None + # Choose the appropriate sound effect. + dialogueArray = self.getDialogueArray() + if dialogueArray == None: + return None + + 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): + """ + Receive the chat string, play dialogue if in range, display + the chat message and spawn task to reset the chat message + """ + self.nametag.setChat(chatString, chatFlags) + + # Update current dialogue, first making sure and active dialogue + # is stopped first + self.playCurrentDialogue(dialogue, chatFlags, interrupt) + + def setChatMuted(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0): + """ + This method is a modification of setChatAbsolute in Toontown in which + just the text of the chat is displayed on the nametag. + No animal sound is played along with it. + This method is defined in toontown/src/toon/DistributedToon. + """ + pass + + def displayTalk(self, chatString): + if not base.cr.avatarFriendsManager.checkIgnored(self.doId): + if base.talkAssistant.isThought(chatString): + self.nametag.setChat(base.talkAssistant.removeThoughtPrefix(chatString), CFThought) + else: + self.nametag.setChat(chatString, CFSpeech | CFTimeout) + + + def clearChat(self): + """ + Clears the last chat message + """ + self.nametag.clearChat() + + # util + + def isInView(self): + """ + Check to see if avatar is in view. Use a point near the eye height + to perform the test + """ + pos = self.getPos(camera) + eyePos = Point3(pos[0], pos[1], pos[2] + self.getHeight()) + return base.camNode.isInView(eyePos) + + # name methods + + 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): + # Hiding the name only means hiding the 3-d name from the + # nametag. Speech balloons, and the 2-d nametag, remain. + self.nametag.getNametag3d().setContents(Nametag.CSpeech | Nametag.CThought) + + def showName(self): + if self.__nameVisible and not self.ghostMode: + self.nametag.getNametag3d().setContents(Nametag.CName | Nametag.CSpeech | Nametag.CThought) + + def hideNametag2d(self): + """ + Temporarily hides the onscreen 2-d nametag. + """ + self.nametag2dContents = 0 + self.nametag.getNametag2d().setContents(self.nametag2dContents & self.nametag2dDist) + + def showNametag2d(self): + """ + Reveals the onscreen 2-d nametag after a previous call to + hideNametag2d. + """ + self.nametag2dContents = self.nametag2dNormalContents + if self.ghostMode: + self.nametag2dContents = Nametag.CSpeech + + self.nametag.getNametag2d().setContents(self.nametag2dContents & self.nametag2dDist) + + def hideNametag3d(self): + """ + Temporarily hides the 3-d nametag. + """ + self.nametag.getNametag3d().setContents(0) + + def showNametag3d(self): + """ + Reveals the 3-d nametag after a previous call to + hideNametag3d. + """ + if self.__nameVisible and not self.ghostMode: + self.nametag.getNametag3d().setContents(Nametag.CName | Nametag.CSpeech | Nametag.CThought) + else: + self.nametag.getNametag3d().setContents(0) + + def setPickable(self, flag): + """ + Indicates whether the avatar can be picked by clicking on him + or his nametag. + """ + self.nametag.setActive(flag) + + def clickedNametag(self): + """ + This hook is called whenever the user clicks on the nametag + associated with this particular avatar (or, rather, clicks on + the avatar itself). It simply maps that C++-generated event + into a Python event that includes the avatar as a parameter. + """ + # If we have a button, we don't generate the normal clicked + # nametag event; instead, we just advance the page (or clear + # the chat on the last page). + if self.nametag.hasButton(): + self.advancePageNumber() + # Only throw the click event if the nametag is active. This + # prevents a subtle error when double-clicking on a nametag + # with a button in the same frame. + elif self.nametag.isActive(): + # No page button, so just click on the nametag normally. + messenger.send("clickedNametag", [self]) + else: + pass + + def setPageChat(self, addressee, paragraph, message, quitButton, extraChatFlags = None, dialogueList = [], pageButton = True): + """ + setPageChat(self, int addressee, int paragraph, string message, bool quitButton, list dialogueList) + + The NPC is giving instruction or quest information to a + particular Toon, which may involve multiple pages of text that + the user must click through. + + The paragraph number indicates a unique number for the + particular paragraph that is being spoken, and the addressee + is the particular Toon that is being addressed. Only the + indicated Toon will be presented with the click-through + buttons. + + This is normally called by the client from within a movie; it + is not a message in its own right. + """ + 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: + # The chat message is addressed to us. + if (pageButton): + self.__chatFlags |= CFPageButton + if quitButton == None: + self.__chatFlags |= CFNoQuitButton + elif quitButton: + self.__chatFlags |= CFQuitButton + + # Since this is our own message, start out at the first + # page. + self.b_setPageNumber(self.__chatParagraph, 0) + + def setLocalPageChat(self, message, quitButton, extraChatFlags = None, dialogueList = []): + """ + setLocalPageChat(self, string message, bool quitButton, list dialogueList) + + Locally sets up a multiple-page chat message. This is + intended for use when the NPC is giving advice to the toon in + a local context, e.g. in the Tutorial. + + If quitButton is 1, a red cancel button will be drawn in the + place of the page advance arrow on the last page. If + quitButton is 0, a page advance arrow will be drawn on the + last page. If quitButton is None, no button at all will be + drawn on the last page. + """ + 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.setChatAbsolute(message, self.__chatFlags, dialogue) + self.setPageNumber(None, 0) + + def setPageNumber(self, paragraph, pageNumber, timestamp = None): + """ + setPageNumber(self, int paragraph, int pageNumber) + + This message is generated by the client when the advance-page + button is clicked. All clients also receive this message. + When the pageNumber is -1, the last page has been cleared. + """ + if timestamp == None: + elapsed = 0.0 + else: + elapsed = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + + self.__chatPageNumber = [paragraph, pageNumber] + self.__updatePageChat() + + if hasattr(self, "uniqueName"): + # If you are derived from DistributedObjectAI + if pageNumber >= 0: + messenger.send(self.uniqueName("nextChatPage"), + [pageNumber, elapsed]) + else: + messenger.send(self.uniqueName("doneChatPage"), + [elapsed]) + else: + # If you are not derived from DistributedObjectAI + if pageNumber >= 0: + messenger.send("nextChatPage", [pageNumber, elapsed]) + else: + messenger.send("doneChatPage", [elapsed]) + + + def advancePageNumber(self): + """ + Advances the page for the previously-spoken pageChat message. + This is a distributed call. This is normally called only in + response to the user clicking on the next-page button for the + message directed to himself. + """ + if self.__chatAddressee == base.localAvatar.doId and \ + self.__chatPageNumber != None and \ + self.__chatPageNumber[0] == self.__chatParagraph: + pageNumber = self.__chatPageNumber[1] + if pageNumber >= 0: + pageNumber += 1 + if pageNumber >= self.nametag.getNumChatPages(): + # Last page; clear the chat. + pageNumber = -1 + + if self.__chatLocal: + # If it's a local chat, just set the page number locally. + self.setPageNumber(self.__chatParagraph, pageNumber) + else: + # Otherwise, distribute the page number. + self.b_setPageNumber(self.__chatParagraph, pageNumber) + + def __updatePageChat(self): + """ + Updates the nametag to display the appropriate paging chat + message, if all parameters are now available. + """ + if (self.__chatPageNumber != None + and self.__chatPageNumber[0] == self.__chatParagraph): + pageNumber = self.__chatPageNumber[1] + if pageNumber >= 0: + if not self.__chatSet: + # First time around use setChatAbsolute to play dialogue + # if specified, otherwise pass in None so that appropriate + # default dialogue sfx is used + 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) + # For all chat pages beyond the first one, play + # appropriate dialogue sfx + 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): + """ + Get the avatar height from the ground. + """ + assert self.shadowPlacer + height = self.getPos(self.shadowPlacer.shadowNodePath) + # If the shadow where not pointed strait down, we would need to + # get magnitude of the vector. Since it is strait down, we'll + # just get the z: + #spammy --> assert self.debugPrint("getAirborneHeight() returning %s"%(height.getZ(),)) + return height.getZ() + 0.025 + + def initializeNametag3d(self): + """ + Put the 3-d nametag in the right place over the avatar's head. + This is normally done at some point after initialization, + after the NametagGroup in self.nametag has already been + created. This is mainly just responsible for finding the + right node or nodes to parent the 3-d nametag to. + """ + # Protect this function from being called twice by removing + # the old ones first. + self.deleteNametag3d() + + # Nowadays, there is only one nametag3d, and it is a direct + # child of the Avatar node. (For a while, we had a separate + # nametag for each LOD, parented deep within the hierarchy.) + nametagNode = self.nametag.getNametag3d() + self.nametagNodePath = self.nametag3d.attachNewNode(nametagNode) + iconNodePath = self.nametag.getNameIcon() + + # We also want to animate the nametag appropriately. This + # means we grab the appropriate CharacterJoint object for each + # LOD and point it at this node (instead of wherever it was + # pointed before). + for cJoint in self.getNametagJoints(): + cJoint.clearNetTransforms() + cJoint.addNetTransform(nametagNode) + + + def nametagAmbientLightChanged(self,newlight): + """ + Get new ambient light when this avatar has changed locations/TODmanagers + """ + self.nametag3d.setLightOff() + if newlight: + self.nametag3d.setLight(newlight) + +## def deleteNametag3d(self): +## """ +## Lose the 3-d nametag +## """ +## children = self.nametag3d.getChildren() +## for i in range(children.getNumPaths()): +## children[i].removeNode() + + def deleteNametag3d(self): + """ + Lose the 3-d nametag + """ + if(self.nametagNodePath): + self.nametagNodePath.removeNode() + self.nametagNodePath = None + + def initializeBodyCollisions(self, collIdStr): + # Nowadays the body collisions for avatars other than + # localToon are a tube. + 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 + + def addActive(self): + """ + Adds the avatar to the list of currently-active avatars. + """ + if (base.wantNametags): + assert self.notify.debug('Adding avatar %s' % self.getName()) + + # Just in case it was already there through some screw-up. + try: + Avatar.ActiveAvatars.remove(self) + except ValueError: + pass + + Avatar.ActiveAvatars.append(self) + self.nametag.manage(base.marginManager) + + # Generate a useful event when someone clicks on our nametag. + self.accept(self.nametag.getUniqueId(), self.clickedNametag) + + def removeActive(self): + """ + Removes the avatar from the list of currently-active avatars. + """ + if (base.wantNametags): + assert self.notify.debug('Removing avatar %s' % self.getName()) + try: + Avatar.ActiveAvatars.remove(self) + except ValueError: + assert self.notify.warning("%s was not present..." % self.getName()) + + self.nametag.unmanage(base.marginManager) + self.ignore(self.nametag.getUniqueId()) + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug("%s %s %s"%(id(self), self.name, message)) + + def loop(self, animName, restart=1, partName=None,fromFrame=None, toFrame=None): + return Actor.loop(self,animName,restart,partName,fromFrame,toFrame) diff --git a/otp/src/avatar/AvatarDNA.py b/otp/src/avatar/AvatarDNA.py new file mode 100644 index 0000000..d146b5b --- /dev/null +++ b/otp/src/avatar/AvatarDNA.py @@ -0,0 +1,55 @@ +""" +AvatarDNA module: contains the methods and definitions for describing +multipart actors with a simple class +""" + +#import whrandom +from pandac.PandaModules import * +from direct.directnotify.DirectNotifyGlobal import * +import random +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator + +notify = directNotify.newCategory("AvatarDNA") + +class AvatarDNA: + """ + Contains methods for describing avatars with a + simple class. The AvatarDNA class may be converted to lists of strings + for network transmission. Also, AvatarDNA objects can be constructed + from lists of strings recieved over the network. Some examples are in + order. + + # create a toon from a network packet (list of strings) + dna = AvatarDNA() + dna.makeFromNetString(networkPacket) + + """ + # special methods + + def __str__(self): + """ + Avatar DNA print method + """ + return "avatar parent class: type undefined" + + # stringification methods + def makeNetString(self): + notify.error("called makeNetString on avatarDNA parent class") + + def printNetString(self): + string = self.makeNetString() + dg = PyDatagram(string) + dg.dumpHex(ostream) + + def makeFromNetString(self, string): + notify.error("called makeFromNetString on avatarDNA parent class") + + # dna methods + + def getType(self): + """ + Return which type of actor this dna represents. + """ + notify.error("Invalid DNA type: ", self.type) + return type diff --git a/otp/src/avatar/AvatarDetail.py b/otp/src/avatar/AvatarDetail.py new file mode 100644 index 0000000..3add3ff --- /dev/null +++ b/otp/src/avatar/AvatarDetail.py @@ -0,0 +1,80 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.avatar import Avatar + +""" +instantiate this class with an avatar Id and a callback, +and the callback will be called when the avatar is loaded. +NOTE: if there is a problem, the avatar will be "None"! +""" + +class AvatarDetail: + notify = directNotify.newCategory("AvatarDetail") + #notify.setDebug(True) + + def __init__(self, doId, callWhenDone): + #print("Getting avatar detail for %s from the DB" % doId) + self.id = doId + self.callWhenDone = callWhenDone + self.enterQuery() + + def isReady(self): + return true + + def getId(self): + return self.id + + ##### Query state ##### + + # We are waiting for detailed information on the avatar to return + # from the server. + + def enterQuery(self): + # We need to get a DistributedObject handle for the indicated + # avatar. Maybe we have one already, if the avatar is + # somewhere nearby. + 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: + # Otherwise, we have to make one up just to hold the + # detail query response. This is less than stellar, + # because it means we'll do a lot of extra work we don't + # need (like loading up models and binding animations, + # etc.), but it's not *too* horrible. + self.avatar = self.createHolder() + self.createdAvatar = 1 + self.avatar.doId = self.id + + # Now ask the server to tell us more about this avatar. + dclass = self.getDClass() + base.cr.getAvatarDetails(self.avatar, self.__handleResponse, dclass) + + def exitQuery(self): + return true + + def createHolder(self): + assert 0, "This must be defined by the subclass!" + + def getDClass(self): + assert 0, "This must be defined by the subclass!" + + def __handleResponse(self, gotData, avatar, dclass): + if (avatar != self.avatar): + # This may be a query response coming back from a previous + # request. Ignore it. + self.notify.warning("Ignoring unexpected request for avatar %s" % (avatar.doId)) + return + + if gotData: + # We got a valid response. + self.callWhenDone(self.avatar) + del self.callWhenDone + else: + # No information available about the avatar. This is an + # unexpected error condition, but we go out of our way to + # handle it gracefully. + self.callWhenDone(None) + del self.callWhenDone + diff --git a/otp/src/avatar/AvatarHandle.py b/otp/src/avatar/AvatarHandle.py new file mode 100644 index 0000000..d1354e8 --- /dev/null +++ b/otp/src/avatar/AvatarHandle.py @@ -0,0 +1,20 @@ +class AvatarHandle: + dclassName = "AvatarHandle" + def getName(self): + if __dev__: + assert False, 'Must override this in inheriting class' + return '' + + def isOnline(self): + if __dev__: + assert False, 'Must override this in inheriting class' + return False + + def isUnderstandable(self): + if __dev__: + assert False, 'Must override this in inheriting class' + return True + + def setTalkWhisper(self, fromAV, fromAC, avatarName, chat, mods, flags): + newText, scrubbed = localAvatar.scrubTalk(chat, mods) + base.talkAssistant.receiveWhisperTalk(fromAV, avatarName, fromAC, None, self.avatarId, self.getName(), newText, scrubbed) diff --git a/otp/src/avatar/AvatarPanel.py b/otp/src/avatar/AvatarPanel.py new file mode 100644 index 0000000..b0cb5ff --- /dev/null +++ b/otp/src/avatar/AvatarPanel.py @@ -0,0 +1,98 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from direct.showbase import DirectObject +import Avatar +from direct.distributed import DistributedObject + +class AvatarPanel(DirectObject.DirectObject): + """ + This is a panel that pops up in response to clicking on a Toon or + Cog nearby you, or to picking a Toon from your friends list. It + draws a little picture of the avatar's head, and gives you a few + options to pick from re the avatar. + """ + # Limit to only have one avatar panel at a time + currentAvatarPanel = None + + def __init__(self, avatar, FriendsListPanel = None): + # You can only have one open at a time + if AvatarPanel.currentAvatarPanel: + AvatarPanel.currentAvatarPanel.cleanup() + AvatarPanel.currentAvatarPanel = self + + # Clean up any friends list panels that may be up + 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 = "Player" + + if (hasattr(avatar, "uniqueName")): + self.avId = avatar.doId + self.avDisableName = avatar.uniqueName('disable') + self.avGenerateName = avatar.uniqueName('generate') + self.avHpChangeName = avatar.uniqueName('hpChange') + + # If we have an actual DistributedObject for this avatar, use + # that one instead of whatever we're given. + if base.cr.doId2do.has_key(self.avId): + 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) + + def cleanup(self): + if AvatarPanel.currentAvatarPanel != self: + # Must already be cleaned up + 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 + + def __handleClose(self): + self.cleanup() + AvatarPanel.currentAvatarPanel = None + if self.friendsListShown: + # Restore the friends list if it was up before. + self.FriendsListPanel.showFriendsList() + + def __handleDisableAvatar(self): + # the old __handleDisableAvatar was sneaking past the inherited class + if AvatarPanel.currentAvatarPanel: + AvatarPanel.currentAvatarPanel.handleDisableAvatar() + else: + self.handleDisableAvatar() + + def handleDisableAvatar(self): + """ + Called whenever an avatar is disabled, this should cleanup the + avatar panel if it's not a friend. + """ + # If the avatar wandered away (or disconnected) shut down the panel. + self.cleanup() + AvatarPanel.currentAvatarPanel = None + + def isHidden(self): + # this function should be sub-classed + return 1 + + def getType(self): + return None diff --git a/otp/src/avatar/DistributedAvatar.py b/otp/src/avatar/DistributedAvatar.py new file mode 100644 index 0000000..a7a7c4c --- /dev/null +++ b/otp/src/avatar/DistributedAvatar.py @@ -0,0 +1,446 @@ +import time +import string + +from pandac.PandaModules import * + +from direct.distributed import DistributedNode +from direct.actor.DistributedActor import DistributedActor +from direct.task import Task +from direct.showbase import PythonUtil + +from libotp import Nametag +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLocalizer +from otp.speedchat import SCDecoders +from otp.chat import ChatGarbler +from otp.chat import ChatManager + +import random + +from Avatar import Avatar +import AvatarDNA + + +class DistributedAvatar(DistributedActor, Avatar): + # This is a text node used to create the numbers that appear over the + # heads of the avatars. + HpTextGenerator = TextNode("HpTextGenerator") + + # This is used to enable/disable the display of the hp numbers + HpTextEnabled = 1 + + # set this True so that we can start accepting nametagAmbientLightChanged in generate + # and ignore it in disable + ManagesNametagAmbientLightChanged = True + + def __init__(self, cr): + """ + Handle distributed updates + """ + try: + self.DistributedAvatar_initialized + return + except: + self.DistributedAvatar_initialized = 1 + + Avatar.__init__(self) + DistributedActor.__init__(self, cr) + + # The node that shows the number of hp just gained or lost + self.hpText = None + self.hp = None + self.maxHp = None + + + ### managing ActiveAvatars ### + + def disable(self): + """ + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + try: + del self.DistributedAvatar_announced + except: + return + self.reparentTo(hidden) + self.removeActive() + self.disableBodyCollisions() + self.hideHpText() + # By setting hp to None, when the distributed avatar is "uncached", + # and the hp gets set, it will be as if the avatar is new, and no + # number will appear over his head. If we don't set this to None, + # the setHp call might think that the number has changed and call + # showHpText, which we don't want. + self.hp = None + self.ignore("nameTagShowAvId") + self.ignore("nameTagShowName") + + DistributedActor.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + try: + self.DistributedAvatar_deleted + except: + self.DistributedAvatar_deleted = 1 + Avatar.delete(self) + DistributedActor.delete(self) + + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + + DistributedActor.generate(self) + if not self.isLocal(): + self.addActive() + self.considerUnderstandable() + + # Initially, a DistributedAvatar is always parented to hidden + # on generate, until we are told otherwise. + self.setParent(OTPGlobals.SPHidden) + + # Now that we have a doId, set a tag so others who find us in + # the collision system can figure out what avatar they hit. + 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() + + + + ### setParent ### + + def do_setParent(self, parentToken): + """do_setParent(self, int parentToken) + + This overrides a function defined in DistributedNode to + reparent the node somewhere. A DistributedAvatar wants to + hide the onscreen nametag when the parent is hidden. + """ + 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() + + ### setHp ### + + def toonUp(self, hpGained): + # WARNING This is extended in DistributedToon.py, please change that + # as well if any changes are made here + # Adjusts the avatar's hp upward by the indicated value + # (limited by maxHp) and shows green numbers flying out of the + # avatar's head. + + if self.hp == None or hpGained < 0: + return + + oldHp = self.hp + + # If hp is below zero, it might mean we're at a timeout in the + # playground, in which case we respect that it is below zero + # until we get our head above water. If our toonUp would + # take us above zero, then we pretend we started at zero in + # the first place, ignoring the timeout. + 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) + + def takeDamage(self, hpLost, bonus=0): + # Adjusts the avatar's hp downward by the indicated value + # (limited by 0) and shows red numbers flying out of the + # avatar's head. + 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() + + def setHp(self, hitPoints): + # We no longer fly numbers out of the avatar's head just for + # calling setHp(). Instead, toonUp() and takeDamage() divide + # that responsibility, and setHp() is just used to quietly + # reset the hp from the AI. + + justRanOutOfHp = (hitPoints is not None and + self.hp is not None and + self.hp - hitPoints > 0 and + hitPoints <= 0) + + # Store the new value. + self.hp = hitPoints + + # Send events so that the hp meter and others can know about the + # change to hp. + self.hpChange(quietly = 1) + + if justRanOutOfHp: + self.died() + + def hpChange(self, quietly = 0): + # We may not have a doId yet... in which case we can't send the + # event, and don't need to anyway. + if hasattr(self, "doId"): + if self.hp != None and self.maxHp != None: + messenger.send(self.uniqueName("hpChange"), [self.hp, self.maxHp, quietly]) + if self.hp != None and self.hp > 0: + messenger.send(self.uniqueName("positiveHP")) + + def died(self): + """ + This is a hook for derived classes to do something when the + avatar runs out of HP. The base function doesn't do anything. + """ + pass + + def getHp(self): + return self.hp + + + ### setMaxHp ### + + def setMaxHp(self, hitPoints): + self.maxHp = hitPoints + self.hpChange() + + def getMaxHp(self): + return self.maxHp + + ### getName ### + + def getName(self): + return(Avatar.getName(self)) + + def setName(self, name): + # Set the name of our top node, so it will be easy to identify + # this avatar in the scene graph. + try: + self.node().setName("%s-%d" % (name, self.doId)) + self.gotName = 1 + except: + # This might fail if the doId hasn't been set yet. + # No big deal. + pass + + + return(Avatar.setName(self, name)) + + ### hpText #### + + def showHpText(self, number, bonus=0, scale=1): + # WARNING if this changes please also change DistributedToon.py + if self.HpTextEnabled and not self.ghostMode: + # We don't show zero change. + if number != 0: + # Get rid of the number if it is already there. + if self.hpText: + self.hideHpText() + # Set the font + self.HpTextGenerator.setFont(OTPGlobals.getSignFont()) + # Show both negative and positive signs + if number < 0: + self.HpTextGenerator.setText(str(number)) + else: + self.HpTextGenerator.setText("+" + str(number)) + # No shadow + self.HpTextGenerator.clearShadow() + # Put a shadow on there + #self.HpTextGenerator.setShadow(0.05, 0.05) + #self.HpTextGenerator.setShadowColor(0, 0, 0, 1) + # Center the number + self.HpTextGenerator.setAlign(TextNode.ACenter) + # Red for negative, green for positive, yellow for bonus + 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() + + # Put the hpText over the head of the avatar + self.hpText = self.attachNewNode(self.hpTextNode) + self.hpText.setScale(scale) + # Make sure it is a billboard + self.hpText.setBillboardPointEye() + # Render it after other things in the scene. + self.hpText.setBin('fixed', 100) + + # Initial position ... Center of the body... the "tan tien" + self.hpText.setPos(0, 0, self.height/2) + seq = Task.sequence( + # Fly the number out of the character + self.hpText.lerpPos(Point3(0, 0, self.height + 1.5), + 1.0, + blendType = 'easeOut'), + # Wait 2 seconds + Task.pause(0.85), + # Fade the number + self.hpText.lerpColor(Vec4(r, g, b, a), + Vec4(r, g, b, 0), + 0.1), + # Get rid of the number + Task.Task(self.hideHpTextTask)) + taskMgr.add(seq, self.uniqueName("hpText")) + else: + # Just play the sound effect. + # TODO: Put in the sound effect! + pass + + def showHpString(self, text, duration=0.85, scale=0.7): + if self.HpTextEnabled and not self.ghostMode: + # We don't show empty strings + if text != '': + # Get rid of text if it is already there. + if self.hpText: + self.hideHpText() + # Set the font + self.HpTextGenerator.setFont(OTPGlobals.getSignFont()) + # Write the text + self.HpTextGenerator.setText(text) + # No shadow + self.HpTextGenerator.clearShadow() + # Put a shadow on there + #self.HpTextGenerator.setShadow(0.05, 0.05) + #self.HpTextGenerator.setShadowColor(0, 0, 0, 1) + # Center the text + self.HpTextGenerator.setAlign(TextNode.ACenter) + # Set the color and alpha scale (a) + r = a = 1.0 + g = b = 0.0 + + self.HpTextGenerator.setTextColor(r, g, b, a) + + self.hpTextNode = self.HpTextGenerator.generate() + + # Put the hpText over the head of the avatar + self.hpText = self.attachNewNode(self.hpTextNode) + # Set its scale + self.hpText.setScale(scale) + # Make sure it is a billboard + self.hpText.setBillboardAxis() + + # Initial position ... Center of the body... the "tan tien" + self.hpText.setPos(0, 0, self.height/2) + seq = Task.sequence( + # Fly the number out of the character + self.hpText.lerpPos(Point3(0, 0, self.height + 1.5), + 1.0, + blendType = 'easeOut'), + # Wait 2 seconds + Task.pause(duration), + # Fade the number + self.hpText.lerpColor(Vec4(r, g, b, a), + Vec4(r, g, b, 0), + 0.1), + # Get rid of the number + Task.Task(self.hideHpTextTask)) + taskMgr.add(seq, self.uniqueName("hpText")) + else: + # Just play the sound effect. + # TODO: Put in the sound effect! + pass + + def hideHpTextTask(self, task): + self.hideHpText() + return Task.done + + def hideHpText(self): + if self.hpText: + taskMgr.remove(self.uniqueName("hpText")) + self.hpText.removeNode() + self.hpText = None + + def getStareAtNodeAndOffset(self): + return self, Point3(0,0,self.height) + + def getAvIdName(self): + # Derived classes can override the base.idTags display. + 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): + #determines if a given avId in on my shard + if base.cr.doId2do.get(avId): + #print("Found Locally") + messenger.send("AvOnShard%s"%(avId), [True]) + else: + #print("asking AI") + self.sendUpdate("checkAvOnShard", [avId]) + + def confirmAvOnShard(self, avId, onShard = True): + messenger.send(("AvOnShard%s"%(avId)), [onShard]) + + ### play dialog sounds ### + + def getDialogueArray(self): + # Inheritors should override + return None + + diff --git a/otp/src/avatar/DistributedAvatarAI.py b/otp/src/avatar/DistributedAvatarAI.py new file mode 100644 index 0000000..65b6a3e --- /dev/null +++ b/otp/src/avatar/DistributedAvatarAI.py @@ -0,0 +1,103 @@ + +from otp.ai.AIBaseGlobal import * +from otp.otpbase import OTPGlobals + +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.distributed import DistributedNodeAI +from direct.task import Task + +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 b_setLocationName(self, locationName): + self.d_setLocationName(locationName) + self.setLocationName(locationName) + + def d_setLocationName(self, locationName): + pass + + def setLocationName(self, locationName): + self.locationName = locationName + + def getLocationName(self): + return self.locationName + + #---------------------------------- + + def b_setActivity(self, activity): + self.d_setActivity(activity) + self.setActivity(activity) + + def d_setActivity(self, activity): + pass + + def setActivity(self, activity): + self.activity = activity + + def getActivity(self): + return self.activity + + #---------------------------------- + + def toonUp(self, num): + # The default toonup is HP recharge. If other games want + # a more involved toonup, they can redefine this function + 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): + senderId = self.air.getAvatarIdFromSender() + onShard = False + if simbase.air.doId2do.get(avId): + onShard = True + self.sendUpdateToAvatarId(senderId,"confirmAvOnShard",[avId, onShard]) diff --git a/otp/src/avatar/DistributedAvatarUD.py b/otp/src/avatar/DistributedAvatarUD.py new file mode 100644 index 0000000..3f84d30 --- /dev/null +++ b/otp/src/avatar/DistributedAvatarUD.py @@ -0,0 +1,27 @@ + +from otp.ai.AIBaseGlobal import * +from otp.otpbase import OTPGlobals + +#from direct.fsm import ClassicFSM +#from direct.fsm import State +from direct.distributed.DistributedNodeAI import DistributedNodeAI +#from direct.task import Task + +class DistributedAvatarUD(DistributedNodeAI): + def __init__(self, air): + 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 diff --git a/otp/src/avatar/DistributedPlayer.py b/otp/src/avatar/DistributedPlayer.py new file mode 100644 index 0000000..6183dd1 --- /dev/null +++ b/otp/src/avatar/DistributedPlayer.py @@ -0,0 +1,707 @@ +"""DistributedPlayer module: contains the DistributedPlayer class""" + +from pandac.PandaModules import * +from libotp import WhisperPopup +from libotp import CFQuicktalker, CFPageButton, CFQuitButton, CFSpeech, CFThought, CFTimeout +from otp.chat import ChatGarbler +import string +from direct.task import Task +from otp.otpbase import OTPLocalizer +from otp.speedchat import SCDecoders +from direct.showbase import PythonUtil +from otp.avatar import DistributedAvatar +import time +from otp.avatar import Avatar, PlayerBase +from otp.chat import TalkAssistant +from otp.otpbase import OTPGlobals + +#hack, init for client-side outgoing chat filter +if base.config.GetBool('want-chatfilter-hacks',0): + from otp.switchboard import badwordpy + import os + badwordpy.init(os.environ.get('OTP')+'\\src\\switchboard\\','') + + +class DistributedPlayer(DistributedAvatar.DistributedAvatar, + PlayerBase.PlayerBase): + """Distributed Player class:""" + + # This is the length of time that should elapse before we allow + # another failed-teleport message to be displayed from the same + # avatar. + TeleportFailureTimeout = 60.0 + + # Create a default chat garbler (can be overridden by child class) + chatGarbler = ChatGarbler.ChatGarbler() + + def __init__(self, cr): + """ + Handle distributed updates + """ + try: + self.DistributedPlayer_initialized + except: + self.DistributedPlayer_initialized = 1 + + DistributedAvatar.DistributedAvatar.__init__(self, cr) + PlayerBase.PlayerBase.__init__(self) + + self.__teleportAvailable = 0 + + self.inventory = None + self.experience = None + + self.friendsList = [] + self.oldFriendsList = None + self.timeFriendsListChanged = None + self.ignoreList = [] + + self.lastFailedTeleportMessage = {} + self._districtWeAreGeneratedOn = None + + self.DISLname = "" + self.DISLid = 0 + + self.autoRun = 0 + + self.whiteListEnabled = base.config.GetBool('whitelist-chat-enabled', 1) + + + ### managing ActiveAvatars ### + + def disable(self): + """ + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + DistributedAvatar.DistributedAvatar.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + 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): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedAvatar.DistributedAvatar.generate(self) + + def setLocation(self, parentId, zoneId, teleport=0): + DistributedAvatar.DistributedAvatar.setLocation(self, parentId, zoneId, teleport) + # if the avatar just got put somewhere it shouldn't be, delete it + # this is to prevent hackers from sidling over into an 'uber' zone, thereby + # keeping themselves on your client even after the client no longer has interest in the + # original, legitimate zone from which the hacker toon was generated + 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) + + def isGeneratedOnDistrict(self, districtId=None): + if districtId is None: + return self._districtWeAreGeneratedOn is not None + else: + return self._districtWeAreGeneratedOn == districtId + + def getArrivedOnDistrictEvent(self, districtId=None): + if districtId is None: + return 'arrivedOnDistrict' + else: + return 'arrivedOnDistrict-%s' % districtId + + def arrivedOnDistrict(self, districtId): + # we have been generated on this district + curFrameTime = globalClock.getFrameTime() + if hasattr(self,"frameTimeWeArrivedOnDistrict") and \ + curFrameTime == self.frameTimeWeArrivedOnDistrict: + # rare case check if we get the zero from the shard we're leaving + # AFTER we get the district id of the shard we were going to + 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 + + def hasParentingRules(self): + # we can't define setParentingRules for the localAvatar in the DC because + # that would define parenting rules for other players' avatars. Just + # override this and always return True for the sake of the DoInterestManager + if self is localAvatar: + return True + + ### setAccountName ### + + def setAccountName(self, accountName): + self.accountName = accountName + + ### setWhisper ### + + def setSystemMessage(self, aboutId, chatString, + whisperType = WhisperPopup.WTSystem): + """setSystemMessage(self, int aboutId, string chatString) + + A message generated from the system (or the AI, or something + like that). If this involves another avatar (e.g. Flippy is + now online), the aboutId is filled in; otherwise, aboutId is + zero. + """ + self.displayWhisper(aboutId, chatString, whisperType) + + def displayWhisper(self, fromId, chatString, whisperType): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This is separate from setWhisper so we can safely call it by + name from within setWhisper and expect the derived function to + override it. + """ + print "Whisper type %s from %s: %s" % (whisperType, fromId, chatString) + + + def displayWhisperPlayer(self, playerId, chatString, whisperType): + """ + Displays the whisper message in whatever capacity makes sense. + This is separate from setWhisper so we can safely call it by + name from within setWhisper and expect the derived function to + override it. + """ + print "WhisperPlayer type %s from %s: %s" % (whisperType, playerId, chatString) + + ### setWhisperSC ### + + def whisperSCTo(self, msgIndex, sendToId, toPlayer): + """ + Sends a speedchat whisper message to the indicated + avatar/player. + """ + if toPlayer: + base.cr.playerFriendsManager.sendSCWhisper(sendToId, msgIndex) + else: + messenger.send("wakeup") + self.sendUpdate("setWhisperSCFrom", [self.doId, msgIndex], sendToId) + + def setWhisperSCFrom(self, fromId, msgIndex): + """ + Receive and decode the SpeedChat message. + """ + handle = base.cr.identifyAvatar(fromId) + if handle == None: + return + + if base.cr.avatarFriendsManager.checkIgnored(fromId): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + if fromId in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTQuickTalker) + base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_NORMAL, msgIndex, fromId) + + ### setWhisperSCCustom ### + + def whisperSCCustomTo(self, msgIndex, sendToId, toPlayer): + """ + Sends a speedchat whisper message to the indicated + toon, prefixed with our own name. + """ + if toPlayer: + base.cr.playerFriendsManager.sendSCCustomWhisper(sendToId, msgIndex) + return + + messenger.send("wakeup") + self.sendUpdate("setWhisperSCCustomFrom", [self.doId, msgIndex], + sendToId) + + def _isValidWhisperSource(self, source): + return True + + def setWhisperSCCustomFrom(self, fromId, msgIndex): + """ + Receive and decode the SC message. + """ + handle = base.cr.identifyAvatar(fromId) + if handle == None: + return + + if not self._isValidWhisperSource(handle): + self.notify.warning('displayWhisper from non-toon %s' % fromId) + return + + # new ignore list is handled by the Friends manager's, there are now two types, avatar and player. + if base.cr.avatarFriendsManager.checkIgnored(fromId): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + if fromId in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + chatString = SCDecoders.decodeSCCustomMsg(msgIndex) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTQuickTalker) + base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_CUSTOM, msgIndex, fromId) + + ### setWhisperSCEmote ### + + def whisperSCEmoteTo(self, emoteId, sendToId, toPlayer): + """ + Sends a speedchat whisper message to the indicated + toon, prefixed with our own name. + """ + print("whisperSCEmoteTo %s %s %s" % (emoteId, sendToId, toPlayer)) + if toPlayer: + base.cr.playerFriendsManager.sendSCEmoteWhisper(sendToId, emoteId) + return + messenger.send("wakeup") + self.sendUpdate("setWhisperSCEmoteFrom", [self.doId, emoteId], + sendToId) + + def setWhisperSCEmoteFrom(self, fromId, emoteId): + """ + Receive and decode the SC message. + """ + handle = base.cr.identifyAvatar(fromId) + if handle == None: + return + + if base.cr.avatarFriendsManager.checkIgnored(fromId): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + chatString = SCDecoders.decodeSCEmoteWhisperMsg(emoteId, + handle.getName()) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTEmote) + base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_EMOTE, emoteId, fromId) + + def d_setWhisperIgnored(self, sendToId): + # Don't send this message for the time being. + # I haven't removed it completely since we may + # want to send it in the future. + + # self.sendUpdate("setWhisperIgnored", [self.doId], sendToId) + pass + + ### setChat ### + def setChatAbsolute(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0): + DistributedAvatar.DistributedAvatar.setChatAbsolute(self, chatString, chatFlags, dialogue, interrupt) + if not quiet: + pass + + def b_setChat(self, chatString, chatFlags): + # Is this a magic word? All magic words begin with a ~ + if (self.cr.wantMagicWords and + (len(chatString) > 0) and (chatString[0] == "~")): + # Tell the magic word manager we just said a magic word + messenger.send("magicWord", [chatString]) + else: + # HACK for demo - Outgoing dirty word check. NEVER RELY ON THIS. + if base.config.GetBool('want-chatfilter-hacks',0): + if base.config.GetBool('want-chatfilter-drop-offending',0): + if badwordpy.test(chatString): + return + else: + chatString = badwordpy.scrub(chatString) + + # Local + # avoid having to check if this is the local toon + messenger.send("wakeup") + self.setChatAbsolute(chatString, chatFlags) + # Distributed + self.d_setChat(chatString, chatFlags) + + + def d_setChat(self, chatString, chatFlags): + self.sendUpdate("setChat", [chatString, chatFlags, 0]) + + #self.sendUpdate("setTalk", [0, 0, chatString, []]) + + + def setTalk(self, fromAV, fromAC, avatarName, chat, mods, flags): + newText, scrubbed = self.scrubTalk(chat, mods) + self.displayTalk(newText) + if base.talkAssistant.isThought(newText): + newText = base.talkAssistant.removeThoughtPrefix(newText) + base.talkAssistant.receiveThought(fromAV, avatarName, fromAC, None, newText, scrubbed) + else: + base.talkAssistant.receiveOpenTalk(fromAV, avatarName, fromAC, None, newText, scrubbed) + + def setTalkWhisper(self, fromAV, fromAC, avatarName, chat, mods, flags): + newText, scrubbed = self.scrubTalk(chat, mods) + #self.displayTalk(newText) + self.displayTalkWhisper(fromAV, avatarName, chat, mods) + base.talkAssistant.receiveWhisperTalk(fromAV, avatarName, fromAC, None, self.doId, self.getName(), newText, scrubbed) + + def displayTalkWhisper(self, fromId, avatarName, chatString, mods): + """displayTalkWhisper(self, int fromId, string chatString) + + Displays the whisper message in whatever capacity makes sense. + This is separate from setWhisper so we can safely call it by + name from within setWhisper and expect the derived function to + override it. + """ + print "TalkWhisper from %s: %s" % (fromId, chatString) + + def scrubTalk(self, chat, mods): + """ + returns chat where the mods have been replaced with appropreiate words + this is not in chat assistant because the replacement needs to be done + by the object that speeaks them. A pirate says "arr", + a duck says "quack", etc.. + """ + return chat + + def setChat(self, chatString, chatFlags, DISLid): + """setChat(self, string) + Garble the message if needed, then pass message to setChatAbsolute + """ + self.notify.error("Should call setTalk") + chatString = base.talkAssistant.whiteListFilterMessage(chatString) + + if base.cr.avatarFriendsManager.checkIgnored(self.doId): + # We're ignoring this jerk. + return + + # if we don't have chat permission, garble the chat message + if base.localAvatar.garbleChat and (not self.isUnderstandable()): + chatString = self.chatGarbler.garble(self, chatString) + + # Clear any buttons or speedchat state from the chat flags; + # both of these go through a different interface. + chatFlags &= ~(CFQuicktalker | CFPageButton | CFQuitButton) + + # Also, enforce that either the speech or thought bit is set, + # and the timeout is set iff speech is set. + if (chatFlags & CFThought): + chatFlags &= ~(CFSpeech | CFTimeout) + else: + chatFlags |= (CFSpeech | CFTimeout) + + self.setChatAbsolute(chatString, chatFlags) + + + + + ### setSC ### + + def b_setSC(self, msgIndex): + # Local + self.setSC(msgIndex) + # Distributed + self.d_setSC(msgIndex) + + def d_setSC(self, msgIndex): + messenger.send("wakeup") + self.sendUpdate("setSC", [msgIndex]) + + def setSC(self, msgIndex): + """ + Receive and decode the SC message + """ + + if base.cr.avatarFriendsManager.checkIgnored(self.doId): + # We're ignoring this jerk. + return + + if self.doId in base.localAvatar.ignoreList: + # We're ignoring this jerk. + return + + chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex) + if chatString: + self.setChatAbsolute(chatString, + CFSpeech | CFQuicktalker | CFTimeout, quiet = 1) + base.talkAssistant.receiveOpenSpeedChat(TalkAssistant.SPEEDCHAT_NORMAL, msgIndex, self.doId) + + ### setSCCustom ### + + def b_setSCCustom(self, msgIndex): + # Local + self.setSCCustom(msgIndex) + # Distributed + self.d_setSCCustom(msgIndex) + + def d_setSCCustom(self, msgIndex): + messenger.send("wakeup") + self.sendUpdate("setSCCustom", [msgIndex]) + + def setSCCustom(self, msgIndex): + """ + Receive and decode the SC message + """ + # new ignore list is handled by the Friends manager's, there are now two types, avatar and player. + if base.cr.avatarFriendsManager.checkIgnored(self.doId): + # We're ignoring this jerk. + return + + if self.doId in base.localAvatar.ignoreList: + # We're ignoring this jerk. + return + + chatString = SCDecoders.decodeSCCustomMsg(msgIndex) + if chatString: + self.setChatAbsolute(chatString, + CFSpeech | CFQuicktalker | CFTimeout) + base.talkAssistant.receiveOpenSpeedChat(TalkAssistant.SPEEDCHAT_CUSTOM, msgIndex, self.doId) + + ### setSCEmote ### + + 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): + """friendsNotify(self, int32 avId, int8 status) + + This message is sent by the AI to notify the client when + friends are added or removed without the client's + participation, if the client happens to be logged in. + + status is one of: + + 1 - The indicated avatar is no longer your friend. + 2 - The indicated avatar is now your "special" friend. + + Other changes don't require notification, because the client + was presumably in direct control. + """ + 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()) + + ### teleportQuery ### + + def d_teleportQuery(self, requesterId, sendToId = None): + self.sendUpdate("teleportQuery", [requesterId], sendToId) + #print("sending teleportQuery %s %s" % (requesterId, sendToId)) + + def teleportQuery(self, requesterId): + """teleportQuery(self, int requesterId) + + This distributed message is sent peer-to-peer from one client + who is considering teleporting to another client. When it is + received, the receiving client should send back a + teleportResponse indicating whether she is available to be + teleported to (e.g. not on a trolley or something), and if so, + where she is. + """ + # Only consider teleport requests from toons who are on our + # friends list, or who are somewhere nearby. + #print("received teleportQuery %s" % (requesterId)) + #avatar = base.cr.identifyAvatar(requesterId) + avatar = base.cr.playerFriendsManager.identifyFriend(requesterId) + + if avatar != None: + # new ignore list is handled by the Friends manager's, there are now two types, avatar and player. + if base.cr.avatarFriendsManager.checkIgnored(requesterId): + self.d_teleportResponse(self.doId, 2, 0, 0, 0, sendToId = requesterId) + return + + # We're ignoring this jerk. Send back a suitable response. + if requesterId in self.ignoreList: + self.d_teleportResponse(self.doId, 2, 0, 0, 0, sendToId = requesterId) + return + + # If we're in a private party and the requester isn't invited, tell + # them we're busy (ie: let them down easy, don't rub it in their face) + if hasattr(base, "distributedParty"): + if base.distributedParty.partyInfo.isPrivate: + # We need to check the guest list of this party and see if + # requesterId is on the list + if requesterId not in base.distributedParty.inviteeIds: + # Sorry, not on the list, send a try-again-later message + self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId = requesterId) + return + + if base.distributedParty.isPartyEnding: + self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId = requesterId) + return + + #print("teleport Available %s Ghost %s" % (self.__teleportAvailable, self.ghostMode)) + if self.__teleportAvailable and not self.ghostMode: + # Generate a whisper message that so-and-so is teleporting + # to us. + self.setSystemMessage(requesterId, OTPLocalizer.WhisperComingToVisit % (avatar.getName())) + + # We don't know where we are, so send a teleportQuery + # to someone who does. Whoever hangs a hook on this + # event will call the appropriate teleportResponse. + messenger.send('teleportQuery', [avatar, self]) + return + + # Generate a whisper message that so-and-so wants to + # teleport to us, but can't because we're busy. But don't + # generate more than one of these per minute or so. + if self.failedTeleportMessageOk(requesterId): + self.setSystemMessage(requesterId, OTPLocalizer.WhisperFailedVisit % (avatar.getName())) + + + # Send back a try-again-later message. + self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId = requesterId) + + def failedTeleportMessageOk(self, fromId): + """failedTeleportMessageOk(self, int fromId) + + Registers a failure-to-teleport attempt from the indicated + avatar. Returns true if it is ok to display this message, or + false if the message should be suppressed (because we just + recently displayed one). + """ + now = globalClock.getFrameTime() + lastTime = self.lastFailedTeleportMessage.get(fromId, None) + if lastTime != None: + elapsed = now - lastTime + if elapsed < self.TeleportFailureTimeout: + return 0 + + self.lastFailedTeleportMessage[fromId] = now + return 1 + + ### teleportResponse ### + + def d_teleportResponse(self, avId, available, shardId, hoodId, zoneId, + sendToId = None): + self.sendUpdate("teleportResponse", [avId, available, shardId, hoodId, zoneId], sendToId) + + def teleportResponse(self, avId, available, shardId, hoodId, zoneId): + messenger.send('teleportResponse', [avId, available, shardId, hoodId, zoneId]) + + ### teleportGiveup ### + + def d_teleportGiveup(self, requesterId, sendToId = None): + self.sendUpdate("teleportGiveup", [requesterId], sendToId) + + def teleportGiveup(self, requesterId): + """teleportGiveup(self, int requesterId) + + This message is sent after a client has failed to teleport + successfully to another client, probably because the target + client didn't stay put. It just pops up a whisper message to + that effect. + + """ + avatar = base.cr.identifyAvatar(requesterId) + + if not self._isValidWhisperSource(avatar): + self.notify.warning('teleportGiveup from non-toon %s' % requesterId) + return + + if avatar != None: + self.setSystemMessage(requesterId, OTPLocalizer.WhisperGiveupVisit % (avatar.getName())) + + ### teleportGreeting ### + + # This message is sent on completion of teleport, to set up the + # automatic "Hi, so-and-so" chat balloon. We can't use setChat + # because that gets garbled for speedchat-only clients. + + def b_teleportGreeting(self, avId): + self.d_teleportGreeting(avId) + self.teleportGreeting(avId) + + def d_teleportGreeting(self, avId): + self.sendUpdate("teleportGreeting", [avId]) + + def teleportGreeting(self, avId): + # Normally, the greeting will be issued to someone who we have + # just teleported to, and is therefore in the same zone with + # us. If this is so, it is easy to determine what the greeted + # toon's actual name is; otherwise, something went wrong + # (maybe our greeted toon just left!) and we can simply ignore + # the message. + + 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)) + + ### Teleport support functions ### + + def setTeleportAvailable(self, available): + """setTeleportAvailable(self, bool available) + + Sets the 'teleportAvailable' flag. When this is true, the + client is deemed to be available to be teleported to, and + someone should be listening to teleportQuery messages from the + messenger. When it is false, teleport queries from nearby + toons will automatically be returned with a false response + without generating a teleportQuery message. + """ + self.__teleportAvailable = available + + def getTeleportAvailable(self): + return self.__teleportAvailable + + def getFriendsList(self): + return self.friendsList + + def setFriendsList(self, friendsList): + self.oldFriendsList = self.friendsList + self.friendsList = friendsList + self.timeFriendsListChanged = globalClock.getFrameTime() + assert self.notify.debug("setting friends list to %s" % self.friendsList) + + # We want to throw a special event whenever the LocalToon's + # friends list changes, although not when just any + # DistributedToon's friends list changes. It would be cleaner + # to define this special behavior in LocalToon.py, but as it + # happens, it won't get called there because of the way + # ClientDistUpdate.py works. + + # Fortunately, this method will *only* get called for + # LocalToon, and not for any of the other DistributedToons. + # So we can get away with putting this here. + messenger.send('friendsListChanged') + + # When our friends list changes, the set of other avatars we + # can understand might also change. + Avatar.reconsiderAllUnderstandable() + + def setDISLname(self, name): + self.DISLname = name + + def setDISLid(self, id): + self.DISLid = id + + + def setAutoRun(self, value): + self.autoRun = value + + def getAutoRun(self): + return self.autoRun diff --git a/otp/src/avatar/DistributedPlayerAI.py b/otp/src/avatar/DistributedPlayerAI.py new file mode 100644 index 0000000..4c5f607 --- /dev/null +++ b/otp/src/avatar/DistributedPlayerAI.py @@ -0,0 +1,158 @@ + +from direct.showbase import GarbageReport +from otp.ai.AIBaseGlobal import * +from otp.avatar import DistributedAvatarAI +from otp.avatar import PlayerBase +from otp.otpbase import OTPGlobals + +class DistributedPlayerAI(DistributedAvatarAI.DistributedAvatarAI, + PlayerBase.PlayerBase): + def __init__(self, air): + DistributedAvatarAI.DistributedAvatarAI.__init__(self, air) + PlayerBase.PlayerBase.__init__(self) + self.friendsList = [] + + if __dev__: + def generate(self): + self._sentExitServerEvent = False + DistributedAvatarAI.DistributedAvatarAI.generate(self) + + def announceGenerate(self): + DistributedAvatarAI.DistributedAvatarAI.announceGenerate(self) + self._doPlayerEnter() + + def _announceArrival(self): + self.sendUpdate('arrivedOnDistrict', [self.air.districtId]) + + def _announceExit(self): + # clear out the 'arrivedOnDistrict' field + self.sendUpdate('arrivedOnDistrict', [0]) + + def _sendExitServerEvent(self): + """call this in your delete() function. This would be an + override of delete(), but player classes typically use + multiple inheritance, and some other base class gets to + call down the chain to DistributedObjectAI before this + class gets a chance, and self.air & self.doId are removed + in the first call to DistributedObjectAI.delete(). Better + would be reference counting calls to generate() and delete() + in base classes that appear more than once in a class' + inheritance heirarchy""" + self.air.writeServerEvent('avatarExit', self.doId, '') + if __dev__: + self._sentExitServerEvent = True + + def delete(self): + if __dev__: + # make sure _sendExitServerEvent() was called + assert self._sentExitServerEvent + del self._sentExitServerEvent + self._doPlayerExit() + if __dev__: + GarbageReport.checkForGarbageLeaks() + DistributedAvatarAI.DistributedAvatarAI.delete(self) + + def isPlayerControlled(self): + return True + + def setLocation(self, parentId, zoneId, teleport=0): + DistributedAvatarAI.DistributedAvatarAI.setLocation(self, parentId, zoneId, teleport) + if self.isPlayerControlled(): + # hmm, did this come from a hacker trying to get somewhere they shouldn't be? + 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() + + # override if you don't want to affect the population count for a + # particular PlayerAI + def incrementPopulation(self): + self.air.incrementPopulation() + def decrementPopulation(self): + # use simbase in case we've already deleted self.air + simbase.air.decrementPopulation() + + def b_setChat(self, chatString, chatFlags): + # Local + self.setChat(chatString, chatFlags) + # Distributed + self.d_setChat(chatString, chatFlags) + + def d_setChat(self, chatString, chatFlags): + self.sendUpdate("setChat", [chatString, chatFlags]) + + def setChat(self, chatString, chatFlags): + # I guess on the AI side there is nothing to do here + pass + + 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_setCommonChatFlags(self, flags): + self.sendUpdate("setCommonChatFlags", [flags]) + + def setCommonChatFlags(self, flags): + pass + + def d_friendsNotify(self, avId, status): + self.sendUpdate("friendsNotify", [avId, status]) + + def friendsNotify(self, avId, status): + pass + + def setAccountName(self, accountName): + self.accountName = accountName + + def getAccountName(self): + return self.accountName + + def setDISLid(self, id): + self.DISLid = id + + 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 extendFriendsList(self, friendId, friendCode): + # This is called only by the friend manager when a new friend + # transaction is successfully completed. Its purpose is + # simply to update the AI's own copy of the avatar's friends + # list, mainly so that the quest manager can reliably know + # if the avatar has any friends. + + # First, see if we already had this friend. + for i in range(len(self.friendsList)): + friendPair = self.friendsList[i] + if friendPair[0] == friendId: + # We did. Update the code. + self.friendsList[i] = (friendId, friendCode) + return + + # We didn't already have this friend; tack it on. + self.friendsList.append((friendId, friendCode)) + + # Note that if an avatar *breaks* a friendship, the AI never + # hears about it. So our friends list will not be 100% + # up-to-date, but it will at least be good enough for the + # quest manager. + diff --git a/otp/src/avatar/Emote.py b/otp/src/avatar/Emote.py new file mode 100644 index 0000000..8889444 --- /dev/null +++ b/otp/src/avatar/Emote.py @@ -0,0 +1,29 @@ +from otp.otpbase import OTPLocalizer +import types + +class Emote: + + EmoteClear = -1 + EmoteEnableStateChanged = 'EmoteEnableStateChanged' + + # Emote data is stored in the order it appears in the SpeedChat m + # The integer stored is the reference count to the Emote. If the + # count goes above zero, it means that the emote is disabled. Fo + # a minigame might increment the reference count if it wants to e + # disable emotes. + + def __init__(self): + self.emoteFunc = None + + def isEnabled(self, index): + # find the emotes index if we are given a string + 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 diff --git a/otp/src/avatar/LocalAvatar.py b/otp/src/avatar/LocalAvatar.py new file mode 100644 index 0000000..c160113 --- /dev/null +++ b/otp/src/avatar/LocalAvatar.py @@ -0,0 +1,2050 @@ +"""LocalAvatar module: contains the LocalAvatar class""" + +from pandac.PandaModules import * +from libotp import Nametag, WhisperPopup +from direct.gui.DirectGui import * +from direct.showbase.PythonUtil import * +from direct.interval.IntervalGlobal import * +from direct.showbase.InputStateGlobal import inputState +from pandac.PandaModules import * + +import Avatar +from direct.controls import ControlManager +import DistributedAvatar +from direct.task import Task +import PositionExaminer +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPRender +import math +import string +#import whrandom +import random +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedSmoothNode +from direct.gui import DirectGuiGlobals +from otp.otpbase import OTPLocalizer + +from direct.controls.GhostWalker import GhostWalker +from direct.controls.GravityWalker import GravityWalker +from direct.controls.ObserverWalker import ObserverWalker +from direct.controls.PhysicsWalker import PhysicsWalker +from direct.controls.SwimWalker import SwimWalker +from direct.controls.TwoDWalker import TwoDWalker +if __debug__: + from direct.controls.DevWalker import DevWalker + +class LocalAvatar(DistributedAvatar.DistributedAvatar, + DistributedSmoothNode.DistributedSmoothNode): + """ + This is the local version of a distributed avatar. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("LocalAvatar") + + wantDevCameraPositions = base.config.GetBool('want-dev-camera-positions', 0) + wantMouse = base.config.GetBool('want-mouse', 0) + + sleepTimeout = base.config.GetInt('sleep-timeout', 120) + swimTimeout = base.config.GetInt('afk-timeout', 600) + + __enableMarkerPlacement = base.config.GetBool('place-markers', 0) + + acceptingNewFriends = base.config.GetBool('accepting-new-friends', 1) + + # special methods + def __init__(self, cr, chatMgr, talkAssistant = None, passMessagesThrough = False): + """ + cr is a ClientRepository + """ + try: + self.LocalAvatar_initialized + return + except: + pass + + self.LocalAvatar_initialized = 1 + DistributedAvatar.DistributedAvatar.__init__(self, cr) + DistributedSmoothNode.DistributedSmoothNode.__init__(self, cr) + + # Set up the collision traverser: + self.cTrav = CollisionTraverser("base.cTrav") + base.pushCTrav(self.cTrav) + self.cTrav.setRespectPrevTransform(1) + + self.avatarControlsEnabled=0 + self.controlManager = ControlManager.ControlManager(True, passMessagesThrough) + + # Set up collisions: + self.initializeCollisions() + # Set up camera: + self.initializeSmartCamera() + self.cameraPositions = [] + # Set animation speed: + self.animMultiplier = 1.0 + + # How long should the button be held to start running: + self.runTimeout = 2.5 + + # Custom SpeedChat messages. + self.customMessages = [] + + # MPG Create chat manager in the child class + self.chatMgr = chatMgr + base.talkAssistant = talkAssistant + + # Initially, we have no common chat flags. The server + # might or might not assign us some special flags based on + # our "green" from login. + self.commonChatFlags = 0 + self.garbleChat = 1 + + # This is normally 1, but some funny states may set this + # to 0 to forbid teleporting away even though we are in + # walk mode. + self.teleportAllowed = 1 + + # This should be set when the local avatar is placed in movie + # mode. This is especially true for the pet movies since we + # want to allow the PetAvatarPanel to pop up if a player clicks + # on the pet while the avatar is locked down. We want to keep the + # functionality consistent, but disable any actions that the + # player may take. + self.lockedDown = 0 + + self.isPageUp=0 + self.isPageDown=0 + + # Let derived classes fill these in + self.soundRun = None + self.soundWalk = None + + if __debug__: + if base.config.GetBool('want-dev-walker', 1): + self.accept('f4', self.useDevControls) + self.accept('f4-up', self.useWalkControls) + + # Is the toon sleeping? + self.sleepFlag = 0 + + # Is the toon disguised? + self.isDisguised = 0 + + # is the toon moving? + self.movingFlag = 0 + self.swimmingFlag = 0 + + + self.lastNeedH = None + + self.accept('friendOnline', self.__friendOnline) + self.accept('friendOffline', self.__friendOffline) + self.accept('clickedWhisper', self.clickedWhisper) + self.accept('playerOnline', self.__playerOnline) + self.accept('playerOffline', self.__playerOffline) + + # We listen for this event all the time, not just while + # we're sleeping. This event serves not just to wake us + # up, but also to keep us from going to sleep! + self.sleepCallback = None + self.accept('wakeup', self.wakeUp) + + self.jumpLandAnimFixTask = None + + self.fov = OTPGlobals.DefaultCameraFov + self.accept("avatarMoving", self.clearPageUpDown) + + # And our nametag2d probably shouldn't be visible. + # On reflection, it *should* be visible. There are times + # when the localToon is off camera--for instance, when + # we're looking in the sticker book, or watching a + # movie--and it would be nice to see our own offscreen + # chat messages. However, we don't want to see an arrow + # pointing to our own name, so instead of hiding the whole + # thing, we just set it to only show our chat messages. + self.nametag2dNormalContents = Nametag.CSpeech + self.showNametag2d() + + # Nor is it pickable. If we wanted to make it pickable, + # we only have to change this flag and also add a hook to + # call self.clickedNametag. + self.setPickable(0) + + def useSwimControls(self): + self.controlManager.use("swim", self) + + def useGhostControls(self): + self.controlManager.use("ghost", self) + + def useWalkControls(self): + self.controlManager.use("walk", self) + + def useTwoDControls(self): + self.controlManager.use("twoD", self) + + if __debug__: + def useDevControls(self): + self.controlManager.use("dev", self) + + def isLockedDown(self): + return self.lockedDown + + def lock(self): + if (self.lockedDown == 1): + self.notify.debug("lock() - already locked!") + self.lockedDown = 1 + + def unlock(self): + if (self.lockedDown == 0): + self.notify.debug("unlock() - already unlocked!") + self.lockedDown = 0 + + def isInWater(self): + return (self.getZ(render) <= 0.0) + + def isTeleportAllowed(self): + """ + Returns true if the local avatar is currently allowed to + teleport away somewhere, false otherwise. + """ + return self.teleportAllowed and not self.isDisguised + + def setTeleportAllowed(self, flag): + """ + Sets the flag that indicates whether the toon is allowed to + teleport away, even if we are in walk mode. Usually this is + 1, but it may be set to 0 in unusual cases + """ + self.teleportAllowed = flag + self.refreshOnscreenButtons() + + def sendFriendsListEvent(self): + self.wakeUp() + messenger.send("openFriendsList") + + def delete(self): + try: + self.LocalAvatar_deleted + return + except: + self.LocalAvatar_deleted = 1 + self.ignoreAll() + self.stopJumpLandTask() + taskMgr.remove('shadowReach') + + base.popCTrav() + + # Precaution in-case the smart camera is being lerped. + taskMgr.remove('posCamera') + + self.disableAvatarControls() + self.stopTrackAnimToSpeed() + self.stopUpdateSmartCamera() + self.shutdownSmartCamera() + self.deleteCollisions() + self.controlManager.delete() + self.physControls = None + del self.controlManager + self.positionExaminer.delete() + del self.positionExaminer + taskMgr.remove(self.uniqueName("walkReturnTask")) + self.chatMgr.delete() + del self.chatMgr + del self.soundRun + del self.soundWalk + if hasattr(self, "soundWhisper"): + del self.soundWhisper + + DistributedAvatar.DistributedAvatar.delete(self) + + def shadowReach(self, state): + if base.localAvatar.shadowPlacer: + base.localAvatar.shadowPlacer.lifter.setReach( + base.localAvatar.getAirborneHeight()+4.0) + return Task.cont + + def wantLegacyLifter(self): + return False + + def setupControls( + self, + avatarRadius = 1.4, + floorOffset = OTPGlobals.FloorOffset, + reach = 4.0, + wallBitmask = OTPGlobals.WallBitmask, + floorBitmask = OTPGlobals.FloorBitmask, + ghostBitmask = OTPGlobals.GhostBitmask, + ): + """ + Set up the local avatar for collisions + """ + if 0: + # Physics Walker: + physControls=PhysicsWalker(gravity = -32.1740 * 2.0) # * 2.0 is a hack + physControls.setWallBitMask(wallBitmask) + physControls.setFloorBitMask(floorBitmask) + physControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + physControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(physControls, "phys") + self.physControls = physControls + + # Avatar Gravity Walker: + walkControls=GravityWalker(legacyLifter=self.wantLegacyLifter()) + walkControls.setWallBitMask(wallBitmask) + walkControls.setFloorBitMask(floorBitmask) + walkControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + walkControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(walkControls, "walk") + self.physControls = walkControls + + # Avatar 2D Scroller Walker: + twoDControls = TwoDWalker() + twoDControls.setWallBitMask(wallBitmask) + twoDControls.setFloorBitMask(floorBitmask) + twoDControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + twoDControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(twoDControls, "twoD") + + # Avatar swimming: + swimControls=SwimWalker() + swimControls.setWallBitMask(wallBitmask) + swimControls.setFloorBitMask(floorBitmask) + swimControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + swimControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(swimControls, "swim") + + # Ghost mode (moving furniture, for example): + ghostControls=GhostWalker() + ghostControls.setWallBitMask(ghostBitmask) + ghostControls.setFloorBitMask(floorBitmask) + ghostControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + ghostControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(ghostControls, "ghost") + + # Observer mode (following ai avatars, for example): + observerControls=ObserverWalker() + observerControls.setWallBitMask(ghostBitmask) + observerControls.setFloorBitMask(floorBitmask) + observerControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + observerControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(observerControls, "observer") + + # Develpment Debug Walker (fly, walk through walls, run, etc.): + if __debug__: + devControls=DevWalker() + devControls.setWallBitMask(wallBitmask) + devControls.setFloorBitMask(floorBitmask) + devControls.initializeCollisions(self.cTrav, self, + avatarRadius, floorOffset, reach) + devControls.setAirborneHeightFunc(self.getAirborneHeight) + self.controlManager.add(devControls, "dev") + + # Default to the standard avatar walk controls: + self.controlManager.use("walk", self) + # Start out disabled otherwise controls will be on + # while you are still not in walk mode + self.controlManager.disable() + + # An override of DistributedAvatar's initializeCollisions() + def initializeCollisions(self): + """ + Set up the local avatar for collisions + """ + self.setupControls() + + # HACK: This should be temporary: + + def deleteCollisions(self): + self.controlManager.deleteCollisions() + self.ignore("entero157") + del self.cTrav + + def initializeSmartCameraCollisions(self): + # SMART-CAMERA STUFF + + # set up the smart camera's collision traverser + # we need this because normal collisions get handled just before + # rendering. using the normal collision mechanisms would introduce + # one frame of lag in the smart camera, allowing the camera to pop + # through walls momentarily. + self.ccTrav = CollisionTraverser("LocalAvatar.ccTrav") + + # Set up the camera obstruction test line segment + # This is a line segment from the visibility point to the ideal + # camera location + self.ccLine = CollisionSegment(0.0, 0.0, 0.0, 1.0, 0.0, 0.0) + self.ccLineNode = CollisionNode('ccLineNode') + self.ccLineNode.addSolid(self.ccLine) + self.ccLineNodePath = self.attachNewNode(self.ccLineNode) + self.ccLineBitMask = OTPGlobals.CameraBitmask + self.ccLineNode.setFromCollideMask(self.ccLineBitMask) + self.ccLineNode.setIntoCollideMask(BitMask32.allOff()) + + # set up camera collision mechanism + self.camCollisionQueue = CollisionHandlerQueue() + + # set up camera obstruction collision reciever + self.ccTrav.addCollider(self.ccLineNodePath, self.camCollisionQueue) + + ## set up a sphere around camera to keep it away from the walls + + # make the sphere + # the sphere attribs will be calculated later + self.ccSphere = CollisionSphere(0,0,0,1) + self.ccSphereNode = CollisionNode('ccSphereNode') + self.ccSphereNode.addSolid(self.ccSphere) + self.ccSphereNodePath = base.camera.attachNewNode(self.ccSphereNode) + self.ccSphereNode.setFromCollideMask(OTPGlobals.CameraBitmask) + self.ccSphereNode.setIntoCollideMask(BitMask32.allOff()) + + # attach a pusher to the sphere + self.camPusher = CollisionHandlerPusher() + # Do this when the camera gets activated + #self.cTrav.addCollider(self.ccSphereNodePath, self.camPusher) + self.camPusher.addCollider(self.ccSphereNodePath, base.camera) + + # Set a special mode on the pusher so that it doesn't get + # fooled by walls facing away from the toon. + self.camPusher.setCenter(self) + + #### + # create another traverser with a camera pusher + # sphere so that we can push the camera at will + self.ccPusherTrav = CollisionTraverser("LocalAvatar.ccPusherTrav") + + # make the sphere + self.ccSphere2 = self.ccSphere + self.ccSphereNode2 = CollisionNode('ccSphereNode2') + self.ccSphereNode2.addSolid(self.ccSphere2) + self.ccSphereNodePath2 = base.camera.attachNewNode(self.ccSphereNode2) + self.ccSphereNode2.setFromCollideMask(OTPGlobals.CameraBitmask) + self.ccSphereNode2.setIntoCollideMask(BitMask32.allOff()) + + # attach a pusher to the sphere + self.camPusher2 = CollisionHandlerPusher() + self.ccPusherTrav.addCollider(self.ccSphereNodePath2, self.camPusher2) + self.camPusher2.addCollider(self.ccSphereNodePath2, base.camera) + + # Set a special mode on the pusher so that it doesn't get + # fooled by walls facing away from the toon. + self.camPusher2.setCenter(self) + + # create a separate node for the camera's floor-detection ray + # If we just parented the ray to the camera, it would rotate + # with the camera and no longer be facing straight down. + self.camFloorRayNode = self.attachNewNode("camFloorRayNode") + + # set up camera collision mechanisms + + # Set up the "cameraman" collison ray + # This is a ray cast from the camera down to detect floor polygons + self.ccRay = CollisionRay(0.0, 0.0, 0.0, 0.0, 0.0, -1.0) + self.ccRayNode = CollisionNode('ccRayNode') + self.ccRayNode.addSolid(self.ccRay) + self.ccRayNodePath = self.camFloorRayNode.attachNewNode(self.ccRayNode) + self.ccRayBitMask = OTPGlobals.FloorBitmask + self.ccRayNode.setFromCollideMask(self.ccRayBitMask) + self.ccRayNode.setIntoCollideMask(BitMask32.allOff()) + + self.ccTravFloor = CollisionTraverser("LocalAvatar.ccTravFloor") + + self.camFloorCollisionQueue = CollisionHandlerQueue() + self.ccTravFloor.addCollider(self.ccRayNodePath, + self.camFloorCollisionQueue) + + self.ccTravOnFloor = CollisionTraverser("LocalAvatar.ccTravOnFloor") + + # set up another ray to generate on-floor/off-floor events + self.ccRay2 = CollisionRay(0.0, 0.0, 0.0, 0.0, 0.0, -1.0) + self.ccRay2Node = CollisionNode('ccRay2Node') + self.ccRay2Node.addSolid(self.ccRay2) + self.ccRay2NodePath = self.camFloorRayNode.attachNewNode( + self.ccRay2Node) + self.ccRay2BitMask = OTPGlobals.FloorBitmask + self.ccRay2Node.setFromCollideMask(self.ccRay2BitMask) + self.ccRay2Node.setIntoCollideMask(BitMask32.allOff()) + + # dummy node for CollisionHandlerFloor to move + self.ccRay2MoveNodePath = hidden.attachNewNode('ccRay2MoveNode') + + #import pdb; pdb.set_trace() + self.camFloorCollisionBroadcaster = CollisionHandlerFloor() + self.camFloorCollisionBroadcaster.setInPattern("on-floor") + self.camFloorCollisionBroadcaster.setOutPattern("off-floor") + # detect the floor with ccRay2, and move a dummy node + self.camFloorCollisionBroadcaster.addCollider( + self.ccRay2NodePath, self.ccRay2MoveNodePath) + + def deleteSmartCameraCollisions(self): + del self.ccTrav + del self.ccLine + + del self.ccLineNode + self.ccLineNodePath.removeNode() + del self.ccLineNodePath + del self.camCollisionQueue + + del self.ccRay + del self.ccRayNode + self.ccRayNodePath.removeNode() + del self.ccRayNodePath + + del self.ccRay2 + del self.ccRay2Node + self.ccRay2NodePath.removeNode() + del self.ccRay2NodePath + self.ccRay2MoveNodePath.removeNode() + del self.ccRay2MoveNodePath + + del self.ccTravOnFloor + del self.ccTravFloor + del self.camFloorCollisionQueue + del self.camFloorCollisionBroadcaster + + del self.ccSphere + del self.ccSphereNode + self.ccSphereNodePath.removeNode() + del self.ccSphereNodePath + + del self.camPusher + del self.ccPusherTrav + + del self.ccSphere2 + del self.ccSphereNode2 + self.ccSphereNodePath2.removeNode() + del self.ccSphereNodePath2 + + del self.camPusher2 + + def collisionsOff(self): + self.controlManager.collisionsOff() + + def collisionsOn(self): + self.controlManager.collisionsOn() + + def recalcCameraSphere(self): + """ this will adjust the smart camera for new FOV/near plane settings""" + # get the near-plane params + nearPlaneDist = base.camLens.getNear() + hFov = base.camLens.getHfov() + vFov = base.camLens.getVfov() + + hOff = nearPlaneDist * math.tan(deg2Rad(hFov/2.)) + vOff = nearPlaneDist * math.tan(deg2Rad(vFov/2.)) + + # average the points together to get the sphere center + camPnts = [Point3( hOff, nearPlaneDist, vOff), + Point3(-hOff, nearPlaneDist, vOff), + Point3( hOff, nearPlaneDist, -vOff), + Point3(-hOff, nearPlaneDist, -vOff), + Point3(0.0, 0.0, 0.0)] + + avgPnt = Point3(0.0,0.0,0.0) + for camPnt in camPnts: + avgPnt = avgPnt + camPnt + avgPnt = avgPnt / len(camPnts) + + #calculate a minimum bounding sphere + sphereRadius = 0.0 + for camPnt in camPnts: + dist = Vec3(camPnt - avgPnt).length() + if (dist > sphereRadius): + sphereRadius = dist + + # set the new sphere params + avgPnt = Point3(avgPnt) + self.ccSphereNodePath.setPos(avgPnt) + self.ccSphereNodePath2.setPos(avgPnt) + # note that this also changes ccSphere2 (which is what we want) + self.ccSphere.setRadius(sphereRadius) + + def putCameraFloorRayOnAvatar(self): + """place the camera ray on the avatar itself""" + self.camFloorRayNode.setPos(self, 0,0,5) + + def putCameraFloorRayOnCamera(self): + """place the camera ray in the center of the camera's collision + sphere""" + self.camFloorRayNode.setPos(self.ccSphereNodePath, 0,0,0) + + #def collidedWithWall(self, collisionEntry): + # base.playSfx(self.soundWalkCollision) + + # set-up + def attachCamera(self): + """ + Make the mouse drive the toon around and make the + camera go to its viewpoint + """ + # attach the camera + camera.reparentTo(self) + base.enableMouse() + base.setMouseOnNode(self.node()) + + # Do we want mouse navigation? + self.ignoreMouse=not self.wantMouse + + self.setWalkSpeedNormal() + + def detachCamera(self): + base.disableMouse() + + def stopJumpLandTask(self): + if self.jumpLandAnimFixTask: + self.jumpLandAnimFixTask.remove() + self.jumpLandAnimFixTask = None + + if 1: # HACK: These are to fix the anmation aftar a jump -- the anim fsm needs to be redone so these are not necessary + def jumpStart(self): + if not self.sleepFlag and self.hp > 0: + self.b_setAnimState("jumpAirborne", 1.0) + self.stopJumpLandTask() + + def returnToWalk(self, task): + # Please help this hack: + if self.sleepFlag: + state = "Sleep" + elif self.hp > 0: + state = "Happy" + else: + state = "Sad" + self.b_setAnimState(state, 1.0) + return Task.done + + def jumpLandAnimFix(self, jumpTime): + if self.playingAnim != "run" and self.playingAnim != "walk": + # We have to be sure to remove this task (along with + # any other task or doLater we spawn) in the delete() + # method, so it will be stopped should we get a sudden + # disconnect from the server. + return taskMgr.doMethodLater( + jumpTime, self.returnToWalk, + self.uniqueName("walkReturnTask")) + + def jumpHardLand(self): + if self.allowHardLand(): + self.b_setAnimState("jumpLand", 1.0) + self.stopJumpLandTask() + self.jumpLandAnimFixTask = self.jumpLandAnimFix(1.0) + + # Send an additional position report every time we land so + # the smoother won't miss the instants when a distributed + # toon is on the floor. + if self.d_broadcastPosHpr: + self.d_broadcastPosHpr() + + def jumpLand(self): + self.jumpLandAnimFixTask = self.jumpLandAnimFix(0.01) + + # Send an additional position report every time we land so + # the smoother won't miss the instants when a distributed + # toon is on the floor. + if self.d_broadcastPosHpr: + self.d_broadcastPosHpr() + + def setupAnimationEvents(self): + assert self.notify.debugStateCall(self) + self.accept("jumpStart", self.jumpStart, []) + self.accept("jumpHardLand", self.jumpHardLand, []) + self.accept("jumpLand", self.jumpLand, []) + + def ignoreAnimationEvents(self): + assert self.notify.debugStateCall(self) + self.ignore("jumpStart") + self.ignore("jumpHardLand") + self.ignore("jumpLand") + + def allowHardLand(self): + return ((not self.sleepFlag) and (self.hp > 0)) + + def enableSmartCameraViews(self): + self.accept("tab", self.nextCameraPos, [1]) + self.accept("shift-tab", self.nextCameraPos, [0]) + self.accept("page_up", self.pageUp) + self.accept("page_down", self.pageDown) + + def disableSmartCameraViews(self): + self.ignore("tab") + self.ignore("shift-tab") + self.ignore("page_up") + self.ignore("page_down") + self.ignore("page_down-up") + + def enableAvatarControls(self): + """ + Activate the tab, page up, arrow keys, etc. + """ + assert self.notify.debugStateCall(self) + if self.avatarControlsEnabled: + assert self.debugPrint(" avatarControlsEnabled=true") + return + self.avatarControlsEnabled=1 + self.setupAnimationEvents() + self.controlManager.enable() + + def disableAvatarControls(self): + """ + Ignore the tab, page up, arrow keys, etc. + """ + assert self.notify.debugStateCall(self) + if not self.avatarControlsEnabled: + assert self.debugPrint(" avatarControlsEnabled=false") + return + self.avatarControlsEnabled=0 + self.ignoreAnimationEvents() + self.controlManager.disable() + self.clearPageUpDown() + + def setWalkSpeedNormal(self): + self.controlManager.setSpeeds( + OTPGlobals.ToonForwardSpeed, + OTPGlobals.ToonJumpForce, + OTPGlobals.ToonReverseSpeed, + OTPGlobals.ToonRotateSpeed) + + def setWalkSpeedSlow(self): + self.controlManager.setSpeeds( + OTPGlobals.ToonForwardSlowSpeed, + OTPGlobals.ToonJumpSlowForce, + OTPGlobals.ToonReverseSlowSpeed, + OTPGlobals.ToonRotateSlowSpeed) + + def pageUp(self): + if not self.avatarControlsEnabled: + return + self.wakeUp() + if not self.isPageUp: + self.isPageDown = 0 + self.isPageUp = 1 + self.lerpCameraFov(70, 0.6) + self.setCameraPositionByIndex(self.cameraIndex) + else: + self.clearPageUpDown() + + def pageDown(self): + if not self.avatarControlsEnabled: + return + self.wakeUp() + if not self.isPageDown: + self.isPageUp = 0 + self.isPageDown = 1 + self.lerpCameraFov(70,0.6) + self.setCameraPositionByIndex(self.cameraIndex) + else: + self.clearPageUpDown() + + def clearPageUpDown(self): + if self.isPageDown or self.isPageUp: + self.lerpCameraFov(self.fov, 0.6) + self.isPageDown = 0 + self.isPageUp = 0 + self.setCameraPositionByIndex(self.cameraIndex) + + def nextCameraPos(self, forward): + """ + Cycle to the next camera position in cameraPositions list + """ + if not self.avatarControlsEnabled: + return + self.wakeUp() + self.__cameraHasBeenMoved = 1 # force the camera to process + + if (forward): + self.cameraIndex += 1 + if (self.cameraIndex > (len(self.cameraPositions) - 1)): + self.cameraIndex = 0 + else: + self.cameraIndex -= 1 + if (self.cameraIndex < 0): + self.cameraIndex = len(self.cameraPositions)-1 + self.setCameraPositionByIndex(self.cameraIndex) + + def initCameraPositions(self): + camHeight = self.getClampedAvatarHeight() + heightScaleFactor = (camHeight * 0.3333333333) + + # default LookAt point is at avatar's height, some distance in front + defLookAt = Point3(0.0, 1.5, camHeight) + + scXoffset = 3.0 + # high, places toon under speedchat + scPosition = (Point3(scXoffset-1, -10.0, camHeight+5.0), + Point3(scXoffset, 2.0, camHeight)) + + # In general, the camera shots move forward, until you're + # looking back at the toon, and then switch to the furthest + # behind the toon and then move forward again. + # + # (position, neutral lookat, up lookat, down lookat, disableSmartCam) + self.cameraPositions = [ + #close shot + (Point3(0.0, (-9.0 * heightScaleFactor), camHeight), #pos + defLookAt, #fwd + Point3(0.0, camHeight, camHeight*4.0), #up + Point3(0.0, camHeight, camHeight*-1.0), #down + 0, + ), + #fireworks shot + #(Point3(0.0, (-8.0 * heightScaleFactor), camHeight/2.0), #pos + # Point3(0.0, 1.5, camHeight*1.8), + # Point3(0.0, camHeight, camHeight*4.0), #up + # Point3(0.0, camHeight, camHeight*-1.0), #down + # 0, + # ), + #first-person + (Point3(0.0, 0.5, camHeight), #pos + defLookAt, #fwd + Point3(0.0, camHeight, camHeight*1.33), #up + Point3(0.0, camHeight, camHeight*0.66), #down + 1, # disable the smart cam + ), + # If you move this shot out of index 2, be sure to adjust + # self.nextCameraPos() to match. Maybe we should at a flag, + # rather than use a hard coded index. + # up shot: + #(Point3(0.0, (1.0 * heightScaleFactor), camHeight), + # Point3(0.0, camHeight, camHeight*1.33)), + #scPosition, + #third person + (Point3((5.7 * heightScaleFactor), #pos + (7.65 * heightScaleFactor), + (camHeight + 2.0)), + Point3(0.0, 1.0, camHeight), + Point3(0.0, 1.0, camHeight*4.0), + Point3(0.0, 1.0, camHeight*-1.0), + 0, + ), + #extra wide shot + (Point3(0.0, (-24.0 * heightScaleFactor), (camHeight + 4.0)), + defLookAt, + Point3(0.0, 1.5, camHeight * 4.0), + Point3(0.0, 1.5, camHeight * -1.0), + 0, + ), + #wide shot (default) + (Point3(0.0, (-12.0 * heightScaleFactor), (camHeight + 4.0)), + defLookAt, + Point3(0.0, 1.5, camHeight * 4.0), + Point3(0.0, 1.5, camHeight * -1.0), + 0, + ), + ] + self.auxCameraPositions + if self.wantDevCameraPositions: + self.cameraPositions+=[ + # Overhead: + (Point3(0.0, 0.0, camHeight*3), #pos + Point3(0.0, 0.0, 0.0), #fwd + Point3(0.0, camHeight*2, 0.0), #up + Point3(0.0, -camHeight*2, 0.0), #down + 1, # disable the smart cam + ), + # From right: + (Point3(camHeight*3, 0.0, camHeight), #pos + Point3(0.0, 0.0, camHeight), #fwd + Point3(0.0, camHeight, camHeight*1.1), #up + Point3(0.0, camHeight, camHeight*0.9), #down + 1, # disable the smart cam + ), + # Close-up of feet (good for physics testing): + (Point3(camHeight*3, 0.0, 0.0), #pos + Point3(0.0, 0.0, camHeight), #fwd + Point3(0.0, camHeight, camHeight*1.1), #up + Point3(0.0, camHeight, camHeight*0.9), #down + 1, # disable the smart cam + ), + # Dramatic from floor (just for fun): + #(Point3(1.5, 4.0, -2.0), #pos + # Point3(0.0, 0.0, camHeight), #fwd + # Point3(0.0, camHeight, camHeight*1.1), #up + # Point3(0.0, camHeight, camHeight*0.9), #down + # 1, # disable the smart cam + # ), + # From left: + (Point3(-camHeight*3, 0.0, camHeight), #pos + Point3(0.0, 0.0, camHeight), #fwd + Point3(0.0, camHeight, camHeight*1.1), #up + Point3(0.0, camHeight, camHeight*0.9), #down + 1, # disable the smart cam + ), + # daisy gardens maze solver + # This shot is too drastic to include in the release + # It shows lots of behind the scenes areas + (Point3(0.0, -60, 60), + (defLookAt + Point3(0, 15, 0)), + (defLookAt + Point3(0, 15, 0)), + (defLookAt + Point3(0, 15, 0)), + 1, # disable the smart cam + ), + # platformer + # This is an attempt at a good general purpose + # camera for jumping from moving platform to moving + # platform. + (Point3(0.0, -20, 20), + (defLookAt + Point3(0, 5, 0)), + (defLookAt + Point3(0, 5, 0)), + (defLookAt + Point3(0, 5, 0)), + 1, # disable the smart cam + ), + ] + + def addCameraPosition(self, camPos = None): + if camPos == None: + lookAtNP = self.attachNewNode('lookAt') + lookAtNP.setPos(base.cam,0,1,0) + lookAtPos = lookAtNP.getPos() + camHeight = self.getClampedAvatarHeight() + camPos = (base.cam.getPos(self), + lookAtPos, + Point3(0.0, 1.5, camHeight * 4.0), + Point3(0.0, 1.5, camHeight * -1.0), + 1, + ) + lookAtNP.removeNode() + self.auxCameraPositions.append(camPos) + self.cameraPositions.append(camPos) + + def resetCameraPosition(self): + self.cameraIndex = 0 + self.setCameraPositionByIndex(self.cameraIndex) + + def removeCameraPosition(self): + if len(self.cameraPositions) > 1: + camPos = self.cameraPositions[self.cameraIndex] + if camPos in self.auxCameraPositions: + self.auxCameraPositions.remove(camPos) + if camPos in self.cameraPositions: + self.cameraPositions.remove(camPos) + self.nextCameraPos(1) + + def printCameraPositions(self): + print '[' + for i in range(len(self.cameraPositions)): + self.printCameraPosition(i) + print ',' + print ']' + + def printCameraPosition(self, index): + cp = self.cameraPositions[index] + print '(Point3(%0.2f, %0.2f, %0.2f),' % (cp[0][0],cp[0][1],cp[0][2]) + print 'Point3(%0.2f, %0.2f, %0.2f),' % (cp[1][0],cp[1][1],cp[1][2]) + print 'Point3(%0.2f, %0.2f, %0.2f),' % (cp[2][0],cp[2][1],cp[2][2]) + print 'Point3(%0.2f, %0.2f, %0.2f),' % (cp[3][0],cp[3][1],cp[3][2]) + print '%d,' % cp[4] + print ')', + + def posCamera(self, lerp, time): + """posCamera(self, boolean, float) + Move the camera to current position as indicated by + getCompromiseCameraPos(). If lerp is true, lerp the + motion over time seconds. + """ + if not lerp: + # load in the target params + self.positionCameraWithPusher( + self.getCompromiseCameraPos(), self.getLookAtPoint()) + else: + camPos = self.getCompromiseCameraPos() + + # we've got a target camera location and look-at point + # we need to figure out our desired HPR from this info + + # save current camera params + savePos = camera.getPos() + saveHpr = camera.getHpr() + + # load in the target params + self.positionCameraWithPusher(camPos, self.getLookAtPoint()) + + x = camPos[0] + y = camPos[1] + z = camPos[2] + destHpr = camera.getHpr() + h = destHpr[0] + p = destHpr[1] + r = destHpr[2] + + # restore camera params + camera.setPos(savePos) + camera.setHpr(saveHpr) + + taskMgr.remove("posCamera") + camera.lerpPosHpr(x, y, z, h, p, r, time, task="posCamera") + + def getClampedAvatarHeight(self): + return max(self.getHeight(), 3.0) + + # smart camera functions + def getVisibilityPoint(self): + """ + this returns the point that must be visible at all times + """ + return Point3(0.0, 0.0, self.getHeight()) + + # if there is ever a setVisibilityPoint function, + # make it call self.updateSmartCameraCollisionLineSegment() + # the way setIdealCameraPos() does + + def setLookAtPoint(self, la): + """setLookAtPoint(self, Point3) + setter for the point that the camera should look at + NOTE: this is not necessarily the point that must be visible; + see getVisibilityPoint() above + """ + self.__curLookAt = Point3(la) + + def getLookAtPoint(self): + """ + getter for the point the camera should look at + """ + return Point3(self.__curLookAt) + + def setIdealCameraPos(self, pos): + """setIdealCameraPos(self, Point3) + setter for the location of where the camera would like to be + """ + self.__idealCameraPos = Point3(pos) + self.updateSmartCameraCollisionLineSegment() + + def getIdealCameraPos(self): + """ + getter for the location of where the camera would like to be + """ + return Point3(self.__idealCameraPos) + + def setCameraPositionByIndex(self, index): + """setCameraPositionByIndex(self, int) + sets the camera's ideal position, lookat point, etc. + based on an index into the cameraPositions table + """ + self.notify.debug('switching to camera position %s' % index) + self.setCameraSettings(self.cameraPositions[index]) + + def setCameraPosForPetInteraction(self): + height = self.getClampedAvatarHeight() + point = Point3(height*(7/3.), height*(-7/3.), height) + + self.prevIdealPos = self.getIdealCameraPos() + # TODO: ADD look at functionality + + self.setIdealCameraPos(point) + self.posCamera(1, 0.7) + + def unsetCameraPosForPetInteraction(self): + assert hasattr(self, "prevIdealPos") + self.setIdealCameraPos(self.prevIdealPos) + del self.prevIdealPos + self.posCamera(1, 0.7) + + def setCameraSettings(self, camSettings): + self.setIdealCameraPos(camSettings[0]) + + if ((self.isPageUp and self.isPageDown) or + ((not self.isPageUp) and (not self.isPageDown))): + self.__cameraHasBeenMoved = 1 # force the camera to process + self.setLookAtPoint(camSettings[1]) + elif self.isPageUp: + self.__cameraHasBeenMoved = 1 # force the camera to process + self.setLookAtPoint(camSettings[2]) + elif self.isPageDown: + self.__cameraHasBeenMoved = 1 # force the camera to process + self.setLookAtPoint(camSettings[3]) + else: + self.notify.error("This case should be impossible.") + + # this camera position may disable the smart camera + self.__disableSmartCam = camSettings[4] + + if self.__disableSmartCam: + # If the smart cam is disabled, put the ray right onto the toon. + # Otherwise, the ray would be floating off in space somewhere + # relative to the toon, generating events for some random piece + # of floor, which is *very* bad for the HQ factories. + self.putCameraFloorRayOnAvatar() + + # if we're disabling the smart camera, reset the floor Z offset + self.cameraZOffset = 0.0 + + def getCompromiseCameraPos(self): + """ + returns the location of where the camera should be to avoid + view obstructions + """ + if (self.__idealCameraObstructed == 0): + compromisePos = self.getIdealCameraPos() + else: + # interpolate the camera position between the + # ideal camera position and the visibility point, + # so that it is in front of the obstruction + visPnt = self.getVisibilityPoint() + idealPos = self.getIdealCameraPos() + distance = Vec3(idealPos - visPnt).length() + ratio = self.closestObstructionDistance / distance + compromisePos = (idealPos * ratio) + (visPnt * (1 - ratio)) + # lift the camera up a bit, the closer it gets to the avatar + liftMult = (1.0 - (ratio*ratio)) + compromisePos = Point3(compromisePos[0], compromisePos[1], + compromisePos[2] + \ + ((self.getHeight()*0.4) * liftMult)) + compromisePos.setZ(compromisePos[2] + self.cameraZOffset) + return compromisePos + + def updateSmartCameraCollisionLineSegment(self): + """ + updates the camera's obstruction-detecting collision-line-segment + """ + # set back end to ideal camera position + pointB = self.getIdealCameraPos() + # set front end to the visibility point, possibly pulled back a bit + pointA = self.getVisibilityPoint() + + vectorAB = Vec3(pointB - pointA) + lengthAB = vectorAB.length() + + """ + This doesn't seem to be necessary any more with the new physics + + # keep the line back from the toon a bit to avoid false + # collisions with objects in front of the toon + pullbackDist = 1. + # if the distance between the ideal camera position and + # the visibility point is less-than-or-equal-to the pullback + # distance, don't bother pulling the line back from the toon + if lengthAB > pullbackDist: + pullbackVector = vectorAB * (pullbackDist / lengthAB) + pointA = Point3(pointA + Point3(pullbackVector)) + lengthAB -= pullbackDist + """ + + # if pointA and pointB are too close, don't set the + # segment endpoints. TODO: figure out something smarter. + if lengthAB > 0.001: + self.ccLine.setPointA(pointA) + self.ccLine.setPointB(pointB) + + + def initializeSmartCamera(self): + self.__idealCameraObstructed = 0 + self.closestObstructionDistance = 0.0 + # create self.cameraIndex + self.cameraIndex = 0 + self.auxCameraPositions = [] + self.cameraZOffset = 0.0 + + self.__onLevelGround = 0 + self.__camCollCanMove = 0 + self.__geom = render + + self.__disableSmartCam = 0 + + self.initializeSmartCameraCollisions() + + self._smartCamEnabled = False + + def shutdownSmartCamera(self): + self.deleteSmartCameraCollisions() + + def setOnLevelGround(self, flag): + self.__onLevelGround = flag + + def setCameraCollisionsCanMove(self, flag): + self.__camCollCanMove = flag + + def setGeom(self, geom): + # optimization 1 + self.__geom = geom + + def startUpdateSmartCamera(self, push = 1): + """ + Spawn a task to update the smart camera every frame + """ + if self._smartCamEnabled: + LocalAvatar.notify.warning( + 'redundant call to startUpdateSmartCamera') + return + + # We use getKey() as a temporary workaround for problem with + # inherited ==. + assert camera.getParent().getKey() == self.getKey(), \ + "camera must be parented to localToon before calling " \ + "startUpdateSmartCamera" + + self._smartCamEnabled = True + + # this flag is needed in cases where the camera is created before + # it's put into the world. When the world suddenly shows up, we + # jump to where the floor is and set this flag. + self.__floorDetected = 0 + + # In the smart camera update task, + # we check to see if the camera has moved (wrt render) since + # the last frame. If it hasn't, then the camera can't possibly + # have become obstructed, since there is nothing that moves AND + # obstructs the camera. We can use this knowledge to save some + # CPU time. + # When we move the camera explicitly to a new location (i.e. when + # the user presses TAB), we need to override this check, by setting + # this flag to a non-zero value. + self.__cameraHasBeenMoved = 0 + + # adjust for any changes to the camera FOV, etc. + self.recalcCameraSphere() + + # initialize a whole buncha stuff + # this would all be in initializeSmartCamera() except that self.height + # is NOT set by the time initializeSmartCamera() is called + self.initCameraPositions() + self.setCameraPositionByIndex(self.cameraIndex) + # slam the camera to its destination + self.posCamera(0, 0.) + # self.__instantaneousCamPos holds the current "instantaneous" + # camera position. this variable is used to keep the camera moving + # in a straight line towards its target position regardless of how + # the camera is pushed off-track by its pusher. + self.__instantaneousCamPos = camera.getPos() + + if push: + + # Activate the camera Pusher + self.cTrav.addCollider(self.ccSphereNodePath, self.camPusher) + # activate the on-floor ray + self.ccTravOnFloor.addCollider(self.ccRay2NodePath, + self.camFloorCollisionBroadcaster) + self.__disableSmartCam = 0 + else: + self.__disableSmartCam = 1 + + self.__lastPosWrtRender = camera.getPos(render) + self.__lastHprWrtRender = camera.getHpr(render) + + taskName = self.taskName("updateSmartCamera") + # remove any old + taskMgr.remove(taskName) + # spawn the new task + # Set the priority somewhere between the collision task and + # the rendering: + taskMgr.add(self.updateSmartCamera, taskName, priority=47) + + self.enableSmartCameraViews() + + def stopUpdateSmartCamera(self): + if not self._smartCamEnabled: + LocalAvatar.notify.warning( + 'redundant call to stopUpdateSmartCamera') + return + self.disableSmartCameraViews() + # Deactivate the cam pusher + self.cTrav.removeCollider(self.ccSphereNodePath) + # Deactivate the on-floor ray + self.ccTravOnFloor.removeCollider(self.ccRay2NodePath) + + # make sure the floor-detection rays are right on top of the + # toon (but watch out when we're shutting down and the toon has + # already been deleted). + if not base.localAvatar.isEmpty(): + self.putCameraFloorRayOnAvatar() + + taskName = self.taskName("updateSmartCamera") + taskMgr.remove(taskName) + self._smartCamEnabled = False + + def updateSmartCamera(self, task): + if (not self.__camCollCanMove) and (not self.__cameraHasBeenMoved): + if self.__lastPosWrtRender == camera.getPos(render): + if self.__lastHprWrtRender == camera.getHpr(render): + return Task.cont + + self.__cameraHasBeenMoved = 0 + self.__lastPosWrtRender = camera.getPos(render) + self.__lastHprWrtRender = camera.getHpr(render) + + self.__idealCameraObstructed = 0 + + # if the smart cam is not disabled, check for view obstructions + if not self.__disableSmartCam: + # traverse the smart camera line segment collision tree + self.ccTrav.traverse(self.__geom) + # check if we have any collisions + if (self.camCollisionQueue.getNumEntries() > 0): + # just take the closest one + self.camCollisionQueue.sortEntries() + self.handleCameraObstruction( + self.camCollisionQueue.getEntry(0)) + + # account for the floor + # optimization 2 + if not self.__onLevelGround: + self.handleCameraFloorInteraction() + + if not self.__idealCameraObstructed: + # move the camera a bit + self.nudgeCamera() + + if not self.__disableSmartCam: + # tell the pusher to do its thing; keep the camera out of the walls + self.ccPusherTrav.traverse(self.__geom) + # make sure the on-floor ray is in the same spot as the camera + self.putCameraFloorRayOnCamera() + + # now that the camera is in its final pos, do the on-floor traversal + # (this generates the on-floor event) + self.ccTravOnFloor.traverse(self.__geom) + + return Task.cont + + def positionCameraWithPusher(self, pos, lookAt): + """positionCameraWithPusher(self, Point3, Point3) + Positions the camera at pos, invokes the camera collision + pusher, and orients the camera towards lookAt + """ + # load in the target position + camera.setPos(pos) + # tell the pusher to do its thing + self.ccPusherTrav.traverse(self.__geom) + # look at the target lookAt point + camera.lookAt(lookAt) + + def nudgeCamera(self): + """ + Move the camera a little bit closer to its desired + position as indicated by getCompromiseCameraPos(). + """ + # when the pos or hpr gets within this distance of the target, + # set it and be done with it + CLOSE_ENOUGH = 0.1 + + # save current camera position + curCamPos = self.__instantaneousCamPos + curCamHpr = camera.getHpr() + + # get target camera params + targetCamPos = self.getCompromiseCameraPos() + targetCamLookAt = self.getLookAtPoint() + + posDone = 0 + if Vec3(curCamPos - targetCamPos).length() <= CLOSE_ENOUGH: + camera.setPos(targetCamPos) + posDone = 1 + + ## do this here to get correct HPR + #targetCamPos.setZ(targetCamPos[2] + self.cameraZOffset) + + #################### + # we've got a target camera position and look-at point + # we need to figure out our desired HPR from this info + + ## this is more correct, but slower; it takes the pusher's + ## effect on the camera HPR into account + #self.positionCameraWithPusher(targetCamPos, targetCamLookAt) + + # this is less correct but quicker; it ignores the influence + # of the pusher on the camera HPR + camera.setPos(targetCamPos) + camera.lookAt(targetCamLookAt) + #################### + + targetCamHpr = camera.getHpr() + + hprDone = 0 + if Vec3(curCamHpr - targetCamHpr).length() <= CLOSE_ENOUGH: + # hpr was just set above + hprDone = 1 + + # can we bail early? + if posDone and hprDone: + return + + # move camera every frame according to this normalized percentage + # of the distance between its current position and its + # destination position + # note: here, 'frame' == 1/30 of a second + lerpRatio = 0.15 + + # account for dt + lerpRatio = 1 - pow((1-lerpRatio), globalClock.getDt()*30.0) + + # calc new instantaneous position + # (this will be modified if necessary by collision pusher) + self.__instantaneousCamPos = ((targetCamPos * lerpRatio) + + (curCamPos * (1 - lerpRatio))) + + if self.__disableSmartCam or (not self.__idealCameraObstructed): + # calc new hpr + newHpr = (targetCamHpr * lerpRatio) + (curCamHpr * (1 - lerpRatio)) + else: + # why lerp the hpr at all in smart-cam mode? + # It was causing a sickening camera-turn + newHpr = targetCamHpr + + # set new camera params + camera.setPos(self.__instantaneousCamPos) + camera.setHpr(newHpr) + + def popCameraToDest(self): + # get new camera params + newCamPos = self.getCompromiseCameraPos() + newCamLookAt = self.getLookAtPoint() + + # set the new camera position with the pusher, so + # that the HPR gets set correctly and we don't make + # the player sick + self.positionCameraWithPusher(newCamPos, newCamLookAt) + + # camera has been 'HOG' moved (Hand Of God) + # so set the instantaneous position to the new + # position, taking the pusher into account + self.__instantaneousCamPos = camera.getPos() + + # camera obstruction detection handler + def handleCameraObstruction(self, camObstrCollisionEntry): + # calculate distance of obstruction + collisionPoint = camObstrCollisionEntry.getSurfacePoint( + self.ccLineNodePath) + collisionVec = Vec3(collisionPoint - self.ccLine.getPointA()) + distance = collisionVec.length() + + self.__idealCameraObstructed = 1 + self.closestObstructionDistance = distance + + self.popCameraToDest() + + + # camera's floor-ray collision handler + def handleCameraFloorInteraction(self): + # figure out a Z offset for the camera + + # first, update the position of the ray's parent node + # if smart cam is disabled, we should not be moving the ray to + # where the camera is + assert not self.__disableSmartCam + self.putCameraFloorRayOnCamera() + + # traverse the smart camera floor ray collision tree + self.ccTravFloor.traverse(self.__geom) + + # we might be in here just for the sake of the collision traversal. + # if so, bail out now. + if self.__onLevelGround: + return + + # check if we have any collisions + if (self.camFloorCollisionQueue.getNumEntries() == 0): + return + + # get the closest one + self.camFloorCollisionQueue.sortEntries() + camObstrCollisionEntry = self.camFloorCollisionQueue.getEntry(0) + + # the intersection point's Z is the floor's offset from the camera; + # we would normally negate it to get the camera's offset from the floor + # This is a negative number + camHeightFromFloor = camObstrCollisionEntry.getSurfacePoint( + self.ccRayNodePath)[2] + + # the "target height" is the camera's ideal height, + # translated to the floor that's currently under it + self.cameraZOffset = camera.getPos()[2] + camHeightFromFloor + + # Don't move down at all because when you are standing on a crate + # you do not want the camera to be below your feet looking up at you + if (self.cameraZOffset < 0): + self.cameraZOffset = 0 + + # if this is the first time we've hit floor, pop right to the + # destination height + if (self.__floorDetected == 0): + self.__floorDetected = 1 + self.popCameraToDest() + + + + def lerpCameraFov(self, fov, time): + """ + lerp the camera fov over time (used by the battle) + """ + taskMgr.remove('cam-fov-lerp-play') + oldFov = base.camLens.getHfov() + # Fov values often have floating point precision errors + if (abs(fov - oldFov) > 0.1): + def setCamFov(fov): + base.camLens.setFov(fov) + self.camLerpInterval = LerpFunctionInterval(setCamFov, + fromData=oldFov, toData=fov, duration=time, + name='cam-fov-lerp') + self.camLerpInterval.start() + + def setCameraFov(self, fov): + """ Sets the camera to a particular fov and remembers this + fov, so that things like page up and page down that + temporarily change fov will restore it properly. """ + + self.fov = fov + if not (self.isPageDown or self.isPageUp): + base.camLens.setFov(self.fov) + + def gotoNode(self, node, eyeHeight = 3): + """gotoNode(self, NodePath node) + + Puts the avatar at a suitable point nearby, and facing, the + indicated NodePath, whatever it might be. This will normally + be another avatar, as in Goto Friend. + """ + # The only trick here is to find a place near the destination + # point that is valid for us to stand. This will be a point + # that (a) has a ground, (b) is not already occupied, and (c) + # is not behind a wall. + # + # To find such a suitable point, we'll consider a series of + # possible points in order from most preferable to least + # preferable, until we find a match. This is admittedly a bit + # expensive, but who really cares? The PositionExaminer + # object will examine each point for us. + possiblePoints = ( + # Try some points in front of the avatar + Point3(3, 6, 0), + Point3(-3, 6, 0), + + Point3(6, 6, 0), + Point3(-6, 6, 0), + + Point3(3, 9, 0), + Point3(-3, 9, 0), + Point3(6, 9, 0), + Point3(-6, 9, 0), + Point3(9, 9, 0), + Point3(-9, 9, 0), + + # Try some points to the side + Point3(6, 0, 0), + Point3(-6, 0, 0), + Point3(6, 3, 0), + Point3(-6, 3, 0), + Point3(9, 9, 0), + Point3(-9, 9, 0), + + # Try some points further in front + Point3(0, 12, 0), + Point3(3, 12, 0), + Point3(-3, 12, 0), + Point3(6, 12, 0), + Point3(-6, 12, 0), + Point3(9, 12, 0), + Point3(-9, 12, 0), + + # Try some points behind + Point3(0, -6, 0), + Point3(-3, -6, 0), + Point3(0, -9, 0), + Point3(-6, -9, 0)) + + for point in possiblePoints: + pos = self.positionExaminer.consider(node, point, eyeHeight) + if pos: + self.setPos(node, pos) + self.lookAt(node) + + # Don't look exactly at the node; instead, look ten + # degrees left or right of it. That way we'll + # actually be able to see it, without it hiding behind + # our toon. + self.setHpr(self.getH() + random.choice((-10, 10)), 0, 0) + return + + # If we couldn't find a suitable point, just punt and drop us + # on top of the thing. + self.setPos(node, 0, 0, 0) + + # Update available custom quicktalker messages + def setCustomMessages(self, customMessages): + self.customMessages = customMessages + messenger.send("customMessagesChanged") + + # Whisper + def displayWhisper(self, fromId, chatString, whisperType): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This function overrides a similar function in DistributedAvatar. + """ + sender = None + sfx = self.soundWhisper + + # MPG we need to identify the sender in a non-toontown specific way + #sender = base.cr.identifyAvatar(fromId) + + if (whisperType == WhisperPopup.WTNormal or \ + whisperType == WhisperPopup.WTQuickTalker): + if sender == None: + return + # Prefix the sender's name to the message. + chatString = sender.getName() + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + whisperType) + if sender != None: + whisper.setClickable(sender.getName(), fromId) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + + # Whisper + def displayWhisperPlayer(self, fromId, chatString, whisperType): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This function overrides a similar function in DistributedAvatar. + """ + sender = None + playerInfo = None + sfx = self.soundWhisper + + # MPG we need to identify the sender in a non-toontown specific way + #sender = base.cr.identifyAvatar(fromId) + #sender = idenityPlayer(fromId) + playerInfo = base.cr.playerFriendsManager.playerId2Info.get(fromId,None) + if playerInfo == None: + return + senderName = playerInfo.playerName + + if (whisperType == WhisperPopup.WTNormal or \ + whisperType == WhisperPopup.WTQuickTalker): + # Prefix the sender's name to the message. + chatString = senderName + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + whisperType) + if sender != None: + whisper.setClickable(senderName, fromId) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + #base.chatAssistant.receivePlayerWhisperTypedChat(chatString, fromId) + + # animation + def setAnimMultiplier(self, value): + """setAnimMultiplier(self, float) + Setter for anim playback speed multiplier + """ + self.animMultiplier = value + + def getAnimMultiplier(self): + """ + Getter for anim playback speed multiplier + """ + return self.animMultiplier + + def enableRun(self): + self.accept("arrow_up", self.startRunWatch) + self.accept("arrow_up-up", self.stopRunWatch) + self.accept("control-arrow_up", self.startRunWatch) + self.accept("control-arrow_up-up", self.stopRunWatch) + self.accept("alt-arrow_up", self.startRunWatch) + self.accept("alt-arrow_up-up", self.stopRunWatch) + self.accept("shift-arrow_up", self.startRunWatch) + self.accept("shift-arrow_up-up", self.stopRunWatch) + + def disableRun(self): + self.ignore("arrow_up") + self.ignore("arrow_up-up") + self.ignore("control-arrow_up") + self.ignore("control-arrow_up-up") + self.ignore("alt-arrow_up") + self.ignore("alt-arrow_up-up") + self.ignore("shift-arrow_up") + self.ignore("shift-arrow_up-up") + + def startRunWatch(self): + def setRun(ignored): + messenger.send("running-on") + taskMgr.doMethodLater( + self.runTimeout, setRun, + self.uniqueName('runWatch')) + return Task.cont + + def stopRunWatch(self): + taskMgr.remove(self.uniqueName('runWatch')) + messenger.send("running-off") + return Task.cont + + def runSound(self): + self.soundWalk.stop() + base.playSfx(self.soundRun, looping = 1) + + def walkSound(self): + self.soundRun.stop() + base.playSfx(self.soundWalk, looping = 1) + + def stopSound(self): + self.soundRun.stop() + self.soundWalk.stop() + + def wakeUp(self): + # If we are watching to see if Toon falls asleep, there should be + # a callback set, which tells us we need to restart the task. + if (self.sleepCallback != None): + taskMgr.remove(self.uniqueName('sleepwatch')) + self.startSleepWatch(self.sleepCallback) + self.lastMoved = globalClock.getFrameTime() + if self.sleepFlag: + self.sleepFlag = 0 + + def gotoSleep(self): + if not self.sleepFlag: + self.b_setAnimState("Sleep", self.animMultiplier) + self.sleepFlag = 1 + + def forceGotoSleep(self): + # Sad toons don't sleep. + if (self.hp > 0): + self.sleepFlag = 0 + self.gotoSleep() + + def startSleepWatch(self, callback): + self.sleepCallback = callback + taskMgr.doMethodLater(self.sleepTimeout, callback, + self.uniqueName('sleepwatch')) + + def stopSleepWatch(self): + taskMgr.remove(self.uniqueName('sleepwatch')) + self.sleepCallback = None + + def startSleepSwimTest(self): + """ + Spawn a task to check for sleep, this is normally handled by trackAnimToSpeed for some reason + Sleepwatch appears to be a simple timeout for the sticker book + + """ + taskName = self.taskName("sleepSwimTest") + + # remove any old + taskMgr.remove(taskName) + # spawn the new task + task = Task.Task(self.sleepSwimTest) + + self.lastMoved = globalClock.getFrameTime() + self.lastState = None + self.lastAction = None + + self.sleepSwimTest(task) + taskMgr.add(self.sleepSwimTest, taskName, 35) + + def stopSleepSwimTest(self): + taskName = self.taskName("sleepSwimTest") + taskMgr.remove(taskName) + self.stopSound() + + def sleepSwimTest(self, task): + now = globalClock.getFrameTime() + speed, rotSpeed, slideSpeed = self.controlManager.getSpeeds() + if (speed != 0.0 or rotSpeed != 0.0 or inputState.isSet("jump")): + # did we just start moving? + if not self.swimmingFlag: + self.swimmingFlag = 1 + else: + # did we just stop moving? + if self.swimmingFlag: + self.swimmingFlag = 0 + #print("sleepTest speed %s slide %s jump %s moving %s hp %s time %s timeout %s" % (speed, rotSpeed, inputState.isSet("jump"), self.swimmingFlag, self.hp, now - self.lastMoved, self.swimTimeout)) + if (self.swimmingFlag or self.hp <= 0): + # The toon is moving or sad; it shouldn't be sleeping now. + self.wakeUp() + else: + # The toon is stationary. Should we go to sleep? + if not self.sleepFlag: + now = globalClock.getFrameTime() + if now - self.lastMoved > self.swimTimeout: + #self.gotoSleep() + self.swimTimeoutAction() + return Task.done + + + return Task.cont + + def swimTimeoutAction(self): + pass + + def trackAnimToSpeed(self, task): + #print("trackAnimToSpeed %s" % (random.random())) + speed, rotSpeed, slideSpeed = self.controlManager.getSpeeds() + + if (speed != 0.0 or rotSpeed != 0.0 or inputState.isSet("jump")): + # did we just start moving? + if not self.movingFlag: + self.movingFlag = 1 + + # stop looking around + self.stopLookAround() + else: + # did we just stop moving? + if self.movingFlag: + self.movingFlag = 0 + + # start looking around + self.startLookAround() + + if (self.movingFlag or self.hp <= 0): + # The toon is moving or sad; it shouldn't be sleeping now. + self.wakeUp() + else: + # The toon is stationary. Should we go to sleep? + if not self.sleepFlag: + now = globalClock.getFrameTime() + if now - self.lastMoved > self.sleepTimeout: + self.gotoSleep() + + state = None + if self.sleepFlag: + state = "Sleep" + elif self.hp > 0: + state = "Happy" + else: + state = "Sad" + + if state != self.lastState: + self.lastState = state + self.b_setAnimState(state, self.animMultiplier) + if state == "Sad": + self.setWalkSpeedSlow() + else: + self.setWalkSpeedNormal() + + if self.cheesyEffect == OTPGlobals.CEFlatProfile or \ + self.cheesyEffect == OTPGlobals.CEFlatPortrait: + # If one of the flat cheesy effects is enabled, rotate the + # toon slightly when we walks left or right so we can see + # him. A better solution might be to attach the camera to + # the toon with a rubber band, instead of parenting it + # rigidly to the toon, but this will do for now. + needH = None + if rotSpeed > 0.0: + needH = -10 + elif rotSpeed < 0.0: + needH = 10 + elif speed != 0.0: + needH = 0 + + if needH != None and self.lastNeedH != needH: + node = self.getGeomNode().getChild(0) + lerp = Sequence(LerpHprInterval(node, 0.5, Vec3(needH, 0, 0), + blendType = 'easeInOut'), + name = 'cheesy-lerp-hpr', + autoPause = 1) + lerp.start() + self.lastNeedH = needH + else: + self.lastNeedH = None + + action = self.setSpeed(speed, rotSpeed) + if action != self.lastAction: + self.lastAction = action + if self.emoteTrack: + self.emoteTrack.finish() + self.emoteTrack = None + if action == OTPGlobals.WALK_INDEX or action == OTPGlobals.REVERSE_INDEX: + self.walkSound() + elif action == OTPGlobals.RUN_INDEX: + self.runSound() + else: + self.stopSound() + + return Task.cont + + def hasTrackAnimToSpeed(self): + # Returns true if startTrackAnimToSpeed() has been called, and + # the task is running. + taskName = self.taskName("trackAnimToSpeed") + return taskMgr.hasTaskNamed(taskName) + + def startTrackAnimToSpeed(self): + """ + Spawn a task to match avatar animation with movement speed + + if speed < 0 -> play walk cycle backwards + if speed = 0 + if rotSpeed = 0 -> neutral cycle + else -> walk cycle + if speed > 0 and speed < runCutOff -> walk cycle + if speed >= runCutOff -> run cycle + """ + taskName = self.taskName("trackAnimToSpeed") + + # remove any old + taskMgr.remove(taskName) + # spawn the new task + task = Task.Task(self.trackAnimToSpeed) + + self.lastMoved = globalClock.getFrameTime() + self.lastState = None + self.lastAction = None + + self.trackAnimToSpeed(task) + taskMgr.add(self.trackAnimToSpeed, taskName, 35) + + def stopTrackAnimToSpeed(self): + taskName = self.taskName("trackAnimToSpeed") + taskMgr.remove(taskName) + self.stopSound() + + # chat methods + def startChat(self): + self.chatMgr.start() + # listen for outgoing chat messages + #self.accept("chatUpdate", self.b_setChat) + #self.accept("chatUpdateSC", self.b_setSC) + #self.accept("chatUpdateSCCustom", self.b_setSCCustom) + #self.accept("chatUpdateSCEmote", self.b_setSCEmote) + #self.accept("whisperUpdate", self.whisperTo) + #self.accept("whisperUpdateSC", self.whisperSCTo) + #self.accept("whisperUpdateSCCustom", self.whisperSCCustomTo) + #self.accept("whisperUpdateSCEmote", self.whisperSCEmoteTo) + self.accept(OTPGlobals.WhisperIncomingEvent, self.handlePlayerFriendWhisper) + self.accept(OTPGlobals.ThinkPosHotkey, self.thinkPos) + self.accept(OTPGlobals.PrintCamPosHotkey, self.printCamPos) + if self.__enableMarkerPlacement: + self.accept(OTPGlobals.PlaceMarkerHotkey, self.__placeMarker) + + def stopChat(self): + self.chatMgr.stop() + #self.ignore("chatUpdate") + #self.ignore("chatUpdateSC") + #self.ignore("chatUpdateSCCustom") + #self.ignore("chatUpdateSCEmote") + #self.ignore("whisperUpdate") + #self.ignore("whisperUpdateSC") + #self.ignore("whisperUpdateSCCustom") + #self.ignore("whisperUpdateSCEmote") + self.ignore(OTPGlobals.WhisperIncomingEvent) + self.ignore(OTPGlobals.ThinkPosHotkey) + self.ignore(OTPGlobals.PrintCamPosHotkey) + if self.__enableMarkerPlacement: + self.ignore(OTPGlobals.PlaceMarkerHotkey) + + def printCamPos(self): + # node = base.localAvatar + node = base.camera.getParent() + pos = base.cam.getPos(node) + hpr = base.cam.getHpr(node) + print 'cam pos = ',`pos`,', cam hpr = ',`hpr` + + def d_broadcastPositionNow(self): + """ + Forces a broadcast of the toon's current position. Normally + this is called immediately before calling + setParent(OTPGlobals.SPRender), to ensure the remote + clients don't observe this toon momentarily in the wrong place + when he appears. + """ + self.d_clearSmoothing() + self.d_broadcastPosHpr() + + def travCollisionsLOS(self, n = None): + if n == None: + n = self.__geom + self.ccTrav.traverse(n) + + def travCollisionsFloor(self, n = None): + if n == None: + n = self.__geom + self.ccTravFloor.traverse(n) + + def travCollisionsPusher(self, n = None): + if n == None: + n = self.__geom + self.ccPusherTrav.traverse(n) + + + def __friendOnline(self, doId, commonChatFlags=0, whitelistChatFlags = 0): + """ + Called when a friend comes online, this should report this + news to the user. + """ + # The first "online" message we get immediately after adding a + # new friend is suspect. + friend = base.cr.identifyFriend(doId) + if (friend != None) and hasattr(friend,'setCommonAndWhitelistChatFlags'): + friend.setCommonAndWhitelistChatFlags(commonChatFlags, whitelistChatFlags) + + if self.oldFriendsList != None: + now = globalClock.getFrameTime() + elapsed = now - self.timeFriendsListChanged + if elapsed < 10.0 and self.oldFriendsList.count(doId) == 0: + # Yep, this is a friend we just added. Don't report + # the online message. But do report future messages. + self.oldFriendsList.append(doId) + return + + if friend != None: + self.setSystemMessage(doId, OTPLocalizer.WhisperFriendComingOnline % (friend.getName())) + + def __friendOffline(self, doId): + """ + Called when a friend goes offline, this should report this + news to the user. + """ + friend = base.cr.identifyFriend(doId) + if friend != None: + self.setSystemMessage(0, OTPLocalizer.WhisperFriendLoggedOut % (friend.getName())) + + def __playerOnline(self, playerId): + playerInfo = base.cr.playerFriendsManager.playerId2Info[playerId] + if playerInfo: + self.setSystemMessage(playerId, OTPLocalizer.WhisperPlayerOnline % (playerInfo.playerName, playerInfo.location)) + + def __playerOffline(self, playerId): + playerInfo = base.cr.playerFriendsManager.playerId2Info[playerId] + if playerInfo: + self.setSystemMessage(playerId, OTPLocalizer.WhisperPlayerOffline % (playerInfo.playerName)) + + def clickedWhisper(self, doId, isPlayer = None): + """ + Called from the C++ code when the user clicks on a whisper + message from a friend. + """ + if not isPlayer: + friend = base.cr.identifyFriend(doId) + if friend != None: + # We request an avatar panel *and* simultaneously open up + # the whisper-to panel. We want the avatar panel to allow + # going to the friend; but sometimes (for instance, in a + # battle) the avatar panel won't come up, so we also open + # the whisper-to panel just in case. + messenger.send("clickedNametag", [friend]) + self.chatMgr.whisperTo(friend.getName(), doId) + else: + friend = base.cr.playerFriendsManager.getFriendInfo(doId) + if friend: + messenger.send("clickedNametagPlayer", [None, doId]) + self.chatMgr.whisperTo(friend.getName(), None, doId) + + + + # this is here to ensure that the correct overloaded method is called + def d_setParent(self, parentToken): + DistributedSmoothNode.DistributedSmoothNode.d_setParent( + self, parentToken) + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message) + + def handlePlayerFriendWhisper(self, playerId, charMessage): + """ + handle player friend message. + """ + print("handlePlayerFriendWhisper") + self.displayWhisperPlayer(playerId, charMessage, WhisperPopup.WTNormal) + + def canChat(self): + """ + Overrided by derived class + """ + assert(0) + return 0 + diff --git a/otp/src/avatar/PlayerBase.py b/otp/src/avatar/PlayerBase.py new file mode 100644 index 0000000..e27ddf7 --- /dev/null +++ b/otp/src/avatar/PlayerBase.py @@ -0,0 +1,23 @@ + +class PlayerBase: + # player code shared by AI & client + + def __init__(self): + self.gmState = False + pass + + def atLocation(self, locationId): + return True + + def getLocation(self): + # return a list of locationIds, starting from general location + # (i.e. 'Pirates') down to specific location (i.e. jungle ID) + return [] + + def setAsGM(self, state): + """ Toggle GM privilages """ + self.gmState = state + + def isGM(self): + return self.gmState + diff --git a/otp/src/avatar/PositionExaminer.py b/otp/src/avatar/PositionExaminer.py new file mode 100644 index 0000000..8feb14e --- /dev/null +++ b/otp/src/avatar/PositionExaminer.py @@ -0,0 +1,159 @@ + +from pandac.PandaModules import * +from direct.showbase.DirectObject import DirectObject +from otp.otpbase import OTPGlobals + +class PositionExaminer(DirectObject, NodePath): + """ + This class defines an object that can be used to examine a point in + space for suitability for standing on. It's used, for instance, + to choose a particular point to Go to when you Goto a friend. + + A valid destination point is one that (a) has a ground whose + height is not too far from our target height, (b) is not already + occupied, and (c) is not behind a wall. + """ + + def __init__(self): + try: + self.__initialized + return + except: + self.__initialized = 1 + + # initialize our NodePath essence + NodePath.__init__(self, hidden.attachNewNode('PositionExaminer')) + + # Set up the collison ray. This is a ray cast from the head + # height down to detect floor polygons. It will tell us the + # height of the floor at our proposed position. + self.cRay = CollisionRay(0.0, 0.0, 6.0, 0.0, 0.0, -1.0) + self.cRayNode = CollisionNode('cRayNode') + self.cRayNode.addSolid(self.cRay) + self.cRayNodePath = self.attachNewNode(self.cRayNode) + self.cRayNodePath.hide() + self.cRayBitMask = OTPGlobals.FloorBitmask + self.cRayNode.setFromCollideMask(self.cRayBitMask) + self.cRayNode.setIntoCollideMask(BitMask32.allOff()) + + # Set up the collision sphere. This is a sphere on the ground + # that exactly matches the position and size of an avatar's + # sphere. It will tell us if our proposed position is occupied + # by anyone else. + self.cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.5) + self.cSphereNode = CollisionNode('cSphereNode') + self.cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = self.attachNewNode(self.cSphereNode) + self.cSphereNodePath.hide() + self.cSphereBitMask = OTPGlobals.WallBitmask + self.cSphereNode.setFromCollideMask(self.cSphereBitMask) + self.cSphereNode.setIntoCollideMask(BitMask32.allOff()) + + # Set up the camera obstruction test line segment. This is a + # line segment from the proposed position point to our target + # position, to ensure that we are not behind a wall. + self.ccLine = CollisionSegment(0.0, 0.0, 0.0, 1.0, 0.0, 0.0) + self.ccLineNode = CollisionNode('ccLineNode') + self.ccLineNode.addSolid(self.ccLine) + self.ccLineNodePath = self.attachNewNode(self.ccLineNode) + self.ccLineNodePath.hide() + self.ccLineBitMask = OTPGlobals.CameraBitmask + self.ccLineNode.setFromCollideMask(self.ccLineBitMask) + self.ccLineNode.setIntoCollideMask(BitMask32.allOff()) + + # Now the gnarly part. Each of the above colliders must have + # a separate traverser, so we can independently activate each + # one and query the results. + self.cRayTrav = CollisionTraverser("PositionExaminer.cRayTrav") + self.cRayTrav.setRespectPrevTransform(False) + self.cRayQueue = CollisionHandlerQueue() + self.cRayTrav.addCollider(self.cRayNodePath, self.cRayQueue) + + self.cSphereTrav = CollisionTraverser("PositionExaminer.cSphereTrav") + self.cSphereTrav.setRespectPrevTransform(False) + self.cSphereQueue = CollisionHandlerQueue() + self.cSphereTrav.addCollider(self.cSphereNodePath, self.cSphereQueue) + + self.ccLineTrav = CollisionTraverser("PositionExaminer.ccLineTrav") + self.ccLineTrav.setRespectPrevTransform(False) + self.ccLineQueue = CollisionHandlerQueue() + self.ccLineTrav.addCollider(self.ccLineNodePath, self.ccLineQueue) + + def delete(self): + del self.cRay + del self.cRayNode + self.cRayNodePath.removeNode() + del self.cRayNodePath + + del self.cSphere + del self.cSphereNode + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + + del self.ccLine + del self.ccLineNode + self.ccLineNodePath.removeNode() + del self.ccLineNodePath + + del self.cRayTrav + del self.cRayQueue + + del self.cSphereTrav + del self.cSphereQueue + + del self.ccLineTrav + del self.ccLineQueue + + + def consider(self, node, pos, eyeHeight): + """consider(self, NodePath node, Point3 pos, eyeHeight) + + Considers the indicated point, relative to the given NodePath. + The point must have a floor polygon that's within a foot or + two of the NodePath's origin, there must be no one standing + near the point, and it must have a clear line-of-sight to the + NodePath's origin. + + Returns the actual point to stand if all these conditions are + met, or None if one of them fails. + """ + self.reparentTo(node) + self.setPos(pos) + + result = None + + # First, check that we have a good floor here. + self.cRayTrav.traverse(render) + if self.cRayQueue.getNumEntries() != 0: + + # Ok, we have a floor. Choose the highest of the possibly + # several floors we're over. + + self.cRayQueue.sortEntries() + floorPoint = self.cRayQueue.getEntry(0).getSurfacePoint(self.cRayNodePath) + + if abs(floorPoint[2]) <= 4.0: + # And the floor is not too high or too low. + pos += floorPoint + self.setPos(pos) + + # Now check that no one else is standing right here. + self.cSphereTrav.traverse(render) + if self.cSphereQueue.getNumEntries() == 0: + # No one's standing there, and there's no wall. + + # Now check that we have a clear line-of-sight. + self.ccLine.setPointA(0, 0, eyeHeight) + self.ccLine.setPointB(-pos[0], -pos[1], eyeHeight) + self.ccLineTrav.traverse(render) + if self.ccLineQueue.getNumEntries() == 0: + # We're not behind a wall or anything. + result = pos + + self.reparentTo(hidden) + self.cRayQueue.clearEntries() + self.cSphereQueue.clearEntries() + self.ccLineQueue.clearEntries() + + return result + diff --git a/otp/src/avatar/ShadowCaster.py b/otp/src/avatar/ShadowCaster.py new file mode 100644 index 0000000..772bdb0 --- /dev/null +++ b/otp/src/avatar/ShadowCaster.py @@ -0,0 +1,198 @@ + +from pandac.PandaModules import * +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +from direct.showbase.ShadowPlacer import ShadowPlacer +from otp.otpbase import OTPGlobals + +# This global variable will be set true or false according to whether +# all avatar's drop shadows should be made visible, by the +# TimeOfDayManager (which currently manages projected shadows). +# Always change its state via this function, instead of monkeying with +# it directly. +globalDropShadowFlag = 1 +def setGlobalDropShadowFlag(flag): + global globalDropShadowFlag + if flag != globalDropShadowFlag: + globalDropShadowFlag = flag + messenger.send('globalDropShadowFlagChanged') + +# A similar trick to control the global gray level of the drop shadows. +globalDropShadowGrayLevel = 0.5 +def setGlobalDropShadowGrayLevel(grayLevel): + global globalDropShadowGrayLevel + if grayLevel != globalDropShadowGrayLevel: + globalDropShadowGrayLevel = grayLevel + messenger.send('globalDropShadowGrayLevelChanged') + +# I made this inherit from DirectObject so that non-distributed things can cast shadows + +class ShadowCaster: + + notify = DirectNotifyGlobal.directNotify.newCategory("ShadowCaster") + #notify.setDebug(1) + + def __init__(self, squareShadow = False): + assert self.notify.debugStateCall(self) + # some shadow initialization stuff + if squareShadow: + self.shadowFileName = "phase_3/models/props/square_drop_shadow" + else: + self.shadowFileName = "phase_3/models/props/drop_shadow" + + self.dropShadow = None + self.shadowPlacer = None + self.activeShadow = 0 + self.wantsActive = 1 + self.storedActiveState = 0 + + # Only create these hooks if we're running a game that cares + # about them. + if hasattr(base,"wantDynamicShadows") and base.wantDynamicShadows: + messenger.accept('globalDropShadowFlagChanged', self, self.__globalDropShadowFlagChanged) + messenger.accept('globalDropShadowGrayLevelChanged', self, self.__globalDropShadowGrayLevelChanged) + + def delete(self): + assert self.notify.debugStateCall(self) + + # Only remove these hooks if we're running a game that cares + # about them. + if hasattr(base,"wantDynamicShadows") and base.wantDynamicShadows: + messenger.ignore('globalDropShadowFlagChanged', self) + messenger.ignore('globalDropShadowGrayLevelChanged', self) + self.deleteDropShadow() + self.shadowJoint = None + + def initializeDropShadow(self, hasGeomNode=True): + """ + Load up and arrange the drop shadow + """ + assert self.notify.debugStateCall(self) + # First, protect this function from being called twice by + # removing the old ones first. + self.deleteDropShadow() + + # This will be used by the shadow system to identify things + # that might want to have projected shadows drawn. + if hasGeomNode: + self.getGeomNode().setTag('cam', 'caster') + + # make the object float above the shadow slightly + # not necessarily a good idea for all avatars + #self.getGeomNode().setZ(0.025) + + # load and prep the drop shadow + dropShadow = loader.loadModel(self.shadowFileName) + dropShadow.setScale(0.4) # Slightly smaller to compensate for billboard + + dropShadow.flattenMedium() + dropShadow.setBillboardAxis(2) # slide the shadow towards the camera + dropShadow.setColor(0.0, 0.0, 0.0, globalDropShadowGrayLevel, 1) # override of 1 to prevent avatar.setColor() from affecting shadows. + self.shadowPlacer = ShadowPlacer( + base.shadowTrav, dropShadow, + OTPGlobals.WallBitmask, OTPGlobals.FloorBitmask) + self.dropShadow = dropShadow + if not globalDropShadowFlag: + self.dropShadow.hide() + if self.getShadowJoint(): + dropShadow.reparentTo(self.getShadowJoint()) + else: + self.dropShadow.hide() + + # Set the state of the shadow placers (in case someone set the + # value before now): + self.setActiveShadow(self.wantsActive) + + self.__globalDropShadowFlagChanged() + self.__globalDropShadowGrayLevelChanged() + + def update(self): + """This method is meant to be overriden.""" + # Toontown doesn't have self.update() for all shadowcasters + # but initializeDropShadow calls it, so this prevents a crash. + pass + + def deleteDropShadow(self): + """ + Lose the drop shadows + """ + assert self.notify.debugStateCall(self) + if self.shadowPlacer: + self.shadowPlacer.delete() + self.shadowPlacer = None + + if self.dropShadow: + self.dropShadow.removeNode() + self.dropShadow = None + + def setActiveShadow(self, isActive=1): + """ + Turn the shadow placement on or off. + """ + assert self.notify.debugStateCall(self) + + isActive = isActive and self.wantsActive + if(not globalDropShadowFlag): + self.storedActiveState = isActive + # changed logic to prevent crash (test remark 13203) - grw + if self.shadowPlacer != None: + isActive = isActive and globalDropShadowFlag + if self.activeShadow != isActive: + self.activeShadow = isActive + if isActive: + self.shadowPlacer.on() + else: + self.shadowPlacer.off() + + + def setShadowHeight(self, shadowHeight): + """ + Places the shadow at a particular height below the avatar (in + effect, asserting that the avatar is shadowHeight feet above + the ground). + + This is only useful when the active shadow is disabled via + setActiveShadow(0). + """ + assert self.notify.debugStateCall(self) + if self.dropShadow: + self.dropShadow.setZ(-shadowHeight) + + def getShadowJoint(self): + assert self.notify.debugStateCall(self) + if hasattr(self, "shadowJoint"): + return self.shadowJoint + shadowJoint = self.find('**/attachShadow') + if shadowJoint.isEmpty(): + # We make a fresh NodePath that refers to the same node as + # self, rather than assigning self directly--this will + # prevent a cyclic Python reference. + self.shadowJoint = NodePath(self) + else: + self.shadowJoint = shadowJoint + return self.shadowJoint + + def hideShadow(self): + assert self.notify.debugStateCall(self) + self.dropShadow.hide() + + def showShadow(self): + assert self.notify.debugStateCall(self) + if not globalDropShadowFlag: + self.dropShadow.hide() + else: + self.dropShadow.show() + + def __globalDropShadowFlagChanged(self): + if (self.dropShadow != None): + if(globalDropShadowFlag == 0): + if(self.activeShadow == 1): + self.storedActiveState = 1 + self.setActiveShadow(0) + elif(self.activeShadow == 0): + self.setActiveShadow(1) + self.showShadow() + + def __globalDropShadowGrayLevelChanged(self): + if (self.dropShadow != None): + self.dropShadow.setColor(0.0, 0.0, 0.0, globalDropShadowGrayLevel, 1) diff --git a/otp/src/avatar/Sources.pp b/otp/src/avatar/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/avatar/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/avatar/__init__.py b/otp/src/avatar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/chat/.cvsignore b/otp/src/chat/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/chat/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/chat/ChatGarbler.py b/otp/src/chat/ChatGarbler.py new file mode 100644 index 0000000..86377a1 --- /dev/null +++ b/otp/src/chat/ChatGarbler.py @@ -0,0 +1,56 @@ +"""ChatGarbler module: conatins the ChatGarbler class""" + +import string +#import whrandom +import random +from otp.otpbase import OTPLocalizer + +class ChatGarbler: + """ChatGarbler class: contains methods to convert chat messages + to animal sounds""" + + def garble(self, avatar, message): + """garble(self, Avatar, string) + Replace a chat message with a series of animal sounds + based on the toon's animal type + Algorithm completely disregards original message to + prohibit any sort of meaningful communication + """ + newMessage = "" + + numWords = random.randint(1, 7) + + wordlist = OTPLocalizer.ChatGarblerDefault + + for i in range(1, numWords+1): + wordIndex = random.randint(0, len(wordlist)-1) + newMessage = newMessage + wordlist[wordIndex] + if (i < numWords): + newMessage = newMessage + " " + + return newMessage + + def garbleSingle(self, avatar, message): + """garble(self, Avatar, string) + Replace a chat message with a series of animal sounds + based on the toon's animal type + Algorithm completely disregards original message to + prohibit any sort of meaningful communication + """ + newMessage = "" + + numWords = 1 + + wordlist = OTPLocalizer.ChatGarblerDefault + + for i in range(1, numWords+1): + wordIndex = random.randint(0, len(wordlist)-1) + newMessage = newMessage + wordlist[wordIndex] + if (i < numWords): + newMessage = newMessage + " " + + return newMessage + + + + diff --git a/otp/src/chat/ChatGlobals.py b/otp/src/chat/ChatGlobals.py new file mode 100644 index 0000000..4e310f9 --- /dev/null +++ b/otp/src/chat/ChatGlobals.py @@ -0,0 +1,77 @@ +import string + +NORMAL_CHAT = 1 +WHISPER_CHAT = 2 +GUILD_CHAT = 3 +CREW_CHAT = 4 +SHIPPVP_CHAT = 5 + +# ERROR CODES +ERROR_NONE = None +ERROR_NO_OPEN_CHAT = 1 +ERROR_NOT_FRIENDS = 2 +ERROR_NO_RECEIVER = 3 +ERROR_NO_GUILD_CHAT = 4 +ERROR_NO_CREW_CHAT = 5 +ERROR_NO_SHIPPVP_CHAT = 6 + +#CHAT TYPES +TYPEDCHAT = 0 +SPEEDCHAT_NORMAL = 1 +SPEEDCHAT_EMOTE = 2 +SPEEDCHAT_CUSTOM = 3 +SYSTEMCHAT = 4 +GAMECHAT = 5 +GUILDCHAT = 6 +PARTYCHAT = 7 +SPEEDCHAT_QUEST = 8 +FRIEND_UPDATE = 9 +CREW_UPDATE = 10 +GUILD_UPDATE = 11 +AVATAR_UNAVAILABLE = 12 +SHIPPVPCHAT = 13 +GMCHAT = 14 + +# the events are hierarchical; when a +# speedchat msg is picked, for instance, the +# following events will be sent: +# 'ChatEvent', 'SCChatEvent' +ChatEvent = 'ChatEvent' +NormalChatEvent = 'NormalChatEvent' +SCChatEvent = 'SCChatEvent' +SCCustomChatEvent = 'SCCustomChatEvent' +SCEmoteChatEvent = 'SCEmoteChatEvent' +SCQuestEvent = 'SCQuestEvent' + +OnScreen = 0 +OffScreen = 1 +Thought = 2 +ThoughtPrefix = '.' + +# thought methods +def isThought(message): + """ + message is a string. + + Return 1 if the given string contains the thought prefix, + Return 0 otherwise + """ + if (len(message) == 0): + # empty string cannot be a thought + return 0 + elif (string.find(message, ThoughtPrefix, 0, + len(ThoughtPrefix)) >= 0): + return 1 + else: + return 0 + +def removeThoughtPrefix(message): + """ + message is a string. + + Return the string with the thought prefix removed + """ + if (isThought(message)): + return message[len(ThoughtPrefix):] + else: + return message diff --git a/otp/src/chat/ChatInputNormal.py b/otp/src/chat/ChatInputNormal.py new file mode 100644 index 0000000..7ef5c2d --- /dev/null +++ b/otp/src/chat/ChatInputNormal.py @@ -0,0 +1,204 @@ +"""ChatInputNormal module: contains the ChatInputNormal class""" + +from direct.showbase import DirectObject +from otp.otpbase import OTPGlobals +import sys +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from otp.otpbase import OTPLocalizer + +class ChatInputNormal(DirectObject.DirectObject): + """ChatInputNormal class: controls the chat input bubble, and handles + chat message construction""" + + # This will hold the local namespace we evaluate '>chat' messages + # within. + ExecNamespace = None + + # special methods + def __init__(self, chatMgr): + self.chatMgr = chatMgr + + self.normalPos = Vec3(-1.083, 0, 0.804) + self.whisperPos = Vec3(0.0, 0, 0.71) + + self.whisperAvatarName = None + self.whisperAvatarId = None + self.toPlayer = 0 + + wantHistory = 0 + if __dev__: + wantHistory = 1 + self.wantHistory = base.config.GetBool('want-chat-history', wantHistory) + self.history = [''] + self.historySize = base.config.GetInt('chat-history-size', 10) + self.historyIndex = 0 + + # It is up to a derived class, like TTChatInputNormal, to + # define self.chatFrame, self.chatButton, self.cancelButton, + # and self.whisperLabel. + + + def typeCallback(self, extraArgs): + messenger.send('enterNormalChat') + + def delete(self): + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + self.chatFrame.destroy() + del self.chatFrame + del self.chatButton + del self.cancelButton + del self.chatEntry + del self.whisperLabel + del self.chatMgr + + def activateByData(self, whisperAvatarId = None, toPlayer = 0): + self.toPlayer = toPlayer + self.whisperAvatarId = whisperAvatarId + self.whisperAvatarName = base.talkAssistant.findName(self.whisperAvatarId, self.toPlayer) + if self.whisperAvatarId: + self.chatFrame.setPos(self.whisperPos) + self.whisperLabel["text"] = (OTPLocalizer.ChatInputWhisperLabel % + (self.whisperAvatarName)) + self.whisperLabel.show() + else: + self.chatFrame.setPos(self.normalPos) + self.whisperLabel.hide() + self.chatEntry['focus'] = 1 + self.chatFrame.show() + + if self.wantHistory: + self.accept('arrow_up-up', self.getPrevHistory) + self.accept('arrow_down-up', self.getNextHistory) + + def deactivate(self): + self.chatEntry.set("") + self.chatEntry['focus'] = 0 + self.chatFrame.hide() + self.whisperLabel.hide() + base.win.closeIme() + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + + def checkForOverRide(self): + #ChatInputNormal likes to intercept other direct entries + #too much was hard wired to the chatManagar so I'm adding a final stage override - JML + return False + + def sendChat(self, text): + """ + Send the text from the entry + """ + if self.checkForOverRide(): + self.chatEntry.enterText("") + return + # Done for now, go away + self.deactivate() + self.chatMgr.fsm.request("mainMenu") + + # Filter out empty string + if text: + if self.toPlayer: + if self.whisperAvatarId: + #base.cr.playerFriendsManager.sendWhisper(self.whisperAvatarId, text) + #base.chatAssistant.sendPlayerWhisperTypedChat(text, self.whisperAvatarId) + self.whisperAvatarName = None + self.whisperAvatarId = None + self.toPlayer = 0 + + elif self.whisperAvatarId: + self.chatMgr.sendWhisperString(text, self.whisperAvatarId) + self.whisperAvatarName = None + self.whisperAvatarId = None + else: + if self.chatMgr.execChat: + # Exec a python command + if (text[0] == '>'): + text = self.__execMessage(text[1:]) + base.localAvatar.setChatAbsolute(text, CFSpeech | CFTimeout) + return + + base.talkAssistant.sendOpenTalk(text) + + if self.wantHistory: + self.addToHistory(text) + + def chatOverflow(self, overflowText): + """ + When the user types too many lines of text, an event gets thrown + which calls this function. Right now it just sends the text just + as if you hit return to complete the sentence. + """ + self.sendChat(self.chatEntry.get()) + + def __execMessage(self, message): + if not ChatInputNormal.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + ChatInputNormal.ExecNamespace = { } + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputNormal.ExecNamespace as + # the local namespace. + try: + return str(eval(message, globals(), ChatInputNormal.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), ChatInputNormal.ExecNamespace + return 'ok' + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + + # button event handlers + def cancelButtonPressed(self): + self.chatEntry.set("") + self.chatMgr.fsm.request("mainMenu") + + def chatButtonPressed(self): + self.sendChat(self.chatEntry.get()) + + def importExecNamespace(self): + # Derived classes should take advantage of this hook to import + # useful variables into the chat namespace for developer + # access. + pass + + def addToHistory(self, text): + self.history = [text] + self.history[:self.historySize-1] + self.historyIndex = 0 + + def getPrevHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex += 1 + self.historyIndex %= len(self.history) + + def getNextHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex -= 1 + self.historyIndex %= len(self.history) + + def setPos(self, posX, posY = None, posZ = None): + if posX and posY and posZ: + self.chatFrame.setPos(posX,posY,posZ) + else: + self.chatFrame.setPos(posX) + + + diff --git a/otp/src/chat/ChatInputTyped.py b/otp/src/chat/ChatInputTyped.py new file mode 100644 index 0000000..999138a --- /dev/null +++ b/otp/src/chat/ChatInputTyped.py @@ -0,0 +1,240 @@ +"""ChatInputTyped module: contains the ChatInputTyped class""" + +from direct.showbase import DirectObject +from otp.otpbase import OTPGlobals +import sys +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from otp.otpbase import OTPLocalizer + +class ChatInputTyped(DirectObject.DirectObject): + """ChatInputTyped class: controls the chat input bubble, and handles + chat message construction""" + + # This will hold the local namespace we evaluate '>chat' messages + # within. + ExecNamespace = None + + # special methods + def __init__(self, mainEntry = 0): + self.whisperName = None + self.whisperId = None + self.toPlayer = 0 + self.mainEntry = mainEntry + + wantHistory = 0 + if __dev__: + wantHistory = 1 + self.wantHistory = base.config.GetBool('want-chat-history', wantHistory) + self.history = [''] + self.historySize = base.config.GetInt('chat-history-size', 10) + self.historyIndex = 0 + + # It is up to a derived class, like ChatInputTyped, to + # define self.chatFrame, self.chatButton, self.cancelButton, + # and self.whisperLabel. + + + def typeCallback(self, extraArgs): + self.activate() + + def delete(self): + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + self.chatFrame.destroy() + del self.chatFrame + del self.chatButton + del self.cancelButton + del self.chatEntry + del self.whisperLabel + del self.chatMgr + + + def show(self, whisperId = None, toPlayer = 0): + self.toPlayer = toPlayer + self.whisperId = whisperId + self.whisperName = None + + if self.whisperId: + self.whisperName = base.talkAssistant.findName(whisperId, toPlayer) + if hasattr(self, "whisperPos"): + self.chatFrame.setPos(self.whisperPos) + self.whisperLabel["text"] = (OTPLocalizer.ChatInputWhisperLabel % + (self.whisperName)) + self.whisperLabel.show() + else: + if hasattr(self, "normalPos"): + self.chatFrame.setPos(self.normalPos) + self.whisperLabel.hide() + + self.chatEntry['focus'] = 1 + self.chatEntry.set("") + self.chatFrame.show() + self.chatEntry.show() + self.cancelButton.show() + self.typedChatButton.hide() + self.typedChatBar.hide() + + if self.wantHistory: + self.accept('arrow_up-up', self.getPrevHistory) + self.accept('arrow_down-up', self.getNextHistory) + + def hide(self): + #import pdb; pdb.set_trace() + self.chatEntry.set("") + self.chatEntry['focus'] = 0 + self.chatFrame.hide() + self.chatEntry.hide() + self.cancelButton.hide() + self.typedChatButton.show() + self.typedChatBar.show() + #base.win.closeIme() + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + + def activate(self): + self.chatEntry.set("") + self.chatEntry['focus'] = 1 + self.chatFrame.show() + self.chatEntry.show() + self.cancelButton.show() + self.typedChatButton.hide() + self.typedChatBar.hide() + if self.whisperId: + print("have id") + if self.toPlayer: + if not base.talkAssistant.checkWhisperTypedChatPlayer(self.whisperId): + messenger.send("Chat-Failed player typed chat test") + self.deactivate() + else: + if not base.talkAssistant.checkWhisperTypedChatAvatar(self.whisperId): + messenger.send("Chat-Failed avatar typed chat test") + self.deactivate() + else: + if not base.talkAssistant.checkOpenTypedChat(): + messenger.send("Chat-Failed open typed chat test") + self.deactivate() + + + + + def deactivate(self): + self.chatEntry.set("") + self.chatEntry['focus'] = 0 + self.chatFrame.show() + self.chatEntry.hide() + self.cancelButton.hide() + self.typedChatButton.show() + self.typedChatBar.show() + + + + def sendChat(self, text): + """ + Send the text from the entry + """ + # Done for now, go away + self.deactivate() + #self.chatMgr.fsm.request("mainMenu") + #print("type send chat id %s toplayer %s" % (self.whisperId, self.toPlayer)) + + # Filter out empty string + if text: + if self.toPlayer: + if self.whisperId: + #base.chatAssistant.sendPlayerWhisperTypedChat(text, self.whisperId) + #self.whisperName = None + #self.whisperId = None + #self.toPlayer = 0 + pass + + elif self.whisperId: + #base.chatAssistant.sendAvatarWhisperTypedChat(text, self.whisperId) + #self.whisperName = None + #self.whisperId = None + pass + elif base.config.GetBool("exec-chat", 0) and (text[0] == '>'): + # Exec a python command + text = self.__execMessage(text[1:]) + base.localAvatar.setChatAbsolute(text, CFSpeech | CFTimeout) + return + else: + #self.chatMgr.sendChatString(text) + base.talkAssistant.sendOpenTalk(text) + + if self.wantHistory: + self.addToHistory(text) + self.chatEntry.set("") + + def chatOverflow(self, overflowText): + """ + When the user types too many lines of text, an event gets thrown + which calls this function. Right now it just sends the text just + as if you hit return to complete the sentence. + """ + self.sendChat(self.chatEntry.get()) + + def __execMessage(self, message): + if not ChatInputTyped.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + ChatInputTyped.ExecNamespace = { } + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputTyped.ExecNamespace as + # the local namespace. + try: + return str(eval(message, globals(), ChatInputTyped.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), ChatInputTyped.ExecNamespace + return 'ok' + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + + # button event handlers + def cancelButtonPressed(self): + self.chatEntry.set("") + self.deactivate() + #self.chatMgr.fsm.request("mainMenu") + + def chatButtonPressed(self): + self.sendChat(self.chatEntry.get()) + + def importExecNamespace(self): + # Derived classes should take advantage of this hook to import + # useful variables into the chat namespace for developer + # access. + pass + + def addToHistory(self, text): + self.history = [text] + self.history[:self.historySize-1] + self.historyIndex = 0 + + def getPrevHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex += 1 + self.historyIndex %= len(self.history) + + def getNextHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex -= 1 + self.historyIndex %= len(self.history) + + diff --git a/otp/src/chat/ChatInputWhiteList.py b/otp/src/chat/ChatInputWhiteList.py new file mode 100644 index 0000000..b9f8635 --- /dev/null +++ b/otp/src/chat/ChatInputWhiteList.py @@ -0,0 +1,382 @@ +from direct.fsm import FSM +from otp.otpbase import OTPGlobals +import sys +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from otp.otpbase import OTPLocalizer +from direct.task import Task +from otp.chat.ChatInputTyped import ChatInputTyped + +class ChatInputWhiteList(FSM.FSM, DirectEntry): + notify = DirectNotifyGlobal.directNotify.newCategory("ChatInputWhiteList") + + # This will hold the local namespace we evaluate '>chat' messages + # within. + ExecNamespace = None + + # special methods + def __init__(self, parent = None, **kw): + FSM.FSM.__init__(self, 'ChatInputWhiteList') + optiondefs = ( + ('parent', parent, None), + ('relief', DGG.SUNKEN, None), + ('text_scale', 0.03, None), + ('frameSize',(-0.2, 25.3, -0.5, 1.2), None), + ('borderWidth', (0.003, 0.003), None), + ('frameColor', (0.9, 0.9, 0.85, 0.8), None), + ('entryFont', OTPGlobals.getInterfaceFont(), None), + ('width', 25, None), + ('numLines', 1, None), + ('cursorKeys', 1, None), + ('backgroundFocus', 0, None), + ('suppressKeys', 1, None), + ('suppressMouse', 1, None), + ('command', self.sendChat, None), + ('failedCommand', self.sendFailed, None), + ('focus', 0, None), + ('text', '', None), + ) + self.defineoptions(kw, optiondefs) + DirectEntry.__init__(self, parent = parent, **kw) + self.initialiseoptions(ChatInputWhiteList) + + self.whisperId = None + #self.bind(DGG.OVERFLOW, self.chatOverflow) + + wantHistory = 0 + if __dev__: + wantHistory = 1 + self.wantHistory = base.config.GetBool('want-chat-history', wantHistory) + self.history = [''] + self.historySize = base.config.GetInt('chat-history-size', 10) + self.historyIndex = 0 + + self.whiteList = None + + self.active = 0 + self.autoOff = 0 + + self.alwaysSubmit = False + + from direct.gui import DirectGuiGlobals + self.bind(DirectGuiGlobals.TYPE,self.applyFilter) + self.bind(DirectGuiGlobals.ERASE,self.applyFilter) + + tpMgr = TextPropertiesManager.getGlobalPtr() + Red = tpMgr.getProperties('red') + Red.setTextColor(1.0,0.0,0.0,1) + tpMgr.setProperties('WLRed',Red) + del tpMgr + + self.origFrameColor = self['frameColor'] + self.origTextScale = self['text_scale'] + self.origFrameSize = self['frameSize'] + + def delete(self): + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + + + def requestMode(self, mode, *args): + """ + This is done so we can provide some symettry with + the PChatInputSpeedChat. + """ + self.request(mode, *args) + + def defaultFilter(self, request, *args): + if request == 'AllChat': + #if not base.chatAssistant.checkOpenSpeedChat(): + # messenger.send("Chat-Failed open typed chat test") + # return None + pass + elif request == 'PlayerWhisper': + if not base.talkAssistant.checkWhisperSpeedChatPlayer(self.whisperId): + messenger.send("Chat-Failed player typed chat test") + return None + elif request == 'AvatarWhisper': + if not base.talkAssistant.checkWhisperSpeedChatAvatar(self.whisperId): + messenger.send("Chat-Failed avatar typed chat test") + return None + return FSM.FSM.defaultFilter(self, request, *args) + + + + + def enterOff(self): + self.deactivate() + + def exitOff(self): + self.activate() + + def enterAllChat(self): + self['focus'] = 1 + self.show() + + def exitAllChat(self): + pass + + def enterGuildChat(self): + self['focus'] = 1 + self.show() + + def exitGuildChat(self): + pass + + def enterCrewChat(self): + self['focus'] = 1 + self.show() + + def exitCrewChat(self): + pass + + def enterShipPVPChat(self): + self['focus'] = 1 + self.show() + + def exitShipPVPChat(self): + pass + + def enterPlayerWhisper(self, whisperId): + self.tempText = self.get() + self.activate() + self.whisperId = whisperId + + def exitPlayerWhisper(self): + self.set(self.tempText) + self.whisperId = None + + def enterAvatarWhisper(self, whisperId): + self.tempText = self.get() + self.activate() + self.whisperId = whisperId + + def exitAvatarWhisper(self): + self.set(self.tempText) + self.whisperId = None + + def activate(self): + self.set("") + self['focus'] = 1 + self.show() + self.active = 1 + self.guiItem.setAcceptEnabled(True) + self.accept('uber-escape',self.handleEscape) + if self.wantHistory: + self.accept('arrow_up-up', self.getPrevHistory) + self.accept('arrow_down-up', self.getNextHistory) + + def deactivate(self): + self.ignore('uber-escape') + self.set("") + self['focus'] = 0 + self.hide() + self.active = 0 + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + + def handleEscape(self): + localAvatar.chatMgr.deactivateChat() + + def isActive(self): + return self.active + + def _checkShouldFilter(self, text): + """ + Should we whitelist filter this message or is it + something special we should leave untouched? + """ + if len(text) > 0 and text[0] in ['/']: + return False + else: + return True + + def sendChat(self, text, overflow = False): + """ + Send the text from the entry + """ + + text = self.get(plain=True) + #print text + # Filter out empty string + if text: + self.set("") + if base.config.GetBool("exec-chat", 0) and (text[0] == '>'): + if text[1:]: + # Exec a python command + ext = base.talkAssistant.execMessage(text[1:]) + base.talkAssistant.receiveDeveloperMessage(text) + base.talkAssistant.receiveDeveloperMessage(ext) + base.localAvatar.setChatAbsolute(ext, CFSpeech | CFTimeout) + if self.wantHistory: + self.addToHistory(text) + localAvatar.chatMgr.deactivateChat() + localAvatar.chatMgr.activateChat() + + #import pdb; pdb.set_trace() + self.set(">") + self.setCursorPosition(1) + return + else: + localAvatar.chatMgr.deactivateChat() + + # If slash command, execute it instead of sending chat + elif base.config.GetBool("want-slash-commands", 1) and (text[0] == '/'): + base.talkAssistant.executeSlashCommand(text) + elif (localAvatar.isGM() or base.cr.wantMagicWords) and (text[0] == '`'): + base.talkAssistant.executeGMCommand(text) + else: + self.sendChatByMode(text) + + if self.wantHistory: + self.addToHistory(text) + else: + localAvatar.chatMgr.deactivateChat() + + if not overflow: + self.hide() + if self.autoOff: + self.requestMode('Off') + localAvatar.chatMgr.messageSent() + + + def sendChatByMode(self, text): + state = self.getCurrentOrNextState() + messenger.send("sentRegularChat") + if state == 'PlayerWhisper': + base.talkAssistant.sendAccountTalk(text, self.whisperId) + elif state == 'AvatarWhisper': + base.talkAssistant.sendWhisperTalk(text, self.whisperId) + else: + base.talkAssistant.sendOpenTalk(text) + + def sendFailed(self, text): + """ + This function is called when the user tries and fails to submit + the chat message. We've disabled submission because there's a + bad word, and need to give them feedback. For now, flash the + field red! + """ + self['frameColor'] = (0.9, 0.0, 0.0, 0.8) + def resetFrameColor(task=None): + self['frameColor'] = self.origFrameColor + return Task.done + taskMgr.doMethodLater(0.1,resetFrameColor,"resetFrameColor") + # Also let them submit if they try again--we'll stomp on any violating words + self.applyFilter(keyArgs=None,strict=False) + self.guiItem.setAcceptEnabled(True) + + + def chatOverflow(self, overflowText): + """ + When the user types too many lines of text, an event gets thrown + which calls this function. Right now it just sends the text just + as if you hit return to complete the sentence. + """ + self.sendChat(self.get(plain=True), overflow = True) + + + #################################### + # History functions + def addToHistory(self, text): + self.history = [text] + self.history[:self.historySize-1] + self.historyIndex = 0 + + def getPrevHistory(self): + self.set(self.history[self.historyIndex]) + self.historyIndex += 1 + self.historyIndex %= len(self.history) + self.setCursorPosition(len(self.get())) + + def getNextHistory(self): + self.set(self.history[self.historyIndex]) + self.historyIndex -= 1 + self.historyIndex %= len(self.history) + self.setCursorPosition(len(self.get())) + + + #################################### + # Exec-chat functions + def importExecNamespace(self): + # Derived classes should take advantage of this hook to import + # useful variables into the chat namespace for developer + # access. + pass + + def __execMessage(self, message): + print ("_execMessage %s" % (message)) + if not ChatInputTyped.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + ChatInputTyped.ExecNamespace = { } + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputTyped.ExecNamespace as + # the local namespace. + try: + return str(eval(message, globals(), ChatInputTyped.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), ChatInputTyped.ExecNamespace + return 'ok' + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + + def applyFilter(self,keyArgs,strict=False): + text = self.get(plain=True) + + if len(text) > 0: + if text[0] == '/': + self.guiItem.setAcceptEnabled(True) + return + elif text[0] == '>' and base.config.GetBool("exec-chat", 0): + self.guiItem.setAcceptEnabled(True) + return + elif text[0] == '~' and base.cr.wantMagicWords: + self.guiItem.setAcceptEnabled(True) + return + + words = text.split(" ") + newwords = [] + self.notify.debug("%s" % words) + + okayToSubmit = True + + for word in words: + if word == "" or self.whiteList.isWord(word): + newwords.append(word) + else: + okayToSubmit = False + newwords.append("\1WLEnter\1" + word + "\2") + + #if not strict: + # newwords[-1] = words[-1] + + if not strict: + lastword = words[-1] + + if lastword == "" or self.whiteList.isPrefix(lastword): + newwords[-1] = lastword + else: + newwords[-1] = "\1WLEnter\1" + lastword + "\2" + + self.guiItem.setAcceptEnabled((okayToSubmit or self.alwaysSubmit)) + newtext = " ".join(newwords) + self.set(newtext) diff --git a/otp/src/chat/ChatInputWhiteListFrame.py b/otp/src/chat/ChatInputWhiteListFrame.py new file mode 100644 index 0000000..28da44d --- /dev/null +++ b/otp/src/chat/ChatInputWhiteListFrame.py @@ -0,0 +1,428 @@ +from direct.fsm import FSM +from otp.otpbase import OTPGlobals +import sys +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from otp.otpbase import OTPLocalizer +from direct.task import Task +from otp.chat.ChatInputTyped import ChatInputTyped + +class ChatInputWhiteListFrame(FSM.FSM, DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory("ChatInputWhiteList") + + # This will hold the local namespace we evaluate '>chat' messages + # within. + ExecNamespace = None + + # special methods + def __init__(self, entryOptions, parent = None, **kw): + FSM.FSM.__init__(self, 'ChatInputWhiteListFrame') + + self.okayToSubmit = True + self.receiverId = None + DirectFrame.__init__(self, + parent = aspect2dp, + pos = (0, 0, 0.30), + relief = None, + image = DGG.getDefaultDialogGeom(), + image_scale = (1.6, 1, 1.4), + image_pos = (0,0,-0.05), + image_color = OTPGlobals.GlobalDialogColor, + borderWidth = (0.01, 0.01), + ) + + optiondefs = { + 'parent' : self, + 'relief': DGG.SUNKEN, + 'scale': 0.05, + 'frameSize' : (-0.2, 25.3, -0.5, 1.2), + 'borderWidth' : (0.1, 0.1), + 'frameColor' : (0.9, 0.9, 0.85, 0.8), + 'pos' : (-0.2,0,0.11), + 'entryFont' : OTPGlobals.getInterfaceFont(), + 'width': 8.6, + 'numLines' : 3, + 'cursorKeys' : 1, + 'backgroundFocus' : 0, + 'suppressKeys' : 1, + 'suppressMouse' : 1, + 'command' : self.sendChat, + 'failedCommand': self.sendFailed, + 'focus' : 0, + 'text' : '', + 'sortOrder' : DGG.FOREGROUND_SORT_INDEX, + } + + entryOptions['parent'] = self + + + + self.chatEntry = DirectEntry(**entryOptions) + + self.whisperId = None + self.chatEntry.bind(DGG.OVERFLOW, self.chatOverflow) + + wantHistory = 0 + if __dev__: + wantHistory = 1 + self.wantHistory = base.config.GetBool('want-chat-history', wantHistory) + self.history = [''] + self.historySize = base.config.GetInt('chat-history-size', 10) + self.historyIndex = 0 + + self.promoteWhiteList = 0# base.config.GetBool('white-list-promotes-to-black', 0) + self.checkBeforeSend = base.config.GetBool('white-list-check-before-send', 0) + + self.whiteList = None + + self.active = 0 + self.autoOff = 0 + self.sendBy = "Mode" + self.prefilter = 1 + + from direct.gui import DirectGuiGlobals + self.chatEntry.bind(DirectGuiGlobals.TYPE,self.applyFilter) + self.chatEntry.bind(DirectGuiGlobals.ERASE,self.applyFilter) + + tpMgr = TextPropertiesManager.getGlobalPtr() + Red = tpMgr.getProperties('red') + Red.setTextColor(1.0,0.0,0.0,1) + tpMgr.setProperties('WLRed',Red) + del tpMgr + + self.origFrameColor = self.chatEntry['frameColor'] + + def destroy(self): + from direct.gui import DirectGuiGlobals + self.chatEntry.unbind(DGG.OVERFLOW) + self.chatEntry.unbind(DirectGuiGlobals.TYPE) + self.chatEntry.unbind(DirectGuiGlobals.ERASE) + self.chatEntry.ignoreAll() + DirectFrame.destroy(self) + + + def delete(self): + self.ignore('arrow_up-up') + self.ignore('arrow_down-up') + + + def requestMode(self, mode, *args): + """ + This is done so we can provide some symettry with + the PChatInputSpeedChat. + """ + # RAU we need to return the result of the self.request + # so toontown can do fallback to a good state if it fails + return self.request(mode, *args) + + def defaultFilter(self, request, *args): + if request == 'AllChat': + if not base.talkAssistant.checkAnyTypedChat(): + messenger.send("Chat-Failed open typed chat test") + self.notify.warning("Chat-Failed open typed chat test") + return None + elif request == 'PlayerWhisper': + if not base.talkAssistant.checkWhisperTypedChatPlayer(self.whisperId): + messenger.send("Chat-Failed player typed chat test") + self.notify.warning("Chat-Failed player typed chat test") + return None + elif request == 'AvatarWhisper': + if not base.talkAssistant.checkWhisperTypedChatAvatar(self.whisperId): + messenger.send("Chat-Failed avatar typed chat test") + self.notify.warning("Chat-Failed avatar typed chat test") + return None + return FSM.FSM.defaultFilter(self, request, *args) + + def enterOff(self): + assert self.notify.debugStateCall(self) + self.deactivate() + localAvatar.chatMgr.fsm.request("mainMenu") + + def exitOff(self): + assert self.notify.debugStateCall(self) + self.activate() + + def enterAllChat(self): + assert self.notify.debugStateCall(self) + self.chatEntry['focus'] = 1 + self.show() + + def exitAllChat(self): + assert self.notify.debugStateCall(self) + pass + + def enterGuildChat(self): + self['focus'] = 1 + self.show() + + def exitGuildChat(self): + pass + + def enterCrewChat(self): + self['focus'] = 1 + self.show() + + def exitCrewChat(self): + pass + + def enterPlayerWhisper(self): + assert self.notify.debugStateCall(self) + self.tempText = self.chatEntry.get() + self.activate() + + def exitPlayerWhisper(self): + assert self.notify.debugStateCall(self) + self.chatEntry.set(self.tempText) + self.whisperId = None + + def enterAvatarWhisper(self): + assert self.notify.debugStateCall(self) + self.tempText = self.chatEntry.get() + self.activate() + + def exitAvatarWhisper(self): + assert self.notify.debugStateCall(self) + self.chatEntry.set(self.tempText) + self.whisperId = None + + def activateByData(self, receiverId = None, toPlayer = 0): + """Return None if something went wrong, otherwise return the some info on the new state.""" + assert self.notify.debugStateCall(self) + self.receiverId = receiverId + self.toPlayer = toPlayer + result = None + if not self.receiverId: + result = self.requestMode("AllChat") + elif self.receiverId and not self.toPlayer: + self.whisperId = receiverId + result = self.requestMode("AvatarWhisper") + elif self.receiverId and self.toPlayer: + self.whisperId = receiverId + result = self.requestMode("PlayerWhisper") + return result + + + def activate(self): + #self.chatEntry.set("") + assert self.notify.debugStateCall(self) + self.chatEntry['focus'] = 1 + self.show() + self.active = 1 + self.chatEntry.guiItem.setAcceptEnabled(False) + + def deactivate(self): + assert self.notify.debugStateCall(self) + self.chatEntry.set("") + self.chatEntry['focus'] = 0 + self.hide() + self.active = 0 + + def isActive(self): + return self.active + + def sendChat(self, text, overflow = False): + """ + Send the text from the entry + """ + # Filter if we're not doing exec or magic word + if not (len(text) > 0 and text[0] in ['~','>']): + if self.prefilter: + words = text.split(" ") + newwords = [] + for word in words: + if word == "" or self.whiteList.isWord(word) or self.promoteWhiteList: + newwords.append(word) + else: + newwords.append(base.whiteList.defaultWord) + + text = " ".join(newwords) + else: + text = self.chatEntry.get(plain=True) + + # Filter out empty string + if text: + self.chatEntry.set("") + + if base.config.GetBool("exec-chat", 0) and (text[0] == '>'): + # Exec a python command + text = self.__execMessage(text[1:]) + base.localAvatar.setChatAbsolute(text, CFSpeech | CFTimeout) + return + else: + self.sendChatBySwitch(text) + + if self.wantHistory: + self.addToHistory(text) + else: + localAvatar.chatMgr.deactivateChat() + + if not overflow: + self.hide() + if self.autoOff: + self.requestMode('Off') + localAvatar.chatMgr.messageSent() + + def sendChatBySwitch(self, text): + if len(text) > 0 and text[0] == '~': + base.talkAssistant.sendOpenTalk(text) + elif self.sendBy == "Mode": + self.sendChatByMode(text) + elif self.sendBy == "Data": + self.sendChatByData(text) + else: + self.sendChatByMode(text) + + def sendChatByData(self, text): + if not self.receiverId: + base.talkAssistant.sendOpenTalk(text) + elif self.receiverId and not self.toPlayer: + base.talkAssistant.sendWhisperTalk(text, self.receiverId) + elif self.receiverId and self.toPlayer: + base.talkAssistant.sendAccountTalk(text, self.receiverId) + + + def sendChatByMode(self, text): + state = self.getCurrentOrNextState() + messenger.send("sentRegularChat") + if state == 'PlayerWhisper': + base.talkAssistant.sendPlayerWhisperWLChat(text, self.whisperId) + elif state == 'AvatarWhisper': + base.talkAssistant.sendAvatarWhisperWLChat(text, self.whisperId) + elif state == 'GuildChat': + base.talkAssistant.sendAvatarGuildWLChat(text) + elif state == 'CrewChat': + base.talkAssistant.sendAvatarCrewWLChat(text) + # Temporary hack for magic words + elif len(text) > 0 and text[0] == '~': + base.talkAssistant.sendOpenTalk(text) + else: + base.talkAssistant.sendOpenTalk(text) + + def sendFailed(self, text): + """ + This function is called when the user tries and fails to submit + the chat message. We've disabled submission because there's a + bad word, and need to give them feedback. For now, flash the + field red! + """ + if not self.checkBeforeSend: + self.sendChat(text) + return + + self.chatEntry['frameColor'] = (0.9, 0.0, 0.0, 0.8) + def resetFrameColor(task=None): + self.chatEntry['frameColor'] = self.origFrameColor + return Task.done + taskMgr.doMethodLater(0.1,resetFrameColor,"resetFrameColor") + # Also let them submit if they try again--we'll stomp on any violating words + self.applyFilter(keyArgs=None,strict=True) + self.okayToSubmit = True + self.chatEntry.guiItem.setAcceptEnabled(True) + + + + def chatOverflow(self, overflowText): + """ + When the user types too many lines of text, an event gets thrown + which calls this function. Right now it just sends the text just + as if you hit return to complete the sentence. + """ + self.notify.debug('chatOverflow') + self.sendChat(self.chatEntry.get(plain=True), overflow = True) + + + #################################### + # History functions + def addToHistory(self, text): + self.history = [text] + self.history[:self.historySize-1] + self.historyIndex = 0 + + def getPrevHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex += 1 + self.historyIndex %= len(self.history) + + def getNextHistory(self): + self.chatEntry.set(self.history[self.historyIndex]) + self.historyIndex -= 1 + self.historyIndex %= len(self.history) + + + #################################### + # Exec-chat functions + def importExecNamespace(self): + # Derived classes should take advantage of this hook to import + # useful variables into the chat namespace for developer + # access. + pass + + def __execMessage(self, message): + if not ChatInputTyped.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + ChatInputTyped.ExecNamespace = { } + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputTyped.ExecNamespace as + # the local namespace. + try: + return str(eval(message, globals(), ChatInputTyped.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), ChatInputTyped.ExecNamespace + return 'ok' + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + + def applyFilter(self,keyArgs,strict=False): + text = self.chatEntry.get(plain=True) + + if len(text) > 0 and text[0] in ['~','>']: + self.okayToSubmit = True + else: + words = text.split(" ") + newwords = [] + self.notify.debug("%s" % words) + + self.okayToSubmit = True + + for word in words: + if word == "" or self.whiteList.isWord(word): + newwords.append(word) + else: + if self.checkBeforeSend: + self.okayToSubmit = False + else: + self.okayToSubmit = True + newwords.append("\1WLEnter\1" + word + "\2") + + if not strict: + lastword = words[-1] + + if lastword == "" or self.whiteList.isPrefix(lastword): + newwords[-1] = lastword + else: + newwords[-1] = "\1WLEnter\1" + lastword + "\2" + + newtext = " ".join(newwords) + self.chatEntry.set(newtext) + + self.chatEntry.guiItem.setAcceptEnabled(self.okayToSubmit) + diff --git a/otp/src/chat/ChatManager.py b/otp/src/chat/ChatManager.py new file mode 100644 index 0000000..e8151ae --- /dev/null +++ b/otp/src/chat/ChatManager.py @@ -0,0 +1,809 @@ +""" +ChatManager module: contains the ChatManager class +""" + +import string +import sys +from direct.showbase import DirectObject +from otp.otpbase import OTPGlobals +from direct.fsm import ClassicFSM +from direct.fsm import State +from otp.login import SecretFriendsInfoPanel +from otp.login import PrivacyPolicyPanel +from otp.otpbase import OTPLocalizer +from direct.directnotify import DirectNotifyGlobal +from otp.login import LeaveToPayDialog +from direct.gui.DirectGui import * +from pandac.PandaModules import * +#from ChatInputSpeedChat import ChatInputSpeedChat + +# other systems can listen for these events if they +# just want to know that a particular event happened +# and don't care about what was actually said +# +# the events are hierarchical; when a +# speedchat msg is picked, for instance, the +# following events will be sent: +# 'ChatEvent', 'SCChatEvent' +ChatEvent = 'ChatEvent' +NormalChatEvent = 'NormalChatEvent' +SCChatEvent = 'SCChatEvent' +SCCustomChatEvent = 'SCCustomChatEvent' +SCEmoteChatEvent = 'SCEmoteChatEvent' + +OnScreen = 0 +OffScreen = 1 +Thought = 2 +ThoughtPrefix = '.' + +# thought methods +def isThought(message): + """ + message is a string. + + Return 1 if the given string contains the thought prefix, + Return 0 otherwise + """ + if (len(message) == 0): + # empty string cannot be a thought + return 0 + elif (string.find(message, ThoughtPrefix, 0, + len(ThoughtPrefix)) >= 0): + return 1 + else: + return 0 + +def removeThoughtPrefix(message): + """ + message is a string. + + Return the string with the thought prefix removed + """ + if (isThought(message)): + return message[len(ThoughtPrefix):] + else: + return message + +class ChatManager(DirectObject.DirectObject): + """ + contains methods for turning chat inputs + into onscreen thought/word balloons + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ChatManager") + execChat = base.config.GetBool("exec-chat", 0) + + # special methods + def __init__(self, cr, localAvatar): + assert self.notify.debug("ChatManager") + + # Store the client repository and the local avatar + self.cr = cr + self.localAvatar = localAvatar + + self.wantBackgroundFocus = 1 + + self.__scObscured = 0 + self.__normalObscured = 0 + + self.openChatWarning = None + self.unpaidChatWarning = None + self.teaser = None + self.paidNoParentPassword = None + self.noSecretChatAtAll = None + self.noSecretChatAtAllAndNoWhitelist = None + self.noSecretChatWarning = None + self.activateChatGui = None + self.chatMoreInfo = None + self.chatPrivacyPolicy = None + self.secretChatActivated = None + self.problemActivatingChat = None + self.leaveToPayDialog = None + + self.fsm = ClassicFSM.ClassicFSM( + 'chatManager', [ + State.State("off", + self.enterOff, + self.exitOff), + State.State("mainMenu", + self.enterMainMenu, + self.exitMainMenu), + State.State("speedChat", + self.enterSpeedChat, + self.exitSpeedChat), + State.State("normalChat", + self.enterNormalChat, + self.exitNormalChat), + State.State("whisper", + self.enterWhisper, + self.exitWhisper), + State.State("whisperChat", + self.enterWhisperChat, + self.exitWhisperChat), + State.State("whisperChatPlayer", + self.enterWhisperChatPlayer, + self.exitWhisperChatPlayer), + State.State("whisperSpeedChat", + self.enterWhisperSpeedChat, + self.exitWhisperSpeedChat), + State.State("whisperSpeedChatPlayer", + self.enterWhisperSpeedChatPlayer, + self.exitWhisperSpeedChatPlayer), + State.State("openChatWarning", + self.enterOpenChatWarning, + self.exitOpenChatWarning), + State.State("leaveToPayDialog", + self.enterLeaveToPayDialog, + self.exitLeaveToPayDialog), + State.State("unpaidChatWarning", + self.enterUnpaidChatWarning, + self.exitUnpaidChatWarning), + State.State("noSecretChatAtAll", + self.enterNoSecretChatAtAll, + self.exitNoSecretChatAtAll), + State.State("noSecretChatAtAllAndNoWhitelist", + self.enterNoSecretChatAtAllAndNoWhitelist, + self.exitNoSecretChatAtAllAndNoWhitelist), + State.State("noSecretChatWarning", + self.enterNoSecretChatWarning, + self.exitNoSecretChatWarning), + State.State("noFriendsWarning", + self.enterNoFriendsWarning, + self.exitNoFriendsWarning), + State.State("otherDialog", + self.enterOtherDialog, + self.exitOtherDialog), + State.State("activateChat", + self.enterActivateChat, + self.exitActivateChat), + State.State("chatMoreInfo", + self.enterChatMoreInfo, + self.exitChatMoreInfo), + State.State("chatPrivacyPolicy", + self.enterChatPrivacyPolicy, + self.exitChatPrivacyPolicy), + State.State("secretChatActivated", + self.enterSecretChatActivated, + self.exitSecretChatActivated), + State.State("problemActivatingChat", + self.enterProblemActivatingChat, + self.exitProblemActivatingChat), + State.State("whiteListOpenChat", + self.enterWhiteListOpenChat, + self.exitWhiteListOpenChat), + State.State("whiteListAvatarChat", + self.enterWhiteListAvatarChat, + self.exitWhiteListAvatarChat), + State.State("whiteListPlayerChat", + self.enterWhiteListPlayerChat, + self.exitWhiteListPlayerChat), + State.State("trueFriendTeaserPanel", + self.enterTrueFriendTeaserPanel, + self.exitTrueFriendTeaserPanel), + ], + "off", + "off", + ) + self.fsm.enterInitialState() + + def delete(self): + assert self.notify.debugStateCall(self) + self.ignoreAll() + del self.fsm + if hasattr(self.chatInputNormal, 'destroy'): + self.chatInputNormal.destroy() + self.chatInputNormal.delete() + del self.chatInputNormal + self.chatInputSpeedChat.delete() + del self.chatInputSpeedChat + if self.openChatWarning: + self.openChatWarning.destroy() + self.openChatWarning = None + if self.unpaidChatWarning: + self.payButton = None + self.unpaidChatWarning.destroy() + self.unpaidChatWarning = None + if self.teaser: + self.teaser.cleanup() + self.teaser.unload() + self.teaser = None + if self.noSecretChatAtAll: + self.noSecretChatAtAll.destroy() + self.noSecretChatAtAll = None + if self.noSecretChatAtAllAndNoWhitelist: + self.noSecretChatAtAllAndNoWhitelist.destroy() + self.noSecretChatAtAllAndNoWhitelist = None + if self.noSecretChatWarning: + self.noSecretChatWarning.destroy() + self.noSecretChatWarning = None + if self.activateChatGui: + self.activateChatGui.destroy() + self.activateChatGui = None + if self.chatMoreInfo: + self.chatMoreInfo.destroy() + self.chatMoreInfo = None + if self.chatPrivacyPolicy: + self.chatPrivacyPolicy.destroy() + self.chatPrivacyPolicy = None + if self.secretChatActivated: + self.secretChatActivated.destroy() + self.secretChatActivated = None + if self.problemActivatingChat: + self.problemActivatingChat.destroy() + self.problemActivatingChat = None + del self.localAvatar + del self.cr + + def obscure(self, normal, sc): + assert self.notify.debugStateCall(self) + self.__scObscured = sc + if (self.__scObscured): + self.scButton.hide() + self.__normalObscured = normal + if (self.__normalObscured): + self.normalButton.hide() + + + def isObscured(self): + assert self.notify.debugStateCall(self) + return self.__normalObscured, self.__scObscured + + # chat methods + def stop(self): + assert self.notify.debugStateCall(self) + self.fsm.request("off") + self.ignoreAll() + + def start(self): + assert self.notify.debugStateCall(self) + self.fsm.request("mainMenu") + + def announceChat(self): + assert self.notify.debugStateCall(self) + messenger.send(ChatEvent) + + def announceSCChat(self): + assert self.notify.debugStateCall(self) + messenger.send(SCChatEvent) + self.announceChat() + + def sendChatString(self, message): + """ + message is a string. + + Send chat message update + """ + assert self.notify.debugStateCall(self) + chatFlags = CFSpeech | CFTimeout + + if base.cr.wantSwitchboardHacks: + from otp.switchboard import badwordpy + badwordpy.init("","") + message = badwordpy.scrub(message) + + if isThought(message): + # If it's intended to be a thought message, send it as + # one. This means we don't include a timeout. + message = removeThoughtPrefix(message) + chatFlags = CFThought + + #messenger.send("chatUpdate", [message, chatFlags]) + #base.chatAssistant.sendAvatarOpenTypedChat(message) + + messenger.send(NormalChatEvent) + self.announceChat() + + + def sendWhisperString(self, message, whisperAvatarId): + """sendWhisperString(self, string, avatarId) + Send chat message update + """ + assert self.notify.debugStateCall(self) + # if isThought(message): + # # If it's intended to be a thought message, send it as + # # one. This means we don't include a timeout. + # message = removeThoughtPrefix(message) + # chatFlags = CFThought + + #messenger.send("whisperUpdate", [message, whisperAvatarId]) + #base.chatAssistant.sendAvatarWhisperTypedChat(message, whisperAvatarId) + + def sendSCChatMessage(self, msgIndex): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + #messenger.send("chatUpdateSC", [msgIndex]) + #self.announceSCChat() + base.talkAssistant.sendOpenSpeedChat(1, msgIndex) + + def sendSCWhisperMessage(self, msgIndex, whisperAvatarId, toPlayer): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + if toPlayer: + base.talkAssistant.sendPlayerWhisperSpeedChat(1, msgIndex, whisperAvatarId) + else: + base.talkAssistant.sendAvatarWhisperSpeedChat(1, msgIndex, whisperAvatarId) + #messenger.send("whisperUpdateSC", [msgIndex, whisperAvatarId,toPlayer]) + + def sendSCCustomChatMessage(self, msgIndex): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + #messenger.send("chatUpdateSCCustom", [msgIndex]) + #messenger.send(SCCustomChatEvent) + #self.announceSCChat() + base.talkAssistant.sendOpenSpeedChat(3, msgIndex) + + def sendSCCustomWhisperMessage(self, msgIndex, whisperAvatarId, toPlayer): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + #messenger.send("whisperUpdateSCCustom", [msgIndex, whisperAvatarId,toPlayer]) + if toPlayer: + base.talkAssistant.sendPlayerWhisperSpeedChat(3, msgIndex, whisperAvatarId) + else: + base.talkAssistant.sendAvatarWhisperSpeedChat(3, msgIndex, whisperAvatarId) + + def sendSCEmoteChatMessage(self, emoteId): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + #messenger.send("chatUpdateSCEmote", [emoteId]) + #messenger.send(SCEmoteChatEvent) + #self.announceSCChat() + base.talkAssistant.sendOpenSpeedChat(2, emoteId) + + def sendSCEmoteWhisperMessage(self, emoteId, whisperAvatarId, toPlayer): + """ + Send speedchat message update + """ + assert self.notify.debugStateCall(self) + #messenger.send("whisperUpdateSCEmote", [emoteId, whisperAvatarId,toPlayer]) + if toPlayer: + base.talkAssistant.sendPlayerWhisperSpeedChat(2, emoteId, whisperAvatarId) + else: + base.talkAssistant.sendAvatarWhisperSpeedChat(2, emoteId, whisperAvatarId) + + + def enterOff(self): + assert self.notify.debugStateCall(self) + self.scButton.hide() + self.normalButton.hide() + self.ignoreAll() + + def exitOff(self): + assert self.notify.debugStateCall(self) + pass + + def enterMainMenu(self): + assert self.notify.debugStateCall(self) + self.checkObscurred() + if (self.localAvatar.canChat() + or self.cr.wantMagicWords): + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 1 + # The chat input normal will send an event when it gets + # its first keystroke. It waits with background focus + # until somebody hits a key. When it does, it throws its + # typeEvent. + self.acceptOnce('enterNormalChat', self.fsm.request, ['normalChat']) + + def checkObscurred(self): + if not self.__scObscured: + self.scButton.show() + if not self.__normalObscured: + self.normalButton.show() + + + def exitMainMenu(self): + assert self.notify.debugStateCall(self) + self.scButton.hide() + self.normalButton.hide() + self.ignore('enterNormalChat') + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + + def whisperTo(self, avatarName, avatarId, playerId = None): + """ + Interface for the outside world to bring up the whisper interface + for this avatar + """ + assert self.notify.debugStateCall(self) + self.fsm.request("whisper", [avatarName, avatarId, playerId]) + + def noWhisper(self): + """ + Interface for the outside world to shut down the whisper + interface if it is up. + """ + assert self.notify.debugStateCall(self) + self.fsm.request("mainMenu") + + def handleWhiteListSelect(self): + self.fsm.request("whiteListOpenChat") + + + def enterWhiteListOpenChat(self): + assert self.notify.debugStateCall(self) + self.checkObscurred() + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + base.localAvatar.chatMgr.chatInputWhiteList.activateByData() + + def exitWhiteListOpenChat(self): + pass + + def enterWhiteListAvatarChat(self, receiverId): + assert self.notify.debugStateCall(self) + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + base.localAvatar.chatMgr.chatInputWhiteList.activateByData(receiverId, 0) + + def exitWhiteListAvatarChat(self): + pass + + def enterWhiteListPlayerChat(self, receiverId): + assert self.notify.debugStateCall(self) + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + base.localAvatar.chatMgr.chatInputWhiteList.activateByData(receiverId, 1) + + def exitWhiteListPlayerChat(self): + pass + + def enterWhisper(self, avatarName, avatarId, playerId = None): + assert self.notify.debugStateCall(self) + self.whisperScButton['extraArgs'] = [avatarName, avatarId, playerId] + self.whisperButton['extraArgs'] = [avatarName, avatarId, playerId] + + playerName = None + chatToToon = 1 #set to 0 to chat to player + + online = 0 + if self.cr.doId2do.has_key(avatarId): + # The avatar is online, and in fact, nearby. + online = 1 + elif self.cr.isFriend(avatarId): + # The avatar is a friend of ours. Is she online? + online = self.cr.isFriendOnline(avatarId) + + hasManager = hasattr(base.cr, "playerFriendsManager") + if hasManager: + if base.cr.playerFriendsManager.askAvatarOnline(avatarId): + online = 1 + + # Do we have chat permission with the other avatar? + avatarUnderstandable = 0 + playerUnderstandable = 0 + av = None + if avatarId: + av = self.cr.identifyAvatar(avatarId) + if av != None: + avatarUnderstandable = av.isUnderstandable() + + ## Security bug! Because we test the chat permission only + ## locally, on the 'sending' avatar, a compromised client + ## could send unfiltered whispers to other players! + + ## This is less true now, but still possible. + + if playerId: + if base.cr.playerFriendsManager.playerId2Info.has_key(playerId): + playerInfo = base.cr.playerFriendsManager.playerId2Info.get(playerId) + playerName = playerInfo.playerName + online = 1 + playerUnderstandable = playerInfo.understandableYesNo + if playerUnderstandable or (not avatarId): + chatToToon = 0 + + if chatToToon: + chatName = avatarName + else: + chatName = playerName + + normalButtonObscured, scButtonObscured = self.isObscured() + + if (avatarUnderstandable or playerUnderstandable) and online and not normalButtonObscured: + self.whisperButton['state'] = 'normal' + self.enablewhisperButton() + else: + self.whisperButton['state'] = 'inactive' + self.disablewhisperButton() + + if online: + self.whisperScButton['state'] = 'normal' + self.changeFrameText(OTPLocalizer.ChatManagerWhisperToName % (chatName)) + #self.whisperFrame["text"] = ( + # OTPLocalizer.ChatManagerWhisperToName % (chatName)) + #import pdb; pdb.set_trace() + + else: + self.whisperScButton['state'] = 'inactive' + self.changeFrameText(OTPLocalizer.ChatManagerWhisperOffline % (chatName)) + #self.whisperFrame["text"] = ( + # OTPLocalizer.ChatManagerWhisperOffline % (chatName)) + + self.whisperFrame.show() + self.refreshWhisperFrame() + + # NOT SURE THIS NEXT PART DOES ANYTHING + + if (avatarUnderstandable or playerUnderstandable): + # sending to a player outside the game + if playerId and (not chatToToon): + #print("chat player %s %s" % (playerId, playerUnderstandable)) + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 1 + + self.acceptOnce( + 'enterNormalChat', self.fsm.request, + ['whisperChatPlayer', [avatarName, playerId]]) + elif online and chatToToon: #in toontown with a toon + #print("chat toon") + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 1 + # The chat input normal will send an event when it gets + # its first keystroke. It waits with background focus + # until somebody hits a key. When it does, it throws its + # typeEvent. + self.acceptOnce( + 'enterNormalChat', self.fsm.request, + ['whisperChat', [avatarName, avatarId]]) + + if base.cr.config.GetBool('force-typed-whisper-enabled',0): + self.whisperButton['state'] = 'normal' + self.enablewhisperButton() + + def disablewhisperButton(self): + pass + + def enablewhisperButton(self): + pass + + def refreshWhisperFrame(self): + pass + + + def changeFrameText(self, newText): + """ + using this to abstract out the message so + that other gui structures can be supported + """ + self.whisperFrame["text"] = newText + + def exitWhisper(self): + assert self.notify.debugStateCall(self) + self.whisperFrame.hide() + self.ignore('enterNormalChat') + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + + + def enterWhisperSpeedChat(self, avatarId): + assert self.notify.debugStateCall(self) + #print("enterWhisperSpeedChat %s" % (avatarId)) + self.whisperFrame.show() + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + self.chatInputSpeedChat.show(avatarId) + + def exitWhisperSpeedChat(self): + assert self.notify.debugStateCall(self) + self.whisperFrame.hide() + self.chatInputSpeedChat.hide() + + def enterWhisperSpeedChatPlayer(self, playerId): + assert self.notify.debugStateCall(self) + #print("enterWhisperSpeedChatPlayer %s" % (playerId)) + self.whisperFrame.show() + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + self.chatInputSpeedChat.show(playerId, 1) + + def exitWhisperSpeedChatPlayer(self): + assert self.notify.debugStateCall(self) + self.whisperFrame.hide() + self.chatInputSpeedChat.hide() + + def enterWhisperChat(self, avatarName, avatarId): + assert self.notify.debugStateCall(self) + #print("enterWhisperChat %s %s" % (avatarName, avatarId)) + result = self.chatInputNormal.activateByData(avatarId) + return result + + def exitWhisperChat(self): + assert self.notify.debugStateCall(self) + self.chatInputNormal.deactivate() + + def enterWhisperChatPlayer(self, avatarName, playerId): + assert self.notify.debugStateCall(self) + #print("enterWhisperChatPlayer %s %s" % (avatarName, playerId)) + playerInfo = base.cr.playerFriendsManager.getFriendInfo(playerId) + if playerInfo: + avatarName = playerInfo.playerName + result = self.chatInputNormal.activateByData(playerId, 1) + return result + + def exitWhisperChatPlayer(self): + assert self.notify.debugStateCall(self) + self.chatInputNormal.deactivate() + + + def enterSpeedChat(self): + assert self.notify.debugStateCall(self) + messenger.send('enterSpeedChat') + if (not self.__scObscured): + self.scButton.show() + if (not self.__normalObscured): + self.normalButton.show() + if self.wantBackgroundFocus: + self.chatInputNormal.chatEntry['backgroundFocus'] = 0 + self.chatInputSpeedChat.show() + + def exitSpeedChat(self): + assert self.notify.debugStateCall(self) + self.scButton.hide() + self.normalButton.hide() + self.chatInputSpeedChat.hide() + + def enterNormalChat(self): + assert self.notify.debugStateCall(self) + result = self.chatInputNormal.activateByData() + return result + + def exitNormalChat(self): + assert self.notify.debugStateCall(self) + self.chatInputNormal.deactivate() + + def enterOpenChatWarning(self): + self.notify.error("called enterOpenChatWarning() on parent class") + pass + + def exitOpenChatWarning(self): + self.notify.error("called exitOpenChatWarning() on parent class") + pass + + def enterLeaveToPayDialog(self): + """ + Tell the user that we will exit the 3D client and take + them to a web page. + """ + assert self.notify.debugStateCall(self) + if self.leaveToPayDialog == None: + self.leaveToPayDialog=LeaveToPayDialog.LeaveToPayDialog( + self.paidNoParentPassword) + self.leaveToPayDialog.setCancel(self.__handleLeaveToPayCancel) + self.leaveToPayDialog.show() + + def exitLeaveToPayDialog(self): + assert self.notify.debugStateCall(self) + if self.leaveToPayDialog: + self.leaveToPayDialog.destroy() + self.leaveToPayDialog = None + + def enterUnpaidChatWarning(self): + self.notify.error("called enterUnpaidChatWarning() on parent class") + pass + + def exitUnpaidChatWarning(self): + self.notify.error("called exitUnpaidChatWarning() on parent class") + pass + + def enterNoSecretChatAtAll(self): + self.notify.error("called enterNoSecretChatAtAll() on parent class") + pass + + def exitNoSecretChatAtAll(self): + self.notify.error("called exitNoSecretChatAtAll() on parent class") + pass + + def enterNoSecretChatAtAllAndNoWhitelist(self): + self.notify.error("called enterNoSecretChatAtAllAndNoWhitelist() on parent class") + pass + + def exitNoSecretChatAtAllAndNoWhitelist(self): + self.notify.error("called exitNoSecretChatAtAllAndNoWhitelist() on parent class") + pass + + + def enterNoSecretChatWarning(self): + self.notify.error("called enterNoSecretChatWarning() on parent class") + pass + + def exitNoSecretChatWarning(self): + self.notify.error("called exitNoSecretChatWarning() on parent class") + pass + + def enterNoFriendsWarning(self): + self.notify.error("called enterNoFriendsWarning() on parent class") + pass + + def exitNoFriendsWarning(self): + self.notify.error("called exitNoFriendsWarning() on parent class") + pass + + def enterActivateChat(self): + self.notify.error("called enterActivateChat() on parent class") + pass + + def exitActivateChat(self): + self.notify.error("called exitActivateChat() on parent class") + pass + + def enterOtherDialog(self): + assert self.notify.debugStateCall(self) + + def exitOtherDialog(self): + assert self.notify.debugStateCall(self) + + def enterChatMoreInfo(self): + assert self.notify.debugStateCall(self) + # A dialog with lots of information about what it means to + # enable secret friends. + + if self.chatMoreInfo == None: + self.chatMoreInfo = SecretFriendsInfoPanel.SecretFriendsInfoPanel( + 'secretFriendsInfoDone') + self.chatMoreInfo.show() + self.accept('secretFriendsInfoDone', self.__secretFriendsInfoDone) + + def exitChatMoreInfo(self): + assert self.notify.debugStateCall(self) + self.chatMoreInfo.hide() + self.ignore('secretFriendsInfoDone') + + def enterChatPrivacyPolicy(self): + assert self.notify.debugStateCall(self) + # A dialog with lots of information about what it means to + # enable secret friends. + + if self.chatPrivacyPolicy == None: + self.chatPrivacyPolicy = PrivacyPolicyPanel.PrivacyPolicyPanel('privacyPolicyDone') + self.chatPrivacyPolicy.show() + self.accept('privacyPolicyDone', self.__privacyPolicyDone) + + def exitChatPrivacyPolicy(self): + assert self.notify.debugStateCall(self) + cleanupDialog("privacyPolicyDialog") + self.chatPrivacyPolicy = None + self.ignore('privacyPolicyDone') + + def enterSecretChatActivated(self): + self.notify.error("called enterSecretChatActivated() on parent class") + pass + + def exitSecretChatActivated(self): + self.notify.error("called exitSecretChatActivated() on parent class") + pass + + def enterProblemActivatingChat(self): + self.notify.error("called enterProblemActivatingChat() on parent class") + pass + + def exitProblemActivatingChat(self): + self.notify.error("called exitProblemActivatingChat() on parent class") + pass + + def enterTrueFriendTeaserPanel(self): + self.notify.error("called enterTrueFriendTeaserPanel () on parent class") + pass + + def exitTrueFriendTeaserPanel(self): + self.notify.error("called exitTrueFriendTeaserPanel () on parent class") + pass + + def __handleLeaveToPayCancel(self): + assert self.notify.debugStateCall(self) + self.fsm.request("mainMenu") + + def __secretFriendsInfoDone(self): + assert self.notify.debugStateCall(self) + self.fsm.request("activateChat") + + def __privacyPolicyDone(self): + assert self.notify.debugStateCall(self) + self.fsm.request("activateChat") diff --git a/otp/src/chat/ChatManagerV2.py b/otp/src/chat/ChatManagerV2.py new file mode 100644 index 0000000..678f718 --- /dev/null +++ b/otp/src/chat/ChatManagerV2.py @@ -0,0 +1,215 @@ +""" +ChatManagerV2 module: contains the ChatManagerV2 class + +ChatManagerV2 varies from the orginal chat manager in that it is only responsible for chat +warnings. Chat sending is handled by the talkAssistant. This was done so that the GUI and +logic could be free of each other +""" + +import string +import sys +from direct.showbase import DirectObject +from otp.otpbase import OTPGlobals +from direct.fsm import ClassicFSM +from direct.fsm import State +from otp.login import SecretFriendsInfoPanel +from otp.login import PrivacyPolicyPanel +from otp.otpbase import OTPLocalizer +from direct.directnotify import DirectNotifyGlobal +from otp.login import LeaveToPayDialog +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.fsm.FSM import FSM + + +class ChatManagerV2(DirectObject.DirectObject): + """ + contains methods for turning chat inputs + into onscreen thought/word balloons + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ChatManagerV2") + + # special methods + def __init__(self): + self.openChatWarning = None + self.unpaidChatWarning = None + self.teaser = None + self.paidNoParentPassword = None + self.noSecretChatAtAll = None + self.noSecretChatWarning = None + self.chatMoreInfo = None + self.chatPrivacyPolicy = None + self.secretChatActivated = None + self.problemActivatingChat = None + self.leaveToPayDialog = None + + self.fsm = ClassicFSM.ClassicFSM( + 'chatManager', [ + State.State("off", + self.enterOff, + self.exitOff), + State.State("mainMenu", + self.enterMainMenu, + self.exitMainMenu), + State.State("openChatWarning", + self.enterOpenChatWarning, + self.exitOpenChatWarning), + State.State("leaveToPayDialog", + self.enterLeaveToPayDialog, + self.exitLeaveToPayDialog), + State.State("unpaidChatWarning", + self.enterUnpaidChatWarning, + self.exitUnpaidChatWarning), + State.State("noSecretChatAtAll", + self.enterNoSecretChatAtAll, + self.exitNoSecretChatAtAll), + State.State("noSecretChatWarning", + self.enterNoSecretChatWarning, + self.exitNoSecretChatWarning), + State.State("noFriendsWarning", + self.enterNoFriendsWarning, + self.exitNoFriendsWarning), + State.State("otherDialog", + self.enterOtherDialog, + self.exitOtherDialog), + State.State("activateChat", + self.enterActivateChat, + self.exitActivateChat), + State.State("chatMoreInfo", + self.enterChatMoreInfo, + self.exitChatMoreInfo), + State.State("chatPrivacyPolicy", + self.enterChatPrivacyPolicy, + self.exitChatPrivacyPolicy), + State.State("secretChatActivated", + self.enterSecretChatActivated, + self.exitSecretChatActivated), + State.State("problemActivatingChat", + self.enterProblemActivatingChat, + self.exitProblemActivatingChat), + ], + "off", + "off", + ) + self.fsm.enterInitialState() + + self.accept("Chat-Failed open typed chat test", self.__handleFailOpenTypedChat) + self.accept("Chat-Failed player typed chat test", self.__handleFailPlayerTypedWhsiper) + self.accept("Chat-Failed avatar typed chat test", self.__handleFailAvatarTypedWhsiper) + + def delete(self): + self.ignoreAll() + del self.fsm + + def __handleFailOpenTypedChat(self, caller = None): + self.fsm.request("openChatWarning") + + def __handleFailPlayerTypedWhsiper(self, caller = None): + self.fsm.request("noSecretChatWarning") + + def __handleFailAvatarTypedWhsiper(self, caller = None): + self.fsm.request("noSecretChatWarning") + + def __handleLeaveToPayCancel(self): + assert self.notify.debugStateCall(self) + self.fsm.request("mainMenu") + + def __secretFriendsInfoDone(self): + assert self.notify.debugStateCall(self) + self.fsm.request("activateChat") + + def __privacyPolicyDone(self): + assert self.notify.debugStateCall(self) + self.fsm.request("activateChat") + + def enterOff(self): + assert self.notify.debugStateCall(self) + self.ignoreAll() + + def exitOff(self): + assert self.notify.debugStateCall(self) + pass + + def enterOtherDialog(self): + assert self.notify.debugStateCall(self) + + def exitOtherDialog(self): + assert self.notify.debugStateCall(self) + + + def enterUnpaidChatWarning(self): + self.notify.error("called enterUnpaidChatWarning() on parent class") + pass + + def exitUnpaidChatWarning(self): + self.notify.error("called exitUnpaidChatWarning() on parent class") + pass + + def enterNoFriendsWarning(self): + self.notify.error("called enterNoFriendsWarning() on parent class") + pass + + def exitNoFriendsWarning(self): + self.notify.error("called exitNoFriendsWarning() on parent class") + pass + + def enterSecretChatActivated(self): + self.notify.error("called enterSecretChatActivated() on parent class") + pass + + def exitSecretChatActivated(self): + self.notify.error("called exitSecretChatActivated() on parent class") + pass + + def enterProblemActivatingChat(self): + self.notify.error("called enterProblemActivatingChat() on parent class") + pass + + def exitProblemActivatingChat(self): + self.notify.error("called exitProblemActivatingChat() on parent class") + pass + + def enterChatPrivacyPolicy(self): + """ + A dialog with lots of information about what it means to + enable secret friends. + """ + self.notify.error("called enterChatPrivacyPolicy() on parent class") + pass + + def exitChatPrivacyPolicy(self): + self.notify.error("called exitChatPrivacyPolicy() on parent class") + pass + + def enterChatMoreInfo(self): + """ + A dialog with lots of information about what it means to + enable secret friends. + """ + self.notify.error("called enterChatMoreInfo() on parent class") + pass + + def exitChatMoreInfo(self): + self.notify.error("called exitChatMoreInfo() on parent class") + pass + + def enterNoSecretChatWarning(self): + self.notify.error("called enterNoSecretChatWarning() on parent class") + pass + + def exitNoSecretChatWarning(self): + self.notify.error("called exitNoSecretChatWarning() on parent class") + pass + + def enterLeaveToPayDialog(self): + """ + Tell the user that we will exit the 3D client and take + them to a web page. + """ + self.notify.error("called enterLeaveToPayDialog() on parent class") + pass + + def exitLeaveToPayDialog(self): + self.notify.error("called exitLeaveToPayDialog() on parent class") + pass + diff --git a/otp/src/chat/ChatMessage.py b/otp/src/chat/ChatMessage.py new file mode 100644 index 0000000..c6e0b3e --- /dev/null +++ b/otp/src/chat/ChatMessage.py @@ -0,0 +1,79 @@ +class ChatMessage: + def __init__(self, + timeStamp, + type, + body, + flags, + id, + name, + isPlayer, + whisper, + sentRatherThanReceived, + + ): + + self.timeStamp = timeStamp + self.type = type + self.body = body + self.flags = flags + self.id = id + self.name = name + self.isPlayer = isPlayer + self.whisper = whisper + self.sentRatherThanReceived = sentRatherThanReceived + + def getTimeStamp(self): + return self.timeStamp + + def setTimeStamp(self, timeStamp): + self.timeStamp = timeStamp + + def getType(self): + return self.type + + def setType(self, type): + self.name = type + + def getBody(self): + return self.body + + def setBody(self, body): + self.body = body + + def getFlags(self): + return self.flags + + def setFlags(self, flags): + self.flags = flags + + def getId(self): + return self.id + + def setId(self, id): + self.id = id + + def getName(self): + return self.name + + def setName(self, name): + self.name = name + + def getIsPlayer(self): + return self.isPlayer + + def setIsPlayer(self, isPlayer): + self.isPlayer = isPlayer + + def getSentRatherThanReceived(self): + return self.sentRatherThanReceived + + def setSentRatherThanReceived(self, sentRatherThanReceived): + self.sentRatherThanReceived = sentRatherThanReceived + + def getWhisper(self): + return self.whisper + + def setWhisper(self, whisper): + self.whisper = whisper + + \ No newline at end of file diff --git a/otp/src/chat/Sources.pp b/otp/src/chat/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/chat/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/chat/TalkAssistant.py b/otp/src/chat/TalkAssistant.py new file mode 100644 index 0000000..3a4b06e --- /dev/null +++ b/otp/src/chat/TalkAssistant.py @@ -0,0 +1,1152 @@ +import string +import sys +from direct.showbase import DirectObject +from otp.otpbase import OTPLocalizer +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import OTPGlobals +from otp.speedchat import SCDecoders +from pandac.PandaModules import * +from otp.chat.TalkMessage import TalkMessage +from otp.chat.TalkHandle import TalkHandle +import time +from otp.chat.TalkGlobals import * +from otp.chat.ChatGlobals import * +from libotp import CFSpeech, CFTimeout, CFThought + + +""" +The purpose of this assistant is to organize everything you need +for chat into one class. It does not rewire the paths for talk. +It is only client side and does not actually send or receive any +messages. + +It's become required because talk functionality has become fractured +and is now spread between the playerFriendsManager, the +avatarFriendsManager, the clientRepository, and the DistributedAvatar. +This assistant makes all this functionality available from a single +point of contact. + +The only unique functionality is does is talk logging. + +It contains helper functions to send and receive talk and logs that +chat whenever these helper functions are used. Each sender function +is hardwired to another function, and each receiver function must be +hardwired into another function: + +Example: + Calling sendOpenSpeedChat also calls base.localAvatar.b_setSC + from within the assistant + +Counter Example: + Calling avatar.setSC needs to call receiveWhisperSpeedChat + from outside the assistant + +This assistant also contains functions to determine whether or not +you can chat with any given avatar or player, one example is +'checkWhisperTypedChatAvatar'. The idea is that these checks +relate directly to the gui buttons a player would use to send messages. + +JML + +talk message data structure is ( + timeStamp, #0 + body, #1 + senderAvatarId, #2 + senderAvatarName, #3 + senderAccountId, #4 + senderAccountName, #5 + receiverAvatarId, #6 + receiverAvatarName, #7 + receiverAccountId, #8 + receiverAccountName,#9 + talkType, #10 + extraInfo, #11 + ) + +(timeStamp, message, doId, accountId, avatarName, accountName, type, typeInfo) +""" + + +ThoughtPrefix = '.' + +class TalkAssistant(DirectObject.DirectObject): + ExecNamespace = None + """ + contains methods for turning chat inputs + into onscreen thought/word balloons + """ + notify = DirectNotifyGlobal.directNotify.newCategory("TalkAssistant") + execChat = base.config.GetBool("exec-chat", 0) + + def __init__(self): + assert self.notify.debug("TalkAssistant") + # Store the client repository + self.logWhispers = 1 + self.whiteList = None + self.clearHistory() + self.zeroTimeDay = time.time() + self.zeroTimeGame = globalClock.getRealTime() + self.floodThreshold = 10.0 + + self.useWhiteListFilter = base.config.GetBool('white-list-filter-openchat', 0) + + self.lastWhisperDoId = None + self.lastWhisperPlayerId = None + self.lastWhisper = None + + self.SCDecoder = SCDecoders + + +#SETUP AND CLEANUP + + def clearHistory(self): + self.historyComplete = [] #all messages + self.historyOpen = [] #open messages + self.historyUpdates = [] #game updates (exp, friend logins, leveling, etc...) + self.historyGuild = [] #guild messages and updates + + self.historyByDoId = {} #messages to and from a certain doId + self.historyByDISLId = {} #message to and from a certain DISLId + + self.floodDataByDoId = {} + + self.labelGuild = OTPLocalizer.TalkGuild + + self.handleDict = {} + + self.messageCount = 0 + + self.shownWhiteListWarning = 0 + + def delete(self): + self.ignoreAll() + self.clearHistory() + + def start(self): + pass + + def stop(self): + pass + +# DATA RETRIEVAL + def countMessage(self): + self.messageCount += 1 + return self.messageCount - 1 + + def getOpenText(self, numLines, startPoint = 0): + return self.historyOpen[startPoint:startPoint + numLines] + + def getSizeOpenText(self): + return len(self.historyOpen) + + def getCompleteText(self, numLines, startPoint = 0): + return self.historyComplete[startPoint:startPoint + numLines] + + def getCompleteTextFromRecent(self, numLines, startPoint = 0): + #print("getCompleteText start %s numLines %s" % (startPoint, numLines)) + start = len(self.historyComplete) - startPoint + if start < 0: + start = 0 + + backStart = max(start-numLines, 0) + + #print start + + text = self.historyComplete[backStart:start] + text.reverse() + return text + + def getAllCompleteText(self): + return self.historyComplete + + def getAllHistory(self): + return self.historyComplete + + def getSizeCompleteText(self): + return len(self.historyComplete) + + def getHandle(self, doId): + return self.handleDict.get(doId) + +# DATA ADDING + + def doWhiteListWarning(self): + pass + + def addToHistoryDoId(self, message, doId, scrubbed = 0): + if (message.getTalkType() == TALK_WHISPER) and (doId != localAvatar.doId): + self.lastWhisperDoId = doId + self.lastWhisper = self.lastWhisperDoId + + if not self.historyByDoId.has_key(doId): + self.historyByDoId[doId] = [] + self.historyByDoId[doId].append(message) + + if (not self.shownWhiteListWarning) and scrubbed and (doId == localAvatar.doId): + self.doWhiteListWarning() + self.shownWhiteListWarning = 1 + + if not self.floodDataByDoId.has_key(doId): + self.floodDataByDoId[doId] = [0.0, self.stampTime(), message] #floodRating, lastTime, message + else: + oldTime = self.floodDataByDoId[doId][1] + newTime = self.stampTime() + timeDiff = newTime - oldTime + oldRating = self.floodDataByDoId[doId][0] + contentMult = 1.0 + if len(message.getBody()) < 6: + contentMult += (0.2 * float(6 - len(message.getBody()))) + if self.floodDataByDoId[doId][2].getBody() == message.getBody(): + contentMult += 1.0 + floodRating = max(0, ((3.0 * contentMult) + oldRating - timeDiff)) + #print ("Flood Time Diff %s %s %s" % (oldTime, newTime, timeDiff)) + #print("Flood Rating %s" % (floodRating)) + self.floodDataByDoId[doId] = [floodRating, self.stampTime(), message] + + if floodRating > self.floodThreshold: + if oldRating < self.floodThreshold: + self.floodDataByDoId[doId] = [floodRating + 3.0, self.stampTime(), message] + #print("start rejecting") + return 1 + else: + self.floodDataByDoId[doId] = [oldRating - timeDiff, self.stampTime(), message] + #print ("don't even log this") + return 2 + + return 0 + + def addToHistoryDISLId(self, message, dISLId, scrubbed = 0): + if (message.getTalkType() == TALK_ACCOUNT) and (dISLId != base.cr.accountDetailRecord.playerAccountId): + self.lastWhisperPlayerId = dISLId + self.lastWhisper = self.lastWhisperPlayerId + if not self.historyByDISLId.has_key(dISLId): + self.historyByDISLId[dISLId] = [] + self.historyByDISLId[dISLId].append(message) + + def addHandle(self, doId, message): + if doId == localAvatar.doId: + return + handle = self.handleDict.get(doId) + if not handle: + handle = TalkHandle(doId, message) + self.handleDict[doId] = handle + else: + handle.addMessageInfo(message) + +# DATA CHECKING + + def stampTime(self): + # because time.time is giving me so much trouble I'm abstracting the time fuction + # note: time.time doesn't work correctly. While running directX it only updates once + # every few minutes, this appears to be a directX "feature" + #return self.zeroTimeDay + (globalClock.getRealTime() - self.zeroTimeGame) + return (globalClock.getRealTime() - self.zeroTimeGame) + +# HELPER FUNCTIONS + + def findName(self, id, isPlayer = 0): + if isPlayer: + return self.findPlayerName(id) + else: + return self.findAvatarName(id) + + + def findAvatarName(self, id): + info = base.cr.identifyAvatar(id) + if info: + return info.getName() + else: + return '' + + def findPlayerName(self, id): + info = base.cr.playerFriendsManager.getFriendInfo(id) + if info: + return info.playerName + else: + return '' + + def whiteListFilterMessage(self, text): + if not self.useWhiteListFilter: + return text + elif not base.whiteList: + return "no list" + words = text.split(" ") + newwords = [] + for word in words: + if word == "" or base.whiteList.isWord(word): + newwords.append(word) + else: + newwords.append(base.whiteList.defaultWord) + + newText = " ".join(newwords) + return newText + + + def colorMessageByWhiteListFilter(self, text): + if not base.whiteList: + return text + words = text.split(" ") + newwords = [] + for word in words: + if word == "" or base.whiteList.isWord(word): + newwords.append(word) + else: + newwords.append("\1WLRed\1" + word + "\2") + + newText = " ".join(newwords) + return newText + + def executeSlashCommand(self, text): + pass + + def executeGMCommand(self, text): + pass + + def isThought(self, message): + """ + message is a string. + + Return 1 if the given string contains the thought prefix, + Return 0 otherwise + """ + if not message: + return 0 + elif (len(message) == 0): + # empty string cannot be a thought + return 0 + elif (string.find(message, ThoughtPrefix, 0, + len(ThoughtPrefix)) >= 0): + return 1 + else: + return 0 + + def removeThoughtPrefix(self, message): + """ + message is a string. + + Return the string with the thought prefix removed + """ + if (self.isThought(message)): + return message[len(ThoughtPrefix):] + else: + return message + + def fillWithTestText(self): + hold = self.floodThreshold + self.floodThreshold = 1000.0 + self.receiveOpenTalk(1001, "Bob the Ghost", None, None, "Hello from the machine") + self.receiveOpenTalk(1001, "Bob the Ghost", None, None, "More text for ya!") + self.receiveOpenTalk(1001, "Bob the Ghost", None, None, "Hope this makes life easier") + self.receiveOpenTalk(1002, "Doug the Spirit", None, None, "Now we need some longer text that will spill over onto two lines") + self.receiveOpenTalk(1002, "Doug the Spirit", None, None, "Maybe I will tell you") + self.receiveOpenTalk(1001, "Bob the Ghost", None, None, "If you are seeing this text it is because you are cool") + self.receiveOpenTalk(1002, "Doug the Spirit", None, None, "That's right, there is no need to call tech support") + self.receiveOpenTalk(localAvatar.doId, localAvatar.getName, None, None, "Okay I won't call tech support, because I am cool") + self.receiveGMTalk(1003, "God of Text", None, None, "Good because I have seen it already") + self.floodThreshold = hold + + def printHistoryComplete(self): + print("HISTORY COMPLETE") + for message in self.historyComplete: + print("%s %s %s\n%s\n" % (message.getTimeStamp(), message.getSenderAvatarName(), message.getSenderAccountName(), message.getBody())) + + #################################### + # Exec-chat functions + def importExecNamespace(self): + # Derived classes should take advantage of this hook to import + # useful variables into the chat namespace for developer + # access. + pass + + def execMessage(self, message): + print ("execMessage %s" % (message)) + if not TalkAssistant.ExecNamespace: + # Import some useful variables into the ExecNamespace initially. + TalkAssistant.ExecNamespace = { } + exec 'from pandac.PandaModules import *' in globals(), self.ExecNamespace + self.importExecNamespace() + + # Now try to evaluate the expression using ChatInputTyped.ExecNamespace as + # the local namespace. + + try: + return str(eval(message, globals(), TalkAssistant.ExecNamespace)) + + except SyntaxError: + # Maybe it's only a statement, like "x = 1", or + # "import math". These aren't expressions, so eval() + # fails, but they can be exec'ed. + try: + exec message in globals(), TalkAssistant.ExecNamespace + return "ok" + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + except: + exception = sys.exc_info()[0] + extraInfo = sys.exc_info()[1] + if extraInfo: + return str(extraInfo) + else: + return str(exception) + +# CHAT PERMISSIONS + + def checkOpenTypedChat(self): + if base.localAvatar.commonChatFlags & OTPGlobals.CommonChat: + return True + return False + + def checkAnyTypedChat(self): + """Return True if we have any form of typed chat, whitelist, truefriend or open.""" + if base.localAvatar.commonChatFlags & OTPGlobals.CommonChat: + return True + if base.localAvatar.canChat(): + return True + return False + + def checkOpenSpeedChat(self): + return True + + def checkWhisperTypedChatAvatar(self, avatarId): + remoteAvatar = base.cr.doId2do.get(avatarId) + if remoteAvatar: + # check for open chat or whitelist chat + if remoteAvatar.isUnderstandable(): + return True + + if base.localAvatar.commonChatFlags & OTPGlobals.SuperChat: + return True + + # check for toon friends in different zones + remoteAvatarOrHandleOrInfo = base.cr.identifyAvatar(avatarId) + if remoteAvatarOrHandleOrInfo and hasattr(remoteAvatarOrHandleOrInfo,'isUnderstandable'): + if remoteAvatarOrHandleOrInfo.isUnderstandable(): + return True + + # check for player friends + info = base.cr.playerFriendsManager.findPlayerInfoFromAvId(avatarId) + if info: + if info.understandableYesNo: + return True + + # check for avatar friend and we both have open chat + info = base.cr.avatarFriendsManager.getFriendInfo(avatarId) + if info: + if info.understandableYesNo: + return True + + # check for true avatar friends + if base.cr.getFriendFlags(avatarId) & OTPGlobals.FriendChat: + return True + + return False + + def checkWhisperSpeedChatAvatar(self, avatarId): + return True + + def checkWhisperTypedChatPlayer(self, playerId): + info = base.cr.playerFriendsManager.getFriendInfo(playerId) + if info: + if info.understandableYesNo: + return True + return False + + def checkWhisperSpeedChatPlayer(self, playerId): + if base.cr.playerFriendsManager.isPlayerFriend(playerId): + return True + return False + + def checkOpenSpeedChat(self): + return True + + def checkWhisperSpeedChatAvatar(self, avatarId): + return True + + def checkWhisperSpeedChatPlayer(self, playerId): + if base.cr.playerFriendsManager.isPlayerFriend(playerId): + return True + return False + + def checkGuildTypedChat(self): + if localAvatar.guildId: + return True + return False + + def checkGuildSpeedChat(self): + if localAvatar.guildId: + return True + return False + + +#RECEIVE TALK + + def receiveOpenTalk(self, avatarId, avatarName, accountId, accountName, message, scrubbed = 0): + error = None + if (not avatarName) and (avatarId): + avatarName = self.findAvatarName(avatarId) + if (not accountName) and (accountId): + accountName = self.findPlayerName(accountId) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + avatarId, #senderAvatarId + avatarName, #senderAvatarName + accountId, #senderAccountId + accountName, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_OPEN, #talkType + None) #extraInfo + + if avatarId != localAvatar.doId: + self.addHandle(avatarId, newMessage) + + reject = 0 + if avatarId: + reject = self.addToHistoryDoId(newMessage, avatarId, scrubbed) + if accountId: + self.addToHistoryDISLId(newMessage, accountId) + if reject == 1: + newMessage.setBody(OTPLocalizer.AntiSpamInChat) + if reject != 2: + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + + messenger.send("NewOpenMessage", [newMessage]) + + return error + + def receiveWhisperTalk(self, avatarId, avatarName, accountId, accountName, toId, toName, message, scrubbed = 0): + error = None + print ("receiveWhisperTalk %s %s %s %s %s" % (avatarId, avatarName, accountId, accountName, message)) + if (not avatarName) and (avatarId): + avatarName = self.findAvatarName(avatarId) + if (not accountName) and (accountId): + accountName = self.findPlayerName(accountId) + + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + avatarId, #senderAvatarId + avatarName, #senderAvatarName + accountId, #senderAccountId + accountName, #senderAccountName + toId, #receiverAvatarId + toName, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_WHISPER, #talkType + None) #extraInfo + + if avatarId == localAvatar.doId: + self.addHandle(toId, newMessage) + else: + self.addHandle(avatarId, newMessage) + + self.historyComplete.append(newMessage) + + if avatarId: + self.addToHistoryDoId(newMessage, avatarId, scrubbed) + if accountId: + self.addToHistoryDISLId(newMessage, accountId) + messenger.send("NewOpenMessage", [newMessage]) + + return error + + + def receiveAccountTalk(self, avatarId, avatarName, accountId, accountName, toId, toName, message, scrubbed = 0): + if (not accountName) and base.cr.playerFriendsManager.playerId2Info.get(accountId): + accountName = base.cr.playerFriendsManager.playerId2Info.get(accountId).playerName + #print ("Getting Name %s " % (base.cr.playerFriendsManager.playerId2Info.get(accountId).playerName)) + + error = None + if (not avatarName) and (avatarId): + avatarName = self.findAvatarName(avatarId) + if (not accountName) and (accountId): + accountName = self.findPlayerName(accountId) + + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + avatarId, #senderAvatarId + avatarName, #senderAvatarName + accountId, #senderAccountId + accountName, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + toId, #receiverAccountId + toName, #receiverAccountName + TALK_ACCOUNT, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + if avatarId: + self.addToHistoryDoId(newMessage, avatarId, scrubbed) + if accountId: + self.addToHistoryDISLId(newMessage, accountId, scrubbed) + messenger.send("NewOpenMessage", [newMessage]) + + return error + + def receiveGuildTalk(self, fromAv, fromAC, avatarName, message, scrubbed = 0): + error = None + if not self.isThought(message): + + accountName = self.findName(fromAC, 1) + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + fromAv, #senderAvatarId + avatarName, #senderAvatarName + fromAC, #senderAccountId + accountName, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_GUILD, #talkType + None) #typeInfo + + reject = self.addToHistoryDoId(newMessage, fromAv, scrubbed) + if reject == 1: + newMessage.setBody(OTPLocalizer.AntiSpamInChat) + if reject != 2: + self.historyComplete.append(newMessage) + self.historyGuild.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + + return error + + def receiveGMTalk(self, avatarId, avatarName, accountId, accountName, message, scrubbed = 0): + error = None + if (not avatarName) and (avatarId): + avatarName = self.findAvatarName(avatarId) + if (not accountName) and (accountId): + accountName = self.findPlayerName(accountId) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + avatarId, #senderAvatarId + avatarName, #senderAvatarName + accountId, #senderAccountId + accountName, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_GM, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + if avatarId: + self.addToHistoryDoId(newMessage, avatarId) + if accountId: + self.addToHistoryDISLId(newMessage, accountId) + messenger.send("NewOpenMessage", [newMessage]) + + return error + + def receiveThought(self, avatarId, avatarName, accountId, accountName, message, scrubbed = 0): + error = None + if (not avatarName) and (avatarId): + avatarName = self.findAvatarName(avatarId) + if (not accountName) and (accountId): + accountName = self.findPlayerName(accountId) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + avatarId, #senderAvatarId + avatarName, #senderAvatarName + accountId, #senderAccountId + accountName, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + AVATAR_THOUGHT, #talkType + None) #extraInfo + + if avatarId != localAvatar.doId: + self.addHandle(avatarId, newMessage) + + reject = 0 + if avatarId: + reject = self.addToHistoryDoId(newMessage, avatarId, scrubbed) + if accountId: + self.addToHistoryDISLId(newMessage, accountId) + if reject == 1: + newMessage.setBody(OTPLocalizer.AntiSpamInChat) + if reject != 2: + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + + messenger.send("NewOpenMessage", [newMessage]) + + return error + +# RECEIVE MESSAGES + + def receiveGameMessage(self, message): + error = None + if not self.isThought(message): + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + None, #senderAvatarId + None, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + INFO_GAME, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def receiveSystemMessage(self, message): + error = None + if not self.isThought(message): + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + None, #senderAvatarId + None, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + INFO_SYSTEM, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def receiveDeveloperMessage(self, message): + error = None + + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + None, #senderAvatarId + None, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + INFO_DEV, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def receiveGuildMessage(self, message, senderId, senderName): + #Should only be used for speedchat + error = None + if not self.isThought(message): + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + senderId, #senderAvatarId + senderName, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_GUILD, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyGuild.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + return error + + +# RECEIVE UPDATES + + def receiveFriendUpdate(self, friendId, friendName, isOnline): + if isOnline: + onlineMessage = OTPLocalizer.FriendOnline + else: + onlineMessage = OTPLocalizer.FriendOffline + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + onlineMessage, #message Body + friendId, #senderAvatarId + friendName, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + UPDATE_FRIEND, #talkType + None) #extraInfo + + self.addHandle(friendId, newMessage) + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + + def receiveFriendAccountUpdate(self, friendId, friendName, isOnline): + if isOnline: + onlineMessage = OTPLocalizer.FriendOnline + else: + onlineMessage = OTPLocalizer.FriendOffline + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + onlineMessage, #message Body + None, #senderAvatarId + None, #senderAvatarName + friendId, #senderAccountId + friendName, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + UPDATE_FRIEND, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + + def receiveGuildUpdate(self, memberId, memberName, isOnline): + if base.cr.identifyFriend(memberId) is None: + if isOnline: + onlineMessage = OTPLocalizer.GuildMemberOnline + else: + onlineMessage = OTPLocalizer.GuildMemberOffline + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + onlineMessage, #message Body + memberId, #senderAvatarId + memberName, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + UPDATE_GUILD, #talkType + None) #extraInfo + + self.addHandle(memberId, newMessage) + self.historyComplete.append(newMessage) + self.historyUpdates.append(newMessage) + self.historyGuild.append(newMessage) + messenger.send("NewOpenMessage", [newMessage]) + +# RECEIVE SPEEDCHAT + + def receiveOpenSpeedChat(self, type, messageIndex, senderId, name = None): + #print("receiveOpenSpeedChat %s %s %s" %(type, messageIndex, senderId)) + error = None + + if (not name) and (senderId): + name = self.findName(senderId, 0) + + if type == SPEEDCHAT_NORMAL: + message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex) + elif type == SPEEDCHAT_EMOTE: + message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name) + elif type == SPEEDCHAT_CUSTOM: + message = self.SCDecoder.decodeSCCustomMsg(messageIndex) + + if message in (None, ""): + return + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + senderId, #senderAvatarId + name, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_OPEN, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + self.addToHistoryDoId(newMessage, senderId) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def receiveAvatarWhisperSpeedChat(self, type, messageIndex, senderId, name = None): + error = None + + if (not name) and (senderId): + name = self.findName(senderId, 0) + + if type == SPEEDCHAT_NORMAL: + message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex) + elif type == SPEEDCHAT_EMOTE: + message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name) + elif type == SPEEDCHAT_CUSTOM: + message = self.SCDecoder.decodeSCCustomMsg(messageIndex) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + senderId, #senderAvatarId + name, #senderAvatarName + None, #senderAccountId + None, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + TALK_WHISPER, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + self.addToHistoryDoId(newMessage, senderId) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def receivePlayerWhisperSpeedChat(self, type, messageIndex, senderId, name = None): + # dprint("receivePlayerWhisperTypedChat %s %s %s" %(type, messageIndex, senderId)) + error = None + + if (not name) and (senderId): + name = self.findName(senderId, 1) + + if type == SPEEDCHAT_NORMAL: + message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex) + elif type == SPEEDCHAT_EMOTE: + message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name) + elif type == SPEEDCHAT_CUSTOM: + message = self.SCDecoder.decodeSCCustomMsg(messageIndex) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + None, #senderAvatarId + None, #senderAvatarName + senderId, #senderAccountId + name, #senderAccountName + localAvatar.doId, #receiverAvatarId + localAvatar.getName(), #receiverAvatarName + localAvatar.DISLid, #receiverAccountId + localAvatar.DISLname, #receiverAccountName + TALK_WHISPER, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.historyOpen.append(newMessage) + self.addToHistoryDISLId(newMessage, senderId) + messenger.send("NewOpenMessage", [newMessage]) + return error + +# SEND TALK + + def sendOpenTalk(self, message): + error = None + if (base.cr.wantMagicWords and + (len(message) > 0) and (message[0] == "~")): + messenger.send("magicWord", [message]) + self.receiveDeveloperMessage(message) + else: + chatFlags = CFSpeech | CFTimeout + if self.isThought(message): + chatFlags = CFThought + base.localAvatar.sendUpdate("setTalk", [0, 0, "", message, [], 0]) + messenger.send("chatUpdate", [message, chatFlags]) + return error + + def sendWhisperTalk(self, message, receiverAvId): + error = None + receiver = base.cr.doId2do.get(receiverAvId) + if receiver: + receiver.sendUpdate("setTalkWhisper", [0, 0, "", message, [], 0]) + else: + receiver = base.cr.identifyAvatar(receiverAvId) + if receiver: + base.localAvatar.sendUpdate("setTalkWhisper", [0, 0, "", message, [], 0], sendToId = receiverAvId) + else: + pass + return error + + def sendAccountTalk(self, message, receiverAccount): + error = None + base.cr.playerFriendsManager.sendUpdate("setTalkAccount", [receiverAccount,0, "",message, [], 0]) + return error + + def sendGuildTalk(self, message): + error = None + if self.checkGuildTypedChat(): + # Guild chat is sent through the guildManager + base.cr.guildManager.sendTalk(message) + else: + print "Guild chat error" + error = ERROR_NO_GUILD_CHAT + return error + +#SEND SPEED CHAT + + def sendOpenSpeedChat(self, type, messageIndex): + error = None + # Open Avatar speed chat is sent through the avatar + if type == SPEEDCHAT_NORMAL: + messenger.send(SCChatEvent) + messenger.send("chatUpdateSC", [messageIndex]) + base.localAvatar.b_setSC(messageIndex) + + + elif type == SPEEDCHAT_EMOTE: + # do these two events even do anything anymore? + messenger.send("chatUpdateSCEmote", [messageIndex]) + messenger.send(SCEmoteChatEvent) + base.localAvatar.b_setSCEmote(messageIndex) + + elif type == SPEEDCHAT_CUSTOM: + messenger.send("chatUpdateSCCustom", [messageIndex]) + messenger.send(SCCustomChatEvent) + base.localAvatar.b_setSCCustom(messageIndex) + + return error + + def sendAvatarWhisperSpeedChat(self, type, messageIndex, receiverId): + error = None + + if type == SPEEDCHAT_NORMAL: + base.localAvatar.whisperSCTo(messageIndex, receiverId, 0) + message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex) + + + elif type == SPEEDCHAT_EMOTE: + base.localAvatar.whisperSCEmoteTo(messageIndex, receiverId, 0) + message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, localAvatar.getName()) + + elif type == SPEEDCHAT_CUSTOM: + base.localAvatar.whisperSCCustomTo(messageIndex, receiverId, 0) + message = self.SCDecoder.decodeSCCustomMsg(messageIndex) + + + if self.logWhispers: + avatarName = None + accountId = None + avatar = base.cr.identifyAvatar(receiverId) + if avatar: + avatarName = avatar.getName() + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + localAvatar.doId, #senderAvatarId + localAvatar.getName(), #senderAvatarName + localAvatar.DISLid, #senderAccountId + localAvatar.DISLname, #senderAccountName + receiverId, #receiverAvatarId + avatarName, #receiverAvatarName + None, #receiverAccountId + None, #receiverAccountName + TALK_WHISPER, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.addToHistoryDoId(newMessage, localAvatar.doId) + self.addToHistoryDISLId(newMessage, base.cr.accountDetailRecord.playerAccountId) + messenger.send("NewOpenMessage", [newMessage]) + + return error + + def sendPlayerWhisperSpeedChat(self, type, messageIndex, receiverId): + error = None + + if type == SPEEDCHAT_NORMAL: + base.cr.speedchatRelay.sendSpeedchat(receiverId, messageIndex) + message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex) + elif type == SPEEDCHAT_EMOTE: + base.cr.speedchatRelay.sendSpeedchatEmote(receiverId, messageIndex) + message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, localAvatar.getName()) + return + elif type == SPEEDCHAT_CUSTOM: + base.cr.speedchatRelay.sendSpeedchatCustom(receiverId, messageIndex) + message = self.SCDecoder.decodeSCCustomMsg(messageIndex) + + if self.logWhispers: + receiverName = self.findName(receiverId, 1) + + newMessage = TalkMessage(self.countMessage(), #messageNumber + self.stampTime(), #timeStamp + message, #message Body + localAvatar.doId, #senderAvatarId + localAvatar.getName(), #senderAvatarName + localAvatar.DISLid, #senderAccountId + localAvatar.DISLname, #senderAccountName + None, #receiverAvatarId + None, #receiverAvatarName + receiverId, #receiverAccountId + receiverName, #receiverAccountName + TALK_ACCOUNT, #talkType + None) #extraInfo + + self.historyComplete.append(newMessage) + self.addToHistoryDoId(newMessage, localAvatar.doId) + self.addToHistoryDISLId(newMessage, base.cr.accountDetailRecord.playerAccountId) + messenger.send("NewOpenMessage", [newMessage]) + return error + + def sendGuildSpeedChat(self, type, msgIndex): + error = None + if self.checkGuildSpeedChat(): + base.cr.guildManager.sendSC(msgIndex) + else: + print "Guild Speedchat error" + error = ERROR_NO_GUILD_CHAT + return error + + def getWhisperReplyId(self): + if self.lastWhisper: + toPlayer = 0 + if self.lastWhisper == self.lastWhisperPlayerId: + toPlayer = 1 + return (self.lastWhisper, toPlayer) + return (0,0) + + + + + + + diff --git a/otp/src/chat/TalkGlobals.py b/otp/src/chat/TalkGlobals.py new file mode 100644 index 0000000..8d0ed08 --- /dev/null +++ b/otp/src/chat/TalkGlobals.py @@ -0,0 +1,22 @@ +#Talk Types +TALK_NONE = 0 +TALK_OPEN = 1 +TALK_WHISPER = 2 +TALK_ACCOUNT = 13 +TALK_GM = 14 +AVATAR_THOUGHT = 16 + +TALK_GUILD = 3 +TALK_PARTY = 4 +TALK_PVP = 5 + +UPDATE_GUILD = 6 +UPDATE_FRIEND = 7 +UPDATE_PARTY = 8 +UPDATE_PVP = 9 + +INFO_SYSTEM = 10 +INFO_GAME = 11 +INFO_AVATAR_UNAVAILABLE = 12 +INFO_OPEN = 15 +INFO_DEV = 17 diff --git a/otp/src/chat/TalkHandle.py b/otp/src/chat/TalkHandle.py new file mode 100644 index 0000000..d7b905f --- /dev/null +++ b/otp/src/chat/TalkHandle.py @@ -0,0 +1,47 @@ +from otp.avatar.AvatarHandle import AvatarHandle + +class TalkHandle(AvatarHandle): + def __init__(self, doId, message): + self.avatarId = doId + self.avatarName = None + self.accountId = None + self.accountName = None + self.addMessageInfo(message) + + def getName(self): + """ + AvatarHandle interface + """ + return self.avatarName + + def isUnderstandable(self): + """ + AvatarHandle interface + """ + return False + + def isOnline(self): + """ + AvatarHandle interface + """ + return False + + def addMessageInfo(self, message): + if self.avatarId == message.getSenderAvatarId(): + if not self.avatarName and message.getSenderAvatarName(): + self.avatarName = message.getSenderAvatarName() + if not self.accountId and message.getSenderAccountId(): + self.accountId = message.getSenderAccountId() + if not self.accountName and message.getSenderAccountName(): + self.accountName = message.getSenderAccountName() + elif self.avatarId == message.getReceiverAvatarId(): + if not self.avatarName and message.getReceiverAvatarName(): + self.avatarName = message.getReceiverAvatarName() + if not self.accountId and message.getReceiverAccountId(): + self.accountId = message.getReceiverAccountId() + if not self.accountName and message.getReceiverAccountName(): + self.accountName = message.getReceiverAccountName() + + def setTalkWhisper(self, fromAV, fromAC, avatarName, chat, mods, flags): + newText, scrubbed = localAvatar.scrubTalk(chat, mods) + base.talkAssistant.receiveWhisperTalk(fromAV, avatarName, fromAC, None, self.avatarId, self.getName(), newText, scrubbed) \ No newline at end of file diff --git a/otp/src/chat/TalkMessage.py b/otp/src/chat/TalkMessage.py new file mode 100644 index 0000000..a25c0c2 --- /dev/null +++ b/otp/src/chat/TalkMessage.py @@ -0,0 +1,124 @@ +class TalkMessage: + def __init__(self, + messageId, + timeStamp, + body, + senderAvatarId, + senderAvatarName, + senderAccountId, + senderAccountName, + receiverAvatarId, + receiverAvatarName, + receiverAccountId, + receiverAccountName, + talkType, + extraInfo = None, + ): + + self.timeStamp = timeStamp + self.body = body + + self.senderAvatarId = senderAvatarId + self.senderAvatarName = senderAvatarName + self.senderAccountId = senderAccountId + self.senderAccountName = senderAccountName + + self.receiverAvatarId = receiverAvatarId + self.receiverAvatarName = receiverAvatarName + self.receiverAccountId = receiverAccountId + self.receiverAccountName = receiverAccountName + + self.talkType = talkType + self.extraInfo = extraInfo + + self.messageId = messageId + + def getMessageId(self): + return self.messageId + + def setMessageId(self, id): + self.messageId = id + + def getTimeStamp(self): + return self.timeStamp + + def setTimeStamp(self, timeStamp): + self.timeStamp = timeStamp + + def getBody(self): + return self.body + + def setBody(self, body): + self.body = body + + + def getSenderAvatarId(self): + return self.senderAvatarId + + def setSenderAvatarId(self, senderAvatarId): + self.senderAvatarId = senderAvatarId + + def getSenderAvatarName(self): + return self.senderAvatarName + + def setSenderAvatarName(self, senderAvatarName): + self.senderAvatarName = senderAvatarName + + + def getSenderAccountId(self): + return self.senderAccountId + + def setSenderAccountId(self, senderAccountId): + self.senderAccountId = senderAccountId + + def getSenderAccountName(self): + return self.senderAccountName + + def setSenderAccountName(self, senderAccountName): + self.senderAccountName = senderAccountName + + + def getReceiverAvatarId(self): + return self.receiverAvatarId + + def setReceiverAvatarId(self, receiverAvatarId): + self.receiverAvatarId = receiverAvatarId + + def getReceiverAvatarName(self): + return self.receiverAvatarName + + def setReceiverAvatarName(self, receiverAvatarName): + self.receiverAvatarName = receiverAvatarName + + + def getReceiverAccountId(self): + return self.receiverAccountId + + def setReceiverAccountId(self, receiverAccountId): + self.receiverAccountId = receiverAccountId + + def getReceiverAccountName(self): + return self.receiverAccountName + + def setReceiverAccountName(self, receiverAccountName): + self.receiverAccountName = receiverAccountName + + + def getTalkType(self): + return self.talkType + + def setTalkType(self, talkType): + self.talkType = talkType + + def getExtraInfo(self): + return self.extraInfo + + def setExtraInfo(self, extraInfo): + self.extraInfo = extraInfo + + + + + + + \ No newline at end of file diff --git a/otp/src/chat/WhiteList.py b/otp/src/chat/WhiteList.py new file mode 100644 index 0000000..d8b4b1b --- /dev/null +++ b/otp/src/chat/WhiteList.py @@ -0,0 +1,65 @@ +from bisect import bisect_left +import string +import sys +import os + +class WhiteList: + """ + Base class for white list chat filtering. Accepts a list of good words and offers + filtering functions performed against that list. + """ + def __init__(self,wordlist): + self.words = [] + + for line in wordlist: + self.words.append(line.strip("\n\r").lower()) + + self.words.sort() + + self.numWords = len(self.words) + assert self.numWords > 2 + + def cleanText(self,text): + text = text.strip(".,?!") + text = text.lower() + return text + + def isWord(self,text): + text = self.cleanText(text) + i = bisect_left(self.words,text) + + if i == self.numWords: + return False + + return self.words[i] == text + + def isPrefix(self,text): + text = self.cleanText(text) + i = bisect_left(self.words,text) + + if i == self.numWords: + return False + + return self.words[i].startswith(text) + + def prefixCount(self,text): + text = self.cleanText(text) + i = bisect_left(self.words,text) + + j = i + + while j < self.numWords and self.words[j].startswith(text): + j += 1 + + return j - i + + def prefixList(self,text): + text = self.cleanText(text) + i = bisect_left(self.words,text) + + j = i + + while j < self.numWords and self.words[j].startswith(text): + j += 1 + + return self.words[i:j] diff --git a/otp/src/chat/__init__.py b/otp/src/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/configfiles/.cvsignore b/otp/src/configfiles/.cvsignore new file mode 100644 index 0000000..ada3421 --- /dev/null +++ b/otp/src/configfiles/.cvsignore @@ -0,0 +1,5 @@ +.cvsignore +Makefile +50_otp.prc +*.pyc +pp.dep diff --git a/otp/src/configfiles/Sources.pp b/otp/src/configfiles/Sources.pp new file mode 100644 index 0000000..81c46e7 --- /dev/null +++ b/otp/src/configfiles/Sources.pp @@ -0,0 +1,5 @@ +#define INSTALL_CONFIG \ + otp.init 50_otp.prc + + +#include $[THISDIRPREFIX]otp.prc.pp diff --git a/otp/src/configfiles/certificates.txt b/otp/src/configfiles/certificates.txt new file mode 100644 index 0000000..d9d038a --- /dev/null +++ b/otp/src/configfiles/certificates.txt @@ -0,0 +1,119 @@ +This file contains SSL public-key certificates to allow the client to +confirm the identify of our various account servers when logging in. + + +This one is for VeriSign. We are no longer using VeriSign to sign our +certificate. + +subject=/C=US/O=RSA Data Security, Inc./OU=Secure Server Certification Authority +notBefore=Nov 9 00:00:00 1994 GMT +notAfter=Jan 7 23:59:59 2010 GMT +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0 +MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV +BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy +dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ +ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII +0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI +uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI +hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3 +YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc +1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- + + +This is the old authority we are using at the moment. + +GTE CyberTrust Root CA +====================== +MD5 Fingerprint: C4:D7:F0:B2:A3:C5:7D:61:67:F0:04:CD:43:D3:BA:58 +PEM Data: +-----BEGIN CERTIFICATE----- +MIIB+jCCAWMCAgGjMA0GCSqGSIb3DQEBBAUAMEUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xHDAaBgNVBAMTE0dURSBDeWJlclRydXN0IFJv +b3QwHhcNOTYwMjIzMjMwMTAwWhcNMDYwMjIzMjM1OTAwWjBFMQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMRwwGgYDVQQDExNHVEUgQ3liZXJU +cnVzdCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC45k+625h8cXyv +RLfTD0bZZOWTwUKOx7pJjTUteueLveUFMVnGsS8KDPufpz+iCWaEVh43KRuH6X4M +ypqfpX/1FZSj1aJGgthoTNE3FQZor734sLPwKfWVWgkWYXcKIiXUT0Wqx73llt/5 +1KiOQswkwB6RJ0q1bQaAYznEol44AwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBABKz +dcZfHeFhVYAA1IFLezEPI2PnPfMD+fQ2qLvZ46WXTeorKeDWanOB5sCJo9Px4KWl +IjeaY8JIILTbcuPI9tl8vrGvU9oUtCG41tWW4/5ODFlitppK+ULdjG+BqXH/9Apy +bW1EDp3zdHSo1TRJ6V6e6bR64eVaH4QwnNOfpSXY +-----END CERTIFICATE----- + +This is the new authority + +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +This is the old intermediary + +-----BEGIN CERTIFICATE----- +MIID8TCCA1qgAwIBAgIEBAADaTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMRwwGgYDVQQDExNHVEUgQ3liZXJU +cnVzdCBSb290MB4XDTA0MDIxMzIwMTQwMFoXDTA2MDIxNDIzNTkwMFowWTELMAkG +A1UEBhMCVVMxGzAZBgNVBAoTEkRpc25leSBFbnRlcnByaXNlczENMAsGA1UECxME +V0RJRzEeMBwGA1UEAxMVRGlzbmV5IEVudGVycHJpc2VzIENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1HrbfLhJJiEh08clD4p7ImZwXJGkSCLiMjhy +W2ZqG+4F31NI5IRuGvi6bYS0JHLGrZlwzOwVYPNJRkw456V9ySiGkn7asYA6UpeX +BKcpvEHzhMECcerASfJkRJGt8LTBMKj+j1Ba6tRFTGJB5ltImTWu8dUK4zmyCIKG +t5ncNQZ9b4okwUepcm066puW+ZdBwVz3mc1QLJd/tqWk+3pG7jXpwe57hOzm8bzc +WN1++Auj3ul5ixO/vSWdcaYcUGZfSIv9cxd4t+ZX+a1416HNbOaWWEDQZL5zdKPv +7+8VMFOHbc4T+uHbTlypq7MQ2N6yQ+Jc0aKFBcbXzSBLtCsDnwIDAQABo4IBVDCC +AVAwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL3d3dy5wdWJsaWMtdHJ1c3QuY29t +L2NnaS1iaW4vQ1JMLzIwMDYvY2RwLmNybDAdBgNVHQ4EFgQUFWzskgNWiGGCVe0x +XzI+hfB446UwVAYDVR0gBE0wSzBJBgoqhkiG+GMBAgEFMDswOQYIKwYBBQUHAgEW +LWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMvT21uaVJvb3QuaHRtbDBu +BgNVHSMEZzBlgBRYpN7jZfL+IfQC8xexeClpZIuW2qFJpEcwRTELMAkGA1UEBhMC +VVMxGDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEcMBoGA1UEAxMTR1RFIEN5YmVy +VHJ1c3QgUm9vdIICAaMwDgYDVR0PAQH/BAQDAgHGMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDQYJKoZIhvcNAQEFBQADgYEAjMKpK/cOEY8Jefw6jp1BOPvzLDpfKV8Yk3jh +M+o2xG/0Hqfj6A5+EsZOtkZbBLo+btB+RWtdcb5OY+5TWJ/I4TLvAspcwJK4uAvO +U1zRB5fsB62g/EqMVD2YSMxWjj4OPXC/ylzzSBXblZh8kG/S6xdOZ0joMqdNAwti +XLyzTjI= +-----END CERTIFICATE----- + +This is the new intermediary + +-----BEGIN CERTIFICATE----- +MIIEXTCCA8agAwIBAgIEBAADyTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MB4XDTA1MDIxMDIwMTUwMFoXDTEwMDIxMDIzNTkwMFowYzELMAkG +A1UEBhMCVVMxGzAZBgNVBAoTEkRpc25leSBFbnRlcnByaXNlczENMAsGA1UECxME +V0RJRzEoMCYGA1UEAxMfRGlzbmV5IEVudGVycHJpc2VzIENBIFZlcnNpb24gMjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2n/e7OBIWirj13JFzaSP8h +lT5FBDMtRa0vN8J+lqTXpcRBsp/izfHEC+nXl3dNTTRaQ5UMEpDsK0LzBZJ8mJwV +7Wv0vL6wJJNtzEKJZFrGIUnypMP3Mz4NARl11CSV/Jyi7IrhQYAts4QYj8ASfiim +S/s1soM4tzETxgGY/44I5Fx98+8mMn3vFYW4eDVjxNuTaqEgJ0pXw1GsuI2EbcM2 +SvpCDeTgRQkofLsaqxX3Mnddn5yKx24dCYPk+z9kWGbp4OSfk1scGK90J5x7kQOD +G5AiViUoLdv5Nmngbm78AtDwobFhr0Sw+w7up0oIHtp9GN7cNMVvEdSFK05xyTkC +AwEAAaOCAYYwggGCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGlj +LXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFKVz +dBkWpmhHGBrXalZmh3RuKtVRMFMGA1UdIARMMEowSAYJKwYBBAGxPgEAMDswOQYI +KwYBBQUHAgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMvT21uaVJv +b3QuaHRtbDCBoAYDVR0jBIGYMIGVgBSmDB2fYf8HF7W/OEbbQzDVjrBSBqF5pHcw +dTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEnMCUGA1UE +CxMeR1RFIEN5YmVyVHJ1c3QgU29sdXRpb25zLCBJbmMuMSMwIQYDVQQDExpHVEUg +Q3liZXJUcnVzdCBHbG9iYWwgUm9vdIICAaUwDgYDVR0PAQH/BAQDAgHGMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQEFBQADgYEAHs7TTLhrA72ikDGRGEDM +wTimVDcGEn6uTiGZlH96TRa0aWphrgtiHhUKVqK1J0bGtYEu4pOeI6VgAe3NmFkn +I81JT4eaK99MytZLFDTxJBCyto0VvGy2Ha7IWkT9v/3qxNk+15zKiMGBAEV2ojIB +5jCXGdQuHPDvXO3KuRS57s0= +-----END CERTIFICATE----- diff --git a/otp/src/configfiles/gameserver.txt b/otp/src/configfiles/gameserver.txt new file mode 100644 index 0000000..6981e3e --- /dev/null +++ b/otp/src/configfiles/gameserver.txt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3jCCAkegAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtDELMAkGA1UEBhMCVVMx +EzARBgNVBAgTCkNhbGlmb3JuaWExGDAWBgNVBAcTD05vcnRoIEhvbGx5d29vZDEb +MBkGA1UEChMSRGlzbmV5IEVudGVycHJpc2VzMQ0wCwYDVQQLEwRXRElHMSAwHgYD +VQQDExdnYW1lc2VydmVyLnRvb250b3duLmNvbTEoMCYGCSqGSIb3DQEJARYZdG9v +bnRvd25AZGlzbmV5b25saW5lLmNvbTAeFw0wMzA5MTgwNTMwMjhaFw0wNjA5MTcw +NTMwMjhaMIG0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEYMBYG +A1UEBxMPTm9ydGggSG9sbHl3b29kMRswGQYDVQQKExJEaXNuZXkgRW50ZXJwcmlz +ZXMxDTALBgNVBAsTBFdESUcxIDAeBgNVBAMTF2dhbWVzZXJ2ZXIudG9vbnRvd24u +Y29tMSgwJgYJKoZIhvcNAQkBFhl0b29udG93bkBkaXNuZXlvbmxpbmUuY29tMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiNKkoSGdeBvMgsHpRWePENUW5ja9w +LTwbyinbDDIyJ8JIEQTPNxYosWvqhZGj+QEI3ya5KJM58LniuGHPbnC+C+XTEwwS +XZXIaMJjNT2GJ3gpJWytgVrk+A6zSaT2PHyE9wAPBF7lbBvLxCFUQ+N0I4QK0tus +k1rqixVGrqGz2wIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAAcgKbTgDY34c881vLrB +o2QqGqauXko9G8surSDbFjZWwbcIyGJyhwMCXzHQCLh9yruMbCMq5S7LZpS4yslX +aH3iboemYE7xT7RoFU4pPU5EQ2hJ+M5v79H81ubnuFrdOhk7pyo67Pn508X62V+j +eic2l1RcbBc5s9iq3+F2Ns5l +-----END CERTIFICATE----- diff --git a/otp/src/configfiles/otp.dc b/otp/src/configfiles/otp.dc new file mode 100644 index 0000000..7daae4a --- /dev/null +++ b/otp/src/configfiles/otp.dc @@ -0,0 +1,824 @@ +// See Also: "direct/src/doc/sample.dc" +// ownrecv: used to mean only send to owner/creator. Currently not used. +// required: send on create/generate +// broadcast: send on set and change, but not necessarily create/generate +// broadcast ram: send on set and change (and generate?) +// required broadcast ram: send on set, change, and create/generate + +from direct.distributed import DistributedObject/AI/UD +from direct.distributed import DistributedNode/AI/UD +from direct.distributed import DistributedSmoothNode/AI +from direct.distributed import DistributedCartesianGrid/AI +from direct.distributed import DistributedCamera/AI/OV + +from otp.distributed import Account/AI/UD +from otp.ai import TimeManager/AI +from otp.ai import MagicWordManager/AI +from otp.avatar import DistributedAvatar/AI/UD +from otp.avatar import DistributedPlayer/AI +from otp.friends import FriendManager/AI +from otp.friends import AvatarFriendsManager/UD +from otp.friends import PlayerFriendsManager/UD +from otp.friends import GuildManager/AI/UD +from otp.friends import FriendInfo +from otp.friends import AvatarFriendInfo +from otp.distributed import ObjectServer/AI/UD +from otp.distributed import DistributedDistrict/AI/UD +from otp.distributed import DistributedDirectory/AI +from otp.distributed import DistributedTestObject/AI +from otp.snapshot import SnapshotDispatcher/AI/UD +from otp.snapshot import SnapshotRenderer/AI/UD +from otp.uberdog import OtpAvatarManager/AI/UD +from otp.uberdog import DistributedChatManager/AI/UD +from otp.uberdog import SpeedchatRelay/UD +from otp.distributed import CentralLogger/AI/UD +from otp.web import SettingsMgr/AI/UD +from otp.status import StatusDatabase/UD +from otp.avatar import AvatarHandle + +typedef uint8 bool; + +typedef uint32 DoId; +typedef DoId [] DoIdList; + +// a pair of longs used to hold avatar id and delete time in DB +struct AvatarPendingDel { + uint32 Avatar; + uint32 date; +}; + +dclass Account { + // RHH + // This is for internal server only... + // the 6 avatars a person has + string DcObjectType db; + // + uint32array ACCOUNT_AV_SET = [0,0,0,0,0,0] required db; + uint32array pirateAvatars = [0,0,0,0,0,0] required db; + // the 6 houses a person has + uint32array HOUSE_ID_SET db; + uint32 ESTATE_ID db; + AvatarPendingDel [] ACCOUNT_AV_SET_DEL db; + string PLAYED_MINUTES db; + string PLAYED_MINUTES_PERIOD db; + string CREATED db; + string LAST_LOGIN db; +}; + + +struct BarrierData { + uint16 context; + string name; + uint32 avIds[]; +}; + +// The most fundamental class +dclass DistributedObject { + // These are used to support DistributedObjectAI.beginBarrier() and + // the matching DistributedObject.doneBarrier(). If you don't call + // these functions, you don't care about these distributed methods. + // (Actually, you probably don't care anyway.) + setBarrierData(BarrierData data[]) broadcast ram; + setBarrierReady(uint16 context) airecv clsend; + execCommand(string, uint32 mwMgrId, uint32 avId, uint32 zoneId); + // this is to allow the client to force a disconnect from the server + broadcastMessage() broadcast; +}; + +// general-purpose test class +// use this when you just need to create a new distributed object +// and the type doesn't matter +// also used for test cases +dclass DistributedTestObject: DistributedObject { + uint32[] AutoInterest = [10]; + setParentingRules(string type="Stated", string Rule="") broadcast ram; + setRequiredField(uint32 r=78) required broadcast ram; + // fields to test for erroneous default values when no value has been set + setB(uint32 B) broadcast; + setBA(uint32 BA) broadcast airecv; + setBO(uint32 BO) broadcast ownsend; + setBR(uint32 BR) broadcast ram; + setBRA(uint32 BRA) broadcast ram airecv; + setBRO(uint32 BRO) broadcast ram ownsend; + setBROA(uint32 BROA) broadcast ram ownsend airecv; +}; + +struct OSInfo { + string name; + int16 platform; + int16 major; + int16 minor; +}; + +struct CPUSpeed { + int32 maxSpeed / 1000; + int32 currentSpeed / 1000; +}; + +// The TimeManager should be created before all other objects, because +// network timestamps can't be accurately decoded until the +// TimeManager has been created. +dclass TimeManager: DistributedObject { + requestServerTime(uint8 context) airecv clsend; // safe... + serverTime(uint8 context, int32 timestamp, uint32 timeOfDay); + + // This message is sent from the client to the AI when it disconnects + // cleanly. It is a bit of a hack to put it here in the TimeManager + // (because what does the TimeManager have to do with that?) but we + // can't put it on the toon because it is a message to the AI, and + // this is a convenient place to put it instead. + setDisconnectReason(uint8 disconnectCode) airecv clsend; //appears to be safe... + // For DisconnectPythonError, we also send a string describing the + // exception, for logging. + setExceptionInfo(string info) airecv clsend; //appears safe. perhaps set a limit to the length of info + + // This is the "signature" value read from the xrc file, which + // presumably indicates a particular xrc file in use. The client + // sets this at startup time. It probably ought to be on the player + // too, but then we have trouble sending it before the AI hears + // about the player. + setSignature(string signature, char hash[16], char pyc[16]) airecv clsend; + + // This is the average frame rate reported by the client, along with + // some relevant machine and game statistics. + setFrameRate(uint16 fps / 10, uint16 deviation / 1000, uint16 numAvs, + string locationCode, + uint32 timeInLocation / 10, uint32 timeInGame / 10, + string gameOptionsCode, + uint16 vendorId, uint16 deviceId, + uint32 processMemory / 10, uint32 pageFileUsage / 10, + uint32 physicalMemory / 10, uint32 pageFaultCount, + OSInfo osInfo, CPUSpeed cpuSpeed, + uint16 num_cpu_cores, uint16 num_logical_cpus, + string apiName) airecv clsend; + + // This is sent at startup to provide verbose CPU information. + setCpuInfo(string info, string cacheStatus) airecv clsend; + + // This message tells the dev AI to check for garbage leaks + checkForGarbageLeaks(bool wantReply) airecv clsend; + setNumAIGarbageLeaks(uint32 numLeaks); + // for logging + setClientGarbageLeak(uint32 num, string leak) airecv clsend; +}; + +// See Also: +// "otp/src/distributed/ObjectServer.py" +dclass ObjectServer { + setName(string serverName) airecv ram required; + setDcHash(uint32 hash) ram required; + setDateCreated(uint32 date) airecv; +}; + +//dclass PiratesRoot { +// requestDistricts() clsend; +// requestDistrictsResponse(uint32[] doId) broadcast; +// newDistrict(uint32 doId) broadcast; +// delDistrict(uint32 doId) broadcast; +//}; + +// Generic named container. Intended to be a parent for +// other objects, somewhat like a directory in a file system. +// +// This object has no attributes and none of them get created, but it +// is still needed. It is used as a parent for the individual games. +// The dc system uses the parenting rules as if this object existed. +// +// See Also: +// "otp/src/distributed/DistributeDirectory.py" +dclass DistributedDirectory: DistributedObject { + setParentingRules(string type="Stated", string Rule="") broadcast ram; +}; + +// See Also: +// "otp/src/distributed/DistributedDistrict.py" +// "otp/src/distributed/DistributedDistrictAI.py" +dclass DistributedDistrict: DistributedObject { + setName(string districtName="unnamed") required broadcast ram; + setAvailable(uint8 = 0) required broadcast ram; +}; + +dclass DistributedNode: DistributedObject { + // if other than '', overrules setParent + setParentStr(blob token) broadcast ram ownsend airecv; + setParent(uint32 token) broadcast ram ownsend airecv; + + setX(int16 / 10) broadcast ram ownsend airecv; + setY(int16 / 10) broadcast ram ownsend airecv; + setZ(int16 / 10) broadcast ram ownsend airecv; + setH(int16 % 360 / 10) broadcast ram ownsend airecv; + setP(int16 % 360 / 10) broadcast ram ownsend airecv; + setR(int16 % 360 / 10) broadcast ram ownsend airecv; + + setPos: setX, setY, setZ; + setHpr: setH, setP, setR; + setPosHpr: setX, setY, setZ, setH, setP, setR; + setXY: setX, setY; + setXZ: setX, setZ; + setXYH: setX, setY, setH; + setXYZH: setX, setY, setZ, setH; +}; + +dclass DistributedSmoothNode: DistributedNode { + // Component set pos and hpr functions. + + setComponentL(uint64) broadcast ram ownsend airecv; + setComponentX(int16 / 10) broadcast ram ownsend airecv; + setComponentY(int16 / 10) broadcast ram ownsend airecv; + setComponentZ(int16 / 10) broadcast ram ownsend airecv; + setComponentH(int16 % 360 / 10) broadcast ram ownsend airecv; + setComponentP(int16 % 360 / 10) broadcast ram ownsend airecv; + setComponentR(int16 % 360 / 10) broadcast ram ownsend airecv; + setComponentT(int16 timestamp) broadcast ram ownsend airecv; + + // Composite set pos and hpr functions. These map to combinations + // of one or more of the above components. They all include + // setComponentT(), which must be called last. + setSmStop: setComponentT; + setSmH: setComponentH, setComponentT; + setSmZ: setComponentZ, setComponentT; + setSmXY: setComponentX, setComponentY, setComponentT; + setSmXZ: setComponentX, setComponentZ, setComponentT; + setSmPos: setComponentX, setComponentY, setComponentZ, setComponentT; + setSmHpr: setComponentH, setComponentP, setComponentR, setComponentT; + setSmXYH: setComponentX, setComponentY, setComponentH, setComponentT; + setSmXYZH: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentT; + setSmPosHpr: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT; + // special update if L (being location, such as zoneId) changes, send everything, intended to + // keep position and 'location' in sync + setSmPosHprL: setComponentL, setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT; + + // I don't need any parameters, but for some reason leaving the + // parameter list empty on this one causes a server crash. + clearSmoothing(int8 bogus) broadcast ownsend; + + suggestResync(uint32 avId, int16 timestampA, int16 timestampB, + int32 serverTimeSec, uint16 serverTimeUSec, + uint16 / 100 uncertainty) ownrecv clsend; + returnResync(uint32 avId, int16 timestampB, + int32 serverTimeSec, uint16 serverTimeUSec, + uint16 / 100 uncertainty) ownrecv clsend; +}; + +dclass DistributedCartesianGrid: DistributedNode { +// Make sure setCellWidth gets called before setParentingRules + setCellWidth(uint32 width) required broadcast ram; +// base:size:resolution +//.. 0:100:1 == 3x3 interest grids.. with 100x100 size.. numbering starting at 0 ie 0.0 = 0 + setParentingRules(string type="Cartesian", string Rule="0:100:1") broadcast ram; +}; + + +struct Fixture { + int32 x / 10; + int32 y / 10; + int32 z / 10; + int16 h / 10; + int16 p / 10; + int16 r / 10; + string state = 'Standby'; +}; + +dclass DistributedCamera: DistributedNode { + setCamParent(uint32 doId = 0) required broadcast ram ownsend airecv; + setFixtures(Fixture[] fixtures) required broadcast ram ownsend airecv; +}; + +// dc base classes for autofiltered chat. now referred to as "talk" + +struct TalkModification + +{ + uint16 offset; + uint16 size; +}; + + +dclass TalkPath_owner +{ + setTalk(uint32 fromAV, uint32 fromAC, string avatarName, string chat, TalkModification mods[], uint8 flags ) broadcast ownsend; +}; + + +dclass TalkPath_whisper +{ + setTalkWhisper(uint32 fromAV,uint32 fromAC,string avatarName, string chat, TalkModification mods[], uint8 flags ) ownrecv clsend; +}; + + +dclass TalkPath_group +{ + setTalkGroup(uint32 fromAV, uint32 fromAC,string avatarName,string chat, TalkModification mods[], uint8 flags ) clsend airecv; +}; + + +dclass TalkPath_account +{ + setTalkAccount(uint32 toAc, uint32 fromAC, string fromName, string chat, TalkModification mods[], uint8 flags ) airecv clsend; +}; + + +dclass AvatarHandle: TalkPath_whisper { +}; + + + +// Base class for DistributedToon, DistributedSuit, etc. +dclass DistributedAvatar: DistributedSmoothNode, TalkPath_owner, TalkPath_whisper { + string DcObjectType db; + setName(string = "unknownDistributedAvatar") required broadcast db airecv; + + // This message is sent by the AI to notify the client when friends + // are added or removed without the client's participation, if the + // client happens to be logged in. It also might be sent directly + // from the friend (or former friend). + // TODO: this is still clsend but should not be to be secure + // avId and status is verified. appears safe. no suspect notification though + friendsNotify(int32 avId, int8 status) ownrecv airecv clsend; + // These messages are used to check if a certain avatar is on my shard + checkAvOnShard(uint32 avId) clsend airecv; + confirmAvOnShard(uint32 avId, int8 onShard); +}; + +// This is the base class for distributed classes (e.g. DistributedToon) +// that might represent player avatars. It excludes non-player avatar +// types like DistributedSuit. +dclass DistributedPlayer: DistributedAvatar { + // The parent rules are handled specially by Roger's server for + // localAvatars, if it where defined here it would look roughly like: + // If we ever need to look into other avatar's "pockets" you might want + // to setup parenting rules here. + // setParentingRules(string type="Stated", string Rule="") broadcast ram; + + // sent by AI avatar to client when avatar is created on the district server + arrivedOnDistrict(uint32 districtId) ownrecv ram; + + setAccountName(string = "unknown") required ownrecv db; + + + // These whisper messages are sent from another client. For now, we + // have to put the fromId in the message, but eventually we need to + // have a way to validate these. + setWhisperFrom(uint32 fromId, string, uint32 senderDISLid) ownrecv clsend; // safe: nice fromId check, string size limit will be nice + setWhisperWLFrom(uint32 fromId, string, uint32 senderDISLid) ownrecv clsend; + setWhisperSCFrom(uint32 fromId, uint16 msgIndex) ownrecv clsend; //security breach: msgIndex need to be range checked...need help = DROSE + setWhisperSCCustomFrom(uint32 fromId, uint16 msgIndex) ownrecv clsend; //security breach: same as above...need help + setWhisperSCEmoteFrom(uint32 fromId, uint16 emoteId) ownrecv clsend; //security breach: emoteId needs range checking...need help = DROSE + // setWhisperIgnored(uint32 fromId) airecv clsend; // fromId validated. appears safe + + // This is an onscreen message sent by the system. + setSystemMessage(uint32 aboutId, string) ownrecv; + + setCommonChatFlags(uint8) broadcast ownrecv ram airecv; + + // RAU weird, I had to add ownrecv to make sure localAvatar and other clients get this message + setWhitelistChatFlags(uint8) broadcast ownrecv ram airecv; + + setSC(uint16 msgIndex) broadcast ownsend airecv; + setSCCustom(uint16 msgIndex) broadcast ownsend airecv; + // this is sent as a setEmoteState call instead + //setSCEmote(uint16 emoteId) broadcast ownsend; + + // This field is airecv so we hear about friend making + // for the questManager. Perhaps there is a better way to do + // this when Roger gets back to help. + setFriendsList(uint32uint8array = {}) ownrecv required db airecv; + + // Added for new friend make/break flows + setDISLname(string="unknown") broadcast ownrecv ram; + // We want to store + setDISLid(uint32=0) broadcast ownrecv ram db airecv required; + + // these fields are for the Servers .. client should never see these ?? + // the index + OwningAccount(uint32 avId = 0); + // the string the person want to be there av's name + WishName(string = "") db ram; + // CLOSED,OPEN,USED,.. The state of the name review process + WishNameState(string = "") db ram; + //Access Contorl for velvet rope + setPreviousAccess(uint8 access = 0) required db airecv; + setAccess(uint8 access = 2) broadcast ownrecv required ram airecv; + + setAsGM(bool isGM = 0) required ram broadcast ownrecv airecv; +}; + +dclass MagicWordManager: DistributedObject { + setMagicWord(string, uint32 avId, uint32 zoneId, + string signature) airecv clsend; + setMagicWordResponse(string) airecv; + setWho(uint32array avIds) airecv clsend; +}; + +// See Also: +// "otp/src/uberdog/OtpAvatarManager.py" +// "otp/src/uberdog/OtpAvatarManagerAI.py" +dclass OtpAvatarManager: DistributedObject { + online(); + + // In response to this message, expect one of + // rejectCreateAvatar() or createAvatarResponse(). + requestAvatarList(uint32 senderId) airecv clsend; + + // result is an error code. + rejectAvatarList(uint32 result); + + // avatarId is the newly created avatarId. + avatarListResponse(blob pickleData); + + + + //----------------------------------- + // Ask to lock a slot for avatar creation + requestAvatarSlot(uint32 senderId,uint32 subId, uint8 slot) clsend airecv; + rejectAvatarSlot(uint32 result, uint32 subId, uint8 slot); + avatarSlotResponse(uint32 subId, uint8 slot); + + //----------------------------------- + // Ask to play an avatar + requestPlayAvatar(uint32 senderId, uint32 avatarId, uint32 subId) clsend airecv; + rejectPlayAvatar(uint32 result, uint32 avatarId); + + // Access codes that the Game Server will sniff out and + // store as broadcast fields on the avatar + // 1 = VELVET_ROPE + // 2 = FULL + playAvatarResponse(uint32 avatarId, uint32 subId, uint8 access, uint8 founder); + + // requestCreateAvatar has to be defined in pirates.dc or toon.dc + // because the DNA has different definitions + + // result is an error code. + rejectCreateAvatar(uint32 result); + + // avatarId is the newly created avatarId + createAvatarResponse(uint32 avatarId, uint32 subId, uint8 access, uint8 founder); + + //----------------------------------- + + // In response to this message, expect one of + // rejectRemoveAvatar() or removeAvatarResponse(). + requestRemoveAvatar(uint32 senderId, uint32 avatarId, uint32 subId, string confirmPassword) airecv clsend; + + // result is an error code. + rejectRemoveAvatar(uint32 result); + + // avatarId is the newly removed avatarId. + removeAvatarResponse(uint32 avatarId, uint32 subId); + + //----------------------------------- + + // In response to this message, expect one of + // rejectShareAvatar() or shareAvatarResponse(). + requestShareAvatar(uint32 senderId, uint32 avatarId, uint32 subId, uint8 shared) airecv clsend; + + // result is an error code. + rejectShareAvatar(uint32 result); + + // avatarId is the modified avatarId. + shareAvatarResponse(uint32 avatarId, uint32 subId, uint8 shared); + +}; + +//---------------------------------------------------------------------------- +dclass DistributedChatManager: DistributedObject { + online(); + + // UD to Clients (ALL clients) + adminChat(uint32 aboutId, string message); + + // AI to Client + //aiChat(string message); + + // AI to UD + setAvatarLocation(uint32 avatarId, uint32 parentId, uint32 zoneId); + setAvatarCrew(uint32 avatarId, uint32 zoneId); + setAvatarGuild(uint32 avatarId, uint32 zoneId); + + // New Chat + chatParentId(uint32 parentId) airecv clsend; + chatZoneId(uint32 zoneOrAvatarId) airecv clsend; + chatFace(uint32 doId) airecv clsend; // LookAt + chatEmote(uint16 emoteId) airecv clsend; + chatEmoteTarget(uint32 doId) airecv clsend; + chatIndex(uint16 msgIndex) airecv clsend; + chatString(string message) airecv clsend; + + chatToAvatarIndex: + chatZoneId, chatIndex; + chatParentZoneFaceEmoteWithTargetIndex: + chatParentId, chatZoneId, chatFace, chatEmote, chatEmoteTarget, chatIndex; + + chatToAvatarString: + chatZoneId, chatString; + chatParentZoneFaceEmoteWithTargetString: + chatParentId, chatZoneId, chatFace, chatEmote, chatEmoteTarget, chatString; + + // Client to location (zone) + + speedChatTo(uint16 msgIndex) airecv clsend; + speedChatFrom(uint32 fromId, uint16 msgIndex); + speedChatCustomTo(uint16 msgIndex) airecv clsend; + speedChatCustomFrom(uint32 fromId, uint16 msgIndex); + + // Client to avatar (doId) + whisperSCTo(uint32 toId, uint16 msgIndex) airecv clsend; + whisperSCFrom(uint32 fromId, uint16 msgIndex); + whisperSCCustomTo(uint32 toId, uint16 msgIndex) airecv clsend; + whisperSCCustomFrom(uint32 fromId, uint16 msgIndex); + whisperSCEmoteTo(uint32 toId, uint16 emoteId) airecv clsend; + whisperSCEmoteFrom(uint32 fromId, uint16 emoteId); + whisperIgnored(uint32 fromId); + +}; + +//---------------------------------------------------------------------------- + +dclass FriendManager: DistributedObject { + // Messages from inviter client to AI + // **** + friendQuery(int32 inviteeId) airecv clsend; //security breach: inviteeId need validation//Done,inviteeId validated and suspicious log entered if invalid + // **** + cancelFriendQuery(int32 context) airecv clsend; //security breach: context need range check...need help = DROSE + + // Messages from invitee client to AI + // **** + inviteeFriendConsidering(int8 yesNo, int32 context) airecv clsend; //security breach: yesNo and context need range check + // **** + inviteeFriendResponse(int8 yesNoMaybe, int32 context) airecv clsend; //security breach: yesNoMaybe and context need range check + // **** + inviteeAcknowledgeCancel(int32 context) airecv clsend; //security breach: context need range check + + // Messages from AI to inviter client + friendConsidering(int8 yesNo, int32 context); + friendResponse(int8 yesNoMaybe, int32 context); + + // Messages from AI to invitee client + inviteeFriendQuery(int32 inviterId, string inviterName, + blob inviterDna, int32 context); + inviteeCancelFriendQuery(int32 context); + + + // Messages involving secrets. + requestSecret() airecv clsend; //safe, timing could be an issue...owner, please check + requestSecretResponse(int8 result, string secret); + + submitSecret(string secret) airecv clsend; //possible security breach: secret might need sanity check...need help = ROGER/DROSE + submitSecretResponse(int8 result, int32 avId); +}; + + +// Player (Switchboard) Friends ONLY +struct FriendInfo +{ + string avatarName; + uint32 avatarId; + string playerName; + uint8 onlineYesNo; + uint8 openChatEnabledYesNo; // does my friend have blacklist chat enabled + uint8 openChatFriendshipYesNo; // am i true friend, implies i can blacklist chat to him + uint8 wlChatEnabledYesNo; // does my friend have whitelist chat enabled + string location; + string sublocation; + uint32 timestamp; +}; + +// Avatar (Product-specific) Friends ONLY +struct AvatarFriendInfo +{ + string avatarName; + string playerName; + uint32 playerId; + + uint8 onlineYesNo; + uint8 openChatEnabledYesNo; // does my friend have blacklist chat enabled + uint8 openChatFriendshipYesNo; // am i true friend, implies i can blacklist chat to him + uint8 wlChatEnabledYesNo; // does my friend have whitelist chat enabled +}; + +struct MemberInfo +{ + uint32 avatarId; + string avatarName; + uint8 avatarRank; + uint8 avatarOnline; + uint32 bandManagerId; + uint32 bandId; +}; + +struct leaderBoardRecordResponces +{ + char found; // was a record found + uint32 id; // the id of the requested object + string text; // the optional text for the object + int32 value; // the value of the object... +}; + +struct leaderBoardRecord +{ + uint32 id; // the is in the leader board.. + string text; // the optional text string defining this object + int32 value; // the value of the object +}; + +dclass LeaderBoardReceiver +{ + getTopTenResponce( string contest, leaderBoardRecord[] details); + getValuesResponce(string contest, leaderBoardRecordResponces[] out) ; +}; + +dclass LeaderBoard : LeaderBoardReceiver{ + // SETTERS + setValue(string[] contest, uint32 id, string text, int32 value) ; + alterValue(string[] contest, uint32 id, string text, int32 delta) ; + setHighScore(string[] contest, uint32 id, string text, int32 value) ; + // ACCESSERS + // + // Used for normal DC responce .. as a distributed object... + getValues(string contest, uint32[] id); + getTopTen( string contest); + + // Used to enable to LeaderBoardReceiver functionality + // the output update will be sent on the object doid passed as + // a last input value... + getValuesRespondTo(string contest, uint32[] id, uint32 doid); + getTopTenRespondTo( string contest, uint32 doid); +}; + +dclass GuildManager: DistributedObject, LeaderBoardReceiver, TalkPath_group { + online(); + //setParentingRules(string type="Stated", string Rule=""); + + guildRejectInvite(uint32 avatarId,uint32 reason); + invitationFrom(uint32 avatarId, string avatarName, uint32 guildId, string guildName); + requestInvite(uint32 avatarId) airecv clsend; // CL -> UD + memberList() airecv clsend; // CL -> UD + createGuild() airecv clsend; // CL -> UD + acceptInvite() airecv clsend; // CL -> UD + declineInvite() airecv clsend; // CL -> UD + setWantName(string newName) airecv clsend; // CL -> UD + removeMember(uint32 avatarId) airecv clsend; // CL -> UD + changeRank(uint32 avatarId, uint8 rank) airecv clsend; // CL -> UD + statusRequest() airecv clsend; // CL -> UD + requestLeaderboardTopTen() airecv clsend; // CL -> UD + guildStatusUpdate(uint32 guildId, string guildname, uint8 rank); // CL -> UD + guildNameReject(uint32 guildId); // UD -> CL + guildNameChange(string guildname, uint8 rank); // UD -> CL + receiveMember(MemberInfo member); // UD -> CL + receiveMembersDone(); // UD -> CL + guildAcceptInvite(uint32 avatarId); // UD -> CL + guildDeclineInvite(uint32 avatarId); // UD -> CL + updateRep(uint32 avatarId, uint32 rep); // AI -> UD + leaderboardTopTen(leaderBoardRecord [] stuff); // UD -> CL + + recvAvatarOnline(uint32 avatarId, string avatarName, uint32 bandManagerId, uint32 bandId); // UD -> CL + recvAvatarOffline(uint32 avatarId, string avatarName); // UD -> CL + + sendChat(string msgText,uint8 chatFlags,uint32 DISLid) airecv clsend; + sendWLChat(string msgText,uint8 chatFlags,uint32 DISLid) airecv clsend; + sendSC(uint16 msgIndex) airecv clsend; + sendSCQuest(uint16 questInt, uint16 msgIndex, uint16 taskNum) airecv clsend; + + recvChat(uint32 senderId,string msgText,uint8 chatFlags,uint32 DISLid); + recvWLChat(uint32 senderId,string msgText,uint8 chatFlags,uint32 DISLid); + recvSC(uint32 senderId,uint16 msgIndex); + recvSCQuest(uint32 senderId, uint16 questInt, uint16 msgIndex, uint16 taskNum); + + sendTokenRequest() airecv clsend; + recvTokenGenerated(string tokenString); + recvTokenInviteValue(string tokenValue, int8 preExistPerm); + sendTokenForJoinRequest(string token, string name) airecv clsend; + recvTokenRedeemMessage(string guildName); + recvTokenRedeemedByPlayerMessage(string redeemerName); + sendTokenRValue(string tokenString, int8 rValue) airecv clsend; + sendPermToken() airecv clsend; + sendNonPermTokenCount() airecv clsend; + recvPermToken(string token); + recvNonPermTokenCount(uint8 count); + sendClearTokens(uint8 type) airecv clsend; + // The next three are currently commented out; + // waiting for "send-to-a-friend" system access + // sendRequestEmailNotificationPref() airecv clsend; + // respondEmailNotificationPref(int8 notify, string emailAddress); + // sendEmailNotificationPrefUpdate(int8 notify, string emailAddress) airecv clsend; + + sendAvatarBandId(uint32 avatarId, uint32 bandManagerId, uint32 bandId); + recvMemberAdded(MemberInfo info); + // (uint32 avatarId, string avatarName, uint8 rank, uint8 rank, uint32 bandManagerId, uint32 bandId); + recvMemberRemoved(uint32 avatarId); + recvMemberUpdateName(uint32 avatarId, string name); + recvMemberUpdateRank(uint32 avatarId, uint8 rank); + recvMemberUpdateBandId(uint32 avatarId, uint32 bandManagerId, uint32 bandId); + + avatarOnline(uint32 avatarId, uint16 avatarType); // AvatarMgrUD to AvatarFriendMgrUD + avatarOffline(uint32 avatarId); // AvatarMgrUD to AvatarFriendMgrUD + + reflectTeleportQuery(uint32 sendToId, uint32 bandMgrId, uint32 bandId, uint32 guildId, uint32 shardId) clsend airecv; + teleportQuery(uint32 requesterId, uint32 bandMgrId, uint32 bandId, uint32 guildId, uint32 shardId); + reflectTeleportResponse(uint32 sendToId, int8 available, + uint32 shardId, uint32 instanceDoId, + uint32 areaDoId) clsend airecv; + teleportResponse(uint32 responderId, int8 available, + uint32 shardId, uint32 instanceDoId, + uint32 areaDoId); + requestGuildMatesList(uint32 doId, uint32 channel, uint32 avId); + updateAvatarName(uint32 avatarId,string avatarName); + avatarDeleted(uint32 avatarId); +}; + +dclass AvatarFriendsManager: DistributedObject { + online(); + requestInvite(uint32 avatarId) airecv clsend; + friendConsidering(uint32 avatarId) airecv clsend; + invitationFrom(uint32 avatarId,string avatarName); + retractInvite(uint32 avatarId); + rejectInvite(uint32 avatarId,uint32 reason); + + requestRemove(uint32 avatarId) airecv clsend; + rejectRemove(uint32 avatarId,uint32 reason); + + updateAvatarFriend(uint32 avatarId,AvatarFriendInfo info); + removeAvatarFriend(uint32 avatarId); + + updateAvatarName(uint32 avatarId,string avatarName); + + avatarOnline(uint32 avatarId, uint32 accountId, string playerName, bool playerNameApproved, bool openChatEnabled, string createFriendsWithChat, string chatCodeCreation); // AvatarMgrUD to AvatarFriendMgrUD + avatarOffline(uint32 avatarId); // AvatarMgrUD to AvatarFriendMgrUD +}; + +dclass PlayerFriendsManager: DistributedObject, TalkPath_account { + //online(); + requestInvite(uint32 senderId,uint32 otherPlayerId,uint8 secretYesNo) airecv clsend; + invitationFrom(uint32 playerId, string playerName); + retractInvite(uint32 playerId); + //rejectInvite(uint32 playerId, uint32 reason); // obsolete in SB 2.0 + invitationResponse(uint32 doid, uint16 respCode, uint32 context); + + requestDecline(uint32 senderId, uint32 playerId) airecv clsend; + requestDeclineWithReason(uint32 senderId, uint32 playerId, uint32 reason) airecv clsend; // new field 249 + requestRemove(uint32 senderId, uint32 playerId) airecv clsend; + //rejectRemove(uint32 playerId, uint32 reason); + + //requestUnlimitedSecret(uint32 senderId) airecv clsend; // no codes for player friends + //requestLimitedSecret(uint32 senderId,string parentUser,string parentPass) airecv clsend; // no codes for player friends + secretResponse(string secret); + rejectSecret(string reason); + + //requestUseUnlimitedSecret(uint32 senderId,string secret) airecv clsend; // no codes for player friends + //requestUseLimitedSecret(uint32 senderId,string secret,string parentUser,string parentPass) airecv clsend; // no codes for player friends + rejectUseSecret(string reason); + + //whisperTo(uint32 senderId,uint32 playerId,string msg) airecv clsend; //no longer needed accdg to John + //whisperWLTo(uint32 senderId,uint32 playerId,string msg) airecv clsend; //no longer needed accdg to John + //whisperSCTo(uint32 senderId,uint32 playerId,uint32 msgId) airecv clsend; + //whisperSCCustomTo(uint32 senderId,uint32 playerId,uint32 msgId) airecv clsend; + //whisperSCEmoteTo(uint32 senderId,uint32 playerId,uint32 msgId) airecv clsend; + //whisperFrom(uint32 playerId,string msg); + //whisperWLFrom(uint32 playerId,string msg); + //whisperSCFrom(uint32 playerId,string msg); + + updatePlayerFriend(uint32 playerId,FriendInfo info,uint8 is_newfriend); + removePlayerFriend(uint32 playerId); + + //avatarOnline(uint32 avatarId, uint32 accountId, string playerName, bool playerNameApproved, bool openChatEnabled, string createFriendsWithChat, string chatCodeCreation); // AvatarMgrUD to AvatarFriendMgrUD + //avatarOffline(uint32 avatarId); // AvatarMgrUD to AvatarFriendMgrUD +}; + +dclass SnapshotDispatcher: DistributedObject { + online(); + requestRender(uint32 avatarId); // AI -> Dispatcher + avatarDeleted(uint32 avatarId); // AvatarManager -> Dispatcher + requestNewWork(uint32 rendererLoc); // Renderer -> Dispatcher + errorFetchingAvatar(uint32 rendererLoc,uint32 jobId); // Renderer -> Dispatcher + errorRenderingAvatar(uint32 rendererLoc,uint32 jobId); // Renderer -> Dispatcher + renderSuccessful(uint32 rendererLoc,uint32 jobId); // Renderer -> Dispatcher +}; + +dclass SnapshotRenderer: DistributedObject { + online(); + requestRender(uint32 jobId,uint32 avatarId,string writeToFile); // Dispatcher -> Renderer +}; + +dclass SpeedchatRelay : DistributedObject, TalkPath_account{ + forwardSpeedchat(uint32 receiverDISLid, uint8 type, uint32 [] index, uint32 senderDISLId, string senderDISLName, uint8 flags) clsend; +}; + + +dclass CentralLogger : DistributedObject { + sendMessage(string category, string text, uint32 targetDISLId, uint32 targetAvatarID) clsend; +}; + +dclass SettingsMgr: DistributedObject { + requestAllChangedSettings() airecv clsend; // AI->UD, client->UD + settingChange(string name, string value) airecv; // UD->AI/client or UD->all AIs/clients +}; + +dclass StatusDatabase : DistributedObject { + requestOfflineAvatarStatus(uint32[] avIds) airecv clsend; // CL -> UD + recvOfflineAvatarStatus(uint32 avId, uint32 lastOnline); // UD -> CL +}; + +// Mixin class for objects to receive a generic ACK/NACK callback response +dclass CallbackObject { + callback(uint32 context,bool success,uint8 errorCode); +}; diff --git a/otp/src/configfiles/otp.init b/otp/src/configfiles/otp.init new file mode 100644 index 0000000..9075fac --- /dev/null +++ b/otp/src/configfiles/otp.init @@ -0,0 +1,3 @@ +ATTACH direct +MODREL ETC_PATH etc + diff --git a/otp/src/configfiles/otp.prc.pp b/otp/src/configfiles/otp.prc.pp new file mode 100644 index 0000000..0c9cb8d --- /dev/null +++ b/otp/src/configfiles/otp.prc.pp @@ -0,0 +1,160 @@ +// +// otp.prc.pp +// +// This file defines the script to auto-generate otp.prc at +// ppremake time. +// + +#output 50_otp.prc notouch +#### Generated automatically by $[PPREMAKE] $[PPREMAKE_VERSION] from $[notdir $[THISFILENAME]]. +################################# DO NOT EDIT ########################### + +dc-file $OTP/src/configfiles/otp.dc + +# We don't contact any account servers or otherwise in-game, so we +# don't need strict SSL certificate validation--and this makes it easier +# to contact a local gameserver without fussing about certificate names. +verify-ssl 0 + +# We compress animation files by default. +compress-channels 1 + +# We take advantage of the render2dp scene graph for things layered on +# top of everything else. +want-render2dp 1 + +# We would like our developers to use correct case; this is +# particularly important for the publish. Therefore, we set this +# variable to have Panda insist on the correct case for all of its +# file accesses. +vfs-case-sensitive 1 + +# International characters are represented internally using the utf8 +# encoding. +text-encoding utf8 + +# We don't want DirectEntries to return unicode strings for now. +direct-wtext 0 + +# Don't break a line before the following punctuation marks (including +# some Japanese punctuation marks). +text-never-break-before ,.-:?!;。?!、 + +# This enables in-game IME (e.g. for Japanese clients) +ime-aware 1 +ime-hide 1 + +# Make sure textures are forced to a power-of-2 size by default, as a +# convenience. +textures-power-2 down + +# This enables checking the clock against the time-of-day clock, +# mainly useful for defeating programs like Speed Gear. +paranoid-clock 1 + +# Let's keep an eye on those messages. +notify-level-clock debug + +# Collect consecutive small TCP packets into one big packet to reduce +# network bandwidth (at the cost of latency). We have this turned +# on here the dev environment mainly to encourage testing of this +# feature. +collect-tcp 1 +collect-tcp-interval 0.2 + +# We want to use the previous transform by default. +respect-prev-transform 1 + +# We don't need to hear about all the silly problems with tiff files. +notify-level-tiff error + +# The ID of the server that we are compatible with + +# For users in the dev environment, we don't care that much about +# enforcing this, and it's easier to keep it always the same than to +# have people constantly having to update their pirates.par files. +server-version dev + +# Also, users in the dev environment want this set. +want-dev 1 + +# The current Toontown code now supports temp-hpr-fix. +# Welcome to the future. +temp-hpr-fix 1 + + +# Custom ObjectTypes for OTP. +# "barrier" means a vertical wall, with bitmask 0x01 +# "floor" means a horizontal floor, with bitmask 0x02 +# "camera-collide" means things that the camera should avoid, with bitmask 0x04 +egg-object-type-barrier collide-mask { 0x01 } { Polyset descend } +egg-object-type-floor collide-mask { 0x02 } { Polyset descend level } +egg-object-type-dupefloor collide-mask { 0x02 } { Polyset keep descend level } +egg-object-type-trigger collide-mask { 0x01 } { Polyset descend intangible } +egg-object-type-planebarrier collide-mask { 0x01 } { Plane descend } +egg-object-type-planefloor collide-mask { 0x02 } { Plane descend } +egg-object-type-sphere collide-mask { 0x01 } { Sphere descend } +egg-object-type-tube collide-mask { 0x01 } { Tube descend } +egg-object-type-camcollide collide-mask { 0x04 } { Polyset descend } +egg-object-type-camtransparent collide-mask { 0x08 } { Polyset descend } +egg-object-type-cambarrier collide-mask { 0x05 } { Polyset descend } +egg-object-type-camtransbarrier collide-mask { 0x09 } { Polyset descend } +egg-object-type-pet collide-mask { 0x08 } { Polyset descend } +egg-object-type-ouch1 ouch { 1 } +egg-object-type-ouch2 ouch { 2 } +egg-object-type-ouch3 ouch { 3 } +egg-object-type-ouch4 ouch { 4 } +egg-object-type-ouch5 ouch { 5 } + +# surface attributes +egg-object-type-dirt-surface surface { dirt } +egg-object-type-gravel-surface surface { gravel } +egg-object-type-grass-surface surface { grass } +egg-object-type-asphalt-surface surface { asphalt } +egg-object-type-wood-surface surface { wood } +egg-object-type-water-surface surface { water } +egg-object-type-snow-surface surface { snow } +egg-object-type-ice-surface surface { ice } +egg-object-type-sticky-surface surface { sticky } + + +# These are deprecated. It's now possible to combine two of the above +# to achieve the same as any of these. +egg-object-type-trigger-sphere collide-mask { 0x01 } { Sphere descend intangible } +egg-object-type-camera-barrier collide-mask { 0x05 } { Polyset descend } +egg-object-type-camera-barrier-sphere collide-mask { 0x05 } { Sphere descend } +egg-object-type-camera-collide-sphere collide-mask { 0x04 } { Sphere descend } +egg-object-type-camera-collide collide-mask { 0x04 } { Polyset descend } + +# The modelers occasionally put { model } instead of +# { 1 }. Let's be accommodating. +egg-object-type-model { 1 } +egg-object-type-dcs { 1 } + +# Define a "shadow" object type, so we can render all shadows in their +# own bin and have them not fight with each other (or with other +# transparent geometry). +egg-object-type-shadow bin { shadow } alpha { blend-no-occlude } +cull-bin shadow 15 fixed + +# We must currently set this to avoid messing up some of +# the suits' faces. +egg-retesselate-coplanar 0 + +# Define a "ground" type, for rendering ground surfaces immediately +# behind the drop shadows. +egg-object-type-shground bin { ground } cam { shground } +egg-object-type-ground bin { ground } cam { shground } +egg-object-type-shadow-ground bin { ground } cam { shground } +cull-bin ground 14 fixed + +# Whenever we load models implicitly, convert them to feet. +ptloader-units ft + +# Allow loading model files without an extension. +default-model-extension .bam + +# Allow devs to run without chat filtering. DO NOT ENABLE IN PRODUCTION. +allow-unfiltered-chat 1 + +#end 50_otp.prc diff --git a/otp/src/configrc/.cvsignore b/otp/src/configrc/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/configrc/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/configrc/Configrc.cxx b/otp/src/configrc/Configrc.cxx new file mode 100644 index 0000000..6f57732 --- /dev/null +++ b/otp/src/configrc/Configrc.cxx @@ -0,0 +1,1326 @@ +// Filename: Configrc.cxx +// $Id$ +// +//////////////////////////////////////////////////////////////////// + +#include "settingsFile.h" +#include "dSearchPath.h" +#include "key_src.cxx" +#ifdef _WIN32 +#include +#endif + +#include "openssl/ssl.h" +#include "openssl/md5.h" + +// Windows may define this macro inappropriately. +#ifdef X509_NAME +#undef X509_NAME +#endif + +// hack to share enums w/installer +class SysInfo { +public: + // MPG hack to avoid moving installer to OTP yet + //#include "../installer/sysinfodefs.h" + #include "sysinfodefs.h" +}; + +#define _str(s) #s +#define _xstr(s) _str(s) + +#ifdef USE_TESTSERVER + #define TESTSTR "Test" + const char *activex_control_name = "tt_test.dll"; + const char *InstallerGuid="FF791555-FDAC-43ab-B792-389E4CC0A6E5"; +#else + #if defined(USE_CASTILLIAN) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="8CD7CB6E-EDD0-4b94-A4B9-27B2E9BE2FA3"; + const char *activex_control_name = "ttinst-castillian.dll"; + #elif defined(USE_JAPANESE) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="E76AABC4-07F4-47c3-BC55-B16119A793CF"; + const char *activex_control_name = "ttinst-japanese.dll"; + #elif defined(USE_GERMAN) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="95BD7A59-567A-4fe1-A412-FCEC29428E42"; + const char *activex_control_name = "ttinst-german.dll"; + #elif defined(USE_PORTUGUESE) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="31CB2F01-72C2-4cf4-B265-450E8817B039"; + const char *activex_control_name = "ttinst-portuguese.dll"; + #elif defined(USE_FRENCH) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="63308B48-F435-42fd-AB0A-3564C7BEF9D7"; + const char *activex_control_name = "ttinst-french.dll"; + #else + #define TESTSTR "" + const char *activex_control_name = "ttinst.dll"; + const char *InstallerGuid="C02226EB-A5D7-4B1F-BD7E-635E46C2288D"; + #endif +#endif + +const char *configrc_override_filename = "xrc"; +const char *ToontownRegKeyName = "SOFTWARE\\Disney\\Disney Online\\Toontown" TESTSTR; //under HKEY_LOCAL_MACHINE + +#ifdef _WIN32 +void write_opengl_hardware_info(HKEY hKeyToontown); +#endif // _WIN32 + +// win32 cruft +// file existance: +// if (GetFileAttributes(strFilePath) != 0xFFFFFFFF) return true; +// file readability: +// You HAVE to try to open the file to find out if it's openable. Since files +// can be opend for either shared or exclusive access by other processes, you +// can't just check the static permissions or attributes. To test for +// openability, Open the file for readonly|sharedaccess. This way you won't +// block any other programs from accessing it. +// executable: +// if it ends in ".exe" or ".com" then you can assume it's executable. If you +// try to createprocess on it and get an error, then it was not. + + +#ifdef _WIN32 + +#include +#include +using namespace std; +typedef vector StrVec; + +static void +filesearch(string rootpath, string pattern, bool bRecursive, bool bSearchForDirs, bool bPrintFileInfo, StrVec &files) +{ + // typical arguments: filesearch("C:\\temp\\mview","*",true,true,sveclist); + WIN32_FIND_DATA current_file; + + // first find all the files in the rootpath dir that match the pattern + string searchpathpattern = rootpath + "\\" + pattern; + HANDLE searcher = FindFirstFile(searchpathpattern.c_str(), ¤t_file); + if ( searcher == INVALID_HANDLE_VALUE) + return; + do { + string fileline; + + if(bSearchForDirs) { + // save only dirs + if ((current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + (!(current_file.cFileName[0] == '.'))) { + fileline = rootpath + "\\" + current_file.cFileName; + files.push_back(fileline); + } + } else { + // save only files + if(!((current_file.cFileName[0] == '.') || + (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))) { + if(bPrintFileInfo) { + SYSTEMTIME stime; + FileTimeToSystemTime(¤t_file.ftCreationTime,&stime); + char extra_info[100]; + sprintf(extra_info, ", (%d/%d/%d), %d bytes",stime.wMonth,stime.wDay,stime.wYear,current_file.nFileSizeLow); + fileline = rootpath + "\\" + current_file.cFileName + extra_info; + } else { + fileline = rootpath + "\\" + current_file.cFileName; + } + files.push_back(fileline); + } + } + } while (FindNextFile(searcher, ¤t_file)); + FindClose(searcher); + + if (bRecursive) { + // then call yourself recursively on all dirs in the rootpath + string newsearchpath = rootpath + "\\*"; + HANDLE searcher = FindFirstFile(newsearchpath.c_str(), ¤t_file); + if ( searcher == INVALID_HANDLE_VALUE) + return; + do { + if ( current_file.cFileName[0] == '.' || + !(current_file.dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY)) + continue; + newsearchpath = rootpath + "\\" + current_file.cFileName; + filesearch(newsearchpath, pattern, true, bSearchForDirs, bPrintFileInfo, files); + } while (FindNextFile(searcher, ¤t_file)); + FindClose(searcher); + } +} + +static void UninstallActiveX(const char *control_name,const char *control_GUID) +{ + typedef HRESULT (WINAPI *REMOVECONTROLBYNAME)( + LPCTSTR lpszFile, + LPCTSTR lpszCLSID, + LPCTSTR lpszTypeLibID, + BOOL bForceRemove, + DWORD dwIsDistUnit); + + HMODULE hMod=NULL; + REMOVECONTROLBYNAME pfnRemoveControl; + HKEY hKey; + const char *ActiveXCache_RegValName = "ActiveXCache"; + const char *InternetSettingsRegKeyStr = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; //under HKLM + char activeXcontrol_filepath[512]; + DWORD bufsize = sizeof(activeXcontrol_filepath); + string guidstr,searchpattern; + StrVec file_list, control_list; + + if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE,InternetSettingsRegKeyStr,0,KEY_READ,&hKey)) { + goto error_exit; + } + + if(ERROR_SUCCESS != RegQueryValueEx(hKey, ActiveXCache_RegValName, NULL, NULL, (LPBYTE)activeXcontrol_filepath, &bufsize)) { + goto error_exit; + } + + RegCloseKey(hKey); + + hMod = LoadLibrary("occache.dll"); + if (!hMod) + goto error_exit; + + pfnRemoveControl = (REMOVECONTROLBYNAME)GetProcAddress(hMod, "RemoveControlByName"); + if (!pfnRemoveControl) + goto error_exit; + + // guid must be bracketed by '{}' for RemoveControlByName + guidstr = "{" + string(control_GUID) + "}"; + + char control_basename[20]; + strcpy(control_basename,control_name); + control_basename[strlen(control_name)-4]='\0'; // assumes control_name ends in .xxx + + filesearch(activeXcontrol_filepath, control_name, true, false, false, control_list); + + for(unsigned i=0;i keysize) { + // This should never happen. Memory overrun! + abort(); + } + if (hashlen <= 0) { + delete[] hash; + return; + } + + /* + cerr << hex; + int i; + for (i = 0; i < md5_len; i++) { + cerr << setw(2) << setfill('0') << (unsigned int)(unsigned char)md[i]; + } + cerr << "\n"; + for (i = 0; i < hashlen; i++) { + cerr << setw(2) << setfill('0') << (unsigned int)(unsigned char)hash[i]; + } + cerr << dec << "\n"; + */ + + bool match = (hashlen == md5_len && memcmp(md, hash, md5_len) == 0); + delete[] hash; + if (!match) { + return; + } + + // Success! + os.write(override.data(), override.length()); +} + +int main(int argc, char*argv[]) { + + Settings::DisplayDriver new_disp_type = Settings::D_NONE; + bool bPrintHelp=false; + bool bDontOverwriteExistingSettings=false; + bool bWriteStdout=false; + bool bSaveSettings=false; + bool bSetCursor=false; + bool bUseCustomCursor; + bool bUninstallActiveX=false; + bool bPickBestRes=false; + bool bSetLowRes=false; + bool bChangedSettings = false; + bool bDoSetWindowedMode=false; + bool bWindowedMode; + bool bDoSetShowFPSMeter=false; // dont want to change this unless user explicitly specified to + bool bShowFPSMeter; + bool bDoSetForceSWMidi=false; // dont want to change this unless user explicitly specified to + bool bForceSWMidi; + + for (int a = 1; a < argc; a++) { + if ((argv[a] != (char*)0L) && (strlen(argv[a])>1) && + (argv[a][0] == '-') || + (argv[a][0] == '/')) { + + char *pArgStr=argv[a]+1; + if(cmp_nocase(pArgStr,"OGL")==0) { + new_disp_type=Settings::GL; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"DX9")==0) { + new_disp_type=Settings::DX9; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"DX8")==0) { + new_disp_type=Settings::DX8; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"DX7")==0) { + // This is deprecated, but supported for historical reasons. + // DX7 means DX8. + new_disp_type=Settings::DX8; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"default")==0) { + new_disp_type=Settings::D_DEFAULT; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"NoOverride")==0) { + bDontOverwriteExistingSettings=true; + } else if(cmp_nocase(pArgStr,"save")==0) { + bSaveSettings=true; + } else if(cmp_nocase(pArgStr,"lowres")==0) { + bSaveSettings=true; + bSetLowRes=true; + } else if(cmp_nocase(pArgStr,"fullscreen")==0) { + bSaveSettings=true; + bWindowedMode=false; + bDoSetWindowedMode=true; + } else if(cmp_nocase(pArgStr,"windowed")==0) { + bSaveSettings=true; + bSetLowRes=true; // make sure window is not bigger than desktop initially + bWindowedMode=true; + bDoSetWindowedMode=true; + } else if(cmp_nocase(pArgStr,"show_fps")==0) { + bSaveSettings=true; + bShowFPSMeter=true; + bDoSetShowFPSMeter=true; + } else if(cmp_nocase(pArgStr,"hide_fps")==0) { + bSaveSettings=true; + bShowFPSMeter=false; + bDoSetShowFPSMeter=true; + } else if(cmp_nocase(pArgStr,"force_sw_midi")==0) { + bSaveSettings=true; + bForceSWMidi=true; + bDoSetForceSWMidi=true; + } else if(cmp_nocase(pArgStr,"allow_hw_midi")==0) { + bSaveSettings=true; + bForceSWMidi=false; + bDoSetForceSWMidi=true; + } else if(cmp_nocase(pArgStr,"stdout")==0) { // this is usually hidden + bWriteStdout=true; + } else if(cmp_nocase(pArgStr,"cursor_on")==0) { + bSetCursor=true; + bUseCustomCursor=true; + bChangedSettings = true; + } else if(cmp_nocase(pArgStr,"cursor_off")==0) { + bSetCursor=true; + bUseCustomCursor=false; + bChangedSettings = true; + #ifdef USE_TESTSERVER + } else if(cmp_nocase(pArgStr,"uninstall_testserver_activex")==0) { + bUninstallActiveX=true; + #endif + } else if(cmp_nocase(pArgStr,"uninstall_activex")==0) { + bUninstallActiveX=true; + } else if(cmp_nocase(pArgStr,"pickbestres")==0) { + bPickBestRes=true; + } else { + cerr << "Invalid argument: " << argv[a] << endl; + bPrintHelp=true; + } + } else { + cerr << "Invalid argument: " << argv[a] << endl; + bPrintHelp=true; + } + } + + bool bDoSavedSettingsExist = Settings::doSavedSettingsExist(); + + if (bChangedSettings && !bWriteStdout) { + // If the user specified one of the -OGL etc. options, but not + // -stdout, he probably meant to specifiy -save to save those + // changes for the next run of Toontown. + + // note: NoOverride cancels the save if file 'useropt' exists + bSaveSettings = true; + } + + if(bPrintHelp) { + cerr << "Syntax: configrc [options]\n"; + cerr << "Options:\n"; + cerr << "-OGL: select OpenGL as the graphics rendering interface\n"; + cerr << "-DX9: select Direct3D 9 as the graphics rendering interface\n"; + cerr << "-DX8: select Direct3D 8 as the graphics rendering interface\n"; + cerr << "-cursor_off: use standard windows mouse cursor\n"; + cerr << "-cursor_on: use custom toontown mouse cursor\n"; + cerr << "-lowres: set the startup screen resolution to 640x480\n"; + cerr << "-fullscreen: use fullscreen mode\n"; + cerr << "-windowed: use windowed mode\n"; + cerr << "-show_fps: show frames/sec perf meter\n"; + cerr << "-hide_fps: do not show frames/sec perf meter\n"; + cerr << "-force_sw_midi: force use of software-midi to play midi music\n"; + cerr << "-allow_hw_midi: allow use of midi hardware to play midi music, if it exists\n"; + cerr << "-NoOverride: ignore new setting specifications if saved options file exists\n"; + + // Let's not advertise this option; it's just for internal use and + // telling users about it makes our config options that much easier + // to hack + // cerr << "-stdout: write new Configrc to stdout\n"; + + // mostly useless to advertise since only I know what it does + // cerr << "-pickbestres: try to pick a startup screen resolution based on vidmem size\n"; + + + cerr << "-save: save settings to '" << configrc_settings_filename << "' file (default)\n"; + cerr << "-uninstall_activex: uninstall toontown activex control\n"; +// cerr << "-uninstall_testserver_activex: uninstall testserver toontown activex control\n"; + exit(1); + } + +#ifdef _WIN32 + if(bUninstallActiveX) { + UninstallActiveX(activex_control_name,InstallerGuid); + // dont want to generate any configrc output here + exit(0); + } +#endif + + // first write out any changes to the Settings object, then translate settings to a Configrc + + // this allows the user to change out of a bad scrn resolution outside of TT + if(bSetLowRes) + Settings::set_resolution(Settings::R640x480); + + if(bDoSetWindowedMode) + Settings::set_windowed_mode(bWindowedMode); + + if(bDoSetShowFPSMeter) + Settings::set_show_fpsmeter(bShowFPSMeter); + + if(bDoSetForceSWMidi) + Settings::set_force_sw_midi(bForceSWMidi); + + if (!bDontOverwriteExistingSettings || + Settings::display_driver() == Settings::D_DEFAULT) { + if(new_disp_type != Settings::D_NONE) { + Settings::set_display_driver(new_disp_type); + } + } + + if(!bDontOverwriteExistingSettings) { + if(bSetCursor) + Settings::set_custom_mouse_cursor(bUseCustomCursor); + } + + if(!(bDontOverwriteExistingSettings && bDoSavedSettingsExist)) { + if(bSaveSettings) + Settings::write_settings(); + } + +#ifdef USE_OFSTREAM + pofstream os("Configrc"); +#else + ostream& os = cout; +#endif + + + if(!bWriteStdout) { +#ifdef USE_OFSTREAM + os.close(); +#endif + return 0; + } + + output_overrides(os); + + write_const(os); + os << endl; + + if(Settings::want_custom_mouse_cursor()) { + os << "cursor-filename toonmono.cur" << endl << endl; + // not using 256 color cursors right now due to common driver probs + // os << "win32-color-cursor phase_3/models/gui/toon.cur" << endl; + } + + if(Settings::get_show_fpsmeter()) { + os << "show-frame-rate-meter #t" << endl << endl; + } + +#if 0 // This seems to crash on dual-core CPU's, and it's not worth the trouble of fixing it. + #ifdef WIN32_VC + float lod_stress_factor=1.0f; + + // stupid hack 1-time adjust of lod stress factor based on CPU speed to reduce close-up + // popping on faster machines. this is a placeholder until we do a system that adjusts + // lod_stress_factor dynamically based on current fps + + DWORD Mhz=FindCPUMhz(0.1f); + // lower lods are not yet designed to be viewed closer up, + // cant increase stress to >1 yet + if(Mhz<1000) + lod_stress_factor=1.0f; + else if(Mhz<1300) + lod_stress_factor=0.7f; + else if(Mhz<1700) + lod_stress_factor=0.3f; + else lod_stress_factor=0.25f; + + os << "lod-stress-factor " << lod_stress_factor << endl << endl; + #endif +#endif // 0 + + Settings::DisplayDriver driver_type = Settings::display_driver(); + switch (driver_type) { + case Settings::DX7: + case Settings::DX8: +#ifdef WIN32 + os << "load-display pandadx8" << endl; + break; +#endif // fall through on non-Windows case + + case Settings::DX9: + case Settings::D_DEFAULT: +#ifdef WIN32 + os << "load-display pandadx9" << endl; + break; +#endif // fall through on non-Windows case + + case Settings::GL: + os << "load-display pandagl" << endl; + break; + + default: + // this is an error, it must be one of the above. + break; + } + os << endl; + + char fs_str[2]; + fs_str[0]=(Settings::get_windowed_mode() ? 'f' : 't'); + fs_str[1]='\0'; + os << "fullscreen #" << fs_str << "\n\n"; + + write_audio(os, Settings::get_sfx(), Settings::get_music(), + Settings::get_sfx_volume(), Settings::get_music_volume()); + os << endl; + + unsigned int xsize,ysize; + Settings::get_resolution_sizes(Settings::get_resolution(),xsize,ysize); + write_res(os,xsize,ysize); + os << endl; + + if(bPickBestRes && !bDoSavedSettingsExist) { + // right now pickbestres only works for dx9, so in case we switch to dx8/ogl we need to write + // a win-size res as normal. + + // for now behavior is 'no-override' by default, + // if file 'useropt' already exists, dont want to override a saved res in that + os << "pick-best-screenres #t\n\n"; + } + + switch (Settings::server_type()) { + case Settings::PRODUCTION: + write_prod(os); + break; + case Settings::DEVELOPMENT: + write_dev(os); + break; + case Settings::DEBUG: + write_debug(os); + break; + default: + // this is an error, it must be one of the above. Emit prod by default + cerr << "There is an error in the settings file w.r.t. the server type\n"; + write_prod(os); + } +#ifdef USE_OFSTREAM + os.close(); +#endif + +#ifdef _WIN32 + // write regkeys for installer to read and send to stat server + + HKEY hKeyToontown; + ULONG regRetVal=RegOpenKeyEx(HKEY_LOCAL_MACHINE, ToontownRegKeyName, 0, + KEY_WRITE, &hKeyToontown); + if(regRetVal!=ERROR_SUCCESS) { + return 7; + } + + // probably cleaner to write all these settings in a 'ConfigSettings' subkey + DWORD bIsWindowed=Settings::get_windowed_mode(); + RegSetValueEx(hKeyToontown, "UsingWindowedMode", 0, REG_DWORD, (LPBYTE)&bIsWindowed, sizeof(bIsWindowed)); + char buf[20]; + sprintf(buf,"%dx%d",xsize,ysize); + RegSetValueEx(hKeyToontown, "LastScreenSize", 0, REG_SZ, (LPBYTE)buf, strlen(buf)); + + // we should really sync installer sysinfo.h and settings.h defs so they use the same ones and can communicate more easily + // probably means changing settingsfilename(useropt) format to sysinfo's since it is more detailed + + SysInfo::GAPIType gapi=SysInfo::GAPI_Unknown; + switch (driver_type) { + case Settings::DX9: + // installer should bump this down to 9.0 if that's all they have + gapi=SysInfo::GAPI_DirectX_9_0; + break; + case Settings::DX8: + case Settings::DX7: + // installer should bump this down to 8.0 if that's all they have + gapi=SysInfo::GAPI_DirectX_8_1; + break; + case Settings::GL: + gapi=SysInfo::GAPI_OpenGL; + write_opengl_hardware_info(hKeyToontown); + break; + } + RegSetValueEx(hKeyToontown, "GfxApiUsed", 0, REG_DWORD, (LPBYTE)&gapi, sizeof(SysInfo::GAPIType)); + + RegCloseKey(hKeyToontown); +#endif + return 0; +} + +#ifdef _WIN32 +// since installer cant read useropt, only configrc.exe knows we are using ogl soon enough to get the glGetString info. +// and even if installer could read useropt, freelib(ogl32.dl) doesnt unload the driver dll, which sits around in IE +// hoggin memory, so better to do this stuff here + +#include + +/* why wont this work? always gives LNK4229 error. waaah. just static-link for now. +#pragma comment(lib,"delayimp.lib") +#pragma comment(linker, "/DELAYLOAD:gdi32.dll") +*/ + +// tests opengl support by opening a new window we can set to ogl fmt +// need to do this for glGetString to work + +static HWND CreateOpenGLWindow(char* title, int pixfmtnum, PIXELFORMATDESCRIPTOR *pPFD, int x, int y, int width, int height, BYTE type, DWORD flags,HDC *pHDC) { + HWND hWnd; + WNDCLASS wc; + + static HINSTANCE hInstance = 0; + const char *pOGLWinClassName = "Test_OpenGLWndClass"; + *pHDC = NULL; + + assert(pPFD!=NULL && pHDC!=NULL); + + /* only register the window class once - use hInstance as a flag. */ + if (!hInstance) { + hInstance = GetModuleHandle(NULL); + if (!hInstance) { + cerr << "GetModuleHandle() failed, err=" << GetLastError() <MaxPixFmtNum) { + // mustve set this mode manually? + cerr << "Error: OGL mode set, but detected no OpenGL hardware support!\n"; + goto _cleanup; + } + + // need to get glString info, so must create context and make it current + // might be cleaner to delay load opengl32.dll + + // Note: this method seems to waste memory because even though we FreeLib(opengl32), + // the OGL driver dll (e.g. nvoglnt.dll) seems to stay loaded. + + /* glGetString will always fail unless you must create a wglContext and make it current. + only way I can think of to get info w/o creation is to use the OpenGLDrivers regkey to get driver dllname + (e.g. nvoglnt.dll) and call glGetString directly on that dll, but I dont know how gdi picks which one of the keys + under OpenGLDrivers\ it uses. + + // Note: if opengl32.dll is not loaded statically or dynamically, DescribePixelFormat will fail with + // ERROR_MOD_NOT_FOUND on win9x + + */ + + // dont static link ogl, save memory + typedef HGLRC (WINAPI *WGLCREATECONTEXTPROC)(HDC); + const char *pWGLCCStr="wglCreateContext"; + WGLCREATECONTEXTPROC pWglCreateContext = (WGLCREATECONTEXTPROC) GetProcAddress(hOGL, pWGLCCStr); + if (NULL == pWglCreateContext) { + cerr << "Error: GetProcAddr failed for " << pWGLCCStr << ", err=" << GetLastError() << endl; + goto _cleanup; + } + typedef BOOL (WINAPI *WGLDELETECONTEXTPROC)(HGLRC); + const char *pWGLDelCStr="wglDeleteContext"; + WGLDELETECONTEXTPROC pWglDeleteContext = (WGLDELETECONTEXTPROC) GetProcAddress(hOGL, pWGLDelCStr); + if (NULL == pWglDeleteContext) { + cerr << "Error: GetProcAddr failed for " << pWGLDelCStr << ", err=" << GetLastError() << endl; + goto _cleanup; + } + typedef BOOL (WINAPI *WGLMAKECURRENTPROC)(HDC, HGLRC); + const char *pWGLMakeCurStr="wglMakeCurrent"; + WGLMAKECURRENTPROC pWglMakeCurrent = (WGLMAKECURRENTPROC) GetProcAddress(hOGL, pWGLMakeCurStr); + if (NULL == pWglMakeCurrent) { + cerr << "Error: GetProcAddr failed for " << pWGLMakeCurStr << ", err=" << GetLastError() << endl; + goto _cleanup; + } + typedef const GLubyte * (WINAPI *GLGETSTRINGPROC)(GLenum name); + const char *pGLGetStr="glGetString"; + GLGETSTRINGPROC pGlGetString = (GLGETSTRINGPROC) GetProcAddress(hOGL, pGLGetStr); + if (NULL == pGlGetString) { + cerr << "Error: GetProcAddr failed for " << pGLGetStr << ", err=" << GetLastError() << endl; + goto _cleanup; + } + + // for glGetString to work, we must create a window and do wglMakeCurrent() + + HDC hOGLWinDC=NULL; + HWND hOGLWnd = CreateOpenGLWindow("opengl_testwindow",pfnum,&pfd,1,1,1,1,0,0x0,&hOGLWinDC); + if(!hOGLWnd) { + cerr << "Error: failed to create OGL test window!\n"; + goto _cleanup; + } + + HGLRC hRC = (*pWglCreateContext)(hOGLWinDC); + if(hRC==NULL) { + cerr << "Error: wglCreateContext failed, err=" << GetLastError() << endl; + goto _wndcleanup; + } + BOOL ret=(*pWglMakeCurrent)(hOGLWinDC, hRC); + if(!ret) { + cerr << "Error: wglMakeCurrent failed, err=" << GetLastError() << endl; + goto _wndcleanup; + } + + const char *vendStr=(const char *) (*pGlGetString)(GL_VENDOR); + const char *rendStr=(const char *) (*pGlGetString)(GL_RENDERER); + const char *versStr=(const char *) (*pGlGetString)(GL_VERSION); + + + if(vendStr!=NULL) + RegSetValueEx(hKeyToontown, "OGLVendor", 0, REG_SZ, (LPBYTE)vendStr, strlen(vendStr)); + if(rendStr!=NULL) + RegSetValueEx(hKeyToontown, "OGLRenderer", 0, REG_SZ, (LPBYTE)rendStr, strlen(rendStr)); + if(versStr!=NULL) + RegSetValueEx(hKeyToontown, "OGLVersion", 0, REG_SZ, (LPBYTE)versStr, strlen(versStr)); + + /* + cerr << "GL_VENDOR: " << _OGLVendorNameStr + << ", GL_RENDERER: " << _OGLRendererNameStr + << ", GL_VERSION: " << _OGLVerStr << endl; + */ + + _wndcleanup: + if(hRC!=NULL) { + (*pWglMakeCurrent)(hOGLWinDC, NULL); + (*pWglDeleteContext)(hRC); + } + if(hOGLWinDC!=NULL) + ReleaseDC(hOGLWnd,hOGLWinDC); // actually has no effect because of CS_OWNDC flag window was created with + if(hOGLWnd!=NULL) + DestroyWindow(hOGLWnd); + + // BUGBUG: need to add stuff to look through all REG_SZ subkeys of + // (w9x) [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\OpenGLdrivers] + // (NT) [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers] + // (use RegEnumKey, RegEnumValue) and call SearchForDriverInfo on each non-null string, + // and print out driver date and version for all listed drivers. you can figure out + // the one actually in use (at least the manufacturer anyway) from the GL_VENDOR string + // printed by wglDisplay + + _cleanup: + + if(hOGL) + FreeLibrary(hOGL); //leaving it loaded in IE is of no benefit, since panda is a separate process +} +#endif diff --git a/otp/src/configrc/Sources.pp b/otp/src/configrc/Sources.pp new file mode 100644 index 0000000..9ea10d1 --- /dev/null +++ b/otp/src/configrc/Sources.pp @@ -0,0 +1,52 @@ +#define BUILD_DIRECTORY $[HAVE_OPENSSL] + +#define OTHER_LIBS \ + dtoolutil:c dtoolbase:c dtool:m + +#define WIN_SYS_LIBS advapi32.lib user32.lib gdi32.lib +#define USE_PACKAGES openssl + +#begin lib_target + #define TARGET settings + #define SOURCES settingsFile.h settingsFile.cxx + #define IGATESCAN settingsFile.h +#end lib_target + +#if $[USE_TESTSERVER] + #print Configuring Configrc.exe build for test server +#endif + +#if $[and $[ne $[LANGUAGE],], $[ne $[LANGUAGE], english]] + #print Configuring $[LANGUAGE] Configrc.exe build for server + #define ODIR_SUFFIX $[ODIR_SUFFIX]-$[LANGUAGE] + // This is also defined in $TOONTOWN/src/publish/client-build-Config.pp. + // But this is probably a better place to put it, so we can successfully + // compile this file in OPT3 for development purposes. + #define EXTRA_CDEFS $[EXTRA_CDEFS] USE_$[upcase $[LANGUAGE]] +#else + #print Defaulting to ENGLISH Configrc.exe build for server + #define EXTRA_CDEFS $[EXTRA_CDEFS] USE_ENGLISH +#endif + +// FIX early evaluation flaw with DTOOLS +#defer ODIR Opt$[OPTIMIZE]-$[PLATFORM]$[ODIR_SUFFIX] + +#begin bin_target + + #define OTHER_LIBS $[OTHER_LIBS] pystub + +#if $[or $[eq $[PLATFORM], Cygwin], $[eq $[PLATFORM],Win32]] +// UPX writes 'UPX' in the exe, but it's better than nothing until I can find a better encrypter +// test_pfstream Configrc.exe -stdout works, so should work in publish + #define bin_postprocess_cmd upx + #define bin_postprocess_arg1 --force // upx bombs on vc7.1 .exes w/o this + #define bin_postprocess_arg2 -o + #define bin_postprocess_target Configrc + #define TARGET Configrc_u +#else + #define TARGET Configrc +#endif + + #define SOURCES Configrc.cxx settingsFile.cxx key_src.cxx \ + serialization.h serialization.I +#end bin_target diff --git a/otp/src/configrc/french/configrc_nls.h b/otp/src/configrc/french/configrc_nls.h new file mode 100644 index 0000000..1df90ca --- /dev/null +++ b/otp/src/configrc/french/configrc_nls.h @@ -0,0 +1,12 @@ +// Filename: configrc_nls.h +// $Id$ +// +//////////////////////////////////////////////////////////////////// + +// for France only +#define CRC_BUILDNUM 1 +#define CRC_LANGNUM 5 +#define CRC_LOGINTYPE "playToken" + +#define CRC_LANGUAGE "french" +#define CRC_DEPLOYMENT "FR" diff --git a/otp/src/configrc/japanese/configrc_nls.h b/otp/src/configrc/japanese/configrc_nls.h new file mode 100644 index 0000000..7968f96 --- /dev/null +++ b/otp/src/configrc/japanese/configrc_nls.h @@ -0,0 +1,12 @@ +// Filename: configrc_nls.h +// $Id$ +// +//////////////////////////////////////////////////////////////////// + +// for Japanese only +#define CRC_BUILDNUM 4 +#define CRC_LANGNUM 2 // JAPANESE +#define CRC_LOGINTYPE "playToken" + +#define CRC_LANGUAGE "japanese" +#define CRC_DEPLOYMENT "JP" diff --git a/otp/src/configrc/key_src.cxx b/otp/src/configrc/key_src.cxx new file mode 100644 index 0000000..2d99fee --- /dev/null +++ b/otp/src/configrc/key_src.cxx @@ -0,0 +1,27 @@ + +/* + * This table was generated by the command: + * + * bin2c -static -o /home/dlo/player3/otp/src/configrc/key_src.cxx -n pubkey xrc_pub.bin + */ + +static const unsigned char pubkey[] = { + 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, + 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xb2, 0x74, 0x26, 0x4f, + 0xb8, 0xa4, 0x99, 0x87, 0x27, 0xa7, 0x78, 0xc2, 0xcb, 0xc1, 0x04, + 0xbe, 0x90, 0x90, 0xb3, 0x5b, 0xd8, 0xa8, 0x5e, 0x11, 0x38, 0x03, + 0x12, 0xa9, 0x25, 0xe0, 0x06, 0xda, 0x62, 0x49, 0x09, 0x08, 0x34, + 0x34, 0x68, 0x40, 0xb1, 0x5a, 0x43, 0xc5, 0x28, 0xb8, 0x52, 0x37, + 0xf4, 0xe2, 0x1f, 0x24, 0xd2, 0x1e, 0xca, 0xbc, 0xcb, 0x7f, 0x66, + 0xe7, 0x06, 0xac, 0xc4, 0x3e, 0x55, 0xeb, 0x5d, 0xf0, 0xd5, 0x4a, + 0xe5, 0xe4, 0xab, 0x34, 0xe9, 0x2e, 0x36, 0x30, 0xaa, 0xeb, 0x39, + 0x44, 0xd1, 0x84, 0x99, 0x26, 0xcd, 0xd1, 0xb2, 0x6a, 0xb0, 0x1e, + 0x2a, 0x49, 0xe2, 0x0d, 0x69, 0xd0, 0xf9, 0xa5, 0xa0, 0xd3, 0x65, + 0xcf, 0x3f, 0x7d, 0x26, 0x48, 0x7b, 0xef, 0x68, 0x7d, 0x43, 0x3f, + 0xda, 0x94, 0x1d, 0xd6, 0x77, 0x71, 0x7e, 0x05, 0xa2, 0xe4, 0xaf, + 0x4d, 0xf4, 0xad, 0x02, 0x03, 0x01, 0x00, 0x01 +}; + +static const int pubkey_len = 162; + diff --git a/otp/src/configrc/portuguese/configrc_nls.h b/otp/src/configrc/portuguese/configrc_nls.h new file mode 100644 index 0000000..2cfd5d5 --- /dev/null +++ b/otp/src/configrc/portuguese/configrc_nls.h @@ -0,0 +1,12 @@ +// Filename: configrc_nls.h +// $Id$ +// +//////////////////////////////////////////////////////////////////// + +// for Brazil only +#define CRC_BUILDNUM 7 +#define CRC_LANGNUM 4 +#define CRC_LOGINTYPE "playToken" + +#define CRC_LANGUAGE "portuguese" +#define CRC_DEPLOYMENT "BR" diff --git a/otp/src/configrc/serialization.I b/otp/src/configrc/serialization.I new file mode 100644 index 0000000..07f4fae --- /dev/null +++ b/otp/src/configrc/serialization.I @@ -0,0 +1,21 @@ +// Filename: serialization.I +// Created by: cary (20Mar00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +INLINE int Length(char) { + return 1; +} + +INLINE int Length(char* x) { + return strlen(x); +} diff --git a/otp/src/configrc/serialization.h b/otp/src/configrc/serialization.h new file mode 100644 index 0000000..ed5806a --- /dev/null +++ b/otp/src/configrc/serialization.h @@ -0,0 +1,184 @@ +// Filename: serialization.h +// Created by: cary (26Aug98) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef __SERIALIZATION_H__ +#define __SERIALIZATION_H__ + +#include "dtoolbase.h" + +namespace Serialize { + +typedef string ConfigString; + +template +class StdIns { + public: + INLINE ConfigString operator()(const X& val) { + ostringstream oss; + oss << val; + return oss.str(); + } +}; + +template +class StdExt { + public: + INLINE X operator()(ConfigString S) { + istringstream iss(S); + X ret; + iss >> ret; + return ret; + } +}; + +template +INLINE int Length(X c) { + return c.length(); +} + +template > +class Serializer { + private: + ConfigString _result; + + ConfigString SerializeToString(const Collection&, const ConfigString&); + Serializer() {} + public: + Serializer(const Collection& C, ConfigString Delim = ":") : + _result(Serializer::SerializeToString(C, Delim)) {} + Serializer(const Serializer& c) : + _result(c._result) {} + ~Serializer() {} + INLINE ConfigString operator()() { return _result; } + INLINE ConfigString operator()(const Collection& C, + const ConfigString& Delim = ":") { + _result = SerializeToString(C, Delim); + return _result; + } + INLINE operator ConfigString() { return _result; } +}; + +template +ConfigString +Serializer::SerializeToString(const Collection& C, + const ConfigString& Delim) +{ + ConfigString ret; + Inserter in; + + for (TYPENAME Collection::const_iterator i=C.begin(); i!=C.end(); ++i) { + if (i != C.begin()) + ret += Delim; + ret += in(*i); + } + return ret; +} + +template > +class Deserializer { + private: + Collection _result; + + INLINE void Clear() { _result.erase(_result.begin(), _result.end()); } + template + int FindFirstOfInString(ConfigString S, ForwardIterator DelimBegin, + ForwardIterator DelimEnd) { + int i = ConfigString::npos; + ForwardIterator j = DelimBegin; + + while (j != DelimEnd) { + int k = S.find(*j); + if (k != ConfigString::npos) + if ((i == ConfigString::npos) || (i > k)) + i = k; + ++j; + } + return i; + } + template + int FindFirstNotOfInString(ConfigString S, ForwardIterator DelimBegin, + ForwardIterator DelimEnd) { + int i = ConfigString::npos; + ForwardIterator j = DelimBegin; + ForwardIterator k = DelimBegin; + + while (j != DelimEnd) { + int l = S.find(*j); + if (l != ConfigString::npos) + if ((i == ConfigString::npos) || (i > l)) { + i = l; + k = j; + } + ++j; + } + if (i != ConfigString::npos) { + i += Serialize::Length(*k); + if (i >= S.length()) + i = ConfigString::npos; + } + return i; + } + template + void DeserializeFromString(ConfigString S, ForwardIterator DelimBegin, + ForwardIterator DelimEnd) { + Clear(); + Extractor ex; + + while (!S.empty()) { + int i = FindFirstOfInString(S, DelimBegin, DelimEnd); + _result.push_back(ex(S.substr(0, i))); + S.erase(0, i); + i = FindFirstNotOfInString(S, DelimBegin, DelimEnd); + S.erase(0, i); + } + } + void DeserializeFromString(ConfigString S, ConfigString Delim) { + Clear(); + Extractor ex; + + while (!S.empty()) { + size_t i = S.find_first_of(Delim); + _result.push_back(ex(S.substr(0, i))); + if (i == ConfigString::npos) + S.erase(0, i); + else + S.erase(0, i+1); + } + } + Deserializer() {} + public: + Deserializer(ConfigString S, ConfigString Delim = ":") { + Deserializer::DeserializeFromString(S, Delim); + } + template + Deserializer(ConfigString S, ForwardIterator DelimBegin, + ForwardIterator DelimEnd) { + Deserializer::DeserializeFromString(S, DelimBegin, DelimEnd); + } + ~Deserializer() {} + INLINE const Collection& operator()() { return _result; } + template + INLINE const Collection& operator()(ConfigString S, + ForwardIterator DelimBegin, + ForwardIterator DelimEnd) { + Deserializer::DeserializeFromString(S, DelimBegin, DelimEnd); + } + INLINE operator const Collection&() { return _result; } +}; + +#include "serialization.I" + +} // close Serialize namespace + +#endif /* __SERIALIZATION_H__ */ diff --git a/otp/src/configrc/settingsFile.I b/otp/src/configrc/settingsFile.I new file mode 100644 index 0000000..0fb6743 --- /dev/null +++ b/otp/src/configrc/settingsFile.I @@ -0,0 +1,267 @@ +// Filename: settingsFile.I +// Created by: cary (14Dec00) +// +//////////////////////////////////////////////////////////////////// + +INLINE bool Settings::get_sfx(void) { + return get_ptr()->ns_get_sfx(); +} + +INLINE bool Settings::get_toon_chat_sounds(void) { + return get_ptr()->ns_get_toon_chat_sounds(); +} + +INLINE bool Settings::get_music(void) { + return get_ptr()->ns_get_music(); +} + +INLINE bool Settings::get_force_sw_midi(void) { + return get_ptr()->ns_get_force_sw_midi(); +} + +INLINE bool Settings::want_chat_log(void) { + return get_ptr()->ns_want_chat_log(); +} + +INLINE bool Settings::get_windowed_mode(void) { + return get_ptr()->ns_get_windowed_mode(); +} + +INLINE bool Settings::get_show_fpsmeter(void) { + return get_ptr()->ns_get_show_fpsmeter(); +} + +INLINE bool Settings::want_custom_mouse_cursor(void) { + return get_ptr()->ns_want_custom_mouse_cursor(); +} + +INLINE float Settings::get_sfx_volume(void) { + return get_ptr()->ns_get_sfx_volume(); +} + +INLINE float Settings::get_music_volume(void) { + return get_ptr()->ns_get_music_volume(); +} + +INLINE Settings::DisplayDriver Settings::display_driver(void) { + return get_ptr()->ns_display_driver(); +} + +INLINE Settings::Resolution Settings::get_resolution(void) { + return get_ptr()->ns_get_resolution(); +} + +INLINE Settings::ServerType Settings::server_type(void) { + return get_ptr()->ns_server_type(); +} + +INLINE bool Settings::get_accepting_new_friends(void) { + return get_ptr()->ns_get_accepting_new_friends(); +} + +INLINE bool Settings::get_embedded_mode(void) { + return get_ptr()->ns_get_embedded_mode(); +} + +INLINE void Settings::set_sfx(bool active) { + get_ptr()->ns_set_sfx(active); +} + +INLINE void Settings::set_toon_chat_sounds(bool active) { + get_ptr()->ns_set_toon_chat_sounds(active); +} + +INLINE void Settings::set_windowed_mode(bool bUseWindowedMode) { + get_ptr()->ns_set_windowed_mode(bUseWindowedMode); +} + +INLINE void Settings::set_music(bool active) { + get_ptr()->ns_set_music(active); +} + +INLINE void Settings::set_force_sw_midi(bool bForceSWMidi) { + get_ptr()->ns_set_force_sw_midi(bForceSWMidi); +} + +INLINE void Settings::set_show_fpsmeter(bool bShowFpsMeter) { + get_ptr()->ns_set_show_fpsmeter(bShowFpsMeter); +} + +INLINE void Settings::set_chat_log(bool active) { + get_ptr()->ns_set_chat_log(active); +} + +INLINE void Settings::set_custom_mouse_cursor(bool cursor_enabled) { + get_ptr()->ns_set_custom_mouse_cursor(cursor_enabled); +} + +INLINE void Settings::set_sfx_volume(float vol) { + get_ptr()->ns_set_sfx_volume(vol); +} + +INLINE void Settings::set_music_volume(float vol) { + get_ptr()->ns_set_music_volume(vol); +} + +INLINE void Settings::set_display_driver(Settings::DisplayDriver driver) { + get_ptr()->ns_set_display_driver(driver); +} + +INLINE void Settings::set_resolution(Settings::Resolution res) { + get_ptr()->ns_set_resolution(res); +} + +INLINE void Settings::set_server_type(Settings::ServerType serve) { + get_ptr()->ns_set_server_type(serve); +} + +INLINE void Settings::set_accepting_new_friends(bool afr) { + get_ptr()->ns_set_accepting_new_friends(afr); +} + +INLINE void Settings::set_embedded_mode(bool afr) { + get_ptr()->ns_set_embedded_mode(afr); +} + +INLINE void Settings::write_settings(void) { + get_ptr()->ns_write_settings(); +} + +INLINE void Settings::read_settings(void) { + get_ptr()->ns_read_settings(); +} + +INLINE bool Settings::ns_doSavedSettingsExist(void) { + // does the saved settings file exist? + return _bReadSavedData; +} + +INLINE bool Settings::doSavedSettingsExist(void) { + return get_ptr()->ns_doSavedSettingsExist(); +} + +INLINE Settings* Settings::get_ptr(void) { + if (_singleton == (Settings*)0L) + _singleton = new Settings; + return _singleton; +} + +INLINE bool Settings::ns_get_sfx(void) { + return _sfx; +} + +INLINE bool Settings::ns_get_toon_chat_sounds(void) { + return _toon_chat_sounds; +} + +INLINE bool Settings::ns_get_music(void) { + return _music; +} + +INLINE bool Settings::ns_get_force_sw_midi(void) { + return _bForceSWMidi; +} + +INLINE bool Settings::ns_get_windowed_mode(void) { + return _bUseWindowedMode; +} + +INLINE bool Settings::ns_get_show_fpsmeter(void) { + return _bShowFpsMeter; +} + +INLINE bool Settings::ns_want_chat_log(void) { + return _chat; +} + +INLINE bool Settings::ns_want_custom_mouse_cursor(void) { + return _custom_mousecursor_enabled; +} + +INLINE float Settings::ns_get_sfx_volume(void) { + return _sfx_vol; +} + +INLINE float Settings::ns_get_music_volume(void) { + return _music_vol; +} + +INLINE Settings::DisplayDriver Settings::ns_display_driver(void) { + return _driver; +} + +INLINE Settings::Resolution Settings::ns_get_resolution(void) { + return _res; +} + +INLINE Settings::ServerType Settings::ns_server_type(void) { + return _stype; +} + +INLINE bool Settings::ns_get_accepting_new_friends(void) { + return _accepting_new_friends; +} + +INLINE bool Settings::ns_get_embedded_mode(void) { + return _embedded_mode; +} + +INLINE void Settings::ns_set_sfx(bool active) { + _sfx = active; +} + +INLINE void Settings::ns_set_toon_chat_sounds(bool active) { + _toon_chat_sounds = active; +} + +INLINE void Settings::ns_set_windowed_mode(bool bUseWindowedMode) { + _bUseWindowedMode = bUseWindowedMode; +} + +INLINE void Settings::ns_set_music(bool active) { + _music = active; +} + +INLINE void Settings::ns_set_force_sw_midi(bool bForceSWMidi) { + _bForceSWMidi = bForceSWMidi; +} + +INLINE void Settings::ns_set_show_fpsmeter(bool bShowFpsMeter) { + _bShowFpsMeter = bShowFpsMeter; +} + +INLINE void Settings::ns_set_chat_log(bool active) { + _chat = active; +} + +INLINE void Settings::ns_set_custom_mouse_cursor(bool cursor_enabled) { + _custom_mousecursor_enabled = cursor_enabled; +} + +INLINE void Settings::ns_set_sfx_volume(float vol) { + _sfx_vol = vol; +} + +INLINE void Settings::ns_set_music_volume(float vol) { + _music_vol = vol; +} + +INLINE void Settings::ns_set_display_driver(Settings::DisplayDriver driver) { + _driver = driver; +} + +INLINE void Settings::ns_set_resolution(Settings::Resolution res) { + _res = res; +} + +INLINE void Settings::ns_set_server_type(Settings::ServerType stype) { + _stype = stype; +} + +INLINE void Settings::ns_set_accepting_new_friends(bool afr) { + _accepting_new_friends = afr; +} + +INLINE void Settings::ns_set_embedded_mode(bool afr) { + _embedded_mode = afr; +} diff --git a/otp/src/configrc/settingsFile.cxx b/otp/src/configrc/settingsFile.cxx new file mode 100644 index 0000000..d3ecfb4 --- /dev/null +++ b/otp/src/configrc/settingsFile.cxx @@ -0,0 +1,565 @@ +// Filename: settingsFile.cxx +// Created by: cary (14Dec00) +// +//////////////////////////////////////////////////////////////////// + +#include "settingsFile.h" +#include "dSearchPath.h" +#include "executionEnvironment.h" +#include "serialization.h" + +const char *configrc_settings_filename = "useropt"; +const char *configrc_debug_filename = "readlog.txt"; +Settings* Settings::_singleton = (Settings*)0L; + +Settings::~Settings(void) { + _singleton = (Settings*)0L; +} + +static void CropString(string& S) { + size_t i = S.find_first_not_of(" \t\r\f\n"); + if (i != string::npos) { + size_t j = S.find_last_not_of(" \t\r\f\n"); + if (j != string::npos) + S = S.substr(i, j-i+1); + else + S = S.substr(i, string::npos); + } else + S.erase(0, string::npos); +} + +string Settings::get_config_path(void) { + // this code is lifted from configTable.cxx. But since this code cannot + // use config in any way (otherwise it might recurse), we have to dup the + // logic here. + static string ret = ""; // lets please only do this once + + if (!(ret.empty())) + return ret; + + // ok, this'll be fun. Start by processing the CONFIG_CONFIG environment + bool cpth = false; + string cpath; + string cc = ExecutionEnvironment::get_environment_variable("CONFIG_CONFIG"); + if ((!cc.empty()) && (cc.length() > 1)) { + string configconfig(cc); + string assign = "="; + string sep = configconfig.substr(0, 1); + typedef pvector strvec; + typedef Serialize::Deserializer > deser; + configconfig.erase(0, 1); + deser ds(configconfig, sep); + strvec sv = ds; + for (strvec::iterator i=sv.begin(); i!=sv.end(); ++i) { + if ((*i).length() == 1) { + // new assignment character + assign += *i; + continue; + } + size_t j = (*i).find_first_of(assign); + if (j != string::npos) { + string tok = (*i).substr(0, j); + string rest = (*i).substr(j+1, string::npos); + if (tok == "configpath") { + if (cpth) + cpath += " " + rest; + else + cpath = rest; + cpth = true; + } + } + } // for + } + if (!cpth) { + // nothing in CONFIG_CONFIG, pull a default +#ifdef PENV_PS2 + cpath = ""; +#else /* PENV_PS2 */ + cpath = "CONFIG_PATH"; +#endif /* PENV_PS2 */ + } + if (cpath.empty()) { +#ifdef PENV_PS2 +#ifndef CONFIG_PATH +#define CONFIG_PATH "." +#endif /* CONFIG_PATH */ + cpath = CONFIG_PATH; +#else /* PENV_PS2 */ + cpath = "."; +#endif /* PENV_PS2 */ + } else { + string S; + while (!cpath.empty()) { + int i = cpath.find_first_of(" "); + string stmp = cpath.substr(0, i); + if (ExecutionEnvironment::has_environment_variable(stmp)) { + S += " "; + S += ExecutionEnvironment::get_environment_variable(stmp); + } + cpath.erase(0, i); + CropString(cpath); + } + if (S.empty()) + S = "."; + CropString(S); + cpath = S; + } + ret = cpath; + return ret; +} + +Settings::Settings(void) { + string path = get_config_path(); + DSearchPath setting_search(path); + DSearchPath::Results setting_files; + + setting_search.find_all_files(configrc_settings_filename, setting_files); + int n = setting_files.get_num_files(); + if (n != 0) { + for (int i=n-1; i>=0; --i) { + Filename setting_file = setting_files.get_file(i); + setting_file.set_binary(); + read_file(setting_file); + } + _bReadSavedData=true; + } else { + // no settings file +#if 0 + // This is silly. Just put it in the current directory. + string foo = setting_search.get_directory(0).c_str(); + foo += "/"; + foo += configrc_settings_filename; + _fname = foo; +#else + _fname = configrc_settings_filename; +#endif + _fname.set_binary(); + _sfx = true; + _music = true; + _chat = true; + _sfx_vol = 1.0f; + _music_vol = 1.0f; + _driver = D_DEFAULT; + _res = R800x600; + _stype = PRODUCTION; + _bUseWindowedMode=true; + _bShowFpsMeter=false; + _bReadSavedData=false; + _custom_mousecursor_enabled = true; + _accepting_new_friends = true; + _embedded_mode=false; + } +} + +inline void write_nibble(ostream& os, unsigned char n) { + switch (n) { + case 0: + cerr << "0"; + break; + case 1: + cerr << "1"; + break; + case 2: + cerr << "2"; + break; + case 3: + cerr << "3"; + break; + case 4: + cerr << "4"; + break; + case 5: + cerr << "5"; + break; + case 6: + cerr << "6"; + break; + case 7: + cerr << "7"; + break; + case 8: + cerr << "8"; + break; + case 9: + cerr << "9"; + break; + case 10: + cerr << "A"; + break; + case 11: + cerr << "B"; + break; + case 12: + cerr << "C"; + break; + case 13: + cerr << "D"; + break; + case 14: + cerr << "E"; + break; + case 15: + cerr << "F"; + break; + default: + cerr << "*"; + break; + } +} + +inline void write_byte(ostream& os, unsigned char b) { + unsigned char b1 = (b >> 4); + unsigned char b2 = (b & 0x0f); + write_nibble(os, b1); + write_nibble(os, b2); +} + +void Settings::ns_write_settings(void) { + pofstream ofs; + + string path = get_config_path(); + DSearchPath setting_search(path); + string dir = setting_search.get_directory(0); + Filename f2 = dir + "/" + configrc_settings_filename; + f2.set_binary(); + + //cerr << "Settings Path " << configrc_settings_filename << " " << _fname << endl; + int canWrite = 0; + + if (f2.open_write(ofs)) { + canWrite = 1; + } + else{ + if (_fname.open_write(ofs)) { + canWrite = 1; + } + } + if (canWrite == 0){ + cerr << "could not open '" << _fname << "' or '" << f2 << "' for writing" + << endl; + return; + } + // to add new fields, increment version number, write backward-compatible read_settings (so field + // is set to suitable default for all version #'s and field is not read off disk if version too old), + // and add new fields to write_settings + + // header + ofs << "UserSettings"; + // cerr << "wrote 'UserSettings'" << endl; + // major version + ofs << (unsigned char)CONFIGRC_MAJOR_VERSION; + // cerr << "wrote 0x01" << endl; + // minor version + ofs << (unsigned char)CONFIGRC_MINOR_VERSION; + // cerr << "wrote 0x00" << endl; + // data + ofs << (_sfx?((unsigned char)1):((unsigned char)0)) ; + // cerr << "wrote 0x" << (_sfx?"01":"00") << endl; + ofs << (_music?((unsigned char)1):((unsigned char)0)) ; + // cerr << "wrote 0x" << (_music?"01":"00") << endl; + ofs << (_chat?((unsigned char)1):((unsigned char)0)) ; + // cerr << "wrote 0x" << (_chat?"01":"00") << endl; + unsigned char* b; + float ftmp = _sfx_vol; + unsigned int cnt = sizeof(float); + // cerr << "wrote 0x"; + for (unsigned int i=0; i> b; + header += b; + } + if (!(header == "UserSettings")) { + // set some defaults, most of these will generate error messages in the + // output + // cerr << "header ('" << header << "') != 'User Settings'" << endl; + ofs << "Invalid Header: " << header << endl; + _sfx = true; + _music = true; + _chat = true; + _sfx_vol = 1.0f; + _music_vol = 1.0f; + _driver = D_NONE; + _res = R_NONE; + _stype = S_NONE; + return; + } + // cerr << "read '" << header << "'" << endl; + int major, minor; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + major = b; + ofs << "major: " << major << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + minor = b; + ofs << "minor: " << major << endl; + switch (major) { + case 0: + ofs << "case 0" << endl; + ifs >> b; + _sfx = _music = (b != 0); + ofs << "_sfx: " << _sfx << endl; + ifs >> b; + _driver = (DisplayDriver)b; + ofs << "_driver: " << _driver << endl; + ifs >> b; + _res = (Resolution)b; + ofs << "_res: " << _res << endl; + ifs >> b; + _stype = (ServerType)b; + ofs << "_stype: " << _stype << endl; + // things not covered in this version + _sfx_vol = _music_vol = 1.0f; + ofs << "_sfx_vol: " << _sfx_vol << endl; + _chat = true; + _custom_mousecursor_enabled = true; + + break; + case 1: + ofs << "case 1" << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _sfx = (b != 0); + ofs << "_sfx: " << _sfx << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _music = (b != 0); + ofs << "_music: " << _music << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _chat = (b != 0); + ofs << "_chat: " << _chat << endl; + { + float ftmp = 0.0f; + unsigned int cnt = sizeof(float); + // cerr << "read 0x"; + for (unsigned int i=0; i> b; + // write_byte(cerr, b); + unsigned char* a = (unsigned char*)(&ftmp); + a += i; + *a = b; + } + // cerr << endl; + _sfx_vol = ftmp; + ofs << "_sfx_vol: " << _sfx_vol << endl; + ftmp = 0.0f; + // cerr << "read 0x"; + for (unsigned int j=0; j> b; + // write_byte(cerr, b); + unsigned char* a = (unsigned char*)(&ftmp); + a += j; + *a = b; + } + // cerr << endl; + _music_vol = ftmp; + ofs << "_music_vol: " << _music_vol << endl; + } + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _driver = (DisplayDriver)b; + ofs << "_driver: " << _driver << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _res = (Resolution)b; + ofs << "_res: " << _res << endl; + ifs >> b; + // cerr << "read 0x"; + // write_byte(cerr, b); + // cerr << endl; + _stype = (ServerType)b; + ofs << "_stype: " << _stype << endl; + + if(minor>=1) { + ifs >> b; + _custom_mousecursor_enabled = (b!=false); + } else _custom_mousecursor_enabled = true; + ofs << "_custom_mousecursor_enabled: " << _custom_mousecursor_enabled << endl; + + if(minor>=2) { + ifs >> b; + _bUseWindowedMode = (b!=false); + } else _bUseWindowedMode = false; + ofs << "_bUseWindowedMode: " << _bUseWindowedMode << endl; + + if(minor>=3) { + ifs >> b; + _bShowFpsMeter = (b!=false); + } else _bShowFpsMeter = false; + ofs << "_bShowFpsMeter: " << _bShowFpsMeter << endl; + + if(minor>=4) { + ifs >> b; + _bForceSWMidi = (b!=false); + } else _bForceSWMidi = true; + ofs << "_bForceSWMidi: " << _bForceSWMidi << endl; + if(minor>=5) { + ifs >> b; + _toon_chat_sounds = (b!=false); + } else _toon_chat_sounds = true; + ofs << "_toon_chat_sounds: " << _toon_chat_sounds << endl; + + if(minor>=6) { + ifs >> b; + _accepting_new_friends = (b!=false); + } else _accepting_new_friends = true; + ofs << "_accepting_new_friends: " << _accepting_new_friends << endl; + + if(minor>=7) { + ifs >> b; + _embedded_mode = (b!=false); + } else _embedded_mode = false; + ofs << "_embedded_mode: " << _embedded_mode << endl; + + break; + default: + ofs << "default" << endl; + // unknown major #, spit out some 'defaults' + _sfx = _music = _chat = true; + _custom_mousecursor_enabled = true; + _bUseWindowedMode = true; + _bShowFpsMeter = false; + _bForceSWMidi = true; + _sfx_vol = _music_vol = 1.0f; + _driver = D_NONE; + _res = R_NONE; + _stype = S_NONE; + _toon_chat_sounds = true; + _accepting_new_friends = true; + _embedded_mode = false; + } + ifs.close(); + } + // } else + // cerr << "**** CANNOT READ FILE ****" << endl; +} + +// it would be simpler to just store the actual res sizes, but abstracting them makes +// it harder to crash with a bad resolution + +// this array must match: enum Resolution { R640x480, R800x600, R1024x768, R1280x1024, R1600x1200, R_NONE }; +const unsigned int resolution_dimensions[Settings::R_NONE][2] = {{640,480}, {800,600}, {1024,768}, {1280,1024}, {1600,1200}}; + +void Settings:: +get_resolution_sizes(Resolution r, unsigned int &xsize, unsigned int &ysize) { + xsize=resolution_dimensions[r][0]; + ysize=resolution_dimensions[r][1]; +} + +void Settings:: +set_resolution_dimensions(unsigned int xsize,unsigned int ysize) { + unsigned int r; + for(r=Resolution(0);r=R_NONE) { + cerr << "set_res failed, Enum Resolution does not contain " << xsize << "x" << ysize << endl; + return; + } + get_ptr()->ns_set_resolution((Resolution)r); +} + diff --git a/otp/src/configrc/settingsFile.h b/otp/src/configrc/settingsFile.h new file mode 100644 index 0000000..c6f16e3 --- /dev/null +++ b/otp/src/configrc/settingsFile.h @@ -0,0 +1,159 @@ +// Filename: settingsFile.h +// Created by: cary (14Dec00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef __SETTINGSFILE_H__ +#define __SETTINGSFILE_H__ + +#include "dtoolbase.h" +#include "filename.h" + +extern const char *configrc_settings_filename; + +#define CONFIGRC_MAJOR_VERSION 1 +#define CONFIGRC_MINOR_VERSION 7 + +// this is awful. We can't include toontownbase.h because it will cause +// a build circularity. So we have to define EXPCL_TOONTOWN on our own. + +#if defined(WIN32_VC) && !defined(CPPPARSER) + +#define EXPCL_TOONTOWN __declspec(dllexport) +#define EXPTP_TOONTOWN + +#else /* !WIN32_VC */ + +#define EXPCL_TOONTOWN +#define EXPTP_TOONTOWN + +#endif /* PENV_WIN32 */ + +class Settings { +PUBLISHED: + // The DisplayDriver option is written to the useropt file by value. + // Don't reorder or remove items from this list, and add all new + // options to the end, unless you are prepared to remap the old + // options to the new options based on the useropt file version + // number. + enum DisplayDriver { + GL, + DX7, // We don't support DX7 any more. This maps to DX8. + D_DEFAULT, // Formerly DX8 + DX9, + D_NONE, + DX8, + }; + enum ServerType { PRODUCTION, DEVELOPMENT, DEBUG, S_NONE }; + +// it would be simpler to just store the actual res sizes, but abstracting them makes +// it harder to crash with a bad resolution + enum Resolution { R640x480, R800x600, R1024x768, R1280x1024, R1600x1200, R_NONE }; + + virtual ~Settings(void); + + static INLINE bool get_sfx(void); + static INLINE bool get_toon_chat_sounds(void); + static INLINE bool get_music(void); + static INLINE bool get_force_sw_midi(void); + static INLINE bool get_windowed_mode(void); + static INLINE bool want_chat_log(void); + static INLINE bool get_show_fpsmeter(void); + static INLINE bool want_custom_mouse_cursor(void); + static INLINE float get_sfx_volume(void); + static INLINE float get_music_volume(void); + static INLINE DisplayDriver display_driver(void); + static INLINE Resolution get_resolution(void); + static INLINE ServerType server_type(void); + static INLINE bool get_accepting_new_friends(void); + static INLINE bool get_embedded_mode(void); + + static INLINE void set_sfx(bool); + static INLINE void set_toon_chat_sounds(bool); + static INLINE void set_music(bool); + static INLINE void set_force_sw_midi(bool); + static INLINE void set_custom_mouse_cursor(bool); + static INLINE void set_chat_log(bool); + static INLINE void set_windowed_mode(bool); + static INLINE void set_sfx_volume(float); + static INLINE void set_music_volume(float); + static INLINE void set_display_driver(DisplayDriver); + static INLINE void set_resolution(Resolution); + static void set_resolution_dimensions(unsigned int xsize,unsigned int ysize); + static INLINE void set_server_type(ServerType); + static INLINE void set_accepting_new_friends(bool); + static INLINE void set_embedded_mode(bool); + + static INLINE void set_show_fpsmeter(bool); + static INLINE bool doSavedSettingsExist(void); // does the saved settings file exist? + static INLINE void write_settings(void); + static INLINE void read_settings(void); +public: + static string get_config_path(void); + static void get_resolution_sizes(Resolution r, unsigned int &xsize,unsigned int &ysize); +private: + Settings(void); + + static INLINE Settings* get_ptr(void); + + INLINE bool ns_doSavedSettingsExist(void); + INLINE bool ns_get_sfx(void); + INLINE bool ns_get_toon_chat_sounds(void); + INLINE bool ns_get_music(void); + INLINE bool ns_get_force_sw_midi(void); + INLINE bool ns_get_windowed_mode(void); + INLINE bool ns_get_show_fpsmeter(void); + INLINE bool ns_want_chat_log(void); + INLINE bool ns_want_custom_mouse_cursor(void); + INLINE float ns_get_sfx_volume(void); + INLINE float ns_get_music_volume(void); + INLINE DisplayDriver ns_display_driver(void); + INLINE Resolution ns_get_resolution(void); + INLINE ServerType ns_server_type(void); + INLINE bool ns_get_accepting_new_friends(void); + INLINE bool ns_get_embedded_mode(void); + + INLINE void ns_set_show_fpsmeter(bool); + INLINE void ns_set_sfx(bool); + INLINE void ns_set_toon_chat_sounds(bool); + INLINE void ns_set_music(bool); + INLINE void ns_set_custom_mouse_cursor(bool); + INLINE void ns_set_chat_log(bool); + INLINE void ns_set_force_sw_midi(bool); + INLINE void ns_set_windowed_mode(bool); + INLINE void ns_set_sfx_volume(float); + INLINE void ns_set_music_volume(float); + INLINE void ns_set_display_driver(DisplayDriver); + INLINE void ns_set_resolution(Resolution); + INLINE void ns_set_server_type(ServerType); + INLINE void ns_set_accepting_new_friends(bool); + INLINE void ns_set_embedded_mode(bool); + void ns_write_settings(void); + void ns_read_settings(void); + void read_file(Filename); + + static Settings* _singleton; + + // actual state data + Filename _fname; + bool _sfx; + bool _toon_chat_sounds; + bool _music; + bool _bForceSWMidi; + bool _custom_mousecursor_enabled; + bool _chat; + bool _bReadSavedData; + bool _bUseWindowedMode; + bool _bShowFpsMeter; + bool _accepting_new_friends; + bool _embedded_mode; + float _sfx_vol; + float _music_vol; + DisplayDriver _driver; + Resolution _res; + ServerType _stype; +}; + +#include "settingsFile.I" + +#endif /* __SETTINGSFILE_H__ */ diff --git a/otp/src/configrc/sysinfodefs.h b/otp/src/configrc/sysinfodefs.h new file mode 100644 index 0000000..e24733b --- /dev/null +++ b/otp/src/configrc/sysinfodefs.h @@ -0,0 +1,75 @@ +#ifndef SYSINFODEFS_H +#define SYSINFODEFS_H + +typedef enum { + CPU_Intel, + CPU_MIPS, + CPU_Alpha, + CPU_PPC, + CPU_unknown + } CPUType; + + typedef enum { + OS_unknown, + OS_Win95, + OS_Win98, + OS_WinMe, + OS_WinNT, // NT (must come after win9x stuff, order important) + OS_Win2000, // Win2000 + OS_WinXP, // WinXP + OS_WinServer2003, // WinXP Server, essentially + OS_WinPostXP, // newer than WinXP + } OSType; + + typedef enum { + GAPI_Unknown = 0, + GAPI_OpenGL , + GAPI_DirectX_3=3, + GAPI_DirectX_5=5, + GAPI_DirectX_6, + GAPI_DirectX_7, + GAPI_DirectX_8_0, + GAPI_DirectX_8_1, + GAPI_DirectX_9_0, + } GAPIType; + + typedef enum { + C_modem, + C_network_bridge, + C_TCPIP_telnet, + C_RS232, + C_unspecified, + C_unknown + } CommType; + + typedef enum { + Status_Unknown, + Status_Unsupported, + Status_Supported, + } GfxCheckStatus; + + typedef enum { + UnknownVendor=0, + _3DFX=0x121A, + _3DLabs=0x3D3D, + ATI=0x1002, + Intel=0x8086, + Matrox=0x102B, + Nvidia=0x10DE, + Nvidia_STB=0x12D2, + PowerVR=0x1023, + S3=0x5333, + SiS=0x1039, + Trident=0x1023, + } GfxVendorIDs; + +#if 0 + // used to index into array of string messages used to communicate with missedReqmts.php + typedef enum { + Unknown, + Intel_i810, + NumCardTypes, + } GfxCardType; +#endif + +#endif diff --git a/otp/src/distributed/.cvsignore b/otp/src/distributed/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/distributed/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/distributed/Account.py b/otp/src/distributed/Account.py new file mode 100644 index 0000000..339fc30 --- /dev/null +++ b/otp/src/distributed/Account.py @@ -0,0 +1,12 @@ +""" +Account module: stub to fulfill the Account toon.dc Distributed Class +This is a class Roger needs for the server to be able to display these values +appropriately in the db web interface. +""" + +from direct.distributed import DistributedObject + +class Account(DistributedObject.DistributedObject): + def __init__(self, cr): + pass + diff --git a/otp/src/distributed/AccountAI.py b/otp/src/distributed/AccountAI.py new file mode 100644 index 0000000..2e56f07 --- /dev/null +++ b/otp/src/distributed/AccountAI.py @@ -0,0 +1,25 @@ +""" +Account module: stub to fulfill the Account toon.dc Distributed Class +This is a class Roger needs for the server to be able to display these values +appropriately in the db web interface. +""" + +from direct.distributed import DistributedObjectAI + +class AccountAI(DistributedObjectAI.DistributedObjectAI): + pirateAvatars = [0,0,0,0,0,0] + + def setPirate(self, slot, avatarId): + assert 0 # Ask AccountUD to setPirate + + def getPirate(self, slot): + return self.pirateAvatars[slot] + + def getSlotLimit(self): + return 6 + + def may(self, perm): + """ + Ask whether the account has permission to . + """ + return 1 diff --git a/otp/src/distributed/AccountUD.py b/otp/src/distributed/AccountUD.py new file mode 100644 index 0000000..d0689a6 --- /dev/null +++ b/otp/src/distributed/AccountUD.py @@ -0,0 +1,36 @@ +""" +Account module: stub to fulfill the Account toon.dc Distributed Class +This is a class Roger needs for the server to be able to display these values +appropriately in the db web interface. +""" + +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedObjectUD + +class AccountUD(DistributedObjectUD.DistributedObjectUD): + notify = DirectNotifyGlobal.directNotify.newCategory('AccountUD') + + def __init__(self, air): + assert air + DistributedObjectUD.DistributedObjectUD.__init__(self, air) + + def setPirate(self, slot, avatarId): + assert self.notify.debugCall() + self.pirateAvatars[slot] = avatarId + assert self.air + self.sendUpdate('pirateAvatars', self.pirateAvatars) + + def getPirate(self, slot): + assert self.notify.debugCall() + return self.pirateAvatars[slot] + + def getSlotLimit(self): + assert self.notify.debugCall() + return 6 + + def may(self, perm): + """ + Ask whether the account has permission to . + """ + assert self.notify.debugCall() + return 1 diff --git a/otp/src/distributed/CentralLogger.py b/otp/src/distributed/CentralLogger.py new file mode 100644 index 0000000..54ed5b8 --- /dev/null +++ b/otp/src/distributed/CentralLogger.py @@ -0,0 +1,38 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal + +REPORT_PLAYER = "REPORT_PLAYER" + +# Report categories +# Moderation +ReportFoulLanguage = "MODERATION_FOUL_LANGUAGE" +ReportPersonalInfo = "MODERATION_PERSONAL_INFO" +ReportRudeBehavior = "MODERATION_RUDE_BEHAVIOR" +ReportBadName = "MODERATION_BAD_NAME" + +class CentralLogger(DistributedObjectGlobal): + + # Keep track of all the players reported. + # We only allow 1 report per target per session. + PlayersReportedThisSession = {} + + def hasReportedPlayer(self, targetDISLId, targetAvId): + # Has this playerId, avatarId already been reported this session? + return self.PlayersReportedThisSession.has_key((targetDISLId, targetAvId)) + + def reportPlayer(self, category, targetDISLId, targetAvId, description = "None"): + # You can only report another player once per session. + if self.hasReportedPlayer(targetDISLId, targetAvId): + # Already reported, dont resend the report. + return False + + # Remember that we reported this user already. + self.PlayersReportedThisSession[(targetDISLId, targetAvId)] = 1 + # Send the update. This shows up in the server event log. + self.sendUpdate("sendMessage", [category, REPORT_PLAYER, targetDISLId, targetAvId]) + return True + + def writeClientEvent(self, eventString): + # This one does not relate to moderation, but to client side + # reporting in general. The idea is to use the existing infrastructure + # to get message to the event log. + self.sendUpdate("sendMessage", ['ClientEvent', eventString, 0, 0]) diff --git a/otp/src/distributed/CentralLoggerAI.py b/otp/src/distributed/CentralLoggerAI.py new file mode 100644 index 0000000..99c3767 --- /dev/null +++ b/otp/src/distributed/CentralLoggerAI.py @@ -0,0 +1,4 @@ +from direct.distributed.DistributedObjectGlobalAI import DistributedObjectGlobalAI + +class CentralLoggerAI(DistributedObjectGlobalAI): + pass diff --git a/otp/src/distributed/CentralLoggerUD.py b/otp/src/distributed/CentralLoggerUD.py new file mode 100644 index 0000000..4696dfb --- /dev/null +++ b/otp/src/distributed/CentralLoggerUD.py @@ -0,0 +1,4 @@ +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD + +class CentralLoggerUD(DistributedObjectGlobalUD): + pass diff --git a/otp/src/distributed/DistributedDirectory.py b/otp/src/distributed/DistributedDirectory.py new file mode 100644 index 0000000..e18cead --- /dev/null +++ b/otp/src/distributed/DistributedDirectory.py @@ -0,0 +1,10 @@ + +from direct.distributed.DistributedObject import DistributedObject + +class DistributedDirectory(DistributedObject): + """ + This object has no attributes and none of them get created, but it + is still needed. It is used as a parent for the individual games. + The dc system uses the parenting rules as if this object existed. + """ + pass diff --git a/otp/src/distributed/DistributedDirectoryAI.py b/otp/src/distributed/DistributedDirectoryAI.py new file mode 100644 index 0000000..e0223b1 --- /dev/null +++ b/otp/src/distributed/DistributedDirectoryAI.py @@ -0,0 +1,10 @@ + +from direct.distributed.DistributedObjectAI import DistributedObjectAI + +class DistributedDirectoryAI(DistributedObjectAI): + """ + This object has no attributes and none of them get created, but it + is still needed. It is used as a parent for the individual games. + The dc system uses the parenting rules as if this object existed. + """ + pass diff --git a/otp/src/distributed/DistributedDistrict.py b/otp/src/distributed/DistributedDistrict.py new file mode 100644 index 0000000..172f27b --- /dev/null +++ b/otp/src/distributed/DistributedDistrict.py @@ -0,0 +1,38 @@ + +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.distributed.DistributedObject import DistributedObject + +class DistributedDistrict(DistributedObject): + """ + See Also: "otp/src/distributed/DistributedDistrictAI.py" + """ + notify = directNotify.newCategory("DistributedDistrict") + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.__init__(self, cr) + self.name = "NotGiven" + self.available = 0 + self.avatarCount = 0 + self.newAvatarCount = 0 + + def announceGenerate(self): + DistributedObject.announceGenerate(self) + self.cr.activeDistrictMap[self.doId] = self + messenger.send('shardInfoUpdated') + + def delete(self): + if base.cr.distributedDistrict is self: + base.cr.distributedDistrict = None + if self.cr.activeDistrictMap.has_key(self.doId): + del self.cr.activeDistrictMap[self.doId] + DistributedObject.delete(self) + messenger.send('shardInfoUpdated') + + def setAvailable(self, available): + self.available = available + messenger.send('shardInfoUpdated') + + def setName(self, name): + self.name=name + messenger.send('shardInfoUpdated') diff --git a/otp/src/distributed/DistributedDistrictAI.py b/otp/src/distributed/DistributedDistrictAI.py new file mode 100644 index 0000000..4f02211 --- /dev/null +++ b/otp/src/distributed/DistributedDistrictAI.py @@ -0,0 +1,49 @@ + + +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.directnotify.DirectNotifyGlobal import directNotify + +from direct.task import Task + +class DistributedDistrictAI(DistributedObjectAI): + notify = directNotify.newCategory("DistributedDistrictAI") + + def __init__(self, air, name="untitled"): + DistributedObjectAI.__init__(self, air) + self.air = air + self.name=name + self.available = 0 + + def delete(self): + self.ignoreAll() + self.b_setAvailable(0) + DistributedObjectAI.delete(self) + + def getAvailable(self): + return self.available + + def getName(self): + return self.name + + # available value + def setAvailable(self, available): + self.available=available + + def d_setAvailable(self, available): + self.sendUpdate("setAvailable", [available]) + + def b_setAvailable(self, available): + self.setAvailable(available) + self.d_setAvailable(available) + + # set name + def setName(self, name): + self.name=name + + def d_setName(self, name): + self.sendUpdate("setName", [name]) + + def b_setName(self, name): + self.setName(name) + self.d_setName(name) + diff --git a/otp/src/distributed/DistributedDistrictUD.py b/otp/src/distributed/DistributedDistrictUD.py new file mode 100644 index 0000000..e540a99 --- /dev/null +++ b/otp/src/distributed/DistributedDistrictUD.py @@ -0,0 +1,8 @@ + + +class DistributedDistrictUD: + def __init__(self, air, name="untitled"): + assert 0, "Hey, This assert is here to let you know that a DistributedDistrict should not be on a UD server." + # There is probably a bug somewhere if you are getting a district + # on an uberDog server, districts are for AI district servers and + # clients. diff --git a/otp/src/distributed/DistributedInterestOpener.py b/otp/src/distributed/DistributedInterestOpener.py new file mode 100644 index 0000000..dbe3b60 --- /dev/null +++ b/otp/src/distributed/DistributedInterestOpener.py @@ -0,0 +1,50 @@ +from direct.distributed.DistributedObject import DistributedObject + +class DistributedInterestOpener(DistributedObject): + """ + Delays opening of an interest set underneath this object until a + specific set of doIds is present on the client. + See also DistributedInterestOpenerAI.py + """ + def __init__(self, cr): + DistributedObject.__init__(self, cr) + + def generate(self): + DistributedObject.generate(self) + self.childInterest = None + print 'DistributedInterestOpener.generate' + + def disable(self): + self._removeInterest() + del self.childInterest + DistributedObject.disable(self) + + def setChildZones(self, childZones): + self.childZones = childZones + + def setRequiredDoIds(self, requiredDoIds): + self.requiredDoIds = requiredDoIds + print 'DistributedInterestOpener.setRequiredDoIds' + if self.childInterest is None: + self.getObject(self.requiredDoIds, self._openInterest) + else: + # we've already got an interest open; change it + self.getObject(self.requiredDoIds, self._alterInterest) + + def _openInterest(self): + print 'DistributedInterestOpener._openInterest: %s' % self.getDoId() + self.childInterest = self.cr.addInterest( + self.getDoId(), self.childZones, + self.uniqueName('interestOpener')) + + def _alterInterest(self): + print 'DistributedInterestOpener._alterInterest' + self.cr.alterInterest(self.childInterest, self.getDoId(), + self.childZones, + self.uniqueName('interestOpenerAlter')) + + def _removeInterest(self): + print 'DistributedInterestOpener._removeInterest' + if self.childInterest is not None: + self.getRepository().removeInterest(self.childInterest) + self.childInterest = None diff --git a/otp/src/distributed/DistributedInterestOpenerAI.py b/otp/src/distributed/DistributedInterestOpenerAI.py new file mode 100644 index 0000000..03bdc2f --- /dev/null +++ b/otp/src/distributed/DistributedInterestOpenerAI.py @@ -0,0 +1,33 @@ +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.showbase.PythonUtil import makeTuple + +class DistributedInterestOpenerAI(DistributedObjectAI): + """ + Use this class to cause the client to delay opening of an interest + set (underneath this object) until a specific set of doIds is present + on the client. + """ + def __init__(self, air, requiredDoIds, zones=None): + DistributedObjectAI.__init__(self, air) + if zones is None: + zones = (2,) + else: + zones = makeTuple(zones) + self.zones = zones + self.requiredDoIds = requiredDoIds + + def announceGenerate(self): + DistributedObjectAI.announceGenerate(self) + print 'DistributedInterestOpenerAI.announceGenerate: %s' % self.doId + + def setRequiredDoIds(self, requiredDoIds): + # call this to change the list of required doIds + if requiredDoIds != self.requiredDoIds: + self.requiredDoIds = requiredDoIds + self.sendUpdate('setRequiredDoIds', [self.requiredDoIds]) + + def getChildZones(self): + return self.zones + + def getRequiredDoIds(self): + return self.requiredDoIds diff --git a/otp/src/distributed/DistributedTestObject.py b/otp/src/distributed/DistributedTestObject.py new file mode 100644 index 0000000..ad855f8 --- /dev/null +++ b/otp/src/distributed/DistributedTestObject.py @@ -0,0 +1,28 @@ +from direct.distributed import DistributedObject + +class DistributedTestObject(DistributedObject.DistributedObject): + def setRequiredField(self, r): + self.requiredField = r + def setB(self, B): + self.B = B + def setBA(self, BA): + self.BA = BA + def setBO(self, BO): + self.BO = BO + def setBR(self, BR): + self.BR = BR + def setBRA(self, BRA): + self.BRA = BRA + def setBRO(self, BRO): + self.BRO = BRO + def setBROA(self, BROA): + self.BROA = BROA + + def gotNonReqThatWasntSet(self): + for field in ('B', + 'BA','BO','BR', + 'BRA','BRO', + 'BROA',): + if hasattr(self, field): + return True + return False diff --git a/otp/src/distributed/DistributedTestObjectAI.py b/otp/src/distributed/DistributedTestObjectAI.py new file mode 100644 index 0000000..96cda63 --- /dev/null +++ b/otp/src/distributed/DistributedTestObjectAI.py @@ -0,0 +1,5 @@ +from direct.distributed import DistributedObjectAI + +class DistributedTestObjectAI(DistributedObjectAI.DistributedObjectAI): + def getRequiredField(self): + return 88 diff --git a/otp/src/distributed/GameServerTestSuite.py b/otp/src/distributed/GameServerTestSuite.py new file mode 100644 index 0000000..024324a --- /dev/null +++ b/otp/src/distributed/GameServerTestSuite.py @@ -0,0 +1,117 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.showbase import DirectObject, TaskThreaded + +class GameServerTestSuite(DirectObject.DirectObject, TaskThreaded.TaskThreaded): + # Suite of client-side game server tests. + # Fire-and-forget, create one and get rid of the reference; if there's a problem + # it will assert sometime later. + notify = directNotify.newCategory("GarbageReport") + + def __init__(self, cr): + self.cr = cr + TaskThreaded.TaskThreaded.__init__(self, self.__class__.__name__) + + class TimeoutTest(DirectObject.DirectObject): + Timeout = 10 + def _getTaskName(self, name): + return '%s-timeout-%s' % (self.__class__.__name__, name) + def startTimeout(self, name): + self.stopTimeout(name) + _taskName = self._getTaskName(name) + taskMgr.doMethodLater(self.Timeout, Functor(self._timeout, _taskName), _taskName) + def stopTimeout(self, name): + _taskName = self._getTaskName(name) + taskMgr.remove(_taskName) + def _timeout(self, taskName, task=None): + self.parent.notify.warning('TEST TIMED OUT: %s' % taskName) + import pdb;pdb.set_trace() + + class MsgHandlerTest: + # shims in a custom message handler that gets first crack at incoming messages + # override self.handleMsg and call down if it's not the message you're waiting for + def installMsgHandler(self): + self.oldHandler = self.parent.handler + self.parent.handler = self.handleMsg + def removeMsgHandler(self): + self.parent.handler = self.oldHandler + del self.oldHandler + def handleMsg(self, msgType, di): + self.parent.cr.handler(msgType, di) + + class TestGetAvatars(TaskThreaded.TaskThread, TimeoutTest, MsgHandlerTest): + def setUp(self): + self.state = 'request' + self.installMsgHandler() + def handleMsg(self, msgType, di): + if msgType == CLIENT_GET_AVATARS_RESP: + self.finished() + else: + MsgHandlerTest.handleMsg(self, msgType, di) + def run(self): + if self.state == 'request': + self.parent.cr.sendGetAvatarsMsg() + self.startTimeout('getAvatarList') + self.state = 'waitForList' + def tearDown(self): + self.stopTimeout('getAvatarList') + self.removeMsgHandler() + + class TestInterestOpenAndClose(TaskThreaded.TaskThread, TimeoutTest): + def setUp(self): + self.state = 'open' + def run(self): + if self.state == 'open': + def openInterestDone(): + self.stopTimeout(self.timeoutName) + self.state = 'modify' + doneEvent = uniqueName('openInterest') + self.acceptOnce(doneEvent, openInterestDone) + openInterestDone = None + self.timeoutName = 'openInterest' + self.startTimeout(self.timeoutName) + self.handle = self.parent.cr.addInterest(self.parent.cr.GameGlobalsId, 91504, 'testInterest', doneEvent) + self.state = 'waitOpenComplete' + elif self.state == 'modify': + def modifyInterestDone(): + self.stopTimeout(self.timeoutName) + self.state = 'close' + doneEvent = uniqueName('openInterest') + self.acceptOnce(doneEvent, modifyInterestDone) + modifyInterestDone = None + self.timeoutName = 'modifyInterest' + self.startTimeout(self.timeoutName) + self.parent.cr.alterInterest(self.handle, self.parent.cr.GameGlobalsId, 91506, 'testInterest', doneEvent) + self.state = 'waitModifyComplete' + elif self.state == 'close': + def closeInterestDone(): + self.stopTimeout(self.timeoutName) + self.state = 'done' + doneEvent = uniqueName('closeInterest') + self.acceptOnce(doneEvent, closeInterestDone) + closeInterestDone = None + self.timeoutName = 'closeInterest' + self.startTimeout(self.timeoutName) + self.handle = self.parent.cr.removeInterest(self.handle, doneEvent) + self.state = 'waitCloseComplete' + elif self.state == 'done': + self.finished() + + class TestNonRequiredNonSetFields(TaskThreaded.TaskThread, TimeoutTest): + # if we start AI and client at the same time it can take a while + # for the object to show up + Timeout = 60 + def setUp(self): + # the test object should be created by the AI on startup + self.timeoutName = 'lookForObj' + self.startTimeout(self.timeoutName) + def run(self): + testObj = self.parent.cr.doFind('DistributedTestObject') + if testObj is not None: + assert not testObj.gotNonReqThatWasntSet() + self.finished() + def tearDown(self): + self.stopTimeout(self.timeoutName) + del self.timeoutName + + self.scheduleThread(TestInterestOpenAndClose()) + self.scheduleThread(TestNonRequiredNonSetFields()) diff --git a/otp/src/distributed/OTPClientRepository.py b/otp/src/distributed/OTPClientRepository.py new file mode 100644 index 0000000..e795bba --- /dev/null +++ b/otp/src/distributed/OTPClientRepository.py @@ -0,0 +1,3309 @@ +import sys +import time +import string +import types +import random +import gc +import os + + +from pandac.PandaModules import * +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from otp.distributed.OtpDoGlobals import * + +from direct.interval.IntervalGlobal import ivalMgr +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.distributed.ClientRepositoryBase import ClientRepositoryBase +from direct.fsm.ClassicFSM import ClassicFSM +from direct.fsm.State import State +from direct.task import Task +from direct.distributed import DistributedSmoothNode +from direct.showbase import PythonUtil, GarbageReport, BulletinBoardWatcher +from direct.showbase.ContainerLeakDetector import ContainerLeakDetector +from direct.showbase import MessengerLeakDetector +from direct.showbase.GarbageReportScheduler import GarbageReportScheduler +from direct.showbase import LeakDetectors +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator + +from otp.avatar import Avatar +from otp.avatar.DistributedPlayer import DistributedPlayer +from otp.login import TTAccount +from otp.login import LoginTTSpecificDevAccount +from otp.login import AccountServerConstants +from otp.login.CreateAccountScreen import CreateAccountScreen +from otp.login import LoginScreen +from otp.otpgui import OTPDialog +from otp.avatar import DistributedAvatar +from otp.otpbase import OTPLocalizer +from otp.login import LoginGSAccount +from otp.login import LoginGoAccount +from otp.login.LoginWebPlayTokenAccount import LoginWebPlayTokenAccount +from otp.login.LoginDISLTokenAccount import LoginDISLTokenAccount +from otp.login import LoginTTAccount +from otp.login import HTTPUtil +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLauncherGlobals +from otp.uberdog import OtpAvatarManager +from otp.distributed import OtpDoGlobals +from otp.ai.GarbageLeakServerEventAggregator import GarbageLeakServerEventAggregator + +from PotentialAvatar import PotentialAvatar + +class OTPClientRepository(ClientRepositoryBase): + # Create a notify category + notify = directNotify.newCategory("OTPClientRepository") + + # The most avatars one account can have + avatarLimit = 6 + + WishNameResult = Enum([ + 'Failure', 'PendingApproval', 'Approved', 'Rejected', + ]) + + def __init__(self, serverVersion, launcher = None, playGame = None): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Ancestor init + ClientRepositoryBase.__init__(self) + + # A derived class should reassign this member to change the + # response behavior appropriately. + self.handler = None + + self.launcher = launcher + # Give base a handle to the launcher + base.launcher = launcher + + #self.launcher.setDisconnectDetailsNormal() + + self.__currentAvId = 0 + + # Specifies which product this is. + # Valid values are currently: + # DisneyOnline-US (US - English) + # DisneyOnline-UK (UK - English) + # DisneyOnline-AP (AP - English) + # Terra-DMC (Spain - Castillian: DMC) + # ES (Spain - Castillian: open internet) + # JP (Japan - Japanese: open internet) + # DE (Germany - German) + # BR (Brazil - Portuguese) + # FR (France - French) + self.productName = config.GetString('product-name', 'DisneyOnline-US') + + # Derived classes should fill this in with the Python class of the + # particular avatar type to create. This is temporary code for + # gateway stuff for now. + self.createAvatarClass = None + + # This will be loaded on demand when it is needed. We can't + # load it up front because it may not have been downloaded + # yet. + self.systemMessageSfx = None + + # If US version, check the registry to get UK/AP + reg_deployment = "" + if (self.productName == 'DisneyOnline-US'): + if self.launcher: + if self.launcher.isDummy(): + reg_deployment = self.launcher.getDeployment() + else: + reg_deployment = self.launcher.getRegistry('DEPLOYMENT') + if reg_deployment != 'UK' and reg_deployment != 'AP': + # try environment variable for VISTA launcher for UK + reg_deployment = self.launcher.getRegistry('GAME_DEPLOYMENT') + self.notify.info ('reg_deployment=%s' % reg_deployment) + + if (reg_deployment == 'UK'): + self.productName = 'DisneyOnline-UK' + elif (reg_deployment == 'AP'): + self.productName = 'DisneyOnline-AP' + + # Get the "blue" token, if there is one, from the launcher. + # This is used for people who are logging in via the Disney + # Magic Connection login system, which happens outside of the + # game. + self.blue = None + if self.launcher: + self.blue = self.launcher.getBlue() + # Also check the Config file for testing. + fakeBlue = config.GetString('fake-blue', '') + if fakeBlue: + self.blue = fakeBlue + + # Get the "playToken", if there is one, from the launcher. + # This is used for people who are logging in via the + # web page, which happens outside of the game. + self.playToken = None + if self.launcher: + self.playToken = self.launcher.getPlayToken() + # Also check the Config file for testing. + fakePlayToken = config.GetString('fake-playtoken', '') + if fakePlayToken: + self.playToken = fakePlayToken + + # Get the "DISL Token", if there is one, from the launcher. + # This is used for people who are logging in via the + # DISL account system, which happens outside of the game. + self.DISLToken = None + if self.launcher: + self.DISLToken = self.launcher.getDISLToken() + # Also check the Config file for testing. + fakeDISLToken = config.GetString('fake-DISLToken', '') + fakeDISLPlayerName = config.GetString('fake-DISL-PlayerName','') + if fakeDISLToken: + self.DISLToken = fakeDISLToken + elif fakeDISLPlayerName: + defaultId = 42 + defaultNumAvatars = 4 + defaultNumAvatarSlots = 4 + defaultNumConcur = 1 + subCount = config.GetInt('fake-DISL-NumSubscriptions', 1) + playerAccountId = config.GetInt('fake-DISL-PlayerAccountId',defaultId) + self.DISLToken = ("ACCOUNT_NAME=%s" % fakeDISLPlayerName + + "&ACCOUNT_NUMBER=%s" % playerAccountId + + "&ACCOUNT_NAME_APPROVAL=%s" % config.GetString('fake-DISL-PlayerNameApproved','YES') + + "&SWID=%s" % config.GetString('fake-DISL-SWID','{1763AC36-D73F-41C2-A54A-B579E58B69C8}') + + "&FAMILY_NUMBER=%s" % config.GetString('fake-DISL-FamilyAccountId','-1') + + "&familyAdmin=%s" % config.GetString('fake-DISL-FamilyAdmin','1') + + "&PIRATES_ACCESS=%s" % config.GetString('fake-DISL-PiratesAccess','FULL') + + "&PIRATES_MAX_NUM_AVATARS=%s" % config.GetInt('fake-DISL-MaxAvatars',defaultNumAvatars) + + "&PIRATES_NUM_AVATAR_SLOTS=%s" % config.GetInt('fake-DISL-MaxAvatarSlots',defaultNumAvatarSlots) + + "&expires=%s" % config.GetString('fake-DISL-expire','1577898000') + + "&OPEN_CHAT_ENABLED=%s" % config.GetString('fake-DISL-OpenChatEnabled','YES') + + "&CREATE_FRIENDS_WITH_CHAT=%s" % config.GetString('fake-DISL-CreateFriendsWithChat','YES') + + "&CHAT_CODE_CREATION_RULE=%s" % config.GetString('fake-DISL-ChatCodeCreation','YES') + + "&FAMILY_MEMBERS=%s" % config.GetString('fake-DISL-FamilyMembers') + + "&PIRATES_SUB_COUNT=%s" % subCount) + for i in range(subCount): + self.DISLToken += ("&PIRATES_SUB_%s_ACCESS=%s" % (i, config.GetString('fake-DISL-Sub-%s-Access'%i, 'FULL')) + + "&PIRATES_SUB_%s_ACTIVE=%s" % (i, config.GetString('fake-DISL-Sub-%s-Active'%i, 'YES')) + + "&PIRATES_SUB_%s_ID=%s" % (i, config.GetInt('fake-DISL-Sub-%s-Id'%i, playerAccountId) + config.GetInt('fake-DISL-Sub-Id-Offset', 0)) + + "&PIRATES_SUB_%s_LEVEL=%s" % (i, config.GetInt('fake-DISL-Sub-%s-Level'%i, 3)) + + "&PIRATES_SUB_%s_NAME=%s" % (i, config.GetString('fake-DISL-Sub-%s-Name'%i, fakeDISLPlayerName)) + + "&PIRATES_SUB_%s_NUM_AVATARS=%s" % (i, config.GetInt('fake-DISL-Sub-%s-NumAvatars'%i, defaultNumAvatars)) + + "&PIRATES_SUB_%s_NUM_CONCUR=%s" % (i, config.GetInt('fake-DISL-Sub-%s-NumConcur'%i, defaultNumConcur)) + + "&PIRATES_SUB_%s_OWNERID=%s" % (i, config.GetInt('fake-DISL-Sub-%s-OwnerId'%i, playerAccountId)) + + "&PIRATES_SUB_%s_FOUNDER=%s" % (i, config.GetString('fake-DISL-Sub-%s-Founder'%i, 'YES')) + ) + self.DISLToken += ("&WL_CHAT_ENABLED=%s" % config.GetString('fake-DISL-WLChatEnabled','YES') + + "&valid=true") + print self.DISLToken + + # Find out what kind of login we are supposed to used and let + # us know if it's not found: + self.requiredLogin=config.GetString("required-login", "auto") + if (self.requiredLogin=="auto"): + # Guess which login to used. + self.notify.info("required-login auto.") + elif (self.requiredLogin=="green"): + self.notify.error("The green code is out of date") + elif (self.requiredLogin=="blue"): + if (not self.blue): + self.notify.error("The tcr does not have the required blue login") + elif (self.requiredLogin=="playToken"): + if (not self.playToken): + self.notify.error("The tcr does not have the required playToken login") + elif (self.requiredLogin=="DISLToken"): + if (not self.DISLToken): + self.notify.error("The tcr does not have the required DISL token login") + elif (self.requiredLogin=="gameServer"): + self.notify.info("Using game server name/password.") + self.DISLToken = None + else: + self.notify.error("The required-login was not recognized.") + + self.computeValidateDownload() + + # Has the user provided a password to enable magic words? + self.wantMagicWords = base.config.GetString('want-magic-words', '') + + # Get the HTTPClient from the Launcher, or make up a new client. + # DummyLauncher does not have an HTTPClient + if self.launcher and hasattr(self.launcher, 'http'): + self.http = self.launcher.http + else: + self.http = HTTPClient() + + # This method is also deliberately misnamed. + self.allocateDcFile() + + self.accountOldAuth = config.GetBool('account-old-auth', 0) + # allow toontown-account-old-auth + self.accountOldAuth = config.GetBool('%s-account-old-auth' % game.name, + self.accountOldAuth) + self.useNewTTDevLogin = base.config.GetBool('use-tt-specific-dev-login', False) + # create a global login/account server interface + if self.useNewTTDevLogin: + self.loginInterface = LoginTTSpecificDevAccount.LoginTTSpecificDevAccount(self) + self.notify.info("loginInterface: LoginTTSpecificDevAccount") + elif self.accountOldAuth: + self.loginInterface = LoginGSAccount.LoginGSAccount(self) + self.notify.info("loginInterface: LoginGSAccount") + elif self.blue: + self.loginInterface = LoginGoAccount.LoginGoAccount(self) + self.notify.info("loginInterface: LoginGoAccount") + elif self.playToken: + self.loginInterface = LoginWebPlayTokenAccount(self) + self.notify.info("loginInterface: LoginWebPlayTokenAccount") + elif self.DISLToken: + self.loginInterface = LoginDISLTokenAccount(self) + self.notify.info("loginInterface: LoginDISLTokenAccount") + else: + self.loginInterface = LoginTTAccount.LoginTTAccount(self) + self.notify.info("loginInterface: LoginTTAccount") + + # This value comes in from the server + self.secretChatAllowed = base.config.GetBool("allow-secret-chat", 0) + self.openChatAllowed = base.config.GetBool("allow-open-chat", 0) + + # This value comes in from the webAcctParams + self.secretChatNeedsParentPassword = ( + base.config.GetBool("secret-chat-needs-parent-password", 0) + or (self.launcher and self.launcher.getNeedPwForSecretKey()) + ) + + self.parentPasswordSet = ( + base.config.GetBool("parent-password-set", 0) + or (self.launcher and self.launcher.getParentPasswordSet())) + + # Is there a signature identifying the particular xrc file in + # use? + if __debug__: + # In the dev environment, the default value comes from the + # username. + default = 'dev-%s' % (os.getenv("USER")) + self.userSignature = base.config.GetString('signature', default); + + else: + # In the publish environment, the default value is "none". + self.userSignature = base.config.GetString('signature', 'none'); + + # Free time is initially unexpired. The Login??Account object + # will fill this in properly at log in. + self.freeTimeExpiresAt = -1 + self.__isPaid=0 + + # We also have a "period timer". This tracks the number of + # seconds of gameplay the player has accumulated so far this + # month (some players have a limited quota of game time per + # month). This is unrelated to the free time counter, above. + self.periodTimerExpired = 0 + self.periodTimerStarted = None + self.periodTimerSecondsRemaining = None + + # register render and hidden for distributed reparents + self.parentMgr.registerParent(OTPGlobals.SPRender, base.render) + # we really don't want to use hidden anymore, just remove from the + # scene graph entirely by reparenting to an empty nodepath + self.parentMgr.registerParent(OTPGlobals.SPHidden, NodePath()) + + self.timeManager = None + + if config.GetBool('detect-leaks', 0) or config.GetBool('client-detect-leaks', 0): + self.startLeakDetector() + + if config.GetBool('detect-messenger-leaks', 0) or config.GetBool('ai-detect-messenger-leaks', 0): + self.messengerLeakDetector = MessengerLeakDetector.MessengerLeakDetector( + 'client messenger leak detector') + if config.GetBool('leak-messages', 0): + MessengerLeakDetector._leakMessengerObject() + + if config.GetBool('run-garbage-reports', 0) or config.GetBool('client-run-garbage-reports', 0): + noneValue = -1. + reportWait = config.GetFloat('garbage-report-wait', noneValue) + reportWaitScale = config.GetFloat('garbage-report-wait-scale', noneValue) + if reportWait == noneValue: + reportWait = 60. * 2. + if reportWaitScale == noneValue: + reportWaitScale = None + self.garbageReportScheduler = GarbageReportScheduler(waitBetween=reportWait, + waitScale=reportWaitScale) + + self._proactiveLeakChecks = (config.GetBool('proactive-leak-checks', 1) and + config.GetBool('client-proactive-leak-checks', 1)) + self._crashOnProactiveLeakDetect = config.GetBool('crash-on-proactive-leak-detect', 1) + + self.activeDistrictMap = {} + + self.serverVersion = serverVersion + + self.waitingForDatabase = None + + # Set up the login state machine. This handles all the state + # prior to the creation of localToon. + self.loginFSM = ClassicFSM('loginFSM', [ + State('loginOff', + self.enterLoginOff, self.exitLoginOff, + ['connect']), + State('connect', + self.enterConnect, self.exitConnect, + ['login', 'failedToConnect', 'failedToGetServerConstants']), + State('login', + self.enterLogin, self.exitLogin, + ['noConnection', + 'waitForGameList', + 'createAccount', + 'reject', + 'failedToConnect', + 'shutdown', + ]), + State('createAccount', + self.enterCreateAccount, self.exitCreateAccount, + ['noConnection', + 'waitForGameList', + 'login', + 'reject', + 'failedToConnect', + 'shutdown', + ]), + State('failedToConnect', + self.enterFailedToConnect, self.exitFailedToConnect, + ['connect', + 'shutdown', + ]), + State('failedToGetServerConstants', + self.enterFailedToGetServerConstants, self.exitFailedToGetServerConstants, + ['connect', + 'shutdown', + 'noConnection', + ]), + State('shutdown', + self.enterShutdown, self.exitShutdown, + ['loginOff', + ]), + State('waitForGameList', + self.enterWaitForGameList, self.exitWaitForGameList, + ['noConnection', + 'waitForShardList', + 'missingGameRootObject', + ]), + State('missingGameRootObject', + self.enterMissingGameRootObject, self.exitMissingGameRootObject, + ['waitForGameList', + 'shutdown', + ]), + State('waitForShardList', + self.enterWaitForShardList, self.exitWaitForShardList, + ['noConnection', + 'waitForAvatarList', + 'noShards', + ]), + State('noShards', + self.enterNoShards, self.exitNoShards, + ['noConnection', + 'noShardsWait', + 'shutdown', + ]), + State('noShardsWait', + self.enterNoShardsWait, self.exitNoShardsWait, + ['noConnection', + 'waitForShardList', + 'shutdown', + ]), + State('reject', + self.enterReject, self.exitReject, + []), + State('noConnection', + self.enterNoConnection, self.exitNoConnection, + ['login', + 'connect', + 'shutdown', + ]), + State('afkTimeout', + self.enterAfkTimeout, self.exitAfkTimeout, + ['waitForAvatarList', + 'shutdown', + ]), + State('periodTimeout', + self.enterPeriodTimeout, self.exitPeriodTimeout, + ['shutdown', + ]), + State('waitForAvatarList', + self.enterWaitForAvatarList, self.exitWaitForAvatarList, + ['noConnection', + 'chooseAvatar', + 'shutdown', + ]), + State('chooseAvatar', + self.enterChooseAvatar, self.exitChooseAvatar, + ['noConnection', + 'createAvatar', + 'waitForAvatarList', + 'waitForSetAvatarResponse', + 'waitForDeleteAvatarResponse', + 'shutdown', + 'login', + ]), + State('createAvatar', + self.enterCreateAvatar, self.exitCreateAvatar, + ['noConnection', + 'chooseAvatar', + 'waitForSetAvatarResponse', + 'shutdown', + ]), + State('waitForDeleteAvatarResponse', + self.enterWaitForDeleteAvatarResponse, self.exitWaitForDeleteAvatarResponse, + ['noConnection', + 'chooseAvatar', + 'shutdown', + ]), + State('rejectRemoveAvatar', + self.enterRejectRemoveAvatar, self.exitRejectRemoveAvatar, + ['noConnection', + 'chooseAvatar', + 'shutdown', + ]), + State('waitForSetAvatarResponse', + self.enterWaitForSetAvatarResponse, self.exitWaitForSetAvatarResponse, + ['noConnection', + 'playingGame', + 'shutdown', + ]), + State('playingGame', + self.enterPlayingGame, self.exitPlayingGame, + ['noConnection', + 'waitForAvatarList', + 'login', # Shticker book may jump to login + 'shutdown', # The user may alt-f4 + 'afkTimeout', + 'periodTimeout', + 'noShards', + ]), + ], + # initial State + 'loginOff', + # final State + 'loginOff', + ) + + # Set up the game state machine. This handles all the state + # after localToon gets created. + self.gameFSM = ClassicFSM('gameFSM', [ + State('gameOff', + self.enterGameOff, + self.exitGameOff, + ['waitOnEnterResponses']), + State('waitOnEnterResponses', + self.enterWaitOnEnterResponses, + self.exitWaitOnEnterResponses, + ['playGame', 'tutorialQuestion', + 'gameOff']), + State('tutorialQuestion', + self.enterTutorialQuestion, + self.exitTutorialQuestion, + ['playGame', 'gameOff']), + State('playGame', + self.enterPlayGame, + self.exitPlayGame, + ['gameOff', + 'closeShard', + 'switchShards']), + State('switchShards', + self.enterSwitchShards, + self.exitSwitchShards, + ['gameOff', + 'waitOnEnterResponses']), + State('closeShard', + self.enterCloseShard, + self.exitCloseShard, + ['gameOff', + 'waitOnEnterResponses' + ]) + ], + # initial State + 'gameOff', + # final State + 'gameOff', + ) + + # The game fsm should be a child of the "playingGame" + # state of loginFSM. + self.loginFSM.getStateNamed("playingGame").addChild(self.gameFSM) + + # Put the loginFSM into its initial state + self.loginFSM.enterInitialState() + self.loginScreen = None + self.music = None + self.gameDoneEvent = "playGameDone" + self.playGame = playGame(self.gameFSM, self.gameDoneEvent) + self.shardInterestHandle = None + self.uberZoneInterest = None + self.wantSwitchboard = config.GetBool('want-switchboard',0) + self.wantSwitchboardHacks = base.config.GetBool('want-switchboard-hacks',0) + + # Used for moderation of report-a-player feature + self.centralLogger = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, + "CentralLogger") + + def startLeakDetector(self): + if hasattr(self, 'leakDetector'): + return False + firstCheckDelay = config.GetFloat('leak-detector-first-check-delay', 2 * 60.) + self.leakDetector = ContainerLeakDetector( + 'client container leak detector', firstCheckDelay = firstCheckDelay) + self.objectTypesLeakDetector = LeakDetectors.ObjectTypesLeakDetector() + self.garbageLeakDetector = LeakDetectors.GarbageLeakDetector() + self.renderLeakDetector = LeakDetectors.SceneGraphLeakDetector(render) + self.hiddenLeakDetector = LeakDetectors.SceneGraphLeakDetector(hidden) + self.cppMemoryUsageLeakDetector = LeakDetectors.CppMemoryUsage() + self.taskLeakDetector = LeakDetectors.TaskLeakDetector() + self.messageListenerTypesLeakDetector = LeakDetectors.MessageListenerTypesLeakDetector() + # this isn't necessary with the current messenger implementation + #self.messageTypesLeakDetector = LeakDetectors.MessageTypesLeakDetector() + return True + + def getGameDoId(self): + return self.GameGlobalsId + +################################################# +## _ _ ______ _____ __ __ +## | | (_) | ____/ ____| \/ | +## | | ___ __ _ _ _ __ | |__ | (___ | \ / | +## | |/ _ \ / _` | | '_ \| __| \___ \| |\/| | +## | | (_) | (_| | | | | | | ____) | | | | +## |_|\___/ \__, |_|_| |_|_| |_____/|_| |_| +## __/ | +## |___/ +## __ _ _ +## / _| | | (_) +## | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ +## | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| +## | | | |_| | | | | (__| |_| | (_) | | | \__ \ +## |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ +## +################################################# + + ##### LoginFSM: loginOff state ##### + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterLoginOff(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleMessageType + self.shardInterestHandle = None + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitLoginOff(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + + def computeValidateDownload(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Get the string value to pass to the server to validate that + # we have received the expected files from the download + # server. This protects against a hacker spoofing the + # download server; the server won't allow us to connect unless + # we produce the expected string. + + if self.launcher: + # Here the hash comes from the launcher, having been + # generated from the two control files downloaded from the + # download server. + + hash = HashVal() + hash.mergeWith(launcher.launcherFileDbHash) + hash.mergeWith(launcher.serverDbFileHash) + self.validateDownload = hash.asHex() + else: + # Here the hash is parsed out of the download.par file, in + # the dev environment. + + self.validateDownload = '' + basePath = os.path.expandvars('$TOONTOWN') or './toontown' + downloadParFilename = Filename.expandFrom( + basePath+'/src/configfiles/download.par') + if downloadParFilename.exists(): + downloadPar = open(downloadParFilename.toOsSpecific()) + for line in downloadPar.readlines(): + i = string.find(line, 'VALIDATE_DOWNLOAD=') + if i != -1: + self.validateDownload = string.strip(line[i + 18:]) + break + + def getServerVersion(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return self.serverVersion + + ##### LoginFSM: connect state ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterConnect(self, serverList): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # We'll need this later if we need to relogin + self.serverList = serverList + + # Show a "connecting..." box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.connectingBox = dialogClass( + message=OTPLocalizer.CRConnecting) + self.connectingBox.show() + # Redraw the screen so the box will be visible. + self.renderFrame() + + self.handler = self.handleMessageType + + self.connect(self.serverList, + successCallback = self._handleConnected, + failureCallback = self.failedToConnect) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def failedToConnect(self, statusCode, statusString): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.loginFSM.request("failedToConnect", [statusCode, statusString]) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitConnect(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.connectingBox.cleanup() + del self.connectingBox + + def handleSystemMessage(self, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Got a system message from the server. + message = ClientRepositoryBase.handleSystemMessage(self, di) + + whisper = WhisperPopup(message, OTPGlobals.getInterfaceFont(), + WhisperPopup.WTSystem) + whisper.manage(base.marginManager) + + if not self.systemMessageSfx: + # Try to load the sound effect. This might fail if the + # phase 3.5 has not yet been downloaded, but that's + # OK--it's just a sound effect. For longer term goodness, + # we should choose a unique sound effect for system + # messages, and ensure that it is downloaded in phase 3. + + self.systemMessageSfx = base.loadSfx( + "phase_3.5/audio/sfx/GUI_whisper_3.mp3") + + if self.systemMessageSfx: + base.playSfx(self.systemMessageSfx) + + def getConnectedEvent(self): + return 'OTPClientRepository-connected' + + def _handleConnected(self): + self.launcher.setDisconnectDetailsNormal() + messenger.send(self.getConnectedEvent()) + self.gotoFirstScreen() + + def gotoFirstScreen(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # attempt to grab the account server constants + try: + self.accountServerConstants = AccountServerConstants.AccountServerConstants(self) + except TTAccount.TTAccountException, e: + self.notify.debug(str(e)) + self.loginFSM.request('failedToGetServerConstants', [e]) + return + + self.startReaderPollTask() + # Start sending heartbeats + self.startHeartbeat() + + # is this a new installation? + newInstall = launcher.getIsNewInstallation() + newInstall = base.config.GetBool("new-installation", newInstall) + + self.loginFSM.request("login") + + ##### LoginFSM: login ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterLogin(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Disconnect the currently-playing avatar, if there is one. + # We need to do this just in case we came directly here from + # the Shticker book in-game. + self.sendSetAvatarIdMsg(0) + + # Pop up the login screen. + self.loginDoneEvent = "loginDone" + self.loginScreen = LoginScreen.LoginScreen(self, self.loginDoneEvent) + self.accept(self.loginDoneEvent, self.__handleLoginDone) + self.loginScreen.load() + self.loginScreen.enter() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleLoginDone(self, doneStatus): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + mode = doneStatus['mode'] + if (mode == 'success'): + # if they've logged in, this is not a new install + self.setIsNotNewInstallation() + self.loginFSM.request("waitForGameList") + elif (mode == 'getChatPassword'): + self.loginFSM.request("parentPassword") + elif (mode == "freeTimeExpired"): + self.loginFSM.request("freeTimeInform") + elif (mode == "createAccount"): + self.loginFSM.request( + "createAccount", [{'back': 'login', 'backArgs': []}]) + elif (mode == 'reject'): + self.loginFSM.request("reject") + elif (mode == 'quit'): + self.loginFSM.request("shutdown") + elif (mode == 'failure'): + # Looks like failed to connect wants a code and string + # Not sure what to put here + self.loginFSM.request("failedToConnect", [-1, "?"]) + else: + self.notify.error( + "Invalid doneStatus mode from loginScreen: " + str(mode)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitLogin(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Get rid of the login screen, if there is one. + if self.loginScreen: + self.loginScreen.exit() + self.loginScreen.unload() + self.loginScreen = None + self.renderFrame() + self.ignore(self.loginDoneEvent) + del self.loginDoneEvent + self.handler = None + + ##### LoginFSM: createAccount ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterCreateAccount( + self, + createAccountDoneData={ + "back":"login", + "backArgs":[]}): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.createAccountDoneData = createAccountDoneData + self.createAccountDoneEvent = "createAccountDone" + self.createAccountScreen = None + + self.createAccountScreen = CreateAccountScreen( + self, self.createAccountDoneEvent) + self.accept(self.createAccountDoneEvent, + self.__handleCreateAccountDone) + self.createAccountScreen.load() + self.createAccountScreen.enter() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleCreateAccountDone(self, doneStatus): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + mode = doneStatus['mode'] + if (mode == 'success'): + # if they've created an account, this is not a new install + self.setIsNotNewInstallation() + self.loginFSM.request("waitForGameList") + elif (mode == 'reject'): + self.loginFSM.request("reject") + elif (mode == 'cancel'): + self.loginFSM.request(self.createAccountDoneData['back'], + self.createAccountDoneData['backArgs']) + elif (mode == 'failure'): + self.loginFSM.request(self.createAccountDoneData['back'], + self.createAccountDoneData['backArgs']) + elif (mode == 'quit'): + self.loginFSM.request("shutdown") + else: + self.notify.error( + "Invalid doneStatus mode from CreateAccountScreen: " + + str(mode)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitCreateAccount(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Get rid of the createAccount screen, if there is one. + if self.createAccountScreen: + self.createAccountScreen.exit() + self.createAccountScreen.unload() + self.createAccountScreen = None + self.renderFrame() + self.ignore(self.createAccountDoneEvent) + del self.createAccountDoneEvent + self.handler = None + + ##### LoginFSM: failedToConnect ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterFailedToConnect(self, statusCode, statusString): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleMessageType + messenger.send("connectionIssue") + url = self.serverList[0] + self.notify.warning( + "Failed to connect to %s (%s %s). Notifying user." % + (url.cStr(), statusCode, statusString)) + + if statusCode == 1403 or statusCode == 1405 or statusCode == 1400: + message = OTPLocalizer.CRNoConnectProxyNoPort % ( + url.getServer(), url.getPort(), url.getPort()) + style = OTPDialog.CancelOnly + else: + message = OTPLocalizer.CRNoConnectTryAgain % ( + url.getServer(), url.getPort()) + style = OTPDialog.TwoChoice + + # Create a dialog box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.failedToConnectBox = dialogClass( + message = message, + doneEvent = "failedToConnectAck", + text_wordwrap = 18, + style = style) + self.failedToConnectBox.show() + + self.notify.info(message) + + # Hang a hook for hitting OK or Cancel + self.accept("failedToConnectAck", self.__handleFailedToConnectAck) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleFailedToConnectAck(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + doneStatus = self.failedToConnectBox.doneStatus + if doneStatus == "ok": + self.loginFSM.request("connect", [self.serverList]) + messenger.send("connectionRetrying") + elif doneStatus == "cancel": + self.loginFSM.request("shutdown") + else: + self.notify.error("Unrecognized doneStatus: " + str(doneStatus)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitFailedToConnect(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + self.ignore("failedToConnectAck") + self.failedToConnectBox.cleanup() + del self.failedToConnectBox + + ##### LoginFSM: failedToGetServerConstants ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterFailedToGetServerConstants(self, e): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleMessageType + messenger.send("connectionIssue") + url = AccountServerConstants.AccountServerConstants.getServerURL() + + # If we failed because of a connection error, get the status + # code. This may help us report a more useful message to the + # user. + statusCode = 0 + if isinstance(e, HTTPUtil.ConnectionError): + statusCode = e.statusCode + self.notify.warning( + "Got status code %s from connection to %s." % + (statusCode, url.cStr())) + else: + self.notify.warning( + "Didn't get status code from connection to %s." % + (url.cStr())) + + if statusCode == 1403 or statusCode == 1400: + message = OTPLocalizer.CRServerConstantsProxyNoPort % ( + url.cStr(), url.getPort()) + style = OTPDialog.CancelOnly + elif statusCode == 1405: + message = OTPLocalizer.CRServerConstantsProxyNoCONNECT % ( + url.cStr()) + style = OTPDialog.CancelOnly + else: + # Just give a generic message. + message = (OTPLocalizer.CRServerConstantsTryAgain % url.cStr()) + style = OTPDialog.TwoChoice + + # Create a dialog box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.failedToGetConstantsBox = dialogClass( + message = message, + doneEvent = "failedToGetConstantsAck", + text_wordwrap = 18, + style = style) + self.failedToGetConstantsBox.show() + + # Hang a hook for hitting OK or Cancel + self.accept("failedToGetConstantsAck", + self.__handleFailedToGetConstantsAck) + + self.notify.warning( + "Failed to get account server constants. Notifying user.") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleFailedToGetConstantsAck(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + doneStatus = self.failedToGetConstantsBox.doneStatus + if doneStatus == "ok": + self.loginFSM.request("connect", [self.serverList]) + messenger.send("connectionRetrying") + elif doneStatus == "cancel": + self.loginFSM.request("shutdown") + else: + self.notify.error("Unrecognized doneStatus: " + str(doneStatus)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitFailedToGetServerConstants(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + self.ignore("failedToGetConstantsAck") + self.failedToGetConstantsBox.cleanup() + del self.failedToGetConstantsBox + + ##### LoginFSM: shutdown ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterShutdown(self, errorCode = None): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleMessageType + self.sendDisconnect() + self.notify.info("Exiting cleanly") + base.exitShow(errorCode) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitShutdown(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if hasattr(self, 'garbageWatcher'): + self.garbageWatcher.destroy() + del self.garbageWatcher + self.handler = None + + ##### LoginFSM: waitForGameList ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitForGameList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.gameDoDirectory = self.addInterest( + self.GameGlobalsId, OTP_ZONE_ID_MANAGEMENT, + "game directory","GameList_Complete") + self.acceptOnce( + "GameList_Complete", self.waitForGetGameListResponse) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def waitForGetGameListResponse(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if self.isGameListCorrect(): + if base.config.GetBool('game-server-tests', 0): + # kick off a game server test suite + from otp.distributed import GameServerTestSuite + GameServerTestSuite.GameServerTestSuite(self) + self.loginFSM.request("waitForShardList") + else: + self.loginFSM.request("missingGameRootObject") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def isGameListCorrect(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return 1 + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitForGameList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + + ##### LoginFSM: missingGameRootObject ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterMissingGameRootObject(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.warning("missing some game root objects.") + self.handler = self.handleMessageType + # Create a dialog box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.missingGameRootObjectBox = dialogClass( + message = OTPLocalizer.CRMissingGameRootObject, + doneEvent = "missingGameRootObjectBoxAck", + style = OTPDialog.TwoChoice) + self.missingGameRootObjectBox.show() + # Hang a hook for hitting OK or Cancel + self.accept( + "missingGameRootObjectBoxAck", + self.__handleMissingGameRootObjectAck) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleMissingGameRootObjectAck(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + doneStatus = self.missingGameRootObjectBox.doneStatus + # TODO: how should we wait for shards? + if doneStatus == "ok": + self.loginFSM.request("waitForGameList") + elif doneStatus == "cancel": + self.loginFSM.request("shutdown") + else: + self.notify.error("Unrecognized doneStatus: " + str(doneStatus)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitMissingGameRootObject(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + self.ignore("missingGameRootObjectBoxAck") + self.missingGameRootObjectBox.cleanup() + del self.missingGameRootObjectBox + + ##### LoginFSM: waitForShardList ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitForShardList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if not self.isValidInterestHandle(self.shardInterestHandle): + self.shardInterestHandle = self.addInterest( + self.GameGlobalsId, OTP_ZONE_ID_DISTRICTS, "LocalShardList", + "ShardList_Complete") + self.acceptOnce("ShardList_Complete", self._wantShardListComplete) + else: + self._wantShardListComplete() + + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitForShardList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.ignore('ShardList_Complete') + self.handler = None + + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _shardsAreReady(self): + # make sure there's at least one shard up + #print self.activeDistrictMap + for shard in self.activeDistrictMap.values(): + if shard.available: + return True + else: + return False + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _wantShardListComplete(self): + if self._shardsAreReady(): + self.loginFSM.request("waitForAvatarList") + else: + self.loginFSM.request("noShards") + + + ##### LoginFSM: noShards ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterNoShards(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + assert self.notify.warning("No shards are available.") + messenger.send("connectionIssue") + self.handler = self.handleMessageType + # Create a dialog box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.noShardsBox = dialogClass( + message = OTPLocalizer.CRNoDistrictsTryAgain, + doneEvent = "noShardsAck", + style = OTPDialog.TwoChoice) + self.noShardsBox.show() + # Hang a hook for hitting OK or Cancel + self.accept("noShardsAck", self.__handleNoShardsAck) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleNoShardsAck(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + doneStatus = self.noShardsBox.doneStatus + # TODO: how should we wait for shards? + if doneStatus == "ok": + messenger.send("connectionRetrying") + self.loginFSM.request("noShardsWait") + elif doneStatus == "cancel": + self.loginFSM.request("shutdown") + else: + self.notify.error("Unrecognized doneStatus: " + str(doneStatus)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitNoShards(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + self.ignore("noShardsAck") + self.noShardsBox.cleanup() + del self.noShardsBox + + ##### LoginFSM: noShardsWait ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterNoShardsWait(self): + # pretend that we're trying to reconnect for a while, to + # cut down on traffic after an AI crash + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Show a "connecting..." box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.connectingBox = dialogClass( + message=OTPLocalizer.CRConnecting) + self.connectingBox.show() + # Redraw the screen so the box will be visible. + self.renderFrame() + self.noShardsWaitTaskName = "noShardsWait" + def doneWait(task, self=self): + self.loginFSM.request('waitForShardList') + if __dev__: + delay = 0. + else: + delay = 6.5 + random.random()*2. + taskMgr.doMethodLater(delay, doneWait, + self.noShardsWaitTaskName) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitNoShardsWait(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + taskMgr.remove(self.noShardsWaitTaskName) + del self.noShardsWaitTaskName + self.connectingBox.cleanup() + del self.connectingBox + + ##### LoginFSM: reject ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterReject(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleMessageType + + self.notify.warning("Connection Rejected") + # tell the activeX control we were rejected by the server. + launcher.setPandaErrorCode(13) + + sys.exit() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitReject(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + + ##### LoginFSM: noConnection ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterNoConnection(self): + messenger.send("connectionIssue") + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # We come here when we've just been dropped by the server for + # some reason. + + self.resetInterestStateForConnectionLoss() + self.shardInterestHandle = None + + self.handler = self.handleMessageType + + # Reset our internal current AvatarID number, so we keep in + # sync with what the server thinks our current AvatarID is (we + # will have to establish a new connection and get a new + # AvatarID). If we don't do this, the server will hang up on + # us again when we try to reset the currentID to 0 from the + # login screen. + self.__currentAvId = 0 + + # Stop sending heartbeats + self.stopHeartbeat() + + # Stop trying to read the connection + self.stopReaderPollTask() + + # Get GAME_USERNAME value from env. + gameUsername = launcher.getValue('GAME_USERNAME', base.cr.userName) + + # Look for a good explanation to display for the user. + if self.bootedIndex != None and OTPLocalizer.CRBootedReasons.has_key( + self.bootedIndex): + # We've got a standard reason code for the boot from the server. + message = (OTPLocalizer.CRBootedReasons[self.bootedIndex]) % {'name' : gameUsername} + + elif self.bootedText != None: + # We don't recognize this reason code, but we have a text + # string explanation from the server. + message = OTPLocalizer.CRBootedReasonUnknownCode % self.bootedIndex + + else: + # We didn't get a reason for the boot from the server. + # That probably means the disconnect didn't come from the + # server, but from some other connectivity problem. + message = OTPLocalizer.CRLostConnection + + # some reasons don't want a reconnect option + reconnect = 1 + if self.bootedIndex in (152,127): + reconnect = 0 + + #report disconnect to launcher for use in exit codes + self.launcher.setDisconnectDetails(self.bootedIndex, message) + + # Create a dialog box. + style = OTPDialog.Acknowledge + if reconnect and self.loginInterface.supportsRelogin(): + message += OTPLocalizer.CRTryConnectAgain + style = OTPDialog.TwoChoice + dialogClass = OTPGlobals.getGlobalDialogClass() + self.lostConnectionBox = dialogClass( + doneEvent = "lostConnectionAck", + message = message, + text_wordwrap = 18, + style = style) + self.lostConnectionBox.show() + + # Hang a hook for hitting OK or Cancel. + self.accept("lostConnectionAck", self.__handleLostConnectionAck) + + # Log a message + self.notify.warning("Lost connection to server. Notifying user.") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleLostConnectionAck(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if self.lostConnectionBox.doneStatus == "ok" and self.loginInterface.supportsRelogin(): + # Go log in again + self.loginFSM.request("connect", [self.serverList]) + else: + # Goodbye! + self.loginFSM.request("shutdown") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitNoConnection(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + # Clean up the dialog box + self.ignore("lostConnectionAck") + self.lostConnectionBox.cleanup() + messenger.send("connectionRetrying") + + ##### LoginFSM: afkTimeout ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterAfkTimeout(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # We need to tell the server to no longer send messages to this + # toon while we wait for the player to click "ok" + self.sendSetAvatarIdMsg(0) + msg = OTPLocalizer.AfkForceAcknowledgeMessage + dialogClass = OTPGlobals.getDialogClass() + self.afkDialog = dialogClass( + text = msg, command = self.__handleAfkOk, + style = OTPDialog.Acknowledge) + self.handler = self.handleMessageType + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleAfkOk(self, value): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.loginFSM.request('waitForAvatarList') + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitAfkTimeout(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if (self.afkDialog): + self.afkDialog.cleanup() + self.afkDialog = None + self.handler = None + + ##### LoginFSM: periodTimeout ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterPeriodTimeout(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.sendSetAvatarIdMsg(0) + + # We *could* just log out the user and go back to the login + # page after the dialog--maybe the user's friend wants to + # play, after all--but I think it makes more sense to go ahead + # and boot the user all the way out of the game. + self.sendDisconnect() + + msg = OTPLocalizer.PeriodForceAcknowledgeMessage + dialogClass = OTPGlobals.getDialogClass() + self.periodDialog = dialogClass(text = msg, + command = self.__handlePeriodOk, + style = OTPDialog.Acknowledge) + self.handler = self.handleMessageType + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handlePeriodOk(self, value): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + base.exitShow() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitPeriodTimeout(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if (self.periodDialog): + self.periodDialog.cleanup() + self.periodDialog = None + self.handler = None + + ##### LoginFSM: waitForAvatarList ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitForAvatarList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleWaitForAvatarList + self._requestAvatarList() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _requestAvatarList(self): + self.cleanupWaitingForDatabase() + self.sendGetAvatarsMsg() + self.waitForDatabaseTimeout(requestName='WaitForAvatarList') + self.acceptOnce(OtpAvatarManager.OtpAvatarManager.OnlineEvent, + self._requestAvatarList) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendGetAvatarsMsg(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Request a list of avatars + datagram = PyDatagram() + # Add a message type + datagram.addUint16(CLIENT_GET_AVATARS) + # Send the message + self.send(datagram) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitForAvatarList(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.cleanupWaitingForDatabase() + self.ignore(OtpAvatarManager.OtpAvatarManager.OnlineEvent) + self.handler = None + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleWaitForAvatarList(self, msgType, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if msgType == CLIENT_GET_AVATARS_RESP: + self.handleGetAvatarsRespMsg(di) + elif msgType == CLIENT_GET_AVATARS_RESP2: + assert 0 # obsolete self.handleGetAvatarsResp2Msg(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_UP: + #Roger wants to remove this self.handleServerUp(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_DOWN: + #Roger wants to remove this self.handleServerDown(di) + else: + self.handleMessageType(msgType, di) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleGetAvatarsRespMsg(self, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Get the return code + returnCode = di.getUint8() + if returnCode == 0: + # If the return code is good, get the list of avatars + # First, get the number of avatars + avatarTotal = di.getUint16() + assert (avatarTotal <= self.avatarLimit) and (avatarTotal >= 0) + avList = [] + + #print di.getDatagram().dumpHex(ostream) + for i in range(0, avatarTotal): + # Get the avatar id number + avNum = di.getUint32() + # Get the avatar name waffle + # avNames is a list of the toon's name, wantName, + # approvedName, and rejectedName + # name is distributed while the other three are + # specialized for makeatoon process + avNames = ["","","",""] + avNames[0] = di.getString() + + avNames[1] = di.getString() + avNames[2] = di.getString() + avNames[3] = di.getString() + + # Get the avatar DNA + avDNA = di.getString() + # Get the avatar position + avPosition = di.getUint8() + # We have to get info about the name here + aname = di.getUint8() + # print "aname = " + str(aname) + # Assemble the data + potAv = PotentialAvatar( + avNum, avNames, avDNA, avPosition, aname) + # Put it on the list + avList.append(potAv) + + # save for future use + self.avList = avList + # Now, we can move on to choosing an avatar + self.loginFSM.request("chooseAvatar", [self.avList]) + else: + # Bad news. Go to "shutdown" mode + self.notify.error("Bad avatar list return code: " + str(returnCode)) + self.loginFSM.request("shutdown") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleGetAvatarsResp2Msg(self, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Get the return code + returnCode = di.getUint8() + if returnCode == 0: + # If the return code is good, get the list of avatars + # First, get the number of avatars + avatarTotal = di.getUint16() + assert (avatarTotal <= self.avatarLimit) and (avatarTotal >= 0) + avList = [] + + #print di.getDatagram().dumpHex(ostream) + for i in range(0, avatarTotal): + # Get the avatar id number + avNum = di.getUint32() + # Get the avatar name waffle + # resp2 reports only the actual avatar name for now + avNames = ["","","",""] + avNames[0] = di.getString() + + # resp2 doesn't report avatar DNA + avDNA = None + # Get the avatar position + avPosition = di.getUint8() + # resp2 doesn't report aname + aname = None + # Assemble the data + potAv = PotentialAvatar( + avNum, avNames, avDNA, avPosition, aname) + # Put it on the list + avList.append(potAv) + + # save for future use + self.avList = avList + # Now, we can move on to choosing an avatar + self.loginFSM.request("chooseAvatar", [self.avList]) + else: + # Bad news. Go to "shutdown" mode + self.notify.error("Bad avatar list return code: " + str(returnCode)) + self.loginFSM.request("shutdown") + + ##### LoginFSM: chooseAvatar ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterChooseAvatar(self, avList): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitChooseAvatar(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + ##### LoginFSM: createAvatar ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterCreateAvatar(self, avList, index, newDNA=None): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitCreateAvatar(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendCreateAvatarMsg(self, avDNA, avName, avPosition): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Create a new avatar + datagram = PyDatagram() + # Add a message type + datagram.addUint16(CLIENT_CREATE_AVATAR) + # Add an echo context ... This appears to be unnecessary + datagram.addUint16(0) + # Put in the new name and DNA + # sdn: we no longer send the name + #datagram.addString(avName) + datagram.addString(avDNA.makeNetString()) + datagram.addUint8(avPosition) + self.newName = avName + self.newDNA = avDNA + self.newPosition = avPosition + # Send the message + self.send(datagram) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendCreateAvatar2Msg(self, avClass, avDNA, avName, avPosition): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Sends the new create-avatar message that creates an avatar + # of a type other than DistributedToon. avClass should be the + # class of avatar to create, + # e.g. DistributedPlayerPirate.DistributedPlayerPirate, + # DistributedTeen.DistributedTeen, or + # DistributedToon.DistributedToon. + + # Note that avDNA and avName are not sent to the server. + + className = avClass.__name__ + dclass = self.dclassesByName[className] + + datagram = PyDatagram() + datagram.addUint16(CLIENT_CREATE_AVATAR2) + datagram.addUint16(0) # echo context + + datagram.addUint8(avPosition) + datagram.addUint16(dclass.getNumber()) + + self.newName = avName + self.newDNA = avDNA + self.newPosition = avPosition + self.send(datagram) + + + ##### LoginFSM: waitForDeleteAvatarResponse ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitForDeleteAvatarResponse(self, potAv): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleWaitForDeleteAvatarResponse + # Send the delete avatar message + self.sendDeleteAvatarMsg(potAv.id) + self.waitForDatabaseTimeout(requestName='WaitForDeleteAvatarResponse') + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendDeleteAvatarMsg(self, avId): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Delete the avatar + datagram = PyDatagram() + # Add a message type + datagram.addUint16(CLIENT_DELETE_AVATAR) + # Put in the new name and DNA + datagram.addUint32(avId) + # Send the message + self.send(datagram) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitForDeleteAvatarResponse(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.cleanupWaitingForDatabase() + self.handler = None + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleWaitForDeleteAvatarResponse(self, msgType, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if msgType == CLIENT_DELETE_AVATAR_RESP: + # code re-use! + self.handleGetAvatarsRespMsg(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_UP: + #Roger wants to remove this self.handleServerUp(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_DOWN: + #Roger wants to remove this self.handleServerDown(di) + else: + self.handleMessageType(msgType, di) + + ##### LoginFSM: rejectRemoveAvatar ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterRejectRemoveAvatar(self, reasonCode): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.warning("Rejected removed avatar. (%s)"%(reasonCode,)) + self.handler = self.handleMessageType + # Create a dialog box + dialogClass = OTPGlobals.getGlobalDialogClass() + self.rejectRemoveAvatarBox = dialogClass( + message = "%s\n(%s)"%(OTPLocalizer.CRRejectRemoveAvatar, reasonCode), + doneEvent = "rejectRemoveAvatarAck", + style = OTPDialog.Acknowledge) + self.rejectRemoveAvatarBox.show() + # Hang a hook for hitting OK or Cancel + self.accept("rejectRemoveAvatarAck", self.__handleRejectRemoveAvatar) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def __handleRejectRemoveAvatar(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.loginFSM.request("chooseAvatar") + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitRejectRemoveAvatar(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + self.ignore("rejectRemoveAvatarAck") + self.rejectRemoveAvatarBox.cleanup() + del self.rejectRemoveAvatarBox + + ##### WaitForSetAvatarResponse state ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitForSetAvatarResponse(self, potAv): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = self.handleWaitForSetAvatarResponse + # Send the set avatar message + self.sendSetAvatarMsg(potAv) + self.waitForDatabaseTimeout(requestName='WaitForSetAvatarResponse') + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitForSetAvatarResponse(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.cleanupWaitingForDatabase() + self.handler = None + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendSetAvatarMsg(self, potAv): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Add the avatar id + self.sendSetAvatarIdMsg(potAv.id) + # Record the avatar data for easy creation + self.avData = potAv + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def sendSetAvatarIdMsg(self, avId): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if avId != self.__currentAvId: + self.__currentAvId = avId + # Choose an avatar + datagram = PyDatagram() + # Add a message type + datagram.addUint16(CLIENT_SET_AVATAR) + # Add the avatar id + datagram.addUint32(avId) + # Send the message + self.send(datagram) + + if avId == 0: + # avId 0 means the avatar is logging out; stop the + # timer. + self.stopPeriodTimer() + else: + # A non-zero avId means an avatar is logging in; start + # the timer. + self.startPeriodTimer() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleAvatarResponseMsg(self, di): + # Inheritors should overwrite + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleWaitForSetAvatarResponse(self, msgType, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if msgType == CLIENT_GET_AVATAR_DETAILS_RESP: + self.handleAvatarResponseMsg(di) + elif msgType == CLIENT_GET_PET_DETAILS_RESP: + self.handleAvatarResponseMsg(di) + elif msgType == CLIENT_GET_FRIEND_LIST_RESP: + self.handleGetFriendsList(di) + elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP: + self.handleGetFriendsListExtended(di) + elif msgType == CLIENT_FRIEND_ONLINE: + self.handleFriendOnline(di) + elif msgType == CLIENT_FRIEND_OFFLINE: + self.handleFriendOffline(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_UP: + #Roger wants to remove this self.handleServerUp(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_DOWN: + #Roger wants to remove this self.handleServerDown(di) + else: + self.handleMessageType(msgType, di) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterPlayingGame(self): + # override and do whatever is necessary to get the avatar into the game + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitPlayingGame(self): + # Throw an event so systems can clean themselves up before + # we scrub around looking for leaks. + self.notify.info("sending clientLogout") + messenger.send("clientLogout") + +################################################# +## _ _ ______ _____ __ __ +## / | | (_) | ____/ ____| \/ | +## / | | ___ __ _ _ _ __ | |__ | (___ | \ / | +## / | |/ _ \ / _` | | '_ \| __| \___ \| |\/| | +## / | | (_) | (_| | | | | | | ____) | | | | +## / |_|\___/ \__, |_|_| |_|_| |_____/|_| |_| +## / __/ | +## / |___/ +## / __ _ _ +## / / _| | | (_) +## / | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ +## / | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| +## / | | | |_| | | | | (__| |_| | (_) | | | \__ \ +## / |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ +## +################################################# + + def detectLeaks(self, okTasks=None, okEvents=None): + if (not __dev__) or \ + configIsToday("allow-unclean-exit"): + assert self.notify.warning("Not enforcing clean exit.") + return + + leakedTasks = self.detectLeakedTasks(okTasks) + leakedEvents = self.detectLeakedEvents(okEvents) + leakedIvals = self.detectLeakedIntervals() + leakedGarbage = self.detectLeakedGarbage() + + if (leakedTasks or leakedEvents or leakedIvals or leakedGarbage): + # if we are leaving to do something specific on the website, + # like buying the game, don't treat leaks as errors + # this is temporary until we pull in the new launcher code in production + #errorCode = launcher.getPandaErrorCode() + errorCode = base.getExitErrorCode() + if ((errorCode >= OTPLauncherGlobals.NonErrorExitStateStart) and + (errorCode <= OTPLauncherGlobals.NonErrorExitStateEnd)): + # log the leaks and continue on + logFunc = self.notify.warning + allowExit = True + else: + if __debug__: + # log the leaks and stop the client + logFunc = self.notify.error + allowExit = False + else: + # In production, lets log the warning so we can do triage, but let + # the user go ahead and exit / logout without submitting a bug. + logFunc = self.notify.warning + allowExit = False + if base.config.GetBool("direct-gui-edit", 0): + logFunc("There are leaks: %s tasks, %s events, %s ivals, %s garbage cycles\nLeaked Events may be due to direct gui editing" % + (leakedTasks, leakedEvents, leakedIvals, leakedGarbage)) + else: + logFunc("There are leaks: %s tasks, %s events, %s ivals, %s garbage cycles" % + (leakedTasks, leakedEvents, leakedIvals, leakedGarbage)) + if allowExit: + self.notify.info('Allowing client to leave, panda error code %s' % errorCode) + else: + base.userExit() + else: + self.notify.info("There are no leaks detected.") + + def detectLeakedGarbage(self, callback=None): + if not __debug__: + return 0 + self.notify.info('checking for leaked garbage...') + if gc.garbage: + self.notify.warning("garbage already contains %d items" % len(gc.garbage)) + report = GarbageReport.GarbageReport('logout', verbose=True) + numCycles = report.getNumCycles() + if numCycles: + msg = "You can't leave until you take out your garbage. See report above & base.garbage" + self.notify.info(msg) + report.destroy() + return numCycles + + def detectLeakedTasks(self, extraTasks=None): + # Make sure there are no leftover tasks that shouldn't be here. + allowedTasks = ["dataLoop", + "resetPrevTransform", + "doLaterProcessor", + "eventManager", + "readerPollTask", + "heartBeat", + "gridZoneLoop", + "igLoop", + "audioLoop", + "asyncLoad", + "collisionLoop", + "shadowCollisionLoop", + "ivalLoop", + "downloadSequence", + "patchAndHash", + "launcher-download", + "launcher-download-multifile", + "launcher-decompressFile", + "launcher-decompressMultifile", + "launcher-extract", + "launcher-patch", + "slowCloseShardCallback", + "tkLoop", + "manager-update", + "downloadStallTask", + "clientSleep", + jobMgr.TaskName, + self.GarbageCollectTaskName, + "RedownloadNewsTask", #in another taskChain and taskMgr.remove doesnt work + ] + if extraTasks is not None: + allowedTasks.extend(extraTasks) + problems = [] + for task in taskMgr.getTasks(): + if not hasattr(task, 'name'): + # this allows pure-C++ tasks, which are all + # 'non-leaking' at the moment + # TODO: come up with a good way to differentiate + # C++ & Python tasks that are OK to 'leak' from those + # that aren't + continue + if task.name in allowedTasks: + continue + else: + if hasattr(task, "debugInitTraceback"): + print task.debugInitTraceback + problems.append(task.name) + if problems: + print taskMgr + msg = "You can't leave until you clean up your tasks: {" + for task in problems: + msg += "\n " + task + msg += "}\n" + self.notify.info(msg) + return len(problems) + else: + return 0 + + def detectLeakedEvents(self, extraHooks=None): + # Make sure there are no leftover hooks that shouldn't be here. + allowedHooks = ["destroy-DownloadWatcherBar", + "destroy-DownloadWatcherText", + "destroy-fade", + "f9", "control-f9", + "launcherAllPhasesComplete", + "launcherPercentPhaseComplete", + "newDistributedDirectory", + "page_down", + "page_up", + "panda3d-render-error", + "PandaPaused", + "PandaRestarted", + "phaseComplete-3", + "press-mouse2-fade", + "print-fade", + "release-mouse2-fade", + "resetClock", + "window-event", + "TCRSetZoneDone", + "aspectRatioChanged", + "newDistributedDirectory", # OTPserver only, but does not hurt either + CConnectionRepository.getOverflowEventName(), + self._getLostConnectionEvent(), + 'render-texture-targets-changed', + 'gotExtraFriendHandles' + ] + if hasattr(loader, 'hook'): + allowedHooks.append(loader.hook) + if extraHooks is not None: + allowedHooks.extend(extraHooks) + problems = [] + for hook in messenger.getEvents(): + if hook not in allowedHooks: + problems.append(hook) + if problems: + msg = "You can't leave until you clean up your messenger hooks: {" + for hook in problems: + whoAccepts = messenger.whoAccepts(hook) + msg += "\n %s" % hook + for obj in whoAccepts: + msg += '\n OBJECT:%s, %s %s' % (obj, obj.__class__, whoAccepts[obj]) + if hasattr(obj, 'getCreationStackTraceCompactStr'): + msg += '\n CREATIONSTACKTRACE:%s' % obj.getCreationStackTraceCompactStr() + else: + try: + # there are so many ways this could fail that it's in a try block + value = whoAccepts[obj] + callback = value[0] + guiObj = callback.im_self + if hasattr(guiObj, 'getCreationStackTraceCompactStr'): + msg += '\n CREATIONSTACKTRACE:%s' % guiObj.getCreationStackTraceCompactStr() + except: + pass + msg += "\n}\n" + self.notify.info(msg) + return len(problems) + else: + return 0 + + def detectLeakedIntervals(self): + # Make sure there are no leftover intervals that shouldn't be here. + numIvals = ivalMgr.getNumIntervals() + if numIvals > 0: + print "You can't leave until you clean up your intervals: {" + for i in range(ivalMgr.getMaxIndex()): + # We go through some effort to print each interval in + # detail. This means we need to find the interval. + # Some intervals exist only as C intervals, some only + # as Python intervals (although most are both). We + # prefer the Python pointer if it exists. + ival = None + if i < len(ivalMgr.ivals): + ival = ivalMgr.ivals[i] + if ival == None: + ival = ivalMgr.getCInterval(i) + if ival: + print ival + if hasattr(ival, "debugName"): + print ival.debugName + if hasattr(ival, "debugInitTraceback"): + print ival.debugInitTraceback + print "}" + self.notify.info( + "You can't leave until you clean up your intervals.") + return numIvals + else: + return 0 + + + def _abandonShard(self): + # Override and do whatever is necessary to shut down our shard + # interest without waiting for interest-complete messages. + self.notify.error('%s must override _abandonShard' % + self.__class__.__name__) + + +##################################################### +## ______ _____ __ __ +## | ____/ ____| \/ | +## __ _ __ _ _ __ ___ ___| |__ | (___ | \ / | +## / _` |/ _` | '_ ` _ \ / _ \ __| \___ \| |\/| | +## | (_| | (_| | | | | | | __/ | ____) | | | | +## \__, |\__,_|_| |_| |_|\___|_| |_____/|_| |_| +## __/ | +## |___/ +## __ _ _ +## / _| | | (_) +## | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ +## | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| +## | | | |_| | | | | (__| |_| | (_) | | | \__ \ +## |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ +## +##################################################### + + ##### gameFSM: gameOff ##### + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterGameOff(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.uberZoneInterest = None + # If cleanGameExit doesn't exist, we haven't run the game yet. + # If cleanGameExit is True, we're exiting the game through normal means. + # If False, we got disconnected (or closed the window, etc.) + if not hasattr(self, 'cleanGameExit'): + self.cleanGameExit = True + + if self.cleanGameExit: + if self.isShardInterestOpen(): + # error, we didn't close up the shard interest + self.notify.error('enterGameOff: shard interest is still open') + # the cache should be empty at this point + assert self.cache.isEmpty() + else: + # we left the game suddenly and uncleanly (disconnected, + # closed window, etc.) + # remove the shard interest manually (don't wait for network + # deletes) + if self.isShardInterestOpen(): + self.notify.warning('unclean exit, abandoning shard') + self._abandonShard() + + self.cleanupWaitAllInterestsComplete() + + del self.cleanGameExit + + # make sure the cache is empty + self.cache.flush() + self.doDataCache.flush() + + self.handler = self.handleMessageType + #Commented out the following lines. Too aggressive, kills preloading + #benefit from character creation and starting game. Will replace + #with unloads at the pirates level + #ModelPool.garbageCollect() + #TexturePool.garbageCollect() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitGameOff(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.handler = None + + ##### gameFSM: waitOnEnterResponses ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterWaitOnEnterResponses(self, shardId, hoodId, zoneId, avId): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # By default, set cleanGameExit to False, and set it to True + # before leaving cleanly. + self.cleanGameExit = False + self.handler = self.handleWaitOnEnterResponses + self.handlerArgs = {"hoodId": hoodId, + "zoneId": zoneId, + "avId": avId} + ## roger -->changed to some resonable login.. like lowest population + if shardId is not None: + district = self.activeDistrictMap.get(shardId) + else: + district = None + if not district: + self.distributedDistrict = self.getStartingDistrict() + if self.distributedDistrict is None: + self.loginFSM.request("noShards") + return + shardId = self.distributedDistrict.doId + else: + self.distributedDistrict = district + + self.notify.info("Entering shard %s" % (shardId)) + localAvatar.setLocation(shardId, zoneId) + + base.localAvatar.defaultShard = shardId + # Sleep for a moment to let the messages process on the game + # server. We can remove this if Roger reworks some of the server. + # There is a race condition. + self.waitForDatabaseTimeout(requestName='WaitOnEnterResponses') + + # We will also assume that we picked a good shard and zone. + # Two responses will come back, and they will be ignored. + self.handleSetShardComplete() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleWaitOnEnterResponses(self, msgType, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if msgType == CLIENT_GET_FRIEND_LIST_RESP: + self.handleGetFriendsList(di) + elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP: + self.handleGetFriendsListExtended(di) + elif msgType == CLIENT_FRIEND_ONLINE: + self.handleFriendOnline(di) + elif msgType == CLIENT_FRIEND_OFFLINE: + self.handleFriendOffline(di) + elif msgType == CLIENT_GET_PET_DETAILS_RESP: + self.handleGetAvatarDetailsResp(di) + else: + self.handleMessageType(msgType, di) + + # In the OTP server you do not need to go into the quiet zone + # to manifest uberzone objects, you can just add interest to the + # shard's uber zone. + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleSetShardComplete(self): + self.cleanupWaitingForDatabase() + # Eventually, this should do some error checking, for now I'll + # assume that everything is AOK + hoodId = self.handlerArgs["hoodId"] + zoneId = self.handlerArgs["zoneId"] + avId = self.handlerArgs["avId"] + + # We will not go to the quiet zone anymore, now we will just + # register interest in the zone where the uber objects + # (TimeManager, etc) live. This happens to be in the District's + # uber zone (2). + self.uberZoneInterest = self.addInterest( + base.localAvatar.defaultShard, + OTPGlobals.UberZone, + "uberZone", + "uberZoneInterestComplete") + self.acceptOnce("uberZoneInterestComplete", self.uberZoneInterestComplete) + # Listen for the arrival of the time manager and tutorial manager. + self.waitForDatabaseTimeout(20, requestName='waitingForUberZone') + + # This replaces the old "reachedQuietZone()" + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def uberZoneInterestComplete(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.__gotTimeSync = 0 + + self.cleanupWaitingForDatabase() + + # Now we're in the UBER zone, and presumably we've seen the + # TimeManager created by now. Wait just a little bit longer, + # if necessary, for the sync message to complete its round + # trip. + if self.timeManager == None: + # If we don't have a time manager, we're probably running + # without an AI, so never mind. + self.notify.info("TimeManager is not present.") + DistributedSmoothNode.globalActivateSmoothing(0, 0) + self.gotTimeSync() + else: + # Since we have a TimeManager, we can enable motion + # smoothing. But we don't enable predictive smoothing by + # default. + DistributedSmoothNode.globalActivateSmoothing(1, 0) + + # Also get the prc hash and signature, and send 'em up. + h = HashVal() + hashPrcVariables(h) + + pyc = HashVal() + if not __dev__: + self.hashFiles(pyc) + + self.timeManager.d_setSignature(self.userSignature, h.asBin(), + pyc.asBin()) + self.timeManager.sendCpuInfo() + + # Ask the TimeManager to sync us up. + if self.timeManager.synchronize("startup"): + self.accept("gotTimeSync", self.gotTimeSync) + self.waitForDatabaseTimeout(requestName='uberZoneInterest-timeSync') + else: + # For some reason, the TimeManager didn't want to + # sync. Presumably it just synced for some other + # reason, so never mind. + self.notify.info("No sync from TimeManager.") + self.gotTimeSync() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitWaitOnEnterResponses(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.ignore('uberZoneInterestComplete') + self.cleanupWaitingForDatabase() + self.handler = None + self.handlerArgs = None + + + ##### gameFSM: CloseShard ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterCloseShard(self, loginState=None): + # override and call _removeLocalAvFromStateServer when you're + # ready + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.info("Exiting shard") + if loginState is None: + loginState = "waitForAvatarList" + self._closeShardLoginState = loginState + # set a flag to prevent new interests from being opened + base.cr.setNoNewInterests(True) + if __debug__: + base.cr.printInterests() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _removeLocalAvFromStateServer(self): + assert self.notify.debug("_removeLocalAvFromStateServer: about to sendSetAvatarIdMsg 0") + # Now we can safely remove the avatar from the state server + self.sendSetAvatarIdMsg(0) + self._removeAllOV() + callback = Functor(self.loginFSM.request, self._closeShardLoginState) + if base.slowCloseShard: + taskMgr.doMethodLater( + base.slowCloseShardDelay*.5, + Functor(self.removeShardInterest, callback), + 'slowCloseShard') + else: + self.removeShardInterest(callback) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _removeAllOV(self): + # force delete for all owner-view objects, OTP server has done the same on its end + ownerDoIds = self.doId2ownerView.keys() + for doId in ownerDoIds: + self.disableDoId(doId, ownerView=True) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def isShardInterestOpen(self): + # override this and return True if we've got a shard interest open + self.notify.error('%s must override isShardInterestOpen' % + self.__class__.__name__) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def removeShardInterest(self, callback, task=None): + self._removeCurrentShardInterest( + Functor(self._removeShardInterestComplete, callback)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _removeShardInterestComplete(self, callback): + # We've removed interest in the current shard. That constitutes + # a clean game exit. If we enter another shard, this will be set + # to False again. + self.cleanGameExit = True + # flush out the DO cache + self.cache.flush() + self.doDataCache.flush() + if base.slowCloseShard: + taskMgr.doMethodLater( + base.slowCloseShardDelay*.5, + Functor(self._callRemoveShardInterestCallback, callback), + 'slowCloseShardCallback') + else: + self._callRemoveShardInterestCallback(callback, None) + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _callRemoveShardInterestCallback(self, callback, task): + callback() + return Task.done + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _removeCurrentShardInterest(self, callback): + # Override this and do whatever is necessary to close interest + # in the current shard. Call callback when done. + self.notify.error('%s must override _removeCurrentShardInterest' % + self.__class__.__name__) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitCloseShard(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + del self._closeShardLoginState + # clear the flag and allow interests to be opened again + base.cr.setNoNewInterests(False) + + ##### gameFSM: tutorial question ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterTutorialQuestion(self, hoodId, zoneId, avId): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitTutorialQuestion(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + pass + + + ##### gameFSM: play game ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterPlayGame(self, hoodId, zoneId, avId): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Stop the music that started in ToontownStart + if self.music: + self.music.stop() + self.music = None + + self.garbageLeakLogger = GarbageLeakServerEventAggregator(self) + + self.handler = self.handlePlayGame + + self.accept(self.gameDoneEvent, self.handleGameDone) + base.transitions.noFade() + + self.playGame.load() + # TODO: pull in bulk load into pirates or OTP + try: + loader.endBulkLoad("localAvatarPlayGame") + except: + pass + self.playGame.enter(hoodId, zoneId, avId) + + def checkScale(task): + # Spammy --> assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + assert base.localAvatar.getTransform().hasUniformScale() + return Task.cont + assert taskMgr.add(checkScale, 'globalScaleCheck') + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def handleGameDone(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # The PlayGame ClassicFSM exited. This normally happens only when + # the player wants to teleport to another shard; in that case, + # we have to back all the way out of the previous PlayGame + # state, switch shards up here, then go all the way back in on + # the new shard. + + if self.timeManager: + self.timeManager.setDisconnectReason(OTPGlobals.DisconnectSwitchShards) + + doneStatus = self.playGame.getDoneStatus() + how = doneStatus["how"] + shardId = doneStatus["shardId"] + hoodId = doneStatus["hoodId"] + zoneId = doneStatus["zoneId"] + avId = doneStatus["avId"] + + if how == "teleportIn": + # We only know how to teleport in when we enter a zone + # from way up here. + # Wait for the shard to go away + self.gameFSM.request( + "switchShards", [shardId, hoodId, zoneId, avId]) + else: + self.notify.error("Exited shard with unexpected mode %s" % (how)) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitPlayGame(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + taskMgr.remove('globalScaleCheck') + + self.handler = None + self.playGame.exit() + self.playGame.unload() + + self.ignore(self.gameDoneEvent) + + self.garbageLeakLogger.destroy() + del self.garbageLeakLogger + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def gotTimeSync(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.info("gotTimeSync") + self.ignore("gotTimeSync") + self.__gotTimeSync = 1 + self.moveOnFromUberZone() + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def moveOnFromUberZone(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if not self.__gotTimeSync: + self.notify.info("Waiting for time sync.") + return + + hoodId = self.handlerArgs["hoodId"] + zoneId = self.handlerArgs["zoneId"] + avId = self.handlerArgs["avId"] + + # It's safe to either go into the actual game, or prompt for a + # tutorial if desired. + + # If the player has acknowledged the tutorial opportunity sometime + # in the past, just go play the game. Otherwise, ask them if + # they would like a tutorial. + if not self.SupportTutorial or base.localAvatar.tutorialAck: + # No tutorial question... go play the game! + self.gameFSM.request("playGame", [hoodId, zoneId, avId]) + else: + if base.config.GetBool('force-tutorial', 1): + # By default, we ask the tutorial question, unless it + # is DConfig'ed off. + # or they clicked on skip tutorial + if hasattr(self,"skipTutorialRequest") and self.skipTutorialRequest: + # hack we go to playGame to get the toon on the AI + self.gameFSM.request("playGame", [hoodId, zoneId, avId]) + # Ask the tutorial manager to skip our tutorial + self.gameFSM.request("skipTutorialRequest", [hoodId, zoneId, avId]) + else: + self.gameFSM.request("tutorialQuestion", [hoodId, zoneId, avId]) + else: + # It's been DConfig'ed off. Go play! + self.gameFSM.request("playGame", [hoodId, zoneId, avId]) + + def handlePlayGame(self, msgType, di): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if self.notify.getDebug(): + self.notify.debug("handle play game got message type: " + `msgType`) + if msgType == CLIENT_CREATE_OBJECT_REQUIRED: + self.handleGenerateWithRequired(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER: + self.handleGenerateWithRequiredOther(di) + elif msgType == CLIENT_OBJECT_UPDATE_FIELD: + self.handleUpdateField(di) + elif msgType == CLIENT_OBJECT_DISABLE_RESP: + self.handleDisable(di) + elif msgType == CLIENT_OBJECT_DELETE_RESP: + assert 0 + self.handleDelete(di) + elif msgType == CLIENT_GET_FRIEND_LIST_RESP: + self.handleGetFriendsList(di) + elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP: + self.handleGetFriendsListExtended(di) + elif msgType == CLIENT_FRIEND_ONLINE: + self.handleFriendOnline(di) + elif msgType == CLIENT_FRIEND_OFFLINE: + self.handleFriendOffline(di) + elif msgType == CLIENT_GET_AVATAR_DETAILS_RESP: + self.handleGetAvatarDetailsResp(di) + elif msgType == CLIENT_GET_PET_DETAILS_RESP: + self.handleGetAvatarDetailsResp(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_UP: + #Roger wants to remove this self.handleServerUp(di) + #Roger wants to remove this elif msgType == CLIENT_SERVER_DOWN: + #Roger wants to remove this self.handleServerDown(di) + #Roger wants to remove this elif msgType == CLIENT_GET_SHARD_LIST_RESP: + #Roger wants to remove this self.handleGetShardListResponseMsg(di) + # Moved to handleMessageType elif msgType == CLIENT_GET_STATE_RESP: + # Moved to handleMessageType di.skipBytes(12) + # Moved to handleMessageType zoneId = di.getInt32() + # Moved to handleMessageType # HACK! This should really be handled. + # Moved to handleMessageType pass + else: + self.handleMessageType(msgType, di) + + ##### gameFSM: switch shards ##### + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def enterSwitchShards(self, shardId, hoodId, zoneId, avId): + self._switchShardParams = [shardId, hoodId, zoneId, avId] + # remove any interests in the old shard + localAvatar.setLeftDistrict() + self.removeShardInterest(self._handleOldShardGone) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def _handleOldShardGone(self): + self.gameFSM.request( + "waitOnEnterResponses", self._switchShardParams) + + @report(types = ['args', 'deltaStamp'], dConfigParam = 'teleport') + def exitSwitchShards(self): + pass + +##################################################### +## ______ _____ __ __ +## / | ____/ ____| \/ | +## / __ _ __ _ _ __ ___ ___| |__ | (___ | \ / | +## / / _` |/ _` | '_ ` _ \ / _ \ __| \___ \| |\/| | +## / | (_| | (_| | | | | | | __/ | ____) | | | | +## / \__, |\__,_|_| |_| |_|\___|_| |_____/|_| |_| +## / __/ | +## / |___/ +## / __ _ _ +## / / _| | | (_) +## / | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ +## / | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| +## / | | | |_| | | | | (__| |_| | (_) | | | \__ \ +## / |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ +## +##################################################### + + ################################################### + # Utility Functions + ################################################### + + ######### Account information ######### + + def isFreeTimeExpired(self): + """ + Returns 1 if the user has no more free playing time remaining. + or 0 if the user may still play for free. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if self.accountOldAuth: + return 0 + + # check for the config overrides + # if true, free-time-expired takes precedence over unlimited-free-time + if base.config.GetBool("free-time-expired", 0): + return 1 + if base.config.GetBool("unlimited-free-time", 0): + return 0 + + # -1 == never expires (paid/exempt) + if self.freeTimeExpiresAt == -1: + return 0 + + # 0 == expired + if self.freeTimeExpiresAt == 0: + return 1 + + if self.freeTimeExpiresAt < -1: + self.notify.warning('freeTimeExpiresAt is less than -1 (%s)' % + self.freeTimeExpiresAt) + + # freeTimeExpiresAt is an epoch time + # is it in the past? + if self.freeTimeExpiresAt < time.time(): + return 1 + else: + return 0 + + def freeTimeLeft(self): + """ + returns number of seconds of free time that the player has left + if player has already paid, or they are out of time, returns 0 + (if they've already paid, there's no reason to use this function; + see isPaid()) + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # -1 == never expires (paid/exempt) + # 0 == expired + if self.freeTimeExpiresAt == -1 or \ + self.freeTimeExpiresAt == 0: + return 0 + + # freeTimeExpiresAt is an epoch time + secsLeft = self.freeTimeExpiresAt - time.time() + # if free time just expired, secsLeft <=0 + # make sure we don't return a negative number + return max(0, secsLeft) + + def isWebPlayToken(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return self.playToken!=None + + def isBlue(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return self.blue!=None + + def isPaid(self): + """ + For Toontown: + Returns 1 if the user has paid or 0 if the user has not paid. + For Pirates: + Returns OTPGlobals.AccessUnknown, OTPGlobals.VelvetRope, or OTPGlobals.Full + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + paidStatus = base.config.GetString('force-paid-status', '') + if not paidStatus: + return self.__isPaid + elif paidStatus == 'paid': + return 1 + elif paidStatus == 'unpaid': + return 0 + elif paidStatus == 'FULL': + return OTPGlobals.AccessFull + elif paidStatus == 'VELVET': + return OTPGlobals.AccessVelvetRope + else: + return 0 + + + def setIsPaid(self, isPaid): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.__isPaid=isPaid + + def allowFreeNames(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Do we allow free trialers to name their toon? + return base.config.GetInt("allow-free-names", 1) + + def allowSecretChat(self): + """ + Returns true if any keyboard chat, including via the "secret + friends" interface, should be allowed, or false if all of + these interfaces should be suppressed for the current player. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return (self.secretChatAllowed or \ + (self.productName == "Terra-DMC" and self.isBlue() and self.secretChatAllowed)) + + def allowWhiteListChat(self): + if hasattr(self,'whiteListChatEnabled') and self.whiteListChatEnabled: + return True + else: + return False + + + def allowAnyTypedChat(self): + if self.allowSecretChat() or self.allowWhiteListChat() or self.allowOpenChat(): + return True + else: + return False + + def allowOpenChat(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return self.openChatAllowed + + def isParentPasswordSet(self): + return self.parentPasswordSet + + def needParentPasswordForSecretChat(self): + """ + Returns true if the "secret friends" interface requires use of the Parent Password. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + return ((self.isPaid() and self.secretChatNeedsParentPassword) or + (self.productName == "Terra-DMC" and self.isBlue() and self.secretChatNeedsParentPassword)) + + def logAccountInfo(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.info('*** ACCOUNT INFO ***') + self.notify.info('username: %s' % self.userName) + if self.blue: + self.notify.info('paid: %s (blue)' % self.isPaid()) + else: + self.notify.info('paid: %s' % self.isPaid()) + if not self.isPaid(): + if self.isFreeTimeExpired(): + self.notify.info('free time is expired') + else: + secs = self.freeTimeLeft() + self.notify.info( + 'free time left: %s' % + (PythonUtil.formatElapsedSeconds(secs))) + if self.periodTimerSecondsRemaining != None: + self.notify.info( + 'period time left: %s' % + (PythonUtil.formatElapsedSeconds(self.periodTimerSecondsRemaining))) + + ######### Shard information ######### + def getStartingDistrict(self): + """ + Get a Proper District For a starting location + None if no District in core + """ + district = None + + if len(self.activeDistrictMap.keys()) == 0: + self.notify.info('no shards') + return None + + if base.fillShardsToIdealPop: + # Choose highest-population shard that is not yet + # a 'high-population' shard + lowPop, midPop, highPop = base.getShardPopLimits() + self.notify.debug('low: %s mid: %s high: %s' % + (lowPop, midPop, highPop)) + for s in self.activeDistrictMap.values(): + if s.available and s.avatarCount < lowPop: + self.notify.debug('%s: pop %s' % + (s.name, s.avatarCount)) + if district is None: + district = s + else: + # if multiple shards have the same population, + # sort them by name so that all clients will + # choose the same one + if s.avatarCount > district.avatarCount or ( + (s.avatarCount == district.avatarCount and + s.name > district.name) + ): + district = s + + # if all of the shards are over the cutoff population, pick + # the lowest-population shard + if district is None: + self.notify.debug( + 'all shards over cutoff, picking lowest-population shard') + for s in self.activeDistrictMap.values(): + if s.available: + self.notify.debug('%s: pop %s' % + (s.name, s.avatarCount)) + if (district is None or + (s.avatarCount < district.avatarCount)): + district = s + + if district is not None: + self.notify.debug('chose %s: pop %s' % (district.name, district.avatarCount)) + return district + + def getShardName(self, shardId): + """ + Returns the name associated with the indicated shard ID, or + None if the shard is unknown. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + try: + return self.activeDistrictMap[shardId].name + except: + return None + + def isShardAvailable(self, shardId): + """ + Returns true if the indicated shard is believed to be up and + running at the moment, false otherwise. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + try: + return self.activeDistrictMap[shardId].available + except: + return 0 + + def listActiveShards(self): + """ + Returns a list of tuples, such that each element of the list + is a tuple of the form (shardId, name, population, + welcomeValleyPopulation) for all the shards believed to be + currently up and running, and accepting avatars. + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + list = [] + for s in self.activeDistrictMap.values(): + if s.available: + list.append( + (s.doId, s.name, s.avatarCount, + s.newAvatarCount)) + + return list + + + ######### General senders and handlers ######### + + def getPlayerAvatars(self): + return [i for i in self.doId2do.values() + if isinstance(i, DistributedPlayer)] + + if 0: + #Roger wants to remove this + def handleQueryOneFieldResp(self, di): + doId = di.getUint32() + fieldId = di.getUint16() + context = di.getUint32() + import pdb + pdb.set_trace() + + if 0: + #Roger wants to remove this + def queryObjectFieldId(self, doId, fieldId, context=0): + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + # The message type + datagram.addUint16(CLIENT_QUERY_ONE_FIELD) + # The doId we're asking about + datagram.addUint32(doId) + # The field id + datagram.addUint16(fieldId) + # A context that can be used to index the response if needed + datagram.addUint32(context) + # Send the message + self.send(datagram) + + def queryObjectField(self, dclassName, fieldName, doId, context=0): + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + assert len(fieldName) > 0 + assert doId > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if dclass is not None: + fieldId = dclass.getFieldByName(fieldName).getNumber() + assert fieldId # is 0 a valid value? + self.queryObjectFieldId(doId, fieldId, context) + + def allocateDcFile(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # NOTE: do not move this function into DIRECT + # This method is deliberately misnamed. It should actually be + # called loadClientPassphrase(), but we are trying to make it + # hard for a casual hacker to find it. + + # This method loads the passphrase needed to decode the client + # private key for the server negotation. + + # This is the current passphrase seed. It's chosen to + # resemble an innocent string that's likely to be found in + # this file. + dcName = "Shard %s cannot be found." + + # We don't even use the above seed directly; instead, we md5 + # it first, and use the resulting md5 hex string as the actual + # passphrase. + hash = HashVal() + hash.hashString(dcName) + self.http.setClientCertificatePassphrase(hash.asHex()) + + def lostConnection(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + ClientRepositoryBase.lostConnection(self) + self.loginFSM.request("noConnection") + + def waitForDatabaseTimeout(self, extraTimeout = 0, requestName='unknown'): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + assert self.waitingForDatabase == None + OTPClientRepository.notify.debug( + 'waiting for database timeout %s at %s' % + (requestName, globalClock.getFrameTime())) + # If nothing happens within a few seconds, pop up a dialog to + # show we're still hanging on, and to give the user a chance + # to bail. + taskMgr.remove("waitingForDatabase") + # tick the clock to ensure we start counting from now, instead + # of from the beginning of the last frame (whenever that was). + globalClock.tick() + taskMgr.doMethodLater((OTPGlobals.DatabaseDialogTimeout + extraTimeout) * choice(__dev__, 10, 1), + self.__showWaitingForDatabase, + "waitingForDatabase", extraArgs=[requestName]) + + def __showWaitingForDatabase(self, requestName): + messenger.send("connectionIssue") + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + OTPClientRepository.notify.info("timed out waiting for %s at %s" % ( + requestName, globalClock.getFrameTime())) + dialogClass = OTPGlobals.getDialogClass() + self.waitingForDatabase = dialogClass( + text = OTPLocalizer.CRToontownUnavailable, + dialogName = "WaitingForDatabase", + buttonTextList = [OTPLocalizer.CRToontownUnavailableCancel], + style = OTPDialog.CancelOnly, + command = self.__handleCancelWaiting) + self.waitingForDatabase.show() + taskMgr.remove("waitingForDatabase") + taskMgr.doMethodLater(OTPGlobals.DatabaseGiveupTimeout, + self.__giveUpWaitingForDatabase, + "waitingForDatabase", extraArgs=[requestName]) + return Task.done + + def __giveUpWaitingForDatabase(self, requestName): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + OTPClientRepository.notify.info("giving up waiting for %s at %s" % ( + requestName, globalClock.getFrameTime())) + self.cleanupWaitingForDatabase() + self.loginFSM.request("noConnection") + return Task.done + + def cleanupWaitingForDatabase(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + if self.waitingForDatabase != None: + self.waitingForDatabase.hide() + self.waitingForDatabase.cleanup() + self.waitingForDatabase = None + taskMgr.remove("waitingForDatabase") + + def __handleCancelWaiting(self, value): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.loginFSM.request("shutdown") + + def setIsNotNewInstallation(self): + """ + Call this function whenever the user does something that would + make this installation no longer 'new' (i.e., creates an account, + logs in, etc.) + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + launcher.setIsNotNewInstallation() + + def renderFrame(self): + """ + force a frame render; this is useful for screen transitions, + where we destroy one screen and load the next; during the load, + we don't want the user staring at the old screen + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + + # Make sure any textures are preloaded before we render. + gsg = base.win.getGsg() + if gsg: + render2d.prepareScene(gsg) + + base.graphicsEngine.renderFrame() + + def refreshAccountServerDate(self, forceRefresh=0): + """ re-get the account server date + if forceRefresh != 0, will unconditionally perform the get + returns None on success + """ + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + try: + self.accountServerDate.grabDate(force=forceRefresh) + except TTAccount.TTAccountException, e: + self.notify.debug(str(e)) + return 1 + + + ################################################## + # Period Timer functions + ################################################## + + def resetPeriodTimer(self, secondsRemaining): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Resets the period counter to the indicated number of seconds + # of gameplay remaining on the clock. Certain users may be + # allowed only a limited amount of time in the game per month; + # this limit is told us by the server when we log in. + # + # If secondsRemaining is None, it implies there is no limit. + + assert self.periodTimerStarted == None + self.periodTimerExpired = 0 + self.periodTimerSecondsRemaining = secondsRemaining + + def recordPeriodTimer(self, task): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Periodically sets the time remaining time in the windows registry + # so the DMC client can query it for its own display + freq = 60.0 # How often to record in seconds + # Write it to the registry using the launcher interface + elapsed = globalClock.getRealTime() - self.periodTimerStarted + # Subtract out this dolater time from the running time + self.runningPeriodTimeRemaining = self.periodTimerSecondsRemaining - elapsed + self.notify.debug("periodTimeRemaining: %s" % (self.runningPeriodTimeRemaining)) + launcher.recordPeriodTimeRemaining(self.runningPeriodTimeRemaining) + # Now spawn a dolater waiting for the next + taskMgr.doMethodLater(freq, self.recordPeriodTimer, "periodTimerRecorder") + return Task.done + + def startPeriodTimer(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Starts the period timer counting down the number of seconds + # till we need to boot the player out. + if self.periodTimerStarted == None and \ + self.periodTimerSecondsRemaining != None: + self.periodTimerStarted = globalClock.getRealTime() + taskMgr.doMethodLater(self.periodTimerSecondsRemaining, + self.__periodTimerExpired, + "periodTimerCountdown") + for warning in OTPGlobals.PeriodTimerWarningTime: + if self.periodTimerSecondsRemaining > warning: + taskMgr.doMethodLater(self.periodTimerSecondsRemaining - warning, + self.__periodTimerWarning, + "periodTimerCountdown") + # Initialize the running count of seconds remaining + self.runningPeriodTimeRemaining = self.periodTimerSecondsRemaining + # Kick off the period timer recording task + self.recordPeriodTimer(None) + + def stopPeriodTimer(self): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + # Stop the period counter. + if self.periodTimerStarted != None: + elapsed = globalClock.getRealTime() - self.periodTimerStarted + self.periodTimerSecondsRemaining -= elapsed + self.periodTimerStarted = None + taskMgr.remove("periodTimerCountdown") + # Also remove the recorder + taskMgr.remove("periodTimerRecorder") + + def __periodTimerWarning(self, task): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + base.localAvatar.setSystemMessage(0, OTPLocalizer.PeriodTimerWarning) + return Task.done + + def __periodTimerExpired(self, task): + assert self.notify.debugStateCall(self, 'loginFSM', 'gameFSM') + self.notify.info("User's period timer has just expired!") + self.stopPeriodTimer() + self.periodTimerExpired = 1 + self.periodTimerStarted = None + self.periodTimerSecondsRemaining = None + messenger.send("periodTimerExpired") + return Task.done + + + def handleMessageType(self, msgType, di): + if msgType == CLIENT_GO_GET_LOST: + self.handleGoGetLost(di) + elif msgType == CLIENT_HEARTBEAT: + self.handleServerHeartbeat(di) + elif msgType == CLIENT_SYSTEM_MESSAGE: + self.handleSystemMessage(di) + elif msgType == CLIENT_SYSTEMMESSAGE_AKNOWLEDGE: + self.handleSystemMessageAknowledge(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED: + self.handleGenerateWithRequired(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER: + self.handleGenerateWithRequiredOther(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER: + self.handleGenerateWithRequiredOtherOwner(di) + elif msgType == CLIENT_OBJECT_UPDATE_FIELD: + self.handleUpdateField(di) + elif msgType == CLIENT_OBJECT_DISABLE: + self.handleDisable(di) + elif msgType == CLIENT_OBJECT_DISABLE_OWNER: + self.handleDisable(di, ownerView=True) + elif msgType == CLIENT_OBJECT_DELETE_RESP: + self.handleDelete(di) + elif msgType == CLIENT_DONE_INTEREST_RESP: + self.gotInterestDoneMessage(di) + elif msgType == CLIENT_GET_STATE_RESP: + # TODO: is this message obsolete? + pass + elif msgType == CLIENT_OBJECT_LOCATION: + self.gotObjectLocationMessage(di) + elif msgType == CLIENT_SET_WISHNAME_RESP: + self.gotWishnameResponse(di) + else: + currentLoginState = self.loginFSM.getCurrentState() + if currentLoginState: + currentLoginStateName = currentLoginState.getName() + else: + currentLoginStateName = "None" + currentGameState = self.gameFSM.getCurrentState() + if currentGameState: + currentGameStateName = currentGameState.getName() + else: + currentGameStateName = "None" + ClientRepositoryBase.notify.warning( + "Ignoring unexpected message type: " + + str(msgType) + + " login state: " + + currentLoginStateName + + " game state: " + + currentGameStateName) + + def gotInterestDoneMessage(self, di): + # We just received this message from the server; decide if we + # should handle it immediately. + if self.deferredGenerates: + # No, we'd better wait, since some generates have been + # deferred. Instead, we'll queue up the message and + # handle it in sequence. + + # Make a copy of the dg and di. + dg = Datagram(di.getDatagram()) + di = DatagramIterator(dg, di.getCurrentIndex()) + + self.deferredGenerates.append((CLIENT_DONE_INTEREST_RESP, (dg, di))) + + else: + # We can handle it immediately. + self.handleInterestDoneMessage(di) + + def gotObjectLocationMessage(self, di): + # See gotInterestDoneMessage(), above. + if self.deferredGenerates: + dg = Datagram(di.getDatagram()) + di = DatagramIterator(dg, di.getCurrentIndex()) + di2 = DatagramIterator(dg, di.getCurrentIndex()) + doId = di2.getUint32() + if doId in self.deferredDoIds: + self.deferredDoIds[doId][3].append((CLIENT_OBJECT_LOCATION, (dg, di))) + else: + # if we don't have a deferred generate stored for this object, process it + # immediately + self.handleObjectLocation(di) + else: + self.handleObjectLocation(di) + + def sendWishName(self, avId, name): + # see setWishNameAnonymous + datagram = PyDatagram() + # Add a message type + datagram.addUint16(CLIENT_SET_WISHNAME) + # Put in the new doID + datagram.addUint32(avId) + # Put in desired name + datagram.addString(name) + # Have TCR Send the message because it has a server open + self.send(datagram) + + def sendWishNameAnonymous(self, name): + # use this to test a type-a-name submission without needing an avatar to test it on + self.sendWishName(0, name) + + def getWishNameResultMsg(self): + # handler must accept args [WishNameResult, avId, name] + return 'OTPCR.wishNameResult' + + def gotWishnameResponse(self, di): + avId = di.getUint32() + returnCode = di.getUint16() + pendingName = '' + approvedName = '' + rejectedName = '' + if returnCode == 0: + pendingName = di.getString() + approvedName = di.getString() + rejectedName = di.getString() + + if approvedName: + name = approvedName + elif pendingName: + name = pendingName + elif rejectedName: + name = rejectedName + else: + name = '' + + WNR = self.WishNameResult + if returnCode: + result = WNR.Failure + elif rejectedName: + result = WNR.Rejected + elif pendingName: + result = WNR.PendingApproval + elif approvedName: + result = WNR.Approved + + messenger.send(self.getWishNameResultMsg(), [result, avId, name]) + + def replayDeferredGenerate(self, msgType, extra): + """ Override this to do something appropriate with deferred + "generate" messages when they are replayed(). """ + + if msgType == CLIENT_DONE_INTEREST_RESP: + dg, di = extra + self.handleInterestDoneMessage(di) + elif msgType == CLIENT_OBJECT_LOCATION: + dg, di = extra + self.handleObjectLocation(di) + else: + ClientRepositoryBase.replayDeferredGenerate(self, msgType, extra) + + + @exceptionLogged(append=False) + def handleDatagram(self, di): + if self.notify.getDebug(): + print "ClientRepository received datagram:" + di.getDatagram().dumpHex(ostream) + + + msgType = self.getMsgType() + if msgType == 65535: + self.lostConnection() + return + + if self.handler == None: + self.handleMessageType(msgType, di) + else: + self.handler(msgType, di) + + # If we're processing a lot of datagrams within one frame, we + # may forget to send heartbeats. Keep them coming! + self.considerHeartbeat() + + def askAvatarKnown(self, avId): + #place holder, make code game specific + return 0 + + def hashFiles(self, pyc): + # Looks for extraneous .pyo, .pyc, or .py files on the Python + # path. We used to be vulnerable to these, but now they're + # harmless. Report 'em anyway. + + for dir in sys.path: + if dir == '': + dir = '.' + if os.path.isdir(dir): + for filename in os.listdir(dir): + if filename.endswith('.pyo') or \ + filename.endswith('.pyc') or \ + filename.endswith('.py') or \ + filename == 'library.zip': + pathname = Filename.fromOsSpecific(os.path.join(dir, filename)) + hv = HashVal() + hv.hashFile(pathname) + pyc.mergeWith(hv) + + def queueRequestAvatarInfo(self, avId): + pass + + def identifyFriend(self, doId):\ + assert False, 'Must override this in inheriting class' + + def identifyPlayer(self, playerId): + assert False, 'Must override this in inheriting class' + + def identifyAvatar(self, doId): + """ + Returns either an avatar, FriendInfo, + whichever we can find, to reference the indicated avatar doId. + """ + info = self.doId2do.get(doId) + if info: + return info + else: + info = self.identifyFriend(doId) + return info + + + def sendDisconnect(self): + if self.isConnected(): + # Tell the game server that we're going: + datagram = PyDatagram() + # Add message type + datagram.addUint16(CLIENT_DISCONNECT) + # Send the message + self.send(datagram) + self.notify.info("Sent disconnect message to server") + self.disconnect() + self.stopHeartbeat() + + # override per-game + def _isPlayerDclass(self, dclass): + return False + + # check if this is a player avatar in a location where they should not be + def _isValidPlayerLocation(self, parentId, zoneId): + return True + + # since the client is able to set the location of their avatar at will, we need + # to make sure we don't try to generate a player at the wrong time + # in particular this is a response to a hack where a player put their avatar in the + # zone that is set aside for DistributedDistricts; the avatar generate crashed when + # referencing localAvatar (since localAvatar hadn't been set up yet) + def _isInvalidPlayerAvatarGenerate(self, doId, dclass, parentId, zoneId): + if self._isPlayerDclass(dclass): + if not self._isValidPlayerLocation(parentId, zoneId): + base.cr.centralLogger.writeClientEvent( + 'got generate for player avatar %s in invalid location (%s, %s)' % ( + doId, parentId, zoneId, + )) + return True + return False + + def handleGenerateWithRequired(self, di): + parentId = di.getUint32() + zoneId = di.getUint32() + assert parentId == self.GameGlobalsId or parentId in self.doId2do + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + + if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId): + return + + dclass.startGenerate() + # Create a new distributed object, and put it in the dictionary + distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId) + dclass.stopGenerate() + + def handleGenerateWithRequiredOther(self, di): + parentId = di.getUint32() + zoneId = di.getUint32() + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + + dclass = self.dclassesByNumber[classId] + + if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId): + return + + deferrable = getattr(dclass.getClassDef(), 'deferrable', False) + if not self.deferInterval or self.noDefer: + deferrable = False + + now = globalClock.getFrameTime() + if self.deferredGenerates or deferrable: + # This object is deferrable, or there are already deferred + # objects in the queue (so all objects have to be held + # up). + if self.deferredGenerates or now - self.lastGenerate < self.deferInterval: + # Queue it for later. + assert(self.notify.debug("deferring generate for %s %s" % (dclass.getName(), doId))) + self.deferredGenerates.append((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId)) + + # Keep a copy of the datagram, and move the di to the copy + dg = Datagram(di.getDatagram()) + di = DatagramIterator(dg, di.getCurrentIndex()) + + self.deferredDoIds[doId] = ((parentId, zoneId, classId, doId, di), deferrable, dg, []) + if len(self.deferredGenerates) == 1: + # We just deferred the first object on the queue; + # start the task to generate it. + taskMgr.remove('deferredGenerate') + taskMgr.doMethodLater(self.deferInterval, self.doDeferredGenerate, 'deferredGenerate') + + else: + # We haven't generated any deferrable objects in a + # while, so it's safe to go ahead and generate this + # one immediately. + self.lastGenerate = now + self.doGenerate(parentId, zoneId, classId, doId, di) + + else: + self.doGenerate(parentId, zoneId, classId, doId, di) + + def handleGenerateWithRequiredOtherOwner(self, di): + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # parentId and zoneId are not relevant here + parentId = di.getUint32() + zoneId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + dclass.startGenerate() + # Create a new distributed object, and put it in the dictionary + distObj = self.generateWithRequiredOtherFieldsOwner(dclass, doId, di) + dclass.stopGenerate() + + def handleQuietZoneGenerateWithRequired(self, di): + # Special handler for quiet zone generates -- we need to filter + parentId = di.getUint32() + zoneId = di.getUint32() + assert parentId in self.doId2do + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + dclass.startGenerate() + distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId) + dclass.stopGenerate() + + def handleQuietZoneGenerateWithRequiredOther(self, di): + # Special handler for quiet zone generates -- we need to filter + parentId = di.getUint32() + zoneId = di.getUint32() + assert parentId in self.doId2do + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + dclass.startGenerate() + distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId) + dclass.stopGenerate() + + def handleDisable(self, di, ownerView=False): + # Get the DO Id + doId = di.getUint32() + if not self.isLocalId(doId): + # disable it. But we never disable our own objects. + self.disableDoId(doId, ownerView) + + def sendSetLocation(self, doId, parentId, zoneId): + datagram = PyDatagram() + datagram.addUint16(CLIENT_OBJECT_LOCATION) + datagram.addUint32(doId) + datagram.addUint32(parentId) + datagram.addUint32(zoneId) + self.send(datagram) + + def sendHeartbeat(self): + datagram = PyDatagram() + # Add message type + datagram.addUint16(CLIENT_HEARTBEAT) + # Send it! + self.send(datagram) + self.lastHeartbeat = globalClock.getRealTime() + # This is important enough to consider flushing immediately + # (particularly if we haven't run readerPollTask recently). + self.considerFlush() + + def isLocalId(self, id): + # compare against localAvatar + try: + return localAvatar.doId == id + except: + self.notify.debug("In isLocalId(), localAvatar not created yet") + return False diff --git a/otp/src/distributed/ObjectServer.py b/otp/src/distributed/ObjectServer.py new file mode 100644 index 0000000..a507958 --- /dev/null +++ b/otp/src/distributed/ObjectServer.py @@ -0,0 +1,19 @@ + +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedObject + +class ObjectServer(DistributedObject.DistributedObject): + """ + This is an object to represent the OTP Object Server itself. You might + not get a create for this object, but at some point you'll probably + make contact with it to start an AI or some such. + + The server version of this object is created by Roger's code. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ObjectServer") + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + def setName(self, name): + self.name=name diff --git a/otp/src/distributed/ObjectServerAI.py b/otp/src/distributed/ObjectServerAI.py new file mode 100644 index 0000000..bd6e97b --- /dev/null +++ b/otp/src/distributed/ObjectServerAI.py @@ -0,0 +1,34 @@ + +import sys + +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.distributed.DistributedObjectAI import DistributedObjectAI + +class ObjectServerAI(DistributedObjectAI): + """ + This is an object to represent the OTP Object Server itself. You might + not get a create for this object, but at some point you'll probably + make contact with it to start an AI or some such. + + The server version of this object is created by Roger's code. + """ + notify = directNotify.newCategory("ObjectServerAI") + + def __init__(self, air): + DistributedObjectAI.__init__(self, air) + + def delete(self): + self.air.removeDOFromTables(self) + DistributedObjectAI.delete(self) + + def setName(self, name): + self.name=name + + def setDcHash(self, dcHash): + self.dcHash=dcHash + if dcHash != self.air.hashVal: + print "\nBad DC Version compare -- hash value mismatch (district %s, otp_server %s)"%( + (self.air.hashVal, dcHash)) + sys.exit() + else: + print "DC hash matches." diff --git a/otp/src/distributed/ObjectServerUD.py b/otp/src/distributed/ObjectServerUD.py new file mode 100644 index 0000000..ca64aec --- /dev/null +++ b/otp/src/distributed/ObjectServerUD.py @@ -0,0 +1,34 @@ + +import sys + +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.distributed.DistributedObjectUD import DistributedObjectUD + +class ObjectServerUD(DistributedObjectUD): + """ + This is an object to represent the OTP Object Server itself. You might + not get a create for this object, but at some point you'll probably + make contact with it to start an UD or some such. + + The server version of this object is created by Roger's code. + """ + notify = directNotify.newCategory("ObjectServerUD") + + def __init__(self, air): + DistributedObjectUD.__init__(self, air) + + def delete(self): + self.air.removeDOFromTables(self) + DistributedObjectUD.delete(self) + + def setName(self, name): + self.name=name + + def setDcHash(self, dcHash): + self.dcHash=dcHash + if dcHash != self.air.hashVal: + print "\nBad DC Version compare -- hash value mismatch (district %s, otp_server %s)"%( + (self.air.hashVal, dcHash)) + sys.exit() + else: + print "DC hash matches." diff --git a/otp/src/distributed/OtpDoGlobals.py b/otp/src/distributed/OtpDoGlobals.py new file mode 100644 index 0000000..d413735 --- /dev/null +++ b/otp/src/distributed/OtpDoGlobals.py @@ -0,0 +1,161 @@ +""" +Contains otp specific network message types. +""" + +from direct.distributed.MsgTypes import * + + +#----------------------------------------------------------------------------- +# Global Distributed Object ID's: + + +# Distributed object IDs from 4000 to 4599 are assigned by Roger: +OTP_DO_ID_SERVER_ROOT = 4007 # The root of the whole graph +OTP_DO_ID_FRIEND_MANAGER = 4501 # Old global friend manager id +OTP_DO_ID_LEADERBOARD_MANAGER = 4502 + +# Distributed object or channel IDs from 4600 to 4699 are assigned by Schuyler: +OTP_DO_ID_SERVER = 4600 # Server Status + +OTP_DO_ID_UBER_DOG = 4601 + +OTP_CHANNEL_AI_AND_UD_BROADCAST = 4602 +OTP_CHANNEL_UD_BROADCAST = 4603 +OTP_CHANNEL_AI_BROADCAST = 4604 +# fyi, the client broadcast channel is in direct/src/ai/AIMsgTypes.py + +OTP_NET_MSGR_CHANNEL_ID_ALL_AI = 4605 +OTP_NET_MSGR_CHANNEL_ID_UBER_DOG =4606 +OTP_NET_MSGR_CHANNEL_ID_AI_ONLY = 4607 + +OTP_DO_ID_COMMON = 4615 # Global objects shared across toontwon, pirates, et al. +OTP_DO_ID_GATEWAY = 4616 # Root of Gateway +OTP_DO_ID_PIRATES = 4617 # Root of Pirates +OTP_DO_ID_TOONTOWN = 4618 # Root of Toontown +OTP_DO_ID_FAIRIES = 4619 # Root of Fairies +OTP_DO_ID_CARS = 4620 # Root of Cars + +OTP_DO_ID_AVATARS = 4630 # Look under the avatarId zone for the avaatar +OTP_DO_ID_FRIENDS = 4640 # Look under the avatarId zone for friend links +OTP_DO_ID_GUILDS = 4650 # Look under a guildId zone for a guild +OTP_DO_ID_ESCROW = 4660 # Look under the avatarId zone for pending trades + +OTP_DO_ID_PIRATES_AVATAR_MANAGER = 4674 +OTP_DO_ID_PIRATES_CREW_MANAGER = 4675 +OTP_DO_ID_PIRATES_INVENTORY_MANAGER = 4677 +OTP_DO_ID_PIRATES_SPEEDCHAT_RELAY = 4711 + + + + +OTP_DO_ID_PIRATES_SHIP_MANAGER = 4678 +OTP_DO_ID_PIRATES_TRAVEL_AGENT = 4679 +OTP_DO_ID_PIRATES_FRIENDS_MANAGER = 4680 + +OTP_DO_ID_CHAT_MANAGER = 4681 + +OTP_DO_ID_TOONTOWN_AVATAR_MANAGER = 4682 +OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER = 4683 +OTP_DO_ID_TOONTOWN_TEMP_STORE_MANAGER=4684 +OTP_DO_ID_TOONTOWN_SPEEDCHAT_RELAY = 4712 + +OTP_DO_ID_SWITCHBOARD_MANAGER = 4685 +OTP_DO_ID_AVATAR_FRIENDS_MANAGER = 4686 +OTP_DO_ID_PLAYER_FRIENDS_MANAGER = 4687 +OTP_DO_ID_CENTRAL_LOGGER = 4688 + +OTP_DO_ID_CARS_AVATAR_MANAGER = 4689 + +OTP_DO_ID_TOONTOWN_MAIL_MANAGER = 4690 +OTP_DO_ID_TOONTOWN_PARTY_MANAGER = 4691 + +OTP_DO_ID_TOONTOWN_RAT_MANAGER = 4692 + +OTP_DO_ID_STATUS_DATABASE = 4693 +OTP_DO_ID_TOONTOWN_AWARD_MANAGER = 4694 + +OTP_DO_ID_TOONTOWN_CODE_REDEMPTION_MANAGER = 4695 +OTP_DO_ID_TOONTOWN_IN_GAME_NEWS_MANAGER = 4696 + +OTP_DO_ID_TOONTOWN_NON_REPEATABLE_RANDOM_SOURCE = 4697 + +OTP_DO_ID_AI_TRADE_AVATAR = 4698 + +OTP_DO_ID_PIRATES_MATCH_MAKER = 4700 +OTP_DO_ID_PIRATES_GUILD_MANAGER = 4701 +OTP_DO_ID_PIRATES_AWARD_MAKER = 4702 +OTP_DO_ID_PIRATES_CODE_REDEMPTION = 4703 +OTP_DO_ID_PIRATES_SETTINGS_MANAGER = 4704 +OTP_DO_ID_PIRATES_HOLIDAY_MANAGER = 4705 +OTP_DO_ID_PIRATES_CREW_MATCH_MANAGER =4706 + +OTP_DO_ID_PIRATES_AVATAR_ACCESSORIES_MANAGER = 4710 + +# Note that 4711 and 4712 are taken +OTP_DO_ID_TOONTOWN_CPU_INFO_MANAGER = 4713 + +# Contiguous ID space makes renderer operation easier, grabbing a block of 10 for now! +OTP_DO_ID_SNAPSHOT_DISPATCHER = 4800 +OTP_DO_ID_SNAPSHOT_RENDERER = 4801 +OTP_DO_ID_SNAPSHOT_RENDERER_01 = 4801 +OTP_DO_ID_SNAPSHOT_RENDERER_02 = 4802 +OTP_DO_ID_SNAPSHOT_RENDERER_03 = 4803 +OTP_DO_ID_SNAPSHOT_RENDERER_04 = 4804 +OTP_DO_ID_SNAPSHOT_RENDERER_05 = 4805 +OTP_DO_ID_SNAPSHOT_RENDERER_06 = 4806 +OTP_DO_ID_SNAPSHOT_RENDERER_07 = 4807 +OTP_DO_ID_SNAPSHOT_RENDERER_08 = 4808 +OTP_DO_ID_SNAPSHOT_RENDERER_09 = 4809 +OTP_DO_ID_SNAPSHOT_RENDERER_10 = 4810 +OTP_DO_ID_SNAPSHOT_RENDERER_11 = 4811 +OTP_DO_ID_SNAPSHOT_RENDERER_12 = 4812 +OTP_DO_ID_SNAPSHOT_RENDERER_13 = 4813 +OTP_DO_ID_SNAPSHOT_RENDERER_14 = 4814 +OTP_DO_ID_SNAPSHOT_RENDERER_15 = 4815 +OTP_DO_ID_SNAPSHOT_RENDERER_16 = 4816 +OTP_DO_ID_SNAPSHOT_RENDERER_17 = 4817 +OTP_DO_ID_SNAPSHOT_RENDERER_18 = 4818 +OTP_DO_ID_SNAPSHOT_RENDERER_19 = 4819 +OTP_DO_ID_SNAPSHOT_RENDERER_20 = 4820 + + +OTP_DO_ID_PIRATES_INVENTORY_MANAGER_BASE = 5001 + +#----------------------------------------------------------------------------- +# Zone IDs are independent from distributed object IDs (0 to 0xffffffff): +OTP_ZONE_ID_INVALID = 0 # invalid zone id (like None or NULL). +OTP_ZONE_ID_OLD_QUIET_ZONE = 1 # obsolete/depreciated +OTP_ZONE_ID_MANAGEMENT = 2 # was uber zone, serves similar role with new name +OTP_ZONE_ID_DISTRICTS = 3 # districts/shards within a game +OTP_ZONE_ID_DISTRICTS_STATS = 4 # Were the district Stats Items are located +OTP_ZONE_ID_ELEMENTS = 5 # a collection of distributed objects, e.g. members in a crew + +# OTP_ZONE_ID_ 100000??? to ?????? # avatar ID zones +# OTP_ZONE_ID_ ?? to ??? # guild ID zones +# OTP_ZONE_ID_ ?? to ??? # old style toontown zones + +#----------------------------------------------------------------------------- + +OTP_NET_MESSENGER_CHANNEL = (OTP_DO_ID_UBER_DOG <<32) + OTP_ZONE_ID_MANAGEMENT + +#----------------------------------------------------------------------------- +# Notes to help skyler remember how this works (not meant as readable comments): +# +# example paths to objects in zone: +# /4007,2/4616,3/dist1,zoneN/zoneObjects +# /4007,2/4616,3/dist2,zoneN/zoneObjects +# /4007,2/4616,3/dist3,zoneN/zoneObjects +# /4007,2/4616,3/dist4,zoneN/zoneObjects +# /4007,2/4616,3/dist5,zoneN/zoneObjects +# +# /4007,2/4660,avatarId/pendingTradeObject +# +# (OTP_DO_ID_SERVER_ROOT, OTP_ZONE_ID_COMMON) +# (OTP_DO_ID_COMMON, OTP_ZONE_ID_FRIENDS) +# +# (OTP_DO_ID_SERVER_ROOT, OTP_ZONE_ID_PIRATES) +# (OTP_DO_ID_PIRATES, OTP_ZONE_ID_DISTRICTS) +# (OTP_DO_ID_PIRATES, OTP_ZONE_ID_GUILDS) +# (OTP_DO_ID_PIRATES, OTP_ZONE_ID_ESCROW) +# +# ((4007, 4650), (avatarDoId, ...path to player guild is what?)) diff --git a/otp/src/distributed/PotentialAvatar.py b/otp/src/distributed/PotentialAvatar.py new file mode 100644 index 0000000..95cf288 --- /dev/null +++ b/otp/src/distributed/PotentialAvatar.py @@ -0,0 +1,25 @@ +"""PotentialAvatar module: contains the PotenialAvatarClass""" + +class PotentialAvatar: + + def __init__(self, id, names, dna, position, allowedName, + creator=1, shared=1, online=0, + wishState = "CLOSED", wishName = "", + defaultShard = 0, lastLogout = 0): + self.id = id + self.name = names[0] + self.dna = dna + self.avatarType = None + self.position = position + self.wantName = names[1] + self.approvedName = names[2] + self.rejectedName = names[3] + self.allowedName = allowedName + self.wishState = wishState + self.wishName = wishName + # Currently these are only used in Pirates + self.creator = creator + self.shared = shared + self.online = online + self.defaultShard = defaultShard + self.lastLogout = lastLogout diff --git a/otp/src/distributed/PotentialShard.py b/otp/src/distributed/PotentialShard.py new file mode 100644 index 0000000..58b847f --- /dev/null +++ b/otp/src/distributed/PotentialShard.py @@ -0,0 +1,12 @@ +"""PotentialShard module: contains the PotenialShardClass""" + +class PotentialShard: + + def __init__(self, id): + self.id = id + self.name = None + self.population = 0 + self.welcomeValleyPopulation = 0 + self.active = 1 + self.available = 1 + diff --git a/otp/src/distributed/Sources.pp b/otp/src/distributed/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/distributed/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/distributed/__init__.py b/otp/src/distributed/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/doc/.cvsignore b/otp/src/doc/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/doc/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/doc/ClientMessages.doc b/otp/src/doc/ClientMessages.doc new file mode 100644 index 0000000..481fbf3 Binary files /dev/null and b/otp/src/doc/ClientMessages.doc differ diff --git a/otp/src/doc/DCFields.doc b/otp/src/doc/DCFields.doc new file mode 100644 index 0000000..d657cc9 Binary files /dev/null and b/otp/src/doc/DCFields.doc differ diff --git a/otp/src/doc/InGameEditor.txt b/otp/src/doc/InGameEditor.txt new file mode 100644 index 0000000..6e0f96f --- /dev/null +++ b/otp/src/doc/InGameEditor.txt @@ -0,0 +1,97 @@ +In-Game Editor +============== + +::OVERVIEW +========== + +The In-Game Editor allows level designers to populate a level with +'entities', such as moving platforms and crates. It also allows level +designers to change, add, or remove entities in real-time, in the game +engine, providing instant feedback on changes in a multiplayer setting. + +::SETUP +======= + +Before starting the editor, you must set 'level-edit-username' in your +Config.prc, e.g. + +level-edit-username darren + +Your username must be listed in the 'username2entIdBase' dictionary in +$OTP/src/level/EditorGlobals.py. If you add a new username, be sure to +follow the conventions documented in that file. + +You will need magic words, and you need to be running in the dev +environment (want-dev 1, normally on by default in +$TOONTOWN/src/configfiles/Configrc). + +::RUNNING THE EDITOR +==================== + +Currently the IGE is used for editing the Toontown Sellbot Factory and the +Toontown Cashbot Mint. To start the editor, enter one of these areas and +run the ~edit magic word. (Note that in the Mint, you must be actually +standing in the room that you want to edit before running ~edit, since each +room is edited independently.) + +After a brief delay, you should see a 'Direct Session Panel' window and an +'In-Game Level Editor' window. We are only concerned with the latter +window; for more information on DIRECT, consult the documents in +$DIRECT/src/doc. + +The editor window is divided vertically into two panes. The left-hand pane +displays a tree view of the entities that exist within the level, and the +right-hand pane displays the attributes/properties of whatever entity is +selected in the tree view. + +TIP: When you select an entity in the tree view, its physical +representation in the game flashes momentarily. + +To insert a new entity, first decide which entity you would like to be the +'parent' of the new object, and select that parent in the tree view. Then +open the 'Entity' menu, and select the name of the type of entity you want +to insert. The Entity menu also allows you to duplicate and delete the +currently-selected entity. + +Once you have selected an entity in the tree view, you may modify its +attributes using the controls that appear in the right-hand pane. A typical +entity that has a visible representation will have the standard Panda +pos/hpr/scale scenegraph attributes. You may edit components of the vectors +directly, or you may call up a DIRECT Placer Panel by clicking on the word +('pos', for instance) and selecting 'Place...' + +NOTE: When editing text-entry attributes, be sure to press +. Otherwise the new value will not stick. + +Other attributes present various interfaces to allow editing, such as text +entries, radio button sets, filenames, entity references, etc. + +::SAVING YOUR WORK +================== + +To save your work at any time, select 'File->Save on AI'. If you have any +unsaved changes when you try to close the editor, you will be prompted to +save at that time. + +NOTE: The level spec will be saved in your source tree; you must now check the +changes in to revision control. See 'SPECS' below for more information +about level specs. + +::SPECS +======= + +The output of the editor is called a 'spec', which is nothing more than a +Python source file that contains a dictionary that maps entity IDs to sets +of attribute values. Each entity ID represents a unique entity in the +level. + +$OTP/src/level/SpecUtil.py contains utility functions to create specs, and +to update those specs when their associated level model changes. See the +instructions in SpecUtil.py for more information. + +::MISCELLANEOUS +=============== + +The 'Model' entity is very useful--use it to load any model and place it in +the level. Enable the entity's 'collisionsOnly' attribute to hide the +visible geometry but leave the collisions. diff --git a/otp/src/doc/Puppeteers.txt b/otp/src/doc/Puppeteers.txt new file mode 100644 index 0000000..7921d5b --- /dev/null +++ b/otp/src/doc/Puppeteers.txt @@ -0,0 +1,51 @@ + + +Puppeteer: A DistributedObject that takes control of the LocalAvatar +and presents a control scheme. Examples include: the battle system, fishing +spots, the trolley, elevator, etc. + +Control Scheme: A mapping of input from the player into actions of the +Avatar in game. Examples include: walk controls, swimming controls, fishing +GUI, battle interface, etc. + + + +Notes: + +A DistributedObject that wants to be a puppeteer should inherit from +DistributedPuppeteer[AI].py to pick up a common API. + +Only one puppeteer may be active at any given point in time. + +Puppeteers may offer several different controls schemes - but only one at a +time. For instance the DistributedToon puppeteer may offer a walk, swim, +and wait control scheme. + +When a puppeteer has registered as the active puppeteer, it can spawn +tasks, accept events, run FSM's, and generally use whatever tools it cares +to in order to get it's job done. Being a puppeteer does not imply any +particular coding architecture. + +Potential puppeteers register as the active puppeteer by sending the +setPuppeteer message on the LocalAvatar, passing in their own doId. This +message gets called from the AI as a p2p message to the LocalAvatar. When +the LocalAvatar gets this message, the old puppeteer is cleaned up and the +new one is started. + +Not all DistributedObjects are puppeteers. Butterflies and ice cream cones +are not puppeteers, for instance. + +It is still ok for other objects, tasks, and events to change avatar +properties and not be a puppeteer. For instance, the emote menu may cause +my avatar to play an animation. The emote system is not a puppeteer though +because it is not my primary activity controlling my avatar. It is just a +layer that the primary puppeteer has allowed to interject some +control. Care must be taken to ensure that other systems like emotes are +not stomping on puppeteer interactions. + +setAnimState as we know it will go away. If your puppeteer wants to play a +complex animation (like swimming) on the avatar, it should just do it +itself. If the puppeteer wants to do something common that other puppeteers +will use also (like walking, swimming, sitting) then the code should be +shared. + diff --git a/otp/src/friends/.cvsignore b/otp/src/friends/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/friends/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/friends/AvatarFriendInfo.py b/otp/src/friends/AvatarFriendInfo.py new file mode 100644 index 0000000..7d9e780 --- /dev/null +++ b/otp/src/friends/AvatarFriendInfo.py @@ -0,0 +1,67 @@ +from otp.avatar.AvatarHandle import AvatarHandle + + +class AvatarFriendInfo(AvatarHandle): + def __init__(self, + avatarName = "", + playerName = "", + playerId = 0, + onlineYesNo = 0, + openChatEnabledYesNo = 0, + openChatFriendshipYesNo = 0, + wlChatEnabledYesNo = 0): + self.avatarName = avatarName + self.playerName = playerName + self.playerId = playerId + self.onlineYesNo = onlineYesNo + self.openChatEnabledYesNo = openChatEnabledYesNo + self.openChatFriendshipYesNo = openChatFriendshipYesNo + self.wlChatEnabledYesNo = wlChatEnabledYesNo + + # just in case we dont get a NoAttribute error + # all values we're getting at this point is zero + self.understandableYesNo = self.isUnderstandable() + + def calcUnderstandableYesNo(self): + # ideally other classes should call isUnderstandable() + # but since they're accessing understandableYesNo + # let's just initially set it to the result of isUnderstandable() + self.understandableYesNo = self.isUnderstandable() + + def getName(self): + """ + AvatarHandle interface + """ + if self.avatarName: + return self.avatarName + elif self.playerName: + return self.playerName + else: + return "" + + def isUnderstandable(self): + """ + AvatarHandle interface + """ + result = False + try: + if self.openChatFriendshipYesNo: + # ok they are true friends and have blacklist chat + result = True + elif self.openChatEnabledYesNo and base.cr.openChatEnabled: + # they both have black list chat + result = True + elif self.wlChatEnabledYesNo and base.cr.whiteListChatEnabled: + result = True + + except: + # what to do with no base.cr? do we need this in uberdog or AI? + pass + + return result + + def isOnline(self): + """ + AvatarHandle interface + """ + return self.onlineYesNo diff --git a/otp/src/friends/AvatarFriendsDB.py b/otp/src/friends/AvatarFriendsDB.py new file mode 100644 index 0000000..7e3392e --- /dev/null +++ b/otp/src/friends/AvatarFriendsDB.py @@ -0,0 +1,175 @@ +import MySQLdb +import MySQLdb.constants.CR +import _mysql_exceptions +import datetime +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.distributed import OtpDoGlobals +from otp.uberdog.DBInterface import DBInterface + +SERVER_GONE_ERROR = MySQLdb.constants.CR.SERVER_GONE_ERROR +SERVER_LOST = MySQLdb.constants.CR.SERVER_LOST + +class AvatarFriendsDB(DBInterface): + """ + DB wrapper class for avatar friends! All SQL code for avatar friends should be in here. + """ + notify = directNotify.newCategory('AvatarFriendsDB') + + def __init__(self,host,port,user,passwd,dbname): + self.sqlAvailable = uber.sqlAvailable + if not self.sqlAvailable: + return + + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.dbname = self.processDBName(dbname) + try: + self.db = MySQLdb.connect(host=host, + port=port, + user=user, + passwd=passwd) + except _mysql_exceptions.OperationalError,e: + if __debug__: + self.notify.warning("Failed to connect to MySQL at %s:%d. Avatar friends DB is disabled."%(host,port)) + self.sqlAvailable = 0 + uber.sqlAvailable = 0 + return + + if __debug__: + self.notify.info("Connected to avatar friends MySQL db at %s:%d."%(host,port)) + + #temp hack for initial dev, create DB structure if it doesn't exist already + cursor = self.db.cursor() + try: + cursor.execute("CREATE DATABASE `%s`"%self.dbname) + if __debug__: + self.notify.info("Database '%s' did not exist, created a new one!"%self.dbname) + except _mysql_exceptions.ProgrammingError,e: + pass + + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + self.notify.debug("Using database '%s'"%self.dbname) + + try: + cursor.execute(""" + CREATE TABLE `avatarfriends` ( + `friendId1` int(32) UNSIGNED NOT NULL, + `friendId2` int(32) UNSIGNED NOT NULL, + `openChatYesNo` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`friendId1`,`friendId2`), + KEY `idxFriend1` (`friendId1`), + KEY `idxFriend2` (`friendId2`) + ) ENGINE=InnoDB DEFAULT CHARSET=latin1 + """) + if __debug__: + self.notify.info("Table avatarfriends did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + def reconnect(self): + if __debug__: + self.notify.debug("MySQL server was missing, attempting to reconnect.") + try: self.db.close() + except: pass + self.db = MySQLdb.connect(host=self.host, + port=self.port, + user=self.user, + passwd=self.passwd) + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port)) + + def disconnect(self): + if not self.sqlAvailable: + return + self.db.close() + self.db = None + + def getFriends(self,avatarId): + if not self.sqlAvailable: + return [] + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("SELECT * FROM avatarfriends WHERE friendId1=%s OR friendId2=%s",(avatarId,avatarId)) + except _mysql_exceptions.OperationalError,e: + if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("SELECT * FROM avatarfriends WHERE friendId1=%s OR friendId2=%s",(avatarId,avatarId)) + else: + raise e + + friends = cursor.fetchall() + + cleanfriends = {} + for f in friends: + if f['friendId1'] == avatarId: + cleanfriends[f['friendId2']] = f['openChatYesNo'] + else: + cleanfriends[f['friendId1']] = f['openChatYesNo'] + return cleanfriends + + def addFriendship(self,avatarId1,avatarId2,openChat=0): + if not self.sqlAvailable: + return + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + if avatarId1 < avatarId2: + cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId1,avatarId2,openChat)) + else: + cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId2,avatarId1,openChat)) + except _mysql_exceptions.OperationalError,e: + if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + cursor = MySQLdb.cursors.DictCursor(self.db) + if avatarId1 < avatarId2: + cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId1,avatarId2,openChat)) + else: + cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId2,avatarId1,openChat)) + else: + raise e + + self.db.commit() + + def removeFriendship(self,avatarId1,avatarId2): + if not self.sqlAvailable: + return + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + if avatarId1 < avatarId2: + cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId1,avatarId2)) + else: + cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId2,avatarId1)) + except _mysql_exceptions.OperationalError,e: + if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: # 'Lost connection to MySQL server during query' + self.reconnect() + cursor = MySQLdb.cursors.DictCursor(self.db) + if avatarId1 < avatarId2: + cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId1,avatarId2)) + else: + cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId2,avatarId1)) + else: + raise e + + self.db.commit() + + #for debugging only + def dumpFriendsTable(self): + assert self.db,"Tried to call dumpFriendsTable when DB was closed." + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("SELECT * FROM avatarfriends") + return cursor.fetchallDict() + + #for debugging only + def clearFriendsTable(self): + assert self.db,"Tried to call clearFriendsTable when DB was closed." + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("TRUNCATE TABLE avatarfriends") + self.db.commit() + + diff --git a/otp/src/friends/AvatarFriendsDB.sql b/otp/src/friends/AvatarFriendsDB.sql new file mode 100644 index 0000000..82e9044 --- /dev/null +++ b/otp/src/friends/AvatarFriendsDB.sql @@ -0,0 +1,9 @@ +CREATE TABLE `avatarfriends` ( + `friendId1` int(32) NOT NULL, + `friendId2` int(32) NOT NULL, + `openChatYesNo` tinyint(1) NOT NULL default '0', + PRIMARY KEY (`friendId1`,`friendId2`), + KEY `idxFriend1` (`friendId1`), + KEY `idxFriend2` (`friendId2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + diff --git a/otp/src/friends/AvatarFriendsManager.py b/otp/src/friends/AvatarFriendsManager.py new file mode 100644 index 0000000..7dc856a --- /dev/null +++ b/otp/src/friends/AvatarFriendsManager.py @@ -0,0 +1,150 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.uberdog.RejectCode import RejectCode +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLocalizer + + +class AvatarFriendsManager(DistributedObjectGlobal): + """ + The Avatar Friends Manager is a global object. + This object handles client requests on avatar-level (as opposed to player-level) friends. + + See Also: + "otp/src/friends/AvatarFriendsManagerUD.py" + "otp/src/friends/PlayerFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('AvatarFriendsManager') + + def __init__(self, cr): + assert self.notify.debugCall() + DistributedObjectGlobal.__init__(self, cr) + self.reset() + + def reset(self): + self.avatarFriendsList = set() + self.avatarId2Info = {} + self.invitedAvatarsList = [] + self.ignoredAvatarList = [] + + #Client only functions + def addIgnore(self, avId): + if avId not in self.ignoredAvatarList: + self.ignoredAvatarList.append(avId) + base.cr.centralLogger.writeClientEvent('ignoring %s' % (avId,)) + messenger.send("AvatarIgnoreChange") + + def removeIgnore(self, avId): + if avId in self.ignoredAvatarList: + self.ignoredAvatarList.remove(avId) + base.cr.centralLogger.writeClientEvent('stopped ignoring %s' % (avId,)) + messenger.send("AvatarIgnoreChange") + + def checkIgnored(self, avId): + """ + This should be checked before querying the user for + interaction or popping up guis requested by other + avatars + """ + return avId and avId in self.ignoredAvatarList + + #Client->UD request functions + + def sendRequestInvite(self,avId): + self.notify.debugCall() + self.sendUpdate("requestInvite", [avId]) + self.invitedAvatarsList.append(avId) + + def sendRequestRemove(self,avId): + self.notify.debugCall() + self.sendUpdate("requestRemove", [avId]) + if avId in self.invitedAvatarsList: + self.invitedAvatarsList.remove(avId) + + + #Functions called from UD + + def friendConsidering(self,avId): + self.notify.debugCall() + messenger.send(OTPGlobals.AvatarFriendConsideringEvent,[1,avId]) + + def invitationFrom(self,avId,avatarName): + self.notify.debugCall() + messenger.send(OTPGlobals.AvatarFriendInvitationEvent,[avId,avatarName]) + + def retractInvite(self,avId): + self.notify.debugCall() + messenger.send(OTPGlobals.AvatarFriendRetractInviteEvent,[avId]) + if avId in self.invitedAvatarsList: + self.invitedAvatarsList.remove(avId) + + def rejectInvite(self,avId,reason): + self.notify.debugCall() + messenger.send(OTPGlobals.AvatarFriendRejectInviteEvent,[avId,reason]) + if avId in self.invitedAvatarsList: + self.invitedAvatarsList.remove(avId) + + def rejectRemove(self,avId,reason): + self.notify.debugCall() + messenger.send(OTPGlobals.AvatarFriendRejectRemoveEvent,[avId,reason]) + + + + #Functions called from UD + + def updateAvatarFriend(self,avId,info): + if hasattr(info, "avatarId") and (not info.avatarId) and avId: + #info.avatarId wasn't being populated, I think this is fixed now + # though it's odd to have the data in two places + info.avatarId = avId + + assert self.notify.debugCall() + if not avId in self.avatarFriendsList: + self.avatarFriendsList.add(avId) + self.avatarId2Info[avId] = info #get the data there before we tell you it's there + messenger.send(OTPGlobals.AvatarFriendAddEvent,[avId,info]) + + if self.avatarId2Info[avId].onlineYesNo != info.onlineYesNo: + base.talkAssistant.receiveFriendUpdate(avId, info.getName(), info.onlineYesNo) + + self.avatarId2Info[avId] = info + messenger.send(OTPGlobals.AvatarFriendUpdateEvent,[avId,info]) + if avId in self.invitedAvatarsList: + self.invitedAvatarsList.remove(avId) + messenger.send(OTPGlobals.AvatarNewFriendAddEvent,[avId]) + + def removeAvatarFriend(self,avId): + assert self.notify.debugCall() + self.avatarFriendsList.remove(avId) + self.avatarId2Info.pop(avId,None) + messenger.send(OTPGlobals.AvatarFriendRemoveEvent,[avId]) + + + + + def setFriends(self, avatarIds): + self.notify.debugCall() + assert 0, "setFriends should not be sent to client." + + + + # client-called helper functions + + def isFriend(self,avId): + return self.isAvatarFriend(avId) + + def isAvatarFriend(self, avId): + return avId in self.avatarFriendsList + + def getFriendInfo(self,avId): + return self.avatarId2Info.get(avId) + + def countTrueFriends(self): + count = 0 + for id in self.avatarId2Info: + if self.avatarId2Info[id].openChatFriendshipYesNo: + count += 1 + return count diff --git a/otp/src/friends/AvatarFriendsManagerUD.py b/otp/src/friends/AvatarFriendsManagerUD.py new file mode 100644 index 0000000..48dde8b --- /dev/null +++ b/otp/src/friends/AvatarFriendsManagerUD.py @@ -0,0 +1,339 @@ +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from otp.otpbase import OTPGlobals +from otp.ai import AIMsgTypes +from otp.uberdog.UberDogUtil import ManagedAsyncRequest +from otp.uberdog.RejectCode import RejectCode + +from direct.directnotify.DirectNotifyGlobal import directNotify + +from otp.friends.AvatarFriendInfo import AvatarFriendInfo + + +class AvatarFriendsManagerUD(DistributedObjectGlobalUD): + """ + The Avatar Friends Manager is a global object. + This object handles client requests on avatar-level (as opposed to player-level) friends. + + See Also: + "otp/src/friends/AvatarFriendsManager.py" + "otp/src/friends/PlayerFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('AvatarFriendsManagerUD') + + def __init__(self, air): + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + + self.DBuser = uber.config.GetString("mysql-user", "ud_rw") + self.DBpasswd = uber.config.GetString("mysql-passwd", "r3adwr1te") + + self.DBhost = uber.config.GetString("avatarfriends-db-host","localhost") + self.DBport = uber.config.GetInt("avatarfriends-db-port",3306) + self.DBname = uber.config.GetString("avatarfriends-db-name","avatar_friends") + + from otp.friends.AvatarFriendsDB import AvatarFriendsDB + self.db = AvatarFriendsDB(host=self.DBhost, + port=self.DBport, + user=self.DBuser, + passwd=self.DBpasswd, + dbname=self.DBname) + + self.avatarId2FriendsList = {} + self.avatarId2Invitations = {} + self.avatarId2Unvitations = {} #an unvitation is a rejected (but not retracted) invitation + #self.avatarId2Name = {} + self.avatarId2Info = {} + + self.asyncRequests = {} + self.isAvatarOnline = {} + + + def announceGenerate(self): + assert self.notify.debugCall() + #self.accept("avatarOnline", self.avatarOnline, []) + self.accept("avatarOnlinePlusAccountInfo", self.avatarOnlinePlusAccountInfo, []) + self.accept("avatarOffline", self.avatarOffline, []) + DistributedObjectGlobalUD.announceGenerate(self) + self.sendUpdateToChannel( + AIMsgTypes.CHANNEL_CLIENT_BROADCAST, "online", []) + self.sendUpdateToChannel( + AIMsgTypes.OTP_CHANNEL_AI_AND_UD_BROADCAST, "online", []) + + + def delete(self): + assert self.notify.debugCall() + self.ignoreAll() + for i in self.asyncRequests.values(): + i.delete() + DistributedObjectGlobalUD.delete(self) + + #---------------------------------- + + def avatarOnlinePlusAccountInfo(self,avatarId,accountId,playerName, + playerNameApproved,openChatEnabled, + createFriendsWithChat,chatCodeCreation): + assert self.notify.debugCall() + assert avatarId + + self.notify.debug("avatarOnlinePlusAccountInfo") + if self.isAvatarOnline.has_key(avatarId): + assert self.notify.debug( + "\n\nWe got a duplicate avatar online notice %s"%(avatarId,)) + if avatarId and not self.isAvatarOnline.has_key(avatarId): + self.isAvatarOnline[avatarId]=True + self.avatarId2Info[avatarId] = AvatarFriendInfo(avatarName=str(avatarId), + playerName = playerName, + playerId = accountId, + onlineYesNo=1, + openChatEnabledYesNo=openChatEnabled,) + + # Get my friends list from the SQL DB + friends = self.db.getFriends(avatarId) + self.avatarId2FriendsList[avatarId]=friends + + if not hasattr(friends, "keys"): #check for error + self.notify.warning("self.db.getFriends(avatarId) has no keys %s" % (friends)) + return + + # Callback function for asynchronous avatar name fetch + def setName(avatarId, avatarId2info, friends, context, name): + if avatarId2info.has_key(avatarId): + avatarId2info[avatarId].avatarName = name[0] + for friendId in friends: + if self.isAvatarOnline.has_key(friendId): + if (friendId in self.avatarId2FriendsList) and (avatarId in self.avatarId2FriendsList[friendId]): + self.sendUpdateToAvatarId(friendId,"updateAvatarFriend", + [avatarId,self.getFriendView(friendId,avatarId)]) + self.sendExtraUpdates(friendId,avatarId) + + # Get my friends' info to me + for friend in friends.keys(): + friendId = friend + if not self.isAvatarOnline.has_key(friendId): + if not self.avatarId2Info.has_key(friendId): + self.avatarId2Info[friendId] = AvatarFriendInfo() + #fetch this friend's name from the gameDB since we don't have it yet + context=self.air.allocateContext() + dclassName="DistributedAvatarUD" + self.air.contextToClassName[context]=dclassName + self.acceptOnce( + "doFieldResponse-%s"%context,setName,[friendId,self.avatarId2Info,[avatarId,]]) + self.air.queryObjectField(dclassName,"setName",friendId,context) + else: + #print "AFMUD warning: info entry found for offline friend" + self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[friendId,self.getFriendView(avatarId,friendId)]) + self.sendExtraUpdates(avatarId,friendId) + else: + assert self.avatarId2Info.has_key(friendId) + self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[friendId,self.getFriendView(avatarId,friendId)]) + self.sendExtraUpdates(avatarId,friendId) + + + # Get my info to my friends + context=self.air.allocateContext() + dclassName="DistributedAvatarUD" + self.air.contextToClassName[context]=dclassName + self.acceptOnce( + "doFieldResponse-%s"%(context,), + setName, [avatarId, self.avatarId2Info, friends.keys()]) + self.air.queryObjectField( + dclassName, "setName", avatarId, context) + + def getFriendView(self, viewerId, friendId): + info = self.avatarId2Info[friendId] + assert self.avatarId2FriendsList.has_key(viewerId), "avatarId2FriendsList has no key %d" % viewerId + assert self.avatarId2FriendsList[viewerId].has_key(friendId), "avatarId2FriendsList[%d] has no key %d" % (viewerId, friendId) + info.openChatFriendshipYesNo = self.avatarId2FriendsList[viewerId][friendId] + if info.openChatFriendshipYesNo or \ + (info.openChatEnabledYesNo and \ + self.avatarId2Info[viewerId].openChatEnabledYesNo): + info.understandableYesNo = 1 + else: + info.understandableYesNo = 0 + return info + + def sendExtraUpdates(self,destId,aboutId): + pass + + @report(types = ['args'], dConfigParam = 'orphanedavatar') + def avatarOffline(self, avatarId): + """ + Is called from handleAvatarUsage when the avatar leaves the game. + + Also is called from DistributedAvatarManagerUD when it detects + an orphaned avatar in the world. + """ + assert self.notify.debugCall() + self.isAvatarOnline.pop(avatarId,None) + + if self.avatarId2Info.has_key(avatarId): + self.avatarId2Info[avatarId].onlineYesNo = 0 + + if avatarId: + friendsList = self.avatarId2FriendsList.get(avatarId, None) + if friendsList is not None and self.avatarId2Info.has_key(avatarId): + for friend in friendsList: + self.sendUpdateToAvatarId( + friend, "updateAvatarFriend", [avatarId,self.avatarId2Info[avatarId]]) + invitations = self.avatarId2Invitations.pop(avatarId, []) + for invitee in invitations: + self.sendUpdateToAvatarId( + invitee, "retractInvite", [avatarId]) + + self.avatarId2FriendsList.pop(avatarId,None) + self.avatarId2Info.pop(avatarId,None) + + +#---------------------------------------------------------------------- + + + # Functions called by the client + + def requestInvite(self, otherAvatarId): + avatarId = self.air.getAvatarIdFromSender() + assert self.notify.debugCall("avatarId:%s"%(avatarId,)) + invitations = self.avatarId2Invitations.setdefault(avatarId, []) + othersInvitations = self.avatarId2Invitations.setdefault( + otherAvatarId, []) + friendsList = self.avatarId2FriendsList.get(avatarId) + otherFriendsList = self.avatarId2FriendsList.get(otherAvatarId) + + def reject(reason): + self.sendUpdateToAvatarId( + avatarId, "rejectInvite", [otherAvatarId, reason]) + + + #clear unvitations + unvitations = self.avatarId2Unvitations.setdefault(avatarId, []) + if otherAvatarId in unvitations: + unvitations.remove(otherAvatarId) + + if friendsList is None: + reject(RejectCode.FRIENDS_LIST_NOT_HANDY) + elif otherFriendsList is None: + reject(RejectCode.INVITEE_NOT_ONLINE) + elif avatarId in self.avatarId2Unvitations.setdefault(otherAvatarId, []): #check for unvitation + reject(RejectCode.INVITATION_DECLINED) + elif otherAvatarId in invitations: + reject(RejectCode.ALREADY_INVITED) + elif avatarId == otherAvatarId: + reject(RejectCode.ALREADY_FRIENDS_WITH_SELF) + elif otherAvatarId in friendsList: + reject(RejectCode.ALREADY_YOUR_FRIEND) + elif avatarId in otherFriendsList: + reject(RejectCode.ALREADY_YOUR_FRIEND) + self.notify.error( + "Friends lists out of sync %s %s"%(avatarId, otherAvatarId)) + #should be adding player friends list? + + elif (len(friendsList) + + 0 + > OTPGlobals.MaxFriends): + reject(RejectCode.FRIENDS_LIST_FULL) + #should be adding player friends list? + elif (len(otherFriendsList) + + 0 + > OTPGlobals.MaxFriends): + reject(RejectCode.OTHER_FRIENDS_LIST_FULL) + elif avatarId in othersInvitations: + othersInvitations.remove(avatarId) + assert otherAvatarId not in invitations + + self.avatarId2FriendsList[avatarId][otherAvatarId] = 0 + if self.avatarId2FriendsList.has_key(otherAvatarId): + self.avatarId2FriendsList[otherAvatarId][avatarId] = 0 + + #update the friends database + try: + self.db.addFriendship(avatarId,otherAvatarId) + except: + pass #HACK for testing + + self.air.writeServerEvent('friendAccept', avatarId, '%s' % otherAvatarId) + + #tell them they're friends and give presence info, includes online status + self.sendUpdateToAvatarId(otherAvatarId,"updateAvatarFriend",[avatarId,self.getFriendView(otherAvatarId,avatarId)]) + self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[otherAvatarId,self.getFriendView(avatarId,otherAvatarId)]) + self.sendExtraUpdates(avatarId,otherAvatarId) + self.sendExtraUpdates(otherAvatarId,avatarId) + + else: + invitations.append(otherAvatarId) + # Tell the other guy we're inviting him! + self.air.writeServerEvent('friendInvite', avatarId, '%s' % otherAvatarId) + self.sendUpdateToAvatarId(avatarId, "friendConsidering", [otherAvatarId]) + self.sendUpdateToAvatarId(otherAvatarId, "invitationFrom", [avatarId,self.avatarId2Info[avatarId].avatarName]) + + + def requestRemove(self, otherAvatarId): + """ + Call this function if you want to retract an invitation you've + made, or to decline an invitation from otherAvatarId, or to + remove an existing friend from your friends list. + + otherAvatarId may be online or offline. + """ + avatarId = self.air.getAvatarIdFromSender() + self.air.writeServerEvent('friendRemove', avatarId, '%s' % otherAvatarId) + friendsList = self.avatarId2FriendsList.get(avatarId,None) + + if friendsList is None: + friendsList = self.db.getFriends(avatarId) + self.avatarId2FriendsList[avatarId] = friendsList + + assert self.notify.debugCall("avatarId:%s"%(avatarId,)) + + def reject(reason): + self.sendUpdateToAvatarId( + avatarId, "rejectRemove", [otherAvatarId, reason]) + + invitations = self.avatarId2Invitations.setdefault(avatarId, []) + if otherAvatarId in invitations: + # The other avatar was only invited and had not yet accepted + self.sendUpdateToAvatarId(otherAvatarId, "retractInvite", [avatarId]) + invitations.remove(otherAvatarId) + assert otherAvatarId not in invitations + assert otherAvatarId not in friendsList + return + else: # create an unvitation + unvitations = self.avatarId2Unvitations.setdefault(avatarId, []) + if otherAvatarId in unvitations: + pass + else: + unvitations.append(otherAvatarId) + + othersInvitations = self.avatarId2Invitations.setdefault(otherAvatarId, []) + if avatarId in othersInvitations: + # I was only invited and had not yet accepted + self.sendUpdateToAvatarId( + otherAvatarId, "rejectInvite", + [avatarId, RejectCode.INVITATION_DECLINED]) + othersInvitations.remove(avatarId) + assert avatarId not in othersInvitations + assert otherAvatarId not in friendsList + return + + if otherAvatarId not in friendsList: + reject(RejectCode.ALREADY_NOT_YOUR_FRIEND) + else: + if self.avatarId2FriendsList.has_key(avatarId): + self.avatarId2FriendsList[avatarId].pop(otherAvatarId,None) + if self.avatarId2FriendsList.has_key(otherAvatarId): + self.avatarId2FriendsList[otherAvatarId].pop(avatarId,None) + self.db.removeFriendship(avatarId,otherAvatarId) + self.sendUpdateToAvatarId(avatarId,"removeAvatarFriend",[otherAvatarId]) + self.sendUpdateToAvatarId(otherAvatarId,"removeAvatarFriend",[avatarId]) + + + def updateAvatarName(self, avatarId, avatarName): + if self.avatarId2Info.has_key(avatarId): + self.avatarId2Info[avatarId].avatarName = avatarName + friends = self.avatarId2FriendsList.get(avatarId,[]) + for friendId in friends: + if self.isAvatarOnline.has_key(friendId): + self.sendUpdateToAvatarId(friendId,"updateAvatarFriend", + [avatarId,self.getFriendView(friendId,avatarId)]) + self.sendExtraUpdates(friendId,avatarId) diff --git a/otp/src/friends/EmailInvite.py b/otp/src/friends/EmailInvite.py new file mode 100644 index 0000000..a03bf65 --- /dev/null +++ b/otp/src/friends/EmailInvite.py @@ -0,0 +1,88 @@ +from otp.distributed import OtpDoGlobals +from direct.directnotify.DirectNotifyGlobal import directNotify + +import smtplib, socket +from email import MIMEImage +from email import MIMEMultipart +from email import MIMEText + +class EmailInvite: + # System for sending an email invite + + notify = directNotify.newCategory('EmailInvite') + + def __init__(self, smtpHost, emailTemplate): + self.smtpAvailable = uber.smtpAvailable + if not self.smtpAvailable: + self.notify.debug('SMTP Server not available') + return + + self.smtpHost = smtpHost + self.emailTemplate = emailTemplate + self.mailServer = smtplib.SMTP(smtpHost) + + # Stitch together an email to send to the toAddr + # Open the email template + + fhndl = open(self.emailTemplate, 'rb') + self.messageBody = fhndl.read() + fhndl.close() + + # Try opening a connection to the server + + self.connect() + + def connect(self): + if not self.smtpAvailable: + return + try: + self.mailServer.connect() + except socket.error: + self.notify.warning('Failed to connect to SMTP server at %s. E-Mailing invites disabled.' % (self.smtpHost)) + self.smtpAvailable = 0 + + def disconnect(self): + if not self.smtpAvailable: + return + self.mailServer.close() + + def sendEmailInvite(self, fromAddr, toAddr, subject, fromAvName, inviteCode): + if not self.smtpAvailable: + self.notify.warning('sendEmailInvite called, but SMTP is not available') + return + + msg = MIMEText.MIMEText(self.messageBody % (fromAvName, inviteCode)) + + # Set the Subject field + msg['Subject'] = subject + # Set the From Field + msg['From'] = fromAddr + # Set the To Field + msg['To'] = toAddr + + # Now send the message + outbound = msg.as_string() + print 'The ougoing message is: %s' % (outbound) + + try: + self.mailServer.sendmail(fromAddr, [toAddr], outbound) + except SMTPServerDisconnected: + # Server is disconnected. Reconnect to server and try request again + self.notify.warning('SMTP Server Disconnected, Trying to send again') + self.connect() + self.sendEmailInvite(fromAddr, toAddr, subject, fromUserName, inviteCode) + except SMTPRecipientsRefused: + # Recipient Address Refused. + self.notify.warning('SMTP Recipent(s) Refused: %s' % toAddr) + + except SMTPSenderRefused: + # Sender or data is considered bad + self.notify.warning('SMTP Sender or Data Refused: %s' % fromAddr) + + except SMTPHeloError: + # The server refused our "HELO" message + self.notify.warning('The server refused our "HELO" message') + + except SMTPException: + self.notify.warning('SMTP Failed, Trying to send again') + self.sendEmailInvite(fromAddr, toAddr, subject, fromUserName, inviteCode) diff --git a/otp/src/friends/FriendInfo.py b/otp/src/friends/FriendInfo.py new file mode 100644 index 0000000..422fdfc --- /dev/null +++ b/otp/src/friends/FriendInfo.py @@ -0,0 +1,89 @@ +from otp.avatar.AvatarHandle import AvatarHandle +#struct FriendInfo +#{ +# string avatarName; +# string playerName; +# uint8 onlineYesNo; +# uint8 understandableYesNo; +# uint16 chatLevel; +# uint32 location; +# uint32 sublocation; +# uint32 timestamp; +#}; + + + +class FriendInfo(AvatarHandle): + def __init__(self, + avatarName = "", + playerName = "", + onlineYesNo = 0, + openChatEnabledYesNo = 0, + openChatFriendshipYesNo = 0, + wlChatEnabledYesNo = 0, + location = "", + sublocation = "", + timestamp = 0, + avatarId = 0, + friendPrivs = 0, + tokenPrivs = 0): + self.avatarName = avatarName + self.playerName = playerName + self.onlineYesNo = onlineYesNo + self.openChatEnabledYesNo = openChatEnabledYesNo + self.openChatFriendshipYesNo = openChatFriendshipYesNo + self.wlChatEnabledYesNo = wlChatEnabledYesNo + self.location = location + self.sublocation = sublocation + self.timestamp = timestamp + self.avatarId = avatarId + self.friendPrivs = friendPrivs + self.tokenPrivs = tokenPrivs + + # just in case we dont get a NoAttribute error + # all values we're getting at this point is zero + self.understandableYesNo = self.isUnderstandable() + + def calcUnderstandableYesNo(self): + # ideally other classes should call isUnderstandable() + # but since they're accessing understandableYesNo + # let's just initially set it to the result of isUnderstandable() + self.understandableYesNo = self.isUnderstandable() + + def getName(self): + """ + AvatarHandle interface + """ + if self.avatarName: + return self.avatarName + elif self.playerName: + return self.playerName + else: + return "" + + def isUnderstandable(self): + """ + AvatarHandle interface + """ + result = False + try: + if self.openChatFriendshipYesNo: + # ok they are true friends and have blacklist chat + result = True + elif self.openChatEnabledYesNo and base.cr.openChatEnabled: + # they both have black list chat + result = True + elif self.wlChatEnabledYesNo and base.cr.whiteListChatEnabled: + result = True + + except: + # what to do with no base.cr? do we need this in uberdog or AI? + pass + + return result + + def isOnline(self): + """ + AvatarHandle interface + """ + return self.onlineYesNo diff --git a/otp/src/friends/FriendManager.py b/otp/src/friends/FriendManager.py new file mode 100644 index 0000000..cbb137d --- /dev/null +++ b/otp/src/friends/FriendManager.py @@ -0,0 +1,290 @@ +from pandac.PandaModules import * +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import OTPGlobals + +class FriendManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("FriendManager") + + # We should never disable this guy. + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + self.__available = 0 + + # used for flexible request processing + # in toontown we are keeping a queue of friends requests and processing them + # once the avatar is available again + self.gameSpecificFunction = None + + ### Interface methods ### + + def setAvailable(self, available): + """setAvailable(self, bool available) + + Sets the 'available' flag. When this is true, the client is + deemed to be available to consider friendship requests, and + they will be allowed through. When this is false, the client + is deemed to be too busy to consider friendship requests, and + they will be sent back. + """ + self.__available = available + if self.__available and self.gameSpecificFunction: + self.gameSpecificFunction() + + def getAvailable(self): + return self.__available + + def setGameSpecificFunction(self, function): + self.gameSpecificFunction = function + + def executeGameSpecificFunction(self): + if self.__available and self.gameSpecificFunction: + self.gameSpecificFunction() + + + + ### DistributedObject methods ### + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + if base.cr.friendManager != None: + base.cr.friendManager.delete() + base.cr.friendManager = self + DistributedObject.DistributedObject.generate(self) + + def disable(self): + """ + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + #self.notify.warning("Hey! The FriendManager was disabled!") + base.cr.friendManager = None + DistributedObject.DistributedObject.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + #self.notify.warning("Hey! The FriendManager was deleted!") + self.gameSpecificFunction = None + base.cr.friendManager = None + DistributedObject.DistributedObject.delete(self) + + + ### Messages sent from inviter client to AI + + def up_friendQuery(self, inviteeId): + """friendQuery(self, int inviteeId) + + Sent by the inviter to the AI to initiate a friendship + request. + """ + self.sendUpdate('friendQuery', [inviteeId]) + self.notify.debug("Client: friendQuery(%d)" % (inviteeId)) + + def up_cancelFriendQuery(self, context): + """cancelFriendQuery(self, int context) + + Sent by the inviter to the AI to cancel a pending friendship + request. + """ + + self.sendUpdate('cancelFriendQuery', [context]) + self.notify.debug("Client: cancelFriendQuery(%d)" % (context)) + + + ### Messages sent from invitee client to AI + + def up_inviteeFriendConsidering(self, yesNo, context): + """inviteeFriendConsidering(self, bool yesNo, int context) + + Sent by the invitee to the AI to indicate whether the invitee + is able to consider the request right now. + + The responses are: + 0 - no + 1 - yes + 4 - the invitee is ignoring you. + 6 - invitee not accepting new friends + """ + + self.sendUpdate('inviteeFriendConsidering', [yesNo, context]) + self.notify.debug("Client: inviteeFriendConsidering(%d, %d)" % (yesNo, context)) + + def up_inviteeFriendResponse(self, yesNoMaybe, context): + """inviteeFriendResponse(self, int yesNoMaybe, int context) + + Sent by the invitee to the AI, following an affirmitive + response in inviteeFriendConsidering, to indicate whether or + not the user decided to accept the friendship. + + The responses are: + 0 - no + 1 - yes + 2 - unable to answer; e.g. entered a minigame or something. + 3 - the invitee has too many friends already. + """ + + self.sendUpdate('inviteeFriendResponse', [yesNoMaybe, context]) + self.notify.debug("Client: inviteeFriendResponse(%d, %d)" % (yesNoMaybe, context)) + + def up_inviteeAcknowledgeCancel(self, context): + """inviteeAcknowledgeCancel(self, int context) + + Sent by the invitee to the AI, in response to an + inviteeCancelFriendQuery message. This simply acknowledges + receipt of the message and tells the AI that it is safe to + clean up the context. + """ + + self.sendUpdate('inviteeAcknowledgeCancel', [context]) + self.notify.debug("Client: inviteeAcknowledgeCancel(%d)" % (context)) + + + + ### Messages sent from AI to inviter client + + def friendConsidering(self, yesNoAlready, context): + """friendConsidering(self, int yesNoAlready, int context) + + Sent by the AI to the inviter client to indicate whether the + invitee is able to consider the request right now. + + The responses are: + 0 - no + 1 - yes + 2 - the invitee is already your friend + 3 - attempt to befriend yourself + 4 - the invitee is ignoring you. + 6 - the invitee is not accepting friends. + """ + + self.notify.info("Roger Client: friendConsidering(%d, %d)" % (yesNoAlready, context)) + + messenger.send('friendConsidering', [yesNoAlready, context]) + + def friendResponse(self, yesNoMaybe, context): + """friendResponse(self, bool yesNoMaybe, int context) + + Sent by the AI to the inviter client, following an affirmitive + response in friendConsidering, to indicate whether or not the + user decided to accept the friendship. + + The responses are: + 0 - no + 1 - yes + 2 - unable to answer; e.g. entered a minigame or something. + 3 - the invitee has too many friends already. + """ + + self.notify.debug("Client: friendResponse(%d, %d)" % (yesNoMaybe, context)) + messenger.send('friendResponse', [yesNoMaybe, context]) + + + + ### Messages sent from AI to invitee client + + def inviteeFriendQuery(self, inviterId, inviterName, inviterDna, context): + """inviteeFriendQuery(self, int inviterId, inviterName, inviterDna, + int context) + + Sent by the AI to the invitee client to initiate a friendship + request from the indiciated inviter. The invitee client + should respond immediately with inviteeFriendConsidering, to + indicate whether the invitee is able to consider the + invitation right now. + """ + + self.notify.debug("Client: inviteeFriendQuery(%d, %s, dna, %d)" % (inviterId, inviterName, context)) + + # Immediately send a response back that indicates whether we + # can consider the invitation. + + if not hasattr(base, "localAvatar"): + # client is closing down... tell the AI 'no' + self.up_inviteeFriendConsidering(0, context) + return + + if inviterId in base.localAvatar.ignoreList: + # We're ignoring this naughty person + self.up_inviteeFriendConsidering(4, context) + return + + if (not base.localAvatar.acceptingNewFriends): + self.up_inviteeFriendConsidering(6, context) + return + + self.up_inviteeFriendConsidering(self.__available, context) + + # And consider the invitation if we can. + if self.__available: + messenger.send('friendInvitation', [inviterId, inviterName, + inviterDna, context]) + + def inviteeCancelFriendQuery(self, context): + """inviteeCancelFriendQuery(self, int context) + + Sent by the AI to the invitee client to initiate that the + inviter has rescinded his/her previous invitation by clicking + the cancel button. + """ + + self.notify.debug("Client: inviteeCancelFriendQuery(%d)" % (context)) + messenger.send('cancelFriendInvitation', [context]) + self.up_inviteeAcknowledgeCancel(context) + + + ### Messages involving secrets + + def up_requestSecret(self): + """ + Sent by the client to the AI to request a new "secret" for the + user. + """ + self.notify.warning("Sending Request") + self.sendUpdate('requestSecret', []) + + def requestSecretResponse(self, result, secret): + """requestSecret(self, int8 result, string secret) + + Sent by the AI to the client in response to requestSecret(). + result is one of: + + 0 - Too many secrets outstanding. Try again later. + 1 - Success. The new secret is supplied. + + """ + messenger.send('requestSecretResponse', [result, secret]) + + + def up_submitSecret(self, secret): + """submitSecret(self, string secret) + + Sent by the client to the AI to submit a "secret" typed in by + the user. + """ + self.sendUpdate('submitSecret', [secret]) + + def submitSecretResponse(self, result, avId): + """submitSecret(self, int8 result, int32 avId) + + Sent by the AI to the client in response to submitSecret(). + result is one of: + + 0 - Failure. The secret is unknown or has timed out. + 1 - Success. You are now friends with the indicated avId. + 2 - Failure. One of the avatars has too many friends already. + 3 - Failure. You just used up your own secret. + + """ + messenger.send('submitSecretResponse', [result, avId]) + + # Be invited by another avatar to be their friend. + diff --git a/otp/src/friends/FriendManagerAI.py b/otp/src/friends/FriendManagerAI.py new file mode 100644 index 0000000..6a6df84 --- /dev/null +++ b/otp/src/friends/FriendManagerAI.py @@ -0,0 +1,571 @@ +## from otp.ai.AIBaseGlobal import * +## from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI +## from direct.directnotify import DirectNotifyGlobal +## from otp.avatar import DistributedAvatarAI + +# all of this is commented out because the friend manager was moved +# to the OTP server + +# we need something for the AI DC parser to load +class FriendManagerAI(DistributedObjectAI.DistributedObjectAI): + pass +## # These structures record the invitations currently being handled. +## nextContext = 0 +## invites = {} +## inviters = {} +## invitees = {} + +## # This is the length of time, in seconds, to sit on a secret guess +## # before processing it. This serves to make it difficult to guess +## # passwords at random. +## SecretDelay = 1.0 + +## # This is the length of time that should elapse before we start to +## # forget who has declined friendships from whom. +## DeclineFriendshipTimeout = 600.0 + +## notify = DirectNotifyGlobal.directNotify.newCategory("FriendManagerAI") + +## # This subclass is used to record currently outstanding +## # in-the-game invitation requests. +## class Invite: +## def __init__(self, context, inviterId, inviteeId): +## self.context = context +## self.inviterId = inviterId +## self.inviteeId = inviteeId +## self.inviter = None +## self.invitee = None +## self.inviteeKnows = 0 +## self.sendSpecialResponse = 0 + +## def __init__(self, air): +## DistributedObjectAI.DistributedObjectAI.__init__(self, air) + +## # We maintain two maps of toons who have declined +## # friendships. We add entries to map1, and every ten +## # minutes, we roll map1 into map2. This provides a +## # timeout of ten to twenty minutes for a particular +## # rejection, and also prevents the maps from growing very +## # large in memory. +## self.declineFriends1 = {} +## self.declineFriends2 = {} +## self.lastRollTime = 0 + +## def generate(self): +## DistributedObjectAI.DistributedObjectAI.generate(self) + +## # The FriendManagerAI always listens for these events, which +## # will be sent in response to secret requests to the database, +## # via the AIR. +## self.accept("makeFriendsReply", self.makeFriendsReply) +## self.accept("requestSecretReply", self.__requestSecretReply) +## self.accept("submitSecretReply", self.__submitSecretReply) + +## def delete(self): +## self.ignore("makeFriendsReply") +## self.ignore("requestSecretReply") +## self.ignore("submitSecretReply") + +## # Clean up all outstanding secret request tasks. +## taskMgr.removeTasksMatching("secret-*") + +## DistributedObjectAI.DistributedObjectAI.delete(self) + + +## ### Messages sent from inviter client to AI + +## def friendQuery(self, inviteeId): +## """friendQuery(self, int inviterId, int inviteeId) + +## Sent by the inviter to the AI to initiate a friendship +## request. +## """ +## inviterId = self.air.getAvatarIdFromSender() +## invitee = self.air.doId2do.get(inviteeId) + +## # see if the inviteeId is valid +## if not invitee: +## self.air.writeServerEvent('suspicious', inviteeId, 'FriendManagerAI.friendQuery not on list') +## return + +## self.notify.debug("AI: friendQuery(%d, %d)" % (inviterId, inviteeId)) +## self.newInvite(inviterId, inviteeId) + +## def cancelFriendQuery(self, context): +## """cancelFriendQuery(self, int context) + +## Sent by the inviter to the AI to cancel a pending friendship +## request. +## """ + +## avId = self.air.getAvatarIdFromSender() +## self.notify.debug("AI: cancelFriendQuery(%d)" % (context)) + +## try: +## invite = FriendManagerAI.invites[context] +## except: +## # The client might legitimately try to cancel a context +## # that has already been cancelled. +## #self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.cancelFriendQuery unknown context') +## #FriendManagerAI.notify.warning('Message for unknown context ' + `context`) +## return + +## self.cancelInvite(invite) + + +## ### Messages sent from invitee client to AI + +## def inviteeFriendConsidering(self, response, context): +## """inviteeFriendConsidering(self, int response, int context) + +## Sent by the invitee to the AI to indicate whether the invitee +## is able to consider the request right now. + +## The responses are: +## 0 - no +## 1 - yes +## 4 - the invitee is ignoring you. +## """ +## self.notify.debug("AI: inviteeFriendConsidering(%d, %d)" % (response, context)) +## avId = self.air.getAvatarIdFromSender() + +## try: +## invite = FriendManagerAI.invites[context] +## except: +## self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeFriendConsidering unknown context') +## FriendManagerAI.notify.warning('Message for unknown context ' + `context`) +## return + +## if response == 1: +## self.inviteeAvailable(invite) +## else: +## self.inviteeUnavailable(invite, response) + +## def inviteeFriendResponse(self, yesNoMaybe, context): +## """inviteeFriendResponse(self, int yesNoMaybe, int context) + +## Sent by the invitee to the AI, following an affirmative +## response in inviteeFriendConsidering, to indicate whether or +## not the user decided to accept the friendship. +## """ + +## self.notify.debug("AI: inviteeFriendResponse(%d, %d)" % (yesNoMaybe, context)) +## avId = self.air.getAvatarIdFromSender() + +## try: +## invite = FriendManagerAI.invites[context] +## except: +## self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeFriendResponse unknown context') +## FriendManagerAI.notify.warning('Message for unknown context ' + `context`) +## return + +## if yesNoMaybe == 1: +## self.makeFriends(invite) +## else: +## self.noFriends(invite, yesNoMaybe) + +## def inviteeAcknowledgeCancel(self, context): +## """inviteeAcknowledgeCancel(self, int context) + +## Sent by the invitee to the AI, in response to an +## inviteeCancelFriendQuery message. This simply acknowledges +## receipt of the message and tells the AI that it is safe to +## clean up the context. +## """ + +## self.notify.debug("AI: inviteeAcknowledgeCancel(%d)" % (context)) +## avId = self.air.getAvatarIdFromSender() + +## try: +## invite = FriendManagerAI.invites[context] +## except: +## # The client might legitimately try to cancel a context +## # that has already been cancelled. +## #self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeAcknowledgeCancel unknown context') +## #FriendManagerAI.notify.warning('Message for unknown context ' + `context`) +## return + +## self.clearInvite(invite) + + +## ### Messages sent from AI to inviter client + +## def down_friendConsidering(self, recipient, yesNoAlready, context): +## """friendConsidering(self, DistributedObject recipient, +## int yesNoAlready, int context) + +## Sent by the AI to the inviter client to indicate whether the +## invitee is able to consider the request right now. + +## The responses are: +## # 0 - the invitee is busy +## # 2 - the invitee is already your friend +## # 3 - the invitee is yourself +## # 4 - the invitee is ignoring you. +## # 6 - the invitee not accepting friends +## """ + +## self.sendUpdateToAvatarId(recipient, "friendConsidering", [yesNoAlready, context]) +## self.notify.debug("AI: friendConsidering(%d, %d)" % (yesNoAlready, context)) + +## def down_friendResponse(self, recipient, yesNoMaybe, context): +## """friendResponse(self, DistributedOBject recipient, +## int yesNoMaybe, int context) + +## Sent by the AI to the inviter client, following an affirmitive +## response in friendConsidering, to indicate whether or not the +## user decided to accept the friendship. +## """ + +## self.sendUpdateToAvatarId(recipient, "friendResponse", [yesNoMaybe, context]) +## self.notify.debug("AI: friendResponse(%d, %d)" % (yesNoMaybe, context)) + + + +## ### Messages sent from AI to invitee client + +## def down_inviteeFriendQuery(self, recipient, inviterId, inviterName, +## inviterDna, context): +## """inviteeFriendQuery(self, DistributedObject recipient, +## int inviterId, string inviterName, +## AvatarDNA inviterDna, int context) + +## Sent by the AI to the invitee client to initiate a friendship +## request from the indiciated inviter. The invitee client +## should respond immediately with inviteeFriendConsidering, to +## indicate whether the invitee is able to consider the +## invitation right now. +## """ + +## self.sendUpdateToAvatarId(recipient, "inviteeFriendQuery", +## [inviterId, inviterName, +## inviterDna.makeNetString(), context]) +## self.notify.debug("AI: inviteeFriendQuery(%d, %s, dna, %d)" % (inviterId, inviterName, context)) + +## def down_inviteeCancelFriendQuery(self, recipient, context): +## """inviteeCancelFriendQuery(self, DistributedObject recipient, +## int context) + +## Sent by the AI to the invitee client to initiate that the +## inviter has rescinded his/her previous invitation by clicking +## the cancel button. +## """ + +## self.sendUpdateToAvatarId(recipient, "inviteeCancelFriendQuery", [context]) +## self.notify.debug("AI: inviteeCancelFriendQuery(%d)" % (context)) + + + +## ### Messages involving secrets + +## def requestSecret(self): +## """requestSecret(self) + +## Sent by the client to the AI to request a new "secret" for the +## user. +## """ +## avId = self.air.getAvatarIdFromSender() +## self.air.requestSecret(avId) + +## def down_requestSecretResponse(self, recipient, result, secret): +## """requestSecret(self, int8 result, string secret) + +## Sent by the AI to the client in response to requestSecret(). +## result is one of: + +## 0 - Too many secrets outstanding. Try again later. +## 1 - Success. The new secret is supplied. + +## """ +## self.sendUpdateToAvatarId(recipient, 'requestSecretResponse', [result, secret]) + +## def submitSecret(self, secret): +## """submitSecret(self, string secret) + +## Sent by the client to the AI to submit a "secret" typed in by +## the user. +## """ +## avId = self.air.getAvatarIdFromSender() + +## # We have to sit on this request for a few seconds before +## # processing it. This delay is solely to discourage password +## # guessing. +## taskName = "secret-" + str(avId) +## taskMgr.remove(taskName) +## if FriendManagerAI.SecretDelay: +## taskMgr.doMethodLater(FriendManagerAI.SecretDelay, +## self.continueSubmission, +## taskName, +## extraArgs = (avId, secret)) +## else: +## # No delay +## self.continueSubmission(avId, secret) + +## def continueSubmission(self, avId, secret): +## """continueSubmission(self, avId, secret) +## Finishes the work of submitSecret, a short time later. +## """ +## self.air.submitSecret(avId, secret) + +## def down_submitSecretResponse(self, recipient, result, avId): +## """submitSecret(self, int8 result, int32 avId) + +## Sent by the AI to the client in response to submitSecret(). +## result is one of: + +## 0 - Failure. The secret is unknown or has timed out. +## 1 - Success. You are now friends with the indicated avId. +## 2 - Failure. One of the avatars has too many friends already. +## 3 - Failure. You just used up your own secret. + +## """ +## self.sendUpdateToAvatarId(recipient, 'submitSecretResponse', [result, avId]) + + +## ### Support methods + +## def newInvite(self, inviterId, inviteeId): +## context = FriendManagerAI.nextContext +## FriendManagerAI.nextContext += 1 + +## invite = FriendManagerAI.Invite(context, inviterId, inviteeId) +## FriendManagerAI.invites[context] = invite + +## # If the invitee has previously (recently) declined a +## # friendship from this inviter, don't ask again. +## previous = self.__previousResponse(inviteeId, inviterId) +## if previous != None: +## self.inviteeUnavailable(invite, previous + 10) +## return + +## # If the invitee is presently being invited by someone else, +## # we don't even have to bother him. +## if FriendManagerAI.invitees.has_key(inviteeId): +## self.inviteeUnavailable(invite, 0) +## return + +## if invite.inviterId == invite.inviteeId: +## # You can't be friends with yourself. +## self.inviteeUnavailable(invite, 3) +## return + +## # If the inviter is already involved in some other context, +## # that one is now void. +## if FriendManagerAI.inviters.has_key(inviterId): +## self.cancelInvite(FriendManagerAI.inviters[inviterId]) + +## FriendManagerAI.inviters[inviterId] = invite +## FriendManagerAI.invitees[inviteeId] = invite + +## #self.air.queryObject(inviteeId, self.gotInvitee, invite) +## #self.air.queryObject(inviterId, self.gotInviter, invite) +## invite.inviter = self.air.doId2do.get(inviterId) +## invite.invitee = self.air.doId2do.get(inviteeId) +## if invite.inviter and invite.invitee: +## self.beginInvite(invite) + + +## # def gotInviter(self, handle, invite): +## # if not invite.inviter: +## # invite.inviter = handle +## # if invite.invitee: +## # self.beginInvite(invite) + +## # def gotInvitee(self, handle, invite): +## # if not invite.invitee: +## # invite.invitee = handle +## # if invite.inviter: +## # self.beginInvite(invite) + +## def beginInvite(self, invite): +## # Ask the invitee if he is available to consider being +## # someone's friend--that is, that he's not busy playing a +## # minigame or something. + +## invite.inviteeKnows = 1 +## self.down_inviteeFriendQuery(invite.inviteeId, invite.inviterId, +## invite.inviter.getName(), +## invite.inviter.dna, invite.context) + +## def inviteeUnavailable(self, invite, code): +## # Cannot make the request for one of these reasons: +## # +## # 0 - the invitee is busy +## # 2 - the invitee is already your friend +## # 3 - the invitee is yourself +## # 4 - the invitee is ignoring you. +## # 6 - the invitee not accepting friends +## self.down_friendConsidering(invite.inviterId, code, invite.context) + +## # That ends the invitation. +## self.clearInvite(invite) + +## def inviteeAvailable(self, invite): +## # The invitee is considering our friendship request. +## self.down_friendConsidering(invite.inviterId, 1, invite.context) + + +## def noFriends(self, invite, yesNoMaybe): +## # The invitee declined to make friends. +## # +## # 0 - no +## # 2 - unable to answer; e.g. entered a minigame or something. +## # 3 - the invitee has too many friends already. + +## if yesNoMaybe == 0 or yesNoMaybe == 3: +## # The user explictly said no or has too many friends. +## # Disallow this guy from asking again for the next ten +## # minutes or so. + +## if not self.declineFriends1.has_key(invite.inviteeId): +## self.declineFriends1[invite.inviteeId] = {} +## self.declineFriends1[invite.inviteeId][invite.inviterId] = yesNoMaybe + +## self.down_friendResponse(invite.inviterId, yesNoMaybe, invite.context) +## self.clearInvite(invite) + +## def makeFriends(self, invite): +## # The invitee agreed to make friends. +## self.air.makeFriends(invite.inviteeId, invite.inviterId, 0, +## invite.context) +## self.down_friendResponse(invite.inviterId, 1, invite.context) +## # The reply will clear the context out when it comes in. + +## def makeSpecialFriends(self, requesterId, avId): +## # The "requester" has typed in a codeword that successfully +## # matches a friend in the world. Attempt to make the +## # friendship (with chat permission), and send back a code +## # indicating success or failure. + +## # Get a special Invite structure just for this purpose. +## context = FriendManagerAI.nextContext +## FriendManagerAI.nextContext += 1 + +## invite = FriendManagerAI.Invite(context, requesterId, avId) +## FriendManagerAI.invites[context] = invite + +## invite.sendSpecialResponse = 1 + +## self.air.makeFriends(invite.inviteeId, invite.inviterId, 1, +## invite.context) + +## def clearInvite(self, invite): +## try: +## del FriendManagerAI.invites[invite.context] +## except: +## pass + +## try: +## del FriendManagerAI.inviters[invite.inviterId] +## except: +## pass + +## try: +## del FriendManagerAI.invitees[invite.inviteeId] +## except: +## pass + +## def cancelInvite(self, invite): +## if invite.inviteeKnows: +## self.down_inviteeCancelFriendQuery(invite.inviteeId, invite.context) + +## invite.inviteeKnows = 0 + + +## def makeFriendsReply(self, result, context): +## try: +## invite = FriendManagerAI.invites[context] +## except: +## FriendManagerAI.notify.warning('Message for unknown context ' + `context`) +## return + +## if result: +## # By now, the server has OK'ed the friends transaction. +## # Update our internal bookkeeping so we remember who's +## # friends with whom. This is mainly useful for correct +## # accounting of the make-a-friend quest. +## invitee = self.air.doId2do.get(invite.inviteeId) +## inviter = self.air.doId2do.get(invite.inviterId) +## if invitee != None: +## invitee.extendFriendsList(invite.inviterId, invite.sendSpecialResponse) +## self.air.questManager.toonMadeFriend(invitee, inviter) + +## #inviter = self.air.doId2do.get(invite.inviterId) +## if inviter != None: +## inviter.extendFriendsList(invite.inviteeId, invite.sendSpecialResponse) +## self.air.questManager.toonMadeFriend(inviter, invitee) + +## if invite.sendSpecialResponse: +## # If this flag is set, the "invite" was generated via the +## # codeword system, instead of through the normal path. In +## # this case, we need to send the acknowledgement back to +## # the client. + +## if result: +## # Success! Send a result code of 1. +## result = 1 +## else: +## # Failure, some friends list problem. Result code of 2. +## result = 2 + +## self.down_submitSecretResponse(invite.inviterId, result, +## invite.inviteeId) + +## # Also send a notification to the other avatar, if he's on. +## avatar = DistributedAvatarAI.DistributedAvatarAI(self.air) +## avatar.doId = invite.inviteeId +## avatar.d_friendsNotify(invite.inviterId, 2) + +## self.clearInvite(invite) + + + +## def __requestSecretReply(self, result, secret, requesterId): +## self.notify.debug("request secret result = %d, secret = '%s', requesterId = %d" % (result, secret, requesterId)) +## self.down_requestSecretResponse(requesterId, result, secret) + + +## def __submitSecretReply(self, result, secret, requesterId, avId): +## self.notify.debug("submit secret result = %d, secret = '%s', requesterId = %d, avId = %d" % (result, secret, requesterId, avId)) +## if result == 1: +## # We successfully matched the secret, so we should do a +## # few more sanity checks and then try to make friends. +## if avId == requesterId: +## # This is the requester's own secret! +## result = 3 +## else: +## # Ok, make us special friends. +## self.makeSpecialFriends(requesterId, avId) + +## # In this case, the response gets sent after the +## # friends transaction completes. +## return + +## self.down_submitSecretResponse(requesterId, result, avId) + +## def __previousResponse(self, inviteeId, inviterId): +## # Return the previous rejection code if this invitee has +## # previously (recently) declined a friendship from this +## # inviter, or None if there was no previous rejection. + +## now = globalClock.getRealTime() +## if now - self.lastRollTime >= self.DeclineFriendshipTimeout: +## self.declineFriends2 = self.declineFriends1 +## self.declineFriends1 = {} + +## # Now, is the invitee/inviter combination present in either +## # map? +## previous = None +## if self.declineFriends1.has_key(inviteeId): +## previous = self.declineFriends1[inviteeId].get(inviterId) +## if previous != None: +## return previous + +## if self.declineFriends2.has_key(inviteeId): +## previous = self.declineFriends2[inviteeId].get(inviterId) +## if previous != None: +## return previous + +## # Nope, go ahead and ask. +## return None diff --git a/otp/src/friends/FriendResponseCodes.py b/otp/src/friends/FriendResponseCodes.py new file mode 100644 index 0000000..ed4665c --- /dev/null +++ b/otp/src/friends/FriendResponseCodes.py @@ -0,0 +1,6 @@ +INVITATION_RESP_OK = 0 +INVITATION_RESP_DECLINE = 1 +INVITATION_RESP_RETRACT = 2 +INVITATION_RESP_CANCEL = 3 +INVITATION_RESP_ALREADY_FRIENDS = 4 +INVITATION_RESP_NEW_FRIENDS = 5 \ No newline at end of file diff --git a/otp/src/friends/FriendSecret.py b/otp/src/friends/FriendSecret.py new file mode 100644 index 0000000..465f8b9 --- /dev/null +++ b/otp/src/friends/FriendSecret.py @@ -0,0 +1,969 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import StateData +import string +from otp.otpbase import OTPLocalizer +from otp.otpbase import OTPGlobals +from otp.uberdog import RejectCode + +globalFriendSecret = None + +AccountSecret = 0 +AvatarSecret = 1 +BothSecrets = 2 + +def showFriendSecret(secretType=AvatarSecret): + """ + Added optional parameter to offer the choice between account and avatar friend tokens. + """ + # A module function to open the global secret panel. + global globalFriendSecret + if not base.cr.isPaid(): + # bring up teaser panel + chatMgr = base.localAvatar.chatMgr + chatMgr.fsm.request("trueFriendTeaserPanel") + elif not base.cr.isParentPasswordSet(): + # non-COPPA gamecard user: prompt to leave game and set parent password + chatMgr = base.localAvatar.chatMgr + if base.cr.productName in ['DisneyOnline-AP', 'DisneyOnline-UK', 'JP', 'DE', 'BR', 'FR']: + chatMgr = base.localAvatar.chatMgr + if not base.cr.isPaid(): + chatMgr.fsm.request("unpaidChatWarning") + else: + chatMgr.paidNoParentPassword = 1 + chatMgr.fsm.request("unpaidChatWarning") + else: + chatMgr.paidNoParentPassword = 1 + chatMgr.fsm.request("noSecretChatAtAll") + # bring up dialog for true friends + # open chat with true friends + + elif not base.cr.allowSecretChat(): + # If we're paid but have not yet activated secrets, pop up the + # dialog to do this. + chatMgr = base.localAvatar.chatMgr + if base.cr.productName in ['DisneyOnline-AP', 'DisneyOnline-UK', 'JP', 'DE', 'BR', 'FR']: + chatMgr = base.localAvatar.chatMgr + if not base.cr.isPaid(): + chatMgr.fsm.request("unpaidChatWarning") + else: + chatMgr.paidNoParentPassword = 1 + chatMgr.fsm.request("unpaidChatWarning") + else: + # we no longer offer the ability to enable chat in game + #chatMgr.fsm.request("noSecretChatWarning") + chatMgr.fsm.request("noSecretChatAtAll") + elif base.cr.needParentPasswordForSecretChat(): + # If we're paid but have the flag set to require the Parent Password to be + # entered to get or use a Secret Friends password, pop up the + # dialog to get the Parent Password. + unloadFriendSecret() + globalFriendSecret = FriendSecretNeedsParentLogin(secretType) + globalFriendSecret.enter() + else: + # Otherwise, actually open the secrets panel. + openFriendSecret(secretType) + +def openFriendSecret(secretType): + global globalFriendSecret + if globalFriendSecret != None: + globalFriendSecret.unload() + globalFriendSecret = FriendSecret(secretType) + globalFriendSecret.enter() + +def hideFriendSecret(): + # A module function to close the global secret panel if it is open. + if globalFriendSecret != None: + globalFriendSecret.exit() + +def unloadFriendSecret(): + # A module function to completely unload the global secret panel. + global globalFriendSecret + if globalFriendSecret != None: + globalFriendSecret.unload() + globalFriendSecret = None + + +class FriendSecretNeedsParentLogin(StateData.StateData): + + notify = DirectNotifyGlobal.directNotify.newCategory("FriendSecretNeedsParentLogin") + + def __init__(self, secretType): + assert self.notify.debugCall() + StateData.StateData.__init__(self, "friend-secret-needs-parent-login-done") + self.dialog = None + self.secretType = secretType + + def enter(self): + assert self.notify.debugCall() + StateData.StateData.enter(self) + base.localAvatar.chatMgr.fsm.request("otherDialog") + # Pop up a dialog indicating the parent account needs to login + # in order to get to the Secret Friends chat: + if self.dialog == None: + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + nameBalloon = loader.loadModel("phase_3/models/props/chatbox_input") + optionsButtonImage = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR")) + okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')) + cancelButtonImage = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')) + + # The Castillian (and all foreign) version relies on a seperate parent + # password system. Omit the the password entry field and cancel button. + withParentAccount = False + try: + withParentAccount = base.cr.withParentAccount + except: + self.notify.warning("withParentAccount not found in base.cr") + pass + + if withParentAccount: + okPos = (-0.22, 0.0, -0.5) + textPos = (0, 0.25) + okCommand = self.__handleOKWithParentAccount + elif base.cr.productName != "Terra-DMC": + okPos = (-0.22, 0.0, -0.5) + textPos = (0, 0.25) + okCommand = self.__oldHandleOK + else: + self.passwordEntry = None + okPos = (0, 0, -0.35) + textPos = (0, 0.125) + okCommand = self.__handleCancel + + # make the common gui elements + self.dialog = DirectFrame( + parent = aspect2dp, + pos = (0.0, 0.1, 0.2), + relief = None, + image = DGG.getDefaultDialogGeom(), + image_color = OTPGlobals.GlobalDialogColor, + image_scale = (1.4, 1.0, 1.25), + image_pos = (0,0,-0.1), + text = OTPLocalizer.FriendSecretNeedsParentLoginWarning, + text_wordwrap = 21.5, + text_scale = 0.055, + text_pos = textPos, + textMayChange = 1) + DirectButton( + self.dialog, + image = okButtonImage, + relief = None, + text = OTPLocalizer.FriendSecretNeedsPasswordWarningOK, + text_scale = 0.05, + text_pos = (0.0, -0.1), + textMayChange = 0, + pos = okPos, + command = okCommand) + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, 0.35), + text = OTPLocalizer.FriendSecretNeedsPasswordWarningTitle, + textMayChange = 0, + text_scale = 0.08) + + # if not foreign, make the domestic only password entry elements + if base.cr.productName != "Terra-DMC": + self.usernameLabel = DirectLabel( + parent = self.dialog, + relief = None, + pos = (-0.07, 0.0, -0.1), + text = OTPLocalizer.ParentLogin, + text_scale = 0.06, + text_align = TextNode.ARight, + textMayChange = 0) + self.usernameEntry = DirectEntry( + parent = self.dialog, + relief = None, + image = nameBalloon, + image1_color = (0.8, 0.8, 0.8, 1.0), + scale = 0.064, + pos = (0.0, 0.0, -0.1), + width = OTPGlobals.maxLoginWidth, + numLines = 1, + focus = 1, + cursorKeys = 1, + obscured = 1, + command = self.__handleUsername) + self.passwordLabel = DirectLabel( + parent = self.dialog, + relief = None, + pos = (-0.02, 0.0, -0.3), + text = OTPLocalizer.ParentPassword, + text_scale = 0.06, + text_align = TextNode.ARight, + textMayChange = 0) + self.passwordEntry = DirectEntry( + parent = self.dialog, + relief = None, + image = nameBalloon, + image1_color = (0.8, 0.8, 0.8, 1.0), + scale = 0.064, + pos = (0.04, 0.0, -0.3), + width = OTPGlobals.maxLoginWidth, + numLines = 1, + focus = 1, + cursorKeys = 1, + obscured = 1, + command = okCommand ) + DirectButton( + self.dialog, + image = cancelButtonImage, + relief = None, + text = OTPLocalizer.FriendSecretNeedsPasswordWarningCancel, + text_scale = 0.05, + text_pos = (0.0, -0.1), + textMayChange = 1, + pos = (0.2, 0.0, -0.5), + command = self.__handleCancel) + + # hide the parent login as it's not valid yet + if withParentAccount: + self.usernameEntry.enterText('') + self.usernameEntry['focus'] = 1 + self.passwordEntry.enterText('') + + else: + self.usernameEntry.hide() + self.usernameLabel.hide() + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + + guiButton.removeNode() + buttons.removeNode() + nameBalloon.removeNode() + else: + self.dialog['text'] = OTPLocalizer.FriendSecretNeedsParentLoginWarning + if self.usernameEntry: + self.usernameEntry['focus'] = 1 + self.usernameEntry.enterText('') + elif self.passwordEntry: + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + + self.dialog.show() + + def exit(self): + assert self.notify.debugCall() + self.ignoreAll() + if self.dialog: + self.dialog.destroy() + self.dialog = None + if self.isEntered: + base.localAvatar.chatMgr.fsm.request("mainMenu") + StateData.StateData.exit(self) + + def __handleUsername(self, *args): + assert self.notify.debugCall() + # activate password entry + if self.passwordEntry: + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + + def __handleOKWithParentAccount(self, *args): + assert self.notify.debugCall() + username = self.usernameEntry.get() + password = self.passwordEntry.get() + # we need to store these in base.cr for __enterSecret + base.cr.parentUsername = username + base.cr.parentPassword = password + tt = base.cr.loginInterface + try: + DISLIdFromLogin = base.cr.DISLIdFromLogin + except: + DISLIdFromLogin = 0 + if DISLIdFromLogin and ( DISLIdFromLogin != localAvatar.DISLid): + # we only expect to have 1 DISLId per player, we're screwed if this happens + self.notify.error("Mismatched DISLIds, fromLogin=%s, localAvatar.dislId=%s" % + ( DISLIdFromLogin, localAvatar.DISLid)) + okflag, message = tt.authenticateParentUsernameAndPassword(localAvatar.DISLid, + base.cr.password, + username, + password) + if okflag: + self.exit() + openFriendSecret(self.secretType) + elif message: + # Error connecting. + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) + else: + # Wrong password. + self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + + + def __oldHandleOK(self, *args): + assert self.notify.debugCall() + username = self.usernameEntry.get() + password = self.passwordEntry.get() + # we need to store these in base.cr for __enterSecret + base.cr.parentUsername = username + base.cr.parentPassword = password + tt = base.cr.loginInterface + okflag, message = tt.authenticateParentPassword(base.cr.userName, base.cr.password, password) + if okflag: + self.exit() + openFriendSecret(self.secretType) + elif message: + # Error connecting. + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) + else: + # Wrong password. + self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + + def __handleOK(self, *args): + assert self.notify.debugCall() + base.cr.parentUsername = self.usernameEntry.get() + base.cr.parentPassword = self.passwordEntry.get() + # we don't have a request for this yet, so pretend we are submitting a limited secret + base.cr.playerFriendsManager.sendRequestUseLimitedSecret('', base.cr.parentUsername, base.cr.parentPassword) + self.accept(OTPGlobals.PlayerFriendRejectUseSecretEvent, self.__handleParentLogin) + # we won't get an answer yet, so spoof a return code + # self.__handleParentLogin(0) + # that doesn't work, just immediately close the panel + self.exit() + + def __handleParentLogin(self, reason): + # don't know reason codes yet, spoof something + if reason == 0: + self.exit() + openFriendSecret(self.secretType) + elif reason == 1: + # Wrong username + self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongUsername + self.usernameEntry['focus'] = 1 + self.usernameEntry.enterText('') + elif reason == 2: + # Wrong password + self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword + self.passwordEntry['focus'] = 1 + self.passwordEntry.enterText('') + else: + # Error connecting (fall through case) + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) + + def __handleCancel(self): + assert self.notify.debugCall() + self.exit() + + +class FriendSecret(DirectFrame, StateData.StateData): + """ + This is a panel that allows the user to manage 'secrets' that is, + code words that identify people who are actually friends in real + life (and thus are entitled to use full chat with each other). + + This panel is the place the user will come to either get a new + 'secret' to give to someone else, or to enter the 'secret' they + got from someone. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("FriendSecret") + + def __init__(self, secretType): + assert self.notify.debugCall() + DirectFrame.__init__(self, + parent = aspect2dp, + pos = (0, 0, 0.30), + relief = None, + image = DGG.getDefaultDialogGeom(), + image_scale = (1.6, 1, 1.4), + image_pos = (0,0,-0.05), + image_color = OTPGlobals.GlobalDialogColor, + borderWidth = (0.01, 0.01), + ) + StateData.StateData.__init__(self, "friend-secret-done") + self.initialiseoptions(FriendSecret) + self.prefix = OTPGlobals.getDefaultProductPrefix() + # what types of secret friends will we offer + self.secretType = secretType + self.notify.debug("### secretType = %s" % self.secretType) + # what type of secret friend has the user chosen + self.requestedSecretType = secretType + self.notify.debug("### requestedSecretType = %s" % self.requestedSecretType) + + + def unload(self): + assert self.notify.debugCall() + if self.isLoaded == 0: + return None + self.isLoaded = 0 + self.exit() + + del self.introText + del self.getSecret + del self.enterSecretText + del self.enterSecret + del self.ok1 + del self.ok2 + del self.cancel + del self.secretText + del self.avatarButton + del self.accountButton + DirectFrame.destroy(self) + self.ignore('clientCleanup') + + def load(self): + assert self.notify.debugCall() + if self.isLoaded == 1: + return None + self.isLoaded = 1 + + self.introText = DirectLabel( + parent = self, + relief = None, + pos = (0, 0, 0.4), + scale = 0.05, + text = OTPLocalizer.FriendSecretIntro, + text_fg = (0,0,0,1), + text_wordwrap = 30, + ) + self.introText.hide() + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + self.getSecret = DirectButton( + parent = self, + relief = None, + pos = (0, 0, -0.11), + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = OTPLocalizer.FSgetSecret, + text = OTPLocalizer.FriendSecretGetSecret, + text_scale = OTPLocalizer.FSgetSecretButton, + text_pos = (0,-0.02), + command = self.__determineSecret, + ) + self.getSecret.hide() + + self.enterSecretText = DirectLabel( + parent = self, + relief = None, + pos = OTPLocalizer.FSenterSecretTextPos, + scale = 0.05, + text = OTPLocalizer.FriendSecretEnterSecret, + text_fg = (0,0,0,1), + text_wordwrap = 30, + ) + self.enterSecretText.hide() + + self.enterSecret = DirectEntry( + parent = self, + relief = DGG.SUNKEN, + scale = 0.06, + pos = (-0.60, 0, -0.38), + frameColor = (0.8,0.8,0.5,1), + borderWidth = (0.1, 0.1), + numLines = 1, + width = 20, + frameSize = (-0.4, 20.4, -0.4, 1.1), + command = self.__enterSecret + ) + + # This fixes a little problem with the initial frame size. + self.enterSecret.resetFrameSize() + self.enterSecret.hide() + + self.ok1 = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = OTPLocalizer.FSok1, + text = OTPLocalizer.FriendSecretEnter, + text_scale = 0.06, + text_pos = (0,-0.02), + pos = (0, 0, -0.5), + command = self.__ok1, + ) + self.ok1.hide() + + if base.cr.productName in ['JP', 'DE', 'BR', 'FR']: + # There should be no 'change secret friends options' button in + # the UK version. + # the only thing we do to self.changeOptions is show and hide it; + # avoid adding a bunch of 'if' statements + class ShowHide: + def show(self): + pass + def hide(self): + pass + self.changeOptions = ShowHide() + + self.ok2 = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = OTPLocalizer.FSok2, + text = OTPLocalizer.FriendSecretOK, + text_scale = 0.06, + text_pos = (0,-0.02), + pos = (0, 0, -0.57), + command = self.__ok2, + ) + self.ok2.hide() + + self.cancel = DirectButton( + parent = self, + relief = None, + text = OTPLocalizer.FriendSecretCancel, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = OTPLocalizer.FScancel, + text_scale = 0.06, + text_pos = (0,-0.02), + pos = (0, 0, -0.57), + command = self.__cancel, + ) + self.cancel.hide() + + self.nextText = DirectLabel( + parent = self, + relief = None, + pos = (0, 0, 0.30), + scale = 0.06, + text = "", + text_scale = OTPLocalizer.FSnextText, + text_fg = (0,0,0,1), + text_wordwrap = 25.5, + ) + self.nextText.hide() + + self.secretText = DirectLabel( + parent = self, + relief = None, + pos = (0, 0, -0.42), + scale = 0.1, + text = "", + text_fg = (0,0,0,1), + text_wordwrap = 30, + ) + self.secretText.hide() + + guiButton.removeNode() + + # moved into it's own method to make overloading easier + self.makeFriendTypeButtons() + + self.accept('clientCleanup', self.__handleCleanup) + self.accept('walkDone', self.__handleStop) + + def __handleStop(self, message): + self.exit() + + def __handleCleanup(self): + self.unload() + + def makeFriendTypeButtons(self): + # NOTE: this uses generic text, if you want game-specific text, please inherit and override + # (see $TOONTOWN/src/friends/ToontownFriendSecret.py for an example) + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + + # make an avatar friend + self.avatarButton = DirectButton( + self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = OTPLocalizer.FriendSecretDetermineSecretAvatar, + text_scale = 0.07, + text_pos = (0.0, -0.1), + pos = (-0.35, 0.0, -0.05), + command = self.__handleAvatar + ) + # make a label to show avatar friend desc on rollover + avatarText = DirectLabel( + parent = self, + relief = None, + pos = Vec3(0.35, 0, -0.3), + text = OTPLocalizer.FriendSecretDetermineSecretAvatarRollover, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0), + text_scale = 0.055, + text_align = TextNode.ACenter, + ) + avatarText.reparentTo(self.avatarButton.stateNodePath[2]) + self.avatarButton.hide() + + # make an account friend + self.accountButton = DirectButton( + self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = OTPLocalizer.FriendSecretDetermineSecretAccount, + text_scale = 0.07, + text_pos = (0.0, -0.1), + pos = (0.35, 0.0, -0.05), + command = self.__handleAccount + ) + # make a label to show feature desc on rollover + accountText = DirectLabel( + parent = self, + relief = None, + pos = Vec3(-0.35, 0, -0.3), + text = OTPLocalizer.FriendSecretDetermineSecretAccountRollover, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0), + text_scale = 0.055, + text_align = TextNode.ACenter, + ) + accountText.reparentTo(self.accountButton.stateNodePath[2]) + self.accountButton.hide() + + buttons.removeNode() + + def enter(self): + assert self.notify.debugCall() + if self.isEntered == 1: + return + self.isEntered = 1 + # Use isLoaded to avoid redundant loading + if self.isLoaded == 0: + self.load() + + self.show() + + # Set us up on the first page. + self.introText.show() + self.getSecret.show() + self.enterSecretText.show() + self.enterSecret.show() + self.ok1.show() + + self.ok2.hide() + self.cancel.hide() + self.nextText.hide() + self.secretText.hide() + + # While the entry's on the screen, we have to turn off the + # background focus on the normal chat entry. Otherwise, + # keypresses would start chatting! + base.localAvatar.chatMgr.fsm.request("otherDialog") + + # And now we can set the focus on our entry. + self.enterSecret['focus'] = 1 + + # The secret panel uses up a lot of space onscreen; in + # particular, it will hide most of the chat balloons. Force + # these to go to the margins. + NametagGlobals.setOnscreenChatForced(1) + + def exit(self): + assert self.notify.debugCall() + if self.isEntered == 0: + return + self.isEntered = 0 + + # Restore the normal chat behavior. + NametagGlobals.setOnscreenChatForced(0) + + self.__cleanupFirstPage() + self.ignoreAll() + self.accept('clientCleanup', self.unload) + self.hide() + + def __determineSecret(self): + assert self.notify.debugCall() + # If we support both types of secrets... + if self.secretType == BothSecrets: + # ask the player what type they want + self.__cleanupFirstPage() + self.ok1.hide() + # NOTE: this uses generic text, if you want game-specific text, please inherit and override + # (see $TOONTOWN/src/friends/ToontownFriendSecret.py for an example) + self.nextText['text'] = OTPLocalizer.FriendSecretDetermineSecret + self.nextText.setPos(0, 0, 0.30) + self.nextText.show() + self.avatarButton.show() + self.accountButton.show() + self.cancel.show() + else: + # or just get the secret + self.__getSecret() + + def __handleAvatar(self): + assert self.notify.debugCall() + self.requestedSecretType = AvatarSecret + self.__getSecret() + + def __handleAccount(self): + assert self.notify.debugCall() + self.requestedSecretType = AccountSecret + self.__getSecret() + + def __handleCancel(self): + assert self.notify.debugCall() + self.exit() + + def __getSecret(self): + assert self.notify.debugCall() + self.__cleanupFirstPage() + self.nextText['text'] = OTPLocalizer.FriendSecretGettingSecret + self.nextText.setPos(0, 0, 0.30) + self.nextText.show() + self.avatarButton.hide() + self.accountButton.hide() + self.ok1.hide() + self.cancel.show() + if self.requestedSecretType == AvatarSecret: + # If we don't have a FriendManager, something's badly wrong. + # Most likely we're running in a development environment + # without an AI client. + if not base.cr.friendManager: + self.notify.warning("No FriendManager available.") + self.exit() + return + base.cr.friendManager.up_requestSecret() + self.accept('requestSecretResponse', self.__gotAvatarSecret) + else: + if base.cr.needParentPasswordForSecretChat(): + self.notify.info("### requestLimitedSecret") + base.cr.playerFriendsManager.sendRequestLimitedSecret(base.cr.parentUsername, base.cr.parentPassword) + else: + base.cr.playerFriendsManager.sendRequestUnlimitedSecret() + self.notify.info("### requestUnlimitedSecret") + self.accept(OTPGlobals.PlayerFriendNewSecretEvent, self.__gotAccountSecret) + self.accept(OTPGlobals.PlayerFriendRejectNewSecretEvent, self.__rejectAccountSecret) + + def __gotAvatarSecret(self, result, secret): + assert self.notify.debugCall() + self.ignore('requestSecretResponse') + + if result == 1: + self.nextText['text'] = OTPLocalizer.FriendSecretGotSecret + self.nextText.setPos(*OTPLocalizer.FSgotSecretPos) + if self.prefix: + # if desired, prepend a prefix to differentiate from other products + self.secretText['text'] = self.prefix + ' ' + secret + else: + self.secretText['text'] = secret + else: + # Oops, too many secrets. + self.nextText['text'] = OTPLocalizer.FriendSecretTooMany + self.nextText.show() + self.secretText.show() + self.cancel.hide() + self.ok1.hide() + self.ok2.show() + + def __gotAccountSecret(self, secret): + assert self.notify.debugCall() + self.ignore(OTPGlobals.PlayerFriendNewSecretEvent) + self.ignore(OTPGlobals.PlayerFriendRejectNewSecretEvent) + + self.nextText['text'] = OTPLocalizer.FriendSecretGotSecret + self.nextText.setPos(0, 0, 0.47) + # for now Account friends (i.e. XD Friends) have no unique prefix + self.secretText['text'] = secret + self.nextText.show() + self.secretText.show() + self.cancel.hide() + self.ok1.hide() + self.ok2.show() + + def __rejectAccountSecret(self, reason): + assert self.notify.debugCall() + print "## rejectAccountSecret: reason = ", reason + self.ignore(OTPGlobals.PlayerFriendNewSecretEvent) + self.ignore(OTPGlobals.PlayerFriendRejectNewSecretEvent) + # TODO: handle more reasons + # Oops, too many secrets. + self.nextText['text'] = OTPLocalizer.FriendSecretTooMany + self.nextText.show() + self.secretText.show() + self.cancel.hide() + self.ok1.hide() + self.ok2.show() + + def __enterSecret(self, secret): + assert self.notify.debugCall() + # Empty the entry for next time. + self.enterSecret.set("") + + secret = string.strip(secret) + if not secret: + # If the secret is empty, it just means to close down + # the dialog. + self.exit() + return + + # If we don't have a FriendManager, something's badly wrong. + # Most likely we're running in a development environment + # without an AI client. + if not base.cr.friendManager: + self.notify.warning("No FriendManager available.") + self.exit() + return + + self.__cleanupFirstPage() + + # The client can now prepend a product prefix to all secrets - strip this off if present + if self.prefix: + if secret[0:2] == self.prefix: + secret = secret[3:] + self.notify.info("### use TT secret") + self.accept('submitSecretResponse', self.__enteredSecret) + base.cr.friendManager.up_submitSecret(secret) + else: + # this is not a code for this product, they must want to make player true friends + self.accept(OTPGlobals.PlayerFriendUpdateEvent, self.__useAccountSecret) + self.accept(OTPGlobals.PlayerFriendRejectUseSecretEvent, self.__rejectUseAccountSecret) + if base.cr.needParentPasswordForSecretChat(): + self.notify.info("### useLimitedSecret") + base.cr.playerFriendsManager.sendRequestUseLimitedSecret(secret, base.cr.parentUsername, base.cr.parentPassword) + else: + self.notify.info("### useUnlimitedSecret") + base.cr.playerFriendsManager.sendRequestUseUnlimitedSecret(secret) + + self.nextText['text'] = OTPLocalizer.FriendSecretTryingSecret + self.nextText.setPos(0, 0, 0.30) + self.nextText.show() + self.ok1.hide() + self.cancel.show() + + def __enteredSecret(self, result, avId): + assert self.notify.debugCall() + self.ignore('submitSecretResponse') + + if result == 1: + # We made it! + handle = base.cr.identifyAvatar(avId) + if handle != None: + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretSuccess % (handle.getName()) + else: + # Shoot. We just made friends with someone, but we + # don't know who it is yet. We'll have to ask the + # server who this is, and wait for the response before + # we can continue. + self.accept('friendsMapComplete', self.__nowFriends, [avId]) + ready = base.cr.fillUpFriendsMap() + if ready: + self.__nowFriends(avId) + return + + elif result == 0: + # Unknown secret. + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretUnknown + + elif result == 2: + # Friends list full. + handle = base.cr.identifyAvatar(avId) + if handle != None: + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretFull % (handle.getName()) + else: + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretFullNoName + + elif result == 3: + # Self match. + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretSelf + + elif result == 4: + # This doesn't look like our secret friend code - wrong prefix + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretWrongProduct % (self.prefix) + + self.nextText.show() + self.cancel.hide() + self.ok1.hide() + self.ok2.show() + + def __useAccountSecret(self, avId, friendInfo): + assert self.notify.debugCall() + self.ignore(OTPGlobals.PlayerFriendUpdateEvent) + self.ignore(OTPGlobals.PlayerFriendRejectUseSecretEvent) + + # todo - pass an avId? + self.__enteredSecret(1, 0) + + def __rejectUseAccountSecret(self, reason): + assert self.notify.debugCall("reason = %s" % reason) + print "## rejectUseAccountSecret: reason = ", reason + self.ignore(OTPGlobals.PlayerFriendUpdateEvent) + self.ignore(OTPGlobals.PlayerFriendRejectUseSecretEvent) + + if (reason == RejectCode.RejectCode.FRIENDS_LIST_FULL): + self.__enteredSecret(2, 0) + elif (reason == RejectCode.RejectCode.ALREADY_FRIENDS_WITH_SELF): + self.__enteredSecret(3, 0) + else: + # todo: this shouldn't be the fall thru + self.__enteredSecret(0, 0) + + def __nowFriends(self, avId): + assert self.notify.debugCall() + # This is called only from enteredSecret(), after the friend + # transaction has completed. This will be called only if the + # client didn't know the identity of the friend at the time, + # but it should know now. + + self.ignore('friendsMapComplete') + + handle = base.cr.identifyAvatar(avId) + if handle != None: + self.nextText['text'] = OTPLocalizer.FriendSecretNowFriends % (handle.getName()) + else: + # This really shouldn't be possible. + self.nextText['text'] = OTPLocalizer.FriendSecretNowFriendsNoName + + self.nextText.show() + self.cancel.hide() + self.ok1.hide() + self.ok2.show() + + def __ok1(self): + assert self.notify.debugCall() + # Clicking "ok" from the front screen is the same thing as + # pressing Enter in the entry. + secret = self.enterSecret.get() + self.__enterSecret(secret) + + def __ok2(self): + assert self.notify.debugCall() + # Clicking "ok" from a finished screen just makes the thing go + # away. + self.exit() + + def __cancel(self): + assert self.notify.debugCall() + # Clicking "cancel" makes the panel close too. + self.exit() + + def __cleanupFirstPage(self): + assert self.notify.debugCall() + # Removes all the widgets etc. that were created for the + # welcome page, except the introText. + + self.introText.hide() + self.getSecret.hide() + self.enterSecretText.hide() + self.enterSecret.hide() + + # Restore the background focus on the chat entry. + base.localAvatar.chatMgr.fsm.request("mainMenu") + + + diff --git a/otp/src/friends/GuildDB.py b/otp/src/friends/GuildDB.py new file mode 100644 index 0000000..c19cdeb --- /dev/null +++ b/otp/src/friends/GuildDB.py @@ -0,0 +1,878 @@ +import MySQLdb +import _mysql_exceptions +import time +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.task import Task +from otp.distributed import OtpDoGlobals +from otp.uberdog.DBInterface import DBInterface +import random, string + +NOACTION_FLAG=0 +REVIEW_FLAG=1 +DENY_FLAG=2 +APPROVE_FLAG=3 +ALLDONE_FLAG=4 + +# During off-line guild token generation, we'll use the badwordpy module to +# check our alpha strings. + +try: + import badwordpy +except ImportError: + class BadwordDummy: + def __init__(self, *args): + pass + def test(self, word): + return False + def scrub(self, str): + return str + badwordpy = BadwordDummy() + +class GuildDB(DBInterface): + """ + DB wrapper class for guilds! All SQL code for guilds should be in here. + """ + notify = directNotify.newCategory('GuildDB') + + def __init__(self,host,port,user,passwd,dbname): + self.sqlAvailable = uber.sqlAvailable + if not self.sqlAvailable: + return + + # Now set the bwDictPath. Used during token generation to make sure + # we're not giving out any strings that contain bad words + + self.bwDictPath = uber.bwDictPath + + # If Path string is empty, flag the dict as being offline + + if self.bwDictPath == "": + self.bwDictOnline = False + self.notify.info("Badword filtering for Guild Tokens not online. Dict path not provided") + else: + self.bwDictOnline = True + # Now that the module has loaded, try to init the lib with the + # bwDictPath provided + badwordpy.init(self.bwDictPath,"") + # If the path provided is good, the next if statement should return 1 + if not badwordpy.test("shit"): + self.notify.info("Bad Word Filtering not online for token guild generation. Dict test failed") + self.bwDistOnline = False + + # Place startCleanUpExpiredTokens into TaskMgr. It will clean up the + # expired guild membership tokens every 5 minutes. + + self.startCleanUpExpiredTokens() + + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.dbname = self.processDBName(dbname) + + if __debug__: + self.notify.info("About to connect to %s MySQL db at %s:%d." % (self.dbname, host, port)) + self.db = MySQLdb.connect(host=host, + port=port, + user=user, + passwd=passwd) + + if __debug__: + self.notify.info("Connected to %s MySQL db at %s:%d." % (self.dbname, host, port)) + + #temp hack for initial dev, create DB structure if it doesn't exist already + cursor = self.db.cursor() + try: + cursor.execute("CREATE DATABASE `%s`" % self.dbname) + if __debug__: + self.notify.debug("Database '%s' did not exist, created a new one!" % self.dbname) + except _mysql_exceptions.ProgrammingError,e: + pass + + cursor.execute("USE `%s`" % self.dbname) + if __debug__: + self.notify.debug("Using database '%s'" % self.dbname) + + try: + cursor.execute("CREATE TABLE `guildinfo` (`gid` INT(32) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(21), `wantname` VARCHAR(21), `namestatus` INT(8), `create_date` DATETIME)") + if __debug__: + self.notify.debug("Table guildinfo did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + try: + cursor.execute("CREATE TABLE `member` (`gid` INT(32) UNSIGNED NOT NULL, `avid` INT(32) UNSIGNED NOT NULL PRIMARY KEY, `rank` INT(8) NOT NULL, FOREIGN KEY (`gid`) REFERENCES `guildinfo` (`gid`))") + if __debug__: + self.notify.debug("Table member did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + try: + cursor.execute("CREATE TABLE `guildtokens` (`tokenid` VARCHAR(8) NOT NULL PRIMARY KEY, `createtime` DATETIME, `ttl` INT(8) UNSIGNED NOT NULL, `gid` INT(32) UNSIGNED, `avid` INT(32) UNSIGNED, `rcount` INT(8), FOREIGN KEY (`gid`) REFERENCES `guildinfo` (`gid`), FOREIGN KEY (`avid`) REFERENCES `member` (`avid`))") + # An index should also be added to the avid col + cursor.execute("CREATE INDEX `avatarid` on guildtokens (avid)") + if __debug__: + self.notify.debug("Table guildtokens did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + +# Commented out next table create function for the time being. +# Waiting for "STAF" access. + +## try: +## cursor.execute("CREATE TABLE `email_notify` (`avid` INT(32) UNSIGNED NOT NULL PRIMARY KEY, `notify` INT(8) NOT NULL, `emailaddress` VARCHAR(75))") +## if __debug__: +## self.notify.debug("Table email_notify did not exist, created a new one!") + +## except _mysql_exceptions.OperationalError,e: +## pass + + def reconnect(self): + if __debug__: + self.notify.debug("MySQL server was missing, attempting to reconnect.") + try: self.db.close() + except: pass + self.db = MySQLdb.connect(host=self.host, + port=self.port, + user=self.user, + passwd=self.passwd) + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port)) + + def disconnect(self): + if not self.sqlAvailable: + return + self.db.close() + self.db = None + + def createGuild(self, avId, isRetry=False): + if not self.sqlAvailable: + return + + # Enter a new Guild into the guildinfo table, and a new member into the member table + try: + # By giving a guild Id of 0, it will auto-increment to the desired id + # Name fields are left blank + cursor = self.db.cursor() + # construct datetime string + foo = time.localtime() + date_time = "%d-%d-%d %d:%d:%d" % (foo[0], foo[1], foo[2], foo[3], foo[4], foo[5]) + cursor.execute("INSERT INTO `guildinfo` VALUES (%s, %s, %s, %s, %s)" , (0, 0, 0, 0, date_time)) + cursor.execute("SELECT LAST_INSERT_ID()") + guildId = cursor.fetchall()[0][0] + self.addMember(guildId, avId, 3) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + if isRetry: + raise e + else: + self.reconnect() + self.createGuild(avId,True) + except _mysql_exceptions.IntegrityError,e: + self.notify.warning("IntegrityError creating new guild for avId %s: %s. Rolling back." % (avId,e)) + from direct.showbase import PythonUtil + self.notify.warning(str(PythonUtil.StackTrace())) + self.db.rollback() + + def memberCount(self, guildId): + if not self.sqlAvailable: + print "Guild DB Unavailable" + return 99999 + + try: + cursor = self.db.cursor() + cursor.execute("SELECT * FROM `member` where `gid` = %s" , guildId) + stuff = cursor.fetchall() + return len(stuff) + except _mysql_exceptions.OperationalError,e: + self.reconnect() + return self.memberCount(guildId) + + print "Guild DB failed member Count for ", guildId + return 9999 + + def verifyGuild(self, guildId): + if not self.sqlAvailable: + print "Guild DB Unavailable" + return False + + try: + cursor = self.db.cursor() + cursor.execute("SELECT * FROM `guildinfo` where `gid` = %s" , guildId) + stuff = cursor.fetchall() + if (len(stuff)): + return True + else: + return False + except _mysql_exceptions.OperationalError,e: + self.reconnect() + return self.verifyGuild(guildId) + + + def queryStatus(self, avatarId): + if not self.sqlAvailable: + print "Guild DB Unavailable" + return 0, "DB Unavailable", 0 + + try: + # Return guildid, name, and rank for the avatarid in question + cursor = self.db.cursor() + cursor.execute("SELECT * FROM `member` where `avid` = %s" , avatarId) + # This will return a single row of gid, avid, rank + stuff = cursor.fetchall() + if (len(stuff) == 0): + # We aren't currently in a guild + guildId = 0 + rank = 0 + name = "Null" + change = 0 + else: + guildId = stuff[0][0] + rank = stuff[0][2] + + cursor.execute("SELECT * FROM `guildinfo` where `gid` = %s" , guildId) + # This will be a single row with gid, name, wantname + stuff = cursor.fetchall() + name = stuff[0][1] + gstatus = stuff[0][3] + if (gstatus == 2): + change = 2 + elif (gstatus == 3): + change = 3 + else: + change = 0 + except _mysql_exceptions.OperationalError,e: + self.reconnect() + return self.queryStatus(avatarId) + + return guildId, name, rank, change + + + def getName(self, guildId): + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("SELECT * FROM `guildinfo` where `gid` = %s" , guildId) + stuff = cursor.fetchall() + return stuff[0][1] + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.getName(guildId) + + + def tryWantName(self, guildId, wantname): + if not self.sqlAvailable: + return + + cursor = self.db.cursor() + try: + cursor.execute("SELECT * FROM `guildinfo` where `wantname` = %s" , wantname) + stuff = cursor.fetchall() + count = len(stuff) + cursor.execute("SELECT * FROM `guildinfo` where `name` = %s" , wantname) + stuff = cursor.fetchall() + count += len(stuff) + except: + self.reconnect() + self.setWantName(guildId, wantname) + + def setWantName(self, guildId, wantname): + if not self.sqlAvailable: + return 0 + + # Insert name into want name field for this guild + cursor = self.db.cursor() + try: + cursor.execute("SELECT * FROM `guildinfo` where `wantname` = %s" , wantname) + stuff = cursor.fetchall() + count = len(stuff) + cursor.execute("SELECT * FROM `guildinfo` where `name` = %s" , wantname) + stuff = cursor.fetchall() + count += len(stuff) + if (count == 0): + cursor.execute("UPDATE `guildinfo` SET `wantname` = %s WHERE `gid` = %s" , (wantname, guildId)) + cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (REVIEW_FLAG, guildId)) + success = 1 + else: + # No longer actually process the name request, just deny it up front + # cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (DENY_FLAG, guildId)) + success = 2 + + self.db.commit() + return success + except _mysql_exceptions.OperationalError,e: + self.reconnect() + return self.setWantName(guildId, wantname) + + def getWantName(self, guildId): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Get currently selected want-name for this guild + cursor = self.db.cursor() + try: + cursor.execute("SELECT * FROM `guildinfo` where `gid` = %s" , guildId) + stuff = cursor.fetchall() + return stuff[0][2] + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.getWantName(guildId) + + def approveName(self, guildId): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Set the official Guildname + # This will only be called by a customer support application + cursor = self.db.cursor() + try: + wantname = self.getWantName(guildId) + if (wantname == "0"): + return + cursor.execute("UPDATE `guildinfo` SET `wantname` = '0' WHERE `gid` = %s" , (guildId)) + cursor.execute("UPDATE `guildinfo` SET `name` = %s WHERE `gid` = %s" , (wantname, guildId)) + cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (APPROVE_FLAG, guildId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.approveName(guildId) + + def rejectName(self, guildId): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Decline the existing wantname and clear it out + cursor = self.db.cursor() + try: + wantname = self.getWantName(guildId) + if (wantname == "0"): + return + cursor.execute("UPDATE `guildinfo` SET `wantname` = 'Rejected' WHERE `gid` = %s" , (guildId)) + cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (DENY_FLAG, guildId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.rejectName(guildId) + + def nameProcessed(self, guildId, newval): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Decline the existing wantname and clear it out + cursor = self.db.cursor() + try: + cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (newval, guildId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.nameProcessed(guildId, newval) + + def addMember(self, guildId, avId, rank): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + if (not self.verifyGuild(guildId)): + return 0 + + # Insert new member into list + try: + # All new members start at rank 1 + cursor = self.db.cursor() + cursor.execute("INSERT INTO `member` VALUES (%s, %s, %s)" , (guildId, avId, rank)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "GuildDB::addMember - reconnect" + self.addMember(guildId, avId, rank) + return + except _mysql_exceptions.IntegrityError,e: + self.notify.warning("IntegrityError adding avId %s to guild %s: %s. Rolling back." % (avId,guildId,e)) + from direct.showbase import PythonUtil + self.notify.warning(str(PythonUtil.StackTrace())) + self.db.rollback() + + def removeMember(self, avId, guildId, guildRank): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Remove member from guild + try: + cursor = self.db.cursor() + cursor.execute("DELETE FROM `guildtokens` WHERE `avid` = %s", avId) + cursor.execute("DELETE FROM `member` WHERE `avId` = %s" , avId) + if (guildRank == 3): + # Removing guild leader, remove all pending name requests as well + cursor.execute("UPDATE `guildinfo` SET `wantname` = %s WHERE `gid` = %s" , (0, guildId)) + cursor.execute("UPDATE `guildinfo` SET `namestatus` = %s WHERE `gid` = %s" , (NOACTION_FLAG, guildId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.removeMember(avId) + return + + if (guildId and self.memberCount(guildId) < 1): + self.removeGuild(guildId) + + # Only used to remove an empty guild. Do not do this to a guild with members + def removeGuild(self, guildId): + try: + cursor = self.db.cursor() + cursor.execute("DELETE FROM `guildtokens` WHERE `gid` = %s" , guildId) + cursor.execute("DELETE FROM `guildinfo` WHERE `gid` = %s", guildId) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.removeGuild(guildId) + return + + def changeRank(self, avId, rank): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Change rank of existing guild member + cursor = self.db.cursor() + try: + cursor.execute("UPDATE `member` SET `rank` = %s WHERE `avId` = %s" , (rank, avId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.changeRank(avId, rank) + + + def getMembers(self, guildId): + if not self.sqlAvailable: + return [] + + # cursor = MySQLdb.cursors.DictCursor(self.db) + cursor = self.db.cursor() + + try: + cursor.execute("SELECT * FROM `member` where `gid` = %s" , guildId) + members = cursor.fetchall() + return members + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error" + return self.getMembers(guildId) + + def genToken(self): + # alpha = string.letters.upper() + alpha = 'ABCDEFHKLMNPRSTUVWXYZ' + # num = string.digits + num = '23456789' + ranNumber = '' + ranAlpha = '' + for i in range(4): + ranNumber = ranNumber + random.choice(num) + for i in range(4): + ranAlpha = ranAlpha + random.choice(alpha) + token = "%s%s" % (ranAlpha, ranNumber) + return token + + def isTokenUnique(self, token): + # Verify that the passed token is unique in the guildToken table + if not self.sqlAvailable: + return "Error: DB not online" + + cursor = self.db.cursor() + try: + cursor.execute("SELECT * FROM `guildtokens` where `tokenid` = %s" , token) + entries = cursor.fetchall() + if len(entries) == 0: + return 1 + else: + return 0 + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error" + return self.isTokenUnique(token) + + def redeemToken(self, token, avId): + # Redeem a token, by adding the avid to the guild in the guildtokens DB + if not self.sqlAvailable: + return "Error: DB not online" + + # Lets first check to make sure the entry is in the guildtokens table + cursor = self.db.cursor() + try: + # print 'Executing Query for %s' % token + cursor.execute("SELECT * FROM `guildtokens` where `tokenid` = %s", token) + entries = cursor.fetchall() + # print len(entries) + if len(entries) == 1: + pass + else: + raise Exception("INVALID_TOKEN") + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error" + return self.redeemToken(token, avId) + + + # Since the entry is there, lets add the avid to the gid + guildToken = entries[0][0] + gNameId = entries[0][3] + creatorAvId = entries[0][4] + # rCount indicates the code type / redeeem rules, i.e. onetime use, + # multi-use, unlimited use. + rCount = entries[0][5] + # print guildToken, gNameId + rank = 1 + + # Make sure we don't have too many members in the guild + from otp.friends import GuildManagerUD + count = self.memberCount(gNameId) + if (count >= GuildManagerUD.MAX_MEMBERS): + raise Exception("GUILD_FULL") + + self.addMember(gNameId, avId, rank) + + # Now delete the entry from the guildToken table + # If rCount is (NULL) None or 1, delete + + if rCount == None or rCount == 1: + self.deleteFriendToken(guildToken) + + # If rCount >= 2, then we need to decrement the rcount in the rec by 1 + + if rCount >= 2: + self.decRCountInDB(token, creatorAvId, rCount - 1) + + # Return the Guild Name + + return [gNameId, creatorAvId] + + def deleteFriendToken(self, token): + if not self.sqlAvailable: + return "Guild DB Unavailable" + + try: + cursor = self.db.cursor() + cursor.execute("DELETE FROM `guildtokens` WHERE `tokenid` = %s" , token) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.deleteFriendToken(token) + return + + def checkForTooManyTokens(self, avId): + # Check the guildToken table to make sure the the passed avId does not + # have too many off-line guild join requests pending. + # If returns True = Too many entries in table pending. They either have + # to be redeemed or wait until they expire; the ttl. + # If returns False = The user does not have too many. + # Currently the max number of entries is 20. It is set locally in this + # function + + if not self.sqlAvailable: + return "Guild DB Unavailable" + + try: + cursor = self.db.cursor() + cursor.execute("SELECT * FROM `guildtokens` WHERE `avid` = %s", avId) + entries = cursor.fetchall() + if len(entries) >= 20: + return True + else: + return False + + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error - Checking for Too Many Tokens from AVID" + return self.checkForTooManyTokens(avId) + + def getFriendToken(self, guildId, avId, ttl = 10): + # Generate a friend token for the membership to the guildId passed + # Step .5: Verify that the user does not have too many tokens pending + # Step One: Generate a token (self.genToken) + # Step Two: Verify that the token does not collide with an existing one + # and if self.bwDictOnline == True, check for bad words + # Step Three: Insert entry into the DB + # Step Four: Return token string + + # Note: The TTL is in days + + if not self.sqlAvailable: + return "Guild DB Unavailable" + + # Step .5: + + tooManyTokens = self.checkForTooManyTokens(avId) + if tooManyTokens == True: + # Too many tokens pending + # Return string 'TOO_MANY_TOKENS' + return 'TOO_MANY_TOKENS' + + from otp.friends import GuildManagerUD + count = self.memberCount(guildId) + if (count >= GuildManagerUD.MAX_MEMBERS): + # Too many people in the guild, refuse to issue a token + return 'GUILD FULL' + + # Step One: + + ourToken = self.genToken() + # print 'Token Generated %s.' % ourToken + # Step Two: + + if self.bwDictPath: + while not self.isTokenUnique(ourToken) or badwordpy.test(ourToken): + self.notify.info('Token is not unique or contains a bad word: %s' % ourToken) + ourToken = self.genToken() + + else: + while self.isTokenUnique(ourToken) == 0: + self.notify.info('Token is not unique: %s' % ourToken) + ourToken = self.genToken() + + # Step Three: + + foo = time.localtime() + date_time = "%d-%d-%d %d:%d:%d" % (foo[0], foo[1], foo[2], foo[3], foo[4], foo[5]) + + try: + cursor = self.db.cursor() + cursor.execute("INSERT INTO `guildtokens` VALUES (%s, %s, %s, %s, %s, NULL)", (ourToken, date_time, ttl, guildId, avId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "GuildDB::getFriendToken Error " + self.getFriendToken(guildId, avId, ttl) + + # Step Four: + + return ourToken + + def changeTokenRValue(self, avatarId, tokenString, rValue): + # Change the rValue for the token (tokenString) + # avatarId is passed to allow us to verify that the token being passed + # is associated with this requesting avId + + try: + cursor = self.db.cursor() + cursor.execute("UPDATE `guildtokens` SET `rcount` = %s WHERE `avid` = %s AND `tokenid` = %s", (rValue, avatarId, tokenString)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.changeTokenRValue(avatarId, tokenString, rValue) + self.notify.debug('Guild Token (%s) rValue (%s) Updated for %s' % (tokenString, rValue, avatarId)) + + def decRCountInDB(self, token, avId, newRCount): + # This will decrement the rcount value in the guildtokens table by one + # Normally, this will get called when we have a multi-use code that + # has to have its record updated. + cursor = self.db.cursor() + try: + cursor.execute("UPDATE `guildtokens` SET `rcount` = %s WHERE `tokenid` = %s AND `avid` = %s", (newRCount, token, avId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + self.decRCountInDB(token, avId, newRCount) + + + def startCleanUpExpiredTokens(self): + taskMgr.remove('cleanUpTokensTask') + taskMgr.doMethodLater(300, self.tokenDeleteSQLCall, 'cleanUpTokensTask') + + def stopCleanUpExpiredTokens(self): + taskMgr.remove('cleanUpTokensTask') + + def tokenDeleteSQLCall(self, task): + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("DELETE FROM `guildtokens` WHERE(`createtime` + INTERVAL `ttl` DAY) < NOW() AND `rcount` = NULL ORDER BY `createtime` LIMIT 100") + self.db.commit() + self.notify.debug('Executing expired token cleanup in DB. Deleting up to 100 expired/old tokens') + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "GuildDB::tokenDeleteSQLCall Error " + self.tokenDeleteSQLCall(task) + return Task.again + + def checkForUnlimitedUseToken(self, avId): + # Check the DB to see if avId, currently has a unlimited use token; + # then token can be active or inactive + # If unlimited token exists, the tokenId is returned. + # If unlimited token does not exist, return None + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("SELECT `tokenid` FROM `guildtokens` WHERE avid = %s AND `rcount` = -1", (avId)) + entries = cursor.fetchall() + if len(entries) == 0: + return None + else: + return entries[0][0] + + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error - checkForUnlimitedUseToken" + return self.checkForUnlimitedUseToken(avId) + + def returnLimitedUseTokens(self, avId): + # Returns an int value, corrisponding to the number of limited + # use tokens in the DB for the avId provided. + # Limited use tokens count as "one time use" and "limited multi use". + # If nothing is in the DB, 0 is returned + + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("SELECT `tokenid` FROM `guildtokens` WHERE avid = %s AND (`rcount` != -1 OR `rcount` IS NULL)", (avId)) + entries = cursor.fetchall() + recCount = len(entries) + return recCount + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Operational Error - returnLimitedUseTokens" + return self.returnLimitedUseTokens(avId) + + def clearLimitedUseTokens(self, avId): + # Deletes all "Limited Use" tokens for the provided avId from the DB + + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("DELETE FROM `guildtokens` WHERE `avid` = %s AND (`rcount` != -1 OR `rcount` IS NULL)", (avId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Error in clearLimitedUseTokens - reconnecting to DB" + self.clearLimitedUseTokens(avId) + return + + def clearPermUseTokens(self, avId): + # Deletes all "Perm Use" tokens for the provided avId from the DB + + if not self.sqlAvailable: + return "DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("DELETE FROM `guildtokens` WHERE `avid` = %s AND `rcount` = -1", (avId)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - Error in clearPermUseTokens - reconnecting to DB" + self.clearPermUseTokens(avId) + return + + def suspendToken(self, avId, token): + # Suspend a perm use token + # This is done by changing the rcount value for the row to -2 + + if not self.sqlAvailable: + return "Guild DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("UPDATE `guildtokens` SET `rcount` = -2 WHERE `avid` = %s AND `tokenid` = %s", (avId, token)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - reconnecting to DB in suspendToken" + self.suspendToken(avId, token) + return + + def reEnableToken(self, avId, token): + # Re-enable a perm use token + # This is done by changing the rcount value for the row to -1 + + if not self.sqlAvailable: + return "Guild DB Unavailable" + + cursor = self.db.cursor() + try: + cursor.execute("UPDATE `guildtokens` SET `rcount` = -1 WHERE `avid` = %s AND `tokenid` = %s", (avId, token)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + self.reconnect() + print "DEBUG - reconnecting to DB in reEnableToken" + self.reEnableToken(avId, token) + return + +# Commented out next several functions, waiting for "STAF" access for email. Will uncomment once +# "STAF" Interface is worked out. + +## def getEmailNotificationPref(self, avId): +## # Get the email notification preferences from the DB for the avid passed in +## # If no rec exists, create a default entry and return it +## # Note: Default config is notify = 0 (no), email set to NULL +## # Returns a list: [notify, emailAddress] + +## if not self.sqlAvailable: +## return "Guild DB Unavailable" + +## cursor = self.db.cursor() +## try: +## cursor.execute("SELECT notify, emailaddress FROM `email_notify` WHERE `avid` = %s" , avId) +## entry = cursor.fetchall() +## if len(entry) == 1: +## return [entry[0][0], entry[0][1]] +## else: +## self.setEmailNotificationPref(avId, 0, None) +## return [0, None] + +## except _mysql_exceptions.OperationalError,e: +## self.reconnect() +## return getEmailNotificationPref(avId) + +## def setEmailNotificationPref(self, avId, notify, emailAddress): +## # Set the Email Notification Preferences for an avId +## # Notify = 0 : Do not notify +## # Notify = 1 : Notify + +## try: +## cursor = self.db.cursor() +## if emailAddress: +## cursor.execute("INSERT INTO `email_notify` VALUES (%s, %s, %s)" , (avId, notify, emailAddress)) +## else: +## cursor.execute("INSERT INTO `email_notify` (`avid`, `notify`) VALUES (%s, %s)" , (avId, notify)) +## self.db.commit() +## except _mysql_exceptions.OperationalError,e: +## self.reconnect() +## print "GuildDB::setEmailNotificationPref - reconnect" +## self.setEmailNotificationPref(avId, notify, emailAddress) +## return +## except _mysql_exceptions.IntegrityError,e: +## print "DEBUG - error is ", e + +## def updateNotificationPref(self, avId, notify, emailAddress): +## # Update the notification rec in the DB + +## try: +## cursor = self.db.cursor() +## cursor.execute("UPDATE email_notify SET notify = %s, emailaddress = %s WHERE avid = %s", (notify, emailAddress, avId)) +## self.db.commit() +## except _mysql_exceptions.OperationalError,e: +## self.reconnect() +## self.updateNotificationPref(avId, notify, emailAddress) + + #for debugging only + def dumpGuildTable(self): + assert self.db,"Tried to call dumpGuildTable when DB was closed." + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("SELECT * FROM guilds") + return cursor.fetchallDict() + + #for debugging only + def clearGuildTable(self): + assert self.db,"Tried to call clearGuildTable when DB was closed." + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("TRUNCATE TABLE guilds") + self.db.commit() + + diff --git a/otp/src/friends/GuildManager.py b/otp/src/friends/GuildManager.py new file mode 100644 index 0000000..a2b4786 --- /dev/null +++ b/otp/src/friends/GuildManager.py @@ -0,0 +1,499 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.distributed import OtpDoGlobals +from otp.otpbase import OTPLocalizer +from otp.otpbase import OTPGlobals +from otp.avatar.AvatarHandle import AvatarHandle +from otp.ai import AIInterestHandles + +GUILDRANK_GM = 3 +GUILDRANK_OFFICER = 2 +GUILDRANK_MEMBER = 1 + + +import Queue + +class GuildMemberInfo(AvatarHandle): + def __init__(self, name, isOnline, rank, bandId): + self.name = name + self.rank = rank + self.bandId = bandId + self.onlineYesNo = isOnline + + def getName(self): + return self.name + + def getRank(self): + return self.rank + + def getBandId(self): + return self.bandId + + def isOnline(self): + return self.onlineYesNo + + def isUnderstandable(self): + # This is for compatibility with the ClientRepository's + # identifyFriend() function + return True + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def sendTeleportQuery(self, sendToId, localBandMgrId, localBandId, localGuildId, localShardId): + base.cr.guildManager.d_reflectTeleportQuery(sendToId, localBandMgrId, localBandId, localGuildId, localShardId) + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def sendTeleportResponse(self, available, shardId, instanceDoId, areaDoId, sendToId = None): + base.cr.guildManager.d_reflectTeleportResponse(available, shardId, instanceDoId, areaDoId, sendToId) + +class GuildManager(DistributedObjectGlobal): + """ + See Also: + "otp/src/friends/GuildManagerUD.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('GuildManager') + + def __init__(self, cr): + DistributedObjectGlobal.__init__(self, cr) + + self.id2Name = {} + self.id2BandId = {} + self.id2Rank = {} + self.id2Online = {} + # queue for incoming msgs we can't display yet + self.pendingMsgs = [] + + self.whiteListEnabled = base.config.GetBool('whitelist-chat-enabled', 1) + + # These are the email notification preferences for the avatar + # They are set by respondEmailNotificationPref + + self.emailNotification = 0 + self.emailNotificationAddress = None + + self.receivingNewList = False + + self.spamGateOpen = True + + def _allowMemberList(self,task): + self.spamGateOpen = True + return task.done + + # Functions called by the client + def memberList(self): + if self.spamGateOpen: + self.sendUpdate("memberList", []) + self.spamGateOpen = False + taskMgr.doMethodLater(60.0,self._allowMemberList,"allowMemberList") + + def createGuild(self): + # Make sure to decline any other guild invitations + messenger.send("declineGuildInvitation") + + self.sendUpdate("createGuild", []) + + def setWantName(self, newName): + self.sendUpdate("setWantName", [newName]) + + def removeMember(self, avatarId): + self.sendUpdate("removeMember", [avatarId]) + + def changeRank(self, avatarId, rank): + self.sendUpdate("changeRank", [avatarId, rank]) + + def statusRequest(self): + self.sendUpdate("statusRequest", []) + + def requestLeaderboardTopTen(self): + self.sendUpdate("requestLeaderboardTopTen", []) + + def sendRequestInvite(self, avatarId): + self.sendUpdate("requestInvite", [avatarId]) + + def sendAcceptInvite(self): + self.sendUpdate("acceptInvite", []) + + def sendDeclineInvite(self): + self.sendUpdate("declineInvite", []) + + def sendTalk(self,msgText,chatFlags=0): + self.sendUpdate("setTalkGroup",[0,0,"",msgText,[],0]) + + def setTalkGroup(self, fromAv, fromAC, avatarName, chat, mods, flags): + if hasattr(base, "localAvatar"): + message, scrubbed = localAvatar.scrubTalk(chat, mods) + base.talkAssistant.receiveGuildTalk(fromAv, fromAC, avatarName, message, scrubbed) + + + def sendSC(self,msgIndex): + self.sendUpdate("sendSC",[msgIndex]) + + def sendSCQuest(self,questInt,msgType,taskNum): + self.sendUpdate("sendSCQuest",[questInt,msgType,taskNum]) + + def sendTokenRequest(self): + self.sendUpdate("sendTokenRequest", []) + + def sendTokenForJoinRequest(self, token): + # print "GuildManager.sendTOkenForJoinRequest() Called : %s" % token + name = base.localAvatar.getName() + self.sendUpdate("sendTokenForJoinRequest", [token, name]) + + def isInGuild(self, avId): + return avId in self.id2Name + + def getRank(self, avId): + return self.id2Rank.get(avId) + + def getBandId(self, avId): + return self.id2BandId.get(avId) + + def getMemberInfo(self, avId): + if self.isInGuild(avId): + return GuildMemberInfo(self.id2Name[avId], + self.id2Online[avId], + self.id2Rank[avId], + self.id2BandId[avId], + ) + return None + + def getOptionsFor(self, avId): + """ + Returns (canpromote, candemote, cankick) based on whether + localAvatar can perform these operations on avId. + + Returns None if avId is not in guild. + """ + if self.isInGuild(avId): + myRank = localAvatar.getGuildRank() + hisRank = self.id2Rank[avId] + + canpromote = False + candemote = False + cankick = False + + if myRank == GUILDRANK_GM: + if hisRank == GUILDRANK_OFFICER: + candemote = True + elif hisRank == GUILDRANK_MEMBER: + canpromote = True + if myRank > GUILDRANK_MEMBER and \ + hisRank <= GUILDRANK_MEMBER: + cankick = True + + return (canpromote, candemote, cankick) + else: + return None + + def updateTokenRValue(self, tokenString, rValue): + # Send this token and redeem value up to the server. + # The token in tokenString will be assigned the rValue + # Just to be sure we're sending the right thing, cast rValue to an int + rValue = int(rValue) + # print 'Sending following values: %s, %s' % (tokenString, rValue) + self.sendUpdate("sendTokenRValue", [tokenString, rValue]) + if rValue == -1: + base.localAvatar.guiMgr.guildPage.receivePermTokenValue(tokenString) + + def requestPermToken(self): + # Requests the perm member token (if one exists). + self.sendUpdate("sendPermToken", []) + + def requestNonPermTokenCount(self): + # Requests a count of the non perm member tokens + self.sendUpdate('sendNonPermTokenCount', []) + + def requestClearTokens(self, type): + # Requests tokens are cleared for the calling avatar. + # type can be one of two types: + # 0 = One time and limited mulituse codes + # 1 = Perm Token + self.sendUpdate("sendClearTokens", [type]) + + + #Functions called from UD + + def receiveMember(self, member): + if not self.receivingNewList: + self.receivingNewList = True + self.newList = [] + + self.newList.append(member) + + def clearMembers(self): + self.newList = [] + self.receiveMembersDone() + + def receiveMembersDone(self): + self.receivingNewList = False + + memberlist = self.newList + self.newList = [] + + self.id2Name = {} + self.id2Rank = {} + self.id2BandId = {} + + # Pass the member list to the guild GUI for use + for guy in memberlist: + id = guy[0] + name = guy[1] + rank = guy[2] + isOnline = guy[3] + self.id2Name[id] = name + self.id2Rank[id] = rank + self.id2Online[id] = isOnline + self.id2BandId[id] = tuple(guy[4:6]) + + for id,msg in self.pendingMsgs: + # move this check to chatAssistant some day + if not self.cr.avatarFriendsManager.checkIgnored(id): + #base.talkAssistant.receiveGuildMessage("%s %s %s" % (self.id2Name.get(id,"Unknown"), + # OTPLocalizer.GuildPrefix, + # msg)) + base.talkAssistant.receiveGuildMessage(msg, id, self.id2Name.get(id,"Unknown")) + + if localAvatar.getGuildId(): + self.accept(self.cr.StopVisibilityEvent, + self.handleLogout) + else: + self.ignore(self.cr.StopVisibilityEvent) + + if hasattr(base, "localAvatar"): + base.localAvatar.guiMgr.guildPage.receiveMembers(memberlist) + messenger.send('guildMemberUpdated', sentArgs = [localAvatar.doId]) + + + def guildStatusUpdate(self, guildId, guildName, guildRank): + if hasattr(base, "localAvatar"): + base.localAvatar.guildStatusUpdate(guildId, guildName, guildRank) + self.memberList() + + def guildNameReject(self, guildId): + if hasattr(base, "localAvatar"): + base.localAvatar.guildNameReject(guildId) + + def guildNameChange(self, guildName, changeStatus): + if hasattr(base, "localAvatar"): + base.localAvatar.guildNameChange(guildName, changeStatus) + + def guildNameUpdate(self, avatarId, guildName): + print "DEBUG - guildNameUpdate for ", avatarId, " to ", guildName + + def invitationFrom(self, avatarId, avatarName, guildId, guildName): + print "GM invitationFrom %s(%d)" % (avatarName,avatarId) + if hasattr(base, "localAvatar"): + base.localAvatar.guiMgr.handleGuildInvitation(avatarId, avatarName, guildId, guildName) + + def retractInvite(self,avatarId): + print "GM retraction" + + def guildAcceptInvite(self, avatarId): + # Tell ourselves the person we are inviting accepted us + print "sending accept event" + messenger.send(OTPGlobals.GuildAcceptInviteEvent,[avatarId]) + + def leaderboardTopTen(self, stuff): + base.localAvatar.guiMgr.handleTopTen(stuff) + + def guildRejectInvite(self, avatarId, reason): + # Tell ourselves the person we are inviting rejected us + messenger.send(OTPGlobals.GuildRejectInviteEvent,[avatarId,reason]) + + def rejectInvite(self,avatarId,reason): + # print "GM rejectInvite to %d because of %d" % (avatarId,reason) + pass + + + + + def recvSC(self,senderId,msgIndex): + senderName = self.id2Name.get(senderId,None) + if (senderName): + # move this check to chatAssistant some day + if not self.cr.avatarFriendsManager.checkIgnored(senderId): + displayMess = "%s %s %s" % (senderName, OTPLocalizer.GuildPrefix, + OTPLocalizer.SpeedChatStaticText[msgIndex]) + message = OTPLocalizer.SpeedChatStaticText[msgIndex] + base.talkAssistant.receiveGuildMessage(message, senderId, senderName) + else: + self.pendingMsgs.append([senderId,OTPLocalizer.SpeedChatStaticText[msgIndex]]) + self.memberList() + + def recvSCQuest(self,senderId,questInt,msgType,taskNum): + senderName = self.id2Name.get(senderId,None) + message = base.talkAssistant.SCDecoder.decodeSCQuestMsgInt(questInt,msgType,taskNum) + if (senderName): + # move this check to chatAssistant some day + if not self.cr.avatarFriendsManager.checkIgnored(senderId): + displayMess = "%s %s %s" % (senderName, OTPLocalizer.GuildPrefix, message) + base.talkAssistant.receiveGuildMessage(message, senderId, senderName) + else: + self.pendingMsgs.append([senderId,message]) + self.memberList() + + def recvAvatarOnline(self, avatarId, avatarName, bandManagerId, bandId): + self.id2Online[avatarId] = True + # Print out a message in the chat log, update the panel we're looking at, play a tick sound, etc! + if hasattr(base, 'localAvatar') and \ + avatarId != base.localAvatar.doId: + # move this check to chatAssistant some day + if not self.cr.avatarFriendsManager.checkIgnored(avatarId): + base.talkAssistant.receiveGuildUpdate(avatarId,avatarName,True) + else: + return + # Send message so guild guis can pick up the update + messenger.send("guildMemberOnlineStatus", [avatarId, 1]) + + def recvAvatarOffline(self,avatarId,avatarName): + # Print out a message in the chat log, update the panel we're looking at, play a tick sound, etc! + # Guard against the case where the localAvatar just logged out and he is hearing his own + # offline message. + self.id2BandId[avatarId] = (0,0) + self.id2Online[avatarId] = False + if hasattr(base, "localAvatar") and \ + avatarId != base.localAvatar.doId: + # move this check to chatAssistant some day + if not self.cr.avatarFriendsManager.checkIgnored(avatarId): + base.talkAssistant.receiveGuildUpdate(avatarId,avatarName,False) + # Send message so guild guis can pick up the update + messenger.send("guildMemberOnlineStatus", [avatarId, 0]) + + def recvMemberAdded(self, memberInfo): + avatarId, avatarName, rank, isOnline, bandManagerId, bandId = memberInfo + self.id2Name[avatarId] = avatarName + self.id2Rank[avatarId] = rank + self.id2BandId[avatarId] = (bandManagerId, bandId) + self.id2Online[avatarId] = isOnline + if hasattr(base, "localAvatar"): + base.localAvatar.guiMgr.guildPage.addMember(memberInfo) + messenger.send('guildMemberUpdated', sentArgs = [avatarId]) + + def recvMemberRemoved(self, avatarId): + if avatarId == localAvatar.doId: + self.clearMembers() + else: + self.id2Name.pop(avatarId, None) + self.id2Rank.pop(avatarId, None) + self.id2BandId.pop(avatarId, None) + self.id2Online.pop(avatarId, None) + if hasattr(base, 'localAvatar'): + base.localAvatar.guiMgr.guildPage.removeMember(avatarId) + messenger.send('guildMemberUpdated', sentArgs = [avatarId]) + + def recvMemberUpdateRank(self, avatarId, rank): + self.id2Rank[avatarId] = rank + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + base.localAvatar.guiMgr.guildPage.updateGuildMemberRank(avatarId, rank) + messenger.send('guildMemberUpdated', sentArgs = [avatarId]) + + def recvMemberUpdateBandId(self, avatarId, bandManagerId, bandId): + self.id2BandId[avatarId] = (bandManagerId, bandId) + messenger.send('guildMemberUpdated', sentArgs = [avatarId]) + + def recvTokenInviteValue(self, tokenValue, preExistPerm): + # print "Token Received from server: %s" % (tokenValue) + # if tokenValue == 'TOO_MANY_TOKENS': + # print "WARNING: This Avatar has too many tokens pending" + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + base.localAvatar.guiMgr.guildPage.displayInviteGuild(tokenValue, preExistPerm) + + def recvTokenRedeemMessage(self, guildName): + # print "Guild (join) message received from server: %s" % (guildName) + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + if guildName == '***ERROR - GUILD CODE INVALID***': + # print "Warning: The guild name is false, request doesn't exist" + base.localAvatar.guiMgr.guildPage.displayRedeemErrorMessage(OTPLocalizer.GuildRedeemErrorInvalidToken) + elif guildName == '***ERROR - GUILD FULL***': + base.localAvatar.guiMgr.guildPage.displayRedeemErrorMessage(OTPLocalizer.GuildRedeemErrorGuildFull) + else: + # print "You have joined guild %s" % guildName + base.localAvatar.guiMgr.guildPage.displayRedeemConfirmMessage(guildName) + + def recvTokenRedeemedByPlayerMessage(self, redeemerName): + # Display a message in the message stack, indicating that a + # guild code that a player issued has been redeeemed by the name passed + # in the string redeemerName + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + base.localAvatar.guiMgr.guildPage.notifyTokenGeneratorOfRedeem(redeemerName) + + def recvPermToken(self, token): + # Response to a request to get the perm invite token for this avatar, + # should they have one. + # token will either be the token string 'ABCD1234', + # or will be '0' (indicating that the avatar does not have a perm token) + + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + if token == '0': + base.localAvatar.guiMgr.guildPage.receivePermTokenValue(None) + else: + base.localAvatar.guiMgr.guildPage.receivePermTokenValue(token) + + def requestEmailNotificationPref(self): + # Request that the guild email notification prefs are sent down + # to the client + + self.sendUpdate('sendRequestEmailNotificationPref', []) + + def respondEmailNotificationPref(self, notify, emailAddress): + # Response from the UD, in reference to a request for + # email notification preferences + + self.emailNotification = notify + if emailAddress == 'None': + self.emailNotificationAddress = None + else: + self.emailNotificationAddress = emailAddress + + def getEmailNotificationPref(self): + # Return the email notification prefs + + return [self.emailNotification, self.emailNotificationAddress] + + def requestEmailNotificationPrefUpdate(self, notify, emailAddress): + # Request that the UD to change the email notification prefs + # for the avatar. + # Notify == 0 : Off + # Notify == 1 : On + + self.sendUpdate("sendEmailNotificationPrefUpdate", [notify, emailAddress]) + # Now set the local client side variables for the notify data + + self.emailNotification = notify + if emailAddress == 'None': + self.emailNotificationAddress = None + else: + self.emailNotificationAddress = emailAddress + + def recvNonPermTokenCount(self, tCount): + # Receive count of Non Perm tokens from the UD + if hasattr(base, 'localAvatar') and base.localAvatar.guiMgr: + base.localAvatar.guiMgr.guildPage.receiveNonPermTokenCount(tCount) + + + # teleport support + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def d_reflectTeleportQuery(self, sendToId, localBandMgrId, localBandId, localGuildId, localShardId): + self.sendUpdate('reflectTeleportQuery', [sendToId, localBandMgrId, localBandId, localGuildId, localShardId]) + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def teleportQuery(self, requesterId, requesterBandMgrId, requesterBandId, requesterGuildId, requesterShardId): + if self.cr.teleportMgr: + self.cr.teleportMgr.handleAvatarTeleportQuery(requesterId, requesterBandMgrId, requesterBandId, requesterGuildId, requesterShardId) + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def d_reflectTeleportResponse(self, available, shardId, instanceDoId, areaDoId, sendToId): + self.sendUpdate('reflectTeleportResponse', [sendToId, available, shardId, instanceDoId, areaDoId]) + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def teleportResponse(self, responderId, available, + shardId, instanceDoId, areaDoId): + if self.cr.teleportMgr: + self.cr.teleportMgr.handleAvatarTeleportResponse(responderId, available, + shardId, instanceDoId, areaDoId, + ) + + @report(types = ['args'], dConfigParam = 'guildmgr') + def handleLogout(self, *args, **kw): + self.cr.removeAIInterest(AIInterestHandles.PIRATES_GUILD) diff --git a/otp/src/friends/GuildManagerAI.py b/otp/src/friends/GuildManagerAI.py new file mode 100644 index 0000000..9a6999e --- /dev/null +++ b/otp/src/friends/GuildManagerAI.py @@ -0,0 +1,31 @@ +from direct.distributed.DistributedObjectGlobalAI import DistributedObjectGlobalAI +from direct.directnotify.DirectNotifyGlobal import directNotify + + +class GuildManagerAI(DistributedObjectGlobalAI): + """ + The Player Friends Manager is a global object. + This object handles client requests on player-level (as opposed to avatar-level) friends. + + See Also: + "otp/src/friends/GuildManagerAIUD.py" + "otp/src/friends/AvatarFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('GuildManagerAI') + + def __init__(self, cr): + DistributedObjectGlobalAI.__init__(self, cr) + self.doNotListenToChannel = True + + def avatarOnline(self, avatarId): + pass + + def avatarOffline(self, avatarId, rep): + simbase.air.sendUpdateToChannel(self, self.doId, "updateRep", [avatarId, rep]) + + def d_sendAvatarBandId(self, avatarId, bandManagerId, bandId): + simbase.air.sendUpdateToChannel(self, self.doId, "sendAvatarBandId", [avatarId, bandManagerId, bandId]) + diff --git a/otp/src/friends/GuildManagerUD.py b/otp/src/friends/GuildManagerUD.py new file mode 100644 index 0000000..79388e4 --- /dev/null +++ b/otp/src/friends/GuildManagerUD.py @@ -0,0 +1,959 @@ +from itertools import izip +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from otp.distributed import OtpDoGlobals +from otp.ai import AIMsgTypes +from otp.uberdog.UberDogUtil import ManagedAsyncRequest +from otp.uberdog.RejectCode import RejectCode +from otp.ai import AIInterestHandles + +from direct.directnotify.DirectNotifyGlobal import directNotify + +from otp.friends.FriendInfo import FriendInfo +from otp.friends.GuildDB import NOACTION_FLAG,REVIEW_FLAG,DENY_FLAG,APPROVE_FLAG,ALLDONE_FLAG +from otp.otpbase import OTPLocalizer + +import time +import string + +#-------------------------------------------------- + +ONLINE = 1 +OFFLINE = 0 + +GUILDRANK_GM = 3 +GUILDRANK_OFFICER = 2 +GUILDRANK_MEMBER = 1 + +MAX_MEMBERS = 500 + +class GuildManagerUD(DistributedObjectGlobalUD): + """ + The Avatar Friends Manager is a global object. + This object handles client requests on avatar-level (as opposed to player-level) friends. + + See Also: + "otp/src/friends/GuildManager.py" + "otp/src/friends/PlayerFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('GuildManagerUD') + + def __init__(self, air): + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + self.debugAvId = 0 + + self.DBuser = uber.config.GetString("mysql-user","ud_rw") + self.DBpasswd = uber.config.GetString("mysql-passwd","r3adwr1te") + + self.DBhost = uber.config.GetString("guild-db-host","localhost") + self.DBport = uber.config.GetInt("guild-db-port",3306) + self.DBname = uber.config.GetString("guild-db-name","guilds") + + from otp.friends.GuildDB import GuildDB + self.db = GuildDB(host=self.DBhost, + port=self.DBport, + user=self.DBuser, + passwd=self.DBpasswd, + dbname=self.DBname) + + self.asyncRequests = {} + + # Maintain a big list of Avatar Names to give out member info with + self.isAvatarOnline = {} + self.avatarName = {} + self.pendingSends = {} + + self.avatarId2Guild = {} + self.avatarId2Rank = {} + + self.avatarId2Invite = {} + + self.avatarId2BandId = {} + + # Maintain information for Token Requests (Generate) + + self.nextTokenRequestId = 0 + self.requestId2AvatarId = {} + + # Maintain information for Token Requests (Redeem) + + self.nextRedeemTokenRequestId = 0 + self.redeemTokenRequestId2AvatarId = {} + + # This next one is to hold the time stamp of an avatar's last + # token redeem request. Format is {avatarId : time) + # Where time is the epoch; stored in 1 second precision + + self.redeemTokenTimeStamps = {} + + self.accept("avatarOnline", self.avatarOnline) + self.accept("avatarOffline", self.avatarOffline) + + self.funcTally = {} + + taskMgr.doMethodLater(60.0, self.logFuncTally, "logFuncTally") + + + def tallyFunction(self,funcName): + if funcName not in self.funcTally: + self.funcTally[funcName] = 1 + funcs = self.funcTally.keys() + funcs.sort() + self.notify.info("funcs tallied: %s" % funcs) + else: + self.funcTally[funcName] += 1 + + def logFuncTally(self,task): + funcs = self.funcTally.keys() + funcs.sort() + str = ["%s" % self.funcTally[f] for f in funcs] + str = string.join(str," ") + self.notify.info("funcTally: %s" % str) + return task.again + + + def announceGenerate(self): + assert self.notify.debugCall() + DistributedObjectGlobalUD.announceGenerate(self) + self.sendUpdateToChannel( + AIMsgTypes.CHANNEL_CLIENT_BROADCAST, "online", []) + self.sendUpdateToChannel( + AIMsgTypes.OTP_CHANNEL_AI_AND_UD_BROADCAST, "online", []) + + + def delete(self): + assert self.notify.debugCall() + self.ignoreAll() + for i in self.asyncRequests.values(): + i.delete() + DistributedObjectGlobalUD.delete(self) + + def sendUpdateToGuildChannel(self, guildId, field, parameters): + if guildId: + self.sendUpdateToChannel((self.doId<<32)+guildId, + field, + parameters) + + + def sendUpdateToGuildChannelWithSender(self, guildId, field, parameters): + messageSender = self.air.getMsgSender() + if guildId: + channelId = (self.doId<<32)+guildId + self.air.sendUpdateToChannelFrom(self, channelId, field, messageSender, parameters) + + + # Functions called by the client + def acceptInvite(self): + self.tallyFunction("acceptInvite") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + guildId,inviterId = self.avatarId2Invite.pop(avatarId,(0,0)) + + if guildId > 0: + self._addMember(guildId, avatarId) + self.air.writeServerEvent('acceptGuildInvite', avatarId, '%s|%s' % (inviterId, guildId)) + self.sendUpdateToAvatarId( + inviterId, 'guildAcceptInvite', [avatarId]) + + def declineInvite(self): + self.tallyFunction("declineInvite") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + guildId,inviterId = self.avatarId2Invite.pop(avatarId,(0,0)) + + if guildId > 0: + self.air.writeServerEvent('declineGuildInvite', avatarId, '%s|%s' % (inviterId, guildId)) + self.sendUpdateToAvatarId( + inviterId, 'guildRejectInvite', [avatarId, RejectCode.NO_GUILD]) + + def createGuild(self): + self.tallyFunction("createGuild") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + self.air.writeServerEvent('createGuild', avatarId, '') + # Add a new guild to the database + self.db.createGuild(avatarId) + self._sendStatus(avatarId) + + def _sendFinishedList(self, player): + """ + Removes the associated pendingSend from the dictionary + and sends the info to 'player'. + """ + if self.debugAvId == player: + assert self.notify.warning('_sendFinishedList(%s)' % (player,)) + + guildInfo = self.pendingSends.pop(player, None) + if guildInfo: + if 0: + # alternate coolness + guildmates, haveData = izip(*guildInfo) + else: + guildmates = [x[0] for x in guildInfo] + haveData = [x[1] for x in guildInfo] + + if __dev__: + assert False not in haveData + pass + if self.debugAvId == player: + assert self.notify.warning('packaging info...') + packagedInfo = [] + for guildId,avid,rank in guildmates: + # Bundle up all the info into send format + bandId = self.avatarId2BandId.get(avid) + if not bandId: + bandId = (0, 0) + packagedInfo.append( ( avid, + self.avatarName.get(avid, 'Unknown'), + rank, + self.isAvatarOnline.get(avid, OFFLINE), + bandId[0], + bandId[1], + ) + ) + if self.debugAvId == player: + for item in packagedInfo: + assert self.notify.warning('%s' % (item,)) + # Send information to player + for member in packagedInfo: + self.sendUpdateToAvatarId(player,"receiveMember",[member]) + self.sendUpdateToAvatarId(player,"receiveMembersDone",[]) + + def _sendFinishedLists(self, arrivingPlayer): + """ + Upon receiving a player's data, look to see if anyone + was waiting for it. If so, update that status. If it + was the last one needed in any pending list, send out + that list. + """ + finishedLists = [] + for player,guildInfo in self.pendingSends.iteritems(): + send = True + for infoItem in guildInfo: + [(guildId,avId,rank), haveData] = infoItem + infoItem[1] |= (avId == arrivingPlayer) + send &= infoItem[1] + + if self.debugAvId == player and \ + avId == arrivingPlayer: + assert self.notify.warning('received info for %s' % (avId,)) + if send: + if self.debugAvId == player: + assert self.notify.warning('finished list, now sending') + finishedLists.append(player) + + for player in finishedLists: + self._sendFinishedList(player) + + + def memberInfo(self, avatarId, context, info): + """ + We have received our data request from the avatar state + server. Use it to update our guild member info and then + send out any pending member lists that this info completes. + """ + self.tallyFunction("memberInfo") + + self.ignore("doFieldQueryFailed-%s"%context) + taskMgr.remove("memberInfoFailure-%s"%context) + + name = info['setName'][0] + bandId = info['setBandId'] + self.avatarName[avatarId] = name + self.avatarId2BandId[avatarId] = bandId + # Initialize this person in isAvatarOnline array + if not self.isAvatarOnline.setdefault(avatarId, OFFLINE) == OFFLINE: + # They're online, this must have been the initial name request + # Tell my guildmates I'm online + gId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(gId,"recvAvatarOnline",[avatarId, name, bandId[0], bandId[1]]) + + self._sendFinishedLists(avatarId) + + + def memberInfoFailure(self, avatarId, context): + """ + This avatarId gave no result from the gamedb. + Bad avatar. Get it out of the guild DB and clean up. + """ + gId = self._getGuildId(avatarId) + rank = self._getGuildId(avatarId) + + self.notify.warning("Avatar %s not found in gamedb. Removing from guild %s." % (avatarId, gId)) + self.air.writeServerEvent('removeBrokenGuildMember', avatarId, str(gId)) + + self.ignore("doFieldResponse-%s"%context) + self.ignore("doFieldQueryFailed-%s"%context) + taskMgr.remove("memberInfoFailure-%s"%context) + + self.db.removeMember(avatarId, gId, rank) + + for recipientId in self.pendingSends.keys(): + info = self.pendingSends.get(recipientId,[]) + newInfo = [] + rep = False + for [(guildId,memberId,memberRank), haveData] in info: + if memberId != avatarId: + newInfo.append([(guildId,memberId,memberRank), haveData]) + rep = True + if rep: + self.pendingSends[recipientId] = newInfo + self._sendFinishedLists(0) + + + def memberList(self): + self.tallyFunction("memberList") + avatarId = self.air.getAvatarIdFromSender() + if avatarId and (avatarId not in self.pendingSends): + + guildId = self._getGuildId(avatarId) + + guildfolk = self.db.getMembers(guildId) + + # queue us a member list send + haveData = [(avId in self.avatarName) for guildId,avId,rank in guildfolk] + + self.pendingSends[avatarId] = [[x,y] for x,y in izip(guildfolk,haveData)] + + if False not in haveData: + self._sendFinishedLists(avatarId) + else: + for guildmate, haveData in self.pendingSends[avatarId]: + if not haveData: + avId = guildmate[1] + self.requestMemberInfo(avId) + + def requestMemberInfo(self, avId): + self.tallyFunction("requestMemberInfo") + context=self.air.allocateContext() + dclassName="DistributedPlayerPirateUD" + self.air.contextToClassName[context]=dclassName + self.acceptOnce("doFieldResponse-%s"%context, self.memberInfo, [avId]) + self.acceptOnce("doFieldQueryFailed-%s"%context, self.memberInfoFailure, [avId]) + taskMgr.doMethodLater(30.0, self.memberInfoFailure, "memberInfoFailure-%s"%context, [avId, context]) + self.air.queryObjectFields(dclassName, ['setName', 'setBandId'], avId, context) + + def setWantName(self, newName): + self.tallyFunction("setWantName") + + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + guildId = self._getGuildId(avatarId) + + # Only the GM can rename the guild + if self._getGuildRank(avatarId) == GUILDRANK_GM: + resultmsg = self.db.setWantName(guildId, newName) + if (resultmsg == 2): + # Send a message to the creator that it was denied + self.sendUpdateToAvatarId(avatarId,"guildNameReject",[guildId]) + + def avatarOnline(self, avatarId, avatarType): + self.tallyFunction("avatarOnline") + if avatarId: + # Change status of this avatarId to show they are online + self.isAvatarOnline[avatarId] = ONLINE + + # Also request a name for this avatar for use later + self.requestMemberInfo(avatarId) + + self._sendStatus(avatarId) + + @report(types = ['args'], dConfigParam = 'orphanedavatar') + def avatarOffline(self, avatarId): + self.tallyFunction("avatarOffline") + # Change status of this avatarId to show they are offline + self.isAvatarOnline[avatarId] = OFFLINE + self.avatarId2BandId[avatarId] = (0,0) + + if self.avatarId2Invite.get(avatarId,None): + self.avatarId2Invite.pop(avatarId,(0,0)) + + # Unregister the client from the guild channel + self.air.removeInterestFromConnection(avatarId,AIInterestHandles.PIRATES_GUILD) + + gId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(gId, "recvAvatarOffline", + [avatarId,self.avatarName.get(avatarId,"Unknown")]) + + def updateRep(self, avatarId, rep): + if avatarId in self.avatarName: + self.updateLeaderboardRep(avatarId, self.avatarName.get(avatarId, 'Unknown'), rep) + + def _addMember(self, guildId, avatarId): + self.tallyFunction("_addMember") + self.air.writeServerEvent('addGuildMember', avatarId, '%s' % (guildId)) + + # Add a new normal member to guild + self.db.addMember(guildId, avatarId, 1) + + + self._sendStatus(avatarId) + name = self.avatarName.get(avatarId) + if not name: + name = OTPLocalizer.GuildNewMember + rank = self.avatarId2Rank.get(avatarId) + if not rank: + rank = 1 + isOnline = self.isAvatarOnline.get(avatarId, OFFLINE) + bandManagerId, bandId = self.avatarId2BandId.get(avatarId, (0,0)) + self.sendUpdateToGuildChannel(guildId, 'recvMemberAdded', + [(avatarId, name, rank, isOnline, bandManagerId, bandId)]) + + def removeMember(self, avatarId): + self.tallyFunction("removeMember") + senderId = self.air.getAvatarIdFromSender() + if senderId: + + senderGuild = self._getGuildId(senderId) + victimGuild = self._getGuildId(avatarId) + senderRank = self._getGuildRank(senderId) + victimRank = self._getGuildRank(avatarId) + + # Removing self from guild + if senderId == avatarId or \ + (senderGuild == victimGuild) and senderRank > victimRank: + self.air.writeServerEvent('removeGuildMember', avatarId, 'by %s from %s' % (senderId, victimGuild)) + self.db.removeMember(avatarId, victimGuild, victimRank) + self.sendUpdateToGuildChannel(victimGuild, 'recvMemberRemoved', + [avatarId]) + # Someone's guild/rank is out of synch or we have a hacker + else: + assert self.notify.warning("%d (guild %d rank %d) was incapable of removing %d (guild %d rank %d) from guild." % \ + (senderId,senderGuild,senderRank,avatarId,victimGuild,victimRank)) + + self._sendStatus(avatarId) + + def changeRank(self, avatarId, rank): + self.tallyFunction("changeRank") + senderId = self.air.getAvatarIdFromSender() + if senderId: + + senderGuild = self._getGuildId(senderId) + senderRank = self._getGuildRank(senderId) + victimGuild = self._getGuildId(avatarId) + + if rank < GUILDRANK_MEMBER or rank > GUILDRANK_GM: + assert self.notify.warning("Invalid guild rank %d sent by avatar %d in changeRank request" % (rank,senderId)) + return + + if senderGuild != victimGuild: + assert self.notify.warning("Guild mismatch in changeRank request from %d for %d." % (senderId,avatarId)) + return + + if senderRank != GUILDRANK_GM: + assert self.notify.warning("%d tried to changeRank but wasn't allowed (rank=%d)!" % (senderId,senderRank)) + return + + self.air.writeServerEvent('changeGuildRank', avatarId, '%s by %s' % (rank, senderId)) + # Change guild member rank + self.db.changeRank(avatarId, rank) + self.sendUpdateToGuildChannel(victimGuild, 'recvMemberUpdateRank', [avatarId, rank]) + self._sendStatus(avatarId) + + def _sendStatus(self,avatarId): + self.tallyFunction("_sendStatus") + if avatarId and \ + self.isAvatarOnline.get(avatarId) == ONLINE: + # Request a guild status update for given avatar + guildId, name, rank, change = self.getStatus(avatarId) + + # Tell this player's guild manager their guild status + self.sendGuildStatusUpdate(avatarId, guildId, name, rank) + + # Update the player's avatar with this guild info + self.sendGuildMemberUpdate(avatarId, guildId, name) + + # If the guild's name has been approved or denied, notify + # the GM that his "want name" has been processed + self.checkForNameUpdate(avatarId, guildId, name, rank, change) + + # Subscribe or unsubscribe the player from guild chat + self.updateGuildChatInterest(avatarId, guildId, guildId != 0) + + + def getStatus(self, avatarId): + self.tallyFunction("getStatus") + # Request a guild status update for given avatar + guildId, name, rank, change = self.db.queryStatus(avatarId) + self.avatarId2Guild[avatarId] = guildId + self.avatarId2Rank[avatarId] = rank + return guildId, name, rank, change + + def sendGuildStatusUpdate(self, avatarId, guildId, name, rank): + # Tell this player's guild manager their guild status + self.sendUpdateToAvatarId(avatarId,"guildStatusUpdate",[guildId, name, rank]) + + def sendGuildMemberUpdate(self, avatarId, guildId, name): + # Update the player's avatar with this guild info + self.air.sendUpdateToDoId("DistributedPlayerPirate", "setGuildId", avatarId, [guildId]) + self.air.sendUpdateToDoId("DistributedPlayerPirate", "setGuildName", avatarId, [name]) + + def checkForNameUpdate(self,avatarId, guildId, name, rank, change): + if (rank == GUILDRANK_GM and change != NOACTION_FLAG): + # Inform guild leader about name changes + self.sendUpdateToAvatarId(avatarId, "guildNameChange", [name, change]) + if (change == DENY_FLAG): + # Name Denied, set back to noaction flag + self.db.nameProcessed(guildId, NOACTION_FLAG) + elif (change == APPROVE_FLAG): + # Name approved, set to all done flag + self.db.nameProcessed(guildId, ALLDONE_FLAG) + + def updateGuildChatInterest(self, avatarId, guildId, allow): + if guildId and allow: + self.air.addInterestToConnection(avatarId, + AIInterestHandles.PIRATES_GUILD, + 0, + self.doId, + guildId) + else: + self.air.removeInterestFromConnection(avatarId,AIInterestHandles.PIRATES_GUILD) + + def statusRequest(self): + self.tallyFunction("statusRequest") + # Request a guild status update for the sending avatar + self._sendStatus(self.air.getAvatarIdFromSender()) + assert False + + def requestInvite(self, otherAvatarId): + self.tallyFunction("requestInvite") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + name = self.avatarName.get(avatarId,"Some pirate") + self.air.writeServerEvent('requestGuildInvite', avatarId, '%s|%s' % (otherAvatarId, name)) + + guildid, guildname, guildrank, change = self.db.queryStatus(avatarId) + print "DEBUG, query came back: ", guildid, " ", guildname + otherguild, othername, otherrank, otherchange = self.db.queryStatus(otherAvatarId) + + if guildrank < GUILDRANK_OFFICER: + assert self.notify.warning("%d tried to do a guild invite but was rank %d!" % (avatarId,guildrank)) + return + + # Make sure guild is not overly full + count = self.db.memberCount(guildid) + if (count >= MAX_MEMBERS): + self.sendUpdateToAvatarId( + avatarId, 'guildRejectInvite', [otherAvatarId, RejectCode.GUILD_FULL]) + return + + # Make sure not already in guild or invite not already outstanding + if (otherguild > 0): + self.sendUpdateToAvatarId( + avatarId, 'guildRejectInvite', [otherAvatarId, RejectCode.ALREADY_IN_GUILD]) + elif self.avatarId2Invite.get(otherAvatarId,None): + self.sendUpdateToAvatarId( + avatarId, 'guildRejectInvite', [otherAvatarId, RejectCode.BUSY]) + else: + self.avatarId2Invite[otherAvatarId] = (guildid,avatarId) + # Inform the other player that they have been invited + self.sendUpdateToAvatarId(otherAvatarId, "invitationFrom", [avatarId,name,guildid,guildname]) + + def updateLeaderboardRep(self, avatarId, name, rep): + #print "updateLeaderboardRep: ", avatarId, ", ", name, ", ", rep + # using the notifier now + self.notify.info("updateLeaderboardRep: %d, %s, %d" % (avatarId,name,rep)) + self.air.setLeaderboardValue('reputation', avatarId, name, rep, self.getDoId()) + + def requestLeaderboardTopTen(self): + self.tallyFunction("requestLeaderboardTopTen") + self.sendToId = self.air.getAvatarIdFromSender() + if self.sendToId: + + dcfile = self.air.getDcFile() + dclass = dcfile.getClassByName('LeaderBoard') + dg = dclass.aiFormatUpdate('getTopTenRespondTo', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + self.getDoId(), + ["reputation", self.getDoId()]) + self.air.send(dg) + + def testThis(self, args): + print "DEBUG: sending setValue and getValuesRespondTo" + + dcfile = self.air.getDcFile() + dclass = dcfile.getClassByName('LeaderBoard') + dg = dclass.aiFormatUpdate('setValue', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + self.getDoId(), + [["guildtest"], 1, "name", 23]) + self.air.send(dg) + + dg = dclass.aiFormatUpdate('getValuesRespondTo', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + self.getDoId(), + ["guildtest", [1], self.getDoId()]) + self.air.send(dg) + + def getValuesResponce(self, contest, stuff): + # This should automatically get called in response by the dclass object + # No additional catch/subscribe required + print "DEBUG: GuildManagerUD:getValuesResponce" + import pdb; pdb.set_trace(); + + def getTopTenResponce(self, contest, stuff): + # This should automatically get called in response by the dclass object + # No additional catch/subscribe required + self.sendUpdateToAvatarId(self.sendToId,"leaderboardTopTen", [stuff]) + + + def setTalkGroup(self,fromAV, fromAC, avatarName, chat, mods, flags): + self.tallyFunction("setTalkGroup") + + #print("Guild Manager - sendTalkGroup") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + #self.air.writeServerEvent('sendChat', avatarId, '%s' % (chat)) + + gId = self._getGuildId(avatarId) + #self.sendUpdateToGuildChannel(gId, "recvChat",[avatarId,msgText,chatFlags,senderDISLid]) + self.sendUpdateToGuildChannelWithSender(gId, "setTalkGroup", [fromAV, fromAC, avatarName, chat, mods, flags]) + + def sendWLChat(self,msgText,chatFlags,senderDISLid): + self.tallyFunction("sendWLChat") + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + self.air.writeServerEvent('sendWLChat', avatarId, '%s' % (msgText)) + + gId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(gId, "recvWLChat", [avatarId,msgText,chatFlags,senderDISLid]) + + def sendSC(self,msgIndex): + self.tallyFunction("sendSC") + #print "GuildManagerUD.sendSC() called" + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + self.air.writeServerEvent('sendSC', avatarId, '%d' % (msgIndex)) + + gId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(gId, "recvSC", [avatarId,msgIndex]) + + def sendSCQuest(self,questInt,msgType,taskNum): + avatarId = self.air.getAvatarIdFromSender() + if avatarId: + + self.air.writeServerEvent('sendSCQuest', avatarId, '%d' % (taskNum)) + + gId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(gId, "recvSCQuest", [avatarId,questInt,msgType,taskNum]) + + def _getGuildId(self,avatarId): + if avatarId in self.avatarId2Guild: + return self.avatarId2Guild[avatarId] + else: + guildId, name, rank, change = self.getStatus(avatarId) + return guildId + + def _getGuildRank(self,avatarId): + if avatarId in self.avatarId2Rank: + return self.avatarId2Rank[avatarId] + else: + guildId, name, rank, change = self.getStatus(avatarId) + return rank + + def sendTokenRequest(self): + self.tallyFunction("sendTokenRequest") + requestId = self.nextTokenRequestId + 1 + self.nextTokenRequestId += 1 + self.requestId2AvatarId[requestId] = self.air.getAvatarIdFromSender() + # Lets get the guildId for this avatar + guildId, name, rank, change = self.db.queryStatus(self.air.getAvatarIdFromSender()) + + if guildId: + token = self.db.getFriendToken(guildId, self.requestId2AvatarId[requestId]) + # OK, now that we have the token, lets send it back to the client + self.recvTokenGenerated(requestId, token) + self.air.writeServerEvent('sendTokenRequest', self.requestId2AvatarId[requestId], '%s|%d|%d' % (token, guildId, rank)) + + def recvTokenGenerated(self,requestId, tokenValue): + # Before responding, let also figure out if the avatar has a + # existing perm code + perm = self.db.checkForUnlimitedUseToken(self.requestId2AvatarId[requestId]) + # if perm == None, then make preExistPerm = 0 + # if perm != None, make preExistPerm = 1 + if perm == None: + preExistPerm = 0 + else: + preExistPerm = 1 + self.sendUpdateToAvatarId(self.requestId2AvatarId[requestId], + 'recvTokenInviteValue', + [tokenValue, preExistPerm]) + # print "Token for request %s: %s" % (requestId,tokenValue) + self.air.writeServerEvent('recvTokenGenerated', self.requestId2AvatarId[requestId], '%s' % (tokenValue)) + + def sendTokenForJoinRequest(self, token, name): + self.tallyFunction("sendTokenForJoinRequest") + + avatarId = self.air.getAvatarIdFromSender() + + requestId = self.nextRedeemTokenRequestId + 1 + self.nextRedeemTokenRequestId += 1 + self.redeemTokenRequestId2AvatarId[requestId] = avatarId + + # Security check. Lets keep track of how quickly users are trying to + # redeem guild tokens. If someone tries to redeem (repeatedly) without + # success, (more than two requests, during a two second time span); + # drop the request + if avatarId in self.redeemTokenTimeStamps: + # They have an entry, take the value (epoch) from the dict + lastTry = self.redeemTokenTimeStamps[self.redeemTokenRequestId2AvatarId[requestId]] + if lastTry >= int(time.time()) - 2: + + # They are trying to redeem too fast + # update their time stamp in self.redeemTokenTimeStamps + # and then return + + self.redeemTokenTimeStamps[self.redeemTokenRequestId2AvatarId[requestId]] = int(time.time()) + self.air.writeServerEvent('tokenRedemptionTooFast', self.redeemTokenRequestId2AvatarId[requestId], '%s|%s|' % (self.redeemTokenTimeStamps[self.redeemTokenRequestId2AvatarId[requestId]], int(time.time()))) + return + else: + self.redeemTokenTimeStamps[avatarId] = int(time.time()) + + # If redeem is sucessful, we'll remove the stamp entry + # in redeemTokenTimeStamps + + try: + results = self.db.redeemToken(token, avatarId) + except Exception,e: + if e.args[0] == "INVALID_TOKEN": + guildName = '***ERROR - GUILD CODE INVALID***' + self.sendTokenRedeemMessage(requestId, guildName) + self.air.writeServerEvent('sendTokenForJoinRequest', avatarId, '%s|0' % (token)) + # Timestamp their request; in case they are trting to redeem + # to fast. + self.redeemTokenTimeStamps[avatarId] = int(time.time()) + return + elif e.args[0] == "GUILD_FULL": + guildName = '***ERROR - GUILD FULL***' + self.sendTokenRedeemMessage(requestId, guildName) + self.air.writeServerEvent('sendTokenForJoinRequest', avatarId, '%s|0' % (token)) + # Timestamp their request; in case they are trting to redeem + # to fast. + self.redeemTokenTimeStamps[avatarId] = int(time.time()) + return + else: + raise e + + guildId = results[0] + creatorAvId = results[1] + guildName = str(self.db.getName(guildId)) + if guildName == '0': + guildName = "Pirate Guild %s" % guildId + # print 'About to send message with %d, %s' % (requestId, guildName) + self.sendTokenRedeemMessage(requestId, guildName) + self.sendTokenRedeemedToTokenCreator(creatorAvId, name) + + self._sendStatus(self.air.getAvatarIdFromSender()) + name = self.avatarName.get(avatarId) + if not name: + name = OTPLocalizer.GuildNewMember + rank = self.avatarId2Rank.get(avatarId) + if not rank: + rank = 1 + isOnline = self.isAvatarOnline.get(avatarId, OFFLINE) + bandManagerId, bandId = self.avatarId2BandId.get(avatarId, (0,0)) + self.sendUpdateToGuildChannel(guildId, 'recvMemberAdded', + [(avatarId, name, rank, isOnline, bandManagerId, bandId)]) + + self.air.writeServerEvent('sendTokenForJoinRequest', self.redeemTokenRequestId2AvatarId[requestId], '%s|1' % (token)) + # Now, remove the timestamp from self.redeemTokenTimeStamps + del self.redeemTokenTimeStamps[self.redeemTokenRequestId2AvatarId[requestId]] + + def sendTokenRedeemMessage(self, requestId, guildName): + # print 'guildName passed is: ' + str(guildName) + self.sendUpdateToAvatarId(self.redeemTokenRequestId2AvatarId[requestId], + 'recvTokenRedeemMessage', + [guildName]) + # print "recvTokenRedeemMessage sent ID: %d : Gname %s" % (requestId, guildName) + + def sendTokenRedeemedToTokenCreator(self, avIdOfCreator, redeemerName): + # Send a message to avIdOfCreator, letting them know that + # redeemerName has redeemed their guild token + self.sendUpdateToAvatarId(avIdOfCreator, 'recvTokenRedeemedByPlayerMessage', [redeemerName]) + + def sendTokenRValue(self, tokenString, rValue): + # This is called from the client, when an avatar wishes to have a token + # flagged as multi-use. (The default behavor is one time use) + # Two values are passed in: + # tokenString = The Token they wish to modify + # rValue = The redeem value they wish to apply to the token passed + # rValue can be as follows... + # if (rValue > 0) = Code can be redeemed rValue number of times, + # everytime it is redeemed, the rValue in the DB will need to be decremented. + # if (rValue == -1) = Code can be redeemed an unlimited number of times + # if (rValue == -2) = Code has been suspended, but is still assigned; + # it can be reactivated at a later time. + + avatarId = self.air.getAvatarIdFromSender() + self.db.changeTokenRValue(avatarId, tokenString, rValue) + self.notify.debug('Requesting Guild Token DB update for %s, updating rValue: %s, for Token %s' % (avatarId, rValue, tokenString)) + self.air.writeServerEvent('Update_Guild_Token_RValue', avatarId, '%s|%s|' % (tokenString, rValue)) + + def sendPermToken(self): + # Called from the client. When called, this will send back the + # perm token that the avatar has registered on the system. + # If none, then return '0' + + avatarId = self.air.getAvatarIdFromSender() + token = self.db.checkForUnlimitedUseToken(avatarId) + if token == None: + token = '0' + self.sendUpdateToAvatarId(avatarId, 'recvPermToken', [token]) + + def sendNonPermTokenCount(self): + # Called from the client. When called, it will send back a total count + # of non-perm tokens + + avatarId = self.air.getAvatarIdFromSender() + tCount = self.db.returnLimitedUseTokens(avatarId) + self.sendUpdateToAvatarId(avatarId, 'recvNonPermTokenCount', [tCount]) + + def sendClearTokens(self, type): + # Called from the client. A request to clear one of two types of tokens, + # Clear all one-time and limited multiuse codes, or clear perm code + # type = 0: Clear onetime use and limited multi-use codes + # type = 1: Clear perm token code + + avatarId = self.air.getAvatarIdFromSender() + if type == 0: + self.db.clearLimitedUseTokens(avatarId) + return + if type == 1: + self.db.clearPermUseTokens(avatarId) + return + + def sendRequestEmailNotificationPref(self): + # Called from the client. Request and avatar's email notification prefs + + avatarId = self.air.getAvatarIdFromSender() + + prefs = self.db.getEmailNotificationPref(avatarId) + + # Now that we have the prefs, respond back to the client + # But before we send, lets check the email address value in prefs. + # If second value is None, we should probably convert it to a string + + notify = prefs[0] + emailAddress = prefs[1] + if emailAddress == None: + emailAddress = 'None' + + self.sendUpdateToAvatarId(avatarId, 'respondEmailNotificationPref', [notify, emailAddress]) + + def sendEmailNotificationPrefUpdate(self, notify, emailAddress): + # Update the notification prefs for the sending avId + + avatarId = self.air.getAvatarIdFromSender() + self.db.updateNotificationPref(avatarId, notify, emailAddress) + + + def sendAvatarBandId(self, avatarId, bandManagerId, bandId): + self.tallyFunction("sendAvatarBandId") + self.avatarId2BandId[avatarId] = (bandManagerId, bandId) + guildId = self._getGuildId(avatarId) + self.sendUpdateToGuildChannel(guildId, 'recvMemberUpdateBandId', [avatarId, bandManagerId, bandId]) + + + """ + # for testing the leaderboard + # this overwrites getTopTenResponce defined above, only uncomment this for testing + + # >>> uber.air.guildManager.getLeaderboardTopTen('pvpBattle') + + def getLeaderboardTopTen(self, category): + dcfile = self.air.getDcFile() + dclass = dcfile.getClassByName('LeaderBoard') + dg = dclass.aiFormatUpdate('getTopTenRespondTo', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + self.getDoId(), + [category, self.getDoId()]) + self.air.send(dg) + + def getTopTenResponce(self, category, info): + print 'TOP 10 RESPONSE (%s): %s' % (category, info) + + """ + + def setDebugAvid(self, avId): + if uber.config.GetBool("guild-manager-ud-debug",0): + self.debugAvId = avId + + + # teleport support + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def reflectTeleportQuery(self, sendToId, localBandMgrId, localBandId, localGuildId, localShardId): + self.tallyFunction("reflectTeleportQuery") + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(sendToId, 'teleportQuery', [avId, localBandMgrId, localBandId, localGuildId, localShardId]) + + @report(types = ['deltaStamp', 'args'], dConfigParam = 'teleport') + def reflectTeleportResponse(self, sendToId, available, shardId, instanceDoId, areaDoId): + self.tallyFunction("reflectTeleportResponse") + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(sendToId, 'teleportResponse', [avId, available, shardId, instanceDoId, areaDoId]) + + def sendMsgToDinghyAI(self, doId, channel, fieldName, args): + self.tallyFunction("sendMsgToDinghyAI") + # Send the Friends list back to the AI that called it + dcfile = self.air.getDcFile() + dclass = dcfile.getClassByName('DistributedDinghy') + # param1: field name to be called + # param2: doId of target object + # param3: channel to send TO (other object must be listening to this channel) + # param4: who sent the message + # param5: arguments to be sent + dg = dclass.aiFormatUpdate(fieldName, + doId, + channel, + self.getDoId(), + args) + self.air.send(dg) + + def requestGuildMatesList(self, doId, channel, avId): + self.tallyFunction("requestGuildMatesList") + # Send the list of avatars who are in the same guild as avId + + guildId = self.avatarId2Guild.get(avId) + if not guildId: + return + + # Now that we have the guild ID, get the list of members + guildMembers = self.db.getMembers(guildId) + + memberList = [] + for member in guildMembers: + if member[1] != avId: + memberList.append(member[1]) + + self.sendMsgToDinghyAI(doId, channel, 'responseGuildMatesList', [avId, memberList]) + + def updateAvatarName(self, avatarId, avatarName): + self.tallyFunction("updateAvatarName") + + if avatarId in self.avatarName: + self.avatarName[avatarId] = avatarName + self._sendStatus(avatarId) + + def avatarDeleted(self, avatarId): + self.tallyFunction("avatarDeleted") + + victimGuild = self._getGuildId(avatarId) + victimRank = self._getGuildRank(avatarId) + + self.db.removeMember(avatarId, victimGuild, victimRank) + self.sendUpdateToGuildChannel(victimGuild, 'recvMemberRemoved', [avatarId]) diff --git a/otp/src/friends/PlayerFriendsManager.py b/otp/src/friends/PlayerFriendsManager.py new file mode 100644 index 0000000..3672dbc --- /dev/null +++ b/otp/src/friends/PlayerFriendsManager.py @@ -0,0 +1,315 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.otpbase import OTPGlobals +from otp.friends import FriendResponseCodes + + + +class PlayerFriendsManager(DistributedObjectGlobal): + """ + The Player Friends Manager is a global object. + This object handles client requests on player-level (as opposed to avatar-level) friends. + + See Also: + "otp/src/friends/PlayerFriendsManagerUD.py" + "otp/src/friends/AvatarFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('PlayerFriendsManager') + + def __init__(self, cr): + assert self.notify.debugCall() + DistributedObjectGlobal.__init__(self, cr) + self.playerFriendsList = set() + self.playerId2Info = {} + self.playerAvId2avInfo = {} #a different (game specific) set of info + self.accept('gotExtraFriendHandles', self.__handleFriendHandles) + + # Functions called by the client + def delete(self): + self.ignoreAll() + + def sendRequestInvite(self,playerId): + print ("PFM sendRequestInvite id:%s" % (playerId)) + assert self.notify.debugCall() + self.sendUpdate("requestInvite", [0,playerId,True]) + + def sendRequestDecline(self,playerId): + assert self.notify.debugCall() + self.sendUpdate("requestDecline", [0,playerId]) + + def sendRequestRemove(self,playerId): + # XXX cannot also use this as a "cancel invite" if we don't know the playerId yet + assert self.notify.debugCall() + self.sendUpdate("requestRemove", [0,playerId]) + + def sendRequestUnlimitedSecret(self): + assert self.notify.debugCall() + self.sendUpdate("requestUnlimitedSecret", [0,]) + + def sendRequestLimitedSecret(self,username,password): + assert self.notify.debugCall() + self.sendUpdate("requestLimitedSecret", [0,username,password]) + + def sendRequestUseUnlimitedSecret(self,secret): + assert self.notify.debugCall() + self.sendUpdate("requestUseUnlimitedSecret", [0,secret]) + + def sendRequestUseLimitedSecret(self,secret,username,password): + assert self.notify.debugCall() + self.sendUpdate("requestUseLimitedSecret", [0,secret,username,password]) + + + def sendSCWhisper(self,recipientId,msgId): + assert self.notify.debugCall() + self.sendUpdate("whisperSCTo",[0,recipientId,msgId]) + + def sendSCCustomWhisper(self,recipientId,msgId): + assert self.notify.debugCall() + self.sendUpdate("whisperSCCustomTo",[0,recipientId,msgId]) + + def sendSCEmoteWhisper(self,recipientId,msgId): + assert self.notify.debugCall() + self.sendUpdate("whisperSCEmoteTo",[0,recipientId,msgId]) + + def setTalkAccount(self, toAc, fromAc, fromName, message, mods, flags): + #print("setTalkAccount in PFM to:%s from:%s message:%s" % (toAc, fromAc, message)) + localAvatar.displayTalkAccount(fromAc, fromName, message, mods) + toName = None + friendInfo = self.getFriendInfo(toAc) + if friendInfo: + toName = friendInfo.playerName + elif toAc == localAvatar.DISLid: + toName = localAvatar.getName() + base.talkAssistant.receiveAccountTalk(None, None, fromAc, fromName, toAc, toName, message) + + #Functions called from UD + + def invitationFrom(self,playerId,avatarName): + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendInvitationEvent,[playerId,avatarName]) + + def retractInvite(self,playerId): + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendRetractInviteEvent,[playerId]) + + def rejectInvite(self,playerId,reason): + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendRejectInviteEvent,[playerId,reason]) + + def rejectRemove(self,playerId,reason): + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendRejectRemoveEvent,[playerId,reason]) + + def secretResponse(self,secret): + print ("secretResponse %s"%(secret)) + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendNewSecretEvent,[secret]) + + def rejectSecret(self,reason): + print ("rejectSecret %s"%(reason)) + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendRejectNewSecretEvent,[reason]) + + def rejectUseSecret(self,reason): + print ("rejectUseSecret %s"%(reason)) + assert self.notify.debugCall() + messenger.send(OTPGlobals.PlayerFriendRejectUseSecretEvent,[reason]) + + def invitationResponse(self, playerId, respCode, context): + if respCode == FriendResponseCodes.INVITATION_RESP_DECLINE: + messenger.send(OTPGlobals.PlayerFriendRejectInviteEvent,[playerId,respCode]) + elif respCode == FriendResponseCodes.INVITATION_RESP_NEW_FRIENDS: + pass + + + + #Functions called from UD + + def updatePlayerFriend(self,id,info,isNewFriend): + assert self.notify.debugCall() + self.notify.warning("updatePlayerFriend: %s, %s, %s" % (id,info,isNewFriend)) + info.calcUnderstandableYesNo() + # ugly hack to make temp DNames (Guest0123456789) not look so ugly in Toontown + if info.playerName[0:5] == 'Guest': + info.playerName = 'Guest ' + info.playerName[5:] + if id not in self.playerFriendsList: + self.playerFriendsList.add(id) + self.playerId2Info[id] = info + messenger.send(OTPGlobals.PlayerFriendAddEvent,[id,info,isNewFriend]) + #need to detect if the playerFriend is coming online so we can send a message + elif self.playerId2Info.has_key(id): + if (not self.playerId2Info[id].onlineYesNo) and info.onlineYesNo: + #send "coming online message" + self.playerId2Info[id] = info + messenger.send("playerOnline", [id]) + base.talkAssistant.receiveFriendAccountUpdate(id,info.playerName,info.onlineYesNo) + elif (self.playerId2Info[id].onlineYesNo) and not info.onlineYesNo: + #send "going offline message" + self.playerId2Info[id] = info + messenger.send("playerOffline", [id]) + base.talkAssistant.receiveFriendAccountUpdate(id,info.playerName,info.onlineYesNo) + if not self.askAvatarKnownHere(info.avatarId): + self.requestAvatarInfo(info.avatarId) + self.playerId2Info[id] = info + + av = base.cr.doId2do.get(info.avatarId,None) + if av is not None: + av.considerUnderstandable() + + messenger.send(OTPGlobals.PlayerFriendUpdateEvent,[id,info]) + + def removePlayerFriend(self,id): + assert self.notify.debugCall() + if not(id in self.playerFriendsList): + return + self.playerFriendsList.remove(id) + info = self.playerId2Info.pop(id,None) + if info is not None: + av = base.cr.doId2do.get(info.avatarId,None) + if av is not None: + av.considerUnderstandable() + messenger.send(OTPGlobals.PlayerFriendRemoveEvent,[id]) + + def whisperSCFrom(self,playerId,msg): + assert self.notify.debugCall() + # print("whisperSCFrom %s %s" % (playerId,msg)) + base.talkAssistant.receivePlayerWhisperSpeedChat(msg, playerId) + + #client-called helper functions + + def isFriend(self,pId): + return self.isPlayerFriend(pId) + + def isPlayerFriend(self,pId): + if not pId: + return 0 + return pId in self.playerFriendsList + + def isAvatarOwnerPlayerFriend(self, avId): + pId = self.findPlayerIdFromAvId(avId) + if pId and self.isPlayerFriend(pId): + return True + else: + return False + + def getFriendInfo(self, pId): + return self.playerId2Info.get(pId) + + def findPlayerIdFromAvId(self, avId): + for playerId in self.playerId2Info: + if self.playerId2Info[playerId].avatarId == avId: + if self.playerId2Info[playerId].onlineYesNo: + return playerId + return None + + def findAvIdFromPlayerId(self, pId): + pInfo = self.playerId2Info.get(pId) + if pInfo: + return pInfo.avatarId + else: + return None + + def findPlayerInfoFromAvId(self, avId): + playerId = self.findPlayerIdFromAvId(avId) + if playerId: + return self.getFriendInfo(playerId) + else: + return None + + def askAvatarOnline(self, avId): + returnValue = 0 + if self.cr.doId2do.has_key(avId): + returnValue = 1 + if self.playerAvId2avInfo.has_key(avId): + playerId = self.findPlayerIdFromAvId(avId) + if self.playerId2Info.has_key(playerId): + playerInfo = self.playerId2Info[playerId] + if playerInfo.onlineYesNo: + returnValue = 1 + return returnValue + + def countTrueFriends(self): + count = 0 + for id in self.playerId2Info: + if self.playerId2Info[id].openChatFriendshipYesNo: + count += 1 + return count + + def askTransientFriend(self, avId): + if self.playerAvId2avInfo.has_key(avId) and not base.cr.isAvatarFriend(avId): + return 1 + else: + return 0 + + def askAvatarKnown(self, avId): + if self.askAvatarKnownElseWhere(avId) or self.askAvatarKnownHere(avId): + return 1 + else: + return 0 + + def askAvatarKnownElseWhere(self, avId): + if hasattr(base, "cr"): + if base.cr.askAvatarKnown(avId): + return 1 + return 0 + + def askAvatarKnownHere(self, avId): + if self.playerAvId2avInfo.has_key(avId): + return 1 + else: + return 0 + + def requestAvatarInfo(self, avId): + if hasattr(base, "cr"): + #base.cr.requestAvatarInfo(avId) + base.cr.queueRequestAvatarInfo(avId) + + def __handleFriendHandles(self, handleList): + for handle in handleList: + # this line requires all handles to have the function getDoId, not much of a good way around it. + self.playerAvId2avInfo[handle.getDoId()] = handle + messenger.send('friendsListChanged') + + def getAvHandleFromId(self, avId): + if self.playerAvId2avInfo.has_key(avId): + return self.playerAvId2avInfo[avId] + else: + return None + + def identifyFriend(self, avId): + handle = None + handle = base.cr.identifyFriend(avId) + if not handle: + handle = self.getAvHandleFromId(avId) + return handle + + def getAllOnlinePlayerAvatars(self): + returnList = [] + for avatarId in self.playerAvId2avInfo: + playerId = self.findPlayerIdFromAvId(avatarId) + if playerId: + if self.playerId2Info[playerId].onlineYesNo: + returnList.append(avatarId) + return returnList + + def identifyAvatar(self, doId): + """ + Returns either an avatar or a FriendHandle, whichever we can + find, to reference the indicated doId. + """ + if base.cr.doId2do.has_key(doId): + return base.cr.doId2do[doId] + else: + return self.identifyFriend(doId) + + def friendsListFull(self): + return len(self.playerFriendsList) >= OTPGlobals.MaxPlayerFriends + + + + + + diff --git a/otp/src/friends/PlayerFriendsManagerUD.py b/otp/src/friends/PlayerFriendsManagerUD.py new file mode 100644 index 0000000..1e3b921 --- /dev/null +++ b/otp/src/friends/PlayerFriendsManagerUD.py @@ -0,0 +1,490 @@ +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.task.Task import Task +from otp.otpbase import OTPGlobals +from otp.ai import AIMsgTypes +from otp.uberdog.RejectCode import RejectCode + +from direct.directnotify.DirectNotifyGlobal import directNotify + +from otp.friends.FriendInfo import FriendInfo +from otp.switchboard.sbWedge import sbWedge + +from otp.otpbase import OTPLocalizerEnglish as localizer + +import random + + +#-------------------------------------------------- + + +class PlayerFriendsManagerUD(DistributedObjectGlobalUD,sbWedge): + """ + The Player Friends Manager is a global object. + This object handles client requests on player-level (as opposed to avatar-level) friends. + + See Also: + "otp/src/friends/AvatarFriendsManager.py" + "otp/src/friends/PlayerFriendsManager.py" + "pirates/src/friends/PiratesFriendsList.py" + "otp/src/configfiles/otp.dc" + "pirates/src/configfiles/pirates.dc" + """ + notify = directNotify.newCategory('PlayerFriendsManagerUD') + + def __init__(self, air, sbListenPort=8888, wedgeName=None, locationName="OTP"): + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + + self.sbName = wedgeName + self.locationName = locationName + + if self.sbName is None: + self.sbName = "OTP%d" % random.randint(0,99999) + + self.everyoneIsFriends = uber.config.GetBool("everyone-is-friends",0) + + self.sbHost = uber.sbNSHost + self.sbPort = uber.sbNSPort + self.sbListenPort = uber.sbListenPort + self.clHost = uber.clHost + self.clPort = uber.clPort + self.allowUnfilteredChat = uber.allowUnfilteredChat + self.bwDictPath = uber.bwDictPath + + #self.avatarId2FriendsList = {} + self.playerId2Invitations = {} + #self.avatarId2Name = {} + #self.avatarId2Info = {} + #self.avatarId2Account = {} + + #self.isAvatarOnline = {} + + #self.isAccountOnline = {} + + #self.accountId2Info = {} + #self.accountId2Friends = {} + + self.accept("avatarOnlinePlusAccountInfo", self.avatarOnlinePlusAccountInfo, []) + self.accept("avatarOffline", self.avatarOffline, []) + + sbWedge.__init__(self,wedgeName=self.sbName, + nsHost=self.sbHost, + nsPort=self.sbPort, + listenPort=self.sbListenPort, + clHost=self.clHost, + clPort=self.clPort, + allowUnfilteredChat=self.allowUnfilteredChat, + bwDictPath=self.bwDictPath) + + def CheckSBWedge(task): + self.handleRequests(0) + return Task.cont + + uber.taskMgr.add(CheckSBWedge,'checkSBwedge') + + + def announceGenerate(self): + assert self.notify.debugCall() + DistributedObjectGlobalUD.announceGenerate(self) + self.sendUpdateToChannel( + AIMsgTypes.CHANNEL_CLIENT_BROADCAST, "online", []) + self.sendUpdateToChannel( + AIMsgTypes.OTP_CHANNEL_AI_AND_UD_BROADCAST, "online", []) + + + def delete(self): + assert self.notify.debugCall() + self.ignoreAll() + DistributedObjectGlobalUD.delete(self) + + #---------------------------------- + + def avatarOnline(self,avatarId,avatarType): + pass + + def avatarOnlinePlusAccountInfo(self,avatarId,accountId,playerName, + playerNameApproved,openChatEnabled, + createFriendsWithChat,chatCodeCreation): + assert self.notify.debugCall() + + if accountId in [-1, 0]: + return + + self.log.debug("Account online. Info: %d, %d, %s, %s, %s, %s, %s"%(avatarId, + accountId, + playerName, + playerNameApproved, + openChatEnabled, + createFriendsWithChat, + chatCodeCreation)) + + if playerName == "Guest": + accountInfo = FriendInfo(avatarName="%d"%avatarId, + playerName="%s%d" % (playerName,accountId), + onlineYesNo=1, + openChatEnabledYesNo=openChatEnabled, + avatarId=avatarId, + location=self.locationName, + sublocation="") + else: + accountInfo = FriendInfo(avatarName="%d"%avatarId, + playerName=playerName, + onlineYesNo=1, + openChatEnabledYesNo=openChatEnabled, + avatarId=avatarId, + location=self.locationName, + sublocation="") + # Don't have my avatar name yet, asyncrequest it + context = self.air.allocateContext() + dclassName = "DistributedAvatarUD" + self.air.contextToClassName[context] = dclassName + self.acceptOnce("doFieldResponse-%s"%context,self.recvAvatarName,[accountId,accountInfo]) + self.air.queryObjectField(dclassName,"setName",avatarId,context) + + + def recvAvatarName(self,accountId,accountInfo,context,name): + self.notify.debug("avatarName fetched for account %d: %s" % (accountId,name[0])) + accountInfo.avatarName = name[0] + + # asynchronous request to SB which will tell everyone we're here and fetch our friends + if self.sbConnected: + self.enterPlayer(accountId,accountInfo) + + + def recvFriendsUpdate(self,accountId,accountInfo,friends): + self.log.debug("recvFriendsUpdate on %d -> %s"%(accountId,str(friends))) + for friend in friends: + friendId = friend[0] + friendInfo = friend[1] + + accountInfo.timestamp = 0 + friendInfo.timestamp = 0 + + accountInfo.openChatFriendshipYesNo = friendInfo.openChatFriendshipYesNo + + accountInfo.understandableYesNo = friendInfo.openChatFriendshipYesNo or \ + (friendInfo.openChatEnabledYesNo and \ + accountInfo.openChatEnabledYesNo) + + friendInfo.understandableYesNo = friendInfo.openChatFriendshipYesNo or \ + (friendInfo.openChatEnabledYesNo and \ + accountInfo.openChatEnabledYesNo) + + if accountInfo.onlineYesNo: + self.sendUpdateToChannel((3L<<32)+accountId, + "updatePlayerFriend", + [friendId,friendInfo,0]) + + self.sendUpdateToChannel((3L<<32)+friend[0], + "updatePlayerFriend", + [accountId,accountInfo,0]) + + + @report(types = ['args'], dConfigParam = 'orphanedavatar') + def avatarOffline(self,avatarId): + assert self.notify.debugCall() + + self.exitAvatar(avatarId) + + +#---------------------------------------------------------------------- + + + # Functions called by the client + + def requestInvite(self, senderId, otherPlayerId, secretYesNo=True): + assert self.notify.debugCall() + self.sendOpenInvite(senderId,otherPlayerId,secretYesNo) + + def requestDecline(self, senderId, otherId): + """ + Call this function to retract an invite to or decline an invite from another player. + """ + self.sendDeclineInvite(senderId,otherId) + + def requestRemove(self, senderId, otherAccountId): + """ + Call this function if you want to remove an existing friend from your friends list. + + otherAccountId may be online or offline. + """ + accountId = senderId + self.air.writeServerEvent('requestFriendRemove', accountId, '%s' % otherAccountId) + + # update DISL friends list through Switchboard + self.removeFriendship(accountId,otherAccountId) + + def recvInviteNotice(self, inviteeId, inviterId, inviterAvName): + self.sendUpdateToChannel((3L<<32)+inviteeId, "invitationFrom", [inviterId,inviterAvName]) + + def recvInviteRetracted(self, inviteeId, inviterId): + self.sendUpdateToChannel((3L<<32)+inviteeId, "retractInvite", [inviterId]) + + def recvInviteRejected(self, inviterId, inviteeId, reason): + self.sendUpdateToChannel((3L<<32)+inviterId, "rejectInvite", [inviteeId, reason]) + + def recvFriendshipRemoved(self,accountId,otherAccountId): + self.notify.debug("recvFriendshipRemoved on %d,%d"%(accountId,otherAccountId)) + + self.sendUpdateToChannel((3L<<32)+accountId,"removePlayerFriend",[otherAccountId]) + self.sendUpdateToChannel((3L<<32)+otherAccountId,"removePlayerFriend",[accountId]) + + + # SECRETS + def requestUnlimitedSecret(self,senderId): + print "# got unlimited secret request" + self.sendSecretRequest(senderId) + + def requestLimitedSecret(self,senderId,parentUsername,parentPassword): + print "# got limited secret request" + self.sendSecretRequest(senderId,parentUsername,parentPassword) + + def requestUseUnlimitedSecret(self,senderId,secret): + self.sendSecretRedeem(senderId,secret) + + def requestUseLimitedSecret(self,senderId,secret,parentUsername,parentPassword): + self.sendSecretRedeem(senderId,secret,parentUsername,parentPassword) + + def recvAddFriendshipError(self,playerId,error): + self.sendUpdateToChannel((3L<<32)+playerId,"rejectInvite",[error]) + + def recvSecretGenerated(self,playerId,secret): + self.sendUpdateToChannel((3L<<32)+playerId,"secretResponse",[secret]) + + def recvSecretRequestError(self,playerId,error): + self.sendUpdateToChannel((3L<<32)+playerId,"rejectSecret",[error]) + + def recvSecretRedeemError(self,playerId,error): + self.sendUpdateToChannel((3L<<32)+playerId,"rejectUseSecret",[error]) + + + # WHISPERS + + def whisperTo(self,senderId,playerId,msg): + assert self.sbConnected + + self.log.debug("PFMUD whisper - %d to %d: %s" % (senderId,playerId,msg)) + + if senderId == -1 or playerId == -1: + return + + if self._validateChatMessage(playerId,senderId,msg): + self.sendWhisper(playerId,senderId,msg) + + + def whisperWLTo(self,senderId,playerId,msg): + assert self.sbConnected + + self.log.debug("PFMUD WLwhisper - %d to %d: %s" % (senderId,playerId,msg)) + + if senderId == -1 or playerId == -1: + return + + # Validation being handled by client agents, do not need + #if self._validateChatMessage(playerId,senderId,msg): + self.sendWLWhisper(playerId,senderId,msg) + + + def whisperSCTo(self,senderId,playerId,msgId): + assert self.sbConnected + + self.log.debug("PFMUD SCwhisper - %d to %d: %s" % (senderId,playerId,msgId)) + + if senderId == -1 or playerId == -1: + return + + msgText = self._translateWhisper(msgId) + + if msgText is None: + self.log.security("Invalid SC index: %d to %d: %d" % (senderId,playerId,msgId)) + return + + if self._validateChatMessage(playerId,senderId,msgText): + self.sendSCWhisper(playerId,senderId,msgText) + + + + def whisperSCCustomTo(self,senderId,playerId,msgId): + assert self.sbConnected + + self.log.debug("PFMUD SCCustomwhisper - %d to %d: %s" % (senderId,playerId,msgId)) + + if senderId == -1: + return + + msgText = self._translateWhisperCustom(msgId) + + if msgText is None: + self.log.security("Invalid SC custom index: %d to %d: %d" % (senderId,playerId,msgId)) + return + + if self._validateChatMessage(playerId,senderId,msgText): + self.sendSCWhisper(playerId,senderId,msgText) + + + def whisperSCEmoteTo(self,senderId,playerId,msgId): + assert self.sbConnected + + self.log.debug("PFMUD SCEmotewhisper - %d to %d: %s" % (senderId,playerId,msgId)) + + if senderId == -1: + return + + msgText = self._translateWhisperEmote(msgId) + + if msgText is None: + self.log.security("Invalid SC emote index: %d to %d: %d" % (senderId,playerId,msgId)) + return + + # XXX Temporarily broken--where does the avatarname come from if we're stateless? + # Stick the sender's avatar name into the emote message! + #senderInfo = self.accountId2Info.get(senderId,None) + #if senderInfo is not None: + # msgText = msgText % (senderInfo.avatarName) + + if self._validateChatMessage(playerId,senderId,msgText): + self.sendSCWhisper(playerId,senderId,msgText) + + + def whisperSCQuestTo(self,senderId,playerId,msgData): + ''' + Quest messages. Uses product-specific _translateWhisperQuest that should be overridden + ''' + assert self.sbConnected + + self.log.debug("PFMUD SCQuestwhisper - %d to %d: %s" % (senderId,playerId,msgData)) + + if senderId == -1: + return + + msgText = self._translateWhisperQuest(msgData) + + if msgText is None: + self.log.security("Invalid SC quest data: %d to %d: %d" % (senderId,playerId,msgData)) + return + + if self._validateChatMessage(playerId,senderId,msgText): + self.sendSCWhisper(playerId,senderId,msgText) + + + #WEDGE -> UD functions + + def recvWhisper(self,recipientId,senderId,msgText): + self.log.debug("Received open whisper from %d to %d: %s" % (senderId,recipientId,msgText)) + self.sendUpdateToChannel((3L<<32)+recipientId,"whisperFrom",[senderId,msgText]) + + def recvWLWhisper(self,recipientId,senderId,msgText): + self.log.debug("Received WLwhisper from %d to %d: %s" % (senderId,recipientId,msgText)) + self.sendUpdateToChannel((3L<<32)+recipientId,"whisperWLFrom",[senderId,msgText]) + + def recvSCWhisper(self,recipientId,senderId,msgText): + self.log.debug("Received SCwhisper from %d to %d: %s" % (senderId,recipientId,msgText)) + self.sendUpdateToChannel((3L<<32)+recipientId,"whisperSCFrom",[senderId,msgText]) + + def recvEnterPlayer(self,playerId,playerInfo,friendsList): + self.log.debug("Saw player %d enter."%playerId) + self.log.debug("friends list: %s"%friendsList) + + for friend in friendsList: + self.notify.debug("update to %d saying that %d is online" % (friend,playerId)) + friendInfo = friendsList[friend] + playerInfo.openChatFriendshipYesNo = friendInfo.openChatFriendshipYesNo + playerInfo.understandableYesNo = friendInfo.openChatFriendshipYesNo or \ + (friendInfo.openChatEnabledYesNo and \ + playerInfo.openChatEnabledYesNo) + self.sendUpdateToChannel((3L<<32)+friend, + "updatePlayerFriend", + [playerId,playerInfo,0]) + + + def recvExitPlayer(self,playerId,playerInfo,friendsList): + self.log.debug("Saw player %d exit."%playerId) + self.log.debug("friends list: %s"%friendsList) + + for friend in friendsList: + self.notify.debug("update to %d saying that %d is offline" % (friend,playerId)) + friendInfo = friendsList[friend] + playerInfo.openChatFriendshipYesNo = friendInfo.openChatFriendshipYesNo + playerInfo.understandableYesNo = friendInfo.openChatFriendshipYesNo or \ + (friendInfo.openChatEnabledYesNo and \ + playerInfo.openChatEnabledYesNo) + self.sendUpdateToChannel((3L<<32)+friend, + "updatePlayerFriend", + [playerId,playerInfo,0]) + + + # helper functions + + def _getFriendView(self, viewerId, friendId, info=None): + if info is None: + info = self.accountId2Info[friendId] + if self.accountId2Friends.has_key(viewerId): + if [friendId,True] in self.accountId2Friends[viewerId]: + info.openChatFriendshipYesNo = 1 + else: + info.openChatFriendshipYesNo = 0 + elif self.accountId2Friends.has_key(friendId): + if [viewerId,True] in self.accountId2Friends[friendId]: + info.openChatFriendshipYesNo = 1 + else: + info.openChatFriendshipYesNo = 0 + else: + info.openChatFriendshipYesNo = 0 + + if self._whisperAllowed(viewerId,friendId): + info.understandableYesNo = 1 + else: + info.understandableYesNo = 0 + + info.timestamp = 0 + + return info + + + def _whisperAllowed(self, fromPlayer, toPlayer): + fromFriends = self.accountId2Friends.get(fromPlayer) + + if fromFriends: + if [toPlayer,True] in fromFriends: + return True + elif [toPlayer,False] in fromFriends: + fromInfo = self.accountId2Info.get(fromPlayer) + toInfo = self.accountId2Info.get(toPlayer) + + if toInfo and fromInfo.openChatEnabledYesNo and toInfo.openChatEnabledYesNo: + return True + else: + return False + else: + return False + + + def _whisperSCAllowed(self, fromPlayer, toPlayer): + fromFriends = self.accountId2Friends.get(fromPlayer) + + if fromFriends: + if [toPlayer,True] in fromFriends or [toPlayer,False] in fromFriends: + return True + else: + return False + else: + return False + + def _translateWhisper(self,msgId): + return localizer.SpeedChatStaticText.get(msgId) + + def _translateWhisperCustom(self,msgId): + return localizer.CustomSCStrings.get(msgId) + + def _translateWhisperEmote(self,msgId): + if msgId >= len(localizer.EmoteWhispers) or msgId < 0: + return None + else: + return localizer.EmoteWhispers[msgId] + + def _translateWhisperQuest(self,msgData): + ''' + Translate quest SC data to a text message. + Product-specific and should be overridden! + ''' + return None diff --git a/otp/src/friends/Sources.pp b/otp/src/friends/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/friends/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/friends/__init__.py b/otp/src/friends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/guild/.cvsignore b/otp/src/guild/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/guild/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/guild/DistributedGuild.py b/otp/src/guild/DistributedGuild.py new file mode 100644 index 0000000..d525e2b --- /dev/null +++ b/otp/src/guild/DistributedGuild.py @@ -0,0 +1,165 @@ +## """ +## The Guild handles all the guilds accross all shards. +## """ + +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObject + +## class DistributedGuild(DistributedObject.DistributedObject): + ## """ + ## The Guild is a global object. + + ## See Also: + ## "otp/src/configfiles/otp.dc" + ## "otp/src/guild/DistributedGuildAI.py" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory('guild') + + ## def __init__(self, cr): + ## assert self.notify.debugCall() + ## DistributedObject.DistributedObject.__init__(self, cr) + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObject.DistributedObject.delete(self) + + + ## def setName(self, name): + ## self.name=name + + ## def setDateCreated(self, dateCreated): + ## self.dateCreated=dateCreated + + ## def setOwnerAvatarId(self, ownerAvatarId): + ## self.ownerAvatarId=ownerAvatarId + + ## def setFlagDna(self, flagDna): + ## self.flagDna=flagDna + + ## def setApplicationHandling(self, handler): + ## """ + ## handler is one of "ask", "accept", "reject" + ## """ + ## self.applicationHandler=handler + + ## def setGuildStatus(self, guildStatus): + ## self.guildStatus=guildStatus + + ## def setGuildTaxLevel(self, guildStatus): + ## self.guildStatus=guildStatus + + +## if 0: + ## class DistributedGuildOffices(DistributedObject.DistributedObject): + ## def __init__(self, cr): + ## pass + + ## """ + ## hrPerm + ## invitePerm + ## addMemberPerm + ## removeMemberPerm + ## promotePerm + ## demotePerm + ## rulerPerm + ## deplomacyPerm -- not in first version + ## createOfficePerm -- not in first version + ## propogandaPerm -- founder (all together) + ## newsAddPerm + ## newsEditPerm + ## newsRemovePerm + ## pollAddPerm + ## pollRemovePerm + ## treasurerPerm -- founder (all together) + ## taxPerm + ## bankPerm + ## salaryPerm + ## auctionierPerm -- tbd (buy and sell together) anybody + ## auctionSellPerm + ## auctionBuyPerm + ## auctionRemovePerm -- not in first version + ## governorPerm -- founder (all together) + ## placeBuildingPerm + ## removeBuildingPerm + ## npcHirePerm + ## npcFirePerm + ## admiralPerm + ## shipSellPerm -- founder + ## shipBuyPerm -- founder + ## shipScuttlePerm -- nope + ## shipUsePerm -- captain + ## leaderPerm + ## captainShipPerm (redundant with shipUsePerm) -- captain + ## leadCrewPerm -- any member + ## inventoryPerm + ## inventoryBrowsePerm -- any + ## inventoryAddPerm -- any + ## inventoryBorrowPerm -- any with limits by title + ## inventoryRemovePerm -- founder + ## inventoryAuctionPerm -- nope + ## memberPerm + ## votePerm -- any + ## """ + + ## """ + ## Data Attributes: + ## guildName (RO string) + ## dateCreated (RO date) + ## owner (reference) + ## guildFlag (DNA) + ## autoApplicationHandling (off, accept, or reject) + ## guildTaxLevel + ## pollTax + + ## current (list) + ## currentQuests (list) + ## currentNews (list) + ## currentPoll + ## #chatLog (RO list) + ## reputations (list) + ## reputations: Totals + ## reputations: Highest + ## offices (list) + ## office: (Membership/application Management) (gets hrPerm) + ## office: foriegn affairs + ## office: (News Mgmt) + ## office: Tax Collector (Tax Mgmt) + ## office: Treasurer (Inventory Mgmt) + ## office: (Salary Mgmt) + ## office: Auctionier (Auction Mgmt) + ## office: Governor (Island/building Mgmt) + ## office: (NPC Mgmt) + ## office: (Officer/promotion Mgmt) + ## office: (Poll Mgmt) + ## office: Admiral (Ship Mgmt) + + ## members (list) + ## applicants (list) + ## invitations (list) + + ## Quest History (RO list) + ## News History (RO list) + ## Tax History (RO list) + ## Poll History (RO list) + + ## Inventory (list) + ## bank? (or is this part of gear?) + ## islands (hide out) + ## gear + ## burried treasure? + ## treasure maps? (or are these quests?) + ## inventory: ships (list) + ## inventory: buildings (list) + ## inventory: locations that are buildable? (list) + ## inventory: NPCs (do NPCs pay taxes?) + ## inventory: auctions (to members (and non-members?)) + + ## leaderboard (between guilds and inside guild, who contributed money or completed quests). + ## community tools + ## meetings ? (events?) calender + ## message board + ## """ diff --git a/otp/src/guild/DistributedGuildAI.py b/otp/src/guild/DistributedGuildAI.py new file mode 100644 index 0000000..1b0bfe3 --- /dev/null +++ b/otp/src/guild/DistributedGuildAI.py @@ -0,0 +1,61 @@ +## """ +## The Guild AI handles all the guilds accross all shards. +## """ + +## from otp.ai.AIBaseGlobal import * +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObjectAI + +## class DistributedGuildAI(DistributedObjectAI.DistributedObjectAI): + ## """ + ## The Guild AI is a global object. + + ## See Also: + ## "otp/src/configfiles/otp.dc" + ## "otp/src/guild/DistributedGuild.py" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGuildAI') + + ## def __init__(self, air, name, ownerAvatarId): + ## assert self.notify.debugCall() + ## DistributedObjectAI.DistributedObjectAI.__init__(self, air) + ## self.name=name + ## self.dateCreated=0 + ## self.ownerAvatarId=ownerAvatarId + ## self.guildStatus=0 + ## self.reputations={} + ## self.memberListId=0 + ## self.memberApplicationListId=0 + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObjectAI.DistributedObjectAI.delete(self) + + ## def getName(self): + ## return self.name + + ## def getDateCreated(self): + ## return self.dateCreated + + ## def getOwnerAvatarId(self): + ## """ + ## return the avatar doId for the avatar that + ## created/owns this guild. + ## """ + ## return self.ownerAvatarId + + ## def getGuldStatus(self): + ## return self.guildStatus + + ## def getReputations(self): + ## return self.reputations + + ## def getMemberListId(self): + ## return self.memberListId + + ## def getMemberApplicationListId(self): + ## return self.memberApplicationListId diff --git a/otp/src/guild/DistributedGuildBase.py b/otp/src/guild/DistributedGuildBase.py new file mode 100644 index 0000000..20e0d47 --- /dev/null +++ b/otp/src/guild/DistributedGuildBase.py @@ -0,0 +1,13 @@ +## """ +## The Guild AI handles all the guilds accross all shards. + +## See Also: + ## "otp/src/guild/DistributedGuildManagerAI.py" + ## "otp/src/guild/DistributedGuildManager.py" + ## "otp/src/configfiles/otp.dc" +## """ + +## # Reject Reason IDs: +## MAY_NOT_CREATE_GUILD=1 +## ALREADY_HAS_GUILD=2 +## GUILD_NAME_TAKEN=3 diff --git a/otp/src/guild/DistributedGuildManager.py b/otp/src/guild/DistributedGuildManager.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/guild/DistributedGuildManagerAI.py b/otp/src/guild/DistributedGuildManagerAI.py new file mode 100644 index 0000000..810ff99 --- /dev/null +++ b/otp/src/guild/DistributedGuildManagerAI.py @@ -0,0 +1,59 @@ +## """ +## The Guild AI handles all the guilds accross all districts. +## """ + +## from otp.ai.AIBaseGlobal import * +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObjectAI +## from otp.guild import DistributedGuildBase +## from otp.guild import DistributedGuildAI + +## class DistributedGuildManagerAI(DistributedObjectAI.DistributedObjectAI): + ## """ + ## The Guild AI is a global object. + ## See Also: + ## "otp/src/guild/DistributedGuildBase.py" + ## "otp/src/guild/DistributedGuildManager.py" + ## "otp/src/configfiles/otp.dc" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGuildManagerAI') + + ## def __init__(self, air): + ## assert self.notify.debugCall() + ## DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + ## # doId to guild object: + ## self.guilds={} + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObjectAI.DistributedObjectAI.delete(self) + + ## def sendReject(self, avatarId, reasonId): + ## assert self.notify.debugCall() + ## self.sendUpdateToAvatarId(avatarId, "rejectCreate", [reasonId]) + + ## def requestCreate(self, newGuildName): + ## avatarId = self.air.getAvatarIdFromSender() + ## assert self.notify.debugCall("avatarId:%s" % (str(avatarId),)) + ## #assert avatarId in self.air.doId2do + ## # we don't have avtar info! avatar = self.air.doId2do[avatarId] + ## if 0 and not avatar.may("createGuild"): + ## # ...this avatar does not have permission to create a guild: + ## self.sendReject(avatarId, DistributedGuildBase.MAY_NOT_CREATE_GUILD) + ## elif 0 and not avatar.has("guild"): + ## # ...this avatar already has a guild: + ## self.sendReject(avatarId, DistributedGuildBase.ALREADY_HAS_GUILD) + ## elif 0 and guildNameUsed(newGuildName): + ## # ...this guild name is taken: + ## self.sendReject(avatarId, DistributedGuildBase.GUILD_NAME_TAKEN) + ## else: + ## # ...ready to create: + ## guild = DistributedGuildAI.DistributedGuildAI( + ## self.air, newGuildName, avatarId) + ## guild.generateOtpObject(self.getDoId(), avatarId) + ## self.guilds[guild.getDoId()]=guild diff --git a/otp/src/guild/DistributedGuildMember.py b/otp/src/guild/DistributedGuildMember.py new file mode 100644 index 0000000..c73eb73 --- /dev/null +++ b/otp/src/guild/DistributedGuildMember.py @@ -0,0 +1,137 @@ +## """ +## """ + +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObject + +## class DistributedGuildMember(DistributedObject.DistributedObject): + ## """ + ## The Guild is a global object. + + ## See Also: + ## "otp/src/guild/DistributedGuildMemberAI.py" + ## "otp/src/configfiles/otp.dc" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory( + ## 'guild') + + ## def __init__(self, cr): + ## assert self.notify.debugCall() + ## DistributedObject.DistributedObject.__init__(self, cr) + ## self.permissions=0 + + ## def announceGenerate(self): + ## assert self.notify.debugCall() + ## #todo: add self as a child of the guild this is a member of. + ## DistributedObject.DistributedObject.announceGenerate(self) + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObject.DistributedObject.delete(self) + + ## def setSince(self, dateJoined): + ## assert self.notify.debugCall() + ## self.dateJoined=dateJoined + + ## def setName(self, name): + ## assert self.notify.debugCall() + ## self.name=name + + ## def setReputations(self, reputations): + ## assert self.notify.debugCall() + ## self.reputations=reputations + + ## def hasPermissionTo(self, permission): + ## assert self.notify.debugCall() + ## #return self.permissions & permission + ## return True + + + ## if 0: + ## def setRank(self, guildRank): + ## self.guildRank=guildRank + ## if guildRank>=250: + ## self.permissions=GUILD_PERM_OWNER + ## elif guildRank>=200: + ## self.permissions=GUILD_PERM_CO_OWNER + ## elif guildRank>=100: + ## self.permissions=GUILD_PERM_CAPTAIN + ## elif guildRank>=1: + ## self.permissions=GUILD_PERM_MEMBER + ## else: + ## self.permissions=GUILD_PERM_APPLICANT + + ## def setAuthority(self, authority): + ## """ + ## authority determines what the avatar is allowed + ## to do within the guild. + ## The authority bits are: + ## 1 = grant authority + ## 2 = member, news, polling, guild buildings, guild npcs + ## 3 = borrow ships + ## 4 = + ## """ + ## self.authority=authority + + ## def setTake(self, take): + ## """ + ## take is the number of shares the pirate will + ## get of the total treasure available. + ## """ + ## self.take=take + + + + ## """ + ## notes: + + ## hrPerm + ## invitePerm -- nope + ## addMemberPerm = 8 + ## removeMemberPerm = 8 + ## promotePerm = 8 + ## demotePerm = 8 + ## rulerPerm + ## deplomacyPerm -- not in first version + ## createOfficePerm -- not in first version + ## propogandaPerm -- founder (all together) + ## newsAddPerm = 8 + ## newsEditPerm = 8 + ## newsRemovePerm = 8 + ## pollAddPerm = 8 + ## pollRemovePerm = 8 + ## treasurerPerm -- founder (all together) + ## taxPerm = 8 + ## bankPerm = 8 + ## salaryPerm = 8 + ## auctionierPerm -- tbd (buy and sell together) anybody + ## auctionSellPerm = 1 + ## auctionBuyPerm = 1 + ## auctionRemovePerm -- not in first version + ## governorPerm -- founder (all together) + ## placeBuildingPerm = 16 + ## removeBuildingPerm = 16 + ## npcHirePerm = 16 + ## npcFirePerm = 16 + ## admiralPerm + ## shipSellPerm = 32 -- founder + ## shipBuyPerm = 32 -- founder + ## shipScuttlePerm -- nope + ## shipUsePerm = 4 -- captain + ## leaderPerm + ## captainShipPerm (redundant with shipUsePerm) -- captain + ## leadCrewPerm = 2 -- any member + ## inventoryPerm + ## inventoryBrowsePerm = 1 -- any + ## inventoryAddPerm = 1 -- any + ## inventoryBorrowPerm -- any with limits by title + ## inventoryRemovePerm = 64 -- founder + ## inventoryAuctionPerm -- nope + ## memberPerm + ## votePerm = 1 -- any + ## """ + diff --git a/otp/src/guild/DistributedGuildMemberAI.py b/otp/src/guild/DistributedGuildMemberAI.py new file mode 100644 index 0000000..67ddfeb --- /dev/null +++ b/otp/src/guild/DistributedGuildMemberAI.py @@ -0,0 +1,43 @@ +## """ +## The Guild AI handles all the guilds accross all shards. +## """ + +## from otp.ai.AIBaseGlobal import * +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObjectAI + +## class DistributedGuildMemberAI(DistributedObjectAI.DistributedObjectAI): + ## """ + ## The Guild AI is a global object. + + ## See Also: + ## "otp/src/guild/DistributedGuildMember.py" + ## "otp/src/configfiles/otp.dc" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory('guild') + + ## def __init__(self, air, dateJoined, name): + ## assert self.notify.debugCall() + ## DistributedObjectAI.DistributedObjectAI.__init__(self, air) + ## self.dateJoined=dateJoined + ## self.name=name + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObjectAI.DistributedObjectAI.delete(self) + + ## def getSince(self): + ## """ + ## returns the date the avatar joined the guild. + ## """ + ## return self.dateJoined + + ## def getName(self): + ## return self.name + + ## def getReputations(self): + ## return self.reputations diff --git a/otp/src/guild/DistributedGuildMembership.py b/otp/src/guild/DistributedGuildMembership.py new file mode 100644 index 0000000..778680f --- /dev/null +++ b/otp/src/guild/DistributedGuildMembership.py @@ -0,0 +1,26 @@ +## """ +## """ + +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObject + +## class DistributedGuildMembership(DistributedObject.DistributedObject): + ## """ + ## See Also: + ## "otp/src/configfiles/otp.dc" + ## "otp/src/guild/DistributedGuildMembershipAI.py" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory( + ## 'DistributedGuildMembership') + + ## def __init__(self, air): + ## assert self.notify.debugCall() + ## DistributedObject.DistributedObject.__init__(self, air) + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObject.DistributedObject.delete(self) diff --git a/otp/src/guild/DistributedGuildMembershipAI.py b/otp/src/guild/DistributedGuildMembershipAI.py new file mode 100644 index 0000000..03c023a --- /dev/null +++ b/otp/src/guild/DistributedGuildMembershipAI.py @@ -0,0 +1,26 @@ +## """ +## """ + +## from direct.distributed.ClockDelta import * + +## from direct.directnotify import DirectNotifyGlobal +## from direct.distributed import DistributedObjectAI + +## class DistributedGuildMembershipAI(DistributedObjectAI.DistributedObjectAI): + ## """ + ## See Also: + ## "otp/src/configfiles/otp.dc" + ## "otp/src/guild/DistributedGuildMembership.py" + ## """ + ## if __debug__: + ## notify = DirectNotifyGlobal.directNotify.newCategory( + ## 'DistributedGuildMembershipAI') + + ## def __init__(self, air): + ## assert self.notify.debugCall() + ## DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + ## def delete(self): + ## assert self.notify.debugCall() + ## self.ignoreAll() + ## DistributedObjectAI.DistributedObjectAI.delete(self) diff --git a/otp/src/guild/Sources.pp b/otp/src/guild/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/guild/__init__.py b/otp/src/guild/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/guild/buildings.txt b/otp/src/guild/buildings.txt new file mode 100644 index 0000000..aae8ada --- /dev/null +++ b/otp/src/guild/buildings.txt @@ -0,0 +1,93 @@ + +buildings/locations version 0.0.1 + +[brothel] +apothecary +auction house (auctions) +bank (coins) +barracks +blacksmith (repairs, horseshoes, ships, gear) +carpenter +courthouse +farm house +fort +general supplies +glass blower +grain mill +guild hall (quests, news, leader board) +house +hut +inn (NPCs) +jail +library +lumber mill +magic shop +mansion (governor's) +market +mason/masonry worker +Practice/training hall +rope maker +sail maker +shipyard (ships) +stables +store house (gear) +tailor +tavern (card games) +tower, stone +tower, wood +villa +warehouse + +battlements +bridge, wood +bridge, stone +buried treasure +cannon +corral/ranch (horses/cows) +courtyard +dock, boat +dock, fishing +dock, loading (with crane) +farm land +fence +fire, bonfire +fire, camp +fire, torch/lantern +flag pole +gallows/guillotine/stocks +gun emplacement +mine +park/garden +portcullis +quarry +road, causeway +road, cobblestone +road, dirt +stair +trees +trench/ditch +walking path +wall, high +wall, low +water well + +breakwater +cave +cavern +cliff +granet +harbor +hill +jungle +lake +lava flow +marsh +mountain +pond +reefs +river +sand +soil +swamp +volcano +waterfall diff --git a/otp/src/guild/guildNotes.txt b/otp/src/guild/guildNotes.txt new file mode 100644 index 0000000..aa2710f --- /dev/null +++ b/otp/src/guild/guildNotes.txt @@ -0,0 +1,540 @@ + +Guilds version 0.0.4 + + +Guild Member: + name (of avatar) + dna (of avatar) + location (in game world) + donations (lit) + rank (in this guild) + is online (computed) + date joined + referred by (reference) + referrals (list) + nominationApprovedBy (reference) + commander/captain (reference) + tax level + tax history (list) + vote history (list) + salary + reputations at this gulid + may vote (bool) + + +Guild Gear: + item (reference) + donatedBy + rentedTo + bidPrice + auctionEndDate + buyNowPrice + + + + +Guilds version 0.0.4 + +(1) + GuildLevel1: + Requirements: + Go to the office of guild managment, + choose a name, + and pay the fee (e.g. 50 gp) + + Data Attributes: + guildName (RO string) + dateCreated (RO date) + owner (reference) + + Computed Attributes: + guildStatusLevel (computed) + requirements for next guildStatusLevel + + New Actions Available: + disband (ownerPerm) + invite second member (ownerPerm) + + note: guild names may not be edited + + Important Events to Watch For: + another players accepting invitation to join + - immediately upgrades the guild to level 2. + + +(2) + GuildLevel2: + Requirements: + Level (1) + Two or more members in the guild + + Data Attributes: + members (list) + chatLog (RO list) + guildFlag (DNA) + autoApplicationHandling (off, accept, or reject) + reputationTotals (list) + reputationHighest (list) + offices (list) + office: (Membership/application Management) (gets hrPerm) + applicants (list) + invitations (list) + + Computed Attributes: + onlineMemberCount (computed integer) + highestMemberCount (computed integer) + requirements for next guildStatusLevel + + New Actions Available: + invite (invitePerm) + view members + info (avatarInfoPerm) + bootOut (hrPerm) + promote (hrPerm) + demote (hrPerm) + whisper to (whisperPerm) + report/accuse (list) + view applicants + accept join request (hrPerm) + decline join request (hrPerm) + view guild flag + edit guild flag (ownerPerm) + choose guild flag (ownerPerm) + guild chat (chatPerm) + message all in guild (broadcastPerm) + + note: the flag is not visible to other players yet (no flown) + note: reputationAverage is not shown as it may discurrage inviting + new players. + note: no ignore list + note: no ban list + + Important Events to Watch For: + Purchase guild flag + - immediately upgrades the guild to level 3. + + +(3) + GuildLevel3: + Requirements: + Level (2) + Go to the office of guild management + choose a flag + buy the flag (nominal fee) + + Data Attributes: + Quest History (RO list) + office: foriegn affairs + currentQuests (list) + + Computed Attributes: + completedQuestCount (integer) + + New Actions Available: + quests (quests that increase guild reputation) + view guild reputations + fly guild flag + + Removed Actions (no longer able to): + choose guild flag (ownerPerm) + + +(4) + GuildLevel4: + Requirements: + Level (3) + Repulation level of N(?) + + Data Attributes: + News History (RO list) + offices: (News Mgmt) + currentNews (list) + + Computed Attributes: + newsItemsCount (integer) + requirements for next guildStatusLevel + + New Actions Available: + create news item (newsPerm) + view current news (memberPerm) + edit current news (newsPerm) + view news history (viewHistoryPerm) + appoint (News Mgmt) (ownerPerm) + + +(5) + GuildLevel5: + Requirements: + Level (4) + Repulation level of N(?) + + - something that you can sail to, + but not place buildings on or + build/create new ships. + + Data Attributes: + Tax History (RO list) + Inventory (list) + bank? (or is this part of gear?) + islands (hide out) + gear + burried treasure? + treasure maps? (or are these quests?) + guildTaxLevel + office: Tax Collector (Tax Mgmt) + office: Treasurer (Inventory Mgmt) + office: (Salary Mgmt) + + Computed Attributes: + gearItemsCount (integer) + requirements for next guildStatusLevel + + New Actions Available: + donate + money + items + sellGearToGuild + sellShipToGuild + view inventory + info + rent + borrow + sell + throwAway + banking + info + loan + give + view taxes + changeGuildTaxLevel + ? view tax history + salary + + +(6) + GuildLevel6: + Requirements: + Level (5) + + Data Attributes: + Poll History (RO list) + office: (Poll Mgmt) + currentPoll + + note: no pollTax + + Computed Attributes: + requirements for next guildStatusLevel + + New Actions Available: + create poll + view current poll + vote (votePerm) + edit poll (pollPerm) + cancel poll (pollPerm) + end poll (pollPerm) + +(7) + GuildLevel7: + Requirements: + Level (6) + + Data Attributes: + inventory: ships (list) + office: Admiral (Ship Mgmt) + + Computed Attributes: + shipCount (integer) + requirements for next guildStatusLevel + + New Actions Available: + purchase guild ship (admiralPerm) + view ships + info (captainPerm) + rename (admiralPerm) + reconfigure (shipwrightPerm) + assign (admiralPerm) + repair (shipwrightPerm) + sell (admiralPerm) + auction (admiralPerm) + scuttle (admiralPerm) + + Important Events to Watch For: + Watch for purchase of guild ship + - go to level (8) + + +(8) + GuildLevel8: + Requirements: + Level (7) + Guild Ship + + Data Attributes: + inventory: buildings (list) + inventory: locations that are buildable? (list) + inventory: NPCs (do NPCs pay taxes?) + inventory: auctions (to members (and non-members?)) + office: Auctionier (Auction Mgmt) + office: Governor (Island/building Mgmt) + office: (NPC Mgmt) + office: (Officer/promotion Mgmt) + leaderboard (between guilds and inside guild, who contributed money or completed quests). + community tools + meetings ? (events?) calender + message board + + Computed Attributes: + buildingCount (integer) + npcCount (integer) + requirements for next guildStatusLevel + + New Actions Available: + develop land? (create locations for buildings?) + + +? Relations (list -- allies, enimies, (neutral implicet) -- guilds, avatars, NPCs) + +What if someone leaves a guild? What happens to the guild reputation? + +There are many types of reputation (one for each skill). + - Also track reputation per guild per player. + - as well as each player individually + - reputation influences renting valuable equipment. + + + +Guilds version 0.0.4 + +Combat Guild Types: + Boxer + Cannonier/Grenadier + Fencer + Guardsman + Magic + Muskettier + Raw/Wild + Roughian/Sailer + Swordsman + + Initial Release Abilities: + brawling/bare fisted/martial/wrestling/makeshift weapons + cannon/gunnery/artilery + club/sap/blackjack/mace/shovel + cutlass/sword + dagger + magic type 1 + magic type 2 + musket + pistol + spear/trident + + Later Release Abilities: + blowgun + bolo/rope/net (entanglement) + bow/archery + chain/morningstar/ball and chain + crossbow + grenade (not hand grenade)/powder keg + harpoon (spear with a cord) + magic type 3 + pike/halberd/pole arms + rake/hoe/scythe (farmering tools) + rapier/fencing + torch/lantern (fire) + whip + + +Craft Guild Types: + Adventurer + Blacksmith/Tinker + Botonist/Naturalist + Captain + Carpenter/Shipwright + Cook + Explorer/Tracker/Hunter + Farmer + Fisher + Navigator + Mariner + Merchant + Native crafts + Tailer + + Craft Abilities: + bandaging/first aid + blacksmithing/create/repair metal + brewing + carpentry + cartography + climbing + cooking + deplomacy + farming + fishing + foraging + flag making + haggling + identify plant + leadership (may manage/form crew) + magic 'stuff' creation + make cannon + make power + make shot + navigtion + rowing/rigging + sewing/tailoring + swabbing the deck/ship maintenance + swimming + tinkering + tracking + wilderness survival + + +Land Owner Guild Types: + Beuracrat + Govener + Native + + Land Owner Abilities: + buy big ships + designate officers + group bank + group inventory + make laws + own navy + place building construction + + +Scribbling: + +Guild Types: + Adventurer + Beuracrat + Black Smith/Tinker + Botonist/Naturalist + Boxer + Cannonier/Grenadier + Captain + Carpenter/Shipwright + Cook + Explorer/Tracker/Hunter + Farmer + Fisher + Fencer/Gentelman + Guardsman + Navigator + Magic + Mariner + Merchant + Muskettier + Native + Roughian/Sailer + Swordsman + Tailer + + +Sword Fighting Guilds: + Swordsman + dagger + cutlass + + Muskettier + musket + pistol + + rapier + + Fisher + spear/trident + rope/net + + harpoon + + fishing + swimming + rowing + + Farmer + rake/hoe(farmer's tools) + scythe + + Hunter + crossbow + + bow + bolo + + Cannonier + cannon/gunnery/artilery + + Sailer + club/sap/blackjack/mace/shovel + climbing + + Mariner + Fisher + Sailer + + Navigator + navigtion + cartography + + Captain + leadership (may manage/form crew) + + Beuracrat + leadership (may manage/form crew) + + Gentelman + rapier + + Tinker + + Shipwright + + Carpenter + + Black Smith + repair metal + make shot + make cannon + + Boxer + brawling/bare fisted/martial/wrestling + + Adventurer + whip + + Explorer + + Tracker + + Botonist/Naturalist + identify plant + + Magic + + Merchant + + Cook + cooking + foraging + bandaging + + Native + bow + + Grenadier + grenade (not hand grenade)/powder keg + + Tailer + sewing + + Guardsman + pike/halberd/pole arms + + + + blowgun + chain/morningstar diff --git a/otp/src/inventory/.cvsignore b/otp/src/inventory/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/inventory/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/inventory/Sources.pp b/otp/src/inventory/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/inventory/__init__.py b/otp/src/inventory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/launcher/DownloadWatcher.py b/otp/src/launcher/DownloadWatcher.py new file mode 100644 index 0000000..a783b69 --- /dev/null +++ b/otp/src/launcher/DownloadWatcher.py @@ -0,0 +1,51 @@ + +from direct.task import Task +from otp.otpbase import OTPLocalizer +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase.DirectObject import DirectObject + +class DownloadWatcher(DirectObject): + def __init__(self, phaseNames): + self.phaseNames = phaseNames + self.text = DirectLabel(relief = None, + guiId = 'DownloadWatcherText', + pos = (-0.96, 0, -0.91), + text = OTPLocalizer.DownloadWatcherInitializing, + text_fg = (1,1,1,1), + # text_shadow = (0,0,0,1), + text_scale = 0.05, + textMayChange = 1, + text_align = TextNode.ALeft, + sortOrder = 50, + ) + self.bar = DirectWaitBar( + guiId = 'DownloadWatcherBar', + pos = (-0.81,0,-0.96), + relief = DGG.SUNKEN, + frameSize = (-0.6,0.6,-0.1,0.1), + borderWidth = (0.02,0.02), + scale = 0.25, + range = 100, + sortOrder = 50, + frameColor = (0.5,0.5,0.5,0.5), + barColor = (0.2,0.7,0.2,0.5), + text = "0%", + text_scale = 0.16, + text_fg = (1, 1, 1, 1), + text_align = TextNode.ACenter, + text_pos = (0,-0.05), + ) + + self.accept("launcherPercentPhaseComplete", self.update) + + def update(self, phase, percent, reqByteRate, actualByteRate): + phaseName = self.phaseNames[phase] + self.text['text'] = (OTPLocalizer.DownloadWatcherUpdate % (phaseName)) + self.bar['text'] = ("%s %%" % (percent)) + self.bar['value'] = percent + + def cleanup(self): + self.text.destroy() + self.bar.destroy() + self.ignoreAll() diff --git a/otp/src/launcher/DummyLauncherBase.py b/otp/src/launcher/DummyLauncherBase.py new file mode 100644 index 0000000..25723b2 --- /dev/null +++ b/otp/src/launcher/DummyLauncherBase.py @@ -0,0 +1,179 @@ + +# It's important to import PandaModules first, in particular before +# importing Task, because once PandaModules is imported, Task.py can +# find pandac.pandaexpressModules. +from pandac.PandaModules import * + +import string +from direct.showbase.MessengerGlobal import * +from direct.showbase.DirectObject import DirectObject +from direct.showbase.EventManagerGlobal import * +from direct.task.TaskManagerGlobal import * +from direct.task.Task import Task + +class DummyLauncherBase: + def __init__(self): + self.logPrefix = '' + self._downloadComplete = False + self.phaseComplete = {} + for phase in self.LauncherPhases: + self.phaseComplete[phase] = 0 + self.firstPhase = self.LauncherPhases[0] + self.finalPhase = self.LauncherPhases[-1] + # Fake download hash values + self.launcherFileDbHash = HashVal() + self.serverDbFileHash = HashVal() + self.setPandaErrorCode(0) + self.setServerVersion("dev") + + def isDummy(self): + # Is this the DummyLauncher? Yes + return 1 + + def startFakeDownload(self): + if ConfigVariableBool('fake-downloads', 0).getValue(): + duration = ConfigVariableDouble('fake-download-duration', 60).getValue() + self.fakeDownload(duration) + else: + for phase in self.LauncherPhases: + self.phaseComplete[phase] = 100 + self.downloadDoneTask(None) + + def isTestServer(self): + # Change this depending on what you want to test + return base.config.GetBool("is-test-server", 0) + + def setPhaseCompleteArray(self, newPhaseComplete): + # useful for testing + self.phaseComplete = newPhaseComplete + + def setPhaseComplete(self, phase, percent): + # again, for testing + self.phaseComplete[phase] = percent + + def getPhaseComplete(self, phase): + return (self.phaseComplete[phase] >= 100) + + def setPandaWindowOpen(self): + self.windowOpen = 1 + + def setPandaErrorCode(self, code): + """ + Set the exit code of panda. 0 means everything ok + """ + self.pandaErrorCode = code + + def getPandaErrorCode(self): + return self.pandaErrorCode + + def setDisconnectDetailsNormal(self): + self.disconnectCode = 0 + self.disconnectMsg = 'normal' + + def setDisconnectDetails(self, newCode, newMsg): + self.disconnectCode = newCode + self.disconnectMsg = newMsg + + def setServerVersion(self, version): + """ + Set the server version. + """ + self.ServerVersion = version + + def getServerVersion(self): + return self.ServerVersion + + def getIsNewInstallation(self): + return base.config.GetBool("new-installation", 0) + + def setIsNotNewInstallation(self): + pass + + def getLastLogin(self): + if hasattr(self, 'lastLogin'): + return self.lastLogin + return '' + + def setLastLogin(self, login): + self.lastLogin = login + + def setUserLoggedIn(self): + self.userLoggedIn = 1 + + def setPaidUserLoggedIn(self): + self.paidUserLoggedIn = 1 + + def getGameServer(self): + return '206.16.11.19' + + def getAccountServer(self): + return '' + + def getDeployment(self): + """ + Get the language version + """ + return 'US' + + def getBlue(self): + # This is a test DMC blue - this does not get downloaded though + # return "aW8gkhzXUIDxVBr4ywXMGYTZFCMVkg0JNzu5lOqqOUAQL9efNvxfVd3TDWeiyHmCNUW5wMgroVq0YzWdSkcbG$StvC4$9RsVmKhuRQSNHV#080siG5BFfNSwuK#vogKAETqbCKnyfAZmC372Y$ndW4YwQMu9MyQ9RgyZWtEYbvy5lgzmHIJr9gJHTS#EQ9nQViRSuKU9ilWUVVABNJrtOXo1kYtcyF#O" + # return "ummm8ChuwvpzE9X6pCY4XaW9ae3LEx4XAmzdkJN#j5xLOHLnW9YDd#HKs4PTXYrH2GZE0k1vhD0sASZeE5Vp3$3qF#yVLqfeh2We6$eUkwLhZgCGpdihiPWpx9Kc8eHah5dAd$muaI2t2ABaAGaKwNVXpKVuU0Yr9DeXwT1lKS27ZX33Oqhh$bJQgaPw5nc1PIydLhBFOLlxccKN83A4EpM0uxoACo2C"; + # return "EyHDPTHdCX5VFYXS8nvKPudKxaMRyUU4115r2wxO9RLQK7biOax1K9gKPwpzDSdeb8pa9PXni8mYdE#OvdsHorNoyjCMvMkWndpEcBytKp$Hq15PUKIXW2txZ$RyT8m7xPT$CSIMiXf#jCYXtt8gsa9zB2jQXd0SD7f#rm3Xz#6gA9Vv#fE0tIlifz8QK4e3eS7OFxPh$C#wuN4HR$K8KudnUq$VALfZQ5KyL4RlNFH#MnLNyu#lTg" + return None + + def getPlayToken(self): + # If you need a test PlayToken, this is the place for it. + return None + + def getDISLToken(self): + # If you need a test DISLToken, this is the place for it. + return None + + def fakeDownloadPhaseTask(self, task): + percentComplete = min(100, int(round((task.time/float(task.timePerPhase) * 100)))) + # The position in the array is phase-1 because it counts from 0 + self.setPhaseComplete(task.phase, percentComplete) + messenger.send("launcherPercentPhaseComplete", [task.phase, percentComplete, 0, 0]) + if (percentComplete >= 100.0): + messenger.send('phaseComplete-' + `task.phase`) + return Task.done + else: + return Task.cont + + def downloadDoneTask(self, task): + self._downloadComplete = True + messenger.send("launcherAllPhasesComplete") + return Task.done + + def fakeDownload(self, timePerPhase): + self.phaseComplete = { + 1 : 100, + 2 : 100, + 3 : 0, + 3.5 : 0, + 4 : 0, + 5 : 0, + 5.5 : 0, + 6 : 0, + 7 : 0, + 8 : 0, + 9 : 0, + 10 : 0, + 11 : 0, + 12 : 0, + 13 : 0, + } + phaseTaskList = [] + # Some phases should already be downloaded + firstPhaseIndex = self.LauncherPhases.index(self.firstPhase) + for phase in self.LauncherPhases[firstPhaseIndex:]: + phaseTask = Task(self.fakeDownloadPhaseTask, ("phaseDownload"+str(phase))) + phaseTask.timePerPhase = timePerPhase + phaseTask.phase = phase + phaseTaskList.append(phaseTask) + + phaseTaskList.append(Task(self.downloadDoneTask)) + downloadSequence = Task.sequence(*phaseTaskList) + taskMgr.remove("downloadSequence") + taskMgr.add(downloadSequence, "downloadSequence") diff --git a/otp/src/launcher/LauncherBase.py b/otp/src/launcher/LauncherBase.py new file mode 100644 index 0000000..fb3224d --- /dev/null +++ b/otp/src/launcher/LauncherBase.py @@ -0,0 +1,3000 @@ + +################################################## +# # +# OTP Python Game Launcher Base Class # +# # +################################################## + +import sys +import os +import time +import string +import __builtin__ +from pandac.libpandaexpressModules import * +# Import DIRECT files +from direct.showbase.MessengerGlobal import * +from direct.showbase.DirectObject import DirectObject +from direct.showbase.EventManagerGlobal import * +from direct.task.MiniTask import MiniTask, MiniTaskManager +from direct.directnotify.DirectNotifyGlobal import * + +# Redirect Python output and err to the same file +class LogAndOutput: + def __init__(self, orig, log): + self.orig = orig + self.log = log + self.console = False + + def write(self, str): + self.log.write(str) + self.log.flush() + + if self.console: + self.orig.write(str) + self.orig.flush() + + def flush(self): + self.log.flush() + self.orig.flush() + +class LauncherBase(DirectObject): + # override and redefine + GameName = 'game' + ArgCount = 6 + LauncherPhases = [1,2,3,4] + TmpOverallMap = [.25,.25,.25,.25] # totals to 1.00 + + # Various bandwidths (bytes per second). It is important that the + # difference between any two adjacent entries not be too large--we + # should probably never more than double the bandwidth at any + # step, and stepping less than double is preferable in most cases. + BANDWIDTH_ARRAY = [1800, # 14.4 modem + 3600, # 28.8 modem + 4200, # 33.6 modem + 6600, # 56k modem (53.3 actual) + 8000, # 64k ISDN + 12000, + 16000, # 128k ISDN + 24000, + 32000, + 48000, # 384k DSL + 72000, + 96000, # 768k DSL + 128000, + 192000, # 1.5MB DSL/Cable Modem + 250000, + 500000, # LAN + 750000, + 1000000, # 100-base T + 1250000, + 1500000, + 1750000, + 2000000, + 3000000, + 4000000, + 6000000, + 8000000, + 10000000, # Super fast connection + 12000000, + 14000000, + 16000000, + 24000000, + 32000000, + 48000000, + 64000000, + 96000000, + 128000000, # Who knows + 256000000, + 512000000, + 1024000000, # Some day + ] + + # These values were extracted from win32con.py + # This was a very large file to include in our distribution for + # these few constants, so we'll just pull in the ones we need here + # import win32con + win32con_FILE_PERSISTENT_ACLS = 0x00000008 + + # Where is the top directory of the game? + InstallDirKey = "INSTALL_DIR" + # What is the name of the game log file? + GameLogFilenameKey = "GAMELOG_FILENAME" + # Used to tell flash when the window finally opens + PandaWindowOpenKey = "PANDA_WINDOW_OPEN" + # Used to tell browser panda exit status (0 means everything normal) + PandaErrorCodeKey = "PANDA_ERROR_CODE" + # Used to keep track of whether or not this is a new installation + NewInstallationKey = "IS_NEW_INSTALLATION" + # Used to keep track of the last login name that was entered + LastLoginKey = "LAST_LOGIN" + # Used to keep track of the fact that a user has logged in + UserLoggedInKey = "USER_LOGGED_IN" + # Used to keep track of the fact that a paid user has logged in + PaidUserLoggedInKey = "PAID_USER_LOGGED_IN" + # Referrer code + ReferrerKey = "REFERRER_CODE" + # Period timer seconds remaining + PeriodTimeRemainingKey = "PERIOD_TIME_REMAINING" + # Period name + PeriodNameKey = "PERIOD_NAME" + # Period name + SwidKey = "SWID" + # If patch, then from what CD. + PatchCDKey = "FROM_CD" + # The DISL Token + DISLTokenKey = "DISLTOKEN" + + # Stores the proxy server (string) (possibly with a :port) + # Empty string if there is no proxy + ProxyServerKey = "PROXY_SERVER" + ProxyDirectHostsKey = "PROXY_DIRECT_HOSTS" + + launcherFileDbFilename = 'launcherFileDb' + + webLauncherFlag = False + + def __init__(self): + self.started = False + + # This line is to ensure that Python is running in opt mode -OO. + if __debug__: + print "WARNING: Client should run Python optimized -OO" + + self.taskMgrStarted = False + + # Setup the log files + # We want C++ and Python to both go to the same log so they + # will be interlaced properly. + + self._downloadComplete = False + self.pandaErrorCode = 0 + + self.WIN32 = (os.name == 'nt') + + if self.WIN32: + # Vista = windows 2.6 + self.VISTA = (sys.getwindowsversion()[3] == 2 and sys.getwindowsversion()[0] == 6) + else: + # it can't be Vista + self.VISTA = 0 + + # match log format specified in installerBase.cxx, + # want this fmt so log files can be sorted oldest first based on name, + # and so old open handles to logs dont prevent game from starting + ltime = time.localtime() + logSuffix = "%02d%02d%02d_%02d%02d%02d" % (ltime[0]-2000,ltime[1],ltime[2],ltime[3],ltime[4],ltime[5]) + logPrefix = '' + if not self.WIN32: + logPrefix = os.environ.get('LOGFILE_PREFIX', '') + logfile = logPrefix + self.getLogFileName() + '-' + logSuffix + '.log' + self.errorfile = 'errorCode' + + # old game log deletion now managed by activeX control + ## Delete old log files so they do not clog up the disk + ##if os.path.exists(logfile): + ## os.remove(logfile) + + # Open the new one for appending + # Make sure you use 'a' mode (appending) because both Python and + # Panda open this same filename to write to. Append mode has the nice + # property of seeking to the end of the output stream before actually + # writing to the file. 'w' mode does not do this, so you will see Panda + # output and Python output not interlaced properly. + log = open(logfile, 'a') + + logOut = LogAndOutput(sys.__stdout__, log) + logErr = LogAndOutput(sys.__stderr__, log) + sys.stdout = logOut + sys.stderr = logErr + + if sys.platform == "darwin": + # On OSX, we want to get the system profile into the log. + os.system('/usr/sbin/system_profiler >>' + logfile) + elif sys.platform == "linux2": + os.system('cat /proc/cpuinfo >>' + logfile) + os.system('cat /proc/meminfo >>' + logfile) + os.system('/sbin/ifconfig -a >>' + logfile) + + # Write to the log + print "\n\nStarting %s..." % self.GameName + print ("Current time: " + time.asctime(time.localtime(time.time())) + + " " + time.tzname[0]) + print "sys.path = ", sys.path + print "sys.argv = ", sys.argv + print "os.environ = ", os.environ + + if(len(sys.argv)>=self.ArgCount): + Configrc_args = sys.argv[self.ArgCount-1] + print("generating configrc using: '" + Configrc_args + "'") + else: + Configrc_args = "" + print("generating standard configrc") + + # This used to be CONFIG_CONFIG, but with the new Config system we + # don't use the old CONFIG_CONFIG environment variable any more. We + # still need to set PRC_EXECUTABLE_ARGS to specify the command-line + # arguments to pass to Configrc.exe, but the use of Configrc.exe + # itself is a stopgap until we can replace that system with the newer + # signed prc file system. + if os.environ.has_key("PRC_EXECUTABLE_ARGS"): + print "PRC_EXECUTABLE_ARGS is set to: " + os.environ["PRC_EXECUTABLE_ARGS"] + print "Resetting PRC_EXECUTABLE_ARGS" + + # Cannot assign to os.environ here; we have to use + # ExecutionEnvironment to make the low-level prc respect it. + ExecutionEnvironment.setEnvironmentVariable( + "PRC_EXECUTABLE_ARGS", '-stdout ' + Configrc_args) + + # Actually, we still have to set CONFIG_CONFIG too, since Configrc.exe + # itself expects that. + if os.environ.has_key("CONFIG_CONFIG"): + print "CONFIG_CONFIG is set to: " + os.environ["CONFIG_CONFIG"] + print "Resetting CONFIG_CONFIG" + os.environ["CONFIG_CONFIG"] = ":_:configdir_.:configpath_:configname_Configrc.exe:configexe_1:configargs_-stdout " + Configrc_args + + # Now reload the Configrc file. We've actually already run it + # once, but with the wrong command-line options. + cpMgr = ConfigPageManager.getGlobalPtr() + cpMgr.reloadImplicitPages() + + # This is our config object until we get the show running + launcherConfig = getConfigExpress() + __builtin__.config = launcherConfig + + # We'll need a MiniTaskManager to manage our download tasks + # before we've downloaded enough to start the real one. + self.miniTaskMgr = MiniTaskManager() + + # Should the launcher do md5 checks on the files? + # We should probably take this out completely when we ship + self.VerifyFiles = self.getVerifyFiles() + self.setServerVersion(launcherConfig.GetString("server-version", "no_version_set")) + self.ServerVersionSuffix = launcherConfig.GetString("server-version-suffix", "") + + # How many seconds should elapse between telling the user how the + # download is progressing? + self.UserUpdateDelay = launcherConfig.GetFloat('launcher-user-update-delay', 0.5) + + # How much telemetry the game server subtracts out (bytes per second) + self.TELEMETRY_BANDWIDTH = launcherConfig.GetInt('launcher-telemetry-bandwidth', 2000) + + # If we are above the increase threshold percentage, then we will + # increase the bandwidth + self.INCREASE_THRESHOLD = launcherConfig.GetFloat('launcher-increase-threshold', 0.75) + + # If we are below the decrease threshold percentage, we will drop + # back down to the next lower bandwidth + self.DECREASE_THRESHOLD = launcherConfig.GetFloat('launcher-decrease-threshold', 0.5) + + # window length in seconds to look back when asking the + # current byte rate + self.BPS_WINDOW = launcherConfig.GetFloat('launcher-bps-window', 8.0) + + # Should we decrease the bandwidth when the connection is not + # going as fast as it had in the past? + self.DECREASE_BANDWIDTH = launcherConfig.GetBool('launcher-decrease-bandwidth', 1) + + # What is our ceiling on downloader bandwidth? This is mainly + # useful for testing. Set it to 0 to impose no ceiling. + self.MAX_BANDWIDTH = launcherConfig.GetInt('launcher-max-bandwidth', 0) + + # Give Panda the same log we use + self.nout = MultiplexStream() + Notify.ptr().setOstreamPtr(self.nout, 0) + self.nout.addFile(Filename(logfile)) + + if launcherConfig.GetBool('console-output', 0): + # Dupe output to the console (stderr, stdout) only if a + # developer has asked us to via the prc file. + self.nout.addStandardOutput() + sys.stdout.console = True + sys.stderr.console = True + + # create a DirectNotify category for the Launcher + self.notify = directNotify.newCategory("Launcher") + + # The launcher needs a reliable clock for various reasons + self.clock = TrueClock.getGlobalPtr() + + # This prefix is also prepended to screenshot filenames. + self.logPrefix = logPrefix + + # Is this the test server? + self.testServerFlag = self.getTestServerFlag() + self.notify.info("isTestServer: %s" % (self.testServerFlag)) + + # The URL for the download server and directory. + downloadServerString = launcherConfig.GetString('download-server', '') + if downloadServerString: + self.notify.info("Overriding downloadServer to %s." % (downloadServerString)) + else: + downloadServerString = self.getValue('DOWNLOAD_SERVER', '') + self.notify.info("Download Server List %s" % (downloadServerString)) + + # server is a semicolon-delimited list of URL's. + self.downloadServerList = [] + for name in string.split(downloadServerString, ';'): + url = URLSpec(name, 1) + self.downloadServerList.append(url) + + # self.downloadServer is the current download server we are + # contemplating. When it is proven bad, we switch to the next + # one. + self.nextDownloadServerIndex = 0 + self.getNextDownloadServer() + + self.gameServer = self.getGameServer() + self.notify.info("Game Server %s" % (self.gameServer)) + + # The number of times to retry each server + self.downloadServerRetries = 3 + # Number of times to retry a multifile + self.multifileRetries = 1 + self.curMultifileRetry = 0 + + # The number of seconds to wait between retries + self.downloadServerRetryPause = 1 + + # Start at the top of the possible bandwidths, so we can + # go downward to reach the actual bandwidth. + self.bandwidthIndex = len(self.BANDWIDTH_ARRAY) - 1 + self.everIncreasedBandwidth = 0 + + self.goUserName = "" + + # How much of each step counts towards the 99 percent + # of having this phase be completely done + # The last 1 percent is set when everything is done + self.downloadPercentage = 90 + self.decompressPercentage = 5 + self.extractPercentage = 4 + + self.lastLauncherMsg = None + + # Local client side directories + self.topDir = Filename.fromOsSpecific(self.getValue(self.InstallDirKey, '.')) + + # set the gamelog filename in the registry + self.setRegistry(self.GameLogFilenameKey, logfile) + + # If need to patch, determine which directory to patch from + tmpVal = self.getValue(self.PatchCDKey) + if tmpVal == None: + self.fromCD = 0 + else: + self.fromCD = tmpVal + self.notify.info('patch directory is ' + `self.fromCD`) + + assert self.notify.debug("init: Launcher found install dir: " + self.topDir.cStr()) + + # Relative directories from the topDir + self.dbDir = self.topDir + self.patchDir = self.topDir + self.mfDir = self.topDir + + # The directory (within the version directory) in which to + # find most files on the download server + self.contentDir = 'content/' + + + + # The name of the client db file + self.clientDbFilename = 'client.ddb' + self.compClientDbFilename = self.clientDbFilename + '.pz' + + # The name of the server db file + self.serverDbFilename = 'server.ddb' + self.compServerDbFilename = self.serverDbFilename + '.pz' + + # The file path of the server db file on the download server + self.serverDbFilePath = self.contentDir + self.compServerDbFilename + + # The file path of the client db file on the download server + self.clientStarterDbFilePath = self.contentDir + self.compClientDbFilename + + # The file name of the progress meter file + self.progressFilename = 'progress' + self.overallComplete = 0 + self.progressSoFar = 0 + + # Patch files extension + self.patchExtension = 'pch' + + # scan for hack programs + self.scanForHacks() + + # Start downloading at this phase + self.firstPhase = self.LauncherPhases[0] + self.finalPhase = self.LauncherPhases[-1] + # Phase at which we may open a 3d window and start the show + self.showPhase = 3.5 + self.numPhases = len(self.LauncherPhases) + + # A map of phase number -> percentDone. For example {3:100, 4:50, 5:0} + self.phaseComplete = {} + # We need a flag for each phase stating whether downloading the multifile or just patch + self.phaseNewDownload = {} + # Also a map of each phase in terms of overall download + # todo: generate this list as part of build launcher + self.phaseOverallMap = {} + tmpOverallMap = self.TmpOverallMap + tmpPhase3Map = [0.001,0.996,0.0,0.0,0.003] + + # Initialize all phases 0 percent done for starters + phaseIdx = 0 + for phase in self.LauncherPhases: + # Clear the registry on each phase's percentage completion + percentPhaseCompleteKey = "PERCENT_PHASE_COMPLETE_" + `phase` + self.setRegistry(percentPhaseCompleteKey, 0) + # Initialize + self.phaseComplete[phase] = 0 + self.phaseNewDownload[phase] = 0 + # Init each phases percentage in overall download + self.phaseOverallMap[phase] = tmpOverallMap[phaseIdx] + phaseIdx += 1 + + self.patchList = [] + self.reextractList = [] + self.byteRate = 0 + self.byteRateRequested = 0 + self.resetBytesPerSecond() + + self.dldb = None + self.currentMfname = None + self.currentPhaseIndex = 0 + self.currentPhase = self.LauncherPhases[self.currentPhaseIndex] + self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhaseIndex] + + # Make sure we were able to run Configrc. Check the server + # version as a flag for this. + if self.getServerVersion() == 'no_version_set': + self.setPandaErrorCode(10) + self.notify.info("Aborting, Configrc did not run!") + sys.exit() + + self.launcherMessage(self.Localizer.LauncherStartingMessage) + + # Every download uses this HTTPClient. + self.http = HTTPClient() + + if self.http.getProxySpec() == '': + # If the HTTPClient doesn't have a proxy already set from + # the Configrc file, get this from the registry. + self.http.setProxySpec(self.getValue(self.ProxyServerKey, '')) + self.http.setDirectHostSpec(self.getValue(self.ProxyDirectHostsKey, '')) + + self.notify.info("Proxy spec is: %s" % (self.http.getProxySpec())) + if self.http.getDirectHostSpec() != '': + self.notify.info("Direct hosts list is: %s" % (self.http.getDirectHostSpec())) + + self.httpChannel = self.http.makeChannel(0) + self.httpChannel.setDownloadThrottle(1) + + # First, try to find a download server that will talk to us at + # all. + connOk = 0 + while not connOk: + proxies = self.http.getProxiesForUrl(self.downloadServer) + if proxies == 'DIRECT': + self.notify.info("No proxy for download.") + else: + self.notify.info("Download proxy: %s" % (proxies)) + + testurl = self.addDownloadVersion(self.launcherFileDbFilename) + connOk = self.httpChannel.getHeader(DocumentSpec(testurl)) + statusCode = self.httpChannel.getStatusCode() + statusString = self.httpChannel.getStatusString() + + if not connOk: + # No good. + self.notify.warning("Could not contact download server at %s" % (testurl.cStr())) + self.notify.warning("Status code = %s %s" % (statusCode, statusString)) + if statusCode == 407 or statusCode == 1407 or \ + statusCode == HTTPChannel.SCSocksNoAcceptableLoginMethod: + self.setPandaErrorCode(3) + elif statusCode == 404: + # A 404 means we're probably trying to access an + # old version. We'll call this a + # server-refused-connection error. + self.setPandaErrorCode(13) + elif statusCode < 100: + # statusCode below 100 implies the connection + # attempt itself failed. This is usually due to + # firewall software interfering. + self.setPandaErrorCode(4) + elif statusCode > 1000: + # A status code over 1000 means something went fubar + # with the proxy. + self.setPandaErrorCode(9) + else: + # Some other status code means we could open a + # connection, but couldn't negotiate the download for + # some reason. This is a bigger problem. + self.setPandaErrorCode(6) + + if not self.getNextDownloadServer(): + sys.exit() + + self.notify.info("Download server: %s" % (self.downloadServer.cStr())) + + # Allow us to inc and dec the bandwidth by hand for testing + if self.notify.getDebug(): + self.accept('page_up', self.increaseBandwidth) + self.accept('page_down', self.decreaseBandwidth) + + # From now on, we will attempt to re-use the same connection + # to the download server, once we are connected. + self.httpChannel.setPersistentConnection(1) + + # Start in the foreground + self.foreground() + self.prepareClient() + self.setBandwidth() + self.downloadLauncherFileDb() + + def getTime(self): + return self.clock.getShortTime() + + def isDummy(self): + # Is this the DummyLauncher? No + return 0 + + def getNextDownloadServer(self): + if self.nextDownloadServerIndex >= len(self.downloadServerList): + # There are no more download servers to try. Too bad. + self.downloadServer = None + return 0 + + # There are more download servers to try; try the next one. + self.downloadServer = self.downloadServerList[self.nextDownloadServerIndex] + self.notify.info("Using download server %s." % (self.downloadServer.cStr())) + self.nextDownloadServerIndex += 1 + return 1 + + def getProductName(self): + config = getConfigExpress() + productName = config.GetString('product-name', '') + if productName and (productName != 'DisneyOnline-US'): + productName = "_%s" % productName + else: + productName = '' + return productName + + + #============================================================ + # Priority functions + #============================================================ + + def background(self): + # Make the launcher operate in the background + self.notify.info('background: Launcher now operating in background') + self.backgrounded = 1 + + def foreground(self): + # Make the launcher operate in the foreground + self.notify.info('foreground: Launcher now operating in foreground') + self.backgrounded = 0 + + #============================================================ + # Registry functions + #============================================================ + + def setRegistry(self, key, value): + self.notify.info('DEPRECATED setRegistry: %s = %s' % (key, value)) + return + + def getRegistry(self, key): + self.notify.info('DEPRECATED getRegistry: %s' % (key)) + return None + + #============================================================ + # Error Handling + #============================================================ + + def handleInitiateFatalError(self, errorCode): + self.notify.warning('handleInitiateFatalError: ' + errorToText(errorCode)) + # TODO: set error code + sys.exit() + + def handleDecompressFatalError(self, task, errorCode): + self.notify.warning('handleDecompressFatalError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handleDecompressWriteError(self, task, errorCode): + self.notify.warning('handleDecompressWriteError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handleDecompressZlibError(self, task, errorCode): + self.notify.warning('handleDecompressZlibError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handleExtractFatalError(self, task, errorCode): + self.notify.warning('handleExtractFatalError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handleExtractWriteError(self, task, errorCode): + self.notify.warning('handleExtractWriteError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handlePatchFatalError(self, task, errorCode): + self.notify.warning('handlePatchFatalError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handlePatchWriteError(self, task, errorCode): + self.notify.warning('handlePatchWriteError: ' + errorToText(errorCode)) + self.miniTaskMgr.remove(task) + self.handleGenericMultifileError() + + def handleDownloadFatalError(self, task): + # This function may return if we should try the download + # again, or it will not return if we are out of download + # servers to try. + + self.notify.warning('handleDownloadFatalError: status code = %s %s' % + (self.httpChannel.getStatusCode(), self.httpChannel.getStatusString())) + self.miniTaskMgr.remove(task) + statusCode = self.httpChannel.getStatusCode() + if statusCode == 404: + self.setPandaErrorCode(5) + elif statusCode < 100: + # statusCode < 100 implies the connection attempt itself + # failed. This is usually due to firewall software + # interfering. Apparently some firewall software might + # allow the first connection and disallow subsequent + # connections; how strange. + self.setPandaErrorCode(4) + else: + # There are other kinds of failures, but these will + # generally have been caught already by the first test; so + # if we get here there may be some bigger problem. Just + # give the generic "big problem" message. + self.setPandaErrorCode(6) + + if not self.getNextDownloadServer(): + sys.exit() + + def handleDownloadWriteError(self, task): + self.notify.warning('handleDownloadWriteError.') + self.miniTaskMgr.remove(task) + self.setPandaErrorCode(2) + sys.exit() + + def handleGenericMultifileError(self): + if not self.currentMfname: + # TODO: we got an error outside a multifile. Could have been + # some error downloading the databases. This case is not + # handled yet, and should be rare. + # TODO: We should set an error code + sys.exit() + + if self.curMultifileRetry < self.multifileRetries: + self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries)) + self.curMultifileRetry += 1 + # Ok, something did not work for some reason. Well I guess + # we have to redownload the entire phase now. I admit, this is + # a big hammer, but problems in this area tend to be fundamental + # problems with the initial file data and require a redownload. + self.notify.info('downloadPatchDone: Recovering from error.' + + ' Deleting files in: ' + self.currentMfname) + # Mark this multifile as incomplete now + self.dldb.setClientMultifileIncomplete(self.currentMfname) + # Mark it as size 0 + self.dldb.setClientMultifileSize(self.currentMfname, 0) + # Redownload this multifile + self.notify.info('downloadPatchDone: Recovering from error.' + + ' redownloading: ' + self.currentMfname) + # Cleanup the HTTP in case it was halfway into something + self.httpChannel.reset() + self.getMultifile(self.currentMfname) + else: + self.setPandaErrorCode(6) + self.notify.info('handleGenericMultifileError: Failed to download multifile') + sys.exit() + + def foregroundSleep(self): + # If we are running in the foreground, we do not want to + # consume all the cpu, so we will sleep for a moment here + if not self.backgrounded: + time.sleep(self.ForegroundSleepTime) + + def forceSleep(self): + # If we are running in the foreground, we do not want to + # consume all the cpu, so we will sleep for a moment here + if not self.backgrounded: + time.sleep(3.00) + + #============================================================ + # Download functions + #============================================================ + + def addDownloadVersion(self, serverFilePath): + url = URLSpec(self.downloadServer) + origPath = url.getPath() + if origPath and origPath[-1] == '/': + origPath = origPath[:-1] + + if self.fromCD: + url.setPath(self.getCDDownloadPath(origPath, serverFilePath)) + else: + url.setPath(self.getDownloadPath(origPath, serverFilePath)) + + self.notify.info('***' + url.cStr()) + + return url + + def download(self, serverFilePath, localFilename, callback, + callbackProgress): + self.launcherMessage(self.Localizer.LauncherDownloadFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.downloadTask) + task.downloadRam = 0 + task.serverFilePath = serverFilePath + task.serverFileURL = self.addDownloadVersion(serverFilePath) + self.notify.info('Download request: %s' % (task.serverFileURL.cStr())) + task.callback = callback + task.callbackProgress = callbackProgress + task.lastUpdate = 0 + self.resetBytesPerSecond() + task.localFilename = localFilename + + # Initiate the download + self.httpChannel.beginGetDocument(DocumentSpec(task.serverFileURL)) + self.httpChannel.downloadToFile(task.localFilename) + self.miniTaskMgr.add(task, 'launcher-download') + + def downloadRam(self, serverFilePath, callback): + self.ramfile = Ramfile() + + task = MiniTask(self.downloadTask) + task.downloadRam = 1 + task.serverFilePath = serverFilePath + task.serverFileURL = self.addDownloadVersion(serverFilePath) + self.notify.info('Download request: %s' % task.serverFileURL.cStr()) + task.callback = callback + task.callbackProgress = None + task.lastUpdate = 0 + self.resetBytesPerSecond() + + # Initiate the download + self.httpChannel.beginGetDocument(DocumentSpec(task.serverFileURL)) + self.httpChannel.downloadToRam(self.ramfile) + self.miniTaskMgr.add(task, 'launcher-download') + + def downloadTask(self, task): + self.maybeStartGame() + if self.httpChannel.run(): + # Nothing to read, nothing wrong, come back next frame + + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + self.testBandwidth() + + # We do not update the downloadDb with these files, + # only multifiles. + if (task.callbackProgress): + task.callbackProgress(task) + bytesWritten = self.httpChannel.getBytesDownloaded() + totalBytes = self.httpChannel.getFileSize() + if totalBytes: + pct = int(round(bytesWritten/float(totalBytes) * 100)) + self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": pct}) + else: + self.launcherMessage(self.Localizer.LauncherDownloadFileBytes % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "bytes": bytesWritten,}) + self.foregroundSleep() + return task.cont + + statusCode = self.httpChannel.getStatusCode() + statusString = self.httpChannel.getStatusString() + self.notify.info('HTTP status %s: %s' % (statusCode, statusString)) + + if self.httpChannel.isValid() and \ + self.httpChannel.isDownloadComplete(): + # We do not update the downloadDb with these files, only + # multifiles. + bytesWritten = self.httpChannel.getBytesDownloaded() + totalBytes = self.httpChannel.getFileSize() + if totalBytes: + pct = int(round(bytesWritten/float(totalBytes) * 100)) + self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": pct,}) + else: + self.launcherMessage(self.Localizer.LauncherDownloadFileBytes % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "bytes": bytesWritten,}) + # NOTE: we do not set the percent phase complete flag here + self.notify.info('downloadTask: Download done: %s' % (task.serverFileURL.cStr())) + # Call the done callback + task.callback() + del task.callback + return task.done + + else: + # Something screwed up. + if statusCode == HTTPChannel.SCDownloadOpenError or \ + statusCode == HTTPChannel.SCDownloadWriteError: + self.handleDownloadWriteError(task) + + elif statusCode == HTTPChannel.SCLostConnection: + # We started downloading, but we lost the connection + # midstream. Try again. + gotBytes = self.httpChannel.getBytesDownloaded() + self.notify.info('Connection lost while downloading; got %s bytes. Reconnecting.' % (gotBytes)) + if task.downloadRam: + self.downloadRam(task.serverFilePath, task.callback) + else: + self.download(task.serverFilePath, task.localFilename, + task.callback, None) + + else: + # 404 not found or some such nonsense. + if self.httpChannel.isValid(): + # Huh? + self.notify.info('Unexpected situation: no error status, but %s incompletely downloaded.' % (task.serverFileURL.cStr())) + self.handleDownloadFatalError(task) + if task.downloadRam: + self.downloadRam(task.serverFilePath, task.callback) + else: + self.download(task.serverFilePath, task.localFilename, + task.callback, None) + return task.done + + #============================================================ + # Download multifile functions + #============================================================ + + def downloadMultifile(self, serverFilename, localFilename, mfname, + callback, totalSize, currentSize, callbackProgress): + if (currentSize != 0) and (currentSize == totalSize): + assert self.notify.debug('downloadMultifile: already done') + callback() + return + + self.launcherMessage(self.Localizer.LauncherDownloadFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.downloadMultifileTask) + mfURL = self.addDownloadVersion(serverFilename) + task.mfURL = mfURL + self.notify.info('downloadMultifile: %s ' % (task.mfURL.cStr())) + task.callback = callback + task.callbackProgress = callbackProgress + task.lastUpdate = 0 + + self.httpChannel.getHeader(DocumentSpec(task.mfURL)) + if self.httpChannel.isFileSizeKnown(): + task.totalSize = self.httpChannel.getFileSize() + assert self.notify.debug('totalSize from header=%s' % task.totalSize) + else: + task.totalSize = totalSize + assert self.notify.debug('totalSize from caller=%s' % task.totalSize) + self.resetBytesPerSecond() + task.serverFilename = serverFilename + task.localFilename = localFilename + task.mfname = mfname + + if currentSize != 0: + if task.totalSize == currentSize: + self.notify.info('already have full file! Skipping download.') + callback() + return + + self.httpChannel.beginGetSubdocument(DocumentSpec(task.mfURL), currentSize, task.totalSize) + self.httpChannel.downloadToFile(task.localFilename, True) + else: + # Full download + self.httpChannel.beginGetDocument(DocumentSpec(task.mfURL)) + self.httpChannel.downloadToFile(task.localFilename) + + self.miniTaskMgr.add(task, 'launcher-download-multifile') + + def downloadPatchSimpleProgress(self, task): + startingByte = self.httpChannel.getFirstByteDelivered() + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesWritten = startingByte + bytesDownloaded + totalBytes = self.httpChannel.getFileSize() + assert self.notify.debug("downloadPatchSimpleProgress: bytesWritten: %s totalBytes: %s" % + (bytesWritten, totalBytes)) + percentPatchComplete = int(round(bytesWritten/float(totalBytes) * self.downloadPercentage)) + self.setPercentPhaseComplete(self.currentPhase, percentPatchComplete) + + def getPercentPatchComplete(self, bytesWritten): + return int(round((self.patchDownloadSoFar + bytesWritten)/float(self.totalPatchDownload) + * self.downloadPercentage)) + + def downloadPatchOverallProgress(self, task): + startingByte = self.httpChannel.getFirstByteDelivered() + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesWritten = startingByte + bytesDownloaded + assert self.notify.debug("downloadPatchOverallProgress: bytesWritten: " + str(bytesWritten)) + + # Calculate the percent done as an int + # Only consider the download 90 percent of the process + # We still need to decompress and extract it too + #self.notify.info('^^^^^download so far = ' + `bytesWritten` + ' $' + `self.patchDownloadSoFar`) + percentPatchComplete = self.getPercentPatchComplete(bytesWritten) + + # Set the percent done in the phase map for phase_3 only + # NOTE: this assumes one multifile per phase, that is, the progress + # through this single multifile represents the progress through the + # current phase. Do not ever set to 100 to prevent roundup from + # prematurely reporting 100. Instead only report 99 here + #self.notify.info('-------percent patch complete = ' + `percentPatchComplete`) + self.setPercentPhaseComplete(self.currentPhase, percentPatchComplete) + + def downloadMultifileWriteToDisk(self, task): + self.maybeStartGame() + startingByte = self.httpChannel.getFirstByteDelivered() + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesWritten = startingByte + bytesDownloaded + assert self.notify.debug("bytesWritten: " + str(bytesWritten)) + + # Read some bytes and write them to disk. + # The download db needs to be told about these bytes + # Record that this multifile has been partially downloaded + if self.dldb: + self.dldb.setClientMultifileSize(task.mfname, bytesWritten) + + # Calculate the percent done as an int + # Only consider the download 90 percent of the process + # We still need to decompress and extract it too + percentComplete = 0 + if task.totalSize != 0: + percentComplete = int(round(bytesWritten/float(task.totalSize) + * self.downloadPercentage)) + # Set the percent done in the phase map + # NOTE: this assumes one multifile per phase, that is, the progress + # through this single multifile represents the progress through the + # current phase. Do not ever set to 100 to prevent roundup from + # prematurely reporting 100. Instead only report 99 here + self.setPercentPhaseComplete(self.currentPhase, percentComplete) + + def downloadMultifileTask(self, task): + # This is a hack to get around some weird behavior observed on + # pirates with the httpChannel + task.totalSize = self.httpChannel.getFileSize() + + if self.httpChannel.run(): + # Nothing to read, nothing wrong, come back next frame + + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + self.testBandwidth() + #self.downloadMultifileWriteToDisk(task) + if (task.callbackProgress): + task.callbackProgress(task) + startingByte = self.httpChannel.getFirstByteDelivered() + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesWritten = startingByte + bytesDownloaded + percentComplete = 0 + if task.totalSize != 0: + percentComplete = int(round( 100.0 * bytesWritten / float(task.totalSize))) + + # We need this feedback if we are on the updating page + self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": percentComplete}) + self.foregroundSleep() + return task.cont + + statusCode = self.httpChannel.getStatusCode() + statusString = self.httpChannel.getStatusString() + self.notify.info('HTTP status %s: %s' % (statusCode, statusString)) + + if self.httpChannel.isValid() and \ + self.httpChannel.isDownloadComplete(): + #self.downloadMultifileWriteToDisk(task) + if (task.callbackProgress): + task.callbackProgress(task) + # NOTE: we do not set the percent phase complete flag here + self.notify.info('done: %s' % (task.mfname)) + # Record that this multifile has been completely downloaded + if self.dldb: + self.dldb.setClientMultifileComplete(task.mfname) + # Call the done callback + task.callback() + del task.callback + return task.done + + else: + # Something screwed up. + if statusCode == HTTPChannel.SCDownloadOpenError or \ + statusCode == HTTPChannel.SCDownloadWriteError: + self.handleDownloadWriteError(task) + + elif statusCode == HTTPChannel.SCLostConnection: + # We started downloading, but we lost the connection + # midstream. Try again. + startingByte = self.httpChannel.getFirstByteDelivered() + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesWritten = startingByte + bytesDownloaded + + self.notify.info('Connection lost while downloading; got %s bytes. Reconnecting.' % (bytesDownloaded)) + self.downloadMultifile(task.serverFilename, task.localFilename, + task.mfname, + task.callback, task.totalSize, + bytesWritten, task.callbackProgress) + + elif (statusCode == 416 or statusCode == HTTPChannel.SCDownloadInvalidRange) and self.httpChannel.getFirstByteRequested() != 0: + # Invalid subrange. Screw it; download the whole file again. + self.notify.info('Invalid subrange; redownloading entire file.') + self.downloadMultifile(task.serverFilename, task.localFilename, + task.mfname, + task.callback, task.totalSize, + 0, task.callbackProgress) + + else: + # 404 not found or some such nonsense. + if self.httpChannel.isValid(): + # Huh? + self.notify.info('Unexpected situation: no error status, but %s incompletely downloaded.' % (task.mfname)) + self.handleDownloadFatalError(task) + self.downloadMultifile(task.serverFilename, task.localFilename, + task.mfname, + task.callback, task.totalSize, + 0, task.callbackProgress) + return task.done + + #============================================================ + # Decompress individual file functions + #============================================================ + + def decompressFile(self, localFilename, callback): + self.notify.info('decompress: request: ' + localFilename.cStr()) + self.launcherMessage(self.Localizer.LauncherDecompressingFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.decompressFileTask) + task.localFilename = localFilename + task.callback = callback + task.lastUpdate = 0 + task.decompressor = Decompressor() + errorCode = task.decompressor.initiate(task.localFilename) + if (errorCode > 0): + self.miniTaskMgr.add(task, 'launcher-decompressFile') + else: + self.handleInitiateFatalError(errorCode) + + def decompressFileTask(self, task): + #if self.notify.getDebug(): + # beforeTime = self.getTime() + errorCode = task.decompressor.run() + #if self.notify.getDebug(): + # self.notify.debug("decompressTask run(): " + str(self.getTime() - beforeTime)) + if (errorCode == EUOk): + # Everything is proceeding normally, come back next frame + + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + progress = task.decompressor.getProgress() + # We need this message if we are on the updating screen + self.launcherMessage(self.Localizer.LauncherDecompressingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": int(round(progress * 100)),}) + self.foregroundSleep() + return task.cont + elif (errorCode == EUSuccess): + # Decompression is done + self.launcherMessage(self.Localizer.LauncherDecompressingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": 100,}) + self.notify.info('decompressTask: Decompress done: ' + + task.localFilename.cStr()) + # Get rid of the decompressor + del task.decompressor + task.callback() + del task.callback + return task.done + elif (errorCode == EUErrorAbort): + self.handleDecompressFatalError(task, errorCode) + return task.done + elif ((errorCode == EUErrorWriteOutOfFiles) or + (errorCode == EUErrorWriteDiskFull) or + (errorCode == EUErrorWriteDiskSectorNotFound) or + (errorCode == EUErrorWriteOutOfMemory) or + (errorCode == EUErrorWriteSharingViolation) or + (errorCode == EUErrorWriteDiskFault) or + (errorCode == EUErrorWriteDiskNotFound) + ): + self.handleDecompressWriteError(task, errorCode) + return task.done + elif (errorCode == EUErrorZlib): + self.handleDecompressZlibError(task, errorCode) + return task.done + elif (errorCode > 0): + # If we get an error code that is positive, but we are not handling it, throw + # a warning and continue + self.notify.warning('decompressMultifileTask: Unknown success return code: ' + + errorToText(errorCode)) + return task.cont + else: + self.notify.warning('decompressMultifileTask: Unknown return code: ' + + errorToText(errorCode)) + self.handleDecompressFatalError(task, errorCode) + return task.done + + #============================================================ + # DecompressMultifile functions + #============================================================ + + def decompressMultifile(self, mfname, localFilename, callback): + self.notify.info('decompressMultifile: request: ' + localFilename.cStr()) + self.launcherMessage(self.Localizer.LauncherDecompressingFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.decompressMultifileTask) + task.mfname = mfname + task.localFilename = localFilename + task.callback = callback + task.lastUpdate = 0 + task.decompressor = Decompressor() + errorCode = task.decompressor.initiate(task.localFilename) + if (errorCode > 0): + self.miniTaskMgr.add(task, 'launcher-decompressMultifile') + else: + self.handleInitiateFatalError(errorCode) + + def decompressMultifileTask(self, task): + #if self.notify.getDebug(): + # beforeTime = self.getTime() + errorCode = task.decompressor.run() + #if self.notify.getDebug(): + # self.notify.debug("decompressMultifileTask run(): " + str(self.getTime() - beforeTime)) + if (errorCode == EUOk): + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + progress = task.decompressor.getProgress() + # We need this message if we are on the updating screen + self.launcherMessage(self.Localizer.LauncherDecompressingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": int(round(progress * 100)),}) + percentProgress = int(round(progress * self.decompressPercentage)) + # By now you have completed download, so add that in + totalPercent = (self.downloadPercentage + percentProgress) + self.setPercentPhaseComplete(self.currentPhase, totalPercent) + + # Everything is proceeding normally, come back next frame + self.foregroundSleep() + return task.cont + elif (errorCode == EUSuccess): + # Decompression is done + self.launcherMessage(self.Localizer.LauncherDecompressingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": 100, }) + + totalPercent = (self.downloadPercentage + self.decompressPercentage) + self.setPercentPhaseComplete(self.currentPhase, totalPercent) + self.notify.info('decompressMultifileTask: Decompress multifile done: ' + + task.localFilename.cStr()) + self.dldb.setClientMultifileDecompressed(task.mfname) + # Get rid of the decompressor + del task.decompressor + task.callback() + del task.callback + return task.done + elif (errorCode == EUErrorAbort): + self.handleDecompressFatalError(task, errorCode) + return task.done + elif ((errorCode == EUErrorWriteOutOfFiles) or + (errorCode == EUErrorWriteDiskFull) or + (errorCode == EUErrorWriteDiskSectorNotFound) or + (errorCode == EUErrorWriteOutOfMemory) or + (errorCode == EUErrorWriteSharingViolation) or + (errorCode == EUErrorWriteDiskFault) or + (errorCode == EUErrorWriteDiskNotFound) + ): + self.handleDecompressWriteError(task, errorCode) + return task.done + elif (errorCode == EUErrorZlib): + self.handleDecompressZlibError(task, errorCode) + return task.done + elif (errorCode > 0): + # If we get an error code that is positive, but we are not handling it, throw + # a warning and continue + self.notify.warning('decompressMultifileTask: Unknown success return code: ' + + errorToText(errorCode)) + return task.cont + else: + self.notify.warning('decompressMultifileTask: Unknown return code: ' + + errorToText(errorCode)) + self.handleDecompressFatalError(task, errorCode) + return task.done + + #============================================================ + # Extract functions + #============================================================ + + def extract(self, mfname, localFilename, destDir, callback): + self.notify.info('extract: request: ' + localFilename.cStr() + + ' destDir: ' + destDir.cStr()) + self.launcherMessage(self.Localizer.LauncherExtractingFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.extractTask) + task.mfname = mfname + task.localFilename = localFilename + task.destDir = destDir + task.callback = callback + task.lastUpdate = 0 + task.extractor = Extractor() + task.extractor.setExtractDir(task.destDir) + if not task.extractor.setMultifile(task.localFilename): + self.setPandaErrorCode(6) + self.notify.info("extract: Unable to open multifile %s" % task.localFilename.cStr()) + sys.exit() + + # Temporary debug output + #self.dldb.write(Notify.out()) + + numFiles = self.dldb.getServerNumFiles(mfname) + for i in range(numFiles): + subfile = self.dldb.getServerFileName(mfname, i) + if not task.extractor.requestSubfile(Filename(subfile)): + self.setPandaErrorCode(6) + self.notify.info("extract: Unable to find subfile %s in multifile %s" % (subfile, mfname)) + sys.exit() + + self.notify.info("Extracting %d subfiles from multifile %s." % (numFiles, mfname)) + self.miniTaskMgr.add(task, 'launcher-extract') + + def extractTask(self, task): + #if self.notify.getDebug(): + # beforeTime = self.getTime() + errorCode = task.extractor.step() + #if self.notify.getDebug(): + # self.notify.debug("extractTask run(): " + str(self.getTime() - beforeTime)) + if (errorCode == EUOk): + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + progress = task.extractor.getProgress() + self.launcherMessage(self.Localizer.LauncherExtractingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": int(round(progress * 100.0)),}) + percentProgress = int(round(progress * self.extractPercentage)) + + # By now you have completed download and decompress, so add those in + totalPercent = (self.downloadPercentage + self.decompressPercentage + percentProgress) + self.setPercentPhaseComplete(self.currentPhase, totalPercent) + # Everything is proceeding normally, come back next frame + self.foregroundSleep() + return task.cont + elif (errorCode == EUSuccess): + # Extraction is done + self.launcherMessage(self.Localizer.LauncherExtractingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": 100,}) + totalPercent = (self.downloadPercentage + self.decompressPercentage + self.extractPercentage) + self.setPercentPhaseComplete(self.currentPhase, totalPercent) + self.notify.info('extractTask: Extract multifile done: ' + + task.localFilename.cStr()) + self.dldb.setClientMultifileExtracted(task.mfname) + # Get rid of the extractor + del task.extractor + task.callback() + del task.callback + return task.done + elif (errorCode == EUErrorAbort): + self.handleExtractFatalError(task, errorCode) + return task.done + elif (errorCode == EUErrorFileEmpty): + self.handleExtractFatalError(task, errorCode) + return task.done + elif ((errorCode == EUErrorWriteOutOfFiles) or + (errorCode == EUErrorWriteDiskFull) or + (errorCode == EUErrorWriteDiskSectorNotFound) or + (errorCode == EUErrorWriteOutOfMemory) or + (errorCode == EUErrorWriteSharingViolation) or + (errorCode == EUErrorWriteDiskFault) or + (errorCode == EUErrorWriteDiskNotFound) + ): + self.handleExtractWriteError(task, errorCode) + return task.done + elif (errorCode > 0): + # If we get an error code that is positive, but we are not handling it, throw + # a warning and continue + self.notify.warning('extractTask: Unknown success return code: ' + + errorToText(errorCode)) + return task.cont + else: + # All other errors are negative, just catch them here for now + self.notify.warning('extractTask: Unknown error return code: ' + + errorToText(errorCode)) + self.handleExtractFatalError(task, errorCode) + return task.done + + #============================================================ + # Patch functions + #============================================================ + + def patch(self, patchFile, patcheeFile, callback): + self.notify.info('patch: request: ' + patchFile.cStr() + + ' patchee: ' + patcheeFile.cStr()) + self.launcherMessage(self.Localizer.LauncherPatchingFile % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + task = MiniTask(self.patchTask) + task.patchFile = patchFile + task.patcheeFile = patcheeFile + task.callback = callback + task.lastUpdate = 0 + task.patcher = Patcher() + errorCode = task.patcher.initiate(task.patchFile, task.patcheeFile) + if (errorCode > 0): + self.miniTaskMgr.add(task, 'launcher-patch') + else: + self.handleInitiateFatalError(errorCode) + + def patchTask(self, task): + #if self.notify.getDebug(): + # beforeTime = self.getTime() + errorCode = task.patcher.run() + #if self.notify.getDebug(): + # self.notify.debug("patchTask run(): " + str(self.getTime() - beforeTime)) + if (errorCode == EUOk): + now = self.getTime() + if now - task.lastUpdate >= self.UserUpdateDelay: + # Time to make an update. + task.lastUpdate = now + progress = task.patcher.getProgress() + self.launcherMessage(self.Localizer.LauncherPatchingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": int(round(progress * 100.0)),}) + # Everything is proceeding normally, come back next frame + self.foregroundSleep() + return task.cont + elif (errorCode == EUSuccess): + # Patch is done + self.launcherMessage(self.Localizer.LauncherPatchingPercent % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases, + "percent": 100,}) + self.notify.info('patchTask: Patch done: ' + task.patcheeFile.cStr()) + # Get rid of the patcher + del task.patcher + task.callback() + del task.callback + return task.done + elif (errorCode == EUErrorAbort): + self.handlePatchFatalError(task, errorCode) + return task.done + elif (errorCode == EUErrorFileEmpty): + self.handlePatchFatalError(task, errorCode) + return task.done + elif ((errorCode == EUErrorWriteOutOfFiles) or + (errorCode == EUErrorWriteDiskFull) or + (errorCode == EUErrorWriteDiskSectorNotFound) or + (errorCode == EUErrorWriteOutOfMemory) or + (errorCode == EUErrorWriteSharingViolation) or + (errorCode == EUErrorWriteDiskFault) or + (errorCode == EUErrorWriteDiskNotFound) + ): + self.handlePatchWriteError(task, errorCode) + return task.done + elif (errorCode > 0): + # If we get an error code that is positive, but we are not handling it, throw + # a warning and continue + self.notify.warning('patchTask: Unknown success return code: ' + + errorToText(errorCode)) + return task.cont + else: + # All other errors are negative, just catch them here for now + self.notify.warning('patchTask: Unknown error return code: ' + + errorToText(errorCode)) + self.handlePatchFatalError(task, errorCode) + return task.done + + #============================================================ + # Overall progress meter functions + #============================================================ + + def getProgressSum(self, phase): + + # given the phase lookup its sum + sum = 0 + for i in xrange(0,len(self.linesInProgress)): + # search for phase and sum the sizes for each + if self.linesInProgress[i].find(phase) > -1: + #split it, find the size, add to the sum + #self.notify.info(self.linesInProgress[i]); + nameSizeTuple = self.linesInProgress[i].split() + # get rid of the L from the number + numSize = nameSizeTuple[1].split('L') + sum += string.atoi(numSize[0]) + return sum + + def readProgressFile(self): + # parse those filenames and their sizes to get a sum + localFilename = Filename(self.dbDir, Filename(self.progressFilename)) + if not localFilename.exists(): + self.notify.warning("File does not exist: %s" % (localFilename.cStr())) + self.linesInProgress = [] + else: + f = open(localFilename.toOsSpecific()) + self.linesInProgress = f.readlines() + f.close() + + # remove the file it is no longer needed + localFilename.unlink() + + self.progressSum = 0 + token = 'phase_' + + self.progressSum = self.getProgressSum(token) + # deduct phase_2 files, I don't think this is downloaded + self.progressSum -= self.getProgressSum(token + '2') + self.notify.info('total phases to be downloaded = ' + `self.progressSum`) + # done reading the file, now carry on with client download + self.checkClientDbExists() + + + #============================================================ + # Main flow control of Launcher + #============================================================ + + def prepareClient(self): + self.notify.info('prepareClient: Preparing client for install') + # Make the local directories if they do not already exist + if not self.topDir.exists(): + self.notify.info('prepareClient: Creating top directory: ' + self.topDir.cStr()) + os.makedirs(self.topDir.toOsSpecific()) + if not self.dbDir.exists(): + self.notify.info('prepareClient: Creating db directory: ' + self.dbDir.cStr()) + os.makedirs(self.dbDir.toOsSpecific()) + if not self.patchDir.exists(): + self.notify.info('prepareClient: Creating patch directory: ' + self.patchDir.cStr()) + os.makedirs(self.patchDir.toOsSpecific()) + if not self.mfDir.exists(): + self.notify.info('prepareClient: Creating mf directory: ' + self.mfDir.cStr()) + os.makedirs(self.mfDir.toOsSpecific()) + + def downloadLauncherFileDb(self): + # We do this whole download of launcherFileDb and verification + # of the launcher files again, even though the ActiveX + # installer has presumably done this already, as an additional + # precaution. It's possible that a curious user has invoked + # the exe directly, without going through the web page + # (and hence without running ActiveX). Also, we need to have + # the launcherFileDb contents to pass its hash to the server + # on login. + + self.notify.info('Downloading launcherFileDb') + self.downloadRam(self.launcherFileDbFilename, self.downloadLauncherFileDbDone) + + def downloadLauncherFileDbDone(self): + self.launcherFileDbHash = HashVal() + self.launcherFileDbHash.hashRamfile(self.ramfile) + + if self.VerifyFiles: + self.notify.info('Validating Launcher files') + for fileDesc in self.ramfile.readlines(): + try: + filename, hashStr = fileDesc.split(' ', 1) + except: + self.notify.info('Invalid line: "%s"' % (fileDesc)) + self.failLauncherFileDb('No hash in launcherFileDb') + + serverHash = HashVal() + if not self.hashIsValid(serverHash, hashStr): + self.notify.info('Not a valid hash string: "%s"' % (hashStr)) + self.failLauncherFileDb('Invalid hash in launcherFileDb') + + localHash = HashVal() + localFilename = Filename(self.topDir, Filename(filename)) + localHash.hashFile(localFilename) + if localHash != serverHash: + assert self.notify.debug('expected %s' % (serverHash.asDec())) + assert self.notify.debug(' got %s' % (localHash.asDec())) + self.failLauncherFileDb('%s does not match expected version.' % (filename)) + + self.downloadServerDbFile() + + def failLauncherFileDb(self, string): + self.notify.info(string) + self.setPandaErrorCode(15) + sys.exit() + + def downloadServerDbFile(self): + self.notify.info('Downloading server db file') + self.launcherMessage(self.Localizer.LauncherDownloadServerFileList) + self.downloadRam(self.serverDbFilePath, self.downloadServerDbFileDone) + + def downloadServerDbFileDone(self): + self.serverDbFileHash = HashVal() + self.serverDbFileHash.hashRamfile(self.ramfile) + + # Now check to see if we have the client db + self.readProgressFile() + #self.checkClientDbExists() + + def checkClientDbExists(self): + # See if the client database exists. If it does, create the download db. + clientFilename = Filename(self.dbDir, Filename(self.clientDbFilename)) + if clientFilename.exists(): + self.notify.info('Client Db exists') + self.createDownloadDb() + # If it does not, then we need to download the starter client db + else: + self.notify.info('Client Db does not exist') + self.downloadClientDbStarterFile() + + def downloadClientDbStarterFile(self): + self.notify.info('Downloading Client Db starter file') + localFilename = Filename(self.dbDir, Filename(self.compClientDbFilename)) + self.download(self.clientStarterDbFilePath, + localFilename, + self.downloadClientDbStarterFileDone, None) + + def downloadClientDbStarterFileDone(self): + # Decompress the file. This file is too small to worry about doing it async. + localFilename = Filename(self.dbDir, Filename(self.compClientDbFilename)) + decompressor = Decompressor() + decompressor.decompress(localFilename) + # Ok, now create the download db + self.createDownloadDb() + + def createDownloadDb(self): + self.notify.info('Creating downloadDb') + self.launcherMessage(self.Localizer.LauncherCreatingDownloadDb) + clientFilename = Filename(self.dbDir, Filename(self.clientDbFilename)) + self.notify.info('Client file name: ' + clientFilename.cStr()) + #serverFilename = Filename(self.dbDir, Filename(self.serverDbFilename)) + self.launcherMessage(self.Localizer.LauncherDownloadClientFileList) + serverFile = self.ramfile + decompressor = Decompressor() + decompressor.decompress(serverFile) + self.notify.info('Finished decompress') + self.dldb = DownloadDb(serverFile, clientFilename) + self.notify.info('created download db') + self.launcherMessage(self.Localizer.LauncherFinishedDownloadDb) + # Ok, start going through the phases + self.currentPhase = self.LauncherPhases[0] + self.currentPhaseIndex = 1 + self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhase] + self.updatePhase(self.currentPhase) + + def maybeStartGame(self): + """ + This function gets called whenever the Launcher determines it has + finished updating everything that was previously downloaded, and + either has hit a phase it has not completed, or is completely done. + Here, we decide if we are in a high enough phase to start the game + and if we are, throw the event that kicks everything off. + Note: you will get this event multiple times, so you probably + want to acceptOnce + """ + if (not self.started and self.currentPhase >= self.showPhase): + self.started = True + self.notify.info("maybeStartGame: starting game") + self.launcherMessage(self.Localizer.LauncherStartingGame) + # Go into the background now since Panda will be fired up shortly + self.background() + # Put ourselves in the global dict + __builtin__.launcher = self + # Start the show + self.startGame() + + def _runTaskManager(self): + """ Runs the task manager main loop. This means running the + mini task manager initially, until we have started the main + task manager, and then it runs the main task manager + instead. """ + + if(not self.taskMgrStarted): + self.miniTaskMgr.run() + self.notify.info("Switching task managers.") + taskMgr.run() + + def _stepMiniTaskManager(self, task): + """ This task is queued up on the main task manager to run the + mini task manager, until it has no more tasks. This is used + to transition from the mini task manager to the main task + manager. """ + + self.miniTaskMgr.step() + if self.miniTaskMgr.taskList: + return task.cont + self.notify.info("Stopping mini task manager.") + self.miniTaskMgr = None + return task.done + + def newTaskManager(self): + """ A derived class should call this, for instance in + startGame, as soon as the main task manager can be safely + created. """ + self.taskMgrStarted = True + if self.miniTaskMgr.running: + self.miniTaskMgr.stop() + + from direct.task.TaskManagerGlobal import taskMgr + taskMgr.remove('miniTaskManager') + taskMgr.add(self._stepMiniTaskManager, 'miniTaskManager') + + def mainLoop(self): + # Put the whole show in a try..except block, so we can catch Python + # exceptions and set the appropriate error code. + try: + self._runTaskManager() + + except SystemExit: + # Presumably the window has already been shut down here, but shut + # it down again for good measure. + if hasattr(__builtin__, "base"): + base.destroy() + + self.notify.info("Normal exit.") + raise + + except: + # Some unexpected Python exception; this is error code 12 for the + # installer. + self.setPandaErrorCode(12) + self.notify.warning("Handling Python exception.") + + if hasattr(__builtin__, "base") and \ + getattr(base, "cr", None): + # Tell the AI (if we have one) why we're going down. + if base.cr.timeManager: + from otp.otpbase import OTPGlobals + base.cr.timeManager.setDisconnectReason(OTPGlobals.DisconnectPythonError) + base.cr.timeManager.setExceptionInfo() + + # Tell the server we're gone too. + base.cr.sendDisconnect() + + if hasattr(__builtin__, "base"): + # Clean up showbase correctly. + base.destroy() + + self.notify.info("Exception exit.\n") + import traceback + traceback.print_exc() + sys.exit() + + def updatePhase(self, phase): + self.notify.info('Updating multifiles in phase: ' + `phase`) + # Phase may need downloading, clear the percentage to 0 + self.setPercentPhaseComplete(self.currentPhase, 0) + assert(self.dldb) + # Make a list of the multifile indexes in this phase + self.phaseMultifileNames = [] + numfiles = self.dldb.getServerNumMultifiles() + for i in range(self.dldb.getServerNumMultifiles()): + # What is this multifile's name? + mfname = self.dldb.getServerMultifileName(i) + # If this multifile is for this phase, then update it + if (self.dldb.getServerMultifilePhase(mfname) == phase): + self.phaseMultifileNames.append(mfname) + self.updateNextMultifile() + + def updateNextMultifile(self): + if (len(self.phaseMultifileNames) > 0): + # Update the first multifile on the list + self.currentMfname = self.phaseMultifileNames.pop() + # Reset the retry counter + self.curMultifileRetry = 0 + self.getMultifile(self.currentMfname) + else: + if self.currentMfname is None: + # Print out some debug info before exiting + self.notify.warning("no multifile found! See below for debug info:") + for i in range(self.dldb.getServerNumMultifiles()): + mfname = self.dldb.getServerMultifileName(i) + phase = self.dldb.getServerMultifilePhase(mfname) + print i, mfname, phase + # This will exit + self.handleGenericMultifileError() + + decompressedMfname = os.path.splitext(self.currentMfname)[0] + localFilename = Filename(self.mfDir, Filename(decompressedMfname)) + + # find the next phase in the phase list + nextIndex = self.LauncherPhases.index(self.currentPhase) + 1 + + # if admin user crashes during dwnload, want to ensure the files he wrote are readable + # by non-admins, so do this after every phase + if nextIndex < len(self.LauncherPhases): + self.MakeNTFSFilesGlobalWriteable(localFilename) + else: + # at the end do the whole directory, just to make sure + self.MakeNTFSFilesGlobalWriteable() + + # Now that the multifile is available, mount it so we can + # load files. + vfs = VirtualFileSystem.getGlobalPtr() + vfs.mount(localFilename, '.', VirtualFileSystem.MFReadOnly) + + # Phase all done, now set the percent to really be 100 + self.setPercentPhaseComplete(self.currentPhase, 100) + # Now we are done updating all the multifiles for this phase + self.notify.info('Done updating multifiles in phase: ' + `self.currentPhase`) + # Also update the overall progress bar + self.progressSoFar += int(round(self.phaseOverallMap[self.currentPhase]*100)) + self.notify.info('progress so far ' + `self.progressSoFar`) + #self.forceSleep() + + # Send the phase complete event in case anybody cares + messenger.send('phaseComplete-' + `self.currentPhase`) + if nextIndex < len(self.LauncherPhases): + # go to the next phase + self.currentPhase = self.LauncherPhases[nextIndex] + self.currentPhaseIndex = nextIndex + 1 + self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhase] + self.updatePhase(self.currentPhase) + else: + self.notify.info('ALL PHASES COMPLETE') + self.maybeStartGame() + messenger.send("launcherAllPhasesComplete") + self.cleanup() + + def isDownloadComplete(self): + return self._downloadComplete + + def updateMultifileDone(self): + # Assuming we are always updating the first multifile on the list, + # just pop that file off and then work on the next one + self.updateNextMultifile() + + def downloadMultifileDone(self): + self.getDecompressMultifile(self.currentMfname) + + def getMultifile(self, mfname): + self.notify.info('Downloading multifile: ' + mfname) + # If the multifile does not exist in the client db, add the new record + if (not self.dldb.clientMultifileExists(mfname)): + self.maybeStartGame() + self.notify.info('Multifile does not exist in client db,' + + 'creating new record: ' + mfname) + # Create a new (incomplete) record on the client side + self.dldb.addClientMultifile(mfname) + + curHash = self.dldb.getServerMultifileHash(mfname) + self.dldb.setClientMultifileHash(mfname, curHash) + + localFilename = Filename(self.mfDir, Filename(mfname)) + if localFilename.exists(): + curSize = localFilename.getFileSize() + self.dldb.setClientMultifileSize(mfname, curSize) + if curSize == self.dldb.getServerMultifileSize(mfname): + self.dldb.setClientMultifileComplete(mfname) + + # Strip off the pz extension leaving file.mf + decompressedMfname = os.path.splitext(mfname)[0] + decompressedFilename = Filename(self.mfDir, Filename(decompressedMfname)) + + if (not self.dldb.clientMultifileComplete(mfname) or \ + not self.dldb.clientMultifileDecompressed(mfname)) and \ + decompressedFilename.exists(): + + # Hey, the client.ddb thinks this multifile's not + # completely downloaded (or not completely decompressed), + # yet the decompressed filename is here on disk. Check + # its contents. + + clientMd5 = HashVal() + clientMd5.hashFile(decompressedFilename) + clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5) + if clientVer != -1: + # Not only is it here on disk, but it's the correct + # version (or at least some recognized version). + # Someone else must have downloaded it for us, swell. + # We can move on to patching it. + + self.notify.info('Decompressed multifile is already on disk and correct: %s (version %s)' % (mfname, clientVer)) + self.dldb.setClientMultifileComplete(mfname) + self.dldb.setClientMultifileDecompressed(mfname) + + compressedFilename = Filename(self.mfDir, Filename(mfname)) + compressedFilename.unlink() + + # Maybe someone's extracted the files, too. + extractedOk = True + numFiles = self.dldb.getServerNumFiles(mfname) + for i in range(numFiles): + subfile = self.dldb.getServerFileName(mfname, i) + fn = Filename(self.mfDir, Filename(subfile)) + if fn.compareTimestamps(decompressedFilename) <= 0: + # Oh, no good. + extractedOk = False + break + + if extractedOk: + self.notify.info('Multifile appears to have been extracted already.') + self.dldb.setClientMultifileExtracted(mfname) + + + # If the multifile is not complete, finish downloading it + if (not self.dldb.clientMultifileComplete(mfname) or + not decompressedFilename.exists()): + self.maybeStartGame() + currentSize = self.dldb.getClientMultifileSize(mfname) + totalSize = self.dldb.getServerMultifileSize(mfname) + localFilename = Filename(self.mfDir, Filename(mfname)) + if not localFilename.exists(): + currentSize = 0 + else: + currentSize = min(currentSize, localFilename.getFileSize()) + + if (currentSize == 0): + self.notify.info('Multifile has not been started, ' + + 'downloading new file: ' + mfname) + # Record what version (hashval) we are working on for + # future reference. This way if we are interrupted, we know + # which file we were on when we restart + curHash = self.dldb.getServerMultifileHash(mfname) + self.dldb.setClientMultifileHash(mfname, curHash) + + #set the new multifile download flag + self.phaseNewDownload[self.currentPhase] = 1 + # Download the file + self.downloadMultifile(self.contentDir + mfname, localFilename, + mfname, + self.downloadMultifileDone, + totalSize, 0, self.downloadMultifileWriteToDisk) + else: + # See what version we are working on + clientHash = self.dldb.getClientMultifileHash(mfname) + # See what the latest version is from the server + serverHash = self.dldb.getServerMultifileHash(mfname) + + # If we are still working on the latest version, pick up + # right where we left off + if (clientHash.eq(serverHash)): + self.notify.info('Multifile is not complete, finishing download for %s, size = %s / %s' % (mfname, currentSize, totalSize)) + # Resume downloading the file + self.downloadMultifile(self.contentDir + mfname, localFilename, + mfname, + self.downloadMultifileDone, + totalSize, currentSize, self.downloadMultifileWriteToDisk) + else: + if self.curMultifileRetry < self.multifileRetries: + self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries)) + self.curMultifileRetry += 1 + # Start over with new version + self.notify.info('Multifile is not complete, and is out of date. ' + + 'Restarting download with newest multifile') + # Mark this multifile as incomplete, with 0 size + self.dldb.setClientMultifileIncomplete(self.currentMfname) + self.dldb.setClientMultifileSize(self.currentMfname, 0) + self.dldb.setClientMultifileHash(self.currentMfname, serverHash) + + # Ok, now try getting the file + self.getMultifile(self.currentMfname) + else: + self.setPandaErrorCode(6) + self.notify.info('getMultifile: Failed to download multifile') + sys.exit() + else: + self.notify.info('Multifile already complete: ' + mfname) + # lets show a percentage here of how much we have downloaded + #self.tempPhaseProgress = self.getProgressSum(mfname) + #self.progressSoFar += self.tempPhaseProgress + self.downloadMultifileDone() + + def updateMultifileDone(self): + # Assuming we are always updating the first multifile on the list, + # just pop that file off and then work on the next one + self.updateNextMultifile() + + def downloadMultifileDone(self): + self.getDecompressMultifile(self.currentMfname) + + def getMultifile(self, mfname): + self.notify.info('Downloading multifile: ' + mfname) + # If the multifile does not exist in the client db, add the new record + if (not self.dldb.clientMultifileExists(mfname)): + self.maybeStartGame() + self.notify.info('Multifile does not exist in client db,' + + 'creating new record: ' + mfname) + # Create a new (incomplete) record on the client side + self.dldb.addClientMultifile(mfname) + + if self.DecompressMultifiles: + curHash = self.dldb.getServerMultifileHash(mfname) + self.dldb.setClientMultifileHash(mfname, curHash) + + localFilename = Filename(self.mfDir, Filename(mfname)) + if localFilename.exists(): + curSize = localFilename.getFileSize() + self.dldb.setClientMultifileSize(mfname, curSize) + if curSize == self.dldb.getServerMultifileSize(mfname): + self.dldb.setClientMultifileComplete(mfname) + + # Strip off the pz extension leaving file.mf + decompressedMfname = os.path.splitext(mfname)[0] + decompressedFilename = Filename(self.mfDir, Filename(decompressedMfname)) + + if self.DecompressMultifiles: + if (not self.dldb.clientMultifileComplete(mfname) or \ + not self.dldb.clientMultifileDecompressed(mfname)) and \ + decompressedFilename.exists(): + + # Hey, the client.ddb thinks this multifile's not + # completely downloaded (or not completely decompressed), + # yet the decompressed filename is here on disk. Check + # its contents. + + clientMd5 = HashVal() + clientMd5.hashFile(decompressedFilename) + clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5) + if clientVer != -1: + # Not only is it here on disk, but it's the correct + # version (or at least some recognized version). + # Someone else must have downloaded it for us, swell. + # We can move on to patching it. + + self.notify.info('Decompressed multifile is already on disk and correct: %s (version %s)' % (mfname, clientVer)) + self.dldb.setClientMultifileComplete(mfname) + self.dldb.setClientMultifileDecompressed(mfname) + + compressedFilename = Filename(self.mfDir, Filename(mfname)) + compressedFilename.unlink() + + # Maybe someone's extracted the files, too. + extractedOk = True + numFiles = self.dldb.getServerNumFiles(mfname) + for i in range(numFiles): + subfile = self.dldb.getServerFileName(mfname, i) + fn = Filename(self.mfDir, Filename(subfile)) + if fn.compareTimestamps(decompressedFilename) <= 0: + # Oh, no good. + extractedOk = False + break + + if extractedOk: + self.notify.info('Multifile appears to have been extracted already.') + self.dldb.setClientMultifileExtracted(mfname) + + + # If the multifile is not complete, finish downloading it + if (not self.dldb.clientMultifileComplete(mfname) or + not decompressedFilename.exists()): + self.maybeStartGame() + currentSize = self.dldb.getClientMultifileSize(mfname) + totalSize = self.dldb.getServerMultifileSize(mfname) + localFilename = Filename(self.mfDir, Filename(mfname)) + if not localFilename.exists(): + currentSize = 0 + + if (currentSize == 0): + self.notify.info('Multifile has not been started, ' + + 'downloading new file: ' + mfname) + # Record what version (hashval) we are working on for + # future reference. This way if we are interrupted, we know + # which file we were on when we restart + curHash = self.dldb.getServerMultifileHash(mfname) + self.dldb.setClientMultifileHash(mfname, curHash) + + #set the new multifile download flag + self.phaseNewDownload[self.currentPhase] = 1 + # Download the file + self.downloadMultifile(self.contentDir + mfname, localFilename, + mfname, + self.downloadMultifileDone, + totalSize, 0, self.downloadMultifileWriteToDisk) + else: + # See what version we are working on + clientHash = self.dldb.getClientMultifileHash(mfname) + # See what the latest version is from the server + serverHash = self.dldb.getServerMultifileHash(mfname) + + # If we are still working on the latest version, pick up + # right where we left off + if (clientHash.eq(serverHash)): + self.notify.info('Multifile is not complete, finishing download for %s, size = %s / %s' % (mfname, currentSize, totalSize)) + # Resume downloading the file + self.downloadMultifile(self.contentDir + mfname, localFilename, + mfname, + self.downloadMultifileDone, + totalSize, currentSize, self.downloadMultifileWriteToDisk) + else: + if self.curMultifileRetry < self.multifileRetries: + self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries)) + self.curMultifileRetry += 1 + # Start over with new version + self.notify.info('Multifile is not complete, and is out of date. ' + + 'Restarting download with newest multifile') + # Mark this multifile as incomplete, with 0 size + self.dldb.setClientMultifileIncomplete(self.currentMfname) + self.dldb.setClientMultifileSize(self.currentMfname, 0) + if self.DecompressMultifiles: + self.dldb.setClientMultifileHash(self.currentMfname, serverHash) + + # Ok, now try getting the file + self.getMultifile(self.currentMfname) + else: + self.setPandaErrorCode(6) + self.notify.info('getMultifile: Failed to download multifile') + sys.exit() + else: + self.notify.info('Multifile already complete: ' + mfname) + # lets show a percentage here of how much we have downloaded + #self.tempPhaseProgress = self.getProgressSum(mfname) + #self.progressSoFar += self.tempPhaseProgress + self.downloadMultifileDone() + + def getDecompressMultifile(self, mfname): + if not self.DecompressMultifiles: + self.decompressMultifileDone() + else: + # If the multifile is not decompressed, decompress it + if (not self.dldb.clientMultifileDecompressed(mfname)): + self.maybeStartGame() + # Decompress the file, then record that it is complete + self.notify.info('decompressMultifile: Decompressing multifile: ' + mfname) + localFilename = Filename(self.mfDir, Filename(mfname)) + self.decompressMultifile(mfname, localFilename, + self.decompressMultifileDone) + else: + self.notify.info('decompressMultifile: Multifile already decompressed: ' + + mfname) + self.decompressMultifileDone() + + def decompressMultifileDone(self): + # Ok, set the progress higher now + if (self.phaseNewDownload[self.currentPhase]): + self.setPercentPhaseComplete(self.currentPhase, 95) + self.extractMultifile(self.currentMfname) + + def extractMultifile(self, mfname): + # If the multifile is not extracted, extract it + if (not self.dldb.clientMultifileExtracted(mfname)): + self.maybeStartGame() + # Extract the file, then record that it is complete + self.notify.info('extractMultifile: Extracting multifile: ' + mfname) + # Strip off the pz extension leaving file.mf + decompressedMfname = os.path.splitext(mfname)[0] + localFilename = Filename(self.mfDir, Filename(decompressedMfname)) + destDir = Filename(self.topDir) + # The extractor extracts to the directory we pass in, so + # set the current directory to where we want these files + # to go, right now they are all relative to the top + # directory + self.notify.info('extractMultifile: Extracting: ' + + localFilename.cStr() + ' to: ' + destDir.cStr()) + self.extract(mfname, localFilename, destDir, self.extractMultifileDone) + else: + self.notify.info('extractMultifile: Multifile already extracted: ' + mfname) + self.extractMultifileDone() + + def extractMultifileDone(self): + # Ok, set the progress higher now only if it was whole multifile download + if (self.phaseNewDownload[self.currentPhase]): + self.setPercentPhaseComplete(self.currentPhase, 99) + # If we got all the way here, the file is done + self.notify.info('extractMultifileDone: Finished updating multifile: ' + + self.currentMfname) + self.patchMultifile() + + def getPatchFilename(self, fname, currentVersion): + # Return a filename that will represent the patch file name on + # the download server from (currentVersion) to (currentVersion+1) + # Example: + # fname = foo.rgb.v1 + # patch = foo.rgb.v1.pch + return (fname + '.v' + `currentVersion` + '.' + self.patchExtension) + + #============================================================ + # Patching functions + #============================================================ + + def downloadPatches(self): + if (len(self.patchList) > 0): + self.currentPatch, self.currentPatchee, self.currentPatchVersion = self.patchList.pop() + + # Patches are compressed + self.notify.info(self.contentDir) + self.notify.info(self.currentPatch) + + patchFile = self.currentPatch + '.pz' + serverPatchFilePath = self.contentDir + patchFile + + self.notify.info(serverPatchFilePath) + localPatchFilename = Filename(self.patchDir, Filename(patchFile)) + if (self.currentPhase > 3): + self.download(serverPatchFilePath, + localPatchFilename, + self.downloadPatchDone, + self.downloadPatchSimpleProgress) + else: + self.download(serverPatchFilePath, + localPatchFilename, + self.downloadPatchDone, + self.downloadPatchOverallProgress) + else: + # Now we are done applying all patches for this multifile + self.notify.info('applyNextPatch: Done patching multifile: ' + + `self.currentPhase`) + # Ok, we are done patching. Lets run through the patchAndHash step + # again to make sure we are all clean. If it decides we are, it will + # move on to the next multifile + self.patchDone() + + + def downloadPatchDone(self): + # record how much you have downloaded; this is harmless if no patch needed + self.patchDownloadSoFar += self.httpChannel.getBytesDownloaded() + # Decompress the patch file (perhaps this should be async? these are small files) + self.notify.info('downloadPatchDone: Decompressing patch file: ' + + self.currentPatch + '.pz') + self.decompressFile(Filename(self.patchDir, Filename(self.currentPatch + '.pz')), + self.decompressPatchDone) + + def decompressPatchDone(self): + self.notify.info('decompressPatchDone: Patching file: ' + self.currentPatchee + + ' from ver: ' + `self.currentPatchVersion`) + # Determine the filenames + patchFile = Filename(self.patchDir, Filename(self.currentPatch)) + patchFile.setBinary() + patchee = Filename(self.mfDir, Filename(self.currentPatchee)) + patchee.setBinary() + # Run the patcher async + self.patch(patchFile, patchee, self.downloadPatches) + + def patchDone(self): + # Ok, the patch worked + self.notify.info('patchDone: Patch successful') + # Cleanup + del self.currentPatch + del self.currentPatchee + del self.currentPatchVersion + # TODO: only extract the files that changed. + # For now just extract them all + # (at least we do not need to patch and hash if we extract) + decompressedMfname = os.path.splitext(self.currentMfname)[0] + localFilename = Filename(self.mfDir, Filename(decompressedMfname)) + destDir = Filename(self.topDir) + self.extract(self.currentMfname, localFilename, destDir, self.updateMultifileDone) + + def startReextractingFiles(self): + # See if there are any files to patch + self.notify.info('startReextractingFiles: Reextracting ' + `len(self.reextractList)` + + ' files for multifile: ' + self.currentMfname) + self.launcherMessage(self.Localizer.LauncherRecoverFiles) + # read in the multifile + self.currentMfile = Multifile() + decompressedMfname = os.path.splitext(self.currentMfname)[0] + self.currentMfile.openRead(Filename(self.mfDir, Filename(decompressedMfname))) + self.reextractNextFile() + + def reextractNextFile(self): + failure = 0 + while (not failure and len(self.reextractList) > 0): + currentReextractFile = self.reextractList.pop() + subfileIndex = self.currentMfile.findSubfile(currentReextractFile) + if subfileIndex >= 0: + destFilename = Filename(self.topDir, Filename(currentReextractFile)) + result = self.currentMfile.extractSubfile(subfileIndex, destFilename) + if not result: + self.notify.warning('reextractNextFile: Failure on reextract.') + failure = 1 + else: + self.notify.warning('reextractNextFile: File not found in multifile: ' + + `currentReextractFile`) + failure = 1 + + if failure: + # TODO: handle this better + sys.exit() + + # Now we are done extracting files for this multifile + self.notify.info('reextractNextFile: Done reextracting files for multifile: ' + + `self.currentPhase`) + # Ok, now move on to the next multifile + del self.currentMfile + self.updateMultifileDone() + + def patchMultifile(self): + self.launcherMessage(self.Localizer.LauncherCheckUpdates % + {"name": self.currentPhaseName, + "current": self.currentPhaseIndex, + "total": self.numPhases,}) + self.notify.info("patchMultifile: Checking for patches on multifile: " + + self.currentMfname) + + self.patchList = [] + + # Compute the hash + clientMd5 = HashVal() + decompressedMfname = os.path.splitext(self.currentMfname)[0] + localFilename = Filename(self.mfDir, Filename(decompressedMfname)) + clientMd5.hashFile(localFilename) + clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5) + + # Check the hash results + if (clientVer == 1): + # On to the next file, leave the clean flag in the state it is in + self.patchAndHash() + return + + elif (clientVer == -1): + # Invalid hash value, file must be corrupted or simply out of date + self.notify.info('patchMultifile: Invalid hash for file: ' + self.currentMfname) + self.maybeStartGame() + if self.curMultifileRetry < self.multifileRetries: + self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries)) + self.curMultifileRetry += 1 + # Start over with new version + self.notify.info('patchMultifile: Restarting download with newest multifile') + # Mark this multifile as incomplete, with 0 size + self.dldb.setClientMultifileIncomplete(self.currentMfname) + self.dldb.setClientMultifileSize(self.currentMfname, 0) + # Ok, now try getting the newest file + self.getMultifile(self.currentMfname) + else: + self.setPandaErrorCode(6) + self.notify.info('patchMultifile: Failed to download multifile') + sys.exit() + return + + elif (clientVer > 1): + self.notify.info('patchMultifile: Old version for multifile: ' + + self.currentMfname + ' Client ver: ' + `clientVer`) + self.maybeStartGame() + self.totalPatchDownload = 0 + self.patchDownloadSoFar = 0 + for ver in range(1, clientVer): + patch = self.getPatchFilename(decompressedMfname, ver+1) + patchee = decompressedMfname + patchVersion = ver+1 + self.patchList.append((patch, patchee, patchVersion)) + # sum the file size to totalPatchDownload (phase_3 only) + if (self.currentPhase == 3): + self.totalPatchDownload += self.getProgressSum(patch) + self.notify.info('total patch to be downloaded = ' + `self.totalPatchDownload`) + self.downloadPatches() + return + + def patchAndHash(self): + # Reset the patch and reextract lists + self.reextractList = [] + # Keep track to see if all the files are clean. + # Assume they are clean to begin with, and prove me wrong + self.PAHClean = 1 + self.PAHNumFiles = self.dldb.getServerNumFiles(self.currentMfname) + self.PAHFileCounter = 0 + if (self.PAHNumFiles > 0): + task = MiniTask(self.patchAndHashTask) + task.cleanCallback = self.updateMultifileDone + task.uncleanCallback = self.startReextractingFiles + self.miniTaskMgr.add(task, "patchAndHash") + else: + self.updateMultifileDone() + + def patchAndHashTask(self, task): + self.launcherMessage(self.Localizer.LauncherVerifyPhase) + #self.launcherMessage(self.Localizer.LauncherVerifyPhase % + # (self.currentPhase, + # int(round(self.PAHFileCounter/float(self.PAHNumFiles)*100.0)))) + # See if we are at the end of the list + if (self.PAHFileCounter == self.PAHNumFiles): + # If we are return task done + if self.PAHClean: + # Everything proceeded as expected. This multifile is DONE! + task.cleanCallback() + else: + # Some files need to be updated + task.uncleanCallback() + # Now we are really done with this task + return task.done + else: + # Otherwise increase the counter and do the next file + i = self.PAHFileCounter + # Increment for next time + self.PAHFileCounter += 1 + + fname = self.dldb.getServerFileName(self.currentMfname, i) + fnameFilename = Filename(self.topDir, Filename(fname)) + + # See if the file exists, if not, add it to the reextractList and return + if (not os.path.exists(fnameFilename.toOsSpecific())): + self.notify.info('patchAndHash: File not found: ' + fname) + # reextract the file + self.reextractList.append(fname) + self.PAHClean = 0 + return task.cont + + if self.VerifyFiles and self.dldb.hasVersion(Filename(fname)): + # If we recorded versioning information on this file, look + # it up and see if it matches the expected version. + + # Compute the hash + clientMd5 = HashVal() + clientMd5.hashFile(fnameFilename) + clientVer = self.dldb.getVersion(Filename(fname), clientMd5) + + if (clientVer == 1): + # On to the next file, leave the clean flag in the state it is in + return task.cont + else: + # Invalid hash value, file must be corrupted or simply out of date + self.notify.info('patchAndHash: Invalid hash for file: ' + fname) + # reextract the file + self.reextractList.append(fname) + self.PAHClean = 0 + + return task.cont + + def launcherMessage(self, msg): + """ + Display a message on the flash movie + """ + if msg != self.lastLauncherMsg: + self.lastLauncherMsg = msg + self.notify.info(msg) + + #============================================================ + # Interface of launcher to the rest of the game + #============================================================ + + def isTestServer(self): + return self.testServerFlag + + def recordPeriodTimeRemaining(self, secondsRemaining): + self.setValue(self.PeriodTimeRemainingKey, int(secondsRemaining)) + + def recordPeriodName(self, periodName): + self.setValue(self.PeriodNameKey, periodName) + + def recordSwid(self, swid): + self.setValue(self.SwidKey, swid) + + def getGoUserName(self): + return self.goUserName + + def setGoUserName(self, userName): + self.goUserName = userName + + def getInstallDir(self): + return self.topDir.cStr() + + def setPandaWindowOpen(self): + """ + Call this when the window is finally opened so + flash knows it can exit + """ + self.setValue(self.PandaWindowOpenKey, 1) + + def setPandaErrorCode(self, code): + """ + Set the exit code of panda. 0 means everything ok + """ + self.notify.info("setting panda error code to %s" % (code)) + self.pandaErrorCode = code + # Note that opening mode 'w' truncates the existing file, which is what we want. + errorLog = open(self.errorfile, 'w') + errorLog.write(str(code)+'\n') + errorLog.flush() # Just to be sure + errorLog.close() + + def getPandaErrorCode(self): + return self.pandaErrorCode + + def setDisconnectDetailsNormal(self): + self.notify.info("Setting Disconnect Details normal") + self.disconnectCode = 0 + self.disconnectMsg = 'normal' + + def setDisconnectDetails(self, newCode, newMsg): + self.notify.info("New Disconnect Details: %s - %s " % (newCode,newMsg)) + self.disconnectCode = newCode + self.disconnectMsg = newMsg + + def setServerVersion(self, version): + """ + Set the server version. + """ + self.ServerVersion = version + + def getServerVersion(self): + return self.ServerVersion + + def getIsNewInstallation(self): + """ + is this a new installation? + """ + result = self.getValue(self.NewInstallationKey, 1) + result = base.config.GetBool("new-installation", result) + return result + + def setIsNotNewInstallation(self): + """ + Set that this is no longer a new installation + (has created account or logged in) + """ + self.setValue(self.NewInstallationKey, 0) + + def getLastLogin(self): + """ + Get the last login + """ + return self.getValue(self.LastLoginKey, '') + + def setLastLogin(self, login): + """ + Set a new 'last login' + """ + self.setValue(self.LastLoginKey, login) + + def setUserLoggedIn(self): + """ + Set that a user has logged in. + """ + self.setValue(self.UserLoggedInKey, '1') # since these are flags they should really be 1, not '1' + + def setPaidUserLoggedIn(self): + """ + Set that a paid user has logged in + """ + self.setValue(self.PaidUserLoggedInKey, '1') # since these are flags they should really be 1, not '1' + + def getReferrerCode(self): + """ + Get the referrer code + """ + return self.getValue(self.ReferrerKey, None) + + def getPhaseComplete(self, phase): + assert(self.phaseComplete.has_key(phase)) + percentDone = self.phaseComplete[phase] + return (percentDone == 100) + + def setPercentPhaseComplete(self, phase, percent): + self.notify.info("phase updating %s, %s"%(phase,percent)) + # Set the value locally in our map + oldPercent = self.phaseComplete[phase] + # Only udpate things if the percentage has changed + if (oldPercent != percent): + self.phaseComplete[phase] = percent + # Update the download watcher + messenger.send("launcherPercentPhaseComplete", + [phase, percent, self.getBandwidth(), self.byteRate]) + # Also set the value in the registry + percentPhaseCompleteKey = "PERCENT_PHASE_COMPLETE_" + `phase` + self.setRegistry(percentPhaseCompleteKey, percent) + + # now calculate an overall percenatage + # rescale this percent according to weight of this phase + self.overallComplete = int(round(percent * self.phaseOverallMap[phase])) + self.progressSoFar + #self.notify.info('overall complete ' + `self.overallComplete` + '%') + # also set the value in the registry + self.setRegistry("PERCENT_OVERALL_COMPLETE", self.overallComplete) + + def getPercentPhaseComplete(self, phase): + # Return the percent [0-100] complete of this phase + return self.phaseComplete[phase] + + dr = finalRequested - startRequested + + if (dt <= 0.0): + assert self.notify.debug('getBytesPerSecond: negative dt') + return -1 + + self.byteRate = db / dt + self.byteRateRequested = dr / dt + assert self.notify.debug("getBytesPerSecond: db = %s, dr = %s, dt = %s byte rate = %s" % + (db, dr, dt, self.byteRate)) + return self.byteRate + + def addPhasePostProcess(self, phase, func, taskChain = 'default'): + """ Adds a post-process callback function to the phase + download. When the indicated phase is successfully + downloaded, the given function will be called, on the + specified taskChain. The phase will not be marked fully + downloaded, and the phaseComplete event will not be sent, + until the callback function has completed. + + If the phase is already complete at the time this function is + called, the callback function is called immediately, in the + current task. """ + + # Actually, here in the lame-duck LauncherBase code, we don't + # bother to fully implement this method. Instead, we simply + # either make the callback immediately, or we hang on a hook + # on the phaseComplete event to call it then. This means that + # that the callback is always made in the made thread, rather + # than a sub-thread. It also means that the phaseComplete + # event is sent *before* the callback is made, rather than + # after; but because the callback is made in the main thread, + # we won't be off by more than a frame, and this is all that + # Pirates requires anyway. (And Toontown doesn't currently + # use this method at all.) Eventually, when this code is + # phased out altogether, it won't even matter any more. + + if self.getPhaseComplete(phase): + # Already downloaded. + func() + return + + self.acceptOnce('phaseComplete-%s' % (phase), func) + + def testBandwidth(self): + # Any time we test, go ahead and record the current bandwidth + self.recordBytesPerSecond() + # Judging by the current byte rate, determine if we need to increase + # or decrease the download byte rate request + byteRate = self.getBytesPerSecond() + if (byteRate < 0): + assert self.notify.debug('testBandwidth: not enough data yet') + return + assert self.notify.debug('testBandwidth: comparing byteRate %s with getBandwidth %s and byteRateRequested %s.' % + (byteRate, self.getBandwidth(), self.byteRateRequested)) + + if (byteRate >= self.getBandwidth() * self.INCREASE_THRESHOLD): + # If we are nearly meeting the ideal bandwidth we are + # requesting, then let's try increasing the requested + # amount. + self.increaseBandwidth(byteRate) + + elif (byteRate < (self.byteRateRequested * self.DECREASE_THRESHOLD)): + # If we are not meeting the actual requested amount, let's + # back off. The semantic difference between ideal + # requested bandwidth and actual requested bandwidth is + # subtle and has to do with what other things the CPU is + # busy with and the size of the downloaded files. + self.decreaseBandwidth(byteRate) + + def getBandwidth(self): + # What is the bandwidth Python thinks it is running at? + # Of course, if nobody has set it, this may not be the one + # the httpChannel is using + if self.backgrounded: + # If we are running in the background, we need to subtract + # out the telemetry bandwidth + bandwidth = (self.BANDWIDTH_ARRAY[self.bandwidthIndex] - + self.TELEMETRY_BANDWIDTH) + else: + # If we are running in the foreground, we have the entire channel + bandwidth = (self.BANDWIDTH_ARRAY[self.bandwidthIndex]) + if self.MAX_BANDWIDTH > 0: + bandwidth = min(bandwidth, self.MAX_BANDWIDTH) + + return bandwidth + + def increaseBandwidth(self, targetBandwidth = None): + # Change the download byte rate to a higher level in the + # BANDWIDTH_ARRAY, unless we are already at the highest level + maxBandwidthIndex = (len(self.BANDWIDTH_ARRAY) - 1) + if (self.bandwidthIndex == maxBandwidthIndex): + self.notify.debug('increaseBandwidth: Already at maximum bandwidth') + return 0 + + # Only go one step at a time when increasing bandwidth, even + # if we have a long way to go. + self.bandwidthIndex += 1 + assert self.notify.debug('increaseBandwidth: Increasing bandwidth to: ' + + `self.getBandwidth()`) + self.everIncreasedBandwidth = 1 + self.setBandwidth() + return 1 + + def decreaseBandwidth(self, targetBandwidth = None): + # Check the flag to see if we should do this at all + if not self.DECREASE_BANDWIDTH: + return 0 + + # Once we have started running the game, we can't fully trust + # the live bandwidth measurement (because the throughput also + # depends on CPU availability). Thus, a client running on an + # overloaded CPU may observe artificially poor bandwidths. If + # we were to take that at face value, we would completely + # starve the download as we continually drop the available + # bandwidth estimate downwards. + + # To avoid this problem, we do not decrease the bandwidth any + # more once we have been backgrounded--we only allow the + # bandwidth estimate to increase. However, it is possible + # that we did not download any content before we were + # backgrounded, which means we don't have a reliable starting + # bandwidth estimate; in this case, we will still need to + # decrease the bandwidth estimate downwards until we find the + # best starting point; we use the everIncreasedBandwidth flag + # to indicate whether we have found this point yet or not. + if self.backgrounded and self.everIncreasedBandwidth: + assert self.notify.debug('decreaseBandwidth: Running in background, not reducing bandwidth.') + return 0 + + # Change the download byte rate to the next lowest level in the + # BANDWIDTH_ARRAY, unless we are already at the lowest level + if (self.bandwidthIndex == 0): + assert self.notify.debug('decreaseBandwidth: Already at minimum bandwidth') + return 0 + else: + self.bandwidthIndex -= 1 + + if targetBandwidth: + # Jump many steps on decreasing bandwidth until we find + # the closest to our target. + while self.bandwidthIndex > 0 and \ + self.BANDWIDTH_ARRAY[self.bandwidthIndex] > targetBandwidth: + self.bandwidthIndex -= 1 + + assert self.notify.debug('decreaseBandwidth: Decreasing bandwidth to: ' + + `self.getBandwidth()`) + self.setBandwidth() + return 1 + + def setBandwidth(self): + # Reset the stats or else we will be counting bytes per second + # with the previous bandwidth + self.resetBytesPerSecond() + # Make the current bandwidth effective to the HTTP channel + self.httpChannel.setMaxBytesPerSecond(self.getBandwidth()) + + #============================================================ + # Functions for controlling the bandwidth + #============================================================ + + def resetBytesPerSecond(self): + self.bpsList = [] + + def recordBytesPerSecond(self): + bytesDownloaded = self.httpChannel.getBytesDownloaded() + bytesRequested = self.httpChannel.getBytesRequested() + t = self.getTime() + self.bpsList.append((t, bytesDownloaded, bytesRequested)) + # Keep the list within the window + while 1: + if (len(self.bpsList) == 0): + break + ft, fb, fr = self.bpsList[0] + # If this time is older than the current time - the window + # get it out of the list + if (ft < (t - self.BPS_WINDOW)): + self.bpsList.pop(0) + else: + # Get out of the while loop + break + + def getBytesPerSecond(self): + # You need at least two data points to determine the byteRate + # If the list is not primed enough, return -1 + # Do not report until you have enough stats to go off of + if (len(self.bpsList) < 2): + assert self.notify.debug('getBytesPerSecond: bpsList not enough elements: %s' % self.bpsList) + return -1 + startTime, startBytes, startRequested = self.bpsList[0] + finalTime, finalBytes, finalRequested = self.bpsList[-1] + dt = finalTime - startTime + db = finalBytes - startBytes + dr = finalRequested - startRequested + + if (dt <= 0.0): + assert self.notify.debug('getBytesPerSecond: negative dt') + return -1 + + self.byteRate = db / dt + self.byteRateRequested = dr / dt + assert self.notify.debug("getBytesPerSecond: db = %s, dr = %s, dt = %s byte rate = %s" % + (db, dr, dt, self.byteRate)) + return self.byteRate + + def testBandwidth(self): + # Any time we test, go ahead and record the current bandwidth + self.recordBytesPerSecond() + # Judging by the current byte rate, determine if we need to increase + # or decrease the download byte rate request + byteRate = self.getBytesPerSecond() + if (byteRate < 0): + assert self.notify.debug('testBandwidth: not enough data yet') + return + assert self.notify.debug('testBandwidth: comparing byteRate %s with getBandwidth %s and byteRateRequested %s.' % + (byteRate, self.getBandwidth(), self.byteRateRequested)) + + if (byteRate >= self.getBandwidth() * self.INCREASE_THRESHOLD): + # If we are nearly meeting the ideal bandwidth we are + # requesting, then let's try increasing the requested + # amount. + self.increaseBandwidth(byteRate) + + elif (byteRate < (self.byteRateRequested * self.DECREASE_THRESHOLD)): + # If we are not meeting the actual requested amount, let's + # back off. The semantic difference between ideal + # requested bandwidth and actual requested bandwidth is + # subtle and has to do with what other things the CPU is + # busy with and the size of the downloaded files. + self.decreaseBandwidth(byteRate) + + def getBandwidth(self): + # What is the bandwidth Python thinks it is running at? + # Of course, if nobody has set it, this may not be the one + # the httpChannel is using + if self.backgrounded: + # If we are running in the background, we need to subtract + # out the telemetry bandwidth + bandwidth = (self.BANDWIDTH_ARRAY[self.bandwidthIndex] - + self.TELEMETRY_BANDWIDTH) + else: + # If we are running in the foreground, we have the entire channel + bandwidth = (self.BANDWIDTH_ARRAY[self.bandwidthIndex]) + if self.MAX_BANDWIDTH > 0: + bandwidth = min(bandwidth, self.MAX_BANDWIDTH) + + return bandwidth + + def increaseBandwidth(self, targetBandwidth = None): + # Change the download byte rate to a higher level in the + # BANDWIDTH_ARRAY, unless we are already at the highest level + maxBandwidthIndex = (len(self.BANDWIDTH_ARRAY) - 1) + if (self.bandwidthIndex == maxBandwidthIndex): + assert self.notify.debug('increaseBandwidth: Already at maximum bandwidth') + return 0 + + # Only go one step at a time when increasing bandwidth, even + # if we have a long way to go. + self.bandwidthIndex += 1 + assert self.notify.debug('increaseBandwidth: Increasing bandwidth to: ' + + `self.getBandwidth()`) + self.everIncreasedBandwidth = 1 + self.setBandwidth() + return 1 + + def decreaseBandwidth(self, targetBandwidth = None): + # Check the flag to see if we should do this at all + if not self.DECREASE_BANDWIDTH: + return 0 + + # Once we have started running the game, we can't fully trust + # the live bandwidth measurement (because the throughput also + # depends on CPU availability). Thus, a client running on an + # overloaded CPU may observe artificially poor bandwidths. If + # we were to take that at face value, we would completely + # starve the download as we continually drop the available + # bandwidth estimate downwards. + + # To avoid this problem, we do not decrease the bandwidth any + # more once we have been backgrounded--we only allow the + # bandwidth estimate to increase. However, it is possible + # that we did not download any content before we were + # backgrounded, which means we don't have a reliable starting + # bandwidth estimate; in this case, we will still need to + # decrease the bandwidth estimate downwards until we find the + # best starting point; we use the everIncreasedBandwidth flag + # to indicate whether we have found this point yet or not. + if self.backgrounded and self.everIncreasedBandwidth: + assert self.notify.debug('decreaseBandwidth: Running in background, not reducing bandwidth.') + return 0 + + # Change the download byte rate to the next lowest level in the + # BANDWIDTH_ARRAY, unless we are already at the lowest level + if (self.bandwidthIndex == 0): + assert self.notify.debug('decreaseBandwidth: Already at minimum bandwidth') + return 0 + else: + self.bandwidthIndex -= 1 + + if targetBandwidth: + # Jump many steps on decreasing bandwidth until we find + # the closest to our target. + while self.bandwidthIndex > 0 and \ + self.BANDWIDTH_ARRAY[self.bandwidthIndex] > targetBandwidth: + self.bandwidthIndex -= 1 + + assert self.notify.debug('decreaseBandwidth: Decreasing bandwidth to: ' + + `self.getBandwidth()`) + self.setBandwidth() + return 1 + + def setBandwidth(self): + # Reset the stats or else we will be counting bytes per second + # with the previous bandwidth + self.resetBytesPerSecond() + # Make the current bandwidth effective to the HTTP channel + self.httpChannel.setMaxBytesPerSecond(self.getBandwidth()) + + def MakeNTFSFilesGlobalWriteable(self, pathToSet = None ): + if not self.WIN32: + return + import win32api + + if(pathToSet == None): + pathToSet = self.getInstallDir() + else: + # make sure both phase_4.mf and phase_4.mf.pz are changed, if they exist + pathToSet = pathToSet.cStr() + "*" + + DrivePath = pathToSet[0:3] # assumes INSTALL_DIR and file paths starts with 'DRIVE:\' + + try: + volname, volsernum, maxfilenamlen, sysflags, filesystemtype = win32api.GetVolumeInformation(DrivePath) + except: + return + + # if NTFS, run cacls to change the entire TT dir tree to Everyone:F + if(self.win32con_FILE_PERSISTENT_ACLS & sysflags): + self.notify.info('NTFS detected, making files global writeable\n') + win32dir = win32api.GetWindowsDirectory() + cmdLine = win32dir + "\\system32\\cacls.exe \"" + pathToSet + "\" /T /E /C /G Everyone:F > nul" + os.system(cmdLine) + + #============================================================ + # Cleanup + #============================================================ + + def cleanup(self): + self.notify.info('cleanup: cleaning up Launcher') + self.ignoreAll() + del self.clock + del self.dldb + del self.httpChannel + del self.http + + # + # scan for known speed hacks + # + def scanForHacks(self): + if not self.WIN32: + return + import _winreg + hacksInstalled = {} + hacksRunning = {} + hackName = [ + '!xSpeed.net', + 'A Speeder', + 'Speed Gear', + ] + + # scan for registry entries + # + knownHacksRegistryKeys = { + hackName[0]:[ + [_winreg.HKEY_LOCAL_MACHINE,'Software\\Microsoft\\Windows\\CurrentVersion\\Run\\!xSpeed'], + [_winreg.HKEY_CURRENT_USER,'Software\\!xSpeednethy'], + [_winreg.HKEY_CURRENT_USER,'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder\\Start Menu\\Programs\\!xSpeednet'], + [_winreg.HKEY_LOCAL_MACHINE,'Software\\Gentee\\Paths\\!xSpeednet'], + [_winreg.HKEY_LOCAL_MACHINE,'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\!xSpeed.net 2.0'], + ], + hackName[1]:[ + [_winreg.HKEY_CURRENT_USER,'Software\\aspeeder'], + [_winreg.HKEY_LOCAL_MACHINE,'Software\\aspeeder'], + [_winreg.HKEY_LOCAL_MACHINE,'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\aspeeder'], + ], + } + try: + for prog in knownHacksRegistryKeys.keys(): + for key in knownHacksRegistryKeys[prog]: + try: + h = _winreg.OpenKey(key[0], key[1]) + #print 'found %s in registry %s' % (prog,key[1]) + hacksInstalled[prog] = 1 + _winreg.CloseKey(h) + break # next program when any registry entry found + except: + pass + except: + pass + + # Fallback #1 + # scan MUICache + # not using libpandaexpress because it doesn't allow enumeration + # + knownHacksMUI = { + '!xspeednet': hackName[0], + 'aspeeder': hackName[1], + 'speed gear': hackName[2], + } + i = 0 + try: + rh = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\ShellNoRoam\\MUICache') + while 1: + name,value,type = _winreg.EnumValue(rh, i) + i += 1 + if type == 1: + val = value.lower() + for hackprog in knownHacksMUI: + if val.find(hackprog) != -1: + #print "found %s in MUICache:%s" % (knownHacksMUI[hackprog], val.encode('utf-8')) + hacksInstalled[knownHacksMUI[hackprog]] = 1 + break + _winreg.CloseKey(rh) + except: + #print "%s: stopped at %d" % (sys.exc_info()[0], i) + pass + + # Fallback #2 + # scan for running hack processes + # + try: + # process access + import otp.launcher.procapi + except: + pass + else: + knownHacksExe = { + '!xspeednet.exe': hackName[0], + 'aspeeder.exe': hackName[1], + 'speedgear.exe': hackName[2], + } + try: + for p in procapi.getProcessList(): + pname = p.name + if knownHacksExe.has_key(pname): + hacksRunning[knownHacksExe[pname]] = 1 + except: + pass + + if len(hacksInstalled) > 0: + self.notify.info('Third party programs installed:') + for hack in hacksInstalled.keys(): + self.notify.info(hack) + + if len(hacksRunning) > 0: + self.notify.info('Third party programs running:') + for hack in hacksRunning.keys(): + self.notify.info(hack) + # quit out because 3rd party program hack detected and is running + self.setPandaErrorCode(8) + sys.exit() + + def getBlue(self): + # Games that potentially use a blue token should override + return None + + def getPlayToken(self): + # Games that potentially use a playtoken should override + return None + + def getDISLToken(self): + """ + Get the DISLToken out of the store and return it. The + DISLToken is not saved; if this method is called a second + time it will return None. + """ + DISLToken = self.getValue(self.DISLTokenKey) + # Immediately clear out the DISLToken so it will be more + # difficult for a hacker to play with it + self.setValue(self.DISLTokenKey, "") + if DISLToken == "NO DISLTOKEN": + DISLToken = None + return DISLToken diff --git a/otp/src/launcher/Sources.pp b/otp/src/launcher/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/launcher/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/launcher/WebLauncherBase.py b/otp/src/launcher/WebLauncherBase.py new file mode 100644 index 0000000..6a8ca13 --- /dev/null +++ b/otp/src/launcher/WebLauncherBase.py @@ -0,0 +1,496 @@ +import direct +from pandac.PandaModules import * +from direct.showbase.DirectObject import DirectObject +from direct.showbase.MessengerGlobal import messenger +from direct.p3d.PackageInstaller import PackageInstaller +from direct.task.TaskManagerGlobal import taskMgr +import sys +import time +import os +import subprocess +import __builtin__ + +class WebLauncherBase(DirectObject): + + """ This class serves as a "launcher" for the games when launched + as a plugin on a web page, or via the Panda3D runtime in general + (e.g. via panda3d.exe). It attempts to replicate the API + presented by the old LauncherBase class, to the extent possible. + In the new world of the Panda3D runtime, it is no longer necessary + to explicitly manage downloading and patching; the runtime system + in AppRunner will do this now. """ + + + notify = directNotify.newCategory("WebLauncherBase") + + # Used to tell browser panda exit status (0 means everything normal) + PandaErrorCodeKey = "PANDA_ERROR_CODE" + # Used to keep track of whether or not this is a new installation + NewInstallationKey = "IS_NEW_INSTALLATION" + # Used to keep track of the last login name that was entered + LastLoginKey = "LAST_LOGIN" + # Used to keep track of the fact that a user has logged in + UserLoggedInKey = "USER_LOGGED_IN" + # Used to keep track of the fact that a paid user has logged in + PaidUserLoggedInKey = "PAID_USER_LOGGED_IN" + # Referrer code + ReferrerKey = "REFERRER_CODE" + # Period timer seconds remaining + PeriodTimeRemainingKey = "PERIOD_TIME_REMAINING" + # Period name + PeriodNameKey = "PERIOD_NAME" + # Period name + SwidKey = "SWID" + # The DISL Token + DISLTokenKey = "DISLTOKEN" + + # Stores the proxy server (string) (possibly with a :port) + # Empty string if there is no proxy + ProxyServerKey = "PROXY_SERVER" + ProxyDirectHostsKey = "PROXY_DIRECT_HOSTS" + + # This is always empty in the web launcher case (in the classic + # launcher, it might be different for different environments). + logPrefix = '' + + class PhaseData: + def __init__(self, phase): + self.phase = phase + self.percent = 0 + self.complete = False + self.postProcessCallbacks = [] + + def markComplete(self): + """ Called when the phase has been fully downloaded. This + function should invoke any postProcessCallbacks recorded, on + the appropriate task chain(s), and then mark the phase + complete and send the phaseComplete event. """ + + self.__nextCallback(None, 'default') + + def __nextCallback(self, currentCallback, currentTaskChain): + # Perform the callback's function. + if currentCallback: + currentCallback() + + # Grab the next callback off the list, and execute it. + if self.postProcessCallbacks: + callback, taskChain = self.postProcessCallbacks[0] + del self.postProcessCallbacks[0] + if taskChain != currentTaskChain: + # Switch to the next task chain. + print "switching to %s" % (taskChain) + taskMgr.add(self.__nextCallback, 'phaseCallback-%s' % (self.phase), + taskChain = taskChain, extraArgs = [callback, taskChain]) + return + + # No more callbacks. Send the phaseComplete event. + self.complete = True + messenger.send('phaseComplete-%s' % (self.phase), taskChain = 'default') + + + def __init__(self, appRunner): + self.appRunner = appRunner + __builtin__.launcher = self + + appRunner.exceptionHandler = self.exceptionHandler + + # Get the game info from the web page. + gameInfoStr = appRunner.getToken('gameInfo') + if gameInfoStr: + self.gameInfo = appRunner.evalScript(gameInfoStr, needsResponse = True) + else: + # No game info available; create a dummy object instead. + class DummyGameInfo: + pass + self.gameInfo = DummyGameInfo() + + self.phasesByPackageName = {} + self.phaseData = {} + self.allPhasesComplete = False + for phase, packageName in self.LauncherPhases: + self.phasesByPackageName[packageName] = phase + self.phaseData[phase] = self.PhaseData(phase) + self.acceptOnce('phaseComplete-%s' % (phase), self.__gotPhaseComplete) + + self.packageInstaller = None + self.started = False + + self.setPandaErrorCode(0) + self.setServerVersion("dev") + + self.WIN32 = (os.name == 'nt') + + if self.WIN32: + # Vista = windows 2.6 + self.VISTA = (sys.getwindowsversion()[3] == 2 and sys.getwindowsversion()[0] == 6) + else: + # it can't be Vista + self.VISTA = 0 + + # Fake download hash values + self.launcherFileDbHash = HashVal() + self.serverDbFileHash = HashVal() + + # Is this the test server? + self.testServerFlag = self.getTestServerFlag() + self.notify.info("isTestServer: %s" % (self.testServerFlag)) + + # Write to the log + print "\n\nStarting %s..." % self.GameName + print ("Current time: " + time.asctime(time.localtime(time.time())) + + " " + time.tzname[0]) + print "sys.argv = ", sys.argv + print "tokens = ", appRunner.tokens + print "gameInfo = ", self.gameInfo + + # Run an external command to get the system hardware + # information into a log file. First, we have to change the + # current directory (dxdiag, in particular, insists on this). + cwd = os.getcwd() + os.chdir(appRunner.logDirectory.toOsSpecific()) + self.notify.info('chdir: %s' % (appRunner.logDirectory.toOsSpecific())) + + hwprofile = 'hwprofile.log' + command = None + if sys.platform == "darwin": + command = '/usr/sbin/system_profiler >%s' % (hwprofile) + shell = True + elif sys.platform == "linux": + command = '(cat /proc/cpuinfo; cat /proc/meminfo; /sbin/ifconfig -a) >%s' % (hwprofile) + shell = True + else: + command = 'dxdiag /t %s' % (hwprofile) + shell = False + + self.notify.info(command) + try: + self.hwpipe = subprocess.Popen(command, shell=shell) + except OSError: + self.notify.warning('Could not run hwpipe command') + self.hwpipe = None + + # Now we can change the directory back. + os.chdir(cwd) + + if self.hwpipe: + self.notify.info('hwpipe pid: %s' % (self.hwpipe.pid)) + taskMgr.add(self.__checkHwpipe, 'checkHwpipe') + + def __gotPhaseComplete(self): + """ A hook added to a phaseComplete message. When all + phaseCompletes have been sent, trigger the allPhasesComplete + message. """ + + if self.allPhasesComplete: + # Already sent. + return + + for phaseData in self.phaseData.values(): + if not phaseData.complete: + # More to come later. + return + + # All phases are now complete. Tell the world. + self.allPhasesComplete = True + print "launcherAllPhasesComplete" + messenger.send("launcherAllPhasesComplete", taskChain = 'default') + + + def __checkHwpipe(self, task): + """ Checks to see if the hwpipe process has finished, so we + can report it to the log. """ + if self.hwpipe.poll() is None: + # Still waiting. + return task.cont + + #hwpipeout = self.hwpipe.communicate() + #self.notify.info('hwpipe output: %s' % (hwpipeout[0])) + #self.notify.info('hwpipe error: %s' % (hwpipeout[1])) + self.notify.info('hwpipe finished: %s' % (self.hwpipe.returncode)) + return task.done + + def isDummy(self): + # This is not the DummyLauncher. + return False + + def setRegistry(self, key, value): + self.notify.info('DEPRECATED setRegistry: %s = %s' % (key, value)) + return + + def getRegistry(self, key): + self.notify.info('DEPRECATED getRegistry: %s' % (key)) + return None + + def getValue(self, key, default=None): + return getattr(self.gameInfo, key, default) + + def setValue(self, key, value): + setattr(self.gameInfo, key, value) + + def getVerifyFiles(self): + # TODO: take this out when we ship. + return config.GetInt('launcher-verify', 0) + + def getTestServerFlag(self): + return self.getValue('IS_TEST_SERVER', 0) + + def getGameServer(self): + return self.getValue('GAME_SERVER', '') + + def getPhaseComplete(self, phase): + return (self.phaseData[phase].complete) + + def getPercentPhaseComplete(self, phase): + return self.phaseData[phase].percent + + def addPhasePostProcess(self, phase, func, taskChain = 'default'): + """ Adds a post-process callback function to the phase + download. When the indicated phase is successfully + downloaded, the given function will be called, on the + specified taskChain. The phase will not be marked fully + downloaded, and the phaseComplete event will not be sent, + until the callback function has completed. + + If the phase is already complete at the time this function is + called, the callback function is called immediately, in the + current task. """ + + if self.getPhaseComplete(phase): + # Already downloaded. + func() + return + + self.phaseData[phase].postProcessCallbacks.append((func, taskChain)) + + def getBlue(self): + # Games that potentially use a blue token should override + return None + + def getPlayToken(self): + # Games that potentially use a playtoken should override + return None + + def getDISLToken(self): + """ + Get the DISLToken out of the store and return it. The + DISLToken is not saved; if this method is called a second + time it will return None. + """ + DISLToken = self.getValue(self.DISLTokenKey) + if DISLToken == "NO DISLTOKEN": + DISLToken = None + return DISLToken + + def startDownload(self): + assert not self.packageInstaller + + self.packageInstaller = WebLauncherInstaller(self) + + for phase, packageName in self.LauncherPhases: + self.packageInstaller.addPackage(packageName) + + self.packageInstaller.donePackages() + + + #============================================================ + # Interface of launcher to the rest of the game + #============================================================ + + def isTestServer(self): + return self.testServerFlag + + def recordPeriodTimeRemaining(self, secondsRemaining): + self.setValue(self.PeriodTimeRemainingKey, int(secondsRemaining)) + + def recordPeriodName(self, periodName): + self.setValue(self.PeriodNameKey, periodName) + + def recordSwid(self, swid): + self.setValue(self.SwidKey, swid) + + def getGoUserName(self): + return self.goUserName + + def setGoUserName(self, userName): + self.goUserName = userName + + def setPandaWindowOpen(self): + # This is called when the Panda window is successfully opened. + # It used to be an important signal in the old ActiveX world. + # It is now a no-op. + pass + + def setPandaErrorCode(self, code): + """ + Set the exit code of panda. 0 means everything ok + """ + self.pandaErrorCode = code + self.gameInfo.pandaErrorCode = code + + def getPandaErrorCode(self): + return self.pandaErrorCode + + def setDisconnectDetailsNormal(self): + self.disconnectCode = 0 + self.disconnectMsg = 'normal' + self.gameInfo.disconnectCode = self.disconnectCode + self.gameInfo.disconnectMsg = self.disconnectMsg + + def setDisconnectDetails(self, newCode, newMsg): + if newCode is None: + newCode = 0 + self.disconnectCode = newCode + self.disconnectMsg = newMsg + self.gameInfo.disconnectCode = self.disconnectCode + self.gameInfo.disconnectMsg = self.disconnectMsg + self.notify.warning("disconnected with code: %s - %s" % (self.gameInfo.disconnectCode,self.gameInfo.disconnectMsg)) + + def setServerVersion(self, version): + """ + Set the server version. Exposed to gameInfo. + """ + self.ServerVersion = version + self.gameInfo.ServerVersion = version + + def getServerVersion(self): + return self.ServerVersion + + def getIsNewInstallation(self): + """ + is this a new installation? + """ + result = self.getValue(self.NewInstallationKey, 1) + result = base.config.GetBool("new-installation", result) + return result + + def setIsNotNewInstallation(self): + """ + Set that this is no longer a new installation + (has created account or logged in) + """ + self.setValue(self.NewInstallationKey, 0) + + def getLastLogin(self): + """ + Get the last login + """ + return self.getValue(self.LastLoginKey, '') + + def setLastLogin(self, login): + """ + Set a new 'last login' + """ + self.setValue(self.LastLoginKey, login) + + def setUserLoggedIn(self): + """ + Set that a user has logged in. + """ + self.setValue(self.UserLoggedInKey, '1') # since these are flags they should really be 1, not '1' + + def setPaidUserLoggedIn(self): + """ + Set that a paid user has logged in + """ + self.setValue(self.PaidUserLoggedInKey, '1') # since these are flags they should really be 1, not '1' + + def getReferrerCode(self): + """ + Get the referrer code + """ + return self.getValue(self.ReferrerKey, None) + + def exceptionHandler(self): + """ This callback is assigned to the + appRunner.exceptionHandler pointer, so we get notified on an + unexpected Python exception. """ + + # A Python exception is error code 12 for the installer. + self.setPandaErrorCode(12) + self.notify.warning("Handling Python exception.") + + if hasattr(__builtin__, "base") and \ + getattr(base, "cr", None): + # Tell the AI (if we have one) why we're going down. + if base.cr.timeManager: + from otp.otpbase import OTPGlobals + base.cr.timeManager.setDisconnectReason(OTPGlobals.DisconnectPythonError) + base.cr.timeManager.setExceptionInfo() + + # Tell the server we're gone too. + base.cr.sendDisconnect() + + if hasattr(__builtin__, "base"): + # Clean up showbase correctly. + base.destroy() + + self.notify.info("Exception exit.\n") + import traceback + traceback.print_exc() + sys.exit() + + def isDownloadComplete(self): + return self.allPhasesComplete + + +class WebLauncherInstaller(PackageInstaller): + """ Subclasses PackageInstaller to send the appropriate messages + as packages are downloaded. """ + + def __init__(self, launcher): + PackageInstaller.__init__(self, launcher.appRunner) + self.launcher = launcher + self.lastProgress = None + + def packageProgress(self, package, progress): + """ This callback is made repeatedly between packageStarted() + and packageFinished() to update the current progress on the + indicated package only. The progress value ranges from 0 + (beginning) to 1 (complete). """ + + PackageInstaller.packageProgress(self, package, progress) + percent = int(progress * 100.0 + 0.5) + phase = self.launcher.phasesByPackageName[package.packageName] + self.launcher.phaseData[phase].percent = percent + if (phase, percent) != self.lastProgress: + messenger.send('launcherPercentPhaseComplete', + [phase, percent, None, None]) + self.lastProgress = (phase, percent) + + def packageFinished(self, package, success): + """ This callback is made for each package between + downloadStarted() and downloadFinished() to indicate that a + package has finished downloading. If success is true, there + were no problems and the package is now installed. + + If this package did not require downloading (because it was + already downloaded), this callback will be made immediately, + *without* a corresponding call to packageStarted(), and may + even be made before downloadStarted(). """ + + PackageInstaller.packageFinished(self, package, success) + if not success: + print "Failed to download %s" % (package.packageName) + self.launcher.setPandaErrorCode(6) + sys.exit() + + phase = self.launcher.phasesByPackageName[package.packageName] + self.launcher.phaseData[phase].markComplete() + + def downloadFinished(self, success): + """ This callback is made when all of the packages have been + downloaded and installed (or there has been some failure). If + all packages where successfully installed, success is True. + + If there were no packages that required downloading, this + callback will be made immediately, *without* a corresponding + call to downloadStarted(). """ + + PackageInstaller.downloadFinished(self, success) + if not success: + print "Failed to download all packages." + + # We don't immediately trigger launcherAllPhasesComplete here, + # because we might still be waiting on one or more + # postProcessCallbacks. diff --git a/otp/src/launcher/__init__.py b/otp/src/launcher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/launcher/procapi.py b/otp/src/launcher/procapi.py new file mode 100644 index 0000000..9f8d0a5 --- /dev/null +++ b/otp/src/launcher/procapi.py @@ -0,0 +1,43 @@ +import ctypes +from ctypes.wintypes import * + +TH32CS_SNAPPROCESS = 2 +INVALID_HANDLE_VALUE = -1 + +cwk = ctypes.windll.kernel32 + +class PROCESSENTRY32(ctypes.Structure): + _fields_ = [('dwSize', DWORD), + ('cntUsage', DWORD), + ('th32ProcessID', DWORD), + ('th32DefaultHeapId', HANDLE), # should be ULONG_PTR :P + ('th32ModuleID', DWORD), + ('cntThreads', DWORD), + ('th32ParentProcessID', DWORD), + ('pcPriClassBase', LONG), + ('dwFlags', DWORD), + ('szExeFile', c_char * MAX_PATH) + ] + +class ProcessEntryPY: + def __init__ (self, name, pid): + self.name = name + self.pid = pid + +def getProcessList(): + hProcessSnap = cwk.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + processList = [] + + if (hProcessSnap != INVALID_HANDLE_VALUE): + pe32 = PROCESSENTRY32() + pe32.dwSize = sizeof(pe32) + + if cwk.Process32First(hProcessSnap, ctypes.byref(pe32)): + while 1: + processList.append(ProcessEntryPY(pe32.szExeFile.lower(), int(pe32.th32ProcessID))) + if not cwk.Process32Next(hProcessSnap, ctypes.byref(pe32)): + break + cwk.CloseHandle(hProcessSnap) + + return processList + diff --git a/otp/src/level/.cvsignore b/otp/src/level/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/level/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/level/AmbientSound.py b/otp/src/level/AmbientSound.py new file mode 100644 index 0000000..7c3897f --- /dev/null +++ b/otp/src/level/AmbientSound.py @@ -0,0 +1,37 @@ +from direct.interval.IntervalGlobal import * +import BasicEntities +import random + +class AmbientSound(BasicEntities.NodePathEntity): + def __init__(self, level, entId): + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.initSound() + + def destroy(self): + self.destroySound() + BasicEntities.NodePathEntity.destroy(self) + + def initSound(self): + if not self.enabled: + return + if self.soundPath == '': + return + self.sound = base.loadSfx(self.soundPath) + if self.sound is None: + return + self.soundIval = SoundInterval(self.sound, node=self, + volume=self.volume) + self.soundIval.loop() + self.soundIval.setT(random.random() * self.sound.length()) + + def destroySound(self): + if hasattr(self, 'soundIval'): + self.soundIval.pause() + del self.soundIval + if hasattr(self, 'sound'): + del self.sound + + if __dev__: + def attribChanged(self, *args): + self.destroySound() + self.initSound() diff --git a/otp/src/level/AttribDesc.py b/otp/src/level/AttribDesc.py new file mode 100644 index 0000000..59adef9 --- /dev/null +++ b/otp/src/level/AttribDesc.py @@ -0,0 +1,32 @@ +"""AttribDesc.py module: contains the AttribDesc class""" + +class AttribDesc: + """ + Entity attribute descriptor + name == name of attribute + default == default value for attrib + """ + def __init__(self, name, default, datatype='string', params = {}): + self.name = name + self.default = default + self.datatype = datatype + self.params = params + + def getName(self): + return self.name + def getDefaultValue(self): + return self.default + def getDatatype(self): + return self.datatype + def getParams(self): + return self.params + + def __str__(self): + return self.name + def __repr__(self): + return "AttribDesc(%s, %s, %s, %s)" % ( + repr(self.name), + repr(self.default), + repr(self.datatype), + repr(self.params), + ) diff --git a/otp/src/level/BasicEntities.py b/otp/src/level/BasicEntities.py new file mode 100644 index 0000000..82a2d5f --- /dev/null +++ b/otp/src/level/BasicEntities.py @@ -0,0 +1,130 @@ +"""BasicEntities module: contains fundamental entity types and base classes""" + +import Entity +import DistributedEntity +from pandac.PandaModules import NodePath + +# base class for entities that support NodePath attributes +# *** Don't derive directly from this class; derive from the appropriate +# specialized class from the classes defined below. +class NodePathEntityBase: + # we don't call this __init__ because it doesn't have to be called + # upon object init + def initNodePathAttribs(self, doReparent=1): + """Call this after the entity has been initialized""" + self.callSetters('pos','x','y','z', + 'hpr','h','p','r', + 'scale','sx','sy','sz') + if doReparent: + self.callSetters('parentEntId') + + self.getNodePath().setName('%s-%s' % + (self.__class__.__name__, self.entId)) + + if __dev__: + # for the editor + self.getNodePath().setTag('entity', '1') + + def setParentEntId(self, parentEntId): + self.parentEntId = parentEntId + self.level.requestReparent(self, self.parentEntId) + + def destroy(self): + if __dev__: + # for the editor + self.getNodePath().clearTag('entity') + +# Entities that already derive from NodePath and Entity should derive +# from this class +class NodePathAttribs(NodePathEntityBase): + def initNodePathAttribs(self, doReparent=1): + NodePathEntityBase.initNodePathAttribs(self, doReparent) + + def destroy(self): + NodePathEntityBase.destroy(self) + + def getNodePath(self): + return self + +# Entities that already derive from Entity, and do not derive from NodePath, +# but want to be a NodePath, should derive from this. +class NodePathAndAttribs(NodePathEntityBase, NodePath): + def __init__(self): + node = hidden.attachNewNode('EntityNodePath') + NodePath.__init__(self, node) + + def initNodePathAttribs(self, doReparent=1): + NodePathEntityBase.initNodePathAttribs(self, doReparent) + + def destroy(self): + NodePathEntityBase.destroy(self) + self.removeNode() + + def getNodePath(self): + return self + +# Entities that already derive from Entity, and do not derive from NodePath, +# but HAVE a NodePath that they want to represent them, should derive from +# this. They must define getNodePath(), which should return their 'proxy' +# NodePath instance. +class NodePathAttribsProxy(NodePathEntityBase): + def initNodePathAttribs(self, doReparent=1): + """Call this after the entity has been initialized""" + NodePathEntityBase.initNodePathAttribs(self, doReparent) + assert self.getNodePath() != self + + def destroy(self): + NodePathEntityBase.destroy(self) + + def setPos(self, *args): self.getNodePath().setPos(*args) + def setX(self, *args): self.getNodePath().setX(*args) + def setY(self, *args): self.getNodePath().setY(*args) + def setZ(self, *args): self.getNodePath().setZ(*args) + + def setHpr(self, *args): self.getNodePath().setHpr(*args) + def setH(self, *args): self.getNodePath().setH(*args) + def setP(self, *args): self.getNodePath().setP(*args) + def setR(self, *args): self.getNodePath().setR(*args) + + def setScale(self, *args): self.getNodePath().setScale(*args) + def setSx(self, *args): self.getNodePath().setSx(*args) + def setSy(self, *args): self.getNodePath().setSy(*args) + def setSz(self, *args): self.getNodePath().setSz(*args) + + def reparentTo(self, *args): self.getNodePath().reparentTo(*args) + +# This is an entity that represents a NodePath on the client. +# It may be instantiated directly or used as a base class for other +# entity types that 'are' NodePaths. +class NodePathEntity(Entity.Entity, NodePath, NodePathAttribs): + def __init__(self, level, entId): + node = hidden.attachNewNode('NodePathEntity') + NodePath.__init__(self, node) + Entity.Entity.__init__(self, level, entId) + self.initNodePathAttribs(self) + + def destroy(self): + NodePathAttribs.destroy(self) + Entity.Entity.destroy(self) + self.removeNode() + +# This is a distributed version of NodePathEntity. It should not +# be instantiated directly; distributed entities that are also NodePaths +# may derive from this instead of DistributedEntity. +class DistributedNodePathEntity(DistributedEntity.DistributedEntity, + NodePath, NodePathAttribs): + def __init__(self, cr): + DistributedEntity.DistributedEntity.__init__(self, cr) + + def generateInit(self): + DistributedEntity.DistributedEntity.generateInit(self) + node = hidden.attachNewNode('DistributedNodePathEntity') + NodePath.__init__(self, node) + + def announceGenerate(self): + DistributedEntity.DistributedEntity.announceGenerate(self) + self.initNodePathAttribs(self) + + def delete(self): + self.removeNode() + DistributedEntity.DistributedEntity.delete(self) diff --git a/otp/src/level/CollisionSolidEntity.py b/otp/src/level/CollisionSolidEntity.py new file mode 100644 index 0000000..5046bd8 --- /dev/null +++ b/otp/src/level/CollisionSolidEntity.py @@ -0,0 +1,43 @@ +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from direct.directnotify import DirectNotifyGlobal +import BasicEntities + +class CollisionSolidEntity(BasicEntities.NodePathEntity): + notify = DirectNotifyGlobal.directNotify.newCategory('CollisionSolidEntity') + + def __init__(self, level, entId): + self.collNodePath = None + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.initSolid() + + def destroy(self): + self.destroySolid() + BasicEntities.NodePathEntity.destroy(self) + + def initSolid(self): + self.destroySolid() + if self.solidType == 'sphere': + solid = CollisionSphere(0, 0, 0, self.radius) + else: + solid = CollisionTube(0, 0, 0, 0, 0, self.length, self.radius) + node = CollisionNode(self.getUniqueName(self.__class__.__name__)) + node.addSolid(solid) + node.setCollideMask(OTPGlobals.WallBitmask) + self.collNodePath = self.attachNewNode(node) + if __dev__: + if self.showSolid: + self.showCS() + else: + self.hideCS() + + def destroySolid(self): + if self.collNodePath is not None: + self.collNodePath.removeNode() + self.collNodePath = None + + if __dev__: + def attribChanged(self, attrib, value): + print 'attribChanged' + self.initSolid() + diff --git a/otp/src/level/CutScene.py b/otp/src/level/CutScene.py new file mode 100644 index 0000000..67c7729 --- /dev/null +++ b/otp/src/level/CutScene.py @@ -0,0 +1,189 @@ +"""CutScene.py""" + + +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +import BasicEntities + +from pandac.PandaModules import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import * + +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +#import DistributedInteractiveEntity + +# effects # + +def nothing(self, track, subjectNodePath, duration): + assert self.debugPrint( + "nothing(track=%s, subjectNodePath=%s, duration=%s)"%( + track, subjectNodePath, duration)) + return track + +def irisInOut(self, track, subjectNodePath, duration): + assert self.debugPrint( + "irisInOut(track=%s, subjectNodePath=%s, duration=%s)"%( + track, subjectNodePath, duration)) + track.append(Sequence( + Func(base.transitions.irisOut, 0.5), + Func(base.transitions.irisIn, 1.5), + Wait(duration), + Func(base.transitions.irisOut, 1.0), + Func(base.transitions.irisIn, 0.5), + )) + return track + +def letterBox(self, track, subjectNodePath, duration): + assert self.debugPrint( + "letterBox(track=%s, subjectNodePath=%s, duration=%s)"%( + track, subjectNodePath, duration)) + track.append(Sequence( + #Func(base.transitions.letterBox, 0.5), + Wait(duration), + #Func(base.transitions.letterBox, 0.5), + )) + return track + +# motions # + +def foo1(self, track, subjectNodePath, duration): + assert self.debugPrint( + "foo1(track=%s, subjectNodePath=%s, duration=%s)"%( + track, subjectNodePath, duration)) + track.append(Sequence( + Func(base.localAvatar.stopUpdateSmartCamera), + PosHprInterval( + camera, + other=subjectNodePath, + pos=Point3(-2, -35, 7.5), + hpr=VBase3(-7, 0, 0)), + LerpPosHprInterval( + nodePath=camera, + other=subjectNodePath, + duration=duration, + pos=Point3(2, -22, 7.5), + hpr=VBase3(4, 0, 0), + blendType="easeInOut"), + PosHprInterval( + camera, + other=subjectNodePath, + pos=Point3(0, -28, 7.5), + hpr=VBase3(0, 0, 0)), + Func(base.localAvatar.startUpdateSmartCamera), + )) + return track + +def doorUnlock(self, track, subjectNodePath, duration): + assert self.debugPrint( + "doorUnlock(track=%s, subjectNodePath=%s, duration=%s)"%( + track, subjectNodePath, duration)) + track.append(Sequence( + Func(base.localAvatar.stopUpdateSmartCamera), + PosHprInterval( + camera, + other=self, + pos=Point3(-2, -35, 7.5), + hpr=VBase3(-7, 0, 0)), + LerpPosHprInterval( + nodePath=camera, + other=self, + duration=duration, + pos=Point3(2, -22, 7.5), + hpr=VBase3(4, 0, 0), + blendType="easeInOut"), + PosHprInterval( + camera, + other=self, + pos=Point3(0, -28, 7.5), + hpr=VBase3(0, 0, 0)), + Func(base.localAvatar.startUpdateSmartCamera), + )) + return track + + +class CutScene(BasicEntities.NodePathEntity, DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory('CutScene') + + effects={ + "nothing": nothing, + "irisInOut": irisInOut, + "letterBox": letterBox, + } + + motions={ + "foo1": foo1, + "doorUnlock": doorUnlock, + } + + def __init__(self, level, entId): + assert self.debugPrint( + "CutScene(level=%s, entId=%s)"%(level, entId)) + DirectObject.DirectObject.__init__(self) + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.track = None + self.setEffect(self.effect) + self.setMotion(self.motion) + self.subjectNodePath = render.attachNewNode("CutScene") + self.subjectNodePath.setPos(self.pos) + self.subjectNodePath.setHpr(self.hpr) + #self.setSubjectNodePath(self.subjectNodePath) + self.setStartStop(self.startStopEvent) + + def destroy(self): + assert self.debugPrint("destroy()") + self.ignore(self.startStopEvent) + self.startStopEvent = None + BasicEntities.NodePathEntity.destroy(self) + #DirectObject.DirectObject.destroy(self) + + def setEffect(self, effect): + assert self.debugPrint("setEffect(effect=%s)"%(effect,)) + self.effect=effect + assert self.effects[effect] + self.getEffect=self.effects[effect] + + def setMotion(self, motion): + assert self.debugPrint("setMotion(motion=%s)"%(motion,)) + self.motionType=motion + assert self.motions[motion] + self.getMotion=self.motions[motion] + + def setSubjectNodePath(self, subjectNodePath): + assert self.debugPrint( + "setSubjectNodePath(subjectNodePath=%s)"%(subjectNodePath,)) + self.subjectNodePath=subjectNodePath + + def startOrStop(self, start): + assert self.debugPrint("startOrStop(start=%s)"%(start,)) + trackName = "cutSceneTrack-%d" % (id(self),) + if start: + if self.track: + self.track.finish() + self.track = None + track = Parallel(name = trackName) + track = self.getEffect(self, track, self.subjectNodePath, self.duration) + track = self.getMotion(self, track, self.subjectNodePath, self.duration) + track = Sequence(Wait(0.4), track) + track.start(0.0) + assert self.debugPrint("starting track=%s"%(track,)) + self.track = track + else: + if self.track: + self.track.pause() + self.track = None + base.localAvatar.startUpdateSmartCamera() + + def setStartStop(self, event): + assert self.debugPrint("setStartStop(event=%s)"%(event,)) + if self.startStopEvent: + self.ignore(self.startStopEvent) + self.startStopEvent = self.getOutputEventName(event) + if self.startStopEvent: + self.accept(self.startStopEvent, self.startOrStop) + + def getName(self): + #return "CutScene-%s"%(self.entId,) + return "switch-%s"%(self.entId,) diff --git a/otp/src/level/DistributedEntity.py b/otp/src/level/DistributedEntity.py new file mode 100644 index 0000000..1306a3f --- /dev/null +++ b/otp/src/level/DistributedEntity.py @@ -0,0 +1,65 @@ +from direct.distributed import DistributedObject +import Entity +from direct.directnotify import DirectNotifyGlobal + +class DistributedEntity(DistributedObject.DistributedObject, Entity.Entity): + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedEntity') + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + Entity.Entity.__init__(self) + + self.levelDoId = 0 + self.entId = 0 + self.level = None + + def generateInit(self): + DistributedEntity.notify.debug('generateInit') + DistributedObject.DistributedObject.generateInit(self) + # load stuff + + def generate(self): + DistributedEntity.notify.debug('generate') + DistributedObject.DistributedObject.generate(self) + + def setLevelDoId(self, levelDoId): + DistributedEntity.notify.debug('setLevelDoId: %s' % levelDoId) + self.levelDoId = levelDoId + + def setEntId(self, entId): + DistributedEntity.notify.debug('setEntId: %s' % entId) + self.entId = entId + + def announceGenerate(self): + ### + ### THIS IS WHERE CLIENT-SIDE DISTRIBUTED ENTITIES GET THEIR + ### ATTRIBUTES SET + ### + DistributedEntity.notify.debug('announceGenerate (%s)' % self.entId) + + # ask our level obj for our spec data + if self.levelDoId != 0: + level = base.cr.doId2do[self.levelDoId] + self.initializeEntity(level, self.entId) + # announce our presence (Level does this for non-distributed entities) + self.level.onEntityCreate(self.entId) + + else: + # We don't have a level. This probably indicates an + # intention to create an Entity unassociated with any + # particular level (e.g. a Goon). + self.level = None + + DistributedObject.DistributedObject.announceGenerate(self) + + def disable(self): + DistributedEntity.notify.debug('disable (%s)' % self.entId) + # stop things + self.destroy() + DistributedObject.DistributedObject.disable(self) + + def delete(self): + DistributedEntity.notify.debug('delete') + # unload things + DistributedObject.DistributedObject.delete(self) diff --git a/otp/src/level/DistributedEntityAI.py b/otp/src/level/DistributedEntityAI.py new file mode 100644 index 0000000..84c8ddd --- /dev/null +++ b/otp/src/level/DistributedEntityAI.py @@ -0,0 +1,55 @@ +from direct.distributed import DistributedObjectAI +import Entity +from direct.directnotify import DirectNotifyGlobal + +class DistributedEntityAI(DistributedObjectAI.DistributedObjectAI, + Entity.Entity): + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedEntityAI') + + def __init__(self, level, entId): + ### + ### THIS IS WHERE AI-SIDE DISTRIBUTED ENTITIES GET THEIR ATTRIBUTES SET + ### + if hasattr(level, "air"): + air = level.air + self.levelDoId = level.doId + + else: + # Assume we were given an AIRepository for the first + # parameter, not a DistributedLevelAI. This is used when + # we are creating an entity not associated with a level + # (e.g. a Goon walking around somewhere else). + air = level + level = None + self.levelDoId = 0 + + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + Entity.Entity.__init__(self, level, entId) + + def generate(self): + self.notify.debug('generate') + DistributedObjectAI.DistributedObjectAI.generate(self) + + def destroy(self): + self.notify.debug('destroy') + Entity.Entity.destroy(self) + self.requestDelete() + + def delete(self): + self.notify.debug('delete') + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getLevelDoId(self): + return self.levelDoId + + def getEntId(self): + return self.entId + + if __dev__: + def setParentEntId(self, parentEntId): + self.parentEntId = parentEntId + # switch to new zone + newZoneId = self.getZoneEntity().getZoneId() + if newZoneId != self.zoneId: + self.sendSetZone(newZoneId) diff --git a/otp/src/level/DistributedInteractiveEntity.py b/otp/src/level/DistributedInteractiveEntity.py new file mode 100644 index 0000000..3379ef0 --- /dev/null +++ b/otp/src/level/DistributedInteractiveEntity.py @@ -0,0 +1,147 @@ +""" DistributedInteractiveEntity module: contains the DistributedInteractiveEntity + class, the client side representation of a 'landmark door'.""" + +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * + +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +import DistributedEntity + +class DistributedInteractiveEntity(DistributedEntity.DistributedEntity): + """ + DistributedInteractiveEntity class: The client side representation of any + simple animated prop. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInteractiveEntity') + + def __init__(self, cr): + """constructor for the DistributedInteractiveEntity""" + DistributedEntity.DistributedEntity.__init__(self, cr) + assert self.debugPrint("DistributedInteractiveEntity()") + + self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntity', + [State.State('off', + self.enterOff, + self.exitOff, + ['playing', + 'attract']), + State.State('attract', + self.enterAttract, + self.exitAttract, + ['playing']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['attract'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + # self.generate will be called automatically. + + def generate(self): + """ + This method is called when the DistributedEntity is introduced + to the world, either for the first time or from the cache. + """ + assert self.debugPrint("generate()") + DistributedEntity.DistributedEntity.generate(self) + + def disable(self): + assert self.debugPrint("disable()") + # Go to the off state when the object is put in the cache + self.fsm.request("off") + DistributedEntity.DistributedEntity.disable(self) + # self.delete() will automatically be called. + + def delete(self): + assert self.debugPrint("delete()") + del self.fsm + DistributedEntity.DistributedEntity.delete(self) + + def setAvatarInteract(self, avatarId): + """ + required dc field. + """ + assert self.debugPrint("setAvatarInteract(%s)"%(avatarId,)) + assert not self.__dict__.has_key(avatarId) + self.avatarId=avatarId + + def setOwnerDoId(self, ownerDoId): + """ + required dc field. + """ + assert self.debugPrint("setOwnerDoId(%s)"%(ownerDoId,)) + assert not self.__dict__.has_key("ownerDoId") + self.ownerDoId=ownerDoId + + def setState(self, state, timestamp): + assert self.debugPrint("setState(%s, %d)" % (state, timestamp)) + if self.isGenerated(): + self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)]) + else: + self.initialState = state + self.initialStateTimestamp = timestamp + + #def __getPropNodePath(self): + # assert self.debugPrint("__getPropNodePath()") + # if (not self.__dict__.has_key('propNodePath')): + # self.propNodePath=self.cr.playGame.hood.loader.geom.find( + # "**/prop"+self.entID+":*_DNARoot") + # return self.propNodePath + + def enterTrigger(self, args=None): + assert self.debugPrint("enterTrigger(args="+str(args)+")") + messenger.send("DistributedInteractiveEntity_enterTrigger") + self.sendUpdate("requestInteract") + # the AI server will reply with toonInteract or rejectInteract. + + def exitTrigger(self, args=None): + assert self.debugPrint("exitTrigger(args="+str(args)+")") + messenger.send("DistributedInteractiveEntity_exitTrigger") + self.sendUpdate("requestExit") + # the AI server will reply with avatarExit. + + def rejectInteract(self): + """ + Server doesn't let the avatar interact with prop. + """ + assert self.debugPrint("rejectInteract()") + self.cr.playGame.getPlace().setState('walk') + + def avatarExit(self, avatarId): + assert self.debugPrint("avatarExit(avatarId=%s)"%(avatarId,)) + + ##### off state ##### + + def enterOff(self): + assert self.debugPrint("enterOff()") + + def exitOff(self): + assert self.debugPrint("exitOff()") + + ##### attract state ##### + + def enterAttract(self, ts): + assert self.debugPrint("enterAttract()") + + def exitAttract(self): + assert self.debugPrint("exitAttract()") + + ##### playing state ##### + + def enterPlaying(self, ts): + assert self.debugPrint("enterPlaying()") + + def exitPlaying(self): + assert self.debugPrint("exitPlaying()") + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(self.__dict__.get('entId', '?'))+' '+message) diff --git a/otp/src/level/DistributedInteractiveEntityAI.py b/otp/src/level/DistributedInteractiveEntityAI.py new file mode 100644 index 0000000..160193f --- /dev/null +++ b/otp/src/level/DistributedInteractiveEntityAI.py @@ -0,0 +1,138 @@ +""" DistributedInteractiveEntityAI module: contains the DistributedInteractiveEntityAI + class, the server side representation of a simple, animated, interactive + prop.""" + + +from otp.ai.AIBaseGlobal import * +from direct.distributed.ClockDelta import * + +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +import DistributedEntityAI +from direct.fsm import State + + +class DistributedInteractiveEntityAI(DistributedEntityAI.DistributedEntityAI): + """ + DistributedInteractiveEntityAI class: The server side representation of + an animated prop. This is the object that remembers what the + prop is doing. The child of this object, the DistributedAnimatedProp + object, is the client side version and updates the display that + client's display based on the state of the prop. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInteractiveEntityAI') + + def __init__(self, level, entId): + """entId: a unique identifier for this prop.""" + DistributedEntityAI.DistributedEntityAI.__init__(self, level, entId) + assert self.debugPrint( + "DistributedInteractiveEntityAI(entId=%s)"%(entId)) + self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntityAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['playing']), + # Attract is an idle mode. It is named attract + # because the prop is not interacting with an + # avatar, and is therefore trying to attract an + # avatar. + State.State('attract', + self.enterAttract, + self.exitAttract, + ['playing']), + # Playing is for when an avatar is interacting + # with the prop. + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['attract'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + self.avatarId=0 + + + def delete(self): + del self.fsm + DistributedEntityAI.DistributedEntityAI.delete(self) + + def getAvatarInteract(self): + assert self.debugPrint("getAvatarInteract() returning: %s"%(self.avatarId,)) + return self.avatarId + + #def getOwnerDoId(self): + # assert self.debugPrint("getOwnerDoId() returning: %s"%(self.ownerDoId,)) + # return self.ownerDoId + + def requestInteract(self): + assert self.debugPrint("requestInteract()") + avatarId = self.air.getAvatarIdFromSender() + assert self.notify.debug(" avatarId:%s"%(avatarId,)) + stateName = self.fsm.getCurrentState().getName() + if stateName != 'playing': + self.sendUpdate("setAvatarInteract", [avatarId]) + self.avatarId=avatarId + self.fsm.request('playing') + else: + self.sendUpdateToAvatarId(avatarId, "rejectInteract", []) + + def requestExit(self): + assert self.debugPrint("requestExit()") + avatarId = self.air.getAvatarIdFromSender() + assert self.notify.debug(" avatarId:%s"%(avatarId,)) + if avatarId==self.avatarId: + stateName = self.fsm.getCurrentState().getName() + if stateName == 'playing': + self.sendUpdate("avatarExit", [avatarId]) + self.fsm.request('attract') + else: + assert self.notify.debug(" requestExit: invalid avatarId") + + def getState(self): + r = [ + self.fsm.getCurrentState().getName(), + globalClockDelta.getRealNetworkTime()] + assert self.debugPrint("getState() returning %s"%(r,)) + return r + + def sendState(self): + assert self.debugPrint("sendState()") + self.sendUpdate('setState', self.getState()) + + ##### off state ##### + + def enterOff(self): + assert self.debugPrint("enterOff()") + #self.setState('off') + + def exitOff(self): + assert self.debugPrint("exitOff()") + + ##### attract state ##### + + def enterAttract(self): + assert self.debugPrint("enterAttract()") + self.sendState() + + def exitAttract(self): + assert self.debugPrint("exitAttract()") + + ##### open state ##### + + def enterPlaying(self): + assert self.debugPrint("enterPlaying()") + self.sendState() + + def exitPlaying(self): + assert self.debugPrint("exitPlaying()") + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(self.__dict__.get('entId', '?'))+' '+message) + diff --git a/otp/src/level/DistributedLevel.py b/otp/src/level/DistributedLevel.py new file mode 100644 index 0000000..e0f606b --- /dev/null +++ b/otp/src/level/DistributedLevel.py @@ -0,0 +1,880 @@ +"""DistributedLevel.py: contains the DistributedLevel class""" + +from direct.distributed.ClockDelta import * +from pandac.PandaModules import * +from direct.showbase.PythonUtil import Functor, sameElements, list2dict, uniqueElements +from direct.interval.IntervalGlobal import * +from toontown.distributed.ToontownMsgTypes import * +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPGlobals +from direct.distributed import DistributedObject +import Level +import LevelConstants +from direct.directnotify import DirectNotifyGlobal +import EntityCreator +from direct.gui import OnscreenText +from direct.task import Task +import LevelUtil +import random + +class DistributedLevel(DistributedObject.DistributedObject, + Level.Level): + """DistributedLevel""" + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel') + + WantVisibility = config.GetBool('level-visibility', 1) + # set this to true to get all distrib objs when showing hidden zones + ColorZonesAllDOs = 0 + + # TODO: move level-model stuff to LevelMgr or FactoryLevelMgr? + FloorCollPrefix = 'zoneFloor' + + OuchTaskName = 'ouchTask' + VisChangeTaskName = 'visChange' + + # Override and set this to False to prevent the level from placing + # the avatar at the origin of a random zone in the absence of an + # entrancePoint entity. + EmulateEntrancePoint = True + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + Level.Level.__init__(self) + self.lastToonZone = None + self.lastCamZone = 0 + self.titleColor = (1,1,1,1) + self.titleText = OnscreenText.OnscreenText( + "", + fg = self.titleColor, + shadow = (0,0,0,1), + font = ToontownGlobals.getSuitFont(), + pos = (0,-0.5), + scale = 0.16, + drawOrder = 0, + mayChange = 1, + ) + + self.smallTitleText = OnscreenText.OnscreenText( + "", + fg = self.titleColor, + font = ToontownGlobals.getSuitFont(), + pos = (0.65,0.9), + scale = 0.08, + drawOrder = 0, + mayChange = 1, + bg = (.5,.5,.5,.5), + align = TextNode.ARight, + ) + self.zonesEnteredList = [] + self.fColorZones = 0 + self.scenarioIndex = 0 + + def generate(self): + DistributedLevel.notify.debug('generate') + DistributedObject.DistributedObject.generate(self) + + # this dict stores entity reparents if the parent hasn't been + # created yet + self.parent2pendingChildren = {} + + # if the AI sends us a full spec, it will be put here + self.curSpec = None + + # Most (if not all) of the timed entities of levels + # run on looping intervals that are started once based on + # the level's start time. + # This sync request is *NOT* guaranteed to finish by the time + # the entities get created. + # We should listen for any and all time-sync events and re-sync + # all our entities at that time. + if base.cr.timeManager is not None: + base.cr.timeManager.synchronize('DistributedLevel.generate') + else: + self.notify.warning('generate(): no TimeManager!') + + + # the real required fields + def setLevelZoneId(self, zoneId): + # this is the zone that the level is in; we should listen to this + # zone the entire time we're in here + self.levelZone = zoneId + + def setPlayerIds(self, avIdList): + self.avIdList = avIdList + assert base.localAvatar.doId in self.avIdList + + def setEntranceId(self, entranceId): + self.entranceId = entranceId + + def getEntranceId(self): + return self.entranceId + + # "required" fields (these ought to be required fields, but + # the AI level obj doesn't know the data values until it has been + # generated.) + def setZoneIds(self, zoneIds): + DistributedLevel.notify.debug('setZoneIds: %s' % zoneIds) + self.zoneIds = zoneIds + + def setStartTimestamp(self, timestamp): + DistributedLevel.notify.debug('setStartTimestamp: %s' % timestamp) + self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32) + + # ugly hack: we treat a few DC fields as if they were required, + # and use 'levelAnnounceGenerate()' in place of regular old + # announceGenerate(). Note that we have to call + # gotAllRequired() in the last 'faux-required' DC update + # handler. If you add another field, move this to the last one. + self.privGotAllRequired() + + """ + # this is no longer used + def setScenarioIndex(self, scenarioIndex): + self.scenarioIndex = scenarioIndex + + # ugly hack: we treat a few DC fields as if they were required, + # and use 'levelAnnounceGenerate()' in place of regular old + # announceGenerate(). Note that we have to call + # gotAllRequired() in the last 'faux-required' DC update + # handler. If you add another field, move this to the last one. + self.privGotAllRequired() + """ + + def privGotAllRequired(self): + self.levelAnnounceGenerate() + def levelAnnounceGenerate(self): + pass + + def initializeLevel(self, levelSpec): + """subclass should call this as soon as it's located its level spec. + Must be called after obj has been generated.""" + if __dev__: + # if we're in dev, give the server the opportunity to send us + # a full spec + self.candidateSpec = levelSpec + self.sendUpdate('requestCurrentLevelSpec', + [hash(levelSpec), + levelSpec.entTypeReg.getHashStr()]) + else: + self.privGotSpec(levelSpec) + + if __dev__: + def reportModelSpecSyncError(self, msg): + DistributedLevel.notify.error( + '%s\n' + '\n' + 'your spec does not match the level model\n' + 'use SpecUtil.updateSpec, then restart your AI and client' % + (msg)) + + def setSpecDeny(self, reason): + DistributedLevel.notify.error(reason) + + def setSpecSenderDoId(self, doId): + DistributedLevel.notify.debug('setSpecSenderDoId: %s' % doId) + blobSender = base.cr.doId2do[doId] + + def setSpecBlob(specBlob, blobSender=blobSender, self=self): + blobSender.sendAck() + from LevelSpec import LevelSpec + spec = eval(specBlob) + if spec is None: + spec = self.candidateSpec + del self.candidateSpec + self.privGotSpec(spec) + + if blobSender.isComplete(): + setSpecBlob(blobSender.getBlob()) + else: + evtName = self.uniqueName('specDone') + blobSender.setDoneEvent(evtName) + self.acceptOnce(evtName, setSpecBlob) + + def privGotSpec(self, levelSpec): + Level.Level.initializeLevel(self, self.doId, levelSpec, + self.scenarioIndex) + + # all of the local entities have been created now. + # TODO: have any of the distributed entities been created at this point? + + # there should not be any pending reparents left at this point + # TODO: is it possible for a local entity to be parented to a + # distributed entity? I think so! + # Yes, it is. Don't do this check. + #assert len(self.parent2pendingChildren) == 0 + # make sure the zoneNums from the model match the zoneNums from + # the zone entities + modelZoneNums = self.zoneNums + specZoneNums = self.zoneNum2zoneId.keys() + if not sameElements(modelZoneNums, specZoneNums): + self.reportModelSpecSyncError( + 'model zone nums (%s) do not match spec zone nums (%s)' % + (modelZoneNums, specZoneNums)) + + # load stuff + self.initVisibility() + self.placeLocalToon() + + def announceLeaving(self): + """call this just before leaving the level; this may result in + the factory being destroyed on the AI""" + DistributedLevel.notify.debug('announceLeaving') + self.doneBarrier() + + def placeLocalToon(self, moveLocalAvatar=True): + initialZoneEnt = None + # the entrancePoint entities register themselves with us + if self.entranceId in self.entranceId2entity: + epEnt = self.entranceId2entity[self.entranceId] + if moveLocalAvatar: + epEnt.placeToon(base.localAvatar, + self.avIdList.index(base.localAvatar.doId), + len(self.avIdList)) + initialZoneEnt = self.getEntity(epEnt.getZoneEntId()) + elif self.EmulateEntrancePoint: + self.notify.debug('unknown entranceId %s' % self.entranceId) + if moveLocalAvatar: + base.localAvatar.reparentTo(render) + base.localAvatar.setPosHpr(0,0,0,0,0,0) + self.notify.debug('showing all zones') + self.setColorZones(1) + # put the toon in a random zone to start + zoneEntIds = list(self.entType2ids['zone']) + zoneEntIds.remove(LevelConstants.UberZoneEntId) + if len(zoneEntIds): + zoneEntId = random.choice(zoneEntIds) + initialZoneEnt = self.getEntity(zoneEntId) + if moveLocalAvatar: + base.localAvatar.setPos( + render, + initialZoneEnt.getZoneNode().getPos(render)) + else: + initialZoneEnt = self.getEntity( + LevelConstants.UberZoneEntId) + if moveLocalAvatar: + base.localAvatar.setPos(render,0,0,0) + + if initialZoneEnt is not None: + # kickstart the visibility + self.enterZone(initialZoneEnt.entId) + + def createEntityCreator(self): + """Create the object that will be used to create Entities. + Inheritors, override if desired.""" + return EntityCreator.EntityCreator(level=self) + + def onEntityTypePostCreate(self, entType): + """listen for certain entity types to be created""" + Level.Level.onEntityTypePostCreate(self, entType) + # NOTE: these handlers are private in order to avoid overriding + # similar handlers in base classes + if entType == 'levelMgr': + self.__handleLevelMgrCreated() + + def __handleLevelMgrCreated(self): + # as soon as the levelMgr has been created, load up the model + # and extract zone info. We need to do this before any entities + # get parented to the level! + levelMgr = self.getEntity(LevelConstants.LevelMgrEntId) + self.geom = levelMgr.geom + + # find the zones in the model and fix them up + self.zoneNum2node = LevelUtil.getZoneNum2Node(self.geom) + + self.zoneNums = self.zoneNum2node.keys() + self.zoneNums.sort() + self.zoneNumDict = list2dict(self.zoneNums) + DistributedLevel.notify.debug('zones from model: %s' % self.zoneNums) + + # give the level a chance to muck with the model before the entities + # get placed + self.fixupLevelModel() + + def fixupLevelModel(self): + # fix up the floor collisions for walkable zones *before* + # any entities get put under the model + for zoneNum,zoneNode in self.zoneNum2node.items(): + # don't do this to the uberzone + if zoneNum == LevelConstants.UberZoneEntId: + continue + # if this is a walkable zone, fix up the model + allColls = zoneNode.findAllMatches('**/+CollisionNode') + # which of them, if any, are floors? + floorColls = [] + for coll in allColls: + bitmask = coll.node().getIntoCollideMask() + if not (bitmask & ToontownGlobals.FloorBitmask).isZero(): + floorColls.append(coll) + if len(floorColls) > 0: + # rename the floor collision nodes, and make sure no other + # nodes under the ZoneNode have that name + floorCollName = '%s%s' % (DistributedLevel.FloorCollPrefix, + zoneNum) + others = zoneNode.findAllMatches( + '**/%s' % floorCollName) + for other in others: + other.setName('%s_renamed' % floorCollName) + for floorColl in floorColls: + floorColl.setName(floorCollName) + + # listen for zone enter events from floor collisions + def handleZoneEnter(collisionEntry, + self=self, zoneNum=zoneNum): + self.toonEnterZone(zoneNum) + floorNode = collisionEntry.getIntoNode() + if floorNode.hasTag('ouch'): + ouchLevel = int(self.getFloorOuchLevel()) + self.startOuch(ouchLevel) + self.accept('enter%s' % floorCollName, handleZoneEnter) + + # also listen for zone exit events for the sake of the + # ouch system + def handleZoneExit(collisionEntry, + self=self, zoneNum=zoneNum): + floorNode = collisionEntry.getIntoNode() + if floorNode.hasTag('ouch'): + self.stopOuch() + self.accept('exit%s' % floorCollName, handleZoneExit) + + def getFloorOuchLevel(self): + # override this to make dangerous ground do more damage + return 1 + + def announceGenerate(self): + DistributedLevel.notify.debug('announceGenerate') + DistributedObject.DistributedObject.announceGenerate(self) + + def disable(self): + DistributedLevel.notify.debug('disable') + + # geom is owned by the levelMgr + if hasattr(self, 'geom'): + del self.geom + + self.shutdownVisibility() + self.destroyLevel() + self.ignoreAll() + + # NOTE: this should be moved to FactoryInterior + taskMgr.remove(self.uniqueName("titleText")) + if self.smallTitleText: + self.smallTitleText.cleanup() + self.smallTitleText = None + if self.titleText: + self.titleText.cleanup() + self.titleText = None + self.zonesEnteredList = [] + + DistributedObject.DistributedObject.disable(self) + + def delete(self): + DistributedLevel.notify.debug('delete') + DistributedObject.DistributedObject.delete(self) + # make sure the ouch task is stopped + self.stopOuch() + + def requestReparent(self, entity, parentId, wrt=False): + if __debug__: + # some things (like cogs) are not actually entities yet; + # they don't have an entId. Big deal, let it go through. + if hasattr(entity, 'entId'): + assert entity.entId != parentId + parent = self.getEntity(parentId) + if parent is not None: + # parent has already been created + if wrt: + entity.wrtReparentTo(parent.getNodePath()) + else: + entity.reparentTo(parent.getNodePath()) + else: + # parent hasn't been created yet; schedule the reparent + DistributedLevel.notify.debug( + 'entity %s requesting reparent to %s, not yet created' % + (entity, parentId)) + + entity.reparentTo(hidden) + + # if this parent doesn't already have another child pending, + # do some setup + if not self.parent2pendingChildren.has_key(parentId): + self.parent2pendingChildren[parentId] = [] + + # do the reparent(s) once the parent is initialized + def doReparent(parentId=parentId, self=self, wrt=wrt): + assert self.parent2pendingChildren.has_key(parentId) + parent=self.getEntity(parentId) + for child in self.parent2pendingChildren[parentId]: + DistributedLevel.notify.debug( + 'performing pending reparent of %s to %s' % + (child, parent)) + if wrt: + child.wrtReparentTo(parent.getNodePath()) + else: + child.reparentTo(parent.getNodePath()) + del self.parent2pendingChildren[parentId] + self.ignore(self.getEntityCreateEvent(parentId)) + + self.accept(self.getEntityCreateEvent(parentId), doReparent) + + self.parent2pendingChildren[parentId].append(entity) + + def getZoneNode(self, zoneEntId): + return self.zoneNum2node.get(zoneEntId) + + def warpToZone(self, zoneNum): + """put avatar at the origin of the given zone""" + zoneNode = self.getZoneNode(zoneNum) + if zoneNode is None: + return + base.localAvatar.setPos(zoneNode,0,0,0) + base.localAvatar.setHpr(zoneNode,0,0,0) + self.enterZone(zoneNum) + + def showZone(self, zoneNum): + zone = self.getZoneNode(zoneNum) + zone.unstash() + zone.clearColor() + + def setColorZones(self, fColorZones): + self.fColorZones = fColorZones + self.resetVisibility() + + def getColorZones(self): + return self.fColorZones + + def hideZone(self, zoneNum): + zone = self.getZoneNode(zoneNum) + if self.fColorZones: + zone.unstash() + zone.setColor(1,0,0) + else: + zone.stash() + + def setTransparency(self, alpha, zone=None): + self.geom.setTransparency(1) + if zone is None: + node = self.geom + else: + node = self.getZoneNode(zoneNum) + node.setAlphaScale(alpha) + + def initVisibility(self): + # start out with every zone visible, since none of the zones have + # been hidden + self.curVisibleZoneNums = list2dict(self.zoneNums) + # the UberZone is always visible, so it's not included in the + # zones' viz lists + del self.curVisibleZoneNums[LevelConstants.UberZoneEntId] + # we have not entered any zone yet + self.curZoneNum = None + + self.visChangedThisFrame = 0 + self.fForceSetZoneThisFrame = 0 + + # listen for camera-ray/floor collision events + def handleCameraRayFloorCollision(collEntry, self=self): + name = collEntry.getIntoNode().getName() + self.notify.debug('camera floor ray collided with: %s' % name) + prefixLen = len(DistributedLevel.FloorCollPrefix) + if (name[:prefixLen] == DistributedLevel.FloorCollPrefix): + try: + zoneNum = int(name[prefixLen:]) + except: + DistributedLevel.notify.warning( + 'Invalid zone floor collision node: %s' + % name) + else: + self.camEnterZone(zoneNum) + self.accept('on-floor', handleCameraRayFloorCollision) + + # if no viz, listen to all the zones + if not DistributedLevel.WantVisibility: + zoneNums = list(self.zoneNums) + zoneNums.remove(LevelConstants.UberZoneEntId) + # make sure a setZone goes out on the first frame + self.forceSetZoneThisFrame() + self.setVisibility(zoneNums) + + # send out any zone changes at the end of the frame, just before + # rendering + taskMgr.add(self.visChangeTask, + self.uniqueName(DistributedLevel.VisChangeTaskName), + priority=49) + + def shutdownVisibility(self): + taskMgr.remove(self.uniqueName(DistributedLevel.VisChangeTaskName)) + + def toonEnterZone(self, zoneNum, ouchLevel=None): + """ + zoneNum is an int. + ouchLevel is a ??. + + The avatar (and not necessarily the camera) has entered + a zone. + See camEnterZone() + """ + DistributedLevel.notify.debug('toonEnterZone%s' % zoneNum) + + if zoneNum != self.lastToonZone: + self.lastToonZone = zoneNum + self.notify.debug("toon is standing in zone %s" % zoneNum) + messenger.send("factoryZoneChanged", [zoneNum]) + + def camEnterZone(self, zoneNum): + """ + zoneNum is an int. + + The camera (and not necessarily the avatar) has entered + a zone. + See toonEnterZone() + """ + DistributedLevel.notify.debug('camEnterZone%s' % zoneNum) + self.enterZone(zoneNum) + + if zoneNum != self.lastCamZone: + self.lastCamZone = zoneNum + self.smallTitleText.hide() + self.spawnTitleText() + + def lockVisibility(self, zoneNum=None, zoneId=None): + """call this to lock the visibility to a particular zone + pass in either network zoneId or zoneNum + + this was added for battles in the HQ factories; if you engage a suit + in zone A with your camera in zone B, and you don't call this func, + your client will remain in zone B. If there's a door between A and B, + and it closes, zone B might disappear, along with the suit and the + battle objects. + """ + assert zoneNum is None or zoneId is None + assert not ((zoneNum is None) and (zoneId is None)) + if zoneId is not None: + zoneNum = self.getZoneNumFromId(zoneId) + + self.notify.debug('lockVisibility to zoneNum %s' % zoneNum) + self.lockVizZone = zoneNum + self.enterZone(self.lockVizZone) + + def unlockVisibility(self): + """release the visibility lock""" + self.notify.debug('unlockVisibility') + if not hasattr(self, 'lockVizZone'): + self.notify.warning('visibility already unlocked') + else: + del self.lockVizZone + self.updateVisibility() + + + def enterZone(self, zoneNum): + DistributedLevel.notify.debug("entering zone %s" % zoneNum) + + if not DistributedLevel.WantVisibility: + return + + if zoneNum == self.curZoneNum: + return + + if zoneNum not in self.zoneNumDict: + DistributedLevel.notify.error( + 'no ZoneEntity for this zone (%s)!!' % zoneNum) + + self.updateVisibility(zoneNum) + + def updateVisibility(self, zoneNum=None): + """update the visibility assuming that we're in the specified + zone; don't check to see if it's the zone we're already in""" + #self.notify.debug('updateVisibility %s' % globalClock.getFrameCount()) + if zoneNum is None: + zoneNum = self.curZoneNum + if zoneNum is None: + return + if hasattr(self, 'lockVizZone'): + zoneNum = self.lockVizZone + + zoneEnt = self.getEntity(zoneNum) + # use dicts to efficiently ensure that there are no duplicates + visibleZoneNums = list2dict([zoneNum]) + visibleZoneNums.update(list2dict(zoneEnt.getVisibleZoneNums())) + + if not __debug__: + # HACK + # make sure that the visibility list includes the zone that the toon + # is standing in + if self.lastToonZone not in visibleZoneNums: + # make sure there IS a last zone + if self.lastToonZone is not None: + self.notify.warning( + 'adding zoneNum %s to visibility list ' + 'because toon is standing in that zone!' % + self.lastToonZone) + visibleZoneNums.update(list2dict([self.lastToonZone])) + + # we should not have the uberZone in the list at this point + zoneEntIds = list(self.entType2ids['zone']) + zoneEntIds.remove(LevelConstants.UberZoneEntId) + if len(zoneEntIds): + assert not LevelConstants.UberZoneEntId in visibleZoneNums + + # this flag will prevent a network msg from being sent if + # the list of visible zones has not changed + vizZonesChanged = 1 + # figure out which zones are new and which are going invisible + # use dicts because 'x in dict' is faster than 'x in list' + addedZoneNums = [] + removedZoneNums = [] + allVZ = dict(visibleZoneNums) + allVZ.update(self.curVisibleZoneNums) + for vz,dummy in allVZ.items(): + new = vz in visibleZoneNums + old = vz in self.curVisibleZoneNums + if new and old: + continue + if new: + addedZoneNums.append(vz) + else: + removedZoneNums.append(vz) + + if (not addedZoneNums) and (not removedZoneNums): + DistributedLevel.notify.debug( + 'visible zone list has not changed') + vizZonesChanged = 0 + else: + # show the new, hide the old + DistributedLevel.notify.debug('showing zones %s' % + addedZoneNums) + for az in addedZoneNums: + self.showZone(az) + DistributedLevel.notify.debug('hiding zones %s' % + removedZoneNums) + for rz in removedZoneNums: + self.hideZone(rz) + + # it's important for us to send a setZone request on the first + # frame, whether or not the visibility is different from what + # we already have + if vizZonesChanged or self.fForceSetZoneThisFrame: + self.setVisibility(visibleZoneNums.keys()) + self.fForceSetZoneThisFrame = 0 + + self.curZoneNum = zoneNum + self.curVisibleZoneNums = visibleZoneNums + + def setVisibility(self, vizList): + """ + vizList is a list of visible zone numbers. + """ + # if we're showing all zones, get all the DOs + if self.fColorZones and DistributedLevel.ColorZonesAllDOs: + vizList = list(self.zoneNums) + vizList.remove(LevelConstants.UberZoneEntId) + # convert the zone numbers into their actual zoneIds + # always include Toontown and factory uberZones + uberZone = self.getZoneId(LevelConstants.UberZoneEntId) + # the level itself is in the 'level zone' + visibleZoneIds = [OTPGlobals.UberZone, self.levelZone, uberZone] + for vz in vizList: + if vz is not LevelConstants.UberZoneEntId: + visibleZoneIds.append(self.getZoneId(vz)) + assert uniqueElements(visibleZoneIds) + DistributedLevel.notify.debug('new viz list: %s' % visibleZoneIds) + + base.cr.sendSetZoneMsg(self.levelZone, visibleZoneIds) + + def resetVisibility(self): + # start out with every zone visible, since none of the zones have + # been hidden + self.curVisibleZoneNums = list2dict(self.zoneNums) + # the UberZone is always visible, so it's not included in the + # zones' viz lists + del self.curVisibleZoneNums[LevelConstants.UberZoneEntId] + # Make sure every zone is visible + for vz,dummy in self.curVisibleZoneNums.items(): + self.showZone(vz) + # Redo visibility using current zone num + self.updateVisibility() + + def handleVisChange(self): + """the zone visibility lists have changed on-the-fly""" + Level.Level.handleVisChange(self) + self.visChangedThisFrame = 1 + + def forceSetZoneThisFrame(self): + # call this to ensure that a setZone call will be generated this frame + self.fForceSetZoneThisFrame = 1 + + def visChangeTask(self, task): + # this runs just before igLoop; if viz lists have changed + # this frame, updates the visibility and sends out a setZoneMsg + if self.visChangedThisFrame or self.fForceSetZoneThisFrame: + self.updateVisibility() + self.visChangedThisFrame = 0 + return Task.cont + + if __dev__: + # level editing stuff + def setAttribChange(self, entId, attribName, valueStr, username): + """every time the spec is edited, we get this message + from the AI""" + value = eval(valueStr) + self.levelSpec.setAttribChange(entId, attribName, value, username) + + def spawnTitleText(self): + def getDescription(zoneNum, self=self): + ent = self.entities.get(zoneNum) + if ent and hasattr(ent, 'description'): + return ent.description + return None + + description = getDescription(self.lastCamZone) + if description and description != '': + taskMgr.remove(self.uniqueName("titleText")) + self.smallTitleText.setText(description) + self.titleText.setText(description) + self.titleText.setColor(Vec4(*self.titleColor)) + self.titleText.setFg(self.titleColor) + + # Only show the big title once per session. + # If we've already seen it, just show the small title + + titleSeq = None + if not self.lastCamZone in self.zonesEnteredList: + self.zonesEnteredList.append(self.lastCamZone) + titleSeq = Task.sequence( + Task.Task(self.hideSmallTitleTextTask), + Task.Task(self.showTitleTextTask), + Task.pause(0.1), + Task.pause(6.0), + self.titleText.lerpColor(Vec4(self.titleColor[0], + self.titleColor[1], + self.titleColor[2], + self.titleColor[3]), + Vec4(self.titleColor[0], + self.titleColor[1], + self.titleColor[2], + 0.0), + 0.5), + ) + smallTitleSeq = Task.sequence(Task.Task(self.hideTitleTextTask), + Task.Task(self.showSmallTitleTask)) + if titleSeq: + seq = Task.sequence(titleSeq, smallTitleSeq) + else: + seq = smallTitleSeq + taskMgr.add(seq, self.uniqueName("titleText")) + + + def showInfoText(self, text = "hello world"): + description = text + if description and description != '': + taskMgr.remove(self.uniqueName("titleText")) + self.smallTitleText.setText(description) + self.titleText.setText(description) + self.titleText.setColor(Vec4(*self.titleColor)) + self.titleText.setFg(self.titleColor) + + # Only show the big title once per session. + # If we've already seen it, just show the small title + + titleSeq = None + titleSeq = Task.sequence( + Task.Task(self.hideSmallTitleTextTask), + Task.Task(self.showTitleTextTask), + Task.pause(0.1), + Task.pause(3.0), + self.titleText.lerpColor(Vec4(self.titleColor[0], + self.titleColor[1], + self.titleColor[2], + self.titleColor[3]), + Vec4(self.titleColor[0], + self.titleColor[1], + self.titleColor[2], + 0.0), + 0.5), + ) + + if titleSeq: + seq = Task.sequence(titleSeq) + taskMgr.add(seq, self.uniqueName("titleText")) + + def showTitleTextTask(self, task): + assert DistributedLevel.notify.debug("hideTitleTextTask()") + self.titleText.show() + return Task.done + + def hideTitleTextTask(self, task): + assert DistributedLevel.notify.debug("hideTitleTextTask()") + if self.titleText: + self.titleText.hide() + return Task.done + + def showSmallTitleTask(self, task): + # make sure large title is hidden + if self.titleText: + self.titleText.hide() + # show the small title + self.smallTitleText.show() + return Task.done + + def hideSmallTitleTextTask(self, task): + assert DistributedLevel.notify.debug("hideTitleTextTask()") + if self.smallTitleText: + self.smallTitleText.hide() + return Task.done + + # Ouch! + def startOuch(self, ouchLevel, period=2): + self.notify.debug('startOuch %s' % ouchLevel) + if not hasattr(self, 'doingOuch'): + def doOuch(task, self=self, ouchLevel=ouchLevel, period=period): + self.b_setOuch(ouchLevel) + self.lastOuchTime = globalClock.getFrameTime() + taskMgr.doMethodLater(period, doOuch, + DistributedLevel.OuchTaskName) + + # check to make sure we haven't done an ouch too recently + delay = 0 + if hasattr(self, 'lastOuchTime'): + curFrameTime = globalClock.getFrameTime() + timeSinceLastOuch = (curFrameTime - self.lastOuchTime) + if timeSinceLastOuch < period: + delay = period - timeSinceLastOuch + + if delay > 0: + taskMgr.doMethodLater( + period, doOuch, + DistributedLevel.OuchTaskName) + else: + doOuch(None) + self.doingOuch = 1 + + def stopOuch(self): + if hasattr(self, 'doingOuch'): + taskMgr.remove(DistributedLevel.OuchTaskName) + del self.doingOuch + + def b_setOuch(self, penalty, anim=None): + self.notify.debug('b_setOuch %s' % penalty) + av = base.localAvatar + + # play the stun track (flashing toon) + if not av.isStunned: + self.d_setOuch(penalty) + self.setOuch(penalty, anim) + + def d_setOuch(self, penalty): + self.sendUpdate("setOuch", [penalty]) + + def setOuch(self, penalty, anim = None): + if anim == "Squish": + if base.cr.playGame.getPlace(): + base.cr.playGame.getPlace().fsm.request('squished') + elif anim == "Fall": + if base.cr.playGame.getPlace(): + base.cr.playGame.getPlace().fsm.request('fallDown') + + av = base.localAvatar + av.stunToon() + av.playDialogueForString("!") + + def complexVis(self): + return 1 diff --git a/otp/src/level/DistributedLevelAI.py b/otp/src/level/DistributedLevelAI.py new file mode 100644 index 0000000..3dc2f7e --- /dev/null +++ b/otp/src/level/DistributedLevelAI.py @@ -0,0 +1,244 @@ +"""DistributedLevelAI.py: contains the DistributedLevelAI class""" + +from otp.ai.AIBaseGlobal import * +from direct.distributed.ClockDelta import * +from direct.distributed import DistributedObjectAI +import Level +from direct.directnotify import DirectNotifyGlobal +import EntityCreatorAI +from direct.showbase.PythonUtil import Functor, weightedChoice + +class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, + Level.Level): + """DistributedLevelAI""" + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI') + + def __init__(self, air, zoneId, entranceId, avIds): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + Level.Level.__init__(self) + # these are required fields + self.zoneId = zoneId + self.entranceId = entranceId + + if len(avIds) <= 0 or len(avIds) > 4: + self.notify.warning('How do we have this many avIds? avIds: %s' %avIds) + assert len(avIds) > 0 and len(avIds) <= 4 + assert 0 not in avIds + assert None not in avIds + self.avIdList = avIds + self.numPlayers = len(self.avIdList) + # this is the list of avatars that are actually present + self.presentAvIds = list(self.avIdList) + self.notify.debug("expecting avatars: %s" % str(self.avIdList)) + + if __dev__: + self.modified = 0 + + def setLevelSpec(self, levelSpec): + self.levelSpec = levelSpec + + def generate(self, levelSpec = None): + self.notify.debug('generate') + DistributedObjectAI.DistributedObjectAI.generate(self) + + if levelSpec == None: + levelSpec = self.levelSpec + + self.initializeLevel(levelSpec) + + # self.zoneIds comes from LevelMgrAI + self.sendUpdate('setZoneIds', [self.zoneIds]) + self.sendUpdate('setStartTimestamp', [self.startTimestamp]) + # this is no longer used + #self.sendUpdate('setScenarioIndex', [self.scenarioIndex]) + if __dev__: + assert self.scenarioIndex == 0 + + def getLevelZoneId(self): + """no entities should be generated in the level's zone; it causes + nasty race conditions on the client if there are entities in the + same zone with the level""" + return self.zoneId + + def getPlayerIds(self): + return self.avIdList + + def getEntranceId(self): + return self.entranceId + + def getBattleCreditMultiplier(self): + return 1. + + def delete(self, deAllocZone=True): + self.notify.debug('delete') + if __dev__: + self.removeAutosaveTask() + self.destroyLevel() + self.ignoreAll() + if deAllocZone: + self.air.deallocateZone(self.zoneId) + DistributedObjectAI.DistributedObjectAI.delete(self) + + def initializeLevel(self, levelSpec): + # record the level's start time so that we can sync the clients + self.startTime = globalClock.getRealTime() + self.startTimestamp = globalClockDelta.localToNetworkTime( + self.startTime, bits=32) + + # choose a scenario + # make list of lists: [(weight, scenarioIndex), ...] + lol = zip([1] * levelSpec.getNumScenarios(), + range(levelSpec.getNumScenarios())) + scenarioIndex = weightedChoice(lol) + + Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex) + + if __dev__: + # listen for requests to save the spec + self.accept(self.editMgrEntity.getSpecSaveEvent(), self.saveSpec) + + # listen for avatar disconnects + for avId in self.avIdList: + self.acceptOnce(self.air.getAvatarExitEvent(avId), + Functor(self.handleAvatarDisconnect, avId)) + + # set up a barrier that will clear when all avs have left or + # disconnected + self.allToonsGoneBarrier = self.beginBarrier( + 'allToonsGone', self.avIdList, 3*24*60*60, self.allToonsGone) + + def handleAvatarDisconnect(self, avId): + try: + self.presentAvIds.remove(avId) + DistributedLevelAI.notify.warning('av %s has disconnected' % avId) + except: + DistributedLevelAI.notify.warning( + 'got disconnect for av %s, not in list' % avId) + if not self.presentAvIds: + self.allToonsGone([]) + + def allToonsGone(self, toonsThatCleared): + DistributedLevelAI.notify.info('allToonsGone') + if hasattr(self, 'allToonsGoneBarrier'): + self.ignoreBarrier(self.allToonsGoneBarrier) + del self.allToonsGoneBarrier + for avId in self.avIdList: + self.ignore(self.air.getAvatarExitEvent(avId)) + self.requestDelete() + + def createEntityCreator(self): + """Create the object that will be used to create Entities. + Inheritors, override if desired.""" + return EntityCreatorAI.EntityCreatorAI(level=self) + + def setOuch(self, penalty): + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + self.notify.debug("setOuch %s" % penalty) + # make sure penalty is > 0 + if av and (penalty > 0): + av.takeDamage(penalty) + # this should be done in DistributedToonAI; do we ever go sad + # without losing our gags? + if av.getHp() <= 0: + av.inventory.zeroInv() + av.d_setInventory(av.inventory.makeNetString()) + + def requestCurrentLevelSpec(self, specHash, entTypeRegHash): + senderId = self.air.getAvatarIdFromSender() + + self.notify.info('av %s: specHash %s, entTypeRegHash %s' % + (senderId, specHash, entTypeRegHash)) + + if not __dev__: + # client is running in dev mode and we're not; that won't fly + self.notify.info("client is in dev mode and we are not") + self.sendUpdateToAvatarId( + senderId, 'setSpecDeny', + ['AI server is not running in dev mode. ' + 'Set want-dev to false on your client or true on the AI.']) + return + + # first check the typeReg hash -- if it doesn't match, the + # client should not be connecting. Their entityTypeRegistry + # is different from ours. + srvHash = self.levelSpec.entTypeReg.getHashStr() + self.notify.info('srv entTypeRegHash %s' % srvHash) + if srvHash != entTypeRegHash: + self.sendUpdateToAvatarId( + senderId, 'setSpecDeny', + ['EntityTypeRegistry hashes do not match! ' + '(server:%s, client:%s' % (srvHash, entTypeRegHash)]) + return + + # now compare the hashes of the client and server specs + if hash(self.levelSpec) != specHash: + self.notify.info('spec hashes do not match, sending our spec') + spec = self.levelSpec + useDisk=simbase.config.GetBool('spec-by-disk', 1) + else: + self.notify.info('spec hashes match, sending null spec') + spec = None + # don't need to hit disk if we're just sending 'None' over the wire + useDisk = 0 + specStr = repr(spec) + + from direct.directutil import DistributedLargeBlobSenderAI + largeBlob = DistributedLargeBlobSenderAI.\ + DistributedLargeBlobSenderAI( + self.air, self.zoneId, senderId, specStr, + useDisk=useDisk) + self.sendUpdateToAvatarId(senderId, + 'setSpecSenderDoId', [largeBlob.doId]) + + if __dev__: + # level editors should call this func to tweak attributes of level + # entities + def setAttribChange(self, entId, attribName, value, username='SYSTEM'): + DistributedLevelAI.notify.info( + "setAttribChange(%s): %s, %s = %s" % + (username, entId, attribName, repr(value))) + # send a copy to the client-side level obj FIRST + # (it may be a message that creates an entity) + self.sendUpdate('setAttribChange', + [entId, attribName, repr(value), username]) + self.levelSpec.setAttribChange(entId, attribName, value, username) + + self.modified = 1 + self.scheduleAutosave() + + # backups are made every N minutes, starting from the time that + # the first edit is made + AutosavePeriod = simbase.config.GetFloat( + 'level-autosave-period-minutes', 5) + + def scheduleAutosave(self): + if hasattr(self, 'autosaveTask'): + return + self.autosaveTaskName = self.uniqueName('autosaveSpec') + self.autosaveTask = taskMgr.doMethodLater( + DistributedLevelAI.AutosavePeriod * 60, + self.autosaveSpec, + self.autosaveTaskName) + + def removeAutosaveTask(self): + if hasattr(self, 'autosaveTask'): + taskMgr.remove(self.autosaveTaskName) + del self.autosaveTask + + def autosaveSpec(self, task=None): + self.removeAutosaveTask() + if self.modified: + DistributedLevelAI.notify.info('autosaving spec') + filename = self.levelSpec.getFilename() + filename = '%s.autosave' % filename + self.levelSpec.saveToDisk(filename, makeBackup=0) + + def saveSpec(self, task=None): + DistributedLevelAI.notify.info('saving spec') + self.removeAutosaveTask() + if not self.modified: + DistributedLevelAI.notify.info('no changes to save') + return + self.levelSpec.saveToDisk() + self.modified = 0 diff --git a/otp/src/level/EditMgr.py b/otp/src/level/EditMgr.py new file mode 100644 index 0000000..a5cea0b --- /dev/null +++ b/otp/src/level/EditMgr.py @@ -0,0 +1,7 @@ +"""EditMgr module: contains the EditMgr class""" + +import EditMgrBase + +class EditMgr(EditMgrBase.EditMgrBase): + """This class handles client-side editor-specific functionality""" + pass diff --git a/otp/src/level/EditMgrAI.py b/otp/src/level/EditMgrAI.py new file mode 100644 index 0000000..db2cf77 --- /dev/null +++ b/otp/src/level/EditMgrAI.py @@ -0,0 +1,60 @@ +"""EditMgrAI module: contains the EditMgrAI class""" + +import EditMgrBase +if __dev__: + from direct.showbase.PythonUtil import list2dict + import EditorGlobals + +class EditMgrAI(EditMgrBase.EditMgrBase): + """This class handles AI-side editor-specific functionality""" + if __dev__: + def setRequestNewEntity(self, data): + # pick an unused entId + spec = self.level.levelSpec + entIds = spec.getAllEntIds() + entIdDict = list2dict(entIds) + + # Note that this uses the ID range associated with the + # AI's username, not the username of the user who requested + # the new entity. + allocRange = EditorGlobals.getEntIdAllocRange() + + if not hasattr(self, 'lastAllocatedEntId'): + self.lastAllocatedEntId = allocRange[0] + + idChosen = 0 + while not idChosen: + # linear search for an unused entId starting with the + # last-allocated id + for id in xrange(self.lastAllocatedEntId, allocRange[1]): + print id + if not id in entIdDict: + idChosen = 1 + break + else: + # we ran off the end of the range. + if self.lastAllocatedEntId != allocRange[0]: + # if we started in the middle, try again from + # the beginning + self.lastAllocatedEntId = allocRange[0] + else: + # every entId is used!! + self.notify.error('out of entIds') + + # OK, we've chosen an unused entId. Add the entId to the data + # dict and do the insert + data.update({'entId': id}) + self.lastAllocatedEntId = id + self.level.setAttribChange(self.entId, 'insertEntity', data) + + # clear out the attrib, it shouldn't be kept in the spec + self.level.levelSpec.doSetAttrib(self.entId, 'requestNewEntity', + None) + + def getSpecSaveEvent(self): + return 'requestSave-%s' % self.level.levelId + def setRequestSave(self, data): + messenger.send(self.getSpecSaveEvent()) + # clear out the attrib, it shouldn't be kept in the spec + self.level.levelSpec.doSetAttrib(self.entId, 'requestSave', + None) diff --git a/otp/src/level/EditMgrBase.py b/otp/src/level/EditMgrBase.py new file mode 100644 index 0000000..78bbf5a --- /dev/null +++ b/otp/src/level/EditMgrBase.py @@ -0,0 +1,34 @@ +"""EditMgrBase module: contains the EditMgrBase class""" + +import Entity +from direct.directnotify import DirectNotifyGlobal + +class EditMgrBase(Entity.Entity): + """This class contains EditMgr code shared between AI and client""" + notify = DirectNotifyGlobal.directNotify.newCategory("EditMgr") + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + + def destroy(self): + Entity.Entity.destroy(self) + self.ignoreAll() + + if __dev__: + def setInsertEntity(self, data): + # tell the level who created this entity + self.level.setEntityCreatorUsername(data['entId'], data['username']) + # create the entity + self.level.levelSpec.insertEntity(data['entId'], + data['entType'], + data['parentEntId'], + ) + # clear out the attrib, it shouldn't be kept in the spec + self.level.levelSpec.doSetAttrib(self.entId, 'insertEntity', + None) + + def setRemoveEntity(self, data): + self.level.levelSpec.removeEntity(data['entId'], + ) + # clear out the attrib, it shouldn't be kept in the spec + self.level.levelSpec.doSetAttrib(self.entId, 'removeEntity', + None) diff --git a/otp/src/level/EditorGlobals.py b/otp/src/level/EditorGlobals.py new file mode 100644 index 0000000..30b303b --- /dev/null +++ b/otp/src/level/EditorGlobals.py @@ -0,0 +1,54 @@ +"""EditorGlobals module: contains global editor data""" + +from direct.showbase.PythonUtil import uniqueElements + +# levels should put themselves into the bboard under this posting +# to assert themselves as the level to be edited by ~edit +EditTargetPostName = 'inGameEditTarget' + +EntIdRange = 10000 +# Once a range has been assigned to a user, please don't change it. +username2entIdBase = { + 'darren': 1*EntIdRange, + 'samir': 2*EntIdRange, + 'skyler': 3*EntIdRange, + 'joe': 4*EntIdRange, + 'DrEvil': 5*EntIdRange, + 'asad': 6*EntIdRange, + 'drose': 7*EntIdRange, + 'pappy': 8*EntIdRange, + 'patricia': 9*EntIdRange, + 'jloehrle':10*EntIdRange, + 'rurbino' :11*EntIdRange, + } +assert uniqueElements(username2entIdBase.values()) + +usernameConfigVar = 'level-edit-username' +undefinedUsername = 'UNDEFINED_USERNAME' +editUsername = config.GetString(usernameConfigVar, undefinedUsername) + +# call this to make sure things have been set up correctly +def checkNotReadyToEdit(): + # returns error string if not ready, None if ready + if editUsername == undefinedUsername: + return "you must config '%s'; see %s.py" % ( + usernameConfigVar, __name__) + # Feel free to add your name to the table if it's not in there + if editUsername not in username2entIdBase: + return "unknown editor username '%s'; see %s.py" % ( + editUsername, __name__) + return None + +def assertReadyToEdit(): + msg = checkNotReadyToEdit() + if msg is not None: + assert False, msg + +def getEditUsername(): + return editUsername + +def getEntIdAllocRange(): + """range of valid entId values for this user. + returns [min, max+1] (values taken by range() and xrange())""" + baseId = username2entIdBase[editUsername] + return [baseId, baseId+EntIdRange] diff --git a/otp/src/level/Entity.py b/otp/src/level/Entity.py new file mode 100644 index 0000000..66b55b0 --- /dev/null +++ b/otp/src/level/Entity.py @@ -0,0 +1,163 @@ +"""Entity.py: contains the Entity class""" + +from direct.showbase.DirectObject import DirectObject +from direct.showbase.PythonUtil import lineInfo +import string +from direct.directnotify import DirectNotifyGlobal + +class Entity(DirectObject): + """ + Entity is the base class for all objects that exist in a Level + and can be edited with the LevelEditor. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('Entity') + + def __init__(self, level=None, entId=None): + self.initializeEntity(level, entId) + + def initializeEntity(self, level, entId): + ### + ### THIS IS WHERE ENTITIES GET THEIR ATTRIBUTES SET + ### + """ + Distributed entities on the client don't know their level or + entId values until they've been generated, so they call this + after they've been generated. At that point, the entity is good + to go. + """ + self.level = level + self.entId = entId + if (self.level is not None) and (self.entId is not None): + self.level.initializeEntity(self) + + def __str__(self): + if hasattr(self, 'level') and self.level: + return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId)) + elif hasattr(self, 'name'): + return self.name + elif hasattr(self, 'entId'): + return '%s-%s' % (self.__class__.__name__, self.entId) + else: + return self.__class__.__name__ + + def destroy(self): + """ + This is called when the level wants this entity to go away. + Once this is called, the Entity should be considered defunct. + NOTE: distributed entities are still valid distributed objects + after this is called, but they are no longer valid entities. + Distributed entities ought to be disabled and/or deleted shortly + after this is called. + """ + Entity.notify.debug('Entity.destroy() %s' % self.entId) + # client-side distributed entities might be doing this after + # the level has been been destroyed...? + if self.level: + if self.level.isInitialized(): + self.level.onEntityDestroy(self.entId) + else: + Entity.notify.warning('Entity %s destroyed after level??' % + self.entId) + self.ignoreAll() + del self.level + del self.entId + + def getUniqueName(self, name, entId=None): + """returns a name that is unique for a particular entity; + defaults to this entity""" + if entId is None: + entId = self.entId + return '%s-%s-%s' % (name, self.level.levelId, entId) + + def getParentToken(self): + """returns a value that uniquely identifies this entity for purposes + of distributed parenting""" + # give the level the option of modifying our entId, to handle instances + # where there are multiple levels present on the client simultaneously + return self.level.getParentTokenForEntity(self.entId) + + def getOutputEventName(self, entId=None): + """returns the event generated by an entity; defaults to this entity""" + if entId is None: + entId = self.entId + return self.getUniqueName('entityOutput', entId) + + def getZoneEntId(self): + """returns entId of zone that contains this entity""" + return self.level.getEntityZoneEntId(self.entId) + + def getZoneEntity(self): + """returns zone entity for zone that contains this entity""" + return self.level.getEntity(self.getZoneEntId()) + + def getZoneNode(self): + """returns zoneNode for zone that contains this entity""" + return self.getZoneEntity().getNodePath() + + def privGetSetter(self, attrib): + setFuncName = 'set%s%s' % (string.upper(attrib[0]), attrib[1:]) + if hasattr(self, setFuncName): + return getattr(self, setFuncName) + return None + + def callSetters(self, *attribs): + """call this with a list of attribs, and any that exist on the + entity and have setters will be passed to their setter""" + self.privCallSetters(0, *attribs) + + def callSettersAndDelete(self, *attribs): + """same as callSetters, but also removes attribs from entity""" + self.privCallSetters(1, *attribs) + + def privCallSetters(self, doDelete, *attribs): + """common implementation of callSetters and callSettersAndDelete""" + for attrib in attribs: + if hasattr(self, attrib): + setter = self.privGetSetter(attrib) + if setter is not None: + value = getattr(self, attrib) + if doDelete: + delattr(self, attrib) + setter(value) + + # this will be called with each item of our spec data on initialization + def setAttribInit(self, attrib, value): +## if __debug__: +## if hasattr(self, attrib): +## Entity.notify.warning( +## '%s already has member %s in setAttribInit' % +## (self, attrib)) + # TODO: we should probably put this crep in a dictionary + # rather than dump it into the entity's namespace + self.__dict__[attrib] = value + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(self.__dict__.get('entId', '?'))+' '+message) + + if __dev__: + # support for level editing + def handleAttribChange(self, attrib, value): + # call callback function if it exists + # otherwise set attrib directly and call notify func + setter = self.privGetSetter(attrib) + if setter is not None: + # call the setter + setter(value) + else: + # set the attrib directly + self.__dict__[attrib] = value + # and call the notify func + self.attribChanged(attrib, value) + + def attribChanged(self, attrib, value): + """ + This is called when a parameter is tweaked and no setter + is called; i.e. the value is set directly on the object. + Some Entities might want to completely reset every time anything + is tweaked; this is the place to do it, just override this func + in your derived class + """ + pass diff --git a/otp/src/level/EntityCreator.py b/otp/src/level/EntityCreator.py new file mode 100644 index 0000000..a8305f7 --- /dev/null +++ b/otp/src/level/EntityCreator.py @@ -0,0 +1,61 @@ +"""EntityCreator module: contains the EntityCreator class""" + +import CutScene +import EntityCreatorBase +import BasicEntities +from direct.directnotify import DirectNotifyGlobal +import EditMgr +import EntrancePoint +import LevelMgr +import LogicGate +import ZoneEntity +import ModelEntity +import PathEntity +import VisibilityExtender +import PropSpinner +import AmbientSound +import LocatorEntity +import CollisionSolidEntity + +# some useful constructor functions +# ctor functions must take (level, entId) +# and they must return the entity that was created, or 'nothing' +def nothing(*args): + """For entities that don't exist on the client at all""" + return 'nothing' + +def nonlocal(*args): + """For entities that don't need to be created by the client and will + show up independently (they're distributed and created by the AI)""" + return 'nonlocal' + +class EntityCreator(EntityCreatorBase.EntityCreatorBase): + """ + This class is responsible for creating instances of Entities on the + client. It can be subclassed to handle more Entity types. + """ + + def __init__(self, level): + EntityCreatorBase.EntityCreatorBase.__init__(self, level) + self.level = level + self.privRegisterTypes({ + 'attribModifier': nothing, + 'ambientSound': AmbientSound.AmbientSound, + 'collisionSolid': CollisionSolidEntity.CollisionSolidEntity, + 'cutScene': CutScene.CutScene, + 'editMgr': EditMgr.EditMgr, + 'entityGroup': nothing, + 'entrancePoint': EntrancePoint.EntrancePoint, + 'levelMgr': LevelMgr.LevelMgr, + 'locator': LocatorEntity.LocatorEntity, + 'logicGate': LogicGate.LogicGate, + 'model': ModelEntity.ModelEntity, + 'nodepath': BasicEntities.NodePathEntity, + 'path': PathEntity.PathEntity, + 'propSpinner': PropSpinner.PropSpinner, + 'visibilityExtender': VisibilityExtender.VisibilityExtender, + 'zone': ZoneEntity.ZoneEntity, + }) + + def doCreateEntity(self, ctor, entId): + return ctor(self.level, entId) diff --git a/otp/src/level/EntityCreatorAI.py b/otp/src/level/EntityCreatorAI.py new file mode 100644 index 0000000..a84eb14 --- /dev/null +++ b/otp/src/level/EntityCreatorAI.py @@ -0,0 +1,68 @@ +"""EntityCreatorAI module: contains the EntityCreatorAI class""" + +import EntityCreatorBase +import LogicGate +import EditMgrAI +import LevelMgrAI +import ZoneEntityAI +from direct.showbase.PythonUtil import Functor + +# some useful constructor functions +# ctor functions for entities must take +# (level, entId, zoneId) +# and they must return the entity that was created, or 'nothing' + +# this func creates distributed entities whose constructors take +# (air, level doId, entId) +# and do not generate themselves +def createDistributedEntity(AIclass, level, entId, zoneId): + """create a distributed entity and call generate""" + ent = AIclass(level, entId) + ent.generateWithRequired(zoneId) + return ent + +# this func creates local entities whose constructors take +# (level, entId) +def createLocalEntity(AIclass, level, entId, zoneId): + """create a local entity""" + ent = AIclass(level, entId) + return ent + +# take any number of args to support local and distributed entities +def nothing(*args): + """Create entity that doesn't have a server side representation.""" + return 'nothing' + +class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase): + """This class is responsible for creating instances of Entities on the AI. + It can be subclassed to handle more Entity types.""" + + def __init__(self, level): + EntityCreatorBase.EntityCreatorBase.__init__(self, level) + + # create short aliases for ctor funcs + cLE = createLocalEntity + + self.privRegisterTypes({ + 'attribModifier': nothing, + 'ambientSound': nothing, + 'collisionSolid': nothing, + 'cutScene': nothing, + 'editMgr': Functor(cLE, EditMgrAI.EditMgrAI), + 'entityGroup': nothing, + 'entrancePoint': nothing, + 'levelMgr': Functor(cLE, LevelMgrAI.LevelMgrAI), + 'locator': nothing, + 'logicGate': Functor(cLE, LogicGate.LogicGate), + 'model': nothing, + 'nodepath': nothing, + 'path': nothing, + 'propSpinner': nothing, + 'visibilityExtender': nothing, + 'zone': Functor(cLE, ZoneEntityAI.ZoneEntityAI), + }) + + def doCreateEntity(self, ctor, entId): + zoneId = self.level.getEntityZoneId(entId) + self.notify.debug('creating entity %s in zone %s' % (entId, zoneId)) + return ctor(self.level, entId, zoneId) diff --git a/otp/src/level/EntityCreatorBase.py b/otp/src/level/EntityCreatorBase.py new file mode 100644 index 0000000..57643f0 --- /dev/null +++ b/otp/src/level/EntityCreatorBase.py @@ -0,0 +1,40 @@ +"""EntityCreatorBase module: contains the EntityCreatorBase class""" + +from direct.directnotify import DirectNotifyGlobal + +class EntityCreatorBase: + """This class is responsible for creating instances of Entities on the + AI and on the client. It must be subclassed to specify what entity + types it can create, and to provide the creation implementation.""" + notify = DirectNotifyGlobal.directNotify.newCategory('EntityCreator') + + def __init__(self, level): + self.level = level + self.entType2Ctor = {} + + def createEntity(self, entId): + entType = self.level.getEntityType(entId) + + if not self.entType2Ctor.has_key(entType): + self.notify.error('unknown entity type: %s (ent%s)' % + (entType, entId)) + + # inheritor must define doCreateEntity + ent = self.doCreateEntity(self.entType2Ctor[entType], entId) + assert ent is not None # must be Entity or 'nothing' + return ent + + def getEntityTypes(self): + """by definition, this object knows the full list of entity types + that may exist within the level""" + return self.entType2Ctor.keys() + + def privRegisterType(self, entType, ctor): + if self.entType2Ctor.has_key(entType): + self.notify.debug('replacing %s ctor %s with %s' % + (entType, self.entType2Ctor[entType], ctor)) + self.entType2Ctor[entType] = ctor + + def privRegisterTypes(self, type2ctor): + for entType, ctor in type2ctor.items(): + self.privRegisterType(entType, ctor) diff --git a/otp/src/level/EntityStateVarSet.py b/otp/src/level/EntityStateVarSet.py new file mode 100644 index 0000000..d530ebe --- /dev/null +++ b/otp/src/level/EntityStateVarSet.py @@ -0,0 +1,37 @@ +from direct.fsm.StatePush import StateVar +from direct.showbase.PythonUtil import getSetterName +from otp.level.Entity import Entity + +# given an entity type, acts as an entity that has a StateVar attribute for each attribute of the entity type +class EntityStateVarSet(Entity): + def __init__(self, entType): + self._entType = entType + self._attribNames = [] + for attrib in self._entType.attribs: + name, defaultVal, type = attrib + self._addAttrib(name, defaultVal, type) + + def initializeEntity(self, level, entId): + # Entity.initializeEntity hammers attributes directly into self.__dict__ + # set the StateVars aside and restore them afterward + stateVars = {} + for attribName in self._attribNames: + stateVars[attribName] = getattr(self, attribName) + Entity.initializeEntity(self, level, entId) + # update the values + for attribName in self._attribNames: + stateVars[attribName].set(getattr(self, attribName)) + # restore the StateVars + for attribName in self._attribNames: + setattr(self, attribName, stateVars[attribName]) + + def _getAttributeNames(self): + return self._attribNames[:] + + def _setter(self, name, value): + getattr(self, name).set(value) + + def _addAttrib(self, name, defaultVal, type): + setattr(self, name, StateVar(defaultVal)) + setattr(self, getSetterName(name), Functor(self._setter, name)) + self._attribNames.append(name) diff --git a/otp/src/level/EntityTypeDesc.py b/otp/src/level/EntityTypeDesc.py new file mode 100644 index 0000000..0ddb3db --- /dev/null +++ b/otp/src/level/EntityTypeDesc.py @@ -0,0 +1,132 @@ +"""EntityTypeDesc module: contains the EntityTypeDesc class""" + +from direct.directnotify import DirectNotifyGlobal +import AttribDesc +from direct.showbase.PythonUtil import mostDerivedLast + +class EntityTypeDesc: + """This class is meta-data that describes an Entity type.""" + notify = DirectNotifyGlobal.directNotify.newCategory('EntityTypeDesc') + + output = None + + def __init__(self): + self.__class__.privCompileAttribDescs(self.__class__) + + self.attribNames = [] + self.attribDescDict = {} + + # ordered list of attrib descriptors + attribDescs = self.__class__._attribDescs + + # create ordered list of attrib names, dict of attrib name to attribDesc + for desc in attribDescs: + attribName = desc.getName() + self.attribNames.append(attribName) + self.attribDescDict[attribName] = desc + + def isConcrete(self): + """ means that entity of this exact type can be created """ + return not self.__class__.__dict__.has_key('abstract') + + def isPermanent(self): + """ means that entity of this exact type cannot be inserted or + removed in the editor """ + return self.__class__.__dict__.has_key('permanent') + + def getOutputType(self): + return self.output + + def getAttribNames(self): + """ returns ordered list of attribute names for this entity type """ + return self.attribNames + + def getAttribDescDict(self): + """ returns dict of attribName -> attribDescriptor """ + return self.attribDescDict + + def getAttribsOfType(self, type): + """returns list of attrib names of the given type""" + names = [] + for attribName, desc in self.attribDescDict.items(): + if desc.getDatatype() == type: + names.append(attribName) + return names + + @staticmethod + def privCompileAttribDescs(entTypeClass): + """this compiles an ordered list of attribDescs for the Entity class + passed in. The attribute descriptors describe the properties of each + of the Entity type's attributes""" + # has someone already compiled the info? + if entTypeClass.__dict__.has_key('_attribDescs'): + return + + c = entTypeClass + EntityTypeDesc.notify.debug('compiling attrib descriptors for %s' % + c.__name__) + + # make sure all of our base classes have their complete list of + # attribDescs + for base in c.__bases__: + EntityTypeDesc.privCompileAttribDescs(base) + + # aggregate the attribute descriptors from our direct base classes + blockAttribs = c.__dict__.get('blockAttribs', []) + baseADs = [] + + bases = list(c.__bases__) + # make sure base-class attribs show up before derived-class attribs + mostDerivedLast(bases) + + for base in bases: + for desc in base._attribDescs: + # are we blocking this attribute? + if desc.getName() in blockAttribs: + continue + + # make sure we haven't already picked up this attribute + # from an earlier base class + for d in baseADs: + if desc.getName() == d.getName(): + EntityTypeDesc.notify.warning( + '%s inherits attrib %s from multiple bases' % + (c.__name__, desc.getName())) + break + else: + baseADs.append(desc) + + # now that we have all of the descriptors from our base classes, + # add the descriptors from this class + attribDescs = [] + if c.__dict__.has_key('attribs'): + for attrib in c.attribs: + desc = AttribDesc.AttribDesc(*attrib) + + if (desc.getName() == 'type' and + entTypeClass.__name__ != 'Entity'): + EntityTypeDesc.notify.error( + '(%s): \'%s\' is a reserved attribute name' % ( + entTypeClass.__name__, desc.getName())) + + # if we picked up an attribute with the same name from a base + # class, this overrides it + for ad in baseADs: + if ad.getName() == desc.getName(): + baseADs.remove(ad) + # there ought to be no more than one desc with + # this name from the base classes + assert ad not in baseADs + break + + attribDescs.append(desc) + + c._attribDescs = baseADs + attribDescs + + def __str__(self): + return str(self.__class__) + def __repr__(self): + # this is used to produce a hash value + return (str(self.__class__.__dict__.get('type',None))+ + str(self.output)+ + str(self.attribDescDict)) diff --git a/otp/src/level/EntityTypeRegistry.py b/otp/src/level/EntityTypeRegistry.py new file mode 100644 index 0000000..debe844 --- /dev/null +++ b/otp/src/level/EntityTypeRegistry.py @@ -0,0 +1,146 @@ +"""EntityTypeRegistry module: contains the EntityTypeRegistry class""" + +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +import types +import AttribDesc +import EntityTypeDesc +from direct.showbase.PythonUtil import mostDerivedLast +import os +import string + +class EntityTypeRegistry: + notify = DirectNotifyGlobal.directNotify.newCategory('EntityTypeRegistry') + + def __init__(self, entityTypeModule): + """pass in a module that contains EntityTypeDesc classes""" + self.entTypeModule = entityTypeModule + + # compute the hash of the source modules as of the time of creation + hv = HashVal() + import EntityTypes + reload(EntityTypes) + reload(self.entTypeModule) + + # Convert a pyc or pyo to a py + # If the client runs genPyCode -n then ihooks will not be installed + # and you will get a pyc file instead of a py file. Then the AI and + # client will be mismatched because the AI automatically installs + # ihooks and thus will hash the py instead of the pyc. Then the + # hashes do not match and everybody is sad. Let's just make sure + # for once and for all that we use the .py file and not a .pyc or a + # .pyo in case that ever happened. + def getPyExtVersion(filename): + base, ext = os.path.splitext(filename) + if (ext == ".pyc") or (ext == ".pyo"): + filename = base + ".py" + return filename + + # avoid CR/LF issues by reading the file line by line + # readlines() converts \r\n to \n + fileLines = file(getPyExtVersion(EntityTypes.__file__)).readlines() + hv.hashString(string.join(fileLines, '')) + s = str(hv.asHex()) + s += '.' + # avoid CR/LF issues by reading the file line by line + # readlines() converts \r\n to \n + fileLines = file(getPyExtVersion(self.entTypeModule.__file__)).readlines() + hv.hashString(string.join(fileLines, '')) + s += str(hv.asHex()) + self.hashStr = s + + getPyExtVersion = None + + # get a list of the EntityTypeDesc classes in the type module + classes = [] + for key, value in entityTypeModule.__dict__.items(): + if type(value) is types.ClassType: + if issubclass(value, EntityTypeDesc.EntityTypeDesc): + classes.append(value) + + self.entTypeName2typeDesc = {} + + # create an instance of each EntityType class with a typename + # make sure that derived classes come after bases + mostDerivedLast(classes) + for c in classes: + if c.__dict__.has_key('type'): + if self.entTypeName2typeDesc.has_key(c.type): + # a more-derived class is replacing a less-derived class + # to implement a particular entity type + EntityTypeRegistry.notify.debug( + "replacing %s with %s for entity type '%s'" % + (self.entTypeName2typeDesc[c.type].__class__, + c, c.type)) + self.entTypeName2typeDesc[c.type] = c() + + # create mapping of entity output types to list of concrete entity + # typenames with that output type + self.output2typeNames = {} + for typename, typeDesc in self.entTypeName2typeDesc.items(): + if typeDesc.isConcrete(): + if hasattr(typeDesc, 'output'): + outputType = typeDesc.output + self.output2typeNames.setdefault(outputType, []) + self.output2typeNames[outputType].append(typename) + + # create list of permanent entity typenames (entity types that cannot + # be inserted or removed in the editor) + self.permanentTypeNames = [] + for typename, typeDesc in self.entTypeName2typeDesc.items(): + if typeDesc.isPermanent(): + assert typeDesc.isConcrete() + self.permanentTypeNames.append(typename) + + # create mapping of entity typename (abstract or concrete) to list + # of entity typenames are concrete and are of that type or derive + # from that type + self.typeName2derivedTypeNames = {} + for typename, typeDesc in self.entTypeName2typeDesc.items(): + typenames = [] + for tn, td in self.entTypeName2typeDesc.items(): + if td.isConcrete(): + if issubclass(td.__class__, typeDesc.__class__): + typenames.append(tn) + self.typeName2derivedTypeNames[typename] = typenames + + def getAllTypeNames(self): + return self.entTypeName2typeDesc.keys() + + def getTypeDesc(self, entTypeName): + """returns EntityTypeDesc instance for concrete Entity type""" + assert entTypeName in self.entTypeName2typeDesc,\ + "unknown entity type '%s'" % entTypeName + # the table has descriptors for abstract entity types, but I don't + # think there's any need for anyone outside this class to access them + assert self.entTypeName2typeDesc[entTypeName].isConcrete(),\ + "entity type '%s' is abstract" % entTypeName + return self.entTypeName2typeDesc[entTypeName] + + def getTypeNamesFromOutputType(self, outputType): + """return Entity typenames for Entity types with particular output""" + return self.output2typeNames.get(outputType, []) + + def getDerivedTypeNames(self, entTypeName): + """return Entity typenames that are of or derive from an entity type, + which may be concrete or abstract""" + assert entTypeName in self.typeName2derivedTypeNames,\ + "unknown entity type '%s'" % entTypeName + return self.typeName2derivedTypeNames[entTypeName] + + def isDerivedAndBase(self, entType, baseEntType): + return entType in self.getDerivedTypeNames(baseEntType) + + def getPermanentTypeNames(self): + return self.permanentTypeNames + + def getHashStr(self): + return self.hashStr + + def __hash__(self): + # THIS IS NOT GUARANTEED TO PRODUCE THE SAME VALUE ACROSS DIFFERENT + # MACHINES; use getHashStr instead + return hash(repr(self)) + def __repr__(self): + # this is used to produce a hash value + return str(self.entTypeName2typeDesc) diff --git a/otp/src/level/EntityTypes.py b/otp/src/level/EntityTypes.py new file mode 100644 index 0000000..8f6648f --- /dev/null +++ b/otp/src/level/EntityTypes.py @@ -0,0 +1,154 @@ +"""EntityTypes module: contains classes that describe Entity types""" + +from EntityTypeDesc import EntityTypeDesc +from toontown.coghq.SpecImports import * + +class Entity(EntityTypeDesc): + abstract = 1 + type = 'entity' + attribs = ( + ('type', None, 'const'), + ('name', '', 'string'), + ('comment', '', 'string'), + ('parentEntId', 0, 'entId'), + ) + +class LevelMgr(Entity): + type = 'levelMgr' + permanent = 1 + attribs = ( + ('name', 'LevelMgr', 'const'), + ('parentEntId', 0, 'const'), + ('modelFilename', '', 'const'), + ) + +class EditMgr(Entity): + type = 'editMgr' + permanent = 1 + blockAttribs = ( + 'comment', + ) + attribs = ( + ('name', 'LevelMgr', 'const'), + ('parentEntId', 0, 'const'), + ('requestSave', None, 'const'), + ('requestNewEntity', None, 'const'), + ('insertEntity', None, 'const'), + ('removeEntity', None, 'const'), + ) + +class AttribModifier(Entity): + type = 'attribModifier' + attribs = ( + ('recursive', 0, 'bool'), + ('typeName', '', 'string'), + ('attribName', '', 'string'), + ('value', '', 'string'), + ) + +class Locator(Entity): + type='locator' + attribs = ( + ('searchPath', '', 'string'), + ) + +class Nodepath(Entity): + type = 'nodepath' + attribs = ( + ('parentEntId', 0, 'entId', {'type':'nodepath'}), + ('pos', Point3(0,0,0), 'pos'), + ('hpr', Vec3(0,0,0), 'hpr'), + ('scale', 1, 'scale'), + ) + +class Zone(Nodepath): + type = 'zone' + permanent = 1 + blockAttribs = ( + 'pos', + 'hpr', + ) + attribs = ( + ('parentEntId', 0, 'const'), + ('description', '', 'string'), + ('visibility', [], 'visZoneList'), + ) + +class EntrancePoint(Nodepath): + type = 'entrancePoint' + attribs = ( + ('entranceId', -1, 'int'), + ('radius', 15, 'float', {'min':0}), + ('theta', 20, 'float', {'min':0}), + ) + +class LogicGate(Entity): + type = 'logicGate' + output = 'bool' + attribs = ( + ('input1Event', 0, 'entId', {'output':'bool'}), + ('input2Event', 0, 'entId', {'output':'bool'}), + ('isInput1', 0, 'bool'), + ('isInput2', 0, 'bool'), + ('logicType', 'or', 'choice', + {'choiceSet':['or','and','xor','nand','nor','xnor']}), + ) + +class CutScene(Entity): + type = 'cutScene' + output = 'bool' + attribs = ( + ('pos', Point3(0,0,0), 'pos'), + ('hpr', Vec3(0,0,0), 'hpr'), + ('startStopEvent', 0, 'entId', {'output':'bool'}), + ('effect', 'irisInOut', 'choice', {'choiceSet':['nothing','irisInOut','letterBox']}), + ('motion', 'foo1', 'choice', {'choiceSet':['foo1']}), + ('duration', 5.0, 'float'), + ) + +class CollisionSolid(Nodepath): + type = 'collisionSolid' + attribs = ( + ('solidType', 'sphere', 'choice', {'choiceSet':['sphere', 'tube']}), + ('radius', 1., 'float'), + ('length', 0., 'float'), + ('showSolid', 0, 'bool'), + ) + +class Model(Nodepath): + type = 'model' + attribs = ( + ('loadType', 'loadModelCopy', 'choice', {'choiceSet':['loadModelCopy','loadModel','loadModelOnce']}), + ('modelPath', None, 'bamfilename'), + ('flattenType', 'light', 'choice', {'choiceSet':['none','light','medium','strong']}), + ('collisionsOnly', 0, 'bool'), + ('goonHatType', 'none', 'choice', {'choiceSet':['none', 'hardhat', 'security']}), + ) + +class Path(Nodepath): + type = 'path' + attribs = ( + ('pathIndex', 0, 'int'), + ('pathScale', 1., 'float'), + ) + +class VisibilityExtender(Entity): + type = 'visibilityExtender' + attribs = ( + ('event', None, 'entId', {'output':'bool'}), + ('newZones', [], 'visZoneList'), + ) + +class AmbientSound(Nodepath): + type = 'ambientSound' + attribs = ( + ('soundPath', '', 'bamfilename'), + ('volume', 1, 'float', {'min':0,'max':1}), + ('enabled', 1, 'bool'), + ) + +class PropSpinner(Entity): + type = 'propSpinner' + +class EntityGroup(Entity): + type = 'entityGroup' diff --git a/otp/src/level/EntrancePoint.py b/otp/src/level/EntrancePoint.py new file mode 100644 index 0000000..b2d53e4 --- /dev/null +++ b/otp/src/level/EntrancePoint.py @@ -0,0 +1,38 @@ +from toontown.toonbase.ToontownGlobals import * +from direct.directnotify import DirectNotifyGlobal +import BasicEntities + +class EntrancePoint(BasicEntities.NodePathEntity): + def __init__(self, level, entId): + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.rotator = self.attachNewNode('rotator') + self.placer = self.rotator.attachNewNode('placer') + self.initEntrancePoint() + + def destroy(self): + self.destroyEntrancePoint() + self.placer.removeNode() + self.rotator.removeNode() + del self.placer, self.rotator + BasicEntities.NodePathEntity.destroy(self) + + def placeToon(self, toon, toonIndex, numToons): + self.placer.setY(-self.radius) + self.rotator.setH((-self.theta*(numToons-1)*.5) + + (toonIndex*self.theta)) + toon.setPosHpr(self.placer, 0,0,0, 0,0,0) + + def initEntrancePoint(self): + if self.entranceId >= 0: + self.level.entranceId2entity[self.entranceId] = self + + def destroyEntrancePoint(self): + if self.entranceId >= 0: + if self.level.entranceId2entity.has_key(self.entranceId): + del self.level.entranceId2entity[self.entranceId] + + if __dev__: + def attribChanged(self, *args): + BasicEntities.NodePathEntity.attribChanged(self, *args) + self.destroyEntrancePoint() + self.initEntrancePoint() diff --git a/otp/src/level/Level.py b/otp/src/level/Level.py new file mode 100644 index 0000000..50f689c --- /dev/null +++ b/otp/src/level/Level.py @@ -0,0 +1,443 @@ +"""Level.py: contains the Level class""" + +from direct.directnotify import DirectNotifyGlobal +import string +import LevelConstants +from direct.showbase.PythonUtil import lineInfo, uniqueElements +import types + +""" +Any data that can be edited by a level editor must be represented as +an attribute of an entity owned by the level, in order to keep the +level-editing interface simple and constant (there are at least three +places where the entire editing interface must be duplicated). + +To support this, we have entities such as 'levelMgr' and 'zoneEntity' that +contain crucial level information, much of which is needed when setting +up the level object, and is needed before other entity types can be +effectively created. (If you try to create a distributed entity, but +you don't yet have the information for the zone that it's in, because +you haven't created the zone's ZoneEntity, you're hurting.) +""" + +""" +ZONE TERMINOLOGY +zoneNum / zoneEntId: the number that a modeler chooses for a zone, and also + the entity ID of the ZoneEntity that represents a zone +zoneId: the network ID of a zone +""" + +class Level: + """Level: representation of a game level, keeps track of all of the + entities and their interrelations, and creates and destroys entities""" + notify = DirectNotifyGlobal.directNotify.newCategory('Level') + + def __init__(self): + self.levelSpec = None + self.initialized = 0 + + def initializeLevel(self, levelId, levelSpec, scenarioIndex): + """subclass should call this as soon as it has located + its spec data. levelId should be a unique integer (a doId works + just fine) that differentiates this level from all other levels + that may exist concurrently.""" + self.levelId = levelId + self.levelSpec = levelSpec + self.scenarioIndex = scenarioIndex + + self.levelSpec.setScenario(self.scenarioIndex) + if __dev__: + self.levelSpec.setLevel(self) + + # create some handy tables + + # entranceId to entrance entity + self.entranceId2entity = {} + + # dict of entId -> list of callbacks to be called upon creation + self.entId2createCallbacks = {} + + # this list contains the entIds of entities that we have actually + # created, in order of creation + self.createdEntIds = [] + + # non-ordered list of entIds of entities that are not 'local', i.e. + # they are created by someone else (i.e. the AI) and will come and go. + self.nonlocalEntIds = {} + + # non-ordered list of entIds of entities that do not ever have any + # representation on this side (i.e. client-side or AI-side). Populated + # as the entities get their turn to be created. + self.nothingEntIds = {} + + # get an entity creator object + self.entityCreator = self.createEntityCreator() + + # entity type -> list of entIds + self.entType2ids = self.levelSpec.getEntType2ids( + self.levelSpec.getAllEntIds()) + # create empty list for any entity types that are not represented + # in the spec + for entType in self.entityCreator.getEntityTypes(): + self.entType2ids.setdefault(entType, []) + + # create all the entities + # TODO: maybe we should leave this to a subclass or the level user + self.createAllEntities(priorityTypes=['levelMgr','zone','propSpinner']) + + # check on the singleton entities + # we make our own references to them rather than expect them to + # create the references so that the editor can create dummy + # do-nothing entities + + # there should be one and only one levelMgr + assert len(self.entType2ids['levelMgr']) == 1 + assert self.entType2ids['levelMgr'][0] == LevelConstants.LevelMgrEntId + self.levelMgrEntity = self.getEntity(LevelConstants.LevelMgrEntId) + + # there should be one and only one editMgr + assert len(self.entType2ids['editMgr']) == 1 + assert self.entType2ids['editMgr'][0] == LevelConstants.EditMgrEntId + if __debug__: + self.editMgrEntity = self.getEntity(LevelConstants.EditMgrEntId) + + # there should be one and only one UberZone + assert LevelConstants.UberZoneEntId in self.entType2ids['zone'] + self.uberZoneEntity = self.getEntity(LevelConstants.UberZoneEntId) + + self.initialized = 1 + + def isInitialized(self): + return self.initialized + + def getLevelId(self): + return self.levelId + + def destroyLevel(self): + self.destroyAllEntities() + if self.initialized: + del self.levelMgrEntity + if __debug__: + del self.editMgrEntity + del self.uberZoneEntity + del self.entityCreator + del self.entId2createCallbacks + del self.entranceId2entity + self.levelSpec.destroy() + del self.levelSpec + self.initialized = 0 + del self.createdEntIds + del self.nonlocalEntIds + del self.nothingEntIds + if hasattr(self, 'entities'): + del self.entities + if hasattr(self, 'levelSpec'): + self.levelSpec.destroy() + del self.levelSpec + + def createEntityCreator(self): + Level.notify.error( + 'concrete Level class must override %s' % lineInfo()[2]) + + def createAllEntities(self, priorityTypes=[]): + """creates all entities in the spec. priorityTypes is an + optional ordered list of entity types to create first.""" + # this will be filled in as the entities are created and report in + # this includes distributed objects on the client + self.entities = {} + + # get list of all entity types we need to create + entTypes = self.entityCreator.getEntityTypes() + + self.onLevelPreCreate() + + # first create the types in the priority list + for type in priorityTypes: + assert type in entTypes + self.createAllEntitiesOfType(type) + entTypes.remove(type) + + # create the other entities in any old order + for type in entTypes: + self.createAllEntitiesOfType(type) + + assert uniqueElements(self.createdEntIds) + + self.onLevelPostCreate() + + def destroyAllEntities(self): + assert uniqueElements(self.createdEntIds) + self.nonlocalEntIds = {} + self.nothingEntIds = {} + # destroy the entities that we created in reverse order + if not uniqueElements(self.createdEntIds): + Level.notify.warning('%s: self.createdEntIds is not unique: %s' % + (getattr(self, 'doId', None), self.createdEntIds)) + while len(self.createdEntIds) > 0: + entId = self.createdEntIds.pop() + entity = self.getEntity(entId) + if entity is not None: + Level.notify.debug('destroying %s %s' % ( + self.getEntityType(entId), entId)) + entity.destroy() + assert not entId in self.entities + else: + Level.notify.error('trying to destroy entity %s, but ' + 'it is already gone' % entId) + + def createAllEntitiesOfType(self, entType): + """creates all entities of a given type""" + assert entType in self.entityCreator.getEntityTypes() + + self.onEntityTypePreCreate(entType) + + for entId in self.entType2ids[entType]: + self.createEntity(entId) + + self.onEntityTypePostCreate(entType) + + def createEntity(self, entId): + assert not entId in self.createdEntIds + spec = self.levelSpec.getEntitySpec(entId) + Level.notify.debug('creating %s %s' % (spec['type'], entId)) + entity = self.entityCreator.createEntity(entId) + # NOTE: the entity is not considered to really be created until + # it has all of its initial spec data; see 'initializeEntity' + # below. + announce = False + if entity is 'nonlocal': + self.nonlocalEntIds[entId] = None + elif entity is 'nothing': + self.nothingEntIds[entId] = None + announce = True + else: + self.createdEntIds.append(entId) + announce = True + + if announce: + # call the create handler + # we used to do this in initializeEntity, but that did not + # allow for additional initialization to be performed in + # derived entity __init__ funcs before their presence was announced + # Note that now DistributedEntity's are responsible for calling + # this for themselves + self.onEntityCreate(entId) + + return entity + + def initializeEntity(self, entity): + """populate an entity with its spec data. This is not done + in createEntity in order to allow other pieces of code to create + entities; this is called directly by Entity. + """ + entId = entity.entId + spec = self.levelSpec.getEntitySpec(entId) + # on initialization, set items directly on entity + for key,value in spec.items(): + if key in ('type', 'name', 'comment',): + continue + entity.setAttribInit(key, value) + + # entity is initialized, add it to the list of entities + # if this assert fails, check distributed entities to make sure + # they're calling down to Entity.destroy + if __debug__: + if entId in self.entities: + self.notify.warning( + 'entity %s already in entity table... '%(entId)+ + 'make sure distributedEntity is calling down to ' + 'Entity.destroy!') + self.entities[entId] = entity + + def getEntity(self, entId): + if hasattr(self, 'entities'): + return self.entities.get(entId) + else: + return None + + def getEntityType(self, entId): + return self.levelSpec.getEntityType(entId) + + def getEntityZoneEntId(self, entId): + """return entId of zone that contains the entity""" + return self.levelSpec.getEntityZoneEntId(entId) + + def getEntityZoneId(self, entId): + """return network zoneId of zone that contains the entity""" + # this is called during entity creation on the AI; we have to + # handle this carefully, since the information required to + # produce a zoneId is not available until the level's zone + # entities have been instantiated. + zoneEntId = self.getEntityZoneEntId(entId) + # fundamental entities (levelMgr) are responsible for creating + # tables like 'zoneNum2zoneId'; if those tables haven't been + # created yet, just return None + if not hasattr(self, 'zoneNum2zoneId'): + return None + # this might return None if all of our zone entities haven't + # been created yet. this could be a problem if zone entities + # are ever distributed. it also means that no distributed entities + # should be created before the zone entities. + return self.zoneNum2zoneId.get(zoneEntId) + + def getZoneId(self, zoneEntId): + """look up network zoneId by zone entId""" + assert zoneEntId in self.zoneNum2zoneId + return self.zoneNum2zoneId[zoneEntId] + + def getZoneNumFromId(self, zoneId): + """returns the model zoneNum that corresponds to a network zoneId""" + return self.zoneId2zoneNum[zoneId] + + def getParentTokenForEntity(self, entId): + """returns a unique parent token for this entity""" + # default impl + # subclasses can override to allow for multiple levels present + # on the client simultaneously + return entId + + # these events are thrown as the level initializes itself + # LEVEL + def getLevelPreCreateEvent(self): + """This is the event that is thrown immediately before the level + creates its entities.""" + return 'levelPreCreate-%s' % (self.levelId) + def getLevelPostCreateEvent(self): + """This is the event that is thrown immediately after the level + creates its entities.""" + return 'levelPostCreate-%s' % (self.levelId) + # ENTITY TYPE + def getEntityTypePreCreateEvent(self, entType): + """This is the event that is thrown immediately before the level + creates the entities of the given type.""" + return 'entityTypePreCreate-%s-%s' % (self.levelId, entType) + def getEntityTypePostCreateEvent(self, entType): + """This is the event that is thrown immediately after the level + creates the entities of the given type.""" + return 'entityTypePostCreate-%s-%s' % (self.levelId, entType) + # ENTITY + def getEntityCreateEvent(self, entId): + """This is the event that is thrown immediately after a + particular entity is initialized""" + return 'entityCreate-%s-%s' % (self.levelId, entId) + def getEntityOfTypeCreateEvent(self, entType): + """This event is thrown immediately after each instance of the + given entity type is created; handlers must accept an entId""" + return 'entityOfTypeCreate-%s-%s' % (self.levelId, entType) + + # these handlers are called as the level initializes itself + # LEVEL + def onLevelPreCreate(self): + """Level is about to create its entities""" + messenger.send(self.getLevelPreCreateEvent()) + def onLevelPostCreate(self): + """Level is done creating its entities""" + messenger.send(self.getLevelPostCreateEvent()) + # ENTITY TYPE + def onEntityTypePreCreate(self, entType): + """Level is about to create these entities""" + messenger.send(self.getEntityTypePreCreateEvent(entType)) + def onEntityTypePostCreate(self, entType): + """Level has just created these entities""" + messenger.send(self.getEntityTypePostCreateEvent(entType)) + # ENTITY + def onEntityCreate(self, entId): + """Level has just created this entity""" + # send the entity-create event + messenger.send(self.getEntityCreateEvent(entId)) + # send the entity-of-type create event + messenger.send( + self.getEntityOfTypeCreateEvent(self.getEntityType(entId)), + [entId]) + # call any callbacks + if entId in self.entId2createCallbacks: + for callback in self.entId2createCallbacks[entId]: + callback() + del self.entId2createCallbacks[entId] + + # Use to set a callback to be called when entity is created. + # If entity already exists, callback will be called immediately. + def setEntityCreateCallback(self, entId, callback): + ent = self.getEntity(entId) + if ent is not None: + # entity already exists + callNow = True + elif entId in self.nothingEntIds: + # entity has been 'created' but will never manifest + callNow = True + else: + # entity has not been created + callNow = False + + if callNow: + callback() + else: + self.entId2createCallbacks.setdefault(entId, []) + self.entId2createCallbacks[entId].append(callback) + + # these are events and handlers that are invoked as entities are destroyed + def getEntityDestroyEvent(self, entId): + """This is the event that is thrown immediately before an + entity is destroyed""" + return 'entityDestroy-%s-%s' % (self.levelId, entId) + def onEntityDestroy(self, entId): + """Level is about to destroy this entity""" + assert entId in self.entities + # send the entity-destroy event + messenger.send(self.getEntityDestroyEvent(entId)) + + del self.entities[entId] + # if we created this entity, remove its entId from the + # createdEntIds list + if entId in self.createdEntIds: + # this should only happen if someone deleted an entity + # with an editor + self.createdEntIds.remove(entId) + + def handleVisChange(self): + """the zone visibility lists have changed""" + pass + + if __dev__: + # the level generates these events when the spec changes + def getAttribChangeEventName(self): + return 'attribChange-%s' % self.levelId + def getInsertEntityEventName(self): + return 'insertEntity-%s' % self.levelId + def getRemoveEntityEventName(self): + return 'removeEntity-%s' % self.levelId + + # these handlers are called directly by our levelSpec + def handleAttribChange(self, entId, attrib, value, username=None): + entity = self.getEntity(entId) + # the entity might be AI- or client-only + if entity is not None: + entity.handleAttribChange(attrib, value) + messenger.send(self.getAttribChangeEventName(), + [entId, attrib, value, username]) + + def setEntityCreatorUsername(self, entId, editUsername): + # this is called just before an entity is inserted, with the + # entId of the new entity and the username of the editor + # that requested its creation. + pass + + def handleEntityInsert(self, entId): + # update our local type->entId table + self.entType2ids[self.getEntityType(entId)].append(entId) + self.createEntity(entId) + messenger.send(self.getInsertEntityEventName(), [entId]) + + def handleEntityRemove(self, entId): + messenger.send(self.getRemoveEntityEventName(), [entId]) + # if we didn't create it, don't destroy it (probably a distributed + # entity on the client; wait for AI to destroy it) + if entId in self.createdEntIds: + entity = self.getEntity(entId) + entity.destroy() + elif entId in self.nothingEntIds: + del self.nothingEntIds[entId] + elif entId in self.nonlocalEntIds: + del self.nonlocalEntIds[entId] + # update our local type->entId table + self.entType2ids[self.getEntityType(entId)].remove(entId) diff --git a/otp/src/level/LevelConstants.py b/otp/src/level/LevelConstants.py new file mode 100644 index 0000000..b7fe44f --- /dev/null +++ b/otp/src/level/LevelConstants.py @@ -0,0 +1,12 @@ +"""LevelConstants module: contains Level-related constants""" + +# Zone Num from model is also the Zone Entity's entId +MinZoneNum = 0 +MaxZoneNum = 999 + +# zoneNum 0 is reserved for UberZone +UberZoneEntId = 0 + +# system-allocated entities start at 1000 +LevelMgrEntId = 1000 +EditMgrEntId = 1001 diff --git a/otp/src/level/LevelMgr.py b/otp/src/level/LevelMgr.py new file mode 100644 index 0000000..246ee77 --- /dev/null +++ b/otp/src/level/LevelMgr.py @@ -0,0 +1,83 @@ +"""LevelMgr module: contains the LevelMgr class""" + +from direct.showbase.PythonUtil import Functor +import LevelMgrBase + +class LevelMgr(LevelMgrBase.LevelMgrBase): + """This class manages editable client-side level attributes""" + + def __init__(self, level, entId): + LevelMgrBase.LevelMgrBase.__init__(self, level, entId) + + # load the model + self.geom = loader.loadModel(self.modelFilename) + + if not self.geom: + import pdb; pdb.set_trace() + + # this will hold the zoneNums/entIds for our own bookkeeping + self.zoneNums = [] + + # zoneNum -> network zoneId + self.level.zoneNum2zoneId = {} + # network zoneId -> zoneNum + self.level.zoneId2zoneNum = {} + + # listen for every zone creation + self.accept(self.level.getEntityOfTypeCreateEvent('zone'), + self.handleZoneCreated) + + def destroy(self): + del self.level.zoneIds + del self.level.zoneId2zoneNum + del self.level.zoneNum2zoneId + self.geom.removeNode() + del self.geom + LevelMgrBase.LevelMgrBase.destroy(self) + + def handleZoneCreated(self, entId): + zoneEnt = self.level.getEntity(entId) + + # register the zone's info in the tables + + assert zoneEnt.entId not in self.zoneNums + self.zoneNums.append(zoneEnt.entId) + + # we can assume that we have a complete list of network zoneIds in + # self.level.zoneIds. As each zone entity is created, set up + # as if we have all of the zone entities. This allows dynamic + # zone entity creation and deletion during editing. + # TODO: we should delay this until all zone entities have been + # created on level init + self.privAssignZoneIds() + + # listen for the zone's destruction + self.accept(self.level.getEntityDestroyEvent(entId), + Functor(self.handleZoneDestroy, entId)) + + def handleZoneDestroy(self, entId): + zoneEnt = self.level.getEntity(entId) + # unregister the zone from the maps + del self.level.zoneId2zoneNum[ + self.level.zoneNum2zoneId[zoneEnt.entId]] + del self.level.zoneNum2zoneId[zoneEnt.entId] + self.zoneNums.remove(zoneEnt.entId) + # reassign the zoneIds (we may not need to do this, if all of the + # other entities already have their correct zoneId...?) + self.privAssignZoneIds() + + def privAssignZoneIds(self): + """assign network zoneIds from self.level.zoneIds, according to + the zones that are registered so far""" + # sort the zoneNums + self.zoneNums.sort() + + # dole out the zoneIds, in increasing order of zoneNum + for i in range(len(self.zoneNums)): + zoneNum = self.zoneNums[i] + zoneEnt = self.level.getEntity(zoneNum) + zoneId = self.level.zoneIds[i] + zoneEnt.setZoneId(zoneId) + # the zoneIds have shifted. update the tables + self.level.zoneNum2zoneId[zoneNum] = zoneId + self.level.zoneId2zoneNum[zoneId] = zoneNum diff --git a/otp/src/level/LevelMgrAI.py b/otp/src/level/LevelMgrAI.py new file mode 100644 index 0000000..751bdda --- /dev/null +++ b/otp/src/level/LevelMgrAI.py @@ -0,0 +1,59 @@ +"""LevelMgrAI module: contains the LevelMgrAI class""" + +from direct.showbase.PythonUtil import Functor +import LevelMgrBase + +class LevelMgrAI(LevelMgrBase.LevelMgrBase): + """This class manages editable AI level attributes""" + def __init__(self, level, entId): + LevelMgrBase.LevelMgrBase.__init__(self, level, entId) + + # zoneNum -> network zoneId + self.level.zoneNum2zoneId = {} + # list of network zoneIDs, sorted by zoneNum + self.level.zoneIds = [] + + # listen for every zone creation + self.accept(self.level.getEntityOfTypeCreateEvent('zone'), + self.handleZoneCreated) + + def destroy(self): + del self.level.zoneIds + del self.level.zoneNum2zoneId + LevelMgrBase.LevelMgrBase.destroy(self) + + def handleZoneCreated(self, entId): + zoneEnt = self.level.getEntity(entId) + + # register the zone's info in the tables + self.level.zoneNum2zoneId[zoneEnt.entId] = zoneEnt.getZoneId() + + # TODO: we should delay this until all zone entities have been + # created on level init + self.privCreateSortedZoneIdList() + + # listen for the zone's destruction + self.accept(self.level.getEntityDestroyEvent(entId), + Functor(self.handleZoneDestroy, entId)) + + def handleZoneDestroy(self, entId): + zoneEnt = self.level.getEntity(entId) + # unregister the zone from the tables + del self.level.zoneNum2zoneId[zoneEnt.entId] + # recreate the sorted network zoneId list + self.privCreateSortedZoneIdList() + + def privCreateSortedZoneIdList(self): + # sort the zoneNums + zoneNums = self.level.zoneNum2zoneId.keys() + zoneNums.sort() + + # create a list of network zoneIds, ordered by their corresponding + # sorted model zoneNum values + self.level.zoneIds = [] + for zoneNum in zoneNums: + self.level.zoneIds.append(self.level.zoneNum2zoneId[zoneNum]) + + # TODO: if we ever allow dynamic insertion and removal of zone + # entities, and we just added or removed a zone entity AFTER the level + # was initialized, we would need to re-send the zoneId list diff --git a/otp/src/level/LevelMgrBase.py b/otp/src/level/LevelMgrBase.py new file mode 100644 index 0000000..c810479 --- /dev/null +++ b/otp/src/level/LevelMgrBase.py @@ -0,0 +1,12 @@ +"""LevelMgrBase module: contains the LevelMgrBase class""" + +import Entity + +class LevelMgrBase(Entity.Entity): + """This class contains LevelMgr code shared by the AI and client""" + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + + def destroy(self): + Entity.Entity.destroy(self) + self.ignoreAll() diff --git a/otp/src/level/LevelSpec.py b/otp/src/level/LevelSpec.py new file mode 100644 index 0000000..3539ab0 --- /dev/null +++ b/otp/src/level/LevelSpec.py @@ -0,0 +1,535 @@ +"""LevelSpec module: contains the LevelSpec class""" + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase.PythonUtil import list2dict, uniqueElements +import string +import LevelConstants +import types +if __dev__: + import os + +class LevelSpec: + """contains spec data for a level, is responsible for handing the data + out upon request, as well as recording changes made during editing, and + saving out modified spec data""" + notify = DirectNotifyGlobal.directNotify.newCategory("LevelSpec") + + SystemEntIds = (LevelConstants.UberZoneEntId, + LevelConstants.LevelMgrEntId, + LevelConstants.EditMgrEntId) + + def __init__(self, spec=None, scenario=0): + """spec must be passed in as a python module or a dictionary. + If not passed in, will create a new spec.""" + newSpec = 0 + if type(spec) is types.ModuleType: + if __dev__: + # reload the spec module to pick up changes + reload(spec) + self.specDict = spec.levelSpec + if __dev__: + self.setFilename(spec.__file__) + elif type(spec) is types.DictType: + # we need this for repr/eval-ing LevelSpecs + self.specDict = spec + elif spec is None: + if __dev__: + newSpec = 1 + self.specDict = { + 'globalEntities': {}, + 'scenarios': [{}], + } + + assert hasattr(self, 'specDict') + + # this maps an entId to the dict that holds its spec; + # entities are either in the global dict or a scenario dict + # update the map of entId to spec dict + self.entId2specDict = {} + self.entId2specDict.update( + list2dict(self.getGlobalEntIds(), + value=self.privGetGlobalEntityDict())) + for i in range(self.getNumScenarios()): + self.entId2specDict.update( + list2dict(self.getScenarioEntIds(i), + value=self.privGetScenarioEntityDict(i))) + + self.setScenario(scenario) + + if __dev__: + if newSpec: + # add basic required entities + import EntityTypes + import EntityTypeRegistry + etr = EntityTypeRegistry.EntityTypeRegistry(EntityTypes) + self.setEntityTypeReg(etr) + + # UberZone + entId = LevelConstants.UberZoneEntId + self.insertEntity(entId, 'zone') + self.doSetAttrib(entId, 'name', 'UberZone') + # LevelMgr + entId = LevelConstants.LevelMgrEntId + self.insertEntity(entId, 'levelMgr') + self.doSetAttrib(entId, 'name', 'LevelMgr') + # EditMgr + entId = LevelConstants.EditMgrEntId + self.insertEntity(entId, 'editMgr') + self.doSetAttrib(entId, 'name', 'EditMgr') + + def destroy(self): + del self.specDict + del self.entId2specDict + del self.scenario + if hasattr(self, 'level'): + del self.level + if hasattr(self, 'entTypeReg'): + del self.entTypeReg + + def getNumScenarios(self): + return len(self.specDict['scenarios']) + + def setScenario(self, scenario): + assert scenario in range(0, self.getNumScenarios()) + self.scenario = scenario + + def getScenario(self): + return self.scenario + + def getGlobalEntIds(self): + return self.privGetGlobalEntityDict().keys() + + def getScenarioEntIds(self, scenario=None): + if scenario is None: + scenario = self.scenario + return self.privGetScenarioEntityDict(scenario).keys() + + def getAllEntIds(self): + """this returns all of the entIds involved in the current scenario""" + return self.getGlobalEntIds() + self.getScenarioEntIds() + + def getAllEntIdsFromAllScenarios(self): + """this returns all of the entIds involved in all scenarios""" + entIds = self.getGlobalEntIds() + for scenario in xrange(self.getNumScenarios()): + entIds.extend(self.getScenarioEntIds(scenario)) + return entIds + + def getEntitySpec(self, entId): + assert entId in self.entId2specDict + specDict = self.entId2specDict[entId] + return specDict[entId] + + def getCopyOfSpec(self, spec): + # return a copy of the spec, making sure that none of the attributes + # are shared between the original and the copy (i.e. Point3's) + specCopy = {} + exec 'from %s import *' % self.getSpecImportsModuleName() + for key in spec.keys(): + specCopy[key] = eval(repr(spec[key])) + return specCopy + + def getEntitySpecCopy(self, entId): + # return a copy of the spec, making sure that none of the attributes + # are shared between the original and the copy (i.e. Point3's) + assert entId in self.entId2specDict + specDict = self.entId2specDict[entId] + return self.getCopyOfSpec(specDict[entId]) + + def getEntityType(self, entId): + return self.getEntitySpec(entId)['type'] + + def getEntityZoneEntId(self, entId): + """ return the entId of the zone that entity is in; if entity + is a zone, returns its entId """ + spec = self.getEntitySpec(entId) + type = spec['type'] + # if it's a zone, this is our entity + if type == 'zone': + return entId + assert spec['parentEntId'] != entId + # keep looking up the heirarchy for a zone entity + return self.getEntityZoneEntId(spec['parentEntId']) + + def getEntType2ids(self, entIds): + """given list of entIds, return dict of entType->entIds""" + entType2ids = {} + for entId in entIds: + type = self.getEntityType(entId) + entType2ids.setdefault(type, []) + entType2ids[type].append(entId) + return entType2ids + + # private support functions to abstract dict structure + def privGetGlobalEntityDict(self): + return self.specDict['globalEntities'] + + def privGetScenarioEntityDict(self, scenario): + return self.specDict['scenarios'][scenario] + + def printZones(self): + """currently prints list of zoneNum->zone name""" + # this could be more efficient + allIds = self.getAllEntIds() + type2id = self.getEntType2ids(allIds) + zoneIds = type2id['zone'] + # omit the UberZone + if 0 in zoneIds: + zoneIds.remove(0) + zoneIds.sort() + for zoneNum in zoneIds: + spec = self.getEntitySpec(zoneNum) + print 'zone %s: %s' % (zoneNum, spec['name']) + + if __dev__: + def setLevel(self, level): + self.level = level + + def hasLevel(self): + return hasattr(self, 'level') + + def setEntityTypeReg(self, entTypeReg): + self.entTypeReg = entTypeReg + self.checkSpecIntegrity() + + def hasEntityTypeReg(self): + return hasattr(self, 'entTypeReg') + + def setFilename(self, filename): + self.filename = filename + + def doSetAttrib(self, entId, attrib, value): + """ do the dirty work of changing an attrib value """ + assert entId in self.entId2specDict + specDict = self.entId2specDict[entId] + assert specDict[entId].has_key(attrib) + specDict[entId][attrib] = value + + def setAttribChange(self, entId, attrib, value, username): + """ we're being asked to change an attribute """ + LevelSpec.notify.info("setAttribChange(%s): %s, %s = %s" % + (username, entId, attrib, repr(value))) + self.doSetAttrib(entId, attrib, value) + if self.hasLevel(): + # let the level know that this attribute value has + # officially changed + self.level.handleAttribChange(entId, attrib, value, username) + + def insertEntity(self, entId, entType, parentEntId='unspecified'): + LevelSpec.notify.info('inserting entity %s (%s)' % (entId, entType)) + assert entId not in self.entId2specDict + assert self.entTypeReg is not None + globalEnts = self.privGetGlobalEntityDict() + self.entId2specDict[entId] = globalEnts + + # create a new entity spec entry w/ default values + globalEnts[entId] = {} + spec = globalEnts[entId] + attribDescs = self.entTypeReg.getTypeDesc(entType + ).getAttribDescDict() + for name, desc in attribDescs.items(): + spec[name] = desc.getDefaultValue() + spec['type'] = entType + if parentEntId != 'unspecified': + spec['parentEntId'] = parentEntId + + if self.hasLevel(): + # notify the level + self.level.handleEntityInsert(entId) + else: + LevelSpec.notify.warning('no level to be notified of insertion') + + """ this was never used/tested but may come in handy + def insertEntityWithSpec(self, entId, spec): + # use this to add an entity with an existing spec + # NOTE: DO NOT use this to add an entity with an editor; this + # will not propogate the spec to the level. For now, editors + # should manually insert the item and set each attribute + # individually. + self.insertEntity(entId, spec['type']) + specCopy = self.getCopyOfSpec(spec) + del specCopy['type'] + for attribName, value in specCopy.items(): + self.doSetAttrib(entId, attribName, value) + """ + + def removeEntity(self, entId): + LevelSpec.notify.info('removing entity %s' % entId) + assert entId in self.entId2specDict + + if self.hasLevel(): + # notify the level + self.level.handleEntityRemove(entId) + else: + LevelSpec.notify.warning('no level to be notified of removal') + + # remove the entity's spec + dict = self.entId2specDict[entId] + del dict[entId] + del self.entId2specDict[entId] + + def removeZoneReferences(self, removedZoneNums): + """call with a list of zoneNums of zone entities that have just + been removed; will clean up references to those zones""" + assert self.hasEntityTypeReg() + # get dict of entType->entIds, for ALL scenarios + type2ids = self.getEntType2ids(self.getAllEntIdsFromAllScenarios()) + # figure out which entity types have attributes that need to be + # updated + for type in type2ids: + typeDesc = self.entTypeReg.getTypeDesc(type) + visZoneListAttribs = typeDesc.getAttribsOfType('visZoneList') + if len(visZoneListAttribs) > 0: + # this entity type has at least one attrib of type + # 'visZoneList'. + # run through all of the existing entities of this type + for entId in type2ids[type]: + spec = self.getEntitySpec(entId) + # for each attrib of type 'visZoneList'... + for attribName in visZoneListAttribs: + # remove each of the removed zoneNums + for zoneNum in removedZoneNums: + while zoneNum in spec[attribName]: + spec[attribName].remove(zoneNum) + + def getSpecImportsModuleName(self): + # name of module that should be imported by spec py file + # TODO: make this generic + return 'toontown.coghq.SpecImports' + + def getFilename(self): + return self.filename + + def privGetBackupFilename(self, filename): + return '%s.bak' % filename + + def saveToDisk(self, filename=None, makeBackup=1): + """returns zero on failure""" + if filename is None: + filename = self.filename + if filename.endswith('.pyc'): + filename = filename.replace('.pyc','.py') + + if makeBackup and self.privFileExists(filename): + # create a backup + try: + backupFilename = self.privGetBackupFilename(filename) + self.privRemoveFile(backupFilename) + os.rename(filename, backupFilename) + except OSError, e: + LevelSpec.notify.warning( + 'error during backup: %s' % str(e)) + + LevelSpec.notify.info("writing to '%s'" % filename) + self.privRemoveFile(filename) + self.privSaveToDisk(filename) + + def privSaveToDisk(self, filename): + """internal. saves spec to file. returns zero on failure""" + retval = 1 + # wb to create a UNIX-format file + f = file(filename, 'wb') + try: + f.write(self.getPrettyString()) + except IOError: + retval = 0 + f.close() + return retval + + def privFileExists(self, filename): + try: + os.stat(filename) + return 1 + except OSError: + return 0 + + def privRemoveFile(self, filename): + try: + os.remove(filename) + return 1 + except OSError: + return 0 + + def getPrettyString(self): + """Returns a string that contains the spec data, nicely formatted. + This should be used when writing the spec out to file.""" + import pprint + + tabWidth = 4 + tab = ' ' * tabWidth + # structure names + globalEntitiesName = 'GlobalEntities' + scenarioEntitiesName = 'Scenario%s' + topLevelName = 'levelSpec' + def getPrettyEntityDictStr(name, dict, tabs=0): + def t(n): + return (tabs+n)*tab + def sortList(lst, firstElements=[]): + """sort list; elements in firstElements will be put + first, in the order that they appear in firstElements; + rest of elements will follow, sorted""" + elements = list(lst) + # put elements in order + result = [] + for el in firstElements: + if el in elements: + result.append(el) + elements.remove(el) + elements.sort() + result.extend(elements) + return result + + firstTypes = ('levelMgr', 'editMgr', 'zone',) + firstAttribs = ('type', 'name', 'comment', 'parentEntId', + 'pos', 'x', 'y', 'z', + 'hpr', 'h', 'p', 'r', + 'scale', 'sx', 'sy', 'sz', + 'color', + 'model', + ) + str = t(0)+'%s = {\n' % name + # get list of types + entIds = dict.keys() + entType2ids = self.getEntType2ids(entIds) + # put types in order + types = sortList(entType2ids.keys(), firstTypes) + for type in types: + str += t(1)+'# %s\n' % string.upper(type) + entIds = entType2ids[type] + entIds.sort() + for entId in entIds: + str += t(1)+'%s: {\n' % entId + spec = dict[entId] + attribs = sortList(spec.keys(), firstAttribs) + for attrib in attribs: + str += t(2)+"'%s': %s,\n" % (attrib, + repr(spec[attrib])) + # maybe this will help with CVS merges? + str += t(2)+'}, # end entity %s\n' % entId + + str += t(1)+'}\n' + return str + def getPrettyTopLevelDictStr(tabs=0): + def t(n): + return (tabs+n)*tab + str = t(0)+'%s = {\n' % topLevelName + str += t(1)+"'globalEntities': %s,\n" % globalEntitiesName + str += t(1)+"'scenarios': [\n" + for i in range(self.getNumScenarios()): + str += t(2)+'%s,\n' % (scenarioEntitiesName % i) + str += t(2)+'],\n' + str += t(1)+'}\n' + return str + + str = 'from %s import *\n' % self.getSpecImportsModuleName() + str += '\n' + + # add the global entities + str += getPrettyEntityDictStr('GlobalEntities', + self.privGetGlobalEntityDict()) + str += '\n' + + # add the scenario entities + numScenarios = self.getNumScenarios() + for i in range(numScenarios): + str += getPrettyEntityDictStr('Scenario%s' % i, + self.privGetScenarioEntityDict(i)) + str += '\n' + + # add the top-level table + str += getPrettyTopLevelDictStr() + + self.testPrettyString(prettyString=str) + + return str + + def _recurKeyTest(self, dict1, dict2): + # recursive key test for testPrettyString + # cannot be sub function due to exec call in testPrettyString + s = '' # error out string + errorCount = 0 # number of non-matching keys; more or less + + #if set of keys don't match than they are not the same + if set(dict1.keys()) != set(dict2.keys()): + return 0 + for key in dict1: + #if they are both dicitonaries we must test the subkeys + #this is because dicts are unordered and we are using repr to dump the + #values into strings for comparision + if type(dict1[key]) == type({}) and type(dict2[key]) == type({}): + if not self._recurKeyTest(dict1[key], dict2[key]): + return 0 + #if they are not dicts turn the values into strings and compare the strings + else: + strd1 = repr(dict1[key]) + strd2 = repr(dict2[key]) + if strd1 != strd2: + #if the strings don't match print an error + s += '\nBAD VALUE(%s): %s != %s\n' % (key, strd1, strd2) + errorCount += 1 #we could just bail here but instead we accumulate the errors + print s + #import pdb;pdb.set_trace + if errorCount == 0: + return 1 + else: + return 0 + + def testPrettyString(self, prettyString=None): + # execute the pretty output in our local scope + if prettyString is None: + prettyString=self.getPrettyString() + exec(prettyString) + if self._recurKeyTest(levelSpec, self.specDict): + return 1 + else: + #import pdb;pdb.set_trace() + assert 0, ( + 'LevelSpec pretty string does not match spec data.\n' + ) + + def checkSpecIntegrity(self): + # make sure there are no duplicate entIds + entIds = self.getGlobalEntIds() + assert uniqueElements(entIds) + entIds = list2dict(entIds) + for i in range(self.getNumScenarios()): + for id in self.getScenarioEntIds(i): + assert not entIds.has_key(id) + entIds[id] = None + + if self.entTypeReg is not None: + # check each spec + allEntIds = entIds + for entId in allEntIds: + spec = self.getEntitySpec(entId) + + assert spec.has_key('type') + entType = spec['type'] + typeDesc = self.entTypeReg.getTypeDesc(entType) + attribNames = typeDesc.getAttribNames() + attribDescs = typeDesc.getAttribDescDict() + + # are there any unknown attribs in the spec? + for attrib in spec.keys(): + if attrib not in attribNames: + LevelSpec.notify.warning( + "entId %s (%s): unknown attrib '%s', omitting" + % (entId, spec['type'], attrib)) + del spec[attrib] + + # does the spec have all of its attributes? + for attribName in attribNames: + if not spec.has_key(attribName): + LevelSpec.notify.warning( + "entId %s (%s): missing attrib '%s'" % ( + entId, spec['type'], attribName)) + + def __hash__(self): + return hash(repr(self)) + + def __str__(self): + return 'LevelSpec' + + def __repr__(self): + return 'LevelSpec(%s, scenario=%s)' % (repr(self.specDict), + self.scenario) diff --git a/otp/src/level/LevelUtil.py b/otp/src/level/LevelUtil.py new file mode 100644 index 0000000..68d845e --- /dev/null +++ b/otp/src/level/LevelUtil.py @@ -0,0 +1,51 @@ +"""LevelUtil module: contains Level utility funcs""" + +import string +import LevelConstants + +def getZoneNum2Node(levelModel, logFunc=lambda str:str): + """ given model, returns dict of ZoneNumber -> ZoneNode """ + def findNumberedNodes(baseString, model, caseInsens=1): + # finds nodes whose name follows the pattern 'baseString#blah' + # returns dictionary that maps # to node + srch = '**/%s*' % baseString + if caseInsens: + srch += ';+i' + potentialNodes = model.findAllMatches(srch) + num2node = {} + for potentialNode in potentialNodes: + name = potentialNode.getName() + logFunc('potential match for %s: %s' % (baseString, name)) + name = name[len(baseString):] + numDigits = 0 + while numDigits < len(name): + if name[numDigits] not in string.digits: + break + numDigits += 1 + if numDigits == 0: + continue + num = int(name[:numDigits]) + # is this a valid zoneNum? + if num == LevelConstants.UberZoneEntId: + logFunc('warning: cannot use UberZone zoneNum (%s). ' + 'ignoring %s' % (LevelConstants.UberZoneEntId, + potentialNode)) + continue + if (num < LevelConstants.MinZoneNum) or ( + num > LevelConstants.MaxZoneNum): + logFunc('warning: zone %s is out of range. ignoring %s' % + (num, potentialNode)) + continue + # do we already have a ZoneNode for this zone num? + if num in num2node: + logFunc('warning: zone %s already assigned to %s. ignoring %s' % + (num, num2node[num], potentialNode)) + continue + num2node[num] = potentialNode + + return num2node + + zoneNum2node = findNumberedNodes('zone', levelModel) + # add the UberZone to the table + zoneNum2node[LevelConstants.UberZoneEntId] = levelModel + return zoneNum2node diff --git a/otp/src/level/LocatorEntity.py b/otp/src/level/LocatorEntity.py new file mode 100644 index 0000000..e9a6d6e --- /dev/null +++ b/otp/src/level/LocatorEntity.py @@ -0,0 +1,33 @@ +import Entity, BasicEntities +from pandac.PandaModules import NodePath +from direct.directnotify import DirectNotifyGlobal + +class LocatorEntity(Entity.Entity, NodePath): + notify = DirectNotifyGlobal.directNotify.newCategory('LocatorEntity') + def __init__(self, level, entId): + node = hidden.attachNewNode('LocatorEntity-%s' % entId) + NodePath.__init__(self, node) + Entity.Entity.__init__(self, level, entId) + self.doReparent() + + def destroy(self): + Entity.Entity.destroy(self) + self.removeNode() + + def getNodePath(self): + # this allows other entities to be parented to us + return self + + def doReparent(self): + if self.searchPath != '': + parent = self.level.geom.find(self.searchPath) + if parent.isEmpty(): + LocatorEntity.notify.warning( + "could not find '%s'" % self.searchPath) + self.reparentTo(hidden) + else: + self.reparentTo(parent) + + if __dev__: + def attribChanged(self, attrib, value): + self.doReparent() diff --git a/otp/src/level/LogicGate.py b/otp/src/level/LogicGate.py new file mode 100644 index 0000000..dd7b238 --- /dev/null +++ b/otp/src/level/LogicGate.py @@ -0,0 +1,140 @@ +""" +LogicGate.py + + Logic Gates: + + and: 0 0 = 0 or: 0 0 = 0 xor: 0 0 = 0 + 0 1 = 0 0 1 = 1 0 1 = 1 + 1 0 = 0 1 0 = 1 1 0 = 1 + 1 1 = 1 1 1 = 1 1 1 = 0 + + nand: 0 0 = 1 nor: 0 0 = 1 xnor: 0 0 = 1 + 0 1 = 1 0 1 = 0 0 1 = 0 + 1 0 = 1 1 0 = 0 1 0 = 0 + 1 1 = 0 1 1 = 0 1 1 = 1 + + In the following: + 1: send a true message + 0: send a false message + -: don't send a message + + a b and or xor nand nor xnor + (0 0) (0) (0) (0) (1) (1) (1) <--- initial state + 1 0 - 1 1 - 0 0 + 0 0 - 0 0 - 1 1 + 1 0 - 1 1 - 0 0 + 1 1 1 - 0 0 - 1 + 0 1 0 - 1 1 - 0 + 1 1 1 - 0 0 - 1 + 0 1 0 - 1 1 - 0 + 0 0 - 0 0 - 1 1 +""" + +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +import Entity + + +def andTest(self, a, b): + assert self.debugPrint("andTest(a=%s, b=%s)"%(a, b)) + if b: + messenger.send(self.getOutputEventName(), [a]) + +def orTest(self, a, b): + assert self.debugPrint("orTest(a=%s, b=%s)"%(a, b)) + if not b: + messenger.send(self.getOutputEventName(), [a]) + # else: ...we already sent the messege when b was set. + +def xorTest(self, a, b): + assert self.debugPrint("xorTest(a=%s, b=%s)"%(a, b)) + messenger.send(self.getOutputEventName(), [(not (a and b)) and (a or b)]) + +def nandTest(self, a, b): + assert self.debugPrint("nandTest(a=%s, b=%s)"%(a, b)) + if b: + messenger.send(self.getOutputEventName(), [not (a and b)]) + +def norTest(self, a, b): + assert self.debugPrint("norTest(a=%s, b=%s)"%(a, b)) + if not b: + messenger.send(self.getOutputEventName(), [not (a or b)]) + # else: ...we already sent the messege when b was set. + +def xnorTest(self, a, b): + assert self.debugPrint("xnorTest(a=%s, b=%s)"%(a, b)) + messenger.send(self.getOutputEventName(), [(a and b) or (not (a or b))]) + + +class LogicGate(Entity.Entity, DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory('LogicGate') + + logicTests={ + "and": andTest, + "or": orTest, + "xor": xorTest, + "nand": nandTest, + "nor": norTest, + "xnor": xnorTest, + } + + def __init__(self, level, entId): + """entId: """ + assert self.debugPrint("LogicGate(entId=%s)"%(entId)) + self.input1Event = None + self.input2Event = None + DirectObject.DirectObject.__init__(self) + Entity.Entity.__init__(self, level, entId) + self.setLogicType(self.logicType) + self.setIsInput1(self.isInput1) + self.setIsInput2(self.isInput2) + self.setInput1Event(self.input1Event) + self.setInput2Event(self.input2Event) + + def destroy(self): + assert self.debugPrint("destroy()") + self.ignore(self.input1Event) + self.input1Event = None + self.ignore(self.input2Event) + self.input2Event = None + Entity.Entity.destroy(self) + + def setLogicType(self, logicType): + assert self.debugPrint("setLogicType(logicType=%s)"%(logicType,)) + self.logicType=logicType + assert self.logicTests[logicType] + self.logicTest=self.logicTests[logicType] + + def setIsInput1(self, isTrue): + assert self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)) + if 1 or (not isTrue) != (not self.input1Event): + # ...the logical state of self.input1Event has changed. + self.isInput1=isTrue + self.logicTest(self, isTrue, self.isInput2) + + def setIsInput2(self, isTrue): + assert self.debugPrint("setIsInput2(isTrue=%s)"%(isTrue,)) + if 1 or (not isTrue) != (not self.input2Event): + # ...the logical state of self.input2Event has changed. + self.isInput2=isTrue + self.logicTest(self, isTrue, self.isInput1) + + def setInput1Event(self, event): + assert self.debugPrint("setInput1Event(event=%s)"%(event,)) + if self.input1Event: + self.ignore(self.input1Event) + self.input1Event = self.getOutputEventName(event) + if self.input1Event: + self.accept(self.input1Event, self.setIsInput1) + + def setInput2Event(self, event): + assert self.debugPrint("setInput2Event(event=%s)"%(event,)) + if self.input2Event: + self.ignore(self.input2Event) + self.input2Event = self.getOutputEventName(event) + if self.input2Event: + self.accept(self.input2Event, self.setIsInput2) + + def getName(self): + #return "logicGate-%s"%(self.entId,) + return "switch-%s"%(self.entId,) diff --git a/otp/src/level/ModelEntity.py b/otp/src/level/ModelEntity.py new file mode 100644 index 0000000..5850215 --- /dev/null +++ b/otp/src/level/ModelEntity.py @@ -0,0 +1,107 @@ +from toontown.toonbase.ToontownGlobals import * +from direct.directnotify import DirectNotifyGlobal +import BasicEntities + +class ModelEntity(BasicEntities.NodePathEntity): + LoadFuncs = { + 'loadModelCopy': loader.loadModelCopy, + 'loadModel': loader.loadModel, + 'loadModelOnce': loader.loadModelOnce, + } + def __init__(self, level, entId): + # TODO: fill in default values automatically for missing attribs + self.collisionsOnly = False + self.loadType = 'loadModelCopy' + self.flattenType = 'light' + self.goonHatType = 'none' + self.entInitialized = False + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.entInitialized = True + self.model = None + self.loadModel() + + def destroy(self): + if self.model: + self.model.removeNode() + del self.model + BasicEntities.NodePathEntity.destroy(self) + + def loadModel(self): + if self.model: + self.model.removeNode() + self.model = None + if self.modelPath is None: + return + + self.model = ModelEntity.LoadFuncs[self.loadType](self.modelPath) + if self.model: + self.model.reparentTo(self) + + # hide/show as appropriate + if self.collisionsOnly: + if __dev__: + self.model.setTransparency(1) + self.model.setColorScale(1,1,1,.1) + else: + self.model.hide() + else: + self.model.show() + + # HACK SDN: special code for moving crate wall collisions down + if self.modelPath in ("phase_9/models/cogHQ/woodCrateB.bam", + "phase_9/models/cogHQ/metal_crateB.bam", + "phase_10/models/cashbotHQ/CBMetalCrate.bam", + "phase_10/models/cogHQ/CBMetalCrate2.bam", + "phase_10/models/cashbotHQ/CBWoodCrate.bam", + "phase_11/models/lawbotHQ/LB_metal_crate.bam", + "phase_11/models/lawbotHQ/LB_metal_crate2.bam", + ): + # get rid of any scales + #self.model.flattenLight() + # move walls down + cNode = self.find("**/wall") + cNode.setZ(cNode, -.75) + # duplicate the floor and move it down to crate a + # catch effect for low-hopped toons + colNode = self.find("**/collision") + floor = colNode.find("**/floor") + floor2 = floor.copyTo(colNode) + floor2.setZ(floor2, -.75) + + """ + # incorporate the entity's overall scale + self.model.setScale(self.getScale()) + self.setScale(1) + self.model.flattenLight() + """ + + if self.goonHatType is not 'none': + self.goonType = {'hardhat':'pg','security':'sg'}[self.goonHatType] + self.hat = self.model + ### this was copied from Goon.createHead + if self.goonType == "pg": + self.hat.find("**/security_hat").hide() + elif self.goonType == "sg": + self.hat.find("**/hard_hat").hide() + ### + del self.hat + del self.goonType + + if self.flattenType == 'light': + self.model.flattenLight() + elif self.flattenType == 'medium': + self.model.flattenMedium() + elif self.flattenType == 'strong': + self.model.flattenStrong() + + def setModelPath(self, path): + self.modelPath = path + self.loadModel() + + def setCollisionsOnly(self, collisionsOnly): + self.collisionsOnly = collisionsOnly + self.loadModel() + + def setGoonHatType(self, goonHatType): + self.goonHatType = goonHatType + self.loadModel() diff --git a/otp/src/level/PathEntity.py b/otp/src/level/PathEntity.py new file mode 100644 index 0000000..92c8f34 --- /dev/null +++ b/otp/src/level/PathEntity.py @@ -0,0 +1,73 @@ +from toontown.toonbase.ToontownGlobals import * +from direct.interval.IntervalGlobal import * +from direct.directnotify import DirectNotifyGlobal +import BasicEntities +from toontown.suit import GoonPathData + +class PathEntity(BasicEntities.NodePathEntity): + notify = DirectNotifyGlobal.directNotify.newCategory('PathEntity') + def __init__(self, level, entId): + self.pathScale = 1. + BasicEntities.NodePathEntity.__init__(self, level, entId) + self.setPathIndex(self.pathIndex) + + def destroy(self): + BasicEntities.NodePathEntity.destroy(self) + + def setPathIndex(self, pathIndex): + self.pathIndex = pathIndex + pathTableId = GoonPathData.taskZoneId2pathId[self.level.getTaskZoneId()] + if self.pathIndex in GoonPathData.Paths[pathTableId]: + self.path = GoonPathData.Paths[pathTableId][self.pathIndex] + if __dev__: + messenger.send(self.getChangeEvent()) + else: + PathEntity.notify.warning('invalid pathIndex: %s' % pathIndex) + self.path = None + + def makePathTrack(self, node, velocity, name, turnTime=1, + lookAroundNode=None): + track = Sequence(name = name) + if self.path is None: + track.append(WaitInterval(1.)) + return track + assert len(self.path) > 1 + + # end with the starting point at the end, so we have a continuous loop + path = self.path + [self.path[0]] + for pointIndex in range(len(path) - 1): + startPoint = Point3(path[pointIndex]) * self.pathScale + endPoint = Point3(path[pointIndex + 1]) * self.pathScale + # Face the endpoint + v = startPoint - endPoint + + # figure out the angle we have to turn to look at the next point + # Note: this will only look right for paths that are defined in a + # counterclockwise order. Otherwise the goon will always turn the + # "long" way to look at the next point + node.setPos(startPoint[0], startPoint[1],startPoint[2]) + node.headsUp(endPoint[0], endPoint[1], endPoint[2]) + theta = node.getH() % 360 + + track.append( + LerpHprInterval(node, # stop and look around + turnTime, + Vec3(theta,0,0))) + + # Calculate the amount of time we should spend walking + distance = Vec3(v).length() + duration = distance / velocity + + # Walk to the end point + track.append( + LerpPosInterval(node, duration=duration, + pos=endPoint, startPos=startPoint)) + return track + + if __dev__: + def getChangeEvent(self): + return self.getUniqueName('pathChanged') + + def setPathScale(self, pathScale): + self.pathScale = pathScale + self.setPathIndex(self.pathIndex) diff --git a/otp/src/level/PropSpinner.py b/otp/src/level/PropSpinner.py new file mode 100644 index 0000000..49a7a76 --- /dev/null +++ b/otp/src/level/PropSpinner.py @@ -0,0 +1,56 @@ + +import string +from direct.interval.IntervalGlobal import * +from Entity import Entity +from pandac.PandaModules import Vec3 + +class PropSpinner(Entity): + def __init__(self, level, entId): + Entity.__init__(self, level, entId) + self.initProps() + + def destroy(self): + self.destroyProps() + Entity.destroy(self) + + def initProps(self): + topNode = self.getZoneNode() + props = topNode.findAllMatches("**/Prop_*") + spinTracks = Parallel() + for prop in props: + name = prop.getName() + nameParts = name.split('_') + # ['Prop', 'Rotate', 'Y', '15', 'Gear2'] + axis = nameParts[2] + rate = 0 + neg = (string.upper(nameParts[3][0]) == 'N') + if neg: + nameParts[3] = nameParts[3][1:] + try: + rate = int(nameParts[3]) + except: + print 'invalid prop rotate string: %s' % name + if neg: + rate = -rate + prop.setHpr(0,0,0) + if axis == "X": + hpr = Vec3(0, rate*360, 0) + elif axis == "Y": + hpr = Vec3(rate*360, 0, 0) + elif axis == "Z": + hpr = Vec3(0, 0, rate*360) + else: + print 'error', axis + spinTracks.append(LerpHprInterval(prop, 60, hpr)) + spinTracks.loop() + self.spinTracks = spinTracks + + def destroyProps(self): + if hasattr(self, 'spinTracks'): + self.spinTracks.pause() + del self.spinTracks + + if __dev__: + def attribChanged(self, *args): + self.destroyProps() + self.initProps() diff --git a/otp/src/level/Sources.pp b/otp/src/level/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/otp/src/level/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/otp/src/level/SpecUtil.py b/otp/src/level/SpecUtil.py new file mode 100644 index 0000000..f192832 --- /dev/null +++ b/otp/src/level/SpecUtil.py @@ -0,0 +1,125 @@ +"""SpecUtil module: contains utility functions for creating and managing level specs""" + +import direct.directbase.DirectStart +from pandac.PandaModules import * +import LevelSpec +import LevelConstants +import LevelUtil +from direct.showbase.PythonUtil import list2dict +import EntityTypes +import types + +""" +TO CREATE A NEW SPEC: +from otp.level import SpecUtil +from toontown.coghq import FactoryEntityTypes +SpecUtil.makeNewSpec('$TOONTOWN/src/coghq/FactoryMockupSpec.py', 'phase_9/models/cogHQ/SelbotLegFactory', FactoryEntityTypes) +""" +def makeNewSpec(filename, modelPath, entTypeModule=EntityTypes): + """call this to create a new level spec for the level model at 'modelPath'. + Spec will be saved as 'filename'""" + spec = LevelSpec.LevelSpec() + # make every zone visible from every other zone + privUpdateSpec(spec, modelPath, entTypeModule, newZonesGloballyVisible=1) + # expand any env vars, then convert to an OS-correct path + fname = Filename.expandFrom(filename).toOsSpecific() + spec.saveToDisk(fname, makeBackup=0) + print 'Done.' + +""" +FOR SAME LEVEL MODEL PATH: +from otp.level import SpecUtil +from toontown.coghq import SellbotLegFactorySpec +from toontown.coghq import FactoryEntityTypes +SpecUtil.updateSpec(SellbotLegFactorySpec, FactoryEntityTypes) + +FOR DIFFERENT LEVEL MODEL PATH: +from otp.level import SpecUtil +from toontown.coghq import SellbotLegFactorySpec +from toontown.coghq import FactoryEntityTypes +SpecUtil.updateSpec(SellbotLegFactorySpec, FactoryEntityTypes, '/i/beta/toons/maya/work/CogHeadquarters/CogFactoriesInteriors/AllFactories/LegFactory/SelbotLegFactory_v##s#.mb') +""" + +def updateSpec(specModule, entTypeModule=EntityTypes, modelPath=None): + """Call this to update an existing levelSpec to work with a new level + model. If the level model has a new path, pass it in as 'modelPath'. + specModule must be a Python module""" + spec = LevelSpec.LevelSpec(specModule) + privUpdateSpec(spec, modelPath, entTypeModule) + spec.saveToDisk() + print 'Done.' + +def privUpdateSpec(spec, modelPath, entTypeModule, newZonesGloballyVisible=0): + """internal: take a spec and update it to match its level model + If newZonesGloballyVisible is true, any new zones will be added to the + visibility lists for every zone. + """ + assert __dev__ + assert type(entTypeModule) is types.ModuleType + import EntityTypeRegistry + etr = EntityTypeRegistry.EntityTypeRegistry(entTypeModule) + spec.setEntityTypeReg(etr) + + if modelPath is None: + modelPath = spec.getEntitySpec( + LevelConstants.LevelMgrEntId)['modelFilename'] + else: + spec.doSetAttrib(LevelConstants.LevelMgrEntId, + 'modelFilename', modelPath) + + # load the model + # disable texture loading for speed + TexturePool.setFakeTextureImage( + '/i/alpha/player/install/ttmodels/src/fonts/ImpressBT.rgb') + model = loader.loadModel(modelPath) + assert model is not None + TexturePool.clearFakeTextureImage() + # get the model's zone info + zoneNum2node = LevelUtil.getZoneNum2Node(model) + zoneNums = zoneNum2node.keys() + + # what zone entities do we have specs for? + type2ids = spec.getEntType2ids(spec.getAllEntIds()) + type2ids.setdefault('zone', []) + zoneEntIds = type2ids['zone'] + + def removeZoneEntity(entId, spec=spec, zoneEntIds=zoneEntIds): + spec.removeEntity(entId) + zoneEntIds.remove(entId) + + def insertZoneEntity(zoneNum, spec=spec, zoneEntIds=zoneEntIds): + spec.insertEntity(zoneNum, 'zone', LevelConstants.UberZoneEntId) + spec.doSetAttrib(zoneNum, 'name', 'zone%s' % zoneNum) + zoneEntIds.append(zoneNum) + + # prune zone entities that reference zones that no longer exist + removedZoneNums = [] + for entId in list(zoneEntIds): + if entId not in zoneNums: + print 'zone %s no longer exists; removing' % entId + removeZoneEntity(entId) + removedZoneNums.append(entId) + + # add new zone entities for new zones + newZoneNums = [] + for zoneNum in zoneNums: + if zoneNum not in zoneEntIds: + newZoneNums.append(zoneNum) + + print 'adding new zone entity %s' % zoneNum + insertZoneEntity(zoneNum) + # by default, new zone can't see any other zones + spec.doSetAttrib(zoneNum, 'visibility', []) + + if newZonesGloballyVisible: + for entId in zoneEntIds: + visList = list(spec.getEntitySpec(entId)['visibility']) + visDict = list2dict(visList) + for zoneNum in newZoneNums: + visDict[zoneNum] = None + visList = visDict.keys() + visList.sort() + spec.doSetAttrib(entId, 'visibility', visList) + + # make sure none of the zones reference removed zones + spec.removeZoneReferences(removedZoneNums) diff --git a/otp/src/level/VisibilityBlocker.py b/otp/src/level/VisibilityBlocker.py new file mode 100644 index 0000000..135b249 --- /dev/null +++ b/otp/src/level/VisibilityBlocker.py @@ -0,0 +1,52 @@ +"""VisibilityBlocker module: contains the VisibilityBlocker class""" + +import Entity + +class VisibilityBlocker: + """This is a mixin class for level entities (see Entity.py) that in some + way 'block' visibility (such as doors) -- entities that can completely + obscure what's behind them. It provides the blocker with a mechanism + whereby they are informed of when it is safe for them to 'unblock' the + visibility and show what's behind them. Without this mechanism, the + blocker might show what's behind it before all of the distributed objects + behind it have been generated.""" + def __init__(self): + self.__nextSetZoneDoneEvent=None + + def destroy(self): + self.cancelUnblockVis() + + def requestUnblockVis(self): + """derived class should call this before the end of the frame in which + they cause the visibility to be extended. okToUnblockVis (see below) + will be called when it's safe to show the new zones.""" + if self.__nextSetZoneDoneEvent is None: + self.__nextSetZoneDoneEvent = self.level.cr.getNextSetZoneDoneEvent() + self.acceptOnce(self.__nextSetZoneDoneEvent, self.okToUnblockVis) + # make sure that a setZone is sent this frame, even if the + # visibility list doesn't change + self.level.forceSetZoneThisFrame() + + def cancelUnblockVis(self): + """ + derived class should call this if they have called + requestUnblockVis, but no longer need that service. For example + the user could have canceled the request that started the + visibility change. + """ + if self.__nextSetZoneDoneEvent is not None: + self.ignore(self.__nextSetZoneDoneEvent) + self.__nextSetZoneDoneEvent = None + + def isWaitingForUnblockVis(self): + """ + returns a boolean for whether there is a requestUnblockVis() pending. + """ + return self.__nextSetZoneDoneEvent is not None + + def okToUnblockVis(self): + """ + derived class should override this func and do the vis unblock + (i.e. open the door, etc.) + """ + self.cancelUnblockVis() diff --git a/otp/src/level/VisibilityExtender.py b/otp/src/level/VisibilityExtender.py new file mode 100644 index 0000000..cb824d6 --- /dev/null +++ b/otp/src/level/VisibilityExtender.py @@ -0,0 +1,67 @@ +"""VisibilityExtender module: contains the VisibilityExtender class""" + +import Entity + +class VisibilityExtender(Entity.Entity): + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + self.initVisExt() + + def initVisExt(self): + self.extended = 0 + self.zoneEntId = self.getZoneEntId() + self.eventName = None + if self.event is not None: + self.eventName = self.getOutputEventName(self.event) + self.accept(self.eventName, self.handleEvent) + + def destroyVisExt(self): + if self.eventName is not None: + self.ignore(self.eventName) + if self.extended: + self.retract() + + def handleEvent(self, doExtend): + if doExtend: + if not self.extended: + self.extend() + else: + if self.extended: + self.retract() + + def extend(self): + """extend the visibility list""" + assert not self.extended + zoneEnt = self.level.getEntity(self.getZoneEntId()) + zoneEnt.incrementRefCounts(self.newZones) + self.extended = 1 + self.level.handleVisChange() + + def retract(self): + """un-extend the visibility list""" + assert self.extended + zoneEnt = self.level.getEntity(self.getZoneEntId()) + zoneEnt.decrementRefCounts(self.newZones) + self.extended = 0 + self.level.handleVisChange() + + def destroy(self): + self.destroyVisExt() + Entity.Entity.destroy(self) + + if __dev__: + def setNewZones(self, newZones): + # we need to call destroyVisExt before accepting the new zone set + extended = self.extended + self.destroyVisExt() + self.newZones = newZones + self.initVisExt() + if extended: + self.extend() + + def attribChanged(self, *args): + extended = self.extended + self.destroyVisExt() + self.initVisExt() + if extended: + self.extend() diff --git a/otp/src/level/ZoneEntity.py b/otp/src/level/ZoneEntity.py new file mode 100644 index 0000000..e10c05a --- /dev/null +++ b/otp/src/level/ZoneEntity.py @@ -0,0 +1,57 @@ +"""ZoneEntity module: contains the ZoneEntity class""" + +import ZoneEntityBase +import BasicEntities + +class ZoneEntity(ZoneEntityBase.ZoneEntityBase, BasicEntities.NodePathAttribs): + def __init__(self, level, entId): + ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId) + + self.nodePath = self.level.getZoneNode(self.entId) + if self.nodePath is None: + if __dev__: + self.level.reportModelSpecSyncError( + 'unknown zoneNum %s; zone was removed from model?' % + self.entId) + else: + self.notify.error('zone %s not found in level model' % + self.entId) + BasicEntities.NodePathAttribs.initNodePathAttribs(self, doReparent=0) + + # dict of zoneNum to 'visible' reference count + self.visibleZoneNums = {} + + # inc ref counts for the zones that are always visible from this zone + self.incrementRefCounts(self.visibility) + + def destroy(self): + # no need to dec our visibility reference counts + BasicEntities.NodePathAttribs.destroy(self) + ZoneEntityBase.ZoneEntityBase.destroy(self) + + def getNodePath(self): + return self.nodePath + + def getVisibleZoneNums(self): + return self.visibleZoneNums.keys() + + # call these with lists of zoneNums to increment or decrement their + # 'visible' reference counts + # zone is visible as long as its ref count is nonzero + def incrementRefCounts(self, zoneNumList): + for zoneNum in zoneNumList: + self.visibleZoneNums.setdefault(zoneNum, 0) + self.visibleZoneNums[zoneNum] += 1 + def decrementRefCounts(self, zoneNumList): + for zoneNum in zoneNumList: + self.visibleZoneNums[zoneNum] -= 1 + if self.visibleZoneNums[zoneNum] == 0: + del self.visibleZoneNums[zoneNum] + + if __dev__: + def setVisibility(self, visibility): + self.decrementRefCounts(self.visibility) + self.visibility = visibility + self.incrementRefCounts(self.visibility) + + self.level.handleVisChange() diff --git a/otp/src/level/ZoneEntityAI.py b/otp/src/level/ZoneEntityAI.py new file mode 100644 index 0000000..4c8e9db --- /dev/null +++ b/otp/src/level/ZoneEntityAI.py @@ -0,0 +1,16 @@ +"""ZoneEntityAI module: contains the ZoneEntityAI class""" + +import ZoneEntityBase + +class ZoneEntityAI(ZoneEntityBase.ZoneEntityBase): + def __init__(self, level, entId): + ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId) + + # allocate a network zoneId for this zone + # there is error checking in air.allocateZone + self.setZoneId(self.level.air.allocateZone()) + + def destroy(self): + if not self.isUberZone(): + self.level.air.deallocateZone(self.getZoneId()) + ZoneEntityBase.ZoneEntityBase.destroy(self) diff --git a/otp/src/level/ZoneEntityBase.py b/otp/src/level/ZoneEntityBase.py new file mode 100644 index 0000000..48f9a07 --- /dev/null +++ b/otp/src/level/ZoneEntityBase.py @@ -0,0 +1,28 @@ +"""ZoneEntityBase module: contains the ZoneEntityBase class""" + +import Entity +import LevelConstants + +class ZoneEntityBase(Entity.Entity): + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + self.zoneId = None + + def destroy(self): + del self.zoneId + Entity.Entity.destroy(self) + + def isUberZone(self): + return self.entId == LevelConstants.UberZoneEntId + + def setZoneId(self, zoneId): + """set the network zoneId that this zone entity corresponds to""" + self.zoneId = zoneId + + def getZoneId(self): + """network zoneId""" + return self.zoneId + + def getZoneNum(self): + """zoneNum from model / entityId""" + return self.entId diff --git a/otp/src/level/__init__.py b/otp/src/level/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/leveleditor/.cvsignore b/otp/src/leveleditor/.cvsignore new file mode 100644 index 0000000..b537295 --- /dev/null +++ b/otp/src/leveleditor/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +*.pyc +pp.dep diff --git a/otp/src/leveleditor/LevelEditor.py b/otp/src/leveleditor/LevelEditor.py new file mode 100644 index 0000000..4a5483a --- /dev/null +++ b/otp/src/leveleditor/LevelEditor.py @@ -0,0 +1,33 @@ +""" +This is just a sample code. + +LevelEditor, ObjectHandler, ObjectPalette should be rewritten +to be game specific. +""" + +from direct.leveleditor.LevelEditorBase import * +from ObjectHandler import * +from ObjectPalette import * +from LevelEditorUI import * +from ProtoPalette import * + +class LevelEditor(LevelEditorBase): + """ Class for Panda3D LevelEditor """ + def __init__(self): + LevelEditorBase.__init__(self) + + # define your own config file similar to this + self.settingsFile = os.path.dirname(__file__) + '/LevelEditor.cfg' + + # If you have your own ObjectPalette and ObjectHandler + # connect them in your own LevelEditor class + self.objectPalette = ObjectPalette() + self.objectHandler = ObjectHandler(self) + self.protoPalette = ProtoPalette() + + # LevelEditorUI class must declared after ObjectPalette + self.ui = LevelEditorUI(self) + + # When you define your own LevelEditor class inheriting LevelEditorBase + # you should call self.initialize() at the end of __init__() function + self.initialize() diff --git a/otp/src/leveleditor/LevelEditorStart.py b/otp/src/leveleditor/LevelEditorStart.py new file mode 100644 index 0000000..3ea17da --- /dev/null +++ b/otp/src/leveleditor/LevelEditorStart.py @@ -0,0 +1,7 @@ +import LevelEditor + +base.le = LevelEditor.LevelEditor() +# You should define LevelEditor instance as +# base.le so it can be reached in global scope + +run() diff --git a/otp/src/leveleditor/LevelEditorUI.py b/otp/src/leveleditor/LevelEditorUI.py new file mode 100644 index 0000000..46da49c --- /dev/null +++ b/otp/src/leveleditor/LevelEditorUI.py @@ -0,0 +1,14 @@ +from direct.leveleditor.LevelEditorUIBase import * + +class LevelEditorUI(LevelEditorUIBase): + """ Class for Panda3D LevelEditor """ + frameWidth = 800 + frameHeight = 600 + appversion = '1.0' + appname = 'OTP Level Editor' + + def __init__(self, editor, *args, **kw): + if not kw.get('size'): + kw['size'] = wx.Size(self.frameWidth, self.frameHeight) + LevelEditorUIBase.__init__(self, editor, *args, **kw) + diff --git a/otp/src/leveleditor/ObjectHandler.py b/otp/src/leveleditor/ObjectHandler.py new file mode 100644 index 0000000..8319a30 --- /dev/null +++ b/otp/src/leveleditor/ObjectHandler.py @@ -0,0 +1,108 @@ +""" +This is just a sample code. + +LevelEditor, ObjectHandler, ObjectPalette should be rewritten +to be game specific. +""" + +from direct.actor import Actor +from direct.leveleditor import ObjectGlobals as OG + +class ObjectHandler: + """ ObjectHandler will create and update objects """ + + def __init__(self, editor): + self.editor = editor + + def createDoubleSmiley(self, horizontal=True): + root = render.attachNewNode('doubleSmiley') + a = loader.loadModel('models/smiley.egg') + b = loader.loadModel('models/smiley.egg') + if horizontal: + a.setName('left') + b.setName('right') + a.setPos(-1, 0, 0) + b.setPos(1, 0, 0) + else: + a.setName('top') + b.setName('bottom') + a.setPos(0, 0, 1) + b.setPos(0, 0, -1) + + a.reparentTo(root) + b.reparentTo(root) + return root + + def updateDoubleSmiley(self, val, obj): + objNP = obj[OG.OBJ_NP] + if objNP.find('left'): + objNP.find('left').setPos(-1 * val, 0, 0) + objNP.find('right').setPos(val, 0, 0) + else: + objNP.find('top').setPos(0, 0, 1 * val) + objNP.find('bottom').setPos(0, 0, -1 * val) + + def updateSmiley(self, val, obj): + objNP = obj[OG.OBJ_NP] + if base.direct: + base.direct.deselectAllCB() + for child in objNP.findAllMatches("+GeomNode"): + child.removeNode() + + for i in range(val): + a = loader.loadModel(obj[OG.OBJ_MODEL]) + b = a.find("+GeomNode") + b.setPos(0, i*2, 0) + b.reparentTo(objNP) + a.removeNode() + + def createPanda(self): + pandaActor = PandaActor() + return pandaActor + + def createGrass(self): + environ = loader.loadModel("models/environment.egg") + environ.setScale(0.25,0.25,0.25) + #environ.setPos(-8,42,0) + return environ + + def createToontownTree(self): + root = loader.loadModel('phase_3.5/models/props/trees.bam') + root.findAllMatches("**/prop_tree_fat/*/prop_tree_*").hide() + root.findAllMatches("**/prop_tree_small/*/prop_tree_*").hide() + root.findAllMatches("**/prop_tree_large/*/prop_tree_*").hide() + root.find("**/prop_tree_fat_no_box_ul").show() + return root + + def updateToontownTree(self, val, obj): + objNP = obj[OG.OBJ_NP] + objNP.findAllMatches("**/prop_tree_fat/*/prop_tree_*").hide() + objNP.findAllMatches("**/prop_tree_small/*/prop_tree_*").hide() + objNP.findAllMatches("**/prop_tree_large/*/prop_tree_*").hide() + objNP.find("**/%s"%val).show() + + def createStreetlightTT(self): + root = loader.loadModel('phase_3.5/models/props/streetlight_TT.bam') + root.find("**/prop_post_three_light").hide() + root.find("**/prop_post_sign").hide() + return root + + def updateStreetlightTT(self, val, obj): + objNP = obj[OG.OBJ_NP] + objNP.find("**/prop_post_one_light").hide() + objNP.find("**/prop_post_three_light").hide() + objNP.find("**/prop_post_sign").hide() + objNP.find("**/%s"%val).show() + + def createTortuga(self): + root = loader.loadModel('models/islands/pir_m_are_isl_tortuga.bam') + root.find("**/minimap").hide() + return root + +class PandaActor(Actor.Actor): + def __init__(self): + Actor.Actor.__init__(self, "models/panda-model.egg") + self.setScale(0.005) + + + diff --git a/otp/src/leveleditor/ObjectPalette.py b/otp/src/leveleditor/ObjectPalette.py new file mode 100644 index 0000000..6359749 --- /dev/null +++ b/otp/src/leveleditor/ObjectPalette.py @@ -0,0 +1,320 @@ +""" +This is just a sample code. + +LevelEditor, ObjectHandler, ObjectPalette should be rewritten +to be game specific. + +You can define object template class inheriting ObjectBase +to define properties shared by multiple object types. +When you are defining properties +you should specify their name, UI type, data type, +update function, default value, and value range. + +Then you need implement ObjectPalette class inheriting ObjectPaletteBase, +and in the populate function you can define ObjectPalette tree structure. +""" + +from direct.leveleditor.ObjectPaletteBase import * + +class ObjectProp(ObjectBase): + def __init__(self, *args, **kw): + ObjectBase.__init__(self, *args, **kw) + self.properties['Abc'] =[OG.PROP_UI_RADIO, # UI type + OG.PROP_STR, # data type + None, # update function + 'a', # default value + ['a', 'b', 'c']] # value range + + +class ObjectSmiley(ObjectProp): + def __init__(self, *args, **kw): + ObjectProp.__init__(self, *args, **kw) + self.properties['123'] = [OG.PROP_UI_COMBO, + OG.PROP_INT, + None, + 1, + [1, 2, 3]] + + +class ObjectDoubleSmileys(ObjectProp): + def __init__(self, *args, **kw): + ObjectProp.__init__(self, *args, **kw) + self.properties['Distance'] = [OG.PROP_UI_SLIDE, + OG.PROP_FLOAT, + ('.updateDoubleSmiley', + {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}), + # In this case, an update function for property is defined + # so whenever you change the value of this property from UI + # this update function will be called with these arguments. + # OG.ARG_VAL will be replaced by the value from UI. + # OG.ARG_OBJ will be replaced by object data structure. + # When an update function is starting with . + # it means this function belongs to the default objectHandler. + 1.0, [0, 10, 0.1]] + + +class ObjectPalette(ObjectPaletteBase): + def __init__(self): + ObjectPaletteBase.__init__(self) + + def populate(self): + # Create a group called 'Prop' in the ObjectPalette tree + self.add('Prop') + + self.add('Pirates', 'Prop') + for propName in ['barrel', 'basket', 'bench', 'bottle_brown', 'bucket', + 'candle', 'cart_broken', 'cart_flat', 'cart_reg', 'chair_bar', 'crate', 'cup_tin', + 'ham', 'jug', 'lamp_candle', 'pir_m_prp_fnc_woodfenceGate', 'pitcher_brown', + 'rock_1_floor', 'treasureChest' ]: + self.add(ObjectBase(name=propName, model='models/props/%s.bam'%propName), 'Pirates') + + + self.add('Toontown', 'Prop') + for propName in [ + 'mailbox_TT', 'phone', 'receiver', 'shredder', 'spray', 'tart', 'big_planter' + ]: + self.add(ObjectBase(name=propName, model='phase_3.5/models/props/%s.bam'%propName), 'Toontown') + + for propName in [ + 'mickey_on_horse', + 'toontown_central_fountain', + 'piers_tt', + ]: + self.add(ObjectBase(name=propName, model='phase_4/models/props/%s.bam'%propName), 'Toontown') + + for propName in ['baseball', 'dagger', 'lawbook', 'safe', 'trashcan_TT', 'TT_hydrant']: + self.add(ObjectBase(name=propName, model='phase_5/models/props/%s.bam'%propName), 'Toontown') + + self.add(ObjectBase(name='tree', + createFunction = ('.createToontownTree', {}), + properties={'Tree Type':[OG.PROP_UI_COMBO, + OG.PROP_STR, + ('.updateToontownTree', + {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}), + 'prop_tree_fat_no_box_ul', + ['prop_tree_fat_no_box_ul', + 'prop_tree_fat_brickbox_ul', + 'prop_tree_fat_no_box_ur', + 'prop_tree_fat_brickbox_ur', + 'prop_tree_large_no_box_ul', + 'prop_tree_large_woodbox_ul', + 'prop_tree_large_brickbox_ul', + 'prop_tree_large_no_box_ur', + 'prop_tree_large_woodbox_ur', + 'prop_tree_large_brickbox_ur', + 'prop_tree_small_no_box_ul', + 'prop_tree_small_woodbox_ul', + 'prop_tree_small_brickbox_ul', + 'prop_tree_small_nobox_ur', + 'prop_tree_small_woodbox_ur', + 'prop_tree_small_brickbox_ur', + ], + ], + }), + 'Toontown') + + self.add(ObjectBase(name='streetlight_TT', + createFunction = ('.createStreetlightTT', {}), + properties={'Light Type':[OG.PROP_UI_COMBO, + OG.PROP_STR, + ('.updateStreetlightTT', + {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}), + 'prop_post_one_light', + ['prop_post_one_light', + 'prop_post_three_light', + 'prop_post_sign' + ], + ], + }), + 'Toontown') + + # Create a group called 'Double Smileys' under 'Prop' group + self.add('Double Smileys', 'Prop') + + # Add an object type 'Smiley' which is inheriting ObjectSmiley template + # and have following properties. + self.add(ObjectSmiley(name='Smiley', + model='models/smiley.egg', + models=['models/smiley.egg', + 'models/frowney.egg', + 'models/jack.egg'], + # when an object is just a simple geometry, you can define + # model, and models like this + # instead of defining createFunction + properties={'Happy':[OG.PROP_UI_CHECK, + OG.PROP_BOOL, + None, + True], + 'Number':[OG.PROP_UI_SPIN, + OG.PROP_INT, + ('.updateSmiley', + {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}), + 1, [1, 10]], + }), + 'Prop') # This object type will be added under the 'Prop' group. + self.add(ObjectDoubleSmileys(name='H Double Smiley', + createFunction = ('.createDoubleSmiley', {})), + # When the createFunction is defined like this, + # this function will be called to create the object. + # When a create function is starting with . + # it means this function belongs to the default objectHandler. + 'Double Smileys') + + self.add(ObjectDoubleSmileys(name='V Double Smiley', + createFunction = ('.createDoubleSmiley', {'horizontal':False})), + # You can specify argument for the create function, too + 'Double Smileys') + + self.add('Animal') + self.add(ObjectBase(name='Panda', + createFunction = ('.createPanda', {}), + anims = ['models/panda-walk4.egg',], + properties = {}), + 'Animal') + + self.add(ObjectBase(name='dog', model='models/char/dog_hi.bam', + anims=['models/char/dog_idle_sitting.bam', + 'models/char/dog_idle_standing.bam', + 'models/char/dog_bark_sitting.bam', + 'models/char/dog_walk.bam'], actor=True), + 'Animal') + + self.add('BG') + self.add(ObjectBase(name='Grass', + createFunction = ('.createGrass', {}), + properties = {}), + 'BG') + + self.add(ObjectBase(name='HQ_interior', model='phase_3.5/models/modules/HQ_interior.bam'), 'BG') + self.add(ObjectBase(name='Toontown_Central', model='phase_4/models/neighborhoods/toontown_central.bam'), 'BG') + self.add(ObjectBase(name='tortuga', + createFunction = ('.createTortuga', {}), + ), + 'BG') + + self.add('Buildings') + self.add('ToonBuildings', 'Buildings') + for modelName in [ + 'partyGate_TT', + 'trolley_station_TT', + 'safe_zone_tunnel_TT', + 'Speedway_Tunnel', + 'bank', + 'library', + 'school_house', + 'mercantile', + 'gagShop_TT', + 'PetShopExterior_TT', + 'clothshopTT', + 'gazebo', + 'toonhall', + ]: + self.add(ObjectBase(name=modelName, model='phase_4/models/modules/%s.bam'%modelName), 'ToonBuildings') + + self.add(ObjectBase(name='hqTT', model='phase_3.5/models/modules/hqTT.bam'), 'ToonBuildings') + + self.add('PiratesBuildings', 'Buildings') + self.add(ObjectBase(name='Pier', model='models/islands/pier_platform.bam', + models=['models/islands/pier_platform', + 'models/islands/pier_port_royal_2deck', + 'models/islands/pier_1_kings', + 'models/islands/pier_2_kings', + 'models/islands/pier_port_royal_1deck', + 'models/islands/pier_walkway', + 'models/props/ship_parking_booth', + 'models/props/ship_valet', + 'models/islands/pier_midisland',]), + 'PiratesBuildings') + + self.add(ObjectBase(name='Building Exterior', + model='models/buildings/jail_exterior', + models=['models/buildings/jail_exterior', + 'models/buildings/fort_door', + 'models/buildings/shanty_cellar_door', + 'models/buildings/shanty_guildhall_exterior', + 'models/buildings/shanty_gypsywagon_exterior', + 'models/buildings/shanty_tavern_exterior', + 'models/buildings/shanty_repairshop_exterior', + 'models/buildings/shanty_leanto_A', + 'models/buildings/shanty_leanto_B', + 'models/buildings/shanty_npc_house_a_exterior', + 'models/buildings/shanty_npc_house_combo_A', + 'models/buildings/shanty_npc_house_combo_B', + 'models/buildings/shanty_npc_house_combo_C', + 'models/buildings/shanty_npc_house_combo_D', + 'models/buildings/shanty_npc_house_combo_E', + 'models/buildings/shanty_npc_house_combo_F', + 'models/buildings/shanty_npc_house_combo_G', + 'models/buildings/shanty_npc_house_combo_H', + 'models/buildings/shanty_npc_house_combo_I', + 'models/buildings/shanty_npc_house_combo_J', + 'models/buildings/shanty_npc_house_combo_Platform', + 'models/buildings/shanty_signpost', + 'models/buildings/spanish_npc_house_a_exterior', + 'models/buildings/spanish_npc_house_b_exterior', + 'models/buildings/spanish_npc_house_c_exterior', + 'models/buildings/spanish_npc_house_d_exterior', + 'models/buildings/spanish_npc_house_e_exterior', + 'models/buildings/spanish_npc_house_f_exterior', + 'models/buildings/spanish_npc_house_g_exterior', + 'models/buildings/spanish_npc_house_i_exterior', + 'models/buildings/spanish_npc_house_j_exterior', + 'models/buildings/spanish_npc_house_k_exterior', + 'models/buildings/spanish_npc_house_l_exterior', + 'models/buildings/spanish_npc_house_n_exterior', + 'models/buildings/spanish_npc_house_o_exterior', + 'models/buildings/spanish_npc_house_p_exterior', + 'models/buildings/spanish_npc_house_q_exterior', + 'models/buildings/spanish_tavern_exterior', + 'models/buildings/pir_m_bld_spn_tavern', + 'models/buildings/pir_m_bld_spn_tavern_burned', + 'models/buildings/pir_m_bld_spn_tavern_door', + 'models/town/bar_exterior', + 'models/buildings/spanish_npc_attach_fullarchL', + 'models/buildings/spanish_npc_attach_fullarchR', + 'models/buildings/spanish_npc_attach_fullnostonearchL', + 'models/buildings/spanish_npc_attach_fullnostonearchR', + 'models/buildings/spanish_npc_attach_halfarchL', + 'models/buildings/spanish_npc_attach_halfarchR', + 'models/buildings/spanish_npc_attach_halfarchflatL', + 'models/buildings/spanish_npc_attach_halfarchflatR', + 'models/buildings/spanish_npc_attach_halfarchwindowL', + 'models/buildings/spanish_npc_attach_halfarchwindowR', + 'models/buildings/spanish_npc_attach_halfporchL', + 'models/buildings/spanish_npc_attach_halfporchR', + 'models/buildings/burned_gate', + 'models/buildings/burned_half_house', + 'models/buildings/burned_house', + 'models/buildings/burned_woods', + 'models/buildings/english_a', + 'models/buildings/english_b', + 'models/buildings/english_c', + 'models/buildings/english_d', + 'models/buildings/english_e', + 'models/buildings/english_f', + 'models/buildings/english_g', + 'models/buildings/english_h', + 'models/buildings/english_i', + 'models/buildings/english_j', + 'models/buildings/english_k', + 'models/buildings/english_k_tutorial', + 'models/buildings/english_l', + 'models/buildings/english_m', + 'models/buildings/english_n', + 'models/buildings/english_corner_a', + 'models/buildings/english_corner_b', + 'models/buildings/english_corner_c', + 'models/buildings/english_corner_c2', + 'models/buildings/english_corner_d', + 'models/buildings/english_corner_e', + 'models/buildings/english_corner_f', + 'models/buildings/english_mansion', + 'models/buildings/pir_m_bld_eng_mansion', + 'models/buildings/pir_m_bld_eng_mansion_burned', + 'models/buildings/pir_m_bld_eng_mansion_door', + 'models/buildings/fort_eitc', + 'models/buildings/fort_small_cave', + 'models/islands/kingshead_zero', + 'models/buildings/pir_m_bld_frt_innerWall_gate', + ]), + 'PiratesBuildings') diff --git a/otp/src/leveleditor/ProtoPalette.py b/otp/src/leveleditor/ProtoPalette.py new file mode 100644 index 0000000..13d41c4 --- /dev/null +++ b/otp/src/leveleditor/ProtoPalette.py @@ -0,0 +1,10 @@ +""" +Palette for Prototyping +""" + +from direct.leveleditor.ProtoPaletteBase import * + +class ProtoPalette(ProtoPaletteBase): + def __init__(self): + self.dirname = os.path.dirname(__file__) + ProtoPaletteBase.__init__(self) diff --git a/otp/src/leveleditor/Sources.pp b/otp/src/leveleditor/Sources.pp new file mode 100644 index 0000000..745e476 --- /dev/null +++ b/otp/src/leveleditor/Sources.pp @@ -0,0 +1,13 @@ +// In the short term, we'll just disable the installation of this +// directory unless you have the ctattach tools in effect (which +// generally indicates that you're a member of the VR Studio). + +// Finally, it's moved from DIRECT to OTP + +#define BUILD_DIRECTORY $[CTPROJS] + +// Install scripts for building zipfiles (leveleditor and RobotToonManager) +#if $[CTPROJS] + #define INSTALL_SCRIPTS printdir printlib copyfiles copyfiles.pl +#endif + diff --git a/otp/src/leveleditor/__init__.py b/otp/src/leveleditor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/otp/src/leveleditor/copyfiles b/otp/src/leveleditor/copyfiles new file mode 100644 index 0000000..34838e9 --- /dev/null +++ b/otp/src/leveleditor/copyfiles @@ -0,0 +1,60 @@ +#! /bin/sh + +if [ "$1" = "-d" ] +then + destdir=$2 + printfilesCmd=$3 + debug_state="-d" +else + destdir=$1 + printfilesCmd=$2 + debug_state="" +fi + +if [ "${destdir}" = "" -o "${printfilesCmd}" = "" ] +then + echo "Usage: copyfiles [-d] destdir printfilesCmd" + exit 1 +fi + +if [ ! -d ${destdir} ] +then + echo "Error: destdir must be a directory" + exit 1 +fi + +for file in `$printfilesCmd $debug_state` +do + copyTo=${destdir} + + moduleDir=`dirname ${file}` + srcDir=`dirname ${moduleDir}` + packageDir=`dirname ${srcDir}` + + moduleDir=`basename ${moduleDir}` + srcDir=`basename ${srcDir}` + packageDir=`basename ${packageDir}` + + if [ `basename ${file} .py` != `basename ${file}` -o \ + `basename ${file} .pyz` != `basename ${file}` ]; then + # The file is a Python file. Is it in a src directory? + if [ ${srcDir} = src ]; then + copyPkg=${destdir}/${packageDir} + copyTo=${copyPkg}/${moduleDir} + (mkdir ${copyPkg}; touch ${copyPkg}/__init__.py) > /dev/null 2>&1 + (mkdir ${copyTo}; touch ${copyTo}/__init__.py) > /dev/null 2>&1 + elif [ ${moduleDir} = pandac ]; then + copyTo=${destdir}/pandac + (mkdir ${copyTo}; touch ${copyTo}/__init__.py) > /dev/null 2>&1 + fi + fi + + if cp -R ${file} ${copyTo} + then + echo "copying ${file} to ${copyTo}" + else + echo "ERROR: could not find ${file}" + exit 1 + fi +done + diff --git a/otp/src/leveleditor/copyfiles.pl b/otp/src/leveleditor/copyfiles.pl new file mode 100644 index 0000000..ee1a03b --- /dev/null +++ b/otp/src/leveleditor/copyfiles.pl @@ -0,0 +1,136 @@ +#!perl -w +use strict; +#use File::Copy; +use File::Basename; + +my ($destdir, $printfilesCmd, $debug_state); + +if ($ARGV[0] && $ARGV[0] eq '-d') { + $destdir = $ARGV[1]; + $printfilesCmd = $ARGV[2]; + $debug_state = "-d"; +} +else { + $destdir = $ARGV[0]; + $printfilesCmd = $ARGV[1]; + $debug_state = ""; +} + +if (!$destdir || !$printfilesCmd) { + print "Usage: copyfiles.pl [-d] destdir printfilesCmd\n"; + exit 1; +} + +if (! -d $destdir ) { + print "Error: destdir must be a directory\n"; + exit 1; +} + +my @fileline = `$printfilesCmd $debug_state`; + +my (%tree, %package); +my ($moduleDir, $packageDir, $installDir); +my ($file); + +sub notsource($) +{ + my ($line) = @_; + print "unrecognized line:|$line|\n"; +} + +sub add_file($ $) +{ + my ($dir,$line) = @_; + if (! exists $package{$dir}) { + $package{$dir} = []; # create a new array entry if module/package key doesn't exist + } + push @{$package{$dir}}, $line; # add the source dir of this file to module/package's list +} + +foreach my $line (@fileline) +{ + chomp($line); + #print "$line\n"; + + + if ( ($line =~ /CVS/) + || ($line =~ /Opt\d\-/) + || ($line =~ /\.cxx|\.obj|\.h|\.I|\.in|\.pdb|\.pp|\.cvsignore|Makefile/) + ) + { # skip if ... + print "skipping $line\n"; + next; + } + + $installDir = ''; + + if ($line =~ /\/([^\/]+)\/src\/([^\/]+)\/(.+)$/ && $3) + { # $1 is module aka dtool or pirates; $2 is package aka ai or battle + if ($3) + { + $moduleDir = $1; + $packageDir = $2; + $file = $3; + + if (-d $file) { # don't handle bare directories + notsource($file); + next; + } + $installDir = "$moduleDir/$packageDir" + if $file =~ /\.py/; # tree install only for Python files + + $tree{$moduleDir} = 1; + #print "recognized module:$moduleDir package:$packageDir\n"; + } + else { + notsource($line); # don't know how to handle this file + } + } + elsif ($line =~ /pandac/) + { + $installDir = 'pandac'; + } + + add_file($installDir, $line); + #print "line:|$line|"; + #print "$1 $2 $3 $4\n"; +} + +############################################################################# + +print "\nSTARTING COPY\n\n"; + +sub echo_cmd($) +{ + my ($cmd) = @_; + print "$cmd\n"; + system($cmd); +} + +my $cmd; +foreach my $dir (keys %tree) +{ # create the master directories + echo_cmd("mkdir -p $destdir/$dir"); + echo_cmd("touch $destdir/$dir/__init__.py"); # linkage file for python +} + +my ($fileline, $finaldir, $files); +foreach my $key (keys %package) +{ # loop and copy each cluster of files + $finaldir = "$destdir/$key"; + print "\ncopying to $finaldir:\n"; + + $files = $package{$key}; + foreach my $file (@{$files}) + { # dump list of files being copied + print "$file\n"; + } + + $fileline = join(' ', @{$files}); # create clustered command line + #print "-t $destdir/$key $fileline\n"; + system("mkdir $destdir/$key"); + system("cp -rpt $destdir/$key $fileline"); # copy +} +print "\n"; + +0; diff --git a/otp/src/leveleditor/printdir b/otp/src/leveleditor/printdir new file mode 100644 index 0000000..afe19d2 --- /dev/null +++ b/otp/src/leveleditor/printdir @@ -0,0 +1,10 @@ +#! /bin/sh + +# Some Cygwin users don't have find set on on their path correctly, so +# we have to be explicit. +if [ -x /bin/find ]; then + /bin/find $1 -name \*$2 -print +else + find $1 -name \*$2 -print +fi + diff --git a/otp/src/leveleditor/printlib b/otp/src/leveleditor/printlib new file mode 100644 index 0000000..315838b --- /dev/null +++ b/otp/src/leveleditor/printlib @@ -0,0 +1,5 @@ +#! /bin/sh + +grep -l "CMODULE \[$2\]" $1/*.py + + diff --git a/otp/src/localization/otp_intl_mega.patch b/otp/src/localization/otp_intl_mega.patch new file mode 100644 index 0000000..4fb6205 --- /dev/null +++ b/otp/src/localization/otp_intl_mega.patch @@ -0,0 +1,168 @@ +--- src/configrc/Configrc.cxx 11 Sep 2007 00:04:03 -0000 1.85.2.14.2.2.2.29.2.14 ++++ src/configrc/Configrc.cxx 29 Jul 2008 23:07:57 -0000 +@@ -10,7 +10,8 @@ + #include + #endif + +-#include ++#include "openssl/ssl.h" ++#include "openssl/md5.h" + + // Windows may define this macro inappropriately. + #ifdef X509_NAME +@@ -49,10 +50,12 @@ + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="31CB2F01-72C2-4cf4-B265-450E8817B039"; + const char *activex_control_name = "ttinst-portuguese.dll"; ++# include "portuguese/configrc_nls.h" + #elif defined(USE_FRENCH) + #define TESTSTR _xstr(PRODUCT_NAME) + const char *InstallerGuid="63308B48-F435-42fd-AB0A-3564C7BEF9D7"; + const char *activex_control_name = "ttinst-french.dll"; ++# include "french/configrc_nls.h" + #else + #define TESTSTR "" + const char *activex_control_name = "ttinst.dll"; +@@ -61,7 +64,7 @@ + #endif + + const char *configrc_override_filename = "xrc"; +-const char *ToontownRegKeyName = "SOFTWARE\\Disney\\Disney Online\\Toontown" TESTSTR; //under HKEY_LOCAL_MACHINE ++const char *ToontownRegKeyName = "SOFTWARE\\Disney\\Disney Online\\Toontown" TESTSTR; //under HKEY_CURRENT_USER + + #ifdef _WIN32 + void write_opengl_hardware_info(HKEY hKeyToontown); +@@ -89,7 +92,8 @@ + typedef vector StrVec; + + static void +-filesearch(string rootpath, string pattern, bool bRecursive, bool bSearchForDirs, bool bPrintFileInfo, StrVec &files) { ++filesearch(string rootpath, string pattern, bool bRecursive, bool bSearchForDirs, bool bPrintFileInfo, StrVec &files) ++{ + // typical arguments: filesearch("C:\\temp\\mview","*",true,true,sveclist); + WIN32_FIND_DATA current_file; + +@@ -145,7 +149,8 @@ + } + } + +-static void UninstallActiveX(const char *control_name,const char *control_GUID) { ++static void UninstallActiveX(const char *control_name,const char *control_GUID) ++{ + typedef HRESULT (WINAPI *REMOVECONTROLBYNAME)( + LPCTSTR lpszFile, + LPCTSTR lpszCLSID, +@@ -157,13 +162,13 @@ + REMOVECONTROLBYNAME pfnRemoveControl; + HKEY hKey; + const char *ActiveXCache_RegValName = "ActiveXCache"; +- const char *InternetSettingsRegKeyStr = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; //under HKLM ++ const char *InternetSettingsRegKeyStr = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; //under HKCR + char activeXcontrol_filepath[512]; + DWORD bufsize = sizeof(activeXcontrol_filepath); + string guidstr,searchpattern; + StrVec file_list, control_list; + +- if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE,InternetSettingsRegKeyStr,0,KEY_READ,&hKey)) { ++ if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_CURRENT_USER, InternetSettingsRegKeyStr,0,KEY_READ,&hKey)) { + goto error_exit; + } + +@@ -272,19 +277,16 @@ + // os << "product-name DisneyOnline-US" << endl; + #elif defined(USE_CASTILLIAN) + os << "language castillian" << endl; +- os << "product-name Terra-DMC" << endl; ++ os << "product-name ES" << endl; + #elif defined(USE_JAPANESE) + os << "language japanese" << endl; + os << "product-name JP" << endl; + #elif defined(USE_GERMAN) + os << "language german" << endl; +- os << "product-name T-Online" << endl; +-#elif defined(USE_PORTUGUESE) +- os << "language portuguese" << endl; +- os << "product-name Terra" << endl; +-#elif defined(USE_FRENCH) +- os << "language french" << endl; +- os << "product-name FR" << endl; ++ os << "product-name DE" << endl; ++#elif defined(USE_PORTUGUESE) || defined(USE_FRENCH) ++ os << "language "<
""" + help = """

Note
- Report any prizing issues to chris.barkoff@disney.com
- Report any technical issues to redmond.urbino@disney.com

""" + return (header,body,footer,help) + + @classmethod + def getClothingChoices(cls): + """Return a dictionary of clothing choices. Key is the description, clothingtype are values.""" + values = {} + for key in CatalogClothingItem.ClothingTypes.keys(): + clothingItem = CatalogClothingItem.ClothingTypes[key] + typeOfClothes = clothingItem[0] + styleString = clothingItem[1] + if typeOfClothes in (CatalogClothingItem.AShirt, + CatalogClothingItem.ABoysShirt, CatalogClothingItem.AGirlsShirt): + textString = TTLocalizer.AwardMgrShirt + # if its an exclusive boy or girl item, then say so + if typeOfClothes == CatalogClothingItem.ABoysShirt: + textString += ' ' + TTLocalizer.AwardMgrBoy + elif typeOfClothes == CatalogClothingItem.AGirlsShirt: + textString += ' ' + TTLocalizer.AwardMgrGirl + else: + textString += ' ' + TTLocalizer.AwardMgrUnisex + textString += ' ' + TTLocalizer.ShirtStylesDescriptions[styleString] + if textString in values: + cls.notify.error("Fix %s, descriptions must be unique" % textString) + values[textString] = key + + # do a 2nd for loop to ensure bottoms always goes last + for key in CatalogClothingItem.ClothingTypes.keys(): + clothingItem = CatalogClothingItem.ClothingTypes[key] + typeOfClothes = clothingItem[0] + styleString = clothingItem[1] + if typeOfClothes in (CatalogClothingItem.AShorts, CatalogClothingItem.ABoysShorts, + CatalogClothingItem.AGirlsShorts, CatalogClothingItem.AGirlsSkirt): + textString = "" + if typeOfClothes == CatalogClothingItem.AGirlsSkirt: + textString = TTLocalizer.AwardMgrSkirt + else: + textString = TTLocalizer.AwardMgrShorts + # if its an exclusive boy or girl item, then say so + if typeOfClothes == CatalogClothingItem.ABoysShorts: + textString += ' ' + TTLocalizer.AwardMgrBoy + elif typeOfClothes in (CatalogClothingItem.AGirlsShorts, CatalogClothingItem.AGirlsSkirt): + textString += ' ' + TTLocalizer.AwardMgrGirl + else: + textString += ' ' + TTLocalizer.AwardMgrUnisex + textString += ' ' + TTLocalizer.BottomStylesDescriptions[styleString] + if textString in values: + cls.notify.error("Fix %s, descriptions must be unique" % textString) + values[textString] = key + return values + + @classmethod + def getFurnitureChoices(cls): + """Return a dictionary of furniture choices. Key is the description , values is the furniture type key""" + values = {} + for key in CatalogFurnitureItem.FurnitureTypes.keys(): + furnitureItem = CatalogFurnitureItem.FurnitureTypes[key] + typeOfFurniture = key + # we must not give animted furniture choices, the item type is wrong for it + if typeOfFurniture in CatalogAnimatedFurnitureItem.AnimatedFurnitureItemKeys: + continue + descString = TTLocalizer.AwardManagerFurnitureNames[typeOfFurniture] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getSpeedChatChoices(cls): + """Return a dictionary of speed chat choices. Key is the description , values is the chat id""" + values = {} + allChatItems = CatalogGenerator.getAllChatItemsSold() + for chatItem in allChatItems: + speedChatKey = chatItem.customIndex + textString = OTPLocalizer.CustomSCStrings[speedChatKey] + # I really can't mess with the strings, I'll add the speedChatKey at the end + keyStr = "%5d" % speedChatKey + textString = keyStr + " " + textString + # javascript messes up with a " in the string + textString = textString.replace('"',"'") + if textString in values: + cls.notify.error("fix duplicate %s" % textString) + values[textString] = speedChatKey + return values + + @classmethod + def getEmoteChoices(cls): + """Return a dictionary of emote choices. Key is the description , values is the emote id""" + values = {} + for key in OTPLocalizer.EmoteFuncDict.keys(): + descString = key + emoteIndex = OTPLocalizer.EmoteFuncDict[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = emoteIndex + return values + + @classmethod + def getBeanChoices(cls): + """Return a dictionary of bean choices. Key is the description , values is the amount of beans""" + values = {} + for key in JellybeanRewardValues: + descString = "%3d" % key + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getWallpaperChoices(cls): + """Return a dictionary of wallpaper choices. Key is the description , values is the wallpaper id""" + values = {} + for key in CatalogWallpaperItem.WallpaperTypes.keys(): + # the comments on CatalogWallpaperItem say 2920 to 2980 are problematic, so don't include them + if key in (2920, 2930, 2940, 2950, 2960, 2970, 2980): + continue + # we have duplicate names, just add the key to be unique + descString = "%5d " % key + # ok it looks like some items are never offered, so if there's no name for it + # lets not include it + if key in TTLocalizer.WallpaperNames: + descString += TTLocalizer.WallpaperNames[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getWindowViewChoices(cls): + """Return a dictionary of window choices. Key is the description , values is the wallpaper id""" + values = {} + for key in CatalogWindowItem.WindowViewTypes.keys(): + descString = "" + descString += TTLocalizer.WindowViewNames[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getFlooringChoices(cls): + """Return a dictionary of flooring choices. Key is the description , values is the wallpaper id""" + values = {} + for key in CatalogFlooringItem.FlooringTypes.keys(): + descString = "%5d " % key # add key to make it unique + descString += TTLocalizer.FlooringNames[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getMouldingChoices(cls): + """Return a dictionary of moulding choices. Key is the description , values is the wallpaper id""" + values = {} + for key in CatalogMouldingItem.MouldingTypes.keys(): + descString = "%5d " % key # add key to make it unique + descString += TTLocalizer.MouldingNames[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getWainscotingChoices(cls): + """Return a dictionary of wainscotting choices. Key is the description , values is the wallpaper id""" + values = {} + for key in CatalogWainscotingItem.WainscotingTypes.keys(): + descString = "" #%5d " % key # add key to make it unique + descString += TTLocalizer.WainscotingNames[key] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getPetTrickChoices(cls): + """Return a dictionary of pet trick choices. Key is the description , values is the wallpaper id""" + values = {} + allTricks = CatalogPetTrickItem.getAllPetTricks() + for oneTrick in allTricks: + descString = "" #%5d " % key # add key to make it unique + descString += oneTrick.getName() + key = oneTrick.trickId + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getRentalChoices(cls): + """Return a dictionary of pet rental choices. Key is the description , values is the wallpaper id""" + values = {} + allRentals = CatalogRentalItem.getAllRentalItems() + for oneRental in allRentals: + descString = "" #%5d " % key # add key to make it unique + descString += oneRental.getName() + key = oneRental.typeIndex + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getAnimatedFurnitureChoices(cls): + """Return a dictionary of furniture choices. Key is the description , values is the furniture type key""" + values = {} + for key in CatalogAnimatedFurnitureItem.AnimatedFurnitureItemKeys: + furnitureItem = CatalogFurnitureItem.FurnitureTypes[key] + typeOfFurniture = key + descString = TTLocalizer.AwardManagerFurnitureNames[typeOfFurniture] + if descString in values: + cls.notify.error("Fix %s, descriptions must be unique" % descString) + values[descString] = key + return values + + @classmethod + def getReversedAwardChoices(cls): + """The key in the returned dictionaries should be catalog item numbers, the value should be desc strings.""" + if hasattr(cls, '_revAwardChoices'): + return cls._revAwardChoices + result = {} + awardChoices = cls.getAwardChoices() + for itemType in awardChoices: + reversedDict = {} + curDict = awardChoices[itemType] + for descString in curDict: + itemId = curDict[descString] + if itemId in reversedDict: + cls.notify.error("item %s already in %s" % (itemId, reversedDict)) + reversedDict[itemId] = descString + result[itemType] = reversedDict + cls._revAwardChoices = result + return result + + @classmethod + def getAwardChoices(cls): + """Return a tree of the choices for our drop down list.""" + # static data, cache it + if hasattr(cls, '_awardChoices'): + return cls._awardChoices + result = {} + for itemType in CatalogItemTypes.CatalogItemTypes.values(): + if itemType in (CatalogItemTypes.INVALID_ITEM, CatalogItemTypes.GARDENSTARTER_ITEM, + CatalogItemTypes.POLE_ITEM, CatalogItemTypes.GARDEN_ITEM, + CatalogItemTypes.NAMETAG_ITEM, CatalogItemTypes.TOON_STATUE_ITEM): + # we really can't give this out as awards, so don't add them to the choices + continue + if itemType == CatalogItemTypes.CLOTHING_ITEM: + values = cls.getClothingChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.FURNITURE_ITEM: + values = cls.getFurnitureChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.CHAT_ITEM: + values = cls.getSpeedChatChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.EMOTE_ITEM: + values = cls.getEmoteChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.BEAN_ITEM: + values = cls.getBeanChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.WALLPAPER_ITEM: + values = cls.getWallpaperChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.WINDOW_ITEM: + values = cls.getWindowViewChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.FLOORING_ITEM: + values = cls.getFlooringChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.MOULDING_ITEM: + values = cls.getMouldingChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.WAINSCOTING_ITEM: + values = cls.getWainscotingChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.PET_TRICK_ITEM: + values = cls.getPetTrickChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.RENTAL_ITEM: + values = cls.getRentalChoices() + result[itemType] = values + elif itemType == CatalogItemTypes.ANIMATED_FURNITURE_ITEM: + values = cls.getAnimatedFurnitureChoices() + result[itemType] = values + + else: + values = {"choice1": "Unimplemented One", "choice2": "Unimplemented Two"} + result [itemType] = values + cls._awardChoices = result + return result + + @staticmethod + def getAwardTypeName(awardType): + return TTLocalizer.CatalogItemTypeNames[awardType] + + @classmethod + def getAwardText(cls, awardType, awardId): + rAwardChoices = cls.getReversedAwardChoices() + return rAwardChoices[awardType][awardId] + + def getExperimentalMenu(self): + """Stuff to fool around with.""" + header = """ + + Main Menu: Toontown Award Manager + + + """ + + help = "" + footer = "" + body = """ + +
+
+
+ ToonIds: +
+ SpecialEventId: + +
+ Item Type
+ Item Details +
+ Special Commands: + +
+ + + +
+ +
+ + """ + help = """

Note
- Use give award immediately only to test the award on your own toon. Try to remove the award may fail if 30 minutes have gone by since the award was given.

- Use Nuke All Awards only if the regular players can't enter toontown.

- Report any prizing issues to chris.barkoff@disney.com
- Report any issues to redmond.urbino@disney.com

""" + footer = """

""" + return (header, body, footer, help) diff --git a/toontown/src/rpc/AwardResponses.py b/toontown/src/rpc/AwardResponses.py new file mode 100644 index 0000000..ce380b2 --- /dev/null +++ b/toontown/src/rpc/AwardResponses.py @@ -0,0 +1,13 @@ +""" +Constants file that contains XML and misc. codes for award responses +""" + +# --- Begin XML message constants --- + + +awardGiveFailureXML = """ + + false + %s + +\r\n""" diff --git a/toontown/src/rpc/RATManager.py b/toontown/src/rpc/RATManager.py new file mode 100644 index 0000000..37035aa --- /dev/null +++ b/toontown/src/rpc/RATManager.py @@ -0,0 +1,4 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal + +class RATManager(DistributedObjectGlobal): + pass diff --git a/toontown/src/rpc/RATManagerUD.py b/toontown/src/rpc/RATManagerUD.py new file mode 100644 index 0000000..b8fc2c6 --- /dev/null +++ b/toontown/src/rpc/RATManagerUD.py @@ -0,0 +1,175 @@ +import string +import direct +import socket +from direct.http.WebRequest import WebRequestDispatcher +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.task import Task +from direct.distributed.AsyncRequest import AsyncRequest +from otp.ai import AIMsgTypes +from otp.distributed import OtpDoGlobals + +from toontown.rpc import RATRequests +from toontown.rpc import RATResponses + +class RATManagerUD(DistributedObjectGlobalUD): + """ + Uberdog object for making RAT awards to Toons + """ + notify = directNotify.newCategory('RATManagerUD') + + def __init__(self, air): + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + + self.air = air + + self.HTTPListenPort = uber.RATManagerHTTPListenPort + + self.numServed = 0 + + self.webDispatcher = WebRequestDispatcher() + self.webDispatcher.landingPage.setTitle("RATManager") + self.webDispatcher.landingPage.setDescription("RATManager is a REST-like interface allowing in-game awards from other services.") + self.webDispatcher.registerGETHandler("getToonList",self.handleHTTPGetToonList) + self.webDispatcher.registerGETHandler("giveToonBeansRAT",self.handleHTTPGiveToonBeansRAT) + self.webDispatcher.registerGETHandler("giveToonBeansCS",self.handleHTTPGiveToonBeansCS) + self.webDispatcher.registerGETHandler("getToonPicId",self.handleHTTPGetToonPicId) + self.webDispatcher.registerGETHandler("getToonDNA",self.handleHTTPGetToonDNA) + + self.webDispatcher.listenOnPort(self.HTTPListenPort) + + self.air.setConnectionName("RATManagerUD") + self.air.setConnectionURL("http://%s:%s/" % (socket.gethostbyname(socket.gethostname()),self.HTTPListenPort)) + + + def announceGenerate(self): + assert self.notify.debugCall() + DistributedObjectGlobalUD.announceGenerate(self) + self.webDispatcher.startCheckingIncomingHTTP() + + + # -- HTTP Handlers -- + + def handleHTTPGetToonList(self,replyTo,**kw): + """ + Given an account name, returns the list of toons owned by the account. + Should never fail unless we have a DB issue or connectivity problem somewhere. + """ + accountName = kw.get("accountName",None) + try: + assert isinstance(accountName,str) + except: + self.notify.warning("Invalid getToonList request from %s: %s" % (replyTo.getSourceAddress(),str(kw))) + replyTo.respondXML(RATResponses.getToonListFailureXML % "INVALID_REQUEST") + return + + RATRequests.GetToonIdListRequest(replyTo,accountName) + + + def handleHTTPGiveToonBeansRAT(self,replyTo,**kw): + """ + Request to award quantity of beans to toon. + Used by RAT service. + """ + toonId = kw.get("toonId",'0') + beanAmount = kw.get("beanAmount",'0') + try: + toonId = int(toonId) + beanAmount = int(beanAmount) + assert toonId > 0 + assert toonId < (1<<32) + assert beanAmount > 0 + assert beanAmount < (1<<16) + except Exception,e: + self.notify.warning("Invalid giveToonBeansRAT request from %s: %s" % (replyTo.getSourceAddress(),str(kw))) + replyTo.respondXML(RATResponses.giveToonBeansRATFailureXML % "INVALID_REQUEST") + return + + self.air.writeServerEvent("UberRPC-GiveToonBeansRAT",replyTo.getSourceAddress(),"%u|%u" % (toonId,beanAmount)) + + if hasattr(self.air,"deliveryManager"): + try: + self.air.deliveryManager.giveBeanBonus(toonId,beanAmount) + except: + replyTo.respondXML(RATResponses.giveToonBeansRATFailureXML % "DELIVERY_FAILURE") + return + else: + self.air.sendUpdateToGlobalDoId( + "DistributedDeliveryManagerUD", + "giveBeanBonus", + OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, + [toonId,beanAmount]) + + replyTo.respondXML(RATResponses.giveToonBeansRATSuccessXML) + + + def handleHTTPGiveToonBeansCS(self,replyTo,**kw): + """ + Request to award quantity of beans to toon. + Used by CS only. + """ + toonId = kw.get("toonId",'0') + beanAmount = kw.get("beanAmount",'0') + try: + toonId = int(toonId) + beanAmount = int(beanAmount) + assert toonId > 0 + assert toonId < (1<<32) + assert beanAmount > 0 + assert beanAmount < (1<<16) + except: + self.notify.warning("Invalid giveToonBeansCS request from %s: %s" % (replyTo.getSourceAddress(),str(kw))) + replyTo.respondXML(RATResponses.giveToonBeansCSFailureXML % "INVALID_REQUEST") + return + + self.air.writeServerEvent("UberRPC-GiveToonBeansCS",replyTo.getSourceAddress(),"%u|%u" % (toonId,beanAmount)) + + if hasattr(self.air,"deliveryManager"): + try: + self.air.deliveryManager.giveBeanBonus(toonId,beanAmount) + except: + replyTo.respondXML(RATResponses.giveToonBeansCSFailureXML % "DELIVERY_FAILURE") + return + else: + self.air.sendUpdateToGlobalDoId( + "DistributedDeliveryManagerUD", + "giveBeanBonus", + OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, + [toonId,beanAmount]) + + replyTo.respondXML(RATResponses.giveToonBeansCSSuccessXML) + + + def handleHTTPGetToonPicId(self,replyTo,**kw): + """ + Given a toon ID, returns the pic ID of that toon's image. + """ + toonId = kw.get("toonId",'0') + try: + toonId = int(toonId) + assert toonId > 0 + assert toonId < (1<<32) + except: + self.notify.warning("Invalid getToonPicId request from %s: %s" % (replyTo.getSourceAddress(),str(kw))) + replyTo.respondXML(RATResponses.getToonPicIdFailureXML % "INVALID_REQUEST") + return + + RATRequests.GetToonPicIdRequest(replyTo,toonId) + + + def handleHTTPGetToonDNA(self,replyTo,**kw): + """ + Given a toon ID, returns a DNA string for that toon. + """ + toonId = kw.get("toonId",'0') + try: + toonId = int(toonId) + assert toonId > 0 + assert toonId < (1<<32) + except: + self.notify.warning("Invalid getToonDNA request from %s: %s" % (replyTo.getSourceAddress(),str(kw))) + replyTo.respondXML(RATResponses.getToonDNAFailureXML % "INVALID_REQUEST") + return + + RATRequests.GetToonDNARequest(replyTo,toonId) diff --git a/toontown/src/rpc/RATRequests.py b/toontown/src/rpc/RATRequests.py new file mode 100644 index 0000000..f8474a9 --- /dev/null +++ b/toontown/src/rpc/RATRequests.py @@ -0,0 +1,168 @@ +from direct.distributed.AsyncRequest import AsyncRequest +from direct.directnotify import DirectNotifyGlobal +from otp.uberdog.UberDogGlobal import * +import binascii +from toontown.rpc import RATResponses + + +class TimeoutException(Exception): + "Exception raised on request timeout" + def __init__(self,*args): + Exception.__init__(self,*args) + +class GetToonIdListRequest(AsyncRequest): + """ + Given an account name, retrieves a list of toon IDs. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonIdListRequest') + + def __init__(self,replyTo,accountName): + """ + replyTo is where we stick the response + accountName is the account whose avatar list we're fetching + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.replyTo = replyTo + self.accountName = accountName + + self.askForObjectFieldsByString(4008,"AccountUD",self.accountName,("ACCOUNT_AV_SET",)) + + def finish(self): + resDict = self.neededObjects[("ACCOUNT_AV_SET",)] + res = resDict.get("ACCOUNT_AV_SET",[]) + toonlist = [] + for id in res: + if id != 0: + toonlist.append(id) + + GetToonNameRequest(self.replyTo,toonlist) + + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonIdListRequest(%s)." % (self.accountName)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.replyTo.getSourceAddress(),"GetToonIdListRequest|%s" % self.accountName) + self.replyTo.respondXML(RATResponses.toonListFailureXML % "DB_TIMEOUT") + self.delete() + + +class GetToonNameRequest(AsyncRequest): + """ + Given a list of toon doIDs, retrieves the toon names. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonNameRequest') + + def __init__(self,replyTo,idList): + """ + replyTo is where we stick the response + idList is a list of IDs of the toons whose names we're fetching + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.replyTo = replyTo + self.idList = idList + for id in idList: + self.askForObjectField("DistributedToonUD","setName",id,key=id) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + + s = "" + + for id in self.idList: + name = self.neededObjects.get(id,("unknown",))[0] + s = s + " %s%s\n" % (id,name) + + self.replyTo.respondXML(RATResponses.getToonListSuccessXML % s) + + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonNameRequest(%s)." % (self.idList)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.replyTo.getSourceAddress(),"GetToonNameRequest|%s" % self.idList) + self.replyTo.respondXML(RATResponses.getToonListFailureXML % "DB_TIMEOUT") + self.delete() + + +class GetToonPicIdRequest(AsyncRequest): + """ + Given the doID of a toon, get the ID for that toon's portrait + + ID format is 'hhggcc' where hh is the two-digit head code, gg is gender code, and cc is the two-digit color code from toon's DNA string + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonPicIdRequest') + def __init__(self,replyTo,toonID): + """ + replyTo is where we stick the response + toonID is the doID of the toon whose picture we want + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.replyTo = replyTo + self.toonID = toonID + self.air.writeServerEvent("UberRPC-GetToonPicId",self.replyTo.getSourceAddress(),"%s" % self.toonID) + self.askForObjectField("DistributedToonUD","setDNAString",self.toonID) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + dna = self.neededObjects["setDNAString"][0] + + picid = "74%02x%02x%02x" % (ord(dna[1]),ord(dna[4]),ord(dna[-1])) + + self.replyTo.respondXML(RATResponses.getToonPicIdSuccessXML % picid) + + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonPicIdRequest(%s) from %s." % (self.toonID,self.replyTo.getSourceAddress())) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.replyTo.getSourceAddress(),"GetToonPicIdRequest|%s" % self.toonID) + self.replyTo.respondXML(RATResponses.getToonPicIdFailureXML % "DB_TIMEOUT") + self.delete() + + +class GetToonDNARequest(AsyncRequest): + """ + Given the doID of a toon, return the toon's DNA string + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonDNARequest') + def __init__(self,replyTo,toonID): + """ + replyTo is where we stick the response + toonID is the doID of the toon whose picture we want + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.replyTo = replyTo + self.toonID = toonID + self.air.writeServerEvent("UberRPC-GetToonDNA",self.replyTo.getSourceAddress(),"%u" % self.toonID) + self.askForObjectField("DistributedToonUD","setDNAString",self.toonID) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + dna = self.neededObjects["setDNAString"][0] + + self.replyTo.respondXML(RATResponses.getToonDNASuccessXML % binascii.hexlify(dna)) + + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonDNARequest(%s) from %s." % (self.toonID,self.replyTo.getSourceAddress())) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.replyTo.getSourceAddress(),"GetToonDNARequest|%s" % self.toonID) + self.replyTo.respondXML(RATResponses.getToonDNAFailureXML % "DB_TIMEOUT") + self.delete() diff --git a/toontown/src/rpc/RATResponses.py b/toontown/src/rpc/RATResponses.py new file mode 100644 index 0000000..a5426d8 --- /dev/null +++ b/toontown/src/rpc/RATResponses.py @@ -0,0 +1,76 @@ +""" +Constants file that contains XML and misc. codes for RAT responses +""" + +# --- Begin XML message constants --- + + +getToonListSuccessXML = """ + + true + +%s + + +\r\n""" + +getToonListFailureXML = """ + + false + %s + +\r\n""" + +giveToonBeansCSSuccessXML = """ + + true + +\r\n""" + +giveToonBeansCSFailureXML = """ + + false + %s + +\r\n""" + +giveToonBeansRATSuccessXML = """ + + true + +\r\n""" + +giveToonBeansRATFailureXML = """ + + false + %s + +\r\n""" + +getToonPicIdSuccessXML = """ + + true + %s + +\r\n""" + +getToonPicIdFailureXML = """ + + false + %s + +\r\n""" + +getToonDNASuccessXML = """ + + true + %s + +\r\n""" + +getToonDNAFailureXML = """ + + false + %s + +\r\n""" diff --git a/toontown/src/rpc/RPCRequests.py b/toontown/src/rpc/RPCRequests.py new file mode 100644 index 0000000..f16cf12 --- /dev/null +++ b/toontown/src/rpc/RPCRequests.py @@ -0,0 +1,243 @@ +from direct.distributed.AsyncRequest import AsyncRequest +from direct.directnotify import DirectNotifyGlobal +from otp.uberdog.UberDogGlobal import * +import binascii + + +class TimeoutException(Exception): + "Exception raised on request timeout" + def __init__(self,*args): + Exception.__init__(self,*args) + +class GetToonIdListRequest(AsyncRequest): + """ + Given an account name, retrieves a list of toon IDs. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonIdListRequest') + + def __init__(self,resultQueue,clientIP,accountName): + """ + resultQueue is where we stick the response + clientIP is the client's address for logging purposes + accountName is the account whose avatar list we're fetching + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.accountName = accountName + + def submit(self): + self.air.writeServerEvent("UberRPC-GetToonIdList",self.clientIP,"%s" % self.accountName) + self.askForObjectFieldsByString(4008,"AccountUD",self.accountName,("ACCOUNT_AV_SET",)) + + def finish(self): + resDict = self.neededObjects[("ACCOUNT_AV_SET",)] + assert resDict.has_key("ACCOUNT_AV_SET") + self.resultQueue.put(resDict["ACCOUNT_AV_SET"]) + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonIdListRequest(%s) from %s." % (self.accountName,self.clientIP)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.clientIP,"GetToonIdListRequest|%s" % self.accountName) + self.resultQueue.put(TimeoutException()) + self.delete() + +class GetToonNameRequest(AsyncRequest): + """ + Given a doID for a toon, retrieves the toon's name. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonNameRequest') + + def __init__(self,resultQueue,clientIP,toonID): + """ + resultQueue is where we stick the response + clientIP is for logging purposes + toonID is the ID of the toon whose name we're fetching + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.toonID = toonID + + def submit(self): + self.askForObjectField("DistributedToonUD","setName",self.toonID) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + resultList = self.neededObjects["setName"] + self.resultQueue.put(resultList[0]) + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonNameRequest(%s) from %s." % (self.toonID,self.clientIP)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.clientIP,"GetToonNameRequest|%s" % self.toonID) + self.resultQueue.put(TimeoutException()) + self.delete() + + +class GiveToonBeansCSRequest(AsyncRequest): + """ + Only for Customer Service requests. + Given a toon ID and a jellybean amount, give a toon jellybeans. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GiveToonBeansCSRequest') + def __init__(self,resultQueue,clientIP,toonID,beanAmount): + """ + resultQueue is where we stick the response + clientIP is for logging purposes + toonID is the toon receiving beans + beanAmount is the number of beans to give + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.toonID = toonID + self.beanAmount = beanAmount + self.done=False + + def submit(self): + self.air.writeServerEvent("UberRPC-GiveToonBeansCS",self.clientIP,"%u|%u" % (self.toonID,self.beanAmount)) + self.air.deliveryManager.giveBeanBonus(self.toonID,self.beanAmount) + self.finish() + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + if not self.done: + self.done = True + self.resultQueue.put("Success") + self.delete() + + +class GiveToonBeansRATRequest(AsyncRequest): + """ + Only for Recruit-a-Toon program. + Given a toon ID and a jellybean amount, give a toon jellybeans. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GiveToonBeansRATRequest') + def __init__(self,resultQueue,clientIP,toonID,beanAmount): + """ + resultQueue is where we stick the response + clientIP is for logging purposes + toonID is the toon receiving beans + beanAmount is the number of beans to give + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.toonID = toonID + self.beanAmount = beanAmount + self.done=False + + def submit(self): + self.air.writeServerEvent("UberRPC-GiveToonBeansRAT",self.clientIP,"%u|%u" % (self.toonID,self.beanAmount)) + self.air.deliveryManager.giveRecruitAToonPayment(self.toonID,self.beanAmount) + self.finish() + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + if not self.done: + self.done = True + self.resultQueue.put("Success") + self.delete() + + + +class GetToonPicIdRequest(AsyncRequest): + """ + Given the doID of a toon, get the ID for that toon's portrait + + ID format is 'hhggcc' where hh is the two-digit head code, gg is gender code, and cc is the two-digit color code from toon's DNA string + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonPicIdRequest') + def __init__(self,resultQueue,clientIP,toonID): + """ + resultQueue is where we stick the response + clientIP is for logging purposes + toonID is the doID of the toon whose picture we want + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.toonID = toonID + + def submit(self): + self.air.writeServerEvent("UberRPC-GetToonPicId",self.clientIP,"%u" % self.toonID) + self.askForObjectField("DistributedToonUD","setDNAString",self.toonID) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + dna = self.neededObjects["setDNAString"][0] + + picid = "74%02x%02x%02x" % (ord(dna[1]),ord(dna[4]),ord(dna[-1])) + + self.resultQueue.put(picid) + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonPicIdRequest(%s) from %s." % (self.toonID,self.clientIP)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.clientIP,"GetToonPicIdRequest|%s" % self.toonID) + self.resultQueue.put(TimeoutException()) + self.delete() + + +class GetToonDNARequest(AsyncRequest): + """ + Given the doID of a toon, return the toon's DNA string + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('GetToonDNARequest') + def __init__(self,resultQueue,clientIP,toonID): + """ + resultQueue is where we stick the response + clientIP is for logging purposes + toonID is the doID of the toon whose picture we want + """ + assert self.notify.debugCall() + self.__deleted=False + AsyncRequest.__init__(self,uber.air) + self.air = uber.air + self.resultQueue = resultQueue + self.clientIP = clientIP + self.toonID = toonID + + def submit(self): + self.air.writeServerEvent("UberRPC-GetToonDNA",self.clientIP,"%u" % self.toonID) + self.askForObjectField("DistributedToonUD","setDNAString",self.toonID) + + def finish(self): + assert self.notify.debugCall() + assert not self.__deleted + dna = self.neededObjects["setDNAString"][0] + + self.resultQueue.put(binascii.hexlify(dna)) + self.delete() + + def timeout(self,task): + self.notify.warning("Request timeout in GetToonDNARequest(%s) from %s." % (self.toonID,self.clientIP)) + self.air.writeServerEvent("UberRPC-RequestTimeout",self.clientIP,"GetToonDNARequest|%s" % self.toonID) + self.resultQueue.put(TimeoutException()) + self.delete() diff --git a/toontown/src/rpc/RPCServer.py b/toontown/src/rpc/RPCServer.py new file mode 100644 index 0000000..2400d6f --- /dev/null +++ b/toontown/src/rpc/RPCServer.py @@ -0,0 +1,360 @@ +import threading,Queue +import SOAPpy +from tlslite.errors import TLSError + +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.AsyncRequest import AsyncRequest +from toontown.rpc.RPCRequests import GetToonIdListRequest +from toontown.rpc.RPCRequests import GiveToonBeansRATRequest +from toontown.rpc.RPCRequests import GiveToonBeansCSRequest +from toontown.rpc.RPCRequests import GetToonPicIdRequest +from toontown.rpc.RPCRequests import GetToonDNARequest +from toontown.rpc.RPCRequests import GetToonNameRequest +from toontown.rpc.RPCRequests import TimeoutException + +class RPCException(Exception): + "Exception raised on invalid RPC arguments" + def __init__(self,*args): + Exception.__init__(self,*args) + + +class RPCServerThread(threading.Thread): + """ + Server thread for RPCServer. + Runs a simple RPC server forever and communicates to RPCServer through request queues. + """ + def __init__(self,rpcip,rpcport,keyfile,certfile,allowed_ipdict): + self.rpcip = rpcip + self.rpcport = rpcport + + threading.Thread.__init__(self) + self.setDaemon(True) + self.requestQueue = Queue.Queue() + self.resultQueue = Queue.Queue() + + SOAPpy.Config.debug = 0 + SOAPpy.Config.dumpFaultInfo = 0 + + + # --- BEGIN PUBLISHED FUNCTIONS --- + + + def getToonList(accountName): + """ + Retrieve a list of toons owned by the given account. + + accountName is the account name string as stored on the OTP server. + + Returns a list of (DOID,name) tuples. + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(accountName,str): + raise RPCException, "Argument accountName was not a string." + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GetToonIdListRequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + accountName)) + idList = self.resultQueue.get() + + if isinstance(idList,TimeoutException): + raise RPCException, "Request timed out." + + resultList = [] + for doid in idList: + if doid > 0: + toonname = self.getToonName(doid) + resultList.append((doid,toonname)) + + return resultList + + + def getToonSlots(accountName): + """ + Identical to getToonList, but also returns empty toon slots. + + Retrieve a list of toons owned by the given account. + + accountName is the account name string as stored on the OTP server. + + Returns a list of (DOID,name) tuples. + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(accountName,str): + raise RPCException, "Argument accountName was not a string." + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GetToonIdListRequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + accountName)) + idList = self.resultQueue.get() + + if isinstance(idList,TimeoutException): + raise RPCException, "Request timed out." + + resultList = [] + id = 0 + for doid in idList: + if doid > 0: + toonname = self.getToonName(doid) + resultList.append((doid,toonname)) + else: + resultList.append((id,"%s"%id)) + id += 1 + + return resultList + + + def giveToonBeansRAT(toonID,beanAmount): + """ + Only for Recruit-a-Toon program. + + Give jellybeans to the toon specified. + + toonID is the DOID of the recipient toon. + + beanAmount is the number of beans to give (0 < beanAmount <= 100). + + Returns None on success, faults on failure. + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(toonID,int): + raise RPCException, "Argument toonID was not an int." + if not isinstance(beanAmount,int): + raise RPCException, "Argument beanAmount was not an int." + if toonID < 1: + raise RPCException, "Argument toonID was non-positive." + if beanAmount > 100: + raise RPCException, "Attempted to give a toon more than 100 jellybeans at once" + if beanAmount < 1: + raise RPCException, "Attempted to give a toon a non-positive jellybean amount" + + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GiveToonBeansRATRequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + toonID, \ + beanAmount)) + result = self.resultQueue.get() + return + + + def giveToonBeansCS(toonID,beanAmount): + """ + Only for Customer Service requests. + + Give jellybeans to the toon specified. + + toonID is the DOID of the recipient toon. + + beanAmount is the number of beans to give (0 < beanAmount <= 100). + + Returns None on success, faults on failure. + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(toonID,int): + raise RPCException, "Argument toonID was not an int." + if not isinstance(beanAmount,int): + raise RPCException, "Argument beanAmount was not an int." + if toonID < 1: + raise RPCException, "Argument toonID was non-positive." + if beanAmount > 100: + raise RPCException, "Attempted to give a toon more than 100 jellybeans at once" + if beanAmount < 1: + raise RPCException, "Attempted to give a toon a non-positive jellybean amount" + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GiveToonBeansCSRequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + toonID, \ + beanAmount)) + result = self.resultQueue.get() + return + + + def getToonPicId(toonID): + """ + Retrieve the picture ID for a given toon. + + toonID is the toon's DOID + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(toonID,int): + raise RPCException, "Argument toonID was not an int." + if toonID < 1: + raise RPCException, "Argument toonID was non-positive." + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GetToonPicIdRequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + toonID)) + picid = self.resultQueue.get() + + if isinstance(picid,TimeoutException): + raise RPCException, "Request timed out." + + return picid + + + def getToonDNA(toonID): + """ + Retrieve the DNA for a given toon. + + toonID is the toon's DOID + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + if not isinstance(toonID,int): + raise RPCException, "Argument toonID was not an int." + if toonID < 1: + raise RPCException, "Argument toonID was non-positive." + + c = SOAPpy.GetSOAPContext() + + self.requestQueue.put(GetToonDNARequest(self.resultQueue, \ + c.connection.getpeername()[0], \ + toonID)) + dna = self.resultQueue.get() + + if isinstance(dna,TimeoutException): + raise RPCException, "Request timed out." + + return dna + + + # --- END PUBLISHED FUNCTIONS --- + + + self.SOAPServer = SOAPpy.SOAPServer(addr=(self.rpcip,self.rpcport),\ + namespace="ToontownRPC",\ + key_file=keyfile,\ + cert_file=certfile,\ + allowed_ipdict=allowed_ipdict) + + self.SOAPServer.registerFunction(getToonList) + self.SOAPServer.registerFunction(getToonSlots) + self.SOAPServer.registerFunction(giveToonBeansRAT) + self.SOAPServer.registerFunction(giveToonBeansCS) + self.SOAPServer.registerFunction(getToonPicId) + self.SOAPServer.registerFunction(getToonDNA) + + self.start() + + def getToonName(self,toonID): + """ + Internal request. Not published for RPC. + Returns the name of the toon with given DOID. + """ + assert self.requestQueue.empty() + assert self.resultQueue.empty() + self.requestQueue.put(GetToonNameRequest(self.resultQueue, \ + "internal", \ + toonID)) + result = self.resultQueue.get() + + if toonID < 1: + raise RPCException, "Argument toonID was non-positive." + if isinstance(result,TimeoutException): + raise RPCException, "Request timed out." + return unicode(result.decode("utf-8")) + + + def run(self): + while 1: + try: + self.SOAPServer.serve_forever() + except TLSError,e: + self.SOAPServer.logRequestQueue.put(("unknown","TLSError",e)) + except Exception,e: + self.SOAPServer.logRequestQueue.put(("unknown","Exception",e)) + + +class RPCServer(object): + """ + Embedded RPC server for use with uberdog. + """ + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('RPCServer') + + def __init__(self,rpcip,rpcport,keyfile="",certfile="",allowed_ipdict={}): + """ + rpcip is the IP to listen on. If empty, do not listen on a specific IP. + + rpcport is the port to listen on. + + keyfile is the server's private key file for use with SSL + + certfile is the server's certificate file for use with SSL + + allowed_ipdict is a dictionary containing IP:True pairs where IP is an address being allowed to connect + + + If keyfile or certfile is empty, no encryption is used. + + If allowed_ipdict is empty, any client is allowed to make requests. + """ + self.air = uber.air + self.notify.info("Embedded RPC server enabled.") + self.notify.info("Starting server on %s:%u." % (rpcip,rpcport)) + if keyfile is "" or certfile is "": + self.notify.warning("No key/cert provided, unable to use SSL. NO ENCRYPTION ENABLED.") + else: + self.notify.info("Initializing SSL using TLSLite.") + self.notify.info("Keyfile: %s" % keyfile) + self.notify.info("Certificate file: %s" % certfile) + + if allowed_ipdict == {}: + self.notify.warning("No allowed IP list provided. ALLOWING ANYONE TO CONNECT.") + else: + self.notify.info("Allowing requests from: %s" % allowed_ipdict.keys()) + + self.rpcip = rpcip + self.rpcport = rpcport + self.serverthread = RPCServerThread(rpcip,rpcport,keyfile,certfile,allowed_ipdict) + + def processLogRequest(self,clientIP,description,info): + """ + Log an event generated by clientIP, with given description and additional info. + """ + if description == "Unauthorized IP": + self.notify.warning("HTTP Unauthorized Address %s" % clientIP) + self.air.writeServerEvent("UberRPC-SecurityAlert",clientIP,"HTTP Unauthorized Address %s" % clientIP) + elif description == "TLSError": + self.notify.warning("Caught TLSError while serving RAT requests:\n%s\nContinuing." % info) + elif description == "Exception": + self.notify.warning("Caught exception while serving RAT requests:\n%s\nContinuing." % info) + else: + assert(False) + pass + + def serverLoop(self): + """ + Process a single client request and a single log request. + """ + try: + request = self.serverthread.requestQueue.get_nowait() + request.submit() + except Queue.Empty: + pass + + try: + clientIP,description,info = self.serverthread.SOAPServer.logRequestQueue.get_nowait() + self.processLogRequest(clientIP,description,info) + except Queue.Empty: + pass + + + + + + diff --git a/toontown/src/rpc/Sources.pp b/toontown/src/rpc/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/rpc/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/rpc/__init__.py b/toontown/src/rpc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/rpc/dummycert.pem b/toontown/src/rpc/dummycert.pem new file mode 100644 index 0000000..79cd1e9 --- /dev/null +++ b/toontown/src/rpc/dummycert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqjCCAhOgAwIBAgIJAJPygyl1elVVMA0GCSqGSIb3DQEBBAUAMEMxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMR8wHQYDVQQDExZEVU1NWSByb290 +IENlcnRpZmljYXRlMB4XDTA2MDIwOTAwNDUzN1oXDTA3MDIwOTAwNDUzN1owQzEL +MAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxHzAdBgNVBAMTFkRVTU1Z +IHJvb3QgQ2VydGlmaWNhdGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMQa +Sa7xej/Co9BdHsIs2xvBuVe706OnotGrqblJ8+PEbtWXWBbjc6kYvj9X4C0bJiwf +ZI7SgHalpWYx7Sd5S+hvyaNMKK2qEhTZkUY9FoUEf05bi0lC9tVHjnMqy5/qHkyE +xhmS69DB+8Byt4fbowlcXQ7DwScPo6VIyiCQ3ftFAgMBAAGjgaUwgaIwHQYDVR0O +BBYEFAISlapFXrXpBkofM96HNST2w/7AMHMGA1UdIwRsMGqAFAISlapFXrXpBkof +M96HNST2w/7AoUekRTBDMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 +ZTEfMB0GA1UEAxMWRFVNTVkgcm9vdCBDZXJ0aWZpY2F0ZYIJAJPygyl1elVVMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAC/k4VUSQ2PaGQ1bBdUotJDDI +aDiv+GvvdAeCy7pGwwzWBsNTxrhjXhwBGDhH2hRcqQ/p3iLDv5qv8jimFdOJC+Fa +89e+zvoALSQK/1qg3h+HZOzasDZi6ADFrAVlxopLAmL4F2b/glkyvWvZQk3/Serg +brfyoF25ZjAV0PPMtNU= +-----END CERTIFICATE----- diff --git a/toontown/src/rpc/dummykey.pem b/toontown/src/rpc/dummykey.pem new file mode 100644 index 0000000..982d133 --- /dev/null +++ b/toontown/src/rpc/dummykey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDEGkmu8Xo/wqPQXR7CLNsbwblXu9Ojp6LRq6m5SfPjxG7Vl1gW +43OpGL4/V+AtGyYsH2SO0oB2paVmMe0neUvob8mjTCitqhIU2ZFGPRaFBH9OW4tJ +QvbVR45zKsuf6h5MhMYZkuvQwfvAcreH26MJXF0Ow8EnD6OlSMogkN37RQIDAQAB +AoGBALg9CmT2MiidMVKdajx78A8P3pXyvU/QO0RJx1dxh1XCQ28glX5Li2qe+H6C +jPdTvnVNuSgF1POjhNWSqoCfkRPV4Qt2Q/4B1sP8S8F0aGB72jLW3llrWPjyWIgU +5hfaZ5jpfAtL/ZPHl8wGl22NeTfD5jAC2fDF2ctmYI5MlpoxAkEA7S3o9BIsHrJ7 +2kNCYwXYqUZqPehJbsxU8haDOrW8SSwCD3Mig04p3/8pzLeDUMOiDeC/BMZBTo5f +Bomm626FmwJBANOp8RiTnpMNv80Y1rK5sJRjY3iWG/HTXuAQiFq7DEGJfEv1fvgH +jwMzTks/UIJDUSqJu6jrWV+U9inava27AJ8CQCN5PGBU33hv5YpNiP5af39B4t4D +gggqU4Ipz9LWH6UqCdzZsY3GAQlZlpzhzagkunYs6SUDqfzf2mKV7/tCeoUCQAFn +qF0EZdIODk7bMlmfV+e0PS3IuUjCoWeVVIJdqXgp5HRQndYhnrZjucpQEkW7EqfY +oDE/1qkGwReIByhHDHsCQDJeB85RIS7VXbkiwVJkvo2lfiQA9E+Zj9fP1Gny/cYe +G4PZq38hQ5PmOri2xa65AlkuYnzIQ6AVneXeQo4lNCo= +-----END RSA PRIVATE KEY----- diff --git a/toontown/src/rpc/testclient.py b/toontown/src/rpc/testclient.py new file mode 100644 index 0000000..50c09a7 --- /dev/null +++ b/toontown/src/rpc/testclient.py @@ -0,0 +1,30 @@ +#!/home/igraham/player/wintools/sdk/python/Python-2.4.1/PCbuild/python + +import SOAPpy +import sys + +SOAPpy.Config.debug = 0 + +connectTo = "http://localhost:8080" +acctName = "mrhead" +toondoid = 100000006 +numqueries = 1000 + + +server = SOAPpy.SOAPProxy(connectTo,namespace="ToontownRPC") + + +print "Running %d queries..." % numqueries +sys.stdout.flush() + + +for i in range(numqueries): + heyalist = server.getToonList(accountName=acctName) + #print server.giveToonBeansRAT(toonID=toondoid,beanAmount=10) + #print server.giveToonBeansCS(toonID=toondoid,beanAmount=10) + #print server.getToonPicId(toonID=toondoid) + + +print "DONE" + + diff --git a/toontown/src/safezone/.cvsignore b/toontown/src/safezone/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/safezone/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/safezone/BRPlayground.py b/toontown/src/safezone/BRPlayground.py new file mode 100644 index 0000000..507ac0f --- /dev/null +++ b/toontown/src/safezone/BRPlayground.py @@ -0,0 +1,71 @@ +from pandac.PandaModules import * + +import Playground +from direct.task.Task import Task +import random +from toontown.hood import Place +from toontown.toonbase import ToontownGlobals + +class BRPlayground(Playground.Playground): + + STILL = 1 + RUN = 2 + ROTATE = 3 + + stillPos = Point3(0, 20, 8) + runPos = Point3(0, 60, 8) + rotatePos = Point3(0, 0, 8) + + timeFromStill = 1.0 + timeFromRotate = 2.0 + + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Playground.Playground.load(self) + + def unload(self): + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + self.nextWindTime = 0 + taskMgr.add(self.__windTask, 'br-wind') + self.state = 0 + + def exit(self): + taskMgr.remove('br-wind') + taskMgr.remove('lerp-snow') + Playground.Playground.exit(self) + + def enterTunnelOut(self, requestStatus): + # We no longer need to stop the snow on tunnel out, because + # the camera doesn't enter the tunnel any more. + #taskMgr.remove('lerp-snow') + #self.loader.snow.reparentTo(hidden) + + Place.Place.enterTunnelOut(self, requestStatus) + + def __windTask(self, task): + now = globalClock.getFrameTime() + if (now < self.nextWindTime): + return Task.cont + randNum = random.random() + wind = (int(randNum * 100) % 3) + 1 + if (wind == 1): + base.playSfx(self.loader.wind1Sound) + elif (wind == 2): + base.playSfx(self.loader.wind2Sound) + elif (wind == 3): + base.playSfx(self.loader.wind3Sound) + self.nextWindTime = now + randNum * 8.0 + return Task.cont + + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Pluto)) diff --git a/toontown/src/safezone/BRSafeZoneLoader.py b/toontown/src/safezone/BRSafeZoneLoader.py new file mode 100644 index 0000000..d0db0f8 --- /dev/null +++ b/toontown/src/safezone/BRSafeZoneLoader.py @@ -0,0 +1,93 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * + +import SafeZoneLoader +import BRPlayground +from toontown.battle import BattleParticles + +class BRSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + + # How quickly does the snow alpha fade in and out when we run + # inside the igloo? + SnowFadeLerpTime = 2.0 + + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = BRPlayground.BRPlayground + self.musicFile = "phase_8/audio/bgm/TB_nbrhood.mid" + self.activityMusicFile = "phase_8/audio/bgm/TB_SZ_activity.mid" + self.dnaFile = "phase_8/dna/the_burrrgh_sz.dna" + self.safeZoneStorageDNAFile = "phase_8/dna/storage_BR_sz.dna" + + def load(self): + SafeZoneLoader.SafeZoneLoader.load(self) + self.wind1Sound = base.loadSfx('phase_8/audio/sfx/SZ_TB_wind_1.mp3') + self.wind2Sound = base.loadSfx('phase_8/audio/sfx/SZ_TB_wind_2.mp3') + self.wind3Sound = base.loadSfx('phase_8/audio/sfx/SZ_TB_wind_3.mp3') + self.snow = BattleParticles.loadParticleFile('snowdisk.ptf') + self.snow.setPos(0, 0, 5) # start the snow slightly above the camera + self.snowRender = self.geom.attachNewNode('snowRender') + self.snowRender.setDepthWrite(0) + self.snowRender.setBin('fixed', 1) + self.snowFade = None + + def unload(self): + del self.wind1Sound + del self.wind2Sound + del self.wind3Sound + del self.snow + del self.snowRender + SafeZoneLoader.SafeZoneLoader.unload(self) + + def enter(self, requestStatus): + SafeZoneLoader.SafeZoneLoader.enter(self, requestStatus) + self.snow.start(camera, self.snowRender) + self.accept('enterigloo-interior', self.enterIgloo) + self.accept('exitigloo-interior', self.exitIgloo) + + def exit(self): + self.ignore('enterigloo-interior') + self.ignore('exitigloo-interior') + self.resetSnowLerp() + self.snow.cleanup() + SafeZoneLoader.SafeZoneLoader.exit(self) + + def enterIgloo(self, entry): + # This would have been called when we ran inside the igloo, + # but we don't have an igloo any more. So this never gets + # called, but the code is allowed to remain in case we do + # eventually have an indoor area like the igloo again. + self.fadeOutSnow() + + def exitIgloo(self, entry): + self.fadeInSnow() + + def resetSnowLerp(self): + if self.snowFade != None: + self.snowFade.stop() + self.snowFade = None + + def fadeInSnow(self): + # Gradually lerp the snow's alpha back to full. + self.resetSnowLerp() + + currentScale = self.snowRender.getColorScale()[3] + ivals = [LerpFunctionInterval( + self.snowRender.setAlphaScale, + fromData = currentScale, toData = 1.0, + duration = self.SnowFadeLerpTime), + FunctionInterval(self.snowRender.clearColorScale)] + self.snowFade = Track(ivals, 'snow-fade') + self.snowFade.play() + + def fadeOutSnow(self): + # Gradually lerp the snow's alpha out to nothing. + self.resetSnowLerp() + + currentScale = self.snowRender.getColorScale()[3] + ivals = [LerpFunctionInterval( + self.snowRender.setAlphaScale, + fromData = currentScale, toData = 0.0, + duration = self.SnowFadeLerpTime)] + self.snowFade = Track(ivals, 'snow-fade') + self.snowFade.play() diff --git a/toontown/src/safezone/BRTreasurePlannerAI.py b/toontown/src/safezone/BRTreasurePlannerAI.py new file mode 100644 index 0000000..d4566eb --- /dev/null +++ b/toontown/src/safezone/BRTreasurePlannerAI.py @@ -0,0 +1,42 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedBRTreasureAI + +class BRTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 12 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedBRTreasureAI.DistributedBRTreasureAI, # Constructor + "BRTreasurePlanner", + 20, # seconds per spawn + 2 # Max of two treasures + ) + return None + + def initSpawnPoints(self): + self.spawnPoints = [ + (-108, 46, 6.2), + (-111, 74, 6.2), + (-126, 81, 6.2), + (-74, -75, 3.0), + (-136, -51, 3.0), + (-20, 35, 6.2), + (-55, 109, 6.2), + (58, -57, 6.2), + (-42, -134, 6.2), + (-68, -148, 6.2), + (-1, -62, 6.2), + (25, 2, 6.2), + (-133, 53, 6.2), + (-99, 86, 6.2), + (30, 63, 6.2), + (-147, 3, 6.2), + (-135, -102, 6.2), + (35, -98, 6.2), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/ButterflyGlobals.py b/toontown/src/safezone/ButterflyGlobals.py new file mode 100644 index 0000000..02e7552 --- /dev/null +++ b/toontown/src/safezone/ButterflyGlobals.py @@ -0,0 +1,297 @@ +from pandac.PandaModules import * + +import random + +OFF = 0 +FLYING = 1 +LANDED = 2 +states = { + OFF : 'off', + FLYING : 'Flying', + LANDED : 'Landed', + } + +NUM_BUTTERFLIES = (6, 36, 5) +NUM_BUTTERFLY_AREAS = (4, 1, 4) +BUTTERFLY_SPEED = 2.0 +BUTTERFLY_HEIGHT = (2.2, 3.2, 2.2) +BUTTERFLY_TAKEOFF = (1.4, 1.8, 1.4) +BUTTERFLY_LANDING = (1.4, 1.8, 1.4) +MAX_LANDED_TIME = 20.0 + +TTC = 0 +DG = 1 +ESTATE = 2 + +ButterflyPoints = (( + # Toontown Central points + (Point3(84.0, -116.0, 3.5), + Point3(95.0, -144.0, 2.6), + Point3(94.0, -145.0, 2.6), + Point3(95.0, -149.0, 2.6), + Point3(50.0, -155.0, 2.6), + Point3(51.0, -147.0, 2.6), + Point3(51.0, -145.0, 2.6), + Point3(14.0, -99.0, 3.1), + Point3(17.0, -94.0, 3.1), + Point3(50.0, -79.0, 3.1), + Point3(47.0, -86.0, 3.1), + Point3(54.0, -127.0, 2.6), + Point3(84.0, -113.0, 3.8) + ), + (Point3(-57.0, -70.0, 0.1), + Point3(-55.0, -68.0, 0.1), + Point3(-90.0, -77.0, 0.6), + Point3(-90.0, -72.0, 0.1), + Point3(-133.0, -50.0, 0.6), + Point3(-129.0, -48.0, 0.6), + Point3(-127.0, -25.0, 0.1), + Point3(-125.0, -22.0, 0.1), + Point3(-123.0, -22.0, 0.1), + Point3(-103.0, -10.0, -3.0), + Point3(-104.0, -13.0, -2.5), + Point3(-100.0, -28.0, -2.7), + Point3(-89.0, -41.0, -4.4), + Point3(-58.0, -34.0, -4.1), + Point3(-69.0, -18.0, -1.9), + Point3(-65.0, -19.0, -1.9), + Point3(-65.0, -16.0, -1.9), + Point3(6.0, -49.0, -0.1), + Point3(2.6, -47.0, 0.1), + Point3(-33.6, -43.0, 0.0) + ), + (Point3(-53.0, 3.0, -1.8), + Point3(-58.0, 2.0, -1.8), + Point3(-58.0, 2.0, -1.8), + Point3(-76.0, 2.0, -1.8), + Point3(-69.0, 11.0, -1.8), + Point3(-100.0, 14.0, -4.1), + Point3(-104.0, 17.0, -2.6), + Point3(-125.0, 34.0, 0.1), + Point3(-124.0, 30.0, 0.1), + Point3(-113.0, 73.0, 0.6), + Point3(-33.0, 78.0, 0.1), + Point3(-65.0, 48.0, -3.0), + Point3(-51.0, 33.0, -3.0), + Point3(-30.0, 71.0, 0.1), + Point3(-26.0, 71.0, 0.1), + Point3(-23.0, 69.0, 0.1), + Point3(-23.0, 64.0, 0.1), + Point3(-5.0, 42.0, 0.1), + Point3(-22.0, 22.0, 0.1), + Point3(-27.0, 22.0, 0.1) + ), + (Point3(14.0, 93.0, 3.1), + Point3(17.0, 93.0, 3.1), + Point3(20.0, 122.0, 2.6), + Point3(21.0, 127.0, 2.6), + Point3(23.0, 123.0, 2.6), + Point3(32.0, 130.0, 2.6), + Point3(48.0, 148.0, 2.6), + Point3(64.0, 111.0, 2.6), + Point3(32.0, 82.0, 2.6), + Point3(63.0, 90.0, 3.1), + Point3(68.0, 85.0, 3.1), + Point3(65.0, 85.0, 3.1), + Point3(70.0, 95.0, 3.1), + )), + # Daisy Gardens points + ( + (Point3(-7.9, 22.9, 0.05), + Point3(-8.0, 17.0, 2.1), + Point3(-7.5, 18.0, 2.1), + Point3(-27.5, 70.7, 0.05), + Point3(-30.0, 70.0, 1.0), + Point3(-31.0, 69.0, 1.0), + Point3(-1.0, 53.0, 2.2), + Point3(-0.5, 53.0, 2.2), + Point3(35.0, 71.5, 1.0), + Point3(33.0, 69.0, 0.05), + Point3(45.0, 61.0, 0.05), + Point3(55.0, 62.0, 0.05), + Point3(80.0, 74.0, 0.05), + Point3(80.0, 73.0, 0.05), + Point3(76.0, 46.0, 0.05), + Point3(76.0, 45.0, 0.05), + Point3(77.0, 41.0, 0.05), + Point3(62.0, 28.0, 0.05), + Point3(48.0, 24.0, 0.05), + Point3(83.0, 122.0, 0.05), + Point3(82.0, 123.0, 0.05), + Point3(81.0, 81.0, 0.05), + Point3(38.0, 77.0, 0.05), + Point3(-26.0, 69.0, 0.05), + Point3(-26.0, 70.0, 0.05), + Point3(-61.0, 71.0, 0.05), + Point3(-61.0, 70.0, 0.05), + Point3(-78.0, 79.0, 0.05), + Point3(-99.0, 106.0, 0.05), + Point3(-99.0, 108.0, 0.05), + Point3(-80.0, 123.0, 0.05), + Point3(-77.0, 125.0, 0.05), + Point3(-32.0, 162.0, 0.05), + Point3(-3.0, 186.5, 2.2), + Point3(-3.2, 186.8, 2.2), + Point3(-1.0, 185.0, 2.2), + Point3(39.0, 165.0, 0.05), + Point3(42.0, 162.0, 0.05), + Point3(62.0, 145.0, 0.05), + Point3(64.0, 145.0, 0.05), + Point3(59.0, 102.0, 0.05), + Point3(32.7, 93.7, 0.05), + Point3(31.2, 90.8, 0.05), + Point3(29.8, 140.1, 0.05), + Point3(16.5, 146.3, 0.05), + Point3(15.3, 146.9, 0.05), + Point3(-24.3, 128.6, 0.05), + Point3(-67.9, 117.9, 0.05), + Point3(-41.6, 88.4, 0.05), + Point3(-13.6, 120.3, 0.05), + Point3(26.0, 117.8, 0.05), + Point3(22.6, 112.3, 0.05), + Point3(-8.2, 107.9, 0.05), + Point3(-18.1, 97.0, 0.05), + Point3(-21.4, 92.9, 0.05), + Point3(-2.1, 74.0, 0.05), + Point3(19.8, 93.5, 0.05), + Point3(21.4, 95.4, 0.05), + Point3(19.2, 97.5, 0.05), + Point3(-10.7, 143.3, 0.05), + Point3(38.2, 120.7, 0.05), + Point3(34.1, 101.5, 0.05), + Point3(32.4, 96.5, 0.05), + Point3(72.9, 121.8, 0.05), + ),), + # Estate points + ( + (Point3(-40, -137, 0.025), + Point3(2.35, -167.95, 0.025), + Point3(70.8, -125.3, 0.025), + Point3(63.49, -67.4, 0.025), + Point3(17.5, -59.25, 0.623), + Point3(-51.87, -107.0, 0.723), + Point3(-20.325, -48.716, 4.884), + Point3(51.03, -67.244, 0.244), + Point3(20.02, -34.271, 7.105), + Point3(24.731, -20.905, 9.247), + ), + (Point3(88, -57.4, 0.025), + Point3(92.347, -7.71, 0.169), + Point3(129.39, 0.85, 0.025), + Point3(121.14, 37, 0.025), + Point3(126, 30.3, 0.025), + Point3(100.3, 21.2, 0.05), + Point3(103.42, 1.544, 0.025), + Point3(82.37, -45, 0.025), + Point3(103.8, 4.306, 0.05), + Point3(119.195, -42.042, 0.025), + ), + (Point3(10, 98.5, -0.028), + Point3(11.65, 92.52, -0.079), + Point3(-16.25, 86.67, 0.216), + Point3(-65.3, 67.8, 0.025), + Point3(-41.6, 67.0, 0.025), + Point3(-34.8, 68.9, 0.025), + Point3(-32.272, 56.65, 1.192), + Point3(-63.956, 39.678, 0.281), + Point3(-79.65, 36.99, 0.025), + Point3(-14.769, 72.399, 0.244), + ), + (Point3(-79.6, 36.9, 0.025), + Point3(-57.6, 27.24, 2.355), + Point3(-69.642, -28.137, 3.98), + Point3(-111, -58.1, 0.025), + Point3(-152.223, 25.627, 0.025), + Point3(-104.4, 43.5, 0.278), + Point3(-85.25, 10.513, 0.111), + Point3(-43.6, 1.644, 3.838), + Point3(-48.993, -21.968, 3.98), + Point3(-30.088, -5.987, 7.025), + )), + ) + +# Nowadays, because hoods may come and go (e.g. Estates and +# WelcomeValley), we have to allocate all of the butterfly indices +# dynamically. We do this by copying from the above lists. +allocatedIndexes = {} + +def generateIndexes(doId, playground): + assert((doId != None) and (not allocatedIndexes.has_key(doId))) + + # Build a list of used and unused index numbers for each area of + # the indicated playground. + usedI = [] + unusedI = [] + for area in ButterflyPoints[playground]: + usedI.append(range(0, len(area))) + unusedI.append([]) + allocatedIndexes[doId] = (usedI, unusedI) + +def clearIndexes(doId): + if (allocatedIndexes.has_key(doId)): + del allocatedIndexes[doId] + +def getFirstRoute(playground, area, doId): + """ Return a starting point, destination point, and time to destination + """ + (curPos, curIndex) = __getCurrentPos(playground, area, doId) + (destPos, destIndex, time) = getNextPos(curPos, playground, area, doId) + return (curPos, curIndex, destPos, destIndex, time) + +def __getCurrentPos(playground, area, doId): + """ Return a valid starting point + """ + if (allocatedIndexes.has_key(doId)): + unusedI = allocatedIndexes[doId][0][area] + usedI = allocatedIndexes[doId][1][area] + else: + return (ButterflyPoints[playground][area][0], 0) + + # We can only prevent duplicate destinations if there are any unused points + if (len(unusedI) == 0): + index = random.choice(usedI) + return (ButterflyPoints[playground][area][index], index) + + index = random.choice(unusedI) + unusedI.remove(index) + usedI.append(index) + return (ButterflyPoints[playground][area][index], index) + +def getNextPos(currentPos, playground, area, doId): + """ Return a tuple of a Point3, its index, and the time required to + get there + """ + if (allocatedIndexes.has_key(doId)): + unusedI = allocatedIndexes[doId][0][area] + usedI = allocatedIndexes[doId][1][area] + else: + return (ButterflyPoints[playground][area][0], 0, 4.0) + + # Make sure we're heading to a new spot + nextPos = currentPos + while (nextPos == currentPos): + if (len(unusedI) == 0): + index = random.choice(usedI) + nextPos = ButterflyPoints[playground][area][index] + else: + index = random.choice(unusedI) + nextPos = ButterflyPoints[playground][area][index] + if (nextPos != currentPos): + unusedI.remove(index) + usedI.append(index) + # Compute the time to next pos + dist = Vec3(nextPos - currentPos).length() + time = (dist / BUTTERFLY_SPEED) + BUTTERFLY_TAKEOFF[playground] + BUTTERFLY_LANDING[playground] + return (nextPos, index, time) + +def recycleIndex(index, playground, area, doId): + if (allocatedIndexes.has_key(doId)): + unusedI = allocatedIndexes[doId][0][area] + usedI = allocatedIndexes[doId][1][area] + else: + return None + + if (usedI.count(index) > 0): + usedI.remove(index) + if (unusedI.count(index) == 0): + unusedI.append(index) diff --git a/toontown/src/safezone/CheckersBoard.py b/toontown/src/safezone/CheckersBoard.py new file mode 100644 index 0000000..76e44c3 --- /dev/null +++ b/toontown/src/safezone/CheckersBoard.py @@ -0,0 +1,149 @@ + +#Checkers (americanStyle) +# +# 28 * 29 * 30 * 31 * +# * 24 * 25 * 26 * 27 +# 20 * 21 * 22 * 23 * +# * 16 * 17 * 18 * 19 +# 12 * 13 * 14 * 15 * +# * 8 * 9 * 10 * 11 +# 4 * 5 * 6 * 7 * +# * 0 * 1 * 2 * 3 +# +#Adjacency list +#1 * 2 +#* X * +#0 * 3 + +class CheckersBoard: + def __init__(self): + self.squareList = [] + for x in range(32): + self.squareList.append(CheckersTile(x)) + + self.squareList[0].setAdjacent([None,None,4,None]) + self.squareList[1].setAdjacent([None,4,5,None]) + self.squareList[2].setAdjacent([None,5,6,None]) + self.squareList[3].setAdjacent([None,6,7,None]) + self.squareList[4].setAdjacent([0,8,9,1]) + self.squareList[5].setAdjacent([1,9,10,2]) + self.squareList[6].setAdjacent([2,10,11,3]) + self.squareList[7].setAdjacent([3,11,None,None]) + self.squareList[8].setAdjacent([None,None,12,4]) + self.squareList[9].setAdjacent([4,12,13,5]) + self.squareList[10].setAdjacent([5,13,14,6]) + self.squareList[11].setAdjacent([6,14, 15,7]) + self.squareList[12].setAdjacent([8,16,17,9]) + self.squareList[13].setAdjacent([9,17,18,10]) + self.squareList[14].setAdjacent([10,18,19,11]) + self.squareList[15].setAdjacent([11,19,None,None]) + self.squareList[16].setAdjacent([None,None,20,12]) + self.squareList[17].setAdjacent([12,20,21,13]) + self.squareList[18].setAdjacent([13,21,22,14]) + self.squareList[19].setAdjacent([14,22,23,15]) + self.squareList[20].setAdjacent([16,24,25,17]) + self.squareList[21].setAdjacent([17,25,26,18]) + self.squareList[22].setAdjacent([18,26,27,19]) + self.squareList[23].setAdjacent([19,27,None,None]) + self.squareList[24].setAdjacent([None,None,28,20]) + self.squareList[25].setAdjacent([20,28,29,21]) + self.squareList[26].setAdjacent([21,29,30,22]) + self.squareList[27].setAdjacent([22,30,31,23]) + self.squareList[28].setAdjacent([24,None,None,25]) + self.squareList[29].setAdjacent([25,None,None,26]) + self.squareList[30].setAdjacent([26,None,None,27]) + self.squareList[31].setAdjacent([27,None,None,None]) + + self.squareList[0].setJumps([None,None,9,None]) + self.squareList[1].setJumps([None,8,10,None]) + self.squareList[2].setJumps([None,9,11,None]) + self.squareList[3].setJumps([None,10,None,None]) + self.squareList[4].setJumps([None,None,13,None]) + self.squareList[5].setJumps([None,12,14,None]) + self.squareList[6].setJumps([None,13,15,None]) + self.squareList[7].setJumps([None,14,None,None]) + self.squareList[8].setJumps([None,None,17,1]) + self.squareList[9].setJumps([0,16,18,2]) + self.squareList[10].setJumps([1,17,19,3]) + self.squareList[11].setJumps([2,18,None,None]) + self.squareList[12].setJumps([None,None,21,5]) + self.squareList[13].setJumps([4,20,22,6]) + self.squareList[14].setJumps([5,21,23,7]) + self.squareList[15].setJumps([6,22,None,None]) + self.squareList[16].setJumps([None,None,25,9]) + self.squareList[17].setJumps([8,24,26,10]) + self.squareList[18].setJumps([9,25,27,11]) + self.squareList[19].setJumps([10,26,None,None]) + self.squareList[20].setJumps([None,None, 29,13]) + self.squareList[21].setJumps([12,28,30,14]) + self.squareList[22].setJumps([13,29,31,15]) + self.squareList[23].setJumps([14,30,None,None]) + self.squareList[24].setJumps([None,None,None,17]) + self.squareList[25].setJumps([16,None,None,18]) + self.squareList[26].setJumps([17,None,None,19]) + self.squareList[27].setJumps([18,None,None,None]) + self.squareList[28].setJumps([None,None,None,21]) + self.squareList[29].setJumps([20,None,None,22]) + self.squareList[30].setJumps([21,None,None,23]) + self.squareList[31].setJumps([22,None,None,None]) + + def delete(self): + for x in self.squareList: + x.delete() + del self.squareList + + def getSquare(self, arrayLoc): + return self.squareList[arrayLoc] + def getState(self, squareNum): + return self.squareList[squareNum].getState() + def setState(self, squareNum, newState): + self.squareList[squareNum].setState(newState) + def getAdjacent(self, squareNum): + return self.squareList[squareNum].adjacent + def getStates(self): + retList = [] + for x in range(32): + retList.append(self.squareList[x].getState()) + return retList + def setStates(self, squares): + y = 0 + for x in range(32): + self.squareList[x].setState(squares[x]) + def getJumps(self, squareNum): + return self.squareList[squareNum].jumps + + + +#STATES == +# 0 == unnocupied +# 1 == Player 1 - normal peice +# 2 == player 2 -normal peice +# 3 == player 1 - KING +# 4 == player 2 - KING +class CheckersTile: + def __init__(self, tileNum): + self.tileNum = tileNum + self.state = 0 + self.adjacent = [] + self.jumps = [] + def delete(self): + del self.tileNum + del self.state + del self.adjacent + def setJumps(self, jumpList): + for x in jumpList: + self.jumps.append(x) + def getJumps(self): + return self.jumps + def setAdjacent(self, adjList): + for x in adjList: + self.adjacent.append(x) + def getAdjacent(self): + return self.adjacent + def setState(self, newState): + self.state = newState + def getState(self): + return self.state + def getNum(self): + return self.tileNum + diff --git a/toontown/src/safezone/ChineseCheckersBoard.py b/toontown/src/safezone/ChineseCheckersBoard.py new file mode 100644 index 0000000..a82c98f --- /dev/null +++ b/toontown/src/safezone/ChineseCheckersBoard.py @@ -0,0 +1,268 @@ +#Class ChineseCheckers: This is the actual Distributed Chinese Checkers +#Game Board. This class has alot of get and set functions to change the state +#of the game board and manipulate it. +# +#**NOTE** +# The design of this class is that such the AI will be the only one to +#manipulate the 'state' of the board. Important to make sure that the client +#does not change anything in the board - rather just looks and asks the AI to +#give him updates on the boards game state +# +#Privates: +# squareList => This is the actual list of 121 elements that are all +# of the legal positions inside of a chinese checkers +# game. These will be accessed via the get/set functions +# Important to note that in all of the Accessor/Changer functions there are +# also ****Offset() functions, the offset simply accounts for the difference +# between 1 and 0 based indexing. This is needed because when the adjacent +# List was handwritten, the chart (below) is in 1 based indexing, thus in the +# adjacentcy list when traversing you would need to account for the offset. +# +# +#Board Looks like this in the squareList. Indexes of this are obviously off by +#one because this chart is 1 based indexing. +# +# +# 120 Blue +# +# 118 119 +# +# 115 116 117 +# +# 111 112 113 114 +#Purple +# 98 99 100 101 102 103 104 105 106 107 108 109 110 Pink +# +# 86 87 88 89 90 91 92 93 94 95 96 97 +# +# 75 76 77 78 79 80 81 82 83 84 85 +# +# 65 66 67 68 69 70 71 72 73 74 +# +# 56 57 58 59 60 61 62 63 64 +# +# 46 47 48 49 50 51 52 53 54 55 +# +# 35 36 37 38 39 40 41 42 43 44 45 +# +# 23 24 25 26 27 28 29 30 31 32 33 34 +# +# 10 11 12 13 14 15 16 17 18 19 20 21 22 Red +#Yellow +# 6 7 8 9 +# +# 3 4 5 +# +# 1 2 +# +# 0 Green +#[0] GREEN [1] YELLOW [2] PURPLE [3] BLUE [4] PINK [5] RED + +class ChineseCheckersBoard: + def __init__(self): + self.squareList = [] + for x in range(121): + self.squareList.append(CheckersSquare(x)) + + self.squareList[ 0 ].setAdjacent([None,1,2,None,None,None]) + self.squareList[ 1 ].setAdjacent([None,3,4,2,0,None]) + self.squareList[ 2 ].setAdjacent([1,4,5,None,None,0]) + self.squareList[ 3 ].setAdjacent([None,6,7,4,1,None]) + self.squareList[ 4 ].setAdjacent([3,7,8,5,2,1]) + self.squareList[ 5 ].setAdjacent([4,8,9,None,None,2]) + self.squareList[ 6 ].setAdjacent([None,14,15,7,3,None]) + self.squareList[ 7 ].setAdjacent([6,15,16,8,4,3]) + self.squareList[ 8 ].setAdjacent([7,16,17,9,5,4]) + self.squareList[ 9 ].setAdjacent([8,17,18,None,None,5]) + self.squareList[ 10 ].setAdjacent([None,None,23,11,None,None]) + self.squareList[ 11 ].setAdjacent([10,23,24,12,None,None]) + self.squareList[ 12 ].setAdjacent([11,24,25,13,None,None]) + self.squareList[ 13 ].setAdjacent([12,25,26,14,None,None]) + self.squareList[ 14 ].setAdjacent([13,26,27,15,6,None]) + self.squareList[ 15 ].setAdjacent([14,27,28,16,7,6]) + self.squareList[ 16 ].setAdjacent([15,28,29,17,8,7]) + self.squareList[ 17 ].setAdjacent([16,29,30,18,9,8]) + self.squareList[ 18 ].setAdjacent([17,30,31,19,None,9]) + self.squareList[ 19 ].setAdjacent([18,31,32,20,None,None]), + self.squareList[ 20 ].setAdjacent([19,32,33,21,None,None]) + self.squareList[ 21 ].setAdjacent([20,33,34,22,None,None]) + self.squareList[ 22 ].setAdjacent([21,34,None,None,None,None]) + self.squareList[ 23 ].setAdjacent([None,None,35,24,11,10]) + self.squareList[ 24 ].setAdjacent([23,35,36,25,12,11]) + self.squareList[ 25 ].setAdjacent([24,36,37,26,13,12]) + self.squareList[ 26 ].setAdjacent([25,37,38,27,14,13]) + self.squareList[ 27 ].setAdjacent([26,38,39,28,15,14]) + self.squareList[ 28 ].setAdjacent([27,39,40,29,16,15]) + self.squareList[ 29 ].setAdjacent([28,40,41,30,17,16]) + self.squareList[ 30 ].setAdjacent([29,41,42,31,18,17]) + self.squareList[ 31 ].setAdjacent([30,42,43,32,19,18]) + self.squareList[ 32 ].setAdjacent([31,43,44,33,20,19]) + self.squareList[ 33 ].setAdjacent([32,44,45,34,21,20]) + self.squareList[ 34 ].setAdjacent([33,45,None,None,22,21]) + self.squareList[ 35 ].setAdjacent([None,None,46,36,24,23]) + self.squareList[ 36 ].setAdjacent([35,46,47,37,25,24]) + self.squareList[ 37 ].setAdjacent([36,47,48,38,26,25]) + self.squareList[ 38 ].setAdjacent([37,48,49,39,27,26]) + self.squareList[ 39 ].setAdjacent([38,49,50,40,28,27]) + self.squareList[ 40 ].setAdjacent([39,50,51,41,29,28]) + self.squareList[ 41 ].setAdjacent([40,51,52,42,30,29]) + self.squareList[ 42 ].setAdjacent([41,52,53,43,31,30]) + self.squareList[ 43 ].setAdjacent([42,53,54,44,32,31]) + self.squareList[ 44 ].setAdjacent([43,54,55,45,33,32]) + self.squareList[ 45 ].setAdjacent([44,55,None,None,34,33]) + self.squareList[ 46 ].setAdjacent([None,None,56,47,36,35]) + self.squareList[ 47 ].setAdjacent([46,56,57,48,37,36]) + self.squareList[ 48 ].setAdjacent([47,57,58,49,38,37]) + self.squareList[ 49 ].setAdjacent([48,58,59,50,39,38]) + self.squareList[ 50 ].setAdjacent([49,59,60,51,40,39]) + self.squareList[ 51 ].setAdjacent([50,60,61,52,41,40]) + self.squareList[ 52 ].setAdjacent([51,61,62,53,42,41]) + self.squareList[ 53 ].setAdjacent([52,62,63,54,43,42]) + self.squareList[ 54 ].setAdjacent([53,63,64,55,44,43]) + self.squareList[ 55 ].setAdjacent([54,64,None,None,45,44]) + self.squareList[ 56 ].setAdjacent([None,65,66,57,47,46]) + self.squareList[ 57 ].setAdjacent([56,66,67,58,48,47]) + self.squareList[ 58 ].setAdjacent([57,67,68,59,49,48]) + self.squareList[ 59 ].setAdjacent([58,68,69,60,50,49]) + self.squareList[ 60 ].setAdjacent([59,69,70,61,51,50]) + self.squareList[ 61 ].setAdjacent([60,70,71,62,52,51]) + self.squareList[ 62 ].setAdjacent([61,71,72,63,53,52]) + self.squareList[ 63 ].setAdjacent([62,72,73,64,54,53]) + self.squareList[ 64 ].setAdjacent([63,73,74,None,55,54]) + self.squareList[ 65 ].setAdjacent([None,75,76,66,56,None]) + self.squareList[ 66 ].setAdjacent([65,76,77,67,57,56]) + self.squareList[ 67 ].setAdjacent([66,77,78,68,58,57]) + self.squareList[ 68 ].setAdjacent([67,78,79,69,59,58]) + self.squareList[ 69 ].setAdjacent([68,79,80,70,60,61]) + self.squareList[ 70 ].setAdjacent([69,80,81,71,61,60]) + self.squareList[ 71 ].setAdjacent([70,81,82,72,62,61]) + self.squareList[ 72 ].setAdjacent([71,82,83,73,63,62]) + self.squareList[ 73 ].setAdjacent([72,83,84,74,64,63]) + self.squareList[ 74 ].setAdjacent([73,84,85,None,None,64]) + self.squareList[ 75 ].setAdjacent([None,86,87,76,65,None]) + self.squareList[ 76 ].setAdjacent([75,87,88,77,66,65]) + self.squareList[ 77 ].setAdjacent([76,88,89,78,67,66]) + self.squareList[ 78 ].setAdjacent([77,89,90,79,68,67]) + self.squareList[ 79 ].setAdjacent([78,90,91,80,69,68]) + self.squareList[ 80 ].setAdjacent([79,91,92,81,70,69]) + self.squareList[ 81 ].setAdjacent([80,92,93,82,71,70]) + self.squareList[ 82 ].setAdjacent([81,93,94,83,72,71]) + self.squareList[ 83 ].setAdjacent([82,94,95,84,73,72]) + self.squareList[ 84 ].setAdjacent([83,95,96,85,74,73]) + self.squareList[ 85 ].setAdjacent([84,96,97,None,None,74]) + self.squareList[ 86 ].setAdjacent([None,98,99,87,75,None]) + self.squareList[ 87 ].setAdjacent([86,99,100,88,76,75]) + self.squareList[ 88 ].setAdjacent([87,100,101,89,77,76]) + self.squareList[ 89 ].setAdjacent([88,101,102,90,78,77]) + self.squareList[ 90 ].setAdjacent([89,102,103,91,79,78]) + self.squareList[ 91 ].setAdjacent([90,103,104,92,80,79]) + self.squareList[ 92 ].setAdjacent([91,104,105,93,81,80]) + self.squareList[ 93 ].setAdjacent([92,105,106,94,82,81]) + self.squareList[ 94 ].setAdjacent([93,106,107,95,83,82]) + self.squareList[ 95 ].setAdjacent([94,107,108,96,84,83]) + self.squareList[ 96 ].setAdjacent([95,108,109,97,85,84]) + self.squareList[ 97 ].setAdjacent([96,109,110,None,None,85]) + self.squareList[ 98 ].setAdjacent([None,None,None,99,86,None]) + self.squareList[ 99 ].setAdjacent([98,None,None,100,87,86]) + self.squareList[ 100 ].setAdjacent([99,None,None,101,88,87]) + self.squareList[ 101 ].setAdjacent([100,None,None,102,89,88]) + self.squareList[ 102 ].setAdjacent([101,None,111,103,90,89]) + self.squareList[ 103 ].setAdjacent([102,111,112,104,91,90]) + self.squareList[ 104 ].setAdjacent([103,112,113,105,92,91]) + self.squareList[ 105 ].setAdjacent([104,113,114,106,93,92]) + self.squareList[ 106 ].setAdjacent([105,114,None,107,94,93]) + self.squareList[ 107 ].setAdjacent([106,None,None,108,95,94]) + self.squareList[ 108 ].setAdjacent([107,None,None,109,96,95]) + self.squareList[ 109 ].setAdjacent([108,None,None,110,97,96]) + self.squareList[ 110 ].setAdjacent([109,None,None,None,None,97]) + self.squareList[ 111 ].setAdjacent([None,None,115,112,103,102]) + self.squareList[ 112 ].setAdjacent([111,115,116,113,104,103]) + self.squareList[ 113 ].setAdjacent([112,116,117,114,105,104]) + self.squareList[ 114 ].setAdjacent([113,117,None,None,106,105]) + self.squareList[ 115 ].setAdjacent([None,None,118,116,112,111]) + self.squareList[ 116 ].setAdjacent([115,118,119,117,113,112]) + self.squareList[ 117 ].setAdjacent([116,119,None,None,114,113]) + self.squareList[ 118 ].setAdjacent([None,None,120,119,116,115]) + self.squareList[ 119 ].setAdjacent([118,120,None,None,117,116]) + self.squareList[ 120 ].setAdjacent([None,None,None,None,119,118]) + def delete(self): + for x in self.squareList: + x.delete() + del self.squareList + + def getSquare(self, arrayLoc): + return self.squareList[arrayLoc] + def getSquareOffset(self, arrayLoc): + return self.squareList[arrayLoc-1] + def getState(self, squareNum): + return self.squareList[squareNum].getState() + def getStateOffset(self,arrayLoc): + return self.squareList[squareNum-1].getState() + def setState(self, squareNum, newState): + self.squareList[squareNum].setState(newState) + def setStateOffset(self, squareNum, newState): + self.squareList[squareNum-1].setState(newState) + def getAdjacent(self, squareNum): + return self.squareList[squareNum].adjacent + def getAdjacentOffset(self, squareNum): + return self.squareList[squareNum-1].adjacent + def getStates(self): + retList = [] + for x in range(121): + retList.append(self.squareList[x].getState()) + return retList + def setStates(self, squares): + y = 0 + for x in range(121): + self.squareList[x].setState(squares[x]) + + + +#---------------------------------------------------------------# +#CheckersSquare: This is the base object for +#a square inside of a chinese checkers game. By 'square', I mean +#a movable location to which a peice can be placed or moved to. +# +#A Square has possible 7 states +# A state of 0 => Unnocupied +# A state of 1-6 => Owned By that corresponding player +# +#A Square also has a corresponding Adjacency list. Meaning: +#from a given square there are up to 6 adjacent squares to it +#that must be represented in a directed manner. This List for the +#square will be represented in this fashion: X is the sqaure on the board +# +# 1 2 +# 0 X 3 +# 5 4 +# +# In a clockwise fashion. If an element of the squares adjacency list +#is None, then there does not exist a square in that given direction from +#that square. +#----------------------------------------------------------------# +class CheckersSquare: + def __init__(self, tileNu): + self.tileNum = tileNu + self.state = 0; #0 for Begins as unnocupied square + self.adjacent = [] + def delete(self): + del self.tileNum + del self.state + del self.adjacent + def setAdjacent(self, adjList): + for x in adjList: + self.adjacent.append(x) + def getAdjacent(self): + return self.adjacent + def setState(self, newState): + self.state = newState + def getState(self): + return self.state + def getNum(self): + return self.tileNum + + + + + + diff --git a/toontown/src/safezone/DDPlayground.py b/toontown/src/safezone/DDPlayground.py new file mode 100644 index 0000000..2842b6c --- /dev/null +++ b/toontown/src/safezone/DDPlayground.py @@ -0,0 +1,211 @@ + +from pandac.PandaModules import * + +import Playground +from direct.task.Task import Task +import random +from direct.fsm import ClassicFSM, State +from direct.actor import Actor +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import Place + +class DDPlayground(Playground.Playground): + notify = DirectNotifyGlobal.directNotify.newCategory("DDPlayground") + + def __init__(self, loader, parentFSM, doneEvent): + assert self.notify.debugStateCall(self) + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + # Underwater stuff + self.cameraSubmerged = -1 + self.toonSubmerged = -1 + self.activityFsm = ClassicFSM.ClassicFSM( + 'Activity', [ + State.State( + 'off', + self.enterOff, + self.exitOff, + ['OnBoat']), + State.State( + 'OnBoat', + self.enterOnBoat, + self.exitOnBoat, + ['off']) + ], + # Initial state + 'off', + # Final state + 'off', + ) + self.activityFsm.enterInitialState() + + def load(self): + assert self.notify.debugStateCall(self) + Playground.Playground.load(self) + + def unload(self): + assert self.notify.debugStateCall(self) + del self.activityFsm + Playground.Playground.unload(self) + + def enter(self, requestStatus): + assert self.notify.debugStateCall(self) + self.nextSeagullTime = 0 + taskMgr.add(self.__seagulls, 'dd-seagulls') + self.loader.hood.setWhiteFog() + # donald=self.loader.donald + # donald.loop("wheel") + # donald.setZ(3.95) + # donald.setY(-1.0) + # donald.reparentTo(base.cr.playGame.hood.loader.boat) + Playground.Playground.enter(self, requestStatus) + + def exit(self): + assert self.notify.debugStateCall(self) + Playground.Playground.exit(self) + taskMgr.remove('dd-check-toon-underwater') + taskMgr.remove('dd-check-cam-underwater') + taskMgr.remove('dd-seagulls') + # Clean up underwater state + self.loader.hood.setNoFog() + # donald=self.loader.donald + # donald.stop() + # donald.reparentTo(hidden) + + def enterStart(self): + assert self.notify.debugStateCall(self) + self.cameraSubmerged = 0 + self.toonSubmerged = 0 + taskMgr.add(self.__checkToonUnderwater, + 'dd-check-toon-underwater') + taskMgr.add(self.__checkCameraUnderwater, + 'dd-check-cam-underwater') + + def enterDoorOut(self): + assert self.notify.debugStateCall(self) + taskMgr.remove('dd-check-toon-underwater') + + def exitDoorOut(self): + assert self.notify.debugStateCall(self) + + def enterDoorIn(self, requestStatus): + assert self.notify.debugStateCall(self) + Playground.Playground.enterDoorIn(self, requestStatus) + taskMgr.add(self.__checkToonUnderwater, + 'dd-check-toon-underwater') + + def __checkCameraUnderwater(self, task): + # spammy: assert self.notify.debugStateCall(self) + # We need to take into account the height of the local toon + # if (base.localAvatar.getZ() < -2.3314585): + # It is more accurate to use the camera because it can + # move independently of the toon + if (camera.getZ(render) < 1.0): + self.__submergeCamera() + else: + self.__emergeCamera() + return Task.cont + + def __checkToonUnderwater(self, task): + # spammy: assert self.notify.debugStateCall(self) + # We need to take into account the height of the local toon + if (base.localAvatar.getZ() < -2.3314585): + self.__submergeToon() + else: + self.__emergeToon() + return Task.cont + + def __submergeCamera(self): + if (self.cameraSubmerged == 1): + return + assert self.notify.debugStateCall(self) + self.loader.hood.setUnderwaterFog() + base.playSfx(self.loader.underwaterSound, looping = 1, volume = 0.8) + self.loader.seagullSound.stop() + taskMgr.remove('dd-seagulls') + self.cameraSubmerged = 1 + self.walkStateData.setSwimSoundAudible(1) + + def __emergeCamera(self): + if (self.cameraSubmerged == 0): + return + assert self.notify.debugStateCall(self) + self.loader.hood.setWhiteFog() + self.loader.underwaterSound.stop() + self.nextSeagullTime = random.random() * 8.0 + taskMgr.add(self.__seagulls, 'dd-seagulls') + self.cameraSubmerged = 0 + self.walkStateData.setSwimSoundAudible(0) + + def __submergeToon(self): + if (self.toonSubmerged == 1): + return + assert self.notify.debugStateCall(self) + base.playSfx(self.loader.submergeSound) # plays a splash sound + + # Make sure you are in walk mode This fixes a bug where you could + # open your stickerbook over the water and get stuck in swim mode + # becuase the Place was still in StickerBook state. + if base.config.GetBool('disable-flying-glitch') == 0: + self.fsm.request('walk') + + # You have to pass in the swim sound effect to swim mode. + self.walkStateData.fsm.request('swimming', [self.loader.swimSound]) + # Let everyone else see your splash + pos = base.localAvatar.getPos(render) + base.localAvatar.d_playSplashEffect(pos[0], pos[1], 1.675) + self.toonSubmerged = 1 + + def __emergeToon(self): + if (self.toonSubmerged == 0): + return + assert self.notify.debugStateCall(self) + self.walkStateData.fsm.request('walking') + self.toonSubmerged = 0 + + def __seagulls(self, task): + if (task.time < self.nextSeagullTime): + return Task.cont + assert self.notify.debugStateCall(self) + base.playSfx(self.loader.seagullSound) + self.nextSeagullTime = task.time + random.random() * 4.0 + 8.0 + return Task.cont + + def enterTeleportIn(self, requestStatus): + assert self.notify.debugStateCall(self) + self.toonSubmerged = -1 + taskMgr.remove('dd-check-toon-underwater') + Playground.Playground.enterTeleportIn(self, requestStatus) + + def teleportInDone(self): + """ + Override Place.py teleportInDone to check if we are cameraSubmerged. + If we are cameraSubmerged, we should swim instead of walk + """ + assert self.notify.debugStateCall(self) + self.toonSubmerged = -1 + taskMgr.add(self.__checkToonUnderwater, + 'dd-check-toon-underwater') + Playground.Playground.teleportInDone(self) + + ##### Off state ##### + + def enterOff(self): + assert self.notify.debugStateCall(self) + return None + + def exitOff(self): + assert self.notify.debugStateCall(self) + return None + + ##### OnBoat state ##### + + def enterOnBoat(self): + assert self.notify.debugStateCall(self) + base.localAvatar.b_setParent(ToontownGlobals.SPDonaldsBoat) + base.playSfx(self.loader.waterSound, looping=1) + + def exitOnBoat(self): + assert self.notify.debugStateCall(self) + base.localAvatar.b_setParent(ToontownGlobals.SPRender) + self.loader.waterSound.stop() diff --git a/toontown/src/safezone/DDSafeZoneLoader.py b/toontown/src/safezone/DDSafeZoneLoader.py new file mode 100644 index 0000000..e4c2315 --- /dev/null +++ b/toontown/src/safezone/DDSafeZoneLoader.py @@ -0,0 +1,72 @@ + +from pandac.PandaModules import * + +import SafeZoneLoader +import DDPlayground +from direct.fsm import State +from toontown.char import CharDNA +from toontown.char import Char +from toontown.toonbase import ToontownGlobals + +class DDSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = DDPlayground.DDPlayground + self.musicFile = "phase_6/audio/bgm/DD_nbrhood.mid" + self.activityMusicFile = "phase_6/audio/bgm/DD_SZ_activity.mid" + self.dnaFile = "phase_6/dna/donalds_dock_sz.dna" + self.safeZoneStorageDNAFile = "phase_6/dna/storage_DD_sz.dna" + + def load(self): + SafeZoneLoader.SafeZoneLoader.load(self) + self.seagullSound = base.loadSfx('phase_6/audio/sfx/SZ_DD_Seagull.mp3') + self.underwaterSound = base.loadSfx('phase_4/audio/sfx/AV_ambient_water.mp3') + self.swimSound = base.loadSfx('phase_4/audio/sfx/AV_swim_single_stroke.mp3') + self.submergeSound = base.loadSfx('phase_5.5/audio/sfx/AV_jump_in_water.mp3') + water = self.geom.find('**/water') + water.setTransparency(1) + water.setColor(1,1,1,0.8) + self.boat = self.geom.find('**/donalds_boat') + if (self.boat.isEmpty()): + self.notify.error("Boat not found") + else: + wheel = self.boat.find('**/wheel') + if (wheel.isEmpty()): + self.notify.warning("Wheel not found") + else: + wheel.hide() + + # Stash the boat model until the DistributedObject is + # generated for it. + self.boat.stash() + + # get the temp donald model and channel + # dna = CharDNA.CharDNA() + # dna.newChar("dw") + # self.donald = Char.Char() + # self.donald.setDNA(dna) + + self.dockSound = base.loadSfx('phase_6/audio/sfx/SZ_DD_dockcreak.mp3') + self.foghornSound = base.loadSfx('phase_5/audio/sfx/SZ_DD_foghorn.mp3') + self.bellSound = base.loadSfx('phase_6/audio/sfx/SZ_DD_shipbell.mp3') + self.waterSound = base.loadSfx('phase_6/audio/sfx/SZ_DD_waterlap.mp3') + + def unload(self): + SafeZoneLoader.SafeZoneLoader.unload(self) + del self.seagullSound + del self.underwaterSound + del self.swimSound + del self.dockSound + del self.foghornSound + del self.bellSound + del self.waterSound + del self.submergeSound + del self.boat + #self.donald.delete() + #del self.donald + + def enter(self, requestStatus): + SafeZoneLoader.SafeZoneLoader.enter(self, requestStatus) + + def exit(self): + SafeZoneLoader.SafeZoneLoader.exit(self) diff --git a/toontown/src/safezone/DDTreasurePlannerAI.py b/toontown/src/safezone/DDTreasurePlannerAI.py new file mode 100644 index 0000000..6727d7a --- /dev/null +++ b/toontown/src/safezone/DDTreasurePlannerAI.py @@ -0,0 +1,50 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedDDTreasureAI + +class DDTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 10 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedDDTreasureAI.DistributedDDTreasureAI, # Constructor + "DDTreasurePlanner", + 20, # seconds per spawn + 2 # Max of two treasures + ) + return None + + def initSpawnPoints(self): + self.spawnPoints = [ + (52.9072, -23.4768, -12.308), + (35.3827, -51.9196, -12.308), + (17.4252, -57.3107, -12.308), + (-0.716054, -68.5, -12.308), + (-29.0169, -66.8887, -12.308), + (-63.492, -64.2191, -12.308), + (-72.2423, -58.3686, -12.308), + (-97.9602, -42.8905, -12.308), + (-102.215, -34.1519, -12.308), + (-102.978, -4.09065, -12.308), + (-101.305, 30.6454, -12.308), + (-45.0621, -21.0088, -12.308), + (-11.4043, -29.0816, -12.308), + (2.33548, -7.71722, -12.308), + (-8.643, 33.9891, -12.308), + (-53.224, 18.1293, -12.308), + (-99.7225, -8.1298, -12.308), + (-100.457, 28.351, -12.308), + (-76.7946, 4.21199, -12.308), + (-64.9137, 37.5765, -12.308), + (-17.6075, 102.135, -12.308), + (-23.4112, 127.777, -12.308), + (-11.3513, 128.991, -12.308), + (-14.1068, 83.2043, -12.308), + (53.2685, 24.3585, -12.308), + (41.4197, 4.35384, -12.308), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/DGPlayground.py b/toontown/src/safezone/DGPlayground.py new file mode 100644 index 0000000..a85d844 --- /dev/null +++ b/toontown/src/safezone/DGPlayground.py @@ -0,0 +1,48 @@ +from pandac.PandaModules import * + +import Playground +import random +from direct.task import Task + +class DGPlayground(Playground.Playground): + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Playground.Playground.load(self) + + def unload(self): + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + self.nextBirdTime = 0 + taskMgr.add(self.__birds, 'DG-birds') + + def exit(self): + Playground.Playground.exit(self) + taskMgr.remove('DG-birds') + + def __birds(self, task): + if (task.time < self.nextBirdTime): + return Task.cont + randNum = random.random() + bird = (int(randNum * 100) % 4) + 1 + if (bird == 1): + base.playSfx(self.loader.bird1Sound) + elif (bird == 2): + base.playSfx(self.loader.bird2Sound) + elif (bird == 3): + base.playSfx(self.loader.bird3Sound) + elif (bird == 4): + base.playSfx(self.loader.bird4Sound) + self.nextBirdTime = task.time + randNum * 20.0 + return Task.cont + + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Goofy)) diff --git a/toontown/src/safezone/DGSafeZoneLoader.py b/toontown/src/safezone/DGSafeZoneLoader.py new file mode 100644 index 0000000..d9b65df --- /dev/null +++ b/toontown/src/safezone/DGSafeZoneLoader.py @@ -0,0 +1,36 @@ +from pandac.PandaModules import * + +import SafeZoneLoader +import DGPlayground + +class DGSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = DGPlayground.DGPlayground + self.musicFile = "phase_8/audio/bgm/DG_nbrhood.mid" + self.activityMusicFile = "phase_8/audio/bgm/DG_SZ.mid" + self.dnaFile = "phase_8/dna/daisys_garden_sz.dna" + self.safeZoneStorageDNAFile = "phase_8/dna/storage_DG_sz.dna" + + def load(self): + SafeZoneLoader.SafeZoneLoader.load(self) + + # sounds + self.bird1Sound = base.loadSfx('phase_8/audio/sfx/SZ_DG_bird_01.mp3') + self.bird2Sound = base.loadSfx('phase_8/audio/sfx/SZ_DG_bird_02.mp3') + self.bird3Sound = base.loadSfx('phase_8/audio/sfx/SZ_DG_bird_03.mp3') + self.bird4Sound = base.loadSfx('phase_8/audio/sfx/SZ_DG_bird_04.mp3') + + def unload(self): + SafeZoneLoader.SafeZoneLoader.unload(self) + del self.bird1Sound + del self.bird2Sound + del self.bird3Sound + del self.bird4Sound + + def enter(self, requestStatus): + SafeZoneLoader.SafeZoneLoader.enter(self, requestStatus) + + def exit(self): + SafeZoneLoader.SafeZoneLoader.exit(self) + diff --git a/toontown/src/safezone/DGTreasurePlannerAI.py b/toontown/src/safezone/DGTreasurePlannerAI.py new file mode 100644 index 0000000..820992f --- /dev/null +++ b/toontown/src/safezone/DGTreasurePlannerAI.py @@ -0,0 +1,44 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedDGTreasureAI + +class DGTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 10 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedDGTreasureAI.DistributedDGTreasureAI, # Constructor + "DGTreasurePlanner", + 15, # seconds per spawn + 2, # Max of two treasures + ) + return None + + def initSpawnPoints(self): + self.spawnPoints = [ + (-49, 156, 0.0), + (-59, 50, 0.0), + (19, 16, 0.0), + (76, 38, 1.1), + (102, 121, 0.0), + (69, 123, 0.0), + (49, 105, 0.0), + (24, 156, 0.0), + (-27, 127, 0.0), + (-56, 105, 0.0), + (-40, 113, 0.0), + (25, 114, 0.0), + (-6, 84, 0.0), + (19, 96, 0.0), + (0, 114, 0.0), + (-78, 157, 10.0), + (-33.4, 218.2, 10.0), + (57, 205, 10.0), + (32, 77, 0.0), + (-102, 101, 0.0), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/DLPlayground.py b/toontown/src/safezone/DLPlayground.py new file mode 100644 index 0000000..4f19d69 --- /dev/null +++ b/toontown/src/safezone/DLPlayground.py @@ -0,0 +1,16 @@ +from pandac.PandaModules import * + +import Playground +import random + +class DLPlayground(Playground.Playground): + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Donald)) diff --git a/toontown/src/safezone/DLSafeZoneLoader.py b/toontown/src/safezone/DLSafeZoneLoader.py new file mode 100644 index 0000000..fb6a8df --- /dev/null +++ b/toontown/src/safezone/DLSafeZoneLoader.py @@ -0,0 +1,13 @@ +from pandac.PandaModules import * + +import SafeZoneLoader +import DLPlayground + +class DLSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = DLPlayground.DLPlayground + self.musicFile = "phase_8/audio/bgm/DL_nbrhood.mid" + self.activityMusicFile = "phase_8/audio/bgm/DL_SZ_activity.mid" + self.dnaFile = "phase_8/dna/donalds_dreamland_sz.dna" + self.safeZoneStorageDNAFile = "phase_8/dna/storage_DL_sz.dna" diff --git a/toontown/src/safezone/DLTreasurePlannerAI.py b/toontown/src/safezone/DLTreasurePlannerAI.py new file mode 100644 index 0000000..763f50b --- /dev/null +++ b/toontown/src/safezone/DLTreasurePlannerAI.py @@ -0,0 +1,41 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedDLTreasureAI + +class DLTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 12 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedDLTreasureAI.DistributedDLTreasureAI, # Constructor + "DLTreasurePlanner", + 20, # seconds/spawn + 2 # Max of two treasures + ) + return None + + def initSpawnPoints(self): + self.spawnPoints = [ + (86, 69, -17.4), + (34, -48, -16.4), + (87, -70, -17.5), + (-98, 99, 0.0), + (51, 100, 0.0), + (-45, -12, -15.0), + (9, 8, -15.0), + (-24, 64, -17.2), + (-100, -99, 0.0), + (21, -101, 0.0), + (88, -17, -15.0), + (32, 70, -17.4), + (53, 35, -15.8), + (2, -30, -15.5), + (-40, -56, -16.8), + (-28, 18, -15.0), + (-34, -88, 0.0), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/DistributedBRTreasure.py b/toontown/src/safezone/DistributedBRTreasure.py new file mode 100644 index 0000000..fe81151 --- /dev/null +++ b/toontown/src/safezone/DistributedBRTreasure.py @@ -0,0 +1,7 @@ +import DistributedSZTreasure + +class DistributedBRTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_8/models/props/snowflake_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" diff --git a/toontown/src/safezone/DistributedBRTreasureAI.py b/toontown/src/safezone/DistributedBRTreasureAI.py new file mode 100644 index 0000000..148186a --- /dev/null +++ b/toontown/src/safezone/DistributedBRTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedBRTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedBoat.py b/toontown/src/safezone/DistributedBoat.py new file mode 100644 index 0000000..6a3011d --- /dev/null +++ b/toontown/src/safezone/DistributedBoat.py @@ -0,0 +1,241 @@ +from pandac.PandaModules import * + +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * + +from direct.distributed import DistributedObject +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from pandac.PandaModules import NodePath +from direct.directutil import Mopath +from toontown.toonbase import ToontownGlobals + +class DistributedBoat(DistributedObject.DistributedObject): + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + self.eastWestMopath = Mopath.Mopath() + self.westEastMopath = Mopath.Mopath() + self.eastWestMopathInterval = None + self.westEastMopathInterval = None + + self.fsm = ClassicFSM.ClassicFSM('DistributedBoat', + [State.State('off', + self.enterOff, + self.exitOff, + ['DockedEast', + 'SailingWest', + 'DockedWest', + 'SailingEast']), + State.State('DockedEast', + self.enterDockedEast, + self.exitDockedEast, + ['SailingWest', + 'SailingEast', + 'DockedWest']), + State.State('SailingWest', + self.enterSailingWest, + self.exitSailingWest, + ['DockedWest', + 'SailingEast', + 'DockedEast']), + State.State('DockedWest', + self.enterDockedWest, + self.exitDockedWest, + ['SailingEast', + 'SailingWest', + 'DockedEast']), + State.State('SailingEast', + self.enterSailingEast, + self.exitSailingEast, + ['DockedEast', + 'DockedWest', + 'SailingWest'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + self.boat = base.cr.playGame.hood.loader.boat + base.cr.parentMgr.registerParent(ToontownGlobals.SPDonaldsBoat, + self.boat) + + self.setupTracks() + self.accept('enterdonalds_boat_floor', self.__handleOnFloor) + self.accept('exitdonalds_boat_floor', self.__handleOffFloor) + + def setupTracks(self): + # Setup Intervals and Tracks + # Sail boat from east pier to west pier + boat = self.boat + + # Now that the DistributedObject is created, we can unstash the boat. + boat.unstash() + + # Sound effects + dockSound = self.cr.playGame.hood.loader.dockSound + foghornSound = self.cr.playGame.hood.loader.foghornSound + bellSound = self.cr.playGame.hood.loader.bellSound + + # Sail boat from east pier to west pier. Play the bell as the + # boat leaves, and the foghorn as it arrives. + self.eastWestMopath.loadFile('phase_6/paths/dd-e-w') + self.eastWestMopathInterval = MopathInterval(self.eastWestMopath, boat) + ewBoatTrack = ParallelEndTogether( + Parallel(self.eastWestMopathInterval, + SoundInterval(bellSound, node=boat)), + SoundInterval(foghornSound, node=boat), + name='ew-boat') + + # Sail boat from west pier to east pier. Play the bell as the + # boat leaves, and the foghorn as it arrives. + self.westEastMopath.loadFile('phase_6/paths/dd-w-e') + self.westEastMopathInterval = MopathInterval(self.westEastMopath, boat) + weBoatTrack = ParallelEndTogether( + Parallel(self.westEastMopathInterval, + SoundInterval(bellSound, node=boat)), + SoundInterval(foghornSound, node=boat), + name='we-boat') + + # Piers + PIER_TIME = 5.0 + + eastPier = self.cr.playGame.hood.loader.geom.find('**/east_pier') + ePierHpr = VBase3(90, -44.2601, 0) + ePierTargetHpr = VBase3(90, 0.25, 0) + + westPier = self.cr.playGame.hood.loader.geom.find('**/west_pier') + wPierHpr = VBase3(-90, -44.2601, 0) + wPierTargetHpr = VBase3(-90, 0.25, 0) + + # Lower east pier as boat leaves + ePierDownTrack = Parallel( + LerpHprInterval(eastPier, PIER_TIME, + ePierHpr, ePierTargetHpr), + SoundInterval(dockSound, node=eastPier), + name='e-pier-down') + + # Raise east pier as boat arrives + ePierUpTrack = Parallel( + LerpHprInterval(eastPier, PIER_TIME, + ePierTargetHpr, ePierHpr), + SoundInterval(dockSound, node=eastPier), + name='e-pier-up') + + # Lower west pier as boat leaves + wPierDownTrack = Parallel( + LerpHprInterval(westPier, PIER_TIME, + wPierHpr, wPierTargetHpr), + SoundInterval(dockSound, node=westPier), + name='w-pier-down') + + # Raise west pier as boat arrives + wPierUpTrack = Parallel( + LerpHprInterval(westPier, PIER_TIME, + wPierTargetHpr, wPierHpr), + SoundInterval(dockSound, node=westPier), + name='w-pier-up') + + self.ewTrack = ParallelEndTogether( + Parallel(ewBoatTrack, + ePierDownTrack), # lower the pier as it leaves + wPierUpTrack, # and bring up the other pier as it arrives + name = 'ew-track') + + self.weTrack = ParallelEndTogether( + Parallel(weBoatTrack, + wPierDownTrack), # lower the pier as it leaves + ePierUpTrack, # and bring up the other pier as it arrives + name = 'we-track') + + def disable(self): + """disable(self) + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + base.cr.parentMgr.unregisterParent(ToontownGlobals.SPDonaldsBoat) + self.ignore('enterdonalds_boat_floor') + self.ignore('exitdonalds_boat_floor') + self.fsm.request('off') + DistributedObject.DistributedObject.disable(self) + self.ewTrack.finish() + self.weTrack.finish() + + def delete(self): + """delete(self) + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + self.eastWestMopath.reset() + self.westEastMopath.reset() + if self.eastWestMopathInterval.mopath: + self.eastWestMopathInterval.destroy() + if self.westEastMopathInterval.mopath: + self.westEastMopathInterval.destroy() + del self.eastWestMopath + del self.westEastMopath + del self.ewTrack + del self.weTrack + del self.fsm + DistributedObject.DistributedObject.delete(self) + + def setState(self, state, timestamp): + self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)]) + + def __handleOnFloor(self, collEntry): + self.cr.playGame.getPlace().activityFsm.request('OnBoat') + + def __handleOffFloor(self, collEntry): + self.cr.playGame.getPlace().activityFsm.request('off') + + ##### Off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### DockedEast state ##### + + def enterDockedEast(self, ts): + self.weTrack.finish() + return None + + def exitDockedEast(self): + return None + + ##### SailingWest state ##### + + def enterSailingWest(self, ts): + self.ewTrack.start(ts) + + def exitSailingWest(self): + self.ewTrack.finish() + + ##### DockedWest state ##### + + def enterDockedWest(self, ts): + self.ewTrack.finish() + return None + + def exitDockedWest(self): + return None + + ##### SailingEast state ##### + + def enterSailingEast(self, ts): + self.weTrack.start(ts) + + def exitSailingEast(self): + self.weTrack.finish() + return None diff --git a/toontown/src/safezone/DistributedBoatAI.py b/toontown/src/safezone/DistributedBoatAI.py new file mode 100644 index 0000000..1084d37 --- /dev/null +++ b/toontown/src/safezone/DistributedBoatAI.py @@ -0,0 +1,132 @@ +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObjectAI +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task + +class DistributedBoatAI(DistributedObjectAI.DistributedObjectAI): + + def __init__(self, air): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.fsm = ClassicFSM.ClassicFSM('DistributedBoatAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['DockedEast']), + State.State('DockedEast', + self.enterDockedEast, + self.exitDockedEast, + ['SailingWest']), + State.State('SailingWest', + self.enterSailingWest, + self.exitSailingWest, + ['DockedWest']), + State.State('DockedWest', + self.enterDockedWest, + self.exitDockedWest, + ['SailingEast']), + State.State('SailingEast', + self.enterSailingEast, + self.exitSailingEast, + ['DockedEast'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + return None + + def delete(self): + self.fsm.request('off') + DistributedObjectAI.DistributedObjectAI.delete(self) + + # setState() + + def b_setState(self, state): + self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()]) + self.fsm.request(state) + + def getState(self): + return [self.fsm.getCurrentState().getName(), + globalClockDelta.getRealNetworkTime()] + + ### How you start up the boat ### + def start(self): + self.b_setState('DockedEast') + return None + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### DockedEast state ##### + + def enterDockedEast(self): + taskMgr.doMethodLater(10.0, self.__departEast, 'depart-east') + return None + + def exitDockedEast(self): + taskMgr.remove('depart-east') + return None + + def __departEast(self, task): + self.b_setState('SailingWest') + return Task.done + + ##### SailingWest state ##### + + def enterSailingWest(self): + taskMgr.doMethodLater(20.0, self.__dockWest, 'dock-west') + return None + + def exitSailingWest(self): + taskMgr.remove('dock-west') + return None + + def __dockWest(self, task): + self.b_setState('DockedWest') + return Task.done + + ##### DockedWest state ##### + + def enterDockedWest(self): + taskMgr.doMethodLater(10.0, self.__departWest, 'depart-west') + return None + + def exitDockedWest(self): + taskMgr.remove('depart-west') + return None + + def __departWest(self, task): + self.b_setState('SailingEast') + return Task.done + + ##### SailingEast state ##### + + def enterSailingEast(self): + taskMgr.doMethodLater(20.0, self.__dockEast, 'dock-east') + return None + + def exitSailingEast(self): + taskMgr.remove('dock-east') + return None + + def __dockEast(self, task): + self.b_setState('DockedEast') + return Task.done diff --git a/toontown/src/safezone/DistributedButterfly.py b/toontown/src/safezone/DistributedButterfly.py new file mode 100644 index 0000000..0754c65 --- /dev/null +++ b/toontown/src/safezone/DistributedButterfly.py @@ -0,0 +1,362 @@ + +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedObject +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from pandac.PandaModules import NodePath +from direct.directutil import Mopath +from toontown.toonbase import ToontownGlobals +from direct.actor import Actor +import ButterflyGlobals +from direct.showbase import RandomNumGen +import random + +class DistributedButterfly(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedButterfly') + + id = 0 + # wings_1 (solid yellow) + # wings_2 (yellow w/ dots) (1, 1, 1), (0.2, 0, 1), (1, 0, 1), (0.8, 0, 1) + # wings_3 (solid white) (0.8, 0, 0.8), (0, 0.8, 0.8), (0.9, 0.4, 0.6) + # (0.9, 0.4, 0.4), (0.8, 0.5, 0.9), (0.4, 0.1, 0.7) + # wings_4 (white w/ dots) + # wings_5 (pale yellow w/ lines) (0.8, 0, 0.8), (0.6, 0.6, 0.9) + # (0.7, 0.6, 0.9), (0.8, 0.6, 0.9), (0.9, 0.6, 0.9), + # (1, 0.6, 0.9) + # wings_6 (blue & yellow) + wingTypes = ('wings_1', + 'wings_2', + 'wings_3', + 'wings_4', + 'wings_5', + 'wings_6') + yellowColors = (Vec4(1, 1, 1, 1), + Vec4(0.2, 0, 1, 1), + Vec4(0.8, 0, 1, 1)) + whiteColors = (Vec4(0.8, 0, 0.8, 1), + Vec4(0, 0.8, 0.8, 1), + Vec4(0.9, 0.4, 0.6, 1), + Vec4(0.9, 0.4, 0.4, 1), + Vec4(0.8, 0.5, 0.9, 1), + Vec4(0.4, 0.1, 0.7, 1)) + paleYellowColors = (Vec4(0.8, 0, 0.8, 1), + Vec4(0.6, 0.6, 0.9, 1), + Vec4(0.7, 0.6, 0.9, 1), + Vec4(0.8, 0.6, 0.9, 1), + Vec4(0.9, 0.6, 0.9, 1), + Vec4(1, 0.6, 0.9, 1)) + shadowScaleBig = Point3(0.07, 0.07, 0.07) + shadowScaleSmall = Point3(0.01, 0.01, 0.01) + + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + + self.fsm = ClassicFSM.ClassicFSM('DistributedButterfly', + [State.State('off', + self.enterOff, + self.exitOff, + ['Flying', 'Landed']), + State.State('Flying', + self.enterFlying, + self.exitFlying, + ['Landed']), + State.State('Landed', + self.enterLanded, + self.exitLanded, + ['Flying'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.butterfly = None + self.butterflyNode = None + self.curIndex = 0 + self.destIndex = 0 + self.time = 0.0 + self.ival = None + self.fsm.enterInitialState() + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + if self.butterfly: + return + + self.butterfly = Actor.Actor() + self.butterfly.loadModel( + 'phase_4/models/props/SZ_butterfly-mod.bam') + self.butterfly.loadAnims({ + 'flutter' : 'phase_4/models/props/SZ_butterfly-flutter.bam', + 'glide' : 'phase_4/models/props/SZ_butterfly-glide.bam', + 'land' : 'phase_4/models/props/SZ_butterfly-land.bam'}) + + # Randomly choose one of the butterfly wing patterns + index = self.doId % len(self.wingTypes) + chosenType = self.wingTypes[index] + node = self.butterfly.getGeomNode() + for type in self.wingTypes: + wing = node.find('**/' + type) + if (type != chosenType): + wing.removeNode() + else: + # Choose an appropriate blend color + if (index == 0 or index == 1): + color = self.yellowColors[self.doId % len(self.yellowColors)] + elif (index == 2 or index == 3): + color = self.whiteColors[self.doId % len(self.whiteColors)] + elif (index == 4): + color = self.paleYellowColors[self.doId % len(self.paleYellowColors)] + else: + color = Vec4(1, 1, 1, 1) + wing.setColor(color) + + # Make another copy of the butterfly model so we can LOD the + # blending. Butterflies that are far away won't bother to + # blend animations; nearby butterflies will use dynamic + # blending to combine two or more animations at once on + # playback for a nice fluttering and landing effect. + self.butterfly2 = Actor.Actor(other = self.butterfly) + + # Allow the nearby butterfly to blend between its three + # animations. All animations will be playing all the time; + # we'll control which one is visible by varying the control + # effect. + self.butterfly.enableBlend(blendType = PartBundle.BTLinear) + self.butterfly.loop('flutter') + self.butterfly.loop('land') + self.butterfly.loop('glide') + + # Make a random play rate so all the butterflies will be + # flapping at slightly different rates. This doesn't affect + # the rate at which the butterfly moves, just the rate at + # which the animation plays on the butterfly. + rng = RandomNumGen.RandomNumGen(self.doId) + playRate = 0.6 + 0.8 * rng.random() + self.butterfly.setPlayRate(playRate, 'flutter') + self.butterfly.setPlayRate(playRate, 'land') + self.butterfly.setPlayRate(playRate, 'glide') + self.butterfly2.setPlayRate(playRate, 'flutter') + self.butterfly2.setPlayRate(playRate, 'land') + self.butterfly2.setPlayRate(playRate, 'glide') + + # Also, a random glide contribution ratio. We'll blend a bit + # of the glide animation in with the flutter animation to + # dampen the effect of flutter. The larger the number here, + # the greater the dampening effect. Some butterflies will be + # more active than others. (Except when seen from a long way + # off, because of the LODNode, below.) + self.glideWeight = rng.random() * 2 + + lodNode = LODNode('butterfly-node') + lodNode.addSwitch(100, 40) # self.butterfly2 + lodNode.addSwitch(40, 0) # self.butterfly + + self.butterflyNode = NodePath(lodNode) + self.butterfly2.setH(180.0) + self.butterfly2.reparentTo(self.butterflyNode) + self.butterfly.setH(180.0) + self.butterfly.reparentTo(self.butterflyNode) + self.__initCollisions() + + # Set up the drop shadow + self.dropShadow = loader.loadModel( + 'phase_3/models/props/drop_shadow') + self.dropShadow.setColor(0, 0, 0, 0.3) + self.dropShadow.setPos(0, 0.1, -0.05) + self.dropShadow.setScale(self.shadowScaleBig) + self.dropShadow.reparentTo(self.butterfly) + + def disable(self): + """disable(self) + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + self.butterflyNode.reparentTo(hidden) + if (self.ival != None): + self.ival.finish() + self.__ignoreAvatars() + DistributedObject.DistributedObject.disable(self) + + def delete(self): + """delete(self) + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + self.butterfly.cleanup() + self.butterfly = None + self.butterfly2.cleanup() + self.butterfly2 = None + self.butterflyNode.removeNode() + self.__deleteCollisions() + self.ival = None + del self.fsm + DistributedObject.DistributedObject.delete(self) + + + def uniqueButterflyName(self, name): + DistributedButterfly.id += 1 + return (name + '-%d' % DistributedButterfly.id) + + def __detectAvatars(self): + self.accept('enter' + self.cSphereNode.getName(), + self.__handleCollisionSphereEnter) + + def __ignoreAvatars(self): + self.ignore('enter' + self.cSphereNode.getName()) + + def __initCollisions(self): + self.cSphere = CollisionSphere(0., 1., 0., 3.) + self.cSphere.setTangible(0) + self.cSphereNode = CollisionNode(self.uniqueButterflyName('cSphereNode')) + self.cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = self.butterflyNode.attachNewNode(self.cSphereNode) + self.cSphereNodePath.hide() + self.cSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + + def __deleteCollisions(self): + del self.cSphere + del self.cSphereNode + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + + def __handleCollisionSphereEnter(self, collEntry): + """ Response for a toon walking up to this NPC + """ + assert(self.notify.debug("Entering collision sphere...")) + # Tell the server + self.sendUpdate('avatarEnter', []) + + def setArea(self, playground, area): + self.playground = playground + self.area = area + + def setState(self, stateIndex, curIndex, destIndex, time, timestamp): + self.curIndex = curIndex + self.destIndex = destIndex + self.time = time + self.fsm.request(ButterflyGlobals.states[stateIndex], + [globalClockDelta.localElapsedTime(timestamp)]) + + ##### Off state ##### + + def enterOff(self, ts=0.0): + if (self.butterflyNode != None): + self.butterflyNode.reparentTo(hidden) + return None + + def exitOff(self): + if (self.butterflyNode != None): + self.butterflyNode.reparentTo(render) + return None + + ##### Flying state ##### + + def enterFlying(self, ts): + self.__detectAvatars() + curPos = ButterflyGlobals.ButterflyPoints[self.playground][self.area][self.curIndex] + destPos = ButterflyGlobals.ButterflyPoints[self.playground][self.area][self.destIndex] + # We'll hit the ground if we go straight from curPos to destPos + flyHeight = max(curPos[2], destPos[2]) + ButterflyGlobals.BUTTERFLY_HEIGHT[self.playground] + curPosHigh = Point3(curPos[0], curPos[1], flyHeight) + destPosHigh = Point3(destPos[0], destPos[1], flyHeight) + + if (ts <= self.time): + flyTime = self.time - (ButterflyGlobals.BUTTERFLY_TAKEOFF[self.playground] + ButterflyGlobals.BUTTERFLY_LANDING[self.playground]) + self.butterflyNode.setPos(curPos) + self.dropShadow.show() + self.dropShadow.setScale(self.shadowScaleBig) + oldHpr = self.butterflyNode.getHpr() + self.butterflyNode.headsUp(destPos) + newHpr = self.butterflyNode.getHpr() + self.butterflyNode.setHpr(oldHpr) + takeoffShadowT = 0.2 * ButterflyGlobals.BUTTERFLY_TAKEOFF[self.playground] + landShadowT = 0.2 * ButterflyGlobals.BUTTERFLY_LANDING[self.playground] + self.butterfly2.loop('flutter') + self.ival = Sequence( + Parallel( + LerpPosHprInterval(self.butterflyNode, + ButterflyGlobals.BUTTERFLY_TAKEOFF[self.playground], + curPosHigh, newHpr), + LerpAnimInterval(self.butterfly, + ButterflyGlobals.BUTTERFLY_TAKEOFF[self.playground], + 'land', 'flutter'), + LerpAnimInterval(self.butterfly, + ButterflyGlobals.BUTTERFLY_TAKEOFF[self.playground], + None, 'glide', + startWeight = 0, endWeight = self.glideWeight), + Sequence( + LerpScaleInterval(self.dropShadow, + takeoffShadowT, + self.shadowScaleSmall, + startScale = self.shadowScaleBig), + HideInterval(self.dropShadow) + ), + ), + LerpPosInterval(self.butterflyNode, flyTime, + destPosHigh), + Parallel( + LerpPosInterval(self.butterflyNode, + ButterflyGlobals.BUTTERFLY_LANDING[self.playground], + destPos), + LerpAnimInterval(self.butterfly, + ButterflyGlobals.BUTTERFLY_LANDING[self.playground], + 'flutter', 'land'), + LerpAnimInterval(self.butterfly, + ButterflyGlobals.BUTTERFLY_LANDING[self.playground], + None, 'glide', + startWeight = self.glideWeight, endWeight = 0), + Sequence( + Wait(ButterflyGlobals.BUTTERFLY_LANDING[self.playground] - landShadowT), + ShowInterval(self.dropShadow), + LerpScaleInterval(self.dropShadow, + landShadowT, + self.shadowScaleBig, + startScale = self.shadowScaleSmall) + ), + ), + name = self.uniqueName("Butterfly")) + self.ival.start(ts) + else: + self.ival = None + self.butterflyNode.setPos(destPos) + self.butterfly.setControlEffect('land', 1.0) + self.butterfly.setControlEffect('flutter', 0.0) + self.butterfly.setControlEffect('glide', 0.0) + self.butterfly2.loop('land') + return None + + def exitFlying(self): + self.__ignoreAvatars() + if (self.ival != None): + self.ival.finish() + self.ival = None + return None + + ##### Landed state ##### + + def enterLanded(self, ts): + self.__detectAvatars() + curPos = ButterflyGlobals.ButterflyPoints[self.playground][self.area][self.curIndex] + self.butterflyNode.setPos(curPos) + self.dropShadow.show() + self.dropShadow.setScale(self.shadowScaleBig) + self.butterfly.setControlEffect('land', 1.0) + self.butterfly.setControlEffect('flutter', 0.0) + self.butterfly.setControlEffect('glide', 0.0) + self.butterfly2.pose('land', random.randrange(self.butterfly2.getNumFrames('land'))) + return None + + def exitLanded(self): + self.__ignoreAvatars() + return None diff --git a/toontown/src/safezone/DistributedButterflyAI.py b/toontown/src/safezone/DistributedButterflyAI.py new file mode 100644 index 0000000..e0887b1 --- /dev/null +++ b/toontown/src/safezone/DistributedButterflyAI.py @@ -0,0 +1,143 @@ +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObjectAI +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +import ButterflyGlobals +import random + +class DistributedButterflyAI(DistributedObjectAI.DistributedObjectAI): + + def __init__(self, air, playground, area, ownerId): + """__init__(air, area) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.playground = playground + self.area = area + self.ownerId = ownerId + + self.fsm = ClassicFSM.ClassicFSM('DistributedButterfliesAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['Flying', 'Landed']), + State.State('Flying', + self.enterFlying, + self.exitFlying, + ['Landed']), + State.State('Landed', + self.enterLanded, + self.exitLanded, + ['Flying']),], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + (self.curPos, self.curIndex, self.destPos, self.destIndex, + self.time) = ButterflyGlobals.getFirstRoute(self.playground, + self.area, self.ownerId) + + return None + + def delete(self): + try: + self.butterfly_deleted + assert(self.notify.debug("butterfly already deleted")) + except: + self.butterfly_deleted = 1 + ButterflyGlobals.recycleIndex(self.curIndex, self.playground, + self.area, self.ownerId) + ButterflyGlobals.recycleIndex(self.destIndex, self.playground, + self.area, self.ownerId) + self.fsm.request('off') + del self.fsm + DistributedObjectAI.DistributedObjectAI.delete(self) + + # setState() + + def d_setState(self, stateIndex, curIndex, destIndex, time): + self.sendUpdate('setState', [stateIndex, curIndex, destIndex, time, + globalClockDelta.getRealNetworkTime()]) + + def getArea(self): + return [self.playground, self.area] + + def getState(self): + return [self.stateIndex, self.curIndex, + self.destIndex, self.time, + globalClockDelta.getRealNetworkTime()] + + def start(self): + self.fsm.request('Flying') + + def avatarEnter(self): + #print 'aha!' + if (self.fsm.getCurrentState().getName() == 'Landed'): + self.__ready() + return None + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### off state ##### + + def enterOff(self): + self.stateIndex = ButterflyGlobals.OFF + return None + + def exitOff(self): + return None + + ##### Flying state ##### + + def enterFlying(self): + self.stateIndex = ButterflyGlobals.FLYING + # We can let someone else fly to curPos now + ButterflyGlobals.recycleIndex(self.curIndex, self.playground, + self.area, self.ownerId) + self.d_setState(ButterflyGlobals.FLYING, self.curIndex, self.destIndex, self.time) + taskMgr.doMethodLater(self.time, self.__handleArrival, + self.uniqueName('butter-flying')) + return None + + def exitFlying(self): + taskMgr.remove(self.uniqueName('butter-flying')) + return None + + def __handleArrival(self, task): + # We're at the destination, so curPos is now destPos + self.curPos = self.destPos + self.curIndex = self.destIndex + self.fsm.request('Landed') + return Task.done + + ##### Landed state ##### + + def enterLanded(self): + self.stateIndex = ButterflyGlobals.LANDED + self.time = random.random() * ButterflyGlobals.MAX_LANDED_TIME + self.d_setState(ButterflyGlobals.LANDED, self.curIndex, self.destIndex, self.time) + taskMgr.doMethodLater(self.time, self.__ready, + self.uniqueName('butter-ready')) + return None + + def exitLanded(self): + taskMgr.remove(self.uniqueName('butter-ready')) + return None + + def __ready(self, task = None): + # Figure out where to go + (self.destPos, self.destIndex, + self.time) = ButterflyGlobals.getNextPos(self.curPos, + self.playground, self.area, self.ownerId) + self.fsm.request('Flying') + return Task.done diff --git a/toontown/src/safezone/DistributedCheckers.py b/toontown/src/safezone/DistributedCheckers.py new file mode 100644 index 0000000..971f6d0 --- /dev/null +++ b/toontown/src/safezone/DistributedCheckers.py @@ -0,0 +1,917 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer + +from direct.distributed import DistributedNode +from direct.distributed.ClockDelta import globalClockDelta +from CheckersBoard import CheckersBoard +from direct.fsm import ClassicFSM, State +from direct.fsm import StateData +#from toontown.distributed import DelayDelete + +from toontown.toonbase.ToontownTimer import ToontownTimer +from toontown.toonbase import ToontownGlobals +from direct.distributed.ClockDelta import * + +from otp.otpbase import OTPGlobals + + +from direct.showbase import PythonUtil + +class DistributedCheckers(DistributedNode.DistributedNode): + def __init__(self, cr): + NodePath.__init__(self, "DistributedCheckers") + DistributedNode.DistributedNode.__init__(self,cr) + self.cr = cr + + self.reparentTo(render) + self.boardNode = loader.loadModel("phase_6/models/golf/regular_checker_game.bam") + self.boardNode.reparentTo(self) + + self.board = CheckersBoard() + + #game variables + self.exitButton = None + self.inGame = False + self.waiting = True + self.startButton = None + self.playerNum = None + self.turnText = None + self.isMyTurn = False + self.wantTimer = True + self.leaveButton = None + self.screenText = None + self.turnText = None + self.exitButton = None + self.numRandomMoves = 0 + self.blinker = Sequence() + self.moveList = [] + self.mySquares = [] + self.myKings = [] + self.isRotated = False + + + #Mouse picking required stuff + self.accept('mouse1', self.mouseClick) + self.traverser = base.cTrav + self.pickerNode = CollisionNode('mouseRay') + self.pickerNP = camera.attachNewNode(self.pickerNode) + self.pickerNode.setFromCollideMask(ToontownGlobals.WallBitmask) + self.pickerRay = CollisionRay() + self.pickerNode.addSolid(self.pickerRay) + self.myHandler = CollisionHandlerQueue() + self.traverser.addCollider(self.pickerNP, self.myHandler) + + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find("**/InventoryButtonRollover") + + self.clockNode = ToontownTimer() + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.setScale(0.3) + self.clockNode.hide() + + #[0] GREEN [1] YELLOW [2] PURPLE [3] BLUE [4] PINK [5] RED + self.playerColors = [ Vec4(0,0,1,1), Vec4(0,1,0,1) ] + self.tintConstant = Vec4(.25,.25,.25,.5) + self.ghostConstant = Vec4(0,0,0,.8) + + #starting positions are used to check and see if a player has gone into + #his opposing players starting position, thus to tell if he won. + self.startingPositions = [[0,1,2,3,4,5,6,7,8,9,10,11], [20,21,22,23,24,25,26,27,28,29,30,31]] + + + + self.knockSound = base.loadSfx("phase_5/audio/sfx/GUI_knock_1.mp3") + self.clickSound = base.loadSfx("phase_3/audio/sfx/GUI_balloon_popup.mp3") + self.moveSound = base.loadSfx("phase_6/audio/sfx/CC_move.mp3") + self.accept('stoppedAsleep', self.handleSleep) + + + ####################### + #Fsm and State Data + # from direct.fsm import ClassicFSM,State + self.fsm = ClassicFSM.ClassicFSM('ChineseCheckers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing','gameOver']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + # Initial State + 'waitingToBegin', + # Final State + 'waitingToBegin', + ) + + ######################### + #Set up the Board Locators + ## + x = self.boardNode.find("**/locator*") + #set up the locator list so we can mess with it + self.locatorList = x.getChildren() + #tag the locators for "picking" ingame + #also add colision spheres for movement + tempList = [] + for x in range(0,32): + self.locatorList[x].setTag("GamePeiceLocator", "%d" % x) + tempList.append(self.locatorList[x].attachNewNode(CollisionNode("picker%d" % x))) + tempList[x].node().addSolid(CollisionSphere(0,0,0,.39)) + for z in self.locatorList: + y = loader.loadModel("phase_6/models/golf/regular_checker_piecewhite.bam") + y.find("**/checker_k*").hide() + zz = loader.loadModel("phase_6/models/golf/regular_checker_pieceblack.bam") + zz.find("**/checker_k*").hide() + y.reparentTo(z) + y.hide() + zz.reparentTo(z) + zz.hide() + + def setName(self, name): + self.name = name + + def announceGenerate(self): + DistributedNode.DistributedNode.announceGenerate(self) + if self.table.fsm.getCurrentState().getName() != 'observing': + if base.localAvatar.doId in self.table.tableState: # Fix for strange state #TEMP until i find the cause + self.seatPos = self.table.tableState.index(base.localAvatar.doId) + def handleSleep(self, task = None): + if self.fsm.getCurrentState().getName() == "waitingToBegin": + self.exitButtonPushed() + if task != None: + task.done + #task.done + ########## + ##setTableDoId (required broadcast ram) + # + #Upon construction, sets local pointer to the table, as well as + #sets the pointer on the table to itself. + #This is necessary to handle events that occur on the table, not + #particularly inside of any one game. + ### + def setTableDoId(self, doId): + self.tableDoId = doId + self.table = self.cr.doId2do[doId] + self.table.setTimerFunc(self.startButtonPushed) + self.fsm.enterInitialState() + self.table.setGameDoId(self.doId) + + + ######### + ##Disable/Delete + #Must be sure to remove/delete any buttons, screen text + #that may be on the screen in the event of a chosen ( or asynchrinous ) + #disable or deletion - Code redundance here is necessary + #being that "disable" is called upon server crash, and delete is + #called upon zone exit + ### + def disable(self): + DistributedNode.DistributedNode.disable(self) + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + + def delete(self): + DistributedNode.DistributedNode.delete(self) + self.table.gameDoId = None + self.table.game = None + if self.exitButton: + self.exitButton.destroy() + if self.startButton : + self.startButton.destroy() + self.clockNode.stop() + self.clockNode.hide() + self.table.startButtonPushed = None + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + self.table = None + + ########## + ##Timer Functions + #setTimer (broadcast ram required) + #setTurnTimer(broadcast ram required) + # + #setTimer() controlls the timer for game begin, which upon its timout + #calls the startButton. + # + #turnTimer() does just that + # + #Important to note that both timers run on the same clockNode. + ########## + def getTimer(self): + self.sendUpdate('requestTimer', []) + def setTimer(self, timerEnd): + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'waitingToBegin' and not self.table.fsm.getCurrentState().getName() == 'observing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(timerEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if(timeLeft > 0 and timerEnd != 0): + if timeLeft > 60: + timeLeft = 60 + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.countdown(timeLeft, self.startButtonPushed) + self.clockNode.show() + else: + self.clockNode.stop() + self.clockNode.hide() + def setTurnTimer(self, turnEnd): + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'playing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(turnEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if timeLeft > 0: + self.clockNode.setPos(-.74, 0, -0.20) + if self.isMyTurn: + self.clockNode.countdown(timeLeft, self.doNothing) + else: + self.clockNode.countdown(timeLeft, self.doNothing) + self.clockNode.show() + + + + ########### + ##Game start(broadcast) and Send Turn (broadcast ram) + # + #IMPORTANT - 255 is the Uint8 sent to the player when a game starts + #to dictate to him that a game is beginning and he is labeled as an observer + #for that game - this affects the visual queues for his player color ect. + ########## + + def gameStart(self, playerNum): + if playerNum != 255: #observer value + self.playerNum = playerNum + if self.playerNum == 1: + self.playerColorString = "white" + else: + self.playerColorString = "black" + self.playerColor = self.playerColors[playerNum-1] + self.moveCameraForGame() + self.fsm.request('playing') + def sendTurn(self,playersTurn): + if self.fsm.getCurrentState().getName() == 'playing': + if playersTurn == self.playerNum: + self.isMyTurn = True + self.enableTurnScreenText(playersTurn) + + def illegalMove(self): + self.exitButtonPushed() + + ########## + ##Move camera + # + #To make camera movement not seem weird (turning 270 degrees example) + #Must check the clients orientation between the seatPos and his current H + # so that he turns the least possible amount for the camera orientation + # + # + ########## + def moveCameraForGame(self): + if self.table.cameraBoardTrack.isPlaying(): + self.table.cameraBoardTrack.finish() + rotation = 0 + if self.seatPos >2: + if self.playerNum == 1: + rotation = 180 + elif self.playerNum == 2: + rotation = 0 + for x in self.locatorList: + x.setH(180) + self.isRotated = True + else: + if self.playerNum == 1: + rotation = 0 + elif self.playerNum == 2: + rotation = 180 + for x in self.locatorList: + x.setH(180) + self.isRotated = True + int = LerpHprInterval(self.boardNode, 4.2, Vec3(rotation, self.boardNode.getP(), self.boardNode.getR()), self.boardNode.getHpr()) + int.start() + + ##################### + #FSM Stuff + ### + def enterWaitingToBegin(self): + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableExitButton() + self.enableStartButton() + + def exitWaitingToBegin(self): + if self.exitButton: + self.exitButton.destroy() + self.exitButton = None + if self.startButton : + self.startButton.destroy() + self.exitButton = None + self.clockNode.stop() + self.clockNode.hide() + + def enterPlaying(self): + self.inGame = True + self.enableScreenText() + #print "IM IN PLAYING?????!?!?!" + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableLeaveButton() + + def exitPlaying(self): + self.inGame = False + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + self.playerNum = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + def enterGameOver(self): + pass + def exitGameOver(self): + pass + + ################################################## + # Button Functions and Text + ### + def exitWaitCountdown(self): + self.__disableCollisions() + self.ignore("trolleyExitButton") + self.clockNode.reset() + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersGetUpButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.4), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableScreenText(self): + defaultPos = (-.80, -0.40) + if self.playerNum == 1: + message = TTLocalizer.CheckersColorWhite + color = Vec4(1,1,1,1) + elif self.playerNum == 2: + message = TTLocalizer.CheckersColorBlack + color = Vec4(0,0,0,1) + else: + message = TTLocalizer.CheckersObserver + color = Vec4(0,0,0,1) + defaultPos = (-.80, -0.40) + self.screenText = OnscreenText(text = message, pos = defaultPos, scale = 0.10,fg=color,align=TextNode.ACenter,mayChange=1) + def enableStartButton(self): + self.startButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersStartButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.6, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.1), + scale = 0.15, + command = lambda self=self: self.startButtonPushed(), + ) + return + def enableLeaveButton(self): + self.leaveButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersQuitButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.13), + text_scale = 0.5, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.4), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableTurnScreenText(self,player): + playerOrder = [1,4,2,5,3,6] + message1 = TTLocalizer.CheckersIts + if(self.turnText != None): + self.turnText.destroy() + if player == self.playerNum: + message2 = TTLocalizer.ChineseCheckersYourTurn + color = (0,0,0,1) + else: + if player == 1: + message2 = TTLocalizer.CheckersWhiteTurn + color = (1,1,1,1) + elif player == 2: + message2 = TTLocalizer.CheckersBlackTurn + color = (0,0,0,1) + self.turnText = OnscreenText(text = message1+message2, pos = (-0.80,-0.50), scale = 0.092,fg=color,align=TextNode.ACenter,mayChange=1) + + #This function is called either if the player clicks on it (to begin a game) + #or if the game begin timer runs out. (timer is in sync with server so results should be + # + or - ~ 1 second + def startButtonPushed(self): + self.sendUpdate("requestBegin") + self.startButton.hide() + self.clockNode.stop() + self.clockNode.hide() + + def exitButtonPushed(self): + self.fsm.request('gameOver') + self.table.fsm.request('off') + self.clockNode.stop() + self.clockNode.hide() + + self.table.sendUpdate("requestExit") + ########## + #Mouse Picking/clicking operations + # + # + #These functions handle all of the mous clicking functions + #Its best to look through the code for comments for it to make + #the most sense. + # + #The self.blinker that is referenced in these functions, is a cosmetic + #colorLerp that gives the player a visual feedback as to what checkers peice + #he has (currently) selected. + ########## + def mouseClick(self): + messenger.send('wakeup') + if self.isMyTurn == True and self.inGame == True : #cant pick stuff if its not your turn + mpos = base.mouseWatcherNode.getMouse() + self.pickerRay.setFromLens(base.camNode,mpos.getX(), mpos.getY()) + + self.traverser.traverse(render) + if self.myHandler.getNumEntries() > 0: + self.myHandler.sortEntries()#get the closest Object + pickedObj = self.myHandler.getEntry(0).getIntoNodePath() + #will return the INT for the locator node closest parent + pickedObj = pickedObj.getNetTag("GamePeiceLocator") + if pickedObj: #make sure something actually was "picked" + self.handleClicked(int(pickedObj)) + + def handleClicked(self, index): + self.sound = Sequence( SoundInterval(self.clickSound)) #You clicked something play the click sound + #First Moved Square + if self.moveList == []: + #check if owned + if not index in self.mySquares and not index in self.myKings: + return #you clicked on nothing or an opposing peice + self.moveList.append(index) #put this on the movelist + type = self.board.squareList[index].getState() + if type == 3 or type == 4: + self.moverType = "king" + else: + self.moverType = "normal" + + #Start blinking the new "active" peice + self.blinker = Sequence() + col = self.locatorList[index].getColor() + self.blinker.append(LerpColorInterval(self.locatorList[index], .7,self.tintConstant, col)) + self.blinker.append(LerpColorInterval(self.locatorList[index], .7, col ,self.tintConstant)) + self.blinker.loop() + self.sound.start() + else: + if index in self.mySquares or index in self.myKings: #selected a new peice + for x in self.moveList: #the nodes from + self.locatorList[x].setColor(1,1,1,1) + self.locatorList[x].hide() + self.blinker.finish() + self.blinker = Sequence() + col = self.locatorList[index].getColor() + self.blinker.append(LerpColorInterval(self.locatorList[index], .7,self.tintConstant, col)) + self.blinker.append(LerpColorInterval(self.locatorList[index], .7, col ,self.tintConstant)) + self.blinker.loop() + self.sound.start() + #Swap back to the original peice + #set the original node back to playercolor + #self.locatorList[self.moveList[0]].setColor(self.playerColor) + self.locatorList[self.moveList[0]].show() + self.moveList = [] + self.moveList.append(index) + type = self.board.squareList[index].getState() + if type == 3 or type == 4: + self.moverType = "king" + else: + self.moverType = "normal" + else: #item is either BLANK or the opposing players (checkJump will validate if it is opposing players) + self.currentMove = index + lastItem = self.board.squareList[self.moveList[len(self.moveList)-1]] + thisItem = self.board.squareList[index] + if self.mustJump == True: + if lastItem.getNum() == index: #selected the same item twice + self.blinker.finish() + self.d_requestMove(self.moveList) + self.isMyTurn = False + self.moveList= [] + return + #print "CHECK LEGAL JUMP", self.checkLegalJump(lastItem, thisItem, self.moverType), "TYPE == ", self.moverType + if self.checkLegalJump(lastItem, thisItem, self.moverType) == True: #There is a Legal Jump + #ghostConstant here is a small alpha offset to give it a transparent look + #tintConstant makes the peice a bit darker (necessary when ghosting) + col = self.locatorList[index].getColor() + self.locatorList[index].show() + self.sound.start() + if self.existsLegalJumpsFrom(index, self.moverType) == False: #No more Series of jumps => Commit the move + self.moveList.append(index) + self.blinker.finish() + self.d_requestMove(self.moveList) + self.moveList= [] + self.isMyTurn = False + else: + self.moveList.append(index) + if self.playerColorString == "white": + x = self.locatorList[index].getChildren()[1] + x.show() + else: + x = self.locatorList[index].getChildren()[2] + x.show() + if self.moverType == "king": + x.find("**/checker_k*").show() + #self.locatorList[index].ls() + self.locatorList[index].setColor(Vec4(.5,.5,.5,.5)) + else: #must do an adjacent move! + if self.checkLegalMove(lastItem, thisItem, self.moverType) == True: #there exists a legal move + self.moveList.append(index) + #ghostConstant here is a small alpha offset to give it a transparent look + #tintConstant makes the peice a bit darker (necessary when ghosting) + col = self.locatorList[index].getColor() + #self.locatorList[index].setColor(col - self.tintConstant - self.ghostConstant) + self.locatorList[index].show() + self.sound.start() + self.blinker.finish() + self.d_requestMove(self.moveList) + self.moveList= [] + self.isMyTurn = False + + + def existsLegalJumpsFrom(self,index, peice): + if peice == "king": + for x in range(4): + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: # Off the board + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + elif jump.getState() == 0: #peice is owned by other dude and its a legal jump + if not index in self.moveList and not jump.getNum() in self.moveList: + return True + else: + pass + return False + elif peice == "normal": + if self.playerNum == 1: + moveForward = [1,2] + elif self.playerNum == 2: + moveForward = [0,3] + for x in moveForward: + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + else: + if jump.getState() == 0: + if not index in self.moveList: + return True + return False + + def existsLegalMovesFrom(self,index, peice): + if peice == "king": + for x in self.board.squareList[index].getAdjacent(): + if x != None: + if self.board.squareList[x].getState() == 0: + return True + return False + elif peice == "normal": + if self.playerNum == 1: + moveForward = [1,2] + elif self.playerNum == 2: + moveForward = [0,3] + for x in moveForward: + if self.board.squareList[index].getAdjacent()[x] != None: + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + if adj.getState() == 0: + return True + return False + + def checkLegalMove(self, firstSquare, secondSquare, peice): + if (not firstSquare.getNum() in self.mySquares) and (not firstSquare.getNum() in self.myKings): + return False + if self.playerNum == 1: + moveForward = [1,2] + else: #self.playerNum == 2: + moveForward = [0,3] + if peice == "king": + for x in range(4): + if firstSquare.getAdjacent()[x] != None: + if self.board.squareList[firstSquare.getAdjacent()[x]].getState() == 0 and secondSquare.getNum() in firstSquare.getAdjacent(): + return True + return False + elif peice == "normal": + for x in moveForward: + if firstSquare.getAdjacent()[x] != None and secondSquare.getNum() in firstSquare.getAdjacent(): + if self.board.squareList[firstSquare.getAdjacent()[x]].getState() == 0 and firstSquare.getAdjacent().index(secondSquare.getNum()) == x: + return True + return False + def checkLegalJump(self, firstSquare, secondSquare, peice): + if (not firstSquare.getNum() in self.mySquares) and (not firstSquare.getNum() in self.myKings) and len(self.moveList) == 1: + return False + if self.playerNum == 1: + moveForward = [1,2] + opposingPeices = [2,4] + else: #self.playerNum == 2: + moveForward = [0,3] + opposingPeices = [1,3] + if peice == "king": + if secondSquare.getNum() in firstSquare.getJumps(): + index = firstSquare.getJumps().index(secondSquare.getNum()) + if self.board.squareList[firstSquare.getAdjacent()[index]].getState() in opposingPeices: + return True + else: + return False + elif peice == "normal": + if secondSquare.getNum() in firstSquare.getJumps(): + index = firstSquare.getJumps().index(secondSquare.getNum()) + if index in moveForward: + if self.board.squareList[firstSquare.getAdjacent()[index]].getState() in opposingPeices: #is the peice jumping over yours or his + return True + else: + return False + else: + return False + else: + return False + def d_requestMove(self, moveList): + self.sendUpdate('requestMove', [moveList]) + + ########## + ##setGameState (required broadcast ram) + # + #This is the function that handles the moves made after a move is parsed by the AI + #and deemed to be valid + # + #If moveList is the empty List, then the player is asynchronously joining the game, (OBSERVING) + #and just wants the board state. + # + #otherwise there is a series of jumps (or jump) that needs to be parsed and animated, then + #consequently setting the board state + # + #after every setGameState, the client checks to see the current + #Status of his peices (checkForWin), if he belives he won, he requests it to the server + ######### + def setGameState(self, tableState, moveList): + if moveList != []: + if self.board.squareList[moveList[0]].getState() == 1 or self.board.squareList[moveList[0]].getState() == 3: + playerColor = "white" + else: + playerColor = "black" + if self.board.squareList[moveList[0]].getState() <= 2: + self.animatePeice(tableState, moveList, "normal", playerColor) + else: + self.animatePeice(tableState, moveList, "king", playerColor) + else: + self.updateGameState(tableState) + def updateGameState(self, squares): + self.board.setStates(squares) #set the board state + self.mySquares = [] + self.myKings = [] + messenger.send('wakeup') + + #Because i designed this poorly, Must do a temporary playerNum + #for observers, and set it back to None when i am done. + isObserve = False + if self.playerNum == None: + self.playerNum = 1 + self.playerColorString = "white" + isObserve = True + + + #Need to traverse the list to do the showing of peices ect + for xx in range(32): + #self.hideChildren(self.locatorList[xx].getChildren()) # Hide all off the pieces + for blah in self.locatorList[xx].getChildren(): + blah.hide() + if self.locatorList[xx].getChildren().index(blah) != 0: + blah1 = blah.find("**/checker_k*") + owner = self.board.squareList[xx].getState() #Get the State of that square + #print owner + # This means it is MY Peice + if owner == self.playerNum: + #need to see what color i am + if self.playerColorString == "white": + x = self.locatorList[xx].getChildren()[1] # white + x.show() + x.find("**/checker_k*").hide() + else: + x = self.locatorList[xx].getChildren()[2] # black + x.show() + x.find("**/checker_k*").hide() + + self.mySquares.append(xx) + ####################### + elif owner == 0: # blank Tile + self.hideChildren(self.locatorList[xx].getChildren()) # Hide all off the pieces + ####################### + elif owner == self.playerNum + 2: #its MY peice but its a KING + if self.playerColorString == "white": + x = self.locatorList[xx].getChildren()[1] # white + x.show() + x.find("**/checker_k*").show() + else: + x = self.locatorList[xx].getChildren()[2] # black + x.show() + x.find("**/checker_k*").show() + self.myKings.append(xx) + ######################## + else: # opposing player peice + if owner <= 2: #Opposing player Non king + if self.playerColorString == "white": + #we are white so show the black piece + x = self.locatorList[xx].getChildren()[2] + x.show() + x.find("**/checker_k*").hide() + else: + x = self.locatorList[xx].getChildren()[1] + x.show() + x.find("**/checker_k*").hide() + else: #the peice is an opposing players KING + if self.playerColorString == "white": + x = self.locatorList[xx].getChildren()[2] + x.show() + x.find("**/checker_k*").show() + else: + x = self.locatorList[xx].getChildren()[1] + x.show() + x.find("**/checker_k*").show() + ########################### + + if isObserve == True: + self.playerNum = None + self.playerColorString = None + return + + ###### + #A player MUST move if he has a legal jump + #This will flag the bool making him do so + ###### + self.mustJump = False + self.hasNormalMoves = False + for x in self.myKings: + if self.existsLegalJumpsFrom(x, "king") == True: + self.mustJump = True + break + else: + self.mustJump = False + if self.mustJump == False: + for x in self.mySquares: + if self.existsLegalJumpsFrom(x, "normal") == True: + self.mustJump = True + break + else: + self.mustJump = False + ############# + #if i dont have any jumps, Must check to see if i have any legal moves + #If i dont, then the game is over. + ### + if self. mustJump != True: #Check for legal moves and possibly LOSS + for x in self.mySquares: + if self.existsLegalMovesFrom(x, "normal") == True: + self.hasNormalMoves = True + break + else: + self.hasNormalMoves = False + if self.hasNormalMoves == False: #make sure to not overRide a True + for x in self.myKings: + if self.existsLegalMovesFrom(x, "king") == True: + self.hasNormalMoves = True + break + else: + self.hasNormalMoves = False + if self.mustJump == False and self.hasNormalMoves == False: + pass #Server is calculating everything + + def hideChildren(self, nodeList): + for x in range (1,2): + nodeList[x].hide() + + def animatePeice(self, tableState, moveList,type, playerColor): + messenger.send('wakeup') + ###for x in self.locatorList: + #x.show() + if playerColor == "white": + gamePeiceForAnimation = loader.loadModel("phase_6/models/golf/regular_checker_piecewhite.bam") + else: + gamePeiceForAnimation = loader.loadModel("phase_6/models/golf/regular_checker_pieceblack.bam") + if type == "king": + gamePeiceForAnimation.find("**/checker_k*").show() + else: + gamePeiceForAnimation.find("**/checker_k*").hide() + + + gamePeiceForAnimation.reparentTo(self.boardNode) + gamePeiceForAnimation.setPos(self.locatorList[moveList[0]].getPos()) + if self.isRotated == True: + gamePeiceForAnimation.setH(180) + + #self.hideChildren(self.locatorList[moveList[0]].getChildren()) + #self.locatorList[moveList[0]].hide() + for x in self.locatorList[moveList[0]].getChildren(): + x.hide() + + checkersPeiceTrack = Sequence() + length = len(moveList) + for x in range(length - 1): + checkersPeiceTrack.append(Parallel( SoundInterval(self.moveSound), + ProjectileInterval(gamePeiceForAnimation, + endPos = self.locatorList[moveList[x+1]].getPos(), + duration = .5 ))) + checkersPeiceTrack.append(Func(gamePeiceForAnimation.removeNode)) + checkersPeiceTrack.append(Func(self.updateGameState, tableState)) + checkersPeiceTrack.append(Func(self.unAlpha, moveList)) + + checkersPeiceTrack.start() + def announceWin(self, avId): + self.fsm.request('gameOver') + + def unAlpha(self, moveList): + for x in moveList: + self.locatorList[x].setColorOff() + ########### + ##doRandomMove + # + #If a clients move Timer runs out, it will calculate a random move for him + #after 3 random moves, it kicks the client out of the game by pushing the + #"get up" button for him. + ########### + def doRandomMove(self): + import random + move = [] + foundLegal = False + self.blinker.pause() + self.numRandomMoves += 1 + while not foundLegal: + x = random.randint(0,9) + for y in self.board.getAdjacent(self.mySquares[x]): + if y != None and self.board.getState(y) == 0: + move.append(self.mySquares[x]) + move.append(y) + foundLegal = True + break + + if move == []: + pass + #current flaw in the logic, but shouldnt really ever happen + #though on live it might + #print "random move is empty" + playSound = Sequence(SoundInterval(self.knockSound)) + playSound.start() + self.d_requestMove(move) + self.moveList=[] + self.isMyTurn = False + if(self.numRandomMoves >= 5): + self.exitButtonPushed() + + + + def doNothing(self): + pass + + + + diff --git a/toontown/src/safezone/DistributedCheckersAI.py b/toontown/src/safezone/DistributedCheckersAI.py new file mode 100644 index 0000000..eecf084 --- /dev/null +++ b/toontown/src/safezone/DistributedCheckersAI.py @@ -0,0 +1,576 @@ +from direct.distributed.DistributedNodeAI import DistributedNodeAI +from direct.distributed.ClockDelta import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from direct.distributed.ClockDelta import * +from toontown.safezone import CheckersBoard + + +class DistributedCheckersAI(DistributedNodeAI): + def __init__(self, air, parent, name, x, y, z, h, p, r): + DistributedNodeAI.__init__(self,air) + self.name = name + self.air = air + + self.setPos(x,y,z) + self.setHpr(h,p,r) + + self.myPos = (x,y,z) + self.myHpr = (h,p,r) + self.board = CheckersBoard.CheckersBoard() + + self.parent = self.air.doId2do[parent] + self.parentDo = parent + self.wantStart = [] + self.playersPlaying = [] + self.playersSitting = 0 + self.playersTurn = 1 + self.movesMade = 0 + self.playerNum = 1 + self.hasWon = False + + #players game positions (separate from where they are sitting down + self.playersGamePos = [None, None] + + self.wantTimer = True + self.timerEnd = 0 + self.turnEnd = 0 + + self.playersObserving = [] + #Arbitrary numbers - can Adjust to feel + self.winLaffPoints = 20 + self.movesRequiredToWin = 10 + + self.zoneId = self.air.allocateZone() + self.generateOtpObject(air.districtId, self.zoneId, optionalFields = ["setX","setY","setZ", "setH", "setP", "setR"]) + #sets the ZoneId in the parent table so when people sit down they know what zone to add + #interest to when they sit down + self.parent.setCheckersZoneId(self.zoneId) + + #parent.b_setCurrentGameId(self.doId) + + #Starting indexes inside of board for setting starting positions + self.startingPositions = [[0,1,2,3,4,5,6,7,8,9,10,11],[20,21,22,23,24,25,26,27,28,29,30,31]] + self.kingPositions = [[31,30,29,28],[0,1,2,3]] + self.timerStart = None + + + self.fsm = ClassicFSM.ClassicFSM('Checkers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + #start state + 'waitingToBegin', + #final state + 'waitingToBegin', + ) + + + + self.fsm.enterInitialState() + #self.setGameCountownTime() + + def announceGenerate(self): + self.parent.setGameDoId(self.doId) + def getTableDoId(self): + #print "PARENT -- ", self.parent + return self.parentDo + def delete(self): + self.fsm.requestFinalState() + self.board.delete() + del self.fsm + DistributedNodeAI.delete(self) + + + ######################################################################### + ##Inform of player and leave + # + #These functions are called by the table in the event of a player leave/join + #this is necessary so that the game may have an accurate count of who is in the table + #as well as a function that will fire upon single "deltas" of the table state + # + #Table state with avId(s) will be updated via setTableState as well as this + #function being called + #### + def informGameOfPlayer(self): + self.playersSitting += 1 + if self.playersSitting < 2: + self.timerEnd = 0 + elif self.playersSitting == 2: + self.timerEnd = globalClock.getRealTime() + 20 + self.parent.isAccepting = False + self.parent.sendUpdate("setIsPlaying", [1]) # 1 is true + elif self.playersSitting > 2: + pass + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + + def informGameOfPlayerLeave(self): + self.playersSitting -= 1 + if self.playersSitting < 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + self.timerEnd = 0 + self.parent.isAccepting = True + self.parent.sendUpdate("setIsPlaying", [0]) # 1 is true + if self.playersSitting > 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + pass + else: + self.timerEnd = 0 + if self.timerEnd != 0: + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + else: + self.sendUpdate("setTimer", [0]) + ########################################################################### + ##Timer Functions for game + # + #These timer functions encapsulate the "timer" functions needed for the game table. + # getTimer - is the timer for sitting down and beginning the game + # getTurnTimer - gets and sets the turn timer for players moves + ### + def setGameCountdownTime(self): + self.timerEnd = globalClock.getRealTime() + 10 + def setTurnCountdownTime(self): + self.turnEnd = globalClock.getRealTime() + 40 + def getTimer(self): + if self.timerEnd != 0: + return 0 + else: + return 0 + def getTurnTimer(self): + return globalClockDelta.localToNetworkTime(self.turnEnd) + def requestTimer(self): + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, "setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + ########################################################################### + ##handlePlayerExit and handleEmptyGame + # + #handlePlayerExit is the mother function that gets called when a player requests to leave an "active" game + #of chinese checkers - there are many special cases here, comments inside the function itself. + ### + + def handlePlayerExit(self, avId): + if avId in self.wantStart: + self.wantStart.remove(avId) + if self.fsm.getCurrentState().getName() == 'playing': + #Get the players Current "player" position in Chinese Checkers + gamePos = self.playersGamePos.index(avId) + self.playersGamePos[gamePos] = None + self.fsm.request('gameOver') + + def handleEmptyGame(self): + self.movesMade = 0 + #elf.playersPlaying = [] + self.playersTurn = 1 + self.playerNum = 1 + self.fsm.request('waitingToBegin') + self.parent.isAccepting = True + ############################################################### + ##Request win and distributed Laugh points + # + #This function explicitly tests for a player (that requested he won) + #and checks to see if his peices equal that of his opposite + ## + def requestWin(self): + avId = self.air.getAvatarIdFromSender() + + def distributeLaffPoints(self): + for x in self.parent.seats: + if x != None: + av = self.air.doId2do.get(x) + av.toonUp(self.winLaffPoints) + + ################################################################## + # Chinese Checkers FSM + # + #The FSM is primarily to keep track of buttons/screen text, but also + #serves the purpose of resetting variables upon game over ect. + ### + def enterWaitingToBegin(self): + self.setGameCountdownTime() + self.parent.isAccepting = True + #self.clearBoard() + pass + def exitWaitingToBegin(self): + self.turnEnd = 0 + def enterPlaying(self): + self.parent.isAccepting = False + for x in self.playersGamePos: + if x != None: + self.playersTurn = self.playersGamePos.index(x) + self.d_sendTurn(self.playersTurn+1) + break + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + + def exitPlaying(self): + pass + def enterGameOver(self): + self.timerEnd = 0 + isAccepting = True + self.parent.handleGameOver() + self.playersObserving = [] + self.playersTurn = 1 + self.playerNum = 1 + #elf.playersPlaying = [] + self.clearBoard() + self.sendGameState( [] ) + self.movesMade = 0 + self.playersGamePos = [None, None, None, None, None, None] + self.parent.isAccepting = True + self.fsm.request('waitingToBegin') + def exitGameOver(self): + pass + + ################################################################## + # Game Start/begin logic + # + #Poor style with setting the player starting Positions I know, But + #is necessary for how the game table must look if odd numbers of people + #are playing IE (* = blank ) + # 3 4 5 + # . * . * . . . . . + # + # * . * . . * . . * + # + # + #Request begin is sent by every avatar when either (1) they click the go button + #or when their start timer runs out + ### + def requestBegin(self): + avId = self.air.getAvatarIdFromSender() + if not( avId in self.wantStart ): + self.wantStart.append(avId) + numPlayers = 0 + for x in self.parent.seats: + if x != None: + numPlayers = numPlayers + 1 + if len(self.wantStart) == numPlayers and numPlayers >= 2: + self.d_gameStart(avId) + self.parent.sendIsPlaying() + def d_gameStart(self,avId): + #255 is a special value just telling the client that he is an Observer + for x in self.playersObserving: + self.sendUpdateToAvatarId(x, "gameStart", [255]) + ### + + + zz = 0 + numPlayers = 0 + for x in self.parent.seats: + if x != None: + numPlayers += 1 + self.playersPlaying.append(x) + if numPlayers == 2: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + for x in self.startingPositions[0]: + self.board.setState(x, 1) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [2]) + self.playersGamePos[1] = player2 + for x in self.startingPositions[1]: + self.board.setState(x,2) + + self.sendGameState( [] ) + self.wantStart = [] + self.fsm.request('playing') + self.parent.getTableState() + def d_sendTurn(self,playersTurn): + self.sendUpdate("sendTurn", [playersTurn]) + + ################################################################## + # Legal Move Request/Checker + #### + def advancePlayerTurn(self): + #print "ADVANCING PLAYERS TURN -- " + if self.playersTurn == 0: + self.playersTurn = 1 + self.playerNum = 2 + else: + self.playerNum = 1 + self.playersTurn = 0 + + + def requestMove(self, moveList): + #Check if its a legal move first + if(self.checkLegalMoves(moveList) == True): + self.makeMove(moveList) + self.advancePlayerTurn() + self.d_sendTurn(self.playersTurn + 1) + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + else: + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, "illegalMove", []) + self.air.writeServerEvent('suspicious', avId, 'has requested an illegal move in Regular checkers - not possible') + #broadcast to him that its not legal, probably kick him out of + #game because hes likely cheating + def checkLegalMoves(self, moveList): + #check logic .. Really we are assuming client makes a legal move + #but if he doesnt, he is likely trying to cheat + #print moveList, "MOVELIST" + if self.board.squareList[moveList[0]].getState() >= 3: # Meaning its a king + moveType = "king" + else: + moveType = "normal" + + if len(moveList) == 2: #adjacent move + firstSquare = self.board.squareList[moveList[0]] + secondSquare = self.board.squareList[moveList[1]] + if self.checkLegalMove(firstSquare, secondSquare, moveType) == True: + return True + else: + for x in range(len(moveList)-1): + y = self.checkLegalJump(self.board.getSquare(moveList[x]), self.board.getSquare(moveList[x+1]), moveType) + if y == False: + return False + else: + return True + + return False #ILLEGAL MOVE KICK HIM OFF TABLE! + elif len(moveList) > 2: # Jump Move + for x in range(len(moveList)-1): + y = self.checkLegalJump(self.board.getSquare(moveList[x]), self.board.getSquare(moveList[x+1]), moveType) + if y == False: + return False + return True + + def makeMove(self, moveList): + for x in range(len(moveList)-1): + firstSquare = self.board.squareList[moveList[x]] + secondSquare = self.board.squareList[moveList[x+1]] + if firstSquare.getNum() in secondSquare.getAdjacent(): + break + index = firstSquare.jumps.index(secondSquare.getNum()) + self.board.squareList[firstSquare.getAdjacent()[index]].setState(0) + + haveMoved = False + squareState = self.board.squareList[moveList[0]].getState() + if squareState <= 2: + piecetype = "normal" + if squareState == 1: + playerNum = 1 + else: + playerNum = 2 + else: + piecetype = "king" + if squareState == 3: + playerNum = 1 + else: + playerNum = 2 + if piecetype == "normal": + lastElement = moveList[len(moveList)-1] + if playerNum ==1: + if lastElement in self.kingPositions[0]: + self.board.squareList[moveList[0]].setState(0) + self.board.squareList[lastElement].setState(3) + haveMoved = True + self.sendGameState( moveList) + else: + if lastElement in self.kingPositions[1]: + self.board.squareList[moveList[0]].setState(0) + self.board.squareList[lastElement].setState(4) + haveMoved = True + self.sendGameState( moveList) + + if haveMoved == False: + spot1 = self.board.squareList[moveList[0]].getState() + self.board.squareList[moveList[0]].setState(0) + self.board.squareList[moveList[len(moveList)-1]].setState(spot1) + self.sendGameState( moveList) + + temp = self.playerNum + self.playerNum = 1 + if self.hasWon == True: + return + if self.hasPeicesAndMoves(1,3) == False: + self.parent.announceWinner("Checkers", self.playersPlaying[1]) + self.fsm.request("gameOver") + self.hasWon = True + return + self.playerNum = temp + temp = self.playerNum + self.playerNum = 2 + if self.hasPeicesAndMoves(2,4) == False: + self.parent.announceWinner("Checkers", self.playersPlaying[0]) + self.fsm.request("gameOver") + self.hasWon = True + return + self.playerNum = temp + +################### + def hasPeicesAndMoves(self, normalNum, kingNum): + for x in self.board.squareList: + if x.getState() == normalNum: + if self.existsLegalMovesFrom(x.getNum(), "normal") == True: + return True + if self.existsLegalJumpsFrom(x.getNum(), "normal") == True: + return True + elif x.getState() == kingNum: + if self.existsLegalMovesFrom(x.getNum(), "king") == True: + return True + if self.existsLegalJumpsFrom(x.getNum(), "king") == True: + return True + return False + + def getState(self): + return self.fsm.getCurrentState().getName() + + def getName(self): + return self.name + + + def getGameState(self): + return [self.board.getStates(), [] ] + + def sendGameState(self, moveList): + gameState = self.board.getStates() + self.sendUpdate('setGameState', [gameState, moveList]) + + + + def clearBoard(self): + for x in self.board.squareList: + x.setState(0) + + def getPosHpr(self): + return self.posHpr + + + def existsLegalJumpsFrom(self,index, peice): + if peice == "king": + for x in range(4): + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: # Off the board + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + elif jump.getState() == 0: #peice is owned by other dude and its a legal jump + return True + else: + pass + return False + elif peice == "normal": + if self.playerNum == 1: + moveForward = [1,2] + elif self.playerNum == 2: + moveForward = [0,3] + for x in moveForward: + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + else: + if jump.getState() == 0: + return True + return False + + def existsLegalMovesFrom(self,index, peice): + if peice == "king": + for x in self.board.squareList[index].getAdjacent(): + if x != None: + if self.board.squareList[x].getState() == 0: + return True + return False + elif peice == "normal": + if self.playerNum == 1: + moveForward = [1,2] + elif self.playerNum == 2: + moveForward = [0,3] + for x in moveForward: + if self.board.squareList[index].getAdjacent()[x] != None: + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + if adj.getState() == 0: + return True + return False + def existsLegalJumpsFrom(self,index, peice): + if peice == "king": + for x in range(4): + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: # Off the board + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + else: #peice is owned by other dude and its a legal jump + if jump.getState() == 0: + return True + return False + elif peice == "normal": + if self.playerNum == 1: + moveForward = [1,2] + elif self.playerNum == 2: + moveForward = [0,3] + for x in moveForward: + if self.board.squareList[index].getAdjacent()[x] != None and self.board.squareList[index].getJumps()[x] != None: + adj = self.board.squareList[self.board.squareList[index].getAdjacent()[x]] + jump = self.board.squareList[self.board.squareList[index].getJumps()[x]] + if adj.getState() == 0: + pass + elif adj.getState() == self.playerNum or adj.getState() == self.playerNum+2: + pass + else: + if jump.getState() == 0: + return True + return False + + + def checkLegalMove(self, firstSquare, secondSquare, peice): + if self.playerNum == 1: + moveForward = [1,2] + else: #self.playerNum == 2: + moveForward = [0,3] + if peice == "king": + for x in range(4): + if firstSquare.getAdjacent()[x] != None: + if self.board.squareList[firstSquare.getAdjacent()[x]].getState() == 0: + return True + return False + elif peice == "normal": + for x in moveForward: + if firstSquare.getAdjacent()[x] != None: + if self.board.squareList[firstSquare.getAdjacent()[x]].getState() == 0: + return True + return False + def checkLegalJump(self, firstSquare, secondSquare, peice): + if self.playerNum == 1: + moveForward = [1,2] + opposingPeices = [2,4] + else: #self.playerNum == 2: + moveForward = [0,3] + opposingPeices = [1,3] + if peice == "king": + if secondSquare.getNum() in firstSquare.getJumps(): + index = firstSquare.getJumps().index(secondSquare.getNum()) + if self.board.squareList[firstSquare.getAdjacent()[index]].getState() in opposingPeices: + return True + else: + return False + elif peice == "normal": + if secondSquare.getNum() in firstSquare.getJumps(): + index = firstSquare.getJumps().index(secondSquare.getNum()) + if index in moveForward: + if self.board.squareList[firstSquare.getAdjacent()[index]].getState() in opposingPeices: #is the peice jumping over yours or his + return True + else: + return False + else: + return False + else: + return False diff --git a/toontown/src/safezone/DistributedChineseCheckers.py b/toontown/src/safezone/DistributedChineseCheckers.py new file mode 100644 index 0000000..0f4ad39 --- /dev/null +++ b/toontown/src/safezone/DistributedChineseCheckers.py @@ -0,0 +1,955 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer + +from direct.distributed import DistributedNode +from direct.distributed.ClockDelta import globalClockDelta +from ChineseCheckersBoard import ChineseCheckersBoard +from direct.fsm import ClassicFSM, State +from direct.fsm import StateData +from toontown.distributed import DelayDelete + +from toontown.toonbase.ToontownTimer import ToontownTimer +from toontown.toonbase import ToontownGlobals +from direct.distributed.ClockDelta import * + +from otp.otpbase import OTPGlobals + + +from direct.showbase import PythonUtil + +class DistributedChineseCheckers(DistributedNode.DistributedNode): + def __init__(self, cr): + NodePath.__init__(self, "DistributedChineseCheckers") + DistributedNode.DistributedNode.__init__(self,cr) + self.cr = cr + + self.reparentTo(render) + self.boardNode = loader.loadModel("phase_6/models/golf/checker_game.bam") + self.boardNode.reparentTo(self) + #self.boardNode.setZ(2.85) + #self.boardNode.setZ(3.5) + #self.boardNode.setZ(0.3) + #self.boardNode.setZ(self.getZ()) + + self.board = ChineseCheckersBoard() + + self.playerTags = render.attachNewNode("playerTags") + self.playerTagList = [] + + #game variables + self.exitButton = None + self.inGame = False + self.waiting = True + self.startButton = None + self.playerNum = None + self.turnText = None + self.isMyTurn = False + self.wantTimer = True + self.leaveButton = None + self.screenText = None + self.turnText = None + self.exitButton = None + self.numRandomMoves = 0 + self.blinker = Sequence() + self.playersTurnBlinker = Sequence() + self.yourTurnBlinker = Sequence() + self.moveList = [] + self.mySquares = [] + self.playerSeats = None + ###self.playerTags = [None, None, None, None, None, None + + + #Mouse picking required stuff + self.accept('mouse1', self.mouseClick) + self.traverser = base.cTrav + self.pickerNode = CollisionNode('mouseRay') + self.pickerNP = camera.attachNewNode(self.pickerNode) + self.pickerNode.setFromCollideMask(ToontownGlobals.WallBitmask) + self.pickerRay = CollisionRay() + self.pickerNode.addSolid(self.pickerRay) + self.myHandler = CollisionHandlerQueue() + self.traverser.addCollider(self.pickerNP, self.myHandler) + + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find("**/InventoryButtonRollover") + + self.clockNode = ToontownTimer() + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.setScale(0.3) + self.clockNode.hide() + + #[0] GREEN [1] YELLOW [2] PURPLE [3] BLUE [4] PINK [5] RED + self.playerColors = [ Vec4(0,.90,0,1), Vec4(.9,.9,0,1), Vec4(.45,0,.45,1), Vec4(.2,.4,.8,1), Vec4(1,.45,1,1), Vec4(.8,0,0,1) ] + self.tintConstant = Vec4(.25,.25,.25,0) + self.ghostConstant = Vec4(0,0,0,.5) + + #starting positions are used to check and see if a player has gone into + #his opposing players starting position, thus to tell if he won. + self.startingPositions = [[0,1,2,3,4,5,6,7,8,9], + [10,11,12,13,23,24,25,35,36,46], + [65,75,76,86,87,88,98,99,100,101], + [111,112,113,114,115,116,117,118,119,120], + [74,84,85,95,96,97,107,108,109,110], + [19,20,21,22,32,33,34,44,45,55]] + self.nonOpposingPositions = [] + + + self.knockSound = base.loadSfx("phase_5/audio/sfx/GUI_knock_1.mp3") + self.clickSound = base.loadSfx("phase_3/audio/sfx/GUI_balloon_popup.mp3") + self.moveSound = base.loadSfx("phase_6/audio/sfx/CC_move.mp3") + self.accept('stoppedAsleep', self.handleSleep) + + #base.setCellsAvailable(base.leftCells + + #[base.bottomCells[0]], 0) + + #base.setCellsAvailable(base.bottomCells,0) + + + ####################### + #Fsm and State Data + from direct.fsm import ClassicFSM,State + self.fsm = ClassicFSM.ClassicFSM('ChineseCheckers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing','gameOver']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + # Initial State + 'waitingToBegin', + # Final State + 'waitingToBegin', + ) + + ######################### + #Set up the Board Locators + ## + x = self.boardNode.find("**/locators") + #set up the locator list so we can mess with it + self.locatorList = x.getChildren() + #tag the locators for "picking" ingame + #also add colision spheres for movement + tempList = [] + for x in range(0,121): + self.locatorList[x].setTag("GamePeiceLocator", "%d" % x) + tempList.append(self.locatorList[x].attachNewNode(CollisionNode("picker%d" % x))) + tempList[x].node().addSolid(CollisionSphere(0,0,0,.115)) + for z in self.locatorList: + y = loader.loadModel("phase_6/models/golf/checker_marble.bam") + z.setColor(0,0,0,0) + y.reparentTo(z) + #y.show() + #y.hide() + + def setName(self, name): + self.name = name + + def announceGenerate(self): + DistributedNode.DistributedNode.announceGenerate(self) + if self.table.fsm.getCurrentState().getName() != 'observing': + if base.localAvatar.doId in self.table.tableState: # Fix for strange state #TEMP until i find the cause + self.seatPos = self.table.tableState.index(base.localAvatar.doId) + + self.playerTags.setPos(self.getPos()) + def handleSleep(self, task = None): + if self.fsm.getCurrentState().getName() == "waitingToBegin": + self.exitButtonPushed() + if task != None: + task.done + #task.done + ########## + ##setTableDoId (required broadcast ram) + # + #Upon construction, sets local pointer to the table, as well as + #sets the pointer on the table to itself. + #This is necessary to handle events that occur on the table, not + #particularly inside of any one game. + ### + def setTableDoId(self, doId): + self.tableDoId = doId + self.table = self.cr.doId2do[doId] + self.table.setTimerFunc(self.startButtonPushed) + self.fsm.enterInitialState() + self.table.setGameDoId(self.doId) + #self.table.tempCheckers.hide() + #self.boardNode.setP(self.table.getP()) + + ######### + ##Disable/Delete + #Must be sure to remove/delete any buttons, screen text + #that may be on the screen in the event of a chosen ( or asynchrinous ) + #disable or deletion - Code redundance here is necessary + #being that "disable" is called upon server crash, and delete is + #called upon zone exit + ### + def disable(self): + DistributedNode.DistributedNode.disable(self) + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + self.cleanPlayerTags() + ###self.table = None + + def delete(self): + DistributedNode.DistributedNode.delete(self) + self.table.gameDoId = None + self.table.game = None + if self.exitButton: + self.exitButton.destroy() + if self.startButton : + self.startButton.destroy() + self.clockNode.stop() + self.clockNode.hide() + self.table.startButtonPushed = None + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + self.table = None + self.cleanPlayerTags() + del self.playerTags + del self.playerTagList + self.playerSeats = None + self.yourTurnBlinker.finish() + + ########## + ##Timer Functions + #setTimer (broadcast ram required) + #setTurnTimer(broadcast ram required) + # + #setTimer() controlls the timer for game begin, which upon its timout + #calls the startButton. + # + #turnTimer() does just that + # + #Important to note that both timers run on the same clockNode. + ########## + def getTimer(self): + self.sendUpdate('requestTimer', []) + def setTimer(self, timerEnd): + #print "TIMEREND! ", timerEnd + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'waitingToBegin' and not self.table.fsm.getCurrentState().getName() == 'observing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(timerEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if(timeLeft > 0 and timerEnd != 0): + if timeLeft > 60: + timeLeft = 60 + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.countdown(timeLeft, self.startButtonPushed) + self.clockNode.show() + else: + self.clockNode.stop() + self.clockNode.hide() + def setTurnTimer(self, turnEnd): + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'playing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(turnEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if timeLeft > 0: + self.clockNode.setPos(-.74, 0, -0.20) + if self.isMyTurn: + self.clockNode.countdown(timeLeft, self.doRandomMove) + else: + self.clockNode.countdown(timeLeft, self.doNothing) + self.clockNode.show() + + + + ########### + ##Game start(broadcast) and Send Turn (broadcast ram) + # + #IMPORTANT - 255 is the Uint8 sent to the player when a game starts + #to dictate to him that a game is beginning and he is labeled as an observer + #for that game - this affects the visual queues for his player color ect. + ########## + + def gameStart(self, playerNum): + if playerNum != 255: #observer value + self.playerNum = playerNum + self.playerColor = self.playerColors[playerNum-1] + self.moveCameraForGame() + + playerPos = playerNum-1 + import copy + self.nonOpposingPositions = copy.deepcopy(self.startingPositions) + if playerPos == 0: + self.nonOpposingPositions.pop(0) + self.opposingPositions = self.nonOpposingPositions.pop(2) + elif playerPos == 1: + self.nonOpposingPositions.pop(1) + self.opposingPositions = self.nonOpposingPositions.pop(3) + elif playerPos == 2: + self.nonOpposingPositions.pop(2) + self.opposingPositions = self.nonOpposingPositions.pop(4) + elif playerPos == 3: + self.nonOpposingPositions.pop(3) + self.opposingPositions = self.nonOpposingPositions.pop(0) + elif playerPos == 4: + self.nonOpposingPositions.pop(4) + self.opposingPositions = self.nonOpposingPositions.pop(1) + elif playerPos == 5: + self.nonOpposingPositions.pop(5) + self.opposingPositions = self.nonOpposingPositions.pop(2) + + self.fsm.request('playing') + def sendTurn(self,playersTurn): + self.playersTurnBlinker.finish() + if self.fsm.getCurrentState().getName() == 'playing': + #print "GETTING HERE!", playersTurn - 1, " LENGTH!!!" , self.playerTagList #self.playerTagList + + if self.playerSeats == None: + self.sendUpdate("requestSeatPositions", []) + else: + if playersTurn == self.playerNum: + self.isMyTurn = True + self.enableTurnScreenText(playersTurn) + self.playersTurnBlinker = Sequence() + origColor = self.playerColors[playersTurn-1] + self.playersTurnBlinker.append(LerpColorInterval(self.playerTagList[self.playerSeats.index(playersTurn)], .4, origColor - self.tintConstant - self.ghostConstant, origColor)) + self.playersTurnBlinker.append(LerpColorInterval(self.playerTagList[self.playerSeats.index(playersTurn)], .4,origColor, origColor - self.tintConstant - self.ghostConstant)) + self.playersTurnBlinker.loop() + + def announceSeatPositions(self, playerPos): + #print "ANNOUNCESEATPOSITIONS!", playerPos + self.playerSeats = playerPos + for x in range(6): + pos = self.table.seats[x].getPos(render) + renderedPeice = loader.loadModel("phase_6/models/golf/checker_marble.bam") + #renderedPeice.setColor(self.playerColors[x-1]) + renderedPeice.reparentTo(self.playerTags) + renderedPeice.setPos(pos) + renderedPeice.setScale(1.5) + if x == 1: + renderedPeice.setZ(renderedPeice.getZ() +3.3) + renderedPeice.setScale(1.3) + elif x == 4: + renderedPeice.setZ(renderedPeice.getZ() +3.3) + renderedPeice.setScale(1.45) + else: + renderedPeice.setZ(renderedPeice.getZ() +3.3) + renderedPeice.hide() + self.playerTagList = self.playerTags.getChildren() + for x in playerPos: + if x != 0: + self.playerTagList[playerPos.index(x)].setColor(self.playerColors[x-1]) + self.playerTagList[playerPos.index(x)].show() + + #if game is already going + #if self.fsm.getCurrentState().getName() == 'playing': + # if not self.playersTurnBlinker.isPlaying(): + # self.playersTurnBlinker = Sequence() + # origColor = self.playerColors[playersTurn-1] + # self.playersTurnBlinker.append(LerpColorInterval(self.playerTagList[self.playerSeats.index(playersTurn)], .4, origColor - self.tintConstant - self.ghostConstant, origColor)) + # self.playersTurnBlinker.append(LerpColorInterval(self.playerTagList[self.playerSeats.index(playersTurn)], .4,origColor, origColor - self.tintConstant - self.ghostConstant)) + # self.playersTurnBlinker.loop() + + def cleanPlayerTags(self): + for x in self.playerTagList: + x.removeNode() + self.playerTagList = [] + self.playerTags.removeNode() + + ########## + ##Move camera + # + #To make camera movement not seem weird (turning 270 degrees example) + #Must check the clients orientation between the seatPos and his current H + # so that he turns the least possible amount for the camera orientation + # + # + ########## + def moveCameraForGame(self): + if self.table.cameraBoardTrack.isPlaying(): + self.table.cameraBoardTrack.finish() + rotation = 0 + if self.seatPos >2: + if self.playerNum == 1: + rotation = 180 + elif self.playerNum == 2: + rotation = -120 + elif self.playerNum == 3: + rotation = -60 + elif self.playerNum == 4: + rotation = 0 + elif self.playerNum == 5: + rotation = 60 + elif self.playerNum == 6: + rotation = 120 + else: + if self.playerNum == 1: + rotation = 0 + elif self.playerNum == 2: + rotation = 60 + elif self.playerNum == 3: + rotation = 120 + elif self.playerNum == 4: + rotation = 180 + elif self.playerNum == 5: + rotation = -120 + elif self.playerNum == 6: + rotation = -60 + + #print self.boardNode.getHpr() + # int = LerpHprInterval(camera, 3,Vec3(camera.getH(),camera.getP(),rotation), camera.getHpr()) + #self.table.tempCheckers.hide() + if rotation == 60 or rotation == -60: + int = LerpHprInterval(self.boardNode, 2.5, Vec3(rotation, self.boardNode.getP(), self.boardNode.getR()), self.boardNode.getHpr()) + elif rotation == 120 or rotation == -120: + int = LerpHprInterval(self.boardNode, 3.5, Vec3(rotation, self.boardNode.getP(), self.boardNode.getR()), self.boardNode.getHpr()) + else: + int = LerpHprInterval(self.boardNode, 4.2, Vec3(rotation, self.boardNode.getP(), self.boardNode.getR()), self.boardNode.getHpr()) + + #self.table.tempCheckers.setHpr( Vec3(rotation, self.table.tempCheckers.getP(), self.table.tempCheckers.getR())) + int.start() + + ##################### + #FSM Stuff + ### + def enterWaitingToBegin(self): + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableExitButton() + self.enableStartButton() + + def exitWaitingToBegin(self): + if self.exitButton: + self.exitButton.destroy() + self.exitButton = None + if self.startButton : + self.startButton.destroy() + self.exitButton = None + self.clockNode.stop() + self.clockNode.hide() + + def enterPlaying(self): + self.inGame = True + self.enableScreenText() + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableLeaveButton() + + def exitPlaying(self): + self.inGame = False + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + self.playerNum = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + self.cleanPlayerTags() + def enterGameOver(self): + pass + def exitGameOver(self): + pass + + ################################################## + # Button Functions and Text + ### + def exitWaitCountdown(self): + self.__disableCollisions() + self.ignore("trolleyExitButton") + self.clockNode.reset() + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersGetUpButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.4), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableScreenText(self): + defaultPos = (-.80, -0.40) + if self.playerNum == 1: + message = TTLocalizer.ChineseCheckersColorG + color = self.playerColors[0] + elif self.playerNum == 2: + message = TTLocalizer.ChineseCheckersColorY + color = self.playerColors[1] + elif self.playerNum == 3: + message = TTLocalizer.ChineseCheckersColorP + color = self.playerColors[2] + elif self.playerNum == 4: + message = TTLocalizer.ChineseCheckersColorB + color = self.playerColors[3] + elif self.playerNum == 5: + message = TTLocalizer.ChineseCheckersColorPink + color = self.playerColors[4] + elif self.playerNum == 6: + message = TTLocalizer.ChineseCheckersColorR + color = self.playerColors[5] + else: + message = TTLocalizer.ChineseCheckersColorO + color = Vec4(0.0,0.0,0.0,1.0) + defaultPos = (-.80, -0.40) + self.screenText = OnscreenText(text = message, pos = defaultPos, scale = 0.10,fg=color,align=TextNode.ACenter,mayChange=1) + def enableStartButton(self): + self.startButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersStartButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.6, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.1), + scale = 0.15, + command = lambda self=self: self.startButtonPushed(), + ) + return + def enableLeaveButton(self): + self.leaveButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersQuitButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.13), + text_scale = 0.5, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.4), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableTurnScreenText(self,player): + self.yourTurnBlinker.finish() + playerOrder = [1,4,2,5,3,6] + message1 = TTLocalizer.ChineseCheckersIts + if(self.turnText != None): + self.turnText.destroy() + #print "player ---",player + #print "playerNum --" ,self.playerNum + if player == self.playerNum: + message2 = TTLocalizer.ChineseCheckersYourTurn + color = (0,0,0,1) + else: + if player == 1: + message2 = TTLocalizer.ChineseCheckersGreenTurn + color = self.playerColors[0] + elif player == 2: + message2 = TTLocalizer.ChineseCheckersYellowTurn + color = self.playerColors[1] + elif player == 3: + message2 = TTLocalizer.ChineseCheckersPurpleTurn + color = self.playerColors[2] + elif player == 4: + message2 = TTLocalizer.ChineseCheckersBlueTurn + color = self.playerColors[3] + elif player == 5: + message2 = TTLocalizer.ChineseCheckersPinkTurn + color = self.playerColors[4] + elif player == 6: + message2 = TTLocalizer.ChineseCheckersRedTurn + color = self.playerColors[5] + self.turnText = OnscreenText(text = message1+message2, pos = (-0.80,-0.50), scale = 0.092,fg=color,align=TextNode.ACenter,mayChange=1) + if player == self.playerNum: + self.yourTurnBlinker = Sequence() + self.yourTurnBlinker.append(LerpScaleInterval(self.turnText, .6, 1.045, 1)) + self.yourTurnBlinker.append(LerpScaleInterval(self.turnText, .6, 1, 1.045)) + self.yourTurnBlinker.loop() + + #This function is called either if the player clicks on it (to begin a game) + #or if the game begin timer runs out. (timer is in sync with server so results should be + # + or - ~ 1 second + def startButtonPushed(self): + self.sendUpdate("requestBegin") + self.startButton.hide() + self.clockNode.stop() + self.clockNode.hide() + + def exitButtonPushed(self): + self.fsm.request('gameOver') + self.table.fsm.request('off') + self.clockNode.stop() + self.clockNode.hide() + + self.table.sendUpdate("requestExit") + ########## + #Mouse Picking/clicking operations + # + # + #These functions handle all of the mous clicking functions + #Its best to look through the code for comments for it to make + #the most sense. + # + #The self.blinker that is referenced in these functions, is a cosmetic + #colorLerp that gives the player a visual feedback as to what checkers peice + #he has (currently) selected. + ########## + def mouseClick(self): + messenger.send('wakeup') + if self.isMyTurn == True and self.inGame == True : #cant pick stuff if its not your turn + mpos = base.mouseWatcherNode.getMouse() + self.pickerRay.setFromLens(base.camNode,mpos.getX(), mpos.getY()) + + self.traverser.traverse(render) + if self.myHandler.getNumEntries() > 0: + self.myHandler.sortEntries()#get the closest Object + pickedObj = self.myHandler.getEntry(0).getIntoNodePath() + #will return the INT for the locator node closest parent + pickedObj = pickedObj.getNetTag("GamePeiceLocator") + if pickedObj: #make sure something actually was "picked" + self.handleClicked(int(pickedObj)) + + def handleClicked(self, index): + #self.inOpposing = False + self.sound = Sequence( SoundInterval(self.clickSound)) #You clicked something play the click sound + #First Moved Square + if self.moveList == []: + #check if owned + if not index in self.mySquares: + return + self.moveList.append(index) #put this on the movelist + if index in self.opposingPositions: + self.isOpposing = True + else: + self.isOpposing = False + + #Start blinking the new "active" peice + self.blinker = Sequence() + self.blinker.append(LerpColorInterval(self.locatorList[index], .7, self.playerColor - self.tintConstant, self.playerColor)) + self.blinker.append(LerpColorInterval(self.locatorList[index], .7,self.playerColor, self.playerColor - self.tintConstant)) + self.blinker.loop() + self.sound.start() + + else: + #Check if the square clicked is not open, if so break out not legal + #If the Player Clicks on one of his peices after an array of clicking new peices + #will reset his "move" and start a new movelist + if self.board.squareList[index].getState() == self.playerNum : + for x in self.moveList: #clear the already selected Nodes back to white + self.locatorList[x].setColor(1,1,1,1) + self.locatorList[x].hide() + #Blinker is the color lerp for the peice "flashing" - need to stop the flashing of the old one + self.blinker.finish() + self.blinker = Sequence() + self.blinker.append(LerpColorInterval(self.locatorList[index], .7, self.playerColor - self.tintConstant, self.playerColor)) + self.blinker.append(LerpColorInterval(self.locatorList[index], .7,self.playerColor, self.playerColor - self.tintConstant)) + self.blinker.loop() + self.sound.start() + + #Swap back to the original peice + #set the original node back to playercolor + self.locatorList[self.moveList[0]].setColor(self.playerColor) + self.locatorList[self.moveList[0]].show() + self.moveList = [] + self.moveList.append(index) + if index in self.opposingPositions: + self.isOpposing = True + else: + self.isOpposing = False + elif self.board.squareList[index].getState() != 0: + return #do nothing because he clicked someone elses peice + else: + #Check for Explicit adjacent move + if len(self.moveList) == 1 and self.board.squareList[index].getState() == 0: + #print "I AM OUTSIDE" + if index in self.board.squareList[self.moveList[0]].getAdjacent(): + #print "I AM INSIDE" + for x in self.nonOpposingPositions: + if index in x: + return #You cannot end a move in a non opposing players square + self.moveList.append(index) + self.blinker.finish() + self.d_requestMove(self.moveList) + self.moveList = [] + self.isMyTurn = False + self.sound.start() + #Check for mid series jumps stoppage + #print len(self.moveList), len(self.moveList)-1 + if len(self.moveList) >= 1: + if index == self.moveList[len(self.moveList)-1]: #you clicked the same thing TWICE + for x in self.nonOpposingPositions: + if index in x: + return #Will force you to jump out ... + if self.existsLegalJumpsFrom(index) == True: + self.blinker.finish() + #self.locatorList[index].setColor(self.playerColor - self.tintConstant) + #self.locatorList[index].show() + self.d_requestMove(self.moveList) + self.moveList=[] + self.isMyTurn = False + self.sound.start() + #Check for Normal jump + #Also check if its a 'finishing jump' + #Therefore no jumps possible after it + ###print "CHECK LEGAL JUMP!", self.checkLegalMove(self.board.getSquare(self.moveList[len(self.moveList)-1]), self.board.getSquare(index)) == True + elif self.checkLegalMove(self.board.getSquare(self.moveList[len(self.moveList)-1]), self.board.getSquare(index)) == True: + #this is the part that adds moves to a series of jumps + ## + #This if statement is an Explicit check to make sure that + #the clicked peice, after a series of jumps + #is not in the middle jump adjacent, This results in a + #bug due to the way ive detected "legal moves" + #but this explicit check should fix that. + if not index in self.board.squareList[self.moveList[len(self.moveList)-1]].getAdjacent(): + for x in self.nonOpposingPositions: + #print " LEGAL JUMPS FROM! ", self.existsLegalJumpsFrom(index) + if self.existsLegalJumpsFrom(index) == False: + if index in x: + return #He tried to JUMP into non opposing players startPos => Illegal + self.moveList.append(index) + #ghostConstant here is a small alpha offset to give it a transparent look + #tintConstant makes the peice a bit darker (necessary when ghosting) + self.locatorList[index].setColor(self.playerColor - self.tintConstant - self.ghostConstant) + self.locatorList[index].show() + self.sound.start() + if self.existsLegalJumpsFrom(index) == False: + self.blinker.finish() + self.d_requestMove(self.moveList) + self.moveList= [] + self.isMyTurn = False + ################################################################## + # Legal Move Request/Checker + # (COPY PASTED FROM AI) + # (Logic here reflects the (NEXT) move not a series of moves, but still + #Checks the validity of two different move peices + # + #This is probably the most complicated as well as most important part + #of the chinese checkers code. To completely understand the CheckLegalMoves, + #get out a peice of paper and try to DRAW out what the logic is doing, ill do + #my best to explain it. + # + # + #Players request moves as a list of Uint8s (already should be verified on the client side) + #but since players are cheating bastards, we check on the server. Basically, how the logic works + #is it takes pairs one at a time and checks for a legal move between the two, ect and traverses the + #list. + # + #A move is Legal if (it is adjacent to its last move) - in a checkerboard adjacent is stored in a list + #of 6 integers representing the other squares (see ChineseCheckerBoard.py) + # + #board.squareList[x].getAdjacent() visually looks like this + # 1 2 + # 0 x 3 where those values [ ] are integers to adjacent squares. + # 5 4 + # + # If a adjacent square does not exist on the board, say the first square only has two adjacent, + # the squares in the adjacent list are set to None + # + #A move is also legal if there exists a legal Jump from A to B. the logic here is difficult to + #express without a picture, but for instance, say A is jumping to B + # + # 1 2 1 2 + # 0 A 3 B 3 + # 5 4 5 6 + # + # You check all of A's adjacents, and check the index of that \ + # particular one (of A's adjacents) that it sits in A, + #if that is equal to B, there is a legal jump, if no one is found the move is illegal. + # + # EX. board.squareList[A].adjacent[3] == (some number to the left of b) + # board.squareList[that number].getAdjacent[self.board.squareList[A].index(some number) + # + # in this case it equals B (draw it out, Trust me it helps) + #### + def existsLegalJumpsFrom(self,index): + for x in self.board.squareList[index].getAdjacent(): + if x == None: + pass + elif x in self.moveList: + pass + elif self.board.getState(x) == 0: + pass + elif self.board.squareList[x].getAdjacent()[self.board.squareList[index].getAdjacent().index(x)] == None: + pass + elif self.board.getState(self.board.squareList[x].getAdjacent()[self.board.squareList[index].getAdjacent().index(x)]) == 0 and not (self.board.squareList[x].getAdjacent()[self.board.squareList[index].getAdjacent().index(x)]) in self.moveList: + return True + return False + def checkLegalMove(self, firstSquare, secondSquare): + if secondSquare.getNum() in firstSquare.getAdjacent(): + return True + else: + for x in firstSquare.getAdjacent(): + if x == None: + pass + elif self.board.squareList[x].getState() == 0: + pass + else: + #print " FIRSTSQUARE ADJACENT AND X -- " , firstSquare.getAdjacent(), " X == " , x + #print "Xs Adjacent and its Index", self.board.squareList[x].getAdjacent(), " INDEX == " , firstSquare.getAdjacent().index(x) + if (self.board.squareList[x].getAdjacent()[firstSquare.getAdjacent().index(x)]) == secondSquare.getNum(): + return True + return False + + def d_requestMove(self, moveList): + self.sendUpdate('requestMove', [moveList]) + + ########## + ##setGameState (required broadcast ram) + # + #This is the function that handles the moves made after a move is parsed by the AI + #and deemed to be valid + # + #If moveList is the empty List, then the player is asynchronously joining the game, (OBSERVING) + #and just wants the board state. + # + #otherwise there is a series of jumps (or jump) that needs to be parsed and animated, then + #consequently setting the board state + # + #after every setGameState, the client checks to see the current + #Status of his peices (checkForWin), if he belives he won, he requests it to the server + ######### + def setGameState(self, tableState, moveList): + if moveList != []: + self.animatePeice(tableState, moveList) + else: + self.updateGameState(tableState) + def updateGameState(self, squares): + self.board.setStates(squares) + self.mySquares = [] + messenger.send('wakeup') + for x in range(121): + self.locatorList[x].clearColor() + owner = self.board.squareList[x].getState() + if owner == self.playerNum: + self.mySquares.append(x) + if owner == 0: + self.locatorList[x].hide() + else: + self.locatorList[x].show() + if owner == 1: + self.locatorList[x].setColor(self.playerColors[0]) + elif owner == 2: + self.locatorList[x].setColor(self.playerColors[1]) + elif owner == 3: + self.locatorList[x].setColor(self.playerColors[2]) + elif owner == 4: + self.locatorList[x].setColor(self.playerColors[3]) + elif owner == 5: + self.locatorList[x].setColor(self.playerColors[4]) + elif owner == 6: + self.locatorList[x].setColor(self.playerColors[5]) + + self.mySquares.sort() + self.checkForWin() + def animatePeice(self, tableState, moveList): + messenger.send('wakeup') + gamePeiceForAnimation = loader.loadModel("phase_6/models/golf/checker_marble.bam") + gamePeiceForAnimation.setColor(self.locatorList[moveList[0]].getColor()) + gamePeiceForAnimation.reparentTo(self.boardNode) + gamePeiceForAnimation.setPos(self.locatorList[moveList[0]].getPos()) + + self.locatorList[moveList[0]].hide() + checkersPeiceTrack = Sequence() + length = len(moveList) + for x in range(length - 1): + checkersPeiceTrack.append(Parallel( SoundInterval(self.moveSound), + ProjectileInterval(gamePeiceForAnimation, + endPos = self.locatorList[moveList[x+1]].getPos(), + duration = .5 ))) + checkersPeiceTrack.append(Func(gamePeiceForAnimation.removeNode)) + checkersPeiceTrack.append(Func(self.updateGameState, tableState)) + checkersPeiceTrack.start() + def checkForWin(self): + if self.playerNum == 1: + if(self.mySquares == self.startingPositions[3]): + self.sendUpdate('requestWin', []) + elif self.playerNum == 2: + if(self.mySquares == self.startingPositions[4]): + self.sendUpdate('requestWin', []) + elif self.playerNum == 3: + if(self.mySquares == self.startingPositions[5]): + self.sendUpdate('requestWin', []) + elif self.playerNum == 4: + if(self.mySquares == self.startingPositions[0]): + self.sendUpdate('requestWin', []) + elif self.playerNum == 5: + if(self.mySquares == self.startingPositions[1]): + self.sendUpdate('requestWin', []) + elif self.playerNum == 6: + if(self.mySquares == self.startingPositions[2]): + self.sendUpdate('requestWin', []) + def announceWin(self, avId): + self.fsm.request('gameOver') + + ########### + ##doRandomMove + # + #If a clients move Timer runs out, it will calculate a random move for him + #after 3 random moves, it kicks the client out of the game by pushing the + #"get up" button for him. + ########### + def doRandomMove(self): + if len(self.moveList) >= 2: + self.blinker.finish() + #self.locatorList[index].setColor(self.playerColor - self.tintConstant) + #self.locatorList[index].show() + self.d_requestMove(self.moveList) + self.moveList=[] + self.isMyTurn = False + self.playSound = Sequence(SoundInterval(self.knockSound)) + self.playSound.start() + else: + import random + move = [] + foundLegal = False + self.blinker.pause() + self.numRandomMoves += 1 + ###self.blinker.finish() + while not foundLegal: + x = random.randint(0,9) + for y in self.board.getAdjacent(self.mySquares[x]): + if y != None and self.board.getState(y) == 0 : + for zz in self.nonOpposingPositions: + if not y in zz: + move.append(self.mySquares[x]) + move.append(y) + foundLegal = True + break + break + + if move == []: + pass + #current flaw in the logic, but shouldnt really ever happen + #though on live it might + #print "random move is empty" + playSound = Sequence(SoundInterval(self.knockSound)) + playSound.start() + self.d_requestMove(move) + self.moveList=[] + self.isMyTurn = False + if(self.numRandomMoves >= 5): + self.exitButtonPushed() + + + + def doNothing(self): + pass diff --git a/toontown/src/safezone/DistributedChineseCheckersAI.py b/toontown/src/safezone/DistributedChineseCheckersAI.py new file mode 100644 index 0000000..dec814b --- /dev/null +++ b/toontown/src/safezone/DistributedChineseCheckersAI.py @@ -0,0 +1,635 @@ +from direct.distributed.DistributedNodeAI import DistributedNodeAI +from direct.distributed.ClockDelta import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from direct.distributed.ClockDelta import * +from toontown.safezone import ChineseCheckersBoard + + +class DistributedChineseCheckersAI(DistributedNodeAI): + def __init__(self, air, parent, name, x, y, z, h, p, r): + DistributedNodeAI.__init__(self,air) + self.name = name + self.air = air + + self.setPos(x,y,z) + self.setHpr(h,p,r) + + self.myPos = (x,y,z) + self.myHpr = (h,p,r) + self.board = ChineseCheckersBoard.ChineseCheckersBoard() + + self.parent = self.air.doId2do[parent] + self.parentDo = parent + self.wantStart = [] + self.playersPlaying = [] + self.playersSitting = 0 + self.playersTurn = 1 + self.movesMade = 0 + + #players game positions (separate from where they are sitting down + self.playersGamePos = [None, None, None, None, None, None] + + self.wantTimer = True + self.timerEnd = 0 + self.turnEnd = 0 + + self.playersObserving = [] + #Arbitrary numbers - can Adjust to feel + self.winLaffPoints = 20 + self.movesRequiredToWin = 10 + + self.zoneId = self.air.allocateZone() + self.generateOtpObject(air.districtId, self.zoneId, optionalFields = ["setX","setY","setZ", "setH", "setP", "setR"]) + #sets the ZoneId in the parent table so when people sit down they know what zone to add + #interest to when they sit down + self.parent.setCheckersZoneId(self.zoneId) + + #parent.b_setCurrentGameId(self.doId) + + #Starting indexes inside of board for setting starting positions + self.startingPositions = [[0,1,2,3,4,5,6,7,8,9], + [10,11,12,13,23,24,25,35,36,46], + [65,75,76,86,87,88,98,99,100,101], + [111,112,113,114,115,116,117,118,119,120], + [74,84,85,95,96,97,107,108,109,110], + [19,20,21,22,32,33,34,44,45,55]] + self.timerStart = None + + + self.fsm = ClassicFSM.ClassicFSM('ChineseCheckers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + #start state + 'waitingToBegin', + #final state + 'waitingToBegin', + ) + + + + self.fsm.enterInitialState() + #self.setGameCountownTime() + + def announceGenerate(self): + self.parent.setGameDoId(self.doId) + def getTableDoId(self): + #print "PARENT -- ", self.parent + return self.parentDo + def delete(self): + self.fsm.requestFinalState() + self.board.delete() + self.playerSeatPos = None + del self.fsm + #simbase.air.deallocateZone(self.zoneId) + DistributedNodeAI.delete(self) + + + def requestSeatPositions(self): + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, "announceSeatPositions", [self.playerSeatPos]) + self.sendUpdateToAvatarId(avId, "sendTurn", [self.playersTurn +1]) + ######################################################################### + ##Inform of player and leave + # + #These functions are called by the table in the event of a player leave/join + #this is necessary so that the game may have an accurate count of who is in the table + #as well as a function that will fire upon single "deltas" of the table state + # + #Table state with avId(s) will be updated via setTableState as well as this + #function being called + #### + def informGameOfPlayer(self): + self.playersSitting += 1 + if self.playersSitting < 2: + self.timerEnd = 0 + elif self.playersSitting == 2: + self.timerEnd = globalClock.getRealTime() + 60 + elif self.playersSitting > 2: + pass + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + + def informGameOfPlayerLeave(self): + self.playersSitting -= 1 + #print "PLAYERSIT", self.playersSitting + if self.playersSitting < 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + self.timerEnd = 0 + if self.playersSitting > 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + pass + else: + self.timerEnd = 0 + if self.timerEnd != 0: + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + else: + self.sendUpdate("setTimer", [0]) + ########################################################################### + ##Timer Functions for game + # + #These timer functions encapsulate the "timer" functions needed for the game table. + # getTimer - is the timer for sitting down and beginning the game + # getTurnTimer - gets and sets the turn timer for players moves + ### + def setGameCountdownTime(self): + self.timerEnd = globalClock.getRealTime() + 60 + def setTurnCountdownTime(self): + self.turnEnd = globalClock.getRealTime() + 60 + def getTimer(self): + if self.timerEnd != 0: + return 0 + # return globalClockDelta.localToNetworkTime(self.timerEnd) + else: + return 0 + def getTurnTimer(self): + return globalClockDelta.localToNetworkTime(self.turnEnd) + # return globalClockDelta.localToNewtorkTime(self.turnTimerEnd) + def requestTimer(self): + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, "setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + ########################################################################### + ##handlePlayerExit and handleEmptyGame + # + #handlePlayerExit is the mother function that gets called when a player requests to leave an "active" game + #of chinese checkers - there are many special cases here, comments inside the function itself. + ### + + def handlePlayerExit(self, avId): + playerOrder = [1,4,2,5,3,6] + if avId in self.wantStart: + self.wantStart.remove(avId) + playstate = self.fsm.getStateNamed('playing') + if self.fsm.getCurrentState().getName() == 'playing': + #Get the players Current "player" position in Chinese Checkers + gamePos = self.playersGamePos.index(avId) + self.playersGamePos[gamePos] = None + for x in self.board.squareList: + if x.getState() == gamePos+1: + x.setState(0) + self.sendGameState( [] ) + if self.playersTurn == gamePos: + self.advancePlayerTurn() + self.d_sendTurn(self.playersTurn+1) + remainingPlayers = 0 + for x in self.playersGamePos: + if x != None: + remainingPlayers += 1 + if remainingPlayers == 1: + for x in self.playersGamePos: + if x != None: + self.clearBoard() + self.sendGameState( [] ) + if(self.movesMade >= self.movesRequiredToWin): + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner("Chinese Checkers", x) + else: + self.fsm.request('gameOver') + + + def handleEmptyGame(self): + #self.clearBoard() + self.movesMade = 0 + self.playersPlaying = [] + self.playersTurn = 1 + self.fsm.request('waitingToBegin') + ############################################################### + ##Request win and distributed Laugh points + # + #This function explicitly tests for a player (that requested he won) + #and checks to see if his peices equal that of his opposite + ## + def requestWin(self): + avId = self.air.getAvatarIdFromSender() + if not avId in self.playersGamePos: + self.air.writeServerEvent('suspicious', avId, 'Has requested a Chinese Checkers win and is NOT playing! SeatList of the table - %s - PlayersGamePos - %s' % (self.parent.seats, self.playersGamePos)) + return + requestWinGamePos = self.playersGamePos.index(avId) + 1 + checkSquares = [] + ##Grab all the tiles with his stamp on them + for x in self.board.squareList: + if x.getState() == requestWinGamePos: + checkSquares.append(x.getNum()) + #explicitly check if they are == + if requestWinGamePos == 1: + if(checkSquares == self.startingPositions[3]): + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner( "Chinese Checkers", avId) + #self.sendUpdate('announceWin', [avId]) + elif requestWinGamePos == 2: + if(checkSquares == self.startingPositions[4]): + # self.sendUpdate('announceWin', [avId]) + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner( "Chinese Checkers", avId) + elif requestWinGamePos == 3: + if(checkSquares == self.startingPositions[5]): + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner("Chinese Checkers", avId) + # self.sendUpdate('announceWin', [avId]) + elif requestWinGamePos == 4: + if(checkSquares == self.startingPositions[0]): + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner("Chinese Checkers", avId) + #self.sendUpdate('announceWin', [avId]) + elif requestWinGamePos == 5: + if(checkSquares == self.startingPositions[1]): + self.fsm.request('gameOver') + self.parent.announceWinner("Chinese Checkers", avId) + #self.sendUpdate('announceWin', [avId]) + elif requestWinGamePos == 6: + if(checkSquares == self.startingPositions[2]): + self.distributeLaffPoints() + self.fsm.request('gameOver') + self.parent.announceWinner("Chinese Checkers", avId) + #self.sendUpdate('announceWin', [avId]) + else: + pass #print "Player requested win, but didnt actually win! OH NOES" + self.parent = None + + + + def distributeLaffPoints(self): + for x in self.parent.seats: + if x != None: + av = self.air.doId2do.get(x) + av.toonUp(self.winLaffPoints) + + ################################################################## + # Chinese Checkers FSM + # + #The FSM is primarily to keep track of buttons/screen text, but also + #serves the purpose of resetting variables upon game over ect. + ### + def enterWaitingToBegin(self): + self.setGameCountdownTime() + self.parent.isAccepting = True + #self.clearBoard() + pass + def exitWaitingToBegin(self): + self.turnEnd = 0 + def enterPlaying(self): + self.parent.isAccepting = False + for x in self.playersGamePos: + if x != None: + self.playersTurn = self.playersGamePos.index(x) + self.d_sendTurn(self.playersTurn+1) + break + #self.d_sendTurn(self.playersTurn) + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + #self.playersTurn += 1 + + pass + def exitPlaying(self): + pass + def enterGameOver(self): + #self.playersObserving = [] + self.timerEnd = 0 + isAccepting = True + self.playersObserving = [] + self.parent.handleGameOver() + self.playersTurn = 1 + self.playersPlaying = [] + self.clearBoard() + self.sendGameState( [] ) + self.movesMade = 0 + self.playersGamePos = [None, None, None, None, None, None] + #self.parent = None + self.fsm.request('waitingToBegin') + + pass + def exitGameOver(self): + pass + + ################################################################## + # Game Start/begin logic + # + #Poor style with setting the player starting Positions I know, But + #is necessary for how the game table must look if odd numbers of people + #are playing IE (* = blank ) + # 3 4 5 + # . * . * . . . . . + # + # * . * . . * . . * + # + # + #Request begin is sent by every avatar when either (1) they click the go button + #or when their start timer runs out + ### + def requestBegin(self): + avId = self.air.getAvatarIdFromSender() + #av = self.air.doId2do.get(avId) + if not( avId in self.wantStart ): + self.wantStart.append(avId) + numPlayers = 0 + for x in self.parent.seats: + if x != None: + numPlayers = numPlayers + 1 + if len(self.wantStart) == numPlayers and numPlayers >= 2: + self.d_gameStart(avId) + self.parent.sendIsPlaying() + def d_gameStart(self,avId): + #255 is a special value just telling the client that he is an Observer + for x in self.playersObserving: + self.sendUpdateToAvatarId(x, "gameStart", [255]) + ### + + playerJoinOrder = [1,4,2,5,3,6] + zz = 0 + numPlayers = 0 + #print "WANT START", self.wantStart + #print "SEATS", self.parent.seats + for x in self.parent.seats: + if x != None: + numPlayers += 1 + self.playersPlaying.append(x) + if numPlayers == 2: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + for x in self.startingPositions[0]: + self.board.setState(x, 1) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [4]) + self.playersGamePos[3] = player2 + for x in self.startingPositions[3]: + self.board.setState(x, 4) + + elif numPlayers == 3: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [2]) + self.playersGamePos[1] = player1 + for x in self.startingPositions[1]: + self.board.setState(x,2) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [4]) + self.playersGamePos[3] = player2 + for x in self.startingPositions[3]: + self.board.setState(x,4) + + player3 = self.playersPlaying[2] + self.sendUpdateToAvatarId(player3, "gameStart", [6]) + self.playersGamePos[5] = player3 + for x in self.startingPositions[5]: + self.board.setState(x,6) + + elif numPlayers == 4: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + for x in self.startingPositions[0]: + self.board.setState(x,1) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [4]) + self.playersGamePos[3] = player2 + for x in self.startingPositions[3]: + self.board.setState(x,4) + + player3 = self.playersPlaying[2] + self.sendUpdateToAvatarId(player3, "gameStart", [2]) + self.playersGamePos[1] = player3 + for x in self.startingPositions[1]: + self.board.setState(x,2) + + player4 = self.playersPlaying[3] + self.sendUpdateToAvatarId(player4, "gameStart", [5]) + self.playersGamePos[4] = player4 + for x in self.startingPositions[4]: + self.board.setState(x,5) + elif numPlayers == 5: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + for x in self.startingPositions[0]: + self.board.setState(x,1) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [4]) + self.playersGamePos[3] = player2 + for x in self.startingPositions[3]: + self.board.setState(x,4) + + player3 = self.playersPlaying[2] + self.sendUpdateToAvatarId(player3, "gameStart", [2]) + self.playersGamePos[1] = player3 + for x in self.startingPositions[1]: + self.board.setState(x,2) + + player4 = self.playersPlaying[3] + self.sendUpdateToAvatarId(player4, "gameStart", [5]) + self.playersGamePos[4] = player4 + for x in self.startingPositions[4]: + self.board.setState(x,5) + + player5 = self.playersPlaying[4] + self.sendUpdateToAvatarId(player5, "gameStart", [3]) + self.playersGamePos[2] = player5 + for x in self.startingPositions[2]: + self.board.setState(x,3) + elif numPlayers == 6: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + for x in self.startingPositions[0]: + self.board.setState(x,1) + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [4]) + self.playersGamePos[3] = player2 + for x in self.startingPositions[3]: + self.board.setState(x,4) + + player3 = self.playersPlaying[2] + self.sendUpdateToAvatarId(player3, "gameStart", [2]) + self.playersGamePos[1] = player3 + for x in self.startingPositions[1]: + self.board.setState(x,2) + + player4 = self.playersPlaying[3] + self.sendUpdateToAvatarId(player4, "gameStart", [5]) + self.playersGamePos[4] = player4 + for x in self.startingPositions[4]: + self.board.setState(x,5) + + player5 = self.playersPlaying[4] + self.sendUpdateToAvatarId(player5, "gameStart", [3]) + self.playersGamePos[2] = player5 + for x in self.startingPositions[2]: + self.board.setState(x,3) + + player6 = self.playersPlaying[5] + self.sendUpdateToAvatarId(player6, "gameStart", [6]) + self.playersGamePos[5] = player6 + for x in self.startingPositions[5]: + self.board.setState(x,6) + + playerSeatPos = [ 0, 0, 0, 0, 0, 0 ] #0 == not taken 1 == green ect. + for x in range(6): + id = self.playersGamePos[x] + if id != None: + playerSeatPos[self.parent.seats.index(id)] = x+1 + self.sendUpdate("announceSeatPositions", [playerSeatPos]) + self.playerSeatPos = playerSeatPos + #print "PLAYER SEAT POS!" , playerSeatPos + self.sendGameState( [] ) + self.wantStart = [] + self.fsm.request('playing') + self.parent.getTableState() + def d_sendTurn(self,playersTurn): + self.sendUpdate("sendTurn", [playersTurn]) + + ################################################################## + # Legal Move Request/Checker + # + #This is probably the most complicated as well as most important part + #of the chinese checkers code. To completely understand the CheckLegalMoves, + #get out a peice of paper and try to DRAW out what the logic is doing, ill do + #my best to explain it. + # + # + #Players request moves as a list of Uint8s (already should be verified on the client side) + #but since players are cheating bastards, we check on the server. Basically, how the logic works + #is it takes pairs one at a time and checks for a legal move between the two, ect and traverses the + #list. + # + #A move is Legal if (it is adjacent to its last move) - in a checkerboard adjacent is stored in a list + #of 6 integers representing the other squares (see ChineseCheckerBoard.py) + # + #board.squareList[x].getAdjacent() visually looks like this + # 1 2 + # 0 x 3 where those values [ ] are integers to adjacent squares. + # 5 4 + # + # If a adjacent square does not exist on the board, say the first square only has two adjacent, + # the squares in the adjacent list are set to None + # + #A move is also legal if there exists a legal Jump from A to B. the logic here is difficult to + #express without a picture, but for instance, say A is jumping to B + # + # 1 2 1 2 + # 0 A 3 B 3 + # 5 4 5 6 + # + # You check all of A's adjacents, and check the index of that \ + # particular one (of A's adjacents) that it sits in A, + #if that is equal to B, there is a legal jump, if no one is found the move is illegal. + # + # EX. board.squareList[A].adjacent[3] == (some number to the left of b) + # board.squareList[that number].getAdjacent[self.board.squareList[A].index(some number) + # + # in this case it equals B (draw it out, Trust me it helps) + #### + def advancePlayerTurn(self): + foundNewPlayer = False + while foundNewPlayer == False: + self.playersTurn += 1 + if self.playersTurn > 5: + self.playersTurn = 0 + if self.playersGamePos[self.playersTurn] != None: + foundNewPlayer = True + + + def requestMove(self, moveList): + #Check if its a legal move first + playerOrder = [1,4,2,5,3,6] + if(self.checkLegalMoves(moveList) == True): + self.movesMade += 1 + self.makeMove(moveList) + self.advancePlayerTurn() + self.d_sendTurn(self.playersTurn + 1) + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + + else: + pass + #broadcast to him that its not legal, probably kick him out of + #game because hes likely cheating + def checkLegalMoves(self, moveList): + #check logic .. Really we are assuming client makes a legal move + #but if he doesnt, he is likely trying to cheat + #print moveList, "MOVELIST" + if not moveList: + return False + elif self.board.squareList[moveList[0]].getState() == 0: + return False + + for x in range(len(moveList)-1): + y = self.checkLegalMove(self.board.getSquare(moveList[x]), self.board.getSquare(moveList[x+1])) + if y == False: + return False + return True + def checkLegalMove(self, firstSquare, secondSquare): + if secondSquare.getNum() in firstSquare.getAdjacent(): + return True + else: + for x in firstSquare.getAdjacent(): + if x == None: + pass + elif self.board.squareList[x].getState() == 0: + pass + else: + if (self.board.squareList[x].getAdjacent()[firstSquare.getAdjacent().index(x)]) == secondSquare.getNum(): + return True + return False + def makeMove(self, moveList): + spot1 = self.board.squareList[moveList[0]].getState() + self.board.squareList[moveList[0]].setState(0) + self.board.squareList[moveList[len(moveList)-1]].setState(spot1) + self.sendGameState( moveList) +################### + def getState(self): + return self.fsm.getCurrentState().getName() + + def getName(self): + return self.name + + + def getGameState(self): + return [self.board.getStates(), [] ] + + def sendGameState(self, moveList): + gameState = self.board.getStates() + self.sendUpdate('setGameState', [gameState, moveList]) + #self.d_setGameState(self.board.getStates()) + + + def clearBoard(self): + for x in self.board.squareList: + x.setState(0) + #self.playersTurn = 0 + #self.playersPlaying = [] + + def getPosHpr(self): + return self.posHpr + + def testWin(self): + self.clearBoard() + for x in self.startingPositions[0]: + self.board.squareList[x].setState(4) + self.board.squareList[self.startingPositions[0][len(self.startingPositions[0])-1]].setState(0) + self.board.squareList[51].setState(4) + + for x in self.startingPositions[3]: + self.board.squareList[x].setState(1) + self.board.squareList[120].setState(0) + self.board.squareList[104].setState(1) + + self.sendGameState( [] ) + + diff --git a/toontown/src/safezone/DistributedDDTreasure.py b/toontown/src/safezone/DistributedDDTreasure.py new file mode 100644 index 0000000..6f7d577 --- /dev/null +++ b/toontown/src/safezone/DistributedDDTreasure.py @@ -0,0 +1,8 @@ +import DistributedSZTreasure + +class DistributedDDTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_6/models/props/starfish_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" + \ No newline at end of file diff --git a/toontown/src/safezone/DistributedDDTreasureAI.py b/toontown/src/safezone/DistributedDDTreasureAI.py new file mode 100644 index 0000000..19eb816 --- /dev/null +++ b/toontown/src/safezone/DistributedDDTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedDDTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedDGFlower.py b/toontown/src/safezone/DistributedDGFlower.py new file mode 100644 index 0000000..aa2d2c1 --- /dev/null +++ b/toontown/src/safezone/DistributedDGFlower.py @@ -0,0 +1,95 @@ +""" DistributedDGFlower module: contains the DistributedDGFlower + class which represents the client version of the round, + spinning flower in Daisy's Garden safezone.""" + +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObject +from toontown.toonbase import ToontownGlobals +from direct.task import Task + +SPIN_RATE = 1.25 + +class DistributedDGFlower(DistributedObject.DistributedObject): + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + def generate(self): + """ + This method is called when the DistributedObject is + reintroduced to the world, either for the first time + or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + # big, rotating flower + self.bigFlower = loader.loadModel('phase_8/models/props/DG_flower-mod.bam') + self.bigFlower.setPos(1.39, 92.91, 2.0) + self.bigFlower.setScale(2.5) + self.bigFlower.reparentTo(render) + + # set-up collision sphere on the flower + self.flowerCollSphere = CollisionSphere(0, 0, 0, 4.5) + self.flowerCollSphereNode = CollisionNode("bigFlowerCollide") + self.flowerCollSphereNode.addSolid(self.flowerCollSphere) + self.flowerCollSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + self.bigFlower.attachNewNode(self.flowerCollSphereNode) + + # set-up trigger sphere on the flower + self.flowerTrigSphere = CollisionSphere(0, 0, 0, 6.0) + self.flowerTrigSphere.setTangible(0) + self.flowerTrigSphereNode = CollisionNode("bigFlowerTrigger") + self.flowerTrigSphereNode.addSolid(self.flowerTrigSphere) + self.flowerTrigSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + self.bigFlower.attachNewNode(self.flowerTrigSphereNode) + + # spawn tasks and hang hooks + taskMgr.add(self.__flowerSpin, + self.taskName('DG-flowerSpin')) + self.accept("enterbigFlowerTrigger", self.__flowerEnter) + self.accept("exitbigFlowerTrigger", self.__flowerExit) + + def disable(self): + """ + This method is called when the DistributedObject is + removed from active duty and stored in a cache. + """ + DistributedObject.DistributedObject.disable(self) + taskMgr.remove(self.taskName('DG-flowerRaise')) + taskMgr.remove(self.taskName('DG-flowerSpin')) + self.ignore("enterbigFlowerTrigger") + self.ignore("exitbigFlowerTrigger") + + def delete(self): + """ + This method is called when the DistributedObject is + permanently removed from the world and deleted from + the cache. + """ + DistributedObject.DistributedObject.delete(self) + self.bigFlower.removeNode() + del self.bigFlower + del self.flowerCollSphere + del self.flowerCollSphereNode + + def __flowerSpin(self, task): + self.bigFlower.setH(self.bigFlower.getH() + SPIN_RATE) + return Task.cont + + def __flowerEnter(self, collisionEntry): + # tell the server + self.sendUpdate("avatarEnter", []) + + def __flowerExit(self, collisionEntry): + # tell the server + self.sendUpdate("avatarExit", []) + + def setHeight(self, newHeight): + # the newHeight is computed by the server + pos = self.bigFlower.getPos() + self.bigFlower.lerpPos(pos[0], pos[1], newHeight, 0.5, + task=self.taskName("DG-flowerRaise")) + + + + diff --git a/toontown/src/safezone/DistributedDGFlowerAI.py b/toontown/src/safezone/DistributedDGFlowerAI.py new file mode 100644 index 0000000..e4cc1bf --- /dev/null +++ b/toontown/src/safezone/DistributedDGFlowerAI.py @@ -0,0 +1,66 @@ +""" DistributedDGFlowerAI module: contains the DistributedDGFlower + class which represents the server version of the round, + spinning flower in Daisy's Garden safezone.""" + +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObjectAI +from direct.task import Task + +HEIGHT_DELTA = 0.5 +MAX_HEIGHT = 10.0 +MIN_HEIGHT = 2.0 + +class DistributedDGFlowerAI(DistributedObjectAI.DistributedObjectAI): + """ + //////////////////////////////////////////////////////////////////// + // + // DistributedDGFlowerAI: server side version of the spinning flower + // located in Daisy's Garden safezone. + // Handles state management of the flower. + // + //////////////////////////////////////////////////////////////////// + """ + def __init__(self, air): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.height = MIN_HEIGHT + self.avList = [] + return None + + def delete(self): + DistributedObjectAI.DistributedObjectAI.delete(self) + + def start(self): + return None + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + if not (avId in self.avList): + # ad avatar to list + self.avList.append(avId) + # compute new height + if (self.height + HEIGHT_DELTA <= MAX_HEIGHT): + self.height += HEIGHT_DELTA + # send update to clients + self.sendUpdate("setHeight", [self.height]) + + def avatarExit(self): + avId = self.air.getAvatarIdFromSender() + if (avId in self.avList): + # remove avatar from list + self.avList.remove(avId) + # compute new height + if (self.height - HEIGHT_DELTA >= MIN_HEIGHT): + self.height -= HEIGHT_DELTA + # send update to clients + self.sendUpdate("setHeight", [self.height]) + + +# History +# +# 01Nov01 gregw created. + diff --git a/toontown/src/safezone/DistributedDGTreasure.py b/toontown/src/safezone/DistributedDGTreasure.py new file mode 100644 index 0000000..790498a --- /dev/null +++ b/toontown/src/safezone/DistributedDGTreasure.py @@ -0,0 +1,7 @@ +import DistributedSZTreasure + +class DistributedDGTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_8/models/props/flower_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" diff --git a/toontown/src/safezone/DistributedDGTreasureAI.py b/toontown/src/safezone/DistributedDGTreasureAI.py new file mode 100644 index 0000000..e27c88c --- /dev/null +++ b/toontown/src/safezone/DistributedDGTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedDGTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedDLTreasure.py b/toontown/src/safezone/DistributedDLTreasure.py new file mode 100644 index 0000000..99bc29e --- /dev/null +++ b/toontown/src/safezone/DistributedDLTreasure.py @@ -0,0 +1,7 @@ +import DistributedSZTreasure + +class DistributedDLTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_8/models/props/zzz_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" diff --git a/toontown/src/safezone/DistributedDLTreasureAI.py b/toontown/src/safezone/DistributedDLTreasureAI.py new file mode 100644 index 0000000..3aac42e --- /dev/null +++ b/toontown/src/safezone/DistributedDLTreasureAI.py @@ -0,0 +1,10 @@ + +import DistributedSZTreasureAI + +class DistributedDLTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedEFlyingTreasure.py b/toontown/src/safezone/DistributedEFlyingTreasure.py new file mode 100644 index 0000000..10fcb3f --- /dev/null +++ b/toontown/src/safezone/DistributedEFlyingTreasure.py @@ -0,0 +1,47 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +import DistributedSZTreasure +from direct.task.Task import Task +import math +import random + +class DistributedEFlyingTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + #self.modelPath = "phase_4/models/props/sun" + self.modelPath = "phase_5.5/models/props/popsicle_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" + self.scale = 2 + self.delT = math.pi * 2.0*random.random() + self.shadow = 0 + + def disable(self): + DistributedSZTreasure.DistributedSZTreasure.disable(self) + taskMgr.remove(self.taskName("flying-treasure")) + + def generateInit(self): + DistributedSZTreasure.DistributedSZTreasure.generateInit(self) + + # The handler that catches the initial position established on the AI + def setPosition(self, x, y, z): + #print "setPosition!" + DistributedSZTreasure.DistributedSZTreasure.setPosition(self, x, y, z) + self.initPos = self.nodePath.getPos() + self.pos = self.nodePath.getPos() + + def startAnimation(self): + taskMgr.add(self.animateTask, + self.taskName("flying-treasure")) + + def animateTask(self, task): + pos = self.initPos + t = .5*math.pi*globalClock.getFrameTime() + dZ = 5.0 * math.sin(t+self.delT) + dY = 2.0 * math.cos(t+self.delT) + self.nodePath.setPos(pos[0], pos[1], pos[2]+dZ) + if self.pos: + del self.pos + self.pos = self.nodePath.getPos() + return Task.cont + + diff --git a/toontown/src/safezone/DistributedEFlyingTreasureAI.py b/toontown/src/safezone/DistributedEFlyingTreasureAI.py new file mode 100644 index 0000000..12f9d17 --- /dev/null +++ b/toontown/src/safezone/DistributedEFlyingTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedEFlyingTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedETreasure.py b/toontown/src/safezone/DistributedETreasure.py new file mode 100644 index 0000000..59113ff --- /dev/null +++ b/toontown/src/safezone/DistributedETreasure.py @@ -0,0 +1,8 @@ +import DistributedSZTreasure + +class DistributedETreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_4/models/props/icecream" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" + diff --git a/toontown/src/safezone/DistributedETreasureAI.py b/toontown/src/safezone/DistributedETreasureAI.py new file mode 100644 index 0000000..4fdf72e --- /dev/null +++ b/toontown/src/safezone/DistributedETreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedETreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedFindFour.py b/toontown/src/safezone/DistributedFindFour.py new file mode 100644 index 0000000..d15ab84 --- /dev/null +++ b/toontown/src/safezone/DistributedFindFour.py @@ -0,0 +1,1126 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer + +from direct.distributed import DistributedNode +from direct.distributed.ClockDelta import globalClockDelta +from ChineseCheckersBoard import ChineseCheckersBoard +from direct.fsm import ClassicFSM, State +from direct.fsm import StateData +#from direct.distributed import DelayDelete + +from toontown.toonbase.ToontownTimer import ToontownTimer +from toontown.toonbase import ToontownGlobals +from direct.distributed.ClockDelta import * + +from otp.otpbase import OTPGlobals + + +from direct.showbase import PythonUtil +#from direct.task import Task + +class DistributedFindFour(DistributedNode.DistributedNode): + def __init__(self, cr): + NodePath.__init__(self, "DistributedFindFour") + DistributedNode.DistributedNode.__init__(self,cr) + self.cr = cr + + self.reparentTo(render) + self.boardNode = loader.loadModel("phase_6/models/golf/findfour_game.bam") + self.boardNode.reparentTo(self) + #self.boardNode.setScale(.05) + + + self.board = [ [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0] ] + + + + + + #game variables + self.exitButton = None + self.inGame = False + self.waiting = True + self.startButton = None + self.playerNum = None + self.turnText = None + self.isMyTurn = False + self.wantTimer = True + self.leaveButton = None + self.screenText = None + self.turnText = None + self.exitButton = None + self.numRandomMoves = 0 + self.blinker = Sequence() + self.playersTurnBlinker = Sequence() + self.yourTurnBlinker = Sequence() + self.winningSequence = Sequence() + self.moveSequence = Sequence() + self.moveList = [] + self.mySquares = [] + self.playerSeats = None + self.moveCol = None + + + self.move = None + ###self.playerTags = [None, None, None, None, None, None + + + #Mouse picking required stuff + self.accept('mouse1', self.mouseClick) + self.traverser = base.cTrav + self.pickerNode = CollisionNode('mouseRay') + self.pickerNP = camera.attachNewNode(self.pickerNode) + self.pickerNode.setFromCollideMask(BitMask32(0x1000)) + self.pickerRay = CollisionRay() + self.pickerNode.addSolid(self.pickerRay) + self.myHandler = CollisionHandlerQueue() + self.traverser.addCollider(self.pickerNP, self.myHandler) + + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find("**/InventoryButtonRollover") + + self.clockNode = ToontownTimer() + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.setScale(0.3) + self.clockNode.hide() + + self.tintConstant = Vec4(.25,.25,.25,0) + self.ghostConstant = Vec4(0,0,0,.5) + + + self.knockSound = base.loadSfx("phase_5/audio/sfx/GUI_knock_1.mp3") + self.clickSound = base.loadSfx("phase_3/audio/sfx/GUI_balloon_popup.mp3") + self.moveSound = base.loadSfx("phase_6/audio/sfx/CC_move.mp3") + self.accept('stoppedAsleep', self.handleSleep) + + + + + + + ####################### + #Fsm and State Data + from direct.fsm import ClassicFSM,State + self.fsm = ClassicFSM.ClassicFSM('ChineseCheckers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing','gameOver']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + # Initial State + 'waitingToBegin', + # Final State + 'waitingToBegin', + ) + + ######################### + #Set up the Board Locators + ## + startLoc = self.boardNode.find("**/locators") + self.locatorList = startLoc.getChildren() + + self.startingPositions = self.locatorList.pop(0) + self.startingPositions = self.startingPositions.getChildren() + + + instancePiece = self.boardNode.find("**/pieces") + + #tag the locators for "picking" ingame + #also add colision spheres for movement + tempList = [] + + #Start Position locators + #Up above the board + for x in range(7): + self.startingPositions[x].setTag("StartLocator", "%d" % x) + collNode = CollisionNode("startpicker%d" %x) + collNode.setIntoCollideMask(BitMask32(0x1000)) + tempList.append(self.startingPositions[x].attachNewNode(collNode)) + tempList[x].node().addSolid(CollisionTube(0,0,.23,0,0,-.23,.2)) + for z in self.startingPositions: + y = instancePiece.copyTo(z) + for val in y.getChildren(): + val.hide() + + tempList = [] + #the peices themselves inside of the board. + for x in range(42): + self.locatorList[x].setTag("GamePeiceLocator", "%d" % x) + collNode = CollisionNode("startpicker%d" %x) + collNode.setIntoCollideMask(BitMask32(0x1000)) + tempList.append(self.locatorList[x].attachNewNode(collNode)) + tempList[x].node().addSolid(CollisionSphere(0,0,0,.2)) + for z in self.locatorList: + y = instancePiece.copyTo(z) + for val in y.getChildren(): + val.hide() + + + dummyHide = instancePiece.getParent().attachNewNode("DummyHider") + instancePiece.reparentTo(dummyHide) + dummyHide.hide() + + def setName(self, name): + self.name = name + + def announceGenerate(self): + DistributedNode.DistributedNode.announceGenerate(self) + if self.table.fsm.getCurrentState().getName() != 'observing': + if base.localAvatar.doId in self.table.tableState: # Fix for strange state #TEMP until i find the cause + #got to rotate all the peices so that they are facing the side the player is sitting in, + #this is a byproduct of backface culling && polygon optimization + self.seatPos = self.table.tableState.index(base.localAvatar.doId) + if self.seatPos <= 2: + for x in self.startingPositions: + x.setH(0) + for x in self.locatorList: + x.setH(0) + else: + for x in self.startingPositions: + x.setH(180) + for x in self.locatorList: + x.setH(180) + self.moveCameraForGame() + else: + self.seatPos = self.table.seatBumpForObserve + if self.seatPos > 2: + for x in self.startingPositions: + x.setH(180) + for x in self.locatorList: + x.setH(180) + self.moveCameraForGame() + + + def handleSleep(self, task = None): + if self.fsm.getCurrentState().getName() == "waitingToBegin": + self.exitButtonPushed() + if task != None: + task.done + + ########## + ##setTableDoId (required broadcast ram) + # + #Upon construction, sets local pointer to the table, as well as + #sets the pointer on the table to itself. + #This is necessary to handle events that occur on the table, not + #particularly inside of any one game. + ### + def setTableDoId(self, doId): + self.tableDoId = doId + self.table = self.cr.doId2do[doId] + self.table.setTimerFunc(self.startButtonPushed) + self.fsm.enterInitialState() + self.table.setGameDoId(self.doId) + + + ######### + ##Disable/Delete + #Must be sure to remove/delete any buttons, screen text + #that may be on the screen in the event of a chosen ( or asynchrinous ) + #disable or deletion - Code redundance here is necessary + #being that "disable" is called upon server crash, and delete is + #called upon zone exit + ### + def disable(self): + DistributedNode.DistributedNode.disable(self) + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + taskMgr.remove('playerTurnTask') + #self.table = None + + def delete(self): + DistributedNode.DistributedNode.delete(self) + self.table.gameDoId = None + self.table.game = None + if self.exitButton: + self.exitButton.destroy() + if self.startButton : + self.startButton.destroy() + self.clockNode.stop() + self.clockNode.hide() + self.table.startButtonPushed = None + self.ignore('mouse1') + self.ignore('stoppedAsleep') + self.fsm = None + self.table = None + self.winningSequence.finish() + taskMgr.remove('playerTurnTask') + + ########## + ##Timer Functions + #setTimer (broadcast ram required) + #setTurnTimer(broadcast ram required) + # + #setTimer() controlls the timer for game begin, which upon its timout + #calls the startButton. + # + #turnTimer() does just that + # + #Important to note that both timers run on the same clockNode. + ########## + def getTimer(self): + self.sendUpdate('requestTimer', []) + def setTimer(self, timerEnd): + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'waitingToBegin' and not self.table.fsm.getCurrentState().getName() == 'observing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(timerEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if(timeLeft > 0 and timerEnd != 0): + if timeLeft > 60: + timeLeft = 60 + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.countdown(timeLeft, self.startButtonPushed) + self.clockNode.show() + else: + self.clockNode.stop() + self.clockNode.hide() + def setTurnTimer(self, turnEnd): + if self.fsm.getCurrentState() != None and self.fsm.getCurrentState().getName() == 'playing': + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(turnEnd) + timeLeft = int(time - globalClock.getRealTime() ) + if timeLeft > 0: + self.clockNode.setPos(.64, 0, -0.27) + self.clockNode.countdown(timeLeft, self.doRandomMove) + self.clockNode.show() + + + + ########### + ##Game start(broadcast) and Send Turn (broadcast ram) + # + #IMPORTANT - 255 is the Uint8 sent to the player when a game starts + #to dictate to him that a game is beginning and he is labeled as an observer + #for that game - this affects the visual queues for his player color ect. + ########## + def gameStart(self, playerNum): + if playerNum != 255: #observer value + self.playerNum = playerNum + if self.playerNum == 1: + self.playerColorString = "Red" + else: + self.playerColorString = "Yellow" + self.moveCameraForGame() + self.fsm.request('playing') + def sendTurn(self,playersTurn): + if self.fsm.getCurrentState().getName() == 'playing': + if playersTurn == self.playerNum: + self.isMyTurn = True + taskMgr.add(self.turnTask, "playerTurnTask") + self.enableTurnScreenText(playersTurn) + + def illegalMove(self): + self.exitButtonPushed() + + ########## + ##Move camera + # + #Got to move the camera to the specific location in the 3d environment so that the + #nametags ect do not collide with the gameboard itself. + + #also half to do some if statements on the camera itself so that the camera will not (for instance) + #rotate 350 degrees when rather it could rotate -10 degrees to get its finishing location + ########## + def moveCameraForGame(self): + if self.table.cameraBoardTrack.isPlaying(): + self.table.cameraBoardTrack.pause() + rotation = 0 + if self.seatPos <= 2: + position = self.table.seats[1].getPos() + position = position + Vec3(0,-8,12.8) + int = LerpPosHprInterval(camera, 2, position, Vec3(0, -38, 0), camera.getPos(), camera.getHpr()) + else: + position = self.table.seats[4].getPos() + position = position + Vec3(0,-8,12.8) + if camera.getH() < 0 : + int = LerpPosHprInterval(camera, 2, position, Vec3(-180, -20, 0), camera.getPos(), camera.getHpr()) + else: + int = LerpPosHprInterval(camera, 2, position, Vec3(180, -20, 0), camera.getPos(), camera.getHpr()) + int.start() + + ##################### + #FSM Stuff + ### + def enterWaitingToBegin(self): + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableExitButton() + self.enableStartButton() + + def exitWaitingToBegin(self): + if self.exitButton: + self.exitButton.destroy() + self.exitButton = None + if self.startButton : + self.startButton.destroy() + self.exitButton = None + self.clockNode.stop() + self.clockNode.hide() + + def enterPlaying(self): + self.inGame = True + self.enableScreenText() + if self.table.fsm.getCurrentState().getName() != 'observing': + self.enableLeaveButton() + + def exitPlaying(self): + self.inGame = False + if self.leaveButton: + self.leaveButton.destroy() + self.leavebutton = None + self.playerNum = None + if self.screenText: + self.screenText.destroy() + self.screenText = None + if self.turnText: + self.turnText.destroy() + self.turnText = None + self.clockNode.stop() + self.clockNode.hide() + def enterGameOver(self): + pass + def exitGameOver(self): + pass + + ################################################## + # Button Functions and Text + ### + def exitWaitCountdown(self): + self.__disableCollisions() + self.ignore("trolleyExitButton") + self.clockNode.reset() + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersGetUpButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.80), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableScreenText(self): + defaultPos = (-.7, -0.29) + if self.playerNum == 1: + message = "You are Red" + color = Vec4(1,0,0,1) + elif self.playerNum == 2: + message = "You are Yellow" + color = Vec4(1,1,0,1) + else: + message = TTLocalizer.CheckersObserver + color = Vec4(0,0,0,1) + #defaultPos = (-.80, -0.25) + self.screenText = OnscreenText(text = message, pos = defaultPos, scale = 0.10,fg=color,align=TextNode.ACenter,mayChange=1) + def enableStartButton(self): + self.startButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersStartButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.6, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.57), + scale = 0.15, + command = lambda self=self: self.startButtonPushed(), + ) + return + def enableLeaveButton(self): + self.leaveButton = DirectButton( + relief = None, + text = TTLocalizer.ChineseCheckersQuitButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.13), + text_scale = 0.5, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.8), + scale = 0.15, + command = lambda self=self: self.exitButtonPushed(), + ) + return + def enableTurnScreenText(self,player): + playerOrder = [1,4,2,5,3,6] + message1 = TTLocalizer.CheckersIts + if(self.turnText != None): + self.turnText.destroy() + if player == self.playerNum: + message2 = TTLocalizer.ChineseCheckersYourTurn + color = (0,0,0,1) + else: + if player == 1: + message2 = "Red's Turn" + color = (1,0,0,1) + elif player == 2: + message2 = "Yellow's Turn" + color = (1,1,0,1) + self.turnText = OnscreenText(text = message1+message2, pos = (-0.7, -0.39), scale = 0.092,fg=color,align=TextNode.ACenter,mayChange=1) + + #This function is called either if the player clicks on it (to begin a game) + #or if the game begin timer runs out. (timer is in sync with server so results should be + # + or - ~ 1 second + def startButtonPushed(self): + self.sendUpdate("requestBegin") + self.startButton.hide() + self.clockNode.stop() + self.clockNode.hide() + + def exitButtonPushed(self): + self.fsm.request('gameOver') + self.table.fsm.request('off') + self.clockNode.stop() + self.clockNode.hide() + self.table.sendUpdate("requestExit") + ########## + #Mouse Picking/clicking operations + # + # + #These functions handle all of the mouse clicking functions + #Its best to look through the code for comments for it to make + #the most sense. + # + #The self.blinker that is referenced in these functions, is a cosmetic + #colorLerp that gives the player a visual feedback as to what checkers peice + #he has (currently) selected. + # + # + #mouseClick is just an extention to the Task that is generated whenever it is your turn - + #mouseClick simply commits the move that you have been deciding upon inside of the 'playerTurnTask" + ########## + def mouseClick(self): + messenger.send('wakeup') + if self.isMyTurn == True and self.inGame == True and not self.moveSequence.isPlaying(): #cant pick stuff if its not your turn + if self.moveCol != None: + self.d_requestMove(self.moveCol) + self.moveCol = None + self.isMyTurn = False + taskMgr.remove('playerTurnTask') + + def handleClicked(self, index): + pass + + #This is the wonderul task that handles the magic for the selecting of the game piece + #it is important to note that this task gets spawned up whenever it is deemed your turn + #by the sendTurn stuff + + #I had to make some optimizations to not make this task completely bogg down the computer + #(1) set the bit masks for the collision traverser so that it will not even test anything that + #is not a collision sphere for the findFour Game + def turnTask(self, task): + + if base.mouseWatcherNode.hasMouse() == False: + return task.cont + if self.isMyTurn == False: + return task.cont + if self.moveSequence.isPlaying(): + return task.cont + + + mpos = base.mouseWatcherNode.getMouse() + self.pickerRay.setFromLens(base.camNode,mpos.getX(), mpos.getY()) + + self.traverser.traverse(render) + if self.myHandler.getNumEntries() > 0: + self.myHandler.sortEntries()#get the closest Object + pickedObj = self.myHandler.getEntry(0).getIntoNodePath() + #will return the INT for the locator node closest parent + pickedObj = pickedObj.getNetTag("StartLocator") + if pickedObj: #make sure something actually was "picked" + colVal = int(pickedObj) + if colVal == self.moveCol: + return task.cont + if self.board[0][colVal] == 0: #it is a legal move + if self.moveCol != None: + for x in self.startingPositions[self.moveCol].getChild(1).getChildren(): #hide the old peices + x.hide() + self.moveCol = colVal + if self.playerNum == 1: + self.startingPositions[self.moveCol].getChild(1).getChild(2).show() + elif self.playerNum == 2: + self.startingPositions[self.moveCol].getChild(1).getChild(3).show() + return task.cont + + + def d_requestMove(self, moveCol): + self.sendUpdate('requestMove', [moveCol]) + + ########## + ##setGameState (required broadcast ram) + # + #This is the function that handles the moves made after a move is parsed by the AI + #and deemed to be valid + # + #If moveList is the empty List, then the player is asynchronously joining the game, (OBSERVING) + #and just wants the board state. + # + #otherwise there is a series of jumps (or jump) that needs to be parsed and animated, then + #consequently setting the board state + # + #after every setGameState, the client checks to see the current + #Status of his peices (checkForWin), if he belives he won, he requests it to the server + ######### + def setGameState(self, tableState, moveCol, movePos, turn): + messenger.send('wakeup') + + ##Funny if statement #1 + #This slew of statements is to handle a specific case that looks funny + #when a client joins the game (to observe) after the game has begun + # + #If you notice, you will see that the movePos and the MoveCol are parts + #of the game state (required RAM), so when a client joins this game asynchrinously + #they too will see the last moves animation as they are sitting down at the table. + # + # These slew of if statements are to handle that fact, by testing the state of the + #current game board, as well as the state of the board that was handed to the client + #through the DC message. + if self.table.fsm.getCurrentState().getName() == 'observing': + isBlank = True + for x in range(7): + if self.board[5][x] != 0: + isBlank = False + break + + gameBlank = True + for x in range(7): + if tableState[5][x] != 0: + gameBlank = False + break + + if isBlank == True and gameBlank == False: + for x in range(6): + for y in range(7): + self.board[x][y] = tableState[x][y] + self.updateGameState() + return + ### + + #This if statement really doesnt apply unless the board is completely blank! + if moveCol == 0 and movePos == 0 and turn == 0: #Someones coming in after a Move + for x in range(6): + for y in range(7): + self.board[x][y] = tableState[x][y] + self.updateGameState() + else: #need to animate because a movePos was given in the Ram Field + self.animatePeice(tableState, moveCol, movePos, turn) + + #Clients all check if they won in this game, where the AI + #verifies - to save cycles on the AI server + didIWin = self.checkForWin() + if didIWin != None: + self.sendUpdate("requestWin", [didIWin]) + + ############################################ + ##UpdateGameState + # + #For the games (visual) state, it is important to note that child 0 and child1 + #are the pieces that have been copied and reparented to that node. + #Really, i just have 2 peices that are in the model, and i hide/show them + #instead of instantiating them on the fly. + ## + + def updateGameState(self): + for x in range(6): + for y in range(7): + for z in self.locatorList[(x*7)+y].getChild(1).getChildren(): + z.hide() + for x in range(6): + for y in range(7): + state = self.board[x][y] + if state == 1: + self.locatorList[(x*7) + y ].getChild(1).getChild(0).show() + elif state == 2: + self.locatorList[(x*7) + y ].getChild(1).getChild(1).show() + + ########## + ##CheckForWin + # + #This function is pretty self explanitory, it checks all of the directions for + #all of the pieces on the players current boardState to check for a win. + # + #this is needed because programming wise it would be a disgusting case to test + #for a win if a peice is in the middle of a (4) piece run - Rather it is easier + #to simply test all the pieces whether or not they are on the Edge of a win, + #thus making the code 1000% easier to read and understand. + ### + def checkForWin(self): + for x in range(6): + for y in range(7): + if self.board[x][y] == self.playerNum: + if self.checkHorizontal(x,y,self.playerNum) == True: + return [x,y] + elif self.checkVertical(x,y,self.playerNum) == True: + return [x,y] + elif self.checkDiagonal(x,y,self.playerNum) == True: + return [x,y] + return None + ########### + ##AnnounceWinnerPosition + # + #Fancy function that is called after the AI determines the players' request was + #in deed a true win. + # + #This function does all of the animations for the blinking after a person wins + # + #Also - Note that there are two separate functions for finding/checking a win. + # + #check(direction) - returns true or false + #find(direction) - returns the list of 4 ints if win and [] for no win + ### + def announceWinnerPosition(self, x, y, winDirection,playerNum): + self.isMyturn = False + if self.turnText: + self.turnText.hide() + self.clockNode.stop() + self.clockNode.hide() + + if winDirection == 0: + blinkList = self.findHorizontal(x,y,playerNum) + elif winDirection == 1: + blinkList = self.findVertical(x,y,playerNum) + elif winDirection == 2: + blinkList = self.findDiagonal(x,y,playerNum) + + if blinkList != []: + print blinkList + val0 = (x*7) + y + + x = blinkList[0][0] + y = blinkList[0][1] + val1 = (x *7) + y + + x = blinkList[1][0] + y = blinkList[1][1] + val2 = (x *7) + y + + x = blinkList[2][0] + y = blinkList[2][1] + val3 = (x *7) + y + + + + self.winningSequence = Sequence() + downBlinkerParallel = Parallel( LerpColorInterval(self.locatorList[val0], .3, Vec4(.5,.5,.5,.5), Vec4(1,1,1,1)), + LerpColorInterval(self.locatorList[val1], .3, Vec4(.5,.5,.5,.5), Vec4(1,1,1,1)), + LerpColorInterval(self.locatorList[val2], .3, Vec4(.5,.5,.5,.5), Vec4(1,1,1,1)), + LerpColorInterval(self.locatorList[val3], .3, Vec4(.5,.5,.5,.5), Vec4(1,1,1,1))) + upBlinkerParallel = Parallel( LerpColorInterval(self.locatorList[val0], .3, Vec4(1,1,1,1), Vec4(.5,.5,.5,.5)), + LerpColorInterval(self.locatorList[val1], .3, Vec4(1,1,1,1), Vec4(.5,.5,.5,.5)), + LerpColorInterval(self.locatorList[val2], .3, Vec4(1,1,1,1), Vec4(.5,.5,.5,.5)), + LerpColorInterval(self.locatorList[val3], .3, Vec4(1,1,1,1), Vec4(.5,.5,.5,.5))) + + + self.winningSequence.append(downBlinkerParallel) + self.winningSequence.append(upBlinkerParallel) + self.winningSequence.loop() + ########## + ##Tie + # + #This is the function that sends the tie message, and animates the whole board in a fun way such that + #dictates that a tie has occurred + ### + def tie(self): + self.tieSequence = Sequence(autoFinish = 1) + self.clockNode.stop() + self.clockNode.hide() + self.isMyTurn = False + self.moveSequence.finish() + + if self.turnText: + self.turnText.hide() + for x in range(41): + self.tieSequence.append(Parallel(LerpColorInterval(self.locatorList[x],.15, Vec4(.5,.5,.5,.5), Vec4(1,1,1,1)), + LerpColorInterval(self.locatorList[x],.15,Vec4(1,1,1,1), Vec4(.5,.5,.5,.5)))) + + whisper = WhisperPopup("This Find Four game has resulted in a Tie!", + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + whisper.manage(base.marginManager) + self.tieSequence.start() + + + def hideChildren(self, nodeList): + pass + ########## + ##animatePiece + # + #Function that handles the animation of the next move, as well as sets the current + #view of the board so that the player can see it + def animatePeice(self, tableState, moveCol, movePos, turn): + messenger.send('wakeup') + + #update the board state from DC parent function + for x in range(6): + for y in range(7): + self.board[x][y] = tableState[x][y] + + #show the column piece that was selected for the move + pos = self.startingPositions[moveCol].getPos() + if turn == 0: + peice = self.startingPositions[moveCol].getChild(1).getChildren()[2] + peice.show() + elif turn == 1: + peice = self.startingPositions[moveCol].getChild(1).getChildren()[3] + peice.show() + + #animate the thing + self.moveSequence = Sequence() + startPos = self.startingPositions[moveCol].getPos() + arrayLoc = (movePos * 7) + moveCol + self.moveSequence.append(LerpPosInterval(self.startingPositions[moveCol], 1.5, self.locatorList[arrayLoc].getPos(self), startPos)) + + self.moveSequence.append(Func(peice.hide)) + self.moveSequence.append(Func(self.startingPositions[moveCol].setPos, startPos)) + self.moveSequence.append(Func(self.updateGameState)) + self.moveSequence.start() + + + + def announceWin(self, avId): + self.fsm.request('gameOver') + ########## + ##doRandomMove + # + #For this game, doRandom move will as well fire when the turnTimer runs out - but will also, + #(if the player has been debating pieces with his cursor), will commite the last selected piece + #by the 'playerTurnTask' + # + #It is also smart enough to not randomly commit an illegal move :) + ########### + def doRandomMove(self): + if self.isMyTurn: + if self.moveCol != None: + self.d_requestMove(self.moveCol) + self.moveCol = None + self.isMyTurn = False + taskMgr.remove('playerTurnTask') + else: + hasfound = False + while hasfound == False: + from random import * + x = randint(0,6) + if self.board[0][x] == 0: + self.d_requestMove(x) + self.moveCol = None + self.isMyTurn = False + taskMgr.remove('playerTurnTask') + hasfound = True + + def doNothing(self): + pass + + ########## + ##Large slew of check/find Functions + # + #I would suggest getting out paper to analyze how these things work, but hopefully + #with a bit of ASCII art and some explaining it wont be too bad. + # + # Game board is represented as such in memory (as you see it in real life): + # |0 0 0 0 0 0 0 | 0 1 2 3 4 5 6 + # |0 0 0 0 0 0 0 | 0 . . . . . . . + # |0 0 0 0 0 0 0 | 1 . . . . . . . + # |0 0 0 0 0 0 0 | 2 . . . . . . . + # |0 0 0 0 0 0 0 | 3 . . . . . . . + # |0 0 0 0 0 0 0 | 4 . . . . . . . + # 5 . . . . . . . + # + # + # as you can see, (0,0) is in the top left corner, and just makes things easier to write about + #because the locators, and lists seem to function well in this sense + # board state looks like [ [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0] ] + # + # + #So when looking at this code just keep in mind this is how the board is working in memory + # + #Also, important to see that there are two types of functions here + #(1) the check(direction) functions who return true or false whether or not they detect + # a win in that direction + #(2) the find(direction) functions who return the list of where they detected a win + # or [ ] if they found nothing. + ## + + def checkHorizontal(self, rVal, cVal, playerNum): + if cVal == 3: + for x in range(1,4): + if self.board[rVal][cVal-x] != playerNum: + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal][cVal + x] != playerNum: + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal == 2: + for x in range(1,4): + if self.board[rVal][cVal + x] != playerNum: + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal == 4: + for x in range(1,4): + if self.board[rVal][cVal-x] != playerNum: + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return True + return False + else: + return False + def checkVertical(self, rVal, cVal, playerNum): + if rVal == 2: + for x in range(1,4): + if self.board[rVal+x][cVal] != playerNum: + break + if self.board[rVal+x][cVal] == playerNum and x == 3: + return True + return False + elif rVal == 3: + for x in range(1,4): + if self.board[rVal-x][cVal] != playerNum: + break + if self.board[rVal-x][cVal] == playerNum and x == 3: + return True + return False + else: + return False + def checkDiagonal(self, rVal, cVal, playerNum): + if cVal <= 2: #Cannot have Left Diagonals + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + if self.board[rVal+x][cVal+x] != playerNum: + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return True + return False + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + if self.board[rVal-x][cVal+x] != playerNum: + break + if self.board[rVal-x][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal >= 4: + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + if self.board[rVal+x][cVal-x] != playerNum: + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return True + return False + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + if self.board[rVal-x][cVal-x] != playerNum: + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return True + return False + else: #we are in column 3 + if rVal == 3 or rVal == 4 or rVal == 5: #we have 2 upward Diagonals + for x in range(1,4):#Up left + if self.board[rVal-x][cVal-x] != playerNum: + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal-x][cVal-x] != playerNum: + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return True + return False + elif rVal == 0 or rVal == 1 or rVal == 2: + for x in range(1,4):#down left + if self.board[rVal+x][cVal-x] != playerNum: + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal+x][cVal+x] != playerNum: + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return True + return False + return False +####################################### + + def findHorizontal(self, rVal, cVal, playerNum): + if cVal == 3: + retList = [] + for x in range(1,4): + retList.append([rVal, cVal-x]) + if self.board[rVal][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return retList + for x in range(1,4): + retList.append([rVal, cVal+x]) + if self.board[rVal][cVal + x] != playerNum: + retList = [] + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return retList + return [] + elif cVal == 2: + retList = [] + for x in range(1,4): + retList.append([rVal, cVal+x]) + if self.board[rVal][cVal + x] != playerNum: + retList = [] + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return retList + return [] + elif cVal == 4: + retList = [] + for x in range(1,4): + retList.append([rVal, cVal-x]) + if self.board[rVal][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return retList + return [] + else: + return [] + def findVertical(self, rVal, cVal, playerNum): + if rVal == 2: + retList = [] + for x in range(1,4): + retList.append([rVal+x, cVal]) + if self.board[rVal+x][cVal] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal] == playerNum and x == 3: + return retList + return [] + elif rVal == 3: + retList = [] + for x in range(1,4): + retList.append([rVal-x, cVal]) + if self.board[rVal-x][cVal] != playerNum: + retList = [] + break + if self.board[rVal-x][cVal] == playerNum and x == 3: + return retList + return [] + else: + return [] + def findDiagonal(self, rVal, cVal, playerNum): + retList = [] + if cVal <= 2: #Cannot have Left Diagonals + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + retList.append([rVal+x , cVal+x]) + if self.board[rVal+x][cVal+x] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return retList + return [] + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + retList.append([rVal-x , cVal+x]) + if self.board[rVal-x][cVal+x] != playerNum: + retList = [] + break + if self.board[rVal-x][cVal+x] == playerNum and x == 3: + return retList + return [] + elif cVal >= 4: + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + retList.append([rVal+x , cVal-x]) + if self.board[rVal+x][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return retList + return [] + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + retList.append([rVal-x , cVal-x]) + if self.board[rVal-x][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return retList + return [] + else: #we are in column 3 + if rVal == 3 or rVal == 4 or rVal == 5: #we have 2 upward Diagonals + for x in range(1,4):#Up left + retList.append([rVal-x , cVal-x]) + if self.board[rVal-x][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return retList + for x in range(1,4): + retList.append([rVal+x , cVal-x]) + if self.board[rVal+x][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return retList + return [] + elif rVal == 0 or rVal == 1 or rVal == 2: + for x in range(1,4):#down left + retList.append([rVal+x , cVal-x]) + if self.board[rVal+x][cVal-x] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return retList + for x in range(1,4): + retList.append([rVal+x , cVal+x]) + if self.board[rVal+x][cVal+x] != playerNum: + retList = [] + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return retList + return [] + return [] diff --git a/toontown/src/safezone/DistributedFindFourAI.py b/toontown/src/safezone/DistributedFindFourAI.py new file mode 100644 index 0000000..63b2af8 --- /dev/null +++ b/toontown/src/safezone/DistributedFindFourAI.py @@ -0,0 +1,538 @@ +from direct.distributed.DistributedNodeAI import DistributedNodeAI +from direct.distributed.ClockDelta import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * + + + +class DistributedFindFourAI(DistributedNodeAI): + def __init__(self, air, parent, name, x, y, z, h, p, r): + DistributedNodeAI.__init__(self,air) + self.name = name + self.air = air + + self.setPos(x,y,z) + self.setHpr(h,p,r) + + self.myPos = (x,y,z) + self.myHpr = (h,p,r) + self.board = [ [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0], + [0,0,0,0,0,0,0] ] + + self.parent = self.air.doId2do[parent] + self.parentDo = parent + self.wantStart = [] + self.playersPlaying = [] + self.playersSitting = 0 + self.playersTurn = 1 + self.movesMade = 0 + self.playerNum = 1 + self.winDirection = None + + #players game positions (separate from where they are sitting down + self.playersGamePos = [None, None] + + self.wantTimer = True + self.timerEnd = 0 + self.turnEnd = 0 + + self.playersObserving = [] + #Arbitrary numbers - can Adjust to feel + self.winLaffPoints = 20 + self.movesRequiredToWin = 10 + + self.zoneId = self.air.allocateZone() + self.generateOtpObject(air.districtId, self.zoneId, optionalFields = ["setX","setY","setZ", "setH", "setP", "setR"]) + #sets the ZoneId in the parent table so when people sit down they know what zone to add + #interest to when they sit down + self.parent.setCheckersZoneId(self.zoneId) + + #parent.b_setCurrentGameId(self.doId) + + #Starting indexes inside of board for setting starting positions + + self.timerStart = None + + + self.fsm = ClassicFSM.ClassicFSM('Checkers', + [State.State('waitingToBegin', + self.enterWaitingToBegin, + self.exitWaitingToBegin, + ['playing']), + State.State('playing', + self.enterPlaying, + self.exitPlaying, + ['gameOver']), + State.State('gameOver', + self.enterGameOver, + self.exitGameOver, + ['waitingToBegin'])], + #start state + 'waitingToBegin', + #final state + 'waitingToBegin', + ) + + + + self.fsm.enterInitialState() + #self.setGameCountownTime() + + def announceGenerate(self): + self.parent.setGameDoId(self.doId) + def getTableDoId(self): + return self.parentDo + def delete(self): + self.fsm.requestFinalState() + self.parent = None + self.parentDo = None + del self.board + del self.fsm + DistributedNodeAI.delete(self) + + + ######################################################################### + ##Inform of player and leave + # + #These functions are called by the table in the event of a player leave/join + #this is necessary so that the game may have an accurate count of who is in the table + #as well as a function that will fire upon single "deltas" of the table state + # + #Table state with avId(s) will be updated via setTableState as well as this + #function being called + #### + def informGameOfPlayer(self): + self.playersSitting += 1 + if self.playersSitting < 2: + self.timerEnd = 0 + elif self.playersSitting == 2: + self.timerEnd = globalClock.getRealTime() + 20 + self.parent.isAccepting = False + self.parent.sendUpdate("setIsPlaying", [1]) # 1 is true + elif self.playersSitting > 2: + pass + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + + def informGameOfPlayerLeave(self): + self.playersSitting -= 1 + if self.playersSitting < 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + self.timerEnd = 0 + self.parent.isAccepting = True + self.parent.sendUpdate("setIsPlaying", [0]) # 1 is true + if self.playersSitting > 2 and self.fsm.getCurrentState().getName() == 'waitingToBegin': + pass + else: + self.timerEnd = 0 + if self.timerEnd != 0: + self.sendUpdate("setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + else: + self.sendUpdate("setTimer", [0]) + ########################################################################### + ##Timer Functions for game + # + #These timer functions encapsulate the "timer" functions needed for the game table. + # getTimer - is the timer for sitting down and beginning the game + # getTurnTimer - gets and sets the turn timer for players moves + ### + def setGameCountdownTime(self): + self.timerEnd = globalClock.getRealTime() + 10 + def setTurnCountdownTime(self): + self.turnEnd = globalClock.getRealTime() + 25 + def getTimer(self): + if self.timerEnd != 0: + return 0 + else: + return 0 + def getTurnTimer(self): + return globalClockDelta.localToNetworkTime(self.turnEnd) + def requestTimer(self): + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, "setTimer", [globalClockDelta.localToNetworkTime(self.timerEnd)]) + ########################################################################### + ##handlePlayerExit and handleEmptyGame + # + #handlePlayerExit is the mother function that gets called when a player requests to leave an "active" game + #of chinese checkers - there are many special cases here, comments inside the function itself. + ### + + def handlePlayerExit(self, avId): + if avId in self.wantStart: + self.wantStart.remove(avId) + if self.fsm.getCurrentState().getName() == 'playing': + #Get the players Current "player" position in Chinese Checkers + gamePos = self.playersGamePos.index(avId) + self.playersGamePos[gamePos] = None + self.fsm.request('gameOver') + + def handleEmptyGame(self): + self.movesMade = 0 + self.playersPlaying = [] + self.playersTurn = 1 + self.playerNum = 1 + self.fsm.request('waitingToBegin') + self.parent.isAccepting = True + ############################################################### + ##Request win and distributed Laugh points + # + #This function explicitly tests for a player (that requested he won) + #and checks to see if his peices equal that of his opposite + ## + def requestWin(self, pieceNum): + avId = self.air.getAvatarIdFromSender() + playerNum = self.playersGamePos.index(avId)+1 + x = pieceNum[0] + y = pieceNum[1] + if self.checkWin(x,y,playerNum) == True: + self.sendUpdate("announceWinnerPosition", [x,y,self.winDirection,playerNum]) + winnersSequence = Sequence(Wait(5.0), Func(self.fsm.request, 'gameOver'),Func(self.parent.announceWinner, "Find Four", avId)) + winnersSequence.start() + else: + self.sendUpdateToAvatarId(avId, "illegalMove", []) + + + + def distributeLaffPoints(self): + for x in self.parent.seats: + if x != None: + av = self.air.doId2do.get(x) + av.toonUp(self.winLaffPoints) + + ################################################################## + # Find Four FSM + # + #The FSM is primarily to keep track of buttons/screen text, but also + #serves the purpose of resetting variables upon game over ect. + ### + def enterWaitingToBegin(self): + self.setGameCountdownTime() + self.parent.isAccepting = True + + def exitWaitingToBegin(self): + self.turnEnd = 0 + def enterPlaying(self): + self.parent.isAccepting = False + for x in self.playersGamePos: + if x != None: + self.playersTurn = self.playersGamePos.index(x) + self.d_sendTurn(self.playersTurn+1) + break + + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + def exitPlaying(self): + pass + def enterGameOver(self): + #self.playersObserving = [] + self.timerEnd = 0 + isAccepting = True + self.parent.handleGameOver() + self.playersObserving = [] + self.playersTurn = 1 + self.playerNum = 1 + self.playersPlaying = [] + #self.clearBoard() + #self.sendGameState( [] ) + self.movesMade = 0 + self.playersGamePos = [None, None] + self.parent.isAccepting = True + self.fsm.request('waitingToBegin') + def exitGameOver(self): + pass + + ################################################################## + # Game Start/begin logic + #Game start just assigns people as player1 or player0 + # + #Request begin is sent by every avatar when either (1) they click the go button + #or when their start timer runs out + ### + def requestBegin(self): + avId = self.air.getAvatarIdFromSender() + if not( avId in self.wantStart ): + self.wantStart.append(avId) + numPlayers = 0 + for x in self.parent.seats: + if x != None: + numPlayers = numPlayers + 1 + if len(self.wantStart) == numPlayers and numPlayers >= 2: + self.d_gameStart(avId) + self.parent.sendIsPlaying() + def d_gameStart(self,avId): + #255 is a special value just telling the client that he is an Observer + for x in self.playersObserving: + self.sendUpdateToAvatarId(x, "gameStart", [255]) + zz = 0 + numPlayers = 0 + for x in self.parent.seats: + if x != None: + numPlayers += 1 + self.playersPlaying.append(x) + if numPlayers == 2: + player1 = self.playersPlaying[0] + self.sendUpdateToAvatarId(player1, "gameStart", [1]) + self.playersGamePos[0] = player1 + + + player2 = self.playersPlaying[1] + self.sendUpdateToAvatarId(player2, "gameStart", [2]) + self.playersGamePos[1] = player2 + + #self.sendGameState( [] ) + self.wantStart = [] + self.fsm.request('playing') + self.parent.getTableState() + def d_sendTurn(self,playersTurn): + self.sendUpdate("sendTurn", [playersTurn]) + + ################################################################## + # Legal Move Request/Checker + # + #All this simply does is swap the players turn + #no sense really adding and what not since it is + #a two player game. + #### + def advancePlayerTurn(self): + if self.playersTurn == 0: + self.playersTurn = 1 + self.playerNum = 2 + else: + self.playerNum = 1 + self.playersTurn = 0 + ########### + ##requestMove + # + #this is the function that controls all of the checking of logic after the clients submit + #moves for them to make + # + #read code for functionality + ### + + def requestMove(self, moveColumn): + avId = self.air.getAvatarIdFromSender() + turn = self.playersTurn + if avId in self.playersGamePos: #good hes at least playing + if self.playersGamePos.index(avId) != self.playersTurn: + pass #if hes submitting a move not during his turn DO SOMETHING BAD IN THE FUTURE + if self.board[0][moveColumn] != 0: + self.sendUpdateToAvatarId(avId, "illegalMove", [])# do something bad. Hes trying to fill an alraedy full column SHOULD NEVER EVER HAPPEN the functions on the AI and the client are identical to check for legality + + #find the lowest spot in the column that is empty + #and set the boardstate and movepos to that + for x in range(6): + if self.board[x][moveColumn] == 0: + movePos = x + self.board[movePos][moveColumn] = self.playersTurn +1 + + #check for a tie, and if so distribute it and return + if self.checkForTie() == True: + self.sendUpdate('setGameState', [self.board, moveColumn, movePos, turn]) + self.sendUpdate("tie", []) + winnersSequence = Sequence(Wait(8.0), Func(self.fsm.request, 'gameOver')) + winnersSequence.start() + return + + self.movesMade += 1 + self.advancePlayerTurn() + self.setTurnCountdownTime() + self.sendUpdate("setTurnTimer", [globalClockDelta.localToNetworkTime(self.turnEnd)]) + self.d_sendTurn(self.playersTurn + 1) + + + self.sendUpdate('setGameState', [self.board, moveColumn, movePos, turn]) + + def checkForTie(self): + for x in range(7): + if self.board[0][x] == 0: + return False + return True + +################### + def getState(self): + return self.fsm.getCurrentState().getName() + + def getName(self): + return self.name + + + def getGameState(self):\ + return [self.board, 0,0,0 ] + + + + def clearBoard(self): + for x in self.board.squareList: + x.setState(0) + + + def getPosHpr(self): + return self.posHpr + + def tempSetBoardState(self): + + self.board = [ [0,0,0,0,0,0,0], + [1,2,1,2,2,2,1], + [2,2,1,2,1,2,1], + [2,1,1,2,2,1,2], + [1,2,2,1,2,1,1], + [1,2,1,2,1,2,1] ] + self.sendUpdate('setGameState', [self.board, 0,0, 1]) + + ########## + ##Large slew of check/find Functions + # + #I would suggest getting out paper to analyze how these things work, but hopefully + #with a bit of ASCII art and some explaining it wont be too bad. + # + # Game board is represented as such in memory (as you see it in real life): + # |0 0 0 0 0 0 0 | 0 1 2 3 4 5 6 + # |0 0 0 0 0 0 0 | 0 . . . . . . . + # |0 0 0 0 0 0 0 | 1 . . . . . . . + # |0 0 0 0 0 0 0 | 2 . . . . . . . + # |0 0 0 0 0 0 0 | 3 . . . . . . . + # |0 0 0 0 0 0 0 | 4 . . . . . . . + # 5 . . . . . . . + # + # + # as you can see, (0,0) is in the top left corner, and just makes things easier to write about + #because the locators, and lists seem to function well in this sense + # board state looks like [ [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0], + # [0,0,0,0,0,0,0] ] + # + # + #So when looking at this code just keep in mind this is how the board is working in memory + # + #Also, important to see that there are two types of functions here + #(1) the check(direction) functions who return true or false whether or not they detect + # a win in that direction + #(2) the find(direction) functions who return the list of where they detected a win + # or [ ] if they found nothing. + ## + + def checkWin(self, rVal, cVal, playerNum): + if self.checkHorizontal(rVal,cVal,playerNum) == True: + self.winDirection = 0 + return True + elif self.checkVertical(rVal,cVal,playerNum) == True: + self.winDirection = 1 + return True + elif self.checkDiagonal(rVal,cVal,playerNum) == True: + self.winDirection = 2 + return True + else: + self.winDirection = None + return False + def checkHorizontal(self, rVal, cVal, playerNum): + if cVal == 3: + for x in range(1,4): + if self.board[rVal][cVal-x] != playerNum: + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal][cVal + x] != playerNum: + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal == 2: + for x in range(1,4): + if self.board[rVal][cVal + x] != playerNum: + break + if self.board[rVal][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal == 4: + for x in range(1,4): + if self.board[rVal][cVal-x] != playerNum: + break + if self.board[rVal][cVal-x] == playerNum and x == 3: + return True + return False + else: + return False + def checkVertical(self, rVal, cVal, playerNum): + if rVal == 2: + for x in range(1,4): + if self.board[rVal+x][cVal] != playerNum: + break + if self.board[rVal+x][cVal] == playerNum and x == 3: + return True + return False + elif rVal == 3: + for x in range(1,4): + if self.board[rVal-x][cVal] != playerNum: + break + if self.board[rVal-x][cVal] == playerNum and x == 3: + return True + return False + else: + return False + def checkDiagonal(self, rVal, cVal, playerNum): + if cVal <= 2: #Cannot have Left Diagonals + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + if self.board[rVal+x][cVal+x] != playerNum: + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return True + return False + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + if self.board[rVal-x][cVal+x] != playerNum: + break + if self.board[rVal-x][cVal+x] == playerNum and x == 3: + return True + return False + elif cVal >= 4: + if rVal == 2:#cannot have upper Diagonal + for x in range(1,4): + if self.board[rVal+x][cVal-x] != playerNum: + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return True + return False + elif rVal == 3: #cannot have downard diagonal + for x in range(1,4): + if self.board[rVal-x][cVal-x] != playerNum: + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return True + return False + else: #we are in column 3 + if rVal == 3 or rVal == 4 or rVal == 5: #we have 2 upward Diagonals + for x in range(1,4):#Up left + if self.board[rVal-x][cVal-x] != playerNum: + break + if self.board[rVal-x][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal+x][cVal-x] != playerNum: + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return True + return False + elif rVal == 0 or rVal == 1 or rVal == 2: + for x in range(1,4):#down left + if self.board[rVal+x][cVal-x] != playerNum: + break + if self.board[rVal+x][cVal-x] == playerNum and x == 3: + return True + for x in range(1,4): + if self.board[rVal+x][cVal+x] != playerNum: + break + if self.board[rVal+x][cVal+x] == playerNum and x == 3: + return True + return False + return False + diff --git a/toontown/src/safezone/DistributedFishingSpot.py b/toontown/src/safezone/DistributedFishingSpot.py new file mode 100644 index 0000000..cea65a6 --- /dev/null +++ b/toontown/src/safezone/DistributedFishingSpot.py @@ -0,0 +1,1641 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.directtools.DirectGeometry import LineNodePath + +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.fishing import FishGlobals +from toontown.shtiker import FishPage +from toontown.toonbase import TTLocalizer +from toontown.quest import Quests +from direct.actor import Actor +from direct.showutil import Rope +import math +from direct.task.Task import Task +import random +import random +from toontown.fishing import FishingTargetGlobals +from toontown.fishing import FishBase +from toontown.fishing import FishPanel +from toontown.effects import Ripples +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownTimer +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + +class DistributedFishingSpot(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedFishingSpot") + + # Parameters to control bob motion + vZeroMax = 25.0 + angleMax = 30.0 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + assert self.notify.debugStateCall(self) + self.lastAvId = 0 + self.lastFrame = 0 + self.avId = 0 + self.av = None + self.placedAvatar = 0 + self.localToonFishing = 0 + self.nodePath = None + self.collSphere = None + self.collNode = None + self.collNodePath = None + self.castTrack = None + self.pond = None + + self.guiTrack = None + + self.madeGui = 0 + self.castGui = None + self.itemGui = None + self.pole = None + self.line = None + self.poleNode = [] + self.ptop = None + self.bob = None + + self.bobBobTask = None + self.splashSounds = None + self.ripples = None + + self.line = None + self.lineSphere = None + + self.power = 0.0 + self.startAngleNP = 0 + self.firstCast = 1 + self.fishPanel = None + + self.fsm = ClassicFSM.ClassicFSM('DistributedFishingSpot', + [State.State('off', + self.enterOff, self.exitOff, + ['waiting', 'distCasting', 'fishing', 'reward', 'leaving']), + State.State('waiting', + self.enterWaiting, self.exitWaiting, + ['localAdjusting', 'distCasting', 'leaving', 'sellFish']), + State.State('localAdjusting', + self.enterLocalAdjusting, self.exitLocalAdjusting, + ['localCasting', 'leaving']), + State.State('localCasting', + self.enterLocalCasting, self.exitLocalCasting, + ['localAdjusting', 'fishing', 'leaving']), + # The transition to reward is kinda strange. I'm not sure + # exactly why that is happening in the release. Need to look + # into it + State.State('distCasting', + self.enterDistCasting, self.exitDistCasting, + ['fishing', 'leaving', 'reward']), + # Fishing needs to be able to go directly to reward + # for distributed toons that we are watching fish + State.State('fishing', + self.enterFishing, self.exitFishing, + ['localAdjusting', 'distCasting', 'waitForAI', 'reward', 'leaving']), + State.State('sellFish', + self.enterSellFish, self.exitSellFish, + ['waiting', 'leaving']), + State.State('waitForAI', + self.enterWaitForAI, self.exitWaitForAI, + ['reward', 'leaving']), + State.State('reward', + self.enterReward, self.exitReward, + ['localAdjusting', 'distCasting', 'leaving', 'sellFish']), + State.State('leaving', + self.enterLeaving, self.exitLeaving, + []), + ], 'off', 'off',) + self.fsm.enterInitialState() + + def disable(self): + assert self.notify.debugStateCall(self) + self.ignore(self.uniqueName('enterFishingSpotSphere')) + self.setOccupied(0) + self.avId = 0 + if self.castTrack != None: + if self.castTrack.isPlaying(): + self.castTrack.finish() + self.castTrack = None + if self.guiTrack != None: + if self.guiTrack.isPlaying(): + self.guiTrack.finish() + self.guiTrack = None + self.__hideBob() + self.nodePath.detachNode() + self.__unmakeGui() + self.pond.stopCheckingTargets() + self.pond = None + DistributedObject.DistributedObject.disable(self) + + def delete(self): + assert self.notify.debugStateCall(self) + del self.pond + del self.fsm + if self.nodePath: + self.nodePath.removeNode() + del self.nodePath + DistributedObject.DistributedObject.delete(self) + if self.ripples: + self.ripples.destroy() + + def generateInit(self): + """ + This method is called when the DistributedObject is first introduced + to the world... Not when it is pulled from the cache. + """ + assert self.notify.debugStateCall(self) + DistributedObject.DistributedObject.generateInit(self) + # First, one NodePath to represent the spot itself. This gets + # repositioned around according to our setPos. + self.nodePath = NodePath(self.uniqueName('FishingSpot')) + self.angleNP = self.nodePath.attachNewNode(self.uniqueName('FishingSpotAngleNP')) + + # Make a collision sphere to detect when an avatar enters the + # fishing spot. + self.collSphere = CollisionSphere(0, 0, 0, self.getSphereRadius()) + + # Make the sphere intangible, initially. + self.collSphere.setTangible(0) + self.collNode = CollisionNode(self.uniqueName('FishingSpotSphere')) + self.collNode.setCollideMask(ToontownGlobals.WallBitmask) + self.collNode.addSolid(self.collSphere) + self.collNodePath = self.nodePath.attachNewNode(self.collNode) + + self.bobStartPos = Point3(0.0, 3.0, 8.5) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + assert self.notify.debugStateCall(self) + DistributedObject.DistributedObject.generate(self) + + def announceGenerate(self): + assert self.notify.debugStateCall(self) + DistributedObject.DistributedObject.announceGenerate(self) + # Now that posHpr has been set, reparent to render + # and accept the sphere event + # Put the fishing spot in the world + self.nodePath.reparentTo(self.getParentNodePath()) + + # When the localToon steps onto the fishing spot, we call + # requestEnter. + self.accept(self.uniqueName('enterFishingSpotSphere'), + self.__handleEnterSphere) + + def setPondDoId(self, pondDoId): + assert self.notify.debugStateCall(self) + self.pond = base.cr.doId2do[pondDoId] + self.area = self.pond.getArea() + self.waterLevel = FishingTargetGlobals.getWaterLevel(self.area) + + def allowedToEnter(self): + """Check if the local toon is allowed to enter.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + # if we're in the estate we should use place.id + if hasattr(place, 'id'): + myHoodId = place.id + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ): + # trialer going to TTC/Estate/Goofy Speedway, let them through + return True + return False + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + def __handleEnterSphere(self, collEntry): + assert self.notify.debugStateCall(self) + if self.allowedToEnter(): + # If the same toon re-enters immediately after exiting, it's + # probably a mistake; ignore it. + assert(self.notify.debug("__handleEnterSphere")) + if base.localAvatar.doId == self.lastAvId and \ + globalClock.getFrameCount() <= self.lastFrame + 1: + self.notify.debug("Ignoring duplicate entry for avatar.") + return + # Only toons with hp > 0 that aren't already fishing can fish + if (base.localAvatar.hp > 0) and (base.cr.playGame.getPlace().fsm.getCurrentState().getName() != "fishing"): + # Take them the localToon out of walk mode + self.cr.playGame.getPlace().detectedFishingCollision() + self.d_requestEnter() + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='fishing', + doneFunc=self.handleOkTeaser) + + def d_requestEnter(self): + assert self.notify.debugStateCall(self) + self.sendUpdate("requestEnter", []) + + def rejectEnter(self): + assert self.notify.debugStateCall(self) + # Take them the localToon out of walk mode + self.cr.playGame.getPlace().setState("walk") + + def d_requestExit(self): + assert self.notify.debugStateCall(self) + self.sendUpdate("requestExit", []) + + def d_doCast(self, power, heading): + assert self.notify.debugStateCall(self) + self.sendUpdate("doCast", [power, heading]) + + def getSphereRadius(self): + assert self.notify.debugStateCall(self) + return 1.5 + + def getParentNodePath(self): + assert self.notify.debugStateCall(self) + return render + + def setPosHpr(self, x, y, z, h, p, r): + """ + The handler that catches the initial position and orientation + established on the AI. + """ + assert self.notify.debugStateCall(self) + self.nodePath.setPosHpr(x, y, z, h, p, r) + self.angleNP.setH(render, self.nodePath.getH(render)) + + def setOccupied(self, avId): + assert self.notify.debugStateCall(self) + if self.av != None: + if not self.av.isEmpty(): + self.__dropPole() + self.av.loop('neutral') + self.av.setParent(ToontownGlobals.SPRender) + self.av.startSmooth() + self.ignore(self.av.uniqueName("disable")) + self.__hideBob() + self.fsm.requestFinalState() + self.__removePole() + self.av = None + self.placedAvatar = 0 + self.angleNP.setH(render, self.nodePath.getH(render)) + self.__hideLine() + + wasLocalToon = self.localToonFishing + + self.lastAvId = self.avId + self.lastFrame = globalClock.getFrameCount() + self.avId = avId + self.localToonFishing = 0 + + if self.avId == 0: + # No one is in the fishing spot; it's available. + self.collSphere.setTangible(0) + else: + # The fishing spot is occupied; no one else may be here. + self.collSphere.setTangible(1) + if self.avId == base.localAvatar.doId: + # Free up all of the nametag cells on the bottom edge + # of the screen to leave room for the fishing gui. + base.setCellsAvailable(base.bottomCells, 0) + + self.localToonFishing = 1 + + if base.wantBingo: + # Set the Pond localToonSpot Reference to 'this instance' of + # a fishing spot because the local toon is now fishing at + # a spot. It was not enough to set and unset the reference + # when checking the fish targets because Bingo needs to be + # able to access the spot GUI when the local Toon is not + # in the 'fishing' state. (JJT - 06/23/04) + self.pond.setLocalToonSpot(self) + + av = self.cr.doId2do.get(self.avId) + if av: + self.av = av + self.__loadStuff() + self.placedAvatar = 0 + self.firstCast = 1 + self.acceptOnce(self.av.uniqueName("disable"), self.__avatarGone) + # Parent it to the fishing spot + # perhaps we need to keep smoothing on? + self.av.stopSmooth() + self.av.wrtReparentTo(self.angleNP) + self.av.setAnimState("neutral", 1.0) + self.createCastTrack() + else: + self.notify.warning("Unknown avatar %d in fishing spot %d" % (self.avId, self.doId)) + + # If the local toon was involved but is no longer, restore + # walk mode. We do this down here, after we have twiddled + # with the tangible flag, so that the toon must walk out and + # walk back in again in order to generate the enter event + # again. + if wasLocalToon and not self.localToonFishing: + self.__hideCastGui() + + if base.wantBingo: + # Reset the Pond FishingSpot Reference to None now that the local + # toon is no longer involved with the spot. (JJT - 06/23/04) + self.pond.setLocalToonSpot() + + # Restore the normal nametag cells. + base.setCellsAvailable([base.bottomCells[1], base.bottomCells[2]], 1) + base.setCellsAvailable(base.rightCells, 1) + # Reset to walk mode, but not if we're exiting all the way + # out (and our place is already gone). + place = base.cr.playGame.getPlace() + if place: + place.setState('walk') + + def __avatarGone(self): + assert self.notify.debugStateCall(self) + # Called when the avatar in the fishing spot vanishes. + # The AI will call setOccupied(0), too, but we call it first + # just to be on the safe side, so we don't try to access a + # non-existent avatar. + self.setOccupied(0) + + def setMovie(self, mode, code, itemDesc1, itemDesc2, itemDesc3, power, h): + assert self.notify.debugStateCall(self) + if self.av == None: + # No avatar, no movie + return + + if mode == FishGlobals.NoMovie: + pass + elif mode == FishGlobals.EnterMovie: + self.fsm.request("waiting") + elif mode == FishGlobals.ExitMovie: + self.fsm.request("leaving") + elif mode == FishGlobals.CastMovie: + # Note: this message is only really used for dist toons + if not self.localToonFishing: + self.fsm.request("distCasting", [power, h]) + elif mode == FishGlobals.PullInMovie: + self.fsm.request("reward", [code, itemDesc1, itemDesc2, itemDesc3]) + + def getStareAtNodeAndOffset(self): + # spammy: assert self.notify.debugStateCall(self) + return self.nodePath, Point3() + + def __loadStuff(self): + assert self.notify.debugStateCall(self) + # The rod index is a required broadcast field on the toon, so we + # should know what everybody's rod index is + rodId = self.av.getFishingRod() + rodPath = FishGlobals.RodFileDict.get(rodId) + if not rodPath: + self.notify.warning("Rod id: %s model not found" % (rodId)) + # Just use the 0 index rod + rodPath = RodFileDict[0] + + self.pole = Actor.Actor() + self.pole.loadModel(rodPath) + # All rods use the same animation + self.pole.loadAnims({'cast' : 'phase_4/models/props/fishing-pole-chan'}) + self.pole.pose('cast', 0) + # Get the top of the pole. + self.ptop = self.pole.find('**/joint_attachBill') + + if self.line == None: + # Make a Rope object to show the fishing line. + self.line = Rope.Rope(self.uniqueName('Line')) + self.line.setColor(1, 1, 1, 0.4) + self.line.setTransparency(1) + # This is the bounding sphere that will be set on the line. + # We don't trust the line to compute its own bounding sphere + # since we'll be moving it around a lot (and plus it's under + # multiple instances). + self.lineSphere = BoundingSphere(Point3(-0.6, -2, -5), 5.5) + + if self.bob == None: + self.bob = loader.loadModel('phase_4/models/props/fishing_bob') + self.bob.setScale(1.5) + self.ripples = Ripples.Ripples(self.nodePath) + self.ripples.setScale(0.4) + self.ripples.hide() + + if self.splashSounds == None: + self.splashSounds = (base.loadSfx('phase_4/audio/sfx/TT_splash1.mp3'), + base.loadSfx('phase_4/audio/sfx/TT_splash2.mp3'), + ) + + def __placeAvatar(self): + assert self.notify.debugStateCall(self) + # Places the avatar at the fishing spot, mainly for the + # benefit of those who did not observe the EnterMovie. + if not self.placedAvatar: + self.placedAvatar = 1 + self.__holdPole() + self.av.setPosHpr(0, 0, 0, 0, 0, 0) + + def __holdPole(self): + assert self.notify.debugStateCall(self) + if self.poleNode != []: + self.__dropPole() + # One node, instanced to each of the toon's three right hands, + # will hold the pole. + np = NodePath('pole-holder') + hands = self.av.getRightHands() + for h in hands: + self.poleNode.append(np.instanceTo(h)) + self.pole.reparentTo(self.poleNode[0]) + + def __dropPole(self): + assert self.notify.debugStateCall(self) + self.__hideBob() + self.__hideLine() + if self.pole != None: + self.pole.clearMat() + self.pole.detachNode() + for pn in self.poleNode: + pn.removeNode() + self.poleNode = [] + + def __removePole(self): + assert self.notify.debugStateCall(self) + self.pole.removeNode() + self.poleNode = [] + self.ptop.removeNode() + self.pole = None + self.ptop = None + + def __showLineWaiting(self): + assert self.notify.debugStateCall(self) + # Show the fishing line, waiting for a nibble. + self.line.setup(4, ((None, (0, 0, 0)), (None, (0, -2, -4)), + (self.bob, (0, -1, 0)), (self.bob, (0, 0, 0)))) + self.line.ropeNode.setBounds(self.lineSphere) + self.line.reparentTo(self.ptop) + + def __showLineCasting(self): + assert self.notify.debugStateCall(self) + # Show the fishing line, waiting for a nibble. + self.line.setup(2, ((None, (0, 0, 0)), + (self.bob, (0, 0, 0)))) + self.line.ropeNode.setBounds(self.lineSphere) + self.line.reparentTo(self.ptop) + + def __showLineReeling(self): + assert self.notify.debugStateCall(self) + # Show the fishing line, waiting for a nibble. + self.line.setup(2, ((None, (0, 0, 0)), + (self.bob, (0, 0, 0)))) + self.line.ropeNode.setBounds(self.lineSphere) + self.line.reparentTo(self.ptop) + + def __hideLine(self): + assert self.notify.debugStateCall(self) + if self.line: + # Hide the fishing line. + self.line.detachNode() + + def __showBobFloat(self): + assert self.notify.debugStateCall(self) + # Put the bob in the water and make it gently float. + self.__hideBob() + self.bob.reparentTo(self.angleNP) + # Ripple effect + self.ripples.reparentTo(self.angleNP) + self.ripples.setPos(self.bob.getPos()) + self.ripples.setZ(self.waterLevel + 0.025) + self.ripples.play() + # Splash sfx + splashSound = random.choice(self.splashSounds) + base.playSfx(splashSound, volume=0.8, node=self.bob) + self.bobBobTask = taskMgr.add(self.__doBobBob, self.taskName('bob')) + + def __hideBob(self): + assert self.notify.debugStateCall(self) + if self.bob: + self.bob.detachNode() + if self.bobBobTask: + taskMgr.remove(self.bobBobTask) + self.bobBobTask = None + if self.ripples: + self.ripples.stop() + self.ripples.detachNode() + + def __doBobBob(self, task): + assert self.notify.debugStateCall(self) + # Task to make the bob bounce up and down as if it is + # floating, but has not yet had a nibble. + z = math.sin(task.time * 1.8) * 0.08 + self.bob.setZ(self.waterLevel + z) + return Task.cont + + def __userExit(self, event=None): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.fsm.request("leaving") + self.d_requestExit() + + def __sellFish(self, result=None): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + if result == DGG.DIALOG_OK: + # send the update to the ai, and disable the buttons + # so they can't multi-click + self.sendUpdate("sellFish", []) + for button in self.sellFishDialog.buttonList: + button['state'] = DGG.DISABLED + else: + # since we only allow them to sell if their bucket is full, + # if they cancelled, kick them out of fishing + self.fsm.request("leaving") + self.d_requestExit() + + def __sellFishConfirm(self, result = None): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.fsm.request("waiting", [False]) + + def __showCastGui(self): + assert self.notify.debugStateCall(self) + self.__hideCastGui() + self.__makeGui() + self.castButton.show() + self.arrow.hide() + self.exitButton.show() + self.timer.show() + self.__updateFishTankGui() + self.castGui.reparentTo(aspect2d) + self.castButton['state'] = DGG.NORMAL + self.jar['text'] = str(self.av.getMoney()) + + self.accept(localAvatar.uniqueName("moneyChange"), self.__moneyChange) + self.accept(localAvatar.uniqueName("fishTankChange"), self.__updateFishTankGui) + + # Hide the cannon game GUI if present + target = base.cr.doFind("DistributedTarget") + if target: + target.hideGui() + + # Should guard this for publish + if base.wantBingo: + self.__setBingoCastGui() + + # I need to make these helper funcs because the event from bind + # adds a mouse parameter onto the message that screws up the fsm request + def requestLocalAdjusting(mouseEvent): + if self.av.isFishTankFull() and self.__allowSellFish(): + self.fsm.request("sellFish") + else: + self.fsm.request("localAdjusting") + def requestLocalCasting(mouseEvent): + if not (self.av.isFishTankFull() and self.__allowSellFish()): + self.fsm.request("localCasting") + + # Lets go ahead and bind both left and right mouse buttons + # so nobody is confused + self.castButton.bind(DGG.B1PRESS, requestLocalAdjusting) + self.castButton.bind(DGG.B3PRESS, requestLocalAdjusting) + self.castButton.bind(DGG.B1RELEASE, requestLocalCasting) + self.castButton.bind(DGG.B3RELEASE, requestLocalCasting) + if (self.firstCast and + (len(self.av.fishCollection) == 0) and + (len(self.av.fishTank) == 0)): + self.__showHowTo(TTLocalizer.FishingHowToFirstTime) + elif (base.wantBingo and + self.pond.hasPondBingoManager() and + not self.av.bFishBingoTutorialDone): + self.__showHowTo(TTLocalizer.FishBingoHelpMain) + self.av.b_setFishBingoTutorialDone(True) + + + def __moneyChange(self, money): + self.jar["text"] = str(money) + + def __initCastGui(self): + assert self.notify.debugStateCall(self) + self.timer.countdown(FishGlobals.CastTimeout) + + def __showQuestItem(self, itemId): + assert self.notify.debugStateCall(self) + # Tells the user what quest item he just caught. + self.__makeGui() + itemName = Quests.getItemName(itemId) + self.itemLabel['text'] = itemName + self.itemGui.reparentTo(aspect2d) + self.itemPackage.show() + self.itemJellybean.hide() + self.itemBoot.hide() + + def __showBootItem(self): + assert self.notify.debugStateCall(self) + # Tells the user he found an old boot + self.__makeGui() + itemName = TTLocalizer.FishingBootItem + self.itemLabel['text'] = itemName + self.itemGui.reparentTo(aspect2d) + self.itemBoot.show() + self.itemJellybean.hide() + self.itemPackage.hide() + + ##################################################### + # Method: __setItemLabel + # Purpose: This method sets the text of the + # boot item panel. If Bingo night is ongoing + # then it should inform the players that the + # boot is a positive wildcard. + # Input: None + # Output: None + ##################################################### + def __setItemLabel(self): + if self.pond.hasPondBingoManager(): + self.itemLabel['text'] = str(itemName + '\n\n' + 'BINGO WILDCARD') + else: + self.itemLabel['text'] = itemName + + def __showJellybeanItem(self, amount): + assert self.notify.debugStateCall(self) + # Tells the user what jellybean amount he just caught. + self.__makeGui() + itemName = TTLocalizer.FishingJellybeanItem % amount + self.itemLabel['text'] = itemName + self.itemGui.reparentTo(aspect2d) + # By now, the avatar's actual money should be updated + # So let's just query it again to update the jar text + self.jar['text'] = str(self.av.getMoney()) + self.itemJellybean.show() + self.itemBoot.hide() + self.itemPackage.hide() + + def __showFishItem(self, code, fish): + assert self.notify.debugStateCall(self) + # Tells the user what fish item he just caught. + self.fishPanel = FishPanel.FishPanel(fish) + self.__setFishItemPos() + # This is carefully placed over the window image. Please try to keep + # this in sync with the window position: + # (tip: FishPicker.py uses the same bounds for its fish dialog. OK, + # maybe they should pull from the same variable; fix it if you like): + #self.fishPanel.setSwimBounds(-0.29, 0.29, -0.23, 0.25) + self.fishPanel.setSwimBounds(-0.3, 0.3, -0.235, 0.25) + # Parchment paper background: + self.fishPanel.setSwimColor(1.0, 1.0, 0.74901, 1.0) + self.fishPanel.load() + self.fishPanel.show(code) + self.__updateFishTankGui() + + ##################################################### + # Method: __setFishItemPos + # Purpose: This class sets the position of the Fish + # panel based on whether Bingo Night is + # occuring. + # Input: None + # Output: None + ##################################################### + def __setFishItemPos(self): + if base.wantBingo: + if self.pond.hasPondBingoManager(): + self.fishPanel.setPos(0.65, 0, 0.4) + else: + self.fishPanel.setPos(0,0,0.5) + else: + self.fishPanel.setPos(0,0,0.5) + + + def __updateFishTankGui(self): + assert self.notify.debugStateCall(self) + # Update our fish tank display base on the latest value we have from the AI + fishTank = self.av.getFishTank() + lenFishTank = len(fishTank) + maxFishTank = self.av.getMaxFishTank() + self.bucket['text'] = ("%s/%s" % (lenFishTank, maxFishTank)) + + def __showFailureReason(self, code): + assert self.notify.debugStateCall(self) + # Tells the user why he caught nothing. + self.__makeGui() + reason = "" + if code == FishGlobals.OverTankLimit: + reason = TTLocalizer.FishingOverTankLimit + self.failureDialog.setMessage(reason) + self.failureDialog.show() + + def __showSellFishDialog(self): + assert self.notify.debugStateCall(self) + self.__makeGui() + self.sellFishDialog.show() + + def __hideSellFishDialog(self): + assert self.notify.debugStateCall(self) + self.__makeGui() + self.sellFishDialog.hide() + + def __showSellFishConfirmDialog(self, numFishCaught): + assert self.notify.debugStateCall(self) + self.__makeGui() + msg = TTLocalizer.STOREOWNER_TROPHY % (numFishCaught, FishGlobals.getTotalNumFish()) + self.sellFishConfirmDialog.setMessage(msg) + self.sellFishConfirmDialog.show() + + def __hideSellFishConfirmDialog(self): + assert self.notify.debugStateCall(self) + self.__makeGui() + self.sellFishConfirmDialog.hide() + + def __showBroke(self): + assert self.notify.debugStateCall(self) + # Tells the user he is broke + self.__makeGui() + self.brokeDialog.show() + self.castButton['state'] = DGG.DISABLED + + def __showHowTo(self, message): + assert self.notify.debugStateCall(self) + # Tells the user how to fish + self.__makeGui() + self.howToDialog.setMessage(message) + self.howToDialog.show() + + def __hideHowTo(self, event=None): + assert self.notify.debugStateCall(self) + # Hid the howto gui + self.__makeGui() + self.howToDialog.hide() + + def __showFishTankFull(self): + assert self.notify.debugStateCall(self) + # Tells the user why he can't fish. + self.__makeGui() + self.__showFailureReason(FishGlobals.OverTankLimit) + self.castButton['state'] = DGG.DISABLED + + def __hideCastGui(self): + assert self.notify.debugStateCall(self) + + # Show the cannon game GUI if present + target = base.cr.doFind("DistributedTarget") + if target: + target.showGui() + + if self.madeGui: + self.timer.hide() + self.castGui.detachNode() + self.itemGui.detachNode() + self.failureDialog.hide() + self.sellFishDialog.hide() + self.sellFishConfirmDialog.hide() + self.brokeDialog.hide() + self.howToDialog.hide() + self.castButton.unbind(DGG.B1PRESS) + self.castButton.unbind(DGG.B3PRESS) + self.castButton.unbind(DGG.B1RELEASE) + self.castButton.unbind(DGG.B3RELEASE) + + self.ignore(localAvatar.uniqueName("moneyChange")) + self.ignore(localAvatar.uniqueName("fishTankChange")) + + def __itemGuiClose(self): + assert self.notify.debugStateCall(self) + self.itemGui.detachNode() + + def __makeGui(self): + assert self.notify.debugStateCall(self) + if self.madeGui: + return + + self.timer = ToontownTimer.ToontownTimer() + self.timer.posInTopRightCorner() + self.timer.hide() + + self.castGui = loader.loadModel("phase_4/models/gui/fishingGui") + self.castGui.setScale(0.67) + self.castGui.setPos(0,1,0) + + for nodeName in ("bucket", "jar", "display_bucket", "display_jar"): + self.castGui.find("**/" + nodeName).reparentTo(self.castGui) + + self.exitButton = DirectButton( + parent = self.castGui, + relief = None, + text = ("", TTLocalizer.FishingExit, TTLocalizer.FishingExit), + text_align = TextNode.ACenter, + text_scale = 0.1, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + text_pos = (0.0, -0.12), + pos = (1.75, 0, -1.33), + textMayChange = 0, + image = (self.castGui.find("**/exit_buttonUp"), + self.castGui.find("**/exit_buttonDown"), + self.castGui.find("**/exit_buttonRollover")), + command = self.__userExit, + ) + # Get rid of the model we copied from + self.castGui.find("**/exitButton").removeNode() + + self.castButton = DirectButton( + parent = self.castGui, + relief = None, + text = TTLocalizer.FishingCast, + text_align = TextNode.ACenter, + text_scale = (3,3*0.75,3*0.75), + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + text_pos = (0, -4), + image = self.castGui.find("**/castButton"), + image0_color = (1, 0, 0, 1), + image1_color = (0, 1, 0, 1), + image2_color = (1, 1, 0, 1), + image3_color = (0.8, 0.5, 0.5, 1), + pos = (0, -0.05, -0.666), + scale = (0.036, 1, 0.048), + ) + # Get rid of the model we copied from + self.castGui.find("**/castButton").removeNode() + + self.arrow = self.castGui.find("**/arrow") + self.arrowTip = self.arrow.find("**/arrowTip") + self.arrowTail = self.arrow.find("**/arrowTail") + self.arrow.reparentTo(self.castGui) + self.arrow.setColorScale(0.9,0.9,0.1,0.7) + self.arrow.hide() + + self.jar = DirectLabel( + parent = self.castGui, + relief = None, + text = str(self.av.getMoney()), + text_scale = 0.16, + text_fg = (0.95, 0.95, 0, 1), + text_font = ToontownGlobals.getSignFont(), + pos = (-1.12, 0, -1.3), + ) + self.bucket = DirectLabel( + parent = self.castGui, + relief = None, + text = "", + text_scale = 0.09, + text_fg = (0.95, 0.95, 0, 1), + text_shadow = (0, 0, 0, 1), + pos = (1.14, 0, -1.33), + ) + self.__updateFishTankGui() + + # Vector line + self.itemGui = NodePath('itemGui') + self.itemFrame = DirectFrame( + parent = self.itemGui, + relief = None, + geom = DGG.getDefaultDialogGeom(), + geom_color = ToontownGlobals.GlobalDialogColor, + geom_scale = (1, 1, 0.6), + text = TTLocalizer.FishingItemFound, + text_pos = (0, 0.2), + text_scale = 0.08, + pos = (0, 0, 0.587), + ) + + self.itemLabel = DirectLabel( + parent = self.itemFrame, + text = "", + text_scale = 0.06, + pos = (0, 0, -0.25), + ) + + # item gui close button + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + self.itemGuiCloseButton = DirectButton( + parent = self.itemFrame, + pos = (0.44, 0, -0.24), + relief = None, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + image_scale = (0.7, 1, 0.7), + command = self.__itemGuiClose, + ) + buttons.removeNode() + + # Images for the item panels + jarGui = loader.loadModel("phase_3.5/models/gui/jar_gui") + bootGui = loader.loadModel("phase_4/models/gui/fishing_boot") + packageGui = loader.loadModel("phase_3.5/models/gui/stickerbook_gui").find("**/package") + self.itemJellybean = DirectFrame( + parent = self.itemFrame, + relief = None, + image = jarGui, + scale = 0.5, + ) + self.itemBoot = DirectFrame( + parent = self.itemFrame, + relief = None, + image = bootGui, + scale = 0.2, + ) + self.itemPackage = DirectFrame( + parent = self.itemFrame, + relief = None, + image = packageGui, + scale = 0.25, + ) + self.itemJellybean.hide() + self.itemBoot.hide() + self.itemPackage.hide() + + self.failureDialog = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("failureDialog"), + doneEvent = self.uniqueName("failureDialog"), + command = self.__userExit, + message = TTLocalizer.FishingFailure, + style = TTDialog.CancelOnly, + cancelButtonText = TTLocalizer.FishingExit, + ) + self.failureDialog.hide() + + self.sellFishDialog = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("sellFishDialog"), + doneEvent = self.uniqueName("sellFishDialog"), + command = self.__sellFish, + message = TTLocalizer.FishBingoOfferToSellFish, + style = TTDialog.YesNo, + ) + self.sellFishDialog.hide() + + self.sellFishConfirmDialog = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("sellFishConfirmDialog"), + doneEvent = self.uniqueName("sellFishConfirmDialog"), + command = self.__sellFishConfirm, + message = TTLocalizer.STOREOWNER_TROPHY, + style = TTDialog.Acknowledge, + ) + self.sellFishConfirmDialog.hide() + + self.brokeDialog = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("brokeDialog"), + doneEvent = self.uniqueName("brokeDialog"), + command = self.__userExit, + message = TTLocalizer.FishingBroke, + style = TTDialog.CancelOnly, + cancelButtonText = TTLocalizer.FishingExit, + ) + self.brokeDialog.hide() + + self.howToDialog = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("howToDialog"), + doneEvent = self.uniqueName("howToDialog"), + fadeScreen = 0, + message = TTLocalizer.FishingHowToFailed, + style = TTDialog.Acknowledge, + ) + self.howToDialog['command'] = self.__hideHowTo + self.howToDialog.setPos(-0.3,0,0.5) + self.howToDialog.hide() + + self.madeGui = 1 + + ############################################################ + # Method: __setBingoCastGui + # Purpose: This method sets the Jar and Bucket position/scale + # whenever a client enters a fishing spot AFTER + # bingo night has begun. The client will not need + # to see the intro "movie" found in setCastGui + # since bingo night has already begun. + # Input: None + # Output: None + ############################################################ + def __setBingoCastGui(self): + if self.pond.hasPondBingoManager(): + self.notify.debug('__setBingoCastGui: Has PondBing Manager %s'%(self.pond.getPondBingoManager().getDoId())) + + bucket = self.castGui.find("**/bucket") + self.castGui.find("**/display_bucket").reparentTo(bucket) + self.bucket.reparentTo(bucket) + + jar = self.castGui.find("**/jar") + self.castGui.find("**/display_jar").reparentTo(jar) + self.jar.reparentTo(jar) + + base.setCellsAvailable(base.rightCells, 0) + + bucket.setScale(0.9) + bucket.setX(-1.9) + bucket.setZ(-.11) + + jar.setScale(0.9) + jar.setX(-.375) + jar.setZ(-.135) + else: + self.notify.debug('__setItemFramePos: Has No Pond Bingo Manager') + + # need to reset the positions and scales + bucket = self.castGui.find("**/bucket") + bucket.setScale(1) + bucket.setPos(0,0,0) + + jar = self.castGui.find("**/jar") + jar.setScale(1) + jar.setPos(0,0,0) + + + ############################################################ + # Method: resetCastGui + # Purpose: This method resets the jar and bucket positions + # and scaling via an interval. When bingo night + # starts, the bucket moves to the left of the + # screen. So when bingo night ends, we must move + # the bucket back to its original position. + # + # This sequence is done in the event that a toon + # continues to fish at a spot after it ends so + # we must gracefully reset the positions and scaling + # of the bucket and jar. + # Input: None + # Output: None + ############################################################ + def resetCastGui(self): + self.notify.debug('resetCastGui: Bingo Night Ends - resetting Gui') + bucket = self.castGui.find("**/bucket") + jar = self.castGui.find("**/jar") + + bucketPosInt = bucket.posInterval(5.0, Point3(0,0,0), startPos=bucket.getPos(), blendType='easeInOut') + bucketScaleInt = bucket.scaleInterval(5.0, VBase3(1.0, 1.0, 1.0), startScale=bucket.getScale(), blendType='easeInOut') + bucketTrack = Parallel( bucketPosInt, bucketScaleInt ) + + jarPosInt = jar.posInterval(5.0, Point3(0,0,0), startPos=jar.getPos(), blendType='easeInOut') + jarScaleInt = jar.scaleInterval(5.0, VBase3(1.0, 1.0, 1.0), startScale=jar.getScale(), blendType='easeInOut') + jarTrack = Parallel( jarPosInt, jarScaleInt ) + + self.guiTrack = Parallel( bucketTrack, jarTrack ) + self.guiTrack.start() + + ############################################################ + # Method: setCastGui + # Purpose: This method creates and plays an interval for + # moving the cast gui to its appropriate position + # for Bingo Night. Originally, bucket is on the + # far right of the screen, but we move it to the + # far left as well as nudget he jar over a little + # too. + # + # This sequence is done in the event that a toon + # is already fishing at a spot so we must gracefully + # change the positions of the jar and bucket. + # Input: None + # Output: None + ############################################################ + def setCastGui(self): + self.notify.debug('setCastGui: Bingo Night Starts - setting Gui') + + # Should probably do this up in __makeGui. Would reduce redundant code. + # TODO: Move this up to the __makeGui. + bucket = self.castGui.find("**/bucket") + self.castGui.find("**/display_bucket").reparentTo(bucket) + self.bucket.reparentTo(bucket) + + jar = self.castGui.find("**/jar") + self.castGui.find("**/display_jar").reparentTo(jar) + self.jar.reparentTo(jar) + + # Set up track + bucketPosInt = bucket.posInterval(3.0, Point3(-1.9,0,-.11), startPos=bucket.getPos(), blendType='easeInOut') + bucketScaleInt = bucket.scaleInterval(3.0, VBase3(0.9, 0.9, 0.9), startScale=bucket.getScale(), blendType='easeInOut') + bucketTrack = Parallel( bucketPosInt, bucketScaleInt ) + + jarPosInt = jar.posInterval(3.0, Point3(-.375, 0, -.135), startPos=jar.getPos(), blendType='easeInOut') + jarScaleInt = jar.scaleInterval(3.0, VBase3(0.9, 0.9, 0.9), startScale=jar.getScale(), blendType='easeInOut') + jarTrack = Parallel( jarPosInt, jarScaleInt ) + + self.guiTrack = Parallel( bucketTrack, jarTrack ) + self.guiTrack.start() + + ############################################################ + # Method: setJarAmount + # Purpose: This method sets the new jellybean count. + # Input: amount - amount to increase current count by. + # Output: None + ############################################################ + def setJarAmount(self, amount): + if self.madeGui: + money = int(self.jar['text']) + amount + pocketMoney = min(money, self.av.getMaxMoney()) + self.jar.setProp('text', str(pocketMoney)) + + def __unmakeGui(self): + assert self.notify.debugStateCall(self) + if not self.madeGui: + return + self.timer.destroy() + del self.timer + self.exitButton.destroy() + self.castButton.destroy() + self.jar.destroy() + self.bucket.destroy() + self.itemFrame.destroy() + self.itemGui.removeNode() + self.failureDialog.cleanup() + self.sellFishDialog.cleanup() + self.sellFishConfirmDialog.cleanup() + self.brokeDialog.cleanup() + self.howToDialog.cleanup() + self.castGui.removeNode() + self.madeGui = 0 + + def localAdjustingCastTask(self, state): + assert self.notify.debugStateCall(self) + # Get the latest mouse values for this frame + self.getMouse() + deltaX = self.mouseX - self.initMouseX + deltaY = self.mouseY - self.initMouseY + + # If we are above the cast button, basically do nothing - hold still + # You must pull down from the cast button + if deltaY >= 0: + if self.power == 0: + self.arrowTail.setScale(0.075,0.075,0) + self.arrow.setR(0) + self.castTrack.pause() + return Task.cont + + # Calculate the power based on how far back we have pulled the mouse + dist = math.sqrt(deltaX * deltaX + deltaY * deltaY) + delta = (dist/0.5) + self.power = max(min(abs(delta), 1.0), 0.0) + + # Based on the current power reading, pull the rod back + # This is done by setting the T value of the track containing + # that animation + self.castTrack.setT(0.2 + self.power * 0.7) + + # Calculate the angle we are casting at + angle = rad2Deg(math.atan(deltaX/deltaY)) + if self.power < 0.25: + angle = angle * math.pow((self.power * 4), 3) + if delta < 0: + angle += 180 + + # Clamp the angle and update the arrow color + # This also updates the gui arrow if we are clamping + minAngle = -FishGlobals.FishingAngleMax + maxAngle = FishGlobals.FishingAngleMax + if angle < minAngle: + self.arrow.setColorScale(1, 0, 0, 1) + angle = minAngle + elif angle > maxAngle: + self.arrow.setColorScale(1, 0, 0, 1) + angle = maxAngle + else: + self.arrow.setColorScale(1, 1 - math.pow(self.power, 3), 0.1, 0.7) + + # Scale the arrow tale based on the power so it looks like you + # are pulling the arrow out of the cast button + self.arrowTail.setScale(0.075,0.075,self.power*0.2) + # Also turn the arrow based on our current angle + self.arrow.setR(angle) + + # Actually turn the avatar to the angle + self.angleNP.setH(-angle) + + # Come back next frame + return Task.cont + + def localAdjustingCastTaskIndAxes(self, state): + assert self.notify.debugStateCall(self) + self.getMouse() + deltaX = self.mouseX - self.initMouseX + deltaY = self.mouseY - self.initMouseY + self.power = max(min(abs(deltaY) * 1.5, 1.0), 0.0) + self.castTrack.setT(0.4 + self.power * 0.5) + angle = deltaX * -180.0 + self.angleNP.setH(self.startAngleNP - angle) + return Task.cont + + def getMouse(self): + assert self.notify.debugStateCall(self) + if (base.mouseWatcherNode.hasMouse()): + self.mouseX = base.mouseWatcherNode.getMouseX() + self.mouseY = base.mouseWatcherNode.getMouseY() + else: + self.mouseX = 0 + self.mouseY = 0 + + def createCastTrack(self): + assert self.notify.debugStateCall(self) + self.castTrack = Sequence(ActorInterval(self.av, 'castlong', playRate=4), + ActorInterval(self.av, 'cast', startFrame=20), + Func(self.av.loop, 'fish-neutral'), + ) + + def startMoveBobTask(self): + assert self.notify.debugStateCall(self) + self.__showBob() + taskMgr.add(self.moveBobTask, self.taskName('moveBobTask')) + + def moveBobTask(self, task): + assert self.notify.debugStateCall(self) + # Accel due to gravity + g = 32.2 + # Elapsed time of cast + t = task.time + # Scale bob velocity and angle based on power of cast + vZero = self.power * self.vZeroMax + angle = deg2Rad(self.power * self.angleMax) + # How far has bob moved from start point? + deltaY = vZero * math.cos(angle) * t + deltaZ = vZero * math.sin(angle) * t - (g * t * t)/2.0 + deltaPos = Point3(0, deltaY, deltaZ) + # Current bob position + self.bobStartPos = Point3(0.0, 3.0, 8.5) + pos = self.bobStartPos + deltaPos + self.bob.setPos(pos) + # Have we reached end condition? + if pos[2] < self.waterLevel: + self.fsm.request("fishing") + return Task.done + else: + return Task.cont + + def __showBob(self): + assert self.notify.debugStateCall(self) + self.__hideBob() + self.bob.reparentTo(self.angleNP) + self.bob.setPos(self.ptop, 0,0,0) + self.av.update(0) + + def hitTarget(self): + assert self.notify.debugStateCall(self) + # This is called from the pond to let us know we found something + # And we are going to try to pull it in + assert(self.notify.debug("hitTarget")) + self.fsm.request("waitForAI") + + def enterOff(self): + assert self.notify.debugStateCall(self) + pass + + def exitOff(self): + assert self.notify.debugStateCall(self) + pass + + def enterWaiting(self, doAnimation = True): + assert self.notify.debugStateCall(self) + self.av.stopLookAround() + self.__hideLine() + # The avatar walks up to the fishing spot and gets out his pole. + self.track = Parallel() + + if doAnimation: + # Create a sequence that runs us to the spot and takes out our pole + toonTrack = Sequence( + Func(self.av.setPlayRate, 1.0, "run"), + Func(self.av.loop, 'run'), + LerpPosHprInterval(self.av, 1.0, + Point3(0, 0, 0), + Point3(0, 0, 0)), + # Bring out the fishing pole. + Func(self.__placeAvatar), + Parallel(ActorInterval(self.av, 'pole'), + Func(self.pole.pose, 'cast', 0), + LerpScaleInterval(self.pole, + duration = 0.5, + scale = 1.0, + startScale = 0.01) + ), + Func(self.av.loop, 'pole-neutral'), + ) + + if self.localToonFishing: + # Move the camera to a suitable location to observe + # the fishing. (runs parallel to toonTrack + camera.wrtReparentTo(render) + self.track.append( + LerpPosHprInterval( + nodePath=camera, + other=self.av, + duration=1.5, + pos=Point3(0, -12, 15), + # pos=Point3(0, -14, 14), + hpr=VBase3(0, -38, 0), + blendType="easeInOut")) + # Pop up the gui when we've reached the fishing spot. + toonTrack.append(Func(self.__showCastGui)) + toonTrack.append(Func(self.__initCastGui)) + if base.wantBingo: + self.__appendBingoMethod(toonTrack, self.pond.showBingoGui) + + self.track.append(toonTrack) + else: + self.__showCastGui() + self.track.start() + + ############################################################ + # Method: __appendBingoMethod + # Purpose: This method appends a Bingo related method to an + # existing interval. + # Input: interval - the interval to append the method. + # callback - the method that will be called by the + # interval. + # Output: None + ############################################################ + def __appendBingoMethod(self, interval, callback): + interval.append(Func(callback)) + + def exitWaiting(self): + assert self.notify.debugStateCall(self) + self.track.finish() + self.track = None + + def enterLocalAdjusting(self, guiEvent=None): + assert self.notify.debugStateCall(self) + if self.track: + self.track.pause() + if self.castTrack: + self.castTrack.pause() + self.power = 0.0 + self.firstCast = 0 + self.castButton['image0_color'] = Vec4(0, 1, 0, 1) + self.castButton['text'] = "" + self.av.stopLookAround() + self.__hideLine() + self.__hideBob() + self.howToDialog.hide() + # make sure we can afford to fish + castCost = FishGlobals.getCastCost(self.av.getFishingRod()) + if self.av.getMoney() < castCost: + self.__hideCastGui() + self.__showBroke() + self.av.loop('pole-neutral') + return + if self.av.isFishTankFull(): + self.__hideCastGui() + self.__showFishTankFull() + self.av.loop('pole-neutral') + return + # Start task to adjust power of cast + self.arrow.show() + self.arrow.setColorScale(1,1,0,0.7) + self.startAngleNP = self.angleNP.getH() + self.getMouse() + self.initMouseX = self.mouseX + self.initMouseY = self.mouseY + self.__hideBob() + if config.GetBool('fishing-independent-axes', 0): + taskMgr.add(self.localAdjustingCastTaskIndAxes, self.taskName('adjustCastTask')) + else: + taskMgr.add(self.localAdjustingCastTask, self.taskName('adjustCastTask')) + + #tell the bingo gui that a cast has begun... this is used for the tutorial + if base.wantBingo: + bingoMgr = self.pond.getPondBingoManager() + if bingoMgr: + bingoMgr.castingStarted() + + def exitLocalAdjusting(self): + assert self.notify.debugStateCall(self) + taskMgr.remove(self.taskName('adjustCastTask')) + self.castButton['image0_color'] = Vec4(1, 0, 0, 1) + self.castButton['text'] = TTLocalizer.FishingCast + self.arrow.hide() + + def enterLocalCasting(self): + assert self.notify.debugStateCall(self) + assert(self.localToonFishing) + # If the cast without any power, and they do not have any fish in + # their collection, they probably do not know how to use the + # interface. + if ((self.power == 0.0) and (len(self.av.fishCollection) == 0)): + self.__showHowTo(TTLocalizer.FishingHowToFailed) + if self.castTrack: + self.castTrack.pause() + self.av.loop('pole-neutral') + self.track = None + return + + # Subtract money from jellybean jar gui (the AI does the real work) + castCost = FishGlobals.getCastCost(self.av.getFishingRod()) + self.jar['text'] = str(max(self.av.getMoney() - castCost, 0)) + if not self.castTrack: + self.createCastTrack() + self.castTrack.pause() + startT = 0.7 + (1 - self.power) * 0.3 + self.castTrack.start(startT) + self.track = Sequence(Wait(1.2 - startT), + Func(self.startMoveBobTask), + Func(self.__showLineCasting), + ) + self.track.start() + # Tell the AI we are casting now + heading = self.angleNP.getH() + self.d_doCast(self.power, heading) + self.timer.countdown(FishGlobals.CastTimeout) + + def exitLocalCasting(self): + assert self.notify.debugStateCall(self) + taskMgr.remove(self.taskName('moveBobTask')) + if self.track: + self.track.pause() + self.track = None + if self.castTrack: + self.castTrack.pause() + self.__hideLine() + self.__hideBob() + + def enterDistCasting(self, power, h): + assert self.notify.debugStateCall(self) + assert(not self.localToonFishing) + self.av.stopLookAround() + self.__placeAvatar() + self.__hideLine() + self.__hideBob() + self.angleNP.setH(h) + self.power = power + self.track = Parallel( + Sequence(ActorInterval(self.av, 'cast'), + Func(self.pole.pose, 'cast', 0), + Func(self.av.loop, 'fish-neutral'), + ), + Sequence(Wait(1.0), + Func(self.startMoveBobTask), + Func(self.__showLineCasting), + ), + ) + self.track.start() + + def exitDistCasting(self): + assert self.notify.debugStateCall(self) + self.track.finish() + self.track = None + taskMgr.remove(self.taskName('moveBobTask')) + self.__hideLine() + self.__hideBob() + + def enterFishing(self): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.track = Sequence(ActorInterval(self.av, 'cast'), + Func(self.pole.pose, 'cast', 0), + Func(self.av.loop, 'fish-neutral'), + ) + self.track.start(self.castTrack.getT()) + else: + self.track = None + self.av.loop('fish-neutral') + self.__showBobFloat() + self.__showLineWaiting() + if self.localToonFishing: + self.pond.startCheckingTargets(self, self.bob.getPos(render)) + + def exitFishing(self): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.pond.stopCheckingTargets() + if self.track: + self.track.finish() + self.track = None + + def enterWaitForAI(self): + assert self.notify.debugStateCall(self) + # While we are waiting to hear back from the AI, you are not + # allowed to fish again. This would cause too many tricky timing + # conditions + self.castButton['state'] = DGG.DISABLED + + def exitWaitForAI(self): + assert self.notify.debugStateCall(self) + self.castButton['state'] = DGG.NORMAL + + def enterReward(self, code, itemDesc1, itemDesc2, itemDesc3): + assert self.notify.debugStateCall(self) + self.__placeAvatar() + self.bob.reparentTo(self.angleNP) + self.bob.setZ(self.waterLevel) + self.__showLineReeling() + self.castTrack.pause() + if self.localToonFishing: + # Switch guis. + self.__showCastGui() + if code == FishGlobals.QuestItem: + self.__showQuestItem(itemDesc1) + elif code in (FishGlobals.FishItem, + FishGlobals.FishItemNewEntry, + FishGlobals.FishItemNewRecord,): + genus, species, weight = itemDesc1, itemDesc2, itemDesc3 + fish = FishBase.FishBase(genus, species, weight) + self.__showFishItem(code, fish) + if base.wantBingo: + self.pond.handleBingoCatch((genus, species)) + elif code == FishGlobals.BootItem: + # TODO: play sfx + self.__showBootItem() + if base.wantBingo: + self.pond.handleBingoCatch(FishGlobals.BingoBoot) + elif code == FishGlobals.JellybeanItem: + # TODO: play sfx + amount = itemDesc1 + self.__showJellybeanItem(amount) + elif code == FishGlobals.OverTankLimit: + self.__hideCastGui() + else: + self.__showFailureReason(code) + self.track = Sequence( + Parallel(ActorInterval(self.av, 'reel'), + ActorInterval(self.pole, 'cast', startFrame=63, endFrame=127), + ), + ActorInterval(self.av, 'reel-neutral'), + Func(self.__hideLine), + Func(self.__hideBob), + ActorInterval(self.av, 'fish-again'), + Func(self.av.loop, 'pole-neutral'), + ) + self.track.start() + + def cleanupFishPanel(self): + if self.fishPanel: + self.fishPanel.hide() + self.fishPanel.destroy() + self.fishPanel = None + + def hideBootPanel(self): + if self.madeGui and self.itemBoot: + self.__itemGuiClose() + + def exitReward(self): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.itemGui.detachNode() + self.cleanupFishPanel() + self.track.finish() + self.track = None + + def enterLeaving(self): + assert self.notify.debugStateCall(self) + if self.localToonFishing: + self.__hideCastGui() + if base.wantBingo: + self.pond.cleanupBingoMgr() + self.av.stopLookAround() + self.av.startLookAround() + self.__placeAvatar() + self.__hideLine() + self.__hideBob() + self.track = Sequence( + Parallel(ActorInterval(self.av, 'fish-end'), + Func(self.pole.pose, 'cast', 0), + LerpScaleInterval(self.pole, + duration = 0.5, + scale = 0.01, + startScale = 1.0), + ), + Func(self.__dropPole), + Func(self.av.loop, 'neutral'), + ) + if self.localToonFishing: + self.track.append(Func(self.fsm.requestFinalState)) + self.track.start() + + def exitLeaving(self): + assert self.notify.debugStateCall(self) + self.track.pause() + self.track = None + + def enterSellFish(self): + assert self.notify.debugStateCall(self) + self.castButton['state'] = DGG.DISABLED + self.__showSellFishDialog() + self.__hideHowTo() + + def exitSellFish(self): + assert self.notify.debugStateCall(self) + self.castButton['state'] = DGG.NORMAL + self.__hideSellFishDialog() + self.__hideSellFishConfirmDialog() + + # message from the ai telling us the sale is completed + def sellFishComplete(self, trophyResult, numFishCaught): + for button in self.sellFishDialog.buttonList: + button['state'] = DGG.NORMAL + + if self.localToonFishing: + if trophyResult: + # congratulate toon + self.__hideSellFishDialog() + self.__showSellFishConfirmDialog(numFishCaught) + else: + self.fsm.request("waiting", [False]) + + def __allowSellFish(self): + if base.wantBingo: + if self.pond.hasPondBingoManager(): + hoodId = base.cr.playGame.getPlaceId() + if hoodId == ToontownGlobals.MyEstate: + return True + return False + diff --git a/toontown/src/safezone/DistributedFishingSpotAI.py b/toontown/src/safezone/DistributedFishingSpotAI.py new file mode 100644 index 0000000..9a19ace --- /dev/null +++ b/toontown/src/safezone/DistributedFishingSpotAI.py @@ -0,0 +1,217 @@ +from otp.ai.AIBase import * + +from direct.distributed import DistributedObjectAI +import random + +from toontown.toonbase import ToontownAccessAI +from toontown.toonbase import TTLocalizer +from direct.directnotify import DirectNotifyGlobal +from toontown.fishing import FishGlobals + +class DistributedFishingSpotAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedFishingSpotAI") + + def __init__(self, air, pond, x, y, z, h, p, r): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.notify.debug("init") + self.posHpr = (x, y, z, h, p, r) + self.avId = 0 + self.timeoutTask = None + self.pond = pond + self.wantTimeouts = simbase.config.GetBool("want-fishing-timeouts", 1) + + def delete(self): + self.notify.debug("delete") + taskMgr.remove(self.taskName("clearEmpty")) + self.ignore(self.air.getAvatarExitEvent(self.avId)) + self.__stopTimeout() + self.d_setMovie(FishGlobals.ExitMovie) + self.avId = 0 + self.pond = None + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getPondDoId(self): + return self.pond.getDoId() + + def requestEnter(self): + # A client is requesting sole use of the fishing spot. If + # it's available, he can have it. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestEnter: avId: %s" % (avId)) + if self.avId == avId: + # This seems to happen in the estates when we get a double request + # coming out of fishing directly onto the dock + self.notify.debug("requestEnter: avId %s is already fishing here" % (avId)) + return + + # Check that player has full access + if not ToontownAccessAI.canAccess(avId, self.zoneId): + self.sendUpdateToAvatarId(avId, "rejectEnter", []) + return + + if self.avId == 0: + self.avId = avId + # Tell the pond we are here + self.pond.addAvSpot(avId, self) + self.acceptOnce(self.air.getAvatarExitEvent(self.avId), + self.unexpectedExit) + self.__stopTimeout() + self.d_setOccupied(self.avId) + self.d_setMovie(FishGlobals.EnterMovie) + self.__startTimeout(FishGlobals.CastTimeout) + self.air.writeServerEvent("fished_enter",self.avId, "%s" % (self.zoneId)) + else: + self.sendUpdateToAvatarId(avId, "rejectEnter", []) + + def requestExit(self): + # The client within the spot is ready to leave. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestExit: avId: %s" % (avId)) + if not self.validate(avId, (self.avId == avId), "requestExit: avId is not fishing in this spot"): + return + self.normalExit() + + def d_setOccupied(self, avId): + self.notify.debug("setOccupied: %s" % (avId)) + self.sendUpdate("setOccupied", [avId]) + + def doCast(self, power, heading): + # The client begins a cast. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("doCast: avId: %s" % (avId)) + if not self.validate(avId, (self.avId == avId), + "doCast: avId is not fishing in this spot"): + return + if not self.validate(avId, (0.0 <= power <= 1.0), + ("doCast: power: %s is out of range" % power)): + return + if not self.validate(avId, + (-FishGlobals.FishingAngleMax <= heading <= FishGlobals.FishingAngleMax), + ("doCast: heading: %s is out of range" % heading)): + return + + av = self.air.doId2do.get(self.avId) + if not self.validate(avId, (av), "doCast: avId not currently logged in to this AI"): + return + + self.__stopTimeout() + money = av.getMoney() + # cast cost is based on rod now + castCost = FishGlobals.getCastCost(av.getFishingRod()) + + if money < castCost: + # Not enough money to cast + self.normalExit() + return + + self.air.writeServerEvent("fished_cast", avId, "%s|%s" %(av.getFishingRod(), castCost)) + av.b_setMoney(money - castCost) + self.d_setMovie(FishGlobals.CastMovie, power=power, h=heading) + self.__startTimeout(FishGlobals.CastTimeout) + + def d_setMovie(self, mode, code=0, itemDesc1=0, itemDesc2=0, itemDesc3=0, power=0, h=0): + self.notify.debug( + "setMovie: mode:%s code:%s itemDesc1:%s itemDesc2:%s itemDesc3:%s power:%s h:%s" % + (mode, code, itemDesc1, itemDesc2, itemDesc3, power, h)) + self.sendUpdate("setMovie", [mode, code, itemDesc1, itemDesc2, itemDesc3, power, h]) + + def getPosHpr(self): + # This is needed because setPosHpr is a required field. + return self.posHpr + + def __startTimeout(self, timeLimit): + self.notify.debug("__startTimeout") + # Sets the timeout counter running. If __stopTimeout() is not + # called before the time expires, we'll exit the avatar. This + # prevents avatars from hanging out in the fishing spot all + # day. + self.__stopTimeout() + if self.wantTimeouts: + self.timeoutTask = taskMgr.doMethodLater(timeLimit, + self.__handleTimeout, + self.taskName("timeout")) + + def __stopTimeout(self): + self.notify.debug("__stopTimeout") + # Stops a previously-set timeout from expiring. + if self.timeoutTask: + taskMgr.remove(self.timeoutTask) + self.timeoutTask = None + + def __handleTimeout(self, task): + self.notify.debug("__handleTimeout") + # Called when a timeout expires, this sends the avatar home. + self.normalExit() + + def cleanupAvatar(self): + # Tell the pond we are leaving + self.air.writeServerEvent("fished_exit",self.avId, "%s" % (self.zoneId)) + self.pond.removeAvSpot(self.avId, self) + self.ignore(self.air.getAvatarExitEvent(self.avId)) + self.__stopTimeout() + self.avId = 0 + + def normalExit(self): + self.notify.debug("normalExit") + # Send the avatar out of the fishing spot, either because of + # his own request or due to some other cause (like a timeout). + self.cleanupAvatar() + self.d_setMovie(FishGlobals.ExitMovie) + # Give everyone enough time to play the goodbye movie, + # then dump the avatar. + taskMgr.doMethodLater(1.2, self.__clearEmpty, + self.taskName("clearEmpty")) + + def __clearEmpty(self, task=None): + self.notify.debug("__clearEmpty") + self.d_setOccupied(0) + + def unexpectedExit(self): + self.notify.debug("unexpectedExit") + # Called when the avatar in the fishing spot vanishes. + # Tell the pond we are leaving + self.cleanupAvatar() + self.d_setOccupied(0) + + def hitTarget(self, code, item): + self.notify.debug("hitTarget: code: %s item: %s" % (code, item)) + if code == FishGlobals.QuestItem: + self.d_setMovie(FishGlobals.PullInMovie, code, item) + elif code in (FishGlobals.FishItem, + FishGlobals.FishItemNewEntry, + FishGlobals.FishItemNewRecord): + genus, species, weight = item.getVitals() + self.d_setMovie(FishGlobals.PullInMovie, code, genus, species, weight) + elif code == FishGlobals.BootItem: + self.d_setMovie(FishGlobals.PullInMovie, code) + elif code == FishGlobals.JellybeanItem: + self.d_setMovie(FishGlobals.PullInMovie, code, item) + else: + self.d_setMovie(FishGlobals.PullInMovie, code) + self.__startTimeout(FishGlobals.CastTimeout) + + def d_sellFishComplete(self, avId, trophyResult, numFishCaught): + self.sendUpdateToAvatarId(avId, "sellFishComplete", [trophyResult, numFishCaught]) + + def sellFish(self): + # The client asks to sell his fish + gotTrophy = -1 + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(self.avId) + self.notify.debug("sellFish: avId: %s" % (avId)) + if not self.validate(avId, (simbase.wantBingo), "sellFish: Currently, you can only do this if bingo is turned on"): + gotTrophy = False + elif not self.validate(avId, (self.pond.hasPondBingoManager()), "sellFish: Currently, you can only do this during bingo night"): + gotTrophy = False + elif not self.validate(avId, (self.avId == avId), "sellFish: avId is not fishing in this spot"): + gotTrophy = False + elif not self.validate(avId, (av), "sellFish: avId not currently logged in to this AI"): + gotTrophy = False + + if gotTrophy is -1: + gotTrophy = self.air.fishManager.creditFishTank(av) + self.d_sellFishComplete(avId, gotTrophy, len(av.fishCollection)) + else: + self.d_sellFishComplete(avId, False, 0) + diff --git a/toontown/src/safezone/DistributedGolfKart.py b/toontown/src/safezone/DistributedGolfKart.py new file mode 100644 index 0000000..57e0bb6 --- /dev/null +++ b/toontown/src/safezone/DistributedGolfKart.py @@ -0,0 +1,787 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * + +from toontown.golf import GolfGlobals +from toontown.toonbase import ToontownGlobals +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from toontown.distributed import DelayDelete +from direct.task.Task import Task +from direct.showbase import PythonUtil +from toontown.toontowngui import TeaserPanel +from toontown.toon import ToonDNA +from toontown.hood import ZoneUtil + +class DistributedGolfKart(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedGolfKart") + SeatOffsets = ((0.5, -0.5, 0), (-0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)) + JumpOutOffsets = ((3, 5, 0), (1.5, 4, 0), (-1.5, 4, 0), (-3, 4, 0)) + KART_ENTER_TIME = 400 + + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + + self.localToonOnBoard = 0 + + self.trolleyCountdownTime = \ + base.config.GetFloat("trolley-countdown-time", + TROLLEY_COUNTDOWN_TIME) + + self.fsm = ClassicFSM.ClassicFSM('DistributedTrolley', + [State.State('off', + self.enterOff, + self.exitOff, + ['entering', + 'waitEmpty', + 'waitCountdown', + 'leaving']), + State.State('entering', + self.enterEntering, + self.exitEntering, + ['waitEmpty']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty', 'leaving']), + State.State('leaving', + self.enterLeaving, + self.exitLeaving, + ['entering'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + self.trolleyAwaySfx = base.loadSfx("phase_4/audio/sfx/SZ_trolley_away.mp3") + self.trolleyBellSfx = base.loadSfx("phase_4/audio/sfx/SZ_trolley_bell.mp3") + + # Tracks on toons, for starting and stopping + # stored by avId : track. There is only a need for one at a time, + # in fact the point of the dict is to ensure only one is playing at a time + self.__toonTracks = {} + + # this is to stop the seeing toons sitting in midair + # a little scary as it might cause more problems + self.avIds = [0,0,0,0] # which toons are in the seats + + self.kartModelPath = 'phase_6/models/golf/golf_cart3.bam' + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + + # Get the state machine stuff for playGame + self.loader = self.cr.playGame.hood.loader + if(self.loader): + self.notify.debug("Loader has been loaded") + self.notify.debug(str(self.loader)) + else: + self.notify.debug("Loader has not been loaded") + + self.golfKart = render.attachNewNode('golfKartNode') + #kart = loader.loadModel('phase_6/models/karting/Kart3_Final') + self.kart = loader.loadModel(self.kartModelPath) + self.kart.setPos(0, 0, 0) + self.kart.setScale(1) + self.kart.reparentTo(self.golfKart) + self.golfKart.reparentTo(self.loader.geom) + + # Wheels + self.wheels = self.kart.findAllMatches('**/wheelNode*') + self.numWheels = self.wheels.getNumPaths() + + #debugAxis = loader.loadModel('models/misc/xyzAxis') + #debugAxis.setColorScale(1.0, 1.0, 1.0, 0.25) + #debugAxis.setTransparency(True) + #debugAxis.reparentTo(self.golfKart) + + trolleyExitBellInterval = SoundInterval(self.trolleyBellSfx, node=self.golfKart) + trolleyExitAwayInterval = SoundInterval(self.trolleyAwaySfx, node=self.golfKart) + + def announceGenerate(self): + """Setup other fields dependent on the required fields.""" + DistributedObject.DistributedObject.announceGenerate(self) + #self.golfKartSphereNode = self.golfKart.attachNewNode(CollisionNode('golfkart_sphere_%d' % self.golfCourse)) + self.golfKartSphereNode = self.golfKart.attachNewNode(CollisionNode('golfkart_sphere_%d' % self.getDoId())) + self.golfKartSphereNode.node().addSolid(CollisionSphere(0, 0, 0, 2)) + + angle = self.startingHpr[0] + angle -= 90 + radAngle = deg2Rad(angle) + unitVec = Vec3( math.cos(radAngle), math.sin(radAngle), 0) + unitVec *= 45.0 + self.endPos = self.startingPos + unitVec + + dist = Vec3(self.endPos - self.enteringPos).length() + wheelAngle = (dist / (4.8 * 1.4 * math.pi)) * 360 + + self.kartEnterAnimateInterval = Parallel( + # start a lerp HPR for each wheel + LerpHprInterval(self.wheels[0], 5.0, Vec3(self.wheels[0].getH(), wheelAngle, self.wheels[0].getR())), + LerpHprInterval(self.wheels[1], 5.0, Vec3(self.wheels[1].getH(), wheelAngle, self.wheels[1].getR())), + LerpHprInterval(self.wheels[2], 5.0, Vec3(self.wheels[2].getH(), wheelAngle, self.wheels[2].getR())), + LerpHprInterval(self.wheels[3], 5.0, Vec3(self.wheels[3].getH(), wheelAngle, self.wheels[3].getR())), + name = "KartAnimate") + + trolleyExitTrack1 = Parallel( + LerpPosInterval(self.golfKart, 5.0, self.endPos), + self.kartEnterAnimateInterval, + name = "KartExitTrack") + self.trolleyExitTrack = Sequence( + trolleyExitTrack1, + Func(self.hideSittingToons), + ) + + self.trolleyEnterTrack = Sequence( + LerpPosInterval(self.golfKart, 5.0, self.startingPos, startPos = self.enteringPos)) + + def disable(self): + DistributedObject.DistributedObject.disable(self) + # Go to the off state when the object is put in the cache + self.fsm.request("off") + + # No more toon animating + self.clearToonTracks() + + del self.wheels + del self.numWheels + + del self.golfKartSphereNode + self.notify.debug("Deleted self loader " + str(self.getDoId())) + del self.loader + self.golfKart.removeNode() + self.kart.removeNode() + del self.kart + del self.golfKart + + + self.trolleyEnterTrack.pause() + self.trolleyEnterTrack = None + del self.kartEnterAnimateInterval + # del'ing this will cause the application to exit with an error code: del self.trolleyEnterTrack + # Lets try it again - maybe the ghosts are gone now? + # If we leave it commented out, we leak trolleys on the clients + del self.trolleyEnterTrack + self.trolleyExitTrack.pause() + self.trolleyExitTrack = None + del self.trolleyExitTrack + + #import pdb; + #pdb.set_trace() + + def delete(self): + self.notify.debug("Golf kart getting deleted: %s" % self.getDoId()) + del self.trolleyAwaySfx + del self.trolleyBellSfx + DistributedObject.DistributedObject.delete(self) + del self.fsm + + def setState(self, state, timestamp): + self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)]) + + def handleEnterTrolleySphere(self, collEntry): + self.notify.debug("Entering Trolley Sphere....") + # Put localToon into requestBoard mode. + self.loader.place.detectedTrolleyCollision() + + def allowedToEnter(self): + """Check if the local toon is allowed to enter.""" + if base.cr.isPaid(): + return True + return False + + def handleEnterGolfKartSphere(self, collEntry): + self.notify.debug("Entering Golf Kart Sphere.... %s" % self.getDoId()) + if self.allowedToEnter(): + # Put localToon into requestBoard mode. + self.loader.place.detectedGolfKartCollision(self) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='golf', + doneFunc=self.handleOkTeaser) + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + + def handleEnterTrolley(self): + # Tell the server that this avatar wants to board. + toon = base.localAvatar + self.sendUpdate("requestBoard",[]) + + def handleEnterGolfKart(self): + # Tell the server that this avatar wants to board. + toon = base.localAvatar + self.sendUpdate("requestBoard",[]) + + def fillSlot0(self, avId): + self.fillSlot(0, avId) + + def fillSlot1(self, avId): + self.fillSlot(1, avId) + + def fillSlot2(self, avId): + self.fillSlot(2, avId) + + def fillSlot3(self, avId): + self.fillSlot(3, avId) + + def fillSlot(self, index, avId): + assert self.notify.debugStateCall(self) + #print "fill Slot: %d for %d" % (index, avId) + self.avIds[index] = avId + if avId == 0: + # This means that the slot is now empty, and no action should + # be taken. + pass + else: + # If localToon is boarding, he needs to change state + if avId == base.localAvatar.getDoId(): + self.loader.place.trolley.fsm.request("boarding", [self.golfKart]) + self.localToonOnBoard = 1 + + # Put that toon on the trolley + + # If it's localToon, tell him he's on the trolley now + if avId == base.localAvatar.getDoId(): + self.loader.place.trolley.fsm.request("boarded") + + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + # Parent it to the trolley + toon.stopSmooth() + toon.wrtReparentTo(self.golfKart) + #toon.setAnimState("run", 1.0) + #toon.headsUp(-5, -4.5 + (index * 3), 1.4) + + sitStartDuration = toon.getDuration("sit-start") + jumpTrack = self.generateToonJumpTrack(toon, index) + track = Sequence( + #LerpPosInterval(toon, TOON_BOARD_TIME * 0.75, + # Point3(-5, -4.5 + (index * 3), 1.4)), + #LerpHprInterval(toon, TOON_BOARD_TIME * 0.25, + # Point3(90, 0, 0)), + #Parallel(Sequence(Wait(sitStartDuration*0.25), + # LerpPosInterval(toon, sitStartDuration*0.25, + # Point3(-3.9, -4.5 + (index * 3), 3.0)), + # ), + # ActorInterval(toon, "sit-start"), + # ), + jumpTrack, + Func(toon.setAnimState, "Sit", 1.0), + Func(self.clearToonTrack, avId), + name = toon.uniqueName("fillTrolley"), + autoPause = 1) + + track.delayDelete = DelayDelete.DelayDelete(toon, 'GolfKart.fillSlot') + self.storeToonTrack(avId, track) + track.start() + else: + self.notify.warning("toon: " + str(avId) + + " doesn't exist, and" + + " cannot board the trolley!") + + def emptySlot0(self, avId, timestamp): + self.emptySlot(0, avId, timestamp) + + def emptySlot1(self, avId, timestamp): + self.emptySlot(1, avId, timestamp) + + def emptySlot2(self, avId, timestamp): + self.emptySlot(2, avId, timestamp) + + def emptySlot3(self, avId, timestamp): + self.emptySlot(3, avId, timestamp) + + def notifyToonOffTrolley(self, toon): + toon.setAnimState("neutral", 1.0) + if toon == base.localAvatar: + self.loader.place.trolley.handleOffTrolley() + self.localToonOnBoard = 0 + else: + toon.startSmooth() + return + + def emptySlot(self, index, avId, timestamp): + #print "Emptying slot: %d for %d" % (index, avId) + # If localToon is exiting, he needs to change state + if avId == 0: + # This means that no one is currently exiting, and no action + # should be taken + pass + else: + self.avIds[index] = 0 + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + # Parent it to render + #toon.setHpr(self.golfKart, 90,0,0) + #toon.wrtReparentTo(render) + toon.stopSmooth() + # toon.setAnimState("run", 1.0) + + # Place it on the appropriate spot relative to the + # trolley station + + sitStartDuration = toon.getDuration("sit-start") + jumpOutTrack = self.generateToonReverseJumpTrack(toon, index) + track = Sequence( + # Hop off the seat + #Parallel(ActorInterval(toon, "sit-start", + # startTime=sitStartDuration, + # endTime=0.0), + # Sequence(Wait(sitStartDuration*0.5), + # LerpPosInterval(toon, sitStartDuration*0.25, + # Point3( -4.5 + (index * 3), 5, 1.4), + # other=self.golfKart), + # ), + # ), + # Then run +## Func(toon.setAnimState, "run", 1.0), +## LerpPosInterval(toon, TOON_EXIT_TIME, +## Point3(21 - (index * 3), +## -5, +## 0.02), +## #Point3(165, 0, 0), +## other=self.golfKart +## ), + + jumpOutTrack, + # Tell the toon he is free to roam now + Func(self.notifyToonOffTrolley, toon), + Func(self.clearToonTrack, avId), + name = toon.uniqueName("emptyTrolley"), + autoPause = 1) + track.delayDelete = DelayDelete.DelayDelete(toon, 'GolfKart.emptySlot') + self.storeToonTrack(avId, track) + track.start() + + # Tell localToon he is exiting (if localToon is on board) + if avId == base.localAvatar.getDoId(): + self.loader.place.trolley.fsm.request("exiting") + + else: + self.notify.warning("toon: " + str(avId) + + " doesn't exist, and" + + " cannot exit the trolley!") + + def rejectBoard(self, avId): + # This should only be sent to us if our localToon requested + # permission to board the trolley. + assert(base.localAvatar.getDoId() == avId) + self.loader.place.trolley.handleRejectBoard() + + def setMinigameZone(self, zoneId, minigameId): + # This is how the server puts the clients into a minigame + self.localToonOnBoard = 0 + messenger.send("playMinigame", [zoneId, minigameId]) + + def setGolfZone(self, zoneId, courseId): + """This is how the server puts the clients into a golf course.""" + self.localToonOnBoard = 0 + messenger.send("playGolf", [zoneId, courseId]) + + def __enableCollisions(self): + # start listening for toons to enter. + assert self.notify.debugStateCall(self) + self.accept('entertrolley_sphere', self.handleEnterTrolleySphere) + self.accept('enterTrolleyOK', self.handleEnterTrolley) + + self.accept('entergolfkart_sphere_%d' % self.getDoId(), self.handleEnterGolfKartSphere) + self.accept('enterGolfKartOK_%d' % self.getDoId(), self.handleEnterGolfKart) + self.golfKartSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + + def __disableCollisions(self): + # stop listening for toons. + self.ignore('entertrolley_sphere') + self.ignore('enterTrolleyOK') + #self.ignore('entergolfkart_sphere_%d' % self.golfCourse) + #self.ignore('enterTrolleyOK_%d' % self.golfCourse) + #self.ignore('enterGolfKartOK_%d' % self.golfCourse) + + self.ignore('entergolfkart_sphere_%d' % self.getDoId()) + self.ignore('enterTrolleyOK_%d' % self.getDoId()) + self.ignore('enterGolfKartOK_%d' % self.getDoId()) + + self.golfKartSphereNode.setCollideMask(BitMask32(0)) + + + ##### Off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### Entering state ##### + + def enterEntering(self, ts): + # Lerp the trolley into place via a track + self.trolleyEnterTrack.start(ts) + + def exitEntering(self): + self.trolleyEnterTrack.finish() + + ##### WaitEmpty state ##### + + def enterWaitEmpty(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + + def exitWaitEmpty(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + + ##### WaitCountdown state ##### + + def enterWaitCountdown(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + self.accept("trolleyExitButton", self.handleExitButton) + # Start the countdown clock... + self.clockNode = TextNode("trolleyClock") + self.clockNode.setFont(ToontownGlobals.getSignFont()) + self.clockNode.setAlign(TextNode.ACenter) + self.clockNode.setTextColor(0.9, 0.1, 0.1, 1) + self.clockNode.setText("10") + self.clock = self.golfKart.attachNewNode(self.clockNode) + self.clock.setBillboardAxis() + self.clock.setPosHprScale(0, -1, 7.0, + -0.00, 0.00, 0.00, + 2.0, 2.0, 2.0) + if ts < self.trolleyCountdownTime: + self.countdown(self.trolleyCountdownTime - ts) + return + + def timerTask(self, task): + countdownTime = int(task.duration - task.time) + timeStr = str(countdownTime) + + if self.clockNode.getText() != timeStr: + self.clockNode.setText(timeStr) + + if task.time >= task.duration: + return Task.done + else: + return Task.cont + + def countdown(self, duration): + countdownTask = Task(self.timerTask) + countdownTask.duration = duration + taskMgr.remove(self.uniqueName("golfKartTimerTask")) + return taskMgr.add(countdownTask, self.uniqueName("golfKartTimerTask")) + + def handleExitButton(self): + # This gets called when the exit button gets pushed. + self.sendUpdate("requestExit") + #import pdb; pdb.set_trace() + + def exitWaitCountdown(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + self.ignore("trolleyExitButton") + # Stop the countdown clock... + taskMgr.remove(self.uniqueName("golfKartTimerTask")) + self.clock.removeNode() + del self.clock + del self.clockNode + + ##### Leaving state ##### + + def enterLeaving(self, ts): + # Move the trolley into the tunnel via a track + self.trolleyExitTrack.start(ts) + if self.localToonOnBoard: + if hasattr(self.loader.place, 'trolley') and self.loader.place.trolley: + self.loader.place.trolley.fsm.request("trolleyLeaving") + + def exitLeaving(self): + self.trolleyExitTrack.finish() + pass + + + ##### Miscellaneous support functions ##### + + def getStareAtNodeAndOffset(self): + return self.golfKart, Point3(0,0,4) + + def storeToonTrack(self, avId, track): + # Clear out any currently playing tracks on this toon + self.clearToonTrack(avId) + # Store this new one + self.__toonTracks[avId] = track + + def clearToonTrack(self, avId): + # Clear out any currently playing tracks on this toon + oldTrack = self.__toonTracks.get(avId) + if oldTrack: + oldTrack.pause() + if self.__toonTracks.get(avId): + DelayDelete.cleanupDelayDeletes(self.__toonTracks[avId]) + del self.__toonTracks[avId] + + def clearToonTracks(self): + #We can't use an iter because we are deleting keys + keyList = [] + for key in self.__toonTracks: + keyList.append(key) + + for key in keyList: + if self.__toonTracks.has_key(key): + self.clearToonTrack(key) + + def setGolfCourse(self, golfCourse): + """Set the golf course as dictated by the AI.""" + assert self.notify.debugStateCall(self) + self.golfCourse = golfCourse + + def setPosHpr(self, x, y, z, h, p ,r): + """Set the pos hpr as dictated by the AI.""" + self.startingPos = Vec3(x, y, z) + self.enteringPos = Vec3(x, y, z - 10) + self.startingHpr = Vec3(h, 0, 0) + self.golfKart.setPosHpr( x, y, z, h, 0, 0 ) + + def setColor(self, r, g, b): + """Set the color of the golf kart.""" + kartBody = self.kart.find('**/main_body') + kartBody.setColor(r / 255.0, g / 255.0, b / 255.0, 1) + cartBase = self.kart.find('**/cart_base*') + + # Desaturate coloring of kart for bumper + red = r / 255.0 + green = g / 255.0 + blue = b / 255.0 + + if red >= green and red > blue: + # Mostly red kart + s = (red - blue) / float(red) + v = red + if(green > blue): + h = ( green - blue ) / (red - blue) + else: + h = ( green - blue ) / (red - green) + elif green >= blue: + # Mostly green kart + s = (green - blue) / float(green) + v = green + if( red > blue ): + h = 2 + ( blue - red ) / ( green - blue ) + else: + h = 2 + ( blue - red ) / ( green - red ) + else: + # Currently unused blue kart + if( red > green ): + s = ( blue - green ) / blue + h = 4 + (red - green) / (blue - green) + else: + s = (blue - red) / blue + h = 4 + (red - green) / (blue - red) + v = blue + + if( h < 0): + h *= 60 + h += 360 + h /= 60 + s /= 3 + + if s == 0: + # All gray + red = green = blue = v + else: + i = int(h) + f = h - i + p = v * ( 1 - s) + q = v * ( 1 - s * f ) + t = v * ( 1 - s * ( 1 - f ) ) + + if i == 0: + red = v + green = t + blue = p + elif i == 1: + red = q + green = v + blue = p + elif i == 2: + red = p + green = v + blue = t + elif i == 3: + red = p + green = q + blue = v + elif i == 4: + red = t + green = p + blue = v + elif i == 5: + red = v + green = p + blue = q + + cartBase.setColorScale(red, green, blue, 1) + #seats = self.kart.find('**/seat_cushion') + #seats.setColor(GolfGlobals.PlayerColors[g]) + #seatBack.setColor(GolfGlobals.PlayerColors[g]) + + def generateToonJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping into the golf kart.""" + # Maintain a reference to Parent and Scale of avatar in case they + # exit from the kart. + #base.sb = self + + av.pose('sit', 47) + hipOffset = av.getHipsParts()[2].getPos(av) + + def getToonJumpTrack( av, seatIndex ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = self.golfKart): + dest = Point3(0,0,0) + if hasattr(self, 'golfKart') and self.golfKart: + dest = Vec3(self.golfKart.getPos(av.getParent())) + seatNode = self.golfKart.find("**/seat" + str(seatIndex + 1)) + dest += seatNode.getPos(self.golfKart) + dna = av.getStyle() + dest -= hipOffset + if(seatIndex < 2): + dest.setY( dest.getY() + 2 * hipOffset.getY()) + dest.setZ(dest.getZ() + 0.1) + else: + self.notify.warning('getJumpDestinvalid golfKart, returning (0,0,0)') + return dest + + def getJumpHpr(av = av, node = self.golfKart): + hpr = Point3(0,0,0) + if hasattr(self, 'golfKart') and self.golfKart: + hpr = self.golfKart.getHpr(av.getParent()) + if(seatIndex < 2): + hpr.setX( hpr.getX() + 180) + else: + hpr.setX( hpr.getX() ) + angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX()) + hpr.setX(angle) + else: + self.notify.warning('getJumpHpr invalid golfKart, returning (0,0,0)') + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.43 ), + Parallel( LerpHprInterval( av, + hpr = getJumpHpr, + duration = .9 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = .9 ) + ), + ) + ) + return toonJumpTrack + + def getToonSitTrack( av ): + toonSitTrack = Sequence( + + ActorInterval( av, 'sit-start' ), + Func( av.loop, 'sit' ) + ) + return toonSitTrack + + toonJumpTrack = getToonJumpTrack( av, seatIndex ) + toonSitTrack = getToonSitTrack( av ) + + jumpTrack = Sequence( + Parallel( + toonJumpTrack, + Sequence( Wait(1), + toonSitTrack, + ), + ), + #Func( self.av.setPosHpr, 0, 0, 0, 0, 0, 0 ), + #Func( self.av.setPosHpr, 0, .45, -.25, 0, 0, 0 ), + Func( av.wrtReparentTo, self.golfKart ), + #Func( av.setPosHpr, self.SeatOffsets[seatIndex][0], self.SeatOffsets[seatIndex][1], + # self.SeatOffsets[seatIndex][2], 180, 0, 0), + #Func( self.av.setScale, self.kart.accGeomScale/self.kart.baseScale ), + #toonSitTrack, + #Func( self.av.wrtReparentTo, self.kart.rotateNode ) + ) + + return jumpTrack + + def generateToonReverseJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping out of the golf kart.""" + self.notify.debug("av.getH() = %s" % av.getH()) + def getToonJumpTrack( av, destNode ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = destNode): + dest = node.getPos(av.getParent()) + dest += Vec3(*self.JumpOutOffsets[seatIndex]) + return dest + + def getJumpHpr(av = av, node = destNode): + hpr = node.getHpr(av.getParent()) + hpr.setX( hpr.getX() + 180) + angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX()) + hpr.setX(angle) + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.1), #43 ), + Parallel( #LerpHprInterval( av, + # hpr = getJumpHpr, + # duration = .9 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = .9 ) ) + ) + ) + return toonJumpTrack + + toonJumpTrack = getToonJumpTrack( av, self.golfKart) + jumpTrack = Sequence( + toonJumpTrack, + Func( av.loop, 'neutral' ), + Func( av.wrtReparentTo, render ), + #Func( self.av.setPosHpr, self.exitMovieNode, 0,0,0,0,0,0 ), + ) + return jumpTrack + + def hideSittingToons(self): + """Hide the toons sittting on the kart. To avoid seeing them sitting in midair.""" + for avId in self.avIds: + if avId: + av = base.cr.doId2do.get(avId) + if av: + av.hide() diff --git a/toontown/src/safezone/DistributedGolfKartAI.py b/toontown/src/safezone/DistributedGolfKartAI.py new file mode 100644 index 0000000..b52ae51 --- /dev/null +++ b/toontown/src/safezone/DistributedGolfKartAI.py @@ -0,0 +1,438 @@ +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * +from TrolleyConstants import * + +from direct.distributed import DistributedObjectAI +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +from direct.directnotify import DirectNotifyGlobal +from toontown.minigame import MinigameCreatorAI +from toontown.quest import Quests +from toontown.minigame import TrolleyHolidayMgrAI +from toontown.golf import GolfManagerAI +from toontown.golf import GolfGlobals +import random + +class DistributedGolfKartAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + "DistributedGolfKartAI") + + def __init__(self, air, golfCourse, x, y, z, h, p, r): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.seats = [None, None, None, None] + self.golfCourse = golfCourse # which course does this cart lead to + self.posHpr = (x, y ,z, h, p, r) + self.color = ( random.randint(GolfGlobals.KartColors[self.golfCourse][0][0], + GolfGlobals.KartColors[self.golfCourse][0][1]), + random.randint(GolfGlobals.KartColors[self.golfCourse][1][0], + GolfGlobals.KartColors[self.golfCourse][1][1]), + random.randint(GolfGlobals.KartColors[self.golfCourse][2][0], + GolfGlobals.KartColors[self.golfCourse][2][1])) + + # For the medium course, fix it to be yellow + if(self.golfCourse == 1): + if(self.color[0] + self.color[1] <= 255): + self.color = (self.color[0], self.color[0] + self.color[1], self.color[2]) + else: + self.color = (self.color[0], 255, self.color[2]) + + # Flag that tells whether the trolley is currently accepting boarders + self.accepting = 0 + + self.trolleyCountdownTime = \ + simbase.config.GetFloat("trolley-countdown-time", + TROLLEY_COUNTDOWN_TIME) + + self.fsm = ClassicFSM.ClassicFSM('DistributedGolfKartAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['entering']), + State.State('entering', + self.enterEntering, + self.exitEntering, + ['waitEmpty']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty', 'allAboard']), + State.State('allAboard', + self.enterAllAboard, + self.exitAllAboard, + ['leaving', 'waitEmpty']), + State.State('leaving', + self.enterLeaving, + self.exitLeaving, + ['entering'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + def delete(self): + self.fsm.requestFinalState() + del self.fsm + DistributedObjectAI.DistributedObjectAI.delete(self) + + + def findAvailableSeat(self): + for i in range(len(self.seats)): + if self.seats[i] == None: + return i + return None + + def findAvatar(self, avId): + for i in range(len(self.seats)): + if self.seats[i] == avId: + return i + return None + + def countFullSeats(self): + avCounter = 0 + for i in self.seats: + if i: + avCounter += 1 + return avCounter + + def rejectingBoardersHandler(self, avId): + self.rejectBoarder(avId) + + def rejectBoarder(self, avId): + self.sendUpdateToAvatarId(avId, "rejectBoard", [avId]) + + def acceptingBoardersHandler(self, avId): + self.notify.debug("acceptingBoardersHandler") + seatIndex = self.findAvailableSeat() + if seatIndex == None: + self.rejectBoarder(avId) + else: + self.acceptBoarder(avId, seatIndex) + + def acceptBoarder(self, avId, seatIndex): + self.notify.debug("acceptBoarder") + # Make sure we have a valid seat number + assert((seatIndex >= 0) and (seatIndex <=3)) + # Make sure the seat is empty + assert(self.seats[seatIndex] == None) + # Make sure this avatar isn't already seated + if (self.findAvatar(avId) != None): + return + # Put the avatar in that seat + self.seats[seatIndex] = avId + # Add a hook that handles the case where the avatar exits + # the district unexpectedly + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + # Record the time of boarding + self.timeOfBoarding = globalClock.getRealTime() + # Tell the clients to put the avatar in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [avId]) + # Put us into waitCountdown state... If we are already there, + # this won't do anything. + self.waitCountdown() + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # Make sure the avatar is really here + if seatIndex == None: + pass + else: + # If the avatar is here, his seat is now empty. + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.clearEmptyNow(seatIndex) + #self.sendUpdate("emptySlot" + str(seatIndex), + # [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + if self.countFullSeats() == 0: + self.waitEmpty() + + def rejectingExitersHandler(self, avId): + self.rejectExiter(avId) + + def rejectExiter(self, avId): + # This doesn't have to do anything. If your exit is rejected, + # you'll know because the trolley leaves. + pass + + def acceptingExitersHandler(self, avId): + self.acceptExiter(avId) + + def acceptExiter(self, avId): + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # It is possible that the avatar exited the shard unexpectedly. + if seatIndex == None: + pass + else: + # Empty that seat + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.sendUpdate("emptySlot" + str(seatIndex), + [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + if self.countFullSeats() == 0: + self.waitEmpty() + # Wait for the avatar to be done leaving the seat, and then + # declare the emptying overwith... + taskMgr.doMethodLater(TOON_EXIT_TIME, + self.clearEmptyNow, + self.uniqueName("clearEmpty-%s" % seatIndex), + extraArgs = (seatIndex,)) + + def clearEmptyNow(self, seatIndex): + self.sendUpdate("emptySlot" + str(seatIndex), + [0, globalClockDelta.getRealNetworkTime()]) + + def clearFullNow(self, seatIndex): + # Get the avatar id + avId = self.seats[seatIndex] + # If there is no one sitting there, that is kind of strange. + if avId == 0: + self.notify.warning("Clearing an empty seat index: " + + str(seatIndex) + " ... Strange...") + else: + # Empty that seat + self.seats[seatIndex] = None + # Tell the clients that the avatar is no longer in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [0]) + # If the avatar isn't in a seat, we don't care anymore, so + # remove the hook to handle unexpected exits. + self.ignore(self.air.getAvatarExitEvent(avId)) + + def d_setState(self, state): + self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()]) + + def getState(self): + return self.fsm.getCurrentState().getName() + + def requestBoard(self, *args): + self.notify.debug("requestBoard") + avId = self.air.getAvatarIdFromSender() + if (self.findAvatar(avId) != None): + self.notify.warning("Ignoring multiple requests from %s to board." % (avId)) + return + + av = self.air.doId2do.get(avId) + if av: + newArgs = (avId,) + args + # Only toons with hp greater than 0 may board the trolley. + if (av.hp > 0) and self.accepting: + self.acceptingBoardersHandler(*newArgs) + else: + self.rejectingBoardersHandler(*newArgs) + else: + self.notify.warning( + "avid: %s does not exist, but tried to board a trolley" % avId + ) + + def requestExit(self, *args): + self.notify.debug("requestExit") + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + if av: + newArgs = (avId,) + args + if self.accepting: + self.acceptingExitersHandler(*newArgs) + else: + self.rejectingExitersHandler(*newArgs) + else: + self.notify.warning( + "avId: %s does not exist, but tried to exit a trolley" % avId + ) + + ##### How you start up the trolley ##### + def start(self): + self.enter() + + ##### Off state ##### + + def enterOff(self): + self.accepting = 0 + # Maybe this task cleanup shouldn't be here, but I didn't know + # where else to put it, since emptying the seats isn't associated + # with any particular task. Perhaps I should have made a nested + # State machine of GolfKartOn, or some such, but it seemed like a lot + # of work for a few crummy tasks. + + # If we don't have a doId yet, we can't possibly have these + # tasks running. + if hasattr(self, "doId"): + for seatIndex in range(4): + taskMgr.remove(self.uniqueName("clearEmpty-" + + str(seatIndex))) + + def exitOff(self): + self.accepting = 0 + + ##### Entering state ##### + + def enter(self): + self.fsm.request('entering') + + def enterEntering(self): + self.d_setState('entering') + self.accepting = 0 + self.seats = [None, None, None, None] + taskMgr.doMethodLater(TROLLEY_ENTER_TIME, self.waitEmptyTask, + self.uniqueName('entering-timer')) + + def exitEntering(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('entering-timer')) + + ##### WaitEmpty state ##### + + def waitEmptyTask(self, task): + self.waitEmpty() + return Task.done + + def waitEmpty(self): + self.fsm.request("waitEmpty") + + def enterWaitEmpty(self): + self.d_setState('waitEmpty') + self.accepting = 1 + + def exitWaitEmpty(self): + self.accepting = 0 + + ##### WaitCountdown state ##### + + def waitCountdown(self): + self.fsm.request("waitCountdown") + + def enterWaitCountdown(self): + self.d_setState('waitCountdown') + self.accepting = 1 + # Start the countdown... + taskMgr.doMethodLater(self.trolleyCountdownTime, self.timeToGoTask, + self.uniqueName('countdown-timer')) + + def timeToGoTask(self, task): + # It is possible that the players exited the district + if self.countFullSeats() > 0: + self.allAboard() + else: + self.waitEmpty() + return Task.done + + def exitWaitCountdown(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('countdown-timer')) + + ##### AllAboard state ##### + + def allAboard(self): + self.fsm.request("allAboard") + + def enterAllAboard(self): + self.accepting = 0 + currentTime = globalClock.getRealTime() + + elapsedTime = currentTime - self.timeOfBoarding + self.notify.debug("elapsed time: " + str(elapsedTime)) + waitTime = max(TOON_BOARD_TIME - elapsedTime, 0) + taskMgr.doMethodLater(waitTime, self.leaveTask, + self.uniqueName('waitForAllAboard')) + + def exitAllAboard(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('waitForAllAboard')) + + ##### Leaving state ##### + + def leaveTask(self, task): + # It is possible that the players exited the district + if self.countFullSeats() > 0: + self.leave() + else: + self.waitEmpty() + return Task.done + + def leave(self): + self.fsm.request("leaving") + + def enterLeaving(self): + self.d_setState('leaving') + self.accepting = 0 + taskMgr.doMethodLater(TROLLEY_EXIT_TIME, self.trolleyLeftTask, + self.uniqueName('leaving-timer')) + + def trolleyLeftTask(self, task): + self.trolleyLeft() + return Task.done + + def trolleyLeft(self): + numPlayers = self.countFullSeats() + + avIdList = [] + # It is possible the players exited the district + if (numPlayers > 0): + for seatIndex in range(len(self.seats)): + avId = self.seats[seatIndex] + avIdList.append(avId) + # Clear the fill slot + self.clearFullNow(seatIndex) + + golfZone = GolfManagerAI.GolfManagerAI().readyGolfCourse(avIdList, + self.golfCourse) + for avId in avIdList: + # Tell each player on the trolley that they should enter the + # minigame now. + if avId: + assert(avId > 0) + self.sendUpdateToAvatarId(avId, "setGolfZone", [golfZone, 0]) + #self.sendUpdateToAvatarId(avId, "setMinigameZone", + # [minigameZone, minigameId]) + else: + self.notify.warning("The trolley left, but was empty.") + + # Switch back into entering mode. + self.enter() + + def exitLeaving(self): + self.accepting = 0 + self.color = ( random.randint(GolfGlobals.KartColors[self.golfCourse][0][0], + GolfGlobals.KartColors[self.golfCourse][0][1]), + random.randint(GolfGlobals.KartColors[self.golfCourse][1][0], + GolfGlobals.KartColors[self.golfCourse][1][1]), + random.randint(GolfGlobals.KartColors[self.golfCourse][2][0], + GolfGlobals.KartColors[self.golfCourse][2][1])) + + # For the medium course, fix it to be yellow + if(self.golfCourse == 1): + if(self.color[0] + self.color[1] <= 255): + self.color = (self.color[0], self.color[0] + self.color[1], self.color[2]) + else: + self.color = (self.color[0], 255, self.color[2]) + + self.sendUpdate("setColor", + [self.color[0], self.color[1], self.color[2]]) + taskMgr.remove(self.uniqueName('leaving-timer')) + + def getGolfCourse(self): + return self.golfCourse + + def getPosHpr( self ): + return self.posHpr + + def getColor(self): + return self.color diff --git a/toontown/src/safezone/DistributedMMPiano.py b/toontown/src/safezone/DistributedMMPiano.py new file mode 100644 index 0000000..6549c3f --- /dev/null +++ b/toontown/src/safezone/DistributedMMPiano.py @@ -0,0 +1,304 @@ +""" DistributedMMPiano module: contains the DistributedMMPiano + class which represents the client version of the round, + spinning piano in Minnie's Melodyland safezone.""" + +from pandac.PandaModules import * +from direct.task.Task import Task +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * + +from direct.distributed import DistributedObject +from pandac.PandaModules import NodePath +from toontown.toonbase import ToontownGlobals + +# This is the amount of time, in seconds, that must elapse between two +# subsequent "change direction" requests from the same client are +# processed. It serves to limit the "bounce" effect from +# inadvertently generating two change direction requests in a short +# time. +ChangeDirectionDebounce = 1.0 + +# This is the amount of time over which to effect a change in +# velocity. Having this be nonzero allows the piano to gradually spin +# up or spin down in response to a message from the server, which +# avoids "popping" as it suddenly resets to a new position based on +# the new velocity. +ChangeDirectionTime = 1.0 + +class DistributedMMPiano(DistributedObject.DistributedObject): + """ + //////////////////////////////////////////////////////////////////// + // + // DistributedMMPiano: client side version of the spinning piano + // located in Minnie's Melodyland safezone. + // Handle's calculating the spin of the piano + // as well as detecting and letting the + // 'playground' know when the local toon + // steps onto the piano. + // + //////////////////////////////////////////////////////////////////// + """ + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + + # spin information + # + self.spinStartTime = 0.0 + self.rpm = 0.0 + self.degreesPerSecond = (self.rpm/60.0) * 360.0 + self.offset = 0.0 + self.oldOffset = 0.0 + self.lerpStart = 0.0 + self.lerpFinish = 1.0 + + self.speedUpSound = None + self.changeDirectionSound = None + self.lastChangeDirection = 0.0 + + def generate(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // reintroduced to the world, either for the first time + // or from the cache. + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.piano = base.cr.playGame.hood.loader.piano + base.cr.parentMgr.registerParent(ToontownGlobals.SPMinniesPiano, + self.piano) + self.accept('enterlarge_round_keyboard_collisions', self.__handleOnFloor) + self.accept('exitlarge_round_keyboard_collisions', self.__handleOffFloor) + + # We want to have some interaction from the toons in the + # world. For now, if a toon bumps into a mailbox or planter + # or something, we consider that triggering the + # "changeDirection" button. Weird to the user--what does a + # trashcan have to do with changing the piano?--but in the + # absence of a "push me" button, this is the next best thing. + self.accept('entero7', self.__handleChangeDirectionButton) + + # We need some handy sound effects to play in response to the + # above. + self.speedUpSound = base.loadSfx('phase_6/audio/sfx/SZ_MM_gliss.mp3') + self.changeDirectionSound = base.loadSfx('phase_6/audio/sfx/SZ_MM_cymbal.mp3') + + self.__setupSpin() + DistributedObject.DistributedObject.generate(self) + + def __setupSpin(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: Start spinning the piano + // Parameters: ts, time at which the server initially started + // spinning the piano + // Changes: self.spinStartTime + //////////////////////////////////////////////////////////////////// + """ + # create a task that will update the heading of the piano often + # in order to make it 'spin', also remember what time we first + # started spinning so we can calculate the same heading on all + # clients + # + taskMgr.add(self.__updateSpin, self.taskName("pianoSpinTask")) + + def __stopSpin(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: Stop spinning the piano + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + taskMgr.remove(self.taskName("pianoSpinTask")) + + def __updateSpin(self,task): + """ + //////////////////////////////////////////////////////////////////// + // Function: set the new heading/facing for the piano based + // on the current time and the time that the server + // says it started the piano spinning + // Parameters: task, task that called this function + // Changes: + //////////////////////////////////////////////////////////////////// + """ + now = globalClock.getFrameTime() + + # What is the current offset? This depends on how much time + # has elapsed since we got the last speed update message, + # since after we get a new update-speed message, we gradually + # lerp the offset from the old value to the new value. + if now > self.lerpFinish: + offset = self.offset + elif now > self.lerpStart: + t = (now - self.lerpStart) / (self.lerpFinish - self.lerpStart) + offset = self.oldOffset + t * (self.offset - self.oldOffset) + else: + offset = self.oldOffset + + heading = \ + (self.degreesPerSecond * (now - self.spinStartTime)) + \ + offset + self.piano.setHprScale( heading % 360.0, 0.0, 0.0, + 1.0, 1.0, 1.0 ) + return Task.cont + + def disable(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // removed from active duty and stored in a cache. + // Parameters: + // Changes: + //////////////////////////////////////////////////////////////////// + """ + del self.piano + base.cr.parentMgr.unregisterParent(ToontownGlobals.SPMinniesPiano) + + self.ignore('enterlarge_round_keyboard_collisions') + self.ignore('exitlarge_round_keyboard_collisions') + + self.ignore('entero7') + self.ignore('entericon_center_collisions') + + self.speedUpSound = None + self.changeDirectionSound = None + self.__stopSpin() + DistributedObject.DistributedObject.disable(self) + + def setSpeed(self, rpm, offset, timestamp): + """ + //////////////////////////////////////////////////////////////////// + // Function: Updates the speed of the piano from the server + // Parameters: rpm, the revolutions per minute of the piano + // offset, the orientation of the piano at the + // indicated start time, in degrees. + // timestamp, the 'start' time at which the piano + // was at the orientation indicated by offset. + // Changes: + //////////////////////////////////////////////////////////////////// + """ + timestamp = globalClockDelta.networkToLocalTime(timestamp) + degreesPerSecond = (rpm/60.0) * 360.0 + now = globalClock.getFrameTime() + + # First, compute what the offset should be to keep the same + # heading given the new rpm and timestamp. + oldHeading = \ + (self.degreesPerSecond * (now - self.spinStartTime)) + \ + self.offset + oldHeading = oldHeading % 360.0 + oldOffset = oldHeading - (degreesPerSecond * (now - timestamp)) + + # Now update to the new rpm and timestamp. + self.rpm = rpm + self.degreesPerSecond = degreesPerSecond + self.offset = offset + self.spinStartTime = timestamp + + # Make sure the old and new offsets are within 180 degrees of + # each other, so we don't try to lerp the wrong way around the + # circle. + while oldOffset - offset < -180.0: + oldOffset += 360.0 + + while oldOffset - offset > 180.0: + oldOffset -= 360.0 + + # Now gradually lerp from oldOffset to offset, to effect a + # gradual change in velocity. + self.oldOffset = oldOffset + self.lerpStart = now + self.lerpFinish = timestamp + ChangeDirectionTime + + def playSpeedUp(self, avId): + """playSpeedUp(self, uint32 avId) + + Plays the speed-up sound effect in response to some *other* + player (indicated by avId) hitting the speed-up button. (We + don't play the sound effect if avId is ourselves, because + we've already played it directly.) + """ + if avId != base.localAvatar.doId: + base.playSfx(self.speedUpSound) + + def playChangeDirection(self, avId): + """playChangeDirection(self, uint32 avId) + + Plays the speed-up sound effect in response to some *other* + player (indicated by avId) hitting the speed-up button. (We + don't play the sound effect if avId is ourselves, because + we've already played it directly.) + """ + if avId != base.localAvatar.doId: + base.playSfx(self.changeDirectionSound) + + def __handleOnFloor(self, collEntry): + """ + //////////////////////////////////////////////////////////////////// + // Function: called when the local toon steps on the piano + // Parameters: collEntry, what floor local toon collided with + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.cr.playGame.getPlace().activityFsm.request('OnPiano') + + # Start the piano turning, or speed it up. + self.sendUpdate('requestSpeedUp', []) + base.playSfx(self.speedUpSound) + + def __handleOffFloor(self, collEntry): + """ + //////////////////////////////////////////////////////////////////// + // Function: called when the local toon steps off the piano + // Parameters: collEntry, what floor the local toon is now off of + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.cr.playGame.getPlace().activityFsm.request('off') + + + def __handleSpeedUpButton(self, collEntry): + """ + //////////////////////////////////////////////////////////////////// + // Function: called when the local toon bumps into the + // "speed up" button. This speeds up the piano a + // notch and gives the user a satisfactory + // reaction indication. + // Parameters: collEntry, ignored. + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.sendUpdate('requestSpeedUp', []) + base.playSfx(self.speedUpSound) + + def __handleChangeDirectionButton(self, collEntry): + """ + //////////////////////////////////////////////////////////////////// + // Function: called when the local toon bumps into the + // "speed up" button. This speeds up the piano a + // notch and gives the user a satisfactory + // reaction indication. + // Parameters: collEntry, ignored. + // Changes: + //////////////////////////////////////////////////////////////////// + """ + now = globalClock.getFrameTime() + if now - self.lastChangeDirection < ChangeDirectionDebounce: + # Too soon. + return + + self.lastChangeDirection = now + self.sendUpdate('requestChangeDirection', []) + base.playSfx(self.changeDirectionSound) + + +# History +# +# 08Oct01 jlbutler created. +# 08Oct22 drose modified to change speeds from time to time. +# + diff --git a/toontown/src/safezone/DistributedMMPianoAI.py b/toontown/src/safezone/DistributedMMPianoAI.py new file mode 100644 index 0000000..0affcca --- /dev/null +++ b/toontown/src/safezone/DistributedMMPianoAI.py @@ -0,0 +1,172 @@ +""" DistributedMMPianoAI module: contains the DistributedMMPiano + class which represents the server version of the round, + spinning piano in Minnie's Melodyland safezone.""" + +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObjectAI +from direct.task import Task + +# These are the various speeds the piano cycles through as people push +# the button. +PianoSpeeds = [ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, + 8.0, 10.0, 12.0, 14.0, 16.0, 18.0 + ] + +PianoMaxSpeed = PianoSpeeds[len(PianoSpeeds) - 1] + +# This is the factor by which the piano slows down every 10 seconds +# when people are not pushing the button. 1.0 would keep a constant +# speed; 0.0 would instantly stop. +PianoSlowDownFactor = 0.7 +PianoSlowDownInterval = 10.0 +PianoSlowDownMinimum = 0.1 + +class DistributedMMPianoAI(DistributedObjectAI.DistributedObjectAI): + """ + //////////////////////////////////////////////////////////////////// + // + // DistributedMMPianoAI: server side version of the spinning piano + // located in Minnie's Melodyland safezone. + // Handle's state management of the piano. + // + //////////////////////////////////////////////////////////////////// + """ + def __init__(self, air): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.spinStartTime = 0.0 + self.rpm = 0.0 + self.degreesPerSecond = (self.rpm/60.0) * 360.0 + self.offset = 0.0 + self.direction = 1 + return None + + def delete(self): + DistributedObjectAI.DistributedObjectAI.delete(self) + + def requestSpeedUp(self): + """ + requestSpeedUp(self) + + Sent from a client to speed up the piano, if allowed. + + """ + if self.rpm < PianoMaxSpeed: + # Look for the first speed greater then self.rpm. + for speed in PianoSpeeds: + if speed > self.rpm: + break + + self.updateSpeed(speed, self.direction) + + self.d_playSpeedUp(self.air.getAvatarIdFromSender()) + self.__slowDownLater() + + def requestChangeDirection(self): + """ + requestChangeDirection(self) + + Sent from a client to reverse the direction of the piano. + + """ + rpm = self.rpm + if rpm == 0.0: + rpm = PianoSpeeds[0] + + self.updateSpeed(rpm, -self.direction) + self.__slowDownLater() + + self.d_playChangeDirection(self.air.getAvatarIdFromSender()) + + def d_setSpeed(self, rpm, offset, startTime): + """d_setSpeed(self, float rpm, float offset, float startTime) + Tells the clients about the new rotate speed of the piano. + """ + self.sendUpdate('setSpeed', [rpm, offset, globalClockDelta.localToNetworkTime(startTime)]) + + def d_playSpeedUp(self, avId): + """d_playSpeedUp(self, uint32 avId) + Tells the clients (other than avId) to play the speed-up + sound effect. + """ + self.sendUpdate('playSpeedUp', [avId]) + + def d_playChangeDirection(self, avId): + """d_playChangeDirection(self, uint32 avId) + Tells the clients (other than avId) to play the change-direction + sound effect. + """ + self.sendUpdate('playChangeDirection', [avId]) + + + ### Support functions ### + def updateSpeed(self, rpm, direction): + """updateSpeed(self, rpm, direction) + + Changes the speed of the piano to the indicated RPM, while + maintaining continuity with its previous position. + + """ + now = globalClock.getRealTime() + + # First, determine its current orientation at this time. + heading = \ + (self.degreesPerSecond * (now - self.spinStartTime)) + \ + self.offset + + # That becomes the new offset as of the new start time, now. + self.rpm = rpm + self.direction = direction + self.degreesPerSecond = (rpm/60.0) * 360.0 * direction + self.offset = heading % 360.0 + self.spinStartTime = now + + # Inform all the clients. + self.d_setSpeed(self.rpm * self.direction, self.offset, + self.spinStartTime) + + + ### How you start up the piano ### + def start(self): + # Nothing to do here at the moment. The piano starts up + # stationary, and starts to move when clients bump into stuff. + return None + + def __slowDownLater(self): + # Slow down the piano after a period of time. + taskName = self.uniqueName("slowDown") + taskMgr.remove(taskName) + taskMgr.doMethodLater(PianoSlowDownInterval, self.__slowDown, taskName) + + def __slowDown(self, task): + """__slowDown(self) + + This is called every PianoSlowDownInterval seconds while the + piano is turning, to cause its speed to gradually decay. + + """ + rpm = self.rpm * PianoSlowDownFactor + if rpm < PianoSlowDownMinimum: + # Consider it stopped. + self.updateSpeed(0.0, self.direction) + + else: + self.updateSpeed(rpm, self.direction) + self.__slowDownLater() + + return Task.done + + + + + +# History +# +# 08Oct01 jlbutler created. +# 08Oct22 drose modified to change speeds from time to time. +# diff --git a/toontown/src/safezone/DistributedMMTreasure.py b/toontown/src/safezone/DistributedMMTreasure.py new file mode 100644 index 0000000..e392e1f --- /dev/null +++ b/toontown/src/safezone/DistributedMMTreasure.py @@ -0,0 +1,7 @@ +import DistributedSZTreasure + +class DistributedMMTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_6/models/props/music_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" diff --git a/toontown/src/safezone/DistributedMMTreasureAI.py b/toontown/src/safezone/DistributedMMTreasureAI.py new file mode 100644 index 0000000..e87933f --- /dev/null +++ b/toontown/src/safezone/DistributedMMTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedMMTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedOZTreasure.py b/toontown/src/safezone/DistributedOZTreasure.py new file mode 100644 index 0000000..8f737ff --- /dev/null +++ b/toontown/src/safezone/DistributedOZTreasure.py @@ -0,0 +1,7 @@ +import DistributedSZTreasure + +class DistributedOZTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_6/models/props/acorn_treasure" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" diff --git a/toontown/src/safezone/DistributedOZTreasureAI.py b/toontown/src/safezone/DistributedOZTreasureAI.py new file mode 100644 index 0000000..3e0dae6 --- /dev/null +++ b/toontown/src/safezone/DistributedOZTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedOZTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedPartyGate.py b/toontown/src/safezone/DistributedPartyGate.py new file mode 100644 index 0000000..8efb7b2 --- /dev/null +++ b/toontown/src/safezone/DistributedPartyGate.py @@ -0,0 +1,271 @@ +#------------------------------------------------------------------------------- +# Contact: Shawn Patton +# Created: Sep 2008 +# +# Purpose: Client side of the party hat which is where toon's go to access +# public parties. +#------------------------------------------------------------------------------- +from pandac.PandaModules import Point3, CollisionSphere, CollisionNode, BitMask32, Vec3, NodePath, TextNode, Vec4 +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLocalizer +from direct.interval.IntervalGlobal import Sequence, Parallel, SoundInterval +from direct.interval.FunctionInterval import Wait +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.gui import DirectLabel +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals +from toontown.parties.ServerTimeGui import ServerTimeGui +from toontown.parties.PublicPartyGui import PublicPartyGui +from toontown.parties import PartyGlobals + +class DistributedPartyGate(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPartyGate") + + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + self.publicPartyChooseGuiDoneEvent = "doneChoosingPublicParty" + self.publicPartyGui = PublicPartyGui(self.publicPartyChooseGuiDoneEvent) + self.publicPartyGui.stash() + self.loadClockSounds() + self.hourSoundInterval = Sequence() + self.accept('stoppedAsleep', self.handleSleep) + + def loadClockSounds(self): + self.clockSounds = [] + for i in range(1,13): + if i < 10: + si = "0%d"%i + else: + si = "%d"%i + self.clockSounds.append(base.loadSfx("phase_4/audio/sfx/clock%s.mp3"%si)) + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + + loader = self.cr.playGame.hood.loader + partyGate = loader.geom.find('**/partyGate_grp') + if partyGate.isEmpty(): + self.notify.warning('Could not find partyGate_grp in loader.geom') + return + self.clockFlat = partyGate.find("**/clock_flat") + collSphere = CollisionSphere(0, 0, 0, 6.9) + collSphere.setTangible(1) + self.partyGateSphere = CollisionNode("PartyGateSphere") + self.partyGateSphere.addSolid(collSphere) + self.partyGateCollNodePath = partyGate.find("**/partyGate_stepsLocator").attachNewNode(self.partyGateSphere) + self.__enableCollisions() +# self.tunnelOrigin = NodePath("PartyGateTunnelOrigin") +# self.tunnelOrigin.reparentTo(partyGate) +# self.tunnelOrigin.setPos(partyGate.find("**/clockText_locator").getPos() + Point3(0.0, 0.0, -12.0)) + + self.toontownTimeGui = ServerTimeGui(partyGate, hourCallback=self.hourChange) + self.toontownTimeGui.setPos(partyGate.find("**/clockText_locator").getPos()+Point3(0.0,0.0,-0.2)) + self.toontownTimeGui.setHpr(partyGate.find("**/clockText_locator").getHpr()) + self.toontownTimeGui.setScale(12.0, 1.0, 26.0) + self.toontownTimeGui.amLabel.setPos(-0.035,0,-0.032) + self.toontownTimeGui.amLabel.setScale(0.5) + self.toontownTimeGui.updateTime() + self.setupSignText() + + def setupSignText(self): + """Attach text to the left and right signs""" + loader = self.cr.playGame.hood.loader + partyGate = loader.geom.find('**/partyGateSignGroup') + if partyGate.isEmpty(): + self.notify.warning('Could not find partyGate_grp in loader.geom') + return + gateFont = ToontownGlobals.getMinnieFont() + leftSign = partyGate.find("**/signTextL_locatorBack") + signScale = 0.35 + wordWrap = 8 + leftText = DirectLabel.DirectLabel( + parent = leftSign, + pos = (0, 0.0, 0.0), + relief = None, + text = TTLocalizer.PartyGateLeftSign, + text_align = TextNode.ACenter, + text_font = gateFont, + text_wordwrap = wordWrap, + text_fg = Vec4(0.7, 0.3, 0.3, 1.0), + scale = signScale, + ) + rightSign = partyGate.find("**/signTextR_locatorFront") + rightText = DirectLabel.DirectLabel( + parent = rightSign, + pos = (0, 0.0, 0.0), + relief = None, + text = TTLocalizer.PartyGateRightSign, + text_align = TextNode.ACenter, + text_font = gateFont, + text_wordwrap = wordWrap, + text_fg = Vec4(0.7, 0.3, 0.3, 1.0), + scale = signScale, + ) + + def announceGenerate(self): + DistributedObject.DistributedObject.announceGenerate(self) + if ToontownGlobals.dnaMap.has_key(self.zoneId): + playground = ToontownGlobals.dnaMap[self.zoneId] + else: + playground = ToontownGlobals.dnaMap[2000] + self.toontownTimeGui.hourLabel["text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground] + self.toontownTimeGui.colonLabel["text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground] + self.toontownTimeGui.minutesLabel["text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground] + self.toontownTimeGui.amLabel["text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground] + + def disable(self): + DistributedObject.DistributedObject.disable(self) + self.__disableCollisions() + self.toontownTimeGui.ival.finish() + self.hourSoundInterval.finish() + if self.publicPartyGui: + self.publicPartyGui.stash() + self.publicPartyGui.destroy() + self.publicPartyGui = None + + def delete(self): + DistributedObject.DistributedObject.delete(self) + self.toontownTimeGui.destroy() + del self.toontownTimeGui + self.hourSoundInterval.finish() + del self.hourSoundInterval + del self.clockFlat + if self.publicPartyGui: + self.publicPartyGui.destroy() + del self.publicPartyGui + self.partyGateCollNodePath.removeNode() + del self.partyGateCollNodePath + self.ignoreAll() + + def showMessage(self, message): + self.messageDoneEvent = self.uniqueName("messageDoneEvent") + self.acceptOnce(self.messageDoneEvent, self.__handleMessageDone) + self.messageGui = TTDialog.TTGlobalDialog( + doneEvent = self.messageDoneEvent, + message = message, + style = TTDialog.Acknowledge, + ) + + def __handleMessageDone(self): + self.ignore(self.messageDoneEvent) + self.freeAvatar() + self.messageGui.cleanup() + self.messageGui = None + + def __handleAskDone(self): + DistributedPartyGate.notify.debug("__handleAskDone") + self.ignore(self.publicPartyChooseGuiDoneEvent) + doneStatus = self.publicPartyGui.doneStatus + self.publicPartyGui.stash() + if doneStatus is None: + # They don't want to party... just let them walk away from the hat + self.freeAvatar() + return + self.sendUpdate("partyChoiceRequest", [base.localAvatar.doId, doneStatus[0], doneStatus[1]]) + + def partyRequestDenied(self, reason): + """ + Called by the AI when the player's request to join a public party was denied. + """ + DistributedPartyGate.notify.debug("partyRequestDenied( reason=%s )" %PartyGlobals.PartyGateDenialReasons.getString( reason ) ) + # let the local toon know that they were denied + # TODO-parties: tell player through gui + if reason == PartyGlobals.PartyGateDenialReasons.Unavailable: + self.showMessage(TTLocalizer.PartyGatePartyUnavailable) + elif reason == PartyGlobals.PartyGateDenialReasons.Full: + self.showMessage(TTLocalizer.PartyGatePartyFull) + + def setParty(self, partyInfoTuple): + """ + Gets called by the AI server with the approved partyId. + """ + DistributedPartyGate.notify.debug("setParty") + + self.freeAvatar() + if partyInfoTuple[0] == 0: + DistributedPartyGate.notify.debug("Public Party closed before toon could get to it.") + return + + # We now need to enter the party with the given partyId, that is, move + # our toon toward the hat entrance and do the appropriate state transition + shardId, zoneId, numberOfGuests, hostName, activityIds, lane = partyInfoTuple + if base.localAvatar.defaultShard == shardId: + shardId = None + base.cr.playGame.getPlace().requestLeave({ + "loader": "safeZoneLoader", + "where": "party", + "how" : "teleportIn", + "hoodId" : ToontownGlobals.PartyHood, + "zoneId" : zoneId, + "shardId" : shardId, + "avId" : -1, +# "partyHat" : True, +# "tunnelOrigin" : self.tunnelOrigin, + }) + + def freeAvatar(self): + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + + def hourChange(self, currentHour): + currentHour = currentHour%12 + if currentHour == 0: + currentHour = 12 + self.hourSoundInterval = Parallel() + # Make a sequence with all the clock sounds + seq1 = Sequence() + for i in range(currentHour): + seq1.append(SoundInterval(self.clockSounds[i])) + seq1.append(Wait(0.2)) + # Now make a sequence that will deform the clock face + timeForEachDeformation = seq1.getDuration() / currentHour + seq2 = Sequence() + for i in range(currentHour): + seq2.append(self.clockFlat.scaleInterval(timeForEachDeformation/2.0, Vec3(0.9, 1.0, 1.2), blendType = 'easeInOut')) + seq2.append(self.clockFlat.scaleInterval(timeForEachDeformation/2.0, Vec3(1.2, 1.0, 0.9), blendType = 'easeInOut')) + seq2.append(self.clockFlat.scaleInterval(timeForEachDeformation/2.0, Vec3(1.0, 1.0, 1.0), blendType = 'easeInOut')) + # Now parallel the two together + self.hourSoundInterval.append(seq1) + self.hourSoundInterval.append(seq2) + self.hourSoundInterval.start() + + def handleEnterGateSphere(self, collEntry): + self.notify.debug("Entering steps Sphere....") + # Freeze the toon, don't let him walk away... + base.cr.playGame.getPlace().fsm.request('stopped') + self.sendUpdate("getPartyList", [base.localAvatar.doId]) + + def listAllPublicParties(self, publicPartyInfo): + """ + Called from DistributedPartyGateAI with a tuple of all the public party + information as told to it by the DistributedPartyManagerAI in order of + newest party to oldest party. + ( shardId, zoneId, numberOfGuests, hostName, activityIds, minLeft ) + """ + self.notify.debug("listAllPublicParties : publicPartyInfo = %s" % publicPartyInfo) + self.acceptOnce(self.publicPartyChooseGuiDoneEvent, self.__handleAskDone) + self.publicPartyGui.refresh(publicPartyInfo) + self.publicPartyGui.unstash() + + def __enableCollisions(self): + # start listening for toons to enter. + self.accept('enterPartyGateSphere', self.handleEnterGateSphere) + self.partyGateSphere.setCollideMask(OTPGlobals.WallBitmask) + + def __disableCollisions(self): + # stop listening for toons. + self.ignore('enterPartyGateSphere') + self.partyGateSphere.setCollideMask(BitMask32(0)) + + def handleSleep(self): + if hasattr(self, 'messageGui'): + self.__handleMessageDone() \ No newline at end of file diff --git a/toontown/src/safezone/DistributedPartyGateAI.py b/toontown/src/safezone/DistributedPartyGateAI.py new file mode 100644 index 0000000..caa7f69 --- /dev/null +++ b/toontown/src/safezone/DistributedPartyGateAI.py @@ -0,0 +1,68 @@ +#------------------------------------------------------------------------------- +# Contact: Shawn Patton +# Created: Sep 2008 +# +# Purpose: AI side of the party hat which is where toon's go to access public +# parties. +#------------------------------------------------------------------------------- + +#from otp.ai.AIBase import * + +from direct.distributed import DistributedObjectAI +from direct.task import Task +from direct.directnotify import DirectNotifyGlobal +from toontown.parties import PartyGlobals + +class DistributedPartyGateAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPartyGateAI") + + def __init__(self, air): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + def getPartyList(self, avId): + senderId = self.air.getAvatarIdFromSender() + if avId != senderId: + self.air.writeServerEvent('suspicious', senderId, 'someone else trying to get a list of public parties for: avId = %d' % avId) + return + self.sendUpdateToAvatarId(avId, "listAllPublicParties", [self.air.partyManager.getAllPublicParties()]) + + def partyChoiceRequest(self, avId, shardId, zoneId): + # A toon would like to go to this party. + # We need to check if the party still has room and is still going (since + # a good bit of time might have passed while they were looking at the + # options). + DistributedPartyGateAI.notify.debug("partyChoiceRequest : avId = %d, shardId = %d, zoneId = %d " % (avId, shardId, zoneId)) + senderId = self.air.getAvatarIdFromSender() + + if avId != senderId: + self.air.writeServerEvent('suspicious', senderId, 'someone else trying to choose a public party for: avId = %d' % avId) + return + + allPublicPartyInfo = self.air.partyManager.getAllPublicParties() + + if len(allPublicPartyInfo) == 0: + # there are no parties at all + DistributedPartyGateAI.notify.debug("partyChoiceRequest denied as no parties exist") + self.sendUpdateToAvatarId(avId, "partyRequestDenied", [PartyGlobals.PartyGateDenialReasons.Unavailable]) + else: + for partyTuple in allPublicPartyInfo: + if partyTuple[0] == shardId and partyTuple[1] == zoneId: + # the specific party they requested has been found + if partyTuple[2] < PartyGlobals.MaxToonsAtAParty: + DistributedPartyGateAI.notify.debug("partyChoiceRequest accepted") + self.sendUpdateToAvatarId(avId, "setParty", [partyTuple]) + else: + DistributedPartyGateAI.notify.debug("partyChoiceRequest denied as number at party is %d and max allowed is %d" %(partyTuple[2], PartyGlobals.MaxToonsAtAParty)) + self.sendUpdateToAvatarId(avId, "partyRequestDenied", [PartyGlobals.PartyGateDenialReasons.Full]) + break # prevent else clause from running + else: + # the desired party was not found + DistributedPartyGateAI.notify.debug("partyChoiceRequest denied as party could not be found") + self.sendUpdateToAvatarId(avId, "partyRequestDenied", [PartyGlobals.PartyGateDenialReasons.Unavailable]) + + # We might want to also send a lane for the toon to use when he walks + # through the hat... if so, we should do a wait of like 3 seconds, and + # then free the lane we just sent... diff --git a/toontown/src/safezone/DistributedPicnicBasket.py b/toontown/src/safezone/DistributedPicnicBasket.py new file mode 100644 index 0000000..036eab3 --- /dev/null +++ b/toontown/src/safezone/DistributedPicnicBasket.py @@ -0,0 +1,748 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * + +from toontown.golf import GolfGlobals +from toontown.toonbase import ToontownGlobals +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from toontown.distributed import DelayDelete +from toontown.toonbase.ToontownTimer import ToontownTimer + +from direct.task.Task import Task +from direct.showbase import PythonUtil + +from toontown.toon import ToonDNA + +from direct.showbase import RandomNumGen +from toontown.battle.BattleSounds import * + +class DistributedPicnicBasket(DistributedObject.DistributedObject): + + seatState = Enum("Empty, Full, Eating") + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPicnicBasket") + + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + + self.localToonOnBoard = 0 + self.seed = 0 + self.random = None + self.picnicCountdownTime = \ + base.config.GetFloat("picnic-countdown-time", + ToontownGlobals.PICNIC_COUNTDOWN_TIME) + self.picnicBasketTrack = None # only one track contains the picnic basket shrink/grow + + self.fsm = ClassicFSM.ClassicFSM('DistributedTrolley', + [State.State('off', + self.enterOff, + self.exitOff, + ['waitEmpty','waitCountdown']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + # Tracks on toons, for starting and stopping + # stored by avId : track. There is only a need for one at a time, + # in fact the point of the dict is to ensure only one is playing at a time + self.__toonTracks = {} + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + + # Get the state machine stuff for playGame + self.loader = self.cr.playGame.hood.loader + self.foodLoader = ['phase_6/models/golf/picnic_sandwich.bam', + 'phase_6/models/golf/picnic_apple.bam', + 'phase_6/models/golf/picnic_cupcake.bam', + 'phase_6/models/golf/picnic_chocolate_cake.bam' + ] + self.fullSeat = [] + self.food = [] + for i in range(4): + self.food.append(None) + self.fullSeat.append(self.seatState.Empty) + self.picnicItem = 0 + + def announceGenerate(self): + """Setup other fields dependent on the required fields.""" + + self.picnicTable = self.loader.geom.find("**/*picnic_table_" + str(self.tableNumber)) + self.picnicTableSphereNodes = [] + + self.numSeats = 4 + self.seats = [] + self.jumpOffsets = [] + self.basket = None + for i in range(self.numSeats): + self.seats.append(self.picnicTable.find("**/*seat%d" % (i+1))) + self.jumpOffsets.append(self.picnicTable.find("**/*jumpOut%d" % (i+1))) + #debugAxis = loader.loadModel('models/misc/xyzAxis') + #debugAxis.setColorScale(1.0, 1.0, 1.0, 0.25) + #debugAxis.setTransparency(True) + #debugAxis.reparentTo(self.seats[i]) + + self.tablecloth = self.picnicTable.find("**/basket_locator") + + DistributedObject.DistributedObject.announceGenerate(self) + for i in range(self.numSeats): + self.picnicTableSphereNodes.append(self.seats[i].attachNewNode(CollisionNode('picnicTable_sphere_%d_%d' % (self.getDoId(), i)))) + self.picnicTableSphereNodes[i].node().addSolid(CollisionSphere(0, 0, 0, 2)) + + self.tableclothSphereNode = self.tablecloth.attachNewNode(CollisionNode('tablecloth_sphere')) + self.tableclothSphereNode.node().addSolid(CollisionSphere(0, 0, -1, 4)) + + angle = self.startingHpr[0] + angle -= 90 + radAngle = deg2Rad(angle) + unitVec = Vec3( math.cos(radAngle), math.sin(radAngle), 0) + unitVec *= 30.0 + self.endPos = self.startingPos + unitVec + + dist = Vec3(self.endPos - self.enteringPos).length() + wheelAngle = dist/(0.5 * 1.4 * math.pi ) * 360 + + self.seatNumber = 0 + + self.clockNode = ToontownTimer() + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.setScale(0.3) + self.clockNode.hide() + + def disable(self): + DistributedObject.DistributedObject.disable(self) + # Go to the off state when the object is put in the cache + self.fsm.request("off") + + # No more toon animating + self.clearToonTracks() + + for i in range(self.numSeats): + del self.picnicTableSphereNodes[0] + del self.picnicTableSphereNodes + + self.notify.debug("Deleted self loader " + str(self.getDoId())) + self.picnicTable.removeNode() + self.picnicBasketTrack = None + #self.kart.removeNode() + #del self.kart + + #import pdb; + #pdb.set_trace() + + def delete(self): + self.notify.debug("Golf kart getting deleted: %s" % self.getDoId()) + DistributedObject.DistributedObject.delete(self) + del self.fsm + + def setState(self, state, seed, timestamp): + self.seed = seed + if not self.random: + self.random = RandomNumGen.RandomNumGen(seed) + self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)]) + + def handleEnterPicnicTableSphere(self, i, collEntry): + # collEntry): + assert self.notify.debugStateCall(self) + self.seatNumber = i + self.notify.debug("Entering Picnic Table Sphere.... %s" % self.getDoId()) + # Put localToon into requestBoard mode. + #import pdb; pdb.set_trace() + self.loader.place.detectedPicnicTableSphereCollision(self) + + def handleEnterPicnicTable(self, i): + # Tell the server that this avatar wants to board. + assert self.notify.debugStateCall(self) + toon = base.localAvatar + self.sendUpdate("requestBoard",[i]) + + def fillSlot0(self, avId): + self.fillSlot(0, avId) + + def fillSlot1(self, avId): + self.fillSlot(1, avId) + + def fillSlot2(self, avId): + self.fillSlot(2, avId) + + def fillSlot3(self, avId): + self.fillSlot(3, avId) + + def fillSlot(self, index, avId): + assert self.notify.debugStateCall(self) + self.notify.debug( "fill Slot: %d for %d" % (index, avId) ) + if avId == 0: + # This means that the slot is now empty, and no action should + # be taken. + pass + else: + self.fullSeat[index] = self.seatState.Full + # If localToon is boarding, he needs to change state + if avId == base.localAvatar.getDoId(): + # Start the countdown clock... + self.clockNode.show() + if index == 0 or index == 3: + side = -1 + else: + side = 1 + if hasattr(self.loader.place, "trolley"): + self.loader.place.trolley.fsm.request("boarding", [self.tablecloth, side]) + else: + self.notify.warning('fillSlot no trolley in place') + self.localToonOnBoard = 1 + + # Put that toon on the table + + # If it's localToon, tell him he's on the trolley now + if avId == base.localAvatar.getDoId(): + if hasattr(self.loader.place, "trolley"): + self.loader.place.trolley.fsm.request("boarded") + # hide the exit button until basket interval is over + self.loader.place.trolley.exitButton.hide() + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + # Parent it to the trolley + toon.stopSmooth() + toon.wrtReparentTo(self.tablecloth) + sitStartDuration = toon.getDuration("sit-start") + jumpTrack = self.generateToonJumpTrack(toon, index) + track = Sequence( + jumpTrack, + Func(toon.setAnimState, "Sit", 1.0)) + # only add basket appear if there is no toons are already sitting + self.notify.debug( "### fillSlot: fullSeat = %s" % self.fullSeat) + if self.fullSeat.count(0) == 3: + self.notify.debug( "### fillSlot: adding basketAppear") + #track.append(self.generateBasketAppearTrack()) + if self.picnicBasketTrack: + self.picnicBasketTrack.finish() + waitDuration = track.getDuration() + self.picnicBasketTrack = Sequence( + Wait(waitDuration), + self.generateBasketAppearTrack()) + self.picnicBasketTrack.start() + # make a random food appear + track.append(self.generateFoodAppearTrack(index)) + # finish the rest of the staging + track.append( + Sequence( + Func(self.clearToonTrack, avId), + name = toon.uniqueName("fillTrolley"), + autoPause = 1) + ) + if avId == base.localAvatar.getDoId(): + if hasattr(self.loader.place, "trolley"): + track.append(Func(self.loader.place.trolley.exitButton.show)) + track.delayDelete = DelayDelete.DelayDelete(toon, 'PicnicBasket.fillSlot') + self.storeToonTrack(avId, track) + track.start() + + def emptySlot0(self, avId, timestamp): + self.emptySlot(0, avId, timestamp) + + def emptySlot1(self, avId, timestamp): + self.emptySlot(1, avId, timestamp) + + def emptySlot2(self, avId, timestamp): + self.emptySlot(2, avId, timestamp) + + def emptySlot3(self, avId, timestamp): + self.emptySlot(3, avId, timestamp) + + def notifyToonOffTrolley(self, toon): + toon.setAnimState("neutral", 1.0) + if hasattr(base,'localAvatar') and toon == base.localAvatar: + if hasattr(self.loader.place, "trolley"): + self.loader.place.trolley.handleOffTrolley() + self.localToonOnBoard = 0 + else: + toon.startSmooth() + return + + def emptySlot(self, index, avId, timestamp): + def emptySeat(index): + # If localToon is exiting, he needs to change state + self.notify.debug( "### seat %s now empty" % index) + self.fullSeat[index] = self.seatState.Empty + if avId == 0: + # This means that the slot is now empty, and no action should + # be taken. + pass + elif avId == 1: + # Special cardinal value for unexpected exit. + # The toon is gone, but we may still need to clean up his food + self.fullSeat[index] = self.seatState.Empty + track = Sequence(self.generateFoodDisappearTrack(index)) + # if no toons left, make the basket go away + self.notify.debug( "### empty slot - unexpetected: fullSeat = %s" % self.fullSeat) + if self.fullSeat.count(0) == 4: + self.notify.debug("### empty slot - unexpected: losing basket") + if self.picnicBasketTrack: + self.picnicBasketTrack.finish() + #track.append(self.generateBasketDisappearTrack()) + waitDuration = track.getDuration() + self.picnicBasketTrack = Sequence( + Wait(waitDuration), + self.generateBasketDisappearTrack()) + self.picnicBasketTrack.start() + track.start() + else: + self.fullSeat[index] = self.seatState.Empty + if self.cr.doId2do.has_key(avId): + if avId == base.localAvatar.getDoId(): + # Stop the countdown clock.. + if(self.clockNode): + self.clockNode.hide() + + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + toon.stopSmooth() + sitStartDuration = toon.getDuration("sit-start") + jumpOutTrack = self.generateToonReverseJumpTrack(toon, index) + track = Sequence(jumpOutTrack) + # make the food go away + track.append(self.generateFoodDisappearTrack(index)) + # if no toons left, make the basket go away + self.notify.debug( "### empty slot: fullSeat = %s" % self.fullSeat) + if self.fullSeat.count(0) == 4: + self.notify.debug( "### empty slot: losing basket") + if self.picnicBasketTrack: + self.picnicBasketTrack.finish() + #track.append(self.generateBasketDisappearTrack()) + waitDuration = track.getDuration() + self.picnicBasketTrack = Sequence( + Wait(waitDuration), + self.generateBasketDisappearTrack()) + self.picnicBasketTrack.start() + + # let the toon loose + track.append(Sequence( + # Tell the toon he is free to roam now + Func(self.notifyToonOffTrolley, toon), + Func(self.clearToonTrack, avId), + Func(self.doneExit, avId), + Func(emptySeat, index), + name = toon.uniqueName("emptyTrolley"), + autoPause = 1)) + track.delayDelete = DelayDelete.DelayDelete(toon, 'PicnicBasket.emptySlot') + self.storeToonTrack(avId, track) + track.start() + + def rejectBoard(self, avId): + # This should only be sent to us if our localToon requested + # permission to board the trolley. + assert(base.localAvatar.getDoId() == avId) + self.loader.place.trolley.handleRejectBoard() + + def __enableCollisions(self): + # start listening for toons to enter. + assert self.notify.debugStateCall(self) + for i in range(self.numSeats): + self.accept('enterpicnicTable_sphere_%d_%d' % (self.getDoId(), i), self.handleEnterPicnicTableSphere, [i]) + self.accept('enterPicnicTableOK_%d_%d' % (self.getDoId(), i), self.handleEnterPicnicTable, [i]) + self.picnicTableSphereNodes[i].setCollideMask(ToontownGlobals.WallBitmask) + + def __disableCollisions(self): + assert self.notify.debugStateCall(self) + for i in range(self.numSeats): + self.ignore('enterpicnicTable_sphere_%d_%d' % (self.getDoId(), i)) + self.ignore('enterPicnicTableOK_%d_%d' % (self.getDoId(), i)) + + for i in range(self.numSeats): + self.picnicTableSphereNodes[i].setCollideMask(BitMask32(0)) + + + ##### Off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### WaitEmpty state ##### + + def enterWaitEmpty(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + + def exitWaitEmpty(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + + ##### WaitCountdown state ##### + + def enterWaitCountdown(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + self.accept("trolleyExitButton", self.handleExitButton) + #self.clockNode.countdown(self.picnicCountdownTime - ts, self.handleExitButton) + self.clockNode.countdown(self.picnicCountdownTime, self.handleExitButton) + + def handleExitButton(self): + # This gets called when the exit button gets pushed. + self.sendUpdate("requestExit") + self.clockNode.hide() + #import pdb; pdb.set_trace() + + def exitWaitCountdown(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + self.ignore("trolleyExitButton") + self.clockNode.reset() + + def getStareAtNodeAndOffset(self): + return self.tablecloth, Point3(0,0,4) + + def storeToonTrack(self, avId, track): + # Clear out any currently playing tracks on this toon + self.clearToonTrack(avId) + # Store this new one + self.__toonTracks[avId] = track + + def clearToonTrack(self, avId): + # Clear out any currently playing tracks on this toon + oldTrack = self.__toonTracks.get(avId) + if oldTrack: + oldTrack.pause() + DelayDelete.cleanupDelayDeletes(oldTrack) + del self.__toonTracks[avId] + + def clearToonTracks(self): + #We can't use an iter because we are deleting keys + keyList = [] + for key in self.__toonTracks: + keyList.append(key) + + for key in keyList: + if self.__toonTracks.has_key(key): + self.clearToonTrack(key) + + def doneExit(self, avId): + if(avId == base.localAvatar.getDoId()): + self.sendUpdate("doneExit") + + def setPosHpr(self, x, y, z, h, p ,r): + """Set the pos hpr as dictated by the AI.""" + self.startingPos =Vec3(x, y, z) + self.enteringPos = Vec3(x, y, z-10) + self.startingHpr =Vec3(h, 0, 0) + #self.golfKart.setPosHpr( x, y, z, h, 0, 0 ) + + def setTableNumber(self, tn): + self.tableNumber = tn + + def generateToonJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping into the golf kart.""" + # Maintain a reference to Parent and Scale of avatar in case they + # exit from the kart. + #base.sb = self + + av.pose('sit', 47) + hipOffset = av.getHipsParts()[2].getPos(av) + + def getToonJumpTrack( av, seatIndex ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = self.tablecloth): + dest = Vec3(self.tablecloth.getPos(av.getParent())) + seatNode = self.picnicTable.find("**/seat" + str(seatIndex + 1)) + dest += seatNode.getPos(self.tablecloth) + dna = av.getStyle() + dest -= hipOffset + if(seatIndex == 2 or seatIndex == 3): + dest.setY( dest.getY() + 2 * hipOffset.getY()) + dest.setZ(dest.getZ() + 0.2) + + return dest + + def getJumpHpr(av = av, node = self.tablecloth): + hpr = self.seats[seatIndex].getHpr(av.getParent()) + #if(seatIndex < 2): + #hpr.setX( hpr.getX() + 180) + #else: + # hpr.setX( hpr.getX() ) + angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX()) + hpr.setX(angle) + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.43 ), + Parallel( LerpHprInterval( av, + hpr = getJumpHpr, + duration = .9 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = .9 ) + ), + ) + ) + return toonJumpTrack + + def getToonSitTrack( av ): + toonSitTrack = Sequence( + + ActorInterval( av, 'sit-start' ), + Func( av.loop, 'sit' ) + ) + return toonSitTrack + + toonJumpTrack = getToonJumpTrack( av, seatIndex ) + toonSitTrack = getToonSitTrack( av ) + + jumpTrack = Sequence( + Parallel( + toonJumpTrack, + Sequence( Wait(1), + toonSitTrack, + ), + ), + Func( av.wrtReparentTo, self.tablecloth ), + ) + + return jumpTrack + + def generateToonReverseJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping out of the golf kart.""" + self.notify.debug("av.getH() = %s" % av.getH()) + def getToonJumpTrack( av, destNode ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = destNode): + dest = node.getPos(self.tablecloth) + dest += self.jumpOffsets[seatIndex].getPos(self.tablecloth) + return dest + + def getJumpHpr(av = av, node = destNode): + hpr = node.getHpr(av.getParent()) + hpr.setX( hpr.getX() + 180) + angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX()) + hpr.setX(angle) + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.1), #43 ), + Parallel( #LerpHprInterval( av, + # hpr = getJumpHpr, + # duration = .9 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = .9 ) ) + ) + ) + return toonJumpTrack + + toonJumpTrack = getToonJumpTrack( av, self.tablecloth) + #self.seats[seatIndex]) + jumpTrack = Sequence( + toonJumpTrack, + Func( av.loop, 'neutral' ), + Func( av.wrtReparentTo, render ), + #Func( self.av.setPosHpr, self.exitMovieNode, 0,0,0,0,0,0 ), + ) + return jumpTrack + + def generateBasketAppearTrack( self ): + """ + """ + if(self.basket == None): + self.basket = loader.loadModel('phase_6/models/golf/picnic_basket.bam') + + self.basket.setScale( 0.1 ) + + basketTrack = Sequence( + Func( self.basket.show ), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self.basket), + Func( self.basket.reparentTo, self.tablecloth ), + Func( self.basket.setPos, 0, 0, .2 ), + Func( self.basket.setHpr, 45, 0, 0), + Func( self.basket.wrtReparentTo, render ), + Func( self.basket.setShear, 0, 0, 0), + #Func( self.basket.setActiveShadow, True ), + # Must be a cleaner way to do this. + Sequence( LerpScaleInterval( self.basket, + scale = Point3( 1.1, 1.1, .1), + duration = 0.2), + LerpScaleInterval( self.basket, + scale = Point3(1.6, 1.6, 0.2 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 1., 1., 0.4 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 1.5, 1.5, 2.5 ), + duration = 0.2 ), + LerpScaleInterval( self.basket, + scale = Point3(2.5,2.5, 1.5 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 2., 2., 2. ), + duration = 0.1 ), + Func( self.basket.wrtReparentTo, self.tablecloth ), + Func( self.basket.setPos, 0, 0, 0) ), + ) + + return basketTrack + + + def generateBasketDisappearTrack(self): + if not self.basket: + return Sequence() + + pos = self.basket.getPos() + pos.addZ(-1) + + basketTrack = Sequence( + LerpScaleInterval( self.basket, + scale = Point3( 2., 2., 1.8 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 1., 1., 2.5 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 2., 2., 0.5 ), + duration = 0.2 ), + LerpScaleInterval( self.basket, + scale = Point3( 0.5, 0.5, 1.0 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( 1.1, 1.1, .1 ), + duration = 0.1 ), + LerpScaleInterval( self.basket, + scale = Point3( .1, .1, .1 ), + duration = 0.2 ), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self.basket), + Wait( 0.2 ), + LerpPosInterval( self.basket, + pos = pos, + duration = 0.2 ), + Func( self.basket.hide ), + ) + return basketTrack + + def generateFoodAppearTrack(self, seat): + """ + """ + if(self.fullSeat[seat] == self.seatState.Full): + self.notify.debug( "### food appear: self.fullSeat = %s" % self.fullSeat) + if not self.food[seat]: + self.food[seat] = loader.loadModel(self.random.choice(self.foodLoader)) + self.notify.debug( "### food appear: self.food = %s" % self.food) + + self.food[seat].setScale(0.1) + self.food[seat].reparentTo(self.tablecloth) + self.food[seat].setPos(self.seats[seat].getPos(self.tablecloth)[0]/2, self.seats[seat].getPos(self.tablecloth)[1]/2, 0) + + # Func( self.food[seat].setActiveShadow, False ), + foodTrack = Sequence( + Func( self.food[seat].show ), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self.food[seat]), + Func( self.food[seat].reparentTo, self.tablecloth ), + Func( self.food[seat].setHpr, 45, 0, 0), + Func( self.food[seat].wrtReparentTo, render ), + Func( self.food[seat].setShear, 0, 0, 0), + #Func( self.food[seat].setActiveShadow, True ), + # Must be a cleaner way to do this. + Sequence( LerpScaleInterval( self.food[seat], + scale = Point3( 1.1, 1.1, .1), + duration = 0.2), + LerpScaleInterval( self.food[seat], + scale = Point3(1.6, 1.6, 0.2 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 1., 1., 0.4 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 1.5, 1.5, 2.5 ), + duration = 0.2 ), + LerpScaleInterval( self.food[seat], + scale = Point3(2.5,2.5, 1.5 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 2., 2., 2. ), + duration = 0.1 ), + Func( self.food[seat].wrtReparentTo, self.tablecloth ) + ), + ) + return foodTrack + else: + return Sequence() + + def generateFoodDisappearTrack(self, seat): + if not self.food[seat]: + return Sequence() + pos = self.food[seat].getPos() + pos.addZ( -1. ) + foodTrack = Sequence( + LerpScaleInterval( self.food[seat], + scale = Point3( 2., 2., 1.8 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 1., 1., 2.5 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 2., 2., 0.5 ), + duration = 0.2 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 0.5, 0.5, 1.0 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( 1.1, 1.1, .1 ), + duration = 0.1 ), + LerpScaleInterval( self.food[seat], + scale = Point3( .1, .1, .1 ), + duration = 0.2 ), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self.food[seat]), + Wait( 0.2 ), + LerpPosInterval( self.food[seat], + pos = pos, + duration = 0.2 ), + Func( self.food[seat].hide ), + ) + return foodTrack + + def destroy(self, node): + node.removeNode() + node = None + self.basket.removeNode() + self.basket = None + for food in self.food: + food.removeNode() + self.food = None + self.clockNode.removeNode() + del self.clockNode + self.clockNode = None + + def setPicnicDone(self): + if self.localToonOnBoard: + if hasattr(self.loader.place, "trolley"): + self.loader.place.trolley.fsm.request("final") + self.loader.place.trolley.fsm.request("start") + self.localToonOnBoard = 0 + messenger.send("picnicDone") diff --git a/toontown/src/safezone/DistributedPicnicBasketAI.py b/toontown/src/safezone/DistributedPicnicBasketAI.py new file mode 100644 index 0000000..c5e8f29 --- /dev/null +++ b/toontown/src/safezone/DistributedPicnicBasketAI.py @@ -0,0 +1,339 @@ +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * +from TrolleyConstants import * + +from toontown.toonbase import ToontownGlobals + +from direct.distributed import DistributedObjectAI +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import RandomNumGen +from toontown.minigame import MinigameCreatorAI +from toontown.quest import Quests +from toontown.minigame import TrolleyHolidayMgrAI +from toontown.golf import GolfManagerAI +from toontown.golf import GolfGlobals + + +class DistributedPicnicBasketAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + "DistributedPicnicBasketAI") + + def __init__(self, air, tableNumber, x, y, z, h, p, r): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.seats = [None, None, None, None] + self.posHpr = (x, y ,z, h, p, r) + self.tableNumber = int(tableNumber) + self.seed = RandomNumGen.randHash(globalClock.getRealTime()) + + # Flag that tells whether the trolley is currently accepting boarders + self.accepting = 0 + self.numPlayersExiting = 0 + + self.trolleyCountdownTime = \ + simbase.config.GetFloat("picnic-countdown-time", + ToontownGlobals.PICNIC_COUNTDOWN_TIME) + + self.fsm = ClassicFSM.ClassicFSM('DistributedPicnicBasketAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['waitEmpty']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + def delete(self): + self.fsm.requestFinalState() + del self.fsm + DistributedObjectAI.DistributedObjectAI.delete(self) + + + def findAvailableSeat(self): + for i in range(len(self.seats)): + if self.seats[i] == None: + return i + return None + + def findAvatar(self, avId): + for i in range(len(self.seats)): + if self.seats[i] == avId: + return i + return None + + def countFullSeats(self): + avCounter = 0 + for i in self.seats: + if i: + avCounter += 1 + return avCounter + + def rejectingBoardersHandler(self, avId, si): + self.rejectBoarder(avId) + + def rejectBoarder(self, avId): + self.sendUpdateToAvatarId(avId, "rejectBoard", [avId]) + + def acceptingBoardersHandler(self, avId, si): + self.notify.debug("acceptingBoardersHandler") + seatIndex = si + if not seatIndex == None: + self.acceptBoarder(avId, seatIndex) + + def acceptBoarder(self, avId, seatIndex): + self.notify.debug("acceptBoarder %d" % avId) + # Make sure we have a valid seat number + assert((seatIndex >= 0) and (seatIndex <=3)) + # Make sure the seat is empty + assert(self.seats[seatIndex] == None) + # Make sure this avatar isn't already seated + if (self.findAvatar(avId) != None): + return + # Put the avatar in that seat + self.seats[seatIndex] = avId + # Add a hook that handles the case where the avatar exits + # the district unexpectedly + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + # Record the time of boarding + self.timeOfBoarding = globalClock.getRealTime() + # Tell the clients to put the avatar in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [avId]) + # Put us into waitCountdown state... If we are already there, + # this won't do anything. + self.waitCountdown() + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # Make sure the avatar is really here + if seatIndex == None: + pass + else: + # If the avatar is here, his seat is now empty. + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.clearEmptyNowUnexpected(seatIndex) + #self.sendUpdate("emptySlot" + str(seatIndex), + # [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + if self.countFullSeats() == 0: + self.waitEmpty() + + def clearEmptyNowUnexpected(self, seatIndex): + self.sendUpdate("emptySlot" + str(seatIndex), + [1, globalClockDelta.getRealNetworkTime()]) + + def rejectingExitersHandler(self, avId): + self.rejectExiter(avId) + + def rejectExiter(self, avId): + # This doesn't have to do anything. If your exit is rejected, + # you'll know because the trolley leaves. + pass + + def acceptingExitersHandler(self, avId): + self.acceptExiter(avId) + + def acceptExiter(self, avId): + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # It is possible that the avatar exited the shard unexpectedly. + if seatIndex == None: + pass + else: + # Empty that seat + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.sendUpdate("emptySlot" + str(seatIndex), + [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + #if self.countFullSeats() == 0: + # self.waitEmpty() + + # Wait for the avatar to be done leaving the seat, and then + # declare the emptying overwith... + taskMgr.doMethodLater(TOON_EXIT_TIME, + self.clearEmptyNow, + self.uniqueName("clearEmpty-%s" % seatIndex), + extraArgs = (seatIndex,)) + + def clearEmptyNow(self, seatIndex): + self.notify.debugStateCall(self) + self.sendUpdate("emptySlot" + str(seatIndex), + [0, globalClockDelta.getRealNetworkTime()]) + + def clearFullNow(self, seatIndex): + # Get the avatar id + avId = self.seats[seatIndex] + # If there is no one sitting there, that is kind of strange. + if avId == 0: + self.notify.warning("Clearing an empty seat index: " + + str(seatIndex) + " ... Strange...") + else: + # Empty that seat + self.seats[seatIndex] = None + # Tell the clients that the avatar is no longer in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [0]) + # If the avatar isn't in a seat, we don't care anymore, so + # remove the hook to handle unexpected exits. + self.ignore(self.air.getAvatarExitEvent(avId)) + + def d_setState(self, state, seed): + self.sendUpdate('setState', [state, seed, globalClockDelta.getRealNetworkTime()]) + + def getState(self): + return self.fsm.getCurrentState().getName() + + def requestBoard(self,si): + self.notify.debug("requestBoard") + avId = self.air.getAvatarIdFromSender() + if (self.findAvatar(avId) != None): + self.notify.warning("Ignoring multiple requests from %s to board." % (avId)) + return + + av = self.air.doId2do.get(avId) + if av: + # Only toons with hp greater than 0 may board the trolley. + if (av.hp > 0) and self.accepting and self.seats[si] == None: + self.notify.debug("accepting boarder %d" % avId) + self.acceptingBoardersHandler(avId, si) + else: + self.notify.debug("rejecting boarder %d" % avId) + self.rejectingBoardersHandler(avId, si) + else: + self.notify.warning( + "avid: %s does not exist, but tried to board a trolley" % avId + ) + + def requestExit(self, *args): + self.notify.debug("requestExit") + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + if av: + # Check to make sure the AI hasn't already told the players to exit + if (self.countFullSeats() > 0): + newArgs = (avId,) + args + self.numPlayersExiting += 1 + if self.accepting: + self.acceptingExitersHandler(*newArgs) + else: + self.rejectingExitersHandler(*newArgs) + else: + self.notify.debug("Player tried to exit after AI already kicked everyone out") + else: + self.notify.warning( + "avId: %s does not exist, but tried to exit a trolley" % avId + ) + + def doneExit(self): + # Check to make sure that player has exited before waiting for new toons + + if(self.numPlayersExiting > 0): + self.numPlayersExiting -= 1 + if (self.numPlayersExiting == 0 and self.countFullSeats() == 0): + self.waitEmpty() + + ##### How you start up the trolley ##### + def start(self): + self.waitEmpty() + + ##### Off state ##### + + def enterOff(self): + self.accepting = 0 + # Maybe this task cleanup shouldn't be here, but I didn't know + # where else to put it, since emptying the seats isn't associated + # with any particular task. Perhaps I should have made a nested + # State machine of PicnicBasketOn, or some such, but it seemed like a lot + # of work for a few crummy tasks. + + # If we don't have a doId yet, we can't possibly have these + # tasks running. + if hasattr(self, "doId"): + for seatIndex in range(4): + taskMgr.remove(self.uniqueName("clearEmpty-" + + str(seatIndex))) + + def exitOff(self): + self.accepting = 0 + + ##### WaitEmpty state ##### + + def waitEmptyTask(self, task): + self.waitEmpty() + return Task.done + + def waitEmpty(self): + self.fsm.request("waitEmpty") + + def enterWaitEmpty(self): + self.notify.debugStateCall(self) + self.d_setState('waitEmpty', self.seed) + self.seats = [None, None, None, None] + self.accepting = 1 + + def exitWaitEmpty(self): + self.notify.debugStateCall(self) + self.accepting = 0 + + ##### WaitCountdown state ##### + + def waitCountdown(self): + self.notify.debugStateCall(self) + self.fsm.request("waitCountdown") + + def enterWaitCountdown(self): + self.notify.debugStateCall(self) + self.d_setState('waitCountdown', self.seed) + self.accepting = 1 + # Start the countdown... + taskMgr.doMethodLater(self.trolleyCountdownTime, self.timeToGoTask, + self.uniqueName('countdown-timer')) + + def timeToGoTask(self, task): + # It is possible that the players exited the district + assert self.notify.debugStateCall(self) + self.accepting = 0 + if self.countFullSeats() > 0: + for x in range(len(self.seats)): + if not (self.seats[x] == None): + self.sendUpdateToAvatarId(self.seats[x], "setPicnicDone", []) + self.acceptExiter(self.seats[x]) + self.numPlayersExiting += 1 + self.waitEmpty() + return Task.done + + def exitWaitCountdown(self): + self.notify.debugStateCall(self) + self.accepting = 0 + taskMgr.remove(self.uniqueName('countdown-timer')) + + def getPosHpr(self): + return self.posHpr + + def getTableNumber(self): + return self.tableNumber + diff --git a/toontown/src/safezone/DistributedPicnicTable.py b/toontown/src/safezone/DistributedPicnicTable.py new file mode 100644 index 0000000..242f1ed --- /dev/null +++ b/toontown/src/safezone/DistributedPicnicTable.py @@ -0,0 +1,921 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer + +from direct.distributed import DistributedNode +from direct.distributed.ClockDelta import globalClockDelta +from ChineseCheckersBoard import ChineseCheckersBoard +from GameTutorials import * +from GameMenu import GameMenu +from direct.fsm import ClassicFSM, State +from direct.fsm import StateData +from toontown.distributed import DelayDelete + +from toontown.toonbase.ToontownTimer import ToontownTimer +from toontown.toonbase import ToontownGlobals + + +from direct.showbase import PythonUtil +from otp.otpbase import OTPGlobals + + +class DistributedPicnicTable(DistributedNode.DistributedNode): + def __init__(self, cr): + self.cr = cr + NodePath.__init__(self, "DistributedPicnicTable") + DistributedNode.DistributedNode.__init__(self,cr) + + self.reparentTo(render) + self.picnicTable = loader.loadModel("phase_6/models/golf/game_table.bam") + self.picnicTable.reparentTo(self) + + + self.picnicTableSphereNodes = [] + self.numSeats = 6 + self.seats = [] + self.jumpOffsets = [] + self.inGame = False + self.requestSeat = None + self.gameState = None + #self.mypos = self.getPos() + self.cameraBoardTrack = Func(self.doNothing) + self.seatBumpForObserve = 0 + self.winTrack = Sequence() + self.outTrack = Sequence() + self.joinButton = None + self.observeButton = None + self.tutorialButton = None + self.exitButton = None + self.isPlaying = False + self.gameMenu = None + self.game = None + self.gameZone = None + self.tutorial = None + + + + self.timerFunc = None + self.gameDoId = None + #self.game = None + self.gameWantTimer = False + + self.tableState = [None, None, None, None, None, None] + self.haveAnimated = [] + self.winSound = base.loadSfx("phase_6/audio/sfx/KART_Applause_1.mp3") + self.happyDance = base.loadSfx("phase_5/audio/sfx/AA_heal_happydance.mp3") + + #Seems like these functions BOTH are required + #To intercept the sleep event. + #Important to take action when GUI elements are up to turn + #them off when the avatar goes to sleep otherwise the wakeup will allow + #him to run around with the gui up. + self.accept('stoppedAsleep', self.handleSleep) + base.localAvatar.startSleepWatch(self.handleSleep) + + self.__toonTracks = {} + + self.fsm = ClassicFSM.ClassicFSM('PicnicTable', + [State.State('off', + self.enterOff, + self.exitOff, + ['chooseMode','observing']), + State.State('chooseMode', + self.enterChooseMode, + self.exitChooseMode, + ['sitting','off', 'observing']), + State.State('sitting', + self.enterSitting, + self.exitSitting, + ['off']), + State.State('observing', + self.enterObserving, + self.exitObserving, + ['off'])], + #start state + 'off', + #final state` + 'off', + ) + self.fsm.enterInitialState() + + #Go find all of the locators for seats and jumpout locators + for i in range(self.numSeats): + self.seats.append(self.picnicTable.find("**/*seat%d" % (i+1))) + self.jumpOffsets.append(self.picnicTable.find("**/*jumpOut%d" % (i+1))) + self.tableCloth = self.picnicTable.find("**/basket_locator") + + + #Stops you from walking on the table + self.tableclothSphereNode = self.tableCloth.attachNewNode(CollisionNode('tablecloth_sphere')) + self.tableclothSphereNode.node().addSolid(CollisionSphere(0, 0, -2, 5.5)) + + self.clockNode = ToontownTimer() + self.clockNode.setPos(1.16, 0, -0.83) + self.clockNode.setScale(0.3) + self.clockNode.hide() + + def announceGenerate(self): + + DistributedNode.DistributedNode.announceGenerate(self) + #Set up the collision spheres for the seats + for i in range(self.numSeats): + self.picnicTableSphereNodes.append(self.seats[i].attachNewNode( + CollisionNode('picnicTable_sphere_%d_%d' % (self.getDoId(), i)))) + self.picnicTableSphereNodes[i].node().addSolid( + CollisionSphere(0, 0, 0, 2)) + + #sit everyone down + self.tableState = [None, None, None, None, None, None] + + self.requestTableState() + + + + + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find("**/InventoryButtonRollover") + + + #self.picnicTable.setScale(.030) + #Preprocessing for Jump into seat Arcs (bad to do at runtime) + angle = self.getH() + angle -= 90 + radAngle = deg2Rad(angle) + unitVec = Vec3( math.cos(radAngle), math.sin(radAngle), 0) + unitVec *= 30.0 + self.endPos = self.getPos() + unitVec + + dist = Vec3(self.endPos - self.getPos()).length() + wheelAngle = dist/(0.5 * 1.4 * math.pi ) * 360 + + self.__enableCollisions() + def handleSleep(self, task = None): + #print "GETTING TO SLEEP!!!" + if self.fsm.getCurrentState().getName() == "chooseMode": + self.cancelButtonPushed() + elif self.fsm.getCurrentState().getName() == "sitting": + self.sendUpdate("requestExit", []) + if self.gameMenu != None: + self.gameMenu.removeButtons() + self.gameMenu.picnicFunction = None + self.gameMenu = None + if task != None: + task.done + #task.done + + def disable(self): + DistributedNode.DistributedNode.disable(self) + self.ignore('stoppedAsleep') + self.clearToonTracks() + self.__disableCollisions() + self.disableChoiceButtons() + #del self.picnicTableSphereNodes + self.picnicTable.removeNode() + self.cameraBoardTrack = None + #self.fsm = None + #self.winTrack.finish() + #self.outTrack.finish() + #self.outTrack = None + + def delete(self): + self.__disableCollisions() + self.ignore('stoppedAsleep') + DistributedNode.DistributedNode.delete(self) + self.disableChoiceButtons() + self.cameraBoardTrack = None + #self.winTrack.finish() + #self.outTrack.finish() + #self.winTrack = None + #self.outTrack = None + del self.winTrack + del self.outTrack + self.fsm = None + self.gameZone = None + self.clearToonTracks() + self.cameraBoardTrack = None + #self.clearToonTrack() + #print " I AM DELETEING \n\n\n\n\n\n\n\n\n" + #self.outTrack = None + #del self + + def setName(self, name): + self.name = name + ################ + ##SetGameDoID - + # This function is called by the child game after it is generated, in order + #to set up the cross references that are needed to handle certain events + #IE (getting up, erroneous disconnects ect.) + def setGameDoId(self, doId): + self.gameDoId = doId + self.game = self.cr.doId2do[doId] + self.game.setHpr(self.getHpr()) + self.gameWantTimer = self.game.wantTimer + if self.gameState == 1: + self.game.fsm.request('playing') + ########## + ##Timer Functions + #setTimer (required broadcast ram) + # + #These are the timer functions that dictate movement and visual timer + #feedback in the game table and chinese checkers. + # + ########## + def setTimerFunc(self, function): + self.timerFunc = function + def setTimer(self, timerEnd): + self.clockNode.stop() + time = globalClockDelta.networkToLocalTime(timerEnd) + self.timeLeft = int(time - globalClock.getRealTime() ) + if self.gameWantTimer and self.game != None: + self.showTimer() + def showTimer(self): + #important to stop the timer before you reset it, otherwise it may still be running + self.clockNode.stop() + self.clockNode.countdown(self.timeLeft, self.timerFunc) + self.clockNode.show() + + ########## + #setTableState (required ram broadcast) + # + #This functions primary purpose is to handle asynchrinous joins of the zone + #if an avatar teleports in, he needs to have a state of the table so he + #can animate the toons to be sitting down in their corresponding seats + ########## + def requestTableState(self): + self.sendUpdate("requestTableState", []) + + def setTableState(self, tableStateList, isplaying): + y = 0 + print "SET TABLE STATE" + if isplaying == 0: + self.isPlaying = False + else: + self.isPlaying = True + for x in tableStateList: + if x != 0: + # If we are to sit him down, make sure that he has not already been animated by fillslot + # (deltas are handled by that function) + if not x in self.tableState and self.cr.doId2do.has_key(x) and x not in self.haveAnimated: + seatIndex = tableStateList.index(x) + toon = self.cr.doId2do[x] + toon.stopSmooth() + toon.setAnimState( "Sit", 1.0) + + + dest = self.seats[seatIndex].getPos(self.tableCloth) + hpr = self.seats[seatIndex].getHpr(render) + toon.setHpr(hpr) + + if(seatIndex > 2): + toon.setH(self.getH()+180) + toon.wrtReparentTo(self) + toon.setPos(dest) + toon.setZ(toon.getZ()+1.35) + + if(seatIndex > 2): + toon.setY(toon.getY()-1.0) + else: + toon.setY(toon.getY()+1.0) + + + if x != 0: + self.tableState[y] = x + else: + self.tableState[y] = None + + y = y + 1 + + ###Handle the game menu stuffs + numPlayers = 0 + for x in self.tableState: + if x != None: + numPlayers += 1 + #check for a game menu up + print " GETTING 2", self.gameMenu, numPlayers + if self.gameMenu: + if numPlayers > 2: + print " GETTING HERE!!" + self.gameMenu.FindFour.setColor(.7,.7,.7,.7) + self.gameMenu.FindFour['command'] = self.doNothing + self.gameMenu.findFourText['fg'] = (.7,.7,.7,.7) + + self.gameMenu.Checkers.setColor(.7,.7,.7,.7) + self.gameMenu.Checkers['command'] = self.doNothing + self.gameMenu.checkersText['fg'] = (.7,.7,.7,.7) + + def setIsPlaying(self, isPlaying): + if isPlaying == 0: + self.isPlaying = False + elif isPlaying == 1: + self.isPlaying = True + ########## + ##announceWinner (broadcast) + # + #Obvious, simply just sets the whisper message + ########## + def announceWinner(self, winString, avId): + if avId == base.localAvatar.getDoId(): + sound = Sequence(Wait(2.0), Parallel( SoundInterval(self.winSound), SoundInterval( self.happyDance))) + sound.start() + base.cr.playGame.getPlace().setState('walk') #To stop Cohesion between EmptySlot and AnnounceWin + if winString == "Chinese Checkers": + whisper = WhisperPopup(TTLocalizer.ChineseCheckersYouWon, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + elif winString == "Checkers": + whisper = WhisperPopup(TTLocalizer.RegularCheckersYouWon, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + elif winString == "Find Four": + whisper = WhisperPopup("You won a game of Find Four!", + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + + else: + if self.cr.doId2do.has_key(avId): + stateString = self.fsm.getCurrentState().getName() + if stateString == "sitting" or stateString == "observing" : + base.cr.playGame.getPlace().setState('walk') #To stop Cohesion between EmptySlot and AnnounceWin + av = self.cr.doId2do[avId] + if winString == "Chinese Checkers": + whisper = WhisperPopup(av.getName() + TTLocalizer.ChineseCheckersGameOf + TTLocalizer.ChineseCheckers, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + elif winString == "Checkers": + whisper = WhisperPopup(av.getName() + TTLocalizer.RegularCheckersGameOf + TTLocalizer.RegularCheckers, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + elif winString == "Find Four": + whisper = WhisperPopup(av.getName() + " has won a game of" + " Find Four!" , + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + #self.winTrack.finish() + self.winTrack = Sequence(autoFinish = 1) + if self.outTrack.isPlaying(): #If toon is jumping out Wait a + self.winTrack.append(Wait(2.0)) # duration till his anim is over to begin + + if avId == base.localAvatar.getDoId(): + #stop him from walking locally + #otherwise he will animate around while moving on other clients + self.winTrack.append(Func(self.stopToWalk)) + self.winTrack.append(ActorInterval(toon, 'happy-dance')) + if avId == base.localAvatar.getDoId(): + self.winTrack.append(Func(self.allowToWalk)) + self.winTrack.start() + #Display the whisper message + whisper.manage(base.marginManager) + + + + ########## + #handleEnterPicnicTableSphere + # + #This is the function that via the messenger, handles the collision + #with one of the six collision spheres on a picnic table + ########## + def handleEnterPicnicTableSphere(self, i, collEntry): + assert self.notify.debugStateCall(self) + #print "COLLISION!!!" + self.notify.debug("Entering Picnic Table Sphere.... %s" % self.getDoId()) + + + #if self.requestSeat == None: + self.requestSeat = i + self.seatBumpForObserve = i + self.fsm.request('chooseMode') + ########## + #Button Logic (handled by handleEnterPicnicTableSphere, and self.fsm + ### + + def enableChoiceButtons(self): + if self.tableState[self.seatBumpForObserve] == None and self.isPlaying == False: + self.joinButton = DirectButton( + relief = None, + text = TTLocalizer.PicnicTableJoinButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (0, 0, .8), + scale = 0.15, + command = lambda self=self: self.joinButtonPushed(), + ) + if self.isPlaying == True: + self.observeButton = DirectButton( + relief = None, + text = TTLocalizer.PicnicTableObserveButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (0, 0, 0.6), + scale = 0.15, + command = lambda self=self: self.observeButtonPushed(), + ) + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.PicnicTableCancelButton, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (1, 0, 0.6), + scale = 0.15, + command = lambda self=self: self.cancelButtonPushed(), + ) + self.tutorialButton = DirectButton( + relief = None, + text = TTLocalizer.PicnicTableTutorial, + text_fg = (1, 1, 0.65, 1), + text_pos = (-.05, -.13), + text_scale = 0.55, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (-1, 0, 0.6), + scale = 0.15, + command = lambda self=self: self.tutorialButtonPushed(), + ) + base.cr.playGame.getPlace().setState('stopped') + def tutorialButtonPushed(self): + #print "GETTING HERE!!!" + self.disableChoiceButtons() + #self.tutorial = ChineseTutorial(self.tutorialDone) + self.gameMenu = GameMenu(self.tutorialFunction, 1) # 1 == Tutorial Num + #self.tutorialFunction(1) + self.tutorialButton.destroy() + self.tutorialButton = None + def tutorialFunction(self, tutVal): + if tutVal == 1: + self.tutorial = ChineseTutorial(self.tutorialDone) + elif tutVal == 2: + self.tutorial = CheckersTutorial(self.tutorialDone) + self.gameMenu.picnicFunction = None + self.gameMenu = None + def tutorialDone(self): + #del self.tutorial + self.requestSeat = None + self.fsm.request('off') + self.tutorial = None + #del self.tutorial + def joinButtonPushed(self): + toon = base.localAvatar + self.sendUpdate("requestJoin", + [self.requestSeat, toon.getX(), toon.getY(), toon.getZ(), + toon.getH(), toon.getP(), toon.getR()]) + self.requestSeat = None + self.fsm.request('sitting') + + def rejectJoin(self): + self.fsm.request('off') + self.allowToWalk() + + def cancelButtonPushed(self): + base.cr.playGame.getPlace().setState('walk') + self.requestSeat = None + self.fsm.request('off') + def disableChoiceButtons(self): + if self.joinButton: + self.joinButton.destroy() + if self.observeButton: + self.observeButton.destroy() + if self.exitButton: + self.exitButton.destroy() + if self.tutorialButton: + self.tutorialButton.destroy() + + ########## + #Distributed fillSlot Functions (broadcasT) + # + #These functions are the distributed functions that the ai sends to tell + #the client if its ok for people to picnic table, and then animates them + ########## + def pickFunction(self, gameNum): + #print "SENDING PICK!" + if gameNum == 1: #Chinese Checkers + self.sendUpdate('requestPickedGame', [gameNum]) + elif gameNum == 2: + self.sendUpdate('requestPickedGame', [gameNum]) + elif gameNum == 3: + self.sendUpdate('requestPickedGame' , [gameNum]) + def allowPick(self): + self.gameMenu = GameMenu(self.pickFunction, 2) # 2 == pick Num for TTlocalizer + #elf.pickFunction(1) + + def setZone(self, zoneId): + #import pdb; pdb.set_trace() + #print "ZONE iD == " , zoneId + #print "CURRENT SATE?!?!? == ", self.fsm.getCurrentState().getName() + #if self.fsm.getCurrentState().getName() == "chooseMode" or self.fsm.getCurrentState().getName() == "sitting": + if self.fsm.getCurrentState().getName() == "sitting" or self.fsm.getCurrentState().getName() == "observing" : + if self.tutorial == None: + self.gameZone = base.cr.addInterest(base.localAvatar.defaultShard, zoneId, 'gameBoard') + if self.gameMenu != None: + self.gameMenu.removeButtons() + self.gameMenu.picnicFunction = None + self.gameMenu = None + def fillSlot(self, avId,index, x, y, z, h, p, r, timestamp, parentDoId): + assert self.notify.debugStateCall(self) + self.notify.debug( "fill Slot: %d for %d" % (index, avId) ) + if not avId in self.haveAnimated: + self.haveAnimated.append(avId) + + if avId == base.localAvatar.getDoId(): + if self.inGame == True: + return #in a game therefore we dont animate + else: + self.inGame = True + self.seatPos = index + pass #not in a game but need to animate into the game + + if self.cr.doId2do.has_key(avId): + toon = self.cr.doId2do[avId] + + toon.stopSmooth() + toon.wrtReparentTo(self.tableCloth) + sitStartDuration = toon.getDuration("sit-start") + jumpTrack = self.generateToonJumpTrack(toon, index) + + track = Sequence(autoFinish = 1) + if avId == base.localAvatar.getDoId(): + if not base.cr.playGame.getPlace() == None: + self.moveCamera(index) + track.append(Func(self.__disableCollisions)) + #self.gameZone = base.cr.addInterest(base.localAvatar.defaultShard, boardZoneId, 'chineseBoard') + track.append(jumpTrack) + track.append(Func(toon.setAnimState, "Sit", 1.0)) + track.append(Func(self.clearToonTrack, avId)) + self.storeToonTrack(avId, track) + track.start() + + ########## + #Empty Slot Distributed Functions (broadcast) + # + #Distributed functions that manage the toons getting off of the picnic + #tables + ########## + def emptySlot(self, avId, index, timestamp): + self.notify.debug( "### seat %s now empty" % index) + + ##Player told to exit is an OBSERVER + if index == 255 and self.game != None: + self.stopObserveButtonPushed() + return + + + + #self.fullSeat[index] = self.seatState.Empty + if avId in self.haveAnimated: + self.haveAnimated.remove(avId) + # self.fullSeat[index] = self.seatState.Empty + if self.cr.doId2do.has_key(avId): + if avId == base.localAvatar.getDoId(): + if self.gameZone: + base.cr.removeInterest(self.gameZone) + if self.inGame == True: #are in a game + self.inGame = False + else: + return #dont animate because we are NOT in a game + toon = self.cr.doId2do[avId] + toon.stopSmooth() + sitStartDuration = toon.getDuration("sit-start") + jumpOutTrack = self.generateToonReverseJumpTrack(toon, index) + #self.outTrack.finish() + self.outTrack = Sequence(jumpOutTrack) + + if base.localAvatar.getDoId() == avId: + self.outTrack.append(Func(self.__enableCollisions)) + self.outTrack.append(Func(self.allowToWalk)) #temp until i can stop the + #camera from jerking + #self.outTrack.append(Func(self.fsm.request, 'off')) + self.fsm.request('off') + #self.outTrack.append(Func(self.tempCheckers.setHpr, 0, self.tempCheckers.getP(), self.tempCheckers.getR())) + + val = self.jumpOffsets[index].getPos(render) + #self.storeToonTrack(avId, self.outTrack) + + self.outTrack.append(Func(toon.setPos, val)) + self.outTrack.append(Func(toon.startSmooth)) + self.outTrack.start() + #self.outTrack = Sequence() + def stopToWalk(self): + base.cr.playGame.getPlace().setState("stopped") + def allowToWalk(self): + #if not self.winTrack.isPlaying(): + base.cr.playGame.getPlace().setState("walk") + #self.winTrack.finish() + #self.winTrack = None + + ########## + #Camera manipulations + ########## + def moveCamera(self,seatIndex): + self.oldCameraPos = camera.getPos() + self.oldCameraHpr = camera.getHpr() + camera.wrtReparentTo(self.picnicTable) + heading = PythonUtil.fitDestAngle2Src( camera.getH(), 90) + #Need to check the seat index so the cameras *down + #is towards the player so he is not facing his own character + #rather he feels he is a bit above him + if seatIndex < 3: + self.cameraBoardTrack = LerpPosHprInterval(camera, 2.0, + Point3(0,0, 17), + Point3(0,-90,0)) + else: + #needed for camera orientation + #If test is not here, the camera may often flip around + #spinning ~340 degrees to the destination + #instead of turning the 20 degrees towards the table + if(camera.getH() < 0):#(turned left) + self.cameraBoardTrack = LerpPosHprInterval(camera, 2.0, + Point3(0,0, 17), + Point3(-180,-90,0)) + + else:#(turned right) + self.cameraBoardTrack = LerpPosHprInterval(camera, 2.0, + Point3(0,0, 17), + Point3(180,-90,0)) + + self.cameraBoardTrack.start() + def moveCameraBack(self): + self.cameraBoardTrack = LerpPosHprInterval(camera, 2.5, + self.oldCameraPos, + self.oldCameraHpr) + self.cameraBoardTrack.start() + ########## + # Enable and Disable Collisions + # + #Turn on and off the collisions for the seat and table collision spheres for + #boarding and unboarding, thus to actaully allow the toons to sit down, animate ect. + ########## + def __enableCollisions(self): + # start listening for toons to enter. + assert self.notify.debugStateCall(self) + for i in range(self.numSeats): + self.accept('enterpicnicTable_sphere_%d_%d' % (self.getDoId(), i), self.handleEnterPicnicTableSphere, [i]) + #self.accept('enterPicnicTableOK_%d_%d' % (self.getDoId(), i), self.handleEnterPicnicTable, [i]) + self.picnicTableSphereNodes[i].setCollideMask(ToontownGlobals.WallBitmask) + self.tableclothSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + + def __disableCollisions(self): + assert self.notify.debugStateCall(self) + #self.ignore('tableClothSphereNode') + for i in range(self.numSeats): + self.ignore('enterpicnicTable_sphere_%d_%d' % (self.getDoId(), i)) + self.ignore('enterPicnicTableOK_%d_%d' % (self.getDoId(), i)) + for i in range(self.numSeats): + self.picnicTableSphereNodes[i].setCollideMask(BitMask32(0)) + self.tableclothSphereNode.setCollideMask(BitMask32(0)) + + ########## + #FSM stuff + ########## + + def enterOff(self): + base.setCellsAvailable(base.leftCells + + base.bottomCells, 0) + def exitOff(self): + base.setCellsAvailable(base.bottomCells,0) + def enterChooseMode(self): + #self.requestTableState() + self.winTrack = Sequence(autoFinish = 1) + self.enableChoiceButtons() + def exitChooseMode(self): + self.disableChoiceButtons() + def enterObserving(self): + self.enableStopObserveButton() + self.moveCamera(self.seatBumpForObserve) + #track.append(Func(self.__disableCollisions)) + self.sendUpdate('requestGameZone') + #self.gameZone = base.cr.addInterest(base.localAvatar.defaultShard, boardZoneId, 'chineseBoard') + def exitObserving(self): + #self.__enableCollisions + if self.cameraBoardTrack.isPlaying(): + self.cameraBoardTrack.pause() + self.allowToWalk() #temp until i can stop the + #camera from jerking + self.stopObserveButton.destroy() + def enterSitting(self): + pass + #self.tempCheckers.hide() + def exitSitting(self): + self.gameMenu = None + #self.winTrack = None + #self.tempCheckers.show() + #self.suxButton.destroy() + #self.sendUpdate('requestExit', []) + ########## + #Observer Functions setGameZone (broadcast) + # + #Need these because you are not "filling" a slot by observing + # + #For this case - When it sends a 1 or a zero with the setGameZone, This is to account for the fact + #an observer can (and will) come into games halfway through, somehow that client needs to know + #about the current state of the game. + # + # + #1 == Playing + #0 == Not Playing + ########## + def setGameZone(self, zoneId, gamestate): + self.gameZone = base.cr.addInterest(base.localAvatar.defaultShard, zoneId, 'gameBoard') + self.gameState = gamestate + def observeButtonPushed(self): + #base.cr.playGame.getPlace().setState('walk') + self.requestSeat = None + self.fsm.request('observing') + def enableStopObserveButton(self): + self.stopObserveButton = DirectButton( + relief = None, + text = "Stop Observing", + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -.23), + text_scale = 0.45, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (.92, 0, 0.4), + scale = 0.15, + command = lambda self=self: self.stopObserveButtonPushed(), + ) + def stopObserveButtonPushed(self): + self.sendUpdate("leaveObserve", []) + self.gameState = None + if self.game: + self.game.fsm.request('gameOver') + base.cr.removeInterest(self.gameZone) + self.fsm.request('off') + + ########## + #Generators for jumps + # + #And the storage/deletion functions for + #handling them. + ########## + + def generateToonReverseJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping out of the golf kart.""" + self.notify.debug("av.getH() = %s" % av.getH()) + def getToonJumpTrack( av, destNode ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = destNode): + dest = node.getPos(self.tableCloth) + dest += self.jumpOffsets[seatIndex].getPos(self.tableCloth) + return dest + + def getJumpHpr(av = av, node = destNode): + hpr = node.getHpr(av.getParent()) + hpr.setX( hpr.getX() + 180) + angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX()) + hpr.setX(angle) + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.1), #43 ), + Parallel( #LerpHprInterval( av, + # hpr = getJumpHpr, + # duration = .9 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = .9 ) ) + ) + ) + return toonJumpTrack + + toonJumpTrack = getToonJumpTrack( av, self.tableCloth) + #self.seats[seatIndex]) + jumpTrack = Sequence( + toonJumpTrack, + Func( av.loop, 'neutral' ), + Func( av.wrtReparentTo, render ), + #Func( self.av.setPosHpr, self.exitMovieNode, 0,0,0,0,0,0 ), + ) + return jumpTrack + + def generateToonJumpTrack( self, av, seatIndex ): + """Return an interval of the toon jumping into the golf kart.""" + # Maintain a reference to Parent and Scale of avatar in case they + # exit from the kart. + #base.sb = self + + av.pose('sit', 47) + hipOffset = av.getHipsParts()[2].getPos(av) + + def getToonJumpTrack( av, seatIndex ): + # using a local func allows the ProjectileInterval to + # calculate this pos at run-time + def getJumpDest(av = av, node = self.tableCloth): + dest = Vec3(self.tableCloth.getPos(av.getParent())) + seatNode = self.picnicTable.find("**/seat" + str(seatIndex + 1)) + dest += seatNode.getPos(self.tableCloth) + dna = av.getStyle() + dest -= hipOffset + if(seatIndex > 2): + dest.setY(dest.getY() - 2.0) + if(seatIndex == 1): + dest.setY(dest.getY()-.5) + #dest.setY( dest.getY() + 2 * hipOffset.getY()) + #dest.setY(dest.getY()) + dest.setZ(dest.getZ() + 0.2) + + return dest + + def getJumpHpr(av = av, node = self.tableCloth): + hpr = self.seats[seatIndex].getHpr(av.getParent()) + if(seatIndex < 3): + hpr.setX( hpr.getX()) + else: + if(av.getH() < 0): + hpr.setX( hpr.getX()-180) + else: + hpr.setX(hpr.getX()+180) + + return hpr + + toonJumpTrack = Parallel( + ActorInterval( av, 'jump' ), + Sequence( + Wait( 0.43 ), + Parallel( LerpHprInterval( av, + hpr = getJumpHpr, + duration = 1 ), + ProjectileInterval( av, + endPos = getJumpDest, + duration = 1 ) + ), + ) + ) + return toonJumpTrack + + + def getToonSitTrack( av ): + toonSitTrack = Sequence( + + ActorInterval( av, 'sit-start' ), + Func( av.loop, 'sit' ) + ) + return toonSitTrack + + toonJumpTrack = getToonJumpTrack( av, seatIndex ) + toonSitTrack = getToonSitTrack( av ) + + jumpTrack = Sequence( + Parallel(toonJumpTrack, + Sequence( Wait(1), + toonSitTrack, + ), + ), + Func( av.wrtReparentTo, self.tableCloth ), + ) + + return jumpTrack + def storeToonTrack(self, avId, track): + # Clear out any currently playing tracks on this toon + self.clearToonTrack(avId) + # Store this new one + self.__toonTracks[avId] = track + + def clearToonTrack(self, avId): + # Clear out any currently playing tracks on this toon + oldTrack = self.__toonTracks.get(avId) + if oldTrack: + oldTrack.pause() + cleanupDelayDeletes(oldTrack) + #del self.__toonTracks[avId] + + def clearToonTracks(self): + #We can't use an iter because we are deleting keys + keyList = [] + for key in self.__toonTracks: + keyList.append(key) + + for key in keyList: + if self.__toonTracks.has_key(key): + self.clearToonTrack(key) + + def doNothing(self): + pass + + + + + diff --git a/toontown/src/safezone/DistributedPicnicTableAI.py b/toontown/src/safezone/DistributedPicnicTableAI.py new file mode 100644 index 0000000..2cd2e78 --- /dev/null +++ b/toontown/src/safezone/DistributedPicnicTableAI.py @@ -0,0 +1,352 @@ +from direct.distributed.DistributedNodeAI import DistributedNodeAI +from direct.distributed.ClockDelta import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from toontown.safezone import DistributedChineseCheckersAI +from toontown.safezone import DistributedCheckersAI +from toontown.safezone import DistributedFindFourAI + + +class DistributedPicnicTableAI(DistributedNodeAI): + def __init__(self, air, zone, name, x, y, z, h, p, r): + DistributedNodeAI.__init__(self,air) + self.name = name + self.air = air + + self.seats = [None, None, None, None, None, None] + self.setPos(x,y,z) + self.setHpr(h,p,r) + self.playersSitting = 0 + + self.myPos = (x,y,z) + self.myHpr = (h,p,r) + + self.playerIdList = [] + self.checkersZoneId = None + + + self.generateOtpObject(air.districtId, zone, optionalFields = ["setX","setY","setZ", "setH", "setP", "setR"]) + self.observers = [] + self.allowPickers = [] + self.hasPicked = False + self.game = None + self.gameDoId = None + + + #Will NOT allow boarders once game has begun + self.isAccepting = True + + def announceGenerate(self): + pass + + + def delete(self): + DistributedNodeAI.delete(self) + self.game = None + self.gameDoId = None + def setGameDoId(self, doId): + self.gameDoId = doId + self.game = self.air.doId2do.get(doId) + + + ############################################################### + #Table "State" + # + #These Functions send the current table state when the client asks + #for it, it is needed so that the client can (usually upon zone in) + #can keep or have a current status of the table, instead of only + #getting "deltas" of the tables state + ### + + def requestTableState(self): + avId = self.air.getAvatarIdFromSender() + self.getTableState() + + def getTableState(self): + tableStateList = [] + for x in self.seats: + if( x == None): + tableStateList.append(0) + else: + tableStateList.append(x) + if self.game and self.game.fsm.getCurrentState().getName() == "playing": + self.sendUpdate("setTableState", [tableStateList, 1]) + else: + self.sendUpdate("setTableState", [tableStateList, 0]) + def sendIsPlaying(self): + if self.game.fsm.getCurrentState().getName() == "playing": + self.sendUpdate("setIsPlaying", [1]) # 1 is true + else: + self.sendUpdate("setIsPlaying", [0]) # 0 is false + + ################################################################# + ## Announce Winner - announcement for table winner (sends whisper + ## on the client + ### + + def announceWinner(self, gameName, avId): + self.sendUpdate("announceWinner", [gameName, avId]) + self.gameDoId = None + self.game = None + + ################################################################ + # Table enter functions + #--- requstJoin (Distributed), rejectBoarder, acceptBoarder + # + #These functiosn handle all of the table requests to join ect + ### + def requestJoin(self,si,x,y,z,h,p,r): + #add him to the player list + avId = self.air.getAvatarIdFromSender() + if (self.findAvatar(avId) != None): + self.notify.warning("Ignoring multiple requests from %s to board." % (avId)) + return + + av = self.air.doId2do.get(avId) + + if av: + # Only toons with hp greater than 0 may board the trolley. + if (av.hp > 0) and self.isAccepting and self.seats[si] == None: + self.notify.debug("accepting boarder %d" % avId) + self.acceptBoarder(avId, si, x, y, z, h, p, r) + else: + self.notify.debug("rejecting boarder %d" % avId) + self.sendUpdateToAvatarId(avId, "rejectJoin", []) + #If he is rejected, no sense wasting bandwith just dont accept + else: + self.notify.warning( + "avid: %s does not exist, but tried to board a picnicTable" % avId + ) + def acceptBoarder(self, avId, seatIndex,x,y,z,h,p,r): + self.notify.debug("acceptBoarder %d" % avId) + # Make sure we have a valid seat number + # Make sure the seat is empty + # Make sure this avatar isn't already seated + if (self.findAvatar(avId) != None): + return + isEmpty = True + for xx in self.seats: + if xx != None: + isEmpty = False + break + if isEmpty == True or self.hasPicked == False: + self.sendUpdateToAvatarId(avId, "allowPick", []) + self.allowPickers.append(avId) #Only people ALLOWED to pick can pick games + + if self.hasPicked == True: + self.sendUpdateToAvatarId(avId, 'setZone', [self.game.zoneId]) + # Put the avatar in that seat + self.seats[seatIndex] = avId + # Add a hook that handles the case where the avatar exits + # the district unexpectedly + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + # Record the time of boarding + self.timeOfBoarding = globalClock.getRealTime() + #Count for the Game to know whos sitting and not "playing" yet + if self.game: + self.game.informGameOfPlayer() + # Tell the clients to put the avatar in that seat + self.sendUpdate("fillSlot", + [avId, seatIndex, x, y, z, h, p, r, + globalClockDelta.localToNetworkTime(self.timeOfBoarding), + self.doId]) + self.getTableState() + # Put us into waitCountdown state... If we are already there, + + def requestPickedGame(self, gameNum): + avId = self.air.getAvatarIdFromSender() + if self.hasPicked == False and avId in self.allowPickers: + self.hasPicked = True + numPickers = len(self.allowPickers) + self.allowPickers = [] + self.pickGame(gameNum) + if self.game: + for x in range(numPickers): + self.game.informGameOfPlayer() + def pickGame(self, gameNum): + x = 0 + for x in self.seats: + if x != None: + x += 1 + + if gameNum == 1: + if simbase.config.GetBool("want-chinese", 0): + self.game = DistributedChineseCheckersAI.DistributedChineseCheckersAI(self.air, self.doId, 'chinese', self.getX(), self.getY(), self.getZ()+2.83, self.getH(),self.getP(),self.getR()) + self.sendUpdate('setZone', [self.game.zoneId]) + elif gameNum == 2: + if x <= 2: + if simbase.config.GetBool("want-checkers", 0): + self.game = DistributedCheckersAI.DistributedCheckersAI(self.air, self.doId, 'checkers', self.getX(), self.getY(), self.getZ()+2.83, self.getH(),self.getP(),self.getR()) + self.sendUpdate('setZone', [self.game.zoneId]) + else: + if x <= 2: + if simbase.config.GetBool("want-findfour", 0): + self.game = DistributedFindFourAI.DistributedFindFourAI(self.air, self.doId, 'findFour', self.getX(), self.getY(), self.getZ()+2.83, self.getH(),self.getP(),self.getR()) + self.sendUpdate('setZone', [self.game.zoneId]) + def requestZone(self): + avId = self.air.getAvatarIdFromSender() + self.sendUpdateToAvatarId(avId, 'setZone', [self.game.zoneId]) + ######################################################### + # Observer ONLY + # + #These game zone functions are specific to the "observer" case because the zoneId ect, + #are sent as a package in the Fillslot function + # + # + #For this case - When it sends a 1 or a zero with the setGameZone, This is to account for the fact + #an observer can (and will) come into games halfway through, somehow that client needs to know + #about the current state of the game. + # + # + #1 == Playing + #0 == Not Playing + ######## + def requestGameZone(self): + if self.hasPicked == True: + avId = self.air.getAvatarIdFromSender() + if self.game: + self.game.playersObserving.append(avId) + self.observers.append(avId) + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.handleObserverExit, extraArgs=[avId]) + if self.game: + if self.game.fsm.getCurrentState().getName() == 'playing': + self.sendUpdateToAvatarId(avId, "setGameZone", [self.checkersZoneId, 1]) + else: + self.sendUpdateToAvatarId(avId, "setGameZone", [self.checkersZoneId, 0]) + def leaveObserve(self): + avId = self.air.getAvatarIdFromSender() + if self.game: + if avId in self.game.playersObserving: + self.game.playersObserving.remove(avId) + def handleObserverExit(self, avId): + if self.game and avId in self.game.playersObserving: + if self.game: + self.game.playersObserving.remove(avId) + self.ignore(self.air.getAvatarExitEvent(avId)) + + ################################################################# + # Table Exit functions + #--- requestExit (Distributed), acceptExiter, __handleUnexpected Exit + # + #These functions handle all of the Exiting of the table + ### + def requestExit(self): + self.notify.debug("requestExit") + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + if av: + # Check to make sure the AI hasn't already told the players to exit + if (self.countFullSeats() > 0): + self.acceptExiter(avId) + else: + self.notify.debug("Player tried to exit after AI already kicked everyone out") + else: + self.notify.warning( + "avId: %s does not exist, but tried to exit picnicTable" % avId + ) + def acceptExiter(self, avId): + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # It is possible that the avatar exited the shard unexpectedly. + if seatIndex == None: + if avId in self.observers: + self.sendUpdateToAvatarId(avId, "emptySlot", [avId, 255, globalClockDelta.getRealNetworkTime()]) + else: + self.seats[seatIndex] = None + #Dont care about clients unexpected exit + self.ignore(self.air.getAvatarExitEvent(avId)) + + + # Tell the clients that the avatar is leaving that seat + self.sendUpdate("emptySlot", + [avId, seatIndex, globalClockDelta.getRealNetworkTime()]) + self.getTableState() + numActive = 0 + for x in self.seats: + if x != None: + numActive = numActive +1 + if self.game: + self.game.informGameOfPlayerLeave() + self.game.handlePlayerExit(avId) + if numActive == 0: + self.isAccepting = True + if self.game: + self.game.handleEmptyGame() + #del self.game + self.game.requestDelete() + self.game = None + self.hasPicked = False + + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + seatIndex = self.findAvatar(avId) + + # Make sure the avatar is really here + if seatIndex == None: + pass + else: + self.seats[seatIndex] = None + #Dont care about clients unexpected exit + self.ignore(self.air.getAvatarExitEvent(avId)) + if self.game: + self.game.informGameOfPlayerLeave() + self.game.handlePlayerExit(avId) + self.hasPicked = False + self.getTableState() + numActive = 0 + for x in self.seats: + if x != None: + numActive = numActive +1 + if numActive == 0 and self.game: + simbase.air.deallocateZone(self.game.zoneId) + self.game.requestDelete() + self.game = None + self.gameDoId = None + def informGameOfPlayerExit(self, avId): + self.game.handlePlayerExit(avId) + def handleGameOver(self): + for x in self.observers: + self.acceptExiter(x) + self.observers.remove(x) + if self.game: + self.game.playersObserving = [] + for x in self.seats: + if x != None: + self.acceptExiter(x) + self.game = None + self.gameDoId = None + self.hasPicked = False + + + ################################################## + # Helper functions + #--- findAvatar, countFullSeats, findAvailableSeat + ### + def findAvatar(self, avId): + for i in range(len(self.seats)): + if self.seats[i] == avId: + return i + return None + + def countFullSeats(self): + avCounter = 0 + for i in self.seats: + if i: + avCounter += 1 + return avCounter + + def findAvailableSeat(self): + for i in range(len(self.seats)): + if self.seats[i] == None: + return i + return None + def setCheckersZoneId(self, zoneId): + self.checkersZoneId = zoneId + ############END OF HELPERS######################## + + diff --git a/toontown/src/safezone/DistributedSZTreasure.py b/toontown/src/safezone/DistributedSZTreasure.py new file mode 100644 index 0000000..14e37e2 --- /dev/null +++ b/toontown/src/safezone/DistributedSZTreasure.py @@ -0,0 +1,134 @@ +import DistributedTreasure +from pandac.PandaModules import VBase3, VBase4 +from direct.interval.IntervalGlobal import Sequence, Wait, Func, LerpColorScaleInterval, LerpScaleInterval +from toontown.toonbase import ToontownGlobals + +class DistributedSZTreasure(DistributedTreasure.DistributedTreasure): + def __init__(self, cr): + DistributedTreasure.DistributedTreasure.__init__(self, cr) + + self.fadeTrack = None + self.heartThrobIval = None + + self.accept('ValentinesDayStart', self.startValentinesDay) + self.accept('ValentinesDayStop', self.stopValentinesDay) + + def delete(self): + DistributedTreasure.DistributedTreasure.delete(self) + # Stop the movie (if there is one) + if self.fadeTrack: + self.fadeTrack.finish() + self.fadeTrack = None + + if self.heartThrobIval: + self.heartThrobIval.finish() + self.heartThrobIval = None + + self.ignore('ValentinesDayStart') + self.ignore('ValentinesDayStop') + + def setHolidayModelPath(self): + """ + Change the modelPath if a holiday is running. + """ + # Set the default model path. + self.defaultModelPath = self.modelPath + holidayIds = base.cr.newsManager.getHolidayIdList() + if ToontownGlobals.VALENTINES_DAY in holidayIds: + self.modelPath = "phase_4/models/props/tt_m_ara_ext_heart" + + def loadModel(self, modelPath, modelFindString = None): + """ + Overiding loadModel of Distributed Treasure to account for holiday based treasures. + """ + self.setHolidayModelPath() + DistributedTreasure.DistributedTreasure.loadModel(self, self.modelPath, modelFindString) + + def startValentinesDay(self): + """ + Do some show when the holiday starts. + Change all the Ice Cream Cones to Hearts when the Valentines Day starts. + """ + newModelPath = "phase_4/models/props/tt_m_ara_ext_heart" + self.replaceTreasure(newModelPath) + self.startAnimation() + + def stopValentinesDay(self): + """ + Do some show when the holiday ends. + Change all the Hearts to Cones when the Valentines Day ends. + """ + self.replaceTreasure(self.defaultModelPath) + self.stopAnimation() + + def replaceTreasure(self, newModelPath): + """ + The new treasure is replaced with the old treasure with a fadeInOut interval. + """ + if self.fadeTrack: + self.fadeTrack.finish() + self.fadeTrack = None + + def replaceTreasureFunc(newModelPath): + if self.nodePath == None: + self.makeNodePath() + else: + self.treasure.getChildren().detach() + # Load the treasure model and put it under our root node. + model = loader.loadModel(newModelPath) + model.instanceTo(self.treasure) + + def getFadeOutTrack(): + fadeOutTrack = LerpColorScaleInterval(self.nodePath, 0.8, + colorScale = VBase4(0, 0, 0, 0), + startColorScale = VBase4(1, 1, 1, 1), + blendType = 'easeIn') + return fadeOutTrack + + def getFadeInTrack(): + fadeInTrack = LerpColorScaleInterval(self.nodePath, 0.5, + colorScale = VBase4(1, 1, 1, 1), + startColorScale = VBase4(0, 0, 0, 0), + blendType = 'easeOut') + return fadeInTrack + + base.playSfx(self.rejectSound, node = self.nodePath) + self.fadeTrack = Sequence( + getFadeOutTrack(), + Func(replaceTreasureFunc, newModelPath), + getFadeInTrack(), + name = self.uniqueName("treasureFadeTrack")) + self.fadeTrack.start() + + def startAnimation(self): + """ + Make the Safezone Treasures animate if there is an animation associated. + Currently, only the Valentine's Day Hearts animate. + """ + holidayIds = base.cr.newsManager.getHolidayIdList() + if ToontownGlobals.VALENTINES_DAY in holidayIds: + # Animate the Valentine's Day Hearts here. + originalScale = self.nodePath.getScale() + throbScale = VBase3(0.85, 0.85, 0.85) + throbInIval = LerpScaleInterval(self.nodePath, 0.3, + scale = throbScale, + startScale = originalScale, + blendType = 'easeIn') + throbOutIval = LerpScaleInterval(self.nodePath, 0.3, + scale = originalScale, + startScale = throbScale, + blendType = 'easeOut') + + self.heartThrobIval = Sequence(throbInIval, + throbOutIval, + Wait(0.75)) + self.heartThrobIval.loop() + + def stopAnimation(self): + """ + Stop anything that is animating. + """ + if self.heartThrobIval: + self.heartThrobIval.finish() + self.heartThrobIval = None + \ No newline at end of file diff --git a/toontown/src/safezone/DistributedSZTreasureAI.py b/toontown/src/safezone/DistributedSZTreasureAI.py new file mode 100644 index 0000000..dc7ea0d --- /dev/null +++ b/toontown/src/safezone/DistributedSZTreasureAI.py @@ -0,0 +1,31 @@ +import DistributedTreasureAI +from toontown.toonbase import ToontownGlobals + +class DistributedSZTreasureAI(DistributedTreasureAI.DistributedTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedTreasureAI.DistributedTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + self.healAmount = treasurePlanner.healAmount + + # override the validate function to indicate that only toons who + # need healing can pick up treasures. + def validAvatar(self, av): + return (av.hp > 0) and (av.hp < av.maxHp) + + + # override the grab function and try to heal the toon + def d_setGrab(self, avId): + DistributedTreasureAI.DistributedTreasureAI.d_setGrab(self, avId) + # Boost that laff meter, if you can + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + # Only toons with positive hp get rewarded for treasures. + if (av.hp > 0) and (av.hp < av.maxHp): + # Modify the heal amount based on which holiday is running. + if simbase.air.holidayManager.currentHolidays.has_key(ToontownGlobals.VALENTINES_DAY): + av.toonUp(self.healAmount * 2) + else: + av.toonUp(self.healAmount) + diff --git a/toontown/src/safezone/DistributedTTTreasure.py b/toontown/src/safezone/DistributedTTTreasure.py new file mode 100644 index 0000000..38d6987 --- /dev/null +++ b/toontown/src/safezone/DistributedTTTreasure.py @@ -0,0 +1,8 @@ +import DistributedSZTreasure + +class DistributedTTTreasure(DistributedSZTreasure.DistributedSZTreasure): + def __init__(self, cr): + DistributedSZTreasure.DistributedSZTreasure.__init__(self, cr) + self.modelPath = "phase_4/models/props/icecream" + self.grabSoundPath = "phase_4/audio/sfx/SZ_DD_treasure.mp3" + \ No newline at end of file diff --git a/toontown/src/safezone/DistributedTTTreasureAI.py b/toontown/src/safezone/DistributedTTTreasureAI.py new file mode 100644 index 0000000..c4d5ca8 --- /dev/null +++ b/toontown/src/safezone/DistributedTTTreasureAI.py @@ -0,0 +1,9 @@ +import DistributedSZTreasureAI + +class DistributedTTTreasureAI(DistributedSZTreasureAI.DistributedSZTreasureAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedSZTreasureAI.DistributedSZTreasureAI.__init__(self, air, + treasurePlanner, + x, y, z) + diff --git a/toontown/src/safezone/DistributedTreasure.py b/toontown/src/safezone/DistributedTreasure.py new file mode 100644 index 0000000..dd9de99 --- /dev/null +++ b/toontown/src/safezone/DistributedTreasure.py @@ -0,0 +1,275 @@ + +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from toontown.toonbase.ToontownGlobals import * + +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal + +class DistributedTreasure(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedTreasure") + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + self.av = None + self.treasureFlyTrack = None + self.modelPath = None + self.nodePath = None + self.dropShadow = None + self.modelFindString = None + self.grabSoundPath = None + + # The default treasure reject sound is the same for all + # treasures. Still, particular safe zones can override this + # if they want to. + self.rejectSoundPath = "phase_4/audio/sfx/ring_miss.mp3" + + self.playSoundForRemoteToons = 1 + self.scale = 1. + self.shadow = 1 + self.fly = 1 + self.zOffset = 0. + self.billboard = 0 + + def disable(self): + self.ignoreAll() + self.nodePath.detachNode() + DistributedObject.DistributedObject.disable(self) + + def delete(self): + # Stop the movie (if there is one) + if self.treasureFlyTrack: + self.treasureFlyTrack.finish() + self.treasureFlyTrack = None + # Call up the chain + DistributedObject.DistributedObject.delete(self) + + # Really, we don't want to unloadModel on this, until we're + # leaving the safezone. Calling unloadModel will force the + # next treasure of this type to reload from disk. + #loader.unloadModel(self.modelPath) + + self.nodePath.removeNode() + + def announceGenerate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.announceGenerate(self) + + # Load the model, (using loadModelOnce), and child it to the nodepath + self.loadModel(self.modelPath, self.modelFindString) + # animate if necessary + self.startAnimation() + # Put this thing in the world + self.nodePath.wrtReparentTo(render) + # Add a hook looking for collisions with localToon, and call + # requestGrab. + self.accept(self.uniqueName('entertreasureSphere'), + self.handleEnterSphere) + + def handleEnterSphere(self, collEntry=None): + # Only toons with hp > 0 can pick up treasures. + #if base.localAvatar.hp > 0: + localAvId = base.localAvatar.getDoId() + + # if treasure is not going to fly, make it disappear + # right away + if not self.fly: + self.handleGrab(localAvId) + + self.d_requestGrab() + + def d_requestGrab(self): + self.sendUpdate("requestGrab", []) + + def getSphereRadius(self): + """getSphereRadius(self) + This method can be overwritten by an inheritor. + """ + return 2.0 + + def loadModel(self, modelPath, modelFindString = None): + # Load the sound effect + self.grabSound = base.loadSfx(self.grabSoundPath) + self.rejectSound = base.loadSfx(self.rejectSoundPath) + + if self.nodePath == None: + self.makeNodePath() + else: + self.treasure.getChildren().detach() + + # Load the treasure model and put it under our root node. + model = loader.loadModel(modelPath) + if modelFindString != None: + model = model.find("**/" + modelFindString) + assert model != None + model.instanceTo(self.treasure) + + def makeNodePath(self): + self.nodePath = NodePath(self.uniqueName("treasure")) + if self.billboard: + self.nodePath.setBillboardPointEye() + self.nodePath.setScale(0.9*self.scale) + + self.treasure = self.nodePath.attachNewNode('treasure') + + if self.shadow: + if not self.dropShadow: + # Load the dropShadow + self.dropShadow = loader.loadModel( + "phase_3/models/props/drop_shadow") + # Set the shadow color + self.dropShadow.setColor(0, 0, 0, 0.5) + self.dropShadow.setPos(0,0,0.025) + self.dropShadow.setScale(0.4*self.scale) + + # Might as well apply the transforms to the shadow vertices. + self.dropShadow.flattenLight() + # Parent the dropShadow to the NodePath root + self.dropShadow.reparentTo(self.nodePath) + + # Make a sphere, name it uniqueName("treasureSphere"), and child it + # to the nodepath. + collSphere = CollisionSphere(0, 0, 0, self.getSphereRadius()) + # Make the sphere intangible + collSphere.setTangible(0) + collNode = CollisionNode(self.uniqueName("treasureSphere")) + collNode.setIntoCollideMask(WallBitmask) + collNode.addSolid(collSphere) + self.collNodePath = self.nodePath.attachNewNode(collNode) + self.collNodePath.stash() + + def getParentNodePath(self): + """getParentNodePath(self) + This defaults to render, but may be overridden by an inheritor + """ + return render + + # The handler that catches the initial position established on the AI + def setPosition(self, x, y, z): + if not self.nodePath: + self.makeNodePath() + + self.nodePath.reparentTo(self.getParentNodePath()) + self.nodePath.setPos(x, y, z+self.zOffset) + self.collNodePath.unstash() + + def setGrab(self, avId): + if avId == 0: + # avId of 0 indicates it hasn't been grabbed by anyone. + return + + # ignore this message if we have already called handleGrab + if self.fly or avId != base.localAvatar.getDoId(): + self.handleGrab(avId) + + def setReject(self): + # Fade the treasure out and back in again to indicate a failed + # grab. + + if self.treasureFlyTrack: + self.treasureFlyTrack.finish() + self.treasureFlyTrack = None + + base.playSfx(self.rejectSound, node = self.nodePath) + self.treasureFlyTrack = Sequence( + LerpColorScaleInterval(self.nodePath, 0.8, + colorScale = VBase4(0, 0, 0, 0), + startColorScale = VBase4(1, 1, 1, 1), + blendType = 'easeIn'), + LerpColorScaleInterval(self.nodePath, 0.2, + colorScale = VBase4(1, 1, 1, 1), + startColorScale = VBase4(0, 0, 0, 0), + blendType = 'easeOut'), + name = self.uniqueName("treasureFlyTrack")) + self.treasureFlyTrack.start() + + def handleGrab(self, avId): + # this function handles client-side 'grabbed' behavior + # NOTE: if the treasure does not fly towards the toon, + # this function will be called immediately, before the + # AI comes back and announces that the treasure was grabbed. + # Someone else may actually end up getting credit for the + # grab from the AI, after this has been called. + + # First, do not try to grab this treasure anymore. + self.collNodePath.stash() + # Save the avId for later, we may need it if there is an unexpected + # exit. + self.avId = avId + + # Look up the avatar + if self.cr.doId2do.has_key(avId): + av = self.cr.doId2do[avId] + self.av = av + else: + # I guess he disconnected... Just hide the treasure + self.nodePath.detachNode() + return + + # Play a sound effect, if appropriate + if self.playSoundForRemoteToons or \ + (self.avId == base.localAvatar.getDoId()): + base.playSfx(self.grabSound, node = self.nodePath) + + if not self.fly: + # don't make it fly, just make it disappear + self.nodePath.detachNode() + return + + # Reparent the treasure to the toon + self.nodePath.wrtReparentTo(av) + + # Create the flying treasure track + if self.treasureFlyTrack: + self.treasureFlyTrack.finish() + self.treasureFlyTrack = None + + # Add a hook in case this avatar gets deleted while the + # Treasure is flying. + avatarGoneName = self.av.uniqueName("disable") + self.accept(avatarGoneName, self.handleUnexpectedExit) + + flytime = 1.0 + track = Sequence( + LerpPosInterval(self.nodePath, + flytime, + pos = Point3(0, 0, 3), + startPos = self.nodePath.getPos(), + blendType = "easeInOut"), + Func(self.nodePath.detachNode), + Func(self.ignore, avatarGoneName)) + + if self.shadow: + self.treasureFlyTrack = Sequence( + HideInterval(self.dropShadow), + track, + ShowInterval(self.dropShadow), + name = self.uniqueName("treasureFlyTrack")) + else: + self.treasureFlyTrack = Sequence( + track, + name = self.uniqueName("treasureFlyTrack")) + + self.treasureFlyTrack.start() + + def handleUnexpectedExit(self): + # The avatar we were flying the treasure to just disconnected. + self.notify.warning("While getting treasure, " + str(self.avId) + + " disconnected.") + # Stop the multitrack + if self.treasureFlyTrack: + self.treasureFlyTrack.finish() + self.treasureFlyTrack = None + + def getStareAtNodeAndOffset(self): + return self.nodePath, Point3() + + def startAnimation(self): + # Most treasures don't have default animations + # Estate flying treasures might, so add this base + # class function + pass diff --git a/toontown/src/safezone/DistributedTreasureAI.py b/toontown/src/safezone/DistributedTreasureAI.py new file mode 100644 index 0000000..aace27a --- /dev/null +++ b/toontown/src/safezone/DistributedTreasureAI.py @@ -0,0 +1,48 @@ +from otp.ai.AIBase import * +from direct.distributed.ClockDelta import * + +from direct.distributed import DistributedObjectAI + +class DistributedTreasureAI(DistributedObjectAI.DistributedObjectAI): + + def __init__(self, air, treasurePlanner, x, y, z): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.treasurePlanner = treasurePlanner + self.pos = (x, y, z) + + def requestGrab(self): + # This is the handler that gets called when a localToon tries to grab + # a DistributedTreasure + avId = self.air.getAvatarIdFromSender() + self.treasurePlanner.grabAttempt(avId, self.getDoId()) + + def validAvatar(self, av): + # This is called by the treasure planner to ask if this + # particular avatar is allowed to have this treasure. + return 1 + + def d_setGrab(self, avId): + # This is how the treasurePlanner tells everyone that this treasure + # has been grabbed. + self.sendUpdate("setGrab", [avId]) + + def d_setReject(self): + # This is how the treasurePlanner tells everyone that this treasure + # has been attempted for, but rejected. + self.sendUpdate("setReject", []) + + def getPosition(self): + # This is needed because setPosition is a required field. + return self.pos + + def setPosition(self, x, y, z): + self.pos = (x, y, z) + + def b_setPosition(self, x, y, z): + self.setPosition(x, y, z) + self.d_setPosition(x, y, z) + + def d_setPosition(self, x, y, z): + # This is how the treasurePlanner tells everyone that this treasure + # has been attempted for, but rejected. + self.sendUpdate("setPosition", [x, y, z]) diff --git a/toontown/src/safezone/DistributedTrolley.py b/toontown/src/safezone/DistributedTrolley.py new file mode 100644 index 0000000..eb794b1 --- /dev/null +++ b/toontown/src/safezone/DistributedTrolley.py @@ -0,0 +1,633 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * + +from toontown.toonbase import ToontownGlobals +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from toontown.distributed import DelayDelete +from direct.task.Task import Task +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + + +class DistributedTrolley(DistributedObject.DistributedObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedTrolley") + + def __init__(self, cr): + """__init__(cr) + """ + DistributedObject.DistributedObject.__init__(self, cr) + + self.localToonOnBoard = 0 + + self.trolleyCountdownTime = \ + base.config.GetFloat("trolley-countdown-time", + TROLLEY_COUNTDOWN_TIME) + + self.fsm = ClassicFSM.ClassicFSM('DistributedTrolley', + [State.State('off', + self.enterOff, + self.exitOff, + ['entering', + 'waitEmpty', + 'waitCountdown', + 'leaving']), + State.State('entering', + self.enterEntering, + self.exitEntering, + ['waitEmpty']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty', 'leaving']), + State.State('leaving', + self.enterLeaving, + self.exitLeaving, + ['entering'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + self.trolleyAwaySfx = base.loadSfx("phase_4/audio/sfx/SZ_trolley_away.mp3") + self.trolleyBellSfx = base.loadSfx("phase_4/audio/sfx/SZ_trolley_bell.mp3") + + # Tracks on toons, for starting and stopping + # stored by avId : track. There is only a need for one at a time, + # in fact the point of the dict is to ensure only one is playing at a time + self.__toonTracks = {} + + def generate(self): + """generate(self) + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedObject.DistributedObject.generate(self) + + # Get the state machine stuff for playGame + self.loader = self.cr.playGame.hood.loader + self.trolleyStation = self.loader.geom.find('**/*trolley_station*') + self.trolleyCar = self.trolleyStation.find('**/trolley_car') + self.trolleySphereNode = self.trolleyStation.find('**/trolley_sphere').node() + + # We'll need a pair of fog objects to enshadow the trolley + # while it's rolling through the entrance or exit tunnels. + + exitFog = Fog("TrolleyExitFog") + exitFog.setColor(0.0, 0.0, 0.0) + exitFog.setLinearOnsetPoint(30.0, 14.0, 0.0) + exitFog.setLinearOpaquePoint(37.0, 14.0, 0.0) + exitFog.setLinearFallback(70.0, 999.0, 1000.0) + self.trolleyExitFog = self.trolleyStation.attachNewNode(exitFog) + self.trolleyExitFogNode = exitFog + + enterFog = Fog("TrolleyEnterFog") + enterFog.setColor(0.0, 0.0, 0.0) + enterFog.setLinearOnsetPoint(0.0, 14.0, 0.0) + enterFog.setLinearOpaquePoint(-7.0, 14.0, 0.0) + enterFog.setLinearFallback(70.0, 999.0, 1000.0) + self.trolleyEnterFog = self.trolleyStation.attachNewNode(enterFog) + self.trolleyEnterFogNode = enterFog + + # We'll have fog explicitly disabled for the trolley car, by + # default. This makes it look maybe a little weird in + # Donald's Dock--why does the trolley punch through the fog so + # well? But it keeps the trolley from flashing in and out as + # we turn on and off the shadow fog. + self.trolleyCar.setFogOff() + + # Variables used to animate trolley parts + # Key + self.keys = self.trolleyCar.findAllMatches('**/key') + self.numKeys = self.keys.getNumPaths() + self.keyInit = [] + self.keyRef = [] + for i in range(self.numKeys): + key = self.keys[i] + key.setTwoSided(1) + ref = self.trolleyCar.attachNewNode('key' + `i` + 'ref') + ref.iPosHpr(key) + self.keyRef.append(ref) + self.keyInit.append(key.getTransform()) + # Front wheels + self.frontWheels = self.trolleyCar.findAllMatches('**/front_wheels') + self.numFrontWheels = self.frontWheels.getNumPaths() + self.frontWheelInit = [] + self.frontWheelRef = [] + for i in range(self.numFrontWheels): + wheel = self.frontWheels[i] + ref = self.trolleyCar.attachNewNode('frontWheel' + `i` + 'ref') + ref.iPosHpr(wheel) + self.frontWheelRef.append(ref) + self.frontWheelInit.append(wheel.getTransform()) + # Back wheels + self.backWheels = self.trolleyCar.findAllMatches('**/back_wheels') + self.numBackWheels = self.backWheels.getNumPaths() + self.backWheelInit = [] + self.backWheelRef = [] + for i in range(self.numBackWheels): + wheel = self.backWheels[i] + ref = self.trolleyCar.attachNewNode('backWheel' + `i` + 'ref') + ref.iPosHpr(wheel) + self.backWheelRef.append(ref) + self.backWheelInit.append(wheel.getTransform()) + + # Create the trolley enter track + trolleyAnimationReset = Func(self.resetAnimation) + trolleyEnterStartPos = Point3(-20, 14, -1) + trolleyEnterEndPos = Point3(15, 14, -1) + + trolleyEnterPos = Sequence(name="TrolleyEnterPos") + if base.wantFog: + trolleyEnterPos.append(Func(self.trolleyCar.setFog, self.trolleyEnterFogNode)) + trolleyEnterPos.append(self.trolleyCar.posInterval( + TROLLEY_ENTER_TIME, + trolleyEnterEndPos, + startPos=trolleyEnterStartPos, + blendType="easeOut")) + if base.wantFog: + trolleyEnterPos.append(Func(self.trolleyCar.setFogOff)) + + trolleyEnterTrack = Sequence(trolleyAnimationReset, + trolleyEnterPos, + name = 'trolleyEnter') + # + # How many revolutions of the wheel? + keyAngle = round(TROLLEY_ENTER_TIME) * 360 + dist = Vec3(trolleyEnterEndPos - trolleyEnterStartPos).length() + wheelAngle = dist/(2.0 * math.pi * 0.95) * 360 + trolleyEnterAnimateInterval = LerpFunctionInterval( + self.animateTrolley, + duration = TROLLEY_ENTER_TIME, + blendType = "easeOut", + extraArgs = [keyAngle, wheelAngle], + name = "TrolleyAnimate") + trolleyEnterSoundTrack = SoundInterval(self.trolleyAwaySfx, node=self.trolleyCar) + self.trolleyEnterTrack = Parallel(trolleyEnterTrack, + trolleyEnterAnimateInterval, + trolleyEnterSoundTrack, + ) + + # Create the trolley exit track + trolleyExitStartPos = Point3(15, 14, -1) + trolleyExitEndPos = Point3(50, 14, -1) + + trolleyExitPos = Sequence(name="TrolleyExitPos") + if base.wantFog: + trolleyExitPos.append(Func(self.trolleyCar.setFog, self.trolleyExitFogNode)) + trolleyExitPos.append(self.trolleyCar.posInterval( + TROLLEY_EXIT_TIME, + trolleyExitEndPos, + startPos=trolleyExitStartPos, + blendType="easeIn")) + if base.wantFog: + trolleyExitPos.append(Func(self.trolleyCar.setFogOff)) + + + trolleyExitBellInterval = SoundInterval(self.trolleyBellSfx, node=self.trolleyCar) + trolleyExitAwayInterval = SoundInterval(self.trolleyAwaySfx, node=self.trolleyCar) + + keyAngle = round(TROLLEY_EXIT_TIME) * 360 + dist = Vec3(trolleyExitEndPos - trolleyExitStartPos).length() + wheelAngle = dist/(2.0 * math.pi * 0.95) * 360 + trolleyExitAnimateInterval = LerpFunctionInterval( + self.animateTrolley, + duration = TROLLEY_EXIT_TIME, + blendType = "easeIn", + extraArgs = [keyAngle, wheelAngle], + name = "TrolleyAnimate") + + self.trolleyExitTrack = Parallel(trolleyExitPos, + trolleyExitBellInterval, + trolleyExitAwayInterval, + trolleyExitAnimateInterval, + name = self.uniqueName("trolleyExit") + ) + + def disable(self): + DistributedObject.DistributedObject.disable(self) + # Go to the off state when the object is put in the cache + self.fsm.request("off") + + # No more toon animating + self.clearToonTracks() + + self.trolleyExitFog.removeNode() + del self.trolleyExitFog + del self.trolleyExitFogNode + self.trolleyEnterFog.removeNode() + del self.trolleyEnterFog + del self.trolleyEnterFogNode + + del self.loader + self.trolleyEnterTrack.pause() + self.trolleyEnterTrack = None + # del'ing this will cause the application to exit with an error code: del self.trolleyEnterTrack + # Lets try it again - maybe the ghosts are gone now? + # If we leave it commented out, we leak trolleys on the clients + del self.trolleyEnterTrack + self.trolleyExitTrack.pause() + self.trolleyExitTrack = None + del self.trolleyExitTrack + del self.trolleyStation + del self.trolleyCar + del self.keys + del self.numKeys + del self.keyInit + del self.keyRef + del self.frontWheels + del self.numFrontWheels + del self.frontWheelInit + del self.frontWheelRef + del self.backWheels + del self.numBackWheels + del self.backWheelInit + del self.backWheelRef + + def delete(self): + del self.trolleyAwaySfx + del self.trolleyBellSfx + DistributedObject.DistributedObject.delete(self) + del self.fsm + + def setState(self, state, timestamp): + self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)]) + + def allowedToEnter(self): + """Check if the local toon is allowed to enter.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ): + # trialer going to TTC/Estate/Goofy Speedway, let them through + return True + return False + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + def handleEnterTrolleySphere(self, collEntry): + self.notify.debug("Entering Trolley Sphere....") + + # To counter the toon trap bug + if(base.localAvatar.getPos(render).getZ() < (self.trolleyCar.getPos(render).getZ())): + return + if self.allowedToEnter(): + # Put localToon into requestBoard mode. + self.loader.place.detectedTrolleyCollision() + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='minigames', + doneFunc=self.handleOkTeaser) + + def handleEnterTrolley(self): + # Tell the server that this avatar wants to board. + toon = base.localAvatar + self.sendUpdate("requestBoard",[]) + + def fillSlot0(self, avId): + self.fillSlot(0, avId) + + def fillSlot1(self, avId): + self.fillSlot(1, avId) + + def fillSlot2(self, avId): + self.fillSlot(2, avId) + + def fillSlot3(self, avId): + self.fillSlot(3, avId) + + def fillSlot(self, index, avId): + #print "fill Slot: %d for %d" % (index, avId) + if avId == 0: + # This means that the slot is now empty, and no action should + # be taken. + pass + else: + # If localToon is boarding, he needs to change state and board the trolley. + if avId == base.localAvatar.getDoId(): + # Ignore this message if it comes late (e.g., trolley is in the leaving state). + if not (self.fsm.getCurrentState().getName() == 'waitEmpty' or + self.fsm.getCurrentState().getName() == 'waitCountdown'): + self.notify.warning("Can't board the trolley while in the '%s' state." % + (self.fsm.getCurrentState().getName())) + self.loader.place.fsm.request('walk') + return + + self.loader.place.trolley.fsm.request("boarding", [self.trolleyCar]) + self.localToonOnBoard = 1 + + # Tell him he's on the trolley now. + self.loader.place.trolley.fsm.request("boarded") + + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + # Parent it to the trolley + toon.stopSmooth() + toon.wrtReparentTo(self.trolleyCar) + toon.setAnimState("run", 1.0) + toon.headsUp(-5, -4.5 + (index * 3), 1.4) + + sitStartDuration = toon.getDuration("sit-start") + + track = Sequence( + LerpPosInterval(toon, TOON_BOARD_TIME * 0.75, + Point3(-5, -4.5 + (index * 3), 1.4)), + LerpHprInterval(toon, TOON_BOARD_TIME * 0.25, + Point3(90, 0, 0)), + Parallel(Sequence(Wait(sitStartDuration*0.25), + LerpPosInterval(toon, sitStartDuration*0.25, + Point3(-3.9, -4.5 + (index * 3), 3.0)), + ), + ActorInterval(toon, "sit-start"), + ), + Func(toon.setAnimState, "Sit", 1.0), + Func(self.clearToonTrack, avId), + name = toon.uniqueName("fillTrolley"), + autoPause = 1) + + track.delayDelete = DelayDelete.DelayDelete(toon, 'Trolley.fillSlot') + self.storeToonTrack(avId, track) + track.start() + else: + DistributedTrolley.notify.warning("toon: " + str(avId) + + " doesn't exist, and" + + " cannot board the trolley!") + + def emptySlot0(self, avId, timestamp): + self.emptySlot(0, avId, timestamp) + + def emptySlot1(self, avId, timestamp): + self.emptySlot(1, avId, timestamp) + + def emptySlot2(self, avId, timestamp): + self.emptySlot(2, avId, timestamp) + + def emptySlot3(self, avId, timestamp): + self.emptySlot(3, avId, timestamp) + + def notifyToonOffTrolley(self, toon): + toon.setAnimState("neutral", 1.0) + if toon == base.localAvatar: + self.loader.place.trolley.handleOffTrolley() + self.localToonOnBoard = 0 + else: + toon.startSmooth() + return + + def emptySlot(self, index, avId, timestamp): + #print "Emptying slot: %d for %d" % (index, avId) + # If localToon is exiting, he needs to change state + if avId == 0: + # This means that no one is currently exiting, and no action + # should be taken + pass + else: + if self.cr.doId2do.has_key(avId): + # If the toon exists, look it up + toon = self.cr.doId2do[avId] + # Parent it to render + toon.setHpr(self.trolleyCar, 90,0,0) + toon.wrtReparentTo(render) + toon.stopSmooth() + # toon.setAnimState("run", 1.0) + + # Place it on the appropriate spot relative to the + # trolley station + + sitStartDuration = toon.getDuration("sit-start") + + track = Sequence( + # Hop off the seat + Parallel(ActorInterval(toon, "sit-start", + startTime=sitStartDuration, + endTime=0.0), + Sequence(Wait(sitStartDuration*0.5), + LerpPosInterval(toon, sitStartDuration*0.25, + Point3(-5, -4.5 + (index * 3), 1.4), + other=self.trolleyCar), + ), + ), + # Then run + Func(toon.setAnimState, "run", 1.0), + LerpPosInterval(toon, TOON_EXIT_TIME, + Point3(21 - (index * 3), + -5, + 0.02), + #Point3(165, 0, 0), + other=self.trolleyStation + ), + # Tell the toon he is free to roam now + Func(self.notifyToonOffTrolley, toon), + Func(self.clearToonTrack, avId), + name = toon.uniqueName("emptyTrolley"), + autoPause = 1) + track.delayDelete = DelayDelete.DelayDelete(toon, 'Trolley.emptySlot') + self.storeToonTrack(avId, track) + track.start() + + # Tell localToon he is exiting (if localToon is on board) + if avId == base.localAvatar.getDoId(): + self.loader.place.trolley.fsm.request("exiting") + + else: + DistributedTrolley.notify.warning("toon: " + str(avId) + + " doesn't exist, and" + + " cannot exit the trolley!") + + def rejectBoard(self, avId): + # This should only be sent to us if our localToon requested + # permission to board the trolley. + assert(base.localAvatar.getDoId() == avId) + self.loader.place.trolley.handleRejectBoard() + + def setMinigameZone(self, zoneId, minigameId): + # This is how the server puts the clients into a minigame + self.localToonOnBoard = 0 + messenger.send("playMinigame", [zoneId, minigameId]) + + def __enableCollisions(self): + # start listening for toons to enter. + self.accept('entertrolley_sphere', self.handleEnterTrolleySphere) + self.accept('enterTrolleyOK', self.handleEnterTrolley) + self.trolleySphereNode.setCollideMask(ToontownGlobals.WallBitmask) + + def __disableCollisions(self): + # stop listening for toons. + self.ignore('entertrolley_sphere') + self.ignore('enterTrolleyOK') + self.trolleySphereNode.setCollideMask(BitMask32(0)) + + ##### Off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### Entering state ##### + + def enterEntering(self, ts): + # Lerp the trolley into place via a track + self.trolleyEnterTrack.start(ts) + + def exitEntering(self): + self.trolleyEnterTrack.finish() + + ##### WaitEmpty state ##### + + def enterWaitEmpty(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + + def exitWaitEmpty(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + + ##### WaitCountdown state ##### + + def enterWaitCountdown(self, ts): + # Toons may now try to board the trolley + self.__enableCollisions() + self.accept("trolleyExitButton", self.handleExitButton) + # Start the countdown clock... + self.clockNode = TextNode("trolleyClock") + self.clockNode.setFont(ToontownGlobals.getSignFont()) + self.clockNode.setAlign(TextNode.ACenter) + self.clockNode.setTextColor(0.9, 0.1, 0.1, 1) + self.clockNode.setText("10") + self.clock = self.trolleyStation.attachNewNode(self.clockNode) + self.clock.setBillboardAxis() + self.clock.setPosHprScale(15.86, 13.82, 11.68, + -0.00, 0.00, 0.00, + 3.02, 3.02, 3.02) + if ts < self.trolleyCountdownTime: + self.countdown(self.trolleyCountdownTime - ts) + return + + def timerTask(self, task): + countdownTime = int(task.duration - task.time) + timeStr = str(countdownTime) + + if self.clockNode.getText() != timeStr: + self.clockNode.setText(timeStr) + + if task.time >= task.duration: + return Task.done + else: + return Task.cont + + def countdown(self, duration): + countdownTask = Task(self.timerTask) + countdownTask.duration = duration + taskMgr.remove("trolleyTimerTask") + return taskMgr.add(countdownTask, "trolleyTimerTask") + + def handleExitButton(self): + # This gets called when the exit button gets pushed. + self.sendUpdate("requestExit") + + def exitWaitCountdown(self): + # Toons may not attempt to board the trolley if it isn't waiting + self.__disableCollisions() + self.ignore("trolleyExitButton") + # Stop the countdown clock... + taskMgr.remove("trolleyTimerTask") + self.clock.removeNode() + del self.clock + del self.clockNode + + ##### Leaving state ##### + + def enterLeaving(self, ts): + # Move the trolley into the tunnel via a track + self.trolleyExitTrack.start(ts) + if self.localToonOnBoard: + if hasattr(self.loader.place, 'trolley') and self.loader.place.trolley: + self.loader.place.trolley.fsm.request("trolleyLeaving") + + def exitLeaving(self): + self.trolleyExitTrack.finish() + + + ##### Miscellaneous support functions ##### + + def animateTrolley(self, t, keyAngle, wheelAngle): + # Make key rotate at 1 rotation per second + for i in range(self.numKeys): + key = self.keys[i] + ref = self.keyRef[i] + key.setH(ref, t * keyAngle) + for i in range(self.numFrontWheels): + frontWheel = self.frontWheels[i] + ref = self.frontWheelRef[i] + frontWheel.setH(ref, t * wheelAngle) + for i in range(self.numBackWheels): + backWheel = self.backWheels[i] + ref = self.backWheelRef[i] + backWheel.setH(ref, t * wheelAngle) + + def resetAnimation(self): + for i in range(self.numKeys): + self.keys[i].setTransform(self.keyInit[i]) + for i in range(self.numFrontWheels): + self.frontWheels[i].setTransform(self.frontWheelInit[i]) + for i in range(self.numBackWheels): + self.backWheels[i].setTransform(self.backWheelInit[i]) + + def getStareAtNodeAndOffset(self): + return self.trolleyCar, Point3(0,0,4) + + def storeToonTrack(self, avId, track): + # Clear out any currently playing tracks on this toon + self.clearToonTrack(avId) + # Store this new one + self.__toonTracks[avId] = track + + def clearToonTrack(self, avId): + # Clear out any currently playing tracks on this toon + oldTrack = self.__toonTracks.get(avId) + if oldTrack: + oldTrack.pause() + DelayDelete.cleanupDelayDeletes(oldTrack) + del self.__toonTracks[avId] + + def clearToonTracks(self): + #We can't use an iter because we are deleting keys + keyList = [] + for key in self.__toonTracks: + keyList.append(key) + + for key in keyList: + if self.__toonTracks.has_key(key): + self.clearToonTrack(key) + + + + diff --git a/toontown/src/safezone/DistributedTrolleyAI.py b/toontown/src/safezone/DistributedTrolleyAI.py new file mode 100644 index 0000000..5a81096 --- /dev/null +++ b/toontown/src/safezone/DistributedTrolleyAI.py @@ -0,0 +1,456 @@ +from otp.ai.AIBase import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * +from TrolleyConstants import * + +from direct.distributed import DistributedObjectAI +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +from direct.directnotify import DirectNotifyGlobal +from toontown.minigame import MinigameCreatorAI +from toontown.quest import Quests +from toontown.minigame import TrolleyHolidayMgrAI +from toontown.minigame import TrolleyWeekendMgrAI +from toontown.toonbase import ToontownAccessAI + +class DistributedTrolleyAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + "DistributedTrolleyAI") + + def __init__(self, air): + """__init__(air) + """ + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.seats = [None, None, None, None] + + # Flag that tells whether the trolley is currently accepting boarders + self.accepting = 0 + + self.trolleyCountdownTime = \ + simbase.config.GetFloat("trolley-countdown-time", + TROLLEY_COUNTDOWN_TIME) + + self.fsm = ClassicFSM.ClassicFSM('DistributedTrolleyAI', + [State.State('off', + self.enterOff, + self.exitOff, + ['entering']), + State.State('entering', + self.enterEntering, + self.exitEntering, + ['waitEmpty']), + State.State('waitEmpty', + self.enterWaitEmpty, + self.exitWaitEmpty, + ['waitCountdown']), + State.State('waitCountdown', + self.enterWaitCountdown, + self.exitWaitCountdown, + ['waitEmpty', 'allAboard']), + State.State('allAboard', + self.enterAllAboard, + self.exitAllAboard, + ['leaving', 'waitEmpty']), + State.State('leaving', + self.enterLeaving, + self.exitLeaving, + ['entering'])], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + + def delete(self): + self.fsm.requestFinalState() + del self.fsm + DistributedObjectAI.DistributedObjectAI.delete(self) + + + def findAvailableSeat(self): + for i in range(len(self.seats)): + if self.seats[i] == None: + return i + return None + + def findAvatar(self, avId): + for i in range(len(self.seats)): + if self.seats[i] == avId: + return i + return None + + def countFullSeats(self): + avCounter = 0 + for i in self.seats: + if i: + avCounter += 1 + return avCounter + + def rejectingBoardersHandler(self, avId): + self.rejectBoarder(avId) + + def rejectBoarder(self, avId): + self.sendUpdateToAvatarId(avId, "rejectBoard", [avId]) + + def acceptingBoardersHandler(self, avId): + self.notify.debug("acceptingBoardersHandler") + seatIndex = self.findAvailableSeat() + if seatIndex == None: + self.rejectBoarder(avId) + else: + self.acceptBoarder(avId, seatIndex) + + def acceptBoarder(self, avId, seatIndex): + self.notify.debug("acceptBoarder") + # Make sure we have a valid seat number + assert((seatIndex >= 0) and (seatIndex <=3)) + # Make sure the seat is empty + assert(self.seats[seatIndex] == None) + # Make sure this avatar isn't already seated + if (self.findAvatar(avId) != None): + return + # Put the avatar in that seat + self.seats[seatIndex] = avId + # Add a hook that handles the case where the avatar exits + # the district unexpectedly + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + # Record the time of boarding + self.timeOfBoarding = globalClock.getRealTime() + # Tell the clients to put the avatar in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [avId]) + # Put us into waitCountdown state... If we are already there, + # this won't do anything. + self.waitCountdown() + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # Make sure the avatar is really here + if seatIndex == None: + pass + else: + # If the avatar is here, his seat is now empty. + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.clearEmptyNow(seatIndex) + #self.sendUpdate("emptySlot" + str(seatIndex), + # [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + if self.countFullSeats() == 0: + self.waitEmpty() + + def rejectingExitersHandler(self, avId): + self.rejectExiter(avId) + + def rejectExiter(self, avId): + # This doesn't have to do anything. If your exit is rejected, + # you'll know because the trolley leaves. + pass + + def acceptingExitersHandler(self, avId): + self.acceptExiter(avId) + + def acceptExiter(self, avId): + # Find the exiter's seat index + seatIndex = self.findAvatar(avId) + # It is possible that the avatar exited the shard unexpectedly. + if seatIndex == None: + pass + else: + # Empty that seat + self.clearFullNow(seatIndex) + # Tell the clients that the avatar is leaving that seat + self.sendUpdate("emptySlot" + str(seatIndex), + [avId, globalClockDelta.getRealNetworkTime()]) + # If all the seats are empty, go back into waitEmpty state + if self.countFullSeats() == 0: + self.waitEmpty() + # Wait for the avatar to be done leaving the seat, and then + # declare the emptying overwith... + taskMgr.doMethodLater(TOON_EXIT_TIME, + self.clearEmptyNow, + self.uniqueName("clearEmpty-%s" % seatIndex), + extraArgs = (seatIndex,)) + + def clearEmptyNow(self, seatIndex): + self.sendUpdate("emptySlot" + str(seatIndex), + [0, globalClockDelta.getRealNetworkTime()]) + + def clearFullNow(self, seatIndex): + # Get the avatar id + avId = self.seats[seatIndex] + # If there is no one sitting there, that is kind of strange. + if avId == 0: + self.notify.warning("Clearing an empty seat index: " + + str(seatIndex) + " ... Strange...") + else: + # Empty that seat + self.seats[seatIndex] = None + # Tell the clients that the avatar is no longer in that seat + self.sendUpdate("fillSlot" + str(seatIndex), + [0]) + # If the avatar isn't in a seat, we don't care anymore, so + # remove the hook to handle unexpected exits. + self.ignore(simbase.air.getAvatarExitEvent(avId)) + + def d_setState(self, state): + self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()]) + + def getState(self): + return self.fsm.getCurrentState().getName() + + def requestBoard(self, *args): + self.notify.debug("requestBoard") + avId = self.air.getAvatarIdFromSender() + if (self.findAvatar(avId) != None): + self.notify.warning("Ignoring multiple requests from %s to board." % (avId)) + return + + av = self.air.doId2do.get(avId) + if av: + newArgs = (avId,) + args + + if not ToontownAccessAI.canAccess(avId, self.zoneId): + self.notify.warning("Tooon %s does not have access to the trolley." % (avId)) + self.rejectingBoardersHandler(*newArgs) + return + + # Only toons with hp greater than 0 may board the trolley. + if (av.hp > 0) and self.accepting: + self.acceptingBoardersHandler(*newArgs) + else: + self.rejectingBoardersHandler(*newArgs) + else: + self.notify.warning( + "avid: %s does not exist, but tried to board a trolley" % avId + ) + + def requestExit(self, *args): + self.notify.debug("requestExit") + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + if av: + newArgs = (avId,) + args + if self.accepting: + self.acceptingExitersHandler(*newArgs) + else: + self.rejectingExitersHandler(*newArgs) + else: + self.notify.warning( + "avId: %s does not exist, but tried to exit a trolley" % avId + ) + + ##### How you start up the trolley ##### + def start(self): + self.enter() + + ##### Off state ##### + + def enterOff(self): + self.accepting = 0 + # Maybe this task cleanup shouldn't be here, but I didn't know + # where else to put it, since emptying the seats isn't associated + # with any particular task. Perhaps I should have made a nested + # State machine of TrolleyOn, or some such, but it seemed like a lot + # of work for a few crummy tasks. + + # If we don't have a doId yet, we can't possibly have these + # tasks running. + if hasattr(self, "doId"): + for seatIndex in range(4): + taskMgr.remove(self.uniqueName("clearEmpty-" + + str(seatIndex))) + + def exitOff(self): + self.accepting = 0 + + ##### Entering state ##### + + def enter(self): + self.fsm.request('entering') + + def enterEntering(self): + self.d_setState('entering') + self.accepting = 0 + self.seats = [None, None, None, None] + taskMgr.doMethodLater(TROLLEY_ENTER_TIME, self.waitEmptyTask, + self.uniqueName('entering-timer')) + + def exitEntering(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('entering-timer')) + + ##### WaitEmpty state ##### + + def waitEmptyTask(self, task): + self.waitEmpty() + return Task.done + + def waitEmpty(self): + if hasattr(self,'fsm') and self.fsm: + self.fsm.request("waitEmpty") + else: + self.notify.warning("waitEmpty no fsm avoided AI crash TOON-1984") + + def enterWaitEmpty(self): + self.d_setState('waitEmpty') + self.accepting = 1 + + def exitWaitEmpty(self): + self.accepting = 0 + + ##### WaitCountdown state ##### + + def waitCountdown(self): + self.fsm.request("waitCountdown") + + def enterWaitCountdown(self): + self.d_setState('waitCountdown') + self.accepting = 1 + # Start the countdown... + taskMgr.doMethodLater(self.trolleyCountdownTime, self.timeToGoTask, + self.uniqueName('countdown-timer')) + + def timeToGoTask(self, task): + # It is possible that the players exited the district + if self.countFullSeats() > 0: + self.allAboard() + else: + self.waitEmpty() + return Task.done + + def exitWaitCountdown(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('countdown-timer')) + + ##### AllAboard state ##### + + def allAboard(self): + self.fsm.request("allAboard") + + def enterAllAboard(self): + self.accepting = 0 + currentTime = globalClock.getRealTime() + + elapsedTime = currentTime - self.timeOfBoarding + self.notify.debug("elapsed time: " + str(elapsedTime)) + waitTime = max(TOON_BOARD_TIME - elapsedTime, 0) + taskMgr.doMethodLater(waitTime, self.leaveTask, + self.uniqueName('waitForAllAboard')) + + def exitAllAboard(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('waitForAllAboard')) + + ##### Leaving state ##### + + def leaveTask(self, task): + # It is possible that the players exited the district + if self.countFullSeats() > 0: + self.leave() + else: + self.waitEmpty() + return Task.done + + def leave(self): + self.fsm.request("leaving") + + def enterLeaving(self): + self.d_setState('leaving') + self.accepting = 0 + taskMgr.doMethodLater(TROLLEY_EXIT_TIME, self.trolleyLeftTask, + self.uniqueName('leaving-timer')) + + def trolleyLeftTask(self, task): + self.trolleyLeft() + return Task.done + + def trolleyLeft(self): + numPlayers = self.countFullSeats() + + # It is possible the players exited the district + if (numPlayers > 0): + + # create a list of ids of players that have never ridden the + # trolley, before we inform the quest manager that they've + # ridden + newbieIds = [] + for avId in self.seats: + if avId: + toon = self.air.doId2do.get(avId) + if toon: + if Quests.avatarHasTrolleyQuest(toon): + if not Quests.avatarHasCompletedTrolleyQuest(toon): + newbieIds.append(avId) + + """ This was moved to NewbiePurchaseManagerAI. We want to make + sure that newbies go through the gag tutorial. Therefore we only + mark their quest as complete when they exit the tutorial purchase + screen through normal means. + + toonRodeTrolley() was only being used for the single first-time + trolley quest, so I renamed it to toonRodeTrolleyFirstTime() and + only call it from the newbie PurchaseMgr. + + # Update the quest manager in case any toon had a trolley quest + for avId in self.seats: + if avId: + toon = self.air.doId2do.get(avId) + self.air.questManager.toonRodeTrolley(toon) + """ + + # Make a nice list for the minigame + playerArray = [] + for i in self.seats: + if i not in [None, 0]: + playerArray.append(i) + # Create a minigame + + startingVotes = None + metagameRound = -1 + trolleyGoesToMetagame = simbase.config.GetBool('trolley-goes-to-metagame', 0) + trolleyHoliday = bboard.get( TrolleyHolidayMgrAI.TrolleyHolidayMgrAI.PostName) + trolleyWeekend = bboard.get( TrolleyWeekendMgrAI.TrolleyWeekendMgrAI.PostName) + if trolleyGoesToMetagame or trolleyHoliday or trolleyWeekend: + metagameRound = 0 + if simbase.config.GetBool('metagame-min-2-players', 1) and \ + len(playerArray) == 1: + # but if there's only 1, bring it back to a regular minigame + metagameRound = -1 + + mgDict = MinigameCreatorAI.createMinigame(self.air, + playerArray, + self.zoneId, + newbieIds=newbieIds, + startingVotes = startingVotes, + metagameRound = metagameRound) + minigameZone = mgDict["minigameZone"] + minigameId = mgDict["minigameId"] + + for seatIndex in range(len(self.seats)): + avId = self.seats[seatIndex] + # Tell each player on the trolley that they should enter the + # minigame now. + if avId: + assert(avId > 0) + self.sendUpdateToAvatarId(avId, "setMinigameZone", + [minigameZone, minigameId]) + # Clear the fill slot + self.clearFullNow(seatIndex) + else: + self.notify.warning("The trolley left, but was empty.") + + # Switch back into entering mode. + self.enter() + + def exitLeaving(self): + self.accepting = 0 + taskMgr.remove(self.uniqueName('leaving-timer')) diff --git a/toontown/src/safezone/EFlyingTreasurePlannerAI.py b/toontown/src/safezone/EFlyingTreasurePlannerAI.py new file mode 100644 index 0000000..2d32a1c --- /dev/null +++ b/toontown/src/safezone/EFlyingTreasurePlannerAI.py @@ -0,0 +1,52 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedEFlyingTreasureAI + +class EFlyingTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, estateAI, zoneId): + self.healAmount = 3 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedEFlyingTreasureAI.DistributedEFlyingTreasureAI, # Constructor + "EFlyingTreasurePlanner", + 20, # Frequency of spawn + 7 # Max number of treasures + ) + # keep reference to estateAI, so we can tell the estate + # when the treasures are updated (needed for cannon fire intersection calc) + self.estateAI = estateAI + + def initSpawnPoints(self): + self.spawnPoints = [ + (-14, -47.06, 25.177), + (-24, -43.17, 30.755), + (121, 9.9, 24.025), + (2.644, 64.941, 29.672), + (-0.3, -22.5, 32.025), + (47.34, 9.3, 35.025), + (26.6, -2.49, 27.485), + (55.9, -13.1, 33.156), + (-57.9, 13.38, 28.386), + (-85.1, 48.9, 30.025), + (-2.702, -96.892, 28.765), + (65, 2.7, 30.025), + (-4.4, 42.945, 29.284), + (-8.495, -62.438, 31.88), + (-74.27, -83.832, 28.05), + (-80.99, 5.753, 28.05), + (80.906, 53.857, 28.05), + (97.955, 0.618, 29.05), + (47.26, -62.193, 30.05), + (-63.865, 24.788, 30.025), + (-28.826, 25.024, 28.559), + ] + return self.spawnPoints + + def placeRandomTreasure(self): + RegenTreasurePlannerAI.RegenTreasurePlannerAI.placeRandomTreasure(self) + + # Update the estate's list of treasures + self.estateAI.updateFlyingTreasureList() + + diff --git a/toontown/src/safezone/ETreasurePlannerAI.py b/toontown/src/safezone/ETreasurePlannerAI.py new file mode 100644 index 0000000..5f551de --- /dev/null +++ b/toontown/src/safezone/ETreasurePlannerAI.py @@ -0,0 +1,44 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedETreasureAI + +class ETreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 2 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedETreasureAI.DistributedETreasureAI, # Constructor + "ETreasurePlanner", + 50, # Frequency of spawn + 3 # Max number of treasures + ) + + def initSpawnPoints(self): + self.spawnPoints = [ + (-125, -20.06, 1.177), + (-24, -43.17, 5.755), + (121, 9.9, 0.025), + (2.644, 64.941, 4.672), + (-0.3, -22.5, 7.025), + (47.34, 9.3, 8.025), + (26.6, -2.49, 11.485), + (55.9, -13.1, 8.156), + (-57.9, 13.38, 2.386), + (-85.1, 48.9, 0.025), + (-2.702, -96.892, 3.765), + # (53, -133.7, 0.025), # Too close to fishing dock + (0.409, -102.945, -1.284), + (-8.495, -62.438, 1.88), + (-74.27, -83.832, 0.05), + (-80.99, 5.753, 0.05), + (80.906, 53.857, 0.05), + (97.955, 0.618, 0.05), + (47.26, -62.193, 0.05), + (-71.865, -132.788, 0.025), + (-28.826, 25.024, 2.559), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/GSPlayground.py b/toontown/src/safezone/GSPlayground.py new file mode 100644 index 0000000..721212f --- /dev/null +++ b/toontown/src/safezone/GSPlayground.py @@ -0,0 +1,145 @@ +from pandac.PandaModules import * + +from toontown.toonbase import ToontownGlobals +import Playground +from toontown.launcher import DownloadForceAcknowledge +from toontown.building import Elevator +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from toontown.racing import RaceGlobals +from direct.fsm import State + +class GSPlayground(Playground.Playground): + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + self.parentFSM=parentFSM + self.startingBlockDoneEvent = "startingBlockDone" + + self.fsm.addState( State.State('startingBlock', + self.enterStartingBlock, + self.exitStartingBlock, + ['walk'])) + state = self.fsm.getStateNamed('walk') + state.addTransition('startingBlock') + + def load(self): + Playground.Playground.load(self) + + def unload(self): + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + blimp=base.cr.playGame.hood.loader.geom.find("**/GS_blimp") + blimp.setPos(-70,250,-70) + blimpBase=NodePath("blimpBase") + blimpBase.setPos(0,-200,25) + blimpBase.setH(-40) + blimp.reparentTo(blimpBase) + blimpRoot=NodePath("blimpRoot") + blimpRoot.setPos(0,-70,40) + blimpRoot.reparentTo(base.cr.playGame.hood.loader.geom) + blimpBase.reparentTo(blimpRoot) + self.rotateBlimp=blimpRoot.hprInterval(360,Vec3(360,0,0)) + self.rotateBlimp.loop() + def exit(self): + Playground.Playground.exit(self) + self.rotateBlimp.finish() + + def doRequestLeave(self, requestStatus): + # when it's time to leave, check their trialer status first + self.fsm.request('trialerFA', [requestStatus]) + + def enterDFA(self, requestStatus): + """ + Override the base class because here we specifically ask for + phase 5, the toontown central streets. + - NEW: we can now go home. Check the hood before assuming we + are going to the streets + """ + doneEvent = "dfaDoneEvent" + self.accept(doneEvent, self.enterDFACallback, [requestStatus]) + self.dfa = DownloadForceAcknowledge.DownloadForceAcknowledge(doneEvent) + if requestStatus["hoodId"] == ToontownGlobals.MyEstate: + # Ask if we can enter phase 5.5 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.MyEstate)) + else: + # Ask if we can enter phase 5 + self.dfa.enter(5) + + + # teleportIn + def enterTeleportIn(self, requestStatus): + reason = requestStatus.get("reason") + if reason == RaceGlobals.Exit_Barrier: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeout, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_Slow: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RacerTooSlow, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_BarrierNoRefund: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeoutNoRefund, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + + Playground.Playground.enterTeleportIn(self, requestStatus) + + def __cleanupDialog(self, value): + if (self.dialog): + self.dialog.cleanup() + self.dialog = None + if hasattr(self, "fsm"): + self.fsm.request('walk', [1]) + + # startingblock state + def enterStartingBlock(self, distStartingBlock): + assert(self.notify.debug("enterStartingBlock()")) + import pdb; pdb.set_trace() + self.accept(self.startingBlockDoneEvent, self.handleStartingBlockDone) + self.startingBlock = Elevator.Elevator(self.fsm.getStateNamed("startingBlock"), + self.startingBlockDoneEvent, + distStartingBlock) + distStartingBlock.elevatorFSM=self.startingBlock + self.startingBlock.load() + self.startingBlock.enter() + + def exitStartingBlock(self): + assert(self.notify.debug("exitStartingBlock()")) + self.ignore(self.startingBlockDoneEvent) + self.startingBlock.unload() + self.startingBlock.exit() + del self.startingBlock + + def detectedStartingBlockCollision(self, distStartingBlock): + assert(self.notify.debug("detectedStartingBlockCollision()")) + import pdb; pdb.set_trace() + self.fsm.request("startingBlock", [distStartingBlock]) + + def handleStartingBlockDone(self, doneStatus): + assert(self.notify.debug("handleStartingBlockDone()")) + self.notify.debug("handling StartingBlock done event") + where = doneStatus['where'] + if (where == 'reject'): + self.fsm.request("walk") + elif (where == 'exit'): + self.fsm.request("walk") + elif (where == 'racetrack'): + print "Entering Racetrack" + self.doneStatus = doneStatus + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + where + + " in handleStartingBlockDone") + + diff --git a/toontown/src/safezone/GSSafeZoneLoader.py b/toontown/src/safezone/GSSafeZoneLoader.py new file mode 100644 index 0000000..2b27052 --- /dev/null +++ b/toontown/src/safezone/GSSafeZoneLoader.py @@ -0,0 +1,193 @@ +########################################################################## +# Module: GSSafeZoneLoader.py +# Purpose: This module oversees the construction of the Goofy Speedway +# safe zone loader object. +# +# Date: 6/10/05 +# Author: jjtaylor (jjtaylor@schellgames.com) +# Note: Modification of original GSLoader.py +########################################################################## + +########################################################################## +# Panda/Direct Import Modules +########################################################################## +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from pandac.PandaModules import * + +########################################################################## +# Toontwon Import Modules +########################################################################## +from toontown.hood import ZoneUtil +from toontown.launcher import DownloadForceAcknowledge +from toontown.safezone.SafeZoneLoader import SafeZoneLoader +from toontown.safezone.GSPlayground import GSPlayground +from toontown.effects.CarSmoke import CarSmoke +from toontown.toonbase import ToontownGlobals + + +########################################################################## +# Python Import Modules +########################################################################## +import random +if( __debug__ ): + import pdb + +class GSSafeZoneLoader( SafeZoneLoader ): + """ + Purpose: The GSSafeZoneLoader Class provides.. yadda yadda + """ + + def __init__( self, hood, parentFSM, doneEvent ): + """ + """ + + # Initialize Super Class + SafeZoneLoader.__init__( self, hood, parentFSM, doneEvent ) + + # Initialize Instance Variables + self.musicFile = "phase_6/audio/bgm/GS_SZ.mid" + self.activityMusicFile = "phase_6/audio/bgm/GS_KartShop.mid" + self.dnaFile = "phase_6/dna/goofy_speedway_sz.dna" + self.safeZoneStorageDNAFile = "phase_6/dna/storage_GS_sz.dna" + + # Override Super Class FSM + del self.fsm + self.fsm = ClassicFSM.ClassicFSM('SafeZoneLoader', + [State.State('start', + self.enterStart, + self.exitStart, + ['quietZone', + 'playground', + 'toonInterior',]), + State.State('playground', + self.enterPlayground, + self.exitPlayground, + ['quietZone', 'racetrack' ]), + State.State('toonInterior', + self.enterToonInterior, + self.exitToonInterior, + ['quietZone']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['playground', 'toonInterior', 'racetrack' ]), + State.State('racetrack', + self.enterRacetrack, + self.exitRacetrack, + ['quietZone', 'playground']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', ) + self.smoke = None + + def load( self ): + """ + """ + + + SafeZoneLoader.load( self ) + + if base.cr.newsManager: + holidayIds = base.cr.newsManager.getDecorationHolidayId() + if ToontownGlobals.CRASHED_LEADERBOARD in holidayIds: + self.startSmokeEffect() + + self.birdSound = map( base.loadSfx, [ 'phase_4/audio/sfx/SZ_TC_bird1.mp3', + 'phase_4/audio/sfx/SZ_TC_bird2.mp3', + 'phase_4/audio/sfx/SZ_TC_bird3.mp3' ] ) + + def unload( self ): + """ + """ + del self.birdSound + + if self.smoke != None: + self.stopSmokeEffect() + + SafeZoneLoader.unload( self ) + + def enterPlayground( self, requestStatus ): + """ + """ + self.playgroundClass = GSPlayground + SafeZoneLoader.enterPlayground( self, requestStatus ) + + # self.hood.spawnTitleText( requestStatus[ 'zoneId' ] ) + + def exitPlayground( self ): + """ + """ + + taskMgr.remove( 'titleText' ) + self.hood.hideTitleText() + SafeZoneLoader.exitPlayground( self ) + self.playgroundClass = None + + def handlePlaygroundDone( self ): + assert( self.notify.debug( "handlePlaygroundDone()" ) ) + status = self.place.doneStatus + if( self.enteringARace( status ) and status.get( 'shardId' ) == None ): + # GIVE THIS A WHIRL + zoneId = status[ 'zoneId' ] + self.fsm.request( 'quietZone', [ status ] ) + elif (ZoneUtil.getBranchZone(status["zoneId"]) == self.hood.hoodId and + # Going to Kart Shop + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send( self.doneEvent ) + + def enteringARace( self, status ): + if( not status[ 'where' ] == 'racetrack' ): + return 0 + if( ZoneUtil.isDynamicZone( status[ 'zoneId' ] ) ): + return status[ 'hoodId' ] == self.hood.hoodId + else: + return ZoneUtil.getHoodId( status[ 'zoneId' ] ) == self.hood.hoodId + + def enterRacetrack( self, requestStatus ): + """ + """ + + # Racetrack will grab this off of us + self.trackId = requestStatus[ 'trackId' ] + self.accept("raceOver",self.handleRaceOver) + self.accept("leavingRace",self.handleLeftRace) + + base.transitions.fadeOut(t=0) + + def exitRacetrack( self ): + """ + """ + del self.trackId + + def handleRaceOver(self): + print "you done!!" + + def handleLeftRace(self): + req={"loader":"safeZoneLoader","where":"playground","how":"teleportIn" + ,"zoneId":8000,"hoodId":8000,"shardId":None} + self.fsm.request("quietZone",[req]) + + def startSmokeEffect(self): + if base.config.GetBool('want-crashedLeaderBoard-Smoke', 1): + leaderBoard = self.geom.find("**/*crashed*") + locator = leaderBoard.find("**/*locator_smoke*") + if locator != None: + self.smoke = CarSmoke(locator) + self.smoke.start() + + def stopSmokeEffect(self): + if base.config.GetBool('want-crashedLeaderBoard-Smoke', 1): + if self.smoke != None: + self.smoke.stop() + self.smoke.destroy() + self.smoke = None diff --git a/toontown/src/safezone/GZPlayground.py b/toontown/src/safezone/GZPlayground.py new file mode 100644 index 0000000..9935de9 --- /dev/null +++ b/toontown/src/safezone/GZPlayground.py @@ -0,0 +1,204 @@ +from pandac.PandaModules import * + +from toontown.toonbase import ToontownGlobals +import Playground +from toontown.launcher import DownloadForceAcknowledge +from toontown.building import Elevator +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from toontown.racing import RaceGlobals +from direct.fsm import State +#from toontown.trolley import Trolley +from toontown.safezone import GolfKart + +class GZPlayground(Playground.Playground): + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + self.parentFSM=parentFSM + self.golfKartBlockDoneEvent = "golfKartBlockDone" + + self.fsm.addState( State.State('golfKartBlock', + self.enterGolfKartBlock, + self.exitGolfKartBlock, + ['walk'])) + state = self.fsm.getStateNamed('walk') + state.addTransition('golfKartBlock') + + self.golfKartDoneEvent = "golfKartDone" + + def load(self): + Playground.Playground.load(self) + self.hub = loader.loadModel("phase_6/models/golf/golf_hub2") + self.hub.reparentTo(render) + self.dnaroot = render.find('**/goofy_speedway_DNARoot') + self.dnaroot = base.cr.playGame.hood.loader.geom.find('**/goofy_speedway_DNARoot') + if not self.dnaroot.isEmpty(): + self.dnaroot.removeNode() + def unload(self): + Playground.Playground.unload(self) + self.hub.removeNode() + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + blimp=base.cr.playGame.hood.loader.geom.find("**/GS_blimp") + if blimp.isEmpty(): + return + blimp.setPos(-70,250,-70) + blimpBase=NodePath("blimpBase") + blimpBase.setPos(0,-200,25) + blimpBase.setH(-40) + blimp.reparentTo(blimpBase) + blimpRoot=NodePath("blimpRoot") + blimpRoot.setPos(0,-70,40) + blimpRoot.reparentTo(base.cr.playGame.hood.loader.geom) + blimpBase.reparentTo(blimpRoot) + self.rotateBlimp=blimpRoot.hprInterval(360,Vec3(360,0,0)) + self.rotateBlimp.loop() + def exit(self): + Playground.Playground.exit(self) + if hasattr(self, 'rotateBlimp'): + self.rotateBlimp.finish() + + def doRequestLeave(self, requestStatus): + # when it's time to leave, check their trialer status first + self.fsm.request('trialerFA', [requestStatus]) + + def enterDFA(self, requestStatus): + """ + Override the base class because here we specifically ask for + phase 5, the toontown central streets. + - NEW: we can now go home. Check the hood before assuming we + are going to the streets + """ + doneEvent = "dfaDoneEvent" + self.accept(doneEvent, self.enterDFACallback, [requestStatus]) + self.dfa = DownloadForceAcknowledge.DownloadForceAcknowledge(doneEvent) + if requestStatus["hoodId"] == ToontownGlobals.MyEstate: + # Ask if we can enter phase 5.5 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.MyEstate)) + else: + # Ask if we can enter phase 5 + self.dfa.enter(5) + + + # teleportIn + def enterTeleportIn(self, requestStatus): + reason = requestStatus.get("reason") + if reason == RaceGlobals.Exit_Barrier: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeout, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_Slow: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RacerTooSlow, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_BarrierNoRefund: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeoutNoRefund, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + + Playground.Playground.enterTeleportIn(self, requestStatus) + + def __cleanupDialog(self, value): + if (self.dialog): + self.dialog.cleanup() + self.dialog = None + if hasattr(self, "fsm"): + self.fsm.request('walk', [1]) + """ + # startingblock state + def enterStartingBlock(self, distStartingBlock): + assert(self.notify.debug("enterStartingBlock()")) + self.accept(self.golfKartBlockDoneEvent, self.handleStartingBlockDone) + self.golfKartBlock = Elevator.Elevator(self.fsm.getStateNamed("golfKartBlock"), + self.golfKartBlockDoneEvent, + distStartingBlock) + distStartingBlock.elevatorFSM=self.golfKartBlock + self.golfKartBlock.load() + self.golfKartBlock.enter() + + def exitStartingBlock(self): + assert(self.notify.debug("exitStartingBlock()")) + self.ignore(self.golfKartBlockDoneEvent) + self.golfKartBlock.unload() + self.golfKartBlock.exit() + del self.golfKartBlock + """ + def enterGolfKartBlock(self, golfKart): + assert(self.notify.debug("enterGolfKartBlock()")) + # Turn on the laff meter + base.localAvatar.laffMeter.start() + + # clear the anim state + base.localAvatar.b_setAnimState("off", 1) + + self.accept(self.golfKartDoneEvent, self.handleGolfKartDone) + #self.trolley = GolfKart.GolfKart(self, self.fsm, self.golfKartDoneEvent, golfKart.golfCourse) + self.trolley = GolfKart.GolfKart(self, self.fsm, self.golfKartDoneEvent, golfKart.getDoId()) + self.trolley.load() + self.trolley.enter() + + def exitGolfKartBlock(self): + assert(self.notify.debug("exitGolfKartBlock()")) + + # Turn off the laff meter + base.localAvatar.laffMeter.stop() + + self.ignore(self.trolleyDoneEvent) + self.trolley.unload() + self.trolley.exit() + del self.trolley + + + + def detectedGolfKartCollision(self, golfKart): + assert(self.notify.debug("detectedGolfkartCollision()")) + self.notify.debug("detectedGolfkartCollision()") + self.fsm.request("golfKartBlock", [golfKart]) + + def handleStartingBlockDone(self, doneStatus): + assert(self.notify.debug("handleStartingBlockDone()")) + self.notify.debug("handling StartingBlock done event") + where = doneStatus['where'] + if (where == 'reject'): + self.fsm.request("walk") + elif (where == 'exit'): + self.fsm.request("walk") + elif (where == 'racetrack'): + print "Entering Racetrack" + self.doneStatus = doneStatus + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + where + + " in handleStartingBlockDone") + + + def handleGolfKartDone(self, doneStatus): + assert(self.notify.debug("handleGolfKartDone()")) + self.notify.debug("handling golf kart done event") + mode = doneStatus["mode"] + if mode == "reject": + self.fsm.request("walk") + elif mode == "exit": + self.fsm.request("walk") + elif mode == "golfcourse": + self.doneStatus = {"loader" : "golfcourse", + "where" : "golfcourse", + "hoodId" : self.loader.hood.id, + "zoneId" : doneStatus["zoneId"], + "shardId" : None, + "courseId" : doneStatus["courseId"] + } + #self.doneStatus = doneStatus + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + mode + " in handleGolfKartDone") diff --git a/toontown/src/safezone/GZSafeZoneLoader.py b/toontown/src/safezone/GZSafeZoneLoader.py new file mode 100644 index 0000000..00d71eb --- /dev/null +++ b/toontown/src/safezone/GZSafeZoneLoader.py @@ -0,0 +1,194 @@ +########################################################################## +# Module: GSSafeZoneLoader.py +# Purpose: This module oversees the construction of the Goofy Speedway +# safe zone loader object. +# +# Date: 6/10/05 +# Author: jjtaylor (jjtaylor@schellgames.com) +# Note: Modification of original GSLoader.py +########################################################################## + +########################################################################## +# Panda/Direct Import Modules +########################################################################## +from direct.directnotify import DirectNotifyGlobal +from direct.gui import DirectGui +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from pandac.PandaModules import * + +########################################################################## +# Toontwon Import Modules +########################################################################## +from toontown.hood import ZoneUtil +from toontown.launcher import DownloadForceAcknowledge +from toontown.safezone.SafeZoneLoader import SafeZoneLoader +from toontown.safezone.GZPlayground import GZPlayground +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals + +########################################################################## +# Python Import Modules +########################################################################## +import random +if( __debug__ ): + import pdb + +class GZSafeZoneLoader( SafeZoneLoader ): + """ + Purpose: The GZSafeZoneLoader Class provides.. yadda yadda + """ + + def __init__( self, hood, parentFSM, doneEvent ): + """ + """ + # Initialize Super Class + SafeZoneLoader.__init__( self, hood, parentFSM, doneEvent ) + + # Initialize Instance Variables + self.musicFile = "phase_6/audio/bgm/GZ_SZ.mid" + self.activityMusicFile = "phase_6/audio/bgm/GS_KartShop.mid" + self.dnaFile = "phase_6/dna/golf_zone_sz.dna" + self.safeZoneStorageDNAFile = "phase_6/dna/storage_GZ_sz.dna" + + # Override Super Class FSM + del self.fsm + self.fsm = ClassicFSM.ClassicFSM('SafeZoneLoader', + [State.State('start', + self.enterStart, + self.exitStart, + ['quietZone', + 'playground', + 'toonInterior',]), + State.State('playground', + self.enterPlayground, + self.exitPlayground, + ['quietZone', 'golfcourse' ]), + State.State('toonInterior', + self.enterToonInterior, + self.exitToonInterior, + ['quietZone']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['playground', 'toonInterior', 'golfcourse' ]), + State.State('golfcourse', + self.enterGolfCourse, + self.exitGolfCourse, + ['quietZone', 'playground']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', ) + + def load( self ): + """ + """ + + + SafeZoneLoader.load( self ) + self.birdSound = map( base.loadSfx, [ 'phase_4/audio/sfx/SZ_TC_bird1.mp3', + 'phase_4/audio/sfx/SZ_TC_bird2.mp3', + 'phase_4/audio/sfx/SZ_TC_bird3.mp3' ] ) + + def unload( self ): + """ + """ + del self.birdSound + SafeZoneLoader.unload( self ) + + def enterPlayground( self, requestStatus ): + """ + """ + self.playgroundClass = GZPlayground + SafeZoneLoader.enterPlayground( self, requestStatus ) + + # add the bbhq sign + top = self.geom.find("**/linktunnel_bosshq_10000_DNARoot") + sign = top.find("**/Sign_5") + sign.node().setEffect(DecalEffect.make()) + locator = top.find("**/sign_origin") + signText = DirectGui.OnscreenText( + text = TextEncoder.upper(TTLocalizer.BossbotHQ[-1]), + font = ToontownGlobals.getSuitFont(), + scale = TTLocalizer.GSZLbossbotSignScale, + fg = (0, 0, 0, 1), + # required for DecalEffect (must be a GeomNode, not a TextNode) + mayChange=False, + parent = sign) + signText.setPosHpr(locator, 0, 0, -0.3, 0, 0, 0) + signText.setDepthWrite(0) + + # self.hood.spawnTitleText( requestStatus[ 'zoneId' ] ) + + def exitPlayground( self ): + """ + """ + + taskMgr.remove( 'titleText' ) + self.hood.hideTitleText() + SafeZoneLoader.exitPlayground( self ) + self.playgroundClass = None + + def handlePlaygroundDone( self ): + assert( self.notify.debug( "handlePlaygroundDone()" ) ) + status = self.place.doneStatus + if( self.enteringAGolfCourse( status ) and status.get( 'shardId' ) == None ): + # GIVE THIS A WHIRL + zoneId = status[ 'zoneId' ] + self.fsm.request( 'quietZone', [ status ] ) + elif (ZoneUtil.getBranchZone(status["zoneId"]) == self.hood.hoodId and + # Going to Kart Shop + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send( self.doneEvent ) + + def enteringARace( self, status ): + if( not status[ 'where' ] == 'golfcourse' ): + return 0 + if( ZoneUtil.isDynamicZone( status[ 'zoneId' ] ) ): + return status[ 'hoodId' ] == self.hood.hoodId + else: + return ZoneUtil.getHoodId( status[ 'zoneId' ] ) == self.hood.hoodId + + def enteringAGolfCourse( self, status ): + if( not status[ 'where' ] == 'golfcourse' ): + return 0 + if( ZoneUtil.isDynamicZone( status[ 'zoneId' ] ) ): + return status[ 'hoodId' ] == self.hood.hoodId + else: + return ZoneUtil.getHoodId( status[ 'zoneId' ] ) == self.hood.hoodId + + def enterGolfCourse( self, requestStatus ): + """ + """ + + # GolfCourse will grab this off of us + if requestStatus.has_key('curseId'): + self.golfCourseId = requestStatus[ 'courseId' ] + else: + self.golfCourseId = 0 + + self.accept("raceOver",self.handleRaceOver) + self.accept("leavingGolf", self.handleLeftGolf) + + base.transitions.irisOut(t=0.2) + + def exitGolfCourse( self ): + """ + """ + del self.golfCourseId + + def handleRaceOver(self): + print "you done!!" + + def handleLeftGolf(self): + req={"loader":"safeZoneLoader","where":"playground","how":"teleportIn" + ,"zoneId":17000,"hoodId":17000,"shardId":None} + self.fsm.request("quietZone",[req]) diff --git a/toontown/src/safezone/GameMenu.py b/toontown/src/safezone/GameMenu.py new file mode 100644 index 0000000..873ba28 --- /dev/null +++ b/toontown/src/safezone/GameMenu.py @@ -0,0 +1,135 @@ +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from direct.task.Task import Task +from direct.interval.IntervalGlobal import * +from TrolleyConstants import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals + + +class GameMenu(DirectFrame): + def __init__(self, picnicFunction, menuType): + + self.picnicFunction = picnicFunction + DirectFrame.__init__(self, + pos = (0.0, 0.0, 0.85), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.8, 0.9, 0.13), + text = "", + text_scale = 0.05, + ) + self['image'] = DGG.getDefaultDialogGeom() + + if menuType == 1: #menu selected is picking a tutorial + self.title = DirectLabel(self, + relief = None, + text = TTLocalizer.PicnicTableMenuTutorial, + text_pos = (0.0, -0.038), + text_fg = (1, 0, 0, 1), + text_scale = 0.09, + text_font = ToontownGlobals.getSignFont(), + text_shadow = (1, 1, 1, 1), + ) + elif menuType == 2: #menu selected is picking a game + self.title = DirectLabel(self, + relief = None, + text = TTLocalizer.PicnicTableMenuSelect, + text_pos = (0.0, -0.04), + text_fg = (1, 0, 0, 1), + text_scale = 0.09, + text_font = ToontownGlobals.getSignFont(), + #text_shadow = (1, 1, 1, 1), + ) + self.selectionButtons = loader.loadModel("phase_6/models/golf/picnic_game_menu.bam") + btn1 = self.selectionButtons.find("**/Btn1") + btn2 = self.selectionButtons.find("**/Btn2") + btn3 = self.selectionButtons.find("**/Btn3") + + self.ChineseCheckers = DirectButton(self, + image = (btn1.find("**/checkersBtnUp"), + btn1.find("**/checkersBtnDn"), + btn1.find("**/checkersBtnHi"), + btn1.find("**/checkersBtnUp")), + scale = .36, + #scale = .45, + relief = 0, + pos = (0, 0, -0.7), + command = self.checkersSelected, + ) + + self.Checkers = DirectButton(self, + image = (btn2.find("**/regular_checkersBtnUp"), + btn2.find("**/regular_checkersBtnDn"), + btn2.find("**/regular_checkersBtnHi"), + btn2.find("**/regular_checkersBtnUp")), + scale = .36, + relief = 0, + pos = (.8, 0, -0.7), + command = self.regCheckersSelected, + ) + self.FindFour = DirectButton(self, + image = (btn3.find("**/findfourBtnUp"), + btn3.find("**/findfourBtnDn"), + btn3.find("**/findfourBtnHi"), + btn3.find("**/findfourBtnUp")), + scale = .36, + relief = 0, + pos = (-0.8, 0, -0.7), + #color = (.7,.7,.7,.7), + command = self.findFourSelected, + ) + if not base.config.GetBool('want-chinese', 0): + self.ChineseCheckers['command'] = self.doNothing + self.ChineseCheckers.setColor(.7,.7,.7,.7) + if not base.config.GetBool('want-checkers', 0): + self.Checkers['command'] = self.doNothing + self.Checkers.setColor(.7,.7,.7,.7) + if not base.config.GetBool('want-findfour', 0): + self.FindFour['command'] = self.doNothing + self.FindFour.setColor(.7,.7,.7,.7) + + + self.chineseText = OnscreenText(text = "Chinese Checkers", pos = (0, .56, -0.8), scale = 0.15, + fg = Vec4(1,1,1,1), align = TextNode.ACenter, + font =ToontownGlobals.getMinnieFont(), wordwrap = 7, + shadow = (0,0,0,0.8), shadowOffset = (-0.1,-0.1), mayChange = True) + self.chineseText.setR(-8) + + self.checkersText = OnscreenText(text = "Checkers", pos = (0.81, -.1, -0.8), scale = 0.15, + fg = Vec4(1,1,1,1), align = TextNode.ACenter, + font =ToontownGlobals.getMinnieFont(), wordwrap = 7, + shadow = (0,0,0,0.8), shadowOffset = (0.1,-0.1), mayChange = True) + self.findFourText = OnscreenText(text = "Find Four", pos = (-0.81, -.08, -0.8), scale = 0.15, + fg = Vec4(1,1,1,1), align = TextNode.ACenter, + font =ToontownGlobals.getMinnieFont(), wordwrap = 8, + shadow = (0,0,0,.8), shadowOffset = (-0.1,-0.1), mayChange = True) + self.findFourText.setR(-8) + self.checkersText.setR(8) + + def delete(self): + self.removeButtons() + + def removeButtons(self): + self.ChineseCheckers.destroy() + self.Checkers.destroy() + self.FindFour.destroy() + self.chineseText.destroy() + self.checkersText.destroy() + self.findFourText.destroy() + DirectFrame.destroy(self) + + def checkersSelected(self): + #self.removeButtons() + self.picnicFunction(1) + self.picnicFunction = None + def regCheckersSelected(self): + #self.removeButtons() + self.picnicFunction(2) + self.picnicFunction = None + def findFourSelected(self): + #self.removeButtons() + self.picnicFunction(3) + self.picnicFunction = None + def doNothing(self): + pass diff --git a/toontown/src/safezone/GameTutorials.py b/toontown/src/safezone/GameTutorials.py new file mode 100644 index 0000000..a410c2c --- /dev/null +++ b/toontown/src/safezone/GameTutorials.py @@ -0,0 +1,410 @@ +from direct.gui.DirectGui import * +from direct.fsm import FSM +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * + +class ChineseTutorial(DirectFrame, FSM.FSM): + #notify = DirectNotifyGlobal.directNotify.newCategory("GardenTutorial") + + def __init__(self, doneFunction, doneEvent=None, callback=None): + #print "GETTING HERE!!!" + FSM.FSM.__init__(self, "ChineseTutorial") + self.doneFunction = doneFunction + base.localAvatar.startSleepWatch(self.handleQuit) + + self.doneEvent = doneEvent + self.callback = callback + self.setStateArray(["Page1", "Page2","Quit"]) + base.localAvatar.startSleepWatch(self.handleQuit) + # initialize our base class. + DirectFrame.__init__(self, + pos = (-0.7, 0.0, 0.0), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.5, 1.0), + text = "", + text_scale = 0.06, + ) + self.accept('stoppedAsleep', self.handleQuit) + + # For some reason, we need to set this after construction to + # get it to work properly. + self['image'] = DGG.getDefaultDialogGeom() + + #title this sucker + self.title = DirectLabel(self, + relief = None, + text = "", + text_pos = (0.0, 0.40), + text_fg = (1, 0, 0, 1), + text_scale = 0.13, + text_font = ToontownGlobals.getSignFont(), + #text_shadow = (1, 1, 1, 1), + ) + + #create some image frames + images = loader.loadModel("phase_6/models/golf/checker_tutorial.bam") + #images.reparentTo(aspect2d) + images.setTransparency(1) + + self.iPage1 = images.find('**/tutorialPage1*') + self.iPage1.reparentTo(aspect2d) + self.iPage1.setPos(0.43, -0.1, 0.0) + self.iPage1.setScale(13.95) + self.iPage1.setTransparency(1) + self.iPage1.hide() + self.iPage1.getChildren()[1].hide() + + + + self.iPage2 = images.find('**/tutorialPage3*') + self.iPage2.reparentTo(aspect2d) + self.iPage2.setPos(0.43, -0.1, 0.5) + self.iPage2.setScale(13.95) + self.iPage2.setTransparency(1) + self.iPage2.hide() + + + self.iPage3 = images.find('**/tutorialPage2*') + self.iPage3.reparentTo(aspect2d) + self.iPage3.setPos(0.43, -0.1, -0.5) + self.iPage3.setScale(13.95) + self.iPage3.setTransparency(1) + self.iPage3.hide() + + + + + # Create some buttons. + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + + self.bNext = DirectButton(self, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP")), + image3_color = Vec4(1, 1, 1, 0.5), + relief = None, + text = TTLocalizer.ChineseTutorialNext, + text3_fg = Vec4(0, 0, 0, 0.5), + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.35, -0.3, -0.33), + command = self.requestNext, + ) + + self.bPrev = DirectButton(self, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP")), + image3_color = Vec4(1, 1, 1, 0.5), + image_scale = (-1.0, 1.0, 1.0), # make the arrow point left + relief = None, + text = TTLocalizer.ChineseTutorialPrev, + text3_fg = Vec4(0, 0, 0, 0.5), + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (-0.35, -0.3, -0.33), + command = self.requestPrev, + ) + + self.bQuit = DirectButton(self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.ChineseTutorialDone, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.0, -0.3, -0.33), + command = self.handleQuit, + ) + self.bQuit.hide() + + buttons.removeNode() + gui.removeNode() + + self.request("Page1") + + def __del__(self): + self.cleanup() + + def enterPage1(self, *args): + self.bNext.show() + self.title['text'] = TTLocalizer.ChineseTutorialTitle1, + self['text'] = TTLocalizer.ChinesePage1 + self['text_pos'] = (0.0, 0.23) + self['text_wordwrap'] = 13.5 + self.bPrev['state'] = DGG.DISABLED + self.bPrev.hide() + self.bNext['state'] = DGG.NORMAL + self.iPage1.show() + + self.blinker = Sequence() + obj = self.iPage1.getChildren()[1] + self.iPage1.getChildren()[1].show() + self.blinker.append(LerpColorInterval(obj, .5, Vec4(0.5,0.5,0,0.0), Vec4(.2,.2,.2, 1))) + self.blinker.append(LerpColorInterval(obj, .5, Vec4(.2,.2,.2, 1) , Vec4(0.5,0.5,0,0.0) )) + self.blinker.loop() + + + def exitPage1(self, *args): + self.bPrev['state'] = DGG.NORMAL + self.iPage1.hide() + self.iPage1.getChildren()[1].hide() + self.blinker.finish() + + def enterPage2(self, *args): + self.bPrev.show() + self.title['text'] = TTLocalizer.ChineseTutorialTitle2, + self['text'] = TTLocalizer.ChinesePage2 + self['text_pos'] = (0.0, 0.28) + self['text_wordwrap'] =12.5 + self.bNext['state'] = DGG.DISABLED + self.bNext.hide() + self.iPage2.show() + self.iPage3.show() + self.bQuit.show() + + def exitPage2(self, *args): + self.iPage2.hide() + self.bQuit.hide() + self.iPage3.hide() + + def enterQuit(self , *args): + self.iPage1.removeNode() + self.iPage2.removeNode() + self.iPage3.removeNode() + self.bNext.destroy() + self.bPrev.destroy() + self.bQuit.destroy() + DirectFrame.destroy(self) + def exitQuit(self, *args): + pass + + def handleQuit(self, task = None): + base.cr.playGame.getPlace().setState("walk") + self.forceTransition("Quit") + self.doneFunction() + if task != None: + task.done + + + + + +class CheckersTutorial(DirectFrame, FSM.FSM): + + def __init__(self, doneFunction, doneEvent=None, callback=None): + + FSM.FSM.__init__(self, "CheckersTutorial") + self.doneFunction = doneFunction + base.localAvatar.startSleepWatch(self.handleQuit) + + self.doneEvent = doneEvent + self.callback = callback + self.setStateArray(["Page1", "Page2","Page3","Quit"]) + + # initialize our base class. + DirectFrame.__init__(self, + pos = (-0.7, 0.0, 0.0), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.5, 1.0), + text = "", + text_scale = 0.06, + ) + self.accept('stoppedAsleep', self.handleQuit) + + # For some reason, we need to set this after construction to + # get it to work properly. + self['image'] = DGG.getDefaultDialogGeom() + + #title this sucker + self.title = DirectLabel(self, + relief = None, + text = "", + text_pos = (0.0, 0.40), + text_fg = (1, 0, 0, 1), + text_scale = 0.13, + text_font = ToontownGlobals.getSignFont(), + #text_shadow = (1, 1, 1, 1), + ) + + #create some image frames + images = loader.loadModel("phase_6/models/golf/regularchecker_tutorial.bam") + #images.reparentTo("aspect2regularchecker_tutorial.egg") + images.setTransparency(1) + #self.iPage1 = DirectFrame(self, + # image = images.find('**/tutorialPage1*'), + # scale = 8.35, + # pos = (0.41, -0.1, 0.05), + # ) + self.iPage1 = images.find('**/tutorialPage1*') + self.iPage1.reparentTo(aspect2d) + self.iPage1.setPos(0.43, -0.1, 0.0) + self.iPage1.setScale(.4) + self.iPage1.setTransparency(1) + self.iPage1.hide() + + + + + self.iPage2 = images.find('**/tutorialPage2*') + self.iPage2.reparentTo(aspect2d) + self.iPage2.setPos(0.43, -0.1, 0.0) + self.iPage2.setScale(.4) + self.iPage2.setTransparency(1) + self.iPage2.hide() + + + self.iPage3 = images.find('**/tutorialPage3*') + self.iPage3.reparentTo(aspect2d) + self.iPage3.setPos(0.6, -0.1, 0.5) + self.iPage3.setScale(.4) + self.iPage3.setTransparency(1) + self.obj = self.iPage3.find('**/king*') + self.iPage3.hide() + + + self.iPage4 = images.find('**/tutorialPage4*') + self.iPage4.reparentTo(aspect2d) + self.iPage4.setPos(0.6, -0.1, -0.5) + self.iPage4.setScale(.4) + self.iPage4.setTransparency(1) + self.iPage4.hide() + + + + # Create some buttons. + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + + self.bNext = DirectButton(self, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP")), + image3_color = Vec4(1, 1, 1, 0.5), + relief = None, + text = TTLocalizer.ChineseTutorialNext, + text3_fg = Vec4(0, 0, 0, 0.5), + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.35, -0.3, -0.33), + command = self.requestNext, + ) + + self.bPrev = DirectButton(self, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP")), + image3_color = Vec4(1, 1, 1, 0.5), + image_scale = (-1.0, 1.0, 1.0), # make the arrow point left + relief = None, + text = TTLocalizer.ChineseTutorialPrev, + text3_fg = Vec4(0, 0, 0, 0.5), + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (-0.35, -0.3, -0.33), + command = self.requestPrev, + ) + + self.bQuit = DirectButton(self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.ChineseTutorialDone, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.0, -0.3, -0.33), + command = self.handleQuit, + ) + self.bQuit.hide() + + buttons.removeNode() + gui.removeNode() + self.request("Page1") + + def __del__(self): + self.cleanup() + + def enterPage1(self, *args): + self.bNext.show() + self.title['text'] = TTLocalizer.ChineseTutorialTitle1, + self['text'] = TTLocalizer.CheckersPage1 + self['text_pos'] = (0.0, 0.23) + self['text_wordwrap'] = 13.5 + self['text_scale'] = 0.06 + self.bPrev['state'] = DGG.DISABLED + self.bPrev.hide() + self.bNext['state'] = DGG.NORMAL + self.iPage1.show() + + def exitPage1(self, *args): + self.bPrev['state'] = DGG.NORMAL + self.iPage1.hide() + + + def enterPage2(self, *args): + self.bPrev.show() + self.bNext.show() + self.title['text'] = TTLocalizer.ChineseTutorialTitle2, + self['text'] = TTLocalizer.CheckersPage2 + self['text_pos'] = (0.0, 0.28) + self['text_wordwrap'] =12.5 + self['text_scale'] = 0.06 + self.bNext['state'] = DGG.NORMAL + self.iPage2.show() + + + def exitPage2(self, *args): + self.iPage2.hide() + + def enterPage3(self, *args): + self.bPrev.show() + self.title['text'] = TTLocalizer.ChineseTutorialTitle2, + self['text'] = TTLocalizer.CheckersPage3 + "\n\n" + TTLocalizer.CheckersPage4 + self['text_pos'] = (0.0, 0.32) + self['text_wordwrap'] =19 + self['text_scale'] = 0.05 + self.bNext['state'] = DGG.DISABLED + + self.blinker = Sequence() + self.blinker.append(LerpColorInterval(self.obj, .5, Vec4(0.5,0.5,0,0.0), Vec4(0.9,0.9,0, 1))) + self.blinker.append(LerpColorInterval(self.obj, .5, Vec4(0.9,0.9,0, 1) , Vec4(0.5,0.5,0,0.0) )) + self.blinker.loop() + + self.bNext.hide() + self.iPage3.show() + self.iPage4.show() + self.bQuit.show() + + def exitPage3(self, *args): + self.blinker.finish() + self.iPage3.hide() + self.bQuit.hide() + self.iPage4.hide() + + def enterQuit(self , *args): + self.iPage1.removeNode() + self.iPage2.removeNode() + self.iPage3.removeNode() + self.bNext.destroy() + self.bPrev.destroy() + self.bQuit.destroy() + DirectFrame.destroy(self) + def exitQuit(self, *args): + pass + + def handleQuit(self, task = None): + self.forceTransition("Quit") + base.cr.playGame.getPlace().setState("walk") + self.doneFunction() + if task != None: + task.done diff --git a/toontown/src/safezone/GolfKart.py b/toontown/src/safezone/GolfKart.py new file mode 100644 index 0000000..8668c9c --- /dev/null +++ b/toontown/src/safezone/GolfKart.py @@ -0,0 +1,278 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from direct.showbase import PythonUtil + + +class GolfKart(StateData.StateData): + def __init__(self, safeZone, parentFSM, doneEvent, golfCourse): + + StateData.StateData.__init__(self, doneEvent) + + self.golfCourse = golfCourse + + self.fsm = ClassicFSM.ClassicFSM('GolfKart', + [State.State('start', + self.enterStart, + self.exitStart, + ['requestBoard', + 'trolleyHFA', + 'trolleyTFA']), + State.State('trolleyHFA', + self.enterTrolleyHFA, + self.exitTrolleyHFA, + ['final', + ]), + State.State('trolleyTFA', + self.enterTrolleyTFA, + self.exitTrolleyTFA, + ['final', + ]), + State.State('requestBoard', + self.enterRequestBoard, + self.exitRequestBoard, + ['boarding']), + State.State('boarding', + self.enterBoarding, + self.exitBoarding, + ['boarded']), + State.State('boarded', + self.enterBoarded, + self.exitBoarded, + ['requestExit', + 'trolleyLeaving', + 'final']), + State.State('requestExit', + self.enterRequestExit, + self.exitRequestExit, + ['exiting', 'trolleyLeaving']), + State.State('trolleyLeaving', + self.enterTrolleyLeaving, + self.exitTrolleyLeaving, + ['final']), + State.State('exiting', + self.enterExiting, + self.exitExiting, + ['final']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', + ) + + self.parentFSM = parentFSM + + return None + + def load(self): + self.parentFSM.getStateNamed("golfKartBlock").addChild(self.fsm) + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find( + "**/InventoryButtonRollover") + return + + def unload(self): + self.parentFSM.getStateNamed("trolley").removeChild(self.fsm) + del self.fsm + del self.parentFSM + self.buttonModels.removeNode() + del self.buttonModels + del self.upButton + del self.downButton + del self.rolloverButton + return + + def enter(self): + """enter(self) + """ + self.fsm.enterInitialState() + if base.localAvatar.hp > 0: + # let the distributed trolley know it is + # ok for us to enter the trolley + messenger.send('enterGolfKartOK_%d' % self.golfCourse) + self.fsm.request("requestBoard") + else: + # can't board if we are 'sad' + self.fsm.request("trolleyHFA") + return None + + def exit(self): + self.ignoreAll() + return None + + def enterStart(self): + return None + + def exitStart(self): + return None + + def enterTrolleyHFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyHFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyHFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def enterTrolleyTFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyTFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyTFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def __handleNoTrolleyAck(self): + ntbDoneStatus = self.noTrolleyBox.doneStatus + if ntbDoneStatus == "ok": + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + else: + self.notify.error("Unrecognized doneStatus: " + str(ntbDoneStatus)) + return + + def enterRequestBoard(self): + return None + + def handleRejectBoard(self): + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + + def exitRequestBoard(self): + return None + + def enterBoarding(self, nodePath): + camera.wrtReparentTo(nodePath) + heading = PythonUtil.fitDestAngle2Src( camera.getH( nodePath), 180 ) + self.cameraBoardTrack = LerpPosHprInterval(camera, 1.5, + Point3(0, 18, 8), + Point3(heading, -10, 0)) + + self.cameraBoardTrack.start() + return None + + def exitBoarding(self): + self.ignore("boardedTrolley") + return None + + def enterBoarded(self): + self.enableExitButton() + return None + + def exitBoarded(self): + # Remove the boarding task... You might think this should be + # removed in exitBoarding, but we want the camera move to continue + # into the boarded state. Since boarding only goes directly into + # boarded state, it's okay. Probably boarding and boarded should + # be the same state. + self.cameraBoardTrack.finish() + self.disableExitButton() + return None + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.TrolleyHopOff, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -0.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (0, 0, 0.8), + scale = 0.15, + command = lambda self=self: self.fsm.request("requestExit"), + ) + return + + def disableExitButton(self): + self.exitButton.destroy() + return + + def enterRequestExit(self): + messenger.send("trolleyExitButton") + return None + + def exitRequestExit(self): + return None + + def enterTrolleyLeaving(self): + # A camera move + #camera.lerpPosHprXYZHPR(0, 18.55, 3.75, -180, 0, 0, 3, + # blendType = "easeInOut", task="leavingCamera") + self.acceptOnce("playMinigame", self.handlePlayMinigame) + self.acceptOnce("playGolf", self.handlePlayGolf) + return None + + def handlePlayMinigame(self, zoneId, minigameId): + base.localAvatar.b_setParent(ToontownGlobals.SPHidden) + doneStatus = {} + doneStatus["mode"] = "minigame" + doneStatus["zoneId"] = zoneId + doneStatus["minigameId"] = minigameId + messenger.send(self.doneEvent, [doneStatus]) + + def handlePlayGolf(self, zoneId, courseId): + base.localAvatar.b_setParent(ToontownGlobals.SPHidden) + doneStatus = {} + doneStatus["mode"] = "golfcourse" + doneStatus["zoneId"] = zoneId + doneStatus["courseId"] = courseId + messenger.send(self.doneEvent, [doneStatus]) + + + def exitTrolleyLeaving(self): + self.ignore("playMinigame") + taskMgr.remove("leavingCamera") + return None + + def enterExiting(self): + return None + + def handleOffTrolley(self): + doneStatus = {} + doneStatus["mode"] = "exit" + messenger.send(self.doneEvent, [doneStatus]) + return None + + def exitExiting(self): + return None + + def enterFinal(self): + return None + + def exitFinal(self): + return None + diff --git a/toontown/src/safezone/MMPlayground.py b/toontown/src/safezone/MMPlayground.py new file mode 100644 index 0000000..5e00235 --- /dev/null +++ b/toontown/src/safezone/MMPlayground.py @@ -0,0 +1,90 @@ +""" MMPlayground module: contains the MMPlayground + class which represents the client version of + Minnie's Melodyland safezone.""" + +from pandac.PandaModules import * + +import Playground +import random +from direct.fsm import ClassicFSM, State +from direct.actor import Actor +from toontown.toonbase import ToontownGlobals + +class MMPlayground(Playground.Playground): + """ + //////////////////////////////////////////////////////////////////// + // + // MMPlayground: client side version of Minnie's Melodyland + // safezone. Handle's the safezone activity for + // the local player. + // + //////////////////////////////////////////////////////////////////// + """ + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + + self.activityFsm = ClassicFSM.ClassicFSM('Activity', + [State.State('off', + self.enterOff, + self.exitOff, + ['OnPiano']), + State.State('OnPiano', + self.enterOnPiano, + self.exitOnPiano, + ['off'])], + # Initial state + 'off', + # Final state + 'off', + ) + self.activityFsm.enterInitialState() + + def load(self): + Playground.Playground.load(self) + + def unload(self): + del self.activityFsm + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + + def exit(self): + Playground.Playground.exit(self) + + def handleBookClose(self): + Playground.Playground.handleBookClose(self) + + def teleportInDone(self): + Playground.Playground.teleportInDone(self) + + ##### Off state ##### + + def enterOff(self): + return None + + def exitOff(self): + return None + + ##### OnPiano state ##### + + def enterOnPiano(self): + """ + reparent the local toon to the piano in the safezone. + """ + base.localAvatar.b_setParent(ToontownGlobals.SPMinniesPiano) + + def exitOnPiano(self): + """ + unparent the local toon from the piano + """ + base.localAvatar.b_setParent(ToontownGlobals.SPRender) + + + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Minnie)) diff --git a/toontown/src/safezone/MMSafeZoneLoader.py b/toontown/src/safezone/MMSafeZoneLoader.py new file mode 100644 index 0000000..30d8af2 --- /dev/null +++ b/toontown/src/safezone/MMSafeZoneLoader.py @@ -0,0 +1,28 @@ + +from pandac.PandaModules import * +import SafeZoneLoader +import MMPlayground +from toontown.toonbase import ToontownGlobals + +class MMSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = MMPlayground.MMPlayground + self.musicFile = "phase_6/audio/bgm/MM_nbrhood.mid" + self.activityMusicFile = "phase_6/audio/bgm/MM_SZ_activity.mid" + self.dnaFile = "phase_6/dna/minnies_melody_land_sz.dna" + self.safeZoneStorageDNAFile = "phase_6/dna/storage_MM_sz.dna" + + def load(self): + print "loading MM safezone" + SafeZoneLoader.SafeZoneLoader.load(self) + self.piano = self.geom.find('**/center_icon') + if (self.piano.isEmpty()): + self.notify.error("Piano not found") + else: + hq = self.geom.find('**/*toon_landmark_hqMM_DNARoot') + hq.wrtReparentTo(self.piano) + + def unload(self): + SafeZoneLoader.SafeZoneLoader.unload(self) + del self.piano diff --git a/toontown/src/safezone/MMTreasurePlannerAI.py b/toontown/src/safezone/MMTreasurePlannerAI.py new file mode 100644 index 0000000..5084e4f --- /dev/null +++ b/toontown/src/safezone/MMTreasurePlannerAI.py @@ -0,0 +1,45 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedMMTreasureAI + +class MMTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 10 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedMMTreasureAI.DistributedMMTreasureAI, # Constructor + "MMTreasurePlanner", + 20, # seconds/spawn + 2 # Max of two treasures + ) + return None + + def initSpawnPoints(self): + self.spawnPoints = [ + (118, -39, 3.3), + (118, 1, 3.3), + (112, -22, 0.8), + (108, -74, -4.5), # drum treasures + (110, -65, -4.5), + (102, 23.5, -4.5), + (60, -115, 6.5), + (-5, -115, 6.5), + (-64, -77, 6.5), + (-77, -44, 6.5), + (-76, 3, 6.5), + (44, 76, 6.5), +# (36, -12, -11.5), no treasures allowed on piano! +# (-22, -23, -11.5), + (136, -96, -13.5), + (85, -6.7, -13.5), + (60, -95, -14.5), + (72, 60, -13.5), + (-55, -23, -14.5), + (-21, 47, -14.5), + (-24, -75, -14.5), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/OZPlayground.py b/toontown/src/safezone/OZPlayground.py new file mode 100644 index 0000000..9c9db00 --- /dev/null +++ b/toontown/src/safezone/OZPlayground.py @@ -0,0 +1,272 @@ +from pandac.PandaModules import * + +from toontown.toonbase import ToontownGlobals +import Playground +from toontown.launcher import DownloadForceAcknowledge +from toontown.building import Elevator +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from toontown.racing import RaceGlobals +from direct.fsm import State +#from toontown.trolley import Trolley +from toontown.safezone import PicnicBasket +from toontown.safezone import GolfKart +from direct.task.Task import Task + +class OZPlayground(Playground.Playground): + + waterLevel = -0.53 + + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + self.parentFSM=parentFSM + self.picnicBasketBlockDoneEvent = "picnicBasketBlockDone" + self.cameraSubmerged = -1 + self.toonSubmerged = -1 + self.fsm.addState( State.State('picnicBasketBlock', + self.enterPicnicBasketBlock, + self.exitPicnicBasketBlock, + ['walk'])) + state = self.fsm.getStateNamed('walk') + state.addTransition('picnicBasketBlock') + + self.picnicBasketDoneEvent = "picnicBasketDone" + + def load(self): + Playground.Playground.load(self) + + def unload(self): + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + + def exit(self): + Playground.Playground.exit(self) + taskMgr.remove('oz-check-toon-underwater') + taskMgr.remove('oz-check-cam-underwater') + #self.rotateBlimp.finish() + # Clean up underwater state + self.loader.hood.setNoFog() + + def doRequestLeave(self, requestStatus): + # when it's time to leave, check their trialer status first + self.fsm.request('trialerFA', [requestStatus]) + + def enterDFA(self, requestStatus): + """ + Override the base class because here we specifically ask for + phase 5, the toontown central streets. + - NEW: we can now go home. Check the hood before assuming we + are going to the streets + """ + doneEvent = "dfaDoneEvent" + self.accept(doneEvent, self.enterDFACallback, [requestStatus]) + self.dfa = DownloadForceAcknowledge.DownloadForceAcknowledge(doneEvent) + if requestStatus["hoodId"] == ToontownGlobals.MyEstate: + # Ask if we can enter phase 5.5 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.MyEstate)) + else: + # Ask if we can enter phase 5 + self.dfa.enter(5) + + def enterStart(self): + assert self.notify.debugStateCall(self) + self.cameraSubmerged = 0 + self.toonSubmerged = 0 + taskMgr.add(self.__checkToonUnderwater, + 'oz-check-toon-underwater') + taskMgr.add(self.__checkCameraUnderwater, + 'oz-check-cam-underwater') + + def __checkCameraUnderwater(self, task): + # spammy: assert self.notify.debugStateCall(self) + # We need to take into account the height of the local toon + # if (base.localAvatar.getZ() < -2.3314585): + # It is more accurate to use the camera because it can + # move independently of the toon + if (camera.getZ(render) < self.waterLevel): + self.__submergeCamera() + else: + self.__emergeCamera() + return Task.cont + + def __checkToonUnderwater(self, task): + # spammy: assert self.notify.debugStateCall(self) + # We need to take into account the height of the local toon + if (base.localAvatar.getZ() < -4.0): + self.__submergeToon() + else: + self.__emergeToon() + return Task.cont + + def __submergeCamera(self): + if (self.cameraSubmerged == 1): + return + assert self.notify.debugStateCall(self) + self.loader.hood.setUnderwaterFog() + base.playSfx(self.loader.underwaterSound, looping = 1, volume = 0.8) + #self.loader.seagullSound.stop() + #taskMgr.remove('dd-seagulls') + self.cameraSubmerged = 1 + self.walkStateData.setSwimSoundAudible(1) + + def __emergeCamera(self): + if (self.cameraSubmerged == 0): + return + assert self.notify.debugStateCall(self) + #self.loader.hood.setWhiteFog() + self.loader.hood.setNoFog() + self.loader.underwaterSound.stop() + #self.nextSeagullTime = random.random() * 8.0 + #taskMgr.add(self.__seagulls, 'dd-seagulls') + self.cameraSubmerged = 0 + self.walkStateData.setSwimSoundAudible(0) + + def __submergeToon(self): + if (self.toonSubmerged == 1): + return + assert self.notify.debugStateCall(self) + base.playSfx(self.loader.submergeSound) # plays a splash sound + # Make sure you are in walk mode This fixes a bug where you could + # open your stickerbook over the water and get stuck in swim mode + # becuase the Place was still in StickerBook state. + if base.config.GetBool('disable-flying-glitch') == 0: + self.fsm.request('walk') + # You have to pass in the swim sound effect to swim mode. + self.walkStateData.fsm.request('swimming', [self.loader.swimSound]) + # Let everyone else see your splash + pos = base.localAvatar.getPos(render) + # our water level is different from donald's dock + base.localAvatar.d_playSplashEffect(pos[0], pos[1], self.waterLevel) + self.toonSubmerged = 1 + + def __emergeToon(self): + if (self.toonSubmerged == 0): + return + assert self.notify.debugStateCall(self) + self.walkStateData.fsm.request('walking') + self.toonSubmerged = 0 + + + # teleportIn + def enterTeleportIn(self, requestStatus): + reason = requestStatus.get("reason") + if reason == RaceGlobals.Exit_Barrier: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeout, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_Slow: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RacerTooSlow, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + elif reason == RaceGlobals.Exit_BarrierNoRefund: + # we timed out of a race + requestStatus['nextState'] = 'popup' + self.dialog = TTDialog.TTDialog( + text = TTLocalizer.KartRace_RaceTimeoutNoRefund, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + self.toonSubmerged = -1 + taskMgr.remove('oz-check-toon-underwater') + Playground.Playground.enterTeleportIn(self, requestStatus) + + def teleportInDone(self): + """ + Override Place.py teleportInDone to check if we are cameraSubmerged. + If we are cameraSubmerged, we should swim instead of walk + """ + assert self.notify.debugStateCall(self) + self.toonSubmerged = -1 + taskMgr.add(self.__checkToonUnderwater, + 'oz-check-toon-underwater') + Playground.Playground.teleportInDone(self) + + def __cleanupDialog(self, value): + if (self.dialog): + self.dialog.cleanup() + self.dialog = None + if hasattr(self, "fsm"): + self.fsm.request('walk', [1]) + + def enterPicnicBasketBlock(self, picnicBasket): + assert(self.notify.debug("enterPicnicBasketBlock()")) + # Turn on the laff meter + base.localAvatar.laffMeter.start() + + # clear the anim state + base.localAvatar.b_setAnimState("off", 1) + + # Disable leave to pay / set parent password (for consistency) + base.localAvatar.cantLeaveGame = 1 + + self.accept(self.picnicBasketDoneEvent, self.handlePicnicBasketDone) + self.trolley = PicnicBasket.PicnicBasket(self, self.fsm, self.picnicBasketDoneEvent, picnicBasket.getDoId(), picnicBasket.seatNumber) + self.trolley.load() + self.trolley.enter() + + def exitPicnicBasketBlock(self): + assert(self.notify.debug("exitPicnicBasketBlock()")) + + # Turn off the laff meter + base.localAvatar.laffMeter.stop() + base.localAvatar.cantLeaveGame = 0 + + self.ignore(self.trolleyDoneEvent) + self.trolley.unload() + self.trolley.exit() + del self.trolley + + def detectedPicnicTableSphereCollision(self, picnicBasket): + assert(self.notify.debug("detectedPicnicTableSphereCollision()")) + self.fsm.request("picnicBasketBlock", [picnicBasket]) + + def handleStartingBlockDone(self, doneStatus): + assert(self.notify.debug("handleStartingBlockDone()")) + self.notify.debug("handling StartingBlock done event") + where = doneStatus['where'] + if (where == 'reject'): + self.fsm.request("walk") + elif (where == 'exit'): + self.fsm.request("walk") + elif (where == 'racetrack'): + self.doneStatus = doneStatus + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + where + + " in handleStartingBlockDone") + + + def handlePicnicBasketDone(self, doneStatus): + assert(self.notify.debug("handlePicnicBasketDone()")) + self.notify.debug("handling picnic basket done event") + mode = doneStatus["mode"] + if mode == "reject": + self.fsm.request("walk") + elif mode == "exit": + self.fsm.request("walk") + #elif mode == "golfcourse": + # self.doneStatus = {"loader" : "golfcourse", + # "where" : "golfcourse", + # "hoodId" : self.loader.hood.id, + # "zoneId" : doneStatus["zoneId"], + # "shardId" : None, + # "courseId" : doneStatus["courseId"] + # } + # #self.doneStatus = doneStatus + # messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + mode + " in handlePicnicBasketDone") + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Chip)) diff --git a/toontown/src/safezone/OZSafeZoneLoader.py b/toontown/src/safezone/OZSafeZoneLoader.py new file mode 100644 index 0000000..848d1c6 --- /dev/null +++ b/toontown/src/safezone/OZSafeZoneLoader.py @@ -0,0 +1,626 @@ +########################################################################## +# Module: GSSafeZoneLoader.py +# Purpose: This module oversees the construction of the Goofy Speedway +# safe zone loader object. +# +# Date: 6/10/05 +# Author: jjtaylor (jjtaylor@schellgames.com) +# Note: Modification of original GSLoader.py +########################################################################## + +########################################################################## +# Panda/Direct Import Modules +########################################################################## +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from pandac.PandaModules import * +from otp.avatar import Avatar + + +########################################################################## +# Toontwon Import Modules +########################################################################## +from toontown.hood import ZoneUtil +from toontown.launcher import DownloadForceAcknowledge +from toontown.safezone.SafeZoneLoader import SafeZoneLoader +from toontown.safezone.OZPlayground import OZPlayground +from direct.actor import Actor +from direct.interval.IntervalGlobal import * +import random +from toontown.distributed import DelayDelete +from direct.distributed.ClockDelta import * +from otp.otpbase import OTPGlobals +import copy +from toontown.effects import Bubbles + + +########################################################################## +# Python Import Modules +########################################################################## +import random +if( __debug__ ): + import pdb + +class OZSafeZoneLoader( SafeZoneLoader ): + """ + Purpose: The OZSafeZoneLoader Class provides.. yadda yadda + """ + + def __init__( self, hood, parentFSM, doneEvent ): + """ + """ + # Initialize Super Class + SafeZoneLoader.__init__( self, hood, parentFSM, doneEvent ) + + # Initialize Instance Variables + self.musicFile = "phase_6/audio/bgm/OZ_SZ.mid" + self.activityMusicFile = "phase_6/audio/bgm/GS_KartShop.mid" + self.dnaFile = "phase_6/dna/outdoor_zone_sz.dna" + self.safeZoneStorageDNAFile = "phase_6/dna/storage_OZ_sz.dna" + + # Tracks on toons, for starting and stopping + # stored by avId : track. There is only a need for one at a time, + # in fact the point of the dict is to ensure only one is playing at a time + self.__toonTracks = {} + + # Override Super Class FSM + del self.fsm + self.fsm = ClassicFSM.ClassicFSM('SafeZoneLoader', + [State.State('start', + self.enterStart, + self.exitStart, + ['quietZone', + 'playground', + 'toonInterior',]), + State.State('playground', + self.enterPlayground, + self.exitPlayground, + ['quietZone', 'golfcourse' ]), + State.State('toonInterior', + self.enterToonInterior, + self.exitToonInterior, + ['quietZone']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['playground', 'toonInterior', 'golfcourse' ]), + State.State('golfcourse', + self.enterGolfCourse, + self.exitGolfCourse, + ['quietZone', 'playground']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', ) + + def load( self ): + """ + """ + self.done = 0 + self.geyserTrack = None + + SafeZoneLoader.load( self ) + self.birdSound = map( base.loadSfx, [ 'phase_4/audio/sfx/SZ_TC_bird1.mp3', + 'phase_4/audio/sfx/SZ_TC_bird2.mp3', + 'phase_4/audio/sfx/SZ_TC_bird3.mp3' ] ) + self.underwaterSound = base.loadSfx('phase_4/audio/sfx/AV_ambient_water.mp3') + self.swimSound = base.loadSfx('phase_4/audio/sfx/AV_swim_single_stroke.mp3') + self.submergeSound = base.loadSfx('phase_5.5/audio/sfx/AV_jump_in_water.mp3') + geyserPlacer = self.geom.find("**/geyser*") + waterfallPlacer = self.geom.find("**/waterfall*") + + binMgr = CullBinManager.getGlobalPtr() + binMgr.addBin('water', CullBinManager.BTFixed, 29) + binMgr = CullBinManager.getGlobalPtr() + + + self.water = self.geom.find("**/water1*") + self.water.setTransparency(1) + self.water.setColorScale(1.0, 1.0, 1.0, 1.0) + self.water.setBin('water', 51, 1) + + pool = self.geom.find("**/pPlane5*") + pool.setTransparency(1) + pool.setColorScale(1.0, 1.0, 1.0, 1.0) + pool.setBin('water', 50, 1) + + + + + self.geyserModel = loader.loadModel("phase_6/models/golf/golf_geyser_model") + self.geyserSound = loader.loadSfx("phase_6/audio/sfx/OZ_Geyser.mp3") + self.geyserSoundInterval = SoundInterval(self.geyserSound, node=geyserPlacer, + listenerNode = base.camera, + seamlessLoop = False, + volume = 1.0, + cutOff = 120) + + self.geyserSoundNoToon = loader.loadSfx("phase_6/audio/sfx/OZ_Geyser_No_Toon.mp3") + self.geyserSoundNoToonInterval = SoundInterval(self.geyserSoundNoToon, node=geyserPlacer, + listenerNode = base.camera, + seamlessLoop = False, + volume = 1.0, + cutOff = 120) + #self.geyserSoundInterval.start() + + if (self.geyserModel): + + self.geyserActor = Actor.Actor(self.geyserModel) + self.geyserActor.loadAnims({'idle': "phase_6/models/golf/golf_geyser"}) + self.geyserActor.reparentTo(render) + self.geyserActor.setPlayRate(8.6, 'idle') + self.geyserActor.loop('idle') + self.geyserActor.setDepthWrite(0) + self.geyserActor.setTwoSided(True, 11) + self.geyserActor.setColorScale(1.0, 1.0, 1.0, 1.0) + self.geyserActor.setBin('fixed', 0) + + # 1st uv animation + + mesh = self.geyserActor.find('**/mesh_tide1') + joint = self.geyserActor.find('**/uvj_WakeWhiteTide1') + mesh.setTexProjector(mesh.findTextureStage('default'), joint, self.geyserActor) + self.geyserActor.setPos(geyserPlacer.getPos()) + self.geyserActor.setZ(geyserPlacer.getZ() - 100.0) + self.geyserPos = geyserPlacer.getPos() + self.geyserPlacer = geyserPlacer + self.startGeyser() + #self.setGeyserAnim() + base.sfxPlayer.setCutoffDistance(160) + self.geyserPoolSfx = loader.loadSfx("phase_6/audio/sfx/OZ_Geyser_BuildUp_Loop.wav") + self.geyserPoolSoundInterval = SoundInterval(self.geyserPoolSfx, node=self.geyserPlacer, + listenerNode = base.camera, + seamlessLoop = True, + volume = 1.0, + cutOff = 120) + self.geyserPoolSoundInterval.loop() + + self.bubbles = Bubbles.Bubbles(self.geyserPlacer, render) + self.bubbles.renderParent.setDepthWrite(0) + self.bubbles.start() + + self.collBase = render.attachNewNode("collisionBase") + + self.geyserCollSphere = CollisionSphere(0, 0, 0, 7.5) + # Make the sphere intangible + self.geyserCollSphere.setTangible(1) + self.geyserCollNode = CollisionNode("barrelSphere") + self.geyserCollNode.setIntoCollideMask(OTPGlobals.WallBitmask) + self.geyserCollNode.addSolid(self.geyserCollSphere) + self.geyserNodePath = self.collBase.attachNewNode(self.geyserCollNode) + + self.geyserNodePath.setPos(self.geyserPos[0], self.geyserPos[1], self.geyserPos[2] -100.0) + + self.waterfallModel = loader.loadModel("phase_6/models/golf/golf_waterfall_model") + if (self.waterfallModel): + + self.waterfallActor = Actor.Actor(self.waterfallModel) + self.waterfallActor.loadAnims({'idle': "phase_6/models/golf/golf_waterfall"}) + self.waterfallActor.reparentTo(render) + self.waterfallActor.setPlayRate(3.5, 'idle') + self.waterfallActor.loop('idle') + + + # 1st uv animation + + mesh = self.waterfallActor.find('**/mesh_tide1') + joint = self.waterfallActor.find('**/uvj_WakeWhiteTide1') + mesh.setTexProjector(mesh.findTextureStage('default'), joint, self.waterfallActor) + self.waterfallActor.setPos(waterfallPlacer.getPos()) + + self.accept('clientLogout', self._handleLogout) + + #shadows = self.geom.findAllMatches("**/shadow_tree*") + #for shadow in shadows: + # shadow.setDepthTest(0) + # shadow.setBin("shadow", 0) + + + #base.oz = self + + def exit(self): + self.clearToonTracks() + SafeZoneLoader.exit(self) + self.ignore('clientLogout') + + def startGeyser(self, task = None): + if hasattr(base.cr, "DTimer") and base.cr.DTimer: + self.geyserCycleTime = 20.0 + useTime = base.cr.DTimer.getTime() + timeToNextGeyser = 20.0 - (useTime % 20.0) + taskMgr.doMethodLater(timeToNextGeyser, self.doGeyser, "geyser Task") + else: + taskMgr.doMethodLater(5.0, self.startGeyser, "start geyser Task") + #return task.done + + def doGeyser(self, task = None): + #print("do geyser time is %s" % (globalClockDelta.networkToLocalTime(globalClockDelta.getRealNetworkTime()))) + if not self.done: + self.setGeyserAnim() + useTime = base.cr.DTimer.getTime() + timeToNextGeyser = 20.0 - (useTime % 20.0) + taskMgr.doMethodLater(timeToNextGeyser, self.doGeyser, "geyser Task") + return task.done + + def restoreLocal(self, task = None): + place = base.cr.playGame.getPlace() + if place: + place.fsm.request("walk") + base.localAvatar.setTeleportAvailable(1) + base.localAvatar.collisionsOn() + base.localAvatar.dropShadow.show() + + + def restoreRemote(self, remoteAv,task = None): + if remoteAv in Avatar.Avatar.ActiveAvatars: + remoteAv.startSmooth() + remoteAv.dropShadow.show() + + + def setGeyserAnim(self, task = None): + if self.done: + return + + maxSize = (0.4 * random.random()) + 0.75 + time = 1.0 #(0.25 * random.random()) + 0.75 + self.geyserTrack = Sequence() + upPos = Vec3(self.geyserPos[0],self.geyserPos[1],self.geyserPos[2]) + downPos = Vec3(self.geyserPos[0],self.geyserPos[1],self.geyserPos[2] - 8.0) + + + + #LerpPosInterval(suit, 0.6, pos=suitPos, other=battle), + + avList = copy.copy(Avatar.Avatar.ActiveAvatars) + + avList.append(base.localAvatar) + + playSound = 0 + + + for av in avList: + distance = self.geyserPlacer.getDistance(av) + if distance < 7.0: + place = base.cr.playGame.getPlace() + local = 0 + avPos = av.getPos() + upToon = Vec3(avPos[0],avPos[1], maxSize * self.geyserPos[2] + 40.0) + midToon = Vec3(avPos[0],avPos[1], maxSize * self.geyserPos[2] + 30.0) + downToon = Vec3(avPos[0],avPos[1],self.geyserPos[2]) + + returnPoints = [(7,7), (8,0), (-8, 3), (-7,7), (3, -7), (0,8), (-10, 0), (8,-3), (5,8), (-8, 5), (-1, 7)] + pick = int(((float(av.doId) - 11.0) / 13.0) % len(returnPoints)) + returnChoice = returnPoints[pick] #random.choice(returnPoints) + + toonReturn = Vec3(self.geyserPos[0] + returnChoice[0],self.geyserPos[1] + returnChoice[1],self.geyserPos[2] - 1.5) + + topTrack = Sequence() + av.dropShadow.hide() + playSound = 1 + + # put toon in cannon + if av == base.localAvatar: + base.cr.playGame.getPlace().setState('fishing') + base.localAvatar.setTeleportAvailable(0) + base.localAvatar.collisionsOff() + #av.loop('jump-idle') + local = 1 + else: + topTrack.delayDeletes = [DelayDelete.DelayDelete( + av, 'OZSafeZoneLoader.setGeyserAnim')] + av.stopSmooth() + + + #import pdb; pdb.set_trace() + #print("FLY TOON FLY!!!") + + + + animTrack = Parallel() + + toonTrack = Sequence() + + toonTrack.append(Wait(0.50)) + #toonTrack.append(Func(av.loop, 'jump-idle')) + animTrack.append(ActorInterval(av, 'jump-idle', loop = 1, endTime = 11.5 * time)) + animTrack.append(ActorInterval(av, 'neutral', loop = 0, endTime = 0.25 * time)) + + holder = render.attachNewNode("toon hold") + + + base.holder = holder + toonPos = av.getPos(render) + toonHpr = av.getHpr(render) + print ("av Pos %s" % (av.getPos()) ) + base.toonPos = toonPos + holder.setPos(toonPos) + av.reparentTo(holder) + + av.setPos(0,0,0) + + #cameraPosTrack = Sequence() + #camPos = camera.getPos() + #camHpr = camera.getHpr() + #cameraPosTrack.append(LerpPosHprInterval(camera, 5.0 * time, pos=Point3(0.0, -20.0, 20.0), hpr=Vec3(180, -45, 0),)) + #cameraPosTrack.append(LerpPosHprInterval(camera, 5.0 * time, pos=camPos, hpr = camHpr)) + #cameraPosTrack.start() + lookAt = 180 + + toonH = (lookAt + toonHpr[0]) % 360 + newHpr = Vec3(toonH, toonHpr[1], toonHpr[2]) + + + if toonH < 180: + lookIn = Vec3(0 + lookAt, -30, 0) + else: + lookIn = Vec3(360 + lookAt,-30,0) + + print("Camera Hprs toon %s; lookIn %s; final %s" % (newHpr, lookIn, (lookIn - newHpr))) + + if local == 1: + pass + camPosOriginal = camera.getPos() + camHprOriginal = camera.getHpr() + camParentOriginal = camera.getParent() + cameraPivot = holder.attachNewNode("camera pivot") + + chooseHeading = random.choice([-10.0, 15.0, 40.0]) + + cameraPivot.setHpr(chooseHeading,-20.0,0.0)#(av.getHpr()) + cameraArm = cameraPivot.attachNewNode("camera arm") + cameraArm.setPos(0.0,-23.0,3.0)#(av.getHpr()) + camPosStart = Point3(0.0,0.0,0.0) + camHprStart = Vec3(0.0,0.0,0.0) + + self.changeCamera(cameraArm, camPosStart, camHprStart) + + cameraTrack = Sequence() + #cameraTrack.append(LerpPosHprInterval(cameraPivot, 1.25 * time, pos= Point3(0,-20,15),hpr=lookIn, startHpr = newHpr)) + cameraTrack.append(Wait(11.0 * time)) + #cameraTrack.append(LerpPosHprInterval(cameraPivot, 1.25 * time, pos = Point3(0,0,0), hpr=newHpr, startHpr = lookIn)) + #cameraPosHprTrack.append(LerpPosHprInterval(cameraHolder, 5.0 * time, pos=Point3(0.0, -20.0, 20.0), hpr=Vec3(180, -45, 0),)) + #cameraPosHprTrack.append(LerpPosHprInterval(cameraHolder, 5.0 * time, pos=camPos, hpr = camHpr)) + cameraTrack.append(Func(self.changeCamera, camParentOriginal, camPosOriginal, camHprOriginal)) + + cameraTrack.start() + + moveTrack = Sequence() + moveTrack.append(Wait(0.50)) + moveTrack.append(LerpPosInterval(holder, 3.0 * time, pos=upToon, startPos = downToon, blendType = 'easeOut')) #up + #toonTrack.append(LerpPosInterval(av, 1.0 * time, pos=upToon, startPos = midToon, blendType = 'noBlend')) #up + moveTrack.append(LerpPosInterval(holder, 2.0 * time, pos=midToon, startPos = upToon, blendType = 'easeInOut')) #down + moveTrack.append(LerpPosInterval(holder, 1.0 * time, pos=upToon, startPos = midToon, blendType = 'easeInOut')) #up + moveTrack.append(LerpPosInterval(holder, 2.0 * time, pos=midToon, startPos = upToon, blendType = 'easeInOut')) #down + moveTrack.append(LerpPosInterval(holder, 1.0 * time, pos=upToon, startPos = midToon, blendType = 'easeInOut')) #up + moveTrack.append(LerpPosInterval(holder, 2.5 * time, pos=toonReturn, startPos = upToon, blendType = 'easeIn')) #down + #moveTrack.append(LerpPosInterval(av, 0.1 * time, pos=toonReturn, startPos = toonReturn, blendType = 'easeIn')) #down + + animTrack.append(moveTrack) + animTrack.append(toonTrack) + topTrack.append(animTrack) + + topTrack.append(Func(av.setPos, toonReturn)) + topTrack.append(Func(av.reparentTo, render)) + topTrack.append(Func(holder.remove)) + + + if local == 1: + topTrack.append(Func(self.restoreLocal)) + #topTrack.append(Func(camera.setPos, camPos)) + #topTrack.append(Func(camera.setHpr, camHpr)) + #topTrack.append(Func(camera.reparentTo, av)) + else: + topTrack.append(Func(self.restoreRemote, av)) + + topTrack.append(Func(self.clearToonTrack, av.doId)) + + #topTrack.timeline() + self.storeToonTrack(av.doId, topTrack) + topTrack.start() + + self.geyserTrack.append(Func(self.doPrint, "geyser start")) + self.geyserTrack.append(Func(self.geyserNodePath.setPos, self.geyserPos[0], self.geyserPos[1], self.geyserPos[2])) + + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, 2.0 * time, 0.75, 0.01), + LerpPosInterval(self.geyserActor, 2.0 * time, pos=downPos, startPos = downPos), + #Func(self.geyserActor.setPlayRate, 15.0, 'idle'), + ) + ) + + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, time, maxSize, 0.75), + LerpPosInterval(self.geyserActor, time, pos=upPos, startPos = downPos), + #Func(self.geyserActor.setPlayRate, 15.0, 'idle'), + ) + ) + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, 2.0 * time, 0.75, maxSize), + LerpPosInterval(self.geyserActor, 2.0 * time, pos=downPos, startPos = upPos), + #Func(self.geyserActor.setPlayRate, 2.0, 'idle'), + ) + ) + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, time, maxSize, 0.75), + LerpPosInterval(self.geyserActor, time, pos=upPos, startPos = downPos), + #Func(self.geyserActor.setPlayRate, 15.0, 'idle'), + ) + ) + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, 2.0 * time, 0.75, maxSize), + LerpPosInterval(self.geyserActor, 2.0 * time, pos=downPos, startPos = upPos), + #Func(self.geyserActor.setPlayRate, 2.0, 'idle'), + ) + ) + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, time, maxSize, 0.75), + LerpPosInterval(self.geyserActor, time, pos=upPos, startPos = downPos), + #Func(self.geyserActor.setPlayRate, 15.0, 'idle'), + ) + ) + + self.geyserTrack.append( + Parallel( + LerpScaleInterval(self.geyserActor, 4.0 * time, 0.01, maxSize), + LerpPosInterval(self.geyserActor, 4.0 * time, pos=downPos, startPos = upPos), + #Func(self.geyserActor.setPlayRate, 2.0, 'idle'), + ) + ) + self.geyserTrack.append(Func(self.geyserNodePath.setPos, self.geyserPos[0], self.geyserPos[1], self.geyserPos[2] - 100.0)) + self.geyserTrack.append(Func(self.doPrint, "geyser end")) + #track.append(LerpScaleInterval(self.geyserActor, 2.0 * time, 0.5, maxSize)) + #self.geyserTrack.append(Wait(5.0)) + #self.geyserTrack.append(Func(self.setGeyserAnim)) + self.geyserTrack.start() + + if playSound: + self.geyserSoundInterval.start() + else: + self.geyserSoundNoToonInterval.start() + + def changeCamera(self, newParent, newPos, newHpr): + camera.reparentTo(newParent) + camera.setPosHpr(newPos, newHpr) + + def doPrint(self, thing): + return 0 + print thing + + + def unload( self ): + """ + """ + del self.birdSound + SafeZoneLoader.unload( self ) + self.done = 1 + self.collBase.remove() + if self.geyserTrack: + self.geyserTrack.finish() + self.geyserTrack = None + self.geyserActor.cleanup() + self.geyserModel.remove() + self.waterfallActor.cleanup() + self.waterfallModel.remove() + self.bubbles.destroy() + del self.bubbles + self.geyserPoolSoundInterval.finish() + self.geyserPoolSfx.stop() + self.geyserPoolSfx = None + self.geyserPoolSoundInterval = None + self.geyserSoundInterval.finish() + self.geyserSound.stop() + self.geyserSoundInterval = None + self.geyserSound = None + + self.geyserSoundNoToonInterval.finish() + self.geyserSoundNoToon.stop() + self.geyserSoundNoToonInterval = None + self.geyserSoundNoToon = None + + + + def enterPlayground( self, requestStatus ): + """ + """ + self.playgroundClass = OZPlayground + SafeZoneLoader.enterPlayground( self, requestStatus ) + + # self.hood.spawnTitleText( requestStatus[ 'zoneId' ] ) + + def exitPlayground( self ): + """ + """ + + taskMgr.remove( 'titleText' ) + self.hood.hideTitleText() + SafeZoneLoader.exitPlayground( self ) + self.playgroundClass = None + + def handlePlaygroundDone( self ): + assert( self.notify.debug( "handlePlaygroundDone()" ) ) + status = self.place.doneStatus + + self.doneStatus = status + messenger.send( self.doneEvent ) + + def enteringARace( self, status ): + if( not status[ 'where' ] == 'golfcourse' ): + return 0 + if( ZoneUtil.isDynamicZone( status[ 'zoneId' ] ) ): + return status[ 'hoodId' ] == self.hood.hoodId + else: + return ZoneUtil.getHoodId( status[ 'zoneId' ] ) == self.hood.hoodId + + def enteringAGolfCourse( self, status ): + if( not status[ 'where' ] == 'golfcourse' ): + return 0 + if( ZoneUtil.isDynamicZone( status[ 'zoneId' ] ) ): + return status[ 'hoodId' ] == self.hood.hoodId + else: + return ZoneUtil.getHoodId( status[ 'zoneId' ] ) == self.hood.hoodId + + def enterGolfCourse( self, requestStatus ): + """ + """ + + # GolfCourse will grab this off of us + if requestStatus.has_key('curseId'): + self.golfCourseId = requestStatus[ 'courseId' ] + else: + self.golfCourseId = 0 + + self.accept("raceOver",self.handleRaceOver) + self.accept("leavingGolf", self.handleLeftGolf) + + base.transitions.irisOut(t=0.2) + + def exitGolfCourse( self ): + """ + """ + del self.golfCourseId + + def handleRaceOver(self): + print "you done!!" + + def handleLeftGolf(self): + req={"loader":"safeZoneLoader","where":"playground","how":"teleportIn" + ,"zoneId":6000,"hoodId":6000,"shardId":None} + self.fsm.request("quietZone",[req]) + + def _handleLogout(self): + self.clearToonTracks() + + def storeToonTrack(self, avId, track): + # Clear out any currently playing tracks on this toon + self.clearToonTrack(avId) + # Store this new one + self.__toonTracks[avId] = track + + def clearToonTrack(self, avId): + # Clear out any currently playing tracks on this toon + oldTrack = self.__toonTracks.get(avId) + if oldTrack: + oldTrack.pause() + DelayDelete.cleanupDelayDeletes(oldTrack) + del self.__toonTracks[avId] + + def clearToonTracks(self): + #We can't use an iter because we are deleting keys + keyList = [] + for key in self.__toonTracks: + keyList.append(key) + + for key in keyList: + if self.__toonTracks.has_key(key): + self.clearToonTrack(key) diff --git a/toontown/src/safezone/OZTreasurePlannerAI.py b/toontown/src/safezone/OZTreasurePlannerAI.py new file mode 100644 index 0000000..f3306e6 --- /dev/null +++ b/toontown/src/safezone/OZTreasurePlannerAI.py @@ -0,0 +1,38 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedOZTreasureAI + +class OZTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 3 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedOZTreasureAI.DistributedOZTreasureAI, # Constructor + "OZTreasurePlanner", + 20, # Frequency of spawn + 5 # Max number of treasures + ) + + def initSpawnPoints(self): + self.spawnPoints = [ + (-156.9, -118.9, 0.025), + (-35.6, 86.0, 1.25), + (116.8, 10.8, 0.104), + (-35, 145.7, 0.025), + (-198.8, -45.1, 0.025), + (-47.1, -25.5, 0.809), + (59.15, 34.8, 1.767), + (-81.02, -72.2, 0.026), + (-167.9, 124.5, 0.025), + (-226.7, -27.6, 0.025), + (-16.0, -108.9, 0.025), + (18.0, 58.5, 5.919), + (91.4, 127.8, 0.025), + (-86.5, -75.9, 0.025), + (-48.751, -32.3, 1.143), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/PicnicBasket.py b/toontown/src/safezone/PicnicBasket.py new file mode 100644 index 0000000..fd559b2 --- /dev/null +++ b/toontown/src/safezone/PicnicBasket.py @@ -0,0 +1,278 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from direct.showbase import PythonUtil + + +class PicnicBasket(StateData.StateData): + def __init__(self, safeZone, parentFSM, doneEvent, tableNumber, seatNumber): + + StateData.StateData.__init__(self, doneEvent) + + self.tableNumber = tableNumber + self.seatNumber = seatNumber + + self.fsm = ClassicFSM.ClassicFSM('PicnicBasket', + [State.State('start', + self.enterStart, + self.exitStart, + ['requestBoard', + 'trolleyHFA', + 'trolleyTFA']), + State.State('trolleyHFA', + self.enterTrolleyHFA, + self.exitTrolleyHFA, + ['final', + ]), + State.State('trolleyTFA', + self.enterTrolleyTFA, + self.exitTrolleyTFA, + ['final', + ]), + State.State('requestBoard', + self.enterRequestBoard, + self.exitRequestBoard, + ['boarding']), + State.State('boarding', + self.enterBoarding, + self.exitBoarding, + ['boarded']), + State.State('boarded', + self.enterBoarded, + self.exitBoarded, + ['requestExit', + 'trolleyLeaving', + 'final', + 'exiting']), + State.State('requestExit', + self.enterRequestExit, + self.exitRequestExit, + ['exiting', 'trolleyLeaving']), + State.State('trolleyLeaving', + self.enterTrolleyLeaving, + self.exitTrolleyLeaving, + ['final']), + State.State('exiting', + self.enterExiting, + self.exitExiting, + ['final']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', + ) + + self.parentFSM = parentFSM + + return None + + def load(self): + self.parentFSM.getStateNamed("picnicBasketBlock").addChild(self.fsm) + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find( + "**/InventoryButtonRollover") + return + + def unload(self): + self.parentFSM.getStateNamed("trolley").removeChild(self.fsm) + del self.fsm + del self.parentFSM + self.buttonModels.removeNode() + del self.buttonModels + del self.upButton + del self.downButton + del self.rolloverButton + return + + def enter(self): + """enter(self) + """ + self.fsm.enterInitialState() + if base.localAvatar.hp > 0: + # let the distributed picnic basket know it is + # ok for us to enter the basket + messenger.send('enterPicnicTableOK_%d_%d' % (self.tableNumber, self.seatNumber)) + self.fsm.request("requestBoard") + else: + # can't board if we are 'sad' + self.fsm.request("trolleyHFA") + return None + + def exit(self): + self.ignoreAll() + return None + + def enterStart(self): + return None + + def exitStart(self): + return None + + def enterTrolleyHFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyHFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyHFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def enterTrolleyTFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyTFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyTFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def __handleNoTrolleyAck(self): + ntbDoneStatus = self.noTrolleyBox.doneStatus + if ntbDoneStatus == "ok": + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + else: + self.notify.error("Unrecognized doneStatus: " + str(ntbDoneStatus)) + return + + def enterRequestBoard(self): + return None + + def handleRejectBoard(self): + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + + def exitRequestBoard(self): + return None + + def enterBoarding(self, nodePath, side): + camera.wrtReparentTo(nodePath) + heading = PythonUtil.fitDestAngle2Src( camera.getH( nodePath), 90 * side) + self.cameraBoardTrack = LerpPosHprInterval(camera, 1.5, + Point3(14.4072 * side, 0, 3.8667), + Point3(heading,-15,0)) + #Point3(heading, -10, 0)) + + self.cameraBoardTrack.start() + return None + + def exitBoarding(self): + self.ignore("boardedTrolley") + return None + + def enterBoarded(self): + self.enableExitButton() + return None + + def exitBoarded(self): + # Remove the boarding task... You might think this should be + # removed in exitBoarding, but we want the camera move to continue + # into the boarded state. Since boarding only goes directly into + # boarded state, it's okay. Probably boarding and boarded should + # be the same state. + self.cameraBoardTrack.finish() + self.disableExitButton() + return None + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.TrolleyHopOff, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -0.23), + text_scale = 0.8, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (0, 0, 0.8), + scale = 0.15, + command = lambda self=self: self.fsm.request("requestExit"), + ) + return + + def disableExitButton(self): + self.exitButton.destroy() + return + + def enterRequestExit(self): + messenger.send("trolleyExitButton") + return None + + def exitRequestExit(self): + return None + + def enterTrolleyLeaving(self): + # A camera move + #camera.lerpPosHprXYZHPR(0, 18.55, 3.75, -180, 0, 0, 3, + # blendType = "easeInOut", task="leavingCamera") + self.acceptOnce("playMinigame", self.handlePlayMinigame) + self.acceptOnce("picnicDone", self.handlePicnicDone) + return None + + def handlePlayMinigame(self, zoneId, minigameId): + base.localAvatar.b_setParent(ToontownGlobals.SPHidden) + doneStatus = {} + doneStatus["mode"] = "minigame" + doneStatus["zoneId"] = zoneId + doneStatus["minigameId"] = minigameId + messenger.send(self.doneEvent, [doneStatus]) + + def handlePicnicDone(self): + #base.localAvatar.b_setParent(ToontownGlobals.SPHidden) + doneStatus = {} + doneStatus["mode"] = "exit" + messenger.send(self.doneEvent, [doneStatus]) + + def exitTrolleyLeaving(self): + self.ignore("playMinigame") + taskMgr.remove("leavingCamera") + return self.notify.debug("handling golf kart done event") + + def enterExiting(self): + return None + + def handleOffTrolley(self): + doneStatus = {} + doneStatus["mode"] = "exit" + messenger.send(self.doneEvent, [doneStatus]) + return None + + def exitExiting(self): + return None + + def enterFinal(self): + return None + + def exitFinal(self): + return None + diff --git a/toontown/src/safezone/Playground.py b/toontown/src/safezone/Playground.py new file mode 100644 index 0000000..76f328f --- /dev/null +++ b/toontown/src/safezone/Playground.py @@ -0,0 +1,952 @@ +"""Playground module: contains the Playground class""" + +from direct.interval.IntervalGlobal import * +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import Place +from direct.showbase import DirectObject +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +from toontown.toon import DeathForceAcknowledge +from toontown.toon import HealthForceAcknowledge +from toontown.tutorial import TutorialForceAcknowledge +from toontown.toon import NPCForceAcknowledge +from toontown.trolley import Trolley +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from direct.gui import DirectLabel +from toontown.quest import Quests + +class Playground(Place.Place): + """Playground class""" + + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("Playground") + + # special methods + + def __init__(self, loader, parentFSM, doneEvent): + """ + Playground constructor: create a play game ClassicFSM + """ + assert(self.notify.debug("__init__()")) + Place.Place.__init__(self, loader, doneEvent) + + self.tfaDoneEvent = "tfaDoneEvent" + # shared state + self.fsm = ClassicFSM.ClassicFSM('Playground', + [State.State('start', + self.enterStart, + self.exitStart, + ['walk', 'deathAck', + 'doorIn', 'tunnelIn']), + State.State('walk', + self.enterWalk, + self.exitWalk, + ['drive', 'sit', 'stickerBook', + 'TFA', 'DFA', 'trialerFA', + 'trolley', 'final', + 'doorOut', 'options', 'quest', + 'purchase', 'stopped', 'fishing']), + State.State('stickerBook', + self.enterStickerBook, + self.exitStickerBook, + ['walk', 'DFA', 'TFA', + # You can get to all of these by jumping over + # the trigger then opening your book + 'trolley', 'final', + 'doorOut', 'quest', + 'purchase', 'stopped', + 'fishing', 'trialerFA', + ]), + State.State('sit', + self.enterSit, + self.exitSit, + ['walk', + 'DFA', # So you can teleport to a friend + 'trialerFA', + ]), + State.State('drive', + self.enterDrive, + self.exitDrive, + ['walk', + 'DFA', # So you can teleport to a friend + 'trialerFA', + ]), + State.State('trolley', + self.enterTrolley, + self.exitTrolley, + ['walk']), + State.State('doorIn', + self.enterDoorIn, + self.exitDoorIn, + ['walk']), + State.State('doorOut', + self.enterDoorOut, + self.exitDoorOut, + ['walk']), # 'final' + # Tutorial Force Acknowledge: + State.State('TFA', + self.enterTFA, + self.exitTFA, + ['TFAReject', 'DFA']), + State.State('TFAReject', + self.enterTFAReject, + self.exitTFAReject, + ['walk']), + # Trialer Force Acknowledge: + State.State('trialerFA', + self.enterTrialerFA, + self.exitTrialerFA, + ['trialerFAReject', 'DFA']), + State.State('trialerFAReject', + self.enterTrialerFAReject, + self.exitTrialerFAReject, + ['walk']), + # Download Force Acknowledge + State.State('DFA', + self.enterDFA, + self.exitDFA, + ['DFAReject', 'NPCFA', 'HFA']), + State.State('DFAReject', + self.enterDFAReject, + self.exitDFAReject, + ['walk']), + # NPC Force Acknowledge: + State.State('NPCFA', + self.enterNPCFA, + self.exitNPCFA, + ['NPCFAReject', 'HFA']), + State.State('NPCFAReject', + self.enterNPCFAReject, + self.exitNPCFAReject, + ['walk']), + # Health Force Acknowledge + State.State('HFA', + self.enterHFA, + self.exitHFA, + ['HFAReject', 'teleportOut', 'tunnelOut']), + State.State('HFAReject', + self.enterHFAReject, + self.exitHFAReject, + ['walk']), + State.State('deathAck', + self.enterDeathAck, + self.exitDeathAck, + ['teleportIn']), + State.State('teleportIn', + self.enterTeleportIn, + self.exitTeleportIn, + ['walk', 'popup']), + State.State('popup', + self.enterPopup, + self.exitPopup, + ['walk']), + State.State('teleportOut', + self.enterTeleportOut, + self.exitTeleportOut, + ['deathAck', 'teleportIn']), # 'final' + State.State('died', # No transitions to "died" in the playground. + self.enterDied, + self.exitDied, + ['final']), + State.State('tunnelIn', + self.enterTunnelIn, + self.exitTunnelIn, + ['walk']), + State.State('tunnelOut', + self.enterTunnelOut, + self.exitTunnelOut, + ['final']), + State.State('quest', + self.enterQuest, + self.exitQuest, + ['walk']), + State.State('purchase', + self.enterPurchase, + self.exitPurchase, + ['walk']), + State.State('stopped', + self.enterStopped, + self.exitStopped, + ['walk']), + State.State('fishing', + self.enterFishing, + self.exitFishing, + ['walk']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + + # Initial State + 'start', + # Final State + 'final', + ) + + self.parentFSM = parentFSM + self.tunnelOriginList = [] + self.trolleyDoneEvent = "trolleyDone" + self.hfaDoneEvent = "hfaDoneEvent" + self.npcfaDoneEvent = "npcfaDoneEvent" + self.dialog = None + self.deathAckBox = None + + def enter(self, requestStatus): + assert(self.notify.debug("enter(requestStatus="+str(requestStatus)+")")) + self.fsm.enterInitialState() + # Let the safe zone manager know that we are here. + messenger.send("enterPlayground") + self.accept("doorDoneEvent", self.handleDoorDoneEvent) + self.accept("DistributedDoor_doorTrigger", self.handleDoorTrigger) + # Play music + base.playMusic(self.loader.music, looping = 1, volume = 0.8) + + self.loader.geom.reparentTo(render) + + # Turn on the animated props once since there is only one zone + for i in self.loader.nodeList: + self.loader.enterAnimatedProps(i) + + # For halloween + def __lightDecorationOn__(): + geom = base.cr.playGame.hood.loader.geom + self.loader.hood.halloweenLights = geom.findAllMatches("**/*light*") + self.loader.hood.halloweenLights += geom.findAllMatches("**/*lamp*") + self.loader.hood.halloweenLights += geom.findAllMatches("**/prop_snow_tree*") + + for light in self.loader.hood.halloweenLights: + #light.reparentTo(render) + light.setColorScaleOff(0) + + newsManager = base.cr.newsManager + + if newsManager: + holidayIds = base.cr.newsManager.getDecorationHolidayId() + if (ToontownGlobals.HALLOWEEN_COSTUMES in holidayIds) and self.loader.hood.spookySkyFile: + + lightsOff = Sequence(LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(0.55, 0.55, 0.65, 1)), + Func(self.loader.hood.startSpookySky), + Func(__lightDecorationOn__), + ) + + lightsOff.start() + else: + # Turn the sky on + self.loader.hood.startSky() + lightsOn = LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(1, 1, 1, 1)) + lightsOn.start() + else: + # Turn the sky on + self.loader.hood.startSky() + lightsOn = LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(1, 1, 1, 1)) + lightsOn.start() + + # Turn on the little red arrows. + NametagGlobals.setMasterArrowsOn(1) + + self.zoneId = requestStatus["zoneId"] + + # Add hooks for the linktunnels + self.tunnelOriginList = base.cr.hoodMgr.addLinkTunnelHooks(self, self.loader.nodeList, self.zoneId) + + how=requestStatus["how"] + if how=="teleportIn": + how="deathAck" + self.fsm.request(how, [requestStatus]) + + def exit(self): + assert(self.notify.debug("exit()")) + self.ignoreAll() + # Let the safe zone manager know that we are leaving + messenger.send("exitPlayground") + + for node in self.tunnelOriginList: + node.removeNode() + del self.tunnelOriginList + + # remove the healing task + # taskName = base.localAvatar.taskName("healToon") + # taskMgr.remove(taskName) + self.loader.geom.reparentTo(hidden) + + # For halloween + def __lightDecorationOff__(): + for light in self.loader.hood.halloweenLights: + light.reparentTo(hidden) + + newsManager = base.cr.newsManager +## if newsManager: +## holidayIds = base.cr.newsManager.getDecorationHolidayId() +## if (ToontownGlobals.HALLOWEEN_COSTUMES in holidayIds) and self.loader.hood.spookySkyFile: +## __lightDecorationOff__() + +## # If the lights had been reparented to render then delete them +## for light in self.loader.hood.halloweenLights: +## light.removeNode() +## del light + + # Turn off the little red arrows. + NametagGlobals.setMasterArrowsOn(0) + + # Turn off the animated props once since there is only one zone + for i in self.loader.nodeList: + self.loader.exitAnimatedProps(i) + + # Turn the sky off + self.loader.hood.stopSky() + # Stop music + self.loader.music.stop() + + def load(self): + assert(self.notify.debug("load()")) + # Call up the chain + Place.Place.load(self) + self.parentFSM.getStateNamed("playground").addChild(self.fsm) + + def unload(self): + assert(self.notify.debug("unload()")) + + self.parentFSM.getStateNamed("playground").removeChild(self.fsm) + del self.parentFSM + del self.fsm + + # remove any dfa dialogs + if self.dialog: + self.dialog.cleanup() + self.dialog = None + if self.deathAckBox: + self.deathAckBox.cleanup() + self.deathAckBox = None + TTDialog.cleanupDialog("globalDialog") + self.ignoreAll() + + # Call up the chain + Place.Place.unload(self) + + if 0: + def handleDoorTrigger(self): + assert(self.notify.debug("handleDoorTrigger()")) + self.requestLeave({ + "how":"doorIn", + #"hoodId":self.hoodId, + #"zoneId":self.zoenId, + }) + + def showTreasurePoints(self, points): + # Reveals all the treasure points in the given point list (a + # list of 3-tuples) by putting a big number there. This is a + # handy tool for debugging the treasure points set up in + # *TreasurePlannerAI.py. + self.hideDebugPointText() + for i in range(len(points)): + p = points[i] + self.showDebugPointText(str(i), p) + + def showDropPoints(self, points): + # Reveals all of the drop points (where a toon enters the + # safezone) in the given point list (a list of 6-tuples, + # defined in HoodMgr.py). + self.hideDebugPointText() + for i in range(len(points)): + p = points[i] + self.showDebugPointText(str(i), p) + + def showPaths(self): + # To be overridden by derived classes to fill in the correct + # parameters for showPathPoints(). + pass + + def hidePaths(self): + # Undoes a previous call to showPaths(). + self.hideDebugPointText() + + def showPathPoints(self, paths, waypoints = None): + # Reveals all of the char path points (where the neighborhood + # char walks around the safezone) in the given paths and + # waypoints lists (defined in CCharPaths.py). + + self.hideDebugPointText() + lines = LineSegs() + lines.setColor(1, 0, 0, 1) + + from toontown.classicchars import CCharPaths + + for name, pointDef in paths.items(): + self.showDebugPointText(name, pointDef[0]) + + # Also draw the connecting lines. + for connectTo in pointDef[1]: + toDef = paths[connectTo] + fromP = pointDef[0] + toP = toDef[0] + lines.moveTo(fromP[0], fromP[1], fromP[2] + 2.0) + + wpList = CCharPaths.getWayPoints(name, connectTo, paths, waypoints) + for wp in wpList: + lines.drawTo(wp[0], wp[1], wp[2] + 2.0) + self.showDebugPointText('*', wp) + + lines.drawTo(toP[0], toP[1], toP[2] + 2.0) + + self.debugText.attachNewNode(lines.create()) + + def hideDebugPointText(self): + # Hides all text previously created by showDebugPointText(). + if hasattr(self, "debugText"): + children = self.debugText.getChildren() + for i in range(children.getNumPaths()): + children[i].removeNode() + + def showDebugPointText(self, text, point): + # Puts a little text object at the indicated point (defined by + # a 3-tuple) in the safezone, for debugging drop points, + # treasure points, char path points, etc. + if not hasattr(self, "debugText"): + self.debugText = self.loader.geom.attachNewNode('debugText') + self.debugTextNode = TextNode('debugTextNode') + self.debugTextNode.setTextColor(1, 0, 0, 1) + self.debugTextNode.setAlign(TextNode.ACenter) + self.debugTextNode.setFont(ToontownGlobals.getSignFont()) + + self.debugTextNode.setText(text) + np = self.debugText.attachNewNode(self.debugTextNode.generate()) + np.setPos(point[0], point[1], point[2]) + np.setScale(4.0) + np.setBillboardPointEye() + + # walk state inherited from Place.py + + # sticker book state inherited from Place.py + + # sit state inherited from Place.py + + # drive state inherited from Place.py + + # Trolley state + def enterTrolley(self): + assert(self.notify.debug("enterTrolley()")) + # Turn on the laff meter + base.localAvatar.laffMeter.start() + + # clear the anim state + base.localAvatar.b_setAnimState("off", 1) + + # Disable leave to pay / set parent password + base.localAvatar.cantLeaveGame = 1 + + self.accept(self.trolleyDoneEvent, self.handleTrolleyDone) + self.trolley = Trolley.Trolley(self, self.fsm, self.trolleyDoneEvent) + self.trolley.load() + self.trolley.enter() + + def exitTrolley(self): + assert(self.notify.debug("exitTrolley()")) + + # Turn off the laff meter + base.localAvatar.laffMeter.stop() + base.localAvatar.cantLeaveGame = 0 + + self.ignore(self.trolleyDoneEvent) + self.trolley.unload() + self.trolley.exit() + del self.trolley + + def detectedTrolleyCollision(self): + assert(self.notify.debug("detectedTrolleyCollision()")) + self.fsm.request("trolley") + + def handleTrolleyDone(self, doneStatus): + assert(self.notify.debug("handleTrolleyDone()")) + self.notify.debug("handling trolley done event") + mode = doneStatus["mode"] + if mode == "reject": + self.fsm.request("walk") + elif mode == "exit": + self.fsm.request("walk") + elif mode == "minigame": + self.doneStatus = {"loader" : "minigame", + "where" : "minigame", + "hoodId" : self.loader.hood.id, + "zoneId" : doneStatus["zoneId"], + "shardId" : None, + "minigameId" : doneStatus["minigameId"] + } + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + mode + " in handleTrolleyDone") + + # this is a debug function for starting up a minigame + # see MinigameDebug.py + def debugStartMinigame(self, zoneId, minigameId): + assert(self.notify.debug("debugStartMinigame()")) + self.doneStatus = {"loader" : "minigame", + "where" : "minigame", + "hoodId" : self.loader.hood.id, + "zoneId" : zoneId, + "shardId" : None, + "minigameId" : minigameId} + messenger.send(self.doneEvent) + + # tunnel dfa state functions inherited from Place.py + # tunnel dfa reject state inherited from Place.py + + def enterTFACallback(self, requestStatus, doneStatus): + assert(self.notify.debug("enterTFACallback()")) + self.tfa.exit() + del self.tfa + doneStatusMode = doneStatus["mode"] + if (doneStatusMode == "complete"): + self.requestLeave(requestStatus) + elif (doneStatusMode == "incomplete"): + self.fsm.request("TFAReject") + else: + self.notify.error("Unknown mode: %s" % doneStatusMode) + return + + def enterDFACallback(self, requestStatus, doneStatus): + """ + Download Force Acknowledge + This function overrides Place.py because from the safe zone we need to + check your health before letting you into the tunnel. So if the download + force acknowledge says we can go, request the HFA state next. + """ + assert(self.notify.debug("enterDFACallback()")) + self.dfa.exit() + del self.dfa + # Check the status from the dfa + # If the download force acknowledge tells us the download is complete, then + # we can enter the tunnel, otherwise for now we just stand there + ds = doneStatus['mode'] + if (ds == 'complete'): + # Allowed, check your quests + # We no longer need the NPCFA with the new tutorial + self.fsm.request("NPCFA", [requestStatus]) + # Skip straight to the HFA + #self.fsm.request("HFA", [requestStatus]) + # Rejected + elif (ds == 'incomplete'): + self.fsm.request("DFAReject") + else: + # Some return code that is not handled + self.notify.error("Unknown done status for DownloadForceAcknowledge: " + + `doneStatus`) + + # door state inherited from Place.py + + # HFA state + + def enterHFA(self, requestStatus): + """Hit Point Force Acknowledge""" + assert(self.notify.debug("enterHFA()")) + self.acceptOnce(self.hfaDoneEvent, self.enterHFACallback, [requestStatus]) + # Make sure we have enough HP to leave the safe zone + # This enforces the so-called time out penalty + self.hfa = HealthForceAcknowledge.HealthForceAcknowledge(self.hfaDoneEvent) + self.hfa.enter(1) + + def exitHFA(self): + assert(self.notify.debug("exitHFA()")) + self.ignore(self.hfaDoneEvent) + + def enterHFACallback(self, requestStatus, doneStatus): + assert(self.notify.debug("enterHFACallback()")) + self.hfa.exit() + del self.hfa + # Check the status from the hfa + # If the health force acknowledge tells us we're healthy, then + # we can enter the tunnel, otherwise for now we just stand there + if (doneStatus["mode"] == "complete"): + # Allowed, do the tunnel transition + # Check to see if this is a partyHat transition. You enter a party + # hat tunnel but you teleport in to the party grounds + if requestStatus.get( "partyHat", 0 ): + outHow = {"teleportIn":"tunnelOut" } + else: + outHow={"teleportIn":"teleportOut", "tunnelIn":"tunnelOut", "doorIn":"doorOut"} + self.fsm.request(outHow[requestStatus["how"]], [requestStatus]) + # Rejected + elif (doneStatus["mode"] == 'incomplete'): + self.fsm.request("HFAReject") + else: + # Some return code that is not handled + self.notify.error("Unknown done status for HealthForceAcknowledge: " + + `doneStatus`) + + # hfa reject state + + def enterHFAReject(self): + assert(self.notify.debug("enterHFAReject()")) + # TODO: reject movie, turn toon around + self.fsm.request("walk") + + def exitHFAReject(self): + assert(self.notify.debug("exitHFAReject()")) + + + # NPCFA state + + def enterNPCFA(self, requestStatus): + """NPC Force Acknowledge""" + assert(self.notify.debug("enterNPCFA()")) + + self.acceptOnce(self.npcfaDoneEvent, self.enterNPCFACallback, [requestStatus]) + # Make sure we have enough HP to leave the safe zone + # This enforces the so-called time out penalty + self.npcfa = NPCForceAcknowledge.NPCForceAcknowledge(self.npcfaDoneEvent) + self.npcfa.enter() + + def exitNPCFA(self): + assert(self.notify.debug("exitNPCFA()")) + self.ignore(self.npcfaDoneEvent) + + def enterNPCFACallback(self, requestStatus, doneStatus): + assert(self.notify.debug("enterNPCFACallback()")) + self.npcfa.exit() + del self.npcfa + # Check the status from the fda + # If the download force acknowledge tells us the download is complete, then + # we can enter the tunnel, otherwise for now we just stand there + # Allowed, do the tunnel transition + if (doneStatus["mode"] == "complete"): + # Allowed, check your health + self.fsm.request("HFA", [requestStatus]) + # Rejected + elif (doneStatus["mode"] == 'incomplete'): + self.fsm.request("NPCFAReject") + else: + # Some return code that is not handled + self.notify.error("Unknown done status for NPCForceAcknowledge: " + + `doneStatus`) + + # npca reject state + + def enterNPCFAReject(self): + assert(self.notify.debug("enterNPCFAReject()")) + # TODO: reject movie, turn toon around + self.fsm.request("walk") + + def exitNPCFAReject(self): + assert(self.notify.debug("exitNPCFAReject()")) + + + def enterWalk(self, teleportIn=0): + if self.deathAckBox: + self.ignore("deathAck") + self.deathAckBox.cleanup() + self.deathAckBox = None + + Place.Place.enterWalk(self, teleportIn) + + # deathAck state + + def enterDeathAck(self, requestStatus): + assert(self.notify.debug("enterDeathAck()")) + self.deathAckBox = None + # If you are dead, let the player know. + #if base.localAvatar.hp < 1: + # self.accept("deathAck", self.__handleDeathAck, + # extraArgs=[requestStatus]) + # self.deathAckBox = DeathForceAcknowledge.DeathForceAcknowledge( + # doneEvent = "deathAck") + #else: + # self.fsm.request("teleportIn", [requestStatus]) + self.fsm.request("teleportIn", [requestStatus]) + + #def __handleDeathAck(self, requestStatus): + # assert(self.notify.debug("__handleDeathAck()")) + # self.fsm.request("teleportIn", [requestStatus]) + + def exitDeathAck(self): + assert(self.notify.debug("exitDeathAck()")) + if self.deathAckBox: + self.ignore("deathAck") + self.deathAckBox.cleanup() + self.deathAckBox = None + + # teleport in state + + def enterTeleportIn(self, requestStatus): + assert(self.notify.debug("enterTeleportIn()")) + imgScale = 0.25 + + # see if someone else is already showing a dialog + if self.dialog: + x,y,z,h,p,r = base.cr.hoodMgr.getPlaygroundCenterFromId(self.loader.hood.id) + + # See if we're sad + elif (base.localAvatar.hp < 1): + requestStatus['nextState'] = 'popup' + x,y,z,h,p,r = base.cr.hoodMgr.getPlaygroundCenterFromId(self.loader.hood.id) + self.accept("deathAck", self.__handleDeathAck, extraArgs=[requestStatus]) + self.deathAckBox = DeathForceAcknowledge.DeathForceAcknowledge(doneEvent = "deathAck") + + # Check to see if the toon has a tier zero quest + elif ((base.localAvatar.hp > 0) and + ((Quests.avatarHasTrolleyQuest(base.localAvatar)) or + (Quests.avatarHasFirstCogQuest(base.localAvatar)) or + (Quests.avatarHasFriendQuest(base.localAvatar)) or + ((Quests.avatarHasPhoneQuest(base.localAvatar)) and + (Quests.avatarHasCompletedPhoneQuest(base.localAvatar)))) and + (self.loader.hood.id == ToontownGlobals.ToontownCentral)): + requestStatus['nextState'] = 'popup' + imageModel = loader.loadModel("phase_4/models/gui/tfa_images") + # trolley quest + if (base.localAvatar.quests[0][0] == Quests.TROLLEY_QUEST_ID): + if not Quests.avatarHasCompletedTrolleyQuest(base.localAvatar): + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralInitialDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage3 + imgNodePath = imageModel.find("**/trolley-dialog-image") + imgPos = (0, 0, 0.04) + imgScale = 0.5 + else: + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralHQDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage4 + imgNodePath = imageModel.find("**/hq-dialog-image") + imgPos = (0, 0, -0.02) + imgScale = 0.5 + # first cog quest + elif (base.localAvatar.quests[0][0] == Quests.FIRST_COG_QUEST_ID): + if not Quests.avatarHasCompletedFirstCogQuest(base.localAvatar): + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralTunnelDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage5 + imgNodePath = imageModel.find("**/tunnelSignA") + imgPos = (0, 0, 0.04) + imgScale = 0.5 + else: + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralHQDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage6 + imgNodePath = imageModel.find("**/hq-dialog-image") + imgPos = (0, 0, 0.05) + imgScale = 0.5 + # make a friend quest + elif (base.localAvatar.quests[0][0] == Quests.FRIEND_QUEST_ID): + if not Quests.avatarHasCompletedFriendQuest(base.localAvatar): + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralInitialDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage7 + gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + imgNodePath = gui.find("**/FriendsBox_Closed") + imgPos = (0, 0, 0.04) + imgScale = 1.0 + gui.removeNode() + else: + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralHQDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage8 + imgNodePath = imageModel.find("**/hq-dialog-image") + imgPos = (0, 0, 0.05) + imgScale = 0.5 + # phone quest + elif (base.localAvatar.quests[0][0] == Quests.PHONE_QUEST_ID): + if Quests.avatarHasCompletedPhoneQuest(base.localAvatar): + x,y,z,h,p,r = base.cr.hoodMgr.getDropPoint( + base.cr.hoodMgr.ToontownCentralHQDropPoints) + msg = TTLocalizer.NPCForceAcknowledgeMessage9 + imgNodePath = imageModel.find("**/hq-dialog-image") + imgPos = (0, 0, 0.05) + imgScale = 0.5 + + self.dialog = TTDialog.TTDialog( + text = msg, + command = self.__cleanupDialog, + style = TTDialog.Acknowledge) + imgLabel = DirectLabel.DirectLabel( + parent = self.dialog, + relief = None, + pos = imgPos, + scale = TTLocalizer.PimgLabel, + image = imgNodePath, + image_scale = imgScale) + imageModel.removeNode() + else: + # ...this toon has completed their trolley quest. + # Choose a random location within the safezone to drop you. + # We do this even if we plan to be teleporting to a toon, + # because the gotoToon option may fail if the toon has moved + # on. + requestStatus['nextState'] = 'walk' + x,y,z,h,p,r = base.cr.hoodMgr.getPlaygroundCenterFromId( + self.loader.hood.id) + + # toon may not be parented to hidden at this point, if say the boat or piano + # on-floor event has detected an intersection (which seems to occur when Toon + # who lost a connection in battle re-enters in melodyland). In that case, + # it would be parented to the moving platform, and for that case the coords below + # are wrong, so before doing a setPos, reparent to hidden. + base.localAvatar.detachNode() + base.localAvatar.setPosHpr(render, x,y,z,h,p,r) + + Place.Place.enterTeleportIn(self, requestStatus) + + def __cleanupDialog(self, value): + if (self.dialog): + self.dialog.cleanup() + self.dialog = None + if hasattr(self, "fsm"): + self.fsm.request('walk', [1]) + + def __handleDeathAck(self, requestStatus): + assert(self.notify.debug("__handleDeathAck()")) + if self.deathAckBox: + self.ignore("deathAck") + self.deathAckBox.cleanup() + self.deathAckBox = None + self.fsm.request('walk', [1]) + + # popup state + + def enterPopup(self, teleportIn=0): + if (base.localAvatar.hp < 1): + base.localAvatar.b_setAnimState("Sad", 1) + else: + base.localAvatar.b_setAnimState("neutral", 1.0) + self.accept("teleportQuery", self.handleTeleportQuery) + base.localAvatar.setTeleportAvailable(1) + + # Spawn the task that checks to see if toon has fallen asleep + base.localAvatar.startSleepWatch(self.__handleFallingAsleepPopup) + + def exitPopup(self): + base.localAvatar.stopSleepWatch() + base.localAvatar.setTeleportAvailable(0) + self.ignore("teleportQuery") + + def __handleFallingAsleepPopup(self, task): + # Go to walk mode if we fall asleep with a popup up. + if hasattr(self, "fsm"): + self.fsm.request("walk") + base.localAvatar.forceGotoSleep() + return Task.done + + # teleport out state + + def enterTeleportOut(self, requestStatus): + assert(self.notify.debug("enterTeleportOut()")) + Place.Place.enterTeleportOut(self, requestStatus, + self.__teleportOutDone) + + def __teleportOutDone(self, requestStatus): + assert(self.notify.debug("__teleportOutDone()")) + # If we're teleporting from a safezone, we need to set the + # activityFsm to the final state + if (hasattr(self, 'activityFsm')): + self.activityFsm.requestFinalState() + hoodId = requestStatus["hoodId"] + zoneId = requestStatus["zoneId"] + avId = requestStatus["avId"] + shardId = requestStatus["shardId"] + if ((hoodId == self.loader.hood.hoodId) and (zoneId == self.loader.hood.hoodId) and (shardId == None)): + # If you are teleporting to somebody in this safezone + # We do not even need to set our zone because it is the same + self.fsm.request("deathAck", [requestStatus]) + elif (hoodId == ToontownGlobals.MyEstate): + self.getEstateZoneAndGoHome(requestStatus) + else: + # Different hood or zone, exit the safe zone + self.doneStatus = requestStatus + messenger.send(self.doneEvent) + + def exitTeleportOut(self): + assert(self.notify.debug("exitTeleportOut()")) + Place.Place.exitTeleportOut(self) + + + # tunnel in state inherited from Place.py + # tunnel out state inherited from Place.py + + + def createPlayground(self, dnaFile): + assert(self.notify.debug("createPlayground()")) + # Load the safe zone specific models and textures + loader.loadDNAFile(self.loader.dnaStore, self.safeZoneStorageDNAFile) + # Load the actual safe zone dna + node = loader.loadDNAFile(self.loader.dnaStore, dnaFile) + + if node.getNumParents() == 1: + # If the node already has a parent arc when it's loaded, we must + # be using the level editor and we want to preserve that arc. + self.geom = NodePath(node.getParent(0)) + self.geom.reparentTo(hidden) + else: + # Otherwise, we should create a new arc for the node. + self.geom = hidden.attachNewNode(node) + # Make the vis dictionaries + self.makeDictionaries(self.loader.dnaStore) + # Add hooks for the linktunnels + self.tunnelOriginList = base.cr.hoodMgr.addLinkTunnelHooks(self, self.nodeList, self.zoneId) + # Flatten the safe zone + self.geom.flattenMedium() + # Preload all textures in neighborhood + gsg = base.win.getGsg() + if gsg: + self.geom.prepareScene(gsg) + + def makeDictionaries(self, dnaStore): + assert(self.notify.debug("makeDictionaries()")) + # A list of all visible nodes + self.nodeList = [] + # There should only be one vis group + for i in range(dnaStore.getNumDNAVisGroups()): + groupFullName = dnaStore.getDNAVisGroupName(i) + groupName = base.cr.hoodMgr.extractGroupName(groupFullName) + groupNode = self.geom.find("**/" + groupFullName) + if groupNode.isEmpty(): + self.notify.error("Could not find visgroup") + self.nodeList.append(groupNode) + self.removeLandmarkBlockNodes() + # Now that we have extracted the vis groups we do not need + # the dnaStore to keep them around + # Remove all references to the safezone specific models and textures + self.loader.dnaStore.resetPlaceNodes() + self.loader.dnaStore.resetDNAGroups() + self.loader.dnaStore.resetDNAVisGroups() + self.loader.dnaStore.resetDNAVisGroupsAI() + + def removeLandmarkBlockNodes(self): + """ + Since we are in the safe zone we do not need the suit_building_origins + """ + assert(self.notify.debug("removeLandmarkBlockNodes()")) + npc = self.geom.findAllMatches("**/suit_building_origin") + for i in range(npc.getNumPaths()): + npc.getPath(i).removeNode() + + def enterTFA(self, requestStatus): + assert(self.notify.debug("enterTFA()")) + self.acceptOnce(self.tfaDoneEvent, self.enterTFACallback, + [requestStatus]) + self.tfa = TutorialForceAcknowledge.TutorialForceAcknowledge( + self.tfaDoneEvent) + self.tfa.enter() + + def exitTFA(self): + assert(self.notify.debug("exitTFA()")) + self.ignore(self.tfaDoneEvent) + + def enterTFAReject(self): + assert(self.notify.debug("enterTFAReject()")) + self.fsm.request("walk") + + def exitTFAReject(self): + assert(self.notify.debug("exitTFAReject()")) + + diff --git a/toontown/src/safezone/PublicWalk.py b/toontown/src/safezone/PublicWalk.py new file mode 100644 index 0000000..d85682d --- /dev/null +++ b/toontown/src/safezone/PublicWalk.py @@ -0,0 +1,89 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.directnotify import DirectNotifyGlobal +import Walk + +class PublicWalk(Walk.Walk): + """ + Walking around in public places. Turns on a lot of interface stuff + that plain old walk doesn't bother with. + """ + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("PublicWalk") + + + def __init__(self, parentFSM, doneEvent): + """ + doneEvent is a string. + PublicWalk state constructor + """ + # Call up the chain + Walk.Walk.__init__(self, doneEvent) + + # We'll need this later + self.parentFSM = parentFSM + + def load(self): + # Call up the chain + Walk.Walk.load(self) + + def unload(self): + # Call up the chain + Walk.Walk.unload(self) + del self.parentFSM + + def enter(self, slowWalk = 0): + # Call up the chain + Walk.Walk.enter(self, slowWalk) + + # The shticker book and associated events + base.localAvatar.book.showButton() + self.accept(StickerBookHotkey, self.__handleStickerBookEntry) + self.accept("enterStickerBook", self.__handleStickerBookEntry) + self.accept(OptionsPageHotkey, self.__handleOptionsEntry) + + # The laffMeter + base.localAvatar.laffMeter.start() + base.localAvatar.beginAllowPies() + + def exit(self): + # Call up the chain + Walk.Walk.exit(self) + + # Put away the book + base.localAvatar.book.hideButton() + self.ignore(StickerBookHotkey) + self.ignore("enterStickerBook") + self.ignore(OptionsPageHotkey) + + # Put away the laff meter + base.localAvatar.laffMeter.stop() + base.localAvatar.endAllowPies() + + def __handleStickerBookEntry(self): + # Don't open sticker book if we're jumping + currentState = base.localAvatar.animFSM.getCurrentState().getName() + if currentState == 'jumpAirborne': + return + + if base.localAvatar.book.isObscured(): + return + else: + doneStatus = {} + doneStatus['mode'] = 'StickerBook' + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handleOptionsEntry(self): + # Don't open the options page if we are jumping + currentState = base.localAvatar.animFSM.getCurrentState().getName() + if currentState == 'jumpAirborne': + return + + if base.localAvatar.book.isObscured(): + return + else: + doneStatus = {} + doneStatus['mode'] = 'Options' + messenger.send(self.doneEvent, [doneStatus]) + return diff --git a/toontown/src/safezone/RegenTreasurePlannerAI.py b/toontown/src/safezone/RegenTreasurePlannerAI.py new file mode 100644 index 0000000..3cbd352 --- /dev/null +++ b/toontown/src/safezone/RegenTreasurePlannerAI.py @@ -0,0 +1,56 @@ +from direct.distributed.ClockDelta import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from direct.task import Task +import random +import TreasurePlannerAI + +class RegenTreasurePlannerAI(TreasurePlannerAI.TreasurePlannerAI): + notify = DirectNotifyGlobal.directNotify.newCategory( + "RegenTreasurePlannerAI") + + def __init__(self, zoneId, treasureConstructor, taskName, + spawnInterval, maxTreasures, callback=None): + + TreasurePlannerAI.TreasurePlannerAI.__init__(self, zoneId, + treasureConstructor, + callback) + + # will spawn a task that creates a treasure every + # spawnInterval seconds unless the max has been reached. + self.taskName = "%s-%s" % (taskName, zoneId) + self.spawnInterval = spawnInterval + self.maxTreasures = maxTreasures + + def start(self): + self.preSpawnTreasures() + self.startSpawning() + + def stop(self): + self.stopSpawning() + + def stopSpawning(self): + taskMgr.remove(self.taskName) + + def startSpawning(self): + self.stopSpawning() + taskMgr.doMethodLater(self.spawnInterval, self.upkeepTreasurePopulation, self.taskName) + + def upkeepTreasurePopulation(self, task): + if self.numTreasures() < self.maxTreasures: + self.placeRandomTreasure() + taskMgr.doMethodLater(self.spawnInterval, self.upkeepTreasurePopulation, self.taskName) + return Task.done + + def placeRandomTreasure(self): + self.notify.debug('Placing a Treasure...') + # Pick a random index from the empty indexes that are available. + # Probably blows up if there aren't any available. + spawnPointIndex = self.nthEmptyIndex( + random.randrange(self.countEmptySpawnPoints())) + + self.placeTreasure(spawnPointIndex) + + def preSpawnTreasures(self): + for i in range(self.maxTreasures): + self.placeRandomTreasure() diff --git a/toontown/src/safezone/SafeZoneLoader.py b/toontown/src/safezone/SafeZoneLoader.py new file mode 100644 index 0000000..c9ea3d5 --- /dev/null +++ b/toontown/src/safezone/SafeZoneLoader.py @@ -0,0 +1,360 @@ +"""SafeZoneLoader module: contains the SafeZoneLoader class""" + +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +from toontown.distributed.ToontownMsgTypes import * +from toontown.hood import ZoneUtil +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import Place +from direct.showbase import DirectObject +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +from toontown.launcher import DownloadForceAcknowledge +from toontown.toon import HealthForceAcknowledge +from toontown.tutorial import TutorialForceAcknowledge +from toontown.toonbase.ToontownGlobals import * +from toontown.building import ToonInterior +from toontown.hood import QuietZoneState + +class SafeZoneLoader(StateData.StateData): + """SafeZoneLoader class""" + + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("SafeZoneLoader") + + def __init__(self, hood, parentFSMState, doneEvent): + """ + SafeZoneLoader constructor: + """ + assert(self.notify.debug("__init__(hood="+str(hood) + +", parentFSMState="+str(parentFSMState) + +", doneEvent="+str(doneEvent)+")")) + StateData.StateData.__init__(self, doneEvent) + self.hood = hood + self.parentFSMState = parentFSMState + self.fsm = ClassicFSM.ClassicFSM('SafeZoneLoader', + [State.State('start', + self.enterStart, + self.exitStart, + ['quietZone', + 'playground', + 'toonInterior']), + State.State('playground', + self.enterPlayground, + self.exitPlayground, + ['quietZone']), + State.State('toonInterior', + self.enterToonInterior, + self.exitToonInterior, + ['quietZone']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['playground', 'toonInterior']), + State.State('golfcourse', #REMOVE THIS STATE + self.enterGolfcourse, + self.exitGolfcourse, + ['quietZone', 'playground']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', + ) + self.placeDoneEvent = "placeDone" + self.place=None + self.playgroundClass = None + + def load(self): + assert(self.notify.debug("load()")) + self.music = base.loadMusic(self.musicFile) + self.activityMusic = base.loadMusic(self.activityMusicFile) + self.createSafeZone(self.dnaFile) + self.parentFSMState.addChild(self.fsm) + + def unload(self): + assert(self.notify.debug("unload()")) + self.parentFSMState.removeChild(self.fsm) + del self.parentFSMState + self.geom.removeNode() + del self.geom + del self.fsm + del self.hood + del self.nodeList + del self.playgroundClass + del self.music + del self.activityMusic + del self.holidayPropTransforms + self.deleteAnimatedProps() + self.ignoreAll() + # Get rid of any references to models or textures from this safe zone + ModelPool.garbageCollect() + TexturePool.garbageCollect() + + def enter(self, requestStatus): + assert(self.notify.debug("enter(requestStatus="+str(requestStatus)+")")) + self.fsm.enterInitialState() + # Let the safe zone manager know that we are here. + messenger.send("enterSafeZone") + self.setState(requestStatus["where"], requestStatus) + + def exit(self): + assert(self.notify.debug("exit()")) + # Let the safe zone manager know that we are leaving + messenger.send("exitSafeZone") + + def setState(self, stateName, requestStatus): + assert(self.notify.debug("setState(stateName=" + +str(stateName)+", requestStatus="+str(requestStatus)+")")) + self.fsm.request(stateName, [requestStatus]) + + def createSafeZone(self, dnaFile): + assert(self.notify.debug("createSafeZone()")) + # Load the safe zone specific models and textures + # The estate has no safeZoneStorageDNAFile + if self.safeZoneStorageDNAFile: + loader.loadDNAFile(self.hood.dnaStore, self.safeZoneStorageDNAFile) + # Load the actual safe zone dna + node = loader.loadDNAFile(self.hood.dnaStore, dnaFile) + + if node.getNumParents() == 1: + # If the node already has a parent arc when it's loaded, we must + # be using the level editor and we want to preserve that arc. + self.geom = NodePath(node.getParent(0)) + self.geom.reparentTo(hidden) + else: + # Otherwise, we should create a new arc for the node. + self.geom = hidden.attachNewNode(node) + # Make the vis dictionaries + self.makeDictionaries(self.hood.dnaStore) + self.createAnimatedProps(self.nodeList) + # Record position of all holiday props before everything is flattened + self.holidayPropTransforms = {} + npl = self.geom.findAllMatches('**/=DNARoot=holiday_prop') + for i in range(npl.getNumPaths()): + np = npl.getPath(i) + np.setTag('transformIndex', `i`) + self.holidayPropTransforms[i] = np.getNetTransform() + # Flatten the safe zone + self.geom.flattenMedium() + # Preload all textures in neighborhood + gsg = base.win.getGsg() + if gsg: + self.geom.prepareScene(gsg) + + def makeDictionaries(self, dnaStore): + assert(self.notify.debug("makeDictionaries()")) + # A list of all visible nodes + self.nodeList = [] + # There should only be one vis group + for i in range(dnaStore.getNumDNAVisGroups()): + groupFullName = dnaStore.getDNAVisGroupName(i) + groupName = base.cr.hoodMgr.extractGroupName(groupFullName) + groupNode = self.geom.find("**/" + groupFullName) + if groupNode.isEmpty(): + self.notify.error("Could not find visgroup") + self.nodeList.append(groupNode) + self.removeLandmarkBlockNodes() + # Now that we have extracted the vis groups we do not need + # the dnaStore to keep them around + # Remove all references to the safezone specific models and textures + self.hood.dnaStore.resetPlaceNodes() + self.hood.dnaStore.resetDNAGroups() + self.hood.dnaStore.resetDNAVisGroups() + self.hood.dnaStore.resetDNAVisGroupsAI() + + def removeLandmarkBlockNodes(self): + """ + Since we are in the safe zone we do not need the suit_building_origins + """ + assert(self.notify.debug("removeLandmarkBlockNodes()")) + npc = self.geom.findAllMatches("**/suit_building_origin") + for i in range(npc.getNumPaths()): + npc.getPath(i).removeNode() + + # start state + + def enterStart(self): + assert(self.notify.debug("enterStart()")) + + def exitStart(self): + assert(self.notify.debug("exitStart()")) + + # playground state + + def enterPlayground(self, requestStatus): + assert(self.notify.debug("enterPlayground(requestStatus=" + +str(requestStatus)+")")) + self.acceptOnce(self.placeDoneEvent, self.handlePlaygroundDone) + self.place=self.playgroundClass(self, self.fsm, self.placeDoneEvent) + self.place.load() + self.place.enter(requestStatus) + #self.hood.place = self.place + base.cr.playGame.setPlace(self.place) + + def exitPlayground(self): + assert(self.notify.debug("exitPlayground()")) + self.ignore(self.placeDoneEvent) + self.place.exit() + self.place.unload() + self.place=None + #self.hood.place = None + base.cr.playGame.setPlace(self.place) + + def handlePlaygroundDone(self): + assert(self.notify.debug("handlePlaygroundDone()")) + status=self.place.doneStatus + if (ZoneUtil.getBranchZone(status["zoneId"]) == self.hood.hoodId and + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send(self.doneEvent) + + # toonInterior state + + def enterToonInterior(self, requestStatus): + assert(self.notify.debug("enterToonInterior(requestStatus=" + +str(requestStatus)+")")) + self.acceptOnce(self.placeDoneEvent, self.handleToonInteriorDone) + self.place=ToonInterior.ToonInterior(self, + self.fsm.getStateNamed("toonInterior"), self.placeDoneEvent) + base.cr.playGame.setPlace(self.place) + self.place.load() + self.place.enter(requestStatus) + + def exitToonInterior(self): + assert(self.notify.debug("exitToonInterior()")) + self.ignore(self.placeDoneEvent) + self.place.exit() + self.place.unload() + self.place=None + base.cr.playGame.setPlace(self.place) + + def handleToonInteriorDone(self): + assert(self.notify.debug("handleToonInteriorDone()")) + status=self.place.doneStatus + if (ZoneUtil.getBranchZone(status["zoneId"]) == self.hood.hoodId and + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send(self.doneEvent) + + # quietZone state + + def enterQuietZone(self, requestStatus): + assert(self.notify.debug("enterQuietZone()")) + self.quietZoneDoneEvent = "quietZoneDone" + self.acceptOnce(self.quietZoneDoneEvent, self.handleQuietZoneDone) + self.quietZoneStateData = QuietZoneState.QuietZoneState( + self.quietZoneDoneEvent) + self.quietZoneStateData.load() + self.quietZoneStateData.enter(requestStatus) + + def exitQuietZone(self): + assert(self.notify.debug("exitQuietZone()")) + self.ignore(self.quietZoneDoneEvent) + del self.quietZoneDoneEvent + self.quietZoneStateData.exit() + self.quietZoneStateData.unload() + self.quietZoneStateData=None + + def handleQuietZoneDone(self): + assert(self.notify.debug("handleQuietZoneDone()\n base.cr.handlerArgs=" + +str(base.cr.handlerArgs))) + # Change to the destination state: + status=self.quietZoneStateData.getRequestStatus() + if (status["where"] == "estate"): + self.doneStatus = status + messenger.send(self.doneEvent) + else: + self.fsm.request(status["where"], [status]) + + + # final state + + def enterFinal(self): + assert(self.notify.debug("enterFinal()")) + + def exitFinal(self): + assert(self.notify.debug("exitFinal()")) + + + def createAnimatedProps(self, nodeList): + assert(self.notify.debug("createAnimatedProps()")) + self.animPropDict = {} + for i in nodeList: + # Get all the anim in the vis group + animPropNodes = i.findAllMatches("**/animated_prop_*") + numAnimPropNodes = animPropNodes.getNumPaths() + for j in range(numAnimPropNodes): + animPropNode = animPropNodes.getPath(j) + + if animPropNode.getName().startswith('animated_prop_generic'): + className = 'GenericAnimatedProp' + else: + # The node name should be "animated_prop_ClassName_DNARoot" + # So strip off the first and last junk to get the ClassName + className = animPropNode.getName()[14:-8] + + symbols = {} + base.cr.importModule(symbols, 'toontown.hood', [className]) + + classObj = getattr(symbols[className], className) + animPropObj = classObj(animPropNode) + animPropList = self.animPropDict.setdefault(i, []) + animPropList.append(animPropObj) + + interactivePropNodes = i.findAllMatches("**/interactive_prop_*") + numInteractivePropNodes = interactivePropNodes.getNumPaths() + for j in range(numInteractivePropNodes): + interactivePropNode = interactivePropNodes.getPath(j) + className = 'GenericAnimatedProp' + + symbols = {} + base.cr.importModule(symbols, 'toontown.hood', [className]) + + classObj = getattr(symbols[className], className) + interactivePropObj = classObj(interactivePropNode) + # [gjeon] I think we can use animPropList to store interactive props + animPropList = self.animPropDict.get(i) + if animPropList is None: + animPropList = self.animPropDict.setdefault(i, []) + animPropList.append(interactivePropObj) + + def deleteAnimatedProps(self): + for zoneNode, animPropList in self.animPropDict.items(): + for animProp in animPropList: + animProp.delete() + del self.animPropDict + + def enterAnimatedProps(self, zoneNode): + for animProp in self.animPropDict.get(zoneNode, ()): + animProp.enter() + + def exitAnimatedProps(self, zoneNode): + for animProp in self.animPropDict.get(zoneNode, ()): + animProp.exit() + + def enterGolfcourse( self, requestStatus ): + """ + """ + #import pdb; pdb.set_trace() + # Racetrack will grab this off of us + #self.trackId = requestStatus[ 'trackId' ] + #self.accept("golfOver",self.handleRaceOver) + #self.accept("leavingGolf",self.handleLeftRace) + + base.transitions.fadeOut(t=0) + + def exitGolfcourse( self ): + """ + """ + pass + #del self.golfId diff --git a/toontown/src/safezone/SafeZoneManager.py b/toontown/src/safezone/SafeZoneManager.py new file mode 100644 index 0000000..88b0219 --- /dev/null +++ b/toontown/src/safezone/SafeZoneManager.py @@ -0,0 +1,26 @@ +from pandac.PandaModules import * +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal + +class SafeZoneManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("SafeZoneManager") + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + def generate(self): + DistributedObject.DistributedObject.generate(self) + self.accept("enterSafeZone", self.d_enterSafeZone) + self.accept("exitSafeZone", self.d_exitSafeZone) + + def disable(self): + self.ignoreAll() + DistributedObject.DistributedObject.disable(self) + + def d_enterSafeZone(self): + self.sendUpdate("enterSafeZone", []) + + def d_exitSafeZone(self): + self.sendUpdate("exitSafeZone", []) + diff --git a/toontown/src/safezone/SafeZoneManagerAI.py b/toontown/src/safezone/SafeZoneManagerAI.py new file mode 100644 index 0000000..a10c2d5 --- /dev/null +++ b/toontown/src/safezone/SafeZoneManagerAI.py @@ -0,0 +1,43 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal + +class SafeZoneManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("SafeZoneManagerAI") + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + # Number of seconds between spontaneous heals + self.healFrequency = 30 # seconds + + def enterSafeZone(self): + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists. + if self.air.doId2do.has_key(avId): + # Find the avatar + av = self.air.doId2do[avId] + # Start healing them + av.startToonUp(self.healFrequency) + + else: + self.notify.warning( + "Toon " + + str(avId) + + " isn't here, but just entered the safe zone. " + + "I will ignore this." + ) + # Send the "avatar escaped to safezone" message, just in case + # there are any battles going on that involve this avatar. + event = "inSafezone-%s" % (avId) + messenger.send(event) + + def exitSafeZone(self): + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists. + if self.air.doId2do.has_key(avId): + # Find the avatar + av = self.air.doId2do[avId] + # Start healing them + av.stopToonUp() + diff --git a/toontown/src/safezone/Sources.pp b/toontown/src/safezone/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/safezone/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/safezone/TTPlayground.py b/toontown/src/safezone/TTPlayground.py new file mode 100644 index 0000000..c8e10f0 --- /dev/null +++ b/toontown/src/safezone/TTPlayground.py @@ -0,0 +1,68 @@ +from pandac.PandaModules import * + +from toontown.toonbase import ToontownGlobals +import Playground +import random +from toontown.launcher import DownloadForceAcknowledge +from direct.task.Task import Task +from toontown.hood import ZoneUtil + +class TTPlayground(Playground.Playground): + def __init__(self, loader, parentFSM, doneEvent): + Playground.Playground.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Playground.Playground.load(self) + + def unload(self): + Playground.Playground.unload(self) + + def enter(self, requestStatus): + Playground.Playground.enter(self, requestStatus) + taskMgr.doMethodLater(1, self.__birds, 'TT-birds') + + def exit(self): + Playground.Playground.exit(self) + taskMgr.remove('TT-birds') + + def __birds(self, task): + base.playSfx(random.choice(self.loader.birdSound)) + t = (random.random() * 20.0) + 1 + taskMgr.doMethodLater(t, self.__birds, 'TT-birds') + return Task.done + + def doRequestLeave(self, requestStatus): + # when it's time to leave, check their trialer status first + self.fsm.request('trialerFA', [requestStatus]) + + def enterDFA(self, requestStatus): + """ + Override the base class because here we specifically ask for + phase 5, the toontown central streets. + - NEW: we can now go home. Check the hood before assuming we + are going to the streets + """ + doneEvent = "dfaDoneEvent" + self.accept(doneEvent, self.enterDFACallback, [requestStatus]) + self.dfa = DownloadForceAcknowledge.DownloadForceAcknowledge(doneEvent) + hood = ZoneUtil.getCanonicalZoneId(requestStatus['hoodId']) + if hood == ToontownGlobals.MyEstate: + # Ask if we can enter phase 5.5 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.MyEstate)) + elif hood == ToontownGlobals.GoofySpeedway: + # Ask if we can enter phase 6 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.GoofySpeedway)) + elif hood == ToontownGlobals.PartyHood: + # ask if we can enter phase 13 + self.dfa.enter(base.cr.hoodMgr.getPhaseFromHood(ToontownGlobals.PartyHood)) + else: + # Ask if we can enter phase 5 + self.dfa.enter(5) + + + def showPaths(self): + # Overridden from Playground to fill in the correct parameters + # for showPathPoints(). + from toontown.classicchars import CCharPaths + from toontown.toonbase import TTLocalizer + self.showPathPoints(CCharPaths.getPaths(TTLocalizer.Mickey)) diff --git a/toontown/src/safezone/TTSafeZoneLoader.py b/toontown/src/safezone/TTSafeZoneLoader.py new file mode 100644 index 0000000..36545f0 --- /dev/null +++ b/toontown/src/safezone/TTSafeZoneLoader.py @@ -0,0 +1,34 @@ +from pandac.PandaModules import * + +import SafeZoneLoader +import TTPlayground +import random +from toontown.launcher import DownloadForceAcknowledge + + +class TTSafeZoneLoader(SafeZoneLoader.SafeZoneLoader): + def __init__(self, hood, parentFSM, doneEvent): + SafeZoneLoader.SafeZoneLoader.__init__(self, hood, parentFSM, doneEvent) + self.playgroundClass = TTPlayground.TTPlayground + self.musicFile = "phase_4/audio/bgm/TC_nbrhood.mid" + self.activityMusicFile = "phase_3.5/audio/bgm/TC_SZ_activity.mid" + self.dnaFile = "phase_4/dna/toontown_central_sz.dna" + self.safeZoneStorageDNAFile = "phase_4/dna/storage_TT_sz.dna" + + def load(self): + SafeZoneLoader.SafeZoneLoader.load(self) + self.birdSound=map(base.loadSfx, [ + 'phase_4/audio/sfx/SZ_TC_bird1.mp3', + 'phase_4/audio/sfx/SZ_TC_bird2.mp3', + 'phase_4/audio/sfx/SZ_TC_bird3.mp3']) + + def unload(self): + del self.birdSound + SafeZoneLoader.SafeZoneLoader.unload(self) + + def enter(self, requestStatus): + SafeZoneLoader.SafeZoneLoader.enter(self, requestStatus) + + def exit(self): + SafeZoneLoader.SafeZoneLoader.exit(self) + diff --git a/toontown/src/safezone/TTTreasurePlannerAI.py b/toontown/src/safezone/TTTreasurePlannerAI.py new file mode 100644 index 0000000..05b9c1a --- /dev/null +++ b/toontown/src/safezone/TTTreasurePlannerAI.py @@ -0,0 +1,44 @@ +from toontown.toonbase.ToontownGlobals import * +import RegenTreasurePlannerAI +import DistributedTTTreasureAI + +class TTTreasurePlannerAI(RegenTreasurePlannerAI.RegenTreasurePlannerAI): + def __init__(self, zoneId): + self.healAmount = 3 + RegenTreasurePlannerAI.RegenTreasurePlannerAI.__init__( + self, + zoneId, + DistributedTTTreasureAI.DistributedTTTreasureAI, # Constructor + "TTTreasurePlanner", + 20, # Frequency of spawn + 5 # Max number of treasures + ) + + def initSpawnPoints(self): + self.spawnPoints = [ + (-59.9, -6.9, 0.84), + (-90.6, -3.0, -0.75), + (27.1, -93.5, 2.5), + (94.2, 33.5, 4), + (35.4, 43.1, 4), + (67.1, 105.5, 2.5), + (-99.15, -87.3407, 0.52499), + (1.60586, -119.492, 3.025), + (43.2026, -76.287, 3.025), + (129.137, -61.9039, 2.525), + (92.99, -158.399, 3.025), + (111.749, -8.59927, 4.57466), + (41.999, -30.2923, 4.025), + (31.0649, -43.9149, 4.025), + (10.0156, 105.218, 2.525), + (46.9667, 169.143, 3.025), + (100.68, 93.9896, 2.525), + (129.285, 58.6107, 2.525), + (-28.6272, 85.9833, 0.525), + (-114.613, 86.1727, 0.525), + (-132.528, 31.255, 0.025), + ] + return self.spawnPoints + + + diff --git a/toontown/src/safezone/Train.py b/toontown/src/safezone/Train.py new file mode 100644 index 0000000..20db9d7 --- /dev/null +++ b/toontown/src/safezone/Train.py @@ -0,0 +1,238 @@ +from pandac.PandaModules import * +from direct.showbase.DirectObject import DirectObject +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import globalClockDelta +from direct.distributed.ClockDelta import NetworkTimePrecision +import random +from direct.task.Task import Task + +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.directutil import Mopath +from toontown.toonbase import ToontownGlobals +from direct.actor import Actor + +# This class is more accurately a train TRACK, it handles many trains moving +# across sequentially + +class Train(DirectObject): + + notify = directNotify.newCategory('Train') + #notify.setDebug(True) + + nameId = 0 + Sfx_TrainPass = 'phase_10/audio/sfx/CBHQ_TRAIN_pass.mp3' + Sfx_TrainStopStart = 'phase_10/audio/sfx/CBHQ_TRAIN_stopstart.mp3' + LocomotiveFile = 'phase_10/models/cogHQ/CashBotLocomotive' + CarFiles = ['phase_10/models/cogHQ/CashBotBoxCar', + 'phase_10/models/cogHQ/CashBotTankCar', + 'phase_10/models/cogHQ/CashBotFlatCar' + ] + CarLength = 88 + + # this indicates the longest a train will take to cross the track + MarkDelta = 15 #seconds + + def __init__(self, trackStartPos, trackEndPos, trackNum, numTotalTracks): + self.trackStartPos = trackStartPos + self.trackEndPos = trackEndPos + self.numCars = len(self.CarFiles) + + # load up the models for the locomotive and the cars + self.locomotive = loader.loadModel(self.LocomotiveFile) + self.cars = [] + + self.trainPassingSfx = base.loadSfx(self.Sfx_TrainPass) + self.trainStopStartSfx = base.loadSfx(self.Sfx_TrainStopStart) + + self.trainId = trackNum + + # flip the models if the tracks run the opposite direction + self.bFlipped = False + if (trackStartPos[0] < trackEndPos[0]): + self.locomotive.setHpr(180, 0, 0) + self.bFlipped = True + + self.collNodeName = 'CollNode-%s' % self.trainId + + # get initial start time + self.firstMark = (self.MarkDelta / numTotalTracks) * trackNum + currentTime = self.__networkTimeInSeconds() + currentRun = int((currentTime - self.firstMark) / self.MarkDelta) + self.lastMark = (currentRun * self.MarkDelta) + self.firstMark + + # start a run + self.doNextRun(True) + self.hide() + + def hide(self): + if self.locomotive: + self.locomotive.reparentTo(hidden) + + def show(self): + if self.locomotive: + self.locomotive.reparentTo(render) + + def __networkTimeInSeconds(self): + time = globalClockDelta.getRealNetworkTime(bits=32) / NetworkTimePrecision + return time + + # this gets a new set of cars, sets up a lerp track for the + # next run and starts the run + + # A) first time through - get the last start time and start the interval in the middle + # B) the last run started less than MarkDelta ago - start a new run sometime in the future + # C) the last run started more than MarkDelta ago - start a new run in the middle + + def doNextRun(self, bFirstRun=False): + if self.locomotive: + if bFirstRun: + # make sure to start this one at the previous mark + nextMark = self.lastMark + else: + nextMark = self.lastMark + self.MarkDelta + self.nextRun.finish() + + self.notify.debug("Next mark %s" % nextMark) + currentTime = self.__networkTimeInSeconds() + + # positive=not ready yet, negative=we're late + timeTillNextMark = nextMark - currentTime + + self.notify.debug("Time diff %s" % timeTillNextMark) + + #the next run starts at the next multiple of self.MarkDelta + runNumber = int((nextMark-self.firstMark) / self.MarkDelta) + + # set up next Run Interval + S = random.getstate() + random.seed(self.trainId + runNumber) + self.nextRun = self.__getNextRun() + random.setstate(S) + + self.__startNextRun(timeTillNextMark) + self.lastMark = nextMark + return Task.done + + def __startNextRun(self, timeTillMark): + if self.locomotive: + self.__disableCollisions() + if timeTillMark > 0: + self.nextRun = Sequence(Wait(timeTillMark), self.nextRun) + self.nextRun.start() + else: + #we're late! + self.nextRun.start(-1*timeTillMark) + self.__enableCollisions() + + return Task.done + + # clean up the self.cars array + def __cleanupCars(self): + # remove the cars from the last run + self.__disableCollisions() + for car in self.cars: + car.removeNode() + self.cars = [] + + # set up the self.cars array with a random set of cars and + # parent them to the locomotive + def __getCars(self): + self.__cleanupCars() + numCarsThisRun = random.randrange(1, 10) + for nCar in range(numCarsThisRun): + carType = random.randrange(0, self.numCars) + car = loader.loadModel(self.CarFiles[carType]) + car.reparentTo(self.locomotive) + car.setPos(self.CarLength*(nCar+1), 0, 0) + self.cars.append(car) + + def __showStart(self): + self.notify.debug("Starting train %s at %s." % (self.trainId,self.__networkTimeInSeconds())) + + # set up a Lerp track for the upcoming run. The final task + # is a call to doNextRun. It is up to doNextRun to determine + # if another run should be made + def __getNextRun(self): + self.__getCars() + trainShouldStop = random.randrange(0, 4) + nextRun = Sequence(Func(self.__showStart)) + if trainShouldStop is 0: + waitTime = 3 #this is how long the delay is in the effect + totalTime = random.randrange(4, (self.MarkDelta-waitTime)/2) + sfxStopTime = 4.3 #this is where the train stops in the effect + halfway = (self.trackStartPos + self.trackEndPos) / 2 + halfway.setX(150) + nextRun.append( \ + Parallel( + Sequence( + Wait(totalTime-sfxStopTime), + SoundInterval(self.trainStopStartSfx, volume = 0.5), + ), + Sequence( + LerpPosInterval(self.locomotive, totalTime, halfway, self.trackStartPos, blendType = "easeInOut"), + WaitInterval(waitTime), + LerpPosInterval(self.locomotive, totalTime, self.trackEndPos, halfway, blendType = "easeIn"), + ) + ) + ) + else: + totalTime = random.randrange(6, self.MarkDelta-1) + #match up the middle of the run time to the middle of the sfx time + sfxTime = 7 + sfxStartTime = (totalTime/2) - (sfxTime/2) + if self.bFlipped: + sfxStartTime -= 1 + else: + sfxStartTime += 1 + nextRun.append( \ + Parallel( + Sequence( + Wait(sfxStartTime), + SoundInterval(self.trainPassingSfx, volume = 0.5), + ), + LerpPosInterval(self.locomotive, totalTime, self.trackEndPos, self.trackStartPos), + ) + ) + + nextRun.append( Func(self.doNextRun) ) + + return nextRun + + def delete(self): + self.__cleanupCars() + self.locomotive.removeNode() + self.locomotive = None + self.nextRun.finish() + self.nextRun = None + del self.trainPassingSfx + del self.trainStopStartSfx + + def uniqueName(self, name): + Train.nameId += 1 + return (name + '-%d' % Train.nameId) + + def __enableCollisions(self): + # find the collision nodes in the locomotive and cars + allColls = self.locomotive.findAllMatches('**/+CollisionNode') + for car in self.cars: + carColls = car.findAllMatches('**/+CollisionNode') + allColls += carColls + + #rename these based off the trainId + for collNode in allColls: + collNode.setName(self.collNodeName) + collNode.setCollideMask(ToontownGlobals.WallBitmask) + + self.accept('enter' + self.collNodeName, self.__handleCollisionSphereEnter) + + def __disableCollisions(self): + # stop listening for toons. + self.ignore('enter' + self.collNodeName) + #self.collisionNode.setCollideMask(BitMask32(0)) + + def __handleCollisionSphereEnter(self, collEntry=None): + # Response for the train hitting a toon + assert(self.notify.debug("Entering collision sphere...")) + base.localAvatar.b_squish(10) diff --git a/toontown/src/safezone/TreasurePlannerAI.py b/toontown/src/safezone/TreasurePlannerAI.py new file mode 100644 index 0000000..78acc57 --- /dev/null +++ b/toontown/src/safezone/TreasurePlannerAI.py @@ -0,0 +1,180 @@ +from direct.distributed.ClockDelta import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from direct.task import Task +import random + +class TreasurePlannerAI(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory( + "TreasurePlannerAI") + + def __init__(self, zoneId, treasureConstructor, callback=None): + + self.zoneId = zoneId + self.treasureConstructor = treasureConstructor + # Callback should be a function that takes one argument (avId) + # It is called when an avatar grabs a treasure so the owner of + # the treasure planner can implement collection logic like scoring + self.callback = callback + + # Determine the spawn points + self.initSpawnPoints() + + # Make a parallel list of what treasures are at what spawn points. + # None means there is no treasure there right now. + self.treasures = [] + for spawnPoint in self.spawnPoints: + self.treasures.append(None) + + # keep a list of the names of the treasure deletion tasks + self.deleteTaskNames = [] + + # These are used to check for a single toon grabbing several + # treasures in a short interval--highly suspicious behavior! + self.lastRequestId = None + self.requestStartTime = None + self.requestCount = None + + def initSpawnPoints(self): + # In this function, a list of (x, y, z) tuples should be created + # that defines all the possible places that treasure might be. + # The list should be called self.spawnPoints + self.spawnPoints = [] + return self.spawnPoints + + def numTreasures(self): + counter = 0 + for treasure in self.treasures: + if treasure: + counter += 1 + return counter + + def countEmptySpawnPoints(self): + counter = 0 + for treasure in self.treasures: + if treasure == None: + counter += 1 + return counter + + def nthEmptyIndex(self, n): + assert(n >= 0) + emptyCounter = -1 + spawnPointCounter = -1 + while emptyCounter < n: + spawnPointCounter += 1 + if self.treasures[spawnPointCounter] == None: + emptyCounter += 1 + return spawnPointCounter + + def findIndexOfTreasureId(self, treasureId): + counter = 0 + for treasure in self.treasures: + if treasure == None: + pass + else: + if treasureId == treasure.getDoId(): + return counter + counter += 1 + return None + + def placeAllTreasures(self): + index = 0 + for treasure in self.treasures: + if not treasure: + self.placeTreasure(index) + index += 1 + + def placeTreasure(self, index): + # make sure this spot is empty + assert (self.treasures[index] == None) + + # Get the spawn point xyz + spawnPoint = self.spawnPoints[index] + + # Create the treasure + treasure = self.treasureConstructor(simbase.air, self, + spawnPoint[0], + spawnPoint[1], + spawnPoint[2]) + # Generate the treasure + treasure.generateWithRequired(self.zoneId) + # Record the presence of the treasure + self.treasures[index] = treasure + + def grabAttempt(self, avId, treasureId): + if self.lastRequestId == avId: + self.requestCount += 1 + now = globalClock.getFrameTime() + elapsed = now - self.requestStartTime + if elapsed > 10: + # Reset the counter after 10 seconds. + self.requestCount = 1 + self.requestStartTime = now + else: + secondsPerGrab = elapsed / self.requestCount + if self.requestCount >= 3 and secondsPerGrab <= 0.4: + simbase.air.writeServerEvent('suspicious', avId, 'TreasurePlannerAI.grabAttempt %s treasures in %s seconds' % (self.requestCount, elapsed)) + else: + self.lastRequestId = avId + self.requestCount = 1 + self.requestStartTime = globalClock.getFrameTime() + + index = self.findIndexOfTreasureId(treasureId) + if index == None: + # If it isn't here, it isn't here. Someone else must have + # grabbed it. + pass + else: + av = simbase.air.doId2do.get(avId) + if av == None: + # If avatar isn't here, do nothing + simbase.air.writeServerEvent('suspicious', avId, 'TreasurePlannerAI.grabAttempt unknown avatar') + self.notify.warning("avid: %s does not exist" % avId) + + else: + # Find the treasure + treasure = self.treasures[index] + if treasure.validAvatar(av): + # Clear the slot + self.treasures[index] = None + # Call the grab callback with avId (if we have one) + if self.callback: + self.callback(avId) + # Tell everyone that the treasure was grabbed, and by who. + treasure.d_setGrab(avId) + # Wait five seconds, then delete the treasure + # I assume that five seconds is plenty of time for the treasure + # animation to complete on the client. + self.deleteTreasureSoon(treasure) + else: + # Reject the attempt. + treasure.d_setReject() + + def deleteTreasureSoon(self, treasure): + # Spawns a task that waits five seconds, then deletes the treasure. + taskName = treasure.uniqueName("deletingTreasure") + taskMgr.doMethodLater(5, self.__deleteTreasureNow, taskName, + extraArgs = (treasure,)) + self.deleteTaskNames.append(taskName) + + def deleteAllTreasuresNow(self): + for treasure in self.treasures: + if treasure: + treasure.requestDelete() + # we also have to manually delete all the treasures that + # have been scheduled for deletion + for taskName in self.deleteTaskNames: + tasks = taskMgr.getTasksNamed(taskName) + assert len(tasks) <= 1 + if len(tasks): + treasure = tasks[0].getArgs()[0] + treasure.requestDelete() + taskMgr.remove(taskName) + self.deleteTaskNames = [] + # Clear out the treasure list + self.treasures = [] + for spawnPoint in self.spawnPoints: + self.treasures.append(None) + + def __deleteTreasureNow(self, treasure): + treasure.requestDelete() diff --git a/toontown/src/safezone/TrolleyConstants.py b/toontown/src/safezone/TrolleyConstants.py new file mode 100644 index 0000000..687a6b2 --- /dev/null +++ b/toontown/src/safezone/TrolleyConstants.py @@ -0,0 +1,8 @@ +# Constants used for trolley coordination +TROLLEY_ENTER_TIME = 2.0 +TROLLEY_EXIT_TIME = 5.0 +TROLLEY_COUNTDOWN_TIME = 10.0 +#TROLLEY_COUNTDOWN_TIME = 5.0 +TOON_BOARD_TIME = 1.0 +TOON_EXIT_TIME = 1.0 + diff --git a/toontown/src/safezone/Walk.py b/toontown/src/safezone/Walk.py new file mode 100644 index 0000000..1c336f1 --- /dev/null +++ b/toontown/src/safezone/Walk.py @@ -0,0 +1,151 @@ +"""Walk state module: contains the walk state which is used by + multiple FSMs""" + +from pandac.PandaModules import * +from direct.task.Task import Task +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.fsm import State + +class Walk(StateData.StateData): + """Walk state class""" + + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("Walk") + + def __init__(self, doneEvent): + """__init__(self, string) + Walk state constructor + """ + StateData.StateData.__init__(self, doneEvent) + + self.fsm = ClassicFSM.ClassicFSM('Walk', + [State.State('off', + self.enterOff, + self.exitOff, + ['walking', + 'swimming', + 'slowWalking', + ]), + State.State('walking', + self.enterWalking, + self.exitWalking, + ['swimming', + 'slowWalking', + ]), + State.State('swimming', + self.enterSwimming, + self.exitSwimming, + ['walking', + 'slowWalking', + ]), + State.State('slowWalking', + self.enterSlowWalking, + self.exitSlowWalking, + ['walking', + 'swimming', + ]), + ], + # Initial State + 'off', + # Final State + 'off', + ) + self.fsm.enterInitialState() + self.IsSwimSoundAudible = 0 + self.swimSoundPlaying = 0 + + def load(self): + pass + + def unload(self): + del self.fsm + + def enter(self, slowWalk = 0): + base.localAvatar.startPosHprBroadcast() + base.localAvatar.startBlink() + base.localAvatar.attachCamera() + # this must be called *after* attachCamera() + base.localAvatar.startUpdateSmartCamera() + #base.localAvatar.setNameVisible(0) + base.localAvatar.showName() + base.localAvatar.collisionsOn() + base.localAvatar.startGlitchKiller() + base.localAvatar.enableAvatarControls() + + def exit(self): + # Go to our final state explicitly + self.fsm.request('off') + self.ignore("control") + base.localAvatar.disableAvatarControls() + base.localAvatar.stopUpdateSmartCamera() + base.localAvatar.stopPosHprBroadcast() + base.localAvatar.stopBlink() + base.localAvatar.detachCamera() + base.localAvatar.stopGlitchKiller() + base.localAvatar.collisionsOff() + base.localAvatar.controlManager.placeOnFloor() + + def enterOff(self): + pass + + def exitOff(self): + pass + + def enterWalking(self): + if base.localAvatar.hp > 0: + base.localAvatar.startTrackAnimToSpeed() + base.localAvatar.setWalkSpeedNormal() + else: + self.fsm.request("slowWalking") + + def exitWalking(self): + base.localAvatar.stopTrackAnimToSpeed() + + # turn it off here, otherwise, let __swim() handle turning this sound on + def setSwimSoundAudible(self, IsSwimSoundAudible): + self.IsSwimSoundAudible = IsSwimSoundAudible + if((IsSwimSoundAudible==0) and self.swimSoundPlaying): + self.swimSound.stop() + self.swimSoundPlaying = 0 + + def enterSwimming(self, swimSound): + base.localAvatar.setWalkSpeedNormal() + self.swimSound = swimSound + self.swimSoundPlaying = 0 + base.localAvatar.b_setAnimState( + 'swim', + base.localAvatar.animMultiplier) + base.localAvatar.startSleepSwimTest() + taskMgr.add(self.__swim, 'localToonSwimming') + + def exitSwimming(self): + taskMgr.remove('localToonSwimming') + self.swimSound.stop() + del self.swimSound + self.swimSoundPlaying = 0 + base.localAvatar.stopSleepSwimTest() + + def __swim(self, task): + speed = base.mouseInterfaceNode.getSpeed() + if ((speed == 0) and (self.swimSoundPlaying)): + self.swimSoundPlaying = 0 + self.swimSound.stop() + elif ((speed > 0) and (self.swimSoundPlaying == 0) and self.IsSwimSoundAudible): + self.swimSoundPlaying = 1 + base.playSfx(self.swimSound, looping = 1) + return Task.cont + + def enterSlowWalking(self): + self.accept(base.localAvatar.uniqueName("positiveHP"), + self.__handlePositiveHP) + base.localAvatar.startTrackAnimToSpeed() + base.localAvatar.setWalkSpeedSlow() + + def __handlePositiveHP(self): + self.fsm.request("walking") + + def exitSlowWalking(self): + base.localAvatar.stopTrackAnimToSpeed() + self.ignore(base.localAvatar.uniqueName("positiveHP")) diff --git a/toontown/src/safezone/__init__.py b/toontown/src/safezone/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/safezone/resistanceEffectBean.ptf b/toontown/src/safezone/resistanceEffectBean.ptf new file mode 100644 index 0000000..d1e23c1 --- /dev/null +++ b/toontown/src/safezone/resistanceEffectBean.ptf @@ -0,0 +1,186 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("GeomParticleRenderer") +p0.setEmitter("SphereVolumeEmitter") +p0.setPoolSize(20) +p0.setBirthRate(0.1000) +p0.setLitterSize(20) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(3.0000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p0.renderer.setUserAlpha(1.00) +# Geom parameters +#p0.renderer.setGeomNode(jellybean4.egg) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(20.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p0.emitter.setRadius(2.0000) +self.addParticles(p0) +p1 = Particles.Particles('particles-2') +# Particles parameters +p1.setFactory("PointParticleFactory") +p1.setRenderer("GeomParticleRenderer") +p1.setEmitter("SphereVolumeEmitter") +p1.setPoolSize(20) +p1.setBirthRate(0.1000) +p1.setLitterSize(20) +p1.setLitterSpread(0) +p1.setSystemLifespan(0.0000) +p1.setLocalVelocityFlag(1) +p1.setSystemGrowsOlderFlag(0) +# Factory parameters +p1.factory.setLifespanBase(3.0000) +p1.factory.setLifespanSpread(0.0000) +p1.factory.setMassBase(1.0000) +p1.factory.setMassSpread(0.0000) +p1.factory.setTerminalVelocityBase(400.0000) +p1.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p1.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p1.renderer.setUserAlpha(1.00) +# Geom parameters +#p1.renderer.setGeomNode(jellybean4.egg) +# Emitter parameters +p1.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p1.emitter.setAmplitude(20.0000) +p1.emitter.setAmplitudeSpread(0.0000) +p1.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p1.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p1.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p1.emitter.setRadius(2.0000) +self.addParticles(p1) +p2 = Particles.Particles('particles-3') +# Particles parameters +p2.setFactory("PointParticleFactory") +p2.setRenderer("GeomParticleRenderer") +p2.setEmitter("SphereVolumeEmitter") +p2.setPoolSize(20) +p2.setBirthRate(0.1000) +p2.setLitterSize(20) +p2.setLitterSpread(0) +p2.setSystemLifespan(0.0000) +p2.setLocalVelocityFlag(1) +p2.setSystemGrowsOlderFlag(0) +# Factory parameters +p2.factory.setLifespanBase(3.0000) +p2.factory.setLifespanSpread(0.0000) +p2.factory.setMassBase(1.0000) +p2.factory.setMassSpread(0.0000) +p2.factory.setTerminalVelocityBase(400.0000) +p2.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p2.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p2.renderer.setUserAlpha(1.00) +# Geom parameters +#p2.renderer.setGeomNode(jellybean4.egg) +# Emitter parameters +p2.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p2.emitter.setAmplitude(20.0000) +p2.emitter.setAmplitudeSpread(0.0000) +p2.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p2.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p2.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p2.emitter.setRadius(2.0000) +self.addParticles(p2) +p3 = Particles.Particles('particles-4') +# Particles parameters +p3.setFactory("PointParticleFactory") +p3.setRenderer("GeomParticleRenderer") +p3.setEmitter("SphereVolumeEmitter") +p3.setPoolSize(20) +p3.setBirthRate(0.1000) +p3.setLitterSize(20) +p3.setLitterSpread(0) +p3.setSystemLifespan(0.0000) +p3.setLocalVelocityFlag(1) +p3.setSystemGrowsOlderFlag(0) +# Factory parameters +p3.factory.setLifespanBase(3.0000) +p3.factory.setLifespanSpread(0.0000) +p3.factory.setMassBase(1.0000) +p3.factory.setMassSpread(0.0000) +p3.factory.setTerminalVelocityBase(400.0000) +p3.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p3.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p3.renderer.setUserAlpha(1.00) +# Geom parameters +#p3.renderer.setGeomNode(jellybean4.egg) +# Emitter parameters +p3.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p3.emitter.setAmplitude(20.0000) +p3.emitter.setAmplitudeSpread(0.0000) +p3.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p3.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p3.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p3.emitter.setRadius(2.0000) +self.addParticles(p3) +p4 = Particles.Particles('particles-5') +# Particles parameters +p4.setFactory("PointParticleFactory") +p4.setRenderer("GeomParticleRenderer") +p4.setEmitter("SphereVolumeEmitter") +p4.setPoolSize(20) +p4.setBirthRate(0.1000) +p4.setLitterSize(20) +p4.setLitterSpread(0) +p4.setSystemLifespan(0.0000) +p4.setLocalVelocityFlag(1) +p4.setSystemGrowsOlderFlag(0) +# Factory parameters +p4.factory.setLifespanBase(3.0000) +p4.factory.setLifespanSpread(0.0000) +p4.factory.setMassBase(1.0000) +p4.factory.setMassSpread(0.0000) +p4.factory.setTerminalVelocityBase(400.0000) +p4.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p4.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p4.renderer.setUserAlpha(1.00) +# Geom parameters +#p4.renderer.setGeomNode(jellybean4.egg) +# Emitter parameters +p4.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p4.emitter.setAmplitude(20.0000) +p4.emitter.setAmplitudeSpread(0.0000) +p4.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p4.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p4.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p4.emitter.setRadius(2.0000) +self.addParticles(p4) +f0 = ForceGroup.ForceGroup('forces') +# Force parameters +force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0000, 1) +force0.setActive(1) +f0.addForce(force0) +self.addForceGroup(f0) diff --git a/toontown/src/safezone/resistanceEffectSparkle.ptf b/toontown/src/safezone/resistanceEffectSparkle.ptf new file mode 100644 index 0000000..8676209 --- /dev/null +++ b/toontown/src/safezone/resistanceEffectSparkle.ptf @@ -0,0 +1,50 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SparkleParticleRenderer") +p0.setEmitter("SphereVolumeEmitter") +p0.setPoolSize(500) +p0.setBirthRate(0.1000) +p0.setLitterSize(500) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(3.0000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE) +p0.renderer.setUserAlpha(1.00) +# Sparkle parameters +p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 1.00, 1.00)) +p0.renderer.setBirthRadius(0.2000) +p0.renderer.setDeathRadius(0.1000) +p0.renderer.setLifeScale(SparkleParticleRenderer.SPSCALE) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(20.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p0.emitter.setRadius(2.0000) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('forces') +# Force parameters +force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0000, 1) +force0.setActive(1) +f0.addForce(force0) +self.addForceGroup(f0) diff --git a/toontown/src/safezone/resistanceEffectSprite.ptf b/toontown/src/safezone/resistanceEffectSprite.ptf new file mode 100644 index 0000000..267f6c1 --- /dev/null +++ b/toontown/src/safezone/resistanceEffectSprite.ptf @@ -0,0 +1,281 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("SphereVolumeEmitter") +p0.setPoolSize(50) +p0.setBirthRate(0.1000) +p0.setLitterSize(50) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(3.0000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p0.renderer.setUserAlpha(1.00) +# Sprite parameters +p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(0) +p0.renderer.setYScaleFlag(0) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.5000) +p0.renderer.setFinalXScale(0.5000) +p0.renderer.setInitialYScale(0.5000) +p0.renderer.setFinalYScale(0.5000) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(20.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p0.emitter.setRadius(2.0000) +self.addParticles(p0) +p1 = Particles.Particles('particles-2') +# Particles parameters +p1.setFactory("PointParticleFactory") +p1.setRenderer("SpriteParticleRenderer") +p1.setEmitter("SphereVolumeEmitter") +p1.setPoolSize(50) +p1.setBirthRate(0.1000) +p1.setLitterSize(50) +p1.setLitterSpread(0) +p1.setSystemLifespan(0.0000) +p1.setLocalVelocityFlag(1) +p1.setSystemGrowsOlderFlag(0) +# Factory parameters +p1.factory.setLifespanBase(3.0000) +p1.factory.setLifespanSpread(0.0000) +p1.factory.setMassBase(1.0000) +p1.factory.setMassSpread(0.0000) +p1.factory.setTerminalVelocityBase(400.0000) +p1.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p1.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p1.renderer.setUserAlpha(1.00) +# Sprite parameters +p1.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p1.renderer.setXScaleFlag(0) +p1.renderer.setYScaleFlag(0) +p1.renderer.setAnimAngleFlag(0) +p1.renderer.setInitialXScale(0.5000) +p1.renderer.setFinalXScale(0.5000) +p1.renderer.setInitialYScale(0.5000) +p1.renderer.setFinalYScale(0.5000) +p1.renderer.setNonanimatedTheta(0.0000) +p1.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p1.renderer.setAlphaDisable(0) +# Emitter parameters +p1.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p1.emitter.setAmplitude(20.0000) +p1.emitter.setAmplitudeSpread(0.0000) +p1.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p1.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p1.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p1.emitter.setRadius(2.0000) +self.addParticles(p1) +p2 = Particles.Particles('particles-3') +# Particles parameters +p2.setFactory("PointParticleFactory") +p2.setRenderer("SpriteParticleRenderer") +p2.setEmitter("SphereVolumeEmitter") +p2.setPoolSize(50) +p2.setBirthRate(0.1000) +p2.setLitterSize(50) +p2.setLitterSpread(0) +p2.setSystemLifespan(0.0000) +p2.setLocalVelocityFlag(1) +p2.setSystemGrowsOlderFlag(0) +# Factory parameters +p2.factory.setLifespanBase(3.0000) +p2.factory.setLifespanSpread(0.0000) +p2.factory.setMassBase(1.0000) +p2.factory.setMassSpread(0.0000) +p2.factory.setTerminalVelocityBase(400.0000) +p2.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p2.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p2.renderer.setUserAlpha(1.00) +# Sprite parameters +p2.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p2.renderer.setXScaleFlag(0) +p2.renderer.setYScaleFlag(0) +p2.renderer.setAnimAngleFlag(0) +p2.renderer.setInitialXScale(0.5000) +p2.renderer.setFinalXScale(0.5000) +p2.renderer.setInitialYScale(0.5000) +p2.renderer.setFinalYScale(0.5000) +p2.renderer.setNonanimatedTheta(0.0000) +p2.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p2.renderer.setAlphaDisable(0) +# Emitter parameters +p2.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p2.emitter.setAmplitude(20.0000) +p2.emitter.setAmplitudeSpread(0.0000) +p2.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p2.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p2.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p2.emitter.setRadius(2.0000) +self.addParticles(p2) +p3 = Particles.Particles('particles-4') +# Particles parameters +p3.setFactory("PointParticleFactory") +p3.setRenderer("SpriteParticleRenderer") +p3.setEmitter("SphereVolumeEmitter") +p3.setPoolSize(50) +p3.setBirthRate(0.1000) +p3.setLitterSize(50) +p3.setLitterSpread(0) +p3.setSystemLifespan(0.0000) +p3.setLocalVelocityFlag(1) +p3.setSystemGrowsOlderFlag(0) +# Factory parameters +p3.factory.setLifespanBase(3.0000) +p3.factory.setLifespanSpread(0.0000) +p3.factory.setMassBase(1.0000) +p3.factory.setMassSpread(0.0000) +p3.factory.setTerminalVelocityBase(400.0000) +p3.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p3.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p3.renderer.setUserAlpha(1.00) +# Sprite parameters +p3.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p3.renderer.setXScaleFlag(0) +p3.renderer.setYScaleFlag(0) +p3.renderer.setAnimAngleFlag(0) +p3.renderer.setInitialXScale(0.5000) +p3.renderer.setFinalXScale(0.5000) +p3.renderer.setInitialYScale(0.5000) +p3.renderer.setFinalYScale(0.5000) +p3.renderer.setNonanimatedTheta(0.0000) +p3.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p3.renderer.setAlphaDisable(0) +# Emitter parameters +p3.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p3.emitter.setAmplitude(20.0000) +p3.emitter.setAmplitudeSpread(0.0000) +p3.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p3.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p3.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p3.emitter.setRadius(2.0000) +self.addParticles(p3) +p4 = Particles.Particles('particles-5') +# Particles parameters +p4.setFactory("PointParticleFactory") +p4.setRenderer("SpriteParticleRenderer") +p4.setEmitter("SphereVolumeEmitter") +p4.setPoolSize(50) +p4.setBirthRate(0.1000) +p4.setLitterSize(50) +p4.setLitterSpread(0) +p4.setSystemLifespan(0.0000) +p4.setLocalVelocityFlag(1) +p4.setSystemGrowsOlderFlag(0) +# Factory parameters +p4.factory.setLifespanBase(3.0000) +p4.factory.setLifespanSpread(0.0000) +p4.factory.setMassBase(1.0000) +p4.factory.setMassSpread(0.0000) +p4.factory.setTerminalVelocityBase(400.0000) +p4.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p4.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p4.renderer.setUserAlpha(1.00) +# Sprite parameters +p4.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p4.renderer.setXScaleFlag(0) +p4.renderer.setYScaleFlag(0) +p4.renderer.setAnimAngleFlag(0) +p4.renderer.setInitialXScale(0.5000) +p4.renderer.setFinalXScale(0.5000) +p4.renderer.setInitialYScale(0.5000) +p4.renderer.setFinalYScale(0.5000) +p4.renderer.setNonanimatedTheta(0.0000) +p4.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p4.renderer.setAlphaDisable(0) +# Emitter parameters +p4.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p4.emitter.setAmplitude(20.0000) +p4.emitter.setAmplitudeSpread(0.0000) +p4.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p4.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p4.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p4.emitter.setRadius(2.0000) +self.addParticles(p4) +p5 = Particles.Particles('particles-6') +# Particles parameters +p5.setFactory("PointParticleFactory") +p5.setRenderer("SpriteParticleRenderer") +p5.setEmitter("SphereVolumeEmitter") +p5.setPoolSize(50) +p5.setBirthRate(0.1000) +p5.setLitterSize(50) +p5.setLitterSpread(0) +p5.setSystemLifespan(0.0000) +p5.setLocalVelocityFlag(1) +p5.setSystemGrowsOlderFlag(0) +# Factory parameters +p5.factory.setLifespanBase(3.0000) +p5.factory.setLifespanSpread(0.0000) +p5.factory.setMassBase(1.0000) +p5.factory.setMassSpread(0.0000) +p5.factory.setTerminalVelocityBase(400.0000) +p5.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p5.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER) +p5.renderer.setUserAlpha(1.00) +# Sprite parameters +p5.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p5.renderer.setXScaleFlag(0) +p5.renderer.setYScaleFlag(0) +p5.renderer.setAnimAngleFlag(0) +p5.renderer.setInitialXScale(0.5000) +p5.renderer.setFinalXScale(0.5000) +p5.renderer.setInitialYScale(0.5000) +p5.renderer.setFinalYScale(0.5000) +p5.renderer.setNonanimatedTheta(0.0000) +p5.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p5.renderer.setAlphaDisable(0) +# Emitter parameters +p5.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p5.emitter.setAmplitude(20.0000) +p5.emitter.setAmplitudeSpread(0.0000) +p5.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 20.0000)) +p5.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p5.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p5.emitter.setRadius(2.0000) +self.addParticles(p5) +f0 = ForceGroup.ForceGroup('forces') +# Force parameters +force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0000, 1) +force0.setActive(1) +f0.addForce(force0) +self.addForceGroup(f0) diff --git a/toontown/src/safezone/snowdisk.ptf b/toontown/src/safezone/snowdisk.ptf new file mode 100644 index 0000000..f6bcdd8 --- /dev/null +++ b/toontown/src/safezone/snowdisk.ptf @@ -0,0 +1,66 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("ZSpinParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("DiscEmitter") +p0.setPoolSize(1024) +p0.setBirthRate(0.0200) +p0.setLitterSize(1) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(4.5000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Z Spin factory parameters +p0.factory.setInitialAngle(0.0000) +p0.factory.setInitialAngleSpread(10.0000) +p0.factory.enableAngularVelocity(1) +p0.factory.setAngularVelocity(0.0000) +p0.factory.setAngularVelocitySpread(500.0000) +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAIN) +p0.renderer.setUserAlpha(1.00) +# Sprite parameters +p0.renderer.setIgnoreScale(1) +p0.renderer.setTextureFromNode("phase_8/models/props/snowflake_particle", "**/p1_2") +p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(0) +p0.renderer.setYScaleFlag(0) +p0.renderer.setAnimAngleFlag(1) +p0.renderer.setInitialXScale(0.03125) +p0.renderer.setFinalXScale(0.50) +p0.renderer.setInitialYScale(0.03125) +p0.renderer.setFinalYScale(0.50) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(0.1000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000)) +p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000)) +# Disc parameters +p0.emitter.setRadius(50.0000) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('gravity') +# Force parameters +force0 = LinearVectorForce(Vec3(0.0000, 0.0000, -1.0000), 1.5000, 0) +force0.setActive(1) +f0.addForce(force0) +force1 = LinearJitterForce(10.0000, 0) +force1.setActive(1) +f0.addForce(force1) +self.addForceGroup(f0) diff --git a/toontown/src/scavengerhunt/SHtest.py b/toontown/src/scavengerhunt/SHtest.py new file mode 100644 index 0000000..c0efe23 --- /dev/null +++ b/toontown/src/scavengerhunt/SHtest.py @@ -0,0 +1,60 @@ +###################################################################### +# This file is meant for unit testing the ScavengerHunt system. # +# It can also be used to demonstrate how the system should be # +# used. # +# # +# It's meant to be run after any changes are made to the system. # +# # +# Usage: python SHtest.py # +###################################################################### + +from ScavengerHuntBase import ScavengerHuntBase +import unittest,copy + +hunt = ScavengerHuntBase(scavengerHuntId = 12,scavengerHuntType = 3) +hunt.defineGoals(range(1,6)) +hunt.defineMilestones([[0,range(1,4)],[1,range(1,6)]]) + +class MilestoneTestCase(unittest.TestCase): + def testDefineGoals(self): + gc = set(range(1,6)) + self.assertEqual(hunt.goals,gc) + + def testDefineMilestones(self): + m = {} + + gc = range(1,4) + m[frozenset(gc)] = 0 + + gc = range(1,6) + m[frozenset(gc)] = 1 + + self.assertEqual(hunt.milestones,m) + + def testRecentMilestonesHit(self): + gc = range(1,4) + m = hunt.getRecentMilestonesHit(gc,2) + self.assertEqual([0],m) + + gc = range(1,6) + m = hunt.getRecentMilestonesHit(gc,2) + m.sort() + self.assertEqual([0,1],m) + + def testRecentMilestonesMissed(self): + gc = range(1,5) + + m = hunt.getRecentMilestonesHit(gc,4) + self.assertEqual([],m) + + def testAllMilestonesHit(self): + gc = range(1,6) + + m = hunt.getAllMilestonesHit(gc) + m.sort() + M = hunt.milestones.values() + M.sort() + self.assertEqual(M,m) + +if __name__ == "__main__": + unittest.main() diff --git a/toontown/src/scavengerhunt/ScavengerHuntBase.py b/toontown/src/scavengerhunt/ScavengerHuntBase.py new file mode 100644 index 0000000..deaee9a --- /dev/null +++ b/toontown/src/scavengerhunt/ScavengerHuntBase.py @@ -0,0 +1,65 @@ +#from direct.directnotify import DirectNotifyGlobal +class ScavengerHuntBase: + """ + Base class for all hunts. There is enough functionality here + such that you shouldn't need to subclass it, though. + """ + + def __init__(self,scavengerHuntId, scavengerHuntType): + self.id = scavengerHuntId + self.type = scavengerHuntType + self.goals = set() + self.milestones = {} + + def defineGoals(self,goalIds): + """ + Accepts a list of Goal identifiers. This could be something as + simple as a range of integers corresponding to the goals in the + hunt. + """ + self.goals = set(goalIds) + + def defineMilestones(self,milestones = []): + """ + Accepts a list with items of the format: + [milestoneId,[goal1,goal2,goal3,...]] + """ + for id,stone in milestones: + self.milestones[frozenset(stone)] = id + + def getRecentMilestonesHit(self,goals,mostRecentGoal): + """ + Given a list of goals, and the most recent goal added to that + list, return a list of milestone ids which that latest goal would + trigger. + """ + milestones = [] + + for milestone in self.milestones.keys(): + if mostRecentGoal in milestone and milestone.issubset(goals): + milestones.append(self.milestones[milestone]) + return milestones + + + def getAllMilestonesHit(self,goals): + """ + Return a list of milestone ids which are satisfied by the Goals listed + in goals. + """ + milestones = [] + + for milestone in self.milestones.keys(): + if(milestone.issubset(goals)): + milestones.append(self.milestones[milestone]) + + return milestones + + + + + + + + + + diff --git a/toontown/src/scavengerhunt/Sources.pp b/toontown/src/scavengerhunt/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/scavengerhunt/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/scavengerhunt/__init__.py b/toontown/src/scavengerhunt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/scavengerhunt/__pycache__/ScavengerHuntBase.cpython-37.pyc b/toontown/src/scavengerhunt/__pycache__/ScavengerHuntBase.cpython-37.pyc new file mode 100644 index 0000000..bcc4096 Binary files /dev/null and b/toontown/src/scavengerhunt/__pycache__/ScavengerHuntBase.cpython-37.pyc differ diff --git a/toontown/src/secure/.cvsignore b/toontown/src/secure/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/secure/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/shtiker/.cvsignore b/toontown/src/shtiker/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/shtiker/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/shtiker/AchievePage.py b/toontown/src/shtiker/AchievePage.py new file mode 100644 index 0000000..1f16b6f --- /dev/null +++ b/toontown/src/shtiker/AchievePage.py @@ -0,0 +1,30 @@ +"""AchievePage module: contains the AchievePage class""" + +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class AchievePage(ShtikerPage.ShtikerPage): + """AchievePage class""" + + # special methods + def __init__(self): + """__init__(self) + AchievePage constructor: create the achieve selector page + """ + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.AchievePageTitle, + text_scale = 0.12, + pos = (0,0,0.6), + ) + + def unload(self): + del self.title + ShtikerPage.ShtikerPage.unload(self) diff --git a/toontown/src/shtiker/BuildingPage.py b/toontown/src/shtiker/BuildingPage.py new file mode 100644 index 0000000..32e103f --- /dev/null +++ b/toontown/src/shtiker/BuildingPage.py @@ -0,0 +1,30 @@ +"""BuildingPage module: contains the BuildingPage class""" + +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class BuildingPage(ShtikerPage.ShtikerPage): + """BuildingPage class""" + + # special methods + def __init__(self): + """__init__(self) + BuildingPage constructor: create the building selector page + """ + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.BuildingPageTitle, + text_scale = 0.12, + pos = (0,0,0.6), + ) + + def unload(self): + del self.title + ShtikerPage.ShtikerPage.unload(self) diff --git a/toontown/src/shtiker/CogPageGlobals.py b/toontown/src/shtiker/CogPageGlobals.py new file mode 100644 index 0000000..e954e9e --- /dev/null +++ b/toontown/src/shtiker/CogPageGlobals.py @@ -0,0 +1,16 @@ +"""CogPageGlobals module: used to hold global vars for SuitPage.py and CogPageManagerAI.py""" + +COG_QUOTAS = ( + #(1, 1, 1, 1, 1, 1, 1, 1), + #(2, 2, 2, 2, 2, 2, 2, 2) + (30, 25, 20, 15, 10, 5, 2, 1), + (45, 40, 35, 30, 25, 20, 15, 10) + ) + +# cog encounter classifications +COG_UNSEEN = 1 +COG_BATTLED = 2 +COG_DEFEATED = 3 +COG_COMPLETE1 = 4 +COG_COMPLETE2 = 5 + diff --git a/toontown/src/shtiker/CogPageManagerAI.py b/toontown/src/shtiker/CogPageManagerAI.py new file mode 100644 index 0000000..ea70f3d --- /dev/null +++ b/toontown/src/shtiker/CogPageManagerAI.py @@ -0,0 +1,126 @@ +from otp.ai.AIBaseGlobal import * +from direct.directnotify import DirectNotifyGlobal +from toontown.suit import SuitDNA +from CogPageGlobals import * + +class CogPageManagerAI: + + notify = DirectNotifyGlobal.directNotify.newCategory("CogPageManagerAI") + + def __init__(self, air): + self.air = air + + def toonEncounteredCogs(self, av, cogList, zoneId): + # This lets the battle system notify us when we have encountered cogs. + # If we have not encountered them before, update the avatar + avId = av.getDoId() + avCogs = av.cogs[:] + changed = 0 + self.notify.debug("toonEncounteredCogs: avId: %s, avCogs: %s, cogList: %s, zoneId: %s" + % (avId, avCogs, cogList, zoneId)) + for encounter in cogList: + cog = encounter['type'] + activeToons = encounter['activeToons'] + index = SuitDNA.suitHeadTypes.index(cog) + if (avCogs[index] == COG_UNSEEN) and (avId in activeToons): + avCogs[index] = COG_BATTLED + changed = 1 + + # update cog status only if we've seen someone new + if changed: + av.b_setCogStatus(avCogs) + + + def toonKilledCogs(self, av, cogList, zoneId): + # This lets the battle system notify us when we have killed cogs. + avId = av.getDoId() + avCogs = av.cogs[:] + cogs_changed = 0 + avCogCounts = av.cogCounts[:] + counts_changed = 0 + avCogRadar = av.cogRadar[:] + cog_radar_changed = 0 + avBuildingRadar = av.buildingRadar[:] + building_radar_changed = 0 + self.notify.debug("toonKilledCogs: avId: %s, cogCounts: %s, cogList: %s, zoneId: %s" + % (avId, avCogCounts, cogList, zoneId)) + + eventMsg = {} + for encounter in cogList: + if encounter['isVP'] or encounter['isCFO']: + continue + cog = encounter['type'] + level = encounter['level'] + track = encounter['track'] + activeToons = encounter['activeToons'] + index = SuitDNA.suitHeadTypes.index(cog) + quotaIndex = index % SuitDNA.suitsPerDept + # if we were active when the cog was defeated, update the count + if (avId in activeToons): + msgName = '%s%s' % (cog, level) + if encounter['isSkelecog']: + msgName += "+" + if eventMsg.has_key(msgName): + eventMsg[msgName] += 1 + else: + eventMsg[msgName] = 1 + + # update but don't exceed the quota + if (avCogCounts[index] < COG_QUOTAS[1][quotaIndex]): + avCogCounts[index] += 1 + counts_changed = 1 + # if we have met the first quota, set to complete1 + if (avCogCounts[index] == COG_QUOTAS[0][quotaIndex]): + avCogs[index] = COG_COMPLETE1 + cogs_changed = 1 + # if we have met the second quota, set to complete2 + elif (avCogCounts[index] == COG_QUOTAS[1][quotaIndex]): + avCogs[index] = COG_COMPLETE2 + cogs_changed = 1 + # otherwise if not already defeated, set to defeated + elif ((avCogs[index] == COG_BATTLED) or + (avCogs[index] == COG_UNSEEN)): + avCogs[index] = COG_DEFEATED + cogs_changed = 1 + + # Now format the message for the AI. + msgText = '' + for msgName, count in eventMsg.items(): + if msgText != '': + msgText += ',' + msgText += '%s%s' % (count, msgName) + + self.air.writeServerEvent( + 'cogsDefeated', avId, "%s|%s" % (msgText, zoneId)) + + # update cog status only if we've had a change of state + if cogs_changed: + av.b_setCogStatus(avCogs) + + # check the quotas for each dept and see if cog radar has been achieved + deptSize = SuitDNA.suitsPerDept + for dept in range(0, len(SuitDNA.suitDepts)): + # only check if it hasn't been achieved yet! + if avCogRadar[dept] == 0: + if min(avCogs[deptSize*dept:deptSize*(dept+1)]) == COG_COMPLETE1: + avCogRadar[dept] = 1 + cog_radar_changed = 1 + + if cog_radar_changed: + av.b_setCogRadar(avCogRadar) + + # check the quotas for each dept and see if bldg radar has been achieved + for dept in range(0, len(SuitDNA.suitDepts)): + # only check if it hasn't been achieved yet! + if avBuildingRadar[dept] == 0: + if min(avCogs[deptSize*dept:deptSize*(dept+1)]) == COG_COMPLETE2: + avBuildingRadar[dept] = 1 + building_radar_changed = 1 + + if building_radar_changed: + av.b_setBuildingRadar(avBuildingRadar) + + # update cog counts only if we've seen someone new + if counts_changed: + av.b_setCogCount(avCogCounts) + diff --git a/toontown/src/shtiker/DeleteManager.py b/toontown/src/shtiker/DeleteManager.py new file mode 100644 index 0000000..32affcf --- /dev/null +++ b/toontown/src/shtiker/DeleteManager.py @@ -0,0 +1,23 @@ +from pandac.PandaModules import * +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal + +class DeleteManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("DeleteManager") + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + def generate(self): + DistributedObject.DistributedObject.generate(self) + self.accept("deleteItems", self.d_setInventory) + + def disable(self): + self.ignore("deleteItems") + DistributedObject.DistributedObject.disable(self) + + def d_setInventory(self, newInventoryString): + self.sendUpdate("setInventory", [newInventoryString]) + + diff --git a/toontown/src/shtiker/DeleteManagerAI.py b/toontown/src/shtiker/DeleteManagerAI.py new file mode 100644 index 0000000..eac9526 --- /dev/null +++ b/toontown/src/shtiker/DeleteManagerAI.py @@ -0,0 +1,30 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal + +class DeleteManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("DeleteManagerAI") + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + def setInventory(self, newInventoryString): + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists. + if self.air.doId2do.has_key(avId): + # Find the avatar + av = self.air.doId2do[avId] + # Create a new inventory list + newInv = av.inventory.makeFromNetString(newInventoryString) + # Delete the items + av.inventory.setToMin(newInv) + # Tell the state server + av.d_setInventory(av.inventory.makeNetString()) + else: + self.air.writeServerEvent('suspicious', avId, 'DeleteManagerAI.setInventory unknown avatar') + self.notify.warning( + "Avatar: " + str(avId) + + " tried to setInventory, but is not in the district.") + + diff --git a/toontown/src/shtiker/DirectNewsFrame.py b/toontown/src/shtiker/DirectNewsFrame.py new file mode 100644 index 0000000..5caba2b --- /dev/null +++ b/toontown/src/shtiker/DirectNewsFrame.py @@ -0,0 +1,512 @@ +import os +import time +import datetime +from pandac.PandaModules import Filename, DSearchPath, TextNode +from pandac.PandaModules import HTTPClient, Ramfile, DocumentSpec +from direct.showbase import DirectObject +from direct.gui.DirectGui import DirectFrame, DGG #, DirectButton, DirectLabel +from direct.directnotify import DirectNotifyGlobal +from direct.task.Task import Task +from direct.showbase import AppRunnerGlobal +from toontown.shtiker import IssueFrame +from toontown.toonbase import TTLocalizer + +class DirectNewsFrame(DirectObject.DirectObject): + + TaskName = 'HtmlViewUpdateTask' + TaskChainName = "RedownladTaskChain" + RedownloadTaskName = "RedownloadNewsTask" + NewsBaseDir = config.GetString("news-base-dir", "phase_3.5/models/news") + NewsStageDir = config.GetString("news-stage-dir", "news") + # taken from In Game NewsFrame + FrameDimensions = (-1.30666637421, 1.30666637421, -0.751666665077, 0.751666665077) + notify = DirectNotifyGlobal.directNotify.newCategory("DirectNewsFrame") + NewsIndexFilename = config.GetString("news-index-filename", "http_news_index.txt") + NewsOverHttp = config.GetBool("news-over-http", True) + CacheIndexFilename = 'cache_index.txt' + + # home page is considered one section, must always be first + # home, news, events, talk of the town, ask toontown, toon resistance + SectionIdents = ['hom', 'new', 'evt', 'tot', 'att', 'tnr'] + + def __init__(self, parent = aspect2d): + DirectObject.DirectObject.__init__(self) + self.accept("newsSnapshot", self.doSnapshot) + self.active = False + self.parent = parent + self.issues = [] + self.accept("newsChangeWeek", self.changeWeek) + self.curIssueIndex = 0 + self.strFilenames = None + self.redownloadingNews = False + self.startRedownload = datetime.datetime.now() # just used for timing + self.endRedownload = datetime.datetime.now() # just used for timing + self.load() + self.percentDownloaded = 0.0 + self.numIssuesExpected = 0 + self.needsParseNews = True + if self.NewsOverHttp: + self.redownloadNews() + + self.accept("newIssueOut", self.handleNewIssueOut) + self.accept("clientCleanup", self.handleClientCleanup) + + def parseNewsContent(self): + """Open up the directory, read all the files, and figure out the structure.""" + if not self.needsParseNews: + return + assert not self.redownloadingNews + self.needsParseNews = False + + result = False + newsDir = self.findNewsDir() + if newsDir: + allHomeFiles = self.getAllHomeFilenames(newsDir) + self.notify.debug("len allHomeFiles = %s" % len(allHomeFiles)) + self.numIssuesExpected = len(allHomeFiles) + if allHomeFiles: + for myIssueIndex, oneHomeFile in enumerate(allHomeFiles): + if type(oneHomeFile) == type(""): + justFilename = oneHomeFile + else: + justFilename = oneHomeFile.getFilename().getBasename() + self.notify.debug("parseNewContent %s" % justFilename) + parts = justFilename.split('_') + dateStr = parts[3] + oneIssue = IssueFrame.IssueFrame(self.backFrame, newsDir, dateStr, myIssueIndex, len(allHomeFiles), self.strFilenames) + oneIssue.hide() + self.issues.append(oneIssue) + if self.issues: + self.issues[-1].show() + self.curIssueIndex = len(self.issues) - 1 + result = True + + if hasattr(base.cr, 'inGameNewsMgr') and base.cr.inGameNewsMgr: + # we should get here only when a new issue comes out mid game + self.createdTime = base.cr.inGameNewsMgr.getLatestIssue() + self.notify.debug("setting created time to latest issue %s" % self.createdTime) + else: + # this is sucky that at this point (initial load) we don't have in game news mgr + self.createdTime = base.cr.toontownTimeManager.getCurServerDateTime() + self.notify.debug("setting created time cur server time %s" % self.createdTime) + return result + + def getAllHomeFilenames(self, newsDir): + """Find all the issues that are available.""" + + self.notify.debug("getAllHomeFilenames") + newsDirAsFile = vfs.getFile(Filename(newsDir)) + fileList = newsDirAsFile.scanDirectory() + fileNames = fileList.getFiles() + self.notify.debug("filenames=%s" % fileNames) + # scan through and find hom1. thats got to be a home page + homeFileNames = set([]) + for name in fileNames: + self.notify.debug("processing %s" % name) + baseName = name.getFilename().getBasename() + self.notify.debug("baseName=%s" % baseName) + if "hom1." in baseName: + homeFileNames.add(name) + else: + self.notify.debug("hom1. not in baseName") + + if not homeFileNames: + #self.notify.error("couldnt find hom1. in %s" % fileNames) + self.notify.warning("couldnt find hom1. in %s" % fileNames) + self.setErrorMessage(TTLocalizer.NewsPageNoIssues) + return [] + + def fileCmp( fileA, fileB): + return fileA.getFilename().compareTo(fileB.getFilename()) + homeFileNames = list(homeFileNames) + homeFileNames.sort(cmp = fileCmp) + self.notify.debug("returned homeFileNames=%s" % homeFileNames) + + return homeFileNames + + def findNewsDir(self): + """Returns the directory string for news content. + + Returns None if it cant find the directory + """ + + if self.NewsOverHttp: + # If we're running news-over-http, we dump the news into a + # staging directory. + return self.NewsStageDir + + searchPath = DSearchPath() + if AppRunnerGlobal.appRunner: + # In the web-publish runtime, it will always be here: + searchPath.appendDirectory(Filename.expandFrom('$TT_3_5_ROOT/phase_3.5/models/news')) + else: + # In the launcher or dev environment, look here: + basePath = os.path.expandvars('$TTMODELS') or './ttmodels' + searchPath.appendDirectory( + Filename.fromOsSpecific(basePath+'/built/' + self.NewsBaseDir)) + searchPath.appendDirectory(Filename(self.NewsBaseDir)) + + pfile = Filename(self.NewsIndexFilename) + found = vfs.resolveFilename(pfile, searchPath) + if not found: + self.notify.warning('findNewsDir - no path: %s' % self.NewsIndexFilename) + self.setErrorMessage(TTLocalizer.NewsPageErrorDownloadingFile % self.NewsIndexFilename) + return None + self.notify.debug("found index file %s" % pfile) + realDir = pfile.getDirname() + return realDir + + def load(self): + """Create the gui objects we need.""" + self.loadBackground() + #self.loadMainPage() + + def loadBackground(self): + """Create a plain white background image, that covers over the shtickerbook""" + # HtmlView: webFrame = -1.30666637421 1.30666637421 -0.751666665077 0.751666665077 + upsellBackground = loader.loadModel("phase_3.5/models/gui/tt_m_gui_ign_newsStatusBackground") + imageScaleX = self.FrameDimensions[1] - self.FrameDimensions[0] + imageScaleY = self.FrameDimensions[3] - self.FrameDimensions[2] + self.backFrame = DirectFrame( + parent = self.parent, + image = upsellBackground, + image_scale = (imageScaleX, 1, imageScaleY), + frameColor = (1,1,1,0), + frameSize = self.FrameDimensions, + pos = (0,0,0), + relief = DGG.FLAT, + text = TTLocalizer.NewsPageDownloadingNews1, + text_scale = 0.06, + text_pos = (0,-0.4), + ) + + def addDownloadingTextTask(self): + """Add a simple little task to show in game news is downloading stuff.""" + self.removeDownloadingTextTask() + task = taskMgr.doMethodLater(1,self.loadingTextTask, "DirectNewsFrameDownloadingTextTask") + task.startTime = globalClock.getFrameTime() + self.loadingTextTask(task) + + def removeDownloadingTextTask(self): + """Add a simple little task to show in game news is downloading stuff.""" + taskMgr.remove("DirectNewsFrameDownloadingTextTask") + + def loadMainPage(self): + """Create the other gui for this.""" + self.mainFrame = DirectFrame( + parent = self.backFrame, + frameSize = self.FrameDimensions, + frameColor = (1,0,0,1), + ) + + def activate(self): + """ + Check if we have a new issue, and prompt the user if we have one. + """ + if hasattr(self,"createdTime") and \ + self.createdTime < base.cr.inGameNewsMgr.getLatestIssue() and \ + self.NewsOverHttp and \ + not self.redownloadingNews: + # we have a new issue, ask the user if he wants to download it + # let's assume he clicked yes + self.redownloadNews() + pass + + else: + self.addDownloadingTextTask() + + # Load up the news content the first time the user asks to see + # it. + if self.needsParseNews and not self.redownloadingNews: + self.parseNewsContent() + + self.active = True + + def deactivate(self): + """ + self.quad.hide() + taskMgr.remove(self.TaskName) + """ + self.removeDownloadingTextTask() + self.active = False + + def unload(self): + """ + self.deactivate() + HtmlView.HtmlView.unload(self) + """ + self.removeDownloadingTextTask() + result = taskMgr.remove(self.RedownloadTaskName) + self.ignore("newsSnapshot") + self.ignore("newsChangeWeek") + self.ignore("newIssueOut") + self.ignore("clientCleanup") + + def handleClientCleanup(self): + """User killing toontown, detach the backframe.""" + pass + + def doSnapshot(self): + "Save the current browser contents to a png file.""" + pass + + def changeWeek(self, issueIndex): + """Change the issue we are displaying.""" + if 0 <= issueIndex and issueIndex < len(self.issues): + self.issues[self.curIssueIndex].hide() + self.issues[issueIndex].show() + self.curIssueIndex = issueIndex + + def loadingTextTask(self, task): + """Change a visual element to indicate we're still downloading.""" + timeIndex = int(globalClock.getFrameTime() - task.startTime) % 3 + timeStrs = (TTLocalizer.NewsPageDownloadingNews0, + TTLocalizer.NewsPageDownloadingNews1, + TTLocalizer.NewsPageDownloadingNews2) + textToDisplay = timeStrs[timeIndex] % (int(self.percentDownloaded*100)) + + if self.backFrame["text"] != textToDisplay: + if TTLocalizer.NewsPageDownloadingNewsSubstr in self.backFrame["text"]: + # don't change the text if we're displaying an error message + self.backFrame["text"] = textToDisplay + + return task.again + + def setErrorMessage(self, errText): + """Tell the user something has gone wrong.""" + self.backFrame["text"] = errText + + def redownloadNews(self): + """Get the new issue that came out while he was playing.""" + if self.redownloadingNews: + self.notify.warning("averting potential crash redownloadNews called twice, just returning") + return + # I know it's info, it's important enough I feel to appear in the logs + self.percentDownloaded = 0.0 + self.notify.info("starting redownloadNews") + self.startRedownload = datetime.datetime.now() + + self.redownloadingNews =True + self.addDownloadingTextTask() + + # Clean up the old issues and start new stuff downloading. + for issue in self.issues: + issue.destroy() + self.issues = [] + self.curIssueIndex = 0 + self.strFilenames = None + self.needsParseNews = True + + # Start by downloading the index file. + self.newsUrl = self.getInGameNewsUrl() + self.newsDir = Filename(self.findNewsDir()) + + # Ensure self.newsDir exists and is a directory. + Filename(self.newsDir + '/.').makeDir() + + http = HTTPClient.getGlobalPtr() + self.url = self.newsUrl + self.NewsIndexFilename + self.ch = http.makeChannel(True) + self.ch.beginGetDocument(self.url) + self.rf = Ramfile() + self.ch.downloadToRam(self.rf) + + taskMgr.remove(self.RedownloadTaskName) + taskMgr.add(self.downloadIndexTask, self.RedownloadTaskName) + + def downloadIndexTask(self, task): + """ Get the initial index file from the HTTP server. """ + if self.ch.run(): + return task.cont + + if not self.ch.isValid(): + self.notify.warning("Unable to download %s" % (self.url)) + self.redownloadingNews = False + return task.done + + # OK, now we've got the list of files hosted by the server. + # Parse the list. + self.newsFiles = [] + filename = self.rf.readline() + while filename: + filename = filename.strip() + if filename: + self.newsFiles.append(filename) + filename = self.rf.readline() + del self.rf + + self.newsFiles.sort() + self.notify.info("Server lists %s news files" % (len(self.newsFiles))) + + # Now see if we already have copies of these files we + # downloaded previously. + self.readNewsCache() + + # Clean up any unexpected files in this directory--they might + # be old news files, or partial failed downloads from before. + for basename in os.listdir(self.newsDir.toOsSpecific()): + if basename != self.CacheIndexFilename and basename not in self.newsCache: + junk = Filename(self.newsDir, basename) + self.notify.info("Removing %s" % (junk)) + junk.unlink() + + # And start downloading the files. + self.nextNewsFile = 0 + return self.downloadNextFile(task) + + def downloadNextFile(self, task): + """ Starts the next news file downloading from the HTTP + server. """ + + if self.nextNewsFile >= len(self.newsFiles): + # Hey, we're done! + self.notify.info("Done downloading news.") + self.percentDownloaded = 1 + + del self.newsFiles + del self.nextNewsFile + del self.newsUrl + del self.newsDir + del self.ch + del self.url + if hasattr(self,'filename'): + del self.filename + self.redownloadingNews = False + + if self.active: + # If we're looking at the page now, go ahead and load it. + self.parseNewsContent() + + return task.done + + self.percentDownloaded = float(self.nextNewsFile) / float(len(self.newsFiles)) + + # Get the next file on the list. + self.filename = self.newsFiles[self.nextNewsFile] + self.nextNewsFile += 1 + self.url = self.newsUrl + self.filename + + localFilename = Filename(self.newsDir, self.filename) + doc = DocumentSpec(self.url) + if self.filename in self.newsCache: + # We have already downloaded this file. Ask the + # server to give us another copy only if the server's + # copy is newer. + size, date = self.newsCache[self.filename] + if date and localFilename.exists() and (size == 0 or localFilename.getFileSize() == size): + doc.setDate(date) + doc.setRequestMode(doc.RMNewer) + + self.ch.beginGetDocument(doc) + self.ch.downloadToFile(localFilename) + + taskMgr.remove(self.RedownloadTaskName) + taskMgr.add(self.downloadCurrentFileTask, self.RedownloadTaskName) + + def downloadCurrentFileTask(self, task): + """ Continues downloading the URL in self.url and self.filename. """ + + if self.ch.run(): + return task.cont + + if self.ch.getStatusCode() == 304: + # This file is still cached from before. We don't need to + # download it again. Move on to the next file. + self.notify.info("already cached: %s" % (self.filename)) + return self.downloadNextFile(task) + + localFilename = Filename(self.newsDir, self.filename) + + if not self.ch.isValid(): + self.notify.warning("Unable to download %s" % (self.url)) + localFilename.unlink() + + if self.filename in self.newsCache: + del self.newsCache[self.filename] + self.saveNewsCache() + + # Might as well see if we can get the next file. + return self.downloadNextFile(task) + + # Successfully downloaded. + self.notify.info("downloaded %s" % (self.filename)) + + # The HTTP "Entity Tag" appears to be useless with our CDN: + # different CDN servers will serve up different etag values + # for the same file. We rely on file size and date instead. + + size = self.ch.getFileSize() + doc = self.ch.getDocumentSpec() + date = '' + if doc.hasDate(): + date = doc.getDate().getString() + + self.newsCache[self.filename] = (size, date) + self.saveNewsCache() + + # Continue downloading files. + return self.downloadNextFile(task) + + def readNewsCache(self): + """ Reads cache_index.txt into self.newsCache. """ + + cacheIndexFilename = Filename(self.newsDir, self.CacheIndexFilename) + self.newsCache = {} + if cacheIndexFilename.isRegularFile(): + file = open(cacheIndexFilename.toOsSpecific(), 'r') + for line in file.readlines(): + line = line.strip() + keywords = line.split('\t') + if len(keywords) == 3: + filename, size, date = keywords + if filename in self.newsFiles: + try: + size = int(size) + except ValueError: + size = 0 + self.newsCache[filename] = (size, date) + + def saveNewsCache(self): + """ Saves self.newsCache to cache_index.txt """ + cacheIndexFilename = Filename(self.newsDir, self.CacheIndexFilename) + + file = open(cacheIndexFilename.toOsSpecific(), 'w') + for filename, (size, date) in self.newsCache.items(): + print >> file, '%s\t%s\t%s' % (filename, size, date) + + def handleNewIssueOut(self): + """Handle getting this newIssueOut message.""" + # we will get this immediately after DistributedInGameNewsManager gets created + # we will get this again when a new issue comes out while we are playing + if hasattr(self,"createdTime") and \ + base.cr.inGameNewsMgr.getLatestIssue() < self.createdTime: + self.createdTime = base.cr.inGameNewsMgr.getLatestIssue() + else: + # we got a new issue while playing the game + if self.NewsOverHttp and not self.redownloadingNews: + # let's not abruptly yank the page if he's reading the news + if not self.active: + self.redownloadNews() + pass + + def getInGameNewsUrl(self): + """Get the appropriate URL to use if we are in test, qa, or live.""" + # First if all else fails, we hard code the live news url + result = base.config.GetString("fallback-news-url", "http://cdn.toontown.disney.go.com/toontown/en/gamenews/") + # next check if we have an override, say they want to url to point to a file in their harddisk + override = base.config.GetString("in-game-news-url", "") + if override: + self.notify.info("got an override url, using %s for in game news" % override) + result = override + else: + try: + launcherUrl = base.launcher.getValue("GAME_IN_GAME_NEWS_URL", "") + if launcherUrl: + result = launcherUrl + self.notify.info("got GAME_IN_GAME_NEWS_URL from launcher using %s" % result) + else: + self.notify.info("blank GAME_IN_GAME_NEWS_URL from launcher, using %s" % result) + + except: + self.notify.warning("got exception getting GAME_IN_GAME_NEWS_URL from launcher, using %s" % result) + return result diff --git a/toontown/src/shtiker/DisguisePage.py b/toontown/src/shtiker/DisguisePage.py new file mode 100644 index 0000000..10cfc1c --- /dev/null +++ b/toontown/src/shtiker/DisguisePage.py @@ -0,0 +1,472 @@ +"""DisguisePage module: contains the DisguisePage class""" + +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.suit import SuitDNA +from toontown.battle import SuitBattleGlobals +from toontown.minigame import MinigamePowerMeter +from toontown.coghq import CogDisguiseGlobals + +# colors for cog background panels +DeptColors = ( + # bossbots + Vec4(0.647, 0.608, 0.596, 1.000), + # lawbots + Vec4(0.588, 0.635, 0.671, 1.000), + # cashbots + Vec4(0.596, 0.714, 0.659, 1.000), + # sellbots + Vec4(0.761, 0.678, 0.690, 1.000), + ) + +NumParts = max(CogDisguiseGlobals.PartsPerSuit) + +PartNames = ( + "lUpleg", "lLowleg", "lShoe", + "rUpleg", "rLowleg", "rShoe", + "lShoulder", "rShoulder", "chest", "waist", "hip", + "lUparm", "lLowarm", "lHand", + "rUparm", "rLowarm", "rHand", + ) + +class DisguisePage(ShtikerPage.ShtikerPage): + + meterColor = Vec4(0.87, 0.87, 0.827, 1.0) + meterActiveColor = Vec4(0.7,0.3,0.3,1) + + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + # default to bossbot info + self.activeTab = 0 + self.progressTitle = None + + def load(self): + ShtikerPage.ShtikerPage.load(self) + + # load the gui + gui = loader.loadModel("phase_9/models/gui/cog_disguises") + + # make a frame for the background + self.frame = DirectFrame( + parent = self, + relief = None, + scale = 0.47, + pos = (0.02,1,0), + ) + + # Add a background panel + self.bkgd = DirectFrame( + parent = self.frame, + geom = gui.find('**/base'), + relief = None, + scale = (0.98,1,1), + ) + # Dont use texture's color + self.bkgd.setTextureOff(1) + + # make the tab text + self.tabs = [] + self.pageFrame = DirectFrame(parent = self.frame, relief = None) + + for dept in SuitDNA.suitDepts: + if dept == 'c': + # Bossbot + tabIndex = 1 + textPos = (1.57,0.75) + elif dept == 'l': + # Lawbot + tabIndex = 2 + textPos = (1.57,0.12) + elif dept == 'm': + # Cashbot + tabIndex = 3 + textPos = (1.57,-0.47) + elif dept == 's': + # Sellbot + tabIndex = 4 + textPos = (1.57,-1.05) + + pageGeom = gui.find('**/page%d' % tabIndex) + tabGeom = gui.find('**/tab%d' % tabIndex) + + tab = DirectButton( + parent = self.pageFrame, + relief = None, + geom = tabGeom, + geom_color = DeptColors[tabIndex-1], + text = SuitDNA.suitDeptFullnames[dept], + text_font = ToontownGlobals.getSuitFont(), + text_pos = textPos, + text_roll = -90, + text_scale = TTLocalizer.DPtab, + text_align = TextNode.ACenter, + # Press + text1_fg = Vec4(1,0,0,1), + # Highlight + text2_fg = Vec4(0.5,0.4,0.4,1), + # Disabled + text3_fg = Vec4(0.4,0.4,0.4,1), + command = self.doTab, + extraArgs = [len(self.tabs)], + # Don't scale down tab on press + pressEffect = 0, + ) + self.tabs.append(tab) + + page = DirectFrame( + parent = tab, + relief = None, + geom = pageGeom, + ) + + self.deptLabel = DirectLabel( + parent = self.frame, + text = '', + text_font = ToontownGlobals.getSuitFont(), + text_scale = TTLocalizer.DPdeptLabel, + text_pos = (-0.1, 0.8), + ) + + # Pipes surrounding gui + DirectFrame( + parent = self.frame, + relief = None, + geom = gui.find("**/pipe_frame"), + ) + + # HACK - these don't need to be stored locally (just for debugging) + self.tube = DirectFrame( + parent = self.frame, + relief = None, + geom = gui.find("**/tube"), + ) + + # Suit's Face + DirectFrame( + parent = self.frame, + relief = None, + geom = gui.find("**/robot/face"), + ) + + # Title for title bar + DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_cog_disguises'), + geom_pos = (0,.1,0), + ) + + # Title for Merit progress readout (sellbot) + self.meritTitle = DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_merit_progress'), + geom_pos = (0,.1,0), + ) + self.meritTitle.hide() + + # Title for Cogbuck progress readout (cashbot) + self.cogbuckTitle = DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_cashbuck_progress'), + geom_pos = (0,.1,0), + ) + self.cogbuckTitle.hide() + + # Title for Jury Notice progress readout (lawbot) + self.juryNoticeTitle = DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_jury_notice_progress'), + geom_pos = (0,.1,0), + ) + self.juryNoticeTitle.hide() + + # Title for Stock Option progress readout (bossbot) + self.stockOptionTitle = DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_stock_option_progress'), + geom_pos = (0,.1,0), + ) + self.stockOptionTitle.hide() + + # will need two more 'merit' progress titles eventually + self.progressTitle = self.meritTitle + + self.promotionTitle = DirectLabel( + parent = self.frame, + relief = None, + geom = gui.find('**/text_ready4promotion'), + geom_pos = (0,.1,0), + ) + + # make the cog name label + self.cogName = DirectLabel( + parent = self.frame, + relief = None, + text = "", + text_font = ToontownGlobals.getSuitFont(), + text_scale = TTLocalizer.DPcogName, + text_align = TextNode.ACenter, + pos = (-0.948, 0, -1.15), + ) + + # make the cog level label + self.cogLevel = DirectLabel( + parent = self.frame, + relief = None, + text = "", + text_font = ToontownGlobals.getSuitFont(), + text_scale = 0.09, + text_align = TextNode.ACenter, + pos = (-0.91, 0, -1.02), + ) + + # this will let us globally scale and position the various parts + self.partFrame = DirectFrame( + parent = self.frame, + relief = None, + ) + + # these are the cog parts when present + self.parts = [] + for partNum in range(0, NumParts): + self.parts.append( + DirectFrame( + parent = self.partFrame, + relief = None, + geom = gui.find("**/robot/" + PartNames[partNum]), + ) + ) + + #self.parts[partNum].hide() + + # these are the holes when the cog parts are absent + self.holes = [] + for partNum in range(0, NumParts): + self.holes.append( + DirectFrame( + parent = self.partFrame, + relief = None, + geom = gui.find("**/robot_hole/" + PartNames[partNum]), + ) + ) + + # make the cog part label + self.cogPartRatio = DirectLabel( + parent = self.frame, + relief = None, + text = "", + text_font = ToontownGlobals.getSuitFont(), + text_scale = 0.08, + text_align = TextNode.ACenter, + pos = (-0.91, 0, -0.82), + ) + + # make the cog part label + self.cogMeritRatio = DirectLabel( + parent = self.frame, + relief = None, + text = "", + text_font = ToontownGlobals.getSuitFont(), + text_scale = 0.08, + text_align = TextNode.ACenter, + pos = (0.45, 0, -0.36), + ) + + meterFace = gui.find('**/meter_face_whole') + meterFaceHalf = gui.find('**/meter_face_half') + + self.meterFace = DirectLabel(parent = self.frame, + relief = None, + geom = meterFace, + color = self.meterColor, + pos = (0.455, 0.00, 0.04)) + self.meterFaceHalf1 = DirectLabel(parent = self.frame, + relief = None, + geom = meterFaceHalf, + color = self.meterActiveColor, + pos = (0.455, 0.00, 0.04)) + self.meterFaceHalf2 = DirectLabel(parent = self.frame, + relief = None, + geom = meterFaceHalf, + color = self.meterColor, + pos = (0.455, 0.00, 0.04)) + + self.frame.hide() + # Start with Sellbot page visible for first factory + self.activeTab = 3 + self.updatePage() + + def unload(self): + # call parent class unload + ShtikerPage.ShtikerPage.unload(self) + + def enter(self): + self.frame.show() + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + self.frame.hide() + ShtikerPage.ShtikerPage.exit(self) + + # updates + + def updatePage(self): + self.doTab(self.activeTab) + + def updatePartsDisplay(self, index, numParts, numPartsRequired): + # we cleverly made the part gui elements in the same order as + # as the parts bitmask so we can simply loop over them as so: + partBitmask = 1 + # the groupingBitmask helps us map smaller numbers of cog parts + # onto the full cog parts display. The bitmask works as such: + # + # left leg upper = 001 + # left leg lower = 010 + # left leg foot = 100 + # + # so if the grouping bit mask read 001 it would indicate that + # if "left leg upper" is present consider it a whole leg. Thus + # effectively mapping one part onto three. If it read 011, it would + # mean that this is a two part leg (consider the foot part of the + # lower leg). Etc. + # + groupingBitmask = CogDisguiseGlobals.PartsPerSuitBitmasks[index] + # previous part helps us map smaller numbers of cog parts onto the + # full cog parts displays. It determines if the only part we really + # care about (a 1 in the groupingBitmask) was present and what + # it's status was. + # + # 0 = no previous part was present + # 1 = previous part present + previousPart = 0 + + for part in self.parts: + groupingBit = groupingBitmask & partBitmask + + #print "not groupingBit and previousPart = %d and %d = %d" % (not groupingBit, + # previousPart, + # (not groupingBit and previousPart)) + #print "(%d & %d) & %d = \n %d & %d = %d" % (numParts, partBitmask, groupingBit, + # (numParts & partBitmask), groupingBit, + # (numParts & partBitmask) & groupingBit) + + # if we have the part + if (numParts & partBitmask)& groupingBit: + part.show() + self.holes[self.parts.index(part)].hide() + # mark the previous part as present + if groupingBit: + previousPart = 1 + # if we don't have the part, but the part is a don't care + elif (not groupingBit and previousPart): + part.show() + self.holes[self.parts.index(part)].hide() + # else we don't have the part + else: + self.holes[self.parts.index(part)].show() + part.hide() + previousPart = 0 + + #print "previousPart = ", previousPart + + # shift the mask to look at the next bit + partBitmask = partBitmask << 1 + + def updateMeritBar(self, dept): + # Update guage + merits = base.localAvatar.cogMerits[dept] + totalMerits = CogDisguiseGlobals.getTotalMerits( + base.localAvatar, dept) + if totalMerits == 0: + progress = 1 + else: + progress = min(merits/float(totalMerits), 1) + self.updateMeritDial(progress) + + if base.localAvatar.readyForPromotion(dept): + self.cogMeritRatio['text'] = TTLocalizer.DisguisePageMeritFull + self.promotionTitle.show() + self.progressTitle.hide() + else: + self.cogMeritRatio['text'] = "%d/%d" % (merits, totalMerits) + self.promotionTitle.hide() + self.progressTitle.show() + + def updateMeritDial(self, progress): + # Progress from 0 to 1 + if (progress == 0): + # Show empty dial + self.meterFaceHalf1.hide() + self.meterFaceHalf2.hide() + self.meterFace.setColor(self.meterColor) + elif (progress == 1): + # Show completely full dial + self.meterFaceHalf1.hide() + self.meterFaceHalf2.hide() + self.meterFace.setColor(self.meterActiveColor) + else: + # Show partially full dial + self.meterFaceHalf1.show() + self.meterFaceHalf2.show() + self.meterFace.setColor(self.meterColor) + if progress < 0.5: + self.meterFaceHalf2.setColor(self.meterColor) + else: + self.meterFaceHalf2.setColor(self.meterActiveColor) + progress = progress - 0.5 + # Turn dial accordingly + self.meterFaceHalf2.setR(180 * (progress/0.5)) + + ## CALLBACKS + def doTab(self, index): + self.activeTab = index + self.tabs[index].reparentTo(self.pageFrame) + # update the tab buttons + for i in range(len(self.tabs)): + tab = self.tabs[i] + if i == index: + tab['text0_fg'] = (1, 0, 0, 1) + tab['text2_fg'] = (1, 0, 0, 1) + else: + tab['text0_fg'] = (0, 0, 0, 1) + tab['text2_fg'] = (0.5,0.4,0.4,1) + # Set background color + self.bkgd.setColor(DeptColors[index]) + # Update the label + self.deptLabel['text'] = SuitDNA.suitDeptFullnames[ + SuitDNA.suitDepts[index]], + # determine the offset in head array for this dept + cogIndex = (base.localAvatar.cogTypes[index] + + (SuitDNA.suitsPerDept * index)) + cog = SuitDNA.suitHeadTypes[cogIndex] + # update the gui + self.progressTitle.hide() + if SuitDNA.suitDepts[index] == 'm': + self.progressTitle = self.cogbuckTitle + elif SuitDNA.suitDepts[index] == 'l': + self.progressTitle = self.juryNoticeTitle + elif SuitDNA.suitDepts[index] == 'c': + self.progressTitle = self.stockOptionTitle + else: + self.progressTitle = self.meritTitle + self.progressTitle.show() + self.cogName['text'] = SuitBattleGlobals.SuitAttributes[cog]['name'] + cogLevel = base.localAvatar.cogLevels[index] + self.cogLevel['text'] = (TTLocalizer.DisguisePageCogLevel % + str(cogLevel + 1)) + numParts = base.localAvatar.cogParts[index] + numPartsRequired = CogDisguiseGlobals.PartsPerSuit[index] + self.updatePartsDisplay(index, numParts, numPartsRequired) + self.updateMeritBar(index) + self.cogPartRatio['text'] = ( + "%d/%d" % + (CogDisguiseGlobals.getTotalParts(numParts), numPartsRequired)) + + diff --git a/toontown/src/shtiker/DisplaySettingsDialog.py b/toontown/src/shtiker/DisplaySettingsDialog.py new file mode 100644 index 0000000..2c3dfe0 --- /dev/null +++ b/toontown/src/shtiker/DisplaySettingsDialog.py @@ -0,0 +1,763 @@ +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.task.Task import Task +from direct.fsm import StateData +from direct.showbase import AppRunnerGlobal +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import TTLocalizer +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals + +class DisplaySettingsDialog(DirectFrame, StateData.StateData): + """DisplaySettingsDialog: + + This is a pop-up from the Options page that allows the user to + change his or her screen resolutions, rendering API, etc. + """ + + ApplyTimeoutSeconds = 15 + TimeoutCountdownTask = "DisplaySettingsTimeoutCountdown" + + WindowedMode = 0 + FullscreenMode = 1 + EmbeddedMode = 2 # embedded inside the web browser + + notify = DirectNotifyGlobal.directNotify.newCategory("DisplaySettingsDialog") + + def __init__(self): + """__init__(self) + """ + DirectFrame.__init__(self, + pos = (0, 0, 0.30), + relief = None, + image = DGG.getDefaultDialogGeom(), + image_scale = (1.6, 1, 1.2), + image_pos = (0,0,-0.05), + image_color = ToontownGlobals.GlobalDialogColor, + text = TTLocalizer.DisplaySettingsTitle, + text_scale = 0.12, + text_pos = (0, 0.4), + borderWidth = (0.01, 0.01), + ) + StateData.StateData.__init__(self, "display-settings-done") + self.setBin('gui-popup', 0) + self.initialiseoptions(DisplaySettingsDialog) + + def unload(self): + if self.isLoaded == 0: + return None + self.isLoaded = 0 + self.exit() + DirectFrame.destroy(self) + + def load(self): + if self.isLoaded == 1: + return None + self.isLoaded = 1 + + self.anyChanged = 0 + self.apiChanged = 0 + self.screenSizes = ((640, 480), + (800, 600), + (1024, 768), + (1280, 1024), + (1600, 1200)) + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + nameShopGui = loader.loadModel("phase_3/models/gui/nameshop_gui") + circle = nameShopGui.find("**/namePanelCircle") + + # Make a copy of the circle so we can move it up a bit. + innerCircle = circle.copyTo(hidden) + innerCircle.setPos(0, 0, 0.2) + + # Also copy the circle to the frame itself, as a white + # circular background behind the radio button circles (and a + # black ring behind that). + self.c1b = circle.copyTo(self, -1) + self.c1b.setColor(0, 0, 0, 1) + self.c1b.setPos(0.044, 0, -0.21) + self.c1b.setScale(0.4) + c1f = circle.copyTo(self.c1b) + c1f.setColor(1, 1, 1, 1) + c1f.setScale(0.8) + self.c2b = circle.copyTo(self, -2) + self.c2b.setColor(0, 0, 0, 1) + self.c2b.setPos(0.044, 0, -0.30) + self.c2b.setScale(0.4) + c2f = circle.copyTo(self.c2b) + c2f.setColor(1, 1, 1, 1) + c2f.setScale(0.8) + + self.c3b = circle.copyTo(self, -2) + self.c3b.setColor(0, 0, 0, 1) + self.c3b.setPos(0.044, 0, -0.40) + self.c3b.setScale(0.4) + c3f = circle.copyTo(self.c3b) + c3f.setColor(1, 1, 1, 1) + c3f.setScale(0.8) + + self.introText = DirectLabel( + parent = self, + relief = None, + scale = TTLocalizer.DSDintroText, + text = TTLocalizer.DisplaySettingsIntro, + text_wordwrap = TTLocalizer.DSDintroTextwordwrap, + text_align = TextNode.ALeft, + pos = (-0.725, 0, 0.3), + ) + + self.introTextSimple = DirectLabel( + parent = self, + relief = None, + scale = 0.06, + text = TTLocalizer.DisplaySettingsIntroSimple, + text_wordwrap = 25, + text_align = TextNode.ALeft, + pos = (-0.725, 0, 0.3), + ) + + self.apiLabel = DirectLabel( + parent = self, + relief = None, + scale = 0.06, + text = TTLocalizer.DisplaySettingsApi, + text_align = TextNode.ARight, + pos = (-0.08, 0, 0), + ) + self.apiMenu = DirectOptionMenu( + parent = self, + relief = DGG.RAISED, + scale = 0.06, + items = ['x'], + pos = (0, 0, 0), + ) + + self.screenSizeLabel = DirectLabel( + parent = self, + relief = None, + scale = 0.06, + text = TTLocalizer.DisplaySettingsResolution, + text_align = TextNode.ARight, + pos = (-0.08, 0, -0.10), + ) + self.screenSizeLeftArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + scale = (-1.0, 1.0, 1.0), # make arrow point the other way + pos = (0.04, 0, -0.085), + command = self.__doScreenSizeLeft, + ) + + self.screenSizeRightArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + pos = (0.54, 0, -0.085), + command = self.__doScreenSizeRight, + ) + + self.screenSizeValueText = DirectLabel( + parent = self, + relief = None, + text = "x", + text_align = TextNode.ACenter, + text_scale = 0.06, + pos = (0.29, 0, -0.10), + ) + + self.windowedButton = DirectCheckButton( + parent = self, + relief = None, + text = TTLocalizer.DisplaySettingsWindowed, + text_align = TextNode.ALeft, + text_scale = 0.6, + scale = 0.1, + boxImage = innerCircle, + boxImageScale = 2.5, + boxImageColor = VBase4(0, 0.25, 0.5, 1), + boxRelief = None, + pos = TTLocalizer.DSDwindowedButtonPos, + command = self.__doWindowed, + ) + + self.fullscreenButton = DirectCheckButton( + parent = self, + relief = None, + text = TTLocalizer.DisplaySettingsFullscreen, + text_align = TextNode.ALeft, + text_scale = 0.6, + scale = 0.1, + boxImage = innerCircle, + boxImageScale = 2.5, + boxImageColor = VBase4(0, 0.25, 0.5, 1), + boxRelief = None, + pos = TTLocalizer.DSDfullscreenButtonPos, + command = self.__doFullscreen, + ) + + self.embeddedButton = DirectCheckButton( + parent = self, + relief = None, + text = TTLocalizer.DisplaySettingsEmbedded, + text_align = TextNode.ALeft, + text_scale = 0.6, + scale = 0.1, + boxImage = innerCircle, + boxImageScale = 2.5, + boxImageColor = VBase4(0, 0.25, 0.5, 1), + boxRelief = None, + pos = TTLocalizer.DSDembeddedButtonPos, + command = self.__doEmbedded, + ) + + self.apply = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (0.6,1,1), + text = TTLocalizer.DisplaySettingsApply, + text_scale = 0.06, + text_pos = (0,-0.02), + pos = (0.52, 0, -0.53), + command = self.__apply, + ) + + self.cancel = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.DisplaySettingsCancel, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (0.6,1,1), + text_scale = TTLocalizer.DSDcancel, + text_pos = (TTLocalizer.DSDcancelButtonPositionX,-0.02), + pos = (0.20, 0, -0.53), + command = self.__cancel, + ) + + guiButton.removeNode() + gui.removeNode() + nameShopGui.removeNode() + innerCircle.removeNode() + + self.hide() + + def enter(self, changeDisplaySettings, changeDisplayAPI): + """enter(self, changeDisplaySettings) + if changeDisplaySettings is false, only the resolution may be + changed. + """ + if self.isEntered == 1: + return None + self.isEntered = 1 + # Use isLoaded to avoid redundant loading + if self.isLoaded == 0: + self.load() + + self.applyDialog = None + self.timeoutDialog = None + self.restoreDialog = None + self.revertDialog = None + base.transitions.fadeScreen(.5) + + properties = base.win.getProperties() + + self.screenSizeIndex = self.chooseClosestScreenSize( + properties.getXSize(), properties.getYSize()) + self.isFullscreen = properties.getFullscreen() + + # at this point we need to figure out correct display mode + # TODO check if we are embedded + if self.isCurrentlyEmbedded(): # check if embedded + self.displayMode = self.EmbeddedMode + pass + elif self.isFullscreen: + self.displayMode = self.FullscreenMode + else: + self.displayMode = self.WindowedMode + + self.updateApiMenu(changeDisplaySettings, changeDisplayAPI) + self.updateWindowed() + self.updateScreenSize() + + if changeDisplaySettings: + self.introText.show() + self.introTextSimple.hide() + if (changeDisplayAPI and (len(self.apis) > 1)): + self.apiLabel.show() + self.apiMenu.show() + else: + self.apiLabel.hide() + self.apiMenu.hide() + self.windowedButton.show() + self.fullscreenButton.show() + self.c1b.show() + self.c2b.show() + if self.isEmbeddedPossible(): + self.c3b.show() + self.embeddedButton.show() + else: + self.c3b.hide() + self.embeddedButton.hide() + else: + self.introText.hide() + self.introTextSimple.show() + self.apiLabel.hide() + self.apiMenu.hide() + self.windowedButton.hide() + self.fullscreenButton.hide() + self.c1b.hide() + self.c2b.hide() + self.c3b.hide() + + # Mark whether the user has made any changes since enter(). + self.anyChanged = 0 + self.apiChanged = 0 + + self.show() + return + + def exit(self): + """exit(self) + """ + if self.isEntered == 0: + return None + self.isEntered = 0 + + self.cleanupDialogs() + base.transitions.noTransitions() + + taskMgr.remove(self.TimeoutCountdownTask) + self.ignoreAll() + self.hide() + + messenger.send(self.doneEvent, [self.anyChanged, self.apiChanged]) + return + + def cleanupDialogs(self): + if self.applyDialog != None: + self.applyDialog.cleanup() + self.applyDialog = None + + if self.timeoutDialog != None: + self.timeoutDialog.cleanup() + self.timeoutDialog = None + + if self.restoreDialog != None: + self.restoreDialog.cleanup() + self.restoreDialog = None + + if self.revertDialog != None: + self.revertDialog.cleanup() + self.revertDialog = None + + def updateApiMenu(self, changeDisplaySettings, changeDisplayAPI): + # Get all of the available graphics pipes up front. + self.apis = [] + self.apiPipes = [] + # Only make the pipes if we are allowed to change APIs + if changeDisplayAPI: + base.makeAllPipes() + for pipe in base.pipeList: + if pipe.isValid(): + self.apiPipes.append(pipe) + self.apis.append(pipe.getInterfaceName()) + + self.apiMenu['items'] = self.apis + self.apiMenu.set(base.pipe.getInterfaceName()) + + def updateWindowed(self): + if self.displayMode == self.FullscreenMode: + self.windowedButton['indicatorValue'] = 0 + self.fullscreenButton['indicatorValue'] = 1 + self.embeddedButton['indicatorValue'] = 0 + elif self.displayMode == self.WindowedMode: + self.windowedButton['indicatorValue'] = 1 + self.fullscreenButton['indicatorValue'] = 0 + self.embeddedButton['indicatorValue'] = 0 + elif self.displayMode == self.EmbeddedMode: + self.windowedButton['indicatorValue'] = 0 + self.fullscreenButton['indicatorValue'] = 0 + self.embeddedButton['indicatorValue'] = 1 + + + def updateScreenSize(self): + xSize, ySize = self.screenSizes[self.screenSizeIndex] + self.screenSizeValueText['text'] = '%s x %s' % (xSize, ySize) + if self.screenSizeIndex > 0: + self.screenSizeLeftArrow.show() + else: + self.screenSizeLeftArrow.hide() + if self.screenSizeIndex < len(self.screenSizes) - 1: + self.screenSizeRightArrow.show() + else: + self.screenSizeRightArrow.hide() + + def chooseClosestScreenSize(self, currentXSize, currentYSize): + # First, look for an exact match. + for i in range(len(self.screenSizes)): + xSize, ySize = self.screenSizes[i] + if currentXSize == xSize and currentYSize == ySize: + return i + + # Failing that, look for the nearest match in total pixel + # count. + currentCount = currentXSize * currentYSize + bestDiff = None + bestI = None + for i in range(len(self.screenSizes)): + xSize, ySize = self.screenSizes[i] + diff = abs(xSize * ySize - currentCount) + if bestI == None or diff < bestDiff: + bestI = i + bestDiff = diff + + return bestI + + def __doWindowed(self, value): + self.displayMode = self.WindowedMode + self.updateWindowed() + + def __doFullscreen(self, value): + self.displayMode = self.FullscreenMode + self.updateWindowed() + + def __doEmbedded(self,value): + """Change the cur settings embedded mode.""" + self.displayMode = self.EmbeddedMode + self.updateWindowed() + + def __doScreenSizeLeft(self): + if self.screenSizeIndex > 0: + self.screenSizeIndex = self.screenSizeIndex - 1 + self.updateScreenSize() + + def __doScreenSizeRight(self): + if self.screenSizeIndex < len(self.screenSizes) - 1: + self.screenSizeIndex = self.screenSizeIndex + 1 + self.updateScreenSize() + + def __apply(self): + self.cleanupDialogs() + + # Move our dialog under the fade screen. + self.clearBin() + + self.applyDialog = TTDialog.TTDialog( + dialogName = 'DisplaySettingsApply', + style = TTDialog.TwoChoice, + text = TTLocalizer.DisplaySettingsApplyWarning % (self.ApplyTimeoutSeconds), + text_wordwrap = 15, + command = self.__applyDone, + ) + self.applyDialog.setBin('gui-popup', 0) + + def __applyDone(self, command): + self.applyDialog.cleanup() + self.applyDialog = None + + # Restore our dialog to the top of the fade screen. + self.setBin('gui-popup', 0) + base.transitions.fadeScreen(.5) + + if command != DGG.DIALOG_OK: + return + + # Now apply the new settings. + self.origPipe = base.pipe + self.origProperties = base.win.getProperties() + + pipe = self.apiPipes[self.apiMenu.selectedIndex] + properties = WindowProperties() + xSize, ySize = self.screenSizes[self.screenSizeIndex] + properties.setSize(xSize, ySize) + properties.setFullscreen(self.displayMode == self.FullscreenMode) + fullscreen = self.displayMode == self.FullscreenMode + embedded = self.displayMode == self.EmbeddedMode + if embedded: + if self.isEmbeddedPossible(): + # yeah you can go embedded + pass + else: + self.notify.warning("how was the player able to choose embedded") + embedded = False + #if not self.resetDisplayProperties(pipe, properties): + if not self.changeDisplayProperties(pipe, xSize, ySize, fullscreen, embedded): + # If we couldn't open the window, go back without + # bothering to prompt the user. + self.__revertBack(1) + return + + # And pop up a dialog giving the user a chance to acknowledge + # that the new settings worked. + + # Move our dialog back under the fade screen. + self.clearBin(); + + self.timeoutDialog = TTDialog.TTDialog( + dialogName = 'DisplaySettingsTimeout', + style = TTDialog.TwoChoice, + text = TTLocalizer.DisplaySettingsAccept % (self.ApplyTimeoutSeconds), + text_wordwrap = 15, + command = self.__timeoutDone + ) + self.timeoutDialog.setBin('gui-popup', 0) + self.timeoutRemaining = self.ApplyTimeoutSeconds + self.timeoutStart = None + taskMgr.add(self.__timeoutCountdown, self.TimeoutCountdownTask) + + def changeDisplayProperties(self, pipe, width, height, fullscreen = False, embedded = False): + """Do some processing before we call resetDisplayProperties.""" + result = False + self.notify.info("changeDisplayProperties") + if embedded: + if self.isEmbeddedPossible(): + width = base.appRunner.windowProperties.getXSize() + height = base.appRunner.windowProperties.getYSize() + self.current_pipe = base.pipe + self.current_properties = WindowProperties(base.win.getProperties()) + properties = self.current_properties + self.notify.debug("DISPLAY PREVIOUS:") + self.notify.debug(" EMBEDDED: %s" % bool(properties.getParentWindow ( ))) + self.notify.debug(" FULLSCREEN: %s" % bool(properties.getFullscreen ( ))) + self.notify.debug(" X SIZE: %s" % properties.getXSize ( )) + self.notify.debug(" Y SIZE: %s" % properties.getYSize ( )) + self.notify.debug("DISPLAY REQUESTED:") + self.notify.debug(" EMBEDDED: %s" % bool(embedded)) + self.notify.debug(" FULLSCREEN: %s" % bool(fullscreen)) + self.notify.debug(" X SIZE: %s" % width) + self.notify.debug(" Y SIZE: %s" % height) + + + if ((self.current_pipe == pipe) and \ + (bool(self.current_properties.getParentWindow( )) == bool(embedded)) and \ + (self.current_properties.getFullscreen ( ) == fullscreen) and \ + (self.current_properties.getXSize ( ) == width) and \ + (self.current_properties.getYSize ( ) == height)): + # no display change required + self.notify.info("DISPLAY NO CHANGE REQUIRED") + state = True + else: + properties = WindowProperties() + properties.setSize(width, height) + properties.setFullscreen(fullscreen) + properties.setParentWindow(0) + + if embedded: + properties = base.appRunner.windowProperties + + # get current sort order + original_sort = base.win.getSort ( ) + + if self.resetDisplayProperties(pipe, properties): + self.notify.debug("DISPLAY CHANGE SET") + + # verify display change + properties = base.win.getProperties() + + self.notify.debug("DISPLAY ACHIEVED:") + self.notify.debug(" EMBEDDED: %s" % bool(properties.getParentWindow ( ))) + self.notify.debug(" FULLSCREEN: %s" % bool(properties.getFullscreen ( ))) + self.notify.debug(" X SIZE: %s" % properties.getXSize ( )) + self.notify.debug(" Y SIZE: %s" % properties.getYSize ( )) + + if ((bool(properties.getParentWindow( )) == bool(embedded)) and \ + (properties.getFullscreen ( ) == fullscreen) and \ + (properties.getXSize ( ) == width) and \ + (properties.getYSize ( ) == height)): + self.notify.info("DISPLAY CHANGE VERIFIED") + result = True + else: + self.notify.warning("DISPLAY CHANGE FAILED, RESTORING PREVIOUS DISPLAY") + #self.restoreWindowProperties ( options ) + else: + self.notify.warning("DISPLAY CHANGE FAILED") + #self.notify.warning("DISPLAY SET - BEFORE RESTORE") + #self.restoreWindowProperties ( options ) + #self.notify.warning("DISPLAY SET - AFTER RESTORE") + + # set current sort order + base.win.setSort (original_sort) + + base.graphicsEngine.renderFrame() + base.graphicsEngine.renderFrame() + + return result + + + def __timeoutCountdown(self, task): + # This task monitors the countdown timer for accepting the new + # display settings. + + # Reset the time we started to the first time this task runs. + # We do it this way, instead of setting it when we spawn the + # task, to ensure we don't start counting until after the new + # window is created. + if self.timeoutStart == None: + self.timeoutStart = globalClock.getRealTime() + elapsed = int(globalClock.getFrameTime() - self.timeoutStart) + remaining = max(self.ApplyTimeoutSeconds - elapsed, 0) + if remaining < self.timeoutRemaining: + self.timeoutRemaining = remaining + self.timeoutDialog['text'] = TTLocalizer.DisplaySettingsAccept % (remaining), + if remaining == 0: + # Automatically cancel when the time is up. + self.__timeoutDone('cancel') + return Task.done + return Task.cont + + def __timeoutDone(self, command): + taskMgr.remove(self.TimeoutCountdownTask) + + self.timeoutDialog.cleanup() + self.timeoutDialog = None + + # Restore our dialog to the top of the fade screen. + self.setBin('gui-popup', 0) + base.transitions.fadeScreen(.5) + + if command == DGG.DIALOG_OK: + # An 'ok' means we're happy with the new settings. + self.anyChanged = 1 + self.exit() + return + + # A cancel or timeout means we want the old settings back. + self.__revertBack(0) + return + + def __revertBack(self, reason): + # Reason code: + # 0 - user cancelled. No explanation needed. + # 1 - pipe just didn't work. + # First, restore the original display settings. + if not self.resetDisplayProperties(self.origPipe, self.origProperties): + # Oops, we couldn't get the original settings back! + self.notify.warning("Couldn't restore original display settings!") + # This is kind of like a low-level panic situation. + base.panda3dRenderError() + + # Now open a dialog telling the user they've been restored. + + # Move our dialog under the fade screen. + self.clearBin() + + if reason == 0: + revertText = TTLocalizer.DisplaySettingsRevertUser + else: + revertText = TTLocalizer.DisplaySettingsRevertFailed + + self.revertDialog = TTDialog.TTDialog( + dialogName = 'DisplaySettingsRevert', + style = TTDialog.Acknowledge, + text = revertText, + text_wordwrap = 15, + command = self.__revertDone, + ) + self.revertDialog.setBin('gui-popup', 0) + + def __revertDone(self, command): + self.revertDialog.cleanup() + self.revertDialog = None + + # Restore our dialog to the top of the fade screen. + self.setBin('gui-popup', 0) + base.transitions.fadeScreen(.5) + + return + + def __cancel(self): + self.exit() + + def resetDisplayProperties(self, pipe, properties): + if base.win: + currentProperties = base.win.getProperties() + gsg = base.win.getGsg() + else: + currentProperties = WindowProperties.getDefault() + gsg = None + + # Check to see if the window properties will change in any + # important way. + newProperties = WindowProperties(currentProperties) + newProperties.addProperties(properties) + + if base.pipe != pipe: + self.apiChanged = 1 + gsg = None + + if gsg == None or \ + currentProperties.getFullscreen() != newProperties.getFullscreen() or \ + (currentProperties.getParentWindow() != newProperties.getParentWindow()): + # For now, assume that if we change fullscreen state, we + # need to destroy the window and create a new one. + + self.notify.debug("window properties: %s" % properties) + self.notify.debug("gsg: %s" % gsg) + + base.pipe = pipe + if not base.openMainWindow(props = properties, gsg = gsg, + keepCamera = True): + self.notify.warning("OPEN MAIN WINDOW FAILED") + return 0 + self.notify.info("OPEN MAIN WINDOW PASSED") + + base.disableShowbaseMouse() + NametagGlobals.setCamera(base.cam) + NametagGlobals.setMouseWatcher(base.mouseWatcherNode) + + # Force a frame to render for good measure. This should + # force the window open right now, which helps us avoid + # starting the countdown timer before the window is open. + # Also, we can check to see if the window actually opened + # or not. + base.graphicsEngine.renderFrame() + base.graphicsEngine.renderFrame() + + base.graphicsEngine.openWindows() + if base.win.isClosed(): + self.notify.info("Window did not open, removing.") + base.closeWindow(base.win) + return 0 + + else: + # If the properties are changing only slightly + # (e.g. window size), we can keep the current window and + # just adjust its properties directly. + self.notify.debug("Adjusting properties") + base.win.requestProperties(properties) + base.graphicsEngine.renderFrame() + + return 1 + + def isEmbeddedPossible(self): + """Returns True if embedded in the browser is a valid option.""" + result = False + # RAU per Robin we don't want embedded, uncomment if they change their minds + #if base.appRunner and base.appRunner.windowProperties: + # result = True + return result + + def isCurrentlyEmbedded(self): + """Returns true if the current game window is inside a browser.""" + result = False + if base.win.getProperties().getParentWindow(): + result = True + return result + diff --git a/toontown/src/shtiker/EmotePage.py b/toontown/src/shtiker/EmotePage.py new file mode 100644 index 0000000..a253b54 --- /dev/null +++ b/toontown/src/shtiker/EmotePage.py @@ -0,0 +1,201 @@ +"""EmotePage module: contains the EmotePage class""" + +from toontown.toonbase import ToontownGlobals +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from toontown.toon import Toon + +PICKER_START_POS = (-0.555, 0, 0) +MAX_FRAMES = 15 + +emoteAnimDict = {'Jump': 'jump', + 'Happy': 'Happy', + 'Sad': 'Sad', + 'Sleepy':'Sleep', + 'Dance': 'victory'} + +class EmoteFrame(DirectFrame): + def __init__(self, emoteName = "?"): + DirectFrame.__init__(self, relief = None) + bookModel = loader.loadModel("phase_3.5/models/gui/stickerbook_gui") + self.normalTextColor = (0.3,0.25,0.2,1) + self.name = emoteName + self.frame = DirectFrame( + parent = self, + relief = None, + image = bookModel.find("**/paper_note"), + image_scale = (0.8,0.9,0.9), + text = self.name, + text_pos = (0,-0.34), + text_fg = self.normalTextColor, + text_scale = 0.15, + ) + self.question = DirectLabel( + parent = self.frame, + relief = None, + pos = (0,0,-0.15), + text = "?", + text_scale = 0.4, + text_pos = (0,0), + text_fg = (0.3,0.25,0.2,0.1), + ) + self.toon = None + if self.name != '?': + self.makeToon() + bookModel.removeNode() + + def updateEmote(self, emoteName): + self.name = emoteName + self.frame['text'] = self.name + self.frame.setText() + self.makeToon() + self.question.hide() + + def makeToon(self): + if self.toon != None: + del self.toon + + self.toon = Toon.Toon() + self.toon.setDNA(base.localAvatar.getStyle()) + self.toon.getGeomNode().setDepthWrite(1) + self.toon.getGeomNode().setDepthTest(1) + # Conserve polygons + self.toon.useLOD(500) + self.toon.reparentTo(self.frame) + self.toon.setPosHprScale(0,10,-0.25, 210,0,0, 0.15,0.15,0.15) + self.toon.loop('neutral') + + try: + anim = emoteAnimDict[self.name] + except: + print "we didnt get the right animation" + anim = 'neutral' + + #self.toon.pose(anim, self.toon.getNumFrames(anim)/2) + self.toon.animFSM.request(anim) + + def play(self, trackId): + if ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(5))): + anim = emoteAnimDict[self.emoteName] + else: + anim = 'neutral' + if self.toon == None: + self.makeToon() + self.toon.play(anim) + + def setTrained(self, trackId): + # make sure this frame has a toon + if self.toon == None: + self.makeToon() + # Make sure we are downloaded + # TODO: show something better here or download these animations sooner + if ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(5))): + anim = emoteAnimDict[self.emoteName] + else: + anim = 'neutral' + self.toon.pose(anim, 0) + self.toon.show() + self.question.hide() + self.frame['image_color'] = Vec4(1,1,1,1) + + def setUntrained(self, trackId): + if self.toon: + self.toon.hide() + self.question.show() + self.frame['image_color'] = Vec4(0.8,0.8,0.8,0.5) + +class EmotePage(ShtikerPage.ShtikerPage): + """EmotePage class: keeps track of which emotes a toon can use""" + + # special methods + def __init__(self): + """__init__(self) + EmotePage constructor: create the emote page + """ + ShtikerPage.ShtikerPage.__init__(self) + self.emotes = [] + self.emoteFrames = [] + self.avatar = None + self.state = DGG.NORMAL + + def setAvatar(self, av): + self.avatar = av + + def getAvatar(self): + return self.avatar + + def placeFrames(self): + rowPos = [0.26, -0.09, -0.44] + colPos = [-0.70, -0.35, 0, 0.35, 0.70] + for index in range(1, MAX_FRAMES+1): + frame = self.emoteFrames[index-1] + col = (index - 1) % 5 + row = (index - 1) / 5 + frame.setPos(colPos[col], 0, rowPos[row]) + frame.setScale(0.4) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + # page title + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.EmotePageTitle, + text_scale = 0.12, + pos = (0,0,0.6), + ) + # Count up from 1 + for index in range(1, MAX_FRAMES+1): + if index < len(self.emotes): + frame = EmoteFrame(self.emotes[index-1]) + else: + frame = EmoteFrame() + frame.reparentTo(self) + self.emoteFrames.append(frame) + self.placeFrames() + + self.updatePage() + + def unload(self): + del self.title + del self.emoteFrames + + ShtikerPage.ShtikerPage.unload(self) + + def updatePage(self): + # the emote list has changed, update the picker + newEmotes = base.localAvatar.emotes + + # Add new buttons + for i in range(len(newEmotes)): + emote = newEmotes[i] + self.emotes.append(emote) + self.emoteFrames[i].updateEmote(emote) + + def makeEmoteButton(self, emote): + return DirectButton( + parent = self, + relief = None, + text = emote, + text_scale = 0.08, + text_align = TextNode.ALeft, + text1_bg = Vec4(1,1,0,1), + text2_bg = Vec4(0.5,0.9,1,1), + text3_fg = Vec4(0.4,0.8,0.4,1), + command = self.showEmotePanel, + extraArgs = [emote], + pos = (-0.25+.05*len(self.emotes), 0, -0.25), + ) + + def showEmotePanel(self): + # pop up a little doober + self.emotePanel.show() + + def hideEmotePanel(self): + # hide the little doober + self.emotePanel.hide() + diff --git a/toontown/src/shtiker/EventsPage.py b/toontown/src/shtiker/EventsPage.py new file mode 100644 index 0000000..b110390 --- /dev/null +++ b/toontown/src/shtiker/EventsPage.py @@ -0,0 +1,1696 @@ +"""EventsPage module: contains the EventsPage class""" +import urllib + +from pandac.PandaModules import Vec4, Vec3, TextNode, PNMImage, StringStream, Texture, HTTPClient, DocumentSpec, Ramfile, Point3 + +from direct.task.Task import Task +from direct.gui.DirectGui import DirectFrame, DirectLabel, DirectButton, DirectScrolledList, DirectCheckButton, OnscreenText +from direct.gui import DirectGuiGlobals +from direct.directnotify import DirectNotifyGlobal + +from otp.otpbase import OTPLocalizer + +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals +from toontown.toontowngui import TTDialog + +from toontown.toon import GMUtils + +from toontown.parties import PartyGlobals +from toontown.parties import PartyUtils +from toontown.parties.CalendarGuiMonth import CalendarGuiMonth +from toontown.parties.PartyUtils import getPartyActivityIcon +from toontown.parties.Party import Party +from toontown.parties.ServerTimeGui import ServerTimeGui +# from toontown.parties import feedparser + +import ShtikerPage + +# display tab modes +EventsPage_Host = 0 +EventsPage_Invited = 1 +EventsPage_Calendar = 2 +EventsPage_News = 3 + +class EventsPage(ShtikerPage.ShtikerPage): + """ + EventsPage in shtiker book shows calendar, hosting, invitations, and news tab + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("EventsPage") + + # warning self.rssFeed garbage leaks + UseNewsTab = base.config.GetBool('want-news-tab', 0) + DefaultNewsUrl = "/news/news_urls.txt" + NewsUrl = base.config.GetString('news-url', DefaultNewsUrl) + DownloadArticlesTaskName = "downloadArticlesTask" + NonblockingDownload = base.config.GetBool("news-nonblocking",1) + + def __init__(self): + """__init__(self) + EventsPage constructor: create the Parties selector page + """ + ShtikerPage.ShtikerPage.__init__(self) + self.mode = EventsPage_Calendar + self.setMode(self.mode) + self.noTeleport = config.GetBool("Parties-page-disable", 0) + self.isPrivate = True + self.gotRssFeed = False + self.gotArticles = False + self.newsList = None + self.articleTextList = None + self.articleIndexList = None + self.hostedPartyInfo = None + self.downloadArticlesInProgress = False + + def load(self): + self.scrollButtonGui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + self.hostingGui = loader.loadModel("phase_4/models/parties/schtickerbookHostingGUI") + self.invitationGui = loader.loadModel("phase_4/models/parties/schtickerbookInvitationGUI") + self.activityIconsModel = loader.loadModel("phase_4/models/parties/eventSignIcons") + self.decorationModels = loader.loadModel("phase_4/models/parties/partyDecorations") + + self.loadTabs() + self.loadHostingTab() + self.loadInvitationsTab() + self.loadCalendarTab() + self.loadNewsTab() + + self.titleLabel = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.EventsPageHostTabTitle, + text_scale = TTLocalizer.EPtitleLabel, + textMayChange = True, + pos = self.hostingGui.find("**/myNextParty_text_locator").getPos(), + ) + + def loadTabs(self): + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1.0, 1.0, 1.0, 1.0) + clickColor = (0.8, 0.8, 0.0, 1.0) + rolloverColor = (0.15, 0.82, 1.0, 1.0) + diabledColor = (1.0, 0.98, 0.15, 1.0) + gui = loader.loadModel("phase_3.5/models/gui/fishingBook") + self.hostTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.EventsPageHostTabName, + text_scale = TTLocalizer.EPhostTab, + text_align = TextNode.ACenter, + text_pos = (0.12, 0.0), + image = gui.find("**/tabs/polySurface1"), + image_pos = (0.55,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [EventsPage_Host], + pos = (0.92, 0, 0.55), + ) + self.invitedTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.EventsPageInvitedTabName, + text_scale = TTLocalizer.EPinvitedTab, + text_pos = (0.12, 0.0), + text_align = TextNode.ACenter, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [EventsPage_Invited], + pos = (0.92, 0, 0.1), + ) + self.calendarTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.EventsPageCalendarTabName, + text_scale = TTLocalizer.EPcalendarTab, + text_pos = (0.12, 0.0), + text_align = TextNode.ACenter, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [EventsPage_Calendar], + pos = (0.92, 0, 0.1), + ) + self.newsTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.EventsPageNewsTabName, + text_scale = TTLocalizer.EPnewsTab, + text_pos = (0.12, 0.0), + text_align = TextNode.ACenter, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [EventsPage_News], + pos = (0.92, 0, 0.1), + ) + self.newsTab.hide() + + if self.UseNewsTab: + self.newsTab.show() + self.calendarTab.setPos(-0.75,0,0.775) + self.hostTab.setPos(-0.33,0,0.775) + self.invitedTab.setPos(0.09,0,0.775) + self.newsTab.setPos(0.51,0,0.775) + else: + self.calendarTab.setPos(-0.55,0,0.775) + self.hostTab.setPos(-0.13,0,0.775) + self.invitedTab.setPos(0.28,0,0.775) + + def loadHostingTab(self): + # tab node for hosted party + self.hostedPartyDisplay = self.attachNewNode("Hosting") + self.hostedPartyDisplay.setPos(0.0, 0.0, 0.04) + self.hostingBackgroundFlat = DirectFrame( + parent = self.hostedPartyDisplay, + relief = None, + geom = self.hostingGui.find("**/background_flat"), + ) + + # create scroll lists to display party guests, activities, and decors + self.hostingGuestList, self.hostingGuestLabel = self.createListAndLabel(self.hostedPartyDisplay, self.hostingGui, "guests", 7) + self.hostingActivityList, self.hostingActivityLabel = self.createListAndLabel(self.hostedPartyDisplay, self.hostingGui, "activities", 1) + self.hostingDecorationList, self.hostingDecorationLabel = self.createListAndLabel(self.hostedPartyDisplay, self.hostingGui, "decorations", 1) + + self.hostingDateLabel = DirectLabel( + parent = self.hostedPartyDisplay, + relief = None, + text = "", + scale = TTLocalizer.EPhostingDateLabel, + text_align = TextNode.ACenter, + text_wordwrap = 10, + textMayChange = True, + pos = self.hostingGui.find("**/date_locator").getPos(), + ) + pos = self.hostingGui.find("**/cancel_text_locator").getPos() + self.hostingCancelButton = DirectButton( + parent = self.hostedPartyDisplay, + relief = None, + geom = ( + self.hostingGui.find("**/cancelPartyButton_up"), + self.hostingGui.find("**/cancelPartyButton_down"), + self.hostingGui.find("**/cancelPartyButton_rollover"), + self.hostingGui.find("**/cancelPartyButton_inactive"), + ), + text = TTLocalizer.EventsPageHostTabCancelButton, + text_scale = TTLocalizer.EPhostingCancelButton, + text_pos=(pos[0], pos[2]), + command = self.__doCancelParty, + ) + pos = self.hostingGui.find("**/startParty_text_locator").getPos() + self.partyGoButton = DirectButton( + parent = self.hostedPartyDisplay, + relief = None, + geom = ( + self.hostingGui.find("**/startPartyButton_up"), + self.hostingGui.find("**/startPartyButton_down"), + self.hostingGui.find("**/startPartyButton_rollover"), + self.hostingGui.find("**/startPartyButton_inactive"), + ), + text = TTLocalizer.EventsPageGoButton, + text_scale = TTLocalizer.EPpartyGoButton, + text_pos = (pos[0], pos[2]), + textMayChange = True, + command = self._startParty, + ) + self.publicPrivateLabel = DirectLabel( + parent = self.hostedPartyDisplay, + relief = None, + text = TTLocalizer.EventsPageHostTabPublicPrivateLabel, + text_scale = TTLocalizer.EPpublicPrivateLabel, + text_align = TextNode.ACenter, + pos = self.hostingGui.find("**/thisPartyIs_text_locator").getPos(), + ) + pos = self.hostingGui.find("**/public_text_locator").getPos() + checkedImage = self.hostingGui.find("**/checked_button") + uncheckedImage = self.hostingGui.find("**/unchecked_button") + self.publicButton = DirectCheckButton( + parent = self.hostedPartyDisplay, + relief = None, + scale = 0.1, + boxBorder = 0.08, + boxImage = (uncheckedImage,checkedImage,None), + boxImageScale = 10, + boxRelief = None, + text = TTLocalizer.EventsPageHostTabToggleToPublic, + text_align = TextNode.ALeft, + text_scale = TTLocalizer.EPpublicButton, + pos = pos, + command = self.__changePublicPrivate, + indicator_pos = (-0.7, 0, 0.2), + ) + pos = self.hostingGui.find("**/private_text_locator").getPos() + self.privateButton = DirectCheckButton( + parent = self.hostedPartyDisplay, + relief = None, + scale = 0.1, + boxBorder = 0.08, + boxImage = (uncheckedImage,checkedImage,None), + boxImageScale = 10, + boxRelief = None, + text = TTLocalizer.EventsPageHostTabToggleToPrivate, + text_align = TextNode.ALeft, + text_scale = TTLocalizer.EPprivateButton, + pos = pos, + command = self.__changePublicPrivate, + indicator_pos = (-0.7, 0, 0.2), + ) + + self.confirmCancelPartyEvent = "confirmCancelPartyEvent" + self.accept(self.confirmCancelPartyEvent, self.confirmCancelOfParty) + self.confirmCancelPartyGui = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("confirmCancelPartyGui"), + doneEvent = self.confirmCancelPartyEvent, + message = TTLocalizer.EventsPageConfirmCancel%int(PartyGlobals.PartyRefundPercentage*100.0), + style = TTDialog.YesNo, + okButtonText = OTPLocalizer.DialogYes, + cancelButtonText = OTPLocalizer.DialogNo, + ) + self.confirmCancelPartyGui.doneStatus = "" + self.confirmCancelPartyGui.hide() + + self.confirmTooLatePartyEvent = "confirmTooLatePartyEvent" + self.accept(self.confirmTooLatePartyEvent, self.confirmTooLateParty) + self.confirmTooLatePartyGui = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("confirmTooLatePartyGui"), + doneEvent = self.confirmTooLatePartyEvent, + message = TTLocalizer.EventsPageTooLateToStart, + style = TTDialog.Acknowledge, + ) + self.confirmTooLatePartyGui.hide() + + self.confirmPublicPrivateChangeEvent = "confirmPublicPrivateChangeEvent" + self.accept(self.confirmPublicPrivateChangeEvent, self.confirmPublicPrivateChange) + self.confirmPublicPrivateGui = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("confirmPublicPrivateGui"), + doneEvent = self.confirmPublicPrivateChangeEvent, + message = TTLocalizer.EventsPagePublicPrivateNoGo, + style = TTDialog.Acknowledge, + ) + self.confirmPublicPrivateGui.hide() + + self.cancelPartyResultGuiEvent = "cancelPartyResultGuiEvent" + self.accept(self.cancelPartyResultGuiEvent, self.cancelPartyResultGuiCommand) + self.cancelPartyResultGui = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("cancelPartyResultGui"), + doneEvent = self.cancelPartyResultGuiEvent, + message = TTLocalizer.EventsPageCancelPartyResultOk % 0, + style = TTDialog.Acknowledge, + ) + self.cancelPartyResultGui.doneStatus = "" + self.cancelPartyResultGui.hide() + + self.__setPublicPrivateButton() + + def loadInvitationsTab(self): + self.invitationDisplay = self.attachNewNode("invitations") + self.invitationDisplay.setPos(0.0, 0.0, 0.04) + + self.invitationBackgroundFlat = DirectFrame( + parent = self.invitationDisplay, + relief = None, + geom = self.invitationGui.find("**/background_flat"), + ) + self.invitationPartiesFlat = DirectFrame( + parent = self.invitationDisplay, + relief = None, + geom = self.invitationGui.find("**/parties_background"), + ) + self.invitationActivtiesFlat = DirectFrame( + parent = self.invitationDisplay, + relief = None, + geom = self.invitationGui.find("**/activities_background"), + ) + + # create scroll lists to display parties and activities + self.invitationPartyList, self.invitationPartyLabel = self.createListAndLabel(self.invitationDisplay, self.invitationGui, "parties", 7, "ButtonDown", "ButtonUp", "Text_locator") + self.invitationActivityList, self.invitationActivityLabel = self.createListAndLabel(self.invitationDisplay, self.invitationGui, "activities", 1, "ButtonDown", "ButtonUp", "Text_locator") + + pos = self.invitationGui.find("**/startText_locator").getPos() + self.invitePartyGoButton = DirectButton( + parent = self.invitationDisplay, + relief = None, + geom = ( + self.invitationGui.find("**/startButton_up"), + self.invitationGui.find("**/startButton_down"), + self.invitationGui.find("**/startButton_rollover"), + self.invitationGui.find("**/startButton_inactive"), + ), + text = TTLocalizer.EventsPageInviteGoButton, + text_scale = TTLocalizer.EPinvitePartyGoButton, + text_pos = (pos[0], pos[2]), + textMayChange = True, + command = self._inviteStartParty, + ) + + self.invitationDateTimeLabel = DirectLabel( + parent = self.invitationDisplay, + relief = None, + text = "", + textMayChange = True, + text_scale = 0.07, + pos = (0,0,-0.65), + ) + + def loadCalendarTab(self): + # tab node calendar + self.calendarDisplay = self.attachNewNode("calendar") + # placeholder calendar items + self.toontownTimeLabel = DirectLabel( + parent = self.calendarDisplay, + pos = (0.175, 0, -0.69), + text_align = TextNode.ARight, + relief = None, + text = TTLocalizer.EventsPageToontownTimeIs, + text_scale = 0.065, + text_font = ToontownGlobals.getMinnieFont(), + text_fg = (255/255.0, 146/255.0, 113/255.0, 1), + textMayChange = 0, + ) + + curServerDate = base.cr.toontownTimeManager.getCurServerDateTime() + self.calendarGuiMonth = CalendarGuiMonth( + self.calendarDisplay, + curServerDate, + ) + + pos = (0.35, 0, -0.69) + self.toontownTimeGui = ServerTimeGui(self.calendarDisplay, pos) + + def loadNewsTab(self): + # news node + self.newsDisplay = self.attachNewNode("news") + newspaper = loader.loadModel("phase_4/models/parties/tt_m_gui_sbk_newspaper.bam") + # debbie made the asset centered, but we need shift it down + self.newsFrame = DirectLabel( + relief = None, + parent = self.newsDisplay, + pos = (0,0,-0.1), + #image = newspaper, + ) + # I don't understand why setting newspaper as the image in the newsFrame + # screws up transparency + newspaper.reparentTo(self.newsFrame) + self.createArticleTextList() + self.articleImage = None + self.newsStatusLabel = DirectLabel( + text= TTLocalizer.EventsPageNewsDownloading, + relief = None, + text_scale = 0.1, + text_wordwrap = 13, + parent = self.newsFrame, + pos = (0,0,0.275) + ) + self.createArticleIndexList() + titlePos = self.newsFrame.find("**/loc_toontimeTimes").getPos() + self.newsPaperTitle = DirectLabel( + text= TTLocalizer.EventsPageNewsPaperTitle, + relief = None, + text_scale = (0.13, 0.25, 1), + text_align = TextNode.ACenter, + text_font = ToontownGlobals.getMinnieFont(), + parent = self.newsFrame, + pos = titlePos, + ) + + subLeftPos = self.newsFrame.find("**/loc_subheaderLf").getPos() + subRightPos = self.newsFrame.find("**/loc_subheaderRt").getPos() + self.subLeft = DirectLabel( + text= TTLocalizer.EventsPageNewsLeftSubtitle, + relief = None, + text_scale = 0.05, + text_align = TextNode.ALeft, + parent = self.newsFrame, + pos = subLeftPos, + ) + self.subRight = DirectLabel( + text= TTLocalizer.EventsPageNewsRightSubtitle, + relief = None, + text_scale = 0.05, + text_align = TextNode.ARight, + parent = self.newsFrame, + pos = subRightPos, + ) + if self.UseNewsTab: + self.downloadArticles() # putting it here means we download as soons as game starts + + + def getGuestItem(self, name, inviteStatus): + label = DirectLabel( + relief = None, + text = name, + text_scale = 0.045, + text_align = TextNode.ALeft, + textMayChange=True, + ) + dot = DirectFrame( + relief = None, + geom = self.hostingGui.find('**/questionMark'), + pos = (0.5, 0.0, 0.01), + ) + if inviteStatus == PartyGlobals.InviteStatus.Accepted: + # green check + dot["geom"] = self.hostingGui.find('**/checkmark'), + elif inviteStatus == PartyGlobals.InviteStatus.Rejected: + # red x + dot["geom"] = self.hostingGui.find('**/x'), + PartyUtils.truncateTextOfLabelBasedOnWidth(label, name, PartyGlobals.EventsPageGuestNameMaxWidth) + dot.reparentTo(label) + return label + + def getActivityItem(self, activityBase, count =1): + """ + Lookup label for the activity + + Returns + DirectLabel with activity information + """ + activityName = TTLocalizer.PartyActivityNameDict[activityBase.activityId]["generic"] + if count == 1: + textForActivity = activityName + else: + textForActivity = "%s x %d" % (activityName, count) + + # Get the party icon + iconString = "" + if activityBase.activityId == PartyGlobals.ActivityIds.PartyJukebox40: + iconString = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyJukebox) + elif activityBase.activityId == PartyGlobals.ActivityIds.PartyDance20: + iconString = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyDance) + else: + iconString = PartyGlobals.ActivityIds.getString(activityBase.activityId) + + geom = getPartyActivityIcon(self.activityIconsModel, iconString) + + label = DirectLabel( + relief = None, + geom = geom, + geom_scale = 0.38, + geom_pos = Vec3(0.0, 0.0, -0.17), + text = textForActivity, + text_scale = TTLocalizer.EPactivityItemLabel, + text_align = TextNode.ACenter, + text_pos = (-0.01, -0.43), + text_wordwrap = 7.0 + ) + return label + + def getDecorationItem(self, decorBase, count =1 ): + # look up name of decoration + decorationName = TTLocalizer.PartyDecorationNameDict[decorBase.decorId]["editor"] + if count == 1: + textForDecoration = decorationName + else: + textForDecoration = decorationName + " x " + str(count) + assetName = PartyGlobals.DecorationIds.getString(decorBase.decorId) + if assetName == "Hydra": + assetName = "StageSummer" + label = DirectLabel( + relief = None, + geom = self.decorationModels.find("**/partyDecoration_%s"%assetName), + text = textForDecoration, + text_scale = TTLocalizer.EPdecorationItemLabel, + text_align = TextNode.ACenter, + text_pos = (-0.01, -0.43), + text_wordwrap = 7.0 + ) + # These need to be assigned after construction... not sure why. + label["geom_scale"] = (2.6, 0.01, 0.05) + label["geom_pos"] = (0.0, 0.0, -0.33) + return label + + def getToonNameFromAvId(self, avId): + result = TTLocalizer.EventsPageUnknownToon + sender = base.cr.identifyAvatar(avId) + if sender: + result =sender.getName() + return result + + def loadInvitations(self): + EventsPage.notify.debug("loadInvitations") + + self.selectedInvitationItem = None + self.invitationPartyList.removeAndDestroyAllItems() + self.invitationActivityList.removeAndDestroyAllItems() + self.invitePartyGoButton["state"] = DirectGuiGlobals.DISABLED + + for partyInfo in base.localAvatar.partiesInvitedTo: + # If a party is cancelled or finished, don't show it here + if partyInfo.status == PartyGlobals.PartyStatus.Cancelled or partyInfo.status == PartyGlobals.PartyStatus.Finished: + continue + inviteInfo = None + # We need the inviteInfo to see if they've read the invite or not. + for inviteInfo in base.localAvatar.invites: + if partyInfo.partyId == inviteInfo.partyId: + break + if inviteInfo is None: + EventsPage.notify.error("No invitation info for party id %d" % partyInfo.partyId) + return + # Only show invites that you've read in the mailbox + if inviteInfo.status == PartyGlobals.InviteStatus.NotRead: + continue + + hostName = self.getToonNameFromAvId(partyInfo.hostId) + if GMUtils.testGMIdentity(hostName): + hostName = GMUtils.handleGMName(hostName) + item = DirectButton( + relief = None, + text = hostName, + text_align = TextNode.ALeft, + text_bg = Vec4(0.0, 0.0, 0.0, 0.0), + text_scale = 0.045, + textMayChange = True, + command = self.invitePartyClicked, + ) + PartyUtils.truncateTextOfLabelBasedOnWidth(item, hostName, PartyGlobals.EventsPageHostNameMaxWidth) + + item["extraArgs"] = [item] + item.setPythonTag("activityIds", partyInfo.getActivityIds()) + item.setPythonTag("partyStatus", partyInfo.status) + item.setPythonTag("hostId", partyInfo.hostId) + item.setPythonTag("startTime", partyInfo.startTime) + self.invitationPartyList.addItem(item) + + def invitePartyClicked(self, item): + if item.getPythonTag("partyStatus") == PartyGlobals.PartyStatus.Started: + self.invitePartyGoButton["state"] = DirectGuiGlobals.NORMAL + else: + self.invitePartyGoButton["state"] = DirectGuiGlobals.DISABLED + if self.selectedInvitationItem is not None: + self.selectedInvitationItem["state"] = DirectGuiGlobals.NORMAL + self.selectedInvitationItem["text_bg"] = Vec4(0.0, 0.0, 0.0, 0.0) + self.selectedInvitationItem = item + self.selectedInvitationItem["state"] = DirectGuiGlobals.DISABLED + self.selectedInvitationItem["text_bg"] = Vec4(1.0, 1.0, 0.0, 1.0) + self.fillInviteActivityList(item.getPythonTag("activityIds")) + startTime = item.getPythonTag("startTime") + self.invitationDateTimeLabel["text"] = TTLocalizer.EventsPageInvitedTabTime % ( + PartyUtils.formatDate( startTime.year, startTime.month, startTime.day ), + PartyUtils.formatTime( startTime.hour, startTime.minute ), + ) + + def fillInviteActivityList(self, activityIds): + self.invitationActivityList.removeAndDestroyAllItems() + countDict = {} + for actId in activityIds: + if actId not in countDict: + countDict[actId] =1 + else: + countDict[actId] +=1 + for activityId in countDict: + if countDict[activityId] == 1: + textOfActivity =TTLocalizer.PartyActivityNameDict[activityId]["generic"] + else: + textOfActivity =TTLocalizer.PartyActivityNameDict[activityId]["generic"] + \ + " x " + str (countDict[activityId]) + item = DirectLabel( + relief = None, + text = textOfActivity, + text_align = TextNode.ACenter, + text_scale = 0.05, + text_pos = (0.0, -0.15), + geom_scale = 0.3, + geom_pos = Vec3(0.0, 0.0, 0.07), + geom = self.activityIconsModel.find("**/%sIcon"%PartyGlobals.ActivityIds.getString(activityId)), + ) + self.invitationActivityList.addItem(item) + + def _inviteStartParty(self): + if self.selectedInvitationItem is None: + self.invitePartyGoButton["state"] = DirectGuiGlobals.DISABLED + return + # Pass the burden onto Place.py after the book gets closed + self.doneStatus = { + "mode" : "startparty", + "firstStart" : False, + "hostId" : self.selectedInvitationItem.getPythonTag("hostId"), + } + messenger.send(self.doneEvent) + + def loadHostedPartyInfo(self): + """ + load information about the party being hosted + """ + self.unloadGuests() + self.unloadActivities() + self.unloadDecorations() + self.hostedPartyInfo = None + self.confirmCancelPartyGui.doneStatus = "" + self.confirmCancelPartyGui.hide() + self.cancelPartyResultGui.doneStatus = "" + self.cancelPartyResultGui.hide() + + if base.localAvatar.hostedParties is not None and len(base.localAvatar.hostedParties)>0: + for partyInfo in base.localAvatar.hostedParties: + if partyInfo.status == PartyGlobals.PartyStatus.Pending or \ + partyInfo.status == PartyGlobals.PartyStatus.CanStart or \ + partyInfo.status == PartyGlobals.PartyStatus.NeverStarted or \ + partyInfo.status == PartyGlobals.PartyStatus.Started: + self.hostedPartyInfo = partyInfo + self.loadGuests() + self.loadActivities() + self.loadDecorations() + + # load date and host text + self.hostingDateLabel['text'] = TTLocalizer.EventsPageHostTabDateTimeLabel % ( + PartyUtils.formatDate( partyInfo.startTime.year, partyInfo.startTime.month, partyInfo.startTime.day ), + PartyUtils.formatTime( partyInfo.startTime.hour, partyInfo.startTime.minute, ), + ) + + # public or private? + self.isPrivate = partyInfo.isPrivate + self.__setPublicPrivateButton() + + # Determine state of party go button + if partyInfo.status == PartyGlobals.PartyStatus.CanStart: + self.partyGoButton['state'] = DirectGuiGlobals.NORMAL + self.partyGoButton['text'] = TTLocalizer.EventsPageGoButton, + elif partyInfo.status == PartyGlobals.PartyStatus.Started: + place = base.cr.playGame.getPlace() + if isinstance(place, Party): + # I am in a party and my party has started. If I'm in my party + # then this button should be disabled + if hasattr(base, "distributedParty"): + if base.distributedParty.partyInfo.hostId == base.localAvatar.doId: + self.partyGoButton['state'] = DirectGuiGlobals.DISABLED + else: + self.partyGoButton['state'] = DirectGuiGlobals.NORMAL + else: + self.partyGoButton['state'] = DirectGuiGlobals.NORMAL # better to enable than disable at this point + self.notify.warning("base.distributedParty is not defined when base.cr.playGame.getPlace is party. This should never happen.") + + else: + self.partyGoButton['state'] = DirectGuiGlobals.NORMAL + self.partyGoButton['text'] = TTLocalizer.EventsPageGoBackButton, + else: + self.partyGoButton['text'] = TTLocalizer.EventsPageGoButton, + self.partyGoButton['state'] = DirectGuiGlobals.DISABLED + + # Determine state of cancel button + if partyInfo.status == PartyGlobals.PartyStatus.Started: + self.hostingCancelButton['state'] = DirectGuiGlobals.DISABLED + else: + self.hostingCancelButton['state'] = DirectGuiGlobals.NORMAL + + self.hostingDateLabel.show() + self.hostedPartyDisplay.show() + return + + # You're not hosting a party right now + self.hostingDateLabel["text"] = TTLocalizer.EventsPageHostingTabNoParty + self.hostingCancelButton['state'] = DirectGuiGlobals.DISABLED + self.partyGoButton['state'] = DirectGuiGlobals.DISABLED + self.publicButton['state'] = DirectGuiGlobals.DISABLED + self.privateButton['state'] = DirectGuiGlobals.DISABLED + self.hostedPartyDisplay.show() + + def checkCanStartHostedParty(self): + """Return True if I can start my hosted party.""" + result = True + if self.hostedPartyInfo.endTime < \ + base.cr.toontownTimeManager.getCurServerDateTime() and \ + self.hostedPartyInfo.status == PartyGlobals.PartyStatus.CanStart: + result = False + self.confirmTooLatePartyGui.show() + + return result + + def confirmTooLateParty(self): + """Hide the too late dialog.""" + if hasattr(self, "confirmTooLatePartyGui"): + self.confirmTooLatePartyGui.hide() + + def confirmPublicPrivateChange(self): + """Hide the public private display""" + if hasattr(self, "confirmPublicPrivateGui"): + self.confirmPublicPrivateGui.hide() + + def _startParty(self): + # Pass the burden onto Place.py after the book gets closed + if not self.checkCanStartHostedParty(): + return + if self.hostedPartyInfo.status == PartyGlobals.PartyStatus.CanStart: + firstStart = True + else: + firstStart = False + self.doneStatus = { + "mode" : "startparty", + "firstStart" : firstStart, + "hostId" : None, + } + messenger.send(self.doneEvent) + + def loadGuests(self): + for partyReplyInfoBase in base.localAvatar.partyReplyInfoBases: + if partyReplyInfoBase.partyId == self.hostedPartyInfo.partyId: + for singleReply in partyReplyInfoBase.replies: + toonName = self.getToonNameFromAvId(singleReply.inviteeId) + self.hostingGuestList.addItem(self.getGuestItem(toonName, singleReply.status)) + + def loadActivities(self): + countDict = {} + for activityBase in self.hostedPartyInfo.activityList: + if activityBase.activityId not in countDict: + countDict[activityBase.activityId] =1 + else: + countDict[activityBase.activityId] +=1 + idsUsed = [] + for activityBase in self.hostedPartyInfo.activityList: + if activityBase.activityId not in idsUsed: + idsUsed.append(activityBase.activityId) + count = countDict[activityBase.activityId] + self.hostingActivityList.addItem(self.getActivityItem(activityBase, count)) + + def loadDecorations(self): + countDict = {} + for decorBase in self.hostedPartyInfo.decors: + if decorBase.decorId not in countDict: + countDict[decorBase.decorId] =1 + else: + countDict[decorBase.decorId] +=1 + idsUsed = [] + for decorBase in self.hostedPartyInfo.decors: + if decorBase.decorId not in idsUsed: + count = countDict[decorBase.decorId] + self.hostingDecorationList.addItem(self.getDecorationItem(decorBase, count)) + idsUsed.append(decorBase.decorId) + + def unloadGuests( self ): + self.hostingGuestList.removeAndDestroyAllItems() + + def unloadActivities(self): + self.hostingActivityList.removeAndDestroyAllItems() + + def unloadDecorations(self): + self.hostingDecorationList.removeAndDestroyAllItems() + + def unload(self): + assert self.notify.debugStateCall(self) + self.scrollButtonGui.removeNode() + self.hostingGui.removeNode() + self.invitationGui.removeNode() + self.activityIconsModel.removeNode() + self.decorationModels.removeNode() + del self.titleLabel + self.hostingGuestList.removeAndDestroyAllItems() + self.hostingGuestList.destroy() + del self.hostingGuestList + self.hostingActivityList.removeAndDestroyAllItems() + self.hostingActivityList.destroy() + del self.hostingActivityList + self.hostingDecorationList.removeAndDestroyAllItems() + self.hostingDecorationList.destroy() + del self.hostingDecorationList + self.invitationPartyList.removeAndDestroyAllItems() + self.invitationPartyList.destroy() + del self.invitationPartyList + self.invitationActivityList.removeAndDestroyAllItems() + self.invitationActivityList.destroy() + del self.invitationActivityList + self.confirmCancelPartyGui.cleanup() + del self.confirmCancelPartyGui + self.confirmTooLatePartyGui.cleanup() + del self.confirmTooLatePartyGui + self.confirmPublicPrivateGui.cleanup() + del self.confirmPublicPrivateGui + self.ignore("changePartyPrivateResponseReceived") + taskMgr.remove("changePartyPrivateResponseReceivedTimeOut") + self.cancelPartyResultGui.cleanup() + del self.cancelPartyResultGui + self.ignore(self.confirmCancelPartyEvent) + self.ignore(self.cancelPartyResultGuiEvent) + if hasattr(self, 'rssFeed') and self.rssFeed: + self.rssFeed = None + if self.articleTextList: + self.articleTextList.removeAndDestroyAllItems() + self.articleTextList.destroy() + self.articleTextList = None + if self.articleIndexList: + self.articleIndexList.removeAndDestroyAllItems() + self.articleIndexList.destroy() + self.articleIndexList = None + if self.newsList: + self.newsList.removeAndDestroyAllItems() + self.newsList.destroy() + self.newsList = None + self.avatar = None + self.hostingCancelButton.destroy() + del self.hostingCancelButton + self.partyGoButton.destroy() + del self.partyGoButton + self.publicButton.destroy() + del self.publicButton + self.privateButton.destroy() + del self.privateButton + self.invitePartyGoButton.destroy() + del self.invitePartyGoButton + self.hostTab.destroy() + self.invitedTab.destroy() + self.calendarTab.destroy() + + self.calendarGuiMonth.destroy() + self.toontownTimeGui.destroy() + + taskMgr.remove('EventsPageUpdateTask-doLater') + taskMgr.remove(self.DownloadArticlesTaskName) + ShtikerPage.ShtikerPage.unload(self) + + def enter(self): + self.updatePage() + # do other page stuff + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + #do final party clean up here + ShtikerPage.ShtikerPage.exit(self) + self.unloadGuests() + self.unloadActivities() + self.unloadDecorations() + + def __handleConfirm(self): + """__handleConfirm(self) + """ + self.ignore("confirmDone") + self.confirm.cleanup() + del self.confirm + + def createListAndLabel(self, parent, gui, typeString, itemsVisible, downString="DownArrow", upString="UpArrow", textString="_text_locator"): + """ + Create a DirectScrolledList for different purposes depending on + typeString : "guests", "activities", "decorations", "parties" + """ + list = DirectScrolledList( + parent = parent, + relief = None, + incButton_image = ( + gui.find("**/%s%s_up"%(typeString,downString)), + gui.find("**/%s%s_down"%(typeString,downString)), + gui.find("**/%s%s_rollover"%(typeString,downString)), + gui.find("**/%s%s_inactive"%(typeString,downString)), + ), + incButton_relief = None, + decButton_image = ( + gui.find("**/%s%s_up"%(typeString,upString)), + gui.find("**/%s%s_down"%(typeString,upString)), + gui.find("**/%s%s_rollover"%(typeString,upString)), + gui.find("**/%s%s_inactive"%(typeString,upString)), + ), + decButton_relief = None, + itemFrame_pos = gui.find("**/%s_locator"%typeString).getPos(), + itemFrame_relief = None, + numItemsVisible = itemsVisible, + # need to set height of each entry to avoid list text running off end of listbox + forceHeight = 0.07, + ) + strings = { + "guests" : TTLocalizer.EventsPageHostingTabGuestListTitle, + "activities" : TTLocalizer.EventsPageHostingTabActivityListTitle, + "decorations" : TTLocalizer.EventsPageHostingTabDecorationsListTitle, + "parties" : TTLocalizer.EventsPageHostingTabPartiesListTitle, + } + label = DirectLabel( + parent = parent, + relief = None, + text = strings[typeString], + text_scale = TTLocalizer.EPcreateListAndLabel, + pos = gui.find("**/%s%s"%(typeString,textString)).getPos(), + ) + return (list, label) + + def setMode(self, mode, updateAnyways=0): + """ + toggle between tabs on page + """ + assert self.notify.debugStateCall(self) + messenger.send('wakeup') + if updateAnyways == False: + if self.mode == mode: + return + else: + self.mode = mode + self.show() + + # just for GP's + self.updatePage() + + def getMode(self): + """Return the current tab we're in.""" + return self.mode + + def updatePage(self): + assert self.notify.debugStateCall(self) + + if self.mode == EventsPage_Host: + # this is the tab for hosted party details + self.hostTab['state'] = DirectGuiGlobals.DISABLED + self.invitedTab['state'] = DirectGuiGlobals.NORMAL + self.calendarTab['state'] = DirectGuiGlobals.NORMAL + self.newsTab['state'] = DirectGuiGlobals.NORMAL + + self.invitationDisplay.hide() + self.hostedPartyDisplay.show() + self.calendarDisplay.hide() + self.newsDisplay.hide() + + self.loadHostedPartyInfo() + if self.hostedPartyInfo is None: + self.titleLabel['text'] = TTLocalizer.EventsPageHostTabTitleNoParties + else: + self.titleLabel['text'] = TTLocalizer.EventsPageHostTabTitle + + elif self.mode == EventsPage_Invited: + # this is the tab to see party invitations + self.titleLabel['text'] = TTLocalizer.EventsPageInvitedTabTitle + self.hostTab['state'] = DirectGuiGlobals.NORMAL + self.invitedTab['state'] = DirectGuiGlobals.DISABLED + self.calendarTab['state'] = DirectGuiGlobals.NORMAL + self.newsTab['state'] = DirectGuiGlobals.NORMAL + + self.hostedPartyDisplay.hide() + self.invitationDisplay.show() + self.calendarDisplay.hide() + self.newsDisplay.hide() + + # load invitations I've received + self.loadInvitations() + + elif self.mode == EventsPage_Calendar: + # calendar tab + self.titleLabel['text'] = "" + self.hostTab['state'] = DirectGuiGlobals.NORMAL + self.invitedTab['state'] = DirectGuiGlobals.NORMAL + self.calendarTab['state'] = DirectGuiGlobals.DISABLED + self.newsTab['state'] = DirectGuiGlobals.NORMAL + + self.hostedPartyDisplay.hide() + self.invitationDisplay.hide() + self.calendarDisplay.show() + self.newsDisplay.hide() + self.calendarGuiMonth.changeMonth(0) + + elif self.mode == EventsPage_News: + self.titleLabel['text'] = "" #TTLocalizer.EventsPageNewsTabTitle + self.hostTab['state'] = DirectGuiGlobals.NORMAL + self.invitedTab['state'] = DirectGuiGlobals.NORMAL + self.calendarTab['state'] = DirectGuiGlobals.NORMAL + self.newsTab['state'] = DirectGuiGlobals.DISABLED + + self.hostedPartyDisplay.hide() + self.invitationDisplay.hide() + self.calendarDisplay.hide() + if not self.gotRssFeed: + #self.getRssFeed() + pass + self.newsDisplay.show() + #self.downloadArticles() # putting it here means we download when they click on news + + def __setPublicPrivateButton(self): + """ + Update the state of the public and private buttons to match self.isPrivate + """ + if self.isPrivate: + self.privateButton["indicatorValue"] = True + self.publicButton["indicatorValue"] = False + self.privateButton["state"] = DirectGuiGlobals.DISABLED + self.publicButton["state"] = DirectGuiGlobals.NORMAL + else: + self.privateButton["indicatorValue"] = False + self.publicButton["indicatorValue"] = True + self.privateButton["state"] = DirectGuiGlobals.NORMAL + self.publicButton["state"] = DirectGuiGlobals.DISABLED + + def __changePublicPrivate(self, indicator): + """ + The player clicked the public or private check buttons + """ + self.__setPublicPrivateButton() + self.confirmPublicPrivateGui["text"] = TTLocalizer.EventsPagePublicPrivateChange + self.confirmPublicPrivateGui.buttonList[0].hide() + self.confirmPublicPrivateGui.show() + + base.cr.partyManager.sendChangePrivateRequest(self.hostedPartyInfo.partyId, not self.isPrivate) + self.accept("changePartyPrivateResponseReceived", self.changePartyPrivateResponseReceived) + taskMgr.doMethodLater(5.0, self.changePartyPrivateResponseReceived, "changePartyPrivateResponseReceivedTimeOut", [0, 0, PartyGlobals.ChangePartyFieldErrorCode.DatabaseError] ) + # changePartyPrivateResponseReceived will be called after we hear back from uberdog + + def changePartyPrivateResponseReceived(self, partyId, newPrivateStatus, errorCode): + EventsPage.notify.debug("changePartyPrivateResponseReceived called with partyId = %d, newPrivateStatus = %d, errorCode = %d" % (partyId, newPrivateStatus, errorCode)) + taskMgr.remove("changePartyPrivateResponseReceivedTimeOut") + self.ignore("changePartyPrivateResponseReceived") + if errorCode == PartyGlobals.ChangePartyFieldErrorCode.AllOk: + # It worked, update local isPrivate variable + self.isPrivate = newPrivateStatus + self.confirmPublicPrivateGui.hide() + else: + self.confirmPublicPrivateGui.buttonList[0].show() + # It didn't work, alert the player + if errorCode == PartyGlobals.ChangePartyFieldErrorCode.AlreadyStarted: + self.confirmPublicPrivateGui["text"] = TTLocalizer.EventsPagePublicPrivateAlreadyStarted + else: + self.confirmPublicPrivateGui["text"] = TTLocalizer.EventsPagePublicPrivateNoGo + # Change the visual to reflect the actual status (uses self.isPrivate) + self.__setPublicPrivateButton() + + def __doCancelParty(self): + if self.hostedPartyInfo: + if self.hostedPartyInfo.status == PartyGlobals.PartyStatus.Pending or \ + self.hostedPartyInfo.status == PartyGlobals.PartyStatus.CanStart or \ + self.hostedPartyInfo.status == PartyGlobals.PartyStatus.NeverStarted: + self.hostingCancelButton['state'] = DirectGuiGlobals.DISABLED + self.confirmCancelPartyGui.show() + + def confirmCancelOfParty(self): + self.confirmCancelPartyGui.hide() + if self.confirmCancelPartyGui.doneStatus == "ok": + base.cr.partyManager.sendChangePartyStatusRequest(self.hostedPartyInfo.partyId, PartyGlobals.PartyStatus.Cancelled) + self.accept("changePartyStatusResponseReceived", self.changePartyStatusResponseReceived) + else: + self.hostingCancelButton['state'] = DirectGuiGlobals.NORMAL + + def changePartyStatusResponseReceived(self, partyId, newPartyStatus, errorCode, beansRefunded ): + EventsPage.notify.debug("changePartyStatusResponseReceived called with partyId = %d, newPartyStatus = %d, errorCode = %d" % (partyId, newPartyStatus, errorCode)) + if errorCode == PartyGlobals.ChangePartyFieldErrorCode.AllOk: + if newPartyStatus == PartyGlobals.PartyStatus.Cancelled: + self.loadHostedPartyInfo() + self.cancelPartyResultGui["text"] = TTLocalizer.EventsPageCancelPartyResultOk % beansRefunded + self.cancelPartyResultGui.show() + else: + self.cancelPartyResultGui["text"] = TTLocalizer.EventsPageCancelPartyResultError + self.cancelPartyResultGui.show() + self.hostingCancelButton['state'] = DirectGuiGlobals.NORMAL + + def cancelPartyResultGuiCommand(self): + self.cancelPartyResultGui.hide() + + def updateToontownTime(self): + """Do an immediate update of the toontown time label.""" + self.toontownTimeGui.updateTime() + + def createArticleTextList(self): + """Just create the article text list gui, don't populate with text yet.""" + bottomLeft = self.newsFrame.find("**/loc_textBoxBtmLf") + topRight = self.newsFrame.find("**/loc_textBoxTopRt") + topLeft = self.newsFrame.find("**/loc_textBoxTopLf") + self.notify.debug("bottomLeft=%s topRight=%s" % (bottomLeft.getPos(), topRight.getPos())) + buttonOffSet = 0.045 + selectedIndex = 0 + self.articleListXorigin = bottomLeft.getPos().getX() + self.articleListFrameSizeX = topRight.getPos().getX() - bottomLeft.getPos().getX() + self.articleListZorigin = bottomLeft.getPos().getZ() + self.articleListFrameSizeZ = topRight.getPos().getZ() - bottomLeft.getPos().getZ() + self.articleArrowButtonScale = 1.3 + self.articleItemFrameXorigin = bottomLeft.getPos().getX() + self.articleButtonXstart = self.articleItemFrameXorigin + 0.25 + + def makeButton(itemName, itemNum, *extraArgs): + def buttonCommand(): + print itemName, itemNum + return DirectLabel(text = itemName, + relief = None, + text_align = TextNode.ALeft, + #frameSize = (-3.5, 3.5, -0.2, 0.8), + scale = 0.06, + ) + itemHeight = 0.062 + topLeftStart = topLeft.getPos()# - Vec3(0,0, itemHeight) + self.notify.debug("topLeft=%s topLeftStart=%s" % (topLeft.getPos(), topLeftStart)) + scrollTopUp = self.newsFrame.find("**/scrollTopUp") + scrollTopDown = self.newsFrame.find("**/scrollTopDown") + scrollTopHover = self.newsFrame.find("**/scrollTopHover") + scrollBtmUp = self.newsFrame.find("**/scrollBtmUp") + scrollBtmDown = self.newsFrame.find("**/scrollBtmDown") + scrollBtmHover = self.newsFrame.find("**/scrollBtmHover") + + decButtonPos = scrollTopUp.getPos(topLeft) + incButtonPos = scrollBtmDown.getPos(topLeft) + self.notify.debug("scrollTopUp pos wrt topLeft %s" % (decButtonPos)) + self.notify.debug("scrollTopUp pos normal %s" % (scrollTopUp.getPos())) + scrollTopUp.setPos(0,0,0) + scrollTopDown.setPos(0,0,0) + scrollTopHover.setPos(0,0,0) + scrollBtmUp.setPos(0,0,0) + scrollBtmDown.setPos(0,0,0) + scrollBtmHover.setPos(0,0,0) + self.numLinesInTextList = 13 + + self.articleTextList = DirectScrolledList( + parent = self.newsFrame, + #items = [], + relief = None, #DirectGuiGlobals.SUNKEN, + pos = topLeftStart, #(-0.80,0,-0.17), + # inc and dec are DirectButtons + # incButton is on the bottom of page, decButton is on the top! + incButton_image = (self.newsFrame.find("**/scrollBtmUp"), + self.newsFrame.find("**/scrollBtmDown"), + self.newsFrame.find("**/scrollBtmHover"), + self.newsFrame.find("**/scrollBtmUp"), + ), + + incButton_relief = None, + incButton_pos = incButtonPos, + incButton_image3_color = (1.0,1.0,1.0,0.1), + + decButton_image = (self.newsFrame.find("**/scrollTopUp"), + self.newsFrame.find("**/scrollTopDown"), + self.newsFrame.find("**/scrollTopHover"), + self.newsFrame.find("**/scrollTopUp") + + ), + decButton_relief = None, + decButton_pos = decButtonPos, + decButton_image3_color = (1.0,1.0,1.0,0.1), + + text_scale = 0.05, + frameSize = (self.articleListXorigin,self.articleListXorigin+self.articleListFrameSizeX, + self.articleListZorigin,self.articleListZorigin + self.articleListFrameSizeZ), + frameColor = (0.82,0.80,0.75,1), + borderWidth = (0.01,0.01), + + numItemsVisible = self.numLinesInTextList, + itemMakeFunction = makeButton, + forceHeight = itemHeight, + ) + + oldParent = self.articleTextList.decButton.getParent() + self.newsFrame.find("**/scroll").hide() + + def createArticleIndexList(self): + """Just create the article Index list gui, don't populate with anything yet.""" + + self.articleIndexList = DirectScrolledList( + parent = self.newsFrame, + relief = None, + pos = (0,0,0), + # inc and dec are DirectButtons + # incButton is on the right of page, left is on the page! + incButton_image = (self.newsFrame.find("**/pageRtUp"), + self.newsFrame.find("**/pageRtUp"), + self.newsFrame.find("**/pageRtHover"), + None, + ), + incButton_relief = None, + incButton_scale = 1, + #incButton_pos = (0.85, 0, 0), + # Make the disabled button fade out + decButton_image = (self.newsFrame.find("**/pageLfUp"), + self.newsFrame.find("**/pageLfUp"), + self.newsFrame.find("**/pageLfHover"), + None, + ), + decButton_relief = None, + decButton_scale = 1, + #decButton_pos = (-0.85, 0, 0), + # Make the disabled button fade out + + text_scale = 0.05, + numItemsVisible = 1 + ) + self.newsFrame.find("**/pageRtUp").hide() + self.newsFrame.find("**/pageRtHover").hide() + self.newsFrame.find("**/pageLfUp").hide() + self.newsFrame.find("**/pageLfHover").hide() + self.articleIndexList['command'] = self.articleIndexChanged + + def articleIndexChanged(self): + """Change the news image and text.""" + if not self.articleIndexList["items"]: + # we are probably closing the gui, do nothing + return + curArticleIndex = self.articleIndexList.getSelectedIndex() + if curArticleIndex in self.articleImages and \ + curArticleIndex in self.articleText: + self.displayArticle(self.articleImages[curArticleIndex], self.articleText[curArticleIndex]) + + + + def getRssFeed(self): + """Get our news feed and display it in the page.""" + if self.gotRssFeed: + # TODO get the feed again after an hour + return + #self.notify.error("feed parser is disabled, renable 'from toontown.parties import feedparser'") + self.rssFeed = feedparser.parse("http://www.wdwinfo.com/news/rss.xml") + feedText = [] + def addFeedText(unicodeText,textSize = 0.03, color = (0,0,0,1), feedText=feedText): + feedText.append( + DirectLabel( + relief = None, + text = unicodeText, + text_scale = textSize, + text_align = TextNode.ALeft, + text_fg = color, + textMayChange = 0, + pos = (0,0,0), + )) + + addFeedText(self.rssFeed['channel']['title'], 0.06) + addFeedText( self.rssFeed['channel']['subtitle'], 0.04) + + for entry in self.rssFeed['entries']: + addFeedText('') + addFeedText(entry['title'],0.04, (0,0,1,1)) + addFeedText(entry['updated'], 0.025), + addFeedText(entry['summary'], 0.035) + + self.feedText = feedText + self.createNewsList() + self.gotRssFeed = True + + + def downloadArticles(self): + """Download the news articles, but only if we need to.""" + if self.gotArticles: + return + if not self.NonblockingDownload: + # wait a bit so we can show Retrieving News... + if not taskMgr.hasTaskNamed(self.DownloadArticlesTaskName): + taskMgr.doMethodLater(0.5, self.downloadArticlesTask, self.DownloadArticlesTaskName, ) + else: + if not self.downloadArticlesInProgress: + self.downloadArticlesNonblocking(); + + def downloadArticlesTask(self, task): + """The task to download the articles.""" + self.articleImages = {} + self.articleText = {} + try: + urlfile = urllib.urlopen(self.getNewsUrl()) + except IOError: + self.notify.warning("Could not open %s" % self.getNewsUrl()) + self.newsStatusLabel["text"] = TTLocalizer.EventsPageNewsUnavailable + return + #self.gotArticles = True + urlStrings = urlfile.read() + urlfile.close() + urls = urlStrings.split("\r\n") + + for index in xrange(len(urls)/2): + imageUrl = urls[(index*2)] + textUrl = urls[(index*2) +1] + + # read in the web image + img = PNMImage() + self.articleImages[index] = img + try: + self.notify.info("opening %s" % imageUrl) + imageFile = urllib.urlopen(imageUrl) + data = imageFile.read() + img.read(StringStream(data)) + imageFile.close() + except IOError: + self.notify.warning("image url %d could not open %s" % (index, imageUrl)) + + text = "" + self.articleText[index] = text + try: + self.notify.info("opening %s" % textUrl) + #if "garden" in textUrl: + # import pdb; pdb.set_trace() + textFile = urllib.urlopen(textUrl) + data = textFile.read() + data = data.replace("\\1","\1") + data = data.replace("\\2","\2") + data = data.replace("\r"," ") # \r showing us a box, no char definiton + self.articleText[index] = data + textFile.close() + except IOError: + self.notify.warning("text url %d could not open %s" % (index, textUrl)) + # add an empty text item, we'll rely on callback to display things properly + self.articleIndexList.addItem("") + self.newsStatusLabel["text"] = "" + self.gotArticles = True + return task.done + + def displayArticle(self, img, articleText): + """Display one news article.""" + self.displayArticleImage(img) + self.displayArticleText(articleText) + + def displayArticleImage(self,img): + """Display one news article image.""" + xSize = img.getXSize() + ySize = img.getYSize() + # the sticker book goes from 0.75 at top to -0.75 at bottom + # it goes from -0.88 at the left to 0.88 at the right + + bottomLeft = self.newsFrame.find("**/loc_picture_BtmLf").getPos() + topRight = self.newsFrame.find("**/loc_picture_TopRt").getPos() + maxFrameXSize = (topRight.getX() - bottomLeft.getX()) + maxFrameYSize = (topRight.getZ() - bottomLeft.getZ()) + + center = (bottomLeft + topRight) / 2.0 + + maxAspectRatio = maxFrameXSize / maxFrameYSize + if ySize: + imgAspectRatio = float(xSize) / float (ySize) + else: + # avoid divizion by zero error + imgAspectRatio = maxAspectRatio + + curXSize = maxFrameXSize + curYSize = maxFrameYSize + shrinkY = True + if imgAspectRatio > maxAspectRatio: + if xSize: + curYSize = maxFrameXSize * ySize / xSize + else: + if ySize: + curXSize = maxFrameYSize * xSize / ySize + shrinkY = False + minX = - curXSize / 2.0 + maxX = curXSize / 2.0 + minY = - curYSize / 2.0 + maxY = curYSize / 2.0 + webTexture = Texture("webTexture") + if img.isValid(): + webTexture.load(img) + else: + webTexture = None + + if self.articleImage: + self.articleImage.destroy() + self.articleImage = DirectFrame( + parent = self.newsFrame, + relief = DirectGuiGlobals.FLAT, + image=webTexture, + image_scale = (curXSize / 2.0, 1, curYSize / 2.0), + pos = center, + frameSize = (minX, maxX, minY, maxY), + #frameTexture = webTexture, + ) + foo1 = Point3(0,0,0) + foo2 = Point3(0,0,0) + self.articleImage.calcTightBounds(foo1,foo2) + foo3 = self.articleImage.getBounds() + + + def displayArticleText(self, articleText): + """Display the news article text.""" + playaLabel = DirectLabel( + parent = None, + relief = None, + text_align = TextNode.ALeft, + text = articleText, + text_scale = 0.06, + text_wordwrap = 13.5 + ) + playaLabel.hide() + textN = playaLabel.component( playaLabel.components()[0] ) + if( type(textN) == OnscreenText): + wrappedText = textN.textNode.getWordwrappedText() + items = wrappedText.split("\n") + self.articleTextList.removeAndDestroyAllItems() + for item in items: + self.articleTextList.addItem(item) + if len(items) <= self.numLinesInTextList: + self.articleTextList.decButton.hide() + self.articleTextList.incButton.hide() + else: + self.articleTextList.decButton.show() + self.articleTextList.incButton.show() + playaLabel.destroy() + + + def createNewsList(self): + """Create a scroll list to display our news items.""" + buttonOffSet = 0.045 + + if self.newsList: + self.newsList.removeAllItems() + self.newsList.destroy() + + selectedIndex = 0 + self.newsListXorigin = -0.02 + self.newsListFrameSizeX = 1.55 + self.newsListZorigin = -0.95 + self.newsListFrameSizeZ = 1.02 + self.newsArrowButtonScale = 1.3 + self.newsItemFrameXorigin = -0.237 + + self.newsButtonXstart = self.newsItemFrameXorigin + 0.725 + + self.newsList = DirectScrolledList( + parent = self.newsFrame, + items = self.feedText, + relief = None, + pos = (-0.50,0,0.40), + # inc and dec are DirectButtons + # incButton is on the bottom of page, decButton is on the top! + incButton_image = (self.scrollButtonGui.find("**/FndsLst_ScrollUp"), + self.scrollButtonGui.find("**/FndsLst_ScrollDN"), + self.scrollButtonGui.find("**/FndsLst_ScrollUp_Rllvr"), + self.scrollButtonGui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_scale = (self.newsArrowButtonScale,self.newsArrowButtonScale, + -self.newsArrowButtonScale), + incButton_pos = (self.newsButtonXstart,0,self.newsListZorigin - buttonOffSet), + # Make the disabled button fade out + incButton_image3_color = Vec4(1,1,1,0.2), + decButton_image = (self.scrollButtonGui.find("**/FndsLst_ScrollUp"), + self.scrollButtonGui.find("**/FndsLst_ScrollDN"), + self.scrollButtonGui.find("**/FndsLst_ScrollUp_Rllvr"), + self.scrollButtonGui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_scale = (self.newsArrowButtonScale,self.newsArrowButtonScale, + self.newsArrowButtonScale), + decButton_pos = (self.newsButtonXstart, 0, self.newsListZorigin + self.newsListFrameSizeZ + buttonOffSet), + # Make the disabled button fade out + decButton_image3_color = Vec4(1,1,1,0.2), + + # itemFrame is a DirectFrame + itemFrame_pos = (self.newsItemFrameXorigin,0,0), + itemFrame_scale = 1.0, + itemFrame_relief = DirectGuiGlobals.SUNKEN, + # frameSize is (minX,maxX,minZ,maxZ); where x goes left->right neg->pos, + # and z goes bottom->top neg->pos + itemFrame_frameSize = (self.newsListXorigin,self.newsListXorigin+self.newsListFrameSizeX, + self.newsListZorigin,self.newsListZorigin + self.newsListFrameSizeZ), + itemFrame_frameColor = (0.82,0.80,0.75,1), + itemFrame_borderWidth = (0.01,0.01), + # each item is a button with text on it + numItemsVisible = 14, + + ) + self.newsList.scrollTo(selectedIndex) + + def downloadArticlesNonblocking(self): + """Download the articles in a non blocking way, so that the user can do other stuff.""" + self.notify.info("Starting download of news articles.") + self.articleImages = {} + self.articleText = {} + + self.downloadArticlesInProgress = True + self.httpSession = HTTPClient() + self.nonBlock = self.httpSession.makeChannel(True) + newsUrl = self.getNewsUrl() + #newsUrl = "http://play.toontown.com/shared/images/newsimages/toons_love_to_party.jpg" + self.curUrlIndex = -1 + self.curArticleIndex =-1 + self.curDownloadIsJpg = True + self.downloadUrl(newsUrl) + + def downloadUrl(self, newsUrl): + """Download a url.""" + if "http:" in newsUrl: + self.getHttpUrl(newsUrl) + else: + self.getFileUrl(newsUrl) + + def doneGettingUrl(self, url, data, allOk): + """Handle getting all the data, or potentially an error result.""" + self.notify.debug("doneGettingUrl %s %s %s" % (url, type(data),allOk)) + self.printCurFields() + # data is either a string or Ramfile + if url == self.getNewsUrl(): + if allOk: + if type(data) == Ramfile: + self.urls = data.getData().split("\r\n") + else: + self.urls = data.split("\r\n") + else: + self.notify.warning("Could not open %s" % url) + self.newsStatusLabel["text"] = TTLocalizer.EventsPageNewsUnavailable + self.gotArticles = True + return + # everything good so far now get the images and text + # first line is date + if self.urls: + self.subRight["text"]=self.urls[0] + self.urls = self.urls[1:] + + self.curUrlIndex = 0 + self.curArticleIndex =0 + self.curDownloadIsJpg = True + if self.curUrlIndex < len(self.urls): + url = self.urls[self.curUrlIndex] + self.downloadUrl(url) + else: + if self.curDownloadIsJpg: + img = PNMImage() + self.articleImages[self.curArticleIndex] = img + try: + if type(data) == Ramfile: + if allOk: + img.read(StringStream(data.getData())) + else: + img.read(StringStream(data)) + except : + self.notify.warning("image url %d could not read %s" % (self.curArticleIndex, url)) + else: + text = "" + self.articleText[self.curArticleIndex] = text + if type(data) == Ramfile: + textData = data.getData() + else: + textData = data + + textData =textData.replace("\\1","\1") + textData = textData.replace("\\2","\2") + textData = textData.replace("\r"," ") # \r showing us a box, no char definiton + self.articleText[self.curArticleIndex] = textData + # add an empty text item, we'll rely on callback to display things properly + self.articleIndexList.addItem("") + if self.newsStatusLabel["text"]: + self.newsStatusLabel["text"] = "" + + self.incrementCurFields() + if self.downloadedAllArticles(): + self.notify.debug("we got everything") + else: + url = self.urls[self.curUrlIndex] + self.downloadUrl(url) + + def downloadedAllArticles(self): + """Returns true if we've downloaded all the articles.""" + maxArticles = len(self.urls) / 2 + result = False + if self.curArticleIndex >= maxArticles: + result = True + if self.curUrlIndex >= len(self.urls): + result = True + return result + + def incrementCurFields(self): + """Increment our fields that keep track what we're downloading.""" + if self.curDownloadIsJpg: + self.curDownloadIsJpg = False + self.curUrlIndex += 1 + else: + self.curDownloadIsJpg = True + self.curUrlIndex += 1 + self.curArticleIndex += 1 + + def printCurFields(self): + self.notify.debug("curUrlIndex=%s curArticleIndex=%s curDownloadIsJpg=%s" % + (self.curUrlIndex, self.curArticleIndex, self.curDownloadIsJpg)) + + def getFileUrl(self, fileUrl): + """Get a local file url.""" + result = True + urlStrings = "" + try: + urlfile = urllib.urlopen(fileUrl) + urlStrings = urlfile.read() + urlfile.close() + except IOError: + self.notify.warning("Could not open %s" % fileUrl) + result = False + self.doneGettingUrl(fileUrl, urlStrings, result) + + + def getHttpUrl(self, httpUrl): + """Get something from the web.""" + docSpec = DocumentSpec(httpUrl) + self.ramfile = Ramfile() + self.nonBlock.beginGetDocument(docSpec) + self.nonBlock.downloadToRam(self.ramfile) + self.startCheckingAsyncRequest(httpUrl) + + def startCheckingAsyncRequest(self, url): + """Start polling our async requests.""" + taskMgr.remove(self.DownloadArticlesTaskName) + task = taskMgr.doMethodLater(0.5, self.pollDownloadTask, self.DownloadArticlesTaskName) + task.url = url + + def stopCheckingAsyncRequest(self): + """Stop polling our async requests.""" + taskMgr.remove(self.DownloadArticlesTaskName) + + def pollDownloadTask(self, task): + """See if we're done getting data back.""" + result = self.nonBlock.run() + if result == 0: + self.stopCheckingAsyncRequest() + allOk = False + if self.nonBlock.getStatusString() == "OK": + allOk = True + else: + self.notify.warning("%s for %s" % + (self.nonBlock.getStatusString(), task.url)) + self.doneGettingUrl(task.url, self.ramfile, allOk) + #rf = Ramfile() + #self.nonBlock.downloadToRam(rf) + #socketStream = self.nonBlock.openReadBody() + #foo = socketStream.read() + else: + return Task.again + + def getNewsUrl(self): + """Return the correct news_url address, for live, test, qa.""" + result = "" + if self.NewsUrl == self.DefaultNewsUrl: + # WARNING change this as needed for international + serverAddress = base.cr.getServerAddress() + if "test.toontown" in serverAddress.getServer(): + result = "http://play.test.toontown.com" + elif "ttown4" in serverAddress.getServer(): + # what whould i use here? + result = "http://ttown4.online.disney.com:1601" + elif "qa.toontown" in serverAddress.getServer(): + result = "http://play.qa.toontown.com" + else: + # this must be live + result = "http://play.toontown.com" + # we have server, append directory and file + result += self.NewsUrl + else: + # we have a different config setting, use that as is + result = self.NewsUrl + + return result + + + + + diff --git a/toontown/src/shtiker/FishPage.py b/toontown/src/shtiker/FishPage.py new file mode 100644 index 0000000..210f10f --- /dev/null +++ b/toontown/src/shtiker/FishPage.py @@ -0,0 +1,453 @@ +"""FishPage module: contains the FishPage class""" + +from toontown.toonbase import ToontownGlobals +import ShtikerPage +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from toontown.fishing import FishPicker +from toontown.fishing import FishBrowser +from toontown.fishing import FishGlobals + +# display modes +FishPage_Tank = 0 +FishPage_Collection = 1 +FishPage_Trophy = 2 + +TROPHIES_PER_ROW = 5 + +class FishPage(ShtikerPage.ShtikerPage): + """ + FishPage keeps track of fish caught and their names. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("FishPage") + + def __init__(self): + """ + FishPage constructor: create the fish page + """ + assert self.notify.debugStateCall(self) + ShtikerPage.ShtikerPage.__init__(self) + self.avatar = None + self.mode = FishPage_Tank + + def enter(self): + assert self.notify.debugStateCall(self) + if not hasattr(self, "title"): + self.load() + # first time in make setMode update even if the mode hasn't changed + self.setMode(self.mode, 1) + self.accept(localAvatar.uniqueName("fishTankChange"), self.updatePage) + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + assert self.notify.debugStateCall(self) + if hasattr(self, "picker"): + self.picker.hide() + if hasattr(self, "browser"): + self.browser.hide() + self.ignore(localAvatar.uniqueName("fishTankChange")) + ShtikerPage.ShtikerPage.exit(self) + + def setAvatar(self, av): + assert self.notify.debugStateCall(self) + self.avatar = av + + def getAvatar(self): + assert self.notify.debugStateCall(self) + return self.avatar + + def load(self): + assert self.notify.debugStateCall(self) + ShtikerPage.ShtikerPage.load(self) + gui = loader.loadModel("phase_3.5/models/gui/fishingBook") + + rodFrame = gui.find("**/bucket/fram1") + rodFrame.removeNode() + + trophyCase = gui.find("**/trophyCase1") + trophyCase.find("glass1").reparentTo(trophyCase,-1) + trophyCase.find("shelf").reparentTo(trophyCase,-1) + self.trophyCase=trophyCase + + # page title + self.title = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.1, + pos = (0,0,0.65), + ) + + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1, 1, 1, 1) + clickColor = (.8, .8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + + self.tankTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.FishPageTankTab, + text_scale = TTLocalizer.FPtankTab, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface1"), + image_pos = (0.55,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [FishPage_Tank], + pos = (0.92, 0, 0.55), + ) + self.collectionTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.FishPageCollectionTab, + text_scale = TTLocalizer.FPcollectionTab, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [FishPage_Collection], + pos = (0.92, 0, 0.1), + ) + self.trophyTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.FishPageTrophyTab, + text_scale = TTLocalizer.FPtrophyTab, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface3"), + image_pos = (-0.28,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [FishPage_Trophy], + pos = (0.92, 0, -0.3), + ) + self.tankTab.setPos(-0.55,0,0.775) + self.collectionTab.setPos(-0.13,0,0.775) + self.trophyTab.setPos(0.28,0,0.775) + + def createFishPicker(self): + """ + Tank/Bucket Tab + """ + if not hasattr(self, "picker"): + # create the various display elements + self.picker = FishPicker.FishPicker(self) + self.picker.setPos(-0.555, 0, 0.1) + self.picker.setScale(0.95) + + # rod info + self.rod = DirectLabel( + parent = self.picker, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = 0.06, + pos = (0.9,0,-0.65), + ) + + def createFishBrowser(self): + """ + Album Tab + """ + if not hasattr(self, "browser"): + self.browser = FishBrowser.FishBrowser(self) + self.browser.setScale(1.1) + # fish collected total: + self.collectedTotal = DirectLabel( + parent = self.browser, + relief = None, + text = "", + text_scale = 0.06, + pos = (0,0,-0.61), + ) + + def createFishTrophyFrame(self): + """ + Trophy Tab + """ + if not hasattr(self, "trophyFrame"): + # trophy stuff + self.trophyFrame = DirectFrame( + parent = self, + relief = None, + image = self.trophyCase, + image_pos = (0,1,0), + image_scale = 0.034, + ) + # Order things properly + self.trophyFrame.hide() + + self.trophies = [] + hOffset = -0.5 + vOffset = 0.4 + for level, trophyDesc in FishGlobals.TrophyDict.items(): + trophy = FishingTrophy(-1) + trophy.nameLabel['text'] = trophyDesc[0] + trophy.reparentTo(self.trophyFrame) + trophy.setScale(0.36) + # see if we have reached start of new row + if (level % TROPHIES_PER_ROW) == 0: + hOffset = -0.5 + vOffset -= 0.4 + trophy.setPos(hOffset,0,vOffset) + hOffset += 0.25 + self.trophies.append(trophy) + + def setMode(self, mode, updateAnyways=0): + assert self.notify.debugStateCall(self) + messenger.send('wakeup') + if not updateAnyways: + if self.mode == mode: + return + else: + self.mode = mode + self.show() + if mode == FishPage_Tank: + self.title['text'] = TTLocalizer.FishPageTitleTank + if not hasattr(self, "picker"): + self.createFishPicker() + self.picker.show() + if hasattr(self, "browser"): + self.browser.hide() + if hasattr(self, "trophyFrame"): + self.trophyFrame.hide() + + self.tankTab['state'] = DGG.DISABLED + self.collectionTab['state'] = DGG.NORMAL + self.trophyTab['state'] = DGG.NORMAL + elif mode == FishPage_Collection: + self.title['text'] = TTLocalizer.FishPageTitleCollection + if hasattr(self, "picker"): + self.picker.hide() + if not hasattr(self, "browser"): + self.createFishBrowser() + self.browser.show() + if hasattr(self, "trophyFrame"): + self.trophyFrame.hide() + + self.tankTab['state'] = DGG.NORMAL + self.collectionTab['state'] = DGG.DISABLED + self.trophyTab['state'] = DGG.NORMAL + elif mode == FishPage_Trophy: + self.title['text'] = TTLocalizer.FishPageTitleTrophy + if hasattr(self, "picker"): + self.picker.hide() + if hasattr(self, "browser"): + self.browser.hide() + if not hasattr(self, "trophyFrame"): + self.createFishTrophyFrame() + self.trophyFrame.show() + + self.tankTab['state'] = DGG.NORMAL + self.collectionTab['state'] = DGG.NORMAL + self.trophyTab['state'] = DGG.DISABLED + else: + # hmmm... + pass + + # just for GP's + self.updatePage() + + def unload(self): + assert self.notify.debugStateCall(self) + self.avatar = None + if hasattr(self, "trophies"): + del self.trophies + if hasattr(self, "trophyCase"): + del self.trophyCase + self.tankTab.destroy() + self.collectionTab.destroy() + self.trophyTab.destroy() + ShtikerPage.ShtikerPage.unload(self) + + def updatePage(self): + assert self.notify.debugStateCall(self) + + if hasattr(self, "collectedTotal"): + # update the total collection + self.collectedTotal['text'] = ( + TTLocalizer.FishPageCollectedTotal % + (len(base.localAvatar.fishCollection), + FishGlobals.getTotalNumFish())) + + if hasattr(self, "rod"): + # update the rod info on the picker + rod = base.localAvatar.fishingRod + rodName = TTLocalizer.FishingRodNameDict[rod] + rodWeightRange = FishGlobals.getRodWeightRange(rod) + self.rod['text'] = TTLocalizer.FishPageRodInfo % ( + rodName, rodWeightRange[0], rodWeightRange[1]) + + if self.mode == FishPage_Tank: + if hasattr(self, "picker"): + # the fish list may have changed, update the picker + newTankFish = base.localAvatar.fishTank.getFish() + self.picker.update(newTankFish) + elif self.mode == FishPage_Collection: + if hasattr(self, "browser"): + # the fish gallery may have changed, update the browser + self.browser.update() + elif self.mode == FishPage_Trophy: + if hasattr(self, "trophies"): + # the fishing trophy list may have changed, update the display + for trophy in self.trophies: + trophy.setLevel(-1) + for trophyId in base.localAvatar.getFishingTrophies(): + self.trophies[trophyId].setLevel(trophyId) + + def destroy(self): + self.notify.debug('destroy') + DirectFrame.destroy(self) + +class FishingTrophy(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory("FishingTrophy") + + def __init__(self, level): + assert self.notify.debugStateCall(self) + DirectFrame.__init__(self, relief = None) + self.initialiseoptions(FishingTrophy) + self.trophy = loader.loadModel("phase_3.5/models/gui/fishingTrophy") + self.trophy.reparentTo(self) + # Fix the model + self.trophy.setPos(0,1,0) + self.trophy.setScale(0.1) + self.base = self.trophy.find("**/trophyBase") + self.column = self.trophy.find("**/trophyColumn") + self.top = self.trophy.find("**/trophyTop") + self.topBase = self.trophy.find("**/trophyTopBase") + self.statue = self.trophy.find("**/trophyStatue") + # Give the base a nice marble look + self.base.setColorScale(1,1,0.8,1) + self.bowl = loader.loadModel("phase_3.5/models/gui/fishingTrophyBowl") + self.bowl.reparentTo(self) + self.bowl.setPos(0,1,0) + self.bowl.setScale(2.0) + self.bowlTop = self.bowl.find("**/fishingTrophyGreyBowl") + self.bowlBase = self.bowl.find("**/fishingTrophyBase") + # Give the base a nice marble look + self.bowlBase.setScale(1.25, 1, 1) + self.bowlBase.setColorScale(1,1,0.8,1) + self.nameLabel = DirectLabel( + parent = self, + relief = None, + pos = (0,0,-0.15), + text = "Trophy Text", + text_scale = 0.125, + text_fg = Vec4(0.9,0.9,0.4,1), + ) + self.shadow = loader.loadModel("phase_3/models/props/drop_shadow") + self.shadow.reparentTo(self) + self.shadow.setColor(1,1,1,0.2) + self.shadow.setPosHprScale(0,1,0.35, 0,90,0, 0.1,0.14,0.1) + self.setLevel(level) + + def setLevel(self, level): + assert self.notify.debugStateCall(self) + self.level = level + if level == -1: + self.trophy.hide() + self.bowl.hide() + self.nameLabel.hide() + elif level == 0: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 1.11878) + self.top.setPos(0,0,-1) + self.__bronze() + elif level == 1: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 1.61878) + self.top.setPos(0,0,-0.5) + self.__bronze() + elif level == 2: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 2.11878) + self.top.setPos(0,0,0) + self.__silver() + elif level == 3: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 2.61878) + self.top.setPos(0,0,0.5) + self.__silver() + elif level == 4: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 3.11878) + self.top.setPos(0,0,1) + self.__gold() + elif level == 5: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(1.75) + self.nameLabel.show() + self.__bronze() + elif level == 6: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(2.0) + self.nameLabel.show() + self.__silver() + elif level >= 7: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(2.25) + self.nameLabel.show() + self.__gold() + + def __bronze(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(0.9,0.6,0.33,1) + self.bowlTop.setColorScale(0.9,0.6,0.33,1) + + def __silver(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(0.9,0.9,1,1) + self.bowlTop.setColorScale(0.9,0.9,1,1) + + def __gold(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(1,0.95,0.1,1) + self.bowlTop.setColorScale(1,0.95,0.1,1) + + def destroy(self): + assert self.notify.debugStateCall(self) + self.trophy.removeNode() + self.bowl.removeNode() + self.shadow.removeNode() + DirectFrame.destroy(self) + + def show(self): + ShtikerPage.show(self) + #print("showing fishPage") + #self.updatePage() diff --git a/toontown/src/shtiker/GardenPage.py b/toontown/src/shtiker/GardenPage.py new file mode 100644 index 0000000..40a257d --- /dev/null +++ b/toontown/src/shtiker/GardenPage.py @@ -0,0 +1,871 @@ +"""GardenPage module: contains the GardenPage class""" + +from direct.directnotify import DirectNotifyGlobal +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from toontown.estate import FlowerBrowser +from toontown.estate import GardenGlobals +from toontown.estate import FlowerPicker +from toontown.estate import SpecialsPhoto +from toontown.toontowngui import TTDialog + +# display modes +GardenPage_Basket = 0 +GardenPage_Collection = 1 +GardenPage_Trophy = 2 +GardenPage_Specials = 3 + +TROPHIES_PER_ROW = 5 + +class GardenPage(ShtikerPage.ShtikerPage): + """ + GardenPage keeps track of flowers picked and their names. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("GardenPage") + + def __init__(self): + """ + GardenPage constructor: create the garden page + """ + self.notify.debug('__init__') + assert self.notify.debugStateCall(self) + ShtikerPage.ShtikerPage.__init__(self) + self.mode = GardenPage_Basket + + self.accept("use-special-response", self.useSpecialDone) + self.resultDialog = None + + def enter(self): + self.notify.debug('enter') + assert self.notify.debugStateCall(self) + if not hasattr(self, "title"): + self.load() + # first time in make setMode update even if the mode hasn't changed + self.setMode(self.mode, 1) + self.accept(localAvatar.uniqueName("flowerBasketChange"), self.updatePage) + ShtikerPage.ShtikerPage.enter(self) + + + def exit(self): + self.notify.debug('exit') + assert self.notify.debugStateCall(self) + if hasattr(self, "picker"): + self.picker.hide() + if hasattr(self, "browser"): + self.browser.hide() + if hasattr(self, "specialsFrame"): + self.specialsFrame.hide() + if hasattr(self, "specialsPhoto"): + self.specialsPhoto.hide() + if hasattr(self, "useSpecialButton"): + self.hide() + self.cleanupResultDialog() + ShtikerPage.ShtikerPage.exit(self) + + + + def load(self): + self.notify.debug('load') + assert self.notify.debugStateCall(self) + ShtikerPage.ShtikerPage.load(self) + gui = loader.loadModel("phase_3.5/models/gui/fishingBook") + + """ + rodFrame = gui.find("**/bucket/fram1") + rodFrame.removeNode() + """ + trophyCase = gui.find("**/trophyCase1") + trophyCase.find("glass1").reparentTo(trophyCase,-1) + trophyCase.find("shelf").reparentTo(trophyCase,-1) + self.trophyCase=trophyCase + + + # page title + self.title = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.1, + pos = (0,0,0.65), + ) + + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1, 1, 1, 1) + clickColor = (.8, .8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + + self.basketTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GardenPageBasketTab, + text_scale = TTLocalizer.GPBasketTabTextScale, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface1"), + image_pos = (0.55,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [GardenPage_Basket], + pos = (0.92, 0, 0.55), + ) + self.collectionTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GardenPageCollectionTab, + text_scale = TTLocalizer.GPCollectionTabTextScale, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [GardenPage_Collection], + pos = (0.92, 0, 0.1), + ) + self.trophyTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GardenPageTrophyTab, + text_scale = TTLocalizer.GPTrophyTabTextScale, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface3"), + image_pos = (-0.28,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [GardenPage_Trophy], + pos = (0.92, 0, -0.3), + ) + + self.specialsTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GardenPageSpecialsTab, + text_scale = TTLocalizer.GPSpecialsTabTextScale, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface3"), + image_pos = (-0.28,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [GardenPage_Specials], + pos = (0.92, 0, -0.3), + ) + self.basketTab.setPos(-0.75,0,0.775) + self.collectionTab.setPos(-0.33,0,0.775) + self.trophyTab.setPos(0.09,0,0.775) + self.specialsTab.setPos(0.51,0,0.775) + + gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + self.gardenSpecialsList = DirectScrolledList( + parent = self, + relief = None, + # inc and dec are DirectButtons + incButton_image = (gui.find("**/FndsLst_ScrollUp"), + gui.find("**/FndsLst_ScrollDN"), + gui.find("**/FndsLst_ScrollUp_Rllvr"), + gui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_pos = (0.0, 0.0, -1.1), + # Make the disabled button darker + incButton_image1_color = Vec4(1.0, 0.9, 0.4, 1.0), + incButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.5), + incButton_scale = (1.0, 1.0, -1.0), + decButton_image = (gui.find("**/FndsLst_ScrollUp"), + gui.find("**/FndsLst_ScrollDN"), + gui.find("**/FndsLst_ScrollUp_Rllvr"), + gui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_pos = (0.0, 0.0, 0.117), + # Make the disabled button darker + decButton_image1_color = Vec4(1.0, 1.0, 0.6, 1.0), + decButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.6), + + # itemFrame is a DirectFrame + itemFrame_pos = (-0.2, 0.0, 0.05), + itemFrame_relief = None, + #itemFrame_frameSize= (-0.05,0.75,-0.75,0.05), + # each item is a button with text on it + numItemsVisible = 18, + items = [], + pos = (-0.60, 0, 0.45), + ) + self.gardenSpecialsList.hide() + + self.specialsFrame = DirectFrame( + parent = self, + relief = None, + pos = (0.45, 0.0, 0.25), + text = "", + text_wordwrap = 14.4, + text_pos = (0, -0.46), + text_scale = 0.06, + ) + + self.specialsInfo = DirectLabel( + parent = self.specialsFrame, + relief = None, + pos = (0.0, 0.0, -0.0), + text = " ", + text_wordwrap = 12.4, + text_pos = (0, -0.46), + text_scale = 0.06, + ) + + self.specialsPhoto = SpecialsPhoto.SpecialsPhoto(-1, parent = self.specialsFrame) + self.specialsPhoto.setBackBounds(-0.3, 0.3, -0.235, 0.25) + # Parchment paper background: + self.specialsPhoto.setBackColor(1.0, 1.0, 0.74901, 1.0) + #self.specialsPhoto.setPos(0.45, 0.0, 0.25) + + + # Init buttons + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + okImageList = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')) + + self.useSpecialButton = DirectButton( + parent = self, + relief = None, + image = okImageList, + #pos = (0.6, 0, -0.58), + pos = (0.45, 0, -0.5), + text = TTLocalizer.UseSpecial, + text_scale = 0.06, + text_pos = (0,-0.1), + command = self.__useSpecial, + ) + buttons.removeNode() + + + def setMode(self, mode, updateAnyways=0): + assert self.notify.debugStateCall(self) + messenger.send('wakeup') + if not updateAnyways: + if self.mode == mode: + return + else: + self.mode = mode + self.gardenSpecialsList.hide() + self.specialsPhoto.hide() + self.specialsFrame.hide() + self.useSpecialButton.hide() + if mode == GardenPage_Basket: + self.title['text'] = TTLocalizer.GardenPageTitleBasket + + if not hasattr(self, "picker"): + self.createFlowerPicker() + self.picker.show() + + if hasattr(self, "browser"): + self.browser.hide() + + if hasattr(self, "trophyFrame"): + self.trophyFrame.hide() + + self.basketTab['state'] = DGG.DISABLED + self.collectionTab['state'] = DGG.NORMAL + self.trophyTab['state'] = DGG.NORMAL + self.specialsTab['state'] = DGG.NORMAL + + + elif mode == GardenPage_Collection: + self.title['text'] = TTLocalizer.GardenPageTitleCollection + if hasattr(self, "picker"): + self.picker.hide() + if not hasattr(self, "browser"): + self.createAlbumBrowser() + pass + + self.browser.show() + if hasattr(self, "trophyFrame"): + self.trophyFrame.hide() + + self.basketTab['state'] = DGG.NORMAL + self.collectionTab['state'] = DGG.DISABLED + self.trophyTab['state'] = DGG.NORMAL + self.specialsTab['state'] = DGG.NORMAL + elif mode == GardenPage_Trophy: + self.title['text'] = TTLocalizer.GardenPageTitleTrophy + + if hasattr(self, "picker"): + self.picker.hide() + + if hasattr(self, "browser"): + self.browser.hide() + + if not hasattr(self, "trophyFrame"): + self.createGardenTrophyFrame() + self.trophyFrame.show() + + self.basketTab['state'] = DGG.NORMAL + self.collectionTab['state'] = DGG.NORMAL + self.trophyTab['state'] = DGG.DISABLED + self.specialsTab['state'] = DGG.NORMAL + + elif mode == GardenPage_Specials: + self.title['text'] = TTLocalizer.GardenPageTitleSpecials + + if hasattr(self, "picker"): + self.picker.hide() + + if hasattr(self, "browser"): + self.browser.hide() + + if hasattr(self, "trophyFrame"): + self.trophyFrame.hide() + + self.basketTab['state'] = DGG.NORMAL + self.collectionTab['state'] = DGG.NORMAL + self.trophyTab['state'] = DGG.NORMAL + self.specialsTab['state'] = DGG.DISABLED + self.gardenSpecialsList.show() + + specialsList = localAvatar.getGardenSpecials() + self.specialsPhoto.show() + self.specialsFrame.show() + + self.createGardenSpecialsList() + else: + # hmmm... + pass + + # just for GP's + self.updatePage() + + def createGardenSpecialsList(self): + self.clearGS() + self.specialsInfo['text'] = '' + self.useSpecialButton.hide() + self.specialsPhoto.hide() + self.specialsPhoto.update(-1) + self.specialsPhoto.show() + + specialsList = localAvatar.getGardenSpecials() + firstEntry = None + + if len(specialsList) == 0: + self.gardenSpecialsList["incButton_image1_color"] = Vec4(1.0, 0.9, 0.4, 0.0) + self.gardenSpecialsList["incButton_image3_color"] = Vec4(1.0, 0.9, 0.4, 0.0) + self.gardenSpecialsList["decButton_image1_color"] = Vec4(1.0, 0.9, 0.4, 0.0) + self.gardenSpecialsList["decButton_image3_color"] = Vec4(1.0, 0.9, 0.4, 0.0) + + + else: + self.gardenSpecialsList["incButton_image1_color"] = Vec4(1.0, 0.9, 0.4, 1.0) + self.gardenSpecialsList["incButton_image3_color"] = Vec4(1.0, 0.9, 0.4, 1.0) + self.gardenSpecialsList["decButton_image1_color"] = Vec4(1.0, 0.9, 0.4, 1.0) + self.gardenSpecialsList["decButton_image3_color"] = Vec4(1.0, 0.9, 0.4, 1.0) + for entry in specialsList: + if not firstEntry: + firstEntry = entry + someItem = DirectScrolledListItem( + parent = self.gardenSpecialsList, + text = ("%s x %s" % (GardenGlobals.Specials[entry[0]]['photoName'], entry[1])), + text_align = TextNode.ALeft, + text_fg = (0.0, 0.0, 0.0, 1), + text_bg = (1.0, 1.0, 1, 0), + text_scale = 0.06, + relief = None, + command = self.showSpecialsPanel, + extraArgs = [entry], + ) + self.gardenSpecialsList.addItem(someItem) + self.specialsPhoto.show() + if firstEntry: + self.showSpecialsPanel(firstEntry) + + + + def showSpecialsPanel(self, entry): + type = entry[0] + number = entry[1] + self.specialsPhoto.hide() + self.specialsPhoto.update(type) + self.specialsPhoto.show() + self.specialsInfo['text'] = GardenGlobals.Specials[entry[0]]['description'] + self.selectedSpecial = type + specialInfo = GardenGlobals.Specials[entry[0]] + if specialInfo.has_key('useFromShtiker') and specialInfo['useFromShtiker']: + self.useSpecialButton.show() + else: + self.useSpecialButton.hide() + + def __useSpecial(self): + #disable the button so we don't call it twice from the client + self.useSpecialButton['state'] = DGG.DISABLED + + localAvatar.sendUpdate('reqUseSpecial',[self.selectedSpecial]) + pass + + + def clearGS(self): + while len(self.gardenSpecialsList['items']) > 0: + for item in self.gardenSpecialsList['items']: + #self.gardenSpecialsList.removeItem(item, 1) + self.gardenSpecialsList.removeItem(item, 1) + if hasattr(item, "destroy"): + item.destroy() + if hasattr(item, "delete"): + item.delete() + del item + + + def createAlbumBrowser(self): + """ + Album Tab + """ + + if not hasattr(self, "browser"): + self.browser = FlowerBrowser.FlowerBrowser(self) + self.browser.setScale(1.1) + # flower collected total: + self.collectedTotal = DirectLabel( + parent = self.browser, + relief = None, + text = "", + text_scale = 0.06, + pos = (0,0,-0.61), + ) + + def createGardenTrophyFrame(self): + """ + Trophy Tab + """ + if not hasattr(self, "trophyFrame"): + # trophy stuff + self.trophyFrame = DirectFrame( + parent = self, + relief = None, + image = self.trophyCase, + image_pos = (0,1,0), + image_scale = 0.034, + ) + # Order things properly + self.trophyFrame.hide() + + self.trophies = [] + hOffset = -0.5 + vOffset = 0.4 + for level, trophyDesc in GardenGlobals.TrophyDict.items(): + trophy = GardenTrophy(-1) + trophy.nameLabel['text'] = trophyDesc[0] + trophy.reparentTo(self.trophyFrame) + trophy.setScale(0.36) + # see if we have reached start of new row + if (level % TROPHIES_PER_ROW) == 0: + hOffset = -0.5 + vOffset -= 0.4 + trophy.setPos(hOffset,0,vOffset) + hOffset += 0.25 + self.trophies.append(trophy) + + def createFlowerPicker(self): + """ + Baseket Tab + """ + if not hasattr(self, "picker"): + # create the various display elements + self.picker = FlowerPicker.FlowerPicker(self) + self.picker.setPos(-0.555, 0, 0.1) + self.picker.setScale(0.95) + + + # shovel info + #self.shovel = DirectLabel( + # parent = self.picker, + # relief = None, + # text = "", + # text_align = TextNode.ALeft, + # text_scale = 0.06, + # pos = (0.65,0,-0.65), + # ) + + self.FUDGE_FACTOR = 0.01 + self.barLength = 1.1 + self.shovelBar = DirectWaitBar( + parent = self.picker, + pos = (0.95,0,-0.55), + relief = DGG.SUNKEN, + frameSize = (-0.65,1.05,-0.1,0.1), + borderWidth = (0.025,0.025), + scale = 0.45, + frameColor = (0.8,0.8,0.7,1), + barColor = (0.6,0.4,0.2,1), + # A small number was added to these to prevent small + # values from not being rendered at odd resolutions. + range = self.barLength + self.FUDGE_FACTOR, + value = (self.barLength * 0.5) + self.FUDGE_FACTOR, + text = " " + TTLocalizer.Laff, + text_scale = 0.11, + text_fg = (0.05,0.14,0.2,1), + text_align = TextNode.ALeft, + text_pos = (-0.57,-0.035), + ) + + self.wateringCanBar = DirectWaitBar( + parent = self.picker, + pos = (0.95,0,-0.75), + relief = DGG.SUNKEN, + frameSize = (-0.65,1.05,-0.1,0.1), + borderWidth = (0.025,0.025), + scale = 0.45, + frameColor = (0.8,0.8,0.7,1), + barColor = (0.4,0.6,1.0,1), + # A small number was added to these to prevent small + # values from not being rendered at odd resolutions. + range = self.barLength + self.FUDGE_FACTOR, + value = (self.barLength * 0.5) + self.FUDGE_FACTOR, + text = " " + TTLocalizer.Laff, + text_scale = 0.11, + text_fg = (0.05,0.14,0.2,1), + text_align = TextNode.ALeft, + text_pos = (-0.57,-0.035), + ) + + + def unload(self): + print("gardenPage Unloading") + assert self.notify.debugStateCall(self) + if hasattr(self, "specialsPhoto"): + #self.specialsPhoto.destroy() + del self.specialsPhoto + if hasattr(self, "trophies"): + del self.trophies + if hasattr(self, "trophyCase"): + del self.trophyCase + if hasattr(self, 'useSpecialButton'): + self.useSpecialButton.destroy() + del self.useSpecialButton + self.cleanupResultDialog() + self.gardenSpecialsList.destroy() + self.basketTab.destroy() + self.collectionTab.destroy() + self.trophyTab.destroy() + self.specialsTab.destroy() + ShtikerPage.ShtikerPage.unload(self) + + def updatePage(self): + #import pdb; pdb.set_trace() + assert self.notify.debugStateCall(self) + + + if hasattr(self, "collectedTotal"): + # update the total collection + self.collectedTotal['text'] = ( + TTLocalizer.GardenPageCollectedTotal % + (len(base.localAvatar.flowerCollection), + GardenGlobals.getNumberOfFlowerVarieties())) + + if hasattr(self, "shovelBar"): + # update the shovel info on the picker + shovel = base.localAvatar.shovel + shovelName = TTLocalizer.ShovelNameDict[shovel] + curShovelSkill = base.localAvatar.shovelSkill + maxShovelSkill = GardenGlobals.ShovelAttributes[shovel]['skillPts'] + + #check if he's on the last shovel, if he is decrease max by 1 + if shovel == GardenGlobals.MAX_SHOVELS -1: + maxShovelSkill -= 1 + + wateringCan = base.localAvatar.wateringCan + wateringCanName = TTLocalizer.WateringCanNameDict[wateringCan] + curWateringCanSkill = base.localAvatar.wateringCanSkill + maxWateringCanSkill = GardenGlobals.WateringCanAttributes[wateringCan]['skillPts'] + + #check if he's on the last watering can, if he is decrease max by 1 + if wateringCan == GardenGlobals.MAX_WATERING_CANS -1: + maxWateringCanSkill -= 1 + + textToUse = TTLocalizer.GardenPageShovelInfo % ( + shovelName, curShovelSkill, maxShovelSkill) + + self.shovelBar['text'] = textToUse + self.shovelBar['value'] = ((float(curShovelSkill) / float(maxShovelSkill)) * self.barLength) + self.FUDGE_FACTOR + + textToUse = TTLocalizer.GardenPageWateringCanInfo % ( + wateringCanName, curWateringCanSkill, maxWateringCanSkill) + + self.wateringCanBar['text'] = textToUse + self.wateringCanBar['value'] = ((float(curWateringCanSkill) / float(maxWateringCanSkill)) * self.barLength) + self.FUDGE_FACTOR + #self.shovel['text'] = textToUse + + else: + print("no shovel bar") + + if self.mode == GardenPage_Collection: + if hasattr(self, "browser"): + # the flower collection may have changed, update the browser + self.browser.update() + elif self.mode == GardenPage_Basket: + if hasattr(self, "picker"): + # the flower list may have changed, update the picker + newBasketFlower = base.localAvatar.flowerBasket.getFlower() + self.picker.update(newBasketFlower) + elif self.mode == GardenPage_Trophy: + if hasattr(self, "trophies"): + # the garden trophy list may have changed, update the display + for trophy in self.trophies: + trophy.setLevel(-1) + for trophyId in base.localAvatar.getGardenTrophies(): + + self.trophies[trophyId].setLevel(trophyId) + elif self.mode == GardenPage_Specials: + self.createGardenSpecialsList() + + if not base.cr.playGame.getPlace().getState() == 'stickerBook': + #this gets called when we get the fertilizer from the mailbox + #with the result that we see statue photo floating on the screen + #so now make sure it doesn't happen + self.specialsPhoto.hide() + + pass + + + def destroy(self): + self.notify.debug('destroy') + assert self.notify.debugStateCall(self) + #self.trophy.removeNode() + #self.bowl.removeNode() + #self.shadow.removeNode() + + #guessing DirectFrame.destroy will also call these + #self.basketTab.destroy() + #self.collectionTab.destroy() + #self.trophyTab.destroy() + #if hasattr(self, "browser"): + # self.browser.destroy() + + self.useSpecialButton.destroy() + if hasattr(self, "gardenSpecialsList"): + self.clearGS() + self.gardenSpecialsList.destroy() + self.ignoreAll() + self.cleanupResultDialog() + DirectFrame.destroy(self) + + + def useSpecialDone(self, response): + stringToShow = "" + if response == 'success': + stringToShow = TTLocalizer.UseSpecialSuccess + elif response == 'badlocation': + stringToShow = TTLocalizer.UseSpecialBadLocation + else: + stringToShow = 'Unknown response %s' % response + + self.resultDialog = TTDialog.TTDialog( + parent = aspect2dp, + style = TTDialog.Acknowledge, + text = stringToShow, + command = self.cleanupResultDialog, + ) + + + + def cleanupResultDialog(self, value = None): + if self.resultDialog: + self.resultDialog.destroy() + self.resultDialog = None + self.useSpecialButton['state'] = DGG.NORMAL + + +class GardenTrophy(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory("GardenTrophy") + + def __init__(self, level): + assert self.notify.debugStateCall(self) + DirectFrame.__init__(self, relief = None) + self.initialiseoptions(GardenTrophy) + self.trophy = loader.loadModel("phase_3.5/models/gui/fishingTrophy") + self.trophy.reparentTo(self) + # Fix the model + self.trophy.setPos(0,1,0) + self.trophy.setScale(0.1) + self.base = self.trophy.find("**/trophyBase") + self.column = self.trophy.find("**/trophyColumn") + self.top = self.trophy.find("**/trophyTop") + self.topBase = self.trophy.find("**/trophyTopBase") + self.statue = self.trophy.find("**/trophyStatue") + # Give the base a nice marble look + self.base.setColorScale(1,1,0.8,1) + self.bowl = loader.loadModel("phase_3.5/models/gui/fishingTrophyBowl") + self.bowl.reparentTo(self) + self.bowl.setPos(0,1,0) + self.bowl.setScale(2.0) + self.bowlTop = self.bowl.find("**/fishingTrophyGreyBowl") + self.bowlBase = self.bowl.find("**/fishingTrophyBase") + # Give the base a nice marble look + self.bowlBase.setScale(1.25, 1, 1) + self.bowlBase.setColorScale(1,1,0.8,1) + self.nameLabel = DirectLabel( + parent = self, + relief = None, + pos = (0,0,-0.15), + text = "Trophy Text", + text_scale = 0.125, + text_fg = Vec4(0.9,0.9,0.4,1), + ) + self.shadow = loader.loadModel("phase_3/models/props/drop_shadow") + self.shadow.reparentTo(self) + self.shadow.setColor(1,1,1,0.2) + self.shadow.setPosHprScale(0,1,0.35, 0,90,0, 0.1,0.14,0.1) + + """ + self.allGardenTrophies = loader.loadModel("phase_5.5/models/estate/gardenTrophies") + self.allGardenTrophies.reparentTo(self) + self.gardenTrophies = [] + + letters = ('A','B','C','D') + for index in range(4): + #letter = 'A' + index + letter = letters[index] + findStr = 'gardenTrophey' + findStr += letter + tempTrophy = self.allGardenTrophies.find("**/%s" % findStr) + tempTrophy.setScale(0.75) + tempTrophy.reparentTo(self) + self.gardenTrophies.append(tempTrophy) + + self.allGardenTrophies.removeNode() + """ + + self.setLevel(level) + + def setLevel(self, level): + assert self.notify.debugStateCall(self) + self.level = level + + #just put them in order of height + order = ('C','D','B','A') + scales = (0.25, 0.25, 0.22, 0.25) + metalTrophy = ('wheelbarrel', 'shovels','flower','watering_can') + if self.level >= 0 and self.level< len(order): + modelStr = "phase_5.5/models/estate/trophy" + modelStr += order[level] + self.gardenTrophy = loader.loadModel(modelStr) + self.gardenTrophy.setScale(scales[level]) + self.gardenTrophy.reparentTo(self) + self.metalTrophy = self.gardenTrophy.find('**/%s' % metalTrophy[level]) + + + + if level == -1: + self.trophy.hide() + self.bowl.hide() + self.nameLabel.hide() + elif level == 0: + self.trophy.show() + self.trophy.hide() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 1.11878) + self.top.setPos(0,0,-1) + self.__bronze() + elif level == 1: + self.trophy.show() + self.trophy.hide() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 1.61878) + self.top.setPos(0,0,-0.5) + self.__bronze() + elif level == 2: + self.trophy.show() + self.trophy.hide() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 2.11878) + self.top.setPos(0,0,0) + self.__silver() + elif level == 3: + self.trophy.show() + self.trophy.hide() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 2.61878) + self.top.setPos(0,0,0.5) + self.__silver() + elif level == 4: + self.trophy.show() + self.bowl.hide() + self.nameLabel.show() + self.column.setScale(1.3229, 1.26468, 3.11878) + self.top.setPos(0,0,1) + self.__gold() + elif level == 5: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(1.75) + self.nameLabel.show() + self.__bronze() + elif level == 6: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(2.0) + self.nameLabel.show() + self.__silver() + elif level >= 7: + self.trophy.hide() + self.bowl.show() + self.bowlTop.setScale(2.25) + self.nameLabel.show() + self.__gold() + + def __bronze(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(0.9,0.6,0.33,1) + self.bowlTop.setColorScale(0.9,0.6,0.33,1) + self.metalTrophy.setColorScale(0.9,0.6,0.33,1) + + def __silver(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(0.9,0.9,1,1) + self.bowlTop.setColorScale(0.9,0.9,1,1) + self.metalTrophy.setColorScale(0.9,0.9,1,1) + + def __gold(self): + assert self.notify.debugStateCall(self) + self.top.setColorScale(1,0.95,0.1,1) + self.bowlTop.setColorScale(1,0.95,0.1,1) + self.metalTrophy.setColorScale(1,0.95,0.1,1) + + + def destroy(self): + assert self.notify.debugStateCall(self) + self.trophy.removeNode() + self.bowl.removeNode() + self.shadow.removeNode() + if hasattr(self,'gardenTrophy'): + self.gardenTrophy.removeNode() + DirectFrame.destroy(self) + diff --git a/toontown/src/shtiker/GolfPage.py b/toontown/src/shtiker/GolfPage.py new file mode 100644 index 0000000..016c0c8 --- /dev/null +++ b/toontown/src/shtiker/GolfPage.py @@ -0,0 +1,897 @@ +########################################################################## +# Module: GolfPage.py +########################################################################## + +########################################################################## +# Panda Import Modules +########################################################################## +#from direct.directbase import DirectStart +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import PythonUtil +from direct.task import Task + +########################################################################## +# Toontown Import Modules +########################################################################## +from toontown.fishing.FishPhoto import DirectRegion +from toontown.shtiker.ShtikerPage import ShtikerPage +from toontown.toonbase import ToontownGlobals, TTLocalizer +from FishPage import FishingTrophy +from toontown.golf import GolfGlobals + +########################################################################## +# Python Import Modules +########################################################################## +if( __debug__ ): + import pdb + +########################################################################## +# Global Variables and Enumerations +########################################################################## +PageMode = PythonUtil.Enum( "Records, Trophy" ) + +class GolfPage( ShtikerPage ): + + """ + Purpose: The GolfPage class provides the basic functionality for + switching between the different pages like records, and trophies. + + Note: The GolfPage is a singleton object because only one instance of + the page should exist at a time. + """ + + ###################################################################### + # Class variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "GolfPage" ) + + def __init__( self ): + """ + Purpose: The __init__ Method provides the intial construction + of the GolfPage object as well as constructing the ShtikerPage + superclass. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + ShtikerPage.__init__( self ) + + self.avatar = None + self.mode = PageMode.Trophy + + def enter( self ): + """ + Purpose: The enter Method. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + # If the Page has not already been loaded, do so now before + # the page mode. + if( not hasattr( self, "title" ) ): + self.load() + self.setMode( self.mode, 1 ) + + # Make the call to the superclass enter method. + ShtikerPage.enter( self ) + + def exit( self ): + """ + Purpose: The exit Method. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + self.golfTrophies.hide() + self.golfRecords.hide() + + # Make the call to the superclass exit method. + ShtikerPage.exit( self ) + + def setAvatar( self, av ): + """ + Purpose: The setAvatar Method sets the current avatar who is + looking at the GolfPage of the Shtiker book. + + Params: av - The avatar who is looking at the page. + Return: None + """ + + self.avatar = av + + def getAvatar( self ): + """ + Purpose: The getAvatar Method retrieves the current avatar who is + looking at the GolfPage of the Shtiker book. + + Params: None + Return: Avatar - The avatar looking at the page. + """ + + return self.avatar + + def load( self ): + """ + Purpose: The load Method is to properly load the appropriate + GUI for the GolfPage of the Shtiker Book. + + Params: None + Return: None + """ + + assert self.notify.debugStateCall(self) + ShtikerPage.load( self ) + + # Load the Records Page GUI + self.golfRecords = GolfingRecordsUI( self.avatar, self ) + self.golfRecords.hide() + self.golfRecords.load() + + # Load the Trophy Page GUI + self.golfTrophies = GolfTrophiesUI( self.avatar, self ) + self.golfTrophies.hide() + self.golfTrophies.load() + + # Page Title + self.title = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.1, + pos = ( 0, 0, 0.65 ), + ) + + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1, 1, 1, 1) + clickColor = (.8, .8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + + # Load the Fish Page to borrow its tabs + gui = loader.loadModel( "phase_3.5/models/gui/fishingBook" ) + + + self.recordsTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GolfPageRecordsTab, + text_scale = TTLocalizer.GFPRecordsTabTextScale, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [ PageMode.Records ], + pos = TTLocalizer.GFPRecordsTabPos, + ) + self.trophyTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.GolfPageTrophyTab, + text_scale = TTLocalizer.GFPTrophyTabTextScale, + text_pos = TTLocalizer.GFPRecordsTabTextPos, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface3"), + image_pos = (-0.28,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [ PageMode.Trophy ], + pos = TTLocalizer.GFPRTrophyTabPos, + ) + + self.recordsTab.setPos(-0.13,0,0.775) + self.trophyTab.setPos(0.28,0,0.775) + adjust = -0.20 + self.recordsTab.setX(self.recordsTab.getX() + adjust) + self.trophyTab.setX(self.trophyTab.getX() + adjust) + + gui.removeNode() + + def unload( self ): + """ + Purpose: The unload Method performs the necessary unloading of + the GolfPage. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + self.avatar = None # break the garbage cycle + ShtikerPage.unload( self ) + + def setMode( self, mode, updateAnyways = 0 ): + """ + Purpose: The setMode Method sets the current mode of the GolfPage + of the Shtiker Book. + + Params: mode - the new mode. + updateAnyways - update the page based on the mode. + Return: None + """ + + messenger.send( 'wakeup' ) + + if( not updateAnyways ): + if( self.mode == mode ): + return + else: + self.mode = mode + + if( mode == PageMode.Records ): + self.title[ 'text' ] = TTLocalizer.GolfPageTitleRecords + self.recordsTab[ 'state' ] = DGG.DISABLED + self.trophyTab[ 'state' ] = DGG.NORMAL + + elif( mode == PageMode.Trophy ): + self.title[ 'text' ] = TTLocalizer.GolfPageTitleTrophy + self.recordsTab[ 'state' ] = DGG.NORMAL + self.trophyTab[ 'state' ] = DGG.DISABLED + + else: + raise StandardError, "GolfPage::setMode - Invalid Mode %s" % ( mode ) + + self.updatePage() + + def updatePage( self ): + """ + Purpose: The updatePage Method updates the GolfPage of the + ShtikerBook. + + Params: None + Return: None + """ + + if( self.mode == PageMode.Records ): + self.golfTrophies.hide() + self.golfRecords.show() + elif( self.mode == PageMode.Trophy ): + self.golfTrophies.show() + self.golfRecords.hide() + else: + raise StandardError, "GolfPage::updatePage - Invalid Mode %s" % ( self.mode ) + + +class GolfingRecordsUI( DirectFrame ): + """ + Purpose: The GolfingRecordsUI class initializes the user interface for + displaying the personal best times earned by a toon. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "GolfingRecordsUI" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the GolfingRecordsUI object. + + Params: None + Return: None + """ + + # Initialize instance variables + self.avatar = avatar + self.bestDisplayList = [] + self.lastHoleBest = [] + self.lastCourseBest = [] + self.scrollList = None + + # Construct the super class object + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + + def destroy( self ): + """ + Purpose: The destroy Method properly handles the destruction of + the GolfingRecordsUI instance by handling appropriate reference + cleanup. + + Params: None + Return: None + """ + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + self.gui.removeNode() + self.scrollList.destroy() + del self.avatar, self.lastHoleBest, self.lastCourseBest, self.bestDisplayList, self.scrollList + + # Destroy the DirectFrame super class. + DirectFrame.destroy( self ) + + def load( self ): + """ + Purpose: The load Method handles the construction of the specific + UI components that make up the RaceRecordsUI object. + + Params: None + Return: None + """ + self.gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + self.listXorigin = -0.5 + self.listFrameSizeX = 1.5 + self.listZorigin = -0.9 + self.listFrameSizeZ = 1.04 + self.arrowButtonScale = 1.3 + self.itemFrameXorigin = -0.237 + self.itemFrameZorigin = 0.365 + self.labelXstart = self.itemFrameXorigin + 0.293 + + self.scrollList = DirectScrolledList( + parent = self, + relief = None, + pos = (0, 0, 0), + # inc and dec are DirectButtons + # incButton is on the bottom of page, decButton is on the top! + incButton_image = (self.gui.find("**/FndsLst_ScrollUp"), + self.gui.find("**/FndsLst_ScrollDN"), + self.gui.find("**/FndsLst_ScrollUp_Rllvr"), + self.gui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_scale = (self.arrowButtonScale, self.arrowButtonScale, -self.arrowButtonScale), + incButton_pos = (self.labelXstart, 0, self.itemFrameZorigin - 0.999), + # Make the disabled button fade out + incButton_image3_color = Vec4(1, 1, 1, 0.2), + decButton_image = (self.gui.find("**/FndsLst_ScrollUp"), + self.gui.find("**/FndsLst_ScrollDN"), + self.gui.find("**/FndsLst_ScrollUp_Rllvr"), + self.gui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_scale = (self.arrowButtonScale, self.arrowButtonScale, self.arrowButtonScale), + decButton_pos = (self.labelXstart, 0, self.itemFrameZorigin + 0.227), + # Make the disabled button fade out + decButton_image3_color = Vec4(1, 1, 1, 0.2), + + # itemFrame is a DirectFrame + itemFrame_pos = (self.itemFrameXorigin, 0, self.itemFrameZorigin), + itemFrame_scale = 1.0, + itemFrame_relief = DGG.SUNKEN, + # frameSize is (minX,maxX,minZ,maxZ); where x goes left->right neg->pos, + # and z goes bottom->top neg->pos + itemFrame_frameSize = (self.listXorigin, self.listXorigin + self.listFrameSizeX, + self.listZorigin, self.listZorigin + self.listFrameSizeZ), + itemFrame_frameColor = (0.85, 0.95, 1, 1), + itemFrame_borderWidth = (0.01, 0.01), + # each item is a button with text on it + numItemsVisible = 12, + # need to set height of each entry to avoid list text running off end of listbox + forceHeight = 0.083, + items = [], + ) + + # course bests + for courseId in GolfGlobals.CourseInfo: + courseName = GolfGlobals.getCourseName(courseId) + frame = DirectFrame( + parent = self.scrollList, + relief = None) + courseNameDisplay = DirectLabel( + parent = frame, + relief = None, + pos = (-0.475, 0, 0.05), + text = courseName, + text_align = TextNode.ALeft, + text_scale = 0.075, + text_fg = (0.85, 0.64, 0.13, 1.0), + text_shadow = (0, 0, 0, 1), + text_font = ToontownGlobals.getSignFont() + ) + bestScoreDisplay = DirectLabel( + parent = frame, + relief = None, + pos = (0.9, 0, 0.05), + text = TTLocalizer.KartRace_Unraced, + text_scale = 0.06, + text_fg = (0.0, 0.0, 0.0, 1.0), + text_font = ToontownGlobals.getToonFont() + ) + # save this one for updating + self.bestDisplayList.append(bestScoreDisplay) + # add to scrolled list + self.scrollList.addItem(frame) + + # hole bests + for holeId in GolfGlobals.HoleInfo: + holeName = GolfGlobals.getHoleName(holeId) + frame = DirectFrame( + parent = self.scrollList, + relief = None) + holeNameDisplay = DirectLabel( + parent = frame, + relief = None, + pos = (-0.475, 0, 0.05), + text = holeName, + text_align = TextNode.ALeft, + text_scale = 0.075, + text_fg = (0.95, 0.95, 0.0, 1.0), + text_shadow = (0, 0, 0, 1), + text_font = ToontownGlobals.getSignFont() + ) + bestScoreDisplay = DirectLabel( + parent = frame, + relief = None, + pos = (0.9, 0, 0.05), + text = TTLocalizer.KartRace_Unraced, + text_scale = 0.06, + text_fg = (0.0, 0.0, 0.0, 1.0), + text_font = ToontownGlobals.getToonFont() + ) + # save this one for updating + self.bestDisplayList.append(bestScoreDisplay) + # add to scrolled list + self.scrollList.addItem(frame) + + def show( self ): + # update personal best times + bestHoles = self.avatar.getGolfHoleBest() + bestCourses = self.avatar.getGolfCourseBest() + # but only if they have changed + if bestHoles != self.lastHoleBest or bestCourses != self.lastCourseBest: + numCourse = len(GolfGlobals.CourseInfo.keys()) + numHoles = len(GolfGlobals.HoleInfo.keys()) + for i in xrange(numCourse): + score = bestCourses[i] + if score != 0: + # use their best score + self.bestDisplayList[i]['text'] = str(score), + else: + self.bestDisplayList[i]['text'] = TTLocalizer.KartRace_Unraced + for i in xrange(numHoles): + score = bestHoles[i] + if score != 0: + self.bestDisplayList[i+numCourse]['text'] = str(score) + else: + self.bestDisplayList[i+numCourse]['text'] = TTLocalizer.KartRace_Unraced + self.lastHoleBest = bestHoles[:] + self.lastCourseBest = bestCourses[:] + DirectFrame.show( self ) + + def regenerateScrollList(self): + print "### regen scroll" + selectedIndex = 0 + if self.scrollList: + selectedIndex = self.scrollList.getSelectedIndex() + for label in self.bestDisplayList: + label.detachNode() + self.scrollList.destroy() + self.scrollList = None + + self.scrollList.scrollTo(selectedIndex) + + +class GolfTrophiesUI( DirectFrame ): + """ + Purpose: The GolfTrophiesUI class initializes the user interface for + displaying the golfing trophies earned by a toon. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "GolfTrophiesUI" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the RacingTrophiesUI object. + + Params: None + Return: None + """ + + # Initialize instance variables + self.avatar = avatar + self.trophyPanels = [] + self.cupPanels = [] + self.trophies = None + self.cups = None + self.trophyTextDisplay = None + + # Construct the super class object + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + + def destroy( self ): + """ + Purpose: The destroy Method properly handles thepass destruction of + the RacingTrophiesUI instance by handling appropriate reference + cleanup. + + Params: None + Return: None + """ + for panel in self.trophyPanels: + panel.destroy() + for panel in self.cupPanels: + panel.destroy() + self.currentHistory.destroy() + self.trophyTextDisplay.destroy() + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + del self.avatar, self.currentHistory, self.trophyPanels, self.trophies, self.trophyTextDisplay, self.cups, self.cupPanels + + # Destroy the DirectFrame super class. + DirectFrame.destroy( self ) + + def load( self ): + """pass + Purpose: The load Method handles the construction of the specific + UI components that make up the RacingTrophiesUI object. + + Params: None + Return: None + """ + self.trophies = base.localAvatar.getGolfTrophies()[:] + self.cups = base.localAvatar.getGolfCups()[:] + + xStart = -0.76 + yStart = 0.475 + xOffset = 0.17 + yOffset = 0.23 + # display the trophies a toon has + for j in range(GolfGlobals.NumCups): + for i in range(GolfGlobals.TrophiesPerCup): + trophyPanel = DirectLabel( + parent = self, + relief = None, + pos = (xStart + (i * xOffset), + 0.0, + yStart - (j * yOffset)), + state = DGG.NORMAL, + image = DGG.getDefaultDialogGeom(), + image_scale = (0.75, 1, 1), + image_color = (0.8, 0.8, 0.8, 1), + text = TTLocalizer.SuitPageMystery[0], + text_scale = 0.45, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0, -0.25), + text_font = ToontownGlobals.getInterfaceFont(), + text_wordwrap = 5.5 + ) + + trophyPanel.scale = 0.2 + trophyPanel.setScale(trophyPanel.scale) + + # add to master list + self.trophyPanels.append(trophyPanel) + + xStart = -0.25 + yStart = -0.38 + xOffset = 0.25 + for i in range(GolfGlobals.NumCups): + cupPanel = DirectLabel( + parent = self, + relief = None, + pos = (xStart + (i * xOffset), 0.0, yStart), + state = DGG.NORMAL, + image = DGG.getDefaultDialogGeom(), + image_scale = (0.75, 1, 1), + image_color = (0.8, 0.8, 0.8, 1), + text = TTLocalizer.SuitPageMystery[0], + text_scale = 0.45, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0, -0.25), + text_font = ToontownGlobals.getInterfaceFont(), + text_wordwrap = 5.5 + ) + cupPanel.scale = 0.3 + cupPanel.setScale(cupPanel.scale) + self.cupPanels.append(cupPanel) + + # display the current number of tickets a toon has + self.currentHistory = DirectLabel( + parent = self, + relief = None, + #image = loader.loadModel('phase_6/models/karting/tickets'), + #image_pos = (0.2,0,-0.635), + #image_scale = 0.2, + text = '', #TTLocalizer.KartPageTickets + str(self.avatar.getTickets()), + text_scale = 0.05, + text_fg = ( 0, 0, 0.95, 1.0 ), + text_pos = ( 0, -0.65), + #text_font = ToontownGlobals.getSignFont() + ) + + # display the text description of a moused over trophy + self.trophyTextDisplay = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.07, + text_fg = ( 1, 0, 0, 1 ), + text_shadow = ( 0, 0, 0, 0 ), + text_pos = ( 0.0, -0.175 ), + text_font = ToontownGlobals.getInterfaceFont() + ) + + self.updateTrophies() + + def grow(self, index, pos): + self.trophyPanels[index]['image_color'] = Vec4(1.0, 1.0, 0.8, 1.0) + if index < GolfGlobals.NumTrophies: + self.trophyTextDisplay['text'] = TTLocalizer.GolfTrophyTextDisplay % \ + {'number': index +1, + 'desc' : TTLocalizer.GolfTrophyDescriptions[index]} + # update the current history + historyIndex = GolfGlobals.getHistoryIndexForTrophy(index) + if historyIndex >= 0: + self.currentHistory['text'] = TTLocalizer.GolfCurrentHistory % \ + {'historyDesc' : TTLocalizer.GolfHistoryDescriptions[historyIndex], + 'num' : self.avatar.getGolfHistory()[historyIndex]} + + + def shrink(self, index, pos): + self.trophyPanels[index]['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + self.trophyTextDisplay['text'] = "" + self.currentHistory['text']='' + + def growCup(self, index, pos): + self.cupPanels[index]['image_color'] = Vec4(1.0, 1.0, 0.8, 1.0) + if index < GolfGlobals.NumTrophies: + self.trophyTextDisplay['text'] = TTLocalizer.GolfCupTextDisplay % \ + {'number': index +1, + 'desc' : TTLocalizer.GolfCupDescriptions[index]} + def shrinkCup(self, index, pos): + self.cupPanels[index]['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + self.trophyTextDisplay['text'] = "" + + def show( self ): + # update current history + self.currentHistory['text'] = '' + # see if the any new trophies need to be displayed + if self.trophies != base.localAvatar.getGolfTrophies(): + self.trophies = base.localAvatar.getGolfTrophies() + self.cups = base.localAvatar.getGolfCups() + self.updateTrophies() + DirectFrame.show( self ) + + def updateTrophies( self ): + for t in range(len(self.trophyPanels)): + if self.trophies[t]: + # add our special scaling functions + trophyPanel = self.trophyPanels[t] + trophyPanel['text'] = "" + # get a trophy + #trophyModel = RacingTrophy(t / RaceGlobals.TrophiesPerCup) + golfTrophy = trophyPanel.find('**/*GolfTrophy*') + if golfTrophy.isEmpty(): + trophyModel = GolfTrophy(t) + trophyModel.reparentTo(trophyPanel) + trophyModel.nameLabel.hide() + trophyModel.setPos(0,0,-0.4) + trophyPanel['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + trophyPanel.bind(DGG.ENTER, self.grow, extraArgs=[t]) + trophyPanel.bind(DGG.EXIT, self.shrink, extraArgs=[t]) + else: + # handle going backward + trophyPanel = self.trophyPanels[t] + toBeNukedGolfTrophy = trophyPanel.find('**/*GolfTrophy*') + if not toBeNukedGolfTrophy.isEmpty(): + toBeNukedGolfTrophy.removeNode() + trophyPanel['text'] = TTLocalizer.SuitPageMystery[0] + trophyPanel['image_color'] = Vec4(0.8, 0.8, 0.8, 1) + trophyPanel.unbind(DGG.ENTER) + trophyPanel.unbind(DGG.EXIT) + pass + for t in range(len(self.cupPanels)): + if self.cups[t]: + # add our special scaling functions + cupPanel = self.cupPanels[t] + cupPanel['text'] = "" + # get a cup + #cupModel = RacingCup(t / RaceGlobals.TrophiesPerCup) + cupTrophy = cupPanel.find('**/*GolfTrophy*') + if cupTrophy.isEmpty(): + cupModel = GolfTrophy(t + GolfGlobals.NumTrophies) + cupModel.reparentTo(cupPanel) + cupModel.nameLabel.hide() + cupModel.setPos(0,0,-0.4) + cupPanel['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + cupPanel.bind(DGG.ENTER, self.growCup, extraArgs=[t]) + cupPanel.bind(DGG.EXIT, self.shrinkCup, extraArgs=[t]) + else: + cupPanel = self.cupPanels[t] + toBeNukedGolfCup = cupPanel.find('**/*GolfTrophy*') + if not toBeNukedGolfCup.isEmpty(): + toBeNukedGolfCup.removeNode() + cupPanel['text'] = TTLocalizer.SuitPageMystery[0] + cupPanel['image_color'] = Vec4(0.8, 0.8, 0.8, 1) + cupPanel.unbind(DGG.ENTER) + cupPanel.unbind(DGG.EXIT) + +class GolfTrophy(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory("GolfTrophy") + + def __init__(self, level, *args, **kwargs): + # Don't init the fishing trophy class. + opts = {'relief':None} + opts.update(kwargs) + DirectFrame.__init__(self,*args,**opts) + + # This is a temporary location for this model + self.trophy = loader.loadModel("phase_6/models/golf/golfTrophy") + self.trophy.reparentTo(self) + # Fix the model + self.trophy.setPos(0,1,0) + self.trophy.setScale(0.1) + self.base = self.trophy.find("**/trophyBase") + self.column = self.trophy.find("**/trophyColumn") + self.top = self.trophy.find("**/trophyTop") + self.topBase = self.trophy.find("**/trophyTopBase") + self.statue = self.trophy.find("**/trophyStatue") + # Give the base a nice marble look + self.base.setColorScale(1,1,0.8,1) + self.topBase.setColorScale(1,1,0.8,1) + + self.greyBowl = loader.loadModel("phase_6/models/gui/racingTrophyBowl2") + self.greyBowl.reparentTo(self) + self.greyBowl.setPos(0,.5,0) + self.greyBowl.setScale(2.0) + + self.goldBowl = loader.loadModel("phase_6/models/gui/racingTrophyBowl") + self.goldBowl.reparentTo(self) + self.goldBowl.setPos(0,.5,0) + self.goldBowl.setScale(2.0) + self.goldBowlBase = self.goldBowl.find("**/fishingTrophyBase") + self.goldBowlBase.hide() + + self.nameLabel = DirectLabel( + parent = self, + relief = None, + pos = (0,0,-0.15), + text = "", + text_scale = 0.125, + text_fg = Vec4(0.9,0.9,0.4,1), + ) + + self.shadow = loader.loadModel("phase_3/models/props/drop_shadow") + self.shadow.reparentTo(self) + self.shadow.setColor(1,1,1,0.2) + self.shadow.setPosHprScale(0,1,0.35, 0,90,0, 0.1,0.14,0.1) + + self.setLevel(level) + + def setLevel(self, level): + assert self.notify.debugStateCall(self) + self.level = level + if level == -1: + self.trophy.hide() + self.greyBowl.hide() + self.goldBowl.hide() + self.nameLabel.hide() + # Trophies are all a mix-and-match of different attributes. + else: + # Always show the name label. + self.nameLabel.show() + # Show the correct bowl/trophy. + if level >= 30: + self.trophy.hide() + self.greyBowl.hide() + self.goldBowl.show() + self.goldBowlBase.hide() + # For all other trophies, show the trophy graphic. + else: + self.trophy.show() + self.goldBowl.hide() + self.greyBowl.hide() + # Set the heights for the last three trophies. + # Why they would scale at different rates is beyond me. + if level == 30: + self.goldBowl.setScale(4.4,3.1,3.1) + #self.column.setScale(1.3229, 1.26468, 2.11878) + #self.top.setPos(0,0,0.5) + elif level == 31: + #self.column.setScale(1.3229, 1.26468, 2.61878) + #self.top.setPos(0,0,0.5) + self.goldBowl.setScale(3.6,3.5,3.5) + elif level >= 32: + #self.column.setScale(1.3229, 1.26468, 3.11878) + #self.top.setPos(0,0,0.5) + self.goldBowl.setScale(5.6,3.9,3.9) + # Set the color. + if (level ) % 3 == 0: + self.column.setScale(1.3229, 1.26468, 1.11878) + self.top.setPos(0,0,-1) + self.__bronze() + elif (level ) % 3 == 1: + self.column.setScale(1.3229, 1.26468, 1.61878) + self.top.setPos(0,0,-.5) + self.__silver() + elif (level ) % 3 == 2: + self.column.setScale(1.3229, 1.26468, 2.11878) + self.top.setPos(0,0,0) + self.__gold() + # Set the column color. + if level < 10: + self.__tealColumn() + elif level < 20: + self.__purpleColumn() + elif level < 30: + self.__blueColumn() + else: + self.__redColumn() + + # Methods to color the trophy statue + def __bronze(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(0.9,0.6,0.33,1) + + def __silver(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(0.9,0.9,1,1) + + def __gold(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(1,0.95,0.1,1) + + def __platinum(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(1,0.95,0.1,1) + + # Methods to color the trophy column + def __tealColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(.5,1.2,.85,1) + + def __purpleColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1,.7,.95,1) + + def __redColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1.2,.6,.6,1) + + def __yellowColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1,1,.8,1) + + def __blueColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(.6,.75,1.2,1) + + def destroy(self): + assert self.notify.debugStateCall(self) + self.trophy.removeNode() + self.goldBowl.removeNode() + self.greyBowl.removeNode() + self.shadow.removeNode() + DirectFrame.destroy(self) diff --git a/toontown/src/shtiker/HtmlView.py b/toontown/src/shtiker/HtmlView.py new file mode 100644 index 0000000..1031d22 --- /dev/null +++ b/toontown/src/shtiker/HtmlView.py @@ -0,0 +1,391 @@ +import array, sys + +# import direct.directbase.DirectStart +from direct.showbase.DirectObject import DirectObject +from direct.task.Task import Task +from direct.directnotify import DirectNotifyGlobal +from pandac.PandaModules import Texture +from pandac.PandaModules import CardMaker +from pandac.PandaModules import NodePath +from pandac.PandaModules import Point3,Vec3,Vec4,VBase4D, Point2 +from pandac.PandaModules import PNMImage +from pandac.PandaModules import TextureStage +from pandac.PandaModules import Texture +from pandac.PandaModules import WindowProperties +from direct.interval.IntervalGlobal import * + +#from toontown.shtiker.pawesomium import * +from pandac.PandaModules import AwWebView +from pandac.PandaModules import AwWebCore +#from pandac.PandaModules import AwWebViewListener + +WEB_WIDTH_PIXELS = 784 +WEB_HEIGHT_PIXELS = 451 +WEB_WIDTH = 1024 +WEB_HEIGHT = 512 +WEB_HALF_WIDTH = WEB_WIDTH / 2 + +WIN_WIDTH = 800 +WIN_HEIGHT = 600 + +GlobalWebcore = None + + +#class HtmlView( DirectObject, AwWebViewListener): +class HtmlView( DirectObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("HtmlView") + useHalfTexture = base.config.GetBool("news-half-texture", 0) + + def __init__(self, parent = aspect2d): + """Properly initialize ourself.""" + #AwWebViewListener.AwWebViewListener.__init__(self) + self.parent = parent + self.mx =0 + self.my = 0 + self.htmlFile = "index.html" + self.transparency = False # this is important looks weird if it's true + global GlobalWebcore + if GlobalWebcore: + # we get a C++ crash if we construct webcore a second time + pass + else: + GlobalWebcore = AwWebCore(AwWebCore.LOGVERBOSE, True, AwWebCore.PFBGRA) + GlobalWebcore.setBaseDirectory('.') + for errResponse in xrange(400,600): + GlobalWebcore.setCustomResponsePage(errResponse, "error.html"); + + self.webView = GlobalWebcore.createWebView(WEB_WIDTH, WEB_HEIGHT, self.transparency, False, 70) + #self.webView.setListener(self) + #self.webView.setCallback("requestFPS"); + frameName = '' + inGameNewsUrl = self.getInGameNewsUrl() + #self.webView.loadURL2(inGameNewsUrl) + + self.imgBuffer = array.array('B') + for i in xrange(WEB_WIDTH*WEB_HEIGHT): + self.imgBuffer.append(0) + self.imgBuffer.append(0) + self.imgBuffer.append(0) + self.imgBuffer.append(255) + + if self.useHalfTexture: + self.leftBuffer = array.array('B') + for i in xrange(WEB_HALF_WIDTH*WEB_HEIGHT): + self.leftBuffer.append(0) + self.leftBuffer.append(0) + self.leftBuffer.append(0) + self.leftBuffer.append(255) + self.rightBuffer = array.array('B') + for i in xrange(WEB_HALF_WIDTH*WEB_HEIGHT): + self.rightBuffer.append(0) + self.rightBuffer.append(0) + self.rightBuffer.append(0) + self.rightBuffer.append(255) + + self.setupTexture() + if self.useHalfTexture: + self.setupHalfTextures() + + #self.interval = LerpHprInterval(self.quad, 2, Vec3(360, 0, 0), Vec3(0, 0, 0)) + + #self.accept("escape", sys.exit, [0]) + #self.accept("w", self.writeTex) + + self.accept("mouse1", self.mouseDown, [AwWebView.LEFTMOUSEBTN]) + self.accept("mouse3", self.mouseDown, [ AwWebView.RIGHTMOUSEBTN]) + self.accept("mouse1-up", self.mouseUp, [AwWebView.LEFTMOUSEBTN]) + self.accept("mouse3-up", self.mouseUp, [ AwWebView.RIGHTMOUSEBTN]) + + #self.accept("f1", self.toggleRotation) + #self.accept("f2", self.toggleTransparency) + #self.accept("f3", self.reload) + #self.accept("f4", self.zoomIn) + #self.accept("f5", self.zoomOut) + + #taskMgr.doMethodLater(1.0, self.update, 'HtmlViewUpdateTask') + # we get a problem if a mid-frame hearbeat fires of this task in conjunction with igLoop + #taskMgr.add(self.update, 'HtmlViewUpdateTask', priority = 51) + #taskMgr.add(self.update, 'HtmlViewUpdateTask') + #base.newsFrame = self + + def getInGameNewsUrl(self): + """Get the appropriate URL to use if we are in test, qa, or live.""" + # First if all else fails, we hard code the live news url + result = base.config.GetString("fallback-news-url", "http://cdn.toontown.disney.go.com/toontown/en/gamenews/") + # next check if we have an override, say they want to url to point to a file in their harddisk + override = base.config.GetString("in-game-news-url", "") + if override: + self.notify.info("got an override url, using %s for in a game news" % override) + result = override + else: + try: + launcherUrl = base.launcher.getValue("GAME_IN_GAME_NEWS_URL", "") + if launcherUrl: + result = launcherUrl + self.notify.info("got GAME_IN_GAME_NEWS_URL from launcher using %s" % result) + else: + self.notify.info("blank GAME_IN_GAME_NEWS_URL from launcher, using %s" % result) + + except: + self.notify.warning("got exception getting GAME_IN_GAME_NEWS_URL from launcher, using %s" % result) + return result + + def setupTexture(self): + cm = CardMaker('quadMaker') + cm.setColor(1.0, 1.0, 1.0, 1.0) + + aspect = base.camLens.getAspectRatio() + htmlWidth = 2.0*aspect * WEB_WIDTH_PIXELS / float(WIN_WIDTH) + htmlHeight = 2.0*float(WEB_HEIGHT_PIXELS) / float(WIN_HEIGHT) + + # the html area will be center aligned and vertically top aligned + #cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0) + cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, - htmlHeight / 2.0, htmlHeight / 2.0) + + bottomRightX = (WEB_WIDTH_PIXELS) / float( WEB_WIDTH +1) + bottomRightY = WEB_HEIGHT_PIXELS / float (WEB_HEIGHT+1) + + #cm.setUvRange(Point2(0,0), Point2(bottomRightX, bottomRightY)) + cm.setUvRange(Point2(0,1-bottomRightY), Point2(bottomRightX,1)) + + card = cm.generate() + self.quad = NodePath(card) + self.quad.reparentTo(self.parent) + + self.guiTex = Texture("guiTex") + self.guiTex.setupTexture(Texture.TT2dTexture, WEB_WIDTH, WEB_HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba) + self.guiTex.setMinfilter(Texture.FTLinear) + self.guiTex.setKeepRamImage(True) + self.guiTex.makeRamImage() + self.guiTex.setWrapU(Texture.WMRepeat) + self.guiTex.setWrapV(Texture.WMRepeat) + + ts = TextureStage('webTS') + self.quad.setTexture(ts, self.guiTex) + self.quad.setTexScale(ts, 1.0, -1.0) + + self.quad.setTransparency(0) + self.quad.setTwoSided(True) + self.quad.setColor(1.0, 1.0, 1.0, 1.0) + #self.quad.setZ(0.1) # shtickerbook is moved up by 0.1 + + self.calcMouseLimits() + + def setupHalfTextures(self): + self.setupLeftTexture() + self.setupRightTexture() + self.fullPnmImage = PNMImage(WEB_WIDTH, WEB_HEIGHT, 4) + self.leftPnmImage = PNMImage(WEB_HALF_WIDTH, WEB_HEIGHT, 4) + self.rightPnmImage = PNMImage(WEB_HALF_WIDTH, WEB_HEIGHT, 4) + + def setupLeftTexture(self): + cm = CardMaker('quadMaker') + cm.setColor(1.0, 1.0, 1.0, 1.0) + + aspect = base.camLens.getAspectRatio() + htmlWidth = 2.0*aspect * WEB_WIDTH / float(WIN_WIDTH) + htmlHeight = 2.0*float(WEB_HEIGHT) / float(WIN_HEIGHT) + + # the html area will be center aligned and vertically top aligned + #cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0) + cm.setFrame(-htmlWidth/2.0, 0, - htmlHeight / 2.0, htmlHeight / 2.0) + card = cm.generate() + self.leftQuad = NodePath(card) + self.leftQuad.reparentTo(self.parent) + + self.leftGuiTex = Texture("guiTex") + self.leftGuiTex.setupTexture(Texture.TT2dTexture, WEB_HALF_WIDTH, WEB_HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba) + self.leftGuiTex.setKeepRamImage(True) + self.leftGuiTex.makeRamImage() + self.leftGuiTex.setWrapU(Texture.WMClamp) + self.leftGuiTex.setWrapV(Texture.WMClamp) + + ts = TextureStage('leftWebTS') + self.leftQuad.setTexture(ts, self.leftGuiTex) + self.leftQuad.setTexScale(ts, 1.0, -1.0) + + self.leftQuad.setTransparency(0) + self.leftQuad.setTwoSided(True) + self.leftQuad.setColor(1.0, 1.0, 1.0, 1.0) + #self.quad.setZ(0.1) # shtickerbook is moved up by 0.1 + + def setupRightTexture(self): + cm = CardMaker('quadMaker') + cm.setColor(1.0, 1.0, 1.0, 1.0) + + aspect = base.camLens.getAspectRatio() + htmlWidth = 2.0*aspect * WEB_WIDTH / float(WIN_WIDTH) + htmlHeight = 2.0*float(WEB_HEIGHT) / float(WIN_HEIGHT) + + # the html area will be center aligned and vertically top aligned + #cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0) + cm.setFrame(0, htmlWidth/2.0, - htmlHeight / 2.0, htmlHeight / 2.0) + card = cm.generate() + self.rightQuad = NodePath(card) + self.rightQuad.reparentTo(self.parent) + + self.rightGuiTex = Texture("guiTex") + self.rightGuiTex.setupTexture(Texture.TT2dTexture, WEB_HALF_WIDTH, WEB_HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba) + self.rightGuiTex.setKeepRamImage(True) + self.rightGuiTex.makeRamImage() + self.rightGuiTex.setWrapU(Texture.WMClamp) + self.rightGuiTex.setWrapV(Texture.WMClamp) + + ts = TextureStage('rightWebTS') + self.rightQuad.setTexture(ts, self.rightGuiTex) + self.rightQuad.setTexScale(ts, 1.0, -1.0) + + self.rightQuad.setTransparency(0) + self.rightQuad.setTwoSided(True) + self.rightQuad.setColor(1.0, 1.0, 1.0, 1.0) + #self.quad.setZ(0.1) # shtickerbook is moved up by 0.1 + + + def calcMouseLimits(self): + ll = Point3() + ur = Point3() + self.quad.calcTightBounds(ll,ur) + self.notify.debug("ll=%s ur=%s" % (ll,ur)) + + # we need to get our relative position to aspect2d, since shtiker books is shifted + offset = self.quad.getPos(aspect2d) + self.notify.debug("offset = %s " % offset) + ll.setZ(ll.getZ() + offset.getZ()) + ur.setZ(ur.getZ() + offset.getZ()) + + self.notify.debug("new LL=%s, UR=%s" % (ll,ur)) + + relPointll = self.quad.getRelativePoint(aspect2d, ll) + self.notify.debug("relPoint = %s" % relPointll) + self.mouseLL = (aspect2d.getScale()[0] * ll[0], aspect2d.getScale()[2] * ll[2]) + self.mouseUR = (aspect2d.getScale()[0] * ur[0], aspect2d.getScale()[2] * ur[2]) + self.notify.debug("original mouseLL=%s, mouseUR=%s" % (self.mouseLL, self.mouseUR)) + + def writeTex(self, filename = "guiText.png"): + self.notify.debug("writing texture") + self.guiTex.generateRamMipmapImages() + self.guiTex.write(filename) + + def toggleRotation(self): + if self.interval.isPlaying(): + self.interval.finish() + else: + self.interval.loop() + + def mouseDown(self, button): + messenger.send('wakeup') + self.webView.injectMouseDown(button) + + def mouseUp(self, button): + self.webView.injectMouseUp(button) + + def reload(self): + pass + #self.webView.loadFile(self.htmlFile, '') + + def zoomIn(self): + self.webView.zoomIn() + + def zoomOut(self): + self.webView.zoomOut() + + def toggleTransparency(self): + self.transparency = not self.transparency + self.webView.setTransparent(self.transparency) + + def update(self, task): + global GlobalWebcore + if base.mouseWatcherNode.hasMouse(): + x, y = self._translateRelativeCoordinates(base.mouseWatcherNode.getMouseX(), base.mouseWatcherNode.getMouseY()) + #self.notify.debug('got mouse move %d %d' % (x,y)) + #self.webView.injectMouseMove(x, y) + + if (self.mx - x) != 0 or (self.my - y) != 0: + self.webView.injectMouseMove(x, y) + #self.notify.debug('injecting mouse move %d %d' % (x,y)) + self.mx, self.my = x, y + + if self.webView.isDirty(): + #self.notify.debug("webview is dirty") + self.webView.render(self.imgBuffer.buffer_info()[0], WEB_WIDTH*4, 4) + #Texture.setTexturesPower2(AutoTextureScale.ATSUp) + Texture.setTexturesPower2(2) + #self.notify.debug("about to modify ram image") + textureBuffer = self.guiTex.modifyRamImage() + #import pdb; pdb.set_trace() + #self.notify.debug("about to call textureBuffer.setData") + textureBuffer.setData(self.imgBuffer.tostring()) + #self.notify.debug("done calling setData") + if self.useHalfTexture: + # TODO check with DRose, this feels inefficient + self.guiTex.store(self.fullPnmImage) + self.leftPnmImage.copySubImage(self.fullPnmImage, 0, 0, 0, 0, WEB_HALF_WIDTH, WEB_HEIGHT) + self.rightPnmImage.copySubImage(self.fullPnmImage, 0, 0, WEB_HALF_WIDTH, 0, WEB_HALF_WIDTH, WEB_HEIGHT) + self.leftGuiTex.load(self.leftPnmImage) + self.rightGuiTex.load(self.rightPnmImage) + self.quad.hide() + #Texture.setTexturesPower2(AutoTextureScale.ATSDown) + Texture.setTexturesPower2(1) + + + GlobalWebcore.update() + return Task.cont + + + def _translateRelativeCoordinates(self, x, y): + sx = int((x - self.mouseLL[0]) / (self.mouseUR[0] - self.mouseLL[0]) * WEB_WIDTH_PIXELS) + sy = WEB_HEIGHT_PIXELS - int((y - self.mouseLL[1]) / (self.mouseUR[1] - self.mouseLL[1]) * WEB_HEIGHT_PIXELS) + return sx, sy + + def unload(self): + """Clean up everything, especially the awesomium bits.""" + self.ignoreAll() + self.webView.destroy() + self.webView = None + #global GlobalWebcore + #GlobalWebcore = None + pass + + + # --------------------[ WebViewListener implementation ]-------------------------- + def onCallback(self, name, args): + assert self.notify.debugStateCall(self) + if name == "requestFPS": + #self.webView.setProperty( "fps", JSValue("%.1f" % (1.0 / globalClock.getDt())) ) + #self.webView.executeJavascript("updateFPS()", "") + pass + + def onBeginNavigation(self, url, frameName): + assert self.notify.debugStateCall(self) + pass + + def onBeginLoading(self, url, frameName, statusCode, mimeType): + assert self.notify.debugStateCall(self) + pass + + def onFinishLoading(self): + assert self.notify.debugStateCall(self) + self.notify.debug( "finished loading") + pass + + def onReceiveTitle(self, title, frameName): + assert self.notify.debugStateCall(self) + pass + + def onChangeTooltip(self, tooltip): + assert self.notify.debugStateCall(self) + pass + + def onChangeCursor(self, cursor): + assert self.notify.debugStateCall(self) + pass + + def onChangeKeyboardFocus(self, isFocused): + assert self.notify.debugStateCall(self) + pass + + def onChangeTargetURL(self, url): + assert self.notify.debugStateCall(self) + pass + diff --git a/toontown/src/shtiker/InGameNewsFrame.py b/toontown/src/shtiker/InGameNewsFrame.py new file mode 100644 index 0000000..76322ad --- /dev/null +++ b/toontown/src/shtiker/InGameNewsFrame.py @@ -0,0 +1,44 @@ +import datetime +from toontown.shtiker import HtmlView + +class InGameNewsFrame(HtmlView.HtmlView): + + TaskName = 'HtmlViewUpdateTask' + + def __init__(self, parent = aspect2d): + HtmlView.HtmlView.__init__(self, parent) + self.initialLoadDone = False + self.accept("newsSnapshot", self.doSnapshot) + + def activate(self): + self.quad.show() + # our first run through on contructor gives wrong results if coming from shticker book + self.calcMouseLimits() + if not self.initialLoadDone: + inGameNewsUrl = self.getInGameNewsUrl() + #import pdb; pdb.set_trace() + self.webView.loadURL2(inGameNewsUrl) + self.initialLoadDone = True + taskMgr.add(self.update, self.TaskName) + + def deactivate(self): + self.quad.hide() + taskMgr.remove(self.TaskName) + + def unload(self): + self.deactivate() + HtmlView.HtmlView.unload(self) + self.ignore("newsSnapshot") + + def doSnapshot(self): + "Save the current browser contents to a png file.""" + curtime = datetime.datetime.now() + filename = "news_snapshot_" + curtime.isoformat() + filename = filename.replace(":", "-") + filename = filename.replace(".", "-") + pngfilename =filename + ".png" + self.writeTex(pngfilename) + jpgfilename = filename + ".jpg" + self.writeTex(jpgfilename) + return jpgfilename + diff --git a/toontown/src/shtiker/InventoryPage.py b/toontown/src/shtiker/InventoryPage.py new file mode 100644 index 0000000..d5a7900 --- /dev/null +++ b/toontown/src/shtiker/InventoryPage.py @@ -0,0 +1,241 @@ +"""InventoryPage module: contains the InventoryPage class""" + +import ShtikerPage +from toontown.toonbase import ToontownBattleGlobals +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer + +class InventoryPage(ShtikerPage.ShtikerPage): + """InventoryPage class""" + + # special methods + def __init__(self): + """__init__(self) + InventoryPage constructor: create a shtiker book page for the inventory + """ + ShtikerPage.ShtikerPage.__init__(self) + self.currentTrackInfo = None + self.onscreen = 0 + self.lastInventoryTime = globalClock.getRealTime() + + def load(self): + ShtikerPage.ShtikerPage.load(self) + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.InventoryPageTitle, + text_scale = 0.12, + textMayChange = 1, + pos = (0,0,0.62), + ) + + self.gagFrame = DirectFrame( + parent = self, + relief = None, + pos = (0.1, 0, -0.47), + scale = (0.35, 0.35, 0.35), + geom = DGG.getDefaultDialogGeom(), + geom_color = ToontownGlobals.GlobalDialogColor, + ) + + self.trackInfo = DirectFrame( + parent = self, + relief = None, + pos = (-0.4, 0, -0.47), + scale = (0.35,0.35,0.35), + geom = DGG.getDefaultDialogGeom(), + geom_scale = (1.4,1,1), + geom_color = ToontownGlobals.GlobalDialogColor, + text = "", + text_wordwrap = 11, + text_align = TextNode.ALeft, + text_scale = 0.12, + text_pos = (-0.65, 0.3), + text_fg = (0.05, 0.14, 0.4, 1), + ) + + self.trackProgress = DirectWaitBar( + parent = self.trackInfo, + pos = (0,0,-0.2), + relief = DGG.SUNKEN, + frameSize = (-0.6,0.6,-0.1,0.1), + borderWidth = (0.025,0.025), + scale = 1.1, + frameColor = (0.4,0.6,0.4,1), + barColor = (0.9,1,0.7,1), + text = "0/0", + text_scale = 0.15, + text_fg = (0.05, 0.14, 0.4, 1), + text_align = TextNode.ACenter, + text_pos = (0,-0.22), + ) + self.trackProgress.hide() + + jarGui = loader.loadModel("phase_3.5/models/gui/jar_gui") + self.moneyDisplay = DirectLabel( + parent = self, + relief = None, + pos = (0.55,0,-0.5), + scale = 0.8, + text = str(base.localAvatar.getMoney()), + text_scale = 0.18, + text_fg = (0.95, 0.95, 0, 1), + text_shadow = (0, 0, 0, 1), + text_pos = (0, -0.1, 0), + image = jarGui.find("**/Jar"), + text_font = ToontownGlobals.getSignFont(), + ) + jarGui.removeNode() + + def unload(self): + del self.title + ShtikerPage.ShtikerPage.unload(self) + + def __moneyChange(self, money): + self.moneyDisplay["text"] = str(money) + + def enter(self): + """enter(self) + """ + ShtikerPage.ShtikerPage.enter(self) + # Go into book mode + base.localAvatar.inventory.setActivateMode('book') + base.localAvatar.inventory.show() + base.localAvatar.inventory.reparentTo(self) + self.moneyDisplay["text"] = str(base.localAvatar.getMoney()) + # hang delete mode hooks + self.accept("enterBookDelete", self.enterDeleteMode) + self.accept("exitBookDelete", self.exitDeleteMode) + self.accept("enterTrackFrame", self.updateTrackInfo) + self.accept("exitTrackFrame", self.clearTrackInfo) + self.accept(localAvatar.uniqueName("moneyChange"), self.__moneyChange) + + def exit(self): + """exit(self) + """ + ShtikerPage.ShtikerPage.exit(self) + self.clearTrackInfo(self.currentTrackInfo) + self.ignore("enterBookDelete") + self.ignore("exitBookDelete") + self.ignore("enterTrackFrame") + self.ignore("exitTrackFrame") + self.ignore(localAvatar.uniqueName("moneyChange")) + self.makePageWhite(None) + base.localAvatar.inventory.hide() + base.localAvatar.inventory.reparentTo(hidden) + self.exitDeleteMode() + + def enterDeleteMode(self): + self.title['text'] = TTLocalizer.InventoryPageDeleteTitle + self.title['text_fg'] = (0,0,0,1) + self.book['image_color'] = Vec4(1,1,0,1) + + def exitDeleteMode(self): + self.title['text'] = TTLocalizer.InventoryPageTitle + self.title['text_fg'] = (0,0,0,1) + self.book['image_color'] = Vec4(1,1,1,1) + + def updateTrackInfo(self, trackIndex): + self.currentTrackInfo = trackIndex + trackName = TextEncoder.upper(ToontownBattleGlobals.Tracks[trackIndex]) + if base.localAvatar.hasTrackAccess(trackIndex): + curExp, nextExp = base.localAvatar.inventory.getCurAndNextExpValues(trackIndex) + trackText = ("%s / %s" % (curExp, nextExp)) + self.trackProgress['range'] = nextExp + self.trackProgress['value'] = curExp + if curExp >= ToontownBattleGlobals.regMaxSkill: + str = (TTLocalizer.InventoryPageTrackFull % (trackName)) + trackText = (TTLocalizer.InventoryUberTrackExp % + {"nextExp": ToontownBattleGlobals.MaxSkill - curExp,}) + self.trackProgress["range"] = ToontownBattleGlobals.UberSkill + uberCurrExp = curExp - ToontownBattleGlobals.regMaxSkill + self.trackProgress["value"] = uberCurrExp + + else: + morePoints = nextExp - curExp + if morePoints == 1: + str = (TTLocalizer.InventoryPageSinglePoint % + {"trackName": trackName, + "numPoints": morePoints, }) + else: + # Plural + str = (TTLocalizer.InventoryPagePluralPoints % + {"trackName": trackName, + "numPoints": morePoints, }) + + self.trackInfo['text'] = str + #self.trackProgress['range'] = nextExp + #self.trackProgress['value'] = curExp + self.trackProgress['text'] = trackText + self.trackProgress['frameColor'] = (ToontownBattleGlobals.TrackColors[trackIndex][0]*0.6, + ToontownBattleGlobals.TrackColors[trackIndex][1]*0.6, + ToontownBattleGlobals.TrackColors[trackIndex][2]*0.6, + 1) + self.trackProgress['barColor'] = (ToontownBattleGlobals.TrackColors[trackIndex][0], + ToontownBattleGlobals.TrackColors[trackIndex][1], + ToontownBattleGlobals.TrackColors[trackIndex][2], + 1) + self.trackProgress.show() + else: + str = (TTLocalizer.InventoryPageNoAccess % + (trackName)) + self.trackInfo['text'] = str + self.trackProgress.hide() + + + def clearTrackInfo(self, trackIndex): + # Since our tracks might overlap a tiny bit (one pixel), we + # have to allow for the possibility that we got a "within" + # event for some other track before we got a "without" event + # for the current track. Thus, we only clear out the current + # track info if it's still showing the track we think it is. + if self.currentTrackInfo == trackIndex: + self.trackInfo['text'] = "" + self.trackProgress.hide() + self.currentTrackInfo = None + return + + # for the hotkey that allows quick display of the inventory page + def acceptOnscreenHooks(self): + self.accept(ToontownGlobals.InventoryHotkeyOn, self.showInventoryOnscreen) + self.accept(ToontownGlobals.InventoryHotkeyOff, self.hideInventoryOnscreen) + + def ignoreOnscreenHooks(self): + self.ignore(ToontownGlobals.InventoryHotkeyOn) + self.ignore(ToontownGlobals.InventoryHotkeyOff) + + def showInventoryOnscreen(self): + messenger.send('wakeup') + timedif = globalClock.getRealTime() - self.lastInventoryTime + if timedif < 0.7: + return + self.lastInventoryTime = globalClock.getRealTime() + if self.onscreen or base.localAvatar.questPage.onscreen: + return + self.onscreen = 1 + base.localAvatar.inventory.setActivateMode('book') + base.localAvatar.inventory.show() + base.localAvatar.inventory.reparentTo(self) + self.moneyDisplay["text"] = str(base.localAvatar.getMoney()) + self.accept("enterTrackFrame", self.updateTrackInfo) + self.accept("exitTrackFrame", self.clearTrackInfo) + self.accept(localAvatar.uniqueName("moneyChange"), self.__moneyChange) + self.reparentTo(aspect2d) + self.title.hide() + self.show() + + def hideInventoryOnscreen(self): + if not self.onscreen: + return + self.onscreen = 0 + self.ignore("enterTrackFrame") + self.ignore("exitTrackFrame") + self.ignore(localAvatar.uniqueName("moneyChange")) + base.localAvatar.inventory.hide() + base.localAvatar.inventory.reparentTo(hidden) + self.reparentTo(self.book) + self.title.show() + self.hide() + diff --git a/toontown/src/shtiker/IssueFrame.py b/toontown/src/shtiker/IssueFrame.py new file mode 100644 index 0000000..de49ad4 --- /dev/null +++ b/toontown/src/shtiker/IssueFrame.py @@ -0,0 +1,550 @@ +import os +from pandac.PandaModules import VirtualFileSystem, Filename, DSearchPath +from pandac.PandaModules import Texture, CardMaker, PNMImage, TextureStage +from pandac.PandaModules import NodePath +from pandac.PandaModules import Point2 +from direct.showbase import DirectObject +from direct.gui.DirectGui import DirectFrame , DirectButton, DGG, DirectLabel +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals + +WEB_WIDTH_PIXELS = 784 +WEB_HEIGHT_PIXELS = 451 +WEB_WIDTH = 1024 +WEB_HEIGHT = 512 +WEB_HALF_WIDTH = WEB_WIDTH / 2 + +WIN_WIDTH = 800 +WIN_HEIGHT = 600 + +class IssueFrame(DirectFrame): + + NewsBaseDir = config.GetString("news-base-dir", "/httpNews") + # taken from In Game NewsFrame + FrameDimensions = (-1.30666637421, 1.30666637421, -0.751666665077, 0.751666665077) + notify = DirectNotifyGlobal.directNotify.newCategory("IssueFrame") + # datestr, section, subsection # + ContentPattern = "tt_i_art_%s_%s%d.jpg" + + # home page is considered one section, must always be first + # home, news, events, talk of the town, ask toontown, toon resistance + SectionIdents = ['hom', 'new', 'evt', 'tot', 'att', 'tnr'] + + def __init__(self, parent, newsDir, dateStr, myIssueIndex, numIssues, strFilenames): + DirectFrame.__init__(self, + frameColor = (1,1,1,0), + frameSize = self.FrameDimensions, + relief = DGG.FLAT, + parent = parent, + ) + self.hide() + self.accept("newsSnapshot", self.doSnapshot) + self.parent = parent + self.newsDir = newsDir + self.dateStr = dateStr + self.myIssueIndex = myIssueIndex + self.numIssues = numIssues + self.strFilenames = strFilenames # this replaces scanDirectory when over http + self.sectionList = [] + self.sectionFrames= {} + self.flatSubsectionList = [] + self.parseNewsContent() + self.load() + self.curSection = 0 + self.curSubsection = 0 + + def parseNewsContent(self): + """Open up the directory, read all the files, and figure out the structure.""" + for section,ident in enumerate(self.SectionIdents): + subSectionList = [] + curSubSection = 0 + endSearch = False; + while not endSearch: + justName = self.ContentPattern % (self.dateStr, ident, curSubSection +1) + fullName = Filename(self.newsDir + '/' + justName) + if self.strFilenames: + # we already have a list of filenames, check if it's in there + if justName in self.strFilenames: + subSectionList.append(fullName) + self.flatSubsectionList.append((section, curSubSection)) + curSubSection +=1 + else: + endSearch = True + else: + theFile = vfs.getFile(Filename(fullName), status_only=1) + if theFile: + subSectionList.append(fullName) + self.flatSubsectionList.append((section, curSubSection)) + curSubSection +=1 + else: + endSearch = True + if not subSectionList: + # we have one of the required files missing, + # put a string inside to indicate its gone wrong + self.notify.warning("Could not load %s" % fullName) + subSectionList.append("error_" + str(fullName)) + self.flatSubsectionList.append((section,0)) + self.sectionList.append(subSectionList) + + self.notify.debug("%s" % self.sectionList) + + def getPreviousTarget(self, section, subSection): + """When pressing the left arrow key, where do we go to, may return None.""" + result = None + if (section,subSection) in self.flatSubsectionList: + index = self.flatSubsectionList.index((section,subSection)) + if index > 0: + result = self.flatSubsectionList[index-1] + return result + + def getNextTarget(self, section, subSection): + """When pressing the right arrow key, where do we go to, may return None.""" + result = None + if (section,subSection) in self.flatSubsectionList: + index = self.flatSubsectionList.index((section,subSection)) + if index < len(self.flatSubsectionList) -1: + result = self.flatSubsectionList[index+1] + return result + + def load(self): + """Create the gui objects we need.""" + self.gui = loader.loadModel("phase_3.5/models/gui/tt_m_gui_ign_directNewsGui") + self.guiNav = loader.loadModel("phase_3.5/models/gui/tt_m_gui_ign_directNewsGuiNav") + numPagesLoaded = 0 + totalNumberOfPages = len(self.flatSubsectionList) + assert self.notify.debug("before loop, numPagesLoaded=%d total=%d"% (numPagesLoaded,totalNumberOfPages)) + for section, subSectionList in enumerate(self.sectionList): + self.notify.debug("loading section %d" % section) + self.sectionFrames[section] ={} + for subsection, fullFilename in enumerate(subSectionList): + self.notify.debug("loading subsection %d" % subsection) + newPage = self.createPage(section,subsection, fullFilename) + numPagesLoaded += 1 + self.sectionFrames[section][subsection] = newPage + + #self.loadBackground() + #self.loadMainPage() + + def createPage(self, section, subsection, fullFilename): + """Create on page, with all the appropriate buttons.""" + assert self.notify.debugStateCall(self) + upsellBackground = loader.loadModel("phase_3.5/models/gui/tt_m_gui_ign_newsStatusBackground") + imageScaleX = self.FrameDimensions[1] - self.FrameDimensions[0] + imageScaleY = self.FrameDimensions[3] - self.FrameDimensions[2] + pageFrame = DirectFrame( + frameColor = (1,1,1,0), + frameSize = self.FrameDimensions, + image = upsellBackground, + image_scale = (imageScaleX, 1, imageScaleY), + relief = DGG.FLAT, + parent = self, + text = "", + text_scale = 0.06, + text_pos = (0, -0.4), + ) + if 'error_' in str(fullFilename): + pageFrame["text"] = TTLocalizer.NewsPageErrorDownloadingFileCanStillRead % fullFilename[len('error_'):] + else: + quad = self.loadFlatQuad(fullFilename) + if quad: + quad.reparentTo(pageFrame) + else: + pageFrame["text"] = TTLocalizer.NewsPageErrorDownloadingFileCanStillRead % fullFilename + self.loadRightArrow(section, subsection, pageFrame) + self.loadLeftArrow(section, subsection, pageFrame) + if section == 0 and subsection == 0: + self.loadHomePageButtons(section, subsection, pageFrame) + else: + self.loadNavButtons(pageFrame) + pageFrame.hide() + return pageFrame + + def loadRightArrow(self,section, subsection, pageFrame): + """Load the right arrow button, if needed.""" + nextTarget = self.getNextTarget(section, subsection) + + position = (1.16,0,-0.69) + # again numbers from direct screen captrues + xSize = 48 + desiredXSize = 22 + imageScale = float(desiredXSize) / xSize + if nextTarget: + image = self.gui.find('**/tt_i_art_btn_ArrowRight') + rollover = self.gui.find('**/tt_i_art_btn_ArrowRightRo') + rightArrow = DirectButton( + relief = None, + parent = pageFrame, + command = self.gotoPage, + extraArgs = (nextTarget[0], nextTarget[1]), + image = (image,image,rollover,image), + pos = position, + image_scale = imageScale, + ) + pass + + def loadLeftArrow(self,section, subsection, pageFrame): + """Load the right arrow button, if needed.""" + prevTarget = self.getPreviousTarget(section, subsection) + position = (-1.16,0,-0.69) + # again numbers from direct screen captrues + xSize = 48 + desiredXSize = 22 + imageScale = float(desiredXSize) / xSize + if prevTarget: + image = self.gui.find('**/tt_i_art_btn_ArrowLeft') + rollover = self.gui.find('**/tt_i_art_btn_ArrowLeftRo') + rightArrow = DirectButton( + relief = None, + parent = pageFrame, + command = self.gotoPage, + extraArgs = (prevTarget[0], prevTarget[1]), + image = (image,image,rollover,image), + pos = position, + image_scale = imageScale, + ) + pass + + def loadHomePageButtons(self,section, subsection, pageFrame): + """Load the navigation buttons for the main page.""" + buttonNames = ['', + 'tt_i_art_btn_HomNew', + 'tt_i_art_btn_HomEvt', + 'tt_i_art_btn_HomTot', + 'tt_i_art_btn_HomAsk', + 'tt_i_art_btn_HomTnr' + ] + rolloverButtonNames = [] + for name in buttonNames: + ro = name + "Ro" + rolloverButtonNames.append(ro) + + positions = [ (0,0.0), + (-1.05333,0,0.29333), + (-1.05333,0,0.0666667 ), + (-1.05333,0,-0.156667), + (-1.05333,0,-0.383333), + (-1.05333,0,-0.606667), + ] + + # values captured using direct screen captures + xSize = 136 + desiredXSize = 69 + image_scale = float(desiredXSize) / xSize + image_scale *= float(69)/70 + + self.sectionBtns = [] + for section in xrange(1, len(self.SectionIdents)): + image = self.gui.find('**/%s' % buttonNames[section]) + rolloverImage = self.gui.find('**/%s' % rolloverButtonNames[section]) + if image.isEmpty(): + self.notify.error('cant find %s' % buttonNames[section]) + sectionBtn = DirectButton( + relief = None, + parent = pageFrame, + image = (image , image, rolloverImage, image), + image_scale = image_scale, + command = self.gotoPage, + extraArgs = (section, 0), + enableEdit = 1, + pos = positions[section], + ) + + readMorePos = (0.906666, 0, -0.19) + readImage = self.gui.find('**/tt_i_art_btn_ReadMore') + readRollover = self.gui.find('**/tt_i_art_btn_ReadMoreRo') + xSize = 228.0 + desiredXSize = 113.0 + imageScale = desiredXSize / xSize + readMoreBtn = DirectButton( + relief = None, + parent = pageFrame, + image = (readImage , readImage, readRollover, readImage), + image_scale = imageScale, + command = self.gotoPage, + extraArgs = (1, 0), + enableEdit = 1, + pos = readMorePos, + ) + self.loadWeekNavButtons(pageFrame) + + def loadWeekNavButtons(self, pageFrame): + """Load the buttons to go back and forth through previous weeks.""" + # check if we even need this at all + if self.numIssues <= 1: + return + if self.myIssueIndex == self.numIssues - 1: + weekStr = TTLocalizer.IssueFrameThisWeek + elif self.myIssueIndex == self.numIssues -2 : + weekStr = TTLocalizer.IssueFrameLastWeek + else: + weeksAgo = self.numIssues - self.myIssueIndex -1 + weekStr = TTLocalizer.IssueFrameWeeksAgo % weeksAgo + + prevImage = self.gui.find("**/tt_i_art_btn_ArchiveArrwLeftNormal") + prevImageRo = self.gui.find("**/tt_i_art_btn_ArchiveArrwLeftRo") + prevImageDisabled = self.gui.find("**/tt_i_art_btn_ArchiveArrwLeftDisabled") + actualY1 =78.0 + desiredY1 = 42.0 + y1Scale = desiredY1 / actualY1 + prevWeekBtn = DirectButton( + relief = None, + parent = pageFrame, + image = [prevImage, prevImage, prevImageRo, prevImageDisabled ], + image_scale = y1Scale, + command = self.changeWeek, + extraArgs = (self.myIssueIndex - 1,), + pos = (0.806666, 0, 0.62), + #scale = 0.05 + ) + if self.myIssueIndex == 0: + prevWeekBtn['state'] = DGG.DISABLED + + nextImage = self.gui.find("**/tt_i_art_btn_ArchiveArrwRightNormal") + nextImageRo = self.gui.find("**/tt_i_art_btn_ArchiveArrwRightRo") + nextImageDisabled = self.gui.find("**/tt_i_art_btn_ArchiveArrwRightDisabled") + actualY2Scale = 63.0 + desiredY2Scale = 34.0 + y2Scale = desiredY2Scale / actualY2Scale + nextWeekBtn = DirectButton( + relief = None, + parent = pageFrame, + image = [nextImage, nextImage, nextImageRo, nextImageDisabled], + image_scale = y2Scale, + command = self.changeWeek, + extraArgs = (self.myIssueIndex +1,), + pos = (1.16, 0, 0.623333), + #scale = 0.05 + ) + if self.myIssueIndex == self.numIssues - 1: + nextWeekBtn['state'] = DGG.DISABLED + + actualX = 176.0 + desiredX = 89.0 + imageScale = desiredX / actualX + midImage =self.gui.find('**/tt_i_art_btn_ArchiveMiddle') + weekColor = (0.0 /255.0 , 23.0 / 255.0, 140.0/ 255.0, 1.0) + weekLabel = DirectLabel( + relief = None, + image = midImage, + image_scale = imageScale, + parent = pageFrame, + text = weekStr, + text_font = ToontownGlobals.InterfaceFont, + text_fg = weekColor, + text_scale = 0.043, + text_pos = (0,-0.01, 0), + pos = ( 0.983333, 0, 0.62), + ) + + + + def loadNavButtons(self, pageFrame): + """Load the navigation buttons for the main page.""" + buttonNames = ['tt_i_art_btn_NavHom', + 'tt_i_art_btn_NavNew', + 'tt_i_art_btn_NavEvt', + 'tt_i_art_btn_NavTot', + 'tt_i_art_btn_NavAtt', + 'tt_i_art_btn_NavTnr' + ] + rolloverButtonNames = [] + for name in buttonNames: + ro = name + "Ro" + rolloverButtonNames.append(ro) + + xPos = 1.24667 + positions = [ (xPos,0,0.623333), + (xPos,0,0.536663), + (xPos,0,0.45 ), + (xPos,0,0.36333), + (xPos,0,0.276667), + (xPos,0,0.19), + ] + + # values captured using direct screen captures + xSize1 = 177 + desiredXSize1 = 90 + image_scale1 = float(desiredXSize1) / xSize1 + image_scale = 1 + + xSize2 = 300 + desiredXSize2 =152 + image_scale2 = float(desiredXSize2) / xSize2 + image_scale2 *= 30.0 / 30.0 + + rolloverPositions = [ (1.15, 0, 0.623333), + (1.15, 0, 0.533333), + (1.15, 0, 0.443333), + (1.045, 0, 0.353333), + (1.045, 0, 0.263334), + (1.045, 0, 0.173333), + ] + + imageScales = [ image_scale1, + image_scale1, + image_scale1, + image_scale2, + image_scale2, + image_scale2 + ] + + frameSizeAdj1 = 0.1 + frameSize1 = (-0.04 + frameSizeAdj1, + 0.04 + frameSizeAdj1, + -0.04, + 0.04) + frameSizeAdj2 = 0.21 + frameSize2 = (-0.04 + frameSizeAdj2, + 0.04 + frameSizeAdj2, + -0.04, + 0.04) + + frameSizes = ( frameSize1, frameSize1, frameSize1, + frameSize2, frameSize2, frameSize2) + # TODO get from Teani normal state buttons same size as rollover + self.sectionBtns = [] + for section in xrange(0, len(self.SectionIdents)): + image = self.guiNav.find('**/%s' % buttonNames[section]) + rolloverImage = self.guiNav.find('**/%s' % rolloverButtonNames[section]) + if image.isEmpty(): + self.notify.error('cant find %s' % buttonNames[section]) + sectionBtn = DirectButton( + relief = None, + parent = pageFrame, + frameSize = frameSizes[section], + image = (image , rolloverImage, rolloverImage, image), + image_scale = imageScales[section], + command = self.gotoPage, + extraArgs = (section, 0), + enableEdit = 1, + pos = rolloverPositions[section] + ) + #import pdb; pdb.set_trace() + + + def gotoPage(self, section, subsection): + """Display the sectionFrame that corresponds to that page.""" + self.sectionFrames[self.curSection][self.curSubsection].hide() + self.sectionFrames[section][subsection].show() + self.curSection = section + self.curSubsection = subsection + messenger.send('wakeup') + base.cr.centralLogger.writeClientEvent("news gotoPage %s %s %s" % (self.dateStr, section, subsection)) + + def loadFlatQuad(self, fullFilename): + """Load the flat jpg into a quad.""" + assert self.notify.debugStateCall(self) + #Texture.setTexturesPower2(AutoTextureScale.ATSUp) + #Texture.setTexturesPower2(2) + + cm = CardMaker('cm-%s'%fullFilename) + cm.setColor(1.0, 1.0, 1.0, 1.0) + aspect = base.camLens.getAspectRatio() + htmlWidth = 2.0*aspect * WEB_WIDTH_PIXELS / float(WIN_WIDTH) + htmlHeight = 2.0*float(WEB_HEIGHT_PIXELS) / float(WIN_HEIGHT) + + # the html area will be center aligned and vertically top aligned + #cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0) + cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, - htmlHeight / 2.0, htmlHeight / 2.0) + + bottomRightX = (WEB_WIDTH_PIXELS) / float( WEB_WIDTH +1) + bottomRightY = WEB_HEIGHT_PIXELS / float (WEB_HEIGHT+1) + + #cm.setUvRange(Point2(0,0), Point2(bottomRightX, bottomRightY)) + cm.setUvRange(Point2(0,1-bottomRightY), Point2(bottomRightX,1)) + + card = cm.generate() + quad = NodePath(card) + #quad.reparentTo(self.parent) + + jpgFile = PNMImage(WEB_WIDTH, WEB_HEIGHT) + smallerJpgFile = PNMImage() + readFile = smallerJpgFile.read(Filename(fullFilename)) + if readFile: + jpgFile.copySubImage(smallerJpgFile, 0, 0) + + guiTex = Texture("guiTex") + guiTex.setupTexture(Texture.TT2dTexture, WEB_WIDTH, WEB_HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba) + guiTex.setMinfilter(Texture.FTLinear) + guiTex.load(jpgFile) + #guiTex.setKeepRamImage(True) + #guiTex.makeRamImage() + guiTex.setWrapU(Texture.WMClamp) + guiTex.setWrapV(Texture.WMClamp) + + ts = TextureStage('webTS') + quad.setTexture(ts, guiTex) + #quad.setTexScale(ts, 1.0, -1.0) + + quad.setTransparency(0) + quad.setTwoSided(True) + quad.setColor(1.0, 1.0, 1.0, 1.0) + result= quad + else: + # if we have an error loading the file, return None to signify an error + result = None + + #Texture.setTexturesPower2(AutoTextureScale.ATSDown) + Texture.setTexturesPower2(1) + return result + + def loadBackground(self): + """Create a plain white background image, that covers over the shtickerbook""" + + # HtmlView: webFrame = -1.30666637421 1.30666637421 -0.751666665077 0.751666665077 + self.backFrame = DirectFrame( + parent = self, + frameColor = (1,1,1,1), + frameSize = self.FrameDimensions, + pos = (0,0,0), + relief = None + ) + + def loadMainPage(self): + """Create the other gui for this.""" + self.mainFrame = DirectFrame( + parent = self, + frameSize = self.FrameDimensions, + frameColor = (1,0,0,1), + ) + #self.newsButton = DirectButton( + # parent = self.mainFrame, + # image = + # parent + + + def activate(self): + """ + self.quad.show() + # our first run through on contructor gives wrong results if coming from shticker book + self.calcMouseLimits() + if not self.initialLoadDone: + inGameNewsUrl = self.getInGameNewsUrl() + #import pdb; pdb.set_trace() + self.webView.loadURL2(inGameNewsUrl) + self.initialLoadDone = True + taskMgr.add(self.update, self.TaskName) + """ + + def deactivate(self): + """ + self.quad.hide() + taskMgr.remove(self.TaskName) + """ + + def unload(self): + """ + self.deactivate() + HtmlView.HtmlView.unload(self) + """ + self.ignore("newsSnapshot") + + def doSnapshot(self): + "Save the current browser contents to a png file.""" + pass + + def changeWeek(self, newIssueWeek): + """Handle player pressing next or prev week buttons.""" + messenger.send("newsChangeWeek", [newIssueWeek]) + diff --git a/toontown/src/shtiker/KartPage.py b/toontown/src/shtiker/KartPage.py new file mode 100644 index 0000000..f2a7d2e --- /dev/null +++ b/toontown/src/shtiker/KartPage.py @@ -0,0 +1,2074 @@ +########################################################################## +# Module: KartPage.py +# Purpose: The KartPage Modules provides the KartPage class which allows +# the player to customize his or her current Kart. This includes +# painting the kart or applying different accessories to the +# Kart. +# Date: 05/10/05 +# Author: jjtaylor +########################################################################## + +########################################################################## +# Panda Import Modules +########################################################################## +#from direct.directbase import DirectStart +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import PythonUtil +from direct.task import Task + +########################################################################## +# Toontown Import Modules +########################################################################## +from toontown.fishing.FishPhoto import DirectRegion +from toontown.racing.KartDNA import * +from toontown.racing.Kart import Kart +from toontown.racing import RaceGlobals +from toontown.shtiker.ShtikerPage import ShtikerPage +from toontown.toonbase import ToontownGlobals, TTLocalizer +from FishPage import FishingTrophy + +########################################################################## +# Python Import Modules +########################################################################## +if( __debug__ ): + import pdb + +########################################################################## +# Global Variables and Enumerations +########################################################################## +PageMode = PythonUtil.Enum( "Customize, Records, Trophy" ) + +class KartPage( ShtikerPage ): + + """ + Purpose: The KartPage class provides the basic functionality for + switching between the different Kart pages which include customization, + race records, and trophies. + + Note: The KartPage is a singleton object because only one instance of + the page should exist at a time. + """ + + ###################################################################### + # Class variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "KartPage" ) + + def __init__( self ): + """ + Purpose: The __init__ Method provides the intial construction + of the KartPage object as well as constructing the ShtikerPage + superclass. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + ShtikerPage.__init__( self ) + + self.avatar = None + self.mode = PageMode.Customize + + def enter( self ): + """ + Purpose: The enter Method. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + # If the Page has not already been loaded, do so now before + # the page mode. + if( not hasattr( self, "title" ) ): + self.load() + self.setMode( self.mode, 1 ) + + # Make the call to the superclass enter method. + ShtikerPage.enter( self ) + + def exit( self ): + """ + Purpose: The exit Method. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + self.kartCustomizer.hide() + self.racingTrophies.hide() + self.racingRecords.hide() + + # Make the call to the superclass exit method. + ShtikerPage.exit( self ) + + def setAvatar( self, av ): + """ + Purpose: The setAvatar Method sets the current avatar who is + looking at the KartPage of the Shtiker book. + + Params: av - The avatar who is looking at the page. + Return: None + """ + + self.avatar = av + + def getAvatar( self ): + """ + Purpose: The getAvatar Method retrieves the current avatar who is + looking at the Kartpage of the Shtiker book. + + Params: None + Return: Avatar - The avatar looking at the page. + """ + + return self.avatar + + def load( self ): + """ + Purpose: The load Method is to properly load the appropriate + GUI for the KartPage of the Shtiker Book. + + Params: None + Return: None + """ + + assert self.notify.debugStateCall(self) + ShtikerPage.load( self ) + + # Load the Customize GUI + self.kartCustomizer = KartCustomizeUI( self.avatar, self ) + self.kartCustomizer.hide() + self.kartCustomizer.load() + + # Load the Records Page GUI + self.racingRecords = RacingRecordsUI( self.avatar, self ) + self.racingRecords.hide() + self.racingRecords.load() + + # Load the Trophy Page GUI + self.racingTrophies = RacingTrophiesUI( self.avatar, self ) + self.racingTrophies.hide() + self.racingTrophies.load() + + # Page Title + self.title = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.1, + pos = ( 0, 0, 0.65 ), + ) + + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1, 1, 1, 1) + clickColor = (.8, .8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + + # Load the Fish Page to borrow its tabs + gui = loader.loadModel( "phase_3.5/models/gui/fishingBook" ) + + self.customizeTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.KartPageCustomizeTab, + text_scale = TTLocalizer.KPkartTab, + text_align = TextNode.ALeft, + text_pos = (-0.025, 0.0, 0.0), + image = gui.find("**/tabs/polySurface1"), + image_pos = (0.55,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [ PageMode.Customize ], + pos = (0.92, 0, 0.55), + ) + self.recordsTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.KartPageRecordsTab, + text_scale = TTLocalizer.KPkartTab, + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [ PageMode.Records ], + pos = (0.92, 0, 0.1), + ) + self.trophyTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.KartPageTrophyTab, + text_scale = TTLocalizer.KPkartTab, + text_pos = (0.03, 0.0, 0.0), + text_align = TextNode.ALeft, + image = gui.find("**/tabs/polySurface3"), + image_pos = (-0.28,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [ PageMode.Trophy ], + pos = (0.92, 0, -0.3), + ) + self.customizeTab.setPos(-0.55,0,0.775) + self.recordsTab.setPos(-0.13,0,0.775) + self.trophyTab.setPos(0.28,0,0.775) + + gui.removeNode() + + def unload( self ): + """ + Purpose: The unload Method performs the necessary unloading of + the KartPage. + + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + ShtikerPage.unload( self ) + + def setMode( self, mode, updateAnyways = 0 ): + """ + Purpose: The setMode Method sets the current mode of the KartPage + of the Shtiker Book. + + Params: mode - the new mode. + updateAnyways - update the page based on the mode. + Return: None + """ + + messenger.send( 'wakeup' ) + + if( not updateAnyways ): + if( self.mode == mode ): + return + else: + self.mode = mode + + if( mode == PageMode.Customize ): + self.title[ 'text' ] = TTLocalizer.KartPageTitleCustomize + self.customizeTab[ 'state' ] = DGG.DISABLED + self.recordsTab[ 'state' ] = DGG.NORMAL + self.trophyTab[ 'state' ] = DGG.NORMAL + + elif( mode == PageMode.Records ): + self.title[ 'text' ] = TTLocalizer.KartPageTitleRecords + self.customizeTab[ 'state' ] = DGG.NORMAL + self.recordsTab[ 'state' ] = DGG.DISABLED + self.trophyTab[ 'state' ] = DGG.NORMAL + + elif( mode == PageMode.Trophy ): + self.title[ 'text' ] = TTLocalizer.KartPageTitleTrophy + self.customizeTab[ 'state' ] = DGG.NORMAL + self.recordsTab[ 'state' ] = DGG.NORMAL + self.trophyTab[ 'state' ] = DGG.DISABLED + + else: + raise StandardError, "KartPage::setMode - Invalid Mode %s" % ( mode ) + + self.updatePage() + + def updatePage( self ): + """ + Purpose: The updatePage Method updates the KartPage of the + ShtikerBook. + + Params: None + Return: None + """ + + if( self.mode == PageMode.Customize ): + self.kartCustomizer.show() + self.racingTrophies.hide() + self.racingRecords.hide() + elif( self.mode == PageMode.Records ): + self.kartCustomizer.hide() + self.racingTrophies.hide() + self.racingRecords.show() + elif( self.mode == PageMode.Trophy ): + self.kartCustomizer.hide() + self.racingTrophies.show() + self.racingRecords.hide() + else: + raise StandardError, "KartPage::updatePage - Invalid Mode %s" % ( self.mode ) + + +class KartCustomizeUI( DirectFrame ): + """ + Purpose: The KartCustomizeUI class initializes the user interface for + customizing a kart based on the body type and the accessories that + are owned by the toon. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "KartCustomizeUI" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the KartCustomizeUI object that will provide the base interface + for Kart customization. + + Params: None + Return: None + """ + + # Initialize instance variables + self.avatar = avatar + + # Construct the super class object from which the selector + # derives. + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + + def destroy( self ): + """ + Purpose: The destroy Method properly handles the destruction of + the KartCustomizeUI instance by handling appropriate reference + cleanup. + + Params: None + Return: None + """ + + # Destroy UI Components of the CustomizeUI + self.itemSelector.destroy() + self.kartViewer.destroy() + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + del self.avatar, self.itemSelector, self.kartViewer + + # Destroy the DirectFrame super class. + DirectFrame.destroy( self ) + + def load( self ): + """ + Purpose: The load Method handles the construction of the specific + UI components that make up the KartCustomizeUI object. + + Params: None + Return: None + """ + + # Load the uiRoot model + uiRootNode = loader.loadModel( "phase_6/models/gui/ShtikerBookUI" ) + + # Generate UI components + self.itemSelector = ItemSelector( self.avatar, parent = self ) + self.itemSelector.setPos( uiRootNode.find( "**/uiAccessoryIcons" ).getPos() ) + self.itemSelector.load( uiRootNode ) + + self.kartViewer = KartViewer( list(self.avatar.getKartDNA()), parent = self ) + self.kartViewer.setPos( uiRootNode.find( "**/uiKartView" ).getPos() ) + #TODO: where is the rotate label going??? + self.kartViewer.load( uiRootNode, "uiKartViewerFrame1", ["rotate_right_up","rotate_right_down","rotate_right_roll","rotate_right_down", (.275,-.08)], ["rotate_left_up","rotate_left_down","rotate_left_roll","rotate_left_down", (-.27,-.08)], (0,-.08) ) + + self.kartViewer.uiRotateLeft.setZ( -.25 ) + self.kartViewer.uiRotateRight.setZ( -.25 ) + + self.itemSelector.itemViewers[ 'main' ].leftArrowButton.setZ( self.kartViewer.getZ() + 0.25 ) + self.itemSelector.itemViewers[ 'main' ].rightArrowButton.setZ( self.kartViewer.getZ() + 0.25 ) + + self.kartViewer.setBounds( -0.38, 0.38, -0.25, 0.325 ) + #self.kartViewer.setBounds( -0.1, 0.1, -.1, .1 ) + self.kartViewer.setBgColor( 1.0, 1.0, 0.8, 1.0 ) + #self.kartViewer.setBgColor( 1, 0, 0, 1 ) + + # Remove the geom nodes + uiRootNode.removeNode() + + def getKartViewer( self ): + """ + """ + return self.kartViewer + + def show( self ): + self.itemSelector.itemViewers[ 'main' ].initUpdatedDNA() + self.itemSelector.setupAccessoryIcons() + self.itemSelector.show() + self.kartViewer.show(list(self.avatar.getKartDNA())) + DirectFrame.show( self ) + + def hide( self ): + if( hasattr( self, "itemSelector" ) ): + if( hasattr( self.itemSelector.itemViewers[ 'main' ], 'updatedDNA' ) ): + self.itemSelector.itemViewers[ 'main' ].setUpdatedDNA() + self.itemSelector.resetAccessoryIcons() + + if( hasattr( self.itemSelector.itemViewers['main'], 'confirmDlg' ) ): + self.itemSelector.itemViewers[ 'main' ].confirmDlg.hide() + + self.itemSelector.hide() + if( hasattr( self, "kartViewer" ) ): + self.kartViewer.hide() + DirectFrame.hide( self ) + +class RacingRecordsUI( DirectFrame ): + """ + Purpose: The RacingRecordsUI class initializes the user interface for + displaying the personal best times earned by a toon. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "RacingRecordsUI" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the RacingRecordsUI object. + + Params: None + Return: None + """ + + # Initialize instance variables + self.avatar = avatar + self.timeDisplayList = [] + self.lastTimes = [] + + # Construct the super class object + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + + def destroy( self ): + """ + Purpose: The destroy Method properly handles the destruction of + the RacingRecordsUI instance by handling appropriate reference + cleanup. + + Params: None + Return: None + """ + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + del self.avatar, self.lastTimes, self.timeDisplayList + + # Destroy the DirectFrame super class. + DirectFrame.destroy( self ) + + def load( self ): + """ + Purpose: The load Method handles the construction of the specific + UI components that make up the RaceRecordsUI object. + + Params: None + Return: None + """ + offset = 0 + trackNameArray = TTLocalizer.KartRace_TrackNames + for trackId in RaceGlobals.TrackIds: + trackName = trackNameArray[trackId] + trackNameDisplay = DirectLabel( + parent = self, + relief = None, + text = trackName, + text_align = TextNode.ALeft, + text_scale = 0.075, + text_fg = ( 0.95, 0.95, 0.0, 1.0 ), + text_shadow = ( 0, 0, 0, 1 ), + text_pos = ( -0.8, 0.5 - offset), + text_font = ToontownGlobals.getSignFont() + ) + bestTimeDisplay = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.KartRace_Unraced, + text_scale = 0.06, + text_fg = ( 0.0, 0.0, 0.0, 1.0 ), + text_pos = ( 0.65, 0.5 - offset ), + text_font = ToontownGlobals.getToonFont() + ) + offset += 0.10 + # save this one for updating + self.timeDisplayList.append(bestTimeDisplay) + + def show( self ): + # update personal best times + bestTimes = self.avatar.getKartingPersonalBestAll() + # but only if they have changed + if bestTimes != self.lastTimes: + for i in range(0, len(bestTimes)): + time = bestTimes[i] + if time != 0.0: + # use their best time + whole, part = divmod( time, 1 ) + min, sec = divmod( whole, 60 ) + timeText = "%02d:%02d:%02d" % (min, sec, part * 100) + self.timeDisplayList[i]['text'] = timeText, + self.lastTimes = bestTimes + DirectFrame.show( self ) + + + +class RacingTrophiesUI( DirectFrame ): + """ + Purpose: The RacingTrophiesUI class initializes the user interface for + displaying the racing trophies earned by a toon. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "RacingTrophiesUI" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the RacingTrophiesUI object. + + Params: None + Return: None + """ + + # Initialize instance variables + self.avatar = avatar + self.trophyPanels = [] + self.trophies = None + self.trophyTextDisplay = None + + # Construct the super class object + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + + def destroy( self ): + """ + Purpose: The destroy Method properly handles thepass destruction of + the RacingTrophiesUI instance by handling appropriate reference + cleanup. + + Params: None + Return: None + """ + for panel in self.trophyPanels: + panel.destroy() + self.ticketDisplay.destroy() + self.trophyTextDisplay.destroy() + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + del self.avatar, self.ticketDisplay, self.trophyPanels, self.trophies, self.trophyTextDisplay + + # Destroy the DirectFrame super class. + DirectFrame.destroy( self ) + + def load( self ): + """pass + Purpose: The load Method handles the construction of the specific + UI components that make up the RacingTrophiesUI object. + + Params: None + Return: None + """ + self.trophies = base.localAvatar.getKartingTrophies() + + xStart = -0.76 + yStart = 0.475 + xOffset = 0.17 + yOffset = 0.23 + # display the trophies a toon has + for j in range(RaceGlobals.NumCups): + for i in range(RaceGlobals.TrophiesPerCup): + trophyPanel = DirectLabel( + parent = self, + relief = None, + pos = (xStart + (i * xOffset), + 0.0, + yStart - (j * yOffset)), + state = DGG.NORMAL, + image = DGG.getDefaultDialogGeom(), + image_scale = (0.75, 1, 1), + image_color = (0.8, 0.8, 0.8, 1), + text = TTLocalizer.SuitPageMystery[0], + text_scale = 0.45, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0, -0.25), + text_font = ToontownGlobals.getInterfaceFont(), + text_wordwrap = 5.5 + ) + + trophyPanel.scale = 0.2 + trophyPanel.setScale(trophyPanel.scale) + + # add to master list + self.trophyPanels.append(trophyPanel) + + xStart = -0.25 + yStart = -0.38 + xOffset = 0.25 + for i in range(RaceGlobals.NumCups): + cupPanel = DirectLabel( + parent = self, + relief = None, + pos = (xStart + (i * xOffset), 0.0, yStart), + state = DGG.NORMAL, + image = DGG.getDefaultDialogGeom(), + image_scale = (0.75, 1, 1), + image_color = (0.8, 0.8, 0.8, 1), + text = TTLocalizer.SuitPageMystery[0], + text_scale = 0.45, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0, -0.25), + text_font = ToontownGlobals.getInterfaceFont(), + text_wordwrap = 5.5 + ) + cupPanel.scale = 0.3 + cupPanel.setScale(cupPanel.scale) + self.trophyPanels.append(cupPanel) + + # display the current number of tickets a toon has + self.ticketDisplay = DirectLabel( + parent = self, + relief = None, + image = loader.loadModel('phase_6/models/karting/tickets'), + image_pos = (0.2,0,-0.635), + image_scale = 0.2, + text = TTLocalizer.KartPageTickets + str(self.avatar.getTickets()), + text_scale = 0.07, + text_fg = ( 0, 0, 0.95, 1.0 ), + text_pos = ( 0, -0.65), + text_font = ToontownGlobals.getSignFont() + ) + + # display the text description of a moused over trophy + self.trophyTextDisplay = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.07, + text_fg = ( 1, 0, 0, 1 ), + text_shadow = ( 0, 0, 0, 0 ), + text_pos = ( 0.0, -0.175 ), + text_font = ToontownGlobals.getInterfaceFont() + ) + + self.updateTrophies() + + def grow(self, index, pos): + self.trophyPanels[index]['image_color'] = Vec4(1.0, 1.0, 0.8, 1.0) + self.trophyTextDisplay['text'] = TTLocalizer.KartPageTrophyDetail % (index+1, TTLocalizer.KartTrophyDescriptions[index]) + + def shrink(self, index, pos): + self.trophyPanels[index]['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + self.trophyTextDisplay['text'] = "" + + def show( self ): + # update toon ticket count + self.ticketDisplay['text'] = TTLocalizer.KartPageTickets + str(self.avatar.getTickets()), + # see if the any new trophies need to be displayed + if self.trophies != base.localAvatar.getKartingTrophies(): + self.trophies = base.localAvatar.getKartingTrophies() + self.updateTrophies() + DirectFrame.show( self ) + + def updateTrophies( self ): + for t in range(len(self.trophyPanels)): + if self.trophies[t]: + # add our special scaling functions + trophyPanel = self.trophyPanels[t] + trophyPanel['text'] = "" + # get a trophy + #trophyModel = RacingTrophy(t / RaceGlobals.TrophiesPerCup) + trophyModel = RacingTrophy(t) + trophyModel.reparentTo(trophyPanel) + trophyModel.nameLabel.hide() + trophyModel.setPos(0,0,-0.4) + trophyPanel['image_color'] = Vec4(1.0, 1.0, 1.0, 1.0) + trophyPanel.bind(DGG.ENTER, self.grow, extraArgs=[t]) + trophyPanel.bind(DGG.EXIT, self.shrink, extraArgs=[t]) + +class ItemSelector( DirectFrame ): + """ + Purpose: The ItemSelector class. + """ + + # Initialize class variables + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "ItemSelector" ) + + class ItemViewer( DirectFrame ): + """ + Purpose: The ItemViewer class shows specific kart items (accessories + and paint buckets) in order to allow the toon to customize the kart. + """ + + # Initialize class variables + notify = DirectNotifyGlobal.directNotify.newCategory( "ItemViewer" ) + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the ItemViewer instance by constructing the DirectFrame super + class. + + Params: parent - the parent node in the 2d scene graph. + Return: None + """ + + # Initialize instance variables + self.currItem = None + self.itemList = None + self.parent = parent + self.avatar = avatar + self.currAccessoryType = None + self.texCount = 1 + + # Initialize the DirectFrame Super Class + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = (0, 0, 0), + scale = (1.0, 1.0, 1.0), + ) + + def destroy( self ): + """ + Purpose: The destroy Method cleans up the UI components for + the item viewer instance. + + Params: None + Return: None + """ + + # Destroy each component of the viewer + self.uiBgFrame.destroy() + self.uiImagePlane.destroy() + self.uiTextBox.destroy() + self.leftArrowButton.destroy() + self.rightArrowButton.destroy() + + # Remove references + del self.avatar, self.parent, self.currItem, self.itemList + del self.uiBgFrame, self.uiImagePlane, self.uiTextBox + del self.leftArrowButton, self.rightArrowButton, self.deleteButton + + # Destroy the DirectFrame Super class + DirectFrame.destroy( self ) + + def setCurrentItem( self, currItem ): + """ + Purpose: The setCurrentItem Method assigns the current item that + is being used with a kart based on the specific category of items + which are found in the item list. For instance, the current paint + bucket item that is used for the body of the kart. + + Params: currItem - the currently selected item for the kart. + Return: None + """ + self.currItem = currItem + + def getCurrentItem( self ): + """ + Purpose: The getCurrentItem Method obtains the current item that + is being used with a kart based on the ui selection. This is + primarily used for customization when a new item has been selected + in the UI. + + Params: None + Return: None + """ + return self.currItem + + def initUpdatedDNA( self ): + """ + """ + self.updatedDNA = list( self.avatar.getKartDNA() ) + + def setUpdatedDNA( self ): + """ + """ + currKartDNA = self.avatar.getKartDNA() + for i in xrange( len( self.updatedDNA ) ): + if( self.updatedDNA[ i ] != currKartDNA[ i ] ): + self.avatar.requestKartDNAFieldUpdate( i, self.updatedDNA[ i ] ) + + del self.updatedDNA + + def setItemList( self, itemList ): + """ + Purpose: The setItemList Method assigns the item list for the + specific category of items for the kart. For instance, the paint + bucket colors that are available for the kart. + + Params: itemList - a list of items based on an item category. + Return: None + """ + self.itemList = itemList + + def load( self, uiRootNode ): + """ + Purpose: The load Method loads the appropriate geometry for each + of the UI componenets that are needed to create the item viewer + object. + + Params: uiRootNode - Node which holds all UI geometry. + Return: None + """ + + self.uiBgFrame = DirectFrame( + parent = self, + relief = None, + geom = uiRootNode.find( "**/uiAccessoryViewerFrame" ), + scale = 1.0, + ) + + self.uiImagePlane = DirectFrame( + parent = self, + relief = None, + geom = uiRootNode.find( "**/uiAccessoryImagePlane" ), + scale = 0.75, + ) + bounds = self.uiImagePlane.getBounds() + cm = CardMaker( "uiImagePlane" ) + cm.setFrame( bounds[ 0 ], bounds[ 1 ], bounds[ 2 ], bounds[ 3 ] ) + self.uiImagePlane[ 'geom' ] = NodePath( cm.generate() ) + self.uiImagePlane.component( 'geom0' ).setColorScale( 1.0, 1.0, 0.8, 1.0 ) + self.uiImagePlane.component( 'geom0' ).setTransparency( True ) + + # Image Plane Locator Nodes + self.locator1 = self.attachNewNode( "locator1" ) + self.locator2 = self.attachNewNode( "locator2" ) + self.locator1.setPos( 0, 0, 0.035 ) + self.locator2.setPos( 0.0, 0.0, 0.0 ) + + tex = loader.loadTexture( "phase_6/maps/NoAccessoryIcon3.jpg", + "phase_6/maps/NoAccessoryIcon3_a.rgb" ) + + self.uiImagePlane.component( 'geom0' ).setTexture( tex, self.texCount ) + self.texCount += 1 + + self.uiTextBox = DirectFrame( + parent = self, + relief = None, + #geom = uiRootNode.find( "**/uiAccessoryTextBlock" ), + scale = 1.0, + text = "", + text_font = ToontownGlobals.getInterfaceFont(), + text_fg = (.5,0,0,1), + text_shadow = (0,0,0,1), + #text_bg = (0,0,0,1), + text_scale = 0.0715, + text_pos = (0.0, -0.230, 0.0), + ) + + self.deleteButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/uiAccessorydelete_up" ), + uiRootNode.find( "**/uiAccessorydelete_down" ), + uiRootNode.find( "**/uiAccessorydelete_rollover" ), + uiRootNode.find( "**/uiAccessorydelete_rollover" ) ), + text = TTLocalizer.KartShtikerDelete, + text_font = ToontownGlobals.getSignFont(), + text_pos = ( 0, -0.125, 0 ), + text_scale = TTLocalizer.KPdeleteButton, + text_fg = ( 1, 1, 1, 1 ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__handleItemDeleteConfirm() ), + #command = ( lambda : self.__handleItemDelete() ), + ) + self.deleteButton.hide() + #print pdir(uiRootNode) + self.leftArrowButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/ArrowLeftButtonUp" ), + uiRootNode.find( "**/ArrowLeftButtonDown" ), + uiRootNode.find( "**/ArrowLeftButtonRollover" ), + uiRootNode.find( "**/ArrowLeftButtonInactive" ), ), + + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__handleItemChange( -1 ) ), + ) + + self.rightArrowButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/ArrowRightButtonUp" ), + uiRootNode.find( "**/ArrowRightButtonDown" ), + uiRootNode.find( "**/ArrowRightButtonRollover" ), + uiRootNode.find( "**/ArrowRightButtonInactive"), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__handleItemChange( 1 ) ), + ) + + def enable( self ): + """ + Purpose: The enable Method handles all of the necessary steps + to ensure that the ItemViewer is enabled, ie. setting the + buttons to an enabled state. + + Params: None + Return: None + """ + self.leftArrowButton['state'] = DGG.NORMAL + self.rightArrowButton['state'] = DGG.NORMAL + + def disable( self ): + """ + Purpose: The disable Method handles all of the necessary steps + to ensure that the ItemViewer is disabled, ie. setting the + buttons to a disabled state. + + Params: None + Return: None + """ + self.leftArrowButton['state'] = DGG.DISABLED + self.rightArrowButton['state'] = DGG.DISABLED + + def setupViewer( self, category ): + """ + Purpose: + + Params: None + Return: None + """ + colorTypeList = [ KartDNA.bodyColor, KartDNA.accColor ] + if( category == InvalidEntry ): + self.__handleHideItem() + self.__updateDeleteButton( DGG.DISABLED ) + self.disable() + else: + accessDict = getAccessDictByType( self.avatar.getKartAccessoriesOwned() ) + + self.currAccessoryType = category + if( category in colorTypeList ): + self.itemList = list( accessDict.get( KartDNA.bodyColor, [] ) ) + self.itemList.append( InvalidEntry ) + elif( category == KartDNA.rimsType ): + self.itemList = list( accessDict.get( KartDNA.rimsType, [] ) ) + self.itemList.append( InvalidEntry ) + else: + self.itemList = list( accessDict.get( category, [] ) ) + + # Set the current item to that found in the updated dna. This is + # used in place of the actual toon dna because the updated dna + # holds the latest changes. + self.currItem = self.updatedDNA[ category ] + + if( category in colorTypeList ): + # If the Default Color is selected, do not allow a delete. It is + # always present, but doesn't count against the accessories owned by + # the toon. + if( self.currItem == InvalidEntry or self.currItem not in accessDict.get( KartDNA.bodyColor ) ): + self.__updateDeleteButton( DGG.DISABLED ) + else: + self.__updateDeleteButton( DGG.NORMAL, TTLocalizer.KartShtikerDelete ) + self.__handleShowItem() + elif( category == KartDNA.rimsType ): + # If Default Rim is selected, do not allow a delete. It is always + # present, but doesn't count against the accessories owned by + # the toon. + if( self.currItem == InvalidEntry ): + self.__updateDeleteButton( DGG.DISABLED ) + else: + self.__updateDeleteButton( DGG.NORMAL, TTLocalizer.KartShtikerDelete ) + self.__handleShowItem() + + elif( self.currItem != InvalidEntry and self.itemList != [] ): + if( self.currItem in self.avatar.accessories ): + self.__handleShowItem() + self.__updateDeleteButton( DGG.NORMAL, TTLocalizer.KartShtikerDelete ) + else: + self.__handleHideItem() + self.__updateDeleteButton( DGG.DISABLED ) + + if( len( self.itemList ) == 1 ): + if( self.currAccessoryType == KartDNA.rimsType ): + self.disable() + self.setViewerText(TTLocalizer.KartShtikerDefault %getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + #self.__updateDeleteButton( DGG.DISABLED ) + elif( self.currAccessoryType in colorTypeList ): + self.disable() + self.setViewerText(TTLocalizer.KartShtikerDefault % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + #elif self.currItem == InvalidEntry: + #self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + else: + self.enable() + elif( len( self.itemList ) == 0 ): + self.disable() + self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + else: + if( self.currAccessoryType == KartDNA.rimsType ): + self.setViewerText(TTLocalizer.KartShtikerDefault %getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + elif( self.currAccessoryType in colorTypeList ): + self.setViewerText(TTLocalizer.KartShtikerDefault % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + elif self.currItem == InvalidEntry: + self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + self.enable() + + def resetViewer( self ): + """ + Purpose: + + Params: + Return: + """ + self.itemList = None + self.currItem = None + self.disable() + + def __updateDeleteButton( self, state, text = TTLocalizer.KartShtikerDelete ): + """ + Purpose: + + Params: state - the new state of the button. + text - the new text of the button. + Return: None + """ + + self.deleteButton[ 'state' ] = state + self.deleteButton[ 'text' ] = text + if( state == DGG.NORMAL ): + self.uiImagePlane.setPos( self.locator1.getPos() ) + self.deleteButton.show() + else: + self.uiImagePlane.setPos( self.locator2.getPos() ) + self.deleteButton.hide() + + def setViewerText( self, text ): + """ + Purpose: The setViewerText Method updates the text within the + uiTextBox in order to show the proper information to the player. + + Params: text - the updated text. + Return: None + """ + self.uiTextBox[ 'text' ] = text + + def __updateViewerUI( self ): + """ + """ + # This accessory list is for the special case of accessory types that + # have default accessories, such as the rims or paint buckets. + accList = [ KartDNA.bodyColor, KartDNA.accColor, KartDNA.rimsType ] + if( self.currItem != InvalidEntry ): + self.__handleShowItem() + + + # Now handle the UI Button update. + if( self.currItem not in self.avatar.accessories and self.currAccessoryType in accList ): + self.__updateDeleteButton( DGG.DISABLED ) + else: + self.__updateDeleteButton( DGG.NORMAL, TTLocalizer.KartShtikerDelete ) + else: + if( self.currAccessoryType in accList ): + self.setViewerText(TTLocalizer.KartShtikerDefault % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + self.__handleShowItem() + else: + self.__handleHideItem() + #display appropriate text for no acccesory of this type + self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + self.__updateDeleteButton( DGG.DISABLED ) + + def __handleItemChange( self, direction ): + """ + Purpose: The __handleItemChange Method provides the necessary + functionality to 'scroll' through the item list for selecting + a desired item. + + Params: direction - move forward or backwards in the list. + Return: None + """ + self.notify.debug( "__handleItemChange: currItem %s" % ( self.currItem ) ) + + def updateItem( self = self, direction = direction ): + """ + """ + # Check if the toon has the item, if not then an accessory + # is not equipped. + if( self.itemList.count( self.currItem ) != 0 ): + # Find the index in the list. + index = self.itemList.index( self.currItem ) + index += direction + + # Check if the index is out-of-bounds. + if( index < 0 or index >= len( self.itemList ) ): + # An accessory within these lists must be equipped. + invalidList = [ KartDNA.bodyColor, KartDNA.accColor, KartDNA.rimsType ] + if( self.currAccessoryType not in invalidList ): + self.currItem = InvalidEntry + else: + # Performs the wrap-around for the invalidList + if( direction > 0 ): + self.currItem = self.itemList[ 0 ] + else: + self.currItem = self.itemList[ -1 ] + else: + self.currItem = self.itemList[ index ] + else: + if( self.itemList == [] ): + self.currItem = InvalidEntry + elif( direction > 0 ): + self.currItem = self.itemList[ 0 ] + else: + self.currItem = self.itemList[ -1 ] + + #pdb.set_trace() + # if we are browsing parts we must be awake + messenger.send('wakeup') + updateItem() + self.__updateViewerUI() + + self.notify.debug( "__handleItemChange: currItem %s" % ( self.currItem ) ) + self.updatedDNA[ self.currAccessoryType ] = self.currItem + + # obtain the kart and set the update + kart = self.parent.parent.getKartViewer().getKart() + kart.updateDNAField( self.currAccessoryType, self.currItem ) + + def __handleShowItem( self ): + """ + """ + self.uiImagePlane.component('geom0').setColorScale( 1.0, 1.0, 1.0, 1.0 ) + + if( self.currAccessoryType in [ KartDNA.ebType, KartDNA.spType, KartDNA.fwwType, KartDNA.bwwType ] ): + #pdb.set_trace() + texNodePath = getTexCardNode( self.currItem ) + tex = loader.loadTexture( "phase_6/maps/%s.jpg" % ( texNodePath ), + "phase_6/maps/%s_a.rgb" % ( texNodePath ) ) + #if( tex is None ): + # tex = loader.loadTexture( "phase_4/maps/robber-baron.jpg" ) + + elif( self.currAccessoryType == KartDNA.rimsType ): + if( self.currItem == InvalidEntry ): + # THIS WILL LIKELY NEED TO BE FIXED WHEN NEW RIM + # IS ADDED TO COMPENSATE FOR LOSS OF DEFAULT RIM + # IN KART SELECTION. + texNodePath = getTexCardNode( getDefaultRim() ) + else: + texNodePath = getTexCardNode( self.currItem ) + tex = loader.loadTexture( "phase_6/maps/%s.jpg" % ( texNodePath ), + "phase_6/maps/%s_a.rgb" % ( texNodePath ) ) + + elif( self.currAccessoryType in [ KartDNA.bodyColor, KartDNA.accColor ] ): + tex = loader.loadTexture( "phase_6/maps/Kartmenu_paintbucket.jpg", + "phase_6/maps/Kartmenu_paintbucket_a.rgb" ) + + # Obtain the default color if the item is -1, handle this similar to the + # rims. + if( self.currItem == InvalidEntry ): + self.uiImagePlane.component( 'geom0' ).setColorScale( getDefaultColor() ) + else: + self.uiImagePlane.component( 'geom0' ).setColorScale( getAccessory( self.currItem ) ) + + elif( self.currAccessoryType == KartDNA.decalType ): + kart = self.parent.parent.getKartViewer().getKart() + kartDecal = getDecalId( kart.kartDNA[ KartDNA.bodyType ] ) + texNodePath = getTexCardNode( self.currItem ) + + tex = loader.loadTexture( "phase_6/maps/%s.jpg" % (texNodePath) % ( kartDecal ), + "phase_6/maps/%s_a.rgb" % (texNodePath) % ( kartDecal ) ) + else: + tex = loader.loadTexture( "phase_6/maps/NoAccessoryIcon3.jpg", + "phase_6/maps/NoAccessoryIcon3_a.rgb" ) + + #display this accessory's name & type + colorTypeList = [ KartDNA.bodyColor, KartDNA.accColor ] + if( self.currItem == InvalidEntry ): + if( self.currAccessoryType == KartDNA.rimsType ): + self.setViewerText(TTLocalizer.KartShtikerDefault %getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + elif( self.currAccessoryType in colorTypeList ): + self.setViewerText(TTLocalizer.KartShtikerDefault % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + elif self.currItem == InvalidEntry: + self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + else: + self.setViewerText(getAccName(self.currItem) +" "+getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + + + self.uiImagePlane.component( 'geom0' ).setTexture( tex, self.texCount ) + self.texCount +=1 + + def __handleHideItem( self ): + """ + """ + self.uiImagePlane.component( 'geom0' ).setColorScale( 1.0, 1.0, 1.0, 1.0 ) + self.uiImagePlane.component( 'geom0' ).setTexture( loader.loadTexture( 'phase_6/maps/NoAccessoryIcon3.jpg', + 'phase_6/maps/NoAccessoryIcon3_a.rgb' ), self.texCount ) + #self.uiImagePlane.component('geom0').setColorScale( 1.0, 1.0, 0.8, 1.0 ) + #self.uiImagePlane.setTextureOff( self.texCount ) + self.texCount += 1 + + def __handleItemDeleteConfirm( self ): + """ + """ + + self.notify.debug( "__handleItemDeleteConfirm:" ) + if( not hasattr( self, "confirmDlg" ) ): + uiRootNode = loader.loadModel( "phase_6/models/gui/ShtikerBookUI" ) + self.confirmDlg = DirectFrame( parent = aspect2d, + relief = None, + geom = uiRootNode.find( "**/uiAccessoryNotice" ), + geom_scale = 1.0, + text = TTLocalizer.KartPageConfirmDelete, + text_scale = 0.07, + text_pos = ( 0, 0.022 ) ) + self.confirmDlg.hide() + self.confirmDlg.setPos( aspect2d, 0, -.195, -.195 ) + + self.cancelButton = DirectButton( + parent = self.confirmDlg, + relief = None, + image = ( uiRootNode.find( "**/CancelButtonUp" ), + uiRootNode.find( "**/CancelButtonDown" ), + uiRootNode.find( "**/CancelButtonRollover" ) ), + geom = uiRootNode.find( "**/CancelIcon" ), + scale = 1.0, + pressEffect = False, + command = self.confirmDlg.hide ) + self.confirmButton = DirectButton( + parent = self.confirmDlg, + relief = None, + image = ( uiRootNode.find( "**/CheckButtonUp" ), + uiRootNode.find( "**/CheckButtonDown" ), + uiRootNode.find( "**/CheckButtonRollover" ) ), + geom = uiRootNode.find( "**/CheckIcon" ), + scale = 1.0, + pressEffect = False, + command = self.__handleItemDelete ) + + self.confirmDlg.show() + + def __handleItemDelete( self ): + """ + """ + def handleColorDelete( self = self ): + """ + """ + if( self.currAccessoryType == KartDNA.bodyColor ): + if( self.updatedDNA[ KartDNA.accColor ] == deletedItem ): + self.avatar.requestKartDNAFieldUpdate( KartDNA.accColor, self.currItem ) + self.updatedDNA[ KartDNA.accColor ] = self.currItem + + # obtain the kart and set the update + kart = self.parent.parent.getKartViewer().getKart() + kart.updateDNAField( KartDNA.accColor, self.currItem ) + + elif( self.currAccessoryType == KartDNA.accColor ): + if( self.updatedDNA[ KartDNA.bodyColor ] == deletedItem ): + self.avatar.requestKartDNAFieldUpdate( KartDNA.bodyColor, self.currItem ) + self.updatedDNA[ KartDNA.bodyColor ] = self.currItem + + # obtain the kart and set the update + kart = self.parent.parent.getKartViewer().getKart() + kart.updateDNAField( KartDNA.bodyColor, self.currItem ) + else: + pass + + self.notify.debug( "__handleItemDelete: Delete request on accessory %s" % ( self.currItem ) ) + self.confirmDlg.hide() + + + # if we are deleting parts we must be awake + messenger.send('wakeup') + + # What needs to be done? + # - request that it is removed. + deletedItem = self.currItem + self.avatar.requestRemoveOwnedAccessory( deletedItem ) + + index = self.itemList.index( self.currItem ) + self.itemList.pop( index ) + + # If the current accessory is deleted, then make the + # accessory the default value. ( InvalidEntry ) + self.currItem = InvalidEntry + + # Update the viewer and keep track of the dna change. + self.__updateViewerUI() + self.updatedDNA[ self.currAccessoryType ] = self.currItem + + # obtain the kart and set the update + kart = self.parent.parent.getKartViewer().getKart() + kart.updateDNAField( self.currAccessoryType, self.currItem ) + + if( self.avatar.getAccessoryByType( self.currAccessoryType ) == deletedItem ): + # Deleting the current item that is equipped on the kart + # in the database. Thus, we must change this now. + self.avatar.requestKartDNAFieldUpdate( self.currAccessoryType, self.currItem ) + + # If it is a color type, then handle the color delete accordingly. + if( self.currAccessoryType in [ KartDNA.bodyColor, KartDNA.accColor ] ): + handleColorDelete() + + if( self.itemList == [] or self.itemList[ 0 ] == InvalidEntry ): + # Do not allow the button corresponding to this + # category to be re-enabled because the last item + # has been deleted. + self.disable() + #self.setViewerText( TTLocalizer.KartShtikerNoAccessories ) + self.setViewerText(TTLocalizer.KartShtikerNo % getattr(TTLocalizer,AccessoryTypeNameDict[self.currAccessoryType])) + + + def __init__( self, avatar, parent = aspect2d ): + """ + Purpose: The __init__ Method provides the initial construction of + the ItemSelector instance by constructing the DirectFrame super + class and initializing instance variables. + + Params: None + Return: None + """ + + # Initialize instance variables. + self.state = InvalidEntry + self.avatar = avatar + self.itemViewers = {} + self.buttonDict = {} + self.parent = parent + + # Construct the DirectFrame super class from which the selector + # is derived. + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = (0, 0, 0), + scale = (1.0, 1.0, 1.0), + ) + + def destroy( self ): + """ + Purpose: The destroy Method provides the final clean up of the + ItemSelector by destroying child UI components and the super + class. In addition, references are removed to ensure proper + garbage collection. + + Params: None + Return: None + """ + + # Destroy UI Componenets + for key in self.buttonDict.keys(): + self.buttonDict[ key ].destroy() + del self.buttonDict[ key ] + + for key in self.itemViewers.keys(): + self.itemViewers[ key ].destroy() + del self.itemViewers[ key ] + + # Remove References + del self.avatar, self.itemViewers, self.buttonDict + del self.ebButton, self.fwwButton, self.bwwButton + del self.rimButton, self.decalButton, self.paintKartButton + del self.paintAccessoryButton + + # Destroy the Super Class + DirectFrame.destroy( self ) + + def load( self, uiRootNode ): + """ + Purpose: The load Method loads the appropriate geometry for each + of the UI componenets that are needed to create the item selector + object. + + Params: uiRootNode - Node which holds all UI geometry. + Return: None + """ + + # Initialize the Main Item Viewer which will be used by all + # item category buttons save for the paint button. + self.itemViewers[ 'main' ] = ItemSelector.ItemViewer( self.avatar, self ) + self.itemViewers[ 'main' ].load( uiRootNode ) + self.itemViewers[ 'main' ].setPos( self.getParent(), uiRootNode.find( "**/uiAccessoryView" ).getPos() ) + + # Initialize Item Category Buttons + self.ebButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/eBlockButton_up" ), + uiRootNode.find( "**/eBlockButton_rollover" ), + uiRootNode.find( "**/eBlockButton_rollover" ), + uiRootNode.find( "**/eBlockButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.ebType ) ), + ) + self.buttonDict[ KartDNA.ebType ] = self.ebButton + + self.spButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/spoilerButton_up" ), + uiRootNode.find( "**/spoilerButton_rollover" ), + uiRootNode.find( "**/spoilerButton_rollover" ), + uiRootNode.find( "**/spoilerButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.spType ) ), + ) + self.buttonDict[ KartDNA.spType ] = self.spButton + + self.fwwButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/frontButton_up" ), + uiRootNode.find( "**/frontButton_rollover" ), + uiRootNode.find( "**/frontButton_rollover" ), + uiRootNode.find( "**/frontButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.fwwType ) ), + ) + self.buttonDict[ KartDNA.fwwType ] = self.fwwButton + + self.bwwButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/rearButton_up" ), + uiRootNode.find( "**/rearButton_rollover" ), + uiRootNode.find( "**/rearButton_rollover" ), + uiRootNode.find( "**/rearButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.bwwType ) ), + ) + self.buttonDict[ KartDNA.bwwType ] = self.bwwButton + + self.rimButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/rimButton_up" ), + uiRootNode.find( "**/rimButton_rollover" ), + uiRootNode.find( "**/rimButton_rollover" ), + uiRootNode.find( "**/rimButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.rimsType ) ), + ) + self.buttonDict[ KartDNA.rimsType ] = self.rimButton + + self.decalButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/decalButton_up" ), + uiRootNode.find( "**/decalButton_rollover" ), + uiRootNode.find( "**/decalButton_rollover" ), + uiRootNode.find( "**/decalButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.decalType ) ), + ) + self.buttonDict[ KartDNA.decalType ] = self.decalButton + + self.paintKartButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/paintKartButton_up" ), + uiRootNode.find( "**/paintKartButton_rollover" ), + uiRootNode.find( "**/paintKartButton_rollover" ), + uiRootNode.find( "**/paintKartButton_inactive" ), ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.bodyColor ) ), + ) + self.buttonDict[ KartDNA.bodyColor ] = self.paintKartButton + + self.paintAccessoryButton = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/paintAccessoryButton_up" ), + uiRootNode.find( "**/paintAccessoryButton_rollover" ), + uiRootNode.find( "**/paintAccessoryButton_rollover" ), + uiRootNode.find( "**/paintAccessoryButton_inactive" ) ), + scale = 1.0, + pressEffect = False, + command = ( lambda : self.__changeItemCategory( KartDNA.accColor ) ), + ) + self.buttonDict[ KartDNA.accColor ] = self.paintAccessoryButton + + def setupAccessoryIcons( self ): + """ + Purpose: The setupAccessoryIcons Method properly sets up the + accessory icons that are used within the ItemSelector to chose + between the different accessory categories. + + Params: None + Return: None + """ + # Now, disable buttons where there aren't any accessories to + # select from. + accessDict = getAccessDictByType( self.avatar.getKartAccessoriesOwned() ) + + # If the toon does not own any accessories, disable the main + # kart viewer component. + if( accessDict == {} ): + self.itemViewers[ 'main' ].disable() + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerNoAccessories ) + + return + + #self.itemViewers[ 'main' ].setupViewer( self.state ) + self.__changeItemCategory( self.state ) + + def resetAccessoryIcons( self ): + """ + Purpose: The resetAccessoryIcons Method resets the accessory + icons for the ItemSelector when the KartPage is no longer being + accessed. + + Params: None + Return: None + """ + for key in self.buttonDict.keys(): + self.buttonDict[ key ].setProp( 'state', DGG.NORMAL ) + + self.itemViewers[ 'main' ].show() + self.itemViewers[ 'main' ].setViewerText( "" ) + + self.state = InvalidEntry + self.itemViewers[ 'main' ].resetViewer() + + def __changeItemCategory( self, buttonType ): + """ + Purpose: The __changeItemCategory Method properly updates the item + category shown in the item viewer based on the item button that was + clicked by the toon. + + Params: buttonType - the id of the button that was clicked. + Return: None + """ + + if( buttonType == KartDNA.ebType ): + # Disable the Category button and update the viewer text. + self.ebButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerEngineBlocks ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.ebType ) + + elif( buttonType == KartDNA.spType ): + # Disable the Category button and update the viewer text. + self.spButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerSpoilers ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.spType ) + + elif( buttonType == KartDNA.fwwType ): + # Disable the Category button and update the viewer text. + self.fwwButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerFrontWheelWells ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.fwwType ) + + elif( buttonType == KartDNA.bwwType ): + # Disable the Category button and update the viewer text. + self.bwwButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerBackWheelWells ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.bwwType ) + + elif( buttonType == KartDNA.rimsType ): + # Disable the Category button and update the viewer text. + self.rimButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerRims ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.rimsType ) + + elif( buttonType == KartDNA.decalType ): + # Disable the Category button and update the viewer text. + self.decalButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerDecals ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.decalType ) + + elif( buttonType == KartDNA.bodyColor ): + # Disable the category button and update the viewer text + self.paintKartButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerBodyColors) + self.itemViewers[ 'main' ].setupViewer( KartDNA.bodyColor ) + + elif( buttonType == KartDNA.accColor ): + # Disable the Category button and update the viewer text. + self.paintAccessoryButton[ 'state' ] = DGG.DISABLED + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerAccColors ) + self.itemViewers[ 'main' ].setupViewer( KartDNA.accColor ) + elif( buttonType == InvalidEntry ): + self.itemViewers[ 'main' ].setViewerText( TTLocalizer.KartShtikerSelect ) + self.itemViewers[ 'main' ].setupViewer( buttonType ) + else: + raise StandardError, "KartPage.py::__changeItemCategory - INVALID Category Type!" + + # Update the state + if( self.state != buttonType and self.state != InvalidEntry ): + self.buttonDict[ self.state ][ 'state' ] = DGG.NORMAL + self.buttonDict[ self.state ].setColorScale( 1,1,1,1 ) + self.state = buttonType + +class KartViewer( DirectFrame ): + """ + """ + + # Initialize Class Variables + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory( "KartViewer" ) + #notify.setDebug(1) + + def __init__( self, dna, parent ): + """ + """ + assert self.notify.debugStateCall(self) + # Intiailize instance variables + self.kart = None + self.dna = dna + self.parent = parent + self.kartFrame = None + + self.bounds = None + self.colors = None + + self.uiRotateRight = None + self.uiRotateLeft = None + self.uiRotateLabel = None + + + # Construct the DirectFrame Super Class + DirectFrame.__init__( + self, + parent = parent, + relief = None, + pos = (0, 0, 0), + scale = (1.0, 1.0, 1.0), + ) + + def destroy( self ): + """ + """ + assert self.notify.debugStateCall(self) + taskMgr.remove( "kartRotateTask" ) + if( self.kart != None ): + self.kart.delete() + self.kart = None + if( hasattr( self, "kartDisplayRegion" ) ): + self.kartDisplayRegion.unload() + # Destroy UI Components + if hasattr(self, "uiBgFrame"): + self.uiBgFrame.destroy() + del self.uiBgFrame + if hasattr(self, "uiRotateLeft") and self.uiRotateLeft: + self.uiRotateLeft.destroy() + del self.uiRotateLeft + if hasattr(self, "uiRotateRight")and self.uiRotateRight: + self.uiRotateRight.destroy() + del self.uiRotateRight + if hasattr(self, "uiRotateLabelt") and self.uiRotateLabel: + self.uiRotateLabel.destroy() + del self.uiRotateLabel + if hasattr(self, "dna"): + del self.dna + if hasattr(self, "parent"): + del self.parent + + # Destroy DirectFrame Super class + DirectFrame.destroy( self ) + + def load( self, uiRootNode, bgFrame="uiKartViewerFrame1", rightArrow=["rotate_right_up","rotate_right_down","rotate_right_roll","rotate_right_down", (0,0)], leftArrow=["rotate_left_up","rotate_left_down","rotate_left_roll","rotate_left_down", (0,0)],rotatePos=(0,0) ): + """ + """ + assert self.notify.debugStateCall(self) + self.uiBgFrame = DirectFrame( + parent = self, + relief = None, + geom = uiRootNode.find( "**/"+bgFrame), + scale = 1.0, + ) + ''' + if rotatePos: + self.uiRotateLabel = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.KartView_Rotate, + text_scale = 0.05, + scale = 1.0, + text_pos = (rotatePos[0], rotatePos[1], 0), + text_fg = ( 1, 1, 1, 1.0 ), + text_shadow = ( 0, 0, 0, 1 ), + text_font = ToontownGlobals.getSignFont(), + ) + ''' + + if leftArrow and len(leftArrow)==5: + self.uiRotateLeft = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/"+leftArrow[0]), + uiRootNode.find( "**/"+leftArrow[1]), + uiRootNode.find( "**/"+leftArrow[2]), + uiRootNode.find( "**/"+leftArrow[3]), ), + #pos = (0,-.275, -.275), + scale = 1.0, + text = TTLocalizer.KartView_Left, + text_scale = TTLocalizer.KProtateButton, + text_pos = ( leftArrow[4][0], leftArrow[4][1], 0), + text_fg = ( 1, 1, 1, 1.0 ), + text_shadow = ( 0, 0, 0, 1 ), + text_font = ToontownGlobals.getSignFont(), + pressEffect = False, + ) + + self.uiRotateLeft.bind( DGG.B1PRESS, self.__handleKartRotate, [ -3 ] ) + self.uiRotateLeft.bind( DGG.B1RELEASE, self.__endKartRotate ) + if rightArrow and len(rightArrow)==5: + self.uiRotateRight = DirectButton( + parent = self, + relief = None, + geom = ( uiRootNode.find( "**/"+rightArrow[0]), + uiRootNode.find( "**/"+rightArrow[1] ), + uiRootNode.find( "**/"+rightArrow[2]), + uiRootNode.find( "**/"+rightArrow[3] ) ), + #pos = (0,-.275, -.275), + scale = 1.0, + text = TTLocalizer.KartView_Right, + text_scale = TTLocalizer.KProtateButton, + text_pos = ( rightArrow[4][0], rightArrow[4][1], 0), + text_fg = ( 1, 1, 1, 1.0 ), + text_shadow = ( 0, 0, 0, 1 ), + text_font = ToontownGlobals.getSignFont(), + pressEffect = False, + ) + self.uiRotateRight.bind( DGG.B1PRESS, self.__handleKartRotate, [ 3 ] ) + self.uiRotateRight.bind( DGG.B1RELEASE, self.__endKartRotate ) + + + def setBounds( self, *bounds ): + assert len( bounds ) == 4 + self.bounds = bounds + + def setBgColor( self, *colors ): + assert len( colors ) == 4 + self.colors = colors + + def makeKartFrame( self): + assert self.notify.debugStateCall(self) + if( self.kart != None ): + self.kart.delete() + self.kart = None + if( not hasattr( self, "kartDisplayRegion" ) ): + self.kartDisplayRegion = DirectRegion( parent = self ) + apply( self.kartDisplayRegion.setBounds, self.bounds ) + apply( self.kartDisplayRegion.setColor, self.colors ) + + frame = self.kartDisplayRegion.load() + if self.dna: + self.kart = Kart() + self.kart.setDNA( self.dna ) + self.kart.generateKart(forGui = 1 ) + self.kart.setDepthTest( 1 ) + self.kart.setDepthWrite( 1 ) + self.pitch = frame.attachNewNode( 'pitch' ) + self.rotate = self.pitch.attachNewNode( 'rotate' ) + self.scale = self.rotate.attachNewNode( 'scale' ) + self.kart.reparentTo( self.scale ) + # Translate the model to the center + bMin, bMax = self.kart.getKartBounds() + center = ( bMin + bMax ) / 2.0 + self.kart.setPos( -center[ 0 ], -center[ 1 ], -center[ 2 ] ) + self.scale.setScale( 0.5 ) + self.rotate.setH( -35 ) + self.pitch.setP( 0 ) + self.pitch.setY( getKartViewDist( self.kart.getBodyType() ) ) + self.kart.setScale( 1, 1, 1.5 ) + self.kart.setTwoSided( 1 ) + #show left right rotate stuff + if self.uiRotateRight: + self.uiRotateRight.show() + if self.uiRotateLeft: + self.uiRotateLeft.show() + if self.uiRotateLabel: + self.uiRotateLabel.show() + else: + #hide left right rotate stuff + if self.uiRotateRight: + self.uiRotateRight.hide() + if self.uiRotateLeft: + self.uiRotateLeft.hide() + if self.uiRotateLabel: + self.uiRotateLabel.hide() + + return frame + + def show( self, dna=None ): + """ + dna: the kartviewer's dna will be updated to this DNA before display (optional) + """ + assert self.notify.debugStateCall(self) + if( self.kartFrame ): + if( self.kart != None ): + self.kart.delete() + self.kart = None + if( hasattr( self, "kartDisplayRegion" ) ): + self.kartDisplayRegion.unload() + self.hide() + + self.uiBgFrame.show() + + + self.refresh(dna) + #start with rotate - crashing in task on "no attrib pitch" + self.__handleKartRotate(1) + + def hide( self ): + assert self.notify.debugStateCall(self) + self.uiBgFrame.hide() + if( self.kart != None ): + self.kart.delete() + self.kart = None + if( hasattr( self, "kartDisplayRegion" ) ): + # Reset Kart translation to 0 + self.kartDisplayRegion.unload() + + def __handleKartRotate( self, direction, extraArgs = [] ): + """ + """ + taskMgr.add( self.__rotateTask, "kartRotateTask", extraArgs = [ direction ] ) + + def __rotateTask( self, direction ): + if hasattr(self, "pitch"): + self.pitch.setH( self.pitch.getH() + 0.4 * direction ) + return Task.cont + else: + # why spin nothing? + return Task.done + + def __endKartRotate( self, extraArgs = [] ): + """ + """ + taskMgr.remove( "kartRotateTask" ) + + def getKart( self ): + """ + """ + return self.kart + + def setDNA(self, dna): + self.dna = dna + + def refresh(self, dna=None): + assert self.notify.debugStateCall(self) + taskMgr.removeTasksMatching("kartRotateTask") + #optionally can pass in new dna + if dna: + self.dna = dna + #temporarily save pitch + curPitch = 0 + if hasattr(self, "pitch"): + curPitch = self.pitch.getH() + else: + curPitch = 0 + if( self.kart != None ): + self.kart.delete() + self.kart = None + del self.kartFrame + self.kartFrame = self.makeKartFrame() + if hasattr(self, "pitch"): + self.pitch.setH(curPitch) + + +class RacingTrophy(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory("RacingTrophy") + + def __init__(self, level, *args, **kwargs): + # Don't init the fishing trophy class. + opts = {'relief':None} + opts.update(kwargs) + DirectFrame.__init__(self,*args,**opts) + + # This is a temporary location for this model + self.trophy = loader.loadModel("phase_6/models/gui/racingTrophy") + self.trophy.reparentTo(self) + # Fix the model + self.trophy.setPos(0,1,0) + self.trophy.setScale(0.1) + self.base = self.trophy.find("**/trophyBase") + self.column = self.trophy.find("**/trophyColumn") + self.top = self.trophy.find("**/trophyTop") + self.topBase = self.trophy.find("**/trophyTopBase") + self.statue = self.trophy.find("**/trophyStatue") + # Give the base a nice marble look + self.base.setColorScale(1,1,0.8,1) + self.topBase.setColorScale(1,1,0.8,1) + + self.greyBowl = loader.loadModel("phase_6/models/gui/racingTrophyBowl2") + self.greyBowl.reparentTo(self) + self.greyBowl.setPos(0,.5,0) + self.greyBowl.setScale(2.0) + + self.goldBowl = loader.loadModel("phase_6/models/gui/racingTrophyBowl") + self.goldBowl.reparentTo(self) + self.goldBowl.setPos(0,.5,0) + self.goldBowl.setScale(2.0) + self.goldBowlBase = self.goldBowl.find("**/fishingTrophyBase") + self.goldBowlBase.hide() + + self.nameLabel = DirectLabel( + parent = self, + relief = None, + pos = (0,0,-0.15), + text = "", + text_scale = 0.125, + text_fg = Vec4(0.9,0.9,0.4,1), + ) + + self.shadow = loader.loadModel("phase_3/models/props/drop_shadow") + self.shadow.reparentTo(self) + self.shadow.setColor(1,1,1,0.2) + self.shadow.setPosHprScale(0,1,0.35, 0,90,0, 0.1,0.14,0.1) + + self.setLevel(level) + + def setLevel(self, level): + assert self.notify.debugStateCall(self) + self.level = level + if level == -1: + self.trophy.hide() + self.greyBowl.hide() + self.goldBowl.hide() + self.nameLabel.hide() + # Trophies are all a mix-and-match of different attributes. + else: + # Always show the name label. + self.nameLabel.show() + # Show the correct bowl/trophy. + if level < 30 and level % 10 == 9: + self.trophy.hide() + self.goldBowl.hide() + self.greyBowl.show() + self.greyBowl.setScale(8.25,3.5,3.5) + elif level >= 30: + self.trophy.hide() + self.greyBowl.hide() + self.goldBowl.show() + self.goldBowlBase.hide() + # For all other trophies, show the trophy graphic. + else: + self.trophy.show() + self.goldBowl.hide() + self.greyBowl.hide() + # Set the heights for the last three trophies. + # Why they would scale at different rates is beyond me. + if level == 30: + self.goldBowl.setScale(4.4,3.1,3.1) + #self.column.setScale(1.3229, 1.26468, 2.11878) + #self.top.setPos(0,0,0.5) + elif level == 31: + #self.column.setScale(1.3229, 1.26468, 2.61878) + #self.top.setPos(0,0,0.5) + self.goldBowl.setScale(3.6,3.5,3.5) + elif level >= 32: + #self.column.setScale(1.3229, 1.26468, 3.11878) + #self.top.setPos(0,0,0.5) + self.goldBowl.setScale(5.6,3.9,3.9) + # Set the color. + if level % 10 == 9: + # All the last ones in a row are already properly colored. + pass + elif (level % 10) % 3 == 0: + self.column.setScale(1.3229, 1.26468, 1.11878) + self.top.setPos(0,0,-1) + self.__bronze() + elif (level % 10) % 3 == 1: + self.column.setScale(1.3229, 1.26468, 1.61878) + self.top.setPos(0,0,-.5) + self.__silver() + elif (level % 10) % 3 == 2: + self.column.setScale(1.3229, 1.26468, 2.11878) + self.top.setPos(0,0,0) + self.__gold() + # Set the column color. + if level < 10: + self.__tealColumn() + elif level < 20: + self.__purpleColumn() + elif level < 30: + self.__blueColumn() + else: + self.__redColumn() + + # Methods to color the trophy statue + def __bronze(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(0.9,0.6,0.33,1) + + def __silver(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(0.9,0.9,1,1) + + def __gold(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(1,0.95,0.1,1) + + def __platinum(self): + assert self.notify.debugStateCall(self) + self.statue.setColorScale(1,0.95,0.1,1) + + # Methods to color the trophy column + def __tealColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(.5,1.2,.85,1) + + def __purpleColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1,.7,.95,1) + + def __redColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1.2,.6,.6,1) + + def __yellowColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(1,1,.8,1) + + def __blueColumn(self): + assert self.notify.debugStateCall(self) + self.column.setColorScale(.6,.75,1.2,1) + + def destroy(self): + assert self.notify.debugStateCall(self) + self.trophy.removeNode() + self.goldBowl.removeNode() + self.greyBowl.removeNode() + self.shadow.removeNode() + DirectFrame.destroy(self) diff --git a/toontown/src/shtiker/MapPage.py b/toontown/src/shtiker/MapPage.py new file mode 100644 index 0000000..be72c31 --- /dev/null +++ b/toontown/src/shtiker/MapPage.py @@ -0,0 +1,318 @@ +"""MapPage module: contains the MapPage class""" + +import ShtikerPage +from toontown.toonbase import ToontownGlobals +from direct.showbase import PythonUtil +from toontown.hood import ZoneUtil +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class MapPage(ShtikerPage.ShtikerPage): + """MapPage class""" + + def __init__(self): + """__init__(self) + MapPage constructor: create the map page and all the happy hotspots + """ + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + + mapModel = loader.loadModel("phase_3.5/models/gui/toontown_map") + self.map = DirectFrame( + parent = self, + relief = None, + image = mapModel.find("**/toontown_map"), + image_scale = (1.8,1,1.35), + scale = 0.97, + pos = (0,0,0.0775), + ) + mapModel.removeNode() + + # Make the hotspots and clouds + self.allZones = [] + # useful for this to be a list + for hood in ToontownGlobals.Hoods: + if hood != ToontownGlobals.GolfZone: + self.allZones.append(hood) + + self.cloudScaleList = ( ((0.55, 0, 0.4), (0.35, 0, 0.25)), + (), + ((0.45, 0, 0.45), (0.5, 0, 0.4),), + ((0.7, 0, 0.45),), + ((0.55, 0, 0.4),), + ((0.6, 0, 0.4), (0.5332, 0, 0.32)), + (), + ((0.7, 0, 0.45),(0.7, 0, 0.45),), + ((0.7998, 0, 0.39),), + ((0.5, 0, 0.4),), # boss + ((-0.45, 0, 0.4),), # sell + ((-0.45, 0, 0.35),), # cash + ((0.5, 0, 0.35),), # law + ((0.5, 0, 0.35),), # golf + ) + + self.cloudPosList = ( ((0.575, 0., -0.04), (0.45, 0., -0.25)), + (), + ((0.375, 0., 0.4), (0.5625, 0., 0.2)), + ((-0.02, 0., 0.23),), + ((-0.3, 0., -0.4),), + ((0.25, 0., -0.425), (0.125, 0., -0.36)), + (), + ((-0.5625, 0., -0.07),(-0.45, 0., 0.2125),), + ((-0.125, 0., 0.5),), + ((0.66, 0., -0.4),), # boss + ((-0.68, 0., -0.444),), # sell + ((-0.6, 0., 0.45),), # cash + ((0.66, 0., 0.5),), # law + ((0.40, 0., -0.35),), # golf + ) + self.labelPosList = ( (0.594, 0., -0.075), + (0., 0., -0.1), + (0.475, 0., 0.25), + (0.1, 0., 0.15), + (-0.3, 0., -0.375), + (0.2, 0., -0.45), + (-0.438, 0., 0.22), + (-0.55, 0., 0.0), + (-0.088, 0., 0.47), + (0.7, 0., -0.5), # Bossbot HQ + (-0.7, 0., -0.5), # Sellbot HQ + (-0.7, 0., 0.5), # Cashbot HQ + (0.7, 0. , 0.5), # Lawbot HQ + (0.45, 0. , -0.45), # golf zone + ) + + self.labels = [] + self.clouds = [] + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + buttonLoc = (0.45, 0, -0.74) + if base.housingEnabled: + # this is the location when the go home button is enabled + buttonLoc = (0.55,0,-0.74) + + self.safeZoneButton = DirectButton( + parent = self.map, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (1.3,1.1,1.1), + pos = buttonLoc, + text = TTLocalizer.MapPageBackToPlayground, + text_scale = TTLocalizer.MPbackToPlayground, + text_pos = (0,-0.02), + textMayChange = 0, + command = self.backToSafeZone, + ) + + self.goHomeButton = DirectButton( + parent = self.map, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (.66,1.1,1.1), + pos = (0.15,0,-.74), + text = TTLocalizer.MapPageGoHome, + text_scale = TTLocalizer.MPgoHome, + text_pos = (0,-0.02), + textMayChange = 0, + command = self.goHome, + ) + self.goHomeButton.hide() + guiButton.removeNode() + + self.hoodLabel = DirectLabel( + parent = self.map, + relief = None, + pos = (-0.43,0,-0.726), + text = "", + text_scale = TTLocalizer.MPhoodLabel, + text_pos = (0,0), + text_wordwrap = TTLocalizer.MPhoodWordwrap, + ) + self.hoodLabel.hide() + + # load the cloud graphic + cloudModel = loader.loadModel("phase_3.5/models/gui/cloud") + cloudImage = cloudModel.find("**/cloud") + + for hood in self.allZones: + abbrev = base.cr.hoodMgr.getNameFromId(hood) + fullname = base.cr.hoodMgr.getFullnameFromId(hood) + hoodIndex = self.allZones.index(hood) + + # Ok, this really wants to be a label, but labels have no + # rollover behavior. So I'll make a button with no callback + # and no click sounds to simulate a label with rollover + label = DirectButton( + parent = self.map, + relief = None, + pos = self.labelPosList[hoodIndex], + pad = (0.2, 0.16), + text = ("", fullname, fullname), + text_bg = Vec4(1,1,1,0.4), + text_scale = 0.055, + text_wordwrap = 8, + rolloverSound = None, + clickSound = None, + pressEffect = 0, + command = self.__buttonCallback, + extraArgs = [hood], + ) + label.resetFrameSize() + self.labels.append(label) + + hoodClouds = [] + for cloudScale, cloudPos in zip(self.cloudScaleList[hoodIndex], + self.cloudPosList[hoodIndex]): + cloud = DirectFrame( + parent = self.map, + relief = None, + state = DGG.DISABLED, # No mouse region needed + image = cloudImage, + scale = (cloudScale[0], + cloudScale[1], + cloudScale[2]), + pos = (cloudPos[0], + cloudPos[1], + cloudPos[2]), + ) + cloud.hide() + hoodClouds.append(cloud) + self.clouds.append(hoodClouds) + + cloudModel.removeNode() + self.resetFrameSize() + return + + def unload(self): + for labelButton in self.labels: + labelButton.destroy() + del self.labels + del self.clouds + self.safeZoneButton.destroy() + self.goHomeButton.destroy() + ShtikerPage.ShtikerPage.unload(self) + + def enter(self): + ShtikerPage.ShtikerPage.enter(self) + # Get the current street name + try: + zone = base.cr.playGame.getPlace().getZoneId() + except: + zone = 0 + + # if we are going to an HQ, change the button + if base.localAvatar.lastHood >= ToontownGlobals.BossbotHQ: + self.safeZoneButton['text'] = TTLocalizer.MapPageBackToCogHQ + else: + self.safeZoneButton['text'] = TTLocalizer.MapPageBackToPlayground + + if ((zone and ZoneUtil.isPlayground(zone)) or self.book.safeMode): + self.safeZoneButton.hide() + else: + self.safeZoneButton.show() + + if ((base.cr.playGame.getPlaceId() == ToontownGlobals.MyEstate and + base.cr.playGame.hood.loader.atMyEstate()) or + self.book.safeMode): + self.goHomeButton.hide() + elif base.housingEnabled: + self.goHomeButton.show() + + if base.cr.playGame.getPlaceId() == ToontownGlobals.MyEstate: + if base.cr.playGame.hood.loader.atMyEstate(): + self.hoodLabel['text'] = (TTLocalizer.MapPageYouAreAtHome) + self.hoodLabel.show() + else: + avatar = base.cr.identifyAvatar(base.cr.playGame.hood.loader.estateOwnerId) + if avatar: + avName = avatar.getName() + self.hoodLabel['text'] = (TTLocalizer.MapPageYouAreAtSomeonesHome % (TTLocalizer.GetPossesive(avName))) + self.hoodLabel.show() + elif zone: + hoodName = ToontownGlobals.hoodNameMap.get(ZoneUtil.getCanonicalHoodId(zone), ("",))[-1] + streetName = ToontownGlobals.StreetNames.get(ZoneUtil.getCanonicalBranchZone(zone), ("",))[-1] + # Make sure we have a hoodName to report + # It is ok if we do not have a street name, it will just be empty + if hoodName: + self.hoodLabel['text'] = (TTLocalizer.MapPageYouAreHere % + (hoodName, streetName)) + self.hoodLabel.show() + else: + self.hoodLabel.hide() + else: + self.hoodLabel.hide() + + safeZonesVisited = base.localAvatar.hoodsVisited + hoodsAvailable = base.cr.hoodMgr.getAvailableZones() + + #print "### hoods visited = ", safeZonesVisited + #print "### hoods avail = ", hoodsAvailable + + # The hoods that we can see is the intersection of the zones we have + # visited and the hoods that are available + hoodVisibleList = PythonUtil.intersection(safeZonesVisited, hoodsAvailable) + + #print "### hoods viz = ", hoodVisibleList + + # The hoods that we can teleport to is the intersection of the hoods + # we can see and the hoods the local toon has teleport access to + hoodTeleportList = base.localAvatar.getTeleportAccess() + + for hood in self.allZones: + label = self.labels[self.allZones.index(hood)] + clouds = self.clouds[self.allZones.index(hood)] + # If we can see that hood, show the button, hide the clouds + if ((not self.book.safeMode) and + (hood in hoodVisibleList)): + label.show() + for cloud in clouds: + cloud.hide() + fullname = base.cr.hoodMgr.getFullnameFromId(hood) + if hood in hoodTeleportList: + text = (TTLocalizer.MapPageGoTo % fullname) + label['text'] = ("", text, text) + else: + label['text'] = ("", fullname, fullname) + + # If we cannot see that hood, hide the button, show the clouds + else: + label.hide() + for cloud in clouds: + cloud.show() + + return + + def exit(self): + ShtikerPage.ShtikerPage.exit(self) + + def backToSafeZone(self): + self.doneStatus = {"mode" : "teleport", + "hood" : base.localAvatar.lastHood, + } + messenger.send(self.doneEvent) + + def goHome(self): + self.doneStatus = {"mode" : "gohome", + "hood" : base.localAvatar.lastHood, + } + messenger.send(self.doneEvent) + + def __buttonCallback(self, hood): + """ + a hood has been selected + """ + if (hood in base.localAvatar.getTeleportAccess()): + self.doneStatus = {"mode" : "teleport", + "hood" : hood, + } + messenger.send(self.doneEvent) diff --git a/toontown/src/shtiker/NPCFriendPage.py b/toontown/src/shtiker/NPCFriendPage.py new file mode 100644 index 0000000..62d31ce --- /dev/null +++ b/toontown/src/shtiker/NPCFriendPage.py @@ -0,0 +1,46 @@ +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toon import NPCFriendPanel +from toontown.toonbase import TTLocalizer + +class NPCFriendPage(ShtikerPage.ShtikerPage): + + # special methods + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.NPCFriendPageTitle, + text_scale = 0.12, + textMayChange = 0, + pos = (0,0,0.6), + ) + self.friendPanel = NPCFriendPanel.NPCFriendPanel(parent = self) + self.friendPanel.setScale(0.1225) + self.friendPanel.setZ(-0.03) + + def unload(self): + ShtikerPage.ShtikerPage.unload(self) + + del self.title + del self.friendPanel + + def updatePage(self): + self.friendPanel.update(base.localAvatar.NPCFriendsDict, + fCallable = 0) + + def enter(self): + """enter(self) + """ + self.updatePage() + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + """exit(self) + """ + ShtikerPage.ShtikerPage.exit(self) + diff --git a/toontown/src/shtiker/NewbiePurchaseManager.py b/toontown/src/shtiker/NewbiePurchaseManager.py new file mode 100644 index 0000000..118229b --- /dev/null +++ b/toontown/src/shtiker/NewbiePurchaseManager.py @@ -0,0 +1,60 @@ +import PurchaseManager +from toontown.quest import QuestParser +from toontown.toon import NPCToons + +""" +npm=base.cr.doFind('Newbie') + +from toontown.quest import QuestParser +QuestParser.lineDict = {} +QuestParser.readFile(QuestParser.scriptFile) + +npm.playMovie() +""" + +class NewbiePurchaseManager(PurchaseManager.PurchaseManager): + def setOwnedNewbieId(self, ownedNewbieId): + self.ownedNewbieId = ownedNewbieId + + def calcHasLocalToon(self): + return base.localAvatar.doId == self.ownedNewbieId + + def announceGenerate(self): + PurchaseManager.PurchaseManager.announceGenerate(self) + if self.hasLocalToon: + self.npc = NPCToons.createLocalNPC(2011) + self.npc.addActive() + # the quest parser wants to call some distributedToon funcs + def getDoId(): return 0 + self.npc.getDoId = getDoId + def acquireDelayDelete(name): return serialNum() + self.npc.acquireDelayDelete = acquireDelayDelete + def releaseDelayDelete(token): pass + self.npc.releaseDelayDelete = releaseDelayDelete + def uniqueName(string): return string + self.npc.uniqueName = uniqueName + + self.accept('gagScreenIsUp', self.playMovie) + # ick. + self.purchase = base.cr.playGame.hood.purchase + self.purchase.enterTutorialMode(self.ownedNewbieId) + + def disable(self): + PurchaseManager.PurchaseManager.disable(self) + if hasattr(self, 'movie'): + self.npc.removeActive() + self.npc.delete() + del self.npc + del self.movie + + def playMovie(self): + self.movie = QuestParser.NPCMoviePlayer("gag_intro", + base.localAvatar, + self.npc) + self.movie.setVar('backToPlaygroundButton', + self.purchase.backToPlayground) + self.movie.setVar('playAgainButton', + self.purchase.playAgain) + self.movie.setVar('purchaseBg', + self.purchase.bg) + self.movie.play() diff --git a/toontown/src/shtiker/NewbiePurchaseManagerAI.py b/toontown/src/shtiker/NewbiePurchaseManagerAI.py new file mode 100644 index 0000000..becf021 --- /dev/null +++ b/toontown/src/shtiker/NewbiePurchaseManagerAI.py @@ -0,0 +1,28 @@ +import PurchaseManagerAI + +class NewbiePurchaseManagerAI(PurchaseManagerAI.PurchaseManagerAI): + + def __init__(self, air, newbieId, playerArray, mpArray, previousMinigameId, + trolleyZone): + self.ownedNewbieId = newbieId + # newbie PMs have an empty newbie list + newbieList = [] + PurchaseManagerAI.PurchaseManagerAI.__init__( + self, air, playerArray, mpArray, previousMinigameId, + trolleyZone, newbieList) + + # newbie purchase screen has no timeout + def startCountdown(self): + pass + + def getOwnedNewbieId(self): + return self.ownedNewbieId + + def getInvolvedPlayerIds(self): + """ only one newbie """ + return [self.ownedNewbieId] + + def handlePlayerLeaving(self, avId): + toon = self.air.doId2do.get(avId) + if toon: + self.air.questManager.toonRodeTrolleyFirstTime(toon) diff --git a/toontown/src/shtiker/NewsOverHttpGlobals.py b/toontown/src/shtiker/NewsOverHttpGlobals.py new file mode 100644 index 0000000..4be0f85 --- /dev/null +++ b/toontown/src/shtiker/NewsOverHttpGlobals.py @@ -0,0 +1,2 @@ +# DRose says use a global flag to tell thread to return immediately and stop the crash +StopRedownloadTask = False diff --git a/toontown/src/shtiker/NewsPage.py b/toontown/src/shtiker/NewsPage.py new file mode 100644 index 0000000..660898d --- /dev/null +++ b/toontown/src/shtiker/NewsPage.py @@ -0,0 +1,106 @@ +from direct.fsm import StateData +from direct.gui.DirectGui import DirectFrame +from direct.gui.DirectGui import DGG +from direct.gui.DirectGui import DirectLabel +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.shtiker import ShtikerPage +from toontown.toonbase import TTLocalizer + +UseDirectNewsFrame = config.GetBool("use-direct-news-frame", True) +HaveNewsFrame = True +if UseDirectNewsFrame: + # we are using a news page that does not use awesomium or a browser + from toontown.shtiker import DirectNewsFrame +else: + try: + from toontown.shtiker import InGameNewsFrame + except : + HaveNewsFrame = False + +class NewsPage(ShtikerPage.ShtikerPage): + """ + NewsPage shows the in game nes. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("NewsPage") + + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + + + def load(self): + self.noNewsLabel = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.NewsPageImportError , + text_scale = 0.12, + ) + + if HaveNewsFrame: + if UseDirectNewsFrame: + import datetime + start = datetime.datetime.now() + self.newsFrame = DirectNewsFrame.DirectNewsFrame(parent = self) + ending = datetime.datetime.now() + self.notify.info("time to load news = %s" % str(ending-start)) + else: + self.newsFrame = InGameNewsFrame.InGameNewsFrame(parent = self) + # this forces a preload of the news web site + self.newsFrame.activate() + + + def unload(self): + if HaveNewsFrame: + self.newsFrame.unload() + del self.newsFrame + + def clearPage(self): + return + + def updatePage(self): + return + + def enter(self): + """enter(self) + """ + self.updatePage() + ShtikerPage.ShtikerPage.enter(self) + # don't let user click on the buttons under the news page + if HaveNewsFrame: + if self.book: + # hide the previous arrow + self.book.prevArrow.hide() + self.book.disableAllPageTabs() + self.newsFrame.activate() + # turn off the cells that obstruct + base.setCellsAvailable(base.leftCells, 0) + base.setCellsAvailable([base.rightCells[1]], 0) + localAvatar.book.bookCloseButton.hide() + localAvatar.setLastTimeReadNews(base.cr.toontownTimeManager.getCurServerDateTime()) + return + + def exit(self): + """exit(self) + """ + self.clearPage() + if self.book: + self.book.prevArrow.show() + self.book.enableAllPageTabs() + ShtikerPage.ShtikerPage.exit(self) + if HaveNewsFrame: + self.newsFrame.deactivate() + base.setCellsAvailable(base.leftCells, 1) + base.setCellsAvailable([base.rightCells[1]], 1) + if localAvatar.book.shouldBookButtonBeHidden(): + localAvatar.book.bookCloseButton.hide() + else: + localAvatar.book.bookCloseButton.show() + return + + def doSnapshot(self): + """Save our current browser page as png file.""" + if HaveNewsFrame: + return self.newsFrame.doSnapshot() + else: + return "No News Frame" diff --git a/toontown/src/shtiker/OptionsPage.py b/toontown/src/shtiker/OptionsPage.py new file mode 100644 index 0000000..02e5e67 --- /dev/null +++ b/toontown/src/shtiker/OptionsPage.py @@ -0,0 +1,1264 @@ +"""OptionsPage module: contains the OptionsPage class""" + +from pandac.PandaModules import * +import ShtikerPage +from toontown.toontowngui import TTDialog +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +import DisplaySettingsDialog +from direct.task import Task +from otp.speedchat import SpeedChat +from otp.speedchat import SCColorScheme +from otp.speedchat import SCStaticTextTerminal +from direct.showbase import PythonUtil +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals + +# array of the possible speedChatStyles and colors to use +# R,G,B for arrow, rollover, and frame color if we want to specify it +# the first parameter refers to the key in the SpeedChatStaticText variable +# in the localizer +# framecolor must be included so that the talk-bubble backgrounds are correct +speedChatStyles = ( + # Purple + (2000, (200/255., 60/255., 229/255.), + (200/255., 135/255., 255/255.), + (220/255., 195/255., 229/255.)), + # Blue + (2001, ( 0/255., 0/255., 255/255.), + (140/255., 150/255., 235/255.), + (201/255., 215/255., 255/255.)), + # Cyan + (2002, ( 90/255., 175/255., 225/255.), + (120/255., 215/255., 255/255.), + (208/255., 230/255., 250/255.)), + # Teal + (2003, (130/255., 235/255., 235/255.), + (120/255., 225/255., 225/255.), + (234/255., 255/255., 255/255.)), + # Green + (2004, ( 0/255., 200/255., 70/255.), + ( 0/255., 200/255., 80/255.), + (204/255., 255/255., 204/255.)), + # Yellow + (2005, (235/255., 230/255., 0/255.), + (255/255., 250/255., 100/255.), + (255/255., 250/255., 204/255.)), + # Orange + (2006, (255/255., 153/255., 0/255.), + (229/255., 147/255., 0/255.), + (255/255., 234/255., 204/255.)), + # Red + (2007, (255/255., 0/255., 50/255.), + (229/255., 0/255., 50/255.), + (255/255., 204/255., 204/255.)), + # Pink + (2008, (255/255., 153/255., 193/255.), + (240/255., 157/255., 192/255.), + (255/255., 215/255., 238/255.)), + # Brown + (2009, (170/255., 120/255., 20/255.), + (165/255., 120/255., 50/255.), + (210/255., 200/255., 180/255.)), + ) + +########################################################################## +# Global Variables and Enumerations +########################################################################## +PageMode = PythonUtil.Enum("Options, Codes") + +class OptionsPage(ShtikerPage.ShtikerPage): + """OptionsPage class""" + + notify = DirectNotifyGlobal.directNotify.newCategory("OptionsPage") + + # special methods + def __init__(self): + """__init__(self) + OptionsPage constructor: create the options page + """ + ShtikerPage.ShtikerPage.__init__(self) + + if __debug__: + base.op = self + + def load(self): + assert self.notify.debugStateCall(self) + ShtikerPage.ShtikerPage.load(self) + + # Create the OptionsTabPage + self.optionsTabPage = OptionsTabPage(self) + self.optionsTabPage.hide() + + # Create the CodesTabPage + self.codesTabPage = CodesTabPage(self) + self.codesTabPage.hide() + + titleHeight = 0.61 # bigger number means higher the title + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.OptionsPageTitle, + text_scale = 0.12, + pos = (0,0,titleHeight), + ) + + # The blue and yellow colors are trying to match the + # rollover and select colors on the options page: + normalColor = (1, 1, 1, 1) + clickColor = (.8, .8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + + # Load the Fish Page to borrow its tabs + gui = loader.loadModel( "phase_3.5/models/gui/fishingBook" ) + + self.optionsTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.OptionsPageTitle, + text_scale = TTLocalizer.OPoptionsTab, + text_align = TextNode.ALeft, + text_pos = (0.01, 0.0, 0.0), + image = gui.find("**/tabs/polySurface1"), + image_pos = (0.55,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [PageMode.Options], + pos = (-0.36, 0, 0.77), + ) + + self.codesTab = DirectButton( + parent = self, + relief = None, + text = TTLocalizer.OptionsPageCodesTab, + text_scale = TTLocalizer.OPoptionsTab, + text_align = TextNode.ALeft, + text_pos = (-0.035, 0.0, 0.0), + image = gui.find("**/tabs/polySurface2"), + image_pos = (0.12,1,-0.91), + image_hpr = (0,0,-90), + image_scale = (0.033,0.033,0.035), + image_color = normalColor, + image1_color = clickColor, + image2_color = rolloverColor, + image3_color = diabledColor, + text_fg = Vec4(0.2,0.1,0,1), + command = self.setMode, + extraArgs = [PageMode.Codes], + pos = (0.11, 0, 0.77), + ) + + def enter(self): + assert self.notify.debugStateCall(self) + + # Default to the Options Page. + self.setMode(PageMode.Options, updateAnyways = 1) + + # Make the call to the superclass enter method. + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + assert self.notify.debugStateCall(self) + self.optionsTabPage.exit() + self.codesTabPage.exit() + + # Make the call to the superclass exit method. + ShtikerPage.ShtikerPage.exit(self) + + def unload(self): + assert self.notify.debugStateCall(self) + self.optionsTabPage.unload() + + del self.title + + # Make the call to the superclass unload method. + ShtikerPage.ShtikerPage.unload(self) + + def setMode(self, mode, updateAnyways = 0): + """ + Purpose: The setMode Method sets the current mode of the OptionsPage + of the Shtiker Book. + Params: mode - the new mode. + updateAnyways - update the page based on the mode. + Return: None + """ + messenger.send('wakeup') + if (not updateAnyways): + if(self.mode == mode): + return + else: + self.mode = mode + + if (mode == PageMode.Options): + self.mode = PageMode.Options + self.title['text'] = TTLocalizer.OptionsPageTitle + self.optionsTab['state'] = DGG.DISABLED + self.optionsTabPage.enter() + self.codesTab['state'] = DGG.NORMAL + self.codesTabPage.exit() + + elif(mode == PageMode.Codes): + self.mode = PageMode.Codes + self.title['text'] = TTLocalizer.CdrPageTitle + self.optionsTab['state'] = DGG.NORMAL + self.optionsTabPage.exit() + self.codesTab['state'] = DGG.DISABLED + self.codesTabPage.enter() + + else: + raise StandardError, "OptionsPage::setMode - Invalid Mode %s" % (mode) + + +class OptionsTabPage(DirectFrame): + """ + Purpose: The OptionsTabPage class initializes the user interface for + the Options Tab. We are splitting the Options Page into the Options Tab + and the Code Redemption Tab. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory("OptionsTabPage") + + # When the user changes the display settings, even after he goes + # through the "yes I approve of this new setting" hoopla, we don't + # write the new setting to disk immediately, just in case it will + # crash as soon as he starts to render a real scene. Rather, we + # write it out after a certain amount of time has elapsed after he + # leaves the options page. These parameters specify the name of + # the doLater task and the amount of time in seconds to wait. + DisplaySettingsTaskName = "save-display-settings" + DisplaySettingsDelay = 60 + + # If this variable is set to false, we're not allowed to change + # the display settings using this interface, except for the screen + # resolution. + ChangeDisplaySettings = base.config.GetBool('change-display-settings', 1) + ChangeDisplayAPI = base.config.GetBool('change-display-api', 0) + + # This maps our expected API interfaces to a symbolic constant in the settings file. + DisplaySettingsApiMap = { + 'OpenGL' : Settings.GL, + 'DirectX7' : Settings.DX7, + 'DirectX8' : Settings.DX8 + } + + def __init__(self, parent = aspect2d): + """ + Purpose: The __init__ Method provides the initial construction of + the OptionsTabPage object that will provide the base interface + for the Options Page. + Params: None + Return: None + """ + self.parent = parent + self.currentSizeIndex = None + # Construct the super class object from which the selector derives. + DirectFrame.__init__( + self, + parent = self.parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + self.load() + + def destroy(self): + """ + Purpose: The destroy Method properly handles the destruction of + the OptionsTabPage instance by handling appropriate reference + cleanup. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + # Destroy UI Components of the CustomizeUI + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + self.parent = None + + # Destroy the DirectFrame super class. + DirectFrame.destroy(self) + + def load(self): + """ + Purpose: The load Method handles the construction of the specific + UI components that make up the OptionsTabPage object. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + self.displaySettings = None + + # These properties are set when we come back from looking at + # the DisplaySettings page. At some later point, we'll + # actually save these to the Settings file. + self.displaySettingsChanged = 0 + self.displaySettingsSize = (None, None) + self.displaySettingsFullscreen = None + self.displaySettingsEmbedded = None + self.displaySettingsApi = None + self.displaySettingsApiChanged = 0 + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + # tweak these constants to change the layout + # the coordinate system is (0,0) in the middle of the page + # Vertical: -1.0 is the bottom, and 1.0 is the top of the screen + # Horizontal: -1.0 is left edge of shticker book, 1.0 is right edge + titleHeight = 0.61 # bigger number means higher the title + textStartHeight = 0.45 # bigger number means higher text + textRowHeight = 0.15 # bigger number means more space between rows + leftMargin = -0.72 # smaller number means farther left + buttonbase_xcoord = 0.35 # bigger number means farther right + buttonbase_ycoord = 0.45 # bigger number means higher buttons + button_image_scale = (0.7,1,1) + button_textpos = (0,-0.02) + options_text_scale = 0.052 + disabled_arrow_color = Vec4(0.6, 0.6, 0.6, 1.0) # Make the disabled button darker + self.speed_chat_scale = 0.055 + + self.Music_Label = DirectLabel( + parent = self, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = options_text_scale, + pos = (leftMargin, 0, + textStartHeight), + ) + + self.SoundFX_Label = DirectLabel( + parent = self, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = options_text_scale, + text_wordwrap = 16, + pos = (leftMargin, 0, + textStartHeight - textRowHeight), + ) + + self.Friends_Label = DirectLabel( + parent = self, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = options_text_scale, + text_wordwrap = 16, + # adjust for taller two-row text + pos = (leftMargin, 0, + textStartHeight - 3 * textRowHeight), + ) + + self.DisplaySettings_Label = DirectLabel( + parent = self, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = options_text_scale, + text_wordwrap = 10, + pos = (leftMargin, 0, + textStartHeight - 4 * textRowHeight), + ) + + self.SpeedChatStyle_Label = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.OptionsPageSpeedChatStyleLabel, + text_align = TextNode.ALeft, + text_scale = options_text_scale, + text_wordwrap = 10, + pos = (leftMargin, 0, + textStartHeight - 5 * textRowHeight), + ) + + self.ToonChatSounds_Label = DirectLabel( + parent = self, + relief = None, + text = "", + text_align = TextNode.ALeft, + text_scale = options_text_scale, + text_wordwrap = 15, + pos = (leftMargin, 0, + textStartHeight - (2 * textRowHeight) + 0.025), + ) + self.ToonChatSounds_Label.setScale(0.9) + + self.Music_toggleButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = button_image_scale, + text = "", + text_scale = options_text_scale, + text_pos = button_textpos, + pos = (buttonbase_xcoord, 0.0, buttonbase_ycoord), + command = self.__doToggleMusic, + ) + + self.SoundFX_toggleButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = button_image_scale, + text = "", + text_scale = options_text_scale, + text_pos = button_textpos, + pos = (buttonbase_xcoord, 0.0, buttonbase_ycoord-textRowHeight), + command = self.__doToggleSfx, + ) + + self.Friends_toggleButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = button_image_scale, + text = "", + text_scale = options_text_scale, + text_pos = button_textpos, + pos = (buttonbase_xcoord, 0.0, buttonbase_ycoord - textRowHeight*3), + command = self.__doToggleAcceptFriends, + ) + + self.DisplaySettingsButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image3_color = Vec4(0.5, 0.5, 0.5, 0.5), + image_scale = button_image_scale, + text = TTLocalizer.OptionsPageChange, + text3_fg = (0.5, 0.5, 0.5, 0.75), + text_scale = options_text_scale, + text_pos = button_textpos, + pos = (buttonbase_xcoord, 0.0, + buttonbase_ycoord - textRowHeight * 4), + command = self.__doDisplaySettings, + ) + + self.speedChatStyleLeftArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + # make the disabled color more transparent + image3_color = Vec4(1, 1, 1, 0.5), + scale = (-1.0, 1.0, 1.0), # make the arrow point left + pos = (0.25, 0, buttonbase_ycoord - textRowHeight * 5), + command = self.__doSpeedChatStyleLeft, + ) + + self.speedChatStyleRightArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + # make the disabled color more transparent + image3_color = Vec4(1, 1, 1, 0.5), + pos = (0.65, 0, buttonbase_ycoord - textRowHeight * 5), + command = self.__doSpeedChatStyleRight, + ) + + self.ToonChatSounds_toggleButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + guiButton.find("**/QuitBtn_UP"), + ), + image3_color = Vec4(0.5, 0.5, 0.5, 0.5), + image_scale = button_image_scale, + text = "", + text3_fg = (0.5, 0.5, 0.5, 0.75), + text_scale = options_text_scale, + text_pos = button_textpos, + pos = (buttonbase_xcoord, 0.0, (buttonbase_ycoord-textRowHeight * 2) + 0.025), + command = self.__doToggleToonChatSounds, + ) + self.ToonChatSounds_toggleButton.setScale(0.8) + + # The [2000] refers to the default color. In the localizer, refer + # to the SpeedChatStaticText variable + self.speedChatStyleText = SpeedChat.SpeedChat( + name='OptionsPageStyleText', structure=[2000], + backgroundModelName='phase_3/models/gui/ChatPanel', + guiModelName='phase_3.5/models/gui/speedChatGui') + self.speedChatStyleText.setScale(self.speed_chat_scale) + # This will be horizontally centered later + self.speedChatStyleText.setPos(0.37, 0, -0.27) + self.speedChatStyleText.reparentTo(self, DGG.FOREGROUND_SORT_INDEX) + + self.exitButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = 1.15, + text = TTLocalizer.OptionsPageExitToontown, + text_scale = options_text_scale, + text_pos = button_textpos, + textMayChange = 0, + pos = (0.45,0,-0.6), + command = self.__handleExitShowWithConfirm, + ) + + guiButton.removeNode() + gui.removeNode() + + def enter(self): + """ + Purpose: This method gets called when the Options Tab is selected. + Also, this is the default tab in this page and gets selected by default + everytime the Options and Codes page is selected. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + self.show() + + # If we haven't yet saved the display settings we set last + # time, stop the task until we leave the page again. + taskMgr.remove(self.DisplaySettingsTaskName) + self.settingsChanged = 0 + + self.__setMusicButton() + self.__setSoundFXButton() + self.__setAcceptFriendsButton() + self.__setDisplaySettings() + self.__setToonChatSoundsButton() + + self.speedChatStyleText.enter() + # get the proper index for the speedChatStyle and set it + # this function is actually found in DistributedToon.py + self.speedChatStyleIndex = base.localAvatar.getSpeedChatStyleIndex() + self.updateSpeedChatStyle() + + if self.parent.book.safeMode: + self.exitButton.hide() + else: + self.exitButton.show() + + def exit(self): + assert self.notify.debugStateCall(self) + self.hide() + if(self.settingsChanged != 0): + Settings.writeSettings() + + self.speedChatStyleText.exit() + + if self.displaySettingsChanged: + # If we have changed the display settings, then spawn a + # task to write the new changes to the SettingsFile in + # about a minute or so. We do this just to be paranoid, + # to give the user a chance to crash first in case it's + # going to. + taskMgr.doMethodLater(self.DisplaySettingsDelay, + self.writeDisplaySettings, + self.DisplaySettingsTaskName) + + def unload(self): + assert self.notify.debugStateCall(self) + # Now that we're unloading, we're confident the user is + # exiting the game through one of the normal, + # non-graphics-driver crashing interfaces: either by clicking + # the exit or logout button, Alt-F4'ing the window, or + # experiencing a Python exception. In any of these cases, + # we'll go ahead and save the display settings if they haven't + # been saved yet. + self.writeDisplaySettings() + + taskMgr.remove(self.DisplaySettingsTaskName) + if self.displaySettings != None: + self.ignore(self.displaySettings.doneEvent) + self.displaySettings.unload() + self.displaySettings = None + self.exitButton.destroy() + self.Music_toggleButton.destroy() + self.SoundFX_toggleButton.destroy() + self.Friends_toggleButton.destroy() + self.DisplaySettingsButton.destroy() + self.speedChatStyleLeftArrow.destroy() + self.speedChatStyleRightArrow.destroy() + del self.exitButton + del self.SoundFX_Label + del self.Music_Label + del self.Friends_Label + del self.SpeedChatStyle_Label + del self.SoundFX_toggleButton + del self.Music_toggleButton + del self.Friends_toggleButton + del self.speedChatStyleLeftArrow + del self.speedChatStyleRightArrow + self.speedChatStyleText.exit() + self.speedChatStyleText.destroy() + del self.speedChatStyleText + self.currentSizeIndex = None + + def __doToggleMusic(self): + messenger.send('wakeup') + if base.musicActive: + base.enableMusic(0) + Settings.setMusic(0) + else: + base.enableMusic(1) + Settings.setMusic(1) + + self.settingsChanged = 1 + self.__setMusicButton() + + def __setMusicButton(self): + if base.musicActive: + self.Music_Label['text'] = TTLocalizer.OptionsPageMusicOnLabel + self.Music_toggleButton['text'] = TTLocalizer.OptionsPageToggleOff + else: + self.Music_Label['text'] = TTLocalizer.OptionsPageMusicOffLabel + self.Music_toggleButton['text'] = TTLocalizer.OptionsPageToggleOn + + def __doToggleSfx(self): + messenger.send('wakeup') + if base.sfxActive: + base.enableSoundEffects(0) + Settings.setSfx(0) + else: + base.enableSoundEffects(1) + Settings.setSfx(1) + + self.settingsChanged = 1 + self.__setSoundFXButton() + + def __doToggleToonChatSounds(self): + messenger.send('wakeup') + if base.toonChatSounds: + base.toonChatSounds = 0 + Settings.setToonChatSounds(0) + else: + base.toonChatSounds = 1 + Settings.setToonChatSounds(1) + + self.settingsChanged = 1 + self.__setToonChatSoundsButton() + + def __setSoundFXButton(self): + if base.sfxActive: + self.SoundFX_Label['text'] = TTLocalizer.OptionsPageSFXOnLabel + self.SoundFX_toggleButton['text'] = TTLocalizer.OptionsPageToggleOff + else: + self.SoundFX_Label['text'] = TTLocalizer.OptionsPageSFXOffLabel + self.SoundFX_toggleButton['text'] = TTLocalizer.OptionsPageToggleOn + + # we affect the toon chat sounds button + self.__setToonChatSoundsButton() + + def __setToonChatSoundsButton(self): + if base.toonChatSounds: + self.ToonChatSounds_Label['text'] = TTLocalizer.OptionsPageToonChatSoundsOnLabel + self.ToonChatSounds_toggleButton['text'] = TTLocalizer.OptionsPageToggleOff + else: + self.ToonChatSounds_Label['text'] = TTLocalizer.OptionsPageToonChatSoundsOffLabel + self.ToonChatSounds_toggleButton['text'] = TTLocalizer.OptionsPageToggleOn + + # we are affected by the sfx active flag + if base.sfxActive: + self.ToonChatSounds_Label.setColorScale(1.0, 1.0, 1.0, 1.0) + self.ToonChatSounds_toggleButton['state'] = DGG.NORMAL + else: + self.ToonChatSounds_Label.setColorScale(0.5, 0.5, 0.5, 0.5) + self.ToonChatSounds_toggleButton['state'] = DGG.DISABLED + + def __doToggleAcceptFriends(self): + messenger.send('wakeup') + if base.localAvatar.acceptingNewFriends: + # now we dont accept friends + base.localAvatar.acceptingNewFriends = 0 + Settings.setAcceptingNewFriends(0) + else: + base.localAvatar.acceptingNewFriends = 1 + Settings.setAcceptingNewFriends(1) + + self.settingsChanged = 1 + self.__setAcceptFriendsButton() + + # We certainly don't want to save the friends setting in the + # Configrc file, since it's something that should be specific + # to the toon, not to the PC the user happens to be playing + # on. In fact, it's a little strange to even have this + # particular option here along with with these PC-specific + # options like screen resolution. + + # If we were to save this property, it would be saved in the + # database along with all the other toon properties. But + # maybe we shouldn't be saving it at all, and force the user + # to re-enable it at each session. + + def __setAcceptFriendsButton(self): + if base.localAvatar.acceptingNewFriends: + self.Friends_Label['text'] = TTLocalizer.OptionsPageFriendsEnabledLabel + self.Friends_toggleButton['text'] = TTLocalizer.OptionsPageToggleOff + else: + self.Friends_Label['text'] = TTLocalizer.OptionsPageFriendsDisabledLabel + self.Friends_toggleButton['text'] = TTLocalizer.OptionsPageToggleOn + + def __doDisplaySettings(self): + if self.displaySettings == None: + self.displaySettings = DisplaySettingsDialog.DisplaySettingsDialog() + self.displaySettings.load() + self.accept(self.displaySettings.doneEvent, self.__doneDisplaySettings) + self.displaySettings.enter(self.ChangeDisplaySettings, self.ChangeDisplayAPI) + return + + def __doneDisplaySettings(self, anyChanged, apiChanged): + if anyChanged: + self.__setDisplaySettings() + + # Save the new settings so we can copy them to the + # SettingsFile a little later. + properties = base.win.getProperties() + self.displaySettingsChanged = 1 + self.displaySettingsSize = (properties.getXSize(), properties.getYSize()) + self.displaySettingsFullscreen = properties.getFullscreen() + self.displaySettingsEmbedded = self.isPropertiesEmbedded(properties) + self.displaySettingsApi = base.pipe.getInterfaceName() + self.displaySettingsApiChanged = apiChanged + + def isPropertiesEmbedded(self, properties): + """Returns true if the current game window is inside a browser.""" + result = False + if properties.getParentWindow(): + result = True + return result + + def __setDisplaySettings(self): + properties = base.win.getProperties() + if properties.getFullscreen(): + screensize = "%s x %s" % (properties.getXSize(), properties.getYSize()) + else: + screensize = TTLocalizer.OptionsPageDisplayWindowed + isEmbedded = self.isPropertiesEmbedded(properties) + if isEmbedded: + screensize = TTLocalizer.OptionsPageDisplayEmbedded + + api = base.pipe.getInterfaceName() + + settings = { + 'screensize' : screensize, + 'api' : api + } + if self.ChangeDisplayAPI: + OptionsPage.notify.debug("change display settings...") + text = TTLocalizer.OptionsPageDisplaySettings % settings + else: + OptionsPage.notify.debug("no change display settings...") + text = TTLocalizer.OptionsPageDisplaySettingsNoApi % settings + self.DisplaySettings_Label['text'] = text + + def __doSpeedChatStyleLeft(self): + if self.speedChatStyleIndex > 0: + self.speedChatStyleIndex = self.speedChatStyleIndex - 1 + self.updateSpeedChatStyle() + + def __doSpeedChatStyleRight(self): + if self.speedChatStyleIndex < len(speedChatStyles) - 1: + self.speedChatStyleIndex = self.speedChatStyleIndex + 1 + self.updateSpeedChatStyle() + + def updateSpeedChatStyle(self): + # update the text color and value + nameKey, arrowColor, rolloverColor, frameColor = \ + speedChatStyles[self.speedChatStyleIndex] + # create the new color scheme object for the text label + newSCColorScheme = SCColorScheme.SCColorScheme( + arrowColor=arrowColor, + rolloverColor=rolloverColor, + frameColor=frameColor, + ) + # set the new color scheme + self.speedChatStyleText.setColorScheme(newSCColorScheme) + # set the new text + self.speedChatStyleText.clearMenu() + colorName = SCStaticTextTerminal.SCStaticTextTerminal(nameKey) + self.speedChatStyleText.append(colorName) + # we must finalize to get the accurate width + self.speedChatStyleText.finalize() + # manual horizonal centering + self.speedChatStyleText.setPos( + 0.445 - self.speedChatStyleText.getWidth() * self.speed_chat_scale / 2, 0, -0.27) + + # show the appropriate arrows + if self.speedChatStyleIndex > 0: + self.speedChatStyleLeftArrow['state'] = DGG.NORMAL + else: + self.speedChatStyleLeftArrow['state'] = DGG.DISABLED + if self.speedChatStyleIndex < len(speedChatStyles) - 1: + self.speedChatStyleRightArrow['state'] = DGG.NORMAL + else: + self.speedChatStyleRightArrow['state'] = DGG.DISABLED + + # actually cause the speed chat color to change and propagate to the DB + # this function is actually found in DistributedToon.py + base.localAvatar.b_setSpeedChatStyleIndex(self.speedChatStyleIndex) + + def writeDisplaySettings(self, task = None): + # Writes the previously-saved display settings to the + # SettingsFile, after the safety timer has expired. + if not self.displaySettingsChanged: + return + + # Make sure our timer task has been removed (we might call + # this method explicitly, before the timer has expired). + taskMgr.remove(self.DisplaySettingsTaskName) + + self.notify.info("writing new display settings %s, %s, %s to SettingsFile." % + (self.displaySettingsSize, self.displaySettingsFullscreen, + self.displaySettingsApi)) + + Settings.setResolutionDimensions(self.displaySettingsSize[0], self.displaySettingsSize[1]) + + Settings.setWindowedMode(not self.displaySettingsFullscreen) + if self.displaySettingsApiChanged: + api = self.DisplaySettingsApiMap.get(self.displaySettingsApi) + if api == None: + self.notify.warning("Cannot save unknown display API: %s" % (self.displaySettingsApi)) + else: + Settings.setDisplayDriver(api) + Settings.writeSettings() + + self.displaySettingsChanged = 0 + + return Task.done + + def __handleExitShowWithConfirm(self): + # For exiting from the options panel to the avatar chooser. + """__handleExitShowWithConfirm(self) + """ + self.confirm = TTDialog.TTGlobalDialog( + doneEvent = "confirmDone", + message = TTLocalizer.OptionsPageExitConfirm, + style = TTDialog.TwoChoice) + self.confirm.show() + self.parent.doneStatus = { + "mode": "exit", + "exitTo": "closeShard"} + self.accept("confirmDone", self.__handleConfirm) + + def __handleConfirm(self): + """__handleConfirm(self) + """ + status = self.confirm.doneStatus + self.ignore("confirmDone") + self.confirm.cleanup() + del self.confirm + if (status == "ok"): + base.cr._userLoggingOut = True + messenger.send(self.parent.doneEvent) + #self.cr.loginFSM.request("chooseAvatar", [self.cr.avList]) + +class CodesTabPage(DirectFrame): + """ + Purpose: The CodesTabPage class initializes the user interface for + the Code Redemption Tab. We are splitting the Options Page into the Options Tab + and the Code Redemption Tab. + """ + + ###################################################################### + # Class Variables + ###################################################################### + #__metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory("CodesTabPage") + + def __init__(self, parent = aspect2d): + """ + Purpose: The __init__ Method provides the initial construction of + the CodesTabPage object that will provide the base interface + for the Code Redemption Page. + Params: None + Return: None + """ + self.parent = parent + # Construct the super class object from which the selector derives. + DirectFrame.__init__( + self, + parent = self.parent, + relief = None, + pos = ( 0.0, 0.0, 0.0 ), + scale = ( 1.0, 1.0, 1.0 ), + ) + self.load() + + def destroy(self): + """ + Purpose: The destroy Method properly handles the destruction of + the CodesTabPage instance by handling appropriate reference + cleanup. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + # Destroy UI Components of the CustomizeUI + + # Remove references to UI Components and instance variables for + # garbage collection purposes. + self.parent = None + + # Destroy the DirectFrame super class. + DirectFrame.destroy(self) + + def load(self): + """ + Purpose: The load Method handles the construction of the specific + UI components that make up the CodesTabPage object. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + cdrGui = loader.loadModel("phase_3.5/models/gui/tt_m_gui_sbk_codeRedemptionGui") + instructionGui = cdrGui.find("**/tt_t_gui_sbk_cdrPresent") + flippyGui = cdrGui.find("**/tt_t_gui_sbk_cdrFlippy") + codeBoxGui = cdrGui.find("**/tt_t_gui_sbk_cdrCodeBox") + self.resultPanelSuccessGui = cdrGui.find("**/tt_t_gui_sbk_cdrResultPanel_success") + self.resultPanelFailureGui = cdrGui.find("**/tt_t_gui_sbk_cdrResultPanel_failure") + self.resultPanelErrorGui = cdrGui.find("**/tt_t_gui_sbk_cdrResultPanel_error") + + self.successSfx = base.loadSfx("phase_3.5/audio/sfx/tt_s_gui_sbk_cdrSuccess.mp3") + self.failureSfx = base.loadSfx("phase_3.5/audio/sfx/tt_s_gui_sbk_cdrFailure.mp3") + + self.instructionPanel = DirectFrame( + parent = self, + relief = None, + image = instructionGui, + image_scale = 0.8, + text = TTLocalizer.CdrInstructions, + text_pos = TTLocalizer.OPCodesInstructionPanelTextPos, + text_align = TextNode.ACenter, + text_scale = TTLocalizer.OPCodesResultPanelTextScale, + text_wordwrap = TTLocalizer.OPCodesInstructionPanelTextWordWrap, + pos = (-0.429, 0, -0.05), + ) + + self.codeBox = DirectFrame( + parent = self, + relief = None, + image = codeBoxGui, + pos = (0.433, 0, 0.35), + ) + + self.flippyFrame = DirectFrame( + parent = self, + relief = None, + image = flippyGui, + pos = (0.44, 0, -0.353), + ) + + self.codeInput = DirectEntry( + parent = self.codeBox, + relief = DGG.GROOVE, + scale = 0.08, + pos = (-0.33, 0, -0.006), + borderWidth = (0.05, 0.05), + frameColor = ((1, 1, 1, 1), (1, 1, 1, 1), (0.5, 0.5, 0.5, 0.5)), + state = DGG.NORMAL, + text_align = TextNode.ALeft, + text_scale = TTLocalizer.OPCodesInputTextScale, + width = 10.5, + numLines = 1, + focus = 1, + backgroundFocus = 0, + cursorKeys = 1, + text_fg = (0, 0, 0, 1), + suppressMouse = 1, + autoCapitalize = 0, + command = self.__submitCode, + ) + + submitButtonGui = loader.loadModel("phase_3/models/gui/quit_button") + self.submitButton = DirectButton( + parent = self, + relief = None, + image = (submitButtonGui.find("**/QuitBtn_UP"), + submitButtonGui.find("**/QuitBtn_DN"), + submitButtonGui.find("**/QuitBtn_RLVR"), + submitButtonGui.find("**/QuitBtn_UP"), + ), + image3_color = Vec4(0.5, 0.5, 0.5, 0.5), + image_scale = 1.15, + state = DGG.NORMAL, + text = TTLocalizer.NameShopSubmitButton, + text_scale = TTLocalizer.OPCodesSubmitTextScale, + text_align = TextNode.ACenter, + text_pos = TTLocalizer.OPCodesSubmitTextPos, + text3_fg = (0.5, 0.5, 0.5, 0.75), + textMayChange = 0, + pos = (0.45, 0.0, 0.0896), + command = self.__submitCode, + ) + + self.resultPanel = DirectFrame( + parent = self, + relief = None, + image = self.resultPanelSuccessGui, + text = "", + text_pos = TTLocalizer.OPCodesResultPanelTextPos, + text_align = TextNode.ACenter, + text_scale = TTLocalizer.OPCodesResultPanelTextScale, + text_wordwrap = TTLocalizer.OPCodesResultPanelTextWordWrap, + pos = (-0.42, 0, -0.0567), + ) + self.resultPanel.hide() + + # Result Panel Close Button + closeButtonGui = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + self.closeButton = DirectButton( + parent = self.resultPanel, + pos = (0.296, 0, -0.466), + relief = None, + state = DGG.NORMAL, + image = (closeButtonGui.find('**/CloseBtn_UP'), + closeButtonGui.find('**/CloseBtn_DN'), + closeButtonGui.find('**/CloseBtn_Rllvr')), + image_scale = (1, 1, 1), + command = self.__hideResultPanel, + ) + + closeButtonGui.removeNode() + cdrGui.removeNode() + submitButtonGui.removeNode() + + def enter(self): + """ + Purpose: This method gets called when the Codes Tab is selected. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + self.show() + + # While the entry's on the screen, we have to turn off the + # background focus on the normal chat entry. Otherwise, + # keypresses would start chatting! + localAvatar.chatMgr.fsm.request("otherDialog") + + # And now we can set the focus on our entry. + self.codeInput['focus'] = 1 + + # Make sure we always start with a blank entry box. + self.codeInput.enterText('') + + # Enable the code entry box. + self.__enableCodeEntry() + + def exit(self): + """ + Purpose: This method gets called when we leave the Codes Tab. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + self.resultPanel.hide() + self.hide() + + # Restore the background focus on the chat entry. + localAvatar.chatMgr.fsm.request("mainMenu") + + def unload(self): + """ + Purpose: This method gets called when we are exiting the game. + Params: None + Return: None + """ + assert self.notify.debugStateCall(self) + + self.instructionPanel.destroy() + self.instructionPanel = None + + self.codeBox.destroy() + self.codeBox = None + + self.flippyFrame.destroy() + self.flippyFrame = None + + self.codeInput.destroy() + self.codeInput = None + + self.submitButton.destroy() + self.submitButton = None + + self.resultPanel.destroy() + self.resultPanel = None + + self.closeButton.destroy() + self.closeButton = None + + del self.successSfx + del self.failureSfx + + def __submitCode(self, input = None): + """ + Purpose: This method the player submits the code. + It could be called by pressing the submit button or by pressing Enter + on the keyboard + Params: input - This is the string the user entered. + Return: None + """ + if input == None: + input = self.codeInput.get() + + # Keep focus on the code input even after entering a code. + self.codeInput['focus'] = 1 + + # Ignoring Blank. + if (input == ''): + return + + # If the player typed something he must be awake. + messenger.send('wakeup') + + if hasattr(base, "codeRedemptionMgr"): + base.codeRedemptionMgr.redeemCode(input, self.__getCodeResult) + + # Make the code entry box empty after submitting a code. + self.codeInput.enterText('') + + # Disable the code entry till we get a result from the Uberdog. + self.__disableCodeEntry() + + def __getCodeResult(self, result, awardMgrResult): + """ + Purpose: This method is called from the AI as a callback from self.__submitCode. + Params: result - result of the code submitted. + awardMgrResult - the award, if the code was successful. + Return: None + """ + assert self.notify.debugStateCall(self) + self.notify.debug("result = %s" %result) + self.notify.debug("awardMgrResult = %s" %awardMgrResult) + + # We've received a response from the Uberdog, enable the code entry again. + self.__enableCodeEntry() + + # Code Successfully Redeemed + if (result == 0): + self.resultPanel['image'] = self.resultPanelSuccessGui + self.resultPanel['text'] = TTLocalizer.CdrResultSuccess + + # Code is Invalid + elif (result == 1 or result == 3): + self.resultPanel['image'] = self.resultPanelFailureGui + self.resultPanel['text'] = TTLocalizer.CdrResultInvalidCode + + # Code has expired + elif (result == 2): + self.resultPanel['image'] = self.resultPanelFailureGui + self.resultPanel['text'] = TTLocalizer.CdrResultExpiredCode + + # Code is correct, but something else went wrong. Check awardMgrResult. + elif (result == 4): + self.resultPanel['image'] = self.resultPanelErrorGui + + if (awardMgrResult == 0): + self.resultPanel['text'] = TTLocalizer.CdrResultSuccess + + elif (awardMgrResult == 1 or awardMgrResult == 2 or awardMgrResult == 15 or awardMgrResult == 16): + self.resultPanel['text'] = TTLocalizer.CdrResultUnknownError + + elif (awardMgrResult == 3 or awardMgrResult == 4): + self.resultPanel['text'] = TTLocalizer.CdrResultMailboxFull + + elif (awardMgrResult == 5 or awardMgrResult == 10): + self.resultPanel['text'] = TTLocalizer.CdrResultAlreadyInMailbox + + elif (awardMgrResult == 6 or awardMgrResult == 7 or awardMgrResult == 11): + self.resultPanel['text'] = TTLocalizer.CdrResultAlreadyInQueue + + elif (awardMgrResult == 8): + self.resultPanel['text'] = TTLocalizer.CdrResultAlreadyInCloset + + elif (awardMgrResult == 9): + self.resultPanel['text'] = TTLocalizer.CdrResultAlreadyBeingWorn + + elif (awardMgrResult == 12 or awardMgrResult == 13 or awardMgrResult == 14): + self.resultPanel['text'] = TTLocalizer.CdrResultAlreadyReceived + + elif (result == 5): + # Too many failed attempts - Display correct error and disable the code entry and submit button. + self.resultPanel['text'] = TTLocalizer.CdrResultTooManyFails + self.__disableCodeEntry() + + elif (result == 6): + # Service Unavailable. + self.resultPanel['text'] = TTLocalizer.CdrResultServiceUnavailable + self.__disableCodeEntry() + + # Play the success or failure sounds. + if (result == 0): + self.successSfx.play() + else: + self.failureSfx.play() + + self.resultPanel.show() + + def __hideResultPanel(self): + """ + Purpose: To hide the Result Panel. + Params: None + Return: None + """ + self.resultPanel.hide() + + def __disableCodeEntry(self): + """ + Purpose: Disable the the code entry box and the submit button. + We'll disable it right after submitting a code, while waiting for + a result, and if the player has submitted too many invalid codes. + Params: None + Return: None + """ + self.codeInput['state'] = DGG.DISABLED + self.submitButton['state'] = DGG.DISABLED + + def __enableCodeEntry(self): + """ + Purpose: Enable the the code entry box and the submit button. + We'll enable it right after the Uberdog gets back to us after + a code submit. This is to ensure that the 2nd code is not submitted + before the result from the 1st has arrived. + Params: None + Return: None + """ + self.codeInput['state'] = DGG.NORMAL + self.codeInput['focus'] = 1 + self.submitButton['state'] = DGG.NORMAL + \ No newline at end of file diff --git a/toontown/src/shtiker/PhotoAlbumPage.py b/toontown/src/shtiker/PhotoAlbumPage.py new file mode 100644 index 0000000..5dfe78a --- /dev/null +++ b/toontown/src/shtiker/PhotoAlbumPage.py @@ -0,0 +1,445 @@ +from pandac.PandaModules import * +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +import os +from toontown.toonbase import ToontownGlobals + +class PhotoAlbumPage(ShtikerPage.ShtikerPage): + + # special methods + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + self.textRolloverColor = Vec4(1,1,0,1) + self.textDownColor = Vec4(0.5,0.9,1,1) + self.textDisabledColor = Vec4(0.4,0.8,0.4,1) + self.photos = {} + self.selectedFileName = None + self.photoIndex = 0 + + def load(self): + self.title = DirectLabel( + parent = self, + relief = None, + text = "Photo Album", + text_scale = 0.10, + pos = (0,0,0.6), + ) + + self.pictureImage = loader.loadModel('phase_3.5/models/gui/photo_frame') + self.pictureImage.setScale(0.2) + self.pictureImage.setPos(0.44,0,0.25) + self.pictureImage.reparentTo(self) + self.pictureFg = self.pictureImage.find('**/fg') + self.pictureFg.setColor(1,1,1,0.1) + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + self.pictureCaption = DirectLabel( + parent = self, + relief = None, + text = "Caption", + text_scale = 0.05, + text_wordwrap = 10, + text_align = TextNode.ACenter, + pos = (0.45, 0, -0.22), + ) + + self.renameButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (1,1,1), + pos = (0.45,0,-0.35), + text = "Caption", + text_scale = 0.06, + text_pos = (0,-0.02), + command = self.renameImage, + state = DGG.DISABLED, + ) + + trashcanGui = loader.loadModel("phase_3/models/gui/trashcan_gui") + + self.deleteButton = DirectButton( + parent = self, + image = (trashcanGui.find("**/TrashCan_CLSD"), + trashcanGui.find("**/TrashCan_OPEN"), + trashcanGui.find("**/TrashCan_RLVR")), + text = ("", "Delete", "Delete"), + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = 0.1, + text_pos = (0, -0.1), + text_font = ToontownGlobals.getInterfaceFont(), + textMayChange = 0, + relief = None, + pos = (0.73, 0, -0.33), + scale = 0.4, + state = DGG.DISABLED, + command = self.deleteImage, + ) + + """ + # We have no print support right now + self.printButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (1,1,1), + pos = (0.45,0,-0.6), + text = "Print", + text_scale = 0.06, + text_pos = (0,-0.02), + state = DGG.DISABLED, + #command = self.printImage, + ) + """ + + guiButton.removeNode() + trashcanGui.removeNode() + + gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + self.scrollList = DirectScrolledList( + parent = self, + relief = None, + forceHeight = 0.07, + pos = (-0.5,0,0), + # inc and dec are DirectButtons + incButton_image = (gui.find("**/FndsLst_ScrollUp"), + gui.find("**/FndsLst_ScrollDN"), + gui.find("**/FndsLst_ScrollUp_Rllvr"), + gui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_scale = (1.3,1.3,-1.3), + incButton_pos = (0,0,-0.51), + # Make the disabled button fade out + incButton_image3_color = Vec4(1,1,1,0.2), + decButton_image = (gui.find("**/FndsLst_ScrollUp"), + gui.find("**/FndsLst_ScrollDN"), + gui.find("**/FndsLst_ScrollUp_Rllvr"), + gui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_scale = (1.3,1.3,1.3), + decButton_pos = (0,0,0.51), + # Make the disabled button fade out + decButton_image3_color = Vec4(1,1,1,0.2), + # itemFrame is a DirectFrame + itemFrame_pos = (-0.237,0,0.41), + itemFrame_scale = 1.0, + itemFrame_relief = DGG.SUNKEN, + itemFrame_frameSize = (-0.05,0.66,-0.88,0.06), + itemFrame_frameColor = (0.85,0.95,1,1), + itemFrame_borderWidth = (0.01, 0.01), + # each item is a button with text on it + numItemsVisible = 13, + items = [], + ) + + self.renamePanel = DirectFrame( + parent = self, + relief = None, + pos = (0.45,0,-0.45), + image = DGG.getDefaultDialogGeom(), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.0, 0.6), + text = 'Caption Photo', + text_scale = 0.06, + text_pos = (0.0, 0.13), + sortOrder = NO_FADE_SORT_INDEX, + ) + + self.renameEntry = DirectEntry( + parent = self.renamePanel, + relief = DGG.SUNKEN, + scale = 0.06, + pos = (-0.3,0,0), + borderWidth = (0.1, 0.1), + numLines = 1, + cursorKeys = 0, + # width = 10, + frameColor = (0.8,0.8,0.5,1), + frameSize = (-0.2, 10, -0.4, 1.1), + command = self.renameDialog, + ) + + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + + self.bCancel = DirectButton(parent = self.renamePanel, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + relief = None, + text = "Cancel", + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.0, 0.0, -0.1), + command = self.renameCancel) + self.renamePanel.hide() + + self.deletePanel = DirectFrame( + parent = self, + relief = None, + pos = (0,0,0), + image = DGG.getDefaultDialogGeom(), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.0, 0.6), + text = 'Delete Photo?', + text_scale = 0.06, + text_pos = (0.0, 0.13), + sortOrder = NO_FADE_SORT_INDEX, + ) + + self.dOk = DirectButton(parent = self.deletePanel, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = "Ok", + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (-0.1, 0.0, -0.1), + command = self.deleteConfirm) + + self.dCancel = DirectButton(parent = self.deletePanel, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + relief = None, + text = "Cancel", + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.1, 0.0, -0.1), + command = self.deleteCancel) + self.deletePanel.hide() + + self.leftArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + # make the disabled color more transparent + image3_color = Vec4(1, 1, 1, 0.5), + scale = (-1.0, 1.0, 1.0), # make the arrow point left + pos = (0.15, 0, -0.21), + command = self.prevPhoto, + ) + + self.rightArrow = DirectButton( + parent = self, + relief = None, + image = (gui.find("**/Horiz_Arrow_UP"), + gui.find("**/Horiz_Arrow_DN"), + gui.find("**/Horiz_Arrow_Rllvr"), + gui.find("**/Horiz_Arrow_UP"), + ), + # make the disabled color more transparent + image3_color = Vec4(1, 1, 1, 0.5), + pos = (0.75, 0, -0.21), + command = self.nextPhoto, + ) + + gui.removeNode() + buttons.removeNode() + + def unload(self): + del self.title + del self.scrollList + self.pictureImage.removeNode() + self.pictureFg.removeNode() + del self.pictureCaption + del self.deleteButton + del self.renameButton + del self.renamePanel + del self.renameEntry + del self.bCancel + del self.deletePanel + del self.dOk + del self.dCancel + del self.leftArrow + del self.rightArrow + ShtikerPage.ShtikerPage.unload(self) + + def renameDialog(self, str): + separator = '_' + validChars = string.letters + string.digits + ' -' + str = filter(lambda s: (s in validChars), str) + if not str: + self.renameCleanup() + return 0 + oldName = self.selectedFileName + numUnders = oldName.count(separator) + if (numUnders == 0): + newName = oldName[0:11] + separator + str + separator + oldName[10:] + elif (numUnders == 2): + sp = oldName.split(separator) + newName = sp[0] + separator + str + separator + sp[2] + else: + self.renameCleanup() + return 0 + os.rename(oldName, newName) + self.renameCleanup() + self.updateScrollList() + self.chosePhoto(newName) + return 1 + + def renameCancel(self): + self.renameCleanup() + + def renameCleanup(self): + self.renamePanel.hide() + # Restore the background focus on the chat entry. + chatEntry = base.localAvatar.chatMgr.chatInputNormal.chatEntry + chatEntry['backgroundFocus'] = self.oldFocus + + def renameImage(self): + self.deleteCleanup() + self.renameEntry.set(self.getPhotoName(self.selectedFileName)) + self.renamePanel.show() + # While the entry's on the screen, we have to turn off the + # background focus on the normal chat entry. Otherwise, + # keypresses would start chatting! + chatEntry = base.localAvatar.chatMgr.chatInputNormal.chatEntry + chatEntry['backgroundFocus'] = 0 + # And now we can set the focus on our entry. + self.renameEntry['focus'] = 1 + print self.selectedFileName + + def deleteConfirm(self): + os.remove(self.selectedFileName) + self.selectedFileName = None + self.deleteCleanup() + self.updateScrollList() + + def deleteCancel(self): + self.deleteCleanup() + + def deleteCleanup(self): + self.deletePanel.hide() + + def deleteImage(self): + self.renameCleanup() + self.deletePanel['text'] = "Delete Photo?\n%s" % self.getPhotoName(self.selectedFileName) + self.deletePanel.show() + + def makePhotoButton(self, fileName): + return DirectButton( + relief = None, + text = self.getPhotoName(fileName), + text_scale = 0.06, + text_align = TextNode.ALeft, + text1_bg = self.textDownColor, + text2_bg = self.textRolloverColor, + text3_fg = self.textDisabledColor, + command = self.chosePhoto, + extraArgs = [fileName], + ) + + def getPhotoName(self, fileName): + # Todo: limit length based on font + # font.calcWidth() + separator = '_' + numUnders = fileName.count(separator) + if (numUnders == 0): + return 'noname' + elif (numUnders == 2): + return fileName.split(separator)[1] + else: + return 'unknown' + + def chosePhoto(self, fileName): + if fileName: + self.selectedFileName = fileName + photoTexture = loader.loadTexture(fileName) + photoName = self.getPhotoName(fileName) + self.pictureFg.setTexture(photoTexture, 1) + self.pictureFg.setColor(1,1,1,1) + self.pictureCaption['text'] = photoName + self.renameButton['state'] = DGG.NORMAL + self.deleteButton['state'] = DGG.NORMAL + self.renameEntry.set(photoName) + else: + self.selectedFileName = None + self.pictureFg.clearTexture() + self.pictureFg.setColor(1,1,1,0.1) + self.pictureCaption['text'] = '' + self.renameButton['state'] = DGG.DISABLED + self.deleteButton['state'] = DGG.DISABLED + self.renameEntry.set('') + + def getPhotos(self): + files = os.listdir('.') + photos = [] + for fileName in files: + if ((fileName[0:10] == 'screenshot') and + (fileName[-4:] == '.jpg')): + photos.append(fileName) + return photos + + def newScreenshot(self, filename): + self.updateScrollList() + + def updateScrollList(self): + newPhotos = self.getPhotos() + + # Remove old buttons + for photo in self.photos.keys(): + if photo not in newPhotos: + photoButton = self.photos[photo] + self.scrollList.removeItem(photoButton) + photoButton.destroy() + del self.photos[photo] + + # Add new photos + for photo in newPhotos: + if not self.photos.has_key(photo): + photoButton = self.makePhotoButton(photo) + self.scrollList.addItem(photoButton) + self.photos[photo] = photoButton + + if self.photos.keys(): + self.chosePhoto(self.photos.keys()[0]) + else: + self.chosePhoto(None) + + + def enter(self): + """enter(self) + """ + self.accept('screenshot', self.newScreenshot) + self.updateScrollList() + chatEntry = base.localAvatar.chatMgr.chatInputNormal.chatEntry + self.oldFocus = chatEntry['backgroundFocus'] + ShtikerPage.ShtikerPage.enter(self) + return + + def exit(self): + """exit(self) + """ + self.ignore('screenshot') + self.renameCleanup() + self.deleteCleanup() + ShtikerPage.ShtikerPage.exit(self) + return + + def updateArrows(self): + pass + + def prevPhoto(self): + pass + + def nextPhoto(self): + pass + diff --git a/toontown/src/shtiker/PurchaseManager.py b/toontown/src/shtiker/PurchaseManager.py new file mode 100644 index 0000000..d5c8511 --- /dev/null +++ b/toontown/src/shtiker/PurchaseManager.py @@ -0,0 +1,135 @@ +from pandac.PandaModules import * +from PurchaseManagerConstants import * +from direct.distributed.ClockDelta import * +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from toontown.minigame import TravelGameGlobals + +class PurchaseManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("PurchaseManager") + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + self.playAgain = 0 + + def disable(self): + DistributedObject.DistributedObject.disable(self) + self.ignoreAll() + + def setPlayerIds(self, *playerIds): + self.notify.debug("setPlayerIds: %s" % (playerIds,)) + self.playerIds = playerIds + + def setNewbieIds(self, newbieIds): + self.notify.debug("setNewbieIds: %s" % (newbieIds,)) + self.newbieIds = newbieIds + + def setMinigamePoints(self, *mpArray): + self.notify.debug("setMinigamePoints: %s" % (mpArray,)) + self.mpArray = mpArray + + def setPlayerMoney(self, *moneyArray): + self.notify.debug("setPlayerMoney: %s" % (moneyArray,)) + self.moneyArray = moneyArray + + def setPlayerStates(self, *stateArray): + self.notify.debug("setPlayerStates: %s" % (stateArray,)) + self.playerStates = stateArray + # Do whatever you do to update the gui states of the players + # only do this if we are generated and have localToon + if self.isGenerated() and self.hasLocalToon: + self.announcePlayerStates() + + def setCountdown(self, timestamp): + self.countdownTimestamp = timestamp + + def announcePlayerStates(self): + messenger.send("purchaseStateChange", [self.playerStates]) + + def announceGenerate(self): + DistributedObject.DistributedObject.announceGenerate(self) + self.hasLocalToon = self.calcHasLocalToon() + if self.hasLocalToon: + # let the rest of the system know the player states + self.announcePlayerStates() + # Start the countdown... + et = globalClockDelta.localElapsedTime(self.countdownTimestamp) + remain = PURCHASE_COUNTDOWN_TIME - et + # Hang hooks for when the buttons get hit + self.acceptOnce("purchasePlayAgain", self.playAgainHandler) + self.acceptOnce("purchaseBackToToontown", + self.backToToontownHandler) + self.acceptOnce("purchaseTimeout", self.setPurchaseExit) + self.accept("boughtGag", self.__handleBoughtGag) + # Do whatever you do to display the purchase screen here + base.cr.playGame.hood.fsm.request("purchase", + [self.mpArray, + self.moneyArray, + self.playerIds, + self.playerStates, + remain, + self.metagameRound, + self.votesArray]) + + def calcHasLocalToon(self): + """ returns true if we 'own' localToon """ + retval = ((base.localAvatar.doId not in self.newbieIds) and + (base.localAvatar.doId in self.playerIds)) + + if self.metagameRound > -1 and \ + self.metagameRound < TravelGameGlobals.FinalMetagameRoundIndex: + # if we are in the middle of a metagame ignore newbieness + retval = base.localAvatar.doId in self.playerIds + + self.notify.debug('calcHasLocalToon returning %s' % retval) + return retval + + + def playAgainHandler(self): + self.d_requestPlayAgain() + + def backToToontownHandler(self): + self.notify.debug("requesting exit to toontown...") + self.d_requestExit() + self.playAgain = 0 + self.setPurchaseExit() + + def d_requestExit(self): + self.sendUpdate("requestExit", []) + + def d_requestPlayAgain(self): + self.notify.debug("requesting play again...") + self.sendUpdate("requestPlayAgain", []) + self.playAgain = 1 + # Go into "Waiting for other players" state... + + def d_setInventory(self, invString, money, done): + # Report our inventory to the server + self.sendUpdate("setInventory", [invString, money, done]) + + def __handleBoughtGag(self): + # Send each gag purchase to the AI as we go. + self.d_setInventory(base.localAvatar.inventory.makeNetString(), + base.localAvatar.getMoney(), 0) + + def setPurchaseExit(self): + if self.hasLocalToon: + self.ignore("boughtGag") + # Report our purchases + self.d_setInventory(base.localAvatar.inventory.makeNetString(), + base.localAvatar.getMoney(), 1) + # Shutdown the purchase window, and go to where we are supposed + # to go + messenger.send("purchaseOver", [self.playAgain]) + + def setMetagameRound(self, round): + self.notify.debug("setMetagameRound: %s" % (round,)) + self.metagameRound = round + + def setVotesArray(self, votesArray): + """ + Since we now convert some votes left to beans, we need to pass this + information to the client, so we can put it in the show + """ + self.notify.debug('setVotesArray: %s' % votesArray) + self.votesArray = votesArray diff --git a/toontown/src/shtiker/PurchaseManagerAI.py b/toontown/src/shtiker/PurchaseManagerAI.py new file mode 100644 index 0000000..9730ecb --- /dev/null +++ b/toontown/src/shtiker/PurchaseManagerAI.py @@ -0,0 +1,474 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * +from PurchaseManagerConstants import * +import copy +from direct.task.Task import Task + +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from toontown.minigame import TravelGameGlobals + +class PurchaseManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("PurchaseManagerAI") + + def __init__(self, air, playerArray, mpArray, previousMinigameId, + trolleyZone, newbieIdList=[], votesArray =None, metagameRound=-1, + desiredNextGame = None): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.playerIds = copy.deepcopy(playerArray) + self.minigamePoints = copy.deepcopy(mpArray) + self.previousMinigameId = previousMinigameId + self.trolleyZone = trolleyZone + self.newbieIds = copy.deepcopy(newbieIdList) + self.isShutdown = 0 + if votesArray: + self.votesArray = copy.deepcopy(votesArray) + else: + self.votesArray = [] + + self.metagameRound = metagameRound #this refers to the previous game played + self.desiredNextGame = desiredNextGame + + # pad playerIds and minigamePoints to have 4 numbers; pad with zeroes + for i in range(len(self.playerIds), 4): + self.playerIds.append(0) + for i in range(len(self.minigamePoints), 4): + self.minigamePoints.append(0) + + # Initialize the states of the players + self.playerStates = [None, None, None, None] + self.playersReported = [None, None, None, None] + + assert (len(self.playerIds) == len(self.playerStates) == len(self.minigamePoints)) + + # create an array to keep track of the player's starting money + self.playerMoney = [0, 0, 0, 0] + + # set up the initial states of all toons + for i in range(len(self.playerIds)): + avId = self.playerIds[i] + # 0 means no player, 1, 2, and 3 are suits. + if avId <= 3: + self.playerStates[i] = PURCHASE_NO_CLIENT_STATE + self.playersReported[i] = PURCHASE_CANTREPORT_STATE + # Player is in dictionary + elif self.air.doId2do.has_key(avId): + if avId not in self.getInvolvedPlayerIds(): + # either we are a normal purchaseMgr with some newbies, or + # we're a newbie purchaseMgr with non-newbies; either way, + # this toon does not belong to us. Mark them as having + # chosen to leave + self.playerStates[i] = PURCHASE_EXIT_STATE + self.playersReported[i] = PURCHASE_REPORTED_STATE + else: + self.playerStates[i] = PURCHASE_WAITING_STATE + self.playersReported[i] = PURCHASE_UNREPORTED_STATE + # Player must be disconnected + else: + self.playerStates[i] = PURCHASE_DISCONNECTED_STATE + self.playersReported[i] = PURCHASE_CANTREPORT_STATE + + # more processing for the toons that we 'own' + for avId in self.getInvolvedPlayerIds(): + # 0 means no player, 1, 2, and 3 are suits. + if avId > 3 and self.air.doId2do.has_key(avId): + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, + extraArgs=[avId]) + av = self.air.doId2do[avId] + avIndex = self.findAvIndex(avId) + # record the starting money for reward screen + money = av.getMoney() + if avIndex == None: + self.notify.warning('__init__ avIndex is none but avId=%s' % avId) + continue + self.playerMoney[avIndex] = money + # Update us and the client avatar with t + av.addMoney(self.minigamePoints[avIndex]) + + # Log the completion (and beans won) to the event server + self.air.writeServerEvent('minigame', + avId, '%s|%s|%s|%s' % ( + self.previousMinigameId, self.trolleyZone, + self.playerIds, self.minigamePoints[avIndex])) + + if self.metagameRound == TravelGameGlobals.FinalMetagameRoundIndex: + # lets add some extra beans from the remaining votes + numPlayers = len (self.votesArray) + extraBeans = self.votesArray[avIndex] * \ + TravelGameGlobals.PercentOfVotesConverted[numPlayers] / 100.0 + av.addMoney(extraBeans) + # Log the completion (and extra beans won) to the event server + self.air.writeServerEvent('minigame_extraBeans', + avId, '%s|%s|%s|%s' % ( + self.previousMinigameId, self.trolleyZone, + self.playerIds, extraBeans)) + + # Flags to indicate state. + #self.receivingInventory = 0 + # NOTE: JNS changed this to be one because it wasn't properly + # handling the case of people exiting to Toontown. This bears + # further examination. + self.receivingInventory = 1 + self.receivingButtons = 1 + return None + + def delete(self): + taskMgr.remove(self.uniqueName("countdown-timer")) + self.ignoreAll() + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getInvolvedPlayerIds(self): + """ everyone but the newbies """ + avIds = [] + for avId in self.playerIds: + if not avId in self.newbieIds: + avIds.append(avId) + else: + # this is a newbie, but if we are in the middle of a metagame + # make him continue + if self.metagameRound > -1 and \ + self.metagameRound < TravelGameGlobals.FinalMetagameRoundIndex: + avIds.append(avId) + + + return avIds + + def getMinigamePoints(self): + return self.minigamePoints + + def getPlayerIds(self): + return self.playerIds + + def getNewbieIds(self): + return self.newbieIds + + def getPlayerMoney(self): + return self.playerMoney + + def d_setPlayerStates(self, stateArray): + self.sendUpdate("setPlayerStates", stateArray) + return None + + def getPlayerStates(self): + return self.playerStates + + # The countdown starts when we create the screen. + def getCountdown(self): + assert(self.notify.debug("Starting the purchase countdown...")) + self.startCountdown() + return globalClockDelta.getRealNetworkTime() + + def startCountdown(self): + if not config.GetBool('disable-purchase-timer', 0): + taskMgr.doMethodLater(PURCHASE_COUNTDOWN_TIME, self.timeIsUpTask, + self.uniqueName("countdown-timer")) + + # Client requests + def requestExit(self): + avId = self.air.getAvatarIdFromSender() + avIndex = self.findAvIndex(avId) + if avIndex is None: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestExit: unknown avatar: %s' % + (avId, )) + return + if self.receivingButtons: + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + if avIndex == None: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestExit not on list') + self.notify.warning("Avatar " + str(avId) + + " requested Exit, but is not on the list!") + else: + avState = self.playerStates[avIndex] + if ((avState == PURCHASE_PLAYAGAIN_STATE) or + (avState == PURCHASE_WAITING_STATE)): + self.playerStates[avIndex] = PURCHASE_EXIT_STATE + self.handlePlayerLeaving(avId) + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestExit invalid transition to exit') + self.notify.warning("Invalid transition to exit state.") + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestExit unknown avatar') + self.notify.warning("Avatar " + str(avId) + + " requested Exit, but is not in doId2do." + + " Assuming disconnected.") + self.playerStates[avIndex] = PURCHASE_DISCONNECTED_STATE + self.playersReported[avIndex] = PURCHASE_CANTREPORT_STATE + self.ignore(self.air.getAvatarExitEvent(avId)) + self.d_setPlayerStates(self.playerStates) + if self.getNumUndecided() == 0: + self.timeIsUp() + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestExit not receiving requests now') + self.notify.warning( + "Avatar " + str(avId) + + " requested Exit, but I am not receiving button requests now.") + return None + + def requestPlayAgain(self): + avId = self.air.getAvatarIdFromSender() + if self.findAvIndex(avId) == None: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestPlayAgain: unknown avatar') + return + if self.receivingButtons: + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + avIndex = self.findAvIndex(avId) + if avIndex == None: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestPlayAgain not on list') + self.notify.warning( + "Avatar " + str(avId) + + " requested PlayAgain, but is not on the list!") + else: + avState = self.playerStates[avIndex] + if (avState == PURCHASE_WAITING_STATE): + self.notify.debug(str(avId) + " wants to play again") + self.playerStates[avIndex] = PURCHASE_PLAYAGAIN_STATE + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestPlayAgain invalid transition to PlayAgain') + self.notify.warning( + "Invalid transition to PlayAgain state.") + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestPlayAgain unknown avatar') + self.notify.warning( + "Avatar " + str(avId) + + " requested PlayAgain, but is not in doId2do." + + " Assuming disconnected.") + avIndex = self.findAvIndex(avId) + self.playerStates[avIndex] = PURCHASE_DISCONNECTED_STATE + self.playersReported[avIndex] = PURCHASE_CANTREPORT_STATE + self.ignore(self.air.getAvatarExitEvent(avId)) + self.d_setPlayerStates(self.playerStates) + if self.getNumUndecided() == 0: + self.timeIsUp() + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.requestPlayAgain not receiving requests now') + self.notify.warning( + "Avatar " + str(avId) + + " requested PlayAgain, but I am not receiving button " + + "requests now.") + return None + + def setInventory(self, blob, newMoney, done): + avId = self.air.getAvatarIdFromSender() + if self.receivingInventory: + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + avIndex = self.findAvIndex(avId) + if avIndex == None: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.setInventory not on list') + self.notify.warning( + "Avatar " + str(avId) + + " requested purchase, but is not on the list!") + else: + newInventory = av.inventory.makeFromNetString(blob) + currentMoney = av.getMoney() + if av.inventory.validatePurchase(newInventory, currentMoney, newMoney): + av.setMoney(newMoney) + if not done: + return + # Sanity check for double reporting + if (self.playersReported[avIndex] != PURCHASE_UNREPORTED_STATE): + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.setInventory bad report state') + self.notify.warning( + "Bad report state: " + + str(self.playersReported[avIndex])) + else: + # Tell the state server about the purchase + av.d_setInventory(av.inventory.makeNetString()) + av.d_setMoney(newMoney) + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.setInventory invalid purchase') + self.notify.warning("Avatar " + str(avId) + + " attempted an invalid purchase.") + # Make sure the avatar is in sync with the AI. + av.d_setInventory(av.inventory.makeNetString()) + av.d_setMoney(av.getMoney()) + + # Record report + self.playersReported[avIndex] = PURCHASE_REPORTED_STATE + # Test to see if we are waiting on anyone else + if self.getNumUnreported() == 0: + self.shutDown() + + else: + self.air.writeServerEvent('suspicious', avId, 'PurchaseManager.setInventory not receiving inventory') + self.notify.warning("Not receiving inventory. Ignored " + + str(avId) + "'s request") + return None + + # Time is up, clients! You can send your inventory requests now! + def d_setPurchaseExit(self): + self.sendUpdate("setPurchaseExit", []) + return None + + # When time is up, let everyone know. + def timeIsUpTask(self, task): + self.timeIsUp() + return Task.done + + def timeIsUp(self): + assert(self.receivingButtons) + # Tell the clients that time is up + self.d_setPurchaseExit() + # Remove the countdown + taskMgr.remove(self.uniqueName("countdown-timer")) + # No longer receiving buttons + self.receivingButtons = 0 + # Now receiving inventory + self.receivingInventory = 1 + # Hang out, waiting for inventory info to come in. + return None + + def getVotesArrayMatchingPlayAgainList(self, playAgainList): + """ + make sure votesArray is consistent with those playing again, + since someone may have dropped + """ + retval = [] + + for playAgainIndex in range(len(playAgainList)): + avId = playAgainList[playAgainIndex] + origIndex = self.playerIds.index(avId) + if self.votesArray and origIndex < len(self.votesArray) : + retval.append(self.votesArray[origIndex]) + else: + retval.append(0) + + + return retval + + def shutDown(self): + if self.isShutdown: + # We no longer own the zoneId. If we accidentally come by + # here again, we don't want to try to deallocate it again (or + # hand it off to another minigame). + # Note: We cannot set the zoneId to None because the AIRepository + # needs to remove us from the zoneId2doIds dict + # Maybe someone called shutDown twice. + self.notify.warning("Got shutDown twice") + return + self.isShutdown = 1 + + # This must be imported here rather than at the top of the + # file in order to avoid circular imports. + from toontown.minigame import MinigameCreatorAI + + # Does anyone want to play again? + playAgainNum = self.getNumPlayAgain() + assert(playAgainNum >=0 and playAgainNum <= 4) + # If so, start a minigame in this zone + if playAgainNum > 0: + playAgainList = self.getPlayAgainList() + newVotesArray = self.getVotesArrayMatchingPlayAgainList(playAgainList) + newRound = self.metagameRound; + newbieIdsToPass = [] + if newRound > -1: + newbieIdsToPass= self.newbieIds # we must pass this on + if newRound < TravelGameGlobals.FinalMetagameRoundIndex: + newRound+= 1 + else: + newRound = 0 + newVotesArray = [TravelGameGlobals.DefaultStartingVotes] * len(playAgainList) + + # but if we only have one player left, don't start the metagame + if len(playAgainList) == 1 and \ + simbase.config.GetBool('metagame-min-2-players', 1): + newRound = -1 + + MinigameCreatorAI.createMinigame( + self.air, playAgainList, + self.trolleyZone, + minigameZone = self.zoneId, + previousGameId = self.previousMinigameId, + newbieIds = newbieIdsToPass, + startingVotes = newVotesArray, + metagameRound = newRound, + desiredNextGame = self.desiredNextGame) + # If not, deallocate this zone, so it can be reused in the future. + else: + MinigameCreatorAI.releaseMinigameZone(self.zoneId) + + # That's it for us! + self.requestDelete() + + # Don't listen for any more unexpected avatar exit events. + self.ignoreAll() + return None + + # Find the index for a given avId. Return None if there isn't one. + def findAvIndex(self, avId): + for i in range(len(self.playerIds)): + if avId == self.playerIds[i]: + return i + return None + + # Return the number of players that we are still waiting on. + def getNumUndecided(self): + undecidedCounter = 0 + for playerState in self.playerStates: + if playerState == PURCHASE_WAITING_STATE: + undecidedCounter += 1 + return undecidedCounter + + def getPlayAgainList(self): + playAgainList = [] + assert(len(self.playerStates) == 4) + for i in range(len(self.playerStates)): + if self.playerStates[i] == PURCHASE_PLAYAGAIN_STATE: + playAgainList.append(self.playerIds[i]) + return playAgainList + + def getNumPlayAgain(self): + playAgainCounter = 0 + for playerState in self.playerStates: + if playerState == PURCHASE_PLAYAGAIN_STATE: + playAgainCounter += 1 + return playAgainCounter + + def getNumUnreported(self): + unreportedCounter = 0 + for playerState in self.playersReported: + if playerState == PURCHASE_UNREPORTED_STATE: + unreportedCounter += 1 + elif playerState == PURCHASE_REPORTED_STATE: + pass + elif playerState == PURCHASE_CANTREPORT_STATE: + pass + else: + self.notify.warning("Weird report state: " + str(playerState)) + return unreportedCounter + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + # Find the avatar's index + index = self.findAvIndex(avId) + if index == None: + self.notify.warning("Something is seriously screwed up..." + + "An avatar exited unexpectedly, and they" + + " are not on my list!") + else: + self.playerStates[index] = PURCHASE_DISCONNECTED_STATE + self.playersReported[index] = PURCHASE_CANTREPORT_STATE + self.d_setPlayerStates(self.playerStates) + if self.receivingButtons: + if self.getNumUndecided() == 0: + self.timeIsUp() + # changed from elif to if to force cleanup (they were leaking) - grw + if self.receivingInventory: + if self.getNumUnreported() == 0: + self.shutDown() + + return None + + def handlePlayerLeaving(self, avId): + pass + + def getMetagameRound(self): + return self.metagameRound + + def getVotesArray(self): + return self.votesArray diff --git a/toontown/src/shtiker/PurchaseManagerConstants.py b/toontown/src/shtiker/PurchaseManagerConstants.py new file mode 100644 index 0000000..204ca14 --- /dev/null +++ b/toontown/src/shtiker/PurchaseManagerConstants.py @@ -0,0 +1,11 @@ +PURCHASE_NO_CLIENT_STATE = 0 +PURCHASE_WAITING_STATE = 1 +PURCHASE_PLAYAGAIN_STATE = 2 +PURCHASE_EXIT_STATE = 3 +PURCHASE_DISCONNECTED_STATE = 4 + +PURCHASE_UNREPORTED_STATE = 10 +PURCHASE_REPORTED_STATE = 11 +PURCHASE_CANTREPORT_STATE = 12 + +PURCHASE_COUNTDOWN_TIME = 120 diff --git a/toontown/src/shtiker/QuestPage.py b/toontown/src/shtiker/QuestPage.py new file mode 100644 index 0000000..69c7e29 --- /dev/null +++ b/toontown/src/shtiker/QuestPage.py @@ -0,0 +1,167 @@ +from pandac.PandaModules import * +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.quest import Quests +from toontown.toon import NPCToons +from toontown.hood import ZoneUtil +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.quest import QuestPoster + +class QuestPage(ShtikerPage.ShtikerPage): + + # special methods + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + # quests maps page index to (questId, npcId) + self.quests = { 0 : None, + 1 : None, + 2 : None, + 3 : None, + } + self.textRolloverColor = Vec4(1,1,0,1) + self.textDownColor = Vec4(0.5,0.9,1,1) + self.textDisabledColor = Vec4(0.4,0.8,0.4,1) + self.onscreen = 0 + self.lastQuestTime = globalClock.getRealTime() + + def load(self): + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.QuestPageToonTasks, + text_scale = 0.12, + textMayChange = 0, + pos = (0,0,0.6), + ) + + # Throw in a little random roll and scale variations for interest + #questFramePlaceList = ((-0.45,0,0.25,0,0,2,1.04, 1.09, 1.09), + # (-0.45,0,-0.35,0,0,-2,1.077, 1.077, 1.023), + # (0.45,0,0.25,0,0,3,1.08, 1.102, 1.08), + # (0.45,0,-0.35,0,0,0,1.095, 1.095, 1.07), + # ) + # The random roll does not look so good with the new artwork + # and progress bars (they alias badly). Scale looks bad without the roll + questFramePlaceList = ((-0.45,0,0.25,0,0,0), + (-0.45,0,-0.35,0,0,0), + (0.45,0,0.25,0,0,0), + (0.45,0,-0.35,0,0,0), + ) + + self.questFrames = [] + + for i in range(ToontownGlobals.MaxQuestCarryLimit): + # reverse the poster graphic on the right-hand page + frame = QuestPoster.QuestPoster(reverse=(i>1)) + frame.reparentTo(self) + frame.setPosHpr(*questFramePlaceList[i]) + frame.setScale(1.06) + self.questFrames.append(frame) + + def acceptOnscreenHooks(self): + self.accept(ToontownGlobals.QuestsHotkeyOn, self.showQuestsOnscreen) + self.accept(ToontownGlobals.QuestsHotkeyOff, self.hideQuestsOnscreen) + + def ignoreOnscreenHooks(self): + self.ignore(ToontownGlobals.QuestsHotkeyOn) + self.ignore(ToontownGlobals.QuestsHotkeyOff) + + def unload(self): + del self.title + del self.quests + del self.questFrames + loader.unloadModel("phase_3.5/models/gui/stickerbook_gui") + ShtikerPage.ShtikerPage.unload(self) + + def clearQuestFrame(self, index): + self.questFrames[index].clear() + self.quests[index] = None + + def fillQuestFrame(self, questDesc, index): + self.questFrames[index].update(questDesc) + self.quests[index] = questDesc + + def getLowestUnusedIndex(self): + for i in range(ToontownGlobals.MaxQuestCarryLimit): + if self.quests[i] == None: + return i + return -1 + + def updatePage(self): + newQuests = base.localAvatar.quests + carryLimit = base.localAvatar.getQuestCarryLimit() + + # Color the frames you can have quests in blue + # Color the frames you cannot use yet alpha + for i in range(ToontownGlobals.MaxQuestCarryLimit): + if i < carryLimit: + self.questFrames[i].show() + else: + self.questFrames[i].hide() + + # This is annoying - the newQuests are lists (not tuples) but + # the keys to the page's quest dict must be tuples (not lists) + # so they are immutable hashable keys. Convert where appropriate. + for index, questDesc in self.quests.items(): + if ((questDesc is not None) and (list(questDesc) not in newQuests)): + # Must be an old quest we have completed + self.clearQuestFrame(index) + + # Add new quests + for questDesc in newQuests: + newQuestDesc = tuple(questDesc) + if newQuestDesc not in self.quests.values(): + index = self.getLowestUnusedIndex() + self.fillQuestFrame(newQuestDesc, index) + + # Always update friend quests to see if they have changed + for i in self.quests.keys(): + questDesc = self.quests[i] + if questDesc: + questId = questDesc[0] + if (Quests.getQuestClass(questId) == Quests.FriendQuest): + self.questFrames[i].update(questDesc) + + def enter(self): + """enter(self) + """ + self.updatePage() + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + """exit(self) + """ + ShtikerPage.ShtikerPage.exit(self) + + def showQuestsOnscreenTutorial(self): + self.setPos(0, 0, -0.2) + self.showQuestsOnscreen() + + def showQuestsOnscreen(self): + messenger.send('wakeup') + timedif = globalClock.getRealTime() - self.lastQuestTime + if timedif < 0.7: + return + self.lastQuestTime = globalClock.getRealTime() + if self.onscreen or base.localAvatar.invPage.onscreen: + return + self.onscreen = 1 + self.updatePage() + self.reparentTo(aspect2d) + self.title.hide() + self.show() + + def hideQuestsOnscreenTutorial(self): + self.setPos(0, 0, 0) + self.hideQuestsOnscreen() + + def hideQuestsOnscreen(self): + if not self.onscreen: + return + self.onscreen = 0 + self.reparentTo(self.book) + self.title.show() + self.hide() + diff --git a/toontown/src/shtiker/ShardPage.py b/toontown/src/shtiker/ShardPage.py new file mode 100644 index 0000000..f8ecc2b --- /dev/null +++ b/toontown/src/shtiker/ShardPage.py @@ -0,0 +1,554 @@ +"""ShardPage module: contains the ShardPage class""" + +from pandac.PandaModules import * +import ShtikerPage +from direct.task.Task import Task +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import ZoneUtil +from toontown.toonbase import ToontownGlobals +from toontown.distributed import ToontownDistrictStats +from toontown.toontowngui import TTDialog + + + +POP_COLORS_NTT = ( + Vec4(0.,1.,0.,1.), + Vec4(1.,1.,0.,1.), + Vec4(1.,0.,0.,1.), + ) + +POP_COLORS = ( + Vec4(0.4,0.4,1.,1.), + Vec4(0.4,1.,0.4,1.), + Vec4(1.,0.4,0.4,1.), + ) + +class ShardPage(ShtikerPage.ShtikerPage): + """ShardPage class""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ShardPage") + + # special methods + def __init__(self): + """__init__(self) + ShardPage constructor: create the shard selector page + """ + ShtikerPage.ShtikerPage.__init__(self) + + self.shardButtonMap = {} + self.shardButtons = [] + self.scrollList = None + + self.textRolloverColor = Vec4(1,1,0,1) + self.textDownColor = Vec4(0.5,0.9,1,1) + self.textDisabledColor = Vec4(0.4,0.8,0.4,1) + self.ShardInfoUpdateInterval = 5.0 # seconds + + self.lowPop, self.midPop, self.highPop = base.getShardPopLimits() + self.showPop = config.GetBool("show-total-population", 0) + self.noTeleport = config.GetBool("shard-page-disable", 0) + + def load(self): + main_text_scale = 0.06 + title_text_scale = 0.12 + + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.ShardPageTitle, + text_scale = title_text_scale, + textMayChange = 0, + pos = (0,0,0.6), + ) + + helpText_ycoord = 0.403 + + self.helpText = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = main_text_scale, + text_wordwrap = 12, + text_align = TextNode.ALeft, + textMayChange = 1, + pos = (0.058, 0, helpText_ycoord), + ) + + shardPop_ycoord = helpText_ycoord - 0.523 + totalPop_ycoord = shardPop_ycoord - 0.26 + + self.totalPopulationText = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.ShardPagePopulationTotal % (1), + text_scale = main_text_scale, + text_wordwrap = 8, + textMayChange = 1, + text_align = TextNode.ACenter, + pos = (0.38, 0, totalPop_ycoord), + ) + + if self.showPop: + self.totalPopulationText.show() + else: + self.totalPopulationText.hide() + + self.gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + self.listXorigin = -0.02 + self.listFrameSizeX = 0.67 + self.listZorigin = -0.96 + self.listFrameSizeZ = 1.04 + self.arrowButtonScale = 1.3 + self.itemFrameXorigin = -0.237 + self.itemFrameZorigin = 0.365 + self.buttonXstart = self.itemFrameXorigin + 0.293 + + self.regenerateScrollList() + + scrollTitle = DirectFrame( + parent = self.scrollList, + text = TTLocalizer.ShardPageScrollTitle, + text_scale = main_text_scale, + text_align = TextNode.ACenter, + relief = None, + pos = (self.buttonXstart, 0, self.itemFrameZorigin+0.127), + ) + + def unload(self): + self.gui.removeNode() + del self.title + self.scrollList.destroy() + del self.scrollList + del self.shardButtons + taskMgr.remove('ShardPageUpdateTask-doLater') + ShtikerPage.ShtikerPage.unload(self) + + def regenerateScrollList(self): + selectedIndex = 0 + if self.scrollList: + selectedIndex = self.scrollList.getSelectedIndex() + for button in self.shardButtons: + button.detachNode() + self.scrollList.destroy() + self.scrollList = None + + self.scrollList = DirectScrolledList( + parent = self, + relief = None, + pos = (-0.5,0,0), + # inc and dec are DirectButtons + # incButton is on the bottom of page, decButton is on the top! + incButton_image = (self.gui.find("**/FndsLst_ScrollUp"), + self.gui.find("**/FndsLst_ScrollDN"), + self.gui.find("**/FndsLst_ScrollUp_Rllvr"), + self.gui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_scale = (self.arrowButtonScale,self.arrowButtonScale,-self.arrowButtonScale), + incButton_pos = (self.buttonXstart,0,self.itemFrameZorigin-0.999), + # Make the disabled button fade out + incButton_image3_color = Vec4(1,1,1,0.2), + decButton_image = (self.gui.find("**/FndsLst_ScrollUp"), + self.gui.find("**/FndsLst_ScrollDN"), + self.gui.find("**/FndsLst_ScrollUp_Rllvr"), + self.gui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_scale = (self.arrowButtonScale,self.arrowButtonScale,self.arrowButtonScale), + #decButton_pos = (self.buttonXstart,0,self.itemFrameZorigin+0.127), + decButton_pos = (self.buttonXstart,0,self.itemFrameZorigin+0.227), + # Make the disabled button fade out + decButton_image3_color = Vec4(1,1,1,0.2), + + # itemFrame is a DirectFrame + itemFrame_pos = (self.itemFrameXorigin,0,self.itemFrameZorigin), + itemFrame_scale = 1.0, + itemFrame_relief = DGG.SUNKEN, + # frameSize is (minX,maxX,minZ,maxZ); where x goes left->right neg->pos, + # and z goes bottom->top neg->pos + itemFrame_frameSize = (self.listXorigin,self.listXorigin+self.listFrameSizeX, + self.listZorigin,self.listZorigin+self.listFrameSizeZ), + itemFrame_frameColor = (0.85,0.95,1,1), + itemFrame_borderWidth = (0.01,0.01), + # each item is a button with text on it + numItemsVisible = 15, + # need to set height of each entry to avoid list text running off end of listbox + forceHeight = 0.065, + items = self.shardButtons, + ) + self.scrollList.scrollTo(selectedIndex) + + def askForShardInfoUpdate(self, task=None): + ToontownDistrictStats.refresh('shardInfoUpdated') + # repeat request several seconds in the future + taskMgr.doMethodLater(self.ShardInfoUpdateInterval, self.askForShardInfoUpdate, 'ShardPageUpdateTask-doLater') + return Task.done + + def makeShardButton(self, shardId, shardName, shardPop): + + shardButtonParent = DirectFrame() + + shardButtonL = DirectButton( + parent = shardButtonParent, + relief = None, + text = shardName, + text_scale = 0.06, + text_align = TextNode.ALeft, + text1_bg = self.textDownColor, + text2_bg = self.textRolloverColor, + text3_fg = self.textDisabledColor, + textMayChange = 0, + command = self.getPopChoiceHandler(shardPop), + extraArgs = [shardId], + ) + + # live ops has requested a dconfig to show shard pop - so we will support both + if self.showPop: + popText = str(shardPop) + if shardPop == None: + popText = "" + shardButtonR = DirectButton( + parent = shardButtonParent, + relief = None, + text = popText, + text_scale = 0.06, + text_align = TextNode.ALeft, + text1_bg = self.textDownColor, + text2_bg = self.textRolloverColor, + text3_fg = self.textDisabledColor, + textMayChange = 1, + pos = (0.5, 0, 0), + command = self.choseShard, + extraArgs = [shardId], + ) + else: + model = loader.loadModel('phase_3.5/models/gui/matching_game_gui') + button = model.find('**/minnieCircle') + + shardButtonR = DirectButton( + parent = shardButtonParent, + relief = None, + image = button, + image_scale = (0.3, 1, 0.3), + image2_scale = (0.35, 1, 0.35), + image_color = self.getPopColor(shardPop), + pos = (0.6, 0, 0.0125), + text = self.getPopText(shardPop), + text_scale = 0.06, + text_align = TextNode.ACenter, + text_pos = (-0.0125, -0.0125), + # only rollover text visible on "stop lights" + text_fg = Vec4(0,0,0,0), + text1_fg = Vec4(0,0,0,0), + text2_fg = Vec4(0,0,0,1), + text3_fg = Vec4(0,0,0,0), + command = self.getPopChoiceHandler(shardPop), + extraArgs = [shardId], + ) + + del model + del button + + return (shardButtonParent, shardButtonR, shardButtonL) + + def getPopColor(self, pop): + """ + Choose the appropriate color based on the population size. + The Japanese client is a little different from the others. + """ + if base.cr.productName == "JP": + if pop < self.midPop: + color1 = POP_COLORS_NTT[0] + color2 = POP_COLORS_NTT[1] + popRange = self.midPop - self.lowPop + pop = pop - self.lowPop + else: + color1 = POP_COLORS_NTT[1] + color2 = POP_COLORS_NTT[2] + popRange = self.highPop - self.midPop + pop = pop - self.midPop + popPercent = pop / float(popRange) + if popPercent > 1: + popPercent = 1 + newColor = (color2 * popPercent) + (color1 * (1 - popPercent)) + else: + if pop <= self.lowPop: + newColor = POP_COLORS[0] + elif pop <= self.midPop: + newColor = POP_COLORS[1] + else: + newColor = POP_COLORS[2] + return newColor + + def getPopText(self, pop): + """ + Choose the appropriate population description text. + """ + if pop <= self.lowPop: + popText = TTLocalizer.ShardPageLow + elif pop <= self.midPop: + popText = TTLocalizer.ShardPageMed + else: + popText = TTLocalizer.ShardPageHigh + return popText + + def getPopChoiceHandler(self, pop): + """ + Returns the appropriate handler for a given shard. + If pop is high, it does not allow the user to enter it. + The showPop flag allows devs to see shard pops and teleport anywhere. + The noTeleport flag allows devs to disable all shard hopping. + The showPop flag overrides the noTeleport flag. + """ + if base.cr.productName == "JP": + handler = self.choseShard + elif pop <= self.midPop: + if self.noTeleport and not self.showPop: + handler = self.shardChoiceReject + else: + handler = self.choseShard + else: + if self.showPop: + # we are a dev - allow the teleport + handler = self.choseShard + else: + # deny + handler = self.shardChoiceReject + return handler + + def getCurrentZoneId(self): + try: + zoneId = base.cr.playGame.getPlace().getZoneId() + except: + zoneId = None + return zoneId + + def getCurrentShardId(self): + # Returns the user's current shard if we are not in + # WelcomeValley, or WelcomeValleyToken if we are in + # WelcomeValley. + zoneId = self.getCurrentZoneId() + if zoneId != None and ZoneUtil.isWelcomeValley(zoneId): + return ToontownGlobals.WelcomeValleyToken + else: + return base.localAvatar.defaultShard + + def updateScrollList(self): + # curShardTuples is a list of 3-item tuples (shardId, shardName, shardPopulation) + curShardTuples = base.cr.listActiveShards() + + """ + # For testing + curShardTuples = [(200000000, 'Shard Name 1', 0, 50), + (200000001, 'Shard Name 2', 16, 50), + (200000002, 'Shard Name 3', 32, 50), + (200000003, 'Shard Name 4', 48, 50), + (200000004, 'Shard Name 5', 64, 50), + (200000005, 'Shard Name 6', 80, 50), + (200000006, 'Shard Name 7', 96, 50), + (200000007, 'Shard Name 8', 112, 50), + (200000008, 'Shard Name 9', 128, 50), + (200000009, 'Shard Name 10', 144, 50), + (200000010, 'Shard Name 11', 160, 50), + (200000011, 'Shard Name 12', 176, 50), + (200000012, 'Shard Name 13', 192, 50), + (200000013, 'Shard Name 14', 208, 50), + (200000014, 'Shard Name 15', 224, 50), + (200000015, 'Shard Name 16', 240, 50), + (200000016, 'Shard Name 17', 256, 50), + (200000017, 'Shard Name 18', 272, 50), + (200000018, 'Shard Name 19', 288, 50), + (200000019, 'Shard Name 20', 304, 50), + (200000020, 'Shard Name 21', 404, 50), + (200000021, 'Shard Name 22', 808, 50), + ] + """ + + # Sort the shard list into alphabetical order before we append + # Welcome Valley onto the end of the list. + def compareShardTuples(a, b): + if a[1] < b[1]: + return -1 + elif b[1] < a[1]: + return 1 + else: + return 0 + curShardTuples.sort(compareShardTuples) + + if base.cr.welcomeValleyManager: + curShardTuples.append((ToontownGlobals.WelcomeValleyToken, + TTLocalizer.WelcomeValley[-1], 0, 0)) + + #print "curShardTuples=",curShardTuples + #print "self.shardButtns.keys=",self.shardButtons.keys() + + currentShardId = self.getCurrentShardId() + actualShardId = base.localAvatar.defaultShard + actualShardName = None + + anyChanges = 0 + totalPop = 0 + totalWVPop = 0 + currentMap = {} + self.shardButtons = [] + for i in range(len(curShardTuples)): + shardId, name, pop, WVPop = curShardTuples[i] + if shardId == actualShardId: + actualShardName = name + + totalPop += pop + totalWVPop += WVPop + + # this is useful for shard balancing, but not machine load balancing + #pop -= WVPop + + currentMap[shardId] = 1 + buttonTuple = self.shardButtonMap.get(shardId) + if buttonTuple == None: + # This is a new shard; add it to the list. + buttonTuple = self.makeShardButton(shardId, name, pop) + self.shardButtonMap[shardId] = buttonTuple + anyChanges = 1 + else: + # This is an existing shard; update the pop. + if self.showPop: + buttonTuple[1]['text'] = str(pop) + else: + buttonTuple[1]['image_color'] = self.getPopColor(pop) + # all but the Japanese product get the new scheme + if not base.cr.productName == "JP": + buttonTuple[1]['text'] = self.getPopText(pop) + buttonTuple[1]['command'] = self.getPopChoiceHandler(pop) + buttonTuple[2]['command'] = self.getPopChoiceHandler(pop) + + self.shardButtons.append(buttonTuple[0]) + + # Enable or disable the button appropriately. + if (shardId == currentShardId or self.book.safeMode): + buttonTuple[1]['state'] = DGG.DISABLED + buttonTuple[2]['state'] = DGG.DISABLED + else: + buttonTuple[1]['state'] = DGG.NORMAL + buttonTuple[2]['state'] = DGG.NORMAL + + # Now look for shards that are no longer on the list. + for shardId, buttonTuple in self.shardButtonMap.items(): + if shardId not in currentMap: + # This shard should be removed. + buttonTuple[0].destroy() + del self.shardButtonMap[shardId] + anyChanges = 1 + + # Set the population for WelcomeValley properly, as the sum of + # all of the WelcomeValley hoods across all shards. + buttonTuple = self.shardButtonMap.get(ToontownGlobals.WelcomeValleyToken) + if buttonTuple: + if self.showPop: + buttonTuple[1]['text'] = str(totalWVPop) + else: + buttonTuple[1]['image_color'] = self.getPopColor(totalWVPop) + # all but the Japanese product get the new scheme + if not base.cr.productName == "JP": + buttonTuple[1]['text'] = self.getPopText(totalWVPop) + buttonTuple[1]['command'] = self.getPopChoiceHandler(totalWVPop) + buttonTuple[2]['command'] = self.getPopChoiceHandler(totalWVPop) + + if anyChanges: + self.regenerateScrollList() + + self.totalPopulationText["text"] = TTLocalizer.ShardPagePopulationTotal % (totalPop) + + helpText = TTLocalizer.ShardPageHelpIntro + + # Is the current shard on the list? It should be, but + # something might have gone wrong. + if actualShardName: + if currentShardId == ToontownGlobals.WelcomeValleyToken: + helpText += (TTLocalizer.ShardPageHelpWelcomeValley % (actualShardName)) + else: + helpText += (TTLocalizer.ShardPageHelpWhere % (actualShardName)) + + if (not self.book.safeMode): + helpText += TTLocalizer.ShardPageHelpMove + + self.helpText["text"] = helpText + + def enter(self): + self.askForShardInfoUpdate() + self.updateScrollList() + + # Center on the current shard. + currentShardId = self.getCurrentShardId() + buttonTuple = self.shardButtonMap.get(currentShardId) + if buttonTuple: + i = self.shardButtons.index(buttonTuple[0]) + self.scrollList.scrollTo(i, centered = 1) + + ShtikerPage.ShtikerPage.enter(self) + self.accept('shardInfoUpdated', self.updateScrollList) + + def exit(self): + self.ignore('shardInfoUpdated') + taskMgr.remove('ShardPageUpdateTask-doLater') + ShtikerPage.ShtikerPage.exit(self) + + def shardChoiceReject(self, shardId): + # we have denied the user's request to move to a crowded shard + self.confirm = TTDialog.TTGlobalDialog( + doneEvent = "confirmDone", + message = TTLocalizer.ShardPageChoiceReject, + style = TTDialog.Acknowledge) + self.confirm.show() + self.accept("confirmDone", self.__handleConfirm) + + def __handleConfirm(self): + """__handleConfirm(self) + """ + self.ignore("confirmDone") + self.confirm.cleanup() + del self.confirm + + def choseShard(self, shardId): + zoneId = self.getCurrentZoneId() + canonicalHoodId = ZoneUtil.getCanonicalHoodId(base.localAvatar.lastHood) + currentShardId = self.getCurrentShardId() + + if shardId == currentShardId: + return + + elif shardId == ToontownGlobals.WelcomeValleyToken: + # This is a special case: it's really more like + # teleporting to a neighborhood, rather than actually + # switching shards. + self.doneStatus = {"mode" : "teleport", + "hood" : ToontownGlobals.WelcomeValleyToken, + } + messenger.send(self.doneEvent) + + elif shardId == base.localAvatar.defaultShard: + # Also, going back to our original real shard is just a + # teleport back to our canonical zone. + + self.doneStatus = {"mode" : "teleport", + "hood" : canonicalHoodId, + } + messenger.send(self.doneEvent) + + else: + try: + place = base.cr.playGame.getPlace() + except: + try: + place = base.cr.playGame.hood.loader.place + except: + place = base.cr.playGame.hood.place + + # Switching to a real shard takes you out of WelcomeValley + # (and hence into your canonical hoodId). + place.requestTeleport(canonicalHoodId, canonicalHoodId, shardId, -1) + return + diff --git a/toontown/src/shtiker/ShtikerBook.py b/toontown/src/shtiker/ShtikerBook.py new file mode 100644 index 0000000..6af355f --- /dev/null +++ b/toontown/src/shtiker/ShtikerBook.py @@ -0,0 +1,658 @@ +"""ShtikerBook module: contains the ShtikerBook class""" + +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from direct.showbase import DirectObject +from direct.fsm import StateData +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from toontown.effects import DistributedFireworkShow +from toontown.parties import DistributedPartyFireworksActivity +from direct.directnotify import DirectNotifyGlobal + +class ShtikerBook(DirectFrame, StateData.StateData): + """ShtikerBook class""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ShtikerBook") + + # special methods + def __init__(self, doneEvent): + DirectFrame.__init__(self, + relief = None, + sortOrder = DGG.BACKGROUND_SORT_INDEX) + self.initialiseoptions(ShtikerBook) + StateData.StateData.__init__(self, doneEvent) + self.pages = [] + self.pageTabs = [] + self.currPageTabIndex = None + self.pageTabFrame = DirectFrame(parent = self, relief = None, + pos = (0.93, 1, 0.575), + scale = 1.25) + self.pageTabFrame.hide() + self.currPageIndex = None + # what was the last shtiker page he was on before he went to news + self.pageBeforeNews = None + self.entered = 0 + self.safeMode = 0 + self.__obscured = 0 + self.__shown = 0 + self.__isOpen = 0 + self.hide() + # Slide the whole book up a tad to keep it out of the space + # reserved for the onscreen chat balloons. + self.setPos(0, 0, 0.1) + + self.pageOrder = [ + TTLocalizer.OptionsPageTitle, + TTLocalizer.ShardPageTitle, + TTLocalizer.MapPageTitle, + TTLocalizer.InventoryPageTitle, + TTLocalizer.QuestPageToonTasks, + TTLocalizer.TrackPageShortTitle, + TTLocalizer.SuitPageTitle, + TTLocalizer.FishPageTitle, + TTLocalizer.KartPageTitle, + TTLocalizer.DisguisePageTitle, + TTLocalizer.NPCFriendPageTitle, + TTLocalizer.GardenPageTitle, + TTLocalizer.GolfPageTitle, + TTLocalizer.EventsPageName, + TTLocalizer.NewsPageName + ] + + if __debug__: + base.sb = self + + def setSafeMode(self, setting): + """ + Safe Mode is primarily used for the tutorial. When pages enter(), they + should check this flag on the book to turn off buttons or options that + may allow the guest to escape or otherwise damage the movie + """ + self.safeMode = setting + + def enter(self): + if self.entered: + return + self.entered = 1 + + # If we're currently in move-furniture mode, stop it. + messenger.send("releaseDirector") + + # Send a message saying that we have entered. We can use this to hide boarding gui, etc. + messenger.send("stickerBookEntered") + + # play the book open sound + base.playSfx(self.openSound) + + # turn off any user control + base.disableMouse() + + # hide the world and turn on screen clear color + base.render.hide() + base.setBackgroundColor(0.05, 0.15, 0.4) + + # Turn off the one nametag cell that obscures some of the page + # tabs. + base.setCellsAvailable([base.rightCells[0]], 0) + + # Make the onscreen chat messages, etc. be somewhat more opaque. + self.oldMin2dAlpha = NametagGlobals.getMin2dAlpha() + self.oldMax2dAlpha = NametagGlobals.getMax2dAlpha() + NametagGlobals.setMin2dAlpha(0.8) + NametagGlobals.setMax2dAlpha(1.0) + + # switch from the open to the close button + self.__isOpen = 1 + self.__setButtonVisibility() + + # manage the book + self.show() + self.showPageArrows() + + if not self.safeMode: + # register events + self.accept("shtiker-page-done", self.__pageDone) + self.accept(ToontownGlobals.StickerBookHotkey, self.__close) + # Add hooks so the keyboard arrow keys work too +## self.accept("arrow_right", self.__pageChange, [1]) +## self.accept("arrow_left", self.__pageChange, [-1]) + # Show the jump buttons + self.pageTabFrame.show() + + # Enter the current page + self.pages[self.currPageIndex].enter() + + def exit(self): + """exit(self) + Remove events and restore display + """ + if not self.entered: + return + self.entered = 0 + + # Send a message saying that we have exited. We can use this to show boarding gui, etc. + messenger.send("stickerBookExited") + + # play the book open sound + base.playSfx(self.closeSound) + + # Exit the current pagex + self.pages[self.currPageIndex].exit() + + # put the world back + base.render.show() + + setBlackBackground = 0 + for obj in base.cr.doId2do.values(): + if isinstance(obj, DistributedFireworkShow.DistributedFireworkShow) or \ + isinstance(obj, DistributedPartyFireworksActivity.DistributedPartyFireworksActivity): + setBlackBackground = 1 + + if setBlackBackground: + base.setBackgroundColor(Vec4(0,0,0,1)) + else: + base.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor) + + # Force all the textures to reload, just in case we switched + # GSG's from the OptionsPage while the book was open. + gsg = base.win.getGsg() + if gsg: + base.render.prepareScene(gsg) + + # Restore the opacity of the chat messages and nametags. + NametagGlobals.setMin2dAlpha(self.oldMin2dAlpha) + NametagGlobals.setMax2dAlpha(self.oldMax2dAlpha) + + # Restore the nametag cell. + base.setCellsAvailable([base.rightCells[0]], 1) + + # unmanage interface + self.__isOpen = 0 + self.hide() + self.hideButton() + + # kill any open dialog boxes + cleanupDialog("globalDialog") + + # Hide the page tabs in case you enter in safe mode next time + self.pageTabFrame.hide() + + self.ignore("shtiker-page-done") + self.ignore(ToontownGlobals.StickerBookHotkey) + self.ignore("arrow_right") + self.ignore("arrow_left") + + def load(self): + """load(self) + """ + self.checkGardenStarted = localAvatar.getGardenStarted() + # models + bookModel = loader.loadModel("phase_3.5/models/gui/stickerbook_gui") + self['image'] = bookModel.find("**/big_book") + self['image_scale'] = (2,1,1.5) + self.resetFrameSize() + + self.bookOpenButton = DirectButton( + image = (bookModel.find("**/BookIcon_CLSD"), + bookModel.find("**/BookIcon_OPEN"), + bookModel.find("**/BookIcon_RLVR"), + ), + relief = None, + pos = (1.175, 0, -0.83), + scale = 0.305, + command = self.__open, + ) + + self.bookCloseButton = DirectButton( + image = (bookModel.find("**/BookIcon_OPEN"), + bookModel.find("**/BookIcon_CLSD"), + bookModel.find("**/BookIcon_RLVR2"), + ), + relief = None, + pos = (1.175, 0, -0.83), + scale = 0.305, + command = self.__close, + ) + + self.bookOpenButton.hide() + self.bookCloseButton.hide() + + self.nextArrow = DirectButton( + parent = self, + relief = None, + image = (bookModel.find("**/arrow_button"), + bookModel.find("**/arrow_down"), + bookModel.find("**/arrow_rollover"), + ), + scale = (0.1, 0.1, 0.1), + pos = (0.838, 0, -0.661), + command = self.__pageChange, + extraArgs = [1], + ) + + self.prevArrow = DirectButton( + parent = self, + relief = None, + image = (bookModel.find("**/arrow_button"), + bookModel.find("**/arrow_down"), + bookModel.find("**/arrow_rollover"), + ), + scale = (-0.1, 0.1, 0.1), # negative to flip the image + pos = (-0.838, 0, -0.661), + command = self.__pageChange, + extraArgs = [-1], + ) + + bookModel.removeNode() + + # sounds + self.openSound = base.loadSfx( + "phase_3.5/audio/sfx/GUI_stickerbook_open.mp3") + self.closeSound = base.loadSfx( + "phase_3.5/audio/sfx/GUI_stickerbook_delete.mp3") + self.pageSound = base.loadSfx( + "phase_3.5/audio/sfx/GUI_stickerbook_turn.mp3") + + def unload(self): + """unload(self) + """ + loader.unloadModel("phase_3.5/models/gui/stickerbook_gui") + + self.destroy() + self.bookOpenButton.destroy() + del self.bookOpenButton + self.bookCloseButton.destroy() + del self.bookCloseButton + + self.nextArrow.destroy() + del self.nextArrow + self.prevArrow.destroy() + del self.prevArrow + + for page in self.pages: + page.unload() + del self.pages + + for pageTab in self.pageTabs: + pageTab.destroy() + del self.pageTabs + del self.currPageTabIndex + + del self.openSound + del self.closeSound + del self.pageSound + + def addPage(self, page, pageName = 'Page'): + """addPage(self, ShtikerPage) + add a page to the ShtikerBook ClassicFSM""" + if not (pageName in self.pageOrder): + self.notify.error('Trying to add page %s in the ShtickerBook. Page not listed in the order.' %pageName) + return + + pageIndex = 0 + if len(self.pages): + newIndex = len(self.pages) + prevIndex = newIndex - 1 + + # A page probably came in late. This page has to be inserted before the + # News Page, which happens to be the last page. + # This is done so that there is no gap in the tabs on the right side. + # Take care of all the indices now while inserting the new page. + + if (self.pages[prevIndex].pageName == TTLocalizer.NewsPageName): + self.pages.insert(prevIndex, page) + pageIndex = prevIndex + if (self.currPageIndex >= pageIndex): + self.currPageIndex += 1 + else: + self.pages.append(page) + pageIndex = len(self.pages) - 1 + else: + self.pages.append(page) + pageIndex = len(self.pages) - 1 + + page.setBook(self) + page.setPageName(pageName) + page.reparentTo(self) + self.addPageTab(page, pageIndex, pageName) + from toontown.shtiker import MapPage + if isinstance(page, MapPage.MapPage): + self.pageBeforeNews = page + + def addPageTab(self, page, pageIndex, pageName = 'Page'): + tabIndex = len(self.pageTabs) + def goToPage(): + messenger.send('wakeup') + base.playSfx(self.pageSound) + self.setPage(page) + localAvatar.newsButtonMgr.setGoingToNewsPageFromStickerBook(False) + localAvatar.newsButtonMgr.showAppropriateButton() + + def goToNewsPage(): + messenger.send('wakeup') + base.playSfx(self.pageSound) + localAvatar.newsButtonMgr.setGoingToNewsPageFromStickerBook(True) + localAvatar.newsButtonMgr.showAppropriateButton() + self.setPage(page) + +## yOffset = 0.07 * (len(self.pages) - 1) + yOffset = 0.07 * pageIndex + iconGeom = None + iconImage = None + iconScale = 1 + iconColor = Vec4(1) + buttonPressedCommand = goToPage + if pageName == TTLocalizer.OptionsPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/switch') + iconModels.detachNode() + elif pageName == TTLocalizer.ShardPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/district') + iconModels.detachNode() + elif pageName == TTLocalizer.MapPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/teleportIcon') + iconModels.detachNode() + elif pageName == TTLocalizer.InventoryPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/inventory_icons") + iconGeom = iconModels.find('**/inventory_tart') + iconScale = 7 + iconModels.detachNode() + elif pageName == TTLocalizer.QuestPageToonTasks: + iconModels = loader.loadModel( + "phase_3.5/models/gui/stickerbook_gui") + iconGeom = iconModels.find("**/questCard") + iconScale = 0.9 + iconModels.detachNode() + elif pageName == TTLocalizer.TrackPageShortTitle: + iconGeom = iconModels = loader.loadModel( + "phase_3.5/models/gui/filmstrip") + iconScale = 1.1 + iconColor = Vec4(.7,.7,.7,1) + iconModels.detachNode() + elif pageName == TTLocalizer.SuitPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/gui_gear') + iconModels.detachNode() + elif pageName == TTLocalizer.FishPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/fish') + iconModels.detachNode() + elif pageName == TTLocalizer.GardenPageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/gardenIcon') + iconModels.detachNode() + elif pageName == TTLocalizer.DisguisePageTitle: + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/disguise2') + iconColor = Vec4(.7,.7,.7,1) + iconModels.detachNode() + elif pageName == TTLocalizer.NPCFriendPageTitle: + iconModels = loader.loadModel( + 'phase_3.5/models/gui/playingCard') + iconImage = iconModels.find('**/card_back') + iconGeom = iconModels.find('**/logo') + iconScale = 0.22 + iconModels.detachNode() + elif( pageName == TTLocalizer.KartPageTitle ): + iconModels = loader.loadModel( "phase_3.5/models/gui/sos_textures" ) + iconGeom = iconModels.find( "**/kartIcon" ) + iconModels.detachNode() + elif( pageName == TTLocalizer.GolfPageTitle ): + iconModels = loader.loadModel( "phase_6/models/golf/golf_gui" ) + iconGeom = iconModels.find( "**/score_card_icon" ) + iconModels.detachNode() + elif( pageName == TTLocalizer.EventsPageName ): + iconModels = loader.loadModel("phase_4/models/parties/partyStickerbook") + iconGeom = iconModels.find('**/Stickerbook_PartyIcon') + iconModels.detachNode() + elif( pageName == TTLocalizer.NewsPageName ): + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/switch') + iconModels.detachNode() + buttonPressedCommand = goToNewsPage + # elif( pageName == TTLocalizer.TIPPageTitle ): + # iconModels = loader.loadModel( + # 'phase_3.5/models/gui/playingCard') + # iconImage = iconModels.find('**/card_back') + # iconGeom = iconModels.find('**/logo') + # iconScale = 0.22 + # iconModels.detachNode() + + # Changing the page name for the tab for the Options Page + if (pageName == TTLocalizer.OptionsPageTitle): + pageName = TTLocalizer.OptionsTabTitle + + pageTab = DirectButton( + parent = self.pageTabFrame, + relief = DGG.RAISED, + frameSize = (-0.575, 0.575, -0.575, 0.575), + borderWidth = (0.05,0.05), + text = ("","",pageName,""), + text_align = TextNode.ALeft, + text_pos = (1,-0.2), + text_scale = TTLocalizer.SBpageTab, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + image = iconImage, + image_scale = iconScale, + geom = iconGeom, + geom_scale = iconScale, + geom_color = iconColor, + pos = (0, 0, -yOffset), + scale = 0.06, + command = buttonPressedCommand) +## self.pageTabs.append(pageTab) + self.pageTabs.insert(pageIndex, pageTab) + + # hide the news page button, so only 1 way to go in and out of news + if pageName == TTLocalizer.NewsPageName: + pageTab.hide() + + def setPage(self, page, enterPage = True): + """setPage(self, ShtikerPage) + go to a specific page""" + # Exit the previous page if there was one + if self.currPageIndex is not None: + self.pages[self.currPageIndex].exit() + # Enter this page + self.currPageIndex = self.pages.index(page) + self.setPageTabIndex(self.currPageIndex) + if enterPage: + self.showPageArrows() + page.enter() + from toontown.shtiker import NewsPage + + if not isinstance(page, NewsPage.NewsPage): + self.pageBeforeNews = page + + def setPageBeforeNews(self, enterPage = True): + self.setPage(self.pageBeforeNews, enterPage) + + def setPageTabIndex(self, pageTabIndex): + if ((self.currPageTabIndex is not None) and + (pageTabIndex != self.currPageTabIndex)): + self.pageTabs[self.currPageTabIndex]['relief'] = DGG.RAISED + self.currPageTabIndex = pageTabIndex + self.pageTabs[self.currPageTabIndex]['relief'] = DGG.SUNKEN + + def isOnPage(self, page): + """Return True if the sticker book is on a certain page.""" + result = False + if self.currPageIndex is not None: + curPage = self.pages[self.currPageIndex] + if curPage == page: + result = True + return result + + + def obscureButton(self, obscured): + """obscureButton(self, int obscured) + Make the be button be obscured, regardless of show and hide + 1 = obscure, 0 = unobscured + """ + self.__obscured = obscured + self.__setButtonVisibility() + + def isObscured(self): + return self.__obscured + + def showButton(self): + """ + show the ShtikerBook button, but only if it is not obscured + """ + self.__shown = 1 + self.__setButtonVisibility() + localAvatar.newsButtonMgr.showAppropriateButton() + + def hideButton(self): + """ + hide the ShtikerBook button + """ + self.__shown = 0 + self.__setButtonVisibility() + localAvatar.newsButtonMgr.request('Hidden') + + def __setButtonVisibility(self): + if self.__isOpen: + # The close button must always be visible while the book + # is open. + self.bookOpenButton.hide() + self.bookCloseButton.show() + + elif self.__shown and not self.__obscured: + # The open button should be visible. + self.bookOpenButton.show() + self.bookCloseButton.hide() + + else: + # The open button is either hidden or obscured. + self.bookOpenButton.hide() + self.bookCloseButton.hide() + + def shouldBookButtonBeHidden(self): + """Mimic __setButtonVisibility returning True in the last case.""" + result = False + if self.__isOpen: + pass + elif self.__shown and not self.__obscured: + pass + else: + result = True + return result + + def __open(self): + messenger.send("enterStickerBook") + if not localAvatar.getGardenStarted(): + for tab in self.pageTabs: + if tab["text"][2] == TTLocalizer.GardenPageTitle: + tab.hide() + + def __close(self): + base.playSfx(self.closeSound) + self.doneStatus = {"mode" : "close"} + messenger.send("exitStickerBook") + messenger.send(self.doneEvent) + + def closeBook(self): + self.__close() + + def __pageDone(self): + page = self.pages[self.currPageIndex] + pageDoneStatus = page.getDoneStatus() + if pageDoneStatus: + if (pageDoneStatus["mode"] == "close"): + self.__close() + else: + self.doneStatus = pageDoneStatus + messenger.send(self.doneEvent) + + def __pageChange(self, offset): + messenger.send('wakeup') + base.playSfx(self.pageSound) + # Exit the current page + self.pages[self.currPageIndex].exit() + self.currPageIndex = (self.currPageIndex + offset) + messenger.send("stickerBookPageChange-" + str(self.currPageIndex)) + # Clamp the page index + self.currPageIndex = max(self.currPageIndex, 0) + self.currPageIndex = min(self.currPageIndex, (len(self.pages) - 1)) + self.setPageTabIndex(self.currPageIndex) + + self.showPageArrows() + + # Enter the new current page + page = self.pages[self.currPageIndex] + page.enter() + + from toontown.shtiker import NewsPage + if not isinstance(page, NewsPage.NewsPage): + self.pageBeforeNews = page + + def showPageArrows(self): + # If we are at the end of the list, disable the next button + if (self.currPageIndex == (len(self.pages) - 1)): + self.prevArrow.show() + self.nextArrow.hide() + else: + self.prevArrow.show() + self.nextArrow.show() + + self.checkForNewsPage() + + # If we are at the beginning of the list, disable the prev button + if (self.currPageIndex == 0): + self.prevArrow.hide() + self.nextArrow.show() + + def checkForNewsPage(self): + """ + Check if the next and previous page is the News Page. + """ + from toontown.shtiker import NewsPage + self.ignore("arrow_left") + self.ignore("arrow_right") + + if ((self.currPageIndex + 1) <= (len(self.pages) - 1)) and \ + (isinstance(self.pages[self.currPageIndex + 1], NewsPage.NewsPage)): + self.prevArrow.show() + self.nextArrow.hide() + self.accept("arrow_left", self.__pageChange, [-1]) + else: + self.prevArrow.show() + self.nextArrow.show() + if not (isinstance(self.pages[self.currPageIndex], NewsPage.NewsPage)): + # Add hooks so the keyboard arrow keys work too + self.accept("arrow_right", self.__pageChange, [1]) + self.accept("arrow_left", self.__pageChange, [-1]) + + # these two functions are for the tutorial + def disableBookCloseButton(self): + if self.bookCloseButton: + self.bookCloseButton['command'] = None + + def enableBookCloseButton(self): + if self.bookCloseButton: + self.bookCloseButton['command'] = self.__close + + def disableAllPageTabs(self): + """The news page overlaps the page tab buttons, but they are still clickable.""" + for button in self.pageTabs: + button['state'] = DGG.DISABLED + + def enableAllPageTabs(self): + """When closing the news page, renable them.""" + for button in self.pageTabs: + button['state'] = DGG.NORMAL diff --git a/toontown/src/shtiker/ShtikerPage.py b/toontown/src/shtiker/ShtikerPage.py new file mode 100644 index 0000000..650d374 --- /dev/null +++ b/toontown/src/shtiker/ShtikerPage.py @@ -0,0 +1,64 @@ +"""ShtikerPage module: contains the ShtikerPage class""" + +import ShtikerBook +from direct.fsm import StateData +from direct.gui.DirectGui import * +from pandac.PandaModules import * + +class ShtikerPage(DirectFrame, StateData.StateData): + """ShtikerPage class""" + + # special methods + def __init__(self): + """ + ShtikerPage constructor: create a shtiker book page + """ + DirectFrame.__init__(self, + relief = None, + sortOrder = DGG.BACKGROUND_SORT_INDEX) + self.initialiseoptions(ShtikerPage) + StateData.StateData.__init__(self, "shtiker-page-done") + self.book = None + self.hide() + + def load(self): + pass + + def unload(self): + self.ignoreAll() + del self.book + + def enter(self): + self.show() + + def exit(self): + self.hide() + + def setBook(self, book): + self.book = book + + def setPageName(self, pageName): + """ + Sets the name of the page to pageName. + """ + self.pageName = pageName + + def makePageWhite(self, item): + """makePageWhite(self): + Make the book backdrop poly color white + """ + white = Vec4(1,1,1,1) + self.book['image_color'] = white + self.book.nextArrow['image_color'] = white + self.book.prevArrow['image_color'] = white + + def makePageRed(self, item): + """makePageRed(self): + Make the book backdrop poly color red + """ + red = Vec4(1,0.5,0.5,1) + self.book['image_color'] = red + self.book.nextArrow['image_color'] = red + self.book.prevArrow['image_color'] = red + + diff --git a/toontown/src/shtiker/Sources.pp b/toontown/src/shtiker/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/shtiker/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/shtiker/SuitPage.py b/toontown/src/shtiker/SuitPage.py new file mode 100644 index 0000000..1d67a4e --- /dev/null +++ b/toontown/src/shtiker/SuitPage.py @@ -0,0 +1,770 @@ +"""SuitPage module: contains the SuitPage class""" + +import ShtikerPage +from direct.task.Task import Task +import SummonCogDialog +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.suit import SuitDNA +from toontown.suit import Suit +from toontown.battle import SuitBattleGlobals +from CogPageGlobals import * + +# how much to scale the cog panel on rollover +SCALE_FACTOR = 1.5 + +# how slowly the radar screen updates +RADAR_DELAY = 0.2 + +# positions for the building radar labels +BUILDING_RADAR_POS = (0.375, 0.065 ,-0.225, -0.5) + +PANEL_COLORS = ( + Vec4(0.8, 0.78, 0.77, 1), + Vec4(0.75, 0.78, 0.8, 1), + Vec4(0.75, 0.82, 0.79, 1), + Vec4(0.825, 0.76, 0.77, 1) + ) + +# TODO: get art to replace these simple color swaps... +PANEL_COLORS_COMPLETE1 = ( + Vec4(0.7, 0.725, 0.545, 1), + Vec4(0.625, 0.725, 0.65, 1), + Vec4(0.6, 0.75, 0.525, 1), + Vec4(0.675, 0.675, 0.55, 1) + ) + +PANEL_COLORS_COMPLETE2 = ( + Vec4(0.9, 0.725, 0.32, 1), + Vec4(0.825, 0.725, 0.45, 1), + Vec4(0.8, 0.75, 0.325, 1), + Vec4(0.875, 0.675, 0.35, 1) + ) + +# the shadows must be carefully positioned behind the heads +SHADOW_SCALE_POS = ( + # scale, x pos, y pos, z pos + # corp + (1.225, 0, 10, -0.03), + (0.9, 0, 10, 0), + (1.125, 0, 10, -0.015), + (1.0, 0, 10, -0.02), + (1.0, -0.02, 10, -0.01), + (1.05, 0, 10, -0.0425), + (1.0, 0, 10, -0.05), + (0.9, -0.0225, 10, -0.025), + # legal + (1.25, 0, 10, -0.03), + (1.0, 0, 10, -0.01), + (1.0, 0.005, 10, -0.01), + (1.0, 0, 10, -0.01), + (0.9, 0.005, 10, -0.01), + (0.95, 0, 10, -0.01), + (1.125, 0.005, 10, -0.035), + (0.85, -0.005, 10, -0.035), + # money + (1.2, 0, 10, -0.01), + (1.05, 0, 10, 0), + (1.1, 0, 10, -0.04), + (1.0, 0, 10, 0), + (0.95, 0.0175, 10, -0.015), + (1.0, 0, 10, -0.06), + (0.95, 0.02, 10, -0.0175), + (0.9 , 0, 10, -0.03), + # sales + (1.15, 0, 10, -0.01), + (1.0, 0, 10, 0), + (1.0, 0, 10, 0), + (1.1, 0, 10, -0.04), + (0.93, 0.005, 10, -0.01), + (0.95, 0.005, 10, -0.01), + (1.0, 0, 10, -0.02), + (0.9, 0.0025, 10, -0.03), + ) + +class SuitPage(ShtikerPage.ShtikerPage): + + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + + # suit page gui + frameModel = loader.loadModel('phase_3.5/models/gui/suitpage_frame') + frameModel.setScale(0.03375, 1, 0.045) + frameModel.setPos(0, 10, -0.575) + + # make some nodes to help organize the things + self.guiTop = NodePath('guiTop') + self.guiTop.reparentTo(self) + self.frameNode = NodePath('frameNode') + self.frameNode.reparentTo(self.guiTop) + self.panelNode = NodePath('panelNode') + self.panelNode.reparentTo(self.guiTop) + self.iconNode = NodePath('iconNode') + self.iconNode.reparentTo(self.guiTop) + self.enlargedPanelNode = NodePath('enlargedPanelNode') + self.enlargedPanelNode.reparentTo(self.guiTop) + + # make sure all this stuff draws in the correct order: + # + # - frame on bottom + # - panels over frame + # - title, icons and screws over panels + # - enlarged panels over everything + # + frame = frameModel.find('**/frame') + frame.wrtReparentTo(self.frameNode) + screws = frameModel.find('**/screws') + screws.wrtReparentTo(self.iconNode) + icons = frameModel.find('**/icons') + del frameModel + + # make the title + self.title = DirectLabel( + parent = self.iconNode, + relief = None, + text = TTLocalizer.SuitPageTitle, + text_scale = 0.1, + text_pos = (0.04, 0), + textMayChange = 0, + ) + + # make the radar buttons + self.radarButtons = [] + icon = icons.find('**/corp_icon') + self.corpRadarButton = DirectButton( + parent = self.iconNode, + relief = None, + state = DGG.DISABLED, + image = icon, + image_scale = (0.03375, 1, 0.045), + # stand in for rollover art + image2_color = Vec4(1.0,1.0,1.0,0.75), + pos = (-0.2, 10, -0.575), + command = self.toggleRadar, + extraArgs = [0], + ) + self.radarButtons.append(self.corpRadarButton) + icon = icons.find('**/legal_icon') + self.legalRadarButton = DirectButton( + parent = self.iconNode, + relief = None, + state = DGG.DISABLED, + image = icon, + image_scale = (0.03375, 1, 0.045), + # stand in for rollover art + image2_color = Vec4(1.0,1.0,1.0,0.75), + pos = (-0.2, 10, -0.575), + command = self.toggleRadar, + extraArgs = [1], + ) + self.radarButtons.append(self.legalRadarButton) + icon = icons.find('**/money_icon') + self.moneyRadarButton = DirectButton( + parent = self.iconNode, + relief = None, + state = DGG.DISABLED, + image = (icon, icon, icon), + image_scale = (0.03375, 1, 0.045), + # stand in for rollover art + image2_color = Vec4(1.0,1.0,1.0,0.75), + pos = (-0.2, 10, -0.575), + command = self.toggleRadar, + extraArgs = [2], + ) + self.radarButtons.append(self.moneyRadarButton) + icon = icons.find('**/sales_icon') + self.salesRadarButton = DirectButton( + parent = self.iconNode, + relief = None, + state = DGG.DISABLED, + image = (icon, icon, icon), + image_scale = (0.03375, 1, 0.045), + # stand in for rollover art + image2_color = Vec4(1.0,1.0,1.0,0.75), + pos = (-0.2, 10, -0.575), + command = self.toggleRadar, + extraArgs = [3], + ) + self.radarButtons.append(self.salesRadarButton) + + # this field will let us know when the builing radar is activated + for radarButton in self.radarButtons: + radarButton.building = 0 + radarButton.buildingRadarLabel = None + + # make panels for the suit heads + gui = loader.loadModel('phase_3.5/models/gui/suitpage_gui') + + # load the panel art work + self.panelModel = gui.find('**/card') + + # load the shadows for the suit heads + self.shadowModels = [] + # put them in order for easy retrieval + for index in range(1, len(SuitDNA.suitHeadTypes) + 1): + self.shadowModels.append(gui.find('**/shadow' + str(index))) + + del gui + + # make the individual panels for each suit + self.makePanels() + + # keep some state + self.radarOn = [0, 0, 0, 0] + + # scoot everything up + self.guiTop.setZ(0.625) + + def unload(self): + # clean up our DirectGui elements + self.title.destroy() + self.corpRadarButton.destroy() + self.legalRadarButton.destroy() + self.moneyRadarButton.destroy() + self.salesRadarButton.destroy() + # clean up our other gui elements + for panel in self.panels: + panel.destroy() + del self.panels + # clean up our master copies + for shadow in self.shadowModels: + shadow.removeNode() + self.panelModel.removeNode() + # call parent class unload + ShtikerPage.ShtikerPage.unload(self) + + def enter(self): + # make sure we reflect current cog status + self.updatePage() + self.bigPanel = None + self.nextPanel = None + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + # remove any do-later hooks + taskMgr.remove('buildingListResponseTimeout-later') + taskMgr.remove('suitListResponseTimeout-later') + taskMgr.remove('showCogRadarLater') + taskMgr.remove('showBuildingRadarLater') + # turn off any radars that are on and reset the button status + for index in range(0, len(self.radarOn)): + if self.radarOn[index]: + self.toggleRadar(index) + self.radarButtons[index]['state'] = DGG.NORMAL + ShtikerPage.ShtikerPage.exit(self) + + + # + # callbacks + # + + def grow(self, panel, pos): + #don't grow if there's already a big panel + if self.bigPanel: + print "setting next panel - " + str(panel) + self.nextPanel = panel + self.nextPanelPos = pos + return + + print "big panel - " + str(panel) + self.bigPanel = panel + # make sure it draws on top of other frames + panel.reparentTo(self.enlargedPanelNode) + # make the panel enlarge upon rollover + panel.setScale(panel.getScale() * SCALE_FACTOR) + if panel.summonButton: + panel.summonButton.show() + panel.summonButton['state'] = DGG.NORMAL + + def shrink(self, panel, pos): + print 'trying to shrink - ' + str(panel) + # calling shrink on a panel that's not enlarged + if panel != self.bigPanel: + self.nextPanel = None + return + + print 'shrink panel - ' + str(panel) + self.bigPanel = None + # make the panel shrink on rollover exit + panel.setScale(panel.scale) + # draw it normally with respect to the others + panel.reparentTo(self.panelNode) + if panel.summonButton: + panel.summonButton.hide() + panel.summonButton['state'] = DGG.DISABLED + + if self.nextPanel: + self.grow(self.nextPanel, self.nextPanelPos) + + + + def toggleRadar(self, deptNum): + messenger.send('wakeup') + + # toggle the cog/building radar display + if self.radarOn[deptNum]: + self.radarOn[deptNum] = 0 + else: + self.radarOn[deptNum] = 1 + + # figure out which panels are effected + deptSize = SuitDNA.suitsPerDept + panels = self.panels[deptSize*deptNum:SuitDNA.suitsPerDept*(deptNum+1)] + + if self.radarOn[deptNum]: + # if we have a handle to the suit planner + if hasattr(base.cr, 'currSuitPlanner'): + # make sure we are on a street w/ a suit planner + if base.cr.currSuitPlanner != None: + # ask the suit planner for a list of suits + base.cr.currSuitPlanner.d_suitListQuery() + # wait for the repsonse to come back from the suit planner + self.acceptOnce('suitListResponse', self.updateCogRadar, + extraArgs=[deptNum, panels]) + # start a timeout in case we never hear back from the suit planner + taskMgr.doMethodLater(1.0, self.suitListResponseTimeout, + 'suitListResponseTimeout-later', + extraArgs = (deptNum, panels)) + # if building radar is also enabled + if self.radarButtons[deptNum].building: + # ask the suit planner for a list of buildings + base.cr.currSuitPlanner.d_buildingListQuery() + # wait for the repsonse to come back from the suit planner + self.acceptOnce('buildingListResponse', self.updateBuildingRadar, + extraArgs=[deptNum]) + # start a timeout in case we never hear back from the suit planner + taskMgr.doMethodLater(1.0, self.buildingListResponseTimeout, + 'buildingListResponseTimeout-later', + extraArgs = (deptNum,)) + else: + # we must be in a building, put in zeros + self.updateCogRadar(deptNum, panels) + self.updateBuildingRadar(deptNum) + else: + # in a safezone, put zeroes in for the number of suits + self.updateCogRadar(deptNum, panels) + self.updateBuildingRadar(deptNum) + + # don't let us hit the button again while updating + self.radarButtons[deptNum]['state'] = DGG.DISABLED + else: + # turn off the radar + self.updateCogRadar(deptNum, panels) + self.updateBuildingRadar(deptNum) + + + def suitListResponseTimeout(self, deptNum, panels): + # ai is not responding, put zeroes in for the number of suits + self.updateCogRadar(deptNum, panels, 1) + + def buildingListResponseTimeout(self, deptNum): + # ai is not responding, put zeroes in for the number of buildings + self.updateBuildingRadar(deptNum, 1) + + # + # util + # + def makePanels(self): + self.panels = [] + base.panels = [] + xStart = -0.66 + yStart = -0.18 + xOffset = 0.199 + yOffset = 0.284 + # for each department + for dept in range(0, len(SuitDNA.suitDepts)): + row = [] + # set the color of the panel per dept + color = PANEL_COLORS[dept] + # for each type of suit in a dept + for type in range(0, SuitDNA.suitsPerDept): + # make a panel + panel = DirectLabel( + parent = self.panelNode, + pos = (xStart + (type * xOffset), + 0.0, + yStart - (dept * yOffset)), + relief = None, + state = DGG.NORMAL, + image = self.panelModel, + image_scale = (1, 1, 1), + image_color = color, + text = TTLocalizer.SuitPageMystery, + text_scale = 0.045, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0.185, 0), + text_font = ToontownGlobals.getSuitFont(), + text_wordwrap = 7 + ) + # add our special scaling functions + #panel.bind(DGG.WITHIN, self.grow, extraArgs=[panel]) + #panel.bind(DGG.WITHOUT, self.shrink, extraArgs=[panel]) + #panel.bind(DGG.ENTER, self.grow, extraArgs=[panel]) + #panel.bind(DGG.EXIT, self.shrink, extraArgs=[panel]) + panel.scale = 0.6 + panel.setScale(panel.scale) + + # these will be added as we progress + panel.quotaLabel = None + panel.head = None + panel.shadow = None + panel.count = 0 + panel.summonButton = None + + # this one which is added now to avoid display timing anomalies + self.addCogRadarLabel(panel) + + # add the panel to our master list + self.panels.append(panel) + base.panels.append(panel) + + def addQuotaLabel(self, panel): + # figure out the current quota + index = self.panels.index(panel) + count = str(base.localAvatar.cogCounts[index]) + if base.localAvatar.cogs[index] < COG_COMPLETE1: + quota = str(COG_QUOTAS[0][index % SuitDNA.suitsPerDept]) + else: + quota = str(COG_QUOTAS[1][index % SuitDNA.suitsPerDept]) + + # add the current quota + quotaLabel = DirectLabel( + parent = panel, + pos = (0.0, 0.0, -0.215), + relief = None, + state = DGG.DISABLED, + text = TTLocalizer.SuitPageQuota % (count, quota), + text_scale = 0.045, + text_fg = (0, 0, 0, 1), + text_font = ToontownGlobals.getSuitFont(), + ) + panel.quotaLabel = quotaLabel + + def addSuitHead(self, panel, suitName): + panelIndex = self.panels.index(panel) + + # add the shadow model + shadow = panel.attachNewNode('shadow') + shadowModel = self.shadowModels[panelIndex] + #shadowModel.setTransparency(0.5) + shadowModel.copyTo(shadow) + coords = SHADOW_SCALE_POS[panelIndex] + shadow.setScale(coords[0]) + shadow.setPos(coords[1], coords[2], coords[3]) + panel.shadow = shadow + + # add the suit head to the panel + panel.head = Suit.attachSuitHead(panel, suitName) + + def addCogRadarLabel(self, panel): + # make a label to show cog radar results + cogRadarLabel = DirectLabel( + parent = panel, + pos = (0.0, 0.0, -0.215), + relief = None, + state = DGG.DISABLED, + text = '', + text_scale = 0.05, + text_fg = (0, 0, 0, 1), + text_font = ToontownGlobals.getSuitFont(), + ) + panel.cogRadarLabel = cogRadarLabel + + def addSummonButton(self, panel): + # make a button for summoning this cog + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + #okButton = buttons.find('**/ChtBx_OKBtn_UP') + okButtonList = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')) + gui = loader.loadModel("phase_3.5/models/gui/stickerbook_gui") + iconGeom = gui.find('**/summons') + summonButton = DirectButton( + parent = panel, + #pos = (0.1, 0.0, 0.1), + pos = (0.1, 0.0, -0.13), + scale = 0.1, + relief = None, + state = DGG.NORMAL, + image = okButtonList, + image_scale = 13.0, + geom = iconGeom, + geom_scale = 0.7, + text = ("", TTLocalizer.IssueSummons, + TTLocalizer.IssueSummons, ""), + text_scale = 0.4, + #text_pos = (-0.7, -0.7), + text_pos = (-1.1, -0.4), + command = self.summonButtonPressed, + extraArgs = [panel] + ) + panel.summonButton = summonButton + + def summonButtonPressed(self, panel): + panelIndex = self.panels.index(panel) + self.summonDialog = SummonCogDialog.SummonCogDialog(panelIndex) + self.summonDialog.load() + self.accept(self.summonDialog.doneEvent, self.summonDone, extraArgs=[panel]) + self.summonDialog.enter() + + def summonDone(self, panel): + if self.summonDialog: + self.summonDialog.unload() + self.summonDialog = None + # if there are no summons left, hide the button + index = self.panels.index(panel) + if not base.localAvatar.hasCogSummons(index): + panel.summonButton.hide() + + def addBuildingRadarLabel(self, button): + # make a label to show building radar results + gui = loader.loadModel('phase_3.5/models/gui/suit_detail_panel') + # determine where to place it + zPos = BUILDING_RADAR_POS[self.radarButtons.index(button)] + buildingRadarLabel = DirectLabel( + parent = button, + relief = None, + pos = (0.225, 0.0, zPos), + state = DGG.DISABLED, + image = gui.find('**/avatar_panel'), + image_hpr = (0, 0, 90), + image_scale = (0.05, 1, 0.1), + image_pos = (0, 0, 0.015), + text = TTLocalizer.SuitPageBuildingRadarP % '0', + text_scale = 0.05, + text_fg = (1, 0, 0, 1), + text_font = ToontownGlobals.getSuitFont(), + ) + gui.removeNode() + button.buildingRadarLabel = buildingRadarLabel + + def resetPanel(self, dept, type): + panel = self.panels[(dept * SuitDNA.suitsPerDept) + type] + # reset the panel to unseen status + panel['text'] = TTLocalizer.SuitPageMystery + if panel.cogRadarLabel: + panel.cogRadarLabel.hide() + if panel.quotaLabel: + panel.quotaLabel.hide() + if panel.head: + panel.head.hide() + if panel.shadow: + panel.shadow.hide() + if panel.summonButton: + panel.summonButton.hide() + #panel.head.setScale(1.25) + #panel.shadow.setScale(1.25) + # set the color of the panel per dept + color = PANEL_COLORS[dept] + panel['image_color'] = color + # hide any building radar labels + for button in self.radarButtons: + if button.buildingRadarLabel: + button.buildingRadarLabel.hide() + + def setPanelStatus(self, panel, status): + index = self.panels.index(panel) + if (status == COG_UNSEEN): + # show nothing but question marks + panel['text'] = TTLocalizer.SuitPageMystery + elif (status == COG_BATTLED): + # show cog name and quota + suitName = SuitDNA.suitHeadTypes[index] + suitFullName = SuitBattleGlobals.SuitAttributes[suitName]['name'] + panel['text'] = suitFullName + # make or show the quota label + if panel.quotaLabel: + panel.quotaLabel.show() + else: + self.addQuotaLabel(panel) + # make or show the cog's head + if panel.head and panel.shadow: + panel.head.show() + panel.shadow.show() + else: + self.addSuitHead(panel, suitName) + #make or show the 'issue summons' button + if base.localAvatar.hasCogSummons(index): + if panel.summonButton: + panel.summonButton.show() + else: + self.addSummonButton(panel) + # shrink the head slightly + #panel.head.setScale(panel.head.getScale()*0.75) + #panel.shadow.setScale(panel.shadow.getScale()*0.75) + elif status == COG_DEFEATED: + # update count + count = str(base.localAvatar.cogCounts[index]) + # determine which quota we are working on + if base.localAvatar.cogs[index] < COG_COMPLETE1: + quota = str(COG_QUOTAS[0][index % SuitDNA.suitsPerDept]) + else: + quota = str(COG_QUOTAS[1][index % SuitDNA.suitsPerDept]) + panel.quotaLabel['text'] = TTLocalizer.SuitPageQuota % (count, quota) + elif status == COG_COMPLETE1: + # if first quota met show green frame + panel['image_color'] = PANEL_COLORS_COMPLETE1[(index / SuitDNA.suitsPerDept)] + elif status == COG_COMPLETE2: + # if second quota met show gold frame + panel['image_color'] = PANEL_COLORS_COMPLETE2[index / SuitDNA.suitsPerDept] + + + # + # update calls + # + + def updateAllCogs(self, status): + # for testing! + for index in range(0, len(base.localAvatar.cogs)): + base.localAvatar.cogs[index] = status + self.updatePage() + + def updatePage(self): + index = 0 + cogs = base.localAvatar.cogs + # loop through and call updateCogStatus for each cog + for dept in range(0, len(SuitDNA.suitDepts)): + for type in range(0, SuitDNA.suitsPerDept): + self.updateCogStatus(dept, type, cogs[index]) + index += 1 + self.updateCogRadarButtons(base.localAvatar.cogRadar) + self.updateBuildingRadarButtons(base.localAvatar.buildingRadar) + + def updateCogStatus(self, dept, type, status): + # make sure they passed in something reasonable + if ((dept < 0) or (dept > len(SuitDNA.suitDepts))): + print 'ucs: bad cog dept: ', dept + elif ((type < 0) or (type > SuitDNA.suitsPerDept)): + print 'ucs: bad cog type: ', type + elif ((status < COG_UNSEEN) or (status > COG_COMPLETE2)): + print 'ucs: bad status: ', status + else: + # go ahead and reset the panel to make sure we can + # gracefully switch from one state to any other state + self.resetPanel(dept, type) + panel = self.panels[(dept * SuitDNA.suitsPerDept) + type] + if (status == COG_UNSEEN): + self.setPanelStatus(panel, COG_UNSEEN) + elif (status == COG_BATTLED): + self.setPanelStatus(panel, COG_BATTLED) + elif status == COG_DEFEATED: + # this state is a cumulative one + self.setPanelStatus(panel, COG_BATTLED) + self.setPanelStatus(panel, COG_DEFEATED) + elif status == COG_COMPLETE1: + # this state is a cumulative one + self.setPanelStatus(panel, COG_BATTLED) + self.setPanelStatus(panel, COG_DEFEATED) + self.setPanelStatus(panel, COG_COMPLETE1) + elif status == COG_COMPLETE2: + # this state is a cumulative one + self.setPanelStatus(panel, COG_BATTLED) + self.setPanelStatus(panel, COG_DEFEATED) + self.setPanelStatus(panel, COG_COMPLETE2) + + + def updateCogRadarButtons(self, radars): + # turn on the appropriate radar button based on 'radars' list + for index in range(0, len(radars)): + if radars[index] == 1: + self.radarButtons[index]['state'] = DGG.NORMAL + + def updateCogRadar(self, deptNum, panels, timeout=0): + # remove timeout task + taskMgr.remove('suitListResponseTimeout-later') + # if we haven't timed out get the current suit list + if (not timeout and hasattr(base.cr, 'currSuitPlanner') + and base.cr.currSuitPlanner != None): + cogList = base.cr.currSuitPlanner.suitList + else: + cogList = [] + + # reset the cog counts for this dept + for panel in panels: + panel.count = 0 + + # get the latest counts from the suit planner info + for cog in cogList: + # the cogs names and panels are 1 to 1 mapping + self.panels[cog].count += 1 + + # update the cog radar label text for the dept in question + for panel in panels: + # update the label text + panel.cogRadarLabel['text'] = TTLocalizer.SuitPageCogRadar % panel.count + # case 1: show the radar, hide the quota + if self.radarOn[deptNum]: + panel.quotaLabel.hide() + def showLabel(label): + label.show() + taskMgr.doMethodLater(RADAR_DELAY * panels.index(panel), + showLabel, + 'showCogRadarLater', + extraArgs = (panel.cogRadarLabel,)) + def activateButton(s = self, index = deptNum): + self.radarButtons[index]['state'] = DGG.NORMAL + return Task.done + + if not self.radarButtons[deptNum].building: + # If we don't have building radar, we need to + # reactivate the button when we're done + # displaying. (If we *do* have building radar, the + # updateBuildingRadar function will take care of + # this.) + taskMgr.doMethodLater(RADAR_DELAY * len(panels), + activateButton, 'activateButtonLater') + # case 2: hide the radar, show the quota + else: + panel.cogRadarLabel.hide() + panel.quotaLabel.show() + + + def updateBuildingRadarButtons(self, radars): + # turn on the appropriate radar ability based on 'radars' list + for index in range(0, len(radars)): + if radars[index] == 1: + self.radarButtons[index].building = 1 + + def updateBuildingRadar(self, deptNum, timeout=0): + # remove timeout task + taskMgr.remove('buildingListResponseTimeout-later') + if (not timeout and hasattr(base.cr, 'currSuitPlanner') + and base.cr.currSuitPlanner != None): + buildingList = base.cr.currSuitPlanner.buildingList + else: + buildingList = [0, 0, 0, 0] + + # figure out the current number of buildings of this type on this street + button = self.radarButtons[deptNum] + if button.building: + # make sure we have a buildingRadarLabel first + if not button.buildingRadarLabel: + self.addBuildingRadarLabel(button) + # case 1: show the radar (after a delay) + if self.radarOn[deptNum]: + num = buildingList[deptNum] + if num == 1: + button.buildingRadarLabel['text'] = TTLocalizer.SuitPageBuildingRadarS % num + else: + button.buildingRadarLabel['text'] = TTLocalizer.SuitPageBuildingRadarP % num + def showLabel(button): + button.buildingRadarLabel.show() + # last but not least, turn the radar button back on + button['state'] = DGG.NORMAL + taskMgr.doMethodLater(RADAR_DELAY * SuitDNA.suitsPerDept, + showLabel, + 'showBuildingRadarLater', + extraArgs = (button,)) + # case 2: hide the radar + else: + button.buildingRadarLabel.hide() + + + + + diff --git a/toontown/src/shtiker/SummonCogDialog.py b/toontown/src/shtiker/SummonCogDialog.py new file mode 100644 index 0000000..11153bb --- /dev/null +++ b/toontown/src/shtiker/SummonCogDialog.py @@ -0,0 +1,295 @@ +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import StateData +from toontown.toonbase import TTLocalizer +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals +from toontown.suit import SuitDNA +from toontown.suit import Suit +from toontown.battle import SuitBattleGlobals +from toontown.toon import NPCToons + +TTL = TTLocalizer +class SummonCogDialog(DirectFrame, StateData.StateData): + """SummonCogDialog: + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("SummonCogDialog") + notify.setInfo(True) + + def __init__(self, suitIndex): + """__init__(self) + """ + DirectFrame.__init__(self, + parent = aspect2dp, + pos = (0, 0, 0.30), + relief = None, + image = DGG.getDefaultDialogGeom(), + image_scale = (1.6, 1, 0.7), + image_pos = (0,0,0.18), + image_color = ToontownGlobals.GlobalDialogColor, + text = TTL.SummonDlgTitle, + text_scale = 0.12, + text_pos = (0, 0.4), + borderWidth = (0.01, 0.01), + sortOrder = NO_FADE_SORT_INDEX, + ) + StateData.StateData.__init__(self, "summon-cog-done") + self.initialiseoptions(SummonCogDialog) + self.suitIndex = suitIndex + base.summonDialog = self + self.popup = None + + self.suitName = SuitDNA.suitHeadTypes[self.suitIndex] + self.suitFullName = SuitBattleGlobals.SuitAttributes[self.suitName]['name'] + + + def unload(self): + if self.isLoaded == 0: + return None + self.isLoaded = 0 + self.exit() + DirectFrame.destroy(self) + + def load(self): + if self.isLoaded == 1: + return None + self.isLoaded = 1 + + gui = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + # add the suit head to the dialog + self.head = Suit.attachSuitHead(self, self.suitName) + # need to shift the head up a tad and to the left + z = self.head.getZ() + self.head.setPos(-0.4, -0.1, z+0.2) + + self.suitLabel = DirectLabel( + parent = self, + relief = None, + text = self.suitFullName, + text_font = ToontownGlobals.getSuitFont(), + pos = (-0.4, 0, 0), + scale = 0.07, + ) + + closeButtonImage = (gui.find('**/CloseBtn_UP'), + gui.find('**/CloseBtn_DN'), + gui.find('**/CloseBtn_Rllvr')) + buttonImage = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ) + disabledColor = Vec4(0.5, 0.5, 0.5, 1) + + self.summonSingleButton = DirectButton( + parent = self, + relief = None, + text = TTL.SummonDlgButton1, + image = buttonImage, + image_scale = (1.7,1,1), + image3_color = disabledColor, + text_scale = 0.06, + text_pos = (0,-0.01), + pos = (0.3, 0, 0.25), + command = self.issueSummons, + extraArgs = ["single"], + ) + + self.summonBuildingButton = DirectButton( + parent = self, + relief = None, + text = TTL.SummonDlgButton2, + image = buttonImage, + image_scale = (1.7,1,1), + image3_color = disabledColor, + text_scale = 0.06, + text_pos = (0,-0.01), + pos = (0.3, 0, 0.125), + command = self.issueSummons, + extraArgs = ["building"], + ) + + self.summonInvasionButton = DirectButton( + parent = self, + relief = None, + text = TTL.SummonDlgButton3, + image = buttonImage, + image_scale = (1.7,1,1), + image3_color = disabledColor, + text_scale = 0.06, + text_pos = (0,-0.01), + pos = (0.3, 0, 0.0), + command = self.issueSummons, + extraArgs = ["invasion"], + ) + + self.statusLabel = DirectLabel( + parent = self, + relief = None, + text = "", + text_wordwrap = 12, + pos = (0.3, 0, 0.25), + scale = 0.07, + ) + + self.cancel = DirectButton( + parent = self, + relief = None, + image = closeButtonImage, + pos = (0.7, 0,-0.1), + command = self.__cancel, + ) + + gui.removeNode() + guiButton.removeNode() + + self.hide() + + def enter(self): + """enter(self, changeDisplaySettings) + if changeDisplaySettings is false, only the resolution may be + changed. + """ + if self.isEntered == 1: + return None + self.isEntered = 1 + # Use isLoaded to avoid redundant loading + if self.isLoaded == 0: + self.load() + + # disable all, then enable the ones we have + self.disableButtons() + self.enableButtons() + + self.popup = None + base.transitions.fadeScreen(.5) + + self.show() + + def exit(self): + """exit(self) + """ + if self.isEntered == 0: + return None + self.isEntered = 0 + + self.cleanupDialogs() + base.transitions.noTransitions() + + self.ignoreAll() + self.hide() + + messenger.send(self.doneEvent, []) + return + + def cleanupDialogs(self): + self.head = None + + if self.popup != None: + self.popup.cleanup() + self.popup = None + + def cogSummonsDone(self, returnCode, suitIndex, buildingId): + self.cancel['state'] = DGG.NORMAL + if self.summonsType == "single": + if returnCode == 'success': + self.statusLabel['text'] = TTL.SummonDlgSingleSuccess + elif returnCode == 'badlocation': + self.statusLabel['text'] = TTL.SummonDlgSingleBadLoc + elif returnCode == 'fail': + self.statusLabel['text'] = TTL.SummonDlgInvasionFail + elif self.summonsType == "building": + if returnCode == 'success': + building = base.cr.doId2do.get(buildingId) + dnaStore = base.cr.playGame.dnaStore + buildingTitle = dnaStore.getTitleFromBlockNumber(building.block) + buildingInteriorZone = building.zoneId + 500 + building.block + npcName = TTLocalizer.SummonDlgShopkeeper + npcId = NPCToons.zone2NpcDict.get(buildingInteriorZone) + if npcId: + npcName = NPCToons.getNPCName(npcId[0]) + if buildingTitle: + self.statusLabel['text'] = \ + TTL.SummonDlgBldgSuccess % (npcName, buildingTitle) + else: + self.statusLabel['text'] = TTL.SummonDlgBldgSuccess2 + elif returnCode == 'badlocation': + self.statusLabel['text'] = TTL.SummonDlgBldgBadLoc + elif returnCode == 'fail': + self.statusLabel['text'] = TTL.SummonDlgInvasionFail + elif self.summonsType == "invasion": + if returnCode == 'success': + self.statusLabel['text'] = TTL.SummonDlgInvasionSuccess + elif returnCode == 'busy': + self.statusLabel['text'] = TTL.SummonDlgInvasionBusy % self.suitFullName + elif returnCode == 'fail': + self.statusLabel['text'] = TTL.SummonDlgInvasionFail + + def hideSummonButtons(self): + self.summonSingleButton.hide() + self.summonBuildingButton.hide() + self.summonInvasionButton.hide() + + def issueSummons(self, summonsType): + if summonsType == "single": + text = TTL.SummonDlgSingleConf + elif summonsType == "building": + text = TTL.SummonDlgBuildingConf + elif summonsType == "invasion": + text = TTL.SummonDlgInvasionConf + + text = text % self.suitFullName #+ \ + #" " + \ + #TTL.SummonDlgNumLeft % "1" + + def handleResponse(resp): + self.popup.cleanup() + self.popup = None + + # Restore our dialog to the top of the fade screen. + self.reparentTo(self.getParent(), NO_FADE_SORT_INDEX) + base.transitions.fadeScreen(.5) + + if resp == DGG.DIALOG_OK: + self.notify.info("issuing %s summons for %s" % (summonsType,self.suitIndex) ) + self.accept("cog-summons-response", self.cogSummonsDone) + self.summonsType = summonsType + self.doIssueSummonsText() + base.localAvatar.d_reqCogSummons(self.summonsType, self.suitIndex) + self.hideSummonButtons() + self.cancel['state'] = DGG.DISABLED + + # Move our dialog under the fade screen. + self.reparentTo(self.getParent(), 0) + + self.popup = TTDialog.TTDialog( + parent = aspect2dp, + style = TTDialog.YesNo, + text = text, + fadeScreen = 1, + command = handleResponse, + ) + + def doIssueSummonsText(self): + self.disableButtons() + self.statusLabel['text'] = TTL.SummonDlgDelivering + + def disableButtons(self): + self.summonSingleButton['state'] = DGG.DISABLED + self.summonBuildingButton['state'] = DGG.DISABLED + self.summonInvasionButton['state'] = DGG.DISABLED + + def enableButtons(self): + if base.localAvatar.hasCogSummons(self.suitIndex, "single"): + self.summonSingleButton['state'] = DGG.NORMAL + if base.localAvatar.hasCogSummons(self.suitIndex, "building"): + self.summonBuildingButton['state'] = DGG.NORMAL + if base.localAvatar.hasCogSummons(self.suitIndex, "invasion"): + self.summonInvasionButton['state'] = DGG.NORMAL + + def __cancel(self): + self.exit() + diff --git a/toontown/src/shtiker/TIPPage.py b/toontown/src/shtiker/TIPPage.py new file mode 100644 index 0000000..4706e07 --- /dev/null +++ b/toontown/src/shtiker/TIPPage.py @@ -0,0 +1,47 @@ +from pandac.PandaModules import * +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toon import NPCToons +from toontown.hood import ZoneUtil +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer + +class TIPPage(ShtikerPage.ShtikerPage): + + # special methods + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + self.textRolloverColor = Vec4(1,1,0,1) + self.textDownColor = Vec4(0.5,0.9,1,1) + self.textDisabledColor = Vec4(0.4,0.8,0.4,1) + + def load(self): + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.TIPPageTitle, + text_scale = 0.12, + textMayChange = 0, + pos = (0,0,0.6), + ) + + def unload(self): + del self.title + loader.unloadModel("phase_3.5/models/gui/stickerbook_gui") + ShtikerPage.ShtikerPage.unload(self) + + def updatePage(self): + pass + + def enter(self): + """enter(self) + """ + self.updatePage() + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + """exit(self) + """ + ShtikerPage.ShtikerPage.exit(self) + \ No newline at end of file diff --git a/toontown/src/shtiker/TrackPage.py b/toontown/src/shtiker/TrackPage.py new file mode 100644 index 0000000..ccff0b8 --- /dev/null +++ b/toontown/src/shtiker/TrackPage.py @@ -0,0 +1,238 @@ +from pandac.PandaModules import * +import ShtikerPage +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.quest import Quests +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.toonbase import TTLocalizer +from toontown.toon import Toon + +MAX_FRAMES = 18 + +Track2Anim = { + ToontownBattleGlobals.HEAL_TRACK : 'juggle', + ToontownBattleGlobals.TRAP_TRACK : 'toss', + ToontownBattleGlobals.LURE_TRACK : 'hypnotize', + ToontownBattleGlobals.SOUND_TRACK : 'sound', + ToontownBattleGlobals.THROW_TRACK : 'throw', + ToontownBattleGlobals.SQUIRT_TRACK : 'firehose', + ToontownBattleGlobals.DROP_TRACK : 'pushbutton', + } + +class TrackFrame(DirectFrame): + def __init__(self, index): + DirectFrame.__init__(self, relief = None) + self.initialiseoptions(TrackFrame) + filmstrip = loader.loadModel("phase_3.5/models/gui/filmstrip") + self.index = index + self.frame = DirectFrame( + parent = self, + relief = None, + image = filmstrip, + image_scale = 1, + text = str(self.index-1), + text_pos = (0.26,-0.22), + text_fg = (1,1,1,1), + text_scale = 0.1, + ) + self.question = DirectLabel( + parent = self.frame, + relief = None, + pos = (0,0,-0.15), + text = "?", + text_scale = 0.4, + text_pos = (0,0.04), + text_fg = (0.72,0.72,0.72,1), + ) + self.toon = None + filmstrip.removeNode() + + def makeToon(self): + if not self.toon: + self.toon = Toon.Toon() + self.toon.setDNA(base.localAvatar.getStyle()) + self.toon.getGeomNode().setDepthWrite(1) + self.toon.getGeomNode().setDepthTest(1) + # Conserve polygons + self.toon.useLOD(500) + self.toon.reparentTo(self.frame) + self.toon.setPosHprScale(0,10,-0.25, 210,0,0, 0.12,0.12,0.12) + # This thing is killing us + self.ignore("nametagAmbientLightChanged") + + def play(self, trackId): + if ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(5))): + anim = Track2Anim[trackId] + else: + anim = 'neutral' + if self.toon: + # self.makeToon() + numFrames = self.toon.getNumFrames(anim) - 1 + #fromFrame = ((self.toon.getNumFrames(anim) - 1) / MAX_FRAMES * self.index) + #toFrame = (self.toon.getNumFrames(anim) - 1) + fromFrame = 0 + toFrame = ((self.toon.getNumFrames(anim) - 1) / MAX_FRAMES * self.index) + self.toon.play(anim, None, fromFrame, toFrame-1) + + def setTrained(self, trackId): + # make sure this frame has a toon + if self.toon == None: + self.makeToon() + # Make sure we are downloaded + # TODO: show something better here or download these animations sooner + if ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(5))): + anim = Track2Anim[trackId] + frame = ((self.toon.getNumFrames(anim) - 1) / MAX_FRAMES * self.index) + else: + anim = 'neutral' + frame = 0 + self.toon.pose(anim, frame) + self.toon.show() + self.question.hide() + trackColorR, trackColorG, trackColorB = ToontownBattleGlobals.TrackColors[trackId] + self.frame['image_color'] = Vec4(trackColorR, trackColorG, trackColorB,1) + self.frame['text_fg'] = Vec4(trackColorR*0.3, trackColorG*0.3, trackColorB*0.3,1) + # No point in coloring the question since it is hidden + + def setUntrained(self, trackId): + if self.toon: + self.toon.delete() + self.toon = None + self.question.show() + if trackId == -1: + self.frame['image_color'] = Vec4(0.7,0.7,0.7,1) + self.frame['text_fg'] = Vec4(0.5,0.5,0.5,1) + self.question['text_fg'] = Vec4(0.6,0.6,0.6,1) + else: + trackColorR, trackColorG, trackColorB = ToontownBattleGlobals.TrackColors[trackId] + self.frame['image_color'] = Vec4(trackColorR*0.7, trackColorG*0.7, trackColorB*0.7,1) + self.frame['text_fg'] = Vec4(trackColorR*0.3, trackColorG*0.3, trackColorB*0.3,1) + self.question['text_fg'] = Vec4(trackColorR*0.6, trackColorG*0.6, trackColorB*0.6,1) + +class TrackPage(ShtikerPage.ShtikerPage): + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + self.trackFrames = [] + + def placeFrames(self): + rowY = 0.38 + rowSpace = -0.32 + rowPos = [] + for i in range(3): + rowPos.append(rowY) + rowY += rowSpace + colX = -0.7 + colSpace = 0.276 + colPos = [] + for i in range(6): + colPos.append(colX) + colX += colSpace + for index in range(1, MAX_FRAMES+1): + frame = self.trackFrames[index-1] + col = (index - 1) % 6 + row = (index - 1) / 6 + frame.setPos(colPos[col], 0, rowPos[row]) + frame.setScale(0.39) + + def load(self): + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.TrackPageTitle, + text_scale = 0.1, + pos = (0,0,0.65), + ) + self.subtitle = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.TrackPageSubtitle, + text_scale = 0.05, + text_fg = (0.5,0.1,0.1,1), + pos = (0,0,0.56), + ) + self.trackText = DirectLabel( + parent = self, + relief = None, + text = "", + text_scale = 0.05, + text_fg = (0.5,0.1,0.1,1), + pos = (0,0,-0.5), + ) + # Count up from 1 + for index in range(1, MAX_FRAMES+1): + frame = TrackFrame(index) + frame.reparentTo(self) + self.trackFrames.append(frame) + self.placeFrames() + + self.startFrame = self.trackFrames[0] + self.endFrame = self.trackFrames[-1] + + self.startFrame.frame['text'] = "" + self.startFrame.frame['text_scale'] = TTLocalizer.TPstartFrame + self.startFrame.frame['image_color'] = Vec4(0.2,0.2,0.2,1) + self.startFrame.frame['text_fg'] = (1,1,1,1) + self.startFrame.frame['text_pos'] = (0,0.08) + self.startFrame.question.hide() + + self.endFrame.frame['text'] = TTLocalizer.TrackPageDone + self.endFrame.frame['text_scale'] = TTLocalizer.TPendFrame + self.endFrame.frame['image_color'] = Vec4(0.2,0.2,0.2,1) + self.endFrame.frame['text_fg'] = (1,1,1,1) + self.endFrame.frame['text_pos'] = (0,0) + self.endFrame.question.hide() + + def unload(self): + del self.title + del self.subtitle + del self.trackText + del self.trackFrames + ShtikerPage.ShtikerPage.unload(self) + + def clearPage(self): + for index in range(1,MAX_FRAMES-1): + self.trackFrames[index].setUntrained(-1) + self.startFrame.frame['text'] = "" + self.trackText['text'] = TTLocalizer.TrackPageClear + return + + def updatePage(self): + trackId, trackProgress = base.localAvatar.getTrackProgress() + # -1 means not training any track now + if trackId == -1: + self.clearPage() + else: + # TODO: keep track of previous progress to make update more efficient + trackName = ToontownBattleGlobals.Tracks[trackId].capitalize() + self.trackText['text'] = (TTLocalizer.TrackPageTraining % (trackName, trackName)) + trackProgressArray = base.localAvatar.getTrackProgressAsArray() + # The first frame and last 2 frames are static, not set by the progress + for index in range(1,MAX_FRAMES-2): + # Subtract 1 from progress array since it is 0 based + if trackProgressArray[index-1]: + self.trackFrames[index].setTrained(trackId) + else: + self.trackFrames[index].setUntrained(trackId) + # The final poster (except the fin poster) is always empty because it + # represents the final track training that you never see + self.trackFrames[MAX_FRAMES-2].setUntrained(trackId) + self.startFrame.frame['text'] = TTLocalizer.TrackPageFilmTitle % trackName + return + + def enter(self): + """enter(self) + """ + self.updatePage() + ShtikerPage.ShtikerPage.enter(self) + return + + def exit(self): + """exit(self) + """ + self.clearPage() + ShtikerPage.ShtikerPage.exit(self) + return + diff --git a/toontown/src/shtiker/__init__.py b/toontown/src/shtiker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/shtiker/error.html b/toontown/src/shtiker/error.html new file mode 100644 index 0000000..ee26c95 --- /dev/null +++ b/toontown/src/shtiker/error.html @@ -0,0 +1,66 @@ + + + +In Game News | Error + + + + + + + + +
+ + +
+

This page is currently unavailable.

+

How awkward...

+

Quick -- click the button below
+ to get away from this page!

+ +
+ + +
+ + + + + + + diff --git a/toontown/src/speedchat/.cvsignore b/toontown/src/speedchat/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/speedchat/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/speedchat/Sources.pp b/toontown/src/speedchat/Sources.pp new file mode 100644 index 0000000..fb8681d --- /dev/null +++ b/toontown/src/speedchat/Sources.pp @@ -0,0 +1,4 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group diff --git a/toontown/src/speedchat/TTSCAprilToonsMenu.py b/toontown/src/speedchat/TTSCAprilToonsMenu.py new file mode 100644 index 0000000..5ac20a2 --- /dev/null +++ b/toontown/src/speedchat/TTSCAprilToonsMenu.py @@ -0,0 +1,67 @@ +## April Toon's speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +AprilToonsMenu = [ + (OTPLocalizer.AprilToonsMenuSections[1], # GREETINGS + [60100, 60101,]), + (OTPLocalizer.AprilToonsMenuSections[2], # PLAYGROUNDS + [60110, 60111, 60112, 60113, 60114, 60115,]), + (OTPLocalizer.AprilToonsMenuSections[3], # CHARACTERS + [60120, 60121, 60122, 60123, 60124, 60125, 60126,]), + (OTPLocalizer.AprilToonsMenuSections[4], # ESTATES + [60130, 60131, 60132, 60133,]), + (OTPLocalizer.AprilToonsMenuSections[0], + [60140, 60141,]), + ] + +class TTSCAprilToonsMenu(SCMenu): + """ + Speedchat phrases for April Toon's + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__aprilToonsMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __aprilToonsMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in AprilToonsMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link April Toons phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link April Toons phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCBoardingMenu.py b/toontown/src/speedchat/TTSCBoardingMenu.py new file mode 100644 index 0000000..874243b --- /dev/null +++ b/toontown/src/speedchat/TTSCBoardingMenu.py @@ -0,0 +1,76 @@ +""" +TTSCBoardingMenu.py: contains the TTSCBoardingMenu class +""" + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + +#this is the structure of the racing menu +BoardingMenuGuide = [ +(OTPLocalizer.BoardingMenuSections[0], []), +(OTPLocalizer.BoardingMenuSections[1], []), +(OTPLocalizer.BoardingMenuSections[2], []), +(OTPLocalizer.BoardingMenuSections[3],[5005, 5006, 5007, 5008, 5009]), +] + +GroupPhrases = [5000, 5001, 5002, 5003, 5004] + +ZoneIdsToMsgs = { + 10000 : [GroupPhrases, [5100, 5101, 5102], [5200, 5201, 5202]], + 10100 : [GroupPhrases, [5103], [5203]], + 11100 : [GroupPhrases, [5104], [5204]], + 11200 : [GroupPhrases, [5105, 5106], [5205, 5206]], + 12000 : [GroupPhrases, [5107, 5108, 5109], [5207, 5208, 5209]], + 12100 : [GroupPhrases, [5110], [5210]], + 13100 : [GroupPhrases, [5111], [5211]], + 13200 : [GroupPhrases, [5112, 5113, 5114, 5115], [5212, 5213, 5214, 5215]], +} + +class TTSCBoardingMenu(SCMenu): + """ + TTSCBoardingMenu represents a menu of TTSCBoardingTerminals. + """ + def __init__(self, zoneId): + SCMenu.__init__(self) + # listen for changes to localtoon's boarding speedchat messages + self.__boardingMessagesChanged(zoneId) + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __boardingMessagesChanged(self, zoneId): + # Clear out everything from our menu + self.clearMenu() + # If local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for count in xrange(len(BoardingMenuGuide)): + section = BoardingMenuGuide[count] + if section[0] == -1: + # This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link boarding phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: + # This should be a submenu, get the list of phrases from the corresponding zoneId + menu = SCMenu() + phrases = ZoneIdsToMsgs[zoneId][count] + for phrase in phrases: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link boarding phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + # add the menu to self (SpeedChat won't display empty menus) + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu)) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCCarolMenu.py b/toontown/src/speedchat/TTSCCarolMenu.py new file mode 100644 index 0000000..f3e2c36 --- /dev/null +++ b/toontown/src/speedchat/TTSCCarolMenu.py @@ -0,0 +1,63 @@ +## April Toon's speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase.OTPLocalizer import SpeedChatStaticText +from toontown.speedchat.TTSCIndexedTerminal import TTSCIndexedTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +CarolMenu = [ + (OTPLocalizer.CarolMenuSections[0], + {60200:60220, 60201:60221, 60202:60222, 60203:60223, 60204:60224, 60205:60225}), + ] + +class TTSCCarolMenu(SCMenu): + """ + Speedchat phrases for Caroling + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__carolMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __carolMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in CarolMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1].keys(): + blatherTxt = section[1][phrase] + if blatherTxt not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Carol phrase %s which does not seem to exist' % blatherTxt) + break + self.append(TTSCIndexedTerminal(SpeedChatStaticText.get(phrase, None), blatherTxt)) + else: + #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Carol phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCCogMenu.py b/toontown/src/speedchat/TTSCCogMenu.py new file mode 100644 index 0000000..30e6979 --- /dev/null +++ b/toontown/src/speedchat/TTSCCogMenu.py @@ -0,0 +1,28 @@ +""" +TTSCCogMenu.py: contains the TTSCCogMenu class + +For standalone testing these are useful: +base.localAvatar.chatMgr.chatInputSpeedChat.addCogMenu() +base.localAvatar.chatMgr.chatInputSpeedChat.removeCogMenu() +base.localAvatar.chatMgr.chatInputSpeedChat.speedChat[2].getMenu() + +""" + +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal + +class TTSCCogMenu(SCMenu): + """ + TTSCCogMenu represents a menu of SCCogTerminals. + """ + + def __init__(self, indices): + SCMenu.__init__(self) + + for index in indices: + term = SCStaticTextTerminal(index) + self.append(term) + + def destroy(self): + SCMenu.destroy(self) + diff --git a/toontown/src/speedchat/TTSCDecoders.py b/toontown/src/speedchat/TTSCDecoders.py new file mode 100644 index 0000000..a6a5393 --- /dev/null +++ b/toontown/src/speedchat/TTSCDecoders.py @@ -0,0 +1,9 @@ +"""TTSCDecoders.py: contains Toontown specific functions to decode SpeedChat messages """ + +""" +Each of these functions normally returns the ready-to-display text +string that corresponds to the encoded message. If there is a problem, +None is returned. +""" +from TTSCToontaskTerminal import decodeTTSCToontaskMsg +from TTSCResistanceTerminal import decodeTTSCResistanceMsg diff --git a/toontown/src/speedchat/TTSCFactoryMenu.py b/toontown/src/speedchat/TTSCFactoryMenu.py new file mode 100644 index 0000000..5a68d66 --- /dev/null +++ b/toontown/src/speedchat/TTSCFactoryMenu.py @@ -0,0 +1,120 @@ +""" +TTSCFactoryMenu.py: contains the TTSCFactoryMenu class + +For standalone testing these are useful: +base.localAvatar.chatMgr.chatInputSpeedChat.addFactoryMenu() +base.localAvatar.chatMgr.chatInputSpeedChat.removeFactoryMenu() +base.localAvatar.chatMgr.chatInputSpeedChat.speedChat[2].getMenu() + +""" + +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + +# TODO: create a table for each factory +ZoneToMsgs = { + 3: [1803, 1903], + 4: [1804, 1904], + 5: [1805, 1905], + 6: [1806, 1906], + 7: [1807, 1907], + 8: [1808, 1908], + 9: [1809, 1909], + 10: [1810, 1910], + 11: [1811, 1911], + 12: [1812, 1912], + 13: [1813, 1913], + 14: [1814, 1914], + 15: [1815, 1915], + 16: [1816, 1916], + 17: [1817, 1917], + 18: [1818, 1918], + 19: [1819, 1919], + 20: [1820, 1920], + 21: [1821, 1921], + 22: [1822, 1922], + 23: [1823, 1923], + 24: [1824, 1924], + 25: [1825, 1925], + 27: [1827, 1927], + 30: [1830, 1930], + 31: [1831, 1931], + 32: [1832, 1932], + 33: [1833, 1933], + 34: [1834, 1934], + 35: [1835, 1935], + 36: [1836, 1936], + 37: [1837, 1937], + 38: [1838, 1938], + 40: [1840, 1940], + 41: [1841, 1941], + 60: [1860, 1960], + 61: [1861, 1961], + } +# Messages you always want in any zone +GLOBAL_MSGS = [1700, 1701, 1702, 1703, 1704,] + +class TTSCFactoryMenu(SCMenu): + """ + SCFactoryMenu represents a menu of factory-related terminals. + """ + + def __init__(self): + SCMenu.__init__(self) + + self.meetMenuHolder = None + # the meet menu really only makes sense for Sellbot HQ + zoneId = base.cr.playGame.getPlaceId() + if zoneId and (zoneId == 11000): + meetMenu = SCMenu() + for msgIndex in OTPLocalizer.SCFactoryMeetMenuIndexes: + term = SCStaticTextTerminal(msgIndex) + meetMenu.append(term) + self.meetMenuHolder = SCMenuHolder.SCMenuHolder(OTPLocalizer.SCMenuFactoryMeet, + meetMenu) + self[0:0] = [self.meetMenuHolder] + + # listen for changes to the factory location + self.accept("factoryZoneChanged", self.__zoneChanged) + self.__zoneChanged() + + def destroy(self): + self.ignore("factoryZoneChanged") + SCMenu.destroy(self) + + def __zoneChanged(self, zoneId=0): + + # save the meet menu if we have one + if self.meetMenuHolder: + del self[0] + + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + + # keep a list of the phrases we've added to the menu, to + # detect duplicates + phrases = [] + def addTerminal(terminal, self=self, phrases=phrases): + displayText = terminal.getDisplayText() + if displayText not in phrases: + self.append(terminal) + phrases.append(displayText) + + # rebuild our menu + # Everybody gets the global messages + # Plus add the ones for the zone you are in if you have some + for msg in (GLOBAL_MSGS + ZoneToMsgs.get(zoneId, [])): + assert (msg in OTPLocalizer.SpeedChatStaticText) + addTerminal(SCStaticTextTerminal(msg)) + + # put the meet menu back, if we had one + if self.meetMenuHolder: + self[0:0] = [self.meetMenuHolder] diff --git a/toontown/src/speedchat/TTSCGolfMenu.py b/toontown/src/speedchat/TTSCGolfMenu.py new file mode 100644 index 0000000..d7542c1 --- /dev/null +++ b/toontown/src/speedchat/TTSCGolfMenu.py @@ -0,0 +1,74 @@ +""" +TTSCGolfMenu.py: contains the TTSCGolfMenu class +""" + +# For standalone testing these are useful: +#base.localAvatar.chatMgr.chatInputSpeedChat.addGolfMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.removeGolfMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.speedChat[2].getMenu() + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +GolfMenuGuide = [ +(OTPLocalizer.GolfMenuSections[1],[4100,4101,4102,4103,4104,4105]), +(OTPLocalizer.GolfMenuSections[2],[4200,4201,4202,4203,4204,4205,4206,4207]), +(OTPLocalizer.GolfMenuSections[3],[4300,4301,4302,4303,4304,4305,4306,4307]), +(OTPLocalizer.GolfMenuSections[0],[4000,4001,4002]), +] + + +class TTSCGolfMenu(SCMenu): + """ + TTSCGolfMenu represents a menu of TTSCGolfTerminals. + """ + + def __init__(self): + SCMenu.__init__(self) + + # listen for changes to localtoon's golf speedchat messages + self.accept("golfMessagesChanged", self.__golfMessagesChanged) + self.__golfMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __golfMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in GolfMenuGuide: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link golf phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link golf phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + # add the menu to self (SpeedChat won't display empty menus) + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) + + diff --git a/toontown/src/speedchat/TTSCIndexedTerminal.py b/toontown/src/speedchat/TTSCIndexedTerminal.py new file mode 100644 index 0000000..ba1887d --- /dev/null +++ b/toontown/src/speedchat/TTSCIndexedTerminal.py @@ -0,0 +1,29 @@ +"""TTSCIndexedTerminal.py: contains the SCIndexedTerminal class""" + +from otp.speedchat.SCTerminal import * +from otp.otpbase.OTPLocalizer import SpeedChatStaticText + +# args: taskId, toNpcId, toonProgress, msgIndex +TTSCIndexedMsgEvent = 'SCIndexedMsg' + +def decodeTTSCIndexedMsg(msgIndex): + + return SpeedChatStaticText.get(msgIndex, None) + +class TTSCIndexedTerminal(SCTerminal): + """ TTSCINdexedTerminal is useful if you want to display a phrase in an SCMenu but have + the toon speak a different phrase once it has been selected. + + msg is the text that is displayed in the menu + msgIndex is the index of the message in OTPLocalizer.SpeedChatStaticText that the toon + will blather""" + + def __init__(self, msg, msgIndex): + SCTerminal.__init__(self) + self.text = msg + self.msgIndex = msgIndex + + def handleSelect(self): + SCTerminal.handleSelect(self) + messenger.send(self.getEventName(TTSCIndexedMsgEvent), + [self.msgIndex]) diff --git a/toontown/src/speedchat/TTSCKartRacingMenu.py b/toontown/src/speedchat/TTSCKartRacingMenu.py new file mode 100644 index 0000000..7545401 --- /dev/null +++ b/toontown/src/speedchat/TTSCKartRacingMenu.py @@ -0,0 +1,76 @@ +""" +TTSCKartRacingMenu.py: contains the TTSCKartRacingMenu class +""" + +# For standalone testing these are useful: +#base.localAvatar.chatMgr.chatInputSpeedChat.addKartRacingMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.removeKartRacingMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.speedChat[2].getMenu() + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +KartRacingMenuGuide = [ +(OTPLocalizer.KartRacingMenuSections[1],[3130,3160,3190,3170,3180,3150,3110]), +(OTPLocalizer.KartRacingMenuSections[2],[3200,3201,3210,3211,3220,3221,3222,3223,3224,3225,3230,3231,3232,3233,3234,3235]), +(OTPLocalizer.KartRacingMenuSections[3],[3600,3601,3602,3603,3640,3641,3642,3643,3660,3661,3662,3663]), +(OTPLocalizer.KartRacingMenuSections[4],[3300,3301,3310,3320,3330,3340,3350,3360]), +(OTPLocalizer.KartRacingMenuSections[5],[3410,3400,3430,3450,3451,3452,3453,3460,3461,3462,3470]), +(OTPLocalizer.KartRacingMenuSections[0],[3010,3020,3030,3040,3050,3060,3061]), +] + + +class TTSCKartRacingMenu(SCMenu): + """ + TTSCKartRacingMenu represents a menu of TTSCKartRacingTerminals. + """ + + def __init__(self): + SCMenu.__init__(self) + + # listen for changes to localtoon's kartRacing speedchat messages + self.accept("kartRacingMessagesChanged", self.__kartRacingMessagesChanged) + self.__kartRacingMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __kartRacingMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in KartRacingMenuGuide: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link kart phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link kart phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + # add the menu to self (SpeedChat won't display empty menus) + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) + + diff --git a/toontown/src/speedchat/TTSCPetTrickMenu.py b/toontown/src/speedchat/TTSCPetTrickMenu.py new file mode 100644 index 0000000..ba5630b --- /dev/null +++ b/toontown/src/speedchat/TTSCPetTrickMenu.py @@ -0,0 +1,46 @@ +"""TTSCPetTrickMenu.py""" + +from direct.directnotify import DirectNotifyGlobal +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer +from toontown.pets import PetTricks + +class TTSCPetTrickMenu(SCMenu): + """ + SCPetTrickMenu represents a menu of pet trick-training terminals. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("TTSCPetTrickMenu") + + def __init__(self): + SCMenu.__init__(self) + + # listen for changes to the pet trick phrases we have + self.accept("petTrickPhrasesChanged", self.__phrasesChanged) + self.__phrasesChanged() + + def destroy(self): + self.ignore("petTrickPhrasesChanged") + SCMenu.destroy(self) + + def __phrasesChanged(self, zoneId=0): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + + # rebuild our menu + for trickId in lt.petTrickPhrases: + if trickId not in PetTricks.TrickId2scIds: + TTSCPetTrickMenu.notify.warning( + 'unknown trick ID: %s' % trickId) + else: + # there may be multiple msgs per trick + for msg in PetTricks.TrickId2scIds[trickId]: + assert msg in OTPLocalizer.SpeedChatStaticText + self.append(SCStaticTextTerminal(msg)) diff --git a/toontown/src/speedchat/TTSCPromotionalMenu.py b/toontown/src/speedchat/TTSCPromotionalMenu.py new file mode 100644 index 0000000..aae3ecb --- /dev/null +++ b/toontown/src/speedchat/TTSCPromotionalMenu.py @@ -0,0 +1,56 @@ +"""TTSCPromotionalMenu.py: contains the TTSCPromotionalMenu class""" + +from direct.directnotify import DirectNotifyGlobal +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer +from toontown.toonbase import ToontownGlobals + +# maps holidayId to (menu title, contents structure) +# see SCMenu.appendFromStructure for structure format +holidayId2menuInfo = { + ToontownGlobals.ELECTION_PROMOTION: + (OTPLocalizer.SCMenuElection, + # third election (pig/goat) + [10000, 10001, 10006, 10007,]), + } + +class TTSCPromotionalMenu(SCMenu): + """ + TTSCPromotionalMenu is a menu that contains promotional phrases. + Only one set of phrases can be active at a time. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('TTSCPromotionalMenu') + + def __init__(self): + SCMenu.__init__(self) + + assert not hasattr(base, 'TTSCPromotionalMenu') + base.TTSCPromotionalMenu = self + + self.curHolidayId = None + self.clearMenu() + + def destroy(self): + del base.TTSCPromotionalMenu + SCMenu.destroy(self) + + def startHoliday(self, holidayId): + if self.curHolidayId is not None: + TTSCPromotionalMenu.notify.warning( + 'overriding existing holidayId %s with %s' % ( + self.curHolidayId, holidayId)) + self.curHolidayId = holidayId + # rebuild our menu + title, structure = holidayId2menuInfo[holidayId] + self.rebuildFromStructure(structure, title=title) + + def endHoliday(self, holidayId): + if holidayId != self.curHolidayId: + TTSCPromotionalMenu.notify.warning( + 'unexpected holidayId: %s' % holidayId) + return + self.curHolidayId = None + self.clearMenu() diff --git a/toontown/src/speedchat/TTSCResistanceMenu.py b/toontown/src/speedchat/TTSCResistanceMenu.py new file mode 100644 index 0000000..ddc36c5 --- /dev/null +++ b/toontown/src/speedchat/TTSCResistanceMenu.py @@ -0,0 +1,62 @@ +""" +TTSCResistanceMenu.py: contains the TTSCResistanceMenu class +""" + +# For standalone testing these are useful: +#base.localAvatar.chatMgr.chatInputSpeedChat.addResistanceMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.removeResistanceMenu() +#base.localAvatar.chatMgr.chatInputSpeedChat.speedChat[2].getMenu() + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from toontown.chat import ResistanceChat +#from toontown.toonbase.TTLocalizer import ResistanceSCStrings +from TTSCResistanceTerminal import TTSCResistanceTerminal + +class TTSCResistanceMenu(SCMenu): + """ + TTSCResistanceMenu represents a menu of TTSCResistanceTerminals. + """ + + def __init__(self): + SCMenu.__init__(self) + + # listen for changes to localtoon's resistance speedchat messages + self.accept("resistanceMessagesChanged", self.__resistanceMessagesChanged) + self.__resistanceMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __resistanceMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + + #create the necessary items in the appropriate submenus + phrases = lt.resistanceMessages + #create the menus + for menuIndex in ResistanceChat.resistanceMenu: + # build a submenu of a particular type (toonup, etc) + menu = SCMenu() + for itemIndex in ResistanceChat.getItems(menuIndex): + textId = ResistanceChat.encodeId(menuIndex, itemIndex) + charges = lt.getResistanceMessageCharges(textId) + if charges > 0: + menu.append(TTSCResistanceTerminal(textId, charges)) + + # add the menu to self (SpeedChat won't display empty menus) + textId = ResistanceChat.encodeId(menuIndex, 0) + menuName = ResistanceChat.getMenuName(textId) + self.append( SCMenuHolder(menuName, menu) ) + diff --git a/toontown/src/speedchat/TTSCResistanceTerminal.py b/toontown/src/speedchat/TTSCResistanceTerminal.py new file mode 100644 index 0000000..c6ba34f --- /dev/null +++ b/toontown/src/speedchat/TTSCResistanceTerminal.py @@ -0,0 +1,29 @@ +"""TTSCResistanceTerminal.py: contains the TTSCResistanceTerminal class""" + +from otp.speedchat.SCTerminal import SCTerminal +from toontown.chat import ResistanceChat + +# args: textId +TTSCResistanceMsgEvent = 'TTSCResistanceMsg' + +def decodeTTSCResistanceMsg(textId): + return ResistanceChat.getChatText(textId) + +class TTSCResistanceTerminal(SCTerminal): + """ TTSCResistanceTerminal represents a terminal SpeedChat entry that + contains a phrase that was purchased from the catalog. """ + def __init__(self, textId, charges): + SCTerminal.__init__(self) + self.setCharges(charges) + self.textId = textId + self.text = ResistanceChat.getItemText(self.textId) + + def isWhisperable(self): + # this terminal is used to trigger area-of-effect abilities + # don't allow whisper + return False + + def handleSelect(self): + SCTerminal.handleSelect(self) + messenger.send(self.getEventName(TTSCResistanceMsgEvent), + [self.textId]) diff --git a/toontown/src/speedchat/TTSCSillyPhaseFiveMenu.py b/toontown/src/speedchat/TTSCSillyPhaseFiveMenu.py new file mode 100644 index 0000000..6e27315 --- /dev/null +++ b/toontown/src/speedchat/TTSCSillyPhaseFiveMenu.py @@ -0,0 +1,61 @@ +## Silly PhaseFive speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +SillyPhaseFiveMenu = [ + (OTPLocalizer.SillyHolidayMenuSections[1], # WORLD + [60325, 60326, 60327,]), + (OTPLocalizer.SillyHolidayMenuSections[2], # BATTLE + [60328, 60329, 60330, 60331, 60332,]), + ] + +class TTSCSillyPhaseFiveMenu(SCMenu): + """ + Speedchat phrases for Silly PhaseFive + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__SillyPhaseFiveMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __SillyPhaseFiveMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in SillyPhaseFiveMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseFive phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseFive phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSillyPhaseFourMenu.py b/toontown/src/speedchat/TTSCSillyPhaseFourMenu.py new file mode 100644 index 0000000..1ef5fd3 --- /dev/null +++ b/toontown/src/speedchat/TTSCSillyPhaseFourMenu.py @@ -0,0 +1,61 @@ +## Silly PhaseFour speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +SillyPhaseFourMenu = [ + (OTPLocalizer.SillyHolidayMenuSections[1], # WORLD + [60325, 60326, 60327,]), + (OTPLocalizer.SillyHolidayMenuSections[2], # BATTLE + [60329, 60330, 60331, 60332,]), + ] + +class TTSCSillyPhaseFourMenu(SCMenu): + """ + Speedchat phrases for Silly PhaseFour + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__SillyPhaseFourMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __SillyPhaseFourMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in SillyPhaseFourMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseFour phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseFour phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSillyPhaseOneMenu.py b/toontown/src/speedchat/TTSCSillyPhaseOneMenu.py new file mode 100644 index 0000000..8700e9d --- /dev/null +++ b/toontown/src/speedchat/TTSCSillyPhaseOneMenu.py @@ -0,0 +1,63 @@ +## Silly PhaseOne speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +SillyPhaseOneMenu = [ + (OTPLocalizer.SillyHolidayMenuSections[1], # WORLD + [60303, 60304, 60305, 60306,]), + (OTPLocalizer.SillyHolidayMenuSections[2], # BATTLE + [60307, 60308,]), + (OTPLocalizer.SillyHolidayMenuSections[0], # SILLY METER + [60301, 60302,]), + ] + +class TTSCSillyPhaseOneMenu(SCMenu): + """ + Speedchat phrases for Silly PhaseOne + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__SillyPhaseOneMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __SillyPhaseOneMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in SillyPhaseOneMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseOne phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseOne phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSillyPhaseThreeMenu.py b/toontown/src/speedchat/TTSCSillyPhaseThreeMenu.py new file mode 100644 index 0000000..51454fa --- /dev/null +++ b/toontown/src/speedchat/TTSCSillyPhaseThreeMenu.py @@ -0,0 +1,61 @@ +## Silly PhaseThree speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +SillyPhaseThreeMenu = [ + (OTPLocalizer.SillyHolidayMenuSections[1], # WORLD + [60323, 60324, 60325, 60326, 60327,]), + (OTPLocalizer.SillyHolidayMenuSections[2], # BATTLE + [60318, 60319, 60320, 60321, 60322,]), + ] + +class TTSCSillyPhaseThreeMenu(SCMenu): + """ + Speedchat phrases for Silly PhaseThree + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__SillyPhaseThreeMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __SillyPhaseThreeMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in SillyPhaseThreeMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseThree phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseThree phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSillyPhaseTwoMenu.py b/toontown/src/speedchat/TTSCSillyPhaseTwoMenu.py new file mode 100644 index 0000000..c1dfb12 --- /dev/null +++ b/toontown/src/speedchat/TTSCSillyPhaseTwoMenu.py @@ -0,0 +1,63 @@ +## Silly PhaseTwo speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the racing menu +SillyPhaseTwoMenu = [ + (OTPLocalizer.SillyHolidayMenuSections[1], # WORLD + [60310, 60311, 60312, 60313, 60314, 60315,]), + (OTPLocalizer.SillyHolidayMenuSections[2], # BATTLE + [60316, 60317,]), + (OTPLocalizer.SillyHolidayMenuSections[0], # SILLY METER + [60309,]), + ] + +class TTSCSillyPhaseTwoMenu(SCMenu): + """ + Speedchat phrases for Silly PhaseTwo + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__SillyPhaseTwoMessagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __SillyPhaseTwoMessagesChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in SillyPhaseTwoMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseTwo phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Silly PhaseTwo phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSingingMenu.py b/toontown/src/speedchat/TTSCSingingMenu.py new file mode 100644 index 0000000..482ffe4 --- /dev/null +++ b/toontown/src/speedchat/TTSCSingingMenu.py @@ -0,0 +1,71 @@ +""" +TTSCSingingMenu.py: contains the TTSCSingingMenu class +""" + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from TTSCSingingTerminal import TTSCSingingTerminal +from otp.otpbase import OTPLocalizer + +#this is the structure of the racing menu +SingingMenuGuide = [ +(OTPLocalizer.SingingMenuSections[0], [{9000:25}, {9001:26}, {9002:27}, {9003:28}, {9004:29}, {9005:30}, {9006:31}, {9007:32}, {9008:33}]), +] + +class TTSCSingingMenu(SCMenu): + """ + TTSCSingingMenu represents a menu of TTSCSingingTerminals. + """ + def __init__(self): + SCMenu.__init__(self) + # listen for changes to localtoon's singing speedchat messages + self.__singingMessagesChanged() + if __debug__: + base.smenu = self + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __singingMessagesChanged(self): + # Clear out everything from our menu + self.clearMenu() + # If local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for count in xrange(len(SingingMenuGuide)): + section = SingingMenuGuide[count] + if section[0] == -1: + # This is not a submenu but a terminal! + for phrase in section[1]: + emote = None + # If the type of the phrase is a dictionary there must be an emote attached to it. + if (type(phrase) == type({})): + assert len(phrase.keys()) == 1 + item = phrase.keys()[0] + emote = phrase[item] + phrase = item + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link a singing phrase %s which does not seem to exist' % phrase) + break + terminal = TTSCSingingTerminal(phrase) + if emote is not None: + terminal.setLinkedEmote(emote) + self.append(terminal) +## else: +## # This should be a submenu, get the list of phrases from the corresponding zoneId +## mmenu = SCMenu() +## for phrase in section[1]: +## if phrase not in OTPLocalizer.SpeedChatStaticText: +## print ('warning: tried to link a singing phrase %s which does not seem to exist' % phrase) +## break +## menu.append(SCStaticTextTerminal(phrase)) +## +## # add the menu to self (SpeedChat won't display empty menus) +## menuName = str(section[0]) +## self.append(SCMenuHolder(menuName, menu)) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCSingingTerminal.py b/toontown/src/speedchat/TTSCSingingTerminal.py new file mode 100644 index 0000000..bd7dd87 --- /dev/null +++ b/toontown/src/speedchat/TTSCSingingTerminal.py @@ -0,0 +1,39 @@ +"""TTSCSingingTerminal.py: contains the TTSCSingingTerminal class.""" + +from otp.speedchat.SCTerminal import SCTerminal +from otp.otpbase.OTPLocalizer import SpeedChatStaticText + +# args: textId +TTSCSingingMsgEvent = 'SCSingingMsg' + +def decodeSCStaticTextMsg(textId): + return SpeedChatStaticText.get(textId, None) + +class TTSCSingingTerminal(SCTerminal): + """ TTSCSingingTerminal represents a terminal SpeedChat entry that + contains a note from the singing system. + + When selected, generates a 'TTSCSingingMsgEvent' event, with arguments: + - textId (16-bit; use as index into OTPLocalizer.SpeedChatStaticText) + + This event in turn makes the avatar sing a selected note depending on the + toon species and torso size. + """ + def __init__(self, textId): + SCTerminal.__init__(self) + self.textId = textId + self.text = SpeedChatStaticText[self.textId] + + def handleSelect(self): + SCTerminal.handleSelect(self) + messenger.send(self.getEventName(TTSCSingingMsgEvent), [self.textId]) + + def finalize(self): + """ + Making sure that these buttons don't have any rollover or click sound, + since affects the music making skills. :) + """ + args = { + 'rolloverSound': None, + 'clickSound': None,} + SCTerminal.finalize(self, args) \ No newline at end of file diff --git a/toontown/src/speedchat/TTSCToontaskMenu.py b/toontown/src/speedchat/TTSCToontaskMenu.py new file mode 100644 index 0000000..43b2b81 --- /dev/null +++ b/toontown/src/speedchat/TTSCToontaskMenu.py @@ -0,0 +1,64 @@ +"""TTSCToontaskMenu.py: contains the SCToontaskMenu class""" + +from otp.speedchat.SCMenu import SCMenu +from TTSCToontaskTerminal import TTSCToontaskTerminal +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from toontown.quest import Quests + +class TTSCToontaskMenu(SCMenu): + """ TTSCToontaskMenu represents a menu of SCToontaskTerminals. """ + def __init__(self): + SCMenu.__init__(self) + + # listen for changes to localtoon's quests + # whenever localtoon is generated, we're going to get + # this msg twice, once for setQuests and once for + # setQuestCarryLimit + self.accept("questsChanged", self.__tasksChanged) + self.__tasksChanged() + + def destroy(self): + SCMenu.destroy(self) + + def __tasksChanged(self): + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + + # keep a list of the phrases we've added to the menu, to + # detect duplicates + phrases = [] + def addTerminal(terminal, self=self, phrases=phrases): + displayText = terminal.getDisplayText() + if displayText not in phrases: + self.append(terminal) + phrases.append(displayText) + + # rebuild our menu + for task in lt.quests: + taskId, fromNpcId, toNpcId, rewardId, toonProgress = task + q = Quests.getQuest(taskId) + if q is None: + continue + msgs = q.getSCStrings(toNpcId, toonProgress) + # getSCStrings might return a list of strings, or just a string + if type(msgs) != type([]): + msgs = [msgs] + for i in xrange(len(msgs)): + addTerminal(TTSCToontaskTerminal(msgs[i], taskId, toNpcId, + toonProgress, i)) + + # if toon has open task slots, or no task slots, + # append 'i need a toontask'. + needToontask = 1 + if hasattr(lt, 'questCarryLimit'): + needToontask = (len(lt.quests) != lt.questCarryLimit) + if needToontask: + # add 'I need to get a ToonTask' + # see Localizer.SpeedChatStaticText + addTerminal(SCStaticTextTerminal(1299)) diff --git a/toontown/src/speedchat/TTSCToontaskTerminal.py b/toontown/src/speedchat/TTSCToontaskTerminal.py new file mode 100644 index 0000000..9c6414e --- /dev/null +++ b/toontown/src/speedchat/TTSCToontaskTerminal.py @@ -0,0 +1,43 @@ +"""TTSCToontaskTerminal.py: contains the SCToontaskTerminal class""" + +from otp.speedchat.SCTerminal import * +from toontown.quest import Quests +from toontown.toon import NPCToons + +# args: taskId, toNpcId, toonProgress, msgIndex +TTSCToontaskMsgEvent = 'SCToontaskMsg' + +def decodeTTSCToontaskMsg(taskId, toNpcId, toonProgress, msgIndex): + q = Quests.getQuest(taskId) + if q is None: + return None + # validate toNpcId + name = NPCToons.getNPCName(toNpcId) + if name is None: + return None + msgs = q.getSCStrings(toNpcId, toonProgress) + if type(msgs) != type([]): + msgs = [msgs] + if msgIndex >= len(msgs): + return None + return msgs[msgIndex] + +class TTSCToontaskTerminal(SCTerminal): + """ TTSCToontaskTerminal represents a terminal SpeedChat node that + contains a ToonTask-generated phrase. """ + def __init__(self, msg, taskId, toNpcId, toonProgress, msgIndex): + SCTerminal.__init__(self) + self.msg = msg + self.taskId = taskId + self.toNpcId = toNpcId + self.toonProgress = toonProgress + self.msgIndex = msgIndex + + def getDisplayText(self): + return self.msg + + def handleSelect(self): + SCTerminal.handleSelect(self) + messenger.send(self.getEventName(TTSCToontaskMsgEvent), + [self.taskId, self.toNpcId, + self.toonProgress, self.msgIndex]) diff --git a/toontown/src/speedchat/TTSCVictoryPartiesMenu.py b/toontown/src/speedchat/TTSCVictoryPartiesMenu.py new file mode 100644 index 0000000..a33e028 --- /dev/null +++ b/toontown/src/speedchat/TTSCVictoryPartiesMenu.py @@ -0,0 +1,94 @@ +## Victory Parties speedchat phrases ## + +from direct.showbase import PythonUtil +from otp.speedchat.SCMenu import SCMenu +from otp.speedchat.SCMenuHolder import SCMenuHolder +from otp.speedchat.SCStaticTextTerminal import SCStaticTextTerminal +from otp.otpbase.OTPLocalizer import SpeedChatStaticText +from toontown.speedchat.TTSCIndexedTerminal import TTSCIndexedTerminal +from otp.otpbase import OTPLocalizer + + +#this is the structure of the victory parties menu +VictoryPartiesMenu = [ + (OTPLocalizer.VictoryPartiesMenuSections[1],[60350, 60351, 60352, 60353, 60354]), + (OTPLocalizer.VictoryPartiesMenuSections[2],[60355, 60356, 60357, 60358, 60359, 60360, 60361]), + (OTPLocalizer.VictoryPartiesMenuSections[0],[]), + ] + +class TTSCVictoryPartiesMenu(SCMenu): + """ + Speedchat phrases for Victory Parties + """ + + def __init__(self): + SCMenu.__init__(self) + + self.__messagesChanged() + submenus = [] + + def destroy(self): + SCMenu.destroy(self) + + def clearMenu(self): + SCMenu.clearMenu(self) + + def __messagesChanged(self): + """ + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in VictoryPartiesMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1].keys(): + blatherTxt = section[1][phrase] + if blatherTxt not in OTPLocalizer.SpeedChatStaticText: + self.notify.warning("tried to link Victory Parties phrase %s which does not seem to exist" % blatherTxt) + break + self.append(TTSCIndexedTerminal(SpeedChatStaticText.get(phrase, None), blatherTxt)) + else: + #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + self.notify.warning("tried to link Victory Parties phrase %s which does not seem to exist" % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) + """ + + # clear out everything from our menu + self.clearMenu() + + # if local toon has not been created, don't panic + try: + lt = base.localAvatar + except: + return + for section in VictoryPartiesMenu: + if section[0] == -1: + #This is not a submenu but a terminal! + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Victory Parties phrase %s which does not seem to exist' % phrase) + break + self.append(SCStaticTextTerminal(phrase)) + else: #this should be a submenu + menu = SCMenu() + for phrase in section[1]: + if phrase not in OTPLocalizer.SpeedChatStaticText: + print ('warning: tried to link Victory Parties phrase %s which does not seem to exist' % phrase) + break + menu.append(SCStaticTextTerminal(phrase)) + + menuName = str(section[0]) + self.append( SCMenuHolder(menuName, menu) ) + \ No newline at end of file diff --git a/toontown/src/speedchat/TTSpeedChatGlobals.py b/toontown/src/speedchat/TTSpeedChatGlobals.py new file mode 100644 index 0000000..3dfad5d --- /dev/null +++ b/toontown/src/speedchat/TTSpeedChatGlobals.py @@ -0,0 +1,10 @@ +"""TTSpeedChatGlobals.py: global Toontown specific SpeedChat data """ + +# These are the base names of the events that may be generated by +# a SpeedChat object. +# To get the actual event name, use: +# speedChat.getEventName(eventBaseName) +# where 'speedChat' is your SpeedChat object, and 'eventBaseName' is +# one of the following: +from TTSCToontaskTerminal import TTSCToontaskMsgEvent +from TTSCResistanceTerminal import TTSCResistanceMsgEvent diff --git a/toontown/src/speedchat/TTSpeedChatTypes.py b/toontown/src/speedchat/TTSpeedChatTypes.py new file mode 100644 index 0000000..3f09ad1 --- /dev/null +++ b/toontown/src/speedchat/TTSpeedChatTypes.py @@ -0,0 +1,23 @@ +"""TTSpeedChatTypes.py: Toontown specific SpeedChat types """ + +from TTSCToontaskMenu import TTSCToontaskMenu +from TTSCFactoryMenu import TTSCFactoryMenu +from TTSCCogMenu import TTSCCogMenu +if hasattr(base, 'wantPets') and base.wantPets: + from TTSCPetTrickMenu import TTSCPetTrickMenu +from TTSCPromotionalMenu import TTSCPromotionalMenu +from TTSCToontaskTerminal import TTSCToontaskTerminal +from TTSCResistanceMenu import TTSCResistanceMenu +from TTSCResistanceTerminal import TTSCResistanceTerminal +from TTSCKartRacingMenu import TTSCKartRacingMenu +from TTSCGolfMenu import TTSCGolfMenu +from TTSCBoardingMenu import TTSCBoardingMenu +from TTSCSingingMenu import TTSCSingingMenu +from TTSCAprilToonsMenu import TTSCAprilToonsMenu +from TTSCSillyPhaseOneMenu import TTSCSillyPhaseOneMenu +from TTSCSillyPhaseTwoMenu import TTSCSillyPhaseTwoMenu +from TTSCSillyPhaseThreeMenu import TTSCSillyPhaseThreeMenu +from TTSCSillyPhaseFourMenu import TTSCSillyPhaseFourMenu +from TTSCSillyPhaseFiveMenu import TTSCSillyPhaseFiveMenu +from TTSCCarolMenu import TTSCCarolMenu +from TTSCVictoryPartiesMenu import TTSCVictoryPartiesMenu diff --git a/toontown/src/speedchat/__init__.py b/toontown/src/speedchat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/suit/.cvsignore b/toontown/src/suit/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/suit/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/suit/BossCog.py b/toontown/src/suit/BossCog.py new file mode 100644 index 0000000..88f19bc --- /dev/null +++ b/toontown/src/suit/BossCog.py @@ -0,0 +1,826 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.actor import Actor +from otp.avatar import Avatar +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from direct.fsm import FSM +from direct.fsm import State +from toontown.toonbase import TTLocalizer +from toontown.battle import BattleParticles +import Suit +from direct.task.Task import Task +import SuitDNA +from toontown.battle import BattleProps +from direct.showbase.PythonUtil import Functor +import string +import types + +GenericModel = "phase_9/models/char/bossCog" + + +ModelDict = { + "s": ("phase_9/models/char/sellbotBoss"), + "m": ("phase_10/models/char/cashbotBoss"), + "l": ("phase_11/models/char/lawbotBoss"), + "c": ("phase_12/models/char/bossbotBoss"), + } + +AnimList = ( + "Ff_speech", "ltTurn2Wave", "wave", "Ff_lookRt", "turn2Fb", + "Ff_neutral", "Bb_neutral", "Ff2Bb_spin", "Bb2Ff_spin", + "Fb_neutral", "Bf_neutral", "Fb_firstHit", + "Fb_downNeutral", "Fb_downHit", "Fb_fall", + "Fb_down2Up", "Fb_downLtSwing", "Fb_downRtSwing", + "Fb_DownThrow", "Fb_UpThrow", "Fb_jump", "golf_swing" + ) + +class BossCog(Avatar.Avatar): + """ + The BossCog is the big supervisor Cog that we have to fight at the + end of the CogHQ level. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('BossCog') + + healthColors = Suit.Suit.healthColors + healthGlowColors = Suit.Suit.healthGlowColors + + def __init__(self): + Avatar.Avatar.__init__(self) + self.setFont(ToontownGlobals.getSuitFont()) + self.setPlayerType(NametagGroup.CCSuit) + self.setPickable(0) + + self.doorA = None + self.doorB = None + self.bubbleL = None + self.bubbleR = None + + # These variables are used to track the current state for + # animation when you call doAnimate(). + self.raised = 1 + self.forward = 1 + self.happy = 1 + self.dizzy = 0 + self.nowRaised = 1 + self.nowForward = 1 + self.nowHappy = 1 + self.currentAnimIval = None + self.queuedAnimIvals = [] + + self.treadsLeftPos = 0 + self.treadsRightPos = 0 + + self.healthBar = None + self.healthCondition = 0 + + # We don't need to uniquify these since there is only one + # BossCog on a client at any given time. + self.animDoneEvent = 'BossCogAnimDone' + self.animIvalName = 'BossCogAnimIval' + + def delete(self): + Avatar.Avatar.delete(self) + self.removeHealthBar() + self.setDizzy(0) + self.stopAnimate() + if self.doorA: + self.doorA.request('Off') + self.doorB.request('Off') + self.doorA = None + self.doorB = None + + def setDNAString(self, dnaString): + self.dna = SuitDNA.SuitDNA() + self.dna.makeFromNetString(dnaString) + self.setDNA(self.dna) + + def setDNA(self, dna): + if self.style: + pass + else: + # store the DNA + self.style = dna + + self.generateBossCog() + + # this no longer works in the Avatar init! + # I moved it here for lack of a better place + # make the drop shadow + self.initializeDropShadow() + if base.wantNametags: + self.initializeNametag3d() + + def generateBossCog(self): + self.throwSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_frisbee_gears.mp3') + self.swingSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_swipe.mp3') + self.spinSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_spin.mp3') + self.rainGearsSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_raining_gears.mp3') + self.swishSfx = loader.loadSfx('phase_5/audio/sfx/General_throw_miss.mp3') + self.boomSfx = loader.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart.mp3') + self.deathSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_big_death.mp3') + self.upSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_raise_up.mp3') + self.downSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_collapse.mp3') + self.reelSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_reeling_backwards.mp3') + #self.treadsSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_tractor_treads.mp3') + self.birdsSfx = loader.loadSfx('phase_4/audio/sfx/SZ_TC_bird1.mp3') + self.dizzyAlert = loader.loadSfx('phase_5/audio/sfx/AA_sound_aoogah.mp3') + self.grunt = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_grunt.mp3') + self.murmur = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_murmur.mp3') + self.statement = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_statement.mp3') + self.question = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_question.mp3') + + self.dialogArray = [ + self.grunt, self.murmur, self.statement, self.question, self.statement, self.statement + ] + + dna = self.style + filePrefix = ModelDict[dna.dept] + # TODO: bosscogs have unique 'legs' + #if dna.dept == 'c': + # self.loadModel(filePrefix + "-legs-zero", "legs") + #else: + # self.loadModel(GenericModel + "-legs-zero", "legs") + self.loadModel(GenericModel + "-legs-zero", "legs") + self.loadModel(filePrefix + "-torso-zero", "torso") + self.loadModel(filePrefix + "-head-zero", "head") + + # This is true if the head model has two faces: happy on the + # front side, angry on the back side (e.g. sellbot). It's + # false if the head model only has a front side (which is + # presumably angry, e.g. cashbot). + self.twoFaced = (dna.dept == 's') + + self.attach("head", "torso", "joint34") + self.attach("torso", "legs", "joint_pelvis") + + # He might need an extra node to rotate for going up ramps. + self.rotateNode = self.attachNewNode('rotate') + geomNode = self.getGeomNode() + geomNode.reparentTo(self.rotateNode) + + # A node for hosting the "rain of gears" particle attack. + self.frontAttack = self.rotateNode.attachNewNode('frontAttack') + self.frontAttack.setPos(0, -10, 10) + self.frontAttack.setScale(2) + + self.setHeight(26) + self.nametag3d.setScale(2) + + for partName in ("legs", "torso", "head"): + animDict = {} + for anim in AnimList: + animDict[anim] = "%s-%s-%s" % (GenericModel, partName, anim) + + self.loadAnims(animDict, partName) + + # We might need some stars if he gets dizzy. + self.stars = BattleProps.globalPropPool.getProp('stun') + self.stars.setPosHprScale(7, 0, 0, 0, 0, -90, 3, 3, 3) + self.stars.loop('stun') + + # Establish local control over some of the joints. + self.pelvis = self.getPart("torso") + self.pelvisForwardHpr = VBase3(0, 0, 0) + self.pelvisReversedHpr = VBase3(-180, 0, 0) + self.neck = self.getPart("head") + self.neckForwardHpr = VBase3(0, 0, 0) + # -540 makes him spin his head once and a half to switch directions. + self.neckReversedHpr = VBase3(0, -540, 0) + + self.axle = self.find('**/joint_axle') + + self.doorA = self.__setupDoor( + '**/joint_doorFront', 'doorA', self.doorACallback, + VBase3(0, 0, 0), VBase3(0, 0, -80), + CollisionPolygon(Point3(5, -4, 0.32), Point3(0, -4, 0), + Point3(0, 4, 0), Point3(5, 4, 0.32))) + + self.doorB = self.__setupDoor( + '**/joint_doorRear', 'doorB', self.doorBCallback, + VBase3(0, 0, 0), VBase3(0, 0, 80), + CollisionPolygon(Point3(-5, 4, 0.84), Point3(0, 4, 0), + Point3(0, -4, 0), Point3(-5, -4, 0.84))) + + # Get the treads in there. They come from a separate model. + treadsModel = loader.loadModel('%s-treads' % (GenericModel)) + treadsModel.reparentTo(self.axle) + self.treadsLeft = treadsModel.find('**/right_tread') + self.treadsRight = treadsModel.find('**/left_tread') + + self.doorA.request('Closed') + self.doorB.request('Closed') + + def initializeBodyCollisions(self, collIdStr): + Avatar.Avatar.initializeBodyCollisions(self, collIdStr) + + if not self.ghostMode: + self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask) + + def generateHealthBar(self): + """ + Create a health meter for the suit and put it on his chest + """ + self.removeHealthBar() + + chestNull = self.find('**/joint_lifeMeter') + if chestNull.isEmpty(): + return + + # Create health button for the suit + model = loader.loadModel('phase_3.5/models/gui/matching_game_gui') + button = model.find('**/minnieCircle') + button.setScale(6.0) + button.setP(-20) + button.setColor(self.healthColors[0]) + button.reparentTo(chestNull) + self.healthBar = button + glow = BattleProps.globalPropPool.getProp('glow') + glow.reparentTo(self.healthBar) + glow.setScale(0.28) + glow.setPos(-0.005, 0.01, 0.015) + glow.setColor(self.healthGlowColors[0]) + button.flattenLight() + + self.healthBarGlow = glow + self.healthCondition = 0 + + def updateHealthBar(self): + if self.healthBar == None: + return + + health = 1.0 - (float(self.bossDamage) / float(self.bossMaxDamage)) + if (health > 0.95): + condition = 0 + elif (health > 0.7): + condition = 1 + elif (health > 0.3): + condition = 2 + elif (health > 0.05): + condition = 3 + elif (health > 0.0): + # This should be blinking red + condition = 4 + else: + # This should be blinking red even faster + condition = 5 + + if (self.healthCondition != condition): + if (condition == 4): + blinkTask = Task.loop(Task(self.__blinkRed), + Task.pause(0.75), + Task(self.__blinkGray), + Task.pause(0.1)) + taskMgr.add(blinkTask, self.uniqueName('blink-task')) + elif (condition == 5): + if (self.healthCondition == 4): + taskMgr.remove(self.uniqueName('blink-task')) + blinkTask = Task.loop(Task(self.__blinkRed), + Task.pause(0.25), + Task(self.__blinkGray), + Task.pause(0.1)) + taskMgr.add(blinkTask, self.uniqueName('blink-task')) + else: + self.healthBar.setColor(self.healthColors[condition],1) + self.healthBarGlow.setColor(self.healthGlowColors[condition],1) + self.healthCondition = condition + + def __blinkRed(self, task): + self.healthBar.setColor(self.healthColors[3],1) + self.healthBarGlow.setColor(self.healthGlowColors[3],1) + if (self.healthCondition == 5): + self.healthBar.setScale(1.17) + return Task.done + + def __blinkGray(self, task): + self.healthBar.setColor(self.healthColors[4],1) + self.healthBarGlow.setColor(self.healthGlowColors[4],1) + if (self.healthCondition == 5): + self.healthBar.setScale(1.0) + return Task.done + + def removeHealthBar(self): + if self.healthBar: + self.healthBar.removeNode() + self.healthBar = None + if (self.healthCondition == 4 or self.healthCondition == 5): + taskMgr.remove(self.uniqueName('blink-task')) + self.healthCondition = 0 + + def reverseHead(self): + # Call this before playing an animation that shows him happy + # when he should be angry, or vice-versa. + self.neck.setHpr(self.neckReversedHpr) + + def forwardHead(self): + # Call this to undo the effects of reverseHead(). + self.neck.setHpr(self.neckForwardHpr) + + def reverseBody(self): + # Call this before playing an animation that shows him facing + # the wrong direction. + self.pelvis.setHpr(self.pelvisReversedHpr) + + def forwardBody(self): + # Call this to undo the effects of reverseBody(). + self.pelvis.setHpr(self.pelvisForwardHpr) + + def getShadowJoint(self): + return self.getGeomNode() + + def getNametagJoints(self): + """ + Return the CharacterJoint that animates the nametag, in a list. + """ + return [] + + def getDialogueArray(self): + return self.dialogArray + + def doorACallback(self, isOpen): + # Called whenever doorA opens or closes. + pass + + def doorBCallback(self, isOpen): + # Called whenever doorB opens or closes. + pass + + # Get intervals that rolls the left and right treads forward or + # backward from the current position. + def __rollTreadsInterval(self, object, start = 0, duration = 0, rate = 1): + def rollTexMatrix(t, object = object): + object.setTexOffset(TextureStage.getDefault(), t, 0) + + return LerpFunctionInterval(rollTexMatrix, fromData = start, + toData = start + rate * duration, + duration = duration) + + def rollLeftTreads(self, duration, rate): + start = self.treadsLeftPos + self.treadsLeftPos += duration * rate + return self.__rollTreadsInterval( + self.treadsLeft, start = start, duration = duration, rate = rate) + + def rollRightTreads(self, duration, rate): + start = self.treadsRightPos + self.treadsRightPos += duration * rate + return self.__rollTreadsInterval( + self.treadsRight, start = start, duration = duration, rate = rate) + + # Manage the doors on the bottom that open and close for Cogs to + # walk out. + + class DoorFSM(FSM.FSM): + def __init__(self, name, animate, callback, + openedHpr, closedHpr, uniqueName): + FSM.FSM.__init__(self, name) + self.animate = animate + self.callback = callback + self.openedHpr = openedHpr + self.closedHpr = closedHpr + self.uniqueName = uniqueName + self.ival=0 + self.openSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_door_open.mp3') + self.closeSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_door_close.mp3') + + self.request('Closed') + + def filterOpening(self, request, args): + if (request == 'close'): + return 'Closing' + return self.defaultFilter(request, args) + + def enterOpening(self): + intervalName = self.uniqueName('open-%s' % (self.animate.getName())) + + self.callback(0) + ival = Parallel( + SoundInterval(self.openSfx, node = self.animate, volume = 0.2), + self.animate.hprInterval(1, self.openedHpr, + blendType = 'easeInOut'), + Sequence(Wait(0.2), Func(self.callback, 1)), + name = intervalName) + ival.start() + self.ival = ival + + def exitOpening(self): + self.ival.pause() + self.ival = None + + def filterOpened(self, request, args): + if (request == 'close'): + return 'Closing' + return self.defaultFilter(request, args) + + def enterOpened(self): + self.animate.setHpr(self.openedHpr) + self.callback(1) + + def filterClosing(self, request, args): + if (request == 'open'): + return 'Opening' + return self.defaultFilter(request, args) + + def enterClosing(self): + intervalName = self.uniqueName('close-%s' % (self.animate.getName())) + + self.callback(1) + ival = Parallel( + SoundInterval(self.closeSfx, node = self.animate, volume = 0.2), + self.animate.hprInterval(1, self.closedHpr, + blendType = 'easeInOut'), + Sequence(Wait(0.8), Func(self.callback, 0)), + name = intervalName) + ival.start() + self.ival = ival + + def exitClosing(self): + self.ival.pause() + self.ival = None + + def filterClosed(self, request, args): + if (request == 'open'): + return 'Opening' + return self.defaultFilter(request, args) + + def enterClosed(self): + self.animate.setHpr(self.closedHpr) + self.callback(0) + + + def __setupDoor(self, jointName, name, callback, + openedHpr, closedHpr, cPoly): + # Find the door joint in the model and set it up for + # animation. + joint = self.find(jointName) + + children = joint.getChildren() + animate = joint.attachNewNode(name) + children.reparentTo(animate) + + # Put the collision polygon there too. + cnode = CollisionNode('BossZap') + cnode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask | ToontownGlobals.CameraBitmask) + cnode.addSolid(cPoly) + animate.attachNewNode(cnode) + + # Now we create a tiny FSM to manage the door's state. + fsm = self.DoorFSM(name, animate, callback, + openedHpr, closedHpr, self.uniqueName) + + return fsm + + ### doAnimate and related functions ### + + ## These functions are used to maintain a steady state of + ## animation on the boss. To play a particular animation, call + ## doAnimate(anim = animName). The animation will be queued for + ## playing when the Boss finishes his current cycle; to play it + ## immediately (and get a "pop" in the animation), pass now = 1. + + ## When the list of currently queued animations is exhausted, the + ## boss will automatically switch back into whatever neutral cycle + ## is appropriate based on his last-played animation. + + def doAnimate(self, anim = None, now = 0, queueNeutral = 1, + raised = None, forward = None, happy = None): + # Queue a particular cycle for playback, or start the + # smart-playback mechanism. See comments above. + + #self.notify.debug('BossCog.doAnimate anim=%s now=%s queueNeutral=%s raised=%s forward=%s happy=%s' % (anim, now,queueNeutral, raised, forward, happy)) + + if now: + # Blow away the currently playing animation and saved + # queue; we want to see this animation now. + self.stopAnimate() + + if not self.twoFaced: + # If he's only got one face, it's always "happy", or + # forward. + happy = 1 + + if raised == None: + raised = self.raised + if forward == None: + forward = self.forward + if happy == None: + happy = self.happy + if now: + self.raised = raised + self.forward = forward + self.happy = happy + + if self.currentAnimIval == None: + self.accept(self.animDoneEvent, self.__getNextAnim) + + else: + # If we already have an animation playing, no need to + # queue up the neutral cycle. + queueNeutral = 0 + + ival, changed = self.__getAnimIval(anim, raised, forward, happy) + + if changed or queueNeutral: + # Queue up and/or play the particular requested anim. + self.queuedAnimIvals.append((ival, self.raised, self.forward, self.happy)) + if self.currentAnimIval == None: + self.__getNextAnim() + + def stopAnimate(self): + # Stop the currently playing and currently queued animations. + self.ignore(self.animDoneEvent) + self.queuedAnimIvals = [] + if self.currentAnimIval: + self.currentAnimIval.setDoneEvent('') + self.currentAnimIval.finish() + self.currentAnimIval = None + self.raised = self.nowRaised + self.forward = self.nowForward + self.happy = self.nowHappy + + def __getNextAnim(self): + # Picks the next animation in the queue when the current + # animation runs out. + if self.queuedAnimIvals: + ival, raised, forward, happy = self.queuedAnimIvals[0] + del self.queuedAnimIvals[0] + else: + ival, changed = self.__getAnimIval(None, self.raised, self.forward, self.happy) + raised = self.raised + forward = self.forward + happy = self.happy + + if self.currentAnimIval: + self.currentAnimIval.setDoneEvent('') + self.currentAnimIval.finish() + self.currentAnimIval = ival + self.currentAnimIval.start() + self.nowRaised = raised + self.nowForward = forward + self.nowHappy = happy + + def __getAnimIval(self, anim, raised, forward, happy): + # Returns an interval to play the indicated animation, and + # also updates the internal state according to the nature of + # the animation. + ival, changed = self.__doGetAnimIval(anim, raised, forward, happy) + seq = Sequence(ival, name = self.animIvalName) + seq.setDoneEvent(self.animDoneEvent) + return seq, changed + + def __doGetAnimIval(self, anim, raised, forward, happy): + # First, we might need to insert some transition animations to + # match the new one. + + if (raised == self.raised and forward == self.forward and happy == self.happy): + # We are not changing state. + return self.getAnim(anim), (anim != None) + + # We are changing state. Figure out how to get there. + startsHappy = self.happy + endsHappy = self.happy + ival = Sequence() + + if raised and not self.raised: + # Pop up first. + upIval = self.getAngryActorInterval('Fb_down2Up') + + if self.forward: + ival = upIval + else: + ival = Sequence( + Func(self.reverseBody), + upIval, + Func(self.forwardBody)) + ival = Parallel(SoundInterval(self.upSfx, node = self), + ival) + + if forward != self.forward: + # Spin to new facing. + if forward: + animName = 'Bb2Ff_spin' + else: + animName = 'Ff2Bb_spin' + ival = Sequence(ival, ActorInterval(self, animName)) + startsHappy = 1 + endsHappy = 1 + + startNeckHpr = self.neckForwardHpr + endNeckHpr = self.neckForwardHpr + if self.happy != startsHappy: + startNeckHpr = self.neckReversedHpr + if happy != endsHappy: + endNeckHpr = self.neckReversedHpr + + if startNeckHpr != endNeckHpr: + ival = Sequence( + Func(self.neck.setHpr, startNeckHpr), + ParallelEndTogether( + ival, + Sequence(self.neck.hprInterval(0.5, endNeckHpr, + startHpr = startNeckHpr, + blendType = 'easeInOut'), + Func(self.neck.setHpr, self.neckForwardHpr)))) + + elif endNeckHpr != self.neckForwardHpr: + ival = Sequence(Func(self.neck.setHpr, startNeckHpr), + ival, + Func(self.neck.setHpr, self.neckForwardHpr)) + + if not raised and self.raised: + # Pop down after. + downIval = self.getAngryActorInterval('Fb_down2Up', playRate = -1) + + if forward: + ival = Sequence(ival, downIval) + else: + ival = Sequence( + ival, + Func(self.reverseBody), + downIval, + Func(self.forwardBody)) + + ival = Parallel(SoundInterval(self.downSfx, node = self), + ival) + + self.raised = raised + self.forward = forward + self.happy = happy + + # Now tack on the animation we're trying for. + if anim != None: + ival = Sequence(ival, self.getAnim(anim)) + + return ival, 1 + + def setDizzy(self, dizzy): + if dizzy and not self.dizzy: + base.playSfx(self.dizzyAlert) + + self.dizzy = dizzy + if dizzy: + self.stars.reparentTo(self.neck) + base.playSfx(self.birdsSfx, looping = 1) + else: + self.stars.detachNode() + self.birdsSfx.stop() + + def getAngryActorInterval(self, animName, **kw): + # Returns an ActorInterval to play the indicated angry + # animation. If self.happy is true, flips the head around to + # show the happy (or only) face while playing it. + if self.happy: + ival = Sequence( + Func(self.reverseHead), + ActorInterval(self, animName, **kw), + Func(self.forwardHead)) + else: + ival = ActorInterval(self, animName, **kw) + + return ival + + def getAnim(self, anim): + # A low-level function to return an interval to play the + # indicated animation, and update the internal state as if the + # animation has been played. This may be called by external + # objects. + ival = None + if anim == None: + # Neutral cycle + partName = None + + if self.happy: + animName = 'Ff_neutral' + else: + animName = 'Fb_neutral' + + if self.raised: + # Play the animation on the whole boss. + ival = ActorInterval(self, animName) + else: + # Play the animation on the upper part of the boss + # only, and play the lowered animation on the legs. + ival = Parallel(ActorInterval(self, animName, + partName = ['torso', 'head']), + ActorInterval(self, 'Fb_downNeutral', + partName = 'legs')) + + if not self.forward: + ival = Sequence( + Func(self.reverseBody), + ival, + Func(self.forwardBody)) + + elif anim == 'down2Up': + ival = Parallel(SoundInterval(self.upSfx, node = self), + self.getAngryActorInterval('Fb_down2Up')) + + self.raised = 1 + + elif anim == 'up2Down': + # We fake this animation by playing down2Up backward. + ival = Parallel(SoundInterval(self.downSfx, node = self), + self.getAngryActorInterval('Fb_down2Up', playRate = -1)) + + self.raised = 0 + + elif anim == 'throw': + self.doAnimate(None, raised = 1, happy = 0, queueNeutral = 0) + + ival = Parallel(Sequence(SoundInterval(self.throwSfx, node = self), duration = 0), + self.getAngryActorInterval('Fb_UpThrow')) + + elif anim == 'hit': + if self.raised: + # Hits always knock us down. + self.raised = 0 + ival = self.getAngryActorInterval('Fb_firstHit') + else: + ival = self.getAngryActorInterval('Fb_downHit') + + ival = Parallel(SoundInterval(self.reelSfx, node = self), ival) + + elif anim == 'ltSwing' or anim == 'rtSwing': + self.doAnimate(None, raised = 0, happy = 0, queueNeutral = 0) + + if anim == 'ltSwing': + ival = Sequence(Track((0, self.getAngryActorInterval('Fb_downLtSwing')), + (0.9, SoundInterval(self.swingSfx, node = self)), + (1, Func(self.bubbleL.unstash))), + Func(self.bubbleL.stash)) + else: + ival = Sequence(Track((0, self.getAngryActorInterval('Fb_downRtSwing')), + (0.9, SoundInterval(self.swingSfx, node = self)), + (1, Func(self.bubbleR.unstash))), + Func(self.bubbleR.stash)) + + elif anim == 'frontAttack': + # This is a bit hacky, and involves code defined in + # DistributedSellbotBoss.py. + self.doAnimate(None, raised = 1, happy = 0, queueNeutral = 0) + + pe = BattleParticles.loadParticleFile('bossCogFrontAttack.ptf') + + # Keep the head reversed so we play the spin animation + # with the sad face showing. + ival = Sequence( + Func(self.reverseHead), + ActorInterval(self, 'Bb2Ff_spin'), + Func(self.forwardHead)) + + if self.forward: + # The animation starts with the torso reversed; undo this. + ival = Sequence( + Func(self.reverseBody), + ParallelEndTogether(ival, + self.pelvis.hprInterval(0.5, self.pelvisForwardHpr, + blendType = 'easeInOut')), + ) + + ival = Sequence(Track((0, ival), + (0, SoundInterval(self.spinSfx, node = self)), + (0.9, Parallel(SoundInterval(self.rainGearsSfx, node = self), + ParticleInterval(pe, self.frontAttack, worldRelative = 0, + duration = 1.5, cleanup = True), + duration = 0)), + (1.9, Func(self.bubbleF.unstash)), + ), + Func(self.bubbleF.stash)) + self.forward = 1 + self.happy = 0 + self.raised = 1 + + elif anim == 'areaAttack': + # This is hacky too, just like above. + if self.twoFaced: + self.doAnimate(None, raised = 1, happy = 0, queueNeutral = 0) + else: + #RAU the lawbot is one faced + self.doAnimate(None, raised = 1, happy = 1, queueNeutral = 1) + + ival = Parallel(ActorInterval(self, 'Fb_jump'), + Sequence(SoundInterval(self.swishSfx, duration = 1.1, node = self), + SoundInterval(self.boomSfx, duration = 1.9)), + Sequence(Wait(1.21), + Func(self.announceAreaAttack))) + if self.twoFaced: + self.happy = 0 + else: + self.happy = 1 + + self.raised = 1 + + elif anim == 'Fb_fall': + ival = Parallel(ActorInterval(self, 'Fb_fall'), + Sequence(SoundInterval(self.reelSfx, node = self), + SoundInterval(self.deathSfx))) + + elif isinstance(anim, types.StringType): + ival = ActorInterval(self, anim) + + else: + # It must be an interval to play directly. + ival = anim + + return ival diff --git a/toontown/src/suit/DistributedBossCog.py b/toontown/src/suit/DistributedBossCog.py new file mode 100644 index 0000000..258f1d3 --- /dev/null +++ b/toontown/src/suit/DistributedBossCog.py @@ -0,0 +1,1640 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import * +from direct.directnotify import DirectNotifyGlobal +from otp.avatar import DistributedAvatar +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.battle import BattleExperience +from toontown.battle import BattleBase +import BossCog +import SuitDNA +from toontown.coghq import CogDisguiseGlobals +from direct.showbase import Transitions +from toontown.hood import ZoneUtil +from toontown.building import ElevatorUtils +from toontown.building import ElevatorConstants +from toontown.distributed import DelayDelete +from toontown.effects import DustCloud +from toontown.toonbase import TTLocalizer +from toontown.friends import FriendsListManager +from direct.controls.ControlManager import CollisionHandlerRayStart +from direct.showbase import PythonUtil +import random + +class DistributedBossCog(DistributedAvatar.DistributedAvatar, + BossCog.BossCog): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBossCog') + + allowClickedNameTag = True #do we let toons click on other toons to see the hp? + + def __init__(self, cr): + DistributedAvatar.DistributedAvatar.__init__(self, cr) + BossCog.BossCog.__init__(self) + + self.gotAllToons = 0 + self.toonsA = [] + self.toonsB = [] + self.involvedToons = [] + self.toonRequest = None + self.battleNumber = 0 + self.battleAId = None + self.battleBId = None + self.battleA = None + self.battleB = None + self.battleRequest = None + self.arenaSide = 0 + + self.toonSphere = None + self.localToonIsSafe = 0 + + self.__toonsStuckToFloor = [] + self.cqueue = None + self.rays = None + self.ray1 = None + self.ray2 = None + self.ray3 = None + self.e1 = None + self.e2 = None + self.e3 = None + + # These nodes mark the relative positions of the battles that + # take place on either side of the BossCog. + self.battleANode = self.attachNewNode('battleA') + self.battleBNode = self.attachNewNode('battleB') + self.battleANode.setPosHpr(*ToontownGlobals.BossCogBattleAPosHpr) + self.battleBNode.setPosHpr(*ToontownGlobals.BossCogBattleBPosHpr) + + self.activeIntervals = {} + self.flashInterval = None + + self.elevatorType = ElevatorConstants.ELEVATOR_VP + + def announceGenerate(self): + DistributedAvatar.DistributedAvatar.announceGenerate(self) + + # store the cog-suit level that localToon came in with + self.prevCogSuitLevel = localAvatar.getCogLevels()[ + CogDisguiseGlobals.dept2deptIndex(self.style.dept)] + + # Put a big trigger bubble around the cog so we can tell the + # AI when the avatar is within directed-attack range. + nearBubble = CollisionSphere(0, 0, 0, 50) + nearBubble.setTangible(0) + nearBubbleNode = CollisionNode('NearBoss') + nearBubbleNode.setCollideMask(ToontownGlobals.WallBitmask) + nearBubbleNode.addSolid(nearBubble) + self.attachNewNode(nearBubbleNode) + self.accept('enterNearBoss', self.avatarNearEnter) + self.accept('exitNearBoss', self.avatarNearExit) + + # Replace the default collision sphere with a pair of tubes + # along the treads to deflect the toon, and a poly across the + # top and around the sides to keep out pies. + self.collNode.removeSolid(0) + tube1 = CollisionTube(6.5, -7.5, 2, 6.5, 7.5, 2, 2.5) + tube2 = CollisionTube(-6.5, -7.5, 2, -6.5, 7.5, 2, 2.5) + roof = CollisionPolygon( + Point3(-4.4, 7.1, 5.5), Point3(-4.4, -7.1, 5.5), + Point3(4.4, -7.1, 5.5), Point3(4.4, 7.1, 5.5)) + side1 = CollisionPolygon( + Point3(-4.4, -7.1, 5.5), Point3(-4.4, 7.1, 5.5), + Point3(-4.4, 7.1, 0), Point3(-4.4, -7.1, 0)) + side2 = CollisionPolygon( + Point3(4.4, 7.1, 5.5), Point3(4.4, -7.1, 5.5), + Point3(4.4, -7.1, 0), Point3(4.4, 7.1, 0)) + front1 = CollisionPolygon( + Point3(4.4, -7.1, 5.5), Point3(-4.4, -7.1, 5.5), + Point3(-4.4, -7.1, 5.2), Point3(4.4, -7.1, 5.2)) + back1 = CollisionPolygon( + Point3(-4.4, 7.1, 5.5), Point3(4.4, 7.1, 5.5), + Point3(4.4, 7.1, 5.2), Point3(-4.4, 7.1, 5.2)) + self.collNode.addSolid(tube1) + self.collNode.addSolid(tube2) + self.collNode.addSolid(roof) + self.collNode.addSolid(side1) + self.collNode.addSolid(side2) + self.collNode.addSolid(front1) + self.collNode.addSolid(back1) + self.collNodePath.reparentTo(self.axle) + self.collNode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask | ToontownGlobals.CameraBitmask) + + # Also listen for collisions with all that. That hurts! + self.collNode.setName('BossZap') + self.setTag('attackCode', str(ToontownGlobals.BossCogElectricFence)) + self.accept('enterBossZap', self.__touchedBoss) + + # Have some bubbles ready to zap the toons with the swat + # attacks too. + + bubbleL = CollisionSphere(10, -5, 0, 10) + bubbleL.setTangible(0) + bubbleLNode = CollisionNode('BossZap') + bubbleLNode.setCollideMask(ToontownGlobals.WallBitmask) + bubbleLNode.addSolid(bubbleL) + self.bubbleL = self.axle.attachNewNode(bubbleLNode) + self.bubbleL.setTag('attackCode', str(ToontownGlobals.BossCogSwatLeft)) + self.bubbleL.stash() + + bubbleR = CollisionSphere(-10, -5, 0, 10) + bubbleR.setTangible(0) + bubbleRNode = CollisionNode('BossZap') + bubbleRNode.setCollideMask(ToontownGlobals.WallBitmask) + bubbleRNode.addSolid(bubbleR) + self.bubbleR = self.axle.attachNewNode(bubbleRNode) + self.bubbleR.setTag('attackCode', str(ToontownGlobals.BossCogSwatRight)) + self.bubbleR.stash() + + bubbleF = CollisionSphere(0, -25, 0, 12) + bubbleF.setTangible(0) + bubbleFNode = CollisionNode('BossZap') + bubbleFNode.setCollideMask(ToontownGlobals.WallBitmask) + bubbleFNode.addSolid(bubbleF) + self.bubbleF = self.rotateNode.attachNewNode(bubbleFNode) + self.bubbleF.setTag('attackCode', str(ToontownGlobals.BossCogFrontAttack)) + self.bubbleF.stash() + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + DistributedAvatar.DistributedAvatar.disable(self) + + self.battleAId = None + self.battleBId = None + self.battleA = None + self.battleB = None + self.cr.relatedObjectMgr.abortRequest(self.toonRequest) + self.toonRequest = None + self.cr.relatedObjectMgr.abortRequest(self.battleRequest) + self.battleRequest = None + + self.stopAnimate() + self.cleanupIntervals() + self.cleanupFlash() + self.disableLocalToonSimpleCollisions() + self.ignoreAll() + + def delete(self): + """ + This method is called when the DistributedObject is + permanently removed from the world and deleted from + the cache. + """ + try: + self.DistributedBossCog_deleted + except: + self.DistributedBossCog_deleted = 1 + self.ignoreAll() + DistributedAvatar.DistributedAvatar.delete(self) + BossCog.BossCog.delete(self) + return + + + # We need to force the BossCog version of these to be called, otherwise + # we get the generic Avatar version which is undefined + def setDNAString(self, dnaString): + BossCog.BossCog.setDNAString(self, dnaString) + + def getDNAString(self): + return self.dna.makeNetString() + + def setDNA(self, dna): + BossCog.BossCog.setDNA(self, dna) + + + def setToonIds(self, involvedToons, toonsA, toonsB): + self.involvedToons = involvedToons + self.toonsA = toonsA + self.toonsB = toonsB + + self.cr.relatedObjectMgr.abortRequest(self.toonRequest) + self.gotAllToons = 0 + self.toonRequest = self.cr.relatedObjectMgr.requestObjects( + self.involvedToons, allCallback = self.__gotAllToons, + eachCallback = self.gotToon) + + + + def getDialogueArray(self, *args): + # Force the right inheritance chain to be called + return BossCog.BossCog.getDialogueArray(self, *args) + + def storeInterval(self, interval, name): + if name in self.activeIntervals: + ival = self.activeIntervals[name] + # if the interval has a delayDelete, finish it now and destroy the delayDeletes + # otherwise, preserve the original behavior of letting the old interval continue + # to run if it's running + if (hasattr(ival, 'delayDelete') or hasattr(ival, 'delayDeletes')): + self.clearInterval(name, finish=1) + self.activeIntervals[name] = interval + + def cleanupIntervals(self): + for interval in self.activeIntervals.values(): + interval.finish() + DelayDelete.cleanupDelayDeletes(interval) + self.activeIntervals = {} + + def clearInterval(self, name, finish=1): + """ Clean up the specified Interval + """ + if (self.activeIntervals.has_key(name)): + ival = self.activeIntervals[name] + if finish: + ival.finish() + else: + ival.pause() + if self.activeIntervals.has_key(name): + DelayDelete.cleanupDelayDeletes(ival) + del self.activeIntervals[name] + else: + self.notify.debug('interval: %s already cleared' % name) + + def finishInterval(self, name): + """ Force the specified Interval to jump to the end + """ + if (self.activeIntervals.has_key(name)): + interval = self.activeIntervals[name] + interval.finish() + + def d_avatarEnter(self): + assert self.notify.debug("d_avatarEnter()") + self.sendUpdate('avatarEnter', []) + + def d_avatarExit(self): + assert self.notify.debug("d_avatarExit()") + self.sendUpdate('avatarExit', []) + + def avatarNearEnter(self, entry): + self.sendUpdate('avatarNearEnter', []) + + def avatarNearExit(self, entry): + self.sendUpdate('avatarNearExit', []) + + + def hasLocalToon(self): + # Returns true if localToon is involved in the final movies or + # battle. This will generally always be true in production, + # but during testing a toon might arrive in the middle of a + # battle-in-progress, and we go a little out of our way to be + # polite and not crash this player, even though he can't + # participate. + doId = localAvatar.doId + return (doId in self.toonsA) or (doId in self.toonsB) + + + def setBattleExperience(self, + id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, + origMerits0, merits0, parts0, + id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, + origMerits1, merits1, parts1, + id2, origExp2, earnedExp2, origQuests2,items2, missedItems2, + origMerits2, merits2, parts2, + id3, origExp3, earnedExp3, origQuests3,items3, missedItems3, + origMerits3, merits3, parts3, + id4, origExp4, earnedExp4, origQuests4, items4, missedItems4, + origMerits4, merits4, parts4, + id5, origExp5, earnedExp5, origQuests5,items5, missedItems5, + origMerits5, merits5, parts5, + id6, origExp6, earnedExp6, origQuests6, items6, missedItems6, + origMerits6, merits6, parts6, + id7, origExp7, earnedExp7, origQuests7, items7, missedItems7, + origMerits7, merits7, parts7, + deathList, uberList, helpfulToons): + + self.deathList = deathList + self.uberList = uberList + self.helpfulToons = helpfulToons + entries = ((id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, + origMerits0, merits0, parts0), + (id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, + origMerits1, merits1, parts1), + (id2, origExp2, earnedExp2, origQuests2, items2, missedItems2, + origMerits2, merits2, parts2), + (id3, origExp3, earnedExp3, origQuests3, items3, missedItems3, + origMerits3, merits3, parts3), + (id4, origExp4, earnedExp4, origQuests4, items4, missedItems4, + origMerits4, merits4, parts4), + (id5, origExp5, earnedExp5, origQuests5, items5, missedItems5, + origMerits5, merits5, parts5), + (id6, origExp6, earnedExp6, origQuests6, items6, missedItems6, + origMerits6, merits6, parts6), + (id7, origExp7, earnedExp7, origQuests7, items7, missedItems7, + origMerits7, merits7, parts7)) + + self.toonRewardDicts = BattleExperience.genRewardDicts(entries) + self.toonRewardIds = [id0,id1,id2,id3,id4,id5,id6,id7] + + + def setArenaSide(self, arenaSide): + self.arenaSide = arenaSide + + def setState(self, state): + self.request(state) + + def gotToon(self, toon): + # A new Toon has arrived. Put him in the right spot, if we + # know what that is yet. Normally, we will only see this + # message in the WaitForToons state, or in the Off state if they + # came in early (but someone might arrive to the battle very + # late and see everything already advanced to the next state). + stateName = self.state + assert self.notify.debug("gotToon(%s) in state %s" % (toon.doId, stateName)) + + def __gotAllToons(self, toons): + # We've seen all the Toons we're expecting. + assert self.notify.debug("gotAllToons()") + self.gotAllToons = 1 + messenger.send('gotAllToons') + + def setBattleIds(self, battleNumber, battleAId, battleBId): + assert self.notify.debug("setBattleIds(%s, %s)" % (battleAId, battleBId)) + self.battleNumber = battleNumber + self.battleAId = battleAId + self.battleBId = battleBId + + self.cr.relatedObjectMgr.abortRequest(self.battleRequest) + self.battleRequest = self.cr.relatedObjectMgr.requestObjects( + [self.battleAId, self.battleBId], + allCallback = self.__gotBattles) + + def __gotBattles(self, battles): + assert self.notify.debug("gotBattles(%s, %s)" % (repr(battles[0]), repr(battles[1]))) + self.battleRequest = None + + if self.battleA and self.battleA != battles[0]: + self.battleA.cleanupBattle() + + if self.battleB and self.battleB != battles[1]: + self.battleB.cleanupBattle() + + self.battleA = battles[0] + self.battleB = battles[1] + + def cleanupBattles(self): + # Call this to clean up the battle objects and make sure + # they're not hanging on to toons or camera modes or + # something. + assert self.notify.debug("cleanupBattles()") + + if self.battleA: + self.battleA.cleanupBattle() + if self.battleB: + self.battleB.cleanupBattle() + + def makeEndOfBattleMovie(self, hasLocalToon): + assert self.notify.debug("makeEndOfBattleMovie(%s)" % (hasLocalToon)) + + # Generate a little interval to be played at the end of a + # battle, to show progress or encouragement or something. + return Sequence() + + def controlToons(self): + # Turn off remote control of the toons. We'll move them + # around locally for now. + assert(self.notify.debug("controlToons")) + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.stopLookAround() + toon.stopSmooth() + + # Also put the local toon in movie mode so he won't try to + # walk around on his own. + if self.hasLocalToon(): + self.toMovieMode() + + def enableLocalToonSimpleCollisions(self): + # Enable a simple collision sphere around the local toon so we + # can lerp him around and not inadvertently push him through + # walls or floors. + if not self.toonSphere: + sphere = CollisionSphere(0, 0, 1, 1) + sphere.setRespectEffectiveNormal(0) + sphereNode = CollisionNode('SimpleCollisions') + sphereNode.setFromCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.FloorBitmask) + sphereNode.setIntoCollideMask(BitMask32.allOff()) + sphereNode.addSolid(sphere) + self.toonSphere = NodePath(sphereNode) + self.toonSphereHandler = CollisionHandlerPusher() + self.toonSphereHandler.addCollider(self.toonSphere, localAvatar) + self.toonSphere.reparentTo(localAvatar) + base.cTrav.addCollider(self.toonSphere, self.toonSphereHandler) + + def disableLocalToonSimpleCollisions(self): + if self.toonSphere: + base.cTrav.removeCollider(self.toonSphere) + self.toonSphere.detachNode() + + def toOuchMode(self): + # Move the localToon to 'ouch' mode: we've been hit by + # something and can't move while we recover. + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('ouch') + + def toCraneMode(self): + # Move the localToon to 'crane' mode: we're not walking the + # avatar around, but we're still controlling something + # in-game, e.g. to move the cranes in the CFO battle. + # Collisions are still active. + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('crane') + + def toMovieMode(self): + # Move the localToon to 'movie' mode: we're totally in control + # of the game logic, watching a cutscene. + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('movie') + + def toWalkMode(self): + # Move the localToon to 'walk' mode. + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('walk') + + def toFinalBattleMode(self): + # Move the localToon to 'finalBattle' mode. Similar to walk + # mode but without a teleport out button. + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('finalBattle') + + def releaseToons(self, finalBattle = 0): + # Allow the toons to control themselves. + assert(self.notify.debug("releaseToons %d" % finalBattle)) + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + if self.battleA and toon in self.battleA.toons: + # The battle owns this toon. + pass + elif self.battleB and toon in self.battleB.toons: + # The battle owns this toon. + pass + else: + toon.startLookAround() + toon.startSmooth() + toon.wrtReparentTo(render) + + if toon == localAvatar: + if finalBattle: + self.toFinalBattleMode() + else: + self.toWalkMode() + + def stickToonsToFloor(self): + # Put a CollisionRay to each toon to ensure that it is stuck + # to the floor during an animation (especially while moving + # the toons on a mopath). + + # This creates a number of CollisionHandlers which are + # registered with the CollisionTraverser; these will be + # disabled when unstickToons() is called. + + # Make sure we don't already have some sticky toons. + self.unstickToons() + + # First, create a CollisionRay in a node. This same node is + # instanced to each toon. + + rayNode = CollisionNode('stickToonsToFloor') + rayNode.addSolid(CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0)) + rayNode.setFromCollideMask(ToontownGlobals.FloorBitmask) + rayNode.setIntoCollideMask(BitMask32.allOff()) + ray = NodePath(rayNode) + + # Also create a lifter. This same handler can be shared by + # all toons. + lifter = CollisionHandlerFloor() + lifter.setOffset(ToontownGlobals.FloorOffset) + lifter.setReach(10.0) + + # Now, walk through the list of toons and activate each one with + # a special lifter. + for toonId in self.involvedToons: + toon = base.cr.doId2do.get(toonId) + if toon: + toonRay = ray.instanceTo(toon) + lifter.addCollider(toonRay, toon) + base.cTrav.addCollider(toonRay, lifter) + self.__toonsStuckToFloor.append(toonRay) + + def unstickToons(self): + # Undoes a previous call to stickToonsToFloor(): this + # removes all of the colliders from the CollisionTraverser and + # leaves the toons to find their own altitude. + for toonRay in self.__toonsStuckToFloor: + base.cTrav.removeCollider(toonRay) + toonRay.removeNode() + self.__toonsStuckToFloor = [] + + def stickBossToFloor(self): + # Put a trio of CollisionRays under the Boss Cog, one in + # front, one in the center, and one behind, so we can do the + # right thing as he rolls up and over ramps. + + # This creates a pair of CollisionHandlers which are + # registered with the CollisionTraverser; these will be + # disabled when unstickBoss() is called. + + self.unstickBoss() + + # First, create three CollisionRays, and put them together in + # a node. + + self.ray1 = CollisionRay(0.0, 10.0, 20.0, 0.0, 0.0, -1.0) + self.ray2 = CollisionRay(0.0, 0.0, 20.0, 0.0, 0.0, -1.0) + self.ray3 = CollisionRay(0.0, -10.0, 20.0, 0.0, 0.0, -1.0) + + rayNode = CollisionNode('stickBossToFloor') + rayNode.addSolid(self.ray1) + rayNode.addSolid(self.ray2) + rayNode.addSolid(self.ray3) + rayNode.setFromCollideMask(ToontownGlobals.FloorBitmask) + rayNode.setIntoCollideMask(BitMask32.allOff()) + self.rays = self.attachNewNode(rayNode) + + # Now the handler for these will be a CollisionHandlerQueue, + # which we query directly. (We can't use a + # CollisionHandlerFloor, which doesn't know about multiple + # rays.) + self.cqueue = CollisionHandlerQueue() + base.cTrav.addCollider(self.rays, self.cqueue) + + def rollBoss(self, t, fromPos, deltaPos): + # This method is called each frame during the rollToBattleTwo + # interval and bossDamage interval. It applies a lerp from + # fromPos to toPos, while sticking the boss to the floor, and + # rotatating him properly. + + # First, set the boss to the appropriate point. + self.setPos(fromPos + deltaPos * t) + + if not self.cqueue: + return + + # Find the first three entries that correspond to our three + # rays. + self.cqueue.sortEntries() + numEntries = self.cqueue.getNumEntries() + if numEntries != 0: + for i in range(self.cqueue.getNumEntries() - 1, -1, -1): + entry = self.cqueue.getEntry(i) + solid = entry.getFrom() + if solid == self.ray1: + self.e1 = entry + elif solid == self.ray2: + self.e2 = entry + elif solid == self.ray3: + self.e3 = entry + else: + self.notify.warning("Unexpected ray in __liftBoss") + return + + # Now be sure the queue is cleared for next time. + self.cqueue.clearEntries() + + if not (self.e1 and self.e2 and self.e3): + self.notify.debug("Some points missed in __liftBoss") + return + + # Now get the three points that each of these detected. + p1 = self.e1.getSurfacePoint(self) + p2 = self.e2.getSurfacePoint(self) + p3 = self.e3.getSurfacePoint(self) + + # Draw a line between p1 and p3 to determine the hypothetical + # center point if we're going up a ramp. + p2a = (p1 + p3) / 2 + + # Our actual height is based on the higher of p2 and p2a. + if (p2a[2] > p2[2]): + center = p2a + else: + center = p2 + self.setZ(self, center[2]) + + # Now, is the Boss level, or tilted? + + if p1[2] > p2[2] + 0.01 or p3[2] > p2[2] + 0.01: + # It's tilted by the full ratio if either end point is + # much above the center point. + mat = Mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + if abs(p3[2] - center[2]) < abs(p1[2] - center[2]): + lookAt(mat, Vec3(p1 - center), CSDefault) + else: + lookAt(mat, Vec3(center - p3), CSDefault) + self.rotateNode.setMat(mat) + + else: + # Otherwise, it's level. + self.rotateNode.clearTransform() + + + def unstickBoss(self): + # Undoes a previous call to stickBossToFloor(): this + # removes all of the colliders from the CollisionTraverser and + # leaves the boss to find its own altitude and orientation. + if self.rays: + base.cTrav.removeCollider(self.rays) + self.rays.removeNode() + + self.rays = None + self.ray1 = None + self.ray2 = None + self.ray3 = None + self.e1 = None + self.e2 = None + self.e3 = None + self.rotateNode.clearTransform() + + self.cqueue = None + + def rollBossToPoint(self, fromPos, fromHpr, toPos, toHpr, reverse): + vector = Vec3(toPos - fromPos) + distance = vector.length() + + if toHpr == None: + # Compute the destination hpr. + mat = Mat3(0, 0, 0, 0, 0, 0, 0, 0, 0) + headsUp(mat, vector, CSDefault) + scale = VBase3(0, 0, 0) + shear = VBase3(0, 0, 0) + toHpr = VBase3(0, 0, 0) + decomposeMatrix(mat, scale, shear, toHpr, CSDefault) + + if fromHpr: + # Fit the toHpr to the same semicircle as the supplied + # fromHpr. + newH = PythonUtil.fitDestAngle2Src(fromHpr[0], toHpr[0]) + toHpr = VBase3(newH, 0, 0) + + else: + # If no fromHpr is given, it's the same as toHpr. + fromHpr = toHpr + + turnTime = abs(toHpr[0] - fromHpr[0]) / ToontownGlobals.BossCogTurnSpeed + if toHpr[0] < fromHpr[0]: + leftRate = ToontownGlobals.BossCogTreadSpeed + else: + leftRate = -ToontownGlobals.BossCogTreadSpeed + if reverse: + rollTreadRate = -ToontownGlobals.BossCogTreadSpeed + else: + rollTreadRate = ToontownGlobals.BossCogTreadSpeed + + rollTime = distance / ToontownGlobals.BossCogRollSpeed + deltaPos = toPos - fromPos + track = Sequence(Func(self.setPos, fromPos), + Func(self.headsUp, toPos), + Parallel(self.hprInterval(turnTime, toHpr, fromHpr), + self.rollLeftTreads(turnTime, leftRate), + self.rollRightTreads(turnTime, -leftRate), + #SoundInterval(self.treadsSfx, duration = turnTime, loop = 1, volume = 0.2), + ), + Parallel(LerpFunctionInterval(self.rollBoss, + duration = rollTime, + extraArgs = [fromPos, deltaPos]), + self.rollLeftTreads(rollTime, rollTreadRate), + self.rollRightTreads(rollTime, rollTreadRate), + #SoundInterval(self.treadsSfx, duration = rollTime, loop = 1, volume = 0.2), + ), + ) + + # Return both the computed track and the destination hpr + # (which might be useful for the next sequence). + return track, toHpr + + + def setupElevator(self, elevatorModel): + self.elevatorModel = elevatorModel + + self.leftDoor = self.elevatorModel.find("**/left-door") + if self.leftDoor.isEmpty(): + self.leftDoor = self.elevatorModel.find("**/left_door") + self.rightDoor = self.elevatorModel.find("**/right-door") + if self.rightDoor.isEmpty(): + self.rightDoor = self.elevatorModel.find("**/right_door") + + self.openSfx = base.loadSfx("phase_9/audio/sfx/CHQ_FACT_door_open_sliding.mp3") + self.finalOpenSfx = base.loadSfx("phase_9/audio/sfx/CHQ_FACT_door_open_final.mp3") + self.closeSfx = base.loadSfx("phase_9/audio/sfx/CHQ_FACT_door_open_sliding.mp3") + self.finalCloseSfx = base.loadSfx("phase_9/audio/sfx/CHQ_FACT_door_open_final.mp3") + self.openDoors = ElevatorUtils.getOpenInterval(self, self.leftDoor, self.rightDoor, + self.openSfx, self.finalOpenSfx, + self.elevatorType) + self.closeDoors = ElevatorUtils.getCloseInterval(self, self.leftDoor, self.rightDoor, + self.closeSfx, self.finalCloseSfx, + self.elevatorType) + + # Guarantee the elevator doors are closed. + self.closeDoors.start() + self.closeDoors.finish() + + def putToonInCogSuit(self, toon): + if not toon.isDisguised: + deptIndex = SuitDNA.suitDepts.index(self.style.dept) + toon.setCogIndex(deptIndex) + + # This shouldn't be necessary. + toon.getGeomNode().hide() + + def placeToonInElevator(self, toon): + self.putToonInCogSuit(toon) + + toonIndex = self.involvedToons.index(toon.doId) + toon.reparentTo(self.elevatorModel) + toon.setPos(*ElevatorConstants.BigElevatorPoints[toonIndex]) + toon.setHpr(180, 0, 0) + toon.suit.loop('neutral') + + def toonNormalEyes(self, toons, bArrayOfObjs = False): + # Returns a track that sets the Toon's eyes to normal, after + # the loseCogSuits track made them sad. This will be played + # during a camera cut. + + if bArrayOfObjs: + toonObjs = toons + else: + toonObjs = [] + for toonId in toons: + toon = base.cr.doId2do.get(toonId) + if toon: + toonObjs.append(toon) + + seq = Sequence() + + for toon in toonObjs: + seq.append(Func(toon.normalEyes)) + seq.append(Func(toon.blinkEyes)) + + return seq + + def toonDied(self, avId): + assert self.notify.debug('toonDied(%s)' % (avId)) + + # The AI is telling us that a toon has met his untimely end. + # Maybe it's us! + if avId == localAvatar.doId: + self.localToonDied() + + + def localToonToSafeZone(self): + assert self.notify.debug('localToonToSafeZone()') + + target_sz = ZoneUtil.getSafeZoneId(localAvatar.defaultZone) + + place = self.cr.playGame.getPlace() + place.fsm.request('teleportOut', [{ + "loader" : ZoneUtil.getLoaderName(target_sz), + "where" : ZoneUtil.getWhereName(target_sz, 1), + 'how' : 'teleportIn', + 'hoodId' : target_sz, + 'zoneId' : target_sz, + 'shardId' : None, + 'avId' : -1, + 'battle' : 1, + }]) + + def localToonDied(self): + assert self.notify.debug('localToonToSafeZone()') + + target_sz = ZoneUtil.getSafeZoneId(localAvatar.defaultZone) + + place = self.cr.playGame.getPlace() + place.fsm.request('died', [{ + "loader" : ZoneUtil.getLoaderName(target_sz), + "where" : ZoneUtil.getWhereName(target_sz, 1), + 'how' : 'teleportIn', + 'hoodId' : target_sz, + 'zoneId' : target_sz, + 'shardId' : None, + 'avId' : -1, + 'battle' : 1, + }]) + + + def toonsToBattlePosition(self, toonIds, battleNode): + # Position the toons in the list to their appropriate position + # relative to the indicated battleNode. + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + self.notify.debug("toonsToBattlePosition: points = %s" % points[0][0]) + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + toon.reparentTo(render) + pos, h = points[i] + + self.notify.debug("toonsToBattlePosition: battleNode=%s %.2f %.2f %.2f %.2f %.2f %.2f" % (battleNode, pos[0], pos[1], pos[2], h, 0, 0)) + + self.notify.debug("old toon pos %s" % toon.getPos()) + self.notify.debug("pos=%.2f %.2f %.2f h=%.2f" % (pos[0], pos[1], pos[2], h)) + self.notify.debug("battleNode.pos = %s" % battleNode.getPos()) + self.notify.debug("battleNode.hpr = %s" % battleNode.getHpr()) + toon.setPosHpr(battleNode, pos[0], pos[1], pos[2], h, 0, 0) + self.notify.debug("new toon pos %s " % toon.getPos()) + + def __touchedBoss(self, entry): + assert self.notify.debug("__touchedBoss()") + # The localToon has come into contact with the boss's + # undercarriage, or one of his attack spheres. Zap! + + # Get the attack code from the thing we touched. + self.notify.debug('%s' % entry) + self.notify.debug('fromPos = %s' % entry.getFromNodePath().getPos(render)) + self.notify.debug('intoPos = %s' % entry.getIntoNodePath().getPos(render)) + attackCodeStr = entry.getIntoNodePath().getNetTag('attackCode') + if attackCodeStr == '': + self.notify.warning("Node %s has no attackCode tag." % (repr(entry.getIntoNodePath()))) + return + attackCode = int(attackCodeStr) + if attackCode == ToontownGlobals.BossCogLawyerAttack and \ + self.dna.dept != 'l': + self.notify.warning('got lawyer attack but not in CJ boss battle') + return + + self.zapLocalToon(attackCode) + + def zapLocalToon(self, attackCode, origin = None): + if self.localToonIsSafe or localAvatar.ghostMode or localAvatar.isStunned: + return + messenger.send('interrupt-pie') + + place = self.cr.playGame.getPlace() + currentState = None + if place: + currentState = place.fsm.getCurrentState().getName() + if currentState != 'walk' and currentState != 'finalBattle' and currentState != 'crane': + # Ignore this except when the Toon's in walk mode. + return + + toon = localAvatar + + fling = 1 + shake = 0 + if attackCode == ToontownGlobals.BossCogAreaAttack: + # For an area attack, we don't fling or move the toons, + # but we do shake the camera. + fling = 0 + shake = 1 + + if fling: + if origin == None: + origin = self + + # Face the toon towards the boss's center (so he can get + # knocked backwards), but keep the camera in the same place. + camera.wrtReparentTo(render) + toon.headsUp(origin) + camera.wrtReparentTo(toon) + + # Also tell the boss where we are relative to him, so he can + # decide whether to swat at us. + bossRelativePos = toon.getPos(self.getGeomNode()) + bp2d = Vec2(bossRelativePos[0], bossRelativePos[1]) + bp2d.normalize() + + pos = toon.getPos() + hpr = toon.getHpr() + timestamp = globalClockDelta.getFrameNetworkTime() + + self.sendUpdate('zapToon', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2], + bp2d[0], bp2d[1], + attackCode, timestamp]) + + self.doZapToon(toon, fling = fling, shake = shake) + + def showZapToon(self, toonId, x, y, z, h, p, r, attackCode, timestamp): + # This message comes from the AI to show the indicated + # distributed toon getting zapped. + if toonId == localAvatar.doId: + # Ignore messages about localToon; he already zapped himself. + return + + ts = globalClockDelta.localElapsedTime(timestamp) + pos = Point3(x, y, z) + hpr = VBase3(h, p, r) + fling = 1 + + toon = self.cr.doId2do.get(toonId) + if toon: + if attackCode == ToontownGlobals.BossCogAreaAttack: + # For an area attack, we don't fling or move the toons. + pos = None + hpr = None + fling = 0 + + else: + # Delay the zap by the same amount of time as the smoothing + # delay, so it will be in sync with the running animation. + ts -= toon.smoother.getDelay() + + self.doZapToon(toon, pos = pos, hpr = hpr, ts = ts, fling = fling) + + def doZapToon(self, toon, pos = None, hpr = None, ts = 0, + fling = 1, shake = 1): + # The indicated distributed toon has come into contact with + # something that hurts him. Play a little movie showing him + # getting zapped. + + zapName = toon.uniqueName('zap') + self.clearInterval(zapName) + + zapTrack = Sequence(name = zapName) + + if toon == localAvatar: + # In the case of localToon, set the state to ouch + # immediately, rather than waiting a frame or two for the + # interval to get there. + self.toOuchMode() + messenger.send('interrupt-pie') + + # But also set up an active collision sphere, similar to + # the one we have in walk mode, just so the local toon + # won't get pushed through a wall by our lerp, below. + self.enableLocalToonSimpleCollisions() + else: + zapTrack.append(Func(toon.stopSmooth)) + + def getSlideToPos(toon = toon): + return render.getRelativePoint(toon, Point3(0, -5, 0)) + + if pos != None and hpr != None: + zapTrack.append(Func(toon.setPosHpr, pos, hpr)), + + toonTrack = Parallel() + + if shake and toon == localAvatar: + toonTrack.append(Sequence(Func(camera.setZ, camera, 1), + Wait(0.15), + Func(camera.setZ, camera, -2), + Wait(0.15), + Func(camera.setZ, camera, 1), + )) + + if fling: + toonTrack += [ActorInterval(toon, 'slip-backward'), + toon.posInterval(0.5, getSlideToPos, fluid = 1)] + else: + toonTrack += [ActorInterval(toon, 'slip-forward')] + + zapTrack.append(toonTrack) + + if toon == localAvatar: + zapTrack.append(Func(self.disableLocalToonSimpleCollisions)) + currentState = self.state + if currentState == 'BattleThree': + zapTrack.append(Func(self.toFinalBattleMode)) + else: + if hasattr(self, 'chairs'): + #if we have chairs, in battle two of lawbot boss + zapTrack.append(Func(self.toFinalBattleMode)) + else: + zapTrack.append(Func(self.toWalkMode)) + else: + zapTrack.append(Func(toon.startSmooth)) + + if (ts > 0): + # We're already late. + startTime = ts + else: + # We need to wait a bit. + zapTrack = Sequence(Wait(-ts), zapTrack) + startTime = 0 + + zapTrack.append(Func(self.clearInterval, zapName)) + + zapTrack.delayDelete = DelayDelete.DelayDelete(toon, 'BossCog.doZapToon') + zapTrack.start(startTime) + self.storeInterval(zapTrack, zapName) + + def setAttackCode(self, attackCode, avId = 0): + # This message is sent from the AI to tell the Boss Cog to + # animate some attack. + assert self.notify.debug("setAttackCode(%s, %s) time=%f" % + (attackCode, avId, globalClock.getFrameTime())) + self.attackCode = attackCode + self.attackAvId = avId + + if attackCode == ToontownGlobals.BossCogDizzy: + self.setDizzy(1) + self.cleanupAttacks() + self.doAnimate(None, raised = 0, happy = 1) + + elif attackCode == ToontownGlobals.BossCogDizzyNow: + self.setDizzy(1) + self.cleanupAttacks() + self.doAnimate('hit', happy = 1, now = 1) + + elif attackCode == ToontownGlobals.BossCogSwatLeft: + self.setDizzy(0) + self.doAnimate('ltSwing', now = 1) + + elif attackCode == ToontownGlobals.BossCogSwatRight: + self.setDizzy(0) + self.doAnimate('rtSwing', now = 1) + + elif attackCode == ToontownGlobals.BossCogAreaAttack: + self.setDizzy(0) + self.doAnimate('areaAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogFrontAttack: + self.setDizzy(0) + self.doAnimate('frontAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogRecoverDizzyAttack: + self.setDizzy(0) + self.doAnimate('frontAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogDirectedAttack or \ + attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + # Rotate to face the toon and spit out gears. + self.setDizzy(0) + self.doDirectedAttack(avId, attackCode) + + elif attackCode == ToontownGlobals.BossCogNoAttack: + # Just stand up. + self.setDizzy(0) + self.doAnimate(None, raised = 1) + + def cleanupAttacks(self): + # Stops any attack currently running. + pass + + def cleanupFlash(self): + if self.flashInterval: + self.flashInterval.finish() + self.flashInterval = None + + def flashRed(self): + # Flash the boss cog red to emphasize that he's been hit. + self.cleanupFlash() + + self.setColorScale(1, 1, 1, 1) + i = Sequence(self.colorScaleInterval(0.1, colorScale = VBase4(1, 0, 0, 1)), + self.colorScaleInterval(0.3, colorScale = VBase4(1, 1, 1, 1))) + self.flashInterval = i + i.start() + + def flashGreen(self): + # Flash the boss cog red to emphasize that he's been healed. (Lawyers put evidence on the prosecution pan) + self.cleanupFlash() + + if not self.isEmpty(): + self.setColorScale(1, 1, 1, 1) + i = Sequence(self.colorScaleInterval(0.1, colorScale = VBase4(0, 1, 0, 1)), + self.colorScaleInterval(0.3, colorScale = VBase4(1, 1, 1, 1))) + self.flashInterval = i + i.start() + + + def getGearFrisbee(self): + return loader.loadModel('phase_9/models/char/gearProp') + + def backupToonsToBattlePosition(self, toonIds, battleNode): + # Returns a movie showing the toons backing up from promotion + # position to battle position. + self.notify.debug("backupToonsToBattlePosition:") + + ival = Parallel() + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + pos, h = points[i] + pos = render.getRelativePoint(battleNode, pos) + ival.append(Sequence( + Func(toon.setPlayRate, -0.8, 'walk'), + Func(toon.loop, 'walk'), + toon.posInterval(3, pos), + Func(toon.setPlayRate, 1, 'walk'), + Func(toon.loop,'neutral'), + )) + + return ival + + def loseCogSuits(self, toons, battleNode, camLoc, arrayOfObjs = False): + # Returns a movie showing the line of toons losing their Cog suits. + seq = Sequence() + + if not toons: + # If there are no toons in the list, never mind. + return seq + + self.notify.debug("battleNode=%s camLoc=%s" % (battleNode, camLoc)) + seq.append(Func(camera.setPosHpr, battleNode, *camLoc)) + + suitsOff = Parallel() + + #build array of toon objects + if arrayOfObjs: + toonArray = toons + else: + toonArray = [] + for toonId in toons: + toon = base.cr.doId2do.get(toonId) + if toon: + toonArray.append(toon) + + for toon in toonArray: + dustCloud = DustCloud.DustCloud() + dustCloud.setPos(0, 2, 3) + dustCloud.setScale(0.5) + + # In this scene we happen to have four adjacent dust + # clouds that overlap each other. To prevent them + # from clipping each other, we turn off depth write, + # and then force the clouds to be drawn last in the + # scene by putting them in the 'fixed' bin. We can do + # this because the camera is locked down and we know + # there is nothing between the dust clouds and the + # camera. + dustCloud.setDepthWrite(0) + dustCloud.setBin('fixed', 0) + + dustCloud.createTrack() + suitsOff.append(Sequence( + Func(dustCloud.reparentTo, toon), + Parallel(dustCloud.track, + Sequence(Wait(0.3), + Func(toon.takeOffSuit), + Func(toon.sadEyes), + Func(toon.blinkEyes), + Func(toon.play, 'slip-backward'), + Wait(0.7), + )), + Func(dustCloud.detachNode), + Func(dustCloud.destroy)), + ) + + seq.append(suitsOff) + return seq + + def doDirectedAttack(self, avId, attackCode): + toon = base.cr.doId2do.get(avId) + if toon: + gearRoot = self.rotateNode.attachNewNode('gearRoot') + gearRoot.setZ(10) + gearRoot.setTag('attackCode', str(attackCode)) + gearModel = self.getGearFrisbee() + gearModel.setScale(0.2) + + # First, get just the H value towards the toon. + gearRoot.headsUp(toon) + toToonH = PythonUtil.fitDestAngle2Src(0, gearRoot.getH() + 180) + + # Now pitch towards the toon so we can throw gears at him. + gearRoot.lookAt(toon) + + neutral = 'Fb_neutral' + if not self.twoFaced: + neutral = 'Ff_neutral' + + gearTrack = Parallel() + for i in range(4): + node = gearRoot.attachNewNode(str(i)) + node.hide() + node.setPos(0, 5.85, 4.0) + gear = gearModel.instanceTo(node) + x = random.uniform(-5, 5) + z = random.uniform(-3, 3) + h = random.uniform(-720, 720) + gearTrack.append(Sequence( + Wait(i * 0.15), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, 50, z), fluid = 1), + node.hprInterval(1, VBase3(h, 0, 0), fluid = 1)), + Func(node.detachNode))) + + # A 1-second animation to play while rotating. It might + # be the neutral cycle, or the climb-up cycle. + if not self.raised: + neutral1Anim = self.getAnim('down2Up') + self.raised = 1 + else: + neutral1Anim = ActorInterval(self, neutral, startFrame = 48) + + throwAnim = self.getAnim('throw') + neutral2Anim = ActorInterval(self, neutral) + + extraAnim = Sequence() + if attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + # If it's the "slow" attack, pause for a bit longer + # here to give the player more warning. + extraAnim = ActorInterval(self, neutral) + + seq = Sequence( + ParallelEndTogether(self.pelvis.hprInterval(1, VBase3(toToonH, 0, 0)), + neutral1Anim), + extraAnim, + Parallel(Sequence(Wait(0.19), + gearTrack, + Func(gearRoot.detachNode), + self.pelvis.hprInterval(0.2, VBase3(0, 0, 0))), + Sequence(throwAnim, neutral2Anim))) + + self.doAnimate(seq, now = 1, raised = 1) + + def announceAreaAttack(self): + # The boss cog has finished his area attack. Any Toons still + # on the ground at this moment report a hit. + + # This assumes we have a PhysicsWalker in use. Is there a way + # to get this information without so many dots? + if not getattr(localAvatar.controlManager.currentControls, "isAirborne", 0): + self.zapLocalToon(ToontownGlobals.BossCogAreaAttack) + + + ##### Environment ##### + + def loadEnvironment(self): + # Elevator: play the introduction-to-danger sting + self.elevatorMusic = base.loadMusic( + 'phase_7/audio/bgm/tt_elevator.mid') + self.stingMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + #'phase_9/audio/bgm/encntr_sting_announce.mid') + # Battle one: play the standard street battle music + self.battleOneMusic = base.loadMusic( + 'phase_3.5/audio/bgm/encntr_general_bg.mid') + # Battle three: play the final boss battle music + self.battleThreeMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + # 'phase_9/audio/bgm/encntr_suit_winning.mid') + # Reward: play the reward music + self.epilogueMusic = base.loadMusic( + 'phase_9/audio/bgm/encntr_hall_of_fame.mid') + #'phase_9/audio/bgm/CogHQ_finale.mid') + + + def unloadEnvironment(self): + pass + + ##### Off state ##### + + def enterOff(self): + assert self.notify.debug('enterOff()') + self.cleanupIntervals() + self.hide() + self.clearChat() + self.toWalkMode() + + def exitOff(self): + self.show() + + + ##### WaitForToons state ##### + + def enterWaitForToons(self): + assert self.notify.debug('enterWaitForToons()') + + # Here we are waiting in the dark for the toons to arrive in + # the elevator. + self.cleanupIntervals() + self.hide() + + if self.gotAllToons: + self.__doneWaitForToons() + else: + self.accept('gotAllToons', self.__doneWaitForToons) + + # Put up a black screen while we're waiting. Use our own + # Transitions object instead of relying on the one in + # ShowBase, which other places seem to rudely reset. + self.transitions = Transitions.Transitions(loader) + self.transitions.IrisModelName = "phase_3/models/misc/iris" + self.transitions.FadeModelName = "phase_3/models/misc/fade" + self.transitions.fadeScreen(alpha = 1) + + NametagGlobals.setMasterArrowsOn(0) + + def __doneWaitForToons(self): + # All the expected toons have arrived; tell the AI so we can + # move on. + self.doneBarrier('WaitForToons') + + def exitWaitForToons(self): + self.show() + + self.transitions.noFade() + del self.transitions + NametagGlobals.setMasterArrowsOn(1) + + ##### Elevator state ##### + + def enterElevator(self): + assert self.notify.debug('enterElevator()') + + # Place all the toons in the elevator. + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.stopLookAround() + toon.stopSmooth() + self.placeToonInElevator(toon) + + self.toMovieMode() + + # Position the camera behind the toons + camera.reparentTo(self.elevatorModel) + camera.setPosHpr(0, 30, 8, 180, 0, 0) + + base.playMusic(self.elevatorMusic, looping=1, volume=1.0) + + ival = Sequence( + ElevatorUtils.getRideElevatorInterval(self.elevatorType), + ElevatorUtils.getRideElevatorInterval(self.elevatorType), + self.openDoors, + Func(camera.wrtReparentTo, render), + Func(self.__doneElevator), + ) + + intervalName = "ElevatorMovie" + ival.start() + self.storeInterval(ival, intervalName) + + def __doneElevator(self): + self.doneBarrier('Elevator') + + def exitElevator(self): + intervalName = "ElevatorMovie" + self.clearInterval(intervalName) + self.elevatorMusic.stop() + + # Don't release toons yet, since we're about to transfer to + # Introduction state, and releasing the toon will cause a + # distributed anim state update that causes a race condition + # in the movie. + #self.releaseToons() + + # Close the elevator doors, even though we'll open them again + # immediately in the next state. + ElevatorUtils.closeDoors(self.leftDoor, + self.rightDoor, + self.elevatorType) + + ##### Introduction state ##### + + def enterIntroduction(self): + assert self.notify.debug('enterIntroduction()') + + self.controlToons() + + # The elevator doors are open now. + ElevatorUtils.openDoors(self.leftDoor, + self.rightDoor, + self.elevatorType) + + # Turn off the onscreen arrows while all this is going + # on--they're distracting. + NametagGlobals.setMasterArrowsOn(0) + + intervalName = "IntroductionMovie" + delayDeletes = [] + + seq = Sequence(self.makeIntroductionMovie(delayDeletes), + Func(self.__beginBattleOne), + name = intervalName) + seq.delayDeletes = delayDeletes + seq.start() + self.storeInterval(seq, intervalName) + + def __beginBattleOne(self): + intervalName = "IntroductionMovie" + self.clearInterval(intervalName) + + self.doneBarrier('Introduction') + + def exitIntroduction(self): + self.notify.debug("DistributedBossCog.exitIntroduction:") + intervalName = "IntroductionMovie" + self.clearInterval(intervalName) + self.unstickToons() + self.releaseToons() + + NametagGlobals.setMasterArrowsOn(1) + + # Make sure the elevator doors are closed. + ElevatorUtils.closeDoors(self.leftDoor, + self.rightDoor, + self.elevatorType) + + ##### BattleOne state ##### + + def enterBattleOne(self): + assert self.notify.debug('enterBattleOne()') + self.cleanupIntervals() + + # Get the credit multiplier + mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(1) + localAvatar.inventory.setBattleCreditMultiplier(mult) + + # The toons should be in their battle position. + self.toonsToBattlePosition(self.toonsA, self.battleANode) + self.toonsToBattlePosition(self.toonsB, self.battleBNode) + + # Now the battle holds the toons. + self.releaseToons() + + base.playMusic(self.battleOneMusic, looping=1, volume=0.9) + + + def exitBattleOne(self): + self.cleanupBattles() + self.battleOneMusic.stop() + + # No more credit multiplier + localAvatar.inventory.setBattleCreditMultiplier(1) + + ##### BattleThree state ##### + + # Added to enable all boss battles to support clicking on toon nametags. + # This is a bit of a hack. I suppose we should inherit from FriendsListManager but + # we really don't want the whole friends list interface... + + def enterBattleThree(self): + assert self.notify.debug('enterBattleThree()') + self.cleanupIntervals() + self.releaseToons(finalBattle = 1) + self.accept('clickedNametag', self.__clickedNameTag) + self.accept('friendAvatar', self.__handleFriendAvatar) + self.accept('avatarDetails', self.__handleAvatarDetails) + NametagGlobals.setMasterArrowsOn(0) + NametagGlobals.setMasterNametagsActive(1) + + + def exitBattleThree(self): + assert self.notify.debug('exitBattleThree()') + self.ignore('clickedNameTag') + self.ignore('friendAvatar') + self.ignore('avatarDetails') + self.cleanupIntervals() + + def __clickedNameTag(self, avatar): + self.notify.debug('__clickedNameTag') + if not (self.state == 'BattleThree' or self.state == 'BattleFour'): + return + if not self.allowClickedNameTag: + return + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + FriendsListManager.FriendsListManager._FriendsListManager__handleClickedNametag(place, avatar) + + def __handleFriendAvatar(self, avId, avName, avDisableName): + self.notify.debug('__handleFriendAvatar') + if not (self.state == 'BattleThree' or self.state == 'BattleFour'): + return + if not self.allowClickedNameTag: + return + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + FriendsListManager.FriendsListManager._FriendsListManager__handleFriendAvatar(place, avId, avName, avDisableName) + + def __handleAvatarDetails(self, avId, avName, playerId = None): + self.notify.debug('__handleAvatarDetails') + if not (self.state == 'BattleThree' or self.state == 'BattleFour'): + return + if not self.allowClickedNameTag: + return + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + FriendsListManager.FriendsListManager._FriendsListManager__handleAvatarDetails(place, avId, avName, playerId) + + + ##### BattleFour state ##### + + # Added to enable all boss battles to support clicking on toon nametags. + # This is a bit of a hack. I suppose we should inherit from FriendsListManager but + # we really don't want the whole friends list interface... + + def enterBattleFour(self): + assert self.notify.debug('enterBattleFour()') + self.cleanupIntervals() + self.releaseToons(finalBattle = 1) + self.accept('clickedNametag', self.__clickedNameTag) + self.accept('friendAvatar', self.__handleFriendAvatar) + self.accept('avatarDetails', self.__handleAvatarDetails) + NametagGlobals.setMasterArrowsOn(0) + NametagGlobals.setMasterNametagsActive(1) + + + def exitBattleFour(self): + assert self.notify.debug('exitBattleFour()') + self.ignore('clickedNameTag') + self.ignore('friendAvatar') + self.ignore('avatarDetails') + self.cleanupIntervals() + + + ##### Frolic state ##### + + # This state is probably only useful for debugging. The toons are + # all free to run around the world. + + def enterFrolic(self): + assert self.notify.debug('enterFrolic()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + + # Boss Cog is hanging out somewhere. + self.reparentTo(render) + self.stopAnimate() + self.pose('Ff_neutral', 0) + + # No one owns the toons. + self.releaseToons() + + def exitFrolic(self): + pass + + #### Util methods #### + + def setToonsToNeutral(self, toonIds): + """ + Make sure all the toons are in neutral state + """ + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + if toon.isDisguised: + toon.suit.loop('neutral') + toon.loop('neutral') + + + def wearCogSuits(self, toons, battleNode, camLoc, arrayOfObjs = False, waiter = False): + # Returns a movie showing the line of toons losing their Cog suits. + seq = Sequence() + + if not toons: + # If there are no toons in the list, never mind. + return seq + + self.notify.debug("battleNode=%s camLoc=%s" % (battleNode, camLoc)) + if camLoc: + seq.append(Func(camera.setPosHpr, battleNode, *camLoc)) + + suitsOff = Parallel() + + #build array of toon objects + if arrayOfObjs: + toonArray = toons + else: + toonArray = [] + for toonId in toons: + toon = base.cr.doId2do.get(toonId) + if toon: + toonArray.append(toon) + + for toon in toonArray: + dustCloud = DustCloud.DustCloud() + dustCloud.setPos(0, 2, 3) + dustCloud.setScale(0.5) + + # In this scene we happen to have four adjacent dust + # clouds that overlap each other. To prevent them + # from clipping each other, we turn off depth write, + # and then force the clouds to be drawn last in the + # scene by putting them in the 'fixed' bin. We can do + # this because the camera is locked down and we know + # there is nothing between the dust clouds and the + # camera. + dustCloud.setDepthWrite(0) + dustCloud.setBin('fixed', 0) + + dustCloud.createTrack() + makeWaiter = Sequence() + if waiter: + makeWaiter = Func(toon.makeWaiter) + suitsOff.append(Sequence( + Func(dustCloud.reparentTo, toon), + Parallel(dustCloud.track, + Sequence(Wait(0.3), + Func(self.putToonInCogSuit, toon), + makeWaiter, + Wait(0.7), + )), + Func(dustCloud.detachNode))) + + seq.append(suitsOff) + return seq diff --git a/toontown/src/suit/DistributedBossCogAI.py b/toontown/src/suit/DistributedBossCogAI.py new file mode 100644 index 0000000..4a38741 --- /dev/null +++ b/toontown/src/suit/DistributedBossCogAI.py @@ -0,0 +1,933 @@ +from direct.directnotify import DirectNotifyGlobal +from otp.avatar import DistributedAvatarAI +from toontown.battle import BattleExperienceAI +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.toon import InventoryBase +from toontown.battle import DistributedBattleFinalAI +from toontown.building import SuitPlannerInteriorAI +from toontown.battle import BattleBase +from pandac.PandaModules import * +import SuitDNA +import random + +AllBossCogs = [] + +class DistributedBossCogAI(DistributedAvatarAI.DistributedAvatarAI): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBossCogAI') + + def __init__(self, air, dept): + DistributedAvatarAI.DistributedAvatarAI.__init__(self, air) + + self.dept = dept + self.dna = SuitDNA.SuitDNA() + self.dna.newBossCog(self.dept) + self.deptIndex = SuitDNA.suitDepts.index(self.dept) + + self.resetBattleCounters() + + # These are the toons who will be participating in our + # battles. + self.looseToons = [] + self.involvedToons = [] + self.toonsA = [] + self.toonsB = [] + self.nearToons = [] + + # These are the suits. + self.suitsA = [] + self.activeSuitsA = [] + self.suitsB = [] + self.activeSuitsB = [] + self.reserveSuits = [] + + self.barrier = None + + # This is the list of state transitions that will be logged in + # the event manager. + self.keyStates = [ + 'BattleOne', + 'BattleTwo', + 'BattleThree', + 'Victory', + ] + + # Accumulated hits on the boss during the climactic final + # battle (battle three) + self.bossDamage = 0 + self.battleThreeStart = 0 + self.battleThreeDuration = 1800 # Expected battle 3 time in seconds + + # What attack code the boss has currently selected. + self.attackCode = None + self.attackAvId = 0 + + # The number of times we are hit during one dizzy spell. + self.hitCount = 0 + + AllBossCogs.append(self) + + def delete(self): + self.ignoreAll() + if self in AllBossCogs: + i = AllBossCogs.index(self) + del AllBossCogs[i] + + return DistributedAvatarAI.DistributedAvatarAI.delete(self) + + def getDNAString(self): + return self.dna.makeNetString() + + + + # avatarEnter/avatarExit is used for the ~bossBattle magic word to + # stage toons in the area. It probably won't be needed once we + # integrate everything fully. + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.avatarEnter(%s)' % (self.doId, avId)) + + self.addToon(avId) + + def avatarExit(self): + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.avatarExit(%s)' % (self.doId, avId)) + self.removeToon(avId) + + # avatarNearEnter/avatarNearExit tell us when a client comes + # within directed attack range. + def avatarNearEnter(self): + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.avatarNearEnter(%s)' % (self.doId, avId)) + + if avId not in self.nearToons: + self.nearToons.append(avId) + + def avatarNearExit(self): + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.avatarNearExit(%s)' % (self.doId, avId)) + + try: + self.nearToons.remove(avId) + except: + pass + + def __handleUnexpectedExit(self, avId): + assert self.notify.debug('%s.handleUnexpectedExit(%s)' % (self.doId, avId)) + self.removeToon(avId) + + def addToon(self, avId): + assert self.notify.debug('%s.addToon(%s)' % (self.doId, avId)) + if avId not in self.looseToons and avId not in self.involvedToons: + self.looseToons.append(avId) + + event = self.air.getAvatarExitEvent(avId) + self.acceptOnce(event, self.__handleUnexpectedExit, extraArgs=[avId]) + + def removeToon(self, avId): + assert self.notify.debug('%s.removeToon(%s)' % (self.doId, avId)) + resendIds = 0 + try: + self.looseToons.remove(avId) + except: + pass + try: + self.involvedToons.remove(avId) + resendIds = 1 + except: + pass + try: + self.toonsA.remove(avId) + except: + pass + try: + self.toonsB.remove(avId) + except: + pass + try: + self.nearToons.remove(avId) + except: + pass + + event = self.air.getAvatarExitEvent(avId) + self.ignore(event) + + assert self.notify.debug('%s. looseToons = %s, involvedToons = %s, toonsA = %s, toonsB = %s' % (self.doId, self.looseToons, self.involvedToons, self.toonsA, self.toonsB)) + + if not self.hasToons(): + # There are no more toons, so we should send the cleanup + # message. But wait a few seconds first so the last Toon + # won't see the universe vanish before he finishes his + # teleport-out animation. + + taskMgr.doMethodLater(10, self.__bossDone, + self.uniqueName('BossDone')) + + def __bossDone(self, task): + self.b_setState('Off') + messenger.send(self.uniqueName('BossDone')) + self.ignoreAll() + + def hasToons(self): + return self.looseToons or self.involvedToons + + def hasToonsAlive(self): + # see if any toons still alive + alive = 0 + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + hp = toon.getHp() + if hp > 0: + alive = 1 + return alive + + def sendBattleIds(self): + self.sendUpdate('setBattleIds', [self.battleNumber, self.battleAId, self.battleBId]) + + def sendToonIds(self): + # We need to send the involvedToons list, in addition to the + # individual toonsA and toonsB lists, just so we don't lose + # the order of involvedToons (which controls how they are + # lined up in the elevator). + self.sendUpdate('setToonIds', [self.involvedToons, self.toonsA, self.toonsB]) + + def damageToon(self, toon, deduction): + toon.takeDamage(deduction) + assert self.notify.debug('%s. toon %s hit for %s to %s/%s' % (self.doId, toon.doId, deduction, toon.getHp(), toon.getMaxHp())) + if toon.getHp() <= 0: + assert self.notify.debug('%s. toon died: %s' % (self.doId, toon.doId)) + self.sendUpdate('toonDied', [toon.doId]) + + # Clear out the Toon's inventory. + empty = InventoryBase.InventoryBase(toon) + toon.b_setInventory(empty.makeNetString()) + + # We'll go ahead and remove the toon now, rather than + # waiting for him to tell us when he's gone. That will + # ensure he's not involved in any more activity even if + # he's got a slow client (or a rogue client) that doesn't + # tell us he's gone right away. + self.removeToon(toon.doId) + + def healToon(self, toon, increment): + toon.toonUp(increment) + assert self.notify.debug('%s. toon %s healed to %s/%s' % (self.doId, toon.doId, toon.getHp(), toon.getMaxHp())) + + # setBattleExperience + + def d_setBattleExperience(self): + assert self.notify.debug('%s.d_setBattleExperience()' % (self.doId)) + self.sendUpdate('setBattleExperience', self.getBattleExperience()) + + def getBattleExperience(self): + result = BattleExperienceAI.getBattleExperience( + 8, self.involvedToons, self.toonExp, + self.toonSkillPtsGained, self.toonOrigQuests, self.toonItems, + self.toonOrigMerits, self.toonMerits, + self.toonParts, self.suitsKilled, self.helpfulToons) + return result + + + def b_setArenaSide(self, arenaSide): + self.setArenaSide(arenaSide) + self.d_setArenaSide(arenaSide) + + def setArenaSide(self, arenaSide): + self.arenaSide = arenaSide + + def d_setArenaSide(self, arenaSide): + self.sendUpdate('setArenaSide', [arenaSide]) + + # setState() + + def b_setState(self, state): + # It is important to set the local state first, so that + # objects created here will be available when the client sets + # its state. + self.setState(state) + self.d_setState(state) + + def d_setState(self, state): + self.sendUpdate('setState', [state]) + + def setState(self, state): + self.demand(state) + + if self.air: + if state in self.keyStates: + self.air.writeServerEvent("bossBattle", self.doId, "%s|%s|%s|%s" % + (self.dept, state, self.involvedToons, + self.formatReward())) + + + def getState(self): + return self.state + + + def formatReward(self): + # Returns the reward indication to write to the event log. + return 'unspecified' + + + ##### Off state ##### + + def enterOff(self): + assert self.notify.debug('enterOff()') + self.resetBattles() + self.resetToons() + self.resetBattleCounters() + + def exitOff(self): + pass + + ##### WaitForToons state ##### + + def enterWaitForToons(self): + assert self.notify.debug('%s.enterWaitForToons()' % (self.doId)) + + # The clients are waiting for all the toons to transition to + # the same zoneId. They'll report back when that happens. If + # they don't report back, move on anyway. + + self.acceptNewToons() + + self.barrier = self.beginBarrier( + "WaitForToons", self.involvedToons, 5, + self.__doneWaitForToons) + + def __doneWaitForToons(self, toons): + assert self.notify.debug('%s.__doneWaitForToons()' % (self.doId)) + self.b_setState("Elevator") + + def exitWaitForToons(self): + self.ignoreBarrier(self.barrier) + + ##### Elevator state ##### + + def enterElevator(self): + assert self.notify.debug('%s.enterElevator()' % (self.doId)) + + if self.notify.getDebug(): + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + self.notify.debug('%s. involved toon %s, %s/%s' % (self.doId, toonId, toon.getHp(), toon.getMaxHp())) + + + self.resetBattles() + + # The clients play a movie showing the elevator ride and the + # doors opening at the top. + self.barrier = self.beginBarrier( + "Elevator", self.involvedToons, 30, + self.__doneElevator) + + def __doneElevator(self, avIds): + assert self.notify.debug('%s.__doneElevator()' % (self.doId)) + self.b_setState("Introduction") + + def exitElevator(self): + self.ignoreBarrier(self.barrier) + + ##### Introduction state ##### + + def enterIntroduction(self): + assert self.notify.debug('%s.enterIntroduction()' % (self.doId)) + + self.resetBattles() + + # Also get the battle objects ready. + self.arenaSide = None + self.makeBattleOneBattles() + + # The clients will play a cutscene. When they are done + # watching the movie, we continue. + + self.barrier = self.beginBarrier( + "Introduction", self.involvedToons, 45, + self.doneIntroduction) + + def doneIntroduction(self, avIds): + self.b_setState("BattleOne") + + def exitIntroduction(self): + self.ignoreBarrier(self.barrier) + + # we no longer take away cog parts for fighting the VP. + # Hopefully this will reduce the elevator griefing, since it's + # not the end of the world to lose a VP battle. + + # Make sure the toons all have their distributed cog suit + # turned off (since they have turned off their local suit by + # now) + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.b_setCogIndex(-1) + #toon.loseCogParts(self.deptIndex) + + ##### BattleOne state ##### + + def enterBattleOne(self): + assert self.notify.debug('%s.enterBattleOne()' % (self.doId)) + + # The boss cog unleashes the first round of Cogs from his + # belly to engage the toons in battle. + + # Begin the battles. + if self.battleA: + self.battleA.startBattle(self.toonsA, self.suitsA) + if self.battleB: + self.battleB.startBattle(self.toonsB, self.suitsB) + + def exitBattleOne(self): + self.resetBattles() + + + ##### Reward state ##### + + def enterReward(self): + assert self.notify.debug('%s.enterReward()' % (self.doId)) + self.resetBattles() + + self.barrier = self.beginBarrier( + "Reward", self.involvedToons, BattleBase.BUILDING_REWARD_TIMEOUT, + self.__doneReward) + + def __doneReward(self, avIds): + self.b_setState("Epilogue") + + def exitReward(self): + pass + + + ##### Epilogue state ##### + + def enterEpilogue(self): + assert self.notify.debug('%s.enterEpilogue()' % (self.doId)) + + def exitEpilogue(self): + pass + + + ##### Frolic state ##### + + def enterFrolic(self): + assert self.notify.debug('%s.enterFrolic()' % (self.doId)) + self.resetBattles() + + def exitFrolic(self): + pass + + + def resetBattleCounters(self): + # Resets all the statistics about who's won what battles. + # This should normally only be done at startup. + + self.battleNumber = 0 + self.battleA = None + self.battleAId = 0 + self.battleB = None + self.battleBId = 0 + self.arenaSide = None + self.toonSkillPtsGained = {} + self.toonExp = {} + self.toonOrigQuests = {} + self.toonItems = {} + self.toonOrigMerits = {} + self.toonMerits = {} + self.toonParts = {} + self.suitsKilled = [] + self.helpfulToons = [] + + def resetBattles(self): + # Interrupts any currently-running battles and associated + # suits. + sendReset = 0 + if self.battleA: + self.battleA.requestDelete() + self.battleA = None + self.battleAId = 0 + sendReset = 1 + if self.battleB: + self.battleB.requestDelete() + self.battleB = None + self.battleBId = 0 + sendReset = 1 + for suit in self.suitsA + self.suitsB: + suit.requestDelete() + for suit, joinChance in self.reserveSuits: + suit.requestDelete() + self.suitsA = [] + self.activeSuitsA = [] + self.suitsB = [] + self.activeSuitsB = [] + self.reserveSuits = [] + self.battleNumber = 0 + + if sendReset: + self.sendBattleIds() + + def resetToons(self): + # Remove all the toons from the A/B division and make them all + # loose toons again. This probably won't be used once we're + # done with the magic word interface. + if self.toonsA or self.toonsB: + self.looseToons = self.looseToons + self.involvedToons + self.involvedToons = [] + self.toonsA = [] + self.toonsB = [] + self.sendToonIds() + + def divideToons(self): + # Divide the toons randomly into toonsA and toonsB for facing + # off with the boss cog. + + toons = self.involvedToons[:] + random.shuffle(toons) + + numToons = min(len(toons), 8) + + # The odd toon ends up randomly on one side or the other, + # unless there are fewer than four toons (in which case the + # odd toon always ends up on side A, to give the boss someone + # to address in the movies). + if (numToons < 4): + numToonsB = numToons / 2 + else: + numToonsB = (numToons + random.choice([0, 1])) / 2 + + self.toonsA = toons[numToonsB:numToons] + self.toonsB = toons[:numToonsB] + self.looseToons += toons[numToons:] + self.sendToonIds() + + def acceptNewToons(self): + # Only the non-ghosts get accepted into the battle. + sourceToons = self.looseToons + self.looseToons = [] + for toonId in sourceToons: + toon = self.air.doId2do.get(toonId) + if toon and not toon.ghostMode: + self.involvedToons.append(toonId) + else: + self.looseToons.append(toonId) + + # Fill in the Toon's original experience and merits. This + # probably isn't strictly necessary, since it will happen + # anyway when battle 1 and 2 start, but duplicating this code + # here allows us to skip directly to battle 3 with a magic + # word. + for avId in self.involvedToons: + toon = self.air.doId2do.get(avId) + if toon: + p = [] + for t in ToontownBattleGlobals.Tracks: + p.append(toon.experience.getExp(t)) + self.toonExp[avId] = p + self.toonOrigMerits[avId] = toon.cogMerits[:] + + # Shuffle the toons and divide them into two groups for the + # two different battles. + self.divideToons() + + + def initializeBattles(self, battleNumber, bossCogPosHpr): + # Set up the pair of battle objects for the BattleOne or + # BattleTwo phase. + self.resetBattles() + + if not self.involvedToons: + self.notify.warning("initializeBattles: no toons!") + return + + self.battleNumber = battleNumber + suitHandles = self.generateSuits(battleNumber) + self.suitsA = suitHandles['activeSuits'] + self.activeSuitsA = self.suitsA[:] + self.reserveSuits = suitHandles['reserveSuits'] + + suitHandles = self.generateSuits(battleNumber) + self.suitsB = suitHandles['activeSuits'] + self.activeSuitsB = self.suitsB[:] + self.reserveSuits += suitHandles['reserveSuits'] + + if self.toonsA: + self.battleA = self.makeBattle( + bossCogPosHpr, ToontownGlobals.BossCogBattleAPosHpr, + self.handleRoundADone, self.handleBattleADone, + battleNumber, 0) + self.battleAId = self.battleA.doId + else: + # If we don't have a battleA, all of the suits that would + # have been in that battle go over to fight in battleB + # instead. + self.moveSuits(self.activeSuitsA) + self.suitsA = [] + self.activeSuitsA = [] + + if self.arenaSide == None: + self.b_setArenaSide(0) + + if self.toonsB: + self.battleB = self.makeBattle( + bossCogPosHpr, ToontownGlobals.BossCogBattleBPosHpr, + self.handleRoundBDone, self.handleBattleBDone, + battleNumber, 1) + self.battleBId = self.battleB.doId + else: + # If we don't have a battleB, all of the suits that would + # have been in that battle go over to fight in battleA + # instead. + self.moveSuits(self.activeSuitsB) + self.suitsB = [] + self.activeSuitsB = [] + + if self.arenaSide == None: + self.b_setArenaSide(1) + + self.sendBattleIds() + + def makeBattle(self, bossCogPosHpr, battlePosHpr, + roundCallback, finishCallback, battleNumber, battleSide): + battle = DistributedBattleFinalAI.DistributedBattleFinalAI( + self.air, self, roundCallback, finishCallback, battleSide) + self.setBattlePos(battle, bossCogPosHpr, battlePosHpr) + + # Just like the DistributedSuitInteriorAI class, we save a + # reference to our suitsKilled and toonSkillPtsGained + # structures in each battle we create. That way, the battle + # will directly adjust these structures and we accumulate the + # toon credits for all battles. + battle.suitsKilled = self.suitsKilled + battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained + battle.toonExp = self.toonExp + battle.toonOrigQuests = self.toonOrigQuests + battle.toonItems = self.toonItems + battle.toonOrigMerits = self.toonOrigMerits + battle.toonMerits = self.toonMerits + battle.toonParts = self.toonParts + battle.helpfulToons = self.helpfulToons + + # We get a bonus factor applied toward each attack's + # experience credit. + mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(battleNumber) + + # We don't, however, get any particular bonus for an invasion + # here. + battle.battleCalc.setSkillCreditMultiplier(mult) + + battle.generateWithRequired(self.zoneId) + return battle + + def setBattlePos(self, battle, cogPosHpr, battlePosHpr): + # We need to set up the global position and rotation of the + # battle objects. To do this most easily, we create a handful + # of temporary nodes to do the matrix math for us. Don't + # panic, there's no harm in creating an unattached NodePath on + # the AI! + + bossNode = NodePath('bossNode') + bossNode.setPosHpr(*cogPosHpr) + battleNode = bossNode.attachNewNode('battleNode') + battleNode.setPosHpr(*battlePosHpr) + + # The battle rotation is described with an initialSuitPos, + # which is the direction in which the battle will look, + # i.e. forward. + suitNode = battleNode.attachNewNode('suitNode') + suitNode.setPos(0, 1, 0) + + battle.pos = battleNode.getPos(NodePath()) + battle.initialSuitPos = suitNode.getPos(NodePath()) + + def moveSuits(self, active): + # Move the active suits to the reserve pool so they can emerge + # on the other side. + for suit in active: + self.reserveSuits.append((suit, 0)) + + def handleRoundADone(self, toonIds, totalHp, deadSuits): + if self.battleA: + assert self.notify.debug("%s. battle A round done, toonIds = %s, totalHp = %s, deadSuits = %s, suits = %s" % (self.doId, toonIds, totalHp, deadSuits, self.battleA.suits,)) + self.handleRoundDone( + self.battleA, self.suitsA, self.activeSuitsA, + toonIds, totalHp, deadSuits) + + def handleRoundBDone(self, toonIds, totalHp, deadSuits): + if self.battleB: + assert self.notify.debug("%s. battle B round done, toonIds = %s, totalHp = %s, deadSuits = %s, suits = %s" % (self.doId, toonIds, totalHp, deadSuits, self.battleB.suits,)) + self.handleRoundDone( + self.battleB, self.suitsB, self.activeSuitsB, + toonIds, totalHp, deadSuits) + + def handleBattleADone(self, zoneId, toonIds): + if self.battleA: + assert self.notify.debug("%s. battle A done, zoneId = %s, toonIds = %s" % (self.doId, zoneId, toonIds)) + self.battleA.requestDelete() + self.battleA = None + self.battleAId = 0 + self.sendBattleIds() + + # Battle A was the first finished; the boss cog will roll up + # on this side to battle two. + if self.arenaSide == None: + self.b_setArenaSide(0) + + if not self.battleB and self.hasToons() and self.hasToonsAlive(): + # Both battles are done; move on. + self.b_setState(self.postBattleState) + + + def handleBattleBDone(self, zoneId, toonIds): + if self.battleB: + assert self.notify.debug("%s. battle B done, zoneId = %s, toonIds = %s" % (self.doId, zoneId, toonIds)) + self.battleB.requestDelete() + self.battleB = None + self.battleBId = 0 + self.sendBattleIds() + + # Battle B was the first finished; the boss cog will roll up + # on this side to battle two. + if self.arenaSide == None: + self.b_setArenaSide(1) + + if not self.battleA and self.hasToons() and self.hasToonsAlive(): + # Both battles are done; move on. + self.b_setState(self.postBattleState) + + def invokeSuitPlanner(self, buildingCode, skelecog): + # Creates a suit planner to generate suits to emerge from the + # boss cog's belly. The buildingCode is the level number to + # use from the table in SuitBuildingGlobals.py; skelecog is + # true to generate skeleton cogs. The return value is the + # dictionary { 'activeSuits' : suits, 'reserveSuits' : suits } + + planner = SuitPlannerInteriorAI.SuitPlannerInteriorAI( + 1, buildingCode, self.dna.dept, self.zoneId) + + planner.respectInvasions = 0 + suits = planner.genFloorSuits(0) + + if skelecog: + # These cogs have already been generated, so we must do a + # distributed setSkelecog + for suit in suits['activeSuits']: + suit.b_setSkelecog(1) + for reserve in suits['reserveSuits']: + suit = reserve[0] + suit.b_setSkelecog(1) + + return suits + + def generateSuits(self, battleNumber): + # A derived class should override this to generate the + # appropriate number and varient of suits for the indicated + # battle. + + raise StandardError, 'generateSuits unimplemented' + + def handleRoundDone(self, battle, suits, activeSuits, + toonIds, totalHp, deadSuits): + # Determine if any reserves need to join + assert self.notify.debug('%s.handleRoundDone() - hp: %d' % (self.doId, totalHp)) + # Calculate the total max HP for all the suits currently on the floor + totalMaxHp = 0 + for suit in suits: + totalMaxHp += suit.maxHP + + for suit in deadSuits: + activeSuits.remove(suit) + + joinedReserves = [] + + # Determine if any reserve suits need to join + if (len(self.reserveSuits) > 0 and len(activeSuits) < 4): + assert(self.notify.debug('%s. potential reserve suits: %d' % \ + (self.doId, len(self.reserveSuits)))) + assert totalHp <= totalMaxHp + hpPercent = 100 - (totalHp / totalMaxHp * 100.0) + assert(self.notify.debug('%s. totalHp: %d totalMaxHp: %d percent: %f' \ + % (self.doId, totalHp, totalMaxHp, hpPercent))) + for info in self.reserveSuits: + if (info[1] <= hpPercent and + len(activeSuits) < 4): + assert(self.notify.debug('%s. reserve: %d joining percent: %f' \ + % (self.doId, info[0].doId, info[1]))) + suits.append(info[0]) + activeSuits.append(info[0]) + joinedReserves.append(info) + for info in joinedReserves: + self.reserveSuits.remove(info) + + battle.resume(joinedReserves) + + def getBattleThreeTime(self): + # Returns the amount of time spent so far in battle three, as + # a ratio of the expected battle three duration. This will + # range 0 .. 1 during the normal course of the battle; if it + # goes beyond 1, the battle should be considered to be in + # overtime (and should rapidly become impossibly difficult, to + # prevent toons from dying a slow, frustrating death). + + elapsed = globalClock.getFrameTime() - self.battleThreeStart + t1 = elapsed / float(self.battleThreeDuration) + return t1 + + def progressValue(self, fromValue, toValue): + # Returns an interpolated value from fromValue to toValue, + # depending on the value of self.bossDamage in the range [0, + # self.bossMaxDamage]. It is also based on time. + + # That is, lerps the value fromValue to toValue during the + # course of the battle. + + # t0 is the elapsed damage + t0 = float(self.bossDamage) / float(self.bossMaxDamage) + + # t1 is the elapsed time + elapsed = globalClock.getFrameTime() - self.battleThreeStart + t1 = elapsed / float(self.battleThreeDuration) + + # We actually progress the value based on the larger of the + # two, so you can't spend all day in the battle. + t = max(t0, t1) + + return fromValue + (toValue - fromValue) * min(t, 1) + + def progressRandomValue(self, fromValue, toValue, radius = 0.2): + + # Similar to progressValue(), but returns a value between + # fromValue and toValue based not exactly on the current + # progress through the battle, but rather on a point *near* + # the current progress through the battle (based on radius, + # which is in the scale 0 to 0.5). + + # The radius starts out at zero and increases linearly to its + # specified value in the middle of the range, then decreases + # linearly to zero again. + + t = self.progressValue(0, 1) + + # First, choose a radius that ramps from 0 to radius to 0 again. + radius = radius * (1.0 - abs(t - 0.5) * 2.0) + + # Then modify t based on a box around that radius. + t += radius * random.uniform(-1, 1) + t = max(min(t, 1.0), 0.0) + + return fromValue + (toValue - fromValue) * t + + def reportToonHealth(self): + # Writes a notify debug message listing all of the involved + # toons and their current health. Useful for determining game + # balancing. + if self.notify.getDebug(): + str = '' + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + str += ', %s (%s/%s)' % (toonId, toon.getHp(), toon.getMaxHp()) + self.notify.debug('%s.toons = %s' % (self.doId, str[2:])) + + def getDamageMultiplier(self): + """Return a multiplier for our damaging attacks.""" + return 1.0 + + def zapToon(self, x, y, z, h, p, r, bpx, bpy, attackCode, timestamp): + # This is sent from the client when he detects a collision + # with one of the boss attacks. + avId = self.air.getAvatarIdFromSender() + + if not self.validate(avId, avId in self.involvedToons, + 'zapToon from unknown avatar'): + return + + if attackCode == ToontownGlobals.BossCogLawyerAttack and \ + self.dna.dept != 'l': + self.notify.warning('got lawyer attack but not in CJ boss battle') + return + + toon = simbase.air.doId2do.get(avId) + if toon: + self.d_showZapToon(avId, x, y, z, h, p, r, attackCode, timestamp) + + damage = ToontownGlobals.BossCogDamageLevels.get(attackCode) + if damage == None: + self.notify.warning("No damage listed for attack code %s" % (attackCode)) + damage = 5 + + damage *= self.getDamageMultiplier() + self.damageToon(toon, damage) + + currState = self.getCurrentOrNextState() + if attackCode == ToontownGlobals.BossCogElectricFence and \ + (currState == 'RollToBattleTwo' or currState == 'BattleThree'): + if bpy < 0 and abs(bpx / bpy) > 0.5: + # If the toon hit us largely in the front and on + # the side, swat at him. + + if bpx < 0: + self.b_setAttackCode(ToontownGlobals.BossCogSwatRight) + else: + self.b_setAttackCode(ToontownGlobals.BossCogSwatLeft) + + def d_showZapToon(self, avId, x, y, z, h, p, r, attackCode, timestamp): + self.sendUpdate('showZapToon', [avId, x, y, z, h, p, r, attackCode, timestamp]) + + def b_setAttackCode(self, attackCode, avId = 0): + self.d_setAttackCode(attackCode, avId) + self.setAttackCode(attackCode, avId) + + + def setAttackCode(self, attackCode, avId = 0): + assert self.notify.debug('%s.setAttackCode(%s, %s)' % (self.doId, attackCode, avId)) + self.attackCode = attackCode + self.attackAvId = avId + + # How long to wait for the next attack? + if attackCode == ToontownGlobals.BossCogDizzy or attackCode == ToontownGlobals.BossCogDizzyNow: + # Stay dizzy for a variable length of time. It starts out + # easier and gets harder. + delayTime = self.progressValue(20, 5) + + # Track the number of hits while we're dizzy. + self.hitCount = 0 + + elif attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + delayTime = ToontownGlobals.BossCogAttackTimes.get(attackCode) + + # Wait a length of time after the slow attack. + delayTime += self.progressValue(10, 0) + + else: + delayTime = ToontownGlobals.BossCogAttackTimes.get(attackCode) + if delayTime == None: + return + + self.waitForNextAttack(delayTime) + + def d_setAttackCode(self, attackCode, avId = 0): + self.sendUpdate('setAttackCode', [attackCode, avId]) + + + def waitForNextAttack(self, delayTime): + currState = self.getCurrentOrNextState() + if (currState == 'BattleThree'): + assert self.notify.debug("%s.Waiting %s seconds for next attack." % (self.doId, delayTime)) + taskName = self.uniqueName('NextAttack') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.doNextAttack, taskName) + else: + assert self.notify.debug("%s.Not doing another attack in state %s." % (self.doId, currState)) + + def stopAttacks(self): + taskName = self.uniqueName('NextAttack') + taskMgr.remove(taskName) + + def doNextAttack(self, task): + # This may be overridden by a derived class to handle the + # boss's attacks in battle three. + self.b_setAttackCode(ToontownGlobals.BossCogNoAttack) + diff --git a/toontown/src/suit/DistributedBossbotBoss.py b/toontown/src/suit/DistributedBossbotBoss.py new file mode 100644 index 0000000..3a2bec5 --- /dev/null +++ b/toontown/src/suit/DistributedBossbotBoss.py @@ -0,0 +1,2285 @@ +import math +import random +from pandac.PandaModules import NametagGroup, CFSpeech, VBase3, CollisionPlane, \ + CollisionNode, CollisionSphere, CollisionTube, NodePath, Plane, Vec3, Vec2,\ + Point3, BitMask32, CollisionHandlerEvent, TextureStage, VBase4, BoundingSphere +from direct.interval.IntervalGlobal import Sequence, Wait, Func, LerpHprInterval, \ + Parallel, LerpPosInterval, Track, ActorInterval, ParallelEndTogether, \ + LerpFunctionInterval, LerpScaleInterval, LerpPosHprInterval, SoundInterval +from direct.task import Task +from direct.fsm import FSM +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.ClockDelta import globalClockDelta +from direct.showbase import PythonUtil +from direct.task import Task +from toontown.distributed import DelayDelete +from toontown.toonbase import ToontownGlobals +from toontown.suit import DistributedBossCog +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals +from toontown.suit import SuitDNA +from toontown.toon import Toon +from toontown.toon import ToonDNA +from toontown.building import ElevatorConstants +from toontown.toonbase import ToontownTimer +from toontown.toonbase import ToontownBattleGlobals +from toontown.battle import RewardPanel +from toontown.battle import MovieToonVictory +from toontown.coghq import CogDisguiseGlobals +from toontown.suit import Suit +from toontown.suit import SuitDNA +from toontown.effects import DustCloud + +# This pointer keeps track of the one DistributedBossbotBoss that +# should appear within the avatar's current visibility zones. If +# there is more than one DistributedSellbotBoss visible to a client at +# any given time, something is wrong. +OneBossCog = None + +TTL = TTLocalizer +class DistributedBossbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM): + """ + Heavily adapted from DistributedLawbotBoss. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBossbotBoss') + BallLaunchOffset = Point3(10.5, 8.5, -5) + + def __init__(self, cr): + """Initialize most fields to zero or None.""" + self.notify.debug("----- __init___") + DistributedBossCog.DistributedBossCog.__init__(self, cr) + FSM.FSM.__init__(self, 'DistributedBossbotBoss') + self.bossDamage = 0 + self.bossMaxDamage = ToontownGlobals.BossbotBossMaxDamage + # lets us know values on how to open the elevator + self.elevatorType = ElevatorConstants.ELEVATOR_BB + + # the resistance toon who guides us + self.resistanceToon = None + self.resistanceToonOnstage = 0 + + self.battleANode.setPosHpr(*ToontownGlobals.WaiterBattleAPosHpr) + self.battleBNode.setPosHpr(*ToontownGlobals.WaiterBattleBPosHpr) + + # keep track if the toons are carrying food or not + self.toonFoodStatus = {} + + # reference to the food belts amd tables + self.belts = [None,None] + self.tables = {} + self.golfSpots = {} + + self.servingTimer = None + self.notDeadList = None + self.moveTrack = None + + self.speedDamage = 0 + self.maxSpeedDamage = ToontownGlobals.BossbotMaxSpeedDamage + self.speedRecoverRate = 0 + self.speedRecoverStartTime = 0 + self.ballLaunch = None + self.moveTrack = None + self.lastZapLocalTime = 0 + self.numAttacks = 0 + + def announceGenerate(self): + """Handle all required fields having been filled in.""" + DistributedBossCog.DistributedBossCog.announceGenerate(self) + self.loadEnvironment() + self.__makeResistanceToon() + + # Enable the special CEO chat menu. + localAvatar.chatMgr.chatInputSpeedChat.addCEOMenu() + + global OneBossCog + if OneBossCog != None: + self.notify.warning("Multiple BossCogs visible.") + OneBossCog = self + + # Anything in the world we hit that's *not* the BossCog + # inherits this global pieCode, so the splat will be colored + # gray. + render.setTag('pieCode', str(ToontownGlobals.PieCodeNotBossCog)) + + # make collisions with this boss do more damage + self.setTag('attackCode', str(ToontownGlobals.BossCogGolfAttack)) + + # Make another bubble--a tube--to serve as a target in battle + # four. + target = CollisionTube(0, -2, -2, 0, -1, 9, 4.0) + targetNode = CollisionNode('BossZap') + targetNode.addSolid(target) + targetNode.setCollideMask(ToontownGlobals.PieBitmask) + self.targetNodePath = self.pelvis.attachNewNode(targetNode) + self.targetNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossCog)) + + # mark the other collision piece set up in the base class with a pie code + self.axle.getParent().setTag('pieCode', str(ToontownGlobals.PieCodeBossCog)) + + # He also gets a disk-shaped shield around his little cog hula + # hoop. + disk = loader.loadModel('phase_9/models/char/bossCog-gearCollide') + disk.find('**/+CollisionNode').setName('BossZap') + disk.reparentTo(self.pelvis) + disk.setZ(0.8) + + # Put a small trigger bubble around the cog so we can tell the + # AI when the boss touches a table + closeBubble = CollisionSphere(0, 0, 0, 10) + closeBubble.setTangible(0) + closeBubbleNode = CollisionNode('CloseBoss') + closeBubbleNode.setIntoCollideMask(BitMask32(0)) + closeBubbleNode.setFromCollideMask(ToontownGlobals.BanquetTableBitmask) + closeBubbleNode.addSolid(closeBubble) + self.closeBubbleNode = closeBubbleNode + self.closeHandler = CollisionHandlerEvent() + self.closeHandler.addInPattern('closeEnter') + #self.closeHandler.addInPattern('closeEnter-%in') + self.closeHandler.addOutPattern('closeExit') + #self.closeHandler.addOutPattern('closeExit-%out') + self.closeBubbleNodePath = self.attachNewNode(closeBubbleNode) + base.cTrav.addCollider( self.closeBubbleNodePath, self.closeHandler), + self.accept('closeEnter', self.closeEnter) + self.accept('closeExit', self.closeExit) + + # get a handle to the treads so we can show speed damage + #import pdb; pdb.set_trace() + self.treads = self.find('**/treads') + + demotedCeo = Suit.Suit() + demotedCeo.dna = SuitDNA.SuitDNA() + demotedCeo.dna.newSuit('f') + demotedCeo.setDNA(demotedCeo.dna) + demotedCeo.reparentTo(self.geom) + demotedCeo.loop('neutral') + demotedCeo.stash() + self.demotedCeo = demotedCeo + + self.bossClub = loader.loadModel('phase_12/models/char/bossbotBoss-golfclub') + overtimeOneClubSequence= Sequence( + self.bossClub.colorScaleInterval(0.1, colorScale = VBase4(0, 1, 0, 1)), + self.bossClub.colorScaleInterval(0.3, colorScale = VBase4(1, 1, 1, 1))) + overtimeTwoClubSequence= Sequence( + self.bossClub.colorScaleInterval(0.1, colorScale = VBase4(1, 0, 0, 1)), + self.bossClub.colorScaleInterval(0.3, colorScale = VBase4(1, 1, 1, 1))) + self.bossClubIntervals = [overtimeOneClubSequence, overtimeTwoClubSequence] + self.rightHandJoint = self.find('**/joint17') + + self.setPosHpr(*ToontownGlobals.BossbotBossBattleOnePosHpr) + self.reparentTo(render) + + self.toonUpSfx = loader.loadSfx('phase_11/audio/sfx/LB_toonup.mp3') + self.warningSfx = loader.loadSfx('phase_5/audio/sfx/Skel_COG_VO_grunt.mp3') + self.swingClubSfx = loader.loadSfx('phase_5/audio/sfx/SA_hardball.mp3') + self.moveBossTaskName = "CEOMoveTask" + + def disable(self): + """Remove this object from active duty. + + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + self.notify.debug("----- disable") + DistributedBossCog.DistributedBossCog.disable(self) + self.demotedCeo.delete() + base.cTrav.removeCollider(self.closeBubbleNodePath) + taskMgr.remove('RecoverSpeedDamage') + self.request('Off') + self.unloadEnvironment() + self.__cleanupResistanceToon() + if self.servingTimer: + self.servingTimer.destroy() + del self.servingTimer + + localAvatar.chatMgr.chatInputSpeedChat.removeCEOMenu() + + global OneBossCog + if OneBossCog == self: + OneBossCog = None + + self.promotionMusic.stop() + self.betweenPhaseMusic.stop() + self.phaseTwoMusic.stop() + self.phaseFourMusic.stop() + self.interruptMove() + for ival in self.bossClubIntervals: + ival.finish() + self.belts = [] + self.tables = {} + self.removeAllTasks() + + ##### Environment ##### + + def loadEnvironment(self): + """Load most of the assets used in the battle.""" + self.notify.debug("----- loadEnvironment") + DistributedBossCog.DistributedBossCog.loadEnvironment(self) + self.geom = loader.loadModel('phase_12/models/bossbotHQ/BanquetInterior_1') + + # do elevator + self.elevatorEntrance = self.geom.find('**/elevator_origin') + elevatorModel = loader.loadModel("phase_12/models/bossbotHQ/BB_Inside_Elevator") + if not elevatorModel: + # something went wrong, try the outside elevator + elevatorModel = loader.loadModel("phase_12/models/bossbotHQ/BB_Elevator") + elevatorModel.reparentTo(self.elevatorEntrance) + self.setupElevator(elevatorModel) + self.banquetDoor = self.geom.find('**/door3') + + # Also, put a big plane across the universe a few feet below + # the floor, to catch things that fall out of the world. + plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -50))) + planeNode = CollisionNode('dropPlane') + planeNode.addSolid(plane) + planeNode.setCollideMask(ToontownGlobals.PieBitmask) + self.geom.attachNewNode(planeNode) + + self.geom.reparentTo(render) + + # before battles: play the boss theme music + self.promotionMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + # 'phase_9/audio/bgm/encntr_head_suit_theme.mid') + + # Between major phases, play the upbeat street battle music + self.betweenPhaseMusic = base.loadMusic( + 'phase_9/audio/bgm/encntr_toon_winning.mid') + # Battle two: play new jury music + self.phaseTwoMusic = base.loadMusic( + 'phase_12/audio/bgm/BossBot_CEO_v1.mid') + self.phaseFourMusic = base.loadMusic( + 'phase_12/audio/bgm/BossBot_CEO_v2.mid') + + self.pickupFoodSfx = loader.loadSfx('phase_6/audio/sfx/SZ_MM_gliss.mp3') + self.explodeSfx = loader.loadSfx('phase_4/audio/sfx/firework_distance_02.mp3') + + def unloadEnvironment(self): + """Unload the environment, also call base class unload.""" + self.notify.debug("----- unloadEnvironment") + + # remove belt and ball models + for belt in self.belts: + if belt: + belt.cleanup() + for spot in self.golfSpots.values(): + if spot: + spot.cleanup() + self.golfSpots = {} + self.geom.removeNode() + del self.geom + + DistributedBossCog.DistributedBossCog.unloadEnvironment(self) + + def __makeResistanceToon(self): + """Generate the resistance toon. + + Generates the Resistance Toon (tm), who will be our initial + guide and then callously abandon us to our fate. + """ + assert self.notify.debugStateCall(self) + if self.resistanceToon: + return + + npc = Toon.Toon() + npc.setName(TTLocalizer.BossbotResistanceToonName) + npc.setPickable(0) + npc.setPlayerType(NametagGroup.CCNonPlayer) + dna = ToonDNA.ToonDNA() + dna.newToonRandom(11237, 'm', 1) + dna.head = "sls" + npc.setDNAString(dna.makeNetString()) + + npc.animFSM.request("neutral") + npc.loop('neutral') + + self.resistanceToon = npc + self.resistanceToon.setPosHpr(*ToontownGlobals.BossbotRTIntroStartPosHpr) + + # determine a random suit to put him in + state = random.getstate() + random.seed(self.doId) + self.resistanceToon.suitType = SuitDNA.getRandomSuitByDept("c") + # test movies with the smalles and biggest suit types! + #self.resistanceToon.suitType = 'mm' + #self.resistanceToon.suitType = 'tbc' + random.setstate(state) + + def __cleanupResistanceToon(self): + """Delete the resistance toon.""" + assert self.notify.debugStateCall(self) + self.__hideResistanceToon() + if self.resistanceToon: + self.resistanceToon.takeOffSuit() + self.resistanceToon.removeActive() + self.resistanceToon.delete() + self.resistanceToon = None + + def __showResistanceToon(self, withSuit): + """Show the resistance toon.""" + assert self.notify.debugStateCall(self) + if not self.resistanceToonOnstage: + self.resistanceToon.addActive() + self.resistanceToon.reparentTo(self.geom) + self.resistanceToonOnstage = 1 + + if withSuit: + suit = self.resistanceToon.suitType + self.resistanceToon.putOnSuit(suit, False) + else: + self.resistanceToon.takeOffSuit() + + def __hideResistanceToon(self): + """Hide the resistance toon.""" + assert self.notify.debugStateCall(self) + if self.resistanceToonOnstage: + self.resistanceToon.removeActive() + self.resistanceToon.detachNode() + self.resistanceToonOnstage = 0 + + ##### Elevator state ##### + + def enterElevator(self): + """Handle entering the elevator state.""" + DistributedBossCog.DistributedBossCog.enterElevator(self) + + # Disable the resistance toon's nametag while we're in the + # elevator. + self.resistanceToon.removeActive() + self.__showResistanceToon(True) + self.resistanceToon.suit.loop('neutral') + + # force the camera to a certain position, to avoid the 1 frame flash + base.camera.setPos(0,21,7) + + # for now show the CEO + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.BossbotBossBattleOnePosHpr) + self.loop('Ff_neutral') + self.show() + + #### Intro #### + def enterIntroduction(self): + """Enter the intro state.""" + if not self.resistanceToonOnstage: + self.__showResistanceToon(True) + DistributedBossCog.DistributedBossCog.enterIntroduction(self) + base.playMusic(self.promotionMusic, looping=1, volume=0.9) + + def exitIntroduction(self): + """Exit the intro state.""" + DistributedBossCog.DistributedBossCog.exitIntroduction(self) + self.promotionMusic.stop() + + def makeIntroductionMovie(self, delayDeletes): + """Generate an interval which shows the toons meeting the Resistance Toon, etc.""" + rToon = self.resistanceToon + rToonStartPos = Point3(ToontownGlobals.BossbotRTIntroStartPosHpr[0], + ToontownGlobals.BossbotRTIntroStartPosHpr[1], + ToontownGlobals.BossbotRTIntroStartPosHpr[2]) + rToonEndPos = rToonStartPos + Point3(40, 0, 0) + elevCamPosHpr = ToontownGlobals.BossbotElevCamPosHpr + closeUpRTCamPos = Point3(elevCamPosHpr[0], + elevCamPosHpr[1], + elevCamPosHpr[2]) + closeUpRTCamHpr = Point3(elevCamPosHpr[3], + elevCamPosHpr[4], + elevCamPosHpr[5]) + closeUpRTCamPos.setY(closeUpRTCamPos.getY() + 20) + closeUpRTCamPos.setZ(closeUpRTCamPos.getZ() + -2) + closeUpRTCamHpr = Point3(0,5,0) + + loseSuitCamPos = Point3(rToonStartPos) + loseSuitCamPos += Point3(0,-5,4) + loseSuitCamHpr = Point3(180,0,0) + + waiterCamPos = Point3(rToonStartPos) + waiterCamPos += Point3(-5,-10,5) + waiterCamHpr = Point3(-30,0,0) + + track =Sequence( + #Cut to resistance toon + Func(camera.reparentTo, render), + Func(camera.setPosHpr, *elevCamPosHpr), + Func(rToon.setChatAbsolute, TTL.BossbotRTWelcome, CFSpeech), + LerpPosHprInterval(camera, 3, closeUpRTCamPos, closeUpRTCamHpr), + #Wait(3), + Func(rToon.setChatAbsolute, TTL.BossbotRTRemoveSuit, CFSpeech), + Wait(3), + + # Cut to toons losing their cog suits. + Func(self.clearChat), + self.loseCogSuits(self.toonsA + self.toonsB, render, + (loseSuitCamPos[0], loseSuitCamPos[1], loseSuitCamPos[2], + loseSuitCamHpr[0], loseSuitCamHpr[1], loseSuitCamHpr[2])), + #clean up the toons' eyes + self.toonNormalEyes(self.involvedToons), + #self.toonNormalEyes([self.resistanceToon], True), + Wait(2), + + # Resistance toon tells them to fight the waiters, and walks away + Func(camera.setPosHpr, closeUpRTCamPos, closeUpRTCamHpr), + Func(rToon.setChatAbsolute, TTL.BossbotRTFightWaiter, CFSpeech), + Wait(1), + LerpHprInterval(camera, 2, Point3(-15,5,0)), + #Wait(3), + Sequence(Func(rToon.suit.loop, 'walk'), + rToon.hprInterval(1, VBase3(270, 0, 0)), #turn him around + rToon.posInterval(2.5, rToonEndPos), + Func(rToon.suit.loop, 'neutral') + ), + Wait(3), + Func(rToon.clearChat), + Func(self.__hideResistanceToon) + ) + return track + + + ##### Frolic state ##### + + # This state is probably only useful for debugging. The toons are + # all free to run around the world. + + def enterFrolic(self): + """Handle entering the frolic state. + + We should only get here through a magic word + """ + self.notify.debug("----- enterFrolic") + self.setPosHpr(*ToontownGlobals.BossbotBossBattleOnePosHpr) + DistributedBossCog.DistributedBossCog.enterFrolic(self) + self.show() + + ##### PrepareBattleTwo state ##### + + def enterPrepareBattleTwo(self): + """Do the pre phase 2 movie. + + The Clients will see the toons donning waiter disguise. + CEO will make brief appearing demanding food. + Resistance toons says give them food till they explode. + Walk toons through door + Close door + """ + self.controlToons() + self.setToonsToNeutral(self.involvedToons) + # remove the disguise, so we can quickly get here riding up the elevator + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.takeOffSuit() + self.__showResistanceToon(True) + self.resistanceToon.setPosHpr(*ToontownGlobals.BossbotRTPreTwoPosHpr) + self.__arrangeToonsAroundResistanceToon() + + intervalName = "PrepareBattleTwoMovie" + delayDeletes = [] + seq = Sequence(self.makePrepareBattleTwoMovie(delayDeletes), + Func(self.__onToBattleTwo), + name = intervalName) + seq.delayDeletes = delayDeletes + seq.start() + self.storeInterval(seq, intervalName) + base.playMusic(self.betweenPhaseMusic, looping=1, volume=0.9) + + def makePrepareBattleTwoMovie(self, delayDeletes): + """Create and return the pre battle two movie.""" + # We need to protect our movie against any of the toons + # disconnecting while the movie plays. + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete( + toon, 'BossbotBoss.makePrepareBattleTwoMovie')) + + rToon = self.resistanceToon + rToonStartPos = Point3(ToontownGlobals.BossbotRTPreTwoPosHpr[0], + ToontownGlobals.BossbotRTPreTwoPosHpr[1], + ToontownGlobals.BossbotRTPreTwoPosHpr[2]) + rToonEndPos = rToonStartPos + Point3(-40, 0, 0) + + bossPos = Point3(ToontownGlobals.BossbotBossPreTwoPosHpr[0], + ToontownGlobals.BossbotBossPreTwoPosHpr[1], + ToontownGlobals.BossbotBossPreTwoPosHpr[2]) + bossEndPos = Point3(ToontownGlobals.BossbotBossBattleOnePosHpr[0], + ToontownGlobals.BossbotBossBattleOnePosHpr[1], + ToontownGlobals.BossbotBossBattleOnePosHpr[2]) + + tempNode = self.attachNewNode('temp') + tempNode.setPos(0,-40,18) + + def getCamBossPos(tempNode=tempNode): + return tempNode.getPos(render) + + rNode =rToon.attachNewNode('temp2') + rNode.setPos(-5,25,12) + + def getCamRTPos(rNode =rNode): + return rNode.getPos(render) + + + track = Sequence( + Func(camera.reparentTo, render), + Func(camera.setPos, rToon, 0, 22, 6), + Func(camera.setHpr, 0, 0, 0), + + + Func(rToon.setChatAbsolute, TTL.BossbotRTWearWaiter, CFSpeech), + Wait(3.0), + self.wearCogSuits( self.toonsA + self.toonsB, render, None, waiter=True), + Func(rToon.clearChat), + + Func(self.setPosHpr, bossPos, Point3(0,0,0)), + # door opens + Parallel( + LerpHprInterval(self.banquetDoor, 2, Point3(90, 0, 0)), + LerpPosInterval(camera, 2, getCamBossPos), + ), + Func(self.setChatAbsolute, TTL.BossbotBossPreTwo1, CFSpeech), + Wait(3.0), + Func(self.setChatAbsolute, TTL.BossbotBossPreTwo2, CFSpeech), + Wait(3.0), + Parallel( + LerpHprInterval(self.banquetDoor, 2, Point3(0, 0, 0)), + LerpPosHprInterval(camera, 2, getCamRTPos, Point3(10,-8,0)), + ), + Func(self.setPos, bossEndPos), + Func(self.clearChat), + + Func(rToon.setChatAbsolute, TTL.BossbotRTServeFood1, CFSpeech), + # Open the door as the resistanceToon is talking + Wait(3.0), + Func(rToon.setChatAbsolute, TTL.BossbotRTServeFood2, CFSpeech), + Wait(1.0), + LerpHprInterval(self.banquetDoor, 2, Point3(120, 0, 0)), + Sequence(Func(rToon.suit.loop, 'walk'), + rToon.hprInterval(1, VBase3(90, 0, 0)), #turn him around + rToon.posInterval(2.5, rToonEndPos), + Func(rToon.suit.loop, 'neutral') + ), + + # move the toons in + self.createWalkInInterval(), + + # shut the door and clean up + Func(self.banquetDoor.setH, 0), + Func(rToon.clearChat), + Func(self.__hideResistanceToon), + ) + + return track + + def createWalkInInterval(self): + """Create a movie of the toons walking into the banquet room.""" + retval = Parallel() + delay = 0 + index = 0 + for toonId in self.involvedToons: + toon = base.cr.doId2do.get(toonId) + if not toon: + continue + destPos = Point3( -14 + (index *4), 25, 0) + def toWalk(toon): + if hasattr(toon, 'suit') and toon.suit: + toon.suit.loop('walk') + def toNeutral(toon): + if hasattr(toon, 'suit') and toon.suit: + toon.suit.loop('neutral') + retval.append( Sequence( + Wait(delay), + Func(toon.wrtReparentTo, render), + Func(toWalk, toon), + Func(toon.headsUp,0,0,0), + LerpPosInterval(toon, 3, Point3(0,0,0)), + Func(toon.headsUp , destPos), + LerpPosInterval(toon, 3, destPos), + LerpHprInterval(toon, 1, Point3(0,0,0)), + Func(toNeutral, toon), + )) + if toon == base.localAvatar: + retval.append( Sequence( + Wait(delay), + Func(camera.reparentTo,toon), + Func(camera.setPos, toon.cameraPositions[0][0]), + Func(camera.setHpr, 0, 0, 0) + )) + delay += 1.0 + index += 1 + return retval + + def __onToBattleTwo(self, elapsedTime=0): + """Tell AI we are done with PrepareBattleTwoState.""" + self.doneBarrier('PrepareBattleTwo') + + # Wait a second. If we don't move on immediately, pop up the + # "waiting for other players" message. + #taskMgr.doMethodLater(1, self.__showWaitingMessage, + # self.uniqueName("WaitingMessage")) + + + def exitPrepareBattleTwo(self): + """Cleanup PrepareBattleTwo state.""" + assert self.notify.debugStateCall(self) + self.clearInterval( "PrepareBattleTwoMovie") + self.betweenPhaseMusic.stop() + pass + + def __arrangeToonsAroundResistanceToon(self): + """Arrange the toons around the resistance toon.""" + radius = 9 + numToons = len(self.involvedToons) + center = (numToons - 1) / 2.0 + for i in range(numToons): + toon = self.cr.doId2do.get(self.involvedToons[i]) + if toon: + angle = 90 - 25 * (i - center) + radians = angle * math.pi / 180.0 + x = math.cos(radians) * radius + y = math.sin(radians) * radius + toon.reparentTo(render) + toon.setPos(self.resistanceToon, x, y, 0) + toon.headsUp(self.resistanceToon) + toon.loop('neutral') + toon.show() + + + ##### BattleTwo state ##### + + def enterBattleTwo(self): + """Enter the serving food state.""" + # Let them walk around + self.releaseToons(finalBattle = 1) + # put the disguise, so we can quickly get here riding up the elevator + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + self.putToonInCogSuit( toon) + #setup jury timer + self.servingTimer = ToontownTimer.ToontownTimer() + self.servingTimer.posInTopRightCorner() + self.servingTimer.countdown(ToontownGlobals.BossbotBossServingDuration) + base.playMusic(self.phaseTwoMusic, looping=1, volume=0.9) + + def exitBattleTwo(self): + """Do the cleanup for the battle two state.""" + if (self.servingTimer): + self.servingTimer.destroy() + del self.servingTimer + self.servingTimer = None + for toonId in self.involvedToons: + self.removeFoodFromToon(toonId) + self.phaseTwoMusic.stop() + + def setBelt(self, belt, beltIndex): + """Register the conveyer belt in the room.""" + if beltIndex < len(self.belts): + self.belts[beltIndex] = belt + + def localToonTouchedBeltFood(self, beltIndex, foodIndex, foodNum): + """Handle the local toon touching food on the conveyer belt.""" + avId = base.localAvatar.doId + doRequest = False + if not avId in self.toonFoodStatus: + doRequest = True + elif not self.toonFoodStatus[avId]: + doRequest = True + if doRequest: + self.sendUpdate('requestGetFood', [beltIndex, foodIndex, foodNum]) + + def toonGotFood(self, avId, beltIndex, foodIndex, foodNum): + """Hande the AI granting a get food request to a toon.""" + if self.belts[beltIndex]: + self.belts[beltIndex].removeFood(foodIndex) + self.putFoodOnToon(avId, beltIndex, foodNum) + + def putFoodOnToon(self, avId, beltIndex, foodNum): + """Put the cog food on the toon's hands.""" + self.toonFoodStatus[avId] = (beltIndex, foodNum) + av = base.cr.doId2do.get(avId) + if av: + intervalName = self.uniqueName('loadFoodSoundIval-%d' % avId) + seq = SoundInterval( self.pickupFoodSfx, node = av, name = intervalName) + oldSeq = self.activeIntervals.get(intervalName) + if oldSeq: + oldSeq.finish() + seq.start() + self.activeIntervals[intervalName] = seq + + # TODO make sure the animations have the left joint in the correct place + # when doing toon standing holding food, and toon moving holding food + foodModel = loader.loadModel('phase_12/models/bossbotHQ/canoffood') + foodModel.setName('cogFood') + foodModel.setScale(ToontownGlobals.BossbotFoodModelScale) + foodModel.reparentTo(av.suit.getRightHand()) + foodModel.setHpr(52.1961, 180.4983, -4.2882) + curAnim = av.suit.getCurrentAnim() + self.notify.debug('curAnim=%s' % curAnim) + if curAnim in ('walk', 'run'): + av.suit.loop('tray-walk') + elif curAnim =='neutral': + self.notify.debug('looping tray-netural') + av.suit.loop('tray-neutral') + else: + self.notify.warning("don't know what to do with anim=%s" % curAnim) + + def removeFoodFromToon(self, avId): + """Remove the cog food from the toon's hands.""" + self.toonFoodStatus[avId] = None + av = base.cr.doId2do.get(avId) + if av: + cogFood = av.find('**/cogFood') + if not cogFood.isEmpty(): + cogFood.removeNode() + + def detachFoodFromToon(self, avId): + """Detach the cog food from the toon's hands. + + It will reparent the food to renderm, then return the food nodepath. + Returns none if there's any problem.""" + cogFood = None + self.toonFoodStatus[avId] = None + av = base.cr.doId2do.get(avId) + if av: + cogFood = av.find('**/cogFood') + if not cogFood.isEmpty(): + retval = cogFood + cogFood.wrtReparentTo(render) + curAnim = av.suit.getCurrentAnim() + self.notify.debug('curAnim=%s' % curAnim) + if curAnim == 'tray-walk': + av.suit.loop('run') + elif curAnim == 'tray-neutral': + av.suit.loop('neutral') + else: + self.notify.warning("don't know what to do with anim=%s" % curAnim) + return cogFood + + def setTable(self, table, tableIndex): + """Register one of the banquet tables in the room.""" + self.tables[tableIndex] = table + + def localToonTouchedChair(self, tableIndex, chairIndex): + """Handle the local toon touching food on the conveyer belt.""" + avId = base.localAvatar.doId + if (avId in self.toonFoodStatus) and \ + self.toonFoodStatus[avId] != None: + # we are carrying food, and we touched a chair with a hungry cog + self.sendUpdate('requestServeFood', [tableIndex, chairIndex]) + + def toonServeFood(self, avId, tableIndex, chairIndex): + """Hande the AI granting a serve food request to a toon.""" + food = self.detachFoodFromToon(avId) + table = self.tables[tableIndex] + table.serveFood(food, chairIndex) + + + ##### Prepare BattleThree state ##### + def enterPrepareBattleThree(self): + """Handle entering the Prepare Battle three state """ + self.calcNotDeadList() + self.battleANode.setPosHpr(*ToontownGlobals.DinerBattleAPosHpr) + self.battleBNode.setPosHpr(*ToontownGlobals.DinerBattleBPosHpr) + self.cleanupIntervals() + self.controlToons() + self.setToonsToNeutral(self.involvedToons) + # put the disguise, so we can quickly get here riding up the elevator + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + self.putToonInCogSuit( toon) + intervalName = "PrepareBattleThreeMovie" + seq = Sequence(self.makePrepareBattleThreeMovie(), + Func(self.__onToBattleThree), + name = intervalName) + + seq.start() + self.storeInterval(seq, intervalName) + base.playMusic(self.betweenPhaseMusic, looping=1, volume=0.9) + + def calcNotDeadList(self): + """Calculate which diners are not dead.""" + if not self.notDeadList: + self.notDeadList = [] + for tableIndex in xrange(len(self.tables)): + table = self.tables[tableIndex] + tableInfo = table.getNotDeadInfo() + self.notDeadList += tableInfo + + def exitPrepareBattleThree(self): + """Handle exiting the Prepare Battle three state """ + self.clearInterval( "PrepareBattleThreeMovie") + self.betweenPhaseMusic.stop() + pass + + def __onToBattleThree(self, elapsedTime=0): + """Tell AI we are done with PrepareBattleThreeState.""" + self.doneBarrier('PrepareBattleThree') + + def makePrepareBattleThreeMovie(self): + """Create and return the pre battle three movie.""" + loseSuitCamAngle = (0,19, 6,-180,-5,0) + track = Sequence( + Func(camera.reparentTo, self), + Func(camera.setPos, Point3(0, -45, 5)), + Func(camera.setHpr, Point3(0, 14, 0)), + Func(self.setChatAbsolute, TTL.BossbotPhase3Speech1, CFSpeech), + Wait(3.0), + Func(self.setChatAbsolute, TTL.BossbotPhase3Speech2, CFSpeech), + Wait(3.0), + Func(camera.setPosHpr, base.localAvatar, *loseSuitCamAngle), + Wait(1.0), + self.loseCogSuits(self.toonsA + self.toonsB, base.localAvatar, loseSuitCamAngle), + self.toonNormalEyes(self.involvedToons), + Wait(2), + Func(camera.reparentTo, self), + Func(camera.setPos, Point3(0, -45, 5)), + Func(camera.setHpr, Point3(0, 14, 0)), + Func(self.setChatAbsolute, TTL.BossbotPhase3Speech3, CFSpeech), + Wait(3.0), + Func(self.clearChat) + ) + + return track + + + ##### BattleThree state ##### + def enterBattleThree(self): + """Handle entering the Battle three state """ + self.cleanupIntervals() + self.calcNotDeadList() + for table in self.tables.values(): + table.setAllDinersToSitNeutral() + self.battleANode.setPosHpr(*ToontownGlobals.DinerBattleAPosHpr) + self.battleBNode.setPosHpr(*ToontownGlobals.DinerBattleBPosHpr) + # self.controlToons() # don't do this so we see laffMeter + self.setToonsToNeutral(self.involvedToons) + # remove the disguise, so we can quickly get here riding up the elevator + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.takeOffSuit() + # Get the credit multiplier + #mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(3) + # force it to 1, or maybe even zero + mult = 1 + localAvatar.inventory.setBattleCreditMultiplier(mult) + + # The toons should be in their battle position. + self.toonsToBattlePosition(self.toonsA, self.battleANode) + self.toonsToBattlePosition(self.toonsB, self.battleBNode) + + # Now the battle holds the toons. + self.releaseToons() + + base.playMusic(self.battleOneMusic, looping=1, volume=0.9) + + def exitBattleThree(self): + """Handle exiting the Battle three state """ + self.cleanupBattles() + self.battleOneMusic.stop() + + # No more credit multiplier + localAvatar.inventory.setBattleCreditMultiplier(1) + pass + + def claimOneChair(self): + """Return one item from the self.notDeadList.""" + chairInfo = None + if self.notDeadList: + chairInfo = self.notDeadList.pop() + return chairInfo + + + + ##### PrepareBattleFour state ##### + def enterPrepareBattleFour(self): + """Handle entering the Prepare Battle Four State.""" + assert self.notify.debug('%s.enterPrepareBattleFour()' % (self.doId)) + self.controlToons() + intervalName = "PrepareBattleFourMovie" + seq = Sequence(self.makePrepareBattleFourMovie(), + Func(self.__onToBattleFour), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + base.playMusic(self.phaseFourMusic, looping=1, volume=0.9) + + def exitPrepareBattleFour(self): + """Handle exiting the Prepare Battle Four State.""" + self.clearInterval( "PrepareBattleFourMovie") + self.phaseFourMusic.stop() + pass + + def makePrepareBattleFourMovie(self): + """Create and return the pre battle four movie.""" + rToon = self.resistanceToon + offsetZ = rToon.suit.getHeight() / 2.0 + track = Sequence( + Func(self.__showResistanceToon, True), + Func(rToon.setPos, Point3(0,-5,0)), + Func(rToon.setHpr, Point3(0,0,0)), + Func(camera.reparentTo, rToon), + Func(camera.setPos, Point3(0,13,3 + offsetZ)), + Func(camera.setHpr, Point3(-180,0,0)), + Func(self.banquetDoor.setH, 90), + Func(rToon.setChatAbsolute, TTL.BossbotRTPhase4Speech1, CFSpeech), + Wait(4.0), + Func(rToon.setChatAbsolute, TTL.BossbotRTPhase4Speech2, CFSpeech), + Wait(4.0), + Func(self.__hideResistanceToon), + Func(camera.reparentTo, self), + Func(camera.setPos, Point3(0, -45, 5)), + Func(camera.setHpr, Point3(0, 14, 0)), + Func(self.setChatAbsolute, TTL.BossbotPhase4Speech1, CFSpeech), + Func(self.banquetDoor.setH, 0), + Wait(3.0), + Func(self.setChatAbsolute, TTL.BossbotPhase4Speech2, CFSpeech), + Func(self.bossClub.setScale, 0.01), + Func(self.bossClub.reparentTo, self.rightHandJoint), + LerpScaleInterval(self.bossClub, 3, Point3(1, 1, 1)), + #Wait(3.0), + Func(self.clearChat), + ) + + return track + + def __onToBattleFour(self, elapsedTime=0): + """Tell AI we are done with PrepareBattleFourState.""" + self.doneBarrier('PrepareBattleFour') + + + ##### BattleFour state ##### + def enterBattleFour(self): + """Handle entering the Battle Four State.""" + DistributedBossCog.DistributedBossCog.enterBattleFour(self) + assert self.notify.debug('%s.enterBattleFour()' % (self.doId)) + self.releaseToons(finalBattle = 1) + self.setToonsToNeutral(self.involvedToons) + # remove the disguise, so we can quickly get here riding up the elevator + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.takeOffSuit() + self.bossClub.reparentTo(self.rightHandJoint) + self.generateHealthBar() + self.updateHealthBar() + base.playMusic(self.phaseFourMusic, looping=1, volume=0.9) + + def exitBattleFour(self): + """Handle exiting the Prepare Battle Four State.""" + DistributedBossCog.DistributedBossCog.exitBattleFour(self) + self.phaseFourMusic.stop() + + def d_hitBoss(self, bossDamage): + self.sendUpdate('hitBoss', [bossDamage]) + + def d_ballHitBoss(self, bossDamage): + assert self.notify.debugStateCall(self) + self.sendUpdate('ballHitBoss', [bossDamage]) + + def setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + if bossDamage > self.bossDamage: + delta = bossDamage - self.bossDamage + self.flashRed() + # make the battle harder by not interrupting his attack if he gets hit + # self.doAnimate('hit', now=1) + self.showHpText(-delta, scale = 5) + + self.bossDamage = bossDamage + self.updateHealthBar() + + def setGolfSpot(self, golfSpot, golfSpotIndex): + """Register one of the banquet golfSpots in the room.""" + self.golfSpots[golfSpotIndex] = golfSpot + + ##### Victory state ##### + + def enterVictory(self): + """ + Toons won. Do the CJ lost speech. + """ + self.notify.debug("----- enterVictory") + assert self.notify.debug('enterVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + self.cleanupAttacks() + self.doAnimate('Ff_neutral', now=1) + self.stopMoveTask() + if hasattr(self,'tableIndex'): + table = self.tables[self.tableIndex] + table.tableGroup.hide() + + #self.reparentTo(render) + #self.setPosHpr(*ToontownGlobals.LawbotBossBattleThreePosHpr) + self.loop('neutral') + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + self.clearChat() + self.controlToons() + #don't leave them in the walking state if we take the control away from player + self.setToonsToNeutral(self.involvedToons) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + + # Play the boss's Defense wins animation. + #self.doAnimate('Ff_speech', now = 1) + + intervalName = "VictoryMovie" + + seq = Sequence(self.makeVictoryMovie(), + Func(self.__continueVictory), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + base.playMusic(self.phaseFourMusic, looping=1, volume=0.9) + + def __continueVictory(self): + """ + # Ok, he's gone! We all move to the reward movie. + """ + self.notify.debug("----- __continueVictory") + self.stopAnimate() + self.doneBarrier('Victory') + + def exitVictory(self): + """ + Done with this state, do cleanup + """ + self.notify.debug("----- exitVictory") + self.stopAnimate() + self.unstash() + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + self.phaseFourMusic.stop() + + #self.battleThreeMusicTime = self.battleThreeMusic.getTime() + #self.battleThreeMusic.stop() + + def makeVictoryMovie(self): + """ + Make the victory movie. + """ + self.show() + dustCloud = DustCloud.DustCloud(fBillboard=0, wantSound=1,) + dustCloud.reparentTo(self) + dustCloud.setPos(0, -10, 3) + dustCloud.setScale(4) + dustCloud.wrtReparentTo( self.geom) + dustCloud.createTrack(12) + + newHpr = self.getHpr() + newHpr.setX( newHpr.getX() + 180) + + bossTrack = Sequence( + Func(self.show), + Func(camera.reparentTo, self), + Func(camera.setPos, Point3(0, -35, 25)), + Func(camera.setHpr, Point3(0, -20, 0)), + Func(self.setChatAbsolute, TTL.BossbotRewardSpeech1, CFSpeech), + Wait(3.0), + Func(self.setChatAbsolute, TTL.BossbotRewardSpeech2, CFSpeech), + Wait(2.0), + Func(self.clearChat), + Parallel( + Sequence( + Wait(0.5), + Func(self.demotedCeo.setPos, self.getPos()), + Func(self.demotedCeo.setHpr, newHpr), + Func(self.hide), + Wait(0.5), + Func(self.demotedCeo.reparentTo, self.geom), + Func(self.demotedCeo.unstash), + + ), + Sequence( + dustCloud.track, + ) + + ), + Wait(2.0), + Func(dustCloud.destroy), + ) + return bossTrack + + ##### Reward state ##### + + def enterReward(self): + """ + Show the reward movie, skillups, questsm etc. + """ + assert self.notify.debug('enterReward()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.resistanceToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + + + # The toons are technically free to run around, but localToon + # starts out locked down for the reward movie. + self.controlToons() + + + # Start the reward movie playing. + + panelName = self.uniqueName('reward') + self.rewardPanel = RewardPanel.RewardPanel(panelName) + (victory, camVictory) = MovieToonVictory.doToonVictory( + 1, self.involvedToons, + self.toonRewardIds, + self.toonRewardDicts, + self.deathList, + self.rewardPanel, + allowGroupShot = 0, + uberList = self.uberList) + + ival = Sequence( + Parallel(victory, camVictory), + Func(self.__doneReward)) + + intervalName = "RewardMovie" + delayDeletes = [] + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete(toon, 'BossbotBoss.enterReward')) + + ival.delayDeletes = delayDeletes + ival.start() + self.storeInterval(ival, intervalName) + + base.playMusic(self.betweenPhaseMusic, looping=1, volume=0.9) + + + def __doneReward(self): + """ + We're done with the reward move + """ + self.notify.debug("----- __doneReward") + self.doneBarrier('Reward') + self.toWalkMode() + + def exitReward(self): + """ + Exit this state. do cleanup + """ + self.notify.debug("----- exitReward") + intervalName = "RewardMovie" + self.clearInterval(intervalName) + + self.unstash() + self.rewardPanel.destroy() + del self.rewardPanel + self.betweenPhaseMusic.stop() + + #self.battleThreeMusicTime = 0 + #self.battleThreeMusic.stop() + + ##### Epilogue state ##### + + def enterEpilogue(self): + """ + Enter the epilogue. Resistance toon thanks us. + """ + assert self.notify.debug('enterEpilogue()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.resistanceToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + # The toons are under our control once again. + self.controlToons() + + self.__showResistanceToon(False) + self.resistanceToon.reparentTo(render) + self.resistanceToon.setPosHpr(*ToontownGlobals.BossbotRTEpiloguePosHpr) + self.resistanceToon.loop('Sit') + self.__arrangeToonsAroundResistanceToonForReward() + + camera.reparentTo(render) + camera.setPos(self.resistanceToon, -9, 12, 6) + camera.lookAt(self.resistanceToon, 0, 0, 3) + + intervalName = "EpilogueMovie" + + seq = Sequence(self.makeEpilogueMovie(), + name = intervalName) + + seq.start() + self.storeInterval(seq, intervalName) + + self.accept("doneChatPage", self.__doneEpilogue) + + base.playMusic(self.epilogueMusic, looping=1, volume=0.9) + + def __doneEpilogue(self, elapsedTime = 0): + """ + Done, teleport to safe zone + """ + self.notify.debug("----- __doneEpilogue") + #self.doneBarrier('Epilogue') + + intervalName = "EpilogueMovieToonAnim" + self.clearInterval(intervalName) + track = Parallel( + Sequence(Wait(0.5), + Func(self.localToonToSafeZone))) + self.storeInterval(track, intervalName) + track.start() + + + def exitEpilogue(self): + """ + Done with this state, cleanup + """ + self.notify.debug("----- exitEpilogue") + self.clearInterval("EpilogueMovieToonAnim") + self.unstash() + self.epilogueMusic.stop() + + def makeEpilogueMovie(self): + """ + Make the epilogue movie. + """ + epSpeech = TTLocalizer.BossbotRTCongratulations + + epSpeech = self.__talkAboutPromotion(epSpeech) + + bossTrack = Sequence ( + Func(self.resistanceToon.animFSM.request,'neutral'), + Func(self.resistanceToon.setLocalPageChat,epSpeech, 0) + ) + return bossTrack + + def __talkAboutPromotion(self, speech): + """ + # Extends the congratulations speech to talk about the earned + # promotion, if any. Returns the newly-extended speech. + """ + + # don't say anything about a promotion if they've maxed their cog suit + if self.prevCogSuitLevel < ToontownGlobals.MaxCogSuitLevel: + newCogSuitLevel = localAvatar.getCogLevels()[ + CogDisguiseGlobals.dept2deptIndex(self.style.dept)] + # if this is their last promotion, tell them + if newCogSuitLevel == ToontownGlobals.MaxCogSuitLevel: + speech += TTLocalizer.BossbotRTLastPromotion % (ToontownGlobals.MaxCogSuitLevel+1) + # if they're getting another LP, tell them + if newCogSuitLevel in ToontownGlobals.CogSuitHPLevels: + speech += TTLocalizer.BossbotRTHPBoost + else: + # level XX, wow! Thanks for coming back! + speech += TTLocalizer.BossbotRTMaxed % (ToontownGlobals.MaxCogSuitLevel+1) + + return speech + + + def __arrangeToonsAroundResistanceToonForReward(self): + """ + Arrange the toons around the resistance toon + """ + radius = 7 + numToons = len(self.involvedToons) + center = (numToons - 1) / 2.0 + for i in range(numToons): + toon = self.cr.doId2do.get(self.involvedToons[i]) + if toon: + angle = 90 - 15 * (i - center) + + radians = angle * math.pi / 180.0 + x = math.cos(radians) * radius + y = math.sin(radians) * radius + toon.setPos(self.resistanceToon, x, y, 0) + toon.headsUp(self.resistanceToon) + toon.loop('neutral') + toon.show() + + + def doDirectedAttack(self, avId, attackCode): + """Attack the toon by throwing gears at him with no distance limit.""" + toon = base.cr.doId2do.get(avId) + if toon: + distance = toon.getDistance(self) + #self.notify.debug('distance = %s' % distance) + gearRoot = self.rotateNode.attachNewNode('gearRoot-atk%d' % self.numAttacks) + gearRoot.setZ(10) + gearRoot.setTag('attackCode', str(attackCode)) + gearModel = self.getGearFrisbee() + gearModel.setScale(0.2) + + # First, get just the H value towards the toon. + gearRoot.headsUp(toon) + toToonH = PythonUtil.fitDestAngle2Src(0, gearRoot.getH() + 180) + + # Now pitch towards the toon so we can throw gears at him. + gearRoot.lookAt(toon) + + neutral = 'Fb_neutral' + if not self.twoFaced: + neutral = 'Ff_neutral' + + gearTrack = Parallel() + for i in range(4): + nodeName = '%s-%s' % (str(i), globalClock.getFrameTime()) + node = gearRoot.attachNewNode(nodeName) + node.hide() + node.setPos(0, 5.85, 4.0) + gear = gearModel.instanceTo(node) + x = random.uniform(-5, 5) + z = random.uniform(-3, 3) + h = random.uniform(-720, 720) + if i == 2: + # guarantee one of them to hit if he doesn't move + x = 0 + z = 0 + + def detachNode( node): + if not node.isEmpty(): + node.detachNode() + return Task.done + + def detachNodeLater( node = node): + # we must detach the gear on the next frame to still hit + # the cheating toons + if node.isEmpty(): + return + center = node.node().getBounds().getCenter() + node.node().setBounds(BoundingSphere(center, distance*1.5)) + node.node().setFinal(1) + + self.doMethodLater(0.005, detachNode, + 'detach-%s-%s' % (gearRoot.getName(),node.getName()), + extraArgs = [ node], + ) + gearTrack.append(Sequence( + Wait(i * 0.15), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, distance, z), fluid = 1), + node.hprInterval(1, VBase3(h, 0, 0), fluid = 1)), + Func(detachNodeLater))) + + # A 1-second animation to play while rotating. It might + # be the neutral cycle, or the climb-up cycle. + if not self.raised: + neutral1Anim = self.getAnim('down2Up') + self.raised = 1 + else: + neutral1Anim = ActorInterval(self, neutral, startFrame = 48) + + throwAnim = self.getAnim('throw') + neutral2Anim = ActorInterval(self, neutral) + + extraAnim = Sequence() + if attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + # If it's the "slow" attack, pause for a bit longer + # here to give the player more warning. + extraAnim = ActorInterval(self, neutral) + + def detachGearRoot( task, gearRoot = gearRoot): + if not gearRoot.isEmpty(): + gearRoot.detachNode() + return task.done + + def detachGearRootLater( gearRoot = gearRoot): + # we must detach the gear root on the next frame to still hit + # the cheating toons + if gearRoot.isEmpty(): + return + self.doMethodLater(0.01, detachGearRoot, + 'detach-%s' % gearRoot.getName(), + ) + + seq = Sequence( + ParallelEndTogether(self.pelvis.hprInterval(1, VBase3(toToonH, 0, 0)), + neutral1Anim), + extraAnim, + Parallel(Sequence(Wait(0.19), + gearTrack, + Func(detachGearRootLater), + self.pelvis.hprInterval(0.2, VBase3(0, 0, 0))), + Sequence(throwAnim, neutral2Anim))) + + self.doAnimate(seq, now = 1, raised = 1) + + def setBattleDifficulty(self, diff): + """ + We got the battle difficulty from the AI + """ + self.notify.debug('battleDifficulty = %d' % diff) + self.battleDifficulty = diff + + def doMoveAttack(self, tableIndex): + """Attack a toon by flattening a table.""" + self.tableIndex = tableIndex + table = self.tables[tableIndex] + fromPos = self.getPos() + fromHpr = self.getHpr() + toPos = table.getPos() + foo = render.attachNewNode('foo') + foo.setPos(self.getPos()) + foo.setHpr(self.getHpr()) + foo.lookAt(table.getLocator()) + toHpr = foo.getHpr() + toHpr.setX( toHpr.getX() - 180) # heading of zero faces towards -y axis + foo.removeNode() + reverse = False + moveTrack, hpr = self.moveBossToPoint(fromPos, fromHpr, toPos, toHpr, reverse) + self.moveTrack = moveTrack + self.moveTrack.start() + self.storeInterval(self.moveTrack, 'moveTrack') + + + def interruptMove(self): + """Stop the CEO from moving.""" + if self.moveTrack and self.moveTrack.isPlaying(): + self.moveTrack.pause() + self.stopMoveTask() + + def setAttackCode(self, attackCode, avId = 0): + """Do the indicated attack, on the given toon.""" + # This message is sent from the AI to tell the Boss Cog to + # animate some attack. + assert self.notify.debug("setAttackCode(%s, %s) time=%f" % + (attackCode, avId, globalClock.getFrameTime())) + if self.state != 'BattleFour': + return + self.numAttacks += 1 + self.notify.debug('numAttacks=%d' % self.numAttacks) + self.attackCode = attackCode + self.attackAvId = avId + if attackCode == ToontownGlobals.BossCogMoveAttack: + # interruptMove stops CEO from moving sideways + self.interruptMove() + self.doMoveAttack(avId) + elif attackCode == ToontownGlobals.BossCogGolfAttack: + self.interruptMove() + self.cleanupAttacks() + self.doGolfAttack(avId, attackCode) + elif attackCode == ToontownGlobals.BossCogDizzy: + self.setDizzy(1) + self.cleanupAttacks() + self.doAnimate(None, raised = 0, happy = 1) + + elif attackCode == ToontownGlobals.BossCogDizzyNow: + self.setDizzy(1) + self.cleanupAttacks() + self.doAnimate('hit', happy = 1, now = 1) + + elif attackCode == ToontownGlobals.BossCogSwatLeft: + self.setDizzy(0) + self.doAnimate('ltSwing', now = 1) + + elif attackCode == ToontownGlobals.BossCogSwatRight: + self.setDizzy(0) + self.doAnimate('rtSwing', now = 1) + + elif attackCode == ToontownGlobals.BossCogAreaAttack: + self.setDizzy(0) + self.doAnimate('areaAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogFrontAttack: + self.setDizzy(0) + self.doAnimate('frontAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogRecoverDizzyAttack: + self.setDizzy(0) + self.doAnimate('frontAttack', now = 1) + + elif attackCode == ToontownGlobals.BossCogDirectedAttack or \ + attackCode == ToontownGlobals.BossCogSlowDirectedAttack or \ + attackCode == ToontownGlobals.BossCogGearDirectedAttack: + # Rotate to face the toon and spit out gears. + self.interruptMove() + self.setDizzy(0) + self.doDirectedAttack(avId, attackCode) + + elif attackCode == ToontownGlobals.BossCogGolfAreaAttack: + self.interruptMove() + self.setDizzy(0) + self.doGolfAreaAttack() + + elif attackCode == ToontownGlobals.BossCogNoAttack: + # Just stand up. + self.setDizzy(0) + self.doAnimate(None, raised = 1) + elif attackCode == ToontownGlobals.BossCogOvertimeAttack: + self.interruptMove() + self.setDizzy(0) + self.cleanupAttacks() + self.doOvertimeAttack(avId) + + + def signalAtTable(self): + """Tell the ai we've reached the table we were moving to.""" + self.sendUpdate('reachedTable',[self.tableIndex]) + + def closeEnter(self, colEntry): + """Handle the CEO we've hit something, probably a table.""" + tableStr = colEntry.getIntoNodePath().getNetTag('tableIndex') + if tableStr: + tableIndex = int(tableStr) + # Note we could hit a different table than the one were were going to. + self.sendUpdate('hitTable', [tableIndex]) + + def closeExit(self, colEntry): + """Handle the CEO we've hit stopped something, probably a table.""" + #import pdb; pdb.set_trace() + tableStr = colEntry.getIntoNodePath().getNetTag('tableIndex') + if tableStr: + tableIndex = int(tableStr) + # Note we could hit a different table than the one were were going to. + # do not generate an awayFromTable if we are headed to that table + if self.tableIndex != tableIndex: + self.sendUpdate('awayFromTable', [tableIndex]) + + def setSpeedDamage(self, speedDamage, recoverRate, timestamp): + """Handle the AI telling us the current speed damage.""" + recoverStartTime = globalClockDelta.networkToLocalTime(timestamp) + self.speedDamage = speedDamage + self.speedRecoverRate = recoverRate + self.speedRecoverStartTime = recoverStartTime + + speedFraction = max( 1 - ( speedDamage/ self.maxSpeedDamage), 0) + self.treads.setColorScale( 1, speedFraction, speedFraction, 1) + + taskName = "RecoverSpeedDamage" + taskMgr.remove(taskName) + + if self.speedRecoverRate: + taskMgr.add(self.__recoverSpeedDamage, taskName) + + + def getSpeedDamage(self): + """Return the speed damage, taking into account the recover rate.""" + now = globalClock.getFrameTime() + elapsed = now - self.speedRecoverStartTime + + # Although the AI side computes and transmits getSpeedDamage() + # as an integer value, on the client side we return it as a + # floating-point value, so we can get the smooth transition + # effect as the speed slowly starts to roll back up. + return max(self.speedDamage - self.speedRecoverRate * elapsed /60.0 ,0 ) + + def getFractionalSpeedDamage(self): + """Return the current speed damage as a fraction between 0 and 1.""" + result = self.getSpeedDamage() / self.maxSpeedDamage + return result + + def __recoverSpeedDamage(self, task): + """Make the speed damage indicator match our current speed damage.""" + speedDamage = self.getSpeedDamage() + speedFraction = max( 1 - ( speedDamage/ self.maxSpeedDamage), 0) + self.treads.setColorScale( 1, speedFraction, speedFraction, 1) + return task.cont + + def moveBossToPoint(self, fromPos, fromHpr, toPos, toHpr, reverse): + """Return an interval and Hpr that moves the CEO to a point.""" + assert self.notify.debugStateCall(self) + vector = Vec3(toPos - fromPos) + distance = vector.length() + self.distanceToTravel = distance + self.notify.debug("self.distanceToTravel = %s" % self.distanceToTravel) + + if toHpr == None: + # Compute the destination hpr. + mat = Mat3(0, 0, 0, 0, 0, 0, 0, 0, 0) + headsUp(mat, vector, CSDefault) + scale = VBase3(0, 0, 0) + shear = VBase3(0, 0, 0) + toHpr = VBase3(0, 0, 0) + decomposeMatrix(mat, scale, shear, toHpr, CSDefault) + + if fromHpr: + # Fit the toHpr to the same semicircle as the supplied + # fromHpr. + newH = PythonUtil.fitDestAngle2Src(fromHpr[0], toHpr[0]) + toHpr = VBase3(newH, 0, 0) + + else: + # If no fromHpr is given, it's the same as toHpr. + fromHpr = toHpr + + turnTime = abs(toHpr[0] - fromHpr[0]) / self.getCurTurnSpeed() + + if toHpr[0] < fromHpr[0]: + leftRate = ToontownGlobals.BossCogTreadSpeed + else: + leftRate = -ToontownGlobals.BossCogTreadSpeed + if reverse: + rollTreadRate = -ToontownGlobals.BossCogTreadSpeed + else: + rollTreadRate = ToontownGlobals.BossCogTreadSpeed + + rollTime = distance / ToontownGlobals.BossCogRollSpeed + deltaPos = toPos - fromPos + self.toPos = toPos + self.fromPos = fromPos + self.dirVector = self.toPos - self.fromPos + self.dirVector.normalize() + track = Sequence(Func(self.setPos, fromPos), + Func(self.headsUp, toPos), + Parallel(self.hprInterval(turnTime, toHpr, fromHpr), + self.rollLeftTreads(turnTime, leftRate), + self.rollRightTreads(turnTime, -leftRate), + #SoundInterval(self.treadsSfx, duration = turnTime, loop = 1, volume = 0.2), + ), + Func(self.startMoveTask), + ) + + # Return both the computed track and the destination hpr + # (which might be useful for the next sequence). + return track, toHpr + + + def getCurTurnSpeed(self): + """Return the turn speed, taking into account the current speed damage.""" + result = ToontownGlobals.BossbotTurnSpeedMax - \ + ((ToontownGlobals.BossbotTurnSpeedMax - ToontownGlobals.BossbotTurnSpeedMin) * + self.getFractionalSpeedDamage()) + return result + + def getCurRollSpeed(self): + """Return the roll speed, taking into account the current speed damage.""" + result = ToontownGlobals.BossbotRollSpeedMax - \ + ((ToontownGlobals.BossbotRollSpeedMax - ToontownGlobals.BossbotRollSpeedMin) * + self.getFractionalSpeedDamage()) + return result + + def getCurTreadSpeed(self): + """Return the tread speed, taking into account the current speed damage.""" + result = ToontownGlobals.BossbotTreadSpeedMax - \ + ((ToontownGlobals.BossbotTreadSpeedMax - ToontownGlobals.BossbotTreadSpeedMin) * + self.getFractionalSpeedDamage()) + return result + + def startMoveTask(self): + """Start the incremental move boss task.""" + taskMgr.add(self.moveBossTask, self.moveBossTaskName) + + def stopMoveTask(self): + """Stop the incremental move boss task.""" + taskMgr.remove( self.moveBossTaskName) + + def moveBossTask(self, task): + """Incrementally move the boss for this frame.""" + dt = globalClock.getDt() + # check if we've arrived + distanceTravelledThisFrame = dt * self.getCurRollSpeed() + diff = self.toPos - self.getPos() + distanceLeft = diff.length() + + def rollTexMatrix(t, object = object): + object.setTexOffset(TextureStage.getDefault(), t, 0) + + self.treadsLeftPos += dt * self.getCurTreadSpeed() + self.treadsRightPos += dt * self.getCurTreadSpeed() + rollTexMatrix( self.treadsLeftPos , self.treadsLeft) + rollTexMatrix( self.treadsRightPos , self.treadsRight) + + if distanceTravelledThisFrame >= distanceLeft: + self.setPos(self.toPos) + self.signalAtTable() + return Task.done + else: + newPos = self.getPos() + self.dirVector * dt * self.getCurRollSpeed() + self.setPos(newPos) + return Task.cont + + + def doZapToon(self, toon, pos = None, hpr = None, ts = 0, + fling = 1, shake = 1): + # The indicated distributed toon has come into contact with + # something that hurts him. Play a little movie showing him + # getting zapped. + + zapName = toon.uniqueName('zap') + self.clearInterval(zapName) + + zapTrack = Sequence(name = zapName) + + if toon == localAvatar: + # In the case of localToon, set the state to ouch + # immediately, rather than waiting a frame or two for the + # interval to get there. + self.toOuchMode() + messenger.send('interrupt-pie') + + # But also set up an active collision sphere, similar to + # the one we have in walk mode, just so the local toon + # won't get pushed through a wall by our lerp, below. + self.enableLocalToonSimpleCollisions() + else: + zapTrack.append(Func(toon.stopSmooth)) + + def getSlideToPos(toon = toon): + return render.getRelativePoint(toon, Point3(0, -5, 0)) + + if pos != None and hpr != None: + zapTrack.append(Func(toon.setPosHpr, pos, hpr)), + + toonTrack = Parallel() + + if shake and toon == localAvatar: + toonTrack.append(Sequence(Func(camera.setZ, camera, 1), + Wait(0.15), + Func(camera.setZ, camera, -2), + Wait(0.15), + Func(camera.setZ, camera, 1), + )) + + if fling: + if self.isToonRoaming(toon.doId): + toonTrack += [ActorInterval(toon, 'slip-backward'),] + toonTrack += [toon.posInterval(0.5, getSlideToPos, fluid = 1)] + else: + toonTrack += [ActorInterval(toon, 'slip-forward')] + + zapTrack.append(toonTrack) + + if toon == localAvatar: + zapTrack.append(Func(self.disableLocalToonSimpleCollisions)) + currentState = self.state + if currentState in ( 'BattleFour', 'BattleTwo'): + zapTrack.append(Func(self.toFinalBattleMode)) + else: + self.notify.warning('doZapToon going to walkMode, how did this happen?') + zapTrack.append(Func(self.toWalkMode)) + else: + zapTrack.append(Func(toon.startSmooth)) + + if (ts > 0): + # We're already late. + startTime = ts + else: + # We need to wait a bit. + zapTrack = Sequence(Wait(-ts), zapTrack) + startTime = 0 + + zapTrack.append(Func(self.clearInterval, zapName)) + + zapTrack.delayDelete = DelayDelete.DelayDelete(toon, 'BossbotBoss.doZapToon') + zapTrack.start(startTime) + self.storeInterval(zapTrack, zapName) + + + def zapLocalToon(self, attackCode, origin = None): + assert self.notify.debugStateCall(self) + if self.localToonIsSafe or localAvatar.ghostMode or localAvatar.isStunned: + return + if globalClock.getFrameTime() < self.lastZapLocalTime + 1.0: + return + else: + self.lastZapLocalTime = globalClock.getFrameTime() + self.notify.debug('zapLocalToon frameTime=%s' % globalClock.getFrameTime()) + messenger.send('interrupt-pie') + + place = self.cr.playGame.getPlace() + currentState = None + if place: + currentState = place.fsm.getCurrentState().getName() + if currentState != 'walk' and currentState != 'finalBattle' and currentState != 'crane': + # Ignore this except when the Toon's in walk mode. + return + self.notify.debug('continuing zap') + + toon = localAvatar + + fling = 1 + shake = 0 + if attackCode == ToontownGlobals.BossCogAreaAttack: + # For an area attack, we don't fling or move the toons, + # but we do shake the camera. + fling = 0 + shake = 1 + + if fling: + if origin == None: + origin = self + + if self.isToonRoaming(toon.doId): + # Face the toon towards the boss's center (so he can get + # knocked backwards), but keep the camera in the same place. + camera.wrtReparentTo(render) + toon.headsUp(origin) + camera.wrtReparentTo(toon) + + # Also tell the boss where we are relative to him, so he can + # decide whether to swat at us. + bossRelativePos = toon.getPos(self.getGeomNode()) + bp2d = Vec2(bossRelativePos[0], bossRelativePos[1]) + bp2d.normalize() + + pos = toon.getPos() + hpr = toon.getHpr() + timestamp = globalClockDelta.getFrameNetworkTime() + + self.sendUpdate('zapToon', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2], + bp2d[0], bp2d[1], + attackCode, timestamp]) + + self.doZapToon(toon, fling = fling, shake = shake) + + def getToonTableIndex(self, toonId): + """Returns the table index he is on, -1 if he's not on a table""" + tableIndex = -1 + for table in self.tables.values(): + if table.avId == toonId: + tableIndex = table.index + break + return tableIndex + + def getToonGolfSpotIndex(self, toonId): + """Returns the golfSpot index he is on, -1 if he's not on a golfSpot""" + golfSpotIndex = -1 + for golfSpot in self.golfSpots.values(): + if golfSpot.avId == toonId: + golfSpotIndex = golfSpot.index + break + return golfSpotIndex + + def isToonOnTable(self, toonId): + """Returns True if the toon is on a table.""" + result = self.getToonTableIndex(toonId) != -1 + return result + + def isToonOnGolfSpot(self, toonId): + """Returns True if the toon is on a golf spot.""" + result = self.getToonGolfSpotIndex(toonId) != -1 + return result + + def isToonRoaming(self, toonId): + result = not self.isToonOnTable(toonId) and not self.isToonOnGolfSpot(toonId) + return result + + def getGolfBall(self): + """Return a cog golf ball model.""" + golfRoot = NodePath('golfRoot') + golfBall = loader.loadModel('phase_6/models/golf/golf_ball') + golfBall.setColorScale(0.75, 0.75, 0.75, 0.5) + golfBall.setTransparency(1) + ballScale = 5 + golfBall.setScale(ballScale) + golfBall.reparentTo(golfRoot) + # we need to create a collision sphere called BossZap + # the golf ball has a radius of 0.25 if it has a scale of 1 + cs = CollisionSphere(0, 0, 0, ballScale * 0.25) + cs.setTangible(0) + cn = CollisionNode('BossZap') + cn.addSolid(cs) + cn.setIntoCollideMask(ToontownGlobals.WallBitmask) + cnp = golfRoot.attachNewNode(cn) + return golfRoot + + def doGolfAttack(self, avId, attackCode): + toon = base.cr.doId2do.get(avId) + if toon: + distance = toon.getDistance(self) + self.notify.debug('distance = %s' % distance) + gearRoot = self.rotateNode.attachNewNode('gearRoot-atk%d' % self.numAttacks) + gearRoot.setZ(10) + gearRoot.setTag('attackCode', str(attackCode)) + gearModel = self.getGolfBall() + #gearModel.setScale(0.2) + + self.ballLaunch = NodePath('') #gearRoot.attachNewNode('ballLaunch') + self.ballLaunch.reparentTo(gearRoot) + self.ballLaunch.setPos(self.BallLaunchOffset) + #axis = loader.loadModel('models/misc/xyzAxis') + #if axis and not axis.isEmpty(): + # axis.setScale(1) + # axis.reparentTo(self.ballLaunch) + + # First, get just the H value towards the toon. + gearRoot.headsUp(toon) + toToonH = PythonUtil.fitDestAngle2Src(0, gearRoot.getH() + 180) + + #self.notify.debug('toToonH = %s' % toToonH) + + # Now pitch towards the toon so we can throw gears at him. + gearRoot.lookAt(toon) + + neutral = 'Fb_neutral' + if not self.twoFaced: + neutral = 'Ff_neutral' + + gearTrack = Parallel() + for i in range(5): + nodeName = '%s-%s' % (str(i), globalClock.getFrameTime()) + node = gearRoot.attachNewNode(nodeName) + node.hide() + #node.setPos(0, 5.85, 4.0) + node.reparentTo(self.ballLaunch) + node.wrtReparentTo(gearRoot) + #node.show() + distance = toon.getDistance(node) + gear = gearModel.instanceTo(node) + x = random.uniform(-5, 5) + z = random.uniform(-3, 3) + p = random.uniform(-720, -90) + y = distance + random.uniform(5, 15) + if i == 2: + # guarantee one of them to hit if he doesn't move + x = 0 + z = 0 + y = distance + 10 + + def detachNode( node): + if not node.isEmpty(): + node.detachNode() + return Task.done + + def detachNodeLater( node = node): + # we must detach the gear on the next frame to still hit + # the cheating toons + if node.isEmpty(): + return + node.node().setBounds(BoundingSphere(Point3(0,0,0), distance*1.5)) + node.node().setFinal(1) + self.doMethodLater(0.005, detachNode, + 'detach-%s-%s' % (gearRoot.getName(),node.getName()), + extraArgs = [ node], + ) + + + gearTrack.append(Sequence( + Wait( 26.0 / 24.), + Wait(i * 0.15), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, y, z), fluid = 1), + node.hprInterval(1, VBase3(0, p, 0), fluid = 1)), + Func(detachNodeLater))) + + # A 1-second animation to play while rotating. It might + # be the neutral cycle, or the climb-up cycle. + if not self.raised: + neutral1Anim = self.getAnim('down2Up') + self.raised = 1 + else: + neutral1Anim = ActorInterval(self, neutral, startFrame = 48) + + throwAnim = self.getAnim('golf_swing') + # the golf swing has a duration of 2 seconds + neutral2Anim = ActorInterval(self, neutral) + + extraAnim = Sequence() + if attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + # If it's the "slow" attack, pause for a bit longer + # here to give the player more warning. + extraAnim = ActorInterval(self, neutral) + + def detachGearRoot( task, gearRoot = gearRoot): + if not gearRoot.isEmpty(): + gearRoot.detachNode() + return task.done + + def detachGearRootLater( gearRoot = gearRoot): + # we must detach the gear root on the next frame to still hit + # the cheating toons + self.doMethodLater(0.01, detachGearRoot, + 'detach-%s' % gearRoot.getName(), + ) + + seq = Sequence( + ParallelEndTogether(self.pelvis.hprInterval(1, VBase3(toToonH, 0, 0)), + neutral1Anim), + extraAnim, + Parallel(Sequence(Wait(0.19), + gearTrack, + Func(detachGearRootLater), + self.pelvis.hprInterval(0.2, VBase3(0, 0, 0))), + Sequence(throwAnim, neutral2Anim), + Sequence(Wait(0.85), + SoundInterval(self.swingClubSfx, node=self, + duration = 0.45, + cutOff = 300, + listenerNode = base.localAvatar), + ) + ) + ) + + + self.doAnimate(seq, now = 1, raised = 1) + + + def doGolfAreaAttack(self): + toons = [] + for toonId in self.involvedToons: + toon = base.cr.doId2do.get(toonId) + if toon: + toons.append(toon) + if not toons: + return + + neutral = 'Fb_neutral' + if not self.twoFaced: + neutral = 'Ff_neutral' + + # A 1-second animation to play while rotating. It might + # be the neutral cycle, or the climb-up cycle. + if not self.raised: + neutral1Anim = self.getAnim('down2Up') + self.raised = 1 + else: + neutral1Anim = ActorInterval(self, neutral, startFrame = 48) + + throwAnim = self.getAnim('golf_swing') + # the golf swing has a duration of 2 seconds + neutral2Anim = ActorInterval(self, neutral) + + extraAnim = Sequence() + if False: + # If it's the "slow" attack, pause for a bit longer + # here to give the player more warning. + extraAnim = ActorInterval(self, neutral) + + + gearModel = self.getGolfBall() + + # First, get just the H value towards the toon. + toToonH = self.rotateNode.getH() + 360 + self.notify.debug('toToonH = %s' % toToonH) + + gearRoots = [] + allGearTracks = Parallel() + for toon in toons: + gearRoot = self.rotateNode.attachNewNode('gearRoot-atk%d-%d' % + (self.numAttacks, toons.index(toon))) + gearRoot.setZ(10) + gearRoot.setTag('attackCode', str(ToontownGlobals.BossCogGolfAreaAttack)) + gearRoot.lookAt(toon) + + ballLaunch = NodePath('') #gearRoot.attachNewNode('ballLaunch') + ballLaunch.reparentTo(gearRoot) + ballLaunch.setPos(self.BallLaunchOffset) + + + gearTrack = Parallel() + for i in range(5): + nodeName = '%s-%s' % (str(i), globalClock.getFrameTime()) + node = gearRoot.attachNewNode(nodeName ) + node.hide() + #node.setPos(0, 5.85, 4.0) + node.reparentTo(ballLaunch) + node.wrtReparentTo(gearRoot) + + distance = toon.getDistance(node) + toonPos = toon.getPos(render) + nodePos = node.getPos(render) + vector = toonPos-nodePos + #self.notify.debug('toonPos=%s nodePos=%s length=%s' % (toonPos, nodePos, vector.length())) + #self.notify.debug('distance = %s' % distance) + + #node.show() + gear = gearModel.instanceTo(node) + x = random.uniform(-5, 5) + z = random.uniform(-3, 3) + p = random.uniform(-720, -90) + y = distance + random.uniform(5,15) + if i == 2: + # guarantee one of them to hit if he doesn't move + x = 0 + z = 0 + y = distance +10 + + def detachNode( node): + if not node.isEmpty(): + node.detachNode() + return Task.done + + def detachNodeLater( node = node): + # we must detach the gear on the next frame to still hit + # the cheating toons + if node.isEmpty(): + return + node.node().setBounds(BoundingSphere(Point3(0,0,0), distance*1.5)) + node.node().setFinal(1) + self.doMethodLater(0.005, detachNode, + 'detach-%s-%s' % (gearRoot.getName(),node.getName()), + extraArgs = [ node], + ) + + gearTrack.append(Sequence( + Wait( 26.0 / 24.), + Wait(i * 0.15), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, y, z), fluid = 1), + node.hprInterval(1, VBase3(0, p, 0), fluid = 1)), + Func(detachNodeLater))) + allGearTracks.append(gearTrack) + + def detachGearRoots(gearRoots = gearRoots): + for gearRoot in gearRoots: + def detachGearRoot( task, gearRoot = gearRoot): + if not gearRoot.isEmpty(): + gearRoot.detachNode() + return task.done + if gearRoot.isEmpty(): + continue + self.doMethodLater(0.01, detachGearRoot, + 'detach-%s' % gearRoot.getName(), + ) + gearRoots = [] + + rotateFire = Parallel( + self.pelvis.hprInterval(2, VBase3(toToonH + 1440, 0, 0)), + allGearTracks + ) + + seq = Sequence( + Func(base.playSfx, self.warningSfx), + Func(self.saySomething, TTLocalizer.GolfAreaAttackTaunt), + ParallelEndTogether(self.pelvis.hprInterval(2, VBase3(toToonH, 0, 0)), + neutral1Anim), + extraAnim, + Parallel(Sequence(#Wait(0.19), + rotateFire, + Func(detachGearRoots), + #self.pelvis.hprInterval(0.2, VBase3(0, 0, 0)) + Func(self.pelvis.setHpr, VBase3(0,0,0)) + ), + Sequence( throwAnim, neutral2Anim), + Sequence(Wait(0.85), + SoundInterval(self.swingClubSfx, node=self, + duration = 0.45, + cutOff = 300, + listenerNode = base.localAvatar), + ), + )) + self.doAnimate(seq, now = 1, raised = 1) + + def saySomething(self, chatString): + """ + Make the CEO say something + """ + intervalName = "CEOTaunt" + seq = Sequence( name = intervalName) + seq.append(Func(self.setChatAbsolute, chatString, CFSpeech)) + seq.append( Wait(4.0) ) + seq.append(Func(self.clearChat)) + oldSeq = self.activeIntervals.get(intervalName) + if oldSeq: + oldSeq.finish() + seq.start() + self.activeIntervals[intervalName] = seq + + def d_hitToon(self, toonId): + """Tell the AI the local client healed a toon.""" + self.notify.debug("----- d_hitToon") + self.sendUpdate('hitToon', [toonId]) + + def toonGotHealed(self, toonId): + """ + A toon got healed, play a sound effect + """ + toon = base.cr.doId2do.get(toonId) + if toon: + base.playSfx(self.toonUpSfx, node = toon) + + + def localToonTouchedBeltToonup(self, beltIndex, toonupIndex, toonupNum): + """Handle the local toon touching toonup on the conveyer belt.""" + avId = base.localAvatar.doId + doRequest = True + if doRequest: + self.sendUpdate('requestGetToonup', [beltIndex, toonupIndex, toonupNum]) + + def toonGotToonup(self, avId, beltIndex, toonupIndex, toonupNum): + """Hande the AI granting a get toonup request to a toon.""" + if self.belts[beltIndex]: + self.belts[beltIndex].removeToonup(toonupIndex) + toon = base.cr.doId2do.get(avId) + if toon: + base.playSfx(self.toonUpSfx, node = toon) + + def doOvertimeAttack(self, index): + """Make him shoot one food belt to stop it, and pulse his golf club.""" + attackCode = ToontownGlobals.BossCogOvertimeAttack + attackBelts = Sequence() + if index < len(self.belts): + # avId is actually an index + belt = self.belts[index] + self.saySomething(TTLocalizer.OvertimeAttackTaunts[index]) + if index: + self.bossClubIntervals[0].finish() + self.bossClubIntervals[1].loop() + else: + self.bossClubIntervals[1].finish() + self.bossClubIntervals[0].loop() + distance = belt.beltModel.getDistance(self) + gearRoot = self.rotateNode.attachNewNode('gearRoot') + gearRoot.setZ(10) + gearRoot.setTag('attackCode', str(attackCode)) + gearModel = self.getGearFrisbee() + gearModel.setScale(0.2) + + # First, get just the H value towards the toon. + gearRoot.headsUp(belt.beltModel) + toToonH = PythonUtil.fitDestAngle2Src(0, gearRoot.getH() + 180) + + # Now pitch towards the toon so we can throw gears at him. + gearRoot.lookAt(belt.beltModel) + + neutral = 'Fb_neutral' + if not self.twoFaced: + neutral = 'Ff_neutral' + + gearTrack = Parallel() + for i in range(4): + node = gearRoot.attachNewNode(str(i)) + node.hide() + node.setPos(0, 5.85, 4.0) + gear = gearModel.instanceTo(node) + x = random.uniform(-5, 5) + z = random.uniform(-3, 3) + h = random.uniform(-720, 720) + gearTrack.append(Sequence( + Wait(i * 0.15), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, distance, z), fluid = 1), + node.hprInterval(1, VBase3(h, 0, 0), fluid = 1)), + Func(node.detachNode))) + + # A 1-second animation to play while rotating. It might + # be the neutral cycle, or the climb-up cycle. + if not self.raised: + neutral1Anim = self.getAnim('down2Up') + self.raised = 1 + else: + neutral1Anim = ActorInterval(self, neutral, startFrame = 48) + + throwAnim = self.getAnim('throw') + neutral2Anim = ActorInterval(self, neutral) + + extraAnim = Sequence() + if attackCode == ToontownGlobals.BossCogSlowDirectedAttack: + # If it's the "slow" attack, pause for a bit longer + # here to give the player more warning. + extraAnim = ActorInterval(self, neutral) + + seq = Sequence( + ParallelEndTogether(self.pelvis.hprInterval(1, VBase3(toToonH, 0, 0)), + neutral1Anim), + extraAnim, + Parallel(Sequence(Wait(0.19), + gearTrack, + Func(gearRoot.detachNode), + Func(self.explodeSfx.play), + self.pelvis.hprInterval(0.2, VBase3(0, 0, 0))), + Sequence(throwAnim, neutral2Anim)), + Func(belt.request,'Inactive') + ) + attackBelts.append(seq) + + self.notify.debug('attackBelts duration= %.2f' % attackBelts.getDuration()) + self.doAnimate(attackBelts, now = 1, raised = 1) diff --git a/toontown/src/suit/DistributedBossbotBossAI.py b/toontown/src/suit/DistributedBossbotBossAI.py new file mode 100644 index 0000000..3baf4ba --- /dev/null +++ b/toontown/src/suit/DistributedBossbotBossAI.py @@ -0,0 +1,1292 @@ +import random +import math +from pandac.PandaModules import Point3 +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import FSM +from direct.interval.IntervalGlobal import LerpPosInterval +from toontown.coghq import DistributedFoodBeltAI +from toontown.coghq import DistributedBanquetTableAI +from toontown.coghq import DistributedGolfSpotAI +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.suit import DistributedBossCogAI +from toontown.suit import DistributedSuitAI +from toontown.suit import SuitDNA +from toontown.building import SuitBuildingGlobals +from toontown.battle import DistributedBattleWaitersAI +from toontown.battle import DistributedBattleDinersAI +from toontown.battle import BattleExperienceAI +from direct.distributed.ClockDelta import globalClockDelta + +class DistributedBossbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBossbotBossAI') + + #the cap we use for the toon level difficulty + maxToonLevels = 77 + toonUpLevels = [1,2,3,4] + + def __init__(self, air): + DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'c') + FSM.FSM.__init__(self, 'DistributedBossbotBossAI') + self.battleOneBattlesMade = False + self.battleThreeBattlesMade = False + self.battleFourSetup = False + self.foodBelts = [] + self.numTables =1 + self.numDinersPerTable = 3 + self.tables = [] + self.numGolfSpots =4 + self.golfSpots = [] + # keep track if the toons are carrying food or not + self.toonFoodStatus = {} + self.bossMaxDamage = ToontownGlobals.BossbotBossMaxDamage + self.threatDict = {} + self.keyStates.append('BattleFour') + self.battleFourStart = 0 + self.battleDifficulty = 0 #how difficult is battle four + + self.movingToTable = False + self.tableDest = -1 + self.curTable = -1 + + self.speedDamage = 0 + self.maxSpeedDamage = ToontownGlobals.BossbotMaxSpeedDamage + self.speedRecoverRate = ToontownGlobals.BossbotSpeedRecoverRate + self.speedRecoverStartTime = 0 + + self.battleFourTimeStarted = 0 + self.numDinersExploded = 0 + + # for tracking purposes + self.numMoveAttacks = 0 + self.numGolfAttacks = 0 + self.numGearAttacks = 0 + self.numGolfAreaAttacks = 0 + self.numToonupGranted = 0 + self.totalLaffHealed = 0 + + self.toonupsGranted = [] + + self.doneOvertimeOneAttack = False + self.doneOvertimeTwoAttack = False + + # what time will he destroy one food belt + self.overtimeOneTime = simbase.air.config.GetInt('overtime-one-time',1200) + # what time will he destroy the second food belt + self.battleFourDuration = simbase.air.config.GetInt('battle-four-duration',1800) + self.overtimeOneStart = float(self.overtimeOneTime) / self.battleFourDuration + + self.moveAttackAllowed = True + + def delete(self): + self.notify.debug('DistributedBossbotBossAI.delete') + self.deleteBanquetTables() + self.deleteFoodBelts() + self.deleteGolfSpots() + return DistributedBossCogAI.DistributedBossCogAI.delete(self) + + def enterElevator(self): + """Handle entering the elevator state.""" + DistributedBossCogAI.DistributedBossCogAI.enterElevator(self) + # we must make the battle now, since the suits will be immediately seen + self.makeBattleOneBattles() + + def enterIntroduction(self): + """Handle enterint the introduction state. + Copied and pasted to just avoid the call to reset battles. + """ + # Also get the battle objects ready. + self.arenaSide = None + self.makeBattleOneBattles() + + # The clients will play a cutscene. When they are done + # watching the movie, we continue. + + self.barrier = self.beginBarrier( + "Introduction", self.involvedToons, 45, + self.doneIntroduction) + + def makeBattleOneBattles(self): + """Create the DistributedBattleWaiters.""" + if not self.battleOneBattlesMade: + self.postBattleState = 'PrepareBattleTwo' + self.initializeBattles(1, ToontownGlobals.BossbotBossBattleOnePosHpr) + self.battleOneBattlesMade = True + + def getHoodId(self): + """Return our canonical hood id.""" + #return ToontownGlobals.BossbotHQ + return ToontownGlobals.LawbotHQ + + def generateSuits(self, battleNumber): + """Create and generate the suits for a battle phase.""" + if battleNumber == 1: + # Battle 1 + # building difficulty 14, first battle with Bossbot Boss. These + # are normal cogs. + weakenedValue = ( ( 1, 1 ), + ( 2, 2 ), + ( 2, 2 ), + ( 1, 1 ), + ( 1, 1, 1, 1, 1 ) ) + listVersion = list(SuitBuildingGlobals.SuitBuildingInfo) + + if simbase.config.GetBool('bossbot-boss-cheat',0): + listVersion[14] = weakenedValue + SuitBuildingGlobals.SuitBuildingInfo = tuple(listVersion) + retval = self.invokeSuitPlanner(14, 0) + return retval; + else: + suits = self.generateDinerSuits() + return suits + + def invokeSuitPlanner(self, buildingCode, skelecog): + "Call the base clase suit planner, but force 4 active suits at start.""" + suits = DistributedBossCogAI.\ + DistributedBossCogAI.invokeSuitPlanner(self, + buildingCode, + skelecog) + activeSuits = suits['activeSuits'][:] + reserveSuits = suits['reserveSuits'][:] + if len(activeSuits) + len(reserveSuits) >= 4: + while len(activeSuits) < 4: + activeSuits.append( reserveSuits.pop()[0]) + retval = { 'activeSuits': activeSuits, + 'reserveSuits': reserveSuits + } + return retval + + def makeBattle(self, bossCogPosHpr, battlePosHpr, + roundCallback, finishCallback, battleNumber, battleSide): + """Create and generate one DistributedBattleWaiters.""" + if battleNumber == 1: + battle = DistributedBattleWaitersAI.DistributedBattleWaitersAI( + self.air, self, roundCallback, finishCallback, battleSide) + else: + battle = DistributedBattleDinersAI.DistributedBattleDinersAI( + self.air, self, roundCallback, finishCallback, battleSide) + + self.setBattlePos(battle, bossCogPosHpr, battlePosHpr) + + # Just like the DistributedSuitInteriorAI class, we save a + # reference to our suitsKilled and toonSkillPtsGained + # structures in each battle we create. That way, the battle + # will directly adjust these structures and we accumulate the + # toon credits for all battles. + battle.suitsKilled = self.suitsKilled + battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained + battle.toonExp = self.toonExp + battle.toonOrigQuests = self.toonOrigQuests + battle.toonItems = self.toonItems + battle.toonOrigMerits = self.toonOrigMerits + battle.toonMerits = self.toonMerits + battle.toonParts = self.toonParts + battle.helpfulToons = self.helpfulToons + + # We get a bonus factor applied toward each attack's + # experience credit. + mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(battleNumber) + + # We don't, however, get any particular bonus for an invasion + # here. + battle.battleCalc.setSkillCreditMultiplier(mult) + + # lets add the initial 4 suits already + activeSuits = self.activeSuitsA + if battleSide: + activeSuits = self.activeSuitsB + for suit in activeSuits: + battle.addSuit(suit) + + battle.generateWithRequired(self.zoneId) + return battle + + def initializeBattles(self, battleNumber, bossCogPosHpr): + """Set up the pair of battle objects for the BattleOne or BattleThree phase.""" + self.resetBattles() + + if not self.involvedToons: + self.notify.warning("initializeBattles: no toons!") + return + + self.battleNumber = battleNumber + suitHandles = self.generateSuits(battleNumber) + self.suitsA = suitHandles['activeSuits'] + self.activeSuitsA = self.suitsA[:] + self.reserveSuits = suitHandles['reserveSuits'] + + if battleNumber == 3: + if self.toonsB: + # move one of the suits in A to B + movedSuit = self.suitsA.pop() + self.suitsB = [movedSuit] + self.activeSuitsB = [movedSuit] + self.activeSuitsA.remove(movedSuit) + else: + self.suitsB = [] + self.activeSuitsB = [] + else: + suitHandles = self.generateSuits(battleNumber) + self.suitsB = suitHandles['activeSuits'] + self.activeSuitsB = self.suitsB[:] + self.reserveSuits += suitHandles['reserveSuits'] + + if self.toonsA: + if battleNumber == 1: + self.battleA = self.makeBattle( + bossCogPosHpr, ToontownGlobals.WaiterBattleAPosHpr, + self.handleRoundADone, self.handleBattleADone, + battleNumber, 0) + self.battleAId = self.battleA.doId + else: + self.battleA = self.makeBattle( + bossCogPosHpr, ToontownGlobals.DinerBattleAPosHpr, + self.handleRoundADone, self.handleBattleADone, + battleNumber, 0) + self.battleAId = self.battleA.doId + else: + # If we don't have a battleA, all of the suits that would + # have been in that battle go over to fight in battleB + # instead. + self.moveSuits(self.activeSuitsA) + self.suitsA = [] + self.activeSuitsA = [] + + if self.arenaSide == None: + self.b_setArenaSide(0) + + if self.toonsB: + if battleNumber == 1: + self.battleB = self.makeBattle( + bossCogPosHpr, ToontownGlobals.WaiterBattleBPosHpr, + self.handleRoundBDone, self.handleBattleBDone, + battleNumber, 1) + self.battleBId = self.battleB.doId + else: + self.battleB = self.makeBattle( + bossCogPosHpr, ToontownGlobals.DinerBattleBPosHpr, + self.handleRoundBDone, self.handleBattleBDone, + battleNumber, 1) + self.battleBId = self.battleB.doId + else: + # If we don't have a battleB, all of the suits that would + # have been in that battle go over to fight in battleA + # instead. + self.moveSuits(self.activeSuitsB) + self.suitsB = [] + self.activeSuitsB = [] + + if self.arenaSide == None: + self.b_setArenaSide(1) + + self.sendBattleIds() + + ##### PrepareBattleTwo state ##### + def enterPrepareBattleTwo(self): + """Handle entering the Prepare Battle Two State.""" + assert self.notify.debug('%s.enterPrepareBattleTwo()' % (self.doId)) + + # The Clients will see the toons donning waiter disguise. + # CEO will make brief appearing demanding food. + # Mata Hairy says give them food till they explode. + # Walk toons through door + # Close door + + self.barrier = self.beginBarrier( + "PrepareBattleTwo", self.involvedToons, 45, + self.__donePrepareBattleTwo) + + #TODO setup the cogs sitting in the tables + #conveyer belts with food too + self.createFoodBelts() + self.createBanquetTables() + + def __donePrepareBattleTwo(self, avIds): + """Force a transition to the next state once the barrier is done.""" + self.b_setState("BattleTwo") + + def exitPrepareBattleTwo(self): + """Handle exiting the Prepare Battle Two State.""" + self.ignoreBarrier(self.barrier) + + def createFoodBelts(self): + """Create the conveyer belts that bring the food out.""" + if self.foodBelts: + # using ~bossBattle, don't make them twice + return + for i in xrange(2): + newBelt = DistributedFoodBeltAI.DistributedFoodBeltAI(self.air, self, i) + self.foodBelts.append(newBelt) + newBelt.generateWithRequired(self.zoneId) + + def deleteFoodBelts(self): + """Delete the food belts we've created.""" + for belt in self.foodBelts: + belt.requestDelete() + self.foodBelts = [] + + def createBanquetTables(self): + """Create the conveyer belts that bring the food out.""" + if self.tables: + # using ~bossBattle, don't make them twice + return + self.calcAndSetBattleDifficulty() + diffInfo = ToontownGlobals.BossbotBossDifficultySettings[self.battleDifficulty] + self.diffInfo = diffInfo + self.numTables =diffInfo[0] + self.numDinersPerTable = diffInfo[1] + dinerLevel = diffInfo[2] + #self.numTables =2 + #self.numDinersPerTable = 8 + for i in xrange(self.numTables): + newTable = DistributedBanquetTableAI.DistributedBanquetTableAI( + self.air, self, i, self.numDinersPerTable, dinerLevel) + self.tables.append(newTable) + newTable.generateWithRequired(self.zoneId) + + def deleteBanquetTables(self): + """Delete the banquet tables we've created.""" + for table in self.tables: + table.requestDelete() + self.tables = [] + + + ##### BattleTwo state ##### + def enterBattleTwo(self): + """Handle entering the Battle Two State.""" + assert self.notify.debug('%s.enterPrepareBattleTwo()' % (self.doId)) + self.resetBattles() + self.createFoodBelts() + self.createBanquetTables() + for belt in self.foodBelts: + belt.turnOn() + for table in self.tables: + table.turnOn() + self.barrier = self.beginBarrier( + "BattleTwo", + self.involvedToons, + ToontownGlobals.BossbotBossServingDuration + 1, + self.__doneBattleTwo) + + def exitBattleTwo(self): + """Handle exiting the Battle Two State.""" + self.ignoreBarrier(self.barrier) + for table in self.tables: + table.goInactive() + for belt in self.foodBelts: + belt.goInactive() + pass + + def __doneBattleTwo(self, avIds): + """Go to the next state.""" + assert(self.notify.debug('%s.__doneBattleTwo' % (self.doId))) + self.b_setState('PrepareBattleThree') + + + def requestGetFood(self, beltIndex, foodIndex, foodNum): + """Handle a toon requesting to get food from the conveyer belts.""" + grantRequest = False + avId = self.air.getAvatarIdFromSender() + if self.state != 'BattleTwo': + grantRequest = False + elif not (beltIndex, foodNum) in self.toonFoodStatus.values(): + # no one else is carrying it, make sure the toon isn't carrying anything + if not avId in self.toonFoodStatus: + grantRequest = True + elif self.toonFoodStatus[avId] == None: + grantRequest = True + + if grantRequest: + self.toonFoodStatus[avId] = (beltIndex, foodNum) + self.sendUpdate('toonGotFood', [avId, beltIndex, foodIndex, foodNum]) + + def requestServeFood(self, tableIndex, chairIndex): + """Handle a toon requesting to get food from the conveyer belts.""" + grantRequest = False + avId = self.air.getAvatarIdFromSender() + if self.state != 'BattleTwo': + grantRequest = False + elif tableIndex < len( self.tables): + table = self.tables[tableIndex] + dinerStatus = table.getDinerStatus(chairIndex) + if dinerStatus in ( table.HUNGRY, table.ANGRY): + if self.toonFoodStatus[avId]: + grantRequest = True + + if grantRequest: + self.toonFoodStatus[avId] = None + table.foodServed(chairIndex) + self.sendUpdate('toonServeFood', [avId, tableIndex, chairIndex]) + + ##### PrepareBattleThree state ##### + def enterPrepareBattleThree(self): + """Handle entering the Battle Three State.""" + self.barrier = self.beginBarrier( + "PrepareBattleThree", + self.involvedToons, + ToontownGlobals.BossbotBossServingDuration + 1, + self.__donePrepareBattleThree) + + self.divideToons() + self.makeBattleThreeBattles() + + def exitPrepareBattleThree(self): + """Handle exiting the Prepare Battle Three State.""" + self.ignoreBarrier(self.barrier) + pass + + def __donePrepareBattleThree(self, avIds): + """Go to the next state.""" + assert(self.notify.debug('%s.__doneBattleThree' % (self.doId))) + self.b_setState('BattleThree') + + def makeBattleThreeBattles(self): + if not self.battleThreeBattlesMade: + # we need tables for battle three + if not self.tables: + self.createBanquetTables() + for table in self.tables: + table.turnOn() + table.goInactive() + notDeadList = [] + for table in self.tables: + tableInfo = table.getNotDeadInfo() + notDeadList += tableInfo + self.notDeadList = notDeadList + self.postBattleState = 'PrepareBattleFour' + self.initializeBattles(3, ToontownGlobals.BossbotBossBattleThreePosHpr) + self.battleThreeBattlesMade = True + + + + def generateDinerSuits(self): + """Generate the diners that fight.""" + diners = [] + for i in xrange(len(self.notDeadList)): + if simbase.config.GetBool('bossbot-boss-cheat',0): + suit = self.__genSuitObject(self.zoneId, 2, 'c', 2, 0) + else: + info = self.notDeadList[i] + suitType = info[2] - 4 + suitLevel = info[2] + suit = self.__genSuitObject(self.zoneId, suitType, 'c', suitLevel, 1) + diners.append((suit, 100)) + active = [] + # we make 2 more than the suits left + for i in xrange(2): + if simbase.config.GetBool('bossbot-boss-cheat',0): + suit = self.__genSuitObject(self.zoneId, 2, 'c', 2, 0) + else: + # BAND-AID: notDeadList might be empty? + suitType = 8 + suitLevel = 12 + suit = self.__genSuitObject(self.zoneId, suitType, 'c', suitLevel, 1) + active.append(suit) + return {'activeSuits': active, + 'reserveSuits': diners + } + + def __genSuitObject(self, suitZone, suitType, bldgTrack, suitLevel, revives = 0): + """ + // Function: generate a distributed suit object + // Parameters: + // Changes: + // Returns: the suit object created + """ + newSuit = DistributedSuitAI.DistributedSuitAI( simbase.air, None ) + skel = self.__setupSuitInfo( newSuit, bldgTrack, suitLevel, suitType ) + if skel: + newSuit.setSkelecog(1) + newSuit.setSkeleRevives(revives) + newSuit.generateWithRequired( suitZone ) + + # Fill in the name so we can tell one suit from another in printouts. + newSuit.node().setName('suit-%s' % (newSuit.doId)) + return newSuit + + def __setupSuitInfo( self, suit, bldgTrack, suitLevel, suitType ): + """ + create dna information for the given suit with the given track + and suit type + """ + + dna = SuitDNA.SuitDNA() + dna.newSuitRandom( suitType, bldgTrack ) + suit.dna = dna + self.notify.debug("Creating suit type " + suit.dna.name + + " of level " + str( suitLevel ) + + " from type " + str( suitType ) + + " and track " + str( bldgTrack ) ) + suit.setLevel( suitLevel ) + + # We can't make a suit a skeleton until after generate. + # Pass this info back so we know whether to do it or not + return False + + ##### BattleThree state ##### + def enterBattleThree(self): + """Handle entering the Battle Three State.""" + self.makeBattleThreeBattles() + self.notify.debug('self.battleA = %s' % self.battleA) + # Begin the battles. + if self.battleA: + self.battleA.startBattle(self.toonsA, self.suitsA) + if self.battleB: + self.battleB.startBattle(self.toonsB, self.suitsB) + + def exitBattleThree(self): + """Handle exiting the Prepare Battle Three State.""" + self.resetBattles() + pass + + ##### PrepareBattleFour state ##### + def enterPrepareBattleFour(self): + """Handle entering the Prepare Battle Four State.""" + self.resetBattles() + assert self.notify.debug('%s.enterPrepareBattleFour()' % (self.doId)) + self.setupBattleFourObjects() + # The Clients will see the CEO doing a speech + self.barrier = self.beginBarrier( + "PrepareBattleFour", self.involvedToons, 45, + self.__donePrepareBattleFour) + + def __donePrepareBattleFour(self, avIds): + """Force a transition to the next state once the barrier is done.""" + self.b_setState("BattleFour") + + def exitPrepareBattleFour(self): + """Handle exiting the Prepare Battle Four State.""" + self.ignoreBarrier(self.barrier) + + + ##### BattleFour state ##### + def enterBattleFour(self): + """Handle entering the Battle Four State.""" + self.battleFourTimeStarted = globalClock.getFrameTime() + self.numToonsAtStart = len(self.involvedToons) + self.resetBattles() + assert self.notify.debug('%s.enterBattleFour()' % (self.doId)) + self.setupBattleFourObjects() + self.battleFourStart = globalClock.getFrameTime() + self.waitForNextAttack(5) + pass + + def exitBattleFour(self): + """Handle exiting the Prepare Battle Four State.""" + self.recordCeoInfo() + for belt in self.foodBelts: + belt.goInactive() + + pass + + def recordCeoInfo(self): + """Record info to the server log so we can tune the battle.""" + didTheyWin = 0 + if self.bossDamage == self.bossMaxDamage: + didTheyWin =1 + self.battleFourTimeInMin = globalClock.getFrameTime() - self.battleFourTimeStarted + self.battleFourTimeInMin /= 60.0 + self.numToonsAtEnd = 0; + toonHps = [] + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + self.numToonsAtEnd += 1 + toonHps.append (toon.hp) + self.air.writeServerEvent( + "ceoInfo", self.doId, + "%d|%.2f|%d|%d|%d|%d|%d|%d|%s|%s|%.1f|%d|%d|%d|%d|%d}%d|%s|" % + (didTheyWin, + self.battleFourTimeInMin, + self.battleDifficulty, + self.numToonsAtStart, + self.numToonsAtEnd, + self.numTables, + self.numTables * self.numDinersPerTable, + self.numDinersExploded, + toonHps, + self.involvedToons, + self.speedDamage, + self.numMoveAttacks, + self.numGolfAttacks, + self.numGearAttacks, + self.numGolfAreaAttacks, + self.numToonupGranted, + self.totalLaffHealed, + "ceoBugfixes" + ) + ) + + + def setupBattleFourObjects(self): + """Setup the AI objects for battle four.""" + if self.battleFourSetup: + return + if not self.tables: + self.createBanquetTables() + #for table in self.tables: + # table.turnOn() + # table.goInactive() + for table in self.tables: + table.goFree() + if not self.golfSpots: + self.createGolfSpots() + self.createFoodBelts() + for belt in self.foodBelts: + belt.goToonup() + self.battleFourSetup = True + + + def hitBoss(self, bossDamage): + """Handle a client telling us he hit the boss. + + # This is sent when the client successfully hits the boss during + # battle three. We have to take the client's word for it here. + """ + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBoss(%s, %s)' % (self.doId, avId, bossDamage)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBoss from unknown avatar'): + return + + # We only expect a bossDamage value of 1 from the client. If + # a client ever sends some other value, it's cause for + # immediate and strong suspicion of a hacked client. However, + # we honor the strange bossDamage value, partly to make it + # convenient for testing, and partly to help trap greedy + # hackers into revealing themselves repeatedly. + self.validate(avId, bossDamage <= 3, + 'invalid bossDamage %s' % (bossDamage)) + if bossDamage < 1: + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleFour': + # This was just a late hit; ignore it. + return + + # increase the damage from tuning + bossDamage *= 2 + + bossDamage = min(self.getBossDamage() + bossDamage, self.bossMaxDamage) + self.b_setBossDamage(bossDamage, 0, 0) + + if self.bossDamage >= self.bossMaxDamage: + # Only set this state locally--the clients will go there + # by themselves when the boss movie finishes playing out. + self.b_setState('Victory') + + else: + self.__recordHit(bossDamage) + + + def __recordHit(self, bossDamage): + """Record that the boss got hit.""" + # Records that the boss has been hit, and counts the number of + # hits in a period of time. + now = globalClock.getFrameTime() + + self.hitCount += 1 + #if (self.hitCount < self.limitHitCount or self.bossDamage < self.hitCountDamage): + # assert self.notify.debug("%s. %s hits, ignoring." % (self.doId, self.hitCount)) + # return + + assert self.notify.debug("%s. %s hits!" % (self.doId, self.hitCount)) + + # Launch an immediate front attack. + #self.b_setAttackCode(ToontownGlobals.BossCogRecoverDizzyAttack) + avId = self.air.getAvatarIdFromSender() + self.addThreat(avId, bossDamage) + + def getBossDamage(self): + """Return the current boss damage.""" + return self.bossDamage + + def b_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + """Set the damage on the AI and on the clients.""" + self.d_setBossDamage(bossDamage, recoverRate, recoverStartTime) + self.setBossDamage(bossDamage, recoverRate, recoverStartTime) + + def setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + """Set the boss damage on the AI.""" + assert self.notify.debug('%s.setBossDamage(%s, %s)' % (self.doId, bossDamage, recoverRate)) + self.bossDamage = bossDamage + self.recoverRate = recoverRate + self.recoverStartTime = recoverStartTime + + def d_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + """Tell clients of the boss damage.""" + timestamp = globalClockDelta.localToNetworkTime(recoverStartTime) + self.sendUpdate('setBossDamage', [bossDamage, recoverRate, timestamp]) + + + def getSpeedDamage(self): + """Return the current speedDamage as an int.""" + now = globalClock.getFrameTime() + elapsed = now - self.speedRecoverStartTime + + self.notify.debug('elapsed=%s' % elapsed) + # It is important that we consistently represent bossDamage as + # an integer value, so there is never any chance of client and + # AI disagreeing about whether bossDamage < bossMaxDamage. + floatSpeedDamage = max(self.speedDamage - self.speedRecoverRate * elapsed / 60.0 , 0) + self.notify.debug('floatSpeedDamage = %s' % floatSpeedDamage) + return int(max(self.speedDamage - self.speedRecoverRate * elapsed / 60.0 , 0)) + + def getFloatSpeedDamage(self): + """Return the current speedDamage as a float.""" + now = globalClock.getFrameTime() + elapsed = now - self.speedRecoverStartTime + + # It is important that we consistently represent bossDamage as + # an integer value, so there is never any chance of client and + # AI disagreeing about whether bossDamage < bossMaxDamage. + floatSpeedDamage = max(self.speedDamage - self.speedRecoverRate * elapsed / 60.0 , 0) + self.notify.debug('floatSpeedDamage = %s' % floatSpeedDamage) + return max(self.speedDamage - self.speedRecoverRate * elapsed / 60.0 , 0) + + + def b_setSpeedDamage(self, speedDamage, recoverRate, recoverStartTime): + """Set the damage on the AI and on the clients.""" + self.d_setSpeedDamage(speedDamage, recoverRate, recoverStartTime) + self.setSpeedDamage(speedDamage, recoverRate, recoverStartTime) + + def setSpeedDamage(self, speedDamage, recoverRate, recoverStartTime): + """Set the speed damage on the AI.""" + assert self.notify.debug('%s.setSpeedDamage(%s, %s)' % (self.doId, speedDamage, recoverRate)) + self.speedDamage = speedDamage + self.speedRecoverRate = recoverRate + self.speedRecoverStartTime = recoverStartTime + + def d_setSpeedDamage(self, speedDamage, recoverRate, recoverStartTime): + """Tell clients of the speed damage.""" + timestamp = globalClockDelta.localToNetworkTime(recoverStartTime) + self.sendUpdate('setSpeedDamage', [speedDamage, recoverRate, timestamp]) + + def createGolfSpots(self): + """Create the golf spots for phase four of the boss battle.""" + if self.golfSpots: + # using ~bossBattle, don't make them twice + return + for i in xrange(self.numGolfSpots): + newGolfSpot = DistributedGolfSpotAI.DistributedGolfSpotAI( + self.air, self, i) + self.golfSpots.append(newGolfSpot) + newGolfSpot.generateWithRequired(self.zoneId) + newGolfSpot.forceFree() + + def deleteGolfSpots(self): + """Delete the golf spots we've created.""" + for spot in self.golfSpots: + spot.requestDelete() + self.golfSpots = [] + + + def ballHitBoss(self, speedDamage): + """Handle a client telling us he hit the boss with the golf ball + + # This is sent when the client successfully hits the boss during + # battle four. We have to take the client's word for it here. + """ + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBoss(%s, %s)' % (self.doId, avId, speedDamage)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBoss from unknown avatar'): + return + + if speedDamage < 1: + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleFour': + # This was just a late hit; ignore it. + return + + now = globalClock.getFrameTime() + newDamage = self.getSpeedDamage() + speedDamage + self.notify.debug('newDamage = %s' % newDamage) + speedDamage = min(self.getFloatSpeedDamage() + speedDamage, self.maxSpeedDamage) + #speedDamage = int(speedDamage) + self.b_setSpeedDamage(speedDamage, self.speedRecoverRate, now) + self.addThreat(avId, 0.1) + #self.__recordHit() + + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('%s.enterVictory()' % (self.doId)) + self.resetBattles() + + for table in self.tables: + table.turnOff() + for golfSpot in self.golfSpots: + golfSpot.turnOff() + + # add a suit-defeat entry for the VP + # based on code in DistributedBattleBaseAI.__movieDone + self.suitsKilled.append({ + 'type' : None, + 'level' : None, + 'track' : self.dna.dept, + 'isSkelecog' : 0, + 'isForeman' : 0, + 'isVP' : 1, + 'isCFO' : 0, + 'isSupervisor' : 0, + 'isVirtual' : 0, + 'activeToons' : self.involvedToons[:], + }) + + self.barrier = self.beginBarrier( + "Victory", self.involvedToons, 30, + self.__doneVictory) + + def __doneVictory(self, avIds): + # Tell the client the information it needs to generate a + # reward movie. + self.d_setBattleExperience() + + # First, move the clients into the reward start. They'll + # build the reward movies immediately. + self.b_setState("Reward") + + # Now that the clients have started to build their reward + # movies, we can actually assign all the experience and + # rewards. If we did this sooner, the quest reward panel + # would show the rewards being applied twice. + + # There is no race condition between AI and client here + # because these messages are sent sequentially on the wire. + + BattleExperienceAI.assignRewards( + self.involvedToons, self.toonSkillPtsGained, + self.suitsKilled, + ToontownGlobals.dept2cogHQ(self.dept), self.helpfulToons) + + # Don't forget to give the toon the pink slip reward and the + # promotion! + + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + self.givePinkSlipReward(toon) + toon.b_promote(self.deptIndex) + + def givePinkSlipReward(self, toon): + """Give a pink slip reward to the toon.""" + self.notify.debug("TODO give pink slip to %s" % toon) + toon.addPinkSlips(self.battleDifficulty +1) + pass + + def getThreat(self, toonId): + """Return the threat level of the toon.""" + if toonId in self.threatDict: + return self.threatDict[toonId] + else: + return 0 + + def addThreat(self, toonId, threat): + """Increase the threat level of the toon.""" + if toonId in self.threatDict: + self.threatDict[toonId] += threat + else: + self.threatDict[toonId] = threat + + def subtractThreat(self, toonId, threat): + """Increase the threat level of the toon.""" + if toonId in self.threatDict: + self.threatDict[toonId] -= threat + else: + self.threatDict[toonId] = 0 + if self.threatDict[toonId] < 0: + self.threatDict[toonId] = 0 + + + def waitForNextAttack(self, delayTime): + currState = self.getCurrentOrNextState() + if (currState == 'BattleFour'): + assert self.notify.debug("%s.Waiting %s seconds for next attack." % (self.doId, delayTime)) + taskName = self.uniqueName('NextAttack') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.doNextAttack, taskName) + else: + assert self.notify.debug("%s.Not doing another attack in state %s." % (self.doId, currState)) + + def doNextAttack(self, task): + assert self.notify.debug("%s.doNextAttack()" % (self.doId)) + # Choose an attack and do it. + attackCode = -1 + optionalParam = None + if self.movingToTable: + # we are still doing the move attack + self.waitForNextAttack(5) + elif self.attackCode == ToontownGlobals.BossCogDizzyNow: + # We always choose this particular attack when recovering + # from dizzy. It's really the same as the front attack, + # with extra time for standing up first. + attackCode = ToontownGlobals.BossCogRecoverDizzyAttack + + else: + # Choose an attack at random. + if self.getBattleFourTime() > self.overtimeOneStart and not self.doneOvertimeOneAttack: + attackCode = ToontownGlobals.BossCogOvertimeAttack + self.doneOvertimeOneAttack = True + optionalParam = 0 + elif self.getBattleFourTime() > 1.0 and not self.doneOvertimeTwoAttack: + attackCode = ToontownGlobals.BossCogOvertimeAttack + self.doneOvertimeTwoAttack = True + optionalParam = 1 + else: + attackCode = random.choice( + [ToontownGlobals.BossCogGolfAreaAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ]) + + if attackCode == ToontownGlobals.BossCogAreaAttack: + self.__doAreaAttack() + if attackCode == ToontownGlobals.BossCogGolfAreaAttack: + self.__doGolfAreaAttack() + elif attackCode == ToontownGlobals.BossCogDirectedAttack: + self.__doDirectedAttack() + elif attackCode >= 0: + self.b_setAttackCode(attackCode, optionalParam) + + + def progressValue(self, fromValue, toValue): + # Returns an interpolated value from fromValue to toValue, + # depending on the value of self.bossDamage in the range [0, + # self.bossMaxDamage]. It is also based on time. + + # That is, lerps the value fromValue to toValue during the + # course of the battle. + + # t0 is the elapsed damage + t0 = float(self.bossDamage) / float(self.bossMaxDamage) + + # t1 is the elapsed time + elapsed = globalClock.getFrameTime() - self.battleFourStart + t1 = elapsed / float(self.battleThreeDuration) + + # We actually progress the value based on the larger of the + # two, so you can't spend all day in the battle. + t = max(t0, t1) + + progVal = fromValue + (toValue - fromValue) * min(t, 1) + self.notify.debug('progVal=%s' % progVal) + import pdb; pdb.set_trace() + return progVal + + def __doDirectedAttack(self): + """Do an attack at a toon.""" + toonId = self.getMaxThreatToon() + self.notify.debug('toonToAttack=%s' % toonId) + unflattenedToons = self.getUnflattenedToons() + attackTotallyRandomToon = random.random() < 0.1 + if unflattenedToons and (attackTotallyRandomToon or toonId==0): + toonId = random.choice(unflattenedToons) + if toonId: + toonThreat = self.getThreat(toonId) + toonThreat *= 0.25 + threatToSubtract = max( toonThreat, 10) + self.subtractThreat(toonId, threatToSubtract) + if self.isToonRoaming(toonId): + self.b_setAttackCode(ToontownGlobals.BossCogGolfAttack, toonId) + self.numGolfAttacks += 1 + elif self.isToonOnTable(toonId): + # lets make him shoot a small percent of the time + doesMoveAttack = simbase.air.config.GetBool('ceo-does-move-attack',1) + if doesMoveAttack: + chanceToShoot = 0.25 + else: + chanceToShoot = 1.0 + # if we have a magic word not to move, then don't move + if not self.moveAttackAllowed: + self.notify.debug('moveAttack is not allowed, doing gearDirectedAttack') + chanceToShoot = 1.0 + if random.random() < chanceToShoot: + self.b_setAttackCode(ToontownGlobals.BossCogGearDirectedAttack, toonId) + self.numGearAttacks += 1 + else: + tableIndex = self.getToonTableIndex(toonId) + self.doMoveAttack(tableIndex) + else: + self.b_setAttackCode(ToontownGlobals.BossCogGolfAttack, toonId) + else: + # let's move him to a random table + uprightTables = self.getUprightTables() + if uprightTables: + tableToMoveTo = random.choice(uprightTables) + self.doMoveAttack(tableToMoveTo) + else: + # really nothing to do + self.waitForNextAttack(4) + + + def doMoveAttack(self, tableIndex): + """A new attack where the boss runs over a table.""" + self.numMoveAttacks += 1 + self.movingToTable = True + self.tableDest = tableIndex + self.b_setAttackCode(ToontownGlobals.BossCogMoveAttack, tableIndex) + + def getUnflattenedToons(self): + """Return a list of toonsIds who are not flattened.""" + result = [] + uprightTables = self.getUprightTables() + for toonId in self.involvedToons: + toonTable = self.getToonTableIndex(toonId) + if toonTable >=0 and \ + not (toonTable in uprightTables): + pass + else: + result.append(toonId) + return result + + + def getMaxThreatToon(self): + """Return the toon with the most threat.""" + returnedToonId = 0 + maxThreat = 0 + maxToons = [] + for toonId in self.threatDict: + curThreat = self.threatDict[toonId] + tableIndex = self.getToonTableIndex(toonId) + if tableIndex > -1 and self.tables[tableIndex].state == 'Flat': + # don't attack a flattened toon + pass + else: + if curThreat > maxThreat: + maxToons = [toonId] + maxThreat = curThreat + elif curThreat == maxThreat: + maxToons.append(toonId) + if maxToons: + returnedToonId = random.choice(maxToons) + return returnedToonId + + def getToonDifficulty(self): + """ + Get the difficulty factor based on just the toons + """ + highestCogSuitLevel = 0 + totalCogSuitLevels = 0.0 + totalNumToons = 0.0 + + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toonLevel = toon.getNumPromotions(self.dept) + totalCogSuitLevels += toonLevel + totalNumToons += 1 + if (toon.cogLevels > highestCogSuitLevel): + highestCogSuitLevel = toonLevel + + if not totalNumToons: + totalNumToons = 1.0 + + averageLevel = totalCogSuitLevels / totalNumToons + self.notify.debug('toons average level = %f, highest level = %d' % (averageLevel, highestCogSuitLevel)) + + #put a cap on it, otherwise we could go as high as 90 + retval = min (averageLevel, self.maxToonLevels) + return retval + + def calcAndSetBattleDifficulty(self): + self.toonLevels = self.getToonDifficulty() + numDifficultyLevels = len(ToontownGlobals.BossbotBossDifficultySettings) + battleDifficulty = int ( self.toonLevels / self.maxToonLevels * numDifficultyLevels) + + if battleDifficulty >= numDifficultyLevels: + battleDifficulty = numDifficultyLevels -1 + + self.b_setBattleDifficulty(battleDifficulty) + + + def b_setBattleDifficulty(self, batDiff): + self.setBattleDifficulty(batDiff) + self.d_setBattleDifficulty(batDiff) + + def setBattleDifficulty(self, batDiff): + self.battleDifficulty = batDiff + + def d_setBattleDifficulty(self, batDiff): + self.sendUpdate('setBattleDifficulty', [batDiff]) + + def getUprightTables(self): + """Return a list of table indices who are still upright.""" + tableList = [] + for table in self.tables: + if table.state != 'Flat': + tableList.append(table.index) + return tableList + + def getToonTableIndex(self, toonId): + """Returns the table index he is on, -1 if he's not on a table""" + tableIndex = -1 + for table in self.tables: + if table.avId == toonId: + tableIndex = table.index + break + return tableIndex + + def getToonGolfSpotIndex(self, toonId): + """Returns the golfSpot index he is on, -1 if he's not on a golfSpot""" + golfSpotIndex = -1 + for golfSpot in self.golfSpots: + if golfSpot.avId == toonId: + golfSpotIndex = golfSpot.index + break + return golfSpotIndex + + def isToonOnTable(self, toonId): + """Returns True if the toon is on a table.""" + result = self.getToonTableIndex(toonId) != -1 + return result + + def isToonOnGolfSpot(self, toonId): + """Returns True if the toon is on a golf spot.""" + result = self.getToonGolfSpotIndex(toonId) != -1 + return result + + def isToonRoaming(self, toonId): + result = not self.isToonOnTable(toonId) and not self.isToonOnGolfSpot(toonId) + return result + + def reachedTable(self, tableIndex): + """One of the clients has signalled the boss has reached the table.""" + if self.movingToTable and self.tableDest == tableIndex: + self.movingToTable = False + self.curTable = self.tableDest + self.tableDest = -1 + + def hitTable(self, tableIndex): + """One of the clients has signalled the boss has reached the table.""" + self.notify.debug('hitTable tableIndex=%d' % tableIndex) + if tableIndex < len(self.tables): + table = self.tables[tableIndex] + if table.state != 'Flat': + table.goFlat() + + def awayFromTable(self, tableIndex): + """One of the clients has signalled the boss has moved away from the table.""" + self.notify.debug('awayFromTable tableIndex=%d' % tableIndex) + if tableIndex < len(self.tables): + #import pdb; pdb.set_trace() + taskName = 'Unflatten-%d' % tableIndex + unflattenTime= self.diffInfo[3] + taskMgr.doMethodLater(unflattenTime, self.unflattenTable, taskName, extraArgs=[tableIndex]) + + def unflattenTable(self, tableIndex): + """Unflatten the table.""" + if tableIndex < len(self.tables): + table = self.tables[tableIndex] + if table.state == 'Flat': + if table.avId and \ + table.avId in self.involvedToons: + table.forceControl(table.avId) + else: + table.goFree() + + def incrementDinersExploded(self): + """Increase the count of diners exploded.""" + self.numDinersExploded +=1 + + def magicWordHit(self, damage, avId): + # Called by the magic word "~bossBattle hit damage" + self.hitBoss(damage) + + def __doAreaAttack(self): + self.b_setAttackCode(ToontownGlobals.BossCogAreaAttack) + + def __doGolfAreaAttack(self): + self.numGolfAreaAttacks += 1 + self.b_setAttackCode(ToontownGlobals.BossCogGolfAreaAttack) + + + def hitToon(self, toonId): + # This is sent when the client pies another toon during battle + # three. We have to take the client's word for it here too. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitToon(%s, %s)' % (self.doId, avId, toonId)) + + if not self.validate(avId, avId != toonId, + 'hitToon on self'): + return + + if avId not in self.involvedToons or toonId not in self.involvedToons: + # Not an error, since either toon might have just died. + return + + toon = self.air.doId2do.get(toonId) + if toon: + self.healToon(toon, 1) + self.sendUpdate('toonGotHealed',[toonId]) + + def requestGetToonup(self, beltIndex, toonupIndex, toonupNum): + """Handle a toon requesting to get toonup from the conveyer belts.""" + grantRequest = False + avId = self.air.getAvatarIdFromSender() + if self.state != 'BattleFour': + grantRequest = False + elif not (beltIndex, toonupNum) in self.toonupsGranted: + # make sure the toon is still there + toon = simbase.air.doId2do.get(avId) + if toon: + # no one else got it + grantRequest = True + + if grantRequest: + self.toonupsGranted.insert(0, (beltIndex, toonupNum)) + if len(self.toonupsGranted) > 8: + self.toonupsGranted = self.toonupsGranted[0:8] + self.sendUpdate('toonGotToonup', [avId, beltIndex, toonupIndex, toonupNum]) + if toonupIndex < len(self.toonUpLevels): + self.healToon(toon, self.toonUpLevels[toonupIndex]) + self.numToonupGranted += 1 + self.totalLaffHealed += self.toonUpLevels[toonupIndex] + else: + self.notify.warning('requestGetToonup this should not happen') + self.healToon(toon,1) + + def toonLeftTable(self, tableIndex): + """Think if the toon leaving the table will make us do something else.""" + if self.movingToTable and self.tableDest == tableIndex: + # the toon on the table we are moving to just jumped off. + if random.random() < 0.5: + self.movingToTable = False + self.waitForNextAttack(0) + + def getBattleFourTime(self): + # Returns the amount of time spent so far in battle four, as + # a ratio of the expected battle three duration. This will + # range 0 .. 1 during the normal course of the battle; if it + # goes beyond 1, the battle should be considered to be in + # overtime (and should rapidly become impossibly difficult, to + # prevent toons from dying a slow, frustrating death). + if self.state != 'BattleFour': + t1 = 0 + else: + elapsed = globalClock.getFrameTime() - self.battleFourStart + t1 = elapsed / float(self.battleFourDuration) + return t1 + + def getDamageMultiplier(self): + """Return a multiplier for cog attacks.""" + mult = 1.0 + if self.doneOvertimeOneAttack and not self.doneOvertimeTwoAttack: + mult = 1.25 + if self.getBattleFourTime() > 1.0: + # we're in overtime make him hit harder + mult = self.getBattleFourTime() + 1 + return mult + + def toggleMove(self): + """Handle the magic word to toggle us doing move attacks.""" + self.moveAttackAllowed = not self.moveAttackAllowed + return self.moveAttackAllowed diff --git a/toontown/src/suit/DistributedCashbotBoss.py b/toontown/src/suit/DistributedCashbotBoss.py new file mode 100644 index 0000000..651280a --- /dev/null +++ b/toontown/src/suit/DistributedCashbotBoss.py @@ -0,0 +1,1452 @@ +from direct.interval.IntervalGlobal import * +from direct.task.TaskManagerGlobal import * +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import TTLocalizer +import DistributedBossCog +from direct.task.Task import Task +import DistributedCashbotBossGoon +import SuitDNA +from toontown.toon import Toon +from toontown.toon import ToonDNA +from direct.fsm import FSM +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPGlobals +from toontown.building import ElevatorUtils +from toontown.building import ElevatorConstants +from toontown.battle import MovieToonVictory +from toontown.battle import RewardPanel +from toontown.distributed import DelayDelete +from toontown.chat import ResistanceChat +from toontown.coghq import CogDisguiseGlobals +from pandac.PandaModules import * +import random +import math + +# This pointer keeps track of the one DistributedCashbotBoss that +# should appear within the avatar's current visibility zones. If +# there is more than one DistributedCashbotBoss visible to a client at +# any given time, something is wrong. +OneBossCog = None + +TTL = TTLocalizer +class DistributedCashbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedCashbotBoss') + + numFakeGoons = 3 + + def __init__(self, cr): + DistributedBossCog.DistributedBossCog.__init__(self, cr) + FSM.FSM.__init__(self, 'DistributedSellbotBoss') + + self.resistanceToon = None + self.resistanceToonOnstage = 0 + self.cranes = {} + self.safes = {} + self.goons = [] + + self.bossMaxDamage = ToontownGlobals.CashbotBossMaxDamage + + self.elevatorType = ElevatorConstants.ELEVATOR_CFO + + #hack for quick access while debugging + base.boss = self + + def announceGenerate(self): + DistributedBossCog.DistributedBossCog.announceGenerate(self) + + # at this point all our attribs have been filled in. + self.setName(TTLocalizer.CashbotBossName) + nameInfo = TTLocalizer.BossCogNameWithDept % { + "name": self.name, + "dept": (SuitDNA.getDeptFullname(self.style.dept)), + } + self.setDisplayName(nameInfo) + + # Our goal in this battle is to drop stuff on the CFO's head. + # For this, we need a target. + target = CollisionSphere(2, 0, 0, 3) + targetNode = CollisionNode('headTarget') + targetNode.addSolid(target) + targetNode.setCollideMask(ToontownGlobals.PieBitmask) + self.headTarget = self.neck.attachNewNode(targetNode) + + # And he gets a big bubble around his torso, just to keep + # things from falling through him. It's a big sphere so + # things will tend to roll off him instead of landing on him. + shield = CollisionSphere(0, 0, 0.8, 7) + shieldNode = CollisionNode('shield') + shieldNode.addSolid(shield) + shieldNode.setCollideMask(ToontownGlobals.PieBitmask) + shieldNodePath = self.pelvis.attachNewNode(shieldNode) + + + # By "heldObject", we mean the safe he's currently wearing as + # a helmet, if any. It's called a heldObject because this is + # the way the cranes refer to the same thing, and we use the + # same interface to manage this. + self.heldObject = None + + self.bossDamage = 0 + + # The BossCog actually owns the environment geometry. Not + # sure if this is a great idea, but it's the way we did it + # with the sellbot boss, and the comment over there seems to + # think it's a great idea. :) + self.loadEnvironment() + + # Set up the caged toon. + self.__makeResistanceToon() + + # Set up a physics manager for the cables and the objects + # falling around in the room. + + self.physicsMgr = PhysicsManager() + integrator = LinearEulerIntegrator() + self.physicsMgr.attachLinearIntegrator(integrator) + + fn = ForceNode('gravity') + self.fnp = self.geom.attachNewNode(fn) + gravity = LinearVectorForce(0, 0, -32) + fn.addForce(gravity) + self.physicsMgr.addLinearForce(gravity) + + # Enable the special CFO chat menu. + localAvatar.chatMgr.chatInputSpeedChat.addCFOMenu() + + global OneBossCog + if OneBossCog != None: + self.notify.warning("Multiple BossCogs visible.") + OneBossCog = self + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + DistributedBossCog.DistributedBossCog.disable(self) + self.demand('Off') + + self.unloadEnvironment() + self.__cleanupResistanceToon() + + self.fnp.removeNode() + self.physicsMgr.clearLinearForces() + + self.battleThreeMusic.stop() + self.epilogueMusic.stop() + + localAvatar.chatMgr.chatInputSpeedChat.removeCFOMenu() + + global OneBossCog + if OneBossCog == self: + OneBossCog = None + + def __makeResistanceToon(self): + # Generates the Resistance Toon (tm), who will be our initial + # guide and then callously abandon us to our fate. + if self.resistanceToon: + return + + npc = Toon.Toon() + npc.setName(TTLocalizer.ResistanceToonName) + npc.setPickable(0) + npc.setPlayerType(NametagGroup.CCNonPlayer) + dna = ToonDNA.ToonDNA() + dna.newToonRandom(11237, 'f', 1) + dna.head = "pls" + npc.setDNAString(dna.makeNetString()) + + npc.animFSM.request("neutral") + + self.resistanceToon = npc + self.resistanceToon.setPosHpr(*ToontownGlobals.CashbotRTBattleOneStartPosHpr) + + # determine a random suit to put him in + state = random.getstate() + random.seed(self.doId) + self.resistanceToon.suitType = SuitDNA.getRandomSuitByDept("m") + random.setstate(state) + + # Make some goons for the resistance toon to play with + self.fakeGoons = [] + for i in range(self.numFakeGoons): + goon = DistributedCashbotBossGoon.DistributedCashbotBossGoon(base.cr) + goon.doId = -1 - i + goon.setBossCogId(self.doId) + goon.generate() + goon.announceGenerate() + self.fakeGoons.append(goon) + self.__hideFakeGoons() + + def __cleanupResistanceToon(self): + self.__hideResistanceToon() + if self.resistanceToon: + self.resistanceToon.removeActive() + self.resistanceToon.delete() + self.resistanceToon = None + + for i in range(self.numFakeGoons): + self.fakeGoons[i].disable() + self.fakeGoons[i].delete() + self.fakeGoons[i] = None + + def __showResistanceToon(self, withSuit): + if not self.resistanceToonOnstage: + self.resistanceToon.addActive() + self.resistanceToon.reparentTo(self.geom) + self.resistanceToonOnstage = 1 + + if withSuit: + suit = self.resistanceToon.suitType + self.resistanceToon.putOnSuit(suit, False) + else: + self.resistanceToon.takeOffSuit() + + def __hideResistanceToon(self): + if self.resistanceToonOnstage: + self.resistanceToon.removeActive() + self.resistanceToon.detachNode() + self.resistanceToonOnstage = 0 + + def __hideFakeGoons(self): + if self.fakeGoons: + for goon in self.fakeGoons: + goon.request('Off') + + def __showFakeGoons(self, state): + print self.fakeGoons + if self.fakeGoons: + for goon in self.fakeGoons: + goon.request(state) + + + + ##### Environment ##### + + def loadEnvironment(self): + DistributedBossCog.DistributedBossCog.loadEnvironment(self) + + self.midVault = loader.loadModel('phase_10/models/cogHQ/MidVault.bam') + self.endVault = loader.loadModel('phase_10/models/cogHQ/EndVault.bam') + self.lightning = loader.loadModel('phase_10/models/cogHQ/CBLightning.bam') + self.magnet = loader.loadModel('phase_10/models/cogHQ/CBMagnet.bam') + self.craneArm = loader.loadModel('phase_10/models/cogHQ/CBCraneArm.bam') + self.controls = loader.loadModel('phase_10/models/cogHQ/CBCraneControls.bam') + self.stick = loader.loadModel('phase_10/models/cogHQ/CBCraneStick.bam') + self.safe = loader.loadModel('phase_10/models/cogHQ/CBSafe.bam') + self.eyes = loader.loadModel('phase_10/models/cogHQ/CashBotBossEyes.bam') + self.cableTex = self.craneArm.findTexture('MagnetControl') + + # Get the eyes ready for putting outside the helmet. + self.eyes.setPosHprScale(4.5, 0, -2.5, + 90, 90, 0, + 0.4, 0.4, 0.4) + self.eyes.reparentTo(self.neck) + self.eyes.hide() + + # Position the two rooms relative to each other, and so that + # the floor is at z == 0 + self.midVault.setPos(0, -222, -70.7) + self.endVault.setPos(84, -201, -6) + + self.geom = NodePath('geom') + self.midVault.reparentTo(self.geom) + self.endVault.reparentTo(self.geom) + + # Clear out unneeded backstage models from the EndVault, if + # they're in the file. + self.endVault.findAllMatches('**/MagnetArms').detach() + self.endVault.findAllMatches('**/Safes').detach() + self.endVault.findAllMatches('**/MagnetControlsAll').detach() + + # Flag the collisions in the end vault so safes and magnets + # don't try to go through the wall. + cn = self.endVault.find('**/wallsCollision').node() + cn.setIntoCollideMask(OTPGlobals.WallBitmask | ToontownGlobals.PieBitmask | (BitMask32.lowerOn(3) << 21)) + + # Get the rolling doors. + + # This is the door to Somewhere Else, through which the boss + # makes his entrance. + self.door1 = self.midVault.find('**/SlidingDoor1/') + + # This is the door from the mid vault to the end vault. + # Everyone proceeds through this door to the final battle + # scene. + self.door2 = self.midVault.find('**/SlidingDoor/') + + # This is the door from the end vault back to the mid vault. + # The boss makes his "escape" through this door. + self.door3 = self.endVault.find('**/SlidingDoor/') + + # Load the elevator model + elevatorModel = loader.loadModel("phase_10/models/cogHQ/CFOElevator") + + # Set up an origin for the elevator. + elevatorOrigin = self.midVault.find('**/elevator_origin') + elevatorOrigin.setScale(1) + + elevatorModel.reparentTo(elevatorOrigin) + + leftDoor = elevatorModel.find('**/left_door') + leftDoor.setName("left-door") + rightDoor = elevatorModel.find('**/right_door') + rightDoor.setName("right-door") + self.setupElevator(elevatorOrigin) + ElevatorUtils.closeDoors(leftDoor, rightDoor, ElevatorConstants.ELEVATOR_CFO) + + # Find all the wall polygons and replace them with planes, + # which are solid, so there will be zero chance of safes or + # toons slipping through a wall. + walls = self.endVault.find('**/RollUpFrameCillison') + walls.detachNode() + self.evWalls = self.replaceCollisionPolysWithPlanes(walls) + self.evWalls.reparentTo(self.endVault) + + # Initially, these new planar walls are stashed, so they don't + # cause us trouble in the intro movie or in battle one. We + # will unstash them when we move to battle three. + self.evWalls.stash() + + # Also replace the floor polygon with a plane, and rename it + # so we can detect a collision with it. + floor = self.endVault.find('**/EndVaultFloorCollision') + floor.detachNode() + self.evFloor = self.replaceCollisionPolysWithPlanes(floor) + self.evFloor.reparentTo(self.endVault) + self.evFloor.setName('floor') + + # Also, put a big plane across the universe a few feet below + # the floor, to catch things that fall out of the world. + plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -50))) + planeNode = CollisionNode('dropPlane') + planeNode.addSolid(plane) + planeNode.setCollideMask(ToontownGlobals.PieBitmask) + self.geom.attachNewNode(planeNode) + + self.geom.reparentTo(render) + + def unloadEnvironment(self): + DistributedBossCog.DistributedBossCog.unloadEnvironment(self) + + self.geom.removeNode() + + def replaceCollisionPolysWithPlanes(self, model): + newCollisionNode = CollisionNode('collisions') + newCollideMask = BitMask32(0) + planes = [] + + collList = model.findAllMatches('**/+CollisionNode') + if not collList: + collList = [model] + + for cnp in collList: + cn = cnp.node() + if not isinstance(cn, CollisionNode): + self.notify.warning("Not a collision node: %s" % (repr(cnp))) + break + + newCollideMask = newCollideMask | cn.getIntoCollideMask() + for i in range(cn.getNumSolids()): + solid = cn.getSolid(i) + if isinstance(solid, CollisionPolygon): + # Save the plane defined by this polygon + plane = Plane(solid.getPlane()) + planes.append(plane) + else: + self.notify.warning("Unexpected collision solid: %s" % (repr(solid))) + newCollisionNode.addSolid(plane) + + newCollisionNode.setIntoCollideMask(newCollideMask) + + # Now sort all of the planes and remove the nonunique ones. + # We can't use traditional dictionary-based tricks, because we + # want to use Plane.compareTo(), not Plane.__hash__(), to make + # the comparison. + threshold = 0.1 + planes.sort(lambda p1, p2: p1.compareTo(p2, threshold)) + lastPlane = None + for plane in planes: + if lastPlane == None or plane.compareTo(lastPlane, threshold) != 0: + cp = CollisionPlane(plane) + newCollisionNode.addSolid(cp) + lastPlane = plane + + return NodePath(newCollisionNode) + + + def __makeGoonMovieForIntro(self): + goonTrack = Parallel() + goon = self.fakeGoons[0] + goonTrack.append( + Sequence(goon.posHprInterval(0, Point3(111, -287, 0), VBase3(165, 0, 0)), + goon.posHprInterval(9, Point3(101, -323, 0), VBase3(165, 0, 0)), + goon.hprInterval(1, VBase3(345, 0, 0)), + goon.posHprInterval(9, Point3(111, -287, 0), VBase3(345, 0, 0)), + goon.hprInterval(1, VBase3(165, 0, 0)), + goon.posHprInterval(9.5, Point3(104, -316, 0), VBase3(165, 0, 0)), + #Wait(3.5), + Func(goon.request, 'Stunned'), + Wait(1), + ) + ) + goon = self.fakeGoons[1] + goonTrack.append( + Sequence(goon.posHprInterval(0, Point3(119, -315, 0), VBase3(357, 0, 0)), + goon.posHprInterval(9, Point3(121, -280, 0), VBase3(357, 0, 0)), + goon.hprInterval(1, VBase3(177, 0, 0)), + goon.posHprInterval(9, Point3(119, -315, 0), VBase3(177, 0, 0)), + goon.hprInterval(1, VBase3(357, 0, 0)), + goon.posHprInterval(9, Point3(121, -280, 0), VBase3(357, 0, 0)), + ) + ) + goon = self.fakeGoons[2] + goonTrack.append( + Sequence(goon.posHprInterval(0, Point3(102, -320, 0), VBase3(231, 0, 0)), + goon.posHprInterval(9, Point3(127, -337, 0), VBase3(231, 0, 0)), + goon.hprInterval(1, VBase3(51, 0, 0)), + goon.posHprInterval(9, Point3(102, -320, 0), VBase3(51, 0, 0)), + goon.hprInterval(1, VBase3(231, 0, 0)), + goon.posHprInterval(9, Point3(127, -337, 0), VBase3(231, 0, 0)), + ) + ) + return Sequence(Func(self.__showFakeGoons, 'Walk'), + goonTrack, + Func(self.__hideFakeGoons)) + + def makeIntroductionMovie(self, delayDeletes): + + # Generate an interval which shows the toons meeting the + # Resistance Toon, and the introduction of the CFO, etc., + # leading to the events of battle one. + + # We need to protect our movie against any of the toons + # disconnecting while the movie plays. + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete( + toon, 'CashbotBoss.makeIntroductionMovie')) + + rtTrack = Sequence() + + startPos = Point3(ToontownGlobals.CashbotBossOffstagePosHpr[0], + ToontownGlobals.CashbotBossOffstagePosHpr[1], + ToontownGlobals.CashbotBossOffstagePosHpr[2]) + battlePos = Point3(ToontownGlobals.CashbotBossBattleOnePosHpr[0], + ToontownGlobals.CashbotBossBattleOnePosHpr[1], + ToontownGlobals.CashbotBossBattleOnePosHpr[2]) + battleHpr = VBase3(ToontownGlobals.CashbotBossBattleOnePosHpr[3], + ToontownGlobals.CashbotBossBattleOnePosHpr[4], + ToontownGlobals.CashbotBossBattleOnePosHpr[5]) + + bossTrack = Sequence() + + # Put him on stage. + bossTrack.append(Func(self.reparentTo, render)) + bossTrack.append(Func(self.getGeomNode().setH, 180)) + bossTrack.append(Func(self.pelvis.setHpr, self.pelvisForwardHpr)) + bossTrack.append(Func(self.loop, 'Ff_neutral')) + + track, hpr = self.rollBossToPoint(startPos, None, battlePos, None, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(battlePos, hpr, battlePos, battleHpr, 0) + bossTrack.append(track) + + bossTrack.append(Func(self.getGeomNode().setH, 0)) + bossTrack.append(Func(self.pelvis.setHpr, self.pelvisReversedHpr)) + + # Create a track for the fake goons + goonTrack = self.__makeGoonMovieForIntro() + + attackToons = TTL.CashbotBossCogAttack + + rToon = self.resistanceToon + rToon.setPosHpr(*ToontownGlobals.CashbotRTBattleOneStartPosHpr) + + track = Sequence( + #Cut to resistance toon + Func(camera.setPosHpr, 82, -219, 5, 267, 0, 0), + Func(rToon.setChatAbsolute, TTL.ResistanceToonWelcome, CFSpeech), + Wait(3), + + #start the goons on their paths + Sequence(goonTrack, duration=0), + #Func(goonTrack.start), + + #RT runs to the endvault door and opens it + Parallel(camera.posHprInterval(4, Point3(108, -244, 4), VBase3(211.5, 0, 0)), + Sequence(Func(rToon.suit.setPlayRate, 1.4, 'walk'), + Func(rToon.suit.loop, 'walk'), + Parallel(rToon.hprInterval(1, VBase3(180, 0, 0)), + rToon.posInterval(3, VBase3(120, -255, 0)), + Sequence(Wait(2), #clear the chat 2 seconds in + Func(rToon.clearChat), + ), + ), + Func(rToon.suit.loop, 'neutral'), + self.door2.posInterval(3, VBase3(0, 0, 30)), + ), + ), + + + + #Cut to the CFO rolling in + Func(rToon.setHpr, 0, 0, 0), + Func(rToon.setChatAbsolute, TTL.ResistanceToonTooLate, CFSpeech), + Func(camera.reparentTo, render), + Func(camera.setPosHpr, 61.1, -228.8, 10.2, -90, 0, 0), + #Open the CFO door + self.door1.posInterval(2, VBase3(0, 0, 30)), + #Roll the CFO in and close the door + Parallel(bossTrack, + Sequence(Wait(3), + Func(rToon.clearChat), + self.door1.posInterval(3, VBase3(0, 0, 0))), + ), + + + #Close-up of the CFO... + Func(self.setChatAbsolute, TTL.CashbotBossDiscoverToons1, CFSpeech), + camera.posHprInterval(1.5, Point3(93.3, -230, 0.7), VBase3(-92.9, 39.7, 8.3)), + Func(self.setChatAbsolute, TTL.CashbotBossDiscoverToons2, CFSpeech), + Wait(4), + + # Cut to toons losing their cog suits. + Func(self.clearChat), + self.loseCogSuits(self.toonsA + self.toonsB, render, (113,-228,10,90,0,0)), + Wait(1), + Func(rToon.setHpr, 0, 0, 0), + self.loseCogSuits([rToon], render, (133,-243,5,143,0,0), True), + + #RT tells the toons to fight and runs off to open the door + Func(rToon.setChatAbsolute, TTL.ResistanceToonKeepHimBusy, CFSpeech), + Wait(1), + Func(self.__showResistanceToon, False), #this turns off his cog suit... + Sequence(Func(rToon.animFSM.request, 'run'), + rToon.hprInterval(1, VBase3(180, 0, 0)), #turn him around + Parallel(Sequence(rToon.posInterval(1.5, VBase3(109, -294, 0)), + Parallel(Func(rToon.animFSM.request, 'jump')), + rToon.posInterval(1.5, VBase3(93.935, -341.065, 2)), + ), + self.door2.posInterval(3, VBase3(0, 0, 0))), + Func(rToon.animFSM.request, 'neutral'), + ), + + #clean up the toons' eyes + self.toonNormalEyes(self.involvedToons), + self.toonNormalEyes([self.resistanceToon], True), + + #cut back to the CFO and move the toons in to place + Func(rToon.clearChat), + Func(camera.setPosHpr, 93.3, -230, 0.7, -92.9, 39.7, 8.3), + + Func(self.setChatAbsolute, attackToons, CFSpeech), + Wait(2), + Func(self.clearChat), + ) + + return Sequence(Func(camera.reparentTo, render), + track) + + def __makeGoonMovieForBattleThree(self): + #start them each walking back and forth between 2 points + goonPosHprs = [ + [Point3(111, -287, 0), VBase3(165, 0, 0), + Point3(101, -323, 0), VBase3(165, 0, 0) ], + [Point3(119, -315, 0), VBase3(357, 0, 0), + Point3(121, -280, 0), VBase3(357, 0, 0) ], + [Point3(102, -320, 0), VBase3(231, 0, 0), + Point3(127, -337, 0), VBase3(231, 0, 0) ] + ] + + #we're gonna leave this guy on the side... stunned + mainGoon = self.fakeGoons[0] + + goonLoop = Parallel() + print self.fakeGoons + for i in range(1, self.numFakeGoons): + #print i + goon = self.fakeGoons[i] + goonLoop.append( + Sequence(goon.posHprInterval(8, goonPosHprs[i][0], goonPosHprs[i][1]), + goon.posHprInterval(8, goonPosHprs[i][2], goonPosHprs[i][3]), + ) + ) + + goonTrack = Sequence( + Func(self.__showFakeGoons, 'Walk'), + Func(mainGoon.request, 'Stunned'), + Func(goonLoop.loop), + Wait(20), + #Func(self.__hideFakeGoons), + ) + return goonTrack + + def makePrepareBattleThreeMovie(self, delayDeletes, crane, safe): + + # Generate an interval which shows the toons meeting the + # Resistance Toon, and the introduction of the CFO, etc., + # leading to the events of battle one. + + # We need to protect our movie against any of the toons + # disconnecting while the movie plays. + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete( + toon, 'CashbotBoss.makePrepareBattleThreeMovie')) + + startPos = Point3(ToontownGlobals.CashbotBossBattleOnePosHpr[0], + ToontownGlobals.CashbotBossBattleOnePosHpr[1], + ToontownGlobals.CashbotBossBattleOnePosHpr[2]) + battlePos = Point3(ToontownGlobals.CashbotBossBattleThreePosHpr[0], + ToontownGlobals.CashbotBossBattleThreePosHpr[1], + ToontownGlobals.CashbotBossBattleThreePosHpr[2]) + startHpr = Point3(ToontownGlobals.CashbotBossBattleOnePosHpr[3], + ToontownGlobals.CashbotBossBattleOnePosHpr[4], + ToontownGlobals.CashbotBossBattleOnePosHpr[5]) + battleHpr = VBase3(ToontownGlobals.CashbotBossBattleThreePosHpr[3], + ToontownGlobals.CashbotBossBattleThreePosHpr[4], + ToontownGlobals.CashbotBossBattleThreePosHpr[5]) + finalHpr = VBase3(135, 0, 0) + + bossTrack = Sequence() + + bossTrack.append(Func(self.reparentTo, render)) + bossTrack.append(Func(self.getGeomNode().setH, 180)) + bossTrack.append(Func(self.pelvis.setHpr, self.pelvisForwardHpr)) + bossTrack.append(Func(self.loop, 'Ff_neutral')) + + track, hpr = self.rollBossToPoint(startPos, startHpr, startPos, battleHpr, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(startPos, None, battlePos, None, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(battlePos, battleHpr, battlePos, finalHpr, 0) + bossTrack.append(track) + + #bossTrack.append(Func(self.getGeomNode().setH, 0)) + + #grab the resistance toon and put him in his starting spot + rToon = self.resistanceToon + rToon.setPosHpr(93.935, -341.065, 0, -45, 0, 0) + + #get a crane/goon to play with + goon = self.fakeGoons[0] + crane = self.cranes[0] + + #craneTrack = Sequence(crane.crane.posInterval(1, Point3(0, 30, 0)), + # Wait(0.5), + # crane.crane.posInterval(1, Point3(0, 20, 0)), + # Wait(0.5), + # ) + + # create a track for the goons in the scene (this only goes to the + # point of interaction with the resistance toon + #goonTrack = self.__makeGoonMovieForBattleThree() + + #build the sequence + track = Sequence( + Func(self.__hideToons), + #set up the crane and goon + Func(crane.request, 'Movie'), + #Func(goon.request, 'LocalGrabbed', -1, crane.doId), + Func(crane.accomodateToon, rToon), + Func(goon.request, 'Stunned'), + Func(goon.setPosHpr, 104, -316, 0, 165, 0, 0), + + #open the door and roll the boss through + Parallel(self.door2.posInterval(4.5, VBase3(0, 0, 30)), + self.door3.posInterval(4.5, VBase3(0, 0, 30)), + bossTrack, + ), + + #Cut to the resistance toon... he's gonna show the players something + Func(rToon.loop, 'leverNeutral'), + Func(camera.reparentTo, self.geom), + Func(camera.setPosHpr, 105, -326, 5, 136.3, 0, 0), + Func(rToon.setChatAbsolute, TTL.ResistanceToonWatchThis, CFSpeech), + Wait(2), + Func(rToon.clearChat), + + #Cut to the CFO telling the RT to knock it off + Func(camera.setPosHpr, 105, -326, 20, -45.3, 11, 0), + Func(self.setChatAbsolute, TTL.CashbotBossGetAwayFromThat, CFSpeech), + Wait(2), + Func(self.clearChat), + + #The RT is having fun... cut to him doing the safe thing + camera.posHprInterval(1.5, Point3(105, -326, 5), + Point3(136.3, 0, 0), + blendType='easeInOut'), + + #tell em what to do + Func(rToon.setChatAbsolute, TTL.ResistanceToonCraneInstructions1, CFSpeech), + Wait(4), + + Func(rToon.setChatAbsolute, TTL.ResistanceToonCraneInstructions2, CFSpeech), + Wait(4), + + Func(rToon.setChatAbsolute, TTL.ResistanceToonCraneInstructions3, CFSpeech), + Wait(4), + + Func(rToon.setChatAbsolute, TTL.ResistanceToonCraneInstructions4, CFSpeech), + Wait(4), + + Func(rToon.clearChat), + + # Cut to the recovering goon + Func(camera.setPosHpr, 102, -323.6, 0.9, -10.6, 14, 0), + Func(goon.request, 'Recovery'), + Wait(2), + + # Cut to the surprised resistance toon + Func(camera.setPosHpr, 95.4, -332.6, 4.2, 167.1, -13.2, 0), + Func(rToon.setChatAbsolute, TTL.ResistanceToonGetaway, CFSpeech), + Func(rToon.animFSM.request, 'jump'), + Wait(1.8), + Func(rToon.clearChat), + + #Cut to the goon chasing rtoon... close the door + Func(camera.setPosHpr, 109.1, -300.7, 13.9, -15.6, -13.6, 0), + Func(rToon.animFSM.request, 'run'), + Func(goon.request, 'Walk'), + Parallel(self.door3.posInterval(3, VBase3(0, 0, 0)), + rToon.posHprInterval(3, Point3(136, -212.9, 0), VBase3(-14,0,0), + startPos = Point3(110.8, -292.7, 0), + startHpr = VBase3(-14, 0, 0)), + goon.posHprInterval(3, Point3(125.2, -243.5, 0), VBase3(-14,0,0), + startPos = Point3(104.8, -309.5, 0), + startHpr = VBase3(-14, 0, 0)), + ), + + Func(self.__hideFakeGoons), + Func(crane.request, 'Free'), + + #fix the CFO's orientation + Func(self.getGeomNode().setH, 0), + + #move the toons to their positions + self.moveToonsToBattleThreePos(self.involvedToons), + + Func(self.__showToons), + ) + + return Sequence(Func(camera.reparentTo, self), + Func(camera.setPosHpr, 0, -27, 25, 0, -18, 0), + track) + + def moveToonsToBattleThreePos(self, toons): + track = Parallel() + for i in range(len(toons)): + toon = base.cr.doId2do.get(toons[i]) + if toon: + posHpr = ToontownGlobals.CashbotToonsBattleThreeStartPosHpr[i] + pos = Point3(*posHpr[0:3]) + hpr = VBase3(*posHpr[3:6]) + track.append( + toon.posHprInterval(0.2, pos, hpr) + ) + + return track + + def makeBossFleeMovie(self): + # Generate an interval which shows the boss giving up and + # running out the door, only to be nailed by a passing train. + + hadEnough = TTLocalizer.CashbotBossHadEnough + outtaHere = TTLocalizer.CashbotBossOuttaHere + + loco = loader.loadModel('phase_10/models/cogHQ/CashBotLocomotive') + car1 = loader.loadModel('phase_10/models/cogHQ/CashBotBoxCar') + car2 = loader.loadModel('phase_10/models/cogHQ/CashBotTankCar') + trainPassingSfx = base.loadSfx('phase_10/audio/sfx/CBHQ_TRAIN_pass.mp3') + boomSfx = loader.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart.mp3') + + rollThroughDoor = self.rollBossToPoint( + fromPos = Point3(120, -280, 0), fromHpr = None, + toPos = Point3(120, -250, 0), toHpr = None, + reverse = 0) + rollTrack = Sequence( + Func(self.getGeomNode().setH, 180), + rollThroughDoor[0], + Func(self.getGeomNode().setH, 0)) + + # Generate a track that shows one long train running by (which + # it achieves by running the same two cars repeatedly past + # the door). + + # The trains move at 300 ft/s, so a gap of this much time puts + # car2 80 ft behind car1. + g = 80.0/300.0 + trainTrack = Track( + (0*g, loco.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (1*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (2*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (3*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (4*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (5*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (6*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (7*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (8*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (9*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (10*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (11*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (12*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (13*g, car2.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + (14*g, car1.posInterval(0.5, Point3(0, -242, 0), startPos=Point3(150, -242, 0))), + ) + + bossTrack = Track( + (0.0, Sequence(Func(camera.reparentTo, render), + Func(camera.setPosHpr, 105, -280, 20, -158, -3, 0), + Func(self.reparentTo, render), + Func(self.show), + Func(self.clearChat), + Func(self.setPosHpr, *ToontownGlobals.CashbotBossBattleThreePosHpr), + Func(self.reverseHead), + ActorInterval(self, 'Fb_firstHit'), + ActorInterval(self, 'Fb_down2Up'))), + (1.0, Func(self.setChatAbsolute, hadEnough, CFSpeech)), + (5.5, Parallel(Func(camera.setPosHpr, 100, -315, 16, -20, 0, 0), + Func(self.hideBattleThreeObjects), + Func(self.forwardHead), + Func(self.loop, 'Ff_neutral'), + rollTrack, + self.door3.posInterval(2.5, Point3(0, 0, 25), + startPos = Point3(0, 0, 18)), + )), + (5.5, Func(self.setChatAbsolute, outtaHere, CFSpeech)), + (5.5, SoundInterval(trainPassingSfx)), + (8.1, Func(self.clearChat)), + (9.4, Sequence(Func(loco.reparentTo, render), + Func(car1.reparentTo, render), + Func(car2.reparentTo, render), + trainTrack, + Func(loco.detachNode), + Func(car1.detachNode), + Func(car2.detachNode), + Wait(2))), + (9.5, SoundInterval(boomSfx)), + (9.5, Sequence(self.posInterval(0.4, Point3(0, -250, 0)), + Func(self.stash))), + ) + + return bossTrack + + def grabObject(self, obj): + # Grab a safe and put it on as a helmet. This method mirrors + # a similar method on DistributedCashbotBossCrane.py; it goes + # through the same API as a crane picking up a safe. + + # This is only called by DistributedCashbotBossObject.enterGrabbed(). + assert self.heldObject == None + + obj.wrtReparentTo(self.neck) + obj.hideShadows() + obj.stashCollisions() + + if obj.lerpInterval: + obj.lerpInterval.finish() + + obj.lerpInterval = Parallel( + obj.posInterval(ToontownGlobals.CashbotBossToMagnetTime, Point3(-1, 0, 0.2)), + obj.quatInterval(ToontownGlobals.CashbotBossToMagnetTime, VBase3(0, -90, 90)), + Sequence(Wait(ToontownGlobals.CashbotBossToMagnetTime), ShowInterval(self.eyes)), + obj.toMagnetSoundInterval) + obj.lerpInterval.start() + + self.heldObject = obj + + def dropObject(self, obj): + # Drop a helmet on the ground. + + # This is only called by DistributedCashbotBossObject.exitGrabbed(). + assert self.heldObject == obj + + if obj.lerpInterval: + obj.lerpInterval.finish() + obj.lerpInterval = None + + obj = self.heldObject + obj.wrtReparentTo(render) + obj.setHpr(obj.getH(), 0, 0) + self.eyes.hide() + + # Actually, we shouldn't reveal the shadows until it + # reaches the ground again. This will do for now. + obj.showShadows() + obj.unstashCollisions() + + self.heldObject = None + + def setBossDamage(self, bossDamage): + if bossDamage > self.bossDamage: + delta = bossDamage - self.bossDamage + self.flashRed() + self.doAnimate('hit', now=1) + self.showHpText(-delta, scale = 5) + + self.bossDamage = bossDamage + self.updateHealthBar() + + def setRewardId(self, rewardId): + self.rewardId = rewardId + + def d_applyReward(self): + self.sendUpdate('applyReward', []) + + def stunAllGoons(self): + # This is called by the "~bossBattle stun" magic word only. + # It stuns all of the goons in the final battle sequence. + + for goon in self.goons: + if goon.state == 'Walk' or goon.state == 'Battle': + goon.demand("Stunned") + goon.sendUpdate("requestStunned", [0]) + + def destroyAllGoons(self): + # This is called by the "~bossBattle destroy" magic word only. + # It destroys all of the goons in the final battle sequence. + + for goon in self.goons: + if goon.state != 'Off' and not goon.isDead: + goon.b_destroyGoon() + + def deactivateCranes(self): + # This locally knocks all toons off cranes. + for crane in self.cranes.values(): + crane.demand("Free") + + def hideBattleThreeObjects(self): + # This turns off all the goons, safes, and cranes on the local + # client. It's played only during the victory movie, to get + # these guys out of the way. + + for goon in self.goons: + goon.demand("Off") + + for safe in self.safes.values(): + safe.demand("Off") + + for crane in self.cranes.values(): + crane.demand("Off") + + def __doPhysics(self, task): + dt = globalClock.getDt() + self.physicsMgr.doPhysics(dt) + return Task.cont + + def __hideToons(self): + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.hide() + + def __showToons(self): + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.show() + + def __arrangeToonsAroundResistanceToon(self): + radius = 7 + numToons = len(self.involvedToons) + center = (numToons - 1) / 2.0 + for i in range(numToons): + toon = self.cr.doId2do.get(self.involvedToons[i]) + if toon: + angle = 90 - 15 * (i - center) + + radians = angle * math.pi / 180.0 + x = math.cos(radians) * radius + y = math.sin(radians) * radius + toon.setPos(self.resistanceToon, x, y, 0) + toon.headsUp(self.resistanceToon) + toon.loop('neutral') + toon.show() + + def __talkAboutPromotion(self, speech): + # Extends the congratulations speech to talk about the earned + # promotion, if any. Returns the newly-extended speech. + + # don't say anything about a promotion if they've maxed their cog suit + if self.prevCogSuitLevel < ToontownGlobals.MaxCogSuitLevel: + newCogSuitLevel = localAvatar.getCogLevels()[ + CogDisguiseGlobals.dept2deptIndex(self.style.dept)] + # if this is their last promotion, tell them + if newCogSuitLevel == ToontownGlobals.MaxCogSuitLevel: + speech += TTLocalizer.ResistanceToonLastPromotion % (ToontownGlobals.MaxCogSuitLevel+1) + # if they're getting another LP, tell them + if newCogSuitLevel in ToontownGlobals.CogSuitHPLevels: + speech += TTLocalizer.ResistanceToonHPBoost + else: + # level XX, wow! Thanks for coming back! + speech += TTLocalizer.ResistanceToonMaxed % (ToontownGlobals.MaxCogSuitLevel+1) + + return speech + + ##### Off state ##### + + def enterOff(self): + DistributedBossCog.DistributedBossCog.enterOff(self) + + if self.resistanceToon: + self.resistanceToon.clearChat() + + ##### WaitForToons state ##### + + def enterWaitForToons(self): + DistributedBossCog.DistributedBossCog.enterWaitForToons(self) + + self.detachNode() + self.geom.hide() + + # Disable the caged toon's nametag while we're here in space + # waiting. + self.resistanceToon.removeActive() + + def exitWaitForToons(self): + DistributedBossCog.DistributedBossCog.exitWaitForToons(self) + + self.geom.show() + self.resistanceToon.addActive() + + + ##### Elevator state ##### + + def enterElevator(self): + DistributedBossCog.DistributedBossCog.enterElevator(self) + + # The CFO himself is offstage at this point. + self.detachNode() + + # Disable the caged toon's nametag while we're in the + # elevator. + self.resistanceToon.removeActive() + + self.endVault.stash() + self.midVault.unstash() + self.__showResistanceToon(True) + + def exitElevator(self): + DistributedBossCog.DistributedBossCog.exitElevator(self) + + self.resistanceToon.addActive() + + ##### Introduction state ##### + + def enterIntroduction(self): + # Initially, the boss cog is offstage. + self.detachNode() + self.stopAnimate() + + self.endVault.unstash() + self.evWalls.stash() + self.midVault.unstash() + self.__showResistanceToon(True) + + base.playMusic(self.stingMusic, looping=1, volume=0.9) + + DistributedBossCog.DistributedBossCog.enterIntroduction(self) + + def exitIntroduction(self): + DistributedBossCog.DistributedBossCog.exitIntroduction(self) + + self.stingMusic.stop() + + ##### BattleOne state ##### + + def enterBattleOne(self): + DistributedBossCog.DistributedBossCog.enterBattleOne(self) + + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.CashbotBossBattleOnePosHpr) + self.show() + self.pelvis.setHpr(self.pelvisReversedHpr) + self.doAnimate() + + self.endVault.stash() + self.midVault.unstash() + self.__hideResistanceToon() + + def exitBattleOne(self): + DistributedBossCog.DistributedBossCog.exitBattleOne(self) + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + assert self.notify.debug('enterPrepareBattleThree()') + + self.controlToons() + + NametagGlobals.setMasterArrowsOn(0) + + intervalName = "PrepareBattleThreeMovie" + delayDeletes = [] + + #grab a crane and put it in to Movie mode + self.movieCrane = self.cranes[0] + self.movieSafe = self.safes[1] + self.movieCrane.request('Movie') + + seq = Sequence(self.makePrepareBattleThreeMovie(delayDeletes, + self.movieCrane, + self.movieSafe), + Func(self.__beginBattleThree), + name = intervalName) + seq.delayDeletes = delayDeletes + seq.start() + self.storeInterval(seq, intervalName) + + self.endVault.unstash() + self.evWalls.stash() + self.midVault.unstash() + self.__showResistanceToon(False) + + taskMgr.add(self.__doPhysics, self.uniqueName('physics'), + priority = 25) + + def __beginBattleThree(self): + intervalName = "PrepareBattleThreeMovie" + self.clearInterval(intervalName) + + self.doneBarrier('PrepareBattleThree') + + def exitPrepareBattleThree(self): + intervalName = "PrepareBattleThreeMovie" + self.clearInterval(intervalName) + self.unstickToons() + self.releaseToons() + + if self.newState == 'BattleThree': + self.movieCrane.request('Free') + self.movieSafe.request('Initial') + + NametagGlobals.setMasterArrowsOn(1) + + # Make sure the elevator doors are closed. + ElevatorUtils.closeDoors(self.leftDoor, self.rightDoor, ElevatorConstants.ELEVATOR_CFO) + + taskMgr.remove(self.uniqueName('physics')) + + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('enterBattleThree()') + DistributedBossCog.DistributedBossCog.enterBattleThree(self) + + self.clearChat() + self.resistanceToon.clearChat() + self.reparentTo(render) + + self.setPosHpr(*ToontownGlobals.CashbotBossBattleThreePosHpr) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + self.doAnimate() + + self.endVault.unstash() + self.evWalls.unstash() + self.midVault.stash() + self.__hideResistanceToon() + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + self.generateHealthBar() + self.updateHealthBar() + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9) + + # It is important to make sure this task runs immediately + # before the collisionLoop of ShowBase. That will fix up the + # z value of the safes, etc., before their position is + # distributed. + taskMgr.add(self.__doPhysics, self.uniqueName('physics'), + priority = 25) + + def exitBattleThree(self): + DistributedBossCog.DistributedBossCog.exitBattleThree(self) + bossDoneEventName = self.uniqueName('DestroyedBoss') + self.ignore(bossDoneEventName) + + self.stopAnimate() + self.cleanupAttacks() + self.setDizzy(0) + + self.removeHealthBar() + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + if self.newState != 'Victory': + self.battleThreeMusic.stop() + + taskMgr.remove(self.uniqueName('physics')) + + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('enterVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + + # Boss Cog flees out the door and gets nailed by a passing + # train. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.CashbotBossBattleThreePosHpr) + self.stopAnimate() + + self.endVault.unstash() + self.evWalls.unstash() + self.midVault.unstash() + self.__hideResistanceToon() + self.__hideToons() + + self.clearChat() + self.resistanceToon.clearChat() + + # Important to knock toons off cranes before we control them. + self.deactivateCranes() + + # This particular crane might get in the way of the camera. + if self.cranes: + self.cranes[1].demand('Off') + + # No one owns the toons. + self.releaseToons(finalBattle = 1) + + # But, we do need to be in movie mode. + if self.hasLocalToon(): + self.toMovieMode() + + intervalName = "VictoryMovie" + + seq = Sequence(self.makeBossFleeMovie(), + Func(self.__continueVictory), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + if self.oldState != 'BattleThree': + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9) + + def __continueVictory(self): + # Ok, he's gone! We all move to the reward movie. + + self.doneBarrier('Victory') + + def exitVictory(self): + self.cleanupIntervals() + + # We don't want to release the local toon if we're moving on + # to the Reward state, since they need to keep dancing. + if self.newState != 'Reward': + if self.hasLocalToon(): + self.toWalkMode() + + self.__showToons() + + self.door3.setPos(0, 0, 0) + + if self.newState != 'Reward': + self.battleThreeMusic.stop() + + ##### Reward state ##### + + def enterReward(self): + assert self.notify.debug('enterReward()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.resistanceToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + self.controlToons() + + # Start the reward movie playing. + + panelName = self.uniqueName('reward') + self.rewardPanel = RewardPanel.RewardPanel(panelName) + (victory, camVictory) = MovieToonVictory.doToonVictory( + 1, self.involvedToons, + self.toonRewardIds, + self.toonRewardDicts, + self.deathList, + self.rewardPanel, + allowGroupShot = 0, + uberList = self.uberList) + + ival = Sequence( + Parallel(victory, camVictory), + Func(self.__doneReward)) + + intervalName = "RewardMovie" + delayDeletes = [] + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete( + toon, 'CashbotBoss.enterReward')) + + ival.delayDeletes = delayDeletes + ival.start() + self.storeInterval(ival, intervalName) + + if self.oldState != 'Victory': + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9) + + def __doneReward(self): + self.doneBarrier('Reward') + self.toWalkMode() + + def exitReward(self): + intervalName = "RewardMovie" + self.clearInterval(intervalName) + + if self.newState != 'Epilogue': + self.releaseToons() + + self.unstash() + self.rewardPanel.destroy() + del self.rewardPanel + + self.battleThreeMusic.stop() + + ##### Epilogue state ##### + + def enterEpilogue(self): + assert self.notify.debug('enterEpilogue()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.resistanceToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + # The toons are under our control once again. + self.controlToons() + + self.__showResistanceToon(False) + self.resistanceToon.setPosHpr(*ToontownGlobals.CashbotBossBattleThreePosHpr) + self.resistanceToon.loop('neutral') + + self.__arrangeToonsAroundResistanceToon() + camera.reparentTo(render) + camera.setPos(self.resistanceToon, -9, 12, 6) + camera.lookAt(self.resistanceToon, 0, 0, 3) + + intervalName = "EpilogueMovie" + + text = ResistanceChat.getChatText(self.rewardId) + menuIndex, itemIndex = ResistanceChat.decodeId(self.rewardId) + value = ResistanceChat.getItemValue(self.rewardId) + if menuIndex == ResistanceChat.RESISTANCE_TOONUP: + if value == -1: + instructions = TTLocalizer.ResistanceToonToonupAllInstructions + else: + instructions = TTLocalizer.ResistanceToonToonupInstructions % ( + value) + + elif menuIndex == ResistanceChat.RESISTANCE_MONEY: + if value == -1: + instructions = TTLocalizer.ResistanceToonMoneyAllInstructions + else: + instructions = TTLocalizer.ResistanceToonMoneyInstructions % ( + value) + + elif menuIndex == ResistanceChat.RESISTANCE_RESTOCK: + if value == -1: + instructions = TTLocalizer.ResistanceToonRestockAllInstructions + else: + trackName = TTLocalizer.BattleGlobalTracks[value] + instructions = TTLocalizer.ResistanceToonRestockInstructions % ( + trackName) + + speech = TTLocalizer.ResistanceToonCongratulations % ( + text, instructions) + speech = self.__talkAboutPromotion(speech) + + self.resistanceToon.setLocalPageChat(speech, 0) + + self.accept("nextChatPage", self.__epilogueChatNext) + self.accept("doneChatPage", self.__epilogueChatDone) + + base.playMusic(self.epilogueMusic, looping=1, volume=0.9) + + def __epilogueChatNext(self, pageNumber, elapsed): + if pageNumber == 1: + # "You're an asset to the Resistance." + + # We don't use the code in TTEmote here, because that + # forces the toon to say stuff. So we just steal the + # ActorIntervals that apply the animation. + toon = self.resistanceToon + playRate = 0.75 + track = Sequence(ActorInterval(toon, 'victory', playRate = playRate, startFrame = 0, endFrame = 9), + ActorInterval(toon, 'victory', playRate = playRate, startFrame = 9, endFrame = 0), + Func(self.resistanceToon.loop, 'neutral')) + intervalName = "EpilogueMovieToonAnim" + self.storeInterval(track, intervalName) + track.start() + + elif pageNumber == 3: + # Page 3 is the special resistance chat. Apply it. + self.d_applyReward() + ResistanceChat.doEffect(self.rewardId, self.resistanceToon, self.involvedToons) + + def __epilogueChatDone(self, elapsed): + assert self.notify.debug('epilogueChatDone()') + self.resistanceToon.setChatAbsolute(TTLocalizer.CagedToonGoodbye, CFSpeech) + + self.ignore("nextChatPage") + self.ignore("doneChatPage") + + intervalName = "EpilogueMovieToonAnim" + self.clearInterval(intervalName) + track = Parallel( + Sequence(ActorInterval(self.resistanceToon, 'wave'), + Func(self.resistanceToon.loop, 'neutral')), + Sequence(Wait(0.5), + Func(self.localToonToSafeZone))) + self.storeInterval(track, intervalName) + track.start() + + def exitEpilogue(self): + self.clearInterval("EpilogueMovieToonAnim") + self.unstash() + + self.epilogueMusic.stop() + + ##### Frolic state ##### + + # This state is probably only useful for debugging. The toons are + # all free to run around the world. + + def enterFrolic(self): + DistributedBossCog.DistributedBossCog.enterFrolic(self) + self.setPosHpr(*ToontownGlobals.CashbotBossBattleOnePosHpr) + + self.releaseToons() + if self.hasLocalToon(): + self.toWalkMode() + + self.door3.setZ(25) + self.door2.setZ(25) + + self.endVault.unstash() + self.evWalls.stash() + self.midVault.unstash() + self.__hideResistanceToon() + + def exitFrolic(self): + self.door3.setZ(0) + self.door2.setZ(0) diff --git a/toontown/src/suit/DistributedCashbotBossAI.py b/toontown/src/suit/DistributedCashbotBossAI.py new file mode 100644 index 0000000..69c6d8e --- /dev/null +++ b/toontown/src/suit/DistributedCashbotBossAI.py @@ -0,0 +1,707 @@ +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.coghq import DistributedCashbotBossCraneAI +from toontown.coghq import DistributedCashbotBossSafeAI +from toontown.suit import DistributedCashbotBossGoonAI +from toontown.coghq import DistributedCashbotBossTreasureAI +from toontown.battle import BattleExperienceAI +from toontown.chat import ResistanceChat +from direct.fsm import FSM +import DistributedBossCogAI +import SuitDNA +import random +import math + +class DistributedCashbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedCashbotBossAI') + + maxGoons = 8 + + def __init__(self, air): + DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'm') + FSM.FSM.__init__(self, 'DistributedCashbotBossAI') + self.cranes = None + self.safes = None + self.goons = None + self.treasures = {} + self.grabbingTreasures = {} + self.recycledTreasures = [] + + # The treasures will look for this quantity on their + # "TreasurePlanner", which is the boss cog. This is just the + # initial default value; we reassign each treasure + # differently. + self.healAmount = 0 + + # Choose a random reward up front in case they succeed. + self.rewardId = ResistanceChat.getRandomId() + self.rewardedToons = [] + + # We need a scene to do the collision detection in. + self.scene = NodePath('scene') + self.reparentTo(self.scene) + + # And some solids to keep the goons constrained to our room. + cn = CollisionNode('walls') + cs = CollisionSphere(0, 0, 0, 13) + cn.addSolid(cs) + cs = CollisionInvSphere(0, 0, 0, 42) + cn.addSolid(cs) + self.attachNewNode(cn) + + + # By "heldObject", we mean the safe he's currently wearing as + # a helmet, if any. It's called a heldObject because this is + # the way the cranes refer to the same thing, and we use the + # same interface to manage this. + self.heldObject = None + + self.waitingForHelmet = 0 + self.avatarHelmets = {} + + self.bossMaxDamage = ToontownGlobals.CashbotBossMaxDamage + + def generate(self): + """ + Inheritors should put functions that require self.zoneId or + other networked info in this function. + """ + DistributedBossCogAI.DistributedBossCogAI.generate(self) + + if __dev__: + self.scene.reparentTo(self.getRender()) + + def getHoodId(self): + return ToontownGlobals.CashbotHQ + + def formatReward(self): + # Returns the reward indication to write to the event log. + return str(self.rewardId) + + def makeBattleOneBattles(self): + self.postBattleState = 'PrepareBattleThree' + self.initializeBattles(1, ToontownGlobals.CashbotBossBattleOnePosHpr) + + def generateSuits(self, battleNumber): + cogs = self.invokeSuitPlanner(11, 0) + skelecogs = self.invokeSuitPlanner(12, 1) + + # Now combine the lists of suits together, so that they all + # come out mix-and-match. + activeSuits = cogs['activeSuits'] + skelecogs['activeSuits'] + reserveSuits = cogs['reserveSuits'] + skelecogs['reserveSuits'] + + random.shuffle(activeSuits) + + # We might have ended up with too many suits on the + # activeSuits list. If that happens, put the overflow suits + # on the reserve list, with a 100% joinChance. + while len(activeSuits) > 4: + suit = activeSuits.pop() + reserveSuits.append((suit, 100)) + + # We must keep the reserveSuits sorted in increasing order of + # joinChance. + def compareJoinChance(a, b): + return cmp(a[1], b[1]) + reserveSuits.sort(compareJoinChance) + + return { 'activeSuits' : activeSuits, 'reserveSuits' : reserveSuits } + + def removeToon(self, avId): + # The toon leaves the zone, either through disconnect, death, + # or something else. Tell all of the safes, cranes, and goons. + + if self.cranes != None: + for crane in self.cranes: + crane.removeToon(avId) + + if self.safes != None: + for safe in self.safes: + safe.removeToon(avId) + + if self.goons != None: + for goon in self.goons: + goon.removeToon(avId) + + DistributedBossCogAI.DistributedBossCogAI.removeToon(self, avId) + + def __makeBattleThreeObjects(self): + if self.cranes == None: + # Generate all of the cranes. + self.cranes = [] + for index in range(len(ToontownGlobals.CashbotBossCranePosHprs)): + crane = DistributedCashbotBossCraneAI.DistributedCashbotBossCraneAI(self.air, self, index) + crane.generateWithRequired(self.zoneId) + self.cranes.append(crane) + + if self.safes == None: + # And all of the safes. + self.safes = [] + for index in range(len(ToontownGlobals.CashbotBossSafePosHprs)): + safe = DistributedCashbotBossSafeAI.DistributedCashbotBossSafeAI(self.air, self, index) + safe.generateWithRequired(self.zoneId) + self.safes.append(safe) + + if self.goons == None: + # We don't actually make the goons right now, but we make + # a place to hold them. + self.goons = [] + + def __resetBattleThreeObjects(self): + if self.cranes != None: + for crane in self.cranes: + crane.request('Free') + + if self.safes != None: + for safe in self.safes: + safe.request('Initial') + + def __deleteBattleThreeObjects(self): + if self.cranes != None: + for crane in self.cranes: + crane.request('Off') + crane.requestDelete() + self.cranes = None + + if self.safes != None: + for safe in self.safes: + safe.request('Off') + safe.requestDelete() + self.safes = None + + if self.goons != None: + for goon in self.goons: + goon.request('Off') + goon.requestDelete() + self.goons = None + + def doNextAttack(self, task): + assert self.notify.debug("%s.doNextAttack()" % (self.doId)) + # Choose an attack and do it. + + # For now, we only do the directed attack. + self.__doDirectedAttack() + + # Make sure we're waiting for a helmet. + if self.heldObject == None and not self.waitingForHelmet: + self.waitForNextHelmet() + + def __doDirectedAttack(self): + # Choose the next toon in line to get the assault. + if self.toonsToAttack: + toonId = self.toonsToAttack.pop(0) + while toonId not in self.involvedToons: + # Oops, this toon is gone. + if not self.toonsToAttack: + # Say, everyone's gone. + self.b_setAttackCode(ToontownGlobals.BossCogNoAttack) + return + toonId = self.toonsToAttack.pop(0) + + self.toonsToAttack.append(toonId) + self.b_setAttackCode(ToontownGlobals.BossCogSlowDirectedAttack, toonId) + + def reprieveToon(self, avId): + # Moves the indicated toon to the tail of the attack queue. + if avId in self.toonsToAttack: + i = self.toonsToAttack.index(avId) + del self.toonsToAttack[i] + self.toonsToAttack.append(avId) + + def makeTreasure(self, goon): + # Places a treasure, as pooped out by the given goon. We + # place the treasure at the goon's current position, or at + # least at the beginning of its current path. Actually, we + # ignore Z, and always place the treasure at Z == 0, + # presumably the ground. + + if self.state != 'BattleThree': + return + + # The BossCog acts like a treasure planner as far as the + # treasure is concerned. + pos = goon.getPos(self) + + # The treasure pops out and lands somewhere nearby. Let's + # start by choosing a point on a ring around the boss, based + # on our current angle to the boss. + v = Vec3(pos[0], pos[1], 0.0) + if not v.normalize(): + v = Vec3(1, 0, 0) + v = v * 27 + + # Then perterb that point by a distance in some random + # direction. + angle = random.uniform(0.0, 2.0 * math.pi) + radius = 10 + dx = radius * math.cos(angle) + dy = radius * math.sin(angle) + + fpos = self.scene.getRelativePoint(self, Point3(v[0] + dx, v[1] + dy, 0)) + + if goon.strength <= 10: + style = ToontownGlobals.ToontownCentral + healAmount = 3 + + elif goon.strength <= 15: + style = random.choice( + [ToontownGlobals.DonaldsDock, + ToontownGlobals.DaisyGardens, + ToontownGlobals.MinniesMelodyland]) + healAmount = 10 + + else: + style = random.choice( + [ToontownGlobals.TheBrrrgh, + ToontownGlobals.DonaldsDreamland]) + healAmount = 12 + + if self.recycledTreasures: + # Reuse a previous treasure object + treasure = self.recycledTreasures.pop(0) + treasure.d_setGrab(0) + treasure.b_setGoonId(goon.doId) + treasure.b_setStyle(style) + treasure.b_setPosition(pos[0], pos[1], 0) + treasure.b_setFinalPosition(fpos[0], fpos[1], 0) + + else: + # Create a new treasure object + treasure = DistributedCashbotBossTreasureAI.DistributedCashbotBossTreasureAI( + self.air, self, goon, + style, fpos[0], fpos[1], 0) + treasure.generateWithRequired(self.zoneId) + + treasure.healAmount = healAmount + self.treasures[treasure.doId] = treasure + + def grabAttempt(self, avId, treasureId): + # An avatar has attempted to grab a treasure. + av = self.air.doId2do.get(avId) + if not av: + return + + treasure = self.treasures.get(treasureId) + if treasure: + if treasure.validAvatar(av): + del self.treasures[treasureId] + treasure.d_setGrab(avId) + self.grabbingTreasures[treasureId] = treasure + # Wait a few seconds for the animation to play, then + # recycle the treasure. + taskMgr.doMethodLater(5, self.__recycleTreasure, + treasure.uniqueName('recycleTreasure'), + extraArgs = [treasure]) + else: + treasure.d_setReject() + + def __recycleTreasure(self, treasure): + if self.grabbingTreasures.has_key(treasure.doId): + del self.grabbingTreasures[treasure.doId] + self.recycledTreasures.append(treasure) + + def deleteAllTreasures(self): + for treasure in self.treasures.values(): + treasure.requestDelete() + self.treasures = {} + + for treasure in self.grabbingTreasures.values(): + taskMgr.remove(treasure.uniqueName('recycleTreasure')) + treasure.requestDelete() + self.grabbingTreasures = {} + + for treasure in self.recycledTreasures: + treasure.requestDelete() + self.recycledTreasures = [] + + def getMaxGoons(self): + # Returns the number of goons to make. + t = self.getBattleThreeTime() + if t <= 1.0: + # Normally, we make the specified number. + return self.maxGoons + + elif t <= 1.1: + # If the battle goes into overtime, we throw out one + # additional goon every three minutes until they all + # succumb. + return self.maxGoons + 1 + + elif t <= 1.2: + return self.maxGoons + 2 + + elif t <= 1.3: + return self.maxGoons + 3 + + elif t <= 1.4: + return self.maxGoons + 4 + + else: + return self.maxGoons + 8 + + + def makeGoon(self, side = None): + if side == None: + side = random.choice(['EmergeA', 'EmergeB']) + + # First, look to see if we have a goon we can recycle. + goon = self.__chooseOldGoon() + if goon == None: + # No, no old goon; is there room for a new one? + if len(self.goons) >= self.getMaxGoons(): + return + + # make a new one. + goon = DistributedCashbotBossGoonAI.DistributedCashbotBossGoonAI(self.air, self) + goon.generateWithRequired(self.zoneId) + self.goons.append(goon) + + if self.getBattleThreeTime() > 1.0: + # If the battle goes into overtime, we only make + # SuperGoons. + goon.STUN_TIME = 4 + goon.b_setupGoon(velocity = 8, + hFov = 90, + attackRadius = 20, + strength = 30, + scale = 1.8) + else: + # Normally, we make regular goons. + goon.STUN_TIME = self.progressValue(30, 8) + goon.b_setupGoon(velocity = self.progressRandomValue(3, 7), + hFov = self.progressRandomValue(70, 80), + attackRadius = self.progressRandomValue(6, 15), + strength = int(self.progressRandomValue(5, 25)), + scale = self.progressRandomValue(0.5, 1.5)) + + goon.request(side) + + def __chooseOldGoon(self): + # Walks through the list of goons managed by the boss to see + # if any of them have recently been deleted and can be + # recycled. + for goon in self.goons: + if goon.state == 'Off': + return goon + + def waitForNextGoon(self, delayTime): + currState = self.getCurrentOrNextState() + if (currState == 'BattleThree'): + taskName = self.uniqueName('NextGoon') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.doNextGoon, taskName) + + def stopGoons(self): + taskName = self.uniqueName('NextGoon') + taskMgr.remove(taskName) + + def doNextGoon(self, task): + if self.attackCode != ToontownGlobals.BossCogDizzy: + self.makeGoon() + + # How long to wait for the next goon? + + delayTime = self.progressValue(10, 2) + self.waitForNextGoon(delayTime) + + def waitForNextHelmet(self): + currState = self.getCurrentOrNextState() + if (currState == 'BattleThree'): + taskName = self.uniqueName('NextHelmet') + taskMgr.remove(taskName) + delayTime = self.progressValue(45, 15) + taskMgr.doMethodLater(delayTime, self.__donHelmet, taskName) + self.waitingForHelmet = 1 + + def __donHelmet(self, task): + self.waitingForHelmet = 0 + if self.heldObject == None: + # Ok, the boss wants to put on a helmet now. He can have + # his special safe 0, which was created for just this + # purpose. + safe = self.safes[0] + safe.request('Grabbed', self.doId, self.doId) + self.heldObject = safe + + def stopHelmets(self): + self.waitingForHelmet = 0 + taskName = self.uniqueName('NextHelmet') + taskMgr.remove(taskName) + + def acceptHelmetFrom(self, avId): + # Returns true if we can accept a helmet from the indicated + # avatar, false otherwise. Each avatar gets a timeout of five + # minutes after giving us a helmet, so we don't accept too + # many helmets from the same avatar--this cuts down on helmet + # griefing. + now = globalClock.getFrameTime() + then = self.avatarHelmets.get(avId, None) + if then == None or (now - then > 300): + self.avatarHelmets[avId] = now + return 1 + + return 0 + + + def magicWordHit(self, damage, avId): + # Called by the magic word "~bossBattle hit damage" + if self.heldObject: + # Drop the current helmet. + self.heldObject.demand('Dropped', avId, self.doId) + self.heldObject.avoidHelmet = 1 + self.heldObject = None + self.waitForNextHelmet() + + else: + # Ouch! + self.recordHit(damage) + + def magicWordReset(self): + # Resets all of the cranes and safes. + # Called only by the magic word "~bossBattle reset" + if self.state == 'BattleThree': + self.__resetBattleThreeObjects() + + def magicWordResetGoons(self): + # Resets all of the goons. + # Called only by the magic word "~bossBattle goons" + if self.state == 'BattleThree': + if self.goons != None: + for goon in self.goons: + goon.request('Off') + goon.requestDelete() + self.goons = None + + self.__makeBattleThreeObjects() + + def recordHit(self, damage): + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.recordHit(%s, %s)' % (self.doId, avId, damage)) + if not self.validate(avId, avId in self.involvedToons, + 'recordHit from unknown avatar'): + return + + if self.state != 'BattleThree': + return + + # Record a successful hit in battle three. + self.b_setBossDamage(self.bossDamage + damage) + + if self.bossDamage >= self.bossMaxDamage: + # Congratulations! + self.b_setState('Victory') + + elif self.attackCode != ToontownGlobals.BossCogDizzy: + if damage >= ToontownGlobals.CashbotBossKnockoutDamage: + # A particularly good hit (when he's not already + # dizzy) will make the boss dizzy for a little while. + self.b_setAttackCode(ToontownGlobals.BossCogDizzy) + self.stopHelmets() + else: + self.b_setAttackCode(ToontownGlobals.BossCogNoAttack) + self.stopHelmets() + self.waitForNextHelmet() + + def b_setBossDamage(self, bossDamage): + self.d_setBossDamage(bossDamage) + self.setBossDamage(bossDamage) + + def setBossDamage(self, bossDamage): + assert self.notify.debug('%s.setBossDamage(%s)' % (self.doId, bossDamage)) + self.reportToonHealth() + self.bossDamage = bossDamage + + def d_setBossDamage(self, bossDamage): + self.sendUpdate('setBossDamage', [bossDamage]) + + def d_setRewardId(self, rewardId): + self.sendUpdate('setRewardId', [rewardId]) + + def applyReward(self): + # The client has reached that point in the movie where he + # should have the reward applied to him. + + avId = self.air.getAvatarIdFromSender() + if avId in self.involvedToons and \ + avId not in self.rewardedToons: + self.rewardedToons.append(avId) + toon = self.air.doId2do.get(avId) + if toon: + toon.doResistanceEffect(self.rewardId) + + ##### Off state ##### + + def enterOff(self): + DistributedBossCogAI.DistributedBossCogAI.enterOff(self) + self.rewardedToons = [] + + def exitOff(self): + DistributedBossCogAI.DistributedBossCogAI.exitOff(self) + + ##### Introduction state ##### + + def enterIntroduction(self): + DistributedBossCogAI.DistributedBossCogAI.enterIntroduction(self) + + # We want to have cranes and safes visible in the next room + # for the introduction cutscene. + self.__makeBattleThreeObjects() + self.__resetBattleThreeObjects() + + def exitIntroduction(self): + DistributedBossCogAI.DistributedBossCogAI.exitIntroduction(self) + + # Clean up battle objects until we need them again later. + self.__deleteBattleThreeObjects() + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + assert self.notify.debug('%s.enterPrepareBattleThree()' % (self.doId)) + + self.resetBattles() + + self.__makeBattleThreeObjects() + self.__resetBattleThreeObjects() + + # The clients will play a cutscene. When they are done + # watching the movie, we continue. + + self.barrier = self.beginBarrier( + "PrepareBattleThree", self.involvedToons, 55, + self.__donePrepareBattleThree) + + def __donePrepareBattleThree(self, avIds): + self.b_setState("BattleThree") + + def exitPrepareBattleThree(self): + + if self.newState != 'BattleThree': + self.__deleteBattleThreeObjects() + + self.ignoreBarrier(self.barrier) + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('%s.enterBattleThree()' % (self.doId)) + + # It's important to set our position correctly even on the AI, + # so the goons can orient to the center of the room. + self.setPosHpr(*ToontownGlobals.CashbotBossBattleThreePosHpr) + + # Just in case we didn't pass through PrepareBattleThree state. + self.__makeBattleThreeObjects() + self.__resetBattleThreeObjects() + + self.reportToonHealth() + + # A list of toons to attack. We start out with the list in + # random order. + self.toonsToAttack = self.involvedToons[:] + random.shuffle(self.toonsToAttack) + + self.b_setBossDamage(0) + self.battleThreeStart = globalClock.getFrameTime() + + self.resetBattles() + self.waitForNextAttack(15) + self.waitForNextHelmet() + + # Make four goons up front to keep things interesting from the + # beginning. + self.makeGoon(side = 'EmergeA') + self.makeGoon(side = 'EmergeB') + taskName = self.uniqueName('NextGoon') + taskMgr.remove(taskName) + taskMgr.doMethodLater(2, self.__doInitialGoons, taskName) + + def __doInitialGoons(self, task): + self.makeGoon(side = 'EmergeA') + self.makeGoon(side = 'EmergeB') + self.waitForNextGoon(10) + + def exitBattleThree(self): + helmetName = self.uniqueName('helmet') + taskMgr.remove(helmetName) + + if self.newState != 'Victory': + self.__deleteBattleThreeObjects() + self.deleteAllTreasures() + self.stopAttacks() + self.stopGoons() + self.stopHelmets() + self.heldObject = None + + + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('%s.enterVictory()' % (self.doId)) + self.resetBattles() + + # add a suit-defeat entry for the VP + # based on code in DistributedBattleBaseAI.__movieDone + self.suitsKilled.append({ + 'type' : None, + 'level' : None, + 'track' : self.dna.dept, + 'isSkelecog' : 0, + 'isForeman' : 0, + 'isVP' : 0, + 'isCFO' : 1, + 'isSupervisor' : 0, + 'isVirtual' : 0, + 'activeToons' : self.involvedToons[:], + }) + + self.barrier = self.beginBarrier( + "Victory", self.involvedToons, 30, + self.__doneVictory) + + def __doneVictory(self, avIds): + + # Tell the client the information it needs to generate a + # reward movie. + self.d_setBattleExperience() + + # First, move the clients into the reward start. They'll + # build the reward movies immediately. + self.b_setState("Reward") + + # Now that the clients have started to build their reward + # movies, we can actually assign all the experience and + # rewards. If we did this sooner, the quest reward panel + # would show the rewards being applied twice. + + # There is no race condition between AI and client here + # because these messages are sent sequentially on the wire. + + BattleExperienceAI.assignRewards( + self.involvedToons, self.toonSkillPtsGained, + self.suitsKilled, + ToontownGlobals.dept2cogHQ(self.dept), self.helpfulToons) + + # Don't forget to give the toon the resistance chat reward and the + # promotion! + + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + toon.addResistanceMessage(self.rewardId) + toon.b_promote(self.deptIndex) + + def exitVictory(self): + self.__deleteBattleThreeObjects() + + + ##### Epilogue state ##### + + def enterEpilogue(self): + DistributedBossCogAI.DistributedBossCogAI.enterEpilogue(self) + + # Tell the clients now what the reward Id will be. + self.d_setRewardId(self.rewardId) diff --git a/toontown/src/suit/DistributedCashbotBossGoon.py b/toontown/src/suit/DistributedCashbotBossGoon.py new file mode 100644 index 0000000..e4cdaa1 --- /dev/null +++ b/toontown/src/suit/DistributedCashbotBossGoon.py @@ -0,0 +1,321 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.task.TaskManagerGlobal import * +from direct.distributed.ClockDelta import * +from direct.directnotify import DirectNotifyGlobal +import GoonGlobals +from direct.task.Task import Task +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPGlobals +from toontown.coghq import DistributedCashbotBossObject +from direct.showbase import PythonUtil +import DistributedGoon + +class DistributedCashbotBossGoon(DistributedGoon.DistributedGoon, + DistributedCashbotBossObject.DistributedCashbotBossObject): + + """ This is a goon that walks around in the Cashbot CFO final + battle scene, tormenting Toons, and also providing ammo for + defeating the boss. """ + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedCashbotBossGoon') + + walkGrabZ = -3.6 + stunGrabZ = -2.2 + + # How long does it take for a live goon to wiggle free of the magnet? + wiggleFreeTime = 2 + + # What happens to the crane and its cable when this object is picked up? + craneFrictionCoef = 0.15 + craneSlideSpeed = 10 + craneRotateSpeed = 20 + + def __init__(self, cr): + DistributedCashbotBossObject.DistributedCashbotBossObject.__init__(self, cr) + DistributedGoon.DistributedGoon.__init__(self, cr) + + self.target = None + self.arrivalTime = None + + self.flyToMagnetSfx = loader.loadSfx('phase_5/audio/sfx/TL_rake_throw_only.mp3') + self.hitMagnetSfx = loader.loadSfx('phase_4/audio/sfx/AA_drop_anvil_miss.mp3') + self.toMagnetSoundInterval = Sequence( + SoundInterval(self.flyToMagnetSfx, duration = ToontownGlobals.CashbotBossToMagnetTime, node = self), + SoundInterval(self.hitMagnetSfx, node = self)) + self.hitFloorSfx = loader.loadSfx('phase_5/audio/sfx/AA_drop_flowerpot.mp3') + self.hitFloorSoundInterval = SoundInterval( + self.hitFloorSfx, duration = 1.0, node = self) + + self.wiggleSfx = loader.loadSfx('phase_5/audio/sfx/SA_finger_wag.mp3') + + def generate(self): + DistributedCashbotBossObject.DistributedCashbotBossObject.generate(self) + DistributedGoon.DistributedGoon.generate(self) + + def announceGenerate(self): + DistributedCashbotBossObject.DistributedCashbotBossObject.announceGenerate(self) + + # It is important to call setupPhysics() before we call + # DistributedGoon.announceGenerate(), since setupPhysics() + # will reassign our NodePath and thereby invalidate any + # messenger hooks already added. In fact, it is important + # that we not have any outstanding messenger hooks at the time + # we call setupPhysics(). + self.setupPhysics('goon') + + DistributedGoon.DistributedGoon.announceGenerate(self) + + self.name = 'goon-%s' % (self.doId) + self.setName(self.name) + + self.setTag('doId', str(self.doId)) + self.collisionNode.setName('goon') + cs = CollisionSphere(0, 0, 4, 4) + self.collisionNode.addSolid(cs) + self.collisionNode.setIntoCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CashbotBossObjectBitmask) + + self.wiggleTaskName = self.uniqueName('wiggleTask') + self.wiggleFreeName = self.uniqueName('wiggleFree') + + assert self not in self.boss.goons + self.boss.goons.append(self) + + self.reparentTo(render) + + def disable(self): + assert self in self.boss.goons + i = self.boss.goons.index(self) + del self.boss.goons[i] + DistributedGoon.DistributedGoon.disable(self) + DistributedCashbotBossObject.DistributedCashbotBossObject.disable(self) + + def delete(self): + DistributedGoon.DistributedGoon.delete(self) + DistributedCashbotBossObject.DistributedCashbotBossObject.delete(self) + + def hideShadows(self): + self.dropShadow.hide() + + def showShadows(self): + self.dropShadow.show() + + def getMinImpact(self): + # This method returns the minimum impact, in feet per second, + # with which the object should hit the boss before we bother + # to tell the server. + return ToontownGlobals.CashbotBossGoonImpact + + def doHitBoss(self, impact): + self.d_hitBoss(impact) + self.b_destroyGoon() + + def __startWalk(self): + # Generate an interval to walk the goon to his target square + # by the specified time. + self.__stopWalk() + + if self.target: + now = globalClock.getFrameTime() + availableTime = self.arrivalTime - now + if availableTime > 0: + # How long will it take to rotate to position? + origH = self.getH() + h = PythonUtil.fitDestAngle2Src(origH, self.targetH) + delta = abs(h - origH) + turnTime = delta / (self.velocity * 5) + + # And how long will it take to walk to position? + dist = Vec3(self.target - self.getPos()).length() + walkTime = dist / self.velocity + + denom = turnTime + walkTime + if denom != 0: + # Fit that within our available time. + timeCompress = availableTime / denom + + self.walkTrack = Sequence( + self.hprInterval(turnTime * timeCompress, VBase3(h, 0, 0)), + self.posInterval(walkTime * timeCompress, self.target)) + self.walkTrack.start() + else: + self.setPos(self.target) + self.setH(self.targetH) + + def __stopWalk(self): + # Stop the walk interval. + if self.walkTrack: + self.walkTrack.pause() + self.walkTrack = None + + def __wiggleTask(self, task): + # If the unfortunate player picks up an active goon, the + # magnet should wiggle erratically to indicate instability. + elapsed = globalClock.getFrameTime() - self.wiggleStart + + h = math.sin(elapsed * 17) * 5 + p = math.sin(elapsed * 29) * 10 + self.crane.wiggleMagnet.setHpr(h, p, 0) + + return Task.cont + + def __wiggleFree(self, task): + # We've successfully wiggled free after being picked up. + self.crane.releaseObject() + + # And we can't be picked up again until we land. + self.stashCollisions() + + return Task.done + + def fellOut(self): + # The goon fell out of the world. Just destroy him and move on. + self.b_destroyGoon() + + def handleToonDetect(self, collEntry=None): + if self.boss.localToonIsSafe: + return + + DistributedGoon.DistributedGoon.handleToonDetect(self, collEntry) + + def prepareGrab(self): + DistributedCashbotBossObject.DistributedCashbotBossObject.prepareGrab(self) + if self.isStunned or self.boss.localToonIsSafe: + self.pose('collapse', 48) + self.grabPos = (0, 0, self.stunGrabZ * self.scale) + + else: + # He's got a live one! + self.setPlayRate(4, 'walk') + self.loop('walk') + self.grabPos = (0, 0, self.walkGrabZ * self.scale) + self.wiggleStart = globalClock.getFrameTime() + taskMgr.add(self.__wiggleTask, self.wiggleTaskName) + base.sfxPlayer.playSfx(self.wiggleSfx, node = self) + if self.avId == localAvatar.doId: + taskMgr.doMethodLater(self.wiggleFreeTime, self.__wiggleFree, self.wiggleFreeName) + + self.radar.hide() + + def prepareRelease(self): + DistributedCashbotBossObject.DistributedCashbotBossObject.prepareRelease(self) + self.crane.wiggleMagnet.setHpr(0, 0, 0) + taskMgr.remove(self.wiggleTaskName) + taskMgr.remove(self.wiggleFreeName) + self.setPlayRate(self.animMultiplier, 'walk') + + ##### Messages To/From The Server ##### + + def setObjectState(self, state, avId, craneId): + if state == 'W': + self.demand('Walk') + elif state == 'B': + if self.state != 'Battle': + self.demand('Battle') + elif state == 'S': + if self.state != 'Stunned': + self.demand('Stunned') + elif state == 'R': + if self.state != 'Recovery': + self.demand('Recovery') + elif state == 'a': + self.demand('EmergeA') + elif state == 'b': + self.demand('EmergeB') + else: + DistributedCashbotBossObject.DistributedCashbotBossObject.setObjectState(self, state, avId, craneId) + + def setTarget(self, x, y, h, arrivalTime): + self.target = Point3(x, y, 0) + self.targetH = h + now = globalClock.getFrameTime() + self.arrivalTime = globalClockDelta.networkToLocalTime(arrivalTime, now) + if self.state == 'Walk': + self.__startWalk() + + def d_destroyGoon(self): + self.sendUpdate('destroyGoon') + + def b_destroyGoon(self): + if not self.isDead: + self.d_destroyGoon() + self.destroyGoon() + + def destroyGoon(self): + if not self.isDead: + self.playCrushMovie(None, None) + self.demand('Off') + + ### FSM States ### + + def enterOff(self): + DistributedGoon.DistributedGoon.enterOff(self) + DistributedCashbotBossObject.DistributedCashbotBossObject.enterOff(self) + + def exitOff(self): + DistributedCashbotBossObject.DistributedCashbotBossObject.exitOff(self) + DistributedGoon.DistributedGoon.exitOff(self) + + def enterWalk(self, avId=None, ts=0): + # start the toon detection on enterWalk. + self.startToonDetect() + self.isStunned = 0 + self.__startWalk() + self.loop('walk', 0) + self.unstashCollisions() + + def exitWalk(self): + self.__stopWalk() + self.stopToonDetect() + self.stop() + + def enterEmergeA(self): + # The goon emerges from door a. + self.undead() + self.reparentTo(render) + self.stopToonDetect() + self.boss.doorA.request('open') + self.radar.hide() + self.__startWalk() + self.loop('walk', 0) + + def exitEmergeA(self): + if self.boss.doorA: + self.boss.doorA.request('close') + self.radar.show() + self.__stopWalk() + + def enterEmergeB(self): + # The goon emerges from door b. + self.undead() + self.reparentTo(render) + self.stopToonDetect() + self.boss.doorB.request('open') + self.radar.hide() + self.__startWalk() + self.loop('walk', 0) + + def exitEmergeB(self): + if self.boss.doorB: + self.boss.doorB.request('close') + self.radar.show() + self.__stopWalk() + + def enterBattle(self, avId = None, ts = 0): + DistributedGoon.DistributedGoon.enterBattle(self, avId, ts) + + avatar = base.cr.doId2do.get(avId) + if avatar: + # Make the toon flash, and knock him off the crane. + messenger.send('exitCrane') + avatar.stunToon() + + self.unstashCollisions() + + def enterStunned(self, ts = 0): + DistributedGoon.DistributedGoon.enterStunned(self, ts) + self.unstashCollisions() + + def enterRecovery(self, ts = 0, pauseTime = 0): + DistributedGoon.DistributedGoon.enterRecovery(self, ts, pauseTime) + self.unstashCollisions() diff --git a/toontown/src/suit/DistributedCashbotBossGoonAI.py b/toontown/src/suit/DistributedCashbotBossGoonAI.py new file mode 100644 index 0000000..4bd8ec2 --- /dev/null +++ b/toontown/src/suit/DistributedCashbotBossGoonAI.py @@ -0,0 +1,432 @@ +from pandac.PandaModules import * +from direct.task.TaskManagerGlobal import * +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * +import GoonGlobals +from direct.task.Task import Task +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPGlobals +from toontown.coghq import DistributedCashbotBossObjectAI +from direct.showbase import PythonUtil +import DistributedGoonAI +import math +import random + +class DistributedCashbotBossGoonAI(DistributedGoonAI.DistributedGoonAI, + DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI): + + """ This is a goon that walks around in the Cashbot CFO final + battle scene, tormenting Toons, and also providing ammo for + defeating the boss. """ + + legLength = 10 + + # A table of likely directions for the next choice at each point. + # The table contains (heading, weight), where heading is the + # direction of choice, and weight is the relative preference of + # this direction over the others. + directionTable = [ + (0, 15), + (10, 10), + (-10, 10), + (20, 8), + (-20, 8), + (40, 5), + (-40, 5), + (60, 4), + (-60, 4), + (80, 3), + (-80, 3), + (120, 2), + (-120, 2), + (180, 1), + ] + + offMask = BitMask32(0) + onMask = CollisionNode.getDefaultCollideMask() + + def __init__(self, air, boss): + DistributedGoonAI.DistributedGoonAI.__init__(self, air, 0) + DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.__init__(self, air, boss) + + # A tube covering our intended path, so other goons will see + # and avoid us. + cn = CollisionNode('tubeNode') + self.tube = CollisionTube(0, 0, 0, 0, 0, 0, 2) + cn.addSolid(self.tube) + self.tubeNode = cn + self.tubeNodePath = self.attachNewNode(self.tubeNode) + + # A spray of feeler wires so we can choose an empty path. + self.feelers = [] + cn = CollisionNode('feelerNode') + self.feelerLength = self.legLength * 1.5 + feelerStart = 1 + for heading, weight in self.directionTable: + rad = deg2Rad(heading) + x = -math.sin(rad) + y = math.cos(rad) + seg = CollisionSegment(x * feelerStart, y * feelerStart , 0, + x * self.feelerLength, y * self.feelerLength, 0) + cn.addSolid(seg) + self.feelers.append(seg) + cn.setIntoCollideMask(self.offMask) + self.feelerNodePath = self.attachNewNode(cn) + + self.isWalking = 0 + + self.cTrav = CollisionTraverser('goon') + self.cQueue = CollisionHandlerQueue() + + self.cTrav.addCollider(self.feelerNodePath, self.cQueue) + + def requestBattle(self, pauseTime): + avId = self.air.getAvatarIdFromSender() + + # Here we ask the boss to damage the toon, instead of asking + # the level to do it. + + avatar = self.air.doId2do.get(avId) + if avatar: + self.boss.damageToon(avatar, self.strength) + + DistributedGoonAI.DistributedGoonAI.requestBattle(self, pauseTime) + + def sendMovie(self, type, avId=0, pauseTime=0): + # Overridden from DistributedGoonAI. + if type == GoonGlobals.GOON_MOVIE_WALK: + self.demand('Walk') + elif type == GoonGlobals.GOON_MOVIE_BATTLE: + self.demand('Battle') + elif type == GoonGlobals.GOON_MOVIE_STUNNED: + self.demand('Stunned') + elif type == GoonGlobals.GOON_MOVIE_RECOVERY: + self.demand('Recovery') + else: + self.notify.warning("Ignoring movie type %s" % (type)) + + def __chooseTarget(self, extraDelay = 0): + # Chooses a random point to walk towards. + direction = self.__chooseDirection() + if direction == None: + # No place to go; just blow up. + self.target = None + self.arrivalTime = None + self.b_destroyGoon() + return + + heading, dist = direction + dist = min(dist, self.legLength) + targetH = PythonUtil.reduceAngle(self.getH() + heading) + + # How long will it take to rotate to position? + origH = self.getH() + h = PythonUtil.fitDestAngle2Src(origH, targetH) + delta = abs(h - origH) + turnTime = delta / (self.velocity * 5) + + # And how long will it take to walk to position? + walkTime = dist / self.velocity + + self.setH(targetH) + self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0)) + + self.departureTime = globalClock.getFrameTime() + self.arrivalTime = self.departureTime + turnTime + walkTime + extraDelay + + self.d_setTarget(self.target[0], self.target[1], h, + globalClockDelta.localToNetworkTime(self.arrivalTime)) + + def __chooseDirection(self): + + # Chooses a direction to walk in next. We do this by + # examining a few likely directions, and we choose the one + # with the clearest path (e.g. the fewest safes and other + # goons in the way), with some randomness thrown in for fun. + + # Hack to prevent self-intersection. + self.tubeNode.setIntoCollideMask(self.offMask) + self.cTrav.traverse(self.boss.scene) + self.tubeNode.setIntoCollideMask(self.onMask) + + entries = {} + + # Walk through the entries from farthest to nearest, so that + # nearer collisions on the same segment will override farther + # ones. + self.cQueue.sortEntries() + + for i in range(self.cQueue.getNumEntries() - 1, -1, -1): + entry = self.cQueue.getEntry(i) + dist = Vec3(entry.getSurfacePoint(self)).length() + + if dist < 1.2: + # Too close; forget it. + dist = 0 + + entries[entry.getFrom()] = dist + + # Now get the lengths of the various paths, and accumulate a + # score table. Each direction gets a score based on the + # distance to the next obstruction, and its weighted + # preference. + netScore = 0 + scoreTable = [] + for i in range(len(self.directionTable)): + heading, weight = self.directionTable[i] + seg = self.feelers[i] + dist = entries.get(seg, self.feelerLength) + + score = dist * weight + netScore += score + scoreTable.append(score) + + if netScore == 0: + # If no paths were any good, bail. + self.notify.info("Could not find a path for %s" % (self.doId)) + return None + + # And finally, choose a random direction from the table, + # with a random distribution weighted by score. + s = random.uniform(0, netScore) + for i in range(len(self.directionTable)): + s -= scoreTable[i] + if s <= 0: + heading, weight = self.directionTable[i] + seg = self.feelers[i] + dist = entries.get(seg, self.feelerLength) + return (heading, dist) + + # Shouldn't be possible to fall off the end, but maybe there + # was a roundoff error. + self.notify.warning("Fell off end of weighted table.") + return (0, self.legLength) + + def __startWalk(self): + # Generate a do-later method to "walk" the goon to his target + # square by the specified time. Actually, on the AI the goon + # just stands where he is until the time expires, but no one + # cares about that. + assert not self.isWalking + + if self.arrivalTime == None: + return + + now = globalClock.getFrameTime() + availableTime = self.arrivalTime - now + + if availableTime > 0: + # Change the tube to encapsulate our path to our target point. + point = self.getRelativePoint(self.boss.scene, self.target) + self.tube.setPointB(point) + self.node().resetPrevTransform() + + taskMgr.doMethodLater(availableTime, self.__reachedTarget, + self.uniqueName('reachedTarget')) + self.isWalking = 1 + else: + self.__reachedTarget(None) + + def __stopWalk(self, pauseTime = None): + if self.isWalking: + # Stop the walk do-later. + taskMgr.remove(self.uniqueName('reachedTarget')) + + # Place us at the appropriate point along the path. + if pauseTime == None: + now = globalClock.getFrameTime() + t = (now - self.departureTime) / (self.arrivalTime - self.departureTime) + else: + t = pauseTime / (self.arrivalTime - self.departureTime) + + t = min(t, 1.0) + pos = self.getPos() + self.setPos(pos + (self.target - pos) * t) + + # The tube is now a sphere. + self.tube.setPointB(0, 0, 0) + + self.isWalking = 0 + + def __reachedTarget(self, task): + self.__stopWalk() + self.__chooseTarget() + self.__startWalk() + + def __recoverWalk(self, task): + self.demand('Walk') + return Task.done + + def doFree(self, task): + # This method is fired as a do-later when we enter WaitFree. + DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.doFree(self, task) + self.demand('Walk') + return Task.done + + ### Messages ### + + def requestStunned(self, pauseTime): + avId = self.air.getAvatarIdFromSender() + + if avId not in self.boss.involvedToons: + return + + if self.state == 'Stunned' or self.state == 'Grabbed': + # Already stunned, or just picked up by a magnet; don't + # stun again. + return + + # Stop the goon right where he is. + self.__stopWalk(pauseTime) + + # And it poops out a treasure right there. + self.boss.makeTreasure(self) + + DistributedGoonAI.DistributedGoonAI.requestStunned(self, pauseTime) + + + def hitBoss(self, impact): + avId = self.air.getAvatarIdFromSender() + + self.validate(avId, impact <= 1.0, + 'invalid hitBoss impact %s' % (impact)) + + if avId not in self.boss.involvedToons: + return + + if self.state == 'Dropped' or self.state == 'Grabbed': + if not self.boss.heldObject: + # A goon can only hurt the boss when he's got a helmet on. + damage = int(impact * 25 * self.scale) + self.boss.recordHit(max(damage, 2)) + + self.b_destroyGoon() + + def d_setTarget(self, x, y, h, arrivalTime): + self.sendUpdate('setTarget', [x, y, h, arrivalTime]) + + def d_destroyGoon(self): + self.sendUpdate('destroyGoon') + + def b_destroyGoon(self): + self.d_destroyGoon() + self.destroyGoon() + + def destroyGoon(self): + # The client or AI informs the world that the goon has + # shuffled off this mortal coil. + + self.demand('Off') + + # We don't actually delete the goon; instead, we leave it in + # the boss's pool to recycle later. + + ### FSM States ### + + def enterOff(self): + self.tubeNodePath.stash() + self.feelerNodePath.stash() + + def exitOff(self): + self.tubeNodePath.unstash() + self.feelerNodePath.unstash() + + def enterGrabbed(self, avId, craneId): + DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.enterGrabbed(self, avId, craneId) + + # If a goon is grabbed while he's just waking up, it + # interrupts the wake-up process. Ditto for a goon in battle + # mode. + taskMgr.remove(self.taskName('recovery')) + taskMgr.remove(self.taskName('resumeWalk')) + + def enterWalk(self): + # The goon is prowling about, looking for trouble. + + self.avId = 0 + self.craneId = 0 + + self.__chooseTarget() + self.__startWalk() + self.d_setObjectState('W', 0, 0) + + def exitWalk(self): + self.__stopWalk() + + def enterEmergeA(self): + # The goon is emerging from door a. + + self.avId = 0 + self.craneId = 0 + + h = 0 + dist = 15 + pos = self.boss.getPos() + walkTime = dist / self.velocity + + self.setPosHpr(pos[0], pos[1], pos[2], h, 0, 0) + self.d_setPosHpr(pos[0], pos[1], pos[2], h, 0, 0) + self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0)) + self.departureTime = globalClock.getFrameTime() + self.arrivalTime = self.departureTime + walkTime + + self.d_setTarget(self.target[0], self.target[1], h, + globalClockDelta.localToNetworkTime(self.arrivalTime)) + + self.__startWalk() + self.d_setObjectState('a', 0, 0) + + taskMgr.doMethodLater(walkTime, self.__recoverWalk, self.uniqueName('recoverWalk')) + + def exitEmergeA(self): + self.__stopWalk() + taskMgr.remove(self.uniqueName('recoverWalk')) + + def enterEmergeB(self): + # The goon is emerging from door b. + + self.avId = 0 + self.craneId = 0 + + h = 180 + dist = 15 + pos = self.boss.getPos() + walkTime = dist / self.velocity + + self.setPosHpr(pos[0], pos[1], pos[2], h, 0, 0) + self.d_setPosHpr(pos[0], pos[1], pos[2], h, 0, 0) + self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0)) + self.departureTime = globalClock.getFrameTime() + self.arrivalTime = self.departureTime + walkTime + + self.d_setTarget(self.target[0], self.target[1], h, + globalClockDelta.localToNetworkTime(self.arrivalTime)) + + self.__startWalk() + self.d_setObjectState('b', 0, 0) + + taskMgr.doMethodLater(walkTime, self.__recoverWalk, self.uniqueName('recoverWalk')) + + def exitEmergeB(self): + self.__stopWalk() + taskMgr.remove(self.uniqueName('recoverWalk')) + + def enterBattle(self): + self.d_setObjectState('B', 0, 0) + + def exitBattle(self): + taskMgr.remove(self.taskName("resumeWalk")) + + def enterStunned(self): + self.d_setObjectState('S', 0, 0) + + def exitStunned(self): + taskMgr.remove(self.taskName("recovery")) + + def enterRecovery(self): + self.d_setObjectState('R', 0, 0) + taskMgr.doMethodLater(2.0, self.__recoverWalk, self.uniqueName('recoverWalk')) + + def exitRecovery(self): + self.__stopWalk() + taskMgr.remove(self.uniqueName('recoverWalk')) diff --git a/toontown/src/suit/DistributedFactorySuit.py b/toontown/src/suit/DistributedFactorySuit.py new file mode 100644 index 0000000..811621b --- /dev/null +++ b/toontown/src/suit/DistributedFactorySuit.py @@ -0,0 +1,648 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * + +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +import DistributedSuitBase +from direct.task.Task import Task +import random +from toontown.toonbase import ToontownGlobals +from otp.level import LevelConstants +from toontown.distributed.DelayDeletable import DelayDeletable + +class DistributedFactorySuit(DistributedSuitBase.DistributedSuitBase, + DelayDeletable): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedFactorySuit') + + def __init__(self, cr): + """__init__(cr)""" + try: + self.DistributedSuit_initialized + except: + self.DistributedSuit_initialized = 1 + DistributedSuitBase.DistributedSuitBase.__init__(self, cr) + + # Set up the DistributedSuit state machine + self.fsm = ClassicFSM.ClassicFSM( + 'DistributedSuit', + [State.State('Off', + self.enterOff, + self.exitOff, + ['Walk', + 'Battle']), + State.State('Walk', + self.enterWalk, + self.exitWalk, + ['WaitForBattle', + 'Battle', + 'Chase'] + ), + State.State('Chase', + self.enterChase, + self.exitChase, + ['WaitForBattle', + 'Battle', + 'Return', + ] + ), + State.State('Return', + self.enterReturn, + self.exitReturn, + ['WaitForBattle', + 'Battle', + 'Walk'] + ), + State.State('Battle', + self.enterBattle, + self.exitBattle, + ['Walk', + 'Chase', + 'Return',]), + State.State('WaitForBattle', + self.enterWaitForBattle, + self.exitWaitForBattle, + ['Battle']), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + self.path = None + self.walkTrack = None + self.chaseTrack = None + self.returnTrack = None + self.fsm.enterInitialState() + self.chasing = 0 + self.paused = 0 + self.pauseTime = 0 + self.velocity = 3 + self.factoryRequest = None + + return None + + def generate(self): + DistributedSuitBase.DistributedSuitBase.generate(self) + + def setLevelDoId(self, levelDoId): + self.notify.debug("setLevelDoId(%s)" % (levelDoId)) + self.levelDoId = levelDoId + + def setCogId(self, cogId): + self.cogId = cogId + def setReserve(self, reserve): + self.reserve = reserve + + def denyBattle(self): + # make this a warning so we see it in the logs + self.notify.warning('denyBattle()') + + place = self.cr.playGame.getPlace() + if place.fsm.getCurrentState().getName() == 'WaitForBattle': + place.setState('walk') + + def doReparent(self): + """the reparent may be delayed if our parent hasn't been created + yet""" + self.notify.debug("Suit requesting reparenting") + if not hasattr(self,'factory'): + self.notify.warning('no factory, get Redmond to look at DistributedFactorySuit.announceGenerate()') + self.factory.requestReparent(self, self.spec['parentEntId']) + # set the path the suit walks on + if self.pathEntId: + self.factory.setEntityCreateCallback(self.pathEntId, + self.setPath) + else: + self.setPath() + + def setCogSpec(self, spec): + self.spec = spec + self.setPos(spec['pos']) + self.setH(spec['h']) + self.originalPos = spec['pos'] + self.escapePos = spec['pos'] + self.pathEntId = spec['path'] + self.behavior = spec['behavior'] + self.skeleton = spec['skeleton'] + self.revives = spec.get('revives') + self.boss = spec['boss'] + # the AI now sets this, it's a required field + #if self.skeleton: + # self.makeSkeleton() + if self.reserve: + self.reparentTo(hidden) + else: + self.doReparent() + + def comeOutOfReserve(self): + self.doReparent() + + def getCogSpec(self, cogId): + if self.reserve: + return self.factory.getReserveCogSpec(cogId) + else: + return self.factory.getCogSpec(cogId) + + def announceGenerate(self): + self.notify.debug("announceGenerate %s" % self.doId) + + #print "%s: originalPos = %s" % (self.doId, self.originalPos) + #self.setState('Walk') + # Since Cogs are not entities, we need to jump through some + # hoops here. We need to wait until the factory has been generated, + # then we need to wait until the factory has had a chance to set + # itself up. + def onFactoryGenerate(factoryList, self=self): + self.factory = factoryList[0] + def onFactoryReady(self=self): + self.notify.debug("factory ready, read spec") + spec = self.getCogSpec(self.cogId) + self.setCogSpec(spec) + self.factoryRequest = None + # we can't get the parent node for the cog + # until the factory's levelMgr has been created + self.factory.setEntityCreateCallback(LevelConstants.LevelMgrEntId, + onFactoryReady) + self.factoryRequest = self.cr.relatedObjectMgr.requestObjects( + [self.levelDoId], onFactoryGenerate) + + + DistributedSuitBase.DistributedSuitBase.announceGenerate(self) + + def disable(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject + // is removed from active duty and stored in a cache. + // Parameters: + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.ignoreAll() + if self.factoryRequest is not None: + self.cr.relatedObjectMgr.abortRequest(self.factoryRequest) + self.factoryRequest = None + self.notify.debug("DistributedSuit %d: disabling" % self.getDoId()) + self.setState('Off') + + # remove walk track + if self.walkTrack: + del self.walkTrack + self.walkTrack = None + + DistributedSuitBase.DistributedSuitBase.disable(self) + taskMgr.remove(self.taskName('returnTask')) + taskMgr.remove(self.taskName('checkStray')) + taskMgr.remove(self.taskName('chaseTask')) + return + + def delete(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // permanently removed from the world and deleted from + // the cache. + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + try: + self.DistributedSuit_deleted + except: + self.DistributedSuit_deleted = 1 + self.notify.debug("DistributedSuit %d: deleting" % self.getDoId()) + + del self.fsm + DistributedSuitBase.DistributedSuitBase.delete(self) + + def d_requestBattle(self, pos, hpr): + """d_requestBattle(toonId) + """ + # Make sure the local toon can't continue to run around (and + # potentially start battles with other suits!) + self.cr.playGame.getPlace().setState('WaitForBattle') + + # lock the factory visibility to the suit's zone + self.factory.lockVisibility( + zoneNum=self.factory.getEntityZoneEntId(self.spec['parentEntId'])) + + self.sendUpdate('requestBattle', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2]]) + + def handleBattleBlockerCollision(self): + """ This function is used the the BattleBlockerEntity to indirectly + call __handleToonCollision when a toon tries to cross the battle + blocker collision geom """ + self.__handleToonCollision(None) + + + def __handleToonCollision(self, collEntry): + """ + ///////////////////////////////////////////////////////////// + // Function: This function is the callback for any + // collision events that the collision sphere + // for this bad guy might receive + // Parameters: collEntry, the collision entry object + // Changes: None + ///////////////////////////////////////////////////////////// + """ + if collEntry: + if collEntry.getFromNodePath().getParent().getKey() != localAvatar.getKey(): + return + + # Hack: Sellbot Warehouse has Cogs standing above the player. Prevent the Cogs' + # big collision spheres from abducting the player. + if hasattr(self, 'factory') and hasattr(self.factory, 'lastToonZone'): + factoryZone = self.factory.lastToonZone + unitsBelow = self.getPos(render)[2] - base.localAvatar.getPos(render)[2] + if factoryZone == 24 and unitsBelow > 10.0: + self.notify.warning('Ignoring toon collision in %d from %f below.' + % (factoryZone, unitsBelow)) + return + + if not base.localAvatar.wantBattles: + return + + toonId = base.localAvatar.getDoId() + self.notify.debug('Distributed suit %d: requesting a Battle with toon: %d' % + (self.doId, toonId)) + self.d_requestBattle(self.getPos(), self.getHpr()) + + # the suit on this machine only will go into wait for battle while it + # is waiting for word back from the server about our battle request + # + self.setState('WaitForBattle') + + return None + + def setPath(self): + self.notify.debug("setPath %s" % self.doId) + # if a path is not defined, then the suit won't walk + if self.pathEntId != None: + # set up the walk heirarchy + # parent + # | + # path + # | + # idealPathNode + # | + # suit + # The idealPathNode exactly follows the path described + # in the path entity. If the suit has strayed far enough off the + # path, he will head back towards the idealPathNode as it + # is lerped around the path. As he is heading back, he will + # course correct several times so he rejoins the path in sync + # with the idealPathNode's movement. + parent = self.factory.entities.get(self.spec['parentEntId']) + self.path = self.factory.entities.get(self.pathEntId) + assert parent != None + assert self.path != None + self.idealPathNode = self.path.attachNewNode("idealPath") + self.reparentTo(self.idealPathNode) + self.setPos(0,0,0) + self.path.reparentTo(parent) + + # make path track + self.walkTrack = self.path.makePathTrack(self.idealPathNode, self.velocity, + self.uniqueName("suitWalk")) + + + # Finally we can enter the walk state + self.setState('Walk') + + def initializeBodyCollisions(self, collIdStr): + DistributedSuitBase.DistributedSuitBase.initializeBodyCollisions(self, collIdStr) + + # setup a sphere for discovering a nearby toon + self.sSphere = CollisionSphere(0.0, 0.0, 0.0, 15) + name = self.uniqueName("toonSphere") + self.sSphereNode = CollisionNode(name) + self.sSphereNode.addSolid(self.sSphere) + self.sSphereNodePath=self.attachNewNode(self.sSphereNode) + self.sSphereNodePath.hide() + self.sSphereBitMask = ToontownGlobals.WallBitmask + self.sSphereNode.setCollideMask(self.sSphereBitMask) + self.sSphere.setTangible(0) + self.accept("enter" + name, self.__handleToonCollision) + + def enableBattleDetect(self, name, handler): + DistributedSuitBase.DistributedSuitBase.enableBattleDetect(self,name,handler) + self.lookForToon(1) + + def disableBattleDetect(self): + DistributedSuitBase.DistributedSuitBase.disableBattleDetect(self) + self.lookForToon(0) + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### Off state ##### + + # Defined in DistributedSuitBase.py + + def subclassManagesParent(self): + return 1 + + ########## Walk state and detect toon functions ########### + + def enterWalk(self, ts=0): + self.enableBattleDetect('walk', self.__handleToonCollision) + # create the walk track if we have a path + if self.path: + if self.walkTrack: + self.walkTrack.loop() + self.walkTrack.pause() + if self.paused: + self.walkTrack.setT(self.pauseTime) + else: + self.walkTrack.setT(ts) + self.walkTrack.resume() + self.loop('walk', 0) + self.paused = 0 + else: + # Just stand here waiting for a toon to approach. + self.loop('neutral', 0) + + def exitWalk(self): + self.disableBattleDetect() + if self.walkTrack: + self.pauseTime = self.walkTrack.pause() + self.paused = 1 + return + + def lookForToon(self, on=1): + if self.behavior in ['chase']: + if on: + self.accept(self.uniqueName("entertoonSphere"), self.__handleToonAlert) + else: + self.ignore(self.uniqueName("entertoonSphere")) + + def __handleToonAlert(self, collEntry): + # TODO: check toonpos against the FOV of the suit + self.notify.debug("%s: ahah! i saw you" % self.doId) + # check the z's, make sure they are the same + toonZ = base.localAvatar.getZ(render) + suitZ = self.getZ(render) + dZ = abs(toonZ-suitZ) + # check if toon is on this floor + # (assume space between floor and ceiling is at least 8 feet) + if dZ < 8.0: + self.sendUpdate("setAlert", [base.localAvatar.doId]) + + # Needed by DistributedSuitBase.denyBattle + def resumePath(self, state): + # go back to walk state + self.setState('Walk') + + ########## Chase state and check stray functions ########## + + + def enterChase(self): + self.enableBattleDetect('walk', self.__handleToonCollision) + self.startChaseTime = globalClock.getFrameTime() + # Start checking if we've strayed too far (or too long) + self.startCheckStrayTask(1) + self.startChaseTask() + + def exitChase(self): + self.disableBattleDetect() + taskMgr.remove(self.taskName('chaseTask')) + # Stop the chase track + if self.chaseTrack: + self.chaseTrack.pause() + del self.chaseTrack + self.chaseTrack = None + self.chasing = 0 + # Stop checking if we've strayed + self.startCheckStrayTask(0) + + def setConfrontToon(self, avId): + self.notify.debug('DistributedFactorySuit.setConfrontToon %d' % avId) + self.chasing = avId + self.setState("Chase") + + def startChaseTask(self, delay=0): + self.notify.debug('DistributedFactorySuit.startChaseTask delay=%s' % delay) + taskMgr.remove(self.taskName('chaseTask')) + taskMgr.doMethodLater(delay, + self.chaseTask, + self.taskName('chaseTask')) + + def chaseTask(self, task): + # Chase an avatar around + if not self.chasing: + return Task.done + + # make sure avatar exists + av = base.cr.doId2do.get(self.chasing, None) + if not av: + self.notify.warning("avatar %s isn't here to chase" % self.chasing) + return Task.done + + # check how long we've been chasing + if (globalClock.getFrameTime() - self.startChaseTime > 3.0): + self.setReturn() + return Task.done + + toonPos = av.getPos(self.getParent()) + suitPos = self.getPos() + distance = Vec3(suitPos-toonPos).length() + + # chase this toon + if self.chaseTrack: + self.chaseTrack.pause() + del self.chaseTrack + self.chaseTrack = None + + import random + rand1 = .5 + rand2 = .5 + rand3 = .5 + # randomize the position a bit, so cogs don't stack up + targetPos = Vec3(toonPos[0] + (4.0 * (rand1 - .5)), + toonPos[1] + (4.0 * (rand2 - .5)), + suitPos[2]) + track = Sequence(Func(self.headsUp, + targetPos[0],targetPos[1],targetPos[2]), + Func(self.loop, 'walk', 0)) + chaseSpeed = 4.0 + duration = distance / chaseSpeed + track.extend([LerpPosInterval(self, duration=duration, + pos = Point3(targetPos), + startPos = Point3(suitPos)), + ]) + self.chaseTrack = track + self.chaseTrack.start() + + # course correct every second + self.startChaseTask(1.0) + + def startCheckStrayTask(self, on=1): + taskMgr.remove(self.taskName("checkStray")) + if on: + taskMgr.add(self.checkStrayTask, + self.taskName("checkStray")) + + def checkStrayTask(self, task): + curPos = self.getPos() + distance = Vec3(curPos - self.originalPos).length() + if distance > 10.0: + self.sendUpdate("setStrayed", []) + + + ############# Return state ############## + + def enterReturn(self): + # Return the cog to it's starting position + + # specifically disable looking for toons on the way back + # but it is ok to battle them if we actually bump into them + self.enableBattleDetect('walk', self.__handleToonCollision) + self.lookForToon(0) + #if not self.chasing: + # return + + # return to where we should be + self.startReturnTask() + + + def exitReturn(self): + self.disableBattleDetect() + taskMgr.remove(self.taskName("checkStray")) + taskMgr.remove(self.taskName('returnTask')) + if self.returnTrack: + self.returnTrack.pause() + self.returnTrack = None + + def setReturn(self): + # put suit in walk state if not already in walk state + self.notify.debug('DistributedFactorySuit.setReturn') + self.setState('Return') + + def startReturnTask(self, delay=0): + taskMgr.remove(self.taskName('returnTask')) + taskMgr.doMethodLater(delay, + self.returnTask, + self.taskName('returnTask')) + + def returnTask(self, task): + # return to parent node, which is the idealPathNode + # first, do a straight reparent to our original parent + self.factory.requestReparent(self, self.spec['parentEntId']) + + # if we are on a path, or just the original pos + # if it is a static suit + if self.returnTrack: + self.returnTrack.pause() + self.returnTrack = None + + # find where to head back to + if self.path: + # head back to our parent, the idealPathNode + targetPos = VBase3(0,0,0) + else: + # head to the origin + targetPos = self.originalPos + + track = Sequence(Func(self.headsUp, + targetPos[0], + targetPos[1], + targetPos[2]), + Func(self.loop, 'walk', 0)) + curPos = self.getPos() + distance = Vec3(curPos - targetPos).length() + duration = distance / 3.0 + track.append(LerpPosInterval(self, duration=duration, + pos = Point3(targetPos), + startPos = Point3(curPos))) + track.append(Func(self.returnDone)) + self.returnTrack = track + self.returnTrack.start() + + def returnDone(self): + assert self.notify.debug("returnDone") + # Reset hpr + self.setHpr(self.spec['h'],0,0) + self.setState("Walk") + # Return to neutral animation if not on a path + if not self.path: + self.loop('neutral') + + def setActive(self, active): + # suit is being put into action by AI, or it is being + # told to take a break. The initial active position is + # given by self.originalPos. The position the suit goes + # to take a break is self.escapePos + + if active: + # put into walk state + self.setState('Walk') + else: + # put into off state + self.setState('Off') + + def disableBattleDetect(self): + if self.battleDetectName: + self.ignore("enter" + self.battleDetectName) + self.battleDetectName = None + if self.collNodePath: + self.collNodePath.removeNode() + self.collNodePath = None + + def disableBodyCollisions(self): + self.disableBattleDetect() + self.enableRaycast(0) + if self.cRayNodePath: + self.cRayNodePath.removeNode() + if hasattr(self, "cRayNode"): + del self.cRayNode + if hasattr(self, "cRay"): + del self.cRay + if hasattr(self, "lifter"): + del self.lifter + + def removeCollisions(self): + """ + clean up the suit's various collision data such + as the battle detection sphere, the ground collision + ray, and the lifter + """ + # make sure to remove any raycast information + # + self.enableRaycast(0) + self.cRay = None + self.cRayNode = None + self.cRayNodePath = None + self.lifter = None + self.cTrav = None + + def setVirtual(self, isVirtual = 1): + self.virtual = isVirtual + if self.virtual: + actorNode = self.find("**/__Actor_modelRoot") + actorCollection = actorNode.findAllMatches("*") + parts = () + for thingIndex in range(0,actorCollection.getNumPaths()): + thing = actorCollection[thingIndex] + if thing.getName() not in ('joint_attachMeter', 'joint_nameTag'): + thing.setColorScale(1.0,0.0,0.0,1.0) + thing.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd)) + thing.setDepthWrite(False) + thing.setBin('fixed', 1) + + + def getVirtual(self): + return self.virtual + + ##### Battle state ##### + + # Defined in DistributedSuitBase.py + + ##### WaitForBattle state ##### + + # Defined in DistributedSuitBase.py diff --git a/toontown/src/suit/DistributedFactorySuitAI.py b/toontown/src/suit/DistributedFactorySuitAI.py new file mode 100644 index 0000000..04e3300 --- /dev/null +++ b/toontown/src/suit/DistributedFactorySuitAI.py @@ -0,0 +1,150 @@ +from otp.ai.AIBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import DistributedSuitBaseAI +import SuitDialog + +class DistributedFactorySuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedFactorySuitAI') + + def __init__(self, air, suitPlanner): + """__init__(air, suitPlanner)""" + DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air, + suitPlanner) + + self.blocker = None + self.battleCellIndex = None + self.chasing = 0 + self.factoryGone = 0 + + def factoryIsGoingDown(self): + self.factoryGone = 1 + + def delete(self): + if not self.factoryGone: + self.setBattleCellIndex(None) + del self.blocker + self.ignoreAll() + DistributedSuitBaseAI.DistributedSuitBaseAI.delete(self) + + def setLevelDoId(self, levelDoId): + self.levelDoId = levelDoId + def getLevelDoId(self): + return self.levelDoId + + def setCogId(self, cogId): + self.cogId = cogId + def getCogId(self): + return self.cogId + + def setReserve(self, reserve): + self.reserve = reserve + def getReserve(self): + return self.reserve + + def requestBattle(self, x, y, z, h, p, r): + """requestBattle(x, y, z, h, p, r) + """ + toonId = self.air.getAvatarIdFromSender() + + if self.notify.getDebug(): + self.notify.debug(str(self.getDoId()) + \ + str(self.zoneId) + \ + ': request battle with toon: %d' % toonId) + + # Store the suit's actual pos and hpr on the client + self.confrontPos = Point3(x, y, z) + self.confrontHpr = Vec3(h, p, r) + + # Request a battle from the suit planner + if (self.sp.requestBattle(self, toonId)): + if self.notify.getDebug(): + self.notify.debug("Suit %d requesting battle in zone %d with toon %d" % + (self.getDoId(), self.zoneId, toonId)) + else: + # Suit tells toon to get lost + if self.notify.getDebug(): + self.notify.debug('requestBattle from suit %d, toon %d- denied by battle manager' % (toonId, self.getDoId())) + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + self.d_denyBattle(toonId) + + def getConfrontPosHpr(self): + """ getConfrontPosHpr() + """ + return (self.confrontPos, self.confrontHpr) + + def setBattleCellIndex(self, battleCellIndex): + self.sp.suitBattleCellChange(self, + oldCell=self.battleCellIndex, + newCell=battleCellIndex) + self.battleCellIndex = battleCellIndex + # attach any battle blockers for this cell + self.attachBattleBlocker() + # and listen for any other blockers that might be created for this cell + self.accept(self.sp.getBattleBlockerEvent(self.battleCellIndex), self.attachBattleBlocker) + + def getBattleCellIndex(self): + return self.battleCellIndex + + def attachBattleBlocker(self): + blocker = self.sp.battleMgr.battleBlockers.get(self.battleCellIndex) + self.blocker = blocker + + def setAlert(self, avId): + #if self.chasing: + # return + + # make sure the avId is the same as the message sender + if avId == self.air.getAvatarIdFromSender(): + av = self.air.doId2do.get(avId) + if av: + self.chasing = avId + # if we are already in a battle don't send the confront + # TODO figure out a better way if this suit is already in a battle + if self.sp.battleMgr.cellHasBattle(self.battleCellIndex): + pass + else: + self.sendUpdate("setConfrontToon", [avId]) + + def setStrayed(self): + if self.chasing > 0: + # tell the clients the Suit has gone too far, + # and let them move the suit back into position + self.chasing = 0 + self.sendUpdate("setReturn", []) + + def resume( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: called by battles to tell this suit that it is no + // longer part of a battle, and it should carry on. + // Parameters: + // Changes: + //////////////////////////////////////////////////////////////////// + """ + + self.notify.debug("Suit %s resume" % (self.doId)) + + if self.currHP <= 0: + messenger.send(self.getDeathEvent()) + # If the suit's dead, take it out. + self.notify.debug("Suit %s dead after resume" % (self.doId)) + self.requestRemoval() + + else: + # return the suit to it's original waiting position + # (or path, if/when the factory suits are walking on paths) + self.sendUpdate("setReturn", []) + return None + + def isForeman(self): + return self.boss + + def setVirtual(self, isVirtual = 1): + self.virtual = isVirtual + + def getVirtual(self): + return self.virtual diff --git a/toontown/src/suit/DistributedGoon.py b/toontown/src/suit/DistributedGoon.py new file mode 100644 index 0000000..d80e938 --- /dev/null +++ b/toontown/src/suit/DistributedGoon.py @@ -0,0 +1,795 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from toontown.battle.BattleProps import * +from GoonGlobals import * + +from direct.fsm import FSM +from direct.distributed import ClockDelta +from otp.level import BasicEntities +from otp.level import DistributedEntity +from direct.directnotify import DirectNotifyGlobal +from toontown.coghq import DistributedCrushableEntity +from toontown.toonbase import ToontownGlobals +from toontown.coghq import MovingPlatform +import Goon +from direct.task.Task import Task +from otp.level import PathEntity +import GoonDeath +import random + + +class DistributedGoon(DistributedCrushableEntity.DistributedCrushableEntity, + Goon.Goon, FSM.FSM): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGoon') + + def __init__(self, cr): + try: + self.DistributedGoon_initialized + except: + self.DistributedGoon_initialized = 1 + DistributedCrushableEntity.DistributedCrushableEntity.__init__(self, cr) + Goon.Goon.__init__(self) + FSM.FSM.__init__(self, 'DistributedGoon') + + # Don't try caching goons. It seems to be a little bit broken + # anyway. + self.setCacheable(0) + + # collision stuff + self.rayNode = None + self.checkForWalls = 0 + self.triggerEvent = None + + # animation and walking + self.animTrack = None + self.walkTrack = None + self.pauseTime = 0 + self.paused = 0 + self.path = None + self.dir = GOON_FORWARD + self.animMultiplier = 1.0 + self.isDead = 0 + self.isStunned = 0 + self.collapseSound = loader.loadSfx("phase_9/audio/sfx/CHQ_GOON_hunker_down.mp3") + self.recoverSound = loader.loadSfx("phase_9/audio/sfx/CHQ_GOON_rattle_shake.mp3") + self.attackSound = loader.loadSfx("phase_9/audio/sfx/CHQ_GOON_tractor_beam_alarmed.mp3") + + def announceGenerate(self): + DistributedCrushableEntity.DistributedCrushableEntity.announceGenerate(self) + + # if the goonType is set by the Spec use it, otherwise make a default goon + if hasattr(self, 'goonType'): + self.initGoon(self.goonType) + else: + self.initGoon('pg') + + # scale the radar depending on fov and attackRadius. + self.scaleRadar() + + # Set the hat color according to his strength. + self.colorHat() + + if self.level: + # enable clip planes + self.initClipPlanes() + + # set the path the goon walks on + self.level.setEntityCreateCallback(self.parentEntId, + self.initPath) + else: + self.enterOff() + taskMgr.doMethodLater(0.1, + self.makeCollidable, + self.taskName("makeCollidable")) + + # usually we want the goons to be a little bigger + self.setGoonScale(self.scale) + # Figure out walk rate to anim speed multiplier + self.animMultiplier = self.velocity / (ANIM_WALK_RATE * self.scale) + self.setPlayRate(self.animMultiplier, 'walk') + + def initPath(self): + """ + Initialize this goon's position and then setup its collision. + This avoids issues with delays in positioning causing undesired + collisions. We found this sequence of events in a bug: + 1.) Goon created at origin, waits for path to load in level. + 2.) Goon's collision activated. + 3.) Path loads, callback positions goon. + 4.) This move shoves the toon. (Not entirely sure why...) + Clearly there is something wrong in step 4, but this at least sets + position before collision. + """ + + self.enterOff() + self.setPath() + + taskMgr.doMethodLater(0.1, + self.makeCollidable, + self.taskName("makeCollidable")) + + + def makeCollidable(self, task): + """ + Initialize the collision and trigger for this goon. After this + the goon is collidable, detecting the toon, and responsive to stun. + """ + + self.initCollisions() + + # set up stun collisions and body sphere for goon + self.initializeBodyCollisions() + triggerName = self.uniqueName('GoonTrigger') + self.trigger.setName(triggerName) + self.triggerEvent = 'enter%s' % (triggerName) + + # Start listening for the local toon. + self.startToonDetect() + + def generate(self): + DistributedCrushableEntity.DistributedCrushableEntity.generate(self) + + def scaleRadar(self): + Goon.Goon.scaleRadar(self) + + self.trigger = self.radar.find('**/trigger') + + # Make sure the trigger name is set. + triggerName = self.uniqueName('GoonTrigger') + self.trigger.setName(triggerName) + + def initCollisions(self): + # Setup a collision sphere that we can't walk through + self.cSphere = CollisionSphere(0.0, 0.0, 1.0, 1.0) + #self.cSphereNode = CollisionNode(self.uniqueName("goonCollSphere")) + self.cSphereNode = CollisionNode("goonCollSphere") # change name on generate + self.cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath=self.head.attachNewNode(self.cSphereNode) + self.cSphereNodePath.hide() + self.cSphereBitMask = ToontownGlobals.WallBitmask + self.cSphereNode.setCollideMask(self.cSphereBitMask) + self.cSphere.setTangible(1) + + # Setup a sphere to detect "stun" collision with the toon + self.sSphere = CollisionSphere(0.0, 0.0, self.headHeight+.8, .2) + #self.sSphereNode = CollisionNode(self.uniqueName("toonSphere")) + self.sSphereNode = CollisionNode("toonSphere") + self.sSphereNode.addSolid(self.sSphere) + self.sSphereNodePath=self.head.attachNewNode(self.sSphereNode) + self.sSphereNodePath.hide() + self.sSphereBitMask = ToontownGlobals.WallBitmask + self.sSphereNode.setCollideMask(self.sSphereBitMask) + self.sSphere.setTangible(1) + + def initializeBodyCollisions(self): + self.cSphereNode.setName(self.uniqueName("goonCollSphere")) + self.sSphereNode.setName(self.uniqueName("toonSphere")) + + # If a toon runs directly into the goon without being detected, + # the goon becomes stunned + self.accept(self.uniqueName("entertoonSphere"), self.__handleStun) + + def disableBodyCollisions(self): + self.ignore(self.uniqueName("entertoonSphere")) + + def deleteCollisions(self): + if hasattr(self, 'sSphereNodePath'): + self.sSphereNodePath.removeNode() + del self.sSphereNodePath + del self.sSphereNode + del self.sSphere + + if hasattr(self, 'cSphereNodePath'): + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + del self.cSphereNode + del self.cSphere + + def initClipPlanes(self): + # look for all clip planes in thiz zone + zoneNum = self.getZoneEntity().getZoneNum() + clipList = self.level.goonClipPlanes.get(zoneNum) + if clipList: + for id in clipList: + clipPlane = self.level.getEntity(id) + self.radar.setClipPlane(clipPlane.getPlane()) + + + def disableClipPlanes(self): + if self.radar: + self.radar.clearClipPlane() + + if __dev__: + def refreshPath(self): + self.setPath() + self.request('Off') + self.request('Walk') + + def setPath(self): + self.path = self.level.getEntity(self.parentEntId) + if __dev__: + if hasattr(self, 'pathChangeEvent'): + self.ignore(self.pathChangeEvent) + self.pathChangeEvent = self.path.getChangeEvent() + self.accept(self.pathChangeEvent, self.refreshPath) + if self.walkTrack: + self.walkTrack.pause() + self.walkTrack = None + self.walkTrack = self.path.makePathTrack(self, self.velocity, + self.uniqueName("goonWalk"), + turnTime = T_TURN) + + # if we are on a grid, tell the AI to parameterize the path + if self.gridId != None: + self.sendUpdate("setParameterize", [self.path.pos[0], + self.path.pos[1], + self.path.pos[2], + self.path.pathIndex]) + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + self.notify.debug("DistributedGoon %d: disabling" % self.getDoId()) + self.ignoreAll() + self.stopToonDetect() + taskMgr.remove(self.taskName("resumeWalk")) + taskMgr.remove(self.taskName("recoveryDone")) + self.request('Off') + self.disableBodyCollisions() + self.disableClipPlanes() + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + if self.walkTrack: + self.walkTrack.pause() + self.walkTrack = None + + DistributedCrushableEntity.DistributedCrushableEntity.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is + permanently removed from the world and deleted from + the cache. + """ + try: + self.DistributedSuit_deleted + except: + self.DistributedSuit_deleted = 1 + self.notify.debug("DistributedGoon %d: deleting" % self.getDoId()) + + # stop waiting to set collisions + taskMgr.remove(self.taskName("makeCollidable")) + + # tear down collisions + self.deleteCollisions() + + self.head.removeNode() + del self.head + del self.attackSound + del self.collapseSound + del self.recoverSound + DistributedCrushableEntity.DistributedCrushableEntity.delete(self) + Goon.Goon.delete(self) + + ##### Off state ##### + + def enterOff(self, *args): + assert self.notify.debug('enterOff()') + self.hideNametag3d() + self.hideNametag2d() + self.hide() + self.isStunned = 0 + self.isDead = 0 + + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + if self.walkTrack: + self.walkTrack.pause() + self.walkTrack = None + + def exitOff(self): + self.show() + self.showNametag3d() + self.showNametag2d() + + ##### Walk state ##### + # The goon is walking around trying to detect toons + def enterWalk(self, avId=None, ts=0): + self.notify.debug('enterWalk, ts = %s' % ts) + # start the toon detection on enterWalk. + self.startToonDetect() + self.loop('walk', 0) + + self.isStunned = 0 + + if self.path: + if not self.walkTrack: + # this should only happen in debug when using ~resyncGoons + self.walkTrack = self.path.makePathTrack(self, self.velocity, + self.uniqueName("goonWalk"), + turnTime = T_TURN) + self.startWalk(ts) + + def startWalk(self, ts): + tOffset = ts % self.walkTrack.getDuration() + self.walkTrack.loop() + self.walkTrack.pause() + self.walkTrack.setT(tOffset) + self.walkTrack.resume() + self.paused = 0 + + def exitWalk(self): + self.notify.debug('exitWalk') + self.stopToonDetect() + if self.walkTrack and not self.paused: + self.pauseTime = self.walkTrack.pause() + self.paused = 1 + self.stop() + + + # The goon has just detected a toon + def enterBattle(self, avId=None, ts=0): + + self.notify.debug('enterBattle') + self.stopToonDetect() + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + + self.isStunned = 0 + + # before we play the attack track, start the resume walk timer. + # This might kick us to enterWalk before even playing the attack + # track, in high latency cases + # on second thought, let the AI tell us when to resume walking + # self.__startResumeWalkTask(ts) + + # stun toon in battle with goon + #if avId != None: + # self.stunToon(avId) + if avId == base.localAvatar.doId: + if self.level: + + #self.level.b_setOuch(self.strength, "Fall") + self.level.b_setOuch(self.strength) + # Get the track for the attack. Since it is just blinking + # the eye color, it isn't necessary to sinc with the timestamp + self.animTrack = self.makeAttackTrack() + self.animTrack.loop() + + + def exitBattle(self): + self.notify.debug('exitBattle') + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + self.head.setHpr(0,0,0) + + + # The toon has temporarily stunned the goon + def enterStunned(self, ts=0): + # disable stun sphere + self.ignore(self.uniqueName("entertoonSphere")) + + self.isStunned = 1 + + self.notify.debug("enterStunned") + if self.radar: + self.radar.hide() + + # before we play the stunned anim, start the recover timer + # This might kick us to enterWalk before even playing the stun + # track, in high latency cases + # on second thought, let the AI tell us when to resume walking + #self.__startRecoverTask(ts) + + self.animTrack = Parallel(Sequence(ActorInterval(self, 'collapse'), + Func(self.pose, 'collapse', 48)), + SoundInterval(self.collapseSound, node=self), + ) + + self.animTrack.start(ts) + + def exitStunned(self): + self.notify.debug("exitStunned") + if self.radar: + self.radar.show() + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + + # reenable stun sphere + self.accept(self.uniqueName("entertoonSphere"), self.__handleStun) + + + # Goon recovery from being stunned + def enterRecovery(self, ts=0, pauseTime=0): + # set a timer to restart the walk track + self.notify.debug("enterRecovery") + + self.ignore(self.uniqueName("entertoonSphere")) + + self.isStunned = 1 + + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + + self.animTrack = self.getRecoveryTrack() + + duration = self.animTrack.getDuration() + self.animTrack.start(ts) + + # start walk track after recovery plays + delay = max(0, duration-ts) + taskMgr.remove(self.taskName("recoveryDone")) + taskMgr.doMethodLater(delay, + self.recoveryDone, + self.taskName("recoveryDone"), + extraArgs = (pauseTime,)) + + def getRecoveryTrack(self): + return Parallel(Sequence(ActorInterval(self, 'recovery'), + Func(self.pose, 'recovery', 96), + ), + Func(base.playSfx,self.recoverSound, node=self), + ) + + def recoveryDone(self, pauseTime): + self.request('Walk', None, pauseTime) + + def exitRecovery(self): + self.notify.debug("exitRecovery") + taskMgr.remove(self.taskName("recoveryDone")) + if self.animTrack: + self.animTrack.finish() + self.animTrack = None + + # reenable stun sphere + self.accept(self.uniqueName("entertoonSphere"), self.__handleStun) + + def makeAttackTrack(self): + h = self.head.getH() + freakDeg = 60 + hatZ = self.hat.getZ() + track = Parallel(Sequence(LerpColorScaleInterval(self.eye, .2, + Vec4(1,0,0,1)), + LerpColorScaleInterval(self.eye, .2, + Vec4(0,0,1,1)), + LerpColorScaleInterval(self.eye, .2, + Vec4(1,0,0,1)), + LerpColorScaleInterval(self.eye, .2, + Vec4(0,0,1,1)), + Func(self.eye.clearColorScale), + ), + SoundInterval(self.attackSound, node=self, volume=.4)) + return track + + + # doDetect looks around for toons + # Subclasses can override this to use a different detection + # method + def doDetect(self): + pass + + + # doAttack penalizes the toon for being caught by this goon + # Subclasses can override this to do a different type + # of attack + def doAttack(self, avId): + return + + def __startResumeWalkTask(self, ts): + resumeTime = 1.5 + + if ts < resumeTime: + # set a timer to put us back in walk mode + taskMgr.remove(self.taskName("resumeWalk")) + taskMgr.doMethodLater(resumeTime-ts, + self.request, + self.taskName("resumeWalk"), + extraArgs = ('Walk',)) + else: + self.request('Walk', ts-resumeTime) + + def __reverseWalk(self, task): + self.request('Walk') + + return Task.done + + def __startRecoverTask(self, ts): + # Stun time should be long enough for collapse animation to play + # plus any additional time the goon should be out of service + stunTime = 4.0 + + if ts < stunTime: + # set a timer to put us back in walk mode + taskMgr.remove(self.taskName("resumeWalk")) + taskMgr.doMethodLater(stunTime-ts, + self.request, + self.taskName("resumeWalk"), + extraArgs = ('Recovery',)) + else: + self.request('Recovery', ts-stunTime) + + + def startToonDetect(self): + self.radar.show() # just in case. + if self.triggerEvent: + self.accept(self.triggerEvent, self.handleToonDetect) + + def stopToonDetect(self): + if self.triggerEvent: + self.ignore(self.triggerEvent) + +## def __detectLocalToon(self, task): +## toon = base.localAvatar +## # check distance from local toon +## toonPos = toon.getPos() +## goonPos = self.head.getPos(render) +## # hack: the ray is facing backwards if we don't flip the direction manually?! +## v = goonPos - toonPos +## distToToon = Vec3(v).length() + +## if (not toon.isStunned) and distToToon < self.attackRadius: +## # the toon is close enough, and not already stunned +## # now check if toon is in field of view + +## # get v relative to radar. Radar is facing Vec3(0,1,0) in its +## # own coordinate system +## vRelToRadar = self.radar.getRelativeVector(render, v) + +## rayDir = Vec3(vRelToRadar) +## vRelToRadar.normalize() + +## vDotR = vRelToRadar[1] # == vRelToRadar.dot(Vec3(0,1,0) + +## # err on the side of the player +## fudge = .05 + +## if vDotR < 1.0 and vDotR > self.cosHalfFov + fudge: +## if self.checkForWalls: +## # THIS CODE ISN'T WORKING RIGHT, NOR DO WE NEED +## # IT FOR THE CURRENT FACTORY LAYOUT. + +## # the toon is in the fov, +## # now make sure he is not occluded +## #rayOrigin = Point3(0,1.5,base.localAvatar.getHeight()/2.0) +## rayOrigin = Point3(0,0,self.headHeight) +## ray = CollisionRay(rayOrigin, rayDir) +## rayNode = CollisionNode(self.uniqueName('goonRay')) +## #rayNode.setCollideMask(BitMask32.allOff()) +## rayBitMask = ToontownGlobals.WallBitmask | ToontownGlobals.FloorBitmask +## rayNode.setFromCollideMask(rayBitMask) +## rayNode.setIntoCollideMask(BitMask32.allOff()) +## #rayNode.setCollideGeom(1) +## rayNode.addSolid(ray) +## rayNodePath = self.head.attachNewNode(rayNode) +## #rayNodePath.show() + +## cqueue = CollisionHandlerQueue() +## world = self.level.geom +## trav = CollisionTraverser("DistributedGoon") +## trav.addCollider(rayNodePath, cqueue) +## trav.traverse(world) + +## cqueue.sortEntries() + +## if cqueue.getNumEntries() == 0: +## # No objects were between the toon and goon +## self.notify.warning("%s: Couldn't find ANYTHING!" % (self.doId)) +## self.handleToonDetect() +## else: +## entry = cqueue.getEntry(0) + +## # if the closest interseciton point is behind our toon +## # then we can start the attack, otherwise consider the toon +## # hidden from the goon +## dist = Vec3(self.getPos(render) - entry.getIntoIntersectionPoint()).length() +## if dist > distToToon: +## colName = entry.getIntoNode().getName() +## self.notify.debug("collider (%s) at d=%s, toon at %s" % (colName, dist,distToToon)) +## self.handleToonDetect(entry) +## else: +## colName = entry.getIntoNode().getName() +## self.notify.debug("toon is occluded by %s, %s > %s" % (colName, distToToon, dist)) + +## rayNodePath.removeNode() +## else: +## # not checking for walls +## # Battle the toon +## self.handleToonDetect() + +## return Task.cont + + def handleToonDetect(self, collEntry=None): + if base.localAvatar.isStunned: + # It doesn't count if the toon is already stunned. + return + + if self.state == 'Off': + return + + # Stop looking for localToon + self.stopToonDetect() + + # Start the attack now for the local toon to see + self.request("Battle", base.localAvatar.doId) + + # pause the walk track before sending battle request to + # the AI so we know what time the walk track was paused + if self.walkTrack: + self.pauseTime = self.walkTrack.pause() + self.paused = 1 + + if self.dclass and hasattr(self, 'dclass'): + self.sendUpdate("requestBattle", [self.pauseTime]) + else: + self.notify.info( "Goon deleted and still trying to call handleToonDetect()" ) + # Need to get more information on why this case happens. + + def __handleStun(self, collEntry): + # Client side check first to see if we're in a reasonable distance to the goon to stun it. + toon = base.localAvatar + if toon: + toonDistance = self.getPos(toon).length() + if toonDistance > self.attackRadius: + self.notify.warning("Stunned a good, but outside of attack radius") + return + else: + self.request("Stunned") + + # pause the walk track before sending battle request to + # the AI so we know what time the walk track was paused + if self.walkTrack: + self.pauseTime = self.walkTrack.pause() + self.paused = 1 + + # Tell the AI we are stunned + self.sendUpdate("requestStunned", [self.pauseTime]) + + def setMovie(self, mode, avId, pauseTime, timestamp): + """ + This is a message from the AI describing a movie for this goon + """ + # do nothing if dead + if self.isDead: + return + + ts = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.notify.debug("%s: setMovie(%s,%s,%s,%s)" % (self.doId, mode,avId,pauseTime,ts)) + + if mode == GOON_MOVIE_BATTLE: + if self.state != "Battle": + self.request("Battle", avId, ts) + elif mode == GOON_MOVIE_STUNNED: + if self.state != "Stunned": + # Client side check first to see if we're in a reasonable distance to the goon to stun it. + toon = base.cr.doId2do.get(avId) + if toon: + toonDistance = self.getPos(toon).length() + if toonDistance > self.attackRadius: + self.notify.warning("Stunned a goon, but outside of attack radius") + return + else: + self.request("Stunned", ts) + elif mode == GOON_MOVIE_RECOVERY: + if self.state != "Recovery": + self.request("Recovery", ts, pauseTime) + elif mode == GOON_MOVIE_SYNC: + if self.walkTrack: + self.walkTrack.pause() + self.paused = 1 + if self.state == "Off" or self.state == "Walk": + self.request("Walk", avId, pauseTime+ts) + else: + # walk + if self.walkTrack: + self.walkTrack.pause() + self.walkTrack = None + self.request("Walk", avId, pauseTime+ts) + + def stunToon(self, avId): + self.notify.debug("stunToon(%s)" % avId) + # Stun localtoon + av = base.cr.doId2do.get(avId) + if av != None: + av.stunToon() + + def isLocalToon(self, avId): + if avId == base.localAvatar.doId: + return 1 + return 0 + + def playCrushMovie(self, crusherId, axis): + goonPos = self.getPos() + # randomize the x and z scale a little + sx = random.uniform(0.3, 0.8) * self.scale + sz = random.uniform(0.3, 0.8) * self.scale + crushTrack = Sequence( + GoonDeath.createGoonExplosion(self.getParent(), + goonPos, VBase3(sx, 1, sz)), + name = self.uniqueName('crushTrack'), + autoFinish = 1) + self.dead() + crushTrack.start() + + def setVelocity(self, velocity): + self.velocity = velocity + # tune play rate + self.animMultiplier = velocity / (ANIM_WALK_RATE * self.scale) + self.setPlayRate(self.animMultiplier, 'walk') + + """ + def reverseDirection(self, colEntry=None): + # Stop walking immediately and reverse direction. + # This is done simply by switching our self.dir and + # calling goToNextPoint again + if self.dir == GOON_FORWARD: + self.dir = GOON_REVERSE + else: + self.dir = GOON_FORWARD + + self.nextInd = (self.curInd + self.dir) % (len(self.path)-1) + self.goToNextPoint(self.curInd, self.nextInd, self.velocity) + """ + + def dead(self): + if not self.isDead and not self.isDisabled(): + # goon is dead, stop detecting, it's not fair + self.stopToonDetect() + # hide for now + self.detachNode() + self.isDead = 1 + + def undead(self): + # start toon detection task again + if self.isDead: + self.startToonDetect() + self.reparentTo(render) + self.isDead = 0 + + def resync(self): + if not self.isDead: + # get the AI and client on the same page again + # (used only in dev environment for now) + self.sendUpdate("requestResync") + + def setHFov(self, hFov): + if hFov != self.hFov: + self.hFov = hFov + if self.isGenerated(): + self.scaleRadar() + + def setAttackRadius(self, attackRadius): + if attackRadius != self.attackRadius: + self.attackRadius = attackRadius + if self.isGenerated(): + self.scaleRadar() + + def setStrength(self, strength): + if strength != self.strength: + self.strength = strength + if self.isGenerated(): + self.colorHat() + + def setGoonScale(self, scale): + # This is not named setScale(), so it can be distinct from + # NodePath.setSale(). + if scale != self.scale: + self.scale = scale + if self.isGenerated(): + self.getGeomNode().setScale(self.scale) + self.scaleRadar() + + def setupGoon(self, velocity, hFov, attackRadius, strength, scale): + self.setVelocity(velocity) + self.setHFov(hFov) + self.setAttackRadius(attackRadius) + self.setStrength(strength) + self.setGoonScale(scale) diff --git a/toontown/src/suit/DistributedGoonAI.py b/toontown/src/suit/DistributedGoonAI.py new file mode 100644 index 0000000..d112c55 --- /dev/null +++ b/toontown/src/suit/DistributedGoonAI.py @@ -0,0 +1,351 @@ +from otp.ai.AIBaseGlobal import * +from GoonGlobals import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +from toontown.coghq import DistributedCrushableEntityAI +import GoonPathData +from direct.distributed import ClockDelta +import random +from direct.task import Task + +class DistributedGoonAI(DistributedCrushableEntityAI.DistributedCrushableEntityAI): + """ + A simple, dumb robot. + The robot should be flexible and reusable, for uses in CogHQ basements + and factories, and perhaps other parts of the game. Let the goon's + movement, discovery, and attack methods be modular, so different behavior + types can be easily plugged in. + """ + + # Send an updated timestamp for each suit after about this many + # seconds have elapsed since the last timestamp. + UPDATE_TIMESTAMP_INTERVAL = 180.0 + + # How long does the goon remain stunned? + STUN_TIME = 4 + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGoonAI') + + def __init__(self, level, entId): + # These are default properties. They are not used in the + # Entity system, which is used by the normal DistributedGoon + # class; but the DistributedObject system as used by the + # DistributedCashbotBossGoon does use them. + + # It is important to initialize these before calling up to the + # DistributedCrushableEntityAI constructor, which may modify + # them according to the spec. + self.hFov = 70 + self.attackRadius = 15 + self.strength = 15 + self.velocity = 4 + self.scale = 1.0 + + DistributedCrushableEntityAI.DistributedCrushableEntityAI.__init__(self, + level, entId) + self.curInd = 0 + self.dir = GOON_FORWARD + self.parameterized = 0 + self.width = 1 + self.crushed = 0 + + self.pathStartTime = None + self.walkTrackTime = 0.0 + self.totalPathTime = 1.0 + + def delete(self): + taskMgr.remove(self.taskName("sync")) + taskMgr.remove(self.taskName("resumeWalk")) + taskMgr.remove(self.taskName("recovery")) + taskMgr.remove(self.taskName("deleteGoon")) + DistributedCrushableEntityAI.DistributedCrushableEntityAI.delete(self) + + def generate(self): + self.notify.debug('generate') + DistributedCrushableEntityAI.DistributedCrushableEntityAI.generate(self) + if self.level: + self.level.setEntityCreateCallback(self.parentEntId, self.startGoon) + + def startGoon(self): + # start path at a randomized point + ts = 100 * random.random() + self.sendMovie(GOON_MOVIE_WALK, pauseTime=ts) + + def requestBattle(self, pauseTime): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestBattle, avId = %s" % avId) + + # For now we don't check the state of the goon and just + # assume that he can always attack, no matter what state he's in + + # Tell the other clients + self.sendMovie(GOON_MOVIE_BATTLE, avId, pauseTime) + + # Wait a little while and put the goon back in walk mode + taskMgr.remove(self.taskName("resumeWalk")) + taskMgr.doMethodLater(5, + self.sendMovie, + self.taskName("resumeWalk"), + extraArgs = (GOON_MOVIE_WALK, avId, pauseTime)) + + def requestStunned(self, pauseTime): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestStunned(%s)" % avId) + + # For now we don't check the state of the goon and just + # assume that he can always be stunned, no matter what state he's in + + # Tell the other clients + self.sendMovie(GOON_MOVIE_STUNNED, avId, pauseTime) + + # Wait a little while and put the goon back in recovery mode + taskMgr.remove(self.taskName("recovery")) + taskMgr.doMethodLater(self.STUN_TIME, + self.sendMovie, + self.taskName("recovery"), + extraArgs = (GOON_MOVIE_RECOVERY, avId, pauseTime)) + + def requestResync(self, task=None): + """ + resync(self) + + Broadcasts a walk message to all clients who care. + This is mainly useful while developing, in case you + have paused the AI or your client and you are now out of sync. + We should resync every 5 minutes, so the timestamp doesn't go + stale. + + The magic word "~resyncGoons" calls this function on every goon + in the current zone + """ + self.notify.debug("resyncGoon") + self.sendMovie(GOON_MOVIE_SYNC) + self.updateGrid() + + return + + def sendMovie(self, type, avId=0, pauseTime=0): + if type == GOON_MOVIE_WALK: + # record the local time we started walking + self.pathStartTime = globalClock.getFrameTime() + # and the time elapsed of the walkTrack + if self.parameterized: + self.walkTrackTime = pauseTime % self.totalPathTime + else: + self.walkTrackTime = pauseTime + + self.notify.debug("GOON_MOVIE_WALK doId = %s, pathStartTime = %s, walkTrackTime = %s" % + (self.doId, self.pathStartTime, self.walkTrackTime)) + + if type == GOON_MOVIE_WALK or type == GOON_MOVIE_SYNC: + curT = globalClock.getFrameTime() + elapsedT = curT - self.pathStartTime + + # how far along the track we are in time + pathT = (self.walkTrackTime + elapsedT) + if self.parameterized: + pathT = pathT % self.totalPathTime + + self.sendUpdate("setMovie", [type, avId, pathT, + ClockDelta.globalClockDelta.localToNetworkTime(curT)]) + + # respawn sync task + taskMgr.remove(self.taskName("sync")) + taskMgr.doMethodLater(self.UPDATE_TIMESTAMP_INTERVAL, + self.requestResync, + self.taskName("sync"), + extraArgs=None) + else: + self.sendUpdate("setMovie", [type, avId, pauseTime, + ClockDelta.globalClockDelta.getFrameNetworkTime()]) + + def updateGrid(self): + """ Figure out our position on the grid. """ + if not self.parameterized: + return + + if self.grid and hasattr(self, "entId"): # The hasattr call is for a bug (quick) fix. + # first remove ourselves from the grid so we aren't in there twice + self.grid.removeObject(self.entId) + # now add + if not self.crushed: + # time elapsed since we started walking + curT = globalClock.getFrameTime() + if self.pathStartTime: + elapsedT = curT - self.pathStartTime + else: + elapsedT = 0 + + # how far along the track we are in time + pathT = (self.walkTrackTime + elapsedT) % self.totalPathTime + # the point on the track + pt = self.getPathPoint(pathT) + #self.notify.debug("updateGrid, pt = %s" % pt) + + if not self.grid.addObjectByPos(self.entId, pt): + self.notify.warning("updateGrid: couldn't put goon in grid") + return + + def doCrush(self, crusherId, axis): + self.notify.debug("doCrush %s" % self.doId) + DistributedCrushableEntityAI.DistributedCrushableEntityAI.doCrush(self, crusherId, axis) + # remove ourselves from grid + # SDN: take this out for testing + self.crushed = 1 + self.grid.removeObject(self.entId) + + # delete ourselves, delay some so the client can show it's explosion + taskMgr.doMethodLater(5.0, + self.doDelete, + self.taskName("deleteGoon")) + + def doDelete(self, task): + self.requestDelete() + return Task.done + + def setParameterize(self, x,y,z, pathIndex): + # Client tells AI to parameterize its path, for later + # position calculations on the AI + + #JML- seems dangerous for a client to send this kind of message + #that's why I've added the following safety check. + if (not hasattr(self, "level")) or (not self.level): + return #ignore this call on a deleted Goon + + pathId = GoonPathData.taskZoneId2pathId[self.level.getTaskZoneId()] + pathData = GoonPathData.Paths[pathId] + self.pathOrigin = Vec3(x,y,z) + # make sure pathIndex is valid + if pathIndex > len(pathData): + self.notify.warning("Invalid path index given, using 0") + pathIndex = 0 + + # get path, and add first point to end to complete the loop + pathPts = pathData[pathIndex] + [pathData[pathIndex][0]] + invVel = 1.0/self.velocity + t=0 + self.tSeg = [t] + self.pathSeg = [] + for i in range(len(pathPts)-1): + ptA = pathPts[i] + ptB = pathPts[i+1] + # add in zero-length segments for the goon turning, or pausing to look around + # at each path point + t += T_TURN + self.tSeg.append(t) + self.pathSeg.append([Vec3(0,0,0),0,ptA]) + + # add segment from ptA to ptB + seg = Vec3(ptB-ptA) + segLength = seg.length() + t += invVel * segLength + self.tSeg.append(t) + self.pathSeg.append([seg,segLength,ptA]) + + self.totalPathTime = t + self.pathPts = pathPts + self.parameterized = 1 + + def getPathPoint(self, t): + for i in range(len(self.tSeg)-1): + if (t >= self.tSeg[i] and t < self.tSeg[i+1]): + tSeg = t-self.tSeg[i] + assert self.tSeg[i+1]-self.tSeg[i] > 0 + t = tSeg/(self.tSeg[i+1]-self.tSeg[i]) # get t in range [0,1) + seg = self.pathSeg[i][0] + ptA = self.pathSeg[i][2] + pt = ptA + seg * t + return self.pathOrigin + pt + self.notify.warning("Couldn't find valid path point") + return Vec3(0,0,0) + + # Distributed properties. These messages are not actually defined + # by the toon.dc for the DistributedGoon class itself, but the + # methods are declared up at this level anyway since this is + # closer to where they are used. And maybe one day the + # DistributedGoon will, in fact, define these. (At the moment, + # the DistributedGoon gets all of its properties from the Entity + # system instead, which completely goes around the + # DistributedObject system that these methods tie into.) + + def b_setVelocity(self, velocity): + self.setVelocity(velocity) + self.d_setVelocity(velocity) + + def setVelocity(self, velocity): + self.velocity = velocity + + def d_setVelocity(self, velocity): + self.sendUpdate('setVelocity', [velocity]) + + def getVelocity(self): + return self.velocity + + def b_setHFov(self, hFov): + self.setHFov(hFov) + self.d_setHFov(hFov) + + def setHFov(self, hFov): + self.hFov = hFov + + def d_setHFov(self, hFov): + self.sendUpdate('setHFov', [hFov]) + + def getHFov(self): + return self.hFov + + def b_setAttackRadius(self, attackRadius): + self.setAttackRadius(attackRadius) + self.d_setAttackRadius(attackRadius) + + def setAttackRadius(self, attackRadius): + self.attackRadius = attackRadius + + def d_setAttackRadius(self, attackRadius): + self.sendUpdate('setAttackRadius', [attackRadius]) + + def getAttackRadius(self): + return self.attackRadius + + def b_setStrength(self, strength): + self.setStrength(strength) + self.d_setStrength(strength) + + def setStrength(self, strength): + self.strength = strength + + def d_setStrength(self, strength): + self.sendUpdate('setStrength', [strength]) + + def getStrength(self): + return self.strength + + def b_setGoonScale(self, scale): + self.setGoonScale(scale) + self.d_setGoonScale(scale) + + def setGoonScale(self, scale): + self.scale = scale + + def d_setGoonScale(self, scale): + self.sendUpdate('setGoonScale', [scale]) + + def getGoonScale(self): + return self.scale + + def b_setupGoon(self, velocity, hFov, attackRadius, strength, scale): + self.setupGoon(velocity, hFov, attackRadius, strength, scale) + self.d_setupGoon(velocity, hFov, attackRadius, strength, scale) + + def setupGoon(self, velocity, hFov, attackRadius, strength, scale): + self.setVelocity(velocity) + self.setHFov(hFov) + self.setAttackRadius(attackRadius) + self.setStrength(strength) + self.setGoonScale(scale) + + def d_setupGoon(self, velocity, hFov, attackRadius, strength, scale): + self.sendUpdate('setupGoon', [velocity, hFov, attackRadius, strength, scale]) + + diff --git a/toontown/src/suit/DistributedGridGoon.py b/toontown/src/suit/DistributedGridGoon.py new file mode 100644 index 0000000..6b26383 --- /dev/null +++ b/toontown/src/suit/DistributedGridGoon.py @@ -0,0 +1,70 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from toontown.battle.BattleProps import * + +from direct.directnotify import DirectNotifyGlobal +import DistributedGoon +from toontown.toonbase import ToontownGlobals +from toontown.coghq import MovingPlatform + +class DistributedGridGoon(DistributedGoon.DistributedGoon): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGoon') + + def __init__(self, cr, type='sg'): + try: + self.DistributedGridGoon_initialized + except: + self.DistributedGridGoon_initialized = 1 + DistributedGoon.DistributedGoon.__init__(self, cr, type) + + def generate(self): + DistributedGoon.DistributedGoon.generate(self) + + # turn off wall collisions, and let the AI figure it out + self.ignore(self.uniqueName('wallHit')) + + self.mazeWalkTrack = None + + def delete(self): + if self.mazeWalkTrack: + self.mazeWalkTrack.pause() + del self.mazeWalkTrack + + DistributedGoon.DistributedGoon.delete(self) + + def setH(self, h): + self.h = h + + def setPathPts(self, xi, yi, zi, xf, yf, zf): + self.notify.debug('setPathPts') + + if self.mazeWalkTrack: + self.mazeWalkTrack.pause() + del self.mazeWalkTrack + self.mazeWalkTrack = None + + curPos = Point3(xi,yi,zi) + nextPos = Point3(xf,yf,zf) + + # Calculate the amount of time we should spend walking + distance = Vec3(curPos - nextPos).length() + duration = distance / self.velocity + + # Naming this reference 'self.walkTrack' was causing the resume() + # call in DistributedGoon.enterWalk to blow up down in the + # IntervalManager. + self.mazeWalkTrack = Sequence(Func(self.headsUp, + nextPos[0],nextPos[1],nextPos[2]), # face next point + LerpPosInterval(self, + duration=duration, + pos=nextPos, + startPos=curPos), # go to next point + name = self.uniqueName("mazeWalkTrack")) + self.mazeWalkTrack.start() + + def enterWalk(self, avId=None, ts=0): + pass + + def exitWalk(self): + pass diff --git a/toontown/src/suit/DistributedGridGoonAI.py b/toontown/src/suit/DistributedGridGoonAI.py new file mode 100644 index 0000000..3de1f7a --- /dev/null +++ b/toontown/src/suit/DistributedGridGoonAI.py @@ -0,0 +1,77 @@ +from otp.ai.AIBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import DistributedGoonAI +from direct.task.Task import Task +from toontown.coghq import DistributedCrushableEntityAI +import random + +class DistributedGridGoonAI(DistributedGoonAI.DistributedGoonAI): + """ + A simple, dumb robot. + The robot should be flexible and reusable, for uses in CogHQ basements + and factories, and perhaps other parts of the game. Let the goon's + movement, discovery, and attack methods be modular, so different behavior + types can be easily plugged in. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGridGoonAI') + + def __init__(self, level, entId): + self.grid = None + self.h = 0 + DistributedGoonAI.DistributedGoonAI.__init__(self, level, entId) + + def generate(self): + self.notify.debug('generate') + # don't call GoonAI's generate, since it sends a walk + # movie to the client + DistributedCrushableEntityAI.DistributedCrushableEntityAI.generate(self) + + def initGridDependents(self): + # anything that needs the grid to be set up before running should + # be initialized/run here + # star a timer to get the goon walking + taskMgr.doMethodLater(2, + self.goToNextPoint, + self.taskName("walkTask")) + + def getPosition(self): + if self.grid: + return self.grid.getObjPos(self.entId) + + def getH(self): + return self.h + + def goToNextPoint(self, task): + if not self.grid: + self.notify.warning("couldn't find grid, not starting") + return + # check the point in front of us + if (self.grid.checkMoveDir(self.entId, self.h)): + # If it is clear, move forward + # - get old pos + ptA = Point3(*self.getPosition()) + # - make the move + self.grid.doMoveDir(self.entId, self.h) + # - get new position + ptB = Point3(*self.getPosition()) + # - tell the client to move the goon + self.sendUpdate("setPathPts", [ptA[0],ptA[1],ptA[2], + ptB[0],ptB[1],ptB[2]]) + # calculate the time in takes to walk to the next cell + tPathSegment = Vec3(ptA - ptB).length() / self.velocity + + else: + # if it isn't clear, turn some amount and try again + turn = int(random.randrange(1,4) * 90) + self.h = (self.h + turn) % 360 + tPathSegment = .1 + + # set a timer for the next segment + taskMgr.doMethodLater(tPathSegment, + self.goToNextPoint, + self.taskName("walkTask")) + + return Task.done + diff --git a/toontown/src/suit/DistributedLawbotBoss.py b/toontown/src/suit/DistributedLawbotBoss.py new file mode 100644 index 0000000..931fd4c --- /dev/null +++ b/toontown/src/suit/DistributedLawbotBoss.py @@ -0,0 +1,3395 @@ +from direct.showbase.ShowBase import * +from direct.interval.IntervalGlobal import * +from toontown.battle.BattleProps import * +from direct.distributed.ClockDelta import * +from direct.showbase.PythonUtil import Functor +from direct.showbase.PythonUtil import StackTrace +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.fsm import FSM +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +import DistributedBossCog +from toontown.toonbase import TTLocalizer +import SuitDNA +from toontown.toon import Toon +from toontown.battle import BattleBase +from direct.directutil import Mopath +from direct.showutil import Rope +from toontown.distributed import DelayDelete +from toontown.battle import MovieToonVictory +from toontown.building import ElevatorUtils +from toontown.battle import RewardPanel +from toontown.toon import NPCToons +from direct.task import Task +import random +import math +from toontown.coghq import CogDisguiseGlobals +from toontown.building import ElevatorConstants +from toontown.toonbase import ToontownTimer + +# This pointer keeps track of the one DistributedSellbotBoss that +# should appear within the avatar's current visibility zones. If +# there is more than one DistributedSellbotBoss visible to a client at +# any given time, something is wrong. +OneBossCog = None + +class DistributedLawbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM): + """ + Adapted from DistributedSellbotBoss. The pie became evide, the cage became the witness stand + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLawbotBoss') + #notify.setDebug(1) + + debugPositions = False + + def __init__(self, cr): + """ + Constructor, initialize most fields to zero or None + """ + self.notify.debug("----- __init___") + DistributedBossCog.DistributedBossCog.__init__(self, cr) + + FSM.FSM.__init__(self, 'DistributedLawbotBoss') + + self.lawyers = [] + self.lawyerRequest = None + + self.bossDamage = 0 + self.attackCode = None + self.attackAvId = 0 + self.recoverRate = 0 + self.recoverStartTime = 0 + self.bossDamageMovie = None + + self.everThrownPie = 0 + self.battleThreeMusicTime = 0 + self.insidesANodePath = None + self.insidesBNodePath = None + + + self.strafeInterval = None + self.onscreenMessage = None + + self.bossMaxDamage = ToontownGlobals.LawbotBossMaxDamage + + self.elevatorType = ElevatorConstants.ELEVATOR_CJ + + self.gavels = {} + self.chairs = {} + self.cannons = {} + + self.useCannons = 1 + + self.juryBoxIval = None + self.juryTimer = None + + self.witnessToon = None + self.witnessToonOnstage = False + + self.numToonJurorsSeated = 0 + + self.mainDoor = None + self.reflectedMainDoor = None + + self.panFlashInterval = None + + self.panDamage = ToontownGlobals.LawbotBossDefensePanDamage + if base.config.GetBool('lawbot-boss-cheat',0): + self.panDamage = 25 + + self.evidenceHitSfx = None + self.toonUpSfx = None + + self.bonusTimer = None + + self.warningSfx = None + self.juryMovesSfx = None + + self.baseColStashed =False #are the collisions for the scale base stashed + self.battleDifficulty = 0 #how hard is battle three + self.bonusWeight = 0 #bonus weight to our evidence due to seating toons + self.numJurorsLocalToonSeated = 0 #how many jurors did local player seat + + self.cannonIndex = -1 #which cannon the local player used + + def announceGenerate(self): + """ + At this point all required fields have been filled in + """ + self.notify.debug("----- announceGenerate") + DistributedBossCog.DistributedBossCog.announceGenerate(self) + # at this point all our attribs have been filled in. + self.setName(TTLocalizer.LawbotBossName) + nameInfo = TTLocalizer.BossCogNameWithDept % { + "name": self.name, + "dept": (SuitDNA.getDeptFullname(self.style.dept)), + } + self.setDisplayName(nameInfo) + + self.piesRestockSfx = loader.loadSfx('phase_5/audio/sfx/LB_receive_evidence.mp3') + self.rampSlideSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_ramp_slide.mp3') + + self.evidenceHitSfx = loader.loadSfx('phase_11/audio/sfx/LB_evidence_hit.mp3') + self.warningSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_tractor_beam_alarmed.mp3') + + self.juryMovesSfx = loader.loadSfx('phase_11/audio/sfx/LB_jury_moves.wav') + self.toonUpSfx = loader.loadSfx('phase_11/audio/sfx/LB_toonup.mp3') + + # We need a different copy of the sfx for each strafe disk. + self.strafeSfx = [] + for i in range(10): + self.strafeSfx.append(loader.loadSfx('phase_3.5/audio/sfx/SA_shred.mp3')) + + # Anything in the world we hit that's *not* the BossCog + # inherits this global pieCode, so the splat will be colored + # gray. + render.setTag('pieCode', str(ToontownGlobals.PieCodeNotBossCog)) + + # Put some polys inside the boss to detect when a pie gets + # inside. That will make the boss dizzy. + + insidesA = CollisionPolygon( + Point3(4.0, -2.0, 5.0), Point3(-4.0, -2.0, 5.0), + Point3(-4.0, -2.0, 0.5), Point3(4.0, -2.0, 0.5)) + insidesANode = CollisionNode('BossZap') + insidesANode.addSolid(insidesA) + insidesANode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask) + self.insidesANodePath = self.axle.attachNewNode(insidesANode) + self.insidesANodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossInsides)) + self.insidesANodePath.stash() + + insidesB = CollisionPolygon( + Point3(-4.0, 2.0, 5.0), Point3(4.0, 2.0, 5.0), + Point3(4.0, 2.0, 0.5), Point3(-4.0, 2.0, 0.5)) + insidesBNode = CollisionNode('BossZap') + insidesBNode.addSolid(insidesB) + insidesBNode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask) + self.insidesBNodePath = self.axle.attachNewNode(insidesBNode) + self.insidesBNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossInsides)) + self.insidesBNodePath.stash() + + # Make another bubble--a tube--to serve as a target in battle + # three. + target = CollisionTube(0, -1, 4, 0, -1, 9, 3.5) + targetNode = CollisionNode('BossZap') + targetNode.addSolid(target) + targetNode.setCollideMask(ToontownGlobals.PieBitmask) + self.targetNodePath = self.pelvis.attachNewNode(targetNode) + self.targetNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossCog)) + + # A similar tube, offset slightly backward, forms a shield + # from behind. We only want to count hits from the front. + shield = CollisionTube(0, 1, 4, 0, 1, 7, 3.5) + shieldNode = CollisionNode('BossZap') + shieldNode.addSolid(shield) + shieldNode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask) + shieldNodePath = self.pelvis.attachNewNode(shieldNode) + + # He also gets a disk-shaped shield around his little cog hula + # hoop. + disk = loader.loadModel('phase_9/models/char/bossCog-gearCollide') + disk.find('**/+CollisionNode').setName('BossZap') + disk.reparentTo(self.pelvis) + disk.setZ(0.8) + + # The BossCog actually owns the environment geometry. This is + # mainly so we can move the ramps in and out under control of + # the FSM here. + self.loadEnvironment() + + #setup the witness toon + self.__makeWitnessToon() + + self.__loadMopaths() + + # Enable the special CJ chat menu. + localAvatar.chatMgr.chatInputSpeedChat.addCJMenu() + + global OneBossCog + if OneBossCog != None: + self.notify.warning("Multiple BossCogs visible.") + OneBossCog = self + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + self.notify.debug("----- disable") + DistributedBossCog.DistributedBossCog.disable(self) + self.request('Off') + self.unloadEnvironment() + self.__cleanupWitnessToon() + + self.__unloadMopaths() + + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + self.__cleanupStrafe() + + self.__cleanupJuryBox() + + + render.clearTag('pieCode') + + self.targetNodePath.detachNode() + + self.cr.relatedObjectMgr.abortRequest(self.lawyerRequest) + self.lawyerRequest = None + + self.betweenBattleMusic.stop() + self.promotionMusic.stop() + self.stingMusic.stop() + self.battleTwoMusic.stop() + self.battleThreeMusic.stop() + self.epilogueMusic.stop() + + if self.juryTimer: + self.juryTimer.destroy() + del self.juryTimer + + if self.bonusTimer: + self.bonusTimer.destroy() + del self.bonusTimer + + localAvatar.chatMgr.chatInputSpeedChat.removeCJMenu() + + global OneBossCog + if OneBossCog == self: + OneBossCog = None + + def delete(self): + """ + to fix a crash bug, just keep track if we ever enter delete, + """ + self.notify.debug('----- delete') + DistributedBossCog.DistributedBossCog.delete(self) + + def d_hitBoss(self, bossDamage): + self.notify.debug("----- d_hitBoss") + self.sendUpdate('hitBoss', [bossDamage]) + + def d_healBoss(self, bossHeal): + self.notify.debug("----- d_bossHeal") + self.sendUpdate('healBoss', [bossHeal]) + + + def d_hitBossInsides(self): + self.notify.debug("----- d_hitBossInsides") + self.sendUpdate('hitBossInsides', []) + + def d_hitDefensePan(self): + self.notify.debug("----- d_hitDefensePan") + self.sendUpdate('hitDefensePan', []) + + def d_hitProsecutionPan(self): + self.notify.debug('----- d_hitProsecutionPan') + self.sendUpdate('hitProsecutionPan', []) + + def d_hitToon(self, toonId): + self.notify.debug("----- d_hitToon") + self.sendUpdate('hitToon', [toonId]) + + def gotToon(self, toon): + """ + # A new Toon has arrived. Put him in the right spot, if we + # know what that is yet. Normally, we will only see this + # message in the WaitForToons state, or in the Off state if they + # came in early (but someone might arrive to the battle very + # late and see everything already advanced to the next state). + """ + #self.notify.debug("----- gotToon") + + stateName = self.state + assert self.notify.debug("gotToon(%s) in state %s" % (toon.doId, stateName)) + + if stateName == "Elevator": + # If the toon arrives late while we're playing the + # elevator movie, try to pop him into place. + + # Actually, this doesn't work, because we haven't yet + # received the "setParent" and "setPos" distributed + # messages, and we're about to. Do something about this + # later. + self.placeToonInElevator(toon) + + def setLawyerIds(self, lawyerIds): + """ + We got the ids for the lawyers, now request the actual lawyer objects. + """ + self.lawyers = [] + self.cr.relatedObjectMgr.abortRequest(self.lawyerRequest) + self.lawyerRequest = self.cr.relatedObjectMgr.requestObjects( + lawyerIds, allCallback = self.__gotLawyers) + + + def __gotLawyers(self, lawyers): + """ + Our request for the actual lawyer objects has been fulfilled + """ + self.lawyerRequest = None + self.lawyers = lawyers + + for i in range(len(self.lawyers)): + suit = self.lawyers[i] + suit.fsm.request('neutral') + suit.loop('neutral') + suit.setBossCogId(self.doId) + + + def setBossDamage(self, bossDamage, recoverRate, timestamp): + """ + AI telling us the new bossDamage, make the scale reflect it + """ + #self.notify.debug("----- setBossDamage %d" % bossDamage) + recoverStartTime = globalClockDelta.networkToLocalTime(timestamp) + self.bossDamage = bossDamage + self.recoverRate = recoverRate + self.recoverStartTime = recoverStartTime + + taskName = "RecoverBossDamage" + taskMgr.remove(taskName) + + if self.bossDamageMovie: + if self.bossDamage >= self.bossMaxDamage: + # We did it! Finish the movie, then transition to + # NearVictory state. + self.notify.debug("finish the movie then transition to NearVictory") + self.bossDamageMovie.resumeUntil(self.bossDamageMovie.getDuration()) + else: + # Push him up to the indicated point and he stops. + self.bossDamageMovie.resumeUntil(self.bossDamage * self.bossDamageToMovie) + + if self.recoverRate: + taskMgr.add(self.__recoverBossDamage, taskName) + + self.makeScaleReflectDamage() + + + + def getBossDamage(self): + """ + Get the boss damage. For CJ, recover rate should always be zero, so it should match AI + """ + self.notify.debug("----- getBossDamage") + now = globalClock.getFrameTime() + elapsed = now - self.recoverStartTime + + # Although the AI side computes and transmits getBossDamage() + # as an integer value, on the client side we return it as a + # floating-point value, so we can get the smooth transition + # effect as the boss slowly starts to roll back up. + return max(self.bossDamage - self.recoverRate * elapsed / 60.0, 0) + + def __recoverBossDamage(self, task): + """ + Unused for CJ. + """ + self.notify.debug("----- __recoverBossDamage") + if self.bossDamageMovie: + self.bossDamageMovie.setT(self.getBossDamage() * self.bossDamageToMovie) + return Task.cont + + def __walkToonToPromotion(self, toonId, delay, mopath, track, delayDeletes): + """ + # Generates an interval to walk the toon along the mopath + # towards its destination (which is the toon's current pos). + """ + self.notify.debug("----- __walkToonToPromotion") + toon = base.cr.doId2do.get(toonId) + if toon: + destPos = toon.getPos() + + # Start the toon off at his position within the elevator. + self.placeToonInElevator(toon) + toon.wrtReparentTo(render) + + # We cleverly combine the MopathInterval with a + # LerpPosInterval so that the toon walks off of his mopath + # to his final destination, in the last few seconds of the + # interval. Note that the LerpPos completely replaces the + # position computed by the Mopath once it kicks in. + + ival = Sequence( + Wait(delay), + Func(toon.suit.setPlayRate, 1, 'walk'), + Func(toon.suit.loop, 'walk'), + toon.posInterval(1, Point3(0, 90, 20)), + ParallelEndTogether(MopathInterval(mopath, toon), + toon.posInterval(2, destPos, + blendType = 'noBlend')), + Func(toon.suit.loop, 'neutral')) + track.append(ival) + delayDeletes.append(DelayDelete.DelayDelete(toon, 'LawbotBoss.__walkToonToPromotion')) + + def __walkSuitToPoint(self, node, fromPos, toPos): + """ + Unused for CJ. + """ + self.notify.debug("----- __walkSuitToPoint") + vector = Vec3(toPos - fromPos) + distance = vector.length() + + # These suits walk a little faster than most. (They're + # still young.) + time = distance / (ToontownGlobals.SuitWalkSpeed * 1.8) + + return Sequence(Func(node.setPos, fromPos), + Func(node.headsUp, toPos), + node.posInterval(time, toPos)) + + + def __makeRollToBattleTwoMovie(self): + """ + # Generate an interval which shows the Boss Cog rolling to the + # battle 2 position. + """ + assert(self.notify.debug("----- __makeRollToBattleTwoMovie")) + + startPos = Point3(ToontownGlobals.LawbotBossBattleOnePosHpr[0], + ToontownGlobals.LawbotBossBattleOnePosHpr[1], + ToontownGlobals.LawbotBossBattleOnePosHpr[2]) + if self.arenaSide: + topRampPos = Point3(*ToontownGlobals.LawbotBossTopRampPosB) + topRampTurnPos = Point3(*ToontownGlobals.LawbotBossTopRampTurnPosB) + p3Pos = Point3(*ToontownGlobals.LawbotBossP3PosB) + else: + topRampPos = Point3(*ToontownGlobals.LawbotBossTopRampPosA) + topRampTurnPos = Point3(*ToontownGlobals.LawbotBossTopRampTurnPosA) + p3Pos = Point3(*ToontownGlobals.LawbotBossP3PosA) + + battlePos = Point3(ToontownGlobals.LawbotBossBattleTwoPosHpr[0], + ToontownGlobals.LawbotBossBattleTwoPosHpr[1], + ToontownGlobals.LawbotBossBattleTwoPosHpr[2]) + battleHpr = VBase3(ToontownGlobals.LawbotBossBattleTwoPosHpr[3], + ToontownGlobals.LawbotBossBattleTwoPosHpr[4], + ToontownGlobals.LawbotBossBattleTwoPosHpr[5]) + bossTrack = Sequence() + + self.notify.debug("calling setPosHpr") # -23.4, -145.6, 44.0, -10.0, -12.5, 0)") + + #cut to wide shot and boss saying placeholder text + myInterval = camera.posHprInterval(8, Point3(-22, -100, 35), + Point3(-10, -13, 0), + startPos = Point3(-22, -90, 35), + startHpr = Point3(-10,-13,0), + blendType = 'easeInOut') + chatTrack = Sequence( + Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempJury1, CFSpeech), + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, localAvatar.cameraPositions[0][0]), + Func(camera.setHpr, 0, 0, 0), + Func(self.releaseToons,1), + ) + + # Turn the boss model around so he rolls forward. + bossTrack.append(Func(self.getGeomNode().setH, 180)) + + track, hpr = self.rollBossToPoint(startPos, None, battlePos, None, 0) + bossTrack.append(track) + + track, hpr = self.rollBossToPoint(battlePos, hpr, battlePos, battleHpr, 0) + + self.makeToonsWait() + + finalPodiumPos = Point3( self.podium.getX(), self.podium.getY(), + self.podium.getZ() + ToontownGlobals.LawbotBossBattleTwoPosHpr[2]) + + finalReflectedPodiumPos = Point3( + self.reflectedPodium.getX(), + self.reflectedPodium.getY(), + self.reflectedPodium.getZ() + ToontownGlobals.LawbotBossBattleTwoPosHpr[2]) + + # Needed to add another stash/unstash boss command because turning the boss cog around causes + # the player to get stuck in the collision of the boss for some reason. + return Sequence( + #Func(self.stickToonsToFloor), + chatTrack, + #Func(self.unstickToons), + bossTrack, + Func(self.getGeomNode().setH, 0), + Parallel( + self.podium.posInterval(5.0, finalPodiumPos), + self.reflectedPodium.posInterval(5.0, finalReflectedPodiumPos), + Func(self.stashBoss), + self.posInterval(5.0, battlePos), + Func(taskMgr.doMethodLater,.01, self.unstashBoss, 'unstashBoss'), + ), + name = self.uniqueName('BattleTwoMovie')) + + + def __makeRollToBattleThreeMovie(self): + """ + No longer used by CJ. + # Generate an interval which shows the Boss Cog rolling to the + # battle 3 position. + """ + assert(self.notify.debug("----- __makeRollToBalleThreeMovie")) + + startPos = Point3(ToontownGlobals.LawbotBossBattleTwoPosHpr[0], + ToontownGlobals.LawbotBossBattleTwoPosHpr[1], + ToontownGlobals.LawbotBossBattleTwoPosHpr[2]) + + + battlePos = Point3(ToontownGlobals.LawbotBossBattleThreePosHpr[0], + ToontownGlobals.LawbotBossBattleThreePosHpr[1], + ToontownGlobals.LawbotBossBattleThreePosHpr[2]) + battleHpr = VBase3(ToontownGlobals.LawbotBossBattleThreePosHpr[3], + ToontownGlobals.LawbotBossBattleThreePosHpr[4], + ToontownGlobals.LawbotBossBattleThreePosHpr[5]) + bossTrack = Sequence() + + + myInterval = camera.posHprInterval(8, Point3(-22, -100, 35), + Point3(-10, -13, 0), + startPos = Point3(-22, -90, 35), + startHpr = Point3(-10,-13,0), + blendType = 'easeInOut') + chatTrack = Sequence( + + Func(self.setChatAbsolute, TTLocalizer.LawbotBossTrialChat1, CFSpeech), + #Func(camera.reparentTo, render), + #myInterval, + #Func(camera.setPosHpr, -23.4, -145.6, 44.0, -10.0, -12.5, 0), + + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, localAvatar.cameraPositions[0][0]), + Func(camera.setHpr, 0, 0, 0), + Func(self.releaseToons, 1), + ) + + # Turn the boss model around so he rolls forward. + bossTrack.append(Func(self.getGeomNode().setH, 180)) + bossTrack.append(Func(self.loop, 'Ff_neutral')) + + track, hpr = self.rollBossToPoint(startPos, None, battlePos, None, 0) + bossTrack.append(track) + + track, hpr = self.rollBossToPoint(battlePos, hpr, battlePos, battleHpr, 0) + self.makeToonsWait() + + return Sequence( + #Func(self.stickToonsToFloor), + chatTrack, + #Func(self.unstickToons), + bossTrack, + Func(self.getGeomNode().setH, 0), + name = self.uniqueName('BattleTwoMovie')) + + + + + def toNeutralMode(self): + """ + # Move the localToon to 'walk' mode. + """ + if self.cr: + place = self.cr.playGame.getPlace() + if place and hasattr(place, 'fsm'): + place.setState('waitForBattle') + + + def makeToonsWait(self): + """ + # Turn off remote control of the toons. We'll move them + # around locally for now. + """ + self.notify.debug("makeToonsWait") + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.stopLookAround() + toon.stopSmooth() + + # Also put the local toon in movie mode so he won't try to + # walk around on his own. + if self.hasLocalToon(): + self.toMovieMode() + + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.loop('neutral') + + + + def makeEndOfBattleMovie(self, hasLocalToon): + """ + # Generate an interval + # This one is called from DistributedBattleFinal. + """ + assert self.notify.debug("makeEndOfBattleMovie(%s)" % (hasLocalToon)) + name = self.uniqueName('Drop') + seq = Sequence(name = name) + + seq += [Wait(0.0), + ] + if hasLocalToon: + seq += [Func(self.show), + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, localAvatar.cameraPositions[0][0]), + Func(camera.setHpr, 0, 0, 0)] + seq.append(Func(self.setChatAbsolute, TTLocalizer.LawbotBossPassExam, CFSpeech)) + seq.append( Wait(5.0) ) + + seq.append(Func(self.clearChat)) + + + return seq + + def __makeBossDamageMovie(self): + """ + Unused in CJ + # Generate an interval which shows the Boss Cog rolling down + # in retreat as the Toons attack. + """ + self.notify.debug("---- __makeBossDamageMovie") + + startPos = Point3(ToontownGlobals.LawbotBossBattleThreePosHpr[0], + ToontownGlobals.LawbotBossBattleThreePosHpr[1], + ToontownGlobals.LawbotBossBattleThreePosHpr[2]) + startHpr = Point3(*ToontownGlobals.LawbotBossBattleThreeHpr) + bottomPos = Point3(*ToontownGlobals.LawbotBossBottomPos) + deathPos = Point3(*ToontownGlobals.LawbotBossDeathPos) + + self.setPosHpr(startPos, startHpr) + + bossTrack = Sequence() + bossTrack.append(Func(self.loop, 'Ff_neutral')) + track, hpr = self.rollBossToPoint(startPos, startHpr, bottomPos, None, 1) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(bottomPos, startHpr, deathPos, None, 1) + bossTrack.append(track) + + duration = bossTrack.getDuration() + return bossTrack + + + def __showOnscreenMessage(self, text): + """ + Shows a screen message. Stays up forever unless __clearOnscreenMessage is called + """ + self.notify.debug("----- __showOnscreenmessage") + if self.onscreenMessage: + self.onscreenMessage.destroy() + self.onscreenMessage = None + + self.onscreenMessage = DirectLabel( + text = text, + text_fg = VBase4(1,1,1,1), + text_align = TextNode.ACenter, + relief = None, + pos = (0, 0, 0.35), + scale = 0.1) + + def __clearOnscreenMessage(self): + """ + Clears the screen message, if any + """ + #self.notify.debug("----- __clearOnscreenMessage") + if self.onscreenMessage: + self.onscreenMessage.destroy() + self.onscreenMessage = None + + def __showWaitingMessage(self, task): + """ + Show a waiting for other players message + """ + self.notify.debug("----- __showWaitingMessage") + self.__showOnscreenMessage(TTLocalizer.BuildingWaitingForVictors) + + + ##### Environment ##### + + def loadEnvironment(self): + """ + Load most of the assets used in the battle + """ + self.notify.debug("----- loadEnvironment") + DistributedBossCog.DistributedBossCog.loadEnvironment(self) + + self.geom = loader.loadModel('phase_11/models/lawbotHQ/LawbotCourtroom3') + #set the floor at z=0 + self.geom.setPos(0, 0, -71.601) + self.geom.setScale(1) + + self.elevatorEntrance = self.geom.find('**/elevator_origin') + + # The elevatorEntrance has some geometry that it shouldn't. + self.elevatorEntrance.getChildren().detach() + self.elevatorEntrance.setScale(1) + + elevatorModel = loader.loadModel("phase_11/models/lawbotHQ/LB_Elevator") + elevatorModel.reparentTo(self.elevatorEntrance) + + + self.setupElevator(elevatorModel) + + # before battles: play the boss theme music + self.promotionMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + # 'phase_9/audio/bgm/encntr_head_suit_theme.mid') + # Between battle one and two: play the upbeat street battle music + self.betweenBattleMusic = base.loadMusic( + 'phase_9/audio/bgm/encntr_toon_winning.mid') + # Battle two: play new jury music + self.battleTwoMusic = base.loadMusic( + 'phase_11/audio/bgm/LB_juryBG.mid') + + # Also replace the floor polygon with a plane, and rename it + # so we can detect a collision with it. + floor = self.geom.find('**/MidVaultFloor1') + if floor.isEmpty(): + floor = self.geom.find('**/CR3_Floor') + #floor.detachNode() + self.evFloor = self.replaceCollisionPolysWithPlanes(floor) + self.evFloor.reparentTo(self.geom) + self.evFloor.setName('floor') + + # Also, put a big plane across the universe a few feet below + # the floor, to catch things that fall out of the world. + plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -50))) + planeNode = CollisionNode('dropPlane') + planeNode.addSolid(plane) + planeNode.setCollideMask(ToontownGlobals.PieBitmask) + self.geom.attachNewNode(planeNode) + + # The boss makes his "escape" through this door. + self.door3 = self.geom.find('**/SlidingDoor1/') + if self.door3.isEmpty(): + self.door3 = self.geom.find('**/interior/CR3_Door') + + #get the main door, so we can hide it in the intro movie + self.mainDoor = self.geom.find('**/Door_1') + if not self.mainDoor.isEmpty(): + #itemsToHide = ['LawbotCourtroom3_collision_Door_1','CR3_LobbyFrontWall3','wall_4'] + itemsToHide = ['interior/Door_1'] + for str in itemsToHide: + stuffToHide = self.geom.find('**/%s' % str) + if not stuffToHide.isEmpty(): + self.notify.debug('found %s' % stuffToHide) + stuffToHide.wrtReparentTo(self.mainDoor) + else: + self.notify.debug('not found %s' % stuffToHide) + + self.reflectedMainDoor = self.geom.find('**/interiorrefl/CR3_Door') + if not self.reflectedMainDoor.isEmpty(): + itemsToHide = ['Reflections/Door_1'] + for str in itemsToHide: + stuffToHide = self.geom.find('**/%s' % str) + if not stuffToHide.isEmpty(): + self.notify.debug('found %s' % stuffToHide) + stuffToHide.wrtReparentTo(self.reflectedMainDoor) + else: + self.notify.debug('not found %s' % stuffToHide) + + self.geom.reparentTo(render) + self.loadWitnessStand() + self.loadScale() + + self.scaleNodePath.stash() + self.loadJuryBox() + + + self.loadPodium() + + #make the floor reflective + ug = self.geom.find('**/Reflections') + ug.setBin('ground',-10) + + def loadJuryBox(self): + """ + Load the jury box, and put it in the initial position. + """ + self.juryBox = self.geom.find('**/JuryBox') + juryBoxPos = self.juryBox.getPos() + newPos = juryBoxPos - Point3(*ToontownGlobals.LawbotBossJuryBoxRelativeEndPos) + + if not self.debugPositions: + self.juryBox.setPos( newPos) + + #move / hide the reflected JuryBox + self.reflectedJuryBox = self.geom.find('**/JuryBox_Geo_Reflect') + + reflectedJuryBoxPos = self.reflectedJuryBox.getPos() + newReflectedPos = reflectedJuryBoxPos - Point3(*ToontownGlobals.LawbotBossJuryBoxRelativeEndPos) + if not self.debugPositions: + self.reflectedJuryBox.setPos(newReflectedPos) + + if not self.reflectedJuryBox.isEmpty(): + if self.debugPositions: + self.reflectedJuryBox.show() + else: + #self.reflectedJuryBox.hide() + pass + + self.reflectedJuryBox.setZ( self.reflectedJuryBox.getZ() + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[2]) + + + def loadPodium(self): + """ + Load the podim and put it in the initial position + """ + self.podium = self.geom.find('**/Podium') + newZ = self.podium.getZ() - ToontownGlobals.LawbotBossBattleTwoPosHpr[2] + if not self.debugPositions: + self.podium.setZ(newZ) + + #move the reflected podium + self.reflectedPodium = self.geom.find('**/Podium_Geo1_Refl') + + #reflectedZ = self.reflectedPodium.getZ() - ToontownGlobals.LawbotBossBattleTwoPosHpr[2] + reflectedZ = self.reflectedPodium.getZ() #+ ToontownGlobals.LawbotBossBattleTwoPosHpr[2] + if not self.debugPositions: + self.reflectedPodium.setZ(reflectedZ) + if not self.reflectedPodium.isEmpty(): + if self.debugPositions: + self.reflectedPodium.show() + else: + #self.reflectedPodium.hide() + pass + + + + def loadCannons(self): + """ + Cannons now done in DistributedLawbotCannon.py + """ + return + + def loadWitnessStand(self): + """ + Loads the witness stand. + """ + #temp hide it, set the mailbox to it's position + self.realWitnessStand = self.geom.find('**/WitnessStand') + if not self.realWitnessStand.isEmpty(): + if 0: #not self.debugPositions: + self.realWitnessStand.stash() + pass + #also hide the reflected witness stand + self.reflectedWitnessStand = self.geom.find('**/Witnessstand_Geo_Reflect') + if not self.reflectedWitnessStand.isEmpty(): + if 0: #not self.debugPositions: + self.reflectedWitnessStand.stash() + pass + + colNode = self.realWitnessStand.find('**/witnessStandCollisions/Witnessstand_Collision') + colNode.setName('WitnessStand') + + + def loadScale(self): + """ + Load the scales of injustice. + """ + self.useProgrammerScale = base.config.GetBool('want-injustice-scale-debug',0) + if self.useProgrammerScale: + self.loadScaleOld() + else: + self.loadScaleNew() + + + + + def __debugScale(self): + """ + Print out info about the scale. + """ + prosecutionPanPos = self.prosecutionPanNodePath.getPos() + origin = Point3(0,0,0) + prosecutionPanRelPos = self.scaleNodePath.getRelativePoint(self.prosecutionPanNodePath, origin) + panRenderPos = render.getRelativePoint(self.prosecutionPanNodePath, origin) + + self.notify.debug('prosecutionPanPos = %s' % prosecutionPanPos) + self.notify.debug('prosecutionPanRelPos = %s' % prosecutionPanRelPos) + self.notify.debug('panRenderPos = %s' % panRenderPos) + + + prosecutionLocatorPos = self.prosecutionLocator.getPos() + prosecutionLocatorRelPos = self.scaleNodePath.getRelativePoint(self.prosecutionLocator, origin) + locatorRenderPos = render.getRelativePoint(self.prosecutionLocator, origin) + self.notify.debug('prosecutionLocatorPos = %s ' % prosecutionLocatorPos) + self.notify.debug('prosecutionLocatorRelPos = %s ' % prosecutionLocatorRelPos) + self.notify.debug('locatorRenderPos = %s' % locatorRenderPos) + + + beamPos = self.beamNodePath.getPos() + beamRelPos = self.scaleNodePath.getRelativePoint(self.beamNodePath, origin) + beamRenderPos = render.getRelativePoint(self.beamNodePath, origin) + self.notify.debug("beamPos = %s" % beamPos) + self.notify.debug("beamRelPos = %s" % beamRelPos) + self.notify.debug('beamRenderPos = %s' % beamRenderPos) + + beamBoundsCenter = self.beamNodePath.getBounds().getCenter() + self.notify.debug('beamBoundsCenter = %s' % beamBoundsCenter) + + + beamLocatorBounds = self.beamLocator.getBounds() + beamLocatorPos = beamLocatorBounds.getCenter() + self.notify.debug('beamLocatorPos = %s' % beamLocatorPos) + + #self.beamNodePath.wrtReparentTo(render) + #self.defensePanNodePath.wrtReparentTo(render) + #self.scaleNodePath.hide() + + def loadScaleNew(self): + """ + Loads the Scales of Injustice. FOr now uses geometry created by the artist + """ + self.scaleNodePath = loader.loadModel('phase_11/models/lawbotHQ/scale') + self.beamNodePath = self.scaleNodePath.find('**/scaleBeam') + self.defensePanNodePath = self.scaleNodePath.find('**/defensePan') + self.prosecutionPanNodePath = self.scaleNodePath.find('**/prosecutionPan') + self.defenseColNodePath = self.scaleNodePath.find('**/DefenseCol') + self.defenseColNodePath.setTag('pieCode',str(ToontownGlobals.PieCodeDefensePan)) + self.prosecutionColNodePath = self.scaleNodePath.find('**/ProsecutionCol') + self.prosecutionColNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeProsecutionPan)) + self.standNodePath = self.scaleNodePath.find('**/scaleStand') + + self.scaleNodePath.setPosHpr(*ToontownGlobals.LawbotBossInjusticePosHpr) + + self.defenseLocator = self.scaleNodePath.find('**/DefenseLocator') + defenseLocBounds = self.defenseLocator.getBounds() + defenseLocPos = defenseLocBounds.getCenter() + self.notify.debug("defenseLocatorPos = %s" % defenseLocPos) + + + self.defensePanNodePath.setPos(defenseLocPos) + self.defensePanNodePath.reparentTo(self.beamNodePath) + + + self.notify.debug("defensePanNodePath.getPos()=%s" % self.defensePanNodePath.getPos()) + + self.prosecutionLocator = self.scaleNodePath.find('**/ProsecutionLocator') + + prosecutionLocBounds = self.prosecutionLocator.getBounds() + prosecutionLocPos = prosecutionLocBounds.getCenter() + self.notify.debug("prosecutionLocatorPos = %s" % prosecutionLocPos) + self.prosecutionPanNodePath.setPos(prosecutionLocPos) + self.prosecutionPanNodePath.reparentTo(self.beamNodePath) + + + self.beamLocator = self.scaleNodePath.find('**/StandLocator1') + + beamLocatorBounds = self.beamLocator.getBounds() + beamLocatorPos = beamLocatorBounds.getCenter() + negBeamLocatorPos = -beamLocatorPos + self.notify.debug('beamLocatorPos = %s' % beamLocatorPos) + self.notify.debug('negBeamLocatorPos = %s' % negBeamLocatorPos) + + self.beamNodePath.setPos(beamLocatorPos) + + #self.__debugScale() + self.scaleNodePath.setScale(*ToontownGlobals.LawbotBossInjusticeScale) + + self.scaleNodePath.wrtReparentTo(self.geom) + + #this is the high collision poly to stop toons standing on the base, but lets pies through + self.baseHighCol = self.scaleNodePath.find('**/BaseHighCol') + oldBitMask = self.baseHighCol.getCollideMask() + newBitMask = oldBitMask & ~ToontownGlobals.PieBitmask + newBitMask = newBitMask & ~ToontownGlobals.CameraBitmask + self.baseHighCol.setCollideMask(newBitMask) + + #this is the high collision poly surrounding the defense pan + self.defenseHighCol = self.scaleNodePath.find('**/DefenseHighCol') + self.defenseHighCol.stash() + self.defenseHighCol.setCollideMask(newBitMask) + + #these are the base collissioins + self.baseTopCol = self.scaleNodePath.find('**/Scale_base_top_collision') + self.baseSideCol = self.scaleNodePath.find('**/Scale_base_side_col') + + #hide the grey locators + self.defenseLocator.hide() + self.prosecutionLocator.hide() + self.beamLocator.hide() + + + + def loadScaleOld(self): + """ + Loads the Scales of Injustice. For now uses geometry created programtically. + """ + startingTilt = 0; #higher number is good for the players + + #This will be the parent of everything + self.scaleNodePath = NodePath("injusticeScale") + + #Create the beam geometry + beamGeom = self.createBlock(0.25,2,0.125, -0.25,-2,-0.125, 0,1.0,0,1.0) + self.beamNodePath = NodePath("scaleBeam") + self.beamNodePath.attachNewNode(beamGeom) + self.beamNodePath.setPos(0,0,3) + self.beamNodePath.reparentTo (self.scaleNodePath) + + + #Create the defense pan geometry + defensePanGeom = self.createBlock(0.5,0.5,0, -0.5, -0.5, -2, 0, 0, 1.0, 0.25) + self.defensePanNodePath = NodePath("defensePan") + self.defensePanNodePath.attachNewNode(defensePanGeom) + self.defensePanNodePath.setPos(0,-2,0) + self.defensePanNodePath.reparentTo (self.beamNodePath) + + + #Create the collision node for the defense pan + defenseTube = CollisionTube(0,0,-0.5, + 0,0,-1.5, + 0.6) + defenseTube.setTangible(1) + defenseCollNode = CollisionNode("DefenseCol") + defenseCollNode.addSolid(defenseTube) + self.defenseColNodePath = self.defensePanNodePath.attachNewNode(defenseCollNode) + self.defenseColNodePath.setTag('pieCode',str(ToontownGlobals.PieCodeDefensePan)) + + #Create the prosecution pan geometry + prosecutionPanGeom = self.createBlock(0.5,0.5,0, -0.5, -0.5, -2, 1.0, 0, 0,1.0) + self.prosecutionPanNodePath = NodePath("prosecutionPan") + self.prosecutionPanNodePath.attachNewNode(prosecutionPanGeom) + self.prosecutionPanNodePath.setPos(0,2,0) + self.prosecutionPanNodePath.reparentTo (self.beamNodePath) + + + #Create the collision node for the prosecution pan + prosecutionTube = CollisionTube(0,0,-0.5, + 0,0,-1.5, + 0.6) + prosecutionTube.setTangible(1) + prosecutionCollNode = CollisionNode(self.uniqueName("ProsecutionCol")) + prosecutionCollNode.addSolid(prosecutionTube) + self.prosecutionColNodePath = self.prosecutionPanNodePath.attachNewNode(prosecutionCollNode) + self.prosecutionColNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeProsecutionPan)) + + + #Create the stand geometry + standGeom = self.createBlock(0.25,0.25,0, -0.25, -0.25, 3) + self.standNodePath = NodePath("scaleStand") + self.standNodePath.attachNewNode(standGeom) + self.standNodePath.reparentTo(self.scaleNodePath) + + + self.scaleNodePath.setPosHpr(*ToontownGlobals.LawbotBossInjusticePosHpr) + self.scaleNodePath.setScale(5.0) + self.scaleNodePath.wrtReparentTo(self.geom) + + self.setScaleTilt(startingTilt) + + + def setScaleTilt(self, tilt): + """ + Tilt the scales of injustice. Tilt should be in degrees. A positive number is + good for the players. + """ + self.beamNodePath.setP(tilt) + + if self.useProgrammerScale: + self.defensePanNodePath.setP(-tilt) + self.prosecutionPanNodePath.setP(-tilt) + else: + self.defensePanNodePath.setP(-tilt) + self.prosecutionPanNodePath.setP(-tilt) + + def stashBaseCol(self): + """ + Stash the base collision. + """ + if not self.baseColStashed: + self.notify.debug('stashBaseCol') + self.baseTopCol.stash() + self.baseSideCol.stash() + self.baseColStashed = True + + def unstashBaseCol(self): + """ + Unstash the base collision. + """ + if self.baseColStashed: + self.notify.debug('unstashBaseCol') + self.baseTopCol.unstash() + self.baseSideCol.unstash() + self.baseColStashed = False + + def makeScaleReflectDamage(self): + """ + Set the scale tilt based on the damage. + Also hide the base collision if the defense pan is low enough. + """ + + diffDamage = self.bossDamage - ToontownGlobals.LawbotBossInitialDamage + diffDamage *= 1.0 + + #potentially initial damage is skewed in favor of the proescution or the defense as we do balancing + if (diffDamage >= 0): + percentDamaged = diffDamage / ( ToontownGlobals.LawbotBossMaxDamage - ToontownGlobals.LawbotBossInitialDamage) + tilt = percentDamaged * ToontownGlobals.LawbotBossWinningTilt + else: + percentDamaged = diffDamage / ( ToontownGlobals.LawbotBossInitialDamage - 0) + tilt = percentDamaged * ToontownGlobals.LawbotBossWinningTilt + + self.setScaleTilt(tilt) + + if self.bossDamage < ToontownGlobals.LawbotBossMaxDamage * 0.85: + self.unstashBaseCol() + else: + self.stashBaseCol() + + + def unloadEnvironment(self): + """ + Unloads the environment, also calls base class unload. + """ + self.notify.debug("----- unloadEnvironment") + DistributedBossCog.DistributedBossCog.unloadEnvironment(self) + + self.geom.removeNode() + del self.geom + + + + def __loadMopaths(self): + """ + Unused in CJ. + """ + self.notify.debug("----- __loadMopaths") + self.toonsEnterA = Mopath.Mopath() + self.toonsEnterA.loadFile('phase_9/paths/bossBattle-toonsEnterA') + self.toonsEnterA.fFaceForward = 1 + self.toonsEnterA.timeScale = 35 + self.toonsEnterB = Mopath.Mopath() + self.toonsEnterB.loadFile('phase_9/paths/bossBattle-toonsEnterB') + self.toonsEnterB.fFaceForward = 1 + self.toonsEnterB.timeScale = 35 + + def __unloadMopaths(self): + """ + Unused in CJ + """ + self.notify.debug("----- __unloadMopaths") + self.toonsEnterA.reset() + self.toonsEnterB.reset() + + + + + ##### Off state ##### + + def enterOff(self): + """ + Handle Off state. We can get to this state if unexpectly losing connection to server + """ + self.notify.debug("----- enterOff") + DistributedBossCog.DistributedBossCog.enterOff(self) + + if self.witnessToon: + self.witnessToon.clearChat() + #temp debug only + #self.__showWitnessToon() + + + + ##### WaitForToons state ##### + + def enterWaitForToons(self): + """ + We're about to do battle two or three, wait for the other plaers + """ + self.notify.debug("----- enterWaitForToons") + DistributedBossCog.DistributedBossCog.enterWaitForToons(self) + + self.geom.hide() + + # Disable the witness toon's nametag while we're here in space + # waiting. + self.witnessToon.removeActive() + + + def exitWaitForToons(self): + """ + Other players done or disconnected, move on + """ + self.notify.debug("----- exitWaitForToons") + DistributedBossCog.DistributedBossCog.exitWaitForToons(self) + + self.geom.show() + + self.witnessToon.addActive() + + + ##### Elevator state ##### + + def enterElevator(self): + """ + The first state, toons are riding the elevator + """ + self.notify.debug("----- enterElevator") + DistributedBossCog.DistributedBossCog.enterElevator(self) + + + # Disable the witness toon's nametag while we're in the + # elevator. + self.witnessToon.removeActive() + + + # Set the boss up in the middle of the floor, so we can see + # him when the doors open. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.LawbotBossBattleOnePosHpr) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + self.doAnimate() + + self.__hideWitnessToon() + + if not self.mainDoor.isEmpty(): + self.mainDoor.stash() + if not self.reflectedMainDoor.isEmpty(): + self.reflectedMainDoor.stash() + + + # Position the camera behind the toons + camera.reparentTo(self.elevatorModel) + camera.setPosHpr(0, 30, 8, 180, 0, 0) + + + def exitElevator(self): + """ + Exit elevator state. + """ + self.notify.debug("----- exitElevator") + DistributedBossCog.DistributedBossCog.exitElevator(self) + + self.witnessToon.removeActive() + + + ##### Introduction state ##### + + def enterIntroduction(self): + """ + Enter the Introduction state, CJ speech, toons are revealed. + """ + # Set the boss up in the middle of the floor, actively + self.notify.debug("----- enterIntroduction") + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.LawbotBossBattleOnePosHpr) + self.stopAnimate() + + self.__hideWitnessToon() + + DistributedBossCog.DistributedBossCog.enterIntroduction(self) + + # Make sure the side ramps are extended and the back ramp is + # retracted. + + + base.playMusic(self.promotionMusic, looping=1, volume=0.9) + + if not self.mainDoor.isEmpty(): + self.mainDoor.stash() + if not self.reflectedMainDoor.isEmpty(): + self.reflectedMainDoor.stash() + + + + def exitIntroduction(self): + """ + Exit introduction state. + """ + self.notify.debug("----- exitIntroduction") + DistributedBossCog.DistributedBossCog.exitIntroduction(self) + + self.promotionMusic.stop() + + if not self.mainDoor.isEmpty(): + #self.mainDoor.unstash() + pass + if not self.reflectedMainDoor.isEmpty(): + self.reflectedMainDoor.unstash() + + + if not self.elevatorEntrance.isEmpty(): + pass + #self.elevatorEntrance.hide() + + + ##### BattleOne state ##### + + def enterBattleOne(self): + """ + Battle one state, fight lots of cogs. + """ + self.notify.debug("----- LawbotBoss.enterBattleOne ") + DistributedBossCog.DistributedBossCog.enterBattleOne(self) + + # Boss Cog is still in the middle of the floor. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.LawbotBossBattleOnePosHpr) + self.clearChat() + self.loop('Ff_neutral') #just in case we got here through a magic word + + + self.notify.debug("self.battleANode = %s" % self.battleANode) + + # The toons should be in their battle position. + #self.toonsToBattlePosition(self.toonsA, self.battleANode) + #self.toonsToBattlePosition(self.toonsB, self.battleBNode) + + self.__hideWitnessToon() + + if self.battleA == None or self.battleB == None: + pass + else: + pass + + + def exitBattleOne(self): + """ + Done with battle one state. + """ + self.notify.debug("----- exitBattleOne") + DistributedBossCog.DistributedBossCog.exitBattleOne(self) + + # helper function for boss temp fix + def stashBoss(self): + self.stash() + + # helper function for boss temp fix + def unstashBoss(self, task): + self.unstash() + self.reparentTo(render) + + ##### RollToBattleTwo state ##### + + def enterRollToBattleTwo(self): + """ + Rolling to the podium. + """ + self.notify.debug("----- enterRollToBattleTwo") + assert self.notify.debug('enterRollToBattleTwo()') + + self.releaseToons(finalBattle = 1) + # There is a strange collision issue when we try to turn the cog boss + # So until this is fixed we stash the boss for a split second to not hit the player + # automatically losing them laff points + self.stashBoss() + + # The Boss Cog rolls up the ramp into position for battle two, + # while the Toons are free to run around for a few seconds. + self.stickBossToFloor() + + # Now generate the interval that plays the movie. + intervalName = "RollToBattleTwo" + seq = Sequence(self.__makeRollToBattleTwoMovie(), + Func(self.__onToPrepareBattleTwo), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + base.playMusic(self.betweenBattleMusic, looping=1, volume=0.9) + #re-enable the collision a little bit later, after the boss has started moving + taskMgr.doMethodLater(.01, self.unstashBoss, 'unstashBoss') + + def __onToPrepareBattleTwo(self): + """ + Done rolling to podium + """ + self.notify.debug("----- __onToPrepareBattleTwo") + # Make sure the boss ends up in his battle position. + self.unstickBoss() + self.setPosHpr(*ToontownGlobals.LawbotBossBattleTwoPosHpr) + self.doneBarrier('RollToBattleTwo') + + def exitRollToBattleTwo(self): + """ + Exit the roll to battle two state. + """ + self.notify.debug("----- exitRollToBattleTwo") + self.unstickBoss() + intervalName = "RollToBattleTwo" + self.clearInterval(intervalName) + + self.betweenBattleMusic.stop() + + ##### PrepareBattleTwo state ##### + + def enterPrepareBattleTwo(self): + """ + Show the witness toon talking, and bring out the cannons. + """ + self.notify.debug("----- enterPrepareBattleTwo") + assert self.notify.debug('enterPrepareBattleTwo()') + self.cleanupIntervals() + + self.controlToons() + #don't leave them in the walking state if we take the control away from player + self.setToonsToNeutral(self.involvedToons) + + #self.releaseToons(finalBattle = 1) + + self.clearChat() + self.reparentTo(render) + + + #now show our star witness and have him give instructions + self.__showWitnessToon() + + prepareBattleTwoMovie = self.__makePrepareBattleTwoMovie() + + # Now generate the interval that plays the movie. + intervalName = "prepareBattleTwo" + seq = Sequence(prepareBattleTwoMovie, + #Func(self.__onToBattleTwo), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + # We see the witness toon giving us advice + # advice for the battle two; we have to click through + # this advice to move on. + #self.acceptOnce("doneChatPage", self.__onToBattleTwo) + self.acceptOnce("doneChatPage", self.__showCannonsAppearing) + + # Let's play the elevator music again; it's dramatic enough to + # use twice. + base.playMusic(self.stingMusic, looping=0, volume=1.0) + + def __showCannonsAppearing(self, elapsedTime = 0): + """ + Show the toons taking out their cannons. + """ + + allCannonsAppear = Sequence( + Func(self.__positionToonsInFrontOfCannons), + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, localAvatar.cameraPositions[2][0]), + Func(camera.lookAt, localAvatar), + ) + + multiCannons = Parallel() + + index = 0 + self.involvedToons.sort() + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + if self.cannons.has_key(index): + cannon = self.cannons[index] + cannonSeq = cannon.generateCannonAppearTrack(toon) + multiCannons.append(cannonSeq) + index += 1 + else: + self.notify.warning('No cannon %d but we have a toon =%d' % (index,toonId)) + + + allCannonsAppear.append(multiCannons) + + # Now generate the interval that plays the movie. + intervalName = "prepareBattleTwoCannonsAppear" + seq = Sequence(allCannonsAppear, + Func(self.__onToBattleTwo), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + + + def __onToBattleTwo(self, elapsedTime=0): + """ + Donw with jury. + """ + self.notify.debug("----- __onToBattleTwo") + self.doneBarrier('PrepareBattleTwo') + + # Wait a second. If we don't move on immediately, pop up the + # "waiting for other players" message. + taskMgr.doMethodLater(1, self.__showWaitingMessage, + self.uniqueName("WaitingMessage")) + + def exitPrepareBattleTwo(self): + """ + Cleanup PrepareBattleTwo state. + """ + self.notify.debug("----- exitPrepareBattleTwo") + self.show() + taskMgr.remove(self.uniqueName("WaitingMessage")) + self.ignore("doneChatPage") + self.__clearOnscreenMessage() + self.stingMusic.stop() + + ##### BattleTwo state ##### + + def enterBattleTwo(self): + """ + Enter the cannons and jury state. + """ + self.notify.debug("----- enterBattleTwo") + assert self.notify.debug('enterBattleTwo()') + self.cleanupIntervals() + + # Get the credit multiplier + mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(2) + localAvatar.inventory.setBattleCreditMultiplier(mult) + + # Boss Cog is now on top of the ramp. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.LawbotBossBattleTwoPosHpr) + + # Clear the chat dialogs left over from the transition movie. + self.clearChat() + self.witnessToon.clearChat() + + + # Now the battle holds the toons. + self.releaseToons(finalBattle = 1) + + self.__showWitnessToon() + + + # Position the toons to the battle. + if not self.useCannons: + self.toonsToBattlePosition(self.toonsA, self.battleANode) + self.toonsToBattlePosition(self.toonsB, self.battleBNode) + + base.playMusic(self.battleTwoMusic, looping=1, volume=0.9) + + + self.startJuryBoxMoving() + + for index in range(len(self.cannons)): + #make sure I can see the cannon + cannon = self.cannons[index] + cannon.cannon.show() + + + def getChairParent(self): + """ + Where do the jury chairs reparent to. + """ + return self.juryBox + + def startJuryBoxMoving(self): + + curPos = self.juryBox.getPos() + endingAbsPos = Point3(curPos[0] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[0], + curPos[1] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[1], + curPos[2] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[2] + ) + + curReflectedPos = self.reflectedJuryBox.getPos() + reflectedEndingAbsPos = Point3 (curReflectedPos[0] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[0], + curReflectedPos[1] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[1], + curReflectedPos[2] + + ToontownGlobals.LawbotBossJuryBoxRelativeEndPos[2] + ) + self.juryBoxIval = Parallel( + self.juryBox.posInterval( ToontownGlobals.LawbotBossJuryBoxMoveTime, endingAbsPos), + self.reflectedJuryBox.posInterval( ToontownGlobals.LawbotBossJuryBoxMoveTime, reflectedEndingAbsPos), + SoundInterval(self.juryMovesSfx, node = self.chairs[2].nodePath, duration = ToontownGlobals.LawbotBossJuryBoxMoveTime, loop = 1, volume = 1.0), + ) + + self.juryBoxIval.start() + + #setup jury timer + self.juryTimer = ToontownTimer.ToontownTimer() + self.juryTimer.posInTopRightCorner() + self.juryTimer.countdown(ToontownGlobals.LawbotBossJuryBoxMoveTime) + + def exitBattleTwo(self): + """ + Done with this state, do the cleanup. + """ + self.notify.debug("----- exitBattleTwo") + intervalName = self.uniqueName('Drop') + self.clearInterval(intervalName) + self.cleanupBattles() + self.battleTwoMusic.stop() + + # No more credit multiplier + localAvatar.inventory.setBattleCreditMultiplier(1) + + if (self.juryTimer): + self.juryTimer.destroy() + del self.juryTimer + self.juryTimer = None + + for chair in self.chairs.values(): + chair.stopCogsFlying() + + + + ##### RollToBattleThree state ##### + + def enterRollToBattleThree(self): + """ + Unused in CJ + """ + self.notify.debug("----- enterRollToBattleThree") + assert self.notify.debug('enterRollToBattleThree()') + + self.reparentTo(render) + + + # The Boss Cog rolls up the ramp into position for battle two, + # while the Toons are free to run around for a few seconds. + self.stickBossToFloor() + + # Now generate the interval that plays the movie. + intervalName = "RollToBattleThree" + seq = Sequence(self.__makeRollToBattleThreeMovie(), + Func(self.__onToPrepareBattleThree), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + base.playMusic(self.betweenBattleMusic, looping=1, volume=0.9) + + def __onToPrepareBattleThree(self): + """ + Unused in CJ. + """ + self.notify.debug("----- __onToPrepareBattleThree") + # Make sure the boss ends up in his battle position. + self.unstickBoss() + self.setPosHpr(*ToontownGlobals.LawbotBossBattleThreePosHpr) + self.doneBarrier('RollToBattleThree') + + def exitRollToBattleThree(self): + """ + Unused in CJ. + """ + self.notify.debug("----- exitRollToBattleThree") + self.unstickBoss() + intervalName = "RollToBattleThree" + self.clearInterval(intervalName) + + self.betweenBattleMusic.stop() + + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + """ + Witness toon gives us a recap and advice. + """ + self.notify.debug("----- enterPrepareBattleThree") + assert self.notify.debug('enterPrepareBattleThree()') + self.cleanupIntervals() + + self.controlToons() + #don't leave them in the walking state if we take the control away from player + self.setToonsToNeutral(self.involvedToons) + #self.releaseToons(finalBattle = 1) + + self.clearChat() + + self.reparentTo(render) + + # Retract all of the ramps but the back one. + base.playMusic(self.betweenBattleMusic, looping=1, volume=0.9) + + self.__showWitnessToon() + + prepareBattleThreeMovie = self.__makePrepareBattleThreeMovie() + + # We see the witness toon giving us advice + # advice for the battle two; we have to click through + # this advice to move on. + self.acceptOnce("doneChatPage", self.__onToBattleThree) + + + # Now generate the interval that plays the movie. + intervalName = "prepareBattleThree" + seq = Sequence(prepareBattleThreeMovie, + #Func(self.__onToBattleTwo), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + + + def __onToBattleThree(self, elapsed): + """ + We're done listening to the witness toon. + """ + self.notify.debug("----- __onToBattleThree") + self.doneBarrier('PrepareBattleThree') + + # Wait a second. If we don't move on immediately, pop up the + # "waiting for other players" message. + taskMgr.doMethodLater(1, self.__showWaitingMessage, + self.uniqueName("WaitingMessage")) + + def exitPrepareBattleThree(self): + """ + Done with this state. Do cleanup + """ + self.notify.debug("----- exitPrepareBattleThree") + self.show() + taskMgr.remove(self.uniqueName("WaitingMessage")) + self.ignore("doneChatPage") + intervalName = "PrepareBattleThree" + self.clearInterval(intervalName) + self.__clearOnscreenMessage() + self.betweenBattleMusic.stop() + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('enterBattleThree()') + DistributedBossCog.DistributedBossCog.enterBattleThree(self) + + self.scaleNodePath.unstash() + + localAvatar.setPos(-3,0,0) + camera.reparentTo( localAvatar) + camera.setPos( localAvatar.cameraPositions[0][0]) + camera.setHpr( 0, 0, 0) + + self.clearChat() + self.witnessToon.clearChat() + self.reparentTo(render) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + self.doAnimate() + + # Now we're in pie mode. + self.accept('enterWitnessStand', self.__touchedWitnessStand) + self.accept('pieSplat', self.__pieSplat) + self.accept('localPieSplat', self.__localPieSplat) + self.accept('outOfPies', self.__outOfPies) + self.accept('begin-pie', self.__foundPieButton) + self.accept('enterDefenseCol', self.__enterDefenseCol) + self.accept('enterProsecutionCol', self.__enterProsecutionCol) + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + # In case they don't figure it out, hit them over the head + # with it after a few seconds. + taskMgr.doMethodLater(30, self.__howToGetPies, + self.uniqueName("PieAdvice")) + + # Now, the Boss mainly sits there and taunts us, and + # occasionally attacks us. But when we hit him, he rolls a + # little bit farther down the launching area. We implement + # this by playing a little bit more of the bossDamageMovie + # with each hit. + self.stickBossToFloor() + + self.setPosHpr(*ToontownGlobals.LawbotBossBattleThreePosHpr) + + self.bossMaxDamage = ToontownGlobals.LawbotBossMaxDamage + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9) + + self.__showWitnessToon() + + diffSettings = ToontownGlobals.LawbotBossDifficultySettings[self.battleDifficulty] + if diffSettings[4]: + #ok so we have toon jurors affecting the weight + # Enable the special CJ chat menu. and include the bonus evidence + localAvatar.chatMgr.chatInputSpeedChat.removeCJMenu() + localAvatar.chatMgr.chatInputSpeedChat.addCJMenu(self.bonusWeight) + + + def __doneBattleThree(self): + """ + Done with battle three. + """ + self.notify.debug("----- __doneBattleThree") + + # We've played the boss damage movie all the way to + # completion; it's time to transition to the NearVictory state. + self.setState('NearVictory') + self.unstickBoss() + + def exitBattleThree(self): + """ + Done with this state, do cleanup + """ + self.notify.debug("----- exitBattleThree") + DistributedBossCog.DistributedBossCog.exitBattleThree(self) + + #restore the master arrows + NametagGlobals.setMasterArrowsOn(1) + + bossDoneEventName = self.uniqueName('DestroyedBoss') + self.ignore(bossDoneEventName) + # Actually, we'll keep the animation going, so we can continue + # it in the NearVictory state. + #self.stopAnimate() + + taskMgr.remove(self.uniqueName('StandUp')) + + # No more pies. + self.ignore('enterWitnessStand') + self.ignore('pieSplat') + self.ignore('localPieSplat') + self.ignore('outOfPies') + self.ignore('begin-pie') + self.ignore('enterDefenseCol') + self.ignore('enterProsecutionCol') + + + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + + if self.bossDamageMovie: + self.bossDamageMovie.finish() + self.bossDamageMovie = None + self.unstickBoss() + + taskName = "RecoverBossDamage" + taskMgr.remove(taskName) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + + ##### NearVictory state ##### + + def enterNearVictory(self): + """ + Unused in CJ + """ + assert self.notify.debug('enterNearVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + + # Boss Cog is on the edge of the precipice, waiting for that + # one final pie toss. + self.reparentTo(render) + self.setPos(*ToontownGlobals.LawbotBossDeathPos) + self.setHpr(*ToontownGlobals.LawbotBossBattleThreeHpr) + self.clearChat() + + + + + # No one owns the toons. + self.releaseToons(finalBattle = 1) + + # Retract all of the ramps but the back one. + + + # We're still in pie mode, for the one more ceremonial toss + # that sends the boss plummeting to his death. + + self.accept('pieSplat', self.__finalPieSplat) + self.accept('localPieSplat', self.__localPieSplat) + self.accept('outOfPies', self.__outOfPies) + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + self.happy = 0 + self.raised = 0 + self.forward = 1 + self.doAnimate() + self.setDizzy(1) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def exitNearVictory(self): + """ + Unused in CJ + """ + self.notify.debug("----- exitNearVictory") + # No more pies. + + self.ignore('pieSplat') + self.ignore('localPieSplat') + self.ignore('outOfPies') + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + + self.setDizzy(0) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + # Actually, we'll keep the animation going, so we can continue + # it in the Victory state. + #self.stopAnimate() + + ##### Victory state ##### + + def enterVictory(self): + """ + Toons won. Do the CJ lost speech. + """ + self.notify.debug("----- enterVictory") + assert self.notify.debug('enterVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.LawbotBossBattleThreePosHpr) + self.loop('neutral') + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + self.clearChat() + self.witnessToon.clearChat() + + + self.controlToons() + #don't leave them in the walking state if we take the control away from player + self.setToonsToNeutral(self.involvedToons) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + + # Play the boss's Defense wins animation. + #self.doAnimate('Ff_speech', now = 1) + + intervalName = "VictoryMovie" + + seq = Sequence(self.makeVictoryMovie(), + Func(self.__continueVictory), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def __continueVictory(self): + """ + # Ok, he's gone! We all move to the reward movie. + """ + self.notify.debug("----- __continueVictory") + self.stopAnimate() + + self.doneBarrier('Victory') + + def exitVictory(self): + """ + Done with this state, do cleanup + """ + self.notify.debug("----- exitVictory") + self.stopAnimate() + self.unstash() + + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + + ##### Defeat state ##### + + def enterDefeat(self): + """ + Prosecution pan went down. Toons lose. + """ + self.notify.debug("----- enterDefeat") + assert self.notify.debug('enterDefeat()') + # No more intervals should be playing. + self.cleanupIntervals() + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + + self.reparentTo(render) + + self.clearChat() + + + # No one owns the toons. + self.releaseToons(finalBattle = 1) + + + self.happy = 0 + self.raised = 0 + self.forward = 1 + + + intervalName = "DefeatMovie" + + seq = Sequence(self.makeDefeatMovie(), + Func(self.__continueDefeat), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def __continueDefeat(self): + """ + We're done. + """ + self.notify.debug("----- __continueDefeat") + self.stopAnimate() + self.doneBarrier('Defeat') + + def exitDefeat(self): + """ + We're done with this state, do cleanup + """ + self.notify.debug("----- exitDefeat") + self.stopAnimate() + self.unstash() + + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + + ##### Reward state ##### + + def enterReward(self): + """ + Show the reward movie, skillups, questsm etc. + """ + assert self.notify.debug('enterReward()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.witnessToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + + + # The toons are technically free to run around, but localToon + # starts out locked down for the reward movie. + self.controlToons() + + + # Start the reward movie playing. + + panelName = self.uniqueName('reward') + self.rewardPanel = RewardPanel.RewardPanel(panelName) + (victory, camVictory) = MovieToonVictory.doToonVictory( + 1, self.involvedToons, + self.toonRewardIds, + self.toonRewardDicts, + self.deathList, + self.rewardPanel, + allowGroupShot = 0, + uberList = self.uberList) + + ival = Sequence( + Parallel(victory, camVictory), + Func(self.__doneReward)) + + intervalName = "RewardMovie" + delayDeletes = [] + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete(toon, 'LawbotBoss.enterReward')) + + ival.delayDeletes = delayDeletes + ival.start() + self.storeInterval(ival, intervalName) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def __doneReward(self): + """ + We're done with the reward move + """ + self.notify.debug("----- __doneReward") + self.doneBarrier('Reward') + self.toWalkMode() + + def exitReward(self): + """ + Exit this state. do cleanup + """ + self.notify.debug("----- exitReward") + intervalName = "RewardMovie" + self.clearInterval(intervalName) + + self.unstash() + self.rewardPanel.destroy() + del self.rewardPanel + + + self.battleThreeMusicTime = 0 + self.battleThreeMusic.stop() + + ##### Epilogue state ##### + + def enterEpilogue(self): + """ + Enter the epilogue. Witness toon thanks us. + """ + assert self.notify.debug('enterEpilogue()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.witnessToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + + + # The toons are under our control once again. + self.controlToons() + + self.__showWitnessToon() + self.witnessToon.reparentTo(render) + self.witnessToon.setPosHpr(*ToontownGlobals.LawbotBossWitnessEpiloguePosHpr) + self.witnessToon.loop('Sit') + self.__arrangeToonsAroundWitnessToon() + + camera.reparentTo(render) + camera.setPos(self.witnessToon, -9, 12, 6) + camera.lookAt(self.witnessToon, 0, 0, 3) + + intervalName = "EpilogueMovie" + + seq = Sequence(self.makeEpilogueMovie(), + name = intervalName) + + seq.start() + self.storeInterval(seq, intervalName) + + self.accept("doneChatPage", self.__doneEpilogue) + + base.playMusic(self.epilogueMusic, looping=1, volume=0.9) + + def __doneEpilogue(self, elapsedTime = 0): + """ + Done, teleport to safe zone + """ + self.notify.debug("----- __doneEpilogue") + #self.doneBarrier('Epilogue') + + intervalName = "EpilogueMovieToonAnim" + self.clearInterval(intervalName) + track = Parallel( + Sequence(Wait(0.5), + Func(self.localToonToSafeZone))) + self.storeInterval(track, intervalName) + track.start() + + + def exitEpilogue(self): + """ + Done with this state, cleanup + """ + self.notify.debug("----- exitEpilogue") + self.clearInterval("EpilogueMovieToonAnim") + self.unstash() + + + self.epilogueMusic.stop() + + + + + ##### Frolic state ##### + + # This state is probably only useful for debugging. The toons are + # all free to run around the world. + + def enterFrolic(self): + """ + We should only get here through a magic word + """ + self.notify.debug("----- enterFrolic") + self.setPosHpr(*ToontownGlobals.LawbotBossBattleOnePosHpr) + DistributedBossCog.DistributedBossCog.enterFrolic(self) + + self.show() + + ##### Misc. utility functions ##### + + def doorACallback(self, isOpen): + """ + # Called whenever doorA opens or closes. + """ + #self.notify.debug("----- doorACallback") + if self.insidesANodePath: + if isOpen: + self.insidesANodePath.unstash() + else: + self.insidesANodePath.stash() + + def doorBCallback(self, isOpen): + """ + # Called whenever doorB opens or closes. + """ + #self.notify.debug("----- doorBCallback") + if self.insidesBNodePath: + if isOpen: + self.insidesBNodePath.unstash() + else: + self.insidesBNodePath.stash() + + def __toonsToPromotionPosition(self, toonIds, battleNode): + """ + Unused in CJ. + """ + self.notify.debug("----- __toonsToPromotionPosition") + + # At first, the toons walk down the ramp and stand close to + # the Boss Cog to receive a promotion. They don't back up to + # battle position until a little bit later. + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + toon.reparentTo(render) + pos, h = points[i] + toon.setPosHpr(battleNode, pos[0], pos[1] + 10, pos[2], h, 0, 0) + + + + def __outOfPies(self): + """ + Tell the user he needs to get more evidence. + """ + self.notify.debug("----- outOfPies") + self.__showOnscreenMessage(TTLocalizer.LawbotBossNeedMoreEvidence) + taskMgr.doMethodLater(20, self.__howToGetPies, + self.uniqueName("PieAdvice")) + + def __howToGetPies(self, task): + """ + Tell user how to get evidence. + """ + self.notify.debug("----- __howToGetPies") + self.__showOnscreenMessage(TTLocalizer.LawbotBossHowToGetEvidence) + + def __howToThrowPies(self, task): + """ + Tell the user how to throw the evidence + """ + self.notify.debug("----- __howToThrowPies") + self.__showOnscreenMessage(TTLocalizer.LawbotBossHowToThrowPies) + + def __foundPieButton(self): + """ + He knows how to throw evidence. + """ + #self.notify.debug("----- __foundPieButton") + self.everThrownPie = 1 + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + def __touchedWitnessStand(self,entry): + """ + The avatar has touched the witness stand. he should be given evidence now. + """ + assert self.notify.debug('__touchedWitnessStand') + + #I think we can piggy back of the existing cage and pie code + #Just change how it's presented to the user + self.sendUpdate('touchWitnessStand',[]) + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + base.playSfx(self.piesRestockSfx) + + if not self.everThrownPie: + taskMgr.doMethodLater(30, self.__howToThrowPies, self.uniqueName('PieAdvice')) + + def __pieSplat(self, toon, pieCode): + """ + # A pie thrown by localToon or some other toon hit something; + # show a visible reaction if that something is the boss. + """ + assert self.notify.debug("__pieSplat()") + + if pieCode == ToontownGlobals.PieCodeBossInsides: + if toon == localAvatar: + self.d_hitBossInsides() + + self.flashRed() + + elif pieCode == ToontownGlobals.PieCodeBossCog: + if toon == localAvatar: + self.d_hitBoss(1) + + if self.dizzy: + self.flashRed() + self.doAnimate('hit', now=1) + + elif pieCode == ToontownGlobals.PieCodeDefensePan: + #self.notify.debug("Defense Pan hit") + self.flashRed() + self.flashPanBlue() + #you are going to hear this sound a lot, made it softer + base.playSfx(self.evidenceHitSfx, node=self.defensePanNodePath,volume = 0.25) + if (toon == localAvatar): + self.d_hitBoss(self.panDamage) + + elif pieCode == ToontownGlobals.PieCodeProsecutionPan: + #self.notify.debug("Prosecution Pan Hit") + self.flashGreen() + if (toon == localAvatar): + #don't heal the boss if we hit the prosecution pan + #self.d_healBoss(ToontownGlobals.LawbotBossDefensePanDamage) + pass + elif pieCode == ToontownGlobals.PieCodeLawyer: + pass + #self.notify.debug("Lawyer hit") + #hmmmm I don't have the info I need here, localPieSplat has it + + def __localPieSplat(self, pieCode, entry): + """ + Our local toon threw evidence and hit something + """ + assert self.notify.debug("__localPieSplat()") + + if pieCode == ToontownGlobals.PieCodeLawyer: + self.__lawyerGotHit(entry) + + + # A pie thrown by localToon toon hit something; tell the AI if + # we hit another toon. + if pieCode != ToontownGlobals.PieCodeToon: + return + + avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId') + if avatarDoId == '': + self.notify.warning("Toon %s has no avatarDoId tag." % (repr(entry.getIntoNodePath()))) + return + + doId = int(avatarDoId) + if doId != localAvatar.doId: + self.d_hitToon(doId) + + + def __lawyerGotHit(self,entry): + """ + One of the lawyers got hit, tell the AI + """ + lawyerCol = entry.getIntoNodePath() + names = lawyerCol.getName().split('-') + lawyerDoId = int( names[1] ) + for lawyer in self.lawyers: + if lawyerDoId == lawyer.doId: + lawyer.sendUpdate('hitByToon',[]) + + + def __finalPieSplat(self, toon, pieCode): + """ + Unused in CJ + """ + assert self.notify.debug("__finalPieSplat()") + # This is the final pie toss that starts the boss's fall. + # It's really just a formality, since we're already in the + # Victory state. + #if pieCode != ToontownGlobals.PieCodeBossCog: + if pieCode != ToontownGlobals.PieCodeDefensePan: + return + + # Tell the AI; the AI will then immediately transition to Victory + # state. + self.sendUpdate('finalPieSplat', []) + + # We don't care to hear any more about pies hitting the boss. + self.ignore('pieSplat') + + + def cleanupAttacks(self): + """ + # Stops any attack currently running. + """ + self.notify.debug("----- cleanupAttacks") + self.__cleanupStrafe() + + def __cleanupStrafe(self): + """ + Unused in CJ + """ + self.notify.debug("----- __cleanupStrage") + if self.strafeInterval: + self.strafeInterval.finish() + self.strafeInterval = None + + def __cleanupJuryBox(self): + """ + Cleanup the jury box. + """ + self.notify.debug("----- __cleanupJuryBox") + if self.juryBoxIval: + self.juryBoxIval.finish() + self.juryBoxIval = None + + if self.juryBox: + self.juryBox.removeNode() + + + def doStrafe(self, side, direction): + """ + Unused in CJ + """ + assert self.notify.debug('doStrafe(%s, %s)' % (side, direction)) + + # Spit a stream of gears out either the front or the back + # door, from left to right (or from right to left). + + gearRoot = self.rotateNode.attachNewNode('gearRoot') + if side == 0: + gearRoot.setPos(0, -7, 3) + gearRoot.setHpr(180, 0, 0) + door = self.doorA + else: + gearRoot.setPos(0, 7, 3) + door = self.doorB + + gearRoot.setTag('attackCode', str(ToontownGlobals.BossCogStrafeAttack)) + gearModel = self.getGearFrisbee() + gearModel.setScale(0.1) + + t = self.getBossDamage() / 100.0 + + gearTrack = Parallel() + + # numGears ranges from 4 to 10 + # time ranges from 5 to 1 + numGears = int(4 + 6 * t + 0.5) + time = 5.0 - 4.0 * t + + spread = 60 * math.pi / 180.0 + if direction == 1: + spread = -spread + + dist = 50 + rate = time / numGears + for i in range(numGears): + node = gearRoot.attachNewNode(str(i)) + node.hide() + node.setPos(0, 0, 0) + gear = gearModel.instanceTo(node) + angle = ((float(i) / (numGears - 1)) - 0.5) * spread + x = dist * math.sin(angle) + y = dist * math.cos(angle) + h = random.uniform(-720, 720) + gearTrack.append(Sequence( + Wait(i * rate), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, y, 0), fluid = 1), + node.hprInterval(1, VBase3(h, 0, 0), fluid = 1), + Sequence(SoundInterval(self.strafeSfx[i], volume = 0.2, node = self), duration = 0), + ), + Func(node.detachNode))) + + seq = Sequence( + Func(door.request, 'open'), + Wait(0.7), + gearTrack, + Func(door.request, 'close'), + ) + + self.__cleanupStrafe() + self.strafeInterval = seq + seq.start() + + + + def replaceCollisionPolysWithPlanes(self, model): + """ + Make the world a safer place to run around in + """ + newCollisionNode = CollisionNode('collisions') + newCollideMask = BitMask32(0) + planes = [] + + collList = model.findAllMatches('**/+CollisionNode') + if not collList: + collList = [model] + + for cnp in collList: + cn = cnp.node() + if not isinstance(cn, CollisionNode): + self.notify.warning("Not a collision node: %s" % (repr(cnp))) + break + + newCollideMask = newCollideMask | cn.getIntoCollideMask() + for i in range(cn.getNumSolids()): + solid = cn.getSolid(i) + if isinstance(solid, CollisionPolygon): + # Save the plane defined by this polygon + plane = Plane(solid.getPlane()) + planes.append(plane) + else: + self.notify.warning("Unexpected collision solid: %s" % (repr(solid))) + newCollisionNode.addSolid(plane) + + newCollisionNode.setIntoCollideMask(newCollideMask) + + # Now sort all of the planes and remove the nonunique ones. + # We can't use traditional dictionary-based tricks, because we + # want to use Plane.compareTo(), not Plane.__hash__(), to make + # the comparison. + threshold = 0.1 + planes.sort(lambda p1, p2: p1.compareTo(p2, threshold)) + lastPlane = None + for plane in planes: + if lastPlane == None or plane.compareTo(lastPlane, threshold) != 0: + cp = CollisionPlane(plane) + newCollisionNode.addSolid(cp) + lastPlane = plane + + return NodePath(newCollisionNode) + + def makeIntroductionMovie(self, delayDeletes): + """ + # Generate an interval which shows the toons meeting the + # Resistance Toon, and the introduction of the CFO, etc., + # leading to the events of battle one. + """ + + self.notify.debug("----- makeIntroductionMovie") + + # We need to protect our movie against any of the toons + # disconnecting while the movie plays. + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete( + toon, 'LawbotBoss.makeIntroductionMovie')) + + + track = Parallel() + + bossAnimTrack = Sequence( + ActorInterval(self, 'Ff_speech', startTime = 2, duration = 10, loop = 1), + #10 + ActorInterval(self, 'Ff_lookRt', duration = 3), + #13 + ActorInterval(self, 'Ff_lookRt', duration = 3, startTime =3, endTime = 0), + #16 + ActorInterval(self, 'Ff_neutral', duration = 2), + # 18 + ActorInterval(self, 'Ff_speech', duration = 7, loop = 1), + # 25 + + # remaining animations mixed in with camera cuts in + # dialogTrack. + ) + track.append(bossAnimTrack) + + attackToons = TTLocalizer.BossCogAttackToons + + dialogTrack = Track( + (0, Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempIntro0, CFSpeech)), + + (5.6, Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempIntro1, CFSpeech)), + + (12, Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempIntro2, CFSpeech)), + + (18, Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempIntro3, CFSpeech)), + + (22, Func(self.setChatAbsolute, TTLocalizer.LawbotBossTempIntro4, CFSpeech)), + + # Cut to toons losing their cog suits. + (24, Sequence(Func(self.clearChat), + self.loseCogSuits(self.toonsA +self.toonsB, render, (-2.798, -70, 10,180,0,0)), + )), + + # Cut to wide shot of battle arena. Toons back up and + # ramps retract. + (27, Sequence(self.toonNormalEyes(self.involvedToons), + Func(self.loop, 'Ff_neutral'), + Func (self.setChatAbsolute, attackToons, CFSpeech) + )), + ) + track.append(dialogTrack) + + return Sequence(Func(self.stickToonsToFloor), + track, + Func(self.unstickToons), + name = self.uniqueName('Introduction')) + + + + + def walkToonsToBattlePosition(self, toonIds, battleNode): + """ + Unused in CJ + # Returns a movie showing the toons walking up from promotion + # position to battle position. + """ + self.notify.debug("walkToonsToBattlePosition-----------------------------------------------") + self.notify.debug("toonIds=%s battleNode=%s" % (toonIds, battleNode)) + ival = Parallel() + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + self.notify.debug("walkToonsToBattlePosition: points = %s" % (points[0][0])) + + #TODO figure out why we are appearing inside the boss for a brief period of time + #wrong computed coordinate = 4.9, -16.9, 0 + #correct one -18.1, -21.9,0 + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + pos, h = points[i] + origPos = pos; + self.notify.debug("origPos = %s" % origPos) + self.notify.debug("batlleNode.getTransform = %s render.getTransform=%s" % (battleNode.getTransform(), render.getTransform())) + self.notify.debug("render.getScale()=%s battleNode.getScale()=%s" % (render.getScale(), battleNode.getScale())) + myCurPos = self.getPos() + self.notify.debug("myCurPos = %s" % self.getPos()) + #pos = pos - self.getPos() + #self.notify.debug("pos-self.getPos() = %s" % pos) + self.notify.debug("battleNode.parent() = %s" % battleNode.getParent()) + self.notify.debug("battleNode.parent().getPos() = %s" % battleNode.getParent().getPos()) + + bnParent = battleNode.getParent() + battleNode.wrtReparentTo(render) + bnWorldPos = battleNode.getPos() + battleNode.wrtReparentTo(bnParent) + + self.notify.debug("battle node world pos = %s" % bnWorldPos) + + pos = render.getRelativePoint(battleNode, pos) + #pos = battleNode.getPos() + pos + self.notify.debug("walktToonsToBattlePosition: render.getRelativePoint result = %s" % pos) + + #self.notify.debug("doing pos - bnWorldPos") + #pos = pos - bnWorldPos + + self.notify.debug("walkToonsToBattlePosition: final pos = %s" % pos) + + + ival.append(Sequence( + Func(toon.setPlayRate, 0.8, 'walk'), + Func(toon.loop, 'walk'), + toon.posInterval(3, pos), + Func(toon.setPlayRate, 1, 'walk'), + Func(toon.loop,'neutral'), + )) + + return ival + + + def toonsToBattlePosition(self, toonIds, battleNode): + """ + # Position the toons in the list to their appropriate position + # relative to the indicated battleNode. + """ + + + self.notify.debug("DistrutedLawbotBoss.toonsToBattlePosition----------------------------------------") + self.notify.debug("toonIds=%s battleNode=%s" % (toonIds,battleNode)) + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + self.notify.debug("toonsToBattlePosition: points = %s" % points[0][0]) + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + toon.wrtReparentTo(render) + pos, h = points[i] + + #self.notify.debug("points = %.2f %.2f %.2f" % (points[0][0], points[0][1], points[0][2]) + #self.notify.debug("toonsToBattlePosition: battleNode=%s %.2f %.2f %.2f %.2f %.2f %.2f" % (battleNode, pos[0], pos[1], pos[2], h, 0, 0)) + + #self.notify.debug("old toon pos %s" % toon.getPos()) + #self.notify.debug("pos=%.2f %.2f %.2f h=%.2f" % (pos[0], pos[1], pos[2], h)) + #self.notify.debug("battleNode.pos = %s" % battleNode.getPos()) + #self.notify.debug("battleNode.hpr = %s" % battleNode.getHpr()) + + bnParent = battleNode.getParent() + battleNode.wrtReparentTo(render) + bnWorldPos = battleNode.getPos() + battleNode.wrtReparentTo(bnParent) + + #pos = pos - bnWorldPos + #toon.setPosHpr(pos[0],pos[1], pos[2], h, 0, 0) + + toon.setPosHpr(battleNode, pos[0], pos[1], pos[2], h, 0, 0) + + self.notify.debug("new toon pos %s " % toon.getPos()) + + + def touchedGavel( self, gavel, entry): + """ + # The localToon has come into contact with a gavel head. Zap! + """ + self.notify.debug('touchedGavel') + + # Get the attack code from the thing we touched. + attackCodeStr = entry.getIntoNodePath().getNetTag('attackCode') + if attackCodeStr == '': + self.notify.warning("Node %s has no attackCode tag." % (repr(entry.getIntoNodePath()))) + return + + attackCode = int(attackCodeStr) + + into = entry.getIntoNodePath(); + self.zapLocalToon(attackCode, into) + + + def touchedGavelHandle( self, gavel, entry): + """ + # The localToon has come into contact with a gavel handle. Zap! + """ + attackCodeStr = entry.getIntoNodePath().getNetTag('attackCode') + if attackCodeStr == '': + self.notify.warning("Node %s has no attackCode tag." % (repr(entry.getIntoNodePath()))) + return + + attackCode = int(attackCodeStr) + + into = entry.getIntoNodePath(); + self.zapLocalToon(attackCode, into) + + + def createBlock(self, x1, y1, z1, x2, y2, z2, r=1.0, g=1.0, b=1.0, a=1.0): + """ + Returns a GeomNode rectangular block with x1,y1,z1 in one corner and x2,y2,z2 in the opposite corner. + r,g,b,a should range from 0 to 1 + """ + + gFormat = GeomVertexFormat.getV3n3cpt2() + myVertexData = GeomVertexData("holds my vertices", gFormat, Geom.UHDynamic) + vertexWriter = GeomVertexWriter(myVertexData, "vertex") + normalWriter = GeomVertexWriter(myVertexData, "normal") + colorWriter = GeomVertexWriter(myVertexData, "color") + texWriter = GeomVertexWriter(myVertexData, "texcoord") + + #setup the vertexs + vertexWriter.addData3f(x1, y1, z1) + vertexWriter.addData3f(x2, y1, z1) + vertexWriter.addData3f(x1, y2, z1) + vertexWriter.addData3f(x2, y2, z1) + vertexWriter.addData3f(x1, y1, z2) + vertexWriter.addData3f(x2, y1, z2) + vertexWriter.addData3f(x1, y2, z2) + vertexWriter.addData3f(x2, y2, z2) + + + for index in range(8): + normalWriter.addData3f(1.0, 1.0, 1.0) + colorWriter.addData4f(r, g, b, a) + texWriter.addData2f(1.0, 1.0) + + + #create geometry and faces + tris=GeomTriangles(Geom.UHDynamic) # triangle obejcet + tris.addVertex(0) #top + tris.addVertex(1) + tris.addVertex(2) + tris.closePrimitive() + + tris.addVertex(1) + tris.addVertex(3) + tris.addVertex(2) + tris.closePrimitive() + + tris.addVertex(2) #front + tris.addVertex(3) + tris.addVertex(6) + tris.closePrimitive() + + tris.addVertex(3) + tris.addVertex(7) + tris.addVertex(6) + tris.closePrimitive() + + tris.addVertex(0) #right + tris.addVertex(2) + tris.addVertex(4) + tris.closePrimitive() + + tris.addVertex(2) + tris.addVertex(6) + tris.addVertex(4) + tris.closePrimitive() + + tris.addVertex(1) #left + tris.addVertex(5) + tris.addVertex(3) + tris.closePrimitive() + + tris.addVertex(3) + tris.addVertex(5) + tris.addVertex(7) + tris.closePrimitive() + + + tris.addVertex(0) #back + tris.addVertex(4) + tris.addVertex(5) + tris.closePrimitive() + + tris.addVertex(1) + tris.addVertex(0) + tris.addVertex(5) + tris.closePrimitive() + + + tris.addVertex(4) #bottom + tris.addVertex(6) + tris.addVertex(7) + tris.closePrimitive() + + tris.addVertex(7) + tris.addVertex(5) + tris.addVertex(4) + tris.closePrimitive() + + + cubeGeom=Geom(myVertexData) + cubeGeom.addPrimitive(tris) + + cubeGN=GeomNode("cube") + cubeGN.addGeom(cubeGeom) + + return cubeGN + + + def __enterDefenseCol(self,entry): + self.notify.debug('__enterDefenseCol') + + def __enterProsecutionCol(self, entry): + self.notify.debug('__enterProsecutionCol') + + + def makeVictoryMovie(self): + """ + Make the victory movie. + """ + + myFromPos = Point3(ToontownGlobals.LawbotBossBattleThreePosHpr[0], + ToontownGlobals.LawbotBossBattleThreePosHpr[1], + ToontownGlobals.LawbotBossBattleThreePosHpr[2]) + myToPos = Point3( myFromPos[0], + myFromPos[1] + 30, + myFromPos[2]) + + + rollThroughDoor = self.rollBossToPoint( + fromPos = myFromPos, fromHpr = None, + toPos = myToPos, toHpr = None, + reverse = 0) + rollTrack = Sequence( + Func(self.getGeomNode().setH, 180), + rollThroughDoor[0], + Func(self.getGeomNode().setH, 0)) + + rollTrackDuration = rollTrack.getDuration() + self.notify.debug('rollTrackDuration = %f' % rollTrackDuration) + + + doorStartPos = self.door3.getPos() + doorEndPos = Point3(doorStartPos[0], doorStartPos[1], doorStartPos[2] + 25) + bossTrack = Track ( + (0.5, Sequence( Func(self.clearChat), + Func(camera.reparentTo, render), + Func(camera.setPos,-3,45,25), + Func(camera.setHpr,0,10,0), + )), + (1.0, Func(self.setChatAbsolute, TTLocalizer.LawbotBossDefenseWins1, CFSpeech)), + (5.5, Func(self.setChatAbsolute, TTLocalizer.LawbotBossDefenseWins2, CFSpeech)), + (9.5, Sequence( Func (camera.wrtReparentTo, render), + )), + (9.6, Parallel( rollTrack, + Func(self.setChatAbsolute, TTLocalizer.LawbotBossDefenseWins3, CFSpeech), + self.door3.posInterval(2, doorEndPos, + startPos = doorStartPos), + )), + (13.1, Sequence(self.door3.posInterval(1, doorStartPos))), + + ) + + retTrack = Parallel(bossTrack, + ActorInterval(self,'Ff_speech',loop=1) + ) + return bossTrack + #return retTrack + + def makeEpilogueMovie(self): + """ + Make the epilogue movie. + """ + + + epSpeech = TTLocalizer.WitnessToonCongratulations + + epSpeech = self.__talkAboutPromotion(epSpeech) + + bossTrack = Sequence ( + Func(self.witnessToon.animFSM.request,'neutral'), + Func(self.witnessToon.setLocalPageChat,epSpeech, 0) + ) + return bossTrack + + + def makeDefeatMovie(self): + """ + Make the defeat movie. + """ + bossTrack = Track ( + (0.0, Sequence( Func(self.clearChat), + Func(self.reverseHead), + ActorInterval(self,'Ff_speech'))), + (1.0, Func(self.setChatAbsolute, TTLocalizer.LawbotBossProsecutionWins, CFSpeech)) + ) + return bossTrack + + def __makeWitnessToon(self): + """ + Make the npc witness. + + dnaNetString came from making a player + type = toon + gender = m + head = bss, torso = ss, legs = m + arm color = 19 + glove color = 0 + leg color = 19 + head color = 19 + top texture = 0 + top texture color = 3 + sleeve texture = 0 + sleeve texture color = 3 + bottom texture = 1 + bottom texture color = 16 + """ + + dnaNetString = 't\x1b\x00\x01\x01\x00\x03\x00\x03\x01\x10\x13\x00\x13\x13' + + npc = Toon.Toon() + npc.setDNAString( dnaNetString) + npc.setName(TTLocalizer.WitnessToonName) + npc.setPickable(0) + npc.setPlayerType(NametagGroup.CCNonPlayer) + #npc.animFSM.request('neutral') + npc.animFSM.request('Sit') + + self.witnessToon = npc + self.witnessToon.setPosHpr(*ToontownGlobals.LawbotBossWitnessStandPosHpr) + + + def __cleanupWitnessToon(self): + """ + Cleanup the witness toon + """ + self.__hideWitnessToon() + if self.witnessToon: + self.witnessToon.removeActive() + self.witnessToon.delete() + self.witnessToon = None + + + def __showWitnessToon(self): + """ + Show the witness toon seated on the witness stand + """ + if not self.witnessToonOnstage: + self.witnessToon.addActive() + self.witnessToon.reparentTo(self.geom) + seatCenter = self.realWitnessStand.find('**/witnessStandSeatEdge') + center = seatCenter.getPos() + self.notify.debug('center = %s' % center) + self.witnessToon.setPos(center) + self.witnessToon.setH(180) + self.witnessToon.setZ(self.witnessToon.getZ() - 1.5) + self.witnessToon.setY(self.witnessToon.getY() - 1.15) + self.witnessToonOnstage = 1 + + def __hideWitnessToon(self): + """ + Hide the witness toon + """ + if self.witnessToonOnstage: + self.witnessToon.removeActive() + self.witnessToon.detachNode() + self.witnessToonOnstage = 0 + + def __hideToons(self): + """ + Hide the toons + """ + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.hide() + + def __showToons(self): + """ + Show the toons + """ + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + toon.show() + + def __arrangeToonsAroundWitnessToon(self): + """ + Arrange the toons around the witness toon + """ + radius = 7 + numToons = len(self.involvedToons) + center = (numToons - 1) / 2.0 + for i in range(numToons): + toon = self.cr.doId2do.get(self.involvedToons[i]) + if toon: + angle = 90 - 15 * (i - center) + + radians = angle * math.pi / 180.0 + x = math.cos(radians) * radius + y = math.sin(radians) * radius + toon.setPos(self.witnessToon, x, y, 0) + toon.headsUp(self.witnessToon) + toon.loop('neutral') + toon.show() + + + def __talkAboutPromotion(self, speech): + """ + # Extends the congratulations speech to talk about the earned + # promotion, if any. Returns the newly-extended speech. + """ + + # don't say anything about a promotion if they've maxed their cog suit + if self.prevCogSuitLevel < ToontownGlobals.MaxCogSuitLevel: + newCogSuitLevel = localAvatar.getCogLevels()[ + CogDisguiseGlobals.dept2deptIndex(self.style.dept)] + # if this is their last promotion, tell them + if newCogSuitLevel == ToontownGlobals.MaxCogSuitLevel: + speech += TTLocalizer.WitnessToonLastPromotion % (ToontownGlobals.MaxCogSuitLevel+1) + # if they're getting another LP, tell them + if newCogSuitLevel in ToontownGlobals.CogSuitHPLevels: + speech += TTLocalizer.WitnessToonHPBoost + else: + # level XX, wow! Thanks for coming back! + speech += TTLocalizer.WitnessToonMaxed % (ToontownGlobals.MaxCogSuitLevel+1) + + return speech + + + def __positionToonsInFrontOfCannons(self): + """ + Put the toons in front of the cannons + """ + self.notify.debug('__positionToonsInFrontOfCannons') + index = 0 + + self.involvedToons.sort() + for toonId in self.involvedToons: + if self.cannons.has_key(index): + cannon = self.cannons[index] + toon = self.cr.doId2do.get(toonId) + + self.notify.debug('cannonId = %d' % cannon.doId) + cannonPos = cannon.nodePath.getPos(render) + self.notify.debug('cannonPos = %s' % cannonPos) + if toon: + self.notify.debug('toon = %s' % toon.getName()) + toon.reparentTo(cannon.nodePath) + toon.setPos(0,8,0) + toon.setH(180) + renderPos = toon.getPos(render) + self.notify.debug('renderPos =%s' % renderPos) + index += 1 + + self.notify.debug('done with positionToons') + + + def __makePrepareBattleTwoMovie(self): + """ + move camera to witness toon and make him talk + """ + chatString = TTLocalizer.WitnessToonPrepareBattleTwo % ToontownGlobals.LawbotBossJurorsForBalancedScale + + movie = Sequence( + Func(camera.reparentTo,self.witnessToon), + Func(camera.setPos,0,8,2), + Func(camera.setHpr,180,10,0), + Func(self.witnessToon.setLocalPageChat,chatString, 0), + +# Wait(10000), + #Func(camera.reparentTo, localAvatar), + #Func(camera.setPos, localAvatar.cameraPositions[0][0]), + #Func(camera.setHpr, 0, 0, 0), + #allCannonsAppear, + ) + return movie + + def __doWitnessPrepareBattleThreeChat(self): + """ + Talk about the jury result, and advice, and bonus weight + """ + self.notify.debug('__doWitnessPrepareBattleThreeChat: original self.numToonJurorsSeated = %d' % self.numToonJurorsSeated) + self.countToonJurors() + self.notify.debug("after calling self.countToonJurors, numToonJurorsSeated=%d" % self.numToonJurorsSeated) + if self.numToonJurorsSeated == 0: + juryResult = TTLocalizer.WitnessToonNoJuror + elif self.numToonJurorsSeated == 1: + juryResult = TTLocalizer.WitnessToonOneJuror + elif self.numToonJurorsSeated == 12: + juryResult = TTLocalizer.WitnessToonAllJurors + else: + juryResult = TTLocalizer.WitnessToonSomeJurors % self.numToonJurorsSeated + + juryResult += '\a' + + trialSpeech = juryResult + + trialSpeech += TTLocalizer.WitnessToonPrepareBattleThree + + #insert the text about the bonus weight from toon jurors + diffSettings = ToontownGlobals.LawbotBossDifficultySettings[self.battleDifficulty] + if diffSettings[4]: + #ok so we have toon jurors affecting the weight + newWeight, self.bonusWeight, self.numJurorsLocalToonSeated = self.calculateWeightOfToon(base.localAvatar.doId) + if self.bonusWeight > 0: + if self.bonusWeight == 1: + juryWeightBonus = TTLocalizer.WitnessToonJuryWeightBonusSingular.get(self.battleDifficulty) + else: + juryWeightBonus = TTLocalizer.WitnessToonJuryWeightBonusPlural.get(self.battleDifficulty) + if juryWeightBonus: + weightBonusText = juryWeightBonus % (self.numJurorsLocalToonSeated, self.bonusWeight) + trialSpeech += '\a' + trialSpeech += weightBonusText + + + + + self.witnessToon.setLocalPageChat(trialSpeech,0) + + + def __makePrepareBattleThreeMovie(self): + """ + move camera to witness toon and make him talk + """ + + movie = Sequence( + Func(camera.reparentTo, render), + Func(camera.setPos, -15, 15, 20), + Func(camera.setHpr, -90,0,0), + Wait(3), + Func(camera.reparentTo,self.witnessToon), + Func(camera.setPos,0,8,2), + Func(camera.setHpr,180,10,0), + Func(self.__doWitnessPrepareBattleThreeChat), + #Func(self.witnessToon.setLocalPageChat,trialSpeech, 0) + +# Wait(10000), +# Func(camera.reparentTo, localAvatar), +# Func(camera.setPos, localAvatar.cameraPositions[0][0]), +# Func(camera.setHpr, 0, 0, 0), + ) + return movie + + def countToonJurors(self): + """ + run through all the chairs we have and count how many are toons + """ + self.numToonJurorsSeated = 0 + for key in self.chairs.keys(): + chair = self.chairs[key] + if chair.state == 'ToonJuror' or \ + (chair.state == None and chair.newState == 'ToonJuror'): + self.numToonJurorsSeated += 1 + #self.notify.debug('self.numToonJurorsSeated = %d' % self.numToonJurorsSeated) + + self.notify.debug('self.numToonJurorsSeated = %d' % self.numToonJurorsSeated) + + def cleanupPanFlash(self): + """ + Cleanup the blue flash + """ + if self.panFlashInterval: + self.panFlashInterval.finish() + self.panFlashInterval = None + + def flashPanBlue(self): + """ + # Flash the pan blue to emphasize that it's been hit. + """ + self.cleanupPanFlash() + + intervalName = "FlashPanBlue" + self.defensePanNodePath.setColorScale(1, 1, 1, 1) + seq = Sequence(self.defensePanNodePath.colorScaleInterval(0.1, colorScale = VBase4(0, 0, 1, 1)), + self.defensePanNodePath.colorScaleInterval(0.3, colorScale = VBase4(1, 1, 1, 1)), + name = intervalName) + self.panFlashInterval = seq + seq.start() + self.storeInterval(seq, intervalName) + + def saySomething(self, chatString): + """ + Make the CJ say something + """ + intervalName = "ChiefJusticeTaunt" + seq = Sequence( name = intervalName) + seq.append(Func(self.setChatAbsolute, chatString, CFSpeech)) + seq.append( Wait(4.0) ) + seq.append(Func(self.clearChat)) + oldSeq = self.activeIntervals.get(intervalName) + if oldSeq: + oldSeq.finish() + seq.start() + self.storeInterval(seq, intervalName) + + + def setTaunt(self, tauntIndex, extraInfo): + """ + Make the CJ do a taunt + """ + gotError = False + if not hasattr(self, 'state'): + self.notify.warning('returning from setTaunt, no attr state') + gotError = True + else: + if not self.state == 'BattleThree': + self.notify.warning('returning from setTaunt, not in battle three state, state=%s',self.state) + gotError = True + + if not hasattr(self, 'nametag'): + self.notify.warning('returning from setTaunt, no attr nametag') + gotError = True + + if gotError: + st = StackTrace() + print st + return + + + chatString = TTLocalizer.LawbotBossTaunts[1] + if tauntIndex == 0: + if extraInfo < len(self.involvedToons): + toonId = self.involvedToons[extraInfo] + toon = base.cr.doId2do.get(toonId) + if toon: + chatString = TTLocalizer.LawbotBossTaunts[tauntIndex] % toon.getName() + else: + chatString = TTLocalizer.LawbotBossTaunts[tauntIndex] + + self.saySomething(chatString) + + + + def toonGotHealed(self, toonId): + """ + A toon got healed, play a sound effect + """ + toon = base.cr.doId2do.get(toonId) + if toon: + base.playSfx(self.toonUpSfx, node = toon) + + def hideBonusTimer(self): + """ + Hide the bonus timer + """ + if self.bonusTimer: + self.bonusTimer.hide() + + def enteredBonusState(self): + """ + We've entered the bonus timer + """ + self.witnessToon.clearChat() + text = TTLocalizer.WitnessToonBonus %( + ToontownGlobals.LawbotBossBonusWeightMultiplier, + ToontownGlobals.LawbotBossBonusDuration) + self.witnessToon.setChatAbsolute(text, CFSpeech | CFTimeout) + + base.playSfx(self.toonUpSfx) + + #setup bonus timer + if not self.bonusTimer: + self.bonusTimer = ToontownTimer.ToontownTimer() + self.bonusTimer.posInTopRightCorner() + self.bonusTimer.show() + self.bonusTimer.countdown(ToontownGlobals.LawbotBossBonusDuration, self.hideBonusTimer) + + + def setAttackCode(self, attackCode, avId = 0): + """ + Give the toons a warning sound, and a different taunt + """ + DistributedBossCog.DistributedBossCog.setAttackCode(self,attackCode, avId) + if attackCode == ToontownGlobals.BossCogAreaAttack: + self.saySomething(TTLocalizer.LawbotBossAreaAttackTaunt) + base.playSfx(self.warningSfx) + + def setBattleDifficulty(self, diff): + """ + We got the battle difficulty from the AI + """ + self.notify.debug('battleDifficulty = %d' % diff) + self.battleDifficulty = diff + + + def toonEnteredCannon(self, toonId, cannonIndex): + """ + Gets called from DistributedLawbotCannon, keep track which + cannon a toon entered + """ + if base.localAvatar.doId == toonId: + self.cannonIndex = cannonIndex + + def numJurorsSeatedByCannon( self, cannonIndex): + """ + how many jurors were seated by a certain cannon + """ + retVal = 0 + for chair in self.chairs.values(): + if chair.state == "ToonJuror": + if chair.toonJurorIndex == cannonIndex: + retVal +=1 + return retVal + + + def calculateWeightOfToon(self, toonId): + """ + calculate evidence weight each toon throws, Warning this code + is duplicated on the server side, update that too if we change this. + """ + defaultWeight = 1 + bonusWeight = 0 + newWeight = 1 + cannonIndex = self.cannonIndex + numJurors = 0 + if not cannonIndex == None and cannonIndex >= 0: + diffSettings = ToontownGlobals.LawbotBossDifficultySettings[self.battleDifficulty] + if diffSettings[4]: + numJurors = self.numJurorsSeatedByCannon( cannonIndex) + bonusWeight = numJurors - diffSettings[5] + if bonusWeight < 0: + bonusWeight = 0 + + + newWeight = defaultWeight + bonusWeight + self.notify.debug('toon %d has weight of %d' % (toonId, newWeight)) + + return newWeight, bonusWeight, numJurors diff --git a/toontown/src/suit/DistributedLawbotBossAI.py b/toontown/src/suit/DistributedLawbotBossAI.py new file mode 100644 index 0000000..d7ad725 --- /dev/null +++ b/toontown/src/suit/DistributedLawbotBossAI.py @@ -0,0 +1,1441 @@ +from otp.ai.AIBaseGlobal import * +from direct.distributed.ClockDelta import * +import DistributedBossCogAI +from direct.directnotify import DirectNotifyGlobal +from otp.avatar import DistributedAvatarAI +import DistributedSuitAI +from toontown.battle import BattleExperienceAI +from direct.fsm import FSM +from toontown.toonbase import ToontownGlobals +from toontown.toon import InventoryBase +from toontown.toonbase import TTLocalizer +from toontown.battle import BattleBase +from toontown.toon import NPCToons +from toontown.building import SuitBuildingGlobals +import SuitDNA +import random +from toontown.coghq import DistributedLawbotBossGavelAI +from toontown.suit import DistributedLawbotBossSuitAI +from toontown.coghq import DistributedLawbotCannonAI +from toontown.coghq import DistributedLawbotChairAI +from toontown.toonbase import ToontownBattleGlobals + +class DistributedLawbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLawbotBossAI') + + # The maximum number of hits we will take while dizzy, once our + # damage crosses the given threshold. + limitHitCount = 6 + hitCountDamage = 35 + + # The number of pies we award for touching the cage. + #numPies = ToontownGlobals.FullPies + numPies = 10 + + #the cap we use for the toon level difficulty + maxToonLevels = 77 + + def __init__(self, air): + DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'l') + FSM.FSM.__init__(self, 'DistributedLawbotBossAI') + + # These are the prosecution lawyers + self.lawyers = [] + + self.cannons = None + self.chairs = None + + self.gavels = None + + + # Choose an NPC toon to be in the cage. + self.cagedToonNpcId = random.choice(NPCToons.npcFriends.keys()) + + self.bossMaxDamage = ToontownGlobals.LawbotBossMaxDamage + self.recoverRate = 0 + self.recoverStartTime = 0 + + + # Accumulated hits on the boss during the climactic final + # battle (battle three) + self.bossDamage = ToontownGlobals.LawbotBossInitialDamage + + #are we using cannons in battle two + self.useCannons = 1 + + #how many toon jurors did we seat in battle two + self.numToonJurorsSeated = 0 + + self.cannonBallsLeft = {} + + self.toonLevels = 0 + + #keep track in the server log when they lose the trial + if not 'Defeat' in self.keyStates: + self.keyStates.append('Defeat') + + self.toonupValue = 1 + + self.bonusState = False + self.bonusTimeStarted = 0 + self.numBonusStates = 0 + self.battleThreeTimeStarted = 0 + self.battleThreeTimeInMin = 0 + self.numAreaAttacks = 0 + self.lastAreaAttackTime = 0 + + self.weightPerToon = {} #each toons evidence weight can be different + self.cannonIndexPerToon = {} #keep track which cannon a toon used + + self.battleDifficulty = 0 #how difficult is battle three + + def delete(self): + self.notify.debug('DistributedLawbotBossAI.delete') + self.__deleteBattleThreeObjects() + self.__deleteBattleTwoObjects() + + taskName = self.uniqueName('clearBonus') + taskMgr.remove(taskName) + + return DistributedBossCogAI.DistributedBossCogAI.delete(self) + + + def getHoodId(self): + return ToontownGlobals.LawbotHQ + + def getCagedToonNpcId(self): + return self.cagedToonNpcId + + def magicWordHit(self, damage, avId): + # Called by the magic word "~bossBattle hit damage" + if self.attackCode != ToontownGlobals.BossCogDizzyNow: + # Make him dizzy first. + self.hitBossInsides() + self.hitBoss(damage) + + def hitBoss(self, bossDamage): + # This is sent when the client successfully hits the boss during + # battle three. We have to take the client's word for it here. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBoss(%s, %s)' % (self.doId, avId, bossDamage)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBoss from unknown avatar'): + return + + # We only expect a bossDamage value of 1 from the client. If + # a client ever sends some other value, it's cause for + # immediate and strong suspicion of a hacked client. However, + # we honor the strange bossDamage value, partly to make it + # convenient for testing, and partly to help trap greedy + # hackers into revealing themselves repeatedly. + self.validate(avId, bossDamage == 1, + 'invalid bossDamage %s' % (bossDamage)) + if bossDamage < 1: + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + # This was just a late hit; ignore it. + return + + #if self.attackCode != ToontownGlobals.BossCogDizzyNow: + # The boss wasn't in his vulnerable state, so it doesn't count. + # return + + if bossDamage <= 12: + #change the bossDamage to reflect the bonusWeight + newWeight = self.weightPerToon.get(avId) + if newWeight: + bossDamage = newWeight + + + if self.bonusState and bossDamage <= 12: + #we hit the scale and the bonus state is happening + bossDamage *= ToontownGlobals.LawbotBossBonusWeightMultiplier + + + bossDamage = min(self.getBossDamage() + bossDamage, self.bossMaxDamage) + self.b_setBossDamage(bossDamage, 0, 0) + + if self.bossDamage >= self.bossMaxDamage: + #transition directly to victory state, Lawbot boss does not have NearVictory state while he waits to fall + self.b_setState('Victory') + + else: + self.__recordHit() + + def healBoss(self, bossHeal): + """ + mostly copy and paste from hit boss, without the bossDamage<1 check + """ + bossDamage = -bossHeal + + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBoss(%s, %s)' % (self.doId, avId, bossDamage)) + + #if not self.validate(avId, avId in self.involvedToons, + # 'hitBoss from unknown avatar'): + # return + + # We only expect a bossDamage value of 1 from the client. If + # a client ever sends some other value, it's cause for + # immediate and strong suspicion of a hacked client. However, + # we honor the strange bossDamage value, partly to make it + # convenient for testing, and partly to help trap greedy + # hackers into revealing themselves repeatedly. + #self.validate(avId, bossDamage == 1, + # 'invalid bossDamage %s' % (bossDamage)) + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + # This was just a late hit; ignore it. + return + + #if self.attackCode != ToontownGlobals.BossCogDizzyNow: + # The boss wasn't in his vulnerable state, so it doesn't count. + # return + + bossDamage = min(self.getBossDamage() + bossDamage, self.bossMaxDamage) + bossDamage = max(bossDamage, 0) #make sure the damage is not neg + self.b_setBossDamage(bossDamage, 0, 0) + + #if self.bossDamage >= self.bossMaxDamage: + if self.bossDamage == 0: + # the toons lost + self.b_setState('Defeat') + + else: + self.__recordHit() + + + def hitBossInsides(self): + # This is sent when the client successfully lobs a pie inside + # the boss's chassis. + + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBossInsides(%s)' % (self.doId, avId)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBossInsides from unknown avatar'): + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + # This was just a late hit; ignore it. + return + + self.b_setAttackCode(ToontownGlobals.BossCogDizzyNow) + self.b_setBossDamage(self.getBossDamage(), 0, 0) + + def hitToon(self, toonId): + # This is sent when the client pies another toon during battle + # three. We have to take the client's word for it here too. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitToon(%s, %s)' % (self.doId, avId, toonId)) + + if not self.validate(avId, avId != toonId, + 'hitToon on self'): + return + + if avId not in self.involvedToons or toonId not in self.involvedToons: + # Not an error, since either toon might have just died. + return + + toon = self.air.doId2do.get(toonId) + if toon: + self.healToon(toon, self.toonupValue) + self.sendUpdate('toonGotHealed',[toonId]) + + + def touchCage(self): + # This is sent from the client when he touches the cage, + # requesting more pies. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.touchCage(%s)' % (self.doId, avId)) + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree' and currState != 'NearVictory': + return + + if not self.validate(avId, avId in self.involvedToons, + 'touchCage from unknown avatar'): + return + + toon = simbase.air.doId2do.get(avId) + if toon: + toon.b_setNumPies(self.numPies) + toon.__touchedCage = 1 + #this call would call cagedToonBattleThree which has been nuked + #self.__goodJump(avId) + + def touchWitnessStand(self): + # This is sent from the client when he touches the witness Stand + self.touchCage() + + + + def finalPieSplat(self): + """ + A client reports he observed the final pie splat that sends + the boss over the edge. This moves the state into Victory. + """ + self.notify.debug("finalPieSplat") + if self.state != 'NearVictory': + return + + self.b_setState('Victory') + + def doTaunt(self): + if not self.state == 'BattleThree': + return + + tauntIndex = random.randrange( len(TTLocalizer.LawbotBossTaunts)) + extraInfo = 0; + if tauntIndex == 0 and self.involvedToons: + extraInfo = random.randrange( len(self.involvedToons)) + self.sendUpdate('setTaunt', [tauntIndex, extraInfo]) + pass + + def doNextAttack(self, task): + assert self.notify.debug("%s.doNextAttack()" % (self.doId)) + + #TODO this will make the lawyers move in lock step, make each lawyer go on an independent timer + for lawyer in self.lawyers: + lawyer.doNextAttack(self) + + self.waitForNextAttack(ToontownGlobals.LawbotBossLawyerCycleTime) + timeSinceLastAttack = globalClock.getFrameTime() - self.lastAreaAttackTime + allowedByTime = 15 < timeSinceLastAttack or self.lastAreaAttackTime == 0 + + doAttack = random.randrange(1,101) + self.notify.debug('allowedByTime=%d doAttack=%d' % (allowedByTime,doAttack)) + if doAttack <= ToontownGlobals.LawbotBossChanceToDoAreaAttack and allowedByTime: + self.__doAreaAttack() + self.numAreaAttacks += 1 + self.lastAreaAttackTime = globalClock.getFrameTime() + else: + chanceToDoTaunt = ToontownGlobals.LawbotBossChanceForTaunt + action = random.randrange(1,101) + if action <= chanceToDoTaunt: + self.doTaunt() + pass + + + + #RAU do nothing for now + return; + # Choose an attack and do it. + if self.attackCode == ToontownGlobals.BossCogDizzyNow: + # We always choose this particular attack when recovering + # from dizzy. It's really the same as the front attack, + # with extra time for standing up first. + attackCode = ToontownGlobals.BossCogRecoverDizzyAttack + + else: + # Choose an attack at random. + attackCode = random.choice( + [ToontownGlobals.BossCogAreaAttack, + ToontownGlobals.BossCogFrontAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ]) + + if attackCode == ToontownGlobals.BossCogAreaAttack: + self.__doAreaAttack() + elif attackCode == ToontownGlobals.BossCogDirectedAttack: + self.__doDirectedAttack() + else: + self.b_setAttackCode(attackCode) + + def __doAreaAttack(self): + self.b_setAttackCode(ToontownGlobals.BossCogAreaAttack) + + # Boost the recovery rate a bit with each area attack. + #if self.recoverRate: + # newRecoverRate = min(200, self.recoverRate * 1.2) + #else: + # newRecoverRate = 2 + #now = globalClock.getFrameTime() + #self.b_setBossDamage(self.getBossDamage(), newRecoverRate, now) + + def __doDirectedAttack(self): + if self.nearToons: + toonId = random.choice(self.nearToons) + self.b_setAttackCode(ToontownGlobals.BossCogDirectedAttack, toonId) + + else: + # If we don't have anyone nearby to aim at, stomp in + # frustration. + self.__doAreaAttack() + + def b_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + self.d_setBossDamage(bossDamage, recoverRate, recoverStartTime) + self.setBossDamage(bossDamage, recoverRate, recoverStartTime) + + def setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + assert self.notify.debug('%s.setBossDamage(%s, %s)' % (self.doId, bossDamage, recoverRate)) + self.bossDamage = bossDamage + self.recoverRate = recoverRate + self.recoverStartTime = recoverStartTime + + def getBossDamage(self): + now = globalClock.getFrameTime() + elapsed = now - self.recoverStartTime + + # It is important that we consistently represent bossDamage as + # an integer value, so there is never any chance of client and + # AI disagreeing about whether bossDamage < bossMaxDamage. + return int(max(self.bossDamage - self.recoverRate * elapsed / 60.0, 0)) + + def d_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + #import pdb; pdb.set_trace() + timestamp = globalClockDelta.localToNetworkTime(recoverStartTime) + self.sendUpdate('setBossDamage', [bossDamage, recoverRate, timestamp]) + + def waitForNextStrafe(self, delayTime): + currState = self.getCurrentOrNextState() + if (currState == 'BattleThree'): + assert self.notify.debug("%s.Waiting %s seconds for next strafe." % (self.doId, delayTime)) + taskName = self.uniqueName('NextStrafe') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.doNextStrafe, taskName) + else: + assert self.notify.debug("%s.Not doing another strafe in state %s." % (self.doId, currState)) + + def stopStrafes(self): + taskName = self.uniqueName('NextStrafe') + taskMgr.remove(taskName) + + def doNextStrafe(self, task): + if self.attackCode != ToontownGlobals.BossCogDizzyNow: + side = random.choice([0, 1]) + direction = random.choice([0, 1]) + assert self.notify.debug('%s.doStrafe(%s, %s)' % (self.doId, side, direction)) + self.sendUpdate('doStrafe', [side, direction]) + + # How long to wait for the next strafe? + + delayTime = 9 + self.waitForNextStrafe(delayTime) + + def __sendLawyerIds(self): + lawyerIds = [] + for suit in self.lawyers: + lawyerIds.append(suit.doId) + + self.sendUpdate('setLawyerIds',[lawyerIds]) + + + def d_cagedToonBattleThree(self, index, avId): + self.sendUpdate('cagedToonBattleThree', [index, avId]) + + def formatReward(self): + # Returns the reward indication to write to the event log. + return str(self.cagedToonNpcId) + + def makeBattleOneBattles(self): + self.postBattleState = 'RollToBattleTwo' + self.initializeBattles(1, ToontownGlobals.LawbotBossBattleOnePosHpr) + + def generateSuits(self, battleNumber): + if battleNumber == 1: + # Battle 1 + + # building difficulty 13, first battle with Lawbot Boss. These + # are normal cogs. + weakenedValue = ( ( 1, 1 ), + ( 2, 2 ), + ( 2, 2 ), + ( 1, 1 ), + ( 1, 1, 1, 1, 1 ) ) + listVersion = list(SuitBuildingGlobals.SuitBuildingInfo) + #self.notify.debug("listversion = ") + #print(listversion)) + #self.notify.debug("listVersion[13] = ") + #print listVersion[13] + + if simbase.config.GetBool('lawbot-boss-cheat',0): + listVersion[13] = weakenedValue + SuitBuildingGlobals.SuitBuildingInfo = tuple(listVersion) + return self.invokeSuitPlanner(13, 0) + else: + # Battle 2 + return self.invokeSuitPlanner(13, 1) + + def removeToon(self, avId): + toon = simbase.air.doId2do.get(avId) + if toon: + toon.b_setNumPies(0) + + DistributedBossCogAI.DistributedBossCogAI.removeToon(self, avId) + + + ##### Off state ##### + + def enterOff(self): + self.notify.debug("enterOff") + DistributedBossCogAI.DistributedBossCogAI.enterOff(self) + self.__deleteBattleThreeObjects() + self.__resetLawyers() + + + ##### Elevator state ##### + + def enterElevator(self): + self.notify.debug("enterElevatro") + DistributedBossCogAI.DistributedBossCogAI.enterElevator(self) + self.b_setBossDamage(ToontownGlobals.LawbotBossInitialDamage, 0, 0) + + ##### Introduction state ##### + + def enterIntroduction(self): + self.notify.debug("enterIntroduction") + DistributedBossCogAI.DistributedBossCogAI.enterIntroduction(self) + + self.b_setBossDamage(ToontownGlobals.LawbotBossInitialDamage, 0, 0) + + # We want to have cranes and safes visible in the next room + # for the introduction cutscene. + #self.__makeBattleThreeObjects() + + #we nees to show the jury chairs + self.__makeChairs() + + def exitIntroduction(self): + self.notify.debug("exitIntroduction") + DistributedBossCogAI.DistributedBossCogAI.exitIntroduction(self) + + + ##### BattleOne state ##### + + ##### RollToBattleTwo state ##### + + def enterRollToBattleTwo(self): + assert self.notify.debug('%s.enterRollToBattleTwo()' % (self.doId)) + + # Reshuffle the remaining toons for the second pair of battles. + self.divideToons() + + + # The clients will play a movie showing the boss cog moving up + # to the top of the area for the phase 2 battle. + + #might as well make the cannons in the idle time that we have + self.__makeCannons() + + self.barrier = self.beginBarrier( + "RollToBattleTwo", self.involvedToons, 50, + self.__doneRollToBattleTwo) + + def __doneRollToBattleTwo(self, avIds): + #import pdb; pdb.set_trace() + assert(self.notify.debug('%s.__doneRollToBattleTwo()' % (self.doId))) + self.b_setState("PrepareBattleTwo") + + def exitRollToBattleTwo(self): + #import pdb; pdb.set_trace() + self.ignoreBarrier(self.barrier) + + + ##### PrepareBattleTwo state ##### + + + def enterPrepareBattleTwo(self): + assert self.notify.debug('%s.enterPrepareBattleTwo()' % (self.doId)) + + # The clients will focus in on the caged toon giving some + # encouragement and advice. We wait for the clients to click + # through. + + self.__makeCannons() + + self.barrier = self.beginBarrier( + "PrepareBattleTwo", self.involvedToons, 45, + self.__donePrepareBattleTwo) + + self.makeBattleTwoBattles() + + def __donePrepareBattleTwo(self, avIds): + #import pdb; pdb.set_trace() + self.b_setState("BattleTwo") + + def exitPrepareBattleTwo(self): + #import pdb; pdb.set_trace() + self.ignoreBarrier(self.barrier) + + ##### BattleTwo state ##### + + def __makeCannons(self): + if self.cannons == None: + # Generate all of the cannons. + self.cannons = [] + + startPt = Point3(*ToontownGlobals.LawbotBossCannonPosA) + endPt = Point3(*ToontownGlobals.LawbotBossCannonPosB) + totalDisplacement = endPt - startPt + self.notify.debug('totalDisplacement=%s' % totalDisplacement) + numToons = len(self.involvedToons) + stepDisplacement = totalDisplacement / (numToons + 1) + for index in range(numToons): + newPos = stepDisplacement * (index + 1) + self.notify.debug('curDisplacement = %s' % newPos) + newPos += startPt + self.notify.debug('newPos = %s' % newPos) + cannon = DistributedLawbotCannonAI.DistributedLawbotCannonAI( + self.air, self, index, + newPos[0], newPos[1], newPos[2], -90, 0, 0 ) + cannon.generateWithRequired(self.zoneId) + self.cannons.append(cannon) + + if 0: + for index in range(len(ToontownGlobals.LawbotBossCannonPosHprs)): + posHpr = ToontownGlobals.LawbotBossCannonPosHprs[index] + cannon = DistributedLawbotCannonAI.DistributedLawbotCannonAI(self.air, self, posHpr[0], posHpr[1], posHpr[2], posHpr[3], posHpr[4], posHpr[5] ) + cannon.generateWithRequired(self.zoneId) + self.cannons.append(cannon) + + + + def __makeChairs(self): + if self.chairs == None: + self.chairs = [] + + for index in range(12): + #posHpr = ToontownGlobals.LawbotBossChairPosHprs[index] + chair = DistributedLawbotChairAI.DistributedLawbotChairAI(self.air, self, index) + chair.generateWithRequired(self.zoneId) + #chair.requestEmptyJuror() + self.chairs.append(chair) + + + + def __makeBattleTwoObjects(self): + self.__makeCannons() + self.__makeChairs() + + def __deleteCannons(self): + if self.cannons != None: + for cannon in self.cannons: + cannon.requestDelete() + self.cannons = None + + def __deleteChairs(self): + if self.chairs != None: + for chair in self.chairs: + chair.requestDelete() + self.chairs = None + + def __stopChairs(self): + if self.chairs != None: + for chair in self.chairs: + chair.stopCogs() + + def __deleteBattleTwoObjects(self): + self.__deleteCannons() + self.__deleteChairs() + + + def getCannonBallsLeft(self, avId): + if self.cannonBallsLeft.has_key(avId): + return self.cannonBallsLeft[avId] + else: + self.notify.warning('getCannonBalsLeft invalid avId: %d' % avId) + return 0 + + def decrementCannonBallsLeft( self ,avId): + if self.cannonBallsLeft.has_key(avId): + self.cannonBallsLeft[avId] -= 1 + if self.cannonBallsLeft[avId] < 0: + self.notify.warning('decrementCannonBallsLeft <0 cannonballs for %d' % avId) + self.cannonBallsLeft[avId] = 0 + else: + self.notify.warning('decrementCannonBallsLeft invalid avId: %d' % avId) + + + + def makeBattleTwoBattles(self): + # Create the battle objects. + #self.postBattleState = 'PrepareBattleThree' + self.postBattleState = 'RollToBattleThree' + + if self.useCannons: + self.__makeBattleTwoObjects() + else: + self.initializeBattles(2, ToontownGlobals.LawbotBossBattleTwoPosHpr) + + + + def enterBattleTwo(self): + assert self.notify.debug('%s.enterBattleTwo()' % (self.doId)) + + + if self.useCannons: + self.cannonBallsLeft = {} + for toonId in self.involvedToons: + self.cannonBallsLeft[toonId] = ToontownGlobals.LawbotBossCannonBallMax + + for chair in self.chairs: + chair.requestEmptyJuror() + + + self.barrier = self.beginBarrier( + "BattleTwo", + self.involvedToons, + ToontownGlobals.LawbotBossJuryBoxMoveTime + 1, + self.__doneBattleTwo) + + + # The boss cog unleashes the second round of Cogs from his + # belly. + + if not self.useCannons: + # Begin the battles. + if self.battleA: + self.battleA.startBattle(self.toonsA, self.suitsA) + if self.battleB: + self.battleB.startBattle(self.toonsB, self.suitsB) + + def __doneBattleTwo(self, avIds): + #import pdb; pdb.set_trace() + assert(self.notify.debug('%s.__doneBattleTwo' % (self.doId))) + + if self.useCannons: + #we're already at the judge's spot if we had cannons + self.b_setState('PrepareBattleThree') + else: + self.b_setState("RollToBattleThree") + + + def exitBattleTwo(self): + assert self.notify.debug('%s.exitBattleTwo()' % (self.doId)) + self.resetBattles() + + self.numToonJurorsSeated = 0 + + for chair in self.chairs: + self.notify.debug('chair.state==%s' % chair.state) + if chair.state == 'ToonJuror': + self.numToonJurorsSeated += 1 + + self.notify.debug('numToonJurorsSeated=%d' % self.numToonJurorsSeated) + + #RAU keep track in server log so we can balance later + self.air.writeServerEvent("jurorsSeated", self.doId, "%s|%s|%s" % + (self.dept, self.involvedToons,self.numToonJurorsSeated)) + + #delete the cannons but keep the chairs around + self.__deleteCannons() + #self.__deleteBattleTwoObjects() + + self.__stopChairs() + + ##### RollToBattleThree state ##### + + def enterRollToBattleThree(self): + assert self.notify.debug('%s.enterRollToBattleThree()' % (self.doId)) + + # Reshuffle the remaining toons for the second pair of battles. + self.divideToons() + + # The clients will play a movie showing the boss cog moving up + # to the top of the area for the phase 2 battle. + + #import pdb; pdb.set_trace() + + self.barrier = self.beginBarrier( + "RollToBattleThree", self.involvedToons, 20, + self.__doneRollToBattleThree) + + #import pdb; pdb.set_trace() + + def __doneRollToBattleThree(self, avIds): + #import pdb; pdb.set_trace() + assert(self.notify.debug('%s.__doneRollToBattleThree()' % (self.doId))) + self.b_setState("PrepareBattleThree") + + def exitRollToBattleThree(self): + #import pdb; pdb.set_trace() + self.ignoreBarrier(self.barrier) + + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + """ + The clients will focus in on the witness toon giving some + encouragement and advice. We wait for the clients to click + through. + """ + + assert self.notify.debug('%s.enterPrepareBattleThree()' % (self.doId)) + + #warning the battle difficulty could conceivably change if + #someone drops out between this state and the BattleThree state + #this would only matter if you switch from having toon jurors + #affect your evidence weight to not, or vice versa. We could take + #battle diffculty only from this state, but I think it's better + #that it's recalculated on BattleThree + self.calcAndSetBattleDifficulty() + + + self.barrier = self.beginBarrier( + "PrepareBattleThree", self.involvedToons, 45, + self.__donePrepareBattleThree) + + def __donePrepareBattleThree(self, avIds): + self.b_setState("BattleThree") + + def exitPrepareBattleThree(self): + self.ignoreBarrier(self.barrier) + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('%s.enterBattleThree()' % (self.doId)) + + self.battleThreeTimeStarted = globalClock.getFrameTime() + self.calcAndSetBattleDifficulty() + + self.calculateWeightPerToon(); + + diffSettings = ToontownGlobals.LawbotBossDifficultySettings[self.battleDifficulty] + self.ammoCount = diffSettings[0] + self.numGavels = diffSettings[1] + if self.numGavels >= len (ToontownGlobals.LawbotBossGavelPosHprs): + self.numGavels = len(ToontownGlobals.LawbotBossGavelPosHprs) + self.numLawyers = diffSettings[2] + if self.numLawyers >= len (ToontownGlobals.LawbotBossLawyerPosHprs): + self.numLawyers = len (ToontownGlobals.LawbotBossLawyerPosHprs) + + self.toonupValue = diffSettings[3] + + + + self.notify.debug("diffLevel=%d ammoCount=%d gavels=%d lawyers = %d, toonup=%d" % + (self.battleDifficulty, self.ammoCount, + self.numGavels, self.numLawyers, self.toonupValue) + ) + + #RAU keep track in server log so we can balance later + self.air.writeServerEvent("lawbotBossSettings", self.doId, "%s|%s|%s|%s|%s|%s" % + (self.dept, self.battleDifficulty, self.ammoCount, + self.numGavels, self.numLawyers, + self.toonupValue) + ) + + + + self.__makeBattleThreeObjects() + + #Make the prosecution lawyers + self.__makeLawyers() + + self.numPies = self.ammoCount + + self.resetBattles() + self.setPieType() + + jurorsOver = self.numToonJurorsSeated - ToontownGlobals.LawbotBossJurorsForBalancedScale + #dmgAdjust could be negative if they do badly in battle two + dmgAdjust = jurorsOver * ToontownGlobals.LawbotBossDamagePerJuror + self.b_setBossDamage(ToontownGlobals.LawbotBossInitialDamage + dmgAdjust, 0, 0) + # make it close to losing if needed + if simbase.config.GetBool('lawbot-boss-cheat',0): + pass + self.b_setBossDamage(ToontownGlobals.LawbotBossMaxDamage - 1, 0, 0) + + + self.battleThreeStart = globalClock.getFrameTime() + + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.__touchedCage = 0 + + # lets just play with one for now + #self.gavels[0].turnOn() + for aGavel in self.gavels: + aGavel.turnOn() + + self.waitForNextAttack(5) + + + self.notify.debug('battleDifficulty = %d' % self.battleDifficulty) + + self.numToonsAtStart = len(self.involvedToons) + + def getToonDifficulty(self): + """ + Get the difficulty factor based on just the toons + """ + + highestCogSuitLevel = 0 + totalCogSuitLevels = 0.0 + totalNumToons = 0.0 + + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toonLevel = toon.getNumPromotions(self.dept) + totalCogSuitLevels += toonLevel + totalNumToons += 1 + if (toon.cogLevels > highestCogSuitLevel): + highestCogSuitLevel = toonLevel + + if not totalNumToons: + totalNumToons = 1.0 + + averageLevel = totalCogSuitLevels / totalNumToons + self.notify.debug('toons average level = %f, highest level = %d' % (averageLevel, highestCogSuitLevel)) + + #put a cap on it, otherwise we could go as high as 90 + retval = min (averageLevel, self.maxToonLevels) + return retval + + def __saySomething(self, task = None): + # The caged toon looks for something to say. + index = None + avId = 0 + + if len(self.involvedToons) == 0: + return + + # Choose a random Toon to "address" from the toons + # involved. If that toon has touched the cage, give him + # the next bit of advice; otherwise, admonish him. + avId = random.choice(self.involvedToons) + toon = simbase.air.doId2do.get(avId) + if toon.__touchedCage: + if self.cagedToonDialogIndex <= TTLocalizer.CagedToonBattleThreeMaxAdvice: + index = self.cagedToonDialogIndex + self.cagedToonDialogIndex += 1 + else: + # We've used up all the advice. Occasionally pick one at + # random to remind everyone. + if random.random() < 0.2: + index = random.randrange(100, TTLocalizer.CagedToonBattleThreeMaxAdvice + 1) + + else: + # The toon hasn't touched the cage yet. Tell him how to. + index = random.randrange(20, TTLocalizer.CagedToonBattleThreeMaxTouchCage + 1) + + if index: + self.d_cagedToonBattleThree(index, avId) + + self.__saySomethingLater() + + def __saySomethingLater(self, delayTime = 15): + # Say something in a few seconds. + taskName = self.uniqueName('CagedToonSaySomething') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.__saySomething, taskName) + + def __goodJump(self, avId): + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + return + + index = random.randrange(10, TTLocalizer.CagedToonBattleThreeMaxGivePies + 1) + self.d_cagedToonBattleThree(index, avId) + + self.__saySomethingLater() + + def __makeBattleThreeObjects(self): + if self.gavels == None: + # Generate all of the gavels. + self.gavels = [] + for index in range(self.numGavels): + gavel= DistributedLawbotBossGavelAI.DistributedLawbotBossGavelAI(self.air, self, index) + gavel.generateWithRequired(self.zoneId) + self.gavels.append(gavel) + + def __deleteBattleThreeObjects(self): + if self.gavels != None: + for gavel in self.gavels: + gavel.request('Off') + gavel.requestDelete() + self.gavels = None + + def doBattleThreeInfo(self): + didTheyWin = 0 + if self.bossDamage == ToontownGlobals.LawbotBossMaxDamage: + didTheyWin = 1 + + self.battleThreeTimeInMin = globalClock.getFrameTime() - self.battleThreeTimeStarted + self.battleThreeTimeInMin /= 60.0 + + + self.numToonsAtEnd = 0; + toonHps = [] + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + self.numToonsAtEnd += 1 + toonHps.append (toon.hp) + + + self.air.writeServerEvent( + "b3Info", self.doId, + "%d|%.2f|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%s|%s" % + (didTheyWin, + self.battleThreeTimeInMin, + self.numToonsAtStart, + self.numToonsAtEnd, + self.numToonJurorsSeated, + self.battleDifficulty, + self.ammoCount, + self.numGavels, + self.numLawyers, + self.toonupValue, + self.numBonusStates, + self.numAreaAttacks, + toonHps, + self.weightPerToon + ) + ) + + def exitBattleThree(self): + self.doBattleThreeInfo() + + self.stopAttacks() + self.stopStrafes() + taskName = self.uniqueName('CagedToonSaySomething') + taskMgr.remove(taskName) + self.__resetLawyers() + + self.__deleteBattleThreeObjects() + #if self.gavels != None: + # for gavel in self.gavels: + # gavel.request('Off') + + + + ##### NearVictory state ##### + + def enterNearVictory(self): + assert self.notify.debug('%s.enterNearVictory()' % (self.doId)) + self.resetBattles() + + def exitNearVictory(self): + pass + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('%s.enterVictory()' % (self.doId)) + + + + self.resetBattles() + + # add a suit-defeat entry for the VP + # based on code in DistributedBattleBaseAI.__movieDone + self.suitsKilled.append({ + 'type' : None, + 'level' : None, + 'track' : self.dna.dept, + 'isSkelecog' : 0, + 'isForeman' : 0, + 'isVP' : 1, + 'isCFO' : 0, + 'isSupervisor' : 0, + 'isVirtual' : 0, + 'activeToons' : self.involvedToons[:], + }) + + self.barrier = self.beginBarrier( + "Victory", self.involvedToons, 30, + self.__doneVictory) + + def __doneVictory(self, avIds): + + # Tell the client the information it needs to generate a + # reward movie. + self.d_setBattleExperience() + + # First, move the clients into the reward start. They'll + # build the reward movies immediately. + self.b_setState("Reward") + + # Now that the clients have started to build their reward + # movies, we can actually assign all the experience and + # rewards. If we did this sooner, the quest reward panel + # would show the rewards being applied twice. + + # There is no race condition between AI and client here + # because these messages are sent sequentially on the wire. + + BattleExperienceAI.assignRewards( + self.involvedToons, self.toonSkillPtsGained, + self.suitsKilled, + ToontownGlobals.dept2cogHQ(self.dept), self.helpfulToons) + + + # Don't forget to give the toon the cog summon reward and the + # promotion! + preferredDept = random.randrange(len(SuitDNA.suitDepts)) + typeWeights = ['single'] * 70 + \ + ['building'] * 27 + \ + ['invasion'] * 3 + preferredSummonType = random.choice(typeWeights) + + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + self.giveCogSummonReward(toon, preferredDept, preferredSummonType) + toon.b_promote(self.deptIndex) + + def giveCogSummonReward(self, toon, prefDeptIndex, prefSummonType): + """ + Try to make sure we don't give a duplicate reward to a toon. + If all else fails give a totally random one + """ + + #we could also make cogLevel random + cogLevel = int ( self.toonLevels / self.maxToonLevels * SuitDNA.suitsPerDept) + cogLevel = min (cogLevel, SuitDNA.suitsPerDept -1) #at cap we can get an invalid cogLevel + deptIndex = prefDeptIndex + summonType = prefSummonType + + + hasSummon = toon.hasParticularCogSummons(prefDeptIndex, cogLevel, prefSummonType) + if (hasSummon): + #lets find another reward he can use + self.notify.debug('trying to find another reward') + if not toon.hasParticularCogSummons(prefDeptIndex, cogLevel, 'single'): + summonType = 'single' + elif not toon.hasParticularCogSummons(prefDeptIndex, cogLevel, 'building'): + summonType = 'building' + elif not toon.hasParticularCogSummons(prefDeptIndex, cogLevel, 'invasion'): + summonType = 'invasion' + else: + #varying the summon type didn't work + foundOne = False + for curDeptIndex in range (len (SuitDNA.suitDepts)): + if not toon.hasParticularCogSummons(curDeptIndex, cogLevel, prefSummonType): + deptIndex = curDeptIndex + foundOne = True + break + elif not toon.hasParticularCogSummons(curDeptIndex, cogLevel, 'single'): + deptIndex = curDeptIndex + summonType = 'single' + foundOne = True + break + elif not toon.hasParticularCogSummons(curDeptIndex, cogLevel, 'building'): + deptIndex = curDeptIndex + summonType = 'building' + foundOne = True + break + elif not toon.hasParticularCogSummons(curDeptIndex, cogLevel, 'invasion'): + summonType = 'invasion' + deptIndex = curDeptIndex + foundOne = True + break + + possibleCogLevel = range(SuitDNA.suitsPerDept) + possibleDeptIndex = range (len(SuitDNA.suitDepts)) + possibleSummonType = ['single','building','invasion'] + + typeWeights = ['single'] * 70 + \ + ['building'] * 27 + \ + ['invasion'] * 3 + + #lets try a random search 5 times + if not foundOne: + for i in range(5): + randomCogLevel = random.choice(possibleCogLevel) + randomSummonType = random.choice(typeWeights) + randomDeptIndex = random.choice (possibleDeptIndex) + if not toon.hasParticularCogSummons(randomDeptIndex, + randomCogLevel, + randomSummonType): + foundOne = True + cogLevel = randomCogLevel + summonType = randomSummonType + deptIndex = randomDeptIndex + assert(self.notify.debug('found on random try %d' % i)) + break + + #exhaustively search through everything and try to find one + for curType in possibleSummonType: + if foundOne: + break + for curCogLevel in possibleCogLevel: + if foundOne: + break + for curDeptIndex in possibleDeptIndex: + if foundOne: + break + if not toon.hasParticularCogSummons(curDeptIndex, + curCogLevel, + curType): + foundOne = True + cogLevel = curCogLevel + summonType = curType + deptIndex = curDeptIndex + assert(self.notify.debug('found on exhaustive search')) + + + + if not foundOne: + #totally give up, make it random + cogLevel = None + summonType = None + deptIndex = None + assert ( self.notify.debug("couldn't find a good reward") ) + else: + assert ( self.notify.debug('reward cogLevel=%d deptIndex=%d summonType=%s' % (cogLevel, deptIndex, summonType))) + pass + + + + + + toon.assignNewCogSummons(cogLevel, summonType, deptIndex) + + def exitVictory(self): + self.takeAwayPies() + + ##### Defeat state ##### + + def enterDefeat(self): + assert self.notify.debug('%s.enterDefeat()' % (self.doId)) + self.resetBattles() + + self.barrier = self.beginBarrier( + "Defeat", self.involvedToons, 10, + self.__doneDefeat) + + def __doneDefeat(self, avIds): + + #hmmm, just set their health to zero? + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + toon.b_setHp(0) + + def exitDefeat(self): + self.takeAwayPies() + + + + ##### Frolic state ##### + + def enterFrolic(self): + DistributedBossCogAI.DistributedBossCogAI.enterFrolic(self) + self.b_setBossDamage(0, 0, 0) + + + def setPieType(self): + # Sets everyone's pie type for the battle. + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + # a pie type of 7 should indicate that it should switch to the evidence button + + toon.d_setPieType(ToontownBattleGlobals.MAX_TRACK_INDEX + 1) + + def takeAwayPies(self): + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.b_setNumPies(0) + + def __recordHit(self): + # Records that the boss has been hit, and counts the number of + # hits in a period of time. + now = globalClock.getFrameTime() + + self.hitCount += 1 + if (self.hitCount < self.limitHitCount or self.bossDamage < self.hitCountDamage): + assert self.notify.debug("%s. %s hits, ignoring." % (self.doId, self.hitCount)) + return + + assert self.notify.debug("%s. %s hits!" % (self.doId, self.hitCount)) + + # Launch an immediate front attack. + #this doesn't look good + #self.b_setAttackCode(ToontownGlobals.BossCogRecoverDizzyAttack) + + #import pdb; pdb.set_trace() + #toonId = self.involvedToons[0] + #self.b_setAttackCode(ToontownGlobals.BossCogDirectedAttack, toonId) + + + + def __resetLawyers(self): + # Free the suits made with an earlier call to __makeLawyers(). + for suit in self.lawyers: + #suit.boss = None + suit.requestDelete() + self.lawyers = [] + + def __makeLawyers(self): + """ + # Generate a handful of suits that we can see the Boss Cog + # promoting as we come in. These shouldn't really need to be + # true DistributedSuits, but I tried to make just a Suit by + # itself and it just doesn't work. It doesn't do any real + # harm to make DistributedSuits, anyway. + """ + + self.__resetLawyers() + + #bottom feeder is body type c. and throw-paper anim is not included in ttmodels/built + lawCogChoices = [ "b", "dt", "ac", "bs", "sd", "le", "bw"] + + # 8 suits, 4 on each side. + for i in range(self.numLawyers): + suit = DistributedLawbotBossSuitAI.DistributedLawbotBossSuitAI(self.air, None) + + # And a random suit type + suit.dna = SuitDNA.SuitDNA() + lawCog = random.choice(lawCogChoices) + suit.dna.newSuit(lawCog) + + suit.setPosHpr( *ToontownGlobals.LawbotBossLawyerPosHprs[i]) + suit.setBoss(self) + + suit.generateWithRequired(self.zoneId) + self.lawyers.append(suit) + + self.__sendLawyerIds() + + + def hitChair(self, chairIndex, npcToonIndex): + """ + This is sent when the client successfully hits one of the juror chairs from a cannon + battle three. We have to take the client's word for it here. + """ + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitChair(%s, %s)' % (self.doId, avId, chairIndex)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitChair from unknown avatar'): + return + + if not self.chairs: + #potentially a late hit, just ignore + return + + if chairIndex <0 or chairIndex >= len(self.chairs): + self.notify.warning('invalid chairIndex = %d' % chairIndex) + return + + if not self.state == 'BattleTwo': + #a late hit, just ignore + return + + self.chairs[chairIndex].b_setToonJurorIndex(npcToonIndex) + self.chairs[chairIndex].requestToonJuror() + + def clearBonus(self, taskName): + if self and hasattr(self,'bonusState'): + self.bonusState = False + + def startBonusState(self): + self.notify.debug('startBonusState') + self.bonusTimeStarted = globalClock.getFrameTime() + self.bonusState = True + self.numBonusStates += 1 + #heal the toons + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + self.healToon(toon, ToontownGlobals.LawbotBossBonusToonup) + + #clear it after a certain amount of time + taskMgr.doMethodLater(ToontownGlobals.LawbotBossBonusDuration , self.clearBonus,self.uniqueName('clearBonus')) + + self.sendUpdate('enteredBonusState',[]) + + def areAllLawyersStunned(self): + for lawyer in self.lawyers: + if not lawyer.stunned: + return False + return True + + + def checkForBonusState(self): + """ + Whenever a lawyer gets stunned, see if we enter the bonus state + """ + if self.bonusState: + #if we're already in the bonus state, do nothing + return + + if not self.areAllLawyersStunned(): + return + + curTime = globalClock.getFrameTime() + delta = curTime - self.bonusTimeStarted; + + if ToontownGlobals.LawbotBossBonusWaitTime < delta: + self.startBonusState() + + def toonEnteredCannon(self, toonId, cannonIndex): + """ + Gets called from DistributedLawbotCannonAI, keep track which + cannon a toon entered + """ + self.cannonIndexPerToon[toonId] = cannonIndex + pass + + def numJurorsSeatedByCannon( self, cannonIndex): + """ + how many jurors were seated by a certain cannon + """ + retVal = 0 + for chair in self.chairs: + if chair.state == "ToonJuror": + if chair.toonJurorIndex == cannonIndex: + retVal +=1 + return retVal + + + + def calculateWeightPerToon(self): + """ + calculate evidence weight each toon throws, Warning this code + is duplicated on the client side, update that too if we change this. + """ + for toonId in self.involvedToons: + defaultWeight = 1 + bonusWeight = 0 + cannonIndex = self.cannonIndexPerToon.get(toonId) + if not cannonIndex == None: + diffSettings = ToontownGlobals.LawbotBossDifficultySettings[self.battleDifficulty] + if diffSettings[4]: + bonusWeight = self.numJurorsSeatedByCannon( cannonIndex) - diffSettings[5] + if bonusWeight < 0: + bonusWeight = 0 + + + newWeight = defaultWeight + bonusWeight + self.weightPerToon[toonId] = newWeight + self.notify.debug('toon %d has weight of %d' % (toonId, newWeight)) + + + def b_setBattleDifficulty(self, batDiff): + self.setBattleDifficulty(batDiff) + self.d_setBattleDifficulty(batDiff) + + def setBattleDifficulty(self, batDiff): + self.battleDifficulty = batDiff + + def d_setBattleDifficulty(self, batDiff): + self.sendUpdate('setBattleDifficulty', [batDiff]) + + def calcAndSetBattleDifficulty(self): + self.toonLevels = self.getToonDifficulty() + numDifficultyLevels = len(ToontownGlobals.LawbotBossDifficultySettings) + battleDifficulty = int ( self.toonLevels / self.maxToonLevels * numDifficultyLevels) + + if battleDifficulty >= numDifficultyLevels: + battleDifficulty = numDifficultyLevels -1 + + self.b_setBattleDifficulty(battleDifficulty) + diff --git a/toontown/src/suit/DistributedLawbotBossSuit.py b/toontown/src/suit/DistributedLawbotBossSuit.py new file mode 100644 index 0000000..07695a8 --- /dev/null +++ b/toontown/src/suit/DistributedLawbotBossSuit.py @@ -0,0 +1,770 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +import DistributedSuitBase +from toontown.toonbase import ToontownGlobals +from toontown.battle import MovieUtil + +class DistributedLawbotBossSuit(DistributedSuitBase.DistributedSuitBase): + """ + These are meant to represent the lawyers in battle three of Lawbot Boss + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedLawbotBossSuit') + + timeToShow = 1.0 #how much time to wait before we show evidence + timeToRelease = 3.15 #how much time to wait before evidence flies off to toon or pan + throwPaperEndTime = 4.33 + + + def __init__(self, cr): + """__init__(cr)""" + + self.flyingEvidenceTrack = None + + try: + self.DistributedSuit_initialized + except: + self.DistributedSuit_initialized = 1 + DistributedSuitBase.DistributedSuitBase.__init__(self, cr) + + self.activeIntervals = {} + self.boss = None + + # Set up the DistributedSuit state machine + self.fsm = ClassicFSM.ClassicFSM( + 'DistributedLawbotBossSuit', + [State.State('Off', + self.enterOff, + self.exitOff, + ['Walk', + 'Battle', + 'neutral']), + State.State('Walk', + self.enterWalk, + self.exitWalk, + ['WaitForBattle', + 'Battle'] + ), + State.State('Battle', + self.enterBattle, + self.exitBattle, + []), + State.State('neutral', + self.enterNeutral, + self.exitNeutral, + ['PreThrowProsecute', + 'PreThrowAttack', + 'Stunned'] + ), + State.State('PreThrowProsecute', + self.enterPreThrowProsecute, + self.exitPreThrowProsecute, + ['PostThrowProsecute', + 'neutral', + 'Stunned'] + ), + State.State('PostThrowProsecute', + self.enterPostThrowProsecute, + self.exitPostThrowProsecute, + ['neutral', + 'Stunned'] + ), + State.State('PreThrowAttack', + self.enterPreThrowAttack, + self.exitPreThrowAttack, + ['PostThrowAttack', + 'neutral', + 'Stunned'] + ), + State.State('PostThrowAttack', + self.enterPostThrowAttack, + self.exitPostThrowAttack, + ['neutral', + 'Stunned'] + ), + State.State('Stunned', + self.enterStunned, + self.exitStunned, + ['neutral'] + ), + State.State('WaitForBattle', + self.enterWaitForBattle, + self.exitWaitForBattle, + ['Battle']), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + + self.fsm.enterInitialState() + + return None + + def generate(self): + self.notify.debug('DLBS.generate:') + DistributedSuitBase.DistributedSuitBase.generate(self) + + def announceGenerate(self): + DistributedSuitBase.DistributedSuitBase.announceGenerate(self) + self.notify.debug('DLBS.announceGenerate') + + colNode = self.find('**/distAvatarCollNode*') + colNode.setTag('pieCode',str(ToontownGlobals.PieCodeLawyer)) + + self.attackEvidenceA = self.getEvidence(True) + self.attackEvidenceB = self.getEvidence(True) + self.attackEvidence = self.attackEvidenceA + + + + self.prosecuteEvidence = self.getEvidence(False) + #self.setState('neutral') + + self.hideName() + self.setPickable(False) + + def disable(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject + // is removed from active duty and stored in a cache. + // Parameters: + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.notify.debug("DistributedSuit %d: disabling" % self.getDoId()) + self.setState('Off') + DistributedSuitBase.DistributedSuitBase.disable(self) + self.cleanupIntervals() + + + #del self.boss.gavels[self.index] + + self.boss = None + + + return + + def delete(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // permanently removed from the world and deleted from + // the cache. + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + try: + self.DistributedSuit_deleted + except: + self.DistributedSuit_deleted = 1 + self.notify.debug("DistributedSuit %d: deleting" % self.getDoId()) + + del self.fsm + DistributedSuitBase.DistributedSuitBase.delete(self) + return + + def d_requestBattle(self, pos, hpr): + """d_requestBattle(toonId) + """ + # Make sure the local toon can't continue to run around (and + # potentially start battles with other suits!) + self.cr.playGame.getPlace().setState('WaitForBattle') + self.sendUpdate('requestBattle', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2]]) + return None + + def __handleToonCollision(self, collEntry): + """ + ///////////////////////////////////////////////////////////// + // Function: This function is the callback for any + // collision events that the collision sphere + // for this bad guy might receive + // Parameters: collEntry, the collision entry object + // Changes: None + ///////////////////////////////////////////////////////////// + """ + + toonId = base.localAvatar.getDoId() + self.notify.debug('Distributed suit: requesting a Battle with ' + + 'toon: %d' % toonId) + self.d_requestBattle(self.getPos(), self.getHpr()) + + # the suit on this machine only will go into wait for battle while it + # is waiting for word back from the server about our battle request + # + self.setState('WaitForBattle') + + return None + + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### Off state ##### + + # Defined in DistributedSuitBase.py + + ##### Walk state ##### + + def enterWalk(self): + self.notify.debug("enterWalk") + self.enableBattleDetect('walk', self.__handleToonCollision) + # Just stand here waiting for a toon to approach. + self.loop('walk', 0) + pathPoints = [Vec3(50, 15, 0), + Vec3(50, 25, 0), + Vec3(20, 25, 0), + Vec3(20, 15, 0), + Vec3(50, 15, 0), + ] + self.tutWalkTrack = self.makePathTrack(self, pathPoints, + 4.5, "tutFlunkyWalk") + self.tutWalkTrack.loop() + + def exitWalk(self): + self.notify.debug("exitWalk") + self.disableBattleDetect() + self.tutWalkTrack.pause() + self.tutWalkTrack = None + return + + ##### Battle state ##### + + # Defined in DistributedSuitBase.py + + + ##### Neutral state ##### + + def enterNeutral(self): + self.notify.debug("enterNeutral") + # Get ready to pass through a door. + self.notify.debug('DistributedLawbotBossSuit: Neutral') + #self.resumePath(0) + self.loop('neutral', 0) + + + def exitNeutral(self): + self.notify.debug("exitNeutral") + return + + ##### WaitForBattle state ##### + + + ##### WaitForBattle state ##### + + # Defined in DistributedSuitBase.py + + def doAttack(self, x1, y1, z1, x2,y2,z2): + self.notify.debug("x1=%.2f y1=%.2f z2=%.2f x2=%.2f y2=%.2f z2=%.2f" % (x1,y1,z1,x2,y2,z2)) + #import pdb; pdb.set_trace() + + self.curTargetPt = Point3(x2,y2,z2) + self.fsm.request('PreThrowAttack') + return + + attackEvidence = self.getEvidence(True) + + #nodePath = self.nodePath + nodePath = render + + node = nodePath.attachNewNode('attackEvidence-%s' % self.doId) + node.setPos(x1,y1,z1) + #attackEvidence.reparentTo(node) + + duration = 3.0; #decrease this to make it the books fly faster + + throwName = self.uniqueName('lawyerAttack') + + throwingSeq = self.makeAttackThrowingTrack(attackEvidence, duration, Point3(x2,y2,z2)) + + fullSequence = Sequence( + throwingSeq, + name = throwName + ) + + + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + pass + + + def doProsecute(self): + self.notify.debug('doProsecute') + #import pdb; pdb.set_trace() + + + bounds = self.boss.prosecutionColNodePath.getBounds() + panCenter = bounds.getCenter(); + localPos = panCenter + prosecutionPanPos = render.getRelativePoint(self.boss.prosecutionColNodePath,localPos) + self.curTargetPt = prosecutionPanPos + + self.fsm.request('PreThrowProsecute') + return + + + attackEvidence = self.getEvidence(False) + + #nodePath = self.nodePath + nodePath = render + + node = nodePath.attachNewNode('prosecuteEvidence-%s' % self.doId) + node.setPos(self.getPos()) + #attackEvidence.reparentTo(node) + + duration = ToontownGlobals.LawbotBossLawyerToPanTime; #decrease this to make it the books fly faster + + + throwName = self.uniqueName('lawyerProsecute') + + """ + evidenceSeq = Sequence( + Func(node.show), + Parallel( + node.posInterval( duration , prosecutionPanPos, fluid = 1), + #node.hprInterval(1, VBase3(720,0,0), fluid = 1) + ), + Func(node.detachNode), + Func(self.boss.flashGreen), + Func(self.clearInterval, throwName) + ) + """ + #throwingSeq = self.makeThrowingTrack(node, duration, prosecutionPanPos) + throwingSeq = self.makeProsecuteThrowingTrack(attackEvidence, duration, prosecutionPanPos) + + + + fullSequence = Sequence( + throwingSeq, + Func(self.boss.flashGreen), + Func(self.clearInterval,throwName), + name = throwName + ) + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + def makeDummySequence(self): + #import pdb; pdb.set_trace() + retval =Sequence(Wait(10)) + return retval + + def makeProsecuteThrowingTrack(self, evidence, inFlightDuration, hitPos): + + suitTrack = Sequence() + suitTrack.append(ActorInterval(self,'throw-paper')) + + throwPaperDuration = suitTrack.getDuration(); + #self.notify.debug('throwPaperDuration=%f' % throwPaperDuration) + + inFlight = Parallel( + evidence.posInterval(inFlightDuration,hitPos,fluid=1) + ) + + origHpr = self.getHpr() + self.headsUp(hitPos) + newHpr = self.getHpr() + self.setHpr(origHpr) + rotateTrack = Sequence( + self.hprInterval( self.timeToShow, newHpr, fluid = 1) + ) + + + propTrack = Sequence( + Func(evidence.hide), + Func(evidence.setPos,0,0.5,-0.3), #do some jiggerring to center it on the hand + Func(evidence.reparentTo, self.getRightHand()), + Wait(self.timeToShow), + Func(evidence.show), + Wait(self.timeToRelease - self.timeToShow), + Func(evidence.wrtReparentTo, render), + Func(self.makeDummySequence), + inFlight, + Func(evidence.detachNode) + ) + + + throwingTrack = Parallel(suitTrack, propTrack, rotateTrack) + #suitTrackDuration = suitTrack.getDuration() + #propTrackDuration = propTrack.getDuration() + #throwingTrackDuration = throwingTrack.getDuration() + + #self.notify.debug("suit=%f prop=%f throwing=%f" % + # (suitTrackDuration, propTrackDuration,throwingTrackDuration)) + return throwingTrack + + def makeAttackThrowingTrack(self, evidence, inFlightDuration, hitPos): + suitTrack = Sequence() + suitTrack.append(ActorInterval(self,'throw-paper')) + + throwPaperDuration = suitTrack.getDuration(); + #self.notify.debug('throwPaperDuration=%f' % throwPaperDuration) + + origHpr = self.getHpr() + self.headsUp(hitPos) + newHpr = self.getHpr() + self.setHpr(origHpr) + rotateTrack = Sequence( + self.hprInterval( self.timeToShow, newHpr, fluid = 1) + ) + + propTrack = Sequence( + Func(evidence.hide), + Func(evidence.setPos,0,0.5,-0.3), #do some jiggerring to center it on the hand + Func(evidence.reparentTo, self.getRightHand()), + Wait(self.timeToShow), + Func(evidence.show), + Wait(self.timeToRelease - self.timeToShow), + Func(evidence.wrtReparentTo, render), + Func(evidence.setZ,1.3), #lower so big cogs like legal eagle won't be firing too high + evidence.posInterval(inFlightDuration,hitPos,fluid=1), + Func(evidence.detachNode) + ) + + + throwingTrack = Parallel(suitTrack, propTrack, rotateTrack) + #suitTrackDuration = suitTrack.getDuration() + #propTrackDuration = propTrack.getDuration() + #throwingTrackDuration = throwingTrack.getDuration() + + #self.notify.debug("suit=%f prop=%f throwing=%f" % + # (suitTrackDuration, propTrackDuration,throwingTrackDuration)) + return throwingTrack + + + def makePreThrowAttackTrack(self, evidence, inFlightDuration, hitPos): + suitTrack = Sequence() + suitTrack.append(ActorInterval(self,'throw-paper', endTime=self.timeToRelease)) + + throwPaperDuration = suitTrack.getDuration(); + #self.notify.debug('throwPaperDuration=%f' % throwPaperDuration) + + origHpr = self.getHpr() + self.headsUp(hitPos) + newHpr = self.getHpr() + self.setHpr(origHpr) + rotateTrack = Sequence( + self.hprInterval( self.timeToShow, newHpr, fluid = 1) + ) + + propTrack = Sequence( + Func(evidence.hide), + Func(evidence.setPos,0,0.5,-0.3), #do some jiggerring to center it on the hand + Func(evidence.setScale,1), + Func(evidence.setHpr,0,0,0), + Func(evidence.reparentTo, self.getRightHand()), + Wait(self.timeToShow), + Func(evidence.show), + Wait(self.timeToRelease - self.timeToShow), + ) + + + throwingTrack = Parallel(suitTrack, propTrack, rotateTrack) + #suitTrackDuration = suitTrack.getDuration() + #propTrackDuration = propTrack.getDuration() + #throwingTrackDuration = throwingTrack.getDuration() + + #self.notify.debug("suit=%f prop=%f throwing=%f" % + + return throwingTrack + + def makePostThrowAttackTrack(self, evidence, inFlightDuration, hitPos): + """ + will return two tracks, one for the suit, other for the evidence in flight + """ + suitTrack = Sequence() + suitTrack.append(ActorInterval(self,'throw-paper', startTime=self.timeToRelease)) + + propTrack = Sequence( + Func(evidence.wrtReparentTo, render), + Func(evidence.setScale,1), + Func(evidence.show), + Func(evidence.setZ,1.3), #lower so big cogs like legal eagle won't be firing too high + evidence.posInterval(inFlightDuration,hitPos,fluid=1), + Func(evidence.hide) + ) + + return suitTrack, propTrack + + def makePreThrowProsecuteTrack(self, evidence, inFlightDuration, hitPos): + return self.makePreThrowAttackTrack( evidence, inFlightDuration, hitPos) + pass + + + def makePostThrowProsecuteTrack(self, evidence, inFlightDuration, hitPos): + suitTrack = Sequence() + suitTrack.append(ActorInterval(self,'throw-paper', startTime=self.timeToRelease)) + + propTrack = Sequence( + Func(evidence.wrtReparentTo, render), + Func(evidence.setScale,1), + Func(evidence.show), + evidence.posInterval(inFlightDuration,hitPos,fluid=1), + Func(evidence.hide) + ) + + return suitTrack, propTrack + + pass + + + def getEvidence(self, usedForAttack = False): + model = loader.loadModel('phase_5/models/props/lawbook') + + if usedForAttack: + bounds = model.getBounds() + center = bounds.getCenter() + radius = bounds.getRadius() + + sphere = CollisionSphere(center.getX(),center.getY(),center.getZ(), radius) + colNode = CollisionNode('BossZap') + colNode.setTag('attackCode',str(ToontownGlobals.BossCogLawyerAttack)) + colNode.addSolid(sphere) + + #self.evidenceNodePath = model.attachNewNode(colNode) + model.attachNewNode(colNode) + + #make it ghostly to explain why it passes through the scale =) + model.setTransparency(1) + model.setAlphaScale(0.5) + + return model + + + def cleanupIntervals(self): + for interval in self.activeIntervals.values(): + interval.finish() + self.activeIntervals = {} + + def clearInterval(self, name, finish=1): + """ Clean up the specified Interval + """ + if (self.activeIntervals.has_key(name)): + ival = self.activeIntervals[name] + if finish: + ival.finish() + else: + ival.pause() + if self.activeIntervals.has_key(name): + del self.activeIntervals[name] + else: + self.notify.debug('interval: %s already cleared' % name) + + + def setBossCogId(self, bossCogId): + self.bossCogId = bossCogId + + # This would be risky if we had toons entering the zone during + # a battle--but since all the toons are always there from the + # beginning, we can be confident that the BossCog has already + # been generated by the time we receive the generate for its + # associated battles. + self.boss = base.cr.doId2do[bossCogId] + + def doStun(self): + self.notify.debug('doStun') + self.fsm.request('Stunned') + + + def enterPreThrowProsecute(self): + assert(self.notify.debug('enterPreThrowProsecute')) + + #just in case he got stunned + #self.attackEvidence.hide() + + duration = ToontownGlobals.LawbotBossLawyerToPanTime; #decrease this to make it the books fly faster + throwName = self.uniqueName('preThrowProsecute') + + + preThrowTrack = self.makePreThrowProsecuteTrack(self.prosecuteEvidence,duration, self.curTargetPt) + + #postThrowTrack, self.flyingEvidenceTrack = self.makePostThrowProsecuteTrack( + # attackEvidence,duration, self.curTargetPt) + + fullSequence = Sequence( + preThrowTrack, + Func(self.requestStateIfNotInFlux,'PostThrowProsecute'), + #Func(self.fsm.request,'PostThrowProsecute'), + #Parallel(postThrowTrack, self.flyingEvidenceTrack), + name = throwName, + ) + + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + return + + def exitPreThrowProsecute(self): + assert(self.notify.debug('exitPreThrowProsecute')) + throwName = self.uniqueName('preThrowProsecute') + if (self.activeIntervals.has_key(throwName)): + #self.activeIntervals[throwName].finish() + self.activeIntervals[throwName].pause() + del self.activeIntervals[throwName] + return + + + def enterPostThrowProsecute(self): + assert(self.notify.debug('enterPostThrowProsecute')) + duration = ToontownGlobals.LawbotBossLawyerToPanTime; #decrease this to make it the books fly faster + throwName = self.uniqueName('postThrowProsecute') + + + postThrowTrack, self.flyingEvidenceTrack = self.makePostThrowProsecuteTrack( + self.prosecuteEvidence,duration, self.curTargetPt) + + #waitKludgeTime = 1.0 + fullSequence = Sequence( + postThrowTrack, + #Wait(waitKludgeTime), + Func(self.requestStateIfNotInFlux,'neutral'), + #Func(self.fsm.request,'neutral'), + name = throwName, + ) + + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + flyName = self.uniqueName('flyingEvidence') + self.activeIntervals[flyName] = self.flyingEvidenceTrack + self.flyingEvidenceTrack.append(Func(self.finishedWithFlying,'prosecute')) + self.flyingEvidenceTrack.start() + return + + + def exitPostThrowProsecute(self): + assert(self.notify.debug('exitPostThrowProsecute')) + + #RAU note that we do not stop the flyingEvidenceTrack + throwName = self.uniqueName('postThrowProsecute') + if (self.activeIntervals.has_key(throwName)): + self.activeIntervals[throwName].finish() + del self.activeIntervals[throwName] + return + + def requestStateIfNotInFlux(self, state): + if not self.fsm._ClassicFSM__internalStateInFlux: + self.fsm.request(state) + + def enterPreThrowAttack(self): + assert(self.notify.debug('enterPreThrowAttack')) + + #just in case he got stunned + #self.prosecuteEvidence.hide() + + if self.attackEvidence == self.attackEvidenceA: + self.attackEvidence = self.attackEvidenceB + else: + self.attackEvidence = self.attackEvidenceA + + + duration = 3.0; #decrease this to make it the books fly faster + + + throwName = self.uniqueName('preThrowAttack') + + + preThrowTrack = self.makePreThrowAttackTrack(self.attackEvidence,duration, self.curTargetPt) + + #postThrowTrack, self.flyingEvidenceTrack = self.makePostThrowAttackTrack( + # attackEvidence,duration, self.curTargetPt) + + fullSequence = Sequence( + preThrowTrack, + Func(self.requestStateIfNotInFlux,'PostThrowAttack'), + #Func(self.fsm.request,'PostThrowAttack'), + #Parallel(postThrowTrack, self.flyingEvidenceTrack), + name = throwName, + ) + + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + return + + def exitPreThrowAttack(self): + assert(self.notify.debug('exitPreThrowAttack')) + throwName = self.uniqueName('preThrowAttack') + if (self.activeIntervals.has_key(throwName)): + #self.activeIntervals[throwName].finish() + self.activeIntervals[throwName].pause() + del self.activeIntervals[throwName] + return + + def enterPostThrowAttack(self): + assert(self.notify.debug('enterPostThrowAttack')) + #attackEvidence = self.getEvidence(True) + duration = 3.0; #decrease this to make it the books fly faster + + throwName = self.uniqueName('postThrowAttack') + + + postThrowTrack, self.flyingEvidenceTrack = self.makePostThrowAttackTrack( + self.attackEvidence,duration, self.curTargetPt) + + #waitKludgeTime = ToontownGlobals.LawbotBossLawyerCycleTime - self.throwPaperEndTime - 0.1 + fullSequence = Sequence( + postThrowTrack, + #Wait(waitKludgeTime), + Func(self.requestStateIfNotInFlux,'neutral'), + #Func(self.fsm.request,'neutral'), + name = throwName, + ) + + self.notify.debug('duration of postThrowAttack = %f' % fullSequence.getDuration()) + + self.activeIntervals[throwName] = fullSequence + fullSequence.start() + + flyName = self.uniqueName('flyingEvidence') + self.activeIntervals[flyName] = self.flyingEvidenceTrack + self.flyingEvidenceTrack.append(Func(self.finishedWithFlying,'attack')) + self.flyingEvidenceTrack.start() + return + + def finishedWithFlying(self, str): + self.notify.debug('finished flyingEvidenceTrack %s' % str) + + def exitPostThrowAttack(self): + assert(self.notify.debug('exitPostThrowAttack')) + #RAU note that we do not stop the flyingEvidenceTrack + throwName = self.uniqueName('postThrowAttack') + if (self.activeIntervals.has_key(throwName)): + self.activeIntervals[throwName].finish() + del self.activeIntervals[throwName] + return + + def enterStunned(self): + assert(self.notify.debug('enterStunned')) + + stunSequence = MovieUtil.createSuitStunInterval(self, 0, ToontownGlobals.LawbotBossLawyerStunTime) + #seqName = self.uniqueName('stunSequence') + seqName = stunSequence.getName() + stunSequence.append(Func(self.fsm.request,'neutral')) + self.activeIntervals[seqName] = stunSequence + stunSequence.start() + + return + + def exitStunned(self): + assert(self.notify.debug('exitStunned')) + self.prosecuteEvidence.hide() + self.attackEvidence.hide() + return + + diff --git a/toontown/src/suit/DistributedLawbotBossSuitAI.py b/toontown/src/suit/DistributedLawbotBossSuitAI.py new file mode 100644 index 0000000..da9a18c --- /dev/null +++ b/toontown/src/suit/DistributedLawbotBossSuitAI.py @@ -0,0 +1,309 @@ +from otp.ai.AIBaseGlobal import * +from direct.distributed.ClockDelta import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +from toontown.toonbase import ToontownGlobals +import DistributedSuitBaseAI +import random +from direct.fsm import ClassicFSM, State +from direct.fsm import State + +class DistributedLawbotBossSuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI): + """ + These are meant to represent the lawyers in battle three of Lawbot Boss + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedLawbotBossSuitAI') + + def __init__(self, air, suitPlanner): + """__init__(air, suitPlanner)""" + DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air, + suitPlanner) + self.stunned = False + self.timeToRelease = 3.15 #should match DistributedLawbotBossSuit.py + self.timeProsecuteStarted = 0 + + + # Set up the DistributedSuit state machine + self.fsm = ClassicFSM.ClassicFSM( + 'DistributedLawbotBossSuitAI', + [State.State('Off', + self.enterOff, + self.exitOff, + ['neutral'] + ), + State.State('neutral', + self.enterNeutral, + self.exitNeutral, + ['PreThrowProsecute', + 'PreThrowAttack', + 'Stunned'] + ), + State.State('PreThrowProsecute', + self.enterPreThrowProsecute, + self.exitPreThrowProsecute, + ['PostThrowProsecute', + 'neutral', + 'Stunned'] + ), + State.State('PostThrowProsecute', + self.enterPostThrowProsecute, + self.exitPostThrowProsecute, + ['neutral', + 'Stunned'] + ), + State.State('PreThrowAttack', + self.enterPreThrowAttack, + self.exitPreThrowAttack, + ['PostThrowAttack', + 'neutral', + 'Stunned'] + ), + State.State('PostThrowAttack', + self.enterPostThrowAttack, + self.exitPostThrowAttack, + ['neutral', + 'Stunned'] + ), + State.State('Stunned', + self.enterStunned, + self.exitStunned, + ['neutral'] + ), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + + self.fsm.enterInitialState() + + + def delete(self): + self.notify.debug("delete %s" % self.doId) + self.ignoreAll() + DistributedSuitBaseAI.DistributedSuitBaseAI.delete(self) + self.notify.debug('setting self.boss to None') + self.boss = None + + taskName = self.uniqueName('ProsecutionHealsBoss') + if taskMgr.hasTaskNamed(taskName): + self.notify.debug('still has task %s' % taskName) + taskMgr.remove(taskName) + + taskName = self.uniqueName('unstun') + if taskMgr.hasTaskNamed(taskName): + self.notify.debug('still has task %s' % taskName) + taskMgr.remove(taskName) + + self.fsm = None + + def requestBattle(self, x, y, z, h, p, r): + """requestBattle(x, y, z, h, p, r) + """ + toonId = self.air.getAvatarIdFromSender() + + if self.notify.getDebug(): + self.notify.debug( str( self.getDoId() ) + \ + str( self.zoneId ) + \ + ': request battle with toon: %d' % toonId ) + + # Store the suit's actual pos and hpr on the client + self.confrontPos = Point3(x, y, z) + self.confrontHpr = Vec3(h, p, r) + + # Request a battle from the suit planner + if (self.sp.requestBattle(self.zoneId, self, toonId)): + self.acceptOnce(self.getDeathEvent(), self._logDeath, [toonId]) + if self.notify.getDebug(): + self.notify.debug( "Suit %d requesting battle in zone %d" % + (self.getDoId(), self.zoneId) ) + else: + # Suit tells toon to get lost + if self.notify.getDebug(): + self.notify.debug('requestBattle from suit %d - denied by battle manager' % (self.getDoId())) + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + self.d_denyBattle( toonId ) + + def getPosHpr(self): + return (self.getX(), self.getY(), self.getZ(), self.getH(), self.getP(), self.getR()) + + def getConfrontPosHpr(self): + """ getConfrontPosHpr() + """ + return (self.confrontPos, self.confrontHpr) + + def _logDeath(self, toonId): + pass + #self.air.writeServerEvent('beatFirstCog', toonId, '') + + def doNextAttack(self, lawbotBoss): + """ + Should we try to hit a toon, or throw stuff on the prosecution evidence. All random for now + """ + + if self.stunned: + return + + chanceToDoAttack = ToontownGlobals.LawbotBossLawyerChanceToAttack + action = random.randrange(1,101) + if action > chanceToDoAttack: + #we throw stuff at the prosecution pan + self.doProsecute() + pass + else: + #we hit a toon + if not lawbotBoss.involvedToons: + #do an immediate return, no more toons around + return + + + toonToAttackId = random.choice(lawbotBoss.involvedToons) + toon = self.air.doId2do.get(toonToAttackId) + + if not toon: + self.doProsecute() + return + + #import pdb; pdb.set_trace() + toonPos = toon.getPos(); + z2 = toonPos[2] + 1.3 #adjust it slightly higher so we're not aiming for the feet + + + + toonPos = Point3(toonPos.getX(), toonPos.getY(), 0) + + lawyerPos = self.getPos(); + + lawyerPos = Point3(self.getPos().getX(), self.getPos().getY(), 0) + + #force the lawyer z to be the same + #lawyerPos[2] += 1.3 + + dirVector = toonPos - lawyerPos + dirVector.normalize() + dirVector *= 200; #TODO change this to the maximum length inside the court room + + destPos = Point3(lawyerPos[0] + dirVector[0], lawyerPos[1] + dirVector[1], lawyerPos[2] + dirVector[2] + 1.3) + + self.d_doAttack(lawyerPos[0], lawyerPos[1], lawyerPos[2], destPos[0], destPos[1], destPos[2] ) + + #self.d_doAttack(lawyerPos[0], lawyerPos[1], lawyerPos[2], toonPos[0], toonPos[1], toonPos[2] ) + + + def doProsecute(self): + self.notify.debug("doProsecute") + + self.timeProsecuteStarted = globalClockDelta.getRealNetworkTime() + + self.d_doProsecute() + #do the heal boss here + taskName = self.uniqueName('ProsecutionHealsBoss') + + duration = 5.65 #derived from looking at throwing track duration in DistributedLawbotBossSuit + taskMgr.doMethodLater(duration , self.__prosecutionHeal, taskName) + + def __prosecutionHeal(self, extraArg): + self.notify.debug("__prosecutionHeal extraArg %s" % extraArg) + if self.boss: + self.boss.healBoss(ToontownGlobals.LawbotBossLawyerHeal) + + + def d_doProsecute(self): + self.notify.debug('d_doProsecute') + self.sendUpdate('doProsecute',[]) + + def d_doAttack(self, x1,y1,z1, x2,y2,z2): + self.notify.debug("doAttack: x1=%.2f y1=%.2f z2=%.2f x2=%.2f y2=%.2f z2=%.2f" % (x1,y1,z1,x2,y2,z2)) + self.sendUpdate('doAttack', [x1, y1,z1,x2,y2,z2]) + + def setBoss(self, lawbotBoss): + self.boss = lawbotBoss + + def hitByToon(self): + self.notify.debug("I got hit by a toon") + if not self.stunned: + #remove the task to heal the boss if we get hit before we throw the evidence + curTime = globalClockDelta.getRealNetworkTime() + deltaTime = curTime - self.timeProsecuteStarted + deltaTime /= 100.0 #convert milliseconds to seconds + self.notify.debug('deltaTime = %f, curTime=%f, prosecuteStarted=%f' % + (deltaTime, curTime, self.timeProsecuteStarted)) + if deltaTime < self.timeToRelease: + taskName = self.uniqueName('ProsecutionHealsBoss') + taskMgr.remove(taskName) + + self.sendUpdate('doStun',[]) + self.setStun(True) + taskName = self.uniqueName('unstun') + taskMgr.doMethodLater(ToontownGlobals.LawbotBossLawyerStunTime , self.unStun,taskName) + if self.boss: + self.boss.checkForBonusState() + + def setStun(self, val): + self.stunned = val + + def unStun(self, taskName): + self.setStun(False) + + + + def enterPreThrowProsecute(self): + assert(self.notify.debug('enterPreThrowProsecute')) + return + + def exitPreThrowProsecute(self): + assert(self.notify.debug('exitPreThrowProsecute')) + return + + def enterPostThrowProsecute(self): + assert(self.notify.debug('enterPostThrowProsecute')) + return + + def exitPostThrowProsecute(self): + assert(self.notify.debug('exitPostThrowProsecute')) + return + + def enterPreThrowAttack(self): + assert(self.notify.debug('enterPreThrowAttack')) + return + + def exitPreThrowAttack(self): + assert(self.notify.debug('exitPreThrowAttack')) + return + + def enterPostThrowAttack(self): + assert(self.notify.debug('enterPostThrowAttack')) + return + + def exitPostThrowAttack(self): + assert(self.notify.debug('exitPostThrowAttack')) + return + + def enterStunned(self): + assert(self.notify.debug('enterStunned')) + return + + def exitStunned(self): + assert(self.notify.debug('exitStunned')) + return + + + def enterOff(self): + assert(self.notify.debug('enterOff')) + return + + def exitOff(self): + assert(self.notify.debug('exitOff')) + return + + def enterNeutral(self): + assert(self.notify.debug('enterNeutral')) + return + + def exitNeutral(self): + assert(self.notify.debug('exitNeutral')) + return diff --git a/toontown/src/suit/DistributedMintSuit.py b/toontown/src/suit/DistributedMintSuit.py new file mode 100644 index 0000000..6d736a0 --- /dev/null +++ b/toontown/src/suit/DistributedMintSuit.py @@ -0,0 +1,5 @@ +from toontown.suit import DistributedFactorySuit +from direct.directnotify import DirectNotifyGlobal + +class DistributedMintSuit(DistributedFactorySuit.DistributedFactorySuit): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedMintSuit') diff --git a/toontown/src/suit/DistributedMintSuitAI.py b/toontown/src/suit/DistributedMintSuitAI.py new file mode 100644 index 0000000..77157bd --- /dev/null +++ b/toontown/src/suit/DistributedMintSuitAI.py @@ -0,0 +1,15 @@ +from toontown.suit import DistributedFactorySuitAI +from direct.directnotify import DirectNotifyGlobal + +class DistributedMintSuitAI(DistributedFactorySuitAI.DistributedFactorySuitAI): + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedMintSuitAI') + + def isForeman(self): + return 0 + + def isSupervisor(self): + return self.boss + + def isVirtual(self): + return 0 diff --git a/toontown/src/suit/DistributedSellbotBoss.py b/toontown/src/suit/DistributedSellbotBoss.py new file mode 100644 index 0000000..1f9aba9 --- /dev/null +++ b/toontown/src/suit/DistributedSellbotBoss.py @@ -0,0 +1,1893 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from toontown.battle.BattleProps import * +from direct.distributed.ClockDelta import * +from direct.showbase.PythonUtil import Functor +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.fsm import FSM +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +import DistributedBossCog +from toontown.toonbase import TTLocalizer +import SuitDNA +from toontown.toon import Toon +from toontown.battle import BattleBase +from direct.directutil import Mopath +from direct.showutil import Rope +from toontown.distributed import DelayDelete +from toontown.battle import MovieToonVictory +from toontown.building import ElevatorUtils +from toontown.battle import RewardPanel +from toontown.toon import NPCToons +from direct.task import Task +import random +import math +from toontown.coghq import CogDisguiseGlobals + +# This pointer keeps track of the one DistributedSellbotBoss that +# should appear within the avatar's current visibility zones. If +# there is more than one DistributedSellbotBoss visible to a client at +# any given time, something is wrong. +OneBossCog = None + +class DistributedSellbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSellbotBoss') + + # The cage slowly drops from the ceiling to the floor as the + # battle progresses. + cageHeights = [100, 81, 63, 44, 25, 18] + + def __init__(self, cr): + DistributedBossCog.DistributedBossCog.__init__(self, cr) + FSM.FSM.__init__(self, 'DistributedSellbotBoss') + + self.cagedToonNpcId = None + self.doobers = [] + self.dooberRequest = None + self.bossDamage = 0 + self.attackCode = None + self.attackAvId = 0 + self.recoverRate = 0 + self.recoverStartTime = 0 + self.bossDamageMovie = None + self.cagedToon = None + self.cageShadow = None + self.cageIndex = 0 + self.everThrownPie = 0 + self.battleThreeMusicTime = 0 + self.insidesANodePath = None + self.insidesBNodePath = None + + self.rampA = None + self.rampB = None + self.rampC = None + + self.strafeInterval = None + self.onscreenMessage = None + + self.bossMaxDamage = ToontownGlobals.SellbotBossMaxDamage + + def announceGenerate(self): + DistributedBossCog.DistributedBossCog.announceGenerate(self) + # at this point all our attribs have been filled in. + self.setName(TTLocalizer.SellbotBossName) + nameInfo = TTLocalizer.BossCogNameWithDept % { + "name": self.name, + "dept": (SuitDNA.getDeptFullname(self.style.dept)), + } + self.setDisplayName(nameInfo) + + self.cageDoorSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_SOS_cage_door.mp3') + self.cageLandSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_SOS_cage_land.mp3') + self.cageLowerSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_SOS_cage_lower.mp3') + self.piesRestockSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_SOS_pies_restock.mp3') + self.rampSlideSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_ramp_slide.mp3') + # We need a different copy of the sfx for each strafe disk. + self.strafeSfx = [] + for i in range(10): + self.strafeSfx.append(loader.loadSfx('phase_3.5/audio/sfx/SA_shred.mp3')) + + # Anything in the world we hit that's *not* the BossCog + # inherits this global pieCode, so the splat will be colored + # gray. + render.setTag('pieCode', str(ToontownGlobals.PieCodeNotBossCog)) + + # Put some polys inside the boss to detect when a pie gets + # inside. That will make the boss dizzy. + + insidesA = CollisionPolygon( + Point3(4.0, -2.0, 5.0), Point3(-4.0, -2.0, 5.0), + Point3(-4.0, -2.0, 0.5), Point3(4.0, -2.0, 0.5)) + insidesANode = CollisionNode('BossZap') + insidesANode.addSolid(insidesA) + insidesANode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask) + self.insidesANodePath = self.axle.attachNewNode(insidesANode) + self.insidesANodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossInsides)) + self.insidesANodePath.stash() + + insidesB = CollisionPolygon( + Point3(-4.0, 2.0, 5.0), Point3(4.0, 2.0, 5.0), + Point3(4.0, 2.0, 0.5), Point3(-4.0, 2.0, 0.5)) + insidesBNode = CollisionNode('BossZap') + insidesBNode.addSolid(insidesB) + insidesBNode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask) + self.insidesBNodePath = self.axle.attachNewNode(insidesBNode) + self.insidesBNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossInsides)) + self.insidesBNodePath.stash() + + # Make another bubble--a tube--to serve as a target in battle + # three. + target = CollisionTube(0, -1, 4, 0, -1, 9, 3.5) + targetNode = CollisionNode('BossZap') + targetNode.addSolid(target) + targetNode.setCollideMask(ToontownGlobals.PieBitmask) + self.targetNodePath = self.pelvis.attachNewNode(targetNode) + self.targetNodePath.setTag('pieCode', str(ToontownGlobals.PieCodeBossCog)) + + # A similar tube, offset slightly backward, forms a shield + # from behind. We only want to count hits from the front. + shield = CollisionTube(0, 1, 4, 0, 1, 7, 3.5) + shieldNode = CollisionNode('BossZap') + shieldNode.addSolid(shield) + shieldNode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask) + shieldNodePath = self.pelvis.attachNewNode(shieldNode) + + # He also gets a disk-shaped shield around his little cog hula + # hoop. + disk = loader.loadModel('phase_9/models/char/bossCog-gearCollide') + disk.find('**/+CollisionNode').setName('BossZap') + disk.reparentTo(self.pelvis) + disk.setZ(0.8) + + # The BossCog actually owns the environment geometry. This is + # mainly so we can move the ramps in and out under control of + # the FSM here. + self.loadEnvironment() + + # Set up the caged toon. + self.__makeCagedToon() + + self.__loadMopaths() + + global OneBossCog + if OneBossCog != None: + self.notify.warning("Multiple BossCogs visible.") + OneBossCog = self + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + DistributedBossCog.DistributedBossCog.disable(self) + self.request('Off') + self.unloadEnvironment() + self.__unloadMopaths() + self.__cleanupCagedToon() + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + self.__cleanupStrafe() + + render.clearTag('pieCode') + + self.targetNodePath.detachNode() + + self.cr.relatedObjectMgr.abortRequest(self.dooberRequest) + self.dooberRequest = None + + self.betweenBattleMusic.stop() + self.promotionMusic.stop() + self.stingMusic.stop() + self.battleTwoMusic.stop() + self.battleThreeMusic.stop() + self.epilogueMusic.stop() + + global OneBossCog + if OneBossCog == self: + OneBossCog = None + + def d_hitBoss(self, bossDamage): + self.sendUpdate('hitBoss', [bossDamage]) + + def d_hitBossInsides(self): + self.sendUpdate('hitBossInsides', []) + + def d_hitToon(self, toonId): + self.sendUpdate('hitToon', [toonId]) + + def setCagedToonNpcId(self, npcId): + self.cagedToonNpcId = npcId + + def gotToon(self, toon): + # A new Toon has arrived. Put him in the right spot, if we + # know what that is yet. Normally, we will only see this + # message in the WaitForToons state, or in the Off state if they + # came in early (but someone might arrive to the battle very + # late and see everything already advanced to the next state). + stateName = self.state + assert self.notify.debug("gotToon(%s) in state %s" % (toon.doId, stateName)) + + if stateName == "Elevator": + # If the toon arrives late while we're playing the + # elevator movie, try to pop him into place. + + # Actually, this doesn't work, because we haven't yet + # received the "setParent" and "setPos" distributed + # messages, and we're about to. Do something about this + # later. + self.placeToonInElevator(toon) + + def setDooberIds(self, dooberIds): + self.doobers = [] + self.cr.relatedObjectMgr.abortRequest(self.dooberRequest) + self.dooberRequest = self.cr.relatedObjectMgr.requestObjects( + dooberIds, allCallback = self.__gotDoobers) + + def __gotDoobers(self, doobers): + self.dooberRequest = None + self.doobers = doobers + + def setBossDamage(self, bossDamage, recoverRate, timestamp): + recoverStartTime = globalClockDelta.networkToLocalTime(timestamp) + self.bossDamage = bossDamage + self.recoverRate = recoverRate + self.recoverStartTime = recoverStartTime + + taskName = "RecoverBossDamage" + taskMgr.remove(taskName) + + if self.bossDamageMovie: + if self.bossDamage >= self.bossMaxDamage: + # We did it! Finish the movie, then transition to + # NearVictory state. + self.bossDamageMovie.resumeUntil(self.bossDamageMovie.getDuration()) + else: + # Push him up to the indicated point and he stops. + self.bossDamageMovie.resumeUntil(self.bossDamage * self.bossDamageToMovie) + + if self.recoverRate: + taskMgr.add(self.__recoverBossDamage, taskName) + + + def getBossDamage(self): + now = globalClock.getFrameTime() + elapsed = now - self.recoverStartTime + + # Although the AI side computes and transmits getBossDamage() + # as an integer value, on the client side we return it as a + # floating-point value, so we can get the smooth transition + # effect as the boss slowly starts to roll back up. + return max(self.bossDamage - self.recoverRate * elapsed / 60.0, 0) + + def __recoverBossDamage(self, task): + self.bossDamageMovie.setT(self.getBossDamage() * self.bossDamageToMovie) + return Task.cont + + def __makeCagedToon(self): + # Generates a Toon for putting in the cage during the movies, + # that we are supposedly rescuing. + if self.cagedToon: + return + + self.cagedToon = NPCToons.createLocalNPC(self.cagedToonNpcId) + self.cagedToon.addActive() + + self.cagedToon.reparentTo(self.cage) + self.cagedToon.setPosHpr(0, -2, 0, 180, 0, 0) + self.cagedToon.loop('neutral') + + # Also make a polygon to register when we jump up (in battle + # three) and touch the bottom of the cage. + + touch = CollisionPolygon(Point3(-3.0382, 3.0382, -1), + Point3(3.0382, 3.0382, -1), + Point3(3.0382, -3.0382, -1), + Point3(-3.0382, -3.0382, -1)) + touchNode = CollisionNode('Cage') + touchNode.setCollideMask(ToontownGlobals.WallBitmask) + touchNode.addSolid(touch) + self.cage.attachNewNode(touchNode) + + + def __cleanupCagedToon(self): + if self.cagedToon: + self.cagedToon.removeActive() + self.cagedToon.delete() + self.cagedToon = None + + def __walkToonToPromotion(self, toonId, delay, mopath, track, delayDeletes): + # Generates an interval to walk the toon along the mopath + # towards its destination (which is the toon's current pos). + toon = base.cr.doId2do.get(toonId) + if toon: + destPos = toon.getPos() + + # Start the toon off at his position within the elevator. + self.placeToonInElevator(toon) + toon.wrtReparentTo(render) + + # We cleverly combine the MopathInterval with a + # LerpPosInterval so that the toon walks off of his mopath + # to his final destination, in the last few seconds of the + # interval. Note that the LerpPos completely replaces the + # position computed by the Mopath once it kicks in. + + ival = Sequence( + Wait(delay), + Func(toon.suit.setPlayRate, 1, 'walk'), + Func(toon.suit.loop, 'walk'), + toon.posInterval(1, Point3(0, 90, 20)), + ParallelEndTogether(MopathInterval(mopath, toon), + toon.posInterval(2, destPos, + blendType = 'noBlend')), + Func(toon.suit.loop, 'neutral')) + track.append(ival) + delayDeletes.append(DelayDelete.DelayDelete(toon, 'SellbotBoss.__walkToonToPromotion')) + + def __walkDoober(self, suit, delay, turnPos, track, delayDeletes): + # Generates an interval to walk the doober around the Boss Cog + # and out to the platform to fly away. + turnPos = Point3(*turnPos) + turnPosDown = Point3(*ToontownGlobals.SellbotBossDooberTurnPosDown) + flyPos = Point3(*ToontownGlobals.SellbotBossDooberFlyPos) + + seq = Sequence( + Func(suit.headsUp, turnPos), + Wait(delay), + Func(suit.loop, 'walk', 0), + self.__walkSuitToPoint(suit, suit.getPos(), turnPos), + self.__walkSuitToPoint(suit, turnPos, turnPosDown), + self.__walkSuitToPoint(suit, turnPosDown, flyPos), + suit.beginSupaFlyMove(flyPos, 0, 'flyAway'), + Func(suit.fsm.request, 'Off'), + ) + track.append(seq) + delayDeletes.append(DelayDelete.DelayDelete(suit, 'SellbotBoss.__walkDoober')) + + + def __walkSuitToPoint(self, node, fromPos, toPos): + vector = Vec3(toPos - fromPos) + distance = vector.length() + + # These suits walk a little faster than most. (They're + # still young.) + time = distance / (ToontownGlobals.SuitWalkSpeed * 1.8) + + return Sequence(Func(node.setPos, fromPos), + Func(node.headsUp, toPos), + node.posInterval(time, toPos)) + + def makeIntroductionMovie(self, delayDeletes): + + # Generate an interval which shows the toons emerging from the + # elevator, walking down to face the Boss Cog, who is + # currently busy promoting a group of new Cogs and sending + # them on their way. The Boss Cog then begins to promote the + # Toons, but then discovers the dupe and engages them in + # battle instead. + + track = Parallel() + + # camTrack animates the camera for the first part of the + # sequence. + + # First, the camera will start off aiming at the elevators, so + # we'll see the toons emerge and start to split off. Then + # we'll pull back to look at the room and watch the Boss Cog + # promote the previous Cogs, while our Toons walk around the + # perimeter. + + # After that, the camera will be animated by the dialogTrack in + # cuts synchronized with the boss's dialog. + + camera.reparentTo(render) + camera.setPosHpr(0, 25, 30, 0, 0, 0) + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + # dooberTrack includes the doobers walking down the platform + # and flying away. Rather than adding it directly into the + # movie, we call it with an IndirectInterval, so we can jump + # around in time. + + dooberTrack = Parallel() + if self.doobers: + # Start the doobers out around the boss. + self.__doobersToPromotionPosition(self.doobers[:4], self.battleANode) + self.__doobersToPromotionPosition(self.doobers[4:], self.battleBNode) + + turnPosA = ToontownGlobals.SellbotBossDooberTurnPosA + turnPosB = ToontownGlobals.SellbotBossDooberTurnPosB + self.__walkDoober(self.doobers[0], 0, turnPosA, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[1], 4, turnPosA, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[2], 8, turnPosA, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[3], 12, turnPosA, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[7], 2, turnPosB, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[6], 6, turnPosB, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[5], 10, turnPosB, + dooberTrack, delayDeletes) + self.__walkDoober(self.doobers[4], 14, turnPosB, + dooberTrack, delayDeletes) + + # toonTrack shows the toons walking out of the elevator and + # down to face the Boss Cog. As above, this is played with an + # IndirectInterval. + + toonTrack = Parallel() + + # Temporarily put the toons in their final position for the + # movie, just so we can see what it is and lerp them there. + self.__toonsToPromotionPosition(self.toonsA, self.battleANode) + self.__toonsToPromotionPosition(self.toonsB, self.battleBNode) + + delay = 0 + for toonId in self.toonsA: + self.__walkToonToPromotion(toonId, delay, self.toonsEnterA, + toonTrack, delayDeletes) + delay += 1 + + for toonId in self.toonsB: + self.__walkToonToPromotion(toonId, delay, self.toonsEnterB, + toonTrack, delayDeletes) + delay += 1 + + # And the elevator doors close behind the last toon. + toonTrack.append(Sequence(Wait(delay), self.closeDoors)) + + self.rampA.request('extended') + self.rampB.request('extended') + self.rampC.request('retracted') + self.clearChat() + self.cagedToon.clearChat() + + # bossTrack shows the Boss's dialog and animations, and the + # later camera cuts. + + promoteDoobers = TTLocalizer.BossCogPromoteDoobers % ( + SuitDNA.getDeptFullnameP(self.style.dept)) + doobersAway = TTLocalizer.BossCogDoobersAway[self.style.dept] + welcomeToons = TTLocalizer.BossCogWelcomeToons + promoteToons = TTLocalizer.BossCogPromoteToons % ( + SuitDNA.getDeptFullnameP(self.style.dept)) + discoverToons = TTLocalizer.BossCogDiscoverToons + attackToons = TTLocalizer.BossCogAttackToons + interruptBoss = TTLocalizer.CagedToonInterruptBoss + rescueQuery = TTLocalizer.CagedToonRescueQuery + + bossAnimTrack = Sequence( + ActorInterval(self, 'Ff_speech', startTime = 2, duration = 10, loop = 1), + # 10 + ActorInterval(self, 'ltTurn2Wave', duration = 2), + # 12 + ActorInterval(self, 'wave', duration = 4, loop = 1), + # 16 + ActorInterval(self, 'ltTurn2Wave', startTime = 2, endTime = 0), + # 18 + ActorInterval(self, 'Ff_speech', duration = 7, loop = 1), + # 25 + + # remaining animations mixed in with camera cuts in + # dialogTrack. + ) + track.append(bossAnimTrack) + + dialogTrack = Track( + (0, Parallel(camera.posHprInterval(8, Point3(-22, -100, 35), + Point3(-10, -13, 0), + blendType = 'easeInOut'), + IndirectInterval(toonTrack, 0, 18))), + (5.6, Func(self.setChatAbsolute, promoteDoobers, CFSpeech)), + (9, IndirectInterval(dooberTrack, 0, 9)), + + # Cut to over-the-shoulder shot of Boss Cog waving goodbye + # to doobers. + (10, Sequence(Func(self.clearChat), + Func(camera.setPosHpr, -23.1, 15.7, 17.2, -160, -2.4, 0))), + (12, Func(self.setChatAbsolute, doobersAway, CFSpeech)), + + # Cut to wide shot of Boss Cog and Toons and caged toon in + # background. + (16, Parallel(Func(self.clearChat), + Func(camera.setPosHpr, -25, -99, 10, -14, 10, 0), + IndirectInterval(dooberTrack, 14), + IndirectInterval(toonTrack, 30))), + (18, Func(self.setChatAbsolute, welcomeToons, CFSpeech)), + + (22, Func(self.setChatAbsolute, promoteToons, CFSpeech)), + (22.2, Sequence(Func(self.cagedToon.nametag3d.setScale, 2), + Func(self.cagedToon.setChatAbsolute, interruptBoss, CFSpeech), + ActorInterval(self.cagedToon, 'wave'), + Func(self.cagedToon.loop, 'neutral'))), + + # Cut to head-and-shoulders shot of Boss Cog looking up at + # source of interruption. + (25, Sequence(Func(self.clearChat), + Func(self.cagedToon.clearChat), + Func(camera.setPosHpr, -12, -15, 27, -151, -15, 0), + ActorInterval(self, 'Ff_lookRt'), + )), + + # Cut to closeup of caged toon. + (27, Sequence(Func(self.cagedToon.setChatAbsolute, rescueQuery, CFSpeech), + Func(camera.setPosHpr, -12, 48, 94, -26, 20, 0), + ActorInterval(self.cagedToon, 'wave'), + Func(self.cagedToon.loop, 'neutral'))), + + # Cut to shot of Boss Cog looking back at Toons from + # Toons' eye view. + (31, Sequence(Func(camera.setPosHpr, -20, -35, 10, -88, 25, 0), + Func(self.setChatAbsolute, discoverToons, CFSpeech), + Func(self.cagedToon.nametag3d.setScale, 1), + Func(self.cagedToon.clearChat), + ActorInterval(self, 'turn2Fb'), + )), + + # Cut to toons losing their cog suits. + (34, Sequence(Func(self.clearChat), + self.loseCogSuits(self.toonsA, self.battleANode, (0,18,5,-180,0,0)), + self.loseCogSuits(self.toonsB, self.battleBNode, (0,18,5,-180,0,0)))), + + # Cut to wide shot of battle arena. Toons back up and + # ramps retract. + (37, Sequence(self.toonNormalEyes(self.involvedToons), + Func(camera.setPosHpr, -23.4, -145.6, 44.0, -10.0, -12.5, 0), + Func(self.loop, 'Fb_neutral'), + Func(self.rampA.request, 'retract'), + Func(self.rampB.request, 'retract'), + Parallel(self.backupToonsToBattlePosition(self.toonsA, self.battleANode), + self.backupToonsToBattlePosition(self.toonsB, self.battleBNode), + Sequence(Wait(2), + Func(self.setChatAbsolute, attackToons, CFSpeech))), + )), + ) + track.append(dialogTrack) + + return Sequence(Func(self.stickToonsToFloor), + track, + Func(self.unstickToons), + name = self.uniqueName('Introduction')) + + def __makeRollToBattleTwoMovie(self): + # Generate an interval which shows the Boss Cog rolling to the + # battle 2 position. + + startPos = Point3(ToontownGlobals.SellbotBossBattleOnePosHpr[0], + ToontownGlobals.SellbotBossBattleOnePosHpr[1], + ToontownGlobals.SellbotBossBattleOnePosHpr[2]) + if self.arenaSide: + topRampPos = Point3(*ToontownGlobals.SellbotBossTopRampPosB) + topRampTurnPos = Point3(*ToontownGlobals.SellbotBossTopRampTurnPosB) + p3Pos = Point3(*ToontownGlobals.SellbotBossP3PosB) + else: + topRampPos = Point3(*ToontownGlobals.SellbotBossTopRampPosA) + topRampTurnPos = Point3(*ToontownGlobals.SellbotBossTopRampTurnPosA) + p3Pos = Point3(*ToontownGlobals.SellbotBossP3PosA) + + battlePos = Point3(ToontownGlobals.SellbotBossBattleTwoPosHpr[0], + ToontownGlobals.SellbotBossBattleTwoPosHpr[1], + ToontownGlobals.SellbotBossBattleTwoPosHpr[2]) + battleHpr = VBase3(ToontownGlobals.SellbotBossBattleTwoPosHpr[3], + ToontownGlobals.SellbotBossBattleTwoPosHpr[4], + ToontownGlobals.SellbotBossBattleTwoPosHpr[5]) + bossTrack = Sequence() + + # Turn the boss model around so he rolls forward. + bossTrack.append(Func(self.getGeomNode().setH, 180)) + bossTrack.append(Func(self.loop, 'Fb_neutral')) + + track, hpr = self.rollBossToPoint(startPos, None, topRampPos, None, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(topRampPos, hpr, topRampTurnPos, None, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(topRampTurnPos, hpr, p3Pos, None, 0) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(p3Pos, hpr, battlePos, None, 0) + bossTrack.append(track) + + return Sequence( + bossTrack, + Func(self.getGeomNode().setH, 0), + name = self.uniqueName('BattleTwo')) + + def makeEndOfBattleMovie(self, hasLocalToon): + assert self.notify.debug("makeEndOfBattleMovie(%s)" % (hasLocalToon)) + # Generate an interval which shows the cage dropping a bit + # further. This one is called from DistributedBattleFinal. + name = self.uniqueName('CageDrop') + seq = Sequence(name = name) + + seq.append(Func(self.cage.setPos, self.cagePos[self.cageIndex])) + + if hasLocalToon: + seq += [Func(camera.reparentTo, render), + Func(camera.setPosHpr, self.cage, 0, -50, 0, 0, 0, 0), + Func(localAvatar.setCameraFov, ToontownGlobals.CogHQCameraFov), + Func(self.hide)] + seq += [Wait(0.5), + Parallel(self.cage.posInterval(1, self.cagePos[self.cageIndex + 1], + blendType = 'easeInOut'), + SoundInterval(self.cageLowerSfx, duration = 1)), + Func(self.cagedToon.nametag3d.setScale, 2), + Func(self.cagedToon.setChatAbsolute, + TTLocalizer.CagedToonDrop[self.cageIndex], CFSpeech), + Wait(3), + Func(self.cagedToon.nametag3d.setScale, 1), + Func(self.cagedToon.clearChat)] + if hasLocalToon: + seq += [Func(self.show), + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, localAvatar.cameraPositions[0][0]), + Func(camera.setHpr, 0, 0, 0)] + + self.cageIndex += 1 + return seq + + def __makeBossDamageMovie(self): + # Generate an interval which shows the Boss Cog rolling down + # in retreat as the Toons attack. + + startPos = Point3(ToontownGlobals.SellbotBossBattleTwoPosHpr[0], + ToontownGlobals.SellbotBossBattleTwoPosHpr[1], + ToontownGlobals.SellbotBossBattleTwoPosHpr[2]) + startHpr = Point3(*ToontownGlobals.SellbotBossBattleThreeHpr) + bottomPos = Point3(*ToontownGlobals.SellbotBossBottomPos) + deathPos = Point3(*ToontownGlobals.SellbotBossDeathPos) + + self.setPosHpr(startPos, startHpr) + + bossTrack = Sequence() + bossTrack.append(Func(self.loop, 'Fb_neutral')) + track, hpr = self.rollBossToPoint(startPos, startHpr, bottomPos, None, 1) + bossTrack.append(track) + track, hpr = self.rollBossToPoint(bottomPos, startHpr, deathPos, None, 1) + bossTrack.append(track) + + duration = bossTrack.getDuration() + return bossTrack + + def __talkAboutPromotion(self, speech): + # Extends the congratulations speech to talk about the earned + # promotion, if any. Returns the newly-extended speech. + + # don't say anything about a promotion if they've maxed their cog suit + if self.prevCogSuitLevel < ToontownGlobals.MaxCogSuitLevel: + speech += TTLocalizer.CagedToonPromotion + newCogSuitLevel = localAvatar.getCogLevels()[ + CogDisguiseGlobals.dept2deptIndex(self.style.dept)] + # if this is their last promotion, tell them + if newCogSuitLevel == ToontownGlobals.MaxCogSuitLevel: + speech += TTLocalizer.CagedToonLastPromotion % (ToontownGlobals.MaxCogSuitLevel+1) + # if they're getting another LP, tell them + if newCogSuitLevel in ToontownGlobals.CogSuitHPLevels: + speech += TTLocalizer.CagedToonHPBoost + else: + # level XX, wow! Thanks for coming back! + speech += TTLocalizer.CagedToonMaxed % (ToontownGlobals.MaxCogSuitLevel+1) + + return speech + + def __makeCageOpenMovie(self): + # Generate an interval which shows the cage dropping all the + # way to the ground and opening, to free the trapped toon who + # bestows upon us a favor. + + # put together what the toon is going to say ahead of time, + # since setLocalPageChat does not queue up messages + speech = TTLocalizer.CagedToonThankYou + speech = self.__talkAboutPromotion(speech) + + name = self.uniqueName('CageOpen') + seq = Sequence( + Func(self.cage.setPos, self.cagePos[4]), + Func(self.cageDoor.setHpr, VBase3(0, 0, 0)), + Func(self.cagedToon.setPos, Point3(0, -2, 0)), + Parallel(self.cage.posInterval(0.5, self.cagePos[5], + blendType = 'easeOut'), + SoundInterval(self.cageLowerSfx, duration = 0.5)), + Parallel(self.cageDoor.hprInterval(0.5, VBase3(0, 90, 0), + blendType = 'easeOut'), + Sequence(SoundInterval(self.cageDoorSfx), duration = 0)), + Wait(0.2), + Func(self.cagedToon.loop, 'walk'), + self.cagedToon.posInterval(0.8, Point3(0, -6, 0)), + Func(self.cagedToon.setChatAbsolute, + TTLocalizer.CagedToonYippee, CFSpeech), + ActorInterval(self.cagedToon, 'jump'), + Func(self.cagedToon.loop, 'neutral'), + Func(self.cagedToon.headsUp, localAvatar), + Func(self.cagedToon.setLocalPageChat, speech, 0), + Func(camera.reparentTo, localAvatar), + Func(camera.setPos, 0, -9, 9), + Func(camera.lookAt, self.cagedToon, Point3(0, 0, 2)), + name = name) + + return seq + + def __showOnscreenMessage(self, text): + if self.onscreenMessage: + self.onscreenMessage.destroy() + self.onscreenMessage = None + + self.onscreenMessage = DirectLabel( + text = text, + text_fg = VBase4(1,1,1,1), + text_align = TextNode.ACenter, + relief = None, + pos = (0, 0, 0.35), + scale = 0.1) + + def __clearOnscreenMessage(self): + if self.onscreenMessage: + self.onscreenMessage.destroy() + self.onscreenMessage = None + + def __showWaitingMessage(self, task): + self.__showOnscreenMessage(TTLocalizer.BuildingWaitingForVictors) + + def __placeCageShadow(self): + if self.cageShadow == None: + self.cageShadow = loader.loadModel('phase_3/models/props/drop_shadow') + self.cageShadow.setPos(0, 77.9, 18) + self.cageShadow.setColorScale(1, 1, 1, 0.6) + self.cageShadow.reparentTo(render) + + def __removeCageShadow(self): + if self.cageShadow != None: + self.cageShadow.detachNode() + + def setCageIndex(self, cageIndex): + # Sets the cage to the appropriate height for the given index. + self.cageIndex = cageIndex + self.cage.setPos(self.cagePos[self.cageIndex]) + if self.cageIndex >= 4: + self.__placeCageShadow() + else: + self.__removeCageShadow() + + + ##### Environment ##### + + def loadEnvironment(self): + DistributedBossCog.DistributedBossCog.loadEnvironment(self) + + self.geom = loader.loadModel('phase_9/models/cogHQ/BossRoomHQ') + self.rampA = self.__findRamp('rampA', '**/west_ramp2') + self.rampB = self.__findRamp('rampB', '**/west_ramp') + self.rampC = self.__findRamp('rampC', '**/west_ramp1') + self.cage = self.geom.find('**/cage') + + elevatorEntrance = self.geom.find('**/elevatorEntrance') + # The elevatorEntrance has some geometry that it shouldn't. + elevatorEntrance.getChildren().detach() + elevatorEntrance.setScale(1) + + elevatorModel = loader.loadModel("phase_9/models/cogHQ/cogHQ_elevator") + elevatorModel.reparentTo(elevatorEntrance) + + self.setupElevator(elevatorModel) + + pos = self.cage.getPos() + self.cagePos = [] + for height in self.cageHeights: + self.cagePos.append(Point3(pos[0], pos[1], height)) + + self.cageDoor = self.geom.find('**/cage_door') + + # Make the cage be scale 1.0, to fit the Toon inside better. + self.cage.setScale(1) + + # Draw a chain from the top of the cage support to the bottom + # of the I-beam. + self.rope = Rope.Rope(name = 'supportChain') + self.rope.reparentTo(self.cage) + self.rope.setup(2, ((self.cage, (0.15, 0.13, 16)), + (self.geom, (0.23, 78, 120)))) + self.rope.ropeNode.setRenderMode(RopeNode.RMBillboard) + self.rope.ropeNode.setUvMode(RopeNode.UVDistance) + self.rope.ropeNode.setUvDirection(0) + + self.rope.ropeNode.setUvScale(0.8) + self.rope.setTexture(self.cage.findTexture('hq_chain')) + self.rope.setTransparency(1) + + # before battles: play the boss theme music + self.promotionMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + # 'phase_9/audio/bgm/encntr_head_suit_theme.mid') + # Between battle one and two: play the upbeat street battle music + self.betweenBattleMusic = base.loadMusic( + 'phase_9/audio/bgm/encntr_toon_winning.mid') + # Battle two: play the top-of-the-building battle music + self.battleTwoMusic = base.loadMusic( + 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid') + + self.geom.reparentTo(render) + + + def unloadEnvironment(self): + DistributedBossCog.DistributedBossCog.unloadEnvironment(self) + + self.geom.removeNode() + del self.geom + del self.cage + + self.rampA.requestFinalState() + self.rampB.requestFinalState() + self.rampC.requestFinalState() + del self.rampA + del self.rampB + del self.rampC + + def __loadMopaths(self): + self.toonsEnterA = Mopath.Mopath() + self.toonsEnterA.loadFile('phase_9/paths/bossBattle-toonsEnterA') + self.toonsEnterA.fFaceForward = 1 + self.toonsEnterA.timeScale = 35 + self.toonsEnterB = Mopath.Mopath() + self.toonsEnterB.loadFile('phase_9/paths/bossBattle-toonsEnterB') + self.toonsEnterB.fFaceForward = 1 + self.toonsEnterB.timeScale = 35 + + def __unloadMopaths(self): + self.toonsEnterA.reset() + self.toonsEnterB.reset() + + def __findRamp(self, name, path): + # Find the ramp in the geom and sets it up for animation. + ramp = self.geom.find(path) + + # The transform on the ramp node represents the coordinate + # system in which the ramp can move. That means we need to + # animate a child of the ramp node itself in order to remain + # within this coordinate system. Since there are multiple + # children (visible polygons as well as collision nodes), we + # create our own node for this purpose. + children = ramp.getChildren() + animate = ramp.attachNewNode(name) + children.reparentTo(animate) + + # Now we create a tiny ClassicFSM to manage the ramp's state. + fsm = ClassicFSM.ClassicFSM(name, + [State.State('extend', + Functor(self.enterRampExtend, animate), + Functor(self.exitRampExtend, animate), + ['extended', 'retract', 'retracted']), + State.State('extended', + Functor(self.enterRampExtended, animate), + Functor(self.exitRampExtended, animate), + ['retract', 'retracted']), + State.State('retract', + Functor(self.enterRampRetract, animate), + Functor(self.exitRampRetract, animate), + ['extend', 'extended', 'retracted']), + State.State('retracted', + Functor(self.enterRampRetracted, animate), + Functor(self.exitRampRetracted, animate), + ['extend', 'extended']), + State.State('off', + Functor(self.enterRampOff, animate), + Functor(self.exitRampOff, animate)), + ], + # Initial state + 'off', + # Final state + 'off', + onUndefTransition = ClassicFSM.ClassicFSM.DISALLOW) + fsm.enterInitialState() + + return fsm + + ##### Ramp states ##### + + def enterRampExtend(self, animate): + intervalName = self.uniqueName('extend-%s' % (animate.getName())) + adjustTime = 2.0 * (animate.getX()) / 18.0 + ival = Parallel( + SoundInterval(self.rampSlideSfx, node = animate), + animate.posInterval(adjustTime, Point3(0, 0, 0), + blendType = 'easeInOut', + name = intervalName), + ) + ival.start() + self.storeInterval(ival, intervalName) + + def exitRampExtend(self, animate): + intervalName = self.uniqueName('extend-%s' % (animate.getName())) + self.clearInterval(intervalName) + + def enterRampExtended(self, animate): + animate.setPos(0, 0, 0) + + def exitRampExtended(self, animate): + pass + + def enterRampRetract(self, animate): + intervalName = self.uniqueName('retract-%s' % (animate.getName())) + adjustTime = 2.0 * (18 - animate.getX()) / 18.0 + ival = Parallel( + SoundInterval(self.rampSlideSfx, node = animate), + animate.posInterval(adjustTime, Point3(18, 0, 0), + blendType = 'easeInOut', + name = intervalName), + ) + ival.start() + self.storeInterval(ival, intervalName) + + def exitRampRetract(self, animate): + intervalName = self.uniqueName('retract-%s' % (animate.getName())) + self.clearInterval(intervalName) + + def enterRampRetracted(self, animate): + animate.setPos(18, 0, 0) + + def exitRampRetracted(self, animate): + pass + + def enterRampOff(self, animate): + pass + + def exitRampOff(self, animate): + pass + + + ##### Off state ##### + + def enterOff(self): + DistributedBossCog.DistributedBossCog.enterOff(self) + + if self.cagedToon: + self.cagedToon.clearChat() + + if self.rampA: + self.rampA.request('off') + if self.rampB: + self.rampB.request('off') + if self.rampC: + self.rampC.request('off') + + ##### WaitForToons state ##### + + def enterWaitForToons(self): + DistributedBossCog.DistributedBossCog.enterWaitForToons(self) + + self.geom.hide() + + # Disable the caged toon's nametag while we're here in space + # waiting. + self.cagedToon.removeActive() + + def exitWaitForToons(self): + DistributedBossCog.DistributedBossCog.exitWaitForToons(self) + + self.geom.show() + self.cagedToon.addActive() + + + ##### Elevator state ##### + + def enterElevator(self): + DistributedBossCog.DistributedBossCog.enterElevator(self) + + # Make sure the side ramps are extended and the back ramp is + # retracted. + self.rampA.request('extended') + self.rampB.request('extended') + self.rampC.request('retracted') + + # And the cage is up in the original position. + self.setCageIndex(0) + + # Set the boss up in the middle of the floor, so we can see + # him when the doors open. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.SellbotBossBattleOnePosHpr) + + self.happy = 1 + self.raised = 1 + self.forward = 1 + self.doAnimate() + + # Disable the caged toon's nametag while we're in the + # elevator. + self.cagedToon.removeActive() + + def exitElevator(self): + DistributedBossCog.DistributedBossCog.exitElevator(self) + + self.cagedToon.addActive() + + ##### Introduction state ##### + + def enterIntroduction(self): + # Set the boss up in the middle of the floor, actively + # promoting some doobers. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.SellbotBossBattleOnePosHpr) + self.stopAnimate() + + DistributedBossCog.DistributedBossCog.enterIntroduction(self) + + # Make sure the side ramps are extended and the back ramp is + # retracted. + self.rampA.request('extended') + self.rampB.request('extended') + self.rampC.request('retracted') + + # And the cage is up in the original position. + self.setCageIndex(0) + + base.playMusic(self.promotionMusic, looping=1, volume=0.9) + + def exitIntroduction(self): + DistributedBossCog.DistributedBossCog.exitIntroduction(self) + + self.promotionMusic.stop() + + ##### BattleOne state ##### + + def enterBattleOne(self): + DistributedBossCog.DistributedBossCog.enterBattleOne(self) + + # Boss Cog is still in the middle of the floor. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.SellbotBossBattleOnePosHpr) + self.clearChat() + self.cagedToon.clearChat() + + # Make sure the ramps are retracted (or on their way there). + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('retract') + + if self.battleA == None or self.battleB == None: + cageIndex = 1 + else: + cageIndex = 0 + self.setCageIndex(cageIndex) + + def exitBattleOne(self): + DistributedBossCog.DistributedBossCog.exitBattleOne(self) + + ##### RollToBattleTwo state ##### + + def enterRollToBattleTwo(self): + assert self.notify.debug('enterRollToBattleTwo()') + # Disable collision on the toon, there is a collision issue where the boss was + # hitting the toons right after the first battle, so we turn off their collision briefly + # until this issue can be addressed in Panda. + self.disableToonCollision() + self.releaseToons() + + # Retract most of the ramps. + if self.arenaSide: + self.rampA.request('retract') + self.rampB.request('extend') + else: + self.rampA.request('extend') + self.rampB.request('retract') + self.rampC.request('retract') + + self.reparentTo(render) + + # By now, the cage has dropped somewhat. + self.setCageIndex(2) + + # The Boss Cog rolls up the ramp into position for battle two, + # while the Toons are free to run around for a few seconds. + self.stickBossToFloor() + + # Now generate the interval that plays the movie. + intervalName = "RollToBattleTwo" + seq = Sequence(self.__makeRollToBattleTwoMovie(), + Func(self.__onToPrepareBattleTwo), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + base.playMusic(self.betweenBattleMusic, looping=1, volume=0.9) + #re-enable the collision a little bit later, after the boss has started moving + taskMgr.doMethodLater(0.5, self.enableToonCollision, 'enableToonCollision') + + def __onToPrepareBattleTwo(self): + # Make sure the boss ends up in his battle position. + self.unstickBoss() + self.setPosHpr(*ToontownGlobals.SellbotBossBattleTwoPosHpr) + self.doneBarrier('RollToBattleTwo') + + def exitRollToBattleTwo(self): + self.unstickBoss() + intervalName = "RollToBattleTwo" + self.clearInterval(intervalName) + + self.betweenBattleMusic.stop() + + def disableToonCollision(self): + base.localAvatar.collisionsOff() + + def enableToonCollision(self, task): + base.localAvatar.collisionsOn() + + ##### PrepareBattleTwo state ##### + + def enterPrepareBattleTwo(self): + assert self.notify.debug('enterPrepareBattleTwo()') + self.cleanupIntervals() + + self.controlToons() + + self.clearChat() + self.cagedToon.clearChat() + self.reparentTo(render) + + # Retract most of the ramps. + if self.arenaSide: + self.rampA.request('retract') + self.rampB.request('extend') + else: + self.rampA.request('extend') + self.rampB.request('retract') + self.rampC.request('retract') + + self.reparentTo(render) + + # By now, the cage has dropped somewhat. + self.setCageIndex(2) + + # In this state, we just show a closeup of the cagedToon. + camera.reparentTo(render) + camera.setPosHpr(self.cage, 0, -17, 3.3, 0, 0, 0) + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov), + + # And the boss cog is actually hidden, because the camera is + # positioned inside him. + self.hide() + + # We see the caged toon give us some congratulations and + # advice for defeating the boss cog; we have to click through + # this advice to move on. + self.acceptOnce("doneChatPage", self.__onToBattleTwo) + self.cagedToon.setLocalPageChat(TTLocalizer.CagedToonPrepareBattleTwo, 1) + + # Let's play the elevator music again; it's dramatic enough to + # use twice. + base.playMusic(self.stingMusic, looping=0, volume=1.0) + + def __onToBattleTwo(self, elapsed): + self.doneBarrier('PrepareBattleTwo') + + # Wait a second. If we don't move on immediately, pop up the + # "waiting for other players" message. + taskMgr.doMethodLater(1, self.__showWaitingMessage, + self.uniqueName("WaitingMessage")) + + def exitPrepareBattleTwo(self): + self.show() + taskMgr.remove(self.uniqueName("WaitingMessage")) + self.ignore("doneChatPage") + self.__clearOnscreenMessage() + self.stingMusic.stop() + + ##### BattleTwo state ##### + + def enterBattleTwo(self): + assert self.notify.debug('enterBattleTwo()') + self.cleanupIntervals() + + # Get the credit multiplier + mult = ToontownBattleGlobals.getBossBattleCreditMultiplier(2) + localAvatar.inventory.setBattleCreditMultiplier(mult) + + # Boss Cog is now on top of the ramp. + self.reparentTo(render) + self.setPosHpr(*ToontownGlobals.SellbotBossBattleTwoPosHpr) + + # Clear the chat dialogs left over from the transition movie. + self.clearChat() + self.cagedToon.clearChat() + + # Make sure the ramps are retracted (or on their way there). + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('retract') + + # Now the battle holds the toons. + self.releaseToons() + + # Position the toons to the battle. + self.toonsToBattlePosition(self.toonsA, self.battleANode) + self.toonsToBattlePosition(self.toonsB, self.battleBNode) + + if self.battleA == None or self.battleB == None: + cageIndex = 3 + else: + cageIndex = 2 + self.setCageIndex(cageIndex) + base.playMusic(self.battleTwoMusic, looping=1, volume=0.9) + + + def exitBattleTwo(self): + intervalName = self.uniqueName('cageDrop') + self.clearInterval(intervalName) + self.cleanupBattles() + self.battleTwoMusic.stop() + + # No more credit multiplier + localAvatar.inventory.setBattleCreditMultiplier(1) + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + assert self.notify.debug('enterPrepareBattleThree()') + self.cleanupIntervals() + + self.controlToons() + + self.clearChat() + self.cagedToon.clearChat() + self.reparentTo(render) + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + self.setCageIndex(4) + + # In this state, we just show a closeup of the cagedToon. + camera.reparentTo(render) + camera.setPosHpr(self.cage, 0, -17, 3.3, 0, 0, 0) + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov), + + # And the boss cog is actually hidden, because the camera is + # positioned inside him. + self.hide() + + # We see the caged toon give us some congratulations and + # advice for defeating the boss cog; we have to click through + # this advice to move on. + self.acceptOnce("doneChatPage", self.__onToBattleThree) + self.cagedToon.setLocalPageChat(TTLocalizer.CagedToonPrepareBattleThree, 1) + base.playMusic(self.betweenBattleMusic, looping=1, volume=0.9) + + def __onToBattleThree(self, elapsed): + self.doneBarrier('PrepareBattleThree') + + # Wait a second. If we don't move on immediately, pop up the + # "waiting for other players" message. + taskMgr.doMethodLater(1, self.__showWaitingMessage, + self.uniqueName("WaitingMessage")) + + def exitPrepareBattleThree(self): + self.show() + taskMgr.remove(self.uniqueName("WaitingMessage")) + self.ignore("doneChatPage") + intervalName = "PrepareBattleThree" + self.clearInterval(intervalName) + self.__clearOnscreenMessage() + self.betweenBattleMusic.stop() + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('enterBattleThree()') + DistributedBossCog.DistributedBossCog.enterBattleThree(self) + + self.clearChat() + self.cagedToon.clearChat() + self.reparentTo(render) + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + self.setCageIndex(4) + + self.happy = 0 + self.raised = 1 + self.forward = 1 + self.doAnimate() + + # Now we're in pie mode. + self.accept('enterCage', self.__touchedCage) + self.accept('pieSplat', self.__pieSplat) + self.accept('localPieSplat', self.__localPieSplat) + self.accept('outOfPies', self.__outOfPies) + self.accept('begin-pie', self.__foundPieButton) + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + # In case they don't figure it out, hit them over the head + # with it after a few seconds. + taskMgr.doMethodLater(30, self.__howToGetPies, + self.uniqueName("PieAdvice")) + + # Now, the Boss mainly sits there and taunts us, and + # occasionally attacks us. But when we hit him, he rolls a + # little bit farther down the launching area. We implement + # this by playing a little bit more of the bossDamageMovie + # with each hit. + self.stickBossToFloor() + + self.bossDamageMovie = self.__makeBossDamageMovie() + bossDoneEventName = self.uniqueName('DestroyedBoss') + self.bossDamageMovie.setDoneEvent(bossDoneEventName) + self.acceptOnce(bossDoneEventName, self.__doneBattleThree) + + self.bossMaxDamage = ToontownGlobals.SellbotBossMaxDamage + + # This factor scales the "max damage" point to the point in + # the movie where the Boss falls over the edge. + self.bossDamageToMovie = self.bossDamageMovie.getDuration() / self.bossMaxDamage + + # Leave the boss movie paused at the current damage level. + self.bossDamageMovie.setT(self.bossDamage * self.bossDamageToMovie) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9) + + def __doneBattleThree(self): + # We've played the boss damage movie all the way to + # completion; it's time to transition to the NearVictory state. + self.setState('NearVictory') + self.unstickBoss() + + def exitBattleThree(self): + DistributedBossCog.DistributedBossCog.exitBattleThree(self) + bossDoneEventName = self.uniqueName('DestroyedBoss') + self.ignore(bossDoneEventName) + # Actually, we'll keep the animation going, so we can continue + # it in the NearVictory state. + #self.stopAnimate() + + taskMgr.remove(self.uniqueName('StandUp')) + + # No more pies. + self.ignore('enterCage') + self.ignore('pieSplat') + self.ignore('localPieSplat') + self.ignore('outOfPies') + self.ignore('begin-pie') + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + self.__removeCageShadow() + + self.bossDamageMovie.finish() + self.bossDamageMovie = None + self.unstickBoss() + + taskName = "RecoverBossDamage" + taskMgr.remove(taskName) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + + ##### NearVictory state ##### + + def enterNearVictory(self): + assert self.notify.debug('enterNearVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + + # Boss Cog is on the edge of the precipice, waiting for that + # one final pie toss. + self.reparentTo(render) + self.setPos(*ToontownGlobals.SellbotBossDeathPos) + self.setHpr(*ToontownGlobals.SellbotBossBattleThreeHpr) + self.clearChat() + self.cagedToon.clearChat() + + self.setCageIndex(4) + + # No one owns the toons. + self.releaseToons(finalBattle = 1) + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + # We're still in pie mode, for the one more ceremonial toss + # that sends the boss plummeting to his death. + self.accept('enterCage', self.__touchedCage) + self.accept('pieSplat', self.__finalPieSplat) + self.accept('localPieSplat', self.__localPieSplat) + self.accept('outOfPies', self.__outOfPies) + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + self.happy = 0 + self.raised = 0 + self.forward = 1 + self.doAnimate() + self.setDizzy(1) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def exitNearVictory(self): + # No more pies. + self.ignore('enterCage') + self.ignore('pieSplat') + self.ignore('localPieSplat') + self.ignore('outOfPies') + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + self.__removeCageShadow() + self.setDizzy(0) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + # Actually, we'll keep the animation going, so we can continue + # it in the Victory state. + #self.stopAnimate() + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('enterVictory()') + # No more intervals should be playing. + self.cleanupIntervals() + + localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov) + + # Boss Cog is on the edge of the precipice, and immediately + # falls to his untimely end. + self.reparentTo(render) + self.setPos(*ToontownGlobals.SellbotBossDeathPos) + self.setHpr(*ToontownGlobals.SellbotBossBattleThreeHpr) + self.clearChat() + self.cagedToon.clearChat() + + self.setCageIndex(4) + + # No one owns the toons. + self.releaseToons(finalBattle = 1) + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + self.happy = 0 + self.raised = 0 + self.forward = 1 + + # Play the boss's death animation. + self.doAnimate('Fb_fall', now = 1) + + # We want to know when the above animation finishes playing. + self.acceptOnce(self.animDoneEvent, self.__continueVictory) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def __continueVictory(self): + # Ok, he's gone! We all move to the reward movie. + + self.stopAnimate() + self.stash() + self.doneBarrier('Victory') + + def exitVictory(self): + self.stopAnimate() + self.unstash() + self.__removeCageShadow() + + localAvatar.setCameraFov(ToontownGlobals.CogHQCameraFov) + + self.battleThreeMusicTime = self.battleThreeMusic.getTime() + self.battleThreeMusic.stop() + + ##### Reward state ##### + + def enterReward(self): + assert self.notify.debug('enterReward()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.cagedToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + self.setCageIndex(4) + + # The toons are technically free to run around, but localToon + # starts out locked down for the reward movie. + self.releaseToons(finalBattle = 1) + self.toMovieMode() + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + # Start the reward movie playing. + + panelName = self.uniqueName('reward') + self.rewardPanel = RewardPanel.RewardPanel(panelName) + (victory, camVictory) = MovieToonVictory.doToonVictory( + 1, self.involvedToons, + self.toonRewardIds, + self.toonRewardDicts, + self.deathList, + self.rewardPanel, + allowGroupShot = 0, + uberList = self.uberList) + + ival = Sequence( + Parallel(victory, camVictory), + Func(self.__doneReward)) + + intervalName = "RewardMovie" + delayDeletes = [] + for toonId in self.involvedToons: + toon = self.cr.doId2do.get(toonId) + if toon: + delayDeletes.append(DelayDelete.DelayDelete(toon, 'SellbotBoss.enterReward')) + + ival.delayDeletes = delayDeletes + ival.start() + self.storeInterval(ival, intervalName) + + base.playMusic(self.battleThreeMusic, looping=1, volume=0.9, + time = self.battleThreeMusicTime) + + def __doneReward(self): + self.doneBarrier('Reward') + self.toWalkMode() + + def exitReward(self): + intervalName = "RewardMovie" + self.clearInterval(intervalName) + + self.unstash() + self.rewardPanel.destroy() + del self.rewardPanel + self.__removeCageShadow() + + self.battleThreeMusicTime = 0 + self.battleThreeMusic.stop() + + ##### Epilogue state ##### + + def enterEpilogue(self): + assert self.notify.debug('enterEpilogue()') + # No more intervals should be playing. + self.cleanupIntervals() + self.clearChat() + self.cagedToon.clearChat() + + # Boss Cog is gone. + self.stash() + self.stopAnimate() + + self.setCageIndex(4) + + # The toons are under our control once again. + self.controlToons() + + # Retract all of the ramps but the back one. + self.rampA.request('retract') + self.rampB.request('retract') + self.rampC.request('extend') + + self.__arrangeToonsAroundCage() + camera.reparentTo(render) + camera.setPosHpr(-24, 52, 27.5, -53, -13, 0) + + intervalName = "EpilogueMovie" + + seq = Sequence(self.__makeCageOpenMovie(), + name = intervalName) + seq.start() + self.storeInterval(seq, intervalName) + + self.accept("nextChatPage", self.__epilogueChatNext) + self.accept("doneChatPage", self.__epilogueChatDone) + + base.playMusic(self.epilogueMusic, looping=1, volume=0.9) + + def __epilogueChatNext(self, pageNumber, elapsed): + if pageNumber == 2: + # "I am in your debt." + if self.cagedToon.style.torso[1] == 'd': + track = ActorInterval(self.cagedToon, 'curtsy') + else: + track = ActorInterval(self.cagedToon, 'bow') + + track = Sequence(track, Func(self.cagedToon.loop, 'neutral')) + intervalName = "EpilogueMovieToonAnim" + self.storeInterval(track, intervalName) + track.start() + + def __epilogueChatDone(self, elapsed): + assert self.notify.debug('epilogueChatDone()') + self.cagedToon.setChatAbsolute(TTLocalizer.CagedToonGoodbye, CFSpeech) + + self.ignore("nextChatPage") + self.ignore("doneChatPage") + + intervalName = "EpilogueMovieToonAnim" + self.clearInterval(intervalName) + track = Parallel( + Sequence(ActorInterval(self.cagedToon, 'wave'), + Func(self.cagedToon.loop, 'neutral')), + Sequence(Wait(0.5), + Func(self.localToonToSafeZone))) + self.storeInterval(track, intervalName) + track.start() + + def exitEpilogue(self): + self.clearInterval("EpilogueMovieToonAnim") + self.unstash() + self.__removeCageShadow() + + self.epilogueMusic.stop() + + def __arrangeToonsAroundCage(self): + radius = 15 + numToons = len(self.involvedToons) + center = (numToons - 1) / 2.0 + for i in range(numToons): + toon = base.cr.doId2do.get(self.involvedToons[i]) + if toon: + angle = 270 - 15 * (i - center) + + radians = angle * math.pi / 180.0 + x = math.cos(radians) * radius + y = math.sin(radians) * radius + toon.setPos(self.cage, x, y, 0) + toon.setZ(18.0) + toon.headsUp(self.cage) + + + + ##### Frolic state ##### + + # This state is probably only useful for debugging. The toons are + # all free to run around the world. + + def enterFrolic(self): + DistributedBossCog.DistributedBossCog.enterFrolic(self) + self.setPosHpr(*ToontownGlobals.SellbotBossBattleOnePosHpr) + + + ##### Misc. utility functions ##### + + def doorACallback(self, isOpen): + # Called whenever doorA opens or closes. + if self.insidesANodePath: + if isOpen: + self.insidesANodePath.unstash() + else: + self.insidesANodePath.stash() + + def doorBCallback(self, isOpen): + # Called whenever doorB opens or closes. + if self.insidesBNodePath: + if isOpen: + self.insidesBNodePath.unstash() + else: + self.insidesBNodePath.stash() + + def __toonsToPromotionPosition(self, toonIds, battleNode): + + # At first, the toons walk down the ramp and stand close to + # the Boss Cog to receive a promotion. They don't back up to + # battle position until a little bit later. + + points = BattleBase.BattleBase.toonPoints[len(toonIds) - 1] + + for i in range(len(toonIds)): + toon = base.cr.doId2do.get(toonIds[i]) + if toon: + toon.reparentTo(render) + pos, h = points[i] + toon.setPosHpr(battleNode, pos[0], pos[1] + 10, pos[2], h, 0, 0) + + def __doobersToPromotionPosition(self, doobers, battleNode): + + # The doobers start out facing the Boss Cog. + + points = BattleBase.BattleBase.toonPoints[len(doobers) - 1] + + for i in range(len(doobers)): + suit = doobers[i] + suit.fsm.request('neutral') + suit.loop('neutral') + pos, h = points[i] + suit.setPosHpr(battleNode, pos[0], pos[1] + 10, pos[2], h, 0, 0) + + def __touchedCage(self, entry): + assert self.notify.debug("__touchedCage()") + # The avatar has jumped up to touch the cage; he should be + # given pies now. + self.sendUpdate('touchCage', []) + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + base.playSfx(self.piesRestockSfx) + + if not self.everThrownPie: + taskMgr.doMethodLater(30, self.__howToThrowPies, + self.uniqueName("PieAdvice")) + + def __outOfPies(self): + self.__showOnscreenMessage(TTLocalizer.BossBattleNeedMorePies) + taskMgr.doMethodLater(20, self.__howToGetPies, + self.uniqueName("PieAdvice")) + + def __howToGetPies(self, task): + self.__showOnscreenMessage(TTLocalizer.BossBattleHowToGetPies) + + def __howToThrowPies(self, task): + self.__showOnscreenMessage(TTLocalizer.BossBattleHowToThrowPies) + + def __foundPieButton(self): + self.everThrownPie = 1 + self.__clearOnscreenMessage() + taskMgr.remove(self.uniqueName("PieAdvice")) + + + def __pieSplat(self, toon, pieCode): + assert self.notify.debug("__pieSplat()") + # A pie thrown by localToon or some other toon hit something; + # show a visible reaction if that something is the boss. + if base.config.GetBool('easy-vp', 0): + if not self.dizzy: + pieCode = ToontownGlobals.PieCodeBossInsides + if pieCode == ToontownGlobals.PieCodeBossInsides: + if toon == localAvatar: + self.d_hitBossInsides() + + self.flashRed() + + elif pieCode == ToontownGlobals.PieCodeBossCog: + if toon == localAvatar: + self.d_hitBoss(1) + + if self.dizzy: + self.flashRed() + self.doAnimate('hit', now=1) + + def __localPieSplat(self, pieCode, entry): + assert self.notify.debug("__localPieSplat()") + # A pie thrown by localToon toon hit something; tell the AI if + # we hit another toon. + if pieCode != ToontownGlobals.PieCodeToon: + return + + avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId') + if avatarDoId == '': + self.notify.warning("Toon %s has no avatarDoId tag." % (repr(entry.getIntoNodePath()))) + return + + doId = int(avatarDoId) + if doId != localAvatar.doId: + self.d_hitToon(doId) + + + def __finalPieSplat(self, toon, pieCode): + assert self.notify.debug("__finalPieSplat()") + # This is the final pie toss that starts the boss's fall. + # It's really just a formality, since we're already in the + # Victory state. + if pieCode != ToontownGlobals.PieCodeBossCog: + return + + # Tell the AI; the AI will then immediately transition to Victory + # state. + self.sendUpdate('finalPieSplat', []) + + # We don't care to hear any more about pies hitting the boss. + self.ignore('pieSplat') + + def cagedToonBattleThree(self, index, avId): + assert self.notify.debug('cagedToonBattleThree(%s, %s)' % (index, avId)) + + # The caged toon says something during battle three. + str = TTLocalizer.CagedToonBattleThree.get(index) + if str: + toonName = '' + if avId: + toon = self.cr.doId2do.get(avId) + if not toon: + self.cagedToon.clearChat() + return + toonName = toon.getName() + text = str % { 'toon' : toonName } + self.cagedToon.setChatAbsolute(text, CFSpeech | CFTimeout) + + else: + self.cagedToon.clearChat() + + def cleanupAttacks(self): + # Stops any attack currently running. + self.__cleanupStrafe() + + def __cleanupStrafe(self): + if self.strafeInterval: + self.strafeInterval.finish() + self.strafeInterval = None + + def doStrafe(self, side, direction): + assert self.notify.debug('doStrafe(%s, %s)' % (side, direction)) + + # Spit a stream of gears out either the front or the back + # door, from left to right (or from right to left). + + gearRoot = self.rotateNode.attachNewNode('gearRoot') + if side == 0: + gearRoot.setPos(0, -7, 3) + gearRoot.setHpr(180, 0, 0) + door = self.doorA + else: + gearRoot.setPos(0, 7, 3) + door = self.doorB + + gearRoot.setTag('attackCode', str(ToontownGlobals.BossCogStrafeAttack)) + gearModel = self.getGearFrisbee() + gearModel.setScale(0.1) + + t = self.getBossDamage() / 100.0 + + gearTrack = Parallel() + + # numGears ranges from 4 to 10 + # time ranges from 5 to 1 + numGears = int(4 + 6 * t + 0.5) + time = 5.0 - 4.0 * t + + spread = 60 * math.pi / 180.0 + if direction == 1: + spread = -spread + + dist = 50 + rate = time / numGears + for i in range(numGears): + node = gearRoot.attachNewNode(str(i)) + node.hide() + node.setPos(0, 0, 0) + gear = gearModel.instanceTo(node) + angle = ((float(i) / (numGears - 1)) - 0.5) * spread + x = dist * math.sin(angle) + y = dist * math.cos(angle) + h = random.uniform(-720, 720) + gearTrack.append(Sequence( + Wait(i * rate), + Func(node.show), + Parallel(node.posInterval(1, Point3(x, y, 0), fluid = 1), + node.hprInterval(1, VBase3(h, 0, 0), fluid = 1), + Sequence(SoundInterval(self.strafeSfx[i], volume = 0.2, node = self), duration = 0), + ), + Func(node.detachNode))) + + seq = Sequence( + Func(door.request, 'open'), + Wait(0.7), + gearTrack, + Func(door.request, 'close'), + ) + + self.__cleanupStrafe() + self.strafeInterval = seq + seq.start() diff --git a/toontown/src/suit/DistributedSellbotBossAI.py b/toontown/src/suit/DistributedSellbotBossAI.py new file mode 100644 index 0000000..bae6d28 --- /dev/null +++ b/toontown/src/suit/DistributedSellbotBossAI.py @@ -0,0 +1,624 @@ +from otp.ai.AIBaseGlobal import * +from direct.distributed.ClockDelta import * +import DistributedBossCogAI +from direct.directnotify import DirectNotifyGlobal +from otp.avatar import DistributedAvatarAI +import DistributedSuitAI +from toontown.battle import BattleExperienceAI +from direct.fsm import FSM +from toontown.toonbase import ToontownGlobals +from toontown.toon import InventoryBase +from toontown.toonbase import TTLocalizer +from toontown.battle import BattleBase +from toontown.toon import NPCToons +import SuitDNA +import random + +class DistributedSellbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSellbotBossAI') + + # The maximum number of hits we will take while dizzy, once our + # damage crosses the given threshold. + limitHitCount = 6 + hitCountDamage = 35 + + # The number of pies we award for touching the cage. + numPies = ToontownGlobals.FullPies + + def __init__(self, air): + DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 's') + FSM.FSM.__init__(self, 'DistributedSellbotBossAI') + + # These are the suits that the Boss Cog is seen promoting when + # the Toons come in. Their only purpose is to provide some + # context in the movie; they don't interact with any of the + # Toons. + self.doobers = [] + + # Choose an NPC toon to be in the cage. + self.cagedToonNpcId = random.choice(NPCToons.npcFriends.keys()) + + self.bossMaxDamage = ToontownGlobals.SellbotBossMaxDamage + self.recoverRate = 0 + self.recoverStartTime = 0 + + def delete(self): + return DistributedBossCogAI.DistributedBossCogAI.delete(self) + + def getHoodId(self): + return ToontownGlobals.SellbotHQ + + def getCagedToonNpcId(self): + return self.cagedToonNpcId + + def magicWordHit(self, damage, avId): + # Called by the magic word "~bossBattle hit damage" + if self.attackCode != ToontownGlobals.BossCogDizzyNow: + # Make him dizzy first. + self.hitBossInsides() + self.hitBoss(damage) + + def hitBoss(self, bossDamage): + # This is sent when the client successfully hits the boss during + # battle three. We have to take the client's word for it here. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBoss(%s, %s)' % (self.doId, avId, bossDamage)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBoss from unknown avatar'): + return + + # We only expect a bossDamage value of 1 from the client. If + # a client ever sends some other value, it's cause for + # immediate and strong suspicion of a hacked client. However, + # we honor the strange bossDamage value, partly to make it + # convenient for testing, and partly to help trap greedy + # hackers into revealing themselves repeatedly. + self.validate(avId, bossDamage == 1, + 'invalid bossDamage %s' % (bossDamage)) + if bossDamage < 1: + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + # This was just a late hit; ignore it. + return + + if self.attackCode != ToontownGlobals.BossCogDizzyNow: + # The boss wasn't in his vulnerable state, so it doesn't count. + return + + bossDamage = min(self.getBossDamage() + bossDamage, self.bossMaxDamage) + self.b_setBossDamage(bossDamage, 0, 0) + + if self.bossDamage >= self.bossMaxDamage: + # Only set this state locally--the clients will go there + # by themselves when the boss movie finishes playing out. + self.setState('NearVictory') + + else: + self.__recordHit() + + def hitBossInsides(self): + # This is sent when the client successfully lobs a pie inside + # the boss's chassis. + + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitBossInsides(%s)' % (self.doId, avId)) + + if not self.validate(avId, avId in self.involvedToons, + 'hitBossInsides from unknown avatar'): + return + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + # This was just a late hit; ignore it. + return + + self.b_setAttackCode(ToontownGlobals.BossCogDizzyNow) + self.b_setBossDamage(self.getBossDamage(), 0, 0) + + def hitToon(self, toonId): + # This is sent when the client pies another toon during battle + # three. We have to take the client's word for it here too. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.hitToon(%s, %s)' % (self.doId, avId, toonId)) + + if not self.validate(avId, avId != toonId, + 'hitToon on self'): + return + + if avId not in self.involvedToons or toonId not in self.involvedToons: + # Not an error, since either toon might have just died. + return + + toon = self.air.doId2do.get(toonId) + if toon: + self.healToon(toon, 1) + + + def touchCage(self): + # This is sent from the client when he touches the cage, + # requesting more pies. + avId = self.air.getAvatarIdFromSender() + assert self.notify.debug('%s.touchCage(%s)' % (self.doId, avId)) + + currState = self.getCurrentOrNextState() + if currState != 'BattleThree' and currState != 'NearVictory': + return + + if not self.validate(avId, avId in self.involvedToons, + 'touchCage from unknown avatar'): + return + + toon = simbase.air.doId2do.get(avId) + if toon: + toon.b_setNumPies(self.numPies) + toon.__touchedCage = 1 + self.__goodJump(avId) + + def finalPieSplat(self): + # A client reports he observed the final pie splat that sends + # the boss over the edge. This moves the state into Victory. + if self.state != 'NearVictory': + return + + self.b_setState('Victory') + + def doNextAttack(self, task): + assert self.notify.debug("%s.doNextAttack()" % (self.doId)) + # Choose an attack and do it. + if self.attackCode == ToontownGlobals.BossCogDizzyNow: + # We always choose this particular attack when recovering + # from dizzy. It's really the same as the front attack, + # with extra time for standing up first. + attackCode = ToontownGlobals.BossCogRecoverDizzyAttack + + else: + # Choose an attack at random. + attackCode = random.choice( + [ToontownGlobals.BossCogAreaAttack, + ToontownGlobals.BossCogFrontAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ToontownGlobals.BossCogDirectedAttack, + ]) + + if attackCode == ToontownGlobals.BossCogAreaAttack: + self.__doAreaAttack() + elif attackCode == ToontownGlobals.BossCogDirectedAttack: + self.__doDirectedAttack() + else: + self.b_setAttackCode(attackCode) + + def __doAreaAttack(self): + self.b_setAttackCode(ToontownGlobals.BossCogAreaAttack) + + # Boost the recovery rate a bit with each area attack. + if self.recoverRate: + newRecoverRate = min(200, self.recoverRate * 1.2) + else: + newRecoverRate = 2 + now = globalClock.getFrameTime() + self.b_setBossDamage(self.getBossDamage(), newRecoverRate, now) + + def __doDirectedAttack(self): + if self.nearToons: + toonId = random.choice(self.nearToons) + self.b_setAttackCode(ToontownGlobals.BossCogDirectedAttack, toonId) + + else: + # If we don't have anyone nearby to aim at, stomp in + # frustration. + self.__doAreaAttack() + + def b_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + self.d_setBossDamage(bossDamage, recoverRate, recoverStartTime) + self.setBossDamage(bossDamage, recoverRate, recoverStartTime) + + def setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + assert self.notify.debug('%s.setBossDamage(%s, %s)' % (self.doId, bossDamage, recoverRate)) + self.bossDamage = bossDamage + self.recoverRate = recoverRate + self.recoverStartTime = recoverStartTime + + def getBossDamage(self): + now = globalClock.getFrameTime() + elapsed = now - self.recoverStartTime + + # It is important that we consistently represent bossDamage as + # an integer value, so there is never any chance of client and + # AI disagreeing about whether bossDamage < bossMaxDamage. + return int(max(self.bossDamage - self.recoverRate * elapsed / 60.0, 0)) + + def d_setBossDamage(self, bossDamage, recoverRate, recoverStartTime): + timestamp = globalClockDelta.localToNetworkTime(recoverStartTime) + self.sendUpdate('setBossDamage', [bossDamage, recoverRate, timestamp]) + + def waitForNextStrafe(self, delayTime): + currState = self.getCurrentOrNextState() + if (currState == 'BattleThree'): + assert self.notify.debug("%s.Waiting %s seconds for next strafe." % (self.doId, delayTime)) + taskName = self.uniqueName('NextStrafe') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.doNextStrafe, taskName) + else: + assert self.notify.debug("%s.Not doing another strafe in state %s." % (self.doId, currState)) + + def stopStrafes(self): + taskName = self.uniqueName('NextStrafe') + taskMgr.remove(taskName) + + def doNextStrafe(self, task): + if self.attackCode != ToontownGlobals.BossCogDizzyNow: + side = random.choice([0, 1]) + direction = random.choice([0, 1]) + assert self.notify.debug('%s.doStrafe(%s, %s)' % (self.doId, side, direction)) + self.sendUpdate('doStrafe', [side, direction]) + + # How long to wait for the next strafe? + + delayTime = 9 + self.waitForNextStrafe(delayTime) + + + def __sendDooberIds(self): + dooberIds = [] + for suit in self.doobers: + dooberIds.append(suit.doId) + + self.sendUpdate('setDooberIds', [dooberIds]) + + def d_cagedToonBattleThree(self, index, avId): + self.sendUpdate('cagedToonBattleThree', [index, avId]) + + def formatReward(self): + # Returns the reward indication to write to the event log. + return str(self.cagedToonNpcId) + + def makeBattleOneBattles(self): + self.postBattleState = 'RollToBattleTwo' + self.initializeBattles(1, ToontownGlobals.SellbotBossBattleOnePosHpr) + + def generateSuits(self, battleNumber): + if battleNumber == 1: + # Battle 1 + return self.invokeSuitPlanner(9, 0) + else: + # Battle 2 + return self.invokeSuitPlanner(10, 1) + + def removeToon(self, avId): + toon = simbase.air.doId2do.get(avId) + if toon: + toon.b_setNumPies(0) + + DistributedBossCogAI.DistributedBossCogAI.removeToon(self, avId) + + + ##### Off state ##### + + def enterOff(self): + DistributedBossCogAI.DistributedBossCogAI.enterOff(self) + self.__resetDoobers() + + ##### Elevator state ##### + + def enterElevator(self): + DistributedBossCogAI.DistributedBossCogAI.enterElevator(self) + self.b_setBossDamage(0, 0, 0) + + ##### Introduction state ##### + + def enterIntroduction(self): + DistributedBossCogAI.DistributedBossCogAI.enterIntroduction(self) + + # Make up some suits for the entrance movie. + self.__makeDoobers() + + self.b_setBossDamage(0, 0, 0) + + def exitIntroduction(self): + DistributedBossCogAI.DistributedBossCogAI.exitIntroduction(self) + + self.__resetDoobers() + + ##### BattleOne state ##### + + ##### RollToBattleTwo state ##### + + def enterRollToBattleTwo(self): + assert self.notify.debug('%s.enterRollToBattleTwo()' % (self.doId)) + + # Reshuffle the remaining toons for the second pair of battles. + self.divideToons() + + # The clients will play a movie showing the boss cog moving up + # to the top of the area for the phase 2 battle. + + self.barrier = self.beginBarrier( + "RollToBattleTwo", self.involvedToons, 45, + self.__doneRollToBattleTwo) + + def __doneRollToBattleTwo(self, avIds): + self.b_setState("PrepareBattleTwo") + + def exitRollToBattleTwo(self): + self.ignoreBarrier(self.barrier) + + + ##### PrepareBattleTwo state ##### + + def enterPrepareBattleTwo(self): + assert self.notify.debug('%s.enterPrepareBattleTwo()' % (self.doId)) + + # The clients will focus in on the caged toon giving some + # encouragement and advice. We wait for the clients to click + # through. + + self.barrier = self.beginBarrier( + "PrepareBattleTwo", self.involvedToons, 30, + self.__donePrepareBattleTwo) + + self.makeBattleTwoBattles() + + def __donePrepareBattleTwo(self, avIds): + self.b_setState("BattleTwo") + + def exitPrepareBattleTwo(self): + self.ignoreBarrier(self.barrier) + + ##### BattleTwo state ##### + + def makeBattleTwoBattles(self): + # Create the battle objects. + self.postBattleState = 'PrepareBattleThree' + self.initializeBattles(2, ToontownGlobals.SellbotBossBattleTwoPosHpr) + + def enterBattleTwo(self): + assert self.notify.debug('%s.enterBattleTwo()' % (self.doId)) + + # The boss cog unleashes the second round of Cogs from his + # belly. + + # Begin the battles. + if self.battleA: + self.battleA.startBattle(self.toonsA, self.suitsA) + if self.battleB: + self.battleB.startBattle(self.toonsB, self.suitsB) + + def exitBattleTwo(self): + self.resetBattles() + + ##### PrepareBattleThree state ##### + + def enterPrepareBattleThree(self): + assert self.notify.debug('%s.enterPrepareBattleThree()' % (self.doId)) + + # The clients will focus in on the caged toon giving some + # encouragement and advice. We wait for the clients to click + # through. + + self.barrier = self.beginBarrier( + "PrepareBattleThree", self.involvedToons, 30, + self.__donePrepareBattleThree) + + def __donePrepareBattleThree(self, avIds): + self.b_setState("BattleThree") + + def exitPrepareBattleThree(self): + self.ignoreBarrier(self.barrier) + + ##### BattleThree state ##### + + def enterBattleThree(self): + assert self.notify.debug('%s.enterBattleThree()' % (self.doId)) + self.resetBattles() + self.setPieType() + self.b_setBossDamage(0, 0, 0) + self.battleThreeStart = globalClock.getFrameTime() + + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.__touchedCage = 0 + + self.waitForNextAttack(5) + self.waitForNextStrafe(9) + + self.cagedToonDialogIndex = 100 + self.__saySomethingLater() + + def __saySomething(self, task = None): + # The caged toon looks for something to say. + index = None + avId = 0 + + if len(self.involvedToons) == 0: + return + + # Choose a random Toon to "address" from the toons + # involved. If that toon has touched the cage, give him + # the next bit of advice; otherwise, admonish him. + avId = random.choice(self.involvedToons) + toon = simbase.air.doId2do.get(avId) + if toon.__touchedCage: + if self.cagedToonDialogIndex <= TTLocalizer.CagedToonBattleThreeMaxAdvice: + index = self.cagedToonDialogIndex + self.cagedToonDialogIndex += 1 + else: + # We've used up all the advice. Occasionally pick one at + # random to remind everyone. + if random.random() < 0.2: + index = random.randrange(100, TTLocalizer.CagedToonBattleThreeMaxAdvice + 1) + + else: + # The toon hasn't touched the cage yet. Tell him how to. + index = random.randrange(20, TTLocalizer.CagedToonBattleThreeMaxTouchCage + 1) + + if index: + self.d_cagedToonBattleThree(index, avId) + + self.__saySomethingLater() + + def __saySomethingLater(self, delayTime = 15): + # Say something in a few seconds. + taskName = self.uniqueName('CagedToonSaySomething') + taskMgr.remove(taskName) + taskMgr.doMethodLater(delayTime, self.__saySomething, taskName) + + def __goodJump(self, avId): + currState = self.getCurrentOrNextState() + if currState != 'BattleThree': + return + + index = random.randrange(10, TTLocalizer.CagedToonBattleThreeMaxGivePies + 1) + self.d_cagedToonBattleThree(index, avId) + + self.__saySomethingLater() + + def exitBattleThree(self): + self.stopAttacks() + self.stopStrafes() + taskName = self.uniqueName('CagedToonSaySomething') + taskMgr.remove(taskName) + + ##### NearVictory state ##### + + def enterNearVictory(self): + assert self.notify.debug('%s.enterNearVictory()' % (self.doId)) + self.resetBattles() + + def exitNearVictory(self): + pass + + ##### Victory state ##### + + def enterVictory(self): + assert self.notify.debug('%s.enterVictory()' % (self.doId)) + self.resetBattles() + + # add a suit-defeat entry for the VP + # based on code in DistributedBattleBaseAI.__movieDone + self.suitsKilled.append({ + 'type' : None, + 'level' : None, + 'track' : self.dna.dept, + 'isSkelecog' : 0, + 'isForeman' : 0, + 'isVP' : 1, + 'isCFO' : 0, + 'isSupervisor' : 0, + 'isVirtual' : 0, + 'activeToons' : self.involvedToons[:], + }) + + self.barrier = self.beginBarrier( + "Victory", self.involvedToons, 10, + self.__doneVictory) + + def __doneVictory(self, avIds): + + # Tell the client the information it needs to generate a + # reward movie. + self.d_setBattleExperience() + + # First, move the clients into the reward start. They'll + # build the reward movies immediately. + self.b_setState("Reward") + + # Now that the clients have started to build their reward + # movies, we can actually assign all the experience and + # rewards. If we did this sooner, the quest reward panel + # would show the rewards being applied twice. + + # There is no race condition between AI and client here + # because these messages are sent sequentially on the wire. + + BattleExperienceAI.assignRewards( + self.involvedToons, self.toonSkillPtsGained, + self.suitsKilled, + ToontownGlobals.dept2cogHQ(self.dept), self.helpfulToons) + + # Don't forget to give the toon the NPC SOS reward and the + # promotion! + for toonId in self.involvedToons: + toon = self.air.doId2do.get(toonId) + if toon: + if not toon.attemptAddNPCFriend(self.cagedToonNpcId, numCalls = 1): + self.notify.info("%s.unable to add NPCFriend %s to %s." % (self.doId, self.cagedToonNpcId, toonId)) + toon.b_promote(self.deptIndex) + + def exitVictory(self): + self.takeAwayPies() + + ##### Frolic state ##### + + def enterFrolic(self): + DistributedBossCogAI.DistributedBossCogAI.enterFrolic(self) + self.b_setBossDamage(0, 0, 0) + + + def __resetDoobers(self): + # Free the suits made with an earlier call to __makeDoobers(). + for suit in self.doobers: + suit.requestDelete() + self.doobers = [] + + def __makeDoobers(self): + + # Generate a handful of suits that we can see the Boss Cog + # promoting as we come in. These shouldn't really need to be + # true DistributedSuits, but I tried to make just a Suit by + # itself and it just doesn't work. It doesn't do any real + # harm to make DistributedSuits, anyway. + + self.__resetDoobers() + + # 8 suits, 4 on each side. + for i in range(8): + suit = DistributedSuitAI.DistributedSuitAI(self.air, None) + + # Choose a random level for each new suit. + level = random.randrange(len(SuitDNA.suitsPerLevel)) + + # And a random type to match the level. + suit.dna = SuitDNA.SuitDNA() + suit.dna.newSuitRandom(level = level, dept = self.dna.dept) + suit.setLevel(level) + + suit.generateWithRequired(self.zoneId) + self.doobers.append(suit) + + self.__sendDooberIds() + + def setPieType(self): + # Sets everyone's pie type for the battle. + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.d_setPieType(4) + + def takeAwayPies(self): + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + toon.b_setNumPies(0) + + def __recordHit(self): + # Records that the boss has been hit, and counts the number of + # hits in a period of time. + now = globalClock.getFrameTime() + + self.hitCount += 1 + if (self.hitCount < self.limitHitCount or self.bossDamage < self.hitCountDamage): + assert self.notify.debug("%s. %s hits, ignoring." % (self.doId, self.hitCount)) + return + + assert self.notify.debug("%s. %s hits!" % (self.doId, self.hitCount)) + + # Launch an immediate front attack. + self.b_setAttackCode(ToontownGlobals.BossCogRecoverDizzyAttack) + + diff --git a/toontown/src/suit/DistributedStageSuit.py b/toontown/src/suit/DistributedStageSuit.py new file mode 100644 index 0000000..6a24e36 --- /dev/null +++ b/toontown/src/suit/DistributedStageSuit.py @@ -0,0 +1,35 @@ +from toontown.suit import DistributedFactorySuit +from toontown.suit.Suit import * +from direct.directnotify import DirectNotifyGlobal +from direct.actor import Actor +from otp.avatar import Avatar +import SuitDNA +from toontown.toonbase import ToontownGlobals +from pandac.PandaModules import * +from toontown.battle import SuitBattleGlobals +from direct.task import Task +from toontown.battle import BattleProps +from toontown.toonbase import TTLocalizer +import string + +class DistributedStageSuit(DistributedFactorySuit.DistributedFactorySuit): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedStageSuit') + + def setCogSpec(self, spec): + self.spec = spec + self.setPos(spec['pos']) + self.setH(spec['h']) + self.originalPos = spec['pos'] + self.escapePos = spec['pos'] + self.pathEntId = spec['path'] + self.behavior = spec['behavior'] + self.skeleton = spec['skeleton'] + self.boss = spec['boss'] + self.revives = spec.get('revives') + # the AI now sets this, it's a required field + #if self.skeleton: + # self.makeSkeleton() + if self.reserve: + self.reparentTo(hidden) + else: + self.doReparent() diff --git a/toontown/src/suit/DistributedStageSuitAI.py b/toontown/src/suit/DistributedStageSuitAI.py new file mode 100644 index 0000000..9808a06 --- /dev/null +++ b/toontown/src/suit/DistributedStageSuitAI.py @@ -0,0 +1,15 @@ +from toontown.suit import DistributedFactorySuitAI +from direct.directnotify import DirectNotifyGlobal + +class DistributedStageSuitAI(DistributedFactorySuitAI.DistributedFactorySuitAI): + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedStageSuitAI') + + def isForeman(self): + return 0 + + def isSupervisor(self): + return self.boss + + def isVirtual(self): + return self.virtual diff --git a/toontown/src/suit/DistributedSuit.py b/toontown/src/suit/DistributedSuit.py new file mode 100644 index 0000000..c95debe --- /dev/null +++ b/toontown/src/suit/DistributedSuit.py @@ -0,0 +1,1236 @@ +"""DistributedSuit module: contains the DistributedSuit class""" + +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import * +from direct.directtools.DirectGeometry import CLAMP +from direct.task import Task +from otp.avatar import DistributedAvatar +import Suit +from toontown.toonbase import ToontownGlobals +from toontown.battle import DistributedBattle +from direct.fsm import ClassicFSM, State +from direct.fsm import State +import SuitTimings +import SuitBase +import DistributedSuitPlanner +from direct.directnotify import DirectNotifyGlobal +import SuitDialog +from toontown.battle import BattleProps +from toontown.distributed.DelayDeletable import DelayDeletable +import math +import copy +import DistributedSuitBase +from otp.otpbase import OTPLocalizer +import random + + +# how far outside of a door to stop in WalkFromStreet mode before +# transitioning to ToSuitBuilding or ToToonBuilding. This distance is +# chosen to roughly match what the DistributedDoor code expects to +# see. +STAND_OUTSIDE_DOOR = 2.5 + +# how long after a suit exits a movement interruption that +# the suit ignore's local battle collisions +# +BATTLE_IGNORE_TIME = 6 +BATTLE_WAIT_TIME = 3 +CATCHUP_SPEED_MULTIPLIER = 3 + +# wether or not to enable battle detection when a suit +# enters bellicose mode +# +ALLOW_BATTLE_DETECT = 1 + + +class DistributedSuit(DistributedSuitBase.DistributedSuitBase, DelayDeletable): + """ + DistributedSuit class: a 'bad guy' which exists on each client's + machine and helps direct the Suits which exist on the server. This + is the object that each individual player interacts with when + initiating combat. This guy has all of the attributes of a + DistributedSuitAI object, plus some more such as collision info + + Attributes: + Derived plus... + DistributedSuit_initialized (integer), flag indicating if this + suit has been properly initialized + fsm, the state machine that this client suit will use, this + includes states of detecting collisions with toons and + entering battles + timeBehind, approximately time in seconds that this suit is + physically behind in it's path from where the server thinks + it is, it is up to this suit to make up for this lost time + moveLerp, used for non-interval movement, keeps track of the + suit's current move lerp so it can be stopped at any moment + startTime, time given to this suit from the server that indicates + when this suit first started moving along its current path + resumePos, a world location of where the suit last was before it + joined a battle, when the suit resumes its course, it moves + to this location before following the normal path again + mtrack, the suit's current track to animate its motion. + dna, dna created for the suit, sent to us from the server + """ + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuit') + + # add extra info to the suit's name such as DoId and zone that the + # suit thinks it is in + # + ENABLE_EXPANDED_NAME = 0 + + def __init__(self, cr): + try: + self.DistributedSuit_initialized + return + except: + self.DistributedSuit_initialized = 1 + + DistributedSuitBase.DistributedSuitBase.__init__(self, cr) + + # our reference to the local hood's suit planner, the doId of + # it is sent to us from the server side suit + # + self.spDoId = None + + # Our current path information. + self.pathEndpointStart = 0 + self.pathEndpointEnd = 0 + self.minPathLen = 0 + self.maxPathLen = 0 + self.pathPositionIndex = 0 + self.pathPositionTimestamp = 0.0 + self.pathState = 0 + self.path = None + self.localPathState = 0 + + self.currentLeg = -1 + self.pathStartTime = 0.0 + self.legList = None + + # remember the suit's initial and end state so we dont have to + # calculate them more than once + # + self.initState = None + self.finalState = None + + # Indicates that the suit is in a building or not + self.buildingSuit = 0 + + # Set up the DistributedSuit state machine + self.fsm = ClassicFSM.ClassicFSM( + 'DistributedSuit', + [State.State('Off', + self.enterOff, + self.exitOff, + ['FromSky', + 'FromSuitBuilding', + 'Walk', + 'Battle', + 'neutral', + 'ToToonBuilding', + 'ToSuitBuilding', + 'ToCogHQ', + 'FromCogHQ', + 'ToSky', + 'FlyAway', + 'DanceThenFlyAway', + 'WalkToStreet', + 'WalkFromStreet']), + State.State('FromSky', + self.enterFromSky, + self.exitFromSky, + ['Walk', + 'Battle', + 'neutral', + 'ToSky', + 'WalkFromStreet']), + State.State('FromSuitBuilding', + self.enterFromSuitBuilding, + self.exitFromSuitBuilding, + ['WalkToStreet', + 'Walk', + 'Battle', + 'neutral', + 'ToSky']), + State.State('WalkToStreet', + self.enterWalkToStreet, + self.exitWalkToStreet, + ['Walk', + 'Battle', + 'neutral', + 'ToSky', + 'ToToonBuilding', + 'ToSuitBuilding', + 'ToCogHQ', + 'WalkFromStreet']), + State.State('WalkFromStreet', + self.enterWalkFromStreet, + self.exitWalkFromStreet, + ['ToToonBuilding', + 'ToSuitBuilding', + 'ToCogHQ', + 'Battle', + 'neutral', + 'ToSky']), + State.State('Walk', + self.enterWalk, + self.exitWalk, + ['WaitForBattle', + 'Battle', + 'neutral', + 'WalkFromStreet', + 'ToSky', + 'ToCogHQ', + 'Walk']), + State.State('Battle', + self.enterBattle, + self.exitBattle, + ['Walk', + 'ToToonBuilding', + 'ToCogHQ', + 'ToSuitBuilding', + 'ToSky']), + State.State('neutral', + self.enterNeutral, + self.exitNeutral, + []), + State.State('WaitForBattle', + self.enterWaitForBattle, + self.exitWaitForBattle, + ['Battle', + 'neutral', + 'Walk', + 'WalkToStreet', + 'WalkFromStreet', + 'ToToonBuilding', + 'ToCogHQ', + 'ToSuitBuilding', + 'ToSky']), + State.State('ToToonBuilding', + self.enterToToonBuilding, + self.exitToToonBuilding, + ['neutral', 'Battle']), + State.State('ToSuitBuilding', + self.enterToSuitBuilding, + self.exitToSuitBuilding, + ['neutral', 'Battle']), + State.State('ToCogHQ', + self.enterToCogHQ, + self.exitToCogHQ, + ['neutral', 'Battle']), + State.State('FromCogHQ', + self.enterFromCogHQ, + self.exitFromCogHQ, + ['neutral', 'Battle', 'Walk']), + State.State('ToSky', + self.enterToSky, + self.exitToSky, + ['Battle']), + State.State('FlyAway', + self.enterFlyAway, + self.exitFlyAway, + []), + State.State('DanceThenFlyAway', + self.enterDanceThenFlyAway, + self.exitDanceThenFlyAway, + []), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + + self.fsm.enterInitialState() + + self.soundSequenceList = [] + self.__currentDialogue = None + + def generate(self): + DistributedSuitBase.DistributedSuitBase.generate(self) + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + for soundSequence in self.soundSequenceList: + soundSequence.finish() + + self.soundSequenceList = [] + + self.notify.debug("DistributedSuit %d: disabling" % self.getDoId()) + self.resumePath(0) + self.stopPathNow() + self.setState('Off') + DistributedSuitBase.DistributedSuitBase.disable(self) + return + + def delete(self): + """ + This method is called when the DistributedObject is + permanently removed from the world and deleted from + the cache. + """ + try: + self.DistributedSuit_deleted + except: + self.DistributedSuit_deleted = 1 + self.notify.debug("DistributedSuit %d: deleting" % self.getDoId()) + + del self.fsm + DistributedSuitBase.DistributedSuitBase.delete(self) + return + + def setPathEndpoints(self, start, end, minPathLen, maxPathLen): + """ + This distributed call is sent from the AI when the suit is + created, and defines the path along the street which the suit + should walk. + + start and end are indices into the SuitPlanner's array of + DNASuitPoints. With these two indices, combined with the + minPathLen and maxPathLen constraints, we can unambiguously + define a complete path, along with all timing information + along that path. + """ + if self.pathEndpointStart == start and \ + self.pathEndpointEnd == end and \ + self.minPathLen == minPathLen and \ + self.maxPathLen == maxPathLen and \ + self.path != None: + # The path endpoints haven't changed since last time. + # Presumably this will happen only when we re-generate a + # suit that was previously disabled (e.g. we encounter the + # same suit in the street that we left behind a few + # minutes ago). + + # In this case, we need do nothing, since the path is + # already set up from last time. + return + + self.pathEndpointStart = start + self.pathEndpointEnd = end + self.minPathLen = minPathLen + self.maxPathLen = maxPathLen + + self.path = None + self.pathLength = 0 + self.currentLeg = -1 + self.legList = None + + if self.maxPathLen == 0: + # If the path is empty, do nothing. This might be the + # case for a suit within a building, for instance. + return + + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + self.startPoint = self.sp.pointIndexes[self.pathEndpointStart] + self.endPoint = self.sp.pointIndexes[self.pathEndpointEnd] + + # Now determine the complete path information based on the + # starting and ending points. + path = self.sp.genPath(self.startPoint, self.endPoint, + self.minPathLen, self.maxPathLen) + self.setPath(path) + self.makeLegList() + + def verifySuitPlanner(self): + """ + Ensures that this suit has a SuitPlanner set. Normally this + is specified at suit creation time, but sometimes the server + farts and creates the suit before its SuitPlanner object is + created, leaving self.sp set to None. This function checks + that condition and recovers from it, if possible. + + The return value is true if a suit planner is available, false + if not. + """ + if self.sp == None and self.spDoId != 0: + self.notify.warning("Suit %d does not have a suit planner! Expected SP doId %s." % (self.doId, self.spDoId)) + self.sp = self.cr.doId2do.get(self.spDoId, None) + + if self.sp == None: + assert self.notify.debug("Cannot create path for suit %d" % (self.doId)) + return 0 + return 1 + + def setPathPosition(self, index, timestamp): + """ + setPathPosition(self, int index, uint16 timestamp) + + This distributed call comes from the AI at suit creation time, + and every once and a while thereafter, to specify a time in + the recent past at which the suit should have passed the + indicated waypoint. + + Updating this information wouldn't normally be necessary--when + the AI sends an update, we don't actually gain any new + information--but it's important to keep the timestamp fresh, + since we use a 16-bit timestamp, which keeps time for only + about 5 minutes. + """ + + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + # If we didn't generate a path yet--possibly because of a + # failed setPathEndpoints()--try again now. + if self.path == None: + self.setPathEndpoints(self.pathEndpointStart, self.pathEndpointEnd, + self.minPathLen, self.maxPathLen) + + self.pathPositionIndex = index + self.pathPositionTimestamp = globalClockDelta.networkToLocalTime(timestamp) + if self.legList != None: + self.pathStartTime = self.pathPositionTimestamp - self.legList.getStartTime(self.pathPositionIndex) + + def setPathState(self, state): + """ + setPathState(self, int8 state) + + This distributed call comes from the AI at suit creation time, + and as needed thereafter, to indicated whether the suit is + actively walking on its path or not. The values are: + + 0 - The suit is not on its path. Its position is controlled + by other factors, e.g. the battle system. + + 1 - The suit is actively walking on its path. Its position is + based on the values set by setPathEndpoints and + setPathPosition, as well as the current time. + + 2 - The suit is flying away right now. + + 3 = The suit is in Tutorial Mode. It walks on a prescribed + rectangle looking for a battle, but it has no path or path + information. + + 4 - The suit is going to do the victory dance and then + flying away. + + """ + self.pathState = state + self.resumePath(state) + + def debugSuitPosition(self, elapsed, currentLeg, x, y, timestamp): + """ + debugSuitPosition(self, float elapsed, int currentLeg, + float x, float y, timestamp) + + This distributed call comes from the AI from time to time only + when debug-suit-positions is configured #t for the AI. Its + purpose is just to make noise if the client and the AI + disagree about where the suit should be right now. + """ + + # We compare real time to frame time so we'll know how big a + # chug we just got. A message from the AI might have arrived + # any time within the previous frame. + now = globalClock.getFrameTime() + chug = globalClock.getRealTime() - now + messageAge = now - globalClockDelta.networkToLocalTime(timestamp, now) + + # If the message seems to come from too far in the past or the + # future, we're probably just out of sync in general. Nothing + # will be reported accurately until we get back in sync. + if messageAge < -(chug + 0.5) or messageAge > (chug + 1.0): + print "Apparently out of sync with AI by %0.2f seconds. Suggest resync!" % (messageAge) + return + + localElapsed = now - self.pathStartTime + + # At messageAge seconds ago, the AI server saw the suit at + # elapsed seconds along the path. Right now, we see the suit + # at localElapsed seconds along the path. Do we agree thus + # far? + timeDiff = localElapsed - (elapsed + messageAge) + if abs(timeDiff) > 0.2: + # We disagree about where the suit is along the path. + # This could be because we paused the AI or the client. + print "%s (%d) appears to be %0.2f seconds out of sync along its path. Suggest '~cogs sync'." % (self.getName(), self.getDoId(), timeDiff) + return + + # Verify the suit's calculated (x, y) position. This ensures + # our path agrees with that from the AI. + if self.legList == None: + print "%s (%d) doesn't have a legList yet." % (self.getName(), self.getDoId()) + return + + netPos = Point3(x, y, 0.0) + leg = self.legList.getLeg(currentLeg) + calcPos = leg.getPosAtTime(elapsed - leg.getStartTime()) + calcPos.setZ(0.0) + calcDelta = Vec3(netPos - calcPos) + diff = calcDelta.length() + if diff > 4.0: + print "%s (%d) is %0.2f feet from the AI computed path!" % (self.getName(), self.getDoId(), diff) + print "Probably your DNA files are out of sync." + return + + # Now verify the suit's actual position. + localPos = Point3(self.getX(), self.getY(), 0.0) + + localDelta = Vec3(netPos - localPos) + diff = localDelta.length() + if diff > 10.0: + print "%s (%d) in state %s is %0.2f feet from its correct position!" % (self.getName(), self.getDoId(), self.fsm.getCurrentState().getName(), diff) + print "Should be at (%0.2f, %0.2f), but is at (%0.2f, %0.2f)." % (x, y, localPos[0], localPos[1]) + return + + print "%s (%d) is in the correct position." % (self.getName(), self.getDoId()) + + def denyBattle(self): + DistributedSuitBase.DistributedSuitBase.denyBattle(self) + + # Since we just denied a battle on this leg, don't ask again + # until we get to the next leg. + self.disableBattleDetect() + + def resumePath(self, state): + """ + resumePath(self, int state) + + This local call is made to temporarily set the local path + state, independent of what the server believes the state + should be. See setPathState(). + """ + + if self.localPathState != state: + self.localPathState = state + + if state == 0: + # Stop the suit from moving. + self.stopPathNow() + + elif state == 1: + # Start the suit moving. + self.moveToNextLeg(None) + + elif state == 2: + # Fly away right now from wherever we are. + self.stopPathNow() + if self.sp != None: + # Go to off state to make sure we can transition to flyaway + self.setState('Off') + self.setState('FlyAway') + + elif state == 3: + pass + + elif state == 4: + # Fly away right now from wherever we are. + self.stopPathNow() + if self.sp != None: + # Go to off state to make sure we can transition to flyaway + self.setState('Off') + self.setState('DanceThenFlyAway') + + else: + self.notify.error("No such state as: " + str(state)) + + def moveToNextLeg(self, task): + """ + This callback function is spawned by a do-later task as each + leg ETA is reached. It handles moving the suit to the + next leg, and all the bookkeeping that goes along with + that. + """ + if self.legList == None: + self.notify.warning("Suit %d does not have a path!" % (self.getDoId())) + return Task.done + + # First, which leg have we reached, anyway? + now = globalClock.getFrameTime() + elapsed = now - self.pathStartTime + + nextLeg = self.legList.getLegIndexAtTime(elapsed, self.currentLeg) + numLegs = self.legList.getNumLegs() + + if self.currentLeg != nextLeg: + self.currentLeg = nextLeg + self.doPathLeg(self.legList[nextLeg], elapsed - self.legList.getStartTime(nextLeg)) + + assert(self.notify.debug("Suit %d reached leg %d of %d." % + (self.getDoId(), nextLeg, numLegs - 1))) + + # Now, which leg should we next wake up for? + nextLeg += 1 + + # Spawn another do-later to get to the next leg. + if nextLeg < numLegs: + nextTime = self.legList.getStartTime(nextLeg) + delay = nextTime - elapsed + + name = self.taskName("move") + taskMgr.remove(name) + taskMgr.doMethodLater(delay, self.moveToNextLeg, name) + + return Task.done + + def doPathLeg(self, leg, time): + """ + doPathLeg(self, SuitLeg leg, float time) + + Puts the suit on the indicated leg of its journey, and plays + whatever animation is appropriate to that leg. The time + parameter represents the amount of time into the leg that has + already elapsed. + """ + + self.fsm.request(SuitLeg.getTypeName(leg.getType()), [leg, time]) + return 0 + + def stopPathNow(self): + """ + Stops the suit wherever it is on the path. This isn't a clean + stop; it's then up to the caller to do something interesting + with the suit's position and animation. + """ + name = self.taskName("move") + taskMgr.remove(name) + self.currentLeg = -1 + + def calculateHeading(self, a, b): + """ + calculateHeading(self, Point3 a, Point3 b) + + Returns the heading component required to face the suit in the + indicated direction to move from point a to point b. + """ + xdelta = b[0] - a[0] + ydelta = b[1] - a[1] + + if ydelta == 0: + if xdelta > 0: + return -90 + else: + return 90 + + elif xdelta == 0: + if ydelta > 0: + return 0 + else: + return 180 + + else: + angle = math.atan2(ydelta, xdelta) + return rad2Deg(angle) - 90 + + + def beginBuildingMove(self, moveIn, doneEvent, suit=0): + """ + Parameters: moveIn, 1 if move should be into building, 0 for out + doneEvent, event to be sent when move finished + suit, wether or not the building is a suit building + + Append an extra path section to allow for movement + either to inside a building or from inside a + building + """ + + # first find a point in our path so we can get a sense of direction + # and calculate a new point in the opposite direction + # + doorPt = Point3(0) + buildingPt = Point3(0) + streetPt = Point3(0) + if self.virtualPos: + doorPt.assign(self.virtualPos) + else: + doorPt.assign(self.getPos()) + if moveIn: + streetPt = self.prevPointPos() + else: + streetPt = self.currPointPos() + + # calculate a point within the building + # + dx = doorPt[0] - streetPt[0] + dy = doorPt[1] - streetPt[1] + buildingPt = Point3(doorPt[0] + dx, + doorPt[1] + dy, + doorPt[2]) + + if moveIn: + # if we are moving into the building, all we have to do + # is move from the current location to the new location + # within the building, be sure to determine if this is a + # suit or toon building we are going into, the time it takes + # for the move might differ + # + if suit: + moveTime = SuitTimings.toSuitBuilding + else: + moveTime = SuitTimings.toToonBuilding + return self.beginMove(doneEvent, buildingPt, + time = moveTime) + else: + # if we are moving out of the building, we have to teleport + # the suit to the new location within the building and tell the + # suit to move to its original position outside of the building + # + return self.beginMove(doneEvent, doorPt, buildingPt, + time=SuitTimings.fromSuitBuilding) + return None + + def setSPDoId(self, doId): + """ + Function: set this suit's suit planner object from the + distributed object id given from the server suit + Parameters: doId, distributed object id of the suit planner + """ + self.spDoId = doId + self.sp = self.cr.doId2do.get(doId, None) + if self.sp == None and self.spDoId != 0: + self.notify.warning("Suit %s created before its suit planner, %d" % (self.doId, self.spDoId)) + + + def d_requestBattle(self, pos, hpr): + # Make sure the local toon can't continue to run around (and + # potentially start battles with other suits!) + self.cr.playGame.getPlace().setState('WaitForBattle') + self.sendUpdate('requestBattle', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2]]) + + def __handleToonCollision(self, collEntry): + """ + Function: This function is the callback for any + collision events that the collision sphere + for this bad guy might receive + Parameters: collEntry, the collision entry object + """ + if not base.localAvatar.wantBattles: + return + + toonId = base.localAvatar.getDoId() + self.notify.debug('Distributed suit: requesting a Battle with ' + + 'toon: %d' % toonId) + self.d_requestBattle(self.getPos(), self.getHpr()) + + # the suit on this machine only will go into wait for battle while it + # is waiting for word back from the server about our battle request + # + self.setState('WaitForBattle') + + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + def setAnimState(self, state): + """ + This is an alias for setState(). It allows Suits to go through + doors without needing conditionals. + """ + self.setState(state) + + # Specific State functions + + ##### Off state ##### + + # Defined in DistributedSuitBase.py + + ##### FromSky state ##### + + def enterFromSky(self, leg, time): + """ + The suit is flying in from the sky. + """ + self.enableBattleDetect('fromSky', self.__handleToonCollision) + self.loop('neutral', 0) + + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + # Set up a track to fly us down from the sky. + a = leg.getPosA() + b = leg.getPosB() + + # Rotate to face in the direction we'll be walking. + h = self.calculateHeading(a, b) + self.setPosHprScale(a[0], a[1], a[2], + h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = self.beginSupaFlyMove(a, 1, 'fromSky') + self.mtrack.start(time) + + def exitFromSky(self): + self.disableBattleDetect() + self.mtrack.finish() + del self.mtrack + + # Clean up stuff the SupaFly track might not have had a chance + # to (we might have interrupted the track before it was done). + self.detachPropeller() + + ##### WalkToStreet state ##### + + def enterWalkToStreet(self, leg, time): + """ + The suit is walking to the street from a door, either from a toon + or suit building. + """ + self.enableBattleDetect('walkToStreet', self.__handleToonCollision) + self.loop('walk', 0) + + a = leg.getPosA() + b = leg.getPosB() + + # Adjust the vector to start just outside the door, instead of + # intersecting it. + delta = Vec3(b - a) + length = delta.length() + delta *= (length - STAND_OUTSIDE_DOOR) / length + a1 = Point3(b - delta) + + # In walk-to-street mode, we might step on the sidewalk, and + # therefore we need to use the raycast to determine our + # correct height above the ground. + self.enableRaycast(1) + + h = self.calculateHeading(a, b) + self.setHprScale(h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = Sequence( + LerpPosInterval(self, leg.getLegTime(), b, startPos = a1), + name = self.taskName('walkToStreet')) + self.mtrack.start(time) + + def exitWalkToStreet(self): + self.disableBattleDetect() + self.enableRaycast(0) + self.mtrack.finish() + del self.mtrack + + ##### WalkFromStreet state ##### + + # The suit is walking from the street up to a door, either a suit + # building side door or a toon building front door. + + def enterWalkFromStreet(self, leg, time): + self.enableBattleDetect('walkFromStreet', self.__handleToonCollision) + self.loop('walk', 0) + + a = leg.getPosA() + b = leg.getPosB() + + # Adjust the vector to start just outside the door, instead of + # intersecting it. + delta = Vec3(b - a) + length = delta.length() + delta *= (length - STAND_OUTSIDE_DOOR) / length + b1 = Point3(a + delta) + + # In walk-from-street mode, we might step on the sidewalk, and + # therefore we need to use the raycast to determine our + # correct height above the ground. + self.enableRaycast(1) + + h = self.calculateHeading(a, b) + self.setHprScale(h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = Sequence( + LerpPosInterval(self, leg.getLegTime(), b1, startPos = a), + name = self.taskName('walkFromStreet')) + self.mtrack.start(time) + + def exitWalkFromStreet(self): + self.disableBattleDetect() + self.enableRaycast(0) + self.mtrack.finish() + del self.mtrack + + ##### Walk state ##### + + # The suit is just walking around on the street, looking for + # trouble. + + def enterWalk(self, leg, time): + self.enableBattleDetect('bellicose', self.__handleToonCollision) + self.loop('walk', 0) + + a = leg.getPosA() + b = leg.getPosB() + + # In bellicose mode, we're always on the street. Thus, we + # don't need the raycast, and we can leave the height at its + # fixed, known amount. + + h = self.calculateHeading(a, b) + + pos = leg.getPosAtTime(time) + self.setPosHprScale(pos[0], pos[1], pos[2], + h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = Sequence( + LerpPosInterval(self, leg.getLegTime(), b, startPos = a), + name = self.taskName('bellicose')) + self.mtrack.start(time) + + def exitWalk(self): + self.disableBattleDetect() + # Here we only pause, because we might have been interrupted + # in the middle of a walk down the block, and we'd like to + # stay where we were. + self.mtrack.pause() + del self.mtrack + + ##### ToSky state ##### + + # The suit is flying away into the sky, from the normal path. + + def enterToSky(self, leg, time): + self.enableBattleDetect('toSky', self.__handleToonCollision) + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + # Set up a track to fly us up into the sky. + a = leg.getPosA() + b = leg.getPosB() + + # Rotate to face in the direction we were walking. + h = self.calculateHeading(a, b) + self.setPosHprScale(b[0], b[1], b[2], + h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = self.beginSupaFlyMove(b, 0, 'toSky') + self.mtrack.start(time) + + def exitToSky(self): + self.disableBattleDetect() + self.mtrack.finish() + del self.mtrack + + # Clean up stuff the SupaFly track might not have had a chance + # to (we might have interrupted the track before it was done). + self.detachPropeller() + + ##### FromSuitBuilding state ##### + + # The suit is walking out of a suit building side door. + + def enterFromSuitBuilding(self, leg, time): + self.enableBattleDetect('fromSuitBuilding', self.__handleToonCollision) + self.loop('walk', 0) + + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + a = leg.getPosA() + b = leg.getPosB() + + # Adjust the vector to stop just outside the door (the same + # point that WalkToStreet will start at). + delta = Vec3(b - a) + length = delta.length() + delta2 = delta * (self.sp.suitWalkSpeed * leg.getLegTime()) / length + delta *= (length - STAND_OUTSIDE_DOOR) /length + b1 = Point3(b - delta) + a1 = Point3(b1 - delta2) + + # In walk-from-street mode, we might step on the sidewalk, and + # therefore we need to use the raycast to determine our + # correct height above the ground. + self.enableRaycast(1) + + h = self.calculateHeading(a, b) + self.setHprScale(h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = Sequence( + LerpPosInterval(self, leg.getLegTime(), b1, startPos = a1), + name = self.taskName('fromSuitBuilding')) + self.mtrack.start(time) + + def exitFromSuitBuilding(self): + self.disableBattleDetect() + self.mtrack.finish() + del self.mtrack + + ##### ToToonBuilding state ##### + + # The suit is walking through a toon building front door. + + # The DistributedDoor code actually takes over from here; this + # state just needs to stand and wait for the door. + + def enterToToonBuilding(self, leg, time): + self.loop('neutral', 0) + + def exitToToonBuilding(self): + return + + ##### ToSuitBuilding state ##### + + # The suit is walking through a suit building side door. + + def enterToSuitBuilding(self, leg, time): + self.loop('walk', 0) + + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + a = leg.getPosA() + b = leg.getPosB() + + # Adjust the vector to start just outside the door (the same + # point that WalkFromStreet stopped at), and continue on + # through. + delta = Vec3(b - a) + length = delta.length() + delta2 = delta * (self.sp.suitWalkSpeed * leg.getLegTime()) / length + delta *= (length - STAND_OUTSIDE_DOOR) /length + a1 = Point3(a + delta) + b1 = Point3(a1 + delta2) + + self.enableRaycast(1) + + h = self.calculateHeading(a, b) + self.setHprScale(h, 0.0, 0.0, + 1.0, 1.0, 1.0) + + self.mtrack = Sequence( + LerpPosInterval(self, leg.getLegTime(), b1, startPos = a1), + name = self.taskName('toSuitBuilding')) + self.mtrack.start(time) + + def exitToSuitBuilding(self): + self.mtrack.finish() + del self.mtrack + + ##### ToCogHQ state ##### + + # The suit is walking through a CogHQ lobby door. + + # The DistributedDoor code actually takes over from here; this + # state just needs to stand and wait for the door. + + def enterToCogHQ(self, leg, time): + self.loop('neutral', 0) + + def exitToCogHQ(self): + return + + ##### FromCogHQ state ##### + + # The suit is walking through a CogHQ lobby door. + + # The DistributedDoor code actually takes over from here; this + # state just needs to stand and wait for the door. + + def enterFromCogHQ(self, leg, time): + self.loop('neutral', 0) + + # Don't be parented to render initially (the door will + # reparent us when it is time). + self.detachNode() + + def exitFromCogHQ(self): + self.reparentTo(render) + + ##### Battle state ##### + + def enterBattle(self): + DistributedSuitBase.DistributedSuitBase.enterBattle(self) + self.resumePath(0) + + ##### Neutral state ##### + + def enterNeutral(self): + # Get ready to pass through a door. + self.notify.debug('DistributedSuit: Neutral (entering a Door)') + self.resumePath(0) + self.loop('neutral', 0) + + def exitNeutral(self): + return + + ##### WaitForBattle state ##### + + def enterWaitForBattle(self): + DistributedSuitBase.DistributedSuitBase.enterWaitForBattle(self) + self.resumePath(0) + + ##### FlyAway state ##### + + # The suit is flying away into the sky from its current position, + # prompted by a setPathState(2) directive from the server. This + # is almost, but not quite, the same thing as the ToSky state. + + def enterFlyAway(self): + self.enableBattleDetect('flyAway', self.__handleToonCollision) + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + b = Point3(self.getPos()) + + self.mtrack = self.beginSupaFlyMove(b, 0, 'flyAway') + self.mtrack.start() + + def exitFlyAway(self): + self.disableBattleDetect() + self.mtrack.finish() + del self.mtrack + + # Clean up stuff the SupaFly track might not have had a chance + # to (we might have interrupted the track before it was done). + self.detachPropeller() + + ##### DanceThenFlyAway state ##### + + # The suit is flying away into the sky from its current position, + # prompted by a setPathState(2) directive from the server. This + # is almost, but not quite, the same thing as the ToSky state. + + def enterDanceThenFlyAway(self): + self.enableBattleDetect('danceThenFlyAway', self.__handleToonCollision) + # If we don't have a suit planner, try again to look it up. + if not self.verifySuitPlanner(): + return + + danceTrack = self.actorInterval('victory') + + b = Point3(self.getPos()) + flyMtrack = self.beginSupaFlyMove(b, 0, 'flyAway') + self.mtrack = Sequence( + danceTrack, flyMtrack, + name = self.taskName('danceThenFlyAway')) + self.mtrack.start() + + def exitDanceThenFlyAway(self): + self.disableBattleDetect() + self.mtrack.finish() + del self.mtrack + + # Clean up stuff the SupaFly track might not have had a chance + # to (we might have interrupted the track before it was done). + self.detachPropeller() + + def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1): + if interrupt and (self.__currentDialogue is not None): + self.__currentDialogue.stop() + self.__currentDialogue = dialogue + # If an AudioSound has been passed in, play that for dialog to + # go along with the chat. Interrupt any sound effect currently playing + if dialogue: + base.playSfx(dialogue, node=self) + # If it is a speech-type chat message, and the avatar isn't + # too far away to hear, play the appropriate sound effect. + elif (chatFlags & CFSpeech) != 0: + if (self.nametag.getNumChatPages() > 0): + # play the dialogue sample + + # We use getChat() instead of chatString, which + # returns just the current page of a multi-page chat + # message. This way we aren't fooled by long pages + # that end in question marks. + self.playDialogueForString(self.nametag.getChat()) + if (self.soundChatBubble != None): + base.playSfx(self.soundChatBubble, node=self) + elif (self.nametag.getChatStomp() > 0 ): + self.playDialogueForString(self.nametag.getStompText(), self.nametag.getStompDelay()) + + def playDialogueForString(self, chatString, delay = 0.0): + """ + Play dialogue samples to match the given chat string + """ + if len(chatString) == 0: + return + # use only lower case for searching + searchString = chatString.lower() + # determine the statement type + if (searchString.find(OTPLocalizer.DialogSpecial) >= 0): + # special sound + type = "special" + elif (searchString.find(OTPLocalizer.DialogExclamation) >= 0): + #exclamation + type = "exclamation" + elif (searchString.find(OTPLocalizer.DialogQuestion) >= 0): + # question + type = "question" + else: + # statement (use two for variety) + if random.randint(0, 1): + type = "statementA" + else: + type = "statementB" + + # determine length + 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, delay) + + def playDialogue(self, type, length, delay = 0.0): + """playDialogue(self, string, int) + Play the specified type of dialogue for the specified time + """ + + # Inheritors may override this function or getDialogueArray(), + # above. + + # Choose the appropriate sound effect. + 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: + soundSequence = Sequence(Wait(delay), + SoundInterval(dialogueArray[sfxIndex], node = None, + listenerNode = base.localAvatar, + loop = 0, + volume = 1.0), + ) + self.soundSequenceList.append(soundSequence) + soundSequence.start() + + self.cleanUpSoundList() + + def cleanUpSoundList(self): + removeList = [] + for soundSequence in self.soundSequenceList: + if soundSequence.isStopped(): + removeList.append(soundSequence) + + for soundSequence in removeList: + self.soundSequenceList.remove(soundSequence) \ No newline at end of file diff --git a/toontown/src/suit/DistributedSuitAI.py b/toontown/src/suit/DistributedSuitAI.py new file mode 100644 index 0000000..2e726e5 --- /dev/null +++ b/toontown/src/suit/DistributedSuitAI.py @@ -0,0 +1,615 @@ +""" DistributedSuitAI module: contains DistributedSuitAI class""" + +# AI code should not import ShowBaseGlobal because it creates a graphics window +# Use AIBaseGlobal instead +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * + +from otp.avatar import DistributedAvatarAI +import SuitTimings +from direct.task import Task +import SuitPlannerBase +import SuitBase +import SuitDialog +import SuitDNA +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +from toontown.building import FADoorCodes +import DistributedSuitBaseAI +from toontown.hood import ZoneUtil +import random + + +class DistributedSuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI): + """ + ///////////////////////////////////////////////////////////////////// + // DistributedSuitAI class: the server's version of a 'suit', this + // object doesnt have to worry about rendering itself, animating, + // or any other visual information, only 'thinking' + // + // Attributes: + // Derived plus... + // + ///////////////////////////////////////////////////////////////////// + """ + + + SUIT_BUILDINGS =simbase.config.GetBool('want-suit-buildings',1) + + DEBUG_SUIT_POSITIONS = simbase.config.GetBool('debug-suit-positions', 0) + + # Send an updated timestamp for each suit after about this many + # seconds have elapsed since the last timestamp. + UPDATE_TIMESTAMP_INTERVAL = 180.0 + + myId = 0 + + # load a config file value to see if we should print out information + # about this suit while it is thinking + # + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuitAI') + + def __init__(self, air, suitPlanner): + """__init__(air, suitPlanner)""" + DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air, + suitPlanner) + + # the track of the suit when it comes out a certain type of a + # building + # + self.bldgTrack = None + + self.branchId = None + if suitPlanner: + self.branchId = suitPlanner.zoneId + + self.pathEndpointStart = 0 + self.pathEndpointEnd = 0 + self.minPathLen = 0 + self.maxPathLen = 0 + self.pathPositionIndex = 0 + self.pathPositionTimestamp = 0.0 + self.pathState = 0 + + self.currentLeg = 0 + self.legType = SuitLeg.TOff + + # True if this suit flew in from the sky. + self.flyInSuit = 0 + + # True if this suit walked in from a suit building. + self.buildingSuit = 0 + + # True if this suit is planning a toon building takeover. + self.attemptingTakeover = 0 + + # The block number of the building the suit is headed to, + # either a suit or a toon building, or None. + self.buildingDestination = None + self.buildingDestinationIsCogdo = False + + def stopTasks(self): + taskMgr.remove(self.taskName("flyAwayNow")) + taskMgr.remove(self.taskName("danceNowFlyAwayLater")) + taskMgr.remove(self.taskName("move")) + + def pointInMyPath(self, point, elapsedTime): + """ + pointInMyPath(self, DNASuitPoint point, float elapsedTime) + + Returns true if the indicated DNASuitPoint is just ahead of or + just behind the where the suit will be in elapsedTime seconds + on his current path. That is to say, returns true if the + point is not suitable for another suit to start walking there. + """ + if self.pathState != 1: + # We're not even walking on our path. + return 0 + + then = globalClock.getFrameTime() + elapsedTime + elapsed = then - self.pathStartTime + + if not self.sp: + assert self.notify.error("%s: looking for point in a nonexistent suitplanner in zone %s" % (self.doId, self.zoneId)) + + return self.legList.isPointInRange(point, + elapsed - self.sp.PATH_COLLISION_BUFFER, + elapsed + self.sp.PATH_COLLISION_BUFFER) + + + def requestBattle(self, x, y, z, h, p, r): + """requestBattle(x, y, z, h, p, r) + """ + toonId = self.air.getAvatarIdFromSender() + if self.air.doId2do.get(toonId) == None: + # Ignore requests from unknown toons. + return + + assert self.notify.debug("%s: request battle with toon %s in zone %s" % (self.doId, toonId, self.zoneId)) + + # First make sure we're in Bellicose mode (i.e. on a Bellicose leg) + if self.pathState == 3: + # We are in tutorialbellicose. No brushoff needed. + pass + elif self.pathState != 1: + # We're not even in path mode. We must be in a battle already. + if self.notify.getDebug(): + self.notify.debug('requestBattle() - suit %d not on path' % (self.getDoId())) + if self.pathState == 2 or self.pathState == 4: + # Or flying away. + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + + self.d_denyBattle( toonId ) + return + + elif self.legType != SuitLeg.TWalk: + # We're on a path, but not in bellicose mode. We're + # probably walking to or from a building. + if self.notify.getDebug(): + self.notify.debug('requestBattle() - suit %d not in Bellicose' % (self.getDoId())) + + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + self.d_denyBattle( toonId ) + return + + # Store the suit's actual pos and hpr on the client + self.confrontPos = Point3(x, y, z) + self.confrontHpr = Vec3(h, p, r) + + # Request a battle from the suit planner + if (self.sp.requestBattle(self.zoneId, self, toonId)): + if self.notify.getDebug(): + self.notify.debug( "Suit %d requesting battle in zone %d" % + (self.getDoId(), self.zoneId) ) + else: + # Suit tells toon to get lost + if self.notify.getDebug(): + self.notify.debug('requestBattle from suit %d - denied by battle manager' % (self.getDoId())) + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + self.d_denyBattle( toonId ) + + def getConfrontPosHpr(self): + """ getConfrontPosHpr() + """ + return (self.confrontPos, self.confrontHpr) + + def flyAwayNow(self): + """ + flyAwayNow(self) + + Sends a message to all client suits to immediately start the + fly-away animation, and then removes them a short time later. + """ + self.b_setPathState(2) + + self.stopPathNow() # just for good measure. + name = self.taskName("flyAwayNow") + taskMgr.remove(name) + taskMgr.doMethodLater(SuitTimings.toSky, self.finishFlyAwayNow, name) + + def danceNowFlyAwayLater(self): + """ + danceNowFlyAwayLater(self) + + Sends a message to all client suits to immediately start the + victory dance animation for all suits, and then have them + fly away. The final task removes them a short time later. + """ + self.b_setPathState(4) + + self.stopPathNow() # just for good measure. + name = self.taskName("danceNowFlyAwayLater") + taskMgr.remove(name) + taskMgr.doMethodLater(SuitTimings.victoryDance + SuitTimings.toSky, + self.finishFlyAwayNow, name) + + def finishFlyAwayNow(self, task): + self.notify.debug("Suit %s finishFlyAwayNow" % (self.doId)) + self.requestRemoval() + return Task.done + + # setSPDoId - set/get the suit planner doId for this suit, needed for + # the client suit so it can get access to the DNA and path + # information for the street it is on + + def d_setSPDoId(self, doId): + self.sendUpdate('setSPDoId', [ doId ]) + + def getSPDoId(self): + if self.sp: + return self.sp.getDoId() + else: + return 0 + + def releaseControl(self): + # Override this function from DistributedSuitBaseAI + self.b_setPathState(0) + + # setPathEndpoints + def b_setPathEndpoints(self, start, end, minPathLen, maxPathLen): + self.setPathEndpoints(start, end, minPathLen, maxPathLen) + self.d_setPathEndpoints(start, end, minPathLen, maxPathLen) + + def d_setPathEndpoints(self, start, end, minPathLen, maxPathLen): + self.sendUpdate("setPathEndpoints", [start, end, minPathLen, maxPathLen]) + + def setPathEndpoints(self, start, end, minPathLen, maxPathLen): + self.pathEndpointStart = start + self.pathEndpointEnd = end + self.minPathLen = minPathLen + self.maxPathLen = maxPathLen + + def getPathEndpoints(self): + return (self.pathEndpointStart, self.pathEndpointEnd, + self.minPathLen, self.maxPathLen) + + + # setPathPosition + def b_setPathPosition(self, index, timestamp): + self.setPathPosition(index, timestamp) + self.d_setPathPosition(index, timestamp) + + def d_setPathPosition(self, index, timestamp): + self.notify.debug("Suit %d reaches point %d at time %0.2f" % (self.getDoId(), index, timestamp)) + self.sendUpdate("setPathPosition", [index, globalClockDelta.localToNetworkTime(timestamp)]) + + def setPathPosition(self, index, timestamp): + self.pathPositionIndex = index + self.pathPositionTimestamp = timestamp + + def getPathPosition(self): + return (self.pathPositionIndex, + globalClockDelta.localToNetworkTime(self.pathPositionTimestamp)) + + + # setPathState + def b_setPathState(self, state): + self.setPathState(state) + self.d_setPathState(state) + + def d_setPathState(self, state): + self.sendUpdate("setPathState", [state]) + + def setPathState(self, state): + if self.pathState != state: + self.pathState = state + if state == 0: + # Stop the suit from moving. + self.stopPathNow() + elif state == 1: + # Start the suit moving. + self.moveToNextLeg(None) + elif state == 2: + # Fly away right now from wherever we are. + self.stopPathNow() + elif state == 3: + pass + elif state == 4: + # Do the victory dance and they fly away from wherever we are. + self.stopPathNow() + else: + self.notify.error("Invalid state: " + str(state)) + + def getPathState(self): + return self.pathState + + # debugSuitPosition + def d_debugSuitPosition(self, elapsed, currentLeg, x, y, timestamp): + timestamp = globalClockDelta.localToNetworkTime(timestamp) + self.sendUpdate( + "debugSuitPosition", + [elapsed, currentLeg, x, y, timestamp]) + + + def initializePath(self): + """ + Sets up some initial parameters about the suit and its path. + This is called by the suit planner before the suit is + generated. + """ + self.makeLegList() + + if self.notify.getDebug(): + self.notify.debug("Leg list:") + print self.legList + + idx1 = self.startPoint.getIndex() + idx2 = self.endPoint.getIndex() + self.pathStartTime = globalClock.getFrameTime() + + # Tell all the clients about the suit's path and its start + # time on that path. + self.setPathEndpoints(idx1, idx2, self.minPathLen, self.maxPathLen) + self.setPathPosition(0, self.pathStartTime) + + # We don't call setPathState() yet, because we haven't been + # generated. + self.pathState = 1 + + self.currentLeg = 0 + + # now be sure to properly set the suit's initial zone after setting + # its path information and we know where the suit will be starting + # + self.zoneId = ZoneUtil.getTrueZoneId(self.legList.getZoneId(0), self.branchId) + self.legType = self.legList.getType(0) + + if self.notify.getDebug(): + self.notify.debug("creating suit in zone %d" % (self.zoneId)) + + def resync(self): + """ + Broadcasts the current position of the suit to all clients who + care. This is mainly useful while developing, in case you + have paused the AI or your client and you are now out of sync. + The suits would catch up eventually anyway, on the next + timestamp broadcast (which happens every + UPDATE_TIMESTAMP_INTERVAL seconds), but this forces it to + happen now for the impatient. + + The magic word "~cogs sync" calls this function on every suit + in the current neighborhood. + """ + self.b_setPathPosition(self.currentLeg, self.pathStartTime + self.legList.getStartTime(self.currentLeg)) + + + def moveToNextLeg(self, task): + """ + This callback function is spawned by a do-later task as each + leg ETA is reached. It handles moving the suit to the + next leg, and all the bookkeeping that goes along with + that. + """ + # First, which leg have we reached, anyway? + now = globalClock.getFrameTime() + elapsed = now - self.pathStartTime + + nextLeg = self.legList.getLegIndexAtTime(elapsed, self.currentLeg) + numLegs = self.legList.getNumLegs() + + if self.currentLeg != nextLeg: + self.currentLeg = nextLeg + self.__beginLegType(self.legList.getType(nextLeg)) + zoneId = self.legList.getZoneId(nextLeg) + zoneId = ZoneUtil.getTrueZoneId(zoneId, self.branchId) + self.__enterZone(zoneId) + + self.notify.debug("Suit %d reached leg %d of %d in zone %d." % + (self.getDoId(), nextLeg, numLegs - 1, + self.zoneId)) + + if self.DEBUG_SUIT_POSITIONS: + leg = self.legList.getLeg(nextLeg) + pos = leg.getPosAtTime(elapsed - leg.getStartTime()) + self.d_debugSuitPosition(elapsed, nextLeg, pos[0], pos[1], now) + + + # Also, make sure our position timestamp doesn't go stale. + # Every now and then, send an updated timestamp. + if now - self.pathPositionTimestamp > self.UPDATE_TIMESTAMP_INTERVAL: + self.resync() + + if self.pathState != 1: + # If we're not even in path mode, don't wait for the next zone. + return Task.done + + # Now, which leg should we next wake up for? Unlike the + # client code, the AI doesn't really care about stopping for + # every silly leg. We only need to know about the next leg in + # which our zoneId or type changes. + nextLeg += 1 + while nextLeg + 1 < numLegs and \ + self.legList.getZoneId(nextLeg) == ZoneUtil.getCanonicalZoneId(self.zoneId) and \ + self.legList.getType(nextLeg) == self.legType: + nextLeg += 1 + + # Spawn another do-later to get to the next leg. + if nextLeg < numLegs: + nextTime = self.legList.getStartTime(nextLeg) + delay = nextTime - elapsed + + taskMgr.remove(self.taskName("move")) + taskMgr.doMethodLater(delay, self.moveToNextLeg, self.taskName("move")) + else: + # No more legs. By now the suit has gone--flown away + # or inside a building. + if self.attemptingTakeover: + # We made it inside our building! + self.startTakeOver() + + #self.notify.debug("Suit %s finished walk to building" % (self.doId)) + self.requestRemoval() + return Task.done + + def stopPathNow(self): + """ + Stops the suit wherever it is on the path. This isn't a clean + stop; it's then up to the caller to do something interesting + with the suit's position and animation. + """ + taskMgr.remove(self.taskName("move")) + + def __enterZone(self, zoneId): + """ + Switches the suit to the indicated zone. Normally this is + called by moveToNextLeg(). + """ + if zoneId != self.zoneId: + self.sp.zoneChange(self, self.zoneId, zoneId) + self.air.sendSetZone(self, zoneId) + self.zoneId = zoneId + + # See if there's a battle going on in our new zone. + if self.pathState == 1: + self.sp.checkForBattle(zoneId, self) + + def __beginLegType(self, legType): + """ + int legType + + Begins a new leg of the indicated type. Normally this is + called by moveToNextLeg(). + """ + self.legType = legType + if legType == SuitLeg.TWalkFromStreet: + self.checkBuildingState() + elif legType == SuitLeg.TToToonBuilding: + self.openToonDoor() + elif legType == SuitLeg.TToSuitBuilding: + self.openSuitDoor() + elif legType == SuitLeg.TToCoghq: + self.openCogHQDoor(1) + elif legType == SuitLeg.TFromCoghq: + self.openCogHQDoor(0) + + + def resume(self): + """ + called by battles to tell this suit that it is no + longer part of a battle, and it should carry on. + """ + self.notify.debug("Suit %s resume" % (self.doId)) + + if self.currHP <= 0: + # If the suit's dead, take it out. + self.notify.debug("Suit %s dead after resume" % (self.doId)) + self.requestRemoval() + else: + # Otherwise, just fly away. We could conceivably return + # to our path from where we left off, but this is + # complicated and doesn't seem to get us much. + + # This seems to work fine, but *something* is broken now + # in the suit code, and this is the only thing that + # changed. Commenting it out for now in favor of the + # original flyAwayNow(). + + # On second thought, this is so funny we need to have it + # in there. Putting it back. + self.danceNowFlyAwayLater() + #self.flyAwayNow() + + def prepareToJoinBattle(self): + self.b_setPathState(0) + + def interruptMove(self): + """ + this function should be called when a suit is + interrupted while it is walking, such as when + the suit encounters a toon it might do battle with + """ + SuitBase.SuitBase.interruptMove(self) + + def checkBuildingState(self): + """ + Checks to ensure the building we're headed for is still a toon + (or suit) building. If not, flies away. This is normally + called as soon as we begin the WalkFromStreet mode. + """ + blockNumber = self.buildingDestination + if blockNumber == None: + return + + assert self.sp.buildingMgr.isValidBlockNumber(blockNumber) + + building = self.sp.buildingMgr.getBuilding(blockNumber) + + if self.attemptingTakeover: + if not building.isToonBlock(): + self.flyAwayNow() + return + + if not hasattr(building, "door"): + self.flyAwayNow() + return + + # Don't bother checking this. It breaks magic words, and + # so what if we get an extra building here and there? + #if self.sp.countNumNeededBuildings() <= 0: + # # We don't need any more buildings; forget it. + # self.flyAwayNow() + # return + + # Also lock the door so toons won't try to enter at the + # last minute. + building.door.setDoorLock(FADoorCodes.SUIT_APPROACHING) + else: + if not building.isSuitBlock(): + self.flyAwayNow() + + def openToonDoor(self): + """ + Stands in front of a toon building front door and asks to be + let in. + """ + blockNumber = self.buildingDestination + assert blockNumber != None + assert self.sp.buildingMgr.isValidBlockNumber(blockNumber) + + building = self.sp.buildingMgr.getBuilding(blockNumber) + if not building.isToonBlock(): + # Oops, it's a suit building somehow. + self.flyAwayNow() + return + + if not hasattr(building, "door"): + # Hmm, no door. + self.flyAwayNow() + return + + building.door.requestSuitEnter(self.getDoId()) + + + def openSuitDoor(self): + """ + Stands in front of a suit building side door and asks to be + let in. + """ + blockNumber = self.buildingDestination + assert blockNumber != None + assert self.sp.buildingMgr.isValidBlockNumber(blockNumber) + + building = self.sp.buildingMgr.getBuilding(blockNumber) + if not building.isSuitBlock(): + # Oops, it's a toon building somehow. + self.flyAwayNow() + return + + def openCogHQDoor(self, enter): + """ + Stands in front of a CogHQ lobby door and asks to be let in. + """ + blockNumber = self.legList.getBlockNumber(self.currentLeg) + try: + door = self.sp.cogHQDoors[blockNumber] + except: + self.notify.error("No CogHQ door %s in zone %s" % (blockNumber, self.sp.zoneId)) + return + + if enter: + door.requestSuitEnter(self.getDoId()) + else: + door.requestSuitExit(self.getDoId()) + + + def startTakeOver(self): + """ + Begins the process of taking over a toon building by a suit. + """ + if not self.SUIT_BUILDINGS: + return + + blockNumber = self.buildingDestination + assert blockNumber != None + + if not self.sp.buildingMgr.isSuitBlock(blockNumber): + self.notify.debug( "Suit %d taking over building %d in %d" % \ + ( self.getDoId(), blockNumber, self.zoneId ) ) + + difficulty = self.getActualLevel() - 1 + if self.buildingDestinationIsCogdo: + self.sp.cogdoTakeOver(blockNumber, difficulty, self.buildingHeight) + else: + dept = SuitDNA.getSuitDept(self.dna.name) + self.sp.suitTakeOver(blockNumber, dept, difficulty, self.buildingHeight) diff --git a/toontown/src/suit/DistributedSuitBase.py b/toontown/src/suit/DistributedSuitBase.py new file mode 100644 index 0000000..4db9d40 --- /dev/null +++ b/toontown/src/suit/DistributedSuitBase.py @@ -0,0 +1,805 @@ +"""DistributedSuit module: contains the DistributedSuit class""" + +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import * +from direct.directtools.DirectGeometry import CLAMP +from direct.controls.ControlManager import CollisionHandlerRayStart +from direct.task import Task +from otp.otpbase import OTPGlobals +from otp.avatar import DistributedAvatar +import Suit +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.toonbase import TTLocalizer +from toontown.battle import DistributedBattle +from direct.fsm import ClassicFSM +from direct.fsm import State +import SuitTimings +import SuitBase +import DistributedSuitPlanner +import SuitDNA +from direct.directnotify import DirectNotifyGlobal +import SuitDialog +from toontown.battle import BattleProps +import math +import copy + + + + +class DistributedSuitBase(DistributedAvatar.DistributedAvatar, Suit.Suit, + SuitBase.SuitBase): + """ + DistributedSuit class: a 'bad guy' which exists on each client's + machine and helps direct the Suits which exist on the server. This + is the object that each individual player interacts with when + initiating combat. This guy has all of the attributes of a + DistributedSuitAI object, plus some more such as collision info + + Attributes: + Derived plus... + DistributedSuit_initialized (integer), flag indicating if this + suit has been properly initialized + fsm, the state machine that this client suit will use, this + includes states of detecting collisions with toons and + entering battles + dna, dna created for the suit, sent to us from the server + """ + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuitBase') + + def __init__(self, cr): + try: + self.DistributedSuitBase_initialized + return + except: + self.DistributedSuitBase_initialized = 1 + + DistributedAvatar.DistributedAvatar.__init__(self, cr) + Suit.Suit.__init__(self) + SuitBase.SuitBase.__init__(self) + self.activeShadow = 0 + self.virtual = 0 #the red glowing effect + + # collision junk + # + self.battleDetectName = None + + self.cRay = None + self.cRayNode = None + self.cRayNodePath = None + self.cRayBitMask = None + self.lifter = None + self.cTrav = None + + # our reference to the local hood's suit planner, the doId of + # it is sent to us from the server side suit + self.sp = None + + # Initialize this for setState() calls - child classes will + # likely redefine this + self.fsm = None + + # propellers for flying into and out of the streets + self.prop = None + self.propInSound = None + self.propOutSound = None + + # make sure to hide the suit when first created, it is not yet + # placed in the right location, so if its current location happens + # to be on the street, we don't want it visible + self.reparentTo(hidden) + self.loop('neutral') + + # number of times to reanimate into a skeleCog + self.skeleRevives = 0 + # keep track of how many times we have reanimated + self.maxSkeleRevives = 0 + # if we were in the holiday when we started a silly surge, in case holiday ends in middle of battle + self.sillySurgeText = False + + # use this for displaying hp bonus text + self.interactivePropTrackBonus = -1 + + def setVirtual(self, virtual): + pass + + def getVirtual(self): + return 0 + + def setSkeleRevives(self, num): + if num == None: + num = 0 + self.skeleRevives = num + if num > self.maxSkeleRevives: + self.maxSkeleRevives = num + if self.getSkeleRevives() > 0: + nameInfo = TTLocalizer.SuitBaseNameWithLevel % {"name": self.name , + "dept": self.getStyleDept(), + "level": ("%s%s" % (self.getActualLevel(), TTLocalizer.SkeleRevivePostFix)),} + self.setDisplayName( nameInfo ) + else: + nameInfo = TTLocalizer.SuitBaseNameWithLevel % {"name": self.name, + "dept": self.getStyleDept(), + "level": self.getActualLevel(),} + self.setDisplayName( nameInfo ) + + + def getSkeleRevives(self): + return self.skeleRevives + + def getMaxSkeleRevives(self): + return self.maxSkeleRevives + + def generate(self): + """ + This method is called when the DistributedObject is + reintroduced to the world, either for the first + time or from the cache. + """ + assert(self.notify.debug("DistributedSuit %d: generating" % + self.getDoId())) + DistributedAvatar.DistributedAvatar.generate(self) + + def disable(self): + """ + This method is called when the DistributedObject + is removed from active duty and stored in a cache. + """ + self.notify.debug("DistributedSuit %d: disabling" % self.getDoId()) + self.ignoreAll() + self.__removeCollisionData() + self.cleanupLoseActor() + self.stop() + taskMgr.remove(self.uniqueName('blink-task')) + DistributedAvatar.DistributedAvatar.disable(self) + + def delete(self): + """ + This method is called when the DistributedObject is + permanently removed from the world and deleted from + the cache. + """ + try: + self.DistributedSuitBase_deleted + except: + self.DistributedSuitBase_deleted = 1 + self.notify.debug("DistributedSuit %d: deleting" % self.getDoId()) + + del self.dna + del self.sp + + DistributedAvatar.DistributedAvatar.delete(self) + Suit.Suit.delete(self) + SuitBase.SuitBase.delete(self) + + # We need to force the Suit version of these to be called, otherwise + # we get the generic Avatar version which is undefined + def setDNAString(self, dnaString): + Suit.Suit.setDNAString(self, dnaString) + + def setDNA(self, dna): + Suit.Suit.setDNA(self, dna) + + def getHP(self): + return self.currHP + + def setHP(self, hp): + """ + Function: set the current health of this suit, this can + be called during battle and at initialization + Parameters: hp, value to set health to + """ + if hp > self.maxHP: + self.currHP = self.maxHP + else: + self.currHP = hp + return None + + def getDialogueArray(self, *args): + # Force the right inheritance chain to be called + return Suit.Suit.getDialogueArray(self, *args) + + def __removeCollisionData(self): + """ + clean up the suit's various collision data such + as the battle detection sphere, the ground collision + ray, and the lifter + """ + # make sure to remove any raycast information + # + self.enableRaycast(0) + + self.cRay = None + self.cRayNode = None + self.cRayNodePath = None + + self.lifter = None + + self.cTrav = None + + def setHeight(self, height): + # We want to make sure we get the specialized one. + Suit.Suit.setHeight(self, height) + + def getRadius(self): + # We want to make sure we get the specialized one. + return Suit.Suit.getRadius(self) + + def setLevelDist(self, level): + """ + level is the new level (int) of the suit. + + The distributed function to be called when the + server side suit changes level + """ + if self.notify.getDebug(): + self.notify.debug("Got level %d from server for suit %d" % \ + (level, self.getDoId())) + self.setLevel(level) + + def attachPropeller(self): + """ + attach a propeller to this suit, used when the suit + is going into it's flying animation + """ + if self.prop == None: + self.prop = BattleProps.globalPropPool.getProp('propeller') + if self.propInSound == None: + self.propInSound = base.loadSfx("phase_5/audio/sfx/ENC_propeller_in.mp3") + if self.propOutSound == None: + self.propOutSound = base.loadSfx("phase_5/audio/sfx/ENC_propeller_out.mp3") + head = self.find("**/joint_head") + self.prop.reparentTo(head) + + def detachPropeller(self): + """ + remove the propeller from a suit if it has one, this + is used after a suit is done with its flying anim + """ + if self.prop: + self.prop.removeNode() + self.prop = None + if self.propInSound: + self.propInSound = None + if self.propOutSound: + self.propOutSound = None + + def beginSupaFlyMove(self, pos, moveIn, trackName): + """ + beginSupaFlyMove(self, Point3 pos, bool moveIn, string trackName) + + Returns an interval that will animate the suit either up into + the sky or back down to the ground, based on moveIn. + + pos is the point on the street over which the animation takes + place. + """ + skyPos = Point3(pos) + + # calculate a point in the sky based on how fast a suit walks + # and how long it has been determined that flying away should take + # + if moveIn: + skyPos.setZ(pos.getZ() + (SuitTimings.fromSky * + ToontownGlobals.SuitWalkSpeed)) + else: + skyPos.setZ(pos.getZ() + (SuitTimings.toSky * + ToontownGlobals.SuitWalkSpeed)) + + # calculate some times used to manipulate the suit's landing + # animation + # + groundF = 28 + dur = self.getDuration('landing') + fr = self.getFrameRate('landing') + + # length of time in animation spent in the air + animTimeInAir = groundF/fr + # length of time in animation spent impacting and reacting to + # the ground + impactLength = dur - animTimeInAir + # the frame at which the suit touches the ground + timeTillLanding = SuitTimings.fromSky - impactLength + # time suit spends playing the flying portion of the landing anim + waitTime = timeTillLanding - animTimeInAir + + # now create info for the propeller's animation + # + if self.prop == None: + self.prop = BattleProps.globalPropPool.getProp('propeller') + propDur = self.prop.getDuration('propeller') + lastSpinFrame = 8 + fr = self.prop.getFrameRate('propeller') + # time from beginning of anim at which propeller plays its spin + spinTime = lastSpinFrame/fr + # time from beginning of anim at which propeller starts to close + openTime = (lastSpinFrame + 1) / fr + + if moveIn: + # if we are moving into the neighborhood from the sky, move + # down from above (skyPos) the first waypoint in the suit's + # current path (pos), first create an interval that will + # move the suit over time, then create a function interval + # to set the suit's animation to a single frame (the first) + # of the landing animation, then create a wait interval to + # wait for the suit to get closer to the ground, then create + # an actor interval to play the landing animation so it ends + # when the suit touches the ground, and lastly create a + # function interval to make sure the suit goes into it's + # walk animation once it lands + # + + # create the lerp intervals that will go in the first track, + # also reparent the suit's shadow to render and set the + # position of it below the suit on the ground + # + lerpPosTrack = Sequence( + self.posInterval(timeTillLanding, pos, startPos=skyPos), + Wait(impactLength), + ) + + + shadowScale = self.dropShadow.getScale() + + # create a scale interval for the suit's shadow so it scales + # up as the suit gets closer to the ground + # + # keep Z scale at 1. so that lifter doesn't go crazy-go-nuts and set Z to infinity + shadowTrack = Sequence( + Func(self.dropShadow.reparentTo, render), + Func(self.dropShadow.setPos, pos), + self.dropShadow.scaleInterval(timeTillLanding, self.scale, + startScale = Vec3(0.01, 0.01, 1.)), + Func(self.dropShadow.reparentTo, self.getShadowJoint()), + Func(self.dropShadow.setPos, 0, 0, 0), + Func(self.dropShadow.setScale, shadowScale), + ) + + fadeInTrack = Sequence( + Func(self.setTransparency, 1), + self.colorScaleInterval(1, colorScale = VBase4(1, 1, 1, 1), + startColorScale = VBase4(1, 1, 1, 0)), + Func(self.clearColorScale), + Func(self.clearTransparency), + ) + + # now create the suit animation intervals that will go in the + # second track + # + animTrack = Sequence( + Func(self.pose, 'landing', 0), + Wait(waitTime), + ActorInterval(self, 'landing', duration = dur), + Func(self.loop, 'walk'), + ) + + # now create the propeller animation intervals that will go in + # the third and final track + # + self.attachPropeller() + propTrack = Parallel( + SoundInterval(self.propInSound, duration = waitTime + dur, + node = self), + Sequence(ActorInterval(self.prop, 'propeller', + constrainedLoop = 1, + duration = waitTime + spinTime, + startTime = 0.0, + endTime = spinTime), + ActorInterval(self.prop, 'propeller', + duration = propDur - openTime, + startTime = openTime), + Func(self.detachPropeller), + ), + ) + + return Parallel(lerpPosTrack, + shadowTrack, + fadeInTrack, + animTrack, + propTrack, + name = self.taskName('trackName') + ) + else: + # move to the sky, move vertically from the current + # position to some location in the sky, also reparent the + # suit's shadow to render and set the position of it below + # the suit on the ground + # + lerpPosTrack = Sequence( + Wait(impactLength), + LerpPosInterval(self, timeTillLanding, skyPos, + startPos=pos), + ) + + # create a scale interval for the suit's shadow so it scales + # down as the suit gets further from the ground + # + # keep Z scale at 1. so that lifter doesn't go crazy-go-nuts and set Z to infinity + shadowTrack = Sequence( + Func(self.dropShadow.reparentTo, render), + Func(self.dropShadow.setPos, pos), + self.dropShadow.scaleInterval(timeTillLanding, Vec3(0.01, 0.01, 1.), + startScale = self.scale), + Func(self.dropShadow.reparentTo, self.getShadowJoint()), + Func(self.dropShadow.setPos, 0, 0, 0), + ) + + fadeOutTrack = Sequence( + Func(self.setTransparency, 1), + self.colorScaleInterval(1, colorScale = VBase4(1, 1, 1, 0), + startColorScale = VBase4(1, 1, 1, 1)), + Func(self.clearColorScale), + Func(self.clearTransparency), + Func(self.reparentTo, hidden), + ) + + + # create the suit animation intervals which will go into + # a second track + # + actInt = ActorInterval(self, 'landing', loop = 0, + startTime = dur, + endTime = 0.0) + + # now create the propeller animation intervals that will go in + # the third and final track + # + self.attachPropeller() + self.prop.hide() + propTrack = Parallel( + SoundInterval(self.propOutSound, duration = waitTime + dur, + node = self), + Sequence(Func(self.prop.show), + ActorInterval(self.prop, 'propeller', + endTime = openTime, + startTime = propDur), + ActorInterval(self.prop, 'propeller', + constrainedLoop = 1, + duration = propDur - openTime, + startTime = spinTime, + endTime = 0.0), + Func(self.detachPropeller), + ), + ) + + return Parallel(ParallelEndTogether(lerpPosTrack, + shadowTrack, + fadeOutTrack), + actInt, + propTrack, + name = self.taskName('trackName') + ) + + def enableBattleDetect(self, name, handler): + if self.collTube: + # We recreate the sphere node every time we switch states + # to force the collision event to be regenerated even if + # the avatar was already within the suit's bubble. + self.battleDetectName = self.taskName(name) + self.collNode = CollisionNode(self.battleDetectName) + self.collNode.addSolid(self.collTube) + self.collNodePath = self.attachNewNode(self.collNode) + self.collNode.setCollideMask(ToontownGlobals.WallBitmask) + self.accept("enter" + self.battleDetectName, handler) + + return Task.done + + def disableBattleDetect(self): + if self.battleDetectName: + self.ignore("enter" + self.battleDetectName) + self.battleDetectName = None + if self.collNodePath: + self.collNodePath.removeNode() + self.collNodePath = None + + def enableRaycast(self, enable=1): + """ + enable/disable raycast, useful for when we know + when the suit will change elevations + """ + if (not self.cTrav + or not hasattr(self, "cRayNode") + or not self.cRayNode): + return + + self.cTrav.removeCollider(self.cRayNodePath) + if enable: + if self.notify.getDebug(): + self.notify.debug("enabling raycast") + self.cTrav.addCollider(self.cRayNodePath, self.lifter) + else: + if self.notify.getDebug(): + self.notify.debug("disabling raycast") + + # setBrushOff + def b_setBrushOff(self, index): + # Local + self.setBrushOff(index) + # Distributed + self.d_setBrushOff(index) + + def d_setBrushOff(self, index): + self.sendUpdate("setBrushOff", [index]) + + def setBrushOff(self, index): + self.setChatAbsolute(SuitDialog.getBrushOffText(self.getStyleName(), index), + CFSpeech | CFTimeout) + + def initializeBodyCollisions(self, collIdStr): + """ + set up collision information for this cog, + only do once when creating the cog + """ + DistributedAvatar.DistributedAvatar.initializeBodyCollisions(self, collIdStr) + + if not self.ghostMode: + self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask) + + # Set up the collison ray + # This is a ray cast from your head down to detect floor polygons + # and is only turned on during specific parts of the suit's path + self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0) + self.cRayNode = CollisionNode(self.taskName("cRay")) + self.cRayNode.addSolid(self.cRay) + self.cRayNodePath = self.attachNewNode(self.cRayNode) + self.cRayNodePath.hide() + self.cRayBitMask = ToontownGlobals.FloorBitmask + self.cRayNode.setFromCollideMask(self.cRayBitMask) + self.cRayNode.setIntoCollideMask(BitMask32.allOff()) + + # set up floor collision mechanism + self.lifter = CollisionHandlerFloor() + self.lifter.setOffset(ToontownGlobals.FloorOffset) + self.lifter.setReach(6.0) + + # Limit our rate-of-fall with the lifter. + self.lifter.setMaxVelocity(8.0) + self.lifter.addCollider(self.cRayNodePath, self) + + # now use the standard collision traverser to handle updating + # collision info + self.cTrav = base.cTrav + + def disableBodyCollisions(self): + self.disableBattleDetect() + self.enableRaycast(0) + if self.cRayNodePath: + self.cRayNodePath.removeNode() + del self.cRayNode + del self.cRay + del self.lifter + + def denyBattle(self): + self.notify.debug('denyBattle()') + + # Deny the local toon's request for battle. This is only sent + # directly to a toon who requested the battle; other toons in + # the zone don't see this message. + + place = self.cr.playGame.getPlace() + if place.fsm.getCurrentState().getName() == 'WaitForBattle': + place.setState('walk') + self.resumePath(self.pathState) + + def makePathTrack(self, nodePath, posPoints, velocity, name): + track = Sequence(name = name) + assert len(posPoints) > 1 + restOfPosPoints = posPoints[1:] + for pointIndex in range(len(posPoints) - 1): + startPoint = posPoints[pointIndex] + endPoint = posPoints[pointIndex + 1] + # Face the endpoint + track.append( + Func(nodePath.headsUp, + endPoint[0], endPoint[1], endPoint[2]) + ) + # Calculate the amount of time we should spend walking + distance = Vec3(endPoint - startPoint).length() + duration = distance / velocity + + # Walk to the end point + track.append( + LerpPosInterval(nodePath, duration=duration, + pos=Point3(endPoint), + startPos=Point3(startPoint)) + ) + return track + + def setState(self, state): + if (self.fsm == None): + return 0 + # check to make sure we aren't going into the state we are already + # in, this is useful so we don't go into the Off state when already + # in the off state, which will result in the stopping of a currently + # playing track + # + if (self.fsm.getCurrentState().getName() == state): + assert(self.notify.debug("State change ignored, already in " + + "state" + str(state))) + return 0 + return self.fsm.request(state) + + # Specific State functions + + ##### Off state ##### + + def subclassManagesParent(self): + # factory suits are parented under other nodes, and the + # parent info doesn't need to be distributed + return 0 + + def enterOff(self, *args): + assert self.notify.debug('enterOff()') + self.hideNametag3d() + self.hideNametag2d() + if not self.subclassManagesParent(): + self.setParent(ToontownGlobals.SPHidden) + + def exitOff(self): + if not self.subclassManagesParent(): + self.setParent(ToontownGlobals.SPRender) + self.showNametag3d() + self.showNametag2d() + self.loop('neutral', 0) + + ##### Battle state ##### + + def enterBattle(self): + # Join a battle object and let it take over control of the suit + assert self.notify.debug('DistributedSuit: entering a Battle') + self.loop('neutral', 0) + self.disableBattleDetect() + self.corpMedallion.hide() + self.healthBar.show() + # make sure health bar updates current suit condition + if self.currHP < self.maxHP: + self.updateHealthBar(0, 1) + + def exitBattle(self): + self.healthBar.hide() + self.corpMedallion.show() + self.currHP = self.maxHP + self.interactivePropTrackBonus = -1 + ##### WaitForBattle state ##### + + def enterWaitForBattle(self): + self.loop('neutral', 0) + + def exitWaitForBattle(self): + pass + + def setSkelecog(self, flag): + SuitBase.SuitBase.setSkelecog(self, flag) + if flag: + Suit.Suit.makeSkeleton(self) + + def showHpText(self, number, bonus=0, scale=1, attackTrack =-1): + if self.HpTextEnabled and not self.ghostMode: + # We don't show zero change. + if number != 0: + # Get rid of the number if it is already there. + if self.hpText: + self.hideHpText() + # Set the font + self.HpTextGenerator.setFont(OTPGlobals.getSignFont()) + # Show both negative and positive signs + if number < 0: + self.HpTextGenerator.setText(str(number)) + # If we're doing the Silly Holiday word additions + if base.cr.newsManager.isHolidayRunning(ToontownGlobals.SILLY_SURGE_HOLIDAY): + self.sillySurgeText = True + absNum = abs(number) + if absNum > 0 and absNum <= 10: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[1]) + elif absNum > 10 and absNum <= 20: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[2]) + elif absNum > 20 and absNum <= 30: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[3]) + elif absNum > 30 and absNum <= 40: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[4]) + elif absNum > 40 and absNum <= 50: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[5]) + elif absNum > 50 and absNum <= 60: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[6]) + elif absNum > 60 and absNum <= 70: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[7]) + elif absNum > 70 and absNum <= 80: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[8]) + elif absNum > 80 and absNum <= 90: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[9]) + elif absNum > 90 and absNum <= 100: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[10]) + elif absNum > 100 and absNum <= 110: + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[11]) + else: # greater than 110 + self.HpTextGenerator.setText(str(number) + "\n" + TTLocalizer.SillySurgeTerms[12]) + + # check for interactive prop gag track bonus + if self.interactivePropTrackBonus > -1 and self.interactivePropTrackBonus == attackTrack: + self.sillySurgeText = True + if attackTrack in TTLocalizer.InteractivePropTrackBonusTerms: + self.HpTextGenerator.setText(str(number) + "\n" + + TTLocalizer.InteractivePropTrackBonusTerms[attackTrack]) + else: + self.HpTextGenerator.setText("+" + str(number)) + # No shadow + self.HpTextGenerator.clearShadow() + # Put a shadow on there + #self.HpTextGenerator.setShadow(0.05, 0.05) + #self.HpTextGenerator.setShadowColor(0, 0, 0, 1) + # Center the number + self.HpTextGenerator.setAlign(TextNode.ACenter) + # Red for negative, green for positive, yellow for bonus + 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 + # if we have a track bonus, for now make it blue + if self.interactivePropTrackBonus > -1 and self.interactivePropTrackBonus == attackTrack: + r = 0 + g = 0 + b = 1 + a = 1 + else: + r = 0 + g = 0.9 + b = 0 + a = 1 + + self.HpTextGenerator.setTextColor(r, g, b, a) + + self.hpTextNode = self.HpTextGenerator.generate() + + # Put the hpText over the head of the avatar + self.hpText = self.attachNewNode(self.hpTextNode) + self.hpText.setScale(scale) + # Make sure it is a billboard + self.hpText.setBillboardPointEye() + # Render it after other things in the scene. + self.hpText.setBin('fixed', 100) + if self.sillySurgeText: + self.nametag3d.setDepthTest(0) + self.nametag3d.setBin('fixed', 99) + + # Initial position ... Center of the body... the "tan tien" + self.hpText.setPos(0, 0, self.height/2) + seq = Task.sequence( + # Fly the number out of the character + self.hpText.lerpPos(Point3(0, 0, self.height + 1.5), + 1.0, + blendType = 'easeOut'), + # Wait 2 seconds + Task.pause(0.85), + # Fade the number + self.hpText.lerpColor(Vec4(r, g, b, a), + Vec4(r, g, b, 0), + 0.1), + # Get rid of the number + Task.Task(self.hideHpTextTask)) + taskMgr.add(seq, self.uniqueName("hpText")) + else: + # Just play the sound effect. + # TODO: Put in the sound effect! + pass + + def hideHpTextTask(self, task): + self.hideHpText() + # If we're doing the Silly Holiday word additions + if self.sillySurgeText: + self.nametag3d.clearDepthTest() + self.nametag3d.clearBin() + self.sillySurgeText = False + return Task.done + diff --git a/toontown/src/suit/DistributedSuitBaseAI.py b/toontown/src/suit/DistributedSuitBaseAI.py new file mode 100644 index 0000000..3c74df0 --- /dev/null +++ b/toontown/src/suit/DistributedSuitBaseAI.py @@ -0,0 +1,261 @@ +from otp.ai.AIBaseGlobal import * + +from otp.avatar import DistributedAvatarAI +import SuitPlannerBase +import SuitBase +import SuitDNA +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals + +class DistributedSuitBaseAI(DistributedAvatarAI.DistributedAvatarAI, + SuitBase.SuitBase): + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuitBaseAI') + + def __init__(self, air, suitPlanner): + DistributedAvatarAI.DistributedAvatarAI.__init__(self, air) + SuitBase.SuitBase.__init__(self) + + self.sp = suitPlanner + + # the current health of this suit + # for now use some default values, these are pulled + # from a table once the suit's level is determined + # + self.maxHP = 10 + self.currHP = 10 + self.zoneId = 0 + self.dna = None + self.virtual = 0 #the red glowing effect + + self.skeleRevives = 0 #number of times to reanimate into a skeleCog + self.maxSkeleRevives = 0 #keep track of how many times we have reanimated + self.reviveFlag = 0 + + # This is filled in only if the suit is trying to take over a + # building of a particular height. + self.buildingHeight = None + + def generate(self): + DistributedAvatarAI.DistributedAvatarAI.generate(self) + + def delete(self): + self.sp = None + del self.dna + DistributedAvatarAI.DistributedAvatarAI.delete(self) + + def requestRemoval(self): + """ + Suggest that this suit is done with its duties and should be removed. + """ + if (self.sp != None): + # If we have a SuitPlanner, it should do the removing. + self.sp.removeSuit(self) + else: + # Otherwise, remove ourselves. + self.requestDelete() + + def setLevel(self, lvl=None): + """ + Function: randomly choose a level for this suit based on the + type of the suit (such as yesman, flunky, etc) or + set the level to be the one specified + Parameters: lvl, level the suit should be + """ + attributes = SuitBattleGlobals.SuitAttributes[ self.dna.name ] + if lvl: + self.level = lvl - attributes[ 'level' ] - 1 + else: + self.level = SuitBattleGlobals.pickFromFreqList( + attributes[ 'freq' ]) + self.notify.debug("Assigning level " + str(lvl)) + if hasattr(self, "doId"): + self.d_setLevelDist(self.level) + + # be sure to set the hp to proper values based on the suit's + # new level + # + hp = attributes['hp'][self.level] + self.maxHP = hp + self.currHP = hp + assert self.notify.debug("Assigning hp " + str(self.currHP)) + + def getLevelDist(self): + """ + Function: the distributed function to be called when the + server side suit changes level + Parameters: level, the new level of the suit + """ + return self.getLevel() + + def d_setLevelDist(self, level): + """ + Function: the distributed function to be called when the + server side suit changes level + Parameters: level, the new level of the suit + """ + self.sendUpdate('setLevelDist', [level]) + + def setupSuitDNA(self, level, type, track): + """ + setupSuitDNA(self, int level, int type, char track) + + Creates the suit DNA, according to the indicated level (1..9), + type (1..8), and track ("c", "l", "m", "s"). + """ + dna = SuitDNA.SuitDNA() + dna.newSuitRandom(type, track) + self.dna = dna + self.track = track + self.setLevel(level) + + return None + + def getDNAString(self): + """ + Function: retrieve the dna information from this suit, called + whenever a client needs to create this suit + Parameters: none + Returns: netString representation of this suit's dna + """ + if self.dna: + return self.dna.makeNetString() + else: + self.notify.debug('No dna has been created for suit %d!' % \ + self.getDoId()) + return "" + + # setBrushOff + def b_setBrushOff(self, index): + # Local + self.setBrushOff(index) + # Distributed + self.d_setBrushOff(index) + return None + + def d_setBrushOff(self, index): + self.sendUpdate("setBrushOff", [index]) + + def setBrushOff(self, index): + # I guess on the AI side there is nothing to do here + pass + + def d_denyBattle(self, toonId): + self.sendUpdateToAvatarId(toonId, 'denyBattle', []) + + def b_setSkeleRevives(self, num): + if num == None: + num = 0 + self.setSkeleRevives(num) + self.d_setSkeleRevives(self.getSkeleRevives()) + + def d_setSkeleRevives(self, num): + self.sendUpdate("setSkeleRevives" , [num]) + + def getSkeleRevives(self): + return self.skeleRevives + + def setSkeleRevives(self, num): + if num == None: + num = 0 + self.skeleRevives = num + if num > self.maxSkeleRevives: + self.maxSkeleRevives = num + + def getMaxSkeleRevives(self): + return self.maxSkeleRevives + + def useSkeleRevive(self): + self.skeleRevives -= 1 + self.currHP = self.maxHP + self.reviveFlag = 1 + + def reviveCheckAndClear(self): + returnValue = 0 + if self.reviveFlag == 1: + returnValue = 1 + self.reviveFlag = 0 + return returnValue + + def getHP(self): + return self.currHP + + def setHP(self, hp): + """ + Function: set the current health of this suit, this can + be called during battle and at initialization + Parameters: hp, value to set health to + """ + if hp > self.maxHP: + self.currHP = self.maxHP + else: + self.currHP = hp + return None + + def b_setHP(self, hp): + self.setHP(hp) + self.d_setHP(hp) + + def d_setHP(self, hp): + self.sendUpdate("setHP", [hp]) + + def releaseControl(self): + # Do whatever needs to be done to turn control of the suit over to + # another party (e.g. a battle) - should be redefined by child if + # any behavior is required + return None + + def getDeathEvent(self): + return 'cogDead-%s' % self.doId + + def resume(self): + self.notify.debug('resume, hp=%s' % self.currHP) + + # Do whatever needs to be done to restore control of the suit from + # another party (e.g. a battle) - should be redefined by child if + # any additional behavior is required + if (self.currHP <= 0): + messenger.send(self.getDeathEvent()) + # Clean up dead suits + self.requestRemoval() + return None + + def prepareToJoinBattle(self): + """ + do whatever is appropriate when the suit is about to join + a battle; most likely, stop doing anything and let the battle + puppeteer + """ + pass + + def b_setSkelecog(self, flag): + self.setSkelecog(flag) + self.d_setSkelecog(flag) + + def setSkelecog(self, flag): + SuitBase.SuitBase.setSkelecog(self, flag) + + def d_setSkelecog(self, flag): + # send update + self.sendUpdate("setSkelecog", [flag]) + + def isForeman(self): + """is this a factory foreman?""" + return 0 + + def isSupervisor(self): + """is this a mint floor supervisor?""" + return 0 + + def setVirtual(self, virtual): + pass + + def getVirtual(self): + return 0 + + # just for consistencies sake + def isVirtual(self): + """is this a virtual laser cog?""" + return self.getVirtual() + + diff --git a/toontown/src/suit/DistributedSuitPlanner.py b/toontown/src/suit/DistributedSuitPlanner.py new file mode 100644 index 0000000..dd38f03 --- /dev/null +++ b/toontown/src/suit/DistributedSuitPlanner.py @@ -0,0 +1,195 @@ +""" DistributedSuitPlannerAI module: contains the SuitPlannerAI class which + handles management of all suits within a single neighborhood.""" + +from pandac.PandaModules import * +from direct.distributed import DistributedObject +import SuitPlannerBase +from toontown.toonbase import ToontownGlobals + +class DistributedSuitPlanner( DistributedObject.DistributedObject, + SuitPlannerBase.SuitPlannerBase ): + """ + ///////////////////////////////////////////////////////////////////////// + // + // SuitPlanner class: The client side version of the suit planner. + // This version has less functionality than the + // server side, but some functionality is common + // between the two + // Attributes: + // + ///////////////////////////////////////////////////////////////////////// + """ + + def __init__( self, cr ): + + # initialize some values that we will be using + # + DistributedObject.DistributedObject.__init__( self, cr ) + SuitPlannerBase.SuitPlannerBase.__init__( self ) + self.suitList = [] + self.buildingList = [0, 0, 0, 0] + self.pathViz = None + return None + + def generate( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // reintroduced to the world, either for the first + // time or from the cache. + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.notify.info("DistributedSuitPlanner %d: generating" % + self.getDoId()) + DistributedObject.DistributedObject.generate( self ) + # register with the cr + base.cr.currSuitPlanner = self + + def disable( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // removed to the world + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.notify.info("DistributedSuitPlanner %d: disabling" % + self.getDoId()) + self.hidePaths() + DistributedObject.DistributedObject.disable( self ) + # unregister with the cr + base.cr.currSuitPlanner = None + + + # + # the following functions let client objects query the state of the suit world + # + + def d_suitListQuery(self): + self.sendUpdate('suitListQuery') + + def suitListResponse(self, suitList): + self.suitList = suitList + # let anyone know that might care + messenger.send('suitListResponse') + + def d_buildingListQuery(self): + self.sendUpdate('buildingListQuery') + + def buildingListResponse(self, buildingList): + self.buildingList = buildingList + # let anyone know that might care + messenger.send('buildingListResponse') + + def hidePaths(self): + # Hides the visualization created by a previous call to + # showPaths(), below. + if self.pathViz: + self.pathViz.detachNode() + self.pathViz = None + + def showPaths(self): + # Draw a visualization of the suit paths at runtime for the + # user's convenience. + + self.hidePaths() + vizNode = GeomNode(self.uniqueName('PathViz')) + lines = LineSegs() + self.pathViz = render.attachNewNode(vizNode) + points = self.frontdoorPointList + self.sidedoorPointList + self.cogHQDoorPointList + self.streetPointList + while len(points) > 0: + self.__doShowPoints(vizNode, lines, None, points) + + # Also create an unused collision sphere to show each battle + # cell. + cnode = CollisionNode('battleCells') + cnode.setCollideMask(BitMask32.allOff()) + for zoneId, cellPos in self.battlePosDict.items(): + cnode.addSolid(CollisionSphere(cellPos, 9)) + + text = "%s" % (zoneId) + self.__makePathVizText(text, cellPos[0], cellPos[1], cellPos[2] + 9, + (1, 1, 1, 1)) + + self.pathViz.attachNewNode(cnode).show() + + def __doShowPoints(self, vizNode, lines, p, points): + if p == None: + # Choose a new point at random. We arbitrarily take the + # last one on the list. + pi = len(points) - 1 + if pi < 0: + return + p = points[pi] + del points[pi] + else: + # Remove the indicated point from the list. + if p not in points: + # We've already visited this point, and presumably + # already drawn the edge. + return + + pi = points.index(p) + del points[pi] + + # Draw a label for the point. + text = "%s" % (p.getIndex()) + pos = p.getPos() + + if (p.getPointType() == DNASuitPoint.FRONTDOORPOINT): + color = (1, 0, 0, 1) + elif (p.getPointType() == DNASuitPoint.SIDEDOORPOINT): + color = (0, 0, 1, 1) + else: + color = (0, 1, 0, 1) + + self.__makePathVizText(text, pos[0], pos[1], pos[2], color) + + # Draw a line to each connected point, and recurse. + adjacent = self.dnaStore.getAdjacentPoints(p) + numPoints = adjacent.getNumPoints() + for i in range(numPoints): + qi = adjacent.getPointIndex(i) + q = self.dnaStore.getSuitPointWithIndex(qi) + + pp = p.getPos() + qp = q.getPos() + + # Get an intermediate point between p and q. + v = Vec3(qp - pp) + v.normalize() + c = v.cross(Vec3.up()) + p1a = pp + v * 2 + c * 0.5 + p1b = pp + v * 3 + p1c = pp + v * 2 - c * 0.5 + + lines.reset() + lines.moveTo(pp) + lines.drawTo(qp) + + # Draw an arrowhead showing direction of travel. + lines.moveTo(p1a) + lines.drawTo(p1b) + lines.drawTo(p1c) + + lines.create(vizNode, 0) + self.__doShowPoints(vizNode, lines, q, points) + + def __makePathVizText(self, text, x, y, z, color): + if not hasattr(self, "debugTextNode"): + self.debugTextNode = TextNode('debugTextNode') + self.debugTextNode.setAlign(TextNode.ACenter) + self.debugTextNode.setFont(ToontownGlobals.getSignFont()) + self.debugTextNode.setTextColor(*color) + self.debugTextNode.setText(text) + np = self.pathViz.attachNewNode(self.debugTextNode.generate()) + np.setPos(x, y, z + 1) + np.setScale(1.0) + np.setBillboardPointEye(2) + + # Use MDual transparency so we can read the numbers through a + # semitransparent bubble (e.g. a battle bubble) + np.node().setAttrib(TransparencyAttrib.make(TransparencyAttrib.MDual), 2) diff --git a/toontown/src/suit/DistributedSuitPlannerAI.py b/toontown/src/suit/DistributedSuitPlannerAI.py new file mode 100644 index 0000000..dfcb05e --- /dev/null +++ b/toontown/src/suit/DistributedSuitPlannerAI.py @@ -0,0 +1,2272 @@ +""" +DistributedSuitPlannerAI module: contains the DistributedSuitPlannerAI +class which handles management of all suits within a single neighborhood. +""" + +# AI code should not import ShowBaseGlobal because it creates a graphics window +# Use AIBaseGlobal instead +from otp.ai.AIBaseGlobal import * + +from direct.distributed import DistributedObjectAI +import SuitPlannerBase +import DistributedSuitAI +from toontown.battle import BattleManagerAI +from direct.task import Task +from direct.directnotify import DirectNotifyGlobal +import SuitDNA +from toontown.battle import SuitBattleGlobals +import SuitTimings +from toontown.toon import NPCToons +from toontown.building import HQBuildingAI +from toontown.hood import ZoneUtil +from toontown.building import SuitBuildingGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.toonbase import ToontownGlobals +import math +import time +import random + +class DistributedSuitPlannerAI(DistributedObjectAI.DistributedObjectAI, + SuitPlannerBase.SuitPlannerBase): + """ + manages all suits which exist within + a single neighborhood (or street), this includes suits in buildings. + It handles creating suits if the neighborhood needs more, or removing + suits if there are too many. This object only exists on the server + AI. + + Attributes: + suitList (list), list of all suits that this planner controls + """ + + # useful constants for the DistributedSuitPlannerAI + # various suit numbers used by each suit planner based on + # which neighborhood it exists in + # +# NUM_SUITS_WHERE_IS_EVERYBODY = 0 +# NUM_SUITS_PIECE_OF_CAKE = 1 +# NUM_SUITS_WALK_IN_THE_PARK = 3 +# NUM_SUITS_LARGE_PILL = 10 +# NUM_SUITS_ROUGHNECK = 20 +# NUM_SUITS_FIGHT_THE_POWER = 30 +# NUM_SUITS_SUITICUS_XTREMICUS = 50 + + # various hood specific information that the suit + # planner for that area will use + # CCC for now have no suits in 2200 and 2300 since + # these hoods have no path information + # 1 zone in which the associated values apply + # + # 2 minimum number of suits that can exist in this hood + # 3 maximum number of suits that can exist in this hood + # The above min/max limit on suits applies only + # to suits that fly in to the zone. Suits that + # walk in from buildings are on top of this + # limit, and each suit building contributes + # SUIT_BUILDING_NUM_SUITS to the expected + # number of suits in a particular zone. + # + # 4 minimum num buildings that can exist in this hood + # 5 maximum num buildings that can exist in this hood + # Note: the min/max number of buildings for a particular + # hood should probably be left as unconstrained as + # possible, to allow the "leaf-blower" system full + # flexibility to dynamically adjust the number of + # buildings everywhere. + # + # 6 weight for chance of new suit building appearing here + # + # 7 maximum number of suits in a battle + # 8 Percent chance that a suit will try to join a battle + # based on the ratio of toons currently in the + # battle to suits that have *ever* been in the + # battle. There are six possible combinations + # where a suit might be able to join. + # + # Starting from the left most entry, + # the first is if the suits outnumber the toons by 2, + # the second is if the suits outnumber the toons by 1, + # the third is if the suits and toons are balanced, + # the fourth is if the toons outnumber the suits by 1, + # the fifth is if the toons outnumber the suits by 2, + # and the last is if the toons outnumber the suits by 3. + # + # So in general, as toons outnumber suits, the chance of + # new suits joining the battle increases (at least + # with the current sets of numbers). + # + # 9 chance of picking from each track (corporate, legal, + # money, sales ) (only used when suit does not enter + # the street from a suit building) + # 10 all possible suit levels for this hood + # + # In general, we set the max suit count fairly high for + # ToontownCentral, and relatively low for the other zones, because + # we expect building suits to make up the difference in the other + # zones. + + # how many more buildings (suit & cogdo) do we want now that there + # are cogdos in the Tooniverse? + CogdoPopFactor = config.GetFloat('cogdo-pop-factor', 1.5) + CogdoRatio = min(1., max(0., config.GetFloat('cogdo-ratio', .5))) + + SuitHoodInfo = [ + # TT is heavy on l, light on c + # Street 2100 is a particularly long street. Lots of room for cogs. + [ 2100, # ZONE + 5, # MIN + 15, # MAX + 0, # BMIN + 5, # BMAX + 20, # BWEIGHT + 3, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 25, 25, 25, 25 ), # TRACK + ( 1, 2, 3 ), # LVL + [], + ], + [ 2200, + 3, + 10, + 0, + 5, + 15, # BWEIGHT + 3, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 10, 70, 10, 10 ), + ( 1, 2, 3 ), + [], + ], + [ 2300, + 3, + 10, + 0, + 5, + 15, # BWEIGHT + 3, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 10, 10, 40, 40 ), + ( 1, 2, 3 ), + [], + ], + + # Donalds dock + # DD is heavy on c (2..4), m (3..6), light on l, s + [ 1100, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 90, 10, 0, 0 ), # TRACK + ( 2, 3, 4 ), # LVL + [], + ], + [ 1200, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 90, 10 ), + ( 3, 4, 5, 6 ), + [], + ], + [ 1300, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 40, 40, 10, 10 ), + ( 3, 4, 5, 6 ), + [], + ], + + # The Brrrgh + # TB is heavy on c, light on l + [ 3100, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 90, 10, 0, 0 ), # TRACK + ( 5, 6, 7 ), # LVL + [], + ], + [ 3200, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 10, 20, 30, 40 ), + ( 5, 6, 7 ), + [], + ], + [ 3300, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 5, 85, 5, 5 ), + ( 7, 8, 9 ), + [], + ], + + # Minnies Melodyland + # MM is heavy on m + [ 4100, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 50, 50 ), # TRACK + ( 2, 3, 4 ), # LVL + [], + ], + [ 4200, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 90, 10 ), + ( 3, 4, 5, 6 ), + [], + ], + [ 4300, + 1, + 5, + 0, + 99, + 100, # BWEIGHT + 4, + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 50, 50, 0, 0 ), + ( 3, 4, 5, 6 ), + [], + ], + + # Daisy Gardens + # DG is heavy on s (2..4), l (3..6) + [ 5100, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 20, 10, 70 ), # TRACK + ( 2, 3, 4 ), # LVL + [], + ], + [ 5200, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 10, 70, 0, 20 ), # TRACK + ( 3, 4, 5, 6 ), # LVL + [], + ], + [ 5300, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + # Mostly sellbot since it is connected to Sellbot HQ + ( 5, 5, 5, 85 ), # TRACK + ( 3, 4, 5, 6 ), # LVL + [], + ], + + # Dreamland + [ 9100, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 25, 25, 25, 25 ), # TRACK + ( 6, 7, 8, 9 ), # LVL + [], + ], + + [ 9200, # ZONE + 1, # MIN + 5, # MAX + 0, # BMIN + 99, # BMAX + 100, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + # Mostly cashbot since it is connected to Cashbot HQ + ( 5, 5, 85, 5 ), # TRACK + ( 6, 7, 8, 9 ), # LVL + [], + ], + + # Sellbot HQ Exterior + [ 11000, # ZONE + 3, # MIN + 15, # MAX + 0, # BMIN + 0, # BMAX + 0, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 0, 100 ), # TRACK + ( 4, 5, 6 ), # LVL + [], + ], + [ 11200, # ZONE + 10, # MIN + 20, # MAX + 0, # BMIN + 0, # BMAX + 0, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 0, 100 ), # TRACK + ( 4, 5, 6 ), # LVL + [], + ], + + # Cash HQ Exterior + [ 12000, # ZONE + 10, # MIN + 20, # MAX + 0, # BMIN + 0, # BMAX + 0, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 0, 100, 0 ), # TRACK + ( 7, 8, 9 ), # LVL + [], + ], + + # Law HQ Exterior + [ 13000, # ZONE + 10, # MIN + 20, # MAX + 0, # BMIN + 0, # BMAX + 0, # BWEIGHT + 4, # SMAX + ( 1, 5, 10, 40, 60, 80 ), # JCHANCE + ( 0, 100, 0, 0 ), # TRACK + ( 8, 9, 10 ), # LVL + [], + ], + + ] + + # index values into the SuitHoodInfo struct above for each type of value + # + SUIT_HOOD_INFO_ZONE = 0 + SUIT_HOOD_INFO_MIN = 1 + SUIT_HOOD_INFO_MAX = 2 + SUIT_HOOD_INFO_BMIN = 3 + SUIT_HOOD_INFO_BMAX = 4 + SUIT_HOOD_INFO_BWEIGHT = 5 + SUIT_HOOD_INFO_SMAX = 6 + SUIT_HOOD_INFO_JCHANCE = 7 + SUIT_HOOD_INFO_TRACK = 8 + SUIT_HOOD_INFO_LVL = 9 + SUIT_HOOD_INFO_HEIGHTS = 10 + + # highest available suit type (flunky, pencil pusher, etc)(1-based) + # + # Exclude the big guys from the streets--they live only inside + # buildings. + MAX_SUIT_TYPES = 6 + + # How often to upkeep and adjust suit population, in seconds. + POP_UPKEEP_DELAY = 10 + POP_ADJUST_DELAY = 300 + + # The time along a path, in seconds, that will be maintained + # between any two suits for spacing. + PATH_COLLISION_BUFFER = 5 + + # A hard maximum on the number of suits we try to put in the zone. + # This overrides any per-zone maximum specified in the above + # table, and also includes the count of building suits. The main + # purpose of this limit is to keep us from wasting resources + # trying to squeeze 200 suits into a street where they can't + # possibly fit. Empirically, with PATH_COLLISION_BUFFER set to 5, + # we can get as many as 80 suits on one of the long streets in + # TTC, but only about 40 on some of the shorter streets. + TOTAL_MAX_SUITS = 50 + + # The minimum and maximum length of a path that will be acceptable + # for a given suit assignment, in number of suit points passed. + + # MIN_PATH_LEN should be at least 2, because less than that will + # cause the AI to crash with assertion failures and array + # underruns. + + # The longest street is Silly Street with 192 points; a path might + # therefore need to be as long as 192 + MIN_PATH_LEN points to + # reach completion. We define MAX_PATH_LEN to be 300 to give a + # comfortable margin; setting it higher just makes it take longer + # to discover disconnected graphs. + MIN_PATH_LEN = 40 + MAX_PATH_LEN = 300 + + # Suits on the takeover march are allowed shorter paths. + MIN_TAKEOVER_PATH_LEN = 2 + + SUITS_ENTER_BUILDINGS = 1 + + # The number of additional suits contributed to the zone by each + # building, at any given time. This may be a floating-point + # number if necessary. + SUIT_BUILDING_NUM_SUITS = 1.5 + + # Suit building timeouts. A particular hood may only have so many + # suit buildings. After a building has been a suit building for a + # length of time, it is automatically reconverted to a toon + # building; however, this timeout is based on the number of suit + # buildings in the block. Thus, the more suit buildings there are + # on a particular block, the more quickly they will reconvert to + # toon buildings. This is intended to prevent suit buildings from + # accumulating in the harder streets and never getting reclaimed. + + # This table is the length of time, in *hours*, for buildings, + # with one entry for each number of buildings in the street. None + # means no timeout. + + SUIT_BUILDING_TIMEOUT = [ + None, None, None, None, None, None, # 0 - 5 buildings: no timeout + 72, 60, 48, 36, 24, # 6 - 10 buildings + 12, 6, 3, 1, 0.5, # 11 - 15 and higher + ] + + # How many suit buildings should there be in the whole world at + # any given time? This is expressed as a percentage of the total + # number of buildings. + TOTAL_SUIT_BUILDING_PCT = 18 * CogdoPopFactor + + # What is the balance of suit building heights in the world? The + # SuitPlanner will attempt to keep this relative weighted ratio of + # building heights. For example, if the first number in the + # following list is 12 and the second number is 24, and the sum of + # all of the numbers in the list is 85, then 12/85 of the + # buildings in the world will be 1-story, and 24/85 will be + # 2-story. + BUILDING_HEIGHT_DISTRIBUTION = [ + 14, 18, 25, 23, 20 + ] + + # We need the total of all BWEIGHT values so we can compute + # weighted chances properly. And, we keep the total weights as + # modified per track, for cases when we want to choose a street to + # prefer a particular kind of suit building over any other kind. + TOTAL_BWEIGHT = 0 + + # We also need the same count, weighted by the chance of a suit of + # a particular track appearing in that zone. This enables us to + # choose a suitable street to hold a building of a particular + # track. + TOTAL_BWEIGHT_PER_TRACK = [0, 0, 0, 0] + + # And again, weighted by the chance of a suit of an appropriate + # level to create a building of a particular height. + TOTAL_BWEIGHT_PER_HEIGHT = [0, 0, 0, 0, 0] + + for currHoodInfo in SuitHoodInfo: + weight = currHoodInfo[SUIT_HOOD_INFO_BWEIGHT] + tracks = currHoodInfo[SUIT_HOOD_INFO_TRACK] + levels = currHoodInfo[SUIT_HOOD_INFO_LVL] + + # levels is the list of suit levels that may be encountered in + # this zone. There's an equal weight chance of each level + # suit appearing, and each level suit makes a building of the + # corresponding level, which has a particular chance of being + # any of a specified number of floors. Crunch these numbers + # down into the overall chance of a particular building height + # on this streeet. + heights = [0, 0, 0, 0, 0] + for level in levels: + minFloors, maxFloors = SuitBuildingGlobals.SuitBuildingInfo[level - 1][0] + # Remember that buildingHeight is numFloors - 1 + for i in range(minFloors - 1, maxFloors): + heights[i] += 1 + + # Now that we've computed this heights list, store it back on + # the global structure for future reference. + currHoodInfo[SUIT_HOOD_INFO_HEIGHTS] = heights + + TOTAL_BWEIGHT += weight + TOTAL_BWEIGHT_PER_TRACK[0] += weight * tracks[0] + TOTAL_BWEIGHT_PER_TRACK[1] += weight * tracks[1] + TOTAL_BWEIGHT_PER_TRACK[2] += weight * tracks[2] + TOTAL_BWEIGHT_PER_TRACK[3] += weight * tracks[3] + + TOTAL_BWEIGHT_PER_HEIGHT[0] += weight * heights[0] + TOTAL_BWEIGHT_PER_HEIGHT[1] += weight * heights[1] + TOTAL_BWEIGHT_PER_HEIGHT[2] += weight * heights[2] + TOTAL_BWEIGHT_PER_HEIGHT[3] += weight * heights[3] + TOTAL_BWEIGHT_PER_HEIGHT[4] += weight * heights[4] + + # This Configrc constrains the kinds of suits we might create. + defaultSuitName = simbase.config.GetString('suit-type', 'random') + if defaultSuitName == 'random': + defaultSuitName = None + + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuitPlannerAI') + + def __init__(self, air, zoneId): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + SuitPlannerBase.SuitPlannerBase.__init__( self ) + self.air = air + self.zoneId = zoneId + self.canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId) + + if simbase.air.wantCogdominiums: + # adjust building populations wrt Cogdos + if not hasattr(self.__class__, 'CogdoPopAdjusted'): + self.__class__.CogdoPopAdjusted = True + for index in xrange(len(self.SuitHoodInfo)): + hoodInfo = self.SuitHoodInfo[index] + hoodInfo[self.SUIT_HOOD_INFO_BMIN] = int(.5 + (self.CogdoPopFactor * + hoodInfo[self.SUIT_HOOD_INFO_BMIN])) + hoodInfo[self.SUIT_HOOD_INFO_BMAX] = int(.5 + (self.CogdoPopFactor * + hoodInfo[self.SUIT_HOOD_INFO_BMAX])) + + # remember which entry in SuitHoodInfo this suit planner will be + # using, this is based on the zone id assigned + # + self.hoodInfoIdx = -1 + for index in range(len(self.SuitHoodInfo)): + currHoodInfo = self.SuitHoodInfo[index] + if currHoodInfo[ self.SUIT_HOOD_INFO_ZONE ] == self.canonicalZoneId: + self.hoodInfoIdx = index + assert self.hoodInfoIdx != -1, "No hood information found in table: zoneId=%s" %zoneId + + # remember the number of suits we want in this hood + # this could vary based on toons currently in the hood and + # a random population range that changes over time + # currDesired is a manual override to create a specific number + # of suits if it is set to a valid number + # + self.currDesired = None + self.baseNumSuits = \ + (self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_MIN] + \ + self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_MAX]) / 2 + + # Remember the number of buildings we are assigned for this + # particular street. This will vary between streets as + # buildings are reclaimed by toons to keep the global number + # of suit buildings across the shard constant. But, we have + # to start out with at least the specified minimum. + self.targetNumSuitBuildings = \ + self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_BMIN] + if ZoneUtil.isWelcomeValley(self.zoneId): + # For now, we won't have any suit buildings in WelcomeValley. + # It bitches the suit ecology since the WelcomeValley zones + # can come and go dynamically. + self.targetNumSuitBuildings = 0 + + # This records the tracks requested for the pending buildings. + # As each building is created, it selects a track from this + # list, if the list is nonempty. + self.pendingBuildingTracks = [] + + # Similarly, for the number of floors requested for pending + # buildings. + self.pendingBuildingHeights = [] + + # various lists of suits, most are temporary holding lists, + # the main list is 'suitList' + # + self.suitList = [] + self.numFlyInSuits = 0 + self.numBuildingSuits = 0 + self.numAttemptingTakeover = 0 + + self.zoneInfo = {} + + self.zoneIdToPointMap = None + + # This may be filled in with a list of lobby doors if this + # happens to be a SuitPlanner for a CogHQExterior. + self.cogHQDoors = [] + + self.battleList = [] + + # Create a battle manager AI for the street + self.battleMgr = BattleManagerAI.BattleManagerAI(self.air) + + # load up the dna to fill in the suit path point information + # + self.setupDNA() + + if self.notify.getDebug(): + self.notify.debug( "Creating a building manager AI in zone" + + str( self.zoneId ) ) + self.buildingMgr = self.air.buildingManagers.get(self.zoneId) + + # tell all buildings in this building manager which suit + # planner is in the same hood, and if the building is a + # suit building, make sure to add it to our list of suit + # blocks (used to find suit source and destination locations + # when creating paths) + # + if self.buildingMgr: + blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks = self.buildingMgr.getDNABlockLists() + for currBlock in blocks: + bldg = self.buildingMgr.getBuilding( currBlock ) + bldg.setSuitPlannerExt( self ) + for currBlock in animBldgBlocks: + bldg = self.buildingMgr.getBuilding( currBlock ) + bldg.setSuitPlannerExt( self ) + + # The block number to zone map was created for the building + # and door creation. Now that it's done, we clear the map: + self.dnaStore.resetBlockNumbers() + + # now let all of the buildings know what path points they should + # be associated with + # + self.initBuildingsAndPoints() + + # perform a simple path test to make sure we can properly + # generate a path from two points given to us by the DNAStorage + # + #self.performPathTest() + + # set the suit number override if one is provided in the xrc + # + numSuits = simbase.config.GetInt( 'suit-count', -1 ) + if numSuits >= 0: + self.currDesired = numSuits + suitHood = simbase.config.GetInt( 'suits-only-in-hood', -1 ) + if suitHood >= 0: + if self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_ZONE] != suitHood: + self.currDesired = 0 + + # an adjustment to the base desired num suits in this hood, this + # adjustment changes gradually over time + # + self.suitCountAdjust = 0 + + return None + + def cleanup(self): + """ + called before this guy is deleted, remove any + pending tasks + """ + # remove the task that updates the suitPlannerAI periodically + # + taskMgr.remove( self.taskName('sptUpkeepPopulation') ) + taskMgr.remove( self.taskName('sptAdjustPopulation') ) + + for suit in self.suitList: + suit.stopTasks() + if suit.isGenerated(): + self.zoneChange(suit, suit.zoneId) + suit.requestDelete() + + self.suitList = [] + self.numFlyInSuits = 0 + self.numBuildingSuits = 0 + self.numAttemptingTakeover = 0 + + + def delete(self): + self.cleanup() + DistributedObjectAI.DistributedObjectAI.delete(self) + + def initBuildingsAndPoints(self): + """ + let all the buildings know about the suit path + points that are in front of them, this way the suit + planner can ask the building for path points which + suits can use to enter and exit the building + """ + if not self.buildingMgr: + return + if self.notify.getDebug(): + self.notify.debug( "Initializing building points" ) + + # We need to associate buildings with their doors. This just + # builds a reverse lookup based on the data we already + # extracted from the DNA file. Each building should have + # one front door and one or more side doors. + + self.buildingFrontDoors = {} + self.buildingSideDoors = {} + + for p in self.frontdoorPointList: + blockNumber = p.getLandmarkBuildingIndex() + if p < 0: + self.notify.warning("No landmark building for (%s) in zone %d" % (repr(p), self.zoneId)) + + elif self.buildingFrontDoors.has_key(blockNumber): + self.notify.warning("Multiple front doors for building %d in zone %d" % (blockNumber, self.zoneId)) + + else: + self.buildingFrontDoors[blockNumber] = p + + for p in self.sidedoorPointList: + blockNumber = p.getLandmarkBuildingIndex() + if p < 0: + self.notify.warning("No landmark building for (%s) in zone %d" % (repr(p), self.zoneId)) + + elif self.buildingSideDoors.has_key(blockNumber): + self.buildingSideDoors[blockNumber].append(p) + else: + self.buildingSideDoors[blockNumber] = [p] + + # Now make sure that each building has *at least* one of each + # kind of door. + for bldg in self.buildingMgr.getBuildings(): + if isinstance(bldg, HQBuildingAI.HQBuildingAI): + # Move to the next one + continue + blockNumber = bldg.getBlock()[0] + if not self.buildingFrontDoors.has_key(blockNumber): + self.notify.warning("No front door for building %d in zone %d" % (blockNumber, self.zoneId)) + if not self.buildingSideDoors.has_key(blockNumber): + self.notify.warning("No side door for building %d in zone %d" % (blockNumber, self.zoneId)) + + + def countNumSuitsPerTrack(self, count): + """ + countNumSuitsPerTrack(self, map count) + + Given that count is a map of track letters to counts, e.g. { + 'c':0, 'l':0, 'm':0, 's':0 }, or an empty map, increment each + count corresponding to the number of suits of that type in the + neighborhood. This is only used for magic word fulfillment + and debug output. + """ + for suit in self.suitList: + if count.has_key(suit.track): + count[suit.track] += 1 + else: + count[suit.track] = 1 + + def countNumBuildingsPerTrack(self, count): + """ + countNumBuildingsPerTrack(self, map count) + + Given that count is a map of track letters to counts, e.g. { + 'c':0, 'l':0, 'm':0, 's':0 }, or an empty map, increment each + count corresponding to the number of suit buildings of that + type in the neighborhood. + """ + if self.buildingMgr: + for building in self.buildingMgr.getBuildings(): + if building.isSuitBuilding(): + if count.has_key(building.track): + count[building.track] += 1 + else: + count[building.track] = 1 + + def countNumBuildingsPerHeight(self, count): + """ + countNumBuildingsPerHeight(self, map count) + + Given that count is a map of heights to counts, e.g. { 0:0, + 1:0, 2:0, 3:0, 4:0 }, or an empty map, increment each count + corresponding to the number of suit buildings of that height + in the neighborhood. + + Note that the building "height" is the zero-based index into + the number of floors: height = numFloors - 1. + """ + if self.buildingMgr: + for building in self.buildingMgr.getBuildings(): + if building.isSuitBuilding(): + # buildingHeight is numFloors - 1 + height = building.numFloors - 1 + if count.has_key(height): + count[height] += 1 + else: + count[height] = 1 + + def formatNumSuitsPerTrack(self, count): + """ + formatNumSuitsPerTrack(self, map count) + + Given a map filled in by a previous call to + countNumSuitsPerTrack() or countNumBuildingsPerTrack(), + formats the result as a string. + """ + result = " " + for track, num in count.items(): + result += " %s:%d" % (track, num) + + return result[2:] + + def calcDesiredNumFlyInSuits(self): + """ + Returns the number of fly-in suits that should be walking + around the neighborhood. + """ + + # first check to see if a manual suit count override has been + # specified, if not, return base number of suits plus any + # previously calculated adjustment + # + if self.currDesired != None: + return 0 + return self.baseNumSuits + self.suitCountAdjust + + def calcDesiredNumBuildingSuits(self): + """ + Returns the number of building suits that should be walking + around the neighborhood. + """ + + if self.currDesired != None: + return self.currDesired + if not self.buildingMgr: + return 0 + suitBuildings = self.buildingMgr.getEstablishedSuitBlocks() + return int(len(suitBuildings) * self.SUIT_BUILDING_NUM_SUITS) + + + def getZoneIdToPointMap(self): + """ + Creates a reverse lookup from street zoneId's to lists of + DNASuitPoints. This is only used for magic word fulfillment, + so the map isn't created until it is demanded. It's fairly + expensive to create this map, since the points aren't really + designed to be looked up this way. + """ + if self.zoneIdToPointMap != None: + return self.zoneIdToPointMap + + self.zoneIdToPointMap = {} + + for point in self.streetPointList: + # Determine the zones the point intersects by pulling out + # all the points adjacent to this one. + points = self.dnaStore.getAdjacentPoints(point) + i = points.getNumPoints() - 1 + while i >= 0: + pi = points.getPointIndex(i) + p = self.pointIndexes[pi] + i -= 1 + + zoneName = self.dnaStore.getSuitEdgeZone( + point.getIndex(), p.getIndex()) + zoneId = int(self.extractGroupName(zoneName)) + + if self.zoneIdToPointMap.has_key(zoneId): + self.zoneIdToPointMap[zoneId].append(point) + else: + self.zoneIdToPointMap[zoneId] = [point] + + return self.zoneIdToPointMap + + def getStreetPointsForBuilding(self, blockNumber): + """ + getStreetPointsForBuilding(self, int blockNumber) + + Returns a list of street points in front of the indicated + building. This function is only used for magic word + fulfillment. + """ + + pointList = [] + if self.buildingSideDoors.has_key(blockNumber): + for doorPoint in self.buildingSideDoors[blockNumber]: + # given the door point, find the street points in + # front of it. + points = self.dnaStore.getAdjacentPoints(doorPoint) + + i = points.getNumPoints() - 1 + while i >= 0: + pi = points.getPointIndex(i) + point = self.pointIndexes[pi] + if point.getPointType() == DNASuitPoint.STREETPOINT: + pointList.append(point) + i -= 1 + + if self.buildingFrontDoors.has_key(blockNumber): + doorPoint = self.buildingFrontDoors[blockNumber] + points = self.dnaStore.getAdjacentPoints(doorPoint) + + i = points.getNumPoints() - 1 + while i >= 0: + pi = points.getPointIndex(i) + pointList.append(self.pointIndexes[pi]) + i -= 1 + + return pointList + + def createNewSuit(self, blockNumbers, streetPoints, + toonBlockTakeover = None, + cogdoTakeover = None, + minPathLen = None, + maxPathLen = None, + buildingHeight = None, + suitLevel = None, + suitType = None, + suitTrack = None, + suitName = None, + skelecog = None, + revives = None): + + """ + createNewSuit(self, list blockNumbers, list streetPoints) + + Chooses a suitable point from streetPoints (if the list is + nonempty), or a suit building from blockNumbers, in which to + create the suit. Removes unsuitable points from either array. + If no suitable point or building can be found, returns 0. + + If a suitable starting point is found, creates a new suit and + starts it walking around. If the starting point is from the + streetPoints, the street is flown in from the sky; otherwise, + it walks out of the chosen building. + + The return value is the suit if one is created, or None otherwise. + + The additional keyword arguments are primarily for the benefit + of magic words to create suits for special purposes. + toonBlockTakeover, if specified, is the block number of a toon + building to take over specifically. minPathLen and maxPathLen + put limits on the length of the suit's path, in number of suit + points. suitLevel, suitType, and suitTrack define the + particular kind of suit to create; otherwise, a random suit is + chosen based on the neighborhood parameters. suitName is + another way to specify a type of suit; it may be any of the + one- or two-letter suit codes, like 'pp' or 'ym'. + """ + # First, choose a good starting point for the suit. + startPoint = None + blockNumber = None + + if self.notify.getDebug(): + self.notify.debug("Choosing origin from %d+%d possibles." % (len(streetPoints), len(blockNumbers))) + + # First, try to create a from-building suit. + while startPoint == None and len(blockNumbers) > 0: + bn = random.choice(blockNumbers) + blockNumbers.remove(bn) + + if self.buildingSideDoors.has_key(bn): + for doorPoint in self.buildingSideDoors[bn]: + # given the door point, find the street points in + # front of it. + points = self.dnaStore.getAdjacentPoints(doorPoint) + + # Now iterate through all the street points. + # We'll count down from the end just because + # that's easier. + i = points.getNumPoints() - 1 + while blockNumber == None and i >= 0: + pi = points.getPointIndex(i) + p = self.pointIndexes[pi] + i -= 1 + + # Now include the travel time from the door point + # to the street. + startTime = SuitTimings.fromSuitBuilding + startTime += self.dnaStore.getSuitEdgeTravelTime( + doorPoint.getIndex(), pi, + self.suitWalkSpeed) + + if not self.pointCollision(p, doorPoint, startTime): + # reset the start time back to our first point. + startTime = SuitTimings.fromSuitBuilding + startPoint = doorPoint + blockNumber = bn + + + # Failing that, just fly a suit in. + while startPoint == None and len(streetPoints) > 0: + p = random.choice(streetPoints) + streetPoints.remove(p) + + if not self.pointCollision(p, None, SuitTimings.fromSky): + startPoint = p + startTime = SuitTimings.fromSky + + if startPoint == None: + return None + + newSuit = DistributedSuitAI.DistributedSuitAI(simbase.air, self) + newSuit.startPoint = startPoint + + if blockNumber != None: + # The suit originates from a building. This also means it + # inherits the building's track. + newSuit.buildingSuit = 1 + if suitTrack == None: + suitTrack = self.buildingMgr.getBuildingTrack(blockNumber) + + else: + # The suit flies in from the sky. + newSuit.flyInSuit = 1 + + # Only fly-in suits may attempt building takeovers. This + # helps preserve the balance of building tracks on a + # particular street. If we did not do this, once a + # particular building track got a toehold it would have an + # advantage over the other tracks. + newSuit.attemptingTakeover = self.newSuitShouldAttemptTakeover() + + if newSuit.attemptingTakeover: + # Also, if he's attempting a takeover, make him be a + # suitable track. + if suitTrack == None and len(self.pendingBuildingTracks) > 0: + suitTrack = self.pendingBuildingTracks[0] + + # Move the suitTrack to the end of the queue, so + # the next suit will choose a different track. We + # can't remove it from the queue until the + # building actually gets created. + del self.pendingBuildingTracks[0] + self.pendingBuildingTracks.append(suitTrack) + + if buildingHeight == None and len(self.pendingBuildingHeights) > 0: + buildingHeight = self.pendingBuildingHeights[0] + del self.pendingBuildingHeights[0] + self.pendingBuildingHeights.append(buildingHeight) + + # If we're constrained to create only a particular type of + # suit, do so. + if suitName == None: + # If there is an invasion, the suit name will be picked for us + suitName, skelecog = self.air.suitInvasionManager.getInvadingCog() + # If we are still at none, use the default suit + if suitName == None: + suitName = self.defaultSuitName + + if suitType == None and suitName != None: + suitType = SuitDNA.getSuitType(suitName) + suitTrack = SuitDNA.getSuitDept(suitName) + + if suitLevel == None and buildingHeight != None: + # Choose an appropriate level suit that will make a + # building of the requested height. + suitLevel = self.chooseSuitLevel(self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_LVL], + buildingHeight) + + # Now fill in the level, type, and track parameters that + # haven't been specified yet. + suitLevel, suitType, suitTrack = \ + self.pickLevelTypeAndTrack(suitLevel, suitType, suitTrack) + newSuit.setupSuitDNA(suitLevel, suitType, suitTrack) + newSuit.buildingHeight = buildingHeight + + gotDestination = self.chooseDestination( + newSuit, startTime, + toonBlockTakeover = toonBlockTakeover, + cogdoTakeover = cogdoTakeover, + minPathLen = minPathLen, + maxPathLen = maxPathLen) + + + if not gotDestination: + # No good destination, for some reason. Delete the suit + # and return 0 to try again. + self.notify.debug("Couldn't get a destination in %d!" % (self.zoneId)) + newSuit.doNotDeallocateChannel=None # This is a hack. See Joe for details. + newSuit.delete() + return None + + # Initialize all the path information for passing down to + # clients. + newSuit.initializePath() + + # be sure to flag the zone change when this suit is first + # created + self.zoneChange(newSuit, None, newSuit.zoneId) + + # if this suit is a skeleton... + if skelecog: + newSuit.setSkelecog(skelecog) + + # if this suit is a skeleton 2.0... + if revives: + newSuit.setSkeleRevives(revives) + + # call 'generate' to create all versions of this suit, + # including the local server side version, as well as all of + # the client side versions, this also creates the suit's + # unique distributed object id + newSuit.generateWithRequired(newSuit.zoneId) + + # And now we can start the suit walking. + newSuit.moveToNextLeg(None) + + # now add the suit to our list so we can do things with it when + # needed + # + self.suitList.append(newSuit) + + if newSuit.flyInSuit: + self.numFlyInSuits += 1 + + if newSuit.buildingSuit: + self.numBuildingSuits += 1 + + if newSuit.attemptingTakeover: + self.numAttemptingTakeover += 1 + + return newSuit + + def countNumNeededBuildings(self): + """ + Returns the number of additional suit buildings we want to try + to take over. + """ + if not self.buildingMgr: + return 0 + numSuitBuildings = len(self.buildingMgr.getSuitBlocks()) + numNeeded = self.targetNumSuitBuildings - numSuitBuildings + + return numNeeded + + def newSuitShouldAttemptTakeover(self): + """ + Decides whether it is time for a newly-created suit to attempt + to take over an innocent toon building. Returns true if so, + false otherwise. + """ + if not self.SUITS_ENTER_BUILDINGS: + return 0 + + numNeeded = self.countNumNeededBuildings() + + if self.numAttemptingTakeover >= numNeeded: + # There's already enough suits on the march. Never mind. + self.pendingBuildingTracks = [] + return 0 + + self.notify.debug("DSP %d is planning a takeover attempt in zone %d" % (self.getDoId(), self.zoneId)) + return 1 + + + def chooseDestination(self, suit, startTime, + toonBlockTakeover = None, + cogdoTakeover = None, + minPathLen = None, + maxPathLen = None): + """ + chooseDestination(self, DistributedSuitAI suit, float startTime) + + Given that the suit has already been assigned a starting point + (suit.startPoint) and that it has already decided whether it + will attempt to take over a toon building + (suit.attemptingTakeover), choose a suitable destination point + for the suit. + + startTime is the number of seconds from now at which the suit + will start on the path. This is needed to properly separate + the suit from other suits. + + The return value is true if a path can be found, or false if not. + """ + # First, build up the list of all of our possible destinations. + possibles = [] + backup = [] + + if cogdoTakeover is None: + cogdoTakeover = False + + if toonBlockTakeover != None: + # The suit is specifically charged with taking over this + # particular toon building. This will only happen due to + # a magic word or something. + suit.attemptingTakeover = 1 + + blockNumber = toonBlockTakeover + if self.buildingFrontDoors.has_key(blockNumber): + possibles.append((blockNumber, self.buildingFrontDoors[blockNumber])) + elif suit.attemptingTakeover: + + # We have all of the toon buildings to choose from, except + # for the "protected" buildings. + for blockNumber in self.buildingMgr.getToonBlocks(): + + building = self.buildingMgr.getBuilding(blockNumber) + extZoneId, intZoneId = building.getExteriorAndInteriorZoneId() + + if not NPCToons.isZoneProtected(intZoneId): + if self.buildingFrontDoors.has_key(blockNumber): + possibles.append((blockNumber, self.buildingFrontDoors[blockNumber])) + else: + # We have all of the suit buildings that match our DNA + # track to choose from (corporate suits don't mingle with + # legal suits), as well as all points in the street. + + if self.buildingMgr: + for blockNumber in self.buildingMgr.getSuitBlocks(): + track = self.buildingMgr.getBuildingTrack(blockNumber) + if track == suit.track and \ + self.buildingSideDoors.has_key(blockNumber): + for doorPoint in self.buildingSideDoors[blockNumber]: + possibles.append((blockNumber, doorPoint)) + + # Suits always prefer to walk into suit buildings, if they + # can find one. If there aren't any suit buildings to + # choose from, they'll pick a point on the street to walk + # to, then fly away from there. + backup = [] + for p in self.streetPointList: + backup.append((None, p)) + + if self.notify.getDebug(): + self.notify.debug("Choosing destination point from %d+%d possibles." % (len(possibles), len(backup))) + + if len(possibles) == 0: + possibles = backup + backup = [] + + if minPathLen == None: + if suit.attemptingTakeover: + minPathLen = self.MIN_TAKEOVER_PATH_LEN + else: + minPathLen = self.MIN_PATH_LEN + if maxPathLen == None: + maxPathLen = self.MAX_PATH_LEN + + # Now pull destinations out at random, one at a time, until + # we're happy with the resulting path. + retryCount = 0 + while len(possibles) > 0 and retryCount < 50: + p = random.choice(possibles) + possibles.remove(p) + + if len(possibles) == 0: + possibles = backup + backup = [] + + path = self.genPath(suit.startPoint, p[1], minPathLen, maxPathLen) + if path and not self.pathCollision(path, startTime): + # The path looks good; take it! + suit.endPoint = p[1] + suit.minPathLen = minPathLen + suit.maxPathLen = maxPathLen + suit.buildingDestination = p[0] + suit.buildingDestinationIsCogdo = cogdoTakeover + suit.setPath(path) + return 1 + + retryCount += 1 + + # None of our destinations were suitable. Probably there was + # a battle going on very near our starting point, trapping us + # in a corner or something. + return 0 + + def pathCollision(self, path, elapsedTime): + """pathCollision(self, DNASuitPath path) + + Returns true if the path is unsuitable because another suit + will be walking too close by its first point in elapsedTime + seconds or if there is a battle there right now, or false + otherwise. + """ + # Get the first street point in the path, and the point just + # before that. + pathLength = path.getNumPoints() + + i = 0 + assert i < pathLength + pi = path.getPointIndex(i) + point = self.pointIndexes[pi] + + # Start off with adjacentPoint indicating the second point in + # the path, in case the first point happens to be a street + # point. + adjacentPoint = self.pointIndexes[path.getPointIndex(i + 1)] + + while point.getPointType() == DNASuitPoint.FRONTDOORPOINT or \ + point.getPointType() == DNASuitPoint.SIDEDOORPOINT: + i += 1 + assert i < pathLength + + lastPi = pi + pi = path.getPointIndex(i) + adjacentPoint = point + point = self.pointIndexes[pi] + + elapsedTime += self.dnaStore.getSuitEdgeTravelTime( + lastPi, pi, self.suitWalkSpeed) + + result = self.pointCollision(point, adjacentPoint, elapsedTime) + + return result + + + def pointCollision(self, point, adjacentPoint, elapsedTime): + """pointCollision(self, DNASuitPoint point, DNASuitPoint adjacentPoint, + float elapsedTime) + + Returns true if the point is unsuitable for starting a path + because another suit will be walking right there in + elapsedTime seconds, or if there is a battle there right now. + See also pathCollision(). + + If adjacentPoint is not None, it is a point adjacent to the + point we are testing, which is used to determine what zone the + point in question is in (for checking for battles). If + adjacentPoint is None, then all adjacent points will be + checked. + """ + for suit in self.suitList: + if suit.pointInMyPath(point, elapsedTime): + return 1 + + if adjacentPoint != None: + return self.battleCollision(point, adjacentPoint) + + else: + # Go through all the points adjacent to the indicated one. + # If there's a battle in any of these, it counts. + + points = self.dnaStore.getAdjacentPoints(point) + i = points.getNumPoints() - 1 + while i >= 0: + pi = points.getPointIndex(i) + p = self.pointIndexes[pi] + i -= 1 + + if self.battleCollision(point, p): + return 1 + + # No suits or battles in sight. + return 0 + + def battleCollision(self, point, adjacentPoint): + """battleCollision(self, DNASuitPoint point, DNASuitPoint adjacentPoint) + Returns true if there is a battle currently underway in the + zone containing the edge connecting point and adjacentPoint. + """ + zoneName = self.dnaStore.getSuitEdgeZone( + point.getIndex(), adjacentPoint.getIndex()) + zoneId = int(self.extractGroupName(zoneName)) + + return self.battleMgr.cellHasBattle(zoneId) + + + def removeSuit(self, suit): + """ + Removes a suit that's no longer needed. This deletes the + DistributedObject and also cleans up any data structures + referencing the suit in the planner. + """ + #self.notify.info("Suit planner removing suit %s" % (suit.doId)) + + # be sure to clear the zone that the suit is in since it + # is going to be removed completely + self.zoneChange( suit, suit.zoneId ) + + if self.suitList.count( suit ) > 0: + self.suitList.remove( suit ) + + if suit.flyInSuit: + self.numFlyInSuits -= 1 + if suit.buildingSuit: + self.numBuildingSuits -= 1 + if suit.attemptingTakeover: + self.numAttemptingTakeover -= 1 + + assert self.numFlyInSuits + self.numBuildingSuits == len(self.suitList) + assert self.numAttemptingTakeover == self.countTakeovers() + + suit.requestDelete() + return + + def countTakeovers(self): + """ + Returns the number of suits *actually* attempting takeover. + This is just a verification check against + self.numAttemptingTakeover; it only gets called when + assertions are enabled. + """ + count = 0 + for suit in self.suitList: + if suit.attemptingTakeover: + count += 1 + return count + + def __waitForNextUpkeep(self): + t = (random.random() * 2.0) + self.POP_UPKEEP_DELAY + taskMgr.doMethodLater(t, self.upkeepSuitPopulation, + self.taskName('sptUpkeepPopulation')) + + def __waitForNextAdjust(self): + t = (random.random() * 10.0) + self.POP_ADJUST_DELAY + taskMgr.doMethodLater(t, self.adjustSuitPopulation, + self.taskName('sptAdjustPopulation')) + + def upkeepSuitPopulation(self, task): + """ + examine the number of suits that exist and remove + or add some in order to keep a reasonable balance, + this should be called every once in a while + """ + + # How many fly-in suits do we expect to have? + targetFlyInNum = self.calcDesiredNumFlyInSuits() + targetFlyInNum = min(targetFlyInNum, self.TOTAL_MAX_SUITS - self.numBuildingSuits) + + # We'll need a copy of the list of street points, so we can + # modify this as we eliminate choices. + streetPoints = self.streetPointList[:] + + # We create one-fourth of the required number of suits each + # time. This will help us get caught up if we are way behind + # in suits. + flyInDeficit = (targetFlyInNum - self.numFlyInSuits + 3) / 4 + + while flyInDeficit > 0: + if not self.createNewSuit([], streetPoints): + break + + flyInDeficit -= 1 + + # How many from-building suits do we expect to have? Here we + # count up the number of from-building suits we want, and add + # in the number of fly-in suits we couldn't have from above, + # to bring our total suit count to as close an approximation + # as possible of our actual target. + if self.buildingMgr: + suitBuildings = self.buildingMgr.getEstablishedSuitBlocks() + else: + suitBuildings = [] + + if self.currDesired != None: + targetBuildingNum = max(0, self.currDesired - self.numFlyInSuits) + else: + targetBuildingNum = int(len(suitBuildings) * self.SUIT_BUILDING_NUM_SUITS) + + targetBuildingNum += flyInDeficit + targetBuildingNum = min(targetBuildingNum, self.TOTAL_MAX_SUITS - self.numFlyInSuits) + + buildingDeficit = (targetBuildingNum - self.numBuildingSuits + 3) / 4 + + # Also, while we create from-building suits, we allow them to + # fall back to fly-in suits if they can't find a door to walk + # out of. + while buildingDeficit > 0: + if not self.createNewSuit(suitBuildings, streetPoints): + break + + buildingDeficit -= 1 + + if self.notify.getDebug() and self.currDesired == None: + self.notify.debug("zone %d has %d of %d fly-in and %d of %d building suits." % + (self.zoneId, + self.numFlyInSuits, targetFlyInNum, + self.numBuildingSuits, targetBuildingNum)) + if buildingDeficit != 0: + self.notify.debug("remaining deficit is %d." % (buildingDeficit)) + + # Finally, automatically reconvert the oldest suit building to + # a toon building, if it's very old. If no one's taken it + # over by now, let it go back into the pool. + + if self.buildingMgr: + suitBuildings = self.buildingMgr.getEstablishedSuitBlocks() + timeoutIndex = min(len(suitBuildings), len(self.SUIT_BUILDING_TIMEOUT) - 1) + timeout = self.SUIT_BUILDING_TIMEOUT[timeoutIndex] + if timeout != None: + timeout *= 3600.0 # convert hours to seconds + + # Determine the oldest (unoccupied) suit building. + oldest = None + oldestAge = 0 + now = time.time() + for b in suitBuildings: + building = self.buildingMgr.getBuilding(b) + if hasattr(building, "elevator"): + if building.elevator.fsm.getCurrentState().getName() == 'waitEmpty': + age = now - building.becameSuitTime + if age > oldestAge: + oldest = building + oldestAge = age + + if oldestAge > timeout: + # It's time to reconvert a building. + self.notify.info("Street %d has %d buildings; reclaiming %0.2f-hour-old building." % (self.zoneId, len(suitBuildings), oldestAge / 3600.0)) + oldest.b_setVictorList([0, 0, 0, 0]) + # Update the trophy manager to let it know these rescuers no longer + # get credit for this building + oldest.updateSavedBy(None) + oldest.toonTakeOver() + + self.__waitForNextUpkeep() + return Task.done + + def adjustSuitPopulation(self, task): + """ + randomly adjust the actual suit population over time + """ + # if our base number of suits is zero, dont do any adjustments since + # in this case there is most likely a reason we want zero suits in + # the first place + hoodInfo = self.SuitHoodInfo[ self.hoodInfoIdx ] + if hoodInfo[self.SUIT_HOOD_INFO_MAX] == 0: + self.__waitForNextAdjust() + return Task.done + + min = hoodInfo[ self.SUIT_HOOD_INFO_MIN ] + max = hoodInfo[ self.SUIT_HOOD_INFO_MAX ] + + adjustment = random.choice((-2, -1, -1, 0, 0, 0, 1, 1, 2)) + + # update the count adjustment to the base number of suits wanted + # in this hood + # + self.suitCountAdjust += adjustment + # if amount is past the min or max, stop there. + desiredNum = self.calcDesiredNumFlyInSuits() + + if desiredNum < min: + self.suitCountAdjust = min - self.baseNumSuits + elif desiredNum > max: + self.suitCountAdjust = max - self.baseNumSuits + + self.__waitForNextAdjust() + return Task.done + + def suitTakeOver(self, blockNumber, suitTrack, difficulty, buildingHeight): + if self.pendingBuildingTracks.count(suitTrack) > 0: + self.pendingBuildingTracks.remove(suitTrack) + if self.pendingBuildingHeights.count(buildingHeight) > 0: + self.pendingBuildingHeights.remove(buildingHeight) + building = self.buildingMgr.getBuilding(blockNumber) + building.suitTakeOver(suitTrack, difficulty, buildingHeight) + + def cogdoTakeOver(self, blockNumber, difficulty, buildingHeight): + if self.pendingBuildingHeights.count(buildingHeight) > 0: + self.pendingBuildingHeights.remove(buildingHeight) + building = self.buildingMgr.getBuilding(blockNumber) + building.cogdoTakeOver(difficulty, buildingHeight) + + def recycleBuilding(self): + # Ok, now that a building has been reclaimed by a toon, make + # sure a new building will pop up somewhere else. + + # What's the minimum number of suit buildings on this street? + bmin = self.SuitHoodInfo[ self.hoodInfoIdx ][ self.SUIT_HOOD_INFO_BMIN ] + # How many suit buildings do we actually have on this street? + current = len(self.buildingMgr.getSuitBlocks()) + + if self.targetNumSuitBuildings > bmin and \ + current <= self.targetNumSuitBuildings: + # If we have more than the minimum here, and we haven't + # passed our target number anyway, we can allow the suit + # building to show up in a different zone. + self.targetNumSuitBuildings -= 1 + self.assignSuitBuildings(1) + + # If we already have only the minimum number of buildings on + # this street, we'll just keep the building here. + + def assignInitialSuitBuildings(self): + """ + This is called at startup after all the + DistributedSuitPlannerAI objects have been created. It + decides how many suit buildings there should be in the world + and assigns them to random zones, just to get the leaf blower + system started. + """ + # First, count up the total number of buildings in the world, + # and also the total number of suit buildings we've already + # got assigned (e.g. from minimums per zone). + + totalBuildings = 0 + targetSuitBuildings = 0 + actualSuitBuildings = 0 + for sp in self.air.suitPlanners.values(): + totalBuildings += len(sp.frontdoorPointList) + targetSuitBuildings += sp.targetNumSuitBuildings + if sp.buildingMgr: + actualSuitBuildings += len(sp.buildingMgr.getSuitBlocks()) + wantedSuitBuildings = \ + int(totalBuildings * self.TOTAL_SUIT_BUILDING_PCT / 100) + + self.notify.debug("Want %d out of %d total suit buildings; we currently have %d assigned, %d actual." % (wantedSuitBuildings, totalBuildings, targetSuitBuildings, actualSuitBuildings)) + + if actualSuitBuildings > 0: + # If we already have *some* suit buildings in the world, + # make sure they're all accounted for before we start + # handing out more. + numReassigned = 0 + + for sp in self.air.suitPlanners.values(): + if sp.buildingMgr: + numBuildings = len(sp.buildingMgr.getSuitBlocks()) + else: + numBuildings = 0 + if numBuildings > sp.targetNumSuitBuildings: + more = numBuildings - sp.targetNumSuitBuildings + sp.targetNumSuitBuildings += more + targetSuitBuildings += more + numReassigned += more + + if numReassigned > 0: + self.notify.debug("Assigned %d buildings where suit buildings already existed." % (numReassigned)) + + if wantedSuitBuildings > targetSuitBuildings: + # Ask for more buildings. + additionalBuildings = wantedSuitBuildings - targetSuitBuildings + self.assignSuitBuildings(additionalBuildings) + + elif wantedSuitBuildings < targetSuitBuildings: + # Hmm, we have to remove some targeted buildings somewhere. + extraBuildings = targetSuitBuildings - wantedSuitBuildings + self.unassignSuitBuildings(extraBuildings) + + def assignSuitBuildings(self, numToAssign): + """ + After a suit building has been reclaimed by a toon (or at + startup), locates a new street to assign each new suit building + to. This implements the so-called "leaf blower" model of suit + building management, where reclaiming a building on one street + causes a building to be taken over on a new street--all you + can do is push buildings from one place to another; the total + number of buildings in the world stays constant. + """ + # Look for a suitable zone. First, get a copy of the + # SuitHoodInfo array, so we can remove elements from it as we + # discover they're unsuitable. + hoodInfo = self.SuitHoodInfo[:] + totalWeight = self.TOTAL_BWEIGHT + totalWeightPerTrack = self.TOTAL_BWEIGHT_PER_TRACK[:] + totalWeightPerHeight = self.TOTAL_BWEIGHT_PER_HEIGHT[:] + + # Count up the number of each track of building already in the + # world, so we can try to balance the world by preferring the + # rarer tracks. + numPerTrack = {'c': 0, 'l': 0, 'm': 0, 's':0} + for sp in self.air.suitPlanners.values(): + sp.countNumBuildingsPerTrack(numPerTrack) + numPerTrack['c'] += sp.pendingBuildingTracks.count('c') + numPerTrack['l'] += sp.pendingBuildingTracks.count('l') + numPerTrack['m'] += sp.pendingBuildingTracks.count('m') + numPerTrack['s'] += sp.pendingBuildingTracks.count('s') + + # Also count up the number of each height of building. + numPerHeight = {0:0, 1: 0 , 2: 0, 3: 0, 4: 0,} + for sp in self.air.suitPlanners.values(): + sp.countNumBuildingsPerHeight(numPerHeight) + numPerHeight[0] += sp.pendingBuildingHeights.count(0) + numPerHeight[1] += sp.pendingBuildingHeights.count(1) + numPerHeight[2] += sp.pendingBuildingHeights.count(2) + numPerHeight[3] += sp.pendingBuildingHeights.count(3) + numPerHeight[4] += sp.pendingBuildingHeights.count(4) + + # For each building: + while numToAssign > 0: + + # Choose the track with the smallest representation for + # this building. + smallestCount = None + smallestTracks = [] + for trackIndex in range(4): + if totalWeightPerTrack[trackIndex]: + track = SuitDNA.suitDepts[trackIndex] + count = numPerTrack[track] + if smallestCount == None or count < smallestCount: + smallestTracks = [track] + smallestCount = count + elif count == smallestCount: + smallestTracks.append(track) + + if not smallestTracks: + self.notify.info("No more room for buildings, with %s still to assign." % (numToAssign)) + return + + # Now smallestTracks is the list of all tracks with the + # fewest number of buildings. (There might be more than + # one with the same number.) + buildingTrack = random.choice(smallestTracks) + buildingTrackIndex = SuitDNA.suitDepts.index(buildingTrack) + + # Do that again, choosing a suitable height. + smallestCount = None + smallestHeights = [] + for height in range(5): + if totalWeightPerHeight[height]: + count = float(numPerHeight[height]) / float(self.BUILDING_HEIGHT_DISTRIBUTION[height]) + if smallestCount == None or count < smallestCount: + smallestHeights = [height] + smallestCount = count + elif count == smallestCount: + smallestHeights.append(height) + + if not smallestHeights: + self.notify.info("No more room for buildings, with %s still to assign." % (numToAssign)) + return + + # Remember, buildingHeight is numFloors - 1. + buildingHeight = random.choice(smallestHeights) + + self.notify.info("Existing buildings are (%s, %s), choosing from (%s, %s), chose %s, %s." % + (self.formatNumSuitsPerTrack(numPerTrack), + self.formatNumSuitsPerTrack(numPerHeight), + smallestTracks, smallestHeights, + buildingTrack, buildingHeight)) + + # Look for a suitable street to have this building. + repeat = 1 + while repeat and buildingTrack != None and buildingHeight != None: + if len(hoodInfo) == 0: + self.notify.warning("No more streets can have suit buildings, with %d buildings unassigned!" % (numToAssign)) + return + + repeat = 0 + + currHoodInfo = self.chooseStreetWithPreference(hoodInfo, buildingTrackIndex, buildingHeight) + + # Get the DistributedSuitPlannerAI associated with this zone. + zoneId = currHoodInfo[ self.SUIT_HOOD_INFO_ZONE ] + + if self.air.suitPlanners.has_key(zoneId): + sp = self.air.suitPlanners[zoneId] + + # How many suit buildings does this zone already have? + numTarget = sp.targetNumSuitBuildings + numTotalBuildings = len(sp.frontdoorPointList) + else: + # There's no SuitPlanner for this zone. We must + # be running with want-suits-everywhere turned + # off. + numTarget = 0 + numTotalBuildings = 0 + + if numTarget >= currHoodInfo[ self.SUIT_HOOD_INFO_BMAX ] or \ + numTarget >= numTotalBuildings: + # This zone has enough buildings. + self.notify.info("Zone %d has enough buildings." % (zoneId)) + hoodInfo.remove(currHoodInfo) + weight = currHoodInfo[self.SUIT_HOOD_INFO_BWEIGHT] + tracks = currHoodInfo[self.SUIT_HOOD_INFO_TRACK] + heights = currHoodInfo[self.SUIT_HOOD_INFO_HEIGHTS] + totalWeight -= weight + + totalWeightPerTrack[0] -= weight * tracks[0] + totalWeightPerTrack[1] -= weight * tracks[1] + totalWeightPerTrack[2] -= weight * tracks[2] + totalWeightPerTrack[3] -= weight * tracks[3] + + totalWeightPerHeight[0] -= weight * heights[0] + totalWeightPerHeight[1] -= weight * heights[1] + totalWeightPerHeight[2] -= weight * heights[2] + totalWeightPerHeight[3] -= weight * heights[3] + totalWeightPerHeight[4] -= weight * heights[4] + + if totalWeightPerTrack[buildingTrackIndex] <= 0: + # Oops, no more of this building track can be + # allocated. + assert(totalWeightPerTrack[buildingTrackIndex] == 0) + buildingTrack = None + + if totalWeightPerHeight[buildingHeight] <= 0: + # Oops, no more of this building height can be + # allocated. + assert(totalWeightPerHeight[buildingHeight] == 0) + buildingHeight = None + + repeat = 1 + + # Ok, now we've got a randomly-chosen zone that wants a + # building. Hand it over. + if buildingTrack != None and buildingHeight != None: + sp.targetNumSuitBuildings += 1 + sp.pendingBuildingTracks.append(buildingTrack) + sp.pendingBuildingHeights.append(buildingHeight) + self.notify.info("Assigning building to zone %d, pending tracks = %s, pending heights = %s" % (zoneId, sp.pendingBuildingTracks, sp.pendingBuildingHeights)) + numPerTrack[buildingTrack] += 1 + numPerHeight[buildingHeight] += 1 + numToAssign -= 1 + + def unassignSuitBuildings(self, numToAssign): + """ + The opposite of assignSuitBuildings(), this removes the + assignment for the indicated number of buildings. The + buildings will remain suit buildings, but when they are + eventually reclaimed by toons, they will not be replaced by + more suit buildings. + + This is just called at startup in the case where we have more + buildings in the world than we actually want to keep. + """ + # Look for a suitable zone. First, get a copy of the + # SuitHoodInfo array, so we can remove elements from it as we + # discover they're unsuitable. + hoodInfo = self.SuitHoodInfo[:] + totalWeight = self.TOTAL_BWEIGHT + + # For each building: + while numToAssign > 0: + # Look for a suitable street to pull a building from. + repeat = 1 + while repeat: + if len(hoodInfo) == 0: + self.notify.warning("No more streets can remove suit buildings, with %d buildings too many!" % (numToAssign)) + return + + repeat = 0 + currHoodInfo = self.chooseStreetNoPreference(hoodInfo, totalWeight) + + # Get the DistributedSuitPlannerAI associated with this zone. + zoneId = currHoodInfo[ self.SUIT_HOOD_INFO_ZONE ] + + if self.air.suitPlanners.has_key(zoneId): + sp = self.air.suitPlanners[zoneId] + + # How many suit buildings does this zone already have? + numTarget = sp.targetNumSuitBuildings + numTotalBuildings = len(sp.frontdoorPointList) + else: + # There's no SuitPlanner for this zone. We must + # be running with want-suits-everywhere turned + # off. + numTarget = 0 + numTotalBuildings = 0 + + if numTarget <= currHoodInfo[ self.SUIT_HOOD_INFO_BMIN ]: + # This zone can't remove any more buildings. + self.notify.info("Zone %d can't remove any more buildings." % (zoneId)) + hoodInfo.remove(currHoodInfo) + totalWeight -= currHoodInfo[ self.SUIT_HOOD_INFO_BWEIGHT ] + repeat = 1 + + # Ok, now we've got a randomly-chosen zone that can remove a + # building. + self.notify.info("Unassigning building from zone %d." % (zoneId)) + sp.targetNumSuitBuildings -= 1 + numToAssign -= 1 + + def chooseStreetNoPreference(self, hoodInfo, totalWeight): + """ Chooses a random street (neighborhood) from the supplied + SuitHoodInfo list, without preference for any particular track + or level. The random decision is weighted based on the + likelihood of a building appearing in the street at all. """ + + assert totalWeight > 0 + c = random.random() * totalWeight + + # Which element does this correspond to? + t = 0 + for currHoodInfo in hoodInfo: + weight = currHoodInfo[self.SUIT_HOOD_INFO_BWEIGHT] + t += weight + if c < t: + return currHoodInfo + + # This shouldn't be possible! + self.notify.warning("Weighted random choice failed! Total is %s, chose %s" % (t, c)) + assert false + return random.choice(hoodInfo) + + def chooseStreetWithPreference(self, hoodInfo, buildingTrackIndex, + buildingHeight): + """ + As above, but the random decision is weighted based on the + requested track and building height, so that streets that are + more likely to have buildings of the indicated track and + height are more likely to be selected. + """ + # First, we need to figure the total distribution. + + dist = [] + for currHoodInfo in hoodInfo: + weight = currHoodInfo[self.SUIT_HOOD_INFO_BWEIGHT] + thisValue = weight * currHoodInfo[self.SUIT_HOOD_INFO_TRACK][buildingTrackIndex] * currHoodInfo[self.SUIT_HOOD_INFO_HEIGHTS][buildingHeight] + dist.append(thisValue) + + totalWeight = sum(dist) + + # Pick a random number in the range [0, totalWeight] + assert totalWeight > 0 + c = random.random() * totalWeight + + t = 0 + for i in range(len(hoodInfo)): + t += dist[i] + if c < t: + return hoodInfo[i] + + # This shouldn't be possible! + self.notify.warning("Weighted random choice failed! Total is %s, chose %s" % (t, c)) + assert false + return random.choice(hoodInfo) + + def chooseSuitLevel(self, possibleLevels, buildingHeight): + """ Chooses an appropriate suit level, based on the list of + possible suit levels allowed on this street, for a suit that + will produce a building of the requested height. """ + + choices = [] + for level in possibleLevels: + minFloors, maxFloors = SuitBuildingGlobals.SuitBuildingInfo[level - 1][0] + # Remember that buildingHeight is numFloors - 1 + if buildingHeight >= minFloors - 1 and buildingHeight <= maxFloors - 1: + # This level is allowed. + choices.append(level) + + return random.choice(choices) + + def initTasks(self): + """ + this should be called just after creating the + suit planner in order to set up tasks that will + update the suit planner occasionally + """ + # create a looping task sequence that will occasionally update the + # suit population in the local neighborhood + self.__waitForNextUpkeep() + + # create a looping task sequence that will occasionally update the + # adjustment to the number of suits desired in this hood, this gradually + # changes over time + self.__waitForNextAdjust() + + def resyncSuits(self): + """ + This calls resync() on every suit managed by the planner. + See the comments in DistributedSuitAI.resync(). + """ + for suit in self.suitList: + suit.resync() + + def flySuits(self): + """ + This asks all the suits to fly away abruptly. No good reason + to do this except for debugging. "~cogs fly" does this. + """ + for suit in self.suitList: + if suit.pathState == 1: + suit.flyAwayNow() + + def requestBattle(self, zoneId, suit, toonId): + self.notify.debug('requestBattle() - zone: %d suit: %d toon: %d' % \ + (zoneId, suit.doId, toonId)) + canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId) + if not self.battlePosDict.has_key(canonicalZoneId): + # If the zone doesn't have a battle cell, brush off the toon. + return 0 + + toon = self.air.doId2do.get(toonId) + + # There is a problem of being able to join two battles at once, + # so check if we are already in a battle first. + if toon.getBattleId() > 0: + self.notify.warning("We tried to request a battle when the toon was already in battle") + return 0 + + # Then set our battleID right up here, to lock out any further requests from getting triggered. + if toon: + if hasattr(toon, "doId"): + print ("Setting toonID ", toonId) + toon.b_setBattleId(toonId) + + pos = self.battlePosDict[canonicalZoneId] + interactivePropTrackBonus = -1 + if simbase.config.GetBool("props-buff-battles", True) and \ + self.cellToGagBonusDict.has_key(canonicalZoneId) : + tentativeBonusTrack = self.cellToGagBonusDict[canonicalZoneId] + # next double check if the holiday for it to buff has started + trackToHolidayDict = { ToontownBattleGlobals.SQUIRT_TRACK: ToontownGlobals.HYDRANTS_BUFF_BATTLES, + ToontownBattleGlobals.THROW_TRACK: ToontownGlobals.MAILBOXES_BUFF_BATTLES, + ToontownBattleGlobals.HEAL_TRACK: ToontownGlobals.TRASHCANS_BUFF_BATTLES, + } + if tentativeBonusTrack in trackToHolidayDict: + holidayId = trackToHolidayDict[tentativeBonusTrack] + if simbase.air.holidayManager.isHolidayRunning(holidayId ) and \ + simbase.air.holidayManager.getCurPhase(holidayId) >= 1: + interactivePropTrackBonus = tentativeBonusTrack + + + self.battleMgr.newBattle( + zoneId, zoneId, pos, suit, toonId, + self.__battleFinished, + self.SuitHoodInfo[ self.hoodInfoIdx ][ self.SUIT_HOOD_INFO_SMAX ], + interactivePropTrackBonus) + + # make sure to pull in any suits currently in this zone into + # the battle, but only if they are in 'Bellicose' and are + # ready to enter a battle. We used to make the non-bellicose + # suits fly away, but that seems like a mistake, since these + # suits will be entering a door or already flying away or + # something else equally harmless. + + for currOther in self.zoneInfo[ zoneId ]: + self.notify.debug("Found suit %d in this new battle zone %d" % \ + ( currOther.getDoId(), zoneId )) + if currOther != suit: + if currOther.pathState == 1 and \ + currOther.legType == SuitLeg.TWalk: + self.checkForBattle( zoneId, currOther ) + + return 1 + + def __battleFinished( self, zoneId ): + """ + zoneId, the zone in which the battle exists + + called when a battle in this neighborhood finishes + """ + # remove any references to this battle from our battle list + # + self.notify.debug( "DistSuitPlannerAI: battle in zone " + + str( zoneId ) + " finished" ) + currBattleIdx = 0 + while currBattleIdx < len( self.battleList): + currBattle = self.battleList[currBattleIdx] + if currBattle[0] == zoneId: + self.notify.debug("DistSuitPlannerAI: battle removed") + self.battleList.remove( currBattle ) + else: + currBattleIdx = currBattleIdx + 1 + return None + + def __suitCanJoinBattle( self, zoneId ): + """ + Function: look at a battle in a specific zone and calculate + if a suit is able to join the battle based on the + various join-chance values specified for this suit + planner + Parameters: zoneId, the zone in which a battle exists + Returns: 1 if the suit can join, 0 otherwise + """ + battle = self.battleMgr.getBattle( zoneId ) + if len( battle.suits ) >= 4: + return 0 + if battle: + # the chance of a suit joining a battle depends on the suit to + # toon ratio of the battle, once this chance is obtained, the + # suit randomly decides if it should join, first check to see + # if we have a config to tell us that suits always join battles + # with an empty slot + # + if simbase.config.GetBool('suits-always-join', 0): + return 1 + jChanceList = self.SuitHoodInfo[ self.hoodInfoIdx ]\ + [ self.SUIT_HOOD_INFO_JCHANCE ] + ratioIdx = len( battle.toons ) - battle.numSuitsEver + 2 + if ratioIdx >= 0: + if ratioIdx < len( jChanceList ): + if random.randint( 0, 99 ) < jChanceList[ ratioIdx ]: + return 1 + else: + self.notify.warning( "__suitCanJoinBattle idx out of range!" ) + return 1 + return 0 + + def checkForBattle(self, zoneId, suit): + # See if zone has a battle or not + if (self.battleMgr.cellHasBattle(zoneId)): + # If zone has a battle, see if there are any spots in it + # but first, randomly decide if this suit should even try + # to join the battle based on the hood's join battle + # randomness + if self.__suitCanJoinBattle( zoneId ) and \ + self.battleMgr.requestBattleAddSuit( zoneId, suit ): + # The suit gets added to the battle and the battle + # takes control + pass + else: + # Make the suit fly away + suit.flyAwayNow() + return 1 + else: + # There is no battle, so continue + return 0 + + def postBattleResumeCheck( self, suit ): + """ + Function: check to see if a specific suit should, after it + gets out of a battle, resume its previous path or + if that path is already occupied and the suit + should fly away + Changes: 1 if suit should resume path, 0 to fly away + """ + self.notify.debug("DistSuitPlannerAI:postBattleResumeCheck: suit " + + str( suit.getDoId() ) + " is leaving battle") + battleIndex = 0 + for currBattle in self.battleList: + if suit.zoneId == currBattle[0]: + self.notify.debug(" battle found" + str( suit.zoneId ) ) + # now that we found the zone in our list of battles, check + # the first path to see if there is any intersection between + # the path and this suit's path, if so, then this suit will + # probably end up conflicting with a previously resumed suit. + # So lets tell this suit to fly away + # + for currPath in currBattle[ 1 ]: + for currPathPtSuit in range( suit.currWpt, + suit.myPath.getNumPoints() ): + ptIdx = suit.myPath.getPointIndex( currPathPtSuit ) + if self.notify.getDebug(): + self.notify.debug(" comparing" + str( ptIdx ) + + "with" + str( currPath ) ) + if currPath == ptIdx: + if self.notify.getDebug(): + self.notify.debug(" match found, telling"+ \ + "suit to fly") + return 0 + else: + battleIndex = battleIndex + 1 + + + # battle was not found, so add one to the list and generate a + # list of indexes which represent each of the next several + # path points in this suit's path, so any future suit that + # exits a battle in this zone will compare its path to this + # path to check for collisions when they disperse and leave + # the battle + # + pointList = [] + for currPathPtSuit in range( suit.currWpt, + suit.myPath.getNumPoints() ): + ptIdx = suit.myPath.getPointIndex( currPathPtSuit ) + if self.notify.getDebug(): + self.notify.debug(" appending point with index of" + + str( ptIdx ) ) + pointList.append( ptIdx ) + + # add the zone id and the list of indexes + # + self.battleList.append( [ suit.zoneId, pointList ] ) + + return 1 + + def zoneChange( self, suit, oldZone, newZone=None ): + """ + Function: notify the suit planner when a suit changes zones + Parameters: suit, the suit that is changing zones + oldZone, where the suit was previously + newZone, where suit is now, None if suit is bye-bye + """ + # remove any old reference of the suit from the zones list + # + if self.zoneInfo.has_key( oldZone ) and \ + suit in self.zoneInfo[ oldZone ]: + self.zoneInfo[ oldZone ].remove( suit ) + + # add the suit to the appropriate zone if one was given + # + if newZone != None: + if not self.zoneInfo.has_key( newZone ): + self.zoneInfo[ newZone ] = [] + self.zoneInfo[ newZone ].append( suit ) + + def d_setZoneId( self, zoneId): + self.sendUpdate( 'setZoneId', [ self.getZoneId() ] ) + def getZoneId( self ): + return self.zoneId + + def suitListQuery( self ): + # just send back a list of suit type indices + suitIndexList = [] + for suit in self.suitList: + suitIndexList.append(SuitDNA.suitHeadTypes.index(suit.dna.name)) + self.sendUpdateToAvatarId( self.air.getAvatarIdFromSender(), 'suitListResponse', [ suitIndexList ] ) + + def buildingListQuery( self ): + # send back a list of suit buildings in the format: + # [numCorp, numLegal, numMoney, numSales] + buildingDict = {} + self.countNumBuildingsPerTrack(buildingDict) + buildingList = [0, 0, 0, 0] + for dept in SuitDNA.suitDepts: + if buildingDict.has_key(dept): + buildingList[SuitDNA.suitDepts.index(dept)] = buildingDict[dept] + self.sendUpdateToAvatarId( self.air.getAvatarIdFromSender(), 'buildingListResponse', [ buildingList ] ) + + def pickLevelTypeAndTrack(self, level = None, type = None, track = None): + """ + Chooses a suitable suit description in terms of its level and + type numbers, and track letter. Normally, all three + parameters are chosen at random, but for special purposes + (e.g. magic words), one or more may be passed in as non-None. + """ + # first randomly choose a level from those available for this hood + if level == None: + level = random.choice( + self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_LVL] ) + + # now randomly choose a type of suit based on the level, any given + # type of suit can only be one of 5 levels + if type == None: + typeChoices = range(max(level - 4, 1), + min(level, self.MAX_SUIT_TYPES) + 1) + type = random.choice(typeChoices) + else: + # if our type is already specified, we might need to + # constrain the level to fit. + level = min(max(level, type), type + 4) + + # now randomly choose a suit 'department', or track, or whatever + # we are calling it. + if track == None: + track = SuitDNA.suitDepts[SuitBattleGlobals.pickFromFreqList( + self.SuitHoodInfo[self.hoodInfoIdx][self.SUIT_HOOD_INFO_TRACK])] + self.notify.debug("pickLevelTypeAndTrack: %d %d %s" % (level, type, track)) + return (level, type, track) + + +# history +# +# 22Jan01 jlbutler created. +# 23Jan01 jlbutler added code for removing suits and a function +# which handles assigning tasks periodically +# 23Jan01 jlbutler created 'generateTask' and 'think' functions +# 06Feb01 jlbutler added 'getBattleCellLocation' so others may ask +# the suit planner for the location a specific +# battle sphere within a specific zone +# 12Feb01 jlbutler derived SuitPlannerAI from SuitPlannerBase, where +# common code that the client might need has been +# placed +# 19Feb01 jlbutler added postBattleResumeCheck which allows the suit +# planner to decide if a suit, after it leaves a battle, +# should resume it's previous path or if it should +# fly away +# 19Apr01 jlbutler added self.zoneInfo to contain a list of zones +# and suits that are currently in those zones, useful +# for finding suits in a specific area (also added +# function zoneChange(...) to help make it easier to +# use the new zone list +# 01May01 jlbutler added the ability for the suit planner to randomly +# pick a suit building for a new suit to exit from, +# and the suit will be created with the same track +# as the building +# diff --git a/toontown/src/suit/DistributedTutorialSuit.py b/toontown/src/suit/DistributedTutorialSuit.py new file mode 100644 index 0000000..b68d012 --- /dev/null +++ b/toontown/src/suit/DistributedTutorialSuit.py @@ -0,0 +1,170 @@ +from pandac.PandaModules import * + +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +from toontown.distributed.DelayDeletable import DelayDeletable +import DistributedSuitBase + +class DistributedTutorialSuit(DistributedSuitBase.DistributedSuitBase, DelayDeletable): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedTutorialSuit') + + def __init__(self, cr): + """__init__(cr)""" + try: + self.DistributedSuit_initialized + except: + self.DistributedSuit_initialized = 1 + DistributedSuitBase.DistributedSuitBase.__init__(self, cr) + + # Set up the DistributedSuit state machine + self.fsm = ClassicFSM.ClassicFSM( + 'DistributedSuit', + [State.State('Off', + self.enterOff, + self.exitOff, + ['Walk', + 'Battle']), + State.State('Walk', + self.enterWalk, + self.exitWalk, + ['WaitForBattle', + 'Battle'] + ), + State.State('Battle', + self.enterBattle, + self.exitBattle, + []), + State.State('WaitForBattle', + self.enterWaitForBattle, + self.exitWaitForBattle, + ['Battle']), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + + self.fsm.enterInitialState() + + return None + + def generate(self): + DistributedSuitBase.DistributedSuitBase.generate(self) + + def announceGenerate(self): + DistributedSuitBase.DistributedSuitBase.announceGenerate(self) + self.setState('Walk') + + def disable(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject + // is removed from active duty and stored in a cache. + // Parameters: + // Changes: + //////////////////////////////////////////////////////////////////// + """ + self.notify.debug("DistributedSuit %d: disabling" % self.getDoId()) + self.setState('Off') + DistributedSuitBase.DistributedSuitBase.disable(self) + return + + def delete(self): + """ + //////////////////////////////////////////////////////////////////// + // Function: This method is called when the DistributedObject is + // permanently removed from the world and deleted from + // the cache. + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + try: + self.DistributedSuit_deleted + except: + self.DistributedSuit_deleted = 1 + self.notify.debug("DistributedSuit %d: deleting" % self.getDoId()) + + del self.fsm + DistributedSuitBase.DistributedSuitBase.delete(self) + return + + def d_requestBattle(self, pos, hpr): + """d_requestBattle(toonId) + """ + # Make sure the local toon can't continue to run around (and + # potentially start battles with other suits!) + self.cr.playGame.getPlace().setState('WaitForBattle') + self.sendUpdate('requestBattle', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2]]) + return None + + def __handleToonCollision(self, collEntry): + """ + ///////////////////////////////////////////////////////////// + // Function: This function is the callback for any + // collision events that the collision sphere + // for this bad guy might receive + // Parameters: collEntry, the collision entry object + // Changes: None + ///////////////////////////////////////////////////////////// + """ + + toonId = base.localAvatar.getDoId() + self.notify.debug('Distributed suit: requesting a Battle with ' + + 'toon: %d' % toonId) + self.d_requestBattle(self.getPos(), self.getHpr()) + + # the suit on this machine only will go into wait for battle while it + # is waiting for word back from the server about our battle request + # + self.setState('WaitForBattle') + + return None + + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### Off state ##### + + # Defined in DistributedSuitBase.py + + ##### Walk state ##### + + def enterWalk(self): + self.enableBattleDetect('walk', self.__handleToonCollision) + # Just stand here waiting for a toon to approach. + self.loop('walk', 0) + # This is the path that the tutorial suit walks along. + # There is a path in the tutorial DNA file that is not used so make sure + # the path gets changed here. + pathPoints = [Vec3(55, 15, -0.5), + Vec3(55, 25, -0.5), + Vec3(25, 25, -0.5), + Vec3(25, 15, -0.5), + Vec3(55, 15, -0.5), + ] + self.tutWalkTrack = self.makePathTrack(self, pathPoints, + 4.5, "tutFlunkyWalk") + self.tutWalkTrack.loop() + + def exitWalk(self): + self.disableBattleDetect() + self.tutWalkTrack.pause() + self.tutWalkTrack = None + return + + ##### Battle state ##### + + # Defined in DistributedSuitBase.py + + ##### WaitForBattle state ##### + + # Defined in DistributedSuitBase.py diff --git a/toontown/src/suit/DistributedTutorialSuitAI.py b/toontown/src/suit/DistributedTutorialSuitAI.py new file mode 100644 index 0000000..ed2b811 --- /dev/null +++ b/toontown/src/suit/DistributedTutorialSuitAI.py @@ -0,0 +1,55 @@ +from otp.ai.AIBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import DistributedSuitBaseAI + + +class DistributedTutorialSuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedTutorialSuitAI') + + def __init__(self, air, suitPlanner): + """__init__(air, suitPlanner)""" + DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air, + suitPlanner) + + def delete(self): + DistributedSuitBaseAI.DistributedSuitBaseAI.delete(self) + self.ignoreAll() + + def requestBattle(self, x, y, z, h, p, r): + """requestBattle(x, y, z, h, p, r) + """ + toonId = self.air.getAvatarIdFromSender() + + if self.notify.getDebug(): + self.notify.debug( str( self.getDoId() ) + \ + str( self.zoneId ) + \ + ': request battle with toon: %d' % toonId ) + + # Store the suit's actual pos and hpr on the client + self.confrontPos = Point3(x, y, z) + self.confrontHpr = Vec3(h, p, r) + + # Request a battle from the suit planner + if (self.sp.requestBattle(self.zoneId, self, toonId)): + self.acceptOnce(self.getDeathEvent(), self._logDeath, [toonId]) + if self.notify.getDebug(): + self.notify.debug( "Suit %d requesting battle in zone %d" % + (self.getDoId(), self.zoneId) ) + else: + # Suit tells toon to get lost + if self.notify.getDebug(): + self.notify.debug('requestBattle from suit %d - denied by battle manager' % (self.getDoId())) + self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName())) + self.d_denyBattle( toonId ) + + def getConfrontPosHpr(self): + """ getConfrontPosHpr() + """ + return (self.confrontPos, self.confrontHpr) + + def _logDeath(self, toonId): + self.air.writeServerEvent('beatFirstCog', toonId, '') diff --git a/toontown/src/suit/Goon.py b/toontown/src/suit/Goon.py new file mode 100644 index 0000000..cefbbc1 --- /dev/null +++ b/toontown/src/suit/Goon.py @@ -0,0 +1,222 @@ +from pandac.PandaModules import * +from direct.actor import Actor +from otp.avatar import Avatar +from toontown.toonbase import ToontownGlobals +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +import GoonGlobals +import SuitDNA +import math + +# list of anims per goon type + +AnimDict = { + # Patroller goon + "pg": (("walk", "-walk"), + ("collapse", "-collapse"), + ("recovery", "-recovery"), + ), + # Securtiy goon + "sg": (("walk", "-walk"), + ("collapse", "-collapse"), + ("recovery", "-recovery"), + ), + } + +ModelDict = { + "pg": ("phase_9/models/char/Cog_Goonie"), + "sg": ("phase_9/models/char/Cog_Goonie"), + } + +class Goon(Avatar.Avatar): + """ Goon class: """ + + def __init__(self, dnaName=None): + try: + self.Goon_initialized + except: + self.Goon_initialized = 1 + Avatar.Avatar.__init__(self) + + # Let's preemptively ignore this event, since Goons don't + # have a nametag anyway (and lighting doesn't even matter + # in Toontown). Ignoring this up front will save trouble + # later. + self.ignore("nametagAmbientLightChanged") + + # Initial properties + self.hFov = 70 + self.attackRadius = 15 + self.strength = 15 + self.velocity = 4 + self.scale = 1.0 + + if dnaName is not None: + self.initGoon(dnaName) + + def initGoon(self, dnaName): + dna = SuitDNA.SuitDNA() + dna.newGoon(dnaName) + self.setDNA(dna) + self.type = dnaName + + self.createHead() + # HACK: rotate the goon to match Panda coordinates + self.find("**/actorGeom").setH(180) + + def initializeBodyCollisions(self, collIdStr): + Avatar.Avatar.initializeBodyCollisions(self, collIdStr) + + if not self.ghostMode: + self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask) + + def delete(self): + try: + self.Goon_deleted + except: + self.Goon_deleted = 1 + filePrefix = ModelDict[self.style.name] + loader.unloadModel(filePrefix + "-zero") + + animList = AnimDict[self.style.name] + for anim in animList: + loader.unloadModel(filePrefix + anim[1]) + + Avatar.Avatar.delete(self) + return None + + def setDNAString(self, dnaString): + self.dna = SuitDNA.SuitDNA() + self.dna.makeFromNetString(dnaString) + self.setDNA(self.dna) + + def setDNA(self, dna): + if self.style: + pass + else: + # store the DNA + self.style = dna + + self.generateGoon() + + # this no longer works in the Avatar init! + # I moved it here for lack of a better place + # make the drop shadow + self.initializeDropShadow() + self.initializeNametag3d() + + def generateGoon(self): + dna = self.style + filePrefix = ModelDict[dna.name] + self.loadModel(filePrefix + "-zero") + + animDict = {} + animList = AnimDict[dna.name] + for anim in animList: + animDict[anim[0]] = filePrefix + anim[1] + + self.loadAnims(animDict) + + def getShadowJoint(self): + return self.getGeomNode() + + def getNametagJoints(self): + """ + Return the CharacterJoint that animates the nametag, in a list. + """ + # Chars don't animate their nametags. + return [] + + def createHead(self): + # head and radar for "hot" area + self.headHeight = 3.0 + + # Insert a head rotation node in between exposed joints for the + # goon's head and hat. Note: the hat is doing some animating seperately + # from the head, that is why we have to have two separate exposed + # nodes for the head and the hat. + head = self.find("**/joint35") + if head.isEmpty(): + head = self.find("**/joint40") + self.hat = self.find("**/joint8") + parentNode = head.getParent() + self.head = parentNode.attachNewNode('headRotate') + head.reparentTo(self.head) + self.hat.reparentTo(self.head) + + # hide the hat of the other type of goon + if self.type == "pg": + self.hat.find("**/security_hat").hide() + elif self.type == "sg": + self.hat.find("**/hard_hat").hide() + else: + # neither - hide both + self.hat.find("**/security_hat").hide() + self.hat.find("**/hard_hat").hide() + + # grab a handle to the eye while we are at it + self.eye = self.find("**/eye") + self.eye.setColorScale(1,1,1,1) + self.eye.setColor(1,1,0,1) + + self.radar = None + + def scaleRadar(self): + # Remove the old radar + if self.radar: + self.radar.removeNode() + + self.radar = self.eye.attachNewNode('radar') + + # Load a new radar + model = loader.loadModel("phase_9/models/cogHQ/alphaCone2") + beam = self.radar.attachNewNode('beam') + transformNode = model.find('**/transform') + transformNode.getChildren().reparentTo(beam) + + self.radar.setPos(0, -.5, .4) + self.radar.setTransparency(1) + self.radar.setDepthWrite(0) + + # scale the width (assumes model width is 21) + self.halfFov = self.hFov/2.0 + fovRad = self.halfFov * math.pi / 180.0 + self.cosHalfFov = math.cos(fovRad) + kw = math.tan(fovRad) * self.attackRadius / 10.5 + + # scale the length (assumes model length is 25, and headHeight=3) + kl = math.sqrt(self.attackRadius * self.attackRadius + 9.0) / 25.0 + + # Scale the beam to the right radius and fov + beam.setScale(kw / self.scale, kl / self.scale, kw / self.scale) + beam.setHpr(0, self.halfFov, 0) + + # and make sure it reaches the floor. + p = self.radar.getRelativePoint(beam, Point3(0, -6, -1.8)) + self.radar.setSz(-3.5 / p[2]) + + # Bake in the transforms. + self.radar.flattenMedium() + + # But we keep the color separate so the eye color won't override it. + self.radar.setColor(1,1,1,.2) + + def colorHat(self): + if self.type == "pg": + colorList = GoonGlobals.PG_COLORS + elif self.type == "sg": + colorList = GoonGlobals.SG_COLORS + else: + return + + # make the hat maroonish if these guys are powerful + if self.strength >= 20: + # red + self.hat.setColorScale(colorList[0]) + elif self.strength >= 15: + # orange + self.hat.setColorScale(colorList[1]) + else: + self.hat.clearColorScale() + + diff --git a/toontown/src/suit/GoonDeath.py b/toontown/src/suit/GoonDeath.py new file mode 100644 index 0000000..46e669f --- /dev/null +++ b/toontown/src/suit/GoonDeath.py @@ -0,0 +1,33 @@ +from direct.interval.IntervalGlobal import * +from pandac.PandaModules import * +from direct.particles import ParticleEffect +from toontown.battle import BattleParticles + +def createExplosionTrack(parent, deathNode, scale): + explosion = loader.loadModel("phase_3.5/models/props/explosion.bam") + explosion.getChild(0).setScale(scale) + explosion.reparentTo(deathNode) + explosion.setBillboardPointEye() + explosion.setPos(0, 0, 2) + return Sequence(Func(deathNode.reparentTo, parent), + Wait(0.6), + Func(deathNode.detachNode) + ) + +def createGoonExplosion(parent, explosionPoint, scale): + BattleParticles.loadParticles() + + deathNode = NodePath('goonDeath') + deathNode.setPos(explosionPoint) + + explosion = createExplosionTrack(parent, deathNode, scale) + smallGearExplosion = BattleParticles.createParticleEffect('GearExplosion', numParticles=10) + bigGearExplosion = BattleParticles.createParticleEffect('WideGearExplosion',numParticles=30) + deathSound = base.loadSfx("phase_3.5/audio/sfx/ENC_cogfall_apart.mp3") + return Parallel(explosion, + SoundInterval(deathSound), + ParticleInterval(smallGearExplosion, deathNode, + worldRelative=0, duration=4.3, cleanup = True), + ParticleInterval(bigGearExplosion, deathNode, + worldRelative=0, duration=1.0, cleanup = True), + name='gears2MTrack') diff --git a/toontown/src/suit/GoonGlobals.py b/toontown/src/suit/GoonGlobals.py new file mode 100644 index 0000000..f2c9de3 --- /dev/null +++ b/toontown/src/suit/GoonGlobals.py @@ -0,0 +1,24 @@ +from pandac.PandaModules import Vec4 + +# pg hat colors +PG_COLORS = [ Vec4(0.95, 0.0, 0.0, 1.0), + Vec4(0.75, 0.35, 0.1, 1.0), + ] +SG_COLORS = [ Vec4(0.0, 0.0, 0.95, 1.0), + Vec4(0.35, 0.0, 0.75, 1.0), + ] + +GOON_FORWARD = 1 +GOON_REVERSE = -1 + +GOON_MOVIE_WALK = 0 +GOON_MOVIE_STUNNED = 1 +GOON_MOVIE_BATTLE = 2 +GOON_MOVIE_RECOVERY = 3 +GOON_MOVIE_SYNC = 4 + +T_TURN = 3.0 +ANIM_WALK_RATE = 2.8 + +ANIM_TURN_RATE = 20 # degrees per second +DEFAULT_WALK_RATE = 4 # feet per second diff --git a/toontown/src/suit/GoonPathData.py b/toontown/src/suit/GoonPathData.py new file mode 100644 index 0000000..47a944a --- /dev/null +++ b/toontown/src/suit/GoonPathData.py @@ -0,0 +1,346 @@ +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals + +taskZoneId2pathId = { + ToontownGlobals.SellbotFactoryInt: 'sellbotFactory', + ToontownGlobals.CashbotMintIntA: 'cashbotMint', + ToontownGlobals.CashbotMintIntB: 'cashbotMint', + ToontownGlobals.CashbotMintIntC: 'cashbotMint', + ToontownGlobals.LawbotOfficeInt: 'lawOfficeStage', + ToontownGlobals.LawbotStageIntA: 'lawOfficeStage', + ToontownGlobals.LawbotStageIntB: 'lawOfficeStage', + ToontownGlobals.LawbotStageIntC: 'lawOfficeStage', + ToontownGlobals.LawbotStageIntD: 'lawOfficeStage', + } + +Paths = { 'sellbotFactory': { + # generic + 0:[Vec3(10., 0., 0.), + Vec3(10., 10.,0.), + Vec3(-10., 10., 0.), + Vec3(-10., 0., 0.), + ], + # generic + 1:[Vec3(10., 5., 0.), + Vec3(10., 0.,0.), + Vec3(-10., -5., 0.), + ], + # oil room catwalk + 2:[Vec3(-48.31, -0.001, 0), + Vec3(-48.0, -3.709, 0), + Vec3(35.041, -3.27, 0), + Vec3(34.751, -91.376, 0), + Vec3(39.869, -91.248, 0), + Vec3(39.93, -0.0220, 0), + ], + 3:[Vec3(-47.9110107422,-6.86798095703,0.0), + Vec3(27.691986084,-5.68200683594,0.0), + Vec3(34.049987793,3.55303955078,0.0), + Vec3(-39.983001709,3.68499755859,0.0) + ], + # piperoom catwalk + 4:[Vec3(3.5649, 35.397, 0), + Vec3(-5.335, 36.067, 0), + Vec3(-4.605, -69.670, 0), + Vec3(17.815, -70.577, 0), + Vec3(17.4979, -50.997, 0), + Vec3(3.479, -46.775, 0), + ], + # paint room catwalk + 5:[Vec3(-2.993, -21.085, 0), + Vec3(5.209, -20.966, 0), + Vec3(2.1640, 74.742, 0), + Vec3(-50.439, 78.55, 0), + Vec3(-52.042, 58.831, 0), + Vec3(-3.549, 57.295, 0), + ], + ## Signature Room Goons ## + 6:[Vec3(31.627, 2.093, 0.000), # goon to bookcase + Vec3(4, 43, 0), # goon under stomper 0 + ], + 7:[Vec3(34.627, 2.093, 0.000), # goon to bookcase + Vec3(32, 43, 0), # goon under stomper 1 + ], + 8:[Vec3(64., 43., 0.000), # goon under stomper 2 + Vec3(59.5, 1.8, 0.), # goon to bookcase + ], + 9:[Vec3(84., 43., 0.000), # goon to under stomper 3 + Vec3(93., 25., 0.), # goon to gear + Vec3(66., 2., 0.), # to bookcase + ], + 10:[Vec3(85., 43., 0.0), # goon under stomper3 + Vec3(66., 47., 0.0), # goon to bookcase 2 + Vec3(53., 49., 0.0), # goon to doorway + Vec3(71., 22., 0.0), # middle of room + ], + 11:[Vec3(63., 43., 0.000), # goon under stomper2 + Vec3(33., 47., 0.0), # goon to bookcase 1 + ], + ## Control Room Cogs ## + 12:[Vec3(10.5139980316,73.4393844604,0.0), + Vec3(-10.0053958893,72.9239883423,0.0), + Vec3(5.55112314224,90.2847213745,0.0)], + 13:[Vec3(-2.62728261948,50.5329399109,0.0), + Vec3(-2.21770620346,3.07684659958,0.0)], + 14:[Vec3(-15.9598636627,-15.0503959656,0.0), + Vec3(8.35170555115,-17.5856513977,0.0), + Vec3(8.34984397888,3.9258685112,0.0), + Vec3(-11.5888309479,7.29379606247,0.0)], + ## Boiler Room Cogs ## + 15:[Vec3(-25.7975006104,18.4752807617,0.0), + Vec3(12.6321563721,19.1084594727,0.0), + Vec3(24.8442764282,-4.62582397461,0.0), + ], + 16:[Vec3(6.70755004883,-4.16766357422,0.0), + Vec3(-24.5565567017,-6.02560424805,0.0), + Vec3(-24.4623031616,15.6810913086,0.0), + Vec3(3.93852233887,17.1640014648,0.0), + ], + ## Lobby Cogs ### + 17:[Vec3(0.474000930786,-29.1558818817,0.0), + Vec3(0.460096359253,-16.5713024139,0.0), + Vec3(27.9419631958,-16.0025310516,0.0), + Vec3(28.3607826233,-38.4133338928,0.0), + Vec3(27.2721481323,0.622072696686,0.0), + Vec3(-29.8864364624,2.13151788712,0.0), + Vec3(-28.907831192,-38.875164032,0.0), + Vec3(-26.5185241699,-16.1851940155,0.0), + Vec3(0.0833129882813,-16.7483196259,0.0) + ], + 18:[Vec3(-37.6936645508,4.92616510391,0.0), + Vec3(-6.09254932404,3.79619073868,0.0), + Vec3(-5.81788635254,16.4960193634,0.0), + Vec3(6.79872131348,17.3943042755,0.0), + Vec3(6.47452545166,6.80963373184,0.0), + Vec3(29.4301567078,4.76448297501,0.0) + ], + ## West silo cogs ## + 19:[Vec3(-11.6953277588,-13.8257198334,-0.00400207517669), + Vec3(-26.5939941406,-38.6423110962,-0.00400207517669), + Vec3(-12.9963378906,-54.1221084595,-0.00400207517669), + Vec3(5.45291900635,-50.0818252563,-0.00400207517669), + Vec3(0.500541687012,-27.6731319427,-0.00400207517669), + Vec3(10.6271972656,-16.9095211029,-0.00400207517669), + Vec3(3.95051574707,-11.6894283295,-0.00400207517669) + ], + 20:[Vec3(1.44152832031,-18.6059322357,-0.00400207517669), + Vec3(-0.327140808105,-38.2261734009,-0.00400207517669), + Vec3(19.4757766724,-20.1440181732,-0.00400207517669), + Vec3(-13.617980957,-38.8843765259,-0.00400207517669), + Vec3(9.44762420654,-28.4113521576,-0.00400207517669) + ], + 21:[Vec3(26.8655929565,-13.403295517,-0.00400207517669), + Vec3(32.0896224976,-27.145483017,-0.00400207517669), + Vec3(23.4544830322,-40.3218765259,-0.00400207517669), + Vec3(14.4268798828,-42.4280166626,-0.00400207517669), + Vec3(6.38285827637,-40.3579483032,-0.00400207517669), + Vec3(-2.16972351074,-26.8708248138,-0.00400207517669), + Vec3(-3.16332244873,-17.7787837982,-0.00400207517669), + Vec3(-3.16332244873,-17.7787837982,-0.00400207517669), + Vec3(2.0150680542,-5.54251718521,-0.00400207517669), + Vec3(7.65352630615,-5.11417245865,-0.00400207517669) + ], + ## East silo goons ## + 22:[Vec3(-4.81932353973,4.0960521698,0.0), + Vec3(-37.2935218811,-35.963394165,0.0), + Vec3(-32.3968849182,-49.8670196533,0.0), + Vec3(-52.6336708069,-33.5889434814,0.0), + Vec3(-53.0538520813,-21.5930957794,0.0), + Vec3(-44.2506980896,-27.3775806427,0.0), + Vec3(-1.58745121956,3.91203117371,0.0) + ], + 23:[Vec3(5.80584430695,1.62095463276,0.0), + Vec3(-10.3068342209,-10.9403858185,0.0), + Vec3(-27.9555549622,11.9806346893,0.0), + Vec3(-2.36316990852,-15.246049881,0.0), + Vec3(-12.8535642624,-3.13337898254,0.0) + ], + 24:[Vec3(-12.7202939987,15.884016037,0.0), + Vec3(16.7396354675,-14.0337085724,0.0) + ], + ## Center silo goons + 25:[Vec3(-17.7801113129,-8.74084472656,0.0), + Vec3(-13.1200618744,-9.26202392578,-0.0) + ], + 26:[Vec3(8.624584198,-10.9699707031,0.0), + Vec3(3.29245567322,-2.59405517578,0.0) + ], + 27:[Vec3(12.3031358719,7.98439788818, 0.0), + Vec3(-13.3801307678,7.98439788818, 0.0) + ], + ## PIPE ROOM RIGHT COGS ## + 28:[Vec3(-32.1953697205,-85.9077148438,0.0), + Vec3(-33.518032074,-60.6316223145,0.0), + Vec3(-25.2015018463,-34.200378418,0.0), + Vec3(-0.624415278435,-24.7206726074,0.0) + ], + 29:[Vec3(2.76529407501,-19.7032775879,0.0), + Vec3(12.5924119949,-5.15737915039,0.0), + Vec3(1.32620728016,16.3902587891,0.0), + Vec3(-13.4380140305,18.5373535156,0.0), + Vec3(-9.65627574921,-6.18496704102,0.0), + Vec3(-24.4610538483,-31.9492492676,0.0), + Vec3(-14.1554517746,-43.6148376465,0.0) + ], + 30:[Vec3(-0.175471186638,24.0636901855,0.0), + Vec3(-18.316400528,61.1733856201,0.0), + Vec3(-34.2972984314,58.8515930176,0.0), + Vec3(-18.7033634186,27.5550231934,0.0) + ], + ## PIPE ROOM LEFT COGS ## + 31:[Vec3(25.8017997742,73.792678833,0.0), + Vec3(11.0946779251,51.4213256836,0.0), + Vec3(31.6651115417,43.7239074707,0.0) + ], + 32:[Vec3(4.61576557159,31.6040344238,0.0), + Vec3(-10.1993589401,-7.03125,0.0), + Vec3(-4.72947978973,-29.3165893555,0.0), + Vec3(22.23179245,-28.7750854492,0.0), + Vec3(11.0534486771,0.391784667969,0.0), + Vec3(18.2302970886,28.4787597656,0.0) + ], + 33:[Vec3(8.04011249542,-51.311706543,0.0), + Vec3(25.7791862488,-46.4603881836,0.0), + Vec3(42.3690605164,-57.4282226563,0.0), + Vec3(34.1978569031,-77.1849365234,0.0) + ], + ## West Catwalk Goons ## + 34:[Point3(2.75689697266,-21.3427124023,0.0), + Point3(2.78796386719,37.0751342773,0.000232696533203) + ], + 35:[Point3(7.67395019531,40.565612793,-0.000492095947266), + Point3(78.0889282227,39.3659667969,-0.00810623168945) + ], + + ## Another West Catwalk goon ## + 36:[Point3(98.1296081543,38.8174743652,-0.0102634429932), + Point3(104.098999023,38.9267272949,-0.0109195709229), + Point3(105.774627686,243.011169434,-0.0125999450684), + Point3(100.351745605,242.852386475,-0.0125999450684), + ], + 37:[Point3(152.121673584,222.440261841,-5.0125999450684), + Point3(249.456481934,223.478790283,-5.01259994507), + Point3(247.701660156,233.899627686,-5.01259994507), + Point3(247.01550293,224.162445068,-5.01259994507), + ], + + ## Duct room cogs ## + 38:[Point3(-1.65826416016,25.0982055664,0.025), + Point3(-1.44326782227,9.02752685547,0.025), + Point3(-8.76708984375,11.2831420898,0.025), + Point3(11.4426574707,7.11584472656,0.025), + Point3(2.77236938477,7.02355957031,0.025), + ], + 39:[Point3(-3.69488525391,-1.89245605469,.025), + Point3(-5.73229980469,-9.62658691406,.025), + Point3(4.14199829102,-10.7971191406,.025), + Point3(7.77349853516,1.58245849609,.025), + ], + 40:[Point3(1.67153930664,18.4436645508,.025), + Point3(-7.59463500977,18.5286254883,.025), + ], + + ## More east catwalk goons ## + 41:[Point3(0.726699829102,-48.1898803711,-4.99999809265), + Point3(0.787185668945,-74.4460754395,-4.99999809265), + Point3(39.1846923828,-76.5356445313,-4.99999809265), + Point3(0.787185668945,-74.4460754395,-4.99999809265), + ], + + ## Entrance goon ## + 42:[Vec3(6., -6., 0.), + Vec3(6., 6.,0.), + Vec3(-6., 6., 0.), + Vec3(-6., -6., 0.), + ], + }, + + 'cashbotMint' : { + ## PIPE ROOM RIGHT COGS ## + 28:[Vec3(-32.1953697205,-85.9077148438,0.0), + Vec3(-33.518032074,-60.6316223145,0.0), + Vec3(-25.2015018463,-34.200378418,0.0), + Vec3(-0.624415278435,-24.7206726074,0.0) + ], + 29:[Vec3(2.76529407501,-19.7032775879,0.0), + Vec3(12.5924119949,-5.15737915039,0.0), + Vec3(1.32620728016,16.3902587891,0.0), + Vec3(-13.4380140305,18.5373535156,0.0), + Vec3(-9.65627574921,-6.18496704102,0.0), + Vec3(-24.4610538483,-31.9492492676,0.0), + Vec3(-14.1554517746,-43.6148376465,0.0) + ], + 30:[Vec3(-0.175471186638,24.0636901855,0.0), + Vec3(-18.316400528,61.1733856201,0.0), + Vec3(-34.2972984314,58.8515930176,0.0), + Vec3(-18.7033634186,27.5550231934,0.0) + ], + ## PIPE ROOM LEFT COGS ## + # square + 0: [Vec3(-5,0.,0.), + Vec3(-5,10.,0.), + Vec3(5.,10.,0.), + Vec3(5.,0.,0.), + ], + # equilateral triangle (forward left, forward right, back) + 1: [Vec3(0.,0.,0.), + Vec3(-5.77,10.,0.), + Vec3(5.77,10.,0.), + ], + # bowtie/hourglass (equilateral triangle w/ mirror image) + 2: [Vec3(-5.77,10.,0.), + Vec3(5.77,10.,0.), + Vec3(-5.77,-10.,0.), + Vec3(5.77,-10.,0.), + ], + # pace + 3: [Vec3(-10,0,0), + Vec3(10,0,0), + ], + }, + + 'lawOfficeStage' : { + ## PIPE ROOM RIGHT COGS ## + 28:[Vec3(-32.1953697205,-85.9077148438,0.0), + Vec3(-33.518032074,-60.6316223145,0.0), + Vec3(-25.2015018463,-34.200378418,0.0), + Vec3(-0.624415278435,-24.7206726074,0.0) + ], + 29:[Vec3(2.76529407501,-19.7032775879,0.0), + Vec3(12.5924119949,-5.15737915039,0.0), + Vec3(1.32620728016,16.3902587891,0.0), + Vec3(-13.4380140305,18.5373535156,0.0), + Vec3(-9.65627574921,-6.18496704102,0.0), + Vec3(-24.4610538483,-31.9492492676,0.0), + Vec3(-14.1554517746,-43.6148376465,0.0) + ], + 30:[Vec3(-0.175471186638,24.0636901855,0.0), + Vec3(-18.316400528,61.1733856201,0.0), + Vec3(-34.2972984314,58.8515930176,0.0), + Vec3(-18.7033634186,27.5550231934,0.0) + ], + ## PIPE ROOM LEFT COGS ## + # square + 0: [Vec3(-5,0.,0.), + Vec3(-5,10.,0.), + Vec3(5.,10.,0.), + Vec3(5.,0.,0.), + ], + # equilateral triangle (forward left, forward right, back) + 1: [Vec3(0.,0.,0.), + Vec3(-5.77,10.,0.), + Vec3(5.77,10.,0.), + ], + # bowtie/hourglass (equilateral triangle w/ mirror image) + 2: [Vec3(-5.77,10.,0.), + Vec3(5.77,10.,0.), + Vec3(-5.77,-10.,0.), + Vec3(5.77,-10.,0.), + ], + # pace + 3: [Vec3(-10,0,0), + Vec3(10,0,0), + ], + }, + } + diff --git a/toontown/src/suit/HolidaySuitInvasionManagerAI.py b/toontown/src/suit/HolidaySuitInvasionManagerAI.py new file mode 100644 index 0000000..a96bfbd --- /dev/null +++ b/toontown/src/suit/HolidaySuitInvasionManagerAI.py @@ -0,0 +1,285 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +import SuitInvasionManagerAI +from toontown.toonbase import ToontownGlobals + +class HolidaySuitInvasionManagerAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory('HolidaySuitInvasionManagerAI') + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + def start(self): + # Stop any current invasion that might be happening by chance + if self.air.suitInvasionManager.getInvading(): + self.notify.info("Stopping current invasion to make room for holiday %s" % + (self.holidayId)) + self.air.suitInvasionManager.stopInvasion() + + if not simbase.config.GetBool('want-invasions', 1): + return 1 + + if (self.holidayId == ToontownGlobals.HALLOWEEN): + # Bloodsucker invasion on Halloween + cogType = 'b' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SKELECOG_INVASION): + # any cog will do + import SuitDNA + import random + cogType = random.choice(SuitDNA.suitHeadTypes) + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 1 + elif (self.holidayId == ToontownGlobals.MR_HOLLYWOOD_INVASION): + # Mr. Hollywood of course... + cogType = 'mh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BOSSCOG_INVASION): + # any cog will do + import SuitDNA + import random + cogType = SuitDNA.getRandomSuitByDept('c') + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MARCH_INVASION): + # Backstabbers... + cogType = 'bs' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DECEMBER_INVASION): + # Sellbots... + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_1): + # Sellbot Surprise... cold caller + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_2 or \ + self.holidayId == ToontownGlobals.NAME_DROPPER_INVASION): + # Sellbot Surprise ... Name dropper + cogType = 'nd' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_3): + # Sellbot Surprise ... gladhander + cogType = 'gh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_4 or \ + self.holidayId == ToontownGlobals.MOVER_AND_SHAKER_INVASION): + # Sellbot Surprise ... mover & shaker + cogType = 'ms' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_1): + # Cashbot Conundrum... Short Change + cogType = 'sc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_2 or \ + self.holidayId == ToontownGlobals.PENNY_PINCHER_INVASION): + # Cashbot Conundrum... Penny Pincher + cogType = 'pp' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_3): + # Cashbot Conundrum... Bean Counter + cogType = 'bc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_4 or \ + self.holidayId == ToontownGlobals.NUMBER_CRUNCHER_INVASION): + # Cashbot Conundrum... Number Cruncher + cogType = 'nc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_1): + # Lawbot Gambit... bottom feeder + cogType = 'bf' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_2 or \ + self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION): + # Lawbot Gambit... double talker + cogType = 'dt' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_3 or \ + self.holidayId == ToontownGlobals.AMBULANCE_CHASER_INVASION): + # Lawbot Gambit... ambulance chaser + cogType = 'ac' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_4): + # Lawbot Gambit... back stabber + cogType = 'bs' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_1): + # The Trouble with Bossbots ... flunky + cogType = 'f' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_2): + # The Trouble with Bossbots ... pencil pusher + cogType = 'p' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_3 or \ + self.holidayId == ToontownGlobals.MICROMANAGER_INVASION): + # The Trouble with Bossbots ... micro manager + cogType = 'mm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_4 or \ + self.holidayId == ToontownGlobals.DOWN_SIZER_INVASION ): + # The Trouble with Bossbots ... downsizer + cogType = 'ds' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.COLD_CALLER_INVASION): + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BEAN_COUNTER_INVASION): + cogType = 'bc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION): + # The Trouble with Bossbots ... downsizer + cogType = 'dt' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DOWNSIZER_INVASION): + # The Trouble with Bossbots ... downsizer + cogType = 'ds' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.YES_MAN_INVASION): + # The Trouble with Bossbots ... yes man + cogType = 'ym' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TIGHTWAD_INVASION): + # tightwad + cogType = 'tw' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TELEMARKETER_INVASION): + # telemarketer + cogType = 'tm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.HEADHUNTER_INVASION): + # head hunter + cogType = 'hh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SPINDOCTOR_INVASION): + # spin doctor + cogType = 'sd' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MONEYBAGS_INVASION): + # money bags + cogType = 'mb' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TWOFACES_INVASION): + # two faces + cogType = 'tf' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MINGLER_INVASION): + # mingler + cogType = 'm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LOANSHARK_INVASION): + # loan sharks + cogType = 'ls' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CORPORATE_RAIDER_INVASION): + # corporate raider + cogType = 'cr' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LEGAL_EAGLE_INVASION): + # legal eagle + cogType = 'le' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.ROBBER_BARON_INVASION): + # robber baron + cogType = 'rb' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BIG_WIG_INVASION): + # big wig + cogType = 'bw' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BIG_CHEESE_INVASION): + # big cheese + cogType = 'tbc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + else: + self.notify.warning("Unrecognized holidayId: %s" % (self.holidayId)) + return 0 + + self.air.suitInvasionManager.startInvasion(cogType, numCogs, skeleton) + self.air.suitInvasionManager.waitForNextInvasion() + return 1 + + def stop(self): + # Holiday is over, stop the invasion if it happens to still be running + if self.air.suitInvasionManager.getInvading(): + self.notify.info("Prematurely stopping holiday invasion: %s" % (self.holidayId)) + self.air.suitInvasionManager.stopInvasion() diff --git a/toontown/src/suit/RoguesGallery.py b/toontown/src/suit/RoguesGallery.py new file mode 100644 index 0000000..415a059 --- /dev/null +++ b/toontown/src/suit/RoguesGallery.py @@ -0,0 +1,288 @@ +from pandac.PandaModules import * +from direct.fsm import StateData +import Suit +import SuitDNA +from toontown.toonbase import ToontownGlobals +import random + +class RoguesGallery(StateData.StateData): + """RoguesGallery + + This generates a presentation of all of the suits in the game, + just like the printed chart we have on the wall. + + It has no in-game purpose. It's mainly useful for testing the + suit dna, and for admiring the suits. + + When load() is called, the gallery is created within the node + represented by self.gallery. The enter() and exit() functions, if + used, set the camera up to look at the gallery, but you can do + other things with it if you want. The gallery is scaled to + correspond with the range of the aspect2d screen coordinates. + """ + + def __init__(self, rognamestr = None): + StateData.StateData.__init__(self, "roguesDone") + + self.rognamestr = rognamestr + + # Set up the constants that define the size and shape of the + # gallery. + self.left = -1.333 + self.right = 1.333 + self.bottom = -1.0 + self.top = 1.0 + + self.sideMargins = 0.1 + self.topMargins = 0.1 + self.xSpaceBetweenDifferentSuits = 0.01 + self.xSpaceBetweenSameSuits = 0.0 + self.ySpaceBetweenSuits = 0.05 + + self.labelScale = 1.0 + + def load(self): + if StateData.StateData.load(self): + # Compute the derived constants. + self.width = self.right - self.left - self.sideMargins * 2.0 + self.height = self.top - self.bottom - self.topMargins * 2.0 + + if(self.rognamestr == None): + self.numSuitTypes = SuitDNA.suitsPerDept + self.numSuitDepts = len(SuitDNA.suitDepts) + else: + self.numSuitTypes = 1 + self.numSuitDepts = 1 + self.xSpaceBetweenDifferentSuits = 0.0 + self.xSpaceBetweenSameSuits = 0.0 + self.ySpaceBetweenSuits = 0.0 + + self.ySuitInc = ( + (self.height + self.ySpaceBetweenSuits) / self.numSuitDepts) + self.ySuitMaxAllowed = self.ySuitInc - self.ySpaceBetweenSuits + + self.xRowSpace = self.width - (self.numSuitTypes - 1) * self.xSpaceBetweenDifferentSuits - self.numSuitTypes * self.xSpaceBetweenSameSuits + + self.__makeGallery() + + def unload(self): + if StateData.StateData.unload(self): + self.gallery.removeNode() + del self.suits + del self.actors + + def enter(self): + if StateData.StateData.enter(self): + # Temporarily hide everything in render2d and render. Drastic! + render.hide() + aspect2d.hide() + + self.gallery.reparentTo(render2d) + self.gallery.setMat(base.aspect2d.getMat()) + + # Push it back a little to avoid the DX near clipping + # plane problem. + self.gallery.setPos(0.0, 10.0, 0.0) + + base.setBackgroundColor(0.6, 0.6, 0.6) + + def exit(self): + if StateData.StateData.exit(self): + self.stop() + + # Undo the damage we did with enter(). + render.show() + aspect2d.show() + self.gallery.reparentTo(hidden) + self.gallery.clearMat() + + base.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor) + + self.ignoreAll() + + def animate(self): + """animate(self) + + Animates all the suits in the gallery, just for kicks. + """ + self.load() + + for suit in self.actors: + suit.pose("neutral", random.randint(0, suit.getNumFrames("neutral") - 1)) + suit.loop("neutral", 0) + + def stop(self): + """stop(self) + + Stops all the animation, and restores the suits to a medium frame. + """ + self.load() + + for suit in self.actors: + suit.pose("neutral", 30) + + def autoExit(self): + """autoExit(self) + + Sets up a hook to close the gallery when the user clicks the + left mouse button. + """ + self.acceptOnce('mouse1', self.exit) + + def __makeGallery(self): + """__makeGallery(self) + + Creates the gallery and populates it with suits. + """ + self.gallery = hidden.attachNewNode('gallery') + + # Ensure the gallery will be depth-tested. This allows us to + # parent it to aspect2d (which normally isn't) and still see + # the suits correctly. + self.gallery.setDepthWrite(1) + self.gallery.setDepthTest(1) + + self.suits = [] + self.actors = [] + self.text = TextNode("rogues") + self.text.setFont(ToontownGlobals.getInterfaceFont()) + self.text.setAlign(TextNode.ACenter) + self.text.setTextColor(0.0, 0.0, 0.0, 1.0) + + self.rowHeight = 0.0 + self.minXScale = None + + print "rognamestr='",self.rognamestr,"'\n" + + if((self.rognamestr == None) or (len(self.rognamestr) == 0)): + for dept in SuitDNA.suitDepts: + self.__makeDept(dept) + else: + self.suitRow = [] + self.rowWidth = 0.0 + self.__makeSuit(None,None,self.rognamestr) + self.minXScale = self.xRowSpace / self.rowWidth + self.suits.append((self.rowWidth, self.suitRow)) + del self.suitRow + + self.__rescaleSuits() + + def __makeDept(self, dept): + """__makeDept(self, string dept) + + Makes a row of suits for the indicated department. + """ + self.suitRow = [] + self.rowWidth = 0.0 + for type in range(self.numSuitTypes): + self.__makeSuit(dept, type) + + # How much would we need to scale these suits horizontally to + # use up all of our available space? + xScale = self.xRowSpace / self.rowWidth + + if self.minXScale == None or self.minXScale > xScale: + self.minXScale = xScale + + self.suits.append((self.rowWidth, self.suitRow)) + del self.suitRow + + + def __makeSuit(self, dept, type, name = None): + """__makeSuit(self, string dept, int type) + + Creates a single suit of the indicated department and type. + Parents the new suit to self.gallery and adds it to + self.rowSuits, in both face-on and profile views, and + accumulates its width. + + """ + dna = SuitDNA.SuitDNA() + + if(name!=None): + dna.newSuit(name) + else: + dna.newSuitRandom(type + 1, dept) + + suit = Suit.Suit() + suit.setStyle(dna) + suit.generateSuit() + suit.pose("neutral", 30) + + # Compute the maximum extents of the suit. We'll use to + # scale all the suits to fit their boxes after we're done. + ll = Point3() + ur = Point3() + suit.update() + suit.calcTightBounds(ll, ur) + + suitWidth = ur[0] - ll[0] + suitDepth = ur[1] - ll[1] + suitHeight = ur[2] - ll[2] + + #print "height of %s (%s) is %0.2f" % (dna.name, suit.name, suitHeight) + + self.rowWidth += suitWidth + suitDepth + self.rowHeight = max(self.rowHeight, suitHeight) + + # Now put the suit in the gallery, once as a head-on, and once + # as a profile. + suit.reparentTo(self.gallery) + suit.setHpr(180.0, 0.0, 0.0) + + profile = Suit.Suit() + profile.setStyle(dna) + profile.generateSuit() + profile.pose("neutral", 30) + profile.reparentTo(self.gallery) + profile.setHpr(90.0, 0.0, 0.0) + + self.suitRow.append((type, suitWidth, suit, suitDepth, profile)) + self.actors.append(suit) + self.actors.append(profile) + + def __rescaleSuits(self): + """__rescaleSuits(self) + + After all the suits have been generated, scales them all down + to fix their boxes in the gallery, based on the computed + xSuitMax and ySuitMax. + """ + + # How much will we need to scale to fit each dimension? + yScale = self.ySuitMaxAllowed / self.rowHeight + + # Our overall scale will be the smaller of those. + scale = min(self.minXScale, yScale) + + y = self.top - self.topMargins + self.ySpaceBetweenSuits + for rowWidth, suitRow in self.suits: + rowWidth *= scale + extraSpace = self.xRowSpace - rowWidth + + # Distribute all the extra space for this row between each + # suit. + extraSpacePerSuit = extraSpace / ((self.numSuitTypes * 2) - 1) + + x = self.left + self.sideMargins + y -= self.ySuitInc + for type, width, suit, depth, profile in suitRow: + left = x + + width *= scale + suit.setScale(scale) + suit.setPos(x + width / 2.0, 0.0, y) + x += width + self.xSpaceBetweenSameSuits + extraSpacePerSuit + + depth *= scale + profile.setScale(scale) + profile.setPos(x + depth / 2.0, 0.0, y) + x += depth + right = x + x += self.xSpaceBetweenDifferentSuits + extraSpacePerSuit + + # Finally put a nametag over the pair of them. + self.text.setText(suit.getName()) + name = self.gallery.attachNewNode(self.text.generate()) + name.setPos((right + left) / 2.0, 0.0, y + (suit.height + self.labelScale * 0.5) * scale) + name.setScale(self.labelScale * scale) diff --git a/toontown/src/suit/Sources.pp b/toontown/src/suit/Sources.pp new file mode 100644 index 0000000..ea2854c --- /dev/null +++ b/toontown/src/suit/Sources.pp @@ -0,0 +1,22 @@ +#begin lib_target + #define TARGET suit + #define LOCAL_LIBS dnaLoader toontownbase + #define OTHER_LIBS \ + panda:m pandaexpress:m \ + interrogatedb:c dconfig:c dtoolconfig:m \ + dtoolutil:c dtoolbase:c dtool:m \ + prc:c + + #define COMBINED_SOURCES $[TARGET]_composite1.cxx + + #define SOURCES \ + suitLeg.I suitLeg.h \ + suitLegList.I suitLegList.h + + #define INCLUDED_SOURCES \ + suitLeg.cxx suitLegList.cxx + + #define IGATESCAN all + +#end lib_target + diff --git a/toontown/src/suit/Suit.py b/toontown/src/suit/Suit.py new file mode 100644 index 0000000..216e6b5 --- /dev/null +++ b/toontown/src/suit/Suit.py @@ -0,0 +1,1370 @@ +""" +Suit module: contains Suit class +Example creation: + +from toontown.suit import Suit +s = Suit.Suit() +from toontown.suit import SuitDNA +d = SuitDNA.SuitDNA() +d.newSuit('tbc') +s.setDNA(d) +s.loop('neutral') +s.reparentTo(render) +s.setPos(base.localAvatar, 0,0,0) + +s.delete() +del s +""" + +""" +Suit Types & Tracks + +BossBot (Corporate): 'c' + Flunky: f + PencilPusher: p + YesMan: ym + MicroManager: mm + DownSizer: ds + HeadHunter: hh + CorporateRaider: cr + BigCheese: bc + +LawBot (Legal): 'l' + BottomFeeder: bf + BloodSucker: b + DoubleTalker: dt + AmbulanceChaser: ac + BackStabber: bs + SpinDoctor: sd + LegalEagle: le + BigWig: bw + +CashBot (Money): 'm' + ShortChange: sc + PennyPincher: pp + TightWad: tw + BeanCounter: bc + NumberCruncher: nc + MoneyBags: mb + LoanShark: ls + RobberBaron: rb + +SellBot (Sales): 's' + ColdCaller: cc + Telemarketer: tm + NameDropper: nd + GladHander: gh + Mover&Shaker: ms + TwoFaced: tf + TheMingler: m + Mr.Hollywood: mh +""" + +from direct.actor import Actor +from otp.avatar import Avatar +import SuitDNA +from toontown.toonbase import ToontownGlobals +from pandac.PandaModules import * +from toontown.battle import SuitBattleGlobals +from direct.task.Task import Task +from toontown.battle import BattleProps +from toontown.toonbase import TTLocalizer +import string + +aSize = 6.06 +bSize = 5.29 +cSize = 4.14 + +SuitDialogArray = [] +SkelSuitDialogArray = [] + +# list of anims for all suit parts + +AllSuits = ( + ("walk", "walk"), + ("run", "walk"), + ) + +AllSuitsMinigame = ( + ("victory", "victory"), + ("flail", "flailing"), + ("tug-o-war", "tug-o-war"), + ("slip-backward", "slip-backward"), + ("slip-forward", "slip-forward"), + ) + +AllSuitsTutorialBattle = ( + ("lose", "lose"), + ("pie-small-react", "pie-small"), + ("squirt-small-react", "squirt-small"), + ) + +AllSuitsBattle = ( + ("drop-react", "anvil-drop"), + ("flatten", "drop"), + ("sidestep-left", "sidestep-left"), + ("sidestep-right", "sidestep-right"), + ("squirt-large-react", "squirt-large"), + ("landing", "landing"), + ("reach", "walknreach"), + ("rake-react", "rake"), + ("hypnotized", "hypnotize"), + ("soak", "soak"), + ) + +SuitsCEOBattle = ( + ("sit", "sit"), + ("sit-eat-in", "sit-eat-in"), + ("sit-eat-loop", "sit-eat-loop"), + ("sit-eat-out", "sit-eat-out"), + ("sit-angry", "sit-angry"), + ("sit-hungry-left", "leftsit-hungry"), + ("sit-hungry-right", "rightsit-hungry"), + ("sit-lose", "sit-lose"), + ("tray-walk", "tray-walk"), + ("tray-neutral", "tray-neutral"), + ("sit-lose", "sit-lose"), + ) + +# corp anims per type +# Flunky (C) Corp 1 +f = ( + # pass on carbon copy for now + ("throw-paper", "throw-paper", 3.5), + ("phone", "phone", 3.5), + ("shredder", "shredder", 3.5), + ) +# PencilPusher (B) Corp 2 +p = ( + ("pencil-sharpener", "pencil-sharpener", 5), + ("pen-squirt", "pen-squirt", 5), + ("hold-eraser", "hold-eraser", 5), + ("finger-wag", "finger-wag", 5), + ("hold-pencil", "hold-pencil", 5), + ) +# YesMan (A) Corp 3 +ym = ( + ("throw-paper", "throw-paper", 5), + # pass on ditto + ("golf-club-swing", "golf-club-swing", 5), + ("magic3", "magic3", 5), + ("rubber-stamp", "rubber-stamp", 5), + ("smile", "smile", 5), + ) +# MicroManager (C) Corp 4 +mm = ( + ("speak", "speak", 5), + ("effort", "effort", 5), + ("magic1", "magic1", 5), + ("pen-squirt", "fountain-pen", 5), + ("finger-wag", "finger-wag", 5), + ) + +# Downsizer (B) Corp 5 +ds = ( + ("magic1", "magic1", 5), # just as default for now + ("magic2", "magic2", 5), + ("throw-paper", "throw-paper", 5), + ("magic3", "magic3", 5), + ) + +# Head Hunter (A) Corp 6 +hh = ( + ("pen-squirt", "fountain-pen", 7), + ("glower", "glower", 5), + ("throw-paper", "throw-paper", 5), + ("magic1", "magic1", 5), + ("roll-o-dex", "roll-o-dex", 5), + ) + +# Corporate Raider (C) Corp 7 +cr = ( + ("pickpocket", "pickpocket", 5), # just as default for now + ("throw-paper", "throw-paper", 3.5), + ("glower", "glower", 5), + ) + +# The Big Cheese (A) Corp 8 +tbc = ( + ("cigar-smoke", "cigar-smoke", 8), + ("glower", "glower", 5), + ("song-and-dance", "song-and-dance", 8), + ("golf-club-swing", "golf-club-swing", 5), + ) + +# sales anims per type +# ColdCaller (C) Sales 1 +cc = ( + ("speak", "speak", 5), + ("glower", "glower", 5), + ("phone", "phone", 3.5), + ("finger-wag", "finger-wag", 5), # place holder for speak animation, won't need it + ) +# TeleMarketer (B) Sales 2 +tm = ( + ("speak", "speak", 5), + ("throw-paper", "throw-paper", 5), + ("pickpocket", "pickpocket", 5), + ("roll-o-dex", "roll-o-dex", 5), + ("finger-wag", "finger-wag", 5), # place holder for speak animation, won't need it + ) +# NameDropper (A) Sales 3 +nd = ( + ("pickpocket", "pickpocket", 5), + ("roll-o-dex", "roll-o-dex", 5), + ("magic3", "magic3", 5), + ("smile", "smile", 5), + ) +# GladHander (C) Sales 4 +gh = ( + ("speak", "speak", 5), + ("pen-squirt", "fountain-pen", 5), + # TODO: + # Neither of these are right... + # They should be all talk attacks... + # But until we have the talk animations, + # these will stay... + ("rubber-stamp", "rubber-stamp", 5), + ) + +# Mover & Shaker (B) Sales 5 +ms = ( + ("effort", "effort", 5), + ("throw-paper", "throw-paper", 5), + ("stomp", "stomp", 5), + ("quick-jump", "jump", 6), + ) + +# Two-Face (A) Sales 6 +tf = ( + ("phone", "phone", 5), + ("smile", "smile", 5), + ("throw-object", "throw-object", 5), + ("glower", "glower", 5), + ) + +# The Mingler (A) Sales 7 +m = ( + ("speak", "speak", 5), + ("magic2", "magic2", 5), + ("magic1", "magic1", 5), + ("golf-club-swing", "golf-club-swing", 5), + ) + +# Mr. Hollywood (A) Sales 8 +mh = ( + ("magic1", "magic1", 5), + ("smile", "smile", 5), + ("golf-club-swing", "golf-club-swing", 5), + ("song-and-dance", "song-and-dance", 5), + ) + +# money anims per type +# ShortChange (C) Cash 1 +sc = ( + ("throw-paper", "throw-paper", 3.5), + ("watercooler", "watercooler", 5), + ("pickpocket", "pickpocket", 5), + ) +# PennyPincher (A) Cash 2 +pp = ( + ("throw-paper", "throw-paper", 5), + ("glower", "glower", 5), + ("finger-wag", "fingerwag", 5), + ) + +# TightWad (C) Cash 3 +tw = ( + ("throw-paper", "throw-paper", 3.5), + ("glower", "glower", 5), + ("magic2", "magic2", 5), + ("finger-wag", "finger-wag", 5), + ) +# BeanCounter (B) Cash 4 +bc = ( + ("phone", "phone", 5), + ("hold-pencil", "hold-pencil", 5), + ) + +# Number Cruncher (A) Cash 5 +nc = ( + ("phone", "phone", 5), + ("throw-object", "throw-object", 5), + ) + +# Money Bags (C) Cash 6 +mb = ( + ("magic1", "magic1", 5), + ("throw-paper", "throw-paper", 3.5), + ) + +# Loan Shark (B) Cash 7 +ls = ( + ("throw-paper", "throw-paper", 5), + ("throw-object", "throw-object", 5), + ("hold-pencil", "hold-pencil", 5), + ) + +# Robber Baron (A) Cash 8 +rb = ( + ("glower", "glower", 5), + ("magic1", "magic1", 5), + ("golf-club-swing", "golf-club-swing", 5), + ) + +# legal anims per type +# BottomFeeder (C) Law 1 +bf = ( + ("pickpocket", "pickpocket", 5), + ("rubber-stamp", "rubber-stamp", 5), + ("shredder", "shredder", 3.5), + ("watercooler", "watercooler", 5), + ) +# BloodSucker (B) Law 2 +b = ( + ("effort", "effort", 5), + ("throw-paper", "throw-paper", 5), + ("throw-object", "throw-object", 5), + ("magic1", "magic1", 5), # also used as place holder for liquidate (glower anim) + ) +# DoubleTalker (A) Law 3 +dt = ( + # TODO: + # Neither of these are right... + # They should be all talk attacks... + # But until we have the talk animations, + # these will stay... + ("rubber-stamp", "rubber-stamp", 5), + ("throw-paper", "throw-paper", 5), + ("speak", "speak", 5), + ("finger-wag", "fingerwag", 5), # place holder for speak animation, won't need it + ("throw-paper", "throw-paper", 5), #added for lawbot boss battle + ) + +# AmbulanceChaser (B) Law 4 +ac = ( + ("throw-object", "throw-object", 5), + ("roll-o-dex", "roll-o-dex", 5), + ("stomp", "stomp", 5), + ("phone", "phone", 5), + ("throw-paper", "throw-paper", 5), #added for lawbot boss battle + ) + +# Back Stabber (A) Law 5 +bs = ( + ("magic1", "magic1", 5), + ("throw-paper", "throw-paper", 5), + ("finger-wag", "fingerwag", 5), + ) + +# Spin Doctor (B) Law 6 +sd = ( + ("magic2", "magic2", 5), + ("quick-jump", "jump", 6), + ("stomp", "stomp", 5), + ("magic3", "magic3", 5), + ("hold-pencil", "hold-pencil", 5), + ("throw-paper", "throw-paper", 5), #added for lawbot boss battle + ) + +# Legal Eagle (A) Law 7 +le = ( + ("speak", "speak", 5), + ("throw-object", "throw-object", 5), + ("glower", "glower", 5), + ("throw-paper", "throw-paper", 5), #added for lawbot boss battle + ) + +# Big Wig (A) Law 8 +bw = ( + ("finger-wag", "fingerwag", 5), # just as default for now + ("cigar-smoke", "cigar-smoke", 8), + ("gavel", "gavel", 8), # incomplete? + ("magic1", "magic1", 5), + ("throw-object", "throw-object", 5), + ("throw-paper", "throw-paper", 5), #added for lawbot boss battle + ) + +ModelDict = { + "a": ("/models/char/suitA-", 4), + "b": ("/models/char/suitB-", 4), + "c": ("/models/char/suitC-", 3.5), + } + +TutorialModelDict = { + "a": ("/models/char/suitA-", 4), + "b": ("/models/char/suitB-", 4), + "c": ("/models/char/suitC-", 3.5), + } + +def loadTutorialSuit(): + """ + Preload the tutorial suit (Flunky) + """ + loader.loadModelNode("phase_3.5/models/char/suitC-mod") + loadDialog(1) + +def loadSuits(level): + """loadSuits(int) + Preload all suit anims and models for given suit level. + """ + loadSuitModelsAndAnims(level, flag = 1) + loadDialog(level) + +def unloadSuits(level): + """unloadSuits(int) + Unload all suit anims and models for given suit level. + """ + loadSuitModelsAndAnims(level, flag = 0) + unloadDialog(level) + +def loadSuitModelsAndAnims(level, flag = 0): + """ + Load (flag = 1) or unload (flag = 0) all suit anims and + models for given suit level. + """ + # print "print loading level %d suits..." % level + + for key in ModelDict.keys(): + # load/unload the models + # All the mods are in 3.5 now, except the suita and B headsd which are in 4 + model, phase = ModelDict[key] + headModel, headPhase = ModelDict[key] + if flag: + loader.loadModelNode("phase_3.5" + model + "mod") + loader.loadModelNode("phase_" + str(headPhase) + headModel + "heads") + else: + loader.unloadModel("phase_3.5" + model + "mod") + loader.unloadModel("phase_" + str(headPhase) + headModel + "heads") + +def loadSuitAnims(suit, flag = 1): + """loadSuitAnims(string, int): + Load or unload (flag = 1 or 0) the special anims for the given suit. + Expects strings as in SuitDNA.suitHeadTypes. + """ + # make sure its a valid name + if (suit in SuitDNA.suitHeadTypes): + # map the name to an animList + try: + animList = eval(suit) + except NameError: + # no suit specific anims defined + animList = () + else: + print "Invalid suit name: ", suit + return -1 + + # process the animList + for anim in animList: + phase = "phase_" + str(anim[2]) + filePrefix = ModelDict[bodyType][0] + animName = filePrefix + anim[1] + if flag: + loader.loadModelNode(animName) + else: + loader.unloadModel(animName) + + +def loadDialog(level): + # use the new dialog + global SuitDialogArray + if len(SuitDialogArray) > 0: + return + else: + loadPath = "phase_3.5/audio/dial/" + SuitDialogFiles = [ "COG_VO_grunt", + "COG_VO_murmur", + "COG_VO_statement", + "COG_VO_question" + ] + # load the audio files and store into the dialogue array + for file in SuitDialogFiles: + SuitDialogArray.append(base.loadSfx(loadPath + file + ".mp3")) + SuitDialogArray.append(SuitDialogArray[2]) + SuitDialogArray.append(SuitDialogArray[2]) + +def loadSkelDialog(): + # use the new dialog + global SkelSuitDialogArray + if len(SkelSuitDialogArray) > 0: + return + else: + grunt = loader.loadSfx('phase_5/audio/sfx/Skel_COG_VO_grunt.mp3') + murmur = loader.loadSfx('phase_5/audio/sfx/Skel_COG_VO_murmur.mp3') + statement = loader.loadSfx('phase_5/audio/sfx/Skel_COG_VO_statement.mp3') + question = loader.loadSfx('phase_5/audio/sfx/Skel_COG_VO_question.mp3') + SkelSuitDialogArray = [grunt, murmur, statement, question, statement, statement] + +def unloadDialog(level): + global SuitDialogArray + SuitDialogArray = [] + +def unloadSkelDialog(): + global SkelSuitDialogArray + SkelSuitDialogArray = [] + +def attachSuitHead(node, suitName): + """ gets a suit head whose scale and vertical pos has been + normalized to all the suit heads + NOTE: calling class is responsible for cleaning this up! + (eg. self.head = None) + """ + suitIndex = SuitDNA.suitHeadTypes.index(suitName) + suitDNA = SuitDNA.SuitDNA() + suitDNA.newSuit(suitName) + suit = Suit() + suit.setDNA(suitDNA) + headParts = suit.getHeadParts() + head = node.attachNewNode('head') + for part in headParts: + copyPart = part.copyTo(head) + # turn on depth write and test. + copyPart.setDepthTest(1) + copyPart.setDepthWrite(1) + suit.delete() + suit = None + p1 = Point3() + p2 = Point3() + head.calcTightBounds(p1, p2) + d = p2 - p1 + biggest = max(d[0], d[2]) + # make them ramp up slightly in size as we go down the row + column = (suitIndex % SuitDNA.suitsPerDept) + s = (0.2 + (column / 100.0)) / biggest + # also make them move down slightly as we go across + pos = -0.14 + ((SuitDNA.suitsPerDept - column - 1) / 135.0) + head.setPosHprScale(0, 0, pos, + 180, 0, 0, + s, s, s) + return head + +class Suit(Avatar.Avatar): + """Suit class:""" + + healthColors = (Vec4(0, 1, 0, 1), + Vec4(1, 1, 0, 1), + Vec4(1, 0.5, 0, 1), + Vec4(1, 0, 0, 1), + Vec4(0.3, 0.3, 0.3, 1)) + healthGlowColors = (Vec4(0.25, 1, 0.25, 0.5), + Vec4(1, 1, 0.25, 0.5), + Vec4(1, 0.5, 0.25, 0.5), + Vec4(1, 0.25, 0.25, 0.5), + Vec4(0.3, 0.3, 0.3, 0)) + medallionColors = { + # Corporate + 'c' : Vec4(0.863, 0.776, 0.769, 1.000), + # Sales + 's' : Vec4(0.843, 0.745, 0.745, 1.000), + # Legal + 'l' : Vec4(0.749, 0.776, 0.824, 1.000), + # Marketing + 'm' : Vec4(0.749, 0.769, 0.749, 1.000), + } + + def __init__(self): + try: + self.Suit_initialized + return + except: + self.Suit_initialized = 1 + + Avatar.Avatar.__init__(self) + self.setFont(ToontownGlobals.getSuitFont()) + self.setPlayerType(NametagGroup.CCSuit) + + # Suits are now pickable + self.setPickable(1) + + self.leftHand = None + self.rightHand = None + self.shadowJoint = None + self.nametagJoint = None + self.headParts = [] + self.healthBar = None + self.healthCondition = 0 + self.isDisguised = 0 + self.isWaiter = 0 + + def delete(self): + try: + self.Suit_deleted + except: + self.Suit_deleted = 1 + if self.leftHand: + self.leftHand.removeNode() + self.leftHand = None + if self.rightHand: + self.rightHand.removeNode() + self.rightHand = None + if self.shadowJoint: + self.shadowJoint.removeNode() + self.shadowJoint = None + if self.nametagJoint: + self.nametagJoint.removeNode() + self.nametagJoint = None + for part in self.headParts: + part.removeNode() + self.headParts = [] + self.removeHealthBar() + Avatar.Avatar.delete(self) + return + + def setHeight(self, height): + Avatar.Avatar.setHeight(self, height) + + # Put our name tag higher, since it has three lines... + self.nametag3d.setPos(0, 0, height + 1.0) + + def getRadius(self): + # Suits have a fatter collision volume than toons. + return 2 + + def setDNAString(self, dnaString): + self.dna = SuitDNA.SuitDNA() + self.dna.makeFromNetString(dnaString) + self.setDNA(self.dna) + + def setDNA(self, dna): + if self.style: + pass + else: + # store the DNA + self.style = dna + + self.generateSuit() + + # this no longer works in the Avatar init! + # I moved it here for lack of a better place + # make the drop shadow + self.initializeDropShadow() + self.initializeNametag3d() + + def generateSuit(self): + """ + Create a suit from dna (an array of strings) + """ + + dna = self.style + self.headParts = [] + + # most heads do not need different poly color or texture + self.headColor = None + self.headTexture = None + + # For suit death animation + self.loseActor = None + + # Have we become a skelecog? + self.isSkeleton = 0 + + # Suit heights have been determined empirically; see + # RoguesGallery.py or the magic word ~rogues. + + # corporate dept + if (dna.name == 'f'): + # flunky + self.scale = 4.0/cSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + # this suit has two head parts + self.generateHead("flunky") + self.generateHead("glasses") + self.setHeight(4.88) + elif (dna.name == 'p'): + # pencil pusher + self.scale = 3.35/bSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + self.generateHead("pencilpusher") + self.setHeight(5.00) + elif (dna.name == 'ym'): + # yes man + self.scale = 4.125/aSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + self.generateHead("yesman") + self.setHeight(5.28) + elif (dna.name == 'mm'): + # micromanager + self.scale = 2.5/cSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + self.generateHead("micromanager") + self.setHeight(3.25) + elif (dna.name == 'ds'): + # downsizer - DEFAULT + self.scale = 4.5/bSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + self.generateHead("beancounter") + self.setHeight(6.08) + elif (dna.name == 'hh'): + # head hunter + self.scale = 6.5/aSize + self.handColor = SuitDNA.corpPolyColor + self.generateBody() + self.generateHead("headhunter") + self.setHeight(7.45) + elif (dna.name == 'cr'): + # corporate raider + self.scale = 6.75/cSize + self.handColor = VBase4(0.85, 0.55, 0.55, 1.0) + self.generateBody() + self.headTexture = "corporate-raider.jpg" + self.generateHead("flunky") + self.setHeight(8.23) + elif (dna.name == 'tbc'): + # the big cheese + self.scale = 7.0/aSize + self.handColor = VBase4(0.75, 0.95, 0.75, 1.0) + self.generateBody() + self.generateHead("bigcheese") + self.setHeight(9.34) + + # legal dept + elif (dna.name == 'bf'): + # bottom feeder + self.scale = 4.0/cSize + self.handColor = SuitDNA.legalPolyColor + self.generateBody() + self.headTexture = "bottom-feeder.jpg" + self.generateHead("tightwad") + self.setHeight(4.81) + elif (dna.name == 'b'): + # blood sucker + self.scale = 4.375/bSize + self.handColor = VBase4(0.95, 0.95, 1.0, 1.0) + self.generateBody() + self.headTexture = "blood-sucker.jpg" + self.generateHead("movershaker") + self.setHeight(6.17) + elif (dna.name == 'dt'): + # double talker + self.scale = 4.25/aSize + self.handColor = SuitDNA.legalPolyColor + self.generateBody() + self.headTexture = "double-talker.jpg" + self.generateHead("twoface") + self.setHeight(5.63) + elif (dna.name == 'ac'): + # ambulance chaser + self.scale = 4.35/bSize + self.handColor = SuitDNA.legalPolyColor + self.generateBody() + self.generateHead("ambulancechaser") + self.setHeight(6.39) + elif (dna.name == 'bs'): + # back stabber + self.scale = 4.5/aSize + self.handColor = SuitDNA.legalPolyColor + self.generateBody() + self.generateHead("backstabber") + self.setHeight(6.71) + elif (dna.name == 'sd'): + # spin doctor + self.scale = 5.65/bSize + self.handColor = VBase4(0.5, 0.8, 0.75, 1.0) + self.generateBody() + self.headTexture = "spin-doctor.jpg" + self.generateHead("telemarketer") + self.setHeight(7.90) + elif (dna.name == 'le'): + # legal eagle + self.scale = 7.125/aSize + self.handColor = VBase4(0.25, 0.25, 0.5, 1.0) + self.generateBody() + self.generateHead("legaleagle") + self.setHeight(8.27) + elif (dna.name == 'bw'): + # bigwig + self.scale = 7.0/aSize + self.handColor = SuitDNA.legalPolyColor + self.generateBody() + self.generateHead("bigwig") + self.setHeight(8.69) + + # money dept + elif (dna.name == 'sc'): + # short changer + self.scale = 3.6/cSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.generateHead("coldcaller") + self.setHeight(4.77) + elif (dna.name == 'pp'): + # penny pincher + self.scale = 3.55/aSize + self.handColor = VBase4( 1.0, 0.5, 0.6, 1.0) + self.generateBody() + self.generateHead("pennypincher") + self.setHeight(5.26) + elif (dna.name == 'tw'): + # tightwad + self.scale = 4.5/cSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.generateHead("tightwad") + self.setHeight(5.41) + elif (dna.name == 'bc'): + # bean counter + self.scale = 4.4/bSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.generateHead("beancounter") + self.setHeight(5.95) + elif (dna.name == 'nc'): + # number cruncher + self.scale = 5.25/aSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.generateHead("numbercruncher") + self.setHeight(7.22) + elif (dna.name == 'mb'): + # money bags + self.scale = 5.3/cSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.generateHead("moneybags") + self.setHeight(6.97) + elif (dna.name == 'ls'): + # load shark + self.scale = 6.5/bSize + self.handColor = VBase4(0.5, 0.85, 0.75, 1.0) + self.generateBody() + self.generateHead("loanshark") + self.setHeight(8.58) + elif (dna.name == 'rb'): + # robber baron + self.scale = 7.0/aSize + self.handColor = SuitDNA.moneyPolyColor + self.generateBody() + self.headTexture = "robber-baron.jpg" + self.generateHead("yesman") + self.setHeight(8.95) + + # sales dept + elif (dna.name == 'cc'): + # cold caller + self.scale = 3.5/cSize + self.handColor = VBase4(0.55, 0.65, 1.0, 1.0) + self.headColor = VBase4(0.25, 0.35, 1.0, 1.0) + self.generateBody() + self.generateHead("coldcaller") + self.setHeight(4.63) + elif (dna.name == 'tm'): + # telemarketer + self.scale = 3.75/bSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.generateHead("telemarketer") + self.setHeight(5.24) + elif (dna.name == 'nd'): + # name dropper + self.scale = 4.35/aSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.headTexture = "name-dropper.jpg" + self.generateHead("numbercruncher") + self.setHeight(5.98) + elif (dna.name == 'gh'): + # glad hander + self.scale = 4.75/cSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.generateHead("gladhander") + self.setHeight(6.40) + elif (dna.name == 'ms'): + # mover & shaker + self.scale = 4.75/bSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.generateHead("movershaker") + self.setHeight(6.70) + elif (dna.name == 'tf'): + # two-face + self.scale = 5.25/aSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.generateHead("twoface") + self.setHeight(6.95) + elif (dna.name == 'm'): + # the mingler + self.scale = 5.75/aSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.headTexture = "mingler.jpg" + self.generateHead("twoface") + self.setHeight(7.61) + elif (dna.name == 'mh'): + # Mr. Hollywood + self.scale = 7.0/aSize + self.handColor = SuitDNA.salesPolyColor + self.generateBody() + self.generateHead("yesman") + self.setHeight(8.95) + + self.setName(SuitBattleGlobals.SuitAttributes[dna.name]['name']) + self.getGeomNode().setScale(self.scale) + self.generateHealthBar() + self.generateCorporateMedallion() + + def generateBody(self): + """ + Load the appropriate suit body and anims + """ + # get the anims + animDict = self.generateAnimDict() + + # NOTE: It is always phase 3.5 because the models are there + # while everything else is in phase 5. + filePrefix, bodyPhase = ModelDict[self.style.body] + self.loadModel("phase_3.5" + filePrefix + "mod") + self.loadAnims(animDict) + self.setSuitClothes() + + def generateAnimDict(self): + # compile a dictionary of all anims for this suit in the format + # { "animName" : "animFilePath", ... } + animDict = {} + + filePrefix, bodyPhase = ModelDict[self.style.body] + + # load all shared anims + for anim in AllSuits: + # a=4, b=4, c=3.5 + animDict[anim[0]] = "phase_" + str(bodyPhase) + filePrefix + anim[1] + for anim in AllSuitsMinigame: + # a=4, b=4, c=4 + animDict[anim[0]] = "phase_4" + filePrefix + anim[1] + for anim in AllSuitsTutorialBattle: + # a = 4, b = 4, c = 3.5 + filePrefix, bodyPhase = TutorialModelDict[self.style.body] + animDict[anim[0]] = "phase_" + str(bodyPhase) + filePrefix + anim[1] + for anim in AllSuitsBattle: + # a=5, b=5, c=5 + animDict[anim[0]] = "phase_5" + filePrefix + anim[1] + + if self.style.body == 'a': + animDict['neutral'] = 'phase_4/models/char/suitA-neutral' + # add the CEO battle specific anims + for anim in SuitsCEOBattle: + animDict[anim[0]] = "phase_12/models/char/suitA-" + anim[1] + elif self.style.body == 'b': + animDict['neutral'] = 'phase_4/models/char/suitB-neutral' + # add the CEO battle specific anims + for anim in SuitsCEOBattle: + animDict[anim[0]] = "phase_12/models/char/suitB-" + anim[1] + elif self.style.body == 'c': + animDict['neutral'] = 'phase_3.5/models/char/suitC-neutral' + # add the CEO battle specific anims + for anim in SuitsCEOBattle: + animDict[anim[0]] = "phase_12/models/char/suitC-" + anim[1] + + # load the suit specific anims + try: + animList = eval(self.style.name) + except NameError: + # no suit specific anims defined + animList = () + + for anim in animList: + phase = "phase_" + str(anim[2]) + animDict[anim[0]] = phase + filePrefix + anim[1] + + return animDict + + def initializeBodyCollisions(self, collIdStr): + Avatar.Avatar.initializeBodyCollisions(self, collIdStr) + + if not self.ghostMode: + self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask) + + def setSuitClothes(self, modelRoot=None): + """ + Set the appropriate textures for this suit dept. + """ + # default to setting textures on ourselves + if not modelRoot: + modelRoot = self + + dept = self.style.dept + phase = 3.5 + + # set the clothes textures for the suit dept + torsoTex = loader.loadTexture("phase_%s/maps/%s_blazer.jpg" % (phase, dept)) + torsoTex.setMinfilter(Texture.FTLinearMipmapLinear) + torsoTex.setMagfilter(Texture.FTLinear) + legTex = loader.loadTexture("phase_%s/maps/%s_leg.jpg" % (phase, dept)) + legTex.setMinfilter(Texture.FTLinearMipmapLinear) + legTex.setMagfilter(Texture.FTLinear) + armTex = loader.loadTexture("phase_%s/maps/%s_sleeve.jpg" % (phase, dept)) + armTex.setMinfilter(Texture.FTLinearMipmapLinear) + armTex.setMagfilter(Texture.FTLinear) + + modelRoot.find("**/torso").setTexture(torsoTex, 1) + modelRoot.find("**/arms").setTexture(armTex, 1) + modelRoot.find("**/legs").setTexture(legTex, 1) + + # find the useful nulls + self.leftHand = self.find("**/joint_Lhold") + self.rightHand = self.find("**/joint_Rhold") + self.shadowJoint = self.find("**/joint_shadow") + self.nametagJoint = self.find("**/joint_nameTag") + + # set hand color + modelRoot.find("**/hands").setColor(self.handColor) + + def makeWaiter(self, modelRoot=None): + """ + Set the appropriate textures for a bosscog battle waiter + """ + # default to setting textures on ourselves + if not modelRoot: + modelRoot = self + + # set the clothes textures for a waiter + self.isWaiter = 1 + torsoTex = loader.loadTexture("phase_3.5/maps/waiter_m_blazer.jpg") + torsoTex.setMinfilter(Texture.FTLinearMipmapLinear) + torsoTex.setMagfilter(Texture.FTLinear) + legTex = loader.loadTexture("phase_3.5/maps/waiter_m_leg.jpg") + legTex.setMinfilter(Texture.FTLinearMipmapLinear) + legTex.setMagfilter(Texture.FTLinear) + armTex = loader.loadTexture("phase_3.5/maps/waiter_m_sleeve.jpg") + armTex.setMinfilter(Texture.FTLinearMipmapLinear) + armTex.setMagfilter(Texture.FTLinear) + + modelRoot.find("**/torso").setTexture(torsoTex, 1) + modelRoot.find("**/arms").setTexture(armTex, 1) + modelRoot.find("**/legs").setTexture(legTex, 1) + + + def generateHead(self, headType): + """generateHead(self, string) + Manipulate the head model to display only the appropriate head + """ + # load the multi-head models + filePrefix, phase = ModelDict[self.style.body] + headModel = loader.loadModel("phase_" + str(phase) + filePrefix + "heads") + + # search for the appropriate parts + headReferences = headModel.findAllMatches("**/" + headType) + for i in range(0, headReferences.getNumPaths()): + headPart = self.instance(headReferences.getPath(i), "modelRoot", + "joint_head") + # set head texture if necessary + if self.headTexture: + headTex = loader.loadTexture("phase_" + str(phase) + "/maps/" + + self.headTexture) + headTex.setMinfilter(Texture.FTLinearMipmapLinear) + headTex.setMagfilter(Texture.FTLinear) + headPart.setTexture(headTex, 1) + + # set head color if necessary + if self.headColor: + headPart.setColor(self.headColor) + self.headParts.append(headPart) + + # Now remove the extra instance that was created in the + # loadModelOnce call; we don't need it anymore now that we've + # copied everything out. + headModel.removeNode() + + def generateCorporateTie(self, modelPath=None): + if not modelPath: + modelPath = self + dept = self.style.dept + tie = modelPath.find('**/tie') + if tie.isEmpty(): + self.notify.warning('skelecog has no tie model!!!') + return + #print '### loading %s tie' % (dept) + if dept == 'c': + tieTex = loader.loadTexture("phase_5/maps/cog_robot_tie_boss.jpg") + elif dept == 's': + tieTex = loader.loadTexture("phase_5/maps/cog_robot_tie_sales.jpg") + elif dept == 'l': + tieTex = loader.loadTexture("phase_5/maps/cog_robot_tie_legal.jpg") + elif dept == 'm': + tieTex = loader.loadTexture("phase_5/maps/cog_robot_tie_money.jpg") + tieTex.setMinfilter(Texture.FTLinearMipmapLinear) + tieTex.setMagfilter(Texture.FTLinear) + tie.setTexture(tieTex, 1) + + def generateCorporateMedallion(self): + icons = loader.loadModel('phase_3/models/gui/cog_icons') + dept = self.style.dept + chestNull = self.find('**/joint_attachMeter') + if dept == 'c': + self.corpMedallion = icons.find('**/CorpIcon').copyTo(chestNull) + elif dept == 's': + self.corpMedallion = icons.find('**/SalesIcon').copyTo(chestNull) + elif dept == 'l': + self.corpMedallion = icons.find('**/LegalIcon').copyTo(chestNull) + elif dept == 'm': + self.corpMedallion = icons.find('**/MoneyIcon').copyTo(chestNull) + self.corpMedallion.setPosHprScale(0.02, 0.05, 0.04, + 180.00, 0.00, 0.00, + 0.51, 0.51, 0.51) + self.corpMedallion.setColor(self.medallionColors[dept]) + icons.removeNode() + + def generateHealthBar(self): + """ + Create a health meter for the suit and put it on his chest + """ + self.removeHealthBar() + + # Create health button for the suit + model = loader.loadModel('phase_3.5/models/gui/matching_game_gui') + button = model.find('**/minnieCircle') + button.setScale(3.0) + button.setH(180.0) + button.setColor(self.healthColors[0]) + chestNull = self.find('**/joint_attachMeter') + button.reparentTo(chestNull) + self.healthBar = button + glow = BattleProps.globalPropPool.getProp('glow') + glow.reparentTo(self.healthBar) + glow.setScale(0.28) + glow.setPos(-0.005, 0.01, 0.015) + glow.setColor(self.healthGlowColors[0]) + button.flattenLight() + + self.healthBarGlow = glow + self.healthBar.hide() + self.healthCondition = 0 + + def reseatHealthBarForSkele(self): + self.healthBar.setPos(0.0, 0.1, 0.0) + + + def updateHealthBar(self, hp, forceUpdate=0): + + if (hp > self.currHP): + hp = self.currHP + self.currHP -= hp + + health = float(self.currHP) / float(self.maxHP) + if (health > 0.95): + condition = 0 + elif (health > 0.7): + condition = 1 + elif (health > 0.3): + condition = 2 + elif (health > 0.05): + condition = 3 + elif (health > 0.0): + # This should be blinking red + condition = 4 + else: + # This should be blinking red even faster + condition = 5 + + if (self.healthCondition != condition) or forceUpdate: + if (condition == 4): + blinkTask = Task.loop(Task(self.__blinkRed), + Task.pause(0.75), + Task(self.__blinkGray), + Task.pause(0.1)) + taskMgr.add(blinkTask, self.uniqueName('blink-task')) + elif (condition == 5): + if (self.healthCondition == 4): + taskMgr.remove(self.uniqueName('blink-task')) + blinkTask = Task.loop(Task(self.__blinkRed), + Task.pause(0.25), + Task(self.__blinkGray), + Task.pause(0.1)) + taskMgr.add(blinkTask, self.uniqueName('blink-task')) + else: + self.healthBar.setColor(self.healthColors[condition], 1) + self.healthBarGlow.setColor(self.healthGlowColors[condition], 1) + self.healthCondition = condition + + def __blinkRed(self, task): + self.healthBar.setColor(self.healthColors[3], 1) + self.healthBarGlow.setColor(self.healthGlowColors[3], 1) + if (self.healthCondition == 5): + self.healthBar.setScale(1.17) + return Task.done + + def __blinkGray(self, task): + if not self.healthBar: + return + self.healthBar.setColor(self.healthColors[4], 1) + self.healthBarGlow.setColor(self.healthGlowColors[4], 1) + if (self.healthCondition == 5): + self.healthBar.setScale(1.0) + return Task.done + + def removeHealthBar(self): + if self.healthBar: + self.healthBar.removeNode() + self.healthBar = None + if (self.healthCondition == 4 or self.healthCondition == 5): + taskMgr.remove(self.uniqueName('blink-task')) + self.healthCondition = 0 + + # the lose actor is seperate cog geometry intended for explosions only + def getLoseActor(self): + """ + Return the lose geometry and anim for this type of suit + as an actor. If we are a skelecog, get that lose actor instead + """ + if (self.loseActor == None): + if not self.isSkeleton: + # standard cog + filePrefix, phase = TutorialModelDict[self.style.body] + loseModel = "phase_" + str(phase) + filePrefix + "lose-mod" + loseAnim = "phase_" + str(phase) + filePrefix + "lose" + + # make the actor + self.loseActor = Actor.Actor(loseModel, {"lose":loseAnim}) + + # copy the current head to the lose actor + loseNeck = self.loseActor.find("**/joint_head") + for part in self.headParts: + part.instanceTo(loseNeck) + + # put the appropriate textures on the suit + if self.isWaiter: + self.makeWaiter(self.loseActor) + else: + self.setSuitClothes(self.loseActor) + else: + # skelecog + loseModel = "phase_5/models/char/cog" + string.upper(self.style.body) + "_robot-lose-mod" + filePrefix, phase = TutorialModelDict[self.style.body] + loseAnim = "phase_" + str(phase) + filePrefix + "lose" + + # make the actor + self.loseActor = Actor.Actor(loseModel, {"lose":loseAnim}) + + # set the appropriate tie texture + self.generateCorporateTie(self.loseActor) + + # set the scale on the lose actor + self.loseActor.setScale(self.scale) + + # put lose actor where actor is + self.loseActor.setPos(self.getPos()) + self.loseActor.setHpr(self.getHpr()) + + # put a shadow under the lose actor + shadowJoint = self.loseActor.find("**/joint_shadow") + dropShadow = loader.loadModel("phase_3/models/props/drop_shadow") + dropShadow.setScale(0.45) + dropShadow.setColor(0.0, 0.0, 0.0, 0.5) + dropShadow.reparentTo(shadowJoint) + + return(self.loseActor) + + def cleanupLoseActor(self): + self.notify.debug('cleanupLoseActor()') + if (self.loseActor != None): + self.notify.debug('cleanupLoseActor() - got one') + self.loseActor.cleanup() + self.loseActor = None + + # the load seperate cog geometry for cogs that become cog skeletons + def makeSkeleton(self): + """ + Convert to skeleton geometry. + """ + model = "phase_5/models/char/cog" + string.upper(self.style.body) + "_robot-zero" + anims = self.generateAnimDict() + + # remember the current anim + anim = self.getCurrentAnim() + + # grab the drop shadow + dropShadow = self.dropShadow + if not dropShadow.isEmpty(): + dropShadow.reparentTo(hidden) + + # remove the old geometry + self.removePart("modelRoot") + + # load the skeleton geometry + self.loadModel(model) + self.loadAnims(anims) + + # set the scale on the skeleton actor (plus a little extra to make it look right) + self.getGeomNode().setScale(self.scale * 1.0173) + self.generateHealthBar() + self.generateCorporateMedallion() + # set the appropriate tie texture + self.generateCorporateTie() + self.setHeight(self.height) + + + # some of the geometry needs to be backfaced and billboarded + parts = self.findAllMatches('**/pPlane*') + for partNum in range(0, parts.getNumPaths()): + #print 'found billboarded part!' + bb = parts.getPath(partNum) + bb.setTwoSided(1) + + # redo the nametag and drop shadow + self.setName(TTLocalizer.Skeleton) + nameInfo = TTLocalizer.SuitBaseNameWithLevel % {"name": self.name, + "dept": self.getStyleDept(), + "level": self.getActualLevel(),} + self.setDisplayName( nameInfo ) + + # re-find the useful nulls + self.leftHand = self.find("**/joint_Lhold") + self.rightHand = self.find("**/joint_Rhold") + self.shadowJoint = self.find("**/joint_shadow") + self.nametagNull = self.find("**/joint_nameTag") + + if not dropShadow.isEmpty(): + dropShadow.setScale(0.75) + if not self.shadowJoint.isEmpty(): + dropShadow.reparentTo(self.shadowJoint) + + # start the animation again + self.loop(anim) + + # set the flag + self.isSkeleton = 1 + + # getters and setters + def getHeadParts(self): + """ + Return the list of stored head parts + """ + return self.headParts + + def getRightHand(self): + """ + Return the null in the right hand + """ + return self.rightHand + + def getLeftHand(self): + """ + Return the null in the left hand + """ + return self.leftHand + + def getShadowJoint(self): + """ + Return the node for attaching the shadow + """ + return self.shadowJoint + + def getNametagJoints(self): + """ + Return the CharacterJoint that animates the nametag, in a list. + """ + # Not sure what the name is right now. + return [] + + def getDialogueArray(self): + if self.isSkeleton: + loadSkelDialog() + return SkelSuitDialogArray + else: + return SuitDialogArray diff --git a/toontown/src/suit/SuitAvatarPanel.py b/toontown/src/suit/SuitAvatarPanel.py new file mode 100644 index 0000000..158fc40 --- /dev/null +++ b/toontown/src/suit/SuitAvatarPanel.py @@ -0,0 +1,160 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +from otp.avatar import Avatar +from direct.distributed import DistributedObject +import SuitDNA +from toontown.toonbase import TTLocalizer +from otp.avatar import AvatarPanel +from toontown.friends import FriendsListPanel + +class SuitAvatarPanel(AvatarPanel.AvatarPanel): + """ + This is a panel that pops up in response to clicking on a Toon or + Cog nearby you, or to picking a Toon from your friends list. It + draws a little picture of the avatar's head, and gives you a few + options to pick from re the avatar. + """ + + # Limit to only have one avatar panel at a time + currentAvatarPanel = None + + def __init__(self, avatar): + AvatarPanel.AvatarPanel.__init__( + self, avatar, FriendsListPanel = FriendsListPanel) + + self.avName = avatar.getName() + + gui = loader.loadModel("phase_3.5/models/gui/suit_detail_panel") + self.frame = DirectFrame( + geom = gui.find("**/avatar_panel"), + geom_scale = 0.21, + geom_pos = (0,0,0.02), + relief = None, + pos = (1.1, 100, 0.525), + ) + + disabledImageColor = Vec4(1,1,1,0.4) + text0Color = Vec4(1,1,1,1) + text1Color = Vec4(0.5,1,0.5,1) + text2Color = Vec4(1,1,0.5,1) + text3Color = Vec4(1,1,1,0.2) + + # Now put the avatar's head in the panel. + self.head = self.frame.attachNewNode('head') + for part in avatar.headParts: + copyPart = part.copyTo(self.head) + # Turn on depth write and test. + copyPart.setDepthTest(1) + copyPart.setDepthWrite(1) + p1 = Point3() + p2 = Point3() + self.head.calcTightBounds(p1, p2) + d = p2 - p1 + biggest = max(d[0], d[1], d[2]) + s = 0.3/biggest + self.head.setPosHprScale( + 0, 0, 0, + 180, 0, 0, + s, s, s) + + # Put the avatar's name across the top. + self.nameLabel = DirectLabel( + parent = self.frame, + pos = (0.0125, 0, 0.36), + relief = None, + text = self.avName, + text_font = avatar.getFont(), + text_fg = Vec4(0,0,0,1), + text_pos = (0, 0), + text_scale = 0.047, + text_wordwrap = 7.5, + text_shadow = (1, 1, 1, 1), + ) + + level = avatar.getActualLevel() + dept = SuitDNA.getSuitDeptFullname(avatar.dna.name) + + self.levelLabel = DirectLabel( + parent = self.frame, + pos = (0, 0, -0.1), + relief = None, + text = (TTLocalizer.AvatarPanelCogLevel % level), + text_font = avatar.getFont(), + text_align = TextNode.ACenter, + text_fg = Vec4(0,0,0,1), + text_pos = (0, 0), + text_scale = 0.05, + text_wordwrap = 8.0, + ) + + # Get a temp copy of the corporate medallion for this suit + corpIcon = avatar.corpMedallion.copyTo(hidden) + corpIcon.iPosHprScale() + self.corpIcon = DirectLabel( + parent = self.frame, + geom = corpIcon, + geom_scale = 0.13, + pos = (0, 0, -0.175), + relief = None, + ) + # Delete copy + corpIcon.removeNode() + + self.deptLabel = DirectLabel( + parent = self.frame, + pos = (0, 0, -0.28), + relief = None, + text = dept, + text_font = avatar.getFont(), + text_align = TextNode.ACenter, + text_fg = Vec4(0,0,0,1), + text_pos = (0, 0), + text_scale = 0.05, + text_wordwrap = 8.0, + ) + + self.closeButton = DirectButton( + parent = self.frame, + relief = None, + pos = (0., 0, -0.36), + text = TTLocalizer.AvatarPanelCogDetailClose, + text_font = avatar.getFont(), + text0_fg = Vec4(0,0,0,1), + text1_fg = Vec4(0.5,0,0,1), + text2_fg = Vec4(1,0,0,1), + text_pos = (0, 0), + text_scale = 0.05, + command = self.__handleClose, + ) + + gui.removeNode() + + menuX = -0.05 + menuScale = 0.064 + + # hide the friend and clarabelle gui + base.localAvatar.obscureFriendsListButton(1) + + self.frame.show() + messenger.send("avPanelDone") + + def cleanup(self): + if self.frame == None: + return + self.frame.destroy() + del self.frame + self.frame = None + + self.head.removeNode() + del self.head + + # show the friend and clarabelle gui + base.localAvatar.obscureFriendsListButton(-1) + + AvatarPanel.AvatarPanel.cleanup(self) + + def __handleClose(self): + self.cleanup() + AvatarPanel.currentAvatarPanel = None diff --git a/toontown/src/suit/SuitBase.py b/toontown/src/suit/SuitBase.py new file mode 100644 index 0000000..2fc0a50 --- /dev/null +++ b/toontown/src/suit/SuitBase.py @@ -0,0 +1,164 @@ +"""SuitBase module: contains the SuitBase class""" + +# AI code should not import ShowBaseGlobal because it creates a graphics window +# from ShowBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed.ClockDelta import * + +import math +import random +from pandac.PandaModules import Point3 +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import SuitTimings +import SuitDNA +from toontown.toonbase import TTLocalizer + +# extra time to add (in seconds) to any time calculations for path movement +# for each leg +# +TIME_BUFFER_PER_WPT = 0.25 +TIME_DIVISOR = 100 + +# spread out the creation of this suit's task, helps to prevent +# slowdowns, but causes suits to take longer to get moving +# +DISTRIBUTE_TASK_CREATION = 0 + +class SuitBase: + """ + //////////////////////////////////////////////////////////////////////// + // SuitBase class: a 'bad guy' which contains common functionality + // that both a client side and a server side suit can + // use + // + // Attributes: + // + //////////////////////////////////////////////////////////////////////// + """ + notify = DirectNotifyGlobal.directNotify.newCategory('SuitBase') + def __init__(self): + self.dna = None + self.level = 0 + # This gets initialized to real value in d_setLevel() + self.maxHP = 10 + self.currHP = 10 + self.isSkelecog = 0 + + def delete(self): + return + + def getStyleName(self): + if (hasattr(self, "dna") and self.dna): + return self.dna.name + else: + self.notify.error('called getStyleName() before dna was set!') + return 'unknown' + + def getStyleDept(self): + if (hasattr(self, "dna") and self.dna): + return SuitDNA.getDeptFullname(self.dna.dept) + else: + self.notify.error('called getStyleDept() before dna was set!') + return 'unknown' + + def getLevel(self): + return self.level + + def setLevel(self, level): + self.level = level + nameWLevel = TTLocalizer.SuitBaseNameWithLevel % {"name": self.name, + "dept": self.getStyleDept(), + "level": self.getActualLevel(),} + self.setDisplayName( nameWLevel ) + # Compute maxHP based on level + attributes = SuitBattleGlobals.SuitAttributes[self.dna.name] + self.maxHP = attributes['hp'][self.level] + self.currHP = self.maxHP + + def getSkelecog(self): + return self.isSkelecog + + def setSkelecog(self, flag): + self.isSkelecog = flag + + def getActualLevel( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: from the suit's 'relative' level (relative to the + // type of suit that this guy is) figure out the suit's + // actual level (1-12) + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + if hasattr(self, "dna"): + return SuitBattleGlobals.getActualFromRelativeLevel( + self.getStyleName(), + self.level ) + 1 + else: + self.notify.warning('called getActualLevel with no DNA, returning 1 for level') + return 1 + + def setPath( self, path ): + """ + //////////////////////////////////////////////////////////////////// + // Function: set the path to be used by this suit, this function + // is called by the SuitPlannerAI + // Parameters: path, the path that this suit should use + // Changes: none + //////////////////////////////////////////////////////////////////// + """ + self.path = path + self.pathLength = self.path.getNumPoints() + + def getPath( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: set the path to be used by this suit, this function + // is called by the SuitPlannerAI + // Parameters: path, the path that this suit should use + // Changes: none + //////////////////////////////////////////////////////////////////// + """ + return self.path + + def printPath( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: print out this suit's current path + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + # print out the path + # + print "%d points in path" % self.pathLength +# print self.path + for currPathPt in range( self.pathLength ): + indexVal = self.path.getPointIndex( currPathPt ) + print "\t", self.sp.dnaStore.getSuitPointWithIndex( indexVal ) + + def makeLegList(self): + """makeLegList(self) + + Fills up self.legList with a list of SuitLeg objects that + reflect the path previously set via setPath(). See + suitLegList.h. + """ + + self.legList = SuitLegList(self.path, self.sp.dnaStore, + self.sp.suitWalkSpeed, + SuitTimings.fromSky, + SuitTimings.toSky, + SuitTimings.fromSuitBuilding, + SuitTimings.toSuitBuilding, + SuitTimings.toToonBuilding) + + +# history +# +# 14Feb01 jlbutler created +# + + diff --git a/toontown/src/suit/SuitDNA.py b/toontown/src/suit/SuitDNA.py new file mode 100644 index 0000000..f5a7703 --- /dev/null +++ b/toontown/src/suit/SuitDNA.py @@ -0,0 +1,297 @@ +"""SuitDNA module: contains the methods and definitions for describing +multipart actors with a simple class""" + +import random +from pandac.PandaModules import * +from direct.directnotify.DirectNotifyGlobal import * +from toontown.toonbase import TTLocalizer +import random +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from otp.avatar import AvatarDNA + +notify = directNotify.newCategory("SuitDNA") + +# suit defines +suitHeadTypes = [ + # + # warning: changes to this list will affect *Suit* methods below. + # We also depend on this ordering in the battle exp system. + # corporate + "f", "p", "ym", "mm", "ds", "hh", "cr", "tbc", + # legal + "bf", "b", "dt", "ac", "bs", "sd", "le", "bw", + # money + "sc", "pp", "tw", "bc", "nc", "mb", "ls", "rb", + # sales + "cc", "tm", "nd", "gh", "ms", "tf", "m", "mh", + ] + +suitATypes = [ "ym", "hh", "tbc", "dt", "bs", "le", "bw", "pp", "nc", + "rb", "nd", "tf", "m", "mh" ] + +suitBTypes = [ "p", "ds", "b", "ac", "sd", "bc", "ls", "tm", "ms" ] + +suitCTypes = [ "f", "mm", "cr", "bf", "sc", "tw", "mb", "cc", "gh" ] + +suitDepts = [ "c", "l", "m", "s" ] +suitDeptFullnames = {"c" : TTLocalizer.Bossbot, + "l" : TTLocalizer.Lawbot, + "m" : TTLocalizer.Cashbot, + "s" : TTLocalizer.Sellbot, + } +suitDeptFullnamesP = {"c" : TTLocalizer.BossbotP, + "l" : TTLocalizer.LawbotP, + "m" : TTLocalizer.CashbotP, + "s" : TTLocalizer.SellbotP, + } +corpPolyColor = VBase4(0.95, 0.75, 0.75, 1.0) +legalPolyColor = VBase4(0.75, 0.75, 0.95, 1.0) +moneyPolyColor = VBase4(0.65, 0.95, 0.85, 1.0) +salesPolyColor = VBase4(0.95, 0.75, 0.95, 1.0) + +suitsPerLevel = [1,1,1,1,1,1,1,1] +suitsPerDept = 8 + +goonTypes = ["pg", "sg"] + +def getSuitBodyType(name): + """getSuitBodyType(string): + Given a suit name, return its body type (a, b, or c) + """ + if (name in suitATypes): + return "a" + elif (name in suitBTypes): + return "b" + elif (name in suitCTypes): + return "c" + else: + print "Unknown body type for suit name: ", name + +def getSuitDept(name): + """getSuitDept(string): + Given a suit name, return its department name as a string + """ + index = suitHeadTypes.index(name) + if (index < suitsPerDept): + return suitDepts[0] + elif (index < suitsPerDept*2): + return suitDepts[1] + elif (index < suitsPerDept*3): + return suitDepts[2] + elif (index < suitsPerDept*4): + return suitDepts[3] + else: + print "Unknown dept for suit name: ", name + return None + +def getDeptFullname(dept): + """getDeptFullname(string): + Given a dept code, return the fullname + """ + return suitDeptFullnames[dept] + +def getDeptFullnameP(dept): + """getDeptFullnameP(string): + Given a dept code, return the fullname (plural) + """ + return suitDeptFullnamesP[dept] + +def getSuitDeptFullname(name): + """getSuitDept(string): + Given a suit code, return the fullname + """ + return suitDeptFullnames[getSuitDept(name)] + +def getSuitType(name): + """getSuitType(string): + Given a suit name, return its type index (1..8). + """ + index = suitHeadTypes.index(name) + return (index % suitsPerDept) + 1 + +def getRandomSuitType(level, rng=random): + """ given a suit level, return a randomly-chosen suit type """ + return random.randint(max(level-4, 1 ), min(level, 8)) + +def getRandomSuitByDept(dept): + """ given a suit dept, return a randomly-chosen suit """ + deptNumber = suitDepts.index(dept) + return suitHeadTypes[(suitsPerDept*deptNumber) + random.randint(0,7)] + +class SuitDNA(AvatarDNA.AvatarDNA): + """SuitDNA class: contains methods for describing avatars with a + simple class. The SuitDNA class may be converted to lists of strings + for network transmission. Also, SuitDNA objects can be constructed + from lists of strings recieved over the network. Some examples are in + order. + + # create a suit's dna + dna = AvatarDNA() + dna.newSuit() # no args defaults to 'Downsizer' + dna.newSuit('ym') # make 'Yes Man' dna + dna.newSuitRandom(3) # make a random level 3 suit + dna.newSuitRandom(3, 'l') # make a random level 3 legal suit + + """ + # special methods + + def __init__(self, str=None, type=None, dna=None, r=None, b=None, g=None): + """__init__(self, string=None, string=None, string()=None, float=None, + float=None, float=None) + SuitDNA contructor - see class comment for usage + """ + # have they passed in a stringified DNA object? + if (str != None): + self.makeFromNetString(str) + # have they specified what type of DNA? + elif (type != None): + if (type == 's'): # Suit + self.newSuit() + else: + # Invalid type + assert 0 + else: + # mark DNA as undefined + self.type = 'u' + + def __str__(self): + """__str__(self) + Avatar DNA print method + """ + if (self.type == 's'): + return "type = %s\nbody = %s, dept = %s, name = %s" % \ + ("suit", self.body, self.dept, self.name) + elif (self.type == 'b'): + return "type = boss cog\ndept = %s" % (self.dept) + else: + return "type undefined" + + + # stringification methods + def makeNetString(self): + dg = PyDatagram() + dg.addFixedString(self.type, 1) + if (self.type == 's'): + dg.addFixedString(self.name, 3) + dg.addFixedString(self.dept, 1) + elif (self.type == 'b'): # Boss Cog + dg.addFixedString(self.dept, 1) + elif (self.type == 'u'): + notify.error("undefined avatar") + else: + notify.error("unknown avatar type: ", self.type) + + return dg.getMessage() + + def makeFromNetString(self, string): + dg=PyDatagram(string) + dgi=PyDatagramIterator(dg) + self.type = dgi.getFixedString(1) + if (self.type == 's'): # Suit + self.name = dgi.getFixedString(3) + self.dept = dgi.getFixedString(1) + self.body = getSuitBodyType(self.name) + elif (self.type == 'b'): # Boss Cog + self.dept = dgi.getFixedString(1) + else: + notify.error("unknown avatar type: ", self.type) + + return None + + def __defaultGoon(self): + """__defaultChar(self) + Make a default character dna + """ + self.type = 'g' + self.name = goonTypes[0] + + def __defaultSuit(self): + """__defaultSuit(self) + Make a default suit dna + """ + self.type = 's' + self.name = 'ds' + self.dept = getSuitDept(self.name) + self.body = getSuitBodyType(self.name) + + def newSuit(self, name=None): + """newSuit(self, string=None) + If no suit name specified, set the dna for the default suit + else set the dna for suit specified by the given string. + """ + if (name == None): + self.__defaultSuit() + else: + self.type = "s" + self.name = name + self.dept = getSuitDept(self.name) + self.body = getSuitBodyType(self.name) + + def newBossCog(self, dept): + self.type = "b" + self.dept = dept + + def newSuitRandom(self, level=None, dept=None): + """newSuitRandom(self, int=None, string=None) + Generate dna for a random suit of random level (unless level + is specified) and random dept (again, unless specified) + """ + self.type = "s" + + if (level==None): + # pick a random level + level = random.choice(range(1, len(suitsPerLevel))) + else: + # make sure supplied one is valid + if (level < 0 or level > len(suitsPerLevel)): + notify.error("Invalid suit level: %d" % level) + + if (dept == None): + # pick a random dept + dept = random.choice(suitDepts) + else: + # make sure supplied one is valid + assert dept in suitDepts + + # calculate range to choose from based on the level and dept + self.dept = dept + index = suitDepts.index(dept) + base = index * suitsPerDept + offset = 0 + if (level > 1): + for i in range(1, level): + offset = offset + suitsPerLevel[i - 1] + bottom = base + offset + top = bottom + suitsPerLevel[level - 1] + self.name = suitHeadTypes[random.choice(range(bottom,top))] + self.body = getSuitBodyType(self.name) + + def newGoon(self, name = None): + """newGoon(self, type) + Return the dna for the goon of this name. If no name is given + return the default goon. + """ + if type == None: + self.__defaultGoon() + else: + self.type = 'g' + if (name in goonTypes): + self.name = name + else: + notify.error("unknown goon type: ", name) + + def getType(self): + """getType(self) + Return which type of actor this dna represents. + """ + if (self.type == 's'): + #suit type + type = "suit" + elif (self.type == 'b'): + #boss type + type = "boss" + else: + notify.error("Invalid DNA type: ", self.type) + + return type diff --git a/toontown/src/suit/SuitDialog.py b/toontown/src/suit/SuitDialog.py new file mode 100644 index 0000000..be4a42e --- /dev/null +++ b/toontown/src/suit/SuitDialog.py @@ -0,0 +1,51 @@ +import random +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import OTPLocalizer +from toontown.toonbase import TTLocalizer + +# Centralize everything a suit can say +# import this file +# Then call SuitDialog.requestBattle.get() to get the next dialog in the list + +notify = DirectNotifyGlobal.directNotify.newCategory('SuitDialog') + +def getBrushOffIndex(suitName): + """ getBrushOffIndex(suitName) + + Chooses a suitable brushoff for a suit of the given type, and + returns its index number (which can later be passed to + getBrushOffText() to retrieve the message itself). + + """ + if SuitBrushOffs.has_key(suitName): + brushoffs = SuitBrushOffs[suitName] + else: + brushoffs = SuitBrushOffs[None] + + # Why do we go through all this work to choose a random brushoff + # when we could just choose one directly via random.randint()? + num = len(brushoffs) + chunk = 100 / num + randNum = random.randint(0, 99) + count = chunk + for i in range(num): + if (randNum < count): + return i + count += chunk + notify.error('getBrushOffs() - no brush off found!') + +def getBrushOffText(suitName, index): + """ getBrushOffText(suitName, index) + + Returns the text of the brushoff with the given index number for + the given suit type. + + """ + if SuitBrushOffs.has_key(suitName): + brushoffs = SuitBrushOffs[suitName] + else: + brushoffs = SuitBrushOffs[None] + + return brushoffs[index] + +SuitBrushOffs = OTPLocalizer.SuitBrushOffs diff --git a/toontown/src/suit/SuitInvasionManagerAI.py b/toontown/src/suit/SuitInvasionManagerAI.py new file mode 100644 index 0000000..2c57af4 --- /dev/null +++ b/toontown/src/suit/SuitInvasionManagerAI.py @@ -0,0 +1,151 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import random +from direct.task import Task + +class SuitInvasionManagerAI: + """ + Manages invasions of Suits + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('SuitInvasionManagerAI') + + def __init__(self, air): + self.air = air + self.invading = 0 + self.cogType = None + self.skeleton = 0 + self.totalNumCogs = 0 + self.numCogsRemaining = 0 + + # Set of cog types to choose from See + # SuitBattleGlobals.SuitAttributes.keys() for all choices I did not + # put the highest level Cogs from each track in here to keep them + # special and only found in buildings. I threw in the Flunky just + # for fun. + self.invadingCogTypes = ( + # Corporate + 'f', # Flunky + 'hh', # Head Hunter + 'cr', # Corporate Raider + # Sales + 'tf', # Two-faced + 'm', # Mingler + # Money + 'mb', # Money Bags + 'ls', # Loan shark + # Legal + 'sd', # Spin Doctor + 'le', # Legal Eagle + ) + + # Picked from randomly how many cogs will invade + # This might need to be adjusted based on population(?) + self.invadingNumList = (1000, 2000, 3000, 4000) + + # Minimum time between invasions on this shard (in seconds) + # No more than 1 per 2 days + self.invasionMinDelay = 2 * 24 * 60 * 60 + # Maximum time between invasions on this shard (in seconds) + # At least once every 7 days + self.invasionMaxDelay = 7 * 24 * 60 * 60 + + # Kick off the first invasion + self.waitForNextInvasion() + + def delete(self): + taskMgr.remove(self.taskName("cogInvasionMgr")) + + def computeInvasionDelay(self): + # Compute the delay until the next invasion + return ((self.invasionMaxDelay - self.invasionMinDelay) * random.random() + + self.invasionMinDelay) + + def tryInvasionAndWaitForNext(self, task): + # Start the invasion if there is not one already + if self.getInvading(): + self.notify.warning("invasionTask: tried to start random invasion, but one is in progress") + else: + self.notify.info("invasionTask: starting random invasion") + cogType = random.choice(self.invadingCogTypes) + totalNumCogs = random.choice(self.invadingNumList) + self.startInvasion(cogType, totalNumCogs) + # In either case, fire off the next invasion + self.waitForNextInvasion() + return Task.done + + def waitForNextInvasion(self): + taskMgr.remove(self.taskName("cogInvasionMgr")) + delay = self.computeInvasionDelay() + self.notify.info("invasionTask: waiting %s seconds until next invasion" % delay) + taskMgr.doMethodLater(delay, self.tryInvasionAndWaitForNext, + self.taskName("cogInvasionMgr")) + + def getInvading(self): + return self.invading + + def getCogType(self): + return self.cogType, self.isSkeleton + + def getNumCogsRemaining(self): + return self.numCogsRemaining + + def getTotalNumCogs(self): + return self.totalNumCogs + + def startInvasion(self, cogType, totalNumCogs, skeleton=0): + if self.invading: + self.notify.warning("startInvasion: already invading cogType: %s numCogsRemaining: %s" % + (cogType, self.numCogsRemaining)) + return 0 + if not SuitBattleGlobals.SuitAttributes.get(cogType): + self.notify.warning("startInvasion: unknown cogType: %s" % cogType) + return 0 + + self.notify.info("startInvasion: cogType: %s totalNumCogs: %s skeleton: %s" % + (cogType, totalNumCogs, skeleton)) + self.invading = 1 + self.cogType = cogType + self.isSkeleton = skeleton + self.totalNumCogs = totalNumCogs + self.numCogsRemaining = self.totalNumCogs + + # Tell the news manager that an invasion is beginning + self.air.newsManager.invasionBegin(self.cogType, self.totalNumCogs, self.isSkeleton) + + # Get rid of all the current cogs on the streets + # (except those already in battle, they can stay) + for suitPlanner in self.air.suitPlanners.values(): + suitPlanner.flySuits() + # Success! + return 1 + + def getInvadingCog(self): + if self.invading: + self.numCogsRemaining -= 1 + if self.numCogsRemaining <= 0: + self.stopInvasion() + self.notify.debug("getInvadingCog: returned cog: %s, num remaining: %s" % + (self.cogType, self.numCogsRemaining)) + return self.cogType, self.isSkeleton + else: + self.notify.debug("getInvadingCog: not currently invading") + return None, None + + def stopInvasion(self): + self.notify.info("stopInvasion: invasion is over now") + # Tell the news manager that an invasion is ending + self.air.newsManager.invasionEnd(self.cogType, 0, self.isSkeleton) + self.invading = 0 + self.cogType = None + self.isSkeleton = 0 + self.totalNumCogs = 0 + self.numCogsRemaining = 0 + # Get rid of all the current invasion cogs on the streets + # (except those already in battle, they can stay) + for suitPlanner in self.air.suitPlanners.values(): + suitPlanner.flySuits() + + # Need this here since this is not a distributed object + def taskName(self, taskString): + return (taskString + "-" + str(hash(self))) diff --git a/toontown/src/suit/SuitPlannerBase.py b/toontown/src/suit/SuitPlannerBase.py new file mode 100644 index 0000000..d7a6609 --- /dev/null +++ b/toontown/src/suit/SuitPlannerBase.py @@ -0,0 +1,337 @@ +""" SuitPlannerBase module: contains common code that both the server + and client use when managing a collection of suits.""" + +# AI code should not import ShowBaseGlobal because it creates a graphics window +# If you need panda classes use PandaModules instead +# from ShowBaseGlobal import * +from pandac.PandaModules import * + +import random +import string +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import ZoneUtil + +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.hood import HoodUtil + +class SuitPlannerBase: + """ + ///////////////////////////////////////////////////////////////////////// + // + // SuitPlannerBase class: manages all suits which exist within a single + // neighborhood (or street), this base version contains general code + // that both the server and client can use, such as path generation + // code, dna storage, path point type storage + // + ///////////////////////////////////////////////////////////////////////// + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('SuitPlannerBase') + + def __init__( self ): + + # initialize some values that we will be using + # + self.suitWalkSpeed = ToontownGlobals.SuitWalkSpeed + + # now load up the dna file for the neighborhood that this suit + # planner is created for + # + self.dnaStore = None + + # keep a map of point indexes and the actual point so when + # suits need to look up information from a point's index, they + # can do it quickly without having to ask the dnaStore + # + self.pointIndexes = {} + + return None + + def setupDNA( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: load up DNA information for the neighborhood that + // this suit planner is in control of. the DNA + // contains suit path information as well as vis + // group (zone) informations + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + + if self.dnaStore: + return None + + self.dnaStore = DNAStorage() + dnaFileName = self.genDNAFileName() + try: + simbase.air.loadDNAFileAI( self.dnaStore, dnaFileName) + except: + loader.loadDNAFileAI( self.dnaStore, dnaFileName) + + + # now create vis group (zone) information + self.initDNAInfo() + + def genDNAFileName( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: determines the name of the DNA file that should + // be loaded for the neighborhood that this suit + // planner manages + // Parameters: none + // Changes: none + //////////////////////////////////////////////////////////////////// + """ + # This code might run on the AI or on the client. + try: + return simbase.air.genDNAFileName(self.getZoneId()) + + except: + # do some number manipulation of my zone id already given + # to me and figure out which dna file to load + + zoneId = ZoneUtil.getCanonicalZoneId(self.getZoneId()) + hoodId = ZoneUtil.getCanonicalHoodId(zoneId) + hood = ToontownGlobals.dnaMap[hoodId] + phase = ToontownGlobals.streetPhaseMap[hoodId] + if hoodId == zoneId: + zoneId = "sz" + + return "phase_%s/dna/%s_%s.dna" % (phase, hood, zoneId) + + + def getZoneId( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: intended to be overridden by any inheriting suit + // planner class, and that class should be a + // distributed object or at least have an attribute + // named 'zoneId' + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + return self.zoneId + + def setZoneId( self, zoneId ): + self.notify.debug( "setting zone id for suit planner" ) + self.zoneId = zoneId + self.setupDNA() + + def extractGroupName(self, groupFullName): + # The Idea here is that group names may have extra flags associated + # with them that tell more information about what is special about + # the particular vis zone. A normal vis zone might just be "13001", + # but a special one might be "14356:safe_zone" or + # "345:safe_zone:exit_zone"... These are hypotheticals. The main + # idea is that there are colon separated flags after the initial + # zone name. + return(string.split(groupFullName, ":", 1)[0]) + + def initDNAInfo( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: load up vis group information into a dictionary + // copied from HoodMgr.py + // Parameters: dnaStore, the dna storage structure to use + // Changes: + //////////////////////////////////////////////////////////////////// + """ + numGraphs = self.dnaStore.discoverContinuity() + if numGraphs != 1: + self.notify.info("zone %s has %s disconnected suit paths." % (self.zoneId, numGraphs)) + + # Construct a dictionary of zone ids to battle cell center points + self.battlePosDict = {} + self.cellToGagBonusDict = {} + #self.dnaStore.printSuitPointStorage() + for i in range(self.dnaStore.getNumDNAVisGroupsAI()): + vg = self.dnaStore.getDNAVisGroupAI(i) + zoneId = int(self.extractGroupName(vg.getName())) + # There is only 1 battle cell per zone + if (vg.getNumBattleCells() == 1): + battleCell = vg.getBattleCell(0) + self.battlePosDict[zoneId] = vg.getBattleCell(0).getPos() + elif (vg.getNumBattleCells() > 1): + self.notify.warning('multiple battle cells for zone: %d' % zoneId) + # Just pick the first one + self.battlePosDict[zoneId] = vg.getBattleCell(0).getPos() + if True: + # lets find the interactive props connected to this battle cell + for i in range(vg.getNumChildren()): + childDnaGroup = vg.at(i) + if (isinstance(childDnaGroup, DNAInteractiveProp)): + self.notify.debug("got interactive prop %s" % childDnaGroup) + battleCellId = childDnaGroup.getCellId() + if battleCellId == -1: + self.notify.warning( + "interactive prop %s at %s not associated with a a battle" % + (childDnaGroup, zoneId)) + elif battleCellId == 0: + if self.cellToGagBonusDict.has_key(zoneId): + self.notify.error( + "FIXME battle cell at zone %s has two props %s %s linked to it" % + (zoneId, self.cellToGagBonusDict[zoneId], childDnaGroup)) + else: + # based on the name of the prop, figure out which gag track bonus + name = childDnaGroup.getName() + propType = HoodUtil.calcPropType(name) + if propType in ToontownBattleGlobals.PropTypeToTrackBonus: + trackBonus = ToontownBattleGlobals.PropTypeToTrackBonus[propType] + self.cellToGagBonusDict[zoneId] = trackBonus + + # Now that we have extracted the vis groups we do not need + # the dnaStore to keep them around + self.dnaStore.resetDNAGroups() + self.dnaStore.resetDNAVisGroups() + self.dnaStore.resetDNAVisGroupsAI() + + # now load up the suit path points, separate them into types + # + self.streetPointList = [] + self.frontdoorPointList = [] + self.sidedoorPointList = [] + self.cogHQDoorPointList = [] + + numPoints = self.dnaStore.getNumSuitPoints() + for i in range(numPoints): + point = self.dnaStore.getSuitPointAtIndex(i) + if (point.getPointType() == DNASuitPoint.FRONTDOORPOINT): + self.frontdoorPointList.append(point) + elif (point.getPointType() == DNASuitPoint.SIDEDOORPOINT): + self.sidedoorPointList.append(point) + elif (point.getPointType() == DNASuitPoint.COGHQINPOINT or \ + point.getPointType() == DNASuitPoint.COGHQOUTPOINT): + self.cogHQDoorPointList.append(point) + else: + self.streetPointList.append(point) + + self.pointIndexes[ point.getIndex() ] = point + + # perform a simple path test to make sure we can properly + # generate a path from two points given to us by the DNAStorage + # +# self.performPathTest() + + return None + + + def performPathTest( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: test out path generation as well as travel time + // calculation for the current dnaStore information + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + + if not self.notify.getDebug(): + return None + + #self.notify.debug( 'street points: ' + str( self.streetPointList ) ) + #self.notify.debug( 'front door points: ' + + # str( self.frontdoorPointList ) ) + #self.notify.debug( 'side door points: ' + + # str( self.sidedoorPointList ) ) + + # create a simple path which will be only used for + # testing the getSuitPath function of the dnaStorage + # + startAndEnd = self.pickPath() + if not startAndEnd: + return None + + startPoint = startAndEnd[ 0 ] + endPoint = startAndEnd[ 1 ] + + path = self.dnaStore.getSuitPath( startPoint, endPoint ) + +# print path + + # now print out travel time for each edge in the resulting path + # as well as which zone each edge is in + # + numPathPoints = path.getNumPoints() + for i in range( numPathPoints - 1 ): + zone = self.dnaStore.getSuitEdgeZone( + path.getPointIndex(i), + path.getPointIndex(i+1) ) + travelTime = self.dnaStore.getSuitEdgeTravelTime( + path.getPointIndex(i), + path.getPointIndex(i+1), + self.suitWalkSpeed ) + self.notify.debug( + 'edge from point ' + `i` + + ' to point ' + `i+1` + + ' is in zone: ' + `zone` + + ' and will take ' + `travelTime` + + ' seconds to walk.' ) + + return None + + + def genPath(self, startPoint, endPoint, minPathLen, maxPathLen): + """ + //////////////////////////////////////////////////////////////////// + // Function: generate a path using the local dnaStorage given + // the start and end points + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ + return self.dnaStore.getSuitPath(startPoint, endPoint, minPathLen, maxPathLen) + + def getDnaStore( self ): + """ + //////////////////////////////////////////////////////////////////// + // Function: get the dnaStore from the suit planner, create the + // dnaStore if it has not already been loaded + // Parameters: none + // Changes: + //////////////////////////////////////////////////////////////////// + """ +# if self.dnaStore == None: +# self.setupDNA() + return self.dnaStore + +# history +# +# 12Feb01 jlbutler created. +# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toontown/src/suit/SuitTimings.py b/toontown/src/suit/SuitTimings.py new file mode 100644 index 0000000..dc6679a --- /dev/null +++ b/toontown/src/suit/SuitTimings.py @@ -0,0 +1,18 @@ +# Timing values for suit tasks and animations on the client used by the +# server + +dict = {} + +# the time it takes for all suits to fly down from the sky onto +# the first waypoint of their calculated path +# +fromSky = 6.5 +toSky = 6.5 +victoryDance = 9.08 + +# The time it takes to navigate the doors on the toon and suit +# buildings. + +fromSuitBuilding = 2.0 +toSuitBuilding = 2.5 +toToonBuilding = 2.5 diff --git a/toontown/src/suit/__init__.py b/toontown/src/suit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/suit/suitLeg.I b/toontown/src/suit/suitLeg.I new file mode 100644 index 0000000..db1ea13 --- /dev/null +++ b/toontown/src/suit/suitLeg.I @@ -0,0 +1,117 @@ +// Filename: suitLeg.I +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_type +// Access: Published +// Description: Returns the type of this leg. Most legs are of type +// T_bellicose, which corresponds to just plain walking +// down the street, but other legs particularly at both +// ends of the path may represent other modes. +//////////////////////////////////////////////////////////////////// +INLINE SuitLeg::Type SuitLeg:: +get_type() const { + return _type; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_start_time +// Access: Published +// Description: Returns the time (in seconds elapsed since the +// beginning of the path) at which this leg begins. +//////////////////////////////////////////////////////////////////// +INLINE double SuitLeg:: +get_start_time() const { + return _start_time; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_leg_time +// Access: Published +// Description: Returns the total length of time, in seconds, which +// this leg represents. +//////////////////////////////////////////////////////////////////// +INLINE double SuitLeg:: +get_leg_time() const { + return _leg_time; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_zone_id +// Access: Published +// Description: Returns the Zone ID associated with this leg. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLeg:: +get_zone_id() const { + return _zone_id; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_block_number +// Access: Published +// Description: Returns the block number associated with this leg, if +// any. Normally this is only relevant for CogHQ door +// type legs, in which it represents the particular door +// index we're going through. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLeg:: +get_block_number() const { + return _block_number; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_point_a +// Access: Published +// Description: Returns the first DNASuitPoint associated with this +// leg. In most cases, the leg represents the path +// between point A and point B. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLeg:: +get_point_a() const { + return _point_a; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_point_b +// Access: Published +// Description: Returns the second DNASuitPoint associated with this +// leg. In most cases, the leg represents the path +// between point A and point B. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLeg:: +get_point_b() const { + return _point_b; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_pos_a +// Access: Published +// Description: Returns the point in space associated with point A. +//////////////////////////////////////////////////////////////////// +INLINE LPoint3f SuitLeg:: +get_pos_a() const { + return _pos_a; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_pos_b +// Access: Published +// Description: Returns the point in space associated with point B. +//////////////////////////////////////////////////////////////////// +INLINE LPoint3f SuitLeg:: +get_pos_b() const { + return _pos_b; +} + +INLINE ostream & +operator << (ostream &out, const SuitLeg &leg) { + leg.output(out); + return out; +} + +INLINE ostream &operator << (ostream &out, SuitLeg::Type type) { + return out << SuitLeg::get_type_name(type); +} diff --git a/toontown/src/suit/suitLeg.cxx b/toontown/src/suit/suitLeg.cxx new file mode 100644 index 0000000..e490f5d --- /dev/null +++ b/toontown/src/suit/suitLeg.cxx @@ -0,0 +1,139 @@ +// Filename: suitLeg.cxx +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + +#include "suitLeg.h" + +#include "dnaSuitPoint.h" + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +SuitLeg:: +SuitLeg(Type type, double start_time, double leg_time, int zone_id, + int block_number, + const DNASuitPoint *point_a, const DNASuitPoint *point_b) : + _type(type), + _start_time(start_time), + _leg_time(leg_time), + _zone_id(zone_id), + _block_number(block_number), + _point_a(point_a->get_index()), + _point_b(point_b->get_index()), + _pos_a(point_a->get_pos()), + _pos_b(point_b->get_pos()) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_pos_at_time +// Access: Published +// Description: Returns the expected position of the suit at the +// indicated time, in seconds elapsed since the start of +// this leg. +//////////////////////////////////////////////////////////////////// +LPoint3f SuitLeg:: +get_pos_at_time(double time) const { + switch (_type) { + case T_walk_from_street: + case T_walk_to_street: + case T_walk: + break; + + case T_from_sky: + return _pos_a; + + case T_to_sky: + return _pos_b; + + case T_from_suit_building: + return _pos_a; + + case T_to_suit_building: + return _pos_b; + + case T_to_toon_building: + return _pos_b; + + case T_from_coghq: + return _pos_a; + + case T_to_coghq: + return _pos_b; + + case T_off: + return _pos_b; + } + + if (time < 0.0) { + return _pos_a; + } else if (time > _leg_time) { + return _pos_b; + } else { + return _pos_a + (time / _leg_time) * (_pos_b - _pos_a); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::get_type_name +// Access: Published, Static +// Description: Returns the string name associated with the indicated +// type. This is also the name that corresponds to a +// state in DistributedSuit. +//////////////////////////////////////////////////////////////////// +string SuitLeg:: +get_type_name(SuitLeg::Type type) { + switch (type) { + case T_walk_from_street: + return "WalkFromStreet"; + + case T_walk_to_street: + return "WalkToStreet"; + + case T_walk: + return "Walk"; + + case T_from_sky: + return "FromSky"; + + case T_to_sky: + return "ToSky"; + + case T_from_suit_building: + return "FromSuitBuilding"; + + case T_to_suit_building: + return "ToSuitBuilding"; + + case T_to_toon_building: + return "ToToonBuilding"; + + case T_from_coghq: + return "FromCogHQ"; + + case T_to_coghq: + return "ToCogHQ"; + + case T_off: + return "Off"; + } + + return "**invalid**"; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLeg::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SuitLeg:: +output(ostream &out) const { + out << "(" << _type << ", " << _start_time << " (" << _leg_time + << "), " << _zone_id << ", " << _point_a << ", " + << _point_b << ")"; +} + + diff --git a/toontown/src/suit/suitLeg.h b/toontown/src/suit/suitLeg.h new file mode 100644 index 0000000..b7550ed --- /dev/null +++ b/toontown/src/suit/suitLeg.h @@ -0,0 +1,96 @@ +// Filename: suitLeg.h +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SUITLEG_H +#define SUITLEG_H + +#include "toontownbase.h" +#include "dnaSuitPoint.h" +#include "luse.h" + +//////////////////////////////////////////////////////////////////// +// Class : SuitLeg +// Description : This class is used by SuitBase, which is the base for +// both DistributedSuit and DistributedSuitAI, to build +// up a list of legs along the suit's path. +// +// Each leg corresponds to a small segment of the suit's +// path as it walks along the street. Generally, there +// is one leg between each two DNASuitPoints that make +// up the path, with some additional legs at both ends +// to manage the transitions in and out of the world. +// +// The client-side DistributedSuit object uses these +// legs to define intervals to lerp it from place to +// place, while the AI side is mainly concerned about +// setting the zone properly each time. +// +// This class is defined in C++ instead of in Python +// because we have to create a long list of SuitLegs +// every time we encounter a new suit, a process which +// takes substantial time in Python but is negligible in +// C++. +//////////////////////////////////////////////////////////////////// +class EXPCL_TOONTOWN SuitLeg { +PUBLISHED: + // Various types of legs. These leg types also correspond to named + // states in the client-side DistributedSuit fsm. + enum Type { + T_walk_from_street, + T_walk_to_street, + T_walk, + T_from_sky, + T_to_sky, + T_from_suit_building, + T_to_suit_building, + T_to_toon_building, + T_from_coghq, + T_to_coghq, + T_off + }; + +public: + SuitLeg(Type type, double start_time, double leg_time, int zone_id, + int block_number, + const DNASuitPoint *point_a, const DNASuitPoint *point_b); + +PUBLISHED: + INLINE Type get_type() const; + INLINE double get_start_time() const; + INLINE double get_leg_time() const; + INLINE int get_zone_id() const; + INLINE int get_block_number() const; + + INLINE int get_point_a() const; + INLINE int get_point_b() const; + INLINE LPoint3f get_pos_a() const; + INLINE LPoint3f get_pos_b() const; + LPoint3f get_pos_at_time(double time) const; + + static string get_type_name(Type type); + + void output(ostream &out) const; + +private: + Type _type; + double _start_time; + double _leg_time; + int _zone_id; + int _block_number; + int _point_a; + int _point_b; + LPoint3f _pos_a; + LPoint3f _pos_b; + + friend class SuitLegList; +}; + +INLINE ostream &operator << (ostream &out, const SuitLeg &leg); +INLINE ostream &operator << (ostream &out, SuitLeg::Type type); + +#include "suitLeg.I" + +#endif + diff --git a/toontown/src/suit/suitLegList.I b/toontown/src/suit/suitLegList.I new file mode 100644 index 0000000..c48174b --- /dev/null +++ b/toontown/src/suit/suitLegList.I @@ -0,0 +1,98 @@ +// Filename: suitLegList.I +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::Indexing Operator +// Access: Published +// Description: Returns the nth leg of the list. +//////////////////////////////////////////////////////////////////// +INLINE const SuitLeg &SuitLegList:: +operator [] (int n) const { + return get_leg(n); +} + + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_type +// Access: Published +// Description: Returns the type of this leg. Most legs are of type +// T_bellicose, which corresponds to just plain walking +// down the street, but other legs particularly at both +// ends of the path may represent other modes. +//////////////////////////////////////////////////////////////////// +INLINE SuitLeg::Type SuitLegList:: +get_type(int n) const { + return get_leg(n).get_type(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_start_time +// Access: Published +// Description: Returns the time (in seconds elapsed since the +// beginning of the path) at which this leg begins. +//////////////////////////////////////////////////////////////////// +INLINE double SuitLegList:: +get_start_time(int n) const { + return get_leg(n).get_start_time(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_leg_time +// Access: Published +// Description: Returns the total length of time, in seconds, which +// this leg represents. +//////////////////////////////////////////////////////////////////// +INLINE double SuitLegList:: +get_leg_time(int n) const { + return get_leg(n).get_leg_time(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_zone_id +// Access: Published +// Description: Returns the Zone ID associated with this leg. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLegList:: +get_zone_id(int n) const { + return get_leg(n).get_zone_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_block_number +// Access: Published +// Description: Returns the block number associated with this leg, if +// any. Normally this is only relevant for CogHQ door +// type legs, in which it represents the particular door +// index we're going through. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLegList:: +get_block_number(int n) const { + return get_leg(n).get_block_number(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_point_a +// Access: Published +// Description: Returns the first DNASuitPoint associated with this +// leg. In most cases, the leg represents the path +// between point A and point B. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLegList:: +get_point_a(int n) const { + return get_leg(n).get_point_a(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_point_b +// Access: Published +// Description: Returns the first DNASuitPoint associated with this +// leg. In most cases, the leg represents the path +// between point A and point B. +//////////////////////////////////////////////////////////////////// +INLINE int SuitLegList:: +get_point_b(int n) const { + return get_leg(n).get_point_b(); +} diff --git a/toontown/src/suit/suitLegList.cxx b/toontown/src/suit/suitLegList.cxx new file mode 100644 index 0000000..69794c9 --- /dev/null +++ b/toontown/src/suit/suitLegList.cxx @@ -0,0 +1,347 @@ +// Filename: suitLegList.cxx +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + +#include "suitLegList.h" + +#include "dnaStorage.h" +#include "dnaSuitPoint.h" +#include "string_utils.h" + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::Constructor +// Access: Published +// Description: Fills up the list with the SuitLeg objects that +// reflect the indicated DNASuitPath. +// +// The path is just a set of DNA points along the +// street; the legs define the actual timings and states +// for walking between those points. +// +// We define one leg for each pair of points, plus an +// additional leg at the beginning and end of the path +// for transitioning in and out. Finally, there is one +// additional "leg" at the end of the whole sequence, +// which marks the removal of the Suit. +// +// The last five parameters are the lengths of time, in +// seconds, we should allow for each of the +// corresponding transitions. +//////////////////////////////////////////////////////////////////// +SuitLegList:: +SuitLegList(const DNASuitPath *path, const DNAStorage &storage, + double suit_walk_speed, + double from_sky_time, + double to_sky_time, + double from_suit_building_time, + double to_suit_building_time, + double to_toon_building_time) { + nassertv(path->get_num_points() > 0); + + int i = 0; + int pi = path->get_point_index(i); + DNASuitPoint *point = storage.get_suit_point_with_index(pi); + + int next_pi = path->get_point_index(i + 1); + DNASuitPoint *next_point = storage.get_suit_point_with_index(next_pi); + + SuitLeg::Type type = get_first_leg_type(point); + double time = 0.0; + int zone_id = 0; + + double leg_time; + switch (type) { + case SuitLeg::T_from_sky: + leg_time = from_sky_time; + break; + + case SuitLeg::T_from_suit_building: + leg_time = from_suit_building_time; + break; + + default: + leg_time = 0.0; + break; + } + + _legs.clear(); + // We expect to have one leg for each pair of points, plus three extras. + int expected_num_legs = path->get_num_points() - 1 + 3; + _legs.reserve(expected_num_legs); + + // First, record the first leg. This transitions the suit onstage. + _legs.push_back(SuitLeg(type, time, leg_time, zone_id, 0, point, next_point)); + time += leg_time; + + // Now record the subsequent legs. We make one of these for + // each pair of points. + i++; + while (i < path->get_num_points()) { + next_pi = path->get_point_index(i); + next_point = storage.get_suit_point_with_index(next_pi); + zone_id = get_zone_id(storage, pi, next_pi); + + if (point->get_point_type() == DNASuitPoint::COGHQ_OUT_POINT) { + // A special case: if we're about to walk out of a CogHQ door, + // insert a new leg to open the door. + type = SuitLeg::T_from_coghq; + leg_time = from_suit_building_time; + _legs.push_back(SuitLeg(type, time, leg_time, zone_id, + point->get_landmark_building_index(), + point, point)); + time += leg_time; + } + + type = get_next_leg_type(point, next_point); + leg_time = storage.get_suit_edge_travel_time(pi, next_pi, suit_walk_speed); + + _legs.push_back(SuitLeg(type, time, leg_time, zone_id, + 0, point, next_point)); + time += leg_time; + + if (next_point->get_point_type() == DNASuitPoint::COGHQ_IN_POINT) { + // A special case: if we just walked into a CogHQ door, insert a + // new leg to open the door. + type = SuitLeg::T_to_coghq; + leg_time = to_suit_building_time; + _legs.push_back(SuitLeg(type, time, leg_time, zone_id, + next_point->get_landmark_building_index(), + next_point, next_point)); + time += leg_time; + } + + point = next_point; + pi = next_pi; + i++; + } + + // Now record the last leg, to transition the suit offstage. + type = get_last_leg_type(point); + switch (type) { + case SuitLeg::T_to_sky: + leg_time = to_sky_time; + break; + + case SuitLeg::T_to_suit_building: + leg_time = to_suit_building_time; + break; + + case SuitLeg::T_to_toon_building: + leg_time = to_toon_building_time; + break; + + default: + leg_time = 0.0; + } + + // Back up and get the penultimate point again. + pi = path->get_point_index(i - 2); + point = storage.get_suit_point_with_index(pi); + + _legs.push_back(SuitLeg(type, time, leg_time, zone_id, 0, point, next_point)); + + // And one more to remove the suit. + time += leg_time; + _legs.push_back(SuitLeg(SuitLeg::T_off, time, 0.0, zone_id, 0, point, next_point)); + + // Also, extend the zoneId backwards from the 1 element to the + // 0 element. + _legs[0]._zone_id = _legs[1]._zone_id; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +SuitLegList:: +~SuitLegList() { +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_num_legs +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +int SuitLegList:: +get_num_legs() const { + return _legs.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_leg +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +const SuitLeg &SuitLegList:: +get_leg(int n) const { + nassertr(n >= 0 && n < (int)_legs.size(), _legs[0]); + return _legs[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_leg_index_at_time +// Access: Published +// Description: Returns the index of the leg within the list that +// covers the indicated elapsed time from the beginning +// of the path. +// +// start is a hint, the index at which to start +// searching. +//////////////////////////////////////////////////////////////////// +int SuitLegList:: +get_leg_index_at_time(double time, int start) const { + if (start < 0 || start >= (int)_legs.size() || + _legs[start]._start_time > time) { + start = 0; + } + + int i = start; + while (i + 1 < (int)_legs.size() && + _legs[i + 1]._start_time <= time) { + i++; + } + + return i; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::is_point_in_range +// Access: Published +// Description: Returns true if the indicated point lies on this +// path, between times begin and end, or false +// otherwise. This is useful for ensuring two suits +// aren't assigned paths too close to each other. +//////////////////////////////////////////////////////////////////// +bool SuitLegList:: +is_point_in_range(const DNASuitPoint *point, double begin, + double end) const { + int point_index = point->get_index(); + int start_index = get_leg_index_at_time(begin, 0); + int end_index = get_leg_index_at_time(end, start_index); + + for (int i = start_index; i <= end_index; i++) { + nassertr(i >= 0 && i < (int)_legs.size(), false); + const SuitLeg &leg = _legs[i]; + if (leg.get_point_a() == point_index || leg.get_point_b() == point_index) { + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SuitLegList:: +output(ostream &out) const { + out << "SuitLegList, " << _legs.size() << " legs."; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::write +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SuitLegList:: +write(ostream &out) const { + out << "SuitLegList:\n"; + for (size_t i = 0; i < _legs.size(); i++) { + out << " " << i << ". " << _legs[i] << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_first_leg_type +// Access: Private, Static +// Description: Returns the type of the first leg in the path, given +// the indicated first DNASuitPoint. +//////////////////////////////////////////////////////////////////// +SuitLeg::Type SuitLegList:: +get_first_leg_type(const DNASuitPoint *point) { + switch (point->get_point_type()) { + case DNASuitPoint::SIDE_DOOR_POINT: + return SuitLeg::T_from_suit_building; + + default: + return SuitLeg::T_from_sky; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_next_leg_type +// Access: Private, Static +// Description: Returns the type of an intermediate leg in the path, +// given a DNASuitPoint and the one preceding it. +//////////////////////////////////////////////////////////////////// +SuitLeg::Type SuitLegList:: +get_next_leg_type(const DNASuitPoint *prev_point, + const DNASuitPoint *curr_point) { + switch (curr_point->get_point_type()) { + case DNASuitPoint::FRONT_DOOR_POINT: + case DNASuitPoint::SIDE_DOOR_POINT: + return SuitLeg::T_walk_from_street; + + default: + break; + } + + switch (prev_point->get_point_type()) { + case DNASuitPoint::FRONT_DOOR_POINT: + case DNASuitPoint::SIDE_DOOR_POINT: + return SuitLeg::T_walk_to_street; + + default: + break; + } + + return SuitLeg::T_walk; +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_last_leg_type +// Access: Private, Static +// Description: Returns the type of the last leg in the path, given +// the indicated last DNASuitPoint. +//////////////////////////////////////////////////////////////////// +SuitLeg::Type SuitLegList:: +get_last_leg_type(const DNASuitPoint *point) { + switch (point->get_point_type()) { + case DNASuitPoint::FRONT_DOOR_POINT: + return SuitLeg::T_to_toon_building; + + case DNASuitPoint::SIDE_DOOR_POINT: + return SuitLeg::T_to_suit_building; + + default: + return SuitLeg::T_to_sky; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SuitLegList::get_zone_id +// Access: Private, Static +// Description: Returns the Zone ID associated with the edge defined +// by the two suit points. +//////////////////////////////////////////////////////////////////// +int SuitLegList:: +get_zone_id(const DNAStorage &storage, int pi_a, int pi_b) { + string name = storage.get_suit_edge_zone(pi_a, pi_b); + + // Get the part of the name before the first colon, if any. + size_t colon = name.find(':'); + if (colon != string::npos) { + name = name.substr(0, colon); + } + + // That should be just a numeric zone ID. + int zone_id; + bool result = string_to_int(name, zone_id); + nassertr(result, 0); + + return zone_id; +} diff --git a/toontown/src/suit/suitLegList.h b/toontown/src/suit/suitLegList.h new file mode 100644 index 0000000..11e39d2 --- /dev/null +++ b/toontown/src/suit/suitLegList.h @@ -0,0 +1,69 @@ +// Filename: suitLegList.h +// Created by: drose (08Nov01) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SUITLEGLIST_H +#define SUITLEGLIST_H + +#include "toontownbase.h" +#include "suitLeg.h" + +#include "pvector.h" + +class DNASuitPath; +class DNAStorage; +class DNASuitPoint; + +//////////////////////////////////////////////////////////////////// +// Class : SuitLegList +// Description : This is a list of SuitLegs. See SuitLeg for a more +// detailed explanation of its purpose. +//////////////////////////////////////////////////////////////////// +class EXPCL_TOONTOWN SuitLegList { +PUBLISHED: + SuitLegList(const DNASuitPath *path, const DNAStorage &storage, + double suit_walk_speed, double from_sky_time, + double to_sky_time, double from_suit_building_time, + double to_suit_building_time, double to_toon_building_time); + ~SuitLegList(); + + int get_num_legs() const; + const SuitLeg &get_leg(int n) const; + INLINE const SuitLeg &operator[] (int n) const; + + int get_leg_index_at_time(double time, int start) const; + + INLINE SuitLeg::Type get_type(int n) const; + INLINE double get_start_time(int n) const; + INLINE double get_leg_time(int n) const; + INLINE int get_zone_id(int n) const; + INLINE int get_block_number(int n) const; + + INLINE int get_point_a(int n) const; + INLINE int get_point_b(int n) const; + + bool is_point_in_range(const DNASuitPoint *point, double begin, + double end) const; + + void output(ostream &out) const; + void write(ostream &out) const; + +private: + static SuitLeg::Type get_first_leg_type(const DNASuitPoint *point); + static SuitLeg::Type get_next_leg_type(const DNASuitPoint *prev_point, + const DNASuitPoint *curr_point); + static SuitLeg::Type get_last_leg_type(const DNASuitPoint *point); + static int get_zone_id(const DNAStorage &storage, int pi_a, int pi_b); + + + typedef pvector Legs; + Legs _legs; +}; + +INLINE ostream &operator << (ostream &out, const SuitLegList &list); + +#include "suitLegList.I" + +#endif + diff --git a/toontown/src/suit/suit_composite1.cxx b/toontown/src/suit/suit_composite1.cxx new file mode 100644 index 0000000..3187b34 --- /dev/null +++ b/toontown/src/suit/suit_composite1.cxx @@ -0,0 +1,2 @@ +#include "suitLeg.cxx" +#include "suitLegList.cxx" diff --git a/toontown/src/testenv/.cvsignore b/toontown/src/testenv/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/testenv/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/testenv/Configrc b/toontown/src/testenv/Configrc new file mode 100644 index 0000000..fb4c9c1 --- /dev/null +++ b/toontown/src/testenv/Configrc @@ -0,0 +1,88 @@ +language japanese + + +district-id 200000000 +district-name TestTown +district-min-channel 200100000 +district-max-channel 200149999 +msg-director-port 6665 +account-old-auth 1 +server-version dev +game-server https://localhost:6667 + +dc-file ./toon.dc + +# This is a workaround to load Japanese fonts +use-vfs 0 + +audio-loader mp3 +audio-loader midi +audio-loader wav + +win32-mono-cursor toonmono.cur + +load-display pandadx8 +chan-config-sanity-check #f +multipass-viz none +win-width 800 +win-height 600 +fullscreen 0 +sync-video #t +want-magic-words 1 +teleport-all 1 +# We must currently set this to avoid messing up some of +# the suits' faces. +egg-retesselate-coplanar #f + +# Custom ObjectTypes for Toontown. +# "barrier" means a vertical wall, with bitmask 0x01 +# "floor" means a horizontal floor, with bitmask 0x02 +# "camera-collide" means things that the camera should avoid, with bitmask 0x04 +egg-object-type-barrier collide-mask { 0x01 } { Polyset descend } +egg-object-type-trigger collide-mask { 0x01 } { Polyset descend intangible } +egg-object-type-sphere collide-mask { 0x01 } { Sphere descend } +egg-object-type-trigger-sphere collide-mask { 0x01 } { Sphere descend intangible } +egg-object-type-floor collide-mask { 0x02 } { Polyset descend } +egg-object-type-camera-collide collide-mask { 0x04 } { Polyset descend } +egg-object-type-camera-collide-sphere collide-mask { 0x04 } { Sphere descend } +egg-object-type-camera-barrier collide-mask { 0x05 } { Polyset descend } +egg-object-type-camera-barrier-sphere collide-mask { 0x05 } { Sphere descend } + +# The modelers occasionally put { model } instead of +# { 1 }. Let's be accommodating. +egg-object-type-model { 1 } +egg-object-type-dcs { 1 } + +# Define a "shadow" object type, so we can render all shadows in their +# own bin and have them not fight with each other (or with other +# transparent geometry). +egg-object-type-shadow bin { shadow } alpha { blend-no-occlude } +cull-bin shadow 15 unsorted + +# Define a "ground" type, for rendering ground surfaces immediately +# behind the drop shadows. +egg-object-type-ground bin { ground } +egg-object-type-shadow-ground bin { ground } +cull-bin ground 14 unsorted + +load-file-type toontown +window-title Toontown +win32-window-icon phase_3/models/gui/toontown.ico + +aux-display pandadx8 +aux-display pandadx7 +aux-display pandagl +change-display-settings 0 +compress-channels #t +text-encoding utf8 +ssl-certificates certificates.txt +ssl-certificates gameserver.txt +ssl-certificates ttown2.txt +ssl-cipher-list RC4-MD5 +collect-tcp 1 +collect-tcp-interval 0.2 +notify-level-chan warning +notify-level-gobj warning +notify-level-loader warning +notify-timestamp #t +server-failover 80 diff --git a/toontown/src/testenv/DesktopToons.py b/toontown/src/testenv/DesktopToons.py new file mode 100644 index 0000000..56e24fb --- /dev/null +++ b/toontown/src/testenv/DesktopToons.py @@ -0,0 +1,67 @@ +# Go to your estate, then run this code. +# You will want this set in your Configrc so the fake desktop is high-res +# max-texture-dimension 1024 + +# To make Panda run always on bottom +# fullscreen 0 +# # Leave room for the windows task bar (32 pixels high) +# win-size 1600 1168 +# undecorated 1 +# z-order bottom + + +from pandac.PandaModules import * + +background = loader.loadModel('phase_3/models/gui/loading-background').find("**/bg") +background.reparentTo(render) + +# Fake desktop texture +background.setTexture(loader.loadTexture('/c/desktop.jpg'), 1) +background.setColor(1,1,1,1) + +# Plain colored cyan like default Windows desktop +# background.setTextureOff(1) +# background.setColor(0.25,0.5,0.5) + +background.reparentTo(camera) +background.setPosHpr(0,100,0,0,0,0) +background.setScale(40*1.333, 1, 40) + +base.localAvatar.stopLookAround() +base.localAvatar.stopUpdateSmartCamera() +camera.reparentTo(render) +camera.setPosHpr(0,0,12,0,0,0) +base.localAvatar.setPosHpr(0,30,0,0,0,0) +base.localAvatar.useLOD(1000) + +render.find("**/Estate").stash() +render.setFogOff() +taskMgr.remove('estate-check-toon-underwater') +taskMgr.remove('estate-check-cam-underwater') + +base.localAvatar.book.hideButton() +base.localAvatar.laffMeter.stop() +base.localAvatar.setFriendsListButtonActive(0) +NametagGlobals.setMasterNametagsActive(0) +NametagGlobals.setMasterArrowsOn(0) + +from toontown.toon import Toon +from toontown.toon import ToonDNA +import random + +toonList = [] +for i in range(5): + t = Toon.Toon() + d = ToonDNA.ToonDNA() + d.newToonRandom() + t.setDNA(d) + t.loop('neutral') + t.reparentTo(render) + t.setPosHpr((random.random() * 20.0) - 10.0, 40, 0, 150.0 + (random.random()*60), 0, 0) + t.useLOD(1000) + toonList.append(t) + +for i in range(len(toonList)): + t = toonList[i] + t.setPosHpr((i * 5.0) - 10.0, 32 + random.random() * 5.0, -0.25, 120.0 + (random.random()*90), 0, 0) + diff --git a/toontown/src/testenv/IM.py b/toontown/src/testenv/IM.py new file mode 100644 index 0000000..1271f6d --- /dev/null +++ b/toontown/src/testenv/IM.py @@ -0,0 +1,36 @@ + +import toc +from direct.task import Task + +class TTToc(toc.TocTalk): + + def __init__(self): + screenName = base.config.GetString("AIM-screenname", "") + password = base.config.GetString("AIM-password", "") + self.taskName = "TTToc" + toc.TocTalk.__init__(self, screenName, password) + self.connect() + self.startTask() + + def startTask(self): + self._running = 1 + self._socket.setblocking(0) + taskMgr.add(self.taskProc, self.taskName) + + def stopTask(self): + self._running = 0 + taskMgr.remove(self.taskName) + + def taskProc(self, task): + event = self.recv_event() + if event: + print event + self.handle_event(event) + return Task.cont + + def on_IM_IN(self,data): + screenname = data.split(":")[0] + message = self.strip_html(data.split(":",2)[2]) + print screenname, message + localAvatar.setSystemMessage(0, "%s: %s" % (screenname, message)) + diff --git a/toontown/src/testenv/Sources.pp b/toontown/src/testenv/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/testenv/__init__.py b/toontown/src/testenv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/testenv/dayNight.py b/toontown/src/testenv/dayNight.py new file mode 100644 index 0000000..195043c --- /dev/null +++ b/toontown/src/testenv/dayNight.py @@ -0,0 +1,242 @@ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * + +dayMusic = loader.loadMusic("phase_4/audio/bgm/TC_nbrhood.mid") +# dayMusic = loader.loadMusic("phase_8/audio/bgm/TB_nbrhood.mid") +# base.cr.playGame.hood.loader.snow.cleanup() +nightMusic = loader.loadMusic("phase_8/audio/bgm/DL_nbrhood.mid") + +# Load up some sfx +birdSfx1 = loader.loadSfx("phase_8/audio/sfx/SZ_DG_bird_01.mp3") +birdSfx2 = loader.loadSfx("phase_8/audio/sfx/SZ_DG_bird_02.mp3") +birdSfx3 = loader.loadSfx("phase_8/audio/sfx/SZ_DG_bird_03.mp3") +cricket1 = loader.loadSfx("/c/soundelux/Estate_Cricket_1.mp3") +cricket2 = loader.loadSfx("/c/soundelux/Estate_Cricket_2.mp3") +rooster = loader.loadSfx("/c/soundelux/Estate_rooster.mp3") + +# No more tt birds chirping +taskMgr.remove("TT-birds") +# Get rid of the sky that comes with TT central +taskMgr.remove("skyTrack") +base.cr.playGame.hood.sky.hide() +base.cr.playGame.hood.loader.music.stop() + +# Load up our own sky models +nightSky = loader.loadModel("phase_8/models/props/DL_sky") +nightSky.setScale(0.8) +nightSky.setTransparency(1) +nightSky.setBin("background", 102) +daySky = loader.loadModel("phase_3.5/models/props/TT_sky") +daySky.setBin("background", 100) +dayCloud1 = daySky.find("**/cloud1") +dayCloud2 = daySky.find("**/cloud2") +dayCloud1.setBin("background", 101) +dayCloud2.setBin("background", 101) +dawnSky = loader.loadModel("phase_6/models/props/MM_sky") +dawnSky.setScale(0.8) +dawnSky.setTransparency(1) +dawnSky.setBin("background", 102 + +pe = PolylightEffect.make() +brightness = 1.25 +darkness = 0.8 +pe.setWeight(brightness) +base.localAvatar.node().setEffect(pe) + + +for sky in (nightSky, daySky, dawnSky): + sky.reparentTo(camera) + sky.setZ(0.0) + sky.setHpr(0.0, 0.0, 0.0) + ce = CompassEffect.make(NodePath(), CompassEffect.PRot | CompassEffect.PZ) + sky.node().setEffect(ce) + sky.setDepthTest(0) + sky.setDepthWrite(0) + +# Color scale defines +dawnColor = Vec4(1,0.8,0.4,1) +dayColor = Vec4(1,1,1,1) +duskColor = Vec4(0.8,0.4,0.7,1) +nightColor = Vec4(0.3,0.3,0.5,1) +onAlpha = Vec4(1,1,1,1) +offAlpha = Vec4(1,1,1,0) + +# Geom of the hood +geom = base.cr.playGame.hood.loader.geom + +# List of butterflies +butterflies = base.cr.doFindAll("DistributedButterfly") + +# List of lamps and glow discs +oneLights = geom.findAllMatches("**/prop_post_one_light_DNARoot") +threeLights = geom.findAllMatches("**/prop_post_three_light_DNARoot") +lamps = oneLights + threeLights +discs = [] + +# List of NodePaths of PolylightNodes +polylights = [] +lightIndex = 0 + + +for lamp in oneLights: + lamp.setColorScale(1,1,1,1,1) + disc = loader.loadModel("phase_3.5/models/props/glow") + # Add PolylightNodes + lightIndex += 1 + plNode = PolylightNode("pl" + str(lightIndex)) + plNode.setRadius(20) + #plNode.setPos(0,0,2) + plNode.setColor(1.0,0.8,0.4) + plNode.setFlickerType(PolylightNode.FSIN) + plNode.setFreq(6.0) + plNode.setOffset(-0.5) + plNodePath = NodePath(plNode) + polylights.append(plNodePath) + base.localAvatar.node().setEffect(base.localAvatar.node().getEffect(PolylightEffect.getClassType()).addLight(plNodePath)) + + + # A glow around the lamp light bulb + + disc.setBillboardPointEye() + disc.setPos(0.2,-1,10) + disc.setScale(8) + disc.setColorScale(1,1,0.8,0.25,1) + disc.setTransparency(1) + disc.reparentTo(lamp.find("**/p13")) + #disc.node().setEffect(pe) + discs.append(disc) + # A glow on the floor + disc = loader.loadModel("phase_3.5/models/props/glow") + disc.setPos(0,0,0.025) + disc.setHpr(0,90,0) + disc.setScale(14) + disc.setColorScale(1,1,0.8,0.25,1) + disc.setTransparency(1) + disc.reparentTo(lamp.find("**/p13")) + plNodePath.reparentTo(disc) + disc.node().setEffect(pe) + discs.append(disc) + + +for lamp in threeLights: + lamp.setColorScale(1,1,1,1,1) + disc = loader.loadModel("phase_3.5/models/props/glow") + # Add PolylightNodes + lightIndex += 1 + plNode = PolylightNode("pl" + str(lightIndex)) + plNode.setRadius(20) + plNode.setColor(1.0,1.0,1.0) + plNode.setFlickerType(PolylightNode.FRANDOM) + #plNode.setFreq(6.0) + plNode.setOffset(-0.5) + plNode.setScale(0.2) + plNode.setAttenuation(PolylightNode.AQUADRATIC) + plNodePath = NodePath(plNode) + polylights.append(plNodePath) + base.localAvatar.node().setEffect(base.localAvatar.node().getEffect(PolylightEffect.getClassType()).addLight(plNodePath)) + + + disc.setBillboardPointEye() + disc.setPos(0,-1,10) + disc.setScale(10) + disc.setColorScale(1,1,0.8,0.25,1) + disc.setTransparency(1) + disc.reparentTo(lamp.find("**/p23")) + plNodePath.reparentTo(disc) + #disc.node().setEffect(pe) + discs.append(disc) + # A glow on the floor + disc = loader.loadModel("phase_3.5/models/props/glow") + disc.setPos(0,0,0.025) + disc.setHpr(0,90,0) + disc.setScale(14) + disc.setColorScale(1,1,0.8,0.2,1) + disc.setTransparency(1) + disc.reparentTo(lamp.find("**/p23")) + #disc.node().setEffect(pe) + discs.append(disc) + +def makeNight(): + for lamp in lamps: + lamp.setColorScale(1,1,1,1,1) + for disc in discs: + disc.show() + base.playSfx(cricket1, volume=0.3) + dayMusic.stop() + base.playMusic(nightMusic, volume=0.5) + for b in butterflies: + b.butterflyNode.hide() + + +def makeDay(): + for lamp in lamps: + lamp.clearColorScale() + for disc in discs: + disc.hide() + base.playSfx(rooster, volume=0.2) + nightMusic.stop() + base.playMusic(dayMusic, volume=0.7) + for b in butterflies: + b.butterflyNode.show() + +def lerpDaySkyFunc(color): + daySky.setColorScale(color, 1) + +def lerpDawnSkyFunc(color): + dawnSky.setColorScale(color, 1) + +def lerpNightSkyFunc(color): + nightSky.setColorScale(color, 1) + +def lerpLightWeightFunc(weight): + base.localAvatar.node().setEffect(base.localAvatar.node().getEffect(PolylightEffect.getClassType()).setWeight(weight)) + +# Change this to change the day/night cycle length +t = 120.0 +tSeg = t / 10.0 +dayMusic.stop() +nightMusic.stop() +nightSky.setColorScale(onAlpha) +daySky.setColorScale(offAlpha) +dawnSky.setColorScale(offAlpha) +render.setColorScale(nightColor) +i = Parallel(Sequence(Parallel(LerpColorScaleInterval(render, tSeg, dawnColor), + LerpFunctionInterval(lerpLightWeightFunc, duration=tSeg, toData=darkness, fromData=brightness), + LerpFunctionInterval(lerpNightSkyFunc, duration=tSeg, toData=offAlpha, fromData=onAlpha), + LerpFunctionInterval(lerpDawnSkyFunc, duration=tSeg, toData=onAlpha, fromData=offAlpha), + ), + Func(makeDay), + Wait(tSeg), + Parallel(LerpFunctionInterval(lerpDawnSkyFunc, duration=tSeg, toData=offAlpha, fromData=onAlpha), + LerpFunctionInterval(lerpDaySkyFunc, duration=tSeg, toData=dayColor, fromData=offAlpha), + LerpColorScaleInterval(render, tSeg, dayColor), + ), + Func(base.playSfx, birdSfx1, 0, 1, 0.3), + Wait(tSeg), + Func(base.playSfx, birdSfx2, 0, 1, 0.3), + Parallel(LerpFunctionInterval(lerpDaySkyFunc, duration=tSeg, toData=duskColor, fromData=dayColor), + LerpColorScaleInterval(render, tSeg, duskColor), + LerpFunctionInterval(lerpLightWeightFunc, duration=tSeg, toData=brightness, fromData=darkness), + ), + Func(makeNight), + Parallel(LerpFunctionInterval(lerpDaySkyFunc, duration=tSeg, toData=offAlpha, fromData=duskColor), + LerpFunctionInterval(lerpNightSkyFunc, duration=tSeg, toData=onAlpha, fromData=offAlpha), + LerpColorScaleInterval(render, tSeg, nightColor), + ), + Func(base.playSfx, cricket2, 0, 1, 0.2), + Wait(tSeg), + Func(base.playSfx, cricket1, 0, 1, 0.2), + Wait(tSeg), + ), + ) +i.loop() + + + +""" +# To undo +i.finish() +render.clearColorScale() +dayMusic.stop() +nightMusic.stop() +""" diff --git a/toontown/src/testenv/instructions.doc b/toontown/src/testenv/instructions.doc new file mode 100644 index 0000000..9b0ea3d Binary files /dev/null and b/toontown/src/testenv/instructions.doc differ diff --git a/toontown/src/testenv/printfiles b/toontown/src/testenv/printfiles new file mode 100644 index 0000000..caf560e --- /dev/null +++ b/toontown/src/testenv/printfiles @@ -0,0 +1,162 @@ +#! /bin/sh + +# Python files +echo $WINTOOLS/bin/python.exe +echo $WINTOOLS/sdk/python/Python-2.2.2/PCbuild/python22.dll +echo $WINTOOLS/sdk/python/Python-2.2.2/PCbuild/_sre.pyd +echo $WINTOOLS/sdk/python/Python-2.2.2/PCbuild/_tkinter.pyd +echo $WINTOOLS/sdk/python/Python-2.2.2/PCbuild/_socket.pyd +echo $WINTOOLS/sdk/python/Python-2.2.2/PCbuild/zlib.pyd + +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/linecache.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/traceback.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/__future__.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/whrandom.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/copy_reg.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/copy.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/fnmatch.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/re.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/sre.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/sre_parse.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/sre_constants.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/sre_compile.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/types.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/string.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/os.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/site.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/stat.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/UserDict.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/ntpath.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/random.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/getopt.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/fpformat.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/bisect.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/warnings.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/inspect.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/dis.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/tokenize.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/token.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/ihooks.py +echo $WINTOOLS/sdk/python/Python-2.2.2/Lib/calendar.py + +# Libraries +echo $WINTOOLS/lib +echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mss32.dll +echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mp3dec.asi +echo $WINTOOLS/lib/nspr4.dll +echo $DTOOL/lib/libdtool.dll +echo $DTOOL/lib/libdtoolconfig.dll +echo $PANDA/lib/libpandaexpress.dll +echo $PANDA/lib/libpanda.dll +echo $PANDA/lib/libpandaphysics.dll +echo $PANDA/lib/libwindisplay.dll +echo $PANDA/lib/libpandadx7.dll +echo $PANDA/lib/libpandadx8.dll +echo $PANDA/lib/libpandagl.dll +echo $PANDA/lib/libmiles_audio.dll +echo $DIRECT/lib/libdirect.dll +echo $TOONTOWN/lib/libtoontown.dll + +# Generated code for libpandaexpress +printlib $DIRECT/lib/py libpandaexpress + +# DIRECT stuff + +printdir $DIRECT/src/actor .py +printdir $DIRECT/src/cluster .py +printdir $DIRECT/src/directbase .py +printdir $DIRECT/src/directdevices .py +printdir $DIRECT/src/directnotify .py +printdir $DIRECT/src/directscripts .py +printdir $DIRECT/src/directtools .py +printdir $DIRECT/src/directutil .py +printdir $DIRECT/src/distributed .py +printdir $DIRECT/src/ffi/ .py +printdir $DIRECT/src/fsm .py +printdir $DIRECT/src/gui .py +printdir $DIRECT/src/interval .py +printdir $DIRECT/src/level .py +printdir $DIRECT/src/leveleditor .py +printdir $DIRECT/src/particles .py +printdir $DIRECT/src/showbase .py +printdir $DIRECT/src/showutil .py +printdir $DIRECT/src/task .py +printdir $DIRECT/src/showbase .py +printdir $DIRECT/src/tkpanels .py +printdir $DIRECT/src/tkwidgets .py + +# Batch file for running AI and Client with proper paths +echo $TOONTOWN/src/testenv/runAI.bat +echo $TOONTOWN/src/testenv/runClient.bat + +# Configrc built special for this test env +echo $TOONTOWN/src/testenv/Configrc + +# Etc files +echo $TOONTOWN/src/configfiles/toon.dc +echo $TOONTOWN/src/configfiles/download.par + +# Generated code for libpanda +printlib $DIRECT/lib/py libpanda +printlib $DIRECT/lib/py libpandaphysics +echo $DIRECT/lib/py/PandaModules.py + +# Generated code for libdirect +printlib $DIRECT/lib/py libdirect + +# Generated code for libtoontown +printlib $DIRECT/lib/py libtoontown + +# code +printdir $TOONTOWN/src/ai .py +printdir $TOONTOWN/src/avatar .py +printdir $TOONTOWN/src/battle .py +printdir $TOONTOWN/src/building .py +printdir $TOONTOWN/src/catalog .py +printdir $TOONTOWN/src/char .py +printdir $TOONTOWN/src/chat .py +printdir $TOONTOWN/src/classicchars .py +printdir $TOONTOWN/src/coghq .py +printdir $TOONTOWN/src/distributed .py +printdir $TOONTOWN/src/effects .py +printdir $TOONTOWN/src/estate .py +printdir $TOONTOWN/src/fishing .py +printdir $TOONTOWN/src/friends .py +printdir $TOONTOWN/src/hood .py +printdir $TOONTOWN/src/launcher .py +printdir $TOONTOWN/src/level .py +printdir $TOONTOWN/src/login .py +printdir $TOONTOWN/src/makeatoon .py +printdir $TOONTOWN/src/minigame .py +printdir $TOONTOWN/src/quest .py +printdir $TOONTOWN/src/safezone .py +printdir $TOONTOWN/src/shtiker .py +printdir $TOONTOWN/src/speedchat .py +printdir $TOONTOWN/src/suit .py +printdir $TOONTOWN/src/toon .py +printdir $TOONTOWN/src/toonbase .py +printdir $TOONTOWN/src/toontowngui .py +printdir $TOONTOWN/src/town .py +printdir $TOONTOWN/src/trolley .py +printdir $TOONTOWN/src/tutorial .py + +printdir $TOONTOWN/src/battle .ptf +printdir $TOONTOWN/src/safezone .ptf + +# Etc files +echo $PANDA/etc/layout_db +echo $PANDA/etc/setup_db +echo $PANDA/etc/window_db +echo $TOONTOWN/src/configfiles/certificates.txt +echo $TOONTOWN/src/configfiles/gameserver.txt +echo $TOONTOWN/src/configfiles/ttown2.txt +echo $TOONTOWN/src/configfiles/NameMasterCastillian.txt +echo $TOONTOWN/src/configfiles/NameMasterEnglish.txt +echo $TOONTOWN/src/quest/QuestScripts.txt + +# DNA files +echo $TTMODELS/phase_*/dna/*.dna + +echo $TTMODELS/phase_* +echo $DMODELS/models/ +echo $DMODELS/maps/ diff --git a/toontown/src/testenv/runAI.bat b/toontown/src/testenv/runAI.bat new file mode 100644 index 0000000..a0725c9 --- /dev/null +++ b/toontown/src/testenv/runAI.bat @@ -0,0 +1,14 @@ +cd toonsrv +call STOPALL.BAT +call UNINSTALLALL.BAT +copy ..\toon.dc . +copy ..\*.dna . +copy ..\NameMaster* . +call INSTALLALL.BAT +call STARTALL.BAT +cd .. +set PATH=lib +set PYTHONPATH=.;lib-tk +set TTMODELS=. +set DMODELS=. +python.exe AIStart.py diff --git a/toontown/src/testenv/runClient.bat b/toontown/src/testenv/runClient.bat new file mode 100644 index 0000000..fb65d15 --- /dev/null +++ b/toontown/src/testenv/runClient.bat @@ -0,0 +1,6 @@ +set PATH=.;lib +set PYTHONPATH=.;lib-tk +set ETCPATH=. +set TTMODELS=. +set DMODELS=. +python.exe ToontownStart.py diff --git a/toontown/src/testenv/toc.py b/toontown/src/testenv/toc.py new file mode 100644 index 0000000..1f7ec6b --- /dev/null +++ b/toontown/src/testenv/toc.py @@ -0,0 +1,426 @@ +############################################ +# Py-TOC 2.4 +# +# Jamie Turner +# + +_VERSION = "2.4" + +import socket +# import select +import re +import struct +import random +import sys +import time + +import thread +import threading + + +TOC_SERV_AUTH = ("login.oscar.aol.com", 5159 ) +TOC_SERV = ( "toc.oscar.aol.com", 9898 ) + +class TOCError(Exception): + pass + +class TOCDisconnectError(Exception): + pass + +class TocTalk: + def __init__(self,nick,passwd): + self._nick = nick + self._passwd = passwd + self._agent = "PY-TOC" + self._info = "I'm running the Python TOC Module by Jamie Turner " + self._seq = random.randint(0,65535) + self._logfd = sys.stdout + self._debug = 1 + self._running = 0 + self._ignore = 0 + self._tsem = threading.Semaphore() + self.build_funcs() + + + def build_funcs(self): + self._dir = [] + for item in dir(self.__class__): + if ( type( eval("self.%s" % item)) == type(self.__init__) and + item[:3] == "on_" ): + self._dir.append(item) + + + def go(self): + self.connect() + self._running = 1 + self.process_loop() + + def start(self): + pass + + def connect(self): + #create the socket object + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except: + raise TOCError, "FATAL: Couldn't create a socket" + + # make the connection + try: + self._socket.connect( TOC_SERV ) + except: + raise TOCDisconnectError, "FATAL: Could not connect to TOC Server" + + buf = "FLAPON\r\n\r\n" + bsent = self._socket.send(buf) + + if bsent <> len(buf): + raise TOCError, "FATAL: Couldn't send FLAPON!" + + def start_log_in(self): + ep = self.pwdenc() + self._normnick = self.normalize(self._nick) + msg = struct.pack("!HHHH",0,1,1,len(self._normnick)) + self._normnick + self.flap_to_toc(1,msg) + + #now, login + self.flap_to_toc(2,"toc_signon %s %s %s %s english %s" % ( + TOC_SERV_AUTH[0],TOC_SERV_AUTH[1],self._normnick,ep,self.encode(self._agent) ) ) + + + def normalize(self,data): + return re.sub("[^A-Za-z0-9]","",data).lower() + + def encode(self,data): + for letter in "\\(){}[]$\"": + data = data.replace(letter,"\\%s"%letter) + return '"' + data + '"' + + def flap_to_toc(self,ftype,msg): + if ftype == 2: + msg = msg + struct.pack("!B", 0) + ditems = [] + ditems.append("*") + ditems.append(struct.pack("!BHH",ftype,self._seq,len(msg))) + ditems.append(msg) + + + data = "".join(ditems) + + if len(data) >= 2048: + raise TOCError, "TOC data with protocol overhead cannot exceed 2048 bytes." + + self.derror( "SEND : \'%r\'" % data ) + + + # in case we're threading + self._tsem.acquire() + bsent = self._socket.send(data) + self._tsem.release() + + + if bsent <> len(data): + #maybe make less severe later + # I've never seen this happen.. have you?? + raise TOCError, "FATAL: Couldn't send all data to TOC Server\n" + + self._seq = self._seq + 1 + + def pwdenc(self): + lookup = "Tic/Toc" + ept = [] + + x = 0 + for letter in self._passwd: + ept.append("%02x" % ( ord(letter) ^ ord( lookup[x % 7]) ) ) + x = x + 1 + return "0x" + "".join(ept) + + def process_loop(self): + # the "main" loop + while 1: + event = self.recv_event() + if not event: + continue + self.handle_event(event) + + def handle_event(self,event): + + self.derror( "RECV : %r" % event[1] ) + + #else, fig out what to do with it + #special case-- login + if event[0] == 1: + self.start_log_in() + return + + if not event[1].count(":"): + data = "" + id = "NOID" + + else: + ind = event[1].find(":") + id = event[1][:ind].upper() + data = event[1][ind+1:] + + #handle manually now + if id == "SIGN_ON": + self.c_SIGN_ON(id,data) + return + + if id == "ERROR": + self.c_ERROR(id,data) + return + + #their imp + if ("on_%s" % id ) in self._dir: + exec ( "self.on_%s(data)" % id ) + + else: + self.werror("INFO : Received unimplemented '%s' id" % id) + + def recv_event(self): + + # TODO: this is a non-blocking hack. + # what if the first 6 bytes are there but nothing else? + + try: + header = self._socket.recv(6) + except: + return + + if header == "": + self.err_disconnect() + return + + (marker,mtype,seq,buflen) = struct.unpack("!sBhh",header) + + #get the info + dtemp = self._socket.recv(buflen) + data = dtemp + while len(data) != buflen: + if dtemp == "": + self.err_disconnect() + return + dtemp = self._socket.recv(buflen - len(data)) + data = data + dtemp + + return (mtype, data) + + def thread_recv_events(self): + while self._running: + rfd,dc,dc = select.select([self._socket],[],[]) + if rfd == []: + continue + try: + header = self._socket.recv(6) + except: + self.err_disconnect() + + if header == "": + self.err_disconnect() + + (marker,mtype,seq,buflen) = struct.unpack("!sBhh",header) + + #get the info + dtemp = self._socket.recv(buflen) + data = dtemp + while len(data) != buflen: + if dtemp == "": + self.err_disconnect() + dtemp = self._socket.recv(buflen - len(data)) + data = data + dtemp + + if not self._ignore: + self.handle_event([mtype,data]) + + def err_disconnect(self): + self.werror( "INFO: Disconnected!\n" ) + raise TOCDisconnectError, "FATAL: We seem to have been disconnected from the TOC server.\n" + + # our event handling + def c_ERROR(self,id,data): + # let's just grab the errors we care about! + + #still more fields + if data.count(":"): + dt = int (data[:data.find(":")]) + else: + dt = int(data) # let's get an int outta it + + if dt == 980: + raise TOCError, "FATAL: Couldn't sign on; Incorrect nickname/password combination" + + elif dt == 981: + raise TOCError, "FATAL: Couldn't sign on; The AIM service is temporarily unavailable" + + elif dt == 982: + raise TOCError, "FATAL: Couldn't sign on; Your warning level is too high" + + elif dt == 983: + raise TOCError, "FATAL: Couldn't sign on; You have been connecting and disconnecting too frequently" + + elif dt == 989: + raise TOCError, "FATAL: Couldn't sign on; An unknown error occurred" + + # ... etc etc etc + else: + # try to let further implementation handle it + if ("on_%s" % id ) in self._dir: + exec ( "self.on_%s(data)" % id ) + else: + self.werror("ERROR: The TOC server sent an unhandled error string: %s" % data) + + def c_SIGN_ON(self,type,data): + self.flap_to_toc(2,"toc_add_buddy %s" % self.normalize(self._nick)) # needs to start up corectly + self.flap_to_toc(2,"toc_set_info %s" % self.encode(self._info) ) + self.flap_to_toc(2,"toc_init_done") + self.start() + + def strip_html(self,data): + return re.sub("<[^>]*>","",data) + + def normbuds(self,buddies): + nbuds = [] + for buddy in buddies: + nbuds.append(self.normalize(buddy)) + return " ".join(nbuds) + + #actions--help the user w/common tasks + + #the all-important + def do_SEND_IM(self,user,message,autoaway=0): + sendmessage = "toc_send_im %s %s" % ( self.normalize(user), self.encode(message) ) + if autoaway: + sendmessage = sendmessage + " auto" + self.flap_to_toc(2, sendmessage) + + def do_ADD_BUDDY(self,buddies): + self.flap_to_toc(2,"toc_add_buddy %s" % self.normbuds(buddies) ) + + def do_ADD_PERMIT(self,buddies): + self.flap_to_toc(2,"toc_add_permit %s" % self.normbuds(buddies) ) + + def do_ADD_DENY(self,buddies): + self.flap_to_toc(2,"toc_add_deny %s" % self.normbuds(buddies) ) + + def do_REMOVE_BUDDY(self,buddies): + self.flap_to_toc(2,"toc_remove_buddy %s" % self.normbuds(buddies) ) + + # away, idle, user info handling + def do_SET_IDLE(self,itime): + self.flap_to_toc(2,"toc_set_idle %d" % itime ) + + def do_SET_AWAY(self,awaymess): + if awaymess == "": + self.flap_to_toc(2,"toc_set_away") + return + + self.flap_to_toc(2,"toc_set_away %s" % self.encode(awaymess) ) + + def do_GET_INFO(self,user): + self.flap_to_toc(2,"toc_get_info %s" % self.normalize(user) ) + + def do_SET_INFO(self,info): + self.flap_to_toc(2,"toc_set_info %s" % self.encode(info) ) + + # warning capability + def do_EVIL(self,user,anon=0): + if anon: + acode = "anon" + else: + acode = "norm" + + self.flap_to_toc(2,"toc_evil %s %s" % (self.normalize(user), acode) ) + + #chat + def do_CHAT_INVITE(self,room,imess,buddies): + self.flap_to_toc(2,"toc_chat_invite %s %s %s" % (self.normalize(room), + self.encode(imess), self.normbuds(buddies) ) ) + + def do_CHAT_ACCEPT(self, id): + self.flap_to_toc(2,"toc_chat_accept %s" % id) + + def do_CHAT_LEAVE(self,id): + self.flap_to_toc(2,"toc_chat_leave %s" % id) + + def do_CHAT_WHISPER(self,room,user,message): + self.flap_to_toc(2,"toc_chat_whisper %s %s %s" % (room, + self.normalize(user), self.encode(message) ) ) + + def do_CHAT_SEND(self,room,message): + self.flap_to_toc(2,"toc_chat_send %s %s" % (room, + self.encode(message) ) ) + + def do_CHAT_JOIN(self,roomname): + self.flap_to_toc(2,"toc_chat_join 4 %s" % roomname) + + def do_SET_CONFIG(self,configstr): + self.flap_to_toc(2,"toc_set_config \"%s\"" % configstr) + + # error funcs + def werror(self,errorstr): + if self._debug: + self._logfd.write("(%s) %s\n"% (self._nick,errorstr)) + + def derror(self,errorstr): + if self._debug > 1: + self._logfd.write("(%s) %s\n"% (self._nick,errorstr)) + +class BotManagerError(Exception): + pass + +class BotManager: + def __init__(self): + self.bots = {} + + def addBot(self,bot,botref,go=1,reconnect=1,delay=30): + if self.bots.has_key(botref): + raise BotManagerError, "That botref is already registered" + + self.bots[botref] = bot + self.bots[botref]._reconnect = reconnect + self.bots[botref]._delay = delay + if go: + self.botGo(botref) + + def botGo(self,botref): + if not self.bots.has_key(botref): + raise BotManagerError, "That botref has not been registered" + thread.start_new_thread(self._dispatcher,(self.bots[botref],)) + + def botStop(self,botref): + if not self.bots.has_key(botref): + raise BotManagerError, "That botref has not been registered" + self.bots[botref]._running = 0 + self.bots[botref]._socket.close() + + def botPause(self,botref,val=1): + if not self.bots.has_key(botref): + raise BotManagerError, "That botref has not been registered" + self.bots[botref]._ignore = val + + def getBot(self,botref): + if not self.bots.has_key(botref): + raise BotManagerError, "That botref has not been registered" + return self.bots[botref] + + + def _dispatcher(self,bot): + while 1: + try: + bot.connect() + bot._running = 1 + bot.thread_recv_events() + except TOCDisconnectError: + if not bot._reconnect or not bot._running: + break + bot._running = 0 + time.sleep(bot._delay) # then we reconnect + else: + break + thread.exit() + + def wait(self): + while 1: + time.sleep(2000000) # not coming back from this... diff --git a/toontown/src/toon/.cvsignore b/toontown/src/toon/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/toon/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/toon/AvatarPanelBase.py b/toontown/src/toon/AvatarPanelBase.py new file mode 100644 index 0000000..79b7458 --- /dev/null +++ b/toontown/src/toon/AvatarPanelBase.py @@ -0,0 +1,502 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from direct.showbase import DirectObject +from otp.avatar import AvatarPanel +from toontown.toonbase import TTLocalizer +from toontown.toontowngui import TTDialog +from otp.distributed import CentralLogger + +IGNORE_SCALE = 0.06 +STOP_IGNORE_SCALE = 0.04 + +class AvatarPanelBase(AvatarPanel.AvatarPanel): + """ + Base class to hold features in common between ToonPanel and PlayerPanel + """ + + def __init__(self, avatar, FriendsListPanel = None): + self.dialog = None + self.category = None + AvatarPanel.AvatarPanel.__init__(self, avatar, FriendsListPanel) + + def getIgnoreButtonInfo(self): + # determine how the ignore button should look and work based on the target avatar's current status + if base.cr.avatarFriendsManager.checkIgnored(self.avId): + return (TTLocalizer.AvatarPanelStopIgnoring, self.handleStopIgnoring, STOP_IGNORE_SCALE) + else: + return (TTLocalizer.AvatarPanelIgnore, self.handleIgnore, IGNORE_SCALE) + + def handleIgnore(self): + isAvatarFriend = base.cr.isFriend(self.avatar.doId) + isPlayerFriend = base.cr.playerFriendsManager.isAvatarOwnerPlayerFriend(self.avatar.doId) + + isFriend = isAvatarFriend or isPlayerFriend + + + if isFriend: + # tell the player they can't ignore a friend + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.CancelOnly, + text = TTLocalizer.IgnorePanelAddFriendAvatar % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + #okButtonText = TTLocalizer.AvatarPanelIgnoreCant, + cancelButtonText = TTLocalizer.lCancel, + doneEvent = "IgnoreBlocked", + command = self.freeLocalAvatar, + ) + else: + + # put up an ignore confirmation dialog + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.TwoChoice, + text = TTLocalizer.IgnorePanelAddIgnore % self.avName, + text_wordwrap = 18.5, + text_scale = TTLocalizer.APBignorePanelAddIgnoreTextScale, + okButtonText = TTLocalizer.AvatarPanelIgnore, + cancelButtonText = TTLocalizer.lCancel, + doneEvent = "IgnoreConfirm", + command = self.handleIgnoreConfirm, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + #pos = (0, 0, 0.15), + pos = (0, TTLocalizer.APBignorePanelTitlePosY, 0.125), + text = TTLocalizer.IgnorePanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleStopIgnoring(self): + # put up a stop ignoring confirmation dialog + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.TwoChoice, + text = TTLocalizer.IgnorePanelRemoveIgnore % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + okButtonText = TTLocalizer.AvatarPanelStopIgnoring, + cancelButtonText = TTLocalizer.lCancel, + buttonPadSF = 4.0, + doneEvent = "StopIgnoringConfirm", + command = self.handleStopIgnoringConfirm, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, TTLocalizer.APBignorePanelTitlePosY, 0.15), + text = TTLocalizer.IgnorePanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleIgnoreConfirm(self, value): + if value == -1: + self.freeLocalAvatar() + return + + # ignore target avId + base.cr.avatarFriendsManager.addIgnore(self.avId) + + # notify user they are now ignoring avId + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.Acknowledge, + text = TTLocalizer.IgnorePanelIgnore % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.1, + doneEvent = "IgnoreComplete", + command = self.handleDoneIgnoring, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, TTLocalizer.APBignorePanelTitlePosY, 0.15), + text = TTLocalizer.IgnorePanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleStopIgnoringConfirm(self, value): + if value == -1: + self.freeLocalAvatar() + return + + # ignore target avId + base.cr.avatarFriendsManager.removeIgnore(self.avId) + + # notify user they are now ignoring avId + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.Acknowledge, + text = TTLocalizer.IgnorePanelEndIgnore % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.1, + doneEvent = "StopIgnoringComplete", + command = self.handleDoneIgnoring, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, TTLocalizer.APBignorePanelTitlePosY, 0.15), + text = TTLocalizer.IgnorePanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleDoneIgnoring(self, value): + self.freeLocalAvatar() + + def handleReport(self): + if base.cr.centralLogger.hasReportedPlayer(self.playerId, self.avId): + self.alreadyReported() + else: + self.confirmReport() + + def confirmReport(self): + # determine if we are friends already + if base.cr.isFriend(self.avId) or base.cr.playerFriendsManager.isPlayerFriend(self.avId): + string = TTLocalizer.ReportPanelBodyFriends + titlePos = 0.410 + else: + string = TTLocalizer.ReportPanelBody + titlePos = 0.350 + + # put up a confirmation dialog + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.TwoChoice, + text = string % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + okButtonText = TTLocalizer.AvatarPanelReport, + cancelButtonText = TTLocalizer.lCancel, + doneEvent = "ReportConfirm", + command = self.handleReportConfirm, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, titlePos), + text = TTLocalizer.ReportPanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleReportConfirm(self, value): + self.cleanupDialog() + if value == 1: + self.chooseReportCategory() + else: + self.requestWalk() + + def alreadyReported(self): + # already reported, notify user + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.Acknowledge, + text = TTLocalizer.ReportPanelAlreadyReported % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.1, + doneEvent = "AlreadyReported", + command = self.handleAlreadyReported, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, 0.2), + text = TTLocalizer.ReportPanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleAlreadyReported(self, value): + self.freeLocalAvatar() + + def chooseReportCategory(self): + # put up a confirmation dialog - need to make a custom one for buttons + self.dialog = TTDialog.TTGlobalDialog( + pos = (0, 0, 0.2), + style = TTDialog.CancelOnly, + text = TTLocalizer.ReportPanelCategoryBody % (self.avName, self.avName), + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.05, + midPad = 0.65, + cancelButtonText = TTLocalizer.lCancel, + doneEvent = "ReportCategory", + command = self.handleReportCategory, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, 0.225), + text = TTLocalizer.ReportPanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + # Foul Language + DirectButton( + parent = self.dialog, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (2.125, 1.0, 1.0), + text = TTLocalizer.ReportPanelCategoryLanguage, + text_scale = 0.06, + text_pos = (0, -0.0124), + pos = (0, 0, -0.3), + command = self.handleReportCategory, + extraArgs = [0], + ) + + # Personal Info + DirectButton( + parent = self.dialog, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (2.25, 1.0, 1.0), + text = TTLocalizer.ReportPanelCategoryPii, + text_scale = 0.06, + text_pos = (0, -0.0125), + pos = (0, 0, -0.425), + command = self.handleReportCategory, + extraArgs = [1], + ) + + # Rude Behavior + DirectButton( + parent = self.dialog, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (2.125, 1.0, 1.0), + text = TTLocalizer.ReportPanelCategoryRude, + text_scale = 0.06, + text_pos = (0, -0.0125), + pos = (0, 0, -0.55), + command = self.handleReportCategory, + extraArgs = [2], + ) + + # Bad Name + DirectButton( + parent = self.dialog, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = (2.125, 1.0, 1.0), + text = TTLocalizer.ReportPanelCategoryName, + text_scale = 0.06, + text_pos = (0, -0.0125), + pos = (0, 0, -0.675), + command = self.handleReportCategory, + extraArgs = [3], + ) + + guiButton.removeNode() + self.dialog.show() + self.__acceptStoppedStateMsg() + self.requestStopped() + + def handleReportCategory(self, value): + self.cleanupDialog() + if value >= 0: + # map into central logger tokens + cat = [ + CentralLogger.ReportFoulLanguage, + CentralLogger.ReportPersonalInfo, + CentralLogger.ReportRudeBehavior, + CentralLogger.ReportBadName, + ] + self.category = cat[value] + self.confirmReportCategory(value) + else: + self.requestWalk() + + def confirmReportCategory(self, category): + string = TTLocalizer.ReportPanelConfirmations[category] + string += "\n\n" + TTLocalizer.ReportPanelWarning + # put up a confirmation category dialog + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.TwoChoice, + text = string % self.avName, + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.1, + okButtonText = TTLocalizer.AvatarPanelReport, + cancelButtonText = TTLocalizer.lCancel, + doneEvent = "ReportConfirmCategory", + command = self.handleReportCategoryConfirm, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, 0.5), + text = TTLocalizer.ReportPanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + self.dialog.show() + self.__acceptStoppedStateMsg() + + def handleReportCategoryConfirm(self, value): + self.cleanupDialog() + removed = 0 + isPlayer = 0 + if value > 0: + # log the chat records + base.cr.centralLogger.reportPlayer(self.category, self.playerId, self.avId) + + # if we are avatar friends, break the friendship + if base.cr.isFriend(self.avId): + base.cr.removeFriend(self.avId) + removed = 1 + + # if we are player friends, break the friendship + if base.cr.playerFriendsManager.isPlayerFriend(self.playerId): + if self.playerId: + base.cr.playerFriendsManager.sendRequestRemove(self.playerId) + removed = 1 + isPlayer = 1 + + # TODO: session-based ignore + self.reportComplete(removed, isPlayer) + else: + self.requestWalk() + + def reportComplete(self, removed, isPlayer): + # Notify user if we have removed a friend + string = TTLocalizer.ReportPanelThanks + titlePos = 0.25 + if removed: + if isPlayer: + string += " " + TTLocalizer.ReportPanelRemovedPlayerFriend % self.playerId + else: + string += " " + TTLocalizer.ReportPanelRemovedFriend % self.avName + + titlePos = 0.3 + + # put up a confirmation category dialog + self.dialog = TTDialog.TTGlobalDialog( + style = TTDialog.Acknowledge, + text = string, + text_wordwrap = 18.5, + text_scale = 0.06, + topPad = 0.1, + doneEvent = "ReportComplete", + command = self.handleReportComplete, + ) + + # Title + DirectLabel( + parent = self.dialog, + relief = None, + pos = (0, 0, titlePos), + text = TTLocalizer.ReportPanelTitle, + textMayChange = 0, + text_scale = 0.08, + ) + + # TODO: notify user we are ignoring + + self.dialog.show() + self.__acceptStoppedStateMsg() + + def handleReportComplete(self, value): + self.freeLocalAvatar() + + def freeLocalAvatar(self, value = None): + self.cleanupDialog() + self.requestWalk() + + def cleanupDialog(self): + if self.dialog: + self.dialog.ignore("exitingStoppedState") + self.dialog.cleanup() + self.dialog = None + + def requestStopped(self): + """Safely go to the stopped state for the place.""" + #Make sure we aren't in the stickerBook state or else we can get into a bad state where the player gets stuck + if not base.cr.playGame.getPlace().fsm.getCurrentState().getName() == "stickerBook": + if base.cr.playGame.getPlace().fsm.hasStateNamed('stopped'): + base.cr.playGame.getPlace().fsm.request('stopped') + else: + self.notify.warning('skipping request to stopped in %s' % + base.cr.playGame.getPlace()) + else: + self.cleanup() + + def requestWalk(self): + """Safely go to the walked state for the place.""" + if base.cr.playGame.getPlace().fsm.hasStateNamed('finalBattle'): + # if we go to walk, toons can teleport out in the middle + # of a boss battle + base.cr.playGame.getPlace().fsm.request('finalBattle') + elif base.cr.playGame.getPlace().fsm.hasStateNamed('walk'): + # Go to the walk state only if you were in the stopped state. + if (base.cr.playGame.getPlace().getState() == 'stopped'): + base.cr.playGame.getPlace().fsm.request('walk') + else: + self.notify.warning('skipping request to walk in %s' % + base.cr.playGame.getPlace()) + + def __acceptStoppedStateMsg(self): + # The player might be able to exit the stop state either through some other + # panel or if his boarding party leader boards the elevator. Close any other + # panels like report or ignore if the toon moves out of the stopped state. + self.dialog.ignore("exitingStoppedState") + self.dialog.accept("exitingStoppedState", self.cleanupDialog) \ No newline at end of file diff --git a/toontown/src/toon/BoardingGroupInviterPanels.py b/toontown/src/toon/BoardingGroupInviterPanels.py new file mode 100644 index 0000000..edd3c6e --- /dev/null +++ b/toontown/src/toon/BoardingGroupInviterPanels.py @@ -0,0 +1,234 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from toontown.toontowngui import TTDialog +from otp.otpbase import OTPLocalizer +from toontown.toontowngui import ToonHeadDialog +from direct.gui.DirectGui import DGG +from otp.otpbase import OTPGlobals +from toontown.toonbase import TTLocalizer + +class BoardingGroupInviterPanels: + """ + BoardingGroupInviterPanels: + This is the class that controls the BoardingGroupInvitingPanel + and the BoardingGroupInvitationRejectedPanel. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("BoardingGroupInviterPanels") + + def __init__(self): + self.__invitingPanel = None + self.__invitationRejectedPanel = None + if __debug__: + base.inviterPanels = self + + def cleanup(self): + self.destroyInvitingPanel() + self.destroyInvitationRejectedPanel() + + def createInvitingPanel(self, boardingParty, inviteeId, **kw): + """ + This methotd opens the Boarding Group Inviting Panel, + after destroying any previously opened panels. + """ + self.destroyInvitingPanel() + self.destroyInvitationRejectedPanel() + self.notify.debug('Creating Inviting Panel.') + self.__invitingPanel = BoardingGroupInvitingPanel(boardingParty, inviteeId, **kw) + + def createInvitationRejectedPanel(self, boardingParty, inviteeId, **kw): + """ + This method opens the Boarding Group Invititation Rejected Panel, + after destroying any previously opened panels. + """ + self.destroyInvitingPanel() + self.destroyInvitationRejectedPanel() + self.notify.debug('Creating Invititation Rejected Panel.') + self.__invitationRejectedPanel = BoardingGroupInvitationRejectedPanel(boardingParty, inviteeId, **kw) + + def destroyInvitingPanel(self): + """ + This method destroys any open Boarding Group Inviting Panel. + """ + if self.isInvitingPanelUp(): + self.__invitingPanel.cleanup() + self.__invitingPanel = None + + def destroyInvitationRejectedPanel(self): + """ + This method destroys any open Boarding Group Invititation Rejected Panel. + """ + if self.isInvitationRejectedPanelUp(): + self.__invitationRejectedPanel.cleanup() + self.__invitationRejectedPanel = None + + def isInvitingPanelIdCorrect(self, inviteeId): + """ + Helper function to verify whether we're dealing with a panel of the same invitee. + """ + if self.isInvitingPanelUp(): + if (inviteeId == self.__invitingPanel.avId): + return True + else: + self.notify.warning('Got a response back from an invitee, but a different invitee panel was open. Maybe lag?') + return False + + def isInvitingPanelUp(self): + """ + Helper function to determine whether any Inviting panel is up or not. + """ + if self.__invitingPanel: + if not self.__invitingPanel.isEmpty(): + return True + self.__invitingPanel = None + return False + + def isInvitationRejectedPanelUp(self): + """ + Helper function to determine whether any Invitation Rejected panel is up or not. + """ + if self.__invitationRejectedPanel: + if not self.__invitationRejectedPanel.isEmpty(): + return True + self.__invitationRejectedPanel = None + return False + + def forceCleanup(self): + """ + Cancels any pending request and removes the panel from the screen, unanswered. + This should be called only when the toon leaves the zone with an unanswered panel. + """ + if self.isInvitingPanelUp(): + self.__invitingPanel.forceCleanup() + self.__invitingPanel = None + + if self.isInvitationRejectedPanelUp(): + self.__invitationRejectedPanel.forceCleanup() + self.__invitationRejectedPanel = None + +class BoardingGroupInviterPanelBase(ToonHeadDialog.ToonHeadDialog): + """ + BoardingGroupInviter: + This is a panel that pops up in the middle of your screen whenever + you invite someone to your Boarding Group. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("BoardingGroupInviterPanelBase") + + def __init__(self, boardingParty, inviteeId, **kw): + if __debug__: + base.inviterPanel = self + self.boardingParty = boardingParty + self.avId = inviteeId + avatar = base.cr.doId2do.get(self.avId) + self.avatarName = '' + if avatar: + self.avatar = avatar + self.avatarName = avatar.getName() + avatarDNA = avatar.getStyle() + + # Get all the parameters from the derived class + self.defineParams() + + command = self.handleButton + optiondefs = ( + ('dialogName', self.dialogName, None), + ('text', self.inviterText, None), + ('style', self.panelStyle, None), + ('buttonTextList',self.buttonTextList, None), + ('command', command, None), + ('image_color', (1.0, 0.89, 0.77, 1.0), None), + # Make the head smaller so the panel can be smaller + ('geom_scale', 0.2, None), + # Don't have to move it over as much + ('geom_pos', (-0.1,0,-0.025), None), + # Reduce padding + ('pad', (0.075,0.075), None), + ('topPad', 0, None), + ('midPad', 0, None), + # Position panel right next to friends panel + ('pos', (0.45, 0, 0.75), None), + ('scale', 0.75, None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + # Initialize our base class. + ToonHeadDialog.ToonHeadDialog.__init__(self, avatarDNA) + # Make sure dialog is visible by default + self.show() + + def defineParams(self): + self.notify.error('setupParams: This method should not be called from the base class. Derived class should override this method') + + def cleanup(self): + """ + Removes the panel from the screen. + """ + self.notify.debug('Destroying Panel.') + ToonHeadDialog.ToonHeadDialog.cleanup(self) + + def forceCleanup(self): + """ + Cancels any pending request and removes the panel from the screen, unanswered. + This should be called only when the toon leaves the zone with an unanswered panel. + """ + self.handleButton(0) + + def handleButton(self, value): + self.cleanup() + + +class BoardingGroupInvitingPanel(BoardingGroupInviterPanelBase): + """ + BoardingGroupInvitingPanel: + This is a panel that pops up in the middle of your screen whenever + you invite someone to your Boarding Group. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("BoardingGroupInvitingPanel") + + def __init__(self, boardingParty, inviteeId, **kw): + BoardingGroupInviterPanelBase.__init__(self, boardingParty, inviteeId, **kw) + # Initialize the dialog + self.initialiseoptions(BoardingGroupInvitingPanel) + self.setupUnexpectedExitHooks() + + def defineParams(self): + self.dialogName = 'BoardingGroupInvitingPanel' + self.inviterText = TTLocalizer.BoardingInvitingMessage %self.avatarName + self.panelStyle = TTDialog.CancelOnly + self.buttonTextList = [OTPLocalizer.GuildInviterCancel] + + def handleButton(self, value): + self.boardingParty.requestCancelInvite(self.avId) + BoardingGroupInviterPanelBase.cleanup(self) + + def setupUnexpectedExitHooks(self): + """Setup hooks to inform us when other toons exit unexpectedly.""" + if base.cr.doId2do.has_key(self.avId): + toon = base.cr.doId2do[self.avId] + self.unexpectedExitEventName = toon.uniqueName('disable') + self.accept(self.unexpectedExitEventName, self.forceCleanup) + + def forceCleanup(self): + self.ignore(self.unexpectedExitEventName) + BoardingGroupInviterPanelBase.forceCleanup(self) + + +class BoardingGroupInvitationRejectedPanel(BoardingGroupInviterPanelBase): + """ + BoardingGroupInvitationRejectedPanel: + This is a panel that pops up in the middle of your screen whenever + someone rejected your invitation to the Boarding Group. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("BoardingGroupInvitationRejectedPanel") + + def __init__(self, boardingParty, inviteeId, **kw): + BoardingGroupInviterPanelBase.__init__(self, boardingParty, inviteeId, **kw) + # Initialize the dialog + self.initialiseoptions(BoardingGroupInvitationRejectedPanel) + + def defineParams(self): + self.dialogName = 'BoardingGroupInvitationRejectedPanel' + self.inviterText = TTLocalizer.BoardingInvitationRejected %self.avatarName + self.panelStyle = TTDialog.Acknowledge + self.buttonTextList = [OTPLocalizer.GuildInviterOK] \ No newline at end of file diff --git a/toontown/src/toon/Configrc.prc b/toontown/src/toon/Configrc.prc new file mode 100644 index 0000000..384c3a1 --- /dev/null +++ b/toontown/src/toon/Configrc.prc @@ -0,0 +1,80 @@ +# want-tk 1 +# want-directtools 1 +# level-editor-hoods TT +level-editor-hoods TT DD DG MM BR DL +style-path-prefix /i + +load-display pandadx8 +chan-config-sanity-check #f +multipass-viz none +win-width 800 +win-height 600 +fullscreen 0 +sync-video #f + +# Configrc for running the Robot Toon Manager + +# THESE LINES ALLOW YOU TO USE DOWNLOAD MODELS INSTEAD OF TTMODELS +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_4.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_6.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_7.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_8.mf /tt 0 + +# Use local copy of ttmodels +model-path . +# Putting this line after ttmodels means models will be read from here first +# model-path /tt +model-path $DMODELS +sound-path . +dna-preload phase_4/dna/storage.dna +default-model-extension .bam + +load-file-type toontown + +window-title Toontown + +merge-lod-bundles 0 +compress-channels #t +text-encoding utf8 + +# We must currently set this to avoid messing up some of +# the suits' faces. +egg-retesselate-coplanar #f + + +# Custom ObjectTypes for Toontown. +# "barrier" means a vertical wall, with bitmask 0x01 +# "floor" means a horizontal floor, with bitmask 0x02 +# "camera-collide" means things that the camera should avoid, with bitmask 0x04 +egg-object-type-barrier collide-mask { 0x01 } { Polyset descend } +egg-object-type-trigger collide-mask { 0x01 } { Polyset descend intangible } +egg-object-type-sphere collide-mask { 0x01 } { Sphere descend } +egg-object-type-trigger-sphere collide-mask { 0x01 } { Sphere descend intangible } +egg-object-type-floor collide-mask { 0x02 } { Polyset descend } +egg-object-type-camera-collide collide-mask { 0x04 } { Polyset descend } +egg-object-type-camera-collide-sphere collide-mask { 0x04 } { Sphere descend } +egg-object-type-camera-barrier collide-mask { 0x05 } { Polyset descend } +egg-object-type-camera-barrier-sphere collide-mask { 0x05 } { Sphere descend } + +# The modelers occasionally put { model } instead of +# { 1 }. Let's be accommodating. +egg-object-type-model { 1 } +egg-object-type-dcs { 1 } + +# Define a "shadow" object type, so we can render all shadows in their +# own bin and have them not fight with each other (or with other +# transparent geometry). +egg-object-type-shadow bin { shadow } alpha { blend-no-occlude } +cull-bin shadow 15 unsorted + +# The ID of the server that we are compatible with +server-version sv1.0.14 + + +notify-level-chan error + + diff --git a/toontown/src/toon/Configrc.rtm b/toontown/src/toon/Configrc.rtm new file mode 100644 index 0000000..41534dd --- /dev/null +++ b/toontown/src/toon/Configrc.rtm @@ -0,0 +1,93 @@ +# want-tk 1 +# want-directtools 1 +# level-editor-hoods TT +level-editor-hoods TT DD DG MM BR DL PA +style-path-prefix /i + +#load-display pandadx8 +load-display pandagl +stencil-bits 8 +framebuffer-alpha 1 +alpha-bits 8 + +framebuffer-multisample 1 +multisamples 8 + +chan-config-sanity-check #f +multipass-viz none +win-width 800 +win-height 600 +fullscreen 0 +sync-video #f + +# Configrc for running the Robot Toon Manager + +# THESE LINES ALLOW YOU TO USE DOWNLOAD MODELS INSTEAD OF TTMODELS +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_4.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.5.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_6.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_7.mf /tt 0 +vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_8.mf /tt 0 + +# Use local copy of ttmodels +model-path . +plugin-path . +dna-directory . +# Putting this line after ttmodels means models will be read from here first +# model-path /tt +model-path $DMODELS +sound-path . +dna-preload phase_4/dna/storage.dna +default-model-extension .bam + +load-file-type toontown + +window-title Toontown + +merge-lod-bundles 0 +compress-channels #t +text-encoding utf8 + +# We must currently set this to avoid messing up some of +# the suits' faces. +egg-retesselate-coplanar #f + + +# Custom ObjectTypes for Toontown. +# "barrier" means a vertical wall, with bitmask 0x01 +# "floor" means a horizontal floor, with bitmask 0x02 +# "camera-collide" means things that the camera should avoid, with bitmask 0x04 +egg-object-type-barrier collide-mask { 0x01 } { Polyset descend } +egg-object-type-trigger collide-mask { 0x01 } { Polyset descend intangible } +egg-object-type-sphere collide-mask { 0x01 } { Sphere descend } +egg-object-type-trigger-sphere collide-mask { 0x01 } { Sphere descend intangible } +egg-object-type-floor collide-mask { 0x02 } { Polyset descend } +egg-object-type-camera-collide collide-mask { 0x04 } { Polyset descend } +egg-object-type-camera-collide-sphere collide-mask { 0x04 } { Sphere descend } +egg-object-type-camera-barrier collide-mask { 0x05 } { Polyset descend } +egg-object-type-camera-barrier-sphere collide-mask { 0x05 } { Sphere descend } + +# The modelers occasionally put { model } instead of +# { 1 }. Let's be accommodating. +egg-object-type-model { 1 } +egg-object-type-dcs { 1 } + +# Define a "shadow" object type, so we can render all shadows in their +# own bin and have them not fight with each other (or with other +# transparent geometry). +egg-object-type-shadow bin { shadow } alpha { blend-no-occlude } +cull-bin shadow 15 unsorted + +# We still need this off for now. +temp-hpr-fix 0 + +# The ID of the server that we are compatible with +server-version sv1.0.14 + + +notify-level-chan error + + diff --git a/toontown/src/toon/DeathForceAcknowledge.py b/toontown/src/toon/DeathForceAcknowledge.py new file mode 100644 index 0000000..bfb65af --- /dev/null +++ b/toontown/src/toon/DeathForceAcknowledge.py @@ -0,0 +1,69 @@ +from pandac.PandaModules import * +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from direct.showbase import Transitions +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import LaffMeter + +class DeathForceAcknowledge: + def __init__(self, doneEvent): + """___init___(self, Event)""" + + # We create a DirectFrame for the fade polygon, instead of + # simply loading the polygon model and using it directly, + # so that it will also obscure mouse events for objects + # positioned behind it. + # Normally, this is done as a transition fade screen, but + # it doesn't work here (noTransitions gets called after + # the dialog is brought up) + fadeModel = loader.loadModel("phase_3/models/misc/fade") + if fadeModel: + self.fade = DirectFrame( + parent = aspect2dp, + relief = None, + image = fadeModel, + image_color = (0, 0, 0, 0.4), + image_scale = 3.0, + state = DGG.NORMAL, + ) + self.fade.reparentTo(aspect2d, FADE_SORT_INDEX) + fadeModel.removeNode() + else: + print "Problem loading fadeModel." + self.fade = None + + self.dialog = TTDialog.TTGlobalDialog( + message = TTLocalizer.PlaygroundDeathAckMessage, + doneEvent = doneEvent, + style = TTDialog.Acknowledge, + suppressKeys = True, + ) + self.dialog['text_pos'] = (-.26,.1) + scale = self.dialog.component('image0').getScale() + scale.setX(scale[0] * 1.3) + self.dialog.component('image0').setScale(scale) + + + av = base.localAvatar + self.laffMeter = LaffMeter.LaffMeter(av.style, av.hp, av.maxHp) + self.laffMeter.reparentTo(self.dialog) + if av.style.getAnimal() == "monkey": + self.laffMeter.setPos(-0.46, 0, -0.035) + self.laffMeter.setScale(0.085) + else: + self.laffMeter.setPos(-0.48, 0, -0.035) + self.laffMeter.setScale(0.1) + self.laffMeter.start() + + self.dialog.show() + + def cleanup(self): + if self.fade: + self.fade.destroy() + if self.laffMeter: + self.laffMeter.destroy() + del self.laffMeter + if self.dialog: + self.dialog.cleanup() + self.dialog = None diff --git a/toontown/src/toon/DistributedNPCBlocker.py b/toontown/src/toon/DistributedNPCBlocker.py new file mode 100644 index 0000000..61a30bd --- /dev/null +++ b/toontown/src/toon/DistributedNPCBlocker.py @@ -0,0 +1,106 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +from toontown.toonbase import TTLocalizer +from direct.distributed import DistributedObject +from toontown.quest import QuestParser + +class DistributedNPCBlocker(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + # Make collision sphere wide enough to block a tunnel + self.cSphereNodePath.setScale(4.5, 1.0, 6.0) + self.isLocalToon = 1 + self.movie = None + + def announceGenerate(self): + #self.nametag.unmanage(base.marginManager) + DistributedNPCToonBase.announceGenerate(self) + + def initToonState(self): + # We'll make all NPC toons loop their neutral cycle by + # default. Normally this is sent from the AI, but because the + # server sometimes loses updates that immediately follow the + # generate, we might lose that message. + self.setAnimState("neutral", 0.9, None, None) + # Set the Blocker's position and orientation + posh = NPCToons.BlockerPositions[self.name] + self.setPos(posh[0]) + self.setH(posh[1]) + + def disable(self): + if hasattr(self, 'movie') and self.movie: + self.movie.cleanup() + del self.movie + if (self.isLocalToon == 1): + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('quest', [self]) + # Tell the server + self.sendUpdate("avatarEnter", []) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + + def resetBlocker(self): + assert self.notify.debug('resetBlocker') + # Make blocker non-collideable + self.cSphereNode.setCollideMask(BitMask32()) + if hasattr(self, 'movie') and self.movie: + self.movie.cleanup() + self.movie = None + self.startLookAround() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon == 1): + base.localAvatar.posCamera(0, 0) + self.freeAvatar() + self.isLocalToon = 0 + + def setMovie(self, mode, npcId, avId, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + + self.npcId = npcId + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + if (mode == NPCToons.BLOCKER_MOVIE_CLEAR): + assert self.notify.debug('BLOCKER_MOVIE_CLEAR') + return + + elif (mode == NPCToons.BLOCKER_MOVIE_START): + assert self.notify.debug('BLOCKER_MOVIE_PLAY') + self.movie = QuestParser.NPCMoviePlayer("tutorial_blocker", + base.localAvatar, self) + self.movie.play() + + elif (mode == NPCToons.BLOCKER_MOVIE_TIMEOUT): + assert self.notify.debug('BLOCKER_MOVIE_TIMEOUT') + return + + return + + def finishMovie(self, av, isLocalToon, elapsedTime): + """ + Final cleanup for a movie that has finished + """ + self.resetBlocker() diff --git a/toontown/src/toon/DistributedNPCBlockerAI.py b/toontown/src/toon/DistributedNPCBlockerAI.py new file mode 100644 index 0000000..868f06a --- /dev/null +++ b/toontown/src/toon/DistributedNPCBlockerAI.py @@ -0,0 +1,78 @@ + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +import NPCToons +from direct.task.Task import Task + +class DistributedNPCBlockerAI(DistributedNPCToonBaseAI): + + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + self.tutorial = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def setTutorial(self, val): + # If you are in the tutorial you have no timeouts + self.tutorial = val + + def getTutorial(self): + return self.tutorial + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + DistributedNPCToonBaseAI.avatarEnter(self) + + # Do what the quest manager does for DistributedNPCToon + av = self.air.doId2do.get(avId) + if av is None: + self.notify.warning('toon isnt there! toon: %s' % avId) + return + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + self.sendStartMovie(avId) + + def sendStartMovie(self, avId): + assert self.notify.debug('sendStartMovie()') + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.BLOCKER_MOVIE_START, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if (not self.tutorial): + taskMgr.doMethodLater(NPCToons.CLERK_COUNTDOWN_TIME, + self.sendTimeoutMovie, + self.uniqueName('clearMovie')) + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + self.timedOut = 1 + self.sendUpdate("setMovie", [NPCToons.BLOCKER_MOVIE_TIMEOUT, + self.npcId, self.busy, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + self.busy = 0 + self.timedOut = 0 + self.sendUpdate("setMovie", [NPCToons.BLOCKER_MOVIE_CLEAR, + self.npcId, 0, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + return Task.done + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + if (not self.tutorial): + self.sendTimeoutMovie(None) + return None diff --git a/toontown/src/toon/DistributedNPCClerk.py b/toontown/src/toon/DistributedNPCClerk.py new file mode 100644 index 0000000..30450f4 --- /dev/null +++ b/toontown/src/toon/DistributedNPCClerk.py @@ -0,0 +1,204 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from toontown.minigame import ClerkPurchase +from toontown.shtiker.PurchaseManagerConstants import * +import NPCToons +from direct.task.Task import Task +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + +class DistributedNPCClerk(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.purchase = None + self.isLocalToon = 0 + self.av = None + self.purchaseDoneEvent = 'purchaseDone' + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPurchaseGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.purchase: + self.purchase.exit() + self.purchase.unload() + self.purchase = None + self.av = None + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def allowedToEnter(self): + """Check if the local toon is allowed to enter.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ): + # trialer going to TTC/Estate/Goofy Speedway, let them through + return True + return False + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + if self.allowedToEnter(): + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='otherGags', + doneFunc=self.handleOkTeaser) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + + def resetClerk(self): + assert self.notify.debug('resetClerk') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPurchaseGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.purchase: + self.purchase.exit() + self.purchase.unload() + self.purchase = None + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + self.startLookAround() + self.detectAvatars() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon): + self.freeAvatar() + return Task.done + + def setMovie(self, mode, npcId, avId, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.PURCHASE_MOVIE_CLEAR): + assert self.notify.debug('PURCHASE_MOVIE_CLEAR') + return + + if (mode == NPCToons.PURCHASE_MOVIE_TIMEOUT): + assert self.notify.debug('PURCHASE_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('popupPurchaseGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if (self.isLocalToon): + self.ignore(self.purchaseDoneEvent) + # See if a button was pressed first + if self.purchase: + self.__handlePurchaseDone() + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetClerk() + + elif (mode == NPCToons.PURCHASE_MOVIE_START): + assert self.notify.debug('PURCHASE_MOVIE_START') + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + self.setupAvatars(self.av) + + if (self.isLocalToon): + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, self.getHeight()-0.5, -150, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + + self.setChatAbsolute(TTLocalizer.STOREOWNER_GREETING, + CFSpeech | CFTimeout) + if (self.isLocalToon): + taskMgr.doMethodLater(1.0, self.popupPurchaseGUI, + self.uniqueName('popupPurchaseGUI')) + + elif (mode == NPCToons.PURCHASE_MOVIE_COMPLETE): + assert self.notify.debug('PURCHASE_MOVIE_COMPLETE') + self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, + CFSpeech | CFTimeout) + self.resetClerk() + + elif (mode == NPCToons.PURCHASE_MOVIE_NO_MONEY): + self.setChatAbsolute(TTLocalizer.STOREOWNER_NEEDJELLYBEANS, + CFSpeech | CFTimeout) + self.resetClerk() + + return + + def popupPurchaseGUI(self, task): + assert self.notify.debug('popupPurchaseGUI()') + self.setChatAbsolute('', CFSpeech) + self.acceptOnce(self.purchaseDoneEvent, self.__handlePurchaseDone) + self.accept('boughtGag', self.__handleBoughtGag) + self.purchase = ClerkPurchase.ClerkPurchase(base.localAvatar, + self.remain, self.purchaseDoneEvent) + self.purchase.load() + self.purchase.enter() + return Task.done + + def __handleBoughtGag(self): + # Send each gag purchase to the AI as we go. + self.d_setInventory(base.localAvatar.inventory.makeNetString(), + base.localAvatar.getMoney(), 0) + + def __handlePurchaseDone(self): + """ + This is the callback from the Purchase object + Cleanup the gui and send the message to the AI + """ + assert self.notify.debug('handlePurchaseDone()') + #print "handlepurchasedone" + self.ignore('boughtGag') + self.d_setInventory(base.localAvatar.inventory.makeNetString(), + base.localAvatar.getMoney(), 1) + #print "handlepurchasedone, set inventory" + self.purchase.exit() + self.purchase.unload() + self.purchase = None + + + def d_setInventory(self, invString, money, done): + # Report our inventory to the server + self.sendUpdate('setInventory', [invString, money, done]) diff --git a/toontown/src/toon/DistributedNPCClerkAI.py b/toontown/src/toon/DistributedNPCClerkAI.py new file mode 100644 index 0000000..ff1d161 --- /dev/null +++ b/toontown/src/toon/DistributedNPCClerkAI.py @@ -0,0 +1,132 @@ + +from otp.ai.AIBaseGlobal import * +from direct.task.Task import Task +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * + +class DistributedNPCClerkAI(DistributedNPCToonBaseAI): + + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + self.timedOut = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + DistributedNPCToonBaseAI.avatarEnter(self) + + # Do what the quest manager does for DistributedNPCToon + av = self.air.doId2do.get(avId) + if av is None: + self.notify.warning('toon isnt there! toon: %s' % avId) + return + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + # If NPC is busy, free the avatar + if (self.isBusy()): + assert self.notify.debug('freeing avatar: %s because NPCClerk is busy' % avId) + self.freeAvatar(avId) + return + + if (av.getMoney()): + self.sendStartMovie(avId) + else: + self.sendNoMoneyMovie(avId) + + def sendStartMovie(self, avId): + assert self.notify.debug('sendStartMovie()') + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_START, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + # Timeout + taskMgr.doMethodLater(NPCToons.CLERK_COUNTDOWN_TIME, + self.sendTimeoutMovie, + self.uniqueName('clearMovie')) + + def sendNoMoneyMovie(self, avId): + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_NO_MONEY, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + return + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + self.timedOut = 1 + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_TIMEOUT, + self.npcId, self.busy, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + self.busy = 0 + self.timedOut = 0 + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_CLEAR, + self.npcId, 0, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + return Task.done + + def completePurchase(self, avId): + assert self.notify.debug('completePurchase()') + self.busy = avId + # Send a movie to reward the avatar + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_COMPLETE, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + return + + def setInventory(self, blob, newMoney, done): + assert self.notify.debug('setInventory(): %s' % self.timedOut) + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCClerkAI.setInventory busy with %s' % (self.busy)) + self.notify.warning('setInventory from unknown avId: %s busy: %s' % (avId, self.busy)) + return + + if self.air.doId2do.has_key(avId): + av = self.air.doId2do[avId] + newInventory = av.inventory.makeFromNetString(blob) + currentMoney = av.getMoney() + if (av.inventory.validatePurchase(newInventory, currentMoney, newMoney)): + av.setMoney(newMoney) + if done: + # Tell the state server about the purchase + av.d_setInventory(av.inventory.makeNetString()) + av.d_setMoney(newMoney) + else: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCClerkAI.setInventory invalid purchase') + self.notify.warning("Avatar " + str(avId) + + " attempted an invalid purchase.") + # Make sure the avatar is in sync with the AI. + av.d_setInventory(av.inventory.makeNetString()) + av.d_setMoney(av.getMoney()) + + if self.timedOut: + return + if done: + taskMgr.remove(self.uniqueName('clearMovie')) + self.completePurchase(avId) + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + self.sendTimeoutMovie(None) + return None diff --git a/toontown/src/toon/DistributedNPCFisherman.py b/toontown/src/toon/DistributedNPCFisherman.py new file mode 100644 index 0000000..82d17dc --- /dev/null +++ b/toontown/src/toon/DistributedNPCFisherman.py @@ -0,0 +1,230 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +from toontown.toonbase import TTLocalizer +from toontown.fishing import FishSellGUI +from direct.task.Task import Task + +class DistributedNPCFisherman(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.isLocalToon = 0 + self.av = None + self.button = None + self.popupInfo = None + self.fishGui = None + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupFishGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.popupInfo: + self.popupInfo.destroy() + self.popupInfo = None + if self.fishGui: + self.fishGui.destroy() + self.fishGui = None + self.av = None + if (self.isLocalToon): + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedNPCToonBase.generate(self) + self.fishGuiDoneEvent = "fishGuiDone" + + def announceGenerate(self): + DistributedNPCToonBase.announceGenerate(self) + + def initToonState(self): + # announceGenerate in DistributedNPCToonBase tries to + # parent the toon to a node called npc_origin_N. For now + # this node doesn't exist for the fisherman, so we will have + # to create our own node, and then call the base class function + self.setAnimState("neutral", 1.05, None, None) + # Make sure you look under stashed nodes as well, since street + # visibility might have stashed the zone this origin is under + npcOrigin = self.cr.playGame.hood.loader.geom.find("**/npc_fisherman_origin_%s;+s" % self.posIndex) + if not npcOrigin.isEmpty(): + self.reparentTo(npcOrigin) + self.clearMat() + else: + self.notify.warning("announceGenerate: Could not find npc_fisherman_origin_" + str(self.posIndex)) + + def getCollSphereRadius(self): + """ + Override DistributedNPCToonBase here to spec a smaller radius + """ + return 1.0 + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + + def setupAvatars(self, av): + """ + Prepare avatars for the quest movie + """ + # Ignore avatars now to prevent unnecessary requestInteractions when we know + # this npc is busy right now. If another toon did manage to request interaction + # before we starting ignoring, he will get a freeAvatar message from the server + self.ignoreAvatars() + # Make us face each other + # Actually this looks funny for the fishermen + # av.headsUp(self, 0, 0, 0) + # self.headsUp(av, 0, 0, 0) + av.stopLookAround() + av.lerpLookAt(Point3(-0.5, 4, 0), time=0.5) + self.stopLookAround() + self.lerpLookAt(Point3(av.getPos(self)), time=0.5) + + def resetFisherman(self): + assert self.notify.debug('resetFisherman') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupFishGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.fishGui: + self.fishGui.destroy() + self.fishGui = None + + self.show() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon): + self.freeAvatar() + return Task.done + + def setMovie(self, mode, npcId, avId, extraArgs, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + self.npcId = npcId + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.SELL_MOVIE_CLEAR): + assert self.notify.debug('SELL_MOVIE_CLEAR') + return + + if (mode == NPCToons.SELL_MOVIE_TIMEOUT): + assert self.notify.debug('SELL_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if (self.isLocalToon): + self.ignore(self.fishGuiDoneEvent) + # hide the popupInfo + if self.popupInfo: + self.popupInfo.reparentTo(hidden) + if self.fishGui: + self.fishGui.destroy() + self.fishGui = None + + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetFisherman() + + elif (mode == NPCToons.SELL_MOVIE_START): + assert self.notify.debug('SELL_MOVIE_START') + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + self.setupAvatars(self.av) + + if (self.isLocalToon): + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, base.localAvatar.getHeight()-0.5, + -150, -2, 0, + 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + + if (self.isLocalToon): + taskMgr.doMethodLater(1.0, self.popupFishGUI, + self.uniqueName('popupFishGUI')) + + elif (mode == NPCToons.SELL_MOVIE_COMPLETE): + assert self.notify.debug('SELL_MOVIE_COMPLETE') + # this is necessary to not show marketing message on test + chatStr = TTLocalizer.STOREOWNER_THANKSFISH + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetFisherman() + + elif (mode == NPCToons.SELL_MOVIE_TROPHY): + assert self.notify.debug('SELL_MOVIE_TROPHY') + + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + numFish, totalNumFish = extraArgs + self.setChatAbsolute(TTLocalizer.STOREOWNER_TROPHY % (numFish, totalNumFish), + CFSpeech | CFTimeout) + self.resetFisherman() + + elif (mode == NPCToons.SELL_MOVIE_NOFISH): + assert self.notify.debug('SELL_MOVIE_NOFISH') + chatStr = TTLocalizer.STOREOWNER_NOFISH + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetFisherman() + + elif (mode == NPCToons.SELL_MOVIE_NO_MONEY): + self.notify.warning('SELL_MOVIE_NO_MONEY should not be called') + self.resetFisherman() + + return + + def __handleSaleDone(self, sell): + self.ignore(self.fishGuiDoneEvent) + # Ask the AI to complete the sale + self.sendUpdate("completeSale", [sell]) + self.fishGui.destroy() + self.fishGui = None + + def popupFishGUI(self, task): + assert self.notify.debug('popupFishGUI()') + self.setChatAbsolute('', CFSpeech) + self.acceptOnce(self.fishGuiDoneEvent, self.__handleSaleDone) + self.fishGui = FishSellGUI.FishSellGUI(self.fishGuiDoneEvent) + + diff --git a/toontown/src/toon/DistributedNPCFishermanAI.py b/toontown/src/toon/DistributedNPCFishermanAI.py new file mode 100644 index 0000000..f64d1e3 --- /dev/null +++ b/toontown/src/toon/DistributedNPCFishermanAI.py @@ -0,0 +1,125 @@ + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +from toontown.fishing import FishGlobals +from toontown.toonbase import TTLocalizer +from toontown.fishing import FishGlobals +from direct.task import Task + +class DistributedNPCFishermanAI(DistributedNPCToonBaseAI): + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + # Fishermen are not in the business of giving out quests + self.givesQuests = 0 + self.busy = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + + if (not self.air.doId2do.has_key(avId)): + self.notify.warning("Avatar: %s not found" % (avId)) + return + + if (self.isBusy()): + self.freeAvatar(avId) + return + + av = self.air.doId2do[avId] + self.busy = avId + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + value = av.fishTank.getTotalValue() + if value > 0: + # If you have some fish, let the client popup a gui to sell them + flag = NPCToons.SELL_MOVIE_START + self.d_setMovie(avId, flag) + taskMgr.doMethodLater(30.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + else: + # If you have no fish, send this instructional movie + flag = NPCToons.SELL_MOVIE_NOFISH + self.d_setMovie(avId, flag) + # Immediately send the clear movie - we are done with this Toon + self.sendClearMovie(None) + DistributedNPCToonBaseAI.avatarEnter(self) + + def rejectAvatar(self, avId): + self.notify.warning("rejectAvatar: should not be called by a fisherman!") + return + + def d_setMovie(self, avId, flag, extraArgs=[]): + # tell the client to popup it's sell/adopt interface + self.sendUpdate("setMovie", + [flag, + self.npcId, avId, extraArgs, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + # The timeout has expired. + self.d_setMovie(self.busy, NPCToons.SELL_MOVIE_TIMEOUT) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.busy = 0 + self.d_setMovie(0, NPCToons.SELL_MOVIE_CLEAR) + return Task.done + + def completeSale(self, sell): + assert self.notify.debug('completeSale()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCFishermanAI.completeSale busy with %s' % (self.busy)) + self.notify.warning("somebody called setMovieDone that I was not busy with! avId: %s" % avId) + return + + if sell: + av = simbase.air.doId2do.get(avId) + if av: + # this function sells the fish, clears the tank, and + # updates the collection, trophies, and maxhp. One stop shopping! + trophyResult = self.air.fishManager.creditFishTank(av) + + if trophyResult: + movieType = NPCToons.SELL_MOVIE_TROPHY + extraArgs = [len(av.fishCollection), FishGlobals.getTotalNumFish()] + else: + movieType = NPCToons.SELL_MOVIE_COMPLETE + extraArgs = [] + + # Send a movie to reward the avatar + self.d_setMovie(avId, movieType, extraArgs) + else: + # perhaps the avatar got disconnected, just leave the fish + # in his tank and let him resell them next time + pass + else: + av = simbase.air.doId2do.get(avId) + if av: + # Send a movie to say goodbye + self.d_setMovie(avId, NPCToons.SELL_MOVIE_NOFISH) + self.sendClearMovie(None) + return + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + self.notify.warning('not busy with avId: %s, busy: %s ' % (avId, self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + diff --git a/toontown/src/toon/DistributedNPCFlippyInToonHall.py b/toontown/src/toon/DistributedNPCFlippyInToonHall.py new file mode 100644 index 0000000..e2f567d --- /dev/null +++ b/toontown/src/toon/DistributedNPCFlippyInToonHall.py @@ -0,0 +1,38 @@ +from pandac.PandaModules import * +from DistributedNPCToon import * + +class DistributedNPCFlippyInToonHall(DistributedNPCToon): + + def __init__(self, cr): + assert self.notify.debug("__init__") + DistributedNPCToon.__init__(self, cr) + + def getCollSphereRadius(self): + return 4 + + def initPos(self): + self.clearMat() + self.setScale(1.25) + #self.setHpr(180, 0, 0) + pass + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + if self.allowedToTalk(): + # Lock down the avatar for quest mode + base.cr.playGame.getPlace().fsm.request('quest', [self]) + # Tell the server + self.sendUpdate("avatarEnter", []) + # make sure this NPCs chat balloon is visible above all others for the locekd down avatar + self.nametag3d.setDepthTest(0) + self.nametag3d.setBin('fixed', 0) + self.lookAt(base.localAvatar) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='quests', + doneFunc=self.handleOkTeaser) \ No newline at end of file diff --git a/toontown/src/toon/DistributedNPCFlippyInToonHallAI.py b/toontown/src/toon/DistributedNPCFlippyInToonHallAI.py new file mode 100644 index 0000000..6718e2b --- /dev/null +++ b/toontown/src/toon/DistributedNPCFlippyInToonHallAI.py @@ -0,0 +1,6 @@ +from DistributedNPCToonAI import * + +class DistributedNPCFlippyInToonHallAI(DistributedNPCToonAI): + + def __init__(self, air, npcId, questCallback=None, hq=0): + DistributedNPCToonAI.__init__(self, air, npcId, questCallback) \ No newline at end of file diff --git a/toontown/src/toon/DistributedNPCKartClerk.py b/toontown/src/toon/DistributedNPCKartClerk.py new file mode 100644 index 0000000..d1bb3be --- /dev/null +++ b/toontown/src/toon/DistributedNPCKartClerk.py @@ -0,0 +1,217 @@ +########################################################################## +# Module: DistributedNPCKartClerk.py +# (Based on Distributed NPCPetclerk.py) +# Date: 4/24/05 +# Author: shaskell +########################################################################## + +#from pandac.PandaModules import * +from DistributedNPCToonBase import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +from direct.task.Task import Task +from toontown.toonbase import TTLocalizer +from toontown.racing.KartShopGui import * +from toontown.racing.KartShopGlobals import * + +class DistributedNPCKartClerk(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.isLocalToon = 0 + self.av = None + self.button = None + self.popupInfo = None + self.kartShopGui = None + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupKartShopGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + #TODO: what is this? + if self.popupInfo: + self.popupInfo.destroy() + self.popupInfo = None + if self.kartShopGui: + self.kartShopGui.destroy() + self.kartShopGui = None + self.av = None + if (self.isLocalToon): + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedNPCToonBase.generate(self) + + + def getCollSphereRadius(self): + """ + Override DistributedNPCToonBase here to spec a smaller radius + """ + return 2.25 + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + + def resetKartShopClerk(self): + assert self.notify.debug('resetKartShopClerk') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupKartShopGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.kartShopGui: + self.kartShopGui.destroy() + self.kartShopGui = None + + self.show() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon): + self.freeAvatar() + + return Task.done + + def ignoreEventDict(self): + for event in KartShopGlobals.EVENTDICT: + self.ignore(event) + + def setMovie(self, mode, npcId, avId, extraArgs, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + self.npcId = npcId + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.SELL_MOVIE_CLEAR): + assert self.notify.debug('SELL_MOVIE_CLEAR') + return + + if (mode == NPCToons.SELL_MOVIE_TIMEOUT): + assert self.notify.debug('SELL_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if (self.isLocalToon): + self.ignoreEventDict() + # hide the popupInfo + if self.popupInfo: + self.popupInfo.reparentTo(hidden) + if self.kartShopGui: + self.kartShopGui.destroy() + self.kartShopGui = None + + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetKartShopClerk() + + elif (mode == NPCToons.SELL_MOVIE_START): + assert self.notify.debug('SELL_MOVIE_START') + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + self.setupAvatars(self.av) + + if (self.isLocalToon): + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, base.localAvatar.getHeight()-0.5, + -150, -2, 0, + 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + + if (self.isLocalToon): + taskMgr.doMethodLater(1.0, self.popupKartShopGUI, + self.uniqueName('popupKartShopGUI')) + + #Player made at least one successful purchase + elif (mode == NPCToons.SELL_MOVIE_COMPLETE): + assert self.notify.debug('SELL_MOVIE_COMPLETE') + #TODO: change this appropriately + self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, + CFSpeech | CFTimeout) + self.resetKartShopClerk() + + #Player canceled with no purchas + elif (mode == NPCToons.SELL_MOVIE_PETCANCELED): + assert self.notify.debug('SELL_MOVIE_PETCANCELED') + #TODO: change this appropriately + self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, + CFSpeech | CFTimeout) + self.resetKartShopClerk() + + + elif (mode == NPCToons.SELL_MOVIE_NO_MONEY): + self.notify.warning('SELL_MOVIE_NO_MONEY should not be called') + #TODO: change this appropriately + self.resetKartShopClerk() + + return + + # self.sendUpdate("petAdopted", [whichPet, nameIndex]) + + def __handleBuyKart(self, kartID): + #base.localAvatar.requestKartDNAFieldUpdate( KartDNA.bodyType, kartID ) + #base.localAvatar.requestKartDNAFieldUpdate( KartDNA.bodyColor, getDefaultColor() ) + self.sendUpdate("buyKart", [kartID]) + + def __handleBuyAccessory(self, accID): + #base.localAvatar.requestAddOwnedAccessory(accID ) + self.sendUpdate("buyAccessory", [accID]) + + def __handleGuiDone(self, bTimedOut=False): + self.ignoreAll() + if hasattr(self, 'kartShopGui') and self.kartShopGui != None: + self.kartShopGui.destroy() + self.kartShopGui = None + #TODO: Need this? + if not bTimedOut: + self.sendUpdate("transactionDone") + + def popupKartShopGUI(self, task): + assert self.notify.debug('popupKartShopGUI()') + self.setChatAbsolute('', CFSpeech) + + self.accept( KartShopGlobals.EVENTDICT["buyAccessory"], self.__handleBuyAccessory ) + self.accept( KartShopGlobals.EVENTDICT["buyKart"], self.__handleBuyKart ) + self.acceptOnce( KartShopGlobals.EVENTDICT[ 'guiDone' ], self.__handleGuiDone ) + + self.kartShopGui = KartShopGuiMgr(KartShopGlobals.EVENTDICT) + diff --git a/toontown/src/toon/DistributedNPCKartClerkAI.py b/toontown/src/toon/DistributedNPCKartClerkAI.py new file mode 100644 index 0000000..71d11a1 --- /dev/null +++ b/toontown/src/toon/DistributedNPCKartClerkAI.py @@ -0,0 +1,213 @@ +########################################################################## +# Module: DistributedNPCKartClerkAI.py +# (Based on Distributed NPCPetclerkAI.py) +# Date: 4/24/05 +# Author: shaskell +########################################################################## + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +from toontown.toonbase import TTLocalizer +from direct.task import Task +from toontown.racing.KartShopGlobals import * +from toontown.racing.KartDNA import * + +class DistributedNPCKartClerkAI(DistributedNPCToonBaseAI): + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + # should kart clerks give out quests? not for now... + self.givesQuests = 0 + self.busy = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + + if (not self.air.doId2do.has_key(avId)): + self.notify.warning("Avatar: %s not found" % (avId)) + return + + if (self.isBusy()): + self.freeAvatar(avId) + return + + self.transactionType = "" + + av = self.air.doId2do[avId] + self.busy = avId + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + #TODO: does this need to change? + flag = NPCToons.SELL_MOVIE_START + self.d_setMovie(avId, flag) + taskMgr.doMethodLater(KartShopGlobals.KARTCLERK_TIMER, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + + DistributedNPCToonBaseAI.avatarEnter(self) + + def rejectAvatar(self, avId): + self.notify.warning("rejectAvatar: should not be called by a kart clerk!") + return + + def d_setMovie(self, avId, flag, extraArgs=[]): + # tell the client to popup it's sell/adopt interface + self.sendUpdate("setMovie", + [flag, + self.npcId, avId, extraArgs, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + # The timeout has expired. + self.d_setMovie(self.busy, NPCToons.SELL_MOVIE_TIMEOUT) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.busy = 0 + self.d_setMovie(0, NPCToons.SELL_MOVIE_CLEAR) + return Task.done + + def buyKart(self, whichKart): + assert self.notify.debug('buyKart()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCKartClerkAI.buyKart busy with %s' % (self.busy)) + self.notify.warning("somebody called buyKart that I was not busy with! avId: %s" % avId) + return + av = simbase.air.doId2do.get(avId) + + if av: + movieType = NPCToons.SELL_MOVIE_COMPLETE + extraArgs = [] + #deduct the tickets from the toon's account + cost = getKartCost(whichKart) + if cost == "key error": + self.air.writeServerEvent('suspicious', avId, 'Player trying to buy non-existant kart %s' % (whichKart)) + self.notify.warning("somebody is trying to buy non-existant kart%s! avId: %s" % (whichKart, avId)) + return + elif cost > av.getTickets(): + #how is THIS possible? + self.air.writeServerEvent('suspicious', avId, "DistributedNPCKartClerkAI.buyKart and toon doesn't have enough tickets!") + self.notify.warning("somebody called buyKart and didn't have enough tickets to purchase! avId: %s" % avId) + return + av.b_setTickets(av.getTickets() - cost) + # Write to the server log + self.air.writeServerEvent("kartingTicketsSpent", avId, "%s" % (cost)) + + av.b_setKartBodyType(whichKart) + # Write to the server log + self.air.writeServerEvent("kartingKartPurchased", avId, "%s" % (whichKart)) + + # Send a movie to reward the avatar + #self.d_setMovie(avId, movieType, extraArgs) + + #TODO: do we want this? + #self.transactionType = "fish" + + else: + # perhaps the avatar got disconnected + pass + #self.sendClearMovie(None) + + def buyAccessory(self, whichAcc): + assert self.notify.debug('buyAccessory()') + + avId = self.air.getAvatarIdFromSender() + av = simbase.air.doId2do.get(avId) + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCKartClerkAI.buyAccessory busy with %s' % (self.busy)) + self.notify.warning("somebody called buyAccessory that I was not busy with! avId: %s" % avId) + return + #check if this toon already has maximum number of accessories + if len(av.getKartAccessoriesOwned()) >= KartShopGlobals.MAX_KART_ACC: + #how is THIS possible? + self.air.writeServerEvent('suspicious', avId, "DistributedNPCKartClerkAI.buyAcc and toon already has max number of accessories!") + self.notify.warning("somebody called buyAcc and already has maximum allowed accessories! avId: %s" % avId) + return + + + av = simbase.air.doId2do.get(avId) + if av: + movieType = NPCToons.SELL_MOVIE_COMPLETE + extraArgs = [] + #deduct the tickets from the toon's account + cost = getAccCost(whichAcc) + if cost > av.getTickets(): + #how is THIS possible? + self.air.writeServerEvent('suspicious', avId, "DistributedNPCKartClerkAI.buyAcc and toon doesn't have enough tickets!") + self.notify.warning("somebody called buyAcc and didn't have enough tickets to purchase! avId: %s" % avId) + return + av.b_setTickets(av.getTickets() - cost) + # Write to the server log + self.air.writeServerEvent("kartingTicketsSpent", avId, "%s" % (cost)) + + #add this accessory to list of owned accessories + av.addOwnedAccessory( whichAcc ) + + # Write to the server log + self.air.writeServerEvent("kartingAccessoryPurchased", avId, "%s" % (whichAcc)) + + #automagically put on this accessory + av.updateKartDNAField( getAccessoryType(whichAcc), whichAcc) + + # Send a movie to reward the avatar + #self.d_setMovie(avId, movieType, extraArgs) + + #TODO: do we want this? + #self.transactionType = "fish" + + else: + # perhaps the avatar got disconnected + pass + #self.sendClearMovie(None) + + + #TODO: do we need this? + def transactionDone(self): + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCKartClerkAI.transactionDone busy with %s' % (self.busy)) + self.notify.warning("somebody called transactionDone that I was not busy with! avId: %s" % avId) + return + + av = simbase.air.doId2do.get(avId) + if av: + + # Send a movie to say goodbye + movieType = NPCToons.SELL_MOVIE_COMPLETE + extraArgs = [] + self.d_setMovie(avId, movieType, extraArgs) + #elif self.transactionType == "return": + # self.d_setMovie(avId, NPCToons.SELL_MOVIE_PETRETURNED) + + #TODO: movie for kart clerk? we're not exiting with every transaction + #so do we need more than one? + #self.d_setMovie(avId, NPCToons.SELL_MOVIE_PETCANCELED) + + + self.sendClearMovie(None) + return + + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + self.notify.warning('not busy with avId: %s, busy: %s ' % (avId, self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + diff --git a/toontown/src/toon/DistributedNPCPartyPerson.py b/toontown/src/toon/DistributedNPCPartyPerson.py new file mode 100644 index 0000000..fe66d68 --- /dev/null +++ b/toontown/src/toon/DistributedNPCPartyPerson.py @@ -0,0 +1,308 @@ +#------------------------------------------------------------------------------- +# Contact: Shawn Patton +# Created: Sep 2008 +# +# Purpose: Client side of a party person, an NPC who stands near the party hat +# and can send you to the party grounds to plan your party +#------------------------------------------------------------------------------- + +from DistributedNPCToonBase import DistributedNPCToonBase +from direct.distributed.DistributedObject import DistributedObject +from toontown.toon import NPCToons +from toontown.toonbase import TTLocalizer +from direct.task.Task import Task +from direct.distributed import ClockDelta +from pandac.PandaModules import CFSpeech, CFTimeout, Point3 +from toontown.toontowngui import TTDialog +from otp.otpbase import OTPLocalizer +from toontown.parties import PartyGlobals +from toontown.toonbase import ToontownGlobals +from toontown.toontowngui import TeaserPanel + +class DistributedNPCPartyPerson(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.isInteractingWithLocalToon = 0 + self.av = None + self.button = None + self.askGui = None + self.teaserDialog = None + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupAskGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + self.av = None + if (self.isInteractingWithLocalToon): + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def delete(self): + if self.askGui: + self.ignore(self.planPartyQuestionGuiDoneEvent) + self.askGui.cleanup() + del self.askGui + + DistributedNPCToonBase.delete(self) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedNPCToonBase.generate(self) + + def announceGenerate(self): + DistributedNPCToonBase.announceGenerate(self) + + # Make sure you look under stashed nodes as well, since street + # visibility might have stashed the zone this origin is under + + self.planPartyQuestionGuiDoneEvent = "planPartyQuestionDone" + + self.askGui = TTDialog.TTGlobalDialog( + dialogName = self.uniqueName("askGui"), + doneEvent = self.planPartyQuestionGuiDoneEvent, + message = TTLocalizer.PartyDoYouWantToPlan, + style = TTDialog.YesNo, + okButtonText = OTPLocalizer.DialogYes, + cancelButtonText = OTPLocalizer.DialogNo, + ) + self.askGui.hide() + + def initToonState(self): + # announceGenerate in DistributedNPCToonBase tries to + # parent the toon to a node called npc_origin_N. For now + # this node doesn't exist for the party person, so we will have + # to create our own node, and then call the base class function + self.setAnimState("neutral", 1.05, None, None) + if self.posIndex%2==0: + side = "left" + else: + side = "right" + npcOrigin = self.cr.playGame.hood.loader.geom.find("**/party_person_%s;+s" % side) + if not npcOrigin.isEmpty(): + self.reparentTo(npcOrigin) + self.clearMat() + else: + self.notify.warning("announceGenerate: Could not find party_person_%s" % side ) + + def getCollSphereRadius(self): + """ + Override DistributedNPCToonBase here to spec a smaller radius + """ + return 1.0 + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + # Lock down the avatar for purchase mode + # This is used to keep the avatar in one place apparently + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + + def setupAvatars(self, av): + """ + Prepare avatars for the quest movie + """ + # Ignore avatars now to prevent unnecessary requestInteractions when we know + # this npc is busy right now. If another toon did manage to request interaction + # before we starting ignoring, he will get a freeAvatar message from the server + self.ignoreAvatars() + # Make us face each other + # Actually this looks funny for the fishermen + # av.headsUp(self, 0, 0, 0) + # self.headsUp(av, 0, 0, 0) + av.stopLookAround() + av.lerpLookAt(Point3(-0.5, 4, 0), time=0.5) + self.stopLookAround() + self.lerpLookAt(Point3(av.getPos(self)), time=0.5) + + def resetPartyPerson(self): + assert self.notify.debug('resetPartyPerson') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupAskGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.askGui: + self.askGui.hide() + + self.show() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if self.isInteractingWithLocalToon: + if hasattr(self, "teaserDialog") and not self.teaserDialog: + self.freeAvatar() + return Task.done + + def setMovie(self, mode, npcId, avId, extraArgs, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + self.npcId = npcId + + # See if this is the local toon + self.isInteractingWithLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isInteractingWithLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if mode == NPCToons.PARTY_MOVIE_CLEAR: + assert self.notify.debug('PARTY_MOVIE_CLEAR') + return + + # It's been a long time since we heard anything + if mode == NPCToons.PARTY_MOVIE_TIMEOUT: + assert self.notify.debug('PARTY_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if self.isInteractingWithLocalToon: + self.ignore(self.planPartyQuestionGuiDoneEvent) + if self.askGui: + self.askGui.hide() + self.ignore(self.planPartyQuestionGuiDoneEvent) + + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetPartyPerson() + + # Ask the toon if they want to plan a party (assuming it's the local toon) + elif mode == NPCToons.PARTY_MOVIE_START: + assert self.notify.debug('PARTY_MOVIE_START') + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + self.setupAvatars(self.av) + + if self.isInteractingWithLocalToon: + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, base.localAvatar.getHeight()-0.5, + -150, -2, 0, + 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + taskMgr.doMethodLater(1.0, self.popupAskGUI, + self.uniqueName('popupAskGUI')) + else: + self.setChatAbsolute(TTLocalizer.PartyDoYouWantToPlan, CFSpeech | CFTimeout) + # They want to plan a party! + elif mode == NPCToons.PARTY_MOVIE_COMPLETE: + assert self.notify.debug('PARTY_MOVIE_COMPLETE') + # this is necessary to not show marketing message on test + chatStr = TTLocalizer.PartyPlannerOnYourWay + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetPartyPerson() + + if self.isInteractingWithLocalToon: + base.localAvatar.aboutToPlanParty = True + base.cr.partyManager.setPartyPlannerStyle(self.style) + base.cr.partyManager.setPartyPlannerName(self.name) + base.localAvatar.creatingNewPartyWithMagicWord = False + loaderId = "safeZoneLoader" + whereId = "party" + hoodId, zoneId = extraArgs + avId = -1 + place = base.cr.playGame.getPlace() + requestStatus = {"loader": loaderId, + "where": whereId, + "how": "teleportIn", + "hoodId": hoodId, + "zoneId": zoneId, + "shardId": None, + "avId": avId} + # we need to do a requestLeave to make sure phase 13 is downloaded + place.requestLeave(requestStatus) + + # They decided not to plan a party this time + elif mode == NPCToons.PARTY_MOVIE_MAYBENEXTTIME: + assert self.notify.debug('PARTY_MOVIE_MAYBENEXTTIME') + + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.setChatAbsolute(TTLocalizer.PartyPlannerMaybeNextTime, CFSpeech | CFTimeout) + self.resetPartyPerson() + + # They're already hosting too many parties + elif mode == NPCToons.PARTY_MOVIE_ALREADYHOSTING: + assert self.notify.debug('PARTY_MOVIE_ALREADYHOSTING') + chatStr = TTLocalizer.PartyPlannerHostingTooMany + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetPartyPerson() + + elif mode == NPCToons.PARTY_MOVIE_ONLYPAID: + assert self.notify.debug('PARTY_MOVIE_ONLYPAID') + chatStr = TTLocalizer.PartyPlannerOnlyPaid + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetPartyPerson() + + elif mode == NPCToons.PARTY_MOVIE_COMINGSOON: + assert self.notify.debug('PARTY_MOVIE_COMINGSOON') + chatStr = TTLocalizer.PartyPlannerNpcComingSoon + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetPartyPerson() + elif mode == NPCToons.PARTY_MOVIE_MINCOST: + assert self.notify.debug('PARTY_MOVIE_MINCOST') + chatStr = TTLocalizer.PartyPlannerNpcMinCost % PartyGlobals.MinimumPartyCost + self.setChatAbsolute(chatStr, CFSpeech | CFTimeout) + self.resetPartyPerson() + + return + + def __handleAskDone(self): + self.ignore(self.planPartyQuestionGuiDoneEvent) + doneStatus = self.askGui.doneStatus + if doneStatus == "ok": + wantsToPlan = 1 + if (localAvatar.getGameAccess() != ToontownGlobals.AccessFull): + wantsToPlan = 0 + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped', force = 1) + self.teaserDialog = TeaserPanel.TeaserPanel(pageName='parties', doneFunc = self.handleOkTeaser) + else: + wantsToPlan = 0 + self.sendUpdate("answer", [wantsToPlan]) + self.askGui.hide() + + def popupAskGUI(self, task): + assert self.notify.debug('popupAskGUI()') + self.setChatAbsolute('', CFSpeech) + self.acceptOnce(self.planPartyQuestionGuiDoneEvent, self.__handleAskDone) + self.askGui.show() + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.teaserDialog.destroy() + del self.teaserDialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') diff --git a/toontown/src/toon/DistributedNPCPartyPersonAI.py b/toontown/src/toon/DistributedNPCPartyPersonAI.py new file mode 100644 index 0000000..ee3f91c --- /dev/null +++ b/toontown/src/toon/DistributedNPCPartyPersonAI.py @@ -0,0 +1,139 @@ +#------------------------------------------------------------------------------- +# Contact: Shawn Patton +# Created: Sep 2008 +# +# Purpose: AI side of a party person, an NPC who stands near the party hat +# and can send you to the party grounds to plan your party +#------------------------------------------------------------------------------- + +from DistributedNPCToonBaseAI import DistributedNPCToonBaseAI +from toontown.toonbase import TTLocalizer +from direct.task import Task +from toontown.toonbase import ToontownGlobals +from toontown.toon import NPCToons +from direct.distributed import ClockDelta +from toontown.parties import PartyGlobals + +class DistributedNPCPartyPersonAI(DistributedNPCToonBaseAI): + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + # Party planners are not in the business of giving out quests + self.givesQuests = 0 + self.busy = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + + if not self.air.doId2do.has_key(avId): + self.notify.warning("Avatar: %s not found" % (avId)) + return + + if self.isBusy(): + self.freeAvatar(avId) + return + + av = self.air.doId2do[avId] + self.busy = avId + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), self.__handleUnexpectedExit, extraArgs=[avId]) + + parties = av.hostedParties + if not self.air.partyManager.canBuyParties(): + # people can't buy parties yet + flag = NPCToons.PARTY_MOVIE_COMINGSOON + self.d_setMovie(avId, flag) + # Immediately send the clear movie - we are done with this Toon + self.sendClearMovie(None) + elif av.getTotalMoney() < PartyGlobals.MinimumPartyCost: + flag = NPCToons.PARTY_MOVIE_MINCOST + self.d_setMovie(avId, flag) + # Immediately send the clear movie - we are done with this Toon + self.sendClearMovie(None) + elif av.canPlanParty(): + # If you're not planning a party, maybe you want to? Ask them! + flag = NPCToons.PARTY_MOVIE_START + self.d_setMovie(avId, flag) + taskMgr.doMethodLater(30.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + else: + # If you have a party planned already, you can't plan another + flag = NPCToons.PARTY_MOVIE_ALREADYHOSTING + self.d_setMovie(avId, flag) + # Immediately send the clear movie - we are done with this Toon + self.sendClearMovie(None) + + DistributedNPCToonBaseAI.avatarEnter(self) + + def rejectAvatar(self, avId): + self.notify.warning("rejectAvatar: should not be called by a party person!") + return + + def d_setMovie(self, avId, flag, extraArgs=[]): + # tell the client what to do + self.sendUpdate("setMovie", [flag, self.npcId, avId, extraArgs, ClockDelta.globalClockDelta.getRealNetworkTime()]) + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + # The timeout has expired. + self.d_setMovie(self.busy, NPCToons.PARTY_MOVIE_TIMEOUT) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.busy = 0 + self.d_setMovie(0, NPCToons.PARTY_MOVIE_CLEAR) + return Task.done + + def answer(self, wantsToPlan): + assert self.notify.debug('answer()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPartyPersonAI.answer busy with %s' % (self.busy)) + self.notify.warning("somebody called setMovieDone that I was not busy with! avId: %s" % avId) + return + + if wantsToPlan: + av = simbase.air.doId2do.get(avId) + if av: + if av.getGameAccess() != ToontownGlobals.AccessFull: + # It can only come here if the client is hacked. + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPartyPersonAI.free player tried to host party.') + # If you are a trialer you can't buy a party + flag = NPCToons.PARTY_MOVIE_ONLYPAID + self.d_setMovie(avId, flag) + else: + # this function sends the toon to the party grounds to start planning! + zoneId = self.air.allocateZone() + # hoodId determines the loader that gets used + hoodId = ToontownGlobals.PartyHood + # Send a movie to reward the avatar + self.d_setMovie(avId, NPCToons.PARTY_MOVIE_COMPLETE, [hoodId, zoneId]) + else: + # perhaps the avatar got disconnected, just ignore him... + pass + else: + av = simbase.air.doId2do.get(avId) + if av: + # Send a movie to say goodbye + self.d_setMovie(avId, NPCToons.PARTY_MOVIE_MAYBENEXTTIME) + self.sendClearMovie(None) + return + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + self.notify.warning('not busy with avId: %s, busy: %s ' % (avId, self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + diff --git a/toontown/src/toon/DistributedNPCPetclerk.py b/toontown/src/toon/DistributedNPCPetclerk.py new file mode 100644 index 0000000..b883981 --- /dev/null +++ b/toontown/src/toon/DistributedNPCPetclerk.py @@ -0,0 +1,292 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +from direct.task.Task import Task +from toontown.toonbase import TTLocalizer +from toontown.pets import PetshopGUI +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + +class DistributedNPCPetclerk(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.isLocalToon = 0 + self.av = None + self.button = None + self.popupInfo = None + self.petshopGui = None + self.petSeeds = None + self.waitingForPetSeeds = False + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPetshopGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.popupInfo: + self.popupInfo.destroy() + self.popupInfo = None + if self.petshopGui: + self.petshopGui.destroy() + self.petshopGui = None + self.av = None + if (self.isLocalToon): + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedNPCToonBase.generate(self) + self.eventDict = {} + self.eventDict['guiDone'] = "guiDone" + self.eventDict['petAdopted'] = "petAdopted" + self.eventDict['petReturned'] = "petReturned" + self.eventDict['fishSold'] = "fishSold" + + def getCollSphereRadius(self): + """ + Override DistributedNPCToonBase here to spec a smaller radius + """ + return 4.0 + + def allowedToEnter(self): + """Check if the local toon is allowed to enter.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ): + # trialer going to TTC/Estate/Goofy Speedway, let them through + return True + return False + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + if self.allowedToEnter(): + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='tricks', + doneFunc=self.handleOkTeaser) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + + def resetPetshopClerk(self): + assert self.notify.debug('resetPetshopClerk') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPetshopGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.petshopGui: + self.petshopGui.destroy() + self.petshopGui = None + + self.show() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon): + self.freeAvatar() + + self.petSeeds = None + self.waitingForPetSeeds = False + + return Task.done + + def ignoreEventDict(self): + for event in self.eventDict.values(): + self.ignore(event) + + def setPetSeeds(self, petSeeds): + self.petSeeds = petSeeds + if self.waitingForPetSeeds: + self.waitingForPetSeeds = False + self.popupPetshopGUI(None) #re-call this now that we have the petseeds + + def setMovie(self, mode, npcId, avId, extraArgs, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + self.npcId = npcId + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.SELL_MOVIE_CLEAR): + assert self.notify.debug('SELL_MOVIE_CLEAR') + return + + if (mode == NPCToons.SELL_MOVIE_TIMEOUT): + assert self.notify.debug('SELL_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if (self.isLocalToon): + self.ignoreEventDict() + # hide the popupInfo + if self.popupInfo: + self.popupInfo.reparentTo(hidden) + if self.petshopGui: + self.petshopGui.destroy() + self.petshopGui = None + + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_START): + assert self.notify.debug('SELL_MOVIE_START') + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + self.setupAvatars(self.av) + + if (self.isLocalToon): + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, base.localAvatar.getHeight()-0.5, + -150, -2, 0, + 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + + if (self.isLocalToon): + taskMgr.doMethodLater(1.0, self.popupPetshopGUI, + self.uniqueName('popupPetshopGUI')) + + elif (mode == NPCToons.SELL_MOVIE_COMPLETE): + assert self.notify.debug('SELL_MOVIE_COMPLETE') + self.setChatAbsolute(TTLocalizer.STOREOWNER_THANKSFISH_PETSHOP, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_PETRETURNED): + assert self.notify.debug('SELL_MOVIE_PETRETURNED') + self.setChatAbsolute(TTLocalizer.STOREOWNER_PETRETURNED, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_PETADOPTED): + assert self.notify.debug('SELL_MOVIE_PETADOPTED') + self.setChatAbsolute(TTLocalizer.STOREOWNER_PETADOPTED, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_PETCANCELED): + assert self.notify.debug('SELL_MOVIE_PETCANCELED') + self.setChatAbsolute(TTLocalizer.STOREOWNER_PETCANCELED, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_TROPHY): + assert self.notify.debug('SELL_MOVIE_TROPHY') + + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + numFish, totalNumFish = extraArgs + self.setChatAbsolute(TTLocalizer.STOREOWNER_TROPHY % (numFish, totalNumFish), + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_NOFISH): + assert self.notify.debug('SELL_MOVIE_NOFISH') + self.setChatAbsolute(TTLocalizer.STOREOWNER_NOFISH, + CFSpeech | CFTimeout) + self.resetPetshopClerk() + + elif (mode == NPCToons.SELL_MOVIE_NO_MONEY): + self.notify.warning('SELL_MOVIE_NO_MONEY should not be called') + self.resetPetshopClerk() + + return + + def __handlePetAdopted(self, whichPet, nameIndex): + #the pet adopted message automatically handles returning the + #current pet, so we need to do this here too + base.cr.removePetFromFriendsMap() + + self.ignore(self.eventDict['petAdopted']) + self.sendUpdate("petAdopted", [whichPet, nameIndex]) + + def __handlePetReturned(self): + base.cr.removePetFromFriendsMap() + self.ignore(self.eventDict['petReturned']) + self.sendUpdate("petReturned") + + def __handleFishSold(self): + self.ignore(self.eventDict['fishSold']) + # Ask the AI to complete the sale + self.sendUpdate("fishSold") + + def __handleGUIDone(self, bTimedOut=False): + self.ignore(self.eventDict['guiDone']) + self.petshopGui.destroy() + self.petshopGui = None + if not bTimedOut: + self.sendUpdate("transactionDone") + + def popupPetshopGUI(self, task): + if not self.petSeeds: + self.waitingForPetSeeds = True + return + + #print "popupPetshopGui" + assert self.notify.debug('popupPetshopGUI()') + self.setChatAbsolute('', CFSpeech) + + self.acceptOnce(self.eventDict['guiDone'], self.__handleGUIDone) + self.acceptOnce(self.eventDict['petAdopted'], self.__handlePetAdopted) + self.acceptOnce(self.eventDict['petReturned'], self.__handlePetReturned) + self.acceptOnce(self.eventDict['fishSold'], self.__handleFishSold) + + self.petshopGui = PetshopGUI.PetshopGUI(self.eventDict, self.petSeeds) + + diff --git a/toontown/src/toon/DistributedNPCPetclerkAI.py b/toontown/src/toon/DistributedNPCPetclerkAI.py new file mode 100644 index 0000000..564f48f --- /dev/null +++ b/toontown/src/toon/DistributedNPCPetclerkAI.py @@ -0,0 +1,205 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +from toontown.toonbase import TTLocalizer +from direct.task import Task +from toontown.fishing import FishGlobals +from toontown.pets import PetUtil, PetDNA, PetConstants + +class DistributedNPCPetclerkAI(DistributedNPCToonBaseAI): + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + # Fishermen are not in the business of giving out quests + self.givesQuests = 0 + self.busy = 0 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + + if (not self.air.doId2do.has_key(avId)): + self.notify.warning("Avatar: %s not found" % (avId)) + return + + if (self.isBusy()): + self.freeAvatar(avId) + return + + self.petSeeds = simbase.air.petMgr.getAvailablePets(3, 2) + # 'fix' pet seeds so there are two of each (female/male) + numGenders = len(PetDNA.PetGenders) + self.petSeeds *= numGenders + self.petSeeds.sort() + self.sendUpdateToAvatarId(avId, "setPetSeeds", [self.petSeeds]) + + self.transactionType = "" + + av = self.air.doId2do[avId] + self.busy = avId + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + # If you have some fish, let the client popup a gui to sell them + flag = NPCToons.SELL_MOVIE_START + self.d_setMovie(avId, flag) + taskMgr.doMethodLater(PetConstants.PETCLERK_TIMER, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + + DistributedNPCToonBaseAI.avatarEnter(self) + + def rejectAvatar(self, avId): + self.notify.warning("rejectAvatar: should not be called by a fisherman!") + return + + def d_setMovie(self, avId, flag, extraArgs=[]): + # tell the client to popup it's sell/adopt interface + self.sendUpdate("setMovie", + [flag, + self.npcId, avId, extraArgs, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + # The timeout has expired. + self.d_setMovie(self.busy, NPCToons.SELL_MOVIE_TIMEOUT) + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.busy = 0 + self.d_setMovie(0, NPCToons.SELL_MOVIE_CLEAR) + return Task.done + + def fishSold(self): + assert self.notify.debug('fishSold()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPetshopAI.fishSold busy with %s' % (self.busy)) + self.notify.warning("somebody called fishSold that I was not busy with! avId: %s" % avId) + return + + av = simbase.air.doId2do.get(avId) + if av: + # this function sells the fish, clears the tank, and + # updates the collection, trophies, and maxhp. One stop shopping! + trophyResult = self.air.fishManager.creditFishTank(av) + + if trophyResult: + movieType = NPCToons.SELL_MOVIE_TROPHY + extraArgs = [len(av.fishCollection), FishGlobals.getTotalNumFish()] + else: + movieType = NPCToons.SELL_MOVIE_COMPLETE + extraArgs = [] + + # Send a movie to reward the avatar + self.d_setMovie(avId, movieType, extraArgs) + self.transactionType = "fish" + else: + # perhaps the avatar got disconnected, just leave the fish + # in his tank and let him resell them next time + pass + self.sendClearMovie(None) + + def petAdopted(self, petNum, nameIndex): + assert self.notify.debug('petAdopted()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPetshopAI.petAdopted busy with %s' % (self.busy)) + self.notify.warning("somebody called petAdopted that I was not busy with! avId: %s" % avId) + return + + av = simbase.air.doId2do.get(avId) + if av: + from toontown.hood import ZoneUtil + zoneId = ZoneUtil.getCanonicalSafeZoneId(self.zoneId) + # make sure this isn't a suspicious request + if not petNum in range(0, len(self.petSeeds)): + # hacker? + self.air.writeServerEvent('suspicious', avId, "DistributedNPCPetshopAI.petAdopted and no such pet!") + self.notify.warning("somebody called petAdopted on a non-existent pet! avId: %s" % avId) + return + cost = PetUtil.getPetCostFromSeed(self.petSeeds[petNum], zoneId) + if cost > av.getTotalMoney(): + #houston, we have a problem + self.air.writeServerEvent('suspicious', avId, "DistributedNPCPetshopAI.petAdopted and toon doesn't have enough money!") + self.notify.warning("somebody called petAdopted and didn't have enough money to adopt! avId: %s" % avId) + return + + if av.petId != 0: + # this function deletes the pet + simbase.air.petMgr.deleteToonsPet(avId) + + #create new pet + gender = petNum % len(PetDNA.PetGenders) + simbase.air.petMgr.createNewPetFromSeed(avId, self.petSeeds[petNum], nameIndex = nameIndex, gender = gender, safeZoneId = zoneId) + self.transactionType = "adopt" + + #deduct the money from the toon's account + bankPrice = min(av.getBankMoney(), cost) + walletPrice = cost - bankPrice + av.b_setBankMoney(av.getBankMoney() - bankPrice) + av.b_setMoney(av.getMoney() - walletPrice) + else: + # perhaps the avatar got disconnected, just leave the fish + # in his tank and let him resell them next time + pass + + def petReturned(self): + assert self.notify.debug('petReturned()') + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPetshopAI.petReturned busy with %s' % (self.busy)) + self.notify.warning("somebody called petReturned that I was not busy with! avId: %s" % avId) + return + + av = simbase.air.doId2do.get(avId) + if av: + # this function deletes the pet + simbase.air.petMgr.deleteToonsPet(avId) + self.transactionType = "return" + else: + # perhaps the avatar got disconnected, just leave the fish + # in his tank and let him resell them next time + pass + + def transactionDone(self): + avId = self.air.getAvatarIdFromSender() + + if self.busy != avId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCPetshopAI.transactionDone busy with %s' % (self.busy)) + self.notify.warning("somebody called transactionDone that I was not busy with! avId: %s" % avId) + return + + av = simbase.air.doId2do.get(avId) + if av: + # Send a movie to say goodbye + if self.transactionType == "adopt": + self.d_setMovie(avId, NPCToons.SELL_MOVIE_PETADOPTED) + elif self.transactionType == "return": + self.d_setMovie(avId, NPCToons.SELL_MOVIE_PETRETURNED) + elif self.transactionType == "": + self.d_setMovie(avId, NPCToons.SELL_MOVIE_PETCANCELED) + + self.sendClearMovie(None) + return + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + self.notify.warning('not busy with avId: %s, busy: %s ' % (avId, self.busy)) + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + diff --git a/toontown/src/toon/DistributedNPCScientist.py b/toontown/src/toon/DistributedNPCScientist.py new file mode 100644 index 0000000..5b18417 --- /dev/null +++ b/toontown/src/toon/DistributedNPCScientist.py @@ -0,0 +1,105 @@ +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +import DistributedNPCToonBase + +class DistributedNPCScientist(DistributedNPCToonBase.DistributedNPCToonBase): + + def __init__(self, cr): + assert self.notify.debug("__init__") + DistributedNPCToonBase.DistributedNPCToonBase.__init__(self, cr) + + def getCollSphereRadius(self): + return 2.5 + + def initPos(self): + #self.clearMat() + self.setHpr(180, 0, 0) + self.setScale(1.0) + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + self.nametag3d.setDepthTest(0) + self.nametag3d.setBin('fixed', 0) + + def setChat(self, topic, partPos, partId, progress, flags): + msg = TTLocalizer.toontownDialogues[topic][(partPos, partId)][progress] + self.setChatMuted(msg, flags) + + def generateToon(self): + """generateToon(self) + Create a toon from dna (an array of strings) + NOTE: DistributedNPCToon overrides this because they do not + need all this extra junk + """ + # set up LOD info + self.setLODs() + # load the toon legs + self.generateToonLegs() + # load the toon head + self.generateToonHead() + # load the toon torso + self.generateToonTorso() + # color the toon as specified by the dna + self.generateToonColor() + self.parentToonParts() + # Make small toons with big heads + self.rescaleToon() + self.resetHeight() + + # Initialize arrays of pointers to useful nodes for the toon + self.rightHands = [] + self.leftHands = [] + self.headParts = [] + self.hipsParts = [] + self.torsoParts = [] + self.legsParts = [] + self.__bookActors = [] + self.__holeActors = [] + + self.setupToonNodes() + if self.style.getTorsoSize() == "short" and self.style.getAnimal() == "duck": + sillyReader = loader.loadModel("phase_4/models/props/tt_m_prp_acs_sillyReader") + for rHand in self.getRightHands(): + placeholder = rHand.attachNewNode("SillyReader") + sillyReader.instanceTo(placeholder) + placeholder.setH(180) + placeholder.setScale(render, 1.0) + placeholder.setPos(0, 0, 0.1) + elif (self.style.getTorsoSize() == "long" and self.style.getAnimal() == "monkey") or \ + (self.style.getTorsoSize() == "medium" and self.style.getAnimal() == "horse"): + clipBoard = loader.loadModel("phase_4/models/props/tt_m_prp_acs_clipboard") + for rHand in self.getRightHands(): + placeholder = rHand.attachNewNode("ClipBoard") + clipBoard.instanceTo(placeholder) + placeholder.setH(180) + placeholder.setScale(render, 1.0) + placeholder.setPos(0, 0, 0.1) + + def startLookAround(self): + """ + Override this method from toonhead because we don't want our scientists looking at anything other + than what the animation specifies + """ + pass + + def scientistPlay(self): + """ + During the scientist play animation + the scientists lose their props + """ + if self.style.getTorsoSize() == "short" and self.style.getAnimal() == "duck": + sillyReaders = self.findAllMatches("**/SillyReader") + for sillyReader in sillyReaders: + if not sillyReader.isEmpty(): + sillyReader.detachNode() + sillyReader = None + elif (self.style.getTorsoSize() == "long" and self.style.getAnimal() == "monkey"): + clipBoards = self.findAllMatches("**/ClipBoard") + #self.setHpr(250, 0, 0) + for clipBoard in clipBoards: + if not clipBoard.isEmpty(): + clipBoard.detachNode() + clipBoard = None \ No newline at end of file diff --git a/toontown/src/toon/DistributedNPCScientistAI.py b/toontown/src/toon/DistributedNPCScientistAI.py new file mode 100644 index 0000000..74350eb --- /dev/null +++ b/toontown/src/toon/DistributedNPCScientistAI.py @@ -0,0 +1,265 @@ +import DistributedNPCToonBaseAI +from toontown.toonbase import TTLocalizer, ToontownGlobals +from direct.fsm import ClassicFSM, State +from direct.task.Task import Task + +class DistributedNPCScientistAI(DistributedNPCToonBaseAI.DistributedNPCToonBaseAI): + + def __init__(self, air, npcId, questCallback=None, hq=0): + DistributedNPCToonBaseAI.DistributedNPCToonBaseAI.__init__(self, air, npcId, questCallback) + + self.scientistFSM = ClassicFSM.ClassicFSM("Scientist", + [State.State('Neutral', + self.enterNeutral, + self.exitNeutral, + ['Phase0', 'Phase1', 'Phase2', 'Phase2_5', 'Phase3', 'Phase4', 'Phase5', 'Off']), + State.State('Phase0', + self.enterPhase0, + self.exitPhase0, + ['Phase1', 'Neutral']), + State.State('Phase1', + self.enterPhase1, + self.exitPhase1, + ['Phase2', 'Neutral']), + State.State('Phase2', + self.enterPhase2, + self.exitPhase2, + ['Phase2_5', 'Neutral']), + State.State('Phase2_5', + self.enterPhase2_5, + self.exitPhase2_5, + ['Phase3', 'Neutral']), + State.State('Phase3', + self.enterPhase3, + self.exitPhase3, + ['Phase4', 'Neutral']), + State.State('Phase4', + self.enterPhase4, + self.exitPhase4, + ['Phase5', 'Neutral']), + State.State('Phase5', + self.enterPhase5, + self.exitPhase5, + ['Neutral']), + State.State('Off', + self.enterOff, + self.exitOff, + []),], + 'Neutral', + 'Off', + ) + + if self.npcId == 2018 or self.npcId == 2019: + self.startAnimState = "ScientistJealous" + elif self.npcId == 2020: + self.startAnimState = "ScientistEmcee" + + self.scientistFSM.enterInitialState() + + def selectPhase(self, newPhase): + try: + if newPhase <=4: + gotoPhase = "0" + elif newPhase <= 6: + gotoPhase = "1" + elif newPhase <= 11: + gotoPhase = "2" + elif newPhase <= 12: + gotoPhase = "2_5" + elif newPhase <= 13: + gotoPhase = "3" + elif newPhase <= 14: + gotoPhase = "4" + elif newPhase <= 15: + gotoPhase = "5" + else: + if not self.scientistFSM.getCurrentState() == self.scientistFSM.getStateNamed('Neutral'): + self.scientistFSM.request('Neutral') + return + gotoPhase = 'Phase'+gotoPhase + if not self.scientistFSM.getCurrentState() == self.scientistFSM.getStateNamed(gotoPhase): + self.scientistFSM.request(gotoPhase) + except: + self.notify.warning('Illegal phase transition requested') + + def startIfNeeded(self): + assert self.notify.debugStateCall(self) + if hasattr(simbase.air, "holidayManager") and simbase.air.holidayManager: + self.curPhase = self.getPhaseToRun() + if self.curPhase != -1: + self.selectPhase(self.curPhase) + + def getPhaseToRun(self): + result = -1 + enoughInfoToRun = False + # first see if the holiday is running, and we can get the cur phase + if ToontownGlobals.SILLYMETER_HOLIDAY in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY] != None \ + and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY].getRunningState(): + if hasattr(simbase.air, "SillyMeterMgr"): + enoughInfoToRun = True + else: + self.notify.debug("simbase.air does not have SillyMeterMgr") + else: + self.notify.debug("holiday is not running") + self.notify.debug("enoughInfoToRun = %s" % enoughInfoToRun) + if enoughInfoToRun and \ + simbase.air.SillyMeterMgr.getIsRunning(): + result = simbase.air.SillyMeterMgr.getCurPhase() + + return result + + def enterNeutral(self): + """ + Enter the phase one dialog + """ + self.accept("SillyMeterPhase", self.selectPhase) + self.startIfNeeded() + + def exitNeutral(self): + """ + Clean up + """ + self.ignore("SillyMeterPhase") + + def enterPhase0(self): + """ + Enter the second phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase1Topic) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase0(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase1Topic) + self.ignore("SillyMeterPhase") + + def enterPhase1(self): + """ + Enter the second phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase2Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistJealous", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase1(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase2Topic) + self.ignore("SillyMeterPhase") + + def enterPhase2(self): + """ + Enter the third phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase3Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistWork", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase2(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase3Topic) + self.ignore("SillyMeterPhase") + + def enterPhase2_5(self): + """ + Enter the third phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase3_5Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistLessWork", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase2_5(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase3_5Topic) + self.ignore("SillyMeterPhase") + + def enterPhase3(self): + """ + Enter the third phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase4Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistPlay", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase3(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase4Topic) + self.ignore("SillyMeterPhase") + + def enterPhase4(self): + """ + Enter the fourth phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase5Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistPlay", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase4(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase5Topic) + self.ignore("SillyMeterPhase") + + def enterPhase5(self): + """ + Enter the fourth phase of silly + """ + if self.npcId == 2020: + self.air.dialogueManager.requestDialogue(self, TTLocalizer.EmceeDialoguePhase6Topic) + elif self.npcId == 2018 or self.npcId == 2019: + self.d_setAnimState("ScientistPlay", 1.) + self.accept("SillyMeterPhase", self.selectPhase) + + def exitPhase5(self): + """ + Clean up + """ + if self.npcId == 2020: + self.air.dialogueManager.leaveDialogue(self, TTLocalizer.EmceeDialoguePhase6Topic) + self.ignore("SillyMeterPhase") + + def enterOff(self): + """ + Turn it off + """ + pass + + def exitOff(self): + """ + Clean up + """ + pass + + def delete(self): + self.scientistFSM.requestFinalState() + if hasattr(self, 'scientistFSM'): + del self.scientistFSM + DistributedNPCToonBaseAI.DistributedNPCToonBaseAI.delete(self) + \ No newline at end of file diff --git a/toontown/src/toon/DistributedNPCSpecialQuestGiver.py b/toontown/src/toon/DistributedNPCSpecialQuestGiver.py new file mode 100644 index 0000000..d68a744 --- /dev/null +++ b/toontown/src/toon/DistributedNPCSpecialQuestGiver.py @@ -0,0 +1,352 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from toontown.quest import QuestParser +from toontown.quest import QuestChoiceGui +from toontown.quest import TrackChoiceGui +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + +ChoiceTimeout = 20 + +class DistributedNPCSpecialQuestGiver(DistributedNPCToonBase): + + def __init__(self, cr): + assert self.notify.debug("__init__") + DistributedNPCToonBase.__init__(self, cr) + self.curQuestMovie = None + self.questChoiceGui = None + self.trackChoiceGui = None + + def announceGenerate(self): + self.setAnimState("neutral", 0.9, None, None) + + npcOrigin = self.cr.playGame.hood.loader.geom.find("**/npc_origin_" + `self.posIndex`) + + # Now he's no longer parented to render, but no one minds. + if not npcOrigin.isEmpty(): + self.reparentTo(npcOrigin) + self.clearMat() + else: + self.notify.warning("announceGenerate: Could not find npc_origin_" + str(self.posIndex)) + + DistributedNPCToonBase.announceGenerate(self) + + def delayDelete(self): + DistributedNPCToonBase.delayDelete(self) + # if there are situations where a quest movie should be able to stick around + # after the NPC is deleted, this will need to change + if self.curQuestMovie: + curQuestMovie = self.curQuestMovie + # do this here in case we get deleted in the call to movie.cleanup() + self.curQuestMovie = None + curQuestMovie.timeout(fFinish = 1) + curQuestMovie.cleanup() + + def disable(self): + self.cleanupMovie() + DistributedNPCToonBase.disable(self) + + def cleanupMovie(self): + self.clearChat() + # Kill any quest choice guis that may be active + self.ignore("chooseQuest") + if self.questChoiceGui: + self.questChoiceGui.destroy() + self.questChoiceGui = None + # Kill any movies that may be playing + self.ignore(self.uniqueName("doneChatPage")) + if self.curQuestMovie: + self.curQuestMovie.timeout(fFinish = 1) + self.curQuestMovie.cleanup() + self.curQuestMovie = None + # Kill any track choice guis that may be active + if self.trackChoiceGui: + self.trackChoiceGui.destroy() + self.trackChoiceGui = None + + def allowedToTalk(self): + """Check if the local toon is allowed to talk to this NPC.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + # if we're in the estate we should use place.id + if hasattr(place, 'id'): + myHoodId = place.id + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ToontownGlobals.Tutorial + ): + return True + return False + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + if self.allowedToTalk(): + # Lock down the avatar for quest mode + base.cr.playGame.getPlace().fsm.request('quest', [self]) + # Tell the server + self.sendUpdate("avatarEnter", []) + # make sure this NPCs chat balloon is visible above all others for th elocekd down avatar + self.nametag3d.setDepthTest(0) + self.nametag3d.setBin('fixed', 0) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='quests', + doneFunc=self.handleOkTeaser) + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + def finishMovie(self, av, isLocalToon, elapsedTime): + """ + Final cleanup for a movie that has finished + """ + self.cleanupMovie() + av.startLookAround() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + if isLocalToon: + taskMgr.remove(self.uniqueName("lerpCamera")) + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + # Tell the AI we are done now + self.sendUpdate("setMovieDone", []) + # set the name tag back to normal rendering + self.nametag3d.clearDepthTest() + self.nametag3d.clearBin() + + def setupCamera(self, mode): + camera.wrtReparentTo(render) + if ((mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE) or + (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE)): + camera.lerpPosHpr(5, 9, self.getHeight()-0.5, 155, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName("lerpCamera")) + else: + camera.lerpPosHpr(-5, 9, self.getHeight()-0.5, -150, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName("lerpCamera")) + + + def setMovie(self, mode, npcId, avId, quests, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + + # See if this is the local toon + isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s %s %s" % + (mode, npcId, avId, quests, timeStamp, isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.QUEST_MOVIE_CLEAR): + assert self.notify.debug("setMovie: movie cleared") + self.cleanupMovie() + return + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.QUEST_MOVIE_TIMEOUT): + assert self.notify.debug("setMovie: movie timeout") + self.cleanupMovie() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if isLocalToon: + self.freeAvatar() + # Act like we finished the chat pages by setting the page number to -1 + self.setPageNumber(0,-1) + self.clearChat() + self.startLookAround() + self.detectAvatars() + return + + av = base.cr.doId2do.get(avId) + if av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + + # Reject is simpler, so lets get that out of the way + if (mode == NPCToons.QUEST_MOVIE_REJECT): + rejectString = Quests.chooseQuestDialogReject() + rejectString = Quests.fillInQuestNames(rejectString, avName = av.name) + # No need for page chat here, just setChatAbsolute + self.setChatAbsolute(rejectString, CFSpeech | CFTimeout) + if isLocalToon: + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + return + + # Reject is simpler, so lets get that out of the way + if (mode == NPCToons.QUEST_MOVIE_TIER_NOT_DONE): + rejectString = Quests.chooseQuestDialogTierNotDone() + rejectString = Quests.fillInQuestNames(rejectString, avName = av.name) + # No need for page chat here, just setChatAbsolute + self.setChatAbsolute(rejectString, CFSpeech | CFTimeout) + if isLocalToon: + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + return + + self.setupAvatars(av) + + fullString = "" + toNpcId = None + if (mode == NPCToons.QUEST_MOVIE_COMPLETE): + questId, rewardId, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_complete_" + str(questId) + if QuestParser.questDefined(scriptId): + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + if greetingString: + fullString += greetingString + "\a" + fullString += Quests.chooseQuestDialog(questId, Quests.COMPLETE) + "\a" + if rewardId: + fullString += Quests.getReward(rewardId).getString() + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE_CANCEL): + fullString = TTLocalizer.QuestMovieQuestChoiceCancel + + elif (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE_CANCEL): + fullString = TTLocalizer.QuestMovieTrackChoiceCancel + + elif (mode == NPCToons.QUEST_MOVIE_INCOMPLETE): + questId, completeStatus, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_incomplete_" + str(questId) + if QuestParser.questDefined(scriptId): + if self.curQuestMovie: + self.curQuestMovie.timeout() + self.curQuestMovie.cleanup() + self.curQuestMovie = None + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + if greetingString: + fullString += greetingString + "\a" + + fullString += Quests.chooseQuestDialog(questId, completeStatus) + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_ASSIGN): + questId, rewardId, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_assign_" + str(questId) + if QuestParser.questDefined(scriptId): + if self.curQuestMovie: + self.curQuestMovie.timeout() + self.curQuestMovie.cleanup() + self.curQuestMovie = None + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + #greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + #if greetingString: + # fullString += greetingString + "\a" + fullString += Quests.chooseQuestDialog(questId, Quests.QUEST) + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE): + # Quest choice movie + if isLocalToon: + self.setupCamera(mode) + assert self.notify.debug("QUEST_MOVIE_QUEST_CHOICE: %s" % quests) + self.setChatAbsolute(TTLocalizer.QuestMovieQuestChoice, CFSpeech) + if isLocalToon: + self.acceptOnce("chooseQuest", self.sendChooseQuest) + self.questChoiceGui = QuestChoiceGui.QuestChoiceGui() + self.questChoiceGui.setQuests(quests, npcId, ChoiceTimeout) + return + + elif (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE): + # If this is a TrackChoiceQuest, complete simply means we are at the + # avatar that will allow us to chose. If the localToon cancels the + # choice, we are not really complete yet + # In this case, the quests are really the track choices + if isLocalToon: + self.setupCamera(mode) + tracks = quests + assert self.notify.debug("QUEST_MOVIE_TRACK_CHOICE: %s" % tracks) + self.setChatAbsolute(TTLocalizer.QuestMovieTrackChoice, CFSpeech) + if isLocalToon: + self.acceptOnce("chooseTrack", self.sendChooseTrack) + self.trackChoiceGui = TrackChoiceGui.TrackChoiceGui(tracks, ChoiceTimeout) + return + + fullString = Quests.fillInQuestNames(fullString, + avName = av.name, + fromNpcId = npcId, + toNpcId = toNpcId) + + self.acceptOnce(self.uniqueName("doneChatPage"), + self.finishMovie, extraArgs = [av, isLocalToon]) + self.setPageChat(avId, 0, fullString, 1) + + def sendChooseQuest(self, questId): + """ + This is the callback from the questChoiceGui event + Cleanup the gui and send the message with our choice to the AI + """ + if self.questChoiceGui: + self.questChoiceGui.destroy() + self.questChoiceGui = None + self.sendUpdate("chooseQuest", [questId]) + + def sendChooseTrack(self, trackId): + """ + This is the callback from the trackChoiceGui event + Cleanup the gui and send the message with our choice to the AI + """ + if self.trackChoiceGui: + self.trackChoiceGui.destroy() + self.trackChoiceGui = None + self.sendUpdate("chooseTrack", [trackId]) diff --git a/toontown/src/toon/DistributedNPCSpecialQuestGiverAI.py b/toontown/src/toon/DistributedNPCSpecialQuestGiverAI.py new file mode 100644 index 0000000..c64694f --- /dev/null +++ b/toontown/src/toon/DistributedNPCSpecialQuestGiverAI.py @@ -0,0 +1,273 @@ + +from otp.ai.AIBaseGlobal import * +from direct.task.Task import Task +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +from toontown.quest import Quests + +class DistributedNPCSpecialQuestGiverAI(DistributedNPCToonBaseAI): + + def __init__(self, air, npcId, questCallback=None, hq=0): + DistributedNPCToonBaseAI.__init__(self, air, npcId, questCallback) + # Am I a hq toon? Maybe this should be a subclass? + self.hq = hq + # Am I part of the tutorial? + self.tutorial = 0 + # Initialize the pendingAvId to None in case we get any rogue messages + self.pendingAvId = None + + def getTutorial(self): + return self.tutorial + + def setTutorial(self, val): + # If you are in the tutorial you have no timeouts + self.tutorial = val + + def getHq(self): + return self.hq + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + self.notify.debug("avatar enter " + str(avId)) + # Let the quest manager figure out what to do from here on + self.air.questManager.requestInteract(avId, self) + DistributedNPCToonBaseAI.avatarEnter(self) + + def chooseQuest(self, questId, quest = None): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("chooseQuest: avatar %s choseQuest %s" % (avId, questId)) + + # Sanity check, this should not happen + if (not self.pendingAvId): + self.notify.warning("chooseQuest: not expecting an answer from any avatar: %s" % (avId)) + return + if (self.pendingAvId != avId): + self.notify.warning("chooseQuest: not expecting an answer from this avatar: %s" % (avId)) + return + + # See if the avatar cancelled + if questId == 0: + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + # Tell the quest manager this avatar cancelled + self.air.questManager.avatarCancelled(avId) + # Tell the avatar goodbye then allow him to finish the movie + self.cancelChoseQuest(avId) + return + + # See if the avatar chose any of the quests offered + for quest in self.pendingQuests: + if (questId == quest[0]): + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + # Let the quest manager figure out what to do from here on + self.air.questManager.avatarChoseQuest(avId, self, *quest) + return + + self.air.questManager.avatarChoseQuest(avId, self, *quest) + + # If we got here, something is wrong, handle it gracefully + self.notify.warning("chooseQuest: avatar: %s chose a quest not offered: %s" % (avId, questId)) + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + return + + def chooseTrack(self, trackId): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("chooseTrack: avatar %s choseTrack %s" % (avId, trackId)) + + if (not self.pendingAvId): + self.notify.warning("chooseTrack: not expecting an answer from any avatar: %s" % (avId)) + return + if (self.pendingAvId != avId): + self.notify.warning("chooseTrack: not expecting an answer from this avatar: %s" % (avId)) + return + + # See if the avatar cancelled + if trackId == -1: + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + # Tell the quest manager this avatar cancelled + self.air.questManager.avatarCancelled(avId) + # Tell the avatar goodbye then allow him to finish the movie + self.cancelChoseTrack(avId) + return + + # See if the avatar chose any of the tracks offered + for track in self.pendingTracks: + if (trackId == track): + # Let the quest manager figure out what to do from here on + self.air.questManager.avatarChoseTrack(avId, self, self.pendingTrackQuest, trackId) + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + return + + # If we got here, something is wrong, handle it gracefully + self.notify.warning("chooseTrack: avatar: %s chose a track not offered: %s" % (avId, trackId)) + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + return + + + def sendTimeoutMovie(self, task): + # Clear the movie + self.pendingAvId = None + self.pendingQuests = None + self.pendingTracks = None + self.pendingTrackQuest = None + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TIMEOUT, + self.npcId, self.busy, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + self.busy = 0 + return Task.done + + def sendClearMovie(self, task): + # Clear the movie + self.pendingAvId = None + self.pendingQuests = None + self.pendingTracks = None + self.pendingTrackQuest = None + self.busy = 0 + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_CLEAR, + self.npcId, 0, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + return Task.done + + def rejectAvatar(self, avId): + self.busy = avId + # Send a movie to reject the avatar with time stamp + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_REJECT, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # No timeout here because we do not wait for the toon to click on rejects + # Just send a clear after a pause + # + # We actually need a longer pause here - people need to read the text before + # clearMovie wipes it -grw + if not self.tutorial: + taskMgr.doMethodLater(5.5, self.sendClearMovie, self.uniqueName("clearMovie")) + return + + def rejectAvatarTierNotDone(self, avId): + self.busy = avId + # Send a movie to reject the avatar with time stamp + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TIER_NOT_DONE, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # No timeout here because we do not wait for the toon to click on rejects + # Just send a clear after a pause + # + # We actually need a longer pause here - people need to read the text before + # clearMovie wipes it -grw + if not self.tutorial: + taskMgr.doMethodLater(5.5, self.sendClearMovie, self.uniqueName("clearMovie")) + return + + def completeQuest(self, avId, questId, rewardId): + self.busy = avId + # nextQuestId will be the npc for the next visiting quest (visitNpcId) + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_COMPLETE, + self.npcId, avId, [questId, rewardId, 0], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def incompleteQuest(self, avId, questId, completeStatus, toNpcId): + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_INCOMPLETE, + self.npcId, avId, [questId, completeStatus, toNpcId], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def assignQuest(self, avId, questId, rewardId, toNpcId): + self.busy = avId + # Call the quest callback now. We could wait until the movie + # is over, but I don't think we need to. + if self.questCallback: + self.questCallback() + #print "assignQuest", avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_ASSIGN, + self.npcId, avId, [questId, rewardId, toNpcId], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def presentQuestChoice(self, avId, quests): + self.busy = avId + self.pendingAvId = avId + self.pendingQuests = quests + flatQuests = [] + for quest in quests: + flatQuests.extend(quest) + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_QUEST_CHOICE, + self.npcId, avId, flatQuests, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def presentTrackChoice(self, avId, questId, tracks): + self.busy = avId + self.pendingAvId = avId + self.pendingTracks = tracks + self.pendingTrackQuest = questId + # Send a movie to present the choice to the avatar + # Instead of quests, we send the trackIds + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TRACK_CHOICE, + self.npcId, avId, tracks, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def cancelChoseQuest(self, avId): + self.busy = avId + # Send a movie to present the choice to the avatar + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_QUEST_CHOICE_CANCEL, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def cancelChoseTrack(self, avId): + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TRACK_CHOICE_CANCEL, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def setMovieDone(self): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("setMovieDone busy: %s avId: %s" % (self.busy, avId)) + if self.busy == avId: + # Kill all pending doLaters that will clear the movie + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + elif self.busy: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCToonAI.setMovieDone busy with %s' % (self.busy)) + self.notify.warning("somebody called setMovieDone that I was not busy with! avId: %s" % avId) diff --git a/toontown/src/toon/DistributedNPCTailor.py b/toontown/src/toon/DistributedNPCTailor.py new file mode 100644 index 0000000..52a3a20 --- /dev/null +++ b/toontown/src/toon/DistributedNPCTailor.py @@ -0,0 +1,416 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +from direct.task.Task import Task +import TailorClothesGUI +from toontown.toonbase import TTLocalizer +import ToonDNA +from toontown.estate import ClosetGlobals + +class DistributedNPCTailor(DistributedNPCToonBase): + + def __init__(self, cr): + DistributedNPCToonBase.__init__(self, cr) + self.isLocalToon = 0 + self.clothesGUI = None + self.av = None + self.oldStyle = None + self.browsing = 0 + self.roomAvailable = 0 + self.button = None + self.popupInfo = None + + def disable(self): + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPurchaseGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.clothesGUI: + self.clothesGUI.exit() + self.clothesGUI.unload() + self.clothesGUI = None + if (self.button != None): + self.button.destroy() + del self.button + self.cancelButton.destroy() + del self.cancelButton + del self.gui + self.counter.show() + del self.counter + if self.popupInfo: + self.popupInfo.destroy() + self.popupInfo = None + self.av = None + self.oldStyle = None + base.localAvatar.posCamera(0, 0) + DistributedNPCToonBase.disable(self) + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + # Lock down the avatar for purchase mode + base.cr.playGame.getPlace().fsm.request('purchase') + # Tell the server + self.sendUpdate("avatarEnter", []) + + def __handleUnexpectedExit(self): + self.notify.warning('unexpected exit') + self.av = None + self.oldStyle = None + + def resetTailor(self): + assert self.notify.debug('resetTailor') + self.ignoreAll() + taskMgr.remove(self.uniqueName('popupPurchaseGUI')) + taskMgr.remove(self.uniqueName('lerpCamera')) + if self.clothesGUI: + self.clothesGUI.hideButtons() + self.clothesGUI.exit() + self.clothesGUI.unload() + self.clothesGUI = None + if (self.button != None): + self.button.destroy() + del self.button + self.cancelButton.destroy() + del self.cancelButton + del self.gui + self.counter.show() + del self.counter + self.show() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.clearMat() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if (self.isLocalToon): + self.freeAvatar() + return Task.done + + def setMovie(self, mode, npcId, avId, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp + + self.npcId = npcId + + # See if this is the local toon + self.isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s" % + (mode, avId, timeStamp, self.isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.PURCHASE_MOVIE_CLEAR): + assert self.notify.debug('PURCHASE_MOVIE_CLEAR') + return + + if (mode == NPCToons.PURCHASE_MOVIE_TIMEOUT): + assert self.notify.debug('PURCHASE_MOVIE_TIMEOUT') + # In case the GUI hasn't popped up yet + taskMgr.remove(self.uniqueName('lerpCamera')) + # Stop listening for the GUI + if (self.isLocalToon): + self.ignore(self.purchaseDoneEvent) + self.ignore(self.swapEvent) + # hide the popupInfo + if self.popupInfo: + self.popupInfo.reparentTo(hidden) + # See if a button was pressed first + if self.clothesGUI: + self.clothesGUI.resetClothes(self.oldStyle) + self.__handlePurchaseDone(timeout = 1) + self.setChatAbsolute(TTLocalizer.STOREOWNER_TOOKTOOLONG, + CFSpeech | CFTimeout) + self.resetTailor() + + elif (mode == NPCToons.PURCHASE_MOVIE_START or + mode == NPCToons.PURCHASE_MOVIE_START_BROWSE or + mode == NPCToons.PURCHASE_MOVIE_START_NOROOM): + assert self.notify.debug('PURCHASE_MOVIE_START') + + if (mode == NPCToons.PURCHASE_MOVIE_START): + self.browsing = 0 + self.roomAvailable = 1 + elif (mode == NPCToons.PURCHASE_MOVIE_START_BROWSE): + self.browsing = 1 + self.roomAvailable = 1 + elif (mode == NPCToons.PURCHASE_MOVIE_START_NOROOM): + self.browsing = 0 + self.roomAvailable = 0 + + self.av = base.cr.doId2do.get(avId) + if self.av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + else: + self.accept(self.av.uniqueName('disable'), + self.__handleUnexpectedExit) + + style = self.av.getStyle() + self.oldStyle = ToonDNA.ToonDNA() + self.oldStyle.makeFromNetString(style.makeNetString()) + self.setupAvatars(self.av) + + if (self.isLocalToon): + camera.wrtReparentTo(render) + camera.lerpPosHpr(-5, 9, self.getHeight()-0.5, -150, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName('lerpCamera')) + + if (self.browsing == 0): + if (self.roomAvailable == 0): + self.setChatAbsolute(TTLocalizer.STOREOWNER_NOROOM, + CFSpeech | CFTimeout) + else: + self.setChatAbsolute(TTLocalizer.STOREOWNER_GREETING, + CFSpeech | CFTimeout) + else: + self.setChatAbsolute(TTLocalizer.STOREOWNER_BROWSING, + CFSpeech | CFTimeout) + + if (self.isLocalToon): + taskMgr.doMethodLater(3.0, self.popupPurchaseGUI, + self.uniqueName('popupPurchaseGUI')) + # print out our clothes and closet information before we start + print ("-----------Starting tailor interaction-----------") + print "avid: %s, gender: %s" % (self.av.doId, self.av.style.gender) + print "current top = %s,%s,%s,%s and bot = %s,%s," % (self.av.style.topTex, self.av.style.topTexColor, + self.av.style.sleeveTex, self.av.style.sleeveTexColor, + self.av.style.botTex, self.av.style.botTexColor) + print "topsList = %s" % self.av.getClothesTopsList() + print "bottomsList = %s" % self.av.getClothesBottomsList() + print ("-------------------------------------------------") + + + elif (mode == NPCToons.PURCHASE_MOVIE_COMPLETE): + assert self.notify.debug('PURCHASE_MOVIE_COMPLETE') + self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, + CFSpeech | CFTimeout) + + if self.av and self.isLocalToon: + # print out our clothes and closet information before we start + print ("-----------ending tailor interaction-----------") + print "avid: %s, gender: %s" % (self.av.doId, self.av.style.gender) + print "current top = %s,%s,%s,%s and bot = %s,%s," % (self.av.style.topTex, self.av.style.topTexColor, + self.av.style.sleeveTex, self.av.style.sleeveTexColor, + self.av.style.botTex, self.av.style.botTexColor) + print "topsList = %s" % self.av.getClothesTopsList() + print "bottomsList = %s" % self.av.getClothesBottomsList() + print ("-------------------------------------------------") + + self.resetTailor() + + elif (mode == NPCToons.PURCHASE_MOVIE_NO_MONEY): + self.notify.warning('PURCHASE_MOVIE_NO_MONEY should not be called') + self.resetTailor() + + return + + def popupPurchaseGUI(self, task): + assert self.notify.debug('popupPurchaseGUI()') + self.setChatAbsolute('', CFSpeech) + self.purchaseDoneEvent = 'purchaseDone' + self.swapEvent = 'swap' + self.acceptOnce(self.purchaseDoneEvent, self.__handlePurchaseDone) + self.accept(self.swapEvent, self.__handleSwap) + self.clothesGUI = TailorClothesGUI.TailorClothesGUI(self.purchaseDoneEvent, + self.swapEvent, self.npcId) + self.clothesGUI.load() + self.clothesGUI.enter(self.av) + self.clothesGUI.showButtons() + + self.gui = loader.loadModel("phase_3/models/gui/create_a_toon_gui") + # Only bring up the purchase button if there's a clothing ticket + + if self.browsing == 0: + self.button = DirectButton( + relief = None, + image = (self.gui.find("**/CrtAtoon_Btn1_UP"), + self.gui.find("**/CrtAtoon_Btn1_DOWN"), + self.gui.find("**/CrtAtoon_Btn1_RLLVR"), + ), + pos = (-0.15, 0, -0.85), + command = self.__handleButton, + text = ("", TTLocalizer.MakeAToonDone, + TTLocalizer.MakeAToonDone), + text_font = ToontownGlobals.getInterfaceFont(), + text_scale = 0.08, + text_pos = (0,-0.03), + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + ) + else: + self.button = None + + self.cancelButton = DirectButton( + relief = None, + image = (self.gui.find("**/CrtAtoon_Btn2_UP"), + self.gui.find("**/CrtAtoon_Btn2_DOWN"), + self.gui.find("**/CrtAtoon_Btn2_RLLVR"), + ), + pos = (0.15, 0, -0.85), + command = self.__handleCancel, + text = ("", TTLocalizer.MakeAToonCancel, + TTLocalizer.MakeAToonCancel), + text_font = ToontownGlobals.getInterfaceFont(), + text_scale = 0.08, + text_pos = (0,-0.03), + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + ) + + camera.setPosHpr(base.localAvatar, + -4.16, 8.25, 2.47, -152.89, 0.00, 0.00) + # Ugly: find counter so you can hide it from toon's view + self.counter = render.find('**/*mo1_TI_counter') + self.counter.hide() + + # Hide the tailor, who will likely block the view of + # ourselves. + self.hide() + + return Task.done + + def __handleButton(self): + messenger.send('next') + + def __handleCancel(self): + self.clothesGUI.resetClothes(self.oldStyle) + messenger.send('last') + + def __handleSwap(self): + assert self.notify.debug("__handleSwap") + self.d_setDNA(self.av.getStyle().makeNetString(), 0) + + def __handlePurchaseDone(self, timeout = 0): + """ + This is the callback from the Purchase object + Cleanup the gui and send the message to the AI + """ + assert self.notify.debug('handlePurchaseDone()') + if (self.clothesGUI.doneStatus == 'last' or timeout == 1): + # The client really does not need to send the DNA here + # since the server is keeping track of it + self.d_setDNA(self.oldStyle.makeNetString(), 1) + else: + # The client really does not need to send the DNA here + # since the server is keeping track of it + + # check if we ever changed the shorts or shirt + # create a bit string that identifies which items + # have been changed + # bit 0 = shirts + # bit 1 = shorts + # bit 2...unused + + which = 0 + if self.clothesGUI.topChoice != -1: + which = which | ClosetGlobals.SHIRT + if self.clothesGUI.bottomChoice != -1: + which = which | ClosetGlobals.SHORTS + print "setDNA: which = %d, top = %d, bot = %d" % (which, self.clothesGUI.topChoice, self.clothesGUI.bottomChoice) + # if the closet is full or almost full, confirm that we want to lose the + # clothes we are wearing + if self.roomAvailable == 0: + if (self.isLocalToon): + if (self.av.isClosetFull() or + (which & ClosetGlobals.SHIRT and which & ClosetGlobals.SHORTS)): + # the closet is at max capacity + # or it's almost full and we are changing both our top and bottom + self.__enterConfirmLoss(2, which) + self.clothesGUI.hideButtons() + self.button.hide() + self.cancelButton.hide() + else: + self.d_setDNA(self.av.getStyle().makeNetString(), 2, which) + else: + self.d_setDNA(self.av.getStyle().makeNetString(), 2, which) + + def __enterConfirmLoss(self, finished, which): + if self.popupInfo == None: + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')) + cancelButtonImage = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')) + # Popup a message warning that you will lose current clothes + self.popupInfo = DirectFrame( + parent = hidden, + relief = None, + state = 'normal', + text = TTLocalizer.STOREOWNER_CONFIRM_LOSS, + text_wordwrap = 10, + textMayChange = 0, + frameSize = (-1,1,-1,1), + text_pos = (0, -0.05), + geom = DGG.getDefaultDialogGeom(), + geom_color = ToontownGlobals.GlobalDialogColor, + geom_scale = (.88, 1, .55), + geom_pos = (0,0,-.18), + text_scale = .08, + ) + DirectButton(self.popupInfo, + image = okButtonImage, + relief = None, + text = TTLocalizer.STOREOWNER_OK, + text_scale = 0.05, + text_pos = (0.0, -0.1), + textMayChange = 0, + pos = (-0.08, 0.0, -0.31), + command = self.__handleConfirmLossOK, + extraArgs = [finished, which]) + DirectButton(self.popupInfo, + image = cancelButtonImage, + relief = None, + text = TTLocalizer.STOREOWNER_CANCEL, + text_scale = 0.05, + text_pos = (0.0, -0.1), + textMayChange = 0, + pos = (0.08, 0.0, -0.31), + command = self.__handleConfirmLossCancel) + buttons.removeNode() + # Show the confim loss popup + self.popupInfo.reparentTo(aspect2d) + + def __handleConfirmLossOK(self, finished, which): + self.d_setDNA(self.av.getStyle().makeNetString(), finished, which) + self.popupInfo.reparentTo(hidden) + + def __handleConfirmLossCancel(self): + self.d_setDNA(self.oldStyle.makeNetString(), 1) + self.popupInfo.reparentTo(hidden) + + + def d_setDNA(self, dnaString, finished, whichItems = ClosetGlobals.SHIRT | ClosetGlobals.SHORTS): + # Report our DNA to the server + self.sendUpdate('setDNA', [dnaString, finished, whichItems]) + + + def setCustomerDNA(self, avId, dnaString): + assert self.notify.debug("setCustomerDNA") + # The AI doesn't set the DNA on swaps (finished=0) anymore. + # This is to avoid bugged clothes on AI crashes while browsing. Now the AI + # just tells the clients the correct DNA for the current customer + # and lets the clients set the value directly on the distributed + # toon. The AI will still do a DNA change on purchase. + + # the av might be gone, so check first + if avId != base.localAvatar.doId: + av = base.cr.doId2do.get(avId, None) + if av: + if self.av == av: + self.av.style.makeFromNetString(dnaString) + self.av.generateToonClothes() diff --git a/toontown/src/toon/DistributedNPCTailorAI.py b/toontown/src/toon/DistributedNPCTailorAI.py new file mode 100644 index 0000000..78fcfa9 --- /dev/null +++ b/toontown/src/toon/DistributedNPCTailorAI.py @@ -0,0 +1,242 @@ + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * + +import ToonDNA +from direct.task.Task import Task +from toontown.ai import DatabaseObject +from toontown.estate import ClosetGlobals + +class DistributedNPCTailorAI(DistributedNPCToonBaseAI): + freeClothes = simbase.config.GetBool('free-clothes', 0) + housingEnabled = simbase.config.GetBool('want-housing', 1) + def __init__(self, air, npcId): + DistributedNPCToonBaseAI.__init__(self, air, npcId) + self.timedOut = 0 + # Tailors are not in the business of giving out quests + self.givesQuests = 0 + self.customerDNA = None + self.customerId = None + + def getTailor(self): + return 1 + + def delete(self): + taskMgr.remove(self.uniqueName('clearMovie')) + self.ignoreAll() + self.customerDNA = None + self.customerId = None + DistributedNPCToonBaseAI.delete(self) + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + assert self.notify.debug("avatar enter " + str(avId)) + + if (not self.air.doId2do.has_key(avId)): + self.notify.warning("Avatar: %s not found" % (avId)) + return + + if (self.isBusy()): + self.freeAvatar(avId) + return + + # Store the original customer DNA so we can revert if a disconnect + # happens + av = self.air.doId2do[avId] + self.customerDNA = ToonDNA.ToonDNA() + self.customerDNA.makeFromNetString(av.getDNAString()) + self.customerId = avId + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + flag = NPCToons.PURCHASE_MOVIE_START_BROWSE + if self.freeClothes: + flag = NPCToons.PURCHASE_MOVIE_START + if self.housingEnabled and self.isClosetAlmostFull(av): + flag = NPCToons.PURCHASE_MOVIE_START_NOROOM + elif (self.air.questManager.hasTailorClothingTicket(av, self) == 1): + flag = NPCToons.PURCHASE_MOVIE_START + if self.housingEnabled and self.isClosetAlmostFull(av): + flag = NPCToons.PURCHASE_MOVIE_START_NOROOM + elif(self.air.questManager.hasTailorClothingTicket(av, self) == 2): + flag = NPCToons.PURCHASE_MOVIE_START + if self.housingEnabled and self.isClosetAlmostFull(av): + flag = NPCToons.PURCHASE_MOVIE_START_NOROOM + + self.sendShoppingMovie(avId, flag) + DistributedNPCToonBaseAI.avatarEnter(self) + + def isClosetAlmostFull(self, av): + numClothes = len(av.clothesTopsList)/4 + len(av.clothesBottomsList)/2 + if numClothes >= av.maxClothes-1: + return 1 + return 0 + + def sendShoppingMovie(self, avId, flag): + assert self.notify.debug('sendShoppingMovie()') + self.busy = avId + self.sendUpdate("setMovie", [flag, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + # Timeout + taskMgr.doMethodLater(NPCToons.TAILOR_COUNTDOWN_TIME, + self.sendTimeoutMovie, + self.uniqueName('clearMovie')) + + + def rejectAvatar(self, avId): + self.notify.warning("rejectAvatar: should not be called by a Tailor!") + return + + def sendTimeoutMovie(self, task): + assert self.notify.debug('sendTimeoutMovie()') + # The timeout has expired. Restore the client back to his + # original DNA automatically (instead of waiting for the + # client to request this). + + toon = self.air.doId2do.get(self.customerId) + # On second thought, we're better off not asserting this. + #assert(self.busy == self.customerId) + if (toon != None and self.customerDNA): + toon.b_setDNAString(self.customerDNA.makeNetString()) + # Hmm, suppose the toon has logged out at the same time? + # Is it possible to miss this update due to a race + # condition? + + self.timedOut = 1 + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_TIMEOUT, + self.npcId, self.busy, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + + self.sendClearMovie(None) + return Task.done + + def sendClearMovie(self, task): + assert self.notify.debug('sendClearMovie()') + # Ignore unexpected exits on whoever I was busy with + self.ignore(self.air.getAvatarExitEvent(self.busy)) + self.customerDNA = None + self.customerId = None + self.busy = 0 + self.timedOut = 0 + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_CLEAR, + self.npcId, 0, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendUpdate("setCustomerDNA", [0,'']) + return Task.done + + def completePurchase(self, avId): + assert self.notify.debug('completePurchase()') + self.busy = avId + # Send a movie to reward the avatar + self.sendUpdate("setMovie", [NPCToons.PURCHASE_MOVIE_COMPLETE, + self.npcId, avId, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + return + + def setDNA(self, blob, finished, which): + assert self.notify.debug('setDNA(): %s' % self.timedOut) + avId = self.air.getAvatarIdFromSender() + if avId != self.customerId: + if self.customerId: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCTailorAI.setDNA customer is %s' % (self.customerId)) + self.notify.warning("customerId: %s, but got setDNA for: %s" % (self.customerId, avId)) + return + + # make sure the DNA is valid + testDNA = ToonDNA.ToonDNA() + if not testDNA.isValidNetString(blob): + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCTailorAI.setDNA: invalid dna: %s' % blob) + return + + if (self.air.doId2do.has_key(avId)): + av = self.air.doId2do[avId] + if (finished == 2 and which > 0): + # Make sure client was actually able to purchase + if (self.air.questManager.removeClothingTicket(av, self) == 1 or self.freeClothes): + assert self.notify.debug('Successful purchase') + # No need to set the dna, it should already be set + av.b_setDNAString(blob) + # SDN: only add clothes if they have been changed (i.e. if (which & n) == 1) + if which & ClosetGlobals.SHIRT: + if (av.addToClothesTopsList(self.customerDNA.topTex, + self.customerDNA.topTexColor, + self.customerDNA.sleeveTex, + self.customerDNA.sleeveTexColor) == 1): + av.b_setClothesTopsList(av.getClothesTopsList()) + else: + self.notify.warning('NPCTailor: setDNA() - unable to save old tops - we exceeded the tops list length') + if which & ClosetGlobals.SHORTS: + if (av.addToClothesBottomsList(self.customerDNA.botTex, + self.customerDNA.botTexColor) == 1): + av.b_setClothesBottomsList(av.getClothesBottomsList()) + else: + self.notify.warning('NPCTailor: setDNA() - unable to save old bottoms - we exceeded the bottoms list length') + + self.air.writeServerEvent( + 'boughtTailorClothes', avId, + "%s|%s|%s" % (self.doId, which, self.customerDNA.asTuple())) + + else: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCTailorAI.setDNA bogus clothing ticket') + self.notify.warning('NPCTailor: setDNA() - client tried to purchase with bogus clothing ticket!') + if self.customerDNA: + av.b_setDNAString(self.customerDNA.makeNetString()) + elif (finished == 1): + # Purchase cancelled - make sure DNA gets reset, but don't + # burn the clothing ticket + if self.customerDNA: + av.b_setDNAString(self.customerDNA.makeNetString()) + else: + # Warning - we are trusting the client to set their DNA here + # This is a big security hole. Either the client should just send + # indexes into the clothing choices or the tailor should verify + #av.b_setDNAString(blob) + # Don't set the avatars DNA. Instead, send a message back to the + # all the clients in this zone telling them them the dna of the localToon + # so they can set it themselves. + self.sendUpdate("setCustomerDNA", [avId, blob]) + else: + self.notify.warning('no av for avId: %d' % avId) + if (self.timedOut == 1 or finished == 0): + return + if (self.busy == avId): + taskMgr.remove(self.uniqueName('clearMovie')) + self.completePurchase(avId) + elif self.busy: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCTailorAI.setDNA busy with %s' % (self.busy)) + self.notify.warning('setDNA from unknown avId: %s busy: %s' % (avId, self.busy)) + + def __handleUnexpectedExit(self, avId): + self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly') + + # Only do customer work with the busy id + if (self.customerId == avId): + toon = self.air.doId2do.get(avId) + if (toon == None): + toon = DistributedToonAI.DistributedToonAI(self.air) + toon.doId = avId + + if self.customerDNA: + toon.b_setDNAString(self.customerDNA.makeNetString()) + # Force a database write since the toon is gone and might + # have missed the distributed update. + db = DatabaseObject.DatabaseObject(self.air, avId) + db.storeObject(toon, ["setDNAString"]) + else: + self.notify.warning('invalid customer avId: %s, customerId: %s ' % (avId, self.customerId)) + + # Only do busy work with the busy id + # Warning: send the clear movie at the end of this transaction because + # it clears out all the useful values needed here + if (self.busy == avId): + self.sendClearMovie(None) + else: + self.notify.warning('not busy with avId: %s, busy: %s ' % (avId, self.busy)) + diff --git a/toontown/src/toon/DistributedNPCToon.py b/toontown/src/toon/DistributedNPCToon.py new file mode 100644 index 0000000..8b74874 --- /dev/null +++ b/toontown/src/toon/DistributedNPCToon.py @@ -0,0 +1,341 @@ +from pandac.PandaModules import * +from DistributedNPCToonBase import * +from toontown.quest import QuestParser +from toontown.quest import QuestChoiceGui +from toontown.quest import TrackChoiceGui +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil +from toontown.toontowngui import TeaserPanel + +ChoiceTimeout = 20 + +class DistributedNPCToon(DistributedNPCToonBase): + + def __init__(self, cr): + assert self.notify.debug("__init__") + DistributedNPCToonBase.__init__(self, cr) + self.curQuestMovie = None + self.questChoiceGui = None + self.trackChoiceGui = None + + def delayDelete(self): + DistributedNPCToonBase.delayDelete(self) + # if there are situations where a quest movie should be able to stick around + # after the NPC is deleted, this will need to change + if self.curQuestMovie: + curQuestMovie = self.curQuestMovie + # do this here in case we get deleted in the call to movie.cleanup() + self.curQuestMovie = None + curQuestMovie.timeout(fFinish = 1) + curQuestMovie.cleanup() + + def disable(self): + self.cleanupMovie() + DistributedNPCToonBase.disable(self) + + def cleanupMovie(self): + self.clearChat() + # Kill any quest choice guis that may be active + self.ignore("chooseQuest") + if self.questChoiceGui: + self.questChoiceGui.destroy() + self.questChoiceGui = None + # Kill any movies that may be playing + self.ignore(self.uniqueName("doneChatPage")) + if self.curQuestMovie: + self.curQuestMovie.timeout(fFinish = 1) + self.curQuestMovie.cleanup() + self.curQuestMovie = None + # Kill any track choice guis that may be active + if self.trackChoiceGui: + self.trackChoiceGui.destroy() + self.trackChoiceGui = None + + def allowedToTalk(self): + """Check if the local toon is allowed to talk to this NPC.""" + if base.cr.isPaid(): + return True + place = base.cr.playGame.getPlace() + myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId) + # if we're in the estate we should use place.id + if hasattr(place, 'id'): + myHoodId = place.id + if myHoodId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ToontownGlobals.Tutorial + ): + # trialer going to TTC/Estate/Goofy Speedway, let them through + return True + return False + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.debug("Entering collision sphere...") + if self.allowedToTalk(): + # Lock down the avatar for quest mode + base.cr.playGame.getPlace().fsm.request('quest', [self]) + # Tell the server + self.sendUpdate("avatarEnter", []) + # make sure this NPCs chat balloon is visible above all others for th elocekd down avatar + self.nametag3d.setDepthTest(0) + self.nametag3d.setBin('fixed', 0) + else: + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('stopped') + self.dialog = TeaserPanel.TeaserPanel(pageName='quests', + doneFunc=self.handleOkTeaser) + + def handleOkTeaser(self): + """Handle the user clicking ok on the teaser panel.""" + self.dialog.destroy() + del self.dialog + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('walk') + + + + def finishMovie(self, av, isLocalToon, elapsedTime): + """ + Final cleanup for a movie that has finished + """ + self.cleanupMovie() + av.startLookAround() + self.startLookAround() + self.detectAvatars() + # Reset the NPC back to original pos hpr in case he had to + # turn all the way around to talk to the toon + # TODO: make this a lerp + self.initPos() + if isLocalToon: + taskMgr.remove(self.uniqueName("lerpCamera")) + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + # Tell the AI we are done now + self.sendUpdate("setMovieDone", []) + # set the name tag back to normal rendering + self.nametag3d.clearDepthTest() + self.nametag3d.clearBin() + + def setupCamera(self, mode): + camera.wrtReparentTo(render) + if ((mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE) or + (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE)): + camera.lerpPosHpr(5, 9, self.getHeight()-0.5, 155, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName("lerpCamera")) + else: + camera.lerpPosHpr(-5, 9, self.getHeight()-0.5, -150, -2, 0, 1, + other=self, + blendType="easeOut", + task=self.uniqueName("lerpCamera")) + + + def setMovie(self, mode, npcId, avId, quests, timestamp): + """ + This is a message from the AI describing a movie between this NPC + and a Toon that has approached us. + """ + timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp) + + # See if this is the local toon + isLocalToon = (avId == base.localAvatar.doId) + + assert(self.notify.debug("setMovie: %s %s %s %s %s %s" % + (mode, npcId, avId, quests, timeStamp, isLocalToon))) + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.QUEST_MOVIE_CLEAR): + assert self.notify.debug("setMovie: movie cleared") + self.cleanupMovie() + return + + # This is an old movie in the server ram that has been cleared. + # Just return and do nothing + if (mode == NPCToons.QUEST_MOVIE_TIMEOUT): + assert self.notify.debug("setMovie: movie timeout") + self.cleanupMovie() + # If we are the local toon and we have simply taken too long + # to read through the chat balloons, just free us + if isLocalToon: + self.freeAvatar() + # Act like we finished the chat pages by setting the page number to -1 + self.setPageNumber(0,-1) + self.clearChat() + self.startLookAround() + self.detectAvatars() + return + + av = base.cr.doId2do.get(avId) + if av is None: + self.notify.warning("Avatar %d not found in doId" % (avId)) + return + + # Reject is simpler, so lets get that out of the way + if (mode == NPCToons.QUEST_MOVIE_REJECT): + rejectString = Quests.chooseQuestDialogReject() + rejectString = Quests.fillInQuestNames(rejectString, avName = av.name) + # No need for page chat here, just setChatAbsolute + self.setChatAbsolute(rejectString, CFSpeech | CFTimeout) + if isLocalToon: + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + return + + # Reject is simpler, so lets get that out of the way + if (mode == NPCToons.QUEST_MOVIE_TIER_NOT_DONE): + rejectString = Quests.chooseQuestDialogTierNotDone() + rejectString = Quests.fillInQuestNames(rejectString, avName = av.name) + # No need for page chat here, just setChatAbsolute + self.setChatAbsolute(rejectString, CFSpeech | CFTimeout) + if isLocalToon: + # Go back into walk mode + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + return + + self.setupAvatars(av) + + fullString = "" + toNpcId = None + if (mode == NPCToons.QUEST_MOVIE_COMPLETE): + questId, rewardId, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_complete_" + str(questId) + if QuestParser.questDefined(scriptId): + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + if greetingString: + fullString += greetingString + "\a" + fullString += Quests.chooseQuestDialog(questId, Quests.COMPLETE) + "\a" + if rewardId: + fullString += Quests.getReward(rewardId).getString() + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE_CANCEL): + fullString = TTLocalizer.QuestMovieQuestChoiceCancel + + elif (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE_CANCEL): + fullString = TTLocalizer.QuestMovieTrackChoiceCancel + + elif (mode == NPCToons.QUEST_MOVIE_INCOMPLETE): + questId, completeStatus, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_incomplete_" + str(questId) + if QuestParser.questDefined(scriptId): + if self.curQuestMovie: + self.curQuestMovie.timeout() + self.curQuestMovie.cleanup() + self.curQuestMovie = None + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + if greetingString: + fullString += greetingString + "\a" + + fullString += Quests.chooseQuestDialog(questId, completeStatus) + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_ASSIGN): + questId, rewardId, toNpcId = quests + + # Try out the new quest script system + scriptId = "quest_assign_" + str(questId) + if QuestParser.questDefined(scriptId): + if self.curQuestMovie: + self.curQuestMovie.timeout() + self.curQuestMovie.cleanup() + self.curQuestMovie = None + self.curQuestMovie = QuestParser.NPCMoviePlayer(scriptId, av, self) + self.curQuestMovie.play() + return + + if isLocalToon: + self.setupCamera(mode) + #greetingString = Quests.chooseQuestDialog(questId, Quests.GREETING) + #if greetingString: + # fullString += greetingString + "\a" + fullString += Quests.chooseQuestDialog(questId, Quests.QUEST) + leavingString = Quests.chooseQuestDialog(questId, Quests.LEAVING) + if leavingString: + fullString += "\a" + leavingString + + elif (mode == NPCToons.QUEST_MOVIE_QUEST_CHOICE): + # Quest choice movie + if isLocalToon: + self.setupCamera(mode) + assert self.notify.debug("QUEST_MOVIE_QUEST_CHOICE: %s" % quests) + self.setChatAbsolute(TTLocalizer.QuestMovieQuestChoice, CFSpeech) + if isLocalToon: + self.acceptOnce("chooseQuest", self.sendChooseQuest) + self.questChoiceGui = QuestChoiceGui.QuestChoiceGui() + self.questChoiceGui.setQuests(quests, npcId, ChoiceTimeout) + return + + elif (mode == NPCToons.QUEST_MOVIE_TRACK_CHOICE): + # If this is a TrackChoiceQuest, complete simply means we are at the + # avatar that will allow us to chose. If the localToon cancels the + # choice, we are not really complete yet + # In this case, the quests are really the track choices + if isLocalToon: + self.setupCamera(mode) + tracks = quests + assert self.notify.debug("QUEST_MOVIE_TRACK_CHOICE: %s" % tracks) + self.setChatAbsolute(TTLocalizer.QuestMovieTrackChoice, CFSpeech) + if isLocalToon: + self.acceptOnce("chooseTrack", self.sendChooseTrack) + self.trackChoiceGui = TrackChoiceGui.TrackChoiceGui(tracks, ChoiceTimeout) + return + + fullString = Quests.fillInQuestNames(fullString, + avName = av.name, + fromNpcId = npcId, + toNpcId = toNpcId) + + self.acceptOnce(self.uniqueName("doneChatPage"), + self.finishMovie, extraArgs = [av, isLocalToon]) + self.setPageChat(avId, 0, fullString, 1) + + def sendChooseQuest(self, questId): + """ + This is the callback from the questChoiceGui event + Cleanup the gui and send the message with our choice to the AI + """ + if self.questChoiceGui: + self.questChoiceGui.destroy() + self.questChoiceGui = None + self.sendUpdate("chooseQuest", [questId]) + + def sendChooseTrack(self, trackId): + """ + This is the callback from the trackChoiceGui event + Cleanup the gui and send the message with our choice to the AI + """ + if self.trackChoiceGui: + self.trackChoiceGui.destroy() + self.trackChoiceGui = None + self.sendUpdate("chooseTrack", [trackId]) diff --git a/toontown/src/toon/DistributedNPCToonAI.py b/toontown/src/toon/DistributedNPCToonAI.py new file mode 100644 index 0000000..c05d69e --- /dev/null +++ b/toontown/src/toon/DistributedNPCToonAI.py @@ -0,0 +1,271 @@ + +from otp.ai.AIBaseGlobal import * +from direct.task.Task import Task +from pandac.PandaModules import * +from DistributedNPCToonBaseAI import * +from toontown.quest import Quests + +class DistributedNPCToonAI(DistributedNPCToonBaseAI): + + def __init__(self, air, npcId, questCallback=None, hq=0): + DistributedNPCToonBaseAI.__init__(self, air, npcId, questCallback) + # Am I a hq toon? Maybe this should be a subclass? + self.hq = hq + # Am I part of the tutorial? + self.tutorial = 0 + # Initialize the pendingAvId to None in case we get any rogue messages + self.pendingAvId = None + + def getTutorial(self): + return self.tutorial + + def setTutorial(self, val): + # If you are in the tutorial you have no timeouts + self.tutorial = val + + def getHq(self): + return self.hq + + def avatarEnter(self): + avId = self.air.getAvatarIdFromSender() + # this avatar has come within range + self.notify.debug("avatar enter " + str(avId)) + # Let the quest manager figure out what to do from here on + self.air.questManager.requestInteract(avId, self) + DistributedNPCToonBaseAI.avatarEnter(self) + + def chooseQuest(self, questId): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("chooseQuest: avatar %s choseQuest %s" % (avId, questId)) + + # Sanity check, this should not happen + if (not self.pendingAvId): + self.notify.warning("chooseQuest: not expecting an answer from any avatar: %s" % (avId)) + return + if (self.pendingAvId != avId): + self.notify.warning("chooseQuest: not expecting an answer from this avatar: %s" % (avId)) + return + + # See if the avatar cancelled + if questId == 0: + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + # Tell the quest manager this avatar cancelled + self.air.questManager.avatarCancelled(avId) + # Tell the avatar goodbye then allow him to finish the movie + self.cancelChoseQuest(avId) + return + + # See if the avatar chose any of the quests offered + for quest in self.pendingQuests: + if (questId == quest[0]): + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + # Let the quest manager figure out what to do from here on + self.air.questManager.avatarChoseQuest(avId, self, *quest) + return + + # If we got here, something is wrong, handle it gracefully + self.notify.warning("chooseQuest: avatar: %s chose a quest not offered: %s" % (avId, questId)) + # Clear the pendings + self.pendingAvId = None + self.pendingQuests = None + return + + def chooseTrack(self, trackId): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("chooseTrack: avatar %s choseTrack %s" % (avId, trackId)) + + if (not self.pendingAvId): + self.notify.warning("chooseTrack: not expecting an answer from any avatar: %s" % (avId)) + return + if (self.pendingAvId != avId): + self.notify.warning("chooseTrack: not expecting an answer from this avatar: %s" % (avId)) + return + + # See if the avatar cancelled + if trackId == -1: + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + # Tell the quest manager this avatar cancelled + self.air.questManager.avatarCancelled(avId) + # Tell the avatar goodbye then allow him to finish the movie + self.cancelChoseTrack(avId) + return + + # See if the avatar chose any of the tracks offered + for track in self.pendingTracks: + if (trackId == track): + # Let the quest manager figure out what to do from here on + self.air.questManager.avatarChoseTrack(avId, self, self.pendingTrackQuest, trackId) + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + return + + # If we got here, something is wrong, handle it gracefully + self.notify.warning("chooseTrack: avatar: %s chose a track not offered: %s" % (avId, trackId)) + # Clear the pendings + self.pendingAvId = None + self.pendingTracks = None + self.pendingTrackQuest = None + return + + + def sendTimeoutMovie(self, task): + # Clear the movie + self.pendingAvId = None + self.pendingQuests = None + self.pendingTracks = None + self.pendingTrackQuest = None + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TIMEOUT, + self.npcId, self.busy, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + self.sendClearMovie(None) + self.busy = 0 + return Task.done + + def sendClearMovie(self, task): + # Clear the movie + self.pendingAvId = None + self.pendingQuests = None + self.pendingTracks = None + self.pendingTrackQuest = None + self.busy = 0 + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_CLEAR, + self.npcId, 0, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + return Task.done + + def rejectAvatar(self, avId): + self.busy = avId + # Send a movie to reject the avatar with time stamp + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_REJECT, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # No timeout here because we do not wait for the toon to click on rejects + # Just send a clear after a pause + # + # We actually need a longer pause here - people need to read the text before + # clearMovie wipes it -grw + if not self.tutorial: + taskMgr.doMethodLater(5.5, self.sendClearMovie, self.uniqueName("clearMovie")) + return + + def rejectAvatarTierNotDone(self, avId): + self.busy = avId + # Send a movie to reject the avatar with time stamp + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TIER_NOT_DONE, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # No timeout here because we do not wait for the toon to click on rejects + # Just send a clear after a pause + # + # We actually need a longer pause here - people need to read the text before + # clearMovie wipes it -grw + if not self.tutorial: + taskMgr.doMethodLater(5.5, self.sendClearMovie, self.uniqueName("clearMovie")) + return + + def completeQuest(self, avId, questId, rewardId): + self.busy = avId + # nextQuestId will be the npc for the next visiting quest (visitNpcId) + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_COMPLETE, + self.npcId, avId, [questId, rewardId, 0], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def incompleteQuest(self, avId, questId, completeStatus, toNpcId): + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_INCOMPLETE, + self.npcId, avId, [questId, completeStatus, toNpcId], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def assignQuest(self, avId, questId, rewardId, toNpcId): + self.busy = avId + # Call the quest callback now. We could wait until the movie + # is over, but I don't think we need to. + if self.questCallback: + self.questCallback() + #print "assignQuest", avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_ASSIGN, + self.npcId, avId, [questId, rewardId, toNpcId], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def presentQuestChoice(self, avId, quests): + self.busy = avId + self.pendingAvId = avId + self.pendingQuests = quests + flatQuests = [] + for quest in quests: + flatQuests.extend(quest) + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_QUEST_CHOICE, + self.npcId, avId, flatQuests, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def presentTrackChoice(self, avId, questId, tracks): + self.busy = avId + self.pendingAvId = avId + self.pendingTracks = tracks + self.pendingTrackQuest = questId + # Send a movie to present the choice to the avatar + # Instead of quests, we send the trackIds + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TRACK_CHOICE, + self.npcId, avId, tracks, + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def cancelChoseQuest(self, avId): + self.busy = avId + # Send a movie to present the choice to the avatar + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_QUEST_CHOICE_CANCEL, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def cancelChoseTrack(self, avId): + self.busy = avId + self.sendUpdate("setMovie", [NPCToons.QUEST_MOVIE_TRACK_CHOICE_CANCEL, + self.npcId, avId, [], + ClockDelta.globalClockDelta.getRealNetworkTime()]) + # Timeout + if not self.tutorial: + taskMgr.doMethodLater(60.0, self.sendTimeoutMovie, self.uniqueName("clearMovie")) + return + + def setMovieDone(self): + avId = self.air.getAvatarIdFromSender() + self.notify.debug("setMovieDone busy: %s avId: %s" % (self.busy, avId)) + if self.busy == avId: + # Kill all pending doLaters that will clear the movie + taskMgr.remove(self.uniqueName("clearMovie")) + self.sendClearMovie(None) + elif self.busy: + self.air.writeServerEvent('suspicious', avId, 'DistributedNPCToonAI.setMovieDone busy with %s' % (self.busy)) + self.notify.warning("somebody called setMovieDone that I was not busy with! avId: %s" % avId) diff --git a/toontown/src/toon/DistributedNPCToonBase.py b/toontown/src/toon/DistributedNPCToonBase.py new file mode 100644 index 0000000..e333290 --- /dev/null +++ b/toontown/src/toon/DistributedNPCToonBase.py @@ -0,0 +1,205 @@ +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +from direct.fsm import State +from toontown.toonbase import ToontownGlobals +import DistributedToon +from direct.distributed import DistributedObject +import NPCToons +from toontown.quest import Quests +from direct.distributed import ClockDelta +from toontown.quest import QuestParser +from toontown.quest import QuestChoiceGui +from direct.interval.IntervalGlobal import * +import random + +class DistributedNPCToonBase(DistributedToon.DistributedToon): + + def __init__(self, cr): + try: + self.DistributedNPCToon_initialized + except: + self.DistributedNPCToon_initialized = 1 + DistributedToon.DistributedToon.__init__(self, cr) + self.__initCollisions() + # Not pickable + self.setPickable(0) + # These guys are specifically non-player characters. + self.setPlayerType(NametagGroup.CCNonPlayer) + + def disable(self): + # Ignore the sphere after the finish because + # the end of the movie adds it in + self.ignore("enter" + self.cSphereNode.getName()) + # Kill any quest choice guis that may be active + # Kill any movies that may be playing + DistributedToon.DistributedToon.disable(self) + + def delete(self): + try: + self.DistributedNPCToon_deleted + except: + self.DistributedNPCToon_deleted = 1 + self.__deleteCollisions() + DistributedToon.DistributedToon.delete(self) + + def generate(self): + DistributedToon.DistributedToon.generate(self) + # We cannot get a unique name until we have been generated + self.cSphereNode.setName(self.uniqueName('NPCToon')) + self.detectAvatars() + # Since we know where the NPC will be standing, we can + # immediately parent him to render. This initializes the + # nametag, etc. + self.setParent(ToontownGlobals.SPRender) + self.startLookAround() + + def generateToon(self): + """generateToon(self) + Create a toon from dna (an array of strings) + NOTE: DistributedNPCToon overrides this because they do not + need all this extra junk + """ + # set up LOD info + self.setLODs() + # load the toon legs + self.generateToonLegs() + # load the toon head + self.generateToonHead() + # load the toon torso + self.generateToonTorso() + # color the toon as specified by the dna + self.generateToonColor() + self.parentToonParts() + # Make small toons with big heads + self.rescaleToon() + self.resetHeight() + + # Initialize arrays of pointers to useful nodes for the toon + self.rightHands = [] + self.leftHands = [] + self.headParts = [] + self.hipsParts = [] + self.torsoParts = [] + self.legsParts = [] + self.__bookActors = [] + self.__holeActors = [] + + def announceGenerate(self): + # This method is called after all the required fields have + # been filled in. In particular, the DNA will have been set, + # so we can safely set an animation state. + self.initToonState() # This may be overidden by derived classes + + DistributedToon.DistributedToon.announceGenerate(self) + + def initToonState(self): + # We'll make all NPC toons loop their neutral cycle by + # default. Normally this is sent from the AI, but because the + # server sometimes loses updates that immediately follow the + # generate, we might lose that message. + self.setAnimState("neutral", 0.9, None, None) + + # TODO: make this a node path collection + npcOrigin = render.find("**/npc_origin_" + `self.posIndex`) + + # Now he's no longer parented to render, but no one minds. + if not npcOrigin.isEmpty(): + self.reparentTo(npcOrigin) + self.initPos() + else: + self.notify.warning("announceGenerate: Could not find npc_origin_" + str(self.posIndex)) + + def initPos(self): + self.clearMat() + + def wantsSmoothing(self): + # This overrides a function from DistributedSmoothNode to + # indicate that NPC's should not ever be smoothed, even though + # they do inherit (indirectly) from DistributedSmoothNode. + return 0 + + def detectAvatars(self): + """ + listen for the collision sphere enter event + """ + self.accept("enter" + self.cSphereNode.getName(), + self.handleCollisionSphereEnter) + + def ignoreAvatars(self): + """ + Do not listen for the enter coll sphere event. + """ + self.ignore("enter" + self.cSphereNode.getName()) + + def getCollSphereRadius(self): + return 3.25 + + def __initCollisions(self): + self.cSphere = CollisionTube(0., 1., 0., 0., 1., 5., self.getCollSphereRadius()) + #self.cSphere = CollisionSphere(0., 1., 0., self.getCollSphereRadius()) + self.cSphere.setTangible(0) + self.cSphereNode = CollisionNode("cSphereNode") + self.cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = self.attachNewNode(self.cSphereNode) + self.cSphereNodePath.hide() + self.cSphereNode.setCollideMask(ToontownGlobals.WallBitmask) + + def __deleteCollisions(self): + del self.cSphere + del self.cSphereNode + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + + def handleCollisionSphereEnter(self, collEntry): + """ + Response for a toon walking up to this NPC + """ + assert self.notify.warning("Don't call me - I'm a base class!") + # Tell the server + # self.sendUpdate("avatarEnter", []) + + def setupAvatars(self, av): + """ + Prepare avatars for the quest movie + """ + # Ignore avatars now to prevent unnecessary requestInteractions when we know + # this npc is busy right now. If another toon did manage to request interaction + # before we starting ignoring, he will get a freeAvatar message from the server + self.ignoreAvatars() + # Make us face each other + # TODO: make this a lerp + av.headsUp(self, 0, 0, 0) + self.headsUp(av, 0, 0, 0) + av.stopLookAround() + av.lerpLookAt(Point3(-0.5, 4, 0), time=0.5) + # In case the avatar jumped up to interact with us, set his Z back down to our Z + # This has issues... so I will not enable it just yet + # We need to clear out the inertia and prevent double enters on the NPC sphere + # av.setZ(render, self.getZ(render)) + # av.setShadowHeight(0) + self.stopLookAround() + self.lerpLookAt(Point3(av.getPos(self)), time=0.5) + + def b_setPageNumber(self, paragraph, pageNumber): + self.setPageNumber(paragraph, pageNumber) + self.d_setPageNumber(paragraph, pageNumber) + + def d_setPageNumber(self, paragraph, pageNumber): + timestamp = ClockDelta.globalClockDelta.getFrameNetworkTime() + self.sendUpdate("setPageNumber", [paragraph, pageNumber, timestamp]) + + def freeAvatar(self): + """ + This is a message from the AI used to free the avatar from movie mode + """ + base.localAvatar.posCamera(0,0) + base.cr.playGame.getPlace().setState("walk") + + def setPositionIndex(self, posIndex): + """ + This required field sets the NPC's position index. + Each zone has N NPCs, and N corresponding NPC origins in the model. + """ + self.posIndex = posIndex + diff --git a/toontown/src/toon/DistributedNPCToonBaseAI.py b/toontown/src/toon/DistributedNPCToonBaseAI.py new file mode 100644 index 0000000..46cafe2 --- /dev/null +++ b/toontown/src/toon/DistributedNPCToonBaseAI.py @@ -0,0 +1,84 @@ + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +import DistributedToonAI +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.distributed import ClockDelta +from toontown.toonbase import ToontownGlobals +import NPCToons +from direct.task import Task +from toontown.quest import Quests + +class DistributedNPCToonBaseAI(DistributedToonAI.DistributedToonAI): + + def __init__(self, air, npcId, questCallback=None): + DistributedToonAI.DistributedToonAI.__init__(self, air) + # Record the repository + self.air = air + self.npcId = npcId + # busy will be replaced with the toon this npc is talking to + self.busy = 0 + self.questCallback = questCallback + # Does this NPC give out quests? + self.givesQuests = 1 + + def delete(self): + taskMgr.remove(self.uniqueName("clearMovie")) + DistributedToonAI.DistributedToonAI.delete(self) + + def _doPlayerEnter(self): + pass + + def _doPlayerExit(self): + pass + + def _announceArrival(self): + # DistributedToonAI derives from DistributedPlayerAI, which sends + # out 'arrivedOnDistrict' in announceGenerate. NPCs don't have + # that field, so we override this func to do nothing. + pass + + def isPlayerControlled(self): + # TODO: we shouldn't derive from DistributedPlayerAI + return False + + def getHq(self): + """ + Override if you should be considered an HQ Toon + """ + return 0 + + def getTailor(self): + """ + Override if you should be considered a Tailor + """ + return 0 + + def getGivesQuests(self): + return self.givesQuests + + def avatarEnter(self): + # Base class behavior + pass + + def isBusy(self): + return (self.busy > 0) + + def getNpcId(self): + return self.npcId + + def freeAvatar(self, avId): + # Free this avatar, probably because he requested interaction while + # I was busy. This can happen when two avatars request interaction + # at the same time. The AI will accept the first, sending a setMovie, + # and free the second + self.sendUpdateToAvatarId(avId, "freeAvatar", []) + return + + def setPositionIndex(self, posIndex): + self.posIndex = posIndex + + def getPositionIndex(self): + return self.posIndex + diff --git a/toontown/src/toon/DistributedToon.py b/toontown/src/toon/DistributedToon.py new file mode 100644 index 0000000..1730460 --- /dev/null +++ b/toontown/src/toon/DistributedToon.py @@ -0,0 +1,3620 @@ +"""DistributedToon module: contains the DistributedToon class""" +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.distributed.ClockDelta import * +from direct.interval.IntervalGlobal import * + +from otp.otpbase import OTPGlobals +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from otp.avatar import DistributedPlayer +from otp.avatar import Avatar, DistributedAvatar +from otp.speedchat import SCDecoders +from otp.chat import TalkAssistant +import Toon +import GMUtils +from direct.task.Task import Task +from direct.distributed import DistributedSmoothNode +from direct.distributed import DistributedObject +from direct.fsm import ClassicFSM +from toontown.hood import ZoneUtil +from toontown.distributed import DelayDelete +from toontown.distributed.DelayDeletable import DelayDeletable +from direct.showbase import PythonUtil +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem +import TTEmote +from toontown.shtiker.OptionsPage import speedChatStyles +from toontown.fishing import FishCollection +from toontown.fishing import FishTank +from toontown.suit import SuitDNA +from toontown.coghq import CogDisguiseGlobals +from toontown.toonbase import TTLocalizer +import Experience +import InventoryNew +from toontown.speedchat import TTSCDecoders +from toontown.chat import ToonChatGarbler +from toontown.chat import ResistanceChat +from direct.distributed.MsgTypes import * +from toontown.effects.ScavengerHuntEffects import * +from toontown.estate import FlowerCollection +from toontown.estate import FlowerBasket +from toontown.estate import GardenGlobals +from toontown.estate import DistributedGagTree +from toontown.golf import GolfGlobals +from toontown.parties.PartyGlobals import InviteStatus, PartyStatus +from toontown.parties.PartyInfo import PartyInfo +from toontown.parties.InviteInfo import InviteInfo +from toontown.parties.PartyReplyInfo import PartyReplyInfoBase +from toontown.parties.SimpleMailBase import SimpleMailBase +from toontown.parties import PartyGlobals +from toontown.friends import FriendHandle +import time +import operator + +from direct.interval.IntervalGlobal import Sequence, Wait, Func, Parallel, SoundInterval +from toontown.distributed import DelayDelete +from otp.otpbase import OTPLocalizer +import random +import copy + + + +if base.wantKarts: + from toontown.racing.KartDNA import * + +if( __debug__ ): + import pdb + +class DistributedToon(DistributedPlayer.DistributedPlayer, + Toon.Toon, DistributedSmoothNode.DistributedSmoothNode, DelayDeletable): + """DistributedToon class:""" + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedToon") + partyNotify = DirectNotifyGlobal.directNotify.newCategory("DistributedToon_Party") + + # This can be a class variable because all Toons can share it + # without problems + chatGarbler = ToonChatGarbler.ToonChatGarbler() + + #Support for GM avatars + gmNameTag = None + + def __init__(self, cr, bFake = False): + try: + self.DistributedToon_initialized + return + except: + self.DistributedToon_initialized = 1 + + DistributedPlayer.DistributedPlayer.__init__(self, cr) + Toon.Toon.__init__(self) + DistributedSmoothNode.DistributedSmoothNode.__init__(self, cr) + + + self.bFake = bFake + + self.kart=None + + # Our trophy score will be set by the AI. + self.trophyScore = 0 + self.trophyStar = None + self.trophyStarSpeed = 0 + + # check for teleport cheat + self.safeZonesVisited = [] + + self.NPCFriendsDict = {} + + self.earnedExperience = None + self.track = None + self.effect = None + self.maxCarry = 0 + self.disguisePageFlag = 0 + + # These are initialized to None here, but for a LocalToon with + # the pages created, they will be filled in. + self.disguisePage = None + self.sosPage = None + self.gardenPage = None + # These are initialized to sensible default values. + self.cogTypes = [0, 0, 0, 0] + self.cogLevels = [0, 0, 0, 0] + self.cogParts = [0, 0, 0, 0] + self.cogMerits = [0, 0, 0, 0] + + self.savedCheesyEffect = CENormal + self.savedCheesyHoodId = 0 + self.savedCheesyExpireTime = 0 + + if hasattr(base, 'wantPets') and base.wantPets: + self.petTrickPhrases = [] + + # This is not guaranteed to be filled in; it will be + # filled in if the toon and the pet happen to be in the + # same zone together, or if someone called lookupPetDNA() + # a "little while" ago. + self.petDNA = None + + self.customMessages = [] + self.resistanceMessages = [] + self.cogSummonsEarned = [] + self.catalogNotify = ToontownGlobals.NoItems + self.mailboxNotify = ToontownGlobals.NoItems + self.simpleMailNotify = ToontownGlobals.NoItems # simple mail for now + self.inviteMailNotify = ToontownGlobals.NoItems + self.catalogScheduleCurrentWeek = 0 + self.catalogScheduleNextTime = 0 + self.monthlyCatalog = CatalogItemList.CatalogItemList() + self.weeklyCatalog = CatalogItemList.CatalogItemList() + self.backCatalog = CatalogItemList.CatalogItemList() + self.onOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + #self.onGiftOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + self.onGiftOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + self.mailboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization) + self.deliveryboxContentsContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.GiftTag) + self.awardMailboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization) + self.onAwardOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + self.splash = None + self.tossTrack = None + self.pieTracks = {} + self.splatTracks = {} + self.lastTossedPie = 0 + + self.clothesTopsList = [] + self.clothesBottomsList = [] + + # tunnel + self.tunnelTrack = None + self.tunnelPivotPos = [-14, -6, 0] + # these values are used to pull the toon towards the center + # of the tunnel, to keep him out of the walls + # offset of tunnel center from pivot node + self.tunnelCenterOffset = 9. + self.tunnelCenterInfluence = .6 # 0=no effect, 1=converge on center + self.pivotAngle = 90 + 45 + + # index of the toon in the avatar chooser + self.posIndex = 0 + + # housing + self.houseId = 0 + + # This wasn't getting set before it was getting used - SP + self.money = 0 + self.bankMoney = 0 + self.maxMoney = 0 + self.maxBankMoney = 0 + + self.petId = 0 + self.bPetTutorialDone = False + self.bFishBingoTutorialDone = False + self.bFishBingoMarkTutorialDone = False + + self.accessories = [] + + if base.wantKarts: + self.kartDNA = [ -1 ] * ( getNumFields() ) + + #Gardening stuff + self.flowerCollection = None + self.shovel = 0 + self.shovelSkill = 0 + self.shovelModel = None + self.wateringCan = 0 + self.wateringCanSkill = 0 + self.wateringCanModel = None + self.gardenSpecials = []#[(0,2), (1,2), (2,2), (3,2)] + + self.unlimitedSwing = 0 + self.soundSequenceList = [] + self.boardingParty = None + self.__currentDialogue = None + self.mail = None + + # parties + self.invites = [] + self.hostedParties = [] + self.partiesInvitedTo = [] + self.partyReplyInfoBases = [] + + # GM related stuff + self.gmState = 0 + self.gmNameTagEnabled = 0 + self.gmNameTagColor = 'whiteGM' + self.gmNameTagString = '' + + def disable(self): + for soundSequence in self.soundSequenceList: + soundSequence.finish() + self.soundSequenceList = [] + + if self.boardingParty: + self.boardingParty.demandDrop() + self.boardingParty = None + self.ignore('clientCleanup') + self.stopAnimations() + self.clearCheesyEffect() + self.stopBlink() + self.stopSmooth() + self.stopLookAroundNow() + self.setGhostMode(0) + if (self.track != None): + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + if (self.effect != None): + #self.effect.stop() + self.effect.destroy() + self.effect = None + if (self.splash != None): + self.splash.destroy() + self.splash = None + if (self.emote != None): + self.emote.finish() + self.emote = None + self.cleanupPies() + + # take off our disguise is present + if self.isDisguised: + self.takeOffSuit() + + #if self.motion: + # self.motion.stop() + if self.tunnelTrack: + self.tunnelTrack.finish() + self.tunnelTrack = None + + # We set the trophy score to 0, mainly to stop the spinning + # star task should it be running. If the avatar is + # regenerated later, setTrophyScore will be called again and + # restart this. + self.setTrophyScore(0) + + self.removeGMIcon() + + DistributedPlayer.DistributedPlayer.disable(self) + # There is no Toon disable, it is not a distributed object + + def delete(self): + """ + This method is called when the DistributedObject is permanently + removed from the world and deleted from the cache. + """ + try: + self.DistributedToon_deleted + except: + self.DistributedToon_deleted = 1 + del self.safeZonesVisited + DistributedPlayer.DistributedPlayer.delete(self) + Toon.Toon.delete(self) + DistributedSmoothNode.DistributedSmoothNode.delete(self) + + def generate(self): + """ + This method is called when the DistributedObject is reintroduced + to the world, either for the first time or from the cache. + """ + DistributedPlayer.DistributedPlayer.generate(self) + DistributedSmoothNode.DistributedSmoothNode.generate(self) + self.cr.toons[self.doId] = self + + # moved from tcr as the av was not in the doId2do there + if base.cr.trophyManager != None: + base.cr.trophyManager.d_requestTrophyScore() + + self.startBlink() + self.startSmooth() + + self.accept('clientCleanup', self._handleClientCleanup) + + + def _handleClientCleanup(self): + # make sure we're not holding a DelayDelete on ourselves + if (self.track != None): + DelayDelete.cleanupDelayDeletes(self.track) + + # We need to force the Toon version of these to be called, otherwise + # we get the generic Avatar version which is undefined + def setDNAString(self, dnaString): + Toon.Toon.setDNAString(self, dnaString) + + def setDNA(self, dna): + Toon.Toon.setDNA(self, dna) + + ### setExperience ### + + def setExperience(self, experience): + self.experience = Experience.Experience(experience, self) + + if self.inventory: + self.inventory.updateGUI() + + ### setInventory ### + + def setInventory(self, inventoryNetString): + # Create a new inventory if we don't already have one + if not self.inventory: + self.inventory = InventoryNew.InventoryNew(self, + inventoryNetString) + # update the inventory + self.inventory.updateInvString(inventoryNetString) + + ### setLastHood ### + + def setLastHood(self, lastHood): + self.lastHood = lastHood + + ### setSCToontask ### + + def setBattleId(self, battleId): + self.battleId = battleId + messenger.send("ToonBattleIdUpdate", [self.doId]) + + def b_setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex): + # Local + self.setSCToontask(taskId, toNpcId, toonProgress, msgIndex) + # Distributed + self.d_setSCToontask(taskId, toNpcId, toonProgress, msgIndex) + return None + + def d_setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex): + messenger.send("wakeup") + self.sendUpdate("setSCToontask", + [taskId, toNpcId, toonProgress, msgIndex]) + + def setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex): + """ + Receive and decode the SC message + """ + if self.doId in base.localAvatar.ignoreList: + # We're ignoring this jerk. + return + + chatString = TTSCDecoders.decodeTTSCToontaskMsg( + taskId, toNpcId, toonProgress, msgIndex) + if chatString: + self.setChatAbsolute(chatString, + CFSpeech | CFQuicktalker | CFTimeout) + + def b_setSCSinging(self, msgIndex): + """ + Set the Singing speedchat on Local and Distributed avatar. + """ + # Local + self.setSCSinging(msgIndex) + # Distributed + self.d_setSCSinging(msgIndex) + return None + + def d_setSCSinging(self, msgIndex): + """ + Set the Singing speedchat on Distributed avatar. + """ + messenger.send("wakeup") + self.sendUpdate("setSCSinging", [msgIndex]) + + def setSCSinging(self, msgIndex): + """ + Set the Singing speedchat on Local avatar. + Receive and decode the SC message + """ + if msgIndex not in OTPLocalizer.SpeedChatStaticText: + self.sendUpdate('logSuspiciousEvent', ['invalid msgIndex in setSCSinging: %s from %s' % ( + msgIndex, self.doId)]) + return + + if self.doId in base.localAvatar.ignoreList: + # We're ignoring this jerk. + return + + chatString = OTPLocalizer.SpeedChatStaticText[msgIndex] + if chatString: + self.setChatMuted(chatString, CFSpeech | CFQuicktalker | CFTimeout) + + ### setSCResistance ### + def d_reqSCResistance(self, msgIndex): + messenger.send("wakeup") + + # We can't rely on the AI to determine who is nearby, since + # the AI doesn't always know. Anytime the client directly + # moves players around--for instance, during cutscenes, + # minigames, or battles--the AI has no idea who's where. + # Also, using zoneId to separate distinct areas isn't reliable + # either, since in the factory or on the streets, two toons + # might be in different zones yet still be adjacent. + + # To solve these issues, we have to rely on the client to + # report who's near him. A minor security risk, since a + # hacker could report all sorts of people near him. We do + # have a few sanity checks on the AI, but mostly we take the + # client's word for it. + + nearbyPlayers = self.getNearbyPlayers(ResistanceChat.EFFECT_RADIUS) + self.sendUpdate("reqSCResistance", [msgIndex, nearbyPlayers]) + + def getNearbyPlayers(self, radius, includeSelf=True): + nearbyToons = [] + toonIds = self.cr.getObjectsOfExactClass(DistributedToon) + for toonId, toon in toonIds.items(): + if toon is not self: + dist = toon.getDistance(self) + if dist < radius: + nearbyToons.append(toonId) + if includeSelf: + nearbyToons.append(self.doId) + + return nearbyToons + + def setSCResistance(self, msgIndex, nearbyToons=[]): + """ + Receive and decode the SC message + """ + chatString = TTSCDecoders.decodeTTSCResistanceMsg(msgIndex) + if chatString: + self.setChatAbsolute(chatString, CFSpeech | CFTimeout) + + ResistanceChat.doEffect(msgIndex, self, nearbyToons) + + ### battleSOS ### + + def d_battleSOS(self, requesterId, sendToId = None): + self.sendUpdate("battleSOS", [requesterId], sendToId) + + def battleSOS(self, requesterId): + """battleSOS(self, int requesterId) + + This message is sent after a client has failed to teleport + successfully to another client, probably because the target + client didn't stay put. It just pops up a whisper message to + that effect. + """ + avatar = base.cr.identifyAvatar(requesterId) + + if (isinstance(avatar, DistributedToon) or + isinstance(avatar, FriendHandle.FriendHandle)): + self.setSystemMessage(requesterId, + TTLocalizer.MovieSOSWhisperHelp % (avatar.getName()), + whisperType = WhisperPopup.WTBattleSOS) + elif avatar is not None: + self.notify.warning('got battleSOS from non-toon %s' % requesterId) + + def getDialogueArray(self, *args): + # Force the right inheritance chain to be called + return Toon.Toon.getDialogueArray(self, *args) + + def setDefaultShard(self, shard): + self.defaultShard = shard + assert self.notify.debug("setting default shard to %s" % shard) + + def setDefaultZone(self, zoneId): + # Now that we have moved the start of the Welcome Valley zones, we need to map + # invalidated Welcome Valley zoneIds into the new range + if (zoneId >= 20000) and (zoneId < 22000): + zoneId = zoneId + 2000 + # Check to see if that zone has been downloaded. It is possible + # you are playing an old account on a new computer or friend's + # computer that has not finished the download yet. + hoodPhase = base.cr.hoodMgr.getPhaseFromHood(zoneId) + if not base.cr.isPaid() or (launcher and not launcher.getPhaseComplete(hoodPhase)): + # We will act like your default zone is ToontownCentral + # since you are not finished downloading the other zones + # (same deal if you haven't paid) + assert self.notify.debug("default zone %s not downloaded yet. Reverting to ToontownCentral." % zoneId) + self.defaultZone = ToontownCentral + else: + assert self.notify.debug("setting default zone to %s" % zoneId) + self.defaultZone = zoneId + + + def setShtickerBook(self, string): + assert self.notify.debug("setting Shticker Book to %s" % string) + + + ### AccountType ### + + def setAsGM(self, state): + """ Give GM's special chat abilities """ + self.notify.debug("Setting GM State: %s" %state) + DistributedPlayer.DistributedPlayer.setAsGM(self, state) + # if self.gmState: + # base.localAvatar.chatMgr.addGMSpeedChat() + + def d_updateGMNameTag(self): + # RAU stop the hack chat for now + # self.sendUpdate('updateGMNameTag', [self.gmNameTagString, self.gmNameTagColor, self.gmNameTagEnabled]) + self.refreshName() + + def updateGMNameTag(self, tagString, color, state): + """ Retrieves the values from the owner's prc file (see setAsGM) if the avatar is an admin """ + # make sure it's a valid UTF-8 string + try: + unicode(tagString, 'utf-8') + except UnicodeDecodeError: + self.sendUpdate('logSuspiciousEvent', ['invalid GM name tag: %s from %s' % (tagString, self.doId)]) + return + # TODO: check other fields for malicious values + + # security hole: this allows a hacked client to change his nametag to anything he wants + """ + self.gmNameTagString = tagString + self.gmNameTagColor = color + self.gmNameTagState = state + self.refreshName() + """ + + + def refreshName(self): + return + self.notify.debug("Refreshing GM Nametag String: %s Color: %s State: %s" % + (self.gmNameTagString, self.gmNameTagColor, self.gmNameTagEnabled)) + if hasattr(self, "nametag") and self.gmNameTagEnabled: + self.setDisplayName(self.gmNameTagString) + self.setName(self.gmNameTagString) + # A gold star! + self.trophyStar1 = loader.loadModel('models/misc/smiley') + self.trophyStar1.reparentTo(self.nametag.getNameIcon()) + self.trophyStar1.setScale(1) + self.trophyStar1.setZ(2.25) + self.trophyStar1.setColor(Vec4(0.75,0.75,0.75, 0.75)) + self.trophyStar1.setTransparency(1) + self.trophyStarSpeed = 15 + # Spinning! + #taskMgr.add(self.__starSpin1, self.uniqueName("starSpin1")) + else: + taskMgr.add(self.__refreshNameCallBack, self.uniqueName("refreshNameCallBack")) + + def __starSpin1(self, task): + now = globalClock.getFrameTime() + r = now * 90 % 360.0 + self.trophyStar1.setH(r) + return Task.cont + + def __refreshNameCallBack(self, task): + if hasattr(self, "nametag") and self.nametag.getName() != '': + self.refreshName() + return Task.done + else: + return Task.cont + + ### setTalk ### + + def setTalk(self, fromAV, fromAC, avatarName, chat, mods, flags): + """ Overridden from Distributed player becase pirates ignores players a different way""" + + if base.cr.avatarFriendsManager.checkIgnored(fromAV): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromAV) + return + + if fromAV in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromAV) + return + + if base.config.GetBool('want-sleep-reply-on-regular-chat', 0): + if base.localAvatar.sleepFlag == 1: + # I am sleeping so I send an autoreply message + self.sendUpdate("setSleepAutoReply" , [base.localAvatar.doId], fromAV) + + newText,scrubbed = self.scrubTalk(chat, mods) + self.displayTalk(newText) + base.talkAssistant.receiveOpenTalk(fromAV, avatarName, fromAC, None, newText) + + def setTalkWhisper(self, fromAV, fromAC, avatarName, chat, mods, flags): + """ Overridden from Distributed player becase pirates ignores players a different way""" + + if GMUtils.testGMIdentity(avatarName): + avatarName = GMUtils.handleGMName(avatarName) + + if base.cr.avatarFriendsManager.checkIgnored(fromAV): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromAV) + return + + if fromAV in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromAV) + return + + # if base.localAvatar.animFSM.getCurrentState() == base.localAvatar.animFSM.getStateNamed('Sleep'): + if base.localAvatar.sleepFlag == 1: + # I am sleeping so I send an autoreply message + if not base.cr.identifyAvatar(fromAV) == base.localAvatar: + self.sendUpdate("setSleepAutoReply" , [base.localAvatar.doId], fromAV) + + newText, scrubbed = self.scrubTalk(chat, mods) + self.displayTalkWhisper(fromAV, avatarName, chat, mods) + base.talkAssistant.receiveWhisperTalk(fromAV, avatarName, fromAC, None, self.doId, self.getName(), newText) + + def setSleepAutoReply(self, fromId): + """To be overrided by subclass""" + pass + + def _isValidWhisperSource(self, source): + return (isinstance(source, FriendHandle.FriendHandle) or + isinstance(source, DistributedToon)) + + def setWhisperSCEmoteFrom(self, fromId, emoteId): + """ + Receive and decode the SC message. + """ + handle = base.cr.identifyAvatar(fromId) + if handle == None: + return + + if not self._isValidWhisperSource(handle): + self.notify.warning('setWhisperSCEmoteFrom non-toon %s' % fromId) + return + + if base.cr.avatarFriendsManager.checkIgnored(fromId): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + # if base.localAvatar.animFSM.getCurrentState() == base.localAvatar.animFSM.getStateNamed('Sleep'): + if base.localAvatar.sleepFlag == 1: + # I am sleeping so I send an autoreply message + if not base.cr.identifyAvatar(fromId) == base.localAvatar: + self.sendUpdate("setSleepAutoReply" , [base.localAvatar.doId], fromId) + + chatString = SCDecoders.decodeSCEmoteWhisperMsg(emoteId, + handle.getName()) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTEmote) + base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_EMOTE, emoteId, fromId) + + def setWhisperSCFrom(self, fromId, msgIndex): + """ + Receive and decode the SpeedChat message. + """ + handle = base.cr.identifyAvatar(fromId) + if handle == None: + return + + if not self._isValidWhisperSource(handle): + self.notify.warning('setWhisperSCFrom non-toon %s' % fromId) + return + + if base.cr.avatarFriendsManager.checkIgnored(fromId): + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + if fromId in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + return + + if base.localAvatar.sleepFlag == 1: + # I am sleeping so I send an autoreply message + if not base.cr.identifyAvatar(fromId) == base.localAvatar: + self.sendUpdate("setSleepAutoReply" , [base.localAvatar.doId], fromId) + + chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTQuickTalker) + base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_NORMAL, msgIndex, fromId) + + ### setWhisperSCToontask ### + + def whisperSCToontaskTo(self, taskId, toNpcId, toonProgress, msgIndex, + sendToId): + """ + Sends a speedchat whisper message to the indicated + toon, prefixed with our own name. + """ + messenger.send("wakeup") + self.sendUpdate("setWhisperSCToontaskFrom", + [self.doId, taskId, toNpcId, toonProgress, msgIndex], + sendToId) + + def setWhisperSCToontaskFrom(self, fromId, + taskId, toNpcId, toonProgress, msgIndex): + """ + Receive and decode the SC message. + """ + sender = base.cr.identifyAvatar(fromId) + if sender == None: + return + + if fromId in self.ignoreList: + # We're ignoring this jerk. + self.d_setWhisperIgnored(fromId) + + chatString = TTSCDecoders.decodeTTSCToontaskMsg( + taskId, toNpcId, toonProgress, msgIndex) + if chatString: + self.displayWhisper(fromId, chatString, WhisperPopup.WTQuickTalker) + + def setMaxNPCFriends(self, max): + self.maxNPCFriends = max + + def getMaxNPCFriends(self): + return self.maxNPCFriends + + def getNPCFriendsDict(self): + return self.NPCFriendsDict + + def setNPCFriendsDict(self, NPCFriendsList): + NPCFriendsDict = {} + for friendPair in NPCFriendsList: + NPCFriendsDict[friendPair[0]] = friendPair[1] + self.NPCFriendsDict = NPCFriendsDict + + def setMaxClothes(self, max): + self.maxClothes = max + + def getMaxClothes(self): + return self.maxClothes + + def getClothesTopsList(self): + return self.clothesTopsList + + def setClothesTopsList(self, clothesList): + self.clothesTopsList = clothesList + + def getClothesBottomsList(self): + return self.clothesBottomsList + + def setClothesBottomsList(self, clothesList): + self.clothesBottomsList = clothesList + + def catalogGenClothes(self, avId): + # this is used only when accepting new bottoms from the mailbox + # after a catalog purchase. + if avId == self.doId: + self.generateToonClothes() + self.loop('neutral') + + def isClosetFull(self, extraClothes = 0): + numClothes = len(self.clothesTopsList)/4 + len(self.clothesBottomsList)/2 + return (numClothes + extraClothes >= self.maxClothes) + + def setMaxHp(self, hitPoints): + DistributedPlayer.DistributedPlayer.setMaxHp(self, hitPoints) + # Just in case something has changed with the number of props + if self.inventory: + self.inventory.updateGUI() + + def died(self): + """ + The toon has run out of HP. + """ + # Tell the world (in particular, tell any battles). + messenger.send(self.uniqueName('died')) + + # make sure we go to a safezone! + if self.isLocal(): + target_sz = ZoneUtil.getSafeZoneId(self.defaultZone) + place = self.cr.playGame.getPlace() + if place and place.fsm: + place.fsm.request('died', [{ + "loader" : ZoneUtil.getLoaderName(target_sz), + "where" : ZoneUtil.getWhereName(target_sz, 1), + 'how' : 'teleportIn', + 'hoodId' : target_sz, + 'zoneId' : target_sz, + 'shardId' : None, + 'avId' : -1, + 'battle' : 1, + }]) + + def setInterface(self, string): + assert self.notify.debug("setting interface to %s" % string) + + def setZonesVisited(self, hoods): + self.safeZonesVisited = hoods + assert self.notify.debug("setting safe zone list to %s" % self.safeZonesVisited) + + def setHoodsVisited(self, hoods): + self.hoodsVisited = hoods + assert self.notify.debug("setting hood list to %s" % self.hoodsVisited) + # For now, visiting any HQ is enough to enable the disguise (and SOS) pages. + if (ToontownGlobals.SellbotHQ in hoods) or (ToontownGlobals.CashbotHQ in hoods) or (ToontownGlobals.LawbotHQ in hoods): + self.setDisguisePageFlag(1) + + def wrtReparentTo(self, parent): + # We need to define this in DistributedToon just to force the + # right function to be called (we need the + # DistributedSmoothNode flavor to be called, but it wants to + # call the NodePath flavor instead). + DistributedSmoothNode.DistributedSmoothNode.wrtReparentTo(self, parent) + + + ### setTutorialAck ### + def setTutorialAck(self, tutorialAck): + """ + This flag tells whether the player has acknowledged the opportunity + for a tutorial. + """ + self.tutorialAck = tutorialAck + + + ### setEarnedExperience ### + def setEarnedExperience(self, earnedExp): + # The AI uses this to tell the toon how much earned experience he + # has accumulated so far within a particular battle. This is + # important to allow the client to gray out gag buttons when the + # toon exceeds his experience cap for the battle. + self.earnedExperience = earnedExp + + + ### setTunnelIn ### + def b_setTunnelIn(self, endX, tunnelOrigin): + timestamp = globalClockDelta.getFrameNetworkTime() + pos = tunnelOrigin.getPos(render) + h = tunnelOrigin.getH(render) + self.setTunnelIn(timestamp, endX, pos[0], pos[1], pos[2], h) + self.d_setTunnelIn(timestamp, endX, pos[0], pos[1], pos[2], h) + + def d_setTunnelIn(self, timestamp, endX, x, y, z, h): + self.sendUpdate("setTunnelIn", [timestamp, endX, x, y, z, h]) + + def setTunnelIn(self, timestamp, endX, x, y, z, h): + t = globalClockDelta.networkToLocalTime(timestamp) + self.handleTunnelIn(t, endX, x, y, z, h) + + def getTunnelInToonTrack(self, endX, tunnelOrigin): + # create a node that the toon will swing around + pivotNode = tunnelOrigin.attachNewNode(self.uniqueName('pivotNode')) + pivotNode.setPos(*self.tunnelPivotPos) + pivotNode.setHpr(0,0,0) + + pivotY = pivotNode.getY(tunnelOrigin) + endY = 5. + straightLerpDur = abs(endY-pivotY) / ToonForwardSpeed + pivotDur = 2. + # lerp the toon's X position over the last 90 degrees + pivotLerpDur = pivotDur * (90./self.pivotAngle) + + self.reparentTo(pivotNode) + self.setPos(0,0,0) + # store the X position that the toon should be in at the end + # of the pivot + self.setX(tunnelOrigin, endX) + targetX = self.getX() + self.setX(self.tunnelCenterOffset + \ + ((targetX - self.tunnelCenterOffset) * \ + (1.-self.tunnelCenterInfluence))) + self.setHpr(tunnelOrigin, 0, 0, 0) + pivotNode.setH(-self.pivotAngle) + + return Sequence( + Wait(.8), # give remote clients a fighting chance at + # starting their animations in time, so that + # toons run around the corner, instead of + # popping into existence + Parallel( + LerpHprInterval(pivotNode, pivotDur, hpr=Point3(0,0,0), + name=self.uniqueName("tunnelInPivot")), + Sequence( + Wait(pivotDur - pivotLerpDur), + LerpPosInterval(self, pivotLerpDur, pos=Point3(targetX,0,0), + name=self.uniqueName("tunnelInPivotLerpPos")), + ), + ), + Func(self.wrtReparentTo, render), + Func(pivotNode.removeNode), + LerpPosInterval(self, straightLerpDur, + pos=Point3(endX, endY, 0.1), + other=tunnelOrigin, + name=self.uniqueName("tunnelInStraightLerp")), + ) + + def handleTunnelIn(self, startTime, endX, x, y, z, h): + """ + this handles tunnel in animations for distributed toons + see LocalToon.py for the local toon animation + """ + assert self.notify.debug("DistributedToon.handleTunnelIn") + + self.stopSmooth() + # don't parent to render yet -- offscreen nametag would show up + #self.reparentTo(render) + + # create a temporary tunnel origin node + tunnelOrigin = render.attachNewNode('tunnelOrigin') + tunnelOrigin.setPosHpr(x,y,z,h,0,0) + + self.tunnelTrack = Sequence( + self.getTunnelInToonTrack(endX, tunnelOrigin), + Func(tunnelOrigin.removeNode), + Func(self.startSmooth), + ) + # add in the smoothing delay + # TODO: this won't work perfectly until + # - telemetry is queued even when smoothing is off + # - we prevent remote toon's Place ClassicFSM from putting them + # into the neutral cycle + tOffset = globalClock.getFrameTime() - \ + (startTime + self.smoother.getDelay()) + if tOffset < 0.: + self.tunnelTrack = Sequence( + Wait(-tOffset), + self.tunnelTrack, + ) + self.tunnelTrack.start() + else: + self.tunnelTrack.start(tOffset) + + ### setTunnelOut ### + def b_setTunnelOut(self, startX, startY, tunnelOrigin): + timestamp = globalClockDelta.getFrameNetworkTime() + pos = tunnelOrigin.getPos(render) + h = tunnelOrigin.getH(render) + self.setTunnelOut(timestamp, startX, startY, + pos[0], pos[1], pos[2], h) + self.d_setTunnelOut(timestamp, startX, startY, + pos[0], pos[1], pos[2], h) + + def d_setTunnelOut(self, timestamp, startX, startY, x, y, z, h): + self.sendUpdate("setTunnelOut", + [timestamp, startX, startY, x, y, z, h]) + + def setTunnelOut(self, timestamp, startX, startY, x, y, z, h): + t = globalClockDelta.networkToLocalTime(timestamp) + self.handleTunnelOut(t, startX, startY, x, y, z, h) + + def getTunnelOutToonTrack(self, startX, startY, tunnelOrigin): + startPos = self.getPos(tunnelOrigin) + startHpr = self.getHpr(tunnelOrigin) + reducedAvH = PythonUtil.fitDestAngle2Src(startHpr[0], 180) + + # create a node that the toon will swing around + pivotNode = tunnelOrigin.attachNewNode(self.uniqueName('pivotNode')) + pivotNode.setPos(*self.tunnelPivotPos) + pivotNode.setHpr(0,0,0) + + pivotY = pivotNode.getY(tunnelOrigin) + straightLerpDur = abs(startY-pivotY) / ToonForwardSpeed + pivotDur = 2. + # lerp the toon's X position over the first 90 degrees + pivotLerpDur = pivotDur * (90./self.pivotAngle) + + def getTargetPos(self=self): + """ this is a thunk that returns the target pos (relative to + pivotNode) that the toon should lerp to during the pivot """ + pos = self.getPos() + return Point3(self.tunnelCenterOffset + \ + ((pos[0] - self.tunnelCenterOffset) * \ + (1.-self.tunnelCenterInfluence)), + pos[1], pos[2]) + return Sequence( + Parallel( + LerpPosInterval(self, straightLerpDur, + pos=Point3(startX, pivotY, 0.1), + startPos = startPos, + other=tunnelOrigin, + name=self.uniqueName("tunnelOutStraightLerp")), + LerpHprInterval(self, straightLerpDur * .8, + hpr=Point3(reducedAvH, 0, 0), + startHpr = startHpr, + other=tunnelOrigin, + name=self.uniqueName("tunnelOutStraightLerpHpr")), + ), + Func(self.wrtReparentTo, pivotNode), + Parallel( + LerpHprInterval(pivotNode, pivotDur, + hpr=Point3(-self.pivotAngle,0,0), + name=self.uniqueName("tunnelOutPivot")), + LerpPosInterval(self, pivotLerpDur, pos=getTargetPos, + name=self.uniqueName("tunnelOutPivotLerpPos")), + ), + Func(self.wrtReparentTo, render), + Func(pivotNode.removeNode), + ) + + + def handleTunnelOut(self, startTime, startX, startY, x, y, z, h): + """ + this handles tunnel out animations for distributed toons + see LocalToon.py for the local toon animation + """ + assert self.notify.debug("DistributedToon.handleTunnelOut") + + # create a temporary tunnel origin node + tunnelOrigin = render.attachNewNode('tunnelOrigin') + tunnelOrigin.setPosHpr(x,y,z,h,0,0) + + self.tunnelTrack = Sequence( + Func(self.stopSmooth), + self.getTunnelOutToonTrack(startX, startY, tunnelOrigin), + #Func(self.reparentTo, hidden), + Func(self.detachNode), + Func(tunnelOrigin.removeNode), + ) + # add in the smoothing delay + tOffset = globalClock.getFrameTime() - \ + (startTime + self.smoother.getDelay()) + if tOffset < 0.: + self.tunnelTrack = Sequence( + Wait(-tOffset), + self.tunnelTrack, + ) + self.tunnelTrack.start() + else: + self.tunnelTrack.start(tOffset) + + def enterTeleportOut(self, *args, **kw): + # We override the definition of this function in Toon.py so we + # can add DelayDelete to the track, so an exiting toon won't + # disappear mid-teleport. + Toon.Toon.enterTeleportOut(self, *args, **kw) + if self.track: + self.track.delayDelete = DelayDelete.DelayDelete(self, 'enterTeleportOut') + + def exitTeleportOut(self): + if (self.track != None): + DelayDelete.cleanupDelayDeletes(self.track) + Toon.Toon.exitTeleportOut(self) + + ### setAnimState ### + + def b_setAnimState(self, animName, animMultiplier=1.0, callback = None, + extraArgs=[]): + # We set the distributed anim state first, since when we set + # it locally it might call the callback, which could impact + # the distributed state. This is particularly true when our + # toon gets sleepy and goes to bed in ghost mode. + + self.d_setAnimState(animName, animMultiplier, None, extraArgs) + self.setAnimState(animName, animMultiplier, None, None, callback, extraArgs) + + def d_setAnimState(self, animName, animMultiplier=1.0, timestamp=None, + extraArgs=[]): + timestamp = globalClockDelta.getFrameNetworkTime() + self.sendUpdate("setAnimState", [animName, animMultiplier, timestamp]) + + def setAnimState(self, animName, animMultiplier=1.0, timestamp=None, + animType=None, callback=None, extraArgs=[]): + + # this is a band-aid: somehow animState was getting set to "None"? + if not animName or (animName == "None"): + return + + if (timestamp == None): + ts = 0.0 + else: + ts = globalClockDelta.localElapsedTime(timestamp) + + # protect against bogus anims + if self.animFSM.getStateNamed(animName): + if (animName in self.setAnimStateAllowedList): + self.animFSM.request( + animName, [animMultiplier, ts, callback, extraArgs]) + else: + self.notify.debug('Hacker trying to setAnimState on an illegal animation. Attacking toon = %d' % self.doId) + base.cr.centralLogger.writeClientEvent('Hacker trying to setAnimState on an illegal animation. Attacking toon = %d' % self.doId) + else: + # suspicious + self.sendUpdate("logSuspiciousEvent", ["setAnimState: " + animName]) + + self.cleanupPieInHand() + + ### setEmoteState ### + + def b_setEmoteState(self, animIndex, animMultiplier): + self.setEmoteState(animIndex, animMultiplier) + self.d_setEmoteState(animIndex, animMultiplier) + + def d_setEmoteState(self, animIndex, animMultiplier): + timestamp = globalClockDelta.getFrameNetworkTime() + self.sendUpdate("setEmoteState", [animIndex, animMultiplier, timestamp]) + + def setEmoteState(self, animIndex, animMultiplier, timestamp=None): + # A -1 is considered a clear -- do nothing Because this is a + # broadcast ram field, one you do any emote, you will always have a + # value sent to future toons you meet It is wasteful to have your + # old emote index and make these new toons do all that work to + # create the emote track, only to have them skip through it since + # the timestamp will be really far in the past. Instead, it is up + # to the localToon to "clear" his anim (emote) index after his + # emote is finished. This is done in Emote.py + if animIndex == TTEmote.EmoteClear: + assert self.notify.debug("setEmoteState: clearing emote state") + return + + if (timestamp == None): + ts = 0.0 + else: + ts = globalClockDelta.localElapsedTime(timestamp) + + assert(self.notify.debug("setEmoteState: animIndex: %s animMult: %s timestamp: %s" % + (animIndex, animMultiplier, timestamp))) + + # Put the animName in extraArgs to match the structure of the ClassicFSM + callback=None + extraArgs=[] + extraArgs.insert(0, animIndex) + + self.doEmote(animIndex, animMultiplier, ts, callback, extraArgs) + #self.animFSM.request( + # "Emote", [animMultiplier, ts, callback, extraArgs]) + + ### setCogStatus ### + def setCogStatus(self, cogStatusList): + assert self.notify.debug("setting cogs to %s" % cogStatusList) + self.cogs = cogStatusList + + ### setCogCount ### + def setCogCount(self, cogCountList): + assert self.notify.debug("setting cogCount to %s" % cogCountList) + self.cogCounts = cogCountList + # update the suit page + if hasattr(self, 'suitPage'): + self.suitPage.updatePage() + + + ### setCogRadar ### + def setCogRadar(self, radar): + assert self.notify.debug("setting cog radar to: %s" % radar) + self.cogRadar = radar + if hasattr(self, 'suitPage'): + self.suitPage.updateCogRadarButtons(radar) + + ### setBuildingRadar ### + def setBuildingRadar(self, radar): + assert self.notify.debug("setting building radar to: %s" % radar) + self.buildingRadar = radar + if hasattr(self, 'suitPage'): + self.suitPage.updateBuildingRadarButtons(radar) + + ### setCogTypes ### + def setCogTypes(self, types): + assert self.notify.debug("setting cog types to: %s" % types) + self.cogTypes = types + if self.disguisePage: + self.disguisePage.updatePage() + + ### setCogLevels ### + def setCogLevels(self, levels): + assert self.notify.debug("setting cog levels to: %s" % levels) + self.cogLevels = levels + if self.disguisePage: + self.disguisePage.updatePage() + + def getCogLevels(self): + return self.cogLevels + + ### setCogParts ### + def setCogParts(self, parts): + assert self.notify.debug("setting cog parts to: %s" % parts) + self.cogParts = parts + if self.disguisePage: + self.disguisePage.updatePage() + + def getCogParts(self): + return self.cogParts + + ### setCogMerits ### + def setCogMerits(self, merits): + assert self.notify.debug("setting cog merits to: %s" % merits) + self.cogMerits = merits + if self.disguisePage: + self.disguisePage.updatePage() + + def readyForPromotion(self, dept): + merits = base.localAvatar.cogMerits[dept] + totalMerits = CogDisguiseGlobals.getTotalMerits(self, dept) + #print "print merits[%d]: %d/%d" % (dept, merits, totalMerits) + if (merits >= totalMerits): + return 1 + else: + return 0 + + ### setCogIndex ### + + # -1 means we are not disguised as a cog. 0, 1, 2, 3 means we are disguised + # as a self.cogTypes[index] cog. + + def setCogIndex(self, index): + assert self.notify.debug("setCogIndex: %s isDisguised: %s" % (index, self.isDisguised)) + self.cogIndex = index + if self.cogIndex == -1: + # we are not a cog + if self.isDisguised: + self.takeOffSuit() + else: + # we are a cog + cogIndex = self.cogTypes[index] + (SuitDNA.suitsPerDept * index) + cog = SuitDNA.suitHeadTypes[cogIndex] + self.putOnSuit(cog) + + def isCog(self): + if self.cogIndex == -1: + return 0 + else: + return 1 + + ### setDisguisePageFlag ### + def setDisguisePageFlag(self, flag): + if flag and hasattr(self, "book"): + self.loadDisguisePages() + + # We don't attempt to unload the pages if the disguisePageFlag + # is ever set back to 0, since that doesn't normally happen + # during gameplay. + self.disguisePageFlag = flag + + ## Fish collection + def setFishCollection(self, genusList, speciesList, weightList): + assert(self.notify.debug("setFishCollection: genusList: %s speciesList: %s weightList: %s" % + (genusList, speciesList, weightList))) + self.fishCollection = FishCollection.FishCollection() + self.fishCollection.makeFromNetLists(genusList, speciesList, weightList) + + def getFishCollection(self): + return self.fishCollection + + ## Max fish tank + def setMaxFishTank(self, maxTank): + self.maxFishTank = maxTank + + def getMaxFishTank(self): + return self.maxFishTank + + ## Fish tank + def setFishTank(self, genusList, speciesList, weightList): + assert(self.notify.debug("setFishTank: genusList: %s speciesList: %s weightList: %s" % + (genusList, speciesList, weightList))) + self.fishTank = FishTank.FishTank() + self.fishTank.makeFromNetLists(genusList, speciesList, weightList) + messenger.send(self.uniqueName("fishTankChange")) + + def getFishTank(self): + return self.fishTank + + def isFishTankFull(self): + """ + Return 1 if the fish tank if full to capacity + Return 0 if there is room for more fish + """ + return (len(self.fishTank) >= self.maxFishTank) + + ## Fishing Rod + def setFishingRod(self, rodId): + assert self.notify.debug("setFishingRod: %s" % rodId) + self.fishingRod = rodId + + def getFishingRod(self): + return self.fishingRod + + ## Fishing trophy List + def setFishingTrophies(self, trophyList): + assert self.notify.debug("setting fish trophies to %s" % trophyList) + self.fishingTrophies = trophyList + + def getFishingTrophies(self): + return self.fishingTrophies + + ### setQuests ### + def setQuests(self, flattenedQuests): + assert self.notify.debug("setting quests to %s" % flattenedQuests) + # Build the real quest list from the flattened one from the network + questList = [] + # A quest is a list with + # (questId, npcId, otherId, rewardId, progress) + questLen = 5 + # Step from 2 to the end, by the questLen + for i in range(0, len(flattenedQuests), questLen): + questList.append(flattenedQuests[i:i+questLen]) + self.quests = questList + + if self == base.localAvatar: + messenger.send("questsChanged") + + def setQuestCarryLimit(self, limit): + assert self.notify.debug("setting questCarryLimit to %s" % limit) + self.questCarryLimit = limit + + if self == base.localAvatar: + messenger.send("questsChanged") + + def getQuestCarryLimit(self): + return self.questCarryLimit + + ### setMaxCarry ### + + def setMaxCarry(self, maxCarry): + self.maxCarry = maxCarry + # update the invenotry gui + if self.inventory: + self.inventory.updateGUI() + + def getMaxCarry(self): + return self.maxCarry + + ### Cheesy rendering effects ### + + def setCheesyEffect(self, effect, hoodId, expireTime): + # if hasattr(base.cr, "newsManager") and base.cr.newsManager: + # holidayIds = base.cr.newsManager.getHolidayIdList() + # if effect == ToontownGlobals.CESnowMan and ToontownGlobals.WINTER_CAROLING not in holidayIds: + # self.savedCheesyEffect = CENormal + # self.reconsiderCheesyEffect() + # return + # elif effect == ToontownGlobals.CEPumpkin and ToontownGlobals.TRICK_OR_TREAT not in holidayIds: + # self.savedCheesyEffect = CENormal + # self.reconsiderCheesyEffect() + # return + # else: + # taskMgr.doMethodLater(5.0, self.setCheesyEffect, "waitForNewsManagerToSetCheesyEffect", extraArgs = [effect, hoodId, expireTime]) + # return + self.savedCheesyEffect = effect + self.savedCheesyHoodId = hoodId + self.savedCheesyExpireTime = expireTime + + if self == base.localAvatar: + self.notify.debug("setCheesyEffect(%s, %s, %s)" % + (effect, hoodId, expireTime)) + if effect != ToontownGlobals.CENormal: + serverTime = time.time() + self.cr.getServerDelta() + duration = (expireTime * 60) - serverTime + if duration < 0: + self.notify.debug("effect should have expired %s ago." % (PythonUtil.formatElapsedSeconds(-duration))) + else: + self.notify.debug("effect will expire in %s." % (PythonUtil.formatElapsedSeconds(duration))) + + if self.activeState == DistributedObject.ESGenerated: + # If we get this message while the avatar is already + # generated, then lerp the effect in smoothly. + self.reconsiderCheesyEffect(lerpTime = 0.5) + + else: + # Otherwise, if we're getting this message as part of the + # generate sequence for the avatar, just set it + # immediately. + self.reconsiderCheesyEffect() + + def reconsiderCheesyEffect(self, lerpTime = 0): + # Reconsiders whether to apply the cheesy effect, or disable + # it, according to the current zoneId, etc. + #import pdb; pdb.set_trace() + effect = self.savedCheesyEffect + hoodId = self.savedCheesyHoodId + if not self.cr.areCheesyEffectsAllowed(): + effect = CENormal + + if hoodId != 0: + # Get our current hood and make sure it matches hoodId. + try: + currentHoodId = base.cr.playGame.hood.id + except: + # Oh, we don't know our current hood yet. Or maybe + # we're in our estate. + currentHoodId = None + + if hoodId == 1: + # hoodId 1 means any hood except TTC. + if currentHoodId == ToontownGlobals.ToontownCentral: + effect = CENormal + else: + # Any other hoodId means only in that hood. + if currentHoodId != None and currentHoodId != hoodId: + effect = CENormal + + if self.ghostMode: + effect = CEGhost + + self.applyCheesyEffect(effect, lerpTime = lerpTime) + + def setGhostMode(self, flag): + assert self.notify.debug("setGhostMode: %s" % flag) + # Ghost mode. This is kind of like a cheesy effect in that it + # makes the toon invisible (or nearly invisible depending on + # whether the local player has seeGhosts enabled), but it also + # has special meaning when it is applied to the local player + # (turning off certain collisions, etc.) + if self.ghostMode != flag: + self.ghostMode = flag + if not hasattr(self, "cr"): + # The toon has already been deleted, forget it. + return + + if self.activeState <= DistributedObject.ESDisabled: + self.notify.debug("not applying cheesy effect to disabled Toon") + elif self.activeState == DistributedObject.ESGenerating: + self.reconsiderCheesyEffect() + elif self.activeState == DistributedObject.ESGenerated: + self.reconsiderCheesyEffect(lerpTime = 0.5) + else: + self.notify.warning("unknown activeState: %s" % self.activeState) + + self.showNametag2d() + self.showNametag3d() + + # No one bumps into ghosts, except maybe other ghosts. + if hasattr(self, "collNode"): + if self.ghostMode: + self.collNode.setCollideMask(ToontownGlobals.GhostBitmask) + else: + self.collNode.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.PieBitmask) + + if self.isLocal(): + # Call methods defined on LocalAvatar only. If we + # defined stubs here, then we'd get into trouble with + # the multiple inheritance ambiguity. + if self.ghostMode: + self.useGhostControls() + else: + self.useWalkControls() + + if hasattr(base, 'wantPets') and base.wantPets: + def setPetTrickPhrases(self, petTricks): + # this is a list of trick IDs, not speedchat IDs + self.petTrickPhrases = petTricks + if self.isLocal(): + messenger.send('petTrickPhrasesChanged') + + # Update available custom speedchat messages + def setCustomMessages(self, customMessages): + self.customMessages = customMessages + if self.isLocal(): + messenger.send("customMessagesChanged") + + # Update available resistance speedchat messages + # this list should be of the form: + # [[msg1, msg1Charges], [msg2, msg2Charges], ...] + def setResistanceMessages(self, resistanceMessages): + self.resistanceMessages = resistanceMessages + if self.isLocal(): + messenger.send("resistanceMessagesChanged") + + def getResistanceMessageCharges(self, textId): + msgs = self.resistanceMessages + for i in range(len(msgs)): + if msgs[i][0] == textId: + return msgs[i][1] + + return 0 + + def setCatalogSchedule(self, currentWeek, nextTime): + self.catalogScheduleCurrentWeek = currentWeek + self.catalogScheduleNextTime = nextTime + + if self.isLocal(): + self.notify.debug("setCatalogSchedule(%s, %s)" % (currentWeek, nextTime)) + if nextTime: + serverTime = time.time() + self.cr.getServerDelta() + duration = (nextTime * 60) - serverTime + self.notify.debug("next catalog in %s." % (PythonUtil.formatElapsedSeconds(duration))) + + def setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog): + self.monthlyCatalog = CatalogItemList.CatalogItemList(monthlyCatalog) + self.weeklyCatalog = CatalogItemList.CatalogItemList(weeklyCatalog) + self.backCatalog = CatalogItemList.CatalogItemList(backCatalog) + + # If we never looked at the old catalog, pretend we did now. + # This will allow the new catalog notify message (which the AI + # is about to send) to generate a new notification. + if self.catalogNotify == ToontownGlobals.NewItems: + self.catalogNotify = ToontownGlobals.OldItems + + def setCatalogNotify(self, catalogNotify, mailboxNotify): + #noCat = " " + #noMail = " " + if len(self.weeklyCatalog) + len(self.monthlyCatalog) == 0: + catalogNotify = ToontownGlobals.NoItems + #noCat = "no catalog items" + if len(self.mailboxContents) == 0: + mailboxNotify = ToontownGlobals.NoItems + #noMail = "no mail items" + #print("Start setCatalogNotify") + #print("catalogNotify %s %s" % (catalogNotify, noCat)) + #print("mailboxNotify %s %s" % (mailboxNotify, noMail)) + + self.catalogNotify = catalogNotify + self.mailboxNotify = mailboxNotify + + if self.isLocal(): + self.gotCatalogNotify = 1 + self.refreshOnscreenButtons() + print("local") + #print("End setCatalogNotify") + + def setDeliverySchedule(self, onOrder): + self.onOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + if self == base.localAvatar: + assert self.notify.debug("setDeliverySchedule(%s)" % (self.onOrder)) + nextTime = self.onOrder.getNextDeliveryDate() + if nextTime != None: + serverTime = time.time() + self.cr.getServerDelta() + duration = (nextTime * 60) - serverTime + self.notify.debug("next delivery in %s." % (PythonUtil.formatElapsedSeconds(duration))) + messenger.send("setDeliverySchedule-%s" % (self.doId)) + + def setMailboxContents(self, mailboxContents): + self.mailboxContents = CatalogItemList.CatalogItemList(mailboxContents, store = CatalogItem.Customization) + messenger.send("setMailboxContents-%s" % (self.doId)) + + """ + def setDeliveryboxContents(self, deliveryboxContents): + self.deliveryboxContents = CatalogItemList.CatalogItemList(deliveryboxContents, store = CatalogItem.Customization | CatalogItem.GiftTag) + """ + + def setAwardSchedule(self, onOrder): + self.onAwardOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + if self == base.localAvatar: + assert self.notify.debug("setAwardSchedule(%s)" % (self.onAwardOrder)) + nextTime = self.onAwardOrder.getNextDeliveryDate() + if nextTime != None: + serverTime = time.time() + self.cr.getServerDelta() + duration = (nextTime * 60) - serverTime + self.notify.debug("next delivery in %s." % (PythonUtil.formatElapsedSeconds(duration))) + messenger.send("setAwardSchedule-%s" % (self.doId)) + + + def setAwardMailboxContents(self, awardMailboxContents): + self.notify.debug("Setting awardMailboxContents to %s." % (awardMailboxContents)) + self.awardMailboxContents = CatalogItemList.CatalogItemList(awardMailboxContents, store = CatalogItem.Customization ) + self.notify.debug("awardMailboxContents is %s." % (self.awardMailboxContents)) + messenger.send("setAwardMailboxContents-%s" % (self.doId)) + + def setAwardNotify(self, awardNotify): + """Handle the AI/Uberdog telling us if we have new, old, or no award.""" + self.notify.debug( "setAwardNotify( %s )" % awardNotify ) + self.awardNotify = awardNotify + + if self.isLocal(): + self.gotCatalogNotify = 1 + self.refreshOnscreenButtons() + + + def setGiftSchedule(self, onGiftOrder): + #self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + + self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if self == base.localAvatar: + assert self.notify.debug("setGetSchedule(%s)" % (self.onGiftOrder)) + nextTime = self.onGiftOrder.getNextDeliveryDate() + if nextTime != None: + serverTime = time.time() + self.cr.getServerDelta() + duration = (nextTime * 60) - serverTime + self.notify.debug("next delivery in %s." % (PythonUtil.formatElapsedSeconds(duration))) + + ### SplashEffect ### + def playSplashEffect(self, x, y, z): + # Show a splash + from toontown.effects import Splash + if self.splash == None: + self.splash = Splash.Splash(render) + self.splash.setPos(x, y, z) + self.splash.setScale(2) + self.splash.play() + # Play a splash sound + place = base.cr.playGame.getPlace() + if place: + # this sound is only used in certain locations but a hacked client can request that it be + # played at any time, even before this client has downloaded a location that supports it + if hasattr(place.loader, 'submergeSound'): + base.playSfx(place.loader.submergeSound, node=self) + + def d_playSplashEffect(self, x, y, z): + # Placeholder parameter to avoid server crash + self.sendUpdate("playSplashEffect", [x, y, z]) + + ### setTrackAccess ### + + def setTrackAccess(self, trackArray): + self.trackArray = trackArray + # update the inventory gui + if self.inventory: + self.inventory.updateGUI() + + def getTrackAccess(self): + return self.trackArray + + + def hasTrackAccess(self, track): + """ + Can this toon use this track? + Returns bool 0/1 + """ + return self.trackArray[track] + + ### setTrackProgress ### + + def setTrackProgress(self, trackId, progress): + """ + Update your progress training trackId. TrackId is an index into + ToontownBattleGlobals.Tracks and progress is a bitarray of progress + markers gathered. A trackId of -1 means you are not training any track. + """ + assert self.notify.debug("setting track %s progress to %s" % (trackId, progress)) + self.trackProgressId = trackId + self.trackProgress = progress + # update the track page + if hasattr(self, 'trackPage'): + self.trackPage.updatePage() + + def getTrackProgress(self): + return [self.trackProgressId, self.trackProgress] + + def getTrackProgressAsArray(self, maxLength = 15): + shifts = map(operator.rshift, maxLength * [self.trackProgress], \ + range(maxLength - 1, -1, -1)) + digits = map(operator.mod, shifts, maxLength * [2]) + digits.reverse() + return digits + + ### setTeleportAccess ### + + def setTeleportAccess(self, teleportZoneArray): + self.teleportZoneArray = teleportZoneArray + + def getTeleportAccess(self): + return self.teleportZoneArray + + def hasTeleportAccess(self, zoneId): + """ + Return true if this zoneId is in our teleport access array + (meaning we can teleport to it) + """ + return (zoneId in self.teleportZoneArray) + + + def setQuestHistory(self, questList): + assert self.notify.debug("setting quest history to %s" % questList) + self.questHistory = questList + + def getQuestHistory(self): + return self.questHistory + + def setRewardHistory(self, rewardTier, rewardList): + assert self.notify.debug("setting reward history to %s" % rewardList) + self.rewardTier = rewardTier + self.rewardHistory = rewardList + + def getRewardHistory(self): + return self.rewardTier, self.rewardHistory + + + ### overridden from DistributedSmoothNode ### + + def doSmoothTask(self, task): + # We override doSmoothTask() instead of smoothPosition(), to + # save on the overhead of one additional Python call. And we + # don't call up to the base class for the same reason. + + self.smoother.computeAndApplySmoothPosHpr(self, self) + self.setSpeed(self.smoother.getSmoothForwardVelocity(), + self.smoother.getSmoothRotationalVelocity()) + return Task.cont + + # this is here to ensure that the correct overloaded method is called + def d_setParent(self, parentToken): + DistributedSmoothNode.DistributedSmoothNode.d_setParent(self, + parentToken) + + ### setEmoteAccess + def setEmoteAccess(self, bits): + assert self.notify.debug("setting Emote access to %s" % bits) + self.emoteAccess = bits + + if self == base.localAvatar: + messenger.send("emotesChanged") + + def b_setHouseId(self, id): + self.setHouseId(id) + self.d_setHouseId(id) + + def d_setHouseId(self, id): + self.sendUpdate("setHouseId", [id]) + + def setHouseId(self, id): + self.houseId = id + + def getHouseId(self): + return self.houseId + + def setPosIndex(self, index): + self.posIndex = index + + def getPosIndex(self): + return self.posIndex + + ### goHome + #def b_goHome(self, zoneId): + # timestamp = globalClockDelta.getFrameNetworkTime() + # self.goHome(timestamp, zoneId) + # self.d_goHome(zoneId) + + #def d_goHome(self, zoneId): + # timestamp = globalClockDelta.getFrameNetworkTime() + # print "i'm going to request my estate zone" + # self.sendUpdate("requestEstateZone", [timestamp, zoneId]) + + #def goHome(self, timestamp, zoneId): + # print "i'm going loco on the client" + + def b_setSpeedChatStyleIndex(self, index): + realIndexToSend = 0 + if type(index) == type(0) and \ + 0 <= index and index < len(speedChatStyles): + realIndexToSend = index + else: + base.cr.centralLogger.writeClientEvent('Hacker alert b_setSpeedChatStyleIndex invalid') + self.setSpeedChatStyleIndex(realIndexToSend) + self.d_setSpeedChatStyleIndex(realIndexToSend) + return None # I don't know why we return None here + + def d_setSpeedChatStyleIndex(self, index): + realIndexToSend = 0 + if type(index) == type(0) and \ + 0 <= index and index < len(speedChatStyles): + realIndexToSend = index + else: + base.cr.centralLogger.writeClientEvent('Hacker alert d_setSpeedChatStyleIndex invalid') + self.sendUpdate("setSpeedChatStyleIndex", [realIndexToSend]) + + def setSpeedChatStyleIndex(self, index): + realIndexToUse = 0 + if type(index) == type(0) and \ + 0 <= index and index < len(speedChatStyles): + realIndexToUse = index + else: + base.cr.centralLogger.writeClientEvent('Hacker victim setSpeedChatStyleIndex invalid attacking toon = %d' % self.doId) + self.speedChatStyleIndex = realIndexToUse + # update the background color for our text + nameKey, arrowColor, rolloverColor, frameColor = \ + speedChatStyles[realIndexToUse] + self.nametag.setQtColor( + VBase4(frameColor[0], frameColor[1], frameColor[2], 1)) + + if self.isLocal(): + messenger.send("SpeedChatStyleChange", []) + + def getSpeedChatStyleIndex(self): + return self.speedChatStyleIndex + + ### setMaxMoney ### + + def setMaxMoney(self, maxMoney): + self.maxMoney = maxMoney + + def getMaxMoney(self): + return self.maxMoney + + ### setMoney ### + + def setMoney(self, money): + if money != self.money: + self.money = money + messenger.send(self.uniqueName("moneyChange"), [self.money]) + + def getMoney(self): + return self.money + + ### setMaxBankMoney ### + + def setMaxBankMoney(self, maxMoney): + self.maxBankMoney = maxMoney + + def getMaxBankMoney(self): + return self.maxBankMoney + + ### setBankMoney ### + + def setBankMoney(self, money): + self.bankMoney = money + messenger.send(self.uniqueName("bankMoneyChange"), [self.bankMoney]) + + def getBankMoney(self): + return self.bankMoney + + def getTotalMoney(self): + return self.getBankMoney() + self.getMoney() + + + ### Tossing a pie (used in final Boss Battle sequence) + + def presentPie(self, x, y, z, h, p, r, timestamp32): + if self.numPies <= 0: + # Someone's tossing pies who doesn't have any. + return + + if not launcher.getPhaseComplete(5): + # We haven't downloaded the pies yet (which are in + # phase_5, the battle phase), so don't try to show them. + return + + lastTossTrack = Sequence() + if self.tossTrack: + lastTossTrack = self.tossTrack + tossTrack = None + + ts = globalClockDelta.localElapsedTime(timestamp32, bits = 32) + + # Delay the toss by the same amount of time as the smoothing + # delay, so it will be in sync with the running animation. + ts -= self.smoother.getDelay() + ival = self.getPresentPieInterval(x, y, z, h, p, r) + + if (ts > 0): + # We're already late. + startTime = ts + lastTossTrack.finish() + else: + # We need to wait a bit. + ival = Sequence(Wait(-ts), + #Func(lastTossTrack.finish), + ival) + + # It seems we need to explicitly finish the lastTossTrack + # now to prevent a hard crash. Maybe a Python refcount + # bug? Investigate later. + lastTossTrack.finish() + startTime = 0 + + # Naming these intervals turns out to be a really bad idea for + # now--the CIntervalManager doesn't properly handle this case. + # Investigate later. + #ival = Sequence(ival, name = self.uniqueName('presentPie')) + ival = Sequence(ival) + ival.start(startTime) + self.tossTrack = ival + + def tossPie(self, x, y, z, h, p, r, sequence, power, timestamp32): + if self.numPies <= 0: + # Someone's tossing pies who doesn't have any. + return + + # Update this on the client just so we can more accurately + # validate a volley of pies at once. The AI will send the + # official update later. + if self.numPies != ToontownGlobals.FullPies: + self.setNumPies(self.numPies - 1) + self.lastTossedPie = globalClock.getFrameTime() + + if not launcher.getPhaseComplete(5): + # We haven't downloaded the pies yet (which are in + # phase_5, the battle phase), so don't try to show them. + return + + lastTossTrack = Sequence() + if self.tossTrack: + lastTossTrack = self.tossTrack + tossTrack = None + + lastPieTrack = Sequence() + if self.pieTracks.has_key(sequence): + lastPieTrack = self.pieTracks[sequence] + del self.pieTracks[sequence] + + ts = globalClockDelta.localElapsedTime(timestamp32, bits = 32) + + # Delay the toss by the same amount of time as the smoothing + # delay, so it will be in sync with the running animation. + ts -= self.smoother.getDelay() + toss, pie, flyPie = self.getTossPieInterval(x, y, z, h, p, r, power) + + if (ts > 0): + # We're already late. + startTime = ts + lastTossTrack.finish() + lastPieTrack.finish() + else: + # We need to wait a bit. + toss = Sequence(Wait(-ts), + #Func(lastTossTrack.finish), + toss) + pie = Sequence(Wait(-ts), + #Func(lastPieTrack.finish), + pie) + + # It seems we need to explicitly finish the lastTossTrack + # now to prevent a hard crash. Maybe a Python refcount + # bug? Investigate later. + lastTossTrack.finish() + lastPieTrack.finish() + startTime = 0 + + # Naming these intervals turns out to be a really bad idea for + # now--the CIntervalManager doesn't properly handle this case. + # Investigate later. + #ival = Sequence(ival, name = self.uniqueName('tossPie')) + self.tossTrack = toss + toss.start(startTime) + pie = Sequence(pie, Func(self.pieFinishedFlying, sequence)) + self.pieTracks[sequence] = pie + pie.start(startTime) + + def pieFinishedFlying(self, sequence): + if self.pieTracks.has_key(sequence): + del self.pieTracks[sequence] + + def pieFinishedSplatting(self, sequence): + if self.splatTracks.has_key(sequence): + del self.splatTracks[sequence] + + def pieSplat(self, x, y, z, sequence, pieCode, timestamp32): + if self.isLocal(): + # LocalToon ignores this message; he tosses his own pies. + return + + elapsed = globalClock.getFrameTime() - self.lastTossedPie + if elapsed > 30: + # Can't make a splat if you didn't toss a pie. + return + + if not launcher.getPhaseComplete(5): + # We haven't downloaded the pies yet (which are in + # phase_5, the battle phase), so don't try to show them. + return + + lastPieTrack = Sequence() + if self.pieTracks.has_key(sequence): + lastPieTrack = self.pieTracks[sequence] + del self.pieTracks[sequence] + + if self.splatTracks.has_key(sequence): + lastSplatTrack = self.splatTracks[sequence] + del self.splatTracks[sequence] + lastSplatTrack.finish() + + ts = globalClockDelta.localElapsedTime(timestamp32, bits = 32) + + # Delay the splat by the same amount of time as the smoothing + # delay, so it will be in sync with the toss animation. + ts -= self.smoother.getDelay() + splat = self.getPieSplatInterval(x, y, z, pieCode) + + splat = Sequence(Func(messenger.send, 'pieSplat', [self, pieCode]), + splat) + + if (ts > 0): + # We're already late. + startTime = ts + lastPieTrack.finish() + else: + # We need to wait a bit. + splat = Sequence(Wait(-ts), + #Func(lastPieTrack.finish), + splat) + + # It seems we need to explicitly finish the lastTossTrack + # now to prevent a hard crash. Maybe a Python refcount + # bug? Investigate later. + #lastPieTrack.finish() + startTime = 0 + + # Naming these intervals turns out to be a really bad idea for + # now--the CIntervalManager doesn't properly handle this case. + # Investigate later. + splat = Sequence(splat, + Func(self.pieFinishedSplatting, sequence)) + #name = self.uniqueName('pieSplat')) + self.splatTracks[sequence] = splat + splat.start(startTime) + + + def cleanupPies(self): + # Make sure the pie is not in our hand or flying through the air. + for track in self.pieTracks.values(): + track.finish() + self.pieTracks = {} + for track in self.splatTracks.values(): + track.finish() + self.splatTracks = {} + self.cleanupPieInHand() + + def cleanupPieInHand(self): + # Make sure the pie is not parented to our hand. + if self.tossTrack: + self.tossTrack.finish() + self.tossTrack = None + + self.cleanupPieModel() + + def setNumPies(self, numPies): + self.numPies = numPies + if self.isLocal(): + self.updatePieButton() + if numPies == 0: + self.interruptPie() + + def setPieType(self, pieType): + self.pieType = pieType + if self.isLocal(): + self.updatePieButton() + + def setTrophyScore(self, score): + self.trophyScore = score + + # Update the floating star over our head that we earn for + # having a certain trophy score. + + if self.trophyStar != None: + self.trophyStar.removeNode() + self.trophyStar = None + + if self.trophyStarSpeed != 0: + taskMgr.remove(self.uniqueName("starSpin")) + self.trophyStarSpeed = 0 + + if hasattr(self, 'gmIcon') and self.gmIcon: + return + + if self.trophyScore >= ToontownGlobals.TrophyStarLevels[4]: + # A gold star! + self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star') + self.trophyStar.reparentTo(self.nametag.getNameIcon()) + self.trophyStar.setScale(2) + self.trophyStar.setZ(2) + self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[4]) + self.trophyStarSpeed = 15 + if self.trophyScore >= ToontownGlobals.TrophyStarLevels[5]: + # Spinning! + taskMgr.add(self.__starSpin, self.uniqueName("starSpin")) + elif self.trophyScore >= ToontownGlobals.TrophyStarLevels[2]: + # A silver star! + self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star') + self.trophyStar.reparentTo(self.nametag.getNameIcon()) + self.trophyStar.setScale(1.5) + self.trophyStar.setZ(1.6) + self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[2]) + self.trophyStarSpeed = 10 + if self.trophyScore >= ToontownGlobals.TrophyStarLevels[3]: + # Spinning! + taskMgr.add(self.__starSpin, self.uniqueName("starSpin")) + elif self.trophyScore >= ToontownGlobals.TrophyStarLevels[0]: + # A bronze star. + self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star') + self.trophyStar.reparentTo(self.nametag.getNameIcon()) + self.trophyStar.setScale(1.5) + self.trophyStar.setZ(1.6) + self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[0]) + self.trophyStarSpeed = 8 + if self.trophyScore >= ToontownGlobals.TrophyStarLevels[1]: + # Spinning! + taskMgr.add(self.__starSpin, self.uniqueName("starSpin")) + + def __starSpin(self, task): + now = globalClock.getFrameTime() + r = now * self.trophyStarSpeed % 360.0 + self.trophyStar.setR(r) + return Task.cont + + def getZoneId(self): + """returns actual zone that toon is currently in""" + place = base.cr.playGame.getPlace() + if place: + return place.getZoneId() + else: + return None + + def getRequestID(self): + return CLIENT_GET_AVATAR_DETAILS + + def announceBingo(self): + #this message is passed by the ai when the toon should say "Bingo!" + self.setChatAbsolute(TTLocalizer.FishBingoBingo, CFSpeech|CFTimeout) + + def squish(self, damage): + if self == base.localAvatar: + base.cr.playGame.getPlace().fsm.request('squished') + self.stunToon() + self.setZ(self.getZ(render)+.025) + + def d_squish(self, damage): + self.sendUpdate("squish", [damage]) + + def b_squish(self, damage): + if not self.isStunned: + self.squish(damage) + self.d_squish(damage) + self.playDialogueForString("!") + + def getShadowJoint(self): + """ + Return the shadow joint + """ + return Toon.Toon.getShadowJoint(self) + + if base.wantKarts: + def hasKart( self ): + """ + Purpose: The hasKart Method determines whether the Toon + currently owns a kart. + + Params: None + Return: Bool - True or False + """ + return ( self.kartDNA[ KartDNA.bodyType ] != -1 ) + + def getKartDNA( self ): + """ + Purpose: The getKartDNA Method obtains the kart dna for the + toon. + + Params: None + Return: [] - the kart dna. + """ + return self.kartDNA + + def setTickets( self, numTickets ): + """ + Purpose: The setTickets Method sets the number of tickets a toon has. + Tickets are gained by winning races and events. + + Params: numTickets - the new nubmer of tickets + Return: None + """ + self.tickets = numTickets + + def getTickets( self ): + """ + Purpose: The getTickets Method obtains the number of + tickets that a toon can has. + + Params: None + Return: tickets + """ + return self.tickets + + def getAccessoryByType( self, accType ): + """ + Purpose: TODO - write this + + Params: None + Return: None + """ + return self.kartDNA[ accType ] + + def setCurrentKart(self,avId): + self.kartId=avId + + def releaseKart(self): + self.kartId=None + + def setKartBodyType( self, bodyType ): + """ + Purpose: The setKartBodyType Method sets the local client side + body type of the kart that the toon currently owns. + + + Params: bodyType - the body type of the kart which the toon + currently owns. + Return: None + """ + self.kartDNA[ KartDNA.bodyType ] = bodyType + + def getKartBodyType( self ): + """ + Purpose: The getKartBodyType Method obtains the local AI side + body type of the kart that the toon currently owns. + + Params: None + Return: bodyType - the body type of the kart. + """ + return self.kartDNA[ KartDNA.bodyType ] + + def setKartBodyColor( self, bodyColor ): + """ + Purpose: The d_setKartBodyColor Method appropriately sets + the body color of the lient on the client side by updating the + local kart dna. + + Params: bodyColor - the color of the kart body. + Return: None + """ + self.kartDNA[ KartDNA.bodyColor ] = bodyColor + + def getKartBodyColor( self ): + """ + Purpose: The getKartBodyColor Method obtains the current + body color of the kart. + + Params: None + Return: bodyColor - the color of the kart body. + """ + return self.kartDNA[ KartDNA.bodyColor ] + + def setKartAccessoryColor( self, accColor ): + """ + Purpose: The setKartAccessoryColor Method appropriately sets + the accessory color of the local client side by updating the kart + dna. + + Params: accColor - the color of the accessories. + Return: None + """ + self.kartDNA[ KartDNA.accColor ] = accColor + + def getKartAccessoryColor( self ): + """ + Purpose: The getKartAccessoryColor Method obtains the + accessory color for the kart. + + Params: None + Return: accColor - the color of the accessories + """ + return self.kartDNA[ KartDNA.accColor ] + + def setKartEngineBlockType( self, ebType ): + """ + Purpose: The setKartEngineBlockType Method sets the engine + block type accessory for the kart by updating the Kart DNA. + + Params: ebType - the type of engine block accessory. + Return: None + """ + self.kartDNA[ KartDNA.ebType ] = ebType + + def getKartEngineBlockType( self ): + """ + Purpose: The getKartEngineBlockType Method obtains the engine + block type accessory for the kart by accessing the + current Kart DNA. + + Params: None + Return: ebType - the type of engine block accessory. + """ + return self.kartDNA[ KartDNA.ebType ] + + def setKartSpoilerType( self, spType ): + """ + Purpose: The setKartSpoilerType Method sets the spoiler + type accessory for the kart by updating the Kart DNA. + + Params: spType - the type of spoiler accessory + Return: None + """ + self.kartDNA[ KartDNA.spType ] = spType + + def getKartSpoilerType( self ): + """ + Purpose: The getKartSpoilerType Method obtains the spoiler + type accessory for the kart by accessing the current Kart DNA. + + Params: None + Return: spType - the type of spoiler accessory + """ + return self.kartDNA[ KartDNA.spType ] + + def setKartFrontWheelWellType( self, fwwType ): + """ + Purpose: The setKartFrontWheelWellType Method sets the + front wheel well accessory for the kart updating the + Kart DNA. + + Params: fwwType - the type of Front Wheel Well accessory + Return: None + """ + self.kartDNA[ KartDNA.fwwType ] = fwwType + + def getKartFrontWheelWellType( self ): + """ + Purpose: The getKartFrontWheelWellType Method obtains the + front wheel well accessory for the kart accessing the + Kart DNA. + + Params: None + Return: fwwType - the type of Front Wheel Well accessory + """ + return self.kartDNA[ KartDNA.fwwType ] + + def setKartBackWheelWellType( self, bwwType ): + """ + Purpose: The setKartWheelWellType Method sets the Back + Wheel Wheel accessory for the kart by updating the Kart DNA. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + self.kartDNA[ KartDNA.bwwType ] = bwwType + + def getKartBackWheelWellType( self ): + """ + Purpose: The getKartWheelWellType Method obtains the Back + Wheel Wheel accessory for the kart by accessing the Kart DNA. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + return self.kartDNA[ KartDNA.bwwType ] + + def setKartRimType( self, rimsType ): + """ + Purpose: The setKartRimType Method sets the rims accessory + for the karts tires by updating the Kart DNA. + + Params: rimsType - the type of rims for the kart tires. + Return: None + """ + self.kartDNA[ KartDNA.rimsType ] = rimsType + + def setKartDecalType( self, decalType ): + """ + Purpose: The setKartDecalType Method sets the decal + accessory of the kart by updating the Kart DNA. + + Params: decalType - the type of decal set for the kart. + Return: None + """ + self.kartDNA[ KartDNA.decalType ] = decalType + + def getKartDecalType( self ): + """ + Purpose: The getKartDecalType Method obtains the decal + accessory of the kart by accessing the Kart DNA. + + Params: None + Return: decalType - the type of decal set for the kart. + """ + return self.kartDNA[ KartDNA.decalType ] + + def getKartRimType( self ): + """ + Purpose: The setKartRimType Method sets the rims accessory + for the karts tires by accessing the Kart DNA. + + Params: None + Return: rimsType - the type of rims for the kart tires. + """ + return self.kartDNA[ KartDNA.rimsType ] + + def setKartAccessoriesOwned( self, accessories ): + """ + Purpose: The setKartAccessoriesOwned Method properly sets the + Accessories owned by the toon on the client side. + + Params: accessories - the ids of the accessories owned. + Return: None + """ + #if this list is shorter than 16, make it 16 + while len(accessories) < 16: + accessories.append(-1) + + assert ( len( accessories ) == 16 ), "DistrubtedToon::setKartAccessoriesOwned - len( accessories != 16 )" + self.accessories = accessories + + def getKartAccessoriesOwned( self ): + """ + Purpose: The getKartAccessoriesOwned Method retrieves the + accessories that are owned by the toon on the client side. + + Params: None + Return: [] - List of Accessories owned by the toon. + """ + owned = copy.deepcopy(self.accessories) + while InvalidEntry in owned: + owned.remove(InvalidEntry) + return owned + + def requestKartDNAFieldUpdate( self, dnaField, fieldValue ): + """ + Purpose: The requestAccessoryUpdate Method sends a distributed + message request to the AI to update the accessory of a + particular type. + + Params: accessoryType - the kind of accessory to update + accessoryId - the new accessory id. + Return: None + """ + self.notify.debug( "requestKartDNAFieldUpdate - dnaField %s, fieldValue %s" % ( dnaField, fieldValue ) ) + self.sendUpdate( "updateKartDNAField", [ dnaField, fieldValue ] ) + + def requestAddOwnedAccessory( self, accessoryId ): + """ + Purpose: The requestAddOwnedAccessory Method sends a distributed + message request to the AI to add a new accessory to the toon's + owned accessory list. + + Params: accessoryId - the id of the accessory that has been purchased. + Return: None + """ + self.notify.debug( "requestAddOwnedAccessor - purchased accessory %s" % ( accessoryId ) ) + self.sendUpdate( "addOwnedAccessory", [ accessoryId ] ) + + def requestRemoveOwnedAccessory( self, accessoryId ): + """ + Purpose: The requestRemoveOwnedAccessory Method sends a distributed + message request to the AI to remove an accessory from the toon's + owned accessory list. + + Params: accessoryId - the id of the accessory that should be removed. + Return: None + """ + self.notify.debug( "requestRemoveOwnedAccessor - removed accessory %s" % ( accessoryId ) ) + self.sendUpdate( "removeOwnedAccessory", [ accessoryId ] ) + + ## Karting trophy list + def setKartingTrophies(self, trophyList): + assert self.notify.debug("setting kart trophies to %s" % trophyList) + self.kartingTrophies = trophyList + + def getKartingTrophies(self): + return self.kartingTrophies + + ## Karting history list + def setKartingHistory(self, history): + assert self.notify.debug("setting kart history to %s" % history) + self.kartingHistory = history + + def getKartingHistory(self): + return self.kartingHistory + + ## Karting personal best list + def setKartingPersonalBest(self, bestTimes): + assert self.notify.debug("setting kart personal best to %s" % bestTimes) + self.kartingPersonalBest = bestTimes + + def getKartingPersonalBest(self): + return self.kartingPersonalBest + + def setKartingPersonalBest2(self, bestTimes2): + assert self.notify.debug("setting kart personal best to %s" % bestTimes2) + self.kartingPersonalBest2 = bestTimes2 + + def getKartingPersonalBest2(self): + return self.kartingPersonalBest2 + + def getKartingPersonalBestAll(self): + return self.kartingPersonalBest + \ + self.kartingPersonalBest2 + + if hasattr(base, 'wantPets') and base.wantPets: + def setPetId(self, petId): + self.petId = petId + if petId == 0: + self.petDNA = None + elif self.isLocal(): + # make sure to add the pet to the friendsMap + base.cr.addPetToFriendsMap() + + def getPetId(self): + return self.petId + + def getPetId(self): + return self.petId + + def hasPet(self): + #print str(self.petId) + return (self.petId != 0) + + def b_setPetTutorialDone(self, bDone): + self.d_setPetTutorialDone(bDone) + self.setPetTutorialDone(bDone) + def d_setPetTutorialDone(self, bDone): + self.sendUpdate('setPetTutorialDone', [bDone]) + def setPetTutorialDone(self, bDone): + self.bPetTutorialDone = bDone + + def b_setFishBingoTutorialDone(self, bDone): + self.d_setFishBingoTutorialDone(bDone) + self.setFishBingoTutorialDone(bDone) + def d_setFishBingoTutorialDone(self, bDone): + self.sendUpdate('setFishBingoTutorialDone', [bDone]) + def setFishBingoTutorialDone(self, bDone): + self.bFishBingoTutorialDone = bDone + + def b_setFishBingoMarkTutorialDone(self, bDone): + self.d_setFishBingoMarkTutorialDone(bDone) + self.setFishBingoMarkTutorialDone(bDone) + def d_setFishBingoMarkTutorialDone(self, bDone): + self.sendUpdate('setFishBingoMarkTutorialDone', [bDone]) + def setFishBingoMarkTutorialDone(self, bDone): + self.bFishBingoMarkTutorialDone = bDone + + def b_setPetMovie(self, petId, flag): + self.d_setPetMovie(petId, flag) + self.setPetMovie(petId, flag) + def d_setPetMovie(self, petId, flag): + self.sendUpdate('setPetMovie', [petId, flag]) + def setPetMovie(self, petId, flag): + pass + + def lookupPetDNA(self): + + # If self.petId is not 0 but self.petDNA is None, this + # sends a requst to the server to go look up our pet's + # DNA. + + # self.petDNA will then be filled in at some unspecified + # time after lookupPetDNA() has been called. You don't + # even get a callback, so you should be prepared for + # self.petDNA to still be None by the time you need it. + + if self.petId and not self.petDNA: + from toontown.pets import PetDetail + PetDetail.PetDetail(self.petId, self.__petDetailsLoaded) + def __petDetailsLoaded(self, pet): + self.petDNA = pet.style + + def trickOrTreatTargetMet(self,beanAmount): + if(self.effect): + self.effect.stop() + + self.effect = TrickOrTreatTargetEffect(beanAmount) + self.effect.play() + + + def trickOrTreatMilestoneMet(self): + if(self.effect): + self.effect.stop() + + self.effect = TrickOrTreatMilestoneEffect() + self.effect.play() + + def winterCarolingTargetMet(self, beanAmount): + if(self.effect): + self.effect.stop() + + self.effect = WinterCarolingEffect(beanAmount) + self.effect.play() + + def d_reqCogSummons(self, type, suitIndex): + if type == 'single': + pass + elif type == 'building': + pass + elif type == 'invasion': + pass + self.sendUpdate("reqCogSummons", [type, suitIndex]) + + def cogSummonsResponse(self, returnCode, suitIndex, doId): + messenger.send("cog-summons-response", [returnCode, suitIndex, doId]) + + def setCogSummonsEarned(self, cogSummonsEarned): + self.cogSummonsEarned = cogSummonsEarned + + def getCogSummonsEarned(self): + return self.cogSummonsEarned + + def hasCogSummons(self, suitIndex, type = None): + summons = self.getCogSummonsEarned() + curSetting = summons[suitIndex] + + if type == "single": + return curSetting & 0x01 + elif type == "building": + return curSetting & 0x02 + elif type == "invasion": + return curSetting & 0x04 + + # just check to see if the toon has *any* for this suit + return curSetting + + + + #////////////////// Gardening Estates Expansion + # Flower collection + def setFlowerCollection(self, speciesList, varietyList): + self.flowerCollection = FlowerCollection.FlowerCollection() + self.flowerCollection.makeFromNetLists( speciesList, varietyList) + + def getFlowerCollection(self): + return self.flowerCollection + + ## Max flower basket + def setMaxFlowerBasket(self, maxFlowerBasket): + self.maxFlowerBasket = maxFlowerBasket + + def getMaxFlowerBasket(self): + return self.maxFlowerBasket + + def isFlowerBasketFull(self): + """ + Return 1 if the flower basket if full to capacity + Return 0 if there is room for more flower + """ + return (len(self.flowerBasket) >= self.maxFlowerBasket) + + + ## Flower Basket + def setFlowerBasket(self, speciesList, varietyList): + self.flowerBasket = FlowerBasket.FlowerBasket() + self.flowerBasket.makeFromNetLists(speciesList, varietyList) + messenger.send("flowerBasketUpdated") + + def getFlowerBasket(self): + return self.flowerBasket + + ## Shovel + def setShovel(self, shovelId): + self.shovel = shovelId + + def attachShovel(self): + self.shovelModel = self.getShovelModel() + self.shovelModel.reparentTo(self.rightHand) + return self.shovelModel + + def detachShovel(self): + if self.shovelModel: + self.shovelModel.removeNode() + + def getShovelModel(self): + shovels = loader.loadModel('phase_5.5/models/estate/shovels') + shovelId = ['A','B','C','D'][self.shovel] + shovel = shovels.find('**/shovel' + shovelId) + + shovel.setH(-90) + shovel.setP(216) + shovel.setX(0.2) + + #these values came from Richard + #shovel.setH(62.0) #rotx + #shovel.setP(85.7) #roty + #shovel.setR(-158.86) #rotz + + shovel.detachNode() + shovels.removeNode() + return shovel + + ## ShovelSkill + def setShovelSkill(self, skillLevel): + self.shovelSkill = skillLevel + + def getBoxCapability(self): + """ + based on his shovel and his current shovel skill, + how many jelly beans can we use + """ + return GardenGlobals.getShovelPower(self.shovel, self.shovelSkill) + + ## WateringCan + def setWateringCan(self, wateringCanId): + self.wateringCan = wateringCanId + + def attachWateringCan(self): + self.wateringCanModel = self.getWateringCanModel() + self.wateringCanModel.reparentTo(self.rightHand) + return self.wateringCanModel + + def detachWateringCan(self): + if self.wateringCanModel: + self.wateringCanModel.removeNode() + #if hasattr(self,'debugAxis'): + # self.debugAxis.removeNode() + + + def getWateringCanModel(self): + #if not hasattr(self,'debugAxis'): + # self.debugAxis = loader.loadModel('models/misc/xyzAxis') + # self.debugAxis.reparentTo(self.rightHand) + + #s x y z h p r + scalePosHprsTable = ( (0.25, 0.1, 0 ,0.2, -90,-125, -45), + (0.2, 0.0, 0.25 ,0.2, -90,-125, -45), + (0.2, 0.2, 0.1 ,0.2, -90,-125, -45), + (0.2, 0.0, 0.25 ,0.2, -90,-125, -45),) + + cans = loader.loadModel('phase_5.5/models/estate/watering_cans') + canId = ['A','B','C','D'][self.wateringCan] + can = cans.find('**/water_can' + canId) + + can.setScale(scalePosHprsTable[self.wateringCan][0]) + can.setPos( scalePosHprsTable[self.wateringCan][1], + scalePosHprsTable[self.wateringCan][2], + scalePosHprsTable[self.wateringCan][3]) + can.setHpr( scalePosHprsTable[self.wateringCan][4], + scalePosHprsTable[self.wateringCan][5], + scalePosHprsTable[self.wateringCan][6]) + + + can.detachNode() + cans.removeNode() + + + if hasattr(base,'rwc'): + if base.rwc: + if hasattr(self,'wateringCan2'): + self.wateringCan2.removeNode() + self.wateringCan2 = can.copyTo(self.rightHand) + #self.wateringCan2.reparentTo(self.rightHand) + else: + self.wateringCan2.removeNode() + + + return can + + ## WateringCanSkill + def setWateringCanSkill(self, skillLevel): + self.wateringCanSkill = skillLevel + + ## GardenSpecials + def setGardenSpecials(self, specials): + self.gardenSpecials = specials + + # update the garden page + if hasattr(self, 'gardenPage') and self.gardenPage: + self.gardenPage.updatePage() + + def getGardenSpecials(self): + return self.gardenSpecials + + def getMyTrees(self): + treeDict = self.cr.getObjectsOfClass(DistributedGagTree.DistributedGagTree) + trees = [] + for tree in treeDict.values(): + if tree.getOwnerId() == self.doId: + trees.append(tree) + + if not trees: + # what happens when the trees aren;t around right now? + pass + + return trees + + def isTreePlanted(self, track, level): + trees = self.getMyTrees() + for tree in trees: + if tree.gagTrack == track and tree.gagLevel == level: + return True + + return False + + def doIHaveRequiredTrees(self, track, level): + """ + When planting a level 4 gag tree, make sure we have the level 1, 2, and 3 gag tree + """ + trees = self.getMyTrees() + trackAndLevelList = [] + for tree in trees: + trackAndLevelList.append( (tree.gagTrack, tree.gagLevel) ) + + haveRequired = True + + for curLevel in range(level): + testTuple = (track, curLevel) + if not testTuple in trackAndLevelList: + haveRequired=False + break + + return haveRequired + + + ### setTrackBonusLevel ### + + def setTrackBonusLevel(self, trackArray): + self.trackBonusLevel = trackArray + # update the inventory gui + if self.inventory: + self.inventory.updateGUI() + + def getTrackBonusLevel(self, track=None): + if track == None: + return self.trackBonusLevel + else: + return self.trackBonusLevel[track] + + def checkGagBonus(self, track, level): + trackBonus = self.getTrackBonusLevel(track) + return (trackBonus >= level) + + ## Garden trophy List + def setGardenTrophies(self, trophyList): + assert self.notify.debug("setting fish trophies to %s" % trophyList) + self.gardenTrophies = trophyList + + def getGardenTrophies(self): + return self.gardenTrophies + + def useSpecialResponse(self, returnCode): + messenger.send("use-special-response", [returnCode]) + + + def setGardenStarted(self, bStarted): + self.gardenStarted = bStarted + #if hasattr(self, 'gardenPage'): + # self.gardenPage.updatePage() + + def getGardenStarted(self): + return self.gardenStarted + + def sendToGolfCourse(self, zoneId): + print("sending to golfCourse") + hoodId = self.cr.playGame.hood.hoodId + golfRequest = { + "loader": "safeZoneLoader", + "where": "golfcourse", + "how" : "teleportIn", + "hoodId" : hoodId, + "zoneId" : zoneId, + "shardId" : None, + "avId" : -1, + } + base.cr.playGame.getPlace().requestLeave(golfRequest) + + def getGolfTrophies(self): + """Get the golf trophies this toon has won.""" + return self.golfTrophies + + def getGolfCups(self): + """Get the golf cups this toon has won. 10 trophies awards you 1 cup.""" + return self.golfCups + + ## Golf history list + def setGolfHistory(self, history): + assert self.notify.debug("setting golf history to %s" % history) + self.golfHistory = history + + # update our trophies and cups too + self.golfTrophies = GolfGlobals.calcTrophyListFromHistory(self.golfHistory) + self.golfCups = GolfGlobals.calcCupListFromHistory(self.golfHistory) + + # if case we have just finished our first game... + if hasattr(self, 'book'): + self.addGolfPage() + + def getGolfHistory(self): + return self.golfHistory + + def hasPlayedGolf(self): + """Returns True if this toon has ever played golf.""" + retval = False + for historyValue in self.golfHistory: + if historyValue: + retval = True; + break + return retval + + def setPackedGolfHoleBest(self, packedHoleBest): + """Set the packed personal hole best on the client.""" + unpacked = GolfGlobals.unpackGolfHoleBest(packedHoleBest) + self.setGolfHoleBest(unpacked) + + def setGolfHoleBest(self, holeBest): + """Set the personal hole best on the client.""" + self.golfHoleBest = holeBest + + def getGolfHoleBest(self): + """Return the personal hole best.""" + return self.golfHoleBest + + def setGolfCourseBest(self, courseBest): + """Set the personal course best on the client.""" + self.golfCourseBest = courseBest + + def getGolfCourseBest(self): + """Return the personal course best.""" + return self.golfCourseBest + + def setUnlimitedSwing(self, unlimitedSwing): + """Set if we can swing an unlimited number of times in golf.""" + self.unlimitedSwing = unlimitedSwing + + def getUnlimitedSwing(self): + """Returns true if we can swing an unlimited number of times in golf.""" + return self.unlimitedSwing + + def getPinkSlips(self): + if hasattr(self, "pinkSlips"): + return self.pinkSlips + else: + return 0 + + def setPinkSlips(self, pinkSlips): + """Set the number of pink slips.""" + self.pinkSlips = pinkSlips + + def setAccess(self, access): + self.setGameAccess(access) + self.setDisplayName(self.getName()) #fancy nametag + #if access == OTPGlobals.AccessFull: + # base.cr.setIsPaid(1) + #elif access == OTPGlobals.AccessVelvetRope + # base.cr.setIsPaid(0) + #else: + # base.cr.setIsPaid(0) + + def setGameAccess(self, access): + self.gameAccess = access + + def getGameAccess(self): + if hasattr(self, "gameAccess"): + return self.gameAccess + else: + return 0 + + # Name Tag Styles + + def setDisplayName(self, str): + if(self.getGameAccess() == OTPGlobals.AccessFull) and (not self.isDisguised): + self.setFancyNametag(name = str) + else: + self.removeFancyNametag() + Avatar.Avatar.setDisplayName(self, str) + + + def setFancyNametag(self, name = None): + + if name == None: + name = self.getName() + #font = ToontownGlobals.getToonFont() + #self.setFont(font) + #Avatar.Avatar.setDisplayName(self, name) + if self.getNametagStyle() == 100: + self.setFont(ToontownGlobals.getToonFont()) + else: + self.setFont(ToontownGlobals.getNametagFont(self.getNametagStyle())) + #self.nametag.setShadow(0.03,0.03) + Avatar.Avatar.setDisplayName(self, name) + self.setFont(ToontownGlobals.getToonFont()) + + + def removeFancyNametag(self): + self.nametag.clearShadow() + pass + + def getNametagStyle(self): + if hasattr(self, "nametagStyle"): + return self.nametagStyle + else: + return 0 + + def setNametagStyle(self, nametagStyle): + """Set the nametag style.""" + if hasattr(self, 'gmToon') and self.gmToon: + return + + # Force a font that has numbers. + if base.config.GetBool('want-nametag-avids', 0): + nametagStyle = 0 + + self.nametagStyle = nametagStyle + self.setDisplayName(self.getName()) + + + def getAvIdName(self): + # Add F (Free) or P (Paid) to want-avid-tags suggested by Red for avatar ID tags. + paidStr = PythonUtil.choice(self.getGameAccess() == OTPGlobals.AccessFull, "P", "F") + return "%s\n%s (%s)" % (self.getName(), self.doId, paidStr) + + def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1): + if interrupt and (self.__currentDialogue is not None): + self.__currentDialogue.stop() + self.__currentDialogue = dialogue + # If an AudioSound has been passed in, play that for dialog to + # go along with the chat. Interrupt any sound effect currently playing + if dialogue: + base.playSfx(dialogue, node=self) + # If it is a speech-type chat message, and the avatar isn't + # too far away to hear, play the appropriate sound effect. + elif (chatFlags & CFSpeech) != 0: + if (self.nametag.getNumChatPages() > 0): + # play the dialogue sample + + # We use getChat() instead of chatString, which + # returns just the current page of a multi-page chat + # message. This way we aren't fooled by long pages + # that end in question marks. + self.playDialogueForString(self.nametag.getChat()) + if (self.soundChatBubble != None): + base.playSfx(self.soundChatBubble, node=self) + elif (self.nametag.getChatStomp() > 0 ): + self.playDialogueForString(self.nametag.getStompText(), self.nametag.getStompDelay()) + + def playDialogueForString(self, chatString, delay = 0.0): + """ + Play dialogue samples to match the given chat string + """ + if len(chatString) == 0: + return + # use only lower case for searching + searchString = chatString.lower() + # determine the statement type + if (searchString.find(OTPLocalizer.DialogSpecial) >= 0): + # special sound + type = "special" + elif (searchString.find(OTPLocalizer.DialogExclamation) >= 0): + #exclamation + type = "exclamation" + elif (searchString.find(OTPLocalizer.DialogQuestion) >= 0): + # question + type = "question" + else: + # statement (use two for variety) + if random.randint(0, 1): + type = "statementA" + else: + type = "statementB" + + # determine length + 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, delay) + + def playDialogue(self, type, length, delay = 0.0): + """playDialogue(self, string, int) + Play the specified type of dialogue for the specified time + """ + + # Inheritors may override this function or getDialogueArray(), + # above. + + # Choose the appropriate sound effect. + 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: + self.notify.error("unrecognized dialogue type: ", type) + + if sfxIndex != None and sfxIndex < len(dialogueArray) and \ + dialogueArray[sfxIndex] != None: + soundSequence = Sequence(Wait(delay), + SoundInterval(dialogueArray[sfxIndex], node = None, + listenerNode = base.localAvatar, + loop = 0, + volume = 1.0), + ) + self.soundSequenceList.append(soundSequence) + soundSequence.start() + + self.cleanUpSoundList() + + def cleanUpSoundList(self): + removeList = [] + for soundSequence in self.soundSequenceList: + if soundSequence.isStopped(): + removeList.append(soundSequence) + + for soundSequence in removeList: + self.soundSequenceList.remove(soundSequence) + + + def sendLogMessage(self, message): + self.sendUpdate("logMessage", [message]) + + def setChatAbsolute(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0): + #isFiltered = 1 + #if isFiltered: + # cleanString = self.replaceBadWords(chatString) + # DistributedAvatar.DistributedAvatar.setChatAbsolute(self, cleanString, chatFlags, dialogue, interrupt) + #else: + DistributedAvatar.DistributedAvatar.setChatAbsolute(self, chatString, chatFlags, dialogue, interrupt) + #if not quiet: + # base.chatAssistant.receiveAvatarOpenTypedChat(chatString, chatFlags, self.doId) + + def setChatMuted(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0): + """ + This method is a modification of setChatAbsolute in Toontown in which + just the text of the chat is displayed on the nametag. + No animal sound is played along with it. + """ + self.nametag.setChat(chatString, chatFlags) + # Removing CFSpeech from the chatFlags so that the chat is muted. + self.playCurrentDialogue(dialogue, (chatFlags - CFSpeech), interrupt) + + def displayTalk(self, chatString, mods = None): + flags = CFSpeech | CFTimeout + if base.talkAssistant.isThought(chatString): + flags = CFThought + chatString = base.talkAssistant.removeThoughtPrefix(chatString) + + self.nametag.setChat(chatString, flags) + if base.toonChatSounds: + self.playCurrentDialogue(None, flags, interrupt=1) + + + def setMail(self, mail): + """Set the mail""" + DistributedToon.partyNotify.debug( "setMail called with %d mail items" % len(mail) ) + self.mail = [] + for i in xrange(len(mail)): + oneMailItem = mail[i] + newMail = SimpleMailBase(*oneMailItem) + self.mail.append(newMail) + assert self.notify.debug("mail[%d]= %s" % (i, newMail)) + #self.simpleMailNotify= ToontownGlobals.NewItems + + #if self.isLocal(): + # self.gotCatalogNotify = 1 + # self.refreshOnscreenButtons() + + def setSimpleMailNotify(self, simpleMailNotify): + """Handle the AI/Uberdog telling us if we have new, old, or no simple mail.""" + DistributedToon.partyNotify.debug( "setSimpleMailNotify( %s )" % simpleMailNotify ) + self.simpleMailNotify = simpleMailNotify + + if self.isLocal(): + self.gotCatalogNotify = 1 + self.refreshOnscreenButtons() + + def setInviteMailNotify(self, inviteMailNotify): + """Handle the AI/Uberdog telling us if we have new, old, or no invite mail.""" + DistributedToon.partyNotify.debug( "setInviteMailNotify( %s )" % inviteMailNotify ) + self.inviteMailNotify = inviteMailNotify + + if self.isLocal(): + self.gotCatalogNotify = 1 + self.refreshOnscreenButtons() + + def setInvites( self, invites): + """Handle uberdog telling us our invitations. + This does not include invites we've already rejected.""" + DistributedToon.partyNotify.debug("setInvites called passing in %d invites." % len(invites)) + self.invites = [] + for i in xrange(len(invites)): + oneInvite=invites[i] + newInvite = InviteInfo(*oneInvite) + self.invites.append(newInvite) + assert self.notify.debug('self.invites[%d]= %s' % (i, newInvite)) + #self.updateInviteMailNotify() # we need to do this after we get partiesInvitedTo + + def updateInviteMailNotify(self): + """Calculate the value for inviteMailNotify, new invite, old invite or no invites""" + invitesInMailbox = self.getInvitesToShowInMailbox() + newInvites = 0 + readButNotRepliedInvites = 0 + for invite in invitesInMailbox: + if invite.status == PartyGlobals.InviteStatus.NotRead: + newInvites += 1 + elif invite.status == PartyGlobals.InviteStatus.ReadButNotReplied: + readButNotRepliedInvites += 1 + # we're guaranted that we have the associated partyInfo, but just in case + if __dev__ : + partyInfo = self.getOnePartyInvitedTo(invite.partyId) + if not partyInfo: + self.notify.error("party info not found in partiesInvtedTo, partyId = %s" % + str(invite.partyId)) + if newInvites: + self.setInviteMailNotify(ToontownGlobals.NewItems) + elif readButNotRepliedInvites: + self.setInviteMailNotify(ToontownGlobals.OldItems) + else: + self.setInviteMailNotify(ToontownGlobals.NoItems) + + def getInvitesToShowInMailbox(self): + """Return a list of inviteInfos that should be displayed in the mailbox.""" + # WARNING keep this in sync with DistributedToonAI.getInvitesToShowInMailbox + result = [] + for invite in self.invites: + appendInvite = True + if invite.status == InviteStatus.Accepted or \ + invite.status == InviteStatus.Rejected: + assert( self.notify.debug('Not showing accepted/rejected invite %s' % invite) ) + appendInvite = False + + if appendInvite: + # some invites are so far in the future we don't have the party info + partyInfo = self.getOnePartyInvitedTo(invite.partyId) + if not partyInfo: + appendInvite = False + # do not show invitations for cancelled parties + if appendInvite: + if partyInfo.status == PartyGlobals.PartyStatus.Cancelled: + appendInvite = False + # do not show mailbox invitations for parties that have finished yesterday + if appendInvite: + # server time and client time may be slightly off, and toon can get mailbox stuck when + # they dont return the same value, using yesterday it's still possible but only + # at around midnight, which minimizes the possibilty + # we use end time because a party could be started 1 minute before it's supposed to end + endDate= partyInfo.endTime.date() + curDate = base.cr.toontownTimeManager.getCurServerDateTime().date() + if endDate < curDate: + appendInvite = False + if appendInvite: + result.append(invite) + return result + + def getNumInvitesToShowInMailbox(self): + """Return how many invites we'll show in the mailbox.""" + result = len(self.getInvitesToShowInMailbox()) + return result + + def setHostedParties( self, hostedParties): + """Handle uberdog telling us our hosted parties.""" + DistributedToon.partyNotify.debug("setHostedParties called passing in %d parties." % len(hostedParties)) + self.hostedParties = [] + for i in xrange(len(hostedParties)): + hostedInfo = hostedParties[i] + newParty = PartyInfo(*hostedInfo) + self.hostedParties.append(newParty) + assert self.notify.debug('self.hostedParties[%d]= %s' % (i,newParty)) + + def setPartiesInvitedTo( self, partiesInvitedTo): + """Handle uberdog telling us details of parties we are invited to.""" + DistributedToon.partyNotify.debug("setPartiesInvitedTo called passing in %d parties." % len(partiesInvitedTo)) + self.partiesInvitedTo = [] + for i in xrange(len(partiesInvitedTo)): + partyInfo = partiesInvitedTo[i] + newParty = PartyInfo(*partyInfo) + self.partiesInvitedTo.append(newParty) + assert self.notify.debug('self.partiesInvitedTo[%d]= %s' % (i,newParty)) + self.updateInviteMailNotify() + + def getOnePartyInvitedTo(self, partyId): + """Return the partyInfo if partyId is in partiesInvitedTo, return None otherwise.""" + # It is possible to get an invite to a party so far in the future that it gets filtered out + # hence returning None is a valid result + result = None + for i in xrange(len(self.partiesInvitedTo)): + partyInfo = self.partiesInvitedTo[i] + if partyInfo.partyId == partyId: + result = partyInfo + break + return result + + def getInviteForPartyId(self, partyId): + """Return the invite that matches partyId, None if not found.""" + result = None + for invite in self.invites: + if invite.partyId == partyId: + result = invite + break + return result + + def setPartyReplies(self, replies): + """Handle uberdog telling us replies to our hosted parties.""" + DistributedToon.partyNotify.debug("setPartyReplies called passing in %d parties." % len(replies)) + self.partyReplyInfoBases = [] + for i in xrange(len(replies)): + partyReply = replies[i] + repliesForOneParty = PartyReplyInfoBase(*partyReply) + self.partyReplyInfoBases.append(repliesForOneParty) + assert DistributedToon.partyNotify.debug('self.partyReplyInfoBases[%d]= %s' % (i,repliesForOneParty)) + + def setPartyCanStart(self, partyId): + """Handle uberdog telling us we can start a party that we're hosting.""" + DistributedToon.partyNotify.debug("setPartyCanStart called passing in partyId=%s" % partyId) + for partyInfo in self.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = PartyGlobals.PartyStatus.CanStart + # we crash if we do the import at the top + from toontown.shtiker import EventsPage + if hasattr(self, "eventsPage") and \ + base.localAvatar.book.entered and \ + base.localAvatar.book.isOnPage(self.eventsPage) and \ + self.eventsPage.getMode() == EventsPage.EventsPage_Host: + base.localAvatar.eventsPage.loadHostedPartyInfo() + if hasattr(self, "displaySystemClickableWhisper"): + self.displaySystemClickableWhisper(0, TTLocalizer.PartyCanStart, + whisperType = WhisperPopup.WTSystem) + else: + self.setSystemMessage(0, TTLocalizer.PartyCanStart) + + def setPartyStatus(self, partyId, newStatus): + """Handle uberdog telling us there's a change in status for either the hosted or invitedTo parties.""" + DistributedToon.partyNotify.debug("setPartyCanStatus called passing in partyId=%s status=%s" % + (partyId, newStatus)) + + found = False + for partyInfo in self.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = newStatus + found = True + #if base.localAvatar.book.entered: + # base.localAvatar.eventsPage.loadHostedPartyInfo() + break + for partyInfo in self.partiesInvitedTo: + if partyInfo.partyId == partyId: + partyInfo.status = newStatus + found = True + # we crash if we do the import at the top + from toontown.shtiker import EventsPage + if hasattr(self, "eventsPage") and \ + base.localAvatar.book.entered and \ + base.localAvatar.book.isOnPage(self.eventsPage) and \ + self.eventsPage.getMode() == EventsPage.EventsPage_Invited: + base.localAvatar.eventsPage.loadInvitations() + if newStatus == PartyStatus.Started and hasattr(self, "displaySystemClickableWhisper"): + invite = self.getInviteForPartyId(partyId) + if invite: + name = " " + host = base.cr.identifyAvatar(partyInfo.hostId) + if host: + name = host.getName() + if GMUtils.testGMIdentity(name): + name = GMUtils.handleGMName(name) + if invite.status == InviteStatus.Accepted: + displayStr = TTLocalizer.PartyHasStartedAcceptedInvite % TTLocalizer.GetPossesive(name) + self.displaySystemClickableWhisper(-1, displayStr, + whisperType = WhisperPopup.WTSystem) + else: + displayStr = TTLocalizer.PartyHasStartedNotAcceptedInvite % TTLocalizer.GetPossesive(name) + self.setSystemMessage(partyInfo.hostId, displayStr, + whisperType = WhisperPopup.WTSystem) + break + if not found: + self.notify.warning("setPartyCanStart can't find partyId=% status=%d" % (partyId, newStatus)) + + + def announcePartyStarted(self, partyId): + DistributedToon.partyNotify.debug("announcePartyStarted") + # rely on the party has started system message whispers instead + return + + # get the guest list for this party + for partyReplyInfo in self.partyReplyInfoBases: + if partyReplyInfo.partyId == partyId: + for singleReply in partyReplyInfo.replies: + # if this toon is still on my friends list, whisper to them + # that my party has started + toonId = singleReply.inviteeId + if base.cr.isFriend(toonId): + if base.cr.isFriendOnline(toonId): + if singleReply.status == InviteStatus.Accepted: + # 'My party has started!' + self.whisperSCTo(5302, toonId, 0) + pass + else: + # they never accepted the invite + # 'My party has started!', + self.whisperSCTo(5302, toonId, 0) + pass + + def updateInvite(self, inviteKey, newStatus): + """We've gotten confirmation from the uberdog to reject/accept the invite.""" + DistributedToon.partyNotify.debug("updateInvite( inviteKey=%d, newStatus=%s )" %(inviteKey, InviteStatus.getString(newStatus)) ) + for invite in self.invites: + if invite.inviteKey == inviteKey: + invite.status = newStatus + self.updateInviteMailNotify() + break + + + def updateReply(self, partyId, inviteeId, newStatus): + """Someone accepted our invite while we were online.""" + DistributedToon.partyNotify.debug("updateReply( partyId=%d, inviteeId=%d, newStatus=%s )" %(partyId, inviteeId, InviteStatus.getString(newStatus)) ) + for partyReplyInfoBase in self.partyReplyInfoBases: + if partyReplyInfoBase.partyId == partyId: + for reply in partyReplyInfoBase.replies: + if reply.inviteeId == inviteeId: + reply.status = newStatus + break + + def scrubTalk(self, message, mods): + scrubbed = 0 + text = copy.copy(message) + for mod in mods: + index = mod[0] + length = mod[1] - mod[0] + 1 + newText = text[0:index] + length*"" + text[index + length:] + text = newText + + + words = text.split(" ") + newwords = [] + for word in words: + if word == "": + newwords.append(word) + elif word[0] == "": + #newwords.append("Bleep") + newwords.append("\1WLDisplay\1" + self.chatGarbler.garbleSingle(self, word) + "\2") + scrubbed = 1 + elif base.whiteList.isWord(word): + newwords.append(word) + else: + # If we are true friends then we shouldn't italacize the text + flag = 0 + for friendId, flags in self.friendsList: + if not (flags & ToontownGlobals.FriendChat): + flag = 1 + if flag: + scrubbed = 1 + newwords.append("\1WLDisplay\1" + word + "\2") + else: + newwords.append(word) + + newText = " ".join(newwords) + return newText, scrubbed + + + def replaceBadWords(self, text): + words = text.split(" ") + newwords = [] + for word in words: + if word == "": + newwords.append(word) + elif word[0] == "": + #newwords.append("Bleep") + newwords.append("\1WLRed\1" + self.chatGarbler.garbleSingle(self, word) + "\2") + elif base.whiteList.isWord(word): + newwords.append(word) + else: + newwords.append("\1WLRed\1" + word + "\2") + + newText = " ".join(newwords) + return newText + + def toonUp(self, hpGained, hasInteractivePropBonus = False): + # Adjusts the avatar's hp upward by the indicated value + # (limited by maxHp) and shows green numbers flying out of the + # avatar's head. + + if self.hp == None or hpGained < 0: + return + + oldHp = self.hp + + # If hp is below zero, it might mean we're at a timeout in the + # playground, in which case we respect that it is below zero + # until we get our head above water. If our toonUp would + # take us above zero, then we pretend we started at zero in + # the first place, ignoring the timeout. + 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, hasInteractivePropBonus=hasInteractivePropBonus) + self.hpChange(quietly = 0) + + def showHpText(self, number, bonus=0, scale=1, hasInteractivePropBonus = False): + if self.HpTextEnabled and not self.ghostMode: + # We don't show zero change. + if number != 0: + # Get rid of the number if it is already there. + if self.hpText: + self.hideHpText() + # Set the font + self.HpTextGenerator.setFont(OTPGlobals.getSignFont()) + # Show both negative and positive signs + if number < 0: + self.HpTextGenerator.setText(str(number)) + else: + hpGainedStr = "+" + str(number) + if hasInteractivePropBonus: + hpGainedStr += "\n" + TTLocalizer.InteractivePropTrackBonusTerms[0] + self.HpTextGenerator.setText(hpGainedStr) + # No shadow + self.HpTextGenerator.clearShadow() + # Put a shadow on there + #self.HpTextGenerator.setShadow(0.05, 0.05) + #self.HpTextGenerator.setShadowColor(0, 0, 0, 1) + # Center the number + self.HpTextGenerator.setAlign(TextNode.ACenter) + # Red for negative, green for positive, yellow for bonus + 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() + + # Put the hpText over the head of the avatar + self.hpText = self.attachNewNode(self.hpTextNode) + self.hpText.setScale(scale) + # Make sure it is a billboard + self.hpText.setBillboardPointEye() + # Render it after other things in the scene. + self.hpText.setBin('fixed', 100) + + # Initial position ... Center of the body... the "tan tien" + self.hpText.setPos(0, 0, self.height/2) + seq = Task.sequence( + # Fly the number out of the character + self.hpText.lerpPos(Point3(0, 0, self.height + 1.5), + 1.0, + blendType = 'easeOut'), + # Wait 2 seconds + Task.pause(0.85), + # Fade the number + self.hpText.lerpColor(Vec4(r, g, b, a), + Vec4(r, g, b, 0), + 0.1), + # Get rid of the number + Task(self.hideHpTextTask)) + taskMgr.add(seq, self.uniqueName("hpText")) + else: + # Just play the sound effect. + # TODO: Put in the sound effect! + pass + + def setName(self, name = "unknownDistributedAvatar"): + if GMUtils.testGMIdentity(name): + self.__handleGMName(name) + return + DistributedPlayer.DistributedPlayer.setName(self, name) + + def __handleGMName(self, name): + """ Parse the name for symbols that will get replaced by prefixes and icons """ + + gmName = GMUtils.handleGMName(name) + # self.setDisplayName(gmName) + DistributedPlayer.DistributedPlayer.setName(self, gmName) + self.setNametagStyle(5) + + # Now setup the icon + self.setGMIcon(GMUtils.getGMType(name)) + self.gmToon = True + + def setGMIcon(self, prefix=None): + + if hasattr(self, 'gmIcon') and self.gmIcon: # Probably has the party gm icon + return + if not prefix: + prefix = GMUtils.getGMType(self.getName()) + + if prefix == TTLocalizer.GM_1: + icons = loader.loadModel('phase_3.5/models/gui/tt_m_gui_trp_toontroop001') + self.gmIcon = icons.find("**/*whistleIcon*") + self.gmIcon.setScale(4) + elif prefix == TTLocalizer.GM_2: + icons = loader.loadModel('phase_3.5/models/gui/tt_m_gui_trp_toontroop001') + self.gmIcon = icons.find("**/*whistleIcon*") + self.gmIcon.setScale(4) + else: + # Shouldn't be a GM + return + #r = self.nametag.getNametag3d().getBounds().getRadius() + self.gmIcon.reparentTo(self.nametag.getNameIcon()) + + # Remove the star if we see it + self.setTrophyScore(self.trophyScore) + + # self.gmIcon.setPos(-(r+1), 0.0, 0.25) + self.gmIcon.setZ(-2.5) + self.gmIcon.setY(0.00) + self.gmIcon.setColor(Vec4(1.0,1.0,1.0, 1.0)) + self.gmIcon.setTransparency(1) + + self.gmIconInterval = LerpHprInterval(self.gmIcon, 3.0, Point3(0,0,0), Point3(-360,0,0)) + self.gmIconInterval.loop() + + def setGMPartyIcon(self): + + self.gmIcon = loader.loadModel('phase_3.5/models/gui/tt_m_gui_trp_toontroop001') + self.gmIcon.reparentTo(self.nametag.getNameIcon()) + self.gmIcon.setScale(3.25) + + # Remove the star if we see it + self.setTrophyScore(self.trophyScore) + + # self.gmIcon.setPos(-(r+1), 0.0, 0.25) + self.gmIcon.setZ(1.0) + self.gmIcon.setY(0.00) + self.gmIcon.setColor(Vec4(1.0,1.0,1.0, 1.0)) + self.gmIcon.setTransparency(1) + + self.gmIconInterval = LerpHprInterval(self.gmIcon, 3.0, Point3(0,0,0), Point3(-360,0,0)) + self.gmIconInterval.loop() + + def removeGMIcon(self): + # Stop the gm spin task + if hasattr(self, 'gmIconInterval') and self.gmIconInterval: + self.gmIconInterval.finish() + del self.gmIconInterval + + if hasattr(self, 'gmIcon') and self.gmIcon: + self.gmIcon.detachNode() + del self.gmIcon diff --git a/toontown/src/toon/DistributedToonAI.py b/toontown/src/toon/DistributedToonAI.py new file mode 100644 index 0000000..4573e6c --- /dev/null +++ b/toontown/src/toon/DistributedToonAI.py @@ -0,0 +1,4907 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from direct.directnotify import DirectNotifyGlobal +import ToonDNA +from toontown.suit import SuitDNA +import InventoryBase +import Experience +from otp.avatar import DistributedAvatarAI +from otp.avatar import DistributedPlayerAI +from direct.distributed import DistributedSmoothNodeAI +from toontown.toonbase import ToontownGlobals +from toontown.quest import QuestRewardCounter +from toontown.quest import Quests +from toontown.toonbase import ToontownBattleGlobals +from toontown.battle import SuitBattleGlobals +from direct.task import Task +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem +from direct.showbase import PythonUtil +from direct.distributed.ClockDelta import * +from toontown.toonbase.ToontownGlobals import * +import types +from toontown.fishing import FishGlobals +from toontown.fishing import FishCollection +from toontown.fishing import FishTank +from NPCToons import npcFriends,isZoneProtected +from toontown.coghq import CogDisguiseGlobals +import random +from toontown.chat import ResistanceChat +from toontown.racing import RaceGlobals +from toontown.hood import ZoneUtil +from toontown.toon import NPCToons + +from toontown.estate import FlowerCollection +from toontown.estate import FlowerBasket +from toontown.estate import GardenGlobals +from toontown.golf import GolfGlobals + +from toontown.parties import PartyGlobals +from toontown.parties.PartyInfo import PartyInfoAI +from toontown.parties.InviteInfo import InviteInfoBase +from toontown.parties.PartyReplyInfo import PartyReplyInfoBase +from toontown.parties.PartyGlobals import InviteStatus + +if simbase.wantPets: + from toontown.pets import PetLookerAI, PetObserve +else: + class PetLookerAI: + class PetLookerAI: + pass + +if( simbase.wantKarts ): + from toontown.racing.KartDNA import * + +class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, + DistributedSmoothNodeAI.DistributedSmoothNodeAI, + PetLookerAI.PetLookerAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedToonAI") + + maxCallsPerNPC = 100 + + # factory type -> cog suit parts + partTypeIds = { + ToontownGlobals.FT_FullSuit: (CogDisguiseGlobals.leftLegIndex, + CogDisguiseGlobals.rightLegIndex, + CogDisguiseGlobals.torsoIndex, + CogDisguiseGlobals.leftArmIndex, + CogDisguiseGlobals.rightArmIndex,), + ToontownGlobals.FT_Leg: (CogDisguiseGlobals.leftLegIndex, + CogDisguiseGlobals.rightLegIndex,), + ToontownGlobals.FT_Arm: (CogDisguiseGlobals.leftArmIndex, + CogDisguiseGlobals.rightArmIndex,), + ToontownGlobals.FT_Torso: (CogDisguiseGlobals.torsoIndex,), + } + + def __init__(self, air): + #if hasattr(simbase, 'trackDistributedToonAI'): + # import pdb; pdb.set_trace() + + DistributedPlayerAI.DistributedPlayerAI.__init__(self, air) + DistributedSmoothNodeAI.DistributedSmoothNodeAI.__init__(self, air) + if simbase.wantPets: + PetLookerAI.PetLookerAI.__init__(self) + # Record the repository + self.air = air + # Initialize our empty DNA. + self.dna = ToonDNA.ToonDNA() + self.inventory = None + self.fishCollection = None + self.fishTank = None + self.experience = None + self.quests = [] + self.cogs = [] + self.cogCounts = [] + + self.NPCFriendsDict = {} + + self.clothesTopsList = [] + self.clothesBottomsList = [] + + # initialize these to lists of zeroes in case there is no + # field in the database yet for old toons created before this + # field existed. + self.cogTypes = [0, 0, 0, 0] + self.cogLevel = [0, 0, 0, 0] + self.cogParts = [0, 0, 0, 0] + self.cogRadar = [0, 0, 0, 0] + self.cogIndex = -1 + self.disguisePageFlag = 0 + self.buildingRadar = [0, 0, 0, 0] + self.fishingRod = 0 + self.fishingTrophies = [] + self.trackArray = [] + self.emoteAccess = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + self.maxBankMoney = 1000 + self.gardenSpecials = []#[(0,2), (1,2), (2,2), (3,2)] + + self.houseId = 0 + self.posIndex = 0 + + self.savedCheesyEffect = ToontownGlobals.CENormal + self.savedCheesyHoodId = 0 + self.savedCheesyExpireTime = 0 + self.ghostMode = 0 + self.immortalMode = 0 + self.numPies = 0 + self.pieType = 0 + + # Most of the time, this is false. But during a battle round, + # we set this true, to tell the toon to temporarily accumulate + # toonups beyond full health (and not to broadcast current hp + # to the client), so that it can be fixed up after the battle + # round is over. + self.hpOwnedByBattle = 0 + + if simbase.wantPets: + self.petTrickPhrases = [] + + if simbase.wantBingo: + self.bingoCheat = False + + self.customMessages = [] + self.catalogNotify = ToontownGlobals.NoItems + self.mailboxNotify = ToontownGlobals.NoItems + self.catalogScheduleCurrentWeek = 0 + self.catalogScheduleNextTime = 0 + self.monthlyCatalog = CatalogItemList.CatalogItemList() + self.weeklyCatalog = CatalogItemList.CatalogItemList() + self.backCatalog = CatalogItemList.CatalogItemList() + self.onOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + #self.onGiftOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + self.onGiftOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + self.mailboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization) + self.awardMailboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization) + self.onAwardOrder = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + #self.deliveryboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.GiftTag) + + self.kart = None + + # Initialize the Kart stuff + if( simbase.wantKarts ): + self.kartDNA = [ -1 ] * ( getNumFields() ) + self.tickets = 200 + self.allowSoloRace = False + self.allowRaceTimeout = True + + #battle stuff, first used for boarding parties + self.setBattleId(0) + + + #Gardening stuff + self.gardenStarted = False + self.flowerCollection = None + self.shovel = 0 + self.shovelSkill = 0 + self.wateringCan = 0 + self.wateringCanSkill = 0 + + self.hatePets = 1 + + # Golf Stuff + self.golfHistory = None + self.golfHoleBest = None + self.golfCourseBest = None + self.unlimitedSwing = False + + self.previousAccess = None + + # mail stuff + self.numMailItems = 0 + self.simpleMailNotify = ToontownGlobals.NoItems + self.inviteMailNotify = ToontownGlobals.NoItems + + # parties + self.invites = [] + self.hostedParties = [] + self.partiesInvitedTo = [] + self.partyReplyInfoBases = [] + + #def __del__(self): + #if hasattr(simbase, 'trackDistributedToonAI'): + # self.notify.info('---- __del__ DistributedToonAI %d ' % self.doId) + # import pdb; pdb.set_trace() + #pass + + + def generate(self): + # super spammy hack to track down ai crash + # self.notify.info('Got generate for %d' % self.doId) + # self.air.writeServerEvent('generate' , self.doId, '') + DistributedPlayerAI.DistributedPlayerAI.generate(self) + DistributedSmoothNodeAI.DistributedSmoothNodeAI.generate(self) + + + def announceGenerate(self): + # super spammy hack to track down ai crash + # self.notify.info('Got announceGenerate for %d' % self.doId) + # self.air.writeServerEvent('announceGenerate' , self.doId, '') + DistributedPlayerAI.DistributedPlayerAI.announceGenerate(self) + DistributedSmoothNodeAI.DistributedSmoothNodeAI.announceGenerate(self) + if self.isPlayerControlled(): + messenger.send('avatarEntered', [self]) + + ### Field definitions + + def sendDeleteEvent(self): + if simbase.wantPets: + # before we let the rest of the system know we're about to be + # deleted, see if we need to notify any pets. The act of sending + # the event may destroy data that we need in order to notify the + # pets. + # Turns out that the EstateMgrAI gets an event from the client + # telling it that the player has left the estate; on an alt+F4, + # that comes in before this is called, so we've already left the + # estate at this point. We still need the 'wasInEstate' mechanism. + isInEstate = self.isInEstate() + wasInEstate = self.wasInEstate() + if isInEstate or wasInEstate: + # announce to the pets that we're logging out + PetObserve.send(self.estateZones, PetObserve.PetActionObserve( + PetObserve.Actions.LOGOUT, self.doId)) + if wasInEstate: + self.cleanupEstateData() + + DistributedAvatarAI.DistributedAvatarAI.sendDeleteEvent(self) + + + + + def delete(self): + self.notify.debug('----Deleting DistributedToonAI %d ' % self.doId) + if self.isPlayerControlled(): + messenger.send('avatarExited', [self]) + if simbase.wantPets: + if self.isInEstate(): + print "ToonAI - Exit estate toonId:%s" % (self.doId) + self.exitEstate() + if self.zoneId != ToontownGlobals.QuietZone: + # simulate a zone change for the benefit of the pets + self.announceZoneChange(ToontownGlobals.QuietZone, + self.zoneId) + + # Stop the cheesy effect timer if we're waiting. + taskName = self.uniqueName('cheesy-expires') + taskMgr.remove(taskName) + # Stop the catalog timer too. + taskName = self.uniqueName('next-catalog') + taskMgr.remove(taskName) + taskName = self.uniqueName('next-delivery') + taskMgr.remove(taskName) + taskName = self.uniqueName('next-award-delivery') + taskMgr.remove(taskName) + taskName = ("next-bothDelivery-%s" % (self.doId)) + taskMgr.remove(taskName) + self.stopToonUp() + + del self.dna + if self.inventory: + self.inventory.unload() + del self.inventory + del self.experience + if simbase.wantPets: + PetLookerAI.PetLookerAI.destroy(self) + del self.kart + + self._sendExitServerEvent() + + DistributedSmoothNodeAI.DistributedSmoothNodeAI.delete(self) + DistributedPlayerAI.DistributedPlayerAI.delete(self) + + def deleteDummy(self): + """ + we create a dummy DistributedToonAI when we close the window in a building battle + So we need to clear it properly + """ + self.notify.debug('----deleteDummy DistributedToonAI %d ' % self.doId) + if self.inventory: + self.inventory.unload() + del self.inventory + + # Stop the catalog timer too. #we get this case when we open a somebody else's closet + taskName = self.uniqueName('next-catalog') + taskMgr.remove(taskName) + + def patchDelete(self): + # called by the patcher to prevent memory leaks + del self.dna + if self.inventory: + self.inventory.unload() + del self.inventory + del self.experience + if simbase.wantPets: + PetLookerAI.PetLookerAI.destroy(self) + # prevent a crash; we do not own our doId and do not have a zoneId + self.doNotDeallocateChannel = True + self.zoneId = None + DistributedSmoothNodeAI.DistributedSmoothNodeAI.delete(self) + DistributedPlayerAI.DistributedPlayerAI.delete(self) + + def handleLogicalZoneChange(self, newZoneId, oldZoneId): + DistributedAvatarAI.DistributedAvatarAI.handleLogicalZoneChange( + self, newZoneId, oldZoneId) + + # make sure ghost mode is disabled on zone change (fixes furniture arranger exploit) + self.b_setGhostMode(0) + + # not quite sure where to do this - we need to assign teleport access + # to the toon when he enters Goofy Stadium + zoneId = ZoneUtil.getCanonicalZoneId(newZoneId) + if zoneId == ToontownGlobals.GoofySpeedway: + if not self.hasTeleportAccess(ToontownGlobals.GoofySpeedway): + self.addTeleportAccess(zoneId) + # NOTE: If others need to listen for zoneId changes then please remove the if statements + elif zoneId == ToontownGlobals.ToonHall: + messenger.send("ToonEnteredZone", [self.doId, zoneId]) + zoneId = ZoneUtil.getCanonicalZoneId(oldZoneId) + if zoneId == ToontownGlobals.ToonHall: + messenger.send("ToonLeftZone", [self.doId, zoneId]) + if simbase.wantPets: + isInEstate = self.isInEstate() + # we may have just left + wasInEstate = self.wasInEstate() + if isInEstate or wasInEstate: + self.announceZoneChange(newZoneId, oldZoneId) + if wasInEstate: + # don't need this data anymore + self.cleanupEstateData() + + def announceZoneChange(self, newZoneId, oldZoneId): + # let the pets know about the zone change + from toontown.pets import PetObserve + + self.air.welcomeValleyManager.toonSetZone(self.doId, newZoneId) + + # if we're in an estate, make sure to broadcast this message + # to all estate zones + broadcastZones = [oldZoneId, newZoneId] + if self.isInEstate() or self.wasInEstate(): + broadcastZones = union(broadcastZones, self.estateZones) + + PetObserve.send(broadcastZones, + PetObserve.PetActionObserve( + PetObserve.Actions.CHANGE_ZONE, self.doId, + (oldZoneId, newZoneId))) + + def b_setDNAString(self, string): + self.d_setDNAString(string) + self.setDNAString(string) + + def d_setDNAString(self, string): + self.sendUpdate('setDNAString', [string]) + + def setDNAString(self, string): + self.dna.makeFromNetString(string) + + def getDNAString( self ): + """ + Function: retrieve the dna information from this suit, called + whenever a client needs to create this suit + Returns: netString representation of this suit's dna + """ + return self.dna.makeNetString() + + def getStyle(self): + # Returns the dna. This mimicks a similar function on Avatar.py. + return self.dna + + def b_setExperience(self, experience): + self.d_setExperience(experience) + self.setExperience(experience) + + def d_setExperience(self, experience): + self.sendUpdate('setExperience', [experience]) + + def setExperience(self, experience): + self.experience = Experience.Experience(experience, self) + + def getExperience(self): + # This returns the experience formatted for the net, not + # directly usable. + return self.experience.makeNetString() + + def b_setInventory(self, inventory): + self.setInventory(inventory) + self.d_setInventory(self.getInventory()) + + def d_setInventory(self, inventory): + self.sendUpdate('setInventory', [inventory]) + + def setInventory(self, inventoryNetString): + if self.inventory: + # Update the inventory + self.inventory.updateInvString(inventoryNetString) + else: + self.inventory = InventoryBase.InventoryBase(self, + inventoryNetString) + + #here we look to see if new gags have been added + + emptyInv = InventoryBase.InventoryBase(self) + emptyString = emptyInv.makeNetString() + lengthMatch = len(inventoryNetString) - len(emptyString) + if lengthMatch != 0: + #Moving from 7 tracks and 6 levels to 7 tracks and 7 levels + if len(inventoryNetString) == 42: + oldTracks = 7 + oldLevels = 6 + elif len(inventoryNetString) == 49: + oldTracks = 7 + oldLevels = 7 + else: + #flags for when the solution is unknown + oldTracks = 0 + oldLevels = 0 + + if oldTracks == 0 and oldLevels == 0: + #if no handcoded solution exists we reset the toon's inventory and give them + #a restock, only including gags you can buy in the shop + #import pdb; pdb.set_trace() + #print(lengthMatch) + #print(self.inventory) + self.notify.warning("reseting invalid inventory to MAX on toon: %s" % (self.doId)) + self.inventory.zeroInv() + self.inventory.maxOutInv(1) + #print(self.inventory) + else: + #handles the conversion for known solutions + newInventory = InventoryBase.InventoryBase(self) + oldList = emptyInv.makeFromNetStringForceSize(inventoryNetString, oldTracks, oldLevels) + #print("inventory %s" % (self.inventory)) + #print("oldList %s" % (oldList)) + for indexTrack in range(0,oldTracks): + for indexGag in range(0,oldLevels): + newInventory.addItems(indexTrack, indexGag, oldList[indexTrack][indexGag]) + #print("new inventory %s" % (newInventory)) + self.inventory.unload() + self.inventory = newInventory + #import pdb; pdb.set_trace() + self.d_setInventory(self.getInventory()) + + + + + def getInventory(self): + # This returns the inventory formatted for the net, not + # directly usable. + return self.inventory.makeNetString() + + def doRestock(self, noUber = 1): + self.inventory.zeroInv() + self.inventory.maxOutInv(noUber) + self.d_setInventory(self.inventory.makeNetString()) + + def setDefaultShard(self, shard): + self.defaultShard = shard + self.notify.debug("setting default shard to %s" % shard) + + def getDefaultShard(self): + return self.defaultShard + + def setDefaultZone(self, zone): + self.defaultZone = zone + self.notify.debug("setting default zone to %s" % zone) + + def getDefaultZone(self): + return self.defaultZone + + def setShtickerBook(self, string): + self.notify.debug("setting shticker book to %s" % string) + + def getShtickerBook(self): + return "" + + def d_setFriendsList(self, friendsList): + self.sendUpdate("setFriendsList", [friendsList]) + return None + + def setFriendsList(self, friendsList): + self.notify.debug("setting friends list to %s" % self.friendsList) + self.friendsList = friendsList + # If the friendsList is nonEmpty, notify the quest manager + if friendsList: + # Assume the newest one on the list is the one we just + # made friends with. Is it? Check with Roger. + friendId = friendsList[-1] + # See if the otherAv is logged in + otherAv = self.air.doId2do.get(friendId) + # Tell the quest manager. Note: there is a design flaw here + # whereby the player could remove a friend and still get credit + # for this a friend quest. Really we need to know when a friend + # is added, not simply when the friends list changed (add or + # remove) + self.air.questManager.toonMadeFriend(self, otherAv) + + def getFriendsList(self): + return self.friendsList + + def extendFriendsList(self, friendId, friendCode): + # This is called only by the friend manager when a new friend + # transaction is successfully completed. Its purpose is + # simply to update the AI's own copy of the avatar's friends + # list, mainly so that the quest manager can reliably know + # if the avatar has any friends. + + # First, see if we already had this friend. + for i in range(len(self.friendsList)): + friendPair = self.friendsList[i] + if friendPair[0] == friendId: + # We did. Update the code. + self.friendsList[i] = (friendId, friendCode) + return + + # We didn't already have this friend; tack it on. + self.friendsList.append((friendId, friendCode)) + + # Note that if an avatar *breaks* a friendship, the AI never + # hears about it. So our friends list will not be 100% + # up-to-date, but it will at least be good enough for the + # quest manager. + + def d_setMaxNPCFriends(self, max): + self.sendUpdate("setMaxNPCFriends", [self.maxNPCFriends]) + + def setMaxNPCFriends(self, max): + self.maxNPCFriends = max + + def b_setMaxNPCFriends(self, max): + self.setMaxNPCFriends(max) + self.d_setMaxNPCFriends(max) + + def getMaxNPCFriends(self): + return self.maxNPCFriends + + def getBattleId(self): + if self.battleId >= 0: + return self.battleId + else: + return 0 + + def b_setBattleId(self, battleId): + self.setBattleId(battleId) + self.d_setBattleId(battleId) + + def d_setBattleId(self, battleId): + if self.battleId >= 0: + self.sendUpdate("setBattleId", [battleId]) + else: + self.sendUpdate("setBattleId", [0]) + + + def setBattleId(self, battleId): + self.battleId = battleId + + def d_setNPCFriendsDict(self, NPCFriendsDict): + NPCFriendsList = [] + for friend in NPCFriendsDict.keys(): + NPCFriendsList.append((friend, NPCFriendsDict[friend])) + self.sendUpdate("setNPCFriendsDict", [NPCFriendsList]) + return None + + def setNPCFriendsDict(self, NPCFriendsList): + self.NPCFriendsDict = {} + for friendPair in NPCFriendsList: + self.NPCFriendsDict[friendPair[0]] = friendPair[1] + self.notify.debug("setting NPC friends dict to %s" % self.NPCFriendsDict) + def getNPCFriendsDict(self): + return self.NPCFriendsDict + + def b_setNPCFriendsDict(self, NPCFriendsList): + self.setNPCFriendsDict(NPCFriendsList) + self.d_setNPCFriendsDict(self.NPCFriendsDict) + + def resetNPCFriendsDict(self): + self.b_setNPCFriendsDict([]) + + def attemptAddNPCFriend(self, npcFriend, numCalls = 1): + self.notify.info('%s.attemptAddNPCFriend(%s, %s)' % (self.doId, npcFriend, numCalls)) + if (numCalls <= 0): + self.notify.warning("invalid numCalls: %d" % numCalls) + return 0 + if (self.NPCFriendsDict.has_key(npcFriend)): + self.NPCFriendsDict[npcFriend] += numCalls + elif (npcFriends.has_key(npcFriend)): + if (len(self.NPCFriendsDict.keys()) >= self.maxNPCFriends): + return 0 + self.NPCFriendsDict[npcFriend] = numCalls + else: + self.notify.warning("invalid NPC: %d" % npcFriend) + return 0 + # Make sure the number of calls is capped at the max + if (self.NPCFriendsDict[npcFriend] > self.maxCallsPerNPC): + self.NPCFriendsDict[npcFriend] = self.maxCallsPerNPC + self.d_setNPCFriendsDict(self.NPCFriendsDict) + return 1 + + def d_setMaxClothes(self, max): + self.sendUpdate("setMaxClothes", [self.maxClothes]) + + def setMaxClothes(self, max): + self.maxClothes = max + + def b_setMaxClothes(self, max): + self.setMaxClothes(max) + self.d_setMaxClothes(max) + + def getMaxClothes(self): + return self.maxClothes + + def isClosetFull(self, extraClothes = 0): + numClothes = len(self.clothesTopsList)/4 + len(self.clothesBottomsList)/2 + return (numClothes + extraClothes >= self.maxClothes) + + def d_setClothesTopsList(self, clothesList): + self.sendUpdate("setClothesTopsList", [clothesList]) + return None + + def setClothesTopsList(self, clothesList): + self.clothesTopsList = clothesList + + def b_setClothesTopsList(self, clothesList): + self.setClothesTopsList(clothesList) + self.d_setClothesTopsList(clothesList) + + def getClothesTopsList(self): + return self.clothesTopsList + + # add clothes to list if there is room + def addToClothesTopsList(self, topTex, topTexColor, + sleeveTex, sleeveTexColor): + # See if there's any room for another top in the clothes list + if self.isClosetFull(): + return 0 + + # See if this top is already there + index = 0 + for i in range(0, len(self.clothesTopsList), 4): + if (self.clothesTopsList[i] == topTex and + self.clothesTopsList[i+1] == topTexColor and + self.clothesTopsList[i+2] == sleeveTex and + self.clothesTopsList[i+3] == sleeveTexColor): + return 0 + # Add the new top + self.clothesTopsList.append(topTex) + self.clothesTopsList.append(topTexColor) + self.clothesTopsList.append(sleeveTex) + self.clothesTopsList.append(sleeveTexColor) + return 1 + + # replace item A with item B + def replaceItemInClothesTopsList(self, topTexA, topTexColorA, + sleeveTexA, sleeveTexColorA, + topTexB, topTexColorB, + sleeveTexB, sleeveTexColorB): + + # Find first occurence of top A + index = 0 + for i in range(0, len(self.clothesTopsList), 4): + if (self.clothesTopsList[i] == topTexA and + self.clothesTopsList[i+1] == topTexColorA and + self.clothesTopsList[i+2] == sleeveTexA and + self.clothesTopsList[i+3] == sleeveTexColorA): + # replace with top B + self.clothesTopsList[i] = topTexB + self.clothesTopsList[i+1] = topTexColorB + self.clothesTopsList[i+2] = sleeveTexB + self.clothesTopsList[i+3] = sleeveTexColorB + return 1 + return 0 + + def removeItemInClothesTopsList(self, topTex, topTexColor, + sleeveTex, sleeveTexColor): + # assume the client has already handled the boundary checking + # but just for sanity, we'll check the length + listLen = len(self.clothesTopsList) + if listLen < 4: + self.notify.warning("Clothes top list is not long enough to delete anything") + return 0 + + # Find first occurence of top + index = 0 + for i in range(0, listLen, 4): + if (self.clothesTopsList[i] == topTex and + self.clothesTopsList[i+1] == topTexColor and + self.clothesTopsList[i+2] == sleeveTex and + self.clothesTopsList[i+3] == sleeveTexColor): + # remove these four elements + self.clothesTopsList = self.clothesTopsList[0:i] + self.clothesTopsList[i+4:listLen] + return 1 + return 0 + + def d_setClothesBottomsList(self, clothesList): + self.sendUpdate("setClothesBottomsList", [clothesList]) + return None + + def setClothesBottomsList(self, clothesList): + self.clothesBottomsList = clothesList + + def b_setClothesBottomsList(self, clothesList): + self.setClothesBottomsList(clothesList) + self.d_setClothesBottomsList(clothesList) + + def getClothesBottomsList(self): + return self.clothesBottomsList + + def addToClothesBottomsList(self, botTex, botTexColor): + # See if there's any room for another bottom in the clothes list + if self.isClosetFull(): + self.notify.warning("clothes bottoms list is full") + return 0 + # See if this bottom is already there + index = 0 + for i in range(0, len(self.clothesBottomsList), 2): + if (self.clothesBottomsList[i] == botTex and + self.clothesBottomsList[i+1] == botTexColor): + return 0 + # Add the new bottom + self.clothesBottomsList.append(botTex) + self.clothesBottomsList.append(botTexColor) + return 1 + + # replace item A with item B + def replaceItemInClothesBottomsList(self, botTexA, botTexColorA, + botTexB, botTexColorB): + + # Find first occurence of bottom A + index = 0 + for i in range(0, len(self.clothesBottomsList), 2): + if (self.clothesBottomsList[i] == botTexA and + self.clothesBottomsList[i+1] == botTexColorA): + # replace with bottom B + self.clothesBottomsList[i] = botTexB + self.clothesBottomsList[i+1] = botTexColorB + return 1 + return 0 + + def removeItemInClothesBottomsList(self, botTex, botTexColor): + # assume the client has already handled the boundary checking + # but just for sanity, we'll check the length + listLen = len(self.clothesBottomsList) + if listLen < 2: + self.notify.warning("Clothes bottoms list is not long enough to delete anything") + return 0 + + # Find first occurence of bottom A + index = 0 + for i in range(0, len(self.clothesBottomsList), 2): + if (self.clothesBottomsList[i] == botTex and + self.clothesBottomsList[i+1] == botTexColor): + # remove these two elements + self.clothesBottomsList = self.clothesBottomsList[0:i] + self.clothesBottomsList[i+2:listLen] + return 1 + return 0 + + def d_catalogGenClothes(self): + self.sendUpdate('catalogGenClothes', [self.doId]) + + def takeDamage(self, hpLost, quietly = 0, sendTotal = 1): + # Adds the indicated hit points to the avatar's total. If + # quietly is 0 (the default), numbers will fly out of his + # head; if sendTotal is 1 (the default), the resulting hp + # value will be sent as well to ensure client and AI are in + # agreement. (Without sendTotal, the client will do the + # arithmetic himself, and presumably will still arrive at the + # same value.) + + if not self.immortalMode: + # First, send the message to make the numbers fly out. + if not quietly: + self.sendUpdate('takeDamage', [hpLost]) + + # Then, we recompute the HP. + + if hpLost > 0 and self.hp > 0: + self.hp -= hpLost + if self.hp <= 0: + # If you get killed, set your HP to -1 so you have + # a timeout in the safezone. + self.hp = -1 + + if not self.hpOwnedByBattle: + # We still need to check maxHp even in takeDamage(), since + # we might have had self.hpOwnedByBattle set previously, + # allowing the toon to go above maxHp for a time. + self.hp = min(self.hp, self.maxHp) + + # Finally, send the new total to the client so he's with us. + if sendTotal: + self.d_setHp(self.hp) + + @staticmethod + def getGoneSadMessageForAvId(avId): + return 'goneSad-%s' % avId + + def getGoneSadMessage(self): + return self.getGoneSadMessageForAvId(self.doId) + + def setHp(self, hp): + DistributedPlayerAI.DistributedPlayerAI.setHp(self, hp) + if hp <= 0: + messenger.send(self.getGoneSadMessage()) + + def b_setTutorialAck(self, tutorialAck): + self.d_setTutorialAck(tutorialAck) + self.setTutorialAck(tutorialAck) + + def d_setTutorialAck(self, tutorialAck): + self.sendUpdate('setTutorialAck', [tutorialAck]) + + def setTutorialAck(self, tutorialAck): + self.tutorialAck = tutorialAck + + def getTutorialAck(self): + return self.tutorialAck + + def d_setEarnedExperience(self, earnedExp): + self.sendUpdate('setEarnedExperience', [earnedExp]) + + def setInterface(self, string): + self.notify.debug("setting interface to %s" % string) + + def getInterface(self): + return "" + + def setZonesVisited(self, hoods): + self.safeZonesVisited = hoods + self.notify.debug("setting safe zone list to %s" % + self.safeZonesVisited) + + def getZonesVisited(self): + return self.safeZonesVisited + + def setHoodsVisited(self, hoods): + self.hoodsVisited = hoods + self.notify.debug("setting hood zone list to %s" % + self.hoodsVisited) + + def getHoodsVisited(self): + return self.hoodsVisited + + def setLastHood(self, hood): + self.lastHood = hood + + def getLastHood(self): + return self.lastHood + + + def b_setAnimState(self, animName, animMultiplier): + self.setAnimState(animName, animMultiplier) + self.d_setAnimState(animName, animMultiplier) + + def d_setAnimState(self, animName, animMultiplier): + timestamp = globalClockDelta.getRealNetworkTime() + self.sendUpdate("setAnimState", [animName, animMultiplier, timestamp]) + return None + + def setAnimState(self, animName, animMultiplier, timestamp=0): + self.animName = animName + self.animMultiplier = animMultiplier + + ### status of cogs for cog page ### + + def b_setCogStatus(self, cogStatusList): + # update the cog status list + self.setCogStatus(cogStatusList) + self.d_setCogStatus(cogStatusList) + + def setCogStatus(self, cogStatusList): + self.notify.debug("setting cogs to %s" % cogStatusList) + self.cogs = cogStatusList + + def d_setCogStatus(self, cogStatusList): + self.sendUpdate("setCogStatus", [cogStatusList]) + + def getCogStatus(self): + return self.cogs + + ### count of cog summons available + + ### count of cogs defeated for cog page ### + + def b_setCogCount(self, cogCountList): + # update the cog count list + self.setCogCount(cogCountList) + self.d_setCogCount(cogCountList) + + def setCogCount(self, cogCountList): + self.notify.debug("setting cogCounts to %s" % cogCountList) + self.cogCounts = cogCountList + + def d_setCogCount(self, cogCountList): + self.sendUpdate("setCogCount", [cogCountList]) + + def getCogCount(self): + return self.cogCounts + + + ### set cog radar ### + + def b_setCogRadar(self, radar): + self.setCogRadar(radar) + self.d_setCogRadar(radar) + + def setCogRadar(self, radar): + if not radar: + self.notify.warning("cogRadar set to bad value: %s. Resetting to [0,0,0,0]" % radar) + self.cogRadar = [0,0,0,0] + else: + self.cogRadar = radar + + def d_setCogRadar(self, radar): + self.sendUpdate("setCogRadar", [radar]) + + def getCogRadar(self): + return self.cogRadar + + ### set building radar ### + + def b_setBuildingRadar(self, radar): + self.setBuildingRadar(radar) + self.d_setBuildingRadar(radar) + + def setBuildingRadar(self, radar): + if not radar: + self.notify.warning("buildingRadar set to bad value: %s. Resetting to [0,0,0,0]" % radar) + self.buildingRadar = [0,0,0,0] + else: + self.buildingRadar = radar + + def d_setBuildingRadar(self, radar): + self.sendUpdate("setBuildingRadar", [radar]) + + def getBuildingRadar(self): + return self.buildingRadar + + ### set cog types ### + + # Cog types indicate which type of cog we are acquiring disguise parts for. + # There is one entry for each type of cog (corp, legal, money, sales). + # Each number represents an index into the SuitDNA suitHeadTypes array. + + def b_setCogTypes(self, types): + self.setCogTypes(types) + self.d_setCogTypes(types) + + def setCogTypes(self, types): + if not types: + self.notify.warning("cogTypes set to bad value: %s. Resetting to [0,0,0,0]" % types) + self.cogTypes = [0,0,0,0] + else: + self.cogTypes = types + + def d_setCogTypes(self, types): + self.sendUpdate("setCogTypes", [types]) + + def getCogTypes(self): + return self.cogTypes + + ### set cog levels ### + + # Cog levels indicate which level of cog we are acquiring disguise parts for. + # There is one entry for each type of cog (corp, legal, money, sales). + + def b_setCogLevels(self, levels): + self.setCogLevels(levels) + self.d_setCogLevels(levels) + + def setCogLevels(self, levels): + if not levels: + self.notify.warning("cogLevels set to bad value: %s. Resetting to [0,0,0,0]" % levels) + self.cogLevels = [0,0,0,0] + else: + self.cogLevels = levels + + def d_setCogLevels(self, levels): + self.sendUpdate("setCogLevels", [levels]) + + def getCogLevels(self): + return self.cogLevels + + def incCogLevel(self, dept): + # Increment cog level for this cogType. If new level does not exist + # for this cogType then increment cogType and set to base level (if we + # are not on the final cogType!) + newLevel = self.cogLevels[dept] + 1 + cogTypeStr = SuitDNA.suitHeadTypes[self.cogTypes[dept]] + lastCog = (self.cogTypes[dept] >= (SuitDNA.suitsPerDept - 1)) + if not lastCog: + maxLevel = SuitBattleGlobals.SuitAttributes[cogTypeStr]['level'] + 4 + else: + maxLevel = ToontownGlobals.MaxCogSuitLevel + # if this is the last level for this cog type + if newLevel > maxLevel: + # if not last cog in dept + if not lastCog: + # increment cog type and reset level + self.cogTypes[dept] += 1 + self.d_setCogTypes(self.cogTypes) + cogTypeStr = SuitDNA.suitHeadTypes[self.cogTypes[dept]] + self.cogLevels[dept] = SuitBattleGlobals.SuitAttributes[cogTypeStr]['level'] + self.d_setCogLevels(self.cogLevels) + # else not the last level for this type of cog + else: + # just increment the level + self.cogLevels[dept] += 1 + self.d_setCogLevels(self.cogLevels) + # give out HP bonuses + if lastCog: + if self.cogLevels[dept] in ToontownGlobals.CogSuitHPLevels: + maxHp = self.getMaxHp() + # Add the amount, but make sure it is not over the + # global max + maxHp = min(ToontownGlobals.MaxHpLimit, maxHp + 1) + self.b_setMaxHp(maxHp) + # Also, give them a full heal + self.toonUp(maxHp) + + self.air.writeServerEvent('cogSuit', self.doId, "%s|%s|%s" % ( + dept, self.cogTypes[dept], self.cogLevels[dept])) + + + def getNumPromotions(self, dept): + """ + Returns how many times this toon has been promoted in the given dept. + dept should be 'c', 'l', 'm' or 's' + New toons will return zero. There is a cap on the return value. See inc + """ + if not dept in SuitDNA.suitDepts: + self.notify.warning('getNumPromotions: Invalid parameter dept=%s' % dept) + return 0 + + deptIndex = SuitDNA.suitDepts.index(dept) + cogType = self.cogTypes[deptIndex] + cogTypeStr = SuitDNA.suitHeadTypes[cogType] + lowestCogLevel = SuitBattleGlobals.SuitAttributes[cogTypeStr]['level'] + + #5 levels per cog Type (determined from visual inspection of SuitBattleGlobals) + multiple = 5 * cogType + additional = self.cogLevels[deptIndex] - lowestCogLevel + numPromotions = multiple + additional + return numPromotions + + + + + + ### set cog parts ### + + # Cog parts indicate what parts of a cog disguise have been acquired. + # There is one array entry for each cog type (corp, legal, money, sales). + # + # Each cog part number is a binary representation of the which parts the + # toon has acquired. See CogDisguiseGlobals.py in coghq for details. + + def b_setCogParts(self, parts): + self.setCogParts(parts) + self.d_setCogParts(parts) + + def setCogParts(self, parts): + if not parts: + self.notify.warning("cogParts set to bad value: %s. Resetting to [0,0,0,0]" % parts) + self.cogParts = [0,0,0,0] + else: + self.cogParts = parts + + def d_setCogParts(self, parts): + self.sendUpdate("setCogParts", [parts]) + + def getCogParts(self): + return self.cogParts + + def giveCogPart(self, part, dept): + """ + Add the given part to part list for the appropriate department. + """ + dept = CogDisguiseGlobals.dept2deptIndex(dept) + parts = self.getCogParts() + parts[dept] = parts[dept] | part + self.b_setCogParts(parts) + + def hasCogPart(self, part, dept): + """ + Return 1 if the toon has the cog part, 0 otherwise. + """ + dept = CogDisguiseGlobals.dept2deptIndex(dept) + if (self.cogParts[dept] & part): + return 1 + else: + return 0 + + def giveGenericCogPart(self, factoryType, dept): + """ + Add the next part awarded by factories of the indicated type. + Return the part added. + """ + for partTypeId in self.partTypeIds[factoryType]: + nextPart = CogDisguiseGlobals.getNextPart( + self.getCogParts(), partTypeId, dept) + if nextPart: + break + if nextPart: + self.giveCogPart(nextPart, dept) + return nextPart + else: + return None + + def takeCogPart(self, part, dept): + """ + Remove the given part from part list for the appropriate department. + NOTE: we no longer support damaged parts + """ + dept = CogDisguiseGlobals.dept2deptIndex(dept) + parts = self.getCogParts() + # if we have the part + if parts[dept] & part: + # remove it + parts[dept] = parts[dept] ^ part + self.b_setCogParts(parts) + + def loseCogParts(self, dept): + # Randomly lose MinPartLoss to MaxPartLoss parts + + # First, decide how many parts we should lose. + loseCount = random.randrange(CogDisguiseGlobals.MinPartLoss, + CogDisguiseGlobals.MaxPartLoss + 1) + + # What parts do we have now? + parts = self.getCogParts() + partBitmask = parts[dept] + + # Generate a list of all possible part index numbers 0 .. 16. + partList = range(17) + + while loseCount > 0 and partList: + # Choose a random part index from our current pool, and + # remove that index from future selections. + losePart = random.choice(partList) + partList.remove(losePart) + + # Check to see if we have that part. If we do, remove it + # and continue; otherwise, do nothing and just keep + # looking for another part to remove. + losePartBit = (1 << losePart) + if partBitmask & losePartBit: + partBitmask &= ~losePartBit + loseCount -= 1 + + # Now send the distributed update. + parts[dept] = partBitmask + self.b_setCogParts(parts) + + ### set cog merits ### + + # Cog merits indicates how many cog promotion merits have been acquired. + # There is one array entry for each cog type (corp, legal, money, sales). + + def b_setCogMerits(self, merits): + self.setCogMerits(merits) + self.d_setCogMerits(merits) + + def setCogMerits(self, merits): + if not merits: + self.notify.warning("cogMerits set to bad value: %s. Resetting to [0,0,0,0]" % merits) + self.cogMerits = [0,0,0,0] + else: + self.cogMerits = merits + + def d_setCogMerits(self, merits): + self.sendUpdate("setCogMerits", [merits]) + + def getCogMerits(self): + return self.cogMerits + + def b_promote(self, dept): + self.promote(dept) + self.d_promote(dept) + + def promote(self, dept): + # if we are lvl 50, don't require any more merits to be collected + if self.cogLevels[dept] < ToontownGlobals.MaxCogSuitLevel: + self.cogMerits[dept] = 0 + self.incCogLevel(dept) + + def d_promote(self, dept): + merits = self.getCogMerits() + # if we are lvl 50, don't require any more merits to be collected + if self.cogLevels[dept] < ToontownGlobals.MaxCogSuitLevel: + merits[dept] = 0 + self.d_setCogMerits(merits) + + def readyForPromotion(self, dept): + merits = self.cogMerits[dept] + totalMerits = CogDisguiseGlobals.getTotalMerits(self, dept) + if (merits >= totalMerits): + return 1 + else: + return 0 + + ### cog index ### + + # cog index which cog (in the above cogTypes) we are disguised as + + def b_setCogIndex(self, index): + self.setCogIndex(index) + self.d_setCogIndex(index) + + def setCogIndex(self, index): + self.cogIndex = index + + def d_setCogIndex(self, index): + self.sendUpdate("setCogIndex", [index]) + + def getCogIndex(self): + return self.cogIndex + + ### disguise page flag ### + + # remembers if we have been given the disguise page of our sticker + # books yet + + def b_setDisguisePageFlag(self, flag): + self.setDisguisePageFlag(flag) + self.d_setDisguisePageFlag(flag) + + def setDisguisePageFlag(self, flag): + self.disguisePageFlag = flag + + def d_setDisguisePageFlag(self, flag): + self.sendUpdate("setDisguisePageFlag", [flag]) + + def getDisguisePageFlag(self): + return self.disguisePageFlag + + ## Fish collection + def b_setFishCollection(self, genusList, speciesList, weightList): + # update the caught fish list + self.setFishCollection(genusList, speciesList, weightList) + self.d_setFishCollection(genusList, speciesList, weightList) + + def d_setFishCollection(self, genusList, speciesList, weightList): + self.sendUpdate("setFishCollection", [genusList, speciesList, weightList]) + + def setFishCollection(self, genusList, speciesList, weightList): + #import pdb; pdb.set_trace() + self.fishCollection = FishCollection.FishCollection() + self.fishCollection.makeFromNetLists(genusList, speciesList, weightList) + + def getFishCollection(self): + return self.fishCollection.getNetLists() + + ## Max fish tank + + def b_setMaxFishTank(self, maxTank): + self.d_setMaxFishTank(maxTank) + self.setMaxFishTank(maxTank) + + def d_setMaxFishTank(self, maxTank): + self.sendUpdate("setMaxFishTank", [maxTank]) + + def setMaxFishTank(self, maxTank): + self.maxFishTank = maxTank + + def getMaxFishTank(self): + return self.maxFishTank + + + ## Fish tank + + def b_setFishTank(self, genusList, speciesList, weightList): + # update the caught fish list + self.setFishTank(genusList, speciesList, weightList) + self.d_setFishTank(genusList, speciesList, weightList) + + def d_setFishTank(self, genusList, speciesList, weightList): + self.sendUpdate("setFishTank", [genusList, speciesList, weightList]) + + def setFishTank(self, genusList, speciesList, weightList): + self.fishTank = FishTank.FishTank() + self.fishTank.makeFromNetLists(genusList, speciesList, weightList) + + def getFishTank(self): + return self.fishTank.getNetLists() + + def makeRandomFishTank(self): + self.fishTank.generateRandomTank() + self.d_setFishTank(*self.fishTank.getNetLists()) + + def addFishToTank(self, fish): + # First check our max limit + numFish = len(self.fishTank) + if numFish >= self.maxFishTank: + self.notify.warning("addFishToTank: cannot add fish, tank is full") + return 0 + else: + # Perhaps this can fail for some reason + if self.fishTank.addFish(fish): + self.d_setFishTank(*self.fishTank.getNetLists()) + return 1 + else: + self.notify.warning("addFishToTank: addFish failed") + return 0 + + def removeFishFromTankAtIndex(self, index): + # Try to remove this fish from the tank + if self.fishTank.removeFishAtIndex(index): + self.d_setFishTank(*self.fishTank.getNetLists()) + return 1 + else: + self.notify.warning("removeFishFromTank: cannot find fish") + return 0 + + + def b_setFishingRod(self, rodId): + self.d_setFishingRod(rodId) + self.setFishingRod(rodId) + + def d_setFishingRod(self, rodId): + self.sendUpdate("setFishingRod", [rodId]) + + def setFishingRod(self, rodId): + self.fishingRod = rodId + + def getFishingRod(self): + return self.fishingRod + + ### fishing trophy list ### + + def b_setFishingTrophies(self, trophyList): + # update the caught fish list + self.setFishingTrophies(trophyList) + self.d_setFishingTrophies(trophyList) + + def setFishingTrophies(self, trophyList): + self.notify.debug("setting fishingTrophies to %s" % trophyList) + self.fishingTrophies = trophyList + + def d_setFishingTrophies(self, trophyList): + self.sendUpdate("setFishingTrophies", [trophyList]) + + def getFishingTrophies(self): + return self.fishingTrophies + + + ### quest list ### + + def b_setQuests(self, questList): + # questList should be a nested list + # [[quest0 properties], [quest1 properties],...] + # This needs to be flattened + flattenedQuests = [] + for quest in questList: + flattenedQuests.extend(quest) + self.setQuests(flattenedQuests) + self.d_setQuests(flattenedQuests) + + def d_setQuests(self, flattenedQuests): + self.sendUpdate("setQuests", [flattenedQuests]) + + def setQuests(self, flattenedQuests): + self.notify.debug("setting quests to %s" % flattenedQuests) + # Build the real quest list from the flattened one from the network + questList = [] + # A quest is a list with + # (questId, npcId, otherId, rewardId, progress) + questLen = 5 + # Step from 2 to the end, by the questLen + for i in range(0, len(flattenedQuests), questLen): + questList.append(flattenedQuests[i:i+questLen]) + self.quests = questList + + def getQuests(self): + # This returns the quests formatted for the net, not directly + # usable. + flattenedQuests = [] + for quest in self.quests: + flattenedQuests.extend(quest) + return flattenedQuests + + def getQuest(self, id, visitNpcId = None): + for quest in self.quests: + if quest[0] == id: + # If a visitNpc was passed in, make sure that matches too + # Visit quests all have the same Id, so you must differentiate + # them with the id of the npc we need to visit + if visitNpcId: + if ((visitNpcId == quest[1]) or + (visitNpcId == quest[2])): + return quest + else: + return quest + return None + + def removeQuest(self, id, visitNpcId = None): + index = -1 + for i in range(len(self.quests)): + if (self.quests[i][0] == id): + # If this is a visit quest, we need to make sure the npc + # we are visiting matches since all visit quests have the + # same id + if visitNpcId: + otherId = self.quests[i][2] + if (visitNpcId == otherId): + index = i + break + else: + index = i + break + if (index >= 0): + del self.quests[i] + self.b_setQuests(self.quests) + return 1 + else: + return 0 + + def addQuest(self, quest, finalReward, recordHistory=1): + # Add this quest to this avatar in the database + # For the final looping tier, we do not want to keep + # accumulating history so you have the option the nor + # recordHistory. This will still update quests, but not + # questHistory or rewardHistory + self.quests.append(quest) + self.b_setQuests(self.quests) + + if recordHistory: + if quest[0] != Quests.VISIT_QUEST_ID: + # Also add this quest to the history. + newQuestHistory = self.questHistory + [quest[0]] + + # And then remove all previous occurrences of the visit + # quest from the quest history list. This is really just + # a hack to repair the damage from previous versions of + # this code, which allowed this quest to accumulate in the + # history list until we exceeded all available space in + # the toon's database record. The first time a particular + # toon adds a new quest, it will eliminate all of these. + while newQuestHistory.count(Quests.VISIT_QUEST_ID) != 0: + newQuestHistory.remove(Quests.VISIT_QUEST_ID) + + self.b_setQuestHistory(newQuestHistory) + + # Now update the reward history, only if this is a single quest + # or the start of a multipart quest. In either case, finalReward + # will be non-None, and we should store it in our history + if finalReward: + newRewardHistory = self.rewardHistory + [finalReward] + self.b_setRewardHistory(self.rewardTier, newRewardHistory) + + def removeAllTracesOfQuest(self, questId, rewardId): + self.notify.warning('removeAllTracesOfQuest: questId: %s rewardId: %s' % (questId, rewardId)) + self.notify.warning('removeAllTracesOfQuest: quests before: %s' % (self.quests)) + self.removeQuest(questId) + self.notify.warning('removeAllTracesOfQuest: quests after: %s' % (self.quests)) + self.notify.warning('removeAllTracesOfQuest: questHistory before: %s' % (self.questHistory)) + self.removeQuestFromHistory(questId) + self.notify.warning('removeAllTracesOfQuest: questHistory after: %s' % (self.questHistory)) + self.notify.warning('removeAllTracesOfQuest: reward history before: %s' % (self.rewardHistory)) + self.removeRewardFromHistory(rewardId) + self.notify.warning('removeAllTracesOfQuest: reward history after: %s' % (self.rewardHistory)) + + # The number of quests you can carry at once + def b_setQuestCarryLimit(self, limit): + self.setQuestCarryLimit(limit) + self.d_setQuestCarryLimit(limit) + + def d_setQuestCarryLimit(self, limit): + self.sendUpdate("setQuestCarryLimit", [limit]) + + def setQuestCarryLimit(self, limit): + self.notify.debug("setting questCarryLimit to %s" % limit) + self.questCarryLimit = limit + + def getQuestCarryLimit(self): + return self.questCarryLimit + + def b_setMaxCarry(self, maxCarry): + self.setMaxCarry(maxCarry) + self.d_setMaxCarry(maxCarry) + + def d_setMaxCarry(self, maxCarry): + self.sendUpdate("setMaxCarry", [maxCarry]) + + def setMaxCarry(self, maxCarry): + self.maxCarry = maxCarry + + def getMaxCarry(self): + return self.maxCarry + + ### cheesy rendering effects ### + + def b_setCheesyEffect(self, effect, hoodId, expireTime): + self.setCheesyEffect(effect, hoodId, expireTime) + self.d_setCheesyEffect(effect, hoodId, expireTime) + + def d_setCheesyEffect(self, effect, hoodId, expireTime): + self.sendUpdate("setCheesyEffect", [effect, hoodId, expireTime]) + + def setCheesyEffect(self, effect, hoodId, expireTime): + if simbase.air.holidayManager and \ + ToontownGlobals.WINTER_CAROLING not in simbase.air.holidayManager.currentHolidays \ + and effect == ToontownGlobals.CESnowMan: + self.b_setCheesyEffect(ToontownGlobals.CENormal, hoodId, expireTime) + return + self.savedCheesyEffect = effect + self.savedCheesyHoodId = hoodId + self.savedCheesyExpireTime = expireTime + + if self.air.doLiveUpdates: + taskName = self.uniqueName('cheesy-expires') + taskMgr.remove(taskName) + + if effect != ToontownGlobals.CENormal: + # Set a timeout to undo the cheesy effect later. + duration = expireTime * 60 - time.time() + if (duration > 0): + taskMgr.doMethodLater(duration, self.__undoCheesyEffect, taskName) + else: + # Undo the cheesy effect right away. + self.__undoCheesyEffect(None) + + def getCheesyEffect(self): + return (self.savedCheesyEffect, self.savedCheesyHoodId, self.savedCheesyExpireTime) + + def __undoCheesyEffect(self, task): + self.b_setCheesyEffect(ToontownGlobals.CENormal, 0, 0) + return Task.cont + + ### setTrackAccess ### + + def b_setTrackAccess(self, trackArray): + # local + self.setTrackAccess(trackArray) + # distributed + self.d_setTrackAccess(trackArray) + + def d_setTrackAccess(self, trackArray): + self.sendUpdate("setTrackAccess", [trackArray]) + + def setTrackAccess(self, trackArray): + self.trackArray = trackArray + + def getTrackAccess(self): + return self.trackArray + + def addTrackAccess(self, track): + """ + Give this toon access to gags on this track + """ + self.trackArray[track] = 1 + self.b_setTrackAccess(self.trackArray) + + def removeTrackAccess(self, track): + """ + Deny this toon access to gags on this track + """ + self.trackArray[track] = 0 + self.b_setTrackAccess(self.trackArray) + + def hasTrackAccess(self, track): + """ + Can this toon use this track? + Returns bool 0/1 + """ + if self.trackArray and track < len(self.trackArray): + return self.trackArray[track] + else: + #RAU this case can happen if we are opening the closet of another toon + return 0 + + + def fixTrackAccess(self): + fixed = 0 + healExp, trapExp, lureExp, soundExp, throwExp, squirtExp, dropExp = self.experience.experience + numTracks = reduce(lambda a,b: a+b, self.trackArray) + if self.rewardTier in [0,1,2,3]: + if numTracks != 2: + self.notify.warning("bad num tracks in tier: %s, %s" % (self.rewardTier, self.trackArray)) + # They should have throw and squirt only + self.b_setTrackAccess([0,0,0,0,1,1,0]) + fixed = 1 + elif self.rewardTier in [4,5,6]: + if numTracks != 3: + self.notify.warning("bad num tracks in tier: %s, %s" % (self.rewardTier, self.trackArray)) + # Now they had a choice between heal and sound + # If they have sound but not heal, give them sound + if (self.trackArray[ToontownBattleGlobals.SOUND_TRACK] and not + self.trackArray[ToontownBattleGlobals.HEAL_TRACK]): + self.b_setTrackAccess([0,0,0,1,1,1,0]) + # If they have heal but not sound, give them heal + elif (self.trackArray[ToontownBattleGlobals.HEAL_TRACK] and not + self.trackArray[ToontownBattleGlobals.SOUND_TRACK]): + self.b_setTrackAccess([1,0,0,0,1,1,0]) + # If they have both, use exp to determine + else: + # If they are higher in sound, give them sound access + if soundExp >= healExp: + self.b_setTrackAccess([0,0,0,1,1,1,0]) + # if they are higher in heal, give them heal access + else: + self.b_setTrackAccess([1,0,0,0,1,1,0]) + fixed = 1 + + elif self.rewardTier in [7,8,9,10]: + if numTracks != 4: + self.notify.warning("bad num tracks in tier: %s, %s" % (self.rewardTier, self.trackArray)) + # If they have sound but not heal, give them sound and drop or lure + if (self.trackArray[ToontownBattleGlobals.SOUND_TRACK] and not + self.trackArray[ToontownBattleGlobals.HEAL_TRACK]): + if dropExp >= lureExp: + # sound and drop + self.b_setTrackAccess([0,0,0,1,1,1,1]) + else: + # sound and lure + self.b_setTrackAccess([0,0,1,1,1,1,0]) + + # If they have heal but not sound, give them heal and drop or lure + elif (self.trackArray[ToontownBattleGlobals.HEAL_TRACK] and not + self.trackArray[ToontownBattleGlobals.SOUND_TRACK]): + if dropExp >= lureExp: + # heal and drop + self.b_setTrackAccess([1,0,0,0,1,1,1]) + else: + # heal and lure + self.b_setTrackAccess([1,0,1,0,1,1,0]) + + # Else look at exp to determine if they should have sound or heal + # then drop or lure + elif soundExp >= healExp: + if dropExp >= lureExp: + # sound and drop + self.b_setTrackAccess([0,0,0,1,1,1,1]) + else: + # sound and lure + self.b_setTrackAccess([0,0,1,1,1,1,0]) + else: + if dropExp >= lureExp: + # heal and drop + self.b_setTrackAccess([1,0,0,0,1,1,1]) + else: + # heal and lure + self.b_setTrackAccess([1,0,1,0,1,1,0]) + + fixed = 1 + elif self.rewardTier in [11,12,13]: + if numTracks != 5: + self.notify.warning("bad num tracks in tier: %s, %s" % (self.rewardTier, self.trackArray)) + if (self.trackArray[ToontownBattleGlobals.SOUND_TRACK] and not + self.trackArray[ToontownBattleGlobals.HEAL_TRACK]): + if (self.trackArray[ToontownBattleGlobals.DROP_TRACK] and not + self.trackArray[ToontownBattleGlobals.LURE_TRACK]): + if healExp >= trapExp: + self.b_setTrackAccess([1,0,0,1,1,1,1]) + else: + self.b_setTrackAccess([0,1,0,1,1,1,1]) + else: + if healExp >= trapExp: + self.b_setTrackAccess([1,0,1,1,1,1,0]) + else: + self.b_setTrackAccess([0,1,1,1,1,1,0]) + elif (self.trackArray[ToontownBattleGlobals.HEAL_TRACK] and not + self.trackArray[ToontownBattleGlobals.SOUND_TRACK]): + if (self.trackArray[ToontownBattleGlobals.DROP_TRACK] and not + self.trackArray[ToontownBattleGlobals.LURE_TRACK]): + if soundExp >= trapExp: + self.b_setTrackAccess([1,0,0,1,1,1,1]) + else: + self.b_setTrackAccess([1,1,0,0,1,1,1]) + else: + if soundExp >= trapExp: + self.b_setTrackAccess([1,0,1,1,1,1,0]) + else: + self.b_setTrackAccess([1,1,1,0,1,1,0]) + fixed = 1 + else: + if numTracks != 6: + self.notify.warning("bad num tracks in tier: %s, %s" % (self.rewardTier, self.trackArray)) + # Do not count throw or squirt because you are stuck with those + sortedExp = [healExp, trapExp, lureExp, soundExp, dropExp] + # Sort will put the smallest (least exp) first + sortedExp.sort() + # Check the least desirable tracks first in case there is a tie + if trapExp == sortedExp[0]: + self.b_setTrackAccess([1,0,1,1,1,1,1]) + elif lureExp == sortedExp[0]: + self.b_setTrackAccess([1,1,0,1,1,1,1]) + elif dropExp == sortedExp[0]: + self.b_setTrackAccess([1,1,1,1,1,1,0]) + elif soundExp == sortedExp[0]: + self.b_setTrackAccess([1,1,1,0,1,1,1]) + elif healExp == sortedExp[0]: + self.b_setTrackAccess([0,1,1,1,1,1,1]) + else: + self.notify.warning("invalid exp?!: %s, %s" % (sortedExp, self.trackArray)) + # Give the avatar something + self.b_setTrackAccess([1,0,1,1,1,1,1]) + fixed = 1 + + if fixed: + # Adjust inventory to fit new tracks + self.inventory.zeroInv() + self.inventory.maxOutInv() + self.d_setInventory(self.inventory.makeNetString()) + self.notify.info("fixed tracks: %s" % (self.trackArray)) + + return fixed + + + ### setTrackProgress ### + + def b_setTrackProgress(self, trackId, progress): + # local + self.setTrackProgress(trackId, progress) + # distributed + self.d_setTrackProgress(trackId, progress) + + def d_setTrackProgress(self, trackId, progress): + self.sendUpdate("setTrackProgress", [trackId, progress]) + + def setTrackProgress(self, trackId, progress): + """ + Update your progress training trackId. TrackId is an index into + ToontownBattleGlobals.Tracks and progress is a bitarray of progress + markers gathered. A trackId of -1 means you are not training any track. + """ + self.trackProgressId = trackId + self.trackProgress = progress + + def addTrackProgress(self, trackId, progressIndex): + """ + Update your progress training trackId with this index. + """ + if self.trackProgressId != trackId: + self.notify.warning("tried to update progress on a track toon is not training") + newProgress = self.trackProgress | (1 << (progressIndex - 1)) + self.b_setTrackProgress(self.trackProgressId, newProgress) + + def clearTrackProgress(self): + """ + Update your progress training to be empty. + """ + self.b_setTrackProgress(-1, 0) + + def getTrackProgress(self): + return [self.trackProgressId, self.trackProgress] + + ### setHoodsVisited ### + + def b_setHoodsVisited(self, hoodsVisitedArray): + # local + self.hoodsVisited = hoodsVisitedArray + # distributed + self.d_setHoodsVisited(hoodsVisitedArray) + + def d_setHoodsVisited(self, hoodsVisitedArray): + self.sendUpdate("setHoodsVisited", [hoodsVisitedArray]) + + ### setTeleportAccess ### + + def b_setTeleportAccess(self, teleportZoneArray): + # local + self.setTeleportAccess(teleportZoneArray) + # distributed + self.d_setTeleportAccess(teleportZoneArray) + + def d_setTeleportAccess(self, teleportZoneArray): + self.sendUpdate("setTeleportAccess", [teleportZoneArray]) + + def setTeleportAccess(self, teleportZoneArray): + self.teleportZoneArray = teleportZoneArray + + def getTeleportAccess(self): + return self.teleportZoneArray + + def hasTeleportAccess(self, zoneId): + """ + Return true if this zoneId is in our teleport access array + (meaning we can teleport to it) + """ + return (zoneId in self.teleportZoneArray) + + def addTeleportAccess(self, zoneId): + """ + Give this toon teleport access to this zoneId + Update the zone array to add this zoneId and + message to the client to update him too + """ + # Make sure this is a valid hood zoneId + assert zoneId in ToontownGlobals.Hoods + if zoneId not in self.teleportZoneArray: + self.teleportZoneArray.append(zoneId) + self.b_setTeleportAccess(self.teleportZoneArray) + + def removeTeleportAccess(self, zoneId): + """ + Update the zone array to remove this zoneId and send a + message to the client to update him too + """ + if zoneId in self.teleportZoneArray: + self.teleportZoneArray.remove(zoneId) + self.b_setTeleportAccess(self.teleportZoneArray) + + + def b_setQuestHistory(self, questList): + self.setQuestHistory(questList) + self.d_setQuestHistory(questList) + + def d_setQuestHistory(self, questList): + self.sendUpdate("setQuestHistory", [questList]) + + def setQuestHistory(self, questList): + self.notify.debug("setting quest history to %s" % questList) + self.questHistory = questList + + def getQuestHistory(self): + return self.questHistory + + def removeQuestFromHistory(self, questId): + if questId in self.questHistory: + # Remove it locally + self.questHistory.remove(questId) + # And on the server + self.d_setQuestHistory(self.questHistory) + return 1 + else: + return 0 + + def removeRewardFromHistory(self, rewardId): + rewardTier, rewardHistory = self.getRewardHistory() + if rewardId in rewardHistory: + rewardHistory.remove(rewardId) + self.b_setRewardHistory(rewardTier, rewardHistory) + return 1 + else: + return 0 + + def b_setRewardHistory(self, tier, rewardList): + self.setRewardHistory(tier, rewardList) + self.d_setRewardHistory(tier, rewardList) + + def d_setRewardHistory(self, tier, rewardList): + self.sendUpdate("setRewardHistory", [tier, rewardList]) + + def setRewardHistory(self, tier, rewardList): + self.air.writeServerEvent("questTier", self.getDoId(), str(tier)) + self.notify.debug("setting reward history to tier %s, %s" % (tier, rewardList)) + self.rewardTier = tier + self.rewardHistory = rewardList + + def getRewardHistory(self): + return self.rewardTier, self.rewardHistory + + def getRewardTier(self): + return self.rewardTier + + # TODO + # If the need ever arises to do upon-login toon DB patching, here's + # a scheme that drose and I worked out: + # + # DistributedToonAI should have a function that validates the toon's DB + # entries, and sends out updates for any values that need to change. + # We can't just call this in DistributedToonAI.generate(), though. + # + # There is a race condition between generation of the Toon on the AI + # and on the client; we must make sure that the Toon has been generated + # on the client before we run the validation function, so that the + # client will receive any updates we send out. We must also make sure + # that the client does not start up the game on its end before the AI + # has had a chance to fix up the Toon's fields. + # + # We can add two new messages to DistributedToon in the toon.dc, one + # going from the AI to the client (QueryReady), the other from the + # client to the AI (ReplyReady). Neither has any arguments. When the + # toon is generated on the AI, it will start a task that will send out + # QueryReady msgs at some interval (or maybe it could be a ram field + # with a default value, that the AI sets to a different value upon + # generation). When the toon is generated on the client, it will enter + # a mode where it will reply to the first QueryReady msg it receives + # with a ReplyReady msg. + # + # When the AI gets a ReplyReady message, it knows that the client has + # generated the Toon, and it can go ahead and fix up the toon, sending + # updates for any fields that change. + # + # (Note that it's not important for the DB server to get these updates; + # if an update is missed, the AI and the client will have the correct + # values for the rest of the session. The same values will be computed + # upon subsequent logins, until they eventually make it into the + # database. Additionally, if the field is modified during the play + # session, the old invalid DB value will be overwritten.) + # + # The only thing left now is preventing the client from starting up the + # game before the AI has vetted its Toon. We could withold the + # TimeManager's first sync reply to the client until the toon has been + # checked. (see ToontownClientRepository.gotTimeSync) Alternatively, we + # could add another DC network message on LocalToon that the AI sends + # just after doing its fixup, and add a new state to the TCR that waits + # for this message to arrive before proceeding. + + # It would also be cool to be able to use AvatarIterators (used to do + # entire-DB patches) in DistributedToonAI's fixup function. + + def fixAvatar(self): + # Fix whatever might be out-of-whack in the avatar. Returns 1 + # if the avatar was broken and has been changed, or 0 if it + # was fine. + + anyChanged = 0 + + # First, recompute and reapply the rewards according to the + # current quest tier. + qrc = QuestRewardCounter.QuestRewardCounter() + if qrc.fixAvatar(self): + self.notify.info("Fixed avatar %d's quest rewards." % (self.doId)) + anyChanged = 1 + + if self.hp > self.maxHp: + self.notify.info("Changed avatar %d to have hp %d instead of %d, to fit with maxHp" % (self.doId, self.maxHp, self.hp)) + self.b_setHp(self.maxHp) + anyChanged = 1 + + # Make sure we aren't carrying gags we're not allowed to + # carry, etc. + inventoryChanged = 0 + carry = self.maxCarry + + for track in range(len(ToontownBattleGlobals.Tracks)): + if not self.hasTrackAccess(track): + for level in range(len(ToontownBattleGlobals.Levels[track])): + count = self.inventory.inventory[track][level] + if count != 0: + self.notify.info("Changed avatar %d to throw away %d items in track %d level %d; no access to track." % (self.doId, count, track, level)) + self.inventory.inventory[track][level] = 0 + inventoryChanged = 1 + else: + # We have access to the track; what's the highest + # skill level we have access to? + curSkill = self.experience.getExp(track) + for level in range(len(ToontownBattleGlobals.Levels[track])): + count = self.inventory.inventory[track][level] + if curSkill < ToontownBattleGlobals.Levels[track][level]: + if count != 0: + self.notify.info("Changed avatar %d to throw away %d items in track %d level %d; no access to level." % (self.doId, count, track, level)) + self.inventory.inventory[track][level] = 0 + inventoryChanged = 1 + else: + newCount = min(count, carry) + newCount = min(count, self.inventory.getMax(track, level)) + if count != newCount: + self.notify.info("Changed avatar %d to throw away %d items in track %d level %d; too many gags." % (self.doId, count - newCount, track, level)) + self.inventory.inventory[track][level] = newCount + inventoryChanged = 1 + carry -= newCount + + self.inventory.calcTotalProps() + if inventoryChanged: + self.d_setInventory(self.inventory.makeNetString()) + anyChanged = 1 + + if len(self.quests) > self.questCarryLimit: + self.notify.info("Changed avatar %d to throw out %d quests; too many quests." % (self.doId, len(self.quests) - self.questCarryLimit)) + self.b_setQuests(self.quests[:self.questCarryLimit]) + + # Now that we've removed one of the quests, we need to do + # the whole thing again, just to make sure we don't lose + # credit for the quest we have now no longer completed. + self.fixAvatar() + anyChanged = 1 + + """ + # Also, remove old quest tiers from the questHistory so we + # keep the database record small. + if self.questHistory: + # Get the largest value on the quest history. This must + # represent the current tier. + m = max(self.questHistory) + + # Integer division. + tier = (m / 1000) + newQuestHistory = [] + for q in self.questHistory: + if (q / 1000) == tier: + newQuestHistory.append(q) + + if self.questHistory != newQuestHistory: + self.notify.info("Changed avatar %d to have questHistory %s instead of %s." % (self.doId, newQuestHistory, self.questHistory)) + self.b_setQuestHistory(newQuestHistory) + anyChanged = 1 + """ + + if not (self.emoteAccess[0] and self.emoteAccess[1] and + self.emoteAccess[2] and self.emoteAccess[3] and + self.emoteAccess[4]): + self.emoteAccess[0] = 1 + self.emoteAccess[1] = 1 + self.emoteAccess[2] = 1 + self.emoteAccess[3] = 1 + self.emoteAccess[4] = 1 + self.b_setEmoteAccess(self.emoteAccess) + self.notify.info("Changed avatar %d to have emoteAccess: %s" % (self.doId, self.emoteAccess)) + anyChanged = 1 + + return anyChanged + + def b_setEmoteAccess(self, bits): + self.setEmoteAccess(bits) + self.d_setEmoteAccess(bits) + + def d_setEmoteAccess(self, bits): + self.sendUpdate("setEmoteAccess", [bits]) + + def setEmoteAccess(self, bits): + if len(bits) == 20: + bits.extend([0,0,0,0,0]) + self.b_setEmoteAccess(bits) + else: + if len(bits) != len(self.emoteAccess): + self.notify.warning("New emote access list must be the same size as the old one.") + return + self.emoteAccess = bits + + def getEmoteAccess(self): + return self.emoteAccess + + def setEmoteAccessId(self, id, bit): + self.emoteAccess[id] = bit + self.d_setEmoteAccess(self.emoteAccess) + + # assign a house to a toon + def b_setHouseId(self, id): + self.setHouseId(id) + self.d_setHouseId(id) + + def d_setHouseId(self, id): + self.sendUpdate("setHouseId", [id]) + + def setHouseId(self, id): + self.houseId = id + + def getHouseId(self): + return self.houseId + + def setPosIndex(self, index): + self.posIndex = index + + def getPosIndex(self): + return self.posIndex + + # Control the list of custom SpeedChat messages the toon may make. + def b_setCustomMessages(self, customMessages): + self.d_setCustomMessages(customMessages) + self.setCustomMessages(customMessages) + + def d_setCustomMessages(self, customMessages): + self.sendUpdate('setCustomMessages', [customMessages]) + + def setCustomMessages(self, customMessages): + self.customMessages = customMessages + + def getCustomMessages(self): + return self.customMessages + + # Control the list of resistance SpeedChat messages the toon may make. + def b_setResistanceMessages(self, resistanceMessages): + self.d_setResistanceMessages(resistanceMessages) + self.setResistanceMessages(resistanceMessages) + + def d_setResistanceMessages(self, resistanceMessages): + self.sendUpdate('setResistanceMessages', [resistanceMessages]) + + def setResistanceMessages(self, resistanceMessages): + self.resistanceMessages = resistanceMessages + + def getResistanceMessages(self): + return self.resistanceMessages + + def addResistanceMessage(self, textId): + msgs = self.getResistanceMessages() + #look through the array and find the textId + for i in range(len(msgs)): + if msgs[i][0] == textId: + msgs[i][1] += 1 + self.b_setResistanceMessages(msgs) + return + + #we didn't find the message... add it to the end + msgs.append([textId, 1]) + self.b_setResistanceMessages(msgs) + + def removeResistanceMessage(self, textId): + # Removes the indicated resistance message from the Toon's + # inventory. Returns true if it was there in the first place, + # false if it was not. + + msgs = self.getResistanceMessages() + #look through the array and find the textId + for i in range(len(msgs)): + if msgs[i][0] == textId: + msgs[i][1] -= 1 + if msgs[i][1] <= 0: + del msgs[i] + self.b_setResistanceMessages(msgs) + return 1 + + self.notify.warning("Toon %s doesn't have resistance message %s" % (self.doId, textId)) + return 0 + + # Control the schedule of catalogs. + + def b_setCatalogSchedule(self, currentWeek, nextTime): + self.setCatalogSchedule(currentWeek, nextTime) + self.d_setCatalogSchedule(currentWeek, nextTime) + + def d_setCatalogSchedule(self, currentWeek, nextTime): + self.sendUpdate("setCatalogSchedule", [currentWeek, nextTime]) + + def setCatalogSchedule(self, currentWeek, nextTime): + self.catalogScheduleCurrentWeek = currentWeek + self.catalogScheduleNextTime = nextTime + + if self.air.doLiveUpdates: + # Schedule the next catalog. + taskName = self.uniqueName('next-catalog') + taskMgr.remove(taskName) + + # Set a timeout to deliver the catalog later. We insist on + # waiting at least 10 seconds mainly to give the avatar enough + # time to completely manifest on the stateserver before we + # start sending messages to it. + + duration = max(10.0, nextTime * 60 - time.time()) + taskMgr.doMethodLater(duration, self.__deliverCatalog, taskName) + #self.notify.info + #self.air.writeServerEvent('CatalogSPAM', self.doId, " %s avId %s week %s, next catalog in %s." % ( + # self.getName(), self.doId, currentWeek, + # PythonUtil.formatElapsedSeconds(duration))) + + def getCatalogSchedule(self): + return (self.catalogScheduleCurrentWeek, self.catalogScheduleNextTime) + + def __deliverCatalog(self, task): + # super spammy hacks to track down ai crash + # self.notify.info('Got __deliverCatalog for %d' % self.doId) + # self.air.writeServerEvent('__deliverCatalog' , self.doId, '') + self.air.catalogManager.deliverCatalogFor(self) + return Task.done + + + def b_setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog): + self.setCatalog(monthlyCatalog, weeklyCatalog, backCatalog) + self.d_setCatalog(monthlyCatalog, weeklyCatalog, backCatalog) + + def d_setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog): + self.sendUpdate("setCatalog", [monthlyCatalog.getBlob(), weeklyCatalog.getBlob(), backCatalog.getBlob()]) + + def setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog): + self.monthlyCatalog = CatalogItemList.CatalogItemList(monthlyCatalog) + self.weeklyCatalog = CatalogItemList.CatalogItemList(weeklyCatalog) + self.backCatalog = CatalogItemList.CatalogItemList(backCatalog) + + def getCatalog(self): + return (self.monthlyCatalog.getBlob(), self.weeklyCatalog.getBlob(), self.backCatalog.getBlob()) + + def b_setCatalogNotify(self, catalogNotify, mailboxNotify): + self.setCatalogNotify(catalogNotify, mailboxNotify) + self.d_setCatalogNotify(catalogNotify, mailboxNotify) + + def d_setCatalogNotify(self, catalogNotify, mailboxNotify): + self.sendUpdate("setCatalogNotify", [catalogNotify, mailboxNotify]) + + def setCatalogNotify(self, catalogNotify, mailboxNotify): + self.catalogNotify = catalogNotify + self.mailboxNotify = mailboxNotify + + def getCatalogNotify(self): + return (self.catalogNotify, self.mailboxNotify) + + def b_setDeliverySchedule(self, onOrder, doUpdateLater = True): + self.setDeliverySchedule(onOrder, doUpdateLater) + self.d_setDeliverySchedule(onOrder) + + def d_setDeliverySchedule(self, onOrder): + self.sendUpdate("setDeliverySchedule", [onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate)]) + + + + + def setDeliverySchedule(self, onOrder, doUpdateLater = True): + self.setBothSchedules(onOrder, None) + return + #I've rerouted this function to setBothSchedules to resolve a stomping issue with the mailbox feild + #the original version follows + + self.onOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if hasattr(self, 'name'): + if doUpdateLater and self.air.doLiveUpdates and hasattr(self, 'air'): + # Schedule the next delivery. + taskName = self.uniqueName('next-delivery') + taskMgr.remove(taskName) + + #print("setting item schedule for %s" % (self.getName())) + # Set a timeout to make the delivery later. We insist on + # waiting at least 10 seconds mainly to give the avatar enough + # time to completely manifest on the stateserver before we + # start sending messages to it. + now = (int)(time.time() / 60 + 0.5) + nextItem = None + nextTime = self.onOrder.getNextDeliveryDate() + nextItem = self.onOrder.getNextDeliveryItem() + if nextItem != None: + pass + #print(">>current time:%s" % (now)) + #print("next item time:%s item:%s" % (nextTime, nextItem.getName())) + else: + pass + #print("No items for regular delivery") + if nextTime != None: + duration = max(10.0, nextTime * 60 - time.time()) + taskMgr.doMethodLater(duration, self.__deliverPurchase, taskName) + + def getDeliverySchedule(self): + return self.onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + def b_setBothSchedules(self, onOrder, onGiftOrder,doUpdateLater = True): + self.setBothSchedules(onOrder, onGiftOrder, doUpdateLater) + self.d_setDeliverySchedule(onOrder) + + def setBothSchedules(self, onOrder, onGiftOrder, doUpdateLater = True): + #this function gets called twice in a row as soon as a toon enters the world + #this stomps the mailbox field in the database, so I'm using the taskManager + #as a cache; + #this function checks to see if it's action "__deliverBothPurchases" aka next-bothDelivery-%s + #is on the taskmanager, if it is and it wants to act sooner it replaces that action + + #We check both the gift and delviery queues. + #Note:when first called one of those queues will be uninitialized hence all the if None calls + #also this does need to be called twice as the toon enters the world + + #print ("Start setBothSchedules") + #print ("xxxx.onOrder %s" % (onOrder)) + #print ("self.onOrder %s" % (self.onOrder)) + #print ("xxxx.onGiftOrder %s " % (onGiftOrder)) + #print ("self.onGiftOrder %s" % (self.onGiftOrder)) + if onOrder != None: + self.onOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if onGiftOrder != None: + self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + #if doUpdateLater and self.air.doLiveUpdates and hasattr(self, 'air') and hasattr(self, 'name'): + if not hasattr(self, 'air') or (self.air == None): + return + if doUpdateLater and self.air.doLiveUpdates and hasattr(self, 'name'): + taskName = ("next-bothDelivery-%s" % (self.doId))#self.uniqueName('next-bothDelivery') + #taskMgr.remove(taskName) + + now = (int)(time.time() / 60 + 0.5) + + nextItem = None + nextGiftItem = None + nextTime = None + nextGiftTime = None + if self.onOrder: + nextTime = self.onOrder.getNextDeliveryDate() + nextItem = self.onOrder.getNextDeliveryItem() + if self.onGiftOrder: + nextGiftTime = self.onGiftOrder.getNextDeliveryDate() + nextGiftItem = self.onGiftOrder.getNextDeliveryItem() + #print(">>current time:%s" % (now)) + + if nextItem: + pass + #print("next item time:%s item:%s" % (nextTime, nextItem.getName())) + else: + pass + #print("No items for regular delivery") + if nextGiftItem: + pass + #print("next gift time:%s item:%s" % (nextGiftTime, nextGiftItem.getName())) + else: + pass + #print("No items for gift delivery") + + if nextTime == None: + nextTime = nextGiftTime + if nextGiftTime == None: + nextGiftTime = nextTime + + if nextGiftTime < nextTime: + nextTime = nextGiftTime + + existingDuration = None + checkTaskList = taskMgr.getTasksNamed(taskName) + if checkTaskList: + currentTime = globalClock.getFrameTime() + assert len(checkTaskList) <= 1 + checkTask = checkTaskList[0] + existingDuration = checkTask.wakeTime - currentTime + #print("existingDuration %s" % (existingDuration)) + #import pdb; pdb.set_trace() + + + #print("taskName %s" % (taskName)) + + if nextTime: + newDuration = max(10.0, nextTime * 60 - time.time()) + #print("Duration %s" % (newDuration)) + if existingDuration and existingDuration >= newDuration: + #print ("replacing duration") + taskMgr.remove(taskName) + taskMgr.doMethodLater(newDuration, self.__deliverBothPurchases, taskName) #change function + elif existingDuration and existingDuration < newDuration: + #print ("leaving duration") + pass + else: + #print ("adding duration") + taskMgr.doMethodLater(newDuration, self.__deliverBothPurchases, taskName) #change function + #print ("End setBothSchedules") + + def __deliverBothPurchases(self, task): + #combines delivering gifts and regular deliveries into one action + #to keep mailbox contents from getting stomped + + #print ("Start __deliverBothPurchases") + # Get the current time in minutes. + now = (int)(time.time() / 60 + 0.5) + # Extract out any items that should have been delivered by now. + delivered, remaining = self.onOrder.extractDeliveryItems(now) + + #self.notify.info("Delivery for %s: %s." % (self.doId, delivered)) + #print("Delivery for %s" % (self.getName())) + #print("Delivered %s." % (delivered)) + #print("Remaining %s." % (remaining)) + + # Extract out any Gift items that should have been delivered by now. + deliveredGifts, remainingGifts = self.onGiftOrder.extractDeliveryItems(now) + #self.notify.info("Gift Delivery for %s: %s." % (self.doId, deliveredGifts)) + #print("Gift Delivery for %s" % (self.getName())) + #print("Delivered %s." % (deliveredGifts)) + #print("Remaining %s." % (remainingGifts)) + simbase.air.deliveryManager.sendDeliverGifts(self.getDoId(), now) + + #b_setMailboxContents must come before b_setCatalogNotify + #because b_setMailboxContents resets the notification data + giftItem = CatalogItemList.CatalogItemList(deliveredGifts, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if(len(giftItem) > 0): + self.air.writeServerEvent("Getting Gift", self.doId, "sender %s receiver %s gift %s" % (giftItem[0].giftTag, self.doId, giftItem[0].getName())) + self.b_setMailboxContents(self.mailboxContents + delivered + deliveredGifts) + self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NewItems) + self.b_setBothSchedules(remaining, remainingGifts) + + #print ("End __deliverBothPurchases") + return Task.done + + + def setGiftSchedule(self, onGiftOrder, doUpdateLater = True): + self.setBothSchedules(None, onGiftOrder) + return + #I've rerouted this function to setBothSchedules to resolve a stomping issue with the mailbox feild + #the original version follows + + #self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + #assert 0 , "setting gift schedule" + if doUpdateLater and self.air.doLiveUpdates and hasattr(self, 'air') and hasattr(self, 'name'): + # Schedule the next gift. + taskName = self.uniqueName('next-gift') + taskMgr.remove(taskName) + + #print("setting gift schedule for %s" % (self.getName())) + # Set a timeout to make the gift later. We insist on + # waiting at least 10 seconds mainly to give the avatar enough + # time to completely manifest on the stateserver before we + # start sending messages to it. + now = (int)(time.time() / 60 + 0.5) + nextItem = None + nextTime = self.onGiftOrder.getNextDeliveryDate() + nextItem = self.onGiftOrder.getNextDeliveryItem() + if nextItem != None: + pass + #print(">>current time:%s" % (now)) + #print("next gift time:%s item:%s" % (nextTime, nextItem.getName())) + else: + pass + #print("No items for gift delivery") + if nextTime != None: + #assertString = ("Now: %s Delivery %s" %(now, nextTime)) + #assert 0 , assertString + duration = max(10.0, nextTime * 60 - time.time()) + duration += 30 #TOTAL HACK to keep __deliverGiftPurchase and __deliverPurchase from stomping each other + taskMgr.doMethodLater(duration, self.__deliverGiftPurchase, taskName) #change function + + + def getGiftSchedule(self): + #return self.onGiftOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + return self.onGiftOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + def __deliverGiftPurchase(self, task): + # Move one or more items from the onGiftOrder list to the + # mailboxContents. + """ + sends a request for the delivery of gifts to the uberdog + the return will call the method that receives gifts which will return + whether or not any new gifts were moved to the the mailbox + """ + + # Get the current time in minutes. + now = (int)(time.time() / 60 + 0.5) + + # Extract out any items that should have been delivered by now. + delivered, remaining = self.onGiftOrder.extractDeliveryItems(now) + + self.notify.info("Gift Delivery for %s: %s." % (self.doId, delivered)) + + self.b_setMailboxContents(self.mailboxContents + delivered) #OLD MAILBOX + simbase.air.deliveryManager.sendDeliverGifts(self.getDoId(), now) + + self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NewItems) + + return Task.done + + def __deliverPurchase(self, task): + # Move one or more items from the onOrder list to the + # mailboxContents. + + # Get the current time in minutes. + now = (int)(time.time() / 60 + 0.5) + + # Extract out any items that should have been delivered by now. + delivered, remaining = self.onOrder.extractDeliveryItems(now) + + self.notify.info("Delivery for %s: %s." % (self.doId, delivered)) + + self.b_setMailboxContents(self.mailboxContents + delivered) + self.b_setDeliverySchedule(remaining) + + self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NewItems) + + return Task.done + + def b_setMailboxContents(self, mailboxContents): + self.setMailboxContents(mailboxContents) + self.d_setMailboxContents(mailboxContents) + + def d_setMailboxContents(self, mailboxContents): + self.sendUpdate("setMailboxContents", [mailboxContents.getBlob(store = CatalogItem.Customization)]) + if len(mailboxContents) == 0: + self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NoItems) + self.checkMailboxFullIndicator() + + def checkMailboxFullIndicator(self): + """Raises the full indicator if we've got stuff in our mailbox.""" + if self.houseId and hasattr(self, 'air'): + # Check to see if our house currently exists in the live + # database. If it does, we should update its mailbox flag + # appropriately. + if self.air: + house = self.air.doId2do.get(self.houseId) + if house and house.mailbox: + + house.mailbox.b_setFullIndicator(len(self.mailboxContents) != 0 or \ + self.numMailItems or \ + self.getNumInvitesToShowInMailbox() or \ + len(self.awardMailboxContents) != 0) + + def setMailboxContents(self, mailboxContents): + self.notify.debug("Setting mailboxContents to %s." % (mailboxContents)) + self.mailboxContents = CatalogItemList.CatalogItemList(mailboxContents, store = CatalogItem.Customization) + self.notify.debug("mailboxContents is %s." % (self.mailboxContents)) + + def getMailboxContents(self): + return self.mailboxContents.getBlob(store = CatalogItem.Customization) + + """ + def b_setDeliveryboxContents(self, deliveryboxContents): + self.setDeliveryboxContents(deliveryboxContents) + self.d_setDeliveryboxContents(deliveryboxContents) + def d_setDeliveryboxContents(self, deliveryboxContents): + self.sendUpdate("setDeliveryboxContents", [deliveryboxContents.getBlob(store = CatalogItem.Customization | CatalogItem.GiftTag)]) + + if len(deliveryboxContents) == 0: + self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NoItems) + + if self.houseId: + # Check to see if our house currently exists in the live + # database. If it does, we should update its mailbox flag + # appropriately. + house = self.air.doId2do.get(self.houseId) + if house and house.mailbox: + house.mailbox.b_setFullIndicator(len(deliveryboxContents) != 0) + + + def setDeliveryboxContents(self, deliveryboxContents): + self.notify.debug("Setting deliveryboxContents to %s." % (deliveryboxContents)) + self.deliveryboxContents = CatalogItemList.CatalogItemList(deliveryboxContents, store = CatalogItem.Customization | CatalogItem.GiftTag) + self.notify.debug("deliveryboxContents is %s." % (self.deliveryboxContents)) + + def getDeliveryboxContents(self): + return self.deliveryboxContents.getBlob(store = CatalogItem.Customization | CatalogItem.GiftTag) + """ + def b_setGhostMode(self, flag): + self.setGhostMode(flag) + self.d_setGhostMode(flag) + + def d_setGhostMode(self, flag): + self.sendUpdate("setGhostMode", [flag]) + + def setGhostMode(self, flag): + self.ghostMode = flag + + def setImmortalMode(self, flag): + self.immortalMode = flag + + def b_setSpeedChatStyleIndex(self, index): + self.setSpeedChatStyleIndex(index) + self.d_setSpeedChatStyleIndex(index) + + def d_setSpeedChatStyleIndex(self, index): + self.sendUpdate("setSpeedChatStyleIndex", [index]) + + def setSpeedChatStyleIndex(self, index): + self.speedChatStyleIndex = index + + def getSpeedChatStyleIndex(self): + return self.speedChatStyleIndex + + def b_setMaxMoney(self, maxMoney): + self.d_setMaxMoney(maxMoney) + self.setMaxMoney(maxMoney) + + def d_setMaxMoney(self, maxMoney): + self.sendUpdate('setMaxMoney', [maxMoney]) + + def setMaxMoney(self, maxMoney): + self.maxMoney = maxMoney + + def getMaxMoney(self): + return self.maxMoney + + def addMoney(self, deltaMoney): + # Add money to you wallet, and let the rest overflow into the bank + # if money goes above max, deposit the rest into the bank + money = deltaMoney + self.money + pocketMoney = min(money, self.maxMoney) + # update wallet + self.b_setMoney(pocketMoney) + # update bank + overflowMoney = money-self.maxMoney + if overflowMoney > 0: + bankMoney = self.bankMoney + overflowMoney + self.b_setBankMoney(bankMoney) + + def takeMoney(self, deltaMoney, bUseBank = True): + # Take money from your wallet first, then from your bank + # if the bool is set + + #do sanity checks + totalMoney = self.money + if bUseBank: + totalMoney += self.bankMoney + + if (deltaMoney > totalMoney): + self.notify.warning("Not enough money! AvId: %s Has:%s Charged:%s" % (self.doId, totalMoney, deltaMoney)) + return False + + #withdraw funds + if (bUseBank and (deltaMoney > self.money)): + self.b_setBankMoney(self.bankMoney - (deltaMoney - self.money)) + self.b_setMoney(0) + else: + self.b_setMoney(self.money - deltaMoney) + + return True + + def b_setMoney(self, money): + + # Auto-rich cheat: Money never goes down. + if bboard.get('autoRich-%s' % self.doId, False): + assert self.notify.debug("%s is ~autoRich, maxing money" % self.doId) + money = self.getMaxMoney() + + self.setMoney(money) + self.d_setMoney(money) + + def d_setMoney(self, money): + self.sendUpdate('setMoney', [money]) + + def setMoney(self, money): + self.money = money + + def getMoney(self): + return self.money + + def getTotalMoney(self): + return (self.money + self.bankMoney) + + def b_setMaxBankMoney(self, maxMoney): + self.d_setMaxBankMoney(maxMoney) + self.setMaxBankMoney(maxMoney) + + def d_setMaxBankMoney(self, maxMoney): + self.sendUpdate("setMaxBankMoney", [maxMoney]) + + def setMaxBankMoney(self, maxMoney): + self.maxBankMoney = maxMoney + + def getMaxBankMoney(self): + return self.maxBankMoney + + def b_setBankMoney(self, money): + # SDN: if bank is too small, just make the excess + # money vanish (for now) + bankMoney = min(money, self.maxBankMoney) + self.setBankMoney(bankMoney) + self.d_setBankMoney(bankMoney) + + def d_setBankMoney(self, money): + self.sendUpdate("setBankMoney", [money]) + + def setBankMoney(self, money): + self.bankMoney = money + + def getBankMoney(self): + return self.bankMoney + + def tossPie(self, x, y, z, h, p, r, sequence, power, timestamp32): + if not self.validate( + self.doId, self.numPies > 0, + 'tossPie with no pies available'): + return + if self.numPies != ToontownGlobals.FullPies: + self.b_setNumPies(self.numPies - 1) + + + def b_setNumPies(self, numPies): + self.setNumPies(numPies) + self.d_setNumPies(numPies) + + def d_setNumPies(self, numPies): + self.sendUpdate('setNumPies', [numPies]) + + def setNumPies(self, numPies): + self.numPies = numPies + + def b_setPieType(self, pieType): + self.setPieType(pieType) + self.d_setPieType(pieType) + + def d_setPieType(self, pieType): + self.sendUpdate('setPieType', [pieType]) + + def setPieType(self, pieType): + self.pieType = pieType + + def d_setTrophyScore(self, score): + self.sendUpdate("setTrophyScore", [score]) + + + ### Auto toon-up functions ### + + def stopToonUp(self): + taskMgr.remove(self.uniqueName("safeZoneToonUp")) + self.ignore(self.air.getAvatarExitEvent(self.getDoId())) + + def startToonUp(self, healFrequency): + """ + Starts the auto-toon-up task that occurs whenever the avatar + is in a playground or in his estate. + """ + self.stopToonUp() + self.healFrequency = healFrequency + self.__waitForNextToonUp() + + def __waitForNextToonUp(self): + # Spawns a doLater for this av to be healed next time + taskMgr.doMethodLater(self.healFrequency, self.toonUpTask, + self.uniqueName("safeZoneToonUp")) + + def toonUpTask(self, task): + self.toonUp(1) + self.__waitForNextToonUp() + return Task.done + + def toonUp(self, hpGained, quietly = 0, sendTotal = 1): + # Adds the indicated hit points to the avatar's total. If + # quietly is 0 (the default), numbers will fly out of his + # head; if sendTotal is 1 (the default), the resulting hp + # value will be sent as well to ensure client and AI are in + # agreement. (Without sendTotal, the client will do the + # arithmetic himself, and presumably will still arrive at the + # same value.) + + # first clamp toonup to hp limit + if hpGained > self.maxHp: + hpGained = self.maxHp + + # First, send the message to make the numbers fly out. + if not quietly: + self.sendUpdate('toonUp', [hpGained]) + + # Then, we recompute the HP. + + # If hp is below zero, it means we're at a timeout in the + # playground, in which case we respect that it is below zero + # until we get our head above water. If our toonup would take + # us above zero, then we pretend we started at zero in the + # first place, ignoring the timeout. + if self.hp + hpGained <= 0: + self.hp += hpGained + else: + self.hp = max(self.hp, 0) + hpGained + + clampedHp = min(self.hp, self.maxHp) + if not self.hpOwnedByBattle: + self.hp = clampedHp + + # Finally, send the new total to the client so he's with us. + if sendTotal and not self.hpOwnedByBattle: + self.d_setHp(clampedHp) + + def isToonedUp(self): + return self.hp >= self.maxHp + + def makeBlackCat(self): + # turn this cat into a black cat + # are we a cat? + if self.dna.getAnimal() != 'cat': + return 'not a cat' + self.air.writeServerEvent('blackCat', self.doId, '') + # set the dna + newDna = ToonDNA.ToonDNA() + newDna.makeFromNetString(self.dna.makeNetString()) + black = 26 + newDna.updateToonProperties(armColor=black, legColor=black, + headColor=black) + self.b_setDNAString(newDna.makeNetString()) + # None means no error, return it explicitly + return None + + def b_announceBingo(self): + self.d_announceBingo() + self.announceBingo + + def d_announceBingo(self): + self.sendUpdate('announceBingo', []) + + def announceBingo(self): + pass + + def incrementPopulation(self): + if self.isPlayerControlled(): + DistributedPlayerAI.DistributedPlayerAI.incrementPopulation(self) + def decrementPopulation(self): + if self.isPlayerControlled(): + DistributedPlayerAI.DistributedPlayerAI.decrementPopulation(self) + + if __dev__: + def _logGarbage(self): + if self.isPlayerControlled(): + DistributedPlayerAI.DistributedPlayerAI._logGarbage(self) + + # stuff for resistance speedchat handling + def reqSCResistance(self, msgIndex, nearbyPlayers): + self.d_setSCResistance(msgIndex, nearbyPlayers) + + def d_setSCResistance(self, msgIndex, nearbyPlayers): + if not ResistanceChat.validateId(msgIndex): + self.air.writeServerEvent('suspicious', self.doId, 'said resistance %s, which is invalid.' % (msgIndex)) + return + + if not self.removeResistanceMessage(msgIndex): + self.air.writeServerEvent('suspicious', self.doId, 'said resistance %s, but does not have it.' % (msgIndex)) + return + + affectedPlayers = [] + for toonId in nearbyPlayers: + toon = self.air.doId2do.get(toonId) + if not toon: + self.notify.warning("%s said resistance %s for %s; not on server" % (self.doId, msgIndex, toonId)) + elif toon.__class__ != DistributedToonAI: + # We check for exact equivalence, not isinstance(), so + # we don't consider NPC's. + self.air.writeServerEvent('suspicious', self.doId, 'said resistance %s for %s; object of type %s' % (msgIndex, toonId, toon.__class__.__name__)) + elif toonId in affectedPlayers: + self.air.writeServerEvent('suspicious', self.doId, 'said resistance %s for %s twice in same message.' % (msgIndex, toonId)) + + else: + toon.doResistanceEffect(msgIndex) + affectedPlayers.append(toonId) + + if len(affectedPlayers) > 50: + self.air.writeServerEvent('suspicious', self.doId, 'said resistance %s for %s toons.' % (msgIndex, len(affectedPlayers))) + self.notify.warning('%s said resistance %s for %s toons: %s' % (self.doId, msgIndex, len(affectedPlayers), affectedPlayers)) + + + self.sendUpdate('setSCResistance', [msgIndex, affectedPlayers]) + + # log the use of a resistance chat phrase (speakerId, msgType, value, list of affectedToons) + type = ResistanceChat.getMenuName(msgIndex) + value = ResistanceChat.getItemValue(msgIndex) + self.air.writeServerEvent('resistanceChat', self.zoneId, + '%s|%s|%s|%s' % (self.doId, type, value, affectedPlayers) ) + + + def doResistanceEffect(self, msgIndex): + # Applies the effect of the indicated resistance chat message, + # as if someone has said it to me. + + msgType, itemIndex = ResistanceChat.decodeId(msgIndex) + msgValue = ResistanceChat.getItemValue(msgIndex) + + if msgType == ResistanceChat.RESISTANCE_TOONUP: + # Toon-up + if msgValue == -1: + self.toonUp(self.maxHp) + else: + self.toonUp(msgValue) + self.notify.debug("Toon-up for " + self.name) + elif msgType == ResistanceChat.RESISTANCE_RESTOCK: + # Restock + self.inventory.NPCMaxOutInv(msgValue) + self.d_setInventory(self.inventory.makeNetString()) + self.notify.debug("Restock for " + self.name) + elif msgType == ResistanceChat.RESISTANCE_MONEY: + # Rich + if msgValue == -1: + self.addMoney(999999) + else: + self.addMoney(msgValue) + self.notify.debug("Money for " + self.name) + + def squish(self, damage): + self.takeDamage(damage) + + if( simbase.wantKarts ): + ################################################################## + # Kart DNA Methods + ################################################################## + def hasKart( self ): + """ + Purpose: The hasKart Method determines whether the Toon + currently owns a kart. + + Params: None + Return: Bool - True or False + """ + return ( self.kartDNA[ KartDNA.bodyType ] != -1 ) + + def b_setTickets( self, numTickets ): + """ + Purpose: The b_setTickets Method sets the number of + tickets that the toon has by calling local and + distributed set methods. + + Params: numTickets - the new number of tickets that a toon has. + Return: None + """ + if numTickets > RaceGlobals.MaxTickets: + numTickets = RaceGlobals.MaxTickets + self.d_setTickets( numTickets ) + self.setTickets( numTickets ) + + def d_setTickets( self, numTickets ): + """ + Purpose: The d_setTickets Method sets the number of + tickets that the toon has by sending a distributed + message to the client. + + Params: numTickets - the number of tickets that a toon has. + Return: None + """ + if numTickets > RaceGlobals.MaxTickets: + numTickets = RaceGlobals.MaxTickets + self.sendUpdate( "setTickets", [ numTickets ] ) + + def setTickets( self, numTickets ): + """ + Purpose: The setTickets Method sets the number of + tickets that the toon has. Tickets are gained by + winning races and events. + + Params: numTickets - the new number of tickets that a toon has. + Return: None + """ + if numTickets > RaceGlobals.MaxTickets: + numTickets = RaceGlobals.MaxTickets + self.tickets = numTickets + + def getTickets( self ): + """ + Purpose: The getTickets Method obtains the number of + tickets that a toon can has. + + Params: None + Return: tickets - the number of tickets a toon has. + """ + return self.tickets + + ### karting trophy list ### + + def b_setKartingTrophies(self, trophyList): + # update the trophies won list + self.setKartingTrophies(trophyList) + self.d_setKartingTrophies(trophyList) + + def setKartingTrophies(self, trophyList): + self.notify.debug("setting kartingTrophies to %s" % trophyList) + self.kartingTrophies = trophyList + + def d_setKartingTrophies(self, trophyList): + self.sendUpdate("setKartingTrophies", [trophyList]) + + def getKartingTrophies(self): + return self.kartingTrophies + + ### karting history list ### + + def b_setKartingHistory(self, history): + # update the trophies won list + self.setKartingHistory(history) + self.d_setKartingHistory(history) + + def setKartingHistory(self, history): + self.notify.debug("setting kartingHistory to %s" % history) + self.kartingHistory = history + + def d_setKartingHistory(self, history): + self.sendUpdate("setKartingHistory", [history]) + + def getKartingHistory(self): + return self.kartingHistory + + ### karting personal best list ### + + def b_setKartingPersonalBest(self, bestTimes): + # update the personal best times list + best1 = bestTimes[0:6] + best2 = bestTimes[6:] + self.setKartingPersonalBest(best1) + self.setKartingPersonalBest2(best2) + self.d_setKartingPersonalBest(bestTimes) + + def d_setKartingPersonalBest(self, bestTimes): + best1 = bestTimes[0:6] + best2 = bestTimes[6:] + self.sendUpdate("setKartingPersonalBest", [best1]) + self.sendUpdate("setKartingPersonalBest2", [best2]) + + def setKartingPersonalBest(self, bestTimes): + self.notify.debug("setting karting to %s" % bestTimes) + self.kartingPersonalBest = bestTimes + + def setKartingPersonalBest2(self, bestTimes2): + self.notify.debug("setting karting2 to %s" % bestTimes2) + self.kartingPersonalBest2 = bestTimes2 + + def getKartingPersonalBest(self): + return self.kartingPersonalBest + + def getKartingPersonalBest2(self): + return self.kartingPersonalBest2 + + def getKartingPersonalBestAll(self): + return self.kartingPersonalBest + \ + self.kartingPersonalBest2 + + def setKartDNA( self, kartDNA ): + """ + Purpose: The setKartDNA Method provides the opportunity to + fill out kart DNA in collective way. + + Params: kartDNA - the kart dna as a list + Return: None + """ + self.b_setKartBodyType( kartDNA[ KartDNA.bodyType ] ) + self.b_setKartBodyColor( kartDNA[ KartDNA.bodyColor ] ) + self.b_setKartAccColor( kartDNA[ KartDNA.accColor ] ) + self.b_setKartEngineBlockType( kartDNA[ KartDNA.ebType ] ) + self.b_setKartSpoilerType( kartDNA[ KartDNA.spType ] ) + self.b_setKartFrontWheelWellType( kartDNA[ KartDNA.fwwType ] ) + self.b_setKartBackWheelWellType( kartDNA[ KartDNA.bwwType ] ) + self.b_setKartRimType( kartDNA[ KartDNA.rimsType ] ) + self.b_setKartDecalType( kartDNA[ KartDNA.decalType ] ) + + def b_setKartBodyType( self, bodyType ): + """ + Purpose: The b_setKartBodyType Method handles the setting of the + kart body type by appropriately calling the local AI and + distributed client set methods. + + Params: bodyType - the body type of the kart which the toon + currently owns. + Return: None + """ + self.d_setKartBodyType( bodyType ) + self.setKartBodyType( bodyType ) + + def d_setKartBodyType( self, bodyType ): + """ + Purpose: The d_setKartBodyType Method handles the distributed + client call to update the body type on the client for + the toon. + + Params: bodyType - the body type of the kart which the toon + currently owns. + Return: None + """ + self.sendUpdate( 'setKartBodyType', [ bodyType ] ) + + def setKartBodyType( self, bodyType ): + """ + Purpose: The setKartBodyType Method sets the local AI side + body type of the kart that the toon currently owns. + + + Params: bodyType - the body type of the kart which the toon + currently owns. + Return: None + """ + self.kartDNA[ KartDNA.bodyType ] = bodyType + + def getKartBodyType( self ): + """ + Purpose: The getKartBodyType Method obtains the local AI side + body type of the kart that the toon currently owns. + + Params: None + Return: bodyType - the body type of the kart. + """ + return self.kartDNA[ KartDNA.bodyType ] + + def b_setKartBodyColor( self, bodyColor ): + """ + Purpose: The b_setKartBodyColor Method appropriately sets the + body color the kart by calling the local and distributed + set methods. + + Params: bodyColor - the color of the kart body. + Return: None + """ + self.d_setKartBodyColor( bodyColor ) + self.setKartBodyColor( bodyColor ) + + def d_setKartBodyColor( self, bodyColor ): + """ + Purpose: The d_setKartBodyColor Method appropriately sets the + body color of the kart on the client side by sending a + distributed update message to the client. + + Params: bodyColor - the color of the kart body. + Return: None + """ + self.sendUpdate( 'setKartBodyColor', [ bodyColor ] ) + + def setKartBodyColor( self, bodyColor ): + """ + Purpose: The setKartBodyColor Method appropriately sets + the body color of the lient on the ai side by updating the + local kart dna. + + Params: bodyColor - the color of the kart body. + Return: None + """ + self.kartDNA[ KartDNA.bodyColor ] = bodyColor + + def getKartBodyColor( self ): + """ + Purpose: The getKartBodyColor Method obtains the current + body color of the kart. + + Params: None + Return: bodyColor - the color of the kart body. + """ + return self.kartDNA[ KartDNA.bodyColor ] + + def b_setKartAccessoryColor( self, accColor ): + """ + Purpose: The b_setKartAccessoryColor Method appropriately + sets the accessory color by calling the local and distributed + set methods. + + Params: accColor - the color of the accessories. + Return: None + """ + self.d_setKartAccessoryColor( accColor ) + self.setKartAccessoryColor( accColor ) + + def d_setKartAccessoryColor( self, accColor ): + """ + Purpose: The d_setKartAccessoryColor Method appropriately sets + the accessory color of the client by sending a distributed + message to the client. + + Params: accColor - the Color of the accessories. + Return: None + """ + self.sendUpdate( 'setKartAccessoryColor', [ accColor ] ) + + def setKartAccessoryColor( self, accColor ): + """ + Purpose: The setKartAccessoryColor Method appropriately sets + the accessory color of the local ai side by updating the kart + dna. + + Params: accColor - the color of the accessories. + Return: None + """ + self.kartDNA[ KartDNA.accColor ] = accColor + + def getKartAccessoryColor( self ): + """ + Purpose: The getKartAccessoryColor Method obtains the + accessory color for the kart. + + Params: None + Return: accColor - the color of the accessories + """ + return self.kartDNA[ KartDNA.accColor ] + + def b_setKartEngineBlockType( self, ebType ): + """ + Purpose: The b_setKartEngineBlockType Method sets the engine + block type of accessory for the kart by calling the local + and distributed set methods. + + Params: ebType - the type of engine block accessory. + Return: None + """ + self.d_setKartEngineBlockType( ebType ) + self.setKartEngineBlockType( ebType ) + + def d_setKartEngineBlockType( self, ebType ): + """ + Purpose: The d_setKartEngineBlockType Method sets the engine + block type accessory for the kart by sending a distributed + message to the client. + + Params: ebType - the type of engine block accessory. + Return: None + """ + self.sendUpdate( "setKartEngineBlockType", [ ebType ] ) + + def setKartEngineBlockType( self, ebType ): + """ + Purpose: The setKartEngineBlockType Method sets the engine + block type accessory for the kart by updating the Kart DNA. + + Params: ebType - the type of engine block accessory. + Return: None + """ + self.kartDNA[ KartDNA.ebType ] = ebType + + def getKartEngineBlockType( self ): + """ + Purpose: The getKartEngineBlockType Method obtains the engine + block type accessory for the kart by accessing the + current Kart DNA. + + Params: None + Return: ebType - the type of engine block accessory. + """ + return self.kartDNA[ KartDNA.ebType ] + + def b_setKartSpoilerType( self, spType ): + """ + Purpose: The b_setKartSpoilerType Method sets the spoiler + type accessory for the kart by calling the local and + distributed set methods. + + Params: spType - the type of spoiler accessory + Return: None + """ + self.d_setKartSpoilerType( spType ) + self.setKartSpoilerType( spType ) + + def d_setKartSpoilerType( self, spType ): + """ + Purpose: The d_setKartSpoilerType Method sets the spoiler + type accessory for the kart by sending a distributed + message to the client. + + Params: spType - the type of spoiler accessory + Return: None + """ + self.sendUpdate( "setKartSpoilerType", [ spType ] ) + + def setKartSpoilerType( self, spType ): + """ + Purpose: The setKartSpoilerType Method sets the spoiler + type accessory for the kart by updating the Kart DNA. + + Params: spType - the type of spoiler accessory + Return: None + """ + self.kartDNA[ KartDNA.spType ] = spType + + def getKartSpoilerType( self ): + """ + Purpose: The getKartSpoilerType Method obtains the spoiler + type accessory for the kart by accessing the current Kart DNA. + + Params: None + Return: spType - the type of spoiler accessory + """ + return self.kartDNA[ KartDNA.spType ] + + def b_setKartFrontWheelWellType( self, fwwType ): + """ + Purpose: The b_setKartFrontWheelWellType Method sets the + front wheel well accessory for the kart by calling the local + and distributed set methods for the DNA. + + Params: fwwType - the type of Front Wheel Well accessory + Return: None + """ + self.d_setKartFrontWheelWellType( fwwType ) + self.setKartFrontWheelWellType( fwwType ) + + def d_setKartFrontWheelWellType( self, fwwType ): + """ + Purpose: The d_setKartFrontWheelWellType Method sets the + front wheel well accessory for the kart by sending a + distributed message to the client to update the DNA. + + Params: fwwType - the type of Front Wheel Well accessory + Return: None + """ + self.sendUpdate( "setKartFrontWheelWellType", [ fwwType ] ) + + def setKartFrontWheelWellType( self, fwwType ): + """ + Purpose: The setKartFrontWheelWellType Method sets the + front wheel well accessory for the kart updating the + Kart DNA. + + Params: fwwType - the type of Front Wheel Well accessory + Return: None + """ + self.kartDNA[ KartDNA.fwwType ] = fwwType + + def getKartFrontWheelWellType( self ): + """ + Purpose: The getKartFrontWheelWellType Method obtains the + front wheel well accessory for the kart accessing the + Kart DNA. + + Params: None + Return: fwwType - the type of Front Wheel Well accessory + """ + return self.kartDNA[ KartDNA.fwwType ] + + def b_setKartBackWheelWellType( self, bwwType ): + """ + Purpose: The b_setKartWheelWellType Method sets the Back + Wheel Wheel accessory for the kart by calling the local + and distributed set methods. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + self.d_setKartBackWheelWellType( bwwType ) + self.setKartBackWheelWellType( bwwType ) + + def d_setKartBackWheelWellType( self, bwwType ): + """ + Purpose: The b_setKartWheelWellType Method sets the Back + Wheel Wheel accessory for the kart by sending a distributed + message to the client. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + self.sendUpdate( "setKartBackWheelWellType", [ bwwType ] ) + + def setKartBackWheelWellType( self, bwwType ): + """ + Purpose: The setKartWheelWellType Method sets the Back + Wheel Wheel accessory for the kart by updating the Kart DNA. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + self.kartDNA[ KartDNA.bwwType ] = bwwType + + def getKartBackWheelWellType( self ): + """ + Purpose: The getKartWheelWellType Method obtains the Back + Wheel Wheel accessory for the kart by accessing the Kart DNA. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + return self.kartDNA[ KartDNA.bwwType ] + + def b_setKartRimType( self, rimsType ): + """ + Purpose: The b_setKartRimType Method sets the rims accessory + for the karts tires by calling the local and distributed + set methods. + + Params: rimsType - the type of rims for the kart tires. + Return: None + """ + self.d_setKartRimType( rimsType ) + self.setKartRimType( rimsType ) + + def d_setKartRimType( self, rimsType ): + """ + Purpose: The d_setKartRimType Method sets the rims accessory + for the karts tires by sending a distributed message to the + client for an update. + + Params: rimsType - the type of rims for the kart tires. + Return: None + """ + self.sendUpdate( "setKartRimType", [ rimsType ] ) + + def setKartRimType( self, rimsType ): + """ + Purpose: The setKartRimType Method sets the rims accessory + for the karts tires by updating the Kart DNA. + + Params: rimsType - the type of rims for the kart tires. + Return: None + """ + self.kartDNA[ KartDNA.rimsType ] = rimsType + + def getKartRimType( self ): + """ + Purpose: The setKartRimType Method sets the rims accessory + for the karts tires by accessing the Kart DNA. + + Params: None + Return: rimsType - the type of rims for the kart tires. + """ + return self.kartDNA[ KartDNA.rimsType ] + + def b_setKartDecalType( self, decalType ): + """ + Purpose: The b_setKartDecalType Method sets the decal + accessory of the kart by calling local and distributed + set methods. + + Params: decalType - the type of decal set for the kart. + Return: None + """ + self.d_setKartDecalType( decalType ) + self.setKartDecalType( decalType ) + + def d_setKartDecalType( self, decalType ): + """ + Purpose: The d_setKartDecalType Method sets the decal + accessory of the kart by sending a distributed message to the + client to update the DNA. + + Params: decalType - the type of decal set for the kart. + Return: None + """ + self.sendUpdate( "setKartDecalType", [ decalType ] ) + + def setKartDecalType( self, decalType ): + """ + Purpose: The setKartDecalType Method sets the decal + accessory of the kart by updating the Kart DNA. + + Params: decalType - the type of decal set for the kart. + Return: None + """ + self.kartDNA[ KartDNA.decalType ] = decalType + + def getKartDecalType( self ): + """ + Purpose: The getKartDecalType Method obtains the decal + accessory of the kart by accessing the Kart DNA. + + Params: None + Return: decalType - the type of decal set for the kart. + """ + return self.kartDNA[ KartDNA.decalType ] + + def b_setKartAccessoriesOwned( self, accessories ): + """ + Purpose: The setKartAccessoriesOwned Method handles the + distributed and local calls to update the Kart Accessories + owned by the toon on both the client and AI side. + + Params: accessories - The accessories owned. + Return: None + """ + self.d_setKartAccessoriesOwned( accessories ) + self.setKartAccessoriesOwned( accessories ) + + def d_setKartAccessoriesOwned( self, accessories ): + """ + Purpose: The d_setKartAccessoriesOwned Method handles the + distributed call to update the Kart Accessories owned by the + Toon on the AI Side. + + Params: accessories - The accessories owned. + Return: None + """ + self.sendUpdate( 'setKartAccessoriesOwned', [ accessories ] ) + + def setKartAccessoriesOwned( self, accessories ): + """ + Purpose: The setKartAccessoriesOwned Method handles the local + call to properly set the Accessories owned by the toon on the + AI Side. + + Params: accessories - the ids of the accessories. + Return: None + """ + if( __debug__ ): + import pdb + #pdb.set_trace() + + self.accessories = accessories + + def getKartAccessoriesOwned( self ): + """ + Purpose: The getKartAccessoriesOwned Method retrieves the + accessories that are owned by the toon on the AI Side. + + Params: None + Return: [] - List of Accessories owned by the toon. + """ + owned = copy.deepcopy(self.accessories) + while InvalidEntry in owned: + owned.remove(InvalidEntry) + return owned + + def addOwnedAccessory( self, accessoryId ): + """ + Purpose: The addOwnedAccessory Method performs the update + on the accessories owned by the toon. It also provides a + the appropriate checks on whether the accessory is valid. + + Params: accessoryId - the id of the accessory. + Return: None + """ + print "in add owned accessory" + if( AccessoryDict.has_key( accessoryId ) ): + # Determine if the toon already owns this accessory. + if( self.accessories.count( accessoryId ) > 0 ): + self.air.writeServerEvent( "suspicious", self.doId, 'attempt to add accessory %s which is already owned!' % ( accessoryId ) ) + return + + # Determine if the toon owns too many accessories. + if( self.accessories.count( InvalidEntry ) > 0 ): + accList = list( self.accessories ) + index = self.accessories.index( InvalidEntry ) + accList[ index ] = accessoryId + + # set the accessory list + self.b_setKartAccessoriesOwned( accList ) + else: + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to add accessory %s when accessory inventory is full!' % ( accessoryId )) + return + else: + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to add accessory %s which is not a valid accessory.' % ( accessoryId ) ) + return + + def removeOwnedAccessory( self, accessoryId ): + """ + Purpose: The removeOwnedAccessory Method performs an update + on the accessories owned by the toon. It also provides the + appropriate checks to determine whether the accessory to be + deleted is removed. + + Params: accessoryId - the id of the accessory. + Return: None + """ + if( AccessoryDict.has_key( accessoryId ) ): + # Make certain tha the toon owns this accessory. + if( self.accessories.count( accessoryId ) == 0 ): + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to remove accessory %s which is not currently owned!' % ( accessoryId ) ) + return + else: + # TODO - do not allow removal of last set of rims. + accList = list( self.accessories ) + index = self.accessories.index( accessoryId ) + accList[ index ] = InvalidEntry + + # log for CS verification + self.air.writeServerEvent( 'deletedKartingAccessory', self.doId, '%s' % ( accessoryId ) ) + + # set the accessory owned list + self.b_setKartAccessoriesOwned( accList ) + else: + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to remove accessory %s which is not a valid accessory.' % ( accessoryId ) ) + return + + + def updateKartDNAField( self, dnaField, fieldValue ): + """ + Purpose: The udpateKartDNAField Method performs the + the update on an accessory based on a client request. + + Params: accessoryType - the kind of accessory to update + accessoryId - the new accessory id. + Return: None + """ + + # Determine if the field is a valid. + if( not checkKartFieldValidity( dnaField ) ): + # Validity Check Failed - log as suspicious + self.air.writeServerEvent('suspicious', self.doId, 'attempt to update to dna value %s in the invalid field %s' % (fieldValue, dnaField)) + return + + # Check if it is a kart body type or what can be considered + # an accessory. + if( dnaField == KartDNA.bodyType ): + if( ( fieldValue not in KartDict.keys() ) and ( fieldValue != InvalidEntry ) ): + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update kart body to invalid body %s.' % ( fieldValue ) ) + return + self.b_setKartBodyType( fieldValue ) + else: + # First check non-paint related accessories + accFields = [ KartDNA.ebType, KartDNA.spType, KartDNA.fwwType, + KartDNA.bwwType, KartDNA.rimsType, KartDNA.decalType ] + colorFields = [ KartDNA.bodyColor, KartDNA.accColor ] + + if( dnaField in accFields ): + if( fieldValue == InvalidEntry ): + # Invalid entries mean that the kart no longer + # has a current accessory of this type. + self.__updateKartDNAField( dnaField, fieldValue ) + else: + # Check to make sure the accessory is owned by the + # toon. + if( fieldValue not in self.accessories ): + # There has been an illegal attempt to update + # to an accessory that is not currently owned + # by the toon. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update to accessory %s which is not currently owned.' % ( fieldValue ) ) + return + + field = getAccessoryType( fieldValue ) + if( field == InvalidEntry ): + # There has been an illegal attempt to update + # an accessory in an invalid field. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update accessory %s in an illegal field %s' % ( fieldValue, field ) ) + return + elif( field != dnaField ): + # There has been an illegal attempt to update + # an accessory field that is not the same as + # the field specified from the client. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update accessory %s in a field %s that does not match client specified field %s' % ( fieldValue, field, dnaField ) ) + return + else: + pass + + # Passed validity checks, now update the field + # value for the specified dna field. + self.__updateKartDNAField( dnaField, fieldValue ) + + elif( dnaField in colorFields ): + # Check if the field is invalid. + if( fieldValue == InvalidEntry ): + # Invalid entries mean that the kart no longer + # has a current color. + self.__updateKartDNAField( dnaField, fieldValue ) + else: + # Determine if the accessory is currently owned by + # the toon. + if( fieldValue not in self.accessories ): + if( fieldValue != getDefaultColor() ): + # An attempt to update to a color that is not + # currently owned has been made. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update to color %s which is not owned!' % ( fieldValue ) ) + return + elif( ( fieldValue == getDefaultColor() ) and ( self.kartDNA[ dnaField ] != InvalidEntry ) ): + # An attempt to update the color to the + # default color when the dna Field is not + # invalid. The color is not owned, thus + # the toon should not be able to paint. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update to default color %s which is not owned!' % ( fieldValue ) ) + return + + # Make certain the value is truly a color and not of + # another accessory type. + # + # NOTE: All colors are listed under KartDNA.bodyColor + # in the AccessoryTypeDict. + if( getAccessoryType( fieldValue ) != KartDNA.bodyColor ): + # The accessory type does not match. + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to update invalid color %s for dna field %s' % ( fieldValue, dnaField ) ) + return + + # All checks should have passed at this point, now + # update the color. + self.__updateKartDNAField( dnaField, fieldValue ) + + else: + # The specified dna field is not valid! + self.air.writeServerEvent( 'suspicious', self.doId, 'attempt to udpate accessory %s in the invalid field %s' %( fieldValue, dnaField ) ) + return + + def __updateKartDNAField( self, dnaField, fieldValue ): + """ + """ + if( dnaField == KartDNA.bodyColor ): + self.b_setKartBodyColor( fieldValue ) + elif( dnaField == KartDNA.accColor ): + self.b_setKartAccessoryColor( fieldValue ) + elif( dnaField == KartDNA.ebType ): + self.b_setKartEngineBlockType( fieldValue ) + elif( dnaField == KartDNA.spType ): + self.b_setKartSpoilerType( fieldValue ) + elif( dnaField == KartDNA.fwwType ): + self.b_setKartFrontWheelWellType( fieldValue ) + elif( dnaField == KartDNA.bwwType ): + self.b_setKartBackWheelWellType( fieldValue ) + elif( dnaField == KartDNA.rimsType ): + self.b_setKartRimType( fieldValue ) + elif( dnaField == KartDNA.decalType ): + self.b_setKartDecalType( fieldValue ) + else: + pass + + def setAllowSoloRace(self, allowSoloRace): + self.allowSoloRace = allowSoloRace + + def setAllowRaceTimeout(self, allowRaceTimeout): + self.allowRaceTimeout = allowRaceTimeout + + if simbase.wantPets: + # PETS + def getPetId(self): + return self.petId + def b_setPetId(self, petId): + self.d_setPetId(petId) + self.setPetId(petId) + def d_setPetId(self, petId): + self.sendUpdate('setPetId', [petId]) + def setPetId(self, petId): + self.petId = petId + + # list of tricks that we can train pets in + # these are trick IDs, not SpeedChat phrases + def getPetTrickPhrases(self): + return self.petTrickPhrases + def b_setPetTrickPhrases(self, tricks): + self.setPetTrickPhrases(tricks) + self.d_setPetTrickPhrases(tricks) + def d_setPetTrickPhrases(self, tricks): + self.sendUpdate('setPetTrickPhrases', [tricks]) + def setPetTrickPhrases(self, tricks): + self.petTrickPhrases = tricks + + def deletePet(self): + if self.petId == 0: + self.notify.warning("this toon doesn't have a pet to delete!") + return + + simbase.air.petMgr.deleteToonsPet(self.doId) + + def setPetMovie(self, petId, flag): + self.notify.debug("setPetMovie: petId: %s, flag: %s" % (petId, flag)) + pet = simbase.air.doId2do.get(petId) + if pet is not None: + if pet.__class__.__name__ == 'DistributedPetAI': + pet.handleAvPetInteraction(flag, self.getDoId()) + else: + self.air.writeServerEvent( + 'suspicious', self.doId, + 'setPetMovie: playing pet movie %s on non-pet object %s' % ( + flag, petId)) + + def setPetTutorialDone(self, bDone): + #don't actually USE the boolean... it's just there to tell the db what to store + self.notify.debug("setPetTutorialDone") + self.bPetTutorialDone = True + + def setFishBingoTutorialDone(self, bDone): + #don't actually USE the boolean... it's just there to tell the db what to store + self.notify.debug("setFishBingoTutorialDone") + self.bFishBingoTutorialDone = True + + def setFishBingoMarkTutorialDone(self, bDone): + #don't actually USE the boolean... it's just there to tell the db what to store + self.notify.debug("setFishBingoMarkTutorialDone") + self.bFishBingoMarkTutorialDone = True + + def enterEstate(self, ownerId, zoneId): + DistributedToonAI.notify.debug('enterEstate: %s %s %s' % ( + self.doId, ownerId, zoneId)) + # we should be in the correct zone at this point + assert self.zoneId == zoneId + if self.wasInEstate(): + self.cleanupEstateData() + + # create a collision sphere for the pets to collide with + collSphere = CollisionSphere(0,0,0,self.getRadius()) + collNode = CollisionNode('toonColl-%s' % self.doId) + collNode.addSolid(collSphere) + collNode.setFromCollideMask(BitMask32.allOff()) + collNode.setIntoCollideMask(ToontownGlobals.WallBitmask) + self.collNodePath = self.attachNewNode(collNode) + # start a task to position the collision sphere at Z=0 + # wrt to render + # this must run after the sphere is moved (distributed updates) + # and before pet collisions are run + # This might be more efficient as a single task that moves + # all spheres to zero in a single pass + taskMgr.add(self._moveSphere, self._getMoveSphereTaskName(), + priority = OTPGlobals.AICollMovePriority) + self.inEstate = 1 + self.estateOwnerId = ownerId + self.estateZones = simbase.air.estateMgr.getEstateZones(ownerId) + self.estateHouseZones = simbase.air.estateMgr.getEstateHouseZones( + ownerId) + + self.enterPetLook() + + def _getPetLookerBodyNode(self): + return self.collNodePath + + def _getMoveSphereTaskName(self): + return 'moveSphere-%s' % self.doId + + def _moveSphere(self, task): + self.collNodePath.setZ(self.getRender(), 0) + return Task.cont + + def isInEstate(self): + return hasattr(self, 'inEstate') and self.inEstate + + def exitEstate(self, ownerId=None, zoneId=None): + DistributedToonAI.notify.debug('exitEstate: %s %s %s' % ( + self.doId, ownerId, zoneId)) + DistributedToonAI.notify.debug('current zone: %s' % ( + self.zoneId)) + + assert self.isInEstate(), "already exitted estate" + + self.exitPetLook() + + taskMgr.remove(self._getMoveSphereTaskName()) + # remove the collision sphere + self.collNodePath.removeNode() + del self.collNodePath + del self.estateOwnerId + del self.estateHouseZones + del self.inEstate + self._wasInEstate = 1 + + def wasInEstate(self): + return hasattr(self, '_wasInEstate') and self._wasInEstate + def cleanupEstateData(self): + del self.estateZones + del self._wasInEstate + + def setSC(self, msgId): + DistributedToonAI.notify.debug('setSC: %s' % msgId) + from toontown.pets import PetObserve + PetObserve.send(self.zoneId, + PetObserve.getSCObserve(msgId, self.doId)) + # if the toon uses the pet message "Please don't bother me" pets can see when they are busy + # positive messages towards the pet disable this + if msgId in [21006]: + self.setHatePets(1) + elif msgId in [21000, 21001, 21003, 21004, 21200, 21201, 21202, 21203, 21204, 21205, 21206]: + self.setHatePets(0) + else: + pass + + def setSCCustom(self, msgId): + DistributedToonAI.notify.debug('setSCCustom: %s' % msgId) + from toontown.pets import PetObserve + PetObserve.send(self.zoneId, + PetObserve.getSCObserve(msgId, self.doId)) + + def setHatePets(self, hate): + self.hatePets = hate + + def takeOutKart(self,zoneId=None): + if not self.kart: + from toontown.racing import DistributedVehicleAI + self.kart = DistributedVehicleAI.DistributedVehicleAI(self.air, self.doId) + if(zoneId): + self.kart.generateWithRequired(zoneId) + else: + self.kart.generateWithRequired(self.zoneId) + self.kart.start() + #self.addDistObj(self.kart) + #self.kart.request("Controlled", self.doId) + + ################################################################# + # Cog Summoning Methods + ################################################################# + def reqCogSummons(self, type, suitIndex): + # RAU moved hasCogSummon and welcome valley checks before doing + # the summons, otherwise invasion would still happen + # even if he didn't have an invasion summon + + if type not in ('single', 'building', 'invasion', ): + self.air.writeServerEvent('suspicious', self.doId, 'invalid cog summons type: %s' % type) + self.sendUpdate('cogSummonsResponse', ['fail', suitIndex, 0]) + return + + if suitIndex >= len(SuitDNA.suitHeadTypes): + self.air.writeServerEvent('suspicious', self.doId, 'invalid suitIndex: %s' % suitIndex) + self.sendUpdate('cogSummonsResponse', ['fail', suitIndex, 0]) + return + + # verify that this is a legitimate summons + if not self.hasCogSummons(suitIndex, type): + self.air.writeServerEvent('suspicious', self.doId, 'bogus cog summons') + self.sendUpdate('cogSummonsResponse', ['fail', suitIndex, 0]) + return + + # make sure we are not on a welcome valley + if ZoneUtil.isWelcomeValley(self.zoneId): + self.sendUpdate('cogSummonsResponse', ['fail', suitIndex, 0]) + return + + returnCode = None + if type == 'single': + returnCode = self.doSummonSingleCog(suitIndex) + elif type == 'building': + returnCode = self.doBuildingTakeover(suitIndex) + elif type == 'invasion': + returnCode = self.doCogInvasion(suitIndex) + + + if returnCode: + # make sure to 'use' the toon's charge + if returnCode[0] == "success": + self.air.writeServerEvent('cogSummoned', self.doId, "%s|%s|%s" % ( + type, suitIndex, self.zoneId)) + self.removeCogSummonsEarned(suitIndex, type) + + self.sendUpdate('cogSummonsResponse', returnCode) + + def doSummonSingleCog(self, suitIndex): + + # some debugging to address AI crashes + if suitIndex >= len(SuitDNA.suitHeadTypes): + self.notify.warning("Bad suit index: %s" % (suitIndex)) + return ['badIndex', suitIndex, 0] + + suitName = SuitDNA.suitHeadTypes[suitIndex] + + # get street points and the street's suitplanner + streetId = ZoneUtil.getBranchZone(self.zoneId) + if not self.air.suitPlanners.has_key(streetId): + return ["badlocation", suitIndex, 0] + + sp = self.air.suitPlanners[streetId] + map = sp.getZoneIdToPointMap() + + # check nearby zones as well + zones = [self.zoneId, self.zoneId-1, self.zoneId+1] + + # are there any points in this zone? + for zoneId in zones: + if map.has_key(zoneId): + points = map[zoneId][:] + + # create the suit + suit = sp.createNewSuit([], points, + suitName = suitName) + if suit: + return ['success', suitIndex, 0] + + return ['badlocation', suitIndex, 0] + + + def doBuildingTakeover(self, suitIndex): + streetId = ZoneUtil.getBranchZone(self.zoneId) + if not self.air.suitPlanners.has_key(streetId): + self.notify.warning("Street %d is not known." % (streetId)) + return ["badlocation", suitIndex, 0] + + sp = self.air.suitPlanners[streetId] + bm = sp.buildingMgr + + # try to figure out which door we're standing near + building = self.findClosestDoor() + + if building == None: + return ["badlocation", suitIndex, 0] + + level = None + + # some debugging to address AI crashes + if suitIndex >= len(SuitDNA.suitHeadTypes): + self.notify.warning("Bad suit index: %s" % (suitIndex)) + return ['badIndex', suitIndex, 0] + + # determine the track and level to use + suitName = SuitDNA.suitHeadTypes[suitIndex] + track = SuitDNA.getSuitDept(suitName) + type = SuitDNA.getSuitType(suitName) + + # take over the building immediately. + level, type, track = \ + sp.pickLevelTypeAndTrack(None, type, track) + building.suitTakeOver(track, level, None) + + self.notify.warning("cogTakeOver %s %s %d %d" % + (track, level, building.block, self.zoneId)) + + return ["success", suitIndex, building.doId] + + def doCogInvasion(self, suitIndex): + invMgr = self.air.suitInvasionManager + + if invMgr.getInvading(): + returnCode = 'busy' + else: + # some debugging to address AI crashes + if suitIndex >= len(SuitDNA.suitHeadTypes): + self.notify.warning("Bad suit index: %s" % (suitIndex)) + return ['badIndex', suitIndex, 0] + + cogType = SuitDNA.suitHeadTypes[suitIndex] + numCogs = 1000 + if invMgr.startInvasion(cogType, numCogs, False): + returnCode = 'success' + else: + returnCode = 'fail' + + return [returnCode, suitIndex, 0] + + # Control the list of earned cog summons + def b_setCogSummonsEarned(self, cogSummonsEarned): + self.d_setCogSummonsEarned(cogSummonsEarned) + self.setCogSummonsEarned(cogSummonsEarned) + + def d_setCogSummonsEarned(self, cogSummonsEarned): + self.sendUpdate('setCogSummonsEarned', [cogSummonsEarned]) + + def setCogSummonsEarned(self, cogSummonsEarned): + self.cogSummonsEarned = cogSummonsEarned + + def getCogSummonsEarned(self): + return self.cogSummonsEarned + + def addCogSummonsEarned(self, suitIndex, type): + summons = self.getCogSummonsEarned() + curSetting = summons[suitIndex] + + if type == "single": + curSetting |= 0x01 + elif type == "building": + curSetting |= 0x02 + elif type == "invasion": + curSetting |= 0x04 + + summons[suitIndex] = curSetting + self.b_setCogSummonsEarned(summons) + + def removeCogSummonsEarned(self, suitIndex, type): + # Removes the indicated cog summons from the Toon's + # inventory. Returns true if it was there in the first place, + # false if it was not. + + summons = self.getCogSummonsEarned() + curSetting = summons[suitIndex] + + if self.hasCogSummons(suitIndex, type): + if type == "single": + curSetting &= ~0x01 + elif type == "building": + curSetting &= ~0x02 + elif type == "invasion": + curSetting &= ~0x04 + + summons[suitIndex] = curSetting + self.b_setCogSummonsEarned(summons) + + self.notify.warning("Toon %s doesn't have a %s summons for %s" % (self.doId, type, suitIndex)) + return False + + def hasCogSummons(self, suitIndex, type = None): + summons = self.getCogSummonsEarned() + curSetting = summons[suitIndex] + + if type == "single": + return curSetting & 0x01 + elif type == "building": + return curSetting & 0x02 + elif type == "invasion": + return curSetting & 0x04 + + # just check to see if the toon has *any* for this suit + return curSetting + + def hasParticularCogSummons(self, deptIndex, level, type ): + """ + checks if this toon has this particular summon already + deptIndex should be from 0-3 + level should be from 0-7 + type can be 'single','building','invasion' + """ + if not deptIndex in range(len(SuitDNA.suitDepts)): + self.notify.warning('invalid parameter deptIndex %s' % deptIndex) + return False + + if not level in range(SuitDNA.suitsPerDept): + self.notify.warning('invalid parameter level %s' % level) + return False + + suitIndex = deptIndex * SuitDNA.suitsPerDept + level + retval = self.hasCogSummons( suitIndex, type) + return retval + + + def assignNewCogSummons(self, level = None, summonType = None, deptIndex = None): + if level != None: + if deptIndex in range(len(SuitDNA.suitDepts)): + #set level and set dept + dept = deptIndex + else: + #set level and random dept + numDepts = len(SuitDNA.suitDepts) + dept = random.randrange(0, numDepts) + + suitIndex = dept * SuitDNA.suitsPerDept + level + else: + if deptIndex in range(len(SuitDNA.suitDepts)): + #random level and set dept + randomLevel = random.randrange(0, SuitDNA.suitsPerDept) + suitIndex = deptIndex * SuitDNA.suitsPerLevel + randomLevel + else: + #random level and random dept + numSuits = len(SuitDNA.suitHeadTypes) + suitIndex = random.randrange(0, numSuits) + + if summonType in ['single','building','invasion']: + type = summonType + else: + typeWeights = ['single'] * 70 + \ + ['building'] * 25 + \ + ['invasion'] * 5 + type = random.choice(typeWeights) + + # some debugging to address AI crashes + if suitIndex >= len(SuitDNA.suitHeadTypes): + self.notify.warning("Bad suit index: %s" % (suitIndex)) + + self.addCogSummonsEarned(suitIndex, type) + return (suitIndex, type) + + def findClosestDoor(self): + zoneId = self.zoneId + streetId = ZoneUtil.getBranchZone(zoneId) + sp = self.air.suitPlanners[streetId] + if not sp: + return None + + bm = sp.buildingMgr + if not bm: + return None + + zones = [zoneId, zoneId-1, zoneId+1, zoneId-2, zoneId+2] + # loop through the valid zones... this ordering means we will + # find the building closest to the toon + for zone in zones: + for i in bm.getToonBlocks(): + building = bm.getBuilding(i) + extZoneId, intZoneId = building.getExteriorAndInteriorZoneId() + # make sure this isn't a quest building + if not NPCToons.isZoneProtected(intZoneId): + if hasattr(building, "door"): + if building.door.zoneId == zone: + return building + return None + + + #////////////////// Gardening Estates Expansion + + ### garden trophy list ### + + def b_setGardenTrophies(self, trophyList): + # update the caught fish list + self.setGardenTrophies(trophyList) + self.d_setGardenTrophies(trophyList) + + def setGardenTrophies(self, trophyList): + self.notify.debug("setting gardenTrophies to %s" % trophyList) + self.gardenTrophies = trophyList + + def d_setGardenTrophies(self, trophyList): + self.sendUpdate("setGardenTrophies", [trophyList]) + + def getGardenTrophies(self): + return self.gardenTrophies + + # garden specials + + def setGardenSpecials(self, specials): + for special in specials: + if special[1] > 255: + special[1] = 255 + self.gardenSpecials = specials + + def getGardenSpecials(self): + return self.gardenSpecials + + def d_setGardenSpecials(self, specials): + self.sendUpdate('setGardenSpecials', [specials]) + + def b_setGardenSpecials(self, specials): + for special in specials: + if special[1] > 255: + newCount = 255 + index = special[0] + self.gardenSpecials.remove(special) + self.gardenSpecials.append((index, newCount)) + self.gardenSpecials.sort() + self.setGardenSpecials(specials) + self.d_setGardenSpecials(specials) + + def addGardenItem(self, index, count): + for item in self.gardenSpecials: + if item[0] == index: + newCount = item[1] + count + self.gardenSpecials.remove(item) + self.gardenSpecials.append((index, newCount)) + self.gardenSpecials.sort() + self.b_setGardenSpecials(self.gardenSpecials) + return + self.gardenSpecials.append((index, count)) + self.gardenSpecials.sort() + self.b_setGardenSpecials(self.gardenSpecials) + + def removeGardenItem(self, index, count): + for item in self.gardenSpecials: + if item[0] == index: + newCount = item[1] - count + self.gardenSpecials.remove(item) + if newCount > 0: + self.gardenSpecials.append((index, newCount)) + self.gardenSpecials.sort() + self.b_setGardenSpecials(self.gardenSpecials) + return + self.notify.warning("removing garden item %d that toon doesn't have" % + index) + + # Flower collection + + def b_setFlowerCollection(self, speciesList, varietyList): + # update the collected flower list + self.setFlowerCollection( speciesList, varietyList) + self.d_setFlowerCollection( speciesList, varietyList) + + def d_setFlowerCollection(self, speciesList, varietyList): + self.sendUpdate("setFlowerCollection", [speciesList, varietyList]) + + def setFlowerCollection(self, speciesList, varietyList): + self.flowerCollection = FlowerCollection.FlowerCollection() + self.flowerCollection.makeFromNetLists( speciesList, varietyList) + + def getFlowerCollection(self): + return self.flowerCollection.getNetLists() + + ## Max flower basket + + def b_setMaxFlowerBasket(self, maxFlowerBasket): + self.d_setMaxFlowerBasket(maxFlowerBasket) + self.setMaxFlowerBasket(maxFlowerBasket) + + def d_setMaxFlowerBasket(self, maxFlowerBasket): + self.sendUpdate("setMaxFlowerBasket", [maxFlowerBasket]) + + def setMaxFlowerBasket(self, maxFlowerBasket): + self.maxFlowerBasket = maxFlowerBasket + + def getMaxFlowerBasket(self): + return self.maxFlowerBasket + + + ## Flower Basket + + def b_setFlowerBasket(self, speciesList, varietyList): + # update the picked flower list + self.setFlowerBasket( speciesList, varietyList) + self.d_setFlowerBasket( speciesList, varietyList) + + def d_setFlowerBasket(self, speciesList, varietyList): + self.sendUpdate("setFlowerBasket", [speciesList, varietyList]) + + def setFlowerBasket(self, speciesList, varietyList): + self.flowerBasket = FlowerBasket.FlowerBasket() + self.flowerBasket.makeFromNetLists(speciesList, varietyList) + + def getFlowerBasket(self): + return self.flowerBasket.getNetLists() + + def makeRandomFlowerBasket(self): + self.flowerBasket.generateRandomBasket() + self.d_setFlowerBasket(*self.flowerBasket.getNetLists()) + + def addFlowerToBasket(self, species, variety): + # First check our max limit + numFlower = len(self.flowerBasket) + if numFlower >= self.maxFlowerBasket: + self.notify.warning("addFlowerToBasket: cannot add flower, basket is full") + return 0 + else: + # Perhaps this can fail for some reason + if self.flowerBasket.addFlower(species, variety): + self.d_setFlowerBasket(*self.flowerBasket.getNetLists()) + return 1 + else: + self.notify.warning("addFlowerToBasket: addFlower failed") + return 0 + + def removeFlowerFromBasketAtIndex(self, index): + # Try to remove this flower from the basket + if self.flowerBasket.removeFlowerAtIndex(index): + self.d_setFlowerBasket(*self.flowerBasket.getNetLists()) + return 1 + else: + self.notify.warning("removeFishFromTank: cannot find fish") + return 0 + + ## Shovel + + def b_setShovel(self, shovelId): + self.d_setShovel(shovelId) + self.setShovel(shovelId) + + def d_setShovel(self, shovelId): + self.sendUpdate("setShovel", [shovelId]) + + def setShovel(self, shovelId): + self.shovel = shovelId + + def getShovel(self): + return self.shovel + + + ## ShovelSkill + + def b_setShovelSkill(self, skillLevel): + self.sendGardenEvent() + if skillLevel >= GardenGlobals.ShovelAttributes[self.shovel]['skillPts']: + #make sure we dont go past gold shovel + if self.shovel < GardenGlobals.MAX_SHOVELS - 1: + self.b_setShovel(self.shovel+1) + self.setShovelSkill(0) + self.d_setShovelSkill(0) + self.sendUpdate("promoteShovel", [self.shovel]) + + #log that he got a better shovel + self.air.writeServerEvent("garden_new_shovel", self.doId, '%d' % self.shovel) + else: + self.setShovelSkill(skillLevel) + self.d_setShovelSkill(skillLevel) + + def d_setShovelSkill(self, skillLevel): + self.sendUpdate("setShovelSkill", [skillLevel]) + + def setShovelSkill(self, skillLevel): + self.shovelSkill = skillLevel + + def getShovelSkill(self): + return self.shovelSkill + + ## WateringCan + + def b_setWateringCan(self, wateringCanId): + self.d_setWateringCan(wateringCanId) + self.setWateringCan(wateringCanId) + + def d_setWateringCan(self, wateringCanId): + self.sendUpdate("setWateringCan", [wateringCanId]) + + def setWateringCan(self, wateringCanId): + self.wateringCan = wateringCanId + + def getWateringCan(self): + return self.wateringCan + + """ + def waterPlant(self, plantId): + plant = self.air.doId2do.get(plantId) + if plant: + waterPower = GardenGlobals.getWateringCanPower(self.wateringCan, self.wateringCanSkill) + skillUp = plant.waterPlant(waterPower) + if skillUp: + self.b_setWateringCanSkill(self.wateringCanSkill+1) + """ + + ## WateringCanSkill + + def b_setWateringCanSkill(self, skillLevel): + self.sendGardenEvent() + if skillLevel >= GardenGlobals.WateringCanAttributes[self.wateringCan]['skillPts']: + if self.wateringCan < GardenGlobals.MAX_WATERING_CANS -1: + #make sure we don't go past the max watering can + self.b_setWateringCan(self.wateringCan+1) + self.setWateringCanSkill(0) + self.d_setWateringCanSkill(0) + self.sendUpdate("promoteWateringCan", [self.wateringCan]) + + #log that he got a better watering can + self.air.writeServerEvent("garden_new_wateringCan", self.doId, '%d' % self.wateringCan) + + else: + #we are at the maximum watering can, ensure skillLevel does not spill over + skillLevel = GardenGlobals.WateringCanAttributes[self.wateringCan]['skillPts'] - 1 + self.setWateringCanSkill(skillLevel) + self.d_setWateringCanSkill(skillLevel) + else: + self.setWateringCanSkill(skillLevel) + self.d_setWateringCanSkill(skillLevel) + + + def d_setWateringCanSkill(self, skillLevel): + self.sendUpdate("setWateringCanSkill", [skillLevel]) + + def setWateringCanSkill(self, skillLevel): + self.wateringCanSkill = skillLevel + + def getWateringCanSkill(self): + return self.wateringCanSkill + + ## TrackBonusLevel + + def b_setTrackBonusLevel(self, trackBonusLevelArray): + self.setTrackBonusLevel(trackBonusLevelArray) + self.d_setTrackBonusLevel(trackBonusLevelArray) + + def d_setTrackBonusLevel(self, trackBonusLevelArray): + self.sendUpdate("setTrackBonusLevel", [trackBonusLevelArray]) + + def setTrackBonusLevel(self, trackBonusLevelArray): + self.trackBonusLevel = trackBonusLevelArray + + def getTrackBonusLevel(self, track=None): + if track == None: + return self.trackBonusLevel + else: + return self.trackBonusLevel[track] + + def checkGagBonus(self, track, level): + trackBonus = self.getTrackBonusLevel(track) + return (trackBonus >= level) + + def giveMeSpecials(self, id = None): + print("Specials Go!!") + self.b_setGardenSpecials([(0,3),(1,2),(2,3),(3,2),(4,3),(5,2),(6,3),(7,2),(100,1),(101,3),(102,1)]) + + + def reqUseSpecial(self, special): + response = self.tryToUseSpecial(special) + self.sendUpdate('useSpecialResponse',[response]) + + def tryToUseSpecial(self, special) : + estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(self.zoneId) + + response = 'badlocation' + doIHaveThisSpecial = False + for curSpecial in self.gardenSpecials: + if curSpecial[0] == special and curSpecial[1] > 0: + doIHaveThisSpecial = True + break + + if not doIHaveThisSpecial: + #hmmm how did this happen, trying to plant a special we don't have + return response + + + if not self.doId == estateOwnerDoId: + self.notify.warning("how did this happen, planting an item you don't own") + return response + + if estateOwnerDoId: + estate = simbase.air.estateMgr.estate.get(estateOwnerDoId) + if estate and hasattr(estate,'avIdList'): + #we should have a valid DistributedEstateAI at this point + ownerIndex = estate.avIdList.index(estateOwnerDoId) + if ownerIndex >= 0: + estate.doEpochNow(onlyForThisToonIndex = ownerIndex) + self.removeGardenItem(special, 1) + response = 'success' + + #log that they used the fertilizer + self.air.writeServerEvent("garden_fertilizer", self.doId, '') + + return response + + def sendGardenEvent(self): + if hasattr(self, "estateZones") and hasattr(self, "doId"): + if simbase.wantPets and self.hatePets: + # announce to the pets that we're busy + PetObserve.send(self.estateZones, PetObserve.PetActionObserve( + PetObserve.Actions.GARDEN, self.doId)) + + def setGardenStarted(self, bStarted): + self.gardenStarted = bStarted + def d_setGardenStarted(self, bStarted): + self.sendUpdate("setGardenStarted", [bStarted]) + def b_setGardenStarted(self, bStarted): + self.setGardenStarted(bStarted) + self.d_setGardenStarted(bStarted) + def getGardenStarted(self): + return self.gardenStarted + + # log suspicious toon behaviors + def logSuspiciousEvent(self, eventName): + self.air.writeServerEvent('suspicious', self.doId, eventName) + + + ### golf trophy list ### + def getGolfTrophies(self): + """Get the golf trophies this toon has won.""" + return self.golfTrophies + + def getGolfCups(self): + """Get the golf cups this toon has won. 10 trophies awards you 1 cup.""" + return self.golfCups + + ### golf history list ### + + def b_setGolfHistory(self, history): + """Set the golf history on the ai and client.""" + # update the trophies won list + self.setGolfHistory(history) + self.d_setGolfHistory(history) + + def d_setGolfHistory(self, history): + """Send the golf history to the client.""" + self.sendUpdate('setGolfHistory',[history]) + + def setGolfHistory(self, history): + """Set the golf history on the ai.""" + self.notify.debug("setting golfHistory to %s" % history) + self.golfHistory = history + + #update our trophies and cups too + self.golfTrophies = GolfGlobals.calcTrophyListFromHistory(self.golfHistory) + self.golfCups = GolfGlobals.calcCupListFromHistory(self.golfHistory) + + def getGolfHistory(self): + """Return the golf history.""" + return self.golfHistory + + def b_setGolfHoleBest(self, holeBest): + """Set the personal hole best on the ai and client.""" + self.setGolfHoleBest(holeBest) + self.d_setGolfHoleBest(holeBest) + + def d_setGolfHoleBest(self, holeBest): + """Send the personal hole best to client.""" + packed = GolfGlobals.packGolfHoleBest(holeBest) + self.sendUpdate('setPackedGolfHoleBest', [packed]) + + def setGolfHoleBest(self, holeBest): + """Set the personal hole best on the ai.""" + self.golfHoleBest = holeBest + + def getGolfHoleBest(self): + """Return the personal hole best.""" + return self.golfHoleBest + + def getPackedGolfHoleBest(self): + """Return the packed personal hole best.""" + packed = GolfGlobals.packGolfHoleBest(self.golfHoleBest) + return packed + + def setPackedGolfHoleBest(self, packedHoleBest): + """Set the packed personal hole best on the client.""" + unpacked = GolfGlobals.unpackGolfHoleBest(packedHoleBest) + self.setGolfHoleBest(unpacked) + + + def b_setGolfCourseBest(self, courseBest): + """Set the personal course best on the ai and client.""" + self.setGolfCourseBest(courseBest) + self.d_setGolfCourseBest(courseBest) + + def d_setGolfCourseBest(self, courseBest): + """Send the personal course best to client.""" + self.sendUpdate('setGolfCourseBest', [courseBest]) + + def setGolfCourseBest(self, courseBest): + """Set the personal course best on the ai.""" + self.golfCourseBest = courseBest + + def getGolfCourseBest(self): + """Return the personal course best.""" + return self.golfCourseBest + + def setUnlimitedSwing(self, unlimitedSwing): + """Set if we can swing an unlimited number of times in golf.""" + self.unlimitedSwing = unlimitedSwing + + def getUnlimitedSwing(self): + """Returns true if we can swing an unlimited number of times in golf.""" + return self.unlimitedSwing + + def b_setUnlimitedSwing(self, unlimitedSwing): + """Set the unlimitedSwing on the ai and client.""" + self.setUnlimitedSwing(unlimitedSwing) + self.d_setUnlimitedSwing(unlimitedSwing) + + def d_setUnlimitedSwing(self, unlimitedSwing): + """Send the personal course best to client.""" + self.sendUpdate('setUnlimitedSwing', [unlimitedSwing]) + + # Control the pink slips + def b_setPinkSlips(self, pinkSlips): + self.d_setPinkSlips(pinkSlips) + self.setPinkSlips(pinkSlips) + + def d_setPinkSlips(self, pinkSlips): + self.sendUpdate('setPinkSlips', [pinkSlips]) + + def setPinkSlips(self, pinkSlips): + self.pinkSlips = pinkSlips + + def getPinkSlips(self): + return self.pinkSlips + + def addPinkSlips(self, amountToAdd): + pinkSlips = min( self.pinkSlips + amountToAdd, 0xff) + self.b_setPinkSlips(pinkSlips) + + def setPreviousAccess(self, access): + #stub function for dc compatibility with DistributedPlayerAI + #used to keep track of access if it changes while play is in session + self.previousAccess = access + + def b_setAccess(self, access): + self.setAccess(access) + self.d_setAccess(access) + + def d_setAccess(self, access): + self.sendUpdate("setAccess", [access]) + + + def setAccess(self, access): + print("Setting Access %s" % (access)) + if access == OTPGlobals.AccessInvalid: + if not __dev__: + self.air.writeServerEvent("Setting Access", self.doId, "setAccess not being sent by the OTP Server, changing access to unpaid") + access = OTPGlobals.AccessVelvetRope + elif __dev__: + access = OTPGlobals.AccessFull + self.setGameAccess(access) + + + def setGameAccess(self, access): + self.gameAccess = access + + def getGameAccess(self): + return self.gameAccess + + # Name Tag Styles + + def b_setNametagStyle(self, nametagStyle): + self.d_setNametagStyle(nametagStyle) + self.setNametagStyle(nametagStyle) + + def d_setNametagStyle(self, nametagStyle): + self.sendUpdate('setNametagStyle', [nametagStyle]) + + def setNametagStyle(self, nametagStyle): + self.nametagStyle = nametagStyle + + def getNametagStyle(self): + return self.nametagStyle + + def logMessage(self, message): + avId = self.air.getAvatarIdFromSender() + if __dev__: + print ("CLIENT LOG MESSAGE %s %s" % (avId, message)) + try: + self.air.writeServerEvent('clientLog', avId, message) + + except: + self.air.writeServerEvent('suspicious', avId, "client sent us a clientLog that caused an exception") + + def b_setMail(self, mail): + self.d_setMail(mail) + self.setMail(mail) + + def d_setMail(self, mail): + self.sendUpdate('setMail', [mail]) + + def setMail(self, mail): + self.mail = mail + + def setNumMailItems(self, numMailItems): + self.numMailItems = numMailItems + + def setSimpleMailNotify(self, simpleMailNotify): + """Handle the uberdog telling us if we have new, old, or no simple mail.""" + self.simpleMailNotify = simpleMailNotify + + def setInviteMailNotify(self, inviteMailNotify): + """Handle the uberdog telling us if we have new, old, or no invite mail.""" + self.inviteMailNotify = inviteMailNotify + + def setInvites( self, invites): + """Handle uberdog telling us our invitations. + This does not include invites we've already rejected.""" + self.invites = [] + for i in xrange(len(invites)): + oneInvite=invites[i] + newInvite = InviteInfoBase(*oneInvite) + self.invites.append(newInvite) + assert self.notify.debug('self.invites[%d]= %s' % (i, newInvite)) + + def updateInviteMailNotify(self): + """Calculate the value for inviteMailNotify, new invite, old invite or no invites""" + invitesInMailbox = self.getInvitesToShowInMailbox() + newInvites = 0 + readButNotRepliedInvites = 0 + for invite in invitesInMailbox: + if invite.status == PartyGlobals.InviteStatus.NotRead: + newInvites += 1 + elif invite.status == PartyGlobals.InviteStatus.ReadButNotReplied: + readButNotRepliedInvites += 1 + # we're guaranted that we have the associated partyInfo, but just in case + if __dev__ : + partyInfo = self.getOnePartyInvitedTo(invite.partyId) + if not partyInfo: + self.notify.error("party info not found in partiesInvtedTo, partyId = %s" % + str(invite.partyId)) + if newInvites: + self.setInviteMailNotify(ToontownGlobals.NewItems) + elif readButNotRepliedInvites: + self.setInviteMailNotify(ToontownGlobals.OldItems) + else: + self.setInviteMailNotify(ToontownGlobals.NoItems) + + def getNumNonResponseInvites(self): + """ + Returns the number of invites that have not yet been responded to. + """ + count = 0 + for i in xrange(len(self.invites)): + if (self.invites[i].status == InviteStatus.NotRead) or (self.invites[i].status == InviteStatus.ReadButNotReplied): + count += 1 + return count + + def getInvitesToShowInMailbox(self): + """Return a list of inviteInfos that should be displayed in the mailbox.""" + # WARNING keep this in sync with DistributedToon.getInvitesToShowInMailbox + result = [] + for invite in self.invites: + appendInvite = True + if invite.status == InviteStatus.Accepted or \ + invite.status == InviteStatus.Rejected: + assert( self.notify.debug('Not showing accepted/rejected invite %s' % invite) ) + appendInvite = False + + if appendInvite: + # some invites are so far in the future we don't have the party info + partyInfo = self.getOnePartyInvitedTo(invite.partyId) + if not partyInfo: + assert( self.notify.debug('Not showing invite because no party ') ) + appendInvite = False + # do not show invitations for cancelled parties + if appendInvite: + if partyInfo.status == PartyGlobals.PartyStatus.Cancelled: + assert( self.notify.debug('Not showing invite party is cancelled') ) + appendInvite = False + + # do not show mailbox invitations for parties that have finished yesterday + if appendInvite: + # server time and client time may be slightly off, and toon can get mailbox stuck when + # they dont return the same value, using yesterday it's still possible but only + # at around midnight, which minimizes the possibilty + # we use end time because a party could be started 1 minute before it's supposed to end + endDate= partyInfo.endTime.date() + curDate = simbase.air.toontownTimeManager.getCurServerDateTime().date() + if endDate < curDate: + appendInvite = False + if appendInvite: + result.append(invite) + return result + + def getNumInvitesToShowInMailbox(self): + """Return how many invites we'll show in the mailbox.""" + result = len(self.getInvitesToShowInMailbox()) + return result + + def setHostedParties( self, hostedParties): + """Handle uberdog telling us our hosted parties.""" + self.hostedParties = [] + for i in xrange(len(hostedParties)): + hostedInfo = hostedParties[i] + newParty = PartyInfoAI(*hostedInfo) + self.hostedParties.append(newParty) + assert self.notify.debug('self.hostedParties[%d]= %s' % (i,newParty)) + + def setPartiesInvitedTo( self, partiesInvitedTo): + """Handle uberdog telling us details of parties we are invited to.""" + self.partiesInvitedTo = [] + for i in xrange(len(partiesInvitedTo)): + partyInfo = partiesInvitedTo[i] + newParty = PartyInfoAI(*partyInfo) + self.partiesInvitedTo.append(newParty) + assert self.notify.debug('self.partiesInvitedTo[%d]= %s' % (i,newParty)) + self.updateInviteMailNotify() + self.checkMailboxFullIndicator() + + def getOnePartyInvitedTo(self, partyId): + """Return the partyInfo if partyId is in partiesInvitedTo, return None otherwise.""" + # It is possible to get an invite to a party so far in the future that it gets filtered out + # hence returning None is a valid result + result = None + for i in xrange(len(self.partiesInvitedTo)): + partyInfo = self.partiesInvitedTo[i] + if partyInfo.partyId == partyId: + result = partyInfo + break + return result + + def setPartyReplyInfoBases(self, replies): + """Handle uberdog telling us replies to our hosted parties.""" + self.partyReplyInfoBases = [] + for i in xrange(len(replies)): + partyReply = replies[i] + repliesForOneParty = PartyReplyInfoBase(*partyReply) + self.partyReplyInfoBases.append(repliesForOneParty) + assert self.notify.debug('self.setPartyReplyInfoBases[%d]= %s' % (i,repliesForOneParty)) + + def updateInvite(self, inviteKey, newStatus): + """We've gotten confirmation from the uberdog to reject/accept the invite.""" + for invite in self.invites: + if invite.inviteKey == inviteKey: + invite.status = newStatus + self.updateInviteMailNotify() + self.checkMailboxFullIndicator() + break + + + def updateReply(self, partyId, inviteeId, newStatus): + """Someone accepted our invite while we were online.""" + for partyReply in self.partyReplyInfoBases: + if partyReply.partyId == partyId: + for reply in partyReply.replies: + if reply.inviteeId == inviteeId: + reply.inviteeId = newStatus + break + + def canPlanParty(self): + """Return true if the toon can plan a party.""" + nonCancelledPartiesInTheFuture = 0 + for partyInfo in self.hostedParties: + if partyInfo.status != PartyGlobals.PartyStatus.Cancelled and partyInfo.status != PartyGlobals.PartyStatus.Finished: + nonCancelledPartiesInTheFuture += 1 + if nonCancelledPartiesInTheFuture >= PartyGlobals.MaxHostedPartiesPerToon: + break + result = nonCancelledPartiesInTheFuture < PartyGlobals.MaxHostedPartiesPerToon + return result + + + def setPartyCanStart(self, partyId): + """Handle uberdog telling us we can start a party that we're hosting.""" + self.notify.debug("setPartyCanStart called passing in partyId=%s" % partyId) + found = False + for partyInfo in self.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = PartyGlobals.PartyStatus.CanStart + found = True + break + if not found: + self.notify.warning("setPartyCanStart can't find partyId %s" % partyId) + + def setPartyStatus(self, partyId, newStatus): + """Handle uberdog telling us status of a party has changed.""" + self.notify.debug("setPartyStatus called passing in partyId=%s newStauts=%d" % (partyId,newStatus)) + found = False + + for partyInfo in self.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = newStatus + found = True + break + + info = self.getOnePartyInvitedTo(partyId) + if info: + found = True + info.status = newStatus + if not found: + self.notify.warning("setPartyCanStart can't find hosted or invitedTO partyId %s" % partyId) + + + def b_setAwardMailboxContents(self, awardMailboxContents): + """Set ai's mailbox contents, and update client.""" + self.setAwardMailboxContents(awardMailboxContents ) + self.d_setAwardMailboxContents(awardMailboxContents ) + + def d_setAwardMailboxContents(self, awardMailboxContents): + """Update the client on the new award mailbox contents.""" + self.sendUpdate("setAwardMailboxContents", [awardMailboxContents.getBlob(store = CatalogItem.Customization)]) + # AWARD_TODO figure out how i can set this properly with mailboxContents + #if len(mailboxContents ) == 0: + # self.b_setCatalogNotify(self.catalogNotify, ToontownGlobals.NoItems) + + def setAwardMailboxContents(self, awardMailboxContents): + """Set AI's mailbox contents.""" + self.notify.debug("Setting awardMailboxContents to %s." % (awardMailboxContents)) + self.awardMailboxContents = CatalogItemList.CatalogItemList(awardMailboxContents, store = CatalogItem.Customization ) + self.notify.debug("awardMailboxContents is %s." % (self.awardMailboxContents)) + if len(awardMailboxContents) == 0: + self.b_setAwardNotify(ToontownGlobals.NoItems) + self.checkMailboxFullIndicator() + + def getAwardMailboxContents(self): + """Return our current award mailbox contents.""" + return self.awardMailboxContents.getBlob(store = CatalogItem.Customization ) + + + def b_setAwardSchedule(self, onOrder, doUpdateLater = True): + """Set AI's award schedule, and update the client.""" + self.setAwardSchedule(onOrder, doUpdateLater) + self.d_setAwardSchedule(onOrder) + + def d_setAwardSchedule(self, onOrder): + """Update the client on the new award schedule.""" + self.sendUpdate("setAwardSchedule", [onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate)]) + + def setAwardSchedule(self, onAwardOrder, doUpdateLater = True): + """Set AI's award schedule.""" + # awards don't have to deal with gifts and own purchased items, so using the original algorithm + self.onAwardOrder = CatalogItemList.CatalogItemList(onAwardOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if hasattr(self, 'name'): + if doUpdateLater and self.air.doLiveUpdates and hasattr(self, 'air'): + # Schedule the next delivery. + taskName = self.uniqueName('next-award-delivery') + taskMgr.remove(taskName) + + #print("setting item schedule for %s" % (self.getName())) + # Set a timeout to make the delivery later. We insist on + # waiting at least 10 seconds mainly to give the avatar enough + # time to completely manifest on the stateserver before we + # start sending messages to it. + now = (int)(time.time() / 60 + 0.5) + nextItem = None + nextTime = self.onAwardOrder.getNextDeliveryDate() + nextItem = self.onAwardOrder.getNextDeliveryItem() + if nextItem != None: + pass + #print(">>current time:%s" % (now)) + #print("next item time:%s item:%s" % (nextTime, nextItem.getName())) + else: + pass + #print("No items for regular delivery") + if nextTime != None: + duration = max(10.0, nextTime * 60 - time.time()) + taskMgr.doMethodLater(duration, self.__deliverAwardPurchase, taskName) + + def __deliverAwardPurchase(self, task): + """Move the award from onAwardOrder to mailboxContents.""" + # Move one from the onAwardOrder list to the + # awardMailboxContents. + # Get the current time in minutes. + now = (int)(time.time() / 60 + 0.5) + + # Extract out any items that should have been delivered by now. + delivered, remaining = self.onAwardOrder.extractDeliveryItems(now) + + self.notify.info("Award Delivery for %s: %s." % (self.doId, delivered)) + + self.b_setAwardMailboxContents(self.awardMailboxContents + delivered) + self.b_setAwardSchedule(remaining) + + if delivered: + self.b_setAwardNotify( ToontownGlobals.NewItems) + + return Task.done + + def b_setAwardNotify(self, awardMailboxNotify): + """Set AI's award notify, and update the client.""" + self.setAwardNotify(awardMailboxNotify) + self.d_setAwardNotify(awardMailboxNotify) + + def d_setAwardNotify(self, awardMailboxNotify): + """update the client.""" + self.sendUpdate("setAwardNotify", [awardMailboxNotify]) + + def setAwardNotify(self, awardNotify): + """Set AI's award notify""" + self.awardNotify = awardNotify + + def hasGMName(self): + """ Returns True if this toon's name starts with '$', indicating they are special. """ + return self.getName().startswith('$') diff --git a/toontown/src/toon/DistributedToonUD.py b/toontown/src/toon/DistributedToonUD.py new file mode 100644 index 0000000..798a170 --- /dev/null +++ b/toontown/src/toon/DistributedToonUD.py @@ -0,0 +1,271 @@ + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals + +from direct.distributed.DistributedObjectUD import DistributedObjectUD +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem +from toontown.catalog import CatalogItemTypes +from toontown.catalog import CatalogClothingItem +from toontown.toonbase import ToontownGlobals +import ToonDNA + +class DistributedToonUD(DistributedObjectUD): + + def __init__(self, air): + DistributedObjectUD.__init__(self, air) + self.dna = ToonDNA.ToonDNA() + self.clothesTopsList = [] + self.clothesBottomsList = [] + self.emoteAccess = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + self.fishingRod = 0 + + if simbase.wantPets: + self.petTrickPhrases = [] + + if simbase.wantBingo: + self.bingoCheat = False + + self.customMessages = [] + + self.mailboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization) + #self.deliveryboxContents = CatalogItemList.CatalogItemList(store = CatalogItem.Customization | CatalogItem.GiftTag) + + + def d_setGiftSchedule(self, onOrder): + #self.sendUpdate("setGiftSchedule", [onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag)]) + self.sendUpdate("setGiftSchedule", [onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate)]) + + """ + def b_setGiftSchedule(self, onOrder, doUpdateLater = True): + self.setGiftSchedule(onOrder, doUpdateLater) + self.d_setGiftSchedule(onOrder) + """ + + + def setGiftSchedule(self, onGiftOrder, doUpdateLater = True): + #self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + def getGiftSchedule(self): + #return self.onGiftOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate | CatalogItem.GiftTag) + return self.onGiftOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + + def setDeliverySchedule(self, onOrder, doUpdateLater = True): + + self.onOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + def getDeliverySchedule(self): + return self.onOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + + def setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog): + self.monthlyCatalog = CatalogItemList.CatalogItemList(monthlyCatalog) + self.weeklyCatalog = CatalogItemList.CatalogItemList(weeklyCatalog) + self.backCatalog = CatalogItemList.CatalogItemList(backCatalog) + + def setName(self, name): + self.name = name + + def getName(self): + return self.name + + def setMoney(self, money): + self.money = money + + def getMoney(self): + return self.money + + def getTotalMoney(self): + return (self.money + self.bankMoney) + + def setBankMoney(self, money): + self.bankMoney = money + + def getBankMoney(self): + return self.bankMoney + + def setMailboxContents(self, mailboxContents): + self.notify.debug("Setting mailboxContents to %s." % (mailboxContents)) + self.mailboxContents = CatalogItemList.CatalogItemList(mailboxContents, store = CatalogItem.Customization) + self.notify.debug("mailboxContents is %s." % (self.mailboxContents)) + + def getMailboxContents(self): + return self.mailboxContents.getBlob(store = CatalogItem.Customization) + + + def setAwardMailboxContents(self, awardMailboxContents): + self.notify.debug("Setting awardMailboxContents to %s." % (awardMailboxContents)) + self.awardMailboxContents = CatalogItemList.CatalogItemList(awardMailboxContents, store = CatalogItem.Customization ) + self.notify.debug("awardMailboxContents is %s." % (self.awardMailboxContents)) + + def getAwardMailboxContents(self): + return self.awardMailboxContents.getBlob(store = CatalogItem.Customization ) + + def setAwardSchedule(self, onOrder, doUpdateLater = True): + + self.onAwardOrder = CatalogItemList.CatalogItemList(onOrder, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + def getAwardSchedule(self): + return self.onAwardOrder.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + """ + def setDeliveryboxContents(self, deliveryboxContents): + self.deliveryboxContents = CatalogItemList.CatalogItemList(deliveryboxContents, store = CatalogItem.Customization | CatalogItem.GiftTag) + + def getDeliveryboxContents(self): + return self.deliveryboxContents.getBlob(store = CatalogItem.Customization | CatalogItem.GiftTag) + """ + def setDNAString(self, string): + self.dna.makeFromNetString(string) + + def getDNAString( self ): + """ + Function: retrieve the dna information from this suit, called + whenever a client needs to create this suit + Returns: netString representation of this suit's dna + """ + return self.dna.makeNetString() + + def getStyle(self): + return self.dna + + def setClothesTopsList(self, clothesList): + self.clothesTopsList = clothesList + + def getClothesTopsList(self): + return self.clothesTopsList + + def setClothesBottomsList(self, clothesList): + self.clothesBottomsList = clothesList + + def getClothesBottomsList(self): + return self.clothesBottomsList + + def setEmoteAccess(self, bits): + if len(bits) != len(self.emoteAccess): + self.notify.warning("New emote access list must be the same size as the old one.") + return + self.emoteAccess = bits + + def getEmoteAccess(self): + return self.emoteAccess + + def setCustomMessages(self, customMessages): + self.customMessages = customMessages + + def getCustomMessages(self): + return self.customMessages + + def setPetTrickPhrases(self, tricks): + self.petTrickPhrases = tricks + + def getPetTrickPhrases(self): + return self.petTrickPhrases + + def setFishingRod(self, rodId): + self.fishingRod = rodId + + def getFishingRod(self): + return self.fishingRod + + def getGardenSpecials(self): + return self.gardenSpecials + + def setGardenSpecials(self, specials): + self.gardenSpecials = specials + + def checkForItemInCloset(self, clothingItem): + """Returns None if the clothing item is not in the closet.""" + result = None + clothingTypeInfo = CatalogClothingItem.ClothingTypes[clothingItem.clothingType] + styleStr = clothingTypeInfo[1] + if clothingItem.isShirt(): + # ok check the tops list + # we have the style str, check TOON DNA to get shirt and sleeve indices + shirtStyleInfo = ToonDNA.ShirtStyles[styleStr] + topTex= shirtStyleInfo[0] + sleeveTex = shirtStyleInfo[1] + topTexColor = shirtStyleInfo[2][clothingItem.colorIndex][0] + sleeveTexColor = shirtStyleInfo[2][clothingItem.colorIndex][1] + # See if this top is already there + for i in range(0, len(self.clothesTopsList), 4): + if (self.clothesTopsList[i] == topTex and + self.clothesTopsList[i+1] == topTexColor and + self.clothesTopsList[i+2] == sleeveTex and + self.clothesTopsList[i+3] == sleeveTexColor): + result = ToontownGlobals.P_ItemInCloset + break + else: + bottomStyleInfo = ToonDNA.BottomStyles[styleStr] + botTex= bottomStyleInfo[0] + botTexColor = bottomStyleInfo[1][clothingItem.colorIndex] + # See if this bottom is already there + for i in range(0, len(self.clothesBottomsList), 2): + if (self.clothesBottomsList[i] == botTex and + self.clothesBottomsList[i+1] == botTexColor): + result = ToontownGlobals.P_ItemInCloset + break + return result + + def checkForItemAlreadyWorn(self, clothingItem): + """Returns None if the toon is not wearing the clothing item.""" + result = None + clothingTypeInfo = CatalogClothingItem.ClothingTypes[clothingItem.clothingType] + styleStr = clothingTypeInfo[1] + if clothingItem.isShirt(): + # ok check the tops list + # we have the style str, check TOON DNA to get shirt and sleeve indices + shirtStyleInfo = ToonDNA.ShirtStyles[styleStr] + topTex= shirtStyleInfo[0] + sleeveTex = shirtStyleInfo[1] + topTexColor = shirtStyleInfo[2][clothingItem.colorIndex][0] + sleeveTexColor = shirtStyleInfo[2][clothingItem.colorIndex][1] + # See if this top is already being worn + if self.dna.topTex == topTex and \ + self.dna.sleeveTex == sleeveTex and \ + self.dna.topTexColor == topTexColor and \ + self.dna.sleeveTexColor == sleeveTexColor: + result = ToontownGlobals.P_ItemAlreadyWorn + else: + bottomStyleInfo = ToonDNA.BottomStyles[styleStr] + bottomTex= bottomStyleInfo[0] + bottomTexColor = bottomStyleInfo[1][clothingItem.colorIndex] + # See if this bottom is already being worn + if self.dna.botTex == bottomTex and \ + self.dna.botTexColor == bottomTexColor: + result = ToontownGlobals.P_ItemAlreadyWorn + return result + + def checkForDuplicateItem(self, catalogItem): + """Return None if the catalog item is not in his mailbox, or on him somehow""" + result = None + # go through his mailbox + if catalogItem in self.mailboxContents: + result = ToontownGlobals.P_ItemInMailbox + elif catalogItem in self.onOrder: + result = ToontownGlobals.P_ItemOnOrder + elif catalogItem in self.onGiftOrder: + result = ToontownGlobals.P_ItemOnGiftOrder + elif catalogItem in self.awardMailboxContents: + result = ToontownGlobals.P_ItemInAwardMailbox + elif catalogItem in self.onAwardOrder: + result = ToontownGlobals.P_ItemOnAwardOrder + + # now based on the item type do some other checking + if not result: + if catalogItem.getTypeCode() == CatalogItemTypes.CLOTHING_ITEM: + result = self.checkForItemInCloset(catalogItem) + if not result: + result = self.checkForItemAlreadyWorn(catalogItem) + elif catalogItem.getTypeCode() == CatalogItemTypes.CHAT_ITEM: + speedChatIndex = catalogItem.customIndex + if speedChatIndex in self.customMessages: + result = ToontownGlobals.P_ItemInMyPhrases + elif catalogItem.getTypeCode() == CatalogItemTypes.PET_TRICK_ITEM: + trickId = catalogItem.trickId + if trickId in self.petTrickPhrases: + result = ToontownGlobals.P_ItemInPetTricks + return result diff --git a/toontown/src/toon/ElevatorNotifier.py b/toontown/src/toon/ElevatorNotifier.py new file mode 100644 index 0000000..87756fb --- /dev/null +++ b/toontown/src/toon/ElevatorNotifier.py @@ -0,0 +1,102 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from direct.gui.DirectGui import * +from pandac.PandaModules import * + +class ElevatorNotifier: + """CatalogNotifyDialog: + + Pops up to tell you when you have a new catalog, or a new delivery + from the catalog. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("CatalogNotifyDialog") + + def __init__(self): + self.frame = None + pass + + + + def handleButton(self): + self.__handleButton(1) + + def createFrame(self, message, framePos = None, withStopping = True): + if not framePos: + framePos = (0.0, 0, 0.78) + self.frame = DirectFrame( + relief = None, + image = DGG.getDefaultDialogGeom(), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.0, 0.40), + text = message, + text_wordwrap = 16, + text_scale = 0.06, + text_pos = (-0.0, 0.1), + pos = framePos, + ) + + buttons = loader.loadModel( + 'phase_3/models/gui/dialog_box_buttons_gui') + cancelImageList = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')) + okImageList = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')) + + + self.doneButton = DirectButton( + parent = self.frame, + relief = None, + image = cancelImageList, + command = self.handleButton, + pos = (0, 0, -0.14), + ) + if not withStopping: + self.doneButton['command'] = self.__handleButtonWithoutStopping + self.doneButton.show() + self.frame.show() + + + def cleanup(self): + """cleanup(self): + Cancels any pending request and removes the panel from the + screen, unanswered. + """ + if self.frame: + self.frame.destroy() + self.frame = None + self.nextButton = None + self.doneButton = None + + def __handleButton(self, value): + self.cleanup() + place = base.cr.playGame.getPlace() + if place: + place.setState('walk') + + def showMe(self, message, pos = None): + if self.frame == None: + place = base.cr.playGame.getPlace() + if place: + self.createFrame(message, pos) + place.setState('stopped') + + def showMeWithoutStopping(self, message, pos = None): + """ + Some messages need not have the toon in a stopped state. + Show the elevator message by keeping them in their previous state itself. + """ + if self.frame == None: + self.createFrame(message, pos, False) + + def __handleButtonWithoutStopping(self): + self.cleanup() + + def isNotifierOpen(self): + if self.frame: + return True + else: + return False \ No newline at end of file diff --git a/toontown/src/toon/Experience.py b/toontown/src/toon/Experience.py new file mode 100644 index 0000000..562284c --- /dev/null +++ b/toontown/src/toon/Experience.py @@ -0,0 +1,212 @@ +### Experience module: contains the Experience class""" + +from pandac.PandaModules import * +from toontown.toonbase.ToontownBattleGlobals import * +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from otp.otpbase import OTPGlobals + +class Experience: + + notify = DirectNotifyGlobal.directNotify.newCategory('Experience') + + # special methods + + def __init__(self, expStr=None, owner=None): + """__init__(self, netString=None) + Create a default experience if no netString given, or + create an experience from the given netString + """ + self.owner = owner + if (expStr == None): + # create default level one inv + self.experience = [] + for track in range(0, len(Tracks)): + self.experience.append(StartingLevel) + else: + # de-stringify the one that came in + self.experience = self.makeFromNetString(expStr) + + def __str__(self): + """__str__(self) + Experience print function + """ + return str(self.experience) + + def makeNetString(self): + """makeNetString(self) + Make a network packet out of the experience + """ + dataList = self.experience + datagram = PyDatagram() + for track in range(0, len(Tracks)): + datagram.addUint16(dataList[track]) + dgi = PyDatagramIterator(datagram) + return dgi.getRemainingBytes() + + def makeFromNetString(self, netString): + """makeFromNetString(self) + Make an experience from a network packet + """ + dataList = [] + dg = PyDatagram(netString) + dgi = PyDatagramIterator(dg) + for track in range(0, len(Tracks)): + dataList.append(dgi.getUint16()) + return dataList + + + # setters and getters + + def addExp(self, track, amount=1): + """addExp(self, [int | string], int=1) + Add 'amount' (defaults to 1) of experience to the given track. + Track may be specified by an index (ie Tracks[index]) or + by string (ie 'drop') + """ + # if string, convert to index + if (type(track) == type('')): + track = Tracks.index(track) + + self.notify.debug("adding %d exp to track %d" % (amount, track)) + if self.owner.getGameAccess() == OTPGlobals.AccessFull: + if (self.experience[track] + amount <= MaxSkill): + self.experience[track] += amount + else: + self.experience[track] = MaxSkill + else: + if (self.experience[track] + amount <= UnpaidMaxSkill): + self.experience[track] += amount + else: + if self.experience[track] > UnpaidMaxSkill: + self.experience[track] += 0 #remain unchanged + else: + self.experience[track] = UnpaidMaxSkill + + def maxOutExp(self): + """maxOutExp(self): + Set all experience fields to MaxSkill + """ + for track in range(0, len(Tracks)): + self.experience[track] = MaxSkill - UberSkill + + def maxOutExpMinusOne(self): + """maxOutExp(self): + Set all experience fields to MaxSkill + """ + for track in range(0, len(Tracks)): + self.experience[track] = MaxSkill - 1 + + def makeExpHigh(self): + for track in range(0, len(Tracks)): + self.experience[track] = Levels[track][len(Levels[track]) - 1] - 1 + + def makeExpRegular(self): + import random + for track in range(0, len(Tracks)): + rank = random.choice((0,int(random.random() * 1500.0),int(random.random() * 2000.0))) + self.experience[track] = Levels[track][len(Levels[track]) - 1] - rank + + def zeroOutExp(self): + """zeroOutExp(self): + Set all experience fields to StartingLevel + """ + for track in range(0, len(Tracks)): + self.experience[track] = StartingLevel + + def setAllExp(self, num): + """ + Sets all the exp to the same number. + This is for debugging. + """ + for track in range(0, len(Tracks)): + self.experience[track] = num + + def getExp(self, track): + """getExp(self, [int | string]) + Return the raw experience of the given track. + Track may be specified by an index (ie Tracks[index]) or + by string (ie 'drop') + """ + # if string, convert to index + if (type(track) == type('')): + track = Tracks.index(track) + + return self.experience[track] + + def setExp(self, track, exp): + """setExp(self, [int | string]) + Sets the raw experience of the given track. + Track may be specified by an index (ie Tracks[index]) or + by string (ie 'drop') + """ + # if string, convert to index + if (type(track) == type('')): + track = Tracks.index(track) + + self.experience[track] = exp + + def getExpLevel(self, track): + """getExpLevel(self, [int | string]) + Return the experience level (1-6) of the given track. + Track may be specified by an index (ie Tracks[index]) or + by string (ie 'drop') + """ + # if string, convert to index + if (type(track) == type('')): + track = Tracks.index(track) + + level = 0 + for amount in Levels[track]: + if (self.experience[track] >= amount): + level = Levels[track].index(amount) + + return level + + def getTotalExp(self): + total = 0 + for level in self.experience: + total += level + return total + + def getNextExpValue(self, track, curSkill=None): + """ + Return the number of total experience to get to the next + track. If the current experience equals or exceeds the highest + next value, the highest next value is returned. + """ + if curSkill == None: + curSkill = self.experience[track] + # The last value is the default + retVal = Levels[track][len(Levels[track]) - 1] + for amount in Levels[track]: + if curSkill < amount: + retVal = amount + return retVal + return retVal + + def getNewGagIndexList(self, track, extraSkill): + """ + Returns a list of indices of new gags that have been earned + During this battle. It is hard to imagine that the list will + be longer than one item, but it might be theoretically possible one day. + """ + retList = [] + curSkill = self.experience[track] + nextExpValue = self.getNextExpValue(track, curSkill) + finalGagFlag = 0 + while ((curSkill + extraSkill >= nextExpValue) and + (curSkill < nextExpValue) and + (not finalGagFlag)): + retList.append(Levels[track].index(nextExpValue)) + newNextExpValue = self.getNextExpValue(track, nextExpValue) + if newNextExpValue == nextExpValue: + finalGagFlag = 1 + else: + nextExpValue = newNextExpValue + return retList + + + + diff --git a/toontown/src/toon/GMUtils.py b/toontown/src/toon/GMUtils.py new file mode 100644 index 0000000..3a75af1 --- /dev/null +++ b/toontown/src/toon/GMUtils.py @@ -0,0 +1,31 @@ +from toontown.toonbase import TTLocalizer + + +def testGMIdentity(name=''): + if name.find('$')!=-1: + return True + else: + return False + +def handleGMName(name=''): + """ Parse the name for symbols that will get replaced by prefixes and icons """ + if name.find('$000')!=-1: + prefix = TTLocalizer.GM_1 + elif name.find('$001')!=-1: + prefix = TTLocalizer.GM_2 + else: + # This is suspicious + prefix = '' + + gmName = prefix + ' ' + name.lstrip('$0123456789') + + return gmName + +def getGMType(name=''): + if (name.find('$000')!=-1) or (name.find(TTLocalizer.GM_1)==0): + return TTLocalizer.GM_1 + elif (name.find('$001')!=-1) or (name.find(TTLocalizer.GM_2)==0): + return TTLocalizer.GM_2 + else: + # This is suspicious + return '' \ No newline at end of file diff --git a/toontown/src/toon/GroupInvitee.py b/toontown/src/toon/GroupInvitee.py new file mode 100644 index 0000000..e376621 --- /dev/null +++ b/toontown/src/toon/GroupInvitee.py @@ -0,0 +1,95 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from toontown.toontowngui import TTDialog +from otp.otpbase import OTPLocalizer +from toontown.toontowngui import ToonHeadDialog +from direct.gui.DirectGui import DGG +from otp.otpbase import OTPGlobals +from toontown.toonbase import TTLocalizer + +class GroupInvitee(ToonHeadDialog.ToonHeadDialog): + """GroupInvitee: + This is a panel that pops up in the middle of your screen whenever + someone invites you to a group. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("GroupInvitee") + + def __init__(self): + pass + + + def make(self, party,toon, leaderId, **kw): + self.leaderId = leaderId + self.avName = toon.getName() + self.av = toon + self.avId = toon.doId + self.avDNA = toon.getStyle() + self.party = party + # Dialog depends upon number of friends in friends list + + text = TTLocalizer.BoardingInviteeMessage % (self.avName) + style = TTDialog.TwoChoice + buttonTextList = [OTPLocalizer.FriendInviteeOK, + OTPLocalizer.FriendInviteeNo] + command = self.__handleButton + + optiondefs = ( + ('dialogName', 'GroupInvitee', None), + ('text', text, None), + ('style', style, None), + ('buttonTextList',buttonTextList, None), + ('command', command, None), + ('image_color', (1.0, 0.89, 0.77, 1.0), None), + # Make the head smaller so the panel can be smaller + ('geom_scale', 0.2, None), + # Don't have to move it over as much + ('geom_pos', (-0.1,0,-0.025), None), + # Reduce padding + ('pad', (0.075,0.075), None), + ('topPad', 0, None), + ('midPad', 0, None), + # Position panel right next to friends panel + ('pos', (0.45, 0, 0.75), None), + ('scale', 0.75, None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + + # initialize our base class. + ToonHeadDialog.ToonHeadDialog.__init__(self, self.avDNA) + + # Add cancel hook + #self.accept('cancelFriendInvitation', self.__handleCancelFromAbove) + + # Initialize dialog + self.initialiseoptions(GroupInvitee) + # Make sure dialog is visible by default + self.show() + + def cleanup(self): + """cleanup(self): + Removes the panel from the screen. + """ + ToonHeadDialog.ToonHeadDialog.cleanup(self) + + def forceCleanup(self): + """ + Cancels any pending request and removes the panel from the screen, unanswered. + This should be called only when the toon leaves the zone with an unanswered panel. + """ + self.party.requestRejectInvite(self.leaderId, self.avId) + self.cleanup() + + ### Button handing methods + def __handleButton(self, value): + # Don't request to leave if the toon is already in the elevator. + # Automatically send a reject if the toon is in the elevator. + place = base.cr.playGame.getPlace() + if (value == DGG.DIALOG_OK) and place and not (place.getState() == 'elevator'): + self.party.requestAcceptInvite(self.leaderId, self.avId) + else: + self.party.requestRejectInvite(self.leaderId, self.avId) + self.cleanup() \ No newline at end of file diff --git a/toontown/src/toon/GroupPanel.py b/toontown/src/toon/GroupPanel.py new file mode 100644 index 0000000..7164b5d --- /dev/null +++ b/toontown/src/toon/GroupPanel.py @@ -0,0 +1,608 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +from toontown.toon import ToonAvatarPanel +from toontown.toontowngui import TTDialog + +class GroupPanel(DirectObject.DirectObject): + """GroupPanel: + Tells you who is in your boarding party/group and provides an interface for leaving. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("GroupPanel") + + def __init__(self, boardingParty): + self.boardingParty = boardingParty + self.leaderId = self.boardingParty.getGroupLeader(localAvatar.doId) + self.elevatorIdList = self.boardingParty.getElevatorIdList() + self.frame = None + self.confirmQuitDialog = None + self.goButton = None + self.destScrollList = None + self.destFrame = None + self.goingToLabel = None + self.destIndexSelected = 0 + self.__load() + self.ignore('stickerBookEntered') + self.accept('stickerBookEntered', self.__forceHide) + self.ignore('stickerBookExited') + self.accept('stickerBookExited', self.__forceShow) + + if __debug__: + base.gpanel = self + + def cleanup(self): + """cleanup(self): + Cancels any pending request and removes the panel from the + screen, unanswered. + """ + # Releasing the left cells of the screen so that chat msgs and arrows can appear there again. + base.setCellsAvailable(base.leftCells, 1) + + self.quitButton.destroy() + self.hideButton.destroy() + self.showButton.destroy() + self.scrollList.destroy() + + if self.goButton: + self.goButton.destroy() + self.goButton = None + + if self.destScrollList: + self.destScrollList.destroy() + self.destScrollList = None + + if self.destFrame: + self.destFrame.destroy() + self.destFrame = None + + if self.goingToLabel: + self.goingToLabel.destroy() + self.goingToLabel = None + + if self.frame: + self.frame.destroy() + self.frame = None + + self.leaveButton = None + self.boardingParty = None + self.ignoreAll() + + def __load(self): + self.guiBg = loader.loadModel('phase_9/models/gui/tt_m_gui_brd_groupListBg') + self.__defineConstants() + + if (self.boardingParty.maxSize == 4): + bgImage = self.guiBg.find('**/tt_t_gui_brd_memberListTop_half') + bgImageZPos = 0.14 + frameZPos = -0.121442 + quitButtonZPos = -0.019958 + else: + bgImage = self.guiBg.find('**/tt_t_gui_brd_memberListTop') + bgImageZPos = 0 + frameZPos = 0.0278943 + quitButtonZPos = -0.30366 + + guiButtons = loader.loadModel('phase_9/models/gui/tt_m_gui_brd_status') + self.frame = DirectFrame( + relief = None, + image = bgImage, + image_scale = (0.5, 1, 0.5), + image_pos = (0, 0, bgImageZPos), + textMayChange = 1, + pos = (-1.044, 0, frameZPos), + ) + self.frameBounds = self.frame.getBounds() + + leaveButtonGui = loader.loadModel('phase_3.5/models/gui/tt_m_gui_brd_leaveBtn') + leaveImageList = (leaveButtonGui.find('**/tt_t_gui_brd_leaveUp'), + leaveButtonGui.find('**/tt_t_gui_brd_leaveDown'), + leaveButtonGui.find('**/tt_t_gui_brd_leaveHover'), + leaveButtonGui.find('**/tt_t_gui_brd_leaveUp')) + + self.leaderButtonImage = guiButtons.find('**/tt_t_gui_brd_statusLeader') + self.availableButtonImage = guiButtons.find('**/tt_t_gui_brd_statusOn') + self.battleButtonImage = guiButtons.find('**/tt_t_gui_brd_statusBattle') + + if (localAvatar.doId == self.leaderId): + quitText = TTLocalizer.QuitBoardingPartyLeader + else: + quitText = TTLocalizer.QuitBoardingPartyNonLeader + + self.disabledOrangeColor = Vec4(1,0.5,0.25,0.9) + + self.quitButton = DirectButton( + parent = self.frame, + relief = None, + image = leaveImageList, + image_scale = 0.065, + command = self.__handleLeaveButton, + text = ("", quitText, quitText, ""), + text_scale = 0.06, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + text_pos = (0.045,0.0), + text_align = TextNode.ALeft, + pos = (0.223, 0, quitButtonZPos), + image3_color = self.disabledOrangeColor, + ) + + arrowGui = loader.loadModel('phase_9/models/gui/tt_m_gui_brd_arrow') + hideImageList = (arrowGui.find('**/tt_t_gui_brd_arrow_up'), + arrowGui.find('**/tt_t_gui_brd_arrow_down'), + arrowGui.find('**/tt_t_gui_brd_arrow_hover')) + showImageList = (arrowGui.find('**/tt_t_gui_brd_arrow_up'), + arrowGui.find('**/tt_t_gui_brd_arrow_down'), + arrowGui.find('**/tt_t_gui_brd_arrow_hover')) + + self.hideButton = DirectButton( + relief = None, + text_pos = (0, 0.15), + text_scale = 0.06, + text_align = TextNode.ALeft, + text_fg = Vec4(0, 0, 0, 1), + text_shadow = Vec4(1, 1, 1, 1), + image = hideImageList, + image_scale = (-0.35, 1, 0.5), + pos = (-1.3081, 0, 0.03), + scale = 1.05, + command = self.hide, + ) + + self.showButton = DirectButton( + relief = None, + text = ("", TTLocalizer.BoardingGroupShow, TTLocalizer.BoardingGroupShow), + text_pos = (0.03, 0), + text_scale = 0.06, + text_align = TextNode.ALeft, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + image = showImageList, + image_scale = (0.35, 1, 0.5), + pos = (-1.3081, 0, 0.03), + scale = 1.05, + command = self.show, + ) + self.showButton.hide() + + self.frame.show() + self.__makeAvatarNameScrolledList() + + if (localAvatar.doId == self.leaderId): + self.__makeDestinationScrolledList() + else: + self.__makeDestinationFrame() + self.__makeGoingToLabel() + + self.accept('updateGroupStatus', self.__checkGroupStatus) + self.accept('ToonBattleIdUpdate', self.__possibleGroupUpdate) + + # Blocking the left cells of the screen so that chat msgs and arrows don't block the gui. + base.setCellsAvailable([base.leftCells[1], base.leftCells[2]], 0) + # Block the bottom left cell only if you are the leader. + # This is because the leader's Group Panel is a little bit longer. + if self.boardingParty.isGroupLeader(localAvatar.doId): + base.setCellsAvailable([base.leftCells[0]], 0) + + # Note: Include this line if you are testing using ~bgui + self.__addTestNames(self.boardingParty.maxSize) + + self.guiBg.removeNode() + guiButtons.removeNode() + leaveButtonGui.removeNode() + arrowGui.removeNode() + + def __defineConstants(self): + self.forcedHidden = False + + self.textFgcolor = NametagGlobals.getNameFg(NametagGroup.CCSpeedChat, PGButton.SInactive) + self.textBgRolloverColor = Vec4(1,1,0,1) + self.textBgDownColor = Vec4(0.5,0.9,1,1) + self.textBgDisabledColor = Vec4(0.4,0.8,0.4,1) + + def __handleLeaveButton(self): + ''' + Confirm if the player really wants to leave the boarding group. + ''' + #if we're clicking on buttons, we're not asleep + messenger.send('wakeup') + + # Show the confirm dialog only if the toon is not already in the elevator. + if not (base.cr.playGame.getPlace().getState() == 'elevator'): + self.confirmQuitDialog = TTDialog.TTDialog( + style = TTDialog.YesNo, + text = TTLocalizer.QuitBoardingPartyConfirm, + command = self.__confirmQuitCallback, + ) + self.confirmQuitDialog.show() + + def __confirmQuitCallback(self, value): + if self.confirmQuitDialog: + self.confirmQuitDialog.destroy() + self.confirmQuitDialog = None + if value > 0: + if self.boardingParty: + self.boardingParty.requestLeave() + + def __handleGoButton(self): + offset = self.destScrollList.getSelectedIndex() + elevatorId = self.elevatorIdList[offset] + self.boardingParty.requestGoToFirstTime(elevatorId) + + def __handleCancelGoButton(self): + self.boardingParty.cancelGoToElvatorDest() + + def __checkGroupStatus(self): + if not self.boardingParty: + return + self.notify.debug("__checkGroupStatus %s" % (self.boardingParty.getGroupMemberList(localAvatar.doId))) + myMemberList = self.boardingParty.getGroupMemberList(localAvatar.doId) + + # Remove and then add all items from the list because the list might have changed. + self.scrollList.removeAndDestroyAllItems(refresh = 0) + if myMemberList: + for avId in myMemberList: + avatarButton = self.__getAvatarButton(avId) + # In case you can't find the toon don't make the avatar button. + if avatarButton: + self.scrollList.addItem(avatarButton, refresh = 0) + self.scrollList.refresh() + + def __possibleGroupUpdate(self, avId): + self.notify.debug("GroupPanel __possibleGroupUpdate") + if not self.boardingParty: + return + myMemberList = self.boardingParty.getGroupMemberList(localAvatar.doId) + if avId in myMemberList: + self.__checkGroupStatus() + + def __makeAvatarNameScrolledList(self): + friendsListGui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + + self.scrollList = DirectScrolledList( + parent = self.frame, + relief = None, + # inc and dec are DirectButtons + incButton_image = (friendsListGui.find("**/FndsLst_ScrollUp"), + friendsListGui.find("**/FndsLst_ScrollDN"), + friendsListGui.find("**/FndsLst_ScrollUp_Rllvr"), + friendsListGui.find("**/FndsLst_ScrollUp"), + ), + incButton_pos = (0.0, 0.0, -0.35), + # Make the disabled button darker + incButton_image1_color = Vec4(1.0, 0.9, 0.4, 0), + incButton_image3_color = Vec4(1.0, 1.0, 0.6, 0), + incButton_scale = (1.0, 1.0, -1.0), + incButton_relief = None, + + decButton_image = (friendsListGui.find("**/FndsLst_ScrollUp"), + friendsListGui.find("**/FndsLst_ScrollDN"), + friendsListGui.find("**/FndsLst_ScrollUp_Rllvr"), + friendsListGui.find("**/FndsLst_ScrollUp"), + ), + decButton_pos = (0.0, 0.0, 0.1), + # Make the disabled button darker + decButton_image1_color = Vec4(1.0, 1.0, 0.6, 0), + decButton_image3_color = Vec4(1.0, 1.0, 0.6, 0), + decButton_relief = None, + + # itemFrame is a DirectFrame + itemFrame_pos = (-0.195, 0.0, 0.185), + itemFrame_borderWidth = (0.1,0.1), + numItemsVisible = 8, + itemFrame_scale = 1.0, + forceHeight = 0.07, + items = [], + pos = (0, 0, 0.075), + ) + # Set up a clipping plane to truncate names that would extend + # off the right end of the scrolled list. + clipper = PlaneNode('clipper') + clipper.setPlane(Plane(Vec3(-1, 0, 0), Point3(0.235, 0, 0))) + clipNP = self.scrollList.attachNewNode(clipper) + self.scrollList.setClipPlane(clipNP) + + friendsListGui.removeNode() + + def __makeDestinationScrolledList(self): + arrowGui = loader.loadModel("phase_9/models/gui/tt_m_gui_brd_gotoArrow") + incrementImageList = (arrowGui.find('**/tt_t_gui_brd_arrowL_gotoUp'), + arrowGui.find('**/tt_t_gui_brd_arrowL_gotoDown'), + arrowGui.find('**/tt_t_gui_brd_arrowL_gotoHover'), + arrowGui.find('**/tt_t_gui_brd_arrowL_gotoUp')) + if (self.boardingParty.maxSize == 4): + zPos = -0.177083 + else: + zPos = -0.463843 + + bottomImage = self.guiBg.find('**/tt_t_gui_brd_memberListBtm_leader') + + self.destScrollList = DirectScrolledList( + parent = self.frame, + relief = None, + image = bottomImage, + image_scale = (0.5, 1, 0.5), + # inc and dec are DirectButtons + incButton_image = incrementImageList, + incButton_pos = (0.217302, 0, 0.07), + # Make the disabled button darker + incButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.5), + incButton_scale = (-0.5, 1, 0.5), + incButton_relief = None, + incButtonCallback = self.__informDestChange, + + decButton_image = incrementImageList, + decButton_pos = (-0.217302, 0, 0.07), + decButton_scale = (0.5, 1, 0.5), + # Make the disabled button darker + decButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.5), + decButton_relief = None, + decButtonCallback = self.__informDestChange, + + # itemFrame is a DirectFrame + itemFrame_pos = (0, 0, 0.06), + itemFrame_borderWidth = (0.1, 0.1), + numItemsVisible = 1, + itemFrame_scale = TTLocalizer.GPdestScrollListScale, + forceHeight = 0.07, + items = [], + + pos = (0, 0, zPos), + scrollSpeed = 0.1, + ) + + arrowGui.removeNode() + + self.__addDestNames() + self.__makeGoButton() + + def __addDestNames(self): + for i in xrange(len(self.elevatorIdList)): + destName = self.__getDestName(i) + self.destScrollList.addItem(destName, refresh = 0) + self.destScrollList.refresh() + + def __getDestName(self, offset): + elevatorId = self.elevatorIdList[offset] + elevator = base.cr.doId2do.get(elevatorId) + if elevator: + destName = elevator.getDestName() + return destName + + def __makeDestinationFrame(self): + ''' + This function makes a frame containing the destination text. + ''' + destName = self.__getDestName(self.destIndexSelected) + + if (self.boardingParty.maxSize == 4): + zPos = -0.12 + else: + zPos = -0.404267 + + bottomImage = self.guiBg.find('**/tt_t_gui_brd_memberListBtm_nonLeader') + + self.destFrame = DirectFrame( + parent = self.frame, + relief = None, + image = bottomImage, + image_scale = (0.5, 1, 0.5), + text = destName, + text_align = TextNode.ACenter, + text_scale = TTLocalizer.GPdestFrameScale, + pos = (0, 0, zPos), + ) + + def __makeGoButton(self): + goGui = loader.loadModel('phase_9/models/gui/tt_m_gui_brd_gotoBtn') + self.goImageList = (goGui.find('**/tt_t_gui_brd_gotoUp'), + goGui.find('**/tt_t_gui_brd_gotoDown'), + goGui.find('**/tt_t_gui_brd_gotoHover'), + goGui.find('**/tt_t_gui_brd_gotoUp')) + + self.cancelGoImageList = (goGui.find('**/tt_t_gui_brd_cancelGotoUp'), + goGui.find('**/tt_t_gui_brd_cancelGotoDown'), + goGui.find('**/tt_t_gui_brd_cancelGotoHover'), + goGui.find('**/tt_t_gui_brd_cancelGotoUp')) + + if (self.boardingParty.maxSize == 4): + zPos = -0.028 + zPos = -0.0360483 + else: + zPos = -0.0353787 + self.goButton = DirectButton( + parent = self.destScrollList, + relief = None, + image = self.goImageList, + image_scale = (0.48, 1, 0.48), + command = self.__handleGoButton, + text = ('', TTLocalizer.BoardingGo, TTLocalizer.BoardingGo, ''), + text_scale = TTLocalizer.GPgoButtonScale, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + text_pos = (0, -0.12), + pos = (-0.003, 0, zPos), + ) + goGui.removeNode() + + def __getAvatarButton(self, avId): + toon = base.cr.doId2do.get(avId) + + # The toon might have left the zone or got disconnected and + # the toon's doId is not there in the doId2do dict + if not toon: + return None + + toonName = toon.getName() + inBattle = 0 + buttonImage = self.availableButtonImage + + if toon.battleId: + # Toon is in battle - change image to buttonImage to battleImage + inBattle = 1 + buttonImage = self.battleButtonImage + # If local avatar is in battle hide the group panel + if (avId == localAvatar.doId): + self.__forceHide() + else: + if (avId == self.leaderId): + # Toon is the group leader - change image to buttonImage to leaderImage + buttonImage = self.leaderButtonImage + # If local avatar is not in battle show the group panel + if (avId == localAvatar.doId): + self.__forceShow() + return DirectButton( + parent = self.frame, + relief = None, + image = buttonImage, + image_scale = (0.06, 1.0, 0.06), + text = toonName, + text_align = TextNode.ALeft, + text_wordwrap = 16, + text_scale = 0.04, + text_pos = (0.05, -0.015), + text_fg = self.textFgcolor, + text1_bg = self.textBgDownColor, + text2_bg = self.textBgRolloverColor, + text3_fg = self.textBgDisabledColor, + pos = (0, 0, 0.2), + command = self.__openToonAvatarPanel, + extraArgs = [toon, avId], + ) + + def __openToonAvatarPanel(self, avatar, avId): + # You should not be able to open your own avatar panel. + if (avId != localAvatar.doId) and avatar: + messenger.send("clickedNametag", [avatar]) + + def __addTestNames(self, num): + for i in xrange(num): + avatarButton = self.__getAvatarButton(localAvatar.doId) + self.scrollList.addItem(avatarButton, refresh = 0) + self.scrollList.refresh() + + def __isForcedHidden(self): + ''' + Returns True if the groupPanel is hidden forcefully. + ''' + if (self.forcedHidden and self.frame.isHidden()): + return True + else: + return False + + def hide(self): + self.frame.hide() + self.hideButton.hide() + self.showButton.show() + + def show(self): + self.frame.show() + self.forcedHidden = False + self.showButton.hide() + self.hideButton.show() + + def __forceHide(self): + ''' + Forcefully hide the groupPanel, either because toon opened + the shticker book or got into a battle. + ''' + if not self.frame.isHidden(): + self.forcedHidden = True + self.hide() + + def __forceShow(self): + ''' + Show the groupPanel ONLY if we have forcefully hidden the group panel. + ''' + if self.__isForcedHidden(): + self.show() + + def __informDestChange(self): + ''' + This function is called everytime the leader changes the destination. + ''' + self.boardingParty.informDestChange(self.destScrollList.getSelectedIndex()) + + def changeDestination(self, offset): + ''' + This function changes the text of the destFrame to reflect the change in destination. + This is done only for a non-leader. + ''' + if (localAvatar.doId != self.leaderId): + self.destIndexSelected = offset + if self.destFrame: + self.destFrame['text'] = self.__getDestName(self.destIndexSelected) + + def scrollToDestination(self, offset): + ''' + This method makes the destination scroll list point to the offset value. + This is only for the leader. + ''' + if (localAvatar.doId == self.leaderId): + if self.destScrollList: + self.destIndexSelected = offset + self.destScrollList.scrollTo(offset) + + def __makeGoingToLabel(self): + if (self.boardingParty.maxSize == 4): + zPos = -0.0466546 + else: + zPos = -0.331731 + self.goingToLabel = DirectLabel( + parent = self.frame, + relief = None, + text = (TTLocalizer.BoardingGoingTo), + text_scale = 0.045, + text_align = TextNode.ALeft, + text_fg = Vec4(0, 0, 0, 1), + pos = (-0.1966, 0, zPos), + ) + + def disableQuitButton(self): + if self.quitButton and not self.quitButton.isEmpty(): + self.quitButton['state'] = DGG.DISABLED + + def enableQuitButton(self): + if self.quitButton and not self.quitButton.isEmpty(): + self.quitButton['state'] = DGG.NORMAL + + def disableGoButton(self): + if self.goButton and not self.goButton.isEmpty(): + self.goButton['state'] = DGG.DISABLED + self.goButton['image_color'] = Vec4(1, 1, 1, 0.4) + + def enableGoButton(self): + if self.goButton and not self.goButton.isEmpty(): + self.goButton['state'] = DGG.NORMAL + self.goButton['image_color'] = Vec4(1, 1, 1, 1) + + def disableDestinationScrolledList(self): + if self.destScrollList and not self.destScrollList.isEmpty(): + self.destScrollList.incButton['state'] = DGG.DISABLED + self.destScrollList.decButton['state'] = DGG.DISABLED + + def enableDestinationScrolledList(self): + if self.destScrollList and not self.destScrollList.isEmpty(): + self.destScrollList.incButton['state'] = DGG.NORMAL + self.destScrollList.decButton['state'] = DGG.NORMAL + + def changeGoToCancel(self): + """ + Change the GO Button to Cancel Go Button. + """ + if self.goButton and not self.goButton.isEmpty(): + self.goButton['image'] = self.cancelGoImageList + self.goButton['text'] = (TTLocalizer.BoardingCancelGo, TTLocalizer.BoardingCancelGo, TTLocalizer.BoardingCancelGo, '') + self.goButton['command'] = self.__handleCancelGoButton + + def changeCancelToGo(self): + """ + Change the Cancel Go Button to the GO Button. + """ + if self.goButton and not self.goButton.isEmpty(): + self.goButton['image'] = self.goImageList + self.goButton['text'] = ('', TTLocalizer.BoardingGo, TTLocalizer.BoardingGo, '') + self.goButton['command'] = self.__handleGoButton \ No newline at end of file diff --git a/toontown/src/toon/HealthForceAcknowledge.py b/toontown/src/toon/HealthForceAcknowledge.py new file mode 100644 index 0000000..3399d68 --- /dev/null +++ b/toontown/src/toon/HealthForceAcknowledge.py @@ -0,0 +1,33 @@ +from pandac.PandaModules import * +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer + +class HealthForceAcknowledge: + def __init__(self, doneEvent): + self.doneEvent = doneEvent + self.dialog = None + + def enter(self, hpLevel): + doneStatus = {} + toonHp = base.localAvatar.getHp() + if (toonHp >= hpLevel): + doneStatus['mode'] = 'complete' + messenger.send(self.doneEvent, [doneStatus]) + else: + # Make the Toon stand still while the panel is up + base.localAvatar.b_setAnimState('neutral', 1) + doneStatus['mode'] = 'incomplete' + self.doneStatus = doneStatus + msg = TTLocalizer.HealthForceAcknowledgeMessage + self.dialog = TTDialog.TTDialog( + text = msg, + command = self.handleOk, + style = TTDialog.Acknowledge) + + def exit(self): + if self.dialog: + self.dialog.cleanup() + self.dialog = None + + def handleOk(self, value): + messenger.send(self.doneEvent, [self.doneStatus]) diff --git a/toontown/src/toon/InventoryBase.py b/toontown/src/toon/InventoryBase.py new file mode 100644 index 0000000..e36d604 --- /dev/null +++ b/toontown/src/toon/InventoryBase.py @@ -0,0 +1,445 @@ +### InventoryBase module: contains the InventoryBase class""" + +from pandac.PandaModules import * +from toontown.toonbase.ToontownBattleGlobals import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator + +class InventoryBase(DirectObject.DirectObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("InventoryBase") + + # special methods + def __init__(self, toon, invStr=None): + """__init__(self, toon, netString=None) + Create a default inv if no netString given, or + create an inv from the given netString. We need the + experience object of the toon to determine max number + of an item and the maxHp to determine the total number + of items. We get these both from the toon. + """ + self.toon = toon + # check to see if config overrides exp + if (invStr == None): + # create default lvl one inv + self.inventory = [] + for track in range(0, len(Tracks)): + level = [] + for thisLevel in range(0, len(Levels[track])): + level.append(0) + self.inventory.append(level) + else: + # de-stringify the one that came in + self.inventory = self.makeFromNetString(invStr) + + self.calcTotalProps() + + def unload(self): + del self.toon + + def __str__(self): + """__str__(self): + Inventory print function + """ + retStr = 'totalProps: %d\n' % (self.totalProps) + for track in range(0, len(Tracks)): + retStr += Tracks[track] + " = " + \ + str(self.inventory[track]) + "\n" + return retStr + + def updateInvString(self, invString): + inventory = self.makeFromNetString(invString) + self.updateInventory(inventory) + return None + + def updateInventory(self, inv): + self.inventory = inv + self.calcTotalProps() + + def makeNetString(self): + """makeNetString(self) + Make a network packet out of the inventory + """ + dataList = self.inventory + datagram = PyDatagram() + for track in range(0, len(Tracks)): + for level in range(0, len(Levels[track])): + datagram.addUint8(dataList[track][level]) + dgi = PyDatagramIterator(datagram) + return dgi.getRemainingBytes() + + def makeFromNetString(self, netString): + """makeFromNetString(self) + Make an inventory from a network packet + """ + dataList = [] + dg = PyDatagram(netString) + dgi = PyDatagramIterator(dg) + for track in range(0, len(Tracks)): + subList = [] + for level in range(0, len(Levels[track])): + if dgi.getRemainingSize() > 0: + value = dgi.getUint8() + else: + value = 0 + subList.append(value) + dataList.append(subList) + return dataList + + def makeFromNetStringForceSize(self, netString, numTracks, numLevels): + """makeFromNetString(self) + Make an inventory from a network packet + """ + dataList = [] + dg = PyDatagram(netString) + dgi = PyDatagramIterator(dg) + for track in range(0, numTracks): + subList = [] + for level in range(0, numLevels): + if dgi.getRemainingSize() > 0: + value = dgi.getUint8() + else: + value = 0 + subList.append(value) + dataList.append(subList) + return dataList + + + # setters and getters + + def addItem(self, track, level): + """addItem(self, [int | string], int): + Add an item to the given track and level + """ + return self.addItems(track, level, 1) + + def addItems(self, track, level, amount): + """addItems(self, [int | string], int, int) + Add amount of an item to the given track and level. Returns total + items in given level and track if successful, 0 if insufficient + skill level, -1 if over item max, -2 if over total max. + """ + if (type(track) == type('')): + track = Tracks.index(track) + + max = self.getMax(track, level) + + # check against current skill level + if hasattr(self.toon,"experience") and hasattr(self.toon.experience, "getExpLevel"): + if (self.toon.experience.getExpLevel(track) >= level) and (self.toon.hasTrackAccess(track)): + # check current against max + if ( self.numItem(track,level) <= (max - amount) ): + #ubergags not limited by gag bag size + if (self.totalProps + amount <= self.toon.getMaxCarry()) or (level > LAST_REGULAR_GAG_LEVEL): + self.inventory[track][level] += amount + self.totalProps += amount + return self.inventory[track][level] + else: + # over total max + return -2 + else: + # over item max + return -1 + else: + # insufficient skill or no access to track + return 0 + else: + # deleted object + return 0 + + def addItemWithList(self, track, levelList): + """ + This is for adding one new item for each of the new gags you earned + on a track during a reward sequence. Generally, levelList will only + contain one level, or no levels. + """ + for level in levelList: + self.addItem(track, level) + + def numItem(self, track, level): + """numItem(self, [int | string], int) + Return the number of items in the given track and level + """ + if (type(track) == type('')): + track = Tracks.index(track) + if track > (len(Tracks) - 1) or level > (len(Levels) - 1): + self.notify.warning("%s is using a gag that doesn't exist %s %s!" % (self.toon.doId, track, level)) + return -1 + return self.inventory[track][level] + + def useItem(self, track, level): + """useItem(self, [int | string], int) + If possible, use one item of given track and level + """ + if type(track) == type(''): + track = Tracks.index(track) + + if self.numItem(track, level) > 0: + self.inventory[track][level] -= 1 + self.calcTotalProps() + elif self.numItem(track, level) == -1: #check for cheaters + return -1 + + def setItem(self, track, level, amount): + """setItem(self, [int | string], int, int) + Set the number of items directly + We should check validity here probably + """ + #print("Setting Item") + if (type(track) == type('')): + track = Tracks.index(track) + + max = self.getMax(track, level) + curAmount = self.numItem(track,level) + + # check against current skill level + if (self.toon.experience.getExpLevel(track) >= level): + # check current against max + if (amount <= max): + if (self.totalProps - curAmount + amount <= self.toon.getMaxCarry()): + self.inventory[track][level] = amount + self.totalProps = self.totalProps - curAmount + amount + return self.inventory[track][level] + else: + # over total max + return -2 + else: + # over item max + return -1 + else: + # insufficient skill + return 0 + + def getMax(self, track, level): + """getMax(self, [int | string], int) + Return the maximum of an item of given track and level + """ + if (type(track) == type('')): + track = Tracks.index(track) + + # find max for this track/level + maxList = CarryLimits[track] + + #RAU we create a dummy DistributedToonAI when we abort from a building battle + #so experience may still be None, if so, just return 0 + if self.toon.experience: + return maxList[self.toon.experience.getExpLevel(track)][level] + else: + return 0 + + + def getTrackAndLevel(self, propName): + """getTrackAndLevel(self, string): + Given a prop name string, return its track and level or + -1, -1 if not found + """ + for track in range(0, len(Tracks)): + if (AvProps[track].count(propName)): + return tracks, AvProps[track].index(propName) + + # else, not found + return -1, -1 + + def calcTotalProps(self): + """calcTotalProps(self): + Tally the current total number of props + """ + self.totalProps = 0 + for track in range(0, len(Tracks)): + for level in range(0, len(Levels[track])): + if level <= LAST_REGULAR_GAG_LEVEL: + self.totalProps += self.numItem(track, level) + + return None + + def countPropsInList(self, invList): + totalProps = 0 + for track in range(len(Tracks)): + for level in range(len(Levels[track])): + if level <= LAST_REGULAR_GAG_LEVEL: + totalProps += invList[track][level] + return(totalProps) + + + #def getMaxTotalProps(self): + # """findMaxTotalProps(self): + # Based on maxHp, determine the total number of props we can carry + # """ + # maxTotalProps = 0 + # for level in range(0, len(MaxProps)): + # if (self.toon.maxHp >= MaxProps[level][0]): + # maxTotalProps = MaxProps[level][1] + # return maxTotalProps + + def setToMin(self, newInventory): + """setToMin(self, newInventory): + Given a new proposed inventory, set each value of the current inventory + to new value, only if it is lower. This is used by the AI when players + attempt to delete items, to prevent cheating. + """ + for track in range(len(Tracks)): + for level in range(len(Levels[track])): + self.inventory[track][level] = min(self.inventory[track][level], + newInventory[track][level]) + + self.calcTotalProps() + return None + + def validateItemsBasedOnExp(self, newInventory, allowUber = 0): + #tempInventory = InventoryBase(none, newInventory) + if type(newInventory) == type("String"): + tempInv = self.makeFromNetString(newInventory) + else: + tempInv = newInventory + for track in range(len(Tracks)): + for level in range(len(Levels[track])): + #print("Track: %s Level: %s" % (track, level)) + if tempInv[track][level] > self.getMax(track, level): + #print("invalid norm %s" % (tempInv[track][level] )) + #import pdb; pdb.set_trace() + return 0 + #UBER no buying of the ubergags + if (level > LAST_REGULAR_GAG_LEVEL) and (tempInv[track][level] > self.inventory[track][level]) or allowUber: + #print("invalid uber") + return 0 + return 1 + + def getMinCostOfPurchase(self, newInventory): + # BUG : check items deleted + return self.countPropsInList(newInventory) - self.totalProps + + def validatePurchase(self, newInventory, currentMoney, newMoney): + """validatePurchase(self, newInventory): + Given a new proposed inventory, and the number of money + that was presumably used to purchase this new inventory, test for + the validity of the purchase. If it is valid, then update the inventory + and new money balance appropriately. + """ + + # Sanity check, you should not be able to make money during the purchase screen + if (newMoney > currentMoney): + self.notify.warning("Somebody lied about their money! Rejecting purchase.") + return 0 + + # First, check to make sure the user didn't overspend + newItemTotal = self.countPropsInList(newInventory) + oldItemTotal = self.totalProps + + # Then, make sure that the user is allowed to purchase the items + # he has chosen. + if (newItemTotal > (oldItemTotal + currentMoney)): + self.notify.warning("Somebody overspent! Rejecting purchase.") + return 0 + + # Make sure they did not buy more items than the new money is + # reporting Note: they could have ended up with no more items + # than they started with but less money if they deleted 10 + # items then bought 10 others. In this case They will have the + # same item total, but 10 less money. There is a hack here + # where a hacked client could delete 10 items, buy 10 others, + # but report no money was spent and we would not catch them, + # but unless we verify each individual buy/delete transation + # with the server, I see no way to prevent this and it is not + # that bad anyways. + if (newItemTotal - oldItemTotal) > (currentMoney - newMoney): + self.notify.warning("Too many items based on money spent! Rejecting purchase.") + return 0 + + # Make sure they did not exceed their maximum carry amount. + if (newItemTotal > self.toon.getMaxCarry()): + self.notify.warning("Cannot carry %s items! Rejecting purchase." % (newItemTotal)) + return 0 + + if self.validateItemsBasedOnExp(newInventory): + # The purchase is valid. + self.updateInventory(newInventory) + return 1 + else: + self.notify.warning("Somebody is trying to buy forbidden items! " + + "Rejecting purchase.") + return 0 + + def maxOutInv(self, filterUberGags = 0): + """maxInv(self): + Iterate over all the props we might be able to use, and keep + adding props until we have reached our max. This is for debugging. + """ + #print("Filter Uber Gags? %s" % (filterUberGags)) + + # First, add at least one gag at each level. + for track in range(len(Tracks)): + if self.toon.hasTrackAccess(track): + for level in range (len(Levels[track])): + if ((level <= LAST_REGULAR_GAG_LEVEL) or (not filterUberGags)): + self.addItem(track, level) + + # Now, add from the top level down, so we end up with mostly + # higher-level gags. + addedAnything = 1 + while addedAnything: + addedAnything = 0 + + for track in range(len(Tracks)): + if self.toon.hasTrackAccess(track): + level = len(Levels[track]) - 1 + if level > LAST_REGULAR_GAG_LEVEL and filterUberGags: + level = LAST_REGULAR_GAG_LEVEL + result = self.addItem(track, level) + level -= 1 + while result <= 0 and level >= 0: + result = self.addItem(track, level) + level -= 1 + if result > 0: + addedAnything = 1 + + self.calcTotalProps() + return None + + def NPCMaxOutInv(self, targetTrack = -1): + """NPCMaxOutInv(self): + Iterate over all the props we might be able to use, and keep + adding props until we have reached our max. + """ + + # Go through the highest level(s) of gags and add + # as many as possible, then move down to the next + # highest + for level in range(5, -1, -1): + anySpotsAvailable = 1 + while (anySpotsAvailable == 1): + anySpotsAvailable = 0 + trackResults = [] + for track in range(len(Tracks)): + if (targetTrack != -1 and targetTrack != track): + continue + result = self.addItem(track, level) + #print "track: %d level: %d result: %d" % (track, level, result) + trackResults.append(result) + if (result == -2): + break + # See if we need to make another pass + for res in trackResults: + if (res > 0): + anySpotsAvailable = 1 + if (result == -2): + break + + self.calcTotalProps() + return None + + def zeroInv(self, killUber = 0): + """maxInv(self): + Erase all our props. + #uber gags are excluded from this prop reset + """ + for track in range(len(Tracks)): + for level in range(UBER_GAG_LEVEL_INDEX):# (len(Levels[track])): + self.inventory[track][level] = 0 + if killUber: + self.inventory[track][UBER_GAG_LEVEL_INDEX] = 0 + if self.inventory[track][UBER_GAG_LEVEL_INDEX] > 1: + self.inventory[track][UBER_GAG_LEVEL_INDEX] = 1 + self.calcTotalProps() + return None diff --git a/toontown/src/toon/InventoryNew.py b/toontown/src/toon/InventoryNew.py new file mode 100644 index 0000000..3d39dee --- /dev/null +++ b/toontown/src/toon/InventoryNew.py @@ -0,0 +1,1829 @@ +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase.ToontownBattleGlobals import * +import InventoryBase +from toontown.toonbase import TTLocalizer +from toontown.quest import BlinkingArrows +from direct.interval.IntervalGlobal import * +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPGlobals + +class InventoryNew(InventoryBase.InventoryBase, DirectFrame): + + notify = DirectNotifyGlobal.directNotify.newCategory("InventoryNew") + + PressableTextColor = Vec4(1,1,1,1) + PressableGeomColor = Vec4(1,1,1,1) + PressableImageColor = Vec4(0,0.6,1,1) + + PropBonusPressableImageColor = Vec4(1.0, 0.6, 0.0, 1) + + NoncreditPressableImageColor = Vec4(0.3,0.6,0.6,1) + + PropBonusNoncreditPressableImageColor = Vec4(0.6,0.6,0.3,1) + + DeletePressableImageColor = Vec4(0.7,0.1,0.1,1) + + UnpressableTextColor = Vec4(1,1,1,0.3) + UnpressableGeomColor = Vec4(1,1,1,0.3) + UnpressableImageColor = Vec4(0.3,0.3,0.3,0.8) + + BookUnpressableTextColor = Vec4(1,1,1,1) + BookUnpressableGeomColor = Vec4(1,1,1,1) + BookUnpressableImage0Color = Vec4(0,0.6,1,1) + BookUnpressableImage2Color = Vec4(0.1,0.7,1,1) + + ShadowColor = Vec4(0, 0, 0, 0) + ShadowBuffedColor = Vec4(1, 1, 1, 1) + UnpressableShadowBuffedColor = Vec4(1, 1, 1, 0.3) + + TrackYOffset = 0.0 + TrackYSpacing = -0.12 + ButtonXOffset = -0.31 + ButtonXSpacing = 0.180 + + def __init__(self, toon, invStr=None, ShowSuperGags = 1): + """__init__(self, toon, netString=None) + Create a default inv if no netString given, or + create an inv from the given netString. We need the + experience object of the toon to determine max number + of an item and the maxHp to determine the total number + of items. We get these both from the toon. + """ + InventoryBase.InventoryBase.__init__(self, toon, invStr) + DirectFrame.__init__(self, relief=None) + self.initialiseoptions(InventoryNew) + + self.battleCreditLevel = None + self.detailCredit = None + self.__battleCreditMultiplier = 1 + self.__invasionCreditMultiplier = 1 + self.__respectInvasions = 1 + self._interactivePropTrackBonus = -1 + self.tutorialFlag = 0 + self.gagTutMode = 0 + self.showSuperGags = ShowSuperGags + self.clickSuperGags = 1 + self.propAndOrganicBonusStack = base.config.GetBool('prop-and-organic-bonus-stack', 0) + self.propBonusIval = Parallel() + self.activateMode = 'book' + self.load() + # Hide until we need it + self.hide() + return + + def setBattleCreditMultiplier(self, mult): + self.__battleCreditMultiplier = mult + + def getBattleCreditMultiplier(self): + return self.__battleCreditMultiplier + + def setInteractivePropTrackBonus(self, trackBonus): + """Handle the battle telling us it has a prop bonus.""" + self._interactivePropTrackBonus = trackBonus + + def getInteractivePropTrackBonus(self): + """Return if we're in a battle with a prop bonus.""" + return self._interactivePropTrackBonus + + def setInvasionCreditMultiplier(self, mult): + self.__invasionCreditMultiplier = mult + + def getInvasionCreditMultiplier(self): + return self.__invasionCreditMultiplier + + def setRespectInvasions(self, flag): + # Set this to false not to promise bonus credit during an + # invasion. This is done, for instance, during the final boss + # battle, which ignores the invasion. + self.__respectInvasions = flag + + def getRespectInvasions(self): + return self.__respectInvasions + + + def show(self): + if self.tutorialFlag: + # Show the arrows and sign + self.tutArrows.arrowsOn(-0.43, -0.12, 180, -0.43, -0.24, 180, + onTime = 1.0, offTime = 0.2) + # Only show the arrow for the gag we have. + if self.numItem(THROW_TRACK, 0) == 0: + self.tutArrows.arrow1.reparentTo(hidden) + else: + # The 1 is sort order... This puts it on top of the battle + # row track thingies. + self.tutArrows.arrow1.reparentTo(self.battleFrame, 1) + if self.numItem(SQUIRT_TRACK, 0) == 0: + self.tutArrows.arrow2.reparentTo(hidden) + else: + self.tutArrows.arrow2.reparentTo(self.battleFrame, 1) + # Show the text + self.tutText.show() + self.tutText.reparentTo(self.battleFrame, 1) + DirectFrame.show(self) + + + def uberGagToggle(self,showSuperGags = 1): + self.showSuperGags = showSuperGags + #allow not showing the super gags + for itemList in self.invModels: + for itemIndex in range(MAX_LEVEL_INDEX + 1): + if itemIndex <= (LAST_REGULAR_GAG_LEVEL + 1) or self.showSuperGags: + itemList[itemIndex].show() + else: + itemList[itemIndex].hide() + + for buttonList in self.buttons: + for buttonIndex in range(MAX_LEVEL_INDEX + 1): + if buttonIndex <= (LAST_REGULAR_GAG_LEVEL) or self.showSuperGags: + buttonList[buttonIndex].show() + else: + buttonList[buttonIndex].hide() + + def enableUberGags(self, enableSG = -1): + if enableSG != -1: + self.clickSuperGags = enableSG + for buttonList in self.buttons: + for buttonIndex in range((LAST_REGULAR_GAG_LEVEL + 1), (MAX_LEVEL_INDEX + 1)): + if self.clickSuperGags: + pass + #buttonList[buttonIndex]['state'] = DGG.NORMAL + else: + self.makeUnpressable(buttonList[buttonIndex], self.buttons.index(buttonList), buttonIndex) + #buttonList[buttonIndex]['state'] = DGG.DISABLED + + def hide(self): + # Stop associated tasks when hiding this object. + if self.tutorialFlag: + self.tutArrows.arrowsOff() + self.tutText.hide() + DirectFrame.hide(self) + + def updateTotalPropsText(self): + textTotal = (TTLocalizer.InventoryTotalGags % + (self.totalProps, self.toon.getMaxCarry())) + if localAvatar.getPinkSlips() > 1: + textTotal = textTotal + "\n\n" + (TTLocalizer.InventroyPinkSlips % + (localAvatar.getPinkSlips() )) + elif localAvatar.getPinkSlips() == 1: + textTotal = textTotal + "\n\n" + TTLocalizer.InventroyPinkSlip + + self.totalLabel["text"] = textTotal + return + + def unload(self): + self.notify.debug("Unloading Inventory for %d" % self.toon.doId) + self.stopAndClearPropBonusIval() + self.propBonusIval.finish() + self.propBonusIval = None + del self.invModels + self.buttonModels.removeNode() + del self.buttonModels + del self.upButton + del self.downButton + del self.rolloverButton + del self.flatButton + del self.invFrame + del self.battleFrame + del self.purchaseFrame + del self.storePurchaseFrame + self.deleteEnterButton.destroy() + del self.deleteEnterButton + self.deleteExitButton.destroy() + del self.deleteExitButton + del self.detailFrame + del self.detailNameLabel + del self.detailAmountLabel + del self.detailDataLabel + del self.totalLabel + for row in self.trackRows: + row.destroy() + del self.trackRows + del self.trackNameLabels + del self.trackBars + for buttonList in self.buttons: + for buttonIndex in range(MAX_LEVEL_INDEX + 1): + buttonList[buttonIndex].destroy() + del self.buttons + InventoryBase.InventoryBase.unload(self) + DirectFrame.destroy(self) + return + + def load(self): + self.notify.debug("Loading Inventory for %d" % self.toon.doId) + invModel = loader.loadModel("phase_3.5/models/gui/inventory_icons") + + # Find all the inventory models and cache them + self.invModels = [] + for track in range(len(AvPropsNew)): + itemList = [] + for item in range(len(AvPropsNew[track])): + itemList.append(invModel.find("**/" + AvPropsNew[track][item])) + self.invModels.append(itemList) + + invModel.removeNode() + del invModel + + + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.rowModel = self.buttonModels.find("**/InventoryRow") + self.upButton = self.buttonModels.find("**/InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find("**/InventoryButtonRollover") + self.flatButton = self.buttonModels.find("**/InventoryButtonFlat") + + self.invFrame = DirectFrame(relief = None, parent = self) + + self.battleFrame = None + self.purchaseFrame = None + self.storePurchaseFrame = None + + # The delete buttons + trashcanGui = loader.loadModel("phase_3/models/gui/trashcan_gui") + self.deleteEnterButton = DirectButton( + parent = self.invFrame, + image = (trashcanGui.find("**/TrashCan_CLSD"), + trashcanGui.find("**/TrashCan_OPEN"), + trashcanGui.find("**/TrashCan_RLVR")), + text = ("", TTLocalizer.InventoryDelete, TTLocalizer.InventoryDelete), + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = 0.1, + text_pos = (0, -0.1), + text_font = getInterfaceFont(), + textMayChange = 0, + relief = None, + pos = (-1, 0, -0.35), + scale = 1.0, + ) + + self.deleteExitButton = DirectButton( + parent = self.invFrame, + image = (trashcanGui.find("**/TrashCan_OPEN"), + trashcanGui.find("**/TrashCan_CLSD"), + trashcanGui.find("**/TrashCan_RLVR")), + text = ("", TTLocalizer.InventoryDone, TTLocalizer.InventoryDone), + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = 0.1, + text_pos = (0, -0.1), + text_font = getInterfaceFont(), + textMayChange = 0, + relief = None, + pos = (-1, 0, -0.35), + scale = 1.0, + ) + trashcanGui.removeNode() + + self.deleteHelpText = DirectLabel( + parent = self.invFrame, + relief = None, + pos = (0.272, 0.3, -0.907), + text = TTLocalizer.InventoryDeleteHelp, + text_fg = (0,0,0,1), + text_scale = 0.08, + textMayChange = 0, + ) + self.deleteHelpText.hide() + + + # The detail window + self.detailFrame = DirectFrame( + parent = self.invFrame, + relief = None, + pos = (1.05, 0, -0.08), + ) + + self.detailNameLabel = DirectLabel( + parent = self.detailFrame, + text = "", + text_scale = TTLocalizer.INdetailNameLabel, + text_fg = (0.05, 0.14, 0.4, 1), + scale = 0.045, + pos = (0, 0, 0), + text_font = getInterfaceFont(), + relief = None, + image = self.invModels[0][0], + ) + + self.detailAmountLabel = DirectLabel( + parent = self.detailFrame, + text = "", + text_fg = (0.05, 0.14, 0.4, 1), + scale = 0.04, + pos = (0.16, 0, -0.175), + text_font = getInterfaceFont(), + text_align = TextNode.ARight, + relief = None, + ) + + self.detailDataLabel = DirectLabel( + parent = self.detailFrame, + text = "", + text_fg = (0.05, 0.14, 0.4, 1), + scale = 0.04, + pos = (-0.22, 0, -0.24), + text_font = getInterfaceFont(), + text_align = TextNode.ALeft, + relief = None, + ) + + self.detailCreditLabel = DirectLabel( + parent = self.detailFrame, + text = TTLocalizer.InventorySkillCreditNone, + text_fg = (0.05, 0.14, 0.4, 1), + scale = 0.04, + pos = (-0.22, 0, -0.365), + text_font = getInterfaceFont(), + text_align = TextNode.ALeft, + relief = None, + ) + self.detailCreditLabel.hide() + + # The total props text + self.totalLabel = DirectLabel( + text = "", + parent = self.detailFrame, + pos = (0, 0, -0.095), + scale = 0.05, + text_fg = (0.05, 0.14, 0.4, 1), + text_font = getInterfaceFont(), + relief = None + ) + self.updateTotalPropsText() + + self.trackRows = [] + self.trackNameLabels = [] + self.trackBars = [] + self.buttons = [] + + # Set up row frames + for track in range(0, len(Tracks)): + + trackFrame = DirectFrame( + parent = self.invFrame, + image = self.rowModel, + scale = (1.0,1.0,1.1), + pos = (0, 0.3, self.TrackYOffset + track * self.TrackYSpacing), + image_color = (TrackColors[track][0], + TrackColors[track][1], + TrackColors[track][2], + 1), + state = DGG.NORMAL, + relief = None) + + trackFrame.bind(DGG.WITHIN, self.enterTrackFrame, extraArgs=[track]) + trackFrame.bind(DGG.WITHOUT, self.exitTrackFrame, extraArgs=[track]) + self.trackRows.append(trackFrame) + + # Add the name of the track + adjustLeft = -0.065 + self.trackNameLabels.append(DirectLabel( + text = TextEncoder.upper(Tracks[track]), + parent = self.trackRows[track], + pos = (-0.72 + adjustLeft, -0.1, 0.01), + scale = TTLocalizer.INtrackNameLabels, + relief = None, + text_fg = (0.2, 0.2, 0.2, 1), + text_font = getInterfaceFont(), + text_align = TextNode.ALeft, + textMayChange = 0, + )) + + self.trackBars.append(DirectWaitBar( + parent = self.trackRows[track], + pos = (-0.58 + adjustLeft, -0.1, -0.025), + relief = DGG.SUNKEN, + frameSize = (-0.6,0.6,-0.1,0.1), + borderWidth = (0.02,0.02), + scale = 0.25, + frameColor = (TrackColors[track][0]*0.6, + TrackColors[track][1]*0.6, + TrackColors[track][2]*0.6, + 1), + barColor = (TrackColors[track][0]*0.9, + TrackColors[track][1]*0.9, + TrackColors[track][2]*0.9, + 1), + text = "0 / 0", + text_scale = 0.16, + text_fg = (0, 0, 0, 0.8), + text_align = TextNode.ACenter, + text_pos = (0,-0.05), + )) + + self.buttons.append([]) + + for item in range(0, len(Levels[track])): + button = DirectButton( + parent = self.trackRows[track], + image = (self.upButton, + self.downButton, + self.rolloverButton, + self.flatButton), + geom = self.invModels[track][item], + text = "50", + text_scale = 0.04, + text_align = TextNode.ARight, + geom_scale = 0.70, + geom_pos = (-0.01, -0.1, 0), + text_fg = Vec4(1, 1, 1, 1), + text_pos = (0.07, -0.04), + textMayChange = 1, + relief = None, + image_color = (0, 0.6, 1, 1), + pos = ((self.ButtonXOffset + item * self.ButtonXSpacing) + adjustLeft, -0.1, 0), + command = self.__handleSelection, + extraArgs = [track, item], + ) + # Bind rollover + button.bind(DGG.ENTER, self.showDetail, extraArgs = [track, item]) + button.bind(DGG.EXIT, self.hideDetail) + + self.buttons[track].append(button) + return + + def __handleSelection(self, track, level): + #print("__handleSelection") + if ((self.activateMode == "purchaseDelete") or + (self.activateMode == "bookDelete") or + (self.activateMode == "storePurchaseDelete")): + if self.numItem(track, level): + self.useItem(track, level) + self.updateGUI(track, level) + messenger.send("inventory-deletion", [track, level]) + # Update the detail panel + self.showDetail(track, level) + elif ((self.activateMode == "purchase") or + (self.activateMode == "storePurchase")): + messenger.send("inventory-selection", [track, level]) + # Update the detail panel + self.showDetail(track, level) + else: + if self.gagTutMode: + # in the gag tutorial, if the user is broke, + pass + else: + messenger.send("inventory-selection", [track, level]) + + def __handleRun(self): + messenger.send("inventory-run") + return + + def __handleFire(self): + messenger.send("inventory-fire") + return + + def __handleSOS(self): + messenger.send("inventory-sos") + return + + def __handlePass(self): + messenger.send("inventory-pass") + return + + def __handleBackToPlayground(self): + messenger.send('inventory-back-to-playground') + return + + def showDetail(self, track, level, event=None): + self.totalLabel.hide() + self.detailNameLabel.show() + + # Configure the image first + self.detailNameLabel.configure(text = AvPropStrings[track][level], + image_image = self.invModels[track][level], + ) + # Then set the image scale and pos after the image has been created + self.detailNameLabel.configure(image_scale = 20, + image_pos = (-0.2, 0, -2.2), + ) + + self.detailAmountLabel.show() + self.detailAmountLabel.configure(text = TTLocalizer.InventoryDetailAmount % + {"numItems": self.numItem(track, level), + "maxItems": self.getMax(track, level),}) + + self.detailDataLabel.show() + damage = getAvPropDamage(track, level, self.toon.experience.getExp(track)) + # add additional text if the gag is buffed (via gardening) + organicBonus= self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + damageBonusStr = "" + damageBonus = 0 + if self.propAndOrganicBonusStack: + if propBonus: + damageBonus += getDamageBonus(damage) + if organicBonus: + damageBonus += getDamageBonus(damage) + if damageBonus: + damageBonusStr = TTLocalizer.InventoryDamageBonus % damageBonus + else: + if propBonus or organicBonus: + damageBonus += getDamageBonus(damage) + if damageBonus: + damageBonusStr = TTLocalizer.InventoryDamageBonus % damageBonus + + accString = AvTrackAccStrings[track] + if (organicBonus or propBonus) and track == LURE_TRACK: + accString = TTLocalizer.BattleGlobalLureAccMedium + + self.detailDataLabel.configure(text = TTLocalizer.InventoryDetailData % + {"accuracy": accString, + "damageString": self.getToonupDmgStr(track, level), + "damage": damage, + "bonus": damageBonusStr, + "singleOrGroup":self.getSingleGroupStr(track, level),}) + + if self.itemIsCredit(track, level): + mult = self.__battleCreditMultiplier + if self.__respectInvasions: + mult *= self.__invasionCreditMultiplier + self.setDetailCredit(track, (level + 1) * mult) + else: + self.setDetailCredit(track, None) + self.detailCreditLabel.show() + + return + + def setDetailCredit(self, track, credit): + if credit != None: + # Cap the credit based on the Toon's earned experience so + # far. + if self.toon.earnedExperience: + maxCredit = ExperienceCap - self.toon.earnedExperience[track] + credit = min(credit, maxCredit) + + # Round the credit to 1 decimal place, and make it be an + # integer if it ends in .0, for display. + credit = int(credit * 10 + 0.5) + if (credit % 10) == 0: + credit /= 10 + else: + credit /= 10.0 + + if self.detailCredit == credit: + return + + if credit != None: + self.detailCreditLabel['text'] = TTLocalizer.InventorySkillCredit % (credit) + if self.detailCredit == None: + self.detailCreditLabel['text_fg'] = (0.05, 0.14, 0.4, 1) + else: + self.detailCreditLabel['text'] = TTLocalizer.InventorySkillCreditNone + self.detailCreditLabel['text_fg'] = (0.5, 0.0, 0.0, 1.0) + + self.detailCredit = credit + + def hideDetail(self, event=None): + self.totalLabel.show() + self.detailNameLabel.hide() + self.detailAmountLabel.hide() + self.detailDataLabel.hide() + self.detailCreditLabel.hide() + return + + def noDetail(self): + # Useful for the toontorial + self.totalLabel.hide() + self.detailNameLabel.hide() + self.detailAmountLabel.hide() + self.detailDataLabel.hide() + self.detailCreditLabel.hide() + return + + def setActivateMode(self, mode, heal=1, trap=1, lure=1, bldg=0, + creditLevel=None, tutorialFlag=0, gagTutMode=0): + self.notify.debug("setActivateMode() mode:%s heal:%s trap:%s lure:%s bldg:%s" % + (mode, heal, trap, lure, bldg)) + self.previousActivateMode = self.activateMode + self.activateMode = mode + self.deactivateButtons() + self.heal = heal + self.trap = trap + self.lure = lure + self.bldg = bldg + self.battleCreditLevel = creditLevel + self.tutorialFlag = tutorialFlag + self.gagTutMode = gagTutMode + self.__activateButtons() + self.enableUberGags() + return None + + def setActivateModeBroke(self): + # We are broke. Sets the activate mode to either + # purchaseBroke or storePurchaseBroke, whichever is + # appropriate. + if self.activateMode == 'storePurchase': + self.setActivateMode('storePurchaseBroke') + elif self.activateMode == 'purchase': + # preserve the gagTut mode flag if we go broke during the tutorial + self.setActivateMode('purchaseBroke', gagTutMode=self.gagTutMode) + else: + self.notify.error("Unexpected mode in setActivateModeBroke(): %s" % (self.activateMode)) + self.enableUberGags() + + def deactivateButtons(self): + if self.previousActivateMode == 'book': + self.bookDeactivateButtons() + elif self.previousActivateMode == 'bookDelete': + self.bookDeleteDeactivateButtons() + elif self.previousActivateMode == 'purchaseDelete': + self.purchaseDeleteDeactivateButtons() + elif self.previousActivateMode == 'purchase': + self.purchaseDeactivateButtons() + elif self.previousActivateMode == 'purchaseBroke': + self.purchaseBrokeDeactivateButtons() + elif self.previousActivateMode == 'gagTutDisabled': + self.gagTutDisabledDeactivateButtons() + elif self.previousActivateMode == 'battle': + self.battleDeactivateButtons() + elif self.previousActivateMode == 'storePurchaseDelete': + self.storePurchaseDeleteDeactivateButtons() + elif self.previousActivateMode == 'storePurchase': + self.storePurchaseDeactivateButtons() + elif self.previousActivateMode == 'storePurchaseBroke': + self.storePurchaseBrokeDeactivateButtons() + elif self.previousActivateMode == 'plantTree': + self.plantTreeDeactivateButtons() + else: + self.notify.error("No such mode as %s" % self.previousActivateMode) + return None + + def __activateButtons(self): + #print("__activateButtons %s" % (self.activateMode)) + if hasattr(self, "activateMode"): + if self.activateMode == 'book': + self.bookActivateButtons() + elif self.activateMode == 'bookDelete': + self.bookDeleteActivateButtons() + elif self.activateMode == 'purchaseDelete': + self.purchaseDeleteActivateButtons() + elif self.activateMode == 'purchase': + self.purchaseActivateButtons() + elif self.activateMode == 'purchaseBroke': + self.purchaseBrokeActivateButtons() + elif self.activateMode == 'gagTutDisabled': + self.gagTutDisabledActivateButtons() + elif self.activateMode == 'battle': + self.battleActivateButtons() + elif self.activateMode == 'storePurchaseDelete': + self.storePurchaseDeleteActivateButtons() + elif self.activateMode == 'storePurchase': + self.storePurchaseActivateButtons() + elif self.activateMode == 'storePurchaseBroke': + self.storePurchaseBrokeActivateButtons() + elif self.activateMode == 'plantTree': + self.plantTreeActivateButtons() + else: + self.notify.error("No such mode as %s" % self.activateMode) + return None + + def bookActivateButtons(self): + """ + This activates and deactivates buttons appropriate to book mode. + """ + self.setPos(0,0,0.52) + self.setScale(1.0) + + self.detailFrame.setPos(0.1, 0, -0.855) + self.detailFrame.setScale(0.75) + + # Hide the delete button (until somebody proves we need it here) + self.deleteEnterButton.hide() + self.deleteEnterButton.setPos(1.029, 0, -0.639) + self.deleteEnterButton.setScale(0.75) + self.deleteExitButton.hide() + self.deleteExitButton.setPos(1.029, 0, -0.639) + self.deleteExitButton.setScale(0.75) + + self.invFrame.reparentTo(self) + self.invFrame.setPos(0,0,0) + self.invFrame.setScale(1) + + # If someone clicks on the delete button, switch to delete mode + self.deleteEnterButton['command'] = self.setActivateMode + self.deleteEnterButton['extraArgs'] = ['bookDelete'] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In book mode, all buttons are unpressable, + # but they are special unpressable + self.makeBookUnpressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return None + + def bookDeactivateButtons(self): + self.deleteEnterButton['command'] = None + return + + def bookDeleteActivateButtons(self): + messenger.send("enterBookDelete") + self.setPos(-0.2,0,0.4) + self.setScale(0.8) + + # Show the delete button + self.deleteEnterButton.hide() + self.deleteEnterButton.setPos(1.029, 0, -0.639) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.show() + self.deleteExitButton.setPos(1.029, 0, -0.639) + self.deleteExitButton.setScale(0.75) + + self.deleteHelpText.show() + + self.invFrame.reparentTo(self) + self.invFrame.setPos(0,0,0) + self.invFrame.setScale(1) + + # If someone clicks on the delete button, switch to delete mode + self.deleteExitButton['command'] = self.setActivateMode + self.deleteExitButton['extraArgs'] = [self.previousActivateMode] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item + if self.numItem(track, level) <= 0: + self.makeUnpressable(button, track, level) + else: + self.makeDeletePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return + + + def bookDeleteDeactivateButtons(self): + messenger.send("exitBookDelete") + self.deleteHelpText.hide() + self.deleteDeactivateButtons() + return + + def purchaseDeleteActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0.2, 0, -0.04) + self.setScale(1) + if self.purchaseFrame == None: + self.loadPurchaseFrame() + self.purchaseFrame.show() + + self.invFrame.reparentTo(self.purchaseFrame) + self.invFrame.setPos(-0.235, 0, 0.52) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.17, 0, -0.02) + self.detailFrame.setScale(1.25) + + self.deleteEnterButton.hide() + self.deleteEnterButton.setPos(-0.441, 0, -0.917) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.show() + self.deleteExitButton.setPos(-0.441, 0, -0.917) + self.deleteExitButton.setScale(0.75) + + # If someone clicks on the delete button, switch back to purchase + self.deleteExitButton[ + 'command'] = self.setActivateMode + self.deleteExitButton['extraArgs'] = [self.previousActivateMode] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item + if (self.numItem(track, level) <= 0) or (level >= UBER_GAG_LEVEL_INDEX): + self.makeUnpressable(button, track, level) + else: + self.makeDeletePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return + + def purchaseDeleteDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.purchaseFrame.hide() + self.deleteDeactivateButtons() + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item + if (self.numItem(track, level) <= 0) or (level >= UBER_GAG_LEVEL_INDEX): + self.makeUnpressable(button, track, level) + else: + self.makeDeletePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + + return + + def storePurchaseDeleteActivateButtons(self): + #print("storePurchaseDeleteActivateButtons") + self.reparentTo(aspect2d) + self.setPos(0.2,0,-0.04) + self.setScale(1) + if self.storePurchaseFrame == None: + self.loadStorePurchaseFrame() + self.storePurchaseFrame.show() + + self.invFrame.reparentTo(self.storePurchaseFrame) + self.invFrame.setPos(-0.23, 0, 0.505) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.175, 0, 0) + self.detailFrame.setScale(1.25) + + self.deleteEnterButton.hide() + self.deleteEnterButton.setPos(-0.55, 0, -0.91) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.show() + self.deleteExitButton.setPos(-0.55, 0, -0.91) + self.deleteExitButton.setScale(0.75) + + # If someone clicks on the delete button, switch back to purchase + self.deleteExitButton['command'] = self.setActivateMode + self.deleteExitButton['extraArgs'] = [self.previousActivateMode] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item + if (self.numItem(track, level) <= 0) or (level >= UBER_GAG_LEVEL_INDEX): + self.makeUnpressable(button, track, level) + else: + self.makeDeletePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return + + def storePurchaseDeleteDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.storePurchaseFrame.hide() + self.deleteDeactivateButtons() + return + + def storePurchaseBrokeActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0.2,0,-0.04) + self.setScale(1) + + if self.storePurchaseFrame == None: + self.loadStorePurchaseFrame() + self.storePurchaseFrame.show() + + self.invFrame.reparentTo(self.storePurchaseFrame) + self.invFrame.setPos(-0.23, 0, 0.505) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.175, 0, 0) + self.detailFrame.setScale(1.25) + + self.deleteEnterButton.show() + self.deleteEnterButton.setPos(-0.55, 0, -0.91) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.hide() + self.deleteExitButton.setPos(-0.551, 0, -0.91) + self.deleteExitButton.setScale(0.75) + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + self.makeUnpressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return + + def storePurchaseBrokeDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.storePurchaseFrame.hide() + return + + def deleteActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0,0,0) + self.setScale(1) + self.deleteEnterButton.hide() + self.deleteExitButton.show() + + # If someone clicks on the delete button, switch to the previous mode + self.deleteExitButton['command'] = self.setActivateMode + self.deleteExitButton['extraArgs'] = [self.previousActivateMode] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item + if self.numItem(track, level) <= 0: + self.makeUnpressable(button, track, level) + else: + self.makePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return None + + def deleteDeactivateButtons(self): + self.deleteExitButton['command'] = None + return + + def purchaseActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0.2, 0, -0.04) + self.setScale(1) + if self.purchaseFrame == None: + self.loadPurchaseFrame() + self.purchaseFrame.show() + + self.invFrame.reparentTo(self.purchaseFrame) + self.invFrame.setPos(-0.235, 0, 0.52) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.17, 0, -0.02) + self.detailFrame.setScale(1.25) + totalProps = self.totalProps + maxProps = self.toon.getMaxCarry() + + self.deleteEnterButton.show() + self.deleteEnterButton.setPos(-0.441, 0, -0.917) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.hide() + self.deleteExitButton.setPos(-0.441, 0, -0.917) + self.deleteExitButton.setScale(0.75) + + # hide the trash can in the gag tutorial + if self.gagTutMode: + self.deleteEnterButton.hide() + + # If someone clicks on the delete button, switch to delete mode + self.deleteEnterButton['command'] = self.setActivateMode + self.deleteEnterButton['extraArgs'] = ['purchaseDelete'] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + if ((self.numItem(track, level) >= + self.getMax(track, level)) or + (totalProps == maxProps) or + (level > LAST_REGULAR_GAG_LEVEL)): + self.makeUnpressable(button, track, level) + else: + self.makePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return None + + def purchaseDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.purchaseFrame.hide() + return + + def storePurchaseActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0.2, 0, -0.04) + self.setScale(1) + if self.storePurchaseFrame == None: + self.loadStorePurchaseFrame() + self.storePurchaseFrame.show() + + self.invFrame.reparentTo(self.storePurchaseFrame) + self.invFrame.setPos(-0.23, 0, 0.505) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.175, 0, 0) + self.detailFrame.setScale(1.25) + totalProps = self.totalProps + maxProps = self.toon.getMaxCarry() + + self.deleteEnterButton.show() + self.deleteEnterButton.setPos(-0.55, 0, -0.91) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.hide() + self.deleteExitButton.setPos(-0.55, 0, -0.91) + self.deleteExitButton.setScale(0.75) + + # If someone clicks on the delete button, switch to delete mode + self.deleteEnterButton['command'] = self.setActivateMode + self.deleteEnterButton['extraArgs'] = ['storePurchaseDelete'] + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + if ((self.numItem(track, level) >= + self.getMax(track, level)) or + (totalProps == maxProps) + or level > LAST_REGULAR_GAG_LEVEL): + self.makeUnpressable(button, track, level) + else: + self.makePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return None + + def storePurchaseDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.storePurchaseFrame.hide() + return + + def purchaseBrokeActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0.2, 0, -0.04) + self.setScale(1) + + if self.purchaseFrame == None: + self.loadPurchaseFrame() + self.purchaseFrame.show() + + self.invFrame.reparentTo(self.purchaseFrame) + self.invFrame.setPos(-0.235, 0, 0.52) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.17, 0, -0.02) + self.detailFrame.setScale(1.25) + + self.deleteEnterButton.show() + self.deleteEnterButton.setPos(-0.441, 0, -0.917) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.hide() + self.deleteExitButton.setPos(-0.441, 0, -0.917) + self.deleteExitButton.setScale(0.75) + + # hide the trash can in the gag tutorial + if self.gagTutMode: + self.deleteEnterButton.hide() + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # don't disable the buttons in the gag tutorial; + # intercept and disallow purchase presses elsewhere + if not self.gagTutMode: + self.makeUnpressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return + + def purchaseBrokeDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.purchaseFrame.hide() + return + + def gagTutDisabledActivateButtons(self): + # this mode is used before we want the user to start buying stuff + self.reparentTo(aspect2d) + self.setPos(0.2, 0, -0.04) + self.setScale(1) + + if self.purchaseFrame == None: + self.loadPurchaseFrame() + self.purchaseFrame.show() + + self.invFrame.reparentTo(self.purchaseFrame) + self.invFrame.setPos(-0.235, 0, 0.52) + self.invFrame.setScale(0.81) + + self.detailFrame.setPos(1.17, 0, -0.02) + self.detailFrame.setScale(1.25) + + self.deleteEnterButton.show() + self.deleteEnterButton.setPos(-0.441, 0, -0.917) + self.deleteEnterButton.setScale(0.75) + + self.deleteExitButton.hide() + self.deleteExitButton.setPos(-0.441, 0, -0.917) + self.deleteExitButton.setScale(0.75) + + # hide the trash can in the gag tutorial + self.deleteEnterButton.hide() + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + self.makeUnpressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + + def gagTutDisabledDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.purchaseFrame.hide() + + def battleActivateButtons(self): + self.stopAndClearPropBonusIval() + self.reparentTo(aspect2d) + self.setPos(0,0,0.1) + self.setScale(1) + + if self.battleFrame == None: + self.loadBattleFrame() + self.battleFrame.show() + self.battleFrame.setScale(0.9) + + self.invFrame.reparentTo(self.battleFrame) + self.invFrame.setPos(-0.26, 0, 0.35) + self.invFrame.setScale(1) + + self.detailFrame.setPos(1.125, 0, -0.08) + self.detailFrame.setScale(1) + + self.deleteEnterButton.hide() + self.deleteExitButton.hide() + + if (self.bldg == 1): + # Hide Run and SOS buttons if we're in a building battle. + self.runButton.hide() + # We need this button to call NPC helpers + self.sosButton.show() + self.passButton.show() + elif (self.tutorialFlag == 1): + # Hide Run, SOS, and Pass buttons if we are in the tutorial + self.runButton.hide() + self.sosButton.hide() + self.passButton.hide() + self.fireButton.hide() + else: + # Otherwise, show all buttons, hide the tutorial stuff + self.runButton.show() + self.sosButton.show() + self.passButton.show() + self.fireButton.show() + if localAvatar.getPinkSlips() > 0: + self.fireButton['state'] = DGG.NORMAL + self.fireButton['image_color'] = Vec4(0, 0.6, 1, 1) + else: + self.fireButton['state'] = DGG.DISABLED + self.fireButton['image_color'] = Vec4(0.4, 0.4, 0.4, 1) + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if self.itemIsUsable(track, level): + button.show() + # In delete mode, buttons are only pressable + # if you have some of the item, and you are + # allowed to use the item at this time. + if ((self.numItem(track, level) <= 0) or + ((track == HEAL_TRACK) and not self.heal) or + ((track == TRAP_TRACK) and not self.trap) or + ((track == LURE_TRACK) and not self.lure)): + self.makeUnpressable(button, track, level) + elif self.itemIsCredit(track, level): + self.makePressable(button, track, level) + else: + self.makeNoncreditPressable(button, track, level) + + else: + button.hide() + else: + self.hideTrack(track) + + self.propBonusIval.loop() + + return None + + def battleDeactivateButtons(self): + self.invFrame.reparentTo(self) + self.battleFrame.hide() + self.stopAndClearPropBonusIval() + #self.tutText.hide() + #self.tutArrows.arrowsOff() + return + + def plantTreeActivateButtons(self): + self.reparentTo(aspect2d) + self.setPos(0,0,0.1) + self.setScale(1) + + if self.battleFrame == None: + self.loadBattleFrame() + self.battleFrame.show() + self.battleFrame.setScale(0.9) + + self.invFrame.reparentTo(self.battleFrame) + self.invFrame.setPos(-0.25, 0, 0.35) + self.invFrame.setScale(1) + + self.detailFrame.setPos(1.125, 0, -0.08) + self.detailFrame.setScale(1) + + self.deleteEnterButton.hide() + self.deleteExitButton.hide() + + self.runButton.hide() + self.sosButton.hide() + + self.passButton['text'] = TTLocalizer.lCancel + self.passButton.show() + + for track in range(len(Tracks)): + # Make sure we are allowed to use this track before you show + # it. + if self.toon.hasTrackAccess(track): + self.showTrack(track) + for level in range(len(Levels[track])): + button = self.buttons[track][level] + # Make sure we are allowed to use this track, and that + # we have some of this item. + if (self.itemIsUsable(track,level) and + (level==0 or self.toon.doIHaveRequiredTrees(track,level))): + button.show() + self.makeUnpressable(button, track, level) + if self.numItem(track, level) > 0: + if not self.toon.isTreePlanted(track, level): + self.makePressable(button, track, level) + else: + button.hide() + else: + self.hideTrack(track) + return None + + def plantTreeDeactivateButtons(self): + self.passButton['text'] = TTLocalizer.InventoryPass + self.invFrame.reparentTo(self) + self.battleFrame.hide() + #self.tutText.hide() + #self.tutArrows.arrowsOff() + return + + def itemIsUsable(self, track, level): + """ + This returns true if the toon may use this item, false if not. + Toons may use an item if their experience is greater than or equal + to the required points for the item. + """ + if self.gagTutMode: + # rely on toon's track access table rather than experience + # this allows the tutorial to show gags that localToon doesn't have + # access to yet + trackAccess = self.toon.getTrackAccess() + return trackAccess[track] >= (level+1) + + curSkill = self.toon.experience.getExp(track) + if curSkill < Levels[track][level]: + return 0 + else: + return 1 + + def itemIsCredit(self, track, level): + """ + This returns true if the toon will gain credit for using this + item in this particular battle, false otherwise. This is + based on the credit level supplied to setActivateMode(), which + represents the highest-level item (1-based) that may be used + for credit. + """ + + # If the toon has an earnedExperience indication, an entire + # track will become unavailable if he exceeds the experience + # cap. + if self.toon.earnedExperience: + if self.toon.earnedExperience[track] >= ExperienceCap: + return 0 + + if self.battleCreditLevel == None: + # No credit restrictions. + return 1 + else: + return level < self.battleCreditLevel + + def getMax(self, track, level): + # in gag tutorial mode, show every button as pressable + if self.gagTutMode and ((track not in (4,5)) or level > 0): + return 1 + return InventoryBase.InventoryBase.getMax(self, track, level) + + def getCurAndNextExpValues(self, track): + """ + Return the number of total experience to get to the next + track. If the current experience equals or exceeds the highest + next value, the highest next value is returned. + """ + curSkill = self.toon.experience.getExp(track) + # MaxSkill is the default + retVal = MaxSkill + for amount in Levels[track]: + if curSkill < amount: + retVal = amount + return curSkill, retVal + return curSkill, retVal + + def makePressable(self, button, track, level): + # make special text color if the gag is buffed (via gardening) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + shadowColor = self.ShadowBuffedColor + else: + shadowColor = self.ShadowColor + button.configure(image0_image = self.upButton, + image2_image = self.rolloverButton, + text_shadow = shadowColor, + geom_color = self.PressableGeomColor, + commandButtons = (DGG.LMB,), + ) + # This must come after the first configure because we need + # to set the color after setting the actual image. There are + # no ordering guarantees in the configure paramaters + if self._interactivePropTrackBonus == track: + button.configure(image_color = self.PropBonusPressableImageColor) + self.addToPropBonusIval(button) + else: + button.configure(image_color = self.PressableImageColor) + return + + def makeNoncreditPressable(self, button, track, level): + # make special text color if the gag is buffed (via gardening) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + shadowColor = self.ShadowBuffedColor + else: + shadowColor = self.ShadowColor + button.configure(image0_image = self.upButton, + image2_image = self.rolloverButton, + text_shadow = shadowColor, + geom_color = self.PressableGeomColor, + commandButtons = (DGG.LMB,), + ) + # This must come after the first configure because we need + # to set the color after setting the actual image. There are + # no ordering guarantees in the configure paramaters + if self._interactivePropTrackBonus == track: + button.configure(image_color = self.PropBonusNoncreditPressableImageColor) + self.addToPropBonusIval(button) + else: + button.configure(image_color = self.NoncreditPressableImageColor) + return + + def makeDeletePressable(self, button, track, level): + # make special text color if the gag is buffed (via gardening) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + shadowColor = self.ShadowBuffedColor + else: + shadowColor = self.ShadowColor + button.configure(image0_image = self.upButton, + image2_image = self.rolloverButton, + text_shadow = shadowColor, + geom_color = self.PressableGeomColor, + commandButtons = (DGG.LMB,), + ) + # This must come after the first configure because we need + # to set the color after setting the actual image. There are + # no ordering guarantees in the configure paramaters + button.configure(image_color = self.DeletePressableImageColor) + return + + def makeUnpressable(self, button, track, level): + # make special text color if the gag is buffed (via gardening) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + shadowColor = self.UnpressableShadowBuffedColor + else: + shadowColor = self.ShadowColor + button.configure(text_shadow = shadowColor, + geom_color = self.UnpressableGeomColor, + image_image = self.flatButton, + commandButtons = (), + ) + # This must come after the first configure because we need + # to set the color after setting the actual image. There are + # no ordering guarantees in the configure paramaters + button.configure(image_color = self.UnpressableImageColor) + return + + def makeBookUnpressable(self, button, track, level): + # make special text color if the gag is buffed (via gardening) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + shadowColor = self.ShadowBuffedColor + else: + shadowColor = self.ShadowColor + button.configure(text_shadow = shadowColor, + geom_color = self.BookUnpressableGeomColor, + image_image = self.flatButton, + commandButtons = (), + ) + # This must come after the first configure because we need + # to set the color after setting the actual image. There are + # no ordering guarantees in the configure paramaters + button.configure(image0_color = self.BookUnpressableImage0Color, + image2_color = self.BookUnpressableImage2Color) + return + + def hideTrack(self, trackIndex): + self.trackNameLabels[trackIndex].show() + self.trackBars[trackIndex].hide() + for levelIndex in range(0, len(Levels[trackIndex])): + self.buttons[trackIndex][levelIndex].hide() + + def showTrack(self, trackIndex): + self.trackNameLabels[trackIndex].show() + self.trackBars[trackIndex].show() + + for levelIndex in range(0, len(Levels[trackIndex])): + self.buttons[trackIndex][levelIndex].show() + + # Put the next label in the right spot + curExp, nextExp = self.getCurAndNextExpValues(trackIndex) + if curExp >= UnpaidMaxSkill and self.toon.getGameAccess() != OTPGlobals.AccessFull: + self.trackBars[trackIndex]['range'] = nextExp + self.trackBars[trackIndex]["text"] = (TTLocalizer.InventoryGuestExp) + + elif curExp >= regMaxSkill: + self.trackBars[trackIndex]['range'] = UberSkill + self.trackBars[trackIndex]["text"] = (TTLocalizer.InventoryUberTrackExp % + {"nextExp": MaxSkill - curExp,}) + else: + self.trackBars[trackIndex]['range'] = nextExp + self.trackBars[trackIndex]["text"] = (TTLocalizer.InventoryTrackExp % + {"curExp": curExp, + "nextExp": nextExp,}) + + #if expVal == MaxSkill: + # nextLabel.hide() + #else: + # nextLabel["text"] = str(expVal) + # index = Levels[trackIndex].index(expVal) + # nextLabel.setPos((-0.31 + index * 0.193), 0, -0.02) + return + + def updateInvString(self, invString): + InventoryBase.InventoryBase.updateInvString(self, invString) + self.updateGUI() + return None + + def updateButton(self, track, level): + button = self.buttons[track][level] + button["text"] = str(self.numItem(track, level)) + organicBonus = self.toon.checkGagBonus(track, level) + propBonus = self.checkPropBonus(track) + bonus = organicBonus or propBonus + if bonus: + textScale = 0.05 + else: + textScale = 0.04 + button.configure(text_scale = textScale) + + def buttonBoing(self, track, level): + button = self.buttons[track][level] + oldScale = button.getScale() + s = Sequence(button.scaleInterval(0.1, oldScale*1.333, blendType='easeOut'), + button.scaleInterval(0.1, oldScale, blendType='easeIn'), + name = ('inventoryButtonBoing-' + str(self.this)), + ) + s.start() + + def updateGUI(self, track=None, level=None): + """updateGUI(self, int=None, int=None): + Update the gui display. If supplied, assume the inventory + item at [track][level] has changed + """ + # Update the text telling the total number of props + self.updateTotalPropsText() + if track == None and level == None: + for track in range(len(Tracks)): + # Update the number of experience points per track + curExp, nextExp = self.getCurAndNextExpValues(track) + if curExp >= UnpaidMaxSkill and self.toon.getGameAccess() != OTPGlobals.AccessFull: + self.trackBars[track]['range'] = nextExp + self.trackBars[track]["text"] = (TTLocalizer.InventoryGuestExp) + + elif curExp >= regMaxSkill: + self.trackBars[track]["text"] = (TTLocalizer.InventoryUberTrackExp % + {"nextExp": MaxSkill - curExp,}) + self.trackBars[track]["value"] = curExp - regMaxSkill + else: + self.trackBars[track]["text"] = (TTLocalizer.InventoryTrackExp % + {"curExp": curExp, + "nextExp": nextExp,}) + self.trackBars[track]["value"] = curExp + + + # Update the text for each button + for level in range(0, len(Levels[track])): + self.updateButton(track, level) + elif track != None and level != None: + self.updateButton(track, level) + else: + self.notify.error("Invalid use of updateGUI") + # Since max props can affect what buttons are active + self.__activateButtons() + return + + def getSingleGroupStr(self, track, level): + if track == HEAL_TRACK: + if isGroup(track, level): + return TTLocalizer.InventoryAffectsAllToons + else: + return TTLocalizer.InventoryAffectsOneToon + else: + if isGroup(track, level): + return TTLocalizer.InventoryAffectsAllCogs + else: + return TTLocalizer.InventoryAffectsOneCog + + def getToonupDmgStr(self, track, level): + if track == HEAL_TRACK: + return TTLocalizer.InventoryHealString + else: + return TTLocalizer.InventoryDamageString + + def deleteItem(self, track, level): + if self.numItem(track, level) > 0: + self.useItem(track, level) + self.updateGUI(track, level) + return + + def loadBattleFrame(self): + battleModels = loader.loadModel("phase_3.5/models/gui/battle_gui") + + self.battleFrame = DirectFrame(relief = None, + image = battleModels.find("**/BATTLE_Menu"), + image_scale = 0.8, + parent = self, + ) + + self.runButton = DirectButton(parent = self.battleFrame, + relief = None, + pos = (0.73, 0, -0.398), + text = TTLocalizer.InventoryRun, + text_scale = TTLocalizer.INrunButton, + text_pos = (0,-0.02), + text_fg = Vec4(1,1,1,1), + textMayChange = 0, + image = (self.upButton, + self.downButton, + self.rolloverButton, + ), + image_scale = 1.05, + image_color = (0, 0.6, 1, 1), + command = self.__handleRun, + ) + + self.sosButton = DirectButton(parent = self.battleFrame, + relief = None, + pos = (0.96, 0, -0.398), + text = TTLocalizer.InventorySOS, + text_scale = 0.05, + text_pos = (0,-0.02), + text_fg = Vec4(1,1,1,1), + textMayChange = 0, + image = (self.upButton, + self.downButton, + self.rolloverButton, + ), + image_scale = 1.05, + image_color = (0, 0.6, 1, 1), + command = self.__handleSOS, + ) + + self.passButton = DirectButton(parent = self.battleFrame, + relief = None, + pos = (0.96, 0, -0.242), + text = TTLocalizer.InventoryPass, + text_scale = TTLocalizer.INpassButton, + text_pos = (0,-0.02), + text_fg = Vec4(1,1,1,1), + textMayChange = 1, + image = (self.upButton, + self.downButton, + self.rolloverButton, + ), + image_scale = 1.05, + image_color = (0, 0.6, 1, 1), + command = self.__handlePass, + ) + + self.fireButton = DirectButton(parent = self.battleFrame, + relief = None, + pos = (0.73, 0, -0.242), + text = TTLocalizer.InventoryFire, + text_scale = 0.05, + text_pos = (0,-0.02), + text_fg = Vec4(1,1,1,1), + textMayChange = 0, + image = (self.upButton, + self.downButton, + self.rolloverButton, + ), + image_scale = 1.05, + image_color = (0, 0.6, 1, 1), + command = self.__handleFire, + ) + + self.tutText = DirectFrame(parent = self.battleFrame, + relief = None, + pos = (0.05, 0, -0.1133), + scale = 0.143, + image = DGG.getDefaultDialogGeom(), + image_scale = 5.125, + image_pos = (0, 0, -0.65), + image_color = ToontownGlobals.GlobalDialogColor, + text_scale = TTLocalizer.INclickToAttack, + text = TTLocalizer.InventoryClickToAttack, + textMayChange = 0, + ) + # This should be initially hidden. + self.tutText.hide() + self.tutArrows = BlinkingArrows.BlinkingArrows(parent=self.battleFrame) + + battleModels.removeNode() + self.battleFrame.hide() + return + + def loadPurchaseFrame(self): + purchaseModels = loader.loadModel("phase_4/models/gui/purchase_gui") + self.purchaseFrame = DirectFrame(relief = None, + image = purchaseModels.find("**/PurchasePanel"), + image_pos = (-0.21, 0, 0.08), + parent = self, + ) + # keep the remote toons' panels on-screen + self.purchaseFrame.setX(-.06) + self.purchaseFrame.hide() + purchaseModels.removeNode() + return + + def loadStorePurchaseFrame(self): + storePurchaseModels = loader.loadModel("phase_4/models/gui/gag_shop_purchase_gui") + self.storePurchaseFrame = DirectFrame(relief = None, + image = storePurchaseModels.find("**/gagShopPanel"), + image_pos = (-0.21, 0, 0.18), + parent = self, + ) + + self.storePurchaseFrame.hide() + storePurchaseModels.removeNode() + return + + def buttonLookup(self, track, level): + return self.invModels[track][level] + + def enterTrackFrame(self, track, guiItem): + messenger.send("enterTrackFrame", [track]) + + def exitTrackFrame(self, track, guiItem): + messenger.send("exitTrackFrame", [track]) + + def checkPropBonus(self, track): + """Return true if this track gag is being buffed by a prop.""" + result = False + if track == self._interactivePropTrackBonus: + result = True + return result + + def stopAndClearPropBonusIval(self): + """If we have a propBonusIval, stop it and reset it to empty Parallel.""" + if self.propBonusIval and self.propBonusIval.isPlaying(): + self.propBonusIval.finish() + self.propBonusIval = Parallel() + + def addToPropBonusIval(self, button): + """We have a button, make it flash and add it to propBonusIval.""" + flashObject = button + try: + flashObject = button.component('image0') + except: + pass + goDark = LerpColorScaleInterval(flashObject, 0.5, Point4(0.1, 0.1, 0.1, 1.0), Point4(1,1,1,1), blendType='easeIn') + goBright = LerpColorScaleInterval(flashObject, 0.5, Point4(1,1,1,1), Point4(0.1, 0.1, 0.1, 1.0), blendType='easeOut') + + newSeq = Sequence(goDark, goBright, Wait(0.2)) + self.propBonusIval.append(newSeq) diff --git a/toontown/src/toon/LaffMeter.py b/toontown/src/toon/LaffMeter.py new file mode 100644 index 0000000..7bb2f9b --- /dev/null +++ b/toontown/src/toon/LaffMeter.py @@ -0,0 +1,269 @@ +"""LaffMeter module: contains the class definition for handling the +laff-o-meter""" + +from pandac.PandaModules import * +from otp.avatar import DistributedAvatar +from toontown.toonbase import ToontownGlobals +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * + +class LaffMeter(DirectFrame): + """LaffMeter class""" + + deathColor = Vec4(0.58039216, 0.80392157, 0.34117647, 1.0) + + # special methods + def __init__(self, avdna, hp, maxHp): + """__init(self, AvatarDNA, int, int) + LaffMeter constructor: create a laff-o-meter for a given DA + """ + DirectFrame.__init__(self, relief=None, sortOrder=50) + self.initialiseoptions(LaffMeter) + + # This is to contain the scale for the animated effect + self.container = DirectFrame(parent = self, relief = None) + + self.style = avdna + self.av = None + self.hp = hp + self.maxHp = maxHp + self.__obscured = 0 + if (self.style.type == 't'): + self.isToon = 1 + else: + self.isToon = 0 + self.load() + + def obscure(self, obscured): + """obscureButton(self, int obscured) + Make the be button be obscured, regardless of show and hide + 1 = obscure, 0 = unobscured + """ + self.__obscured = obscured + if self.__obscured: + self.hide() + + def isObscured(self): + return self.__obscured + + def load(self): + gui = loader.loadModel("phase_3/models/gui/laff_o_meter") + if self.isToon: + hType = self.style.getType() + if (hType == "dog"): + headModel = gui.find("**/doghead") + elif (hType == "cat"): + headModel = gui.find("**/cathead") + elif (hType == "mouse"): + headModel = gui.find("**/mousehead") + elif (hType == "horse"): + headModel = gui.find("**/horsehead") + elif (hType == "rabbit"): + headModel = gui.find("**/bunnyhead") + elif (hType == "duck"): + headModel = gui.find("**/duckhead") + elif (hType == "monkey"): + headModel = gui.find("**/monkeyhead") + elif (hType == "bear"): + headModel = gui.find("**/bearhead") + elif (hType == "pig"): + headModel = gui.find("**/pighead") + else: + raise StandardError("unknown toon species: ", hType) + + self.color = self.style.getHeadColor() + + self.container['image'] = headModel + self.container['image_color'] = self.color + self.resetFrameSize() + self.setScale(0.1) + self.frown = DirectFrame(parent = self.container, relief = None, + image = gui.find("**/frown")) + self.smile = DirectFrame(parent = self.container, relief = None, + image = gui.find("**/smile")) + self.eyes = DirectFrame(parent = self.container, relief = None, + image = gui.find("**/eyes")) + self.openSmile = DirectFrame(parent = self.container, relief = None, + image = gui.find("**/open_smile")) + self.tooth1 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_1")) + self.tooth2 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_2")) + self.tooth3 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_3")) + self.tooth4 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_4")) + self.tooth5 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_5")) + self.tooth6 = DirectFrame(parent = self.openSmile, relief = None, + image = gui.find("**/tooth_6")) + + + self.maxLabel = DirectLabel(parent = self.eyes, + relief = None, + pos = (0.442, 0, 0.051), + text = "120", + text_scale = 0.4, + text_font = ToontownGlobals.getInterfaceFont(), + ) + self.hpLabel = DirectLabel(parent = self.eyes, + relief = None, + pos = (-0.398, 0, 0.051), + text = "120", + text_scale = 0.4, + text_font = ToontownGlobals.getInterfaceFont(), + ) + + self.teeth = [self.tooth6, self.tooth5, self.tooth4, + self.tooth3, self.tooth2, self.tooth1] + self.fractions = [0., 0.166666, 0.333333, 0.5, 0.666666, 0.833333] + + gui.removeNode() + + def destroy(self): + if self.av: + taskMgr.remove(self.av.uniqueName('laffMeterBoing') + '-' + str(self.this)) + taskMgr.remove(self.av.uniqueName('laffMeterBoing') + '-' + str(self.this) + '-play') + self.ignore(self.av.uniqueName("hpChange")) + del self.style + del self.av + del self.hp + del self.maxHp + if self.isToon: + del self.frown + del self.smile + del self.openSmile + del self.tooth1 + del self.tooth2 + del self.tooth3 + del self.tooth4 + del self.tooth5 + del self.tooth6 + del self.teeth + del self.fractions + del self.maxLabel + del self.hpLabel + DirectFrame.destroy(self) + + def adjustTeeth(self): + """adjustTeeth(self) + if teeth are showing, decide which ones should be + """ + if self.isToon: + # Look through the fractions of hp for each tooth + for i in range(len(self.teeth)): + # if the hp is more than that fraction, turn this tooth on + if (self.hp > (self.maxHp * self.fractions[i])): + self.teeth[i].show() + else: + self.teeth[i].hide() + + def adjustText(self): + """adjustText(self) + set the text for current HP and maxHP + """ + if self.isToon: + # Only update if the text has changed + if (self.maxLabel['text'] != str(self.maxHp) or + self.hpLabel['text'] != str(self.hp)): + self.maxLabel['text'] = str(self.maxHp) + self.hpLabel['text'] = str(self.hp) + return + + + def animatedEffect(self, delta): + # Note: the task name here must be unique to this avatar and + # to this laffmeter. We'll use the python this pointer to + # differentiate multiple laffmeters watching the same toon + # This happens in battle and on avatar detail panels + if (delta == 0) or (self.av == None): + return + taskName = self.av.uniqueName('laffMeterBoing') + '-' + str(self.this) + taskMgr.remove(taskName) + if delta > 0: + # Laffmeter increase + Sequence(self.container.scaleInterval(0.2, 1.333, blendType='easeOut'), + self.container.scaleInterval(0.2, 1, blendType='easeIn'), + name = taskName, + autoFinish = 1 + ).start() + else: + # Laffmeter decrease + Sequence(self.container.scaleInterval(0.2, 0.666, blendType='easeOut'), + self.container.scaleInterval(0.2, 1, blendType='easeIn'), + name = taskName, + autoFinish = 1 + ).start() + + def adjustFace(self, hp, maxHp, quietly = 0): + """adjustFace(self, int, int) + make sure the laff-o-meter face is in sync with the avatar state + """ + if self.isToon and self.hp != None: + # Hide everything first + self.frown.hide() + self.smile.hide() + self.openSmile.hide() + self.eyes.hide() + for tooth in self.teeth: + tooth.hide() + # Now show elements based on hp + delta = hp - self.hp + self.hp = hp + self.maxHp = maxHp + if (self.hp < 1): + self.frown.show() + self.container['image_color'] = self.deathColor + elif (self.hp >= self.maxHp): + self.smile.show() + self.eyes.show() + self.container['image_color'] = self.color + else: + self.openSmile.show() + self.eyes.show() + self.maxLabel.show() + self.hpLabel.show() + self.container['image_color'] = self.color + self.adjustTeeth() + + self.adjustText() + + if not quietly: + self.animatedEffect(delta) + + def start(self): + """start + manage the GUI elements of the laff-o-meter + """ + + if self.av: + # Refresh the hp and max hp, in case they changed when + # we weren't managed. + self.hp = self.av.hp + self.maxHp = self.av.maxHp + + if self.isToon: + if not self.__obscured: + self.show() + self.adjustFace(self.hp, self.maxHp, 1) # Do not animate this first one + if self.av: + self.accept(self.av.uniqueName("hpChange"), self.adjustFace) + + def stop(self): + """stop(self) + unmanage the GUI elements of the laff-o-meter + """ + if self.isToon: + self.hide() + if self.av: + self.ignore(self.av.uniqueName("hpChange")) + + def setAvatar(self, av): + """setAvatar(self, DistributedAvatar): + set an avatar structure for use by the auto-update system + """ + # Get rid of any previous avatar hooks + if self.av: + self.ignore(self.av.uniqueName("hpChange")) + self.av = av diff --git a/toontown/src/toon/LocalToon.py b/toontown/src/toon/LocalToon.py new file mode 100644 index 0000000..2e6d059 --- /dev/null +++ b/toontown/src/toon/LocalToon.py @@ -0,0 +1,2825 @@ +"""LocalToon module: contains the LocalToon class""" + +#import time +import random +import math + +from direct.interval.IntervalGlobal import * +from direct.distributed.ClockDelta import * +#from direct.showbase.InputStateGlobal import inputState +from direct.showbase.PythonUtil import * +from direct.gui.DirectGui import * +from direct.task import Task +from direct.showbase import PythonUtil +from direct.directnotify import DirectNotifyGlobal +from direct.gui import DirectGuiGlobals + +from pandac.PandaModules import * + +from otp.avatar import LocalAvatar +from otp.login import LeaveToPayDialog +from otp.avatar import PositionExaminer +from otp.otpbase import OTPGlobals + +from toontown.shtiker import ShtikerBook +from toontown.shtiker import InventoryPage +from toontown.shtiker import MapPage +from toontown.shtiker import OptionsPage +from toontown.shtiker import ShardPage +from toontown.shtiker import QuestPage +from toontown.shtiker import TrackPage +from toontown.shtiker import KartPage +from toontown.shtiker import GardenPage +from toontown.shtiker import GolfPage +from toontown.shtiker import SuitPage +from toontown.shtiker import DisguisePage +from toontown.shtiker import PhotoAlbumPage +#from toontown.shtiker import BuildingPage +from toontown.shtiker import FishPage +from toontown.shtiker import NPCFriendPage +from toontown.shtiker import EventsPage +from toontown.shtiker import TIPPage + + + +from toontown.quest import Quests +from toontown.quest import QuestParser +from toontown.toonbase.ToontownGlobals import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.catalog import CatalogNotifyDialog +from toontown.chat import ToontownChatManager +from toontown.chat import TTTalkAssistant +from toontown.estate import GardenGlobals +#from toontown.estate import GardenProgressMeter +from toontown.battle.BattleSounds import * +from toontown.battle import Fanfare +from toontown.parties import PartyGlobals + +from toontown.toon import ElevatorNotifier +import DistributedToon +import Toon +import LaffMeter + +# Checks whether we want to display the news page +# which uses Awesomium to render HTML +WantNewsPage = base.config.GetBool('want-news-page', ToontownGlobals.DefaultWantNewsPageSetting) +from toontown.toontowngui import NewsPageButtonManager +if WantNewsPage: + from toontown.shtiker import NewsPage + +AdjustmentForNewsButton = -0.275 + +if (__debug__): + import pdb + +class LocalToon(DistributedToon.DistributedToon, LocalAvatar.LocalAvatar): + """LocalToon class:""" + + # The localToon should not be disabled when we enter the quiet zone + neverDisable = 1 + + # The number of seconds it takes to move the pie power meter to + # full the first time. + piePowerSpeed = base.config.GetDouble('pie-power-speed', 0.2) + + # The exponent that controls the factor at which the pie power + # meter slows down over time. Values closer to 1.0 slow down less + # quickly. + piePowerExponent = base.config.GetDouble('pie-power-exponent', 0.75) + + def __init__(self, cr): + """ + Local toon constructor + """ + try: + self.LocalToon_initialized + except: + self.LocalToon_initialized = 1 + self.numFlowers = 0 + self.maxFlowerBasket = 0 + DistributedToon.DistributedToon.__init__(self, cr) + chatMgr = ToontownChatManager.ToontownChatManager(cr, self) + talkAssistant = TTTalkAssistant.TTTalkAssistant() + LocalAvatar.LocalAvatar.__init__(self, cr, chatMgr, talkAssistant,passMessagesThrough = True) + #self.setNameVisible(0) + + ### BEGIN FROM LOCAL AVATAR ################################## + # This used to be in LocalAvatar, but it really is Toon specific + + # Note: we cannot load these phase 4 sounds in the + # initialize function because of the phased download + self.soundRun = base.loadSfx( + "phase_3.5/audio/sfx/AV_footstep_runloop.wav") + self.soundWalk = base.loadSfx( + "phase_3.5/audio/sfx/AV_footstep_walkloop.wav") + self.soundWhisper = base.loadSfx( + "phase_3.5/audio/sfx/GUI_whisper_3.mp3") + self.soundPhoneRing = base.loadSfx( + "phase_3.5/audio/sfx/telephone_ring.mp3") + self.positionExaminer = PositionExaminer.PositionExaminer() + + # A button to open up the Friends List. + friendsGui = loader.loadModel("phase_3.5/models/gui/friendslist_gui") + friendsButtonNormal = friendsGui.find("**/FriendsBox_Closed") + friendsButtonPressed = friendsGui.find("**/FriendsBox_Rollover") + friendsButtonRollover = friendsGui.find("**/FriendsBox_Rollover") + newScale = oldScale = 0.8 + if WantNewsPage: + newScale = oldScale * ToontownGlobals.NewsPageScaleAdjust + self.bFriendsList = DirectButton( + image = (friendsButtonNormal, + friendsButtonPressed, + friendsButtonRollover), + relief = None, + pos = (1.192, 0, 0.875), + scale = newScale, + text = ("", TTLocalizer.FriendsListLabel, TTLocalizer.FriendsListLabel), + text_scale = 0.09, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + text_pos = (0,-0.18), + text_font = ToontownGlobals.getInterfaceFont(), + command = self.sendFriendsListEvent, + ) + self.bFriendsList.hide() + self.friendsListButtonActive = 0 + self.friendsListButtonObscured = 0 + self.moveFurnitureButtonObscured = 0 + self.clarabelleButtonObscured = 0 + friendsGui.removeNode() + + self.__furnitureGui = None + self.__clarabelleButton = None + self.__clarabelleFlash = None + # These flags are set by setFurnitureDirector(). + self.furnitureManager = None + self.furnitureDirector = None + + # In case we got the catalog notify message while we were + # still downloading phase5.5, we'll need to delay it and pop + # it up when we actually download the phase. + self.gotCatalogNotify = 0 + self.__catalogNotifyDialog = None + self.accept('phaseComplete-5.5', self.loadPhase55Stuff) + + ### END FROM LOCAL AVATAR #################################### + + # Init the avatar sounds + Toon.loadDialog() + + # It variable for the Tag minigame + # I know it is strange living in here, but it + # was the only way I could think of for the Distributed + # Treasure to know if the local toon is it + self.isIt = 0 + + self.cantLeaveGame = 0 + + # define these tunnel variables, just to be safe + # these are set to real values when you exit an area + # through a tunnel + self.tunnelX = 0. + + self.estate = None + + # We use this to detect where our tossed pie hits. + self.__pieBubble = None + + # And this is set if we're allowed to throw pies. + self.allowPies = 0 + self.__pieButton = None + self.__piePowerMeter = None + self.__piePowerMeterSequence = None + self.__pieButtonType = None + self.__pieButtonCount = None + self.tossPieStart = None + self.__presentingPie = 0 + self.__pieSequence = 0 + + self.wantBattles = base.config.GetBool('want-battles', 1) + self.seeGhosts = base.config.GetBool("see-ghosts", 0) + wantNameTagAvIds = base.config.GetBool('want-nametag-avids',0) + if wantNameTagAvIds: + # simulate doing ~idTags + messenger.send('nameTagShowAvId', []) + base.idTags = 1 + + self.glitchX = 0 + self.glitchY = 0 + self.glitchZ = 0 + self.glitchCount = 0 + self.ticker = 0 + self.glitchOkay = 1 + self.tempGreySpacing = 0 + + self.wantStatePrint = base.config.GetBool('want-statePrint', 0) + + #These items related to the gardening estates expansion + self.__gardeningGui = None + self.__gardeningGuiFake = None + self.__shovelButton = None + self.shovelRelatedDoId = 0 #this could be a garden plot, a tree + self.shovelAbility = "" #this could be "Plant", "Pick" + self.plantToWater = 0 + + self.shovelButtonActiveCount = 0 + self.wateringCanButtonActiveCount = 0 + + self.showingWateringCan = 0 + self.showingShovel = 0 + + self.touchingPlantList = [] + + self.inGardenAction = None + self.guiConflict = 0 + + self.lastElevatorLeft = 0 + + self.elevatorNotifier = ElevatorNotifier.ElevatorNotifier() + + # switchboard friends messages + + self.accept(OTPGlobals.AvatarFriendAddEvent, self.sbFriendAdd) + self.accept(OTPGlobals.AvatarFriendUpdateEvent, self.sbFriendUpdate) + self.accept(OTPGlobals.AvatarFriendRemoveEvent, self.sbFriendRemove) + + self._zoneId = None + + self.accept("system message aknowledge", self.systemWarning) + self.systemMsgAckGuiDoneEvent = "systemMsgAckGuiDoneEvent" + self.accept(self.systemMsgAckGuiDoneEvent, self.hideSystemMsgAckGui) + self.systemMsgAckGui = None + self.createSystemMsgAckGui() + if not hasattr(base.cr, 'lastLoggedIn'): + # I'm not sure this will ever happen in test or on live + # but in the dev local environment this case hits after I've just created a new toon + # on a brand new account + base.cr.lastLoggedIn = self.cr.toontownTimeManager.convertStrToToontownTime("") + self.setLastTimeReadNews(base.cr.lastLoggedIn) + + # GMs have accepting-new-friends-default 0, which forces them to explicitly enable + # friend requests if they ever want it. + self.acceptingNewFriends = Settings.getAcceptingNewFriends() and base.config.GetBool('accepting-new-friends-default', True) + + def wantLegacyLifter(self): + return True + + def startGlitchKiller(self): + #print ("trying glitch killer %s" % (localAvatar.getZoneId())) + if localAvatar.getZoneId() not in GlitchKillerZones: + #print("skipping") + return + if __dev__: + self.glitchMessage = "START GLITCH KILLER" + randChoice = random.randint(0, 3) + if randChoice == 0: + self.glitchMessage = "START GLITCH KILLER" + elif randChoice == 1: + self.glitchMessage = "GLITCH KILLER ENGAGED" + elif randChoice == 2: + self.glitchMessage = "GLITCH KILLER GO!" + elif randChoice == 3: + self.glitchMessage = "GLITCH IN YO FACE FOOL!" + else: + pass + + self.notify.debug(self.glitchMessage) + #print("starting") + taskMgr.remove(self.uniqueName("glitchKiller")) + taskMgr.add(self.glitchKiller, self.uniqueName("glitchKiller")) + self.glitchOkay = 1 + + def pauseGlitchKiller(self): + self.tempGreySpacing = 1 + + def unpauseGlitchKiller(self): + self.tempGreySpacing = 0 + + def stopGlitchKiller(self): + if __dev__ and (hasattr(self, "glitchMessage")): + if self.glitchMessage == "START GLITCH KILLER": + self.notify.debug("STOP GLITCH KILLER") + elif self.glitchMessage == "GLITCH KILLER ENGAGED": + self.notify.debug("GLITCH KILLER DISENGAGED") + elif self.glitchMessage == "GLITCH KILLER GO!": + self.notify.debug("GLITCH KILLER NO GO!") + elif self.glitchMessage == "GLITCH IN YO FACE FOOL!": + self.notify.debug("GLITCH OFF YO FACE FOOL!") + else: + pass + + taskMgr.remove(self.uniqueName("glitchKiller")) + self.glitchOkay = 1 + + def glitchKiller(self, taskFooler = 0): + if base.greySpacing or self.tempGreySpacing: + return Task.cont + self.ticker += 1 #only used for printouts + if not self.physControls.lifter.hasContact() and not self.glitchOkay: + self.glitchCount += 1 + else: + self.glitchX = self.getX() + self.glitchY = self.getY() + self.glitchZ = self.getZ() + self.glitchCount = 0 + if self.physControls.lifter.hasContact(): + self.glitchOkay = 0 + if hasattr(self, "physControls"): + if self.ticker >= 10: + self.ticker = 0 + #height = self.getPos(self.shadowPlacer.shadowNodePath) + #height = self.physControls.determineHeight() + #print(self.physControls.lifter.hasContact()) + if self.glitchCount >= 7: + print("GLITCH MAXED!!! resetting pos") + self.setX(self.glitchX - (1 * (self.getX() - self.glitchX))) + self.setY(self.glitchY - (1 * (self.getY() - self.glitchY))) + #self.setPos(self.glitchX, self.glitchY, self.getZ()) + self.glitchCount = 0 + + return Task.cont + + def announceGenerate(self): + # Start looking around + self.startLookAround() + + if (base.wantNametags): + # Manage our own nametag. + self.nametag.manage(base.marginManager) + + DistributedToon.DistributedToon.announceGenerate(self) + + from otp.friends import FriendInfo + + + def disable(self): + """ + This method is called when the DistributedObject is removed from + active duty and stored in a cache. + """ + self.laffMeter.destroy() + del self.laffMeter + + if hasattr(self, 'purchaseButton'): + self.purchaseButton.destroy() + del self.purchaseButton + + # Clean up the book + self.newsButtonMgr.request('Off') + self.book.unload() + del self.optionsPage + del self.shardPage + del self.mapPage + del self.invPage + #del self.achievePage + del self.questPage + del self.suitPage + del self.sosPage + del self.disguisePage + del self.fishPage + del self.gardenPage + del self.trackPage + #del self.buildingPage + del self.book + + if base.wantKarts: + if( hasattr( self, "kartPage" ) ): + del self.kartPage + + if (base.wantNametags): + self.nametag.unmanage(base.marginManager) + + # We shouldn't need this... + self.ignoreAll() + # Call down the inheritance chain + + DistributedToon.DistributedToon.disable(self) + return + + def disableBodyCollisions(self): + """ + Override DistributedAvatar because we do not have body collisions + """ + pass + + def delete(self): + try: + self.LocalToon_deleted + except: + self.LocalToon_deleted = 1 + # Unload the avatar sounds + Toon.unloadDialog() + QuestParser.clear() + DistributedToon.DistributedToon.delete(self) + LocalAvatar.LocalAvatar.delete(self) + self.bFriendsList.destroy() + del self.bFriendsList + if self.__pieButton: + self.__pieButton.destroy() + self.__pieButton = None + if self.__piePowerMeter: + self.__piePowerMeter.destroy() + self.__piePowerMeter = None + ### LOCAL AVATAR ########################### + taskMgr.remove("unlockGardenButtons") + taskMgr.remove('lerpFurnitureButton') + if self.__furnitureGui: + self.__furnitureGui.destroy() + del self.__furnitureGui + if self.__gardeningGui: + self.__gardeningGui.destroy() + del self.__gardeningGui + if self.__gardeningGuiFake: + self.__gardeningGuiFake.destroy() + del self.__gardeningGuiFake + if self.__clarabelleButton: + self.__clarabelleButton.destroy() + del self.__clarabelleButton + if self.__clarabelleFlash: + self.__clarabelleFlash.finish() + del self.__clarabelleFlash + if self.__catalogNotifyDialog: + self.__catalogNotifyDialog.cleanup() + del self.__catalogNotifyDialog + + def initInterface(self): + self.newsButtonMgr = NewsPageButtonManager.NewsPageButtonManager() + self.newsButtonMgr.request('Hidden') + + # make one and only one ShtikerBook + self.book = ShtikerBook.ShtikerBook("bookDone") + self.book.load() + self.book.hideButton() + + self.optionsPage = OptionsPage.OptionsPage() + self.optionsPage.load() + self.book.addPage( + self.optionsPage, pageName = TTLocalizer.OptionsPageTitle) + + self.shardPage = ShardPage.ShardPage() + self.shardPage.load() + self.book.addPage(self.shardPage, pageName = TTLocalizer.ShardPageTitle) + + self.mapPage = MapPage.MapPage() + self.mapPage.load() + self.book.addPage(self.mapPage, pageName = TTLocalizer.MapPageTitle) + + self.invPage = InventoryPage.InventoryPage() + self.invPage.load() + self.book.addPage( + self.invPage, pageName = TTLocalizer.InventoryPageTitle) + + self.questPage = QuestPage.QuestPage() + self.questPage.load() + self.book.addPage( + self.questPage, pageName = TTLocalizer.QuestPageToonTasks) + + self.trackPage = TrackPage.TrackPage() + self.trackPage.load() + self.book.addPage( + self.trackPage, pageName = TTLocalizer.TrackPageShortTitle) + + #self.achievePage = AchievePage.AchievePage() + #self.achievePage.load() + #self.book.addPage( + # self.achievePage, pageName = TTLocalizer.AchievePageTitle) + + self.suitPage = SuitPage.SuitPage() + self.suitPage.load() + self.book.addPage(self.suitPage, pageName = TTLocalizer.SuitPageTitle) + + if base.config.GetBool("want-photo-album", 0): + self.photoAlbumPage = PhotoAlbumPage.PhotoAlbumPage() + self.photoAlbumPage.load() + self.book.addPage( + self.photoAlbumPage, pageName = TTLocalizer.PhotoPageTitle) + + self.fishPage = FishPage.FishPage() + self.fishPage.setAvatar(self) + self.fishPage.load() + self.book.addPage(self.fishPage, pageName = TTLocalizer.FishPageTitle) + + if base.wantKarts: + self.addKartPage() + + # The disguisePage and sosPage members are initialized to None + # by DistributedToon.__init__(). + + if self.disguisePageFlag: + self.loadDisguisePages() + + #self.buildingPage = BuildingPage.BuildingPage() + #self.buildingPage.load() + #self.book.addPage( + # self.buildingPage, pageName = TTLocalizer.BuildingPageTitle) + + if self.gardenStarted: + self.loadGardenPages() + + self.addGolfPage() + + self.addEventsPage() + + if WantNewsPage: + self.addNewsPage() + + # self.addTIPPage() + + self.book.setPage(self.mapPage, enterPage = False) + + # make a laff-o-meter for the localToon + self.laffMeter = LaffMeter.LaffMeter(self.style, + self.hp, + self.maxHp) + self.laffMeter.setAvatar(self) + self.laffMeter.setScale(0.075) + if self.style.getAnimal() == "monkey": + # The monkey laff meter is slightly bigger because the + # ears hang off to the side, so slide it over to the right + self.laffMeter.setPos(-1.18, 0., -0.87) + else: + self.laffMeter.setPos(-1.2, 0., -0.87) + self.laffMeter.stop() + + # make a purchase button for non-paid players + if not base.cr.isPaid(): + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + self.purchaseButton = DirectButton( + parent = aspect2d, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = 0.9, + text = TTLocalizer.OptionsPagePurchase, + text_scale = 0.05, + text_pos = (0, -0.01), + textMayChange = 0, + pos = (0.885, 0, -0.94), + command = self.__handlePurchase, + ) + # turn of the margin cell this overlaps with + base.setCellsAvailable([base.bottomCells[4]], 0) + + # We used to use the insert key for tossing pies. + self.accept('time-insert', self.__beginTossPie) + self.accept('time-insert-up', self.__endTossPie) + + # Nowadays we use the delete key instead, for better + # consistency with Macs (which lack an insert key). + self.accept('time-delete', self.__beginTossPie) + self.accept('time-delete-up', self.__endTossPie) + + self.accept('pieHit', self.__pieHit) + + # These events interrupt a pie toss in progress. + self.accept('interrupt-pie', self.interruptPie) + self.accept('InputState-jump', self.__toonMoved) + self.accept('InputState-forward', self.__toonMoved) + self.accept('InputState-reverse', self.__toonMoved) + self.accept('InputState-turnLeft', self.__toonMoved) + self.accept('InputState-turnRight', self.__toonMoved) + self.accept('InputState-slide', self.__toonMoved) + + QuestParser.init() + + def __handlePurchase(self): + # confirm exit game to purchase + """__handlePurchase(self) + """ + self.purchaseButton.hide() + if base.cr.isWebPlayToken() or __dev__: + if base.cr.isPaid(): + if base.cr.productName in ['DisneyOnline-UK', 'DisneyOnline-AP', + 'JP', 'DE', 'BR', 'FR'] : + paidNoParentPassword = launcher and launcher.getParentPasswordSet() + else: + paidNoParentPassword = launcher and not launcher.getParentPasswordSet() + else: + paidNoParentPassword = 0 + self.leaveToPayDialog = LeaveToPayDialog.LeaveToPayDialog(paidNoParentPassword, self.purchaseButton.show) + self.leaveToPayDialog.show() + else: + self.notify.error("You should not get here without a PlayToken") + + if base.wantKarts: + def addKartPage( self ): + """ + Purpose: + + Params: None + Return: None + """ + # Only show the button if the toon owns a kart. + if( self.hasKart() ): + + if hasattr(self, "kartPage") and self.kartPage != None: + # The page is already loaded; never mind. + return + + if not launcher.getPhaseComplete(6): + # We haven't downloaded phase 6 yet; set a callback hook + # so the pages will load when we do get phase 6. + self.acceptOnce('phaseComplete-6', self.addKartPage) + return + + self.kartPage = KartPage.KartPage() + self.kartPage.setAvatar( self ) + self.kartPage.load() + self.book.addPage( self.kartPage, + pageName = TTLocalizer.KartPageTitle ) + + def setWantBattles(self, wantBattles): + self.wantBattles = wantBattles + + def loadDisguisePages(self): + # Load up and add the Cog disguise page and NPC SOS pages to + # the shticker book. This is deferred because it doesn't + # happen for a new toon who has never been to CogHQ, and it + # doesn't happen until we have downloaded phase 9. + if self.disguisePage != None or self.sosPage != None: + # The pages are already loaded; never mind. + return + + if not launcher.getPhaseComplete(9): + # We haven't downloaded phase 9 yet; set a callback hook + # so the pages will load when we do get phase 9. + self.acceptOnce('phaseComplete-9', self.loadDisguisePages) + return + + self.disguisePage = DisguisePage.DisguisePage() + self.disguisePage.load() + self.book.addPage(self.disguisePage, + pageName = TTLocalizer.DisguisePageTitle) + + self.sosPage = NPCFriendPage.NPCFriendPage() + self.sosPage.load() + self.book.addPage(self.sosPage, + pageName = TTLocalizer.NPCFriendPageTitle) + + def loadGardenPages(self): + if self.gardenPage != None : + # The pages are already loaded; never mind. + return + + if not launcher.getPhaseComplete(5.5): + # We haven't downloaded phase 5.5 yet; set a callback hook + # so the pages will load when we do get phase 5.5. + self.acceptOnce('phaseComplete-5.5', self.loadPhase55Stuff) + return + + self.gardenPage = GardenPage.GardenPage() + self.gardenPage.load() + self.book.addPage(self.gardenPage, pageName = TTLocalizer.GardenPageTitle) + + def loadPhase55Stuff(self): + if self.gardenPage == None: + self.gardenPage = GardenPage.GardenPage() + self.gardenPage.load() + self.book.addPage(self.gardenPage, pageName = TTLocalizer.GardenPageTitle) + elif not launcher.getPhaseComplete(5.5): + self.acceptOnce('phaseComplete-5.5', self.loadPhase55Stuff) + self.refreshOnscreenButtons() + + +# def displayWhisper(self, fromId, chatString, whisperType): +# # We have to define this here to force the correct +# # displayWhisper() function to be called. +# LocalAvatar.LocalAvatar.displayWhisper(self, fromId, chatString, whisperType) + +# def displayWhisperPlayer(self, fromId, chatString, whisperType): +# # We have to define this here to force the correct +# # displayWhisper() function to be called. +# LocalAvatar.LocalAvatar.displayWhisperPlayer(self, fromId, chatString, whisperType) + + # GM account stuff + def setAsGM(self, state): + self.notify.debug("Setting GM State: %s in LocalToon" %state) + DistributedToon.DistributedToon.setAsGM(self, state) + if self.gmState: + if base.config.GetString('gm-nametag-string', '') != '': + self.gmNameTagString = base.config.GetString('gm-nametag-string') + if base.config.GetString('gm-nametag-color', '') != '': + self.gmNameTagColor = base.config.GetString('gm-nametag-color') + if base.config.GetInt('gm-nametag-enabled', 0): + self.gmNameTagEnabled = 1 + self.d_updateGMNameTag() + + # Whisper + def displayTalkWhisper(self, fromId, avatarName, rawString, mods): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This function overrides a similar function in DistributedAvatar. + """ + sender = base.cr.identifyAvatar(fromId) + if sender: + chatString, scrubbed = sender.scrubTalk(rawString, mods) + else: + chatString,scrubbed = self.scrubTalk(rawString, mods) + + sender = self + sfx = self.soundWhisper + + # MPG we need to identify the sender in a non-toontown specific way + #sender = base.cr.identifyAvatar(fromId) + + + chatString = avatarName + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + + whisper.setClickable(avatarName, fromId) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + + def displayTalkAccount(self, fromId, senderName, rawString, mods): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This function overrides a similar function in DistributedAvatar. + """ + sender = None + playerInfo = None + sfx = self.soundWhisper + + # MPG we need to identify the sender in a non-toontown specific way + #sender = base.cr.identifyAvatar(fromId) + #sender = idenityPlayer(fromId) + playerInfo = base.cr.playerFriendsManager.playerId2Info.get(fromId,None) + if playerInfo == None: + #import pdb; pdb.set_trace() + return + + senderAvId = base.cr.playerFriendsManager.findAvIdFromPlayerId(fromId) + + if (not senderName) and base.cr.playerFriendsManager.playerId2Info.get(fromId): + senderName = base.cr.playerFriendsManager.playerId2Info.get(fromId).playerName + + senderAvatar = base.cr.identifyAvatar(senderAvId) + if sender: + chatString,scrubbed = senderAvatar.scrubTalk(rawString, mods) + else: + chatString,scrubbed = self.scrubTalk(rawString, mods) + + chatString = senderName + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + WhisperPopup.WTNormal) + if playerInfo != None: + whisper.setClickable(senderName, fromId, 1) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + + + def isLocal(self): + return 1 + + def canChat(self): + """ + Returns true if there is some point to chatting: the local + toon has at least one secret friend, for instance, or he has + chat permission. + """ + if not self.cr.allowAnyTypedChat(): + return 0 + + if self.commonChatFlags & (ToontownGlobals.CommonChat | ToontownGlobals.SuperChat): + return 1 + + if base.cr.whiteListChatEnabled: + return 1 + + for friendId, flags in self.friendsList: + if flags & ToontownGlobals.FriendChat: + return 1 + + return 0 + + # chat methods + def startChat(self): + if self.tutorialAck: + self.notify.info("calling LocalAvatar.startchat") + LocalAvatar.LocalAvatar.startChat(self) + self.accept("chatUpdateSCToontask", self.b_setSCToontask) + self.accept("chatUpdateSCResistance", self.d_reqSCResistance) + self.accept("chatUpdateSCSinging", self.b_setSCSinging) + self.accept("whisperUpdateSCToontask", self.whisperSCToontaskTo) + else: + self.notify.info("NOT calling LocalAvatar.startchat, in tutorial") + + def stopChat(self): + LocalAvatar.LocalAvatar.stopChat(self) + self.ignore("chatUpdateSCToontask") + self.ignore("chatUpdateSCResistance") + self.ignore("chatUpdateSCSinging") + self.ignore("whisperUpdateSCToontask") + + def tunnelIn(self, tunnelOrigin): + self.b_setTunnelIn(self.tunnelX * .8, tunnelOrigin) + + def tunnelOut(self, tunnelOrigin): + self.tunnelX = self.getX(tunnelOrigin) + tunnelY = self.getY(tunnelOrigin) + self.b_setTunnelOut(self.tunnelX * .95, tunnelY, tunnelOrigin) + + def handleTunnelIn(self, startTime, endX, x, y, z, h): + self.notify.debug("LocalToon.handleTunnelIn") + + # create a temporary tunnel origin node + tunnelOrigin = render.attachNewNode('tunnelOrigin') + tunnelOrigin.setPosHpr(x,y,z,h,0,0) + + self.b_setAnimState("run", self.animMultiplier) + self.stopLookAround() + self.reparentTo(render) + self.runSound() + + # Pull the camera back + camera.reparentTo(render) + camera.setPosHpr(tunnelOrigin, 0, 20, 12, 180, -20, 0) + + # iris in + base.transitions.irisIn(0.4) + + # lerp the toon + toonTrack = self.getTunnelInToonTrack(endX, tunnelOrigin) + + def cleanup(self=self, tunnelOrigin=tunnelOrigin): + self.stopSound() + tunnelOrigin.removeNode() + messenger.send("tunnelInMovieDone") + + self.tunnelTrack = Sequence( + toonTrack, + Func(cleanup), + ) + self.tunnelTrack.start(globalClock.getFrameTime() - startTime) + + def handleTunnelOut(self, startTime, startX, startY, x, y, z, h): + self.notify.debug("LocalToon.handleTunnelOut") + + # create a temporary tunnel origin node + tunnelOrigin = render.attachNewNode('tunnelOrigin') + tunnelOrigin.setPosHpr(x,y,z,h,0,0) + + self.b_setAnimState("run", self.animMultiplier) + self.runSound() + self.stopLookAround() + + tracks = Parallel() + + # Pull the camera back + camera.wrtReparentTo(render) + startPos = camera.getPos(tunnelOrigin) + startHpr = camera.getHpr(tunnelOrigin) + camLerpDur = 1. + reducedCamH = fitDestAngle2Src(startHpr[0], 180) + tracks.append( + LerpPosHprInterval(camera, camLerpDur, pos=Point3(0, 20, 12), + hpr=Point3(reducedCamH, -20, 0), + startPos = startPos, + startHpr = startHpr, + other=tunnelOrigin, blendType="easeInOut", + name="tunnelOutLerpCamPos"), + ) + + # lerp the toon + toonTrack = self.getTunnelOutToonTrack(startX, startY, tunnelOrigin) + tracks.append(toonTrack) + + # iris out + irisDur = .4 + tracks.append(Sequence( + Wait(toonTrack.getDuration() - (irisDur + .1)), + Func(base.transitions.irisOut, irisDur), + )) + + def cleanup(self=self, tunnelOrigin=tunnelOrigin): + self.stopSound() + self.detachNode() + tunnelOrigin.removeNode() + messenger.send("tunnelOutMovieDone") + + self.tunnelTrack = Sequence( + tracks, + Func(cleanup), + ) + self.tunnelTrack.start(globalClock.getFrameTime() - startTime) + + ### Tossing a pie (used in final Boss Battle sequence) + + def getPieBubble(self): + if self.__pieBubble == None: + bubble = CollisionSphere(0, 0, 0, 1) + node = CollisionNode('pieBubble') + node.addSolid(bubble) + node.setFromCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask | ToontownGlobals.FloorBitmask) + node.setIntoCollideMask(BitMask32.allOff()) + self.__pieBubble = NodePath(node) + self.pieHandler = CollisionHandlerEvent() + self.pieHandler.addInPattern('pieHit') + self.pieHandler.addInPattern('pieHit-%in') + return self.__pieBubble + + def __beginTossPieMouse(self, mouseParam): + self.__beginTossPie(globalClock.getFrameTime()) + + def __endTossPieMouse(self, mouseParam): + self.__endTossPie(globalClock.getFrameTime()) + + def __beginTossPie(self, time): + # The toss-pie key was pressed. + if self.tossPieStart != None: + # This is probably just key-repeat. + return + + if not self.allowPies: + return + + if self.numPies == 0: + messenger.send('outOfPies') + return + + if self.__pieInHand(): + return + + if getattr(self.controlManager.currentControls, "isAirborne", 0): + # Can't toss a pie while we're airborne. + return + + messenger.send('wakeup') + self.localPresentPie(time) + + taskName = self.uniqueName('updatePiePower') + taskMgr.add(self.__updatePiePower, taskName) + + def __endTossPie(self, time): + if self.tossPieStart == None: + return + + taskName = self.uniqueName('updatePiePower') + taskMgr.remove(taskName) + + messenger.send('wakeup') + # The toss-pie key was released. Toss the pie. + power = self.__getPiePower(time) + self.tossPieStart = None + + self.localTossPie(power) + + def localPresentPie(self, time): + import TTEmote + from otp.avatar import Emote + + self.__stopPresentPie() + if self.tossTrack: + tossTrack = self.tossTrack + self.tossTrack = None + tossTrack.finish() + + self.interruptPie() + + self.tossPieStart = time + self.__pieSequence = (self.__pieSequence + 1) & 0xff + sequence = self.__pieSequence + self.__presentingPie = 1 + + pos = self.getPos() + hpr = self.getHpr() + timestamp32 = globalClockDelta.getFrameNetworkTime(bits = 32) + + self.sendUpdate('presentPie', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2], + timestamp32]) + + # We are now in pie-throwing mode, and can't move until we get + # the end-pie event (which is thrown after our toss animation + # completes, below). We can, however, jump, which will + # interrupt the pie in the air. + Emote.globalEmote.disableBody(self) + messenger.send('begin-pie') + + ival = self.getPresentPieInterval(pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2]) + ival = Sequence(ival, + name = self.uniqueName('localPresentPie')) + assert self.tossTrack == None + self.tossTrack = ival + ival.start() + + self.makePiePowerMeter() + self.__piePowerMeter.show() + self.__piePowerMeterSequence = sequence + self.__piePowerMeter['value'] = 0 + + def __stopPresentPie(self): + if self.__presentingPie: + import TTEmote + from otp.avatar import Emote + Emote.globalEmote.releaseBody(self) + messenger.send('end-pie') + self.__presentingPie = 0 + + taskName = self.uniqueName('updatePiePower') + taskMgr.remove(taskName) + + def __getPiePower(self, time): + elapsed = max(time - self.tossPieStart, 0.0) + t = elapsed / self.piePowerSpeed + t = math.pow(t, self.piePowerExponent) + power = int(t * 100) % 200 + if power > 100: + power = 200 - power + + return power + + def __updatePiePower(self, task): + if not self.__piePowerMeter: + return Task.done + + self.__piePowerMeter['value'] = self.__getPiePower(globalClock.getFrameTime()) + return Task.cont + + def interruptPie(self): + # Externally interrupt the present pie cycle (for instance, + # because we've been hit). + self.cleanupPieInHand() + + self.__stopPresentPie() + if self.__piePowerMeter: + self.__piePowerMeter.hide() + + pie = self.pieTracks.get(self.__pieSequence) + if pie and pie.getT() < 14./24.: + # If the pie hasn't started to fly yet, stop it prematurely. + del self.pieTracks[self.__pieSequence] + pie.pause() + + def __pieInHand(self): + # Returns true if calling interruptPie() would eliminate the + # current pie being thrown. + pie = self.pieTracks.get(self.__pieSequence) + return (pie and pie.getT() < 15./24.) + + def __toonMoved(self, isSet): + if isSet: + self.interruptPie() + + def localTossPie(self, power): + if not self.__presentingPie: + return + + pos = self.getPos() + hpr = self.getHpr() + timestamp32 = globalClockDelta.getFrameNetworkTime(bits = 32) + sequence = self.__pieSequence + + if self.tossTrack: + tossTrack = self.tossTrack + self.tossTrack = None + tossTrack.finish() + if self.pieTracks.has_key(sequence): + pieTrack = self.pieTracks[sequence] + del self.pieTracks[sequence] + pieTrack.finish() + if self.splatTracks.has_key(sequence): + splatTrack = self.splatTracks[sequence] + del self.splatTracks[sequence] + splatTrack.finish() + + self.makePiePowerMeter() + self.__piePowerMeter['value'] = power + self.__piePowerMeter.show() + self.__piePowerMeterSequence = sequence + + # Get a new instance of the pie collision bubble. + pieBubble = self.getPieBubble().instanceTo(NodePath()) + + # Define a function to call when the pie leaves our hand. + # This will actually distribute the tossPie call. We can't + # distribute this early, because we might interrupt the pie + # toss. + def pieFlies(self = self, pos = pos, hpr = hpr, sequence = sequence, + power = power, timestamp32 = timestamp32, + pieBubble = pieBubble): + self.sendUpdate('tossPie', [pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2], + sequence, power, timestamp32]) + if self.numPies != ToontownGlobals.FullPies: + self.setNumPies(self.numPies - 1) + base.cTrav.addCollider(pieBubble, self.pieHandler) + + toss, pie, flyPie = self.getTossPieInterval( + pos[0], pos[1], pos[2], + hpr[0], hpr[1], hpr[2], + power, + beginFlyIval = Func(pieFlies)) + + pieBubble.reparentTo(flyPie) + flyPie.setTag('pieSequence', str(sequence)) + + toss = Sequence(toss) + assert self.tossTrack == None + self.tossTrack = toss + toss.start() + + pie = Sequence(pie, + Func(base.cTrav.removeCollider, pieBubble), + Func(self.pieFinishedFlying, sequence)) + assert not self.pieTracks.has_key(sequence) + self.pieTracks[sequence] = pie + pie.start() + + def pieFinishedFlying(self, sequence): + DistributedToon.DistributedToon.pieFinishedFlying(self, sequence) + + if self.__piePowerMeterSequence == sequence: + self.__piePowerMeter.hide() + + def __finishPieTrack(self, sequence): + if self.pieTracks.has_key(sequence): + pieTrack = self.pieTracks[sequence] + del self.pieTracks[sequence] + pieTrack.finish() + + def __pieHit(self, entry): + if not entry.hasSurfacePoint() or not entry.hasInto(): + # Not a collision solid we understand. Weird. + return + if not entry.getInto().isTangible(): + # Just a trigger polygon. Ignore it. + return + + sequence = int(entry.getFromNodePath().getNetTag('pieSequence')) + self.__finishPieTrack(sequence) + + if self.splatTracks.has_key(sequence): + splatTrack = self.splatTracks[sequence] + del self.splatTracks[sequence] + splatTrack.finish() + + # The pie hit something solid. Generate a distributed splat. + + # Check the thing we hit for a pieCode. If it has one, it + # gets passed along with the message. This may mean something + # different according to context (it may indicate, for + # instance, the kind of target we hit). + pieCode = 0 + pieCodeStr = entry.getIntoNodePath().getNetTag('pieCode') + if pieCodeStr: + pieCode = int(pieCodeStr) + + pos = entry.getSurfacePoint(render) + timestamp32 = globalClockDelta.getFrameNetworkTime(bits = 32) + self.sendUpdate('pieSplat', [pos[0], pos[1], pos[2], + sequence, pieCode, + timestamp32]) + splat = self.getPieSplatInterval(pos[0], pos[1], pos[2], pieCode) + + splat = Sequence(splat, + Func(self.pieFinishedSplatting, sequence)) + assert not self.splatTracks.has_key(sequence) + self.splatTracks[sequence] = splat + splat.start() + + messenger.send('pieSplat', [self, pieCode]) + messenger.send('localPieSplat', [pieCode, entry]) + + def beginAllowPies(self): + # Call this when entering a mode (e.g. walk) in which it is + # allowable to toss pies, whether you have any pies or not. + self.allowPies = 1 + self.updatePieButton() + + def endAllowPies(self): + self.allowPies = 0 + self.updatePieButton() + + def makePiePowerMeter(self): + from direct.gui.DirectGui import DirectWaitBar, DGG + if self.__piePowerMeter == None: + self.__piePowerMeter = DirectWaitBar( + frameSize = (-0.2, 0.2, -0.03, 0.03), + relief = DGG.SUNKEN, + borderWidth = (0.005, 0.005), + barColor = (0.4, 0.6, 1.0, 1), + pos = (0, 0.1, 0.8), + ) + + self.__piePowerMeter.hide() + + def updatePieButton(self): + from toontown.toonbase import ToontownBattleGlobals + from direct.gui.DirectGui import DirectButton, DGG + + # Redraws the onscreen button for throwing pies. + wantButton = 0 + if self.allowPies and self.numPies > 0: + wantButton = 1 + + if not launcher.getPhaseComplete(5): + # If we haven't downloaded the pies yet, don't bother with + # a pie button. + wantButton = 0 + + haveButton = (self.__pieButton != None) + if not haveButton and not wantButton: + return + + if haveButton and not wantButton: + self.__pieButton.destroy() + self.__pieButton = None + self.__pieButtonType = None + self.__pieButtonCount = None + return + + if self.__pieButtonType != self.pieType: + # We need a new icon. Might as well get a whole new button. + if self.__pieButton: + self.__pieButton.destroy() + self.__pieButton = None + + if self.__pieButton == None: + inv = self.inventory + + if (self.pieType >= len(inv.invModels[ToontownBattleGlobals.THROW_TRACK])): + gui = loader.loadModel('phase_3.5/models/gui/stickerbook_gui') + pieGui = gui.find('**/summons') + pieScale = 0.1 + else: + gui = None + pieGui = inv.invModels[ToontownBattleGlobals.THROW_TRACK][self.pieType], + pieScale = 0.85 + + self.__pieButton = DirectButton( + image = (inv.upButton, + inv.downButton, + inv.rolloverButton), + geom = pieGui, + text = "50", + text_scale = 0.04, + text_align = TextNode.ARight, + geom_scale = pieScale, + geom_pos = (-0.01, 0, 0), + text_fg = Vec4(1, 1, 1, 1), + text_pos = (0.07, -0.04), + relief = None, + image_color = (0, 0.6, 1, 1), + pos = (0, 0.1, 0.9), + ) + self.__pieButton.bind(DGG.B1PRESS, self.__beginTossPieMouse) + self.__pieButton.bind(DGG.B1RELEASE, self.__endTossPieMouse) + self.__pieButtonType = self.pieType + self.__pieButtonCount = None + if gui: + del gui + + if self.__pieButtonCount != self.numPies: + if self.numPies == ToontownGlobals.FullPies: + self.__pieButton['text'] = '' + else: + self.__pieButton['text'] = str(self.numPies) + self.__pieButtonCount = self.numPies + + ### BEGIN FROM LOCAL AVATAR ######################################## + + def displayWhisper(self, fromId, chatString, whisperType): + """displayWhisper(self, int fromId, string chatString, int whisperType) + + Displays the whisper message in whatever capacity makes sense. + This function overrides a similar function in DistributedAvatar. + """ + sender = None + sfx = self.soundWhisper + + # A message from clarabelle about the catalog + if fromId == TTLocalizer.Clarabelle: + chatString = TTLocalizer.Clarabelle + ": " + chatString + # Play the phone ring sound instead of the pssst + sfx = self.soundPhoneRing + elif fromId != 0: + sender = base.cr.identifyAvatar(fromId) + + if (whisperType == WhisperPopup.WTNormal or \ + whisperType == WhisperPopup.WTQuickTalker): + if sender == None: + return + # Prefix the sender's name to the message. + chatString = sender.getName() + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + whisperType) + if sender != None: + whisper.setClickable(sender.getName(), fromId) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + + def displaySystemClickableWhisper(self, fromId, chatString, whisperType): + """displayPartyCanStartWhisper (self, int fromId, string chatString, int whisperType) + + Displays the party can start whisper message. + """ + sender = None + sfx = self.soundWhisper + + # A message from clarabelle about the catalog + if fromId == TTLocalizer.Clarabelle: + chatString = TTLocalizer.Clarabelle + ": " + chatString + # Play the phone ring sound instead of the pssst + sfx = self.soundPhoneRing + elif fromId != 0: + sender = base.cr.identifyAvatar(fromId) + + if (whisperType == WhisperPopup.WTNormal or \ + whisperType == WhisperPopup.WTQuickTalker): + if sender == None: + return + # Prefix the sender's name to the message. + chatString = sender.getName() + ": " + chatString + + whisper = WhisperPopup(chatString, + OTPGlobals.getInterfaceFont(), + whisperType) + # this is the main difference, we know avId 0 (the system) is sending the whisper + # but we force the whisper to be clickable anyway + whisper.setClickable("", fromId) + + whisper.manage(base.marginManager) + base.playSfx(sfx) + + def clickedWhisper(self, doId, isPlayer = None): + """Overriden from LocalAvatar to handle the case of party can start whisper.""" + if doId > 0: + LocalAvatar.LocalAvatar.clickedWhisper(self, doId, isPlayer) + else: + foundCanStart = False + for partyInfo in self.hostedParties: + if partyInfo.status == PartyGlobals.PartyStatus.CanStart: + foundCanStart = True + break + if base.cr and base.cr.playGame and base.cr.playGame.getPlace() and base.cr.playGame.getPlace().fsm: + fsm = base.cr.playGame.getPlace().fsm + curState = fsm.getCurrentState().getName() + if curState == 'walk': + if hasattr(self, "eventsPage"): + desiredMode = -1 + if doId == -1: + desiredMode = EventsPage.EventsPage_Invited + elif foundCanStart: + desiredMode = EventsPage.EventsPage_Host + if desiredMode >= 0: + self.book.setPage(self.eventsPage) + self.eventsPage.setMode(desiredMode) + fsm.request("stickerBook") + + def loadFurnitureGui(self): + # Make sure we are not already loaded + if self.__furnitureGui: + return + # Related to the friends list button (at least, adjacent to + # it) is the move-furniture button, which is only revealed + # when the friends list button is revealed and we have a + # global DistributedFurnitureManager available. + guiModels = loader.loadModel('phase_5.5/models/gui/house_design_gui') + # This consists of an attic frame + self.__furnitureGui = DirectFrame( + relief = None, + pos = (-1.19, 0.00, 0.33), + scale= 0.04, + image = guiModels.find('**/attic') + ) + # Add a roof + DirectLabel( + parent = self.__furnitureGui, + relief = None, + image = guiModels.find('**/rooftile') + ) + # And a start button + bMoveStartUp = guiModels.find('**/bu_attic/bu_attic_up') + bMoveStartDown = guiModels.find('**/bu_attic/bu_attic_down') + bMoveStartRollover = guiModels.find('**/bu_attic/bu_attic_rollover') + DirectButton( + parent = self.__furnitureGui, + relief = None, + image = [bMoveStartUp, bMoveStartDown, + bMoveStartRollover, bMoveStartUp], + text = ["",TTLocalizer.HDMoveFurnitureButton, + TTLocalizer.HDMoveFurnitureButton], + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_font = ToontownGlobals.getInterfaceFont(), + pos = (-0.3, 0, 9.4), + command = self.__startMoveFurniture, + ) + self.__furnitureGui.hide() + guiModels.removeNode() + + def showFurnitureGui(self): + # Presumably whoever is calling this is in the correct download phase + self.loadFurnitureGui() + self.__furnitureGui.show() + + def hideFurnitureGui(self): + if self.__furnitureGui: + self.__furnitureGui.hide() + + def loadClarabelleGui(self): + # Make sure we are not already loaded + if self.__clarabelleButton: + return + guiItems = loader.loadModel('phase_5.5/models/gui/catalog_gui') + circle = guiItems.find('**/cover/blue_circle') + icon = guiItems.find('**/cover/clarabelle') + icon.reparentTo(circle) + + # This is the initial color of the blue circle. + rgba = VBase4(0.71589, 0.784547, 0.974, 1.0) + white = VBase4(1.0, 1.0, 1.0, 1.0) + + # Prevent the picture of Clarabelle from changing colors as we + # monkey with the color of the circle. + icon.setColor(white) + claraXPos = 1.45 + newScale = oldScale = 0.5 + newPos = (claraXPos, 1.0, 0.37) + if WantNewsPage: + claraXPos += AdjustmentForNewsButton + oldPos = (claraXPos, 1.0, 0.37), + newScale = oldScale * ToontownGlobals.NewsPageScaleAdjust + newPos = (claraXPos - 0.1, 1.0, 0.45) + + self.__clarabelleButton = DirectButton( + relief = None, + image = circle, + text = "", + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = 0.10, + text_pos = (-1.06, 1.06), + text_font = ToontownGlobals.getInterfaceFont(), + pos = newPos, + scale = newScale, + command = self.__handleClarabelleButton, + ) + + # Give it a sort of 1 so it appears on top of the + # CatalogNotifyDialog. + self.__clarabelleButton.reparentTo(aspect2d, 1) + + # Set up an interval to flash the circle slowly to catch the + # player's attention. + button = self.__clarabelleButton.stateNodePath[0] + + self.__clarabelleFlash = Sequence( + LerpColorInterval(button, 2, white, blendType='easeInOut'), + LerpColorInterval(button, 2, rgba, blendType='easeInOut') + ) + + # Start it looping, but pause it, so we can resume/pause it to + # start/stop the flashing. + self.__clarabelleFlash.loop() + self.__clarabelleFlash.pause() + + def showClarabelleGui(self, mailboxItems): + # Presumably whoever is calling this is in the correct download phase + self.loadClarabelleGui() + if mailboxItems: + self.__clarabelleButton['text'] = ["",TTLocalizer.CatalogNewDeliveryButton,TTLocalizer.CatalogNewDeliveryButton] + else: + self.__clarabelleButton['text'] = ["",TTLocalizer.CatalogNewCatalogButton,TTLocalizer.CatalogNewCatalogButton] + + # double check if it's just mail + if not self.mailboxNotify and \ + not self.awardNotify and \ + (self.catalogNotify == ToontownGlobals.OldItems) and \ + ((self.simpleMailNotify != ToontownGlobals.NoItems) or (self.inviteMailNotify != ToontownGlobals.NoItems)): + self.__clarabelleButton['text'] = ["",TTLocalizer.MailNewMailButton, + TTLocalizer.MailNewMailButton] + + self.__clarabelleButton.show() + self.__clarabelleFlash.resume() + + def hideClarabelleGui(self): + if self.__clarabelleButton: + self.__clarabelleButton.hide() + self.__clarabelleFlash.pause() + + def __handleClarabelleButton(self): + place = base.cr.playGame.getPlace() + if place == None: + self.notify.warning("Tried to go home, but place is None.") + return + if self.__catalogNotifyDialog: + self.__catalogNotifyDialog.cleanup() + self.__catalogNotifyDialog = None + place.goHomeNow(self.lastHood) + + def __startMoveFurniture(self): + if self.cr.furnitureManager != None: + self.cr.furnitureManager.d_suggestDirector(self.doId) + elif self.furnitureManager != None: + self.furnitureManager.d_suggestDirector(self.doId) + + def stopMoveFurniture(self): + if self.furnitureManager != None: + self.furnitureManager.d_suggestDirector(0) + + def setFurnitureDirector(self, avId, furnitureManager): + # This method is called by the furniture manager. + if avId == 0: + # No one is the furniture director. + if self.furnitureManager == furnitureManager: + messenger.send('exitFurnitureMode', [furnitureManager]) + self.furnitureManager = None + self.furnitureDirector = None + + elif avId != self.doId: + # Someone else is the furniture director. + if self.furnitureManager == None or \ + self.furnitureDirector != avId: + self.furnitureManager = furnitureManager + self.furnitureDirector = avId + messenger.send('enterFurnitureMode', [furnitureManager, 0]) + + else: # avId == self.doId + # We are the furniture director. + if self.furnitureManager != None: + messenger.send('exitFurnitureMode', [self.furnitureManager]) + self.furnitureManager = None + self.furnitureManager = furnitureManager + self.furnitureDirector = avId + messenger.send('enterFurnitureMode', [furnitureManager, 1]) + + self.refreshOnscreenButtons() + + def getAvPosStr(self): + pos = self.getPos() + hpr = self.getHpr() + serverVersion = base.cr.getServerVersion() + districtName = base.cr.getShardName(base.localAvatar.defaultShard) + # See if you are in the town + if (hasattr(base.cr.playGame.hood, "loader") and + hasattr(base.cr.playGame.hood.loader, "place") and + (base.cr.playGame.getPlace() != None)): + zoneId = base.cr.playGame.getPlace().getZoneId() + else: + zoneId = "?" + strPosCoordText = "X: %.3f" % pos[0] + ", Y: %.3f" % pos[1] + \ + "\nZ: %.3f" % pos[2] + ", H: %.3f" % hpr[0] + \ + "\nZone: %s" % str(zoneId) + ", Ver: %s, " % serverVersion + \ + "District: %s" % districtName + return strPosCoordText + self.refreshOnscreenButtons() + + def thinkPos(self): + """ + display the current position in a thought balloon + """ + pos = self.getPos() + hpr = self.getHpr() + serverVersion = base.cr.getServerVersion() + districtName = base.cr.getShardName(base.localAvatar.defaultShard) + # See if you are in the town + if (hasattr(base.cr.playGame.hood, "loader") and + hasattr(base.cr.playGame.hood.loader, "place") and + (base.cr.playGame.getPlace() != None)): + zoneId = base.cr.playGame.getPlace().getZoneId() + else: + zoneId = "?" + + strPos = "(%.3f" % pos[0] + "\n %.3f" % pos[1] + "\n %.3f)" % pos[2] + \ + "\nH: %.3f" % hpr[0] + \ + "\nZone: %s" % str(zoneId) + ",\nVer: %s, " % serverVersion + \ + "\nDistrict: %s" % districtName + # We don't really need to see the PR, and + # it just clutters up the display. + #"\nP: %.3f" % hpr[1] + "\nR: %.3f" % hpr[2] + + # print to log too + print "Current position=",strPos.replace('\n', ', ') + + self.setChatAbsolute(strPos, CFThought | CFTimeout) + + # relief = None) + # make sure it appears in front of arrows and other gui items + #self.CoordText.setBin("gui-popup",0) + + def __placeMarker(self): + # for treasure placement, etc. + pos = self.getPos() + hpr = self.getHpr() + #print '(%d, %d, %0.1f),' % (pos[0], pos[1], pos[2]) + # print '(%0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f),' % \ + # (pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2]) + chest = loader.loadModel("phase_4/models/props/coffin") + chest.reparentTo(render) + chest.setColor(1, 0, 0, 1) + chest.setPosHpr(pos, hpr) + chest.setScale(0.5) + + ### Button to open up the friends list. ### + + def setFriendsListButtonActive(self, active): + """setFriendsListButtonActive(self, bool active) + + Sets whether the friends list button should be 'active' or + not; i.e. the game is in a state that it can accept it (like + walk mode). The button will be revealed only when it is both + 'active' and not obscured. + """ + self.friendsListButtonActive = active + self.refreshOnscreenButtons() + + def obscureFriendsListButton(self, increment): + """obscureFriendsListButton(self, int increment) + + Increments or decrements the obscuration count for the friends + list button. When this count is greater than zero, the button + is considered obscured, and will not be revealed even if it is + active. + + The increment value is the amount to add, which should + generally be either 1 or -1. + """ + self.friendsListButtonObscured += increment + self.refreshOnscreenButtons() + + def obscureMoveFurnitureButton(self, increment): + """obscureMoveFurnitureButton(self, int increment) + + Increments or decrements the obscuration count for the + move-furniture button. When this count is greater than zero, + the button is considered obscured, and will not be revealed + even if it is active. + + The increment value is the amount to add, which should + generally be either 1 or -1. + """ + self.moveFurnitureButtonObscured += increment + self.refreshOnscreenButtons() + + def obscureClarabelleButton(self, increment): + """obscureClarabelleButton(self, int increment) + + Increments or decrements the obscuration count for the + clarabelle button. When this count is greater than zero, the + button is considered obscured, and will not be revealed even + if it is active. + + Note that the Clarabelle button is also automatically obscured + whenever the friends list button is obscured, regardless of + the setting of this flag. + + The increment value is the amount to add, which should + generally be either 1 or -1. + """ + self.clarabelleButtonObscured += increment + self.refreshOnscreenButtons() + + def refreshOnscreenButtons(self): + #print ("sanity") + #print self.mailboxNotify + self.bFriendsList.hide() + self.hideFurnitureGui() + self.hideClarabelleGui() + clarabelleHidden = 1 + + self.ignore(ToontownGlobals.FriendsListHotkey) +# import pdb; pdb.set_trace() + + if self.friendsListButtonActive and \ + self.friendsListButtonObscured <= 0: + self.bFriendsList.show() + self.accept(ToontownGlobals.FriendsListHotkey, self.sendFriendsListEvent) + + if self.clarabelleButtonObscured <= 0 and self.isTeleportAllowed(): + if self.catalogNotify == ToontownGlobals.NewItems or \ + self.mailboxNotify == ToontownGlobals.NewItems or \ + self.simpleMailNotify == ToontownGlobals.NewItems or \ + self.inviteMailNotify == ToontownGlobals.NewItems or \ + self.awardNotify == ToontownGlobals.NewItems: + # There are new items at the phone or in the mailbox. + # Show a Clarabelle button we can click on to go home + # and check it out. + showClarabelle = not launcher or launcher.getPhaseComplete(5.5) + # also make sure we do not pop-up the panel if we are early + # in the game + for quest in self.quests: + if quest[0] in Quests.PreClarabelleQuestIds and \ + ((self.mailboxNotify != ToontownGlobals.NewItems) and (self.awardNotify != ToontownGlobals.NewItems)): + showClarabelle = 0 + if showClarabelle: + newItemsInMailbox = self.mailboxNotify == ToontownGlobals.NewItems or \ + self.awardNotify == ToontownGlobals.NewItems + self.showClarabelleGui( newItemsInMailbox) + clarabelleHidden = 0 + + if clarabelleHidden: + # If we have to hide the clarabelle button, then we can't + # show the catalog notification either. + if self.__catalogNotifyDialog: + self.__catalogNotifyDialog.cleanup() + self.__catalogNotifyDialog = None + else: + # Otherwise, show it if there's a new one. + self.newCatalogNotify() + + if self.moveFurnitureButtonObscured <= 0: + if self.furnitureManager != None and \ + self.furnitureDirector == self.doId: + + # We have entered furniture moving mode. The + # move-furniture button should be hidden (because the + # furniture gui is there instead). + + # However, when furniture mode is done, it will scale + # the move-furniture button back down to its normal + # size (below). Until then, scale it up to match the + # scale of the furniture gui. + + # Make sure we are loaded + self.loadFurnitureGui() + + self.__furnitureGui.setPos(-1.16,0,-0.03) + self.__furnitureGui.setScale(0.06) + + elif self.cr.furnitureManager != None: + # We are not in furniture moving mode, but we are + # standing in our own house. The move-furniture + # button should be visible. + + self.showFurnitureGui() + + # Scale down button to default size. + taskMgr.remove('lerpFurnitureButton') + self.__furnitureGui.lerpPosHprScale( + pos = Point3(-1.19, 0.00, 0.33), + hpr = Vec3(0.00, 0.00, 0.00), + scale = Vec3(0.04, 0.04, 0.04), + time = 1.0, blendType = 'easeInOut', + task = 'lerpFurnitureButton') + #print ("End refreshOnscreenButtons") + + if hasattr(self,'inEstate') and self.inEstate: + self.loadGardeningGui() + self.hideGardeningGui() + else: + self.hideGardeningGui() + + def setGhostMode(self, flag): + if flag == 2: + # ghost mode 2 indicates a magic word. + self.seeGhosts = 1 + DistributedToon.DistributedToon.setGhostMode(self, flag) + + def newCatalogNotify(self): + #print("start newCatalogNotify") + if not self.gotCatalogNotify: + # No undelivered catalog notify message. + return + + hasPhase = not launcher or launcher.getPhaseComplete(5.5) + if not hasPhase: + # We have to wait; the phase isn't ready yet. + return + + if not self.friendsListButtonActive or \ + self.friendsListButtonObscured > 0: + # The friends list button is obscured or otherwise + # unavailable, so that general region of the screen isn't + # available yet. + return + + # Ok, we're processing the new notify message now. + self.gotCatalogNotify = 0 + + currentWeek = self.catalogScheduleCurrentWeek - 1 + if currentWeek < 57: + seriesNumber = currentWeek / ToontownGlobals.CatalogNumWeeksPerSeries + 1 + weekNumber = currentWeek % ToontownGlobals.CatalogNumWeeksPerSeries + 1 + # Catalog Series 5 & 6 are short. Need some special math here. + elif currentWeek < 65: + seriesNumber = 6 + weekNumber = (currentWeek - 56) + # All catalogs after 5 & 6 now need to get bumped up by + # one since the last 13 weeks used two series numbers. + else: + seriesNumber = currentWeek / ToontownGlobals.CatalogNumWeeksPerSeries + 2 + weekNumber = currentWeek % ToontownGlobals.CatalogNumWeeksPerSeries + 1 + + message = None + if self.mailboxNotify == ToontownGlobals.NoItems: + # Nothing in the mailbox. + if self.catalogNotify == ToontownGlobals.NewItems: + # New catalog! + if self.catalogScheduleCurrentWeek == 1: + message = (TTLocalizer.CatalogNotifyFirstCatalog, + TTLocalizer.CatalogNotifyInstructions) + else: + message = (TTLocalizer.CatalogNotifyNewCatalog % (weekNumber),) + + elif self.mailboxNotify == ToontownGlobals.NewItems: + # A new delivery in the mailbox. + if self.catalogNotify == ToontownGlobals.NewItems: + # A new delivery, *and* a new catalog! + message = (TTLocalizer.CatalogNotifyNewCatalogNewDelivery % (weekNumber),) + else: + # Just a new delivery. + message = (TTLocalizer.CatalogNotifyNewDelivery,) + + elif self.mailboxNotify == ToontownGlobals.OldItems: + # Old, unclaimed items in the mailbox. + if self.catalogNotify == ToontownGlobals.NewItems: + # And a new catalog! + message = (TTLocalizer.CatalogNotifyNewCatalogOldDelivery % (weekNumber),) + else: + # Just a new delivery. + message = (TTLocalizer.CatalogNotifyOldDelivery,) + + if self.awardNotify == ToontownGlobals.NoItems: + # nothing in award mailbox + pass + elif self.awardNotify == ToontownGlobals.NewItems: + # A new award delivery in the mailbox + oldStr ='' + if message: + oldStr = message[0] + ' ' + oldStr += TTLocalizer.AwardNotifyNewItems + message = (oldStr,) + elif self.awardNotify == ToontownGlobals.OldItems: + oldStr ='' + if message: + oldStr = message[0] + ' ' + oldStr += TTLocalizer.AwardNotifyOldItems + message = (oldStr,) + + if ((self.simpleMailNotify == ToontownGlobals.NewItems) or (self.inviteMailNotify == ToontownGlobals.NewItems)): + oldStr = '' + if message: + oldStr = message[0] + ' ' + oldStr += TTLocalizer.MailNotifyNewItems + message = (oldStr,) + + if message == None: + # Nothing to report. + return + + # Create a dialog to tell the user what's up. + if self.__catalogNotifyDialog: + self.__catalogNotifyDialog.cleanup() + self.__catalogNotifyDialog = CatalogNotifyDialog.CatalogNotifyDialog(message) + base.playSfx(self.soundPhoneRing) + #print("end newCatalogNotify") + + def allowHardLand(self): + retval = LocalAvatar.LocalAvatar.allowHardLand(self) + return (retval and (not self.isDisguised)) + + def setShovelGuiLevel(self, level = 0): + return + + def setWateringCanGuiLevel(self, level = 0): + return + + def loadGardeningGui(self): + # Make sure we are not already loaded + if self.__gardeningGui: + return + # Related to the friends list button (at least, adjacent to + # it) is the move-furniture button, which is only revealed + # when the friends list button is revealed and we have a + # global DistributedFurnitureManager available. + # This consists of an attic frame + + gardenGuiCard = loader.loadModel("phase_5.5/models/gui/planting_gui") + + self.__gardeningGui = DirectFrame( + relief = None, + geom = gardenGuiCard, + geom_color = GlobalDialogColor, + #geom_scale = (12, 1, 3), + geom_scale=(0.17,1.0,0.3), + #pos = (0, 0, 0.8), + pos = (-1.2, 0, 0.50), + #scale = 0.1, + scale = 1.0, + + ) + self.__gardeningGui.setName('gardeningFrame') + + + self.__gardeningGuiFake = DirectFrame( + relief = None, + geom = None, + geom_color = GlobalDialogColor, + #geom_scale = (12, 1, 3), + geom_scale=(0.17,1.0,0.3), + #pos = (0, 0, 0.8), + pos = (-1.2, 0, 0.50), + #scale = 0.1, + scale = 1.0, + + ) + self.__gardeningGuiFake.setName('gardeningFrameFake') + + iconScale = 1 + iconColorWhite = Vec4(1.0,1.0,1.0,1.0) + iconColorGrey = Vec4(0.7,0.7,0.7,1.0) + iconColorBrown = Vec4(0.7,0.4,0.3,1.0) + iconColorBlue = Vec4(0.2,0.3,1.0,1.0) + + shovelCardP = loader.loadModel("phase_5.5/models/gui/planting_but_shovel_P") + shovelCardY = loader.loadModel("phase_5.5/models/gui/planting_but_shovel_Y") + + wateringCanCardP = loader.loadModel("phase_5.5/models/gui/planting_but_can_P") + wateringCanCardY = loader.loadModel("phase_5.5/models/gui/planting_but_can_Y") + + backCard = loader.loadModel("phase_5.5/models/gui/planting_gui") + + iconImage = None + iconModels = loader.loadModel( + "phase_3.5/models/gui/sos_textures") + iconGeom = iconModels.find('**/fish') + + buttonText = TTLocalizer.GardeningPlant + + + self.shovelText = ("","",buttonText,"") + self.__shovelButtonFake = DirectLabel( + parent = self.__gardeningGuiFake, + relief = None, + #frameSize = (-0.575, 0.575, -0.575, 0.575), + #borderWidth = (0.05,0.05), + text = self.shovelText, + text_align = TextNode.ALeft, + text_pos = (0.0,-0.0), + text_scale = 0.07, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + #image = (shovelCardP, shovelCardY, shovelCardY, shovelCardY), + image_scale = (0.18, 1.0, 0.36), + geom = None, + geom_scale = iconScale, + geom_color = iconColorWhite, + pos = (0.15, 0, 0.20), + scale = 0.775, + #command = None + ) + self.shovelButtonFake = self.__shovelButtonFake + + + + self.shovelText = ("","",buttonText,"") + self.__shovelButton = DirectButton( + parent = self.__gardeningGui, + relief = None, + #frameSize = (-0.575, 0.575, -0.575, 0.575), + #borderWidth = (0.05,0.05), + text = self.shovelText, + text_align = TextNode.ACenter, + text_pos = (0.0,-0.0), + text_scale = 0.1, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + image = (shovelCardP, shovelCardY, shovelCardY, shovelCardY), + image_scale = (0.18, 1.0, 0.36), + geom = None, + geom_scale = iconScale, + geom_color = iconColorWhite, + pos = (0, 0, 0.20), + scale = 0.775, + command = self.__shovelButtonClicked) + self.shovelButton = self.__shovelButton + + iconGeom = iconModels.find('**/teleportIcon') + + buttonText = TTLocalizer.GardeningWater + self.waterText = (buttonText,buttonText,buttonText,"") + + self.__wateringCanButtonFake = DirectLabel( + parent = self.__gardeningGuiFake, + relief = None, + #frameSize = (-0.575, 0.575, -0.575, 0.575), + #borderWidth = (0.05,0.05), + text = self.waterText, + text_align = TextNode.ALeft, + text_pos = (0.0,-0.0), + text_scale = 0.07, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + #image = (wateringCanCardP, wateringCanCardY, wateringCanCardY, wateringCanCardY), + image_scale = (0.18, 1.0, 0.36), + geom = None, + geom_scale = iconScale, + geom_color = iconColorWhite, + pos = (0.15, 0, 0.01), + scale = 0.775, + #command = None + ) + self.wateringCanButtonFake = self.__wateringCanButtonFake + + + self.__wateringCanButton = DirectButton( + parent = self.__gardeningGui, + relief = None, + #frameSize = (-0.575, 0.575, -0.575, 0.575), + #borderWidth = (0.05,0.05), + text = self.waterText, + text_align = TextNode.ACenter, + text_pos = (0.0,-0.0), + text_scale = 0.1, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + image = (wateringCanCardP, wateringCanCardY, wateringCanCardY, wateringCanCardY), + image_scale = (0.18, 1.0, 0.36), + geom = None, + geom_scale = iconScale, + geom_color = iconColorWhite, + pos = (0, 0, 0.01), + scale = 0.775, + command = self.__wateringCanButtonClicked + ) + self.wateringCanButton = self.__wateringCanButton + + + + self.basketText = ("%s / %s" % (self.numFlowers, self.maxFlowerBasket)) + + self.basketButton = DirectLabel( + parent = self.__gardeningGui, + relief = None, + #frameSize = (-0.575, 0.575, -0.575, 0.575), + #borderWidth = (0.05,0.05), + text = (self.basketText), + text_align = TextNode.ALeft, + text_pos = (0.82,-1.4), + text_scale = 0.2, + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + image = None, + image_scale = iconScale, + geom = None, + geom_scale = iconScale, + geom_color = iconColorWhite, + pos = (-0.34, 0, 0.16), + scale = 0.3, + textMayChange = 1, + ) + + + if hasattr(self, 'shovel'): + self.setShovelGuiLevel(self.shovel) + if hasattr(self, 'wateringCan'): + self.setWateringCanGuiLevel(self.wateringCan) + + self.__shovelButton.hide() + self.__wateringCanButton.hide() + self.__shovelButtonFake.hide() + self.__wateringCanButtonFake.hide() + + + def changeButtonText(self, button, text): + button["text"] = text + #self.basketButton["text"] = self.basketText + + def resetWaterText(self): + self.wateringCanButton["text"] = self.waterText + + def resetShovelText(self): + self.shovelButton["text"] = self.holdShovelText + + def showGardeningGui(self): + # Presumably whoever is calling this is in the correct download phase + #import pdb; pdb.set_trace() + self.loadGardeningGui() + self.__gardeningGui.show() + base.setCellsAvailable([base.leftCells[2]], 0) + + def hideGardeningGui(self): + if self.__gardeningGui: + self.__gardeningGui.hide() + base.setCellsAvailable([base.leftCells[2]], 1) + + + + def showShovelButton(self, add = 0): + """ + also make sure our parent is shown, otherwise we won't show + """ + if add: + self.shovelButtonActiveCount += add + else: + self.showingShovel = 1 + + self.notify.debug("showing shovel %s" % (self.shovelButtonActiveCount)) + self.__gardeningGui.show() + self.__shovelButton.show() + + + def hideShovelButton(self, deduct = 0): + self.shovelButtonActiveCount -= deduct + if deduct == 0: + self.showingShovel = 0 + if self.shovelButtonActiveCount < 1: + self.shovelButtonActiveCount = 0 + if self.showingShovel == 0: + self.__shovelButton.hide() + self.handleAllGardeningButtonsHidden() + self.notify.debug("hiding shovel %s" % (self.shovelButtonActiveCount)) + + # the watering buttons needs to be merged with Pappy's code, potential conflict here + def showWateringCanButton(self, add = 0): + """ + also make sure our parent is shown, otherwise we won't show + """ + if add: + self.wateringCanButtonActiveCount += add + else: + self.showingWateringCan = 1 + self.__gardeningGui.show() + self.__wateringCanButton.show() + self.basketButton.show() + + def hideWateringCanButton(self, deduct = 0): + #print("watering active count %s" % (self.wateringCanButtonActiveCount)) + self.wateringCanButtonActiveCount -= deduct + if deduct == 0: + self.showingWateringCan = 0 + if self.wateringCanButtonActiveCount < 1: + wateringCanButtonActiveCount = 0 + if self.showingWateringCan == 0: + self.__wateringCanButton.hide() + self.handleAllGardeningButtonsHidden() + + def showWateringCanButtonFake(self, add = 0): + self.__wateringCanButtonFake.show() + + def hideWateringCanButtonFake(self, deduct = 0): + self.__wateringCanButtonFake.hide() + + def showShovelButtonFake(self, add = 0): + self.__shovelButtonFake.show() + + def hideShovelButtonFake(self, deduct = 0): + self.__shovelButtonFake.hide() + + def levelWater(self, change = 1): + #print("level WAter") + if change < 0: + return + self.showWateringCanButtonFake(1) + #TODO fix +1 sticking around when another toon waters your plant + #oldHideState = self.__wateringCanButton.isHidden() + #self.wateringCanButtonActiveCount += 1 + if change < 1: + changeString = TTLocalizer.GardeningNoSkill + else: + changeString = ("+%s %s" % (change, TTLocalizer.GardeningWaterSkill)) + #self.holdWateringCanText = TTLocalizer.GardeningWater#self.wateringCanButton["text"] + self.waterTrack = Sequence( + Wait(0.0), + Func(self.changeButtonText, self.wateringCanButtonFake, changeString), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self), + Wait(1.0), + #Func(self.changeButtonText, self.wateringCanButtonFake, self.holdWateringCanText), + Func(self.hideWateringCanButtonFake, 1), + ) + #if oldHideState: + # pass + self.waterTrack.start() + + def levelShovel(self, change = 1): + #print("level SHovel") + if change < 1: + return + self.showShovelButtonFake(1) + #oldHideState = self.__shovelButton.isHidden() + #self.shovelButtonActiveCount += 1 + if change < 1: + changeString = TTLocalizer.GardeningNoSkill + else: + changeString = ("+%s %s" % (change, TTLocalizer.GardeningShovelSkill)) + #self.holdShovelText = self.shovelButton["text"] + plant = base.cr.doId2do.get(self.shovelRelatedDoId) + if plant: + self.holdShovelText = plant.getShovelAction() + + #levelSound = globalBattleSoundCache.getSound('GUI_ballon_popup.mp3') + #self.shovelTrack = Sequence() + + #self.shovelTrack.append(Wait(0.0)) + #self.shovelTrack.append(Func(self.changeButtonText, self.shovelButtonFake, changeString)) + #if levelSound: + # self.shovelTrack.append(SoundInterval(levelSound, node=self)) + #self.shovelTrack.append(Wait(1.0)) + #self.shovelTrack.append(Func(self.hideShovelButtonFake, 1)) + + self.shovelTrack = Sequence( + Wait(0.0), + Func(self.changeButtonText, self.shovelButtonFake, changeString), + SoundInterval(globalBattleSoundCache.getSound('GUI_balloon_popup.mp3'), node=self), + Wait(1.0), + #Func(self.changeButtonText, self.shovelButtonFake, self.holdShovelText), + Func(self.hideShovelButtonFake, 1), + ) + + #if oldHideState: + # pass + self.shovelTrack.start() + + def setGuiConflict(self, con): + self.guiConflict = con + #print("setting Gui conflict %s" % (con)) + + def getGuiConflict(self, con): + return self.guiConflict + + def verboseState(self): + self.lastPlaceState = "None" + taskMgr.add(self.__expressState, "expressState", extraArgs = []) + + def __expressState(self, task = None): + place = base.cr.playGame.getPlace() + if place: + state = place.fsm.getCurrentState() + if state.getName() != self.lastPlaceState: + #PRINT is okay in this case because this is magic word thing + print("Place State Change From %s to %s" % (self.lastPlaceState, state.getName())) + self.lastPlaceState = state.getName()#[:] + return Task.cont + + + def addShovelRelatedDoId(self, doId): + """ + because we can get the enter and exits of the garden plots + in any order, we use the add and remove metaphors + """ + if hasattr(base.cr.playGame.getPlace(), 'detectedGardenPlotDone'): + place = base.cr.playGame.getPlace() + state = place.fsm.getCurrentState() + #print("estate state %s" % (state.getName())) + if state.getName() == 'stopped': + return + + self.touchingPlantList.append(doId) + self.autoSetActivePlot() + + def removeShovelRelatedDoId(self,doId): + #the if check is important, since we may have gotten the enter before the exit + if doId in self.touchingPlantList: + self.touchingPlantList.remove(doId) + self.autoSetActivePlot() + + def autoSetActivePlot(self): + if self.guiConflict: + return + if len(self.touchingPlantList) > 0: + minDist = 10000 + minDistPlot = 0 + for plot in self.touchingPlantList: + plant = base.cr.doId2do.get(plot) + if plant: + if self.getDistance(plant) < minDist: + minDist = self.getDistance(plant) + minDistPlot = plot + else: + self.touchingPlantList.remove(plot) + if len(self.touchingPlantList) == 0: + self.setActivePlot(None) + else: + self.setActivePlot(minDistPlot) + else: + self.setActivePlot(None) + + def setActivePlot(self, doId): + #print("setActivePlot %s" % (doId)) + if not self.gardenStarted: + return + self.shovelRelatedDoId = doId + plant = base.cr.doId2do.get(doId) + if plant: + self.startStareAt(plant, Point3(0,0,1)) + self.__shovelButton['state'] = DGG.NORMAL + if not plant.canBePicked(): + self.hideShovelButton() + else: + self.showShovelButton() + self.setShovelAbility(TTLocalizer.GardeningPlant) + if plant.getShovelAction(): + self.setShovelAbility(plant.getShovelAction()) + if plant.getShovelAction() == TTLocalizer.GardeningPick: + #print("action is picking") + if not plant.unlockPick(): + #print(plant.unlockPick()) + self.__shovelButton['state'] = DGG.DISABLED + self.setShovelAbility(TTLocalizer.GardeningFull) + else: + #print("action not is picking") + pass + self.notify.debug("self.shovelRelatedDoId = %d" % self.shovelRelatedDoId) + if plant.getShovelCommand(): + self.extraShovelCommand = plant.getShovelCommand() + self.__shovelButton['command'] = self.__shovelButtonClicked + if plant.canBeWatered(): + self.showWateringCanButton() + else: + self.hideWateringCanButton() + else: + #print("hiding GUI") + self.stopStareAt() + self.shovelRelatedDoId = 0 + if self.__shovelButton: + self.__shovelButton['command'] = None + self.hideShovelButton() + self.hideWateringCanButton() + self.handleAllGardeningButtonsHidden() + if not self.inGardenAction: + if hasattr(base.cr.playGame.getPlace(), 'detectedGardenPlotDone'): + place = base.cr.playGame.getPlace() + if place: + place.detectedGardenPlotDone() + + def setPlantToWater(self, plantId): + import pdb; pdb.set_trace() + if self.plantToWater == None: + self.plantToWater = plantId + self.notify.debug("setting plant to water %s" % (plantId)) + + def clearPlantToWater(self, plantId): + #import pdb; pdb.set_trace() + if not hasattr(self, "secondaryPlant"): + self.secondaryWaterPlant = None + + if self.plantToWater == plantId: + self.plantToWater = None + self.hideWateringCanButton() + + def hasPlant(self): + if self.plantToWater != None: + return 1 + else: + return 0 + + def handleAllGardeningButtonsHidden(self): + """ + if all the buttons on the gardening gui are hidden, hide it too + """ + somethingVisible = False + if not self.__shovelButton.isHidden(): + somethingVisible = True + + if not self.__wateringCanButton.isHidden(): + somethingVisible = True + + if not somethingVisible: + self.hideGardeningGui() + + def setShovelAbility(self, ability): + self.shovelAbility = ability + if self.__shovelButton: + self.__shovelButton['text'] = ability + + def setFlowerBasket(self, speciesList, varietyList): + DistributedToon.DistributedToon.setFlowerBasket(self, speciesList, varietyList) + self.numFlowers = len(self.flowerBasket.flowerList) + self.maxFlowerBasket + if hasattr(self, "basketButton"): + self.basketText = ("%s / %s" % (self.numFlowers, self.maxFlowerBasket)) + self.basketButton["text"] = self.basketText + + def setShovelSkill(self, skillLevel): + if hasattr(self, 'shovelSkill') and hasattr(self, 'shovelButton'): + if self.shovelSkill != None: + self.levelShovel(skillLevel - self.shovelSkill) + oldShovelSkill = self.shovelSkill + DistributedToon.DistributedToon.setShovelSkill(self, skillLevel) + if hasattr(self, 'shovel'): + oldShovelPower = GardenGlobals.getShovelPower(self.shovel, oldShovelSkill) + newShovelPower = GardenGlobals.getShovelPower(self.shovel, self.shovelSkill) + almostMaxedSkill = GardenGlobals.ShovelAttributes[GardenGlobals.MAX_SHOVELS-1]['skillPts'] - 2 + if skillLevel >= GardenGlobals.ShovelAttributes[self.shovel]['skillPts']: + self.promoteShovel() + elif oldShovelSkill and (oldShovelPower < newShovelPower): + #he reached a new number of slots + self.promoteShovelSkill(self.shovel, self.shovelSkill) + elif oldShovelSkill == almostMaxedSkill and \ + newShovelPower == GardenGlobals.getNumberOfShovelBoxes(): + #he maxed gardening skill for the first time + self.promoteShovelSkill(self.shovel, self.shovelSkill) + + def setWateringCanSkill(self, skillLevel): + #if hasattr(base.cr.playGame.getPlace(), 'detectedGardenPlotDone'): + # place = base.cr.playGame.getPlace() + # if place: + # place.detectedGardenPlotDone() + skillDelta = skillLevel - self.wateringCanSkill + if skillDelta or 1: + if hasattr(self, 'wateringCanSkill') and hasattr(self, 'wateringCanButton'): + if self.wateringCanSkill != None: + self.levelWater(skillDelta) + DistributedToon.DistributedToon.setWateringCanSkill(self, skillLevel) + if hasattr(self, 'wateringCan'): + if skillLevel >= GardenGlobals.WateringCanAttributes[self.wateringCan]['skillPts']: + self.promoteWateringCan() + # done watering, turn the button back on + #self.reactivateWater() + + def unlockGardeningButtons(self, task = None): + #print("unlockingGardenButton") + if hasattr(self, "_LocalToon__shovelButton"): + try: + self.__shovelButton['state'] = DGG.NORMAL + except TypeError: + self.notify.warning("Could not unlock the shovel button- Type Error") + if hasattr(self, "_LocalToon__wateringCanButton"): + try: + self.__wateringCanButton['state'] = DGG.NORMAL + except TypeError: + self.notify.warning("Could not unlock the watering can button - Type Error") + taskMgr.remove("unlockGardenButtons") + return None + + def lockGardeningButtons(self, task = None): + #print("unlockingGardenButton") + if hasattr(self, "_LocalToon__shovelButton"): + try: + self.__shovelButton['state'] = DGG.DISABLED + except TypeError: + self.notify.warning("Could not lock the shovel button- Type Error") + if hasattr(self, "_LocalToon__wateringCanButton"): + try: + self.__wateringCanButton['state'] = DGG.DISABLED + except TypeError: + self.notify.warning("Could not lock the watering can button - Type Error") + + self.accept("endPlantInteraction", self.__handleEndPlantInteraction) + #taskMgr.doMethodLater(15, self.__handleEndPlantInteraction, "unlockGardenButtons") + + return None + + def reactivateShovel(self, task = None): + #print("reactivatingShovel") + if hasattr(self, "_LocalToon__shovelButton"): + self.__shovelButton['state'] = DGG.NORMAL + else: + pass + #import pdb; pdb.set_trace() + taskMgr.remove("reactShovel") + return None + + def reactivateWater(self, task = None): + #print("reactivatingWater") + if hasattr(self, "_LocalToon__wateringCanButton"): + self.__wateringCanButton['state'] = DGG.NORMAL + else: + pass + #import pdb; pdb.set_trace() + taskMgr.remove("reactWater") + return None + + def handleEndPlantInteraction(self, object = None, replacement = 0): + #print "### LocalToon: handleEndPlantInteraction -> reactivateWater" + #self.unlockGardeningButtons() + if not replacement: + self.setInGardenAction(None, object) + self.autoSetActivePlot() + return None + + def __handleEndPlantInteraction(self, task = None): + #print "### LocalToon: handleEndPlantInteraction -> reactivateWater" + #self.unlockGardeningButtons() + self.setInGardenAction(None) + self.autoSetActivePlot() + return None + + def promoteShovelSkill(self, shovelLevel, shovelSkill): + shovelName = GardenGlobals.ShovelAttributes[shovelLevel]['name'] + shovelBeans = GardenGlobals.getShovelPower(shovelLevel, shovelSkill) + oldShovelBeans = GardenGlobals.getShovelPower(shovelLevel, shovelSkill - 1) + doPartyBall = False + message = TTLocalizer.GardenShovelSkillLevelUp % {"shovel":shovelName, + "oldbeans":oldShovelBeans, + "newbeans":shovelBeans} + if shovelBeans == GardenGlobals.getNumberOfShovelBoxes(): + if shovelSkill == GardenGlobals.ShovelAttributes[shovelLevel]['skillPts'] - 1: + doPartyBall = True + message = TTLocalizer.GardenShovelSkillMaxed % {"shovel":shovelName, + "oldbeans":oldShovelBeans, + "newbeans":shovelBeans} + messagePos = Vec2(0,0.2) + messageScale = 0.07 + image = loader.loadModel("phase_5.5/models/gui/planting_but_shovel_P") + imagePos = Vec3(0,0,-0.13) + imageScale = Vec3(0.28,0,0.56) + + if doPartyBall: + go = Fanfare.makeFanfareWithMessageImage(0, base.localAvatar, 1, message, + Vec2(0,0.2), 0.08, + image, Vec3(0,0,-0.1), Vec3(0.35,0,0.7), + wordwrap =23) + Sequence(go[0], + Func(go[1].show), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,0), + colorScale=Vec4(1,1,1,1)), + Wait(10), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,1), + colorScale=Vec4(1,1,1,0)), + Func(go[1].remove)).start() + else: + go = Fanfare.makePanel(base.localAvatar, 1) + Fanfare.makeMessageBox(go, message, messagePos, messageScale, wordwrap = 24) + Fanfare.makeImageBox(go.itemFrame, image, imagePos, imageScale) + + Sequence(Func(go.show), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,0),colorScale=Vec4(1,1,1,1)),Wait(10), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,1),colorScale=Vec4(1,1,1,0)), + Func(go.remove)).start() + + + def promoteShovel(self, shovelLevel = 0): + #GardenProgressMeter.GardenProgressMeter("shovel", shovelLevel) + shovelName = GardenGlobals.ShovelAttributes[shovelLevel]['name'] + shovelBeans = GardenGlobals.getShovelPower(shovelLevel, 0) + message = TTLocalizer.GardenShovelLevelUp % {"shovel":shovelName, + "oldbeans":shovelBeans - 1, + "newbeans":shovelBeans} + + messagePos = Vec2(0,0.2) + messageScale = 0.07 + image = loader.loadModel("phase_5.5/models/gui/planting_but_shovel_P") + imagePos = Vec3(0,0,-0.13) + imageScale = Vec3(0.28,0,0.56) + + if 0: #shovelLevel >= (GardenGlobals.MAX_SHOVELS - 1): + go = Fanfare.makeFanfareWithMessageImage(0, base.localAvatar, 1, message, Vec2(0,0.2), 0.08, image, Vec3(0,0,-0.1), Vec3(0.35,0,0.7)) + Sequence(go[0],Func(go[1].show), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,0),colorScale=Vec4(1,1,1,1)),Wait(5), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,1),colorScale=Vec4(1,1,1,0)), + Func(go[1].remove)).start() + else: + go = Fanfare.makePanel(base.localAvatar, 1) + Fanfare.makeMessageBox(go, message, messagePos, messageScale, wordwrap = 24) + Fanfare.makeImageBox(go.itemFrame, image, imagePos, imageScale) + + Sequence(Func(go.show), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,0),colorScale=Vec4(1,1,1,1)),Wait(10), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,1),colorScale=Vec4(1,1,1,0)), + Func(go.remove)).start() + + def promoteWateringCan(self, wateringCanlevel = 0): + #GardenProgressMeter.GardenProgressMeter("wateringCan", wateringCanlevel) + message = TTLocalizer.GardenWateringCanLevelUp + " \n" + GardenGlobals.WateringCanAttributes[wateringCanlevel]['name'] + messagePos = Vec2(0,0.2) + messageScale = 0.08 + image = loader.loadModel("phase_5.5/models/gui/planting_but_can_P") + imagePos = Vec3(0,0,-0.1) + imageScale = Vec3(0.35,0,0.7) + + if wateringCanlevel >= (GardenGlobals.MAX_WATERING_CANS - 1): + go = Fanfare.makeFanfareWithMessageImage(0, base.localAvatar, 1, message, Vec2(0,0.2), 0.08, image, Vec3(0,0,-0.1), Vec3(0.35,0,0.7)) + Sequence(go[0],Func(go[1].show), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,0),colorScale=Vec4(1,1,1,1)),Wait(5), + LerpColorScaleInterval(go[1],duration=0.5,startColorScale=Vec4(1,1,1,1),colorScale=Vec4(1,1,1,0)), + Func(go[1].remove)).start() + else: + go = Fanfare.makePanel(base.localAvatar, 1) + Fanfare.makeMessageBox(go, message, messagePos, messageScale) + Fanfare.makeImageBox(go.itemFrame, image, imagePos, imageScale) + + Sequence(Func(go.show), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,0),colorScale=Vec4(1,1,1,1)),Wait(5), + LerpColorScaleInterval(go,duration=0.5,startColorScale=Vec4(1,1,1,1),colorScale=Vec4(1,1,1,0)), + Func(go.remove)).start() + + def setInGardenAction(self, actionObject, fromObject = None): + if actionObject: + #print("setting In Garden Action on %s" % (actionObject)) + self.lockGardeningButtons() + elif fromObject: + #print("setting Out of Garden Action from %s" % (fromObject)) + self.unlockGardeningButtons() + else: + #print("setting Out of Garden Action from None") + self.unlockGardeningButtons() + self.inGardenAction = actionObject + + def __wateringCanButtonClicked(self): + self.notify.debug ("wateringCanButtonClicked") + if self.inGardenAction: + return + + assert self.notify.debugStateCall(self) + # We need the DistributedPlantBase or LocalToon to send the + # water plant message to the AI, but not both. + # Decided to make the plant send it, in case we have more stuff + # to do client side + + # lock the toon down now + plant = base.cr.doId2do.get(self.shovelRelatedDoId) + if plant: + if hasattr(plant, "handleWatering"): + plant.handleWatering() + + # if we're clicking on buttons, we're not asleep + messenger.send('wakeup') + + def __shovelButtonClicked(self): + if self.inGardenAction: + return + self.notify.debug("shovelButtonClicked") + assert self.notify.debugStateCall(self) + #if we're clicking on buttons, we're not asleep + messenger.send('wakeup') + + thing = base.cr.doId2do.get(self.shovelRelatedDoId) + + if hasattr(self,"extraShovelCommand"): + self.extraShovelCommand() + #self.setInGardenAction(1, thing) + #self.lockGardeningButtons() + + def setShovel(self, shovelId): + DistributedToon.DistributedToon.setShovel(self, shovelId) + if self.__gardeningGui: + self.setShovelGuiLevel(shovelId) + + def setWateringCan(self, wateringCanId): + DistributedToon.DistributedToon.setWateringCan(self, wateringCanId) + if self.__gardeningGui: + self.setWateringCanGuiLevel(wateringCanId) + + def setGardenStarted(self, bStarted): + self.gardenStarted = bStarted + if self.gardenStarted and (not self.gardenPage) and hasattr(self, "book"): + self.loadGardenPages() + + def b_setAnimState(self, animName, animMultiplier=1.0, callback = None, extraArgs=[]): + if self.wantStatePrint: + print("Local Toon Anim State %s" % (animName)) + DistributedToon.DistributedToon.b_setAnimState(self, animName, animMultiplier, callback, extraArgs) + + def swimTimeoutAction(self): + assert self == base.localAvatar + self.ignore('wakeup') + # This will check to see if we're actually wearing a suit before + # doing anything + self.takeOffSuit() + base.cr.playGame.getPlace().fsm.request('final') + self.b_setAnimState('TeleportOut', 1, self.__handleSwimExitTeleport, [0]) + return Task.done + + + def __handleSwimExitTeleport(self, requestStatus): + self.notify.info('closing shard...') + base.cr.gameFSM.request('closeShard', ['afkTimeout']) + + def sbFriendAdd(self, id, info): + print "sbFriendAdd" + + def sbFriendUpdate(self, id, info): + print "sbFriendUpdate" + + def sbFriendRemove(self, id): + print "sbFriendRemove" + + def addGolfPage( self ): + """ + Purpose: + + Params: None + Return: None + """ + # Only show the button if the toon has played golf. + if self.hasPlayedGolf(): + + if hasattr(self, "golfPage") and self.golfPage != None: + # The page is already loaded; never mind. + return + + if not launcher.getPhaseComplete(6): + # We haven't downloaded phase 6 yet; set a callback hook + # so the pages will load when we do get phase 6. + self.acceptOnce('phaseComplete-6', self.addGolfPage) + return + + self.golfPage = GolfPage.GolfPage() + self.golfPage.setAvatar( self ) + self.golfPage.load() + self.book.addPage( self.golfPage, + pageName = TTLocalizer.GolfPageTitle ) + + def addEventsPage( self ): + if hasattr(self, "eventsPage") and self.eventsPage != None: + # The page is already loaded; never mind. + return + if not launcher.getPhaseComplete(4): + # We haven't downloaded phase 4 yet; set a callback hook + # so the pages will load when we do get phase 4. + self.acceptOnce('phaseComplete-4', self.addEventsPage) + return + self.eventsPage = EventsPage.EventsPage() + self.eventsPage.load() + self.book.addPage(self.eventsPage, pageName = TTLocalizer.EventsPageName) + + def addNewsPage(self): + self.newsPage = NewsPage.NewsPage() + self.newsPage.load() + self.book.addPage(self.newsPage, pageName = TTLocalizer.NewsPageName) + + def addTIPPage(self): + self.tipPage = TIPPage.TIPPage() + self.tipPage.load() + self.book.addPage(self.tipPage, pageName = TTLocalizer.TIPPageTitle) + + def setPinkSlips(self, pinkSlips): + """Set the number of pink slips.""" + DistributedToon.DistributedToon.setPinkSlips(self, pinkSlips) + self.inventory.updateTotalPropsText() + + def getAccountDays(self): + """Return the number of days since the owning account has been created. + + Note the returned value is a float. + """ + days = 0 + defaultDays = base.cr.config.GetInt('account-days', -1) + if defaultDays >= 0: + days = defaultDays + elif hasattr(base.cr, 'accountDays'): + days = base.cr.accountDays + return days + + def hasActiveBoardingGroup(self): + if hasattr(localAvatar, "boardingParty") and localAvatar.boardingParty: + return localAvatar.boardingParty.hasActiveGroup(localAvatar.doId) + else: + return False + + def getZoneId(self): + return self._zoneId + + def setZoneId(self, value): + if value == -1: + # hopefully we get a stack trace with this + self.notify.error("zoneId should not be set to -1, tell Redmond") + self._zoneId = value + + zoneId = property(getZoneId, setZoneId) + + def systemWarning(self, warningText = "Acknowledge this system message."): + """We got a system message that we must hit ok on.""" + self.createSystemMsgAckGui() + self.systemMsgAckGui["text"] = warningText + self.systemMsgAckGui.show() + + def createSystemMsgAckGui(self): + """Safely create the system message ack gui.""" + if self.systemMsgAckGui == None or self.systemMsgAckGui.isEmpty(): + message = "o" * 100 + self.systemMsgAckGui = TTDialog.TTGlobalDialog( + doneEvent = self.systemMsgAckGuiDoneEvent, + message = message, + style = TTDialog.Acknowledge, + ) + self.systemMsgAckGui.hide() + + def hideSystemMsgAckGui(self): + """Safely hide the system ack gui.""" + if self.systemMsgAckGui != None and not self.systemMsgAckGui.isEmpty(): + self.systemMsgAckGui.hide() + + def setSleepAutoReply(self, fromId): + av = base.cr.identifyAvatar(fromId) + if isinstance(av, DistributedToon.DistributedToon): + base.localAvatar.setSystemMessage(0, TTLocalizer.sleep_auto_reply %av.getName(), WhisperPopup.WTToontownBoardingGroup) + elif av is not None: + self.notify.warning('setSleepAutoReply from non-toon %s' % fromId) + + + def setLastTimeReadNews(self, newTime): + self.lastTimeReadNews = newTime + + def getLastTimeReadNews(self): + return self.lastTimeReadNews + + def isReadingNews(self): + """Returns true if the toon is reading the news.""" + result = False + if base.cr and base.cr.playGame and base.cr.playGame.getPlace() and hasattr(base.cr.playGame.getPlace(), 'fsm') and base.cr.playGame.getPlace().fsm: + fsm = base.cr.playGame.getPlace().fsm + curState = fsm.getCurrentState().getName() + if curState == 'stickerBook' and WantNewsPage : + if hasattr(self, 'newsPage'): + if self.book.isOnPage(self.newsPage): + result = True + return result diff --git a/toontown/src/toon/Motion.py b/toontown/src/toon/Motion.py new file mode 100644 index 0000000..800b697 --- /dev/null +++ b/toontown/src/toon/Motion.py @@ -0,0 +1,158 @@ + + +from direct.fsm import StateData +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM, State +from direct.fsm import State +import TTEmote +from otp.avatar import Emote + +# Motion class just keeps track of what motion animation the +# Toon is playing + + +class Motion(StateData.StateData): + + notify = DirectNotifyGlobal.directNotify.newCategory("Motion") + + def __init__(self, toon): + self.lt = toon + self.doneEvent = "motionDone" + StateData.StateData.__init__(self, self.doneEvent) + + # The motionFSM keeps track of whether you are playing the + # run/walk/reverse/stand animation. It is seperate from + # the animFSM states, because you can be in the "happy" or + # "sad" animFSM state and also be running/walking/standing, etc. + # SDN: should this be defined here or somewhere else? + self.fsm = ClassicFSM.ClassicFSM('Motion', + [State.State('off', + self.enterOff, + self.exitOff), + State.State('neutral', + self.enterNeutral, + self.exitNeutral), + State.State('walk', + self.enterWalk, + self.exitWalk), + State.State('run', + self.enterRun, + self.exitRun), + State.State('sad-neutral', + self.enterSadNeutral, + self.exitSadNeutral), + State.State('sad-walk', + self.enterSadWalk, + self.exitSadWalk), + State.State('catch-neutral', + self.enterCatchNeutral, + self.exitCatchNeutral), + State.State('catch-run', + self.enterCatchRun, + self.exitCatchRun), + State.State('catch-eatneutral', + self.enterCatchEatNeutral, + self.exitCatchEatNeutral), + State.State('catch-eatnrun', + self.enterCatchEatNRun, + self.exitCatchEatNRun), + ], + # Initial State + 'off', + # Final State + 'off', + ) + + self.fsm.enterInitialState() + + #self.parentFSMState = parentFSMState + #self.parentFSMState.addChild(self.fsm) + + def delete(self): + del self.fsm + + def load(self): + pass + + def unload(self): + pass + + def enter(self): + self.notify.debug('enter') + #self.fsm.enterInitialState() + + def exit(self): + self.fsm.requestFinalState() + + def enterOff(self, rate=0): + self.notify.debug('enterOff') + + def exitOff(self): + pass + + def enterNeutral(self, rate=0): + self.notify.debug('enterNeutral') + + def exitNeutral(self): + self.notify.debug('exitNeutral') + + def enterWalk(self, rate=0): + self.notify.debug('enterWalk') + # disable emotes using the whole body + Emote.globalEmote.disableBody(self.lt, "enterWalk") + + def exitWalk(self): + self.notify.debug('exitWalk') + Emote.globalEmote.releaseBody(self.lt, "exitWalk") + + def enterRun(self, rate=0): + self.notify.debug('enterRun') + # disable emotes using the whole body + Emote.globalEmote.disableBody(self.lt, "enterRun") + + def exitRun(self): + self.notify.debug('exitRun') + Emote.globalEmote.releaseBody(self.lt, "exitRun") + + def enterSadNeutral(self, rate=0): + self.notify.debug('enterSadNeutral') + + def exitSadNeutral(self): + self.notify.debug('exitSadNeutral') + + def enterSadWalk(self, rate=0): + self.notify.debug('enterSadWalk') + + def exitSadWalk(self): + pass + + def enterCatchNeutral(self, rate=0): + self.notify.debug('enterCatchNeutral') + + def exitCatchNeutral(self): + self.notify.debug('exitCatchNeutral') + + def enterCatchRun(self, rate=0): + self.notify.debug('enterCatchRun') + + def exitCatchRun(self): + self.notify.debug('exitCatchRun') + + def enterCatchEatNeutral(self, rate=0): + self.notify.debug('enterCatchEatNeutral') + + def exitCatchEatNeutral(self): + self.notify.debug('exitCatchEatNeutral') + + def enterCatchEatNRun(self, rate=0): + self.notify.debug('enterCatchEatNRun') + + def exitCatchEatNRun(self): + self.notify.debug('exitCatchEatNRun') + + def setState(self, anim, rate): + toon = self.lt + if toon.playingAnim != anim: + self.fsm.request(anim, [rate]) + diff --git a/toontown/src/toon/NPCDialogue.py b/toontown/src/toon/NPCDialogue.py new file mode 100644 index 0000000..2bda087 --- /dev/null +++ b/toontown/src/toon/NPCDialogue.py @@ -0,0 +1,177 @@ +from toontown.toonbase import TTLocalizer +from direct.distributed.ClockDelta import * +from direct.directnotify import DirectNotifyGlobal +import DistributedNPCToonBaseAI +from direct.task import Task +import random + +class NPCDialogue: + """ + The NPC dialogue for a given topic and set of participants + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("NPCDialogue") + + def __init__(self, participant, dialogueTopic): + self.participants = {} + + if dialogueTopic in TTLocalizer.toontownDialogues: + self.topic = dialogueTopic + else: + self.notify.warning("Dialogue does not exist: %s" %dialogueTopic) + self.topic = TTLocalizer.BoringTopic + + self.conversation = TTLocalizer.toontownDialogues[self.topic] + + if participant and isinstance(participant, DistributedNPCToonBaseAI.DistributedNPCToonBaseAI): + self.addParticipant(participant) + self.participantProgress = 0 + self.currentParticipant = participant.npcId + else: + self.notify.warning("Participant does not exist: %s" %participant) + self.participantProgress = 0 + self.currrentParticipant = None + + def calcMaxNumMsgs(self): + """ + Find the participant that has the most number of things to say + """ + self.maxNumMsgs = 0 + for participant, spiel in self.conversation.items(): + if len(spiel)>self.maxNumMsgs and participant[1] in self.participants: + self.maxNumMsgs = len(spiel) + + def getTopic(self): + """ + Accessor function for topic + """ + return self.topic + + def addParticipant(self, participant): + """ + Add a new participant + """ + if self.getNumParticipants() > self.getMaxParticipants(): + return False + if participant: + for partPos in self.conversation.keys(): + if partPos[1] == participant.npcId: + if not (participant.npcId in self.participants): + self.participants[participant.npcId] = [participant] + else: + if participant not in self.participants[participant.npcId]: + self.participants[participant.npcId].append(participant) + else: + self.notify.warning("Participant: %s already in the conversation" %participant) + self.calcMaxNumMsgs() + return True + self.notify.warning("Participant: %s should not be in conversation" %participant) + return False + + def removeParticipant(self, participant): + """ + Remove a participant + """ + if participant.npcId in self.participants: + if participant.npcId == self.currentParticipant: + self.getNextParticipant() + self.participants[participant.npcId].remove(participant) + if self.participants[participant.npcId] == []: + del self.participants[participant.npcId] + self.calcMaxNumMsgs() + return True + return False + + def getNextParticipant(self): + while 1: + self.currentParticipant = self.calcNextParticipant() + if self.currentParticipant in self.participants: + break + + def calcNextParticipant(self): + """ + Returns the next in line to talk + """ + nextParticipant = None + + for partPos in self.conversation.keys(): + if partPos[1] == self.currentParticipant: + nextPos = partPos[0]+1 + break + if nextPos>len(self.conversation): + nextPos = 1 + self.participantProgress = (self.participantProgress+1)%self.maxNumMsgs + for partPos in self.conversation.keys(): + if partPos[0] == nextPos: + nextParticipant = partPos[1] + return nextParticipant + + return self.currentParticipant + + + def getMaxParticipants(self): + """ + Number of conversation pieces provided in TTLocalizer + """ + return len(self.conversation) + + def getNumParticipants(self): + """ + Returns the number of NPC's currently participating + """ + return len(self.participants) + + def isRunning(self): + if taskMgr.hasTaskNamed("Dialogue"+self.topic): + return True + + def start(self): + """ + Start up a dialogue amongst the participants + """ + self.nextChatTime = 0 + + taskMgr.add(self.__blather, "Dialogue"+self.topic) + + return True + + def stop(self): + """ + This conversation is over! + """ + taskMgr.remove("Dialogue"+self.topic) + + def __blather(self, task): + """ + Speak in turn + """ + now = globalClock.getFrameTime() + if now < self.nextChatTime: + return Task.cont + + if not self.currentParticipant: + return Task.done + + # Increment the participantProgress + for partPos in self.conversation.keys(): + if partPos[1] == self.currentParticipant: + convKey = partPos + break + if self.participantProgress >= len(self.conversation[convKey]): + self.getNextParticipant() + return Task.cont + + # Select the current spiel + #msg = self.conversation[self.participants[self.currentParticipant]][self.participantProgress] + + for participant in self.participants[self.currentParticipant]: + chatFlags = CFSpeech | CFTimeout + + participant.sendUpdate("setChat", [self.topic, convKey[0], convKey[1], self.participantProgress, chatFlags]) + + self.getNextParticipant() + + # Delay before next message + self.nextChatTime = now + 5.0 + + return Task.cont \ No newline at end of file diff --git a/toontown/src/toon/NPCDialogueManagerAI.py b/toontown/src/toon/NPCDialogueManagerAI.py new file mode 100644 index 0000000..cb54086 --- /dev/null +++ b/toontown/src/toon/NPCDialogueManagerAI.py @@ -0,0 +1,55 @@ +from toontown.toonbase import TTLocalizer +from direct.directnotify import DirectNotifyGlobal +import NPCDialogue + +class NPCDialogueManagerAI: + """ + Create and distroy dialogues here. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("NPCDialogueManagerAI") + + def __init__(self): + self.dialogues = [] + + def createNewDialogue(self, participant, dialogueTopic): + """ + Create a new dialogue + """ + dialogue = NPCDialogue.NPCDialogue(participant, dialogueTopic) + result = dialogue.start() + if result: + self.dialogues.append(dialogue) + return result + + def requestDialogue(self, participant, dialogueTopic): + """ + Request to be added to the dialogue: dialogueTopic + """ + for dialogue in self.dialogues: + if dialogue.getTopic() == dialogueTopic: + result = dialogue.addParticipant(participant) + if result and not dialogue.isRunning(): + result = dialogue.start() + return result + + result = self.createNewDialogue(participant, dialogueTopic) + return result + + def leaveDialogue(self, participant, dialogueTopic): + """ + Stop participating in this dialogue + """ + result = False + for dialogue in self.dialogues: + if dialogue.getTopic() == dialogueTopic: + result = dialogue.removeParticipant(participant) + + if dialogue.getNumParticipants() == 0: + dialogue.stop() + try: + self.dialogues.remove(dialogue) + except: + self.notify.warning("Couldn't find the dialogue: %s" %dialogue) + + return result \ No newline at end of file diff --git a/toontown/src/toon/NPCForceAcknowledge.py b/toontown/src/toon/NPCForceAcknowledge.py new file mode 100644 index 0000000..f4411d2 --- /dev/null +++ b/toontown/src/toon/NPCForceAcknowledge.py @@ -0,0 +1,64 @@ +from pandac.PandaModules import * +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer +from direct.gui import DirectLabel +from toontown.quest import Quests + +class NPCForceAcknowledge: + + def __init__(self, doneEvent): + self.doneEvent = doneEvent + self.dialog = None + + def enter(self): + doneStatus = {} + questHistory = base.localAvatar.getQuestHistory() + imgScale = 0.5 + if ((questHistory != []) and + (questHistory != [1000]) and + (questHistory != [101, 110])): + doneStatus['mode'] = 'complete' + messenger.send(self.doneEvent, [doneStatus]) + elif ((len(base.localAvatar.quests) > 1) or + (len(base.localAvatar.quests) == 0)): + doneStatus['mode'] = 'complete' + messenger.send(self.doneEvent, [doneStatus]) + elif (base.localAvatar.quests[0][0] != Quests.TROLLEY_QUEST_ID): + doneStatus['mode'] = 'complete' + messenger.send(self.doneEvent, [doneStatus]) + else: + # Make the Toon stand still while the panel is up + base.localAvatar.b_setAnimState('neutral', 1) + doneStatus['mode'] = 'incomplete' + self.doneStatus = doneStatus + imageModel = loader.loadModel("phase_4/models/gui/tfa_images") + if Quests.avatarHasTrolleyQuest(base.localAvatar): + if (base.localAvatar.quests[0][4] != 0): + imgNodePath = imageModel.find("**/hq-dialog-image") + imgPos = (0, 0, -0.02) + msg = TTLocalizer.NPCForceAcknowledgeMessage2 + else: + imgNodePath = imageModel.find("**/trolley-dialog-image") + imgPos = (0, 0, 0.04) + msg = TTLocalizer.NPCForceAcknowledgeMessage + + self.dialog = TTDialog.TTDialog( + text = msg, + command = self.handleOk, + style = TTDialog.Acknowledge) + imgLabel = DirectLabel.DirectLabel( + parent = self.dialog, + relief = None, + pos = imgPos, + scale = TTLocalizer.NPCFimgLabel, + image = imgNodePath, + image_scale = imgScale, + ) + + def exit(self): + if self.dialog: + self.dialog.cleanup() + self.dialog = None + + def handleOk(self, value): + messenger.send(self.doneEvent, [self.doneStatus]) diff --git a/toontown/src/toon/NPCFriendPanel.py b/toontown/src/toon/NPCFriendPanel.py new file mode 100644 index 0000000..e60d33b --- /dev/null +++ b/toontown/src/toon/NPCFriendPanel.py @@ -0,0 +1,296 @@ +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import NPCToons +import ToonHead +import ToonDNA +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals + +class NPCFriendPanel(DirectFrame): + def __init__(self, parent = aspect2d, **kw): + # Define options + optiondefs = ( + ('relief', None, None), + ('doneEvent', None, None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + # Initialize superclass + DirectFrame.__init__(self, parent = parent) + self.cardList = [None, None, None, None, + None, None, None, None] + xOffset = -5.25 + yOffset = 2.3 + count = 0 + for i in range(8): + card = NPCFriendCard(parent = self, doneEvent = self['doneEvent']) + self.cardList[count] = card + card.setPos(xOffset, 1, yOffset) + xOffset += 3.5 + count += 1 + if count == 4: + xOffset = -5.25 + yOffset = -2.3 + # Initialize instance + self.initialiseoptions(NPCFriendPanel) + + def update(self, friendDict, fCallable = 0): + friendList = friendDict.keys() + for i in range(8): + card = self.cardList[i] + try: + NPCID = friendList[i] + count = friendDict[NPCID] + except IndexError: + NPCID = None + count = 0 + card.update(NPCID, count, fCallable) + +class NPCFriendCard(DirectFrame): + normalTextColor = (0.3,0.25,0.2,1) + maxRarity = 5 + sosTracks = (ToontownBattleGlobals.Tracks + + ToontownBattleGlobals.NPCTracks) + + def __init__(self, parent = aspect2dp, **kw): + # Define options + optiondefs = ( + ('NPCID', 'Uninitialized', None), + ('relief', None, None), + ('doneEvent', None, None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + # Initialize superclass + DirectFrame.__init__(self, parent = parent) + # Initialize instance + self.initialiseoptions(NPCFriendCard) + + # Front side of the card + cardModel = loader.loadModel('phase_3.5/models/gui/playingCard') + self.front = DirectFrame( + parent = self, relief = None, + image = cardModel.find('**/card_front'), + ) + self.front.hide() + + # Back side of the card + self.back = DirectFrame( + parent = self, relief = None, + image = cardModel.find('**/card_back'), + geom = cardModel.find('**/logo') + ) + + # Detail information about the quest + self.sosTypeInfo = DirectLabel( + parent = self.front, + relief = None, + text = '', + text_font = ToontownGlobals.getMinnieFont(), + text_fg = self.normalTextColor, + text_scale = 0.35, + text_align = TextNode.ACenter, + text_wordwrap = 7.0, + pos = (0,0,1.6), + ) + + # Toon head + self.NPCHead = None + + # NPC Name + self.NPCName = DirectLabel( + parent = self.front, + relief = None, + text = '', + text_fg = self.normalTextColor, + text_scale = 0.34, + text_align = TextNode.ACenter, + text_wordwrap = 8.0, + pos = (0, 0, -0.78) + ) + + # Call button (only show during battle + buttonModels = loader.loadModel( + "phase_3.5/models/gui/inventory_gui") + upButton = buttonModels.find("**/InventoryButtonUp") + downButton = buttonModels.find("**/InventoryButtonDown") + rolloverButton = buttonModels.find("**/InventoryButtonRollover") + self.sosCallButton = DirectButton( + parent = self.front, + relief = None, + text = TTLocalizer.NPCCallButtonLabel, + text_fg = self.normalTextColor, + text_scale = 0.28, + text_align = TextNode.ACenter, + image = (upButton, + downButton, + rolloverButton, + upButton, + ), + image_color = (1.0, 0.2, 0.2, 1), + # Make the rollover button pop out + image0_color = Vec4(1.0, 0.4, 0.4, 1), + # Make the disabled button fade out + image3_color = Vec4(1.0, 0.4, 0.4, 0.4), + image_scale = (4.4,1,3.6), + image_pos = Vec3(0,0,0.08), + pos = (-0.96, 0, -1.6), + scale = 1.25, + command = self.__chooseNPCFriend, + ) + self.sosCallButton.hide() + + # Info on how many more times one can use this card + self.sosCountInfo = DirectLabel( + parent = self.front, + relief = None, + text = '', + text_fg = self.normalTextColor, + text_scale = 0.4, + text_align = TextNode.ALeft, + textMayChange = 1, + pos = (0.0, 0, -1.5) + ) + + star = loader.loadModel('phase_3.5/models/gui/name_star') + self.rarityStars = [] + for i in range(self.maxRarity): + label = DirectLabel( + parent = self.front, + relief = None, + image = star, + image_scale = 0.2, + image_color = Vec4(0.502, 0.251, 0.251, 1.000), + pos = (1.1 - i * 0.24, 0, -1.8) + ) + label.hide() + self.rarityStars.append(label) + + def __chooseNPCFriend(self): + if self['NPCID'] and self['doneEvent']: + doneStatus = {} + doneStatus['mode'] = 'NPCFriend' + doneStatus['friend'] = self['NPCID'] + messenger.send(self['doneEvent'], [doneStatus]) + + def destroy(self): + if(self.NPCHead): + self.NPCHead.detachNode() + self.NPCHead.delete() + DirectFrame.destroy(self) + + + def update(self, NPCID, count=0, fCallable=0): + + # Record ID for next update + oldNPCID = self['NPCID'] + self['NPCID'] = NPCID + + if NPCID != oldNPCID: + # Update things that only change when NPC ID changes + if self.NPCHead: + # New NPC, get rid of old head if it exists + self.NPCHead.detachNode() + self.NPCHead.delete() + + if NPCID is None: + # Show back of card + self.showBack() + return + + # Show front of card + self.front.show() + self.back.hide() + + # Update labels + self.NPCName['text'] = TTLocalizer.NPCToonNames[NPCID] + # Creat new toon head + self.NPCHead = self.createNPCToonHead(NPCID, dimension = 1.4) + self.NPCHead.reparentTo(self.front) + self.NPCHead.setZ(0.3) + # Get details about toon + track, level, hp, rarity = NPCToons.getNPCTrackLevelHpRarity(NPCID) + # Update sos type info + sosText = self.sosTracks[track] + if track == ToontownBattleGlobals.NPC_RESTOCK_GAGS: + # In this case level is the track being restocked + if level == -1: + sosText += " All" + else: + sosText += " " + self.sosTracks[level] + sosText = TextEncoder.upper(sosText) + self.sosTypeInfo['text'] = sosText + # Update Rarity stars + for i in range(self.maxRarity): + if i < rarity: + self.rarityStars[i].show() + else: + self.rarityStars[i].hide() + + if fCallable: + self.sosCallButton.show() + self.sosCountInfo.setPos(-0.4, 0, -1.54) + self.sosCountInfo['text_scale'] = 0.28 + self.sosCountInfo['text_align'] = TextNode.ALeft + else: + self.sosCallButton.hide() + self.sosCountInfo.setPos(0, 0, -1.5) + self.sosCountInfo['text_scale'] = 0.4 + self.sosCountInfo['text_align'] = TextNode.ACenter + + if count > 0: + countText = (TTLocalizer.NPCFriendPanelRemaining % (count)) + self.sosCallButton['state'] = DGG.NORMAL + else: + countText = "Unavailable" + self.sosCallButton['state'] = DGG.DISABLED + + self.sosCountInfo['text'] = countText + + def showFront(self): + self.front.show() + self.back.hide() + + def showBack(self): + self.front.hide() + self.back.show() + + def createNPCToonHead(self, NPCID, dimension = 0.5): + # Given an NPC id create a toon head suitable for framing + NPCInfo = NPCToons.NPCToonDict[NPCID] + dnaList = NPCInfo[2] + gender = NPCInfo[3] + if dnaList == 'r': + dnaList = NPCToons.getRandomDNA(NPCID, gender) + dna = ToonDNA.ToonDNA() + dna.newToonFromProperties(*dnaList) + head = ToonHead.ToonHead() + head.setupHead(dna, forGui = 1) + # Insert xform with gets head to uniform size + self.fitGeometry(head, fFlip = 1, dimension = dimension) + return head + + def fitGeometry(self, geom, fFlip = 0, dimension = 0.5): + # Insert an xform which centers geometry on origin and scales it + # to +/-0.8 in size + p1 = Point3() + p2 = Point3() + geom.calcTightBounds(p1, p2) + if fFlip: + t = p1[0] + p1.setX(-p2[0]) + p2.setX(-t) + d = p2 - p1 + biggest = max(d[0], d[2]) + s = dimension/biggest + # find midpoint + mid = (p1 + d/2.0) * s + geomXform = hidden.attachNewNode('geomXform') + for child in geom.getChildren(): + child.reparentTo(geomXform) + geomXform.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], + 180, 0, 0, + s, s, s) + geomXform.reparentTo(geom) + diff --git a/toontown/src/toon/NPCToonDNACopy.py b/toontown/src/toon/NPCToonDNACopy.py new file mode 100644 index 0000000..4a9734a --- /dev/null +++ b/toontown/src/toon/NPCToonDNACopy.py @@ -0,0 +1,67 @@ +import os, sys + +npcDNA = {} + +def parseAndCopyDNA(): + loadDNA() + + if not os.path.isfile("/cygwin/home/abhinath/player/toontown/src/toon/NPCToons.py"): + print "NPCToons.py not found" + else: + npcToonsFile = open("/cygwin/home/abhinath/player/toontown/src/toon/NPCToons.py", "r+") + npcToons = npcToonsFile.read() + #npcToons.replace("\n", "\n ") + npcCount = 0 + while npcCount < len(npcDNA): + npcId = npcDNA.keys()[npcCount] + lineStart = npcToons.find(npcId+" :") + lineEnd = npcToons.find("\n", lineStart) + randPos = npcToons.find(" \"r\"", lineStart, lineEnd) + + oldLine = npcToons[lineStart:lineEnd] + newLine = oldLine.replace(" \"r\"", npcDNA[npcId]) + + npcToons = npcToons.replace(oldLine, newLine) + + npcCount += 1 + + # print npcToons + npcToonsFile.seek(0,0) + npcToonsFile.write(npcToons) + +def loadDNA(): + if not os.path.isfile("/cygwin/home/abhinath/player/toontown/src/toon/RTDNAFile.txt"): + print "RTDNAFile.txt not found" + return 1 + else: + npcDNAFile = open("/cygwin/home/abhinath/player/toontown/src/toon/RTDNAFile.txt", "r") + while 1: + NPCLine = npcDNAFile.readline() + if NPCLine == "": + break + if NPCLine.find("NPC Id: ")>-1: + break + + while NPCLine != "": + DNALine = npcDNAFile.readline() + if (NPCLine.find("NPC Id: ")>-1): + NPCLine = NPCLine.replace("NPC Id: ", "") + NPCLine = NPCLine.replace("\n", "") + if(DNALine.find("DNA: ")>-1): + DNALine = DNALine.replace("DNA: ", "") + DNALine = DNALine.replace("\n", "") + npcDNA[NPCLine] = DNALine + + while 1: + NPCLine = npcDNAFile.readline() + if NPCLine == "": + break + if NPCLine.find("NPC Id: ")>-1: + break + + # print npcDNA + return 0 + + +parseAndCopyDNA() + \ No newline at end of file diff --git a/toontown/src/toon/NPCToons.py b/toontown/src/toon/NPCToons.py new file mode 100644 index 0000000..7063c79 --- /dev/null +++ b/toontown/src/toon/NPCToons.py @@ -0,0 +1,1114 @@ + +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +import random +from toontown.hood import ZoneUtil +import ToonDNA +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownBattleGlobals +import sys, os +import string + +# These are the various modes for DistributedNPC setMovie +QUEST_MOVIE_CLEAR = 0 +QUEST_MOVIE_REJECT = 1 +QUEST_MOVIE_COMPLETE = 2 +QUEST_MOVIE_INCOMPLETE = 3 +QUEST_MOVIE_ASSIGN = 4 +QUEST_MOVIE_BUSY = 5 +QUEST_MOVIE_QUEST_CHOICE = 6 +QUEST_MOVIE_QUEST_CHOICE_CANCEL = 7 +QUEST_MOVIE_TRACK_CHOICE = 8 +QUEST_MOVIE_TRACK_CHOICE_CANCEL = 9 +QUEST_MOVIE_TIMEOUT = 10 +QUEST_MOVIE_TIER_NOT_DONE = 11 + +PURCHASE_MOVIE_CLEAR = 0 +PURCHASE_MOVIE_START = 1 +PURCHASE_MOVIE_START_BROWSE = 9 +PURCHASE_MOVIE_COMPLETE = 2 +PURCHASE_MOVIE_NO_MONEY = 3 +PURCHASE_MOVIE_TIMEOUT = 8 +PURCHASE_MOVIE_START_NOROOM = 10 + +SELL_MOVIE_CLEAR = 0 +SELL_MOVIE_START = 1 +SELL_MOVIE_COMPLETE = 2 +SELL_MOVIE_NOFISH = 3 +SELL_MOVIE_TROPHY = 4 +SELL_MOVIE_TIMEOUT = 8 +SELL_MOVIE_PETRETURNED = 9 #these have to live nicely with the fish clerk ones +SELL_MOVIE_PETADOPTED = 10 +SELL_MOVIE_PETCANCELED = 11 + +# The following are used by DistributedPartyPerson/AI +PARTY_MOVIE_CLEAR = 0 +PARTY_MOVIE_START = 1 +PARTY_MOVIE_COMPLETE = 2 +PARTY_MOVIE_ALREADYHOSTING = 3 +PARTY_MOVIE_MAYBENEXTTIME = 4 +PARTY_MOVIE_ONLYPAID = 5 +PARTY_MOVIE_COMINGSOON = 6 +PARTY_MOVIE_MINCOST = 7 +PARTY_MOVIE_TIMEOUT = 8 + +BLOCKER_MOVIE_CLEAR = 0 +BLOCKER_MOVIE_START = 1 +BLOCKER_MOVIE_TIMEOUT = 8 + +NPC_REGULAR = 0 +NPC_CLERK = 1 +NPC_TAILOR = 2 +NPC_HQ = 3 +NPC_BLOCKER = 4 +NPC_FISHERMAN = 5 +NPC_PETCLERK = 6 +NPC_KARTCLERK = 7 +NPC_PARTYPERSON = 8 +NPC_SPECIALQUESTGIVER = 9 +NPC_FLIPPYTOONHALL = 10 +NPC_SCIENTIST = 11 + +CLERK_COUNTDOWN_TIME = 120 +TAILOR_COUNTDOWN_TIME = 300 + +RTDNAFile = "/RTDNAFile.txt" +saveDNA = False + +def getRandomDNA(seed, gender): + randomDNA = ToonDNA.ToonDNA() + randomDNA.newToonRandom(seed, gender, 1) + return randomDNA.asTuple() + +def createNPC(air, npcId, desc, zoneId, posIndex=0, questCallback=None): + import DistributedNPCToonAI + import DistributedNPCClerkAI + import DistributedNPCTailorAI + import DistributedNPCBlockerAI + import DistributedNPCFishermanAI + import DistributedNPCPetclerkAI + import DistributedNPCKartClerkAI + import DistributedNPCPartyPersonAI + import DistributedNPCSpecialQuestGiverAI + import DistributedNPCFlippyInToonHallAI + import DistributedNPCScientistAI + canonicalZoneId, name, dnaType, gender, protected, type = desc + if (type == NPC_REGULAR): + npc = DistributedNPCToonAI.DistributedNPCToonAI( + air, npcId, + questCallback=questCallback) + elif (type == NPC_HQ): + npc = DistributedNPCToonAI.DistributedNPCToonAI( + air, npcId, + questCallback=questCallback, hq=1) + elif (type == NPC_CLERK): + npc = DistributedNPCClerkAI.DistributedNPCClerkAI(air, npcId) + elif (type == NPC_TAILOR): + npc = DistributedNPCTailorAI.DistributedNPCTailorAI(air, npcId) + elif (type == NPC_BLOCKER): + npc = DistributedNPCBlockerAI.DistributedNPCBlockerAI(air, npcId) + elif (type == NPC_FISHERMAN): + npc = DistributedNPCFishermanAI.DistributedNPCFishermanAI(air, npcId) + elif (type == NPC_PETCLERK): + npc = DistributedNPCPetclerkAI.DistributedNPCPetclerkAI(air, npcId) + elif (type == NPC_KARTCLERK): + npc = DistributedNPCKartClerkAI.DistributedNPCKartClerkAI(air, npcId) + elif (type == NPC_PARTYPERSON): + npc = DistributedNPCPartyPersonAI.DistributedNPCPartyPersonAI(air, npcId) + elif(type == NPC_SPECIALQUESTGIVER): + npc = DistributedNPCSpecialQuestGiverAI.DistributedNPCSpecialQuestGiverAI(air, npcId) + elif(type == NPC_FLIPPYTOONHALL): + npc = DistributedNPCFlippyInToonHallAI.DistributedNPCFlippyInToonHallAI(air, npcId) + elif(type == NPC_SCIENTIST): + npc = DistributedNPCScientistAI.DistributedNPCScientistAI(air, npcId) + else: + print 'createNPC() error!!!' + npc.setName(name) + dna = ToonDNA.ToonDNA() + + if dnaType == "r": + # ...random dna. + dnaList = getRandomDNA(npcId, gender) + else: + dnaList = dnaType + + if saveDNA: + strList = [] + strList.append("\n\nNPC Id: ") + strList.append(str(npcId)) + strList.append("\nDNA: ") + count = 0 + strList.append("(") + for item in dnaList: + if count < 4: + strList.append("\""+str(item)+"\"") + else: + strList.append(str(item)) + count += 1 + strList.append(" ,") + strList.append(")") + rtDNA = "".join(strList) + + if os.path.isfile(RTDNAFile): + rtDnaFile = open(RTDNAFile, "r+") + rtDnaFile.seek(0,2) + rtDnaFile.writelines(rtDNA) + else: + rtDnaFile = open(RTDNAFile, "w") + rtDnaFile.writelines(rtDNA) + + rtDnaFile.close() + + dna.newToonFromProperties(*dnaList) + npc.setDNAString(dna.makeNetString()) + npc.setHp(15) + npc.setMaxHp(15) + # NOTE: The npc will be placed on the client when it is created + npc.setPositionIndex(posIndex) + npc.generateWithRequired(zoneId) + + if hasattr(npc, "startAnimState"): + npc.d_setAnimState(npc.startAnimState, 1.) + else: + npc.d_setAnimState("neutral", 1.) + return npc + +def createNpcsInZone(air, zoneId): + npcs = [] + canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId) + npcIdList = zone2NpcDict.get(canonicalZoneId, []) + for i in range(len(npcIdList)): + npcId = npcIdList[i] + npcDesc = NPCToonDict.get(npcId) + assert npcDesc + npcs.append(createNPC(air, npcId, npcDesc, zoneId, posIndex=i)) + return npcs + +def createLocalNPC(npcId): + import Toon + if (not NPCToonDict.has_key(npcId)): + return None + desc = NPCToonDict[npcId] + canonicalZoneId, name, dnaType, gender, protected, type = desc + npc = Toon.Toon() + npc.setName(name) + # You cannot click on the nametags of npc sos toons + npc.setPickable(0) + npc.setPlayerType(NametagGroup.CCNonPlayer) + dna = ToonDNA.ToonDNA() + if dnaType == "r": + # ...random dna. + dnaList = getRandomDNA(npcId, gender) + else: + dnaList = dnaType + dna.newToonFromProperties(*dnaList) + npc.setDNAString(dna.makeNetString()) + + npc.animFSM.request("neutral") + return npc + +def isZoneProtected(zoneId): + """ + isZoneProtected(zoneId) + + Returns true if the building with the indicated interior zone ID + is marked as a protected building: the NPC toon(s) standing within + it won't be conquered by a wandering suit. + + The building may still be taken over with a magic word, but a suit + will be too afraid to take over a protected building automatically. + """ + + # The building is protected if any NPC toon standing within it is + # marked protected. This is just the most natural place for us to + # indicate 'protected' status from a designer's point of view, + # although the code has to go through some hoops to get to it. + + npcs = [] + npcIdList = zone2NpcDict.get(zoneId, []) + for npcId in npcIdList: + npcDesc = NPCToonDict.get(npcId) + assert npcDesc + if npcDesc[4]: + return 1 + + return 0 + + +# Since the names are localized, we cannot simply type them in here. Instead we will +# read them out of the localizer. +# Just to make the NPCToonDict a little smaller and easier to read, lets store a local +# variable on the NPCToonNames dict. lnames is short for localized names. +lnames = TTLocalizer.NPCToonNames + +NPCToonDict = { + # (INTERIOR zoneId, "name", "r"|, "m"|"f", protected, type), + # if protected is 1, the building will not be taken over automatically. + # DNA is (head, torso, legs, armColor, gloveColor, legColor, headColor, + # topTexture, bottomTexture) + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : (-1, lnames[20000], ("dll" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,2 ,6 ,2 ,6 ,2 ,16), "m", 1, NPC_REGULAR), + 999 : (-1, lnames[999], "r", "m", 1, NPC_TAILOR), + 1000 : (-1, lnames[1000], "r", "m", 1, NPC_HQ), + # Flippy DNA matches marketing materials + 20001 : (-1, lnames[20001], ('dss', 'ms', 'm', 'm', 17,0,17,17,3,3,3,3,7,2), "m", 1, NPC_BLOCKER), + + # Toontown Central + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + # Flippy DNA matches marketing materials + 2002 : (2514, lnames[2002], ("hss" ,"ls" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,3 ,0 ,3 ,1 ,18), "m", 1, NPC_REGULAR), + 2003 : (2516, lnames[2003], ("cll" ,"ms" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,1 ,15), "m", 1, NPC_REGULAR), + 2004 : (2521, lnames[2004], ('rll', 'md', 'm', "f", 15,0,5,7,3,5,3,5,0,3), "f", 1, NPC_TAILOR), + 2005 : (2518, lnames[2005], ("cls" ,"ls" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,4 ,0 ,4 ,1 ,9), "m", 1, NPC_REGULAR), + 2006 : (2519, lnames[2006], ("dsl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,4 ,1 ,4 ,1 ,2), "m", 1, NPC_CLERK), + 2011 : (2519, lnames[2011], ("rll" ,"ms" ,"l" ,"f" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,23 ,27), "f", 1, NPC_CLERK), + 2007 : (2520, lnames[2007], ("dss" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,1 ,20), "m", 1, NPC_HQ), + 2008 : (2520, lnames[2008], ("fll" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,5 ,1 ,5 ,1 ,17), "m", 1, NPC_HQ), + 2009 : (2520, lnames[2009], ("fsl" ,"md" ,"l" ,"f" ,18 ,0 ,18 ,18 ,1 ,8 ,1 ,8 ,11 ,27), "f", 1, NPC_HQ), + 2010 : (2520, lnames[2010], ("fls" ,"ls" ,"l" ,"f" ,11 ,0 ,11 ,11 ,1 ,8 ,1 ,8 ,8 ,4), "f", 1, NPC_HQ), + 2012 : (2000, lnames[2012], ("rss" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,6 ,1 ,6 ,1 ,1), "m", 1, NPC_FISHERMAN), + 2013 : (2522, lnames[2013], ("rls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,7 ,0 ,7 ,1 ,19), "m", 1, NPC_PETCLERK), + 2014 : (2522, lnames[2014], ("mls" ,"ms" ,"m" ,"f" ,2 ,0 ,2 ,2 ,0 ,12 ,0 ,12 ,1 ,0 ,), "f", 1, NPC_PETCLERK), + 2015 : (2522, lnames[2015], ("hsl" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,0 ,8 ,0 ,8 ,1 ,13 ,), "m", 1, NPC_PETCLERK), + 2016 : (2000, lnames[2016], ("sls", "ls", "m", "m", 10, 0, 9, 9, 0, 3, 0, 3, 0, 18), "m", 1, NPC_PARTYPERSON), + 2017 : (2000, lnames[2017], ("sss", "ld", "m", "f", 10, 0, 9, 9, 0, 23, 0, 23, 0, 5), "f", 1, NPC_PARTYPERSON), + 2018 : (2513, lnames[2019], ("fll" ,"ss" ,"s" ,"m" ,15 ,0 ,15 ,15 ,99 ,27 ,86 ,27 ,39 ,27), "m", 1, NPC_SCIENTIST), + 2019 : (2513, lnames[2018], ("pls" ,"ls" ,"l" ,"m" ,9 ,0 ,9 ,9 ,98 ,27 ,86 ,27 ,38 ,27), "m", 1, NPC_SCIENTIST), + 2020 : (2513, lnames[2020], ("hss" ,"ms" ,"m" ,"m" ,20 ,0 ,20 ,20 ,97 ,27 ,86 ,27 ,37 ,27 ,), "m", 1, NPC_SCIENTIST), + #2018 : (2000, lnames[2018], ('dss', 'ld', 'l', "f", 15,0,5,7,3,5,3,5,0,3), "m", 1, NPC_SPECIALQUESTGIVER), + + 2101 : (2601, lnames[2101], ("rll" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,9 ,0 ,9 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 2102 : (2619, lnames[2102], "r", "f", 0, NPC_REGULAR), + 2103 : (2616, lnames[2103], ("csl", "ss", "s", 'm', 9,0,8,5,0,11,0,11,2,10), "m", 0, NPC_REGULAR), # Tribute + 2104 : (2671, lnames[2104], ("mls" ,"ms" ,"m" ,"m" ,15 ,0 ,15 ,15 ,1 ,10 ,1 ,10 ,0 ,16 ,), "m", 1, NPC_HQ), + 2105 : (2671, lnames[2105], ("hsl" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,10 ,1 ,10 ,0 ,13 ,), "m", 1, NPC_HQ), + 2106 : (2671, lnames[2106], ("hss" ,"ld" ,"m" ,"f" ,23 ,0 ,23 ,23 ,1 ,23 ,1 ,23 ,24 ,27 ,), "f", 1, NPC_HQ), + 2107 : (2671, lnames[2107], ("cll" ,"sd" ,"m" ,"f" ,14 ,0 ,14 ,14 ,1 ,24 ,1 ,24 ,7 ,4 ,), "f", 1, NPC_HQ), + 2108 : (2603, lnames[2108], ("csl" ,"ms" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,24 ,1 ,24 ,3 ,2 ,), "f", 0, NPC_REGULAR), + 2109 : (2604, lnames[2109], "r", "m", 0, NPC_REGULAR), + 2110 : (2605, lnames[2110], ("dll" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,0 ,15 ,), "m", 0, NPC_REGULAR), + 2111 : (2607, lnames[2111], "r", "m", 0, NPC_REGULAR), + 2112 : (2610, lnames[2112], ("fll" ,"ss" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,27 ,0 ,27 ,0 ,9 ,), "m", 1, NPC_REGULAR), + 2113 : (2617, lnames[2113], ("fsl" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,0 ,0 ,0 ,0 ,2 ,), "m", 0, NPC_REGULAR), + 2114 : (2618, lnames[2114], ("fls" ,"sd" ,"m" ,"f" ,6 ,0 ,6 ,6 ,0 ,0 ,0 ,0 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 2115 : (2621, lnames[2115], ("rll" ,"ms" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 2116 : (2624, lnames[2116], ("rss" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,1 ,1 ,1 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 2117 : (2625, lnames[2117], ("rls" ,"sd" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 2118 : (2626, lnames[2118], ("mls" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,1 ,1 ,1 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 2119 : (2629, lnames[2119], ("hll" ,"ss" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,19 ,27 ,), "f", 1, NPC_REGULAR), + 2120 : (2632, lnames[2120], ("hss" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,2 ,1 ,2 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 2121 : (2633, lnames[2121], ("cll" ,"ls" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,4 ,0 ,4 ,4 ,4 ,), "f", 0, NPC_REGULAR), + 2122 : (2639, lnames[2122], ("csl" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,3 ,0 ,3 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 2123 : (2643, lnames[2123], ("cls" ,"md" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 2124 : (2644, lnames[2124], ("dll" ,"sd" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,5 ,0 ,5 ,8 ,21 ,), "f", 0, NPC_REGULAR), + 2125 : (2649, lnames[2125], ("dss" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,4 ,0 ,4 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 2126 : (2654, lnames[2126], ("dls" ,"ld" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,6 ,0 ,6 ,3 ,7 ,), "f", 1, NPC_REGULAR), + 2127 : (2655, lnames[2127], ("fsl" ,"ms" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,5 ,0 ,5 ,1 ,15 ,), "m", 1, NPC_REGULAR), + 2128 : (2656, lnames[2128], ("fss" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,5 ,1 ,5 ,1 ,12 ,), "m", 1, NPC_REGULAR), + 2129 : (2657, lnames[2129],("rll" ,"ss" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 2130 : (2659, lnames[2130],("rss" ,"md" ,"l" ,"f" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,7 ,7 ,), "f", 0, NPC_REGULAR), + 2131 : (2660, lnames[2131],("rls" ,"ls" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,8 ,1 ,8 ,1 ,26 ,), "f", 1, NPC_REGULAR), + 2132 : (2661, lnames[2132],("mls" ,"ss" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,6 ,1 ,6 ,1 ,17 ,), "m", 0, NPC_REGULAR), + 2133 : (2662, lnames[2133],("hll" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,6 ,1 ,6 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 2134 : (2664, lnames[2134], "r", "f", 0, NPC_REGULAR), + 2135 : (2665, lnames[2135],("hls" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,0 ,12 ,0 ,12 ,2 ,26 ,), "f", 1, NPC_REGULAR), + 2136 : (2666, lnames[2136],("csl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,8 ,0 ,8 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 2137 : (2667, lnames[2137],("css" ,"sd" ,"l" ,"f" ,11 ,0 ,11 ,11 ,0 ,21 ,0 ,21 ,24 ,27 ,), "f", 0, NPC_REGULAR), + 2138 : (2669, lnames[2138],("dll" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,0 ,9 ,0 ,9 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 2139 : (2670, lnames[2139], "r", "m", 0, NPC_REGULAR), + 2140 : (2156, lnames[2140],("dls" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,9 ,1 ,9 ,1 ,10 ,), "m", 0, NPC_FISHERMAN), + + 2201 : (2711, lnames[2201],("dss" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,0 ,17 ,), "m", 1, NPC_REGULAR), + 2202 : (2718, lnames[2202], "r", "f", 1, NPC_REGULAR), + 2203 : (2742, lnames[2203],("fss" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,0 ,7 ,0 ,7 ,0 ,11 ,), "m", 1, NPC_HQ), + 2204 : (2742, lnames[2204],("fls" ,"ss" ,"s" ,"m" ,13 ,0 ,13 ,13 ,0 ,7 ,0 ,7 ,0 ,6 ,), "m", 1, NPC_HQ), + 2205 : (2742, lnames[2205],("rsl" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,16 ,27 ,), "f", 1, NPC_HQ), + 2206 : (2742, lnames[2206],("rss" ,"sd" ,"s" ,"f" ,21 ,0 ,21 ,21 ,0 ,12 ,0 ,12 ,0 ,8 ,), "f", 1, NPC_HQ), + 2207 : (2705, lnames[2207],("mss" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,0 ,8 ,0 ,8 ,0 ,16 ,), "m", 1, NPC_REGULAR), + 2208 : (2708, lnames[2208],("mls" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,8 ,1 ,8 ,0 ,13 ,), "m", 1, NPC_REGULAR), + 2209 : (2712, lnames[2209],("hsl" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,0 ,10 ,), "m", 1, NPC_REGULAR), + 2210 : (2713, lnames[2210],("hss" ,"ms" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,21 ,1 ,21 ,1 ,24 ,), "f", 1, NPC_REGULAR), + 2211 : (2716, lnames[2211],("cll" ,"ss" ,"s" ,"f" ,3 ,0 ,3 ,3 ,1 ,22 ,1 ,22 ,25 ,27 ,), "f", 1, NPC_REGULAR), + 2212 : (2717, lnames[2212],("css" ,"ls" ,"s" ,"m" ,18 ,0 ,18 ,18 ,1 ,9 ,1 ,9 ,0 ,18 ,), "m", 0, NPC_REGULAR), + 2213 : (2720, lnames[2213],("cls" ,"ls" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,23 ,1 ,23 ,11 ,27 ,), "f", 1, NPC_REGULAR), + 2214 : (2723, lnames[2214], "r", "m", 0, NPC_REGULAR), + 2215 : (2727, lnames[2215],("dss" ,"ls" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,11 ,0 ,11 ,0 ,9 ,), "m", 0, NPC_REGULAR), + 2216 : (2728, lnames[2216],("fll" ,"sd" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,25 ,0 ,25 ,12 ,27 ,), "f", 0, NPC_REGULAR), + 2217 : (2729, lnames[2217],("fsl" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,0 ,20 ,), "m", 1, NPC_REGULAR), + 2218 : (2730, lnames[2218], "r", "f", 0, NPC_REGULAR), + 2219 : (2732, lnames[2219],("rll" ,"ms" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,27 ,0 ,27 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 2220 : (2733, lnames[2220],("rss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,12 ,1 ,12 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 2221 : (2734, lnames[2221], "r", "f", 0, NPC_REGULAR), + 2222 : (2735, lnames[2222],("mls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,0 ,1 ,0 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 2223 : (2739, lnames[2223], "r", "f", 0, NPC_REGULAR), + 2224 : (2740, lnames[2224],("hss" ,"ss" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,1 ,1 ,1 ,0 ,16 ,), "m", 0, NPC_REGULAR), + 2225 : (2236, lnames[2225],("cll" ,"ls" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,1 ,1 ,1 ,0 ,13 ,), "m", 0, NPC_FISHERMAN), + + 2301 : (2804, lnames[2301],("cll" ,"ms" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,3 ,1 ,3 ,0 ,6 ,), "m", 1, NPC_REGULAR), + 2302 : (2831, lnames[2302],("css" ,"ms" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,0 ,1 ,), "m", 1, NPC_REGULAR), + 2303 : (2834, lnames[2303], "r", "f", 0, NPC_REGULAR), + 2304 : (2832, lnames[2304],("dss" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,0 ,10 ,0 ,10 ,1 ,12 ,), "m", 1, NPC_HQ), + 2305 : (2832, lnames[2305],("dss" ,"ss" ,"m" ,"m" ,8 ,0 ,8 ,8 ,1 ,0 ,1 ,0 ,1 ,9 ,), "m", 1, NPC_HQ), + 2306 : (2832, lnames[2306],("fll" ,"md" ,"m" ,"f" ,24 ,0 ,24 ,24 ,1 ,0 ,1 ,0 ,16 ,27 ,), "f", 1, NPC_HQ), + 2307 : (2832, lnames[2307],("fsl" ,"ls" ,"m" ,"f" ,16 ,0 ,16 ,16 ,1 ,1 ,1 ,1 ,3 ,1 ,), "f", 1, NPC_HQ), + 2308 : (2801, lnames[2308],("fls" ,"ss" ,"m" ,"f" ,8 ,0 ,8 ,8 ,1 ,1 ,1 ,1 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 2309 : (2802, lnames[2309],("rsl" ,"ls" ,"m" ,"m" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 2311 : (2809, lnames[2311],("mss" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,2 ,0 ,2 ,1 ,6 ,), "m", 1, NPC_REGULAR), + 2312 : (2837, lnames[2312],("mls" ,"ld" ,"m" ,"f" ,24 ,0 ,24 ,24 ,0 ,3 ,0 ,3 ,4 ,6 ,), "f", 0, NPC_REGULAR), + 2313 : (2817, lnames[2313], "r", "f", 0, NPC_REGULAR), + 2314 : (2818, lnames[2314],("hss" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,3 ,0 ,3 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 2315 : (2822, lnames[2315],("cll" ,"ss" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,3 ,0 ,3 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 2316 : (2823, lnames[2316],("csl" ,"md" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,0 ,23 ,), "f", 0, NPC_REGULAR), + 2318 : (2829, lnames[2318],("dsl" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,4 ,1 ,4 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 2319 : (2830, lnames[2319],("dss" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 2320 : (2839, lnames[2320], "r", "m", 0, NPC_REGULAR), + 2321 : (2341, lnames[2321],("fsl" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,5 ,1 ,5 ,0 ,12 ,), "m", 0, NPC_FISHERMAN), + + # Donald's Dock + 1001 : (1506, lnames[1001],("rss" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,11 ,0 ,11 ,0 ,0 ,), "m", 0, NPC_CLERK), + 1002 : (1506, lnames[1002],("mss" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,10 ,1 ,10 ,0 ,18 ,), "m", 0, NPC_CLERK), + 1003 : (1507, lnames[1003],("mls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,11 ,1 ,11 ,0 ,15 ,), "m", 0, NPC_HQ), + 1004 : (1507, lnames[1004],("hsl" ,"md" ,"l" ,"f" ,10 ,0 ,10 ,10 ,1 ,24 ,1 ,24 ,24 ,27 ,), "f", 0, NPC_HQ), + 1005 : (1507, lnames[1005],("hss" ,"ms" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,11 ,1 ,11 ,0 ,9 ,), "m", 0, NPC_HQ), + 1006 : (1507, lnames[1006],("cll" ,"ss" ,"l" ,"f" ,18 ,0 ,18 ,18 ,1 ,25 ,1 ,25 ,19 ,27 ,), "f", 0, NPC_HQ), + 1007 : (1508, lnames[1007],("csl" ,"ls" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,12 ,1 ,12 ,0 ,20 ,), "m", 0, NPC_TAILOR), + 1008 : (1000, lnames[1008],("cls" ,"ms" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,27 ,0 ,27 ,0 ,17 ,), "m", 0, NPC_FISHERMAN), + 1009 : (1510, lnames[1009],("dsl" ,"ss" ,"m" ,"m" ,17 ,0 ,17 ,17 ,0 ,0 ,0 ,0 ,0 ,14 ,), "m", 0, NPC_PETCLERK), + 1010 : (1510, lnames[1010],("dss" ,"ld" ,"m" ,"f" ,10 ,0 ,10 ,10 ,0 ,0 ,0 ,0 ,26 ,27 ,), "f", 0, NPC_PETCLERK), + 1011 : (1510, lnames[1011],("fll" ,"sd" ,"m" ,"f" ,1 ,0 ,1 ,1 ,0 ,1 ,0 ,1 ,4 ,25 ,), "f", 0, NPC_PETCLERK), + 1012 : (1000, lnames[1012], ("fls", "ms", "l", "m", 14, 0, 3, 3, 0, 1, 0, 1, 0, 13), "m", 1, NPC_PARTYPERSON), + 1013 : (1000, lnames[1013], ("fss", "ms", "m", "f", 2, 0, 3, 3, 1, 6, 1, 6, 5, 6), "f", 1, NPC_PARTYPERSON), + + 1101 : (1627, lnames[1101],("fll" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,1 ,3 ,1 ,3 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 1102 : (1612, lnames[1102],("fsl" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,3 ,1 ,3 ,1 ,2 ,), "m", 0, NPC_REGULAR), + 1103 : (1626, lnames[1103], "r", "m", 0, NPC_REGULAR), + 1104 : (1617, lnames[1104], "r", "m", 0, NPC_REGULAR), + 1105 : (1606, lnames[1105],("rss" ,"ms" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,4 ,0 ,4 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 1106 : (1604, lnames[1106], "r", "f", 0, NPC_REGULAR), + 1107 : (1621, lnames[1107], "r", "m", 0, NPC_REGULAR), + 1108 : (1629, lnames[1108],("hsl" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,6 ,0 ,6 ,1 ,1 ,), "m", 0, NPC_HQ), + 1109 : (1629, lnames[1109],("hss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,0 ,8 ,0 ,8 ,14 ,27 ,), "f", 0, NPC_HQ), + 1110 : (1629, lnames[1110],("cll" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,1 ,16 ,), "m", 0, NPC_HQ), + 1111 : (1629, lnames[1111],("csl" ,"ld" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,2 ,2 ,), "f", 0, NPC_HQ), + 1112 : (1602, lnames[1112],("cls" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,7 ,1 ,7 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 1113 : (1608, lnames[1113],("dll" ,"ms" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,11 ,1 ,11 ,0 ,27 ,), "f", 0, NPC_REGULAR), + 1114 : (1609, lnames[1114],("dss" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,7 ,1 ,7 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 1115 : (1613, lnames[1115],("fll" ,"sd" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,12 ,1 ,12 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 1116 : (1614, lnames[1116],("fsl" ,"ls" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,12 ,1 ,12 ,1 ,25 ,), "f", 0, NPC_REGULAR), + 1117 : (1615, lnames[1117],("fls" ,"ss" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,9 ,0 ,9 ,1 ,12 ,), "m", 0, NPC_REGULAR), + 1118 : (1616, lnames[1118],("rll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,9 ,0 ,9 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 1121 : (1619, lnames[1121], "r", "f", 0, NPC_REGULAR), + 1122 : (1620, lnames[1122],("hll" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,11 ,0 ,11 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 1123 : (1622, lnames[1123],("hss" ,"ms" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,23 ,1 ,23 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 1124 : (1624, lnames[1124],("cll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,11 ,1 ,11 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 1125 : (1628, lnames[1125],("csl" ,"sd" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,24 ,1 ,24 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 1126 : (1129, lnames[1126],("cls" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,11 ,1 ,11 ,0 ,19 ,), "m", 0, NPC_FISHERMAN), + + 1201 : (1710, lnames[1201],("css" ,"ls" ,"s" ,"f" ,12 ,0 ,12 ,12 ,0 ,0 ,0 ,0 ,1 ,24 ,), "f", 0, NPC_REGULAR), + 1202 : (1713, lnames[1202],("cls" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,0 ,0 ,0 ,0 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 1203 : (1725, lnames[1203], "r", "m", 0, NPC_REGULAR), + 1204 : (1712, lnames[1204],("dss" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,1 ,1 ,1 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 1205 : (1729, lnames[1205],("fll" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,1 ,1 ,1 ,1 ,1 ,), "m", 0, NPC_HQ), + 1206 : (1729, lnames[1206],("fss" ,"ld" ,"s" ,"f" ,19 ,0 ,19 ,19 ,1 ,2 ,1 ,2 ,7 ,11 ,), "f", 0, NPC_HQ), + 1207 : (1729, lnames[1207],("fls" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,1 ,16 ,), "m", 0, NPC_HQ), + 1208 : (1729, lnames[1208],("rsl" ,"ls" ,"m" ,"f" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,23 ,27 ,), "f", 0, NPC_HQ), + 1209 : (1701, lnames[1209],("rss" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,4 ,0 ,4 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 1210 : (1703, lnames[1210], "r", "m", 0, NPC_REGULAR), + 1211 : (1705, lnames[1211],("mls" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,4 ,0 ,4 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 1212 : (1706, lnames[1212],("hsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 1213 : (1707, lnames[1213],("hss" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,4 ,0 ,4 ,1 ,15 ,), "m", 0, NPC_REGULAR), + 1214 : (1709, lnames[1214],("cll" ,"sd" ,"m" ,"f" ,2 ,0 ,2 ,2 ,0 ,7 ,0 ,7 ,1 ,12 ,), "f", 0, NPC_REGULAR), + 1215 : (1711, lnames[1215],("css" ,"ms" ,"m" ,"f" ,18 ,0 ,18 ,18 ,0 ,7 ,0 ,7 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 1216 : (1714, lnames[1216],("cls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,1 ,2 ,), "m", 0, NPC_REGULAR), + 1217 : (1716, lnames[1217], "r", "f", 0, NPC_REGULAR), + 1218 : (1717, lnames[1218],("dss" ,"ms" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,6 ,1 ,6 ,1 ,17 ,), "m", 0, NPC_REGULAR), + 1219 : (1718, lnames[1219],("fll" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,6 ,1 ,6 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 1220 : (1719, lnames[1220],("fsl" ,"md" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,7 ,23 ,), "f", 0, NPC_REGULAR), + 1221 : (1720, lnames[1221], "r", "m", 0, NPC_REGULAR), + 1222 : (1721, lnames[1222], "r", "m", 0, NPC_REGULAR), + 1223 : (1723, lnames[1223],("rss" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,0 ,8 ,0 ,8 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 1224 : (1724, lnames[1224],("mss" ,"sd" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,21 ,0 ,21 ,7 ,9 ,), "f", 0, NPC_REGULAR), + 1225 : (1726, lnames[1225],("mls" ,"ss" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 1226 : (1727, lnames[1226],("hsl" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,0 ,9 ,0 ,9 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 1227 : (1728, lnames[1227],("hss" ,"sd" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,22 ,0 ,22 ,3 ,7 ,), "f", 0, NPC_REGULAR), + 1228 : (1236, lnames[1228],("cll" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,1 ,9 ,1 ,9 ,1 ,0 ,), "m", 0, NPC_FISHERMAN), + + 1301 : (1828, lnames[1301],("mls" ,"md" ,"m" ,"f" ,16 ,0 ,16 ,16 ,1 ,8 ,1 ,8 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 1302 : (1832, lnames[1302],("hsl" ,"ms" ,"m" ,"m" ,8 ,0 ,8 ,8 ,1 ,6 ,1 ,6 ,0 ,18 ,), "m", 0, NPC_REGULAR), + 1303 : (1826, lnames[1303],("hls" ,"ss" ,"m" ,"m" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,0 ,15 ,), "m", 0, NPC_REGULAR), + 1304 : (1804, lnames[1304],("cll" ,"md" ,"m" ,"f" ,15 ,0 ,15 ,15 ,1 ,9 ,1 ,9 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 1305 : (1835, lnames[1305],("css" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,7 ,1 ,7 ,0 ,9 ,), "m", 0, NPC_HQ), + 1306 : (1835, lnames[1306],("cls" ,"ms" ,"m" ,"f" ,24 ,0 ,24 ,24 ,0 ,12 ,0 ,12 ,0 ,7 ,), "f", 0, NPC_HQ), + 1307 : (1835, lnames[1307],("dsl" ,"ls" ,"m" ,"m" ,15 ,0 ,15 ,15 ,0 ,8 ,0 ,8 ,0 ,20 ,), "m", 0, NPC_HQ), + 1308 : (1835, lnames[1308],("dss" ,"sd" ,"m" ,"f" ,8 ,0 ,8 ,8 ,0 ,21 ,0 ,21 ,4 ,5 ,), "f", 0, NPC_HQ), + 1309 : (1802, lnames[1309],("fll" ,"ms" ,"l" ,"f" ,23 ,0 ,23 ,23 ,0 ,21 ,0 ,21 ,1 ,12 ,), "f", 0, NPC_REGULAR), + 1310 : (1805, lnames[1310],("fsl" ,"ss" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,9 ,0 ,9 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 1311 : (1806, lnames[1311], "r", "f", 0, NPC_REGULAR), + 1312 : (1807, lnames[1312],("rsl" ,"ms" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,9 ,1 ,9 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 1313 : (1808, lnames[1313],("rss" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,10 ,1 ,10 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 1314 : (1809, lnames[1314],("mss" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,10 ,1 ,10 ,0 ,16 ,), "m", 0, NPC_REGULAR), + 1315 : (1810, lnames[1315], "r", "f", 0, NPC_REGULAR), + 1316 : (1811, lnames[1316],("hsl" ,"ms" ,"l" ,"f" ,14 ,0 ,14 ,14 ,1 ,24 ,1 ,24 ,7 ,7 ,), "f", 0, NPC_REGULAR), + 1317 : (1813, lnames[1317],("hss" ,"ld" ,"l" ,"f" ,7 ,0 ,7 ,7 ,1 ,25 ,1 ,25 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 1318 : (1814, lnames[1318],("cll" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,12 ,0 ,12 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 1319 : (1815, lnames[1319],("csl" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,0 ,18 ,), "m", 0, NPC_REGULAR), + 1320 : (1818, lnames[1320], "r", "m", 0, NPC_REGULAR), + 1321 : (1819, lnames[1321],("dsl" ,"md" ,"l" ,"f" ,22 ,0 ,22 ,22 ,0 ,27 ,0 ,27 ,7 ,5 ,), "f", 0, NPC_REGULAR), + 1322 : (1820, lnames[1322],("dss" ,"ls" ,"m" ,"f" ,13 ,0 ,13 ,13 ,0 ,0 ,0 ,0 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 1323 : (1821, lnames[1323],("fll" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,0 ,0 ,0 ,1 ,2 ,), "m", 0, NPC_REGULAR), + 1324 : (1823, lnames[1324],("fsl" ,"md" ,"m" ,"f" ,22 ,0 ,22 ,22 ,0 ,1 ,0 ,1 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 1325 : (1824, lnames[1325], "r", "m", 0, NPC_REGULAR), + 1326 : (1825, lnames[1326],("rll" ,"ms" ,"m" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 1327 : (1829, lnames[1327], "r", "f", 0, NPC_REGULAR), + 1328 : (1830, lnames[1328],("rls" ,"ms" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,2 ,1 ,2 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 1329 : (1831, lnames[1329],("mls" ,"ms" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,3 ,1 ,3 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 1330 : (1833, lnames[1330],("hsl" ,"ls" ,"m" ,"m" ,19 ,0 ,19 ,19 ,1 ,3 ,1 ,3 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 1331 : (1834, lnames[1331],("hss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,3 ,0 ,3 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 1332 : (1330, lnames[1332],("cll" ,"ms" ,"m" ,"m" ,5 ,0 ,5 ,5 ,0 ,4 ,0 ,4 ,1 ,13 ,), "m", 0, NPC_FISHERMAN), + + # The Brrgh + 3001 : (3506, lnames[3001], "r", "f", 0, NPC_REGULAR), + 3002 : (3508, lnames[3002],("cls" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,1 ,9 ,1 ,9 ,0 ,18 ,), "m", 0, NPC_HQ), + 3003 : (3508, lnames[3003],("dsl" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,22 ,1 ,22 ,25 ,27 ,), "f", 0, NPC_HQ), + 3004 : (3508, lnames[3004],("dss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,10 ,1 ,10 ,0 ,12 ,), "m", 0, NPC_HQ), + 3005 : (3508, lnames[3005],("fll" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,0 ,9 ,), "m", 0, NPC_HQ), + 3006 : (3507, lnames[3006],("fsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,11 ,0 ,11 ,0 ,2 ,), "m", 0, NPC_CLERK), + 3007 : (3507, lnames[3007],("fls" ,"ld" ,"m" ,"f" ,12 ,0 ,12 ,12 ,0 ,25 ,0 ,25 ,8 ,5 ,), "f", 0, NPC_CLERK), + 3008 : (3509, lnames[3008],("rll" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,0 ,17 ,), "m", 0, NPC_TAILOR), + 3009 : (3000, lnames[3009],("rss" ,"ls" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,26 ,0 ,26 ,4 ,23 ,), "f", 0, NPC_FISHERMAN), + 3010 : (3511, lnames[3010],("rls" ,"ss" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,0 ,11 ,), "m", 0, NPC_PETCLERK), + 3011 : (3511, lnames[3011],("mls" ,"md" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,26 ,1 ,26 ,26 ,27 ,), "f", 0, NPC_PETCLERK), + 3012 : (3511, lnames[3012],("hsl" ,"ms" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,12 ,1 ,12 ,0 ,1 ,), "m", 0, NPC_PETCLERK), + 3013 : (3000, lnames[3013], ("cls", "ss", "m", "m", 18, 0, 17, 17, 1, 7, 1, 7, 1, 9), "m", 1, NPC_PARTYPERSON), + 3014 : (3000, lnames[3014], ("css", "sd", "m", "f", 17, 0, 16, 16, 0, 24, 0, 24, 0, 9), "f", 1, NPC_PARTYPERSON), + + # Walrus Way + 3101 : (3611, lnames[3101],("mls" ,"ls" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,1 ,1 ,1 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 3102 : (3625, lnames[3102], "r", "f", 0, NPC_REGULAR), + 3103 : (3641, lnames[3103], "r", "m", 0, NPC_REGULAR), + 3104 : (3602, lnames[3104],("cll" ,"ss" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,4 ,0 ,4 ,3 ,2 ,), "f", 0, NPC_REGULAR), + 3105 : (3651, lnames[3105], "r", "m", 0, NPC_REGULAR), + 3106 : (3636, lnames[3106], ('fll', 'ls', 'l', 'm', 8,2,8,8,10,27,0,27,7,11), "m", 0, NPC_REGULAR), + 3107 : (3630, lnames[3107],("dll" ,"ms" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,4 ,4 ,), "f", 0, NPC_REGULAR), + 3108 : (3638, lnames[3108], "r", "m", 0, NPC_REGULAR), + 3109 : (3637, lnames[3109],("fll" ,"sd" ,"m" ,"f" ,23 ,0 ,23 ,23 ,1 ,6 ,1 ,6 ,12 ,27 ,), "f", 0, NPC_REGULAR), + 3110 : (3629, lnames[3110], ('fss', 'ms', 'l', 'm', 10,10,10,10,16,4,0,4,5,4), "m", 0, NPC_REGULAR), + 3111 : (3627, lnames[3111], ('dsl', 'ls', 's', 'm', 6,0,6,6,14,27,10,27,1,14), "m", 1, NPC_REGULAR), + 3112 : (3607, lnames[3112],("rll" ,"ls" ,"m" ,"m" ,21 ,0 ,21 ,21 ,1 ,5 ,1 ,5 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 3113 : (3618, lnames[3113],("rss" ,"ms" ,"m" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,0 ,2 ,), "m", 0, NPC_REGULAR), + 3114 : (3620, lnames[3114],("rls" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,6 ,0 ,6 ,0 ,20 ,), "m", 0, NPC_REGULAR), + 3115 : (3654, lnames[3115],("mls" ,"ls" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,7 ,0 ,7 ,0 ,17 ,), "m", 0, NPC_HQ), + 3116 : (3654, lnames[3116],("hll" ,"ls" ,"m" ,"f" ,14 ,0 ,14 ,14 ,0 ,11 ,0 ,11 ,0 ,12 ,), "f", 0, NPC_HQ), + 3117 : (3654, lnames[3117],("hss" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,7 ,0 ,7 ,0 ,11 ,), "m", 0, NPC_HQ), + 3118 : (3654, lnames[3118],("cll" ,"ls" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,8 ,0 ,8 ,0 ,6 ,), "m", 0, NPC_HQ), + 3119 : (3653, lnames[3119],("csl" ,"ms" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,8 ,0 ,8 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 3120 : (3610, lnames[3120],("cls" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,1 ,8 ,1 ,8 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 3121 : (3601, lnames[3121],("dll" ,"ls" ,"m" ,"m" ,20 ,0 ,20 ,20 ,1 ,8 ,1 ,8 ,0 ,16 ,), "m", 0, NPC_REGULAR), + 3122 : (3608, lnames[3122],("dss" ,"md" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,21 ,1 ,21 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 3123 : (3612, lnames[3123],("dls" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,0 ,10 ,), "m", 0, NPC_REGULAR), + 3124 : (3613, lnames[3124],("fsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,9 ,1 ,9 ,0 ,4 ,), "m", 0, NPC_REGULAR), + 3125 : (3614, lnames[3125],("fls" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,9 ,1 ,9 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 3126 : (3615, lnames[3126],("rll" ,"ls" ,"l" ,"f" ,6 ,0 ,6 ,6 ,0 ,24 ,0 ,24 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 3127 : (3617, lnames[3127],("rss" ,"ms" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,24 ,0 ,24 ,19 ,27 ,), "f", 0, NPC_REGULAR), + 3128 : (3621, lnames[3128],("rls" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,11 ,0 ,11 ,0 ,12 ,), "m", 0, NPC_REGULAR), + 3129 : (3623, lnames[3129],("mls" ,"sd" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,25 ,0 ,25 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 3130 : (3624, lnames[3130],("hll" ,"ms" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,26 ,0 ,26 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 3131 : (3634, lnames[3131],("hss" ,"ls" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,12 ,0 ,12 ,0 ,20 ,), "m", 0, NPC_REGULAR), + 3132 : (3635, lnames[3132], "r", "f", 0, NPC_REGULAR), + 3133 : (3642, lnames[3133],("csl" ,"ms" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,12 ,1 ,12 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 3134 : (3643, lnames[3134],("cls" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,0 ,1 ,0 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 3135 : (3644, lnames[3135],("dll" ,"md" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,0 ,1 ,0 ,4 ,12 ,), "f", 0, NPC_REGULAR), + 3136 : (3647, lnames[3136],("dss" ,"ls" ,"l" ,"f" ,19 ,0 ,19 ,19 ,1 ,1 ,1 ,1 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 3137 : (3648, lnames[3137],("dls" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,1 ,1 ,1 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 3138 : (3649, lnames[3138],("fsl" ,"ld" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 3139 : (3650, lnames[3139],("fss" ,"sd" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,2 ,0 ,2 ,16 ,27 ,), "f", 0, NPC_REGULAR), + 3140 : (3136, lnames[3140],("rll" ,"ms" ,"l" ,"f" ,11 ,0 ,11 ,11 ,0 ,3 ,0 ,3 ,12 ,27 ,), "f", 0, NPC_FISHERMAN), + + # Sleet Street + 3201 : (3715, lnames[3201], "r", "f", 0, NPC_REGULAR), + 3202 : (3723, lnames[3202],("rsl" ,"ss" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,12 ,1 ,12 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 3203 : (3712, lnames[3203],("rss" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,12 ,1 ,12 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 3204 : (3734, lnames[3204],("mss" ,"md" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,26 ,1 ,26 ,4 ,5 ,), "f", 0, NPC_REGULAR), + 3205 : (3721, lnames[3205], "r", "m", 0, NPC_REGULAR), + 3206 : (3722, lnames[3206],("hsl" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,0 ,1 ,0 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 3207 : (3713, lnames[3207],("hss" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,0 ,0 ,0 ,1 ,15 ,), "m", 0, NPC_REGULAR), + 3208 : (3732, lnames[3208],("cll" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,1 ,0 ,1 ,1 ,12 ,), "m", 0, NPC_REGULAR), + 3209 : (3737, lnames[3209],("css" ,"ss" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,1 ,0 ,1 ,1 ,9 ,), "m", 0, NPC_REGULAR), + # Simian Sam is now a monkey! + #3210 : (3728, lnames[3210],("pls" ,"ls" ,"s" ,"m" ,13 ,0 ,13 ,13 ,2 ,1 ,2 ,1 ,5 ,2 ,), "m", 0, NPC_REGULAR), + 3210 : (3728, lnames[3210], ('pls', 'ls', 's', 'm', 13,0,13,13,2,1,2,1,5,2), "m", 0, NPC_REGULAR), + 3211 : (3710, lnames[3211], "r", "f", 0, NPC_REGULAR), + 3212 : (3707, lnames[3212],("dss" ,"ss" ,"s" ,"m" ,19 ,0 ,19 ,19 ,0 ,2 ,0 ,2 ,1 ,17 ,), "m", 0, NPC_REGULAR), + 3213 : (3739, lnames[3213],("fll" ,"ls" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,1 ,14 ,), "m", 0, NPC_HQ), + 3214 : (3739, lnames[3214],("fsl" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,3 ,1 ,), "f", 0, NPC_HQ), + 3215 : (3739, lnames[3215],("fls" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,3 ,1 ,3 ,1 ,6 ,), "m", 0, NPC_HQ), + 3216 : (3739, lnames[3216],("rll" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,4 ,1 ,4 ,1 ,1 ,), "m", 0, NPC_HQ), + # At some point Sweaty Pete accidentally became female + # We'll just change him/her back to male + 3217 : (3738, lnames[3217],("rss" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 3218 : (3702, lnames[3218],("mss" ,"ms" ,"s" ,"m" ,18 ,0 ,18 ,18 ,1 ,4 ,1 ,4 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 3219 : (3705, lnames[3219],("mls" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,0 ,5 ,0 ,5 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 3220 : (3706, lnames[3220],("hsl" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 3221 : (3708, lnames[3221],("hss" ,"sd" ,"s" ,"f" ,19 ,0 ,19 ,19 ,0 ,8 ,0 ,8 ,7 ,12 ,), "f", 0, NPC_REGULAR), + 3222 : (3716, lnames[3222], "r", "f", 0, NPC_REGULAR), + 3223 : (3718, lnames[3223],("csl" ,"ls" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,6 ,0 ,6 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 3224 : (3719, lnames[3224],("cls" ,"md" ,"m" ,"f" ,18 ,0 ,18 ,18 ,0 ,9 ,0 ,9 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 3225 : (3724, lnames[3225], "r", "m", 0, NPC_REGULAR), + 3226 : (3725, lnames[3226],("dss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,7 ,1 ,7 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 3227 : (3726, lnames[3227],("fll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,7 ,1 ,7 ,1 ,2 ,), "m", 0, NPC_REGULAR), + 3228 : (3730, lnames[3228],("fsl" ,"ls" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,12 ,1 ,12 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 3229 : (3731, lnames[3229],("fls" ,"ms" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,12 ,1 ,12 ,0 ,7 ,), "f", 0, NPC_REGULAR), + 3230 : (3735, lnames[3230],("rll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,8 ,1 ,8 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 3231 : (3736, lnames[3231],("rss" ,"ms" ,"m" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,1 ,12 ,), "m", 0, NPC_REGULAR), + 3232 : (3236, lnames[3232],("rls" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,10 ,0 ,10 ,1 ,9 ,), "m", 0, NPC_FISHERMAN), + + # Polar Place + 3301 : (3810, lnames[3301],("dsl" ,"ms" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,22 ,0 ,22 ,2 ,11 ,), "f", 0, NPC_REGULAR), + 3302 : (3806, lnames[3302],("dls" ,"ls" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,10 ,0 ,10 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 3303 : (3830, lnames[3303],("fll" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,10 ,0 ,10 ,1 ,19 ,), "m", 0, NPC_REGULAR), + # Yeti Eddie + 3304 : (3828, lnames[3304], ('pll', 'ls', 'l', 'm', 0,0,0,0,1,5,1,5,1,6), "f", 0, NPC_REGULAR), + 3305 : (3812, lnames[3305],("fls" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,11 ,0 ,11 ,1 ,13 ,), "m", 0, NPC_REGULAR), + # Paula Behr + 3306 : (3821, lnames[3306], ('bss', 'sd', 'm', 'f', 0,0,0,0,31,27,22,27,8,11), "f", 0, NPC_REGULAR), + 3307 : (3329, lnames[3307],("rss" ,"ls" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,24 ,1 ,24 ,1 ,9 ,), "f", 0, NPC_FISHERMAN), + 3308 : (3815, lnames[3308],("mss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,11 ,1 ,11 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 3309 : (3826, lnames[3309],("hll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,11 ,1 ,11 ,1 ,18 ,), "m", 0, NPC_REGULAR), + # Professor Flake + 3310 : (3823, lnames[3310], ('pll', 'ms', 'm', 'm', 10,0,10,10,60,27,49,27,0,13), "m", 0, NPC_REGULAR), + 3311 : (3829, lnames[3311], "r", "f", 0, NPC_REGULAR), + # March Harry + 3312 : (3813, lnames[3312], ('rss', 'ms', 'l', 'm', 4,0,4,4,5,2,5,2,1,10), "m", 0, NPC_REGULAR), + # Toon HQ + 3313 : (3801, lnames[3313],("css" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,0 ,0 ,0 ,1 ,2 ,), "m", 0, NPC_HQ), + 3314 : (3801, lnames[3314],("cls" ,"ms" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,0 ,0 ,0 ,3 ,25 ,), "f", 0, NPC_HQ), + 3315 : (3801, lnames[3315],("dsl" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,0 ,0 ,0 ,1 ,17 ,), "m", 0, NPC_HQ), + 3316 : (3801, lnames[3316],("dss" ,"md" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,1 ,0 ,1 ,10 ,27 ,), "f", 0, NPC_HQ), + 3317 : (3816, lnames[3317],("fll" ,"ls" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,2 ,0 ,2 ,3 ,24 ,), "f", 0, NPC_REGULAR), + # Johnny Cashmere + 3318 : (3808, lnames[3318], ('dss', 'ms', 'm', 'm', 18,0,18,18,57,1,46,1,12,1), "m", 0, NPC_REGULAR), + 3319 : (3825, lnames[3319],("fls" ,"ls" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,2 ,1 ,2 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 3320 : (3814, lnames[3320],("rsl" ,"ls" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,3 ,1 ,3 ,12 ,27 ,), "f", 0, NPC_REGULAR), + 3321 : (3818, lnames[3321],("rss" ,"ss" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,2 ,1 ,2 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 3322 : (3819, lnames[3322], "r", "m", 0, NPC_REGULAR), + 3323 : (3811, lnames[3323],("mls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,3 ,1 ,3 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 3324 : (3809, lnames[3324], "r", "m", 0, NPC_REGULAR), + 3325 : (3827, lnames[3325],("hss" ,"ls" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,4 ,0 ,4 ,1 ,0 ,), "m", 0, NPC_REGULAR), + 3326 : (3820, lnames[3326],("cll" ,"md" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,6 ,0 ,6 ,12 ,27 ,), "f", 0, NPC_REGULAR), + 3327 : (3824, lnames[3327],("css" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,1 ,15 ,), "m", 0, NPC_REGULAR), + 3328 : (3807, lnames[3328],("dll" ,"sd" ,"l" ,"f" ,8 ,0 ,8 ,8 ,0 ,25 ,0 ,25 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 3329 : (3817, lnames[3329],("dll" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,1 ,0 ,1 ,1 ,1 ,), "m", 0, NPC_REGULAR), + + # Minnie's Melody Land + 4001 : (4502, lnames[4001], "r", "f", 0, NPC_REGULAR), + 4002 : (4504, lnames[4002],("fll" ,"ss" ,"m" ,"m" ,5 ,0 ,5 ,5 ,0 ,2 ,0 ,2 ,1 ,17 ,), "m", 0, NPC_HQ), + 4003 : (4504, lnames[4003],("fsl" ,"md" ,"m" ,"f" ,21 ,0 ,21 ,21 ,0 ,3 ,0 ,3 ,10 ,27 ,), "f", 0, NPC_HQ), + 4004 : (4504, lnames[4004],("fls" ,"ls" ,"m" ,"f" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,2 ,11 ,), "f", 0, NPC_HQ), + 4005 : (4504, lnames[4005],("rll" ,"ss" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,24 ,27 ,), "f", 0, NPC_HQ), + 4006 : (4503, lnames[4006],("rss" ,"md" ,"m" ,"f" ,21 ,0 ,21 ,21 ,1 ,4 ,1 ,4 ,8 ,8 ,), "f", 0, NPC_CLERK), + 4007 : (4503, lnames[4007],("rls" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,3 ,1 ,3 ,1 ,19 ,), "m", 0, NPC_CLERK), + 4008 : (4506, lnames[4008],("mls" ,"ms" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,7 ,9 ,), "f", 0, NPC_TAILOR), + 4009 : (4000, lnames[4009],("hsl" ,"ld" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,6 ,1 ,6 ,12 ,27 ,), "f", 0, NPC_FISHERMAN), + 4010 : (4508, lnames[4010],("hss" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,5 ,0 ,5 ,1 ,10 ,), "m", 0, NPC_PETCLERK), + 4011 : (4508, lnames[4011],("cll" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,1 ,4 ,), "m", 0, NPC_PETCLERK), + 4012 : (4508, lnames[4012],("csl" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,8 ,0 ,8 ,10 ,27 ,), "f", 0, NPC_PETCLERK), + 4013 : (4000, lnames[4013], ("bll", "ls", "s", "m", 3, 0, 19, 19, 0, 8, 0, 8, 1, 12), "m", 1, NPC_PARTYPERSON), + 4014 : (4000, lnames[4014], ("bss", "md", "m", "f", 24, 0, 19, 19, 0, 24, 0, 24, 0, 12), "f", 1, NPC_PARTYPERSON), + + 4101 : (4603, lnames[4101],("cll" ,"ms" ,"m" ,"m" ,16 ,0 ,16 ,16 ,1 ,7 ,1 ,7 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 4102 : (4605, lnames[4102],("csl" ,"ms" ,"m" ,"f" ,9 ,0 ,9 ,9 ,1 ,11 ,1 ,11 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 4103 : (4612, lnames[4103],("cls" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,1 ,8 ,1 ,8 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 4104 : (4659, lnames[4104],("dll" ,"ms" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,8 ,1 ,8 ,0 ,16 ,), "m", 0, NPC_HQ), + 4105 : (4659, lnames[4105],("dss" ,"ls" ,"l" ,"f" ,9 ,0 ,9 ,9 ,1 ,21 ,1 ,21 ,11 ,27 ,), "f", 0, NPC_HQ), + 4106 : (4659, lnames[4106],("fll" ,"ss" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,22 ,0 ,22 ,19 ,27 ,), "f", 0, NPC_HQ), + 4107 : (4659, lnames[4107],("fsl" ,"md" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,22 ,0 ,22 ,17 ,27 ,), "f", 0, NPC_HQ), + 4108 : (4626, lnames[4108],("fls" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,10 ,0 ,10 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 4109 : (4606, lnames[4109],("rll" ,"ss" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,11 ,0 ,11 ,0 ,18 ,), "m", 0, NPC_REGULAR), + 4110 : (4604, lnames[4110],("rss" ,"ld" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,24 ,0 ,24 ,3 ,2 ,), "f", 0, NPC_REGULAR), + 4111 : (4607, lnames[4111], "r", "m", 0, NPC_REGULAR), + 4112 : (4609, lnames[4112],("mls" ,"ms" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,25 ,0 ,25 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 4113 : (4610, lnames[4113],("hsl" ,"ld" ,"l" ,"f" ,15 ,0 ,15 ,15 ,1 ,25 ,1 ,25 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 4114 : (4611, lnames[4114],("hss" ,"ms" ,"l" ,"m" ,7 ,0 ,7 ,7 ,1 ,11 ,1 ,11 ,1 ,20 ,), "m", 0, NPC_REGULAR), + 4115 : (4614, lnames[4115],("cll" ,"ls" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,26 ,1 ,26 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 4116 : (4615, lnames[4116],("csl" ,"ss" ,"m" ,"m" ,15 ,0 ,15 ,15 ,1 ,12 ,1 ,12 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 4117 : (4617, lnames[4117],("cls" ,"md" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,0 ,1 ,0 ,1 ,25 ,), "f", 0, NPC_REGULAR), + 4118 : (4618, lnames[4118], "r", "m", 0, NPC_REGULAR), + 4119 : (4619, lnames[4119],("dss" ,"ss" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,0 ,0 ,0 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 4120 : (4622, lnames[4120],("dls" ,"ld" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,1 ,0 ,1 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4121 : (4623, lnames[4121],("fsl" ,"ms" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,1 ,0 ,1 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 4122 : (4625, lnames[4122],("fls" ,"ms" ,"m" ,"f" ,14 ,0 ,14 ,14 ,0 ,2 ,0 ,2 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 4123 : (4628, lnames[4123],("rll" ,"ls" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,2 ,0 ,2 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 4124 : (4629, lnames[4124],("rss" ,"ms" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,2 ,0 ,2 ,1 ,4 ,), "m", 0, NPC_REGULAR), + 4125 : (4630, lnames[4125],("rls" ,"ls" ,"m" ,"f" ,14 ,0 ,14 ,14 ,1 ,3 ,1 ,3 ,8 ,6 ,), "f", 0, NPC_REGULAR), + 4126 : (4631, lnames[4126],("mls" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,1 ,3 ,1 ,3 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 4127 : (4632, lnames[4127],("hll" ,"md" ,"m" ,"f" ,22 ,0 ,22 ,22 ,1 ,4 ,1 ,4 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 4128 : (4635, lnames[4128],("hss" ,"ms" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,1 ,12 ,), "m", 0, NPC_REGULAR), + 4129 : (4637, lnames[4129],("hls" ,"ss" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,5 ,1 ,5 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4130 : (4638, lnames[4130], "r", "m", 0, NPC_REGULAR), + 4131 : (4639, lnames[4131], "r", "m", 0, NPC_REGULAR), + 4132 : (4641, lnames[4132],("dll" ,"ms" ,"l" ,"f" ,6 ,0 ,6 ,6 ,0 ,7 ,0 ,7 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 4133 : (4642, lnames[4133],("dss" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,5 ,0 ,5 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 4134 : (4645, lnames[4134], "r", "m", 0, NPC_REGULAR), + 4135 : (4648, lnames[4135],("fsl" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,6 ,0 ,6 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 4136 : (4652, lnames[4136],("fss" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,9 ,0 ,9 ,7 ,4 ,), "f", 0, NPC_REGULAR), + 4137 : (4654, lnames[4137],("rll" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 4138 : (4655, lnames[4138],("rsl" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,7 ,1 ,7 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 4139 : (4657, lnames[4139],("rls" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,11 ,1 ,11 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 4140 : (4658, lnames[4140],("mls" ,"ls" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,7 ,1 ,7 ,1 ,10 ,), "m", 0, NPC_REGULAR), + 4141 : (4148, lnames[4141],("hll" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,8 ,1 ,8 ,1 ,4 ,), "m", 0, NPC_FISHERMAN), + + 4201 : (4704, lnames[4201],("mss" ,"ss" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,6 ,0 ,6 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 4202 : (4725, lnames[4202],("mls" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,5 ,0 ,5 ,0 ,13 ,), "m", 0, NPC_REGULAR), + 4203 : (4702, lnames[4203],("hsl" ,"ms" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,5 ,0 ,5 ,0 ,10 ,), "m", 0, NPC_REGULAR), + 4204 : (4739, lnames[4204],("hss" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,6 ,0 ,6 ,0 ,4 ,), "m", 0, NPC_HQ), + 4205 : (4739, lnames[4205],("cll" ,"ld" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,8 ,1 ,8 ,10 ,27 ,), "f", 0, NPC_HQ), + 4206 : (4739, lnames[4206],("css" ,"sd" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,8 ,1 ,8 ,25 ,27 ,), "f", 0, NPC_HQ), + 4207 : (4739, lnames[4207],("cls" ,"ls" ,"l" ,"f" ,14 ,0 ,14 ,14 ,1 ,9 ,1 ,9 ,17 ,27 ,), "f", 0, NPC_HQ), + 4208 : (4730, lnames[4208],("dsl" ,"ss" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 4209 : (4701, lnames[4209],("dss" ,"md" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,11 ,1 ,11 ,1 ,9 ,), "f", 0, NPC_REGULAR), + 4211 : (4703, lnames[4211],("fsl" ,"ss" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,8 ,1 ,8 ,0 ,20 ,), "m", 0, NPC_REGULAR), + 4212 : (4705, lnames[4212],("fls" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,9 ,0 ,9 ,0 ,17 ,), "m", 0, NPC_REGULAR), + 4213 : (4707, lnames[4213],("rll" ,"sd" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,21 ,0 ,21 ,24 ,27 ,), "f", 0, NPC_REGULAR), + 4214 : (4709, lnames[4214], "r", "f", 0, NPC_REGULAR), + 4215 : (4710, lnames[4215],("mss" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,10 ,0 ,10 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 4216 : (4712, lnames[4216],("mls" ,"ms" ,"s" ,"m" ,13 ,0 ,13 ,13 ,0 ,10 ,0 ,10 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 4217 : (4713, lnames[4217],("hsl" ,"ms" ,"s" ,"m" ,5 ,0 ,5 ,5 ,0 ,10 ,0 ,10 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 4218 : (4716, lnames[4218],("hss" ,"ss" ,"s" ,"f" ,21 ,0 ,21 ,21 ,1 ,23 ,1 ,23 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4219 : (4717, lnames[4219], "r", "m", 0, NPC_REGULAR), + 4220 : (4718, lnames[4220], "r", "m", 0, NPC_REGULAR), + 4221 : (4719, lnames[4221],("cls" ,"ss" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,11 ,1 ,11 ,0 ,4 ,), "m", 0, NPC_REGULAR), + 4222 : (4720, lnames[4222],("dsl" ,"ls" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,11 ,1 ,11 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 4223 : (4722, lnames[4223],("dss" ,"sd" ,"s" ,"f" ,3 ,0 ,3 ,3 ,1 ,25 ,1 ,25 ,24 ,27 ,), "f", 0, NPC_REGULAR), + 4224 : (4723, lnames[4224], "r", "m", 0, NPC_REGULAR), + 4225 : (4724, lnames[4225],("fsl" ,"ld" ,"s" ,"f" ,12 ,0 ,12 ,12 ,0 ,27 ,0 ,27 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 4226 : (4727, lnames[4226],("fls" ,"sd" ,"s" ,"f" ,3 ,0 ,3 ,3 ,0 ,0 ,0 ,0 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 4227 : (4728, lnames[4227],("rll" ,"ls" ,"s" ,"f" ,19 ,0 ,19 ,19 ,0 ,0 ,0 ,0 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 4228 : (4729, lnames[4228],("rss" ,"ss" ,"s" ,"f" ,11 ,0 ,11 ,11 ,0 ,1 ,0 ,1 ,0 ,1 ,), "f", 0, NPC_REGULAR), + 4229 : (4731, lnames[4229],("rls" ,"md" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,1 ,0 ,1 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4230 : (4732, lnames[4230],("mls" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,1 ,1 ,1 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 4231 : (4735, lnames[4231],("hsl" ,"ss" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,2 ,1 ,2 ,8 ,0 ,), "f", 0, NPC_REGULAR), + 4232 : (4736, lnames[4232],("hss" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,1 ,6 ,), "m", 0, NPC_REGULAR), + 4233 : (4737, lnames[4233],("cll" ,"ms" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,2 ,1 ,2 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 4234 : (4738, lnames[4234], "r", "m", 0, NPC_REGULAR), + 4235 : (4240, lnames[4235],("cls" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,1 ,16 ,), "m", 0, NPC_FISHERMAN), + + 4301 : (4819, lnames[4301],("fss" ,"md" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 4302 : (4821, lnames[4302],("fls" ,"ls" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,2 ,3 ,), "f", 0, NPC_REGULAR), + 4303 : (4853, lnames[4303],("rsl" ,"ss" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,2 ,1 ,2 ,0 ,18 ,), "m", 0, NPC_REGULAR), + 4304 : (4873, lnames[4304],("rss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,2 ,0 ,2 ,0 ,15 ,), "m", 0, NPC_HQ), + 4305 : (4873, lnames[4305],("mss" ,"sd" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,4 ,0 ,4 ,26 ,27 ,), "f", 0, NPC_HQ), + 4306 : (4873, lnames[4306],("hll" ,"ms" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,5 ,0 ,5 ,4 ,25 ,), "f", 0, NPC_HQ), + 4307 : (4873, lnames[4307],("hsl" ,"ld" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,5 ,0 ,5 ,17 ,27 ,), "f", 0, NPC_HQ), + 4308 : (4835, lnames[4308], ('css', 'md', 'm', 'f', 6,0,6,6,3,5,3,5,0,14), "f", 0, NPC_REGULAR), + 4309 : (4801, lnames[4309],("cll" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,0 ,17 ,), "m", 0, NPC_REGULAR), + 4310 : (4803, lnames[4310], "r", "f", 0, NPC_REGULAR), + 4311 : (4804, lnames[4311], "r", "f", 0, NPC_REGULAR), + 4312 : (4807, lnames[4312],("dsl" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,5 ,1 ,5 ,0 ,9 ,), "m", 0, NPC_REGULAR), + 4313 : (4809, lnames[4313],("dss" ,"ss" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,0 ,2 ,), "m", 0, NPC_REGULAR), + 4314 : (4817, lnames[4314],("fll" ,"ld" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,8 ,1 ,8 ,12 ,27 ,), "f", 0, NPC_REGULAR), + 4315 : (4827, lnames[4315],("fss" ,"sd" ,"m" ,"f" ,18 ,0 ,18 ,18 ,1 ,9 ,1 ,9 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4316 : (4828, lnames[4316],("fls" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,6 ,1 ,6 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 4317 : (4829, lnames[4317],("rsl" ,"ls" ,"l" ,"m" ,3 ,0 ,3 ,3 ,0 ,7 ,0 ,7 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 4318 : (4836, lnames[4318],("rss" ,"ms" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,8 ,0 ,8 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 4319 : (4838, lnames[4319],("mss" ,"ls" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,1 ,23 ,), "f", 0, NPC_REGULAR), + 4320 : (4840, lnames[4320],("mls" ,"ss" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,21 ,0 ,21 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 4321 : (4841, lnames[4321],("hsl" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,9 ,0 ,9 ,0 ,16 ,), "m", 0, NPC_REGULAR), + 4322 : (4842, lnames[4322],("hls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,0 ,13 ,), "m", 0, NPC_REGULAR), + 4323 : (4844, lnames[4323],("cll" ,"ss" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,21 ,1 ,21 ,24 ,27 ,), "f", 0, NPC_REGULAR), + 4324 : (4845, lnames[4324],("css" ,"ld" ,"l" ,"f" ,17 ,0 ,17 ,17 ,1 ,22 ,1 ,22 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 4325 : (4848, lnames[4325],("cls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,9 ,1 ,9 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 4326 : (4850, lnames[4326],("dsl" ,"ms" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,23 ,1 ,23 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 4327 : (4852, lnames[4327],("dss" ,"ld" ,"l" ,"f" ,16 ,0 ,16 ,16 ,1 ,23 ,1 ,23 ,7 ,1 ,), "f", 0, NPC_REGULAR), + 4328 : (4854, lnames[4328],("fll" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,1 ,11 ,1 ,11 ,0 ,12 ,), "m", 0, NPC_REGULAR), + 4329 : (4855, lnames[4329],("fsl" ,"ls" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,25 ,0 ,25 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 4330 : (4862, lnames[4330], "r", "m", 0, NPC_REGULAR), + 4331 : (4867, lnames[4331], "r", "m", 0, NPC_REGULAR), + 4332 : (4870, lnames[4332],("rss" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,27 ,0 ,27 ,0 ,17 ,), "m", 0, NPC_REGULAR), + 4333 : (4871, lnames[4333],("mss" ,"ss" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,27 ,0 ,27 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 4334 : (4872, lnames[4334],("mls" ,"ls" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,0 ,0 ,0 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 4335 : (4345, lnames[4335],("hsl" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,0 ,1 ,0 ,0 ,6 ,), "m", 0, NPC_FISHERMAN), + + # Daisy Gardens + 5001 : (5502, lnames[5001],("fls" ,"ls" ,"s" ,"m" ,14 ,0 ,14 ,14 ,1 ,7 ,1 ,7 ,0 ,20 ,), "m", 0, NPC_HQ), + 5002 : (5502, lnames[5002],("rll" ,"ms" ,"s" ,"m" ,6 ,0 ,6 ,6 ,0 ,8 ,0 ,8 ,0 ,17 ,), "m", 0, NPC_HQ), + 5003 : (5502, lnames[5003],("rss" ,"ms" ,"s" ,"f" ,22 ,0 ,22 ,22 ,0 ,12 ,0 ,12 ,26 ,27 ,), "f", 0, NPC_HQ), + 5004 : (5502, lnames[5004],("rls" ,"ld" ,"s" ,"f" ,13 ,0 ,13 ,13 ,0 ,21 ,0 ,21 ,4 ,11 ,), "f", 0, NPC_HQ), + 5005 : (5501, lnames[5005],("mls" ,"md" ,"s" ,"f" ,6 ,0 ,6 ,6 ,0 ,21 ,0 ,21 ,2 ,3 ,), "f", 0, NPC_CLERK), + 5006 : (5501, lnames[5006],("hsl" ,"ms" ,"s" ,"m" ,20 ,0 ,20 ,20 ,0 ,9 ,0 ,9 ,0 ,1 ,), "m", 0, NPC_CLERK), + 5007 : (5503, lnames[5007],("hss" ,"ss" ,"s" ,"f" ,13 ,0 ,13 ,13 ,0 ,22 ,0 ,22 ,3 ,2 ,), "f", 0, NPC_TAILOR), + 5008 : (5000, lnames[5008],("cll" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,1 ,22 ,1 ,22 ,19 ,27 ,), "f", 0, NPC_FISHERMAN), + 5009 : (5505, lnames[5009],("csl" ,"ls" ,"m" ,"f" ,21 ,0 ,21 ,21 ,1 ,23 ,1 ,23 ,8 ,23 ,), "f", 0, NPC_PETCLERK), + 5010 : (5505, lnames[5010],("cls" ,"ss" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,10 ,1 ,10 ,0 ,10 ,), "m", 0, NPC_PETCLERK), + 5011 : (5505, lnames[5011],("dll" ,"ls" ,"m" ,"m" ,5 ,0 ,5 ,5 ,1 ,10 ,1 ,10 ,0 ,4 ,), "m", 0, NPC_PETCLERK), + 5012 : (5000, lnames[5012], ("dls", "ms", "m", "m", 13, 0, 12, 12, 0, 1, 0, 1, 0, 6), "m", 1, NPC_PARTYPERSON), + 5013 : (5000, lnames[5013], ("dss", "md", "m", "f", 1, 0, 3, 3, 1, 5, 1, 5, 0, 5), "f", 1, NPC_PARTYPERSON), + + # Elm Street + 5101 : (5602, lnames[5101],("dsl" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,4 ,1 ,4 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 5102 : (5610, lnames[5102], "r", "f", 0, NPC_REGULAR), + 5103 : (5615, lnames[5103],("fll" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,5 ,1 ,5 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 5104 : (5617, lnames[5104],("fsl" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,0 ,19 ,), "m", 0, NPC_REGULAR), + 5105 : (5619, lnames[5105], "r", "m", 0, NPC_REGULAR), + 5106 : (5613, lnames[5106],("rsl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,6 ,1 ,6 ,0 ,13 ,), "m", 0, NPC_REGULAR), + 5107 : (5607, lnames[5107], "r", "m", 0, NPC_REGULAR), + 5108 : (5616, lnames[5108],("mss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,11 ,0 ,11 ,24 ,27 ,), "f", 0, NPC_REGULAR), + 5109 : (5627, lnames[5109],("mls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,7 ,0 ,7 ,0 ,0 ,), "m", 1, NPC_HQ), + 5110 : (5627, lnames[5110],("hsl" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,8 ,0 ,8 ,0 ,18 ,), "m", 1, NPC_HQ), + 5111 : (5627, lnames[5111],("hss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,12 ,0 ,12 ,7 ,4 ,), "f", 1, NPC_HQ), + 5112 : (5627, lnames[5112],("cll" ,"ms" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,21 ,0 ,21 ,14 ,27 ,), "f", 1, NPC_HQ), + 5113 : (5601, lnames[5113],("css" ,"ld" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,21 ,0 ,21 ,3 ,2 ,), "f", 0, NPC_REGULAR), + 5114 : (5603, lnames[5114],("cls" ,"ms" ,"l" ,"m" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,0 ,2 ,), "m", 0, NPC_REGULAR), + 5115 : (5604, lnames[5115],("dsl" ,"ms" ,"l" ,"f" ,17 ,0 ,17 ,17 ,1 ,22 ,1 ,22 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 5116 : (5605, lnames[5116],("dss" ,"ls" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,9 ,1 ,9 ,0 ,17 ,), "m", 0, NPC_REGULAR), + 5117 : (5606, lnames[5117],("fll" ,"md" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,23 ,1 ,23 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 5118 : (5608, lnames[5118],("fsl" ,"ms" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,10 ,1 ,10 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 5119 : (5609, lnames[5119],("fls" ,"ss" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,10 ,1 ,10 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 5120 : (5611, lnames[5120],("rsl" ,"ss" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,3 ,1 ,3 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 5121 : (5618, lnames[5121],("rss" ,"ss" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,9 ,1 ,9 ,0 ,25 ,), "f", 0, NPC_REGULAR), + 5122 : (5620, lnames[5122], "r", "m", 0, NPC_REGULAR), + 5123 : (5621, lnames[5123],("mls" ,"sd" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,11 ,1 ,11 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 5124 : (5622, lnames[5124],("hll" ,"ss" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,8 ,0 ,8 ,0 ,4 ,), "m", 0, NPC_REGULAR), + 5125 : (5623, lnames[5125],("hss" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,9 ,0 ,9 ,0 ,0 ,), "m", 0, NPC_REGULAR), + 5126 : (5624, lnames[5126],("hls" ,"sd" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,21 ,0 ,21 ,14 ,27 ,), "f", 0, NPC_REGULAR), + 5127 : (5625, lnames[5127],("csl" ,"ms" ,"m" ,"f" ,23 ,0 ,23 ,23 ,0 ,22 ,0 ,22 ,2 ,2 ,), "f", 0, NPC_REGULAR), + 5128 : (5626, lnames[5128], "r", "f", 0, NPC_REGULAR), + 5129 : (5139, lnames[5129],("dll" ,"md" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,23 ,0 ,23 ,17 ,27 ,), "f", 0, NPC_FISHERMAN), + + # Maple Street + 5201 : (5702, lnames[5201],("hls" ,"ls" ,"l" ,"m" ,15 ,0 ,15 ,15 ,1 ,10 ,1 ,10 ,1 ,16 ,), "m", 0, NPC_REGULAR), + 5202 : (5703, lnames[5202],("cll" ,"ls" ,"l" ,"f" ,7 ,0 ,7 ,7 ,1 ,23 ,1 ,23 ,11 ,27 ,), "f", 0, NPC_REGULAR), + 5203 : (5704, lnames[5203],("css" ,"ss" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,24 ,1 ,24 ,19 ,27 ,), "f", 0, NPC_REGULAR), + 5204 : (5726, lnames[5204],("cls" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,12 ,0 ,12 ,1 ,4 ,), "m", 0, NPC_REGULAR), + 5205 : (5718, lnames[5205], "r", "m", 0, NPC_REGULAR), + 5206 : (5720, lnames[5206],("dss" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,27 ,0 ,27 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 5207 : (5717, lnames[5207],("fll" ,"ld" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 5208 : (5719, lnames[5208],("fsl" ,"sd" ,"l" ,"f" ,7 ,0 ,7 ,7 ,0 ,27 ,0 ,27 ,1 ,12 ,), "f", 0, NPC_REGULAR), + 5209 : (5728, lnames[5209],("fls" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,0 ,0 ,0 ,1 ,9 ,), "m", 1, NPC_HQ), + 5210 : (5728, lnames[5210],("rsl" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,0 ,1 ,0 ,1 ,2 ,), "m", 1, NPC_HQ), + 5211 : (5728, lnames[5211],("rss" ,"md" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,1 ,1 ,1 ,23 ,27 ,), "f", 1, NPC_HQ), + 5212 : (5728, lnames[5212],("mss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,10 ,27 ,), "f", 1, NPC_HQ), + 5213 : (5701, lnames[5213],("mls" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,1 ,1 ,1 ,1 ,14 ,), "m", 0, NPC_REGULAR), + 5214 : (5705, lnames[5214],("hsl" ,"md" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,17 ,27 ,), "f", 0, NPC_REGULAR), + 5215 : (5706, lnames[5215], "r", "f", 0, NPC_REGULAR), + 5216 : (5707, lnames[5216],("cll" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,2 ,0 ,2 ,1 ,1 ,), "m", 0, NPC_REGULAR), + 5217 : (5708, lnames[5217],("csl" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,3 ,0 ,3 ,1 ,19 ,), "m", 0, NPC_REGULAR), + 5218 : (5709, lnames[5218], "r", "m", 0, NPC_REGULAR), + 5219 : (5710, lnames[5219],("dsl" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,3 ,0 ,3 ,1 ,13 ,), "m", 0, NPC_REGULAR), + 5220 : (5711, lnames[5220], "r", "f", 0, NPC_REGULAR), + 5221 : (5712, lnames[5221],("fll" ,"md" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,6 ,0 ,6 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 5222 : (5713, lnames[5222], "r", "f", 0, NPC_REGULAR), + 5223 : (5714, lnames[5223],("fls" ,"ss" ,"s" ,"m" ,5 ,0 ,5 ,5 ,1 ,4 ,1 ,4 ,1 ,18 ,), "m", 0, NPC_REGULAR), + 5224 : (5715, lnames[5224],("rll" ,"ls" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,5 ,1 ,5 ,1 ,15 ,), "m", 0, NPC_REGULAR), + 5225 : (5716, lnames[5225],("rss" ,"sd" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,7 ,1 ,7 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 5226 : (5721, lnames[5226],("rls" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,1 ,9 ,), "m", 0, NPC_REGULAR), + 5227 : (5725, lnames[5227],("mls" ,"ld" ,"s" ,"f" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 5228 : (5727, lnames[5228],("hsl" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,6 ,1 ,6 ,1 ,20 ,), "m", 0, NPC_REGULAR), + 5229 : (5245, lnames[5229],("hss" ,"ms" ,"s" ,"f" ,3 ,0 ,3 ,3 ,0 ,11 ,0 ,11 ,16 ,27 ,), "f", 0, NPC_FISHERMAN), + + # Oak Street + 5301 : (5802, lnames[5301],("rss" ,"ms" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,11 ,0 ,11 ,1 ,12 ,), "f", 1, NPC_HQ), + 5302 : (5802, lnames[5302],("mss" ,"ss" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,17 ,27 ,), "f", 1, NPC_HQ), + 5303 : (5802, lnames[5303],("hll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,1 ,18 ,), "m", 1, NPC_HQ), + 5304 : (5802, lnames[5304],("hsl" ,"ls" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,12 ,1 ,12 ,19 ,27 ,), "f", 1, NPC_HQ), + 5305 : (5804, lnames[5305],("hls" ,"ss" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,21 ,1 ,21 ,16 ,27 ,), "f", 0, NPC_REGULAR), # ("Just Vase It", ""), + 5306 : (5805, lnames[5306], "r", "m", 0, NPC_REGULAR), # ("Snail Mail", ""), + 5307 : (5809, lnames[5307],("css" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,9 ,1 ,9 ,1 ,2 ,), "m", 0, NPC_REGULAR), # ("Fungi Clown School", ""), + 5308 : (5810, lnames[5308],("cls" ,"ms" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,22 ,1 ,22 ,10 ,27 ,), "f", 0, NPC_REGULAR), # ("Honeydew This", ""), + 5309 : (5811, lnames[5309], "r", "f", 0, NPC_REGULAR), # ("Lettuce Inn", ""), + 5310 : (5815, lnames[5310],("dls" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,11 ,0 ,11 ,1 ,14 ,), "m", 0, NPC_REGULAR), # ("Grass Roots", ""), + 5311 : (5817, lnames[5311],("fll" ,"ms" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,24 ,0 ,24 ,12 ,27 ,), "f", 0, NPC_REGULAR), # ("Apples and Oranges", ""), + 5312 : (5819, lnames[5312],("fss" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,12 ,0 ,12 ,1 ,6 ,), "m", 0, NPC_REGULAR), # ("Green Bean Jeans", ""), + 5313 : (5821, lnames[5313],("fls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,1 ,1 ,), "m", 0, NPC_REGULAR), # ("Squash and Stretch Gym", ""), + 5314 : (5826, lnames[5314], "r", "f", 0, NPC_REGULAR), # ("Ant Farming Supplies", ""), + 5315 : (5827, lnames[5315],("rss" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,12 ,1 ,12 ,1 ,16 ,), "m", 0, NPC_REGULAR), # ("Dirt. Cheap.", ""), + 5316 : (5828, lnames[5316],("mss" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,12 ,1 ,12 ,1 ,13 ,), "m", 0, NPC_REGULAR), # ("Couch Potato Furniture", ""), + 5317 : (5830, lnames[5317], "r", "m", 0, NPC_REGULAR), # ("Spill the Beans", ""), + 5318 : (5833, lnames[5318],("hsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,0 ,1 ,0 ,1 ,4 ,), "m", 0, NPC_REGULAR), # ("The Salad Bar", ""), + 5319 : (5835, lnames[5319], "r", "f", 0, NPC_REGULAR), # ("Flower Bed and Breakfast", ""), + 5320 : (5836, lnames[5320],("cll" ,"sd" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,1 ,1 ,1 ,17 ,27 ,), "f", 0, NPC_REGULAR), # ("April's Showers and Tubs", ""), + 5321 : (5837, lnames[5321],("css" ,"ms" ,"m" ,"f" ,18 ,0 ,18 ,18 ,1 ,1 ,1 ,1 ,17 ,27 ,), "f", 0, NPC_REGULAR), # ("School of Vine Arts", ""), + 5322 : (5318, lnames[5322],("cls" ,"ss" ,"m" ,"f" ,10 ,0 ,10 ,10 ,0 ,2 ,0 ,2 ,11 ,27 ,), "f", 0, NPC_FISHERMAN), + #(head, torso, legs, sex, armColor, gloveColor, legColor, headColor, + # topTexture, bottomTexture) + #['White', 'Peach', 'Bright Red', 'Red', 'Maroon', + # 'Sienna', 'Brown', 'Tan', 'Coral', 'Orange', + # 'Yellow', 'Cream', 'Citrine', 'Lime', 'Sea Green', + # 'Green', 'Light Blue', 'Aqua', 'Blue', + # 'Periwinkle', 'Royal Blue', 'Slate Blue', 'Purple', + # 'Lavender', 'Pink', 'Plum', 'Black'] + + # Goofy's Speedway + 8001 : (8501, lnames[8001], ("psl", "ms", "m", 'm', 13, 0, 13, 13, 0, 11, 0, 11, 2, 10), "m", 0, NPC_KARTCLERK), + 8002 : (8501, lnames[8002], ("psl", "ld", "s", 'f', 23, 0, 23, 23, 0, 11, 0, 11, 2, 10), "f", 0, NPC_KARTCLERK), + 8003 : (8501, lnames[8003], ("pll", "ss", "l", 'f', 1, 0, 1, 1, 0, 11, 0, 11, 2, 10), "f", 0, NPC_KARTCLERK), + 8004 : (8501, lnames[8004], ("pls", "ms", "l", 'm', 16, 0, 16, 16, 0, 11, 0, 11, 2, 10), "m", 0, NPC_KARTCLERK), + + # Dreamland + 9001 : (9503, lnames[9001],("fll" ,"ss" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,6 ,0 ,6 ,26 ,27 ,), "f", 0, NPC_REGULAR), + 9002 : (9502, lnames[9002], "r", "m", 0, NPC_REGULAR), + 9003 : (9501, lnames[9003],("fls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,5 ,1 ,5 ,0 ,14 ,), "m", 0, NPC_REGULAR), + 9004 : (9505, lnames[9004],("rll" ,"ms" ,"l" ,"f" ,16 ,0 ,16 ,16 ,1 ,7 ,1 ,7 ,3 ,8 ,), "f", 1, NPC_HQ), + 9005 : (9505, lnames[9005],("rss" ,"ld" ,"l" ,"f" ,9 ,0 ,9 ,9 ,1 ,8 ,1 ,8 ,19 ,27 ,), "f", 1, NPC_HQ), + 9006 : (9505, lnames[9006],("rls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,0 ,1 ,), "m", 1, NPC_HQ), + 9007 : (9505, lnames[9007],("mls" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,1 ,6 ,1 ,6 ,0 ,19 ,), "m", 1, NPC_HQ), + 9008 : (9504, lnames[9008],("hll" ,"ss" ,"l" ,"f" ,8 ,0 ,8 ,8 ,1 ,9 ,1 ,9 ,12 ,27 ,), "f", 0, NPC_CLERK), + 9009 : (9504, lnames[9009],("hss" ,"ls" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,7 ,0 ,7 ,0 ,13 ,), "m", 0, NPC_CLERK), + 9010 : (9506, lnames[9010],("cll" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,8 ,0 ,8 ,0 ,10 ,), "m", 0, NPC_TAILOR), + 9011 : (9000, lnames[9011],("csl" ,"ss" ,"l" ,"m" ,7 ,0 ,7 ,7 ,0 ,8 ,0 ,8 ,0 ,4 ,), "m", 0, NPC_FISHERMAN), + 9012 : (9508, lnames[9012],("cls" ,"ld" ,"l" ,"f" ,23 ,0 ,23 ,23 ,0 ,21 ,0 ,21 ,10 ,27 ,), "f", 0, NPC_PETCLERK), + 9013 : (9508, lnames[9013],("dll" ,"sd" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,21 ,0 ,21 ,10 ,27 ,), "f", 0, NPC_PETCLERK), + 9014 : (9508, lnames[9014],("dss" ,"ss" ,"l" ,"m" ,7 ,0 ,7 ,7 ,0 ,9 ,0 ,9 ,1 ,15 ,), "m", 0, NPC_PETCLERK), + 9015 : (9000, lnames[9015], ("rss", "ls", "l", "m", 21, 0, 20, 20, 0, 12, 0, 12, 0, 11), "m", 1, NPC_PARTYPERSON), + 9016 : (9000, lnames[9016], ("rls", "md", "l", "f", 6, 0, 21, 21, 1, 11, 1, 11, 0, 11), "f", 1, NPC_PARTYPERSON), + + 9101 : (9604, lnames[9101],("css" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,1 ,1 ,1 ,0 ,11 ,), "m", 0, NPC_REGULAR), + 9102 : (9607, lnames[9102], "r", "f", 0, NPC_REGULAR), + 9103 : (9620, lnames[9103],("dsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,2 ,0 ,2 ,0 ,1 ,), "m", 0, NPC_REGULAR), + 9104 : (9642, lnames[9104],("dss" ,"ld" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,3 ,0 ,3 ,0 ,23 ,), "f", 0, NPC_REGULAR), + 9105 : (9609, lnames[9105], "r", "m", 0, NPC_REGULAR), + 9106 : (9619, lnames[9106],("fsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,3 ,0 ,3 ,0 ,13 ,), "m", 0, NPC_REGULAR), + 9107 : (9601, lnames[9107],("fls" ,"ld" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,5 ,0 ,5 ,3 ,2 ,), "f", 0, NPC_REGULAR), + 9108 : (9602, lnames[9108],("rll" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,4 ,1 ,4 ,0 ,4 ,), "m", 0, NPC_REGULAR), + 9109 : (9605, lnames[9109],("rss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 9110 : (9608, lnames[9110],("mss" ,"ss" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 9111 : (9616, lnames[9111], "r", "f", 0, NPC_REGULAR), + 9112 : (9617, lnames[9112],("hsl" ,"ms" ,"m" ,"m" ,19 ,0 ,19 ,19 ,1 ,5 ,1 ,5 ,0 ,12 ,), "m", 0, NPC_REGULAR), + 9113 : (9622, lnames[9113],("hss" ,"ss" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,5 ,1 ,5 ,0 ,9 ,), "m", 0, NPC_REGULAR), + 9114 : (9625, lnames[9114],("cll" ,"ld" ,"m" ,"f" ,4 ,0 ,4 ,4 ,0 ,8 ,0 ,8 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 9115 : (9626, lnames[9115], "r", "m", 0, NPC_REGULAR), + 9116 : (9627, lnames[9116],("cls" ,"ss" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,7 ,0 ,7 ,0 ,17 ,), "m", 0, NPC_REGULAR), + 9117 : (9628, lnames[9117],("dsl" ,"ld" ,"m" ,"f" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,2 ,9 ,), "f", 0, NPC_REGULAR), + 9118 : (9629, lnames[9118], "r", "f", 0, NPC_REGULAR), + 9119 : (9630, lnames[9119],("fll" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,8 ,0 ,8 ,0 ,6 ,), "m", 0, NPC_REGULAR), + 9120 : (9631, lnames[9120], "r", "f", 0, NPC_REGULAR), + 9121 : (9634, lnames[9121],("fls" ,"md" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,12 ,1 ,12 ,16 ,27 ,), "f", 0, NPC_REGULAR), + 9122 : (9636, lnames[9122],("rll" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,8 ,1 ,8 ,0 ,16 ,), "m", 0, NPC_REGULAR), + 9123 : (9639, lnames[9123],("rss" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,1 ,9 ,1 ,9 ,0 ,13 ,), "m", 0, NPC_REGULAR), + 9124 : (9640, lnames[9124],("rls" ,"md" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,22 ,1 ,22 ,8 ,9 ,), "f", 0, NPC_REGULAR), + 9125 : (9643, lnames[9125],("mls" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,9 ,1 ,9 ,0 ,4 ,), "m", 0, NPC_REGULAR), + 9126 : (9644, lnames[9126],("hsl" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,23 ,1 ,23 ,23 ,27 ,), "f", 0, NPC_REGULAR), + 9127 : (9645, lnames[9127],("hss" ,"ld" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,24 ,0 ,24 ,10 ,27 ,), "f", 0, NPC_REGULAR), + 9128 : (9647, lnames[9128],("cll" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,11 ,0 ,11 ,0 ,15 ,), "m", 0, NPC_REGULAR), + 9129 : (9649, lnames[9129],("csl" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,0 ,25 ,0 ,25 ,25 ,27 ,), "f", 0, NPC_REGULAR), + 9130 : (9650, lnames[9130],("cls" ,"ss" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,12 ,0 ,12 ,0 ,9 ,), "m", 0, NPC_REGULAR), + 9131 : (9651, lnames[9131], "r", "f", 0, NPC_REGULAR), + + 9132 : (9652, lnames[9132],("dss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,27 ,0 ,27 ,0 ,0 ,), "f", 0, NPC_HQ), + 9133 : (9652, lnames[9133],("dls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,12 ,1 ,12 ,0 ,17 ,), "m", 0, NPC_HQ), + 9134 : (9652, lnames[9134],("fsl" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,0 ,1 ,0 ,0 ,14 ,), "m", 0, NPC_HQ), + 9135 : (9652, lnames[9135],("fls" ,"ms" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,0 ,1 ,0 ,0 ,11 ,), "m", 0, NPC_HQ), + 9136 : (9153, lnames[9136],("rll" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,0 ,1 ,0 ,1 ,6 ,), "m", 0, NPC_FISHERMAN), + + 9201 : (9752, lnames[9201], ('psl', 'ss', 'm', 'm', 9,0,9,9,17,11,0,11,7,20), 'm', 0, NPC_REGULAR), + 9202 : (9703, lnames[9202], ('dss', 'ss', 's', 'm', 21,0,21,21,8,3,8,3,1,17), 'm', 0, NPC_REGULAR), + 9203 : (9741, lnames[9203], ('pls', 'ls', 's', 'm', 5,0,5,5,37,27,26,27,7,4), 'm', 0, NPC_REGULAR), + 9204 : (9704, lnames[9204], ('fsl', 'sd', 's', 'f', 19,0,19,19,21,10,0,10,8,23), 'f', 0, NPC_REGULAR), + 9205 : (9736, lnames[9205], ('dsl', 'ms', 'm', 'm', 15,0,15,15,45,27,34,27,2,17), 'm', 0, NPC_REGULAR), + 9206 : (9727, lnames[9206], ('rls', 'ld', 'l', 'f', 8,0,8,8,25,27,16,27,10,27), 'f', 0, NPC_REGULAR), + 9207 : (9709, lnames[9207], ('hss', 'ss', 's', 'f', 24,0,24,24,36,27,25,27,9,27), 'f', 0, NPC_REGULAR), + 9208 : (9705, lnames[9208], ('dsl', 'ms', 's', 'm', 20,0,20,20,46,27,35,27,6,27), 'm', 0, NPC_REGULAR), + 9209 : (9706, lnames[9209], ('pll', 'ss', 'm', 'm', 13,0,13,13,8,12,8,12,1,12), 'm', 0, NPC_REGULAR), + 9210 : (9740, lnames[9210], ('hsl', 'ls', 'l', 'm', 6,0,6,6,1,0,1,0,0,0), 'm', 0, NPC_REGULAR), + 9211 : (9707, lnames[9211], ('rll', 'ss', 's', 'f', 3,0,3,3,22,22,0,22,6,22), 'f', 0, NPC_REGULAR), + 9212 : (9753, lnames[9212], ('pss', 'md', 'm', 'f', 16,0,16,16,45,27,34,27,0,3), 'f', 0, NPC_REGULAR), + 9213 : (9711, lnames[9213], ('fsl', 'ss', 'm', 'm', 2,0,2,2,37,27,26,27,7,18), 'm', 0, NPC_REGULAR), + 9214 : (9710, lnames[9214], ('rll', 'ls', 'l', 'm', 18,0,18,18,10,27,0,27,0,13), 'm', 0, NPC_REGULAR), + 9215 : (9744, lnames[9215], ('csl', 'ls', 'l', 'm', 18,0,18,18,11,4,0,4,0,4), 'm', 0, NPC_REGULAR), + 9216 : (9725, lnames[9216], ('csl', 'sd', 'm', 'f', 14,0,14,14,1,7,1,7,3,7), 'f', 0, NPC_REGULAR), + 9217 : (9713, lnames[9217], ('mss', 'ms', 'm', 'f', 17,0,17,17,20,26,0,26,5,12), 'f', 0, NPC_REGULAR), + 9218 : (9737, lnames[9218], ('dss', 'md', 'l', 'f', 23,0,23,23,24,27,15,27,11,27), 'f', 0, NPC_REGULAR), + 9219 : (9712, lnames[9219], ('hll', 'sd', 'l', 'f', 10,0,10,10,9,22,9,22,12,27), 'f', 0, NPC_REGULAR), + 9220 : (9716, lnames[9220], ('mls', 'ms', 'l', 'm', 7,0,7,7,0,27,0,27,1,10), 'm', 0, NPC_REGULAR), + 9221 : (9738, lnames[9221], ('fss', 'md', 'l', 'f', 22,0,22,22,45,27,34,27,0,6), 'f', 0, NPC_REGULAR), + 9222 : (9754, lnames[9222], ('hsl', 'ls', 'l', 'm', 10,0,10,10,52,27,41,27,12,27), 'm', 0, NPC_REGULAR), + 9223 : (9714, lnames[9223], ('fsl', 'ms', 'm', 'm', 20,0,20,20,43,27,32,27,0,0), 'm', 0, NPC_REGULAR), + 9224 : (9718, lnames[9224], ('css', 'ms', 'm', 'f', 1,0,1,1,6,8,6,8,6,8), 'f', 0, NPC_REGULAR), + 9225 : (9717, lnames[9225], ('rss', 'md', 'm', 'f', 11,0,11,11,40,27,29,27,0,27), 'f', 0, NPC_REGULAR), + 9226 : (9715, lnames[9226], ('mls', 'ms', 's', 'm', 12,0,12,12,3,10,3,10,6,10), 'm', 0, NPC_REGULAR), + 9227 : (9721, lnames[9227], ('cls', 'ss', 's', 'm', 13,0,13,13,8,5,8,5,3,18), 'm', 0, NPC_REGULAR), + 9228 : (9720, lnames[9228], ('fss', 'sd', 's', 'f', 4,0,4,4,15,5,11,5,8,5), 'f', 0, NPC_REGULAR), + 9229 : (9708, lnames[9229], ('css', 'ld', 'm', 'f', 4,0,4,4,22,21,0,21,4,21), 'f', 0, NPC_REGULAR), + 9230 : (9719, lnames[9230], ('mss', 'ss', 's', 'm', 8,0,8,8,53,27,42,27,13,27), 'm', 0, NPC_REGULAR), + 9231 : (9722, lnames[9231], ('dll', 'ss', 's', 'm', 6,0,6,6,27,27,18,27,3,8), 'm', 0, NPC_REGULAR), + 9232 : (9759, lnames[9232], ('pss', 'ld', 'm', 'f', 21,0,21,21,0,27,0,27,13,27), 'f', 0, NPC_REGULAR), + + 9233 : (9756, lnames[9233],("csl" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,7 ,1 ,7 ,12 ,27 ,), "f", 0, NPC_HQ), + 9234 : (9756, lnames[9234],("cls" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,0 ,19 ,), "m", 0, NPC_HQ), + 9235 : (9756, lnames[9235],("dll" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,6 ,1 ,6 ,0 ,16 ,), "m", 0, NPC_HQ), + 9236 : (9756, lnames[9236],("dss" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,6 ,0 ,6 ,0 ,13 ,), "m", 0, NPC_HQ), + 9237 : (9255, lnames[9237],("dls" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,7 ,0 ,7 ,0 ,10 ,), "m", 0, NPC_FISHERMAN), + + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +try: + config = simbase.config +except: + config = base.config + +if config.GetBool("want-new-toonhall",1): + NPCToonDict[2001] = (2513, lnames[2001], ('dss', 'ms', 'm', 'm', 17,0,17,17,3,3,3,3,7,2), "m", 1, NPC_FLIPPYTOONHALL) +else: + NPCToonDict[2001] = (2513, lnames[2001], ('dss', 'ms', 'm', 'm', 17,0,17,17,3,3,3,3,7,2), "m", 1, NPC_REGULAR) + + +# We are done with this now +del lnames + +BlockerPositions = { + TTLocalizer.Flippy : (Point3(207.4, 18.81, -0.475), 90.0), + } + +# Maps toon interior zones to NPC ids +# The toon interior zone is the branch zone + 500 + block number +# You have to look in the dna to know what block numbers there are +# they are not necessary +zone2NpcDict = {} + +# Fill out the zone2NpcDict so we can efficiently lookup toons by zoneId +def generateZone2NpcDict(): + for id, npcDesc in NPCToonDict.items(): + zoneId = npcDesc[0] + if zone2NpcDict.has_key(zoneId): + zone2NpcDict[zoneId].append(id) + else: + zone2NpcDict[zoneId] = [id] + +def getNPCName(npcId): + npc = NPCToonDict.get(npcId) + if npc: + return npc[1] + else: + return None + +def getNPCZone(npcId): + npc = NPCToonDict.get(npcId) + if npc: + return npc[0] + else: + return None + +# NOTE: building article and name now stored as a tuple. +# If no article is defined an empty string is returned. + +def getBuildingArticle(zoneId): + return TTLocalizer.zone2TitleDict.get(zoneId, "Toon Building")[1] + +def getBuildingTitle(zoneId): + return TTLocalizer.zone2TitleDict.get(zoneId, "Toon Building")[0] + +npcFriends = { + # A dictionary of tuples, indexed by rescued toon npcId + # Each tuple consists of (type, level, hp, rarity) except for the + # restock sos which consists of (type, track, 0, rarity) + + # Healers + # Flippy + 2001 : (ToontownBattleGlobals.HEAL_TRACK, 5, ToontownGlobals.MaxHpLimit,5), + # Daffy Don + 2132 : (ToontownBattleGlobals.HEAL_TRACK, 5, 70, 4), + # Madam Chuckle + 2121 : (ToontownBattleGlobals.HEAL_TRACK, 5, 45, 3), + + # Trappers + # Clerk Clara + 2011 : (ToontownBattleGlobals.TRAP_TRACK, 4, 180, 5), + # Clerk Penny + 3007 : (ToontownBattleGlobals.TRAP_TRACK, 4, 70, 4), + # Clerk Will + 1001 : (ToontownBattleGlobals.TRAP_TRACK, 4, 50, 3), + + # Lurers + # Lil Oldman + 3112 : (ToontownBattleGlobals.LURE_TRACK, 5, 0, 5), + # Stinky Ned + 1323 : (ToontownBattleGlobals.LURE_TRACK, 5, 0, 3), + # Nancy Gas + 2308 : (ToontownBattleGlobals.LURE_TRACK, 5, 0, 3), + + # Musicians + # Moe Zart + 4119 : (ToontownBattleGlobals.SOUND_TRACK, 5, 80, 5), + # Sid Sonata + 4219 : (ToontownBattleGlobals.SOUND_TRACK, 5, 50, 4), + # Barbara Seville + 4115 : (ToontownBattleGlobals.SOUND_TRACK, 5, 40, 3), + + # Droppers + # Barnacle Bessie + 1116 : (ToontownBattleGlobals.DROP_TRACK, 5, 170, 5), + # Franz Neckvein + 2311 : (ToontownBattleGlobals.DROP_TRACK, 5, 100, 4), + # Clumsy Ned + 4140 : (ToontownBattleGlobals.DROP_TRACK, 5, 60, 3), + + # Cogs miss + # Mr. Freeze + 3137 : (ToontownBattleGlobals.NPC_COGS_MISS, 0, 0, 4), + # Flim Flam + 4327 : (ToontownBattleGlobals.NPC_COGS_MISS, 0, 0, 4), + # Julius Wheezer + 4230 : (ToontownBattleGlobals.NPC_COGS_MISS, 0, 0, 4), + + # Toons hit + # Soggy Nell + 3135 : (ToontownBattleGlobals.NPC_TOONS_HIT, 0, 0, 4), + # Sticky Lou + 2208 : (ToontownBattleGlobals.NPC_TOONS_HIT, 0, 0, 4), + # Soggy Bottom + 5124 : (ToontownBattleGlobals.NPC_TOONS_HIT, 0, 0, 4), + + # Restockers + # Professor Pete + 2003 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, -1, 0, 5), + # Professor Guffaw + 2126 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.HEAL_TRACK, 0, 3), + # Clerk Ray + 4007 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.TRAP_TRACK, 0, 3), + # Doctor Drift + 1315 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.LURE_TRACK, 0, 3), + # Sophie Squirt + 5207 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.SQUIRT_TRACK, 0, 3), + # Baker Bridget + 3129 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.THROW_TRACK, 0, 3), + # Melody Wavers + 4125 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.SOUND_TRACK, 0, 3), + # Shelly Seaweed + 1329 : (ToontownBattleGlobals.NPC_RESTOCK_GAGS, + ToontownBattleGlobals.DROP_TRACK, 0, 3), + } + +def getNPCTrack(npcId): + if (npcFriends.has_key(npcId)): + return npcFriends[npcId][0] + return None + +def getNPCTrackHp(npcId): + if (npcFriends.has_key(npcId)): + track, level, hp, rarity = npcFriends[npcId] + return track, hp + return None, None + +def getNPCTrackLevelHp(npcId): + if (npcFriends.has_key(npcId)): + track, level, hp, rarity = npcFriends[npcId] + return track, level, hp + return None, None, None + + +def getNPCTrackLevelHpRarity(npcId): + if (npcFriends.has_key(npcId)): + return npcFriends[npcId] + return None, None, None, None diff --git a/toontown/src/toon/PlayerDetailPanel.py b/toontown/src/toon/PlayerDetailPanel.py new file mode 100644 index 0000000..196eea9 --- /dev/null +++ b/toontown/src/toon/PlayerDetailPanel.py @@ -0,0 +1,223 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +import DistributedToon +from toontown.friends import FriendInviter +import ToonTeleportPanel +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil +from toontown.toonbase.ToontownBattleGlobals import Tracks, Levels + +globalAvatarDetail = None + +def showPlayerDetail(avId, avName, playerId = None): + # A module function to open the global avatar detail panel. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + + globalAvatarDetail = PlayerDetailPanel(avId, avName, playerId) + +def hidePlayerDetail(): + # A module function to close the global avatar detail if it is open. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + +def unloadPlayerDetail(): + # A module function to completely unload the global friend + # inviter. This is the same thing as hideAvatarDetail, actually. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + +class PlayerDetailPanel(DirectFrame): + """ + This is a panel that pops up in response to clicking the "Details" + button on the AvatarPanel. It displays more details about the + particular avatar. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ToonAvatarDetailPanel") + + def __init__(self, avId, avName, playerId = None, parent = aspect2dp, **kw): + # Inherits from DirectFrame + # Must specify avId and avName on creation + + self.playerId = playerId + self.isPlayer = 0 + self.playerInfo = None + if playerId: + self.isPlayer = 1 + if base.cr.playerFriendsManager.playerId2Info.has_key(playerId): + self.playerInfo = base.cr.playerFriendsManager.playerId2Info[playerId] + if not self.playerInfo.onlineYesNo: + avId = None + else: + avId = None + + self.avId = avId + self.avName = avName + self.avatar = None + self.createdAvatar = None + + # Load required models + buttons = loader.loadModel( + 'phase_3/models/gui/dialog_box_buttons_gui') + gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') + detailPanel = gui.find('**/avatarInfoPanel') + + textScale = 0.132 + textWrap = 10.4 + + if self.playerId: + textScale = 0.100 + textWrap = 18.0 + + # Specify default options + optiondefs = ( + ('pos', (0.525, 0.0, 0.525), None), + ('scale', 0.5, None), + ('relief', None, None), + ('image', detailPanel, None), + ('image_color', GlobalDialogColor, None), + ('text', '', None), + ('text_wordwrap', textWrap, None), + ('text_scale', textScale, None), + #('text_pos', (0.0, 0.75), None), + ('text_pos', (-0.125, 0.75), None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + + # initialize our base class. + DirectFrame.__init__(self, parent) + + # Information about avatar + self.dataText = DirectLabel(self, + text = '', + text_scale = 0.085, + text_align = TextNode.ALeft, + text_wordwrap = 15, + relief = None, + pos = (-0.85, 0.0, 0.725), + ) + + + + if self.avId: + self.avText = DirectLabel(self, + text = (TTLocalizer.PlayerToonName % {"toonname" : self.avName}), + text_scale = 0.09, + text_align = TextNode.ALeft, + text_wordwrap = 15, + relief = None, + pos = (-0.85, 0.0, 0.56), + ) + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + self.gotoToonButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = 1.15, + text = TTLocalizer.PlayerShowToon, + text_scale = 0.08, + text_pos = (0.0,-0.02), + textMayChange = 0, + pos = (0.43, 0, 0.415), + command = self.__showToon, + ) + + ToonTeleportPanel.hideTeleportPanel() + FriendInviter.hideFriendInviter() + + # Create some buttons. + self.bCancel = DirectButton( + self, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + relief = None, + text = TTLocalizer.AvatarDetailPanelCancel, + text_scale = 0.05, + text_pos = (0.12, -0.01), + pos = (-0.865, 0.0, -0.765), + scale = 2.0, + command = self.__handleCancel) + self.bCancel.show() + + # Call option initialization functions + self.initialiseoptions(PlayerDetailPanel) + + self.__showData() + + # Clean up + buttons.removeNode() + gui.removeNode() + + def cleanup(self): + """cleanup(self): + + Cancels any pending request and removes the panel from the + screen. + + """ + + if self.createdAvatar: + self.avatar.delete() + self.createdAvatar = None + + self.destroy() + + + ### Button handing methods + def __handleCancel(self): + unloadPlayerDetail() + + + ### Support methods + + def __showData(self): + + if self.isPlayer and self.playerInfo: #a player coming in from trhough switch board from some other game + if self.playerInfo.onlineYesNo: + someworld = self.playerInfo.location + else: + someworld = TTLocalizer.OfflineLocation + text = (TTLocalizer.AvatarDetailPanelPlayer % + #{"player":self.playerInfo.playerName, "world": self.playerInfo.location, "location": self.playerInfo.sublocation}) + # sublocation is not working now + {"player":self.playerInfo.playerName, "world": someworld}) + else: + text = TTLocalizer.AvatarDetailPanelOffline + self.dataText['text'] = text + + def __showToon(self): + messenger.send('wakeup') + # Picking a friend from your friends list has exactly the same + # effect as clicking on his or her name in the world. + hasManager = hasattr(base.cr, "playerFriendsManager") + handle = base.cr.identifyFriend(self.avId) + if not handle and hasManager: + handle = base.cr.playerFriendsManager.getAvHandleFromId(self.avId) + if handle != None: + self.notify.info("Clicked on name in friend's list. doId = %s" % handle.doId) + messenger.send("clickedNametagPlayer", [handle, self.playerId, 0]) + + + + + + + diff --git a/toontown/src/toon/PlayerInfoPanel.py b/toontown/src/toon/PlayerInfoPanel.py new file mode 100644 index 0000000..8a355ad --- /dev/null +++ b/toontown/src/toon/PlayerInfoPanel.py @@ -0,0 +1,480 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject + +from toontown.friends import FriendHandle +from otp.avatar import Avatar +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.friends import ToontownFriendSecret +import ToonAvatarDetailPanel +import AvatarPanelBase +import PlayerDetailPanel +from otp.otpbase import OTPGlobals + +GAME_LOGO_NAMES = {"Default" : 'GameLogo_Unknown', + "Disney XD" : 'GameLogo_XD', + "Toontown" : 'GameLogo_Toontown', + "Pirates" : 'GameLogo_Pirates',} + +GAME_LOGO_FILE = 'phase_3/models/misc/game_logo_card' + +class PlayerInfoPanel(AvatarPanelBase.AvatarPanelBase): + notify = DirectNotifyGlobal.directNotify.newCategory('PlayerInfoPanel') + + """ + This is a panel that pops up in response to clicking on a Toon or + Cog nearby you, or to picking a Toon from your friends list. + """ + + def __init__(self, playerId): + from toontown.friends import FriendsListPanel + AvatarPanelBase.AvatarPanelBase.__init__( + self, None, FriendsListPanel = FriendsListPanel) + self.setup(playerId) + self.avId = 0 + self.avName = None + + def setup(self, playerId): + from toontown.friends import FriendsListPanel + self.playerId = playerId + self.playerInfo = base.cr.playerFriendsManager.playerId2Info.get(playerId) + + if not self.playerInfo: + return + + avId = None + avatar = None + + # if you don't have an avatar and your online, try and go get one + if playerId: + if self.playerInfo.onlineYesNo: + avId = self.playerInfo.avatarId + avatar = base.cr.playerFriendsManager.identifyFriend(avId) + + self.notify.debug("Opening player panel, %s" % self.playerInfo) + self.avatar = avatar + + self.noAv = 0 + if not avatar: + self.noAv = 1 + + self.accountText = None + + self.listName = " " #playerInfo.playerName + world = self.playerInfo.location + # the player is offline or playing WoW + if self.playerInfo.onlineYesNo == 0: + world = TTLocalizer.AvatarDetailPanelRealLife + self.accountText = self.playerInfo.playerName + + if self.noAv: + avButtonState = DGG.DISABLED + #state = avButtonState, + else: + avButtonState = DGG.NORMAL + + self.online = self.playerInfo.onlineYesNo + if self.online: + onlineButtonState = DGG.NORMAL + #state = onlineButtonState, + else: + onlineButtonState = DGG.DISABLED + + base.localAvatar.obscureFriendsListButton(1) + + gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui") + self.frame = DirectFrame( + image = gui.find("**/avatar_panel"), + relief = None, + pos = (1.1, 100, 0.525), + ) + + disabledImageColor = Vec4(1,1,1,0.4) + text0Color = Vec4(1,1,1,1) + text1Color = Vec4(0.5,1,0.5,1) + text2Color = Vec4(1,1,0.5,1) + text3Color = Vec4(0.6,0.6,0.6,1) + + #load the logo image + + if self.playerInfo: + logoImageName = GAME_LOGO_NAMES["Default"] + if not self.playerInfo.onlineYesNo: + logoImageName = GAME_LOGO_NAMES["Default"] + elif GAME_LOGO_NAMES.has_key(self.playerInfo.location): + logoImageName = GAME_LOGO_NAMES[self.playerInfo.location] + model = loader.loadModel(GAME_LOGO_FILE) + logoImage = model.find("**/" + logoImageName) + del model + self.outsideLogo = DirectLabel( + parent = self.frame, + relief = None, + image = logoImage, + pos = (0.0125, 0.0, 0.25), + image_color = (1.0, 1.0, 1.0, 1), + scale = (0.175,1,0.175), + ) + + # Put the player's name across the top. + font = ToontownGlobals.getInterfaceFont() + textScale = 0.047 + textWrap = 7.5 + textAlign = TextNode.ACenter + textPos = (0, 0) + + self.nameLabel = DirectLabel( + parent = self.frame, + pos = (0.0125, 0, 0.385), + relief = None, + text = self.listName, + text_font = font, + text_fg = Vec4(0,0,0,1), + text_pos = textPos, + text_scale = textScale, + text_wordwrap = textWrap, + text_align = textAlign, + text_shadow = (1, 1, 1, 1), + ) + + if self.accountText: + self.accountLabel = DirectLabel( + parent = self.frame, + pos = (0.0125, 0, 0.385), + text = self.accountText, + relief = None, + text_font = font, + text_fg = Vec4(0,0,0,1), + text_pos = textPos, + text_scale = textScale, + text_wordwrap = textWrap, + text_align = textAlign, + text_shadow = (1, 1, 1, 1), + ) + self.accountLabel.show() + + self.closeButton = DirectButton( + parent = self.frame, + image = (gui.find("**/CloseBtn_UP"), + gui.find("**/CloseBtn_DN"), + gui.find("**/CloseBtn_Rllvr"), + gui.find("**/CloseBtn_UP"), + ), + relief = None, + pos = (0.157644, 0, -0.379167), + command = self.__handleClose, + ) + + self.friendButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Frnds_Btn_UP"), + gui.find("**/Frnds_Btn_DN"), + gui.find("**/Frnds_Btn_RLVR"), + gui.find("**/Frnds_Btn_UP"), + ), + image3_color = disabledImageColor, + image_scale = 0.90, + relief = None, + text = TTLocalizer.AvatarPanelFriends, + text_scale = 0.06, + pos = (-0.103, 0, 0.133), + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_pos = (0.06, -0.02), + text_align = TextNode.ALeft, + state = avButtonState, + command = self.__handleFriend, + ) + self.friendButton['state'] = DGG.DISABLED + + self.goToButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Go2_Btn_UP"), + gui.find("**/Go2_Btn_DN"), + gui.find("**/Go2_Btn_RLVR"), + gui.find("**/Go2_Btn_UP"), + ), + image3_color = disabledImageColor, + image_scale = 0.90, + relief = None, + pos = (-0.103, 0, 0.045), + text = TTLocalizer.AvatarPanelGoTo, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = 0.06, + text_pos = (0.06, -0.015), + text_align = TextNode.ALeft, + state = avButtonState, + command = self.__handleGoto, + ) + self.goToButton['state'] = DGG.DISABLED + + self.whisperButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ChtBx_ChtBtn_UP"), + gui.find("**/ChtBx_ChtBtn_DN"), + gui.find("**/ChtBx_ChtBtn_RLVR"), + gui.find("**/ChtBx_ChtBtn_UP"), + ), + image3_color = disabledImageColor, + relief = None, + image_scale = 0.90, + pos = (-0.103, 0, -0.0375), + text = TTLocalizer.AvatarPanelWhisper, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = TTLocalizer.PIPwisperButton, + text_pos = (0.06, -0.0125), + text_align = TextNode.ALeft, + state = onlineButtonState, + command = self.__handleWhisper, + ) + + self.secretsButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ChtBx_ChtBtn_UP"), + gui.find("**/ChtBx_ChtBtn_DN"), + gui.find("**/ChtBx_ChtBtn_RLVR"), + gui.find("**/ChtBx_ChtBtn_UP"), + ), + image3_color = disabledImageColor, + image_scale = 0.90, + relief = None, + pos = (-0.103, 0, -0.13), + text = TTLocalizer.AvatarPanelSecrets, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = TTLocalizer.PIPsecretsButtonScale, + text_pos = (0.055, -0.01), + text_align = TextNode.ALeft, + state = avButtonState, + command = self.__handleSecrets, + ) + self.secretsButton['state'] = DGG.DISABLED + + if not base.localAvatar.isTeleportAllowed(): + # Can't teleport to a friend while we're wearing our + # cog suit or in certain other states. + self.goToButton['state'] = DGG.DISABLED + + # ignore or stop ignoring? + ignoreStr, ignoreCmd, ignoreSize = self.getIgnoreButtonInfo() + + self.ignoreButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Ignore_Btn_UP"), + gui.find("**/Ignore_Btn_DN"), + gui.find("**/Ignore_Btn_RLVR"), + gui.find("**/Ignore_Btn_UP"), + ), + image3_color = disabledImageColor, + image_scale = 0.9, + relief = None, + pos = (-0.103697, 0, -0.21), + text = ignoreStr, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = ignoreSize, + text_pos = (0.06, -0.015), + text_align = TextNode.ALeft, + state = avButtonState, + command = ignoreCmd, + ) + + # disabled for intl + if not base.cr.productName in ['JP', 'DE', 'BR', 'FR'] : + self.reportButton = DirectButton( + parent = self.frame, + image = (gui.find("**/report_BtnUP"), + gui.find("**/report_BtnDN"), + gui.find("**/report_BtnRLVR"), + gui.find("**/report_BtnUP"), + ), + image3_color = disabledImageColor, + image_scale = 0.65, + relief = None, + pos = (-0.103, 0, -0.29738), + text = TTLocalizer.AvatarPanelReport, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = 0.06, + text_pos = (0.06, -0.015), + text_align = TextNode.ALeft, + command = self.handleReport, + ) + + self.detailButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ChtBx_BackBtn_UP"), + gui.find("**/ChtBx_BackBtn_DN"), + gui.find("**/ChtBx_BackBtn_Rllvr"), + gui.find("**/ChtBx_BackBtn_UP"), + ), + relief = None, + text = ("", TTLocalizer.PlayerPanelDetail, + TTLocalizer.PlayerPanelDetail, ""), + text_fg = text2Color, + text_shadow = (0, 0, 0, 1), + text_scale = TTLocalizer.PIPdetailButton, + text_pos = (0.085, 0.055), + text_align = TextNode.ACenter, + pos = (-0.133773, 0, -0.387132), + state = DGG.NORMAL, + command = self.__handleDetails, + ) + + gui.removeNode() + + menuX = -0.05 + menuScale = 0.064 + + + self.frame.show() + messenger.send("avPanelDone") + self.accept("playerOnline" , self.__handlePlayerChanged) + self.accept("playerOffline", self.__handlePlayerChanged) + self.accept(OTPGlobals.PlayerFriendUpdateEvent, self.__handlePlayerChanged) + self.accept(OTPGlobals.PlayerFriendRemoveEvent, self.__handlePlayerUnfriend) + + + def disableAll(self): + self.detailButton['state'] = DGG.DISABLED + self.ignoreButton['state'] = DGG.DISABLED + # disabled for intl + if not base.cr.productName in ['JP', 'DE', 'BR', 'FR'] : + self.reportButton['state'] = DGG.DISABLED + self.goToButton['state'] = DGG.DISABLED + self.secretsButton['state'] = DGG.DISABLED + self.whisperButton['state'] = DGG.DISABLED + self.friendButton['state'] = DGG.DISABLED + self.closeButton['state'] = DGG.DISABLED + + + + def cleanup(self): + self.unsetup() + self.ignore("playerOnline") + self.ignore("playerOffline") + self.ignore(OTPGlobals.PlayerFriendUpdateEvent) + self.ignore(OTPGlobals.PlayerFriendRemoveEvent) + + AvatarPanelBase.AvatarPanelBase.cleanup(self) + return + + def unsetup(self): + if not hasattr(self, "frame") or (self.frame == None): + return + + # Make sure avatar detail panel is put away + PlayerDetailPanel.unloadPlayerDetail() + + self.frame.destroy() + del self.frame + self.frame = None + + base.localAvatar.obscureFriendsListButton(-1) + + # Note: this gets destroyed because it is a child of the frame + self.laffMeter = None + + self.ignore('updateLaffMeter') + + + if hasattr(self.avatar, "bFake") and self.avatar.bFake: + self.avatar.delete() + + def __handleGoto(self): + if base.localAvatar.isTeleportAllowed(): + base.localAvatar.chatMgr.noWhisper() + messenger.send("gotoAvatar", [self.avId, self.avName, self.avDisableName]) + + + def __handleWhisper(self): + if self.noAv: + base.localAvatar.chatMgr.whisperTo(self.listName, 0, self.playerId) + else: + base.localAvatar.chatMgr.whisperTo(self.avName, self.avId, self.playerId) + + def __handleSecrets(self): + base.localAvatar.chatMgr.noWhisper() + ToontownFriendSecret.showFriendSecret(ToontownFriendSecret.BothSecrets) + + def __handleFriend(self): + base.localAvatar.chatMgr.noWhisper() + self.__getAvInfo() + messenger.send("friendAvatar", [self.avId, self.avName, + self.avDisableName]) + + def __getAvInfo(self): + if self.playerId: + self.avId = self.playerInfo.avatarId + if self.avId: + avatar = base.cr.playerFriendsManager.identifyFriend(self.avId) + if avatar: + self.avName = avatar.getName() + if not self.avDisableName: + self.avDisableName = avatar.uniqueName('disable') + + def __handleDetails(self): + base.localAvatar.chatMgr.noWhisper() + self.__getAvInfo() + messenger.send("playerDetails", [self.avId, self.avName, self.playerId]) + + def handleDisableAvatar(self): + """ + Called whenever an avatar is disabled, this should cleanup the + avatar panel if it's not a friend. + """ + pass + + def __handlePlayerChanged(self, playerId, info = None): + if playerId == self.playerId: + self.unsetup() + self.setup(playerId) + + def __handlePlayerUnfriend(self, playerId): + if playerId == self.playerId: + self.__handleClose() + + + def __handleClose(self): + self.cleanup() + AvatarPanelBase.currentAvatarPanel = None + if self.friendsListShown: + # Restore the friends list if it was up before. + self.FriendsListPanel.showFriendsList() + + def getAvId(self): + if hasattr(self, "avatar"): + if self.avatar: + return self.avatar.doId + return None + + def getPlayerId(self): + if hasattr(self, "playerId"): + return self.playerId + return None + + def isHidden(self): + if not hasattr(self, "frame") or not self.frame: + return 1 + return self.frame.isHidden() + + def getType(self): + return "player" + diff --git a/toontown/src/toon/RTDNAFile.txt b/toontown/src/toon/RTDNAFile.txt new file mode 100644 index 0000000..01b5eb8 --- /dev/null +++ b/toontown/src/toon/RTDNAFile.txt @@ -0,0 +1,1607 @@ +NPC Id: 2012 +DNA: ("rss" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,6 ,1 ,6 ,1 ,1 ,) + +NPC Id: 2016 +DNA: ("sls" ,"ls" ,"m" ,"m" ,10 ,0 ,9 ,9 ,0 ,3 ,0 ,3 ,0 ,18 ,) + +NPC Id: 2017 +DNA: ("sss" ,"ld" ,"m" ,"f" ,10 ,0 ,9 ,9 ,0 ,23 ,0 ,23 ,0 ,5 ,) + +NPC Id: 2140 +DNA: ("dls" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,9 ,1 ,9 ,1 ,10 ,) + +NPC Id: 2225 +DNA: ("cll" ,"ls" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,1 ,1 ,1 ,0 ,13 ,) + +NPC Id: 2321 +DNA: ("fsl" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,5 ,1 ,5 ,0 ,12 ,) + +NPC Id: 2001 +DNA: ("dss" ,"ms" ,"m" ,"m" ,17 ,0 ,17 ,17 ,3 ,3 ,3 ,3 ,7 ,2 ,) + +NPC Id: 2002 +DNA: ("hss" ,"ls" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,3 ,0 ,3 ,1 ,18 ,) + +NPC Id: 2003 +DNA: ("cll" ,"ms" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,1 ,15 ,) + +NPC Id: 2005 +DNA: ("cls" ,"ls" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,4 ,0 ,4 ,1 ,9 ,) + +NPC Id: 2004 +DNA: ("rll" ,"md" ,"m" ,"f" ,15 ,0 ,5 ,7 ,3 ,5 ,3 ,5 ,0 ,3 ,) + +NPC Id: 2007 +DNA: ("dss" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,1 ,20 ,) + +NPC Id: 2008 +DNA: ("fll" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,5 ,1 ,5 ,1 ,17 ,) + +NPC Id: 2009 +DNA: ("fsl" ,"md" ,"l" ,"f" ,18 ,0 ,18 ,18 ,1 ,8 ,1 ,8 ,11 ,27 ,) + +NPC Id: 2010 +DNA: ("fls" ,"ls" ,"l" ,"f" ,11 ,0 ,11 ,11 ,1 ,8 ,1 ,8 ,8 ,4 ,) + +NPC Id: 2006 +DNA: ("dsl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,4 ,1 ,4 ,1 ,2 ,) + +NPC Id: 2011 +DNA: ("rll" ,"ms" ,"l" ,"f" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,23 ,27 ,) + +NPC Id: 2013 +DNA: ("rls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,7 ,0 ,7 ,1 ,19 ,) + +NPC Id: 2014 +DNA: ("mls" ,"ms" ,"m" ,"f" ,2 ,0 ,2 ,2 ,0 ,12 ,0 ,12 ,1 ,0 ,) + +NPC Id: 2015 +DNA: ("hsl" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,0 ,8 ,0 ,8 ,1 ,13 ,) + +NPC Id: 2101 +DNA: ("rll" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,9 ,0 ,9 ,0 ,6 ,) + +NPC Id: 2108 +DNA: ("csl" ,"ms" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,24 ,1 ,24 ,3 ,2 ,) + +NPC Id: 2110 +DNA: ("dll" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,0 ,15 ,) + +NPC Id: 2112 +DNA: ("fll" ,"ss" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,27 ,0 ,27 ,0 ,9 ,) + +NPC Id: 2103 +DNA: ("csl" ,"ss" ,"s" ,"m" ,9 ,0 ,8 ,5 ,0 ,11 ,0 ,11 ,2 ,10 ,) + +NPC Id: 2113 +DNA: ("fsl" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,0 ,0 ,0 ,0 ,2 ,) + +NPC Id: 2114 +DNA: ("fls" ,"sd" ,"m" ,"f" ,6 ,0 ,6 ,6 ,0 ,0 ,0 ,0 ,23 ,27 ,) + +NPC Id: 2115 +DNA: ("rll" ,"ms" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,26 ,27 ,) + +NPC Id: 2116 +DNA: ("rss" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,1 ,1 ,1 ,1 ,14 ,) + +NPC Id: 2117 +DNA: ("rls" ,"sd" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,25 ,27 ,) + +NPC Id: 2118 +DNA: ("mls" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,1 ,1 ,1 ,1 ,6 ,) + +NPC Id: 2119 +DNA: ("hll" ,"ss" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,19 ,27 ,) + +NPC Id: 2120 +DNA: ("hss" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,2 ,1 ,2 ,1 ,19 ,) + +NPC Id: 2121 +DNA: ("cll" ,"ls" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,4 ,0 ,4 ,4 ,4 ,) + +NPC Id: 2122 +DNA: ("csl" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,3 ,0 ,3 ,1 ,13 ,) + +NPC Id: 2123 +DNA: ("cls" ,"md" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,14 ,27 ,) + +NPC Id: 2124 +DNA: ("dll" ,"sd" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,5 ,0 ,5 ,8 ,21 ,) + +NPC Id: 2125 +DNA: ("dss" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,4 ,0 ,4 ,1 ,0 ,) + +NPC Id: 2126 +DNA: ("dls" ,"ld" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,6 ,0 ,6 ,3 ,7 ,) + +NPC Id: 2127 +DNA: ("fsl" ,"ms" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,5 ,0 ,5 ,1 ,15 ,) + +NPC Id: 2128 +DNA: ("fss" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,5 ,1 ,5 ,1 ,12 ,) + +NPC Id: 2129 +DNA: ("rll" ,"ss" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,1 ,9 ,) + +NPC Id: 2130 +DNA: ("rss" ,"md" ,"l" ,"f" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,7 ,7 ,) + +NPC Id: 2131 +DNA: ("rls" ,"ls" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,8 ,1 ,8 ,1 ,26 ,) + +NPC Id: 2132 +DNA: ("mls" ,"ss" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,6 ,1 ,6 ,1 ,17 ,) + +NPC Id: 2133 +DNA: ("hll" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,6 ,1 ,6 ,1 ,14 ,) + +NPC Id: 2135 +DNA: ("hls" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,0 ,12 ,0 ,12 ,2 ,26 ,) + +NPC Id: 2136 +DNA: ("csl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,8 ,0 ,8 ,1 ,1 ,) + +NPC Id: 2137 +DNA: ("css" ,"sd" ,"l" ,"f" ,11 ,0 ,11 ,11 ,0 ,21 ,0 ,21 ,24 ,27 ,) + +NPC Id: 2138 +DNA: ("dll" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,0 ,9 ,0 ,9 ,1 ,16 ,) + +NPC Id: 2104 +DNA: ("mls" ,"ms" ,"m" ,"m" ,15 ,0 ,15 ,15 ,1 ,10 ,1 ,10 ,0 ,16 ,) + +NPC Id: 2105 +DNA: ("hsl" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,10 ,1 ,10 ,0 ,13 ,) + +NPC Id: 2106 +DNA: ("hss" ,"ld" ,"m" ,"f" ,23 ,0 ,23 ,23 ,1 ,23 ,1 ,23 ,24 ,27 ,) + +NPC Id: 2107 +DNA: ("cll" ,"sd" ,"m" ,"f" ,14 ,0 ,14 ,14 ,1 ,24 ,1 ,24 ,7 ,4 ,) + +NPC Id: 2207 +DNA: ("mss" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,0 ,8 ,0 ,8 ,0 ,16 ,) + +NPC Id: 2208 +DNA: ("mls" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,8 ,1 ,8 ,0 ,13 ,) + +NPC Id: 2201 +DNA: ("dss" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,0 ,17 ,) + +NPC Id: 2209 +DNA: ("hsl" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,0 ,10 ,) + +NPC Id: 2210 +DNA: ("hss" ,"ms" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,21 ,1 ,21 ,1 ,24 ,) + +NPC Id: 2211 +DNA: ("cll" ,"ss" ,"s" ,"f" ,3 ,0 ,3 ,3 ,1 ,22 ,1 ,22 ,25 ,27 ,) + +NPC Id: 2212 +DNA: ("css" ,"ls" ,"s" ,"m" ,18 ,0 ,18 ,18 ,1 ,9 ,1 ,9 ,0 ,18 ,) + +NPC Id: 2213 +DNA: ("cls" ,"ls" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,23 ,1 ,23 ,11 ,27 ,) + +NPC Id: 2215 +DNA: ("dss" ,"ls" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,11 ,0 ,11 ,0 ,9 ,) + +NPC Id: 2216 +DNA: ("fll" ,"sd" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,25 ,0 ,25 ,12 ,27 ,) + +NPC Id: 2217 +DNA: ("fsl" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,0 ,20 ,) + +NPC Id: 2219 +DNA: ("rll" ,"ms" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,27 ,0 ,27 ,0 ,14 ,) + +NPC Id: 2220 +DNA: ("rss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,12 ,1 ,12 ,0 ,11 ,) + +NPC Id: 2222 +DNA: ("mls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,0 ,1 ,0 ,0 ,1 ,) + +NPC Id: 2224 +DNA: ("hss" ,"ss" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,1 ,1 ,1 ,0 ,16 ,) + +NPC Id: 2203 +DNA: ("fss" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,0 ,7 ,0 ,7 ,0 ,11 ,) + +NPC Id: 2204 +DNA: ("fls" ,"ss" ,"s" ,"m" ,13 ,0 ,13 ,13 ,0 ,7 ,0 ,7 ,0 ,6 ,) + +NPC Id: 2205 +DNA: ("rsl" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,16 ,27 ,) + +NPC Id: 2206 +DNA: ("rss" ,"sd" ,"s" ,"f" ,21 ,0 ,21 ,21 ,0 ,12 ,0 ,12 ,0 ,8 ,) + +NPC Id: 2308 +DNA: ("fls" ,"ss" ,"m" ,"f" ,8 ,0 ,8 ,8 ,1 ,1 ,1 ,1 ,14 ,27 ,) + +NPC Id: 2309 +DNA: ("rsl" ,"ls" ,"m" ,"m" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,1 ,14 ,) + +NPC Id: 2301 +DNA: ("cll" ,"ms" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,3 ,1 ,3 ,0 ,6 ,) + +NPC Id: 2311 +DNA: ("mss" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,2 ,0 ,2 ,1 ,6 ,) + +NPC Id: 2314 +DNA: ("hss" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,3 ,0 ,3 ,1 ,16 ,) + +NPC Id: 2315 +DNA: ("cll" ,"ss" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,3 ,0 ,3 ,1 ,13 ,) + +NPC Id: 2316 +DNA: ("csl" ,"md" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,0 ,23 ,) + +NPC Id: 2318 +DNA: ("dsl" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,4 ,1 ,4 ,1 ,0 ,) + +NPC Id: 2319 +DNA: ("dss" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,1 ,18 ,) + +NPC Id: 2302 +DNA: ("css" ,"ms" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,0 ,1 ,) + +NPC Id: 2312 +DNA: ("mls" ,"ld" ,"m" ,"f" ,24 ,0 ,24 ,24 ,0 ,3 ,0 ,3 ,4 ,6 ,) + +NPC Id: 2304 +DNA: ("dss" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,0 ,10 ,0 ,10 ,1 ,12 ,) + +NPC Id: 2305 +DNA: ("dss" ,"ss" ,"m" ,"m" ,8 ,0 ,8 ,8 ,1 ,0 ,1 ,0 ,1 ,9 ,) + +NPC Id: 2306 +DNA: ("fll" ,"md" ,"m" ,"f" ,24 ,0 ,24 ,24 ,1 ,0 ,1 ,0 ,16 ,27 ,) + +NPC Id: 2307 +DNA: ("fsl" ,"ls" ,"m" ,"f" ,16 ,0 ,16 ,16 ,1 ,1 ,1 ,1 ,3 ,1 ,) + +NPC Id: 1008 +DNA: ("cls" ,"ms" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,27 ,0 ,27 ,0 ,17 ,) + +NPC Id: 1012 +DNA: ("fls" ,"ms" ,"l" ,"m" ,14 ,0 ,3 ,3 ,0 ,1 ,0 ,1 ,0 ,13 ,) + +NPC Id: 1013 +DNA: ("fss" ,"ms" ,"m" ,"f" ,2 ,0 ,3 ,3 ,1 ,6 ,1 ,6 ,5 ,6 ,) + +NPC Id: 1126 +DNA: ("cls" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,11 ,1 ,11 ,0 ,19 ,) + +NPC Id: 1228 +DNA: ("cll" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,1 ,9 ,1 ,9 ,1 ,0 ,) + +NPC Id: 1332 +DNA: ("cll" ,"ms" ,"m" ,"m" ,5 ,0 ,5 ,5 ,0 ,4 ,0 ,4 ,1 ,13 ,) + +NPC Id: 1007 +DNA: ("csl" ,"ls" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,12 ,1 ,12 ,0 ,20 ,) + +NPC Id: 1003 +DNA: ("mls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,11 ,1 ,11 ,0 ,15 ,) + +NPC Id: 1004 +DNA: ("hsl" ,"md" ,"l" ,"f" ,10 ,0 ,10 ,10 ,1 ,24 ,1 ,24 ,24 ,27 ,) + +NPC Id: 1005 +DNA: ("hss" ,"ms" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,11 ,1 ,11 ,0 ,9 ,) + +NPC Id: 1006 +DNA: ("cll" ,"ss" ,"l" ,"f" ,18 ,0 ,18 ,18 ,1 ,25 ,1 ,25 ,19 ,27 ,) + +NPC Id: 1001 +DNA: ("rss" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,11 ,0 ,11 ,0 ,0 ,) + +NPC Id: 1002 +DNA: ("mss" ,"ss" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,10 ,1 ,10 ,0 ,18 ,) + +NPC Id: 1009 +DNA: ("dsl" ,"ss" ,"m" ,"m" ,17 ,0 ,17 ,17 ,0 ,0 ,0 ,0 ,0 ,14 ,) + +NPC Id: 1010 +DNA: ("dss" ,"ld" ,"m" ,"f" ,10 ,0 ,10 ,10 ,0 ,0 ,0 ,0 ,26 ,27 ,) + +NPC Id: 1011 +DNA: ("fll" ,"sd" ,"m" ,"f" ,1 ,0 ,1 ,1 ,0 ,1 ,0 ,1 ,4 ,25 ,) + +NPC Id: 1112 +DNA: ("cls" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,7 ,1 ,7 ,1 ,10 ,) + +NPC Id: 1105 +DNA: ("rss" ,"ms" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,4 ,0 ,4 ,1 ,14 ,) + +NPC Id: 1113 +DNA: ("dll" ,"ms" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,11 ,1 ,11 ,0 ,27 ,) + +NPC Id: 1114 +DNA: ("dss" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,7 ,1 ,7 ,1 ,0 ,) + +NPC Id: 1102 +DNA: ("fsl" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,3 ,1 ,3 ,1 ,2 ,) + +NPC Id: 1115 +DNA: ("fll" ,"sd" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,12 ,1 ,12 ,25 ,27 ,) + +NPC Id: 1116 +DNA: ("fsl" ,"ls" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,12 ,1 ,12 ,1 ,25 ,) + +NPC Id: 1117 +DNA: ("fls" ,"ss" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,9 ,0 ,9 ,1 ,12 ,) + +NPC Id: 1118 +DNA: ("rll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,9 ,0 ,9 ,1 ,9 ,) + +NPC Id: 1122 +DNA: ("hll" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,11 ,0 ,11 ,0 ,14 ,) + +NPC Id: 1123 +DNA: ("hss" ,"ms" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,23 ,1 ,23 ,23 ,27 ,) + +NPC Id: 1124 +DNA: ("cll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,11 ,1 ,11 ,0 ,6 ,) + +NPC Id: 1101 +DNA: ("fll" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,1 ,3 ,1 ,3 ,1 ,9 ,) + +NPC Id: 1125 +DNA: ("csl" ,"sd" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,24 ,1 ,24 ,25 ,27 ,) + +NPC Id: 1108 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,6 ,0 ,6 ,1 ,1 ,) + +NPC Id: 1109 +DNA: ("hss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,0 ,8 ,0 ,8 ,14 ,27 ,) + +NPC Id: 1110 +DNA: ("cll" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,1 ,16 ,) + +NPC Id: 1111 +DNA: ("csl" ,"ld" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,2 ,2 ,) + +NPC Id: 1209 +DNA: ("rss" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,4 ,0 ,4 ,17 ,27 ,) + +NPC Id: 1211 +DNA: ("mls" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,4 ,0 ,4 ,1 ,0 ,) + +NPC Id: 1212 +DNA: ("hsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,1 ,18 ,) + +NPC Id: 1213 +DNA: ("hss" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,4 ,0 ,4 ,1 ,15 ,) + +NPC Id: 1214 +DNA: ("cll" ,"sd" ,"m" ,"f" ,2 ,0 ,2 ,2 ,0 ,7 ,0 ,7 ,1 ,12 ,) + +NPC Id: 1201 +DNA: ("css" ,"ls" ,"s" ,"f" ,12 ,0 ,12 ,12 ,0 ,0 ,0 ,0 ,1 ,24 ,) + +NPC Id: 1215 +DNA: ("css" ,"ms" ,"m" ,"f" ,18 ,0 ,18 ,18 ,0 ,7 ,0 ,7 ,25 ,27 ,) + +NPC Id: 1204 +DNA: ("dss" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,1 ,1 ,1 ,1 ,6 ,) + +NPC Id: 1202 +DNA: ("cls" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,0 ,0 ,0 ,0 ,1 ,14 ,) + +NPC Id: 1216 +DNA: ("cls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,1 ,2 ,) + +NPC Id: 1218 +DNA: ("dss" ,"ms" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,6 ,1 ,6 ,1 ,17 ,) + +NPC Id: 1219 +DNA: ("fll" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,6 ,1 ,6 ,1 ,14 ,) + +NPC Id: 1220 +DNA: ("fsl" ,"md" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,7 ,23 ,) + +NPC Id: 1223 +DNA: ("rss" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,0 ,8 ,0 ,8 ,1 ,19 ,) + +NPC Id: 1224 +DNA: ("mss" ,"sd" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,21 ,0 ,21 ,7 ,9 ,) + +NPC Id: 1225 +DNA: ("mls" ,"ss" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,1 ,13 ,) + +NPC Id: 1226 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,0 ,9 ,0 ,9 ,1 ,10 ,) + +NPC Id: 1227 +DNA: ("hss" ,"sd" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,22 ,0 ,22 ,3 ,7 ,) + +NPC Id: 1205 +DNA: ("fll" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,1 ,1 ,1 ,1 ,1 ,) + +NPC Id: 1206 +DNA: ("fss" ,"ld" ,"s" ,"f" ,19 ,0 ,19 ,19 ,1 ,2 ,1 ,2 ,7 ,11 ,) + +NPC Id: 1207 +DNA: ("fls" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,1 ,16 ,) + +NPC Id: 1208 +DNA: ("rsl" ,"ls" ,"m" ,"f" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,23 ,27 ,) + +NPC Id: 1309 +DNA: ("fll" ,"ms" ,"l" ,"f" ,23 ,0 ,23 ,23 ,0 ,21 ,0 ,21 ,1 ,12 ,) + +NPC Id: 1304 +DNA: ("cll" ,"md" ,"m" ,"f" ,15 ,0 ,15 ,15 ,1 ,9 ,1 ,9 ,23 ,27 ,) + +NPC Id: 1310 +DNA: ("fsl" ,"ss" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,9 ,0 ,9 ,0 ,11 ,) + +NPC Id: 1312 +DNA: ("rsl" ,"ms" ,"l" ,"m" ,21 ,0 ,21 ,21 ,1 ,9 ,1 ,9 ,0 ,1 ,) + +NPC Id: 1313 +DNA: ("rss" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,10 ,1 ,10 ,0 ,19 ,) + +NPC Id: 1314 +DNA: ("mss" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,10 ,1 ,10 ,0 ,16 ,) + +NPC Id: 1316 +DNA: ("hsl" ,"ms" ,"l" ,"f" ,14 ,0 ,14 ,14 ,1 ,24 ,1 ,24 ,7 ,7 ,) + +NPC Id: 1317 +DNA: ("hss" ,"ld" ,"l" ,"f" ,7 ,0 ,7 ,7 ,1 ,25 ,1 ,25 ,26 ,27 ,) + +NPC Id: 1318 +DNA: ("cll" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,12 ,0 ,12 ,0 ,0 ,) + +NPC Id: 1319 +DNA: ("csl" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,0 ,18 ,) + +NPC Id: 1321 +DNA: ("dsl" ,"md" ,"l" ,"f" ,22 ,0 ,22 ,22 ,0 ,27 ,0 ,27 ,7 ,5 ,) + +NPC Id: 1322 +DNA: ("dss" ,"ls" ,"m" ,"f" ,13 ,0 ,13 ,13 ,0 ,0 ,0 ,0 ,26 ,27 ,) + +NPC Id: 1323 +DNA: ("fll" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,0 ,0 ,0 ,1 ,2 ,) + +NPC Id: 1324 +DNA: ("fsl" ,"md" ,"m" ,"f" ,22 ,0 ,22 ,22 ,0 ,1 ,0 ,1 ,10 ,27 ,) + +NPC Id: 1326 +DNA: ("rll" ,"ms" ,"m" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,10 ,27 ,) + +NPC Id: 1303 +DNA: ("hls" ,"ss" ,"m" ,"m" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,0 ,15 ,) + +NPC Id: 1301 +DNA: ("mls" ,"md" ,"m" ,"f" ,16 ,0 ,16 ,16 ,1 ,8 ,1 ,8 ,14 ,27 ,) + +NPC Id: 1328 +DNA: ("rls" ,"ms" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,2 ,1 ,2 ,1 ,6 ,) + +NPC Id: 1329 +DNA: ("mls" ,"ms" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,3 ,1 ,3 ,25 ,27 ,) + +NPC Id: 1302 +DNA: ("hsl" ,"ms" ,"m" ,"m" ,8 ,0 ,8 ,8 ,1 ,6 ,1 ,6 ,0 ,18 ,) + +NPC Id: 1330 +DNA: ("hsl" ,"ls" ,"m" ,"m" ,19 ,0 ,19 ,19 ,1 ,3 ,1 ,3 ,1 ,19 ,) + +NPC Id: 1331 +DNA: ("hss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,3 ,0 ,3 ,1 ,16 ,) + +NPC Id: 1305 +DNA: ("css" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,1 ,7 ,1 ,7 ,0 ,9 ,) + +NPC Id: 1306 +DNA: ("cls" ,"ms" ,"m" ,"f" ,24 ,0 ,24 ,24 ,0 ,12 ,0 ,12 ,0 ,7 ,) + +NPC Id: 1307 +DNA: ("dsl" ,"ls" ,"m" ,"m" ,15 ,0 ,15 ,15 ,0 ,8 ,0 ,8 ,0 ,20 ,) + +NPC Id: 1308 +DNA: ("dss" ,"sd" ,"m" ,"f" ,8 ,0 ,8 ,8 ,0 ,21 ,0 ,21 ,4 ,5 ,) + +NPC Id: 4009 +DNA: ("hsl" ,"ld" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,6 ,1 ,6 ,12 ,27 ,) + +NPC Id: 4013 +DNA: ("bll" ,"ls" ,"s" ,"m" ,3 ,0 ,19 ,19 ,0 ,8 ,0 ,8 ,1 ,12 ,) + +NPC Id: 4014 +DNA: ("bss" ,"md" ,"m" ,"f" ,24 ,0 ,19 ,19 ,0 ,24 ,0 ,24 ,0 ,12 ,) + +NPC Id: 4141 +DNA: ("hll" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,1 ,8 ,1 ,8 ,1 ,4 ,) + +NPC Id: 4235 +DNA: ("cls" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,3 ,1 ,3 ,1 ,16 ,) + +NPC Id: 4335 +DNA: ("hsl" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,0 ,1 ,0 ,0 ,6 ,) + +NPC Id: 4008 +DNA: ("mls" ,"ms" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,7 ,9 ,) + +NPC Id: 4002 +DNA: ("fll" ,"ss" ,"m" ,"m" ,5 ,0 ,5 ,5 ,0 ,2 ,0 ,2 ,1 ,17 ,) + +NPC Id: 4003 +DNA: ("fsl" ,"md" ,"m" ,"f" ,21 ,0 ,21 ,21 ,0 ,3 ,0 ,3 ,10 ,27 ,) + +NPC Id: 4004 +DNA: ("fls" ,"ls" ,"m" ,"f" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,2 ,11 ,) + +NPC Id: 4005 +DNA: ("rll" ,"ss" ,"m" ,"f" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,24 ,27 ,) + +NPC Id: 4006 +DNA: ("rss" ,"md" ,"m" ,"f" ,21 ,0 ,21 ,21 ,1 ,4 ,1 ,4 ,8 ,8 ,) + +NPC Id: 4007 +DNA: ("rls" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,3 ,1 ,3 ,1 ,19 ,) + +NPC Id: 4010 +DNA: ("hss" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,5 ,0 ,5 ,1 ,10 ,) + +NPC Id: 4011 +DNA: ("cll" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,1 ,4 ,) + +NPC Id: 4012 +DNA: ("csl" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,8 ,0 ,8 ,10 ,27 ,) + +NPC Id: 4101 +DNA: ("cll" ,"ms" ,"m" ,"m" ,16 ,0 ,16 ,16 ,1 ,7 ,1 ,7 ,0 ,6 ,) + +NPC Id: 4110 +DNA: ("rss" ,"ld" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,24 ,0 ,24 ,3 ,2 ,) + +NPC Id: 4102 +DNA: ("csl" ,"ms" ,"m" ,"f" ,9 ,0 ,9 ,9 ,1 ,11 ,1 ,11 ,10 ,27 ,) + +NPC Id: 4109 +DNA: ("rll" ,"ss" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,11 ,0 ,11 ,0 ,18 ,) + +NPC Id: 4112 +DNA: ("mls" ,"ms" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,25 ,0 ,25 ,11 ,27 ,) + +NPC Id: 4113 +DNA: ("hsl" ,"ld" ,"l" ,"f" ,15 ,0 ,15 ,15 ,1 ,25 ,1 ,25 ,14 ,27 ,) + +NPC Id: 4114 +DNA: ("hss" ,"ms" ,"l" ,"m" ,7 ,0 ,7 ,7 ,1 ,11 ,1 ,11 ,1 ,20 ,) + +NPC Id: 4103 +DNA: ("cls" ,"ls" ,"l" ,"m" ,2 ,0 ,2 ,2 ,1 ,8 ,1 ,8 ,0 ,19 ,) + +NPC Id: 4115 +DNA: ("cll" ,"ls" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,26 ,1 ,26 ,10 ,27 ,) + +NPC Id: 4116 +DNA: ("csl" ,"ss" ,"m" ,"m" ,15 ,0 ,15 ,15 ,1 ,12 ,1 ,12 ,1 ,14 ,) + +NPC Id: 4117 +DNA: ("cls" ,"md" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,0 ,1 ,0 ,1 ,25 ,) + +NPC Id: 4119 +DNA: ("dss" ,"ss" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,0 ,0 ,0 ,1 ,1 ,) + +NPC Id: 4120 +DNA: ("dls" ,"ld" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,1 ,0 ,1 ,26 ,27 ,) + +NPC Id: 4121 +DNA: ("fsl" ,"ms" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,1 ,0 ,1 ,1 ,16 ,) + +NPC Id: 4122 +DNA: ("fls" ,"ms" ,"m" ,"f" ,14 ,0 ,14 ,14 ,0 ,2 ,0 ,2 ,17 ,27 ,) + +NPC Id: 4108 +DNA: ("fls" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,10 ,0 ,10 ,0 ,0 ,) + +NPC Id: 4123 +DNA: ("rll" ,"ls" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,2 ,0 ,2 ,1 ,10 ,) + +NPC Id: 4124 +DNA: ("rss" ,"ms" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,2 ,0 ,2 ,1 ,4 ,) + +NPC Id: 4125 +DNA: ("rls" ,"ls" ,"m" ,"f" ,14 ,0 ,14 ,14 ,1 ,3 ,1 ,3 ,8 ,6 ,) + +NPC Id: 4126 +DNA: ("mls" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,1 ,3 ,1 ,3 ,1 ,18 ,) + +NPC Id: 4127 +DNA: ("hll" ,"md" ,"m" ,"f" ,22 ,0 ,22 ,22 ,1 ,4 ,1 ,4 ,23 ,27 ,) + +NPC Id: 4128 +DNA: ("hss" ,"ms" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,3 ,1 ,3 ,1 ,12 ,) + +NPC Id: 4129 +DNA: ("hls" ,"ss" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,5 ,1 ,5 ,26 ,27 ,) + +NPC Id: 4132 +DNA: ("dll" ,"ms" ,"l" ,"f" ,6 ,0 ,6 ,6 ,0 ,7 ,0 ,7 ,17 ,27 ,) + +NPC Id: 4133 +DNA: ("dss" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,5 ,0 ,5 ,1 ,14 ,) + +NPC Id: 4135 +DNA: ("fsl" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,6 ,0 ,6 ,1 ,6 ,) + +NPC Id: 4136 +DNA: ("fss" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,9 ,0 ,9 ,7 ,4 ,) + +NPC Id: 4137 +DNA: ("rll" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,1 ,19 ,) + +NPC Id: 4138 +DNA: ("rsl" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,7 ,1 ,7 ,1 ,16 ,) + +NPC Id: 4139 +DNA: ("rls" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,11 ,1 ,11 ,14 ,27 ,) + +NPC Id: 4140 +DNA: ("mls" ,"ls" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,7 ,1 ,7 ,1 ,10 ,) + +NPC Id: 4104 +DNA: ("dll" ,"ms" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,8 ,1 ,8 ,0 ,16 ,) + +NPC Id: 4105 +DNA: ("dss" ,"ls" ,"l" ,"f" ,9 ,0 ,9 ,9 ,1 ,21 ,1 ,21 ,11 ,27 ,) + +NPC Id: 4106 +DNA: ("fll" ,"ss" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,22 ,0 ,22 ,19 ,27 ,) + +NPC Id: 4107 +DNA: ("fsl" ,"md" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,22 ,0 ,22 ,17 ,27 ,) + +NPC Id: 4209 +DNA: ("dss" ,"md" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,11 ,1 ,11 ,1 ,9 ,) + +NPC Id: 4203 +DNA: ("hsl" ,"ms" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,5 ,0 ,5 ,0 ,10 ,) + +NPC Id: 4211 +DNA: ("fsl" ,"ss" ,"l" ,"m" ,5 ,0 ,5 ,5 ,1 ,8 ,1 ,8 ,0 ,20 ,) + +NPC Id: 4201 +DNA: ("mss" ,"ss" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,6 ,0 ,6 ,11 ,27 ,) + +NPC Id: 4212 +DNA: ("fls" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,9 ,0 ,9 ,0 ,17 ,) + +NPC Id: 4213 +DNA: ("rll" ,"sd" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,21 ,0 ,21 ,24 ,27 ,) + +NPC Id: 4215 +DNA: ("mss" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,10 ,0 ,10 ,0 ,6 ,) + +NPC Id: 4216 +DNA: ("mls" ,"ms" ,"s" ,"m" ,13 ,0 ,13 ,13 ,0 ,10 ,0 ,10 ,0 ,1 ,) + +NPC Id: 4217 +DNA: ("hsl" ,"ms" ,"s" ,"m" ,5 ,0 ,5 ,5 ,0 ,10 ,0 ,10 ,0 ,19 ,) + +NPC Id: 4218 +DNA: ("hss" ,"ss" ,"s" ,"f" ,21 ,0 ,21 ,21 ,1 ,23 ,1 ,23 ,26 ,27 ,) + +NPC Id: 4221 +DNA: ("cls" ,"ss" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,11 ,1 ,11 ,0 ,4 ,) + +NPC Id: 4222 +DNA: ("dsl" ,"ls" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,11 ,1 ,11 ,0 ,0 ,) + +NPC Id: 4223 +DNA: ("dss" ,"sd" ,"s" ,"f" ,3 ,0 ,3 ,3 ,1 ,25 ,1 ,25 ,24 ,27 ,) + +NPC Id: 4225 +DNA: ("fsl" ,"ld" ,"s" ,"f" ,12 ,0 ,12 ,12 ,0 ,27 ,0 ,27 ,11 ,27 ,) + +NPC Id: 4202 +DNA: ("mls" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,5 ,0 ,5 ,0 ,13 ,) + +NPC Id: 4226 +DNA: ("fls" ,"sd" ,"s" ,"f" ,3 ,0 ,3 ,3 ,0 ,0 ,0 ,0 ,11 ,27 ,) + +NPC Id: 4227 +DNA: ("rll" ,"ls" ,"s" ,"f" ,19 ,0 ,19 ,19 ,0 ,0 ,0 ,0 ,23 ,27 ,) + +NPC Id: 4228 +DNA: ("rss" ,"ss" ,"s" ,"f" ,11 ,0 ,11 ,11 ,0 ,1 ,0 ,1 ,0 ,1 ,) + +NPC Id: 4208 +DNA: ("dsl" ,"ss" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,10 ,27 ,) + +NPC Id: 4229 +DNA: ("rls" ,"md" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,1 ,0 ,1 ,26 ,27 ,) + +NPC Id: 4230 +DNA: ("mls" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,1 ,1 ,1 ,0 ,14 ,) + +NPC Id: 4231 +DNA: ("hsl" ,"ss" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,2 ,1 ,2 ,8 ,0 ,) + +NPC Id: 4232 +DNA: ("hss" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,1 ,6 ,) + +NPC Id: 4233 +DNA: ("cll" ,"ms" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,2 ,1 ,2 ,1 ,1 ,) + +NPC Id: 4204 +DNA: ("hss" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,6 ,0 ,6 ,0 ,4 ,) + +NPC Id: 4205 +DNA: ("cll" ,"ld" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,8 ,1 ,8 ,10 ,27 ,) + +NPC Id: 4206 +DNA: ("css" ,"sd" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,8 ,1 ,8 ,25 ,27 ,) + +NPC Id: 4207 +DNA: ("cls" ,"ls" ,"l" ,"f" ,14 ,0 ,14 ,14 ,1 ,9 ,1 ,9 ,17 ,27 ,) + +NPC Id: 4309 +DNA: ("cll" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,4 ,0 ,4 ,0 ,17 ,) + +NPC Id: 4312 +DNA: ("dsl" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,5 ,1 ,5 ,0 ,9 ,) + +NPC Id: 4313 +DNA: ("dss" ,"ss" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,0 ,2 ,) + +NPC Id: 4314 +DNA: ("fll" ,"ld" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,8 ,1 ,8 ,12 ,27 ,) + +NPC Id: 4301 +DNA: ("fss" ,"md" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,17 ,27 ,) + +NPC Id: 4302 +DNA: ("fls" ,"ls" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,2 ,3 ,) + +NPC Id: 4315 +DNA: ("fss" ,"sd" ,"m" ,"f" ,18 ,0 ,18 ,18 ,1 ,9 ,1 ,9 ,26 ,27 ,) + +NPC Id: 4316 +DNA: ("fls" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,1 ,6 ,1 ,6 ,0 ,14 ,) + +NPC Id: 4317 +DNA: ("rsl" ,"ls" ,"l" ,"m" ,3 ,0 ,3 ,3 ,0 ,7 ,0 ,7 ,0 ,11 ,) + +NPC Id: 4308 +DNA: ("css" ,"md" ,"m" ,"f" ,6 ,0 ,6 ,6 ,3 ,5 ,3 ,5 ,0 ,14 ,) + +NPC Id: 4318 +DNA: ("rss" ,"ms" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,8 ,0 ,8 ,0 ,6 ,) + +NPC Id: 4319 +DNA: ("mss" ,"ls" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,1 ,23 ,) + +NPC Id: 4320 +DNA: ("mls" ,"ss" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,21 ,0 ,21 ,11 ,27 ,) + +NPC Id: 4321 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,9 ,0 ,9 ,0 ,16 ,) + +NPC Id: 4322 +DNA: ("hls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,0 ,13 ,) + +NPC Id: 4323 +DNA: ("cll" ,"ss" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,21 ,1 ,21 ,24 ,27 ,) + +NPC Id: 4324 +DNA: ("css" ,"ld" ,"l" ,"f" ,17 ,0 ,17 ,17 ,1 ,22 ,1 ,22 ,10 ,27 ,) + +NPC Id: 4325 +DNA: ("cls" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,9 ,1 ,9 ,0 ,0 ,) + +NPC Id: 4326 +DNA: ("dsl" ,"ms" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,23 ,1 ,23 ,14 ,27 ,) + +NPC Id: 4327 +DNA: ("dss" ,"ld" ,"l" ,"f" ,16 ,0 ,16 ,16 ,1 ,23 ,1 ,23 ,7 ,1 ,) + +NPC Id: 4303 +DNA: ("rsl" ,"ss" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,2 ,1 ,2 ,0 ,18 ,) + +NPC Id: 4328 +DNA: ("fll" ,"ms" ,"l" ,"m" ,8 ,0 ,8 ,8 ,1 ,11 ,1 ,11 ,0 ,12 ,) + +NPC Id: 4329 +DNA: ("fsl" ,"ls" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,25 ,0 ,25 ,26 ,27 ,) + +NPC Id: 4332 +DNA: ("rss" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,27 ,0 ,27 ,0 ,17 ,) + +NPC Id: 4333 +DNA: ("mss" ,"ss" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,27 ,0 ,27 ,0 ,14 ,) + +NPC Id: 4334 +DNA: ("mls" ,"ls" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,0 ,0 ,0 ,0 ,11 ,) + +NPC Id: 4304 +DNA: ("rss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,2 ,0 ,2 ,0 ,15 ,) + +NPC Id: 4305 +DNA: ("mss" ,"sd" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,4 ,0 ,4 ,26 ,27 ,) + +NPC Id: 4306 +DNA: ("hll" ,"ms" ,"m" ,"f" ,19 ,0 ,19 ,19 ,0 ,5 ,0 ,5 ,4 ,25 ,) + +NPC Id: 4307 +DNA: ("hsl" ,"ld" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,5 ,0 ,5 ,17 ,27 ,) + +NPC Id: 5008 +DNA: ("cll" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,1 ,22 ,1 ,22 ,19 ,27 ,) + +NPC Id: 5012 +DNA: ("dls" ,"ms" ,"m" ,"m" ,13 ,0 ,12 ,12 ,0 ,1 ,0 ,1 ,0 ,6 ,) + +NPC Id: 5013 +DNA: ("dss" ,"md" ,"m" ,"f" ,1 ,0 ,3 ,3 ,1 ,5 ,1 ,5 ,0 ,5 ,) + +NPC Id: 5129 +DNA: ("dll" ,"md" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,23 ,0 ,23 ,17 ,27 ,) + +NPC Id: 5229 +DNA: ("hss" ,"ms" ,"s" ,"f" ,3 ,0 ,3 ,3 ,0 ,11 ,0 ,11 ,16 ,27 ,) + +NPC Id: 5322 +DNA: ("cls" ,"ss" ,"m" ,"f" ,10 ,0 ,10 ,10 ,0 ,2 ,0 ,2 ,11 ,27 ,) + +NPC Id: 5007 +DNA: ("hss" ,"ss" ,"s" ,"f" ,13 ,0 ,13 ,13 ,0 ,22 ,0 ,22 ,3 ,2 ,) + +NPC Id: 5001 +DNA: ("fls" ,"ls" ,"s" ,"m" ,14 ,0 ,14 ,14 ,1 ,7 ,1 ,7 ,0 ,20 ,) + +NPC Id: 5002 +DNA: ("rll" ,"ms" ,"s" ,"m" ,6 ,0 ,6 ,6 ,0 ,8 ,0 ,8 ,0 ,17 ,) + +NPC Id: 5003 +DNA: ("rss" ,"ms" ,"s" ,"f" ,22 ,0 ,22 ,22 ,0 ,12 ,0 ,12 ,26 ,27 ,) + +NPC Id: 5004 +DNA: ("rls" ,"ld" ,"s" ,"f" ,13 ,0 ,13 ,13 ,0 ,21 ,0 ,21 ,4 ,11 ,) + +NPC Id: 5005 +DNA: ("mls" ,"md" ,"s" ,"f" ,6 ,0 ,6 ,6 ,0 ,21 ,0 ,21 ,2 ,3 ,) + +NPC Id: 5006 +DNA: ("hsl" ,"ms" ,"s" ,"m" ,20 ,0 ,20 ,20 ,0 ,9 ,0 ,9 ,0 ,1 ,) + +NPC Id: 5009 +DNA: ("csl" ,"ls" ,"m" ,"f" ,21 ,0 ,21 ,21 ,1 ,23 ,1 ,23 ,8 ,23 ,) + +NPC Id: 5010 +DNA: ("cls" ,"ss" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,10 ,1 ,10 ,0 ,10 ,) + +NPC Id: 5011 +DNA: ("dll" ,"ls" ,"m" ,"m" ,5 ,0 ,5 ,5 ,1 ,10 ,1 ,10 ,0 ,4 ,) + +NPC Id: 5113 +DNA: ("css" ,"ld" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,21 ,0 ,21 ,3 ,2 ,) + +NPC Id: 5101 +DNA: ("dsl" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,4 ,1 ,4 ,0 ,11 ,) + +NPC Id: 5114 +DNA: ("cls" ,"ms" ,"l" ,"m" ,2 ,0 ,2 ,2 ,1 ,9 ,1 ,9 ,0 ,2 ,) + +NPC Id: 5115 +DNA: ("dsl" ,"ms" ,"l" ,"f" ,17 ,0 ,17 ,17 ,1 ,22 ,1 ,22 ,10 ,27 ,) + +NPC Id: 5116 +DNA: ("dss" ,"ls" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,9 ,1 ,9 ,0 ,17 ,) + +NPC Id: 5117 +DNA: ("fll" ,"md" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,23 ,1 ,23 ,17 ,27 ,) + +NPC Id: 5118 +DNA: ("fsl" ,"ms" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,10 ,1 ,10 ,0 ,11 ,) + +NPC Id: 5119 +DNA: ("fls" ,"ss" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,10 ,1 ,10 ,0 ,6 ,) + +NPC Id: 5120 +DNA: ("rsl" ,"ss" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,3 ,1 ,3 ,1 ,19 ,) + +NPC Id: 5106 +DNA: ("rsl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,6 ,1 ,6 ,0 ,13 ,) + +NPC Id: 5103 +DNA: ("fll" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,5 ,1 ,5 ,0 ,1 ,) + +NPC Id: 5108 +DNA: ("mss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,11 ,0 ,11 ,24 ,27 ,) + +NPC Id: 5104 +DNA: ("fsl" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,5 ,1 ,5 ,0 ,19 ,) + +NPC Id: 5121 +DNA: ("rss" ,"ss" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,9 ,1 ,9 ,0 ,25 ,) + +NPC Id: 5123 +DNA: ("mls" ,"sd" ,"m" ,"f" ,7 ,0 ,7 ,7 ,1 ,11 ,1 ,11 ,25 ,27 ,) + +NPC Id: 5124 +DNA: ("hll" ,"ss" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,8 ,0 ,8 ,0 ,4 ,) + +NPC Id: 5125 +DNA: ("hss" ,"ls" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,9 ,0 ,9 ,0 ,0 ,) + +NPC Id: 5126 +DNA: ("hls" ,"sd" ,"m" ,"f" ,7 ,0 ,7 ,7 ,0 ,21 ,0 ,21 ,14 ,27 ,) + +NPC Id: 5127 +DNA: ("csl" ,"ms" ,"m" ,"f" ,23 ,0 ,23 ,23 ,0 ,22 ,0 ,22 ,2 ,2 ,) + +NPC Id: 5110 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,8 ,0 ,8 ,0 ,18 ,) + +NPC Id: 5111 +DNA: ("hss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,12 ,0 ,12 ,7 ,4 ,) + +NPC Id: 5112 +DNA: ("cll" ,"ms" ,"l" ,"f" ,17 ,0 ,17 ,17 ,0 ,21 ,0 ,21 ,14 ,27 ,) + +NPC Id: 5109 +DNA: ("mls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,7 ,0 ,7 ,0 ,0 ,) + +NPC Id: 5213 +DNA: ("mls" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,1 ,1 ,1 ,1 ,14 ,) + +NPC Id: 5201 +DNA: ("hls" ,"ls" ,"l" ,"m" ,15 ,0 ,15 ,15 ,1 ,10 ,1 ,10 ,1 ,16 ,) + +NPC Id: 5202 +DNA: ("cll" ,"ls" ,"l" ,"f" ,7 ,0 ,7 ,7 ,1 ,23 ,1 ,23 ,11 ,27 ,) + +NPC Id: 5203 +DNA: ("css" ,"ss" ,"l" ,"f" ,23 ,0 ,23 ,23 ,1 ,24 ,1 ,24 ,19 ,27 ,) + +NPC Id: 5214 +DNA: ("hsl" ,"md" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,2 ,1 ,2 ,17 ,27 ,) + +NPC Id: 5216 +DNA: ("cll" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,2 ,0 ,2 ,1 ,1 ,) + +NPC Id: 5217 +DNA: ("csl" ,"ls" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,3 ,0 ,3 ,1 ,19 ,) + +NPC Id: 5219 +DNA: ("dsl" ,"ss" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,3 ,0 ,3 ,1 ,13 ,) + +NPC Id: 5221 +DNA: ("fll" ,"md" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,6 ,0 ,6 ,25 ,27 ,) + +NPC Id: 5223 +DNA: ("fls" ,"ss" ,"s" ,"m" ,5 ,0 ,5 ,5 ,1 ,4 ,1 ,4 ,1 ,18 ,) + +NPC Id: 5224 +DNA: ("rll" ,"ls" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,5 ,1 ,5 ,1 ,15 ,) + +NPC Id: 5225 +DNA: ("rss" ,"sd" ,"s" ,"f" ,12 ,0 ,12 ,12 ,1 ,7 ,1 ,7 ,10 ,27 ,) + +NPC Id: 5207 +DNA: ("fll" ,"ld" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,27 ,0 ,27 ,26 ,27 ,) + +NPC Id: 5208 +DNA: ("fsl" ,"sd" ,"l" ,"f" ,7 ,0 ,7 ,7 ,0 ,27 ,0 ,27 ,1 ,12 ,) + +NPC Id: 5206 +DNA: ("dss" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,27 ,0 ,27 ,1 ,18 ,) + +NPC Id: 5226 +DNA: ("rls" ,"ss" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,5 ,1 ,5 ,1 ,9 ,) + +NPC Id: 5227 +DNA: ("mls" ,"ld" ,"s" ,"f" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,23 ,27 ,) + +NPC Id: 5204 +DNA: ("cls" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,12 ,0 ,12 ,1 ,4 ,) + +NPC Id: 5228 +DNA: ("hsl" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,6 ,1 ,6 ,1 ,20 ,) + +NPC Id: 5209 +DNA: ("fls" ,"ss" ,"l" ,"m" ,21 ,0 ,21 ,21 ,0 ,0 ,0 ,0 ,1 ,9 ,) + +NPC Id: 5210 +DNA: ("rsl" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,0 ,1 ,0 ,1 ,2 ,) + +NPC Id: 5211 +DNA: ("rss" ,"md" ,"l" ,"f" ,6 ,0 ,6 ,6 ,1 ,1 ,1 ,1 ,23 ,27 ,) + +NPC Id: 5212 +DNA: ("mss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,1 ,1 ,1 ,10 ,27 ,) + +NPC Id: 5305 +DNA: ("hls" ,"ss" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,21 ,1 ,21 ,16 ,27 ,) + +NPC Id: 5307 +DNA: ("css" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,9 ,1 ,9 ,1 ,2 ,) + +NPC Id: 5308 +DNA: ("cls" ,"ms" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,22 ,1 ,22 ,10 ,27 ,) + +NPC Id: 5310 +DNA: ("dls" ,"ms" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,11 ,0 ,11 ,1 ,14 ,) + +NPC Id: 5311 +DNA: ("fll" ,"ms" ,"m" ,"f" ,3 ,0 ,3 ,3 ,0 ,24 ,0 ,24 ,12 ,27 ,) + +NPC Id: 5312 +DNA: ("fss" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,12 ,0 ,12 ,1 ,6 ,) + +NPC Id: 5313 +DNA: ("fls" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,1 ,1 ,) + +NPC Id: 5315 +DNA: ("rss" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,12 ,1 ,12 ,1 ,16 ,) + +NPC Id: 5316 +DNA: ("mss" ,"ls" ,"m" ,"m" ,10 ,0 ,10 ,10 ,1 ,12 ,1 ,12 ,1 ,13 ,) + +NPC Id: 5318 +DNA: ("hsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,1 ,0 ,1 ,0 ,1 ,4 ,) + +NPC Id: 5320 +DNA: ("cll" ,"sd" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,1 ,1 ,1 ,17 ,27 ,) + +NPC Id: 5321 +DNA: ("css" ,"ms" ,"m" ,"f" ,18 ,0 ,18 ,18 ,1 ,1 ,1 ,1 ,17 ,27 ,) + +NPC Id: 5302 +DNA: ("mss" ,"ss" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,17 ,27 ,) + +NPC Id: 5303 +DNA: ("hll" ,"ls" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,8 ,1 ,8 ,1 ,18 ,) + +NPC Id: 5301 +DNA: ("rss" ,"ms" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,11 ,0 ,11 ,1 ,12 ,) + +NPC Id: 5304 +DNA: ("hsl" ,"ls" ,"l" ,"f" ,12 ,0 ,12 ,12 ,1 ,12 ,1 ,12 ,19 ,27 ,) + +NPC Id: 3009 +DNA: ("rss" ,"ls" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,26 ,0 ,26 ,4 ,23 ,) + +NPC Id: 3013 +DNA: ("cls" ,"ss" ,"m" ,"m" ,18 ,0 ,17 ,17 ,1 ,7 ,1 ,7 ,1 ,9 ,) + +NPC Id: 3014 +DNA: ("css" ,"sd" ,"m" ,"f" ,17 ,0 ,16 ,16 ,0 ,24 ,0 ,24 ,0 ,9 ,) + +NPC Id: 3140 +DNA: ("rll" ,"ms" ,"l" ,"f" ,11 ,0 ,11 ,11 ,0 ,3 ,0 ,3 ,12 ,27 ,) + +NPC Id: 3232 +DNA: ("rls" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,10 ,0 ,10 ,1 ,9 ,) + +NPC Id: 3307 +DNA: ("rss" ,"ls" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,24 ,1 ,24 ,1 ,9 ,) + +NPC Id: 3008 +DNA: ("rll" ,"ms" ,"l" ,"m" ,4 ,0 ,4 ,4 ,0 ,12 ,0 ,12 ,0 ,17 ,) + +NPC Id: 3002 +DNA: ("cls" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,1 ,9 ,1 ,9 ,0 ,18 ,) + +NPC Id: 3003 +DNA: ("dsl" ,"ss" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,22 ,1 ,22 ,25 ,27 ,) + +NPC Id: 3004 +DNA: ("dss" ,"ls" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,10 ,1 ,10 ,0 ,12 ,) + +NPC Id: 3005 +DNA: ("fll" ,"ms" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,0 ,9 ,) + +NPC Id: 3006 +DNA: ("fsl" ,"ss" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,11 ,0 ,11 ,0 ,2 ,) + +NPC Id: 3007 +DNA: ("fls" ,"ld" ,"m" ,"f" ,12 ,0 ,12 ,12 ,0 ,25 ,0 ,25 ,8 ,5 ,) + +NPC Id: 3010 +DNA: ("rls" ,"ss" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,12 ,0 ,12 ,0 ,11 ,) + +NPC Id: 3011 +DNA: ("mls" ,"md" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,26 ,1 ,26 ,26 ,27 ,) + +NPC Id: 3012 +DNA: ("hsl" ,"ms" ,"l" ,"m" ,18 ,0 ,18 ,18 ,1 ,12 ,1 ,12 ,0 ,1 ,) + +NPC Id: 3121 +DNA: ("dll" ,"ls" ,"m" ,"m" ,20 ,0 ,20 ,20 ,1 ,8 ,1 ,8 ,0 ,16 ,) + +NPC Id: 3104 +DNA: ("cll" ,"ss" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,4 ,0 ,4 ,3 ,2 ,) + +NPC Id: 3112 +DNA: ("rll" ,"ls" ,"m" ,"m" ,21 ,0 ,21 ,21 ,1 ,5 ,1 ,5 ,1 ,9 ,) + +NPC Id: 3122 +DNA: ("dss" ,"md" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,21 ,1 ,21 ,10 ,27 ,) + +NPC Id: 3120 +DNA: ("cls" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,1 ,8 ,1 ,8 ,0 ,19 ,) + +NPC Id: 3101 +DNA: ("mls" ,"ls" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,1 ,1 ,1 ,1 ,6 ,) + +NPC Id: 3123 +DNA: ("dls" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,9 ,1 ,9 ,0 ,10 ,) + +NPC Id: 3124 +DNA: ("fsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,9 ,1 ,9 ,0 ,4 ,) + +NPC Id: 3125 +DNA: ("fls" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,1 ,9 ,1 ,9 ,0 ,0 ,) + +NPC Id: 3126 +DNA: ("rll" ,"ls" ,"l" ,"f" ,6 ,0 ,6 ,6 ,0 ,24 ,0 ,24 ,17 ,27 ,) + +NPC Id: 3127 +DNA: ("rss" ,"ms" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,24 ,0 ,24 ,19 ,27 ,) + +NPC Id: 3113 +DNA: ("rss" ,"ms" ,"m" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,0 ,2 ,) + +NPC Id: 3114 +DNA: ("rls" ,"ss" ,"m" ,"m" ,7 ,0 ,7 ,7 ,0 ,6 ,0 ,6 ,0 ,20 ,) + +NPC Id: 3128 +DNA: ("rls" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,11 ,0 ,11 ,0 ,12 ,) + +NPC Id: 3129 +DNA: ("mls" ,"sd" ,"l" ,"f" ,4 ,0 ,4 ,4 ,0 ,25 ,0 ,25 ,23 ,27 ,) + +NPC Id: 3130 +DNA: ("hll" ,"ms" ,"l" ,"f" ,21 ,0 ,21 ,21 ,0 ,26 ,0 ,26 ,25 ,27 ,) + +NPC Id: 3111 +DNA: ("dsl" ,"ls" ,"s" ,"m" ,6 ,0 ,6 ,6 ,14 ,27 ,10 ,27 ,1 ,14 ,) + +NPC Id: 3110 +DNA: ("fss" ,"ms" ,"l" ,"m" ,10 ,10 ,10 ,10 ,16 ,4 ,0 ,4 ,5 ,4 ,) + +NPC Id: 3107 +DNA: ("dll" ,"ms" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,4 ,4 ,) + +NPC Id: 3131 +DNA: ("hss" ,"ls" ,"l" ,"m" ,12 ,0 ,12 ,12 ,0 ,12 ,0 ,12 ,0 ,20 ,) + +NPC Id: 3106 +DNA: ("fll" ,"ls" ,"l" ,"m" ,8 ,2 ,8 ,8 ,10 ,27 ,0 ,27 ,7 ,11 ,) + +NPC Id: 3109 +DNA: ("fll" ,"sd" ,"m" ,"f" ,23 ,0 ,23 ,23 ,1 ,6 ,1 ,6 ,12 ,27 ,) + +NPC Id: 3133 +DNA: ("csl" ,"ms" ,"l" ,"m" ,19 ,0 ,19 ,19 ,1 ,12 ,1 ,12 ,0 ,14 ,) + +NPC Id: 3134 +DNA: ("cls" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,0 ,1 ,0 ,0 ,11 ,) + +NPC Id: 3135 +DNA: ("dll" ,"md" ,"l" ,"f" ,4 ,0 ,4 ,4 ,1 ,0 ,1 ,0 ,4 ,12 ,) + +NPC Id: 3136 +DNA: ("dss" ,"ls" ,"l" ,"f" ,19 ,0 ,19 ,19 ,1 ,1 ,1 ,1 ,25 ,27 ,) + +NPC Id: 3137 +DNA: ("dls" ,"ss" ,"l" ,"m" ,12 ,0 ,12 ,12 ,1 ,1 ,1 ,1 ,0 ,19 ,) + +NPC Id: 3138 +DNA: ("fsl" ,"ld" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,2 ,1 ,2 ,26 ,27 ,) + +NPC Id: 3139 +DNA: ("fss" ,"sd" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,2 ,0 ,2 ,16 ,27 ,) + +NPC Id: 3119 +DNA: ("csl" ,"ms" ,"m" ,"m" ,14 ,0 ,14 ,14 ,0 ,8 ,0 ,8 ,0 ,1 ,) + +NPC Id: 3115 +DNA: ("mls" ,"ls" ,"m" ,"m" ,21 ,0 ,21 ,21 ,0 ,7 ,0 ,7 ,0 ,17 ,) + +NPC Id: 3116 +DNA: ("hll" ,"ls" ,"m" ,"f" ,14 ,0 ,14 ,14 ,0 ,11 ,0 ,11 ,0 ,12 ,) + +NPC Id: 3117 +DNA: ("hss" ,"ss" ,"m" ,"m" ,6 ,0 ,6 ,6 ,0 ,7 ,0 ,7 ,0 ,11 ,) + +NPC Id: 3118 +DNA: ("cll" ,"ls" ,"m" ,"m" ,20 ,0 ,20 ,20 ,0 ,8 ,0 ,8 ,0 ,6 ,) + +NPC Id: 3218 +DNA: ("mss" ,"ms" ,"s" ,"m" ,18 ,0 ,18 ,18 ,1 ,4 ,1 ,4 ,1 ,16 ,) + +NPC Id: 3219 +DNA: ("mls" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,0 ,5 ,0 ,5 ,1 ,13 ,) + +NPC Id: 3220 +DNA: ("hsl" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,0 ,5 ,0 ,5 ,1 ,10 ,) + +NPC Id: 3212 +DNA: ("dss" ,"ss" ,"s" ,"m" ,19 ,0 ,19 ,19 ,0 ,2 ,0 ,2 ,1 ,17 ,) + +NPC Id: 3221 +DNA: ("hss" ,"sd" ,"s" ,"f" ,19 ,0 ,19 ,19 ,0 ,8 ,0 ,8 ,7 ,12 ,) + +NPC Id: 3203 +DNA: ("rss" ,"ls" ,"l" ,"m" ,20 ,0 ,20 ,20 ,1 ,12 ,1 ,12 ,1 ,10 ,) + +NPC Id: 3207 +DNA: ("hss" ,"ls" ,"l" ,"m" ,13 ,0 ,13 ,13 ,0 ,0 ,0 ,0 ,1 ,15 ,) + +NPC Id: 3223 +DNA: ("csl" ,"ls" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,6 ,0 ,6 ,1 ,18 ,) + +NPC Id: 3224 +DNA: ("cls" ,"md" ,"m" ,"f" ,18 ,0 ,18 ,18 ,0 ,9 ,0 ,9 ,17 ,27 ,) + +NPC Id: 3206 +DNA: ("hsl" ,"ss" ,"l" ,"f" ,21 ,0 ,21 ,21 ,1 ,0 ,1 ,0 ,11 ,27 ,) + +NPC Id: 3202 +DNA: ("rsl" ,"ss" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,12 ,1 ,12 ,1 ,13 ,) + +NPC Id: 3226 +DNA: ("dss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,7 ,1 ,7 ,1 ,9 ,) + +NPC Id: 3227 +DNA: ("fll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,7 ,1 ,7 ,1 ,2 ,) + +NPC Id: 3210 +DNA: ("pls" ,"ls" ,"s" ,"m" ,13 ,0 ,13 ,13 ,2 ,1 ,2 ,1 ,5 ,2 ,) + +NPC Id: 3228 +DNA: ("fsl" ,"ls" ,"m" ,"f" ,11 ,0 ,11 ,11 ,1 ,12 ,1 ,12 ,25 ,27 ,) + +NPC Id: 3229 +DNA: ("fls" ,"ms" ,"m" ,"f" ,2 ,0 ,2 ,2 ,1 ,12 ,1 ,12 ,0 ,7 ,) + +NPC Id: 3208 +DNA: ("cll" ,"ms" ,"l" ,"m" ,5 ,0 ,5 ,5 ,0 ,1 ,0 ,1 ,1 ,12 ,) + +NPC Id: 3204 +DNA: ("mss" ,"md" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,26 ,1 ,26 ,4 ,5 ,) + +NPC Id: 3230 +DNA: ("rll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,8 ,1 ,8 ,1 ,14 ,) + +NPC Id: 3231 +DNA: ("rss" ,"ms" ,"m" ,"m" ,9 ,0 ,9 ,9 ,0 ,9 ,0 ,9 ,1 ,12 ,) + +NPC Id: 3209 +DNA: ("css" ,"ss" ,"l" ,"m" ,19 ,0 ,19 ,19 ,0 ,1 ,0 ,1 ,1 ,9 ,) + +NPC Id: 3217 +DNA: ("rss" ,"ls" ,"s" ,"m" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,1 ,19 ,) + +NPC Id: 3213 +DNA: ("fll" ,"ls" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,2 ,1 ,2 ,1 ,14 ,) + +NPC Id: 3214 +DNA: ("fsl" ,"md" ,"s" ,"f" ,4 ,0 ,4 ,4 ,1 ,4 ,1 ,4 ,3 ,1 ,) + +NPC Id: 3215 +DNA: ("fls" ,"ms" ,"s" ,"m" ,19 ,0 ,19 ,19 ,1 ,3 ,1 ,3 ,1 ,6 ,) + +NPC Id: 3216 +DNA: ("rll" ,"ss" ,"s" ,"m" ,12 ,0 ,12 ,12 ,1 ,4 ,1 ,4 ,1 ,1 ,) + +NPC Id: 3302 +DNA: ("dls" ,"ls" ,"m" ,"m" ,4 ,0 ,4 ,4 ,0 ,10 ,0 ,10 ,1 ,1 ,) + +NPC Id: 3328 +DNA: ("dll" ,"sd" ,"l" ,"f" ,8 ,0 ,8 ,8 ,0 ,25 ,0 ,25 ,14 ,27 ,) + +NPC Id: 3318 +DNA: ("dss" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,57 ,1 ,46 ,1 ,12 ,1 ,) + +NPC Id: 3301 +DNA: ("dsl" ,"ms" ,"m" ,"f" ,11 ,0 ,11 ,11 ,0 ,22 ,0 ,22 ,2 ,11 ,) + +NPC Id: 3323 +DNA: ("mls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,3 ,1 ,3 ,1 ,10 ,) + +NPC Id: 3305 +DNA: ("fls" ,"ls" ,"m" ,"m" ,3 ,0 ,3 ,3 ,0 ,11 ,0 ,11 ,1 ,13 ,) + +NPC Id: 3320 +DNA: ("rsl" ,"ls" ,"l" ,"f" ,1 ,0 ,1 ,1 ,1 ,3 ,1 ,3 ,12 ,27 ,) + +NPC Id: 3308 +DNA: ("mss" ,"ss" ,"m" ,"m" ,3 ,0 ,3 ,3 ,1 ,11 ,1 ,11 ,1 ,0 ,) + +NPC Id: 3317 +DNA: ("fll" ,"ls" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,2 ,0 ,2 ,3 ,24 ,) + +NPC Id: 3329 +DNA: ("dll" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,0 ,1 ,0 ,1 ,1 ,1 ,) + +NPC Id: 3321 +DNA: ("rss" ,"ss" ,"l" ,"m" ,16 ,0 ,16 ,16 ,1 ,2 ,1 ,2 ,1 ,16 ,) + +NPC Id: 3326 +DNA: ("cll" ,"md" ,"l" ,"f" ,24 ,0 ,24 ,24 ,0 ,6 ,0 ,6 ,12 ,27 ,) + +NPC Id: 3306 +DNA: ("bss" ,"sd" ,"m" ,"f" ,0 ,0 ,0 ,0 ,31 ,27 ,22 ,27 ,8 ,11 ,) + +NPC Id: 3327 +DNA: ("css" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,5 ,0 ,5 ,1 ,15 ,) + +NPC Id: 3319 +DNA: ("fls" ,"ls" ,"l" ,"m" ,9 ,0 ,9 ,9 ,1 ,2 ,1 ,2 ,1 ,1 ,) + +NPC Id: 3309 +DNA: ("hll" ,"ls" ,"m" ,"m" ,17 ,0 ,17 ,17 ,1 ,11 ,1 ,11 ,1 ,18 ,) + +NPC Id: 3325 +DNA: ("hss" ,"ls" ,"l" ,"m" ,8 ,0 ,8 ,8 ,0 ,4 ,0 ,4 ,1 ,0 ,) + +NPC Id: 3304 +DNA: ("pll" ,"ls" ,"l" ,"m" ,0 ,0 ,0 ,0 ,1 ,5 ,1 ,5 ,1 ,6 ,) + +NPC Id: 3303 +DNA: ("fll" ,"ms" ,"m" ,"m" ,18 ,0 ,18 ,18 ,0 ,10 ,0 ,10 ,1 ,19 ,) + +NPC Id: 3313 +DNA: ("css" ,"ms" ,"l" ,"m" ,9 ,0 ,9 ,9 ,0 ,0 ,0 ,0 ,1 ,2 ,) + +NPC Id: 3314 +DNA: ("cls" ,"ms" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,0 ,0 ,0 ,3 ,25 ,) + +NPC Id: 3315 +DNA: ("dsl" ,"ls" ,"l" ,"m" ,17 ,0 ,17 ,17 ,0 ,0 ,0 ,0 ,1 ,17 ,) + +NPC Id: 3316 +DNA: ("dss" ,"md" ,"l" ,"f" ,10 ,0 ,10 ,10 ,0 ,1 ,0 ,1 ,10 ,27 ,) + +NPC Id: 9011 +DNA: ("csl" ,"ss" ,"l" ,"m" ,7 ,0 ,7 ,7 ,0 ,8 ,0 ,8 ,0 ,4 ,) + +NPC Id: 9015 +DNA: ("rss" ,"ls" ,"l" ,"m" ,21 ,0 ,20 ,20 ,0 ,12 ,0 ,12 ,0 ,11 ,) + +NPC Id: 9016 +DNA: ("rls" ,"md" ,"l" ,"f" ,6 ,0 ,21 ,21 ,1 ,11 ,1 ,11 ,0 ,11 ,) + +NPC Id: 9136 +DNA: ("rll" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,0 ,1 ,0 ,1 ,6 ,) + +NPC Id: 9237 +DNA: ("dls" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,0 ,7 ,0 ,7 ,0 ,10 ,) + +NPC Id: 9003 +DNA: ("fls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,5 ,1 ,5 ,0 ,14 ,) + +NPC Id: 9001 +DNA: ("fll" ,"ss" ,"l" ,"f" ,16 ,0 ,16 ,16 ,0 ,6 ,0 ,6 ,26 ,27 ,) + +NPC Id: 9010 +DNA: ("cll" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,0 ,8 ,0 ,8 ,0 ,10 ,) + +NPC Id: 9004 +DNA: ("rll" ,"ms" ,"l" ,"f" ,16 ,0 ,16 ,16 ,1 ,7 ,1 ,7 ,3 ,8 ,) + +NPC Id: 9005 +DNA: ("rss" ,"ld" ,"l" ,"f" ,9 ,0 ,9 ,9 ,1 ,8 ,1 ,8 ,19 ,27 ,) + +NPC Id: 9006 +DNA: ("rls" ,"ms" ,"l" ,"m" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,0 ,1 ,) + +NPC Id: 9007 +DNA: ("mls" ,"ms" ,"l" ,"m" ,15 ,0 ,15 ,15 ,1 ,6 ,1 ,6 ,0 ,19 ,) + +NPC Id: 9008 +DNA: ("hll" ,"ss" ,"l" ,"f" ,8 ,0 ,8 ,8 ,1 ,9 ,1 ,9 ,12 ,27 ,) + +NPC Id: 9009 +DNA: ("hss" ,"ls" ,"l" ,"m" ,22 ,0 ,22 ,22 ,0 ,7 ,0 ,7 ,0 ,13 ,) + +NPC Id: 9012 +DNA: ("cls" ,"ld" ,"l" ,"f" ,23 ,0 ,23 ,23 ,0 ,21 ,0 ,21 ,10 ,27 ,) + +NPC Id: 9013 +DNA: ("dll" ,"sd" ,"l" ,"f" ,15 ,0 ,15 ,15 ,0 ,21 ,0 ,21 ,10 ,27 ,) + +NPC Id: 9014 +DNA: ("dss" ,"ss" ,"l" ,"m" ,7 ,0 ,7 ,7 ,0 ,9 ,0 ,9 ,1 ,15 ,) + +NPC Id: 9107 +DNA: ("fls" ,"ld" ,"l" ,"f" ,13 ,0 ,13 ,13 ,0 ,5 ,0 ,5 ,3 ,2 ,) + +NPC Id: 9108 +DNA: ("rll" ,"ms" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,4 ,1 ,4 ,0 ,4 ,) + +NPC Id: 9101 +DNA: ("css" ,"ls" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,1 ,1 ,1 ,0 ,11 ,) + +NPC Id: 9109 +DNA: ("rss" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,6 ,1 ,6 ,10 ,27 ,) + +NPC Id: 9110 +DNA: ("mss" ,"ss" ,"l" ,"f" ,13 ,0 ,13 ,13 ,1 ,6 ,1 ,6 ,25 ,27 ,) + +NPC Id: 9112 +DNA: ("hsl" ,"ms" ,"m" ,"m" ,19 ,0 ,19 ,19 ,1 ,5 ,1 ,5 ,0 ,12 ,) + +NPC Id: 9106 +DNA: ("fsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,3 ,0 ,3 ,0 ,13 ,) + +NPC Id: 9103 +DNA: ("dsl" ,"ss" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,2 ,0 ,2 ,0 ,1 ,) + +NPC Id: 9113 +DNA: ("hss" ,"ss" ,"m" ,"m" ,13 ,0 ,13 ,13 ,1 ,5 ,1 ,5 ,0 ,9 ,) + +NPC Id: 9114 +DNA: ("cll" ,"ld" ,"m" ,"f" ,4 ,0 ,4 ,4 ,0 ,8 ,0 ,8 ,10 ,27 ,) + +NPC Id: 9116 +DNA: ("cls" ,"ss" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,7 ,0 ,7 ,0 ,17 ,) + +NPC Id: 9117 +DNA: ("dsl" ,"ld" ,"m" ,"f" ,4 ,0 ,4 ,4 ,0 ,11 ,0 ,11 ,2 ,9 ,) + +NPC Id: 9119 +DNA: ("fll" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,0 ,8 ,0 ,8 ,0 ,6 ,) + +NPC Id: 9121 +DNA: ("fls" ,"md" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,12 ,1 ,12 ,16 ,27 ,) + +NPC Id: 9122 +DNA: ("rll" ,"ms" ,"m" ,"m" ,12 ,0 ,12 ,12 ,1 ,8 ,1 ,8 ,0 ,16 ,) + +NPC Id: 9123 +DNA: ("rss" ,"ss" ,"m" ,"m" ,4 ,0 ,4 ,4 ,1 ,9 ,1 ,9 ,0 ,13 ,) + +NPC Id: 9124 +DNA: ("rls" ,"md" ,"m" ,"f" ,19 ,0 ,19 ,19 ,1 ,22 ,1 ,22 ,8 ,9 ,) + +NPC Id: 9104 +DNA: ("dss" ,"ld" ,"l" ,"f" ,14 ,0 ,14 ,14 ,0 ,3 ,0 ,3 ,0 ,23 ,) + +NPC Id: 9125 +DNA: ("mls" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,9 ,1 ,9 ,0 ,4 ,) + +NPC Id: 9126 +DNA: ("hsl" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,1 ,23 ,1 ,23 ,23 ,27 ,) + +NPC Id: 9127 +DNA: ("hss" ,"ld" ,"l" ,"f" ,19 ,0 ,19 ,19 ,0 ,24 ,0 ,24 ,10 ,27 ,) + +NPC Id: 9128 +DNA: ("cll" ,"ms" ,"l" ,"m" ,10 ,0 ,10 ,10 ,0 ,11 ,0 ,11 ,0 ,15 ,) + +NPC Id: 9129 +DNA: ("csl" ,"ms" ,"l" ,"f" ,3 ,0 ,3 ,3 ,0 ,25 ,0 ,25 ,25 ,27 ,) + +NPC Id: 9130 +DNA: ("cls" ,"ss" ,"l" ,"m" ,18 ,0 ,18 ,18 ,0 ,12 ,0 ,12 ,0 ,9 ,) + +NPC Id: 9132 +DNA: ("dss" ,"ls" ,"l" ,"f" ,2 ,0 ,2 ,2 ,0 ,27 ,0 ,27 ,0 ,0 ,) + +NPC Id: 9133 +DNA: ("dls" ,"ss" ,"l" ,"m" ,17 ,0 ,17 ,17 ,1 ,12 ,1 ,12 ,0 ,17 ,) + +NPC Id: 9134 +DNA: ("fsl" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,1 ,0 ,1 ,0 ,0 ,14 ,) + +NPC Id: 9135 +DNA: ("fls" ,"ms" ,"l" ,"m" ,3 ,0 ,3 ,3 ,1 ,0 ,1 ,0 ,0 ,11 ,) + +NPC Id: 9202 +DNA: ("dss" ,"ss" ,"s" ,"m" ,21 ,0 ,21 ,21 ,8 ,3 ,8 ,3 ,1 ,17 ,) + +NPC Id: 9204 +DNA: ("fsl" ,"sd" ,"s" ,"f" ,19 ,0 ,19 ,19 ,21 ,10 ,0 ,10 ,8 ,23 ,) + +NPC Id: 9208 +DNA: ("dsl" ,"ms" ,"s" ,"m" ,20 ,0 ,20 ,20 ,46 ,27 ,35 ,27 ,6 ,27 ,) + +NPC Id: 9209 +DNA: ("pll" ,"ss" ,"m" ,"m" ,13 ,0 ,13 ,13 ,8 ,12 ,8 ,12 ,1 ,12 ,) + +NPC Id: 9211 +DNA: ("rll" ,"ss" ,"s" ,"f" ,3 ,0 ,3 ,3 ,22 ,22 ,0 ,22 ,6 ,22 ,) + +NPC Id: 9229 +DNA: ("css" ,"ld" ,"m" ,"f" ,4 ,0 ,4 ,4 ,22 ,21 ,0 ,21 ,4 ,21 ,) + +NPC Id: 9214 +DNA: ("rll" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,10 ,27 ,0 ,27 ,0 ,13 ,) + +NPC Id: 9213 +DNA: ("fsl" ,"ss" ,"m" ,"m" ,2 ,0 ,2 ,2 ,37 ,27 ,26 ,27 ,7 ,18 ,) + +NPC Id: 9217 +DNA: ("mss" ,"ms" ,"m" ,"f" ,17 ,0 ,17 ,17 ,20 ,26 ,0 ,26 ,5 ,12 ,) + +NPC Id: 9223 +DNA: ("fsl" ,"ms" ,"m" ,"m" ,20 ,0 ,20 ,20 ,43 ,27 ,32 ,27 ,0 ,0 ,) + +NPC Id: 9226 +DNA: ("mls" ,"ms" ,"s" ,"m" ,12 ,0 ,12 ,12 ,3 ,10 ,3 ,10 ,6 ,10 ,) + +NPC Id: 9220 +DNA: ("mls" ,"ms" ,"l" ,"m" ,7 ,0 ,7 ,7 ,0 ,27 ,0 ,27 ,1 ,10 ,) + +NPC Id: 9224 +DNA: ("css" ,"ms" ,"m" ,"f" ,1 ,0 ,1 ,1 ,6 ,8 ,6 ,8 ,6 ,8 ,) + +NPC Id: 9230 +DNA: ("mss" ,"ss" ,"s" ,"m" ,8 ,0 ,8 ,8 ,53 ,27 ,42 ,27 ,13 ,27 ,) + +NPC Id: 9228 +DNA: ("fss" ,"sd" ,"s" ,"f" ,4 ,0 ,4 ,4 ,15 ,5 ,11 ,5 ,8 ,5 ,) + +NPC Id: 9231 +DNA: ("dll" ,"ss" ,"s" ,"m" ,6 ,0 ,6 ,6 ,27 ,27 ,18 ,27 ,3 ,8 ,) + +NPC Id: 9216 +DNA: ("csl" ,"sd" ,"m" ,"f" ,14 ,0 ,14 ,14 ,1 ,7 ,1 ,7 ,3 ,7 ,) + +NPC Id: 9206 +DNA: ("rls" ,"ld" ,"l" ,"f" ,8 ,0 ,8 ,8 ,25 ,27 ,16 ,27 ,10 ,27 ,) + +NPC Id: 9205 +DNA: ("dsl" ,"ms" ,"m" ,"m" ,15 ,0 ,15 ,15 ,45 ,27 ,34 ,27 ,2 ,17 ,) + +NPC Id: 9218 +DNA: ("dss" ,"md" ,"l" ,"f" ,23 ,0 ,23 ,23 ,24 ,27 ,15 ,27 ,11 ,27 ,) + +NPC Id: 9221 +DNA: ("fss" ,"md" ,"l" ,"f" ,22 ,0 ,22 ,22 ,45 ,27 ,34 ,27 ,0 ,6 ,) + +NPC Id: 9210 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,0 ,1 ,0 ,0 ,0 ,) + +NPC Id: 9203 +DNA: ("pls" ,"ls" ,"s" ,"m" ,5 ,0 ,5 ,5 ,37 ,27 ,26 ,27 ,7 ,4 ,) + +NPC Id: 9215 +DNA: ("csl" ,"ls" ,"l" ,"m" ,18 ,0 ,18 ,18 ,11 ,4 ,0 ,4 ,0 ,4 ,) + +NPC Id: 9201 +DNA: ("psl" ,"ss" ,"m" ,"m" ,9 ,0 ,9 ,9 ,17 ,11 ,0 ,11 ,7 ,20 ,) + +NPC Id: 9222 +DNA: ("hsl" ,"ls" ,"l" ,"m" ,10 ,0 ,10 ,10 ,52 ,27 ,41 ,27 ,12 ,27 ,) + +NPC Id: 9232 +DNA: ("pss" ,"ld" ,"m" ,"f" ,21 ,0 ,21 ,21 ,0 ,27 ,0 ,27 ,13 ,27 ,) + +NPC Id: 9233 +DNA: ("csl" ,"ls" ,"l" ,"f" ,22 ,0 ,22 ,22 ,1 ,7 ,1 ,7 ,12 ,27 ,) + +NPC Id: 9234 +DNA: ("cls" ,"ss" ,"l" ,"m" ,14 ,0 ,14 ,14 ,1 ,5 ,1 ,5 ,0 ,19 ,) + +NPC Id: 9235 +DNA: ("dll" ,"ls" ,"l" ,"m" ,6 ,0 ,6 ,6 ,1 ,6 ,1 ,6 ,0 ,16 ,) + +NPC Id: 9236 +DNA: ("dss" ,"ms" ,"l" ,"m" ,20 ,0 ,20 ,20 ,0 ,6 ,0 ,6 ,0 ,13 ,) + +NPC Id: 8001 +DNA: ("psl" ,"ms" ,"m" ,"m" ,13 ,0 ,13 ,13 ,0 ,11 ,0 ,11 ,2 ,10 ,) + +NPC Id: 8002 +DNA: ("psl" ,"ld" ,"s" ,"f" ,23 ,0 ,23 ,23 ,0 ,11 ,0 ,11 ,2 ,10 ,) + +NPC Id: 8003 +DNA: ("pll" ,"ss" ,"l" ,"f" ,1 ,0 ,1 ,1 ,0 ,11 ,0 ,11 ,2 ,10 ,) + +NPC Id: 8004 +DNA: ("pls" ,"ms" ,"l" ,"m" ,16 ,0 ,16 ,16 ,0 ,11 ,0 ,11 ,2 ,10 ,) \ No newline at end of file diff --git a/toontown/src/toon/RobotToon.py b/toontown/src/toon/RobotToon.py new file mode 100644 index 0000000..10c9790 --- /dev/null +++ b/toontown/src/toon/RobotToon.py @@ -0,0 +1,283 @@ +import Toon +from toontown.suit import Suit +from toontown.pets import Pet +from otp.avatar import Avatar +import NPCToons +import ToonDNA +from toontown.suit import SuitDNA +from toontown.toonbase import ToontownGlobals +import math +import types +import __builtin__ +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from random import * +from direct.distributed.PyDatagram import PyDatagram + +try: + __builtin__.launcher +except AttributeError: + __builtin__.launcher = None + +class RobotAvatarBase: + # Base class for robot toons and robot suits + # Not meant to be instantiated by itself + def __init__(self, parent = render, + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral'): + self.setTag('robotAvatar', '1') + # Place robot within world + self.reparentTo(parent) + self.setStartPos(startPos) + self.setStartHpr(startHpr) + self.setEndPos(endPos) + self.setEndHpr(endHpr) + self.setPosHpr(self.startPos, self.startHpr) + self.ival = self.victoryIval = None + if not base.config.GetBool('want-new-anims',1): + self.updateWalkIval() + self.accept('playVictoryIval', lambda: self.setAnimState('victory')) + self.accept('playRTMWalkIval', lambda: self.setAnimState('walk')) + self.accept('playRTMRunIval', lambda: self.setAnimState('run')) + self.setAnimState(state) + self.state = state + def convertServerDNAString(self, serverString, type = 't'): + # Strip out blank space and take last 30 characters + serverString = serverString.replace(' ', '') + if type == 't': + stringLen = 30 + else: + stringLen = 6 + serverString = serverString[-stringLen:] + # Create a datagram from server string + dg = PyDatagram() + for i in range(0,len(serverString),2): + eval('dg.addUint8(0x%s)' % serverString[i:i+2]) + return dg.getMessage() + def setAnimState(self,state): + self.stopIvals() + self.state = state + if not base.config.GetBool('want-new-anims',1): + if state == 'victory': + if self.victoryIval != None: + self.victoryIval.start() + return + elif state in ['run', 'walk', 'sad-walk']: + if state == 'run': + self.ival.loop() + elif state == 'walk': + self.ival.loop(playRate = 0.25) + elif state == 'sad-walk': + self.ival.loop(playRate = 0.0625) + else: + self.setPosHpr(self.startPos, self.startHpr) + self.loop(state) + def setStartPos(self, pos): + self.startPos = Point3(pos) + def setEndPos(self, pos): + self.endPos = Point3(pos) + def setStartHpr(self, hpr): + self.startHpr = Point3(hpr) + def setEndHpr(self, hpr): + self.endHpr = Point3(hpr) + def updateStartPos(self, pos): + self.setStartPos(pos) + self.updateWalkIval() + if self.state == 'neutral': + self.setAnimState('walk') + else: + self.setAnimState(self.state) + def updateEndPos(self, pos): + self.setEndPos(pos) + self.updateWalkIval() + if self.state == 'neutral': + self.setAnimState('walk') + else: + self.setAnimState(self.state) + def updateWalkIval(self): + self.stopIvals() + start2Stop = Vec3(self.endPos - self.startPos) + dist = start2Stop.length() + walkDuration = dist/ToontownGlobals.ToonForwardSpeed + angleVec = Vec3(start2Stop) + angleVec.setZ(0) + angleVec.normalize() + dotProd = angleVec.dot(Vec3(0,1,0)) + angle = rad2Deg(math.acos(dotProd)) + if angleVec[0] >= 0: + angle *= -1 + if angle > 0: + backAngle = angle - 180.0 + else: + backAngle = angle + 180.0 + backAngle = (angle + 180.0) + self.ival = Sequence( + Func(self.setHpr, angle, 0, 0), + self.posInterval(duration = walkDuration, pos = self.endPos, + startPos = self.startPos), + self.hprInterval(duration = 1, hpr = Vec3(backAngle,0,0), + startHpr = Vec3(angle,0,0)), + self.posInterval(duration = walkDuration, pos = self.startPos, + startPos = self.endPos), + self.hprInterval(duration = 1, hpr = Vec3(angle,0,0), + startHpr = Vec3(backAngle,0,0)), + ) + vDuration = self.getDuration('victory') + if vDuration: + vRemainder = (walkDuration % vDuration) + vWait = vDuration - vRemainder + self.victoryIval = Sequence( + # Jitter by up to 2 frames + Wait(randint(0,2) * (1/24.0)), + Func(self.setPosHpr, self.startPos, Vec3(angle,0,0)), + Func(self.loop,'neutral'), + Func(self.loop,'run'), + self.posInterval(duration = walkDuration, pos = self.endPos, + startPos = self.startPos), + Parallel( + Sequence(ActorInterval(self, 'victory', startTime = vRemainder)), + Sequence( + Wait(vWait), + Func(self.loop,'victory') + ) + ) + ) + else: + # doodle don't have a victory anim + self.victoryIval = None + def stopIvals(self): + if self.ival != None: + self.ival.finish() + if self.victoryIval != None: + self.victoryIval.finish() + def destroy(self): + self.stopIvals() + self.stop() + self.ignore('playVictoryIval') + self.removeNode() + +class RobotToon(Toon.Toon, RobotAvatarBase): + # Default is flippy + def __init__(self, description = None, parent = render, + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral'): + # Initialize superclasses + Toon.Toon.__init__(self) + self.customMessages = [] + self.setCogLevels([1,1,1,1]) + self.updateDNA(description) + RobotAvatarBase.__init__(self, parent, startPos, startHpr, + endPos, endHpr, state) + self.showHiRes() + + def updateDNA(self, description): + # Create dna + if isinstance(description, ToonDNA.ToonDNA): + dna = description + else: + dna = ToonDNA.ToonDNA() + if (isinstance(description, types.ListType) or + isinstance(description, types.TupleType)): + # Assume it is a property list + dna.newToonFromProperties(*description) + elif isinstance(description, Datagram): + # Create dna straight from datagram + dna.makeFromNetString(description) + elif isinstance(description, types.StringType): + # Assume it is a server string description + # Convert to datagram then create dna + dna.makeFromNetString(self.convertServerDNAString(description)) + elif isinstance(description, types.IntType): + # Assume it is an NPC id + npcInfo = NPCToons.NPCToonDict[description] + properties = npcInfo[2] + if properties == 'r': + gender = npcInfo[3] + properties = NPCToons.getRandomDNA(description, gender) + dna.newToonFromProperties(*properties) + else: + if random() < 0.5: + gender = 'm' + else: + gender = 'f' + dna.newToonRandom(gender = gender) + if not self.style: + # New toon, need to initialize style + self.setDNA(dna) + else: + # Just jump straight to the update function + self.updateToonDNA(dna,fForce = 1) + + def setCogLevels(self, levels): + self.cogLevels = levels + + def setCustomMessages(self, customMessages): + self.customMessages = customMessages + + def showHiRes(self, switchIn = 10000): + lodNames = self.getLODNames() + if lodNames: + maxLOD = int(lodNames[0]) + self.setLOD(maxLOD, switchIn,0) + for lod in lodNames[1:]: + self.setLOD(int(lod), switchIn + 10, switchIn) + switchIn += 10 + +class RobotSuit(Suit.Suit, RobotAvatarBase): + # Default is flippy + def __init__(self, description = None, parent = render, + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral'): + # Initialize superclasses + Suit.Suit.__init__(self) + self.updateDNA(description) + RobotAvatarBase.__init__(self, parent, startPos, startHpr, + endPos, endHpr, state) + def updateDNA(self, description): + # Create dna + if isinstance(description, ToonDNA.ToonDNA): + dna = description + else: + dna = SuitDNA.SuitDNA() + if isinstance(description, types.StringType): + # Assume it is a suit specification + dna.newSuit(description) + elif isinstance(description, types.IntType): + # Assume it specifies suit level + dna.newSuitRandom(description) + elif (isinstance(description, types.ListType) or + isinstance(description, types.TupleType)): + # Assume it is a (level,track) list + dna.newSuitRandom(description[0], description[1]) + else: + level = randint(0,7) + trackVal = random() + if trackVal < 0.25: + track = 'c' + elif trackVal < 0.5: + track = 's' + elif trackVal < 0.75: + track = 'l' + else: + track = 'm' + dna.newSuitRandom(level, track) + self.setDNA(dna) + +class RobotDoodle(Pet.Pet, RobotAvatarBase): + def __init__(self, description = None, parent = render, + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral'): + # Initialize superclasses + Pet.Pet.__init__(self) + self.updateDNA(description) + RobotAvatarBase.__init__(self, parent, startPos, startHpr, + endPos, endHpr, state) + def updateDNA(self, description): + # doodle dna is an array of the form: [head, ears, nose, tail, body, color, partColor, eyes, gender] + if (isinstance(description, types.ListType) or isinstance(description, types.TupleType)): + self.setDNA(description) + diff --git a/toontown/src/toon/RobotToonManager.py b/toontown/src/toon/RobotToonManager.py new file mode 100644 index 0000000..d0c0d1a --- /dev/null +++ b/toontown/src/toon/RobotToonManager.py @@ -0,0 +1,3510 @@ +# Is being run outside of Toontown? Then you need to create a window +# and a base +try: + base +except NameError: + from direct.directbase import DirectStart + # Let the world know there is no localAvatar + base.localAvatar = None + +from direct.showbase.DirectObject import DirectObject +from direct.showbase import ShowBase +from RobotToon import * +from toontown.battle.BattleProps import * +from direct.gui.DirectGui import * +from direct.gui import DirectGuiGlobals +from pandac.PandaModules import * +from toontown.leveleditor.PieMenu import * +from direct.directtools.DirectSelection import SelectionRay +from direct.showbase.TkGlobal import * +from Tkinter import * +from tkFileDialog import askopenfilename, asksaveasfilename +from tkSimpleDialog import askstring, askfloat +from tkMessageBox import showwarning, showinfo +from direct.tkwidgets.AppShell import * +from direct.tkwidgets.SceneGraphExplorer import * +from direct.interval.IntervalGlobal import * +from toontown.battle.SuitBattleGlobals import SuitAttributes +from toontown.makeatoon import NameGenerator +from direct.tkwidgets import Valuator +from direct.tkwidgets import Slider +import ToonDNA +from direct.task.Task import Task +from toontown.suit import SuitDNA +from toontown.suit import Suit +from otp.otpbase import OTPLocalizer +from toontown.toonbase import TTLocalizer +import __builtin__ +from toontown.hood import SkyUtil +from direct.distributed.PyDatagram import PyDatagram +from toontown.pets import PetDNA +import sys, os +import string +import Pmw + +from toontown.leveleditor.LevelStyleManager import * + +from toontown.effects import Fireworks, FireworkShows, FireworkGlobals +from toontown.battle import BattleParticles + +try: + if direct is None: + base.startDirect() +except AttributeError: + base.startDirect() + +# Note: While adding names to the ToonTopsDict make sure there is a space after the number. +# For eg: 'xx - ' and not 'xx-'. The code will crash if there is no space. +ToonTopsDict = { + 0 : '00 - solid', + 1 : '01 - single stripe', + 2 : '02 - collar', + 3 : '03 - double stripe', + 4 : '04 - multiple stripes', + 5 : '05 - collar w/ pocket', + 6 : '06 - flower print', + 7 : '07 - flower trim', + 8 : '08 - hawaiian', + 9 : '09 - collar w/ 2 pockets', + 10 : '10 - bowling shirt ', + 11 : '11 - special, vest', + 12 : '12 - denim vest', + 13 : '13 - peasant', + 14 : '14 - collar w/ ruffles', + 15 : '15 - peasant w/ mid stripe', + 16 : '16 - soccer jersey', + 17 : '17 - lightning bolt', + 18 : '18 - jersey 19', + 19 : '19 - guayavera', + 20 : '20 - hearts', + 21 : '21 - stars', + 22 : '22 - flower', + 23 : '23 - blue with 3 yellow stripes', + 24 : '24 - pink and beige with flower', + 25 : '25 - yellow hooded sweatshirt', + 26 : '26 - blue stripes', + 27 : '27 - yellow with palm tree', + 28 : '28 - orange', + 29 : '29 - ghost (Halloween)', + 30 : '30 - pumpkin (Halloween)', + 31 : '31 - snowman (Winter Holiday)', + 32 : '32 - snowflakes (Winter Holiday)', + 33 : '33 - candy canes (Winter Holiday)', + 34 : '34 - scarf (Winter Holiday)', + 35 : '35 - Blue and gold wavy stripes', + 36 : '36 - Blue and pink with bows', + 37 : '37 - Lime green with stripe', + 38 : '38 - Purple with stars', + 39 : '39 - Red kimono with checkerboard', + 40 : '40 - Aqua kimono with white stripes', + 41 : '41 - Pink heart border (Valentine)', + 42 : '42 - Red hearts (Valentine)', + 43 : '43 - Winged heart (Valentine)', + 44 : '44 - Flaming heart (Valentine)', + 45 : '45 - Tie Dye', + 46 : '46 - Blue and white stripe', + 47 : '47 - (St. Pats) Four Leaf Clover', + 48 : '48 - (St. Pats) Pot O Gold', + 49 : '49 - (T-shirt Contest) Fishing Vest', + 50 : '50 - (T-shirt Contest) Fish Tank', + 51 : '51 - (T-shirt Contest) Paw Print', + 52 : '52 - (Western) Cowboy Shirt', + 53 : '53 - (Western) Cowboy Shirt', + 54 : '54 - (Western) Cowboy Shirt', + 55 : '55 - (Western) Cowboy Shirt', + 56 : '56 - (Western) Cowboy Shirt', + 57 : '57 - (Western) Cowboy Shirt', + 58 : '58 - (July 4th) Flag Shirt', + 59 : '59 - (July 4th) Fireworks Shirt', + 60 : '60 - (Catalog 7) Green Shirt', + 61 : '61 - (Catalog 7) Purple w/Flower Shirt', + 62 : '62 - (T-shirtContest 2) Multicolor shirt w/ backpack', + 63 : '63 - (T-shirtContest 2) Lederhosen', + 64 : '64 - (T-shirtContest 2) Watermelon', + 65 : '65 - (T-shirtContest 2) Race Shirt (UK winner)', + + # Pyjama series + 66 : '66 - Blue Banana Pajama shirt', + 67 : '67 - Red Horn Pajama shirt', + 68 : '68 - Purple Glasses Pajama shirt', + + # 2009 Valentines Day Shirts + 69 : '69 - Valentines Shirt 1', + 70 : '70 - Valentines Shirt 2', + + # Special award shirts + 71 : '71 - Striped Shirt', + 72 : '72 - Fishing Shirt 1', + 73 : '73 - Fishing Shirt 2', + 74 : '74 - Gardening Shirt 1', + 75 : '75 - Gardening Shirt 2', + 76 : '76 - Party Shirt 1', + 77 : '77 - Party Shirt 2', + 78 : '78 - Racing Shirt 1', + 79 : '79 - Racing Shirt 2', + 80 : '80 - Summer Shirt 1', + 81 : '81 - Summer Shirt 2', + 82 : '82 - Golf Shirt 1', + 83 : '83 - Golf Shirt 2', + 84 : '84 - Halloween Costume Shirt 1', + 85 : '85 - Halloween Costume Shirt 2', + 86 : '86 - Marathon Shirt 1', + 87 : '87 - Save Building Shirt 1', + 88 : '88 - Save Building Shirt 2', + 89 : '89 - Toontask Shirt 1', + 90 : '90 - Toontask Shirt 2', + 91 : '91 - Trolley Shirt 1', + 92 : '92 - Trolley Shirt 2', + 93 : '93 - Winter Shirt 1', + 94 : '94 - Halloween Costume Shirt 3', + 95 : '95 - Halloween Costume Shirt 4', + 96 : '96 - Valentines angel wings', + 97 : '97 - Scientist top 1', + 98 : '98 - Scientist top 2', + 99 : '99 - Scientist top 3', + 100 : '100 - Silly Mailbox Shirt', + 101 : '101 - Silly Trashcan Shirt', + 102 : '102 - Loony Labs Shirt', + 103 : '103 - Silly Hydrant Shirt', + 104 : '104 - Sillymeter Whistle Shirt', + 105 : '105 - Silly Cog-Crusher Shirt', + 106 : '106 - Most Cogs Defeated Shirt', + 107 : '107 - Victory Party Shirt 1', + 108 : '108 - Victory Party Shirt 2', + } + +# Note: While adding names to the ToonTopsDict make sure there is a space after the number. +# For eg: 'xx - ' and not 'xx-'. The code will crash if there is no space. + +BoyBottomsDict = { + 0 : '00 - plain w/ pockets', + 1 : '01 - belt', + 2 : '02 - cargo', + 3 : '03 - hawaiian', + 4 : '04 - side stripes', + 5 : '05 - soccer shorts ', + 6 : '06 - flames side stripes', + 7 : '07 - denim', + 8 : "08 - Valentine's Day", + 9 : '09 - Orange/blue stripes', + 10 : '10 - Blue/gold stripes', + 11 : '11 - Leprechaun Shorts', + 12 : "12 - Cowboy Shorts 1", + 13 : "13 - Cowboy Shorts 2", + 14 : "14 - July 4th Shorts", + 15 : "15 - Green stripes", + + # Pyjama shorts + 16 : "16 - Blue Banana Pajama pants", + 17 : "17 - Red Horn Pajama pants", + 18 : "18 - Purple Glasses Pajama pants", + + # Winter Holiday Shorts + 19 : "19 - Winter Holiday Shorts Style 1", + 20 : "20 - Winter Holiday Shorts Style 2", + 21 : "21 - Winter Holiday Shorts Style 3", + 22 : "22 - Winter Holiday Shorts Style 4", + + # 2009 Valentines Day Shorts + 23 : "23 - Valentines Shorts 1", + 24 : "24 - Valentines Shorts 2", + + # Special award clothing + 25 : "25 - Fishing", + 26 : "26 - Gardening", + 27 : "27 - Party", + 28 : "28 - Racing", + 29 : "29 - Summer", + 30 : '30 - Golf Shorts 1', + 31 : '31 - Halloween Costume Shorts 1', + 32 : '32 - Halloween Costume Shorts 2', + 33 : '33 - Save Building Shorts 1', + 34 : '34 - Trolley Shorts 1', + 35 : '35 - Halloween Costume Shorts 3', + 36 : '36 - Halloween Costume Shorts 4', + 37 : '37 - Scientist bottom 1', + 38 : '38 - Scientist bottom 2', + 39 : '39 - Scientist bottom 3', + 40 : '40 - Cog-Crusher Shorts', + } + +GirlBottomsDict = { + 0 : '00 - solid', + 1 : '01 - polka dots', + 2 : '02 - vertical stripes', + 3 : '03 - horizontal stripe', + 4 : '04 - flower print', + 5 : '05 - plain w/ pockets', + 6 : '06 - flower', + 7 : '07 - 2 pockets', + 8 : '08 - denim1', + 9 : '09 - denim2', + 10 : '10 - blue with tan', + 11 : '11 - purple with pink', + 12 : '12 - teal with yellow', + 13 : "13 - Valentine's Day", + 14 : '14 - Rainbow skirt', + 15 : '15 - Leprechaun shorts', + 16 : '16 - Cowboy Skirt 1', + 17 : '17 - Cowboy Skirt 2', + 18 : '18 - July 4th Skirt', + 19 : '19 - Blue with flower', + + # Pyjama shorts + 20 : '20 - Blue Banana Pajama pants', + 21 : '21 - Red Horn Pajama pants', + 22 : '22 - Purple Glasses Pajama pants', + + # Winter Holiday Skirts + 23 : '23 - Winter Holiday Skirt Style 1', + 24 : '24 - Winter Holiday Skirt Style 2', + 25 : '25 - Winter Holiday Skirt Style 3', + 26 : '26 - Winter Holiday Skirt Style 4', + + # 2009 Valentines Day Skirts + 27 : '27 - Valentines Skirt 1', + 28 : '28 - Valentines Skirt 2', + + # Special award clothing + 29 : "29 - Fishing", + 30 : "30 - Gardening", + 31 : "31 - Party", + 32 : "32 - Racing", + 33 : "33 - Summer", + 34 : '34 - Golf Skirt 1', + 35 : '35 - Halloween Costume Skirt 1', + 36 : '36 - Halloween Costume Skirt 2', + 37 : '37 - Save Building Skirt 1', + 38 : '38 - Trolley Skirt 1', + 39 : '39 - Halloween Costume Skirt 3', + 40 : '40 - Halloween Costume Skirt 4', + 41 : '41 - Scientist bottom 1', + 42 : '42 - Scientist bottom 2', + 43 : '43 - Scientist bottom 3', + 44 : '44 - Cog-Crusher Shorts', + } + +ChatCategories = { + 1: 'Basic', + 100 : 'HELLO', + 200 : "GOODBYE", + 300 : "HAPPY", + 400 : "SAD", + 500 : "FRIENDLY", + 600 : "You...", + 700 : "I like...", + 800 : "SORRY", + 900 : "STINKY", + 1000 : "PLACES", + 1100 : "Let's go...", + 1200 : "TOONTASKS", + 1300 : "I think...", + 1400 : "BATTLE", + 1500 : "BATTLE Submenu", + 1600 : "GAG SHOP", + 1700 : "FACTORY", + 1800 : "Sellbot Factory 1", + 1900 : "Sellbot Factory 2", + 2000 : "Option Page Colors", + 10000 : "Promotional", + 20000 : "Cog Phrases", + 20100 : "Cog Phrases", + 20200 : "Cog Phrases", + 20300 : "Cog Phrases", + 21000 : "DOODLES", + 21200 : "DOODLES Tricks", + 50000 : "PIRATES", + 50100 : "PIRATES Common", + 50200 : "PIRATES Insults", + 50300 : "PIRATES Places", + 60100 : "GATEWAY Greetings", + 60200 : "GATEWAY Bye", + 60300 : "GATEWAY Happy", + 60400 : "GATEWAY Sad", + 60500 : "GATEWAY Places", + } + +chatDict = OTPLocalizer.SpeedChatStaticText +chatKeys = chatDict.keys() +chatKeys.sort() + +customChatDict = OTPLocalizer.CustomSCStrings +customChatKeys = customChatDict.keys() +customChatKeys.sort() + +faceoffTaunts = OTPLocalizer.SuitFaceoffTaunts +faceoffTauntsKeys = faceoffTaunts.keys() +faceoffTauntsKeys.sort() + +attackTaunts = TTLocalizer.SuitAttackTaunts +attackTauntsKeys = attackTaunts.keys() +attackTauntsKeys.sort() + +namegen = NameGenerator.NameGenerator() + +ToonAnimList = [ + "angry", + "applause", + "bank", + "book", + "bored", + "bow", + "cast", + "catch-eatneutral", + "catch-eatnrun", + "catch-intro-throw", + "catch-neutral", + "catch-run", + "confused", + "conked", + "cringe", + "curtsy", + "down", + "firehose", + "fish", + "fish-end", + "fish-neutral", + "happy-dance", + "hold-bottle", + "hold-magnet", + "hypnotize", + "juggle", + "jump", + "jump-idle", + "jump-land", + "jump-squat", + "left", + "lose", + "melt", + "neutral", + "phoneBack", + "phoneNeutral", + "pole", + "pole-neutral", + "pushbutton", + "reel", + "reel-H", + "reel-neutral", + "right", + "run", + "running-jump", + "running-jump-idle", + "running-jump-land", + "running-jump-squat", + "sad-neutral", + "sad-walk", + "shrug", + "sidestep-left", + "sit", + "sit-start", + "slip-backward", + "slip-forward", + "smooch", + "sound", + "spit", + "sprinkle-dust", + "struggle", + "swim", + "takePhone", + "teleport", + "think", + "throw", + "tickle", + "toss", + "tug-o-war", + "up", + "victory", + "walk", + "water-gun", + "wave", + ] + +SuitAnimList = [ + Suit.AllSuits, + Suit.AllSuitsMinigame, + Suit.AllSuitsTutorialBattle, + Suit.AllSuitsBattle + ] + +SuitAnimCategories = [ + 'All', + 'Minigame', + 'Tutorial', + 'Battle' + ] + +SuitTrackList = [ + 'Corporate', + 'Legal', + 'Financial', + 'Sales' + ] + +SuitDNAList = [ + 'f : flunky', + 'p : pencil pusher', + 'ym : yes man', + 'mm : micromanager', + 'ds : downsizer', + 'hh : head hunter', + 'cr : corporate raider', + 'tbc : the big cheese', + + 'bf : bottom feeder', + 'b : blood sucker', + 'dt : double talker', + 'ac : ambulance chaser', + 'bs : back stabber', + 'sd : spin doctor', + 'le : legal eagle', + 'bw : bigwig', + + 'sc : short changer', + 'pp : penny pincher', + 'tw : tightwad', + 'bc : bean counter', + 'nc : number cruncher', + 'mb : money bags', + 'ls : load shark', + 'rb : robber baron', + + 'cc : cold caller', + 'tm : telemarketer', + 'nd : name dropper', + 'gh : glad hander', + 'ms : mover & shaker', + 'tf : two-face', + 'm : the mingler', + 'mh : Mr. Hollywood', + ] + +DoodleAnimList = [ + 'toBeg', + 'beg', + 'fromBeg', + 'backflip', + 'dance', + 'toDig', + 'dig', + 'fromDig', + 'disappear', + 'eat', + 'jump', + 'neutral', + 'neutralHappy', + 'neutralSad', + 'toPet', + 'pet', + 'fromPet', + 'playDead', + 'fromPlayDead', + 'reappear', + 'run', + 'rollover', + 'walkSad', + 'speak', + 'swallow', + 'swim', + 'toBall', + 'walk', + 'walkHappy', + ] + +rtmHelp = "\n- Once the program has loaded hit the ADD RANDOM TOON Button. \ +\n- MIDDLE CLICK near the toon to place a COA(center of action) marker. \ +\n- Hold the ALT KEY DOWN and use your three mouse buttons to NAVIGATE. \ +\n -LEFT click for ROTATE \ +\n -RIGHT click for ZOOM \ +\n -MIDDLE click to PAN \ +\n- Use the ANIMATION PANEL that pops up when you add a toon to manipulate the animations. \ +\n- For quickly changing through animations simply RIGHT CLICK anywhere on the screen. \ +\n- If the Animation panel is closed you can hit the ANIMS button(in the bottom row) to open it again. \ +\n- If you have multiple characters on screen, first select the character with the mouse and then open thier Aimation Panel \ +\n- Use the RADIO BUTTONS to change TOON DNA." + +# NEIGHBORHOOD DATA +# If you run this from the command line you can pass in the hood codes +# you want to load. For example: +# ppython LevelEditor.py DD TT BR +# +if sys.argv[1:]: + try: + opts, pargs = getopt.getopt(sys.argv[1:], '') + hoods = pargs + except Exception, e: + print e +# If you do not run from the command line, we just load all of them +# or you can hack this up for your own purposes. +else: + hoodString = base.config.GetString('level-editor-hoods', + 'TT DD BR DG DL MM PA') + hoods = string.split(hoodString) + +# The list of neighborhoods to edit +hoodIds = {'TT' : 'toontown_central', + 'DD' : 'donalds_dock', + 'MM' : 'minnies_melody_land', + 'BR' : 'the_burrrgh', + 'DG' : 'daisys_garden', + 'DL' : 'donalds_dreamland', + 'PA' : 'party_zone', + } + +# Init neighborhood arrays +NEIGHBORHOODS = [] +NEIGHBORHOOD_CODES = {} +for hoodId in hoods: + if hoodIds.has_key(hoodId): + hoodName = hoodIds[hoodId] + NEIGHBORHOOD_CODES[hoodName] = hoodId + NEIGHBORHOODS.append(hoodName) + else: + print 'Error: no hood defined for: ', hoodId + +# Load DNA +dnaDirectory = Filename.expandFrom(base.config.GetString("dna-directory", "$TTMODELS/src/dna")) + +try: + if dnaLoaded: + pass +except NameError: + print "Loading LevelEditor for hoods: ", hoods + # DNAStorage instance for storing level DNA info + # We need to use the __builtin__.foo syntax, not the + # __builtins__["foo"] syntax, since this file runs at the top + # level. + __builtin__.DNASTORE = DNASTORE = DNAStorage() + # Load the generic storage files + loadDNAFile(DNASTORE, 'phase_4/dna/storage.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_5/dna/storage_town.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_5.5/dna/storage_estate.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_5.5/dna/storage_house_interior.dna', CSDefault, 1) + # Load all the neighborhood specific storage files + if 'TT' in hoods: + loadDNAFile(DNASTORE, 'phase_4/dna/storage_TT.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_4/dna/storage_TT_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_5/dna/storage_TT_town.dna', CSDefault, 1) + if 'DD' in hoods: + loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD_town.dna', CSDefault, 1) + if 'MM' in hoods: + loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM_town.dna', CSDefault, 1) + if 'BR' in hoods: + loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR_town.dna', CSDefault, 1) + if 'DG' in hoods: + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG_town.dna', CSDefault, 1) + if 'DL' in hoods: + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL_sz.dna', CSDefault, 1) + loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL_town.dna', CSDefault, 1) + if 'PA' in hoods: + loadDNAFile(DNASTORE, 'phase_13/dna/storage_party_sz.dna', CSDefault, 1) + __builtin__.dnaLoaded = 1 + +class RobotToonManager(DirectObject): + def __init__(self, toonParent = None): + if toonParent is None: + toonParent = render.attachNewNode('toonTop') + self.toonParent = toonParent + self.avatarDict = {} + self.avatarType = 't' + self.toonIds = NPCToons.NPCToonDict.keys() + self.namePlusIds = map( + lambda id: (TTLocalizer.NPCToonNames.get(id, 'Mystery NPC'), id), + self.toonIds) + self.namePlusIds.sort() + self.numToons = len(self.toonIds) + self.suitTrack = 'Corporate' + self.suitLevel = 0 + self.doodleHead = None + self.doodleEars = None + self.doodleNose = None + self.doodleTail = None + self.doodleBody = None + self.doodleGender = None + self.doodleColor = -1 + self.doodleColorScale = -1 + self.doodleEyeColor = -1 + self.selectedToon = None + self.s1 = loader.loadModel('models/misc/smiley') + self.s1.reparentTo(render) + self.s1.hide() + self.s2 = loader.loadModel('models/misc/smiley') + self.s2.reparentTo(render) + self.s2.hide() + self.fDirectMode = 0 + self.iRay = SelectionRay(base.cam) + # See if a magin manager exists, if so, use it + try: + self.marginManager = base.marginManager + except AttributeError: + self.initNametagGlobals() + self.skyFiles = ["phase_3.5/models/props/TT_sky", + "phase_3.5/models/props/BR_sky", + "phase_6/models/props/MM_sky", + "phase_8/models/props/DL_sky", + "phase_9/models/cogHQ/cog_sky", + ] + self.skies = [] + for f in self.skyFiles: + sky = loader.loadModel(f) + sky.setScale(1.0) + sky.setFogOff() + sky.setZ(-10) + self.skies.append(sky) + # Just to make compatible with cloud sky + self.skies[4].attachNewNode('Sky') + self.skyIndex = -1 + self.sky = self.skies[0] + self.fGrid = 1 + self.fRender2d = 1 + # Drop a toon + self.accept('f10', self.makeRandomToon) + self.accept('f11', self.toggleRender2d) + self.accept('DIRECT_selectedNodePath', self.findSelectedToon) + self.accept('DIRECT-mouse3', self.nextAnim) + self.accept('f9', base.screenshot) + if base.__class__ != ShowBase.ShowBase: + # If not a plain showbase object, add these key bindings + self.accept('f12', self.toggleDirectMode) +## self.pieMenu = TextPieMenu(['start pos', 'end pos', +## 'walk', 'sad-walk', 'run', +## 'victory', 'neutral'], +## radius = 0.35, +## action = self.pieMenuCommand) + + self.animDisplay = TextNode('Animation Name') + self.animDisplay.setText('Animation: None') + self.animDisplay.setTextColor(0.5, 0.5, 1.0, 1.0) + self.animDisplay.setShadow(0.1, 0.1) + self.animDisplayNP = aspect2d.attachNewNode(self.animDisplay) + self.animDisplayNP.setScale(0.05) + self.animDisplayNP.setPos(-1.0,0.0,0.85) + + # [gjeon] to find out currently moving camera in maya mode + self.mouseMayaCamera = True + base.direct.cameraControl.useMayaCamControls = True + base.direct.cameraControl.lockRoll = True + self.styleManager = LevelStyleManager(NEIGHBORHOODS, NEIGHBORHOOD_CODES) + + self.setLastAngle(0.0) + + base.enableParticles() + + self.animPanel = None + + def createToplevel(self, dnaNode, nodePath = None): + # When you create a new level, data is added to this node + # When you load a DNA file, you replace this node with the new data + self.DNAToplevel = dnaNode + self.DNAData.add(self.DNAToplevel) + if nodePath: + # Node path given, use it + self.NPToplevel = nodePath + self.NPToplevel.reparentTo(self) + else: + # No node path given, traverse + self.NPToplevel = self.DNAToplevel.traverse(self, DNASTORE, 1) + # Update parent pointers + self.DNAParent = self.DNAToplevel + self.NPParent = self.NPToplevel + self.VGParent = None + # Add toplevel node path for suit points + self.suitPointToplevel = self.NPToplevel.attachNewNode('suitPoints') + + def addProp(self, propType): + print "addProp %s " % propType + # Record new prop type + self.setCurrent('prop_texture', propType) + # And create new prop + newDNAProp = DNAProp(propType + '_DNARoot') + newDNAProp.setCode(propType) + newDNAProp.setPos(VBase3(0)) + newDNAProp.setHpr(VBase3(0)) + # Now place new prop in the world + self.initNodePath(newDNAProp) + + def toggleReaderPollTask(self): + self.fReaderPollTask = 1 - self.fReaderPollTask + if self.fReaderPollTask: + base.cr.startReaderPollTask() + direct.selectedNPReadout.setText('POLL START') + direct.selectedNPReadout.reparentTo(aspect2d) + else: + base.cr.stopReaderPollTask() + direct.selectedNPReadout.setText('POLL STOP') + direct.selectedNPReadout.reparentTo(aspect2d) + def printCameraPosition(self): + base.localAvatar.printCameraPosition(base.localAvatar.cameraIndex) + def showHiRes(self, switchIn = 10000): + for t in self.avatarDict.values(): + t.showHiRes(switchIn = switchIn) + def findToonTop(self): + if last: + np = last.findNetTag('robotAvatar') + if not np.isEmpty(): + np.select() + def makeRandomToon(self, toNpcId = None): + # Check for intersection + entry = self.iRay.pickGeom( + skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA) + if not entry: + # Try again just at the center of the screen + entry = self.iRay.pickGeom( + skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA, + xy = (0, -0.2)) + # If we got a valid intersection point + if entry: + self.s1.setPos(camera, entry.getSurfacePoint(entry.getFromNodePath())) + else: + self.s1.setPos(camera, 0, 15, -2) + startPos = Point3(self.s1.getPos()) + endPos = Point3(startPos + Vec3(0,10,0)) + if self.avatarType == 't': + if toNpcId == None: + toNpcId = self.toonIds[randint(0,self.numToons)] + a = RobotToon(description = toNpcId, parent = self.toonParent, + startPos = startPos, endPos = endPos) + elif self.avatarType == 's': + if self.suitTrack == 'Corporate': + track = 'c' + elif self.suitTrack == 'Legal': + track = 'l' + elif self.suitTrack == 'Financial': + track = 'm' + elif self.suitTrack == 'Sales': + track = 's' + else: + track = ['c','l','m','s'][randint(0,3)] + if self.suitLevel < 0: + level = randint(1,8) + else: + level = self.suitLevel + 1 + a = RobotSuit(description = [level,track], + parent = self.toonParent, + startPos = startPos, endPos = endPos) + else: + desc = PetDNA.getRandomPetDNA() + if self.doodleHead != None: + desc[0] = self.doodleHead + if self.doodleEars != None: + desc[1] = self.doodleEars + if self.doodleNose != None: + desc[2] = self.doodleNose + if self.doodleTail != None: + desc[3] = self.doodleTail + if self.doodleBody != None: + desc[4] = self.doodleBody + # not sure why, but None didn't work as a cardinal value in these cases + if self.doodleColor != -1: + desc[5] = self.doodleColor + if self.doodleColorScale != -1: + desc[6] = self.doodleColorScale + if self.doodleEyeColor != -1: + desc[7] = self.doodleEyeColor + if self.doodleGender != None: + desc[8] = self.doodleGender + a = RobotDoodle(description = desc, + parent = self.toonParent, + startPos = startPos, endPos = endPos) + + a.nametag.manage(self.marginManager) + self.avatarDict[a.id()] = a + a.select() + if self.avatarType == 't': + self.setToonAnimState('neutral') + self.faceCamera() + a.setStartHpr(a.getHpr()) + messenger.send('SGE_Update Explorer', [a]) + + def makeToonFromProperties(self, properties, + pos, hpr, + startPos, startHpr, + endPos, endHpr, state): + t = RobotToon(description = properties, parent = self.toonParent, + startPos = startPos, startHpr = startHpr, + endPos = endPos, endHpr = endHpr) + t.nametag.manage(self.marginManager) + t.setPosHpr(pos, hpr) + self.avatarDict[t.id()] = t + t.select() + self.setToonAnimState(state) + messenger.send('SGE_Update Explorer', [t]) + return t + def makeToonFromServerString(self, serverString, + pos = Point3(0), hpr = Point3(0), + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral'): + dna = ToonDNA.ToonDNA() + netString = self.convertServerDNAString(serverString) + dna.makeFromNetString(netString) + return self.makeToonFromProperties( + dna.asTuple(),pos,hpr,startPos,startHpr,endPos,endHpr,state) + def convertServerDNAString(self, serverString): + # Strip out blank space and take last 30 characters + serverString = serverString.replace(' ', '') + serverString = serverString[-30:] + # Create a datagram from server string + dg = PyDatagram() + for i in range(0,len(serverString),2): + eval('dg.addUint8(0x%s)' % serverString[i:i+2]) + return dg.getMessage() + def _setStartPos(self): + self.setStartPos((self.pieMenu.originX, self.pieMenu.originY)) + def _setEndPos(self): + self.setEndPos((self.pieMenu.originX, self.pieMenu.originY)) + def setStartPos(self, xy = None): + # Check for intersection + entry = self.iRay.pickGeom( + xy = xy, skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA) + # If we got a valid intersection point + if entry and (self.selectedToon != None): + st = self.selectedToon + st.setPos(camera, entry.getSurfacePoint(entry.getFromNodePath())) + st.setStartPos(st.getPos()) + st.setStartHpr(st.getHpr()) + st.updateWalkIval() + def setEndPos(self, xy = None): + # Check for intersection + entry = self.iRay.pickGeom( + xy = xy, skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA) + # If we got a valid intersection point + if entry and (self.selectedToon != None): + st = self.selectedToon + st.setPos(camera, entry.getSurfacePoint(entry.getFromNodePath())) + st.setEndPos(st.getPos()) + st.setEndHpr(st.getHpr()) + st.updateWalkIval() + def findSelectedToon(self, nodePath): + id = nodePath.id() + np = nodePath.findNetTag('robotAvatar') + if not np.isEmpty(): + if np.id() != nodePath.id(): + np.select() + self.selectedToon = self.avatarDict.get(np.id()) + if self.panel: + self.panel.updateToonInfo() + def makeSuitFromProperties(self, properties, + pos, hpr, + startPos, startHpr, + endPos, endHpr, state): + t = RobotSuit(description = properties[2], parent = self.toonParent, + startPos = startPos, startHpr = startHpr, + endPos = endPos, endHpr = endHpr, state = state) + t.nametag.manage(self.marginManager) + t.setPosHpr(pos, hpr) + self.avatarDict[t.id()] = t + messenger.send('SGE_Update Explorer', [t]) + return t + def openCrowdFilePanel(self): + tcfFilename = askopenfilename( + defaultextension = '.tcf', initialdir = '.', + filetypes = (('Toon Crowd', '*.tcf'),('All files', '*')), + title = 'Open Toon Crowd File') + if tcfFilename: + self.openCrowdFile(tcfFilename) + def openCrowdFile(self, tcfFilename): + filename = Filename(tcfFilename) + f = open(filename.toOsSpecific(), 'rb') + rawData = f.readlines() + for line in rawData: + (type,props,pos,hpr,startPos,startHpr, + endPos,endHpr,state) = self.parseAvatarProperties(line) + if type == 't': + self.makeToonFromProperties( + props,pos,hpr,startPos,startHpr,endPos,endHpr,state) + else: + self.makeSuitFromProperties( + props,pos,hpr,startPos,startHpr,endPos,endHpr,state) + f.close() + def parseAvatarProperties(self, line): + line = string.strip(line) + if line: + line = line.split('*') + i = 0 + type = line[i];i+=1 + if type == 't': + head = line[i];i+=1 + torso = line[i];i+=1 + legs = line[i];i+=1 + gender = line[i];i+=1 + armColor = string.atoi(line[i]);i+=1 + gloveColor = string.atoi(line[i]);i+=1 + legColor = string.atoi(line[i]);i+=1 + headColor = string.atoi(line[i]);i+=1 + topTexture = string.atoi(line[i]);i+=1 + topTextureColor = string.atoi(line[i]);i+=1 + sleeveTexture = string.atoi(line[i]);i+=1 + sleeveTextureColor = string.atoi(line[i]);i+=1 + bottomTexture = string.atoi(line[i]);i+=1 + bottomTextureColor = string.atoi(line[i]);i+=1 + props = [head, torso, legs, gender, + armColor, gloveColor, legColor, headColor, + topTexture, topTextureColor, sleeveTexture, + sleeveTextureColor, bottomTexture, + bottomTextureColor] + else: + body = line[i];i+=1 + dept = line[i];i+=1 + name = line[i];i+=1 + props = [body, dept, name] + x = string.atof(line[i]);i+=1 + y = string.atof(line[i]);i+=1 + z = string.atof(line[i]);i+=1 + h = string.atof(line[i]);i+=1 + p = string.atof(line[i]);i+=1 + r = string.atof(line[i]);i+=1 + x1 = string.atof(line[i]);i+=1 + y1 = string.atof(line[i]);i+=1 + z1 = string.atof(line[i]);i+=1 + h1 = string.atof(line[i]);i+=1 + p1 = string.atof(line[i]);i+=1 + r1 = string.atof(line[i]);i+=1 + x2 = string.atof(line[i]);i+=1 + y2 = string.atof(line[i]);i+=1 + z2 = string.atof(line[i]);i+=1 + h2 = string.atof(line[i]);i+=1 + p2 = string.atof(line[i]);i+=1 + r2 = string.atof(line[i]);i+=1 + state = line[i] + return (type, props, + Point3(x,y,z), + Point3(h,p,r), + Point3(x1,y1,z1), + Point3(h1,p1,r1), + Point3(x2,y2,z2), + Point3(h2,p2,r2), + state) + def saveCrowdFile(self): + tcfFilename = asksaveasfilename( + defaultextension = '.tcf', initialdir = '.', + filetypes = (('Toon Crowd', '*.tcf'),('All files', '*')), + title = 'Save Toon Crowd File') + if tcfFilename: + filename = Filename(tcfFilename) + f = open(filename.toOsSpecific(), 'wb') + for t in self.avatarDict.values(): + type = t.style.type + if type == 't': + style = t.style.asTuple() + styleStr = ("%s*%s*%s*%s*%d*%d*%d*%d*%d*%d*%d*%d*%d*%d" % + (style[0], style[1], style[2], style[3], + style[4], style[5], style[6], style[7], + style[8], style[9], style[10], style[11], + style[12], style[13])) + else: + style = t.style + styleStr = ("%s*%s*%s" % + (style.body, style.dept, style.name)) + pos = t.getPos() + hpr = t.getHpr() + pose = ("%0.2f*%0.2f*%0.2f*%0.2f*%0.2f*%0.2f" % + (pos[0],pos[1],pos[2],hpr[0],hpr[1],hpr[2])) + startPose = ("%0.2f*%0.2f*%0.2f*%0.2f*%0.2f*%0.2f" % + (t.startPos[0],t.startPos[1],t.startPos[2], + t.startHpr[0],t.startHpr[1],t.startHpr[2])) + endPose = ("%0.2f*%0.2f*%0.2f*%0.2f*%0.2f*%0.2f" % + (t.endPos[0],t.endPos[1],t.endPos[2], + t.endHpr[0],t.endHpr[1],t.endHpr[2])) + state = t.state + f.write("%s*%s*%s*%s*%s*%s\n" % + (type,styleStr,pose,startPose,endPose,state)) + f.close() + def pieMenuCommand(self, cmd): + if base.config.GetBool('want-new-anims', 1): + return + if self.selectedToon: + if cmd == 'start pos': + self._setStartPos() + elif cmd == 'end pos': + self._setEndPos() + else: + self.selectedToon.setAnimState(cmd) + def toggleRender2d(self): + self.fRender2d = 1 - self.fRender2d + if self.fRender2d: + render2d.show() + else: + render2d.hide() + + def clearToons(self): + for t in self.avatarDict.values(): + t.nametag.unmanage(self.marginManager) + t.destroy() + self.avatarDict = {} + self.selectedToon = None + + def toggleDirectMode(self): + self.fDirectMode = 1 - self.fDirectMode + if self.fDirectMode: + print 'SWITCH TO DIRECT MODE' + # Start up direct + taskMgr.removeTasksMatching('updateSmartCamera*') + camera.wrtReparentTo(render) + base.startTk() + base.startDirect() + direct.selectedNPReadout.setText('DIRECT MODE') + direct.selectedNPReadout.reparentTo(aspect2d) + else: + print 'SWITCH TO TOONTOWN MODE' + # Return to toontown mode + if direct: + direct.deselectAll() + direct.disable() + camera.wrtReparentTo(base.localAvatar) + base.localAvatar.startUpdateSmartCamera() + + def initNametagGlobals(self): + """initNametagGlobals(self) + + Should be called once during startup to initialize a few + defaults for the Nametags. + """ + arrow = loader.loadModel('phase_3/models/props/arrow') + card = loader.loadModel('phase_3/models/props/panel') + speech3d = ChatBalloon(loader.loadModelNode( + 'phase_3/models/props/chatbox')) + thought3d = ChatBalloon(loader.loadModelNode( + 'phase_3/models/props/chatbox_thought_cutout')) + speech2d = ChatBalloon(loader.loadModelNode( + 'phase_3/models/props/chatbox_noarrow')) + chatButtonGui = loader.loadModel( + "phase_3/models/gui/chat_button_gui") + NametagGlobals.setCamera(base.cam) + NametagGlobals.setArrowModel(arrow) + NametagGlobals.setNametagCard(card, VBase4(-0.5, 0.5, -0.5, 0.5)) + NametagGlobals.setMouseWatcher(base.mouseWatcherNode) + NametagGlobals.setSpeechBalloon3d(speech3d) + NametagGlobals.setThoughtBalloon3d(thought3d) + NametagGlobals.setSpeechBalloon2d(speech2d) + NametagGlobals.setThoughtBalloon2d(thought3d) + NametagGlobals.setPageButton( + PGButton.SReady, chatButtonGui.find("**/Horiz_Arrow_UP")) + NametagGlobals.setPageButton( + PGButton.SDepressed, chatButtonGui.find("**/Horiz_Arrow_DN")) + NametagGlobals.setPageButton( + PGButton.SRollover, chatButtonGui.find("**/Horiz_Arrow_Rllvr")) + NametagGlobals.setQuitButton( + PGButton.SReady, chatButtonGui.find("**/CloseBtn_UP")) + NametagGlobals.setQuitButton( + PGButton.SDepressed, chatButtonGui.find("**/CloseBtn_DN")) + NametagGlobals.setQuitButton( + PGButton.SRollover, chatButtonGui.find("**/CloseBtn_Rllvr")) + + # We don't need these instances any more. + arrow.removeNode() + card.removeNode() + chatButtonGui.removeNode() + rolloverSound = DirectGuiGlobals.getDefaultRolloverSound() + if rolloverSound: + NametagGlobals.setRolloverSound(rolloverSound) + clickSound = DirectGuiGlobals.getDefaultClickSound() + if clickSound: + NametagGlobals.setClickSound(clickSound) + + # For now, we'll leave the Toon at the same point as the + # camera. When we have a real toon later, we'll change it. + NametagGlobals.setToon(base.cam) + + # We need a node to be the parent of all of the 2-d onscreen + # messages along the margins. This should be in front of many + # things, but not all things. + self.marginManager = MarginManager() + self.margins = \ + base.aspect2d.attachNewNode( + self.marginManager, DirectGuiGlobals.MIDGROUND_SORT_INDEX + 1) + + # And define a bunch of cells along the margins. + mm = self.marginManager + self.leftCells = [ + mm.addGridCell(0, 1, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(0, 2, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(0, 3, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop) + ] + self.bottomCells = [ + mm.addGridCell(0.5, 0, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(1.5, 0, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(2.5, 0, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(3.5, 0, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(4.5, 0, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop) + ] + self.rightCells = [ + mm.addGridCell(5, 2, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop), + mm.addGridCell(5, 1, base.a2dLeft, base.a2dRight, + base.a2dBottom, base.a2dTop) + ] + + def faceCamera(self): + toon = self.selectedToon + if toon is None: + return + toon.lookAt(camera) + h = toon.getH(render) + toon.setHpr(render,h,0,0) + + def toggleSky(self): + self.stopSky() + self.skyIndex += 1 + if self.skyIndex >= len(self.skies): + self.skyIndex = -1 + else: + self.sky = self.skies[self.skyIndex] + self.startSky() + + def startSky(self): + SkyUtil.startCloudSky(self) + + def skyTrack(self, task): + # Every frame nail the sky to 0, with no rotation + # Actually we can raise the sky to the lowest point + # on the horizon since you will not be able to see + # over or between buildings. Drawing part of the sky + # that will always be behind buildings is wasteful. + # DL has some 10 foot fences, so the highest we can + # currently put the sky is at 10 feet. + # Actually, the tag minigame uses the sky now, and wants + # it at 0.0, so until we have real artwork there, just + # keep it at 0 + # Rotate the sky slowly to simulate clouds passing + task.h += (globalClock.getDt() * 0.25) + if not task.cloud1.isEmpty(): + task.cloud1.setH(task.h) + if not task.cloud2.isEmpty(): + task.cloud2.setH(-task.h * 0.8) + return Task.cont + + def stopSky(self): + # Remove the sky task just in case it was spawned. + taskMgr.remove("skyTrack") + self.sky.reparentTo(hidden) + + def toggleGrid(self): + self.fGrid = 1 - self.fGrid + if self.fGrid: + direct.grid.enable() + else: + direct.grid.disable() + + def popupControls(self): + self.panel = RobotToonControlPanel(self) + + def destroy(self): + self.ignore('f9') + self.ignore('f10') + self.ignore('f11') + self.ignore('f12') + self.ignore('DIRECT_selectedNodePath') + self.s1.removeNode() + self.s2.removeNode() + # self.pieMenu.destroy() + self.clearToons() + + def getAttribute(self, attribute): + """ Return specified attribute for current neighborhood """ + return self.styleManager.getAttribute(attribute) + + def setCurrent(self, attribute, newCurrent): + """ Set neighborhood's current selection for specified attribute """ + self.getAttribute(attribute).setCurrent(newCurrent, fEvent = 0) + + def initNodePath(self, dnaNode, hotKey = None): + """ + Update DNA to reflect latest style choices and then generate + new node path and add it to the scene graph + """ + # Determine dnaNode Class Type + nodeClass = DNAGetClassType(dnaNode) + # Did the user hit insert or space? + if hotKey: + # Yes, make a new copy of the dnaNode + dnaNode = dnaNode.__class__(dnaNode) + # And determine dnaNode type and perform any type specific updates + if nodeClass.eq(DNA_PROP): + dnaNode.setCode(self.getCurrent('prop_texture')) + elif nodeClass.eq(DNA_STREET): + dnaNode.setCode(self.getCurrent('street_texture')) + elif nodeClass.eq(DNA_FLAT_BUILDING): + # If insert, pick a new random style + if hotKey == 'insert': + self.setRandomBuildingStyle(dnaNode, dnaNode.getName()) + # Get a new building width + self.setCurrent('building_width', + self.getRandomWallWidth()) + dnaNode.setWidth(self.getCurrent('building_width')) + + # Position it + # Check for intersection + entry = self.iRay.pickGeom( + skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA) + if not entry: + # Try again just at the center of the screen + entry = self.iRay.pickGeom( + skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA, + xy = (0, -0.2)) + # If we got a valid intersection point + if entry: + self.s1.setPos(camera, entry.getSurfacePoint(entry.getFromNodePath())) + else: + self.s1.setPos(camera, 0, 15, -2) + + dnaNode.setPos(self.s1.getPos()) + # Initialize angle to match last object + dnaNode.setHpr(Vec3(self.getLastAngle(), 0, 0)) + + # And create the geometry + newNodePath = dnaNode.traverse(self.toonParent, DNASTORE, 1) + newNodePath.reparentTo(self.toonParent) + + base.direct.select(newNodePath) + self.faceCamera() + + messenger.send('SGE_Update Explorer', [newNodePath]) + + # Angle of last object added to level + def setLastAngle(self, angle): + self.lastAngle = angle + + def getLastAngle(self): + return self.lastAngle + + def setToonAnimState(self,anim): + self.selectedToon.setAnimState(anim) + self.animDisplay.setText("Animation: "+str(anim)) + + if not self.animPanel: + self.showAnimPanel() + + self.animPanel.actorControlList[0].selectAnimNamed(anim) + self.animPanel.actorControlList[0].updateDisplay() + self.animPanel.actorControlList[0].animMenu.selectitem(anim) + self.animPanel.playActorControls() + self.animPanel.loopVar.set(1) + + def showAnimPanel(self): + # show animation panel for currently selected actors + from direct.tkpanels import AnimPanel + if self.selectedToon: + self.animPanel = AnimPanel.AnimPanel( + self.selectedToon,session = self) + self.animPanel.setDestroyCallBack(self.animPanelClosed) + + if self.selectedToon and self.selectedToon.state: + self.setToonAnimState(self.selectedToon.state) + + def animPanelClosed(self): + self.animPanel = None + + def getState(self): + if not self.animPanel: + self.showAnimPanel() + if self.selectedToon: + self.selectedToon.state = self.animPanel.actorControlList[0]['active'] + return self.selectedToon.state + + def nextAnim(self, modifiers = 0): + if not self.animPanel: + self.showAnimPanel() + if self.selectedToon and modifiers==0: + currState = self.getState() + try: + i = self.animPanel.actorControlList[0]['animList'].index(currState) + except: + i = -1 + + newIndex = i+1 + if (newIndex) < len(ToonAnimList): + self.setToonAnimState(self.animPanel.actorControlList[0]['animList'][newIndex]) + else: + self.setToonAnimState(self.animPanel.actorControlList[0]['animList'][0]) + + +""" Robot Toon Manager Control Panel module """ +class RobotToonControlPanel(AppShell): + # Override class variables + appname = 'Robot Toon Manager Panel' + frameWidth = 650 + frameHeight = 600 + usecommandarea = 1 + usestatusarea = 0 + contactname = 'Mark Mine' + contactphone = '(818) 623-3915' + contactemail = "Mark.Mine@disney.com" + + def __init__(self, robotToonManager, **kw): + DGG.INITOPT = Pmw.INITOPT + optiondefs = ( + ) + self.defineoptions(kw, optiondefs) + + self.rtm = robotToonManager + + AppShell.__init__(self) + + def appInit(self): + # Initialize any instance variables you use here + self.fDoIt = 0 + self.colorButtonList = [] + self.topsVariants = [] + self.bottomsVariants = [] + self.npToplevel = None + self.maleTopsList = ToonDNA.getAllTops('m') + self.maleTopsDict = self.sortVariants(self.maleTopsList) + self.maleTopsKeys = self.maleTopsDict.keys() + self.maleTopsKeys.sort() + self.maleTopsNames = map(lambda x: ToonTopsDict[x], self.maleTopsKeys) + self.maleBottomsList = ToonDNA.getAllBottoms('m') + self.maleBottomsDict = self.sortVariants(self.maleBottomsList) + self.maleBottomsKeys = self.maleBottomsDict.keys() + self.maleBottomsKeys.sort() + self.maleBottomsNames = map( + lambda x: BoyBottomsDict[x], self.maleBottomsKeys) + self.femaleTopsList = ToonDNA.getAllTops('f') + self.femaleTopsDict = self.sortVariants(self.femaleTopsList) + self.femaleTopsKeys = self.femaleTopsDict.keys() + self.femaleTopsKeys.sort() + self.femaleTopsNames = map(lambda x: ToonTopsDict[x], + self.femaleTopsKeys) + self.femaleBottomsList = ToonDNA.getAllBottoms('f') + self.femaleBottomsDict = self.sortVariants(self.femaleBottomsList) + self.femaleSkirtsList = ToonDNA.getAllBottoms('f','skirts') + self.femaleSkirtsDict = self.sortVariants(self.femaleSkirtsList) + self.femaleSkirtsKeys = self.femaleSkirtsDict.keys() + self.femaleSkirtsKeys.sort() + self.femaleSkirtsNames = map( + lambda x: GirlBottomsDict[x], self.femaleSkirtsKeys) + self.femaleShortsList = ToonDNA.getAllBottoms('f','shorts') + self.femaleShortsDict = self.sortVariants(self.femaleShortsList) + self.femaleShortsKeys = self.femaleShortsDict.keys() + self.femaleShortsKeys.sort() + self.femaleShortsNames = map( + lambda x: GirlBottomsDict[x], self.femaleShortsKeys) + self.doodleColorButtonList = [] + self.doodleColorScaleButtonList = [] + self.doodleEyeColorButtonList = [] + + self.lastPath = None + + def sortVariants(self, styleList): + styleDict = {} + styleList.sort() + for style in styleList: + idx = style[0] + variantList = styleDict.get(idx, []) + variantList.append(style) + styleDict[idx] = variantList + return styleDict + + def createInterface(self): + # Create the tk components + interior = self.interior() + menuBar = self.menuBar + + # Create a command entry + menuBar.addmenu('Crowds', 'Crowd File Operations') + menuBar.addmenuitem('Crowds', 'command', + 'Open Crowd File', + label = 'Open...', + command = self.rtm.openCrowdFilePanel) + + menuBar.addmenuitem('Crowds', 'command', + 'Save Crowd File', + label = 'Save...', + command = self.rtm.saveCrowdFile) + + menuBar.addmenuitem('Crowds', 'command', + 'Clear Crowd', + label = 'Clear Crowd...', + command = self.rtm.clearToons) + + menuBar.addmenu('Scene', 'Scene File Operations') + menuBar.addmenuitem('Scene', 'command', + 'Open DNA Scene File', + label = 'Open...', + command = self.loadSpecifiedDNAFile) + menuBar.addmenuitem('Scene', 'command', + 'Clear Scene', + label = 'Clear Scene...', + command = self.resetScene) + + menuBar.addmenu('Tools', 'Tools') + menuBar.addmenuitem('Tools', 'command', + 'Resize Window', + label = 'Resize Window...', + command = self.resizeWindow) + + menuBar.addmenuitem('Tools', 'command', + 'Toggle Anti-Aliasing', + label = 'Toggle Anti-Aliasing', + command = self.toggleAntiAliasing) + + menuBar.addmenuitem('Tools', 'command', + 'Import Maya File', + label = 'Import Maya File', + command = self.importMaya) + + menuBar.addmenuitem('File', 'command', + 'Show Anim Panel', + label = 'Show Anim Panel', + command = self.rtm.showAnimPanel) + + # Paned widget for dividing two halves + mainFrame = Frame(interior) + self.framePane = Pmw.PanedWidget(mainFrame, orient = DGG.HORIZONTAL) + self.explorerFrame = self.framePane.add('left', min = 200) + self.rightFrame = self.framePane.add('right', min = 300) + self.notebookFrame = Frame(self.rightFrame) + + self.explorer = SceneGraphExplorer(parent = self.explorerFrame, + nodePath = self.rtm.toonParent, + scrolledCanvas_hull_width = 200, + scrolledCanvas_hull_height = 50) + self.explorer.pack(fill = BOTH, expand = 1) + self.explorerFrame.pack(fill = BOTH, expand = 1) + self.explorer._node.state = 'expanded' + + # Create the notebook pages + self.notebook = Pmw.NoteBook(self.notebookFrame) + self.pageOne = self.notebook.add('Toons') + dnaFrame = Frame(self.pageOne) + self.serverString = StringVar() + self.serverStringEntry = Pmw.EntryField( + parent = dnaFrame, + labelpos = W, + label_text = 'Server String', + entry_width = 20, + entry_justify = 'right', + entry_textvar = self.serverString, + command = self._setServerString) + self.serverStringEntry.pack(side = LEFT, fill = X, expand = 1) + + self.npcButton = Menubutton(dnaFrame, width = 15, + text = "NPC Toons", + relief = RAISED, + borderwidth = 2) + self.npcMenu = Menu(self.npcButton, tearoff = 0) + self.npcButton['menu'] = self.npcMenu + self.npcButton.pack(side = LEFT, fill = X, expand = 1) + firstLetter = None + for name, id in self.rtm.namePlusIds: + if TextEncoder.upper(name[0]) != firstLetter: + firstLetter = TextEncoder.upper(name[0]) + subMenu = Menu(self.npcMenu, tearoff = 0) + self.npcMenu.add_cascade( + label = 'NPC %s' % firstLetter, + menu = subMenu) + subMenu.add_command( + label = TTLocalizer.NPCToonNames.get(id,'Unnamed NPC'), + command = lambda id=id: self.rtm.makeRandomToon(id)) + + dnaFrame.pack(fill = X, expand = 0) + + # LOD + self.lod = StringVar() + self.lod.set('1000') + lodFrame = Frame(self.pageOne) + Label(lodFrame,text='LOD:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + lodFrame, 'LOD', 'high', + self.lod, '1000', self.selectLOD, + help = 'high LOD', + side = LEFT) + self.newCreateRadiobutton( + lodFrame, 'LOD', 'medium', + self.lod, '500', self.selectLOD, + help = 'medium LOD', + side = LEFT) + self.newCreateRadiobutton( + lodFrame, 'LOD', 'low', + self.lod, '250', self.selectLOD, + help = 'low LOD', + side = LEFT) + lodFrame.pack(fill = X, expand = 0) + + # GENDER + self.gender = StringVar() + self.gender.set('m') + genderFrame = Frame(self.pageOne) + Label(genderFrame,text='Gender:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + genderFrame, 'Gender', 'Male', + self.gender, 'm', self.swapGender, + help = 'Male Toon', + side = LEFT) + self.newCreateRadiobutton( + genderFrame, 'Gender', 'Female', + self.gender, 'f', self.swapGender, + help = 'Female Toon', + side = LEFT) + genderFrame.pack(fill = X, expand = 0) + + # HEAD + self.speciesDict = { 'c' : 'Cat', 'd' : 'Dog', 'f' : 'Duck', + 'h' : 'Horse', 'm' : 'Mouse', 'r' : 'Rabbit', + 'p' : 'Monkey', 'b' : 'Bear', 's' : 'Swine' } + speciesList = self.speciesDict.values() + speciesList.sort() + self.headDict = {} + for head in ToonDNA.toonHeadTypes: + headList = self.headDict.get(self.speciesDict[head[0]], []) + headList.append(head) + self.headDict[self.speciesDict[head[0]]] = headList + + speciesFrame = Frame(self.pageOne) + Label(speciesFrame, text = 'Species:', + anchor = W, justify = LEFT, + width=6).pack(side = LEFT, expand = 0) + self.species = StringVar() + self.species.set('Cat') + for s in speciesList: + self.newCreateRadiobutton( + speciesFrame, 'Species', s, + self.species, s, self.setSpecies, + help = 'Set species to %s' % s, + side = LEFT) + speciesFrame.pack(expand = 0, fill = X) + + headFrame = Frame(self.pageOne) + Label(headFrame, text = 'Head:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.head = StringVar() + self.head.set('ls') + self.ssHeadButton = self.newCreateRadiobutton( + headFrame, 'Head', 'ss', + self.head, 'ss', self.setHead, + help = 'Set head to ss', + side = LEFT) + self.lsHeadButton = self.newCreateRadiobutton( + headFrame, 'Head', 'ls', + self.head, 'ls', self.setHead, + help = 'Set head to ls', + side = LEFT) + self.slHeadButton = self.newCreateRadiobutton( + headFrame, 'Head', 'sl', + self.head, 'sl', self.setHead, + help = 'Set head to sl', + side = LEFT) + self.llHeadButton = self.newCreateRadiobutton( + headFrame, 'Head', 'll', + self.head, 'll', self.setHead, + help = 'Set head to ll', + side = LEFT) + headFrame.pack(expand = 0, fill = X) + + eyesFrame = Frame(self.pageOne) + Label(eyesFrame, text = 'Eyes:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.eyes = StringVar() + self.eyes.set('normal') + self.normalEyesButton = self.newCreateRadiobutton( + eyesFrame, 'Eyes', 'normal', + self.eyes, 'normal', self.setEyes, + help = 'Set eyes to normal blink', + side = LEFT) + self.angryEyesButton = self.newCreateRadiobutton( + eyesFrame, 'Eyes', 'angry', + self.eyes, 'angry', self.setEyes, + help = 'Set eyes to angry blink', + side = LEFT) + self.sadEyesButton = self.newCreateRadiobutton( + eyesFrame, 'Eyes', 'sad', + self.eyes, 'sad', self.setEyes, + help = 'Set eyes to sad blink', + side = LEFT) + eyesFrame.pack(expand = 0, fill = X) + + # Muzzles + muzzleFrame = Frame(self.pageOne) + Label(muzzleFrame, text = 'Muzzles: ', width=6, anchor = W, \ + justify = LEFT).pack(side=LEFT, expand = 0) + self.muzzle = StringVar() + self.muzzle.set('normal') + self.normalMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'normal', self.muzzle, + 'normal', self.setMuzzle, + help = 'Set muzzle to normal', + side = LEFT) + + self.angryMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'angry', + self.muzzle, 'angry', self.setMuzzle, + help = 'Set muzzle to angry', + side = LEFT) + + self.sadMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'sad', + self.muzzle, 'sad', self.setMuzzle, + help = 'Set muzzle to sad', + side = LEFT) + + self.smileMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'smile', + self.muzzle, 'smile', self.setMuzzle, + help = 'Set muzzle to smile', + side = LEFT) + + self.laughMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'laugh', + self.muzzle, 'laugh', self.setMuzzle, + help = 'Set muzzle to laugh', + side = LEFT) + + self.surpriseMuzzleButton = self.newCreateRadiobutton( + muzzleFrame, 'Muzzle', 'surprise', + self.muzzle, 'surprise', self.setMuzzle, + help = 'Set muzzle to surprise', + side = LEFT) + muzzleFrame.pack(expand = 0, fill = X) + + # TORSO + self.torso = StringVar() + self.torso.set('ss') + torsoFrame = Frame(self.pageOne) + Label(torsoFrame, text = 'Torso:',width=6, + anchor = W, justify = LEFT).pack(side=LEFT,expand = 0) + self.ssButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'SS', + self.torso, 'ss', self.swapTorso, + help = 'S-Shorts', + side = LEFT) + self.msButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'MS', + self.torso, 'ms', self.swapTorso, + help = 'M-Shorts', + side = LEFT) + self.lsButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'LS', + self.torso, 'ls', self.swapTorso, + help = 'L-Shorts', + side = LEFT) + self.sdButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'SD', + self.torso, 'sd', self.swapTorso, + help = 'S-Dress', + side = LEFT) + self.mdButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'MD', + self.torso, 'md', self.swapTorso, + help = 'M-Dress', + side = LEFT) + self.ldButton = self.newCreateRadiobutton( + torsoFrame, 'Torso', 'LD', + self.torso, 'ld', self.swapTorso, + help = 'L-Dress', + side = LEFT) + torsoFrame.pack(fill = X, expand = 0) + + # LEGS + self.legs = StringVar() + self.legs.set('ss') + legsFrame = Frame(self.pageOne) + Label(legsFrame, text = 'Legs:',width=6, + anchor = W, justify = LEFT).pack(side=LEFT,expand = 0) + self.sLegsButton = self.newCreateRadiobutton( + legsFrame, 'Legs', 's', + self.legs, 's', self.swapLegs, + help = 'Short Legs', + side = LEFT) + self.mLegsButton = self.newCreateRadiobutton( + legsFrame, 'Legs', 'm', + self.legs, 'm', self.swapLegs, + help = 'Medium Legs', + side = LEFT) + self.lLegsButton = self.newCreateRadiobutton( + legsFrame, 'Legs', 'l', + self.legs, 'l', self.swapLegs, + help = 'Long Legs', + side = LEFT) + legsFrame.pack(fill = X, expand = 0) + + # COLOR MODE + self.colorMode = StringVar() + self.colorMode.set('all') + colorModeFrame = Frame(self.pageOne) + Label(colorModeFrame, text = 'Color:', width = 6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + colorModeFrame, 'ColorMode', 'All', + self.colorMode, 'all', None, + help = 'Set all body colors', + side = LEFT) + self.newCreateRadiobutton( + colorModeFrame, 'ColorMode', 'Arms', + self.colorMode, 'arms', None, + help = 'Set arm color', + side = LEFT) + self.newCreateRadiobutton( + colorModeFrame, 'ColorMode', 'Gloves', + self.colorMode, 'gloves', None, + help = 'Set gloves color', + side = LEFT) + self.newCreateRadiobutton( + colorModeFrame, 'ColorMode', 'Legs', + self.colorMode, 'legs', None, + help = 'Set legs color', + side = LEFT) + self.newCreateRadiobutton( + colorModeFrame, 'ColorMode', 'Head', + self.colorMode, 'head', None, + help = 'Set head color', + side = LEFT) + colorModeFrame.pack(fill = X, expand = 0) + + # COLOR TABLETS + colorFrame = Frame(self.pageOne) + for i in range(2): + cf = Frame(colorFrame) + for j in range(14): + index = i * 14 + j + if index < 27: + color = self.transformRGB(ToonDNA.allColorsList[index]) + b = Button(cf, width = 1, height = 1, background = color, + text = "%d" % index, + command = lambda ci=index: self.setToonColor(ci)) + b.pack(side = LEFT, fill = X, expand = 0) + self.colorButtonList.append(b) + cf.pack(fill = X, expand = 0) + colorFrame.pack(fill = X, expand = 0) + + # TOP TEXTURE + topsFrame = Frame(self.pageOne) + # Create this first so it exists when creating the counter + self.topsMenu = Pmw.OptionMenu( + parent = topsFrame, + labelpos = W, + label_text = 'Tops:', + label_width = 8, + label_anchor = W, label_justify = LEFT, + menubutton_width = 25) + self.topsMenu['command'] = lambda x: self.setTop(x,1) + self.topsMenu.pack(side = LEFT, fill = X, expand = 0) + + self.topsIndex = IntVar() + self.topsIndex.set(0) + self.topsCounter = Pmw.Counter( + parent = topsFrame, + entry_textvariable = self.topsIndex, + entryfield_value = self.topsIndex.get(), + entryfield_validate = { 'validator' : self.__switchTops }, + entryfield_entry_width = 3) + self.topsCounter['entryfield_command'] = self.setTopVariant + self.topsCounter.pack(side = LEFT, fill = X, expand = 0) + + topsFrame.pack(fill = X, expand = 0) + + # BOTTOM TEXTURE + bottomsFrame = Frame(self.pageOne) + # Create this first so it exists when creating the counter + self.bottomsMenu = Pmw.OptionMenu( + parent = bottomsFrame, + labelpos = W, + label_text = 'Bottoms', + label_width = 8, + label_anchor = W, label_justify = LEFT, + menubutton_width = 25) + self.bottomsMenu['command'] = lambda x: self.setBottom(x,1) + self.bottomsMenu.pack(side = LEFT, fill = X, expand = 0) + + self.bottomsIndex = IntVar() + self.bottomsIndex.set(0) + self.bottomsCounter = Pmw.Counter( + parent = bottomsFrame, + entry_textvariable = self.bottomsIndex, + entryfield_value = self.bottomsIndex.get(), + entryfield_validate = { 'validator' : self.__switchBottoms }, + entryfield_entry_width = 3) + self.bottomsCounter['entryfield_command'] = self.setBottomVariant + self.bottomsCounter.pack(side = LEFT, fill = X, expand = 0) + + bottomsFrame.pack(fill = X, expand = 0) + + nameFrame = Frame(self.pageOne) + Label(nameFrame, text = "Name:", width = 8, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0, + fill = X) + self.toonName = StringVar() + self.toonName.set('') + self.nameEntry = Entry(nameFrame, width = 10, + textvariable = self.toonName) + self.nameEntry.bind('', self.setToonName) + self.nameEntry.pack(side = LEFT, expand = 1, fill = X) + + self.randomName = Button(nameFrame, text = 'Random Name', + command = self.setRandomToonName) + self.randomName.pack(side = LEFT, fill = X, expand = 0) + + self.clearName = Button(nameFrame, text = 'Clear Name', + command = self.clearToonName) + self.clearName.pack(side = LEFT, fill = X, expand = 0) + nameFrame.pack(fill = X, expand = 0) + + chatFrame = Frame(self.pageOne) + self.chatButton = Menubutton(chatFrame, width = 8, + text = "SpeedChat", + relief = RAISED, + borderwidth = 2) + self.chatMenu = Menu(self.chatButton, tearoff = 0) + self.chatButton['menu'] = self.chatMenu + self.chatButton.pack(side = LEFT, fill = 'x', expand = 1) + subMenu = Menu(self.chatMenu, tearoff = 0) + self.chatMenu.add_cascade(label = 'Basic', menu = subMenu) + for key in chatKeys: + if (key % 100) == 0: + subMenu = Menu(self.chatMenu, tearoff = 0) + self.chatMenu.add_cascade(label = ChatCategories.get(key,''), + menu = subMenu) + subMenu.add_command( + label = chatDict[key], + command = lambda c=chatDict[key]: self.setToonChat(c)) + + self.chatButton2 = Menubutton(chatFrame, width = 8, + text = "Custom Chat", + relief = RAISED, + borderwidth = 2) + self.chatMenu2 = Menu(self.chatButton2, tearoff = 0) + self.chatButton2['menu'] = self.chatMenu2 + self.chatButton2.pack(side = LEFT, fill = 'x', expand = 1) + subMenu = Menu(self.chatMenu2, tearoff = 0) + self.chatMenu2.add_cascade(label = 'Series 0', menu = subMenu) + for key in customChatKeys: + if (key % 100) == 0: + subMenu = Menu(self.chatMenu2, tearoff = 0) + self.chatMenu2.add_cascade( + label = 'Series %d' % key, + menu = subMenu) + subMenu.add_command( + label = customChatDict[key], + command = lambda c=customChatDict[key]: self.setToonChat(c)) + + # Arbitrary string + self.openChat = Button(chatFrame, text = 'Open Chat', + command = self.openToonChat) + self.openChat.pack(side = LEFT, fill = X, expand = 1) + + # Clear chat string + self.clearChat = Button(chatFrame, text = 'Clear Chat', + command = self.clearToonChat) + self.clearChat.pack(side = LEFT, fill = X, expand = 1) + chatFrame.pack(fill = X, expand = 0) + + animFrame = Frame(self.pageOne) + + if not base.config.GetBool('want-new-anims', 1): + self.animButton = Menubutton(animFrame, width = 18, + text = 'Anims', + relief = RAISED, + borderwidth = 2) + self.anim = StringVar() + self.anim.set('') + self.animMenu = Menu(self.animButton) + self.animButton['menu'] = self.animMenu + self.animButton.pack(side = LEFT, expand = 0, fill = X) + animIndex = 0 + for anim in ToonAnimList: + if (animIndex % 10) == 0: + subMenu = Menu(self.animMenu, tearoff = 0) + self.animMenu.add_cascade(label = ToonAnimList[animIndex], + menu = subMenu) + subMenu.add_command( + label = anim, + command = lambda a = anim: self.setToonAnim(a)) + animIndex += 1 + else: + self.buttonAdd('Anims', + helpMessage='Bring Up Anim Panel', + statusMessage='Control Animations!', + command=self.rtm.showAnimPanel) + self.neutralButton = Button( + animFrame, text = 'Neutral', width = 18, + command = lambda s=self: s.setToonAnim('neutral')) + self.neutralButton.pack(side = LEFT, expand = 0, fill = X) + self.poseSlider = Slider.Slider(animFrame, text = 'Pose:', + min = 0, max = 100, resolution = 1) + self.poseSlider['command'] = self.poseToon + self.poseSlider.pack(side = LEFT, fill = X, expand = 0) + animFrame.pack(expand = 0, fill = X) + + headPoseFrame = Frame(self.pageOne) + self.headH = Slider.Slider(headPoseFrame, text = 'Head H:', + min = -80, max = 80, resolution = 1, + command = self.setHeadH) + self.headH.pack(side = LEFT, fill = X, expand = 0) + self.headP = Slider.Slider(headPoseFrame, text = 'Head P:', + min = -80, max = 80, resolution = 1, + command = self.setHeadP) + self.headP.pack(side = LEFT, fill = X, expand = 0) + headPoseFrame.pack(expand=0,fill =X) + + frame = Frame(self.pageOne) + self.toonSuitTypeButton = Menubutton(frame, width = 18, + text = 'Toon Suit Type', + relief = RAISED, + borderwidth = 2) + # Associate menu with button and vice versa + self.toonSuitMenu = Menu(self.toonSuitTypeButton) + self.toonSuitTypeButton['menu'] = self.toonSuitMenu + # Pack button + self.toonSuitTypeButton.pack(side = LEFT, expand = 0, fill = X) + for suitIndex in range(len(SuitDNAList)): + if (suitIndex % 8) == 0: + subMenu = Menu(self.toonSuitMenu, tearoff = 0) + self.toonSuitMenu.add_cascade( + label = SuitTrackList[suitIndex/8], + menu = subMenu) + suit = SuitDNAList[suitIndex] + suitLabel = suit.split(':')[1].strip() + subMenu.add_command( + label = suitLabel, + command = lambda i = suitIndex: self.putOnSuitSuit(i)) + takeOffSuitButton = Button(frame, text = 'Take off Cog Suit', + command = self.takeOffSuitSuit) + takeOffSuitButton.pack(side = LEFT, fill = X, expand = 0) + + self.handPropChoiceButton = Menubutton(frame, width = 18, + text = 'Choose prop', + relief = RAISED, + borderwidth = 2) + self.handPropChoiceButton.pack(side = LEFT, expand = 0, fill = X) + # Associate menu with button and vice versa + self.props = Menu(self.handPropChoiceButton) + self.handPropChoiceButton['menu'] = self.props + for prop in globalPropPool.propTypes.keys(): + self.props.add_command(label = prop, command = lambda selectProp = prop: self.useProp(selectProp)) + frame.pack(fill = X, expand = 0) + + frame = Frame(self.pageOne) + self.addToonButton = Button(frame, + text = 'Add Random Toon', + command = self.rtm.makeRandomToon) + self.addToonButton.pack(side = LEFT, expand = 1, fill = X) + + self.createTestToonButton = Button(frame, + text = 'Create Anim Test Toon', + command = self.createTestToon) + self.createTestToonButton.pack(side = LEFT, expand = 1, fill = X) + frame.pack(fill = X, expand = 0) + + # + # SUIT TAB + # + self.pageTwo = self.notebook.add('Suits') + + # SUIT TRACK + self.suitTrack = StringVar() + self.suitTrack.set('Corporate') + trackFrame = Frame(self.pageTwo) + Label(trackFrame,text='Track:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + trackFrame, 'Track', 'Corporate', + self.suitTrack, 'Corporate', self.setSuitTrack, + help = 'Corporate Suit', + side = LEFT) + self.newCreateRadiobutton( + trackFrame, 'Track', 'Legal', + self.suitTrack, 'Legal', self.setSuitTrack, + help = 'Legal Suit', + side = LEFT) + self.newCreateRadiobutton( + trackFrame, 'Track', 'Financial', + self.suitTrack, 'Financial', self.setSuitTrack, + help = 'Financial Suit', + side = LEFT) + self.newCreateRadiobutton( + trackFrame, 'Track', 'Sales', + self.suitTrack, 'Sales', self.setSuitTrack, + help = 'Sales Suit', + side = LEFT) + self.newCreateRadiobutton( + trackFrame, 'Track', 'Random', + self.suitTrack, 'Random', self.setSuitTrack, + help = 'Random Suit', + side = LEFT) + trackFrame.pack(fill = X, expand = 0) + + # SUIT LEVEL + self.suitLevel = IntVar() + self.suitLevel.set(0) + levelFrame = Frame(self.pageTwo) + Label(levelFrame,text='Level:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + for i in range(8): + self.newCreateRadiobutton( + levelFrame, 'Level', '%d' % i, + self.suitLevel, i, self.setSuitLevel, + help = 'Level %d Suit' % i, + side = LEFT) + self.newCreateRadiobutton( + levelFrame, 'Level', 'Random', + self.suitLevel, -1, self.setSuitLevel, + help = 'Random Suit Level', + side = LEFT) + levelFrame.pack(fill = X, expand = 0) + + # SPECIFIC SUIT + frame = Frame(self.pageTwo) + self.suitName = Label(frame, text = 'flunky', width = 14) + self.suitName.pack(side = LEFT, expand = 0, fill = X) + Label(frame, text = 'Track:', width = 10).pack( + side = LEFT, expand = 0, fill = X) + self.trackLabel = Label(frame, text = 'Corporate', width = 8) + self.trackLabel.pack(side = LEFT, expand = 0, fill = X) + Label(frame, text = 'Level:', width = 10).pack( + side = LEFT, expand = 0, fill = X) + self.levelLabel = Label(frame, text = '0', width = 8) + self.levelLabel.pack(side = LEFT, expand = 0, fill = X) + self.suitTypeButton = Menubutton(frame, width = 18, + text = 'Suit Type', + relief = RAISED, + borderwidth = 2) + # Associate menu with button and vice versa + self.suitMenu = Menu(self.suitTypeButton) + self.suitTypeButton['menu'] = self.suitMenu + # Pack button + self.suitTypeButton.pack(side = LEFT, expand = 0, fill = X) + for suitIndex in range(len(SuitDNAList)): + if (suitIndex % 8) == 0: + subMenu = Menu(self.suitMenu, tearoff = 0) + self.suitMenu.add_cascade(label = SuitTrackList[suitIndex/8], + menu = subMenu) + suit = SuitDNAList[suitIndex] + suitLabel = suit.split(':')[1].strip() + subMenu.add_command( + label = suitLabel, + command = lambda i = suitIndex: self.setSuitType(i)) + frame.pack(fill = X, expand = 0) + + suitChatFrame = Frame(self.pageTwo) + + self.suitFaceoffButton = Menubutton(suitChatFrame, width = 8, + text = "Faceoff Taunts", + relief = RAISED, + borderwidth = 2) + self.suitFaceoffMenu = Menu(self.suitFaceoffButton, tearoff = 0) + self.suitFaceoffButton['menu'] = self.suitFaceoffMenu + self.suitFaceoffButton.pack(side = LEFT, fill = 'x', expand = 1) + for key in faceoffTauntsKeys: + subMenu = Menu(self.suitFaceoffMenu, tearoff = 0) + suitName = SuitAttributes[key]['name'] + self.suitFaceoffMenu.add_cascade(label = suitName, menu = subMenu) + for taunt in faceoffTaunts[key]: + subMenu.add_command( + label = taunt, + command = lambda t=taunt: self.setToonChat(t)) + + self.suitAttackButton = Menubutton(suitChatFrame, width = 8, + text = "Attack Taunts", + relief = RAISED, + borderwidth = 2) + self.suitAttackMenu = Menu(self.suitAttackButton, tearoff = 0) + self.suitAttackButton['menu'] = self.suitAttackMenu + self.suitAttackButton.pack(side = LEFT, fill = 'x', expand = 1) + for key in attackTauntsKeys: + subMenu = Menu(self.suitAttackMenu, tearoff = 0) + self.suitAttackMenu.add_cascade(label = key, menu = subMenu) + for taunt in attackTaunts[key]: + subMenu.add_command( + label = taunt, + command = lambda t=taunt: self.setToonChat(t)) + + # Arbitrary string + self.openSuitChat = Button(suitChatFrame, text = 'Open Taunt', + command = self.openToonChat) + self.openSuitChat.pack(side = LEFT, fill = X, expand = 1) + + # Clear suitChat string + self.clearSuitChat = Button(suitChatFrame, text = 'Clear Suit Taunt', + command = self.clearToonChat) + self.clearSuitChat.pack(side = LEFT, fill = X, expand = 1) + suitChatFrame.pack(fill = X, expand = 0) + + suitAnimFrame = Frame(self.pageTwo) + self.suitAnimButton = Menubutton(suitAnimFrame, width = 18, + text = 'SuitAnims', + relief = RAISED, + borderwidth = 2) + self.suitAnim = StringVar() + self.suitAnim.set('') + self.suitAnimMenu = Menu(self.suitAnimButton) + self.suitAnimButton['menu'] = self.suitAnimMenu + self.suitAnimButton.pack(side = LEFT, expand = 0, fill = X) + for catIndex in range(len(SuitAnimCategories)): + subMenu = Menu(self.suitAnimMenu, tearoff = 0) + self.suitAnimMenu.add_cascade(label = SuitAnimCategories[catIndex], + menu = subMenu) + for anim in SuitAnimList[catIndex]: + subMenu.add_command( + label = anim[1], + command = lambda a = anim[0]: self.setSuitAnim(a)) + self.suitNeutralButton = Button( + suitAnimFrame, text = 'Neutral', width = 18, + command = lambda s=self: s.setSuitAnim('neutral')) + self.suitNeutralButton.pack(side = LEFT, expand = 0, fill = X) + self.suitPoseSlider = Slider.Slider(suitAnimFrame, text = 'Pose:', + min = 0, max = 100, resolution = 1) + self.suitPoseSlider['command'] = self.poseSuit + self.suitPoseSlider.pack(side = LEFT, fill = X, expand = 0) + suitAnimFrame.pack(expand = 0, fill = X) + + suitNameFrame = Frame(self.pageTwo) + self.clearSuitName = Button(suitNameFrame, text = 'Clear Name', + command = self.clearToonName) + self.clearSuitName.pack(side = LEFT, fill = X, expand = 0) + suitNameFrame.pack(expand = 0, fill = X) + + self.addSuitButton = Button(self.pageTwo, + text = 'Add Suit', + command = self.addSuit) + self.addSuitButton.pack(expand = 1, fill = NONE) + + # + # Doodle Tab + # + + self.pageThree = self.notebook.add('Doodles') + + # Doodle head parts + self.doodleHead = IntVar() + self.doodleHead.set(None) + doodleHeadFrame = Frame(self.pageThree) + Label(doodleHeadFrame,text='Head Part:',width=8, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleHeadFrame, 'Head', 'None', + self.doodleHead, -1, self.setDoodleHead, + help = 'No Head Part (-1)', + side = LEFT) + self.newCreateRadiobutton( + doodleHeadFrame, 'Head', 'Feathers', + self.doodleHead, 0, self.setDoodleHead, + help = 'Head Feathers (0)', + side = LEFT) + doodleHeadFrame.pack(fill = X, expand = 0) + + # Doodle ears + self.doodleEars = IntVar() + self.doodleEars.set(None) + doodleEarsFrame = Frame(self.pageThree) + Label(doodleEarsFrame,text='Ears:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'None', + self.doodleEars, -1, self.setDoodleEars, + help = 'No Ears Part (-1)', + side = LEFT) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'Horns', + self.doodleEars, 0, self.setDoodleEars, + help = 'Horns (0)', + side = LEFT) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'Antennae', + self.doodleEars, 1, self.setDoodleEars, + help = 'Antennae (1)', + side = LEFT) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'Dog', + self.doodleEars, 2, self.setDoodleEars, + help = 'Dog ears (2)', + side = LEFT) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'Cat', + self.doodleEars, 3, self.setDoodleEars, + help = 'Cat ears (3)', + side = LEFT) + self.newCreateRadiobutton( + doodleEarsFrame, 'Ears', 'Rabbit', + self.doodleEars, 4, self.setDoodleEars, + help = 'Rabbit ears (4)', + side = LEFT) + doodleEarsFrame.pack(fill = X, expand = 0) + + # Doodle nose + self.doodleNose = IntVar() + self.doodleNose.set(None) + doodleNoseFrame = Frame(self.pageThree) + Label(doodleNoseFrame,text='Nose:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleNoseFrame, 'Nose', 'None', + self.doodleNose, -1, self.setDoodleNose, + help = 'No Nose Part (-1)', + side = LEFT) + self.newCreateRadiobutton( + doodleNoseFrame, 'Nose', 'Clown', + self.doodleNose, 0, self.setDoodleNose, + help = 'Clown Nose (0)', + side = LEFT) + self.newCreateRadiobutton( + doodleNoseFrame, 'Nose', 'Dog', + self.doodleNose, 1, self.setDoodleNose, + help = 'Dog Nose (1)', + side = LEFT) + self.newCreateRadiobutton( + doodleNoseFrame, 'Nose', 'Oval', + self.doodleNose, 2, self.setDoodleNose, + help = 'Oval-shaped Nose (2)', + side = LEFT) + self.newCreateRadiobutton( + doodleNoseFrame, 'Nose', 'Pig', + self.doodleNose, 3, self.setDoodleNose, + help = 'Upturned Nose (3)', + side = LEFT) + doodleNoseFrame.pack(fill = X, expand = 0) + + # Doodle tail + self.doodleTail = IntVar() + self.doodleTail.set(None) + doodleTailFrame = Frame(self.pageThree) + Label(doodleTailFrame,text='Tail:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleTailFrame, 'Tail', 'None', + self.doodleTail, -1, self.setDoodleTail, + help = 'No Tail Part (-1)', + side = LEFT) + self.newCreateRadiobutton( + doodleTailFrame, 'Tail', 'Cat', + self.doodleTail, 0, self.setDoodleTail, + help = 'Cat Tail (0)', + side = LEFT) + self.newCreateRadiobutton( + doodleTailFrame, 'Tail', 'Long', + self.doodleTail, 1, self.setDoodleTail, + help = 'Long Tail (1)', + side = LEFT) + self.newCreateRadiobutton( + doodleTailFrame, 'Tail', 'Bird', + self.doodleTail, 2, self.setDoodleTail, + help = 'Feathery Tail (2)', + side = LEFT) + self.newCreateRadiobutton( + doodleTailFrame, 'Tail', 'Bunny', + self.doodleTail, 3, self.setDoodleTail, + help = 'Fluffy, Cotton Tail (3)', + side = LEFT) + doodleTailFrame.pack(fill = X, expand = 0) + + # Doodle body + self.doodleBody = IntVar() + self.doodleBody.set(None) + doodleBodyFrame = Frame(self.pageThree) + Label(doodleBodyFrame,text='Body:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Dots', + self.doodleBody, 0, self.setDoodleBody, + help = 'Polka Dots (0)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Stripes', + self.doodleBody, 1, self.setDoodleBody, + help = 'Three Stripes (1)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Tiger', + self.doodleBody, 2, self.setDoodleBody, + help = 'Tiger Stripes (2)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Tummy', + self.doodleBody, 3, self.setDoodleBody, + help = 'Darker Tummy (3)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Turtle', + self.doodleBody, 4, self.setDoodleBody, + help = 'Green Turtle (4)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Giraffe', + self.doodleBody, 5, self.setDoodleBody, + help = 'Giraffe (5)', + side = LEFT) + self.newCreateRadiobutton( + doodleBodyFrame, 'Body', 'Leopard', + self.doodleBody, 6, self.setDoodleBody, + help = 'Leopard (6)', + side = LEFT) + doodleBodyFrame.pack(fill = X, expand = 0) + + # Doodle main color + self.doodleColor = IntVar() + self.doodleColor.set(-1) + colorFrame = Frame(self.pageThree) + Label(colorFrame,text='Body Color:',width=16, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + for i in range(2): + cf = Frame(colorFrame) + for j in range(13): + index = i * 13 + j + color = self.transformRGB(PetDNA.AllPetColors[index]) + b = Button(cf, width = 1, height = 1, background = color, + text = "%d" % index, + command = lambda ci=index: self.setDoodleColor(ci)) + b.pack(side = LEFT, fill = X, expand = 0) + self.doodleColorButtonList.append(b) + cf.pack(fill = X, expand = 0) + colorFrame.pack(fill = X, expand = 0) + + # Doodle part color scale + self.doodleColorScale = IntVar() + self.doodleColorScale.set(-1) + colorFrame = Frame(self.pageThree) + Label(colorFrame,text='Part Color Scale:',width=16, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + for index in range(len(PetDNA.ColorScales)): + c = PetDNA.ColorScales[index] + color = self.transformRGB(VBase4(c/1.2,c/1.2,c/1.2,1.0)) + b = Button(colorFrame, width = 1, height = 1, background = color, + text = "%d" % index, + command = lambda ci=index: self.setDoodleColorScale(ci)) + b.pack(side = LEFT, fill = X, expand = 0) + self.doodleColorScaleButtonList.append(b) + colorFrame.pack(fill = X, expand = 0) + + # Doodle eye color + self.doodleEyeColor = IntVar() + self.doodleEyeColor.set(-1) + colorFrame = Frame(self.pageThree) + Label(colorFrame,text='Eye Color:',width=16, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + for index in range(len(PetDNA.PetEyeColors)): + color = self.transformRGB(PetDNA.PetEyeColors[index]) + b = Button(colorFrame, width = 1, height = 1, background = color, + text = "%d" % index, + command = lambda ci=index: self.setDoodleEyeColor(ci)) + b.pack(side = LEFT, fill = X, expand = 0) + self.doodleEyeColorButtonList.append(b) + colorFrame.pack(fill = X, expand = 0) + + # Doodle gender + self.doodleGender = IntVar() + self.doodleGender.set(None) + doodleGenderFrame = Frame(self.pageThree) + Label(doodleGenderFrame,text='Gender:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + doodleGenderFrame, 'Gender', 'Girl', + self.doodleGender, 0, self.setDoodleGender, + help = 'Eyelashes (0)', + side = LEFT) + self.newCreateRadiobutton( + doodleGenderFrame, 'Gender', 'Boy', + self.doodleGender, 1, self.setDoodleGender, + help = 'No Eyelashes (1)', + side = LEFT) + doodleGenderFrame.pack(fill = X, expand = 0) + + # Doodle anim menu + doodleAnimFrame = Frame(self.pageThree) + self.doodleAnimButton = Menubutton(doodleAnimFrame, width = 18, + text = 'DoodleAnims', + relief = RAISED, + borderwidth = 2) + self.doodleAnim = StringVar() + self.doodleAnim.set('') + self.doodleAnimMenu = Menu(self.doodleAnimButton) + self.doodleAnimButton['menu'] = self.doodleAnimMenu + self.doodleAnimButton.pack(side = LEFT, expand = 0, fill = X) + animIndex = 0 + for anim in DoodleAnimList: + if (animIndex % 10) == 0: + subMenu = Menu(self.doodleAnimMenu, tearoff = 0) + self.doodleAnimMenu.add_cascade(label = DoodleAnimList[animIndex], + menu = subMenu) + subMenu.add_command( + label = anim, + command = lambda a = anim: self.setDoodleAnim(a)) + animIndex += 1 + + # Doodle neutral button + self.doodleNeutralButton = Button( + doodleAnimFrame, text = 'Neutral', width = 18, + command = lambda s=self: s.setDoodleAnim('neutral')) + self.doodleNeutralButton.pack(side = LEFT, expand = 0, fill = X) + self.doodlePoseSlider = Slider.Slider(doodleAnimFrame, text = 'Pose:', + min = 0, max = 100, resolution = 1) + self.doodlePoseSlider['command'] = self.poseDoodle + self.doodlePoseSlider.pack(side = LEFT, fill = X, expand = 0) + doodleAnimFrame.pack(expand = 0, fill = X) + + self.addDoodleButton = Button(self.pageThree, + text = 'Add Doodle', + command = self.addDoodle) + self.addDoodleButton.pack(expand = 1, fill = NONE) + + + self.notebook.pack(fill = BOTH, expand = 1) + self.notebookFrame.pack(fill = BOTH, expand = 1) + + self.fMaya = IntVar() + self.fMaya.set(1) + self.mayaButton = Checkbutton(self.rightFrame, + text = 'Maya Cam', + width = 6, + variable = self.fMaya, + command = self.toggleMaya) + self.mayaButton.pack(side = LEFT, expand = 1, fill = X) + + self.rightFrame.pack(fill = BOTH, expand = 1) + # Put this here so it isn't called right away + self.notebook['raisecommand'] = self.switchAvatarType + + self.framePane.pack(fill = BOTH, expand = 1) + + propsPage = self.notebook.add('Props') + + # PROPS + Label(propsPage, text = 'Props', + font=('MSSansSerif', 14, 'bold')).pack(expand = 0) + self.addPropsButton = Button( + propsPage, + text = 'ADD PROP', + command = self.addProp) + self.addPropsButton.pack(fill = X, padx = 20, pady = 10) + codes = [] + self.styleManager = self.rtm.styleManager + + codes = (self.styleManager.getCatalogCodes('prop') + + self.styleManager.getCatalogCodes('holiday_prop')) + codes.sort() + + self.propSelector = Pmw.ComboBox( + propsPage, + dropdown = 0, + listheight = 200, + labelpos = W, + label_width = 12, + label_anchor = W, + label_text = 'Prop type:', + entry_width = 30, + selectioncommand = self.setPropType, + scrolledlist_items = codes + ) + self.propType = self.styleManager.getCatalogCode('prop', 0) + self.propSelector.selectitem( + self.styleManager.getCatalogCode('prop', 0)) + self.propSelector.pack(expand = 1, fill = BOTH) + + # Effects tab + effectsPage = self.notebook.add('Effects') + + self.holiday = IntVar() + self.holiday.set(0) + holidayFrame = Frame(effectsPage) + Label(holidayFrame,text='Holiday:',width=6, + anchor = W, justify = LEFT).pack(side = LEFT, expand = 0) + self.newCreateRadiobutton( + holidayFrame, 'Holiday', 'July 4th', + self.holiday, 0, None, + help = 'July 4th', + side = LEFT) + self.newCreateRadiobutton( + holidayFrame, 'Holiday', 'New Year', + self.holiday, 1, None, + help = 'New Year', + side = LEFT) + holidayFrame.pack(fill = X, expand = 0) + + fireworkTypeFrame = Frame(effectsPage) + Label(fireworkTypeFrame, text = 'Type:', + anchor = W, justify = LEFT, + width=6).pack(side = LEFT, expand = 0) + self.fireworkType = IntVar() + self.fireworkType.set(0) + for i in range(6): + self.newCreateRadiobutton( + fireworkTypeFrame, 'FireworkType', '%d'%i, + self.fireworkType, i, None, + side = LEFT) + fireworkTypeFrame.pack(fill = X, expand = 0) + + fireworkButton = Button( + effectsPage, + text = 'Firework', + command = self.firework) + fireworkButton.pack(fill = X, padx = 20, pady = 10) + + snowButton = Button( + effectsPage, + text = 'Toggle Snow', + command = self.toggleSnow) + snowButton.pack(fill = X, padx = 20, pady = 10) + + mainFrame.pack(fill = BOTH, expand = 1) + self.createButtons() + + self.initialiseoptions(RobotToonControlPanel) + # Enable widget commands + self.updateToonInfo() + self.setSuitTrack() + self.setSuitLevel() + self.fDoIt = 1 + self.snow = None + + def importMaya(self): + mbFilename = askopenfilename( + defaultextension = '.mb', + filetypes = (('Maya Files', '*.mb'), + ('Panda Models', '*.bam *.egg'), + ('All files', '*')), + initialdir = '.', + title = 'Load Maya File', + parent = self.component('hull') + ) + + mbFilePath = Filename.fromOsSpecific(mbFilename).getFullpath() + eggFilePath = os.path.splitext(mbFilePath)[0] + '.egg' + os.system('maya2egg_bin -o' + eggFilePath + ' ' + mbFilePath) + newNode = loader.loadModel(eggFilePath, noCache=True) + newNode.ls() + + def toggleSnow(self): + if self.snow: + self.snow.cleanup() + del self.snow + del self.snowRender + self.snow = None + else: + self.snow = BattleParticles.loadParticleFile('snowdisk.ptf') + self.snow.setPos(0, 0, 5) # start the snow slightly above the camera + self.snowRender = render.attachNewNode('snowRender') + self.snowRender.setDepthWrite(0) + self.snowRender.setBin('fixed', 1) + self.snowFade = None + self.snow.start(camera, self.snowRender) + + def firework(self): + holidays = [ToontownGlobals.JULY4_FIREWORKS, ToontownGlobals.NEWYEARS_FIREWORKS] + show = FireworkShows.getShow(holidays[self.holiday.get()], self.fireworkType.get()) + + mainShow = Sequence() + currentT = 0 + startT = 0 + for effect in show: + waitTime, style, colorIndex1, colorIndex2, amp, x, y, z = effect + if waitTime > 0: + currentT += waitTime + mainShow.append(Wait(waitTime)) + # Do not add the firework Ival if our start time is greater than + # the current time since the Func Interval can not be skipped over + # and all the skipped over fireworks will otherwise fire at once + if currentT >= startT: + mainShow.append( + Func( + Fireworks.shootFirework, + style, + x, + y, + z, + colorIndex1, + colorIndex2, + amp + ) + ) + + mainShow.start() + + def createTestToon(self): + """ + Create a toon with the following properties: + Gender: male + + Species: dog + Head: sl + Eyes: normal + Muzzles: normal + Torso: MS + Legs: m + Color: All + skintone BRIGHT GREEN + Tops: 00 - solid , color 0 + Bottoms: 00 - plain w/ pockets , color 0 + """ + dna = ToonDNA.ToonDNA() + dna.newToonFromProperties("dsl" ,"ms" ,"m" ,"m" ,15 ,0 ,15 ,15 ,0 ,0 ,0 ,0 ,0 ,0 ,) + self.rtm.makeToonFromProperties(dna.asTuple(), pos = Point3(0), hpr = Point3(180,0,0), + startPos = Point3(0), startHpr = Point3(0), + endPos = Point3(0,1,0), endHpr = Point3(0), + state = 'neutral') + self.updateToonInfo() + + def addProp(self): + self.rtm.addProp(self.propType) + + def setPropType(self, name): + self.propType = name + self.rtm.setCurrent('prop_texture', self.propType) + + # [gjeon] to toggle maya cam mode + def toggleMaya(self): + base.direct.cameraControl.lockRoll = self.fMaya.get() + direct.cameraControl.useMayaCamControls = self.fMaya.get() + + def toggleAntiAliasing(self): + if render.getAntialias() > 0: + render.clearAntialias() + else: + render.setAntialias(AntialiasAttrib.MAuto) + + def resizeWindow(self): + text = askstring('New window size', 'Type new window size such as 800x600\n', + parent = self.component('hull')) + tokens = text.split('x') + if len(tokens) != 2: + return + width = string.atoi(tokens[0]) + height = string.atoi(tokens[1]) + + props = WindowProperties(base.win.getProperties()) + props.setSize(width, height) + base.win.requestProperties(props) + + def switchAvatarType(self, page = 'Toons'): + if page == 'Toons': + self.rtm.avatarType = 't' + elif page == 'Suits': + self.rtm.avatarType = 's' + else: + self.rtm.avatarType = 'd' + direct.deselectAll() + + def setSuitName(self): + if ((self.suitTrack.get() == 'Random') or + (self.suitLevel.get() < 0)): + self.suitName['text'] = 'Random' + else: + suitIndex = (SuitTrackList.index(self.suitTrack.get()) * 8 + + self.suitLevel.get()) + suitName = SuitDNAList[suitIndex].split(':')[1].strip() + self.suitName['text'] = suitName + + def setSuitTrack(self): + track = self.suitTrack.get() + self.trackLabel['text'] = track + self.rtm.suitTrack = track + self.setSuitName() + + def setSuitLevel(self): + level = self.suitLevel.get() + self.rtm.suitLevel = level + if level < 0: + self.levelLabel['text'] = 'Random' + else: + self.levelLabel['text'] = '%d' % level + self.setSuitName() + + def setSuitType(self, suitIndex): + self.suitTrack.set(['Corporate','Legal', + 'Financial','Sales'][suitIndex/8]) + self.setSuitTrack() + self.suitLevel.set(suitIndex % 8) + self.setSuitLevel() + + def useProp(self, prop): + if not self.rtm.selectedToon: + return + if hasattr(self, "propHandle") and self.propHandle: + self.propHandle.detachNode() + del self.propHandle + hands = self.rtm.selectedToon.getRightHands() + self.propHandle = globalPropPool.getProp(prop) + handHigh = hands[0].attachNewNode('highLODHand') + handMed = hands[1].attachNewNode('medLODHand') + self.propHandle.reparentTo(handHigh) + + handHigh.instanceTo(handMed) + return prop + + def addSuit(self): + self.rtm.makeRandomToon() + + def setDoodleHead(self): + head = self.doodleHead.get() + self.rtm.doodleHead = head + + def setDoodleEars(self): + ears = self.doodleEars.get() + self.rtm.doodleEars = ears + + def setDoodleNose(self): + nose = self.doodleNose.get() + self.rtm.doodleNose = nose + + def setDoodleTail(self): + tail = self.doodleTail.get() + self.rtm.doodleTail = tail + + def setDoodleBody(self): + body = self.doodleBody.get() + self.rtm.doodleBody = body + + def setDoodleGender(self): + gender = self.doodleGender.get() + self.rtm.doodleGender = gender + + def setDoodleColor(self, colorIndex): + self.doodleColor.set(colorIndex) + self.rtm.doodleColor = colorIndex + + def setDoodleColorScale(self, colorIndex): + self.doodleColorScale.set(colorIndex) + self.rtm.doodleColorScale = colorIndex + + def setDoodleEyeColor(self, colorIndex): + self.doodleEyeColor.set(colorIndex) + self.rtm.doodleEyeColor = colorIndex + + def addDoodle(self): + self.rtm.makeRandomToon() + + def setTop(self, topName, fUpdateVariants = 0): + if fUpdateVariants: + topIndex = int(topName[:3]) + self.topsVariants = self.topsDict[topIndex] + self.topsCounter.setentry(0) + self.topsCounter.invoke() + + def __switchTops(self, text): + value = string.atoi(text) + if (value < 0) or (value >= len(self.topsVariants)): + return Pmw.ERROR + else: + if self.fDoIt: + self.topsCounter.invoke() + return Pmw.OK + + def setTopVariant(self, event = None): + if self.fDoIt and self.rtm.selectedToon: + topStyle = self.topsVariants[self.topsIndex.get()] + st = self.rtm.selectedToon + st.style.topTex = topStyle[0] + st.style.topTexColor = topStyle[1] + st.style.sleeveTex = topStyle[2] + st.style.sleeveTexColor = topStyle[3] + st.generateToonClothes() + + def setBottom(self, bottomName, fUpdateVariants = 0): + if fUpdateVariants: + bottomIndex = int(bottomName[:2]) + self.bottomsVariants = self.bottomsDict[bottomIndex] + self.bottomsCounter.setentry(0) + self.bottomsCounter.invoke() + + def __switchBottoms(self, text): + if not Pmw.integervalidator(text): + return Pmw.ERROR + value = string.atoi(text) + if (value < 0) or (value >= len(self.bottomsVariants)): + return Pmw.ERROR + else: + if self.fDoIt: + self.bottomsCounter.invoke() + return Pmw.OK + + def setBottomVariant(self, event = None): + if self.fDoIt and self.rtm.selectedToon: + bottomStyle = self.bottomsVariants[self.bottomsIndex.get()] + st = self.rtm.selectedToon + st.style.botTex = bottomStyle[0] + st.style.botTexColor = bottomStyle[1] + st.generateToonClothes() + + def setToonColor(self, colorIndex): + cm = self.colorMode.get() + st = self.rtm.selectedToon + if st: + dna = st.style + if cm == 'all': + dna.armColor = colorIndex + dna.legColor = colorIndex + dna.headColor = colorIndex + elif cm == 'arms': + dna.armColor = colorIndex + elif cm == 'gloves': + dna.gloveColor = colorIndex + elif cm == 'legs': + dna.legColor = colorIndex + elif cm == 'head': + dna.headColor = colorIndex + st.swapToonColor(dna) + + def updateToonInfo(self): + st = self.rtm.selectedToon + if st: + dna = st.style + if not hasattr(dna, 'type') or dna.type == 's': + return + self.fDoIt = 0 + self.setGender(dna.gender) + self.species.set(self.speciesDict[dna.head[0]]) + self.setSpecies(fUpdateHead = 0) + self.head.set(dna.head[1:]) + self.torso.set(dna.torso) + self.legs.set(dna.legs) + topStyle = dna.asTuple()[8:12] + self.topsMenu.invoke(ToonTopsDict[topStyle[0]]) + idx = self.topsVariants.index(topStyle) + self.topsIndex.set(idx) + bottomStyle = dna.asTuple()[12:] + self.bottomsMenu.invoke(self.toonBottomsDict[bottomStyle[0]]) + idx = self.bottomsVariants.index(bottomStyle) + self.bottomsIndex.set(idx) + self.fDoIt = 1 + + def selectLOD(self): + """ + swap the lod + """ + st = self.rtm.selectedToon + if st: + st.useLOD(self.lod.get()) + + def swapGender(self): + st = self.rtm.selectedToon + if st: + oldGender = st.style.gender + if self.gender.get() != oldGender: + st.style.gender = self.gender.get() + topTex = ToonDNA.getRandomTop(st.style.gender) + botStyle = ToonDNA.getRandomBottom(st.style.gender) + st.style.topTex = topTex[0] + st.style.topTexColor = topTex[1] + st.style.sleeveTex = topTex[2] + st.style.sleeveTexColor = topTex[3] + st.style.botTex = botStyle[0] + st.style.botTexColor = botStyle[1] + st.swapToonHead(st.style.head) + # Deal with those damn pants! + st.style.torso = 'ms' + st.swapToonTorso(st.style.torso) + st.generateToonClothes() + st.loop(st.state) + self.updateGenderRelatedInfo() + + def setGender(self, gender): + self.gender.set(gender) + self.updateGenderRelatedInfo() + + def updateGenderRelatedInfo(self): + gender = self.gender.get() + if gender == 'm': + self.sdButton['state'] = DGG.DISABLED + self.mdButton['state'] = DGG.DISABLED + self.ldButton['state'] = DGG.DISABLED + activeColorList = ToonDNA.defaultBoyColorList + self.topsDict = self.maleTopsDict + self.topsNames = self.maleTopsNames + self.bottomsDict = self.maleBottomsDict + self.bottomsNames = self.maleBottomsNames + self.toonBottomsDict = BoyBottomsDict + elif gender == 'f': + self.sdButton['state'] = DGG.NORMAL + self.mdButton['state'] = DGG.NORMAL + self.ldButton['state'] = DGG.NORMAL + activeColorList = ToonDNA.defaultGirlColorList + self.topsDict = self.femaleTopsDict + self.topsNames = self.femaleTopsNames + self.bottomsDict = self.femaleBottomsDict + self.toonBottomsDict = GirlBottomsDict + st = self.rtm.selectedToon + if st: + torso = st.style.torso + if torso[1] == 'd': + self.bottomsNames = self.femaleSkirtsNames + else: + self.bottomsNames = self.femaleShortsNames + else: + self.bottomsNames = self.femaleSkirtsNames + # Update color tablets + #index = 0 + #for b in self.colorButtonList: + # if index in activeColorList: + # b['state'] = DGG.NORMAL + # b['text'] = index + # else: + # b['state'] = DGG.DISABLED + # b['text'] = 'X' + # index += 1 + # Update clothing option menus + self.topsMenu.setitems(self.topsNames) + self.bottomsMenu.setitems(self.bottomsNames) + if self.rtm.selectedToon: + topTex = self.rtm.selectedToon.style.topTex + topVariantIndex = ToonTopsDict[topTex] + botTex = self.rtm.selectedToon.style.botTex + bottomVariantIndex = self.toonBottomsDict[botTex] + else: + topVariantIndex = ToonTopsDict[0] + bottomVariantIndex = self.toonBottomsDict[0] + self.topsMenu.invoke(topVariantIndex) + self.bottomsMenu.invoke(bottomVariantIndex) + self.topsCounter.checkentry() + self.bottomsCounter.checkentry() + + def setSpecies(self, fUpdateHead = 1): + if self.species.get() == 'Mouse': + self.slHeadButton['state'] = DGG.DISABLED + self.llHeadButton['state'] = DGG.DISABLED + if self.head.get() in ['sl', 'll']: + self.head.set('ss') + else: + self.slHeadButton['state'] = DGG.NORMAL + self.llHeadButton['state'] = DGG.NORMAL + if fUpdateHead: + self.setHead() + + def setHead(self): + if self.species.get() == 'Duck': + prefix = 'f' # fowl + elif self.species.get() == 'Monkey': + prefix = 'p' # primate + else: + prefix = self.species.get()[0].lower() + self.setHeadType(prefix + self.head.get()) + + def setHeadType(self, headType): + st = self.rtm.selectedToon + if st: + st.style.head = headType + st.swapToonHead(st.style.head) + + + def setMuzzle(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.hideNormalMuzzle() + self.rtm.selectedToon.hideAngryMuzzle() + self.rtm.selectedToon.hideSadMuzzle() + self.rtm.selectedToon.hideLaughMuzzle() + self.rtm.selectedToon.hideSurpriseMuzzle() + self.rtm.selectedToon.hideSmileMuzzle() + muzzle = self.muzzle.get() + if muzzle == 'normal': + self.rtm.selectedToon.showNormalMuzzle() + elif muzzle == 'angry': + self.rtm.selectedToon.showAngryMuzzle() + elif muzzle == 'sad': + self.rtm.selectedToon.showSadMuzzle() + elif muzzle == 'smile': + self.rtm.selectedToon.showSmileMuzzle() + elif muzzle == 'laugh': + self.rtm.selectedToon.showLaughMuzzle() + elif muzzle == 'surprise': + self.rtm.selectedToon.showSurpriseMuzzle() + + def setEyes(self): + if self.rtm.selectedToon: + eyes = self.eyes.get() + if eyes == 'normal': + self.rtm.selectedToon.normalEyes() + elif eyes == 'angry': + self.rtm.selectedToon.angryEyes() + elif eyes == 'sad': + self.rtm.selectedToon.sadEyes() + self.rtm.selectedToon.startBlink() + + def setHeadH(self, h): + st = self.rtm.selectedToon + if st: + head = st.getPart('head', st.getLODNames()[0]) + head.setH(h) + + def setHeadP(self, p): + st = self.rtm.selectedToon + if st: + head = st.getPart('head', st.getLODNames()[0]) + head.setP(p) + + def swapTorso(self): + self.setTorsoType(self.torso.get()) + + def setTorsoType(self, torsoType): + st = self.rtm.selectedToon + if st: + # Is this a girl that is swapping shorts to skirt or vice versa? + if ((st.style.getGender() == 'f') and + (torsoType[1] != st.style.torso[1])): + # Need to change bot texture and color + if torsoType[1] == 's': + st.style.botTex = 5 + else: + st.style.botTex = 0 + st.style.botTexColor = 0 + fUpdate = 1 + else: + fUpdate = 0 + st.style.torso = torsoType + st.swapToonTorso(st.style.torso) + st.loop(st.state) + if fUpdate: + self.updateGenderRelatedInfo() + + def swapLegs(self): + self.setLegsType(self.legs.get()) + + def setLegsType(self, legsType): + st = self.rtm.selectedToon + if st: + st.style.legs = legsType + st.swapToonLegs(st.style.legs) + st.loop(st.state) + + def setToonName(self, event = None): + if self.rtm.selectedToon: + self.rtm.selectedToon.nametag.setName(self.toonName.get()) + + def setRandomToonName(self): + if self.rtm.selectedToon: + if self.rtm.selectedToon.style.gender == 'm': + boy = 1 + girl = 0 + else: + boy = 0 + girl = 1 + self.rtm.selectedToon.nametag.setName( + namegen.randomNameMoreinfo(boy = boy, girl = girl)[-1]) + + def clearToonName(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.nametag.setName('') + + def setToonChat(self, text): + if self.rtm.selectedToon: + self.rtm.selectedToon.nametag.setChat(text, CFSpeech) + + def openToonChat(self): + text = askstring('Open Chat String', 'Phrase:', + parent = self.component('hull')) + self.setToonChat(text) + + def clearToonChat(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.nametag.clearChat() + + def setToonAnim(self, anim): + self.anim.set(anim) + st = self.rtm.selectedToon + if st: + if st.style.type == 't': + numFrames = st.getNumFrames(anim) + if numFrames is None: + numFrames = 100 + else: + numFrames = numFrames - 1 + self.poseSlider['max'] = numFrames + + #self.rtm.selectedToon.loop(anim) + + # This will maintain the animation as the body parts change + self.rtm.setToonAnimState(anim) + + def poseToon(self, frame): + st = self.rtm.selectedToon + if st: + if st.style.type == 't': + st.stop() + anim = self.anim.get() + if anim is None: + anim = 'neutral' + frame = max(0, frame) + frame = min(frame, st.getNumFrames(anim)) + st.pose(anim, int(frame)) + + def setSuitAnim(self, anim): + self.suitAnim.set(anim) + st = self.rtm.selectedToon + if st: + if ((st.style.type == 's') or + ((st.style.type == 't') and (hasattr(st, 'suit')) and + (st.suit is not None))): + numFrames = st.getNumFrames(anim) + if numFrames is None: + numFrames = 100 + else: + numFrames = numFrames - 1 + self.suitPoseSlider['max'] = numFrames + if st.style.type == 's': + # Update toon + st.loop(anim) + else: + # Update cog suit + st.suit.loop(anim) + + def poseSuit(self, frame): + st = self.rtm.selectedToon + if st: + if ((st.style.type == 's') or + ((st.style.type == 't') and (hasattr(st, 'suit')) and + (st.suit is not None))): + st.stop() + anim = self.suitAnim.get() + if anim is None: + anim = 'neutral' + frame = max(0, frame) + frame = min(frame, st.getNumFrames(anim)) + if st.style.type == 's': + # Update toon + st.pose(anim, int(frame)) + else: + # Update cog suit + st.suit.pose(anim, int(frame)) + + def putOnSuitSuit(self, suitIndex): + if self.rtm.selectedToon: + if isinstance(self.rtm.selectedToon, RobotToon): + desc = SuitDNAList[suitIndex] + dna = desc.split(':')[0].strip() + self.rtm.selectedToon.putOnSuit(dna) + + def takeOffSuitSuit(self): + if self.rtm.selectedToon: + if isinstance(self.rtm.selectedToon, RobotToon): + self.rtm.selectedToon.takeOffSuit() + + def setDoodleAnim(self, anim): + self.anim.set(anim) + st = self.rtm.selectedToon + if st: + if (isinstance(st.style, types.ListType) or isinstance(st.style, types.TupleType)): + numFrames = st.getNumFrames(anim) + if numFrames is None: + numFrames = 100 + else: + numFrames = numFrames - 1 + self.poseSlider['max'] = numFrames + + self.rtm.selectedToon.loop(anim) + + def poseDoodle(self, frame): + st = self.rtm.selectedToon + if st: + if (isinstance(st.style, types.ListType) or isinstance(st.style, types.TupleType)): + st.stop() + anim = self.anim.get() + if anim is None: + anim = 'neutral' + frame = max(0, frame) + frame = min(frame, st.getNumFrames(anim)) + st.pose(anim, int(frame)) + + def createButtons(self): + self.buttonAdd('Stand Up!', + helpMessage='Upright selected toon', + statusMessage='Stop slouching!', + command=self.uprightSelectedToon) + self.buttonAdd('Face Camera', + helpMessage='Turn selected toon toward camera', + statusMessage='Look at me!', + command=self.rtm.faceCamera) + self.buttonAdd('Look Around', + helpMessage='Start look around task', + statusMessage='Look around start', + command=self.startToonLookAround) + self.buttonAdd('Look Ahead', + helpMessage='Stop look around task', + statusMessage='Look around stop', + command=self.stopToonLookAround) + self.buttonAdd('Toggle Sky', + helpMessage='Toggle Sky visibility', + statusMessage='Somewhere over the rainbow!', + command=self.rtm.toggleSky) + self.buttonAdd('Toggle Grid', + helpMessage='Toggle grid visibility', + statusMessage='Show DIRECT Grid', + command=self.rtm.toggleGrid) + self.buttonAdd('Screenshot', + helpMessage='Take Screenshot', + statusMessage='Say Cheese!', + command=self.takeScreenshot) + self.buttonAdd('Log', + helpMessage='Log Toon DNA', + statusMessage='Write Log', + command=self.appendRtmState) + self.buttonAdd('Render', + helpMessage='Render Animation', + statusMessage='Go!', + command=self.renderMovie) + self.buttonAdd('Help', + helpMessage='RTM Help', + statusMessage='Click for help', + command=self.showHelp) + + + # STYLE/DNA FILE FUNCTIONS + def loadSpecifiedDNAFile(self): + path = dnaDirectory.toOsSpecific() + if not os.path.isdir(path): + print 'Robot Toon Manager Warning: Invalid default DNA directory!' + print 'Using current directory' + path = '.' + dnaFilename = askopenfilename( + defaultextension = '.dna', + filetypes = (('DNA Files', '*.dna'),('All files', '*')), + initialdir = path, + title = 'Load DNA File', + parent = self.component('hull')) + if dnaFilename: + self.loadDNAFromFile(dnaFilename) + print "Finished Load: ", dnaFilename + + def loadDNAFromFile(self, filename): + # Reset level, destroying existing scene/DNA hierarcy + self.resetScene() + + node = loadDNAFile(DNASTORE, + Filename.fromOsSpecific(filename).cStr(), + CSDefault, 1) + + self.npToplevel = render.attachNewNode(node) + + def resetScene(self): + if self.npToplevel: + self.npToplevel.removeNode() + self.npToplevel = None + + def toggleBalloon(self): + # 'balloon' shows ballon help + # 'status' shows status bar + # 'both' shows in both places + # 'none' shows nothing + if self.toggleBalloonVar.get(): + self.balloon().configure(state = 'both') + else: + self.balloon().configure(state = 'none') + + def onDestroy(self, event): + """ Called on Robot Toon Manager Panel shutdown """ + del self.rtm + + ### IN SUPPORT OF DEMO + def _setServerString(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.updateDNA(self.serverString.get()) + self.rtm.selectedToon.loop('neutral') + self.updateToonInfo() + + def uprightSelectedToon(self): + if self.rtm.selectedToon: + h = self.rtm.selectedToon.getH(render) + self.rtm.selectedToon.setHpr(render,h,0,0) + + def startToonLookAround(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.startLookAround() + + def stopToonLookAround(self): + if self.rtm.selectedToon: + self.rtm.selectedToon.stopLookAround() + + def renderMovie(self): + duration = askfloat('Render duration', 'Type duration of animation in sec.\n', + parent = self.component('hull')) + + if duration <= 0: + return + + self.filename = asksaveasfilename( + initialdir = self.lastPath, + title = 'Enter name of file...', + parent = self.component('hull')) + + if self.filename == None or self.filename == "": + return + + self.lastPath = os.path.dirname(self.filename) + + if self.filename == None or self.filename == "": + return + + lowerFilename = self.filename.lower() + + if not (lowerFilename.endswith('.jpg') or + lowerFilename.endswith('.png') or + lowerFilename.endswith('.tif') or + lowerFilename.endswith('.bmp')): + filename = self.filename + '.tif' + else: + filename = self.filename + + format = filename[-3:] + filename = filename[:-4] + base.movie(namePrefix = filename, duration=duration, format=format) + + def takeScreenshot(self): + self.filename = asksaveasfilename( + initialdir = self.lastPath, + title = 'Enter name of file...', + parent = self.component('hull')) + + if self.filename == None or self.filename == "": + return + + self.lastPath = os.path.dirname(self.filename) + +## self.filename = askstring('Toon Manager Screen Shot', 'Filename:', +## parent = self.component('hull')) +## if self.filename == None: +## return + + if self.filename == None or self.filename == "": + return + + def setText(l,text): + l['text'] = text + l = DirectLabel(text = '10', scale = 0.3, + text_fg = (1,1,1,1), + text_shadow = Vec4(0,0,0,1), + relief = None) + ivalList = [] + for i in range(10,0,-1): + ivalList.append(Func(setText, l, `i`)) + ivalList.append(Wait(1.0)) + ivalList.append(Func(l.destroy)) + ivalList.append(Func(self._takeScreenshot)) + s = Sequence(*ivalList) + s.start() + + def _takeScreenshot(self): + render2d.hide() + direct.deselectAll() + base.graphicsEngine.renderFrame() + #base.screenshot(self.filename) + + lowerFilename = self.filename.lower() + + if not (lowerFilename.endswith('.jpg') or + lowerFilename.endswith('.png') or + lowerFilename.endswith('.tif') or + lowerFilename.endswith('.bmp')): + filename = self.filename + '.tif' + else: + filename = self.filename + base.screenshot(filename, defaultFilename = 0) + render2d.show() + + def appendRtmState(self): + + comments = askstring("Comments", "Type your comments", parent = self.component('hull')) + + self.filename = asksaveasfilename( + initialdir = self.lastPath, + title = 'Enter name of file...', + parent = self.component('hull')) + + self.lastPath = os.path.dirname(self.filename) + strList = [] + strList.append("\nCOMMENTS: ") + strList.append(comments) + strList.append("\nSPECIES: ") + strList.append(self.species.get()) + strList.append("\nGENDER: ") + strList.append(self.gender.get()) + strList.append("\nHEAD: ") + strList.append(self.head.get()) + strList.append("\nTORSO: ") + strList.append(self.torso.get()) + strList.append("\nLEGS: ") + strList.append(self.legs.get()) + strList.append("\nANIM: ") + strList.append(str(self.rtm.getState())) + + rtmState = "".join(strList) + + if os.path.isfile(self.filename): + rtmFile = open(self.filename, "r+") + rtmFile.seek(0,2) + rtmFile.writelines(rtmState) + else: + rtmFile = open(self.filename, "w") + rtmFile.writelines(rtmState) + + rtmFile.close() + + def showHelp(self): + + showinfo(title='RTM HELP', message = rtmHelp, parent = self.component('hull')) + + def setParameterGroup(self, values): + self.bar1.updateProgress(values[0]) + self.bar2.updateProgress(values[1]) + self.bar3.updateProgress(values[2]) + self.bar4.updateProgress(values[3]) + + def setObstacleType(self): + print 'CHOOSING OBSTACLE DGG.TYPE:', self.obstacleType.get() + + def toggleFun(self): + if self.getVariable('Obstacle', 'Make Fun?').get(): + print 'THIS IS GOING TO BE FUN!' + else: + print 'NOT SO FUN' + + def popupFactoryDialog(self): + data = askstring('Input Factory Data', 'Factory Data:', + parent = self) + if data: + self.messageBar().helpmessage(data) + + def toggleGridSnap(self): + if self._fGridSnap.get(): + print 'Turning on grid!' + else: + print 'Turning off grid!' + + def setStomperSize(self, size): + print 'New Stomper Size:', size + + + +base.rtm = RobotToonManager() +base.rtm.popupControls() +direct.grid.enable() +camera.setPosHpr(0,-60,5,0,0,0) +run() + + +""" +# TOONS OF THE MONTH +# serverString = '7412010101020302 03000f07000707' +# July +# serverString = '7413000000140100 01050218001818' + +# KEEFE AND YOUNG COMMERCIAL +spike = rtm.makeToonFromServerString('0f00741702010103040304020d12001212') +biscuit = rtm.makeToonFromServerString('0f0074030201011c1b131b071008000808') + +def RunAction(): + spike.setAnimState('run') + biscuit.setAnimState('run') + +def WalkAction(): + spike.setAnimState('walk') + biscuit.setAnimState('walk') + +tl = Toplevel() +bRun = Button(tl, text = 'Run', command = RunAction) +bRun.pack(expand = 1, fill = X) + +bWalk= Button(tl, text = 'Walk', command = WalkAction) +bWalk.pack(expand = 1, fill = X) + +""" diff --git a/toontown/src/toon/Sources.pp b/toontown/src/toon/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/toon/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/toon/TTEmote.py b/toontown/src/toon/TTEmote.py new file mode 100644 index 0000000..7003591 --- /dev/null +++ b/toontown/src/toon/TTEmote.py @@ -0,0 +1,770 @@ +"""TTEmote module: contains the TTEmote class + +This class defines emotions that the Toon can enable +at certain times during the game. (Times when a toon +should not be able to use an emote are: during some +parts of a battle, minigames, while running, etc) + +""" + + + +import Toon, ToonDNA +from direct.interval.IntervalGlobal import * +from otp.otpbase import OTPLocalizer +from toontown.toonbase import TTLocalizer +from otp.otpbase import OTPLocalizer +import types +from direct.showbase import PythonUtil +from pandac.PandaModules import * +from otp.avatar import Emote +from direct.directnotify import DirectNotifyGlobal + +# The emoteHandlerFunc takes an emote name from the SpeedChat menu +# and calls setAnimState function for the toon +#def emoteHandlerFunc(): + +# Some emotes are Tracks which can be played, joined in progress, and stopped +# Emotes generally return a track (can be empty for simple things), and a duration (can +# be zero for simple things). It is important that the duration is specified for most +# animations, since we will need to restart the trackToAnim task after the animation +# is played. Exceptions are sleep, yes, no, etc, where we are not playing an +# actual animation, rather just putting the toon in sleep state, or manually moving +# it's head for yes/no. + +# added later: sometime we have something special we want to play on exiting +# the emote....add this to the exitTrack arg. + +# special case behavior: +# keep track of which is the sleep index, +EmoteSleepIndex = 4 + +EmoteClear = -1 + +def doVictory(toon, volume = 1): + duration = toon.getDuration('victory', 'legs') + sfx = base.loadSfx("phase_3.5/audio/sfx/ENC_Win.mp3") + + # Loop this sound effect for the appropriate duration + sfxDuration = duration - 1.0 + sfxTrack = SoundInterval(sfx, loop=1, duration=sfxDuration, node=toon, volume=volume) + + # We must include the sound interval in the overall sequence so + # that it gets cleaned up properly should we suddenly abort, but + # we artificially set the duration to 0 to make the Emote system + # happy (since it expects a zero-length sequence). + track = Sequence(Func(toon.play, 'victory'), + sfxTrack, + duration = 0) + return track, duration, None + +def doJump(toon, volume = 1): + track = Sequence(Func(toon.play, 'jump')) + return track, 0, None + +def doDead(toon, volume = 1): + toon.animFSM.request('Sad') + return None, 0, None + +def doAnnoyed(toon, volume = 1): + duration = toon.getDuration('angry', 'torso') + # This sfx is in phase 3.5 because it can be triggered during the tutorial + sfx = None + if (toon.style.getAnimal() == 'bear'): + sfx = base.loadSfx("phase_3.5/audio/dial/AV_bear_exclaim.mp3") + else: + sfx = base.loadSfx("phase_3.5/audio/sfx/avatar_emotion_angry.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = volume, node = toon) + track = Sequence(Func(toon.angryEyes), + Func(toon.blinkEyes), + Func(toon.play, 'angry'), + Func(playSfx) + ) + exitTrack = Sequence( + Func(toon.normalEyes), + Func(toon.blinkEyes)) + return track, duration, exitTrack + +def doAngryEyes(toon, volume=1): + track = Sequence(Func(toon.angryEyes), + Func(toon.blinkEyes), + Wait(10.0), + Func(toon.normalEyes)) + return track, .1, None + +def doHappy(toon, volume=1): + track = Sequence(Func(toon.play, 'jump'), + Func(toon.normalEyes), + Func(toon.blinkEyes)) + duration = toon.getDuration('jump', 'legs') + return track, duration, None + +def doSad(toon, volume=1): + track = Sequence(Func(toon.sadEyes), + Func(toon.blinkEyes)) + exitTrack = Sequence(Func(toon.normalEyes), + Func(toon.blinkEyes)) + return track, 3, exitTrack + +# Sleep is a special case. We don't want to set up a +# sleep track....just put the avatar to sleep, and then +# wait for any activity on the client to wake him up +def doSleep(toon, volume=1): + #if toon == base.localAvatar: + # toon.gotoSleep() + #return None, 0, None + duration = 4 + track = Sequence(Func(toon.stopLookAround), + Func(toon.stopBlink), + Func(toon.closeEyes), + Func(toon.lerpLookAt, Point3(0, 1, -4)), + Func(toon.loop, "neutral"), + Func(toon.setPlayRate, 0.4, "neutral"), + Func(toon.setChatAbsolute, TTLocalizer.ToonSleepString, CFThought), + ) + def wakeUpFromSleepEmote(): + toon.startLookAround() + toon.openEyes() + toon.startBlink() + toon.setPlayRate(1, "neutral") + if toon.nametag.getChat() == TTLocalizer.ToonSleepString: + toon.clearChat() + toon.lerpLookAt(Point3(0, 1, 0), time=0.25) + exitTrack = Sequence(Func(wakeUpFromSleepEmote), + ) + return track, duration, exitTrack + + +# Nodding yes or no. These also work like sleep, since we are +# manually animating the head, there is no need to return the +# track. Just play it, and the rest of the toons body continues +# to do whatever it was previously doing +def doYes(toon, volume = 1): + tracks = Parallel(autoFinish = 1) + for lod in toon.getLODNames(): + h = toon.getPart('head', lod) + tracks.append( + Sequence(LerpHprInterval(h, 0.1, Vec3(0,-30,0)), LerpHprInterval(h, 0.15, Vec3(0,20,0)), + LerpHprInterval(h, 0.15, Vec3(0,-20,0)), LerpHprInterval(h, 0.15, Vec3(0,20,0)), + LerpHprInterval(h, 0.15, Vec3(0,-20,0)), LerpHprInterval(h, 0.15, Vec3(0,20,0)), + LerpHprInterval(h, 0.1, Vec3(0,0,0)), )) + tracks.start() + return None, 0, None + +def doNo(toon, volume = 1): + tracks = Parallel(autoFinish = 1) + for lod in toon.getLODNames(): + h = toon.getPart('head', lod) + tracks.append( + Sequence(LerpHprInterval(h, 0.1, Vec3(40,0,0)), LerpHprInterval(h, 0.15, Vec3(-40,0,0)), + LerpHprInterval(h, 0.15, Vec3(40,0,0)), LerpHprInterval(h, 0.15, Vec3(-40,0,0)), + LerpHprInterval(h, 0.15, Vec3(20,0,0)), LerpHprInterval(h, 0.15, Vec3(-20,0,0)), + LerpHprInterval(h, 0.1, Vec3(0,0,0)), )) + tracks.start() + return None, 0, None + +def doOk(toon, volume = 1): + # fill in when we have an animation ready + return None, 0, None + +def doShrug(toon, volume = 1): + # This sfx is in phase 3.5 because it can be triggered during the tutorial + sfx = base.loadSfx("phase_3.5/audio/sfx/avatar_emotion_shrug.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = volume, node = toon) + track = Sequence(Func(toon.play, 'shrug'), + Func(playSfx)) + duration = toon.getDuration('shrug', 'torso') + return track, duration, None + +def doWave(toon, volume = 1): + track = Sequence(Func(toon.play, 'wave')) + duration = toon.getDuration('wave', 'torso') + return track, duration, None + +def doApplause(toon, volume = 1): + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_applause.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = 1, node = toon) + track = Sequence(Func(toon.play, 'applause'), + Func(playSfx)) + duration = toon.getDuration('applause', 'torso') + return track, duration, None + +def doConfused(toon, volume =1): + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_confused.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, node = toon, volume = volume) + track = Sequence(Func(toon.play, 'confused'), + Func(playSfx)) + duration = toon.getDuration('confused', 'torso') + return track, duration, None + +def doSlipForward(toon, volume = 1): + sfx = base.loadSfx("phase_4/audio/sfx/MG_cannon_hit_dirt.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = volume, node = toon) + sfxDelay = 0.7 + track = Sequence(Func(toon.play, 'slip-forward'), + Wait(sfxDelay), + Func(playSfx)) + duration = toon.getDuration('slip-forward', 'torso') - sfxDelay + return track, duration, None + +def doBored(toon, volume = 1): + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_bored.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = volume, node = toon) + sfxDelay = 2.2 + track = Sequence(Func(toon.play, 'bored'), + Wait(sfxDelay), + Func(playSfx) + ) + # Subtract out wait to get proper wait interval in DoEmote + duration = toon.getDuration('bored', 'torso') - sfxDelay + return track, duration, None + +def doBow(toon, volume = 1): + if toon.style.torso[1] == 'd': + track = Sequence(Func(toon.play, 'curtsy')) + duration = toon.getDuration('curtsy', 'torso') + else: + track = Sequence(Func(toon.play, 'bow')) + duration = toon.getDuration('bow', 'torso') + return track, duration, None + +def doSlipBackward(toon, volume = 1): + sfx = base.loadSfx("phase_4/audio/sfx/MG_cannon_hit_dirt.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(): + base.playSfx(sfx, volume = volume, node = toon) + sfxDelay = 0.7 + track = Sequence(Func(toon.play, 'slip-backward'), + Wait(sfxDelay), + Func(playSfx)) + duration = toon.getDuration('slip-backward', 'torso') - sfxDelay + return track, duration, None + +def doThink(toon, volume = 1): + duration = (47.0 / 24.0) * 2 + animTrack = Sequence( + ActorInterval(toon, 'think', startFrame = 0, endFrame = 46), + ActorInterval(toon, 'think', startFrame = 46, endFrame = 0), + ) + track = Sequence(animTrack, + duration = 0) + return track, duration, None + +def doCringe(toon, volume = 1): + track = Sequence(Func(toon.play, 'cringe')) + duration = toon.getDuration('cringe', 'torso') + return track, duration, None + +def doResistanceSalute(toon, volume = 1): + playRate = 0.75 + duration = ((10.0 / 24.0) * ( 1 / playRate)) * 2 + animTrack = Sequence( + Func(toon.setChatAbsolute, OTPLocalizer.CustomSCStrings[4020], CFSpeech | CFTimeout), + Func(toon.setPlayRate, playRate, 'victory'), + ActorInterval(toon, 'victory', playRate = playRate, startFrame = 0, endFrame = 9), + ActorInterval(toon, 'victory', playRate = playRate, startFrame = 9, endFrame = 0), + ) + track = Sequence(animTrack, + duration = 0) + return track, duration, None + +def doNothing(toon, volume =1): + return None, 0, None + +def doSurprise(toon, volume = 1): + sfx = None + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_surprise.mp3") + + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + + def playAnim(anim): + anim.start() + + def stopAnim(anim): + anim.finish() + toon.stop() + sfx.stop() + + anim = Sequence(ActorInterval(toon, 'conked', startFrame = 9, endFrame = 50), + ActorInterval(toon, 'conked', startFrame = 70, endFrame = 101)) + + track = Sequence(Func(toon.stopBlink), + Func(toon.surpriseEyes), + Func(toon.showSurpriseMuzzle), + Parallel(Func(playAnim, anim), Func(playSfx, volume))) + + exitTrack = Sequence(Func(toon.hideSurpriseMuzzle), + Func(toon.openEyes), + Func(toon.startBlink), + Func(stopAnim, anim)) + + # Adding duration of 0.1 so that we return back to the last animation. + return track, 3., exitTrack + +def doUpset(toon, volume = 1): + sfx = None + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_very_sad_1.mp3") + + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + + def playAnim(anim): + anim.start() + + def stopAnim(anim): + anim.finish() + toon.stop() + sfx.stop() + + anim = Sequence(ActorInterval(toon, 'bad-putt', startFrame = 29, endFrame = 59, playRate = -0.75), + ActorInterval(toon, 'bad-putt', startFrame = 29, endFrame = 59, playRate = 0.75)) + + track = Sequence(Func(toon.sadEyes), + Func(toon.blinkEyes), + Func(toon.showSadMuzzle), + Parallel(Func(playAnim, anim), Func(playSfx, volume))) + exitTrack = Sequence(Func(toon.hideSadMuzzle), + Func(toon.normalEyes), + Func(stopAnim, anim)) + return track, 4., exitTrack + +def doDelighted(toon, volume = 1): + sfx = None + sfx = base.loadSfx('phase_4/audio/sfx/delighted_06.mp3') + + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + + def playAnim(anim): + anim.start() + + def stopAnim(anim): + anim.finish() + toon.stop() + sfx.stop() + + anim = Sequence(ActorInterval(toon, 'left'), + Wait(1), + ActorInterval(toon, 'left', playRate = -1)) + + track = Sequence(Func(toon.blinkEyes), + Func(toon.showSmileMuzzle), + Parallel(Func(playAnim, anim), Func(playSfx, volume))) + + exitTrack = Sequence(Func(toon.hideSmileMuzzle), + Func(toon.blinkEyes), + Func(stopAnim, anim)) + + return track, 2.5, exitTrack + +def doFurious(toon, volume = 1): + duration = toon.getDuration('angry', 'torso') + # This sfx is in phase 3.5 because it can be triggered during the tutorial + sfx = None + sfx = base.loadSfx("phase_4/audio/sfx/furious_03.mp3") + + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + + track = Sequence(Func(toon.angryEyes), + Func(toon.blinkEyes), + Func(toon.showAngryMuzzle), + Func(toon.play, 'angry'), + Func(playSfx, volume)) + exitTrack = Sequence( + Func(toon.normalEyes), + Func(toon.blinkEyes), + Func(toon.hideAngryMuzzle)) + return track, duration, exitTrack + +def doLaugh(toon, volume = 1): + sfx = None + sfx = base.loadSfx("phase_4/audio/sfx/avatar_emotion_laugh.mp3") + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + def playAnim(): + toon.setPlayRate(10, 'neutral') + toon.loop('neutral') + def stopAnim(): + toon.setPlayRate(1, 'neutral') + track = Sequence(Func(toon.blinkEyes), + Func(toon.showLaughMuzzle), + Func(playAnim), + Func(playSfx, volume)) + exitTrack = Sequence(Func(toon.hideLaughMuzzle), + Func(toon.blinkEyes), + Func(stopAnim)) + return track, 2, exitTrack + +def getSingingNote(toon, note, volume = 1): + """ + Returns a track of the toon singing the requested note. + note is a string in small letters. Eg - g1, a, b, c, d, e, f, g2 + """ + sfx = None + filePath = "phase_3.5/audio/dial/" + filePrefix = "tt_s_dlg_sng_" + fileSuffix = ".mp3" + speciesName = ToonDNA.getSpeciesName(toon.style.head) + sfx = base.loadSfx(filePath + filePrefix + speciesName + "_" + note + fileSuffix) + # Need to use a Func interval since DoEmote expects a 0 duration track + def playSfx(volume = 1): + base.playSfx(sfx, volume = volume, node = toon) + def playAnim(): + toon.loop('neutral') + def stopAnim(): + toon.setPlayRate(1, 'neutral') + + track = Sequence(Func(toon.showSurpriseMuzzle), + Parallel(Func(playAnim), Func(playSfx, volume))) + + exitTrack = Sequence(Func(toon.hideSurpriseMuzzle), + Func(stopAnim)) + + return track, 0.1, exitTrack + +def playSingingAnim(toon): + """ + """ + pass + +def stopSinginAnim(toon): + """ + """ + pass + +def singNote1(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'g1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'g2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'g3') + +def singNote2(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'a1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'a2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'a3') + +def singNote3(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'b1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'b2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'b3') + +def singNote4(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'c1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'c2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'c3') + +def singNote5(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'd1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'd2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'd3') + +def singNote6(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'e1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'e2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'e3') + +def singNote7(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'f1') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'f2') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'f3') + +def singNote8(toon, volume = 1): + if base.config.GetBool('want-octaves', True): + if toon.style.getTorsoSize() == 'short': + return getSingingNote(toon, 'g2') + elif toon.style.getTorsoSize() == 'medium': + return getSingingNote(toon, 'g3') + elif toon.style.getTorsoSize() == 'long': + return getSingingNote(toon, 'g4') + +def singNoteEmpty(toon, volume = 0): + track = Sequence() + return track, 0.1, None + +def returnToLastAnim(toon): + if hasattr(toon, "playingAnim") and toon.playingAnim: + toon.loop(toon.playingAnim) + else: + if not hasattr(toon, "hp") or toon.hp > 0: + toon.loop('neutral') + else: + toon.loop('sad-neutral') + +# Emote data is stored in the order it appears in the SpeedChat +# The integer stored is the reference count to the Emote. If t +# count goes above zero, it means that the emote is disabled. +# a minigame might increment the reference count if it wants to +# disable emotes. + +# NOTE: Missing slot for idea, may require database patch to ad +EmoteFunc = [ + [doWave, 0], # 0 + [doHappy, 0], # 1 + [doSad, 0], # 2 + [doAnnoyed, 0], # 3 + [doSleep, 0], # 4 + [doShrug, 0], # 5 Catalog Series 1 + [doVictory, 0], # 6 Catalog Series 2 + [doThink, 0], # 7 Catalog Series 4 + [doBored, 0], # 8 Catalog Series 2 + [doApplause, 0], # 9 Catalog Series 1 + [doCringe, 0], # 10 Catalog Series 4 + [doConfused, 0], # 11 Catalog Series 2 + [doSlipForward, 0], # 12 Catalog Series 7 + [doBow, 0], # 13 Catalog Series 1 + [doSlipBackward, 0], # 14 Catalog Series 7 + [doResistanceSalute, 0], # 15 Toons of the World Unite! + [doNothing, 0], # 16 doLaugh - TBD + [doYes, 0], #17 doYes + [doNo, 0], #18 doNo + [doOk, 0], #19 doOk + [doSurprise, 0], #20 doSurprise muzzle emote + [doUpset, 0], #21 doUpset muzzle emote + [doDelighted, 0],#22 doDelighted muzzle emote + [doFurious, 0], #23 doFurious muzzle emote + [doLaugh, 0], #24 doLaugh muzzle emote +## [singNote1, 0], #25 sing c1 note of toon species +## [singNote2, 0], #26 sing d note of toon species +## [singNote3, 0], #27 sing e note of toon species +## [singNote4, 0], #28 sing f note of toon species +## [singNote5, 0], #29 sing g note of toon species +## [singNote6, 0], #30 sing a note of toon species +## [singNote7, 0], #31 sing b note of toon species +## [singNote8, 0], #32 sing c2 note of toon species +## [singNoteEmpty, 0], #33 sing blank note of toon species + ] + +assert(len(EmoteFunc) == len(OTPLocalizer.EmoteList)) + +class TTEmote(Emote.Emote): + notify = DirectNotifyGlobal.directNotify.newCategory('TTEmote') + # Store sleep index, because it is a special case + SLEEP_INDEX = 4 + + def __init__(self): + # emoteAccess is an array of bits that tells which + # emotes we have access to. Must be the same size + # as Emote.emoteFunc. Yes, Ok, No, Bye and Hello + # should be turned off, because they appear in a + # different part of the SC menu + + self.emoteFunc = EmoteFunc + # Index of "body" emotes (emotes using all of the body) + self.bodyEmotes = [0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,20,21,22,23,24] + + # Index of "head" emotes (emotes just involving the head) + self.headEmotes = [2,17,18,19] + + # sanity check + if len(self.emoteFunc) != len(OTPLocalizer.EmoteList): + self.notify.error("Emote.EmoteFunc and OTPLocalizer.EmoteList are different lengths.") + + # the current track being played + self.track = None + + self.stateChangeMsgLocks = 0 + self.stateHasChanged = 0 + + # utility functions to queue up EmoteEnableStateChanged messages + + def lockStateChangeMsg(self): + self.stateChangeMsgLocks += 1 + + def unlockStateChangeMsg(self): + if self.stateChangeMsgLocks <= 0: + print PythonUtil.lineTag() + ": someone unlocked too many times" + return + + self.stateChangeMsgLocks -= 1 + if self.stateChangeMsgLocks == 0 and self.stateHasChanged: + messenger.send(self.EmoteEnableStateChanged) + self.stateHasChanged = 0 + + def emoteEnableStateChanged(self): + if self.stateChangeMsgLocks > 0: + self.stateHasChanged = 1 + else: + messenger.send(self.EmoteEnableStateChanged) + + # All emotes + def disableAll(self, toon, msg = None): + if toon != base.localAvatar: + return + # increment the reference count on all emotes + self.disableGroup(range(len(self.emoteFunc)), toon) + + #self.printEmoteState("disableAll", msg) + + def releaseAll(self, toon, msg = None): + if toon != base.localAvatar: + return + + # decrement the reference count on all emotes + self.enableGroup(range(len(self.emoteFunc)), toon) + #self.printEmoteState("releaseAll", msg) + + # Body emotes + def disableBody(self, toon, msg = None): + if toon != base.localAvatar: + return + + # increment the reference count on body emotes + self.disableGroup(self.bodyEmotes, toon) + #self.printEmoteState("disableBody", msg) + + def releaseBody(self, toon, msg = None): + if toon != base.localAvatar: + return + + # decrement the reference count on body emotes + self.enableGroup(self.bodyEmotes, toon) + #self.printEmoteState("releaseBody", msg) + + # Head emotes + def disableHead(self, toon, msg = None): + if toon != base.localAvatar: + return + # increment the reference count on head emotes + self.disableGroup(self.headEmotes, toon) + #self.printEmoteState("disableHead", msg) + + def releaseHead(self, toon, msg = None): + if toon != base.localAvatar: + return + + # decrement the reference count on head emotes + self.enableGroup(self.headEmotes, toon) + #self.printEmoteState("releaseHead", msg) + + def getHeadEmotes(self): + return self.headEmotes + + # groups of emotes + def disableGroup(self, indices, toon): + self.lockStateChangeMsg() + for i in indices: + self.disable(i, toon) + self.unlockStateChangeMsg() + + def enableGroup(self, indices, toon): + self.lockStateChangeMsg() + for i in indices: + self.enable(i, toon) + self.unlockStateChangeMsg() + + # Specific emotes + def disable(self, index, toon): + # find the emotes index if we are given a string + if isinstance(index, types.StringType): + index = OTPLocalizer.EmoteFuncDict[index] + + self.emoteFunc[index][1] = self.emoteFunc[index][1] + 1 + if toon is base.localAvatar: + if self.emoteFunc[index][1] == 1: + self.emoteEnableStateChanged() + + def enable(self, index, toon): + # find the emotes index if we are given a string + if isinstance(index, types.StringType): + index = OTPLocalizer.EmoteFuncDict[index] + + self.emoteFunc[index][1] = self.emoteFunc[index][1] - 1 + if toon is base.localAvatar: + if self.emoteFunc[index][1] == 0: + self.emoteEnableStateChanged() + + def doEmote(self, toon, emoteIndex, ts=0, volume = 1): + try: + func = self.emoteFunc[emoteIndex][0] + except: + print "Error in finding emote func %s" % emoteIndex + return None, None + + def clearEmoteTrack(): + base.localAvatar.emoteTrack = None + # Send everybody a clear for this field + # TODO: do we need to make sure we are still a valid distObj here? + base.localAvatar.d_setEmoteState(self.EmoteClear, 1.0) + + # Get the track for this emotion + if (volume == 1): + track, duration, exitTrack = func(toon) + else: + track, duration, exitTrack = func(toon, volume) + if (track != None): # and toon == base.localAvatar): + # disable other emotes during this emote + track = Sequence(Func(self.disableAll, toon, "doEmote"), track) + if (duration > 0): + track = Sequence(track,Wait(duration)) + if (exitTrack != None): + track = Sequence(track, exitTrack) + #if (emoteIndex != EmoteSleepIndex and duration > 0): + if (duration > 0): + track = Sequence(track, Func(returnToLastAnim, toon)) + # reenable emotes + track = Sequence(track, Func(self.releaseAll, toon, "doEmote"), autoFinish = 1) + if toon.isLocal(): + track = Sequence(track, Func(clearEmoteTrack)) + + if track != None: + if toon.emote != None: + toon.emote.finish() + toon.emote = None + toon.emote = track + track.start(ts) + + del clearEmoteTrack + + return track, duration + + def printEmoteState(self, action, msg): + if __debug__: + print "%s(%s), body(%s), head(%s)" % (action, msg, EmoteFunc[0][1], EmoteFunc[2][1]) + +Emote.globalEmote = TTEmote() + diff --git a/toontown/src/toon/TailorClothesGUI.py b/toontown/src/toon/TailorClothesGUI.py new file mode 100644 index 0000000..4121e65 --- /dev/null +++ b/toontown/src/toon/TailorClothesGUI.py @@ -0,0 +1,30 @@ +from toontown.makeatoon import ClothesGUI +import ToonDNA + +class TailorClothesGUI(ClothesGUI.ClothesGUI): + notify = directNotify.newCategory("MakeClothesGUI") + + def __init__(self, doneEvent, swapEvent, tailorId): + ClothesGUI.ClothesGUI.__init__(self, ClothesGUI.CLOTHES_TAILOR, + doneEvent, swapEvent) + self.tailorId = tailorId + + def setupScrollInterface(self): + self.dna = self.toon.getStyle() + gender = self.dna.getGender() + + # Handle the case where we're in a shop (clothes aren't randomized, + # we can't be changing gender, etc. + if (self.swapEvent != None): + self.tops = ToonDNA.getTops(gender, tailorId = self.tailorId) + self.bottoms = ToonDNA.getBottoms(gender, + tailorId = self.tailorId) + self.gender = gender + # We're off the wheel of choices to start with because we're + # wearing clothes purchased elsewhere + self.topChoice = -1 + self.bottomChoice = -1 + + # setup the buttons + self.setupButtons() + diff --git a/toontown/src/toon/Toon.py b/toontown/src/toon/Toon.py new file mode 100644 index 0000000..abe6ce1 --- /dev/null +++ b/toontown/src/toon/Toon.py @@ -0,0 +1,3769 @@ +"""Toon module: contains the Toon class""" +"""Toon module: contains the Toon class""" + +from otp.avatar import Avatar +import ToonDNA +from direct.task.Task import Task +from toontown.suit import SuitDNA +from direct.actor import Actor +import string +from ToonHead import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from otp.otpbase import OTPLocalizer +from toontown.toonbase import TTLocalizer +import random +from toontown.effects import Wake +import TTEmote +from otp.avatar import Emote +import Motion +from toontown.hood import ZoneUtil +from toontown.battle import SuitBattleGlobals +from otp.otpbase import OTPGlobals +from toontown.effects import DustCloud +from direct.showbase.PythonUtil import Functor +from toontown.distributed import DelayDelete +import types + +""" +import Toon +t = Toon.Toon() +import ToonDNA +d = ToonDNA.ToonDNA() +# d.newToon( ('dll', 'md', 'l', 'f') ) +d.newToonRandom(gender='f') +t.setDNA(d) +t.loop('neutral') +t.reparentTo(render) +t.setPos(base.localAvatar, 0,0,0) + +from pandac.Texture import Texture +shirtTex = loader.loadTexture("/i/beta/toons/maya/work/characterClothing/textures/female_shirt2.rgb") +shirtTex.setMinfilter(Texture.FTLinearMipmapLinear) +shirtTex.setMagfilter(Texture.FTLinear) +sleeveTex = loader.loadTexture("/i/beta/toons/maya/work/characterClothing/textures/female_sleeve2.rgb") +sleeveTex.setMinfilter(Texture.FTLinearMipmapLinear) +sleeveTex.setMagfilter(Texture.FTLinear) +bottomTex = loader.loadTexture("/i/beta/toons/maya/work/characterClothing/textures/female_skirt2.rgb") +bottomTex.setMinfilter(Texture.FTLinearMipmapLinear) +bottomTex.setMagfilter(Texture.FTLinear) +for lodName in t.getLODNames(): + thisPart = t.getPart("torso", lodName) + top = thisPart.find("**/torso-top") + top.setTexture(shirtTex, 1) + top.setColor(1,1,1,1) + sleeves = thisPart.find("**/sleeves") + sleeves.setTexture(sleeveTex, 1) + sleeves.setColor(1,1,1,1) + bottoms = thisPart.findAllMatches("**/torso-bot") + for bottomNum in range(0, bottoms.getNumPaths()): + bottom = bottoms.getPath(bottomNum) + bottom.setTexture(bottomTex, 1) + bottom.setColor(1,1,1,1) + + +c = VBase4(0.96875, 0.691406, 0.699219, 1.0) +parts = t.findAllMatches("**/arms") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +parts = t.findAllMatches("**/neck") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +parts = t.findAllMatches("**/torso*") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(1,1,1,1) + +parts = t.findAllMatches("**/legs") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +parts = t.findAllMatches("**/feet") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +parts = t.findAllMatches("**/head*") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +parts = t.findAllMatches("**/ear?-*") +for partNum in range(0, parts.getNumPaths()): + parts.getPath(partNum).setColor(c) + +""" + +SLEEP_STRING = TTLocalizer.ToonSleepString + +# Moved these to OTPGlobals +""" +# Indices into standWalkRunReverse, and also return values from setSpeed(). +STAND_INDEX = 0 +WALK_INDEX = 1 +RUN_INDEX = 2 +REVERSE_INDEX = 3 +""" + +# module vars +DogDialogueArray = [] +CatDialogueArray = [] +HorseDialogueArray = [] +RabbitDialogueArray = [] +MouseDialogueArray = [] +DuckDialogueArray = [] +MonkeyDialogueArray = [] +BearDialogueArray = [] +PigDialogueArray = [] + +LegsAnimDict = {} +TorsoAnimDict = {} +HeadAnimDict = {} + +Preloaded = [] + +# list of anims for all toon parts +# create-a-toon +Phase3AnimList = ( + ("neutral", "neutral"), + ("run", "run"), + ) + +# tutorial +Phase3_5AnimList = ( + ("walk", "walk"), + ("teleport", "teleport"), + ("book", "book"), + ("jump", "jump"), + ("running-jump", "running-jump"), + ("jump-squat", "jump-zstart"), + ("jump-idle", "jump-zhang"), + ("jump-land", "jump-zend"), + ("running-jump-squat", "leap_zstart"), + ("running-jump-idle", "leap_zhang"), + ("running-jump-land", "leap_zend"), + # squirt (and drop) + ("pushbutton", "press-button"), + # throw + ("throw", "pie-throw"), + ("victory", "victory-dance"), + ("sidestep-left", "sidestep-left"), + # react + ("conked", "conked"), + ("cringe", "cringe"), + # emotes that are available to brand-new toons + ("wave", "wave"), + ("shrug", "shrug"), + ("angry", "angry"), + ### + ### Special Animations + ### + # tutorial + ### WARNING: these channels only exist for Flippy!!! (dna: dls, m, m) + ("tutorial-neutral", "tutorial-neutral"), + ("left-point", "left-point"), + ("right-point", "right-point"), + ("right-point-start", "right-point-start"), + ("give-props", "give-props"), + ("give-props-start", "give-props-start"), + ("right-hand", "right-hand"), + ("right-hand-start", "right-hand-start"), + ("duck", "duck"), + ("sidestep-right", "jump-back-right"), + # toon hq + ### WARNING: this cycle only exists for dogMM!!! + ("periscope", "periscope"), + ) + +# minigame +Phase4AnimList = ( + ("sit", "sit"), + ("sit-start", "intoSit"), + ("swim", "swim"), + ("tug-o-war", "tug-o-war"), + ("sad-walk", "losewalk"), + ("sad-neutral", "sad-neutral"), + ("up", "up"), + ("down", "down"), + ("left", "left"), + ("right", "right"), + # emotes (that must be purchased) + ("applause", "applause"), + ("confused", "confused"), + ("bow", "bow"), + ("curtsy", "curtsy"), + ("bored", "bored"), + ("think", "think"), + + # For use in battle + ("battlecast", "fish"), + + ("cast", "cast"), + ("castlong", "castlong"), + ("fish-end", "fishEND"), + ("fish-neutral", "fishneutral"), + ("fish-again", "fishAGAIN"), + ("reel", "reel"), + ("reel-H", "reelH"), + ("reel-neutral", "reelneutral"), + ("pole", "pole"), + ("pole-neutral", "poleneutral"), + + ("slip-forward", "slip-forward"), + ("slip-backward", "slip-backward"), + # anims for the catching game + ("catch-neutral", "gameneutral"), + ("catch-run", "gamerun"), + ("catch-eatneutral", "eat_neutral"), + ("catch-eatnrun", "eatnrun"), + ("catch-intro-throw", "gameThrow"), + + # anims for swing game + ('swing', 'swing'), + # pet cycles + ("pet-start", "petin"), + ("pet-loop", "petloop"), + ("pet-end", "petend"), + + # For toonhall + ("scientistJealous", "scientistJealous"), + ("scientistEmcee", "scientistEmcee"), + ("scientistWork", "scientistWork"), + ("scientistGame", "scientistGame"), + ) + +# battle +Phase5AnimList = ( + ("water-gun", "water-gun"), + ("hold-bottle", "hold-bottle"), + ("firehose", "firehose"), + ("spit", "spit"), + # heal + ("tickle", "tickle"), + ("smooch", "smooch"), + ("happy-dance", "happy-dance"), + ("sprinkle-dust", "sprinkle-dust"), + ("juggle", "juggle"), + ("climb", "climb"), + # sound + ("sound", "shout"), + # trap + ("toss", "toss"), + # lure + ("hold-magnet", "hold-magnet"), + ("hypnotize", "hypnotize"), + # react + ("struggle", "struggle"), + ("lose", "lose"), + ("melt", "melt"), + ) + +# estate +Phase5_5AnimList = ( + ("takePhone", "takePhone"), + ("phoneNeutral", "phoneNeutral"), + ("phoneBack", "phoneBack"), + ("bank", "jellybeanJar"), + ("callPet", "callPet"), + ("feedPet", "feedPet"), + ("start-dig", "into_dig"), + ("loop-dig", "loop_dig"), + ("water", "water"), + ) + +# estate +Phase6AnimList = ( + ("headdown-putt","headdown-putt"), + ("into-putt","into-putt"), + ("loop-putt","loop-putt"), + ("rotateL-putt","rotateL-putt"), + ("rotateR-putt","rotateR-putt"), + ("swing-putt","swing-putt"), + ("look-putt","look-putt"), + ("lookloop-putt","lookloop-putt"), + ("bad-putt","bad-putt"), + ("badloop-putt","badloop-putt"), + ("good-putt","good-putt"), + ) + +# sellbotHQ +Phase9AnimList = ( + ("push", "push"), + ) + +# cashbotHQ +Phase10AnimList = ( + ("leverReach", "leverReach"), + ("leverPull", "leverPull"), + ("leverNeutral", "leverNeutral"), + ) + +# bossbotHq +Phase12AnimList = ( + ) + +if not base.config.GetBool('want-new-anims', 1): + # toon leg models dictionary + LegDict = { "s":"/models/char/dogSS_Shorts-legs-", \ + "m":"/models/char/dogMM_Shorts-legs-", \ + "l":"/models/char/dogLL_Shorts-legs-" } + + # toon torso models dictionary + TorsoDict = { "s":"/models/char/dogSS_Naked-torso-", \ + "m":"/models/char/dogMM_Naked-torso-", \ + "l":"/models/char/dogLL_Naked-torso-", \ + "ss":"/models/char/dogSS_Shorts-torso-", \ + "ms":"/models/char/dogMM_Shorts-torso-", \ + "ls":"/models/char/dogLL_Shorts-torso-", \ + "sd":"/models/char/dogSS_Skirt-torso-", \ + "md":"/models/char/dogMM_Skirt-torso-", \ + "ld":"/models/char/dogLL_Skirt-torso-" } +else: + # toon leg models dictionary + LegDict = { "s":"/models/char/tt_a_chr_dgs_shorts_legs_", \ + "m":"/models/char/tt_a_chr_dgm_shorts_legs_", \ + "l":"/models/char/tt_a_chr_dgl_shorts_legs_" } + + # toon torso models dictionary + TorsoDict = { "s":"/models/char/dogSS_Naked-torso-", \ + "m":"/models/char/dogMM_Naked-torso-", \ + "l":"/models/char/dogLL_Naked-torso-", \ + "ss":"/models/char/tt_a_chr_dgs_shorts_torso_", \ + "ms":"/models/char/tt_a_chr_dgm_shorts_torso_", \ + "ls":"/models/char/tt_a_chr_dgl_shorts_torso_", \ + "sd":"/models/char/tt_a_chr_dgs_skirt_torso_", \ + "md":"/models/char/tt_a_chr_dgm_skirt_torso_", \ + "ld":"/models/char/tt_a_chr_dgl_skirt_torso_" } + +# toon head models dictionary is in ToonHead.py. + +def loadModels(): + """ + Toon class model and texture initialize + """ + preloadAvatars = base.config.GetBool("preload-avatars", 0) + + if preloadAvatars: + # preload the clothing textures + global Preloaded + + def loadTex(path): + # Only preload the texture if we have downloaded that phase + # Peel off the phase and see if it is complete + #if (path[:5] == "phase"): + # # The phase is from the 6th letter up to the / + # # This covers the phase_3.5/ case + # phaseStr = path[6:path.index('/')] + # phase = float(phaseStr) + # print phase + # if (phase in Launcher.LauncherPhases): + # if not self.getPhaseComplete(phase): + # return 0 + tex = loader.loadTexture(path) + tex.setMinfilter(Texture.FTLinearMipmapLinear) + tex.setMagfilter(Texture.FTLinear) + Preloaded.append(tex) + + # Boys + for shirt in ToonDNA.Shirts: + loadTex(shirt) + for sleeve in ToonDNA.Sleeves: + loadTex(sleeve) + for short in ToonDNA.BoyShorts: + loadTex(short) + + # Girls + # Girl bottoms are a tuple (texName, type) + for bottom in ToonDNA.GirlBottoms: + loadTex(bottom[0]) + + # preload the leg models + for key in LegDict.keys(): + fileRoot = LegDict[key] + model = loader.loadModelNode("phase_3" + fileRoot + "1000") + Preloaded.append(model) + model = loader.loadModelNode("phase_3" + fileRoot + "500") + Preloaded.append(model) + model = loader.loadModelNode("phase_3" + fileRoot + "250") + Preloaded.append(model) + + #preload the torso models + for key in TorsoDict.keys(): + fileRoot = TorsoDict[key] + model = loader.loadModelNode("phase_3" + fileRoot + "1000") + Preloaded.append(model) + # don't load LOD's for the naked toon + if (len(key) > 1): + model = loader.loadModelNode("phase_3" + fileRoot + "500") + Preloaded.append(model) + model = loader.loadModelNode("phase_3" + fileRoot + "250") + Preloaded.append(model) + + #preload the head models + for key in HeadDict.keys(): + fileRoot = HeadDict[key] + model = loader.loadModelNode("phase_3" + fileRoot + "1000") + Preloaded.append(model) + model = loader.loadModelNode("phase_3" + fileRoot + "500") + Preloaded.append(model) + model = loader.loadModelNode("phase_3" + fileRoot + "250") + Preloaded.append(model) + +def loadBasicAnims(): + """ + Load the default Toon anims + """ + loadPhaseAnims() + +def unloadBasicAnims(): + """ + Unload the default Toon anims + """ + loadPhaseAnims(0) + +def loadTutorialBattleAnims(): + loadPhaseAnims("phase_3.5") + +def unloadTutorialBattleAnims(): + loadPhaseAnims("phase_3.5", 0) + +def loadMinigameAnims(): + """ + Load the minigame specific Toon anims + """ + loadPhaseAnims("phase_4") + +def unloadMinigameAnims(): + """ + Unload the minigame specific Toon anims + """ + loadPhaseAnims("phase_4", 0) + +def loadBattleAnims(): + """ + Load the battle specific Toon anims1 + """ + loadPhaseAnims("phase_5") + +def unloadBattleAnims(): + """ + Unload the battle specific Toon anims + """ + loadPhaseAnims("phase_5", 0) + +def loadSellbotHQAnims(): + """ + Load the sellbot hq specific Toon anims + """ + loadPhaseAnims("phase_9") + +def unloadSellbotHQAnims(): + """ + Unload the sellbot hq specific Toon anims + """ + loadPhaseAnims("phase_9", 0) + +def loadCashbotHQAnims(): + """ + Load the cashbot hq specific Toon anims + """ + loadPhaseAnims("phase_10") + +def unloadCashbotHQAnims(): + """ + Unload the cashbot hq specific Toon anims + """ + loadPhaseAnims("phase_10", 0) + +def loadBossbotHQAnims(): + """ + Load the bossbot hq specific Toon anims + """ + loadPhaseAnims("phase_12") + +def unloadBossbotHQAnims(): + """ + Unload the bossbot hq specific Toon anims + """ + loadPhaseAnims("phase_12", 0) + +def loadPhaseAnims(phaseStr="phase_3", loadFlag = 1): + """loadPhaseAnims(string, int): + If loadFlag = 1, load the anims for the given phase(default = 'phase_3'). + Otherwise unload them. This is kinda ugly but it keeps from having too + much redundant code. + """ + if (phaseStr == "phase_3"): + animList = Phase3AnimList + elif (phaseStr == "phase_3.5"): + animList = Phase3_5AnimList + elif (phaseStr == "phase_4"): + animList = Phase4AnimList + elif (phaseStr == "phase_5"): + animList = Phase5AnimList + elif (phaseStr == "phase_5.5"): + animList = Phase5_5AnimList + elif (phaseStr == "phase_6"): + animList = Phase6AnimList + elif (phaseStr == "phase_9"): + animList = Phase9AnimList + elif (phaseStr == "phase_10"): + animList = Phase10AnimList + elif (phaseStr == "phase_12"): + animList = Phase12AnimList + else: + # bad phase string + self.notify.error("Unknown phase string %s" % phaseStr) + + # For now, we are loading on bind and relying on garbage collection + # to tidy up the model pool. All we actually do here is delete the + # anim controls from the localToon's dictionary + for key in LegDict.keys(): + for anim in animList: + #file = phaseStr + LegDict[key] + anim[1] + if loadFlag: + #loader.loadModelNode(file) + pass + else: + #loader.unloadModel(file) + if LegsAnimDict[key].has_key(anim[0]): + if (base.localAvatar.style.legs == key): + base.localAvatar.unloadAnims([anim[0]], "legs", None) + + for key in TorsoDict.keys(): + for anim in animList: + #file = phaseStr + TorsoDict[key] + anim[1] + if loadFlag: + #loader.loadModelNode(file) + pass + else: + #loader.unloadModel(file) + if TorsoAnimDict[key].has_key(anim[0]): + if (base.localAvatar.style.torso == key): + base.localAvatar.unloadAnims([anim[0]], "torso", None) + + for key in HeadDict.keys(): + # only load anims for dog heads + if (string.find(key,"d") >= 0): + for anim in animList: + #file = phaseStr + HeadDict[key] + anim[1] + if loadFlag: + #loader.loadModelNode(file) + pass + else: + #loader.unloadModel(file) + if HeadAnimDict[key].has_key(anim[0]): + if (base.localAvatar.style.head == key): + base.localAvatar.unloadAnims([anim[0]], "head", None) + +def compileGlobalAnimList(): + """ + Munge the anim names and file paths into big dictionaries for the + leg, torso, and head parts. These are used for loading the anims. + """ + # Nowadays we want all anims in the list. It would save work + # in loadphaseAnims if we split this into phases. Optimize later. + phaseList = [Phase3AnimList,Phase3_5AnimList,Phase4AnimList,Phase5AnimList,Phase5_5AnimList,Phase6AnimList,Phase9AnimList,Phase10AnimList, Phase12AnimList] + phaseStrList = ["phase_3", "phase_3.5", "phase_4", "phase_5", "phase_5.5", "phase_6", "phase_9", "phase_10", "phase_12"] + + for animList in phaseList: + phaseStr = phaseStrList[phaseList.index(animList)] + for key in LegDict.keys(): + LegsAnimDict.setdefault(key, {}) + for anim in animList: + file = phaseStr + LegDict[key] + anim[1] + LegsAnimDict[key][anim[0]] = file + + for key in TorsoDict.keys(): + TorsoAnimDict.setdefault(key, {}) + for anim in animList: + file = phaseStr + TorsoDict[key] + anim[1] + TorsoAnimDict[key][anim[0]] = file + + for key in HeadDict.keys(): + # only load anims for dog heads + if (string.find(key,"d") >= 0): + HeadAnimDict.setdefault(key, {}) + for anim in animList: + file = phaseStr + HeadDict[key] + anim[1] + HeadAnimDict[key][anim[0]] = file + +def loadDialog(): + """ + Load the dialogue audio samples + """ + loadPath = "phase_3.5/audio/dial/" + + # load the dog dialogue + DogDialogueFiles = ( "AV_dog_short", + "AV_dog_med", + "AV_dog_long", + "AV_dog_question", + "AV_dog_exclaim", + "AV_dog_howl" + ) + # load the audio files and store into the dialogue array + for file in DogDialogueFiles: + DogDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the cat dialogue + catDialogueFiles = ( "AV_cat_short", + "AV_cat_med", + "AV_cat_long", + "AV_cat_question", + "AV_cat_exclaim", + "AV_cat_howl" + ) + # load the audio files and store into the dialogue array + for file in catDialogueFiles: + CatDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the horse dialogue + horseDialogueFiles = ( "AV_horse_short", + "AV_horse_med", + "AV_horse_long", + "AV_horse_question", + "AV_horse_exclaim", + "AV_horse_howl" + ) + + # load the audio files and store into the dialogue array + for file in horseDialogueFiles: + HorseDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the rabbit dialogue + rabbitDialogueFiles = ( "AV_rabbit_short", + "AV_rabbit_med", + "AV_rabbit_long", + "AV_rabbit_question", + "AV_rabbit_exclaim", + "AV_rabbit_howl" + ) + + # load the audio files and store into the dialogue array + for file in rabbitDialogueFiles: + RabbitDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the mouse dialogue + # for now the mouse reuses the rabbit sounds + mouseDialogueFiles = ( "AV_mouse_short", + "AV_mouse_med", + "AV_mouse_long", + "AV_mouse_question", + "AV_mouse_exclaim", + "AV_mouse_howl" + ) + + # load the audio files and store into the dialogue array + for file in mouseDialogueFiles: + MouseDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the duck dialogue array + duckDialogueFiles = ( "AV_duck_short", + "AV_duck_med", + "AV_duck_long", + "AV_duck_question", + "AV_duck_exclaim", + "AV_duck_howl" + ) + + # load the audio files and store into the dialogue array + for file in duckDialogueFiles: + DuckDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + # load the monkey dialogue array + monkeyDialogueFiles = ( "AV_monkey_short", + "AV_monkey_med", + "AV_monkey_long", + "AV_monkey_question", + "AV_monkey_exclaim", + "AV_monkey_howl" + ) + + # load the audio files and store into the dialogue array + for file in monkeyDialogueFiles: + MonkeyDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + + # load the bear dialogue array + bearDialogueFiles = ( "AV_bear_short", + "AV_bear_med", + "AV_bear_long", + "AV_bear_question", + "AV_bear_exclaim", + "AV_bear_howl" + ) + + # load the audio files and store into the dialogue array + for file in bearDialogueFiles: + BearDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + + + # load the pig dialogue array + pigDialogueFiles = ( "AV_pig_short", + "AV_pig_med", + "AV_pig_long", + "AV_pig_question", + "AV_pig_exclaim", + "AV_pig_howl" + ) + + # load the audio files and store into the dialogue array + for file in pigDialogueFiles: + PigDialogueArray.append(base.loadSfx(loadPath + file + ".mp3")) + +def unloadDialog(): + global DogDialogueArray + global CatDialogueArray + global HorseDialogueArray + global RabbitDialogueArray + global MouseDialogueArray + global DuckDialogueArray + global MonkeyDialogueArray + global BearDialogueArray + global PigDialogueArray + DogDialogueArray = [] + CatDialogueArray = [] + HorseDialogueArray = [] + RabbitDialogueArray = [] + MouseDialogueArray = [] + DuckDialogueArray = [] + MonkeyDialogueArray = [] + BearDialogueArray = [] + PigDialogueArray = [] + + +class Toon(Avatar.Avatar, ToonHead): + """Toon class:""" + + notify = DirectNotifyGlobal.directNotify.newCategory("Toon") + + afkTimeout = base.config.GetInt('afk-timeout', 600) + + # This is the tuple of allowed animations that can be set by using toon.setAnimState(). + # If you add an animation that you want to do a setAnimState on please add this + # animation to this list. + setAnimStateAllowedList = ( + 'off', + 'neutral', + 'victory', + 'Happy', + 'Sad', + 'Catching', + 'CatchEating', + 'Sleep', + 'walk', + 'jumpSquat', + 'jump', + 'jumpAirborne', + 'jumpLand', + 'run', + 'swim', + 'swimhold', + 'dive', + 'cringe', + 'OpenBook', + 'ReadBook', + 'CloseBook', + 'TeleportOut', + 'Died', + 'TeleportIn', + 'Emote', + 'SitStart', + 'Sit', + 'Push', + 'Squish', + 'FallDown', + 'GolfPuttLoop', + 'GolfRotateLeft', + 'GolfRotateRight', + 'GolfPuttSwing', + 'GolfGoodPutt', + 'GolfBadPutt', + 'Flattened', + 'CogThiefRunning', + 'ScientistJealous', + 'ScientistEmcee', + 'ScientistWork', + 'ScientistLessWork', + 'ScientistPlay' + ) + + def __init__(self): + try: + self.Toon_initialized + return + except: + self.Toon_initialized = 1 + Avatar.Avatar.__init__(self) + ToonHead.__init__(self) + + self.forwardSpeed = 0.0 + self.rotateSpeed = 0.0 + + # Set Avatar Type (Toon, Teen, or Pirate) + self.avatarType = "toon" + + # These members are only used to track the current actual + # animation and play rate in effect when motion.standWalkRunReverse + # is not None. + self.motion = Motion.Motion(self) + self.standWalkRunReverse = None + self.playingAnim = None + self.soundTeleport = None + + self.cheesyEffect = ToontownGlobals.CENormal + self.effectTrack = None + self.emoteTrack = None + self.emote = None + self.stunTrack = None + + self.__bookActors = [] + self.__holeActors = [] + self.holeClipPath = None + + self.wake = None + self.lastWakeTime = 0 + + self.numPies = 0 + self.pieType = 0 + self.pieModel = None + self.__pieModelType = None + + # Stunned if recently hit by stomper + self.isStunned = 0 + + # are we disguised as a suit? + self.isDisguised = 0 + + self.defaultColorScale = None + + self.jar = None + + self.setTag('pieCode', str(ToontownGlobals.PieCodeToon)) + + # Define Toon's Font + # fancy nametag point 1 + self.setFont(ToontownGlobals.getToonFont()) + + # chat balloon sound + self.soundChatBubble = base.loadSfx("phase_3/audio/sfx/GUI_balloon_popup.mp3") + + # The animFSM doesn't really have any restrictions on + # transitions between states--we don't care which anim + # state might follow from the current one; we only want to + # ensure everything gets cleaned up properly. + self.animFSM = ClassicFSM( + 'Toon', + [State('off', self.enterOff, self.exitOff), + State('neutral', self.enterNeutral, self.exitNeutral), + State('victory', self.enterVictory, self.exitVictory), + State('Happy', self.enterHappy, self.exitHappy), + State('Sad', self.enterSad, self.exitSad), + State('Catching', self.enterCatching, self.exitCatching), + State('CatchEating', self.enterCatchEating, self.exitCatchEating), + State('Sleep', self.enterSleep, self.exitSleep), + State('walk', self.enterWalk, self.exitWalk), + State('jumpSquat', self.enterJumpSquat, self.exitJumpSquat), + State('jump', self.enterJump, self.exitJump), + State('jumpAirborne', self.enterJumpAirborne, self.exitJumpAirborne), + State('jumpLand', self.enterJumpLand, self.exitJumpLand), + State('run', self.enterRun, self.exitRun), + State('swim', self.enterSwim, self.exitSwim), + State('swimhold', self.enterSwimHold, self.exitSwimHold), + State('dive', self.enterDive, self.exitDive), + State('cringe', self.enterCringe, self.exitCringe), + State('OpenBook', self.enterOpenBook, self.exitOpenBook, ['ReadBook','CloseBook']), + State('ReadBook', self.enterReadBook, self.exitReadBook), + State('CloseBook', self.enterCloseBook, self.exitCloseBook), + State('TeleportOut', self.enterTeleportOut, self.exitTeleportOut), + State('Died', self.enterDied, self.exitDied), + State('TeleportedOut', self.enterTeleportedOut, self.exitTeleportedOut), + State('TeleportIn', self.enterTeleportIn, self.exitTeleportIn), + State('Emote', self.enterEmote, self.exitEmote), + State('SitStart', self.enterSitStart, self.exitSitStart), + State('Sit', self.enterSit, self.exitSit), + State('Push', self.enterPush, self.exitPush), + State('Squish', self.enterSquish, self.exitSquish), + State('FallDown', self.enterFallDown, self.exitFallDown), + State('GolfPuttLoop', self.enterGolfPuttLoop, self.exitGolfPuttLoop), + State('GolfRotateLeft', self.enterGolfRotateLeft, self.exitGolfRotateLeft), + State('GolfRotateRight', self.enterGolfRotateRight, self.exitGolfRotateRight), + State('GolfPuttSwing', self.enterGolfPuttSwing, self.exitGolfPuttSwing), + State('GolfGoodPutt', self.enterGolfGoodPutt, self.exitGolfGoodPutt), + State('GolfBadPutt', self.enterGolfBadPutt, self.exitGolfBadPutt), + State('Flattened', self.enterFlattened, self.exitFlattened), + State('CogThiefRunning', self.enterCogThiefRunning, self.exitCogThiefRunning), + State('ScientistJealous', self.enterScientistJealous, self.exitScientistJealous), + State('ScientistEmcee', self.enterScientistEmcee, self.exitScientistEmcee), + State('ScientistWork', self.enterScientistWork, self.exitScientistWork), + State('ScientistLessWork', self.enterScientistLessWork, self.exitScientistLessWork), + State('ScientistPlay', self.enterScientistPlay, self.enterScientistPlay), + ], + # Initial State + 'off', + # Final State + 'off', + ) + self.animFSM.enterInitialState() + # Note: When you add an animation to this animFSM list also add it to + # setAnimStateAllowedList if you want to use setAnimState to change to that animation. + + def stopAnimations(self): + assert self.notify.debugStateCall(self, "animFsm") + if not self.animFSM.isInternalStateInFlux(): + self.animFSM.request('off') + else: + self.notify.warning('animFSM in flux, state=%s, not requesting off' % + self.animFSM.getCurrentState().getName()) + if self.effectTrack != None: + self.effectTrack.finish() + self.effectTrack = None + if self.emoteTrack != None: + self.emoteTrack.finish() + self.emoteTrack = None + if self.stunTrack != None: + self.stunTrack.finish() + self.stunTrack = None + if self.wake: + self.wake.stop() + self.wake.destroy() + self.wake = None + self.cleanupPieModel() + + def delete(self): + assert self.notify.debugStateCall(self, "animFsm") + try: + self.Toon_deleted + except: + self.Toon_deleted = 1 + self.stopAnimations() + self.rightHands = None + self.rightHand = None + self.leftHands = None + self.leftHand = None + self.headParts = None + self.torsoParts = None + self.hipsParts = None + self.legsParts = None + del self.animFSM + for bookActor in self.__bookActors: + bookActor.cleanup() + del self.__bookActors + for holeActor in self.__holeActors: + holeActor.cleanup() + del self.__holeActors + self.soundTeleport = None + self.motion.delete() + self.motion = None + Avatar.Avatar.delete(self) + ToonHead.delete(self) + + # toon methods + + def updateToonDNA(self, newDNA, fForce = 0): + """ + update the toon's appearance based on new DNA + """ + assert self.notify.debugStateCall(self, "animFsm") + # Make sure gender is updated (for RobotToons) + self.style.gender = newDNA.getGender() + # test and only update the new parts + oldDNA = self.style + if fForce or (newDNA.head != oldDNA.head): + self.swapToonHead(newDNA.head) + if fForce or (newDNA.torso != oldDNA.torso): + self.swapToonTorso(newDNA.torso, genClothes = 0) + self.loop('neutral') + if fForce or (newDNA.legs != oldDNA.legs): + self.swapToonLegs(newDNA.legs) + # easier just to do these color!'s than to check + self.swapToonColor(newDNA) + self.__swapToonClothes(newDNA) + + def setDNAString(self, dnaString): + assert self.notify.debugStateCall(self, "animFsm") + newDNA = ToonDNA.ToonDNA() + newDNA.makeFromNetString(dnaString) + self.setDNA(newDNA) + + def setDNA(self, dna): + assert self.notify.debugStateCall(self, "animFsm") + # if we are disguised, don't mess up our custom geom + if hasattr(self, "isDisguised"): + if self.isDisguised: + return + + if self.style: + self.updateToonDNA(dna) + else: + # store the DNA + self.style = dna + + self.generateToon() + + # this no longer works in the Avatar init! + # I moved it here for lack of a better place + # make the drop shadow + self.initializeDropShadow() + self.initializeNametag3d() + + + + def parentToonParts(self): + """ + attach the toon's parts - recurse over all LODs + """ + #import pdb; pdb.set_trace() + assert self.notify.debugStateCall(self, "animFsm") + # import pdb; pdb.set_trace() + # attach all the various toon pieces + if (self.hasLOD()): + for lodName in self.getLODNames(): + if base.config.GetBool('want-new-anims', 1): + if not self.getPart("torso", lodName).find('**/def_head').isEmpty(): + self.attach("head", "torso", "def_head", lodName) + else: + self.attach("head", "torso", "joint_head", lodName) + else: + self.attach("head", "torso", "joint_head", lodName) + self.attach("torso", "legs", "joint_hips", lodName) + else: + self.attach("head", "torso", "joint_head") + self.attach("torso", "legs", "joint_hips") + + + def unparentToonParts(self): + """ + attach all parts to the geomNode - recurse over all LODs + """ + assert self.notify.debugStateCall(self, "animFsm") + # unattach the various toon pieces + if (self.hasLOD()): + for lodName in self.getLODNames(): + self.getPart("head", lodName).reparentTo(self.getLOD(lodName)) + self.getPart("torso", lodName).reparentTo(self.getLOD(lodName)) + self.getPart("legs", lodName).reparentTo(self.getLOD(lodName)) + else: + self.getPart("head").reparentTo(self.getGeomNode()) + self.getPart("torso").reparentTo(self.getGeomNode()) + self.getPart("legs").reparentTo(self.getGeomNode()) + + def setLODs(self): + """ + Get LOD switch distances from dconfig, or use defaults + """ + assert self.notify.debugStateCall(self, "animFsm") + # set up the LOD node for avatar LOD + self.setLODNode() + + # get the switch values + levelOneIn = base.config.GetInt("lod1-in", 20) + levelOneOut = base.config.GetInt("lod1-out", 0) + levelTwoIn = base.config.GetInt("lod2-in", 80) + levelTwoOut = base.config.GetInt("lod2-out", 20) + levelThreeIn = base.config.GetInt("lod3-in", 280) + levelThreeOut = base.config.GetInt("lod3-out", 80) + + # add the LODs + self.addLOD(1000, levelOneIn, levelOneOut) + self.addLOD(500, levelTwoIn, levelTwoOut) + self.addLOD(250, levelThreeIn, levelThreeOut) + + + def generateToon(self): + """ + Create a toon from dna (an array of strings) + NOTE: DistributedNPCToon overrides this because they do not need it all + """ + assert self.notify.debugStateCall(self, "animFsm") + # set up LOD info + self.setLODs() + # load the toon legs + self.generateToonLegs() + # load the toon head + self.generateToonHead() + # load the toon torso + self.generateToonTorso() + # color the toon as specified by the dna + self.generateToonColor() + self.parentToonParts() + # make small toons with big heads + self.rescaleToon() + self.resetHeight() + self.setupToonNodes() + + def setupToonNodes(self): + assert self.notify.debugStateCall(self, "animFsm") + # Initialize arrays of pointers to useful nodes for the toon + rightHand = NodePath('rightHand') + self.rightHand = None + self.rightHands = [] + leftHand = NodePath('leftHand') + self.leftHands = [] + self.leftHand = None + for lodName in self.getLODNames(): + hand = self.getPart('torso', lodName).find('**/joint_Rhold') + if base.config.GetBool('want-new-anims', 1): + if not self.getPart('torso', lodName).find('**/def_joint_right_hold').isEmpty(): + hand = self.getPart('torso', lodName).find('**/def_joint_right_hold') + else: + hand = self.getPart('torso', lodName).find('**/joint_Rhold') + self.rightHands.append(hand) + #import pdb; pdb.set_trace() + rightHand = rightHand.instanceTo(hand) + if base.config.GetBool('want-new-anims', 1): + if not self.getPart('torso', lodName).find('**/def_joint_left_hold').isEmpty(): + hand = self.getPart('torso', lodName).find('**/def_joint_left_hold') + else: + hand = self.getPart('torso', lodName).find('**/joint_Lhold') + self.leftHands.append(hand) + leftHand = leftHand.instanceTo(hand) + # It's important that self.rightHand and self.leftHand + # reflect the first instance in the list (for the highest + # level LOD), for historical reasons. + if self.rightHand == None: + self.rightHand = rightHand + if self.leftHand == None: + self.leftHand = leftHand + self.headParts = self.findAllMatches('**/__Actor_head') + self.legsParts = self.findAllMatches('**/__Actor_legs') + # Hips are under the legs + self.hipsParts = self.legsParts.findAllMatches('**/joint_hips') + # Torso is under the hips + self.torsoParts = self.hipsParts.findAllMatches('**/__Actor_torso') + + def initializeBodyCollisions(self, collIdStr): + Avatar.Avatar.initializeBodyCollisions(self, collIdStr) + + if not self.ghostMode: + self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask) + + def getBookActors(self): + """ + Return the book actors. If they have not been created, + we need to set them up the first time + """ + # See if we have already created them + # If we have, just return them + if self.__bookActors: + return self.__bookActors + # Otherwise we need to load them + bookActor = Actor.Actor('phase_3.5/models/props/book-mod', + {'book': 'phase_3.5/models/props/book-chan'}) + bookActor2 = Actor.Actor(other=bookActor) + bookActor3 = Actor.Actor(other=bookActor) + self.__bookActors = [bookActor, bookActor2, bookActor3] + hands = self.getRightHands() + for bookActor, hand in zip(self.__bookActors, hands): + bookActor.reparentTo(hand) + bookActor.hide() + return self.__bookActors + + def getHoleActors(self): + """ + Return the teleport hole actors. If they have not been created, + we need to set them up the first time + """ + # See if we have already created them + # If we have, just return them + if self.__holeActors: + return self.__holeActors + # Otherwise we need to load them + holeActor = Actor.Actor('phase_3.5/models/props/portal-mod', + {'hole': 'phase_3.5/models/props/portal-chan'}) + holeActor2 = Actor.Actor(other=holeActor) + holeActor3 = Actor.Actor(other=holeActor) + self.__holeActors = [holeActor, holeActor2, holeActor3] + for ha in self.__holeActors: + if hasattr(self, "uniqueName"): + holeName = self.uniqueName("toon-portal") + else: + holeName = "toon-portal" + ha.setName(holeName) + return self.__holeActors + + def rescaleToon(self): + """ + Rescale all the toons. The values are typed in + ToontownGlobals dictionaries and get set here. + """ + assert self.notify.debugStateCall(self, "animFsm") + animalStyle = self.style.getAnimal() + bodyScale = ToontownGlobals.toonBodyScales[animalStyle] + headScale = ToontownGlobals.toonHeadScales[animalStyle] + self.setAvatarScale(bodyScale) + for lod in self.getLODNames(): + self.getPart('head', lod).setScale(headScale) + + def getBodyScale(self): + animalStyle = self.style.getAnimal() + bodyScale = ToontownGlobals.toonBodyScales[animalStyle] + return bodyScale + + + def resetHeight(self): + """ + Reset the height based on the current style, including scales + """ + assert self.notify.debugStateCall(self, "animFsm") + if hasattr(self,'style') and self.style: + animal = self.style.getAnimal() + bodyScale = ToontownGlobals.toonBodyScales[animal] + headScale = ToontownGlobals.toonHeadScales[animal][2] + shoulderHeight = ( + (ToontownGlobals.legHeightDict[self.style.legs] * bodyScale) + + (ToontownGlobals.torsoHeightDict[self.style.torso] * bodyScale) + ) + height = ( + shoulderHeight + + (ToontownGlobals.headHeightDict[self.style.head] * headScale) + ) + self.shoulderHeight = shoulderHeight + if self.cheesyEffect == ToontownGlobals.CEBigToon or self.cheesyEffect == ToontownGlobals.CEBigWhite: + height *= ToontownGlobals.BigToonScale + elif self.cheesyEffect == ToontownGlobals.CESmallToon: + height *= ToontownGlobals.SmallToonScale + self.setHeight(height) + + def generateToonLegs(self, copy = 1): + """generateToonLegs(self, bool = 0) + Load the leg models for the toon. + If copy = 0, don't copy new geometry, instance it. + """ + legStyle = self.style.legs + filePrefix = LegDict.get(legStyle) + if filePrefix is None: + self.notify.error("unknown leg style: %s" % legStyle) + # load the models + self.loadModel("phase_3" + filePrefix + "1000", "legs", "1000", copy) + self.loadModel("phase_3" + filePrefix + "500", "legs", "500", copy) + self.loadModel("phase_3" + filePrefix + "250", "legs", "250", copy) + if not copy: + self.showPart("legs", "1000") + self.showPart("legs", "500") + self.showPart("legs", "250") + # load the anims for this type of leg + self.loadAnims(LegsAnimDict[legStyle], "legs", "1000") + self.loadAnims(LegsAnimDict[legStyle], "legs", "500") + self.loadAnims(LegsAnimDict[legStyle], "legs", "250") + + def swapToonLegs(self, legStyle, copy = 1): + """swapToonLegs(self, string, bool = 1) + Switch out the current toon models for the given legStyle. + See ToonDNA for leg type list. + """ + # unparent all the toon parts + self.unparentToonParts() + # Delete the old legs. It is ok to "remove" them, even if we + # are working with instances instead of with copies, because + # of the way instances work. + self.removePart("legs", "1000") + self.removePart("legs", "500") + self.removePart("legs", "250") + # make the new legs part of the dna + self.style.legs = legStyle + # load the new legs + self.generateToonLegs(copy) + # color the new legs + self.generateToonColor() + # put everything back together + self.parentToonParts() + self.rescaleToon() + self.resetHeight() + # re-make the drop shadows + del self.shadowJoint # This is a bit of a hack for Make a Toon. + self.initializeDropShadow() + self.initializeNametag3d() + + def generateToonTorso(self, copy = 1, genClothes = 1): + """generateToonTorso(string, bool = 1) + Load the torso model for the toon. + If copy = 0, instance geom instead of copying + """ + torsoStyle = self.style.torso + filePrefix = TorsoDict.get(torsoStyle) + if filePrefix is None: + self.notify.error("unknown torso style: %s" % torsoStyle) + # load the models + self.loadModel("phase_3" + filePrefix + "1000", "torso", "1000", copy) + # this is a hack to support the naked toons + if (len(torsoStyle) == 1): + self.loadModel("phase_3" + filePrefix + "1000", "torso", "500", copy) + self.loadModel("phase_3" + filePrefix + "1000", "torso", "250", copy) + else: + self.loadModel("phase_3" + filePrefix + "500", "torso", "500", copy) + self.loadModel("phase_3" + filePrefix + "250", "torso", "250", copy) + if not copy: + self.showPart('torso', '1000') + self.showPart('torso', '500') + self.showPart('torso', '250') + # load the anims for this type of torso + self.loadAnims(TorsoAnimDict[torsoStyle], "torso", "1000") + self.loadAnims(TorsoAnimDict[torsoStyle], "torso", "500") + self.loadAnims(TorsoAnimDict[torsoStyle], "torso", "250") + # if not a naked toon, set the clothing textures + if (genClothes == 1 and not (len(torsoStyle) == 1)): + self.generateToonClothes() + + def swapToonTorso(self, torsoStyle, copy = 1, genClothes = 1): + """swapToonTorso(self, string, bool = 1) + Switch the current toon torso model for the specified one. + See ToonDNA for torso style list + """ + assert self.notify.debug("swapToonTorso() - torso: %s genClothes: %d" % (torsoStyle, genClothes)) + # unparent all the parts + self.unparentToonParts() + # delete the old torso model + self.removePart('torso', '1000') + self.removePart('torso', '500') + self.removePart('torso', '250') + # add the new torso to the dna + self.style.torso = torsoStyle + # load the new torso model + self.generateToonTorso(copy, genClothes) + # color the new torso + self.generateToonColor() + # put everything back together + self.parentToonParts() + self.rescaleToon() + self.resetHeight() + self.setupToonNodes() + + def generateToonHead(self, copy = 1): + """generateToonHead(self, bool = 1) + Load the head model and textures for the toon. + If copy = 0, instance geom instead of copying. + """ + headHeight = ToonHead.generateToonHead( + self, copy, self.style, ('1000', '500', '250')) + # load the anims for the dog head only + if self.style.getAnimal() == 'dog': + self.loadAnims(HeadAnimDict[self.style.head], "head", "1000") + self.loadAnims(HeadAnimDict[self.style.head], "head", "500") + self.loadAnims(HeadAnimDict[self.style.head], "head", "250") + + + + + def swapToonHead(self, headStyle, copy = 1): + """swapToonHead(self, string) + Switch the current head model for the specified one. + See ToonDNA for head style list + """ + self.stopLookAroundNow() + # First, make sure our eyes are open. + self.eyelids.request('open') + # unparent all parts + self.unparentToonParts() + # delete the old head + self.removePart('head', '1000') + self.removePart('head', '500') + self.removePart('head', '250') + # add the new head to the dna + self.style.head = headStyle + # load the new head + self.generateToonHead(copy) + # color the new head + self.generateToonColor() + # put it all back together + self.parentToonParts() + # If the head changes, we need to rescale + self.rescaleToon() + self.resetHeight() + self.eyelids.request("open") + self.startLookAround() + + def generateToonColor(self): + """ + Color the toon's parts as specified by the dna. + Color any LODs by searching for ALL matches. + """ + ToonHead.generateToonColor(self, self.style) + + armColor = self.style.getArmColor() + gloveColor = self.style.getGloveColor() + legColor = self.style.getLegColor() + + for lodName in self.getLODNames(): + torso = self.getPart('torso', lodName) + # if the toon has no clothes make sure the torso parts colored + if (len(self.style.torso) == 1): + # There are multiple torso pieces, so find all matches + parts = torso.findAllMatches("**/torso*") + parts.setColor(armColor) + # Color the arms and neck + for pieceName in ('arms', 'neck'): + piece = torso.find('**/' + pieceName) + piece.setColor(armColor) + # Color the gloves + hands = torso.find('**/hands') + hands.setColor(gloveColor) + # Color the lower body + # import pdb; pdb.set_trace() + legs = self.getPart('legs', lodName) + for pieceName in ('legs', 'feet'): + piece = legs.find('**/' + pieceName) + piece.setColor(legColor) + + # no shoes yet, forget this bit + # color the front of the feet - may have multiple pieces + #parts = self.findAllMatches("**/toBall*") + #parts.setColor(dna.getLegColor()) + + def swapToonColor(self, dna): + """ + Update the avatar's dna and adjust body colors accordingly + """ + self.setStyle(dna) + self.generateToonColor() + + def __swapToonClothes(self, dna): + self.setStyle(dna) + self.generateToonClothes(fromNet = 1) + + def generateToonClothes(self, fromNet = 0): + """ + Set the textures and colors described in the dna for the clothes + """ + # for each lod, get the model, find the parts and bang + # the appropriate texture + swappedTorso = 0 + if (self.hasLOD()): + if (self.style.getGender() == 'f' and fromNet == 0): + # See if we need to switch torso to change from skirts to + # shorts or vice versa + # Only do this once + try: + bottomPair = ToonDNA.GirlBottoms[self.style.botTex] + except: + bottomPair = ToonDNA.GirlBottoms[0] + if (self.style.torso[1] == 's' and + bottomPair[1] == ToonDNA.SKIRT): + assert self.notify.debug("genToonClothes() - swapping torso from 's' to 'd', tex: %s" % bottomPair[0]) + self.swapToonTorso(self.style.torso[0] + 'd', + genClothes = 0) + swappedTorso = 1 + elif (self.style.torso[1] == 'd' and + bottomPair[1] == ToonDNA.SHORTS): + assert self.notify.debug("genToonClothes() - swapping torso from 'd' to 's', tex: %s" % bottomPair[0]) + self.swapToonTorso(self.style.torso[0] + 's', + genClothes = 0) + swappedTorso = 1 + + # Setup all the textures and colors for all the LODS ahead of time + # get the top texture and color + try: + texName = ToonDNA.Shirts[self.style.topTex] + except: + texName = ToonDNA.Shirts[0] + shirtTex = loader.loadTexture(texName) + shirtTex.setMinfilter(Texture.FTLinearMipmapLinear) + shirtTex.setMagfilter(Texture.FTLinear) + try: + shirtColor = ToonDNA.ClothesColors[self.style.topTexColor] + except: + shirtColor = ToonDNA.ClothesColors[0] + # set the sleeve texture and color + try: + texName = ToonDNA.Sleeves[self.style.sleeveTex] + except: + texName = ToonDNA.Sleeves[0] + sleeveTex = loader.loadTexture(texName) + sleeveTex.setMinfilter(Texture.FTLinearMipmapLinear) + sleeveTex.setMagfilter(Texture.FTLinear) + try: + sleeveColor = ToonDNA.ClothesColors[self.style.sleeveTexColor] + except: + sleeveColor = ToonDNA.ClothesColors[0] + # set the bottom texture and color + if (self.style.getGender() == 'm'): + try: + texName = ToonDNA.BoyShorts[self.style.botTex] + except: + texName = ToonDNA.BoyShorts[0] + else: + try: + texName = ToonDNA.GirlBottoms[self.style.botTex][0] + except: + texName = ToonDNA.GirlBottoms[0][0] + bottomTex = loader.loadTexture(texName) + bottomTex.setMinfilter(Texture.FTLinearMipmapLinear) + bottomTex.setMagfilter(Texture.FTLinear) + try: + bottomColor = ToonDNA.ClothesColors[self.style.botTexColor] + except: + bottomColor = ToonDNA.ClothesColors[0] + # Make the color darker + darkBottomColor = bottomColor * 0.5 + # Fix the alpha back to 1.0 + darkBottomColor.setW(1.0) + + # import pdb; pdb.set_trace() + # Now apply all the colors and textures + for lodName in self.getLODNames(): + thisPart = self.getPart("torso", lodName) + top = thisPart.find("**/torso-top") + top.setTexture(shirtTex, 1) + top.setColor(shirtColor) + sleeves = thisPart.find("**/sleeves") + sleeves.setTexture(sleeveTex, 1) + sleeves.setColor(sleeveColor) + bottoms = thisPart.findAllMatches("**/torso-bot") + for bottomNum in range(0, bottoms.getNumPaths()): + bottom = bottoms.getPath(bottomNum) + bottom.setTexture(bottomTex, 1) + bottom.setColor(bottomColor) + caps = thisPart.findAllMatches("**/torso-bot-cap") + caps.setColor(darkBottomColor) + return swappedTorso + + + # dialog methods + def getDialogueArray(self): + # determine what kind of animal we are + animalType = self.style.getType() + if (animalType == "dog"): + dialogueArray = DogDialogueArray + elif (animalType == "cat"): + dialogueArray = CatDialogueArray + elif (animalType == "horse"): + dialogueArray = HorseDialogueArray + elif (animalType == "mouse"): + dialogueArray = MouseDialogueArray + elif (animalType == "rabbit"): + dialogueArray = RabbitDialogueArray + elif (animalType == "duck"): + dialogueArray = DuckDialogueArray + elif (animalType == "monkey"): + dialogueArray = MonkeyDialogueArray + elif (animalType == "bear"): + dialogueArray = BearDialogueArray + elif (animalType == "pig"): + dialogueArray = PigDialogueArray + else: + dialogueArray = None + return dialogueArray + + def getShadowJoint(self): + """ + Return the shadow joint + """ + if hasattr(self, "shadowJoint"): + return self.shadowJoint + shadowJoint = NodePath("shadowJoint") + for lodName in self.getLODNames(): + joint = self.getPart('legs', lodName).find('**/joint_shadow') + shadowJoint = shadowJoint.instanceTo(joint) + self.shadowJoint = shadowJoint + return shadowJoint + + def getNametagJoints(self): + """ + Return a list of CharacterJoints for each LOD (1000, 500, 250) + """ + joints = [] + for lodName in self.getLODNames(): + bundle = self.getPartBundle('legs', lodName) + joint = bundle.findChild('joint_nameTag') + if joint: + joints.append(joint) + return joints + + def getRightHands(self): + """ + Return a list of right hands for each LOD (1000, 500, 250) + """ + return self.rightHands + + def getLeftHands(self): + """ + Return a list of left hands for each LOD (1000, 500, 250) + """ + return self.leftHands + + def getHeadParts(self): + return self.headParts + + def getHipsParts(self): + return self.hipsParts + + def getTorsoParts(self): + return self.torsoParts + + def getLegsParts(self): + return self.legsParts + + + def findSomethingToLookAt(self): + """ + Overrides the function in ToonHead to find a point in the + world to look at, if possible. + """ + # Sometimes we just choose randomly + if ((self.randGen.random() < 0.1) or + (not hasattr(self, "cr"))): + x = self.randGen.choice((-0.8, -0.5, 0, 0.5, 0.8)) + y = self.randGen.choice((-0.5, 0, 0.5, 0.8)) + # Now look that direction! + self.lerpLookAt(Point3(x, 1.5, y), blink=1) + return + + # Crawl through the doId2do to see if there are objects + # which support the "getStareAtNodeAndOffset" interface + # Build up a list of nodes around us and pick one to look at + nodePathList = [] + for id, obj in self.cr.doId2do.items(): + if (hasattr(obj, "getStareAtNodeAndOffset") and + (obj!=self)): + node, offset = obj.getStareAtNodeAndOffset() + # Only include things in front of us + if node.getY(self) > 0.0: + nodePathList.append((node, offset)) + # If we found some, sort them to see what is closest + if nodePathList: + # Sort based on distance, closest first + nodePathList.sort(lambda x,y: cmp(x[0].getDistance(self), y[0].getDistance(self))) + # If there are more then two, choose one of the closest 2 + if len(nodePathList) >= 2: + if (self.randGen.random() < 0.9): + # ususally choose the closest + chosenNodePath = nodePathList[0] + else: + # sometimes choose the next closest + chosenNodePath = nodePathList[1] + else: + chosenNodePath = nodePathList[0] + # Do we want to look or stare? Stare is expensive! + # TODO: optimize this + # self.startStareAt(chosenNodePath[0], chosenNodePath[1]) + # Now look that direction! + self.lerpLookAt(chosenNodePath[0].getPos(self), blink=1) + else: + # Didnt find any? Just look randomly + ToonHead.findSomethingToLookAt(self) + + + def setupPickTrigger(self): + """ + Overrides the similar function from Avatar to position the + trigger polygon in the appropriate place for a Toon. + """ + Avatar.Avatar.setupPickTrigger(self) + torso = self.getPart('torso', '1000') + if torso == None: + return 0 + + self.pickTriggerNp.reparentTo(torso) + size = self.style.getTorsoSize() + if size == 'short': + self.pickTriggerNp.setPosHprScale(0, 0, 0.5, 0, 0, 0, 1.5, 1.5, 2) + elif size == 'medium': + self.pickTriggerNp.setPosHprScale(0, 0, 0.5, 0, 0, 0, 1, 1, 2) + else: # long + self.pickTriggerNp.setPosHprScale(0, 0, 1, 0, 0, 0, 1, 1, 2) + + return 1 + + def showBooks(self): + for bookActor in self.getBookActors(): + bookActor.show() + + def hideBooks(self): + for bookActor in self.getBookActors(): + bookActor.hide() + + def getWake(self): + """ + Returns the wake object associated with this Toon. Creates it + if it has not yet been created. + """ + if (not self.wake): + self.wake = Wake.Wake(render, self) + return self.wake + + def getJar(self): + """ + Returns the jar object associated with this toon. Creates it + if it has not yet been created. + """ + if not self.jar: + self.jar = loader.loadModel("phase_5.5/models/estate/jellybeanJar") + # make it line up with the hands a little better + self.jar.setP(290.0) + self.jar.setY(0.5) + self.jar.setZ(0.5) + # scale so it's invisible + self.jar.setScale(0.0) + return self.jar + + def removeJar(self): + """ + If it exists, remove the jar object associated with this toon. + """ + if self.jar: + self.jar.removeNode() + self.jar = None + + + def setSpeed(self, forwardSpeed, rotateSpeed): + """setSpeed(self, float forwardSpeed, float rotateSpeed) + + Sets the indicated forward velocity and rotational velocities + of the toon. This is used when in the Happy and Sad states to + determine which animation to play. + + The return value is one of RUN_INDEX, WALK_INDEX, etc., or + None if the animation does not specialize for the various + actions. + """ + self.forwardSpeed = forwardSpeed + self.rotateSpeed = rotateSpeed + + action = None + + if self.standWalkRunReverse != None: + # If we have a list of anims to play for stand, walk, and + # run, then choose the appropriate one and play it. + if (forwardSpeed >= ToontownGlobals.RunCutOff): + # Running + action = OTPGlobals.RUN_INDEX + elif (forwardSpeed > ToontownGlobals.WalkCutOff): + # Walking + action = OTPGlobals.WALK_INDEX + elif (forwardSpeed < -ToontownGlobals.WalkCutOff): + # Walking backwards + action = OTPGlobals.REVERSE_INDEX + elif (rotateSpeed != 0.0): + # Spin in place + action = OTPGlobals.WALK_INDEX + else: + # Stand still + action = OTPGlobals.STAND_INDEX + + anim, rate = self.standWalkRunReverse[action] + # change the motion state before we proceed + self.motion.enter() + self.motion.setState(anim, rate) + + if (anim != self.playingAnim): + self.playingAnim = anim + self.playingRate = rate + self.stop() + self.loop(anim) + self.setPlayRate(rate, anim) + # if we are in disguise, play the anim on the suit body too + if self.isDisguised: + rightHand = self.suit.rightHand + numChildren = rightHand.getNumChildren() + if numChildren > 0: + anim = 'tray-' + anim + if anim == 'tray-run': + anim = 'tray-walk' + self.suit.stop() + self.suit.loop(anim) + self.suit.setPlayRate(rate, anim) + elif (rate != self.playingRate): + self.playingRate = rate + if not self.isDisguised: + self.setPlayRate(rate, anim) + else: + self.suit.setPlayRate(rate, anim) + + # Show wake if moving through water + # This is determined by your height + showWake, wakeWaterHeight = ZoneUtil.getWakeInfo() + # Are we walking below water level? + # Use the showWake flag to short circuit so we do not need to + # needlessly check Z against render when we do not want wake + if (showWake and + (self.getZ(render) < wakeWaterHeight) and + (abs(forwardSpeed) > ToontownGlobals.WalkCutOff)): + currT = globalClock.getFrameTime() + deltaT = currT - self.lastWakeTime + # Create new ripple if it's been long enough + # since the last one + if (((action == OTPGlobals.RUN_INDEX) and + (deltaT > ToontownGlobals.WakeRunDelta)) or + (deltaT > ToontownGlobals.WakeWalkDelta)): + self.getWake().createRipple( + wakeWaterHeight, rate = 1, startFrame = 4) + self.lastWakeTime = currT + + return action + + def enterOff(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.setActiveShadow(0) + self.playingAnim = None + + def exitOff(self): + pass + + def enterNeutral(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # make the toon start breathing at a random frame + anim = "neutral" + self.pose(anim, int(self.getNumFrames(anim) * self.randGen.random())) + self.loop(anim, restart=0) + self.setPlayRate(animMultiplier, anim) + self.playingAnim = anim + self.setActiveShadow(0) + + def exitNeutral(self): + self.stop() + + def enterVictory(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # Do the victory dance. It "started" ts seconds ago; + # therefore, begin at the appropriate frame. + anim = "victory" + frame = int(ts * self.getFrameRate(anim) * animMultiplier) + self.pose(anim, frame) + self.loop("victory", restart=0) + self.setPlayRate(animMultiplier, "victory") + self.playingAnim = anim + self.setActiveShadow(0) + + def exitVictory(self): + self.stop() + + def enterHappy(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # In this state, the Toon automatically switches between the + # neutral, walk, and run animations, as appropriate. This is + # the normal state for walking around in Toontown. + self.playingAnim = None + self.playingRate = None + self.standWalkRunReverse = ( + ("neutral", 1.0), + ("walk", 1.0), + ("run", 1.0), + ("walk", -1.0) + ) + self.setSpeed(self.forwardSpeed, self.rotateSpeed) + self.setActiveShadow(1) + + def exitHappy(self): + self.standWalkRunReverse = None + self.stop() + self.motion.exit() + + def enterSad(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # In this state, the Toon automatically switches between the + # sad-neutral and sad-walk animations, as appropriate. + self.playingAnim = 'sad' + self.playingRate = None + self.standWalkRunReverse = ( + ("sad-neutral", 1.0), + ("sad-walk", 1.2), + ("sad-walk", 1.2), + ("sad-walk", -1.0) + ) + self.setSpeed(0, 0) + + # disable body emotes + Emote.globalEmote.disableBody(self, "toon, enterSad") + self.setActiveShadow(1) + + def exitSad(self): + self.standWalkRunReverse = None + self.stop() + self.motion.exit() + Emote.globalEmote.releaseBody(self, "toon, exitSad") + + def enterCatching(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + # In this state, the Toon stretches his arms out in a catching + # pose. + self.playingAnim = None + self.playingRate = None + self.standWalkRunReverse = ( + ("catch-neutral", 1.0), + ("catch-run", 1.0), + ("catch-run", 1.0), + ("catch-run", -1.0) + ) + self.setSpeed(self.forwardSpeed, self.rotateSpeed) + self.setActiveShadow(1) + + def exitCatching(self): + self.standWalkRunReverse = None + self.stop() + self.motion.exit() + + def enterCatchEating(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + # In this state, the Toon raises his arm to his mouth + # in an eating action + self.playingAnim = None + self.playingRate = None + self.standWalkRunReverse = ( + ("catch-eatneutral", 1.0), + ("catch-eatnrun", 1.0), + ("catch-eatnrun", 1.0), + ("catch-eatnrun", -1.0) + ) + self.setSpeed(self.forwardSpeed, self.rotateSpeed) + self.setActiveShadow(0) + + def exitCatchEating(self): + self.standWalkRunReverse = None + self.stop() + self.motion.exit() + + def enterWalk(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("walk") + self.setPlayRate(animMultiplier, "walk") + self.setActiveShadow(1) + + def exitWalk(self): + self.stop() + + def getJumpDuration(self): + # We check the duration on the legs in case there is a frame + # mismatch between the various animations for the parts. + if self.playingAnim == 'neutral': + return self.getDuration('jump', 'legs') + else: + return self.getDuration('running-jump', 'legs') + + def enterJump(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + """ + This jump is the old-style emote jump. It does't actually get the + avatar's collision sphere airborne. It is used for the the mini- + game victory, for example. + """ + # don't jump if the toon is disguised a a suit + if not self.isDisguised: + if self.playingAnim == 'neutral': + # ...stopped + anim = "jump" + else: + # ...must be moving! + anim = "running-jump" + self.playingAnim = anim + self.setPlayRate(animMultiplier, anim) + self.play(anim) + self.setActiveShadow(1) + + def exitJump(self): + self.stop() + self.playingAnim = "neutral" + + def enterJumpSquat(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # don't jump if the toon is disguised a a suit + if not self.isDisguised: + if self.playingAnim == 'neutral': + # ...stopped + anim = "jump-squat" + else: + # ...must be moving! + anim = "running-jump-squat" + self.playingAnim = anim + self.setPlayRate(animMultiplier, anim) + #self.play(anim, fromFrame=0, toFrame=16) + self.play(anim) + self.setActiveShadow(1) + + def exitJumpSquat(self): + self.stop() + self.playingAnim = "neutral" + + def enterJumpAirborne(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # don't jump if the toon is disguised a a suit + if not self.isDisguised: + if self.playingAnim == 'neutral': + # ...stopped + anim = "jump-idle" + else: + # ...must be moving! + anim = "running-jump-idle" + self.playingAnim = anim + self.setPlayRate(animMultiplier, anim) + #self.play(anim, fromFrame=17, toFrame=20) + self.loop(anim) + self.setActiveShadow(1) + + def exitJumpAirborne(self): + self.stop() + self.playingAnim = "neutral" + + def enterJumpLand(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # don't jump if the toon is disguised as a suit + if not self.isDisguised: + if self.playingAnim == 'running-jump-idle': + # ...must be moving! + anim = "running-jump-land" + skipStart = 0.2 + else: + # ...stopped + anim = "jump-land" + skipStart = 0.0 + self.playingAnim = anim + self.setPlayRate(animMultiplier, anim) + #frameCount = self.getNumFrames(anim) + #self.play(anim, fromFrame=int(frameCount*skipStart), toFrame=frameCount-1) + self.play(anim) + self.setActiveShadow(1) + + def exitJumpLand(self): + self.stop() + self.playingAnim = "neutral" + + def enterRun(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("run") + self.setPlayRate(animMultiplier, "run") + Emote.globalEmote.disableBody(self, "toon, enterRun") + self.setActiveShadow(1) + + def exitRun(self): + self.stop() + Emote.globalEmote.releaseBody(self, "toon, exitRun") + + def enterSwim(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableAll(self, "enterSwim") + self.playingAnim = "swim" + self.loop("swim") + self.setPlayRate(animMultiplier, "swim") + # Change your orientation to be face up + self.getGeomNode().setP(-89.0) + self.dropShadow.hide() + if self.isLocal(): + self.useSwimControls() + self.nametag3d.setPos(0, -2, 1) + # Bobbing task + self.startBobSwimTask() + self.setActiveShadow(0) + + def enterCringe(self,animMultiplier=1, ts=0, callback=None, extraArgs=[]): + #print "cringing" + self.loop("cringe") + self.getGeomNode().setPos(0,0,-2) + self.setPlayRate(animMultiplier,"swim") + #self.setActiveShadow(0) + #self.dropShadow.hide() + #self.nametag3d.setPos(0,-2,1) + + def exitCringe(self,animMultiplier=1, ts=0, callback=None, extraArgs=[]): + #self.loop("cringe") + #print "end cringe" + self.stop() + self.getGeomNode().setPos(0,0,0) + self.playingAnim="neutral" + self.setPlayRate(animMultiplier,"swim") + #self.setActiveShadow(0) + #self.dropShadow.hide() + #self.nametag3d.setPos(0,-2,1) + + def enterDive(self,animMultiplier=1, ts=0, callback=None, extraArgs=[]): + #print "swimming" + self.loop("swim") + #self.pose('swim', 55) + # 40 is arms back, 20 is arms middle + if hasattr(self.getGeomNode(), 'setPos'): + self.getGeomNode().setPos(0,0,-2) + self.setPlayRate(animMultiplier,"swim") + self.setActiveShadow(0) + self.dropShadow.hide() + self.nametag3d.setPos(0,-2,1) + + def exitDive(self): + #print "end swim" + self.stop() + self.getGeomNode().setPos(0,0,0) + self.playingAnim="neutral" + self.dropShadow.show() + self.nametag3d.setPos(0, 0, self.height + 0.5) + + def enterSwimHold(self,animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.getGeomNode().setPos(0,0,-2) + self.nametag3d.setPos(0,-2,1) + self.pose('swim', 55) + + def exitSwimHold(self): + self.stop() + self.getGeomNode().setPos(0,0,0) + self.playingAnim="neutral" + self.dropShadow.show() + self.nametag3d.setPos(0, 0, self.height + 0.5) + #self.playingAnim="neutral" + + def exitSwim(self): + self.stop() + self.playingAnim = "neutral" + # Stop bobbing task + self.stopBobSwimTask() + self.getGeomNode().setPosHpr(0, 0, 0, + 0, 0, 0) + self.dropShadow.show() + if self.isLocal(): + self.useWalkControls() + self.nametag3d.setPos(0, 0, self.height + 0.5) + Emote.globalEmote.releaseAll(self, "exitSwim") + + def startBobSwimTask(self): + swimTaskName = self.taskName("swimBobTask") + taskMgr.remove("swimTask") + taskMgr.remove(swimTaskName) + self.getGeomNode().setZ(4.0) + self.nametag3d.setZ(5.0) + newTask = Task.loop( + self.getGeomNode().lerpPosXYZ(0, -3, 3, 1, blendType="easeInOut"), + self.getGeomNode().lerpPosXYZ(0, -3, 4, 1, blendType="easeInOut") + ) + taskMgr.add(newTask, swimTaskName) + + def stopBobSwimTask(self): + swimTaskName = self.taskName("swimBobTask") + taskMgr.remove(swimTaskName) + self.getGeomNode().setPos(0, 0, 0) + self.nametag3d.setZ(1.0) + + def enterOpenBook(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableAll(self, "enterOpenBook") + self.playingAnim = 'openBook' + self.stopLookAround() + self.lerpLookAt(Point3(0, 1, -2)) + bookTracks = Parallel() + for bookActor in self.getBookActors(): + bookTracks.append(ActorInterval(bookActor, 'book', + startTime=1.2, endTime=1.5)) + bookTracks.append(ActorInterval(self, 'book', startTime=1.2, + endTime=1.5)) + + if hasattr(self, "uniqueName"): + trackName = self.uniqueName("openBook") + else: + trackName = "openBook" + self.track = Sequence( + Func(self.showBooks), + bookTracks, + Wait(0.1), + name = trackName) + + if callback: + self.track.setDoneEvent(self.track.getName()) + self.acceptOnce(self.track.getName(), callback, extraArgs) + + self.track.start(ts) + self.setActiveShadow(0) + + def exitOpenBook(self): + self.playingAnim = 'neutralob' + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + self.hideBooks() + self.startLookAround() + Emote.globalEmote.releaseAll(self, "exitOpenBook") + + def enterReadBook(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableBody(self, "enterReadBook") + self.playingAnim = 'readBook' + self.stopLookAround() + self.lerpLookAt(Point3(0, 1, -2)) + self.showBooks() + for bookActor in self.getBookActors(): + bookActor.pingpong('book', fromFrame=38, toFrame=118) + self.pingpong('book', fromFrame=38, toFrame=118) + self.setActiveShadow(0) + + def exitReadBook(self): + self.playingAnim = 'neutralrb' + self.hideBooks() + for bookActor in self.getBookActors(): + bookActor.stop() + self.startLookAround() + Emote.globalEmote.releaseBody(self, "exitReadBook") + + def enterCloseBook(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableAll(self, "enterCloseBook") + self.playingAnim = 'closeBook' + bookTracks = Parallel() + for bookActor in self.getBookActors(): + bookTracks.append(ActorInterval(bookActor, 'book', + startTime=4.96, endTime=6.5)) + bookTracks.append(ActorInterval(self, 'book', startTime=4.96, + endTime=6.5)) + if hasattr(self, "uniqueName"): + trackName = self.uniqueName("closeBook") + else: + trackName = "closeBook" + self.track = Sequence( + Func(self.showBooks), + bookTracks, + Func(self.hideBooks), + name = trackName) + + if callback: + self.track.setDoneEvent(self.track.getName()) + self.acceptOnce(self.track.getName(), callback, extraArgs) + + self.track.start(ts) + self.setActiveShadow(0) + + def exitCloseBook(self): + self.playingAnim = 'neutralcb' + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + Emote.globalEmote.releaseAll(self, "exitCloseBook") + + def getSoundTeleport(self): + # This is loaded on demand so it does not need to be downloaded with the tutorial + # which does not use it + if not self.soundTeleport: + self.soundTeleport = base.loadSfx("phase_3.5/audio/sfx/AV_teleport.mp3") + return self.soundTeleport + + def getTeleportOutTrack(self, autoFinishTrack = 1): + def showHoles(holes, hands): + for hole, hand in zip(holes, hands): + hole.reparentTo(hand) + def reparentHoles(holes, toon): + assert len(holes) == 3 + holes[0].reparentTo(toon) + holes[1].detachNode() + holes[2].detachNode() + # When we throw the hole on the ground, it goes into the + # shadow bin so it will render correctly w.r.t. the + # ground. + holes[0].setBin('shadow', 0) + holes[0].setDepthTest(0) + holes[0].setDepthWrite(0) + def cleanupHoles(holes): + holes[0].detachNode() + holes[0].clearBin() + holes[0].clearDepthTest() + holes[0].clearDepthWrite() + holes = self.getHoleActors() + hands = self.getRightHands() + holeTrack = Track( + (0.0, Func(showHoles, holes, hands)), + (0.5, SoundInterval(self.getSoundTeleport(), node = self)), + (1.708, Func(reparentHoles, holes, self)), + (3.4, Func(cleanupHoles, holes)), + ) + + if hasattr(self, "uniqueName"): + trackName = self.uniqueName("teleportOut") + else: + trackName = "teleportOut" + + track = Parallel(holeTrack, + name = trackName, + autoFinish = autoFinishTrack) + for hole in holes: + track.append(ActorInterval(hole, 'hole', duration=3.4)) + + track.append(ActorInterval(self, 'teleport', duration=3.4)) + + return track + + def enterTeleportOut(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + name = self.name + if hasattr(self, "doId"): + name += '-' + str( self.doId) + self.notify.debug('enterTeleportOut %s' % name) + if self.ghostMode or self.isDisguised: + # In ghost mode or in a cog suit, just go straight to the callback. + if callback: + callback(*extraArgs) + return + + self.playingAnim = "teleport" + Emote.globalEmote.disableAll(self, "enterTeleportOut") + + # If the toon is localToon, we do not want to autofinish this + # interval. When we lose our connection, we interrupt the + # ivalMgr. This causes all intervals that are autofinish to + # actually finish. Finishing TeleportOut makes us transition into + # the next state (like going to the next hood, estate, pick-a-toon, + # etc). We actually do not want this final transition to take place + # if our connection has been lost. If we are a DistributedToon we + # do need the finish to take place because it has a delay delete + # flag on it. + if self.isLocal(): + autoFinishTrack = 0 + else: + autoFinishTrack = 1 + + self.track = self.getTeleportOutTrack(autoFinishTrack) + + # It seems like we do not need these and they are causing leaks + # self.teleportOutCallback = callback + # self.teleportOutCallbackArgs = extraArgs + + self.track.setDoneEvent(self.track.getName()) + self.acceptOnce(self.track.getName(), self.finishTeleportOut, + [callback, extraArgs]) + + # Also, make sure the toon is not visible when he passes + # through to the other side of the hole (in case there is not + # a ground plane to obscure him there). We set up a clip + # plane at the floor level that only affects the toon. + holeClip = PlaneNode('holeClip') + self.holeClipPath = self.attachNewNode(holeClip) + self.getGeomNode().setClipPlane(self.holeClipPath) + self.nametag3d.setClipPlane(self.holeClipPath) + + self.track.start(ts) + self.setActiveShadow(0) + + def finishTeleportOut(self, callback=None, extraArgs=[]): + # It's necessary to clean up the track in finishTeleportOut + # (which is called when the track finishes itself) even though + # it is also being cleaned up in exitTeleportOut (which is + # called whenever we leave the TeleportOut state). + + # This is because cleaning up the track *might* result in the + # Toon object being deleted, if this is a DistributedToon on + # the way out and the only thing keeping it around is a + # DelayDelete object on the track. We can't have the Toon be + # deleted while it is exiting from a state, so we arrange to + # ensure it will be deleted here, instead. + + name = self.name + if hasattr(self, "doId"): + name += '-' + str( self.doId) + self.notify.debug('finishTeleportOut %s' % name) + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + + # And again, since we might have been deleted by the above + # operation, we should check first before we try to move on to + # the next state. + if hasattr(self, "animFSM"): + self.animFSM.request('TeleportedOut') + + if callback: + callback(*extraArgs) + + def exitTeleportOut(self): + name = self.name + if hasattr(self, "doId"): + name += '-' + str( self.doId) + self.notify.debug('exitTeleportOut %s' % name) + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + self.track = None + + geomNode = self.getGeomNode() + + if geomNode and (not geomNode.isEmpty()): + self.getGeomNode().clearClipPlane() + if self.nametag3d and (not self.nametag3d.isEmpty()): + self.nametag3d.clearClipPlane() + if self.holeClipPath: + self.holeClipPath.removeNode() + self.holeClipPath = None + + # Because we got hidden at the end of the movie. + Emote.globalEmote.releaseAll(self, "exitTeleportOut") + if self and not self.isEmpty(): + self.show() + + def enterTeleportedOut(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # In this state, the toon has finished teleporting out and is + # gone. This state is normally transitioned to when the + # teleportOut animation finishes (but it might be skipped if + # someone requests a transition to another state while the + # teleportOut animation is still playing). + self.setActiveShadow(0) + + def exitTeleportedOut(self): + pass + + def getDiedInterval(self, autoFinishTrack = 1): + sound = loader.loadSfx('phase_5/audio/sfx/ENC_Lose.mp3') + + if hasattr(self, "uniqueName"): + trackName = self.uniqueName("died") + else: + trackName = "died" + + ival = Sequence( + Func(Emote.globalEmote.disableBody, self), + Func(self.sadEyes), + Func(self.blinkEyes), + Track((0, ActorInterval(self, 'lose')), + (2, SoundInterval(sound, node = self)), + (5.333, self.scaleInterval(1.5, VBase3(0.01, 0.01, 0.01), blendType = 'easeInOut'))), + Func(self.detachNode), + Func(self.setScale, 1, 1, 1), + Func(self.normalEyes), + Func(self.blinkEyes), + Func(Emote.globalEmote.releaseBody, self), + name = trackName, + autoFinish = autoFinishTrack) + + return ival + + def enterDied(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + if self.ghostMode: + # In ghost mode, just go straight to the callback. + if callback: + callback(*extraArgs) + return + + if self.isDisguised: + self.takeOffSuit() + + self.playingAnim = "lose" + Emote.globalEmote.disableAll(self, "enterDied") + + if self.isLocal(): + autoFinishTrack = 0 + else: + autoFinishTrack = 1 + + # extended hack: prevent the jumping code from interfering + if hasattr(self, 'jumpLandAnimFixTask') and self.jumpLandAnimFixTask: + self.jumpLandAnimFixTask.remove() + self.jumpLandAnimFixTask = None + + self.track = self.getDiedInterval(autoFinishTrack) + + # tack the callback right onto the interval, to make sure it gets + # called if we're interrupted. + if callback: + self.track = Sequence( + self.track, + Func(callback, *extraArgs), + autoFinish = autoFinishTrack) + + self.track.start(ts) + self.setActiveShadow(0) + + def finishDied(self, callback=None, extraArgs=[]): + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + + if hasattr(self, "animFSM"): + self.animFSM.request('TeleportedOut') + + if callback: + callback(*extraArgs) + + def exitDied(self): + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + + Emote.globalEmote.releaseAll(self, "exitDied") + self.show() + + def getTeleportInTrack(self): + hole = self.getHoleActors()[0] + # Put the hole into the shadow bin so it will render correctly + # w.r.t. the ground. + hole.setBin('shadow', 0) + hole.setDepthTest(0) + hole.setDepthWrite(0) + holeTrack = Sequence() + holeTrack.append(Func(hole.reparentTo, self)) + pos = Point3(0, -2.4, 0) + holeTrack.append(Func(hole.setPos, self, pos)) + holeTrack.append(ActorInterval(hole, 'hole', startTime=3.4, + endTime=3.1)) + holeTrack.append(Wait(0.6)) + holeTrack.append(ActorInterval(hole, 'hole', startTime=3.1, + endTime=3.4)) + def restoreHole(hole): + hole.setPos(0, 0, 0) + hole.detachNode() + hole.clearBin() + hole.clearDepthTest() + hole.clearDepthWrite() + holeTrack.append(Func(restoreHole, hole)) + + toonTrack = Sequence(Wait(0.3), + Func(self.getGeomNode().show), + Func(self.nametag3d.show), + ActorInterval(self, 'jump', startTime=0.45)) + if hasattr(self, "uniqueName"): + trackName = self.uniqueName("teleportIn") + else: + trackName = "teleportIn" + return (Parallel(holeTrack, toonTrack, name = trackName)) + + def enterTeleportIn(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + if self.ghostMode or self.isDisguised: + # In ghost mode, or if we're wearing a cog suit, just go + # straight to the callback. + if callback: + callback(*extraArgs) + return + + # try to fix invisible toons in minigolf zone + self.show() + + self.playingAnim = "teleport" + Emote.globalEmote.disableAll(self, "enterTeleportIn") + + # Start by posing to the last frame of the "teleport" + # animation, which has the toon below the floor. That way we + # won't be visible in some bogus pose briefly before we jump + # out of the hole. + self.pose("teleport", self.getNumFrames("teleport") - 1) + + # Do not hide the actual localToon because the camera is under that + self.getGeomNode().hide() + # Also hide the nametag3d + self.nametag3d.hide() + + self.track = self.getTeleportInTrack() + + if callback: + self.track.setDoneEvent(self.track.getName()) + self.acceptOnce(self.track.getName(), callback, extraArgs) + + self.track.start(ts) + self.setActiveShadow(0) + + def exitTeleportIn(self): + self.playingAnim = None + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + if not self.ghostMode and not self.isDisguised: + self.getGeomNode().show() + self.nametag3d.show() + Emote.globalEmote.releaseAll(self, "exitTeleportIn") + + def enterSitStart(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableBody(self) + self.playingAnim = 'sit-start' + + if (self.isLocal()): + self.track = Sequence( + ActorInterval(self, 'sit-start'), + Func(self.b_setAnimState, 'Sit', animMultiplier), + ) + else: + self.track = Sequence( + ActorInterval(self, 'sit-start'), + ) + self.track.start(ts) + self.setActiveShadow(0) + + def exitSitStart(self): + self.playingAnim = 'neutral' + if (self.track != None): + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + Emote.globalEmote.releaseBody(self) + + def enterSit(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableBody(self) + self.playingAnim = 'sit' + self.loop('sit') + self.setActiveShadow(0) + + def exitSit(self): + self.playingAnim = 'neutral' + Emote.globalEmote.releaseBody(self) + + def enterSleep(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.stopLookAround() + self.stopBlink() + self.closeEyes() + self.lerpLookAt(Point3(0, 1, -4)) + self.loop("neutral") + self.setPlayRate(animMultiplier * 0.4, "neutral") + self.setChatAbsolute(SLEEP_STRING, CFThought) + # This is where we send Toons back to the Toon page after they've + # been sleeping for a while + if (self == base.localAvatar): + print("adding timeout task") + taskMgr.doMethodLater(self.afkTimeout, self.__handleAfkTimeout, + self.uniqueName('afkTimeout')) + self.setActiveShadow(0) + + def __handleAfkTimeout(self, task): + print("handling timeout") + assert self == base.localAvatar + self.ignore('wakeup') + # This will check to see if we're actually wearing a suit before + # doing anything + self.takeOffSuit() + base.cr.playGame.getPlace().fsm.request('final') + self.b_setAnimState('TeleportOut', 1, self.__handleAfkExitTeleport, [0]) + return Task.done + + def __handleAfkExitTeleport(self, requestStatus): + self.notify.info('closing shard...') + base.cr.gameFSM.request('closeShard', ['afkTimeout']) + + + + def exitSleep(self): + taskMgr.remove(self.uniqueName('afkTimeout')) + self.startLookAround() + self.openEyes() + self.startBlink() + if self.nametag.getChat() == SLEEP_STRING: + self.clearChat() + self.lerpLookAt(Point3(0, 1, 0), time=0.25) + self.stop() + + def enterPush(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableBody(self) + self.playingAnim = 'push' + self.track = Sequence( + ActorInterval(self, 'push'), + ) + self.track.loop() + self.setActiveShadow(1) + + def exitPush(self): + self.playingAnim = 'neutral' + if (self.track != None): + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + Emote.globalEmote.releaseBody(self) + + def enterEmote(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + if len(extraArgs) > 0: + emoteIndex = extraArgs[0] + else: + return + + self.playingAnim = None + self.playingRate = None + + self.standWalkRunReverse = ( + ("neutral", 1.0), + ("walk", 1.0), + ("run", 1.0), + ("walk", -1.0) + ) + self.setSpeed(self.forwardSpeed, self.rotateSpeed) + + # Wake the toon up + if (self.isLocal() and emoteIndex != Emote.globalEmote.EmoteSleepIndex): + if self.sleepFlag: + # If we were in sleep mode, switch to happy mode + # before we play the emote. Not completely sure why + # this is necessary. + self.b_setAnimState("Happy", self.animMultiplier) + self.wakeUp() + + # Play the emote + duration = 0 + self.emoteTrack, duration = Emote.globalEmote.doEmote(self, emoteIndex, ts) + + self.setActiveShadow(1) + + def doEmote(self, emoteIndex, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + # don't play emotes for toons we are ignoring + if not self.isLocal(): + if base.cr.avatarFriendsManager.checkIgnored(self.doId): + return + + # Play the emote + duration = 0 + if self.isLocal(): + self.wakeUp() + # Make sure the wake up flags are processed before we play the emote + if self.hasTrackAnimToSpeed(): + self.trackAnimToSpeed(None) + self.emoteTrack, duration = Emote.globalEmote.doEmote(self, emoteIndex, ts) + + def __returnToLastAnim(self, task): + if self.playingAnim: + self.loop(self.playingAnim) + else: + if self.hp > 0: + self.loop('neutral') + else: + self.loop('sad-neutral') + return Task.done + + def __finishEmote(self, task): + if self.isLocal(): + if self.hp > 0: + self.b_setAnimState('Happy') + else: + self.b_setAnimState('Sad') + return Task.done + + def exitEmote(self): + #self.standWalkRunReverse = None + #self.motion.exit() + self.stop() + if self.emoteTrack != None: + self.emoteTrack.finish() + self.emoteTrack = None + taskMgr.remove(self.taskName('finishEmote')) + + def enterSquish(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableAll(self) + sound = loader.loadSfx('phase_9/audio/sfx/toon_decompress.mp3') + + lerpTime = .1 + node = self.getGeomNode().getChild(0) + origScale = node.getScale() + self.track = Sequence( + LerpScaleInterval(node, lerpTime, VBase3(2,2,.025), blendType = 'easeInOut'), + Wait(1.0), + Parallel( + Sequence(Wait(.4), + LerpScaleInterval(node, lerpTime, VBase3(1.4,1.4,1.4), blendType = 'easeInOut'), + LerpScaleInterval(node, lerpTime/2.0, VBase3(.8,.8,.8), blendType = 'easeInOut'), + LerpScaleInterval(node, lerpTime/3.0, origScale, blendType = 'easeInOut'),), + ActorInterval(self, 'jump', startTime=.2), + SoundInterval(sound), + ), + ) + self.track.start(ts) + self.setActiveShadow(1) + + def exitSquish(self): + self.playingAnim = 'neutral' + if (self.track != None): + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + Emote.globalEmote.releaseAll(self) + + def enterFallDown(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.playingAnim = 'fallDown' + Emote.globalEmote.disableAll(self) + self.track = Sequence(ActorInterval(self,'slip-backward'), + name = "fallTrack") + if callback: + self.track.setDoneEvent(self.track.getName()) + self.acceptOnce(self.track.getName(), callback, extraArgs) + self.track.start(ts) + + def exitFallDown(self): + self.playingAnim = 'neutral' + if (self.track != None): + self.ignore(self.track.getName()) + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + Emote.globalEmote.releaseAll(self) + + def stunToon(self, ts=0, callback=None, knockdown=0): + if not self.isStunned: + if self.stunTrack: + self.stunTrack.finish() + self.stunTrack = None + + def setStunned(stunned): + self.isStunned = stunned + if self == base.localAvatar: + messenger.send("toonStunned-" + str(self.doId), [self.isStunned]) + + # setup a track that lerps the transparency of the toon + # up and down to make him appear to flash. like he's hurt see. + node = self.getGeomNode() + + lerpTime = .5 + down = self.doToonColorScale(VBase4(1, 1, 1, 0.6), lerpTime) + up = self.doToonColorScale(VBase4(1, 1, 1, 0.9), lerpTime) + clear = self.doToonColorScale(self.defaultColorScale, lerpTime) + #trackName = "stunTrack-" + str(self.doId) + #stunTrack = Sequence(name = trackName) + track = Sequence(Func(setStunned, 1), + down,up,down,up,down,up,down,clear, + Func(self.restoreDefaultColorScale), + Func(setStunned, 0)) + + if knockdown: + self.stunTrack = Parallel(ActorInterval(self, animName='slip-backward'), + track, + ) + else: + self.stunTrack = track + + self.stunTrack.start() + + def getPieces(self, *pieces): + """getPieces(self, tuple pieces) + + The arguments to this function are of the form: + ((partName, (pieceName, pieceName, ..., pieceName)), + (partName, (pieceName, pieceName, ..., pieceName)), + ..., + ) + + where partName is the name of one part of the toon, + e.g. 'torso', 'legs', or 'head', and pieceName are the names + of nodes that may be found under the partName. This returns a + list of all the pieces that match among all LOD's. + """ + results = [] + for lodName in self.getLODNames(): + for partName, pieceNames in pieces: + part = self.getPart(partName, lodName) + if part: + if type(pieceNames) == types.StringType: + pieceNames = (pieceNames,) + for pieceName in pieceNames: + npc = part.findAllMatches("**/" + pieceName) + for i in range (npc.getNumPaths()): + results.append(npc[i]) + return results + + + ### Cheesy rendering effects. This is an experiment in + ### novelty--can we give cheesy rendering effects as rewards for + ### quests? + + def applyCheesyEffect(self, effect, lerpTime = 0): + if self.effectTrack != None: + self.effectTrack.finish() + self.effectTrack = None + + if self.cheesyEffect != effect: + oldEffect = self.cheesyEffect + self.cheesyEffect = effect + if oldEffect == ToontownGlobals.CENormal: + self.effectTrack = self.__doCheesyEffect(effect, lerpTime) + elif effect == ToontownGlobals.CENormal: + self.effectTrack = self.__undoCheesyEffect(oldEffect, lerpTime) + else: + self.effectTrack = Sequence( + self.__undoCheesyEffect(oldEffect, lerpTime / 2.0), + self.__doCheesyEffect(effect, lerpTime / 2.0)) + self.effectTrack.start() + + def clearCheesyEffect(self, lerpTime = 0): + self.applyCheesyEffect(ToontownGlobals.CENormal, lerpTime = lerpTime) + if self.effectTrack != None: + self.effectTrack.finish() + self.effectTrack = None + + def __doHeadScale(self, scale, lerpTime): + if scale == None: + scale = ToontownGlobals.toonHeadScales[self.style.getAnimal()] + + track = Parallel() + for hi in range(self.headParts.getNumPaths()): + head = self.headParts[hi] + track.append(LerpScaleInterval(head, lerpTime, scale, blendType = 'easeInOut')) + return track + + def __doLegsScale(self, scale, lerpTime): + if scale == None: + scale = 1 + invScale = 1 + else: + invScale = 1.0 / scale + + track = Parallel() + for li in range(self.legsParts.getNumPaths()): + legs = self.legsParts[li] + torso = self.torsoParts[li] + track.append(LerpScaleInterval(legs, lerpTime, scale, blendType = 'easeInOut')) + track.append(LerpScaleInterval(torso, lerpTime, invScale, blendType = 'easeInOut')) + return track + + def __doToonScale(self, scale, lerpTime): + if scale == None: + scale = 1 + + node = self.getGeomNode().getChild(0) + track = Sequence(Parallel(LerpHprInterval(node, lerpTime, Vec3(0.0, 0.0, 0.0), blendType = 'easeInOut'), + LerpScaleInterval(node, lerpTime, scale, blendType = 'easeInOut')), + Func(self.resetHeight)) + return track + + def doToonColorScale(self, scale, lerpTime, keepDefault = 0): + # Apply a color scale to the entire toon, probably to make a + # transparency effect. This does not correctly handle scaling + # the alpha all the way down to 0; see doToonGhostColorScale() + # for that. + + if keepDefault: + self.defaultColorScale = scale + + if scale == None: + scale = VBase4(1, 1, 1, 1) + + node = self.getGeomNode() + caps = self.getPieces(('torso', ('torso-bot-cap'))) + + track = Sequence() + track.append(Func(node.setTransparency, 1)) + if scale[3] != 1: + for cap in caps: + track.append(HideInterval(cap)) + track.append(LerpColorScaleInterval(node, lerpTime, scale, blendType = 'easeInOut')) + if scale[3] == 1: + track.append(Func(node.clearTransparency)) + for cap in caps: + track.append(ShowInterval(cap)) + elif scale[3] == 0: + track.append(Func(node.clearTransparency)) + + return track + + def __doPumpkinHeadSwitch(self, lerpTime, toPumpkin): + node = self.getGeomNode() + + def getDustCloudIval(): + dustCloud = DustCloud.DustCloud(fBillboard=0,wantSound=1) + dustCloud.setBillboardAxis(2.) + dustCloud.setZ(3) + dustCloud.setScale(0.4) + dustCloud.createTrack() + return Sequence( + Func(dustCloud.reparentTo, self), + dustCloud.track, + Func(dustCloud.destroy), + name = 'dustCloadIval' + ) + + dust = getDustCloudIval() + + track = Sequence() + if toPumpkin: + track.append(Func(self.stopBlink)) + track.append(Func(self.closeEyes)) + if(lerpTime > 0.0): # this is to keep the dust cloud from occuring with every generate + track.append(Func(dust.start)) + track.append(Wait(0.5)) + else: + dust.finish() + + def hideParts(): + self.notify.debug("HidePaths") + for hi in range(self.headParts.getNumPaths()): + head = self.headParts[hi] + parts = head.getChildren() + for pi in range(parts.getNumPaths()): + p = parts[pi] + if not p.isHidden(): + p.hide() + p.setTag("pumpkin", "enabled") + + track.append(Func(hideParts)) + track.append(Func(self.enablePumpkins,True)) + else: + if(lerpTime > 0.0): + track.append(Func(dust.start)) + track.append(Wait(0.5)) + else: + dust.finish() + + def showHiddenParts(): + self.notify.debug("ShowHiddenPaths") + for hi in range(self.headParts.getNumPaths()): + head = self.headParts[hi] + parts = head.getChildren() + for pi in range(parts.getNumPaths()): + p = parts[pi] + if (not self.pumpkins.hasPath(p)) and p.getTag("pumpkin") == "enabled": + p.show() + p.setTag("pumpkin", "disabled") + + track.append(Func(showHiddenParts)) + track.append(Func(self.enablePumpkins,False)) + track.append(Func(self.startBlink)) + return track + + def __doSnowManHeadSwitch(self, lerpTime, toSnowMan): + node = self.getGeomNode() + + def getDustCloudIval(): + dustCloud = DustCloud.DustCloud(fBillboard=0,wantSound=0) + dustCloud.setBillboardAxis(2.) + dustCloud.setZ(3) + dustCloud.setScale(0.4) + dustCloud.createTrack() + return Sequence( + Func(dustCloud.reparentTo, self), + dustCloud.track, + Func(dustCloud.destroy), + name = 'dustCloadIval' + ) + + dust = getDustCloudIval() + + track = Sequence() + if toSnowMan: + track.append(Func(self.stopBlink)) + track.append(Func(self.closeEyes)) + if(lerpTime > 0.0): # this is to keep the dust cloud from occuring with every generate + track.append(Func(dust.start)) + track.append(Wait(0.5)) + else: + dust.finish() + + def hideParts(): + self.notify.debug("HidePaths") + for hi in range(self.headParts.getNumPaths()): + head = self.headParts[hi] + parts = head.getChildren() + for pi in range(parts.getNumPaths()): + p = parts[pi] + if not p.isHidden(): + p.hide() + p.setTag("snowman", "enabled") + + track.append(Func(hideParts)) + track.append(Func(self.enableSnowMen,True)) + else: + if(lerpTime > 0.0): + track.append(Func(dust.start)) + track.append(Wait(0.5)) + else: + dust.finish() + def showHiddenParts(): + self.notify.debug("ShowHiddenPaths") + + for hi in range(self.headParts.getNumPaths()): + head = self.headParts[hi] + parts = head.getChildren() + for pi in range(parts.getNumPaths()): + p = parts[pi] + if (not self.snowMen.hasPath(p)) and p.getTag("snowman") == "enabled": + p.show() + p.setTag("snowman", "disabled") + + track.append(Func(showHiddenParts)) + track.append(Func(self.enableSnowMen,False)) + track.append(Func(self.startBlink)) + return track + + def __doBigAndWhite(self, color, scale, lerpTime): + # call the two existing functions and combine the tracks + track = Parallel() + track.append(self.__doToonColor(color, lerpTime)) + track.append(self.__doToonScale(scale, lerpTime)) + return track + + def __doVirtual(self): + # call the two existing functions and combine the tracks + track = Parallel() + track.append(self.__doToonColor(VBase4(0.25, 0.25, 1.0, 1), 0.0)) + self.setPartsAdd(self.getHeadParts()) + self.setPartsAdd(self.getTorsoParts()) + self.setPartsAdd(self.getHipsParts()) + self.setPartsAdd(self.getLegsParts()) + #self.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd)) + #self.setDepthWrite(False) + #self.setBin('fixed', 1) + return track + + def __doUnVirtual(self): + # call the two existing functions and combine the tracks + track = Parallel() + track.append(self.__doToonColor(None, 0.0)) + self.setPartsNormal(self.getHeadParts(), 1) + self.setPartsNormal(self.getTorsoParts(), 1) + self.setPartsNormal(self.getHipsParts(), 1) + self.setPartsNormal(self.getLegsParts(), 1) + #self.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MNone)) + #self.setDepthWrite(True) + #self.setBin('default', 0) + return track + + def setPartsAdd(self, parts): + #actorNode = self.find("**/__Actor_modelRoot") + actorCollection = parts #self.getHeadParts() #actorNode.findAllMatches("*") + for thingIndex in range(0,actorCollection.getNumPaths()): + thing = actorCollection[thingIndex] + if thing.getName() not in ('joint_attachMeter', 'joint_nameTag'): + thing.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd)) + thing.setDepthWrite(False) + self.setBin('fixed', 1) + + + def setPartsNormal(self, parts, alpha = 0): + #actorNode = self.find("**/__Actor_modelRoot") + actorCollection = parts + for thingIndex in range(0,actorCollection.getNumPaths()): + thing = actorCollection[thingIndex] + if thing.getName() not in ('joint_attachMeter', 'joint_nameTag'): + thing.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MNone)) + thing.setDepthWrite(True) + self.setBin('default', 0) + if alpha: + thing.setTransparency(1) + thing.setBin('transparent', 0) + + def __doToonGhostColorScale(self, scale, lerpTime, keepDefault = 0): + + # This is similar to doToonColorScale, above, except it has + # additional support for scaling the alpha all the way down to + # 0 and back again (in particular, it does a show and hide at + # alpha 0). + + # This doesn't work when toons might be wearing a cog suit, + # which also monkeys with the show/hide transition on the geom + # node. But we only use this method to implement ghost mode, + # which is currently only invoked in move-furniture mode or + # via a magic word. + + if keepDefault: + self.defaultColorScale = scale + + if scale == None: + scale = VBase4(1, 1, 1, 1) + + node = self.getGeomNode() + caps = self.getPieces(('torso', ('torso-bot-cap'))) + + track = Sequence() + track.append(Func(node.setTransparency, 1)) + track.append(ShowInterval(node)) + if scale[3] != 1: + for cap in caps: + track.append(HideInterval(cap)) + track.append(LerpColorScaleInterval(node, lerpTime, scale, blendType = 'easeInOut')) + if scale[3] == 1: + track.append(Func(node.clearTransparency)) + for cap in caps: + track.append(ShowInterval(cap)) + elif scale[3] == 0: + track.append(Func(node.clearTransparency)) + track.append(HideInterval(node)) + + return track + + def restoreDefaultColorScale(self): + # Restore the currently active color scale (e.g. after + # temporarily changing it with a resistance chat or ouch + # effect). + node = self.getGeomNode() + if node: + if self.defaultColorScale: + node.setColorScale(self.defaultColorScale) + if self.defaultColorScale[3] != 1: + node.setTransparency(1) + else: + node.clearTransparency() + else: + node.clearColorScale() + node.clearTransparency() + + def __doToonColor(self, color, lerpTime): + node = self.getGeomNode() + + # Can't lerp setColor, so this is always an instant lerp. + if color == None: + return Func(node.clearColor) + else: + # An override of 1 to override toon color (but not shadow color) + return Func(node.setColor, color, 1) + + def __doPartsColorScale(self, scale, lerpTime): + # color scale only the fleshy parts of the toon--an "invisible man" effect. + if scale == None: + scale = VBase4(1, 1, 1, 1) + + node = self.getGeomNode() + pieces = self.getPieces(('torso', ('arms', 'neck')), + ('legs', ('legs', 'feet')), + ('head', ('+GeomNode'))) + track = Sequence() + track.append(Func(node.setTransparency, 1)) + for piece in pieces: + # Hide/Show only the neutral muzzle. Don't do anything to the emote muzzles. + if (piece.getName()[:7] == 'muzzle-') and (piece.getName()[-8:] != '-neutral'): + continue + track.append(ShowInterval(piece)) + + p1 = Parallel() + for piece in pieces: + # Hide/Show only the neutral muzzle. Don't do anything to the emote muzzles. + if (piece.getName()[:7] == 'muzzle-') and (piece.getName()[-8:] != '-neutral'): + continue + p1.append(LerpColorScaleInterval(piece, lerpTime, scale, blendType = 'easeInOut')) + track.append(p1) + + if scale[3] == 1: + track.append(Func(node.clearTransparency)) + elif scale[3] == 0: + track.append(Func(node.clearTransparency)) + for piece in pieces: + # Hide/Show only the neutral muzzle. Don't do anything to the emote muzzles. + if (piece.getName()[:7] == 'muzzle-') and (piece.getName()[-8:] != '-neutral'): + continue + track.append(HideInterval(piece)) + + return track + + def __doCheesyEffect(self, effect, lerpTime): + # Returns a track that applies the given effect from a normal state. + if effect == ToontownGlobals.CEBigHead: + return self.__doHeadScale(2.5, lerpTime) + elif effect == ToontownGlobals.CESmallHead: + return self.__doHeadScale(0.5, lerpTime) + elif effect == ToontownGlobals.CEBigLegs: + return self.__doLegsScale(1.4, lerpTime) + elif effect == ToontownGlobals.CESmallLegs: + return self.__doLegsScale(0.6, lerpTime) + elif effect == ToontownGlobals.CEBigToon: + return self.__doToonScale(ToontownGlobals.BigToonScale, lerpTime) + elif effect == ToontownGlobals.CESmallToon: + return self.__doToonScale(ToontownGlobals.SmallToonScale, lerpTime) + elif effect == ToontownGlobals.CEFlatPortrait: + return self.__doToonScale(VBase3(1, 0.05, 1), lerpTime) + elif effect == ToontownGlobals.CEFlatProfile: + return self.__doToonScale(VBase3(0.05, 1, 1), lerpTime) + elif effect == ToontownGlobals.CETransparent: + return self.doToonColorScale(VBase4(1, 1, 1, 0.6), lerpTime, keepDefault = 1) + elif effect == ToontownGlobals.CENoColor: + return self.__doToonColor(VBase4(1, 1, 1, 1), lerpTime) + elif effect == ToontownGlobals.CEInvisible: + return self.__doPartsColorScale(VBase4(1, 1, 1, 0), lerpTime) + elif effect == ToontownGlobals.CEPumpkin: + return self.__doPumpkinHeadSwitch(lerpTime,toPumpkin = True) + elif effect == ToontownGlobals.CEBigWhite: + return self.__doBigAndWhite(VBase4(1, 1, 1, 1), ToontownGlobals.BigToonScale, lerpTime) + elif effect == ToontownGlobals.CESnowMan: + return self.__doSnowManHeadSwitch(lerpTime, toSnowMan = True) + elif effect == ToontownGlobals.CEVirtual: + return self.__doVirtual() + elif effect == ToontownGlobals.CEGhost: + alpha = 0 + if localAvatar.seeGhosts: + alpha = 0.2 + return Sequence( + self.__doToonGhostColorScale(VBase4(1, 1, 1, alpha), lerpTime, keepDefault = 1), + Func(self.nametag3d.hide)) + + # Invalid effect. + return Sequence() + + def __undoCheesyEffect(self, effect, lerpTime): + # Returns a track that returns from the given effect to a normal state. + if effect == ToontownGlobals.CEBigHead: + return self.__doHeadScale(None, lerpTime) + elif effect == ToontownGlobals.CESmallHead: + return self.__doHeadScale(None, lerpTime) + if effect == ToontownGlobals.CEBigLegs: + return self.__doLegsScale(None, lerpTime) + elif effect == ToontownGlobals.CESmallLegs: + return self.__doLegsScale(None, lerpTime) + elif effect == ToontownGlobals.CEBigToon: + return self.__doToonScale(None, lerpTime) + elif effect == ToontownGlobals.CESmallToon: + return self.__doToonScale(None, lerpTime) + elif effect == ToontownGlobals.CEFlatPortrait: + return self.__doToonScale(None, lerpTime) + elif effect == ToontownGlobals.CEFlatProfile: + return self.__doToonScale(None, lerpTime) + elif effect == ToontownGlobals.CETransparent: + return self.doToonColorScale(None, lerpTime, keepDefault = 1) + elif effect == ToontownGlobals.CENoColor: + return self.__doToonColor(None, lerpTime) + elif effect == ToontownGlobals.CEInvisible: + return self.__doPartsColorScale(None, lerpTime) + elif effect == ToontownGlobals.CEPumpkin: + return self.__doPumpkinHeadSwitch(lerpTime,toPumpkin = False) + elif effect == ToontownGlobals.CEBigWhite: + return self.__doBigAndWhite(None, None, lerpTime) + elif effect == ToontownGlobals.CESnowMan: + return self.__doSnowManHeadSwitch(lerpTime, toSnowMan = False) + elif effect == ToontownGlobals.CEVirtual: + return self.__doUnVirtual() + elif effect == ToontownGlobals.CEGhost: + return Sequence( + Func(self.nametag3d.show), + self.__doToonGhostColorScale(None, lerpTime, keepDefault = 1)) + + # Invalid effect. + return Sequence() + + + # special methods for making a toon put on and take off a suit disguise for the cog HQ + def putOnSuit(self, suitType, setDisplayName=True): + # suitType = suit dna string (ie "le" for legal eagle) + if self.isDisguised: + self.takeOffSuit() + + if not launcher.getPhaseComplete(5): + # If we haven't downloaded phase 5 yet, don't attempt to + # wear a suit; that will just crash the client. This + # should only be possible if someone is hacking us to wear + # a suit in the playground. + return + + # make sure this is a valid suit name + assert suitType in SuitDNA.suitHeadTypes + + from toontown.suit import Suit + + # generate suit geometry based on this dna + suit = Suit.Suit() + dna = SuitDNA.SuitDNA() + dna.newSuit(suitType) + suit.setStyle(dna) + suit.isDisguised = 1 + suit.generateSuit() + suit.initializeDropShadow() + suit.setPos(self.getPos()) + suit.setHpr(self.getHpr()) + # hide the suit head as the toon head will be used + for part in suit.getHeadParts(): + part.hide() + + # reparent the toon head to the suit + suitHeadNull = suit.find("**/joint_head") + toonHead = self.getPart('head', '1000') + + # turn off emotions + Emote.globalEmote.disableAll(self) + + # hide the toon geometry + toonGeom = self.getGeomNode() + toonGeom.hide() + + # preserve scale on the head + worldScale = toonHead.getScale(render) + self.headOrigScale = toonHead.getScale() + # we need to add an intermediate node to the head to make the offset work with scale + headPosNode = hidden.attachNewNode("headPos") + toonHead.reparentTo(headPosNode) + toonHead.setPos(0, 0, 0.2) + headPosNode.reparentTo(suitHeadNull) + headPosNode.setScale(render, worldScale) + # reparent the suit geom to the toon + suitGeom = suit.getGeomNode() + suitGeom.reparentTo(self) + + # save these for later + self.suit = suit + self.suitGeom = suitGeom + self.setHeight(suit.getHeight()) + self.nametag3d.setPos(0, 0, self.height + 1.3) + + + + # if we are local hide the sticker book and alter our walk speed + if self.isLocal(): + if hasattr(self, "book"): + self.book.obscureButton(1) + + # make toon move at suit movement rates + self.oldForward = ToontownGlobals.ToonForwardSpeed + self.oldReverse = ToontownGlobals.ToonReverseSpeed + self.oldRotate = ToontownGlobals.ToonRotateSpeed + ToontownGlobals.ToonForwardSpeed = ToontownGlobals.SuitWalkSpeed + ToontownGlobals.ToonReverseSpeed = ToontownGlobals.SuitWalkSpeed + ToontownGlobals.ToonRotateSpeed = ToontownGlobals.ToonRotateSlowSpeed + + # stop and restart to make sure these values "take" + if self.hasTrackAnimToSpeed(): + self.stopTrackAnimToSpeed() + self.startTrackAnimToSpeed() + + # turn off jump ability + self.controlManager.disableAvatarJump() + + # add some suit phrases to the speed chat + indices = range(OTPLocalizer.SCMenuCommonCogIndices[0], OTPLocalizer.SCMenuCommonCogIndices[1] + 1) + customIndices = OTPLocalizer.SCMenuCustomCogIndices[suitType] + indices += range(customIndices[0], customIndices[1] + 1) + self.chatMgr.chatInputSpeedChat.addCogMenu(indices) + + # make sure we start in neutral + self.suit.loop("neutral") + + + # set the flag + self.isDisguised = 1 + + # make our chat and name display the suit font + self.setFont(ToontownGlobals.getSuitFont()) + if setDisplayName: + # determine which dept this suit is in order to display the correct level info + # We print the suit name instead of the dept name, 'cause + # that's what people care about more. + suitDept = SuitDNA.suitDepts.index(SuitDNA.getSuitDept(suitType)) + suitName = SuitBattleGlobals.SuitAttributes[suitType]['name'] + self.nametag.setDisplayName(TTLocalizer.SuitBaseNameWithLevel % { + "name": self.getName(), + "dept": suitName, + "level": self.cogLevels[suitDept] + 1}) + self.nametag.setNameWordwrap(9.0) + + + + def takeOffSuit(self): + # make sure we are a suit first + if not self.isDisguised: + return + + suitType = self.suit.style.name + + # put the toon head back on the toon body + toonHeadNull = self.find("**/1000/**/def_head") + if not toonHeadNull: + toonHeadNull = self.find("**/1000/**/joint_head") + toonHead = self.getPart('head', '1000') + toonHead.reparentTo(toonHeadNull) + + # reset the scale and position on the toon head + toonHead.setScale(self.headOrigScale) + toonHead.setPos(0,0,0) + + # clean up the intermedita node + headPosNode = self.suitGeom.find("**/headPos") + headPosNode.removeNode() + + # hide the suit body + self.suitGeom.reparentTo(self.suit) + self.resetHeight() + self.nametag3d.setPos(0, 0, self.height + 0.5) + + # show the toon geom + toonGeom = self.getGeomNode() + toonGeom.show() + + # turn emotes back on + Emote.globalEmote.releaseAll(self) + + # set the flag + self.isDisguised = 0 + + # turn our font back to the toon font + self.setFont(ToontownGlobals.getToonFont()) + self.nametag.setNameWordwrap(-1) + self.setDisplayName(self.getName()) + + # if we are local show the sticker book again and reset toon walk speeds + if self.isLocal(): + if hasattr(self, "book"): + self.book.obscureButton(0) + + # restore toon movement rates + ToontownGlobals.ToonForwardSpeed = self.oldForward + ToontownGlobals.ToonReverseSpeed = self.oldReverse + ToontownGlobals.ToonRotateSpeed = self.oldRotate + + # stop and restart to make sure these values "take" + if self.hasTrackAnimToSpeed(): + self.stopTrackAnimToSpeed() + self.startTrackAnimToSpeed() + + del(self.oldForward) + del(self.oldReverse) + del(self.oldRotate) + + # turn jump ability back on + self.controlManager.enableAvatarJump() + + # remove the cog chat phrases + self.chatMgr.chatInputSpeedChat.removeCogMenu() + + + # clean up these temp var's + self.suit.delete() + del(self.suit) + del(self.suitGeom) + + def makeWaiter(self): + # Convenience func to make disguise toon look like cog waiter + if not self.isDisguised: + return + self.suit.makeWaiter(self.suitGeom) + + def getPieModel(self): + # Returns a pie prop for the following intervals. This is + # also saved in self.pieModel. + from toontown.toonbase import ToontownBattleGlobals + from toontown.battle import BattleProps + + if self.pieModel != None and self.__pieModelType != self.pieType: + # Throw away the old pie; we need to get a new pie type. + self.pieModel.detachNode() + self.pieModel = None + + if self.pieModel == None: + self.__pieModelType = self.pieType + pieName = ToontownBattleGlobals.pieNames[self.pieType] + self.pieModel = BattleProps.globalPropPool.getProp(pieName) + + return self.pieModel + + def getPresentPieInterval(self, x, y, z, h, p, r): + # Returns an interval to show the avatar pulling out a pie and + # weighing it before tossing. See getTossPieInterval(). + from toontown.toonbase import ToontownBattleGlobals + from toontown.battle import BattleProps + from toontown.battle import MovieUtil + + pie = self.getPieModel() + pieName = ToontownBattleGlobals.pieNames[self.pieType] + pieType = BattleProps.globalPropPool.getPropType(pieName) + animPie = Sequence() + pingpongPie = Sequence() + if pieType == 'actor': + animPie = ActorInterval(pie, pieName, startFrame = 0, + endFrame = 31) + pingpongPie = Func(pie.pingpong, pieName, fromFrame=32, toFrame=47) + + track = Sequence( + Func(self.setPosHpr, x, y, z, h, p, r), + Func(pie.reparentTo, self.rightHand), + Func(pie.setPosHpr, 0, 0, 0, 0, 0, 0), + Parallel(pie.scaleInterval(1, pie.getScale(), + startScale = MovieUtil.PNT3_NEARZERO), + ActorInterval(self, 'throw', startFrame = 0, + endFrame = 31), + animPie), + Func(self.pingpong, 'throw', fromFrame=32, toFrame=47), + pingpongPie, + ) + return track + + def getTossPieInterval(self, x, y, z, h, p, r, power, + beginFlyIval = Sequence()): + # Returns (toss, pie, flyPie), where toss is an interval to + # animate the toon tossing a pie, pie is the interval to + # animate the pie flying through the air, and pieModel is the + # model that flies. This is used in the final BossBattle + # sequence of CogHQ when we all throw pies directly at the + # boss cog. + + from toontown.toonbase import ToontownBattleGlobals + from toontown.battle import BattleProps + + pie = self.getPieModel() + flyPie = pie.copyTo(NodePath('a')) + pieName = ToontownBattleGlobals.pieNames[self.pieType] + pieType = BattleProps.globalPropPool.getPropType(pieName) + animPie = Sequence() + if pieType == 'actor': + animPie = ActorInterval(pie, pieName, startFrame = 48) + + sound = loader.loadSfx('phase_3.5/audio/sfx/AA_pie_throw_only.mp3') + + # First, create a ProjectileInterval to compute the relative + # velocity. + + t = power / 100.0 + + # Distance ranges from 100 - 20 ft, time ranges from 1 - 1.5 s. + dist = 100 - 70 * t + time = 1 + 0.5 * t + proj = ProjectileInterval( + None, startPos = Point3(0, 0, 0), endPos = Point3(0, dist, 0), + duration = time) + relVel = proj.startVel + + def getVelocity(toon = self, relVel = relVel): + return render.getRelativeVector(toon, relVel) + + toss = Track( + (0, Sequence(Func(self.setPosHpr, x, y, z, h, p, r), + Func(pie.reparentTo, self.rightHand), + Func(pie.setPosHpr, 0, 0, 0, 0, 0, 0), + Parallel(ActorInterval(self, 'throw', startFrame = 48), + animPie), + Func(self.loop, 'neutral'), + )), + (16./24., Func(pie.detachNode))) + + fly = Track( + (14./24., SoundInterval(sound, node = self)), + (16./24., + Sequence(Func(flyPie.reparentTo, render), + Func(flyPie.setPosHpr, self, + 0.52, 0.97, 2.24, + 89.42, -10.56, 87.94), + beginFlyIval, + ProjectileInterval(flyPie, startVel = getVelocity, duration = 3), + Func(flyPie.detachNode), + )), + ) + return (toss, fly, flyPie) + + def getPieSplatInterval(self, x, y, z, pieCode): + # Returns an interval showing a pie hitting a target in the + # world. See getTossPieInterval(), above. + from toontown.toonbase import ToontownBattleGlobals + from toontown.battle import BattleProps + + pieName = ToontownBattleGlobals.pieNames[self.pieType] + splatName = 'splat-%s' % (pieName) + if (pieName == 'lawbook'): + #we need to create a splat-lawbook asset, while there is none, use the dust instead + splatName = 'dust' + splat = BattleProps.globalPropPool.getProp(splatName) + splat.setBillboardPointWorld(2) + + color = ToontownGlobals.PieCodeColors.get(pieCode) + if color: + splat.setColor(*color) + + vol = 1.0 + if (pieName == 'lawbook'): + sound = loader.loadSfx('phase_11/audio/sfx/LB_evidence_miss.mp3') + vol = 0.25 + else: + sound = loader.loadSfx('phase_4/audio/sfx/AA_wholepie_only.mp3') + + ival = Parallel( + Func(splat.reparentTo, render), + Func(splat.setPos, x, y, z), + SoundInterval(sound, node = splat, volume = vol), + Sequence(ActorInterval(splat, splatName), + Func(splat.detachNode)), + ) + return ival + + def cleanupPieModel(self): + # Make sure the pie model doesn't stay stuck to the Toon's + # hand. + if self.pieModel != None: + self.pieModel.detachNode() + self.pieModel = None + + def getFeedPetIval(self): + return Sequence( ActorInterval(self, "feedPet"), + Func(self.animFSM.request, "neutral")) + #return ActorInterval(self, "feedPet") + + def getScratchPetIval(self): + return Sequence( ActorInterval(self, "pet-start"), + ActorInterval(self, "pet-loop"), + ActorInterval(self, "pet-end") ) + + def getCallPetIval(self): + return ActorInterval(self, "callPet") + + + def enterGolfPuttLoop(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("loop-putt") + + def exitGolfPuttLoop(self): + self.stop() + + def enterGolfRotateLeft(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("rotateL-putt") + + def exitGolfRotateLeft(self): + self.stop() + + def enterGolfRotateRight(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("rotateR-putt") + + def exitGolfRotateRight(self): + self.stop() + + def enterGolfPuttSwing(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("swing-putt") + + def exitGolfPuttSwing(self): + self.stop() + + def enterGolfGoodPutt(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("good-putt", restart=0) + + def exitGolfGoodPutt(self): + self.stop() + + def enterGolfBadPutt(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + self.loop("badloop-putt", restart=0) + + def exitGolfBadPutt(self): + self.stop() + + def enterFlattened(self, animMultiplier=1, ts=0, callback=None, extraArgs=[]): + Emote.globalEmote.disableAll(self) + sound = loader.loadSfx('phase_9/audio/sfx/toon_decompress.mp3') + + lerpTime = .1 + node = self.getGeomNode().getChild(0) + self.origScale = node.getScale() + self.track = Sequence( + LerpScaleInterval(node, lerpTime, VBase3(2,2,.025), blendType = 'easeInOut'), + ) + self.track.start(ts) + self.setActiveShadow(1) + + def exitFlattened(self): + self.playingAnim = 'neutral' + if (self.track != None): + self.track.finish() + DelayDelete.cleanupDelayDeletes(self.track) + self.track = None + node = self.getGeomNode().getChild(0) + node.setScale(self.origScale) + Emote.globalEmote.releaseAll(self) + + def enterCogThiefRunning(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + """Enter the running state for the cog thief minigame.""" + self.playingAnim = None + self.playingRate = None + self.standWalkRunReverse = ( + ("neutral", 1.0), + ("run", 1.0), + ("run", 1.0), + ("run", -1.0) + ) + self.setSpeed(self.forwardSpeed, self.rotateSpeed) + self.setActiveShadow(1) + + def exitCogThiefRunning(self): + self.standWalkRunReverse = None + self.stop() + self.motion.exit() + + def enterScientistJealous(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + self.loop("scientistJealous") + + def exitScientistJealous(self): + self.stop() + + def enterScientistEmcee(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + #self.loop("scientistEmcee", fromFrame = 1, toFrame = 315) + self.loop("scientistEmcee") + + def exitScientistEmcee(self): + self.stop() + + def enterScientistWork(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + self.loop("scientistWork") + + def exitScientistWork(self): + self.stop() + + def enterScientistLessWork(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + self.loop("scientistWork", fromFrame = 319, toFrame=619) + + def exitScientistLessWork(self): + self.stop() + + def enterScientistPlay(self, animMultiplier=1, ts=0, + callback=None, extraArgs=[]): + self.loop("scientistGame") + if hasattr(self, 'scientistPlay'): + self.scientistPlay() + + def exitScientistPlay(self): + self.stop() + +# module calls +loadModels() +compileGlobalAnimList() + diff --git a/toontown/src/toon/ToonAvatarDetailPanel.py b/toontown/src/toon/ToonAvatarDetailPanel.py new file mode 100644 index 0000000..55f48c5 --- /dev/null +++ b/toontown/src/toon/ToonAvatarDetailPanel.py @@ -0,0 +1,429 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +import DistributedToon +from toontown.friends import FriendInviter +import ToonTeleportPanel +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil +from toontown.toonbase.ToontownBattleGlobals import Tracks, Levels + +globalAvatarDetail = None + +def showAvatarDetail(avId, avName, playerId = None): + # A module function to open the global avatar detail panel. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + + + playerId = base.cr.playerFriendsManager.findPlayerIdFromAvId(avId) + + globalAvatarDetail = ToonAvatarDetailPanel(avId, avName, playerId) + +def hideAvatarDetail(): + # A module function to close the global avatar detail if it is open. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + +def unloadAvatarDetail(): + # A module function to completely unload the global friend + # inviter. This is the same thing as hideAvatarDetail, actually. + global globalAvatarDetail + if globalAvatarDetail != None: + globalAvatarDetail.cleanup() + globalAvatarDetail = None + +class ToonAvatarDetailPanel(DirectFrame): + """ + This is a panel that pops up in response to clicking the "Details" + button on the AvatarPanel. It displays more details about the + particular avatar. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ToonAvatarDetailPanel") + + def __init__(self, avId, avName, playerId = None, parent = aspect2dp, **kw): + # Inherits from DirectFrame + # Must specify avId and avName on creation + print("ToonAvatarDetailPanel %s" % (playerId)) + + # Load required models + buttons = loader.loadModel( + 'phase_3/models/gui/dialog_box_buttons_gui') + gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') + detailPanel = gui.find('**/avatarInfoPanel') + self.playerId = playerId + textScale = 0.095 + textWrap = 16.4 + self.playerInfo = None + if self.playerId: + #textScale = 0.100 + #textWrap = 18.0 + #self.isPlayer = 1 + self.playerInfo = base.cr.playerFriendsManager.playerId2Info.get(playerId) + # Specify default options + optiondefs = ( + ('pos', (0.525, 0.0, 0.525), None), + ('scale', 0.5, None), + ('relief', None, None), + ('image', detailPanel, None), + ('image_color', GlobalDialogColor, None), + ('text', '', None), + ('text_wordwrap', textWrap, None), + ('text_scale', textScale, None), + ('text_pos', (-0.125, 0.775), None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + + # initialize our base class. + DirectFrame.__init__(self, parent) + + # Information about avatar + self.dataText = DirectLabel(self, + text = '', + text_scale = 0.09, + text_align = TextNode.ALeft, + text_wordwrap = 15, + relief = None, + pos = (-0.85, 0.0, 0.645), + ) + + self.avId = avId + self.avName = avName + self.avatar = None + self.createdAvatar = None + + self.fsm = ClassicFSM.ClassicFSM('ToonAvatarDetailPanel', + [State.State('off', + self.enterOff, + self.exitOff, + ['begin']), + State.State('begin', + self.enterBegin, + self.exitBegin, + ['query', 'data', 'off']), + State.State('query', + self.enterQuery, + self.exitQuery, + ['data', 'invalid', 'off']), + State.State('data', + self.enterData, + self.exitData, + ['off']), + State.State('invalid', + self.enterInvalid, + self.exitInvalid, + ['off'])], + # Initial State + 'off', + # Final State + 'off', + ) + + ToonTeleportPanel.hideTeleportPanel() + FriendInviter.hideFriendInviter() + + # Create some buttons. + self.bCancel = DirectButton( + self, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + image_scale = 1.1, + relief = None, + text = TTLocalizer.AvatarDetailPanelCancel, + text_scale = TTLocalizer.TADPcancelButton, + text_pos = (0.12, -0.01), + pos = TTLocalizer.TADPcancelPos, + scale = 2.0, + command = self.__handleCancel) + self.bCancel.hide() + + # Call option initialization functions + self.initialiseoptions(ToonAvatarDetailPanel) + + # Initialize ClassicFSM + self.fsm.enterInitialState() + self.fsm.request('begin') + + # Clean up + buttons.removeNode() + gui.removeNode() + + def cleanup(self): + """cleanup(self): + + Cancels any pending request and removes the panel from the + screen. + + """ + if self.fsm: + self.fsm.request('off') + self.fsm = None + # Remove any pending detail requests + base.cr.cancelAvatarDetailsRequest(self.avatar) + if self.createdAvatar: + self.avatar.delete() + self.createdAvatar = None + + self.destroy() + + ##### Off state ##### + + # Represents the initial state of the detail query: no query in + # progress. + + def enterOff(self): + pass + + def exitOff(self): + pass + + ##### Begin state ##### + + # We have clicked on the "details" button from the Avatar panel. + # Start the ball rolling. + + def enterBegin(self): + myId = base.localAvatar.doId + + # Update name label + self['text'] = self.avName + + if (self.avId == myId): + self.avatar = base.localAvatar + self.createdAvatar = 0 + self.fsm.request('data') + else: + self.fsm.request('query') + + def exitBegin(self): + pass + + ##### Query state ##### + + # We are waiting for detailed information on the avatar to return + # from the server. + + def enterQuery(self): + self.dataText['text'] = ( + TTLocalizer.AvatarDetailPanelLookup % (self.avName)) + self.bCancel.show() + + # We need to get a DistributedObject handle for the indicated + # avatar. Maybe we have one already, if the avatar is + # somewhere nearby. + self.avatar = base.cr.doId2do.get(self.avId) + if self.avatar != None and not self.avatar.ghostMode: + self.createdAvatar = 0 + else: + # Otherwise, we have to make one up just to hold the + # detail query response. This is less than stellar, + # because it means we'll do a lot of extra work we don't + # need (like loading up models and binding animations, + # etc.), but it's not *too* horrible. + self.avatar = DistributedToon.DistributedToon(base.cr) + self.createdAvatar = 1 + self.avatar.doId = self.avId + # getAvatarDetails puts a DelayDelete on the avatar, and this + # is not a real DO, so bypass the 'generated' check + self.avatar.forceAllowDelayDelete() + + # Now ask the server to tell us more about this avatar. + base.cr.getAvatarDetails(self.avatar, self.__handleAvatarDetails, "DistributedToon") + + def exitQuery(self): + self.bCancel.hide() + + ##### Data state ##### + + # We have detailed information now available in self.avatar. + + def enterData(self): + self.bCancel['text'] = TTLocalizer.AvatarDetailPanelClose + self.bCancel.show() + self.__showData() + + def exitData(self): + self.bCancel.hide() + + ##### Invalid state ##### + + # For some reason, the server was unable to return data on the + # avatar in question. + + def enterInvalid(self): + self.dataText['text'] = ( + TTLocalizer.AvatarDetailPanelFailedLookup % (self.avName)) + + def exitInvalid(self): + self.bCancel.hide() + + ### Button handing methods + def __handleCancel(self): + unloadAvatarDetail() + + + ### Support methods + + def __handleAvatarDetails(self, gotData, avatar, dclass): + if ((not self.fsm) or (avatar != self.avatar)): + # This may be a query response coming back from a previous + # request. Ignore it. + self.notify.warning("Ignoring unexpected request for avatar %s" % (avatar.doId)) + return + + if gotData: + # We got a valid response. + self.fsm.request('data') + else: + # No information available about the avatar. This is an + # unexpected error condition, but we go out of our way to + # handle it gracefully. + self.fsm.request('invalid') + + def __showData(self): + av = self.avatar + online = 1 + if base.cr.isFriend(self.avId): + online = base.cr.isFriendOnline(self.avId) + + if online: + shardName = base.cr.getShardName(av.defaultShard) + hoodName = base.cr.hoodMgr.getFullnameFromId(av.lastHood) + if ZoneUtil.isWelcomeValley(av.lastHood): + shardName = "%s (%s)" % (TTLocalizer.WelcomeValley[-1], shardName) + if self.playerInfo: + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + self.gotoAvatarButton = DirectButton( + parent = self, + relief = None, + image = (guiButton.find("**/QuitBtn_UP"), + guiButton.find("**/QuitBtn_DN"), + guiButton.find("**/QuitBtn_RLVR"), + ), + image_scale = 1.1, + text = TTLocalizer.AvatarShowPlayer, + text_scale = 0.07, + text_pos = (0.0, -0.02), + textMayChange = 0, + pos = (0.44, 0, 0.41), + command = self.__showAvatar, + ) + + text = (TTLocalizer.AvatarDetailPanelOnlinePlayer % + {"district": shardName, "location": hoodName, "player" : self.playerInfo.playerName}) + else: + text = (TTLocalizer.AvatarDetailPanelOnline % + {"district": shardName, "location": hoodName}) + + else: + text = TTLocalizer.AvatarDetailPanelOffline + self.dataText['text'] = text + + self.__updateTrackInfo() + self.__updateTrophyInfo() + self.__updateLaffInfo() + + def __showAvatar(self): + messenger.send('wakeup') + # Picking a friend from your friends list has exactly the same + # effect as clicking on his or her name in the world. + hasManager = hasattr(base.cr, "playerFriendsManager") + handle = base.cr.identifyFriend(self.avId) + if not handle and hasManager: + handle = base.cr.playerFriendsManager.getAvHandleFromId(self.avId) + if handle != None: + self.notify.info("Clicked on name in friend's list. doId = %s" % handle.doId) + messenger.send("clickedNametagPlayer", [handle, self.playerId, 1]) + + def __updateLaffInfo(self): + # Send a message to force the avatar panel to display the laff meter + avatar = self.avatar + messenger.send('updateLaffMeter', + [avatar, avatar.hp, avatar.maxHp]) + + def __updateTrackInfo(self): + xOffset = -0.501814 + xSpacing = 0.1835 + yOffset = 0.1 + ySpacing = -0.115 + inventory = self.avatar.inventory + inventoryModels = loader.loadModel( + "phase_3.5/models/gui/inventory_gui") + buttonModel = inventoryModels.find("**/InventoryButtonUp") + for track in range(0, len(Tracks)): + # Track Label + DirectLabel(parent = self, + relief = None, + text = TextEncoder.upper(TTLocalizer.BattleGlobalTracks[track]), + text_scale = TTLocalizer.TADPtrackLabel, + text_align = TextNode.ALeft, + pos = (-0.90, 0, TTLocalizer.TADtrackLabelPosZ + track * ySpacing) + ) + # Fill in track if avatar has access + if self.avatar.hasTrackAccess(track): + curExp, nextExp = inventory.getCurAndNextExpValues(track) + for item in range(0, len(Levels[track])): + level = Levels[track][item] + if curExp >= level: + numItems = inventory.numItem(track,item) + if numItems == 0: + image_color = Vec4(0.5,0.5,0.5,1) + geom_color = Vec4(0.2,0.2,0.2,0.5) + else: + image_color = Vec4(0,0.6,1,1) + geom_color = None + DirectLabel( + parent = self, + image = buttonModel, + image_scale = (0.92,1,1), + image_color = image_color, + geom = inventory.invModels[track][item], + geom_color = geom_color, + geom_scale = 0.6, + relief = None, + pos = (xOffset + item * xSpacing, + 0, + yOffset + track * ySpacing), + ) + else: + break + + def __updateTrophyInfo(self): + # For now we don't know how many buildings a created avatar has + if self.createdAvatar: + return + if (self.avatar.trophyScore >= TrophyStarLevels[2]): + # A gold star! + color = TrophyStarColors[2] + elif self.avatar.trophyScore >= TrophyStarLevels[1]: + # A silver star! + color = TrophyStarColors[1] + elif self.avatar.trophyScore >= TrophyStarLevels[0]: + # A bronze star. + color = TrophyStarColors[0] + else: + color = None + + if color: + gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') + star = gui.find('**/avatarStar') + self.star = DirectLabel( + parent = self, + image = star, + image_color = color, + pos = (0.610165, 0, -0.760678), + scale = 0.9, + relief = None) + gui.removeNode() + diff --git a/toontown/src/toon/ToonAvatarPanel.py b/toontown/src/toon/ToonAvatarPanel.py new file mode 100644 index 0000000..294975c --- /dev/null +++ b/toontown/src/toon/ToonAvatarPanel.py @@ -0,0 +1,718 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +import ToonHead +from toontown.friends import FriendHandle +import LaffMeter +from otp.avatar import Avatar +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.friends import ToontownFriendSecret +import ToonAvatarDetailPanel +import AvatarPanelBase +from toontown.toontowngui import TTDialog +from otp.otpbase import OTPGlobals + +class ToonAvatarPanel(AvatarPanelBase.AvatarPanelBase): + notify = DirectNotifyGlobal.directNotify.newCategory('ToonAvatarPanel') + + """ + This is a panel that pops up in response to clicking on a Toon or + Cog nearby you, or to picking a Toon from your friends list. It + draws a little picture of the avatar's head, and gives you a few + options to pick from re the avatar. + """ + + def __init__(self, avatar, playerId = None): + from toontown.friends import FriendsListPanel + # the avatar might just be a handle if the avatar also exists in our cr + # we use the cr version over the handle version + if base.cr.doId2do.get(avatar.getDoId()): + avatar = base.cr.doId2do.get(avatar.getDoId()) + AvatarPanelBase.AvatarPanelBase.__init__( + self, avatar, FriendsListPanel = FriendsListPanel) + + if __debug__: + base.avPanel = self + + self.notify.debug("Opening toon panel, avId=%d" % self.avId) + self.playerId = playerId + + # if we don't have it, look up the DISL id + if not self.playerId: + av = base.cr.doId2do.get(self.avId) + if av: + self.playerId = avatar.DISLid + else: + self.playerId = 0 + + self.playerInfo = None + if self.playerId: + self.playerInfo = base.cr.playerFriendsManager.playerId2Info.get(playerId) + + self.laffMeter = None + wantsLaffMeter = hasattr(avatar, "hp") + + if not hasattr(avatar, "style"): + # for some reason, this avatar is not in a good state. + # Abort intialization and do some cleanup. + self.notify.warning("Avatar has no 'style'. Abort initialization.") + AvatarPanelBase.AvatarPanelBase.cleanup(self) + return + + base.localAvatar.obscureFriendsListButton(1) + + gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui") + self.frame = DirectFrame( + image = gui.find("**/avatar_panel"), + relief = None, + pos = (1.1, 100, 0.525), + ) + + self.disabledImageColor = Vec4(1,1,1,0.4) + self.text0Color = Vec4(1,1,1,1) + self.text1Color = Vec4(0.5,1,0.5,1) + self.text2Color = Vec4(1,1,0.5,1) + self.text3Color = Vec4(0.6,0.6,0.6,1) + + # First, put the avatar's head in the panel. We do this + # first so it ends up behind all of the text. + self.head = self.frame.attachNewNode('head') + self.head.setPos(0.02, 0, 0.31) + self.headModel = ToonHead.ToonHead() + self.headModel.setupHead(avatar.style, forGui = 1) + self.headModel.fitAndCenterHead(0.175, forGui = 1) + self.headModel.reparentTo(self.head) + + # Start blinking and looking around. + self.headModel.startBlink() + self.headModel.startLookAround() + + self.healthText = DirectLabel( + parent = self.frame, + text = "", + pos = (0.06, 0, 0.2), + text_pos = (0,0), + text_scale = 0.05, + ) + self.healthText.hide() + + # Put the avatar's name across the top. + self.nameLabel = DirectLabel( + parent = self.frame, + pos = (0.0125, 0, 0.4), + relief = None, + text = self.avName, + text_font = avatar.getFont(), + text_fg = Vec4(0,0,0,1), + text_pos = (0, 0), + text_scale = 0.042, + text_wordwrap = 7.5, + text_shadow = (1, 1, 1, 1), + ) + + self.closeButton = DirectButton( + parent = self.frame, + image = (gui.find("**/CloseBtn_UP"), + gui.find("**/CloseBtn_DN"), + gui.find("**/CloseBtn_Rllvr"), + gui.find("**/CloseBtn_UP"), + ), + relief = None, + pos = (0.157644, 0, -0.379167), + command = self.__handleClose, + ) + + self.friendButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Frnds_Btn_UP"), + gui.find("**/Frnds_Btn_DN"), + gui.find("**/Frnds_Btn_RLVR"), + gui.find("**/Frnds_Btn_UP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.90, + relief = None, + text = TTLocalizer.AvatarPanelFriends, + text_scale = 0.06, + pos = (-0.103, 0, 0.133), + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_pos = (0.06, -0.02), + text_align = TextNode.ALeft, + command = self.__handleFriend, + ) + + # The friends button is diabled for transient (player only) friends that are not in our cr + if base.cr.playerFriendsManager.askTransientFriend(self.avId) and not base.cr.doId2do.has_key(self.avId): + self.friendButton['state'] = DGG.DISABLED + + # The friends button is also disabled for people we are ignoring + if base.cr.avatarFriendsManager.checkIgnored(self.avId): + self.friendButton['state'] = DGG.DISABLED + + self.goToButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Go2_Btn_UP"), + gui.find("**/Go2_Btn_DN"), + gui.find("**/Go2_Btn_RLVR"), + gui.find("**/Go2_Btn_UP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.90, + relief = None, + pos = (-0.103, 0, 0.045), + text = TTLocalizer.AvatarPanelGoTo, + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = 0.06, + text_pos = (0.06, -0.015), + text_align = TextNode.ALeft, + command = self.__handleGoto, + ) + + # The goTo button is also disabled for people we are ignoring + if base.cr.avatarFriendsManager.checkIgnored(self.avId): + self.goToButton['state'] = DGG.DISABLED + + self.whisperButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ChtBx_ChtBtn_UP"), + gui.find("**/ChtBx_ChtBtn_DN"), + gui.find("**/ChtBx_ChtBtn_RLVR"), + gui.find("**/ChtBx_ChtBtn_UP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.90, + relief = None, + pos = (-0.103, 0, -0.0375), + text = TTLocalizer.AvatarPanelWhisper, + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = TTLocalizer.TAPwisperButtonScale, + text_pos = (0.06, -0.0125), + text_align = TextNode.ALeft, + command = self.__handleWhisper, + ) + + # The whisper button is also disabled for people we are ignoring + if base.cr.avatarFriendsManager.checkIgnored(self.avId): + self.whisperButton['state'] = DGG.DISABLED + + self.secretsButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Amuse_Btn_UP"), + gui.find("**/Amuse_Btn_DN"), + gui.find("**/Amuse_Btn_RLVR"), + gui.find("**/Amuse_Btn_UP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.90, + relief = None, + pos = (-0.103, 0, -0.13), + text = TTLocalizer.AvatarPanelSecrets, + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = TTLocalizer.TAPsecretsButtonScale, + text_pos = (0.055, -0.01), + text_align = TextNode.ALeft, + command = self.__handleSecrets, + ) + + # The secrets button is also disabled for people we are ignoring + if base.cr.avatarFriendsManager.checkIgnored(self.avId): + self.secretsButton['state'] = DGG.DISABLED + + # ignore or stop ignoring? (see AvatarPanelBase.py) + ignoreStr, ignoreCmd, ignoreScale = self.getIgnoreButtonInfo() + + self.ignoreButton = DirectButton( + parent = self.frame, + image = (gui.find("**/Ignore_Btn_UP"), + gui.find("**/Ignore_Btn_DN"), + gui.find("**/Ignore_Btn_RLVR"), + gui.find("**/Ignore_Btn_UP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.9, + relief = None, + pos = (-0.103697, 0, -0.21), + text = ignoreStr, + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = ignoreScale, + text_pos = (0.06,-0.015), + text_align = TextNode.ALeft, + command = ignoreCmd, + ) + + # disabled for all intl realms + if not base.cr.productName in ['JP', 'DE', 'BR', 'FR'] : + self.reportButton = DirectButton( + parent = self.frame, + image = (gui.find("**/report_BtnUP"), + gui.find("**/report_BtnDN"), + gui.find("**/report_BtnRLVR"), + gui.find("**/report_BtnUP"), + ), + image3_color = self.disabledImageColor, + image_scale = 0.65, + relief = None, + pos = (-0.103, 0, -0.29738), + text = TTLocalizer.AvatarPanelReport, + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = 0.06, + text_pos = (0.06, -0.015), + text_align = TextNode.ALeft, + command = self.handleReport, + ) + + if not base.localAvatar.isTeleportAllowed(): + # Can't teleport to a friend while we're wearing our + # cog suit or in certain other states. + self.goToButton['state'] = DGG.DISABLED + + self.detailButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ChtBx_BackBtn_UP"), + gui.find("**/ChtBx_BackBtn_DN"), + gui.find("**/ChtBx_BackBtn_Rllvr"), + gui.find("**/ChtBx_BackBtn_UP"), + ), + relief = None, + text = ("", TTLocalizer.AvatarPanelDetail, + TTLocalizer.AvatarPanelDetail, ""), + text_fg = self.text2Color, + text_shadow = (0, 0, 0, 1), + text_scale = 0.055, + text_pos = (-0.075, -0.01), + text_align = TextNode.ARight, + pos = (-0.133773, 0, -0.395), + command = self.__handleDetails, + ) + + self.__makeBoardingGui() + self.__makePetGui(avatar) + + self.__checkGroupStatus() + + gui.removeNode() + + + # We must check to make sure we have an actual avatar before + # we try to create a LaffMeter. This is not the same thing as + # isToon, because we might have been given a FriendHandle, + # which is a kind of Toon but is not an Avatar (and it doesn't + # have hit points). + if wantsLaffMeter: + self.__makeLaffMeter(avatar) + self.__updateHp(avatar.hp, avatar.maxHp) + self.healthText.show() + self.laffMeter.show() + + menuX = -0.05 + menuScale = 0.064 + + if self.avGenerateName: + self.accept(self.avGenerateName, self.__handleGenerateAvatar) + if self.avHpChangeName: + self.accept(self.avHpChangeName, self.__updateHp) + self.accept('updateLaffMeter', self.__updateLaffMeter) + + self.accept('updateGroupStatus', self.__checkGroupStatus) + + self.frame.show() + messenger.send("avPanelDone") + + def disableAll(self): + self.detailButton['state'] = DGG.DISABLED + # disabled for all intl realms + if not base.cr.productName in ['ES', 'JP', 'DE', 'BR', 'FR'] : + self.reportButton['state'] = DGG.DISABLED + self.ignoreButton['state'] = DGG.DISABLED + self.goToButton['state'] = DGG.DISABLED + self.secretsButton['state'] = DGG.DISABLED + self.whisperButton['state'] = DGG.DISABLED + self.petButton['state'] = DGG.DISABLED + self.friendButton['state'] = DGG.DISABLED + self.closeButton['state'] = DGG.DISABLED + self.groupButton['state'] = DGG.DISABLED + self.boardingInfoButton['state'] = DGG.DISABLED + + def cleanup(self): + if not hasattr(self, "frame") or (self.frame == None): + return + + self.notify.debug("Clean up toon panel, avId=%d" % self.avId) + + if self.frame: + self.frame.destroy() + del self.frame + self.frame = None + + # Make sure avatar detail panel is put away + ToonAvatarDetailPanel.unloadAvatarDetail() + + if self.groupButton: + self.groupButton.destroy() + del self.groupButton + self.groupButton = None + + if self.boardingInfoButton: + self.boardingInfoButton.destroy() + del self.boardingInfoButton + self.boardingInfoButton = None + + if self.boardingInfoText: + self.boardingInfoText.destroy() + del self.boardingInfoText + self.boardingInfoText = None + + if self.groupFrame: + self.groupFrame.destroy() + del self.groupFrame + self.groupFrame = None + + self.head.removeNode() + del self.head + + self.headModel.stopBlink() + self.headModel.stopLookAroundNow() + self.headModel.delete() + del self.headModel + base.localAvatar.obscureFriendsListButton(-1) + + # Note: this gets destroyed because it is a child of the frame + self.laffMeter = None + + self.ignore('updateLaffMeter') + self.ignoreAll() + + if hasattr(self.avatar, "bFake") and self.avatar.bFake: + self.avatar.delete() + + # The right middle cell was switched off because of avatar boarding panel got in the way. + # Switching it back ON when we close the ToonAvatarPanel. + base.setCellsAvailable([base.rightCells[0]], 1) + + AvatarPanelBase.AvatarPanelBase.cleanup(self) + return + + def __handleGoto(self): + if base.localAvatar.isTeleportAllowed(): + base.localAvatar.chatMgr.noWhisper() + messenger.send("gotoAvatar", [self.avId, self.avName, + self.avDisableName]) + + def __handleToPet(self): + toonAvatar = self.avatar + # if we have the friend in our cr it's more up to date and we should get the pet from that. + if base.cr.doId2do.get(toonAvatar.getDoId()): + toonAvatar = base.cr.doId2do.get(toonAvatar.getDoId()) + petAvatar = base.cr.doId2do.get(toonAvatar.getPetId()) + + self.disableAll() + + from toontown.pets import PetDetail + PetDetail.PetDetail(toonAvatar.getPetId(), self.__petDetailsLoaded) + + + def __petDetailsLoaded(self, avatar): + self.cleanup() + if avatar: + self.notify.debug("Looking at someone's pet. pet doId = %s" % avatar.doId) + messenger.send("clickedNametag", [avatar]) + + def __handleWhisper(self): + # switch these lines to reroute whispers through the player if possible + #base.localAvatar.chatMgr.whisperTo(self.avName, self.avId, self.playerId) + base.localAvatar.chatMgr.whisperTo(self.avName, self.avId, None) + + def __handleSecrets(self): + base.localAvatar.chatMgr.noWhisper() + ToontownFriendSecret.showFriendSecret(ToontownFriendSecret.AvatarSecret) #grep keywords: BothSecrets AvatarSecret AccountSecret secretType switchboard + + def __handleFriend(self): + base.localAvatar.chatMgr.noWhisper() + messenger.send("friendAvatar", [self.avId, self.avName, + self.avDisableName]) + + def __handleDetails(self): + base.localAvatar.chatMgr.noWhisper() + messenger.send("avatarDetails", [self.avId, self.avName, self.playerId]) + + def __handleDisableAvatar(self): + """ + Called whenever an avatar is disabled, this should cleanup the + avatar panel if it's not a friend. + """ + if not base.cr.isFriend(self.avId): + # If the avatar wandered away (or disconnected), and he + # wasn't a friend, shut down the panel. + self.cleanup() + AvatarPanelBase.currentAvatarPanel = None + else: + # If, on the other hand, he *was* a friend, keep the panel + # up but hide the LaffMater. + self.healthText.hide() + if self.laffMeter != None: + self.laffMeter.stop() + self.laffMeter.destroy() + self.laffMeter = None + + def __handleGenerateAvatar(self, avatar): + """ + Called whenever an avatar is generated (which can only happen + if a friend wanders near), this should reveal the LaffMeter. + """ + newAvatar = base.cr.doId2do.get(self.avatar.doId) + if newAvatar: #replace friend handle with full avatar + self.avatar = newAvatar + self.__updateLaffMeter(avatar, avatar.hp, avatar.maxHp) + self.__checkGroupStatus() + + def __updateLaffMeter(self, avatar, hp, maxHp): + if self.laffMeter == None: + self.__makeLaffMeter(avatar) + self.__updateHp(avatar.hp, avatar.maxHp) + self.laffMeter.show() + self.healthText.show() + + def __makeLaffMeter(self, avatar): + # Create a mini-LaffMeter within the panel, if we know what + # the avatar's health is. We'll only know this if the avatar + # was in our visibility zone when we created the panel. + self.laffMeter = LaffMeter.LaffMeter(avatar.style, avatar.hp, avatar.maxHp) + self.laffMeter.reparentTo(self.frame) + self.laffMeter.setPos(-0.1, 0, 0.24) + self.laffMeter.setScale(0.03) + + + def __updateHp(self, hp, maxHp, quietly=0): + if self.laffMeter != None and hp != None and maxHp != None: + self.laffMeter.adjustFace(hp, maxHp) + self.healthText['text'] = '%d / %d' % (hp, maxHp) + + def __handleClose(self): + self.cleanup() + AvatarPanelBase.currentAvatarPanel = None + if self.friendsListShown: + # Restore the friends list if it was up before. + self.FriendsListPanel.showFriendsList() + + def getAvId(self): + if hasattr(self, "avatar"): + if self.avatar: + return self.avatar.doId + return None + + def getPlayerId(self): + if hasattr(self, "playerId"): + return self.playerId + return None + + def isHidden(self): + if not hasattr(self, "frame") or not self.frame: + return 1 + return self.frame.isHidden() + + def getType(self): + return "toon" + + def handleInvite(self): + # Show an error message if there is a pending invite + if localAvatar.boardingParty.isInviteePanelUp(): + localAvatar.boardingParty.showMe(TTLocalizer.BoardingPendingInvite, pos = (0, 0, 0)) + else: + self.groupButton['state'] = DGG.DISABLED + localAvatar.boardingParty.requestInvite(self.avId) + + def handleKick(self): + # Show the confirm dialog only if the toon is not already in the elevator. + if not (base.cr.playGame.getPlace().getState() == 'elevator'): + self.confirmKickOutDialog = TTDialog.TTDialog( + style = TTDialog.YesNo, + text = TTLocalizer.BoardingKickOutConfirm %self.avName, + command = self.__confirmKickOutCallback, + ) + self.confirmKickOutDialog.show() + + def __confirmKickOutCallback(self, value): + if self.confirmKickOutDialog: + self.confirmKickOutDialog.destroy() + self.confirmKickOutDialog = None + if value > 0: + self.groupButton['state'] = DGG.DISABLED + localAvatar.boardingParty.requestKick(self.avId) + + def __checkGroupStatus(self): + self.groupFrame.hide() + if hasattr(self, "avatar"): + if self.avatar and (hasattr(self.avatar, "getZoneId")) and (localAvatar.getZoneId() == self.avatar.getZoneId()): + if localAvatar.boardingParty: + # If avId is there is my boarding party + if self.avId in localAvatar.boardingParty.getGroupMemberList(localAvatar.doId): + # If I am the leader + if localAvatar.boardingParty.getGroupLeader(localAvatar.doId) == localAvatar.doId: + # Show "Kick Out" button. + self.groupButton['text'] = ("", TTLocalizer.AvatarPanelGroupMemberKick, TTLocalizer.AvatarPanelGroupMemberKick) + self.groupButton['image'] = self.kickOutImageList + self.groupButton['command'] = self.handleKick + self.groupButton['state'] = DGG.NORMAL + else: + # Show "Already in Group" button. + self.groupButton['text'] = ("", TTLocalizer.AvatarPanelGroupMember, TTLocalizer.AvatarPanelGroupMember) + self.groupButton['command'] = None + self.groupButton['image'] = self.inviteImageDisabled + self.groupButton['image_color'] = Vec4(1, 1, 1, 0.4) + self.groupButton['state'] = DGG.NORMAL + # If avId is not there in my boarding party + else: + self.groupButton['text'] = ("", TTLocalizer.AvatarPanelGroupInvite, TTLocalizer.AvatarPanelGroupInvite) + self.groupButton['command'] = self.handleInvite + self.groupButton['image'] = self.inviteImageList + self.groupButton['state'] = DGG.NORMAL + if base.config.GetBool("want-boarding-groups", 1): + # The boarding avatar panel interferes with the right middle sreen area, + # Switching it OFF before showing the boarding panel. + base.setCellsAvailable([base.rightCells[0]], 0) + self.groupFrame.show() + + def handleReadInfo(self, task = None): + self.boardingInfoButton['state'] = DGG.DISABLED + if self.boardingInfoText: + self.boardingInfoText.destroy() + self.boardingInfoText = TTDialog.TTDialog( + style = TTDialog.Acknowledge, + text = (TTLocalizer.BoardingPartyInform % (localAvatar.boardingParty.maxSize)), + command = self.handleCloseInfo + ) + + def handleCloseInfo(self, *extraArgs): + self.boardingInfoButton['state'] = DGG.NORMAL + if self.boardingInfoText: + self.boardingInfoText.destroy() + del self.boardingInfoText + self.boardingInfoText = None + + def __makePetGui(self, avatar): + ''' + This function makes the Show Doodle button. + ''' + petGui = loader.loadModel("phase_3.5/models/gui/PetControlPannel") + self.petButton = DirectButton( + parent = self.frame, + image = (petGui.find("**/PetControlToonButtonUp1"), + petGui.find("**/PetControlToonButtonDown1"), + petGui.find("**/PetControlToonButtonRollover1"), + ), + geom = petGui.find("**/PetBattleIcon"), + geom3_color = self.disabledImageColor, + relief = None, + pos = (0.22, -0.2, -0.475), + text = ("", TTLocalizer.AvatarPanelPet, TTLocalizer.AvatarPanelPet, ""), + text_fg = self.text2Color, + ##text_bg = Vec4(0,0,0,1), + text_shadow = (0, 0, 0, 1), + text_scale = 0.325, + text_pos = (-1.3, 0.05), + text_align = TextNode.ACenter, + command = self.__handleToPet, + ) + self.petButton.setScale(0.15) + + if not (base.wantPets and avatar.hasPet()): + self.petButton['state'] = DGG.DISABLED + self.petButton.hide() + petGui.removeNode() + + def __makeBoardingGui(self): + ''' + This function makes all the elements of the boarding gui. + ''' + self.confirmKickOutDialog = None + groupAvatarBgGui = loader.loadModel("phase_3.5/models/gui/tt_m_gui_brd_avatarPanelBg") + boardingGroupBGImage = groupAvatarBgGui.find('**/tt_t_gui_brd_avatar_panel_party') + self.groupFrame = DirectFrame( + parent = self.frame, + relief = None, + image = boardingGroupBGImage, + image_scale = (0.5, 1, 0.5), + textMayChange = 1, + text = TTLocalizer.BoardingPartyTitle, + text_wordwrap = 16, + text_scale = TTLocalizer.TAPgroupFrameScale, + text_pos = (0.01, 0.08), + pos = (0, 0, -0.61), + ) + + groupInviteGui = loader.loadModel("phase_3.5/models/gui/tt_m_gui_brd_inviteButton") + self.inviteImageList = (groupInviteGui.find('**/tt_t_gui_brd_inviteUp'), + groupInviteGui.find('**/tt_t_gui_brd_inviteDown'), + groupInviteGui.find('**/tt_t_gui_brd_inviteHover'), + groupInviteGui.find('**/tt_t_gui_brd_inviteUp')) + self.kickOutImageList = (groupInviteGui.find('**/tt_t_gui_brd_kickoutUp'), + groupInviteGui.find('**/tt_t_gui_brd_kickoutDown'), + groupInviteGui.find('**/tt_t_gui_brd_kickoutHover'), + groupInviteGui.find('**/tt_t_gui_brd_kickoutUp')) + self.inviteImageDisabled = groupInviteGui.find('**/tt_t_gui_brd_inviteDisabled') + self.groupButton = DirectButton( + parent = self.groupFrame, + image = self.inviteImageList, + image3_color = self.disabledImageColor, + image_scale = 0.85, + relief = None, + text = ("", TTLocalizer.AvatarPanelGroupInvite, TTLocalizer.AvatarPanelGroupInvite), + text0_fg = self.text0Color, + text1_fg = self.text1Color, + text2_fg = self.text2Color, + text3_fg = self.text3Color, + text_scale = TTLocalizer.TAPgroupButtonScale, + text_pos = (-0.0, -0.1), + text_align = TextNode.ACenter, + command = self.handleInvite, + pos = (0.01013, 0, -0.05464), + ) + + helpGui = loader.loadModel('phase_3.5/models/gui/tt_m_gui_brd_help') + helpImageList = (helpGui.find('**/tt_t_gui_brd_helpUp'), + helpGui.find('**/tt_t_gui_brd_helpDown'), + helpGui.find('**/tt_t_gui_brd_helpHover'), + helpGui.find('**/tt_t_gui_brd_helpDown'),) + + self.boardingInfoButton = DirectButton( + parent = self.groupFrame, + relief = None, + text_pos = (-0.05, 0.05), + text_scale = 0.06, + text_align = TextNode.ALeft, + text_fg = Vec4(1,1,1,1), + text_shadow = Vec4(0,0,0,1), + image = helpImageList, + image_scale = (0.5, 1, 0.5), + image3_color = self.disabledImageColor, + scale = 1.05, + command = self.handleReadInfo, + pos = (0.1829, 0, 0.02405), + ) + self.boardingInfoText = None + + groupInviteGui.removeNode() + groupAvatarBgGui.removeNode() + helpGui.removeNode() \ No newline at end of file diff --git a/toontown/src/toon/ToonDNA.py b/toontown/src/toon/ToonDNA.py new file mode 100644 index 0000000..c64fd12 --- /dev/null +++ b/toontown/src/toon/ToonDNA.py @@ -0,0 +1,1958 @@ +"""ToonDNA module: contains the methods and definitions for describing +multipart actors with a simple class""" + +import random +from pandac.PandaModules import * +from direct.directnotify.DirectNotifyGlobal import * +import random +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from otp.avatar import AvatarDNA + +notify = directNotify.newCategory("ToonDNA") + +# Warning! DNA values are stored in the server database by indexing +# into these arrays. Do not reorder entries in this array, or add +# things to the middle, or all toons already stored in the database +# will get screwed up. + +toonSpeciesTypes = ['d', # Dog + 'c', # Cat + 'h', # Horse + 'm', # Mouse + 'r', # Rabbit + 'f', # Duck + 'p', # Monkey + 'b', # Bear + 's' # Pig (swine) + ] + +toonHeadTypes = [ "dls", "dss", "dsl", "dll", # Dog + "cls", "css", "csl", "cll", # Cat + "hls", "hss", "hsl", "hll", # Horse + "mls", "mss", # Mouse + "rls", "rss", "rsl", "rll", # Rabbit + "fls", "fss", "fsl", "fll", # Duck (Fowl) + "pls", "pss", "psl", "pll", # Monkey (Primate) + "bls", "bss", "bsl", "bll", # Bear + "sls", "sss", "ssl", "sll" # Pig (swine) + ] + +def getHeadList(species): + """ + Returns a list of head types given the species. + This list returned is a subset of toonHeadTypes pertaining to only that species. + """ + headList = [] + for head in toonHeadTypes: + if (head[0] == species): + headList.append(head) + return headList + +def getHeadStartIndex(species): + """ + Returns an index of toonHeadTypes which corresponds to the start of + the given species. + """ + for head in toonHeadTypes: + if (head[0] == species): + return toonHeadTypes.index(head) + +def getSpecies(head): + """ + Returns the species when the head is given. + """ + for species in toonSpeciesTypes: + if (species == head[0]): + return species + +def getSpeciesName(head): + """ + Returns the full name of the species in small letters + given the head of the species. + """ + species = getSpecies(head) + if (species == 'd'): + speciesName = 'dog' + elif (species == 'c'): + speciesName = 'cat' + elif (species == 'h'): + speciesName = 'horse' + elif (species == 'm'): + speciesName = 'mouse' + elif (species == 'r'): + speciesName = 'rabbit' + elif (species == 'f'): + speciesName = 'duck' + elif (species == 'p'): + speciesName = 'monkey' + elif (species == 'b'): + speciesName = 'bear' + elif (species == 's'): + speciesName = 'pig' + return speciesName + +# TODO: if base.wantNewSpecies + + +# NOTE: if you change the toonHeadTypes, please change below to match! +toonHeadAnimalIndices = [ 0, # start of dog heads + 4, # start of cat heads + 8, # start of horse heads + 12, # start of mouse heads + 14, # start of rabbit heads + 18, # start of duck heads + 22, # start of monkey heads + 26, # start of bear heads + 30, # start of pig heads + ] + +# free trialers cannot be monkeys, bears, or horses +toonHeadAnimalIndicesTrial = [ 0, # start of dog heads + 4, # start of cat heads + 12, # start of mouse heads + 14, # start of rabbit heads + 18, # start of duck heads + 30, # start of pig heads + ] + +allToonHeadAnimalIndices = [ 0, 1, 2, 3, # Dog + 4, 5, 6, 7, # Cat + 8, 9, 10, 11, # Horse + 12, 13, # Mouse + 14, 15, 16, 17, # Rabbit + 18, 19, 20, 21, # Duck + 22, 23, 24, 25, # Monkey + 26, 27, 28, 29, # Bear + 30, 31, 32, 33, # Pig + ] + +# Free trialers cannot be monkeys, Bears, or Horses +allToonHeadAnimalIndicesTrial = [ 0, 1, 2, 3, # Dog + 4, 5, 6, 7, # Cat + 12, 13, # Mouse + 14, 15, 16, 17, # Rabbit + 18, 19, 20, 21, # Duck + 30, 31, 32, 33, # Pig + ] + +toonTorsoTypes = [ "ss", "ms", "ls", "sd", "md", "ld", "s", "m", "l" ] +# short shorts, medium shorts, long shorts, +# short dress, medium dress, long dress. +# short naked, medium naked, long naked + +toonLegTypes = [ "s", "m", "l" ] # Short, Medium, Long. + +Shirts = [ + "phase_3/maps/desat_shirt_1.jpg", # 0 solid + "phase_3/maps/desat_shirt_2.jpg", # 1 single stripe + "phase_3/maps/desat_shirt_3.jpg", # 2 collar + "phase_3/maps/desat_shirt_4.jpg", # 3 double stripe + "phase_3/maps/desat_shirt_5.jpg", # 4 multiple stripes (boy) + "phase_3/maps/desat_shirt_6.jpg", # 5 collar w/ pocket + "phase_3/maps/desat_shirt_7.jpg", # 6 flower print (girl) + "phase_3/maps/desat_shirt_8.jpg", # 7 special, flower trim (girl) + "phase_3/maps/desat_shirt_9.jpg", # 8 hawaiian (boy) + "phase_3/maps/desat_shirt_10.jpg", # 9 collar w/ 2 pockets + "phase_3/maps/desat_shirt_11.jpg", # 10 bowling shirt + "phase_3/maps/desat_shirt_12.jpg", # 11 special, vest (boy) + "phase_3/maps/desat_shirt_13.jpg", # 12 special (no color), denim vest (girl) + "phase_3/maps/desat_shirt_14.jpg", # 13 peasant (girl) + "phase_3/maps/desat_shirt_15.jpg", # 14 collar w/ ruffles + "phase_3/maps/desat_shirt_16.jpg", # 15 peasant w/ mid stripe (girl) + "phase_3/maps/desat_shirt_17.jpg", # 16 special (no color), soccer jersey + "phase_3/maps/desat_shirt_18.jpg", # 17 special, lightning bolt + "phase_3/maps/desat_shirt_19.jpg", # 18 special, jersey 19 (boy) + "phase_3/maps/desat_shirt_20.jpg", # 19 guayavera (boy) + "phase_3/maps/desat_shirt_21.jpg", # 20 hearts (girl) + "phase_3/maps/desat_shirt_22.jpg", # 21 special, stars (girl) + "phase_3/maps/desat_shirt_23.jpg", # 22 flower (girl) + + # Catalog exclusive shirts + "phase_4/maps/female_shirt1b.jpg", # 23 blue with 3 yellow stripes + "phase_4/maps/female_shirt2.jpg", # 24 pink and beige with flower + "phase_4/maps/female_shirt3.jpg", # 25 yellow hooded sweatshirt (also for boys) + "phase_4/maps/male_shirt1.jpg", # 26 blue stripes + "phase_4/maps/male_shirt2_palm.jpg", # 27 yellow with palm tree + "phase_4/maps/male_shirt3c.jpg", # 28 orange + + # Halloween + "phase_4/maps/shirt_ghost.jpg", # 29 ghost (Halloween) + "phase_4/maps/shirt_pumkin.jpg", # 30 pumpkin (Halloween) + + # Winter holiday + "phase_4/maps/holiday_shirt1.jpg", # 31 (Winter Holiday) + "phase_4/maps/holiday_shirt2b.jpg", # 32 (Winter Holiday) + "phase_4/maps/holidayShirt3b.jpg", # 33 (Winter Holiday) + "phase_4/maps/holidayShirt4.jpg", # 34 (Winter Holiday) + + # Catalog 2 exclusive shirts + "phase_4/maps/female_shirt1b.jpg", # 35 Blue and gold wavy stripes + "phase_4/maps/female_shirt5New.jpg", # 36 Blue and pink with bow + "phase_4/maps/shirtMale4B.jpg", # 37 Lime green with stripe + "phase_4/maps/shirt6New.jpg", # 38 Purple with stars + "phase_4/maps/shirtMaleNew7.jpg", # 39 Red kimono with checkerboard + + # Unused + "phase_4/maps/femaleShirtNew6.jpg", # 40 Aqua kimono white stripe + + # Valentines + "phase_4/maps/Vday1Shirt5.jpg", # 41 (Valentines) + "phase_4/maps/Vday1Shirt6SHD.jpg", # 42 (Valentines) + "phase_4/maps/Vday1Shirt4.jpg", # 43 (Valentines) + "phase_4/maps/Vday_shirt2c.jpg", # 44 (Valentines) + + # Catalog 3 exclusive shirts + "phase_4/maps/shirtTieDyeNew.jpg", # 45 Tie dye + "phase_4/maps/male_shirt1.jpg", # 46 Light blue with blue and white stripe + + # St Patrick's Day shirts + "phase_4/maps/StPats_shirt1.jpg", # 47 (St. Pats) Four leaf clover shirt + "phase_4/maps/StPats_shirt2.jpg", # 48 (St. Pats) Pot o gold + + # T-Shirt Contest shirts + "phase_4/maps/ContestfishingVestShirt2.jpg", # 49 (T-shirt Contest) Fishing Vest + "phase_4/maps/ContestFishtankShirt1.jpg", # 50 (T-shirt Contest) Fish Tank + "phase_4/maps/ContestPawShirt1.jpg", # 51 (T-shirt Contest) Paw Print + + # Catlog 4 exclusive shirts + "phase_4/maps/CowboyShirt1.jpg", # 52 (Western) Cowboy Shirt + "phase_4/maps/CowboyShirt2.jpg", # 53 (Western) Cowboy Shirt + "phase_4/maps/CowboyShirt3.jpg", # 54 (Western) Cowboy Shirt + "phase_4/maps/CowboyShirt4.jpg", # 55 (Western) Cowboy Shirt + "phase_4/maps/CowboyShirt5.jpg", # 56 (Western) Cowboy Shirt + "phase_4/maps/CowboyShirt6.jpg", # 57 (Western) Cowboy Shirt + + # July 4 shirts + "phase_4/maps/4thJulyShirt1.jpg", # 58 (July 4th) Flag Shirt + "phase_4/maps/4thJulyShirt2.jpg", # 59 (July 4th) Fireworks Shirt + + # Catalog 7 exclusive shirts + "phase_4/maps/shirt_Cat7_01.jpg", # 60 Green w/ yellow buttons + "phase_4/maps/shirt_Cat7_02.jpg", # 61 Purple w/ big flower + + # T-Shirt Contest 2 shirts + "phase_4/maps/contest_backpack3.jpg", # 62 Multicolor shirt w/ backpack + "phase_4/maps/contest_leder.jpg", # 63 Lederhosen + "phase_4/maps/contest_mellon2.jpg", # 64 Watermelon + "phase_4/maps/contest_race2.jpg", # 65 Race Shirt (UK winner) + + # Pajama shirts + "phase_4/maps/PJBlueBanana2.jpg", # 66 Blue Banana PJ Shirt + "phase_4/maps/PJRedHorn2.jpg", # 67 Red Horn PJ Shirt + "phase_4/maps/PJGlasses2.jpg", # 68 Purple Glasses PJ Shirt + + # 2009 Valentines Day Shirts + "phase_4/maps/tt_t_chr_avt_shirt_valentine1.jpg", # 69 Valentines Shirt 1 + "phase_4/maps/tt_t_chr_avt_shirt_valentine2.jpg", # 70 Valentines Shirt 2 + + # Award Clothes + "phase_4/maps/tt_t_chr_avt_shirt_desat4.jpg", # 71 + "phase_4/maps/tt_t_chr_avt_shirt_fishing1.jpg", # 72 + "phase_4/maps/tt_t_chr_avt_shirt_fishing2.jpg", # 73 + "phase_4/maps/tt_t_chr_avt_shirt_gardening1.jpg", # 74 + "phase_4/maps/tt_t_chr_avt_shirt_gardening2.jpg", # 75 + "phase_4/maps/tt_t_chr_avt_shirt_party1.jpg", # 76 + "phase_4/maps/tt_t_chr_avt_shirt_party2.jpg", # 77 + "phase_4/maps/tt_t_chr_avt_shirt_racing1.jpg", # 78 + "phase_4/maps/tt_t_chr_avt_shirt_racing2.jpg", # 79 + "phase_4/maps/tt_t_chr_avt_shirt_summer1.jpg", # 80 + "phase_4/maps/tt_t_chr_avt_shirt_summer2.jpg", # 81 + + "phase_4/maps/tt_t_chr_avt_shirt_golf1.jpg", # 82 + "phase_4/maps/tt_t_chr_avt_shirt_golf2.jpg", # 83 + "phase_4/maps/tt_t_chr_avt_shirt_halloween1.jpg", # 84 + "phase_4/maps/tt_t_chr_avt_shirt_halloween2.jpg", # 85 + "phase_4/maps/tt_t_chr_avt_shirt_marathon1.jpg", # 86 + "phase_4/maps/tt_t_chr_avt_shirt_saveBuilding1.jpg", # 87 + "phase_4/maps/tt_t_chr_avt_shirt_saveBuilding2.jpg", # 88 + "phase_4/maps/tt_t_chr_avt_shirt_toonTask1.jpg", # 89 + "phase_4/maps/tt_t_chr_avt_shirt_toonTask2.jpg", # 90 + "phase_4/maps/tt_t_chr_avt_shirt_trolley1.jpg", # 91 + "phase_4/maps/tt_t_chr_avt_shirt_trolley2.jpg", # 92 + "phase_4/maps/tt_t_chr_avt_shirt_winter1.jpg", # 93 + "phase_4/maps/tt_t_chr_avt_shirt_halloween3.jpg", # 94 + "phase_4/maps/tt_t_chr_avt_shirt_halloween4.jpg", # 95 + # 2010 Valentines Day Shirts + "phase_4/maps/tt_t_chr_avt_shirt_valentine3.jpg", # 96 Valentines Shirt 3 + + # Scientist Shirts + "phase_4/maps/tt_t_chr_shirt_scientistC.jpg", # 97 + "phase_4/maps/tt_t_chr_shirt_scientistA.jpg", # 98 + "phase_4/maps/tt_t_chr_shirt_scientistB.jpg", # 99 + + # Silly Story Shirts + "phase_4/maps/tt_t_chr_avt_shirt_mailbox.jpg", # 100 Mailbox Shirt + "phase_4/maps/tt_t_chr_avt_shirt_trashcan.jpg", # 101 Trash Can Shirt + "phase_4/maps/tt_t_chr_avt_shirt_loonyLabs.jpg",# 102 Loony Labs Shirt + "phase_4/maps/tt_t_chr_avt_shirt_hydrant.jpg", # 103 Hydrant Shirt + "phase_4/maps/tt_t_chr_avt_shirt_whistle.jpg", # 104 Sillymeter Whistle Shirt + "phase_4/maps/tt_t_chr_avt_shirt_cogbuster.jpg", # 105 Silly Cogbuster Shirt + + "phase_4/maps/tt_t_chr_avt_shirt_mostCogsDefeated01.jpg", # 106 Most Cogs Defeated Shirt + "phase_4/maps/tt_t_chr_avt_shirt_victoryParty01.jpg", # 107 Victory Party Shirt 1 + "phase_4/maps/tt_t_chr_avt_shirt_victoryParty02.jpg", # 108 Victory Party Shirt 2 + ] + +# These are deemed safe for MakeAToon +BoyShirts = [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (8, 8), (9, 9), (10, 0), (11, 0), (14, 10), (16, 0), (17, 0), (18, 12), (19, 13)] +GirlShirts = [(0, 0), (1, 1), (2, 2), (3, 3), (5, 5), (6, 6), (7, 7), (9, 9), (12, 0), (13, 11), (15, 11), (16, 0), (20, 0), (21, 0), (22, 0)] + +def isValidBoyShirt(index): + for pair in BoyShirts: + if (index == pair[0]): + return 1 + return 0 + +def isValidGirlShirt(index): + for pair in GirlShirts: + if (index == pair[0]): + return 1 + return 0 + +Sleeves = [ + "phase_3/maps/desat_sleeve_1.jpg", # 0 + "phase_3/maps/desat_sleeve_2.jpg", # 1 + "phase_3/maps/desat_sleeve_3.jpg", # 2 + "phase_3/maps/desat_sleeve_4.jpg", # 3 + "phase_3/maps/desat_sleeve_5.jpg", # 4 + "phase_3/maps/desat_sleeve_6.jpg", # 5 + "phase_3/maps/desat_sleeve_7.jpg", # 6 + "phase_3/maps/desat_sleeve_8.jpg", # 7 + "phase_3/maps/desat_sleeve_9.jpg", # 8 + "phase_3/maps/desat_sleeve_10.jpg", # 9 + "phase_3/maps/desat_sleeve_15.jpg", # 10 + "phase_3/maps/desat_sleeve_16.jpg", # 11 + "phase_3/maps/desat_sleeve_19.jpg", # 12 + "phase_3/maps/desat_sleeve_20.jpg", # 13 + + # Catalog exclusive shirt sleeves + "phase_4/maps/female_sleeve1b.jpg", # 14 blue with 3 yellow stripes + "phase_4/maps/female_sleeve2.jpg", # 15 pink and beige with flower + "phase_4/maps/female_sleeve3.jpg", # 16 yellow hooded sweatshirt + "phase_4/maps/male_sleeve1.jpg", # 17 blue stripes + "phase_4/maps/male_sleeve2_palm.jpg", # 18 yellow with palm tree + "phase_4/maps/male_sleeve3c.jpg", # 19 orange + + "phase_4/maps/shirt_Sleeve_ghost.jpg", # 20 ghost (Halloween) + "phase_4/maps/shirt_Sleeve_pumkin.jpg", # 21 pumpkin (Halloween) + + "phase_4/maps/holidaySleeve1.jpg", # 22 (Winter Holiday) + "phase_4/maps/holidaySleeve3.jpg", # 23 (Winter Holiday) + + # Catalog series 2 + "phase_4/maps/female_sleeve1b.jpg", # 24 Blue and gold wavy stripes + "phase_4/maps/female_sleeve5New.jpg", # 25 Blue and pink with bow + "phase_4/maps/male_sleeve4New.jpg", # 26 Lime green with stripe + "phase_4/maps/sleeve6New.jpg", # 27 Purple with stars + "phase_4/maps/SleeveMaleNew7.jpg", # 28 Red kimono/hockey shirt + + # Unused + "phase_4/maps/female_sleeveNew6.jpg", # 29 Aqua kimono white stripe + + "phase_4/maps/Vday5Sleeve.jpg", # 30 (Valentines) + "phase_4/maps/Vda6Sleeve.jpg", # 31 (Valentines) + "phase_4/maps/Vday_shirt4sleeve.jpg", # 32 (Valentines) + "phase_4/maps/Vday2cSleeve.jpg", # 33 (Valentines) + + # Catalog series 3 + "phase_4/maps/sleeveTieDye.jpg", # 34 Tie dye + "phase_4/maps/male_sleeve1.jpg", # 35 Blue with blue and white stripe + + # St. Patrick's day + "phase_4/maps/StPats_sleeve.jpg", # 36 (St. Pats) Four leaf clover + "phase_4/maps/StPats_sleeve2.jpg", # 37 (St. Pats) Pot o gold + + # T-Shirt Contest sleeves + "phase_4/maps/ContestfishingVestSleeve1.jpg", # 38 (T-Shirt Contest) fishing vest sleeve + "phase_4/maps/ContestFishtankSleeve1.jpg", # 39 (T-Shirt Contest) fish bowl sleeve + "phase_4/maps/ContestPawSleeve1.jpg", # 40 (T-Shirt Contest) paw print sleeve + + # Catalog Series 4 + "phase_4/maps/CowboySleeve1.jpg", # 41 (Western) cowboy shirt sleeve + "phase_4/maps/CowboySleeve2.jpg", # 42 (Western) cowboy shirt sleeve + "phase_4/maps/CowboySleeve3.jpg", # 43 (Western) cowboy shirt sleeve + "phase_4/maps/CowboySleeve4.jpg", # 44 (Western) cowboy shirt sleeve + "phase_4/maps/CowboySleeve5.jpg", # 45 (Western) cowboy shirt sleeve + "phase_4/maps/CowboySleeve6.jpg", # 46 (Western) cowboy shirt sleeve + + # July 4th + "phase_4/maps/4thJulySleeve1.jpg", # 47 (July 4th) flag shirt sleeve + "phase_4/maps/4thJulySleeve2.jpg", # 48 (July 4th) fireworks shirt sleeve + + # Catlog series 7 + "phase_4/maps/shirt_sleeveCat7_01.jpg", # 49 Green shirt w/ yellow buttons sleeve + "phase_4/maps/shirt_sleeveCat7_02.jpg", # 50 Purple shirt w/ big flower sleeve + + # T-Shirt Contest 2 sleeves + "phase_4/maps/contest_backpack_sleeve.jpg", # 51 (T-Shirt Contest) Multicolor shirt 2/ backpack sleeve + "phase_4/maps/Contest_leder_sleeve.jpg", # 52 (T-Shirt Contest) Lederhosen sleeve + "phase_4/maps/contest_mellon_sleeve2.jpg", # 53 (T-Shirt Contest) Watermelon sleeve + "phase_4/maps/contest_race_sleeve.jpg", # 54 (T-Shirt Contest) Race Shirt sleeve (UK winner) + + # Pajama sleeves + "phase_4/maps/PJSleeveBlue.jpg", # 55 Blue Pajama sleeve + "phase_4/maps/PJSleeveRed.jpg", # 56 Red Pajama sleeve + "phase_4/maps/PJSleevePurple.jpg", # 57 Purple Pajama sleeve + + # 2009 Valentines Day Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_valentine1.jpg", # 58 Valentines Sleeves 1 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_valentine2.jpg", # 59 Valentines Sleeves 2 + + # Special Award Clothing + "phase_4/maps/tt_t_chr_avt_shirtSleeve_desat4.jpg", # 60 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_fishing1.jpg", # 61 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_fishing2.jpg", # 62 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_gardening1.jpg", # 63 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_gardening2.jpg", # 64 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_party1.jpg", # 65 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_party2.jpg", # 66 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_racing1.jpg", # 67 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_racing2.jpg", # 68 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_summer1.jpg", # 69 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_summer2.jpg", # 70 + + "phase_4/maps/tt_t_chr_avt_shirtSleeve_golf1.jpg", # 71 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_golf2.jpg", # 72 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_halloween1.jpg", # 73 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_halloween2.jpg", # 74 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_marathon1.jpg", # 75 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_saveBuilding1.jpg", # 76 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_saveBuilding2.jpg", # 77 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_toonTask1.jpg", # 78 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_toonTask2.jpg", # 79 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_trolley1.jpg", # 80 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_trolley2.jpg", # 81 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_winter1.jpg", # 82 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_halloween3.jpg", # 83 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_halloween4.jpg", # 84 + + # 2010 Valentines Day Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_valentine3.jpg", # 85 Valentines Sleeves 1 + + # Scientist Sleeves + "phase_4/maps/tt_t_chr_shirtSleeve_scientist.jpg", # 86 Toon sceintist + + # Silly Story Shirt Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_mailbox.jpg", # 87 Mailbox Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_trashcan.jpg", # 88 Trash Can Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_loonyLabs.jpg", # 89 Loony Labs Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_hydrant.jpg", # 90 Hydrant Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_whistle.jpg", # 91 Sillymeter Whistle Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_cogbuster.jpg", # 92 Silly Cogbuster Sleeves + + "phase_4/maps/tt_t_chr_avt_shirtSleeve_mostCogsDefeated01.jpg",# 93 Most Cogs Defeated Sleeves + "phase_4/maps/tt_t_chr_avt_shirtSleeve_victoryParty01.jpg", # 94 Victory Party Sleeves 1 + "phase_4/maps/tt_t_chr_avt_shirtSleeve_victoryParty02.jpg", # 95 Victory Party Sleeves 2 + ] + +# len = 9 +BoyShorts = [ + "phase_3/maps/desat_shorts_1.jpg", # plain w/ pockets + "phase_3/maps/desat_shorts_2.jpg", # belt + "phase_3/maps/desat_shorts_4.jpg", # cargo + "phase_3/maps/desat_shorts_6.jpg", # hawaiian + "phase_3/maps/desat_shorts_7.jpg", # special, side stripes + "phase_3/maps/desat_shorts_8.jpg", # soccer shorts + "phase_3/maps/desat_shorts_9.jpg", # special, flames side stripes + "phase_3/maps/desat_shorts_10.jpg", # denim (2 darker colors) + + # Valentines + "phase_4/maps/VdayShorts2.jpg", # 8 valentines shorts + + # Catalog series 3 exclusive + "phase_4/maps/shorts4.jpg", # 9 Orange with blue side stripes + "phase_4/maps/shorts1.jpg", # 10 Blue with gold stripes on cuff + + # St. Pats + "phase_4/maps/shorts5.jpg", # 11 Leprechaun shorts + + # Catalog series 4 exclusive + "phase_4/maps/CowboyShorts1.jpg", # 12 Cowboy Shorts 1 + "phase_4/maps/CowboyShorts2.jpg", # 13 Cowboy Shorts 2 + # July 4th + "phase_4/maps/4thJulyShorts1.jpg", # 14 July 4th Shorts + + # Catalog series 7 + "phase_4/maps/shortsCat7_01.jpg", # 15 Green stripes + + # Pajama Shorts + "phase_4/maps/Blue_shorts_1.jpg", # 16 Blue Pajama shorts + "phase_4/maps/Red_shorts_1.jpg", # 17 Red Pajama shorts + "phase_4/maps/Purple_shorts_1.jpg", # 18 Purple Pajama shorts + + # Winter Holiday Shorts + "phase_4/maps/tt_t_chr_avt_shorts_winter1.jpg", # 19 Winter Holiday Shorts Style 1 + "phase_4/maps/tt_t_chr_avt_shorts_winter2.jpg", # 20 Winter Holiday Shorts Style 2 + "phase_4/maps/tt_t_chr_avt_shorts_winter3.jpg", # 21 Winter Holiday Shorts Style 3 + "phase_4/maps/tt_t_chr_avt_shorts_winter4.jpg", # 22 Winter Holiday Shorts Style 4 + + # 2009 Valentines Day Shorts + "phase_4/maps/tt_t_chr_avt_shorts_valentine1.jpg", # 23 Valentines Shorts 1 + "phase_4/maps/tt_t_chr_avt_shorts_valentine2.jpg", # 24 Valentines Shorts 2 + + # Special award Clothes + "phase_4/maps/tt_t_chr_avt_shorts_fishing1.jpg", # 25 + "phase_4/maps/tt_t_chr_avt_shorts_gardening1.jpg", # 26 + "phase_4/maps/tt_t_chr_avt_shorts_party1.jpg", # 27 + "phase_4/maps/tt_t_chr_avt_shorts_racing1.jpg", # 28 + "phase_4/maps/tt_t_chr_avt_shorts_summer1.jpg", # 29 + + "phase_4/maps/tt_t_chr_avt_shorts_golf1.jpg", # 30 + "phase_4/maps/tt_t_chr_avt_shorts_halloween1.jpg", # 31 + "phase_4/maps/tt_t_chr_avt_shorts_halloween2.jpg", # 32 + "phase_4/maps/tt_t_chr_avt_shorts_saveBuilding1.jpg", # 33 + "phase_4/maps/tt_t_chr_avt_shorts_trolley1.jpg", # 34 + "phase_4/maps/tt_t_chr_avt_shorts_halloween4.jpg", # 35 + "phase_4/maps/tt_t_chr_avt_shorts_halloween3.jpg", # 36 + + "phase_4/maps/tt_t_chr_shorts_scientistA.jpg", # 37 + "phase_4/maps/tt_t_chr_shorts_scientistB.jpg", # 38 + "phase_4/maps/tt_t_chr_shorts_scientistC.jpg", # 39 + + "phase_4/maps/tt_t_chr_avt_shorts_cogbuster.jpg", # 40 Silly Cogbuster Shorts + ] + +SHORTS = 0 +SKIRT = 1 + +# len = 14 +GirlBottoms = [ + ("phase_3/maps/desat_skirt_1.jpg", SKIRT), # 0 solid + ("phase_3/maps/desat_skirt_2.jpg", SKIRT), # 1 special, polka dots + ("phase_3/maps/desat_skirt_3.jpg", SKIRT), # 2 vertical stripes + ("phase_3/maps/desat_skirt_4.jpg", SKIRT), # 3 horizontal stripe + ("phase_3/maps/desat_skirt_5.jpg", SKIRT), # 4 flower print + ("phase_3/maps/desat_shorts_1.jpg", SHORTS), # 5 plain w/ pockets + ("phase_3/maps/desat_shorts_5.jpg", SHORTS), # 6 flower + ("phase_3/maps/desat_skirt_6.jpg", SKIRT), # 7 special, 2 pockets + ("phase_3/maps/desat_skirt_7.jpg", SKIRT), # 8 denim (2 darker colors) + ("phase_3/maps/desat_shorts_10.jpg", SHORTS), # 9 denim (2 darker colors) + + # Catalog Series 1 exclusive + ("phase_4/maps/female_skirt1.jpg", SKIRT), # 10 blue with tan border and button + ("phase_4/maps/female_skirt2.jpg", SKIRT), # 11 purple with pink border and ribbon + ("phase_4/maps/female_skirt3.jpg", SKIRT), # 12 teal with yellow border and star + + # Valentines + ("phase_4/maps/VdaySkirt1.jpg", SKIRT), # 13 valentines skirts + + # Catalog Series 3 exclusive + ("phase_4/maps/skirtNew5.jpg", SKIRT), # 14 rainbow skirt + + ("phase_4/maps/shorts5.jpg", SHORTS), # 15 leprechaun shorts + # St. Pats + + # Catalog Series 4 exclusive + ("phase_4/maps/CowboySkirt1.jpg", SKIRT), # 16 cowboy skirt 1 + ("phase_4/maps/CowboySkirt2.jpg", SKIRT), # 17 cowboy skirt 2 + + # July 4th Skirt + ("phase_4/maps/4thJulySkirt1.jpg", SKIRT), # 18 july 4th skirt 1 + + # Catalog series 7 + ("phase_4/maps/skirtCat7_01.jpg", SKIRT), # 19 blue with flower + + # Pajama Shorts + ("phase_4/maps/Blue_shorts_1.jpg", SHORTS), # 20 Blue Pajama shorts + ("phase_4/maps/Red_shorts_1.jpg", SHORTS), # 21 Red Pajama shorts + ("phase_4/maps/Purple_shorts_1.jpg", SHORTS),# 22 Purple Pajama shorts + + # Winter Holiday Skirts + ("phase_4/maps/tt_t_chr_avt_skirt_winter1.jpg", SKIRT), # 23 Winter Holiday Skirt Style 1 + ("phase_4/maps/tt_t_chr_avt_skirt_winter2.jpg", SKIRT), # 24 Winter Holiday Skirt Style 2 + ("phase_4/maps/tt_t_chr_avt_skirt_winter3.jpg", SKIRT), # 25 Winter Holiday Skirt Style 3 + ("phase_4/maps/tt_t_chr_avt_skirt_winter4.jpg", SKIRT), # 26 Winter Holiday Skirt Style 4 + + # 2009 Valentines Day Skirts + ("phase_4/maps/tt_t_chr_avt_skirt_valentine1.jpg", SKIRT), # 27 Valentines Skirt 1 + ("phase_4/maps/tt_t_chr_avt_skirt_valentine2.jpg", SKIRT), # 28 Valentines Skirt 2 + + # Special award clothing + ("phase_4/maps/tt_t_chr_avt_skirt_fishing1.jpg", SKIRT), # 29 + ("phase_4/maps/tt_t_chr_avt_skirt_gardening1.jpg", SKIRT), # 30 + ("phase_4/maps/tt_t_chr_avt_skirt_party1.jpg", SKIRT), # 31 + ("phase_4/maps/tt_t_chr_avt_skirt_racing1.jpg", SKIRT), # 32 + ("phase_4/maps/tt_t_chr_avt_skirt_summer1.jpg", SKIRT), # 33 + + ("phase_4/maps/tt_t_chr_avt_skirt_golf1.jpg", SKIRT), # 34 + ("phase_4/maps/tt_t_chr_avt_skirt_halloween1.jpg", SKIRT), # 35 + ("phase_4/maps/tt_t_chr_avt_skirt_halloween2.jpg", SKIRT), # 36 + ("phase_4/maps/tt_t_chr_avt_skirt_saveBuilding1.jpg", SKIRT), # 37 + ("phase_4/maps/tt_t_chr_avt_skirt_trolley1.jpg", SKIRT), # 38 + ("phase_4/maps/tt_t_chr_avt_skirt_halloween3.jpg", SKIRT), # 39 + ("phase_4/maps/tt_t_chr_avt_skirt_halloween4.jpg", SKIRT), # 40 + + ("phase_4/maps/tt_t_chr_shorts_scientistA.jpg", SHORTS), # 41 + ("phase_4/maps/tt_t_chr_shorts_scientistB.jpg", SHORTS), # 42 + ("phase_4/maps/tt_t_chr_shorts_scientistC.jpg", SHORTS), # 43 + + ("phase_4/maps/tt_t_chr_avt_shorts_cogbuster.jpg", SHORTS), # 44 Silly Cogbuster Shorts + ] + +# len = 28 +ClothesColors = [ + # Boy shirts (0 - 12) + VBase4(0.933594, 0.265625, 0.28125, 1.0), # (0) bright red + VBase4(0.863281, 0.40625, 0.417969, 1.0), # (1) light red + VBase4(0.710938, 0.234375, 0.4375, 1.0), # (2) plum + VBase4(0.992188, 0.480469, 0.167969, 1.0), # (3) orange + VBase4(0.996094, 0.898438, 0.320312, 1.0), # (4) yellow + VBase4(0.550781, 0.824219, 0.324219, 1.0), # (5) light green + VBase4(0.242188, 0.742188, 0.515625, 1.0), # (6) seafoam + VBase4(0.433594, 0.90625, 0.835938, 1.0), # (7) light blue green + VBase4(0.347656, 0.820312, 0.953125, 1.0), # (8) light blue + VBase4(0.191406, 0.5625, 0.773438, 1.0), # (9) medium blue + VBase4(0.285156, 0.328125, 0.726562, 1.0), + VBase4(0.460938, 0.378906, 0.824219, 1.0), # (11) purple blue + VBase4(0.546875, 0.28125, 0.75, 1.0), # (12) dark purple blue + # Boy shorts + VBase4(0.570312, 0.449219, 0.164062, 1.0), + VBase4(0.640625, 0.355469, 0.269531, 1.0), + VBase4(0.996094, 0.695312, 0.511719, 1.0), + VBase4(0.832031, 0.5, 0.296875, 1.0), + VBase4(0.992188, 0.480469, 0.167969, 1.0), + VBase4(0.550781, 0.824219, 0.324219, 1.0), + VBase4(0.433594, 0.90625, 0.835938, 1.0), + VBase4(0.347656, 0.820312, 0.953125, 1.0), + # Girl clothes + VBase4(0.96875, 0.691406, 0.699219, 1.0), # (21) light pink + VBase4(0.996094, 0.957031, 0.597656, 1.0), # (22) light yellow + VBase4(0.855469, 0.933594, 0.492188, 1.0), # (23) light yellow green + VBase4(0.558594, 0.589844, 0.875, 1.0), # (24) light purple + VBase4(0.726562, 0.472656, 0.859375, 1.0), # (25) medium purple + VBase4(0.898438, 0.617188, 0.90625, 1.0), # (26) purple + # Special + VBase4(1.0, 1.0, 1.0, 1.0), # (27) white + # Pajama colors + # Not using these colors yet, possibly for gloves + VBase4(0.0, 0.2, 0.956862, 1.0), # (28) Blue Banana Pajama + VBase4(0.972549, 0.094117, 0.094117, 1.0), # (29) Red Horn Pajama + VBase4(0.447058, 0.0, 0.901960, 1.0), # (30) Purple Glasses Pajama + ] + +# If you add to this, please add to TTLocalizer.ShirtStyleDescriptions +ShirtStyles = { + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + # ------------------------------------------------------------------------- + # Boy styles + # ------------------------------------------------------------------------- + # solid + 'bss1' : [ 0, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (27, 27) ]], + # single stripe + 'bss2' : [ 1, 1, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # collar + 'bss3' : [ 2, 2, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # double stripe + 'bss4' : [ 3, 3, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # multiple stripes + 'bss5' : [ 4, 4, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (9, 9), (10, 10), (11, 11), (12, 12) ]], + # collar w/ pocket + 'bss6' : [ 5, 5, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # hawaiian + 'bss7' : [ 8, 8, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (8, 8), (9, 9), (11, 11), (12, 12), (27, 27) ]], + # collar w/ 2 pockets + 'bss8' : [ 9, 9, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # bowling shirt + 'bss9' : [ 10, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (27, 27) ]], + # vest (special) + 'bss10' : [ 11, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (27, 27) ]], + # collar w/ ruffles + 'bss11' : [ 14, 10, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # soccer jersey (special) + 'bss12' : [ 16, 0, [(27, 27), (27, 4), (27, 5), (27, 6), (27, 7), + (27, 8), (27, 9)]], + # lightning bolt (special) + 'bss13' : [ 17, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12) ]], + # jersey 19 (special) + 'bss14' : [ 18, 12, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (8, 8), (9, 9), (11, 11), (12, 12), (27, 27) ]], + # guayavera + 'bss15' : [ 19, 13, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (27, 27) ]], + # ------------------------------------------------------------------------- + # Girl styles + # ------------------------------------------------------------------------- + # solid + 'gss1' : [ 0, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), + (27, 27)]], + # single stripe + 'gss2' : [ 1, 1, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # collar + 'gss3' : [ 2, 2, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # double stripes + 'gss4' : [ 3, 3, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # collar w/ pocket + 'gss5' : [ 5, 5, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # flower print + 'gss6' : [ 6, 6, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # flower trim (special) + 'gss7' : [ 7, 7, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # collar w/ 2 pockets + 'gss8' : [ 9, 9, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (11, 11), (12, 12), (21, 21), + (22, 22), (23, 23), (24, 24), (25, 25), (26, 26)]], + # denim vest (special) + 'gss9' : [ 12, 0, [(27, 27)]], + # peasant + 'gss10' : [ 13, 11, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), + (26, 26) ]], + # peasant w/ mid stripe + 'gss11' : [ 15, 11, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), + (26, 26) ]], + # soccer jersey (special) + 'gss12' : [ 16, 0, [(27, 27), (27, 4), (27, 5), (27, 6), (27, 7), + (27, 8), (27, 9)]], + # hearts + 'gss13' : [ 20, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), + (26, 26) ]], + # stars (special) + 'gss14' : [ 21, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), + (26, 26) ]], + # flower + 'gss15' : [ 22, 0, [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), + (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), + (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), + (26, 26) ]], + + + # Special Catalog-only shirts. + + # yellow hooded - Series 1 + 'c_ss1' : [ 25, 16, [(27, 27),]], + + # yellow with palm tree - Series 1 + 'c_ss2' : [ 27, 18, [(27, 27),]], + + # purple with stars - Series 2 + 'c_ss3' : [ 38, 27, [(27, 27),]], + + # blue stripes (boys only) - Series 1 + 'c_bss1' : [ 26, 17, [(27, 27),]], + + # orange (boys only) - Series 1 + 'c_bss2' : [ 28, 19, [(27, 27),]], + + # lime green with stripe (boys only) - Series 2 + 'c_bss3' : [ 37, 26, [(27, 27),]], + + # red kimono with checkerboard (boys only) - Series 2 + 'c_bss4' : [ 39, 28, [(27, 27),]], + + # blue with yellow stripes (girls only) - Series 1 + 'c_gss1' : [ 23, 14, [(27, 27), ]], + + # pink and beige with flower (girls only) - Series 1 + 'c_gss2' : [ 24, 15, [(27, 27), ]], + + # Blue and gold with wavy stripes (girls only) - Series 2 + 'c_gss3' : [ 35, 24, [(27, 27), ]], + + # Blue and pink with bow (girls only) - Series 2 + 'c_gss4' : [ 36, 25, [(27, 27), ]], + + # Aqua kimono white stripe (girls only) - UNUSED + 'c_gss5' : [ 40, 29, [(27, 27), ]], + + # Tie dye shirt (boys and girls) - Series 3 + 'c_ss4' : [45, 34, [(27, 27), ]], + + # light blue with blue and white stripe (boys only) - Series 3 + 'c_ss5' : [ 46, 35, [(27, 27), ]], + + # cowboy shirt 1-6 : Series 4 + 'c_ss6' : [ 52, 41, [(27, 27), ]], + 'c_ss7' : [ 53, 42, [(27, 27), ]], + 'c_ss8' : [ 54, 43, [(27, 27), ]], + 'c_ss9' : [ 55, 44, [(27, 27), ]], + 'c_ss10' : [ 56, 45, [(27, 27), ]], + 'c_ss11' : [ 57, 46, [(27, 27), ]], + + # Special Holiday-themed shirts. + + # Halloween ghost + 'hw_ss1' : [ 29, 20, [(27, 27), ]], + # Halloween pumpkin + 'hw_ss2' : [ 30, 21, [(27, 27), ]], + + # Winter Holiday + 'wh_ss1' : [ 31, 22, [(27, 27), ]], + # Winter Holiday + 'wh_ss2' : [ 32, 22, [(27, 27), ]], + # Winter Holiday + 'wh_ss3' : [ 33, 23, [(27, 27), ]], + # Winter Holiday + 'wh_ss4' : [ 34, 23, [(27, 27), ]], + + # Valentines day, pink with red hearts (girls) + 'vd_ss1' : [ 41, 30, [(27, 27), ]], + # Valentines day, red with white hearts + 'vd_ss2' : [ 42, 31, [(27, 27), ]], + # Valentines day, white with winged hearts (boys) + 'vd_ss3' : [ 43, 32, [(27, 27), ]], + # Valentines day, pink with red flamed heart + 'vd_ss4' : [ 44, 33, [(27, 27), ]], + # 2009 Valentines day, white with red cupid + 'vd_ss5' : [ 69, 58, [(27, 27), ]], + # 2009 Valentines day, blue with green and red hearts + 'vd_ss6' : [ 70, 59, [(27, 27), ]], + # 2010 Valentines day, red with white wings + 'vd_ss7' : [ 96, 85, [(27, 27), ]], + # St Pat's Day, four leaf clover shirt + 'sd_ss1' : [ 47, 36, [(27, 27), ]], + # St Pat's Day, pot o gold shirt + 'sd_ss2' : [ 48, 37, [(27, 27), ]], + + # T-Shirt Contest, Fishing Vest + 'tc_ss1' : [ 49, 38, [(27, 27), ]], + # T-Shirt Contest, Fish Bowl + 'tc_ss2' : [ 50, 39, [(27, 27), ]], + # T-Shirt Contest, Paw Print + 'tc_ss3' : [ 51, 40, [(27, 27), ]], + # T-Shirt Contest, Backpack + 'tc_ss4' : [ 62, 51, [(27, 27), ]], + # T-Shirt Contest, Lederhosen + 'tc_ss5' : [ 63, 52, [(27, 27), ]], + # T-Shirt Contest, Watermelon + 'tc_ss6' : [ 64, 53, [(27, 27), ]], + # T-Shirt Contest, Race Shirt + 'tc_ss7' : [ 65, 54, [(27, 27), ]], + + # July 4th, Flag + 'j4_ss1' : [ 58, 47, [(27, 27), ]], + # July 4th, Fireworks + 'j4_ss2' : [ 59, 48, [(27, 27), ]], + + # Catalog series 7, Green w/ yellow buttons + 'c_ss12' : [ 60, 49, [(27, 27), ]], + + # Catalog series 7, Purple w/ big flower + 'c_ss13' : [ 61, 50, [(27, 27), ]], + + # Pajama series + 'pj_ss1' : [66, 55, [(27, 27),]], # Blue Banana Pajama shirt + 'pj_ss2' : [67, 56, [(27, 27),]], # Red Horn Pajama shirt + 'pj_ss3' : [68, 57, [(27, 27),]], # Purple Glasses Pajama shirt + + # Special Award Clothes + 'sa_ss1' : [ 71, 60, [(27, 27),]], + 'sa_ss2' : [ 72, 61, [(27, 27),]], + 'sa_ss3' : [ 73, 62, [(27, 27),]], + 'sa_ss4' : [ 74, 63, [(27, 27),]], + 'sa_ss5' : [ 75, 64, [(27, 27),]], + 'sa_ss6' : [ 76, 65, [(27, 27),]], + 'sa_ss7' : [ 77, 66, [(27, 27),]], + 'sa_ss8' : [ 78, 67, [(27, 27),]], + 'sa_ss9' : [ 79, 68, [(27, 27),]], + 'sa_ss10' : [ 80, 69, [(27, 27),]], + 'sa_ss11' : [ 81, 70, [(27, 27),]], + 'sa_ss12' : [ 82, 71, [(27, 27),]], + 'sa_ss13' : [ 83, 72, [(27, 27),]], + 'sa_ss14' : [ 84, 73, [(27, 27),]], + 'sa_ss15' : [ 85, 74, [(27, 27),]], + 'sa_ss16' : [ 86, 75, [(27, 27),]], + 'sa_ss17' : [ 87, 76, [(27, 27),]], + 'sa_ss18' : [ 88, 77, [(27, 27),]], + 'sa_ss19' : [ 89, 78, [(27, 27),]], + 'sa_ss20' : [ 90, 79, [(27, 27),]], + 'sa_ss21' : [ 91, 80, [(27, 27),]], + 'sa_ss22' : [ 92, 81, [(27, 27),]], + 'sa_ss23' : [ 93, 82, [(27, 27),]], + 'sa_ss24' : [ 94, 83, [(27, 27),]], + 'sa_ss25' : [ 95, 84, [(27, 27),]], + 'sa_ss26' : [ 106, 93, [(27, 27), ]], # Most Cogs Defeated Shirt + + # Scientists + 'sc_1' : [ 97, 86, [(27, 27),]], + 'sc_2' : [ 98, 86, [(27, 27),]], + 'sc_3' : [ 99, 86, [(27, 27),]], + + # Silly Story Shirts + 'sil_1' : [ 100, 87, [(27, 27),]], # Silly Mailbox Shirt + 'sil_2' : [ 101, 88, [(27, 27),]], # Silly Trashcan Shirt + 'sil_3' : [ 102, 89, [(27, 27),]], # Loony Labs Shirt + 'sil_4' : [ 103, 90, [(27, 27),]], # Silly Hydrant Shirt + 'sil_5' : [ 104, 91, [(27, 27),]], # Sillymeter Whistle Shirt + 'sil_6' : [ 105, 92, [(27, 27),]], # Silly Cogbuster Shirt + 'sil_7' : [ 107, 94, [(27, 27),]], # Victory Party Shirt 1 + 'sil_8' : [ 108, 95, [(27, 27),]], # Victory Party Shirt 2 + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + } + +# If you add to this, please add to TTLocalizer.BottomStylesDescriptions +BottomStyles = { + # name : [ bottomIdx, [bottomColorIdx, ...]] + # ------------------------------------------------------------------------- + # Boy styles (shorts) + # ------------------------------------------------------------------------- + # plain w/ pockets + 'bbs1' : [ 0, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20]], + # belt + 'bbs2' : [ 1, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20]], + # cargo + 'bbs3' : [ 2, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20]], + # hawaiian + 'bbs4' : [ 3, [0, 1, 2, 4, 6, 8, 9, 11, 12, 13, 15, 16, 17, 18, 19, 20, + 27]], + # side stripes (special) + 'bbs5' : [ 4, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20]], + # soccer shorts + 'bbs6' : [ 5, [0, 1, 2, 4, 6, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, + 27]], + # side flames (special) + 'bbs7' : [ 6, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 20, 27]], + # denim + 'bbs8' : [ 7, [0, 1, 2, 4, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 27]], + # Valentines shorts + 'vd_bs1' : [ 8, [ 27, ]], + # Green with red heart + 'vd_bs2' : [ 23, [ 27, ]], + # Blue denim with green and red heart + 'vd_bs3' : [ 24, [ 27, ]], + + # Catalog only shorts + # Orange with blue side stripes + 'c_bs1' : [ 9, [ 27, ]], + + # Blue with gold cuff stripes + 'c_bs2' : [ 10, [ 27, ]], + + # Green stripes - series 7 + 'c_bs5' : [ 15, [ 27, ]], + + # St. Pats leprechaun shorts + 'sd_bs1' : [ 11, [27, ]], + + # Pajama shorts + 'pj_bs1' : [ 16, [27, ]], # Blue Banana Pajama pants + 'pj_bs2' : [ 17, [27, ]], # Red Horn Pajama pants + 'pj_bs3' : [ 18, [27, ]], # Purple Glasses Pajama pants + + # Winter Holiday Shorts + 'wh_bs1' : [ 19, [27, ]], # Winter Holiday Shorts Style 1 + 'wh_bs2' : [ 20, [27, ]], # Winter Holiday Shorts Style 2 + 'wh_bs3' : [ 21, [27, ]], # Winter Holiday Shorts Style 3 + 'wh_bs4' : [ 22, [27, ]], # Winter Holiday Shorts Style 4 + + # ------------------------------------------------------------------------- + # Girl styles (shorts and skirts) + # ------------------------------------------------------------------------- + # skirts + # ------------------------------------------------------------------------- + # solid + 'gsk1' : [ 0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + # polka dots (special) + 'gsk2' : [ 1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26]], + # vertical stripes + 'gsk3' : [ 2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26]], + # horizontal stripe + 'gsk4' : [ 3, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26]], + # flower print + 'gsk5' : [ 4, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26]], + # 2 pockets (special) + 'gsk6' : [ 7, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + # denim + 'gsk7' : [ 8, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + + # shorts + # ------------------------------------------------------------------------- + # plain w/ pockets + 'gsh1' : [ 5, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + # flower + 'gsh2' : [ 6, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + # denim + 'gsh3' : [ 9, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 21, 22, 23, 24, 25, + 26, 27]], + + # Special catalog-only skirts and shorts. + + # blue skirt with tan border and button + 'c_gsk1' : [ 10, [ 27, ]], + + # purple skirt with pink and ribbon + 'c_gsk2' : [ 11, [ 27, ]], + + # teal skirt with yellow and star + 'c_gsk3' : [ 12, [ 27, ]], + + # Valentines skirt (note, do not name with gsk, otherwise NPC might randomly get this skirt) + # red skirt with hearts + 'vd_gs1' : [ 13, [ 27, ]], + # Pink flair skirt with polka hearts + 'vd_gs2' : [ 27, [ 27, ]], + # Blue denim skirt with green and red heart + 'vd_gs3' : [ 28, [ 27, ]], + + # rainbow skirt - Series 3 + 'c_gsk4' : [ 14, [ 27, ]], + + # St. Pats day shorts + 'sd_gs1' : [ 15, [ 27, ]], + + # Western skirts + 'c_gsk5' : [ 16, [ 27, ]], + 'c_gsk6' : [ 17, [ 27, ]], + + # Western shorts + 'c_bs3' : [ 12, [ 27, ]], + 'c_bs4' : [ 13, [ 27, ]], + + # July 4th shorts + 'j4_bs1' : [ 14, [ 27, ]], + + # July 4th Skirt + 'j4_gs1' : [ 18, [ 27, ]], + + # Blue with flower - series 7 + 'c_gsk7' : [ 19, [ 27, ]], + + # pajama shorts + 'pj_gs1' : [ 20, [27, ]], # Blue Banana Pajama pants + 'pj_gs2' : [ 21, [27, ]], # Red Horn Pajama pants + 'pj_gs3' : [ 22, [27, ]], # Purple Glasses Pajama pants + + # Winter Holiday Skirts + 'wh_gsk1' : [ 23, [27, ]], # Winter Holiday Skirt Style 1 + 'wh_gsk2' : [ 24, [27, ]], # Winter Holiday Skirt Style 2 + 'wh_gsk3' : [ 25, [27, ]], # Winter Holiday Skirt Style 3 + 'wh_gsk4' : [ 26, [27, ]], # Winter Holiday Skirt Style 4 + + # Special award clothes + 'sa_bs1' : [25, [27, ]], + 'sa_bs2' : [26, [27, ]], + 'sa_bs3' : [27, [27, ]], + 'sa_bs4' : [28, [27, ]], + 'sa_bs5' : [29, [27, ]], + 'sa_bs6' : [30, [27, ]], + 'sa_bs7' : [31, [27, ]], + 'sa_bs8' : [32, [27, ]], + 'sa_bs9' : [33, [27, ]], + 'sa_bs10' : [34, [27, ]], + 'sa_bs11' : [35, [27, ]], + 'sa_bs12' : [36, [27, ]], + + # Special award clothes + 'sa_gs1' : [29, [27, ]], + 'sa_gs2' : [30, [27, ]], + 'sa_gs3' : [31, [27, ]], + 'sa_gs4' : [32, [27, ]], + 'sa_gs5' : [33, [27, ]], + 'sa_gs6' : [34, [27, ]], + 'sa_gs7' : [35, [27, ]], + 'sa_gs8' : [36, [27, ]], + 'sa_gs9' : [37, [27, ]], + 'sa_gs10' : [38, [27, ]], + 'sa_gs11' : [39, [27, ]], + 'sa_gs12' : [40, [27, ]], + + # Scientists + 'sc_bs1' : [37, [27, ]], + 'sc_bs2' : [38, [27, ]], + 'sc_bs3' : [39, [27, ]], + + 'sc_gs1' : [41, [27, ]], + 'sc_gs2' : [42, [27, ]], + 'sc_gs3' : [43, [27, ]], + + 'sil_bs1' : [ 40, [27, ]], # Silly Cogbuster Shorts + 'sil_gs1' : [44, [27, ]], # Silly Cogbuster Shorts + } + +# Define MakeAToon to be Tailor 1 +MAKE_A_TOON = 1 +TAMMY_TAILOR = 2004 # TTC +LONGJOHN_LEROY = 1007 # DD +TAILOR_HARMONY = 4008 # MM +BONNIE_BLOSSOM = 5007 # DG +WARREN_BUNDLES = 3008 # TB +WORNOUT_WAYLON = 9010 # DDR + +TailorCollections = { + # TailorId : [ [ boyShirts ], [ girlShirts ], [boyShorts], [girlBottoms] ] + MAKE_A_TOON : [ ['bss1', 'bss2'], + ['gss1', 'gss2'], + ['bbs1', 'bbs2'], + ['gsk1', 'gsh1'] ], + TAMMY_TAILOR : [ ['bss1', 'bss2'], + ['gss1', 'gss2'], + ['bbs1', 'bbs2'], + ['gsk1', 'gsh1'] ], + LONGJOHN_LEROY : [ ['bss3', 'bss4', 'bss14'], ['gss3', 'gss4', 'gss14'], ['bbs3', 'bbs4'], ['gsk2', 'gsh2'] ], + TAILOR_HARMONY : [ ['bss5', 'bss6', 'bss10'], ['gss5', 'gss6', 'gss9'], ['bbs5'], ['gsk3', 'gsh3'] ], + BONNIE_BLOSSOM : [ ['bss7', 'bss8', 'bss12'], ['gss8', 'gss10', 'gss12'], ['bbs6'], ['gsk4', 'gsk5'] ], + WARREN_BUNDLES : [ ['bss9','bss13'], ['gss7', 'gss11'], ['bbs7'], ['gsk6'] ], + WORNOUT_WAYLON : [ ['bss11', 'bss15'], ['gss13', 'gss15'], ['bbs8'], ['gsk7'] ], + } + +BOY_SHIRTS = 0 +GIRL_SHIRTS = 1 +BOY_SHORTS = 2 +GIRL_BOTTOMS = 3 + + +# Make a list of the girl bottoms in MakeAToon +# This is used in the body shop when switching genders +MakeAToonBoyBottoms = [] +MakeAToonBoyShirts = [] +MakeAToonGirlBottoms = [] +MakeAToonGirlShirts = [] +MakeAToonGirlSkirts = [] +MakeAToonGirlShorts = [] + +for style in TailorCollections[MAKE_A_TOON][BOY_SHORTS]: + index = BottomStyles[style][0] + MakeAToonBoyBottoms.append(index) + +for style in TailorCollections[MAKE_A_TOON][BOY_SHIRTS]: + index = ShirtStyles[style][0] + MakeAToonBoyShirts.append(index) + +for style in TailorCollections[MAKE_A_TOON][GIRL_BOTTOMS]: + index = BottomStyles[style][0] + MakeAToonGirlBottoms.append(index) + +for style in TailorCollections[MAKE_A_TOON][GIRL_SHIRTS]: + index = ShirtStyles[style][0] + MakeAToonGirlShirts.append(index) + +# Separate out the skirts and shorts +for index in MakeAToonGirlBottoms: + flag = GirlBottoms[index][1] + if flag == SKIRT: + MakeAToonGirlSkirts.append(index) + elif flag == SHORTS: + MakeAToonGirlShorts.append(index) + else: + notify.error("Invalid flag") + + +# Convenience funtions for clothing +def getRandomTop(gender, tailorId = MAKE_A_TOON, generator = None): + # Returns (shirtTex, color, sleeveTex, color) + if (generator == None): + generator = random + collection = TailorCollections[tailorId] + if (gender == 'm'): + style = generator.choice(collection[BOY_SHIRTS]) + else: + style = generator.choice(collection[GIRL_SHIRTS]) + styleList = ShirtStyles[style] + colors = generator.choice(styleList[2]) + return styleList[0], colors[0], styleList[1], colors[1] + +def getRandomBottom(gender, tailorId = MAKE_A_TOON, generator = None, girlBottomType = None): + # Returns (bottomTex, color) + if (generator == None): + generator = random + collection = TailorCollections[tailorId] + if (gender == 'm'): + style = generator.choice(collection[BOY_SHORTS]) + else: + if (girlBottomType is None): + style = generator.choice(collection[GIRL_BOTTOMS]) + elif (girlBottomType == SKIRT): + skirtCollection = filter(lambda style: GirlBottoms[BottomStyles[style][0]][1] == SKIRT, + collection[GIRL_BOTTOMS]) + style = generator.choice(skirtCollection) + elif (girlBottomType == SHORTS): + shortsCollection = filter(lambda style: GirlBottoms[BottomStyles[style][0]][1] == SHORTS, + collection[GIRL_BOTTOMS]) + style = generator.choice(shortsCollection) + else: + notify.error("Bad girlBottomType: %s" % girlBottomType) + + styleList = BottomStyles[style] + color = generator.choice(styleList[1]) + return styleList[0], color + +def getRandomGirlBottom(type): + bottoms = [] + index = 0 + for bottom in GirlBottoms: + if (bottom[1] == type): + bottoms.append(index) + index += 1 + return random.choice(bottoms) + +def getRandomGirlBottomAndColor(type): + bottoms = [] + if type == SHORTS: + typeStr = 'gsh' + else: + typeStr = 'gsk' + for bottom in BottomStyles.keys(): + if bottom.find(typeStr) >= 0: + bottoms.append(bottom) + style = BottomStyles[random.choice(bottoms)] + return style[0], random.choice(style[1]) + +def getRandomizedTops(gender, tailorId = MAKE_A_TOON, generator = None): + # Returns a list of [ (shirt, color, sleeve, color), ... ] + if (generator == None): + generator = random + collection = TailorCollections[tailorId] + if (gender == 'm'): + collection = collection[BOY_SHIRTS][:] + else: + collection = collection[GIRL_SHIRTS][:] + tops = [] + random.shuffle(collection) + for style in collection: + colors = ShirtStyles[style][2][:] + random.shuffle(colors) + for color in colors: + tops.append((ShirtStyles[style][0], color[0], + ShirtStyles[style][1], color[1])) + return tops + +def getRandomizedBottoms(gender, tailorId = MAKE_A_TOON, generator = None): + # Returns a list of [ (bottom, color), ... ] + if (generator == None): + generator = random + collection = TailorCollections[tailorId] + if (gender == 'm'): + collection = collection[BOY_SHORTS][:] + else: + collection = collection[GIRL_BOTTOMS][:] + bottoms = [] + random.shuffle(collection) + for style in collection: + colors = BottomStyles[style][1][:] + random.shuffle(colors) + for color in colors: + bottoms.append((BottomStyles[style][0], color)) + return bottoms + +def getTops(gender, tailorId = MAKE_A_TOON): + # Returns a list of [ (shirt, color, sleeve, color), ... ] + if (gender == 'm'): + collection = TailorCollections[tailorId][BOY_SHIRTS] + else: + collection = TailorCollections[tailorId][GIRL_SHIRTS] + tops = [] + for style in collection: + for color in ShirtStyles[style][2]: + tops.append((ShirtStyles[style][0], color[0], + ShirtStyles[style][1], color[1])) + return tops + +def getAllTops(gender): + tops = [] + for style in ShirtStyles.keys(): + if gender == 'm': + if (style[0] == 'g') or (style[:3] == 'c_g'): + continue + else: + if (style[0] == 'b') or (style[:3] == 'c_b'): + continue + for color in ShirtStyles[style][2]: + tops.append((ShirtStyles[style][0], color[0], + ShirtStyles[style][1], color[1])) + return tops + +def getBottoms(gender, tailorId = MAKE_A_TOON): + # Returns a list of [ (bottom, color), ... ] + if (gender == 'm'): + collection = TailorCollections[tailorId][BOY_SHORTS] + else: + collection = TailorCollections[tailorId][GIRL_BOTTOMS] + bottoms = [] + for style in collection: + for color in BottomStyles[style][1]: + bottoms.append((BottomStyles[style][0], color)) + return bottoms + +def getAllBottoms(gender, output = 'both'): + bottoms = [] + for style in BottomStyles.keys(): + if gender == 'm': + if ((style[0] == 'g') or (style[:3] == 'c_g') or + (style[:4] == 'vd_g') or (style[:4] == 'sd_g') or + (style[:4] == 'j4_g') or (style[:4] == 'pj_g') or + (style[:4] == 'wh_g') or (style[:4] == 'sa_g') or + (style[:4] == 'sc_g') or (style[:5] == 'sil_g')) : + + continue + else: + if ((style[0] == 'b') or (style[:3] == 'c_b') or + (style[:4] == 'vd_b') or (style[:4] == 'sd_b') or + (style[:4] == 'j4_b') or (style[:4] == 'pj_b') or + (style[:4] == 'wh_b') or (style[:4] == 'sa_b') or + (style[:4] == 'sc_b') or (style[:5] == 'sil_b')): + continue + + bottomIdx = BottomStyles[style][0] + # What type of texture is at this index? + if gender == 'f': + # Female textures can be shorts or skirts + textureType = GirlBottoms[bottomIdx][1] + else: + # All male textures are shorts + textureType = SHORTS + if ((output == 'both') or + ((output == 'skirts') and (textureType == SKIRT)) or + ((output == 'shorts') and (textureType == SHORTS))): + for color in BottomStyles[style][1]: + bottoms.append((bottomIdx, color)) + return bottoms + +# color defines + +# Warning! DNA values are stored in the server database by indexing +# into this array. Do not reorder entries in this array, or add +# things to the middle, or all toons already stored in the database +# will get screwed up. + +# This is the total list of all available colors a part of the toon +# might possibly be set to. It's not the same thing as the list of +# color choices presented by make-a-toon, but it's close. + +# len = 27 +allColorsList = [ + VBase4(1.0, 1.0, 1.0, 1.0), # 0 + VBase4(0.96875, 0.691406, 0.699219, 1.0), # 1 + VBase4(0.933594, 0.265625, 0.28125, 1.0), # 2 + VBase4(0.863281, 0.40625, 0.417969, 1.0), # 3 + VBase4(0.710938, 0.234375, 0.4375, 1.0), # 4 + VBase4(0.570312, 0.449219, 0.164062, 1.0), # 5 + VBase4(0.640625, 0.355469, 0.269531, 1.0), # 6 + VBase4(0.996094, 0.695312, 0.511719, 1.0), # 7 + VBase4(0.832031, 0.5, 0.296875, 1.0), # 8 + VBase4(0.992188, 0.480469, 0.167969, 1.0), # 9 + VBase4(0.996094, 0.898438, 0.320312, 1.0), # 10 + VBase4(0.996094, 0.957031, 0.597656, 1.0), # 11 + VBase4(0.855469, 0.933594, 0.492188, 1.0), # 12 + VBase4(0.550781, 0.824219, 0.324219, 1.0), # 13 + VBase4(0.242188, 0.742188, 0.515625, 1.0), # 14 + VBase4(0.304688, 0.96875, 0.402344, 1.0), # 15 + VBase4(0.433594, 0.90625, 0.835938, 1.0), # 16 + VBase4(0.347656, 0.820312, 0.953125, 1.0), # 17 + VBase4(0.191406, 0.5625, 0.773438, 1.0), # 18 + VBase4(0.558594, 0.589844, 0.875, 1.0), # 19 + VBase4(0.285156, 0.328125, 0.726562, 1.0), # 20 + VBase4(0.460938, 0.378906, 0.824219, 1.0), # 21 + VBase4(0.546875, 0.28125, 0.75, 1.0), # 22 + VBase4(0.726562, 0.472656, 0.859375, 1.0), # 23 + VBase4(0.898438, 0.617188, 0.90625, 1.0), # 24 + VBase4(0.7, 0.7, 0.8, 1.0), # 25 + VBase4(0.3, 0.3, 0.35, 1.0), # black-ish cat# 26 + ] + +# *This* is the list of color choices presented by make-a-toon. It +# indexes into the above array. +defaultBoyColorList = [ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + ] +defaultGirlColorList = [ + 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24 + ] + +class ToonDNA(AvatarDNA.AvatarDNA): + """ToonDNA class: contains methods for describing avatars with a + simple class. The ToonDNA class may be converted to lists of strings + for network transmission. Also, ToonDNA objects can be constructed + from lists of strings recieved over the network. Some examples are in + order. + + # create a random toon dna + dna = AvatarDNA() + dna.newToonRandom() + + # create a random toon but specify its color + dna = AvatarDNA() + dna.newToonRandom(0.7, 0.6, 0.5) + + # create a specific toon by passing in a tuple of body + # part specifier strings (see 'toon defines' above) + dna = AvatarDNA() + dna.newToon( ('dll', 'md', 'l', 'm') ) + # 'l'ong legs, 'm'edium 'd'ress, 'd'og w/ 'l'ong head and 'l'ong muzzle, and 'm'ale + + # create a toon with a part tuple and a color + dna = AvatarDNA() + dna.newToon( ('css', 'ss', 's', 'm'), 0.4, 0.2, 0.2 ) ) + + # create a toon from a network packet (list of strings) + dna = AvatarDNA() + dna.makeFromNetString( networkPacket ) + + """ + # special methods + + def __init__(self, str=None, type=None, dna=None, r=None, b=None, g=None): + """__init__(self, string=None, string=None, string()=None, float=None, + float=None, float=None) + AvatarDNA contructor - see class comment for usage + """ + # have they passed in a stringified DNA object? + if (str != None): + self.makeFromNetString(str) + # have they specified what type of DNA? + elif (type != None): + if (type == 't'): # Toon + if (dna == None): + self.newToonRandom(r, g, b) + else: + self.newToonFromProperties(*dna.asTuple()) + else: + # Invalid type + assert 0 + else: + # mark DNA as undefined + self.type = 'u' + + def __str__(self): + """__str__(self) + Avatar DNA print method + """ + string = "type = toon\n" + string = string + "gender = %s\n" % (self.gender) + string = string + "head = %s, torso = %s, legs = %s\n" % \ + (self.head, self.torso, self.legs) + string = string + "arm color = %d\n" % \ + (self.armColor) + string = string + "glove color = %d\n" % \ + (self.gloveColor) + string = string + "leg color = %d\n" % \ + (self.legColor) + string = string + "head color = %d\n" % \ + (self.headColor) + string = string + "top texture = %d\n" % self.topTex + string = string + "top texture color = %d\n" % self.topTexColor + string = string + "sleeve texture = %d\n" % self.sleeveTex + string = string + "sleeve texture color = %d\n" % self.sleeveTexColor + string = string + "bottom texture = %d\n" % self.botTex + string = string + "bottom texture color = %d\n" % self.botTexColor + return string + + + # stringification methods + def makeNetString(self): + dg = PyDatagram() + dg.addFixedString(self.type, 1) + if (self.type == 't'): # Toon + # These strings had better appear in the lists! + headIndex = toonHeadTypes.index(self.head) + torsoIndex = toonTorsoTypes.index(self.torso) + legsIndex = toonLegTypes.index(self.legs) + dg.addUint8(headIndex) + dg.addUint8(torsoIndex) + dg.addUint8(legsIndex) + if self.gender == 'm': + # male is 1 + dg.addUint8(1) + else: + # female is 0 + dg.addUint8(0) + # Clothes + dg.addUint8(self.topTex) # We assume < 256 textures. + dg.addUint8(self.topTexColor) + dg.addUint8(self.sleeveTex) + dg.addUint8(self.sleeveTexColor) + dg.addUint8(self.botTex) + dg.addUint8(self.botTexColor) + # Colors + dg.addUint8(self.armColor) # We assume < 256 colors. + dg.addUint8(self.gloveColor) + dg.addUint8(self.legColor) + dg.addUint8(self.headColor) + elif (self.type == 'u'): + notify.error("undefined avatar") + else: + notify.error("unknown avatar type: ", self.type) + + return dg.getMessage() + + def isValidNetString(self, string): + dg=PyDatagram(string) + dgi=PyDatagramIterator(dg) + if dgi.getRemainingSize() != 15: + return False + type = dgi.getFixedString(1) + if type not in ('t', ): + return False + + headIndex = dgi.getUint8() + torsoIndex = dgi.getUint8() + legsIndex = dgi.getUint8() + + if headIndex >= len(toonHeadTypes): + return False + if torsoIndex >= len(toonTorsoTypes): + return False + if legsIndex >= len(toonLegTypes): + return False + + gender = dgi.getUint8() + if gender == 1: + gender = 'm' + else: + gender = 'f' + + topTex = dgi.getUint8() + topTexColor = dgi.getUint8() + sleeveTex = dgi.getUint8() + sleeveTexColor = dgi.getUint8() + botTex = dgi.getUint8() + botTexColor = dgi.getUint8() + armColor = dgi.getUint8() + gloveColor = dgi.getUint8() + legColor = dgi.getUint8() + headColor = dgi.getUint8() + + if topTex >= len(Shirts): + return False + if topTexColor >= len(ClothesColors): + return False + if sleeveTex >= len(Sleeves): + return False + if sleeveTexColor >= len(ClothesColors): + return False + if botTex >= choice(gender == 'm', len(BoyShorts), len(GirlBottoms)): + return False + if botTexColor >= len(ClothesColors): + return False + if armColor >= len(allColorsList): + return False + if gloveColor >= len(allColorsList): + return False + if legColor >= len(allColorsList): + return False + if headColor >= len(allColorsList): + return False + + return True + + def makeFromNetString(self, string): + dg=PyDatagram(string) + dgi=PyDatagramIterator(dg) + self.type = dgi.getFixedString(1) + if (self.type == 't'): # Toon + headIndex = dgi.getUint8() + torsoIndex = dgi.getUint8() + legsIndex = dgi.getUint8() + self.head = toonHeadTypes[headIndex] + self.torso = toonTorsoTypes[torsoIndex] + self.legs = toonLegTypes[legsIndex] + gender = dgi.getUint8() + if gender == 1: + self.gender = 'm' + else: + self.gender = 'f' + self.topTex = dgi.getUint8() + self.topTexColor = dgi.getUint8() + self.sleeveTex = dgi.getUint8() + self.sleeveTexColor = dgi.getUint8() + self.botTex = dgi.getUint8() + self.botTexColor = dgi.getUint8() + self.armColor = dgi.getUint8() + self.gloveColor = dgi.getUint8() + self.legColor = dgi.getUint8() + self.headColor = dgi.getUint8() + else: + notify.error("unknown avatar type: ", self.type) + + return None + + # dna methods + def defaultColor(self): + return 25 + + def __defaultColors(self): + """__defaultColors(self) + Set everything to white by default + """ + color = self.defaultColor() + self.armColor = color + # gloves are always white + self.gloveColor = 0 + self.legColor = color + self.headColor = color + + def newToon(self, dna, color = None): + """newToon(self, string(), float=None, float=None, float=None) + Return the dna for the toon described in the given tuple. + If color is specified use for all parts. Otherwise, use the + default color. Use default clothes textures. + """ + if ( len(dna) == 4): + self.type = "t" + self.head = dna[0] + self.torso = dna[1] + self.legs = dna[2] + self.gender = dna[3] + self.topTex = 0 + self.topTexColor = 0 + self.sleeveTex = 0 + self.sleeveTexColor = 0 + self.botTex = 0 + self.botTexColor = 0 + + if (color == None): + color = self.defaultColor() + + self.armColor = color + self.legColor = color + self.headColor = color + + # gloves always white + self.gloveColor = 0 + else: + notify.error("tuple must be in format ('%s', '%s', '%s', '%s')") + + def newToonFromProperties(self, head, torso, legs, gender, + armColor, gloveColor, legColor, headColor, + topTexture, topTextureColor, sleeveTexture, + sleeveTextureColor, bottomTexture, + bottomTextureColor): + """ + Fill in Toon dna using known parameters for all values. Example: + dna.newToonFromProperties('dll', 'md', 'l', 'm', 1, 0, 23, 3, 22, 22, 30) + """ + self.type = 't' + self.head = head + self.torso = torso + self.legs = legs + self.gender = gender + self.armColor = armColor + self.gloveColor = gloveColor + self.legColor = legColor + self.headColor = headColor + self.topTex = topTexture + self.topTexColor = topTextureColor + self.sleeveTex = sleeveTexture + self.sleeveTexColor = sleeveTextureColor + self.botTex = bottomTexture + self.botTexColor = bottomTextureColor + return + + def updateToonProperties(self, head = None, torso = None, legs = None, + gender = None, armColor = None, gloveColor = None, + legColor = None, headColor = None, + topTexture = None, topTextureColor = None, + sleeveTexture = None, + sleeveTextureColor = None, bottomTexture = None, + bottomTextureColor = None, + shirt = None, bottom = None): + + # Changes only the named properties. 'shirt' and 'bottom' are + # special properties that specify an article of clothing with + # a 2-tuple, the string and color index, e.g.: ('bss1', 1) + + assert self.type == 't' + if head: + self.head = head + if torso: + self.torso = torso + if legs: + self.legs = legs + if gender: + self.gender = gender + if armColor: + self.armColor = armColor + if gloveColor: + self.gloveColor = gloveColor + if legColor: + self.legColor = legColor + if headColor: + self.headColor = headColor + if topTexture: + self.topTex = topTexture + if topTextureColor: + self.topTexColor = topTextureColor + if sleeveTexture: + self.sleeveTex = sleeveTexture + if sleeveTextureColor: + self.sleeveTexColor = sleeveTextureColor + if bottomTexture: + # string (see 'c + self.botTex = bottomTexture + if bottomTextureColor: + self.botTexColor = bottomTextureColor + if shirt: + str, colorIndex = shirt + defn = ShirtStyles[str] + self.topTex = defn[0] + self.topTexColor = defn[2][colorIndex][0] + self.sleeveTex = defn[1] + self.sleeveTexColor = defn[2][colorIndex][1] + if bottom: + str, colorIndex = bottom + defn = BottomStyles[str] + self.botTex = defn[0] + self.botTexColor = defn[1][colorIndex] + + return + + def newToonRandom(self, seed = None, gender = "m", npc = 0, stage = None): + """newToonRandom(self, float=None, float=None, float=None) + Return the dna tuple for a random toon. Use random + colors and random clothes textures. + """ + if seed: + generator = random.Random() + generator.seed(seed) + else: + # Just use the normal one + generator = random + + self.type = "t" # Toon. + # Skew the leg length toward medium and long: + self.legs = generator.choice(toonLegTypes + ["m", "l", "l", "l"]) + self.gender = gender + + # We have added the monkey species. It would be weird for existing NPCs + # to change into monkeys so don't use those heads for NPCs. + if not npc: + if (stage == MAKE_A_TOON): + if not base.cr.isPaid(): + animalIndicesToUse = allToonHeadAnimalIndicesTrial + else: + animalIndicesToUse = allToonHeadAnimalIndices + animal = generator.choice(animalIndicesToUse) + self.head = toonHeadTypes[animal] + else: + self.head = generator.choice(toonHeadTypes) + else: + self.head = generator.choice(toonHeadTypes[:22]) + top, topColor, sleeve, sleeveColor = getRandomTop(gender, generator = generator) + bottom, bottomColor = getRandomBottom(gender, generator = generator) + if gender == "m": + self.torso = generator.choice(toonTorsoTypes[:3]) + # Choose a random boy shirt style from MakeAToon + self.topTex = top + self.topTexColor = topColor + self.sleeveTex = sleeve + self.sleeveTexColor = sleeveColor + self.botTex = bottom + self.botTexColor = bottomColor + color = generator.choice(defaultBoyColorList) + self.armColor = color + self.legColor = color + self.headColor = color + else: + self.torso = generator.choice(toonTorsoTypes[:6]) + self.topTex = top + self.topTexColor = topColor + self.sleeveTex = sleeve + self.sleeveTexColor = sleeveColor + +## # Make sure the bottom type matches the torso type +## if (self.torso[1] == 'd'): +## tex, color = getRandomGirlBottomAndColor(SKIRT) +## self.botTex = tex +## self.botTexColor = color +## else: +## tex, color = getRandomGirlBottomAndColor(SKIRT) +## self.botTex = tex +## self.botTexColor = color + + # Make sure the bottom type matches the torso type + if (self.torso[1] == 'd'): + bottom, bottomColor = getRandomBottom(gender, generator = generator, girlBottomType = SKIRT) + else: + bottom, bottomColor = getRandomBottom(gender, generator = generator, girlBottomType = SHORTS) + self.botTex = bottom + self.botTexColor = bottomColor + color = generator.choice(defaultGirlColorList) + self.armColor = color + self.legColor = color + self.headColor = color + + # gloves always white + self.gloveColor = 0 + + def asTuple(self): + return (self.head, self.torso, self.legs, self.gender, + self.armColor, self.gloveColor, self.legColor, self.headColor, + self.topTex, self.topTexColor, self.sleeveTex, + self.sleeveTexColor, self.botTex, self.botTexColor) + + def getType(self): + """getType(self) + Return which type of actor this dna represents. + """ + if (self.type == 't'): + #toon type + type = self.getAnimal() + else: + notify.error("Invalid DNA type: ", self.type) + + return type + + # toon helper funcs + def getAnimal(self): + """getAnimal(self) + Return animal name corresponding to head type as string + """ + if (self.head[0] == 'd'): + return("dog") + elif (self.head[0] == 'c'): + return("cat") + elif (self.head[0] == 'm'): + return("mouse") + elif (self.head[0] == 'h'): + return("horse") + elif (self.head[0] == 'r'): + return("rabbit") + elif (self.head[0] == 'f'): + return("duck") + elif (self.head[0] == 'p'): + return("monkey") + elif (self.head[0] == 'b'): + return("bear") + elif (self.head[0] == 's'): + return("pig") + else: + notify.error("unknown headStyle: ", self.head[0]) + + def getHeadSize(self): + """getHeadSize(self) + Return head size corresponding to head type as string + """ + if (self.head[1] == 'l'): + return("long") + elif (self.head[1] == 's'): + return("short") + else: + notify.error("unknown head size: ", self.head[1]) + + def getMuzzleSize(self): + """getMuzzleSize(self) + Return muzzle size corresponding to head type as string + """ + if (self.head[2] == 'l'): + return("long") + elif (self.head[2] == 's'): + return("short") + else: + notify.error("unknown muzzle size: ", self.head[2]) + + def getTorsoSize(self): + """getTorsoSize(self) + Return the size of the torso as a string + """ + if (self.torso[0] == 'l'): + return("long") + elif (self.torso[0] == 'm'): + return("medium") + elif (self.torso[0] == 's'): + return("short") + else: + notify.error("unknown torso size: ", self.torso[0]) + + def getLegSize(self): + """getLegSize(self) + Return the size of the legs as a string + """ + if (self.legs == 'l'): + return("long") + elif (self.legs == 'm'): + return("medium") + elif (self.legs == 's'): + return("short") + else: + notify.error("unknown leg size: ", self.legs) + + def getGender(self): + return self.gender + + def getClothes(self): + """getClothes(self) + Return the type of clothing as a string + """ + if (len(self.torso) == 1): + return("naked") + elif (self.torso[1] == 's'): + return("shorts") + elif (self.torso[1] == 'd'): + return("dress") + else: + notify.error("unknown clothing type: ", self.torso[1]) + + def getArmColor(self): + try: + return allColorsList[self.armColor] + except: + return allColorsList[0] + + def getLegColor(self): + try: + return allColorsList[self.legColor] + except: + return allColorsList[0] + + def getHeadColor(self): + try: + return allColorsList[self.headColor] + except: + return allColorsList[0] + + def getGloveColor(self): + try: + return allColorsList[self.gloveColor] + except: + return allColorsList[0] + + def getBlackColor(self): + try: + return allColorsList[26] + except: + return allColorsList[0] + + diff --git a/toontown/src/toon/ToonDetail.py b/toontown/src/toon/ToonDetail.py new file mode 100644 index 0000000..db59936 --- /dev/null +++ b/toontown/src/toon/ToonDetail.py @@ -0,0 +1,18 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.avatar import AvatarDetail +from toontown.toon import DistributedToon + +class ToonDetail(AvatarDetail.AvatarDetail): + + notify = directNotify.newCategory("ToonDetail") + + def getDClass(self): + return "DistributedToon" + + def createHolder(self): + toon = DistributedToon.DistributedToon(base.cr, bFake = True) + # getAvatarDetails puts a DelayDelete on the avatar, and this + # is not a real DO, so bypass the 'generated' check + toon.forceAllowDelayDelete() + return toon + diff --git a/toontown/src/toon/ToonHead.py b/toontown/src/toon/ToonHead.py new file mode 100644 index 0000000..4758898 --- /dev/null +++ b/toontown/src/toon/ToonHead.py @@ -0,0 +1,1903 @@ +""" +ToonHead module: contains the ToonHead class + +This class defines just the head portion of a Toon. It's useful for +getting a floating head to put in an Avatar panel for instance; +furthermore, Toon inherits from this class to define its own head +piece. + +""" + +from direct.actor import Actor +from direct.task import Task +from toontown.toonbase import ToontownGlobals +import string +import random +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.fsm.ClassicFSM import ClassicFSM +from direct.fsm.State import State +from direct.directnotify import DirectNotifyGlobal + +# toon head models dictionary +if not base.config.GetBool('want-new-anims', 1): + HeadDict = { "dls": "/models/char/dogMM_Shorts-head-", \ + "dss":"/models/char/dogMM_Skirt-head-", \ + "dsl":"/models/char/dogSS_Shorts-head-", \ + "dll":"/models/char/dogLL_Shorts-head-", \ + "c":"/models/char/cat-heads-", \ + "h":"/models/char/horse-heads-", \ + "m":"/models/char/mouse-heads-", \ + "r":"/models/char/rabbit-heads-", \ + "f":"/models/char/duck-heads-", \ + "p":"/models/char/monkey-heads-", \ + "b":"/models/char/bear-heads-",\ + "s":"/models/char/pig-heads-" + } +else: + HeadDict = { "dls": "/models/char/tt_a_chr_dgm_shorts_head_", \ + "dss":"/models/char/tt_a_chr_dgm_skirt_head_", \ + "dsl":"/models/char/tt_a_chr_dgs_shorts_head_", \ + "dll":"/models/char/tt_a_chr_dgl_shorts_head_", \ + "c":"/models/char/cat-heads-", \ + "h":"/models/char/horse-heads-", \ + "m":"/models/char/mouse-heads-", \ + "r":"/models/char/rabbit-heads-", \ + "f":"/models/char/duck-heads-", \ + "p":"/models/char/monkey-heads-", \ + "b":"/models/char/bear-heads-",\ + "s":"/models/char/pig-heads-" + } + +EyelashDict = {"d": "/models/char/dog-lashes", \ + "c": "/models/char/cat-lashes", \ + "h": "/models/char/horse-lashes", \ + "m": "/models/char/mouse-lashes", \ + "r": "/models/char/rabbit-lashes", \ + "f": "/models/char/duck-lashes", \ + "p": "/models/char/monkey-lashes", \ + "b": "/models/char/bear-lashes",\ + "s": "/models/char/pig-lashes" + } + +DogMuzzleDict = { 'dls': '/models/char/dogMM_Shorts-headMuzzles-', + 'dss': '/models/char/dogMM_Skirt-headMuzzles-', + 'dsl': '/models/char/dogSS_Shorts-headMuzzles-', + 'dll': '/models/char/dogLL_Shorts-headMuzzles-' + } + +class ToonHead(Actor.Actor): + """Toon class:""" + + notify = DirectNotifyGlobal.directNotify.newCategory('ToonHead') + + # Eyes open and closed textures for blinks + EyesOpen = loader.loadTexture('phase_3/maps/eyes.jpg', + 'phase_3/maps/eyes_a.rgb') + EyesOpen.setMinfilter(Texture.FTLinear) + EyesOpen.setMagfilter(Texture.FTLinear) + EyesClosed = loader.loadTexture('phase_3/maps/eyesClosed.jpg', + 'phase_3/maps/eyesClosed_a.rgb') + EyesClosed.setMinfilter(Texture.FTLinear) + EyesClosed.setMagfilter(Texture.FTLinear) + + # Emotional eye textures for tutorial + EyesSadOpen = loader.loadTexture('phase_3/maps/eyesSad.jpg', + 'phase_3/maps/eyesSad_a.rgb') + EyesSadOpen.setMinfilter(Texture.FTLinear) + EyesSadOpen.setMagfilter(Texture.FTLinear) + EyesSadClosed = loader.loadTexture('phase_3/maps/eyesSadClosed.jpg', + 'phase_3/maps/eyesSadClosed_a.rgb') + EyesSadClosed.setMinfilter(Texture.FTLinear) + EyesSadClosed.setMagfilter(Texture.FTLinear) + EyesAngryOpen = loader.loadTexture('phase_3/maps/eyesAngry.jpg', + 'phase_3/maps/eyesAngry_a.rgb') + EyesAngryOpen.setMinfilter(Texture.FTLinear) + EyesAngryOpen.setMagfilter(Texture.FTLinear) + EyesAngryClosed = loader.loadTexture('phase_3/maps/eyesAngryClosed.jpg', + 'phase_3/maps/eyesAngryClosed_a.rgb') + EyesAngryClosed.setMinfilter(Texture.FTLinear) + EyesAngryClosed.setMagfilter(Texture.FTLinear) + EyesSurprised = loader.loadTexture('phase_3/maps/eyesSurprised.jpg', + 'phase_3/maps/eyesSurprised_a.rgb') + EyesSurprised.setMinfilter(Texture.FTLinear) + EyesSurprised.setMagfilter(Texture.FTLinear) + Muzzle = loader.loadTexture('phase_3/maps/muzzleShrtGeneric.jpg') + Muzzle.setMinfilter(Texture.FTLinear) + Muzzle.setMagfilter(Texture.FTLinear) + MuzzleSurprised = loader.loadTexture('phase_3/maps/muzzleShortSurprised.jpg') + MuzzleSurprised.setMinfilter(Texture.FTLinear) + MuzzleSurprised.setMagfilter(Texture.FTLinear) + + + # We define four points around the perimeter of each eye, as + # seen from the front: + # + # A B A B + # + # * * + # D C D C + # + # These four points represent the extremes of motion of the + # pupil in each of the diagonal directions. These four + # points, along with the origin approximated above with *, + # determine the mapping of pupil directions to pupil + # coordinates. + LeftA = Point3(0.06, 0.0, 0.14) + LeftB = Point3(-0.13, 0.0, 0.1) + LeftC = Point3(-0.05, 0.0, 0.0) + LeftD = Point3(0.06, 0.0, 0.0) + RightA = Point3(0.13, 0.0, 0.1) + RightB = Point3(-0.06, 0.0, 0.14) + RightC = Point3(-0.06, 0.0, 0.0) + RightD = Point3(0.05, 0.0, 0.0) + # This point is the between LeftA and LeftD where y = 0. + LeftAD = Point3(LeftA[0] - + (LeftA[2] * (LeftD[0] - LeftA[0]) / + (LeftD[2] - LeftA[2])), + 0.0, 0.0) + # This point is the between LeftB and LeftC where y = 0. + LeftBC = Point3(LeftB[0] - + (LeftB[2] * (LeftC[0] - LeftB[0]) / + (LeftC[2] - LeftB[2])), + 0.0, 0.0) + # Similarly on the right eye. + RightAD = Point3(RightA[0] - + (RightA[2] * (RightD[0] - RightA[0]) / + (RightD[2] - RightA[2])), + 0.0, 0.0) + RightBC = Point3(RightB[0] - + (RightB[2] * (RightC[0] - RightB[0]) / + (RightC[2] - RightB[2])), + 0.0, 0.0) + + + def __init__(self): + try: + self.ToonHead_initialized + except: + self.ToonHead_initialized = 1 + Actor.Actor.__init__(self) + + # This is a unique string that identifies this particular + # ToonHead among all others. It's used to generate task + # names; we can't necessarily use doId, because we might + # not have one at this level. + self.toonName = 'ToonHead-' + str(self.this) + + # Here are some of those task names we were talking about. + self.__blinkName = 'blink-' + self.toonName + self.__stareAtName = 'stareAt-' + self.toonName + self.__lookName = 'look-' + self.toonName + self.lookAtTrack = None + + # Set up a simple state machine to manage the eyelids. + + self.__eyes = None + self.__eyelashOpen = None + self.__eyelashClosed = None + self.__lod500Eyes = None + self.__lod250Eyes = None + self.__lpupil = None + self.__lod500lPupil = None + self.__lod250lPupil = None + self.__rpupil = None + self.__lod500rPupil = None + self.__lod250rPupil = None + self.__muzzle = None + self.__eyesOpen = ToonHead.EyesOpen + self.__eyesClosed = ToonHead.EyesClosed + self.__height = 0.0 + + # Create our own random number generator. We do this + # mainly so we don't jumble up the random number chain of + # the rest of the world (making playback from a session + # more reliable). + self.randGen = random.Random() + self.randGen.seed(random.random()) + + self.eyelids = ClassicFSM('eyelids', + [State('off', + self.enterEyelidsOff, + self.exitEyelidsOff, + ['open', 'closed', 'surprised']), + State('open', + self.enterEyelidsOpen, + self.exitEyelidsOpen, + ['closed', 'surprised', 'off']), + State('surprised', + self.enterEyelidsSurprised, + self.exitEyelidsSurprised, + ['open', 'closed', 'off']), + State('closed', + self.enterEyelidsClosed, + self.exitEyelidsClosed, + ['open', 'surprised', 'off'])], + # initial State + 'off', + # final State + 'off', + ) + + self.eyelids.enterInitialState() + self.emote = None + + # This is the node and the point relative to the node that + # the stareAt task will make the ToonHead look at. + self.__stareAtNode = NodePath() + self.__defaultStarePoint = Point3(0, 0, 0) + self.__stareAtPoint = self.__defaultStarePoint + self.__stareAtTime = 0 + self.lookAtPositionCallbackArgs = None + + return None + + def delete(self): + try: + self.ToonHead_deleted + except: + self.ToonHead_deleted = 1 + taskMgr.remove(self.__blinkName) + taskMgr.remove(self.__lookName) + taskMgr.remove(self.__stareAtName) + if self.lookAtTrack: + self.lookAtTrack.finish() + self.lookAtTrack = None + del self.eyelids + del self.__stareAtNode + del self.__stareAtPoint + if self.__eyes: + del self.__eyes + if self.__lpupil: + del self.__lpupil + if self.__rpupil: + del self.__rpupil + if self.__eyelashOpen: + del self.__eyelashOpen + if self.__eyelashClosed: + del self.__eyelashClosed + self.lookAtPositionCallbackArgs = None + Actor.Actor.delete(self) + + def setupHead(self, dna, forGui = 0): + """setupHead(self, AvatarDNA dna) + + Loads the high-resolution head model only and plays the + neutral animation on it. Useful only when you want a floating + head, not a complete Toon. + + If forGui is true, this sets up the head for placement within + the DirectGui system; specifically, the depth test and write + transitions are set appropriately, and the eyes are fixed so + they will render properly. For making a head to parent in the + 3-d world, leave forGui false. + + This also scales the head by toonHeadScale and toonBodyScale, + so that all heads are relatively sized. + + """ + self.__height = self.generateToonHead(1, dna, ("1000",), forGui) + self.generateToonColor(dna) + + animalStyle = dna.getAnimal() + bodyScale = ToontownGlobals.toonBodyScales[animalStyle] + headScale = ToontownGlobals.toonHeadScales[animalStyle] + + # We also scale up by 1.3 to compensate for legacy code that + # was written before we started applying the bodyScale. + self.getGeomNode().setScale(headScale[0] * bodyScale * 1.3, + headScale[1] * bodyScale * 1.3, + headScale[2] * bodyScale * 1.3) + + if forGui: + # Turn on depth write and test. + self.getGeomNode().setDepthWrite(1) + self.getGeomNode().setDepthTest(1) + + if dna.getAnimal() == "dog": + self.loop("neutral") + + def fitAndCenterHead(self, maxDim, forGui = 0): + # Compute an xform which centers geometry on origin and scales it + # to max +/- maxDim/2.0 in size + p1 = Point3() + p2 = Point3() + self.calcTightBounds(p1, p2) + # Take into account rotation by 180 degrees if necessary + if forGui: + h = 180 + # Need to flip max and min + t = p1[0] + p1.setX(-p2[0]) + p2.setX(-t) + else: + h = 0 + # Find dimension + d = p2 - p1 + biggest = max(d[0], d[2]) + s = maxDim/biggest + # find midpoint + mid = (p1 + d/2.0) * s + # We must push the head a distance forward in Y so it doesn't + # intersect the near plane, which is incorrectly set to 0 in + # DX for some reason. + self.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], + h, 0, 0, + s, s, s) + + def setLookAtPositionCallbackArgs(self, argTuple): + # findSomethingToLookAt assumes argTuple is an array + # of obj w/'getLookAtPosition' method, and method argument + self.lookAtPositionCallbackArgs = argTuple + + def getHeight(self): + return self.__height + + def getRandomForwardLookAtPoint(self): + x = self.randGen.choice((-0.8, -0.5, 0, 0.5, 0.8)) + z = self.randGen.choice((-0.5, 0, 0.5, 0.8)) + return Point3(x, 1.5, z) + + def findSomethingToLookAt(self): + """findSomethingToLookAt(self) + + Picks a random direction at spawns a StareAt task to look in + that direction. This is called from time to time by the LookAround task. + + This is overridden in Toon.py to find something in + particular to look at. + + """ + if(self.lookAtPositionCallbackArgs != None): + pnt = self.lookAtPositionCallbackArgs[0].getLookAtPosition(self.lookAtPositionCallbackArgs[1],self.lookAtPositionCallbackArgs[2]) + self.startStareAt(self, pnt) + return + + if(self.randGen.random() < 0.33): + lookAtPnt = self.getRandomForwardLookAtPoint() + else: + lookAtPnt = self.__defaultStarePoint + + # Now look that direction! + self.lerpLookAt(lookAtPnt, blink=1) + + # Generate and color methods. These are called by setupHead() and + # by Toon.py to create and color a head. + + def generateToonHead(self, copy, style, lods, forGui = 0): + """generateToonHead(self, bool copy, AvatarDNA style, + tuple lods) + Load the head model for the toon. + If copy = 0, instance geom instead of copying. + """ + headStyle = style.head + #ToonHead.notify.debug('RAU headstyle = %s' % headStyle) + + # this will store the method we need + # to call to hide various head parts + fix = None + + # load the appropriate file + if (headStyle == "dls"): + # dog, long head, short muzzle + filePrefix = HeadDict["dls"] + headHeight = 0.75 + elif (headStyle == "dss"): + # dog, short head, short muzzle + filePrefix = HeadDict["dss"] + headHeight = 0.5 + elif (headStyle == "dsl"): + # dog, short head, long muzzle + filePrefix = HeadDict["dsl"] + headHeight = 0.5 + elif (headStyle == "dll"): + # dog, long head, long muzzle + filePrefix = HeadDict["dll"] + headHeight = 0.75 + elif (headStyle == "cls"): + # cat, long head, short muzzle + filePrefix = HeadDict["c"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "css"): + # cat, short head, short muzzle + filePrefix = HeadDict["c"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "csl"): + # cat, short head, long muzzle + filePrefix = HeadDict["c"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "cll"): + # cat, long head, long muzzle + filePrefix = HeadDict["c"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "hls"): + # horse, long head, short muzzle + filePrefix = HeadDict["h"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "hss"): + # horse, short head, short muzzle + filePrefix = HeadDict["h"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "hsl"): + # horse, short head, long muzzle + filePrefix = HeadDict["h"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "hll"): + # horse, long head, long muzzle + filePrefix = HeadDict["h"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "mls"): + # mouse, long head, short muzzle + filePrefix = HeadDict["m"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "mss"): + # mouse, short head, short muzzle + filePrefix = HeadDict["m"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "rls"): + # rabbit, long head/muzzle, short ears + filePrefix = HeadDict["r"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "rss"): + # rabbit, short head/muzzle, short ears + filePrefix = HeadDict["r"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "rsl"): + # rabbit, short head/muzzle, long ears + filePrefix = HeadDict["r"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "rll"): + # rabbit, long head/muzzle, long ears + filePrefix = HeadDict["r"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "fls"): + # duck, long head, short bill + filePrefix = HeadDict["f"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "fss"): + # duck, short head, short bill + filePrefix = HeadDict["f"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "fsl"): + # duck, short head, long bill + filePrefix = HeadDict["f"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "fll"): + # duck, long head, long bill + filePrefix = HeadDict["f"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "pls"): + # monkey, long head, short muzzle + filePrefix = HeadDict["p"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "pss"): + # monkey, short head, short muzzle + filePrefix = HeadDict["p"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "psl"): + # monkey, short head, long muzzle + filePrefix = HeadDict["p"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "pll"): + # monkey, long head, long muzzle + filePrefix = HeadDict["p"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "bls"): + # bear, long head, short muzzle + filePrefix = HeadDict["b"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "bss"): + # bear, short head, short muzzle + filePrefix = HeadDict["b"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "bsl"): + # bear, short head, long muzzle + filePrefix = HeadDict["b"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "bll"): + # bear, long head, long muzzle + filePrefix = HeadDict["b"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + elif (headStyle == "sls"): + # pig, long head, short muzzle + filePrefix = HeadDict["s"] + fix = self.__fixHeadLongShort + headHeight = 0.75 + elif (headStyle == "sss"): + # pig, short head, short muzzle + filePrefix = HeadDict["s"] + fix = self.__fixHeadShortShort + headHeight = 0.5 + elif (headStyle == "ssl"): + # pig, short head, long muzzle + filePrefix = HeadDict["s"] + fix = self.__fixHeadShortLong + headHeight = 0.5 + elif (headStyle == "sll"): + # pig, long head, long muzzle + filePrefix = HeadDict["s"] + fix = self.__fixHeadLongLong + headHeight = 0.75 + else: + ToonHead.notify.error("unknown head style: %s" % headStyle) + + # load the model and massage the geometry + if len(lods) == 1: + self.loadModel("phase_3" + filePrefix + lods[0], "head", "lodRoot", + copy) + if not forGui: + pLoaded = self.loadPumpkin(headStyle[1], None, copy) + self.loadSnowMan(headStyle[1], None, copy) + + if not copy: + self.showAllParts('head') + if fix != None: + fix(style, None, copy) + + if not forGui: + if pLoaded: + self.__fixPumpkin(style, None, copy) + else: + self.__lods = lods + self.__style = style + self.__headStyle = headStyle + self.__copy = copy + + else: + for lod in lods: + self.loadModel("phase_3" + filePrefix + lod, "head", lod, copy) + if not forGui: + pLoaded = self.loadPumpkin(headStyle[1], lod, copy) + self.loadSnowMan(headStyle[1], lod, copy) + + if not copy: + self.showAllParts('head', lod) + if fix != None: + fix(style, lod, copy) + + if not forGui: + if pLoaded: + self.__fixPumpkin(style, lod, copy) + else: + self.__lods = lods + self.__style = style + self.__headStyle = headStyle + self.__copy = copy + + # use the appropriate eye texture + self.__fixEyes(style, forGui) + self.setupEyelashes(style) + self.eyelids.request("closed") + self.eyelids.request("open") + self.setupMuzzles(style) + + return headHeight + + def loadPumpkin(self,headStyle, lod, copy): + if (hasattr(base, 'launcher') and + ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(4)))): + + if not hasattr(self,'pumpkins'): + self.pumpkins = NodePathCollection() + + ppath = 'phase_4/models/estate/pumpkin_' + if(headStyle is 'l'): + if copy: + pmodel = loader.loadModel(ppath + 'tall') + else: + pmodel = loader.loadModel(ppath + 'tall') + ptype = 'tall' + else: + if copy: + pmodel = loader.loadModel(ppath + 'short') + else: + pmodel = loader.loadModel(ppath + 'short') + ptype = 'short' + + if pmodel: + p = pmodel.find('**/pumpkin_'+ptype+'*') + p.setScale(0.5) + p.setZ(-0.5) + p.setH(180) + if lod: + p.reparentTo(self.find('**/' + lod + '/**/__Actor_head')) + else: + p.reparentTo(self.find('**/__Actor_head')) + self.pumpkins.addPath(p) + pmodel.remove() + return True + else: + del self.pumpkins + return False + else: + ToonHead.notify.debug("phase_4 not complete yet. Postponing pumpkin head load.") + + def loadSnowMan(self, headStyle, lod, copy): + if hasattr(base, 'launcher') and ((not base.launcher) or + (base.launcher and base.launcher.getPhaseComplete(4))): + if not hasattr(self, 'snowMen'): + self.snowMen = NodePathCollection() + + snowManPath = 'phase_4/models/props/tt_m_int_snowmanHead_' + if headStyle is 'l': + snowManPath = snowManPath+'tall' + else: + snowManPath = snowManPath + 'short' + + model = loader.loadModel(snowManPath) + if model: + model.setScale(0.4) + model.setZ(-0.5) + model.setH(180) + if lod: + model.reparentTo(self.getPart('head',lod)) + else: + model.reparentTo(self.find('**/__Actor_head')) + self.snowMen.addPath(model) + model.stash() + return True + else: + del self.snowMen + return False + else: + ToonHead.notify.debug("phase_4 not loaded yet.") + + def __fixPumpkin(self, style, lodName = None, copy = 1): + if (lodName == None): + searchRoot = self + else: + searchRoot = self.find("**/" + str(lodName)) + + pumpkin = searchRoot.find( "**/__Actor_head/pumpkin*" ) + pumpkin.stash() + + def enablePumpkins(self, enable): + # This is for the case that the head was generated before the pumpkin model was available, due to phase loading. + # It uses a few temporary private variables created when the initial pumpkin load attempt failed. + # If successful, it should clean those variables up. + if not hasattr(self,'pumpkins'): + if len(self.__lods) == 1: + pLoaded = self.loadPumpkin(self.__headStyle[1], None, self.__copy) + if pLoaded: + self.__fixPumpkin(self.__style, None, self.__copy) + else: + for lod in self.__lods: + pLoaded = self.loadPumpkin(self.__headStyle[1], lod, self.__copy) + if pLoaded: + self.__fixPumpkin(self.__style, lod, self.__copy) + + if hasattr(self,'pumpkins'): + for x in ['__lods','__style','__headStyle','__copy']: + if hasattr(self,'_ToonHead'+x): + delattr(self,'_ToonHead'+x) + + if hasattr(self,'pumpkins'): + if enable: + if self.__eyelashOpen: + self.__eyelashOpen.stash() + if self.__eyelashClosed: + self.__eyelashClosed.stash() + self.pumpkins.unstash() + else: + if self.__eyelashOpen: + self.__eyelashOpen.unstash() + if self.__eyelashClosed: + self.__eyelashClosed.unstash() + self.pumpkins.stash() + + def enableSnowMen(self, enable): + if not hasattr(self, 'snowMen'): + if len(self.__lods) == 1: + self.loadSnowMan(self.__headStyle[1], None, self.__copy) + else: + for lod in self.__lds: + self.loadSnowMan(self.__headStyle[1], lod, self.__copy) + + if hasattr(self, 'snowMen'): + if enable: + if self.__eyelashOpen: + self.__eyelashOpen.stash() + if self.__eyelashClosed: + self.__eyelashClosed.stash() + self.snowMen.unstash() + else: + if self.__eyelashOpen: + self.__eyelashOpen.unstash() + if self.__eyelashClosed: + self.__eyelashClosed.unstash() + self.snowMen.stash() + + def generateToonColor(self, style): + """generateToonColor(self, AvatarDNA style) + Color the toon's parts as specified by the dna. + Color any LODs by searching for ALL matches. + """ + + # color the head - may have multiple pieces + parts = self.findAllMatches("**/head*") + parts.setColor(style.getHeadColor()) + + # color the ears, if they are not black + animalType = style.getAnimal() + if ((animalType == "cat") or + (animalType == "rabbit") or + (animalType == 'bear') or + (animalType == "mouse") or + (animalType == "pig")): + parts = self.findAllMatches("**/ear?-*") + parts.setColor(style.getHeadColor()) + + def __fixEyes(self, style, forGui = 0): + """__fixEyes(self, AvatarDNA style) + Make sure the eyes render in proper order, and don't attempt + to animate the pupils via the animation. Instead, we may move + them around directly.""" + + mode = -3 + if forGui: + mode = -2 + + if (self.hasLOD()): + for lodName in self.getLODNames(): + self.drawInFront("eyes*", "head-front*", mode, lodName=lodName) + # NOTE: had to change all ref's to "joint-" to "joint_" as Maya + # does not support "-" in node names + if base.config.GetBool('want-new-anims', 1): + if not self.find("**/joint_pupil*").isEmpty(): + self.drawInFront("joint_pupil*", "eyes*", -1, lodName=lodName) + else: + self.drawInFront("def_*_pupil", "eyes*", -1, lodName=lodName) + else: + self.drawInFront("joint_pupil*", "eyes*", -1, lodName=lodName) + + # Save the various eye LODs for blinking. + self.__eyes = self.getLOD(1000).find('**/eyes*') + self.__lod500Eyes = self.getLOD(500).find('**/eyes*') + self.__lod250Eyes = self.getLOD(250).find('**/eyes*') + + # Now make sure the eyes are still white--they might tend to + # inherit the color from the head otherwise. + + if self.__lod500Eyes.isEmpty(): + self.__lod500Eyes = None + else: + self.__lod500Eyes.setColorOff() + if base.config.GetBool('want-new-anims', 1): + if not self.find('**/joint_pupilL*').isEmpty(): + self.__lod500lPupil = self.__lod500Eyes.find('**/joint_pupilL*') + self.__lod500rPupil = self.__lod500Eyes.find('**/joint_pupilR*') + else: + self.__lod500lPupil = self.__lod500Eyes.find('**/def_left_pupil*') + self.__lod500rPupil = self.__lod500Eyes.find('**/def_right_pupil*') + else: + self.__lod500lPupil = self.__lod500Eyes.find('**/joint_pupilL*') + self.__lod500rPupil = self.__lod500Eyes.find('**/joint_pupilR*') + if self.__lod250Eyes.isEmpty(): + self.__lod250Eyes = None + else: + self.__lod250Eyes.setColorOff() + if base.config.GetBool('want-new-anims', 1): + if not self.find('**/joint_pupilL*').isEmpty(): + self.__lod250lPupil = self.__lod250Eyes.find('**/joint_pupilL*') + self.__lod250rPupil = self.__lod250Eyes.find('**/joint_pupilR*') + else: + self.__lod250lPupil = self.__lod250Eyes.find('**/def_left_pupil*') + self.__lod250rPupil = self.__lod250Eyes.find('**/def_right_pupil*') + else: + self.__lod250lPupil = self.__lod250Eyes.find('**/joint_pupilL*') + self.__lod250rPupil = self.__lod250Eyes.find('**/joint_pupilR*') + else: + self.drawInFront("eyes*", "head-front*", mode) + if base.config.GetBool('want-new-anims', 1): + if not self.find("joint_pupil*").isEmpty(): + self.drawInFront("joint_pupil*", "eyes*", -1) + else: + self.drawInFront("def_*_pupil", "eyes*", -1) + else: + self.drawInFront("joint_pupil*", "eyes*", -1) + # Save the eyes for blinking. + self.__eyes = self.find('**/eyes*') + + # Now locate each pupil and put it in a local coordinate space so + # we can easily slide it around on the face. + if not self.__eyes.isEmpty(): + self.__eyes.setColorOff() + self.__lpupil = None + self.__rpupil = None + if base.config.GetBool('want-new-anims', 1): + if not self.find('**/joint_pupilL*').isEmpty(): + if self.getLOD(1000): + lp = self.getLOD(1000).find('**/joint_pupilL*') + rp = self.getLOD(1000).find('**/joint_pupilR*') + else: + lp = self.find('**/joint_pupilL*') + rp = self.find('**/joint_pupilR*') + else: + if not self.getLOD(1000): + lp = self.find('**/def_left_pupil*') + rp = self.find('**/def_right_pupil*') + else: + lp = self.getLOD(1000).find('**/def_left_pupil*') + rp = self.getLOD(1000).find('**/def_right_pupil*') + else: + lp = self.__eyes.find('**/joint_pupilL*') + rp = self.__eyes.find('**/joint_pupilR*') + + if lp.isEmpty() or rp.isEmpty(): + print "Unable to locate pupils." + else: + leye = self.__eyes.attachNewNode('leye') + reye = self.__eyes.attachNewNode('reye') + + # These matrices were determined empirically. They + # setup a coordinate space so that the X-Y plane is + # parallel to the eye, and 0,0,0 is in the pupil's + # natural resting place. + lmat = Mat4(0.802174, 0.59709, 0, 0, + -0.586191, 0.787531, 0.190197, 0, + 0.113565, -0.152571, 0.981746, 0, + -0.233634, 0.418062, 0.0196875, 1) + leye.setMat(lmat) + + rmat = Mat4(0.786788, -0.617224, 0, 0, + 0.602836, 0.768447, 0.214658, 0, + -0.132492, -0.16889, 0.976689, 0, + 0.233634, 0.418062, 0.0196875, 1) + reye.setMat(rmat) + + # Now move the pupils into their new coordinate + # spaces, and flatten out the vertices so when they're + # at (0,0,0) they're in the right place. + self.__lpupil = leye.attachNewNode('lpupil') + self.__rpupil = reye.attachNewNode('rpupil') + lpt = self.__eyes.attachNewNode('') + rpt = self.__eyes.attachNewNode('') + lpt.wrtReparentTo(self.__lpupil) + rpt.wrtReparentTo(self.__rpupil) + lp.reparentTo(lpt) + rp.reparentTo(rpt) + + # Also bump up the override parameter on the pupil + # textures so they won't get overridden when we set + # the blink texture. + + self.__lpupil.adjustAllPriorities(1) + self.__rpupil.adjustAllPriorities(1) + if self.__lod500Eyes: + self.__lod500lPupil.adjustAllPriorities(1) + self.__lod500rPupil.adjustAllPriorities(1) + if self.__lod250Eyes: + self.__lod250lPupil.adjustAllPriorities(1) + self.__lod250rPupil.adjustAllPriorities(1) + + # This breaks the "animating" dog eyes. For now, + # we'll only flatten if we haven't got a dog. + animalType = style.getAnimal() + if animalType != "dog": + self.__lpupil.flattenStrong() + self.__rpupil.flattenStrong() + + def __setPupilDirection(self, x, y): + """__setPupilDirection(self, float x, float y) + + Sets both pupils to look in the indicated direction, where x + and y are both in the range [-1 .. 1] and represent the + direction the eyes should look relative to forward: [0, 0] is + straight ahead, [-1, 0] is to the left, and [0, 1] is straight + up. + + """ + + # Compute the points on the left and right edges of each eye, + # corresponding to the y height. + if y < 0.0: + y2 = -y + left1 = self.LeftAD + (self.LeftD - self.LeftAD) * y2 + left2 = self.LeftBC + (self.LeftC - self.LeftBC) * y2 + right1 = self.RightAD + (self.RightD - self.RightAD) * y2 + right2 = self.RightBC + (self.RightC - self.RightBC) * y2 + else: + y2 = y + left1 = self.LeftAD + (self.LeftA - self.LeftAD) * y2 + left2 = self.LeftBC + (self.LeftB - self.LeftBC) * y2 + right1 = self.RightAD + (self.RightA - self.RightAD) * y2 + right2 = self.RightBC + (self.RightB - self.RightBC) * y2 + + # Now interpolate between these points based on the x position. + left0 = Point3(0.0, 0.0, + left1[2] - + (left1[0] * (left2[2] - left1[2]) / + (left2[0] - left1[0]))) + right0 = Point3(0.0, 0.0, + right1[2] - + (right1[0] * (right2[2] - right1[2]) / + (right2[0] - right1[0]))) + + if x < 0.0: + x2 = -x + left = left0 + (left2 - left0) * x2 + right = right0 + (right2 - right0) * x2 + else: + x2 = x + left = left0 + (left1 - left0) * x2 + right = right0 + (right1 - right0) * x2 + + self.__lpupil.setPos(left) + self.__rpupil.setPos(right) + + def __lookPupilsAt(self, node, point): + """__lookPupilsAt(self, NodePath node, Point3 point) + + Positions the pupils to look, as nearly as possible, toward + the indicated point in space, relative to the indicated + NodePath. + + If node is None, it is a special indication that point + represents a vector relative to the head, not necessarily a + particular point in space. + + """ + + # First, we need to convert the point to the coordinate space + # of our eyes. + + if node != None: + mat = node.getMat(self.__eyes) + point = mat.xformPoint(point) + + # Now, determine the intersection with a plane a certain + # distance in front of our face of a line drawn from the + # center of our head through the point. This maps the point + # in space to a 2-d pupil offse. + + distance = 1.0 + recip_z = 1.0/max(0.1, point[1]) + + x = distance * point[0] * recip_z + y = distance * point[2] * recip_z + + # Now clamp these to -1 .. 1. + x = min(max(x, -1), 1) + y = min(max(y, -1), 1) + + self.__setPupilDirection(x, y) + + + def __lookHeadAt(self, node, point, frac = 1.0, lod = None): + """__lookHeadAt(self, NodePath node, Point3 point, float frac) + + Positions the head to look, as nearly as possible, toward + the indicated point in space, relative to the indicated + NodePath. + + If frac is specified, it should be a number between 0 and 1 + and indicates what fraction of the rotation should be made + this frame. + + If node is None, it is a special indication that point + represents a vector relative to the head, not necessarily a + particular point in space. + + The return value is true if we are within 1 degree of our + target, false otherwise. + + """ + + reachedTarget = 1 + + # First, we need to get the relative transform from the head's + # parent. We'll assume the same transform applies to all + # heads, and just pick the first LOD head for this. + + if lod == None: + head = self.getPart('head', self.getLODNames()[0]) + else: + head = self.getPart('head', lod) + + if node != None: + headParent = head.getParent() + mat = node.getMat(headParent) + point = mat.xformPoint(point) + + rot = Mat3(0, 0, 0, 0, 0, 0, 0, 0, 0) + lookAt(rot, Vec3(point), Vec3(0, 0, 1), CSDefault) + + scale = VBase3(0, 0, 0) + hpr = VBase3(0, 0, 0) + if decomposeMatrix(rot, scale, hpr, CSDefault): + # Clamp the HPR to a natural range. I believe H should + # be in the range -60 .. 60, while P should be in the + # range -20 .. 30. More than this seems unnatural. + + hpr = VBase3(min(max(hpr[0], -60), 60), + min(max(hpr[1], -20), 30), + 0) + + if frac != 1: + # Rotate only part of the way. This must be based on + # our current rotation. + currentHpr = head.getHpr() + + reachedTarget = (abs(hpr[0] - currentHpr[0]) < 1.0) and \ + (abs(hpr[1] - currentHpr[1]) < 1.0) + + hpr = currentHpr + (hpr - currentHpr) * frac + + + # Now rotate each of the heads. + if lod == None: + for lodName in self.getLODNames(): + head = self.getPart('head', lodName) + head.setHpr(hpr) + else: + head.setHpr(hpr) + + return reachedTarget + + def setupEyelashes(self, style): + # if the toon is male no need to set lashes + if style.getGender() == 'm': + # if eyelashes are present, remove them + if self.__eyelashOpen: + self.__eyelashOpen.removeNode() + self.__eyelashOpen = None + if self.__eyelashClosed: + self.__eyelashClosed.removeNode() + self.__eyelashClosed = None + else: + # it's a female load the appropriate eyelash models + if self.__eyelashOpen: + self.__eyelashOpen.removeNode() + if self.__eyelashClosed: + self.__eyelashClosed.removeNode() + animal = style.head[0] + model = loader.loadModel('phase_3' + EyelashDict[animal]) + if self.hasLOD(): + # probably can't see them beyond 1st LOD... + head = self.getPart('head', '1000') + else: + head = self.getPart('head', 'lodRoot') + # determine which type of lash to use (long or short) + length = style.head[1] + if length == "l": + openString = "open-long" + closedString = "closed-long" + else: + openString = "open-short" + closedString = "closed-short" + self.__eyelashOpen = model.find("**/" + openString).copyTo(head) + self.__eyelashClosed = model.find("**/" + closedString).copyTo(head) + model.removeNode() + + def __fixHeadLongLong(self, style, lodName=None, copy = 1): + """__fixHeadLongLong(self, AvatarDNA style, string=None, bool = 1) + Hide all short parts""" + if (lodName == None): + searchRoot = self + else: + searchRoot = self.find("**/" + str(lodName)) + + otherParts = searchRoot.findAllMatches( "**/*short*" ) + for partNum in range(0, otherParts.getNumPaths()): + if copy: + otherParts.getPath(partNum).removeNode() + else: + otherParts.getPath(partNum).stash() + + + def __fixHeadLongShort(self, style, lodName=None, copy = 1): + """__fixHeadLongShort(self, AvatarDNA style, string=None, bool = 1) + Hide the short heads and long muzzles - some special cases""" + animalType = style.getAnimal() + headStyle = style.head + if (lodName == None): + searchRoot = self + else: + searchRoot = self.find("**/" + str(lodName)) + + # if there are ears to switch + if (animalType != "duck") and (animalType != "horse"): + # rabbits are reversed + if (animalType == "rabbit"): + if copy: + searchRoot.find("**/ears-long").removeNode() + else: + searchRoot.find("**/ears-long").hide() + else: + if copy: + searchRoot.find("**/ears-short").removeNode() + else: + searchRoot.find("**/ears-short").hide() + + # rabbits only have one type of eye poly + if (animalType != "rabbit"): + if copy: + searchRoot.find("**/eyes-short").removeNode() + else: + searchRoot.find("**/eyes-short").hide() + + # Now every animal except dog has 2 types of pupils except the dog + if animalType != 'dog': + if copy: + searchRoot.find("**/joint_pupilL_short").removeNode() + searchRoot.find("**/joint_pupilR_short").removeNode() + else: + searchRoot.find("**/joint_pupilL_short").stash() + searchRoot.find("**/joint_pupilR_short").stash() + + # hide the short head + if copy: + self.find("**/head-short").removeNode() + self.find("**/head-front-short").removeNode() + else: + self.find("**/head-short").hide() + self.find("**/head-front-short").hide() + + # rabbits are backwards once again + if (animalType != "rabbit"): + muzzleParts = searchRoot.findAllMatches("**/muzzle-long*") + for partNum in range(0, muzzleParts.getNumPaths()): + if copy: + muzzleParts.getPath(partNum).removeNode() + else: + muzzleParts.getPath(partNum).hide() + else: + muzzleParts = searchRoot.findAllMatches("**/muzzle-short*") + for partNum in range(0, muzzleParts.getNumPaths()): + if copy: + muzzleParts.getPath(partNum).removeNode() + else: + muzzleParts.getPath(partNum).hide() + + def __fixHeadShortLong(self, style, lodName=None, copy = 1): + """__fixHeadShortLong(self, AvatarDNA style, string=None, bool = 1) + Hide the long heads and short muzzles - some special cases""" + animalType = style.getAnimal() + headStyle = style.head + if (lodName == None): + searchRoot = self + else: + searchRoot = self.find("**/" + str(lodName)) + + # if there are ears to switch + if (animalType != "duck") and (animalType != "horse") : + # rabbits are reversed + if (animalType == "rabbit"): + if copy: + searchRoot.find("**/ears-short").removeNode() + else: + searchRoot.find("**/ears-short").hide() + else: + if copy: + searchRoot.find("**/ears-long").removeNode() + else: + searchRoot.find("**/ears-long").hide() + + # rabbits only have one type of eye poly + if (animalType != "rabbit"): + if copy: + searchRoot.find("**/eyes-long").removeNode() + else: + searchRoot.find("**/eyes-long").hide() + + # Now every animal except dog has 2 types of pupils except the dog + if animalType != 'dog': + if copy: + searchRoot.find("**/joint_pupilL_long").removeNode() + searchRoot.find("**/joint_pupilR_long").removeNode() + else: + searchRoot.find("**/joint_pupilL_long").stash() + searchRoot.find("**/joint_pupilR_long").stash() + + # hide the short head + if copy: + searchRoot.find("**/head-long").removeNode() + searchRoot.find("**/head-front-long").removeNode() + else: + searchRoot.find("**/head-long").hide() + searchRoot.find("**/head-front-long").hide() + + # rabbits are backwards once again + if (animalType != "rabbit"): + muzzleParts = searchRoot.findAllMatches("**/muzzle-short*") + for partNum in range(0, muzzleParts.getNumPaths()): + if copy: + muzzleParts.getPath(partNum).removeNode() + else: + muzzleParts.getPath(partNum).hide() + else: + muzzleParts = searchRoot.findAllMatches("**/muzzle-long*") + for partNum in range(0, muzzleParts.getNumPaths()): + if copy: + muzzleParts.getPath(partNum).removeNode() + else: + muzzleParts.getPath(partNum).hide() + + def __fixHeadShortShort(self, style, lodName=None, copy = 1): + """__fixHeadShortShort(self, AvatarDNA style, string=None, bool = 1) + Hide all long parts""" + if (lodName == None): + searchRoot = self + else: + searchRoot = self.find("**/" + str(lodName)) + + otherParts = searchRoot.findAllMatches( "**/*long*" ) + for partNum in range(0, otherParts.getNumPaths()): + if copy: + otherParts.getPath(partNum).removeNode() + else: + otherParts.getPath(partNum).stash() + + + + ### + ### Tasks + ### + + def __blinkOpenEyes(self, task): + # Only try to open our eyes if they're currently closed. + if self.eyelids.getCurrentState().getName() == 'closed': + self.eyelids.request('open') + # Do a double blink every once in a while + r = self.randGen.random() + if r < 0.1: + # Short time for a double blink + t = 0.2 + else: + # Pick a random time for the next blink + # We can just reuse r here instead of computing a new one + t = r * 4.0 + 1.0 + taskMgr.doMethodLater(t, self.__blinkCloseEyes, self.__blinkName) + return Task.done + + def __blinkCloseEyes(self, task): + if self.eyelids.getCurrentState().getName() != 'open': + # If our eyes are not currently open, do nothing and wait + # till next blink cycle. + taskMgr.doMethodLater(4.0, self.__blinkCloseEyes, self.__blinkName) + + else: + # In the normal case, blink the eyes closed then open again. + self.eyelids.request('closed') + taskMgr.doMethodLater(0.125, self.__blinkOpenEyes, self.__blinkName) + return Task.done + + def startBlink(self): + """startBlink(self) + Starts the Blink task. + """ + # remove any old + taskMgr.remove(self.__blinkName) + # spawn the new task + if self.__eyes: + self.openEyes() + taskMgr.doMethodLater(self.randGen.random() * 4.0 + 1, self.__blinkCloseEyes, self.__blinkName) + + def stopBlink(self): + """stopBlink(self) + """ + taskMgr.remove(self.__blinkName) + # It appears this function may sometimes be called + # AFTER some nodepaths have been deleted. Make sure + # they are still around before proceeding + if self.__eyes: + self.eyelids.request('open') + + # state entry functions + def closeEyes(self): + self.eyelids.request('closed') + + def openEyes(self): + self.eyelids.request('open') + + def surpriseEyes(self): + self.eyelids.request('surprised') + + # these three aren't explicit states, just texture substitutions + def sadEyes(self): + self.__eyesOpen = ToonHead.EyesSadOpen + self.__eyesClosed = ToonHead.EyesSadClosed + + def angryEyes(self): + self.__eyesOpen = ToonHead.EyesAngryOpen + self.__eyesClosed = ToonHead.EyesAngryClosed + + def normalEyes(self): + self.__eyesOpen = ToonHead.EyesOpen + self.__eyesClosed = ToonHead.EyesClosed + + def blinkEyes(self): + """blinkEyes(self) + blinks the eyes once, then starts the blink task to keep blinking. + """ + taskMgr.remove(self.__blinkName) + self.eyelids.request('closed') + taskMgr.doMethodLater(0.1, self.__blinkOpenEyes, self.__blinkName) + + def __stareAt(self, task): + """stareAt(self, task) + + This task makes the eyes and head track a particular point in + space, which may be either fixed or moving. It's defined by + the __stareAtNode and __stareAtPoint members. + + """ + + # Rather than lerping to a particular point, we'll chase the + # target by moving some fraction of the distance remaining + # each frame. This will give us a nice ease-out to the + # target, and give us an elastic feel as the target moves + # around our head. + + frac = 2 * globalClock.getDt() # controls speed of head turning + reachedTarget = self.__lookHeadAt( + self.__stareAtNode, self.__stareAtPoint, frac) + self.__lookPupilsAt(self.__stareAtNode, self.__stareAtPoint) + + if reachedTarget and self.__stareAtNode == None: + # If we're staring at what we were supposed to, and it's + # not going to move anywhere, then we might as well + # stop the task. + return Task.done + else: + return Task.cont + + def doLookAroundToStareAt(self, node, point): + self.startStareAt(node, point) + # Restart the look around + self.startLookAround() + + def startStareAtHeadPoint(self, point): + # stare at a point relative to own head + self.startStareAt(self, point) + + def startStareAt(self, node, point): + """startStareAt(self, NodePath node, Point3 point) + + Starts the StareAt task. The ToonHead and eyes will rotate + each frame to track the indicated point, relative to the + indicated node. + + """ + # remove any old + taskMgr.remove(self.__stareAtName) + if self.lookAtTrack: + self.lookAtTrack.finish() + self.lookAtTrack = None + + self.__stareAtNode = node + + if(point != None): + self.__stareAtPoint = point + else: + self.__stareAtPoint = self.__defaultStarePoint + + self.__stareAtTime = globalClock.getFrameTime() + + # spawn the new task + taskMgr.add(self.__stareAt, self.__stareAtName) + + def lerpLookAt(self, point, time = 1.0, blink=0): + """lerpLookAt(self, Point3 point, float time) + + This is similar to startStareAt(), except (a) it can only look + at a point in space around the head, not at an arbitrary node, + and (b) it uses an actual lerp to achieve this, rather than + trying to chase a potentially moving target each frame. This + is superior to using startStareAt() when the target is just a + point, because it gets better ease-in and ease-out effects, + and because it uses less CPU. + + BUT it has a major artifact: the pupils can 'twitch' far off center before returning to being + centered as the head turns up and down. This effect is less noticeable with stareAt + + stopStareAt() can be used to interrupt either the lerp begun + with lerpLookAt, or the task started with startStareAt(). + + """ + # Remove any old tasks + taskMgr.remove(self.__stareAtName) + if self.lookAtTrack: + self.lookAtTrack.finish() + self.lookAtTrack = None + + + lodNames = self.getLODNames() + if lodNames: + lodName = lodNames[0] + else: + # nevermind: we are being deleted + return 0 + head = self.getPart('head', lodName) + + # First, save the current head and eye position. + startHpr = head.getHpr() + startLpupil = self.__lpupil.getPos() + startRpupil = self.__rpupil.getPos() + + # Now, rotate immediately to the target. We do this just as a + # cheesy way to compute the target head and eye position; + # we'll put it back in a second. + self.__lookHeadAt(None, point, lod = lodName) + self.__lookPupilsAt(None, point) + + endHpr = head.getHpr() + endLpupil = self.__lpupil.getPos() * 0.5 + endRpupil = self.__rpupil.getPos() * 0.5 + + # Now restore the positions and begin the lerp. + head.setHpr(startHpr) + self.__lpupil.setPos(startLpupil) + self.__rpupil.setPos(startRpupil) + + # If you are already looking in that general direction, stay there + if startHpr.almostEqual(endHpr, 10): + return 0 + + # People naturally blink when turning their heads + if blink: + self.blinkEyes() + + lookToTgt_TimeFraction = 0.2 + lookToTgtTime = time * lookToTgt_TimeFraction + returnToEyeCenterTime = time - lookToTgtTime - 0.5 + origin = Point3(0,0,0) + + blendType = "easeOut" + self.lookAtTrack = Parallel( + Sequence( + LerpPosInterval(self.__lpupil, lookToTgtTime, endLpupil, blendType = blendType), + Wait(0.5), + LerpPosInterval(self.__lpupil, returnToEyeCenterTime, origin, blendType = blendType), + ), + Sequence( + LerpPosInterval(self.__rpupil, lookToTgtTime, endRpupil, blendType = blendType), + Wait(0.5), + LerpPosInterval(self.__rpupil, returnToEyeCenterTime, origin, blendType = blendType), + ), + name = self.__stareAtName, + ) + + for lodName in self.getLODNames(): + head = self.getPart('head', lodName) + self.lookAtTrack.append(LerpHprInterval(head, time, endHpr, blendType = "easeInOut")) + + self.lookAtTrack.start() + + #lPupilSeq = Task.sequence( + # self.__lpupil.lerpPos(endLpupil, lookToTgtTime, blendType = blend_type), + # Task.pause(0.5), + # self.__lpupil.lerpPos(origin, returnToEyeCenterTime, blendType = blend_type), + # ) + #rPupilSeq = Task.sequence( + # self.__rpupil.lerpPos(endRpupil, lookToTgtTime, blendType = blend_type), + # Task.pause(0.5), + # self.__rpupil.lerpPos(origin, returnToEyeCenterTime, blendType = blend_type), + # ) + #taskMgr.add(lPupilSeq, self.__stareAtName) + #taskMgr.add(rPupilSeq, self.__stareAtName) + return 1 + + def stopStareAt(self): + """stopStareAt(self) + + Stops the StareAt task, and lerps the head back to its neutral + position. + + """ + self.lerpLookAt(Vec3.forward()) + + def stopStareAtNow(self): + """stopStareAtNow(self) + + Stops the StareAt task, and pops the head back to its neutral + position. Useful when you want to delete the head right now + and you don't want to wait for it to return to neutral. + + """ + taskMgr.remove(self.__stareAtName) + if self.lookAtTrack: + self.lookAtTrack.finish() + self.lookAtTrack = None + + # it seems this maybe getting called AFTER the pupils are deleted... + if self.__lpupil and self.__rpupil: + self.__setPupilDirection(0, 0) + + for lodName in self.getLODNames(): + head = self.getPart('head', lodName) + head.setHpr(0, 0, 0) + + def __lookAround(self, task): + """lookAround(self, task) + This task makes the head look in a new direction, chosen by + findSomethingToLookAt(), every so often. + """ + self.findSomethingToLookAt() + t = self.randGen.random() * 4.0 + 3.0 + taskMgr.doMethodLater(t, self.__lookAround, self.__lookName) + return Task.done + + def startLookAround(self): + """startLookAround(self) + Starts the LookAround task. + """ + # remove any old + taskMgr.remove(self.__lookName) + # spawn the new task + t = self.randGen.random() * 5.0 + 2.0 + taskMgr.doMethodLater(t, self.__lookAround, self.__lookName) + + def stopLookAround(self): + """stopLookAround(self) + + Stops the LookAround task gracefully, by lerping the head back + to neutral. + + """ + taskMgr.remove(self.__lookName) + self.stopStareAt() + + def stopLookAroundNow(self): + """stopLookAroundNow(self) + + Stops the LookAround task suddenly. + + """ + taskMgr.remove(self.__lookName) + self.stopStareAtNow() + + ### + ### State transitions + ### + + def enterEyelidsOff(self): + """enterEyelidsOff(self) + """ + pass + + def exitEyelidsOff(self): + pass + + def enterEyelidsOpen(self): + """enterEyelidsOpen(self) + """ + if not self.__eyes.isEmpty(): + self.__eyes.setTexture(self.__eyesOpen, 1) + if self.__eyelashOpen: + self.__eyelashOpen.show() + if self.__eyelashClosed: + self.__eyelashClosed.hide() + if self.__lod500Eyes: + self.__lod500Eyes.setTexture(self.__eyesOpen, 1) + if self.__lod250Eyes: + self.__lod250Eyes.setTexture(self.__eyesOpen, 1) + if self.__lpupil: + self.__lpupil.show() + self.__rpupil.show() + if self.__lod500lPupil: + self.__lod500lPupil.show() + self.__lod500rPupil.show() + if self.__lod250lPupil: + self.__lod250lPupil.show() + self.__lod250rPupil.show() + + def exitEyelidsOpen(self): + pass + + + def enterEyelidsClosed(self): + """enterEyelidsClosed(self) + """ + if not self.__eyes.isEmpty() and self.__eyesClosed: + self.__eyes.setTexture(self.__eyesClosed, 1) + if self.__eyelashOpen: + self.__eyelashOpen.hide() + if self.__eyelashClosed: + self.__eyelashClosed.show() + if self.__lod500Eyes: + self.__lod500Eyes.setTexture(self.__eyesClosed, 1) + if self.__lod250Eyes: + self.__lod250Eyes.setTexture(self.__eyesClosed, 1) + if self.__lpupil: + self.__lpupil.hide() + self.__rpupil.hide() + if self.__lod500lPupil: + self.__lod500lPupil.hide() + self.__lod500rPupil.hide() + if self.__lod250lPupil: + self.__lod250lPupil.hide() + self.__lod250rPupil.hide() + + + def exitEyelidsClosed(self): + pass + + def enterEyelidsSurprised(self): + """enterEyelidsSurprised(self) + """ + if not self.__eyes.isEmpty() and ToonHead.EyesSurprised: + self.__eyes.setTexture(ToonHead.EyesSurprised, 1) + if self.__eyelashOpen: + self.__eyelashOpen.hide() + if self.__eyelashClosed: + self.__eyelashClosed.hide() + if self.__lod500Eyes: + self.__lod500Eyes.setTexture(ToonHead.EyesSurprised, 1) + if self.__lod250Eyes: + self.__lod250Eyes.setTexture(ToonHead.EyesSurprised, 1) + if self.__muzzle: + self.__muzzle.setTexture(ToonHead.MuzzleSurprised, 1) + if self.__lpupil: + self.__lpupil.show() + self.__rpupil.show() + if self.__lod500lPupil: + self.__lod500lPupil.show() + self.__lod500rPupil.show() + if self.__lod250lPupil: + self.__lod250lPupil.show() + self.__lod250rPupil.show() + + def exitEyelidsSurprised(self): + if self.__muzzle: + self.__muzzle.setTexture(ToonHead.Muzzle, 1) + + def setupMuzzles(self, style): +## self.__muzzle = self.find("**/1000/**/muzzle*") + self.__muzzles = [] + self.__surpriseMuzzles = [] + self.__angryMuzzles = [] + self.__sadMuzzles = [] + self.__smileMuzzles = [] + self.__laughMuzzles = [] + + def hideAddNonEmptyItemToList(item, list): + if not item.isEmpty(): + # Also hiding the item + item.hide() + list.append(item) + + def hideNonEmptyItem(item): + if not item.isEmpty(): + item.hide() +## # For AlphaLerp +## item.setTransparency(True) +## item.setColor(1,1,1,0) +## item.setDepthWrite(1) +## item.setDepthTest(1) + + if (self.hasLOD()): + # Save the various muzzle LODs. + for lodName in self.getLODNames(): + animal = style.getAnimal() + if (animal != 'dog'): + muzzle = self.find('**/' + lodName + '/**/muzzle*neutral') + # Treating the dog separately + # Explanation: All the animals have their heads and muzzles in the same maya file + # The dog has its head in the soft file and the muzzles are coming from a respective maya file + # @TODO Samik 09-05-2008: Remove all this loading muzzle maya files to clean up how the dog is done + else: + muzzle = self.find('**/' + lodName + '/**/muzzle*') + if (lodName == '1000') or (lodName == '500'): + filePrefix = DogMuzzleDict[style.head] + muzzles = loader.loadModel("phase_3" + filePrefix + lodName) + if base.config.GetBool('want-new-anims', 1): + if not self.find('**/' + lodName + '/**/def_head').isEmpty(): + muzzles.reparentTo(self.find('**/' + lodName + '/**/def_head')) + else: + muzzles.reparentTo(self.find('**/' + lodName + '/**/joint_toHead')) + elif self.find('**/' + lodName + '/**/joint_toHead'): + muzzles.reparentTo(self.find('**/' + lodName + '/**/joint_toHead')) + + surpriseMuzzle = self.find('**/' + lodName + '/**/muzzle*surprise') + angryMuzzle = self.find('**/' + lodName + '/**/muzzle*angry') + sadMuzzle = self.find('**/' + lodName + '/**/muzzle*sad') + smileMuzzle = self.find('**/' + lodName + '/**/muzzle*smile') + laughMuzzle = self.find('**/' + lodName + '/**/muzzle*laugh') + + self.__muzzles.append(muzzle) + hideAddNonEmptyItemToList(surpriseMuzzle, self.__surpriseMuzzles) + hideAddNonEmptyItemToList(angryMuzzle, self.__angryMuzzles) + hideAddNonEmptyItemToList(sadMuzzle, self.__sadMuzzles) + hideAddNonEmptyItemToList(smileMuzzle, self.__smileMuzzles) + hideAddNonEmptyItemToList(laughMuzzle, self.__laughMuzzles) + +## # For AlphaLerp +## if not muzzle.isEmpty(): +## muzzle.setTransparency(True) +## muzzle.setDepthWrite(1) +## muzzle.setDepthTest(1) + + else: + if style.getAnimal() != 'dog': + muzzle = self.find('**/muzzle*neutral') + # Treating the dog separately + # Explanation: All the animals have their heads and muzzles in the same maya file + # The dog has its head in the soft file and the muzzles are coming from a respective maya file + # @TODO Samik 09-05-2008: Remove all this loading muzzle maya files to clean up how the dog is done + else: + muzzle = self.find('**/muzzle*') + filePrefix = DogMuzzleDict[style.head] + muzzles = loader.loadModel("phase_3" + filePrefix + '1000') + if base.config.GetBool('want-new-anims', 1): + if not self.find('**/def_head').isEmpty(): + muzzles.reparentTo(self.find('**/def_head')) + else: + muzzles.reparentTo(self.find('**/joint_toHead')) + else: + muzzles.reparentTo(self.find('**/joint_toHead')) + + surpriseMuzzle = self.find('**/muzzle*surprise') + angryMuzzle = self.find('**/muzzle*angry') + sadMuzzle = self.find('**/muzzle*sad') + smileMuzzle = self.find('**/muzzle*smile') + laughMuzzle = self.find('**/muzzle*laugh') + + self.__muzzles.append(muzzle) +## # For AlphaLerp +## if not muzzle.isEmpty(): +## muzzle.setTransparency(True) +## muzzle.setDepthWrite(1) +## muzzle.setDepthTest(1) + + hideAddNonEmptyItemToList(surpriseMuzzle, self.__surpriseMuzzles) + hideAddNonEmptyItemToList(angryMuzzle, self.__angryMuzzles) + hideAddNonEmptyItemToList(sadMuzzle, self.__sadMuzzles) + hideAddNonEmptyItemToList(smileMuzzle, self.__smileMuzzles) + hideAddNonEmptyItemToList(laughMuzzle, self.__laughMuzzles) + + def getMuzzles(self): + """ + Return a list of muzzles for each LOD (1000, 500, 250) + """ + return self.__muzzles + + def getSurpriseMuzzles(self): + """ + Return a list of surpriseMuzzles for each LOD (1000, 500, 250) + """ + return self.__surpriseMuzzles + + def getAngryMuzzles(self): + """ + Return a list of angryMuzzles for each LOD (1000, 500, 250) + """ + return self.__angryMuzzles + + def getSadMuzzles(self): + """ + Return a list of sadMuzzles for each LOD (1000, 500, 250) + """ + return self.__sadMuzzles + + def getSmileMuzzles(self): + """ + Return a list of smileMuzzles for each LOD (1000, 500, 250) + """ + return self.__smileMuzzles + + def getLaughMuzzles(self): + """ + Return a list of laughMuzzles for each LOD (1000, 500, 250) + """ + return self.__laughMuzzles + + def showNormalMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__muzzles)): + self.__muzzles[muzzleNum].show() + + def hideNormalMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__muzzles)): + self.__muzzles[muzzleNum].hide() + + def showAngryMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__angryMuzzles)): + self.__angryMuzzles[muzzleNum].show() + self.__muzzles[muzzleNum].hide() + + def hideAngryMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__angryMuzzles)): + self.__angryMuzzles[muzzleNum].hide() + self.__muzzles[muzzleNum].show() + + def showSadMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__sadMuzzles)): + self.__sadMuzzles[muzzleNum].show() + self.__muzzles[muzzleNum].hide() + + def hideSadMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__sadMuzzles)): + self.__sadMuzzles[muzzleNum].hide() + self.__muzzles[muzzleNum].show() + + def showSmileMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__smileMuzzles)): + self.__smileMuzzles[muzzleNum].show() + self.__muzzles[muzzleNum].hide() + + def hideSmileMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__smileMuzzles)): + self.__smileMuzzles[muzzleNum].hide() + self.__muzzles[muzzleNum].show() + + def showLaughMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__laughMuzzles)): + self.__laughMuzzles[muzzleNum].show() + self.__muzzles[muzzleNum].hide() + + def hideLaughMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__laughMuzzles)): + self.__laughMuzzles[muzzleNum].hide() + self.__muzzles[muzzleNum].show() + + def showSurpriseMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__surpriseMuzzles)): + self.__surpriseMuzzles[muzzleNum].show() + self.__muzzles[muzzleNum].hide() + + def hideSurpriseMuzzle(self): + if self.isIgnoreCheesyEffect(): + return + for muzzleNum in range(len(self.__surpriseMuzzles)): + self.__surpriseMuzzles[muzzleNum].hide() + self.__muzzles[muzzleNum].show() + + def isIgnoreCheesyEffect(self): + if hasattr(self, 'savedCheesyEffect'): + # Do nothing if the Invisible, NoColor, Pumpkin or BigWhite cheesy effect is ON + if (self.savedCheesyEffect == 10) \ + or (self.savedCheesyEffect == 11) \ + or (self.savedCheesyEffect == 12) \ + or (self.savedCheesyEffect == 13) \ + or (self.savedCheesyEffect == 14): + return True + return False diff --git a/toontown/src/toon/ToonHeadFrame.py b/toontown/src/toon/ToonHeadFrame.py new file mode 100644 index 0000000..26123f5 --- /dev/null +++ b/toontown/src/toon/ToonHeadFrame.py @@ -0,0 +1,95 @@ +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import ToonHead +from toontown.distributed import DelayDelete +from toontown.toonbase import ToontownGlobals + +class ToonHeadFrame(DirectFrame): + def __init__(self, av, color = ToontownGlobals.GlobalDialogColor, g = DGG.getDefaultDialogGeom()): + DirectFrame.__init__( + self, + relief = None, + geom = g, + geom_color = color, + geom_scale = (1,1,0.5), + pos = (0,0,0), + ) + self.initialiseoptions(ToonHeadFrame) + + self.av = av + # Do not allow the avatar to be deleted now + # because we made a chat bubble and nametag on him + # and try to delete them when we exit + self.avKeep = DelayDelete.DelayDelete(av, 'ToonHeadFrame.avKeep') + + self.head = self.stateNodePath[0].attachNewNode('head', 20) + self.head.setPosHprScale(-0.27, 10.0, -0.09, + 180., 0., 0., + 0.2, 0.2, 0.2) + + self.headModel = ToonHead.ToonHead() + self.headModel.startBlink() + self.headModel.setupHead(self.av.style, forGui = 1) + self.headModel.reparentTo(self.head) + + # now enable a chat balloon + self.tag1Node = NametagFloat2d() + self.tag1Node.setContents(Nametag.CSpeech | Nametag.CThought) + self.av.nametag.addNametag(self.tag1Node) + + self.tag1 = self.attachNewNode(self.tag1Node.upcastToPandaNode()) + self.tag1.setPosHprScale(-0.16,0,-0.09, + 0,0,0, + 0.055,0.055,0.055) + + # As well as a nametag just to display the name. + self.tag2Node = NametagFloat2d() + self.tag2Node.setContents(Nametag.CName) + self.av.nametag.addNametag(self.tag2Node) + + self.tag2 = self.attachNewNode(self.tag2Node.upcastToPandaNode()) + self.tag2.setPosHprScale(-0.27, 10.0, 0.16, + 0,0,0, + 0.05,0.05,0.05) + + self.extraData = DirectLabel( + parent = self, + relief = None, + pos = (0.0, 0.0, 0.06), + scale = 1.0, + text = "", + #text_font = ToontownGlobals.getSignFont(), + text0_fg = (0.3, 0.2, 1, 1), + text_scale = (0.14,0.06), + text_pos = (0, -0.01), + ) + self.extraData.hide() + + def destroy(self): + DirectFrame.destroy(self) + self.headModel.delete() + del self.headModel + self.head.removeNode() + del self.head + if not self.av.isEmpty(): + self.av.nametag.removeNametag(self.tag1Node) + self.av.nametag.removeNametag(self.tag2Node) + self.tag1.removeNode() + self.tag2.removeNode() + del self.tag1 + del self.tag2 + del self.tag1Node + del self.tag2Node + del self.av + if self.avKeep: + self.avKeep.destroy() + del self.avKeep + self.extraData.removeNode() + del self.extraData + + + def removeAvKeep(self): + """Remove the delayDelete so the toon can disappear from the golf course.""" + if hasattr(self, 'avKeep') and self.avKeep: + self.avKeep.destroy() + self.avKeep = None diff --git a/toontown/src/toon/ToonTeleportPanel.py b/toontown/src/toon/ToonTeleportPanel.py new file mode 100644 index 0000000..c735cad --- /dev/null +++ b/toontown/src/toon/ToonTeleportPanel.py @@ -0,0 +1,482 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from direct.showbase import DirectObject +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.directnotify import DirectNotifyGlobal +import ToonAvatarDetailPanel +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil + +globalTeleport = None + +def showTeleportPanel(avId, avName, avDisableName): + # A module function to open the global avatar detail panel. + global globalTeleport + if globalTeleport != None: + globalTeleport.cleanup() + globalTeleport = None + + globalTeleport = ToonTeleportPanel(avId, avName, avDisableName) + +def hideTeleportPanel(): + # A module function to close the global avatar detail if it is open. + global globalTeleport + if globalTeleport != None: + globalTeleport.cleanup() + globalTeleport = None + +def unloadTeleportPanel(): + # A module function to completely unload the global friend + # inviter. This is the same thing as hideTeleportPanel, actually. + global globalTeleport + if globalTeleport != None: + globalTeleport.cleanup() + globalTeleport = None + +class ToonTeleportPanel(DirectFrame): + """ToonTeleportPanel: + + This panel pops up in response to clicking the "Go To" button from + the Avatar panel. It handles determining where the avatar is and + teleporting to him/her, if possible. + + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("ToonTeleportPanel") + + def __init__(self, avId, avName, avDisableName): + + # initialize our base class. + DirectFrame.__init__(self, + pos = (0.3, 0.1, 0.65), + image_color = ToontownGlobals.GlobalDialogColor, + image_scale = (1.0, 1.0, 0.6), + text = '', + text_wordwrap = 13.5, + text_scale = 0.06, + text_pos = (0.0, 0.18), + ) + + # If we're currently in move-furniture mode, stop it. + messenger.send("releaseDirector") + + # For some reason, we need to set this after construction to + # get it to work properly. + self['image'] = DGG.getDefaultDialogGeom() + + self.avId = avId + self.avName = avName + self.avDisableName = avDisableName + + self.fsm = ClassicFSM.ClassicFSM('ToonTeleportPanel', + [State.State('off', + self.enterOff, + self.exitOff), + State.State('begin', + self.enterBegin, + self.exitBegin), + State.State('checkAvailability', + self.enterCheckAvailability, + self.exitCheckAvailability), + State.State('notAvailable', + self.enterNotAvailable, + self.exitNotAvailable), + State.State('ignored', + self.enterIgnored, + self.exitIgnored), + State.State('notOnline', + self.enterNotOnline, + self.exitNotOnline), + State.State('wentAway', + self.enterWentAway, + self.exitWentAway), + State.State('self', + self.enterSelf, + self.exitSelf), + State.State('unknownHood', + self.enterUnknownHood, + self.exitUnknownHood), + State.State('unavailableHood', + self.enterUnavailableHood, + self.exitUnavailableHood), + State.State('otherShard', + self.enterOtherShard, + self.exitOtherShard), + State.State('teleport', + self.enterTeleport, + self.exitTeleport), + ], + # Initial State + 'off', + # Final State + 'off', + ) + + + # This is imported here instead of at the top of the file to + # protect against circular imports. + from toontown.friends import FriendInviter + + # Now set up the panel. + FriendInviter.hideFriendInviter() + ToonAvatarDetailPanel.hideAvatarDetail() + + # Create some buttons. + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') + + self.bOk = DirectButton(self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.TeleportPanelOK, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.0, 0.0, -0.1), + command = self.__handleOk) + self.bOk.hide() + + self.bCancel = DirectButton(self, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + relief = None, + text = TTLocalizer.TeleportPanelCancel, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.0, 0.0, -0.1), + command = self.__handleCancel) + self.bCancel.hide() + + self.bYes = DirectButton(self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.TeleportPanelYes, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (-0.15, 0.0, -0.15), + command = self.__handleYes) + self.bYes.hide() + + self.bNo = DirectButton(self, + image = (buttons.find('**/CloseBtn_UP'), + buttons.find('**/CloseBtn_DN'), + buttons.find('**/CloseBtn_Rllvr')), + relief = None, + text = TTLocalizer.TeleportPanelNo, + text_scale = 0.05, + text_pos = (0.0, -0.1), + pos = (0.15, 0.0, -0.15), + command = self.__handleNo) + self.bNo.hide() + + buttons.removeNode() + + self.accept(self.avDisableName, self.__handleDisableAvatar) + + self.show() + self.fsm.enterInitialState() + self.fsm.request('begin') + + + def cleanup(self): + """cleanup(self): + + Cancels any pending request and removes the panel from the + screen. + + """ + self.fsm.request('off') + del self.fsm + self.ignore(self.avDisableName) + self.destroy() + + ##### Off state ##### + + # Represents the initial state of the teleport query: no query in + # progress. + + def enterOff(self): + pass + + def exitOff(self): + pass + + ##### Begin state ##### + + # We have clicked on the "go to" button from the Avatar detail + # panel. Perform a few sanity checks. + + def enterBegin(self): + myId = base.localAvatar.doId + hasManager = hasattr(base.cr, "playerFriendsManager") + + if (self.avId == myId): + self.fsm.request('self') + + elif base.cr.doId2do.has_key(self.avId): + # The avatar is online, and in fact, nearby. + self.fsm.request('checkAvailability') + + elif base.cr.isFriend(self.avId): + # The avatar is a friend of ours. Is she online? + if base.cr.isFriendOnline(self.avId): + self.fsm.request('checkAvailability') + else: + self.fsm.request('notOnline') + + elif hasManager and base.cr.playerFriendsManager.getAvHandleFromId(self.avId): + # The avatar is a transient friend of ours. Is she online? + id = base.cr.playerFriendsManager.findPlayerIdFromAvId(self.avId) + info = base.cr.playerFriendsManager.getFriendInfo(id) + if info: + if info.onlineYesNo: + self.fsm.request('checkAvailability') + else: + self.fsm.request('notOnline') + else: + self.fsm.request('wentAway') + + else: + # The avatar is not our friend and isn't around. + self.fsm.request('wentAway') + + def exitBegin(self): + pass + + ##### Checking availability state ##### + + # Ask the other avatar if he's available to be teleported to, and + # where is he. + + def enterCheckAvailability(self): + + myId = base.localAvatar.getDoId() + base.localAvatar.d_teleportQuery(myId, sendToId = self.avId) + self['text'] = TTLocalizer.TeleportPanelCheckAvailability % (self.avName) + self.accept('teleportResponse', self.__teleportResponse) + self.bCancel.show() + + def exitCheckAvailability(self): + self.ignore('teleportResponse') + self.bCancel.hide() + + ##### Not available state ##### + + # In this state, the avatar we have tried to teleport to is + # currently not available to answer that query: maybe he's in a + # minigame or something. + + def enterNotAvailable(self): + self['text'] = TTLocalizer.TeleportPanelNotAvailable % (self.avName) + self.bOk.show() + + def exitNotAvailable(self): + self.bOk.hide() + + ##### Ignored state ##### + + # In this state, the avatar we have tried to teleport to is + # ignoring us. Too bad for us. + + def enterIgnored(self): + self['text'] = TTLocalizer.TeleportPanelNotAvailable % (self.avName) + self.bOk.show() + + def exitIgnored(self): + self.bOk.hide() + + ##### Not online state ##### + + # In this state, the avatar is not even online. + + def enterNotOnline(self): + self['text'] = TTLocalizer.TeleportPanelNotOnline % (self.avName) + self.bOk.show() + + def exitNotOnline(self): + self.bOk.hide() + + ##### Went away state ##### + + # The avatar was here, but now he's gone. + + def enterWentAway(self): + self['text'] = TTLocalizer.TeleportPanelWentAway % (self.avName) + self.bOk.show() + + def exitWentAway(self): + self.bOk.hide() + + ##### UnknownHood state ##### + + # Invalid attempt to teleport to a hood we haven't been to yet. + + def enterUnknownHood(self, hoodId): + self['text'] = TTLocalizer.TeleportPanelUnknownHood % \ + base.cr.hoodMgr.getFullnameFromId(hoodId) + self.bOk.show() + + def exitUnknownHood(self): + self.bOk.hide() + + ##### UnavailableHood state ##### + + # Invalid attempt to teleport to a hood we haven't downloaded yet. + + def enterUnavailableHood(self, hoodId): + self['text'] = (TTLocalizer.TeleportPanelUnavailableHood % + base.cr.hoodMgr.getFullnameFromId(hoodId)) + self.bOk.show() + + def exitUnavailableHood(self): + self.bOk.hide() + + ##### Self state ##### + + # Invalid attempt to teleport to yourself. + + def enterSelf(self): + self['text'] = TTLocalizer.TeleportPanelDenySelf + self.bOk.show() + + def exitSelf(self): + self.bOk.hide() + + + ##### OtherShard state ##### + + # Warn the user that we're about to switch shards, and give + # him/her an opportunity to bail. + + def enterOtherShard(self, shardId, hoodId, zoneId): + shardName = base.cr.getShardName(shardId) + if shardName is None: + # unknown shard. potentially a hacker--just ignore + self.fsm.request('notAvailable') + return + myShardName = base.cr.getShardName(base.localAvatar.defaultShard) + + # determine the population of new shard + pop = None + for shard in base.cr.listActiveShards(): + if shard[0] == shardId: + pop = shard[2] + + # if we got a pop for the shard in question and it's full + if pop and pop > localAvatar.shardPage.midPop: + self.notify.warning("Entering full shard: issuing performance warning") + self['text'] = (TTLocalizer.TeleportPanelBusyShard % + ({"avName" : self.avName})) + else: + self['text'] = (TTLocalizer.TeleportPanelOtherShard % + ({"avName" : self.avName, + "shardName" : shardName, + "myShardName" : myShardName, + })) + self.bYes.show() + self.bNo.show() + + # Save the parameter variables for when we click "yes". + self.shardId = shardId + self.hoodId = hoodId + self.zoneId = zoneId + + def exitOtherShard(self): + self.bYes.hide() + self.bNo.hide() + + ##### Teleport state ##### + + # Actually perform the teleport operation. + + def enterTeleport(self, shardId, hoodId, zoneId): + hoodsVisited = base.localAvatar.hoodsVisited + + canonicalHoodId = ZoneUtil.getCanonicalZoneId(hoodId) + + if hoodId == ToontownGlobals.MyEstate: + if shardId == base.localAvatar.defaultShard: + # If we're staying on the same shard, don't make the + # shardId part of the request. + shardId = None + place = base.cr.playGame.getPlace() + place.requestTeleport(hoodId, zoneId, shardId, self.avId) + unloadTeleportPanel() + + elif canonicalHoodId not in (hoodsVisited + ToontownGlobals.HoodsAlwaysVisited): + # We've never been to this hood before, so we can't go + # there now. + self.fsm.request('unknownHood', [hoodId]) + + elif canonicalHoodId not in base.cr.hoodMgr.getAvailableZones(): + print "hoodId %d not ready" % hoodId + # We haven't finished downloading this hood yet. + self.fsm.request('unavailableHood', [hoodId]) + + else: + # All right already, just go there. + if shardId == base.localAvatar.defaultShard: + # If we're staying on the same shard, don't make the + # shardId part of the request. + shardId = None + + # All right already, just go there. + place = base.cr.playGame.getPlace() + place.requestTeleport(hoodId, zoneId, shardId, self.avId) + unloadTeleportPanel() + + return + + def exitTeleport(self): + pass + + + ### Button handing methods + + def __handleOk(self): + unloadTeleportPanel() + + def __handleCancel(self): + unloadTeleportPanel() + + def __handleYes(self): + self.fsm.request('teleport', [self.shardId, self.hoodId, + self.zoneId]) + + def __handleNo(self): + unloadTeleportPanel() + + + ### Support methods + + def __teleportResponse(self, avId, available, shardId, hoodId, zoneId): + if avId != self.avId: + # Ignore responses from an unexpected avatar. + return + + if available == 0: + # The other avatar is not available to teleport to. + self.fsm.request('notAvailable') + elif available == 2: + # The other avatar is ignoring us. + self.fsm.request('ignored') + elif shardId != base.localAvatar.defaultShard: + self.fsm.request('otherShard', [shardId, hoodId, zoneId]) + else: + self.fsm.request('teleport', [shardId, hoodId, zoneId]) + + + def __handleDisableAvatar(self): + """__handleDisableAvatar(self) + + Called whenever the avatar in question is disabled, this + should update the panel appropriately. + + """ + self.fsm.request('wentAway') diff --git a/toontown/src/toon/TopToonPics.py b/toontown/src/toon/TopToonPics.py new file mode 100644 index 0000000..aa0e061 --- /dev/null +++ b/toontown/src/toon/TopToonPics.py @@ -0,0 +1,339 @@ +import sys +import string +import __builtin__ +from direct.directtools.DirectUtil import getFileData +from direct.distributed.PyDatagram import PyDatagram +from math import tan + +from pandac.PandaModules import * + +try: + __builtin__.launcher +except AttributeError: + __builtin__.launcher = None + +def hexToColor(hexString, alpha = None): + if alpha == None: + alpha = 1 + if len(hexString) >= 8: + alpha = eval ('0x' + hexString[6:8])/255. + + return Vec4(eval ('0x' + hexString[0:2])/255., + eval ('0x' + hexString[2:4])/255., + eval ('0x' + hexString[4:6])/255., + alpha) + +# Parse command line arguments +headAngle = 0 +pitchAngle = -1 +rollAngle = 20 +ringColor = 'F0AA40' +bgColor = 'FFFFCC' +ringScale = 0.1 +fov = 20 +fillFactor = 0.75 +outputDirectory = '.' +inputFile = 'TopToons.txt' +overrideTexture = None +backgroundImage = 'Sky' +lookAtTarget = 'head' +fHideRing = 0 +argFlag = None +outputExtension = 'jpg' +size = 50 + +def printHelp(): + print + print 'ppython TopToonPics.py [options]' + print 'Options:' + print ' -a headAngle Specify angle of toons head (default 10 deg)' + print ' -b BG color Specify background color (default FFFFCC)' + print ' -B BG image Specify file name for background image' + print ' -c Ring color Specify ring color (default F0AA40)' + print ' -d outputDirectory Specify directory for resulting images' + print ' -f FOV Specify camera FOV' + print ' -F fillFactor Specify percent of FOV to fill (1-100)' + print ' -i inputFile Specify filename holding DNA strings' + print ' -l lookTarget Specify look at target (head/body)' + print ' -nr Hide circular frame surrounding toon' + print ' -p head pitch Specify pitch of toons head (default -1 deg)' + print ' -r roll Specify camera roll' + print ' -s ring thickness Specify thickness for outer ring (0 no-ring)' + print ' -e extension Specify extension (and file type) of output' + print ' -S size Specify the width/height of the images in pixes. Default is 50' + print ' -h Print this help message' + sys.exit() + +for arg in sys.argv: + if arg == '-a': + argFlag = 'a' + elif arg == '-b': + argFlag = 'b' + elif arg == '-B': + argFlag = 'B' + elif arg == '-c': + argFlag = 'c' + elif arg == '-d': + argFlag = 'd' + elif arg == '-f': + argFlag = 'f' + elif arg == '-F': + argFlag = 'F' + elif arg == '-h': + printHelp() + elif arg == '-i': + argFlag = 'i' + elif arg == '-l': + argFlag = 'l' + elif arg == '-nr': + fHideRing = 1 + argFlag = None + elif arg == '-p': + argFlag = 'p' + elif arg == '-r': + argFlag = 'r' + elif arg == '-s': + argFlag = 's' + elif arg == '-S': + argFlag = 'S' + elif arg == '-e': + argFlag = 'e' + elif argFlag == 'a': + headAngle = string.atof(arg) + argFlag = None + elif argFlag == 'b': + bgColor = arg + argFlag = None + elif argFlag == 'B': + overrideTexture = 1 + backgroundImage = arg + argFlag = None + elif argFlag == 'c': + ringColor = arg + argFlag = None + elif argFlag == 'd': + outputDirectory = arg + argFlag = None + elif argFlag == 'f': + fov = string.atof(arg) + argFlag = None + elif argFlag == 'F': + fillFactor = string.atof(arg)/100.0 + argFlag = None + elif argFlag == 'i': + inputFile = arg + argFlag = None + elif argFlag == 'l': + lookAtTarget = arg + argFlag = None + elif argFlag == 'p': + pitchAngle = string.atof(arg) + argFlag = None + elif argFlag == 'r': + rollAngle = string.atof(arg) + argFlag = None + elif argFlag == 's': + ringScale = string.atof(arg) + argFlag = None + elif argFlag == 'e': + outputExtension = arg + argFlag = None + elif argFlag == 'S': + size = string.atoi(arg) + argFlag = None + else: + argFlag = None + +print "Input File: %s" % inputFile +print "Output Directory: %s" % outputDirectory +print "Look Target: %s" % lookAtTarget +print "Head Angle: %f" % headAngle +print "Pitch Angle: %f" % pitchAngle +print "Roll Angle: %f" % rollAngle +print "FOV: %f" % fov +print "FOV Fill Percent: %f" % (fillFactor * 100.0) +print "Background Color: %s" % bgColor +print "Background Image: %s" % backgroundImage +if fHideRing: + print "No Ring" +else: + print "Show Ring" + print "Ring Color: %s" % ringColor + print "Ring Thickness: %f" % ringScale + +ConfigVariableInt('win-size').setStringValue('%d %d' % (size,size)) + +from direct.directbase.DirectStart import * +from Toon import * +import ToonDNA + +frame = render2d.attachNewNode('frame') +base.win.setClearColor(VBase4(0,0,0,0)) + +topModels = loader.loadModel('models/gui/topToonPictures') + +outerFrame = topModels.find('**/topToonPictureFrame').copyTo(frame) +outerFrame.setScale(2 + ringScale) +# output this outer frame so we have a hole in the middle +# to test for +base.graphicsEngine.renderFrame() +outerFrameImg = PNMImage.PNMImage(size,size) +base.win.getScreenshot(outerFrameImg) +outerFrame.setColor(hexToColor(bgColor, alpha = 1)) +outerFrame.setBin('fixed', 1) + +pictureFrame = topModels.find('**/topToonPictureFrame').copyTo(frame) +pictureFrame.setColor(hexToColor(ringColor)) +pictureFrame.setScale(2) +pictureFrame.setBin('fixed', 0) + +# Make one more frame layer, this one specially set not to draw to the +# color channels, but to write the inverse alpha channel (so the +# resulting image can have a transparent or semitransparent +# background). +alphaFrame = topModels.find('**/topToonPictureFrame').copyTo(frame) + +ts = TextureStage('invertAlpha') +ts.setCombineRgb(TextureStage.CMReplace, + TextureStage.CSTexture, TextureStage.COSrcColor) +ts.setCombineAlpha(TextureStage.CMAdd, + TextureStage.CSTexture, TextureStage.COOneMinusSrcAlpha, + TextureStage.CSPrevious, TextureStage.COSrcAlpha) +tex = alphaFrame.findTexture('*') +alphaFrame.setTextureOff(1) +alphaFrame.setTexture(ts, tex, 1) + +alphaFrame.setColor(hexToColor(bgColor)) +alphaFrame.setScale(2 + ringScale) +alphaFrame.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.CAlpha)) +alphaFrame.setTransparency(TransparencyAttrib.MNone, 1) +alphaFrame.setBin('fixed', 2) + +if fHideRing: + frame.hide() + +background = topModels.find('**/topToonBackground') +background.reparentTo(camera) +background.setPos(0,40,-1) +background.setScale(40) +if overrideTexture: + overrideTexture = Texture() + overrideTexture.read(Filename(backgroundImage)) + overrideTexture.setMinfilter(Texture.FTLinearMipmapLinear) + overrideTexture.setMagfilter(Texture.FTLinear) + background.setTexture(overrideTexture, 1) + +# effectiveFOV is the approximate FOV of the ring +effectiveFOV = fov * 0.75 + +def calcBodyBounds(): + p1,p2 = tt.getTightBounds() + c = Vec3((p2 + p1)/2.0) + delta = p2 - p1 + return c, delta[2] + +def calcHeadBounds(): + head = tt.getPart('head', '1000') + headParent = head.getParent() + # Temporarily reparent head to render to get bounds aligned with render + head.wrtReparentTo(render) + # Look for explicitly named ears and stash them + ears = head.findAllMatches('**/ear*') + # And stash them before computing bounds + for ear in ears.asList(): + ear.stash() + stashed = [] + # If this is a horse and we didn't find any ears, + # stash nodes with empty string names + if ears.isEmpty() and (tt.style.head[0] == 'h'): + # Now stash all unnamed nodes + for child in head.getChildrenAsList(): + if child.getName() == '': + stashed.append(child) + child.stash() + # Where is center of head in render space? + p1,p2 = head.getTightBounds() + # Put all stashed things back + for ear in ears.asList(): + ear.unstash() + for child in stashed: + child.unstash() + c = Vec3((p2 + p1)/2.0) + delta = p2 - p1 + # Restore orientation + head.wrtReparentTo(headParent) + return c, delta[2] + +def lookAtToon(): + if lookAtTarget == 'head': + c,height = calcHeadBounds() + else: + c,height = calcBodyBounds() + # Move camera there + camera.setHpr(render, -180, 0, 0) + camera.setPos(render, c) + # Move it back to fit around the target + offset = ((height/2.0)/ + tan(deg2Rad((fillFactor * effectiveFOV)/2.0))) + camera.setY(camera, -offset) + +tt = Toon() +dna = ToonDNA.ToonDNA() +dna.newToonRandom(gender = 'f') +tt.setDNA(dna) +tt.reparentTo(render) + +base.disableMouse() +base.camLens.setFov(fov,fov) + +def convertServerDNAString(serverString): + # Strip out blank space and take last 30 characters + serverString = serverString.replace(' ', '') + stringLen = 30 + serverString = serverString[-stringLen:] + # Create a datagram from server string + dg = PyDatagram() + for i in range(0,len(serverString),2): + eval('dg.addUint8(0x%s)' % serverString[i:i+2]) + return dg.getMessage() + +def updateToon(DNAString): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(convertServerDNAString(DNAString)) + tt.setDNA(dna) + tt.pose('neutral', 0) + tt.stopLookAroundNow() + tt.stopBlink() + head = tt.getPart('head', '1000') + head.setHpr(headAngle, pitchAngle, rollAngle) + +def snapPics(): + data = getFileData(Filename(inputFile)) + myBgColorAlpha = hexToColor(bgColor)[3] + for topToonData in data: + DNAString = topToonData[0].replace(' ', '') + print DNAString + updateToon(DNAString) + base.graphicsEngine.renderFrame() + lookAtToon() + base.graphicsEngine.renderFrame() + imageName = outputDirectory + '/' + DNAString + "." + outputExtension + print ("Taking screenshot: " + imageName) + if myBgColorAlpha == 1.0: + base.win.saveScreenshot(Filename(imageName)) + else: + myImage = PNMImage.PNMImage(size,size) + base.win.getScreenshot(myImage) + myImage.addAlpha() + for y in xrange(size): + for x in xrange(size): + isOuterFramePixel = outerFrameImg.getRed(x,y) + if isOuterFramePixel: + myImage.setAlpha(x,y,myBgColorAlpha) + else: + myImage.setAlpha(x,y,1) + print myImage + myImage.write(Filename(imageName)) + +snapPics() + diff --git a/toontown/src/toon/__init__.py b/toontown/src/toon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/toon/rtmPrintfiles b/toontown/src/toon/rtmPrintfiles new file mode 100644 index 0000000..20cb4cc --- /dev/null +++ b/toontown/src/toon/rtmPrintfiles @@ -0,0 +1,198 @@ +#! /bin/sh + +# Configrc for running the show +# This uses the executable system now, not the configrc +echo $TOONTOWN/src/toon/Configrc.prc +echo $TOONTOWN/src/toon/runRTM.bat + +# Python files +echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/python.exe +echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/python24.dll +# built into 2.4 now +#echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_sre.pyd +echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_tkinter.pyd +echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_socket.pyd +echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/zlib.pyd + +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/lib-tk +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/site-packages/Pmw +echo $WINTOOLS/sdk/python/tcltk/lib +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/linecache.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/traceback.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/__future__.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/whrandom.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/copy_reg.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/copy.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/fnmatch.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/re.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_parse.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_constants.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_compile.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/types.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/string.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/os.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/site.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/stat.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/UserDict.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/ntpath.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/random.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/getopt.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/fpformat.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/bisect.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/warnings.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/inspect.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/dis.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/tokenize.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/token.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/ihooks.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/weakref.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/opcode.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/new.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/optparse.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/textwrap.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/gettext.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/locale.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/codecs.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/encodings +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/profile.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/pstats.py +echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/StringIO.py + +# Libraries +#echo $WINTOOLS/lib +echo $WINTOOLS/built/lib +echo $WINTOOLS/built/bin/avcodec.dll +echo $WINTOOLS/built/bin/avutil.dll +echo $WINTOOLS/built/bin/avformat.dll +#echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mss32.dll +#echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mp3dec.asi +echo $DTOOL/built/lib/libdtool.dll +echo $DTOOL/built/lib/libdtoolconfig.dll +echo $PANDA/built/lib/libpandaexpress.dll +echo $PANDA/built/lib/libpanda.dll +echo $PANDA/built/lib/libpandaphysics.dll +echo $PANDA/built/lib/libwindisplay.dll +echo $PANDA/built/lib/libmesadisplay.dll +echo $PANDA/built/lib/libpandadx9.dll +echo $PANDA/built/lib/libpandadx8.dll +echo $PANDA/built/lib/libpandagl.dll +echo $PANDA/built/lib/libpandafx.dll +echo $PANDA/built/lib/libpandaegg.dll +echo $PANDA/built/lib/libpandaode.dll +echo $PANDA/built/lib/libmiles_audio.dll +echo $DIRECT/built/lib/libdirect.dll +echo $DIRECT/built/lib/libheapq.dll +echo $OTP/built/lib/libotp.dll +echo $TOONTOWN/built/lib/libtoontown.dll + +# Generated code for libpanda +printlib $DIRECT/built/lib/pandac libpanda +printlib $DIRECT/built/lib/pandac libpandaphysics +echo $DIRECT/built/lib/pandac/*.py +#echo $DIRECT/built/lib/pandac/PandaModules.pyz + +# Generated code for libdirect +printlib $DIRECT/built/lib/pandac libdirect + +# Generated code for libotp +printlib $DIRECT/built/lib/pandac libotp + +# Generated code for libtoontown +printlib $DIRECT/built/lib/pandac libtoontown + +# DIRECT stuff +printdir $DIRECT/src/actor .py +printdir $DIRECT/src/cluster .py +printdir $DIRECT/src/controls .py +printdir $DIRECT/src/directbase .py +printdir $DIRECT/src/directdevices .py +printdir $DIRECT/src/directnotify .py +printdir $DIRECT/src/directscripts .py +printdir $DIRECT/src/directtools .py +printdir $DIRECT/src/directutil .py +printdir $DIRECT/src/distributed .py +printdir $DIRECT/src/ffi/ .py +printdir $DIRECT/src/fsm .py +printdir $DIRECT/src/gui .py +printdir $DIRECT/src/interval .py +printdir $DIRECT/src/particles .py +printdir $DIRECT/src/showbase .py +printdir $DIRECT/src/task .py +printdir $DIRECT/src/showbase .py +printdir $DIRECT/src/tkpanels .py +printdir $DIRECT/src/tkwidgets .py +printdir $DIRECT/src/extensions_native .py +printdir $DIRECT/src/stdpy .py + +# OTP stuff +echo $OTP/src/avatar/Avatar.py +echo $OTP/src/avatar/AvatarDNA.py +echo $OTP/src/avatar/Emote.py +echo $OTP/src/otpbase/OTPLocalizer.py +echo $OTP/src/otpbase/OTPLocalizerEnglish.py +echo $OTP/src/otpbase/OTPGlobals.py +echo $OTP/src/otpbase/OTPRender.py +echo $OTP/src/otpgui/OTPDialog.py +echo $OTP/src/avatar/ShadowCaster.py + +# Toontown stuff +echo $TOONTOWN/src/battle/BattleBase.py +echo $TOONTOWN/src/battle/BattleProps.py +echo $TOONTOWN/src/battle/BattleParticles.py +echo $TOONTOWN/src/battle/SuitBattleGlobals.py +echo $TOONTOWN/src/configfiles/NameMasterEnglish.txt +echo $TOONTOWN/src/effects/Wake.py +echo $TOONTOWN/src/effects/DustCloud.py +echo $TOONTOWN/src/effects/Fireworks.py +echo $TOONTOWN/src/effects/FireworkShows.py +echo $TOONTOWN/src/effects/FireworkGlobals.py +echo $TOONTOWN/src/hood/SkyUtil.py +echo $TOONTOWN/src/hood/ZoneUtil.py +printdir $TOONTOWN/src/leveleditor .py +echo $TOONTOWN/src/makeatoon/NameGenerator.py +echo $TOONTOWN/src/makeatoon/ +echo $TOONTOWN/src/toon/TTEmote.py +echo $TOONTOWN/src/toon/Motion.py +echo $TOONTOWN/src/toon/NPCToons.py +echo $TOONTOWN/src/toon/RobotToon.py +echo $TOONTOWN/src/toon/RobotToonManager.py +echo $TOONTOWN/src/toon/Toon.py +echo $TOONTOWN/src/toon/ToonDNA.py +echo $TOONTOWN/src/toon/ToonHead.py +echo $TOONTOWN/src/toonbase/TTLocalizer.py +echo $TOONTOWN/src/toonbase/TTLocalizerEnglish.py +echo $TOONTOWN/src/toonbase/ToontownBattleGlobals.py +echo $TOONTOWN/src/toonbase/ToontownGlobals.py +echo $TOONTOWN/src/suit/Suit.py +echo $TOONTOWN/src/suit/SuitDNA.py +echo $TOONTOWN/src/toontowngui/TTDialog.py +echo $TOONTOWN/src/pets/Pet.py +echo $TOONTOWN/src/pets/PetDNA.py +echo $TOONTOWN/src/racing/RaceGlobals.py +echo $TOONTOWN/src/golf/GolfGlobals.py +echo $TOONTOWN/src/distributed/DelayDelete.py +echo $TOONTOWN/src/parties/PartyGlobals.py + +echo $TOONTOWN/src/safezone/snowdisk.ptf + +# DMODELS stuff +echo $DMODELS/built/audio +echo $DMODELS/built/icons +echo $DMODELS/built/maps +echo $DMODELS/built/models + +# TTMODELS stuff +echo $TTMODELS/built/phase_3 +echo $TTMODELS/built/phase_3.5 +echo $TTMODELS/built/phase_4 +echo $TTMODELS/built/phase_5 +echo $TTMODELS/built/phase_5.5 +echo $TTMODELS/built/phase_6 +echo $TTMODELS/built/phase_7 +echo $TTMODELS/built/phase_8 +echo $TTMODELS/built/phase_9 +echo $TTMODELS/built/phase_13 +echo $TTMODELS/built/nondownload +echo $TTMODELS/built/models +echo $TTMODELS/src/dna/stylefiles diff --git a/toontown/src/toon/rtmZipfiles b/toontown/src/toon/rtmZipfiles new file mode 100644 index 0000000..36d0748 --- /dev/null +++ b/toontown/src/toon/rtmZipfiles @@ -0,0 +1,45 @@ +#! /bin/sh + +if [ "$1" = "-d" ] +then + #destdir=$2 + destdir="robotToonManager" + zipfile="robotToonManager.zip" + debug_state="-d" +else + #destdir=$1 + destdir="robotToonManager" + zipfile="robotToonManager.zip" + debug_state="" +fi + +if [ "${destdir}" = "" ] +then + echo "Usage: rtmZipfiles [-d]" + exit 1 +fi + +if [ -d ${destdir} ] +then + echo "Removing ${destdir}" + rm -rf ${destdir} +fi + +if mkdir ${destdir} +then + cp Configrc.prc Configrc + cp Configrc.rtm Configrc.prc + if copyfiles ${debug_state} ${destdir} rtmPrintfiles + then + if [ -f ${zipfile} ] + then + rm ${zipfile} + fi + zip -r ${zipfile} ${destdir} + rm -rf ${destdir} + echo "Done creating ${zipfile}" + cp Configrc Configrc.prc + fi +else + echo "ERROR: Could not mkdir ${destdir}" +fi diff --git a/toontown/src/toon/runRTM.bat b/toontown/src/toon/runRTM.bat new file mode 100644 index 0000000..ae606f8 --- /dev/null +++ b/toontown/src/toon/runRTM.bat @@ -0,0 +1,7 @@ +set PATH=lib +set PYTHONPATH=.;lib-tk +set TTMODELS=. +set DMODELS=. +set CFG_PATH=. +set CONFIG_CONFIG=:configpath=CFG_PATH +python.exe -i toontown\toon\RobotToonManager.py diff --git a/toontown/src/toon/runTTP.bat b/toontown/src/toon/runTTP.bat new file mode 100644 index 0000000..fbe8933 --- /dev/null +++ b/toontown/src/toon/runTTP.bat @@ -0,0 +1,6 @@ +set PATH=lib +set PYTHONPATH=.;lib-tk +set TTMODELS=. +set DMODELS=. +set PRC_DIR=. +python.exe toontown\toon\TopToonPics.py diff --git a/toontown/src/toon/runTTP.sh b/toontown/src/toon/runTTP.sh new file mode 100644 index 0000000..648a1f5 --- /dev/null +++ b/toontown/src/toon/runTTP.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +LD_LIBRARY_PATH=. +export LD_LIBRARY_PATH + +PRC_DIR=. +export PRC_DIR + +python -c 'import toontown.toon.TopToonPics' -s 0.20 -b FFFFFF -d /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d` + diff --git a/toontown/src/toon/runTTP_superToons.sh b/toontown/src/toon/runTTP_superToons.sh new file mode 100644 index 0000000..1cb0274 --- /dev/null +++ b/toontown/src/toon/runTTP_superToons.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +LD_LIBRARY_PATH=. +export LD_LIBRARY_PATH + +PRC_DIR=. +export PRC_DIR + +python -c 'import toontown.toon.TopToonPics' -s 0.20 -b FFFFCC -d /event_logs/superToons/ + +#python TopToonPics.py -s 0.20 -b FFFFCC -d /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small` + diff --git a/toontown/src/toon/runTTP_yellow.sh b/toontown/src/toon/runTTP_yellow.sh new file mode 100644 index 0000000..1af3f3b --- /dev/null +++ b/toontown/src/toon/runTTP_yellow.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +LD_LIBRARY_PATH=. +export LD_LIBRARY_PATH + +PRC_DIR=. +export PRC_DIR + +python -c 'import toontown.toon.TopToonPics' -s 0.20 -b FFFFCC -d /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small` + diff --git a/toontown/src/toon/top_toons_kart_racing.sh b/toontown/src/toon/top_toons_kart_racing.sh new file mode 100644 index 0000000..7e51f29 --- /dev/null +++ b/toontown/src/toon/top_toons_kart_racing.sh @@ -0,0 +1,136 @@ +#!/bin/sh +DOW=`date +%A` +#DOW='Thursday' +echo "$DOW" +#Move to the location where you want to run the script +cd /event_logs/ +rm /event_logs/toon*.gz +scp toonpub@toon21:/log/live/event/`date -d yesterday +%Y/%m/%d`/* . +#scp toonpub@toon21:/log/live/event/2005/12/14/* . +#Searches the file for completed quests +rm ttoon_* +zgrep -h '|questComplete|' /event_logs/toon*.gz | awk -F \| '{print $4}'> ttoon_quest +zgrep -h '|buildingDefeated|' /event_logs/toon*.gz | awk -F \| '{print $4}'> ttoon_building_defeated +zgrep -h '|fishedFish|' /event_logs/toon*.gz | awk -F \| '{print $4}' > ttoon_fish +zgrep -h '|fishedBoot|' /event_logs/toon*.gz | awk -F \| '{print $4}' > ttoon_boot +zgrep -h '|catalog-purchase|' /event_logs/toon*.gz | awk -F \| '{print $4}' > ttoon_catalogs +zgrep -h '|minigame|' /event_logs/toon*.gz | awk -F \| '{print $4}' > ttoon_minigame +zgrep -h '|kartingPlaced|' /event_logs/toon*.gz | awk -F \| '{print $4"|"$5}' | grep "|1" | awk -F \| '{print $1}' > ttoon_karting_placed +zgrep -h '|minigame|' /event_logs/toon*.gz | awk -F \| '{print $4"\t"$8}' > ttoon_beans +zgrep -h '|fishedFish|' /event_logs/toon*.gz | awk -F \| '{print $4"\t"$10}' >> ttoon_beans +zgrep -h '|kartingTicketsSpent|' /event_logs/toon*.gz | awk -F \| '{print $4"\t"$5}' > ttoon_tickets_spent +zgrep -h '|merits|' /event_logs/toon*.gz | awk -F \| '{print $4"\t"$8}' > ttoon_merits +zgrep -h '|merits|' /event_logs/toon*.gz | awk -F \| '{print $4"\t"$7}' > ttoon_bucks +zgrep -h '|bossBattle|' /event_logs/toon*.gz | grep "|s|" | grep "|Victory|" | awk -F \| '{print $7}' | sed -e 's/\[//g' -e 's/]//g' -e 's/,/\n/g' -e 's/ //g' -e 's/L//g' > ttoon_VP +zgrep -h '|bossBattle|' /event_logs/toon*.gz | grep "|m|" | grep "|Victory|" | awk -F \| '{print $7}' | sed -e 's/\[//g' -e 's/]//g' -e 's/,/\n/g' -e 's/ //g' -e 's/L//g' > ttoon_cfo_victory +zgrep -h '|cogsDefeated|' /event_logs/toon*.gz | awk -F \| '{print $4"|"$5}' | sed -e 's/+//g' -e 's/10$//g' -e 's/11$//g' -e 's/12$//g' -e 's/10,/,/g' -e 's/11,/,/g' -e 's/12,/,/g' -e 's/[0-9],/,/g' -e 's/[0-9]$//g' -e 's/[a-z]//g'| awk -F \| '{print $1","$2}' | awk -F \, '{s=0;for (i=1; i<=NF; i++) s=(s+$i); print $1"\t"s-$1}' > ttoon_cogs_defeated +if [ $DOW = "Thursday" ] +then +echo "$DOW" +zgrep -i "fishbingowin" /event_logs/toon*.gz | awk -F \| '{print $8"\n"$9"\n"$10"\n"$11}' > ttoon_bingo_win +awk -f /home/toonpub/scripts/word_freq.awk ttoon_bingo_win | sort +1 -nr | sed 5q > top_fish_bingo +else +echo "Not Thursday move on" +fi + +awk -f /home/toonpub/scripts/word_freq.awk ttoon_quest | sort +1 -nr | sed 5q > top_quests +awk -f /home/toonpub/scripts/word_freq.awk ttoon_building_defeated | sort +1 -nr | sed 5q > top_buildings +awk -f /home/toonpub/scripts/word_freq.awk ttoon_fish | sort +1 -nr | sed 5q > top_fish +awk -f /home/toonpub/scripts/word_freq.awk ttoon_boot | sort +1 -nr | sed 5q > top_boot +awk -f /home/toonpub/scripts/word_freq.awk ttoon_catalogs | sort +1 -nr | sed 5q > top_catalogs +awk -f /home/toonpub/scripts/word_freq.awk ttoon_minigame | sort +1 -nr | sed 5q > top_minigame +awk -f /home/toonpub/scripts/word_freq.awk ttoon_karting_placed | sort +1 -nr | sed 5q > top_karting_placed +awk -f /home/toonpub/scripts/word_freq.awk ttoon_VP | sort +1 -nr | sed 5q > top_VP +awk -f /home/toonpub/scripts/word_freq.awk ttoon_cfo_victory | sort +1 -nr | sed 5q > top_CFO +awk '{ x[$1] += $2 } END { for ( i in x ) print i, x[i] }' < ttoon_merits | sort +1 -nr | sed 5q > top_merits +awk '{ x[$1] += $2 } END { for ( i in x ) print i, x[i] }' < ttoon_bucks | sort +1 -nr | sed 5q > top_bucks +awk '{ x[$1] += $2 } END { for ( i in x ) print i, x[i] }' < ttoon_beans | sort +1 -nr | sed 5q > top_beans +awk '{ x[$1] += $2 } END { for ( i in x ) print i, x[i] }' < ttoon_tickets_spent | sort +1 -nr | sed 5q > top_tickets +awk '{ x[$1] += $2 } END { for ( i in x ) print i, x[i] }' < ttoon_cogs_defeated | sort +1 -nr | sed 5q > top_cogs +cd /event_logs/ +rm /event_logs/top_toons +rm /game_logs/topToonPictures/TopToons.txt +mkdir /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d` +mkdir /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small` + +cat top_cogs >> /event_logs/top_toons +cat top_buildings >> /event_logs/top_toons +cat top_catalogs >> /event_logs/top_toons +cat top_quests >> /event_logs/top_toons +cat top_minigame >> /event_logs/top_toons +cat top_beans >> /event_logs/top_toons +cat top_fish >> /event_logs/top_toons +cat top_boot >> /event_logs/top_toons +cat top_merits >> /event_logs/top_toons +cat top_VP >> /event_logs/top_toons +cat top_bucks >> /event_logs/top_toons +cat top_CFO >> /event_logs/top_toons +cat top_karting_placed >> /event_logs/top_toons +cat top_tickets >> /event_logs/top_toons +if [ $DOW = "Thursday" ] +then +echo "$DOWN" +cat top_fish_bingo >> /event_logs/top_toons +else +echo "Not Thursday move on" +fi + +cd /event_logs/ +cat /event_logs/top_toons | sed -n '1~5p' | awk -F \ '{print $1}' > /event_logs/toon_images +rm /event_logs/dna +rm /event_logs/top_toon_dna + +for file in $(cat /event_logs/toon_images); + do + wget --http-user=a1rw0lf --http-passwd=str33th4wk http://10.192.44.249:7780/_do_querry___data_toontown_dat?object_id=$file -O /event_logs/dna + cat /event_logs/dna | sed -e 's/<\/TH>/<\/TH>\n/g' | grep -a "setDNA" -A 1 | awk -F \< '{print $5}' | sed '2q' |awk -F \> '{print $1}' >> /event_logs/top_toon_dna + done +cat /event_logs/top_toon_dna | sed '/^$/d' > /game_logs/topToonPictures/TopToons.txt +cat /game_logs/topToonPictures/TopToons.txt | awk -F \\n '{print$1".jpg"}'|sed 1,1's/^/topToonImages=/g' > /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToonImages.txt +cat /event_logs/top_toons | awk -F \ '{print $1}' > /event_logs/toon_names +rm /event_logs/names +rm /event_logs/top_toon_names +rm /event_logs/top_toon_score_nav +for file in $(cat /event_logs/toon_names); + do + wget --http-user=a1rw0lf --http-passwd=str33th4wk http://10.192.44.249:7780/_do_querry___data_toontown_dat?object_id=$file -O /event_logs/names + grep -a "name='new_name" /event_logs/names | sed -e 's/> /event_logs/top_toon_names + done + +cat /event_logs/top_toon_names |sed 1,1's/^/topToons=/g'|sed 50's/$/\&/' > /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToons.txt +cat /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToons.txt >> /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToon.txt +rm /event_logs/toon_scores +cat /event_logs/top_toons | awk -F \ '{print $2}' > /event_logs/toon_scores +cat /event_logs/top_toons | awk -F \ '{print $2}' >> /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToon.txt +cat /event_logs/toon_scores | sed 1,1's/^/\&topToonScores=/g' > /event_logs/top_toon_score_nav +cat /event_logs/top_toon_score_nav >> /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToon.txt + +cd /game_logs/topToonPictures/ +cp /game_logs/topToonPictures2/ttp.prc /game_logs/topToonPictures/ttp.prc +cat ttp.prc | sed 's/win-width 50/win-width 60/g' | sed 's/win-height 50/win-height 60/g' > temp_log | mv temp_log ttp.prc +./runTTP.sh +cat ttp.prc | sed 's/win-width 60/win-width 50/g' | sed 's/win-height 60/win-height 50/g' > temp_log | mv temp_log ttp.prc +./runTTP_yellow.sh + +if [ $DOW = "Thursday" ] +then +echo "$DOW" +echo "USED THURSDAY PHP" +php -q /vol01/www/html/topToonsphppageRacingThursday.php > /event_logs/`date -d yesterday +%Y_%m_%d`.php +cat /event_logs/`date -d yesterday +%Y_%m_%d`.php | sed 's/Content-type: text\/html//' > /event_logs/topToons_`date -d yesterday +%Y_%m_%d`.php +else +echo "non Thursday" +php -q /vol01/www/html/topToonsphppageRacing.php > /event_logs/`date -d yesterday +%Y_%m_%d`.php +cat /event_logs/`date -d yesterday +%Y_%m_%d`.php | sed 's/Content-type: text\/html//' > /event_logs/topToons_`date -d yesterday +%Y_%m_%d`.php +fi +echo "created php pages" +scp /event_logs/topToons_`date -d yesterday +%Y_%m_%d`.php toonpub@toon21:/toontown/1400/common/dynamic/topToons/ +scp -r /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d`/ toonpub@toon21:/toontown/1400/common/shared/images/dynamic/topToonImages/ +scp -r /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/ toonpub@toon21:/toontown/1400/common/shared/images/dynamic/topToonImages/ +echo "Moved Images to 21" +top_toon_file_count=`wc -l /event_logs/topToonImages/`date -d yesterday +%Y_%m_%d_small`/TopToon.txt` + +#ssh toonpub@toon21 /home/toonpub/bin/syncTopToons.sh live +ssh toonpub@toon21 /home/toonpub/bin/syncTopToons.sh qa + +#mysql -u rweiner -pmini100 remarks_orig_copy < top_toon_images > top_toon_dna diff --git a/toontown/src/toon/ttp.prc b/toontown/src/toon/ttp.prc new file mode 100644 index 0000000..29a3825 --- /dev/null +++ b/toontown/src/toon/ttp.prc @@ -0,0 +1,47 @@ +win-size 50 50 +preload-avatars #t + +default-model-extension .bam + +# Mesa doesn't work under Windows - let's use Dx now + +#window-type offscreen +# At the moment, pandadx does not support offscreen rendering. Prefer +# mesadisplay, which does this well and doesn't require anyone logged +# onto the desktop. Fall back to pandagl if something goes wrong with +# mesadisplay. +#load-display mesadisplay +#aux-display pandagl + +# mesadisplay seems to crash unless you turn off vertex-arrays. +#vertex-arrays 0 + +load-display pandadx9 +aux-display pandadx8 + +# Configrc for running the Robot Toon Manager + +# THESE LINES ALLOW YOU TO USE DOWNLOAD MODELS INSTEAD OF TTMODELS +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_3.5.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_4.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_5.5.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_6.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_7.mf /tt 0 +#vfs-mount /c/Program Files/Disney/Disney Online/Toontown/phase_8.mf /tt 0 + +# Use local copy of ttmodels +model-path . +# Putting this line after ttmodels means models will be read from here first +# model-path /tt +sound-path . +dna-preload phase_4/dna/storage.dna + +text-encoding utf8 + +cull-bin shadow 15 unsorted + +# Turn off noisy errors. +notify-level-chan error + diff --git a/toontown/src/toon/ttpPrintFiles b/toontown/src/toon/ttpPrintFiles new file mode 100644 index 0000000..384ad5b --- /dev/null +++ b/toontown/src/toon/ttpPrintFiles @@ -0,0 +1,212 @@ +#! /bin/sh + +# Config file for running the show +echo $TOONTOWN/src/toon/ttp.prc + +if [ -z "$DTOOL" -a -d "$PLAYER" ]; then + # Unattached case. + directsrc=$PLAYER/direct/src + otpsrc=$PLAYER/otp/src + toontownsrc=$PLAYER/toontown/rsc + + dtoolbuilt=$PLAYER/install + pandabuilt=$PLAYER/install + directbuilt=$PLAYER/install + otpbuilt=$PLAYER/install + toontownbuilt=$PLAYER/install + dmodelsbuilt=$PLAYER/install + ttmodelsbuilt=$PLAYER/install + +else + # Attached case. + directsrc=$DIRECT/src + otpsrc=$OTP/src + toontownsrc=$TOONTOWN/src + + dtoolbuilt=$DTOOL/built + pandabuilt=$PANDA/built + directbuilt=$DIRECT/built + otpbuilt=$OTP/built + toontownbuilt=$TOONTOWN/built + dmodelsbuilt=$DMODELS/built + ttmodelsbuilt=$TTMODELS/built +fi + +if [ "$WINTOOLS" ]; then + # Windows case + echo $toontownsrc/toon/runTTP.bat + + # Python files + echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/python.exe + echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/python24.dll + # built into 2.4 now + #echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_sre.pyd + echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_tkinter.pyd + echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/_socket.pyd + echo $WINTOOLS/sdk/python/Python-2.4.1/PCbuild/zlib.pyd + + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/lib-tk + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/linecache.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/traceback.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/__future__.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/whrandom.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/copy_reg.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/copy.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/fnmatch.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/re.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_parse.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_constants.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/sre_compile.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/types.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/string.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/os.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/site.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/stat.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/UserDict.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/ntpath.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/random.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/getopt.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/fpformat.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/bisect.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/warnings.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/inspect.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/dis.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/tokenize.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/token.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/ihooks.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/weakref.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/opcode.py + echo $WINTOOLS/sdk/python/Python-2.4.1/Lib/new.py + + echo $WINTOOLS/built/lib/nspr4.dll + echo $WINTOOLS/built/lib/highgui096.dll + echo $WINTOOLS/built/lib/cxcore096.dll + + # Libraries + echo $WINTOOLS/built/lib + echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mss32.dll + echo $WINTOOLS/sdk/rad/Miles6/redist/win32/Mp3dec.asi + echo $WINTOOLS/built/bin/avcodec.dll + echo $WINTOOLS/built/bin/avformat.dll + echo $WINTOOLS/built/bin/avutil.dll + echo $dtoolbuilt/lib/libdtool.dll + echo $dtoolbuilt/lib/libdtoolconfig.dll + echo $pandabuilt/lib/libpandaexpress.dll + echo $pandabuilt/lib/libpanda.dll + echo $pandabuilt/lib/libpandaphysics.dll + echo $pandabuilt/lib/libwindisplay.dll + echo $pandabuilt/lib/libmesadisplay.dll + echo $pandabuilt/lib/libpandadx8.dll + echo $pandabuilt/lib/libpandadx9.dll + echo $pandabuilt/lib/libpandagl.dll + echo $pandabuilt/lib/libpandafx.dll + echo $pandabuilt/lib/libpandaegg.dll + echo $pandabuilt/lib/libmiles_audio.dll + echo $directbuilt/lib/libdirect.dll + echo $directbuilt/lib/libheapq.dll + echo $otpbuilt/lib/libotp.dll + echo $toontownbuilt/lib/libtoontown.dll + +else + # Unix case + echo $toontownsrc/toon/runTTP*.sh + echo $toontownsrc/toon/top_toons_kart_racing.sh + + echo $dtoolbuilt/lib/*.so + echo $pandabuilt/lib/*.so + echo $directbuilt/lib/*.so + echo $otpbuilt/lib/*.so + echo $toontownbuilt/lib/*.so +fi + +# Generated code for libpanda +printlib $directbuilt/lib/pandac libpanda +printlib $directbuilt/lib/pandac libpandaphysics +echo $directbuilt/lib/pandac/*.py + + +# Generated code for libdirect +printlib $directbuilt/lib/pandac libdirect + +# Generated code for libotp +printlib $directbuilt/lib/pandac libotp + +# Generated code for libtoontown +printlib $directbuilt/lib/pandac libtoontown + +# DIRECT stuff +printdir $directsrc/actor .py +printdir $directsrc/cluster .py +printdir $directsrc/controls .py +printdir $directsrc/directbase .py +printdir $directsrc/directdevices .py +printdir $directsrc/directnotify .py +printdir $directsrc/directscripts .py +printdir $directsrc/directtools .py +printdir $directsrc/directutil .py +printdir $directsrc/distributed .py +printdir $directsrc/ffi/ .py +printdir $directsrc/fsm .py +printdir $directsrc/gui .py +printdir $directsrc/interval .py +printdir $directsrc/particles .py +printdir $directsrc/showbase .py +printdir $directsrc/task .py +printdir $directsrc/showbase .py +printdir $directsrc/tkpanels .py +printdir $directsrc/tkwidgets .py + +# OTP stuff +echo $otpsrc/avatar/Avatar.py +echo $otpsrc/avatar/AvatarDNA.py +echo $otpsrc/avatar/Emote.py +echo $otpsrc/avatar/ShadowCaster.py +echo $otpsrc/otpbase/OTPLocalizer.py +echo $otpsrc/otpbase/OTPLocalizerEnglish.py +echo $otpsrc/otpbase/OTPGlobals.py +echo $otpsrc/otpbase/OTPRender.py +echo $otpsrc/otpgui/OTPDialog.py + +# Toontown stuff +echo $toontownsrc/battle/BattleBase.py +echo $toontownsrc/battle/BattleProps.py +echo $toontownsrc/battle/SuitBattleGlobals.py +echo $toontownsrc/configfiles/NameMasterEnglish.txt +echo $toontownsrc/effects/Wake.py +echo $toontownsrc/effects/DustCloud.py +echo $toontownsrc/hood/SkyUtil.py +echo $toontownsrc/hood/ZoneUtil.py +echo $toontownsrc/leveleditor .py +echo $toontownsrc/makeatoon/NameGenerator.py +echo $toontownsrc/racing/RaceGlobals.py +echo $toontownsrc/suit/Suit.py +echo $toontownsrc/suit/SuitDNA.py +echo $toontownsrc/toon/TTEmote.py +echo $toontownsrc/toon/Motion.py +echo $toontownsrc/toon/NPCToons.py +echo $toontownsrc/toon/TopToonPics.py +echo $toontownsrc/toon/Toon.py +echo $toontownsrc/toon/ToonDNA.py +echo $toontownsrc/toon/ToonHead.py +echo $toontownsrc/toonbase/TTLocalizer.py +echo $toontownsrc/toonbase/TTLocalizerEnglish.py +echo $toontownsrc/toonbase/ToontownBattleGlobals.py +echo $toontownsrc/toonbase/ToontownGlobals.py +echo $toontownsrc/toontowngui/TTDialog.py + +# DMODELS stuff +echo $dmodelsbuilt/audio +echo $dmodelsbuilt/icons +echo $dmodelsbuilt/maps +echo $dmodelsbuilt/models + +# TTMODELS stuff +echo $ttmodelsbuilt/phase_3 +echo $ttmodelsbuilt/phase_3.5 +echo $ttmodelsbuilt/phase_4 +echo $ttmodelsbuilt/phase_5 +echo $ttmodelsbuilt/phase_5.5 +echo $ttmodelsbuilt/phase_9 +echo $ttmodelsbuilt/nondownload +echo $ttmodelsbuilt/models diff --git a/toontown/src/toon/ttpZipfiles b/toontown/src/toon/ttpZipfiles new file mode 100644 index 0000000..ba865d9 --- /dev/null +++ b/toontown/src/toon/ttpZipfiles @@ -0,0 +1,42 @@ +#! /bin/sh + +if [ "$1" = "-d" ] +then + #destdir=$2 + destdir="topToonPictures" + zipfile="topToonPictures.zip" + debug_state="-d" +else + #destdir=$1 + destdir="topToonPictures" + zipfile="topToonPictures.zip" + debug_state="" +fi + +if [ "${destdir}" = "" ] +then + echo "Usage: ttpZipfiles [-d]" + exit 1 +fi + +if [ -d ${destdir} ] +then + echo "Removing ${destdir}" + rm -rf ${destdir} +fi + +if mkdir ${destdir} +then + if copyfiles ${debug_state} ${destdir} ./ttpPrintFiles + then + if [ -f ${zipfile} ] + then + rm ${zipfile} + fi + zip -r ${zipfile} ${destdir} + rm -rf ${destdir} + echo "Done creating ${zipfile}" + fi +else + echo "ERROR: Could not mkdir ${destdir}" +fi diff --git a/toontown/src/toonbase/.cvsignore b/toontown/src/toonbase/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/toonbase/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/toonbase/DisplayOptions.py b/toontown/src/toonbase/DisplayOptions.py new file mode 100644 index 0000000..10a5cf6 --- /dev/null +++ b/toontown/src/toonbase/DisplayOptions.py @@ -0,0 +1,267 @@ +import copy +import string +import os +import sys +import datetime +from pandac.PandaModules import loadPrcFileData, Settings, WindowProperties +from otp.otpgui import OTPDialog +from otp.otpbase import OTPGlobals +from otp.otpbase import OTPRender +from direct.directnotify import DirectNotifyGlobal + +try: + import embedded +except: + pass + +class DisplayOptions: + """A class stolen from Pirates so we can respect saved window settings when we get to Pick A Toon.""" + notify = DirectNotifyGlobal.directNotify.newCategory("DisplayOptions") + + def __init__(self): + self.restore_failed = False + #self.restrictToEmbedded(1, False) + self.loadFromSettings() + + def loadFromSettings(self): + """Read the saved settings.""" + Settings.readSettings() + mode = not Settings.getWindowedMode() + music = Settings.getMusic() + sfx = Settings.getSfx() + toonChatSounds = Settings.getToonChatSounds() + musicVol = Settings.getMusicVolume() + sfxVol = Settings.getSfxVolume() + resList = [(640, 480),(800,600),(1024,768),(1280,1024),(1600,1200)] #copied from Resolution in settingsFile.h + res = resList[Settings.getResolution()] + embed = Settings.getEmbeddedMode() + self.notify.debug("before prc settings embedded mode=%s" % str(embed)) + self.notify.debug("before prc settings full screen mode=%s" % str(mode)) + if mode == None: + mode = 1 + if res == None: + res = (800,600) + + loadPrcFileData("toonBase Settings Window Res", ("win-size %s %s" % (res[0], res[1]))) + self.notify.debug("settings resolution = %s" % str(res)) + loadPrcFileData("toonBase Settings Window FullScreen", ("fullscreen %s" % (mode))) + self.notify.debug("settings full screen mode=%s" % str(mode)) + loadPrcFileData("toonBase Settings Music Active", ("audio-music-active %s" % (music))) + loadPrcFileData("toonBase Settings Sound Active", ("audio-sfx-active %s" % (sfx))) + loadPrcFileData("toonBase Settings Music Volume", ("audio-master-music-volume %s" % (musicVol))) + loadPrcFileData("toonBase Settings Sfx Volume", ("audio-master-sfx-volume %s" % (sfxVol))) + loadPrcFileData("toonBase Settings Toon Chat Sounds", ("toon-chat-sounds %s" % (toonChatSounds))) + self.settingsFullScreen = mode + self.settingsWidth = res[0] + self.settingsHeight = res[1] + self.settingsEmbedded = embed + self.notify.debug("settings embedded mode=%s" % str(self.settingsEmbedded)) + + self.notify.info("settingsFullScreen = %s, embedded = %s width=%d height=%d" % + (self.settingsFullScreen, self.settingsEmbedded, + self.settingsWidth, self.settingsHeight)) + # these settings test error recovery, as no one has a 16000 x 12000 monitor (yet) + #self.settingsFullScreen = True + #self.settingsWidth = 16000 + #self.settingsHeight = 12000 + + def restrictToEmbedded(self, restrict, change_display = True): + # to completely disable restrict to embedded, uncomment: + #restrict = 0 + + # if we are not running embedded, restricting to + # embedded is not an option + if base.appRunner is None or base.appRunner.windowProperties is None: + restrict = 0 + + self.restrict_to_embedded = choice(restrict, 1, 0) + self.notify.debug("restrict_to_embedded: %s" % self.restrict_to_embedded) + + # window mode may have changed + if change_display: + self.set( base.pipe, self.settingsWidth, self.settingsHeight, + self.settingsFullScreen, self.settingsEmbedded) + + def set(self, pipe, width, height, fullscreen, embedded): + self.notify.debugStateCall(self) + state = False + + self.notify.info("SET") + + #fullscreen = options.fullscreen_runtime + #embedded = options.embedded_runtime + if self.restrict_to_embedded: + fullscreen = 0 + embedded = 1 + + if embedded: + if base.appRunner.windowProperties: + width = base.appRunner.windowProperties.getXSize () + height = base.appRunner.windowProperties.getYSize () + + self.current_pipe = base.pipe + self.current_properties = WindowProperties(base.win.getProperties()) + + properties = self.current_properties + self.notify.debug("DISPLAY PREVIOUS:") + self.notify.debug(" EMBEDDED: %s" % bool(properties.getParentWindow ( ))) + self.notify.debug(" FULLSCREEN: %s" % bool(properties.getFullscreen ( ))) + self.notify.debug(" X SIZE: %s" % properties.getXSize ( )) + self.notify.debug(" Y SIZE: %s" % properties.getYSize ( )) + self.notify.debug("DISPLAY REQUESTED:") + self.notify.debug(" EMBEDDED: %s" % bool(embedded)) + self.notify.debug(" FULLSCREEN: %s" % bool(fullscreen)) + self.notify.debug(" X SIZE: %s" % width) + self.notify.debug(" Y SIZE: %s" % height) + + if ((self.current_pipe == pipe) and \ + (bool(self.current_properties.getParentWindow( )) == bool(embedded)) and \ + (self.current_properties.getFullscreen ( ) == fullscreen) and \ + (self.current_properties.getXSize ( ) == width) and \ + (self.current_properties.getYSize ( ) == height)): + # no display change required + self.notify.info("DISPLAY NO CHANGE REQUIRED") + state = True + else: + properties = WindowProperties() + properties.setSize(width, height) + properties.setFullscreen(fullscreen) + properties.setParentWindow(0) + + if embedded: + if base.appRunner.windowProperties: + properties = base.appRunner.windowProperties + + # get current sort order + original_sort = base.win.getSort ( ) + + if self.resetWindowProperties(pipe, properties): + self.notify.debug("DISPLAY CHANGE SET") + + # verify display change + properties = base.win.getProperties() + + self.notify.debug("DISPLAY ACHIEVED:") + self.notify.debug(" EMBEDDED: %s" % bool(properties.getParentWindow ( ))) + self.notify.debug(" FULLSCREEN: %s" % bool(properties.getFullscreen ( ))) + self.notify.debug(" X SIZE: %s" % properties.getXSize ( )) + self.notify.debug(" Y SIZE: %s" % properties.getYSize ( )) + + if ((bool(properties.getParentWindow( )) == bool(embedded)) and \ + (properties.getFullscreen ( ) == fullscreen) and \ + (properties.getXSize ( ) == width) and \ + (properties.getYSize ( ) == height)): + self.notify.info("DISPLAY CHANGE VERIFIED") + state = True + else: + self.notify.warning("DISPLAY CHANGE FAILED, RESTORING PREVIOUS DISPLAY") + self.restoreWindowProperties () + else: + self.notify.warning("DISPLAY CHANGE FAILED") + self.notify.warning("DISPLAY SET - BEFORE RESTORE") + self.restoreWindowProperties () + self.notify.warning("DISPLAY SET - AFTER RESTORE") + + # set current sort order + base.win.setSort (original_sort) + + base.graphicsEngine.renderFrame() + base.graphicsEngine.renderFrame() + + return state + + def resetWindowProperties(self, pipe, properties): + if base.win: + currentProperties = WindowProperties(base.win.getProperties()) + gsg = base.win.getGsg() + else: + currentProperties = WindowProperties.getDefault() + gsg = None + + # Check to see if the window properties will change in any + # important way. + newProperties = WindowProperties(currentProperties) + newProperties.addProperties(properties) + + if base.pipe != pipe: + gsg = None + + if (gsg == None) or \ + (currentProperties.getFullscreen() != newProperties.getFullscreen()) or \ + (currentProperties.getParentWindow() != newProperties.getParentWindow()): + # For now, assume that if we change fullscreen state, we + # need to destroy the window and create a new one. + + self.notify.debug("window properties: %s" % properties) + self.notify.debug("gsg: %s" % gsg) + + base.pipe = pipe + if not base.openMainWindow(props = properties, gsg = gsg, + keepCamera = True): + self.notify.warning("OPEN MAIN WINDOW FAILED") + return 0 + + self.notify.info("OPEN MAIN WINDOW PASSED") + + base.graphicsEngine.openWindows() + if base.win.isClosed(): + self.notify.warning("Window did not open, removing.") + base.closeWindow(base.win) + return 0 + + base.disableShowbaseMouse() + + # If we've already imported (and therefore downloaded) + # libotp.dll, then we already have a NametagGlobals, and + # we should keep it up-to-date with the new MouseWatcher + # etc. + if 'libotp' in sys.modules: + from libotp import NametagGlobals + NametagGlobals.setCamera(base.cam) + NametagGlobals.setMouseWatcher(base.mouseWatcherNode) + + else: + # If the properties are changing only slightly + # (e.g. window size), we can keep the current window and + # just adjust its properties directly. + self.notify.debug("Adjusting properties") + base.win.requestProperties(properties) + base.graphicsEngine.renderFrame() + + return 1 + + def restoreWindowProperties (self): + if (self.resetWindowProperties(self.current_pipe, self.current_properties)): + self.restore_failed = False + else: + # Oops, we couldn't get the original settings back! + self.notify.warning("Couldn't restore original display settings!") + + if base.appRunner and base.appRunner.windowProperties: + # Try to go back into embedded. That might help. + fullscreen = 0 + embedded = 1 + tryProps = base.appRunner.windowProperties + if (self.resetWindowProperties(self.current_pipe, tryProps)): + self.current_properties = copy.copy(tryProps) + self.restore_failed = False + return + + if self.current_properties.getFullscreen(): + # Try again without the fullscreen option. That might + # help. + fullscreen = 0 + embedded = 0 + tryProps = self.current_properties + tryProps.setFullscreen(0) + if (self.resetWindowProperties(self.current_pipe, tryProps)): + self.current_properties = copy.copy(tryProps) + self.restore_failed = False + return + + # Couldn't even open a regular window. This is kind of + # like a low-level panic situation. + self.notify.error("Failed opening regular window!") + base.panda3dRenderError() + self.restore_failed = True + return diff --git a/toontown/src/toonbase/Sources.pp b/toontown/src/toonbase/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/toonbase/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/toonbase/TTLocalizer.py b/toontown/src/toonbase/TTLocalizer.py new file mode 100644 index 0000000..41c0a6f --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer.py @@ -0,0 +1,66 @@ +""" +This module determines what language to run the game in +and imports the appropriate language module. +Import this module, not the individual language modules +to use in the game. +""" + +# Do not import panda modules because it is not downloaded until Phase 3 +# This file is in phase 2 +from pandac.libpandaexpressModules import * +import string +import types + +try: + # Client + # The Launcher will define config in the builtin namespace + # before importing this file + language = getConfigExpress().GetString("language", "english") + checkLanguage = getConfigExpress().GetBool("check-language", 0) +except: + # AI + language = simbase.config.GetString("language", "english") + checkLanguage = simbase.config.GetBool("check-language", 0) + +# Ask what language we are running in. Returns a string. +def getLanguage(): + return language + +print ("TTLocalizer: Running in language: %s" % (language)) +if language == 'english': + _languageModule = "toontown.toonbase.TTLocalizer" + string.capitalize(language) +else: + checkLanguage = 1 + _languageModule = "toontown.toonbase.TTLocalizer_" + language + +print ("from " + _languageModule + " import *") +exec("from " + _languageModule + " import *") + +if checkLanguage: + l = {} + g = {} + englishModule = __import__("toontown.toonbase.TTLocalizerEnglish", g, l) + foreignModule = __import__(_languageModule, g, l) + for key, val in englishModule.__dict__.items(): + if not foreignModule.__dict__.has_key(key): + print ("WARNING: Foreign module: %s missing key: %s" % (_languageModule, key)) + # Add the english version to our local namespace so we do not crash + locals()[key] = val + else: + # The key is in both files, but if it is a dictionary we + # should go one step further and make sure the individual + # elements also match. + if isinstance(val, types.DictType): + fval = foreignModule.__dict__.get(key) + for dkey, dval in val.items(): + if not fval.has_key(dkey): + print ("WARNING: Foreign module: %s missing key: %s.%s" % (_languageModule, key, dkey)) + fval[dkey] = dval + for dkey in fval.keys(): + if not val.has_key(dkey): + print ("WARNING: Foreign module: %s extra key: %s.%s" % (_languageModule, key, dkey)) + + + for key in foreignModule.__dict__.keys(): + if not englishModule.__dict__.has_key(key): + print ("WARNING: Foreign module: %s extra key: %s" % (_languageModule, key)) diff --git a/toontown/src/toonbase/TTLocalizerCastillian.py b/toontown/src/toonbase/TTLocalizerCastillian.py new file mode 100644 index 0000000..ec1cc97 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizerCastillian.py @@ -0,0 +1,8889 @@ +import string +from LocalizerCastillianProperty import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# common names +Mickey = "Mickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Goofy" +Pluto = "Pluto" +Flippy = "Flipi" + +# common locations +lTheBrrrgh = 'The Brrrgh' +lDaisyGardens = 'Daisy Gardens' +lDonaldsDock = "Donald's Dock" +lDonaldsDreamland = "Donald's Dreamland" +lMinniesMelodyland = "Minnie's Melodyland" +lToontownCentral = 'Toontown Central' + +# common strings +lCancel = 'Cancel' +lClose = 'Close' +lOK = 'OK' +lNext = 'Next' +lNo = 'No' +lQuit = 'Quit' +lYes = 'Yes' + +lHQOfficerF = 'HQ Officer' +lHQOfficerM = 'HQ Officer' + +MickeyMouse = "Mickey Mouse" + +AIStartDefaultDistrict = "Villaboba" + +Cog = "Bot" +Cogs = "Bots" +ACog = "un Bot" +TheCogs = "Los bots" +Skeleton = "esquelebot" +SkeletonP = "esquelebots" +ASkeleton = "un esquelebot" +Foreman = "capataz" +ForemanP = "capataces" +AForeman = "un capataz" +CogVP = Cog + " VIP" +CogVPs = "bots VIPS" +ACogVP = ACog + " un VIP" + +# Quests.py +TheFish = "los peces" +AFish = "un pez" +Level = "nivel" +QuestsCompleteString = "Completada" +QuestsNotChosenString = "No está elegida" +Period = "." + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("¡Hola, _avName_!", + "¡Buenas, _avName_!", + "¿Qué tal, _avName_?", + "¿Qué tal todo, _avName_?", + "¿Cómo te va, _avName_?", + "¿Qué hay, _avName_?", + "¿Cómo estás, _avName_?", + "Greetings _avName_!", + ) +QuestsDefaultIncomplete = ("¿Qué tal va la tarea, _avName_?", + "¡Parece que todavía te queda trabajo por hacer con esa tarea!", + "¡Sigue trabajando así, _avName_!", + "Intenta terminar la tarea. ¡Sé que puedes hacerlo!", + "¡Sigue intentando terminar la tarea, contamos contigo!", + "¡Sigue trabajando en tu dibutarea!", + ) +QuestsDefaultIncompleteProgress = ("Has venido al lugar adecuado, pero antes tienes que terminar la dibutarea.", + "Cuando hayas terminado con la dibutarea, vuelve por aquí.", + "Vuelve cuando hayas terminado la dibutarea.", + ) +QuestsDefaultIncompleteWrongNPC = ("Buen trabajo con esa dibutarea. Deberías ir a ver a _toNpcName_._where_", + "Parece que estás a punto de acabar tu dibutarea. Ve a ver a _toNpcName_._where_.", + "Ve a ver a _toNpcName_ para terminar la dibutarea._where_", + ) +QuestsDefaultComplete = ("¡Buen trabajo! Aquí está tu recompensa...", + "¡Buen trabajo, _avName_! Toma, tu recompensa...", + "¡Muy bien, _avName_! Aquí está tu recompensa...", + ) +QuestsDefaultLeaving = ("¡Chao!", + "¡Adiós!", + "Nos vemos, _avName_.", + "¡Hasta la vista, _avName_!", + "¡Buena suerte!", + "¡Diviértete en Toontown!", + "¡Hasta luego!", + ) +QuestsDefaultReject = ("Hola.", + "¿En qué puedo ayudarte?", + "¿Cómo estás?", + "Muy buenas.", + "Ahora estoy un poco ocupado, _avName_.", + "¿Sí?", + "¿Qué hay, _avName_?", + "¿Cómo te va, _avName_?", + "¡Eh, _avName_! ¿Cómo va todo?", + # Game Hints + "¿Sabías que puedes abrir el dibucuaderno pulsando la tecla F8?", + "¡Puedes usar el mapa para teletransportarte de vuelta al dibuparque!", + "Para hacerte amigo de otros jugadores, haz clic en ellos.", + "Para averiguar más sobre un " + Cog + " haz clic en él.", + "Reúne tesoros en el dibuparque para rellenar el risómetro.", + "Edificios" + Cog + " son lugares peligrosos! ¡No entres en ellos solo!", + "Cuando pierdas un combate, los " + Cogs + " se llevarán todas tus bromas.", + "Para conseguir más bromas, juega a los juegos del tranvía!", + "Para conseguir más puntos de risa, completa las dibutareas.", + "Todas las dibutareas proporcionan recompensas.", + "Algunas recompensas sirven para poder llevar más bromas.", + "Si ganas un combate, consigues créditos de dibutareas por cada " + Cog + " derrotado.", + "Si recuperas un edificio " + Cog + " vuelve a entrar para ver el agradecimiento especial de su propietario.", + "Para mirar hacia arriba, mantén pulsada la tecla Re Pág.", + "Si pulsas la tecla Tab, podrás contemplar diferentes vistas de los alrededores.", + "Para mostrar lo que piensas a tus amigos secretos, escribe un '.' antes del pensamiento.", + "Cuando dejes aturdido a un " + Cog + " le será más difícil esquivar los objetos que caen.", + "Cada tipo de edificio " + Cog + " tiene un aspecto distinto.", + "Cuando derrotes a los " + Cogs + " de los pisos altos de un edificio, obtendrás habilidades superiores.", + ) +QuestsDefaultTierNotDone = ("¡Hola, _avName_! Antes de acceder a una nueva dibutarea debes terminar la actual.", + "¡Muy buenas! Tienes que terminar las dibutareas actuales para acceder a una nueva.", + "¡Buenas, _avName_! Para poderte asignar una nueva dibutarea, tienes que terminar las dibutareas actuales.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("He oído que _toNpcName_ te anda buscando._where_", + "Déjate caer por donde _toNpcName_ cuando tengas la ocasión._where_", + "Ve a ver a _toNpcName_ cuando pases por allí._where_", + "Si tienes la ocasión, pásate a saludar a _toNpcName_._where_", + "_toNpcName_ te asignará tu próxima dibutarea._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "つ" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogQuestHeadline = "SE BUSCA" +QuestsCogQuestSCStringS = "Tengo que derrotar un %(cogName)s%(cogLoc)s." +QuestsCogQuestSCStringP = "Tengo que derrotar algunos %(cogName)s%(cogLoc)s." +QuestsCogQuestDefeat = "Derrotar a %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Ayuda a los dibus con %d puntos de risas o derrota a algunos %s" +QuestsCogNewNewbieQuestCaption = "Ayuda a un nuevo dibu con %d puntos de risas o menos" +QuestsCogOldNewbieQuestObjective = "Help a Toon with %(laffPoints)d Laff or less defeat %(objective)s" +QuestsCogOldNewbieQuestCaption = "Help a Toon %d Laff or less" +QuestsCogNewbieQuestAux = "Derrotar:" +QuestsNewbieQuestHeadline = "Aprendís" + +QuestsCogTrackQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogTrackQuestHeadline = "SE BUSCA" +QuestsCogTrackQuestSCStringS = "Tengo que derrotar un %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestSCStringP = "Tengo que derrotar algunos %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Derrotar a %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogLevelQuestHeadline = "SE BUSCA" +QuestsCogLevelQuestDefeat = "Derrotar a %s" +QuestsCogLevelQuestDesc = "un " + Cog + " de nivel %(level)s+" +QuestsCogLevelQuestDescC = "%(count)s " + Cogs + " de nivel %(level)s+" +QuestsCogLevelQuestDescI = "algunos " + Cogs + " de nivel %(level)s+" +QuestsCogLevelQuestSCString = "Tengo que derrotar %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'dos+', 'tres+', 'cuatro+', 'cinco+') +QuestsBuildingQuestBuilding = "Edificio" +QuestsBuildingQuestBuildings = "Edificios" +QuestsBuildingQuestHeadline = "DERROTAR" +QuestsBuildingQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsBuildingQuestString = "Derrotar a %s" +QuestsBuildingQuestSCString = "Tengo que derrotar %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "un edificio %(type)s" +QuestsBuildingQuestDescF = "un edificio %(type)s de %(floors)s pisos" +QuestsBuildingQuestDescC = "%(count)s edificios %(type)s" +QuestsBuildingQuestDescCF = "%(count)s edificios %(type)s de %(floors)s pisos" +QuestsBuildingQuestDescI = "algunos edificios %(type)s" +QuestsBuildingQuestDescIF = "algunos edificios %(type)s de %(floors)s pisos" + +QuestFactoryQuestFactory = "Fábrica" +QuestsFactoryQuestFactories = "Fábricas" +QuestsFactoryQuestHeadline = "DERROTAR" +QuestsFactoryQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsFactoryQuestString = "Derrotar a %s" +QuestsFactoryQuestSCString = "Tengo que derrotar a %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "una fábrica %(type)s" +QuestsFactoryQuestDescC = "%(count)s fábricas %(type)s" +QuestsFactoryQuestDescI = "algunas fábricas %(type)s" + +QuestsRescueQuestProgress = "%(progress)s de %(numToons)s rescatados" +QuestsRescueQuestHeadline = "RESCATAR" +QuestsRescueQuestSCStringS = "Tengo que rescatar a un dibu%(toonLoc)s." +QuestsRescueQuestSCStringP = "Tengo que rescatar a algunos dibus%(toonLoc)s." +QuestsRescueQuestRescue = "Rescatar a %s" +QuestsRescueQuestRescueDesc = "%(numToons)s dibus" +QuestsRescueQuestToonS = "un dibu" +QuestsRescueQuestToonP = "dibus" +QuestsRescueQuestAux = "Rescatar a:" + +QuestsRescueNewbieQuestObjective = "Ayuda a un nuevo dibu a rescatar a %s" +QuestsRescueOldNewbieQuestObjective = "Help a Toon with %(laffPoints)d Laff or less rescue %(objective)s" + +QuestCogPartQuestCogPart = "Pieza de traje bot" +QuestsCogPartQuestFactories = "Fábricas" +QuestsCogPartQuestHeadline = "RECUPERAR" +QuestsCogPartQuestProgressString = "%(progress)s de %(num)s recuperadas" +QuestsCogPartQuestString = "Recuperar %s" +QuestsCogPartQuestSCString = "Tengo que recuperar %(objective)s%(location)s." +QuestsCogPartQuestAux = "Recuperar:" + +QuestsCogPartQuestDesc = "una pieza de traje bot" +QuestsCogPartQuestDescC = "%(count)s piezas de traje bot " +QuestsCogPartQuestDescI = "algunas piezas de traje bot " + +QuestsCogPartNewbieQuestObjective = 'Ayuda a un nuevo dibu a recuperar %s' +QuestsCogPartOldNewbieQuestObjective = 'Help a Toon with %(laffPoints)d Laff or less retrieve %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s de %(numGags)s entregados" +QuestsDeliverGagQuestHeadline = "ENTREGAR" +QuestsDeliverGagQuestToSCStringS = "Tengo que entregar %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Tengo que entregar algunos %(gagName)s." +QuestsDeliverGagQuestSCString = "Tengo que hacer una entrega." +QuestsDeliverGagQuestString = "Entregar %s" +QuestsDeliverGagQuestStringLong = "Entregar %s a _toNpcName_." +QuestsDeliverGagQuestInstructions = "Tú puedes comprar esta broma en la Tienda de Bromas una vez que tengas acceso a la broma." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "ENTREGAR" +QuestsDeliverItemQuestSCString = "Tengo que entregar %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Entregar %s" +QuestsDeliverItemQuestStringLong = "Entregar %s a _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITAR" +QuestsVisitQuestStringShort = "Visitar" +QuestsVisitQuestStringLong = "Ir a ver a _toNpcName_" +QuestsVisitQuestSeeSCString = "Tengo que ver a %s." + +QuestsRecoverItemQuestProgress = "%(progress)s de %(numItems)s recuperados" +QuestsRecoverItemQuestHeadline = "RECUPERAR" +QuestsRecoverItemQuestSeeHQSCString = "Tengo que ver a un funcionario del cuartel general." +QuestsRecoverItemQuestReturnToHQSCString = "Tengo que devolver %s a un funcionario del cuartel general." +QuestsRecoverItemQuestReturnToSCString = "Tengo que devolver %(item)s a %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Tengo que ir al Cuartel General Dibu." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Tengo que ir al dibuparque %s." +QuestsRecoverItemQuestGoToStreetSCString = "Tengo que ir %(to)s %(street)s en %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Tengo que ir a %s %s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "¿Dónde está %s %s?" +QuestsRecoverItemQuestRecoverFromSCString = "Tengo que recuperar: %(item)s de %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Recuperar %(item)s de %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "ELIGE" +QuestsTrackChoiceQuestSCString = "Tengo que escoger entre %(trackA)s y %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Quizá deba escoger %s." +QuestsTrackChoiceQuestString = "Elige entre %(trackA)s y %(trackB)s" + +QuestsFriendQuestHeadline = "AMIGO" +QuestsFriendQuestSCString = "Necesito hacer amigos." +QuestsFriendQuestString = "Hacer un amigo" + +QuestsMailboxQuestHeadline = "MAIL" +QuestsMailboxQuestSCString = "I need to check my mail." +QuestsMailboxQuestString = "Check your mail" + +QuestsPhoneQuestHeadline = "CLARABELLE" +QuestsPhoneQuestSCString = "I need to call Clarabelle." +QuestsPhoneQuestString = "Call Clarabelle" + +QuestsFriendNewbieQuestString = "Has %d amigos con %d puntos de risa o menos" +QuestsFriendNewbieQuestProgress = "%(progress)s de %(numFriends)s hechos" +QuestsFriendNewbieQuestObjective = "Haste amigo con %d dibus que tengan %d puntos de risa o menos" + +QuestsTrolleyQuestHeadline = "TRANVÍA" +QuestsTrolleyQuestSCString = "Tengo que subir al tranvía." +QuestsTrolleyQuestString = "Subir al tranvía." +QuestsTrolleyQuestStringShort = "¿Quieres subir al tranvía?" + +QuestsMinigameNewbieQuestString = "%d Minijuegos" +QuestsMinigameNewbieQuestProgress = "%(progress)s of %(numMinigames)s Played" +QuestsMinigameNewbieQuestObjective = "Juega %d minijuegos con Dibus que tienen %d puntos un el risometro o menos." +QuestsMinigameNewbieQuestSCString = "Necesito jugar en los minijuegos con Dibus nuevos." +QuestsMinigameNewbieQuestCaption = "Ayuda a un Dibu Nuevo con %d puntos en el risometro o menos." +QuestsMinigameNewbieQuestAux = "Juega:" + +QuestsMaxHpReward = "Tu risómetro ha aumentado en %s." +QuestsMaxHpRewardPoster = "Recompensa: %s punto(s) de aumento en el risómetro" + +QuestsMoneyRewardSingular = "Has conseguido 1 gominola." +QuestsMoneyRewardPlural = "Has conseguido %s gominolas." +QuestsMoneyRewardPosterSingular = "Recompensa: 1 gominola" +QuestsMoneyRewardPosterPlural = "Recompensa: %s gominolas" + +QuestsMaxMoneyRewardSingular = "Ahora puedes llevar 1 gominola." +QuestsMaxMoneyRewardPlural = "Ahora puedes llevar %s gominolas." +QuestsMaxMoneyRewardPosterSingular = "Recompensa: Llevar 1 gominola" +QuestsMaxMoneyRewardPosterPlural = "Recompensa: Llevar %s gominolas" + +QuestsMaxGagCarryReward = "Consigues un %(name)s. Ahora puedes llevar %(num)s bromas." +QuestsMaxGagCarryRewardPoster = "Recompensa: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "Ahora puedes tener %s dibutareas." +QuestsMaxQuestCarryRewardPoster = "Recompensa: Tener %s dibutareas" + +QuestsTeleportReward = "Ahora puedes teletransportarte a %s." +QuestsTeleportRewardPoster = "Recompensa: Acceso por teletransporte a %s" + +QuestsTrackTrainingReward = "Ahora puedes entrenar las bromas de \"%s\"." +QuestsTrackTrainingRewardPoster = "Recompensa: Entrenamiento de bromas" + +QuestsTrackProgressReward = "Ahora tienes el fotograma %(frameNum)s de la animación del circuito %(trackName)s." +QuestsTrackProgressRewardPoster = "Recompensa: Fotograma %(frameNum)s de la animación de circuito \"%(trackName)s\"" + +QuestsTrackCompleteReward = "Ahora puedes llevar y usar las bromas de \"%s\"." +QuestsTrackCompleteRewardPoster = "Recompensa: Entrenamiento de circuito %s final" + +QuestsClothingTicketReward = "Puedes cambiarte de ropa" +QuestsClothingTicketRewardPoster = "Recompensa: Ticket de ropa" + +QuestsCheesyEffectRewardPoster = "Recompensa: %s" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "en este parque" +QuestsStreetLocationThisStreet = "en esta calle" +QuestsStreetLocationNamedPlayground = "en el parque de %s" +QuestsStreetLocationNamedStreet = "en la %(toStreetName)s de %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "El edificio de %s se llama" +QuestsLocationBuildingVerb = "el cual está " +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Necesito terminar la Dibutarea." + +# MaxGagCarryReward names +QuestsMediumPouch = "Bolsita mediana" +QuestsLargePouch = "Bolsita grande" +QuestsSmallBag = "Bolsa pequeña" +QuestsMediumBag = "Bolsa mediana" +QuestsLargeBag = "Bolsa grande" +QuestsSmallBackpack = "Mochila pequeña" +QuestsMediumBackpack = "Mochila mediana" +QuestsLargeBackpack = "Mochila grande" +QuestsItemDict = { + 1 : ["Gafas", "Gafas", "unas "], + 2 : ["Llave", "Llaves", "una "], + 3 : ["Pizarra", "Pizarras", "una "], + 4 : ["Libro", "Libros", "un "], + 5 : ["Chocolatina", "Chocolatinas", "una "], + 6 : ["Tiza", "Tizas", "una "], + 7 : ["Receta", "Recetas", "una "], + 8 : ["Nota", "Notas", "una "], + 9 : ["Calculadora", "Calculadoras", "una "], + 10 : ["Neumático de coche de payasos", "Neumáticos de coche de payasos", "un "], + 11 : ["Bomba de aire", "Bombas de aire", "una "], + 12 : ["Tinta de pulpo", "Tintas de pulpo", "un poco de "], + 13 : ["Paquete", "Paquetes", "un "], + 14 : ["Recibo de pez de acuario", "Recibos de pez de acuario", "un "], + 15 : ["Pez de acuario", "Peces de acuario", "un "], + 16 : ["Aceite", "Aceites", "un poco de "], + 17 : ["Grasa", "Grasas", "un poco de "], + 18 : ["Agua", "Aguas", "un poco de "], + 19 : ["Informe de equipo", "Informes de equipo", "un "], + 20 : ["Blackboard Eraser", "Blackboard Erasers", "a "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Ticket de ropa", "Tickets de ropa", "un "], + + # Donald's Dock quest items + 2001 : ["Tubo interno", "Tubos internos", "un "], + 2002 : ["Prescripción de monóculo", "Prescripciones de monóculo", "una "], + 2003 : ["Montura de monóculo", "Monturas de monóculo", "una "], + 2004 : ["Monóculo", "Monóculos", "un "], + 2005 : ["Peluca blanca grande", "Pelucas blancas grandes", "una "], + 2006 : ["Granel de lastre", "Graneles de lastre", "una "], + 2007 : ["Engranaje de bot", "Engranajes de bot", "un "], + 2008 : ["Carta náutica", "Cartas náuticas", "una "], + 2009 : ["Coraza repugnante", "Corazas repugnantes", "un "], + 2010 : ["Coraza limpia", "Corazas limpias", "un "], + 2011 : ["Resorte de reloj", "Resortes de reloj", "un "], + 2012 : ["Contrapeso", "Contrapesos", "un "], + + # Minnie's Melodyland quest items + 4001 : ["Inventario de Tina", "Inventarios de Tina", ""], + 4002 : ["Inventario de Uki", "Inventarios de Uki", ""], + 4003 : ["Formulario de inventario", "Formularios de inventario", "un "], + 4004 : ["Inventario de Bibi", "Inventarios de Bibi", ""], + 4005 : ["Billete de Chopo Chopín", "Billetes de Chopo Chopín", ""], + 4006 : ["Billete de Felisa Felina", "Billetes de Felisa Felina", ""], + 4007 : ["Billete de Barbo", "Billetes de Barbo", ""], + 4008 : ["Castañuela sucia", "Castañuelas sucias", ""], + 4009 : ["Tinta azul de pulpo", "Tintas azules de pulpo", "un poco de "], + 4010 : ["Castañuela transparente", "Castañuelas transparentes", "una "], + 4011 : ["Letra de Leo", "Letras de Leo", ""], + + # Daisy's Gardens quest items + 5001 : ["Corbata de seda", "Corbatas de seda", "una "], + 5002 : ["Traje mil rayas", "Trajes mil rayas", "un "], + 5003 : ["Tijera", "Tijeras", "unas "], + 5004 : ["Postal", "Postales", "una "], + 5005 : ["Pluma", "Plumas", "una "], + 5006 : ["Tintero", "Tinteros", "un "], + 5007 : ["Libreta", "Libretas", "una "], + 5008 : ["Caja de seguridad", "Cajas de seguridad", "una "], + 5009 : ["Bolsa de alpiste", "Bolsas de alpiste", "una "], + 5010 : ["Rueda dentada", "Ruedas dentadas", "una "], + 5011 : ["Ensalada", "Ensaladas", "una "], + 5012 : ["Llave de los Jardines de Daisy", "Llaves de los Jardines de Daisy", "una "], + 5013 : ["planos del cuartel general vendebot", "planos del cuartel general vendebot", "algunos "], + 5014 : ["nota del cuartel general vendebot", "notas del cuartel general vendebot", "una "], + 5015 : ["nota del cuartel general vendebot", "notas del cuartel general vendebot", "una "], + 5016 : ["nota del cuartel general vendebot", "notas del cuartel general vendebot", "una "], + 5017 : ["nota del cuartel general vendebot", "notas del cuartel general vendebot", "una "], + + # The Brrrgh quests + 3001 : ["Balón de fútbol", "Balones de fútbol", "un "], + 3002 : ["Tobogán", "Toboganes", "un "], + 3003 : ["Cubito de hielo", "Cubitos de hielo", "un "], + 3004 : ["Carta de amor", "Cartas de amor", "una "], + 3005 : ["Perrito caliente", "Perritos calientes", "un "], + 3006 : ["Anillo de compromiso", "Anillos de compromiso", "un "], + 3007 : ["Aleta de sardina", "Aletas de sardina", "una "], + 3008 : ["Poción calmante", "Pociones calmantes", "una "], + 3009 : ["Diente roto", "Dientes rotos", "un "], + 3010 : ["Diente de oro", "Dientes de oro", "un "], + 3011 : ["Pan de piñones", "Panes de piñones", "un "], + 3012 : ["Queso abultado", "Quesos abultados", "unos "], + 3013 : ["Cuchara sencilla", "Cucharas sencillas", "una "], + 3014 : ["Sapo parlanchín", "Sapos parlanchines", "un "], + 3015 : ["Helado de cucurucho", "Helados de cucurucho ", "un "], + 3016 : ["Talco para peluca", "Talco para pelucas", "un poco de "], + 3017 : ["Patito de goma", "Patitos de goma", "un "], + 3018 : ["Dados de goma", "Dados de goma", "unos "], + 3019 : ["Micrófono", "Micrófonos", "un "], + 3020 : ["Teclado electrónico", "Teclados electrónicos", "un "], + 3021 : ["Zapatos con plataforma", "Zapatos con plataforma", "unos "], + 3022 : ["Caviar", "Caviar", "un poco de "], + 3023 : ["Maquillaje", "Maquillaje", "un poco de "], + } +QuestsHQOfficerFillin = "Funcionario del cuartel general" +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = "Cuartel General Dibu" +QuestsHQLocationNameFillin = "en cualquier barrio" + +QuestsTailorFillin = "Sastre" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Tienda de Ropa" +QuestsTailorLocationNameFillin = "en cualquier barrio" +QuestsTailorQuestSCString = "Tengo que ir al sastre." + +QuestMovieQuestChoiceCancel = "¡Vuelve más tarde, si necesitas una Dibutarea! ¡Chao!" +QuestMovieTrackChoiceCancel = "Vuelve más tarde, cuando te hayas decidido! ¡Chao!" +QuestMovieQuestChoice = "Elige una Dibutarea." +QuestMovieTrackChoice = "¿Te has decidido? Elige un circuito, o vuelve más tarde." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Ya estás listo.\aSal y ponte a caminar hasta que decidas qué circuito elegir.\aPiénsalo bien, porque éste será tu circuito final.\aCuando estés seguro, vuelve conmigo.", + INCOMPLETE_PROGRESS : "Piénsalo bien.", + INCOMPLETE_WRONG_NPC : "Piénsalo bien.", + COMPLETE : "¡Muy buena elección!", + LEAVING : "Buena suerte. Vuelve conmigo cuando tengas dominada tu nueva habilidad.", + } + +QuestDialog_3225 = { + QUEST : "Gracias por venir, _avName_!\a"+TheCogs+" del vecindario han asustado a mi repartidor.\aNo tengo a nadie que entregue esta ensalada a _toNpcName_!\a¿Puedes encargarte tú? ¡Muchas gracias!_where_" + } + +QuestDialog_2910 = { + QUEST : "¿Ya has vuelto?\aBuen trabajo con el resorte.\aEl objeto final es un contrapeso.\aPásate a ver a _toNpcName_ y tráete lo que encuentres._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 jefebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, has derrotado a los jefebots. ¡Ve al cuartel general dibu para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 abogabots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, has derrotado a los abogabots. ¡Ve al cuartel general dibu para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 chequebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, has derrotado a los chequebots. ¡Ve al cuartel general dibu para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 vendebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, has derrotado a los vendebots. ¡Ve al cuartel general dibu para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Creo que te vendrán bien unas cuantas bromas nuevas.\aVe a ver a %s, quizá te pueda ayudar._where_" % Flippy }, + 165 : {QUEST : "Muy buenas.\aCreo que tienes que practicar con las bromas.\aCada vez que alcances a un bot con una de las bromas, aumentará tu experiencia.\aCuando tengas la suficiente experiencia, podrás usar bromas mejores.\aPractica ahora con tus bromas derrotando a 4 bots."}, + 166 : {QUEST : "Te felicito, has derrotado a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 jefebots."}, + 167 : {QUEST : "Te felicito, has derrotado a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 abogabots."}, + 168 : {QUEST : "Te felicito, has derrotado a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 vendebots."}, + 169 : {QUEST : "Te felicito, has derrotado a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 chequebots."}, + 170 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; él te aconsejará bien._where_" }, + 171 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; él te aconsejará bien._where_" }, + 172 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; ella te aconsejará bien._where_" }, + + 175 : {GREETING : "", + QUEST : "Did you know you have your very own Toon house?\aClarabelle Cow runs a phone catalog where you can order furniture to decorate your house.\aYou can also buy SpeedChat phrases, clothing, and other fun things!\aI'll tell Clarabelle to send you your first catalog now.\aYou get a catalog with new items every week!\aGo to your home and use your phone to call Clarabelle.", + INCOMPLETE_PROGRESS : "Go home and use your phone to call Clarabelle.", + COMPLETE : "Hope you have fun ordering things from Clarabelle!\aI just finished redecorating my house. It looks Toontastic!\aKeep doing ToonTasks to get more rewards!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Las bromas de lanzamiento y chorro son estupendas, pero te harán falta otras para enfrentarte a los bots de niveles superiores.\aCuando te juntes con otros dibus para luchar contra los bots, podrás combinar ataques para infligirles más daños. \aPrueba con distintas combinaciones de trucos para ver cuáles funcionan mejor.\aEn el siguiente circuito, escoge entre Sonido y Curadibu.\aSonido es una broma especial que causa daños a todos los bots al hacer impacto.\aCuradibu te permite sanar a otros dibus durante el combate.\aCuando te hayas decidido, vuelve para elegir la broma que desees.", + INCOMPLETE_PROGRESS : "¿Ya has vuelto? Vale, ¿te has decidido ya?", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Buena decisión. Antes de usar esas bromas, deberás entrenarte con ellas.\aEn el entrenamiento tienes que completar una serie de dibutareas.\aCada tarea te proporcionará un fotograma de la animación del ataque con la broma.\aCuando reúnas los 15, conseguirás la tarea final de entrenamiento, que te permitirá usar la nueva broma.\aComprueba cómo vas en el dibucuaderno.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Si quieres recorrer la ciudad más fácilmente, ve a ver a _toNpcName_._where_" }, + 1040 : { QUEST : "Si quieres recorrer la ciudad más fácilmente, ve a ver a _toNpcName_._where_" }, + 1041 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos, o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amiguete mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1042 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos, o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amiguete mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1043 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos, o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amiguete mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1044 : { QUEST : "Vaya, gracias por pasarte por aquí. La verdad es que necesito ayuda.\aComo ves, no tengo clientes.\aHe perdido mi libro secreto de recetas y ya nadie viene a mi restaurante.\aLo vi por última vez justo antes de que los bots ocupasen mi edificio.\a¿Puedes ayudarme a recuperar cuatro famosas recetas mías?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mis recetas?" }, + 1045 : { QUEST : "¡Muchas gracias!\aEn poco tiempo tendré todo el recetario y podré volver a abrir mi restaurante.\aAh, tengo una nota para ti: algo sobre el teletransporte.\aDice Gracias por ayudar a mi amigo. Entrega esto en el Cuartel General Dibu.\aDe verdad, muchas gracias.\a¡Adiós!", + LEAVING : "", + COMPLETE : "Ah, sí, aquí dice que has sido de gran ayuda para algunos de los amigos de la calle Locuela.\aDice que necesitas teletransportarte al centro de Toontown.\aPues bien, eso está hecho.\aAhora puedes teletransportarte para volver al dibuparque desde casi cualquier lugar de Toontown.\aAbre tu mapa y haz clic en Centro de Toontown." }, + 1046 : { QUEST : "Los chequebots han estado dando la lata en la Caja de Ahorros Pastagansa.\aPásate por allí para ver si puedes hacer algo._where_" }, + 1047 : { QUEST : "Los chequebots se han estado colando en el banco para robar nuestras calculadoras.\aRecupera cinco calculadoras que han robado los chequebots.\aPara no tener que estar yendo y viniendo, tráelas todas de una vez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Sigues buscando las calculadoras?" }, + 1048 : { QUEST : "¡Vaya! Gracias por recuperar nuestras calculadoras.\aMmm... Están un poco estropeadas.\a¿Puedes llevárselas a _toNpcName_ a su tienda, \"Cosquilladores automáticos\" en esta calle?\aTal vez ella pueda arreglarlas.", + LEAVING : "", }, + 1049 : { QUEST : "¿Qué es eso? ¿Calculadoras rotas?\a¿Chequebots, dices?\aUm, veamos...\aSí, han quitado los engranajes, pero no me quedan repuestos...\a¿Sabes que podría servirnos? Unos engranajes de bots, grandes, de bots grandotes.\aLos engranajes de bots de nivel 3 valdrán. Necesito dos para cada máquina, así que son diez en total.\a¡Tráelos enseguida y los arreglaré!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Recuerda que necesito diez engranajes para arreglar las calculadoras." }, + 1053 : { QUEST : "Muy bien, con esto seguro que vale.\aTodas arregladas, es gratis.\aDevuélveselas a Pastagansa y salúdales de mi parte.", + LEAVING : "", + COMPLETE : "¿Están arregladas todas las calculadoras?\aBuen trabajo. Seguro que tengo algo por aquí con lo que recompensarte..." }, + 1054 : { QUEST : "_toNpcName_ necesita ayuda con sus coches de payasos._where_" }, + 1055 : { QUEST : "¡Hola! ¡No puedo encontrar por ningún sitio los neumáticos de este coche de payasos!\a¿Crees que podrás echarme una mano?\aMe parece que Perico Pirado los ha tirado al estanque del dibuparque del centro de Toontown. \aSi te colocas encima de uno de los amarraderos del estanque podrás intentar pescar los neumáticos para traérmelos.", + GREETING : "¡Jujujú!", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Algún problemilla pescando los cuatro neumáticos?" }, + 1056 : { QUEST : "¡Estupendísimo! ¡Ahora puedo volver a conducir este viejo coche de payasos!\aEh, creía que tenía una vieja bomba de aire por aquí para inflar estos neumáticos...\a¿Se la habrá llevado prestada _toNpcName_?\a¿Podrías hacerme el favor de pedirle que me la devuelva?_where_", + LEAVING : "" }, + 1057 : { QUEST : "¿Qué tal?\a¿Una bomba de aire, dices?\a¿Sabes lo que te digo? Si me ayudas a limpiar las calles de algunos de esos bots de nivel alto...\aTe daré la bomba de aire.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Eso es todo lo que sabes hacer?" }, + 1058 : { QUEST : "Buen trabajo, sabía que lo conseguirías.\aAquí está la bomba. Seguro que _toNpcName_ se alegra de recuperarla.", + LEAVING : "", + GREETING : "", + COMPLETE : "¡Yujuuu! ¡Ya puedo conducir!\aPor cierto, gracias por ayudarme.\aToma esto." }, + 1059 : { QUEST : "_toNpcName_ se está quedando sin suministros. ¿Puedes echarle una mano?_where_" }, + 1060 : { QUEST : "¡Gracias por venir!\aEsos bots han estado robándome la tinta, y casi no me queda nada.\a¿Podrías pescar un poco de tinta de pulpo en el estanque?\aPara pescar, basta con que te sitúes en el amarradero de la orilla.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has tenido problemas para pescar?" }, + 1061 : { QUEST : "¡Estupendo, gracias por la tinta!\a¿Sabes qué? Si quitases de en medio a unos cuantos de esos chupatintas...\aNo me quedaría sin tinta tan rápidamente.\aDerrota a seis chupatintas en el centro de Toontown para llevarte una recompensa.", + LEAVING : "", + COMPLETE : "¡Gracias! Te recompensaré por tu ayuda.", + INCOMPLETE_PROGRESS : "He visto unos cuantos chupatintas más." }, + 1062 : { QUEST : "¡Estupendo, gracias por la tinta!\a¿Sabes qué? Si quitases de en medio a unos cuantos de esos chupasangres...\aNo me quedaría sin tinta tan rápidamente.\aDerrota a seis chupasangres en el centro de Toontown para llevarte una recompensa.", + LEAVING : "", + COMPLETE : "¡Gracias! Te recompensaré por tu ayuda.", + INCOMPLETE_PROGRESS : "He visto unos cuantos chupasangres más." }, + 900 : { QUEST : "He oído que _toNpcName_ necesita ayuda con un paquete._where_" }, + 1063 : { QUEST : "¡Hola, gracias por venir!\aUn bot me ha robado un paquete muy importante delante de mis narices.\aPor favor, intenta recuperarlo. Creo que era de nivel 3...\aAsí que tendrás que derrotar a bots de nivel 3 hasta que encuentres mi paquete.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No ha habido suerte con el paquete, ¿eh?" }, + 1067 : { QUEST : "¡Ése es, muy bien!\aEh, la dirección está borrosa...\aSólo se lee que es para un doctor, el resto está emborronado.\a¿Será para _toNpcName_? ¿Puedes llevárselo?_where_", + LEAVING : "" }, + 1068 : { QUEST : "No esperaba ningún paquete. Quizá sea para el doctor Eufo Rico.\aMi ayudante iba a ir allí hoy de todas formas, así que le diré que se lo pregunte.\aMientras tanto, ¿te importa deshacerte de unos cuantos bots en mi calle?\aDerrota a diez bots en el centro de Toontown.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mi ayudante no ha vuelto todavía." }, + 1069 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un chequebot se lo ha robado a mi ayudante cuando volvía.\a¿Podrías intentar recuperarlo?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No ha habido suerte con el paquete, ¿eh?" }, + 1070 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un vendebot se lo ha robado a mi ayudante cuando volvía.\aLo siento, pero tendrás que encontrar a ese vendebot para recuperarlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No ha habido suerte con el paquete, ¿eh?" }, + 1071 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un jefebot se lo ha robado a mi ayudante cuando volvía.\a¿Podrías intentar recuperarlo?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No ha habido suerte con el paquete, ¿eh?" }, + 1072 : { QUEST : "¡Estupendo, lo has recuperado!\aDeberías probar con _toNpcName_, puede que sea para él._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, gracias por traerme mis paquetes.\aEspera un momento, estaba esperando dos. ¿Podrías ir a ver a _toNpcName_ para preguntarle si tiene el otro?", + INCOMPLETE : "¿Has conseguido encontrar mi otro paquete?", + LEAVING : "" }, + 1074 : { QUEST : "¿Ha dicho que había otro paquete? A lo mejor lo han robado también los bots.\aDerrota a bots hasta que encuentres el segundo paquete.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No ha habido suerte con el otro paquete, ¿eh?" }, + 1075 : { QUEST : "¡Al final resulta que sí que había un segundo paquete!\aDeprisa, llévaselo a _toNpcName_ y pídele disculpas de mi parte.", + COMPLETE : "¡Eh, ha llegado mi paquete!\aComo pareces ser un dibu muy servicial, esto te vendrá bien.", + LEAVING : "" }, + 1076 : { QUEST : "Ha habido problemas en Peces dorados de 14 kilates.\aA _toNpcName_ le vendrá bien una ayudita._where_" }, + 1077 : { QUEST : "Gracias por venir. "+TheCogs+" han robado todos mis peces dorados.\aCreo que quieren venderlos para sacar un dinero rápido.\aEsos cinco peces han sido mi única compañía en esta tiendecita durante tantos años...\aSi pudieses hacerme el favor de recuperarlos, te lo agradecería eternamente.\aSeguro que uno de los bots tiene mis peces.\aDerrota a bots hasta que encuentres mis peces.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Por favor, recupera mis peces dorados." }, + 1078 : { QUEST : "¡Oh, tienes mis peces!\a¿Eh? ¿Qué es eso? ¿Un recibo?\aAh, sí son los bots, a fin de cuentas.\aNo consigo averiguar qué diantre es este recibo. ¿Podrías llevárselo a _toNpcName_ para ver si él lo entiende?_where_", + INCOMPLETE : "¿Qué ha dicho _toNpcName_ del recibo?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, déjame ver ese recibo.\a... Ah, sí, dice que un pez dorado fue vendido a un secuaz.\aNo menciona para nada qué ha sido de los otros cuatro peces.\aQuizá debas ponerte a buscar a ese secuaz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No creo que te pueda ayudar más.\a¿Por qué no te pones a buscar ese pez dorado?" }, + 1092 : { QUEST : "Mmm, déjame ver ese recibo.\a... Ah, sí, dice que un pez dorado fue vendido a un calderilla.\aNo menciona para nada qué ha sido de los otros cuatro peces.\aQuizá debas ponerte a buscar a ese calderilla.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No creo que te pueda ayudar más.\a¿Por qué no te pones a buscar ese pez dorado?" }, + 1080 : { QUEST : "¡Oh, gracias a Dios! Has encontrado a Óscar. Es mi favorito.\a¿Qué pasa, Óscar? Oh, vaya... ¿de verdad? ... ¿Están allí?\aÓscar dice que los otros cuatro escaparon y se metieron en el estanque del dibuparque.\a¿Me haces el favor de ir a recogerlos? \aBasta con que los pesques en el estanque.", + LEAVING : "", + COMPLETE : "¡Oooh, qué contento estoy! ¡Por fin vuelvo a estar junto a mis amiguitos!\a¡Te mereces una estupenda recompensa!", + INCOMPLETE_PROGRESS : "¿Te está costando encontrar a los peces?" }, + 1081 : { QUEST : "Parece ser que _toNpcName_ se encuentra en una situación pegajosa. Seguro que le vendrá bien una ayudita._where_" }, + 1082 : { QUEST : "¡Se me ha derramado el pegamento rápido y me he quedado pegado!\aSi pudiera hacer algo para liberarme...\aSe me ocurre una idea, si te sientes valiente.\aDerrota a unos vendebots y tráeme un poco de aceite.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1083 : { QUEST : "El aceite ha ayudado, pero sigo sin despegarme del todo.\a¿Qué puedo probar? Nada procede.\aSe me ocurre una idea, por probarla no pasa nada.\aDerrota a unos abogabots y tráeme un poco de grasa.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1084 : { QUEST : "No, no ha servido de nada. Esto no me divierte.\aHe puesto la grasa y no ha habido suerte.\aSe me ocurre una idea para sacarme de aquí.\aDerrota a unos chequebots y trae agua para mojarme.", + LEAVING : "", + GREETING : "", + COMPLETE : "¡Hurra! Estoy libre de este pegamento rápido.\aComo recompensa, toma este obsequio.\aPodrás disfrutar de una combativa velada...\a¡Oh, no! ¡He vuelto a quedarme pegado!", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1085 : { QUEST : "_toNpcName_ está llevando a cabo ciertas investigaciones sobre los bots.\aVete a hablar con él si quieres ayudarle._where_" }, + 1086 : { QUEST : "Efectivamente, estoy realizando un estudio sobre los bots.\aQuiero saber cómo funcionan.\aMe sería de gran ayuda que consiguieses algunos engranajes de bot.\aAsegúrate de que pertenezcan a bots de nivel 2 al menos, para que tengan el tamaño suficiente para examinarlos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿No puedes conseguir suficientes engranajes?" }, + 1089 : { QUEST : "Muy bien, veamos. ¡Excelentes especímenes!\aMmm...\aDe acuerdo, aquí está mi informe. Lleva esto enseguida al cuartel general dibu.", + INCOMPLETE : "¿Has llevado mi informe al cuartel general?", + COMPLETE : "Buen trabajo, _avName_, a partir de ahora nos ocuparemos nosotros.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ tiene información útil para ti._where_" }, + 1091 : { QUEST : "He oído que en el cuartel general dibu están trabajando en una especie de radar de bots.\aTe permitirá ver dónde están los bots y así poder encontrarlos más fácilmente.\aLa página Bot del dibucuaderno es la clave.\aSi derrotas suficientes bots, podrás sintonizar sus señales y detectar su paradero.\aSigue derrotando a los bots para estar listo.", + COMPLETE : "¡Buen trabajo! Seguro que esto te viene bien...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsatelo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Esos bots tan pesados están dando problemas otra vez.\a_toNpcName_ ha informado de que falta otro objeto. Pásate por allí, a ver si puedes arreglar la situación._where_" }, + 2202 : { QUEST : "Hola, _avName_. Gracias a Dios que has venido. Un cacomatraco acaba de pasar por aquí y se ha largado corriendo con un tubo interno.\aTemo que lo usen para sus malvados propósitos.\aPor favor, busca al bot y recupera el tubo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mi tubo interno?", + COMPLETE : "¡Has encontrado mi tubo interno! Eres SENSACIONAL. Aquí tienes tu recompensa...", + }, + 2203 : { QUEST : TheCogs+" están sembrando el caos en el banco.\aVe a ver al capitán Doblón, a ver qué puedes hacer._where_" }, + 2204 : { QUEST : "Bienvenido a bordo, grumete.\a¡Arg! Esa escoria de bots han aplastado mi monóculo y no me puedo apañar sin él.\aSé un buen marinero y lleva esta prescripción al doctor Rompecubiertas para que me haga uno nuevo._where_.", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "¿Qué es esto?\aMe encantaría hacer este monóculo, pero los bots han estado saqueando mis existencias.\aSi consigues arrebatarle las monturas de monóculo a un secuaz, me serás de gran ayuda.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Lo siento. Si no tengo las monturas del secuaz, no hay monóculo.", + }, + 2206: { QUEST : "¡Excelente!\aUn momento...\aAquí tienes el monóculo de la prescripción. Llévaselo al capitán Doblón._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "¡Avante a toda!\aNo, si al final te vas a ganar los galones y todo.\aAquí tienes.", + }, + 2207 : { QUEST : "¡Perci Percebe tiene un bot en la tienda!\aMás vale que vayas para allá enseguida._where_" }, + 2208 : { QUEST : "¡Vaya! Se te acaba de escapar, cariño.\aAquí había un apuñalaespaldas. Se ha llevado mi peluca blanca grande.\aHa dicho que era para su jefe, y no sé qué sobre un \"precedente legal\".\aSi pudieses recuperarla, te estaría eternamente agradecida.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Todavía no lo has encontrado?\aEs alto y tiene la cabeza puntiaguda.", + COMPLETE : "¡La has encontrado!\a¡Eres todo un encanto!\aTe has ganado esto, sin duda...", + }, + 2209 : { QUEST : "Isidoro se está preparando para un viaje importante.\aAcércate a ver en qué puedes ayudarle._where_"}, + 2210 : { QUEST : "Puedo utilizar tu ayuda.\aEn el cuartel general dibu me han pedido que haga un viaje para averiguar de dónde proceden los bots.\aNecesito unas cuantas cosas para mi barco, pero ando escaso de gominolas.\aPásate a ver a Pepa Sastre para que te dé algo de lastre. Para conseguirlo, tendrás que hacerle un favor._where_", + GREETING : "¿Qué hay, _avName_?", + LEAVING : "", + }, + 2211 : { QUEST : "Así que Isidoro quiere lastre, ¿eh?\aTodavía me debe la última fanega.\aTe la daré si consigues limpiar mi calle de cinco microgerentes.", + INCOMPLETE_PROGRESS : "¡No, tonto! ¡He dicho CINCO microgerentes!...", + GREETING : "¿Qué puedo hacer por ti?", + LEAVING : "", + }, + 2212 : { QUEST : "Un trato es un trato.\aAquí tienes el lastre para ese tacaño de Isidoro._where_", + GREETING : "Vaya, mira lo que aparece por aquí...", + LEAVING : "", + }, + 2213 : { QUEST : "Gran trabajo. Sabía que se atendría a razones. \aAhora necesito una carta náutica de Pasma Rote.\aNo creo que me fíen allí tampoco, así que tendrás que llegar a un acuerdo con él._where_.", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Sí, tengo la carta de navegación que necesita Isidoro.\aY te la daré si estás dispuesto a trabajar para conseguirla.\aEstoy intentando construir un astrolabio para orientarme con las estrellas.\aPara hacerlo necesito tres engranajes de bot.\aVuelve cuando los hayas conseguido.", + INCOMPLETE_PROGRESS: "¿Qué tal te va con los engranajes de bot?", + GREETING : "¡Bienvenido!", + LEAVING : "¡Buena suerte!", + }, + 2215 : { QUEST : "¡Oooh! Estos engranajes me vendrán muy bien.\aAquí tienes la carta. Dásela a Isidoro, y dale recuerdos._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bueno, esto es todo. ¡Estoy listo para zarpar!\aTe dejaría acompañarme si no estuvieses tan verde. A cambio, toma esto.", + }, + 901 : { QUEST : "Si estás disponible, a Ajab le vendría bien una ayuda._where_", + }, + 2902 : { QUEST : "¿Tú eres el nuevo recluta?\aBien, bien. Tal vez puedas ayudarme.\aEstoy construyendo un gigantesco cangrejo prefabricado para confundir a los bots.\aAunque también me serviría una coraza. Ve a ver a Clodovico Cromañón y pídele una, por favor._where_", + }, + 2903 : { QUEST : "¡Muy buenas!\aSí, he oído hablar del cangrejo gigante en el que trabaja Ajab.\aSin embargo, la mejor coraza que tengo está un poco sucia.\aPórtate bien y llévala a la tintorería antes de entregarla._where_", + LEAVING : "¡Gracias!" + }, + 2904 : { QUEST : "Debes de ser el que viene de parte de Clodovico.\aCreo que puedo limpiar eso en un santiamén.\aDame un minuto...\aAquí tienes. ¡Como nuevo!\aSaluda a Ajab de mi parte._where_", + }, + 2905 : { QUEST : "Vaya, esto es exactamente lo que andaba buscando.\aYa que estás aquí, también voy a necesitar un resorte de reloj muy grande.\aPásate por la tienda de Garfio para ver si tiene uno._where_", + }, + 2906 : { QUEST : "Un resorte grande, ¿eh?\aLo siento, pero el más grande que tengo es bastante pequeño, en realidad.\aQuizás pueda hacer uno con los resortes de los gatillos de pistola de agua.\aTráeme tres de esos chismes y veré que puedo hacer.", + }, + 2907 : { QUEST : "Veamos...\aImpresionante. Simplemente impresionante.\aA veces me sorprendo a mí mismo.\aAquí tienes: un resorte grande para Ajab._where_", + LEAVING : "¡Buen viaje!", + }, + 2911 : { QUEST : "Me encantaría ayudar a la causa, _avName_.\aPero me temo que las calles ya no son seguras.\a¿Por qué no acabas con unos cuantos chequebots? Después hablaremos.", + INCOMPLETE_PROGRESS : "Creo que las calles no son todavía muy seguras que digamos.", + }, + 2916 : { QUEST : "Sí, tengo un contrapeso que le vendría bien a Ajab.\aSin embargo, creo que sería mejor que antes derrotases a un par de vendebots.", + INCOMPLETE_PROGRESS : "Todavía no. Derrota a unos cuantos vendebots más.", + }, + 2921 : { QUEST : "Mmm, se supone que tengo que darte un contrapeso.\aPero me sentiría mucho más seguro si no hubiese tantos jefebots rondando por aquí.\aDerrota a seis y ven a verme.", + INCOMPLETE_PROGRESS : "Creo que esta zona todavía no es segura...", + }, + 2925 : { QUEST : "¿Has acabado?\aBien, supongo que la zona ya es bastante segura.\aAquí tienes el contrapeso para Ajab._where_" + }, + 2926 : {QUEST : "Bueno, eso es todo.\aVeamos si funciona.\aMmm, hay un pequeño problema.\aNo puedo encenderlo porque ese edificio bot está tapando mi panel solar.\a¿Puedes hacerme el favor de reconquistarlo?", + INCOMPLETE_PROGRESS : "Sigo sin tener electricidad. ¿Qué hay de ese edificio?", + COMPLETE : "¡Estupendo! ¡Se te da de miedo zurrar a los bots! Toma, aquí tienes tu recompensa...", + }, + 3200 : { QUEST : "Acabo de recibir una llamada de _toNpcName_.\aEstá teniendo un día de perros. Tal vez puedas ayudarle.\aPásate por allí para ver qué necesita._where_" }, + 3201 : { QUEST : "¡Vaya, gracias por venir!\aNecesito que alguien lleve esta corbata de seda nueva a _toNpcName_.\a¿Me harías tú ese favor?_where_" }, + 3203 : { QUEST : "¡Ah, ésta debe de ser la corbata que he encargado! ¡Gracias!\aVa a juego con el traje mil rayas que acabo de terminar, justo ahí.\aEh, ¿qué ha pasado con el traje?\a¡Oh, no! ¡"+TheCogs+" deben de haberme robado el traje nuevo!\aLucha con los bots hasta que encuentres el traje y tráemelo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya mi traje? ¡Seguro que se lo han llevado los bots!", + COMPLETE : "¡Hurra! ¡Has encontrado mi traje nuevo!\a¿Ves? Te dije que los bots lo tenían. Toma tu recompensa...", + }, + + 3204 : { QUEST : "_toNpcName_ acaba de llamar para informar de un robo.\a¿Por qué no te pasas por allí para ver si puedes arreglar la situación?_where_" }, + 3205 : { QUEST : "¡Hola, _avName_! ¿Has venido a ayudarme?\aAcabo de ahuyentar a un chupasangres de mi tienda. ¡Guau! Daba mucho miedo.\a¡Pero ahora no puedo encontrar las tijeras! Seguro que se las ha llevado el chupasangres.\aPor favor, busca al chupasangres y recupera las tijeras.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Sigues buscando las tijeras?", + COMPLETE : "¡Mis tijeras! ¡Muchas gracias! Toma tu recompensa...", + }, + + 3206 : { QUEST : "Parece ser que _toNpcName_ está teniendo algún que otro problema con los bots.\aVe a ver si puedes ayudarle._where_" }, + 3207 : { QUEST : "¡Buenas, _avName_! ¡Gracias por venir!\aUnos cuantos embaucadores han entrado y se han llevado un taco de postales del mostrador.\a¡Por favor, derrota a esos embaucadores y recupera mis postales!", + INCOMPLETE_PROGRESS : "¡No hay postales suficientes! ¡Sigue buscando!", + COMPLETE : "¡Oh, gracias! ¡Ahora puedo entregar el correo a tiempo! Toma tu recompensa...", + }, + + 3208 : { QUEST : "Hemos estado recibiendo quejas de los vecinos sobre todos esos gorrones.\aPor favor, intenta derrotar a diez gorrones para ayudar a tus amigos, los dibus de los Jardines de Daisy. " }, + 3209 : { QUEST : "¡Gracias por ocuparte de los gorrones!\a¡Pero ahora los televendedores se han desmadrado!\aDerrota a diez televendedores en los Jardines de Daisy y vuelve para llevarte una recompensa." }, + + 3247 : { QUEST : "Hemos estado recibiendo quejas de los vecinos sobre todos esos chupasangres.\aPor favor, intenta derrotar a veinte chupasangres para ayudar a tus amigos, los dibus de los Jardines de Daisy. " }, + + + 3210 : { QUEST : "¡Oh, no, La flor chorreante, de la calle Arce, se ha quedado sin flores!\aLlévales diez flores chorreantes de las tuyas para ayudarles. \aPrimero comprueba que tienes diez flores chorreantes en el inventario.", + LEAVING: "", + INCOMPLETE_PROGRESS : "Necesito diez flores chorreantes. ¡No tienes suficientes!" }, + 3211 : { QUEST : "¡Oh, muchas gracias! Esas flores arreglarán la situación.\aPero los bots que hay fuera me dan miedo.\a¿Puedes ayudarme derrotando a unos cuantos?\aVuelve cuando hayas derrotado a veinte bots en esta calle.", + INCOMPLETE_PROGRESS : "¡Todavía quedan bots ahí fuera! ¡Sigue luchando!", + COMPLETE : "¡Oh, gracias! Me has ayudado un montón. Tu recompensa es...", + }, + + 3212 : { QUEST : "_toNpcName_ necesita ayuda para encontrar una cosa que ha perdido.\aVe a verla, tal vez puedas ayudarla._where_" }, + 3213 : { QUEST : "Hola, _avName_. ¿Puedes ayudarme?\aParece que he perdido mi pluma estilográfica. Creo que se la han llevado unos bots.\aDerrótalos y recupera la pluma, por favor.", + INCOMPLETE_PROGRESS : "¿Has encontrado ya mi pluma?" }, + 3214 : { QUEST : "¡Sí, ésa es! ¡Muchas gracias!\aPero en cuanto te has marchado me he dado cuenta de que también me faltaba el tintero.\aVence a los bots y recupera el tintero, por favor.", + INCOMPLETE_PROGRESS : "¡Sigo buscando el tintero!" }, + 3215 : { QUEST : "¡Fantástico! Ahora he recuperado la pluma y el tintero.\aPero ¿a que no te lo imaginas?\a¡No encuentro la libreta! ¡La deben haber robado también!\aDerrota a los bots hasta que encuentres la libreta y tráemela para que te dé una recompensa.", + INCOMPLETE_PROGRESS : "¿Alguna novedad sobre la libreta?" }, + 3216 : { QUEST : "¡Ésa es mi libreta! ¡Hurra! Tu recompensa es...\a¡Eh! ¿Dónde está?\aTenía tu recompensa justo aquí, en la caja de seguridad. ¡Pero se la han llevado!\a¿Puedes creerlo? ¡"+TheCogs+" han robado tu recompensa!\aVence a los bots para recuperar la caja de seguridad.\aCuando me la traigas, te daré tu recompensa.", + INCOMPLETE_PROGRESS : "¡Sigue buscando la caja de seguridad! ¡Tu recompensa está dentro!", + COMPLETE : "¡Por fin! Tenía tu nueva bolsa de bromas en la caja. Aquí está...", + }, + + 3217 : { QUEST : "Hemos estado estudiando la mecánica de los vendebots.\aQueremos estudiar más de cerca algunas piezas.\aTráenos la rueda dentada de un fardoncete.\aPuedes atraparlas cuando los bots estallan." }, + 3218 : { QUEST : "¡Buen trabajo! Ahora tenemos que compararla con la rueda dentada de un efusivo.\aEsas ruedas dentadas son más difíciles de atrapar, así que sigue intentándolo." }, + 3219 : { QUEST : "¡Fantástico! Ahora sólo necesitamos una rueda dentada más.\aEsta vez necesitamos la rueda de un mandamás.\aPara encontrar a estos bots, tal vez tengas que buscar en el interior de algunos edificios de vendebots.\aCuando la tengas, tráela, y a cambio obtendrás tu recompensa." }, + + 3244 : { QUEST : "Hemos estado estudiando la mecánica de los abogabots.\aQueremos estudiar más de cerca algunas piezas.\aTráenos la rueda dentada de un persigueambulancias.\aPuedes atraparlas cuando los bots estallan." }, + 3245 : { QUEST : "¡Buen trabajo! Ahora tenemos que compararla con la rueda dentada de un apuñalaespaldas.\aEsas ruedas dentadas son más difíciles de atrapar, así que sigue intentándolo." }, + 3246 : { QUEST : "¡Fantástico! Ahora sólo necesitamos una rueda dentada más.\aEsta vez necesitamos la rueda de un portavoz.\aCuando la tengas, tráela para obtener a cambio tu recompensa." }, + + 3220 : { QUEST : "Acabo de oír que _toNpcName_ estaba preguntando por ti.\a¿Por qué no pasas a verla para ver qué quiere?_where_" }, + 3221 : { QUEST : "¡Buenas, _avName_! ¡Aquí estás!\aHe oído que eres todo un experto en ataques chorreantes.\aNecesito a alguien que dé un buen ejemplo a todos los dibus de Jardines de Daisy.\aUsa tus ataques chorreantes para derrotar a un montón de bots.\aAnima a tus amigos a usar este tipo de ataques.\aCuando hayas derrotado a veinte bots, vuelve para llevarte una recompensa." }, + + 3222 : { QUEST : "Ha llegado el momento de demostrar tu dibupuntería.\aSi consigues recuperar cierto número de edificios bot, tendrás el privilegio de asumir tres tareas a la vez.\aPrimero reconquista dos edificios bot cualesquiera.\aLlama a los amigos que quieras para que te ayuden."}, + 3223 : { QUEST : "¡Un gran trabajo con los edificios! \aAhora reconquista dos edificios más.\aLos edificios deben de tener al menos dos pisos de altura." }, + 3224 : { QUEST : "¡Fantástico!\aAhora basta con que recuperes dos edificios más.\aLos edificios deben tener al menos tres pisos de altura.\aCuando termines, vuelve para obtener tu recompensa.", + COMPLETE : "¡Lo has conseguido, _avName_!\a¡Has demostrado una dibupuntería increíble!", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ dice que necesita ayuda.\a¿Por qué no vas a verla para ver en qué la puedes ayudar?_where_" }, + 3235 : { QUEST : "¡Ah, ésta debe de ser la ensalada que encargué!\aGracias por traérmela.\aTodos esos bots deben de haber asustado al repartidor habitual de _toNpcName_.\a¿Por qué no nos haces un favor y derrotas a unos cuantos bots de ahí fuera?\aDerrota a diez bots en los Jardines de Daisy y vuelve con _toNpcName_.", + INCOMPLETE_PROGRESS : "¿No estabas venciendo a los bots por mí?\a¡Maravilloso! ¡Sigue así!", + COMPLETE : "¡Oh, muchas gracias por derrotar a esos bots!\aAhora quizás pueda seguir con mis repartos normales.\aTu recompensa es...", + INCOMPLETE_WRONG_NPC : "Informa a _toNpcName_ sobre los bots a los que has derrotado._where_" }, + + 3236 : { QUEST : "Hay demasiados abogabots allí.\a¡Ayuda en lo que puedas!\aRecupera tres edificios de abogabots." }, + 3237 : { QUEST : "¡Un gran trabajo con los edificios de abogabots! \a¡Pero ahora hay demasiados vendebots!\aRecupera tres edificios de vendebots y vuelve a por tu recompensa." }, + + 3238 : { QUEST : "¡Oh, no! ¡Un bot \"confraternizador\" ha robado la llave de los Jardines de Daisy!\aIntenta recuperarla.\aRecuerda, sólo encontrarás al confraternizador en el interior de edificios de vendebots. " }, + 3239 : { QUEST : "Sí, has encontrado una llave, pero no es la correcta.\aNecesitamos la llave de los Jardines de Daisy.\a¡Sigue buscando! ¡La tiene un bot \"confraternizador\"!" }, + + 3242 : { QUEST : "¡Oh, no! ¡Un bot picapleitos ha robado la llave de Jardines de Daisy!\aIntenta recuperarla.\aRecuerda, sólo encontrarás al picapleitos en el interior de edificios de abogabots. " }, + 3243 : { QUEST : "Sí, has encontrado una llave, pero no es la correcta.\aNecesitamos la llave de los Jardines de Daisy.\a¡Sigue buscando! ¡La tiene un bot picapleitos!" }, + + 3240 : { QUEST : "_toNpcName_ me acaba de decir que un picapleitos le ha robado una bolsa de alpiste.\aDerrota a los picapleitos hasta que encuentres el alpiste de Federico Tilla y llévaselo.\aSólo encontrarás a los picapleitos en el interior de los edificios de abogabots._where_", + COMPLETE : "¡Oh, muchas gracias por encontrar el alpiste!\aTu recompensa es...", + INCOMPLETE_WRONG_NPC : "¡Muy bien, has conseguido recuperar el alpiste!\aAhora llévaselo a _toNpcName_._where_", + }, + + 3241 : { QUEST : "Algunos edificios bot están creciendo demasiado para nuestro gusto.\aIntenta recuperar algunos de los edificios más altos.\aReconquista cinco edificios de tres plantas o más y vuelve para llevarte una recompensa.", + }, + + 3250 : { QUEST : "Doña Citronia ha recibido información sobre la existencia de un cuartel general vendebot en la calle Arce.\aReúnete con ella para investigarlo.", + }, + 3251 : { QUEST : "Aquí se cuece algo.\aHay una barbaridad de vendebots.\aDicen que han construido su propio cuartel general al final de la calle.\aRecórrela a ver si averiguas lo que está pasando.\aBusca vendebots por su cuartel general, derrota a cinco de ellos y regresa.", + }, + 3252 : { QUEST : "Venga, suelta prenda.\a¿Qué dices?\a¿Un cuartel general vendebot? ¡Dios mío! Hay que actuar.\aDebemos poner al corriente a Doña Zanahoria, que seguro que sabe lo que hay que hacer.\aVete corriendo y cuéntale lo que sabes. Está ahí mismo, bajando la calle.", + }, + 3253 : { QUEST : "¿Qué quieres? Estoy muy ocupada.\a¿Cómo? ¿Un cuartel general bot?\aMenuda bobada. Es imposible.\aTe debes haber equivocado. Es inconcebible.\a¿Cómo? No me lo discutas.\aBueno, mira, si quieres, tráeme pruebas.\aSi los vendebots están construyendo su cuartel general, todos los bots que anden por allí llevarán planos.\aA los bots les encanta la burocracia, ¿no sabías? \aVe derrotando vendebots en el lugar que me dices hasta que encuentres los planos.\aLuego me los traes y ya veremos si te creo o no.", + }, + 3254 : { QUEST : "¿Otra vez tú por aquí? ¿Cómo? ¿Planos? ¿Los has conseguido?\aDéjame que los vea. Vaya, ¿una fábrica?\aDebe ser la planta donde construyen los vendebots... ¿Y esto qué es?\aJusto lo que pensaba.\aComo sospechaba, están construyendo un cuartel general vendebot.\aMenudo problema. Voy a tener que hacer unas cuantas llamadas. Mira, estoy muy ocupada. Adiós.\a¿Cómo? Sí, sí. Llévale los planos a Doña Citronia.\aSeguro que ella los entenderá mejor que yo.", + COMPLETE : "¿Qué te ha dicho Doña Zanahoria?\aEntonces, teníamos razón. Esto es muy peligroso. ¿A ver los planos?\aVaya, parece que los vendebots han construido una planta con maquinaria para la fabricación de bots.\aEsto me huele fatal. Mantente al margen hasta que consigas más puntos de risa.\aCuando reúnas más podremos seguir con la investigación.\aPor ahora, aquí tienes tu recompensa. ¡Muy bien!", + }, + + + 3255 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3256 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3257 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3258 : { QUEST : "No sabemos realmente qué es lo que están tramando los bots en su nuevo cuartel general.\aQuiero que me traigas información, pero obtenida directamente de los propios bots.\aSi conseguimos cuatro notas de oficina de los vendebots sacadas del mismo cuartel, podremos hacernos una idea más clara.\aTráeme la primera nota en cuanto la consigas y así voy estudiando la información a medida que aparezca.", + }, + 3259 : { QUEST : "¡Estupendo! A ver qué dice la nota\a\"Estimados vendebots:\"\a\"Estaré en mi oficina del ático de las torres vendebots ascendiendo bots a niveles superiores.\"\a\"Cuando reúnan los méritos necesarios, suban a mi oficina en el ascensor del vestíbulo de entrada.\"\a\"Se acabó el descanso. ¡A trabajar!\"\a\"Firmado, vendebot VIP\"\aVaya, vaya. A "+Flippy+" le va a interesar esto. Se lo voy a mandar ahora mismo.\aMárchate ya en busca de la segunda nota y tráemela en cuanto la tengas.", + }, + 3260 : { QUEST : "Ya has vuelto, qué bien. A ver qué has encontrado....\a\"Estimados vendebots:\"\a\"Se ha instalado un sistema de seguridad nuevo en las torres vendebots para impedir el acceso a los dibus.\"\a\"Los dibus que se encuentren en las torres serán detenidos para su interrogatorio.\"\a\"Nos reuniremos en el vestíbulo para discutir el asunto durante el aperitivo.\"\a\"Firmado, Confraternizadora\"\aMuy, muy interesante. Tengo que pasar esta información inmediatamente.\aTráeme una tercera nota, por favor.", + }, + 3261 : { QUEST : "¡Muy bien _avName_! ¿Qué dice la nota?\a\"Estimados vendebots:\"\a\"Los dibus se las han ingeniado para infiltrarse en las torres vendebot.\"\a\"Esta noche les llamaré a la hora de la cena para darles más datos.\"\a\"Firmado, Televendedor\"\a¡Vaya! Me pregunto cómo estarán colándose los dibus....\aTráeme una última nota. Creo que con eso bastará para hacerme una idea.", + COMPLETE : "¡Sabía que lo lograrías! A ver, la nota dice....\a\"Estimados vendebots:\"\a\"Ayer comí con el Sr. Hollywood y me dijo que VIP lleva unos días muy ocupado.\"\a\"Sólo recibirá bots que merezcan un ascenso.\"\a\"Se me olvidaba, he quedado con Efusivo para jugar al golf el domingo.\"\a\"Firmado, Fardoncete\"\aBueno, _avName_, muchas gracias por tu valiosa ayuda.\aToma, aquí tienes tu recompensa.", + }, + + 3262 : { QUEST : "_toNpcName_ posee información reciente sobre la fábrica-cuartel general vendebot.\aVe a ver de qué se trata._where_" }, + 3263 : { GREETING : "¡Qué pasa, colega!", + QUEST : "Me llamo Don Silvestre, Silvestre a secas para los amigos.\aVoy al grano, que no me gusta andarme por las ramas.\aMira, los vendebots acaban de construir una gran fábrica para reproducirse como hormigas.\aAgénciate a unos cuantos colegas dibus y destruye la dichosa fábrica.\aDentro del cuartel general vendebot deberás localizar el túnel que lleva a la fábrica y luego subir en el ascensor.\aAntes que nada, comprueba que vas bien cargado de bromas y puntos de risa y que te acompañan unos dibus bien fornidos.\aDerrota al capataz dentro de la fábrica y así detendrás el avance vendebot.\a¡Hala! Ahí tienes tu tablita de ejercicios. Un, dos, un, dos.", + LEAVING : "¡Nos vemos!", + COMPLETE : "¿Qué pasa, colega? ¡Qué bien te lo has montado en la fábrica!\aAsí que has pillado una pieza de traje bot.\aDebe ser un excedente de la cadena de montaje bot.\aNos podría venir de perlas. Recoge todas las que puedas siempre que tengas un huequecillo.\aA lo mejor, si consigues un traje completo reuniendo todas las piezas, podemos sacarle algún provecho...." + }, + + 4001 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsatelo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsatelo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Seguro que a Ropo Pompón le viene bien un poco de ayuda en su investigación._where_", + }, + 4201 : { GREETING: "¡Hola!", + QUEST : "Estoy muy preocupado con una racha de robos de instrumentos musicales.\aEstoy llevando a cabo un estudio entre mis compañeros comerciantes.\aTal vez pueda encontrar una pauta que me ayude a resolver el caso.\aPásate por Sonatas y sonatinas para que Tina te dé su inventario de concertinas. _where_", + }, + 4202 : { QUEST : "Sí, he hablado con Ropo esta mañana.\aTengo el inventario aquí mismo.\aLlévaselo, ¿vale?_where_" + }, + 4203 : { QUEST : "¡Fantástico! Uno menos...\aAhora ve a por el de Uki._where_", + }, + 4204 : { QUEST : "¡Oh! ¡El inventario!\aMe había olvidado de él.\aSeguro que lo tengo terminado para cuando hayas derrotado a diez bots.\aPásate por aquí después y te prometo que estará listo.", + INCOMPLETE_PROGRESS : "31, 32... ¡Vaya!\a¡Me has hecho perder la cuenta!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, ahí estás.\aGracias por darme algo de tiempo.\aLlévale esto a Ropo y salúdale de mi parte._where_", + }, + 4206 : { QUEST : "Mmm, muy interesante.\aAhora sí que estoy dando con ello.\aMuy bien, el último inventario es el de Bibi._where_", + }, + 4207 : { QUEST : "¿Inventario?\a¿Cómo voy a hacerlo si no tengo el formulario?\aVe a ver si Sordino Quena puede darme uno._where_", + INCOMPLETE_PROGRESS : "¿Alguna novedad sobre el formulario?", + }, + 4208 : { QUEST : "¡Pues claro que tengo un formulario de inventario!\aPero no es gratis, ¿sabes?\a¿Sabes qué? Te lo daré a cambio de una tarta de nata entera.", + GREETING : "¡Muy buenas!", + LEAVING : "Hasta luego...", + INCOMPLETE_PROGRESS : "No me vale con un trozo.\aMe quedaré con hambre. Quiero TODA la tarta.", + }, + 4209 : { GREETING : "", + QUEST : "Mmm...\a¡Qué rica!\aAquí tienes el formulario para Bibi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Gracias. Has sido de gran ayuda.\aVeamos... Violines Bibi: 2\a¡Ya está! ¡Aquí tienes!", + COMPLETE : "Buen trabajo, _avName_.\aSeguro que ahora llego al fondo de la cuestión de los robos.\a¿Por qué no llegas tú al fondo de esto?", + }, + + 4211 : { QUEST : "Mira, el doctor Pavo Rotti está llamando cada cinco minutos. ¿Puedes ir a ver qué problema tiene?_where_", + }, + 4212 : { QUEST : "¡Guau! Me alegro de que el cuartel general haya mandado por fin a alguien.\aNo he tenido un solo cliente durante días.\aSon estos malditos contables que hay por todas partes.\aCreo que están propagando una mala higiene bucal entre los vecinos.\aDerrota a diez de ellos y veamos si el negocio mejora.", + INCOMPLETE_PROGRESS : "Sigo sin tener clientes. ¡Pero sigue luchando!", + }, + 4213 : { QUEST : "¿Sabes? A lo mejor resulta que los culpables no eran los contables.\aIgual son los chequebots en general.\aAcaba con veinte de ellos y tal vez venga alguien por fin a mi clínica.", + INCOMPLETE_PROGRESS : "Sé que veinte son muchos. Pero seguro que merece la pena.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "¡No lo entiendo!\aSigo sin tener ni un solo cliente.\aA lo mejor hay que atacar al origen del problema.\aIntenta reconquistar un edificio de chequebots.\aEso debería bastar.", + INCOMPLETE_PROGRESS : "¡Por favor! ¡Sólo un pequeño edificio de nada!...", + COMPLETE : "Sigue sin venir un alma por aquí.\aPero la verdad es que, pensándolo bien...\a¡Tampoco venían clientes antes de que los bots nos invadiesen!\aSin embargo, aprecio de veras tu ayuda.\aSeguro que esto te viene bien." + }, + + 4215 : { QUEST : "Olga necesita desesperadamente a alguien que la ayude.\a¿Por qué no te pasas a verla para ver qué puedes hacer?_where_", + }, + 4216 : { QUEST : "¡Gracias por venir tan pronto!\aParece que los bots se han hecho con muchos de los billetes de crucero de mis clientes.\aUki dice que ha visto a un efusivo irse de aquí con un montón.\aMira a ver si puedes recuperar el billete a Alaska de Chopo Chopín.", + INCOMPLETE_PROGRESS : "Los efusivos pueden estar ya en cualquier sitio...", + }, + 4217 : { QUEST : "¡Estupendo! ¡Lo has encontrado!\a¿Me harías ahora el favor de llevárselo a Chopo Chopín?_where_", + }, + 4218 : { QUEST : "¡Estupendo, estupendísimo!\a¡Alaska, voy para allá!\aYa no soporto a estos malditos bots.\aOye, creo que Olga te vuelve a necesitar._where_", + }, + 4219 : { QUEST : "Sí, lo has adivinado.\aNecesito que sacudas a los pesados de los efusivos para recuperar el billete de Felisa Felina al festival de Jazz. \aYa sabes cómo funciona...", + INCOMPLETE_PROGRESS : "Hay más en alguna parte...", + }, + 4220 : { QUEST : "¡Estupendo!\a¿Podrías llevarle también el billete?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Hasta luego...", + QUEST : "¡Hola!\aMe voy de viaje, _avName_.\aAntes de irte, más vale que te pases de nuevo a ver a Olga..._where_", + }, + 4222 : { QUEST : "¡Éste es el último, lo prometo!\aAhora hay que buscar el billete de Barbo para el gran concurso de canto.", + INCOMPLETE_PROGRESS : "Vamos, _avName_.\aBarbo cuenta contigo.", + }, + 4223 : { QUEST : "Esto hará que Barbo se alegre mucho._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "¡Vaya, vaya, VAYA!\a¡Sensacional!\a¿Sabes? Este año, los chicos y yo vamos a barrer en el concurso.\aOlga dice que vuelvas por allí para recoger tu recompensa._where_\a¡Adiós, adiós, ADIÓS!", + COMPLETE : "Gracias por todo, _avName_.\aEres muy valioso aquí en Toontown.\aHablando de cosas valiosas...", + }, + + 902 : { QUEST : "Ve a ver a Leo.\aNecesita que alguien entregue un mensaje._where_", + }, + 4903 : { QUEST : "¡Colega!\aMis castañuelas están empañadas y tengo una gran actuación esta noche.\aLlévaselas a Carlos para ver si las puede limpiar._where_", + }, + 4904 : { QUEST : "Sí, creo que puedo limpiarlas.\aPero necesito un poco de tinta azul de un pulpo.", + GREETING : "¡Hola!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Los pulpos están cerca de los amarraderos.", + }, + 4905 : { QUEST : "¡Sí! ¡Eso es!\aAhora necesito un poco de tiempo para limpiarlas.\a¿Por qué no vas a recuperar un edificio de un piso mientras trabajo?", + GREETING : "¡Hola!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Sólo un poco más...", + }, + 4906 : { QUEST : "¡Muy bien!\aAquí tienes las castañuelas para Leo._where_", + }, + 4907 : { GREETING : "", + QUEST : "¡Muy bien, colega!\aTienen una pinta estupenda.\aAhora necesito que consigas una copia de la letra de \"Navidades felices\" de Lírica Tástrofe._where_", + }, + 4908 : { QUEST: "¡Muy buenas!\aMmm, no tengo un ejemplar de esa letra a mano.\aSi me dieses algo de tiempo, la podría escribir de memoria.\a¿Por qué no te vas a recuperar un edificio de dos plantas mientras la escribo?", + }, + 4909 : { QUEST : "Lo siento.\aMi memoria ya no es lo que era.\aSi vas a recuperar un edificio de tres plantas, seguro que tendré la letra lista para cuando vuelvas.", + }, + 4910 : { QUEST : "¡Ya está!\aSiento haber tardado tanto.\aLlévale esto a Leo._where_", + GREETING : "", + COMPLETE : "¡Genial, colega!\a¡Mi concierto va a ser la bomba!\aAh, que no se me olvide. Toma, esto te servirá para los bots." + }, + 5247 : { QUEST : "Este barrio es bastante duro...\aTe vendría bien aprender unos cuantos trucos nuevos.\a_toNpcName_ me enseñó todo lo que sé, así que a lo mejor te puede enseñar a ti también._where_" }, + 5248 : { GREETING : "Aah, sí.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que mi tarea te está causando problemas.", + QUEST : "Aaah, un nuevo aprendiz, bienvenido.\aYo sé todo lo que hay que saber sobre las tartas.\aPero antes de empezar con tu entrenamiento, tienes que hacerme una pequeña demostración.\aSal fuera y derrota a diez de los bots más grandes." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "¡Excelente!\aAhora, demuéstrame tu habilidad como pescador.\aAyer tiré tres dados de goma al estanque.\aPéscalos y tráemelos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que la caña y el sedal no se te dan tan bien." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán de miedo colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de abogabots más grandes.", + INCOMPLETE_PROGRESS : "¿Estás teniendo problemas con los edificios?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán de miedo colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de jefebots más grandes.", + INCOMPLETE_PROGRESS : "¿Estás teniendo problemas con los edificios?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán de miedo colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de chequebots más grandes.", + INCOMPLETE_PROGRESS : "¿Estás teniendo problemas con los edificios?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán de miedo colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de vendebots más grandes.", + INCOMPLETE_PROGRESS : "¿Estás teniendo problemas con los edificios?", }, + 5200 : { QUEST : "Esos bots tan pesados están otra vez dando problemas.\a_toNpcName_ ha informado de que falta otro objeto. Pásate por allí, a ver si puedes arreglar la situación._where__where_" }, + 5201 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de cazacabezas ha estado aquí y se ha llevado mi balón de fútbol.\a¡Su jefe me ha dicho que tenía que hacer un recorte de plantilla y me lo ha quitado!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo has encontrado! Aquí tienes tu recompensa...", + }, + 5261 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de doscaras ha estado aquí y se ha llevado mi balón de fútbol.\a¡Su jefe me ha dicho que tenía que hacer un recorte de plantilla y me lo ha quitado!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo has encontrado! Aquí tienes tu recompensa...", + }, + 5262 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de monederos ha estado aquí y se ha llevado mi balón de fútbol.\a¡Su jefe me ha dicho que tenía que hacer un recorte de plantilla y me lo ha quitado!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo has encontrado! Aquí tienes tu recompensa...", + }, + 5263 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de portavoces ha estado aquí y se ha llevado mi balón de fútbol.\a¡Su jefe me ha dicho que tenía que hacer un recorte de plantilla y me lo ha quitado!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo has encontrado! Aquí tienes tu recompensa...", + }, + 5202 : { QUEST : "Frescolandia ha sido invadida por los bots más duros de pelar que he visto en mi vida.\aMás vale que cargues más bromas.\aMe han dicho que es posible que _toNpcName_ tenga una bolsa más grande en la que te cabrán más bromas._where_" }, + 5203 : { GREETING: "¿Eh? ¿Estás en mi equipo de trineo?", + QUEST : "¿Qué? ¿Quieres una bolsa?\aEl caso es que tenía una por aquí... ¿Estará en mi trineo?\aVaya... ¡No he visto mi trineo desde la gran carrera!\a¿Se lo habrá llevado uno de esos bots?", + LEAVING : "¿Has visto mi trineo?", + INCOMPLETE_PROGRESS : "¿Quién decías que eras? Lo siento, estoy un poco mareado por el choque." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "¿Es ése mi trineo? No veo ninguna bolsa por aquí.\aCreo que Perico Arenque estaba en el equipo... ¿La tendrá él?_where_" }, + 5205 : { GREETING : "¡Oooh, la cabeza!", + LEAVING : "", + QUEST : "¿Eh? ¿Doroteo qué? ¿Una bolsa?\aAh, ¿no era miembro de nuestro equipo de trineo?\aMe duele tanto la cabeza que no puedo pensar bien.\a¿Puedes pescar unos cuantos cubitos de hielo en el estanque para que me los ponga en la cabeza?", + INCOMPLETE_PROGRESS : "¡Ayyy, la cabeza me va a estallar! ¿Tienes un poco de hielo?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "¡Aaah, así está mucho mejor!\aAsí que buscas la bolsa de Doroteo, ¿eh?\aCreo que acabó en la cabeza de Nutrio Cenutrio después del choque._where_" }, + 5207 : { GREETING : "¡Eeeh!", + LEAVING : "", + QUEST : "¿Bolsa? ¿Quién ser Perico?\a¡Yo tener miedo de edificios! ¡Tú derrotar edificios, yo darte bolsa!", + INCOMPLETE_PROGRESS : "¡Más edificios! ¡Yo todavía tener miedo!", + COMPLETE : "¡Oooh! ¡Tú gustarme!" }, + 5208 : { GREETING : "", + LEAVING : "¡Eeeh!", + QUEST : "¡Oooh! ¡Tú gustarme!\aTú ir a clínica de esquí. Bolsa allí." }, + 5209 : { GREETING : "¡Colega!", + LEAVING : "¡Nos vemos!", + QUEST : "¡Tío, ese tal Nutrio Cenutrio está loco!\aColega, si estás loco como Nutrio, la bolsa será tuya.\a¡Embólsate a unos cuantos bots y la bolsa será tuya, colega! ¡Vamos!", + INCOMPLETE_PROGRESS : "¿Estás seguro de que eres bastante bestiajo? Anda, vete a zurrar a los bots.", + COMPLETE : "¡Eh, eres todo un campeón! ¡Has zurrado de lo lindo a un montón de bots!\a¡Aquí tienes la bolsa!" }, + + 5210 : { QUEST : "_toNpcName_ está enamorada en secreto de alguien del barrio.\aSi la ayudas, te recompensará de lo lindo._where_" }, + 5211 : { GREETING: "¡Buaaaa!", + QUEST : "Me he pasado toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots con pico ha entrado y se la ha llevado.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5264 : { GREETING: "¡Buaaaa!", + QUEST : "Me he pasado toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots con aleta ha entrado y se la ha llevado.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5265 : { GREETING: "¡Buaaaa!", + QUEST : "Me he pasado toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots confraternizadores ha entrado y se la ha llevado.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5266 : { GREETING: "¡Buaaaa!", + QUEST : "Me he pasado toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots corporativistas ha entrado y se la ha llevado.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5212 : { QUEST : "¡Oh, gracias por encontrar la carta!\aPor favor, ¿podrías entregársela al perro más guapo de todo el barrio?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "No has entregado la carta, ¿verdad?", + }, + 5213 : { GREETING : "Encantado de verte.", + QUEST : "Lo siento, pero no puedo molestarme con tu carta.\a¡Me han quitado a todos mis perritos!\aTráemelos y hablaremos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡Mis pobres perritos!" }, + 5214 : { GREETING : "", + LEAVING : "¡Hasta luego!", + QUEST : "Gracias por devolverme a mis preciosidades.\aEchemos un vistazo a tu carta...\nMmmm, parece que tengo otra admiradora secreta.\aEsto pide a gritos una visita a mi querido amigo Carlos Congelado.\a¡Seguro que te cae muy bien!_where_" }, + 5215 : { GREETING : "Je, je...", + LEAVING : "Vuelve, sí, sí.", + INCOMPLETE_PROGRESS : "Todavía quedan unos cuantos grandullones. Vuelve cuando ya no estén.", + QUEST : "¿Quién te ha enviado? No me gustan los forasteros, no...\aPero todavía me gustan menos los bots...\aAcaba con los grandotes y ya veremos si te ayudo." }, + 5216 : { QUEST : "Te he dicho que te vamos a ayudar.\aAsí que llévale este anillo a la chica.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡¿Sigues teniendo el anillo?!", + COMPLETE : "¡Oh, queriiiido! ¡¡¡Gracias!!!\aAh, también tengo algo especial para ti.", + }, + 5217 : { QUEST : "Parece que a _toNpcName_ le vendría bien algo de ayuda._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguro que hay confraternizadores por algún sitio.", + QUEST : "¡¡¡Socorro!!! ¡¡¡Socorro!!! ¡Ya no lo aguanto más!\a¡Esos confraternizadores me están volviendo tarumba!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "No pueden ser todos. ¡Sólo he visto a uno!", + QUEST : "¡Vaya, gracias, pero ahora se trata de los corporativistas!\a¡Tienes que ayudarme!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡No, no, no, había uno justo aquí!", + QUEST : "¡Ahora me doy cuenta de que son esos prestamistas despiadados!\a¡Creía que ibas a salvarme!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "¿Sabes qué? ¡A lo mejor la culpa no es de los bots!\a¿Puedes pedirle a Pega Moide que me prepare una poción calmante? A lo mejor eso ayuda..._where_" }, + 5222 : { LEAVING : "", + QUEST : "¡El tal Cris Térico es todo un personaje!\a¡Voy a prepararle algo que le pondrá a tono!\aVaya, parece que me he quedado sin aletas de sardina...\aPórtate bien y ve al estanque a pescarme unas cuantas.", + INCOMPLETE_PROGRESS : "¿Ya has conseguido las aletas?", }, + 5223 : { QUEST : "Vale. Gracias, cariño.\aToma, ahora llévale esto a Cris. Seguro que se calma enseguida.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vamos, llévale la poción a Cris.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hazme el favor de acabar con los picapleitos ¿vale?", + QUEST : "¡Oh, gracias a Dios que has vuelto!\a¡¡¡Dame la poción, rápido!!!\aGlu, glu, glu...\a¡Puaj, qué mal sabía!\aPero ¿sabes qué? Me siento mucho más tranquilo. Ahora que puedo pensar con claridad, me doy cuenta de que...\a¡¡¡Eran los picapleitos los que me volvían loco todo el rato!!!", + COMPLETE : "¡Es estupendo! ¡Ahora puedo relajarme!\aSeguro que por aquí hay algo que pueda darte. ¡Toma!" }, + 5225 : { QUEST : "Desde el incidente del bocadillo de nabos, Felipe el gruñón ha estado enfadado con _toNpcName_. \aA lo mejor puedes ayudar a Frigo a arreglar las cosas entre ellos._where_" }, + 5226 : { QUEST : "Sí, seguro que te han contado que Felipe el gruñón está enfadado conmigo...\aSólo intentaba ser amable regalándole un bocadillo de nabos.\aA lo mejor puedes animarle.\aFelipe odia a los chequebots, sobre todo sus edificios. \aSi reconquistas unos cuantos edificios de chequebots, tal vez sirva de algo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Y si lo intentas con unos cuantos edificios más?", }, + 5227 : { QUEST : "¡Es fantástico! Ve a contar a Felipe lo que has hecho._where_" }, + 5228 : { QUEST : "Vaya, eso ha hecho, ¿eh?\aEse Frigo cree que puede arreglarlo todo así de fácil, ¿eh?\a¡Me rompí una muela con ese bocadillo de nabos que me dio!\aQuizá si le llevas la muela al doctor Bocamaraca, él pueda arreglarla.", + GREETING : "Brrrrr.", + LEAVING : "Grrñññ, grññññ.", + INCOMPLETE_PROGRESS : "¿Tú otra vez? ¡Creía que ibas a arreglarme la muela!", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela tiene bastante mala pinta, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos chequebots?\aEstán asustando a mis pacientes." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela tiene bastante mala pinta, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos vendebots?\aEstán asustando a mis pacientes." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela tiene bastante mala pinta, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos abogabots?\aEstán asustando a mis pacientes." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela tiene bastante mala pinta, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos jefebots?\aEstán asustando a mis pacientes." }, + 5230 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aHe desistido de intentar arreglar la muela, y en su lugar, le he fabricado a Felipe una nueva, de oro.\aPor desgracia, un barón ladrón me la ha birlado.\aSi te das prisa, a lo mejor consigues atraparle.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya la muela?" }, + 5270 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aHe desistido de intentar arreglar la muela, y en su lugar, le he fabricado a Felipe una nueva, de oro.\aPor desgracia, un pez gordo me la ha birlado.\aSi te das prisa, a lo mejor consigues atraparle.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya la muela?" }, + 5271 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aHe desistido de intentar arreglar la muela, y en su lugar, le he fabricado a Felipe una nueva, de oro.\aPor desgracia, un Sr. Hollywood me la ha birlado.\aSi te das prisa, a lo mejor consigues atraparle.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya la muela?" }, + 5272 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aHe desistido de intentar arreglar la muela, y en su lugar, le he fabricado a Felipe una nueva, de oro.\aPor desgracia, un pelucón me la ha birlado.\aSi te das prisa, a lo mejor consigues atraparle.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya la muela?" }, + 5231 : { QUEST : "¡Estupendo, ésa es la muela!\a¿Por qué no me haces el favor de llevársela a Felipe?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguro que Felipe está impaciente por ver su nueva muela.", + }, + 5232 : { QUEST : "Oh, gracias.\aUmmmmf\a¿Qué tal estoy, eh?\aBueno, puedes decirle a Frigo que le perdono.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, me alegro muchísimo de oír eso.\aMe imaginaba que el cascarrabias de Felipe no podría seguir enfadado conmigo.\aComo gesto de amistad, le he preparado este bocadillo de piñones.\a¿Me haces el favor de llevárselo? ", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Date prisa, por favor. El bocadillo de piñones está más rico cuando está calentito.", + COMPLETE : "Vaya, ¿qué es esto? ¿Es para mí?\aÑam, ñam....\a¡Aaay! ¡Mi muela! ¡Ese Frigo Sabañón!\aBueno, vale, no ha sido culpa tuya. Toma, llévate esto como recompensa por tu esfuerzo.", + }, + 903 : { QUEST : "Creo que estás listo para ir a ver a _toNpcName_, en Ventisca a la vista, para tu prueba final._where_", }, + 5234 : { GREETING: "", + QUEST : "Ah, has vuelto.\aAntes de empezar, tenemos que comer algo.\aTráenos un poco de queso abultado para el caldo.\aEl queso abultado sólo se puede conseguir de los bots peces gordos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguimos necesitando queso abultado." }, + 5278 : { GREETING: "", + QUEST : "Ah, has vuelto.\aAntes de empezar, tenemos que comer algo.\aTráenos un poco de caviar para el caldo.\aEl caviar sólo se puede conseguir de los bots Sr. Hollywood.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguimos necesitando caviar." }, + 5235 : { GREETING: "", + QUEST : "Los hombres sencillos comen con cucharas sencillas.\aUn bot se ha llevado mi cuchara sencilla, así que sencillamente, no puedo comer.\aTráeme mi cuchara. Creo que un barón ladrón se la ha llevado.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Es sencillo: debo recuperar la cuchara." }, + 5279 : { GREETING: "", + QUEST : "Los hombres sencillos comen con cucharas sencillas.\aUn bot se ha llevado mi cuchara sencilla, así que no puedo comer.\aTráeme mi cuchara. Creo que un pelucón se la ha llevado.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Es sencillo: debo recuperar la cuchara." }, + 5236 : { GREETING: "", + QUEST : "Oh, gracias.\aSlurp, slurp...\aAaah, ahora tienes que atrapar a un sapo parlanchín. Ponte a pescar en el estanque.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el sapo parlanchín?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Todavía no has conseguido el postre.", + QUEST : "Vaya, no cabe duda de que es un sapo parlanchín. Dámelo.\a¿Qué dices, sapo?\aVaya vaya.\aVaya vaya...\aEl sapo ha hablado. Necesitamos un postre.\aTráenos unos cuantos cucuruchos de helado de _toNpcName_.\aPor algún motivo, al sapo le gusta el helado de judías pintas._where_", }, + 5238 : { GREETING: "", + QUEST : "Así que te ha enviado Fredo Dedo. Siento decirte que nos hemos quedado sin cucuruchos de helado de judías pintas.\aVerás, un montón de bots ha entrado y se los ha llevado.\aHan dicho que eran para un señor de Hollywood o algo así.\aTe agradecería mucho que los recuperases.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya los cucuruchos de helado?" }, + 5280 : { GREETING: "", + QUEST : "Así que te ha enviado Fredo Dedo. Siento decirte que nos hemos quedado sin cucuruchos de helado de judías pintas.\aVerás, un montón de bots ha entrado y se los ha llevado.\aHan dicho que eran para el pez gordo o algo así.\aTe agradecería mucho que los recuperases.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado ya los cucuruchos de helado?" }, + 5239 : { QUEST : "¡Gracias por recuperar los cucuruchos de helado!\aAquí tienes uno para Fredo Dedo.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Más vale que le lleves el helado a Fredo Dedo antes de que se derrita.", }, + 5240 : { GREETING: "", + QUEST : "Muy bien. Aquí tienes, sapo...\aSlurp, slurp...\aMuy bien, estamos casi listos.\aSi pudieses traerme un poco de talco para secarme las manos...\aCreo que los bots pelucones llevan a veces polvos de talco en la peluca.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado talco?" }, + 5281 : { GREETING: "", + QUEST : "Muy bien. Aquí tienes, sapo...\aSlurp, slurp...\aMuy bien, estamos casi listos.\aSi pudieses traerme un poco de talco para secarme las manos...\aCreo que los bots Sr. Hollywood llevan a veces polvos de talco para empolvarse la nariz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has encontrado talco?" }, + 5241 : { QUEST : "Vale.\aComo dije en su día, para lanzar bien una tarta, no se debe usar la mano...\a... sino con el alma.\aNo sé qué significa eso, así que me sentaré a contemplar cómo reconquistas edificios.\aVuelve cuando hayas terminado tu tarea.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu tarea todavía no ha terminado.", }, + 5242 : { GREETING: "", + QUEST : "Aunque sigo sin saber de qué hablo, no cabe duda de que eres de gran valor.\aTe asigno una tarea final...\aAl sapo parlanchín le gustaría tener una novia.\aBusca otro sapo parlanchín. El sapo ha hablado.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el otro sapo parlanchín?", + COMPLETE : "¡Guau! Estoy cansado de todo este esfuerzo. Voy a descansar.\aToma, ten tu recompensa y márchate." }, + + 5243 : { QUEST : "Pedro Glaciares está empezando a apestar la calle.\a¿Puedes convencerle de que se dé una ducha?_where_" }, + 5244 : { GREETING: "", + QUEST : "Sí, supongo que suelo sudar bastante aquí.\aMmm, a lo mejor si pudiese arreglar la tubería que gotea en mi ducha...\aSupongo que un engranaje de esos bots diminutos me servirá.\aVe a por un engranaje de un microgerente y lo intentaremos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el engranaje ese que me ibas a traer?" }, + 5245 : { GREETING: "", + QUEST : "Sí, parece que eso ha funcionado.\aPero cuando me ducho me siento solo...\a¿Podrías ir a pescarme un patito de goma para que me haga compañía?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar el patito?" }, + 5246 : { QUEST : "El patito es estupendo, pero...\aTodos esos edificios alrededor me ponen nervioso.\aMe sentiría mucho más relajado si hubiese menos edificios cerca.", + LEAVING : "", + COMPLETE : "Vale, me voy a dar una ducha. Toma, esto es para ti.", + INCOMPLETE_PROGRESS : "Siguen preocupándome los edificios.", }, + 5251 : { QUEST : "Creo que Pago Gelado va a dar un recital esta noche.\aMe han dicho que el material del concierto le está dando problemas._where_" }, + 5252 : { GREETING: "", + QUEST : "¡Ah, sí! Pues claro que me viene bien algo de ayuda.\a"+TheCogs+" han venido y me han robado todo el equipo mientras descargaba la furgoneta.\a¿Puedes echarme una mano recuperando el micrófono?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Oye, tío, no puedo cantar sin un micrófono." }, + 5253 : { GREETING: "", + QUEST : "¡Sí, ése es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\aLo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo ha llevado uno de esos corporativistas.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar el teclado?" }, + 5273 : { GREETING: "", + QUEST : "¡Sí, ése es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\aLo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo ha llevado uno de esos confraternizadores.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar el teclado?" }, + 5274 : { GREETING: "", + QUEST : "¡Sí, ése es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\aLo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo ha llevado uno de esos prestamistas despiadados.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar el teclado?" }, + 5275 : { GREETING: "", + QUEST : "¡Sí, ése es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\aLo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo ha llevado uno de esos picapleitos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Has conseguido encontrar el teclado?" }, + 5254 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que han terminado en manos de un Sr. Hollywood.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, Frescolandia!\a¿Eh? ¿Dónde está la gente?\aVale, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5282 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que han terminado en manos de un pez gordo.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, Frescolandia!\a¿Eh? ¿Dónde está la gente?\aVale, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5283 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que han terminado en manos de un barón ladrón.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, Frescolandia!\a¿Eh? ¿Dónde está la gente?\aVale, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5284 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que han terminado en manos de un pelucón.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, Frescolandia!\a¿Eh? ¿Dónde está la gente?\aVale, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + + 5255 : { QUEST : "Creo que te vendrían bien más puntos de risa.\aQuizá puedas hacer un trato con _toNpcName_.\aNo te olvides de ponerlo por escrito..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un trato es un trato.", + QUEST : "Así que quieres puntos de risa, ¿eh?\a¡Te propongo un trato!\aSi te ocupas de unos cuantos jefebots...\aYo te recompensaré por ello." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un trato es un trato.", + QUEST : "Así que quieres puntos de risa, ¿eh?\a¡Te propongo un trato!\aSi te ocupas de unos cuantos abogabots...\aYo te recompensaré por ello." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Vale, pero estoy seguro de que te dije que te ocupases de unos cuantos abogabots.\aBueno, si tú lo dices... Pero me debes una.", + INCOMPLETE_PROGRESS : "Creo que todavía no has terminado.", + QUEST : "¿Dices que has terminado? ¿Has derrotado a todos los bots?\aMe debes de haber entendido mal; nuestro trato se refería a los vendebots.\aEstoy segurísimo de que te dije que te ocupases de unos cuantos vendebots." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Vale, pero estoy seguro de que te dije que te ocupases de unos cuantos abogabots.\aBueno, si tú lo dices... Pero me debes una.", + INCOMPLETE_PROGRESS : "Creo que todavía no has terminado.", + QUEST : "¿Dices que has terminado? ¿Has derrotado a todos los bots?\aMe debes de haber entendido mal, nuestro trato se refería a los chequebots.\aEstoy segurísimo de que te dije que te ocupases de unos cuantos chequebots." }, + } + +# ChatGarbler.py +ChatGarblerDog = ["guau", "arf", "grrrr"] +ChatGarblerCat = ["miau", "miao"] +ChatGarblerMouse = ["ñic", "ñiiiic", "ñic ñic"] +ChatGarblerHorse = ["relincho", "brrr"] +ChatGarblerRabbit = ["iiik", "ipr", "iiipi", "iiiki"] +ChatGarblerDuck = ["cuac", "cuaaac ", "cuac cuac"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerDefault = ["bla"] + +# AvatarDNA.py +Bossbot = "Jefebot" +Lawbot = "Abogabot" +Cashbot = "Chequebot" +Sellbot = "Vendebot" +BossbotS = "un Jefebot" +LawbotS = "un Abogabot" +CashbotS = "un Chequebot" +SellbotS = "un Vendebot" +BossbotP = "Jefebots" +LawbotP = "Abogabots" +CashbotP = "Chequebots" +SellbotP = "Vendebots" +BossbotSkelS = "un esquelebot jefebot" +LawbotSkelS = "un esquelebot abogabot" +CashbotSkelS = "un esquelebot chequebot" +SellbotSkelS = "un esquelebot vendebot" +BossbotSkelP = "esquelebots jefebots" +LawbotSkelP = "esquelebots abogabots" +CashbotSkelP = "esquelebots chequebots" +SellbotSkelP = "esquelebots vendebots" + +# AvatarDetailPanel.py +AvatarDetailPanelOK = "Aceptar" +AvatarDetailPanelCancel = "Cancelar" +AvatarDetailPanelClose = "Cerrar" +AvatarDetailPanelLookup = "Buscando detalles de %s." +AvatarDetailPanelFailedLookup = "Imposible obtener detalles de %s." +AvatarDetailPanelOnline = "Distrito: %(district)s\nLocalidad: %(location)s" +AvatarDetailPanelOffline = "Distrito: sin conexión\nLocalidad: sin conexión" + +# AvatarPanel.py +AvatarPanelFriends = "Amigos" +AvatarPanelWhisper = "Susurrar" +AvatarPanelSecrets = "Secretos" +AvatarPanelGoTo = "Ir a" +AvatarPanelPet = "Show Doodle" +AvatarPanelIgnore = "No hacer caso" +#AvatarPanelCogDetail = "Depart.: %s\nNivel: %s\n" +AvatarPanelCogLevel = "Nivel: %s" +AvatarPanelCogDetailClose = "Cerrar" + +# DistributedAvatar.py +WhisperNoLongerFriend = "%s ha abandonado tu lista de amigos." +WhisperNowSpecialFriend = "¡%s es ahora tu amigo secreto!" +WhisperComingToVisit = "%s viene a verte." +WhisperFailedVisit = "%s ha intentado venir a verte." +WhisperTargetLeftVisit = "%s se ha ido a otro sitio. ¡Prueba de nuevo!" +WhisperGiveupVisit = "%s no ha podido encontrarte porque te estás moviendo." +WhisperIgnored = "¡%s no te está haciendo caso!" +TeleportGreeting = "Hola, %s." +DialogSpecial = "ooo" +DialogExclamation = "!" +DialogQuestion = "?" +# Cutoff string lengths to determine how much barking to play +DialogLength1 = 6 +DialogLength2 = 12 +DialogLength3 = 20 + +# LocalAvatar.py +FriendsListLabel = "Amigos" +#WhisperFriendComingOnline = "¡%s se está conectando!" +#WhisperFriendLoggedOut = "%s ha cerrado la sesión." + +# TeleportPanel.py +TeleportPanelOK = "Aceptar" +TeleportPanelCancel = "Cancelar" +TeleportPanelYes = "Sí" +TeleportPanelNo = "No" +TeleportPanelCheckAvailability = "Intentando ir a %s." +TeleportPanelNotAvailable = "%s está ocupado ahora mismo. Inténtalo más tarde." +TeleportPanelIgnored = "%s no te está haciendo caso." +TeleportPanelNotOnline = "%s no está conectado ahora mismo." +TeleportPanelWentAway = "%s se ha marchado." +TeleportPanelUnknownHood = "¡No sabes cómo llegar a %s!" +TeleportPanelUnavailableHood = "%s no está disponible ahora mismo. Inténtalo más tarde." +TeleportPanelDenySelf = "¡No puedes ir tú solo!" +TeleportPanelOtherShard = "%(avName)s está en el distrito %(shardName)s, y tú estás en el distrito %(myShardName)s. ¿Quieres cambiarte a %(shardName)s?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Soy el jefe." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Soy el capataz." +FactoryBossBattleTaunt = "Te presento al capataz." + +# HealJokes.py +ToonHealJokes = [ + ["¿Qué le dice un dos a un cero?", + "¡Vente conmigo! "], + ["¿Qué es una brújula?", + "Una viéjula montada en una escóbula."], + ["Qué es un lóbulo?", + "Un perro grándulo que se come a las ovéjulas."], + ["¿Qué es una orilla?", + "Sesenta minutillos."], + ["¿Qué es una oreja?", + "Sesenta minutejos."], + ["¿Qué es un código?", + "Por donde se dobla el brácigo."], + ["¿Qué le dice un cero a otro cero?", + "No valemos nada."], + ["¿Qué le dice un jaguar a otro?", + "¿Jaguar you?"], + ["¿Cuántos astrónomos hacen falta para cambiar una bombilla?", + "Ninguno, los astrónomos prefieren la oscuridad."], + ["¿Cuál es el hombre que piensa más profundo?", + "El minero."], + ["¿Cómo se dice suspense en chino?", + "¡Cha cha cha chaaaaan!"], + ["¿Qué le dice el pingüino a la pingüina?", + "Te quiero como a ningüina."], + ["¿Qué le dice la pelota a la raqueta? ", + "Lo nuestro es imposible, siempre me estás pegando..."], + ["¿Qué le dice un poste a otro poste?", + "Póstate bien."], + ["¿Qué le dice un caimán mexicano a otro?", + "¡Cai-manito!"], + ["¿Cuál es la sal que peor huele?", + "La sal-pargatas."], + ["¿Qué es un punto verde en una esquina?", + "Un guisante castigado."], + ["¿Cómo se dice 99 en chino?", + "Cachi-chien."], + ["¿Por qué en Lepe ponen ajos en la carretera nacional?", + "Porque son buenos para la circulación."], + ["¿Qué es más asqueroso que encontrarse un gusano en una manzana?", + "Encontrar sólo medio gusano."], + ["¿Quién invento las fracciones?", + "Enrique Octavo."], + ["¿Quién mató al libro de lengua?", + "El sujeto."], + ["¿Qué hora es cuando un elefante se sienta en una valla?", + "La hora de ponerla nueva."], + ["¿Por qué los elefantes no pueden montar en bicicleta?", + "Porque no tienen dedo gordo para tocar el timbre."], + ["¿Cómo se meten cuatro elefantes en un Mini?", + "Pues dos delante y dos detrás."], + ["¿Cuántos psicoanalistas hacen falta para cambiar una bombilla?", + "Uno, pero la bombilla tiene que querer ser cambiada."], + ["¿Por qué los flamencos se sostienen sobre una sola pata?", + "Porque si no se caen."], + ["¿Cuál es la patrona de los informáticos? ", + "Santa Tecla."], + ["¿Cuál es el patrón de los náufragos?", + "San Están Aislados de la Costa."], + ["¿Quién es el patrón de los profesores de educación física?", + "San Gimnasio de Loyola."], + ["¿Cuál es el grupo musical más oscuro? ", + "Estatos Qúo."], + ["¿Cuál es el vaquero más sucio del Oeste?", + "Johny Melabo."], + ["¿Cuál es el queso favorito de Sherlock Holmes?", + "El emmental, querido Watson."], + ["¿En qué se parece un elefante a una cama?", + "En que el elefante es paquidermo y la cama paquiduermas."], + ["¿En qué se parece una camisa vieja a un hotel barato? ", + "En que ninguno tiene botones."], + ["¿En qué se parece un boxeador a un telescopio?", + "En que los dos hacen ver las estrellas."], + ["¿En que se parece una diligencia a una silla?", + "La diligencia va para Kansas City y la silla es para \"siti\" cansas."], + ["¿En qué se parecen una escopeta y una gata?", + "En que las dos tienen gatillos."], + ["¿En qué se parecen un panadero y un político?", + "En que los dos mueven masas."], + ["¿En qué se parece una bruja a un fin de semana?", + "En que los dos pasan volando."], + ["¿En que se parecen los limones a los terratenientes?", + "En que los dos tienen muchas propiedades."], + ["¿En qué se parece una casa incendiándose a una casa vacía?", + "En que de la casa incendiándose \"salen llamas\" y en la casa vacía \"llamas y nadie sale\"."], + ["¿En qué se parece una pistola a un panadero?", + "En que los dos hacen pan."], + ["¿En qué se parece un ladrón a una pulga?", + "En que la pulga salta y el ladrón asalta."], + ["¿En qué se parece una estufa a un avión?", + "En que los dos tienen piloto."], + ["¿Qué le dice un chinche a una chinche?", + "Te amo chincheramente."], + ["¿Qué le dice un semáforo a otro?", + "No me mires que me estoy cambiando."], + ["¿Qué le dice el número 3 al número 30?", + "Para ser como yo, tienes que ser sincero."], + ["¿Qué le dice un semáforo a otro?", + "No me mires que me pongo rojo."], + ["¿Qué le dice el timbre al dedo?", + "Si me tocas, grito."], + ["¿Qué le dice un zapato al otro?", + "¡Qué vida más arrastrada llevamos!"], + ["¿Qué le dice un diente a otro?", + "Si te picas, pierdes."], + ["¿Qué le dice un ojo a otro?", + "Estamos separados por narices."], + ["¿Cuál es el último animal acuático?", + "El delfín."], + ["¿Por qué es la cebra el animal más antiguo de la selva?", + "Porque está en blanco y negro."], + ["¿Por qué las películas de Chaplin eran mudas?", + "Porque el director siempre decía: ¡No charles, Chaplin! "], + ["¿Por qué los perros persiguen a los coches?", + "Porque llevan un gato en el maletero."], + ["¿Por qué los franceses comen caracoles? ", + "Porque no les gusta la comida rápida."], + ["¿Por qué se esconden los animales de la selva? ", + "Porque los elefantes están haciendo paracaidismo."], + ["Ring, ring - Muy buenas, ¿es el uno-uno-uno-uno-uno-uno? ", + "- No, éste es el once-once-once."], + ["Ring, ring - Oiga, ¿es la Real Academia de la Lengua? ", + "- No, pero como si lo seriese."], + ["Ring, ring - ¿Es Otto? ", + "- No, soy el de siempre."], + ["Ring, ring - Hola, ¿está Agustín?", + "- Pues sí, estoy aquí calentito."], + ["Ring, ring - Hola, ¿está Cholo?", + "No, estoy acompañado."], + ["Ring, ring - Hola, ¿está Alberto?", + "- ¡No, está celado!"], + ["Ring, ring - Hola, ¿está Conchita?", + "- No, estoy con Tarzán."], + ["¿Cuál es el colmo de la lírica?", + "Haber tenido un plácido domingo."], + ["¿Cuál es el colmo de un carnicero?", + "Tener un hijo chuleta."], + ["¿Cuál es el colmo de un forzudo?", + "Doblar una esquina."], + ["¿Cuál es el colmo de un sastre?", + "Tener un hijo botones que se case con una americana."], + ["¿Cuál es el colmo de un carpintero?", + "Tener una hija cómoda y otra coqueta."], + ["¿Cuál es el colmo de un arquitecto?", + "Construir castillos en el aire."], + ["¿Cuál es el colmo de un peluquero?", + "Perder el tren por los pelos."], + ["¿Cuál es el colmo de un caballo?", + "Tener silla y no poder sentarse."], + ["¿Cuál es el colmo de un albañil?", + "Tener una hija paleta."], + ["¿Cuál es el colmo de un fotógrafo?", + "Que se le rebelen los hijos."], + ["¿Cuál es el colmo de la pereza?", + "Levantarse dos horas antes para estar más tiempo sin hacer nada."], + ["¿Cuál es el colmo de un jardinero?", + "Que su novia se llame Rosa y lo deje plantado."], + ["¿Cuál es el colmo de los colmos?", + "Sentarse en un pajar y pincharse con la aguja."], + ["¿Cuál es el colmo de un electricista?", + "Que su mujer se llame Luz y sus hijos le sigan la corriente."], + ["¿Cuál es el colmo de un libro?", + "Que en otoño se le caigan las hojas."], + ["¿Cuál es el colmo de una ballena?", + "Ir vacía."], + ["¿Cuál es el colmo de un policía?", + "Denunciar a un huracán por exceso de velocidad. "], + ["¿Cuál es el colmo de un robot?", + "Tener nervios de acero."], + ["¿Cuál es el colmo de una jirafa?", + "Tener dolor de garganta."], + ["¿Cuál es el colmo de un pez?", + "Quejarse porque no llueve."], + ["¿Cuál es el colmo de Aladino?", + "Tener mal genio."], + ["¿Cuál es el colmo de un astronauta?", + "Quejarse de no tener espacio."], + ["¿Cuál es el colmo de un dinamitero?", + "Que lo exploten en su trabajo."], + ["¿Cuál es el colmo de los colmos?", + "Que dos palomas de la paz se peleen por la ramita de olivo."], + ["¿Cuál es el colmo de Santa Claus?", + "No poder bajar por la chimenea debido a la claustrofobia."], + ["¿Por qué echa Donald azúcar en la almohada?", + "Porque quiere tener dulces sueños."], + ["¿Por qué ha llevado Goofy el peine al dentista?", + "Porque ha perdido todos los dientes."], + ["¿Por qué se sienta Goofy en la última fila de los cines?", + "Porque el que ríe el último, ríe mejor."], + ["¿Cuál es el colmo de un escritor?", + "Que su esposa le dé sopa de letras."], + ["¿Cuál es el colmo de un calvo?", + "Tener ideas descabelladas."], + ["¿Cuál es el colmo de una maleta?", + "Pedir vacaciones porque está cansada de tanto viajar."], + ["¿Cuál es el colmo de un gallo?", + "Que se le ponga la piel de gallina."], + ["¿Cuál es el colmo de un charcutero?", + "Tener un perro salchicha."], + ["¿Cuál es el colmo de una silla? ", + "Tener patas y no poder caminar."], + ["¿Cuál es el colmo de un gato?", + "Vivir una vida de perros."], + ["¿Cuál es el colmo más pequeño?", + "El colmillo."], + ["¿Cuál es el colmo de un paracaidista?", + "Tener la moral por los suelos."], + ["¿Cuál es el colmo de la tacañería?", + "Contarse los dedos cada vez que se le da la mano a alguien."], + ["¿Cuál es el colmo de un dálmata?", + "Que le saquen una foto y salga en blanco y negro."], + ["¿Cuál es el colmo de un pastor?", + "Quedarse dormido contando ovejas."], + ["¿Cuál es el colmo de un vidriero?", + "Que quiebre su negocio."], + ["¿Cuál es el colmo de un cerrajero? ", + "Dejarse las llaves dentro de casa."], + ["¿Cuál es el colmo de la paciencia?", + "Tirar una moneda al agua y esperar a que la cara pida socorro."], + ["¿Cuál es colmo de una costurera?", + "Perder el hilo de la conversación."], + ["¿Cuál es el colmo de un escritor?", + "Querer poner siempre punto final a todas las reuniones."], + ["¿Cuál es el colmo de un leñador?", + "Dormir como un tronco."], + ["¿Cuál es el colmo de un músico?", + "Que su hijo dé la nota."], + ["¿Cuál es el colmo de Saturno?", + "Tener anillos y no tener dedos."], + ["¿Cuál es el colmo de un comediante?", + "Que le digan que es un artista serio."], + ["¿Cuál es el colmo de un vanidoso?", + "Que su juego favorito sea el yo-yo."], + ["¿Cuál es el colmo de un plumero?", + "Ser alérgico al polvo."], + ["¿Cuál es el colmo de una cocinera?", + "Llamar a la policía porque los fideos se están pegando."], + ["¿Cuál es el colmo de un dinamitero?", + "Que lo exploten en su trabajo."], + ["¿Cuál es el colmo de un dentista?", + "Quitarle los dientes a un serrucho."], + ["¿Por qué era el matemático un infeliz?", + "Porque tenía muchos problemas."], + ["¿Cuál es el colmo de un pez?", + "Morir ahogado."], + ["¿Cuál es el colmo de un cazador?", + "Querer cazar la Osa Mayor."], + ["¿Cuál es el colmo de un bombero?", + "Llevarse trabajo a casa."], + ["¿Cual es el colmo de la paciencia?", + "Meter una zapatilla en una jaula y esperar a que cante."], + ["¿Cuál es el colmo de un desdentado? ", + "Estar armado hasta los dientes."], + ["¿Cuál es el colmo de una enfermera?", + "Llamarse Dolores de Cabeza."], + ["¿Cuál es el colmo de un cobarde? ", + "Salirse de la cocina cuando se pegan los fideos."], + ["¿Cuál es el colmo de Pinocho?", + "No tener madera de estudiante."], + ["¿Cuál es el colmo de una enfermera?", + "Ponerle una tirita a la leche cortada."], + ["¿Cuál es el colmo de un camello?", + "Vivir toda la vida jorobado."], + ["¿Cuál es colmo de un cementerio?", + "Estar cerrado por defunción."], + ["¿Cuál es el colmo de una sardina?", + "Que le dé lata."], + ["¿Cuál es el colmo de un cóctel?", + "Sentirse agitado."], + ["¿Cuál es el colmo de un granjero?", + "Dejar abierta la puerta del corral para que se ventile."], + ["¿Cuál es la palabra más larga del mundo?", + "Arroz, porque empieza con A y termina con Z."], + ["¿Cuál es el animal que ve menos?", + "La venada."], + ["¿Cuál es el animal más fiero? ", + "El lopintan, porque no es tan fiero el león como lopintan."], + ["¿Cuál es el perro más explosivo?", + "El vol-can."], + ["¿Cuál es el animal que juega al ajedrez?", + "El caballo."], + ["¿Qué es un codo? ", + "Un gdupo de niñods cantodes."], + ["¿En que se parece una cueva a un frigorífico? ", + "La cueva tiene estalactitas y estalagmitas y el frigorífico esta latita de atún, esta latita de anchoas..."], + ["- Doctor, me siento mal.", + "- Pues siéntese bien."], + ["- Hoy tose usted mejor que ayer.", + "- Sí, doctor... He estado ensayando toda la noche."], + ["- Doctor, doctor... ¿cómo sé si estoy perdiendo la memoria? ", + "- Eso ya se lo dije ayer."], + ["- Doctor, ¿qué me aconseja para evitar resfriarme de nuevo? ", + "Conservar el resfriado que tiene ahora."], + ["- Doctor, doctor, sigo pensando que soy invisible.", + "- ¿Quién ha dicho eso?"], + ["- Doctor, doctor, el pelo se me está cayendo. ¿Cómo puedo conservarlo?", + "- Tome una caja de zapatos."], + ["- Doctor, doctor, hace dos semanas que no como ni duermo. ¿Qué tengo? ", + "- Seguramente sueño y hambre."], + ["- Doctor, doctor, vengo a que me reconozca.", + "- Pues ahora mismo no caigo."], + ["- Doctor, doctor, es que tengo un hueso fuera...", + "- Pues dígale que pase."], + ["- Doctor, doctor que se me juntan las letras.", + "- Pues páguelas, páguelas..."], + ["- Doctor, doctor, cuando tomo café se me ponen los ojos morados.", + "- ¿Ha probado a apartar la cucharilla?"], + ["- ¡¡Doctor, doctor, vengo a que me osculte!!", + "- ¡Rápido, detrás del sillón!"], + ["- Doctor, tengo los dientes muy amarillos, ¿qué me recomienda?", + "- Una corbata marrón."], + ["- Mamá, mamá, en el cole me llaman despistado.", + "- Anda, niño, vete a tu casa."], + ["- Mamá, mamá, en el colegio me llaman despistado.", + "- SU TABACO, GRACIAS."], + ["- Mamá, mamá, ¿cuándo vamos a comer pan de hoy?", + "- Mañana, hijo, mañana."], + ["- ¡Mamá, mamá, están golpeando la puerta!", + "- Déjala que se defienda sola."], + ["- Mamá, mamá... ¿por qué me llaman pies grandes en el cole?", + "No lo sé, pero ¿has guardado los zapatos en el garaje?"], + ["- Mamá, ¿qué es la amnesia?", + "- ¿Qué? ¿Quién eres?"], + ["- Camarero, camarero, ¿qué hace esta mosca en mi sopa? ", + "- Yo diría que braza australiana, señor."], + ["- Camarero, camarero, ¿me aliña la ensalada? ", + "- Con el uno pepino, con el dos tomate, con el tres cebolla..."], + ["- ¿Cómo ha encontrado el señor el solomillo?", + "- De milagro, oiga, de milagro."], + ["- Camarero, camarero, está usted metiendo la corbata en mi sopa.", + "- No se preocupe, señor, no encoge."], + ["- Camarero, ¡ya le he pedido cien veces un vaso de agua! ", + "- ¡Cien vasos de agua para el señor!"], + ["- Camarero, camarero, hay una mosca muerta en mi sopa.", + "- ¿Y qué esperaba por este precio? ¿Una viva?"], + ["- Camarero, ¿el pescado viene solo?", + "- No, se lo traigo yo."], + ["- Camarero, un café solo, por favor.", + "- ¡Todo el mundo fuera!"], + ["- ¡Capitán, capitán, que vamos a pique! ", + "-¡He dicho yo que vamos a Cádiz y vamos a Cádiz!"], + ["- Capitán, capitán, ¡nos hundimos!", + "- ¡Pero bobo, si estamos en un submarino!"], + ["- ¡Capitán, capitán, hemos perdido la guerra! ", + "- Pues buscadla enseguida."], + ["- ¡Soldado, ice la bandera!", + "- Le felicito mi general, le quedó muy bonita."], + ["- ¡Soldados, presenten armas!", + "- Aquí mi general, aquí mi fusil."], + ["- Mi capitán, los soldados no aguantan más, estamos a 42º a la sombra.", + "- Está bien sargento, pueden descansar diez minutos al sol."], + ["- Por favor, ¿la calle Sagasta?", + "- Hombre, si pisa fuerte..."], + ["- Oiga, que este reloj no anda.", + "- Claro, todavía no tiene un año."], + ["- Camarero, póngame un café corto.", + "- Se me ha roto la máquina, cambio."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("ji","je","ja","jua, jua") +MovieHealLaughterHits1= ("Ja, ja, ja","ji, ji","Je, je, je","Ja, ja") +MovieHealLaughterHits2= ("¡JUA, JUA, JUA!","¡JUO, JUO, JUO!","¡JA, JA, JA!") + +# MovieSOS.py +MovieSOSCallHelp = "¡SOCORRO %s!" +MovieSOSWhisperHelp = "¡%s necesita que le ayuden en el combate!" +MovieSOSObserverHelp = "¡SOCORRO!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "¡Hola, %s! ¡Me alegra poder ayudarte!" +MovieNPCSOSGoodbye = "¡Nos vemos!" +MovieNPCSOSToonsHit = "¡Los dibus aciertan siempre!" +MovieNPCSOSCogsMiss = "¡"+TheCogs+" fallan siempre!" +MovieNPCSOSRestockGags = "Reaprovisionamiento de %s bromas" +MovieNPCSOSHeal = "Curadibu" +MovieNPCSOSTrap = "Trampa" +MovieNPCSOSLure = "Cebo" +MovieNPCSOSSound = "Sonido" +MovieNPCSOSThrow = "Lanzamiento" +MovieNPCSOSSquirt = "Chorro" +MovieNPCSOSDrop = "Caída" +MovieNPCSOSAll = "Todo" + +# MovieSuitAttacks.py +MovieSuitCancelled = "CANCELADO\nCANCELADO\nCANCELADO" + +# RewardPanel.py +RewardPanelToonTasks = "Dibutareas" +RewardPanelItems = "Objetos recuperados" +RewardPanelMissedItems = "Objetos no recuperados" +RewardPanelQuestLabel = "Tarea %s" +RewardPanelCongratsStrings = ["¡Así se hace!", "¡Enhorabuena!", "¡Guau!", + "¡Chupi!", "¡Impresionante!", "¡Dibufantástico!"] +RewardPanelNewGag = "¡Nueva broma %(gagName)s para %(avName)s!" +RewardPanelMeritsMaxed = "Máxima puntuación" +RewardPanelMeritBarLabel = "Méritos" +RewardPanelMeritAlert = "¡Listo para un ascenso!" + +RewardPanelCogPart = "Has conseguido una pieza de disfraz de bot" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Dibu normal", "serás normal"), + ("Cabezota grande", "tendrás la cabeza grande"), + ("Cabecita pequeña", "tendrás la cabeza pequeña"), + ("Piernotas grandes", "tendrás las piernas grandes"), + ("Piernecitas pequeñas", "tendrás las piernas pequeñas"), + ("Dibu grandote", "serás un poco más grande"), + ("Dibu pequeñito", "serás un poco más pequeño"), + ("Imagen plana", "tendrás sólo dos dimensiones"), + ("Perfil plano", "tendrás sólo dos dimensiones"), + ("Transparente", "serás transparente"), + ("Incoloro", "no tendrás color"), + ("Dibu invisible", "serás invisible"), + ] +CheesyEffectIndefinite = "hasta que elijas otro efecto, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Durante los próximos %(time)s minutos, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Durante los próximas %(time)s horas, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Durante los próximos %(time)s días, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " mientras estás en %s" +CheesyEffectExceptIn = ", excepto en %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Secuaz" +SuitPencilPusher = "Chupatintas" +SuitYesman = "Sonriente" +SuitMicromanager = "Micro\3gerente" +SuitDownsizer = "Regulador de Empleo" +SuitHeadHunter = "Cazacabezas" +SuitCorporateRaider = "Corporati\3vista" +SuitTheBigCheese = "Pez Gordo" +SuitColdCaller = "Gorrón" +SuitTelemarketer = "Tele\3vendedor" +SuitNameDropper = "Fardoncete" +SuitGladHander = "Efusivo" +SuitMoverShaker = "Mandamás" +SuitTwoFace = "Doscaras" +SuitTheMingler = "Confrater\3nizadora" +SuitMrHollywood = "Sr. Hollywood" +SuitShortChange = "Calderilla" +SuitPennyPincher = "Cacomatraco" +SuitTightwad = "Roñoso" +SuitBeanCounter = "Cuenta cuentos" +SuitNumberCruncher = "Contable" +SuitMoneyBags = "Monedero" +SuitLoanShark = "Prestamista Despiadado" +SuitRobberBaron = "Barón ladrón" +SuitBottomFeeder = "Morrudo" +SuitBloodsucker = "Chupa\3sangres" +SuitDoubleTalker = "Embaucador" +SuitAmbulanceChaser = "Persigue Ambulancias" +SuitBackStabber = "Apuña la Espaldas" +SuitSpinDoctor = "Portavoz" +SuitLegalEagle = "Picapleitos" +SuitBigWig = "Pelucón" + +# Singular versions (indefinite article) +SuitFlunkyS = "un Secuaz" +SuitPencilPusherS = "un Chupatintas" +SuitYesmanS = "un Sonriente" +SuitMicromanagerS = "una Micro\3gerente" +SuitDownsizerS = "un Regulador de Empleo" +SuitHeadHunterS = "un Cazacabezas" +SuitCorporateRaiderS = "un Corporati\3vista" +SuitTheBigCheeseS = "un Pez gordo" +SuitColdCallerS = "un Gorrón" +SuitTelemarketerS = "un Tele\3vendedor" +SuitNameDropperS = "un Fardoncete" +SuitGladHanderS = "un Efusivo" +SuitMoverShakerS = "un Mandamás" +SuitTwoFaceS = "un Doscaras" +SuitTheMinglerS = "una Confrater\3nizadora" +SuitMrHollywoodS = "un Sr. Hollywood" +SuitShortChangeS = "un Calderilla" +SuitPennyPincherS = "un Cacomatraco" +SuitTightwadS = "un Roñoso" +SuitBeanCounterS = "un Cuenta Cuentos" +SuitNumberCruncherS = "una Contable" +SuitMoneyBagsS = "un Monedero" +SuitLoanSharkS = "un Prestamista Despiadado" +SuitRobberBaronS = "un Barón Ladrón" +SuitBottomFeederS = "un Morrudo" +SuitBloodsuckerS = "un Chupa\3sangres" +SuitDoubleTalkerS = "un Embaucador" +SuitAmbulanceChaserS = "un Persigue Ambulancias" +SuitBackStabberS = "un Apuña la Espaldas" +SuitSpinDoctorS = "un Portavoz" +SuitLegalEagleS = "un Picapleitos" +SuitBigWigS = "un Pelucón" + +# Plural versions +SuitFlunkyP = "Secuaces" +SuitPencilPusherP = "Chupatintas" +SuitYesmanP = "Sonrientes" +SuitMicromanagerP = "Micro\3gerentes" +SuitDownsizerP = "Reguladores de Empleo" +SuitHeadHunterP = "Cazacabezas" +SuitCorporateRaiderP = "Corporati\3vistas" +SuitTheBigCheeseP = "Peces Gordos" +SuitColdCallerP = "Gorrones" +SuitTelemarketerP = "Tele\3vendedores" +SuitNameDropperP = "Fardoncetes" +SuitGladHanderP = "Efusivos" +SuitMoverShakerP = "Mandamases" +SuitTwoFaceP = "Doscaras" +SuitTheMinglerP = "Confrater\3nizadoras" +SuitMrHollywoodP = "Sres. Hollywood" +SuitShortChangeP = "Calderillas" +SuitPennyPincherP = "Cacomatracos" +SuitTightwadP = "Roñosos" +SuitBeanCounterP = "Cuenta Cuentos" +SuitNumberCruncherP = "Contables" +SuitMoneyBagsP = "Monederos" +SuitLoanSharkP = "Prestamistas Despiadados" +SuitRobberBaronP = "Barones Ladrones" +SuitBottomFeederP = "Morrudos" +SuitBloodsuckerP = "Chupa\3sangres" +SuitDoubleTalkerP = "Embaucadores" +SuitAmbulanceChaserP = "Persigue Ambulancias" +SuitBackStabberP = "Apuña la Espaldas" +SuitSpinDoctorP = "Portavoces" +SuitLegalEagleP = "Picapleitos" +SuitBigWigP = "Pelucones" + +SuitFaceOffDefaultTaunts = ['¡Buuu!'] + +SuitFaceoffTaunts = { + 'b': ["¿Me haces una donación?", + "Te voy a dejar hecho unos zorros.", + "Te voy a dejar más seco que la mojama.", + "Hay que ser \"RH positivo\" ante la vida.", + "Oh, no seas tan \"RH negativo\".", + "Me sorprende que me hayas encontrado, soy muy escurridizo.", + "Voy a tener que hacerte una transfusión rápida.", + "Pronto vas a tener que tomarte un bocadillo y un zumito.", + "Cuando acabe contigo no vas a poder ni levantarte.", + "No mires, sólo te pinchará un poquito.", + "Vas a marearte un poquito.", + "Justo a tiempo, estaba un poco sediento.", + ], + 'm': ["No sabes con quién estás confraternizando.", + "¿Has confraternizado alguna vez con gente como yo?", + "Estupendo, confraternicemos pues.", + "¡Me encanta confraternizar!", + "Parece un buen sitio para confraternizar.", + "Qué situación más tierna, ¿no?", + "Vas a confraternizar con la derrota.", + "Voy a hacer que confraternices con el suelo.", + "¿Seguro que quieres confraternizar conmigo?", + ], + 'ms': ["Prepárate; soy muy mandón.", + "Más te valdría quitarte de en medio.", + "Te mando que te largues.", + "Creo que me toca mandar.", + "Vas a ver adónde te mando.", + "Cuando mando, los demás tiemblan.", + "Hoy me he levantado de lo más mandón.", + "Cuidado, dibu, te va a caer encima un mandoble.", + "Te voy a mandar bien lejos.", + "El corazón me manda que te zurre.", + "¿Necesitas que te manden un poco?", + ], + 'hh': ["Te saco una cabeza.", + "Vas de cráneo, me parece.", + "Creo que has perdido la cabeza.", + "Estupendo. Tenía ganas de coleccionar tu cocorota.", + "Te vas a quedar sin cabeza por esto.", + "¡Cabeza al frente!", + "Me parece que la cabeza te ha jugado una mala pasada.", + "Qué poca cabeza estás teniendo.", + "Un trofeo perfecto para mi colección.", + "Vas a tener un buen dolor de cabeza.", + "¡Cuidado, no pierdas la cabeza!", + ], + 'tbc': ["Te voy a pescar con las manos.", + "Puedes llamarme Cachalote.", + "Ten cuidado. A veces soy como un tiburón blanco.", + "Por fin, ya pensaba que me estabas dando sedal.", + "Voy a cocinarte a la sal.", + "¿Qué tal me sienta el escabeche?", + "Ven aquí, voy a quitarte las escamas.", + "Te va a pasar igual que a Jonás.", + "Cuidadito, te he preparado un buen cebo.", + "¿Te gusta que te preparen al pil-pil?", + "¡Te voy a dar un buen pez-cozón!", + ], + 'cr': ["¡DESPEDIDO!", + "No encajas en mi colectivo.", + "Vas a ser expulsado de la hermandad.", + "No pareces defender los intereses comunes.", + "Ese atuendo no es propio de tu colectivo.", + "Te gusta sacar los pies del tiesto, ¿eh?", + "Te voy a expulsar del colegio profesional.", + "Un esquirol, ¿eh? ¡Vas a ver!", + "No defiendes bien las ideas del colectivo.", + "Relájate; esto es por el bien del colectivo.", + ], + 'mh': ["¿Estás listo para mi escena?", + "¡Luces, cámaras, acción!", + "¡Estamos rodando!", + "¡Hoy te toca desempeñar el papel del dibu derrotado!", + "Por esta escena me van a dar el Oscar.", + "Acabo de encontrar la inspiración para esta escena.", + "¿Estás listo para tu escena final?", + "No vas a aparecer ni en los créditos finales.", + "No pienso firmarte ni un autógrafo.", + "Voy a rodar contigo una escena de terror.", + "¡Me encanta zurrar a los extras como tú!", + "Espero que no olvides tu parte del guión.", + ], + 'nc': ["Parece que tu balance no cuadra.", + "Creo que tienes un déficit enorme.", + "Déjame que equilibre tus cuentas.", + "¡Estás en números rojos!", + "¡Vas a tener que contabilizar tu factura del hospital!", + "¿En qué columna te pongo? ¿Debe o haber?", + "Eres un cero a la izquierda.", + "Tu presupuesto está muy desequilibrado.", + "Cuando acabe contigo no vas a saber ni contar.", + "Voy a contar las veces que te machaco.", + ], + 'ls': ["Es la hora de que pagues tu préstamo.", + "Te he prestado demasiado tiempo.", + "Es el momento del vencimiento.", + "Venga, vamos a saldar cuentas.", + "Pediste un anticipo y te lo voy a dar.", + "Vas a pagar por esto.", + "Llegó el día del ajuste de cuentas.", + "¿Me prestas una oreja?", + "Me alegro de que estés aquí; quiero lo que es mío.", + "Voy a prestarte una paliza.", + "Te voy a ofrecer un interés especial.", + ], + 'mb': ["Es la hora de recoger la calderilla.", + "Eres dinero suelto para mí.", + "¿En efectivo o con tarjeta?", + "¿Tienes el recibo?", + "Recuerda que el dinero no da la felicidad.", + "Me parece que andas escaso de fondos.", + "Vas a tener un pequeño problema de efectivo.", + "Después de esto, te veo pidiendo calderilla.", + "Soy demasiado rico para mancharme las manos contigo.", + "¡No hay dinero suficiente para satisfacerme!", + ], + 'rb': ["Te han robado.", + "Te voy a robar la victoria.", + "¡Soy un barón del incordio!", + "Soy la nobleza avasalladora.", + "Vas a tener que denunciar este robo.", + "Tengo la sangre azul... Veamos la tuya.", + "Soy un rival noble.", + "Te voy a dejar pelado.", + "Supongo que esto es un robo a mano desarmada.", + "¿No sabías que no se debe hablar con desconocidos?", + ], + 'bs': ["Nunca me des la espalda.", + "Te voy a dar un buen espaldarazo.", + "Vas de espaldas por la vida.", + "Se me da bien cortar el lomo.", + "¿Te hago la acupuntura en la espalda?", + "¡De espaldas contra la pared!", + "¿Quieres que te haga cosquillas en la espalda?", + "Me encanta jugar con las espalderas.", + "Deja que te rasque la espalda.", + "¡Date la vuelta, alguien viene!", + "¡Mira, a tu espalda!", + ], + 'bw': ["¿Quieres que te pase el cepillo?", + "Sólo de verte se me riza el pelo.", + "Si quieres hacemos esto permanente.", + "Creo que vas a tener las puntas un poco abiertas.", + "Creo que estás un poco bisoño.", + "Te voy a teñir todo el cuerpo de morado.", + "Has venido justo a tiempo para que te dé un buen repaso.", + "Se te va a caer el pelo.", + "Sólo de verte me salen entradas.", + "Se te va a poner el pelo blanco.", + ], + 'le': ["Creo que no tienes defensa posible. ", + "Estoy picado contigo.", + "Va a caer todo el peso de la ley encima de ti.", + "Deberías saber que, cuando llevo la toga, soy implacable.", + "Lo tuyo es un caso perdido de antemano.", + "Creo que te va a caer cadena perpetua.", + "Esto es tan divertido que debería ser ilegal.", + "Lo siento; te tendrás que defender a ti mismo.", + "Mis honorarios son bastante altos. ¿Podrás permitírtelos?", + "Te voy a hacer trizas en el estrado.", + ], + 'sd': ["Voy a anunciar tu fin.", + "Deja que proclame tu derrota.", + "El portavoz va anunciar tu desaparición.", + "El mundo entero va a saber lo acabado que estás.", + "Te vendría bien alguien que hablase por ti.", + "¡Uy! ¡Al verte se me corta la voz!", + "Deja que me aclare la voz un momento.", + "Cuando acabe contigo no vas a tener voz ni voto.", + "Podría anunciar mi victoria antes de tiempo.", + "Damas y caballeros, este dibu es penoso.", + ], + 'f': ["¡Me voy a chivar al jefe de ti!", + "¡Soy un secuaz, pero soy muy pertinaz!", + "Gracias a ti voy a conseguir un ascenso.", + "No creo que te guste mi forma de trabajar.", + "El jefe cuenta conmigo para ponerte fin.", + "Vas a hacer que gane puntos ante el jefe.", + "Primero tendrás que vértelas conmigo.", + "Veamos qué te parece mi trabajo.", + "Se me da de miedo deshacerme de los dibus.", + "Nunca llegarás a ver a mi jefe.", + "Te voy a enviar de vuelta al dibuparque.", + ], + 'p': ["¡Voy a borrarte de un plumazo!", + "¡Chúpate ésta, pelele!", + "¡Voy a cargar las tintas!", + "Esto está adquiriendo tintes dramáticos...", + "Deja que te aplique un poco de secante.", + "Te voy a archivar para siempre.", + "Deprisa, tengo que fichar pronto.", + "Habré acabado contigo antes de que la tinta se seque.", + "¡Nuestro encuentro hará correr ríos de tinta!", + "Creo que tienes la tinta un poco seca, déjame que te vea.", + "¡Cuidado, que mancho!", + ], + 'ym': ["Lástima que esto no te vaya a gustar.", + "Odio que la gente esté seria.", + "Sonríe, la vida es bella... Aunque no para ti. Una sonrisa vale por cien dibus.", + "Necesitas algo de alegría en tu vida.", + "Después de esto se te va a quedar sonrisa de tonto.", + "Mi sonrisa desarma a cualquiera.", + "Al verte, he sonreído más todavía.", + "¿Te gusta mi sonrisa? ¡Vas a recordarla mucho tiempo!", + "Te veo y sonrío para mis adentros.", + "¿Una sonrisita antes de que acabe contigo?", + "No sonríes nada... No me extraña.", + ], + 'mm': ["¡Voy tomar el control de tus negocios!", + "Las grandes palizas vienen a veces en frasco pequeño.", + "Ningún reto que queda grande.", + "Cuando quiero que algo salga bien, lo hago yo mismo.", + "Necesitas a alguien que te gestione bien.", + "¡Qué bien, un proyecto!", + "Te voy a gestionar una buena lección.", + "Hay que reorganizarte la agenda del día.", + "Voy a gestionar tu presencia aquí.", + "Estoy vigilando todos tus movimientos.", + "¿Seguro que te atreves?", + "Haremos esto a mi manera.", + "No te pienso quitar el ojo de encima.", + "Vas a ver lo que es acoso laboral.", + ], + 'ds': ["¡Vas a irte a la calle!", + "Tu puesto de trabajo peligra.", + "Creo que tu perfil no se ajusta a mis necesidades.", + "Ya no nos eres de utilidad.", + "Yo que tú empezaba a pedir entrevistas.", + "Tendré que hacer algunos ajustes de plantilla.", + "Tienes poco futuro en mi empresa.", + "Voy a tener que hacer algunos recortes.", + ], + 'cc': ["Hola, ¿llevas algo suelto?", + "Te devolveré lo que te debo mañana sin falta.", + "¿Me dejas que llame desde tu móvil?", + "¿Me invitas a comer? Me dejado la cartera en casa.", + "¿Qué tienes hoy de comida?", + "Me gusta tu ropa; creo que me la voy a quedar.", + "Hoy vas a prestarme dinerito, ¿verdad?", + "No te preocupes; siempre lo devuelvo todo.", + "Creo que me voy a quedar una semanita en tu casa.", + "Creo que voy a hacer unos recados en tu coche.", + "Seguro que tus zapatos me quedan bien.", + "Me encanta como cocinas; ¡voy a aficionarme a tu casa!", + "He puesto mi línea telefónica a tu nombre.", + ], + 'tm': ["Nunca he visto un producto peor que tú.", + "Con mi superquitamanchas salen borrones como tú.", + "Te voy a aplastar con mi megabdominator.", + "Si acabo contigo, me regalo un cuchillo de cocina.", + "Tu final está disponible con una llamada. ¡Date prisa! ¡Tu fin está al caer!", + "Acabe con los dibus molestos con \"zurradibu\".", + "¡Te voy a poner los superéxitos de los bots!", + "¿Cansado de tu figura? ¡Yo tengo la solución!", + "¿No sabes qué hacer con tu pelo? ¡Llámame!", + "Acepto tarjetas de crédito.", + "Cuando acabe contigo, no habrás probado nada igual.", + ], + 'nd': ["Seguro que mi coche corre más que el tuyo. ", + "Supongo que sabrás que tengo mucho más dinero que tú.", + "Contigo no tengo ni para empezar.", + "Venga, deprisa, que tengo que comer con el Sr. Hollywood.", + "¿Te había dicho que conozco al pez gordo?", + "Soy íntimo amigo del mandamás.", + "Conozco a gente que sabría encargarse de ti.", + "¿Sabes con quién estás hablando?", + "Acabaré rápidamente contigo; he quedado con gente importante.", + "Lo siento; no me suelo codear con dibus como tú.", + ], + 'gh': ["¡Hombre, un dibu! ¡Qué alegría machacarte!", + "¡Qué bien! ¡Un dibu al que zurrar!", + "¡Me lo voy a pasar de lo lindo contigo!", + "¡Yupiii! ¡Tenía ganas de vérmelas con un dibu!", + "¡Vas a ver lo que es bueno!", + "¡Cómo me alegro de no volver a verte!", + "Encantado de conocer... ¡tu fin!", + "¡Cuánto tiempo sin verte! ¡Y cuánto más va a pasar!", + "¡He estado años esperando este momento!", + "¡Me siento feliz de poder zurrarte!", + "¡Hurra! ¡Un dibu tiernecito!", + "¡Hoooola! ¿Cómo quieres que acabe contigo?", + "¡Un dibu! ¡Déjame que te estreche bien la mano!", + ], + 'sc': ["¡Voy a hacerte calderilla!", + "Vas a tener un pequeño problema de efectivo.", + "No acepto tus divisas.", + "No tengo cambio para ti.", + "Cuando acabe contigo, no vas a valer ni un céntimo.", + "Creo que la inflación te va a venir muy mal.", + "Te voy a depreciar en breve.", + "Mmm, creo que no llevo dibus sueltos.", + "Cuando acabe contigo no te va a quedar ni un céntimo.", + "¿Llevas calderilla para tu vuelta al dibuparque?", + "No acepto propinas de un dibu.", + ], + 'pp': ["Espera, te voy a aligerar de peso. ", + "¿No notas que te falta algo?", + "Yo que tú guardaría bien la cartera.", + "¿Te has acordado de cerrar con llave tu casa?", + "Me encanta tu reloj; creo que me lo voy a quedar.", + "Vas a volver al dibuparque pelado de gominolas.", + "Lo siento; tengo que requisarte unas cosillas.", + "¿Me dejas ver qué llevas en los bolsillos?", + "¿Algo que declarar?", + "¡Otro dibu al que desplumar!", + ], + 'tw': ["No esperes que dé ni los buenos días.", + "Para ti soy el Sr. Roñoso.", + "Voy a cortarte los fondos.", + "¿Es ésa la mejor oferta que tienes?", + "Venga, deprisa. El tiempo es oro.", + "¡Soy de la hermandad del puño cerrado!", + "Creo que tu oferta no me convence.", + "Vas a tener que ofrecer mucho más, me temo.", + "A ver si puedes permitirte esto.", + "No te pienso dar ni una oportunidad.", + "Voy a pegarles un buen bocado a tus fondos.", + ], + 'bc': ["Me encanta contar cuentos a los dibus.", + "Cuenta conmigo para pasarlo mal.", + "Cuenta con que te voy a zurrar bien.", + "¿Te cuento un cuento de miedo?", + "Aquí el que cuenta soy yo.", + "Cuenta con volver al dibuparque.", + "Después de esto, no lo vas a contar.", + "Este cuento va a acabar mal para ti.", + "No tengas cuento...", + "Cuando acabe contigo, no te van a salir las cuentas.", + "Tenía unas cuantas cuentas pendientes contigo...", + ], + 'bf': ["Todos me dicen que tengo mucho morro.", + "Siempre le hecho morro a la vida.", + "Hay que tener morro para venir aquí.", + "Tienes bastante morro, ¿no crees?", + "Justo a tiempo, te voy a hinchar los morros.", + "Tengo un morro que me lo piso.", + "Para ganarme le vas a tener que echar mucho morro.", + "Tu morro no está a la altura de las circunstancias.", + "Te voy a mandar al dibuparque de un morrazo.", + "Vas a ver mis morros por última vez.", + ], + 'tf': ["¡Por fin nos vemos las caras!", + "¡Vas a tener que encarar la derrota!", + "¿A que no sabes hacia dónde estoy mirando?", + "Como tengo dos caras, es difícil que me las rompan.", + "Dos caras son mejor que una.", + "¿Cuál de mis dos caras te gusta más?", + "Creo que te llaman en el dibuparque.", + "¿Qué cara quieres que se encargue de ti?", + "Tengo bastante más cara que tú.", + "No sabes la cara que tengo...", + "Lo mire por donde lo mire, siempre te veo...", + ], + 'dt': ["Ha llegado el momento de embaucar a alguien.", + "Oye, hay un elefante detrás de ti.", + "¿Quieres que le eche un poco de cara al asunto?", + "¿Cuál de mis dos caras dice la verdad?", + "Yo que tú encaraba la salida.", + "No te va a gustar mi doble juego.", + "Yo que tú me lo pensaba dos veces.", + "Prepárate para una ración DOBLE.", + "Vas a ver mis caras en sueños.", + "Para vencerme hacen falta dos como tú.", + ], + 'ac': ["¡Te voy a perseguir hasta el dibuparque!", + "¿No oyes una sirena?", + "Ja, ja, cómo voy a disfrutar.", + "Me encanta la emoción de la persecución.", + "¡Corre, corre, que te pillo!", + "¿Te has hecho un seguro?", + "Espero que te hayas traído una camilla.", + "Dudo que aguantes mi ritmo.", + "A partir de aquí se te va a hacer todo cuesta arriba.", + "Pronto vas a necesitar atención médica urgente.", + "Esto no es para reírse.", + "Espero que te gusten los hospitales.", + ] + } + +SuitAttackDefaultTaunts = ['¡Toma ya!!', '¡Fíjate bien en esto!'] + +SuitAttackNames = { + 'Audit' : '¡Auditoría!', + 'Bite' : '¡Mordisco!', + 'BounceCheck' : 'Cheque sin fondos!', + 'BrainStorm' : '¡Aguacero!', + 'BuzzWord' : '¡Charlatán!', + 'Calculate' : '¡Calculadora!', + 'Canned' : '¡Enlatado!', + 'Chomp' : '¡Zampón!', + 'CigarSmoke' : '¡Humo de Cigarro!', + 'ClipOnTie' : '¡Corbatón!', + 'Crunch' : '¡Crujido!', + 'Demotion' : '¡Degradación!', + 'Downsize' : '¡Recorte de plantilla!', + 'DoubleTalk' : '¡Embaucar!', + 'EvictionNotice' : '¡Deshaucio!', + 'EvilEye' : '¡Mal de ojo!', + 'Filibuster' : '¡Discurso plasta!', + 'FillWithLead' : '¡Lleno de Plomo!', + 'FiveOClockShadow' : "¡Barbudo!", + 'FingerWag' : '¡Regañado!', + 'Fired' : '¡Despedido!', + 'FloodTheMarket' : '¡Saturar el Mercado!', + 'FountainPen' : '¡Manchón de tinta!', + 'FreezeAssets' : '¡Activos congelados!', + 'Gavel' : '¡Martillo!', + 'GlowerPower' : '¡Mirada feroz!', + 'GuiltTrip' : '¡Culpable!', + 'HalfWindsor' : '¡Nudo imposible!', + 'HangUp' : '¡Corte de línea!', + 'HeadShrink' : '¡Reducción de cabeza!', + 'HotAir' : '¡Aire caliente!', + 'Jargon' : '¡Jerga jurídica!', + 'Legalese' : '¡Parrafada legal!', + 'Liquidate' : '¡Liquidación!', + 'MarketCrash' : '¡Desplome de bolsa!', + 'MumboJumbo' : '¡Rollo total!', + 'ParadigmShift' : '¡Cambio de rumbo!', + 'PeckingOrder' : '¡Pajarraco!', + 'PickPocket' : '¡Caco!', + 'PinkSlip' : '¡Carta de despido!', + 'PlayHardball' : '¡Última partida!', + 'PoundKey' : '¡Factura de teléfono!', + 'PowerTie' : '¡Corbata feroz!', + 'PowerTrip' : '¡Viajecito!', + 'Quake' : '¡Terremoto!', + 'RazzleDazzle' : '¡Sonrisote!', + 'RedTape' : '¡Cinta roja!', + 'ReOrg' : '¡Reorganización!', + 'RestrainingOrder' : '¡Orden de alejamiento!', + 'Rolodex' : '¡Agenda!', + 'RubberStamp' : '¡Sellazo!', + 'RubOut' : '¡Borrado!', + 'Sacked' : '¡Al saco!', + 'SandTrap' : '¡Arenas movedizas!', + 'Schmooze' : '¡Adulación!', + 'Shake' : '¡Sacudida!', + 'Shred' : '¡Triturador!', + 'SongAndDance' : '¡Canto y danza!', + 'Spin' : '¡Giro loco!', + 'Synergy' : 'Sinergia!', + 'Tabulate' : '¡Contabilidad!', + 'TeeOff' : '¡Bolazo!', + 'ThrowBook' : '¡Tirar el libro!', + 'Tremor' : '¡Trepidación!', + 'Watercooler' : '¡Nevera!', + 'Withdrawal' : '¡Retidada de fondos!', + 'WriteOff' : '¡Agujero contable!', + } + +SuitAttackTaunts = { + 'Audit': ["Creo que no te cuadran las cuentas.", + "Parece que estás en números rojos.", + "Deja que te ayude con la contabilidad.", + "Tu columna de débito está por las nubes. ", + "Voy a echar un vistazo a tus activos.", + "Esto te va a desequilibrar las cuentas.", + "Voy a examinar de cerca lo que debes.", + "Con esto voy a dejar tu cuenta a cero.", + "Es la hora de contabilizar tus gastos.", + "He encontrado un error en tu libro de contabilidad.", + ], + 'Bite': ["¿Te apetece un mordisquito?", + "¡Prueba un poco de esto!", + "Perro ladrador, poco mordedor.", + "¡Hoy estoy que muerdo!", + "¡Vas a morder el polvo!", + "Cuidado, que muerdo.", + "Muerdo siempre que puedo.", + "Voy a morderte un poquito.", + "No me pienso morder la lengua contigo.", + "Sólo un mordisquito... ¿Es pedir demasiado?", + ], + 'BounceCheck': ["Qué lástima, eres un rollo.", + "Tienes pendiente un pago.", + "Creo que este cheque es tuyo.", + "Me debes este cheque.", + "Estoy cobrando deudas atrasadas.", + "Este cheque está al rojo vivo.", + "Te voy a pasar un buen recargo.", + "Echa un vistazo a esto.", + "Esto te va a costar una buena pasta.", + "Me gustaría cobrar esto.", + "Voy a devolverte este regalito.", + "Éste será tu talón de Aquiles.", + "Voy a incluir una penalización.", + ], + 'BrainStorm':["Mi predicción es que va a llover.", + "Espero que lleves paraguas.", + "Voy a remojarte un poco.", + "¿Qué te parecen unas cuantas GOTITAS?", + "Ya no hace un día tan bueno, ¿eh, dibu?", + "¿Estás listo para un aguacero?", + "Vas a ver lo que es una buena tormenta.", + "A esto lo llamo ataque relámpago.", + "Me encanta ser un aguafiestas.", + ], + 'BuzzWord':["Deja que te diga unas palabras.", + "¿Te has enterado de lo último?", + "A ver si pillas esto.", + "Intenta pronunciar esto, dibu.", + "Deja que te ponga los puntos sobre las íes.", + "Te doy mi palabra de que seré claro.", + "Deberías medir mejor tus palabras.", + "A ver cómo esquivas esto.", + "Cuidado, esto no es un juego de palabras.", + "Déjate de palabrería y prueba esto.", + ], + 'Calculate': ["¡Con esto te van a salir las cuentas!", + "¿Habías contado con esto?", + "Suma y sigue; estás acabado.", + "Espera; te voy a ayudar a sumar esto.", + "¿Has sumado todos tus gastos?", + "Según mis cálculos, no estarás aquí mucho tiempo.", + "Aquí tienes el total.", + "Vaya; tu factura no para de aumentar.", + "¡Ponte a sumar esto!", + "Bots: 1; Dibus: 0", + ], + 'Canned': ["¿Te gustan las conservas?", + "\"Conserva\" esto como recuerdo.", + "¡Esto está recién salido de la lata!", + "¿Te han dado el latazo alguna vez?", + "¡Voy a hacerte una donación de alimentos en conserva!", + "¡Prepárate para una buena lata!", + "Me gusta que \"conserves\" todos tus ánimos.", + "¡Te voy a poner en conserva!", + "¡El menú de hoy va a ser dibu enlatado!", + "\"Conserva\" esto para el recuerdo...", + ], + 'Chomp': ["¿Quieres hacer el favor de masticar bien?", + "¡Ñam, ñam, ñam!", + "No te olvides de cerrar la boca al comer.", + "¿Tienes ganas de masticar algo?", + "Prueba a masticar esto.", + "¡Vas a ser mi cena!", + "¡Me encantan las dietas a base de dibus!", + ], + 'ClipOnTie': ["¿Por qué no te arreglas un poco?", + "¡Tienes que llevar corbata a las reuniones!", + TheCogs+" elegantes siempre se ponen una de éstas...", + "Pruébate ésta, a ver qué tal te queda.", + "La imagen es fundamental para tener éxito en la vida.", + "Aquí no se admite a nadie sin corbata.", + "¿Quieres que te ayude a ponerte esto?", + "Una buena corbata dice mucho de ti.", + "Veamos qué tal te sienta esto.", + "Esto a lo mejor te aprieta un poco.", + "Es mejor que te arregles antes de MARCHARTE.", + "Toma, con esto serás el dibu más guapo del dibuparque.", + ], + 'Crunch': ["Parece que estás un poco crujido.", + "¡Es hora de crujirse un poco!", + "Con esto te van a crujir las articulaciones.", + "¡Mira qué crujido más delicioso!", + "¿No oyes un crujido?", + "¿Qué prefieres, blandito o crujiente?", + "Esto está crujiente y apetitoso.", + "¡Prepárate para que te crujan los huesos!", + "¡Me encantan los postres crujientes!" + ], + 'Demotion': ["Vas a bajar puestos en la empresa. ", + "Vas a volver a trabajar de botones.", + "Me parece que te has quedado sin despacho.", + "¡Majo, vas para abajo!", + "Creo que tu puesto peligra.", + "Tienes poco futuro en esta empresa.", + "Laboralmente, estás en un callejón sin salida.", + "Tu puesto en la empresa se está tambaleando.", + "Te veo preparando café bien pronto.", + "Esto va a ir directo a tu expediente.", + ], + 'Downsize': ["¿Qué tal unos cuantos recortes?", + "A veces hay que aplicar la tijera.", + "Yo que tú iría pidiendo entrevistas de trabajo.", + "¿Has guardado el suplemento de empleo de tu periódico?", + "¿Nunca te han dicho que eres prescindible?", + "Tu perfil no se ajusta a nuestras necesidades actuales.", + "¿Has oído hablar de las reestructuraciones?", + "Me temo que ya no nos eres útil.", + "¿Por qué no te buscas un trabajo en otro sitio?", + "Este año no te comes el turrón en esta empresa.", + "Me temo que los cambios en la empresa te van a afectar.", + "Me temo que nos sobra algo de personal.", + ], + 'EvictionNotice': ["¡Ha llegado la hora de la mudanza!", + "Haz las maletas, dibu.", + "Creo que vas a tener que cambiar de residencia.", + "Creo que debajo del puente se está muy bien.", + "Me temo que no has pagado el alquiler.", + "¿Habías pensado en redecorar tu vivienda?", + "A partir de ahora vas a disfrutar del aire libre.", + "¿No decías que te gustaban los espacios abiertos?", + "¡Estás fuera de lugar!", + "Prepárate para ser reubicado.", + "Tranquilo; ahora vas a conocer a más gente.", + "You're in a hostel position.", + ], + 'EvilEye': ["Te voy a echar el mal de ojo.", + "¿Puedes echarle un ojo a esto?", + "Espera. Se me ha metido algo en el ojo.", + "¡Te he puesto el ojo encima!", + "En la vida hay que tener ojo para todo.", + "Tengo mucho ojo para el mal.", + "¡Te voy a meter el dedo en el ojo!", + "¡Ten mucho ojo conmigo!", + "¡Te voy a meter en el ojo del huracán!", + "¡No te pienso quitar ojo!", + ], + 'Filibuster':["¿Te gustan los discursos?", + "Esto va a durar un ratito.", + "Podría estar así todo el día.", + "No me hace falta tomarme ni un respiro.", + "Puedo seguir y seguir.", + "Nunca me canso de hacer esto.", + "Soy capaz de aburrir a las ovejas.", + "¿Te importa si digo unas palabras?", + "Creo que voy a soltar un discursito.", + "Tengo preparadas unas frases para ti.", + ], + 'FingerWag': ["Te lo he dicho un millón de veces.", + "Dibu, te estoy hablando a ti.", + "No me hagas reír.", + "No me hagas ir hasta ahí.", + "Estoy harto de repetírtelo.", + "Creo que ya te había dicho esto.", + "No nos guardas ningún respeto a los bots.", + "Es hora de que empieces a prestar atención.", + "Bla, bla, bla, bla, bla.", + "Voy a tener que aplicarte un correctivo.", + "¿Cuántas veces te lo tengo que decir?", + "No es la primera vez que pasa esto.", + ], + 'Fired': ["Espero que te hayas traído algo para la barbacoa.", + "Esto va a ponerse calentito.", + "Seguro que con esto entres en calor.", + "Espero que seas un animal de sangre fría.", + "¡Caliente, caliente!", + "Creo que te va a hacer falta un extintor.", + "¡Vas a quedarte chamuscado!", + "¡Vas a quedar muy doradito!", + "Esto le da otro significado a la expresión \"bien hecho\".", + "Espero que te hayas puesto protección solar.", + "Avísame cuando estés crujiente.", + "La cosa está que arde.", + "Vas a arder en deseos de volver al dibuparque.", + "Creo que tienes un temperamento ardiente.", + "A ver, déjame que te ponga el termómetro...", + "¡Tienes mucha chispa!", + "El que juega con fuego...", + "¿Nunca te han dicho que te sale humo de las orejas?", + ], + 'FountainPen': ["Esta mancha no va a salir. ", + "Vas a tener que ir a la tintorería.", + "Prepárate para comprar detergente.", + "Esto no es precisamente tinta invisible.", + "Vas a tener que cambiarte de ropa.", + "La tinta de esta pluma no se acaba nunca.", + "Toma, usa mi pluma.", + "¿Lees bien mi letra?", + "Ya no hacen plumas como las de antes.", + "Vaya, hay un borrón en tu expediente.", + "Te dije que no cargaras las tintas.", + ], + 'FreezeAssets': ["¿Te sirvo un poco de hielo?", + "Te voy a juzgar por tus acciones.", + "Me parece que voy a pasar a la acción.", + "Voy a congelar la imagen.", + "El ambiente está muy frío.", + "Este año se va a adelantar el invierno.", + "Esto te enfriará los ánimos.", + "Mi plan está a punto de cristalizar.", + "Te vas a quedar petrificado.", + "Se te van a congelar las ideas.", + "¿Te gustan las cenas frías?", + "Voy a refrescarte la memoria.", + ], + 'GlowerPower': ["¿Me miras a mí?", + "Me han dicho que tengo una mirada penetrante.", + "Mírame fijamente a los ojos...", + "¿Te gusta mi caída de ojos?", + "Tengo una mirada arrebatadora.", + "¿No te parecen unos ojos muy expresivos?", + "Siempre me han dicho que tengo unos ojos preciosos.", + "El secreto está en la mirada.", + "¡Veo, veo! ¡Veo un dibu en apuros!", + "Deja que te mire bien...", + "¿Echamos una mirada a tu futuro?", + ], + 'GuiltTrip': ["¡Vas a cargar con toda la culpa!", + "¿Te sientes culpable?", + "¡Todo es culpa tuya!", + "¡Te pienso culpar por todo!", + "¡Has sido declarado culpable!", + "¡No pienso volver a hablarte!", + "¡Más te valdría pedir perdón!", + "¡No te pienso perdonar en la vida!", + "¿No crees que te has portado mal?", + "¡No intentes echarme la culpa!", + "¡Eres un culpable con causa!", + ], + 'HalfWindsor': ["¡Ésta es la corbata más bonita que has visto en tu vida!", + "Intenta no hacerte un nudo.", + "Se te va a poner un nudo en el estómago.", + "Tienes suerte de que sea un nudo fácil.", + "¿No se te hace un nudo en la garganta?", + "¡Seguro que no sabes ni hacerte el nudo!", + "¡Después de esto te voy a anudar la lengua!", + "No debería malgastar esta corbata contigo.", + "¡No te mereces esta corbata tan bonita!", + ], + 'HangUp': ["Se ha cortado tu llamada.", + "¡Adiós!", + "Es el momento de terminar la conexión.", + "... ¡Y no vuelvas a llamarme!", + "¡Clic!", + "Se acabó la conversación.", + "Voy a cortar la línea.", + "Creo que tienes la línea en mal estado.", + "Me parece que no tienes línea.", + "Ha finalizado tu llamada.", + "Espero que me escuches alto y claro.", + "Te has equivocado de número.", + ], + 'HeadShrink': ["¿Has estado últimamente en el Amazonas?", + "Cariño, he encogido a un dibu.", + "Espero que tu orgullo no se quede encogido.", + "¿Has encogido en la lavadora?", + "Te he dicho que no te laves con agua caliente.", + "No pierdas la cabeza por esto.", + "¿Es que has perdido la cabeza?", + "¡Pero qué poca cabeza tienes!", + "¡Eres un cabeza de chorlito!", + "No sabía que habías pasado una temporada con los jíbaros.", + ], + 'HotAir':["El ambiente se está acalorando.", + "Vas a sufrir una ola de calor.", + "He llegado al punto de ebullición.", + "Me parece que te vas a achicharrar un poco.", + "Vas a quedar un poco doradito...", + "Recuerda: si ves humo, es que hay fuego.", + "Te veo un poco quemado.", + "Creo que hoy va a haber fumata blanca.", + "Supongo que es la hora de avivar un poco el fuego.", + "Permíteme que encienda la llama del amor.", + "¡Esto se va a poner al rojo vivo!", + "¿Te seco un poco el pelo?", + ], + 'Jargon':["Qué cantidad de tonterías se dicen...", + "A ver si adivinas qué significa esto.", + "Espero que me recibas alto y claro.", + "Me parece que voy a tener que hablar más alto.", + "Insisto; es mi turno para hablar.", + "Te voy a ser muy sincero.", + "He de pontificar sobre este asunto.", + "¿Ves? Las palabras pueden hacer daño.", + "¿Has pillado lo que quiero decir?", + "Palabras, palabras, palabras...", + ], + 'Legalese':["Debes cejar en tu empeño.", + "En términos legales, vas a perder el juicio.", + "¿Estás al tanto de las connotaciones legales?", + "¡No puedes situarte al margen de la ley!", + "Deberían hacer una ley expresamente contra ti.", + "Me reservo el derecho de modificar tu contrato.", + "Las opiniones expresadas en este ataque no coinciden con las de Toontown Online.", + "No me hago responsable de los daños derivados de este ataque.", + "Vas a asumir los costes directos e indirectos de este ataque.", + "Me reservo el derecho de prolongar este ataque.", + "¡Estás fuera de mi sistema legal!", + "No vas a poder asumir los costes legales de este ataque.", + ], + 'Liquidate':["Me encanta que nuestra relación sea fluida.", + "¿Estás teniendo problemas de liquidez?", + "Voy a tener que ponerte en remojo.", + "Hay que dar fluidez a este proceso.", + "¡Recuerda que el suelo está resbaladizo!", + "Tu dinero es papel mojado.", + "En esta vida hay que mojarse un poco.", + "Te voy a poner en venta al 50 %.", + "Te vas a diluir un poco...", + "Me apetece un dibu pasado por agua.", + ], + 'MarketCrash':["Me parece que tus acciones han caído.", + "No vas a sobrevivir al cierre de sesión.", + "Tus valores caen en picado.", + "Tu cartera de acciones va a quedarse temblando.", + "Va a ser todo un lunes negro para ti.", + "Hoy estoy de lo más alcista.", + "Creo que me voy a desprender de tus acciones.", + "¡Más vale que vendas todo pronto!", + "¡Vende, rápido!", + "Vas a iniciar una tendencia bajista.", + "El mercado se va a desplomar encima de ti.", + ], + 'MumboJumbo':["Voy a ver si me expreso con claridad.", + "Es así de sencillo.", + "Te voy a explicar cómo vamos a resolver esto.", + "Permíteme que te resuma esto.", + "A lo mejor esto te suena a discurso.", + "No quiero soltarte una parrafada, pero...", + "Vaya; se me llena la boca.", + "Odio alargarme en mis peroratas.", + "Permíteme unas palabrillas.", + "Tengo preparado un discursito para ti.", + ], + 'ParadigmShift':["¡Cuidado! Hoy estoy de lo más cambiante.", + "¡Prepárate para un buen golpe de timón!", + "Creo que hay que enderezar tu rumbo.", + "Vas a tener un ligero cambio de perspectiva.", + "Supongo que no vas a tener cambio para esto.", + "¡Has perdido el norte!", + "Seguro que nunca has cambiado tanto de orientación.", + "¡Creo que no te va a gustar este cambio!", + "¡No me hagas cambiar de parecer!", + ], + 'PeckingOrder':["Sí; soy todo un pájaro.", + "Más vale pájaro en mano...", + "Me ha dicho un pajarito que vas a volver de golpe al dibuparque.", + "¡No huyas como un gallina!", + "Creo que tienes la cabeza llena de pájaros.", + "¡Cría cuervos y tendrás muchos!", + "¡Ya has volado bastante, pajarito!", + "Me encanta salir de picoteo.", + "Voy a hacer un buen caldo de gallina contigo.", + ], + 'PickPocket': ["Deja que me haga cargo de tus objetos personales.", + "¿A ver qué llevas ahí?", + "Esto es como quitarle un caramelo a un niño.", + "Menudo robo...", + "Espera, yo te sujeto eso.", + "No pierdas de vista mis manos.", + "La mano es más rápida que el ojo...", + "Nada por aquí...", + "La dirección no se hace responsable de los objetos perdidos.", + "Santa Rita, Rita...", + "No te vas a dar ni cuenta.", + "Te voy a dejar desplumado.", + "¿Te importa si me quedo con esto?", + "Te voy a aligerar de peso.", + ], + 'PinkSlip': ["Tengo algo de correspondencia para ti.", + "¿Estás asustado? ¡Te has puesto pálido!", + "Esta carta te va a hacer mucha ilusión.", + "Vaya; creo que alguien va a tener que hacer las maletas.", + "¡Eh, no te vayas sin despedirte!", + "¿Te has despedido ya de todo el mundo?", + "¡Mira, una carta de amor!", + "Creo que este papel es para ti...", + "La verdad es que éste color no te favorece.", + "¡Aquí tienes tu carta de despido. Largo de aquí!", + ], + 'PlayHardball': ["¿Así que quieres jugar al béisbol?", + "No te recomiendo que juegues conmigo.", + "¡Batea de una vez!", + "¡Venga, batea esto!", + "¡Y aquí viene el lanzamiento...!", + "Vas a tener que mandarla lejos.", + "Te voy a sacar del estadio de un batazo.", + "Te voy a mandar al dibuparque de un batazo.", + "¡Ésta es tu carrera final!", + "¡No vas a poder jugar conmigo!", + "¡Te voy a poner en órbita!", + "¡Vas a ver qué bola te lanzo!", + ], + 'PoundKey': ["Es hora de devolver algunas llamadas.", + "¿Qué tal una llamada a cobro revertido?", + "¡Ring, ring! ¡Es para ti!", + "Toma, para que me llames cuando quieras.", + "Me sobra una almohadilla...", + "Espero que tu teléfono sea de tonos.", + "Déjame que marque este número.", + "Voy a hacer una llamada por sorpresa.", + "Espera, te voy a dar un toque.", + "Dibu, vas a poder hacer sólo una llamada.", + ], + 'PowerTie': ["Te veré más tarde; parece que se te ha hecho un nudo en la garganta.", + "Voy a atar unos cuantos cabos sueltos.", + "¡Con esto vas a ir muy elegante!", + "Para que vayas practicando los nudos...", + "Ya es hora de que empieces a vestir bien.", + "¡Ésta es la corbata más fea que has visto en tu vida!", + "¿No te sientes importante llevando esto?", + "¡Vas a ver cómo cambia tu aspecto!", + "¡Nada mejor que una corbata de regalo!", + "No sabía qué regalarte, así que ¡toma!", + ], + 'PowerTrip': ["Haz las maletas, que nos vamos de excursión.", + "¿Has tenido un buen viaje?", + "Bonito viaje; te veré el año que viene.", + "¿Qué tal tu viaje?", + "¡Siento que hayas tenido que venir hasta aquí!", + "¡Me parece que vas a irte de viaje!", + "¡Vas a ver lo que es viajar!", + "¿Te gusta la astrología?", + "¿Quieres tener una experiencia astral?", + "¡Vas a ver las estrellas!", + "Prepara las maletas, te vas de viaje... ¡astral!", + ], + 'Quake': ["¿Qué tal una sacudidita de tierra?", + "Me encantan los modelos a escala... Richter.", + "Eres todo un terremoto, ¿eh?", + "¿A que no sabes dónde está el epicentro?", + "Éste se va a salir de la escala Richter.", + "¡Con esto se van a sacudir los cimientos!", + "¡Vas a ver qué meneo!", + "¿Has sufrido alguna vez un terremoto?", + "¡Cuidado; la tierra se agita bajo tus pies!", + ], + 'RazzleDazzle': ["Léeme los labios.", + "¿Te gustan mis dientes?", + "¿No te parezco encantador?", + "Disfruta de mi encantadora sonrisa...", + "Mi dentista es un gran profesional.", + "Una sonrisa cegadora, ¿eh?", + "Parece mentira que sean postizos, ¿eh?", + "Una sonrisa arrebatadora, ¿eh?", + "Suelo anunciar dentífricos, ¿sabes?", + "Siempre uso seda dental después de comer.", + "¡Di \"patata\"!", + ], + 'RedTape': ["Te voy a envolver para regalo.", + "Voy a dejar todo atado y bien atado.", + "¡Cómo te enrollas!", + "A ver si puedes cortar esta cinta roja.", + "Vas a estar un poco estrecho ahí.", + "Espero que no tengas claustrofobia.", + "Me aseguraré de que no vayas a ninguna parte.", + "Espera; te voy a aislar.", + "¡Vamos a inaugurar un nuevo dibu!", + "Quiero que sientas apego por mí.", + ], + 'ReOrg': ["¡No te va a gustar la forma en que he reorganizado todo!", + "Creo que hace falta un poco de reorganización.", + "No estás tan mal, sólo hay que reorganizarte.", + "¿Te gusta mi capacidad de reorganización?", + "He pensado que le iría bien una nueva imagen a todo.", + "¡Hay que reorganizarte!", + "Pareces un poco desorganizado.", + "Espera; voy a reorganizar tus pensamientos.", + "Voy a esperar a que te reorganices un poco.", + "¿Te importa si organizo un poco todo esto?", + ], + 'RestrainingOrder': ["Deberías alejarte un poco.", + "¡Me han encargado que te dé una orden de alejamiento!", + "No te puedes acercar a menos de dos metros de mí.", + "Creo que deberías guardar las distancias.", + "Debería alejarte para siempre.", + "¡Hay que alejar a este dibu!", + "Intenta alejar tu mente de todo esto.", + "Espero que todo esto no te aleje demasiado de la realidad.", + "¡A ver si consigues acercarte ahora!", + "¡Te ordeno que te alejes!", + "¿Por qué no empezamos por alejar posiciones?" + ], + 'Rolodex': ["Tengo tu dirección en algún sitio.", + "Aquí tengo el número de la perrera. ", + "Espera; te voy a dar mi tarjeta.", + "Tengo tu número aquí mismo.", + "Te tengo controlado de la A a la Z.", + "Te tengo más que fichado.", + "¡Esquiva esto!", + "Cuidado; no te cortes con los bordes.", + "Voy a darte unas cuantas direcciones útiles.", + "Toma; llámame a estos números.", + "Quiero asegurarme de que sigamos en contacto.", + ], + 'RubberStamp': ["Me gusta dejar siempre una buena impresión.", + "Es importante aplicar una presión firme.", + "Dejo siempre una buena huella.", + "Te voy a dejar más plano que un sello.", + "Hay que DEVOLVERTE AL REMITENTE.", + "Has sido CANCELADO.", + "Voy a mandarte al dibuparque con sello URGENTE.", + "Creo que te vas a sentir muy RECHAZADO.", + "Para mandarte al dibuparque no hará falta FRANQUEO.", + "Vas a ir al dibuparque POR AVIÓN.", + ], + 'RubOut': ["Y ahora, la desaparición final.", + "Me parece que te he perdido.", + "He decidido que te quedas fuera.", + "Siempre elimino todos los obstáculos.", + "Vaya; voy a borrar este error.", + "Me gusta que todo lo molesto desaparezca.", + "Me gusta que todo esté limpio y ordenado.", + "Por favor, intenta seguir animado.", + "Ahora te veo... Ahora no te veo.", + "Creo que vas a ponerte borroso.", + "Voy a eliminar el problema.", + "Me voy a ocupar de tus áreas problemáticas.", + ], + 'Sacked':["Cuidado; viene el hombre del saco.", + "¡Te tengo en el saco!", + "¿Te apetece echar una carrera de sacos?", + "¿Sacas tú o saco yo?", + "¡Voy a ponerte a buen recaudo!", + "Tengo el récord de carreras de sacos.", + "Te voy a sacar de aquí...", + "¡Se acabó; te voy a meter en el saco!", + "¡No nos metas a todos los bots en el mismo saco!", + "Todos me dicen que tengo un buen saque.", + ], + 'Schmooze':["Seguro que no te esperabas esto.", + "Esto te va a quedar muy bien.", + "¡Te lo has ganado!", + "No quiero aburrirte con mi discurso.", + "Adular a la gente me da buenos resultados.", + "Voy a exagerar un poquito.", + "Es hora de dorar la píldora un poco.", + "Ahora hablemos un poco de ti.", + "Te mereces una palmadita en la espalda.", + "Ha llegado el momento de alabar tu trayectoria.", + "Siento tener que bajarte de tu pedestal, pero...", + ], + 'Shake': ["Estás justo en el epicentro.", + "Estás pisando una falla.", + "Esto va a estar movidito...", + "Creo que va a ocurrir un desastre natural.", + "Es un desastre de proporciones sísmicas.", + "Éste se va a salir de la escala Richter.", + "Es hora de ponerse a cubierto.", + "Pareces alterado.", + "¿Listo para bailar el meneíto?", + "No te agites demasiado, por favor.", + "Esto te va a poner patas arriba.", + "Te recomiendo un buen plan de escape.", + ], + 'Shred': ["Tengo que deshacerme de bastante morralla.", + "Voy a reciclar un poco de papel.", + "Creo que me voy a deshacer de ti ahora mismo.", + "Con esto me desharé de las pruebas.", + "Ya no hay manera de demostrar nada.", + "A ver si consigues recomponer esto.", + "Esto te va a hacer trizas.", + "Voy a triturar esa idea.", + "No quiero que esto caiga en malas manos.", + "Adiós a las pruebas.", + "Creo que ésta era tu última esperanza.", + ], + 'Spin': ["¿Te apetece ir a dar una vuelta?", + "¿Qué tal si te centrifugo un poco?", + "¡Tu cabeza va a dar vueltas con esto!", + "Voy a dar otra vuelta de tuerca.", + "Te voy a dar una vuelta.", + "Yo siempre estoy de vuelta de todo.", + "Cuidado. La vida da muchas vueltas.", + "¡Vamos a hacer un doble giro mortal!", + "Un poco más y de vuelta al dibuparque.", + ], + 'Synergy': ["Voy a presentar esto en el comité.", + "Tu proyecto ha sido cancelado.", + "Hemos cortado tus fondos.", + "Estamos reestructurando tu división.", + "Lo he sometido a votación y has perdido.", + "Acabo de recibir la aprobación final.", + "Un buen equipo puede superar cualquier problema.", + "Luego me ocuparé de esto contigo.", + "Vamos a ir al grano.", + "Considera esto una crisis sinergética.", + ], + 'Tabulate': ["Esto no me cuadra.", + "No te salen las cuentas.", + "Te va a salir una cuenta enorme.", + "Voy a desglosarte en un momento.", + "¿Estás listo para unas cifras de vértigo?", + "Es la hora de que pagues la dolorosa.", + "Hora de saldar cuentas...", + "Me encanta tener las cuentas claras.", + "Las cuentas claras y el chocolate oscuro.", + "Estos números van a dejarte con la boca abierta.", + ], + 'TeeOff': ["Tienes un par nefasto.", + "¡Bola!", + "Vas a ver qué bien le pego.", + "¡Caddie, dame un hierro!", + "A ver si mejoras este golpe.", + "¡Mira qué swing!", + "Ésta bola va a entrar de un solo golpe.", + "Estás pisando mi green.", + "Fíjate qué buen grip tengo.", + "¡Mira qué birdie!", + "¡No pierdas de vista la bola!", + "¿Te importa si juego un poco?", + ], + 'Tremor': ["¿Has notado eso?", + "No te dan miedo los temblores, ¿verdad?", + "Los temblores suelen ser el principio.", + "Pareces tembloroso. ", + "¡Voy a agitar un poco las cosas!", + "¿Estás listo para bailar la rumba?", + "¿Qué te pasa? Pareces agitado.", + "¡Tiembla de miedo!", + "¿Por qué tiemblas de miedo?", + ], + 'Watercooler': ["Esto te refrescará un poco.", + "¿No es refrescante?", + "Venía a entregar un pedido.", + "¡Pruébala; está fresquita!", + "¿Qué pasa? ¡Es sólo agua!", + "No te preocupes; es agua potable.", + "Qué bien; otro cliente satisfecho.", + "Es la hora de entregar el pedido diario.", + "Espero que no destiñas.", + "¿Te apetece un trago?", + "Hay que dar de beber al sediento.", + "¿No tenías sed? ¡Pues toma!", + ], + 'Withdrawal': ["Creo que te has quedado sin fondos", + "Espero que tengas suficientes fondos en tu cuenta.", + "¡Toma ya, con intereses!", + "Te estás quedando sin liquidez.", + "Pronto vas a tener que hacer un ingreso.", + "Estás al borde del colapso económico.", + "Creo que estás en recesión.", + "Tus finanzas se van a ver afectadas.", + "Preveo una crisis inminente.", + "¡Vas a tener un agujero en tu cuenta!", + ], + 'WriteOff': ["Permíteme que incremente tus deudas.", + "Vamos a intentar sanear tu situación.", + "Es hora de hacer el balance de tus cuentas.", + "Esto no va a quedar nada bien en tu libro de contabilidad.", + "Estoy buscando dividendos.", + "¿Por qué no haces balance de tus pérdidas?", + "Olvídate de la gratificación que te toca.", + "Voy a poner patas arriba tus cuentas.", + "Creo que tus pérdidas van a ser cuantiosas.", + "Tu saldo se va a ver un poco afectado.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Esperando a otros jugadores...", + +# Elevator.py +ElevatorHopOff = "Bajarse" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Inc." +CogsIncModifier = "%s" + CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "¿Quién es?" +DoorWhoAppendix = " qué?" +DoorNametag = "Door" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "¡Tù necesitas bromas! Anda a hablar con Tato Tutorial" +FADoorCodes_DEFEAT_FLUNKY_HQ = "¡Ven devuelta cuando hayas derrotado al Secuaz!" +FADoorCodes_TALK_TO_HQ = "¡Anda y obten tu recompense del funcionario Enrique!" +FADoorCodes_WRONG_DOOR_HQ = "¡Puerta incorrecta! ¡Sal por la otra puerta al Dibuparque!" +FADoorCodes_GO_TO_PLAYGROUND = "¡Salida equivocada! ¡Tù necesitas salir al Dibuparque!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "¡Asércate al Secuaz para combatir con él!" +FADoorCodes_TALK_TO_HQ_TOM = "Ve y obtén tu recompense en el Cuartel General Dibu!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "¡Cuidado! ¡Adentro está un Bot!" +FADoorCodes_DISGUISE_INCOMPLETE = "Si vas de dibu te van a pillar. Reúne primero un disfraz de bot completo.\n\nHáztelo con piezas de la fábrica." + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Aitor ", + "¡Aitor Tilla de Patatas!"], + + ["Adela", + "¡Carmelo Cotonenalmíbar!"], + + ["Abraham", + "¡Abraham Lapuerta!"], + + ["Amira", + "¡Amira Quienhavenido!"], + + ["Aquiles", + "¡Aquiles Dejoporhoy!"], + + ["Archibaldo", + "¡Archibaldo Enlascarpetas!"], + + ["Armando", + "¡Armando Adistancia!"], + + ["Ariel", + "¡Ariel Lavamasblanco!"], + + ["Augusto", + "¡Augusto de Conocerle!"], + + ["Azucena", + "¡Azucena Estaservida!"], + + ["Baldomero", + "¡Baldomero Alaplancha!"], + + ["Baltasar", + "¡Baltasar Ysecayó!"], + + ["Belinda", + "¡Belinda Flordeljardín!"], + + ["Beltrán", + "¡Beltrán Chetes de Quesito!"], + + ["Bernabé", + "¡Bernabé Abrirlapuerta!"], + + ["Blasa", + "¿Blasa Brironó?"], + + ["Carmen", + "¡Carmen Tolada y Fresca!"], + + ["Calixto", + "¡Calixto Queesuno!"], + + ["Camila", + "¡Camila Groquehayavenido!"], + + ["Cintia", + "¡Cintia Palpelo!"], + + ["Clemente", + "¡Clemente Claraydespejada!"], + + ["Cleopatra", + "¡Cleopatra Ficoenhorapunta!"], + + ["Clotilde", + "¡Clotilde Alfinal!"], + + ["Consuelo", + "¡Consuelo Recienfregado!"], + + ["Crisóstomo", + "¡Crisóstomo Uncafeconleche!"], + + ["Demetrio", + "¡Demetrio Doloquetenga!"], + + ["Bernabé", + "¡Bernabé Averquienés!"], + + ["Edmundo", + "¡Edmundo Esunpañuelo!"], + + ["Engracia", + "¡Engracia Porabrirlapuerta!"], + + ["Estela", + "¡Estela Marinera!"], + + ["Eugenio", + "¡Eugenio de la Lámpara!"], + + ["Ezequiel", + "¿Ezequiel Es?"], + + ["Fabiola", + "¡Fabiola Iadiós!"], + + ["Filomeno", + "Filomeno Malquehellegado."], + + ["Florinda", + "¡Florinda Se y Bajelasmanos!"], + + ["Gaspar", + "¡Gaspar Ecequevallover!"], + + ["Genoveva", + "¡Genoveva Desabotella!"], + + ["Jairo", + "¡Jairo Niasdelavida!"], + + ["Jazmín", + "¡Jazmín Nero del Carbón!"], + + ["Jeremías", + "¡Jeremías Sonpasiempre!"], + + ["Jessica", + "¡Jessica Nálisis Gratuito!"], + + ["Jesús", + "¡Jesús Piros de España!"], + + ["Joaquín", + "¿Joaquín Havenidoestavez?"], + + ["Kevin", + "¿Kevin Olesirvo?"], + + ["Leonor", + "¡Leonor Abuena Porelpremio!"], + + ["Mabel", + "¡Mabel Siabresdunavez!"], + + ["Macarena", + "¡Macarena Blanca de la Playa!"], + + ["Magdalena", + "¡Magdalena Ycafé Conleche!"], + + ["Maite", + "¡Maite Digoquesoyyó!"], + + ["Marcos", + "¡Marcos Sacos del Don!"], + + ["Armando", + "¡Armando de la Tropa!"], + + ["Olimpia", + "¡Olimpia Fijaydaesplendor!"], + + ["Olivia", + "¡Olivia Ductoromano!"], + + ["Omar", + "¡Omar Ejadilla del Cantábrico!"], + + ["Óscar", + "¡Óscar Naval de Tenerife!"], + + ["Pánfilo", + "¡Pánfilo de la Navaja!"], + + ["Pascual", + "¿Pascual Esudireción?"], + + ["Pura", + "¡Pura Suertehaberloencontrado!"], + + ["Ramiro", + "¡Raimiro Ynoteveo!"], + + ["Ramona", + "¡Ramona Vestida de Seda!"], + + ["Raúl", + "¡Raúl Timo de la Fila!"], + + ["Renato", + "¡Renato Sigadoportodos!"], + + ["Juan Ricardo", + "¡Juan Ricardo Borriquero!"], + + ["Rubén", + "¡Rubén Averloquemehecomprado!"], + + ["Sabina", + "¡Sabina Questariasaquí!"], + + ["Samanta", + "¡Samanta de Lanaquehacefrío!"], + + ["Sandra", + "¡Sandra Josa!"], + + ["Serafín", + "¡Serafín de la Historia!"], + + ["Serena", + "¡Serena y Tranquila!"], + + ["Servando", + "¡Servando Lero de Sierra Morena!"], + + ["Silvestre", + "¡Silvestre Senunburro!"], + + ["Silvio", + "¿Silvio Ustedamiperro?"], + + ["Sixta", + "¡Sixta Vezquevengoaquí!"], + + ["Socorro", + "¡Socorro Auxílio!"], + + ["Sol", + "¡Sol Oquieropasar!"], + + ["Soledad", + "¡Soledad de Tenertentusbrazos!"], + + ["Tadeo", + "¡Tadeo Graciasadiós!"], + + ["Tamara", + "¡Tamara Casdemachín!"], + + ["Teobaldo", + "¡Teobaldo Sas de Cerámica!"], + + ["Ulrico", + "¡Ulrico Helado, oiga!"], + + ["Virgilio", + "¡Virgilío Mehasmetido!"], + + ["Vladimir", + "¡Vladimir Aquienhavenido!"], + + ["Wenceslao", + "¡Wenceslao de Vainilla!"], + + ["Wenceslao", + "¡Wenceslao de Chocolate!"], + + ["Amira", + "¡Amira Quiensoy!"], + + ["Saúl", + "¡Saúl Timaoportunidadqueledoy!"], + + ["Noé", + "¡Noé Nadieconocido!"], + + ["Noé", + "¿Noé Verdadquemesperabas?"], + + ["Nadia", + "¡Nadia Importante, la verdad!"], + + ["Otto", + "¡Otto Quenomeconoce!"], + + ["Abel", + "¡Abel Quienés!"], + + ["Alcira", + "¡Alcira Esapuertaquehacefrío!"], + + ["Apolo", + "¡Apolo de Naranja y Limón!"], + + ["Carmelo", + "¿Carmelo Dicesomelocuentas?"], + + ["Padmé", + "¿Padmé Dicequehoraes?"], + + ["Eleazar", + "¡Eleazar Aleatóriez!"], + + ["Elijah", + "¡Elijah Elquemasleguste!"], + + ["Elmer", + "¡Elmer Cader de Venecia!"], + + ["Elpida", + "¡Elpida Loqueleapetezca!"], + + ["Quique", + "¡Quique Cosasdice, oiga!"], + + ["Euridice", + "¡Euridice Quelellames!"], + + ["Calvino", + "¡Calvino Tinto de Verano!"], + + ["Mercedes", + "¡Mercedes Elpaso!"], + + ["Mercurio", + "¡Mercurio Soquelopregunte!"], + + ["Merlin", + "¡Merlin Dacasatieneusted!"], + + ["Minerva", + "¡Minerva Minfada y Mixaspera!"], + + ["Miranda", + "¡Miranda Yvetedeunavez!"], + + ["Morgana", + "¡Morgana Tengodefastidiar!"], + + ["Morfeo", + "¡Morfeo Quepegarleaunpadre!"], + + ["Pancho", + "¡Pancho Colate Blanco!"], + + ["Parker", + "¡Parker Levoyacontar!"], + + ["Pasha", + "¿Pasha Contigotío?"], + + ["Patty", + "Patty Todo Noquieronada."], + + ["Stan", + "¡Stan Todosdetenidos!"], + + ["Pierre", + "¡Pierre Queerre!"], + + ["Ida", + "¡Ida Yvuelta!"], + + ["Savannah", + "¡Savannah de Lino Blanco!"], + + ["Renata", + "¡Renata Conchocolate!"], +] + +# ChatInputNormal.py +ChatInputNormalSayIt = "Decírselo" +ChatInputNormalCancel = "Cancelar" +ChatInputNormalWhisper = "Susurrar" +ChatInputWhisperLabel = "A %s" + +# ChatInputSpeedChat.py +SCEmoteNoAccessMsg = "Todavía no tienes acceso\n a este emoticono." +SCEmoteNoAccessOK = "Aceptar" + +# ChatManager.py +ChatManagerChat = "Charla" +ChatManagerWhisperTo = "Susurrar a:" +ChatManagerWhisperToName = "Susurrar a:\n%s" +ChatManagerCancel = "Cancelar" +ChatManagerWhisperOffline = "%s está desconectado." +OpenChatWarning = '¡Todavía no tienes "amigos secretos"! No puedes conversar con otros dibus a menos que sean tus amigos secretos.\n\nPara convertirte en amigo secreto de alguien, haz clic en él y selecciona "Secretos" en el panel de detalles. No hace falta decir que siempre puedes hablar con quien quieras por medio de la Charla rápida.' +OpenChatWarningOK = "Aceptar" +UnpaidChatWarning = 'Cuando te hayas suscrito, podrás usar este botón para charlar con tus amigos mediante el teclado. Hasta entonces, deberías usar la herramienta Charla rápida para conversar con los demás dibus.' +UnpaidChatWarningPay = "¡Suscríbete ya!" +UnpaidChatWarningContinue = "Continuar prueba gratuita" +#PaidNoParentPasswordWarning = 'Una vez creada la contraseña parental, podrás activar este botón para charlar con tus amigos mediante el teclado. Hasta entonces, puedes usar la herramienta Charla rápida para conversar con los demás dibus. +#PaidNoParentPasswordWarningSet = "Crear la contraseña parental ahora" +#PaidNoParentPasswordWarningContinue = "Seguir jugando" +NoSecretChatAtAllTitle = "Charla de Amigos secretos" +NoSecretChatAtAll = 'Para charlar con un amigo, la herramienta Amigos secretos debe estar activada. La herramienta Amigos secretos permite a un miembro charlar con otro gracias al uso de un código secreto que se debe comunicar fuera del juego.\n\nPara activar esta herramienta sal de Toontown y actívala a través de Los Controles Parentales en Conecta Disney' +NoSecretChatAtAllOK = "Aceptar" +NoSecretChatWarningTitle = "Controles parentales" +NoSecretChatWarning = 'Para que sea posible charlar con un amigo, la herramienta Amigos secretos debe estar activada. Para saber más cosas sobre la herramienta Amigos secretos y acceder a los controles parentales, diles a tus padres que abran la sesión con su contraseña parental.' +NoSecretChatWarningOK = "Aceptar" +NoSecretChatWarningCancel = "Cancelar" +NoSecretChatWarningWrongPassword = 'Esa contraseña no es correcta. Introduce la contraseña parental que se creó al adquirir esta cuenta. No se trata de la misma contraseña que se emplea para jugar al juego.' + +ActivateChat = """La herramienta Amigos secretos permite a los socios charlar entre sí gracias al uso de un código secreto que se debe comunicar fuera del juego. Para obtener toda la información, haz clic aquí: + +La herramienta Amigos secretos no está moderada ni supervisada. Si los padres permiten a sus hijos usar su cuenta con la opción Amigos secretos activada, les aconsejamos que los supervisen mientras juegan. Una vez activada, la herramienta Amigos secretos está disponible hasta que se desactiva. + +Al activar la herramienta Amigos secretos, los padres reconocen que existen ciertos riesgos inherentes a la posibilidad de charla de la herramienta y reconocen que han sido informados sobre dichos riesgos y están de acuerdo en aceptarlos.""" + +ActivateChatYes = "Activar" +ActivateChatNo = "Cancelar" +ActivateChatMoreInfo = "Más información" +ActivateChatPrivacyPolicy = "Normas de confidencialidad" + +LeaveToPay = """Para adquirir Toontown, el producto irá a Toontown.com.""" +LeaveToPayYes = "Adquirir" +LeaveToPayNo = "Cancelar" + +#LeaveToSetParentPassword = """In order to set parent password, the game will exit to Toontown.com.""" +#LeaveToSetParentPasswordYes = "Set Password" +#LeaveToSetParentPasswordNo = "Cancel" + +ChatMoreInfoOK = "Aceptar" +SecretChatActivated = '¡La herramienta "Amigos secretos" ha sido activada!\n\nSi más tarde cambias de opinión y decides desactivar esta herramienta, haz clic en "Opciones de cuenta" en la página web de Toontown.' +SecretChatActivatedOK = "Aceptar" +ProblemActivatingChat = '¡Vaya! No hemos podido activar la herramienta de charla "Amigos secretos".\n\n%s\n\nVuelve a intentarlo más tarde.' +ProblemActivatingChatOK = "Aceptar" + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "¡Hola, %!", + "Eh, %, me alegro de verte.", + "¡Me alegro de que hayas venido hoy!", + "¿Cómo estás, %?", + ] + +SharedChatterComments = [ + "Es un nombre estupendo, %.", + "Me gusta tu nombre.", + "Cuidado con los bots.", + "¡Parece que llega el tranvía!", + "¡Tengo que subir al tranvía para jugar y conseguir tartas!", + "A veces me subo al tranvía sólo para conseguir la tarta de frutas.", + "Guau, acabo de deshacerme de un montón de bots. ¡Necesito descansar un poco!", + "¡Vaya, hay algunos bots que son muy grandotes!", + "Parece que te lo pasas pipa.", + "Caramba, qué buen día estoy teniendo.", + "Me gusta lo que llevas puesto.", + "Creo que esta tarde me voy a ir de pesca.", + "Diviértete en mi barrio.", + "¡Espero que te lo estés pasando en grande en Toontown!", + "Me han dicho que en Frescolandia está nevando.", + "¿Has subido hoy al tranvía?", + "Me gustaría conocer a más gente.", + "Caramba, en Frescolandia hay un montón de bots.", + "Me encanta jugar al \"Tú la llevas\". ¿Y a ti?", + "Los juegos del tranvía son divertidísimos.", + "Me encanta hacer que la gente se ría.", + "Ayudar a los amigos es muy divertido.", + "Ejem, ¿te has perdido? No olvides que tienes un mapa en el dibucuaderno.", + "¡Que no te líen los bots con su cinta roja!", + "Me han dicho que Daisy ha plantado flores nuevas en su jardín.", + "¡Para mirar hacia arriba, mantén pulsada la tecla Re Pág!", + "¡Si ayudas a reconquistar los edificios bot, podrás ganar una estrella de bronce!", + "Si pulsas la tecla Tab, podrás contemplar diferentes vistas de los alrededores.", + "¡Si pulsas la tecla Ctrl, podrás saltar!", + ] + +SharedChatterGoodbyes = [ + "Me tengo que ir; adiós.", + "Creo que voy a subir al tranvía para jugar.", + "Bueno, hasta otra. Te veo luego, %.", + "Más me vale darme prisa para acabar con los bots.", + "Es la hora de ponerse en marcha.", + "Lo siento, pero tengo que irme.", + "Adiós.", + "¡Hasta luego, %!", + "Creo que voy a practicar el lanzamiento de magdalenas.", + "Voy a unirme a un grupo para acabar con unos cuantos bots.", + "Me alegro de haberte visto hoy, %.", + "Hoy tengo mucho que hacer. Voy a ponerme en marcha.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bienvenido al Centro de Toontown.", + "Hola, me llamo Mickey. ¿Cómo te llamas?", + ], + [ # Comments + "Eh, ¿has visto a Donald?", + "Voy a ver cómo sube la marea en Puerto Donald.", + "Si ves a mi amiguete Goofy, dale recuerdos de mi parte.", + "Me han dicho que Daisy ha plantado flores nuevas en su jardín.", + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a Minnie!", + "¡Dios mío, llego tarde a mi cita con Minnie!", + "Parece que es la hora de la cena de Pluto.", + "Creo que voy a Puerto Donald a nadar un poco.", + "Es la hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bienvenido a Melodilandia.", + "Hola, me llamo Minnie. ¿Cómo te llamas?", + ], + [ # Comments + "¡La música se siente por todas partes!", + "No te olvides de montarte en el gran tiovivo.", + "Llevas un disfraz muy chulo, %.", + "Eh, ¿has visto a Mickey?", + "Si ves a mi amigo Goofy, dale recuerdos de mi parte.", + "Caramba, en Sueñolandia de Donald hay un montón de bots.", + "Me han dicho que en Puerto Donald hay niebla.", + "No te olvides de probar el laberinto de los Jardines de Daisy.", + "Creo que voy a escuchar música.", + "Eh, %, mira eso.", + "Me encanta la música.", + "¿A que no sabías que a Melodilandia también la llaman Cancioncity? ¡Ji, ji!", + "Me encanta jugar al juego de imitar movimientos. ¿Y a ti?", + "Me encanta hacer reír a la gente.", + "¡Uf, andar todo el día con tacones acaba haciendo daño a los pies!", + "Qué camisa más bonita, %.", + "¿Eso del suelo es una gominola?", + ], + [ # Goodbyes + "¡Vaya, llego tarde a mi cita con %s!" % Mickey, + "Parece que es la hora de la cena de %s." % Pluto, + "Es la hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bienvenido a los Jardines de Daisy.", + "Hola, me llamo Goofy. ¿Y tú?", + "¡Hola, me alegro de verte, %!", + ], + [ # Comments + "¡Vaya! ¡Cualquiera se pierde en el laberinto del jardín!", + "No te olvides de ir al laberinto.", + "No he visto a Daisy en todo el día.", + "¿Dónde estará Daisy?", + "Eh, ¿has visto a Donald?", + "Si ves a mi amigo Mickey, dale recuerdos de mi parte.", + "¡Oh! ¡Me he olvidado de prepararle el desayuno a Mickey!", + "Guau, seguro que en Puerto Donald hay un montón de bots.", + "Parece ser que Daisy ha plantado flores nuevas en su jardín.", + "¡Las gafas hipnóticas están rebajadas a una gominola en mi tienda de bromas de Frescolandia!", + "¡Las tiendas de bromas de Goofy tienen las mejores bromas, trucos y gansadas de todo Toontown!", + "En las tiendas de bromas de Goofy garantizamos que todas las tartas en la cara te harán reír. ¡Si no es así, te devolvemos tus gominolas!" + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a Minnie!", + "¡Vaya, llego tarde a mi cita con Donald!", + "Creo que voy a Puerto Donald a nadar un poco.", + "Es la hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bienvenido a Sueñolandia.", + "Hola, me llamo %s. ¿Y tú?" % Donald, + ], + [ # Comments + "A veces, este sitio me da escalofríos.", + "No te olvides de probar el laberinto de los Jardines de Daisy.", + "Caramba, qué buen día estoy teniendo.", + "Eh, ¿has visto a Mickey?", + "Si ves a mi buen amigo Goofy, dale recuerdos de mi parte.", + "Creo que esta tarde me voy a ir de pesca.", + "Caramba, en Puerto Donald hay un montón de bots.", + "Eh, ¿no te he llevado en barco en Puerto Donald?", + "No he visto a Daisy en todo el día.", + "Me han dicho que "+Daisy+" ha plantado flores nuevas en su jardín.", + "Cuac.", + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a %s!" % Minnie, + "¡Vaya! ¡Llego tarde a mi cita con %s!" % Daisy, + "Creo que voy a mi puerto a nadar un poco.", + "Creo que voy a darme una vuelta en mi barco en Puerto Donald.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + + +# ToontownClientRepository.py +TCRConnecting = "Conectando..." +# host, port +TCRNoConnectTryAgain = "Imposible conectar con %s:%s. ¿Quieres intentarlo de nuevo?" +TCRNoConnectProxyNoPort = "Imposible conectar con %s:%s.\n\nTe estás conectando al Internet a través de un proxy que no permite las conexiones al puerto %s.\n\nPara jugar a Toontown es necesario abrir este puerto o desactivar el proxy. Si el proxy ha sido suministrado por tu proveedor de Internet, debes ponerte en contacto con él para que abra este puerto." +TCRNoDistrictsTryAgain = "No hay distritos de Toontown disponibles. ¿Deseas intentarlo de nuevo?" +TCRLostConnection = "Tu conexión de Internet a Toontown se ha interrumpido inesperadamente." +TCRBootedReasons = { + 1: "Se ha producido un problema inesperado. Se ha perdido la conexión, pero deberías poder conectarte de nuevo y volver al juego.", + 100: "Has sido desconectado porque otra persona que ha abierto una sesión con tu cuenta en otro ordenador.", + 120: "Has sido desconectado debido a un problema con tu autorización para usar la charla mediante el teclado.", + 122: "Se ha producido un problema inesperado en la conexión con Toontown. Ponte en contacto con el Servicio de atención al cliente de Toontown.", + 125: "Tus archivos instalados de Toontown parecen ser incorrectos. Pulsa el botón Jugar de la página web oficial de Toontown para ejecutar el juego.", + 126: "No tienes privilegios de administrador.", + 151: "Tu sesión ha sido cerrada por un administrador de los servidores de Toontown.", + 153: "Se ha reiniciado el distrito de Toontown en el que te hallabas. Todos los que estaban en ese distrito han sido desconectados. Sin embargo, deberías poder conectarte de nuevo para volver al juego.", + 288: "Lo sentimos, pero has gastado todos los minutos de que disponías en Toontown este mes.", + 349: "Lo sentimos, pero has gastado todos los minutos de que disponías en Toontown este mes.", + } +TCRBootedReasonUnknownCode = "Ha surgido un problema inesperado (código de error %s). Se ha perdido la conexión, pero deberías poder conectarte de nuevo y volver al juego." +TCRTryConnectAgain = "\n\n¿Quieres intentar conectarte de nuevo?" +# avName +TCRTutorialAckQuestion = "%s es nuevo en Toontown.\n\n¿Quieres que Mickey te lo enseñe?" +TCRTutorialAckOk = "Sí" +TCRTutorialAckCancel = "No" +TCRToontownUnavailable = "Toontown no parece estar disponible por el momento; seguimos intentándolo..." +TCRToontownUnavailableCancel = "Cancelar" +TCRNameCongratulations = "¡¡ENHORABUENA!!" +TCRNameAccepted = "Tu nombre ha sido\naprobado por el Consejo Dibu.\n\nA partir de ahora,\nte llamarás\n\"%s\"" +TCRServerConstantsProxyNoPort = "Imposible ponerse en contacto con %s.\n\nTe estás conectando al Internet a través de un proxy que no permite conexiones al puerto %s.\n\nPara jugar a Toontown es necesario abrir este puerto o desactivar el proxy. Si el proxy ha sido suministrado por tu proveedor de Internet, debes ponerte en contacto con él para que abra este puerto." +TCRServerConstantsProxyNoCONNECT = "Imposible ponerse en contacto con %s.\n\nTe estás conectando al Internet a través de un proxy que no es compatible con el método CONNECT.\n\nPara jugar a Toontown es necesario activar esta opción o desactivar el proxy. Si el proxy ha sido suministrado por tu proveedor de Internet, debes ponerte en contacto con él para activar esta opción." +TCRServerConstantsTryAgain = "Imposible ponerse en contacto con %s.\n\nEl servidor de la cuenta de Toontown podría estar inoperativo en este momento, o puede ser que haya surgido un problema con tu conexión de Internet.\n\n¿Deseas intentarlo de nuevo?" +TCRServerDateTryAgain = "Imposible obtener la fecha del servidor de %s. ¿Deseas intentarlo de nuevo?" +AfkForceAcknowledgeMessage = "A tu dibu le ha entrado sueño y se ha ido a la cama." +PeriodTimerWarning = "¡Tu límite de tiempo en Toontown este mes casi ha terminado!" +PeriodForceAcknowledgeMessage = "Has gastado todos los minutos de que disponías en Toontown este mes. ¡Ven otra vez a jugar el mes que viene!" +TCREnteringToontown = "Entrando a Toontown..." + +# FriendInvitee.py +FriendInviteeTooManyFriends = "%s desea ser tu amigo, pero ya tienes demasiados amigos en tu lista." +FriendInviteeInvitation = "A %s le gustaría ser tu amigo." +FriendInviteeOK = "Aceptar" +FriendInviteeNo = "No" + +# FriendInviter.py +FriendInviterOK = "Aceptar" +FriendInviterCancel = "Cancelar" +FriendInviterStopBeingFriends = "Dejar de ser amigo" +FriendInviterYes = "Sí" +FriendInviterNo = "No" +FriendInviterClickToon = "Haz clic en el dibu del que deseas ser amigo." +FriendInviterTooMany = "No puedes añadir más amigos a tu lista porque ya tienes demasiados. Si quieres ser amigo de %s, tendrás que quitar algunos amigos de tu lista." +FriendInviterNotYet = "¿Quieres ser amigo de %s?" +FriendInviterCheckAvailability = "Comprobando si %s está disponible." +FriendInviterNotAvailable = "%s está ocupado ahora mismo. Inténtalo más tarde." +FriendInviterWentAway = "%s se ha marchado." +FriendInviterAlready = "%s ya es tu amigo." +FriendInviterAskingCog = "Preguntando a %s si quiere ser tu amigo." +FriendInviterEndFriendship = "¿Seguro que quieres dejar de ser amigo de %s?" +FriendInviterFriendsNoMore = "%s ya no es tu amigo." +FriendInviterSelf = "¡ Ya eres tu propio amigo!" +FriendInviterIgnored = "%s no te está haciendo caso." +FriendInviterAsking = "Preguntando a %s si quiere ser tu amigo." +FriendInviterFriendSaidYes = "¡%s ha dicho que sí!" +FriendInviterFriendSaidNo = "%s ha dicho que no, gracias." +FriendInviterFriendSaidNoNewFriends = "%s no quiere tener amigos nuevos ahora mismo." +FriendInviterTooMany = "¡%s ya tiene demasiados amigos!" +FriendInviterMaybe = "%s no ha podido responder." +FriendInviterDown = "Imposible hacer amigos ahora." + +# FriendSecret.py +FriendSecretNeedsPasswordWarningTitle = "Parental Controls" +FriendSecretNeedsPasswordWarning = """To get or type a secret, you must enter the Parent Password. You can disable this prompt in Member Services on the Toontown web page.""" +FriendSecretNeedsPasswordWarningOK = "OK" +FriendSecretNeedsPasswordWarningCancel = "Cancel" +FriendSecretNeedsPasswordWarningWrongPassword = """That's not the correct password. Please enter the Parent Password created when purchasing this account. This is not the same password used to play the game.""" +FriendSecretIntro = "Si estás jugando a Disney's Toontown Online con alguien que conozcas en la vida real, los dos pueden convertirse en amigos secretos. Puedes charlar con tus amigos secretos usando el teclado. Los demás dibus no entenderán lo que estás diciendo.\n\nPara hacer esto, necesitas obtener un Secreto. Transmítele el Secreto a tu amigo y a nadie más. Cuando tu amigo escriba el Secreto en su pantalla, los dos seran amigos secretos en Toontown." +FriendSecretGetSecret = "Obtener Secreto" +FriendSecretEnterSecret = "Si tienes un Secreto de alguien que conozcas, escríbelo aquí." +FriendSecretOK = "Aceptar" +FriendSecretCancel = "Cancelar" +FriendSecretGettingSecret = "Obteniendo Secreto. . ." +FriendSecretGotSecret = "Aquí tienes tu nuevo Secreto. ¡No te olvides de anotarlo!\n\nPuedes dar este Secreto solamente a una persona. Cuando alguien escriba tu Secreto, éste no valdrá ya para nadie más. Si deseas dar un Secreto a alguien más, tienes que pedir otro.\n\nEl Secreto sólo valdrá durante los dos días siguientes. Tu amigo tendrá que escribirlo antes de que desaparezca, o de lo contrario el proceso no funcionará.\n\nTu Secreto es:" +FriendSecretTooMany = "Lo siento, no puedes tener más Secretos hoy. ¡Ya has tenido más que suficientes!\n\nPrueba de nuevo mañana." +FriendSecretTryingSecret = "Probando Secreto. . ." +FriendSecretEnteredSecretSuccess = "¡Ya eres amigo secreto de %s!" +FriendSecretEnteredSecretUnknown = "Ése no es el Secreto de nadie. ¿Seguro que lo has escrito bien?\n\nSi lo has escrito correctamente, tal vez haya caducado. Pídele a tu amigo que te obtenga un Secreto nuevo (o consigue tú uno y dáselo a tu amigo)." +FriendSecretEnteredSecretFull = "No puedes ser amigo de %s porque uno de los dos tiene demasiados amigos en la lista." +FriendSecretEnteredSecretFullNoName = "No pueden ser amigos porque uno de los dos tiene demasiados amigos en la lista." +FriendSecretEnteredSecretSelf = "¡Acabas de escribir tu propio Secreto! Ahora, nadie más puede usar ese Secreto." +FriendSecretNowFriends = "¡Ya eres amigo secreto de %s!" +FriendSecretNowFriendsNoName = "¡Ya sois amigos secretos!" + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Amigo nuevo" +FriendsListPanelSecrets = "Secretos" +FriendsListPanelOnlineFriends = "AMIGOS\nCONECTADOS" +FriendsListPanelAllFriends = "TODOS LOS\nAMIGOS" +FriendsListPanelIgnoredFriends = "DIBUS NO\nATENDIDOS" +FriendsListPanelPets = "NEARBY\nPETS" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Lo siento; no puedes avanzar porque la descarga de %(phase)s sólo lleva completada un %(percent)s%%.\n\nVuelve a intentarlo más tarde." + +# TeaserPanel.py +TeaserTop = " Sorry, but you can't do that in the free trial.\n\nSubscribe now and enjoy these great features:" +TeaserOtherHoods = "Visit all 6 unique neighborhoods!" +TeaserTypeAName = "Type in your favorite name for your toon!" +TeaserSixToons = "Create up to 6 Toons on one account!" +TeaserOtherGags = "Collect 6 skill levels in 6 different gag tracks!" +TeaserClothing = "Buy unique clothing items to individualize your toon!" +TeaserFurniture = "Purchase and arrange furniture in your own house!" +TeaserCogHQ = "Infiltrate dangerous advanced Cog areas!" +TeaserSecretChat = "Trade secrets with friends so you can chat with them online!" +TeaserCardsAndPosters = "Collect Toontown trading cards and posters!" +TeaserHolidays = "Participate in exciting special events and holiday celebrations!" +TeaserQuests = "Complete hundreds of ToonTasks to help save Toontown!" +TeaserEmotions = "Purchase emotions to make your Toon more expressive!" +TeaserMinigames = "Play all 8 minigame varieties!" +TeaserSubscribe = "Subscribe Now" +TeaserContinue = "Continue Trial" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Descargando %s." +DownloadWatcherInitializing = "Iniciando descarga..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Inicializando", + 3 : "Crear un dibu", + 3.5 : "Dibututorial", + 4 : "Dibuparque", + 5 : "Calles", + 5.5 : "Propiedades", + 6 : "Barrio I", + 7 : "Edificios " + Cog, + 8 : "Barrio II", + 9 : "Cuartel general" + Cog, + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s de %(total)s)" +LauncherStartingMessage = "Iniciando Disney's Toontown Online... " +LauncherDownloadFile = "Descargando actualización para " + LauncherProgress + "..." +LauncherDownloadFileBytes = "Descargando actualización para " + LauncherProgress + ": %(bytes)s" +LauncherDownloadFilePercent = "Descargando actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherDecompressingFile = "Descomprimiendo actualización para " + LauncherProgress + "..." +LauncherDecompressingPercent = "Descomprimiendo actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherExtractingFile = "Extrayendo actualización para " + LauncherProgress + "..." +LauncherExtractingPercent = "Extrayendo actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherPatchingFile = "Aplicando actualización para " + LauncherProgress + "..." +LauncherPatchingPercent = "Aplicando actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherConnectProxyAttempt = "Conectando con Toontown: %s (proxy: %s) intento: %s" +LauncherConnectAttempt = "Conectando con Toontown: %s intento %s" +LauncherDownloadServerFileList = "Actualizando Toontown..." +LauncherCreatingDownloadDb = "Actualizando Toontown..." +LauncherDownloadClientFileList = "Actualizando Toontown..." +LauncherFinishedDownloadDb = "Actualizando Toontown... " +LauncherStartingToontown = "Iniciando Toontown..." +LauncherRecoverFiles = "Actualizando Toontown. Recuperando archivos..." +LauncherCheckUpdates = "Comprobando actualizaciones para " + LauncherProgress +LauncherVerifyPhase = "Actualizando Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Crea un\ndibu" +AvatarChoicePlayThisToon = "Juega con\neste dibu" +AvatarChoiceSubscribersOnly = "Subscribe\n\n\n\nNow!" +AvatarChoiceDelete = "Borrar" +AvatarChoiceDeleteConfirm = "Con esto borrarás a %s para siempre." +AvatarChoiceNameRejected = "Nombre\nrechazado" +AvatarChoiceNameApproved = "¡Nombre\naprobado!" +AvatarChoiceNameReview = "En proceso\nde revisión" +AvatarChoiceNameYourToon = "¡Pon un nombre\na tu dibu!" +AvatarChoiceDeletePasswordText = "¡Cuidado! Con esto borrarás a %s para siempre. Para borrar este dibu, escribe tu contraseña." +AvatarChoiceDeleteConfirmText = "¡Cuidado! Con esto borrarás a %(name)s para siempre. Para confirmar escribe \"%(confirm)s\" y hace click en Aceptar." +AvatarChoiceDeleteConfirmUserTypes = "Borrar" +AvatarChoiceDeletePasswordTitle = "¿Quieres borrar este dibu?" +AvatarChoicePassword = "Contraseña" +AvatarChoiceDeletePasswordOK = "Aceptar" +AvatarChoiceDeletePasswordCancel = "Cancelar" +AvatarChoiceDeleteWrongPassword = "Esa contraseña no coincide. Para borrar este dibu, escribe tu contraseña." +AvatarChoiceDeleteWrongConfirm = "Tú no has escrito la palabra correcta. Para borrar a %(name)s, escribe \"%(confirm)s\" y hace click en Aceptar. No escribas las apóstrofes . Hace click en Cancelar, si has cambiado de parecer." + +# AvatarChooser.py +AvatarChooserPickAToon = "Escoge el dibu con el que vas a jugar" +AvatarChooserQuit = "Salir" + +# MultiPageTextFrame.py +MultiPageTextFrameNext = 'Siguiente' +MultiPageTextFramePrev = 'Anterior' +MultiPageTextFramePage = 'Página %s/%s' + +# MemberAgreementScreen.py +MemberAgreementScreenTitle = 'Contrato de registro' +MemberAgreementScreenAgree = 'Acepto' +MemberAgreementScreenDisagree = 'No acepto' +MemberAgreementScreenCancel = 'Cancelar' +MemberAgreementScreenWelcome = "¡Bienvenido!" +MemberAgreementScreenOnYourWay = "Se ha iniciado el proceso para que te conviertas en un socio oficial de" +MemberAgreementScreenToontown = "Avance preliminar de Disney's Toontown Online" +MemberAgreementScreenPricing = "El avance preliminar de Disney's Toontown Online cuesta \nel primer mes. Cada mes adicional es .\nY el registro es fácil: basta con leer y rellenar la \ninformación que aparece a continuación y ya está." +MemberAgreementScreenCCUpFrontPricing = "Inscríbete ya en la prueba GRATUITA de días. Puedes cancelar tu suscripción\nen cualquier momento del período gratuito sin coste alguno. Al final del\nperíodo gratuito de prueba, se te facturarán automáticamente por\nel primer mes, y después, por cada mes adicional." +MemberAgreementScreenGetParents = "Debes tener al menos 18 años para adquirir Disney's Toontown Online. Pide a tus padres o tutores que te ayuden." +MemberAgreementScreenGetParentsUnconditional = "Debes tener al menos 18 años para adquirir Disney's Toontown Online. Si tienes menos de 18 años, pide ayuda a tus padres o tutores." +MemberAgreementScreenMustBeOlder = "Debes tener al menos 18 años para adquirir Disney's Toontown Online. Pide a tus padres o tutores que te ayuden." +MemberAgreementScreenYouMustAgree = "Para adquirir Disney's Toontown Online, debes aceptar el Contrato de registro." +MemberAgreementScreenYouMustAgreeOk = "Aceptar" +MemberAgreementScreenYouMustAgreeQuit = "Salir" +MemberAgreementScreenAgreementTitle = "Contrato de registro" +MemberAgreementScreenClickNext = "Pulsa \"Siguiente\" para pasar a la página siguiente." +# this is useful for tweaking the member agreement: +#import LocalizerEnglish; import Localizer +#reload(LocalizerEnglish);reload(Localizer);page=toonbase.tcr.memberAgreementScreen.memAgreement.getCurPage();toonbase.tcr.loginFSM.request('freeTimeInform');toonbase.tcr.loginFSM.request('memberAgreement');toonbase.tcr.memberAgreementScreen.memAgreement.setPage(page) +MemberAgreementScreenLegalText = [ +""" + + + + + +""" # spacing for graphics; start next section on a new line (i.e. """\nText) +""" +CONTRATO DE REGISTRO DE TOONTOWN ONLINE DE DISNEY + +Bienvenido/a a Toontown Online de Disney (en lo sucesivo, el "Servicio"). LE ROGAMOS QUE LEA ATENTAMENTE ESTE CONTRATO (EN LO SUCESIVO, EL "CONTRATO") ANTES DE UTILIZAR ESTE SERVICIO. Este Servicio es propiedad de Disney Online, el cual se encarga también de su explotación (en la presente, referido como "Disney", "nosotros" o cualquier forma de la primera persona del plural). +""",""" +El uso de este Servicio implica la aceptación de estas condiciones, las Cláusulas de utilización y las Normas de la casa que aparecen en nuestro sitio Web. Si no está de acuerdo con todas ellas, le rogamos que se abstenga de utilizar este Servicio. Tenga en cuenta que en este Contrato usted aparecerá como el "Socio". La persona que se registre por primera vez en el Servicio también recibirá la denominación de "Cuenta matriz" en este documento. Por "Cuenta" se entiende la cuenta con la que se ha registrado cualquier socio, en virtud de los procedimientos de registro del Servicio. Las cláusulas de este Contrato serán de aplicación para todos los Socios, conformen éstos o no la Cuenta matriz. El titular de la Cuenta matriz será responsable de hacer que todos los socios de una misma familia (y cualquier otra persona a la que permita jugar mediante la Cuenta) conozcan las condiciones de este Contrato y de garantizar que las cumplan. El titular de la Cuenta matriz de una Cuenta cualquiera será totalmente responsable de todas las actividades que se lleven a cabo mediante dicha Cuenta. +""",""" +Nos reservamos el derecho, cuando así lo estimemos oportuno, de cambiar, modificar, agregar o eliminar cualquier parte de este Contrato en cualquier momento. Todas las modificaciones realizadas en él serán notificadas por medio de su publicación en el Servicio o mediante correo electrónico u ordinario. + +Si algún cambio futuro en este Contrato resultase inaceptable para usted, o debido a dicho cambio usted no cumpliese dicho Contrato, podrá cancelar su Cuenta. El uso continuado por su parte del Servicio tras haber sido informado de los cambios que se hayan dado en el Contrato (incluidas las Cláusulas de uso y las Normas de la casa) implicará que ha aceptado dichos cambios. +""",""" +Nos reservamos el derecho de cambiar, modificar, suspender o interrumpir cualquiera de los aspectos del Servicio en cualquier momento, lo que incluye, entre otros aspectos, la disponibilidad de cualquier Servicio, base de datos o contenido, las horas de disponibilidad y el equipo necesario para acceder al Servicio. También podremos establecer límites sobre algunas funciones concretas o restringir su acceso a parte o la totalidad del Servicio, durante amplios periodos, sin notificación previa y sin que incurramos en ninguna responsabilidad. + +El Socio será el único responsable de obtener la conexión telefónica y el equipo necesarios para acceder al Servicio, lo que incluye, entre otras partes del equipo, el software y los medios de a Internet. +""",""" +RESTRICCIONES EN EL USO DEL MATERIAL + +Todo el material publicado por Disney (incluidos, entre otros, los recursos de información, las fotografías, las imágenes, las ilustraciones y los clips de sonido y de vídeo, denominados de manera colectiva el "Contenido") están protegidos por derechos de copyright y son propiedad de Disney, sus empresas matrices o filiales o cualquier proveedor externo, y están controlados por todos ellos. El socio deberá acatar todas las notificaciones informaciones y restricciones de copyright que aparezca en cualquier Contenido al que tenga acceso mediante el Servicio. +""",""" +El Servicio está protegido por derechos de copyright como trabajo colectivo y/o recopilación, en virtud de las leyes estadounidenses de copyright, los convenios internacionales y el resto de las leyes sobre copyright existentes. Queda prohibido copiar, reproducir, volver a publicar, subir a un sitio web, publicar y transmitir cualquier material procedente del Servicio o de cualquier sitio Web que sea propiedad de Disney, que esté explotado o controlado por Disney o sobre el cual Disney posea una licencia; asimismo está prohibido crear y distribuir cualquier trabajo derivado de dicho material, excepto en el caso de la descarga de Internet de una copia del material en un único ordenador para su uso personal no comercial, siempre y cuando cumpla todas las notificaciones de copyright y de propiedad. La utilización de nuestro Contenido para cualquier otro uso constituirá una violación de nuestros derechos de copyright y de propiedad. A efectos de este Contrato, el uso de cualquier parte de nuestro Contenido en cualquier otro sitio web o en cualquier ordenador que esté conectado a una red queda terminantemente prohibido. No está autorizado a vender ni subastar ninguno de los personajes, artículos y material protegidos por los derechos de copyright de Disney. +""",""" +Si descarga algún tipo de software del Servicio, dicho software, incluidos todos los archivos, las imágenes que se encuentren en él o que éste genere y todos los datos que acompañen a dicho software (denominados, de manera colectiva, el "Software"), contará con la licencia que le ofrece Disney. Por la presente, le conferimos una licencia no exclusiva para utilizar el Software únicamente en relación con el Servicio a través de una Cuenta autorizada y pagada (o una versión de prueba autorizada). La Cuenta matriz pacta y acuerda que (a) ninguno de los materiales, sea del tipo que sea, que se envíe a través de su Cuenta podrá (i) transgredir, plagiar o infringir en menoscabo de los derechos a terceros, incluidos los derechos de copyright, marca registrada, confidencialidad y cualquier otro tipo de derecho personal o de propiedad, ni (ii) contener material ilegal ni injurioso; (b) el número de la tarjeta de crédito que nos ha facilitado es válido, el titular de la Cuenta matriz tiene la autorización para utilizar dicha tarjeta de crédito y tiene al menos 18 años de edad; (c) podemos cargar pagos en la tarjeta de crédito cuyo número se nos ha proporcionado, tal y como se describe en detalle en la sección llamada "Precios y pagos" que aparece a continuación y (d) el titular de la Cuenta matriz y todos los Socios cumplirán plenamente las cláusulas de este Contrato. +""",""" +El titular de la Cuenta matriz, por la presente eximirá a Disney, sus empresas matrices y filiales, y a cualquier empleado, cargo directivo, propietario, agente, proveedor de información, afiliado, licenciatario o concedente (denominados de manera colectiva las "Partes eximidas") de cualquier indemnización por daños y perjuicios en los que hayan incurrido las Partes eximidas en conexión con cualquier reclamación derivada del incumplimiento por parte suya o de cualquier otro Socio. Disney no acuerda ni acepta la exactitud ni la fiabilidad de ningún consejo, opinión, declaración o cualquier otro tipo de información publicada, subida o distribuida mediante el Servicio por parte de cualquier Socio, proveedor de información o cualquier otra persona física o jurídica. Por la presente, el Socio acepta bajo su propio riesgo la fiabilidad de dichas opiniones, consejos, declaraciones, memorandos o información en general. Disney se reserva el derecho, cuando así lo estime oportuno, de corregir cualquier error u omisión en cualquier parte del Servicio. +""",""" +EXENCIÓN DE RESPONSABILIDAD + +EL MATERIAL QUE APARECE EN ESTE SERVICIO SE OFRECE "TAL Y COMO ESTÁ" Y SIN NINGÚN TIPO DE GARANTÍA, IMPLÍCITA NI EXPLÍCITA. SIEMPRE QUE LA LEY VIGENTE LO PERMITA, DISNEY ESTARÁ EXENTO DE TODAS LAS GARANTÍAS, IMPLÍCITAS O EXPLÍCITAS, INCLUIDAS, ENTRE OTRAS, LAS GARANTÍAS IMPLÍCITAS DE COMERCIALIZACIÓN Y ADECUACIÓN A UN OBJETIVO CONCRETO. DISNEY NO GARANTIZA QUE LAS FUNCIONES QUE APARECEN EN ESTE SERVICIO ESTÉN EXENTAS DE INTERRUPCIONES O ERRORES, QUE SE CORRIJAN TODOS LOS DEFECTOS NI QUE ESTE SERVICIO NI EL SERVIDOR A TRAVÉS DEL QUE FUNCIONA ESTÉN EXENTOS DE VIRUS U OTROS COMPONENTES DAÑINOS. DISNEY NO GARANTIZA TAMPOCO NI ACUERDA NINGUNA CLÁUSULA RESPECTO AL USO O LAS CONSECUENCIAS DEL USO DEL MATERIAL DE ESTE SERVICIO EN CUANTO A SU EXACTITUD, FIABILIDAD, ETC. +""",""" +EL SOCIO (Y NO DISNEY) ASUME EL COSTO TOTAL POR CUALQUIER TAREA DE MANTENIMIENTO, REPARACIÓN O CORRECCIÓN. EN CASO DE QUE LA LEY VIGENTE PROHÍBA LA EXCLUSIÓN DE LAS GARANTÍAS IMPLÍCITAS, LA EXCLUSIÓN ANTERIOR PUEDE NO RESULTAR DE APLICACIÓN EN SU CASO. + +SIN PERJUICIO DE LO ESTABLECIDO ANTERIORMENTE, EL SOCIO RECONOCE QUE, EN LA CALIDAD DE SERVICIO PARA LOS USUARIOS DEL SERVICIO DE DISNEY, PODEMOS INCLUIR ENLACES A OTROS SITIOS WEB DE INTERNET, Y DISNEY NO TIENE NINGÚN CONTROL NI PACTA NINGÚN TIPO DE GARANTÍA EN RELACIÓN CON EL CONTENIDO O LA ADECUACIÓN DEL CONTENIDO DE DICHOS SITIOS WEB. EL SOCIO, POR LA PRESENTE, NOS EXIME DE MANERA IRREVOCABLE DE CUALQUIER RECLAMACIÓN REFERIDA A DICHOS SITIOS WEB. +""",""" +Además, Disney rechaza cualquier responsabilidad relativa a la exactitud, el contenido o la disponibilidad de la información que aparece en los sitios web a los que se acceda a través de Toontown Online de Disney de terceros que no estén asociados con Disney. En Disney le aconsejamos que sea precavido cuando navegue por el Internet, ya se encuentre, utilizando nuestro servicio o el de cualquier otra persona o empresa. Dado que en ocasiones algunos sitios utilizan los resultados de búsquedas automatizadas o bien conducen a sitios web que contienen información que puede ser considerada ofensiva o inadecuada, Disney no se responsabilizará por la exactitud ni por el cumplimiento de los derechos de copyright, legalidad o decencia del material en los sitios Web de terceros, y el socio, por la presente, nos exime de cualquier reclamación contra nosotros en relación con dichos sitios web. Disney no podrá garantizarle que estará satisfecho con los productos o servicios que contrate en un sitio Web de terceros conectado a Toontown Online de Disney, ya que los canales de otros establecimientos son propiedad de otros minoristas que los explotan. +""",""" +Disney no garantiza ninguna de las mercancías, ni tampoco confirma la exactitud o la fiabilidad de la información que aparezca en los sitios web de dichas terceras personas. Disney no pacta ni garantiza la seguridad de ningún tipo de información, lo que incluye, entre otras cosas, los datos de su tarjeta de crédito y cualquier otro tipo de información personal que le pida cualquier empresa externa, y el Socio, por la presente, nos exime de cualquier reclamación contra nosotros en relación con dichos sitios web. Le rogamos encarecidamente que efectúe las investigaciones necesarias o que considere adecuadas antes de realizar cualquier transacción a través del Internet o fuera de ella con dichas terceras partes. +""",""" +RESPONSABILIDAD LIMITADA + +BAJO NINGUNA CIRCUNSTANCIA, INCLUIDA, ENTRE OTRAS, LA NEGLIGENCIA, SERÁ DISNEY RESPONSABLE DE NINGUNA INDEMNIZACIÓN POR DAÑOS Y PERJUICIOS, ESPECIALES O INDIRECTOS, QUE SE DERIVEN DEL USO DE LOS MATERIAL DE ESTE SERVICIO O DE CUALQUIER SITIO WEB, NI DE LA INCAPACIDAD PARA USARLO, NI SIQUIERA EN EL CASO DE QUE DISNEY O UN REPRESENTANTE AUTORIZADO POR DISNEY HAYA SIDO ADVERTIDO DE LA POSIBILIDAD DE DICHA RECLAMACIÓN POR DAÑOS Y PERJUICIOS. ES POSIBLE QUE LA LEGISLACIÓN VIGENTE NO PERMITA LA EXCLUSIÓN O LA LIMITACIÓN DE RESPONSABILIDAD O DE DAÑOS Y PERJUICIOS ACCESORIOS O INDIRECTOS, POR LO CUAL LA LIMITACIÓN O EXCLUSIÓN ANTERIOR PUEDE NO RESULTAR APLICABLE PARA USTED. EN TODO CASO, LA RESPONSABILIDAD TOTAL DE DISNEY FRENTE AL SOCIO POR CUALQUIER INDEMNIZACIÓN POR DAÑOS Y PERJUICIOS, PÉRDIDAS Y ACCIONES LEGALES (YA SEAN CONTRACTUALES O POR ILÍCITO CIVIL INCLUIDA, ENTRE OTRAS, LA NEGLIGENCIA) NO SOBREPASARÁ LA CANTIDAD ABONADA, DE HABERLO HECHO, PARA ACCEDER AL SERVICIO. +""",""" +SEGURIDAD + +Como parte integrante del proceso de registro, los Socios deberán elegir una contraseña, una contraseña principal y un nombre de socio (en lo sucesivo, el "Nombre de socio"). Deberá proporcionar a Disney información de la Cuenta actualizada, exacta y completa. En caso contrario, estará incumpliendo este Contrato, lo que puede resultar en la cancelación inmediata de su Cuenta. En ningún caso podrá (i) seleccionar o utilizar el Nombre de socio de otra persona con la intención de hacerse pasar por esa persona; (ii) utilizar un nombre que esté sujeto a los derechos de otra persona sin autorización o (iii) utilizar un Nombre de socio que, en opinión de Disney, sea inadecuado u ofensivo. +""",""" +En caso de que le conste o sospeche que existe algún usuario no autorizado de su Cuenta, o si conoce algún incumplimiento de la seguridad (o lo sospecha), lo que incluye la pérdida, el robo y la divulgación no autorizada de su contraseña o de la contraseña principal, deberá notificárselo inmediatamente a Disney en la dirección de correo electrónico toontown@disneyonline.com,. La responsabilidad de mantener la confidencialidad de su contraseña y contraseña principal será únicamente suya. + +Todos los titulares de las Cuentas matrices deberán tener 18 años de edad o más para poder abrir una Cuenta. Si Disney descubriese que el titular de una Cuenta matriz es menor de 18 años, se reserva el derecho de cancelar dicha Cuenta. + +Cualquier actividad fraudulenta, abusiva o que no resulte conforme a derecho puede constituir la base de la cancelación de su Cuenta, y cuando Disney así lo estime oportuno, se pondrá su caso en conocimiento de las instituciones legislativas pertinentes. +""",""" +PRECIOS Y PAGOS + +Disney se reserva el derecho de cobrar, cuando así lo considere oportuno, cualquier tarifa adicional por el acceso al Servicio. Disney se reserva el derecho de cobrar cualquier cantidad o tarifa por el Servicio y de establecer nuevas tarifas o precios, que entrarán en vigor previa notificación a los Socios. Disney se reserva el derecho de ofrecer el Servicio de manera gratuita por motivos promocionales u otras razones (como, por ejemplo, una versión de prueba). + +Los titulares de las Cuentas matrices se comprometen a abonar todos los pagos en que incurra la Cuenta matriz, incluidos los impuestos vigentes, de conformidad con las normas de facturación existentes en el momento en el que la tarifa o el gravamen resulte pagadero. Los titulares de las Cuentas matrices deberán proporcionar a Disney información de la tarjeta de crédito válida, tal y como se solicita durante el proceso de registro. +""",""" +Disney pasará el cobro a la tarjeta de crédito del titular de la Cuenta matriz en la fecha en la que éste se suscriba al Servicio. A partir de esa fecha, Disney efectuará, de manera automática, las siguientes acciones y procederá al cobro de la Cuenta matriz como se indica a continuación: + +- Cada mes, en concepto del Servicio del mes siguiente para suscripciones mensuales. + +- Cada tres (3) meses desde el primer cobro para las suscripciones trimestrales. + +- Cada seis (6) meses desde el primer cobro para las suscripciones semestrales. + +- Cada año (1) desde el primer cobro para las suscripciones anuales. +""",""" +El cargo por renovación será equivalente o inferior al precio de suscripción, a menos que Disney indique previamente lo contrario. Podrá informar a Disney de que desea cancelar su suscripción en cualquier momento. Disney se compromete a cancelar su Cuenta a la recepción de dicha notificación de la Cuenta matriz, tal y como se describe más adelante. + +En el caso de las suscripciones mensuales: Si se recibe la notificación de la cancelación durante los 15 días siguientes al cobro inicial, tendrá derecho a que le devuelvan todas las tasas de suscripción del Servicio, pero deberá pagar el resto de los gastos en los que haya incurrido. Si cancela el Servicio después de los 15 días siguientes al cobro inicial, su Cuenta será cancelada al final del período de facturación en curso y no se le devolverá ningún importe por el tiempo en que no lo haya utilizado. +""",""" +En el caso de las suscripciones trimestrales: Si se recibe la notificación de la cancelación durante los 30 días siguientes al cobro inicial, tendrá derecho a que le devuelvan todas las tasas de suscripción del Servicio, pero deberá pagar el resto de los gastos en los que haya incurrido. Si cancela el Servicio después de transcurridos 30 días, no se le devolverá ningún importe por el tiempo en que no lo haya utilizado. + +En el caso de las suscripciones semestrales: Si se recibe la notificación de la cancelación durante los 30 días siguientes al cobro inicial, tendrá derecho a que le devuelvan todas las tasas de suscripción del Servicio, pero deberá pagar el resto de los gastos en los que haya incurrido. Si cancela el Servicio después de los 30, no se le devolverá ninguna cantidad por el tiempo en que no lo haya utilizado. +""",""" +En el caso de las suscripciones anuales: Si se recibe la notificación de la cancelación durante los 30 días siguientes al cobro inicial, tendrá derecho a que le devuelvan todas las tasas de suscripción del Servicio, pero deberá pagar el resto de los gastos en los que haya incurrido. Si cancela el Servicio después de transcurridos 30 días, no se le devolverá ningún importe por el tiempo en que no lo haya utilizado. + +Su derecho a utilizar el Servicio está sujeto a los límites que establezca Disney o la entidad emisora de su tarjeta de crédito. Si no se pueden cargar o nos son devueltos los pagos cargados en su tarjeta de crédito, incluido el cargo al usuario, Disney se reserva el derecho de suspender o cancelar su acceso y Cuenta, con lo que quedan rescindidos este Contrato y todas las obligaciones de Disney. +""",""" +Si alguna de sus Cuentas de Disney presenta un saldo deudor, el Socio se compromete a que Disney puede cargar estas tarifas morosas en su tarjeta de crédito. Disney se reserva el derecho a establecer un límite crediticio (en lo sucesivo, el "Límite máximo")para cada Socio. Si la Cuenta de un Socio alcanza el Límite máximo en algún momento, Disney cargará todos los pagos morosos de la cuenta en la tarjeta de crédito del Socio. A menos que se especifique lo contrario, el Límite máximo para cada Socio es de 100 dólares estadounidenses. + +Si sospecha que su Cuenta no es segura (por ejemplo, en caso de pérdida, robo o divulgación no autorizada de su Nombre de socio, su Contraseña o el número de la tarjeta de crédito o de débito que aparezca en el Servicio), deberá cambiar su Contraseña inmediatamente e informar a Disney del problema (mediante notificación de la forma que se indica en la sección Notificaciones, a continuación) para evitar cualquier posible responsabilidad por cobros no autorizados que se carguen en su Cuenta. +""",""" +CONSENTIMIENTO DE LOS PROGENITORES + +De acuerdo con la Ley estadounidense para la protección del menor en medios electrónicos (Children's Online Privacy Protection Act, en lo sucesivo "COPPA"), se necesita el consentimiento de los progenitores para la recopilación, el uso y la divulgación de información personal correspondiente a un niño de menos de 13 años. Como parte del proceso de registro del Servicio, el titular de la Cuenta matriz deberá proporcionar una tarjeta de crédito válida. Los progenitores y representantes legales podrán crear un máximo de seis Toons (un Toon es un personaje que el socio crea y utiliza para jugar en el Servicio), todos ellos dentro de la Cuenta matriz. Los niños podrán crear su propio Toon dentro de la Cuenta matriz previo consentimiento del progenitor o el tutor inscrito como titular de la Cuenta matriz. +""",""" +Al proporcionar el número de su tarjeta de crédito, el titular de la Cuenta matriz (a) pacta y acuerda ser el progenitor o representante legal de cualquier niño menor de 13 años al que permita utilizar la Cuenta matriz y (b) consiente en que recopilemos, utilicemos y divulguemos la información personal, en conformidad con las Normas de confidencialidad, respecto a cualquier niño menor de 13 años al que el titular de la Cuenta matriz permita utilizar dicha cuenta. + +El Servicio incluye una función interactiva que denominados Amigos secretos. El titular de la Cuenta matriz podrá desactivar la función de Amigos secretos una vez que esté dentro del Servicio. La función de Amigos secretos permite a los socios charlar con otros socios mediante un código secreto que se debe comunicar fuera del juego. La función de Amigos secretos no tiene ningún moderador ni supervisor. +""",""" +Si el titular de una Cuenta matriz permite a un niño usar su propia cuenta con la función de Amigos secretos activada, le rogamos que supervise a sus hijos mientras juegan con el Servicio. Al activar la función de Amigos secretos, el titular de la Cuenta matriz reconoce que existen riesgos inherentes a dicha función y que ha sido informado y acepta dichos riesgos. Podrá obtener más información de la función de Amigos secretos y la forma de activarla dentro del Servicio. +""",""" +NOTIFICACIONES + +El titular de la Cuenta matriz enviará y dispondrá de una dirección de correo electrónico correcta, así como otros datos sobre la Cuenta. Podremos realizar las notificaciones al titular de la Cuenta matriz a través de una notificación de tipo general en el Servicio, mediante correo electrónico a la dirección que consta en nuestra información de Cuenta, o mediante carta urgente enviada a la dirección postal que consta en nuestro poder. Puede enviar cualquier notificación a Disney. Dicha notificación se considerará entregada cuando Disney la reciba por correo electrónico en la dirección toontown@disneyonline.com. +""",""" +NO TRANSFERIBILIDAD + +Disney le otorga una licencia personal, no exclusiva y de no cesión para utilizar y poder ver el Software de Disney en cualquier dispositivo del cual usted sea el principal usuario. La copia no autorizada del Software o la reproducción de cualquier forma del software del programa principal y el software que haya sido modificado, integrado o incluido con el Software, así como la documentación relacionada con él, quedan totalmente prohibidas. Por la presente, el Socio acuerda que no puede ceder esta licencia ni el Software, ni transferirlos, venderlos o cederlos. Cualquier intento de emprender dichas acciones se considera ilegítimo. +""",""" +CUESTIONES JURÍDICAS + +Este Servicio está controlado y explotado por Disney desde sus oficinas en el estado de California (Estados Unidos). Disney no garantiza que el material que aparece en el Servicio sea adecuado o esté disponible en otros lugares. Las personas que decidan acceder a este Servicio desde otros lugares lo harán por iniciativa propia y serán responsables del cumplimiento de la legislación local aplicable. El Software disponible en este Servicio también está sujeto a los controles de exportación de los Estados Unidos. Está prohibido descargar, exportar o hacer llegar el Software de este Servicio a (i) Cuba, Irak, Libia, Corea del Norte, Irán, Siria (ni a un ciudadano o residente de estos países), a ningún país con el que Estados Unidos mantenga un embargo o (ii) a alguien que aparezca en la lista Specially Designated Nationals (Ciudadanos especialmente mencionados) del Ministerio de Hacienda estadounidense o en la Table of Deny Orders (tabla de denegación de pedidos) del Ministerio de Comercio. +""",""" +Al descargar o utilizar el Software, el Socio pacta y conviene que no está situado bajo el control de dichos países, ni es ciudadano o residente de ellos, y asimismo, que no aparece en las listas mencionadas. Algunos tipos de Software que los Socios descargan para usarlo o instalan desde un CD-ROM es "Software restringido a ordenadores". El uso, la copia y la divulgación por parte del gobierno estadounidense están sujetos a las restricciones que se establecen en este Contrato y en las leyes federales DFARS 227.7202-1(a) y 227.7202-3(a) (1995), DFARS 252.227-7013 (Octubre de 1988), FAR 12.212(a) (1995), FAR 52.227-19, o FAR 52.227-14, según sea el caso. +""",""" +EXPIRACIÓN DEL SERVICIO + +Este Contrato estará en vigencia hasta que sea cancelado por una de las dos partes. Puede cancelar este Contrato y su derecho a utilizar el Servicio cuando lo desee enviando un mensaje de correo electrónico a toontown@disneyonline.com. Disney podrá cancelar su Cuenta o sus derechos de acceso a este Servicio de manera inmediata sin notificación previa si, en opinión de Disney, incumple alguna de las cláusulas de este Contrato (incluidas las Cláusulas de uso y las Normas de la casa). Una vez cancelado el contrato, deberá destruir todo el material que haya obtenido gracias a este Servicio y cualquier copia existente, independientemente de que se hiciera de acuerdo con las cláusulas de este Contrato. +""",""" +VARIOS + +Este Contrato se regirá e interpretará de conformidad con las leyes del estado de Carolina, sin consideración de ningún principio de conflicto de leyes. Si alguna cláusula del presente Contrato no fuese conforme a derecho, fuese nula o, por alguna razón, no resultara aplicable, dicha cláusula podrá eliminarse de este Contrato y el resto de las cláusulas conservarán su vigencia. El presente Contrato constituye la totalidad del acuerdo entre las partes acerca del objeto que se trata en él y sólo podrá ser modificado por escrito, y siempre de la forma que se describe a continuación. +""",""" +CONTRATO COMPLETO + +Este Contrato constituye la totalidad del contrato entre las partes respecto al objeto que en él se trata y sustituye a cualquier contrato anterior o actual que exista o haya existido, así como a cualquier propuesta o comunicado, ya sea escrito o verbal, entre los representantes de Disney y el Socio. Disney podrá modificarlo o alterarlo, así como incluir nuevas cláusulas, siempre que lo estime oportuno, previa notificación al Socio, tal y como se describe en el apartado "Notificaciones" anterior. Cualquier uso del Servicio por su parte posterior a dicha notificación constituirá una aceptación implícita de dichas modificaciones, alteraciones o nuevas cláusulas.ÚLTIMA ACTUALIZACIÓN: + +LAST UPDATED: +18/10/2002 +""" +] + +# BillingScreen.py +BillingScreenCCTypeInitialText = 'Elija una opción' +BillingScreenCreditCardTypes = ['Visa', 'American Express', 'MasterCard'] +BillingScreenTitle = "Introduzca la información de facturación" +BillingScreenAccountName = "Nombre de la cuenta" +BillingScreenEmail = "Dirección de correo electrónico de los padres para la facturación" +BillingScreenEmailConfirm = "Confirme la dirección de correo electrónico" +BillingScreenCreditCardType = "Tipo de tarjeta de crédito" +BillingScreenCreditCardNumber = "Número de la tarjeta de crédito" +BillingScreenCreditCardExpires = "Fecha de caducidad" +BillingScreenCreditCardName = "Nombre que aparece en la tarjeta de crédito" +#BillingScreenAgreementText = """*Al emplear mi tarjeta de crédito y hacer clic en "Comprar", acepto, de acuerdo con el Contrato de Socio: (1) que pueden cargar gastos en mi tarjeta de crédito y (2) que pueden recopilar, usar y difundir datos personales de mis hijos de acuerdo con las Normas de confidencialidad.""" +BillingScreenAgreementText = """Al hacer clic en el botón "Comprar" acepto que, de acuerdo con las Normas de confidencialidad, mis hijos pueden usar las herramientas interactivas autorizadas mediante la contraseña parental que estableceré en la pantalla siguiente.""" +BillingScreenBillingAddress = "Dirección de facturación: Calle 1" +BillingScreenBillingAddress2 = "Calle 2 (si procede)" +BillingScreenCity = "Ciudad" +BillingScreenCountry = "País" +BillingScreenState = "Estado" +BillingScreenZipCode = "Código postal" +BillingScreenCAProvince = "Provincia o territorio" +BillingScreenProvince = "Provincia (si procede)" +BillingScreenPostalCode = "Código postal" +BillingScreenPricing = (' durante el primer mes, después' + ' al mes') +BillingScreenSubmit = "Comprar" +BillingScreenCancel = "Cancelar" +BillingScreenConfirmCancel = "¿Desea cancelar la compra?" +BillingScreenConfirmCancelYes = "Sí" +BillingScreenConfirmCancelNo = "No" +BillingScreenPleaseWait = "Espere un momento..." +BillingScreenConnectionErrorSuffix = ".\nVuelva a intentarlo más tarde." +BillingScreenEnterEmail = "Escriba su dirección de correo electrónico." +BillingScreenEnterEmailConfirm = "Vuelva a escribir su dirección de correo electrónico." +BillingScreenEnterValidEmail = "Introduzca una dirección de correo electrónico válida." +BillingScreenEmailMismatch = "Las direcciones de correo electrónico que ha introducido no coinciden. Inténtelo de nuevo." +BillingScreenEnterAddress = "Escriba su dirección de facturación completa." +BillingScreenEnterValidState = "Escriba la abreviatura de dos letras correspondiente al estado." +BillingScreenChooseCreditCardType = "Elija un tipo de tarjeta de crédito." +BillingScreenEnterCreditCardNumber = "Escriba el número de la tarjeta de crédito." +BillingScreenEnterValidCreditCardNumber = "Compruebe el número de la tarjeta de crédito." +BillingScreenEnterValidSpecificCreditCardNumber = "Escriba un número válido de la tarjeta de crédito %s." +BillingScreenEnterValidCreditCardExpDate = "Escriba una fecha de caducidad válida de la tarjeta de crédito." +BillingScreenEnterNameOnCard = "Escriba el nombre que aparece en la tarjeta de crédito." +BillingScreenCreditCardProblem = "Se ha producido un error al procesar la tarjeta de crédito." +BillingScreenTryAnotherCC = "¿Desea probar con otra tarjeta?" +# Fill in %s with phone number from account server +BillingScreenCustomerServiceHelp = "\n\nSi necesita ayuda, póngase en contacto con el Servicio de atención al cliente, en el teléfono %s." +BillingScreenCCProbQuit = "Salir" +BillingScreenWhySafe = "Seguridad de la tarjeta de crédito" +BillingScreenWhySafeTitle = "Seguridad de la tarjeta de crédito" +BillingScreenWhySafeCreditCardGuarantee = "GARANTÍA DE LA TARJETA DE CRÉDITO" +BillingScreenWhySafeJoin = "¡JUEGA EN" +BillingScreenWhySafeToontown = "DISNEY'S TOONTOWN ONLINE" +BillingScreenWhySafeToday = "HOY MISMO!" +BillingScreenWhySafeClose = "Cerrar" +BillingScreenWhySafeText = [ +""" + + + + +Usamos la tecnología Secure Sockets Layer (SSL) para cifrar la información de la tarjeta de crédito, protegiéndola y garantizando la confidencialidad. Esta tecnología permite introducir y transmitir la información de la tarjeta de crédito por Internet con total seguridad. +Esta tecnología de seguridad protege sus comunicaciones en Internet con: + + Verificación de servidores (impide las suplantaciones) + Confidencialidad mediante el cifrado (evita la monitorización oculta) + Integridad de los datos (evita el vandalismo) + +Para aumentar más aún la seguridad, todos los números de tarjetas de crédito se almacenan en un ordenador que no está conectado a Internet. Una vez introducido el número completo de la tarjeta de crédito, éste se transfiere a dicho ordenador seguro a través de una conexión no estándar. Los números de las tarjetas de crédito no se almacenan en ningún otro sitio. + + + +Por tanto, la información de su tarjeta de crédito no sólo está a salvo en Disney's Toontown Online, sino que además la garantizamos. +Todas las suscripciones a Disney's Toontown Online están respaldadas por nuestra garantía de tarjetas de crédito. Si en su extracto de cuentas aparecen cargos no autorizados de los que usted no es responsable como resultado directo de haber enviado los datos de su tarjeta de crédito a Disney's Toontown Online, haremos efectiva la cantidad que le reclama su banco hasta un máximo de 50 USD. + +Si sospecha que hay un problema, dé parte siguiendo el procedimiento habitual del proveedor de su tarjeta de crédito y póngase en contacto de inmediato con nosotros. La mayoría de las compañías de tarjetas de crédito se hacen cargo de todos los gastos derivados del uso no autorizado de dichas tarjetas, pero pueden reclamarle el pago de un máximo de 50 USD. Nosotros nos hacemos cargo del pasivo que no esté cubierto por su tarjeta de crédito. +¿Qué significa todo esto? Significa que puede confiar en la seguridad y el servicio proporcionados por Disney's Toontown Online. + +¿A qué espera, entonces? +""", +] +BillingScreenPrivacyPolicy = "Normas de confidencialidad" +BillingScreenPrivacyPolicyClose = "Cerrar" +BillingScreenPrivacyPolicyText = [ +""" +Normas de confidencialidad + +P1 ¿Qué tipo de información recogen los sitios web de WDIG y cómo lo hacen? + +La mayoría de los excelentes productos y servicios que se presentan en nuestros sitios web se ofrecen sin necesidad de recabar ningún tipo de información personal de los visitantes. Puede navegar por los sitios web de WDIG y ver una gran parte de nuestro estupendo contenido de forma anónima. Por ejemplo, puede consultar los titulares de última hora en ABCNEWS.com sin por ello tener que facilitar ningún tipo de información de identificación personal. + +La información que usted nos proporciona +En nuestros sitios web existen algunas actividades para las cuales resulta necesario recabar información de identificación personal. Entre ellas se incluyen, por ejemplo, la participación en concursos, las compras y los mensajes dirigidos a Disney. Cuando recabemos información de identificación personal usted será consciente de ello, ya que tendrá que rellenar un impreso. Para la mayoría de las actividades sólo se solicitan el nombre, la dirección de correo electrónico, la fecha de nacimiento, el sexo y el código postal. Cuando se efectúa una compra, también se solicitan las direcciones postales de envío y facturación, el número de teléfono y los datos de la tarjeta de crédito. Según lo que se compre, es posible que también se solicite otro tipo de información personal como, por ejemplo, la talla de ropa. +""",""" +Información personal recopilada mediante dispositivos tecnológicos +Los sitios web de WDIG recaban algunos datos mediante dispositivos tecnológicos, de tal forma que puede no darse cuenta de que estamos recogiendo dicha información. Por ejemplo, cuando visita nuestro sitio web, se recoge su dirección IP para que sepamos dónde tenemos que enviar la información que está solicitando. Normalmente, la dirección IP está asociada con el lugar desde el que se ha accedido a Internet, como por ejemplo, el proveedor de Internet, la empresa o la universidad. Esta información no le identifica individualmente. Gracias a la información recabada mediante los dispositivos tecnológicos, los sitios web de WDIG resultan más interesantes y útiles para sus visitantes. Esto incluye ayudar a las empresas que se anuncian en nuestro sitio web a diseñar anuncios por los que nuestros visitantes puedan sentirse atraídos. Normalmente no combinamos esta información con los datos personales. No obstante, en caso necesario combinaremos la información de este tipo con los datos personales con el fin de identificar a los visitante para hacer cumplir las normas de la casa o las cláusulas del servicio, así como para proteger el servicio, el sitio web, a los otros visitantes, etc. + +¿Qué son las cookies y cómo las utiliza WDIG? +Las cookies son pequeños fragmentos de información que los sitios web visitados envían al ordenador del visitante. Esta información permite al sitio web recordar datos importantes que harán que su uso del sitio sea más útil. WDIG y otras empresas de Internet utilizan cookies por diversos motivos. Por ejemplo, DisneyStore.com utiliza cookies para recordar y procesar los artículos del carro de la compra, y todos los sitios web de WDIG utilizan cookies para asegurarse de que los niños no entran en las salas de conversación que no estén moderadas. + +Puede elegir que el ordenador le avise siempre que se envíe una cookie, o puede desactivar todas las cookies. Para ello, es necesario modificar la configuración navegador (como, por ejemplo, Netscape Navigator o Internet Explorer). Todos los navegadores son distintos; por tanto, si desea información sobre la forma de modificar las cookies, consulte el menú Ayuda del programa que utiliza. Si desactiva todas las cookies, no podrá acceder a muchas funciones de WDIG que mejoran su visita a la web sea mejor (como por ejemplo, las funciones que hemos mencionado antes), y no todos nuestros servicios funcionarán correctamente. +""",""" +P2 ¿Cómo utiliza WDIG la información de identificación personal recabada? + +WDIG utiliza la información de identificación personal en situaciones muy concretas. Los datos se utilizan para llevar a cabo las transacciones. Por ejemplo, si adquiere un equipo de fantasía en ESPN.com, utilizamos su información para procesar el pedido, o si se pone en contacto con nosotros para pedirnos ayuda, utilizamos esa información para ponernos en contacto con usted. Asimismo, utilizaremos la información recogida para comunicarle si ha ganado un juego o concurso. Los datos solicitados también se utilizan para enviarle por correo electrónico actualizaciones y boletines sobre nuestros sitios web, así como sobre las promociones de WDIG y las ofertas especiales de nuestros patrocinadores externos. +""",""" +P3 ¿Comparte WDIG en algún caso la información con empresas u otro tipo de organizaciones que no forman parte de su grupo de sitios web? + +Nuestros clientes son los activos más importantes de nuestro negocio. No nos dedicamos a vender la información de nuestros visitantes. Sin embargo, cuando esto suponga una ventaja para nuestros visitantes, compartiremos la información que tenemos sobre usted o le enviaremos mensajes de parte de otra empresa, como describimos más adelante. También podemos compartir la información por motivos de seguridad. +Las empresas subyacentes al WDIG +En ocasiones contratamos a otras empresas para la entrega de productos o servicios, como por ejemplo una empresa de envíos que entrega un paquete. En esas ocasiones, nos vemos obligados a compartir la información con ellos. Estas empresas prácticamente son representantes de WDIG, y sólo pueden utilizar la información para entregar el producto o el servicio. +""",""" +Empresas que ofrecen promociones, productos o servicios +De vez en cuando, lanzamos promociones, como concursos o suscripciones gratuitas, en colaboración con un patrocinador. Compartiremos la información con los patrocinadores si la necesitan para enviarle un producto, como puede ser la suscripción a una revista. Asimismo, podemos compartir la información con dichos patrocinadores para que puedan ofrecerle sus promociones especiales, pero sólo si usted así lo permite y, en ese caso, la compartiremos sólo con ese patrocinador en concreto. Además, WDIG puede enviar por correo electrónico a los visitantes promociones de parte de otros patrocinadores. En estos casos, no compartimos su nombre con dichos patrocinadores; lo que hacemos es enviarle los mensajes en su nombre. Únicamente le enviaremos estas promociones si nos ha autorizado para ello. + +Colaboradores de contenido +En algunos de nuestros sitios web ofrecemos contenido creado por un sitio web de un colaborador externo. Por ejemplo, ESPN.com ofrece oportunidades de compra en empresas de terceros. En algunos casos, los sitos web de terceros solicitan información con el fin de realizar la transacción o para que el uso de su contenido resulte más productivo y eficaz. En estos casos, la información que se recoge se comparte entre WDIG y los patrocinadores externos. + +Anunciadores externos y anunciadores de la red +Con el fin de aumentar la protección de la intimidad de nuestros visitantes, WDIG sólo permite anunciarse en nuestros sitios web a empresas que tienen sus propias normas de confidencialidad. Cuando se hace clic en un anuncio y se abandonan los sitios web de WDIG, nuestras normas de confidencialidad dejan de ser aplicables. Debe leer las normas de confidencialidad de la empresa anunciada para saber cómo se tratará su información personal en su sitio web. +""",""" +Además, en nuestro sitio web existen muchos anuncios comerciales gestionados y publicados por empresas externas. Estas empresas reciben el nombre de "anunciadores de la red". Los anunciadores de la red recogen información de carácter no personal cuando se hace clic en sus báners, y en ocasiones, cuando se pasa por encima con el ratón. La información se obtiene por medio de dispositivos tecnológicos, por lo que es posible que no se dé cuenta de que está siendo recogida. Los anunciadores de la red recogen esta información para mostrarle después anuncios que pueden resultarle más interesantes. Si desea obtener más información sobre los anunciadores de la red o no desea que recojan este tipo de información de carácter no personal sobre usted, haga clic aquí. + +Compra y venta de negocios +Los negocios en línea se encuentran todavía en una etapa muy temprana, pero están cambiando y evolucionando con mucha rapidez. Como WDIG busca continuamente formas de mejorar nuestro negocio, se puede dar el caso de que compremos o vendamos una empresa. Si compramos o vendemos un negocio, es probable que los nombres recogidos se transfieran como parte de la venta. La información sobre las personas registradas se utilizará en la sociedad constituida. Sin embargo, si compramos un negocio, satisfaremos los deseos de sus clientes en lo que se refiere a comunicaciones por correo electrónico. En caso de que vendamos un negocio, haremos todo lo que esté a nuestro alcance para garantizar que se cumplan las peticiones de comunicaciones por correo electrónico que nos confió. + +Organizaciones que ayudan a proteger y salvaguardar la seguridad de nuestros visitantes y nuestros sitios web +Divulgaremos la información personal cuando la legislación así lo requiera, por ejemplo, en cumplimiento de un requerimiento judicial o una cédula de citación; para hacer cumplir nuestras Cláusulas de servicio o las normas del sitio web o de los juegos; o para proteger y salvaguardar la seguridad de los visitantes y de nuestros sitios web. +""",""" +P4 ¿Qué opciones tiene el cliente en lo relativo a la información recogida, utilizada y compartida por WDIG? + +Puede utilizar gran parte de nuestro sitio web sin darnos ningún tipo de información de identificación personal. Cuando se registre con nosotros o nos proporcione información de identificación personal, tendrá la oportunidad de restringir las comunicaciones por correo electrónico de WDIG y de nuestros colaboradores externos. Puede solicitar en cualquier momento que WDIG deje de enviarle más mensajes de correo electrónico, bien cancelando su suscripción a dicha comunicación, bien poniéndose en contacto con nosotros en la dirección memeberservices@help.go.com. Asimismo, como hemos mencionado anteriormente, existen formas de restringir la información que se recoge a través de nuestros dispositivos tecnológicos, aunque, en ese caso, algunas de nuestras funciones no se podrán utilizar. +""",""" +P5 ¿Qué tipo de seguridad ofrece WDIG? + +La importancia de la seguridad de toda la información de identificación personal de nuestros visitantes supone nuestra mayor preocupación. WDIG adopta medidas técnicas, contractuales, administrativas y físicas relacionadas con la seguridad, con el fin de proteger los datos de todos los visitantes. Cuando los visitantes proporcionan información relativa a su tarjeta de crédito, nos valemos del cifrado SSL para protegerla. Los visitantes también pueden realizar varias acciones para ayudarnos a proteger la seguridad de su información. Por ejemplo, no divulgue nunca su contraseña, ya que con ella se puede acceder a toda la información de su cuenta. No se olvide tampoco de cerrar la sesión de su cuenta y la ventana del navegador cuando acabe de navegar por la red, de manera que si otra persona utiliza el mismo ordenador no pueda acceder a su información. +""",""" +P6 ¿Cómo puedo acceder a la información de mi cuenta? + +Puede acceder a la información de identificación personal que nos facilitó durante el proceso de registro en el Centro de opciones de cuentas, disponible en http://play.toontown.com. Inicie una sesión con su nombre de cuenta y la contraseña principal. En la página de inicio encontrará instrucciones para poder recuperar su contraseña en caso de que la olvide. +Si desea ponerse en contacto con nosotros, haga clic en el enlace "Contact Us" (Contacto) que aparece al pie de todas las páginas de WDIG y seleccione "Registration/Personalization" (Registro/Personalización) en el menú desplegable, o bien envíenos un mensaje de correo electrónico con información que nos ayude a identificar su cuenta con el fin de que podamos ayudarle a resolver el problema. +""",""" +P7 ¿Con quién hay que ponerse contacto si surge alguna pregunta o duda sobre estas normas de confidencialidad? + +Si necesita más ayuda, le rogamos que nos envíe un mensaje de correo electrónico con sus preguntas y comentarios a memberservices@help.go.com. +También puede escribirnos por correo ordinario a: + +Member Services +Walt Disney Internet Group +506 2nd Avenue +Suite 2100 +Seattle, WA 98104, Estados Unidos + +Walt Disney Internet Group es licenciatario del TRUSTe Privacy Program. Si considera que WDIG no ha contestado a su pregunta o no la ha enfocado de la forma deseada, le rogamos que se ponga en contacto con el programa TRUSTe en http://www.truste.org/users/users_watchdog.html. +*Para llamar a este teléfono es necesario tener 18 años de edad o contar con el permiso de los padres o el tutor. +""",""" +Normas de confidencialidad para niños +Somos conscientes de la necesidad de ofrecer servicios adicionales de protección de los datos personales de los niños que visitan nuestros sitios web. + +P1 ¿Qué tipo de información recogen los sitios web de WDIG sobre los niños que tienen 12 años o menos? + +Los niños pueden navegar por Disney.com u otros sitios web de WDIG, ver distintos contenidos y jugar a algunos juegos sin que se recoja ningún tipo de información de identificación personal. Además, esporádicamente alojamos salas de conversación moderadas en las que no se solicita ni se hace pública información de identificación personal de ningún tipo. No obstante, en algunas zonas es necesario recoger información de identificación personal de los niños para permitir la participación en ciertas actividades (como, por ejemplo, un concurso) o para comunicarse con nuestra comunidad (por correo electrónico o tablones de mensajes). +En WDIG no consideramos adecuado recoger más información de identificación personal de niños de 12 años o menores que la necesaria para que puedan participar en nuestras actividades en línea. Además, se debe tener en cuenta que los sitios web que están dirigidos a niños de 12 años y menores no pueden, por ley, solicitar más información de la necesaria. + +La única información de identificación personal que recogemos de los niños es el nombre, la fecha de nacimiento y la dirección de correo electrónico de los padres. La fecha de nacimiento se recoge para comprobar la edad de los visitantes. También podemos solicitar información personal, como por ejemplo el nombre de un animal doméstico, para recordar a los visitantes su nombre de inicio de sesión y su """,""" +contraseña en caso de que los olviden. + +También permitimos a los padres solicitar, cuando lo estimen oportuno, la supresión de nuestra base de datos de toda la información que figura sobre sus hijos. Si desea desactivar la cuenta de su hijo, le rogamos que nos lo solicite por medio de un mensaje dirigido a ms_support@help.go.com en el que consten el nombre de inicio de sesión y la contraseña del niño. +""",""" +P2 ¿Cómo utiliza y comparte WDIG la información de identificación personal que recabada? + +Ningún dato sobre los visitantes de 12 años o menores se utiliza con ningún fin de marketing ni promocional, ni dentro ni fuera de la familia de sitios web del Walt Disney Internet Group. +Los sitios web de WDIG sólo utilizan los datos recogidos sobre los niños de 12 años o menores para ofrecer servicios (como por ejemplo calendarios) o para llevar a cabo algunos juegos o concursos. A pesar de que los visitantes de 12 años y menores pueden participar en algunos concursos en los que se recoge información, las notificaciones y los premios se envían a la dirección de correo electrónico de los padres o tutores que se proporcionó durante el proceso de registro inicial. No se publican el nombre completo, la edad ni las fotos de los ganadores de los concursos para niños de 12 años o menores sin el consentimiento de los padres o el tutor. En ocasiones se publica una forma inidentificable del nombre del niño. En esos casos, es posible que no nos volvamos a poner en contacto con los padres para pedirles permiso. + +No permitimos a los niños de 12 años y menores participar en salas de conversación sin moderador. + +Facilitaremos la información personal sobre los niños cuando la legislación así lo requiera, por ejemplo, en cumplimiento de un requerimiento judicial o cédula de citación; para hacer cumplir nuestras Cláusulas de servicio, o las normas del sitio web o de los juegos; o para proteger y salvaguardar la seguridad de los visitantes y de nuestros sitios web. +""",""" +P3 ¿Se ocupa WDIG de informar a los padres sobre la recogida de información de niños de 12 años o menores? + +Siempre que un niño de 12 años o menor se registre en nuestro servicio, se lo notificaremos por correo electrónico a sus padres o a su tutor. Además, solicitamos a los padres que otorguen un permiso explícito para permitir a sus hijos utilizar el correo electrónico, los tableros de mensajes y otras funciones a través de las cuales se puede hacer pública la información de identificación personal en Internet y compartirla con usuarios de todas las edades. +También damos a los padres un plazo de 48 horas para rechazar cualquier registro que sus hijos hayan efectuado para jugar a juegos y concursos. Si no recibimos ningún mensaje en contra, damos por supuesto que no hay ningún problema en que el niño esté registrado en el servicio. Cuando el niño se haya registrado, podrá acceder a cualquier juego y concurso que requiera inscripción, pero no se lo volveremos a notificar a sus padres. En este caso, utilizamos la información recogida únicamente para comunicar a los padres si un niño ha ganado un juego o concurso. No utilizamos esta información con ningún otro fin. +""",""" +P4 ¿Cómo pueden acceder los padres a la información de sus hijos? + +Existen tres formas de revisar la información que se ha recogido sobre los niños de 12 años o menores. + +Cuando los padres proporcionan a sus hijos el acceso a funciones interactivas, como los tableros de mensajes, se les solicita que configuren una cuenta familiar. Cuando la cuenta familiar se encuentre en funcionamiento, el titular de la cuenta principal podrá revisar la información de identificación personal de todas las cuentas de los socios de la familia, incluidas las de los niños. Para acceder a esta información, inicie una sesión en su cuenta familiar en la página de inicio Your Account (Su cuenta). + +Si todavía no es socio de un sitio web de WDIG, para revisar la información de identificación personal de su hijo inicie una sesión de su cuenta en la página de inicio Account Options (Opciones de cuenta). Deberá tener el nombre de cuenta y la contraseña de su hijo. En la página de inicio Your Account (Su cuenta) encontrará instrucciones para poder recuperar la contraseña de su hijo en caso de que la olvide. + +También puede ponerse en contacto con el Servicio de atención al cliente para ver la información que se ha recogido de su hijo mediante un mensaje dirigido a ms_support@help.go.com. Si todavía no ha establecido una cuenta familiar, deberá tener el nombre de usuario y la contraseña de su hijo. Le rogamos que incluya en el mensaje de correo electrónico datos (nombre de cuenta del niño, dirección de correo electrónico de los padres) que nos permitan identificar la cuenta de su hijo, con el fin de que podamos ayudarle a resolver el problema. +""",""" +P5 ¿Qué tipo de seguridad ofrece WDIG? + +La importancia de la seguridad de toda la información de identificación personal de nuestros visitantes supone nuestra mayor preocupación. WDIG adopta medidas técnicas, contractuales, administrativas y físicas relacionadas con la seguridad, con el fin de proteger los datos de todos los visitantes. Cuando los visitantes proporcionan información relativa a su tarjeta de crédito, nos valemos del cifrado SSL para protegerla. Los visitantes también pueden realizar varias acciones para ayudarnos a proteger la seguridad de su información. Por ejemplo, no divulgue nunca su contraseña, ya que con ella se puede acceder a toda la información de su cuenta. No se olvide tampoco de cerrar la sesión de su cuenta y la ventana del navegador cuando acabe de navegar por la red, de manera que si otra persona utiliza el mismo ordenador no pueda acceder a su información. +""",""" +P6 ¿Cómo se enteran los padres si WDIG modifica estas normas de confidencialidad? + +Si WDIG modifica estas normas de confidencialidad, se lo notificaremos a los padres por correo electrónico. + +P7 ¿ Con quién hay que ponerse contacto si surge alguna pregunta o duda sobre estas normas de confidencialidad? + +Si necesita más ayuda, le rogamos que envíe un mensaje de correo electrónico con sus preguntas o comentarios a ms_support@help.go.com. +También puede escribirnos por correo ordinario a: + +Member Services +Walt Disney Internet Group +506 2nd Avenue +Suite 2100 +Seattle, WA 98104, Estados Unidos +O llamarnos por teléfono al número 00 (1) (509) 742-4698. + +Walt Disney Internet Group es licenciatario del TRUSTe Privacy Program. Si considera que WDIG no ha contestado a su pregunta o no la ha enfocado de la forma deseada, le rogamos que se ponga en contacto con el programa TRUSTe en http://www.truste.org/users/users_watchdog.html. +*Para llamar a este teléfono es necesario tener 18 años de edad o contar con el permiso de los padres o el tutor. +""", +] +BillingScreenCountryNames = { + "US" : "Estados Unidos de América", + "CA" : "Canadá", + "AF" : "Afganistán", + "AL" : "Albania", + "DZ" : "Argelia", + "AS" : "Samoa estadounidense", + "AD" : "Andorra", + "AO" : "Angola", + "AI" : "Anguilla", + "AQ" : "Antártida", + "AG" : "Antigua y Barbuda", + "AR" : "Argentina", + "AM" : "Armenia", + "AW" : "Aruba", + "AU" : "Australia", + "AT" : "Austria", + "AZ" : "Azerbaiyán", + "BS" : "Bahamas", + "BH" : "Bahréin", + "BD" : "Bangladesh", + "BB" : "Barbados", + "BY" : "Bielorrusia", + "BE" : "Bélgica", + "BZ" : "Belice", + "BJ" : "Benín", + "BM" : "Bermudas", + "BT" : "Bután", + "BO" : "Bolivia", + "BA" : "Bosnia y Herzegovina", + "BW" : "Botsuana", + "BV" : "Isla de Bouvet", + "BR" : "Brasil", + "IO" : "Territorio oceánico de las Indias Británicas", + "BN" : "Brunéi Darussalam", + "BG" : "Bulgaria", + "BF" : "Burkina Faso", + "BI" : "Burundi", + "KH" : "Camboya", + "CM" : "Camerún", + "CV" : "Cabo Verde", + "KY" : "Islas Caimán", + "CF" : "República Centroafricana", + "TD" : "Chad", + "CL" : "Chile", + "CN" : "China", + "CX" : "Isla de Navidad", + "CC" : "Islas Cocos", + "CO" : "Colombia", + "KM" : "Comoras", + "CG" : "Congo", + "CK" : "Islas Cook ", + "CR" : "Costa Rica", + "CI" : "Costa de Marfil", + "HR" : "Croacia", + "CU" : "Cuba", + "CY" : "Chipre", + "CZ" : "República Checa", + "CS" : "Checoslovaquia (anteriormente)", + "DK" : "Dinamarca", + "DJ" : "Yibuti", + "DM" : "Dominica", + "DO" : "República Dominicana", + "TP" : "Timor Oriental", + "EC" : "Ecuador", + "EG" : "Egipto", + "SV" : "El Salvador", + "GQ" : "Guinea Ecuatorial", + "ER" : "Eritrea", + "EE" : "Estonia", + "ET" : "Etiopía", + "FK" : "Islas Malvinas", + "FO" : "Islas Feroe", + "FJ" : "Fiyi", + "FI" : "Finlandia", + "FR" : "Francia", + "FX" : "Francia (Europa)", + "GF" : "Guyana Francesa", + "PF" : "Polinesia Francesa", + "TF" : "Territorios franceses de los Mares del Sur", + "GA" : "Gabón", + "GM" : "Gambia", + "GE" : "Georgia", + "DE" : "Alemania", + "GH" : "Ghana", + "GI" : "Gibraltar", + "GB" : "Reino Unido", + "GR" : "Grecia", + "GL" : "Groenlandia", + "GD" : "Isla de Granada", + "GP" : "Guadalupe", + "GU" : "Guam", + "GT" : "Guatemala", + "GN" : "Guinea", + "GW" : "Guinea-Bissau", + "GY" : "Guyana", + "HT" : "Haití", + "HM" : "Islas Heard y McDonald", + "HN" : "Honduras", + "HK" : "Hong Kong", + "HU" : "Hungría", + "IS" : "Islandia", + "IN" : "India", + "ID" : "Indonesia", + "IR" : "Irán", + "IQ" : "Irak", + "IE" : "Irlanda", + "IL" : "Israel", + "IT" : "Italia", + "JM" : "Jamaica", + "JP" : "Japón", + "JO" : "Jordania", + "KZ" : "Kazajstán", + "KE" : "Kenia", + "KI" : "Kiribati", + "KP" : "Corea del Norte", + "KR" : "Corea del Sur", + "KW" : "Kuwait", + "KG" : "Kirguistán", + "LA" : "Laos", + "LV" : "Letonia", + "LB" : "Líbano", + "LS" : "Lesoto", + "LR" : "Liberia", + "LY" : "Libia", + "LI" : "Liechtenstein", + "LT" : "Lituania", + "LU" : "Luxemburgo", + "MO" : "Macao", + "MK" : "Macedonia", + "MG" : "Madagascar", + "MW" : "Malawi", + "MY" : "Malasia", + "MV" : "Maldivas", + "ML" : "Malí", + "MT" : "Malta", + "MH" : "Islas Marshall", + "MQ" : "Martinica", + "MR" : "Mauritania", + "MU" : "Mauricio", + "YT" : "Mayotte", + "MX" : "México", + "FM" : "Micronesia", + "MD" : "Moldavia", + "MC" : "Mónaco", + "MN" : "Mongolia", + "MS" : "Montserrat", + "MA" : "Marruecos", + "MZ" : "Mozambique", + "MM" : "Myanmar", + "NA" : "Namibia", + "NR" : "Nauru", + "NP" : "Nepal", + "NL" : "Países Bajos", + "AN" : "Antillas Holandesas", + "NT" : "Zona neutral", + "NC" : "Nueva Caledonia", + "NZ" : "Nueva Zelanda", + "NI" : "Nicaragua", + "NE" : "Níger", + "NG" : "Nigeria", + "NU" : "Niue", + "NF" : "Isla Norfolk", + "MP" : "Islas Marianas Septentrionales", + "NO" : "Noruega", + "OM" : "Omán", + "PK" : "Pakistán", + "PW" : "Paláu", + "PA" : "Panamá", + "PG" : "Papua Nueva Guinea", + "PY" : "Paraguay", + "PE" : "Perú", + "PH" : "Filipinas", + "PN" : "Pitcairn", + "PL" : "Polonia", + "PT" : "Portugal", + "PR" : "Puerto Rico", + "QA" : "Qatar", + "RE" : "Reunión", + "RO" : "Rumanía", + "RU" : "Federación Rusa", + "RW" : "Ruanda", + "GS" : "Islas Meridionales de Georgia y Sandwich", + "KN" : "Saint Kitts y Nevis", + "LC" : "Santa Lucía", + "VC" : "San Vicente y las Granadinas", + "WS" : "Samoa", + "SM" : "San Marino", + "ST" : "Santo Tomé y Príncipe", + "SA" : "Arabia Saudí ", + "SN" : "Senegal", + "SC" : "Seychelles", + "SL" : "Sierra Leona", + "SG" : "Singapur", + "SK" : "República Eslovaca", + "SI" : "Eslovenia", + "Sb" : "Islas Salomón", + "SO" : "Somalia", + "ZA" : "República Sudafricana", + "ES" : "España", + "LK" : "Sri Lanka", + "SH" : "Santa Elena", + "PM" : "St. Pierre y Miquelon", + "SD" : "Sudán", + "SR" : "Surinam", + "SJ" : "Islas Svalbard y Jan Mayen", + "SZ" : "Suazilandia", + "SE" : "Suecia", + "CH" : "Suiza", + "SY" : "Siria", + "TW" : "Taiwán", + "TJ" : "Tayikistán", + "TZ" : "Tanzania", + "TH" : "Tailandia", + "TG" : "Togo", + "TK" : "Tokelau", + "TO" : "Tonga", + "TT" : "Trinidad y Tobago", + "TN" : "Túnez", + "TR" : "Turquía", + "TM" : "Turkmenistán", + "TC" : "Islas Turks y Caicos", + "TV" : "Tuvalu", + "UG" : "Uganda", + "UA" : "Ucrania", + "AE" : "Emiratos Árabes Unidos", + "UK" : "Reino Unido", + "UY" : "Uruguay", + "UM" : "Islas adyacentes a los EE.UU.", + "SU" : "URSS (anteriormente)", + "UZ" : "Uzbekistán", + "VU" : "Vanuatu", + "VA" : "Ciudad del Vaticano", + "VE" : "Venezuela", + "VN" : "Vietnam", + "VG" : "Islas Vírgenes Británicas", + "VI" : "Islas Vírgenes Estadounidenses", + "WF" : "Islas Wallis y Futuna", + "EH" : "Sahara Occideental", + "YE" : "Yemen", + "YU" : "Yugoslavia", + "ZR" : "Zaire", + "ZM" : "Zambia", + "ZW" : "Zimbabue", + } +BillingScreenStateNames = { + "AL" : "Alabama", + "AK" : "Alaska", + "AR" : "Arkansas", + "AZ" : "Arizona", + "CA" : "California", + "CO" : "Colorado", + "CT" : "Connecticut", + "DE" : "Delaware", + "FL" : "Florida", + "GA" : "Georgia", + "HI" : "Hawai", + "IA" : "Iowa", + "ID" : "Idaho", + "IL" : "Illinois", + "IN" : "Indiana", + "KS" : "Kansas", + "KY" : "Kentucky", + "LA" : "Lousiana", + "MA" : "Massachusetts", + "MD" : "Maryland", + "ME" : "Maine", + "MI" : "Míchigan", + "MN" : "Minnesota", + "MO" : "Missouri", + "MS" : "Misisipi", + "MT" : "Montana", + "NE" : "Nebraska", + "NC" : "Carolina del Norte", + "ND" : "Dakota del Norte ", + "NH" : "Nuevo Hampshire", + "NJ" : "Nueva Jersey", + "NM" : "Nuevo México", + "NV" : "Nevada", + "NY" : "Nueva York", + "OH" : "Ohio", + "OK" : "Oklahoma", + "OR" : "Oregón", + "PA" : "Pensilvania", + "RI" : "Rhode Island", + "SC" : "Carolina del Sur ", + "SD" : "Dakota del Sur ", + "TN" : "Tennessee", + "TX" : "Texas", + "UT" : "Utah", + "VA" : "Virginia", + "VT" : "Vermont", + "WA" : "Washington", + "WI" : "Wisconsin", + "WV" : "Virginia Occidental", + "WY" : "Wyoming", + "DC" : "Distrito de Columbia", + "AS" : "Samoa estadounidense", + "GU" : "Guam", + "MP" : "Islas Marianas Septentrionales", + "PR" : "Puerto Rico", + "VI" : "Islas Vírgenes Estadounidenses", + "FPO" : ["Isla de Midway", + "Arrecife Kingman", + ], + "APO" : ["Isla Wake", + "Isla Johnston", + ], + "MH" : "Islas Marshall", + "PW" : "Paláu", + "FM" : "Micronesia", + } +BillingScreenCanadianProvinces = { + 'AB' : 'Alberta', + 'BC' : 'Columbia Británica', + 'MB' : 'Manitoba', + 'NB' : 'Nueva Brunswick', + 'NF' : 'Newfoundland', + 'NT' : 'Territorios del Noroeste', + 'NS' : 'Nueva Escocia', + #'XX' : 'Nunavut', + 'ON' : 'Ontario', + 'PE' : 'Isla Prince Edward', + 'QC' : 'Québec', + 'SK' : 'Saskatchewan', + 'YT' : 'Yukon', + } + +ParentPassword = "Contraseña parental" + +# WelcomeScreen.py +WelcomeScreenHeading = "¡Bienvenido!" +WelcomeScreenOk = "¡VAMOS A JUGAR!" +WelcomeScreenSentence1 = "Ahora eres socio oficial de" +WelcomeScreenToontown = "Disney's Toontown Online" +WelcomeScreenSentence2 = "No te olvides de buscar más adelante en el correo electrónico las sorprendentes noticias sobre Disney's Toontown Online." + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Para ponerte en contacto con el Servicio de atención al cliente, llama al %s." +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\n\nSi necesitas ayuda, ponte en contacto con el Servicio de atención al cliente, en el número %s." +TTAccountIntractibleError = "Se ha producido un error." + +# LoginScreen.py +LoginScreenUserName = "Nombre de la cuenta" +LoginScreenPassword = "Contraseña" +LoginScreenLogin = "Inicio de sesión" +LoginScreenCreateAccount = "Crear cuenta" +LoginScreenForgotPassword = "¿Has olvidado la contraseña?" +LoginScreenQuit = "Salir" +LoginScreenLoginPrompt = "Introduce un nombre de usuario y una contraseña." +LoginScreenBadPassword = "Contraseña incorrecta.\nInténtalo de nuevo." +LoginScreenInvalidUserName = "Nombre de usuario incorrecto.\nInténtalo de nuevo." +LoginScreenUserNameNotFound = "No se ha encontrado el nombre de usuario.\nInténtalo de nuevo o crea otra cuenta." +LoginScreenPeriodTimeExpired = "Lo sentimos, pero ya has gastado todos los minutos de que disponías en Toontown este mes. Vuelve a principios del mes que viene." +LoginScreenNoNewAccounts = "Lo sentimos mucho, pero no aceptamos nuevas cuentas en este momento." +LoginScreenTryAgain = "Inténtalo de nuevo" + +# NewPlayerScreen.py +NewPlayerScreenNewAccount = "Empezar prueba gratuita" +NewPlayerScreenLogin = "Socio Activo" +NewPlayerScreenQuit = "Salir" + +# FreeTimeInformScreen.py +FreeTimeInformScreenDontForget = "No te olvides de que tu prueba gratuita\ncaducará en " +FreeTimeInformScreenNDaysLeft = FreeTimeInformScreenDontForget + "¡sólo %s días!" +FreeTimeInformScreenOneDayLeft = FreeTimeInformScreenDontForget + "¡un día!" +FreeTimeInformScreenNHoursLeft = FreeTimeInformScreenDontForget + "¡sólo %s horas!" +FreeTimeInformScreenOneHourLeft = FreeTimeInformScreenDontForget + "¡una hora!" +FreeTimeInformScreenLessThanOneHourLeft = FreeTimeInformScreenDontForget + "¡menos de una hora!" +FreeTimeInformScreenSecondSentence = "Pero todavía tienes tiempo para hacerte\nsocio oficial de Disney's Toontown Online." +FreeTimeInformScreenOops = "¡VAYA!" +FreeTimeInformScreenExpired = " , tu prueba gratuita ha caducado.\n¿Deseas convertirte en socio oficial de Disney's Toontown Online?\n¡Inscríbete ahora y diviértete como nunca!" +FreeTimeInformScreenExpiredQuitText = "¿No puedes hacerlo ahora mismo? No te preocupes, te guardaremos\nel dibu. Pero ¡date prisa! Sólo podemos\nguardarte el dibu durante una semana después\nde que haya caducado tu prueba gratuita." +FreeTimeInformScreenExpiredCCUF = "Todavía no has adquirido Disney's\nToontown Online. Para usar esta cuenta\ndebes registrarte ahora con una tarjeta de crédito.\n¡Inscríbete ahora y vuelve a divertirte como nunca!" +FreeTimeInformScreenExpiredQuitCCUFText = "¿No puedes hacerlo ahora mismo? No te preocupes, te guardaremos\nla cuenta. Pero ¡date prisa! Sólo podemos\nguardarte la cuenta durante una semana." +FreeTimeInformScreenPurchase = "¡Suscríbete ya!" +FreeTimeInformScreenFreePlay = "Continuar prueba gratuita" +FreeTimeInformScreenQuit = "Salir" + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',] +DateOfBirthEntryDefaultLabel = "Fecha de nacimiento" + +# CreateAccountScreen.py +CreateAccountScreenUserName = "Nombre de la cuenta" +CreateAccountScreenPassword = "Contraseña" +CreateAccountScreenConfirmPassword = "Confirmar contraseña" +CreateAccountScreenFree = "GRATIS" +CreateAccountScreenFreeTrialLength = 'Para empezar tu prueba de %s días\ntienes que crear una cuenta.' +CreateAccountScreenInstructionsUsername = "Escribe el nombre de la cuenta que deseas usar:" +CreateAccountScreenInstructionsPassword = "Escribe una contraseña:" +CreateAccountScreenInstructionsConfirmPassword = "Para asegurarte, escribe de nuevo la contraseña:" +CreateAccountScreenInstructionsDob = "Escribe tu fecha de nacimiento:" +CreateAccountScreenCancel = "Cancelar" +CreateAccountScreenSubmit = "Siguiente" +CreateAccountScreenConnectionErrorSuffix = ".\n\nVuelve a intentarlo más tarde." +CreateAccountScreenNoAccountName = "Escribe un nombre de cuenta." +CreateAccountScreenAccountNameTooShort = "El nombre de cuenta debe tener al menos %s caracteres. Inténtalo de nuevo." +CreateAccountScreenPasswordTooShort = "La contraseña debe tener al menos %s caracteres. Inténtalo de nuevo." +CreateAccountScreenPasswordMismatch = "Las contraseñas que has escrito no coinciden. Inténtalo de nuevo." +CreateAccountScreenInvalidDob = "Escribe tu fecha de nacimiento." +CreateAccountScreenUserNameTaken = "Ese nombre de usuario ya existe. Inténtalo de nuevo." +CreateAccountScreenInvalidUserName = "Nombre de usuario no válido.\nInténtalo de nuevo." +CreateAccountScreenUserNameNotFound = "No se ha encontrado el nombre de usuario.\nInténtalo de nuevo o crea otra cuenta." +CreateAccountScreenEmailInstructions = "Escribe tu dirección de correo electrónico.\n¿Por qué? Por dos razones:\n1. Si olvidas la contraseña, te la podremos enviar.\n2. Podemos enviarte la información más reciente\nsobre Disney's Toontown Online." +CreateAccountScreenEmailInstructionsUnder13 = "Has indicado que tienes menos de 13 años.\nPara crear una cuenta necesitamos la dirección de correo electrónico de tus padres o tu tutor." +CreateAccountScreenEmailConfirm = "Para asegurarte, escribe de nuevo la dirección de correo electrónico." +CreateAccountScreenEmailPanelSubmit = "Siguiente" +CreateAccountScreenEmailPanelCancel = "Cancelar" +CreateAccountScreenInvalidEmail = "Escribe la dirección de correo electrónico completa." +CreateAccountScreenEmailMismatch = "Las direcciones de correo electrónico que has introducido no coinciden. Inténtalo de nuevo." + +# SecretFriendsInfoPanel.py +SecretFriendsInfoPanelOk = "Aceptar" +SecretFriendsInfoPanelText = [""" +La herramienta Amigos secretos + +La herramienta Amigos secretos permite a los socios conversar directamente entre sí mediante en Disney's Toontown Online (en lo sucesivo, el "Servicio"). Para ello, los socios deben establecer una conexión de Amigos secretos. Cuando su hijo intente usar la herramienta Amigos secretos, le pediremos que introduzca su contraseña parental para confirmar su consentimiento al uso de dicha herramienta. A continuación se describe con detalle proceso de creación de una conexión de Amigos secretos entre dos socios imaginarios a los que llamaremos "Susana" y "Miguel". +1. Los padres de Susana y los de Miguel activan la herramienta Amigos secretos, introduciendo sus contraseñas parentales (a) en las Opciones de cuenta del Servicio o (b) cuando el juego se lo requiera en una ventana emergente de Control parental. +2. Susana solicita un Secreto (descrito más adelante) desde dentro del Servicio. +""",""" +3. El Secreto de Susana se envía a Miguel fuera del Servicio. (Susana le puede comunicar su Secreto a Miguel directamente o indirectamente, a través de otra persona.) +4. Miguel envía el Secreto de Susana al Servicio en un plazo de 48 horas a partir del momento en que Susana lo ha solicitado. +5. El Servicio comunica a Miguel que Susana se ha convertido en su amiga secreta. De igual modo, el Servicio notifica a Susana que Miguel se ha convertido en su amigo secreto. +6. Ahora, Susana y Miguel pueden charlar entre sí hasta que uno de ellos decida que el otro deje de ser su amigo secreto o hasta que los padres de Susana o Miguel desactiven la herramienta Amigos secretos. Por tanto, la conexión Amigos secretos puede ser desactivada en cualquier momento por: +""",""" +(a) Un socio que borre a otro de su lista de amigos secretos (de la forma +descrita en el Servicio) o (b) los padres de un socio que desactiven la herramienta Amigos secretos por medio de las Opciones de cuenta del Servicio (siguiendo los pasos allí establecidos). + +Un Secreto es un código informático generado al azar y asignado a un socio en concreto. Es necesario usar el Secreto para activar la conexión de Amigos secretos dentro de un plazo de 48 horas a partir del momento de su solicitud. De lo contrario, el Secreto caduca y no se puede utilizar. Además, un secreto sólo se puede usar para establecer una sola conexión de Amigos Secretos. Para crear más conexiones de Amigos secretos, el socio debe solicitar un Secreto adicional para cada uno de los nuevos amigos. + +Las amistades secretas no se transfieren. Por ejemplo, si Susana es amiga +""",""" +secreta de Miguel y Miguel es amigo secreto de Ana, Susana no se convierte automáticamente en amiga secreta de Ana. Para que Susana y Ana se hagan +amigas secretas, una de ellas deberá solicitar un nuevo Secreto al Servicio y +comunicárselo a la otra. + +Los amigos secretos se comunican entre sí mediante un servicio de conversación interactivo de formato libre. El contenido de esta conversación es escrito directamente por el socio participante y procesado a través del Servicio, que está gestionado por Walt Disney Internet Group ("WDIG"), 506 2nd Avenue, Suite 2100, Seattle, WA 98104, EE.UU. (teléfono +1 (509) 742-4698; correo electrónico: ms_support@help.go.com). Aunque aconsejamos a los socios que no intercambien datos personales, como sus nombres y apellidos, direcciones de correo electrónico, direcciones postales o números de teléfono mientras utilizan la herramienta Amigos secretos, no +""",""" +podemos garantizar que tales intercambios de información personal no se produzcan. Aunque el servicio de conversación de Amigos secretos tiene un filtro automático de palabras malsonantes y obscenas, no está moderado ni supervisado por nosotros. Si los padres permiten a sus hijos usar su cuenta con la opción Amigos secretos activada, les aconsejamos que los supervisen mientras juegan en el Servicio.. + +WDIG no hace uso del contenido de las conversaciones de Amigos secretos para ningún otro propósito que no sea el de comunicar dicho contenido al amigo secreto del socio, y no divulga ese contenido a terceros excepto en los siguientes casos: (1) Si la ley lo requiere, por ejemplo, para acatar una orden o citación judicial; (2) para hacer cumplir las Condiciones de uso aplicables al Servicio (a las que se puede acceder en la página principal del Servicio); (3) para proteger la seguridad de los socios del Servicio y del Servicio mismo. +""",""" +Los padres de un niño pueden, previa petición a WDIG, revisar y borrar el contenido de cualquier conversación mantenida por ese niño, suponiendo que dicho contenido no haya sido ya borrado por WDIG de sus archivos. Según lo estipulado en la Ley estadounidense para la protección del menor en medios electrónicos (Children's Online Privacy Protection Act), no estamos autorizados a condicionar la participación de un niño en ninguna actividad (lo que incluye Amigos secretos) en base a la revelación por parte del niño de más información personal de la razonablemente necesaria para participar en tal actividad. + +Además, tal y como se menciona anteriormente, reconocemos el derecho de los padres a negarse a permitir que el niño siga utilizando la herramienta Amigos secretos. Al activar la herramienta Amigos secretos, los padres reconocen que existen ciertos riesgos inherentes a la posibilidad de charlar de los socios por medio de dicha herramienta, y reconocen que han sido informados sobre dichos riesgos y están de acuerdo en aceptarlos. +""" +] + +# ParentPasswordScreen.py +ParentPasswordScreenTitle = "Controles parentales" +ParentPasswordScreenPassword = "Crear contraseña parental" +ParentPasswordScreenConfirmPassword = "Confirmar contraseña parental" +ParentPasswordScreenSubmit = "Establecer contraseña parental" +ParentPasswordScreenConnectionErrorSuffix = ".\nVuelva a intentarlo más tarde." +ParentPasswordScreenPasswordTooShort = "La contraseña debe tener al menos %s caracteres. Inténtelo de nuevo." +ParentPasswordScreenPasswordMismatch = "Las contraseñas que ha escrito no coinciden. Inténtelo de nuevo." +ParentPasswordScreenConnectionProblemJustPaid = "Ha surgido un problema con la conexión con el servidor de la cuenta, pero la adquisición se ha procesado.\n\nLa próxima vez que inicie una sesión se le solicitará que establezca la contraseña parental." +ParentPasswordScreenConnectionProblemJustLoggedIn = "Ha surgido un problema en la conexión con el servidor de la cuenta. Vuelva a intentarlo más tarde." +ParentPasswordScreenSecretFriendsMoreInfo = "Más información" +ParentPasswordScreenInstructions = """Cree una contraseña parental para esta cuenta. La contraseña parental se le solicitará más adelante: + + 1. Cuando le pidamos consentimiento para que tu hijo + use ciertas herramientas interactivas de Toontown + tales como la herramienta "Amigos secretos". Si desea + una descripción completa de la herramienta Amigos + secretos, que permite que sus hijos se + comuniquen en línea con otros socios de Toontown, + pulse el botón """+ParentPasswordScreenSecretFriendsMoreInfo+"""', situado debajo. Es necesario que dé su consentimiento + para activar esta herramienta. + + + 2. Para actualizar la información sobre la facturación + y la cuenta desde la página web de Toontown. +""" +ParentPasswordScreenAdvice = "Recuerde que la contraseña parental debe ser confidencial. Mantenerla a salvo es fundamental para conservar el control sobre el uso de las herramientas interactivas de la cuenta por parte de su hijo. " +ParentPasswordScreenPrivacyPolicy = "Normas de confidencialidad" + + +# ForgotPasswordScreen.py +ForgotPasswordScreenTitle = "Si ha olvidado la contraseña, se la podemos enviar." +ForgotPasswordScreenInstructions = "Introduzca el nombre de su cuenta o la dirección de correo electrónico que nos facilitó." +ForgotPasswordScreenEmailEntryLabel = "Dirección de correo electrónico" +ForgotPasswordScreenOr = "O bien" +ForgotPasswordScreenAcctNameEntryLabel = "Nombre de la cuenta" +ForgotPasswordScreenSubmit = "Enviar" +ForgotPasswordScreenCancel = "Cancelar" +ForgotPasswordScreenEmailSuccess = "Su contraseña ha sido enviada a '%s'." +ForgotPasswordScreenEmailFailure = "Dirección de correo electrónico no encontrada: '%s'." +ForgotPasswordScreenAccountNameSuccess = "Su contraseña ha sido enviada a la dirección de correo electrónico que nos facilitó al crear la cuenta." +ForgotPasswordScreenAccountNameFailure = "Cuenta no encontrada: %s" +ForgotPasswordScreenNoEmailAddress = "Esa cuenta ha sido creada por un menor de 13 años y no tiene una dirección de correo electrónico. No podemos enviarle la contraseña.\n\n¡Cree otra cuenta si lo desea!" +ForgotPasswordScreenInvalidEmail = "Introduzca una dirección de correo electrónico válida." + +# GuiScreen.py +GuiScreenToontownUnavailable = "Toontown no parece estar disponible por el momento, seguimos intentándolo..." +GuiScreenCancel = "Cancelar" + +# AchievePage.py +AchievePageTitle = "Logros\n(próximamente)" + +# PhotoPage.py +PhotoPageTitle = "Foto\n(próximamente)" + +# BuildingPage.py +BuildingPageTitle = "Edificios\n(próximamente)" + +# InventoryPage.py +InventoryPageTitle = "Bromas" +InventoryPageDeleteTitle = "BORRAR BROMAS" +InventoryPageTrackFull = "Tienes todas las bromas del circuito %s. " +InventoryPagePluralPoints = "Conseguirás una nueva\nbroma de %(trackName)s cuando\nconsigas %(numPoints)s puntos más de %(trackName)s." +InventoryPageSinglePoint = "Conseguirás una nueva\nbroma de %(trackName)s cuando\nconsigas %(numPoints)s punto más de %(trackName)s." +InventoryPageNoAccess = "Todavía no tienes acceso al circuito %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS dibus" + +# MapPage.py +MapPageTitle = "Mapa" +MapPageBackToPlayground = "Volver al dibuparque" +MapPageBackToCogHQ = "Regresar al cuartel general bot" +MapPageGoHome = "Ir a casa" +# hood name, street name +MapPageYouAreHere = "Estás en: %s\n%s" +MapPageYouAreAtHome = "Estás en\ntu propiedad" +MapPageYouAreAtSomeonesHome = "Estás en\nla propiedad %s" +MapPageGoTo = "Ir a\n%s" + +# OptionsPage.py +OptionsPageTitle = "Opciones" +OptionsPagePurchase = "¡Suscríbete ya!" +OptionsPageLogout = "Cerrar sesión" +OptionsPageExitToontown = "Salir de Toontown" +OptionsPageMusicOnLabel = "La música está activada." +OptionsPageMusicOffLabel = "La música está desactivada." +OptionsPageSFXOnLabel = "Los efectos de sonido están activados." +OptionsPageSFXOffLabel = "Los efectos de sonido están desactivados." +OptionsPageFriendsEnabledLabel = "Se aceptan solicitudes de nuevos amigos." +OptionsPageFriendsDisabledLabel = "No se aceptan solicitudes de nuevos amigos." +OptionsPageSpeedChatStyleLabel = "Color para la Charla rápida" +OptionsPageDisplayWindowed = "en ventana" +OptionsPageSelect = "Escoger" +OptionsPageToggleOn = "Activar" +OptionsPageToggleOff = "Desactivar" +OptionsPageChange = "Cambiar" +OptionsPageDisplaySettings = "Pantalla: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Pantalla: %(screensize)s" +OptionsPageExitConfirm = "¿Quieres salir de Toontown?" + +DisplaySettingsTitle = "Configuración de la pantalla" +DisplaySettingsIntro = "Los siguientes parámetros sirven para configurar el aspecto de Toontown en tu ordenador. Lo más probable es que no haga falta modificarlos a no ser que surja algún problema." +DisplaySettingsIntroSimple = "Usted puede ajustar la resolucion de su pantalla a un valor más alto, para mejorar la claridad gráfica y de texto, pero todo depende de la tarjeta gráfica, un valor más alto puede hacer el juego menos fluido o que no trabaje del todo" + +DisplaySettingsApi = "Interfaz gráfica:" +DisplaySettingsResolution = "Resolución:" +DisplaySettingsWindowed = "En ventana" +DisplaySettingsFullscreen = "Pantalla completa" +DisplaySettingsApply = "Aplicar" +DisplaySettingsCancel = "Cancelar" +DisplaySettingsApplyWarning = "Cuando pulses Aceptar cambiará la configuración gráfica. Si la nueva configuración no se representa correctamente en tu ordenador, la pantalla volverá automáticamente a la configuración original transcurridos %s segundos." +DisplaySettingsAccept = "Pulsa Aceptar para conservar la nueva configuración, o Cancelar para volver a la anterior. Si no pulsas nada, se volverá a la configuración anterior al cabo de %s segundos." +DisplaySettingsRevertUser = "Se ha restablecido la configuración anterior de la pantalla. " +DisplaySettingsRevertFailed = "La configuración de pantalla seleccionada no funciona en tu ordenador. Se ha restablecido la configuración anterior de la pantalla. " + + +# TrackPage.py +TrackPageTitle = "Circuito Entrenador de Bromas" +TrackPageShortTitle = "Entrenador de Bromas" +TrackPageSubtitle = "¡Completa las dibutareas para aprender a usar las bromas nuevas!" +TrackPageTraining = "Estás entrenándote para usar las bromas de %s.\nCuando completes las 16 tareas,\npodrás usar las bromas de %s en los combates." +TrackPageClear = "Ahora mismo no te estás entrenando en ningún circuito de bromas." +TrackPageFilmTitle = "%s\nPelícula de\nentrenamiento" +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "Dibutareas" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nPara: %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nPara elegir, ve a:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Elige" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = "Funcionario del cuartel general" +QuestPosterHQBuildingName = "Cuartel General Dibu" +QuestPosterHQStreetName = "Cualquier calle" +QuestPosterHQLocationName = "Cualquier barrio" + +QuestPosterTailor = "Sastre" +QuestPosterTailorBuildingName = "Tienda de Ropa" +QuestPosterTailorStreetName = "Cualquier Dibuparque" +QuestPosterTailorLocationName = "Cualquier barrio" +QuestPosterPlayground = "En el Dibuparque" +QuestPosterAtHome = "At your home" +QuestPosterInHome = "In your home" +QuestPosterOnPhone = "On your phone" +QuestPosterEstate = "At your estate" +QuestPosterAnywhere = "Cualquier parte" +QuestPosterAuxTo = "hacia:" +QuestPosterAuxFrom = "de:" +QuestPosterAuxFor = "para:" +QuestPosterAuxOr = "o:" +QuestPosterAuxReturnTo = "Vuelve a:" +QuestPosterLocationIn = " en " +QuestPosterLocationOn = " en " +QuestPosterFun = "¡Para Divertirse!" +QuestPosterFishing = "Anda a pescar" +QuestPosterComplete = "COMPLETADA" + +# ShardPage.py +ShardPageTitle = "Distritos" +ShardPageHelpIntro = "Cada distrito es una copia del mundo de Toontown." +ShardPageHelpWhere = "Ahora tú estás en el distrito \"%s\"." +ShardPageHelpWelcomeValley = "Ahora tú estás en el distrito \"Valle Bienvenido\", dentro de \"%s\"." +ShardPageHelpMove = "Para desplazarte a otro distrito, haz clic en su nombre." + +ShardPagePopulationTotal = "N.º total de habitantes de Toontown:\n%d" +ShardPageScrollTitle = "Nombre Habitantes" + +# SuitPage.py +SuitPageTitle = "Galería de bots" +SuitPageMystery = DialogQuestion + DialogQuestion + DialogQuestion +SuitPageQuota = "%s de %s" +SuitPageCogRadar = "%s presente" +SuitPageBuildingRadarS = "%s edificio" +SuitPageBuildingRadarP = "%s edificios" + +# DisguisePage.py +DisguisePageTitle = "Disfraces de" + Cog +DisguisePageMeritAlert = "¡Listo para un ascenso!" +DisguisePageCogLevel = "Nivel %s" +DisguisePageMeritFull = "Lleno" +DisguisePageMeritBar = "Merit Progress" +DisguisePageCogPartRatio = "%d / %d" + +# FishPage.py +FishPageTitle = "Pecera" +FishPageTitleTank = "Cubo para los peces" +FishPageTitleCollection = "Álbum de peces" +FishPageTitleTrophy = "Trofeos de pesca" +FishPageWeightStr = "Peso: " +FishPageWeightLargeS = "%d kg. " +FishPageWeightLargeP = "%d kg. " +FishPageWeightSmallS = "%d gr." +FishPageWeightSmallP = "%d gr." +FishPageWeightConversion = 16 +FishPageValueS = "Valor: %d gominola" +FishPageValueP = "Valor: %d gominolas" +FishPageTotalValue = "" +FishPageCollectedTotal = "Especies de peces recogidas: %d de %d" +FishPageRodInfo = "%s Caña de\n%d - %d kilos" +FishPageTankTab = "Cubo" +FishPageCollectionTab = "Álbum" +FishPageTrophyTab = "Trofeos" + +FishPickerTotalValue = "Cubo: %s / %s\nValor: %d gominolas" + +UnknownFish = "???" + +FishingRod = "Caña de %s" +FishingRodNameDict = { + 0 : "rama", + 1 : "bambú", + 2 : "madera", + 3 : "acero", + 4 : "oro", + } +FishTrophyNameDict = { + 0 : "Boquerón", + 1 : "Salmonete", + 2 : "Merluza", + 3 : "Pez volador", + 4 : "Tiburón", + } + +# QuestChoiceGui.py +QuestChoiceGuiCancel = "Cancelar" + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Elige" +TrackChoiceGuiCancel = "Cancelar" +TrackChoiceGuiHEAL = 'Curadibu te permite sanar a otros dibus durante el combate.' +TrackChoiceGuiTRAP = 'Las trampas son potentes bromas que se deben usar con cebos.' +TrackChoiceGuiLURE = 'los cebos permiten aturdir a los bots y atraerlos a las trampas.' +TrackChoiceGuiSOUND = 'Las bromas de sonido afectan a todos los bots, pero no son muy potentes.' +TrackChoiceGuiDROP = "Las bromas de caída causan un montón de daños, pero no son muy precisas." + +# EmotePage.py +EmotePageTitle = "Expresiones / Emociones" +EmotePageDance = "Has creado la siguiente secuencia de baile:" +EmoteJump = "Saltar" +EmoteDance = "Bailar" +EmoteHappy = "Feliz" +EmoteSad = "Triste" +EmoteAnnoyed = "Fastidiado" +EmoteSleep = "Soñoliento" + +# Emote.py +# List of emotes in the order they should appear in the SpeedChat. +# Must be in the same order as the function list (EmoteFunc) in Emote.py +EmoteList = [ + "Saludar", + "Contento", + "Triste", + "Enfadado", + "Soñoliento", + "Encoger hombros", + "Bailar", + "Guiñar ojo", + "Aburrido", + "Aplaudir", + "Sorprendido", + "Confundido", + "Mofarse", + "Inclinarse", + "Muy triste", + "Hola", + "Adiós", + "Sí", + "No", + "Vale", + ] + +EmoteWhispers = [ + "%s Saluda.", + "%s esta contento.", + "%s esta triste.", + "%s esta enfadado.", + "%s esta soñoliento.", + "%s se encoge de hombros.", + "%s baila.", + "%s guiña un ojo.", + "%s esta aburrido.", + "%s aplaude.", + "%s esta sorprendido.", + "%s esta confundido.", + "%s se mofa de tí.", + "%s te hace una reverencia.", + "%s esta muy triste.", + "%s dice 'Hola'.", + "%s dice 'Adiós'.", + "%s dice 'Sí'.", + "%s dice 'No'.", + "%s dice 'Vale'.", + ] + +# Reverse lookup: get the index from the name. +EmoteFuncDict = { + "saludar" : 0, + "contento" : 1, + "triste" : 2, + "enfadado" : 3, + "soñoliento" : 4, + "reír" : 5, + "bailar" : 6, + "guiñar ojo": 7, + "aburrido" : 8, + "aplaudir" : 9, + "sorprendido" : 10, + "confundido" : 11, + "mofarse" : 12, + "inclinarse" : 13, + "muy triste" : 14, + "hola" : 15, + "adiós" : 16, + "sí" : 17, + "no" : 18, + "vale" : 19, + } + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNivel %(level)s" + +# SuitDialog.py +SuitBrushOffs = { + 'f': ["Llego tarde a una reunión", + ], + 'p': ["Lárgate", + ], + 'ym': ['Al sonriente no le hace gracia', + ], + None: ["Es mi día libre", + "Creo que te has equivocado de despacho", + "Que tu secretaria llame a la mía", + "No tengo tiempo para reunirme contigo", + "Habla con mi ayudante"] + } + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "¡No puedes irte del dibuparque hasta que tu risómetro esté sonriendo!" + +# InventoryNew.py +InventoryTotalGags = "Bromas totales\n%d / %d" +InventoryDelete = "BORRAR" +InventoryDone = "HECHO" +InventoryDeleteHelp = "Haz clic en una broma para BORRARLA." +InventorySkillCredit = "Habilidad: %s" +InventorySkillCreditNone = "Habilidad: Ninguna" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Precisión: %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryAffectsOneCog = "Afecta a: Un " + Cog +InventoryAffectsOneToon = "Afecta a: Un dibu" +InventoryAffectsAllToons = "Afecta a: Todos los dibus" +InventoryAffectsAllCogs = "Afecta a: Todos los " + Cogs +InventoryHealString = "Curadibu" +InventoryDamageString = "Daños" +InventoryBattleMenu = "MENÚ DE COMBATE" +InventoryRun = "CORRER" +InventorySOS = "S.O.S." +InventoryPass = "PASAR" +InventoryClickToAttack = "Haz clic en \nuna broma \npara atacar" + +# NPCForceAcknowledge.py +#NPCForceAcknowledgeMessage = "Visit " + Flippy + " to get your first ToonTask before leaving.\n\n\n\nYou can find\n" + Flippy + " inside\nToonHall." +NPCForceAcknowledgeMessage = "Antes de marcharte debes subirte en el tranvia.\n\n\n\n\n\nEl tranvia esta al lado de la tienda de bromas de Goofy." +NPCForceAcknowledgeMessage2 = "!Enhorabuena por completar tu tarea del tranvia!\nVe al cuartel general dibu para recibir tu recompensa.\n\n\n\n\n\n\nEl cuartel general dibu esta cerca del centro del dibuparque." +NPCForceAcknowledgeMessage3 = "Recuerda que tienes que subirte en el tranvia.\n\n\n\n\n\nEl tranvia esta al lado de la tienda de bromas de Goofy." +NPCForceAcknowledgeMessage4 = "!Enhorabuena! !Has completado la primera dibutarea!\n\n\n\n\n\nVe al cuartel general dibu para recibir tu recompensa." +NPCForceAcknowledgeMessage6 = "Great job defeating those Cogs!\n\n\n\n\n\n\n\n\nHead back to Toon Headquarters as soon as possible." +NPCForceAcknowledgeMessage7 = "Don't forget to make a friend!\n\n\n\n\n\n\nClick on another player and use the New Friend button." +NPCForceAcknowledgeMessage8 = "Great! You made a new friend!\n\n\n\n\n\n\n\n\nYou should go back at Toon Headquarters now." +NPCForceAcknowledgeMessage9 = "Good job using the phone!\n\n\n\n\n\n\n\n\nReturn to Toon Headquarters to claim your reward." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "¡Has recibido 1 punto de lanzamiento! ¡Cuando hayas recibido 10, recibiras una broma nueva!" +MovieTutorialReward2 = "¡Has recibido 1 punto de chorro! Cuando hayas recibido 10, recibiras una broma nueva!" +MovieTutorialReward3 = "¡Buen trabajo! ¡Has completado tu primera Dibutarea!" +MovieTutorialReward4 = "¡Anda al Cuartel General para recibir tu premio!" +MovieTutorialReward5 = "¡Que te entretengas!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['curadibu', 'trampa', 'cebo', 'sonido', 'lanzamiento', 'chorro', 'caída'] +BattleGlobalNPCTracks = ['reaprovisionamiento', 'dibus aciertan', 'bots fallan'] +BattleGlobalAvPropStrings = ( + ('Pluma', 'Megáfono', 'Pintalabios', 'Caña de bambú', 'Polvo de hadas', 'Bolas de malabarista'), + ('Cascara de Plátano', 'Rastrillo', 'Canicas', 'Arena movediza', 'Trampilla', 'TNT'), + ('Billete de 10 euros', 'Imán pequeño', 'Billete de 20 euros', 'Imán grande', 'Billete de 50 euros', 'Gafas hipnóticas'), + ('Bocina de bicicleta', 'Silbato', 'Corneta', 'Sirena', 'Trompa de elefante', 'Sirena de niebla'), + ('Magdalena', 'Trozo de tarta de frutas', 'Trozo de tarta de nata', 'Tarta de frutas entera', 'Tarta de nata entera', 'Tarta de cumpleaños'), + ('Flor chorreante', 'Vaso de agua', 'Pistola de agua', 'Botella de soda', 'Manguera', 'Nube tormentosa'), + ('Maceta', 'Saco de arena', 'Yunque', 'Pesa grande', 'Caja fuerte', 'Piano de cola') + ) +BattleGlobalAvPropStringsSingular = ( + ('una Pluma', 'un Megáfono', 'un Pintalabios', 'una Caña de bambú', 'un Polvo de hadas', 'unas Bolas de malabarista'), + ('una Cascara de Plátano', 'un Rastrillo', 'unas Canicas', 'una Arena movediza', 'una Trampilla', 'un TNT'), + ('un Billete de 10 euros', 'un Imán pequeño', 'un Billete de 20 euros', 'un Imán grande', 'un Billete de 50 euros', 'unas Gafas hipnóticas'), + ('una Bocina de bicicleta', 'un Silbato', 'una Corneta', 'una Sirena', 'una Trompa de elefante', 'una Sirena de niebla'), + ('una Magdalena', 'un Trozo de tarta de frutas', 'un Trozo de tarta de nata', 'una Tarta de frutas entera', 'una Tarta de nata entera', 'una Tarta de cumpleaños'), + ('una Flor chorreante', 'un Vaso de agua', 'una Pistola de agua', 'una Botella de soda', 'una Manguera', 'una Nube tormentosa'), + ('una Maceta', 'un Saco de arena', 'un Yunque', 'una Pesa grande', 'una Caja fuerte', 'un Piano de cola') + ) +BattleGlobalAvPropStringsPlural = ( + ('Plumas', 'Megáfonos', 'Pintalabios', 'Cañas de bambú', 'Polvos de hadas', 'Bolas de malabarista'), + ('Cascaras de Plátano', 'Rastrillos', 'Canicas', 'Arenas movedizas', 'Trampillas','TNT'), + ('Billetes de 10 euros', 'Imanes pequeños', 'Billetes de 20 euros', 'Imanes grandes','Billetes de 50 euros', 'Gafas hipnóticas'), + ('Bocinas de bicicleta', 'Silbatos', 'Cornetas', 'Sirenas', 'Trompas de elefante', 'Sirenas de niebla'), + ('Magdalenas', 'Trozos de tarta de frutas', 'Trozos de tarta de nata','Tartas de frutas enteras', 'Tartas de nata enteras', 'Tartas de cumpleaños'), + ('Flores chorreantes', 'Vasos de agua', 'Pistolas de agua','Botellas de soda', 'Mangueras', 'Nubes tormentosas'), + ('Macetas', 'Sacos de arena', 'Yunques', 'Pesas grandes', 'Cajas fuertes','Pianos de cola') + ) +BattleGlobalAvTrackAccStrings = ("Media", "Perfecta", "Baja", "Alta", "Media", "Alta", "Baja") + +AttackMissed = "Erraste" + +NPCCallButtonLabel = 'LLAMAR' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("a la", "", "Calle del Tutorial"), # Tutorial + 1000 : ("al", "", "Dibuparque"), + 1100 : ("al", "", "Paseo del Percebe"), + 1200 : ("a la", "", "Avenida de las Algas"), + 1300 : ("a la", "", "Calle del Faro"), + 2000 : ("al", "", "Dibuparque"), + 2100 : ("a la", "", "Calle Boba"), + 2200 : ("a la", "", "Calle Locuela"), + 2300 : ("a la", "", "Avenida del Chiste"), + 3000 : ("al", "", "Dibuparque"), + 3100 : ("a la", "", "Calle de la Morsa"), + 3200 : ("a la", "", "Travesía del Trineo"), + 4000 : ("al", "", "Dibuparque"), + 4100 : ("a la", "", "Travesía de la Melodía"), + 4200 : ("al", "", "Bulevar del Barítono"), + 4300 : ("a la", "", "Calle del Tenor"), + 5000 : ("al", "", "Dibuparque"), + 5100 : ("a la", "", "Calle del Chopo"), + 5200 : ("a la", "", "Calle Arce"), + 5300 : ("a la", "", "Calle de Los Robles"), # translate + 9000 : ("al", "", "Dibuparque"), + 9100 : ("a la", "", "Avenida de la Nana"), + 10000 : ("al", "", "cuartel general jefebot"), + 10100 : ("al", "", "vestíbulo del cuartel general jefebot"), + 11000 : ("al", "", "patio del cuartel general vendebot"), + 11100 : ("al", "", "vestíbulo del cuartel general vendebot"), + 11200 : ("a la", "", "fábrica vendebot"), + 11500 : ("a la", "", "fábrica vendebot"), + 12000 : ("al", "", "cuartel general chequebot"), + 12100 : ("al", "", "vestíbulo del cuartel general chequebot"), + 13000 : ("al", "", "cuartel general abogabot"), + 13100 : ("al", "", "vestíbulo del cuartel general abogabot"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("a", "", "Puerto Donald") +ToontownCentral = ("al", "", "Centro de Toontown") +TheBrrrgh = ("a", "", "Frescolandia") +MinniesMelodyland = ("a", "", "Melodilandia de Minnie") +DaisyGardens = ("a", "", "los Jardines de Daisy") +ConstructionZone = ("a la", "", "Zona de obras") +FunnyFarm = ("a la", "", "Granja Jolgorio") +GoofyStadium = ("al", "", "Estadio Goofy") +DonaldsDreamland = ("a", "", "Sueñolandia de Donald") +BossbotHQ = ("al", "", "Cuartel general jefebot") +SellbotHQ = ("al", "", "Cuartel general vendebot") +CashbotHQ = ("al", "", "Cuartel general chequebot") +LawbotHQ = ("al", "", "Cuartel general abogabot") +Tutorial = ("al", "", "Dibututorial") +MyEstate = ("a", "", "Tu casa") +WelcomeValley = ("a", "", "Valle Bienvenido") + +Factory = 'Fábrica' +Headquarters = 'Cuartel general' +SellbotFrontEntrance = 'Entrada principal' +SellbotSideEntrance = 'Entrada de servicio' + +FactoryNames = { + 0 : 'Maqueta de la fábrica', + 11500 : 'Fábrica de bots vendebot', + } + +FactoryTypeLeg = 'Pierna' +FactoryTypeArm = 'Brazo' +FactoryTypeTorso = 'Torso' + +# ToontownLoader.py +LoaderLabel = "Cargando..." + +# PlayGame.py +HeadingToHood = "Entrando %(to)s %(hood)s..." # to phrase, hood name +HeadingToYourEstate = "Entrando a tú propiedad..." +HeadingToEstate = "Entrando a la propiedad %s..." # avatar name +HeadingToFriend = "Entrando a la propiedad del amigo %s..." # avatar name + +# Hood.py +HeadingToPlayground = "Entrando al Dibuparque..." +HeadingToStreet = "Entrando %(to)s %(street)s..." # to phrase, street name + +# ToontownDialog.py +ToontownDialogOK = "Aceptar" +ToontownDialogCancel = "Cancelar" +ToontownDialogYes = "Sí" +ToontownDialogNo = "No" + +# TownBattle.py +TownBattleRun = "¿Quieres volver corriendo al dibuparque?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "¿QUÉ DIBU?" +TownBattleChooseAvatarCogTitle = "¿CUÁL " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "ATRÁS" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "¡No tienes amigos a los que llamar!" +TownBattleSOSWhichFriend = "¿A qué amigo quieres llamar?" +TownBattleSOSNPCFriends = "Dibus rescatados" +TownBattleSOSBack = "ATRÁS" + +# TownBattleToonPanel.py +TownBattleToonSOS = "S.O.S." +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Esperando a\notros jugadores..." +TownSoloBattleWaitTitle = "Espera..." +TownBattleWaitBack = "ATRÁS" + +# Trolley.py +TrolleyHFAMessage = "No puedes subirte al tranvía hasta que el risómetro esté sonriendo." +TrolleyTFAMessage = "No puedes subirte al tranvía hasta que lo diga Mickey." +TrolleyHopOff = "Bajarse" + +# DistributedFishingSpot.py +FishingExit = "Hecho" +FishingCast = "Lanzar" +FishingAutoReel = "Carrete automático" +FishingItemFound = "Has pescado:" +FishingCrankTooSlow = "Muy\nlento" +FishingCrankTooFast = "Muy\nrápido" +FishingFailure = "¡No has pescado nada!" +FishingFailureTooSoon = "No empieces a enrollar el carrete hasta que veas que pican. ¡Espera a que la boya se mueva hacia arriba y abajo rápidamente!" +FishingFailureTooLate = "¡Es importante que enrolles el sedal mientras el pez está mordiendo el anzuelo!" +FishingFailureAutoReel = "El carrete automático no ha funcionado esta vez. Enrolla a mano el carrete a la velocidad correcta para poder pescar algo." +FishingFailureTooSlow = "Has enrollado el carrete demasiado despacio. Algunos peces son más rápidos que otros. ¡Intenta mantener centrada la barra de velocidad!" +FishingFailureTooFast = "Has enrollado el carrete demasiado deprisa. Algunos peces son más lentos que otros. ¡Intenta mantener centrada la barra de velocidad!" +FishingOverTankLimit = "El cubo de peces está lleno. Vende unos cuantos peces y vuelve." +FishingBroke = "¡No tienes nada que poner en el anzuelo! Súbete al tranvía para conseguir más gominolas." +FishingHowToFirstTime = "Pulsa el botón Lanzar y arrastra hacia abajo. Cuanto más lejos arrastres, más fuerte será el lanzamiento. Ajusta el ángulo para acertar en los peces.\n\n¡Pruébalo ya!" +FishingHowToFailed = "Pulsa el botón para Lanzar y arrastra hacia abajo. Cuanto más lejos arrastres, más fuerte será el lanzamiento. Ajusta el ángulo para acertar en los peces.\n\n¡Prueba de nuevo!" +FishingBootItem = "Una bota vieja" +FishingJellybeanItem = "%s gominolas" +FishingNewEntry = "¡Una nueva especie!" +FishingNewRecord = "¡Nuevo récord!" + +# FishPoker +FishPokerCashIn = "Canjear\n%s\n%s" +FishPokerLock = "Fijar" +FishPokerUnlock = "Liberar" +FishPoker5OfKind = "Escalera de color" +FishPoker4OfKind = "Póquer" +FishPokerFullHouse = "Full" +FishPoker3OfKind = "Trío" +FishPoker2Pair = "Doble pareja" +FishPokerPair = "Pareja" + +# DistributedTutorial.py +TutorialGreeting1 = "¡Hola, %s!" +TutorialGreeting2 = "¡Hola, %s!\n¡Ven aquí!" +TutorialGreeting3 = "¡Hola, %s!\n¡Ven aquí!\n¡Usa las flechas del teclado!" +TutorialMickeyWelcome = "¡Bienvenido a Toontown!" +TutorialFlippyIntro = "Te voy a presentar a mi amigo %s." % Flippy +TutorialFlippyHi = "¡Hola, %s!" +TutorialQT1 = "Puedes usar esto para hablar." +TutorialQT2 = "Puedes usar esto para hablar.\nHaz clic y elige \"Hola\"." +TutorialChat1 = "Puedes hablar con cualquiera de estos botones." +TutorialChat2 = "El botón azul te permite charlar en línea por medio del teclado." +TutorialChat3 = "¡Ten cuidado! Muchos de los jugadores no entenderán lo que dices si usas el teclado." +TutorialChat4 = "El botón verde abre el %s." +TutorialChat5 = "Todos entienden lo que dices cuando usas el %s." +TutorialChat6 = "Prueba a decir \"Hola\"." +TutorialBodyClick1 = "¡Muy bien!" +TutorialBodyClick2 = "¡Encantado de conocerte! ¿Quieres ser mi amigo?" +TutorialBodyClick3 = "Para ser amigo de %s, haz clic en él..." % Flippy +TutorialHandleBodyClickSuccess = "¡Buen trabajo!" +TutorialHandleBodyClickFail = "Todavía no. Prueba a hacer clic en %s..." % Flippy +TutorialFriendsButton = "Ahora pulsa el botón \"Amigos\", situado debajo de la imagen de %s, en la esquina derecha." % Flippy +TutorialHandleFriendsButton = "Después, pulsa el botón \"Sí\"." +TutorialOK = "Aceptar" +TutorialYes = "Sí" +TutorialNo = "No" +TutorialFriendsPrompt = "¿Quieres ser amigo de %s?" % Flippy +TutorialFriendsPanelMickeyChat = "%s ha accedido a ser tu amigo. Pulsa \"Aceptar\" para terminar." % Flippy +TutorialFriendsPanelYes = "¡%s ha dicho que sí!" % Flippy +TutorialFriendsPanelNo = "¡No es que seas muy amable!" +TutorialFriendsPanelCongrats = "¡Enhorabuena! Acabas de hacer tu primer amigo." +TutorialFlippyChat1 = "Ven a verme cuando estés preparado para tu primera dibutarea." +TutorialFlippyChat2 = "Estaré en el Ayuntamiento." +TutorialAllFriendsButton = "Para ver a todos tus amigos, haz clic en el botón Amigos. Pruébalo..." +TutorialEmptyFriendsList = "Ahora mismo la lista está vacía porque %s no es un jugador de verdad." % Flippy +TutorialCloseFriendsList = "Pulsa el botón\nCerrar para que la\nlista se cierre." +TutorialShtickerButton = "El botón de la esquina inferior derecha sirve para abrir el dibucuaderno. Prúebalo..." +TutorialBook1 = "El dibucuaderno contiene un montón de información útil, como este mapa de Toontown." +TutorialBook2 = "También puedes ver cómo van tus dibutareas." +TutorialBook3 = "Cuando hayas terminado, vuelve a pulsar el botón del libro para que se cierre." +TutorialLaffMeter1 = "También vas a necesitar esto..." +TutorialLaffMeter2 = "También vas a necesitar esto...\nEs tu risómetro." +TutorialLaffMeter3 = "Cuando los bots te ataquen, irá disminuyendo." +TutorialLaffMeter4 = "Cuando estés en dibuparques como éste, subirá de nuevo." +TutorialLaffMeter5 = "Cuando completes dibutareas obtendrás recompensas, como por ejemplo un aumento del límite del risómetro." +TutorialLaffMeter6 = "¡Ten cuidado! Si los bots te vencen, perderás todas las bromas." +TutorialLaffMeter7 = "Para conseguir más bromas, juega a los juegos del tranvía." +TutorialTrolley1 = "¡Sígueme hasta el tranvía!" +TutorialTrolley2 = "¡Súbete!" +TutorialBye1 = "¡Juega a unos cuantos juegos!" +TutorialBye2 = "¡Juega a unos cuantos juegos!\n¡Compra unas cuantas bromas!" +TutorialBye3 = "Cuando hayas terminado, ve a ver a %s" % Flippy + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "¡Vas en dirección contraria! ¡Ve a buscar a %s!" % Mickey + +# SpeedChat + +# Used several places in the game. Defined globally because +# we keep changing the name +GlobalSpeedChatName = "Charla rápida" + +SCMenuEmotions = "EMOCIONES" +SCMenuCustom = "MIS FRASES" +SCMenuCog = "COG SPEAK" +SCMenuHello = "HOLA" +SCMenuBye = "ADIÓS" +SCMenuHappy = "FELIZ" +SCMenuSad = "TRISTE" +SCMenuFriendly = "AMIGABLE" +SCMenuSorry = "LO SIENTO" +SCMenuStinky = "FÉTIDO" +SCMenuPlaces = "LUGARES" +SCMenuToontasks = "DIBUTAREAS" +SCMenuBattle = "COMBATE" +SCMenuGagShop = "BROMAS" +SCMenuFactory = "FABRICA" +SCMenuFactoryMeet = "ENCUENTRO" +SCMenuFriendlyYou = "Eres..." +SCMenuFriendlyILike = "me gusta..." +SCMenuPlacesLetsGo = "Vamos a..." +SCMenuToontasksMyTasks = "Mis tareas" +SCMenuToontasksYouShouldChoose = "Creo que deberias escoger..." +SCMenuBattleLetsUse = "Vamos a usar..." + +# These are all the standard SpeedChat phrases. +# The indices must fit into 16 bits (0..65535) +SpeedChatStaticText = { + # top-level + 1 : 'Sí', + 2 : 'No', + 3 : 'Aceptar', + + # Hello + 100 : "¡Buenas!", + 101 : "¡Hola!", + 102 : "¡Muy Buenas!", + 103 : "¡Eh!", + 104 : "¿Qué hay?", + 105 : "¡Hola a todos!", + 106 : "¡Bienvenido a Toontown!", + 107 : "¿Qué tal?", + 108 : "¿Cómo estás?", + 109 : "¿Hola?", + + # Bye + 200 : "¡Chao!", + 201 : "¡Nos vemos!", + 202 : "¡Hasta la vista!", + 203 : "¡Que pases un buen día!", + 204 : "¡Diviértete!", + 205 : "¡Buena suerte!", + 206 : "Vuelvo enseguida.", + 207 : "Tengo que irme.", + + # Happy + 300 : ":-)", + 301 : "¡Hey!", + 302 : "¡Hurra!", + 303 : "¡chupi!", + 304 : "¡Yujuuu!", + 305 : "¡Sí!", + 306 : "¡Ja, ja!", + 307 : "¡Ji, ji!", + 308 : "¡Guau!", + 309 : "¡Fantástico!", + 310 : "¡Yepaa!", + 311 : "¡Estupendo!", + 312 : "¡Yupii!", + 313 : "¡Yipee!", + 314 : "¡Yiii ja!", + 315 : "¡Dibufantástico!", + + # Sad + 400 : ":-(", + 401 : "¡Oh no!", + 402 : "¡Oh oh!", + 403 : "¡Caramba!", + 404 : "¡Vaya!", + 405 : "¡Ay!", + 406 : "¡Uf!", + 407 : "¡¡¡No!!!", + 408 : "¡Auuu!", + 409 : "¿Eh?", + 410 : "Necesito más puntos de risa.", + + # Friendly + 500 : "¡Gracias!", + 501 : "No hay de qué.", + 502 : "¡De nada!", + 503 : "¡A tu disposición!", + 504 : "No, gracias a ti.", + 505 : "¡Buen trabajo en equipo!", + 506 : "¡Qué divertido!", + 507 : "¡Sé mi amigo!", + 508 : "¡Trabajemos en equipo!", + 509 : "¡Sois estupendos!", + 510 : "¿Eres nuevo aquí?", + 511 : "¿Has ganado?", + 512 : "Creo que esto es demasiado para ti.", + 513 : "¿Quieres que te ayude?", + 514 : "¿Puedes ayudarme?", + + # Friendly "You..." + 600 : "Pareces simpático.", + 601 : "¡Eres genial!", + 602 : "¡Eres la bomba!", + 603 : "¡Eres sensacional!", + + # Friendly "I like..." + 700 : "Me gusta tu nombre.", + 701 : "Me gusta tu aspecto.", + 702 : "Me gusta tu camiseta.", + 703 : "Me gusta tu falda.", + 704 : "Me gustan tus shorts.", + 705 : "¡Me gusta este juego!", + + # Sorry + 800 : "¡Lo siento!", + 801 : "¡Vaya!", + 802 : "¡Lo siento, estoy peleando con los bots!", + 803 : "¡Lo siento, estoy obteniendo gominolas!", + 804 : "¡Lo siento, estoy haciendo una dibutarea!", + 805 : "Lo siento, he tenido que marcharme repentinamente.", + 806 : "Discúlpame, me retrasé.", + 807 : "Lo siento, no puedo.", + 808 : "No he podido esperar más.", + 809 : "No te entiendo.", + 810 : "Usa la %s." % GlobalSpeedChatName, + + # Stinky + 900 : "¡Eh!", + 901 : "¡Vete de aquí!", + 902 : "¡Deja de hacer eso!", + 903 : "¡Eso es mala educación!", + 904 : "¡No seas malo!", + 905 : "¡Hueles mal!", + 906 : "Envía un informe de error.", + 907 : "No me puedo mover, porque hay un error.", + + # Places + 1000 : "¡Vamos!", + 1001 : "¿Puedes teletransportarte a donde estoy?", + 1002 : "¿Nos vamos?", + 1003 : "¿Adónde deberíamos ir?", + 1004 : "¿Por qué camino?", + 1005 : "Por aquí.", + 1006 : "Sígueme.", + 1007 : "¡Espérame!", + 1008 : "Vamos a esperar a mi amigo.", + 1009 : "Vamos a buscar a otros dibus.", + 1010 : "Espera aquí.", + 1011 : "Espera un momento.", + 1012 : "Nos encontraremos aquí.", + 1013 : "¿Puedes venir a mi casa?", + + # Places "Let's go..." + 1100 : "¡Vámonos en el tranvía!", + 1101 : "¡Vamos a volver al dibuparque!", + 1102 : "¡Vamos a luchar contra los %s!" % Cogs, + 1103 : "¡Vamos a tomar un edificio %s!" % Cog, + 1104 : "¡Vamos al ascensor!", + 1105 : "¡Vamos al Centro de Toontown!", + 1106 : "¡Vamos a Puerto Donald!", + 1107 : "¡Vamos a Melodilandia de Minnie!", + 1108 : "¡Vamos a los Jardines de Daisy!", + 1109 : "¡Vamos a Frescolandia!", + 1110 : "¡Vamos a Sueñolandia de Donald!", + 1111 : "¡Vamos a mi casa!", + + # Toontasks + 1200 : "¿En qué dibutarea estás trabajando?", + 1201 : "Ocupémonos de eso.", + 1202 : "Esto no es lo que busco.", + 1203 : "Voy a buscar eso.", + 1204 : "No está en esta calle.", + 1205 : "Todavía no lo he encontrado.", + 1299 : "Necesito que me asignen una dibutarea.", + + # Toontasks "I think you should choose..." + 1300 : "Creo que debes usarun curadibu.", + 1301 : "Creo que debes usar un sonido.", + 1302 : "Creo que debes usar una caída.", + 1303 : "Creo que debes usar un cebo.", + 1304 : "Creo que debes usar una trampa.", + + # Battle + 1400 : "¡Deprisa!", + 1401 : "¡Buen Disparo!", + 1402 : "¡Buena Broma!", + 1403 : "¡No me has dado!", + 1404 : "¡Lo has conseguido!", + 1405 : "¡Lo hemos hecho!", + 1406 : "¡Sigue así!", + 1407 : "¡Está chupado!", + 1408 : "¡Qué facil!", + 1409 : "¡Corre!", + 1410 : "¡Socorro!", + 1411 : "¡Uf!", + 1412 : "Tenemos problemas.", + 1413 : "Necesito más bromas.", + 1414 : "Necesito un curadibu.", + 1415 : "Deberías pasar.", + + # Battle "Let's use..." + 1500 : "¡Usemos un curadibu!", + 1501 : "¡Usemos una trampa!", + 1502 : "¡Usemos un cebo!", + 1503 : "¡Usemos un sonido!", + 1504 : "¡Usemos un lanzamiento!", + 1505 : "¡Usemos un chorro!", + 1506 : "¡Usemos una caida!", + + # Gag Shop + 1600 : "Tengo suficientes bromas.", + 1601 : "Necesito más gominolas.", + 1602 : "Yo también.", + 1603 : "¡Deprisa!", + 1604 : "¿Una más?", + 1605 : "¿Quieres jugar otra vez?", + 1606 : "Jugemos de nuevo.", + + # Factory + 1700 : "Separémonos.", + 1701 : "No nos separemos.", + 1702 : "Vamos a luchar contra los bots.", + 1703 : "Salta encima del interruptor.", + 1704 : "Atraviesa la puerta.", + + # Sellbot Factory + 1803 : "Estoy en la entrada principal.", + 1804 : "Estoy en el vestíbulo.", + 1805 : "Estoy en la entrada que da al vestíbulo.", + 1806 : "Estoy en la entrada que da al vestíbulo.", + 1807 : "Estoy en la sala de máquinas.", + 1808 : "Estoy en la sala de calderas.", + 1809 : "Estoy en la pasarela este.", + 1810 : "Estoy en la mezcladora de pintura.", + 1811 : "Estoy en la sala donde se guarda la mezcladora de pintura.", + 1812 : "Estoy en la pasarela del silo oeste.", + 1813 : "Estoy en la sala de tuberías.", + 1814 : "Estoy en las escaleras que dan a la sala de tuberías.", + 1815 : "Estoy en la sala de conductos.", + 1816 : "Estoy en la entrada de servicio.", + 1817 : "Estoy en el callejón del Pisotón.", + 1818 : "Estoy fuera de la sala de la lava.", + 1819 : "Estoy en la sala de la lava.", + 1820 : "Estoy en la sala donde se guarda la lava.", + 1821 : "Estoy en la pasarela oeste.", + 1822 : "Estoy en la sala del aceite.", + 1823 : "Estoy en la cabina de vigilancia del almacén.", + 1824 : "Estoy en el almacén.", + 1825 : "Estoy fuera de la mezcladora de pintura.", + 1827 : "Estoy fuera de la sala del aceite.", + 1830 : "Estoy en la sala de control del silo este.", + 1831 : "Estoy en la sala de control del silo oeste.", + 1832 : "Estoy en la sala de control del silo central.", + 1833 : "Estoy en el silo este.", + 1834 : "Estoy en el silo oeste.", + 1835 : "Estoy en el silo central.", + 1836 : "Estoy en el silo oeste.", + 1837 : "Estoy en el silo este.", + 1838 : "Estoy en la pasarela del silo este.", + 1840 : "Estoy encima del silo oeste.", + 1841 : "Estoy encima del silo este.", + 1860 : "Estoy en el ascensor del silo oeste.", + 1861 : "Estoy en el ascensor del silo este.", + # Sellbot Factory continued + 1903 : "Reunámonos en la entrada principal.", + 1904 : "Reunámonos en el vestíbulo.", + 1905 : "Reunámonos en la entrada que da al vestíbulo.", + 1906 : "Reunámonos en la entrada que da al vestíbulo.", + 1907 : "Reunámonos en la sala de máquinas.", + 1908 : "Reunámonos en la sala de calderas.", + 1909 : "Reunámonos en la pasarela este.", + 1910 : "Reunámonos en la mezladora de pintura.", + 1911 : "Reunámonos en la sala donde se guarda la mezcladora de pintura.", + 1912 : "Reunámonos en la pasarela del silo oeste.", + 1913 : "Reunámonos en la sala de tuberías.", + 1914 : "Reunámonos en las escaleras que dan a la sala de tuberías.", + 1915 : "Reunámonos en la sala de conductos.", + 1916 : "Reunámonos en la entrada de servicio.", + 1917 : "Reunámonos en el callejón del Pisotón.", + 1918 : "Reunámonos fuera de la sala de la lava.", + 1919 : "Reunámonos en la sala de la lava.", + 1920 : "Reunámonos en la sala donde se guarda la lava.", + 1921 : "Reunámonos en la pasarela oeste.", + 1922 : "Reunámonos en la sala del aceite.", + 1923 : "Reunámonos en la cabina de vigilancia del almacén.", + 1924 : "Reunámonos en el almacén.", + 1925 : "Reunámonos fuera de la mezcladora de pintura.", + 1927 : "Reunámonos fuera de la sala del aceite.", + 1930 : "Reunámonos en la sala de control del silo este.", + 1931 : "Reunámonos en la sala de control del silo oeste.", + 1932 : "Reunámonos en la sala de control del silo central.", + 1933 : "Reunámonos en el silo este.", + 1934 : "Reunámonos en el silo oeste.", + 1935 : "Reunámonos en el silo central.", + 1936 : "Reunámonos en el silo oeste.", + 1937 : "Reunámonos en el silo este.", + 1938 : "Reunámonos en la pasarela del silo este.", + 1940 : "Reunámonos encima del silo oeste.", + 1941 : "Reunámonos encima del silo este.", + 1960 : "Reunámonos en el ascensor del silo oeste.", + 1961 : "Reunámonos en el ascensor del silo este.", + + # These are used only for the style settings in the OptionsPage + # These should never actually be spoken or listed on the real speed chat + 2000 : "Morado", + 2001 : "Azul", + 2002 : "Añil", + 2003 : "Aguamarina", + 2004 : "Verde", + 2005 : "Amarillo", + 2006 : "Naranja", + 2007 : "Rojo", + 2008 : "Rosa", + 2009 : "Marrón", + + # cog phrases for disguised toons + # (just references to cog dialog above) + + # common cog phrases + 20000 : SuitBrushOffs[None][0], + 20001 : SuitBrushOffs[None][1], + 20002 : SuitBrushOffs[None][2], + 20003 : SuitBrushOffs[None][3], + 20004 : SuitBrushOffs[None][4], + + # specific cog phrases + 20005: SuitFaceoffTaunts['bf'][0], + 20006: SuitFaceoffTaunts['bf'][1], + 20007: SuitFaceoffTaunts['bf'][2], + 20008: SuitFaceoffTaunts['bf'][3], + 20009: SuitFaceoffTaunts['bf'][4], + 20010: SuitFaceoffTaunts['bf'][5], + 20011: SuitFaceoffTaunts['bf'][6], + 20012: SuitFaceoffTaunts['bf'][7], + 20013: SuitFaceoffTaunts['bf'][8], + 20014: SuitFaceoffTaunts['bf'][9], + + 20015: SuitFaceoffTaunts['nc'][0], + 20016: SuitFaceoffTaunts['nc'][1], + 20017: SuitFaceoffTaunts['nc'][2], + 20018: SuitFaceoffTaunts['nc'][3], + 20019: SuitFaceoffTaunts['nc'][4], + 20020: SuitFaceoffTaunts['nc'][5], + 20021: SuitFaceoffTaunts['nc'][6], + 20022: SuitFaceoffTaunts['nc'][7], + 20023: SuitFaceoffTaunts['nc'][8], + 20024: SuitFaceoffTaunts['nc'][9], + + 20025: SuitFaceoffTaunts['ym'][0], + 20026: SuitFaceoffTaunts['ym'][1], + 20027: SuitFaceoffTaunts['ym'][2], + 20028: SuitFaceoffTaunts['ym'][3], + 20029: SuitFaceoffTaunts['ym'][4], + 20030: SuitFaceoffTaunts['ym'][5], + 20031: SuitFaceoffTaunts['ym'][6], + 20032: SuitFaceoffTaunts['ym'][7], + 20033: SuitFaceoffTaunts['ym'][8], + 20034: SuitFaceoffTaunts['ym'][9], + 20035: SuitFaceoffTaunts['ym'][10], + + 20036: SuitFaceoffTaunts['ms'][0], + 20037: SuitFaceoffTaunts['ms'][1], + 20038: SuitFaceoffTaunts['ms'][2], + 20039: SuitFaceoffTaunts['ms'][3], + 20040: SuitFaceoffTaunts['ms'][4], + 20041: SuitFaceoffTaunts['ms'][5], + 20042: SuitFaceoffTaunts['ms'][6], + 20043: SuitFaceoffTaunts['ms'][7], + 20044: SuitFaceoffTaunts['ms'][8], + 20045: SuitFaceoffTaunts['ms'][9], + 20046: SuitFaceoffTaunts['ms'][10], + + 20047: SuitFaceoffTaunts['bc'][0], + 20048: SuitFaceoffTaunts['bc'][1], + 20049: SuitFaceoffTaunts['bc'][2], + 20050: SuitFaceoffTaunts['bc'][3], + 20051: SuitFaceoffTaunts['bc'][4], + 20052: SuitFaceoffTaunts['bc'][5], + 20053: SuitFaceoffTaunts['bc'][6], + 20054: SuitFaceoffTaunts['bc'][7], + 20055: SuitFaceoffTaunts['bc'][8], + 20056: SuitFaceoffTaunts['bc'][9], + 20057: SuitFaceoffTaunts['bc'][10], + + 20058: SuitFaceoffTaunts['cc'][0], + 20059: SuitFaceoffTaunts['cc'][1], + 20060: SuitFaceoffTaunts['cc'][2], + 20061: SuitFaceoffTaunts['cc'][3], + 20062: SuitFaceoffTaunts['cc'][4], + 20063: SuitFaceoffTaunts['cc'][5], + 20064: SuitFaceoffTaunts['cc'][6], + 20065: SuitFaceoffTaunts['cc'][7], + 20066: SuitFaceoffTaunts['cc'][8], + 20067: SuitFaceoffTaunts['cc'][9], + 20068: SuitFaceoffTaunts['cc'][10], + 20069: SuitFaceoffTaunts['cc'][11], + 20070: SuitFaceoffTaunts['cc'][12], + + 20071: SuitFaceoffTaunts['nd'][0], + 20072: SuitFaceoffTaunts['nd'][1], + 20073: SuitFaceoffTaunts['nd'][2], + 20074: SuitFaceoffTaunts['nd'][3], + 20075: SuitFaceoffTaunts['nd'][4], + 20076: SuitFaceoffTaunts['nd'][5], + 20077: SuitFaceoffTaunts['nd'][6], + 20078: SuitFaceoffTaunts['nd'][7], + 20079: SuitFaceoffTaunts['nd'][8], + 20080: SuitFaceoffTaunts['nd'][9], + + 20081: SuitFaceoffTaunts['ac'][0], + 20082: SuitFaceoffTaunts['ac'][1], + 20083: SuitFaceoffTaunts['ac'][2], + 20084: SuitFaceoffTaunts['ac'][3], + 20085: SuitFaceoffTaunts['ac'][4], + 20086: SuitFaceoffTaunts['ac'][5], + 20087: SuitFaceoffTaunts['ac'][6], + 20088: SuitFaceoffTaunts['ac'][7], + 20089: SuitFaceoffTaunts['ac'][8], + 20090: SuitFaceoffTaunts['ac'][9], + 20091: SuitFaceoffTaunts['ac'][10], + 20092: SuitFaceoffTaunts['ac'][11], + + 20093: SuitFaceoffTaunts['tf'][0], + 20094: SuitFaceoffTaunts['tf'][1], + 20095: SuitFaceoffTaunts['tf'][2], + 20096: SuitFaceoffTaunts['tf'][3], + 20097: SuitFaceoffTaunts['tf'][4], + 20098: SuitFaceoffTaunts['tf'][5], + 20099: SuitFaceoffTaunts['tf'][6], + 20100: SuitFaceoffTaunts['tf'][7], + 20101: SuitFaceoffTaunts['tf'][8], + 20102: SuitFaceoffTaunts['tf'][9], + 20103: SuitFaceoffTaunts['tf'][10], + + 20104: SuitFaceoffTaunts['hh'][0], + 20105: SuitFaceoffTaunts['hh'][1], + 20106: SuitFaceoffTaunts['hh'][2], + 20107: SuitFaceoffTaunts['hh'][3], + 20108: SuitFaceoffTaunts['hh'][4], + 20109: SuitFaceoffTaunts['hh'][5], + 20110: SuitFaceoffTaunts['hh'][6], + 20111: SuitFaceoffTaunts['hh'][7], + 20112: SuitFaceoffTaunts['hh'][8], + 20113: SuitFaceoffTaunts['hh'][9], + 20114: SuitFaceoffTaunts['hh'][10], + + 20115: SuitFaceoffTaunts['le'][0], + 20116: SuitFaceoffTaunts['le'][1], + 20117: SuitFaceoffTaunts['le'][2], + 20118: SuitFaceoffTaunts['le'][3], + 20119: SuitFaceoffTaunts['le'][4], + 20120: SuitFaceoffTaunts['le'][5], + 20121: SuitFaceoffTaunts['le'][6], + 20122: SuitFaceoffTaunts['le'][7], + 20123: SuitFaceoffTaunts['le'][8], + 20124: SuitFaceoffTaunts['le'][9], + + 20125: SuitFaceoffTaunts['bs'][0], + 20126: SuitFaceoffTaunts['bs'][1], + 20127: SuitFaceoffTaunts['bs'][2], + 20128: SuitFaceoffTaunts['bs'][3], + 20129: SuitFaceoffTaunts['bs'][4], + 20130: SuitFaceoffTaunts['bs'][5], + 20131: SuitFaceoffTaunts['bs'][6], + 20132: SuitFaceoffTaunts['bs'][7], + 20133: SuitFaceoffTaunts['bs'][8], + 20134: SuitFaceoffTaunts['bs'][9], + 20135: SuitFaceoffTaunts['bs'][10], + + 20136: SuitFaceoffTaunts['cr'][0], + 20137: SuitFaceoffTaunts['cr'][1], + 20138: SuitFaceoffTaunts['cr'][2], + 20139: SuitFaceoffTaunts['cr'][3], + 20140: SuitFaceoffTaunts['cr'][4], + 20141: SuitFaceoffTaunts['cr'][5], + 20142: SuitFaceoffTaunts['cr'][6], + 20143: SuitFaceoffTaunts['cr'][7], + 20144: SuitFaceoffTaunts['cr'][8], + 20145: SuitFaceoffTaunts['cr'][9], + + 20146: SuitFaceoffTaunts['tbc'][0], + 20147: SuitFaceoffTaunts['tbc'][1], + 20148: SuitFaceoffTaunts['tbc'][2], + 20149: SuitFaceoffTaunts['tbc'][3], + 20150: SuitFaceoffTaunts['tbc'][4], + 20151: SuitFaceoffTaunts['tbc'][5], + 20152: SuitFaceoffTaunts['tbc'][6], + 20153: SuitFaceoffTaunts['tbc'][7], + 20154: SuitFaceoffTaunts['tbc'][8], + 20155: SuitFaceoffTaunts['tbc'][9], + 20156: SuitFaceoffTaunts['tbc'][10], + + 20157: SuitFaceoffTaunts['ds'][0], + 20158: SuitFaceoffTaunts['ds'][1], + 20159: SuitFaceoffTaunts['ds'][2], + 20160: SuitFaceoffTaunts['ds'][3], + 20161: SuitFaceoffTaunts['ds'][4], + 20162: SuitFaceoffTaunts['ds'][5], + 20163: SuitFaceoffTaunts['ds'][6], + 20164: SuitFaceoffTaunts['ds'][7], + + 20165: SuitFaceoffTaunts['gh'][0], + 20166: SuitFaceoffTaunts['gh'][1], + 20167: SuitFaceoffTaunts['gh'][2], + 20168: SuitFaceoffTaunts['gh'][3], + 20169: SuitFaceoffTaunts['gh'][4], + 20170: SuitFaceoffTaunts['gh'][5], + 20171: SuitFaceoffTaunts['gh'][6], + 20172: SuitFaceoffTaunts['gh'][7], + 20173: SuitFaceoffTaunts['gh'][8], + 20174: SuitFaceoffTaunts['gh'][9], + 20175: SuitFaceoffTaunts['gh'][10], + 20176: SuitFaceoffTaunts['gh'][11], + 20177: SuitFaceoffTaunts['gh'][12], + + 20178: SuitFaceoffTaunts['pp'][0], + 20179: SuitFaceoffTaunts['pp'][1], + 20180: SuitFaceoffTaunts['pp'][2], + 20181: SuitFaceoffTaunts['pp'][3], + 20182: SuitFaceoffTaunts['pp'][4], + 20183: SuitFaceoffTaunts['pp'][5], + 20184: SuitFaceoffTaunts['pp'][6], + 20185: SuitFaceoffTaunts['pp'][7], + 20186: SuitFaceoffTaunts['pp'][8], + 20187: SuitFaceoffTaunts['pp'][9], + + 20188: SuitFaceoffTaunts['b'][0], + 20189: SuitFaceoffTaunts['b'][1], + 20190: SuitFaceoffTaunts['b'][2], + 20191: SuitFaceoffTaunts['b'][3], + 20192: SuitFaceoffTaunts['b'][4], + 20193: SuitFaceoffTaunts['b'][5], + 20194: SuitFaceoffTaunts['b'][6], + 20195: SuitFaceoffTaunts['b'][7], + 20196: SuitFaceoffTaunts['b'][8], + 20197: SuitFaceoffTaunts['b'][9], + 20198: SuitFaceoffTaunts['b'][10], + 20199: SuitFaceoffTaunts['b'][11], + + 20200: SuitFaceoffTaunts['f'][0], + 20201: SuitFaceoffTaunts['f'][1], + 20202: SuitFaceoffTaunts['f'][2], + 20203: SuitFaceoffTaunts['f'][3], + 20204: SuitFaceoffTaunts['f'][4], + 20205: SuitFaceoffTaunts['f'][5], + 20206: SuitFaceoffTaunts['f'][6], + 20207: SuitFaceoffTaunts['f'][7], + 20208: SuitFaceoffTaunts['f'][8], + 20209: SuitFaceoffTaunts['f'][9], + 20210: SuitFaceoffTaunts['f'][10], + + 20211: SuitFaceoffTaunts['mm'][0], + 20212: SuitFaceoffTaunts['mm'][1], + 20213: SuitFaceoffTaunts['mm'][2], + 20214: SuitFaceoffTaunts['mm'][3], + 20215: SuitFaceoffTaunts['mm'][4], + 20216: SuitFaceoffTaunts['mm'][5], + 20217: SuitFaceoffTaunts['mm'][6], + 20218: SuitFaceoffTaunts['mm'][7], + 20219: SuitFaceoffTaunts['mm'][8], + 20220: SuitFaceoffTaunts['mm'][9], + 20221: SuitFaceoffTaunts['mm'][10], + 20222: SuitFaceoffTaunts['mm'][11], + 20223: SuitFaceoffTaunts['mm'][12], + 20224: SuitFaceoffTaunts['mm'][13], + + 20225: SuitFaceoffTaunts['tw'][0], + 20226: SuitFaceoffTaunts['tw'][1], + 20227: SuitFaceoffTaunts['tw'][2], + 20228: SuitFaceoffTaunts['tw'][3], + 20229: SuitFaceoffTaunts['tw'][4], + 20230: SuitFaceoffTaunts['tw'][5], + 20231: SuitFaceoffTaunts['tw'][6], + 20232: SuitFaceoffTaunts['tw'][7], + 20233: SuitFaceoffTaunts['tw'][8], + 20234: SuitFaceoffTaunts['tw'][9], + 20235: SuitFaceoffTaunts['tw'][10], + + 20236: SuitFaceoffTaunts['mb'][0], + 20237: SuitFaceoffTaunts['mb'][1], + 20238: SuitFaceoffTaunts['mb'][2], + 20239: SuitFaceoffTaunts['mb'][3], + 20240: SuitFaceoffTaunts['mb'][4], + 20241: SuitFaceoffTaunts['mb'][5], + 20242: SuitFaceoffTaunts['mb'][6], + 20243: SuitFaceoffTaunts['mb'][7], + 20244: SuitFaceoffTaunts['mb'][8], + 20245: SuitFaceoffTaunts['mb'][9], + + 20246: SuitFaceoffTaunts['m'][0], + 20247: SuitFaceoffTaunts['m'][1], + 20248: SuitFaceoffTaunts['m'][2], + 20249: SuitFaceoffTaunts['m'][3], + 20250: SuitFaceoffTaunts['m'][4], + 20251: SuitFaceoffTaunts['m'][5], + 20252: SuitFaceoffTaunts['m'][6], + 20253: SuitFaceoffTaunts['m'][7], + 20254: SuitFaceoffTaunts['m'][8], + + 20255: SuitFaceoffTaunts['mh'][0], + 20256: SuitFaceoffTaunts['mh'][1], + 20257: SuitFaceoffTaunts['mh'][2], + 20258: SuitFaceoffTaunts['mh'][3], + 20259: SuitFaceoffTaunts['mh'][4], + 20260: SuitFaceoffTaunts['mh'][5], + 20261: SuitFaceoffTaunts['mh'][6], + 20262: SuitFaceoffTaunts['mh'][7], + 20263: SuitFaceoffTaunts['mh'][8], + 20264: SuitFaceoffTaunts['mh'][9], + 20265: SuitFaceoffTaunts['mh'][10], + 20266: SuitFaceoffTaunts['mh'][11], + + 20267: SuitFaceoffTaunts['dt'][0], + 20268: SuitFaceoffTaunts['dt'][1], + 20269: SuitFaceoffTaunts['dt'][2], + 20270: SuitFaceoffTaunts['dt'][3], + 20271: SuitFaceoffTaunts['dt'][4], + 20272: SuitFaceoffTaunts['dt'][5], + 20273: SuitFaceoffTaunts['dt'][6], + 20274: SuitFaceoffTaunts['dt'][7], + 20275: SuitFaceoffTaunts['dt'][8], + 20276: SuitFaceoffTaunts['dt'][9], + + 20277: SuitFaceoffTaunts['p'][0], + 20278: SuitFaceoffTaunts['p'][1], + 20279: SuitFaceoffTaunts['p'][2], + 20280: SuitFaceoffTaunts['p'][3], + 20281: SuitFaceoffTaunts['p'][4], + 20282: SuitFaceoffTaunts['p'][5], + 20283: SuitFaceoffTaunts['p'][6], + 20284: SuitFaceoffTaunts['p'][7], + 20285: SuitFaceoffTaunts['p'][8], + 20286: SuitFaceoffTaunts['p'][9], + 20287: SuitFaceoffTaunts['p'][10], + + 20288: SuitFaceoffTaunts['tm'][0], + 20289: SuitFaceoffTaunts['tm'][1], + 20290: SuitFaceoffTaunts['tm'][2], + 20291: SuitFaceoffTaunts['tm'][3], + 20292: SuitFaceoffTaunts['tm'][4], + 20293: SuitFaceoffTaunts['tm'][5], + 20294: SuitFaceoffTaunts['tm'][6], + 20295: SuitFaceoffTaunts['tm'][7], + 20296: SuitFaceoffTaunts['tm'][8], + 20297: SuitFaceoffTaunts['tm'][9], + 20298: SuitFaceoffTaunts['tm'][10], + + 20299: SuitFaceoffTaunts['bw'][0], + 20300: SuitFaceoffTaunts['bw'][1], + 20301: SuitFaceoffTaunts['bw'][2], + 20302: SuitFaceoffTaunts['bw'][3], + 20303: SuitFaceoffTaunts['bw'][4], + 20304: SuitFaceoffTaunts['bw'][5], + 20305: SuitFaceoffTaunts['bw'][6], + 20306: SuitFaceoffTaunts['bw'][7], + 20307: SuitFaceoffTaunts['bw'][8], + 20308: SuitFaceoffTaunts['bw'][9], + + 20309: SuitFaceoffTaunts['ls'][0], + 20310: SuitFaceoffTaunts['ls'][1], + 20311: SuitFaceoffTaunts['ls'][2], + 20312: SuitFaceoffTaunts['ls'][3], + 20313: SuitFaceoffTaunts['ls'][4], + 20314: SuitFaceoffTaunts['ls'][5], + 20315: SuitFaceoffTaunts['ls'][6], + 20316: SuitFaceoffTaunts['ls'][7], + 20317: SuitFaceoffTaunts['ls'][8], + 20318: SuitFaceoffTaunts['ls'][9], + 20319: SuitFaceoffTaunts['ls'][10], + + 20320: SuitFaceoffTaunts['rb'][0], + 20321: SuitFaceoffTaunts['rb'][1], + 20322: SuitFaceoffTaunts['rb'][2], + 20323: SuitFaceoffTaunts['rb'][3], + 20324: SuitFaceoffTaunts['rb'][4], + 20325: SuitFaceoffTaunts['rb'][5], + 20326: SuitFaceoffTaunts['rb'][6], + 20327: SuitFaceoffTaunts['rb'][7], + 20328: SuitFaceoffTaunts['rb'][8], + 20329: SuitFaceoffTaunts['rb'][9], + + 20330: SuitFaceoffTaunts['sc'][0], + 20331: SuitFaceoffTaunts['sc'][1], + 20332: SuitFaceoffTaunts['sc'][2], + 20333: SuitFaceoffTaunts['sc'][3], + 20334: SuitFaceoffTaunts['sc'][4], + 20335: SuitFaceoffTaunts['sc'][5], + 20336: SuitFaceoffTaunts['sc'][6], + 20337: SuitFaceoffTaunts['sc'][7], + 20338: SuitFaceoffTaunts['sc'][8], + 20339: SuitFaceoffTaunts['sc'][9], + 20340: SuitFaceoffTaunts['sc'][10], + + 20341: SuitFaceoffTaunts['sd'][0], + 20342: SuitFaceoffTaunts['sd'][1], + 20343: SuitFaceoffTaunts['sd'][2], + 20344: SuitFaceoffTaunts['sd'][3], + 20345: SuitFaceoffTaunts['sd'][4], + 20346: SuitFaceoffTaunts['sd'][5], + 20347: SuitFaceoffTaunts['sd'][6], + 20348: SuitFaceoffTaunts['sd'][7], + 20349: SuitFaceoffTaunts['sd'][8], + 20350: SuitFaceoffTaunts['sd'][9], + } + +# These indexes, defined above, will construct a submenu in the FACTORY menu +# to allow the user to describe all the places he might want to meet +SCFactoryMeetMenuIndexes = (1903, 1904, 1906, 1907, 1908, 1910, 1913, + 1915, 1916, 1917, 1919, 1922, 1923, + 1924, 1932, 1940, 1941) + +# CustomSCStrings: SpeedChat phrases available for purchase. It is +# safe to remove entries from this list, which will disable them for +# use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +CustomSCStrings = { + # Series 1 + 10 : "Está bien.", + 20 : "¿Porque no?", + 30 : "¡Naturalmente!", + 40 : "Esa es la manera de hacerlo.", + 50 : "¡Exacto!", + 60 : "¿Que pasa?", + 70 : "¡Por supuesto!", + 80 : "Bingo!", + 90 : "Debes de estar bromeando...", + 100 : "Me suena bien.", + 110 : "¡Qué locura!", + 120 : "¡Impresionante!", + 130 : "¡Por amor de Dios!", + 140 : "No te preocupes.", + 150 : "¡Grrrr!", + 160 : "¿Alguna novedad?", + 170 : "¡Eh, eh, eh!", + 180 : "Hasta mañana.", + 190 : "Hasta otra vez.", + 200 : "Nos vemos.", + 210 : "Hasta la vista.", + 220 : "Tengo que irme pronto.", + 230 : "¡No sé nada de esto!", + 240 : "¡Largo de aquí!", + 250 : "¡Ay! ¡Cómo duele!", + 260 : "¡Te tengo!", + 270 : "¡Por favor!", + 280 : "¡Mil gracias!", + 290 : "¡Me encanta cómo vas!", + 300 : "¡Perdona!", + 310 : "¿Te puedo ayudar?", + 320 : "¡A eso me refería!", + 330 : "Si no aguantas el calor, no te acerques al horno.", + 340 : "¡Truenos y relampagos!", + 350 : "¡Pero qué cosa más especial!", + 360 : "¡Deja de enredar!", + 370 : "¿Te ha comido la lengua el gato?", + 380 : "¡Estás metido en un buen lío!", + 390 : "Pero mira lo que tenemos aquí...", + 400 : "Tengo que ir a ver a un dibu.", + 410 : "¡No te enfades!", + 420 : "¡No seas gallina!", + 430 : "Eres un blanco fácil.", + 440 : "¡Lo que sea!", + 450 : "¡Totalmente!", + 460 : "¡Estupendo!", + 470 : "¡Fantástico!", + 480 : "¡Por supuesto!", + 490 : "¡A que no me pillas!", + 500 : "Primero tienes que curarte.", + 510 : "Necesitas más puntos de risa.", + 520 : "Vuelvo en un momento.", + 530 : "Tengo hambre.", + 540 : "¡Sí, claro!", + 550 : "Tengo sueño.", + 560 : "¡Estoy listo!", + 570 : "Estoy aburrido.", + 580 : "¡Me encanta!", + 590 : "¡Qué emocionante!", + 600 : "¡Salta!", + 610 : "¿Tienes bromas?", + 620 : "¿Qué pasa?", + 630 : "Poco a poco.", + 640 : "Despacito y buena letra.", + 650 : "¡Gol!", + 660 : "¡Prepararse!", + 670 : "¡Listos!", + 680 : "¡Ya!", + 690 : "¡Vamos por aquí!", + 700 : "¡Has ganado!", + 710 : "Yo voto que sí.", + 720 : "Yo voto que no.", + 730 : "Cuenta conmigo.", + 740 : "No cuentés conmigo.", + 750 : "Quédate aquí, vuelvo enseguida.", + 760 : "¡Qué rápido!", + 770 : "¿Has visto eso?", + 780 : "¿A qué huele eso?", + 790 : "¡Qué hediondo!", + 800 : "No me importa.", + 810 : "Justo lo que el medico ordenó.", + 820 : "¡Que empiece la fiesta!", + 830 : "¡Por aquí todos!", + 840 : "¿Qué pasa?", + 850 : "El cheque está en el correo.", + 860 : "¡Te he oído!", + 870 : "¿Me estas hablando?", + 880 : "Gracias, estaré aquí toda la semana.", + 890 : "Mmm.", + 900 : "Yo me ocupo de éste.", + 910 : "¡Lo tengo!", + 920 : "¡Es mio!", + 930 : "Por favor, toma esto.", + 940 : "No te acerques; esto puede ser peligroso.", + 950 : "¡No te preocupes!", + 960 : "¡Vaya!", + 970 : "¡Guau!", + 980 : "¡Uuuuuu!", + 990 : "¡Todos a bordo!", + 1000 : "¡Que fantastico!", + 1010 : "La curiosidad mató al gato.", + # Series 2 + 2000 : "¡Vamos, no seas infantil!", + 2010 : "¡Qué alegría de verte!", + 2020 : "No faltaba más.", + 2030 : "No te habrás metido en líos, ¿verdad?", + 2040 : "¡Mejor tarde que nunca!", + 2050 : "¡Bravo!", + 2060 : "No, en serio, amigos...", + 2070 : "¿Te animas?", + 2080 : "¡Nos vemos luego!", + 2090 : "¿Has cambiado de idea?", + 2100 : "¡Si lo quieres, ven a cogerlo!", + 2110 : "¡Ay, dios mío!", + 2120 : "Encantado de conocerte.", + 2130 : "¡No hagas nada que yo no haría!", + 2140 : "¡Ni se te ocurra!", + 2150 : "¡No tires la toalla!", + 2160 : "Mejor espera sentado.", + 2170 : "No preguntes.", + 2180 : "Para ti todo es muy fácil.", + 2190 : "¡Ya está bien!", + 2200 : "¡Estupendo!", + 2210 : "¡Vaya, qué casualidad!", + 2220 : "¡Déjame en paz!", + 2230 : "Y yo que me alegro.", + 2240 : "Venga, que me voy a divertir un rato.", + 2250 : "¡A por todas!", + 2260 : "¡Bien hecho!", + 2270 : "¡Qué alegría de verte!", + 2280 : "Me tengo que pirar.", + 2290 : "Me tengo que largar.", + 2300 : "Espera.", + 2310 : "Un momento.", + 2320 : "¡Disfruta como un loco!", + 2330 : "¡Diviértete!", + 2340 : "Oye, que no tengo todo el día…", + 2350 : "¡Un momento!", + 2360 : "¡Qué pamplinas!", + 2370 : "¡No me lo puedo creer!", + 2380 : "Lo dudo mucho.", + 2390 : "Te debo una.", + 2400 : "Te oigo perfectamente.", + 2410 : "Creo que sí.", + 2420 : "Creo que deberías pasar.", + 2430 : "Ya lo podía haber dicho yo.", + 2440 : "Ni se te ocurra.", + 2450 : "¡Por mí, encantado!", + 2460 : "Estoy ayudando a mi amigo.", + 2470 : "Me quedo toda la semana.", + 2480 : "¡Imagínate!", + 2490 : "Justo a tiempo...", + 2500 : "Todavía no ha acabado la cosa.", + 2510 : "Estaba pensando en voz alta.", + 2520 : "No te pierdas; llama de vez en cuando.", + 2530 : "¡Garbanzos en remojo!", + 2540 : "¡Muévete!", + 2550 : "Ponte cómodo.", + 2560 : "En otro momento, quizás.", + 2570 : "¿Interrumpo?", + 2580 : "¡Menuda choza!", + 2590 : "Bueno, una charla muy agradable.", + 2600 : "Sin lugar a dudas.", + 2610 : "¡No me digas!", + 2620 : "Ni por asomo.", + 2630 : "¡Qué cara!", + 2640 : "Por mí, vale.", + 2650 : "¡Vale!", + 2660 : "¡Sonrían, por favor!", + 2670 : "¿Qué?", + 2680 : "¡Pachán!", + 2690 : "Tómatelo con calma.", + 2700 : "¡Chao!", + 2710 : "Gracias, pero mejor no.", + 2720 : "¡Esto es el acabóse!", + 2730 : "Qué bueno.", + 2740 : "¡Justo lo que necesitaba!", + 2750 : "¡"+TheCogs+" nos invaden!", + 2760 : "¡Arrivederci!", + 2770 : "¡Cuidado!", + 2780 : "¡Muy bien!", + 2790 : "¿Qué se cuece?", + 2800 : "¿Qué pasa?", + 2810 : "Por mí, vale.", + 2820 : "Sí, señor.", + 2830 : "Por supuesto.", + 2840 : "Haz el cálculo.", + 2850 : "¿Ya te vas?", + 2860 : "¡No me hagas reír!", + 2870 : "Vete por la derecha.", + 2880 : "¡Despídete de este mundo!", + # Series 3 + 3000 : "Lo que tú digas.", + 3010 : "¿Molesto?", + 3020 : "La cuenta, por favor.", + 3030 : "Yo no estaría tan seguro.", + 3040 : "No te voy a decir que no.", + 3050 : "Tampoco te partas la crisma…", + 3060 : "Lo sabes muy bien.", + 3070 : "Yo, como si no estuviera.", + 3080 : "¡Eureka!", + 3090 : "¡Imagínate!", + 3100 : "¡Ni lo sueñes!", + 3110 : "¿Vienes conmigo?", + 3120 : "¡Muy bien!", + 3130 : "¡Por dios!", + 3140 : "¡Que disfrutes!", + 3150 : "¡Ánimo!", + 3160 : "Otra vez.", + 3170 : "¡Toma ya!", + 3180 : "¡Ahí tienes!", + 3190 : "Eso creo.", + 3200 : "Ni de broma.", + 3210 : "Luego te contesto.", + 3220 : "Soy todo oídos.", + 3230 : "Estoy ocupado.", + 3240 : "¡Que no estoy de broma!", + 3250 : "Me he quedado de una piedra.", + 3260 : "Venga, ánimo.", + 3270 : "¡Mantenme al corriente!", + 3280 : "¡Lluvia de tartas!", + 3290 : "Lo mismo digo.", + 3300 : "¡Andando, que es gerundio!", + 3310 : "El tiempo vuela.", + 3320 : "Sin comentarios.", + 3330 : "¡Así se habla!", + 3340 : "Por mí, bien.", + 3350 : "Encantado.", + 3360 : "Vale.", + 3370 : "Claro que sí.", + 3380 : "Muchísimas gracias.", + 3390 : "Así me gusta.", + 3400 : "¡Así se hace!", + 3410 : "Me voy para el catre.", + 3420 : "¡Hazme caso!", + 3430 : "Hasta la próxima.", + 3440 : "¡Espera un momentito!", + 3450 : "¡Así se hace!", + 3460 : "¿Cómo tú por aquí?", + 3470 : "¿Qué ha pasado?", + 3480 : "¿Ahora qué?", + 3490 : "Después de ti.", + 3500 : "Vete por la izquierda.", + 3510 : "¡Eso es lo que tú querrías!", + 3520 : "¡Date por muerto!", + 3530 : "¡Menudo eres tú!", + + # Halloween + 10000 : "Esto es una ciudad fantasma.", + 10001 : "¡Bonito disfraz!", + 10002 : "Creo que esto está embrujado.", + 10003 : "¡Chuches o venganza!", + 10004 : "¡Buuu!", + 10005 : "¡Feliz embrujamiento!", + 10006 : "¡Feliz Halloween!", + 10007 : "Es la hora de convertirme en una calabaza.", + 10008 : "¡Fantasmástico!", + 10009 : "¡Espeluznante!", + 10010 : "¡Qué miedo da!", + 10011 : "¡Odio las arañas!", + 10012 : "¿Has oído eso?", + 10013 : "¡Tienes menos posibilidades que un fantasma!", + 10014 : "¡Me has asustado!", + 10015 : "¡Qué susto!", + 10016 : "¡Qué monstruoso!", + 10017 : "Eso ha sido muy raro...", + 10018 : "¿Habrán esqueletos en el armario?", + 10019 : "¿Te he asustado?", + + # Fall Festivus + 11000 : "Anda, que estás más relleno que un pavo en Navidad", + 11001 : "¡No me vengas con pucheros!", + 11002 : "¡Brrr!", + 11003 : "¡Tranqui, colega!", + 11004 : "¡Ven a por ello!", + 11005 : "Qué pavo eres.", + 11006 : "¡glu, glu, glu!", + 11007 : "¡Felices vacaciones!", + 11008 : "¡Feliz año nuevo!", + 11009 : "Sé bueno que los Reyes Magos lo ven todo.", + # 11010 : "¡Feliz pavo!", + 11011 : "¡No te atragantes con las uvas!", + 11012 : "Estás más ilocalizable que Papá Noel en Nochebuena.", + 11013 : "Va más rápido que las campanadas de Fin de Año.", + 11014 : "¡Que nieve!", + 11015 : "Trae para acá.", + 11016 : "¡Felices Fiestas!", + 11017 : "Estoy más mosqueado que un pavo oyendo panderetas.", + 11018 : "¡Nevando voy, nevando vengo!", + 11019 : "¡Te vas a arrepentir, tronco!", + + # Valentines + # 12000 : "Si el mundo fuera un pañuelo…, serías mi moco preferido.", + 12001 : "¡Te quieroooooooo!", + 12002 : "¡Feliz día de San Valentín!", + 12003 : "Oooh, qué bonito.", + 12004 : "Me gustas un montón.", + 12005 : "Amor incondicional.", + 12006 : "¡Te quiero! ¿Me oyes?", + 12007 : "¿Quedamos por San Valentín?", + 12008 : "Gracias, mi vida.", + 12009 : "Gracias, cariñín.", + 12010 : "Qué guapo eres.", + 12011 : "Qué guapa eres.", + 12012 : "¡Estupendo!", + 12013 : "¡Qué bien!", + 12014 : "Las rosas son rojas...", + 12015 : "Las violetas azuladas...", + 12016 : "¡Qué bonito!", + + # St. Patricks Day + 13000 : "Llegó el verano y los mosquitos mueren entre aplausos.", + 13001 : "¡Feliz veraneo!", + 13002 : "¡Con las chanclas y a la playa!", + 13003 : "Si la piscina es honda… ¿El mar es toyota?", + 13004 : "Tengo más problemas que las Vacaciones Santillana.", + 13005 : "Buen viaje, y si el avión viene demorado, tú ponte “deverde”.", + 13006 : "¡Te repites más que Verano Azul!", + 13007 : "¡Que disfrutes del sol!", + } + +# indices into cog phrase arrays +SCMenuCommonCogIndices = (20000, 20004) +SCMenuCustomCogIndices = { + 'bf' : (20005, 20014), + 'nc' : (20015, 20024), + 'ym' : (20025, 20035), + 'ms' : (20036, 20046), + 'bc' : (20047, 20057), + 'cc' : (20058, 20070), + 'nd' : (20071, 20080), + 'ac' : (20081, 20092), + 'tf' : (20093, 20103), + 'hh' : (20104, 20114), + 'le' : (20115, 20124), + 'bs' : (20125, 20135), + 'cr' : (20136, 20145), + 'tbc' : (20146, 20156), + 'ds' : (20157, 20164), + 'gh' : (20165, 20177), + 'pp' : (20178, 20187), + 'b' : (20188, 20199), + 'f' : (20200, 20210), + 'mm' : (20211, 20224), + 'tw' : (20225, 20235), + 'mb' : (20236, 20245), + 'm' : (20246, 20254), + 'mh' : (20255, 20266), + 'dt' : (20267, 20276), + 'p' : (20277, 20287), + 'tm' : (20288, 20298), + 'bw' : (20299, 20308), + 'ls' : (20309, 20319), + 'rb' : (20320, 20329), + 'sc' : (20330, 20331), + 'sd' : (20341, 20350), + } + +# Playground.py +PlaygroundDeathAckMessage = "¡"+TheCogs+" se han llevado todas tus bromas!\n\nEstás triste. No puedes irte del dibuparque hasta que estés feliz." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "El capataz de la fábrica ha sido derrotado antes de que llegaras. No has recuperado ninguna pieza bot." + +# DistributedFactory.py +HeadingToFactoryTitle = "De camino a %s..." +ForemanConfrontedMsg = "¡%s está luchando contra el capataz de la fábrica!" + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Esperando a que se unan otros jugadores..." +MinigamePleaseWait = "Espera..." +DefaultMinigameTitle = "Título del minijuego" +DefaultMinigameInstructions = "Instrucciones del minijuego" +HeadingToMinigameTitle = "Entrando al %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Indicador\nde fuerza" +MinigamePowerMeterTooSlow = "Muy\nlento" +MinigamePowerMeterTooFast = "Muy\nrápido" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Plantilla del minijuego" +MinigameTemplateInstructions = "Ésta es una plantilla de minijuegos. Úsala para crear nuevos minijuegos." + +# DistributedCannonGame.py +CannonGameTitle = "Juego El Cañón" +CannonGameInstructions = "Dispara a tu dibu para meterlo en el depósito de agua tan rápido como puedas. Usa el ratón o las teclas de flecha para apuntar el cañón. ¡Date prisa y consigue una gran recompensa para todos!" +CannonGameReward = "RECOMPENSA" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Juego La Cuerda" +TugOfWarInstructions = "Pulsa alternativamente las teclas de flecha izquierda y derecha con la suficiente rapidez para alinear la barra verde con la línea roja. ¡No pulses demasiado deprisa ni demasiado despacio, o acabarás en el agua!" +TugOfWarGameGo = "¡YA!" +TugOfWarGameReady = "Listo..." +TugOfWarGameEnd = "¡Muy bien!" +TugOfWarGameTie = "¡Has empatado!" +TugOfWarPowerMeter = "Indicador de fuerza" + +# DistributedPatternGame.py +PatternGameTitle = "Imita a %s" % Minnie +PatternGameInstructions = Minnie + " te enseñará una secuencia de baile " + \ + "Intenta repetir con las teclas de flecha el baile de "+Minnie+" justo igual que lo hace ella." +PatternGameWatch = "Observa estos pasos de baile..." +PatternGameGo = "¡YA!" +PatternGameRight = "¡Bien, %s!" +PatternGameWrong = "¡Vaya!" +PatternGamePerfect = "¡Ha sido perfecto, %s!" +PatternGameBye = "¡Gracias por jugar!" +PatternGameWaitingOtherPlayers = "Esperando a otros jugadores..." +PatternGamePleaseWait = "Espera..." +PatternGameFaster = "¡Has sido\nmuy rápido!" +PatternGameFastest = "¡Has sido\nel más rápido!" +PatternGameYouCanDoIt = "¡Vamos!\n¡Puedes hacerlo!" +PatternGameOtherFaster = "\nha sido más rápido." +PatternGameOtherFastest = "\nha sido el más rápido." +PatternGameGreatJob = "¡Buen trabajo!" +PatternGameRound = "¡Asalto nº %s!" # Round 1! Round 2! .. + +# DistributedRaceGame.py +RaceGameTitle = "Juego La Carrera" +RaceGameInstructions = "Haz clic en un número. ¡Piénsalo bien! Sólo avanzarás si nadie más ha escogido ese mismo número." +RaceGameWaitingChoices = "Esperando a que elijan otros jugadores..." +RaceGameCardText = "%(name)s saca: %(reward)s" +RaceGameCardTextBeans = "%(name)s recibe: %(reward)s" +RaceGameCardTextHi1 = "¡%(name)s es un dibu fabuloso!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " adelante 1 espacio" +RaceGameForwardTwoSpaces = " adelante 2 espacios" +RaceGameForwardThreeSpaces = " adelante 3 espacios" +RaceGameBackOneSpace = " atrás 1 espacio" +RaceGameBackTwoSpaces = " atrás 2 espacios" +RaceGameBackThreeSpaces = " atrás 3 espacios" +RaceGameOthersForwardThree = " todos los demás, adelante \n3 espacios" +RaceGameOthersBackThree = "todos los demás, atrás \n3 espacios" +RaceGameInstantWinner = "¡Ganador instantáneo!" +RaceGameJellybeans2 = "2 gominolas" +RaceGameJellybeans4 = "4 gominolas" +RaceGameJellybeans10 = "¡10 gominolas!" + +# DistributedRingGame.py +RingGameTitle = "Juego Los Anillos" +# color +RingGameInstructionsSinglePlayer = "Intenta atravesar nadando todos los anillos que puedas de color %s. Usa las teclas de flecha para nadar." +# color +RingGameInstructionsMultiPlayer = "Intenta atravesar nadando los anillos de color %s. Los demás jugadores intentarán atravesar el resto de los anillos. Usa las flechas del teclado para nadar." +RingGameMissed = "FALLO" +RingGameGroupPerfect = "GRUPO\n¡¡PERFECTO!!" +RingGamePerfect = "¡PERFECTO!" +RingGameGroupBonus = "BONIFICACIÓN POR GRUPO" + +# RingGameGlobals.py +ColorRed = "rojo" +ColorGreen = "verde" +ColorOrange = "naranja" +ColorPurple = "morado" +ColorWhite = "blanco" +ColorBlack = "negro" +ColorYellow = "amarillo" + +# DistributedTagGame.py +TagGameTitle = "Tú la llevas" +TagGameInstructions = "Recoge los tesoros. ¡No puedes recoger tesoros cuando LA LLEVES!" +TagGameYouAreIt = "¡Tú la LLEVAS!" +TagGameSomeoneElseIsIt = "¡%s la LLEVA!" + +# DistributedMazeGame.py +MazeGameTitle = "Juego El Laberinto" +MazeGameInstructions = "Recoge los tesoros. ¡Intenta recogerlos todos, pero ten cuidado con los bots!" + +# DistributedCatchGame.py +CatchGameTitle = "Juego Atrapa las frutas" +CatchGameInstructions = "Atrapa %(fruit)s que puedas. Cuidado con los " + Cogs + ", y trata de no ‘atrapar’ ningún %(badThing)s!" +CatchGamePerfect = "¡PERFECTO!" +CatchGameApples = 'todas las manzanas' +CatchGameOranges = 'todas las naranjas' +CatchGamePears = 'todas las peras' +CatchGameCoconuts = 'todos los cocos' +CatchGameWatermelons = 'todas las sandìas' +CatchGamePineapples = 'todas las piñas' +CatchGameAnvils = 'yunque' + +# DistributedPieTossGame.py +PieTossGameTitle = "Juego Lanzatartas" +PieTossGameInstructions = "Prueba tu puntería lanzando tartas." + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JUGAR" + +# Purchase.py +GagShopName = "Tienda de bromas de Goofy" +GagShopPlayAgain = "JUGAR\nOTRA VEZ" +GagShopBackToPlayground = "VOLVER AL\nDIBUPARQUE" +GagShopYouHave = "Tienes %s gominolas para gastar" +GagShopYouHaveOne = "Tienes 1 gominola para gastar" +GagShopTooManyProps = "Lo siento, tienes demasiados accesorios" +GagShopDoneShopping = "COMPRAS\nFINALIZADAS" +# name of a gag +GagShopTooManyOfThatGag = "Lo siento, ya tienes suficientes %s." +GagShopInsufficientSkill = "Todavía no tienes suficiente habilidad para eso" +# name of a gag +GagShopYouPurchased = "Has comprado %s" +GagShopOutOfJellybeans = "¡Lo siento, te has quedado sin gominolas!" +GagShopWaitingOtherPlayers = "Esperando a otros jugadores..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s se ha desconectado" +GagShopPlayerExited = "%s se ha marchado" +GagShopPlayerPlayAgain = "Jugar de nuevo" +GagShopPlayerBuying = "Comprando" + +# MakeAToon.py +# +# The voices for GenderShopQuestionMickey and Minnie should not be played simultaneously. +# Options are as follows: +# 1: Mickey first and Minnie follow in a few second. +# 2: When player moves cursor onto the character, the voice to be played. +# But the voice shouldn't be played while other character is talking. +# Please choose whichever feasible. +# +GenderShopQuestionMickey = "¡Para crear un dibuchico, haz clic en mí!" +GenderShopQuestionMinnie = "¡Para crear una dibuchica, haz clic en mí!" +GenderShopFollow = "¡Sígueme!" +GenderShopSeeYou = "¡Hasta luego!" +GenderShopBoyButtonText = "Chico" +GenderShopGirlButtonText = "Chica" + +# BodyShop.py +BodyShopHead = "Cabeza" +BodyShopBody = "Cuerpo" +BodyShopLegs = "Piernas" + +# ColorShop.py +ColorShopHead = "Cabeza" +ColorShopBody = "Cuerpo" +ColorShopLegs = "Piernas" +ColorShopToon = "Dibu" +ColorShopParts = "Partes" +ColorShopAll = "Todo" + +# ClothesShop.py +ClothesShopShorts = "Shorts" +ClothesShopShirt = "Camiseta" +ClothesShopBottoms = "Falda" + +# MakeAToon +MakeAToonDone = "Hecho" +MakeAToonCancel = "Cancelar" +MakeAToonNext = "Siguiente" +MakeAToonLast = "Atrás" +CreateYourToon = "Haz clic en las flechas para crear a tu dibu." +CreateYourToonTitle = "Crea a tu dibu" +CreateYourToonHead = "Haz clic en las flechas de la \"cabeza\" para escoger diferentes animales." +MakeAToonClickForNextScreen = "Haz clic en la flecha situada abajo para ir a la pantalla siguiente." +PickClothes = "¡Haz clic en las flechas para escoger prendas!" +PickClothesTitle = "Escoge tus prendas" +PaintYourToon = "¡Haz clic en las flechas para pintar a tu dibu!" +PaintYourToonTitle = "Pinta a tu dibu" +MakeAToonYouCanGoBack = "¡También puedes volver para cambiar tu cuerpo!" +MakeAFunnyName = "¡Elige un nombre divertido para el dibu con el juego de los nombres!" +MustHaveAFirstOrLast1 = "Tu dibu debería tener un nombre o un apellido, ¿no crees?" +MustHaveAFirstOrLast2 = "¿No quieres que tu dibu tenga un nombre o un apellido?" +ApprovalForName1 = "¡Eso es, tu dibu se merece un gran nombre!" +ApprovalForName2 = "¡Los nombres de dibus son los mejores!" +MakeAToonLastStep = "¡Último paso antes de ir a Toontown!" +PickANameYouLike = "¡Escoge un nombre que te guste!" +NameToonTitle = "Pon un nombre a tu dibu" +TitleCheckBox = "Título" +FirstCheckBox = "Nombre" +LastCheckBox = "Apellido" +RandomButton = "Al azar" +NameShopSubmitButton = "Enviar" +TypeANameButton = "Escribe un nombre" +TypeAName = "¿No te gustan estos nombres?\nHaz clic aquí -->" +PickAName = "¡Prueba con el juego de los nombres!\nHaz clic aquí -->" +PickANameButton = "Juego de los nombres" +RejectNameText = "Ese nombre no está permitido. Inténtalo de nuevo." +WaitingForNameSubmission = "Se está enviando tu nombre..." + +# NameShop.py +NameShopNameMaster = "NameMasterCastillian.txt" +NameShopPay = "¡Suscríbete ya!" +NameShopPlay = "Prueba gratuita" +NameShopOnlyPaid = "Sólo los usuarios abonados\npueden poner nombre a sus dibus.\nHasta que te suscribas,\ntu nombre será\n" +NameShopContinueSubmission = "Continuar envío" +NameShopChooseAnother = "Elegir otro nombre" +NameShopToonCouncil = "El Consejo Dibu\nrevisará tu\nnombre. " + \ + "La revisión puede\ntardar unos días.\nMientras esperas,\ntu nombre será\n " +PleaseTypeName = "Escribe tu nombre:" +AllNewNames = "Todos los nombres nuevos\ndeben ser aprobados\npor el Consejo Dibu." +NameShopNameRejected = "El nombre que has\nenviado ha sido\nrechazado." +NameShopNameAccepted = "¡Enhorabuena!\nEl nombre que has\nenviado ha sido\naceptado." +NoPunctuation = "¡No puedes usar signos de puntuación en tu nombre!" +PeriodOnlyAfterLetter = "Tu nombre puede incluir un punto, pero sólo después de una letra." +ApostropheOnlyAfterLetter = "Tu nombre puede incluir un apóstrofo, pero sólo después de una letra." +NoNumbersInTheMiddle = "Es posible que los números no aparezcan si están en medio de una palabra." +ThreeWordsOrLess = "Tu nombre debe tener un máximo de tres palabras." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "donald duck", + "donaldduck", + "pluto", + "goofy", + ) +NumToColor = ['Blanco', 'Melocotón', 'Rojo brillante', 'Rojo', 'Castaño', + 'Siena', 'Marrón', 'Marrón claro', 'Coral', 'Naranja', + 'Amarillo', 'Crema', 'Topacio', 'Lima', 'Verde mar', + 'Verde', 'Azul claro', 'Aguamarina', 'Azul', + 'Hierba', 'Azul marino', 'Azul pizarra', 'Morado', + 'Lavanda', 'Rosa'] +AnimalToSpecies = { + 'dog' : 'Perro', + 'cat' : 'Gato', + 'mouse' : 'Ratón', + 'horse' : 'Caballo', + 'rabbit' : 'Conejo', + 'duck' : 'Pato', + } +NameTooLong = "Ese nombre es demasiado largo. Inténtalo de nuevo." +ToonAlreadyExists = "¡Ya tienes un dibu llamado %s!" +NameAlreadyInUse = "¡Ese nombre ya ha sido usado!" +EmptyNameError = "Primero debes introducir un nombre." +NameError = "Lo siento. Ese nombre no sirve." + +# NameCheck.py +NCTooShort = 'Ese nombre es demasiado corto.' +NCNoDigits = 'Tu nombre no puede contener números.' +NCNeedLetters = 'Todas las palabras de tu nombre deben contener letras.' +NCNeedVowels = 'Todas las palabras de tu nombre deben contener vocales.' +NCAllCaps = 'Tu nombre no puede estar por completo en mayúsculas.' +NCMixedCase = 'Ese nombre tiene demasiadas mayúsculas.' +NCBadCharacter = "Tu nombre no puede contener el carácter '%s'" +NCGeneric = 'Lo siento, ese nombre no sirve.' +NCTooManyWords = 'Tu nombre no puede tener más de cuatro palabras.' +NCDashUsage = ("Sólo puedes usar los guiones para unir dos palabras " + "(como en \"Bu-bu\").") +NCCommaEdge = "Tu nombre no puede comenzar ni terminar con una coma." +NCCommaAfterWord = "No puedes comenzar una palabra con una coma." +NCCommaUsage = ('Ese nombre no emplea las comas correctamente. Las comas deben ' + 'unir dos palabras, como en el nombre \"Dr. Pato, cirujano\"". ' + 'Además, las comas deben ir seguidas por un espacio.') +NCPeriodUsage = ('Ese nombre no emplea los puntos correctamente. Sólo se permiten ' + 'los puntos en palabras como \"Sr.\", \"Sra.\", \"J.T.\", etc.') +NCApostrophes = 'Ese nombre tiene demasiados apóstrofos.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = "Cuartel general dibu: ¡"+TheCogs+" han tomado el control de uno de los edificios que has recuperado!" + +# toon\DistributedNPCTailor/Clerk.py +STOREOWNER_TOOKTOOLONG = '¿Necesitas más tiempo para pensártelo?' +STOREOWNER_GOODBYE = '¡Hasta luego!' +STOREOWNER_NEEDJELLYBEANS = 'Tienes que subir al tranvía para conseguir gominolas.' +STOREOWNER_GREETING = 'Elige lo que quieras comprar.' +STOREOWNER_BROWSING = 'Puedes mirar, pero para comprar necesitas un tícket de ropa.' +STOREOWNER_NOCLOTHINGTICKET = 'Para comprar prendas necesitas un tícket de ropa.' +# translate +STOREOWNER_NOFISH = 'Vuelve aquí para vender peces a la tienda de animales a cambio de gominolas.' +STOREOWNER_THANKSFISH = '¡Gracias! A la tienda de animales le van a encantar. ¡Adiós!' + +STOREOWNER_NOROOM = "Mmm...tienes que tener mas sitio en tu clóset antes de comprar mas ropa.\n" +STOREOWNER_CONFIRM_LOSS = "Tú closet esta lleno. Tú vas a perder la ropa que estabas usando." +STOREOWNER_OK = "Muy bien" +STOREOWNER_CANCEL = "Cancelar" +STOREOWNER_TROPHY = "Wow! You collected %s of %s fish. That deserves a trophy and a LaffBoost!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = "Cuartel general: ¡¡Hay una invasión de bots!!" +SuitInvasionBegin2 = "Cuartel general: ¡¡Los %s han tomado Toontown!!!" +SuitInvasionEnd1 = "Cuartel general: ¡¡¡La invasión de %s ha terminado!!!" +SuitInvasionEnd2 = "Cuartel general: ¡¡¡Los dibus han vuelto a salvarnos!!!" +SuitInvasionUpdate1 = "Cuartel general: ¡¡¡La invasión de bots consta ahora de %s bots!!!" +SuitInvasionUpdate2 = "Cuartel general: ¡¡¡Debemos derrotar a esos %s!!!" +SuitInvasionBulletin1 = "Cuartel general: ¡¡¡Se está produciendo una invasión de bots!!!" +SuitInvasionBulletin2 = "Cuartel general: ¡¡Los %s han tomado Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Pelotón de dibus" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "¡Toontown tiene un nuevo habitante! ¿Tienes alguna broma de sobra?" +QuestScriptTutorialMickey_2 = "¡Claro, %s!" +QuestScriptTutorialMickey_3 = "Tato Tutorial te lo explicará todo sobre los bots.\a¡Tengo que irme!" +QuestScriptTutorialMickey_4 = "¡Ven aquí! Usa las flechas del teclado para moverte." + +# These are needed to correspond to the Japanese gender specific phrases +# +#QuestScriptTutorialMinnie_1 = "¡Toontown tiene un nuevo habitante! ¿Tienes alguna broma de sobra?" +#QuestScriptTutorialMinnie_2 = "¡Claro, %s!" +#QuestScriptTutorialMinnie_3 = "Tato Tutorial te lo explicará todo sobre los bots.\a¡Tengo que irme!" +# + +# +# If there is "\a" between the sentense, we would like to have one of the following sequence. +# 1: display 1st text with 1st voice -> when voice finished, arrow appear. -> if player pushes the arrow button, display 2nd text with 2nd voice. +# 2: display 1st text with 1st voice and altomatically display 2nd text with 2nd voice. +# 3: display 1st text and play 1st voice (arrow is displayed) -> whenever player pushes the button, the voice will be skipped and display 2nd text with 2nd voice. +# Anyway, we need to have some "Skip" rule while playing the voice because from DCV(Disney Character Voice)'s view, it is not preferrable to have voice skipped. +# + +QuestScript101_1 = "Ésos son los BOTS. Son robots que intentan tomar el control de Toontown." +QuestScript101_2 = "Hay muchos tipos distintos de BOTS, que..." +QuestScript101_3 = "...convierten los alegres edificios de los dibus..." +QuestScript101_4 = "...¡en horribles edificios bot!" +QuestScript101_5 = "¡Pero los BOTS no aguantan las bromas!" +QuestScript101_6 = "Una buena broma acaba con ellos." +QuestScript101_7 = "Hay un montón de bromas, pero puedes empezar con éstas." +QuestScript101_8 = "¡Oh! ¡También necesitas un risómetro!" +QuestScript101_9 = "Si tu risómetro disminuye demasiado, te pondrás triste." +QuestScript101_10 = "¡Cuanto más contento está un dibu, más sano está!" +QuestScript101_11 = "¡OH, NO! ¡Hay un BOT fuera de la tienda!" +QuestScript101_12 = "¡AYÚDAME, POR FAVOR! ¡Derrota a ese BOT!" +QuestScript101_13 = "¡Aquí tienes tu primera dibutarea!" +QuestScript101_14 = "¡Deprisa! ¡Derrota a ese secuaz!" + +QuestScript110_1 = "Gracias por derrotar a ese secuaz. Te voy a dar un dibucuaderno..." +QuestScript110_2 = "Es un cuaderno lleno de cosas chulas." +QuestScript110_3 = "Ábrelo y te iré enseñando cosas." +QuestScript110_4 = "El mapa te muestra dónde has estado." +QuestScript110_5 = "Pasa la página para ver tus bromas..." +QuestScript110_6 = "¡Vaya! ¡No tienes bromas! Te voy a asignar una tarea." +QuestScript110_7 = "Pasa la página para ver las tareas." +QuestScript110_8 = "Para comprar bromas tienes que subirte al tranvía y conseguir gominolas." +QuestScript110_9 = "Para subirte al tranvía, sal por la puerta que hay detrás de mí y dirígete al dibuparque." +QuestScript110_10 = "Ahora, cierra el dibucuaderno y busca el tranvía." +QuestScript110_11 = "Vuelve al cuartel general cuando hayas terminado. ¡Chao!" + +QuestScriptTutorialBlocker_1 = "¡Eh, hola!" +QuestScriptTutorialBlocker_2 = "¿Hola?" +QuestScriptTutorialBlocker_3 = "¡Oh! ¡No sabes cómo se usa la Charla rápida!" +QuestScriptTutorialBlocker_4 = "Haz clic en el botón para decir algo." +QuestScriptTutorialBlocker_5 = "¡Muy bien!\aEn el sitio al que vas hay muchos dibus con los que puedes hablar." +QuestScriptTutorialBlocker_6 = "Si quieres charlar con tus amigos mediante el teclado, tienes que usar otro botón." +QuestScriptTutorialBlocker_7 = "Es el botón \"Charla\". Para poder usarlo tienes que ser habitante oficial de Toontown." +QuestScriptTutorialBlocker_8 = "¡Buena suerte! ¡Hasta luego!" + +""" +GagShopTut + +You will also earn the ability to use other types of gags. + +""" + +QuestScriptGagShop_1 = "Welcome to the Gag Shop!" +QuestScriptGagShop_1a = "This is where Toons come to buy gags to use against the Cogs." +#QuestScriptGagShop_2 = "This jar shows how many jellybeans you have." +QuestScriptGagShop_3 = "To buy a gag, click on a gag button. Try it now!" +QuestScriptGagShop_4 = "Good! You can use these gags in battle against the Cogs." +QuestScriptGagShop_5 = "Here's a peek at the advanced throw and squirt gags..." +QuestScriptGagShop_6 = "When you're done buying gags, click this button to return to the Playground." +QuestScriptGagShop_7 = "Normally you can use this button to play another Trolley Game..." +QuestScriptGagShop_8 = "...but there's no time for another game right now. You're needed in Toon HQ!" + +QuestScript120_1 = "¡Que bueno, encontraste el tranvía!\aPor cierto, ¿conoces ya a Pecunio el banquero?\aEs bastante goloso.\a¿Por qué no te presentas llevándole esta chocolatina como regalo?" +QuestScript120_2 = "Pecunio el banquero está en el Banco de Toontown." + +QuestScript121_1 = "Mmm, gracias por la chocolatina.\aOye, si me ayudas, te daré una recompensa.\a"+TheCogs+" han robado las llaves de mi caja fuerte. Derrota a los bots hasta recuperar la llave robada.\aCuando encuentres la llave, tráemela." + +QuestScript130_1 = "¡Que bueno, encontraste el tranvía!\aPor cierto, hoy he recibido un paquete para Pedro el maestro.\aDeben de ser las nuevas tizas que ha encargado.\a¿Puedes llevárselas?\aEstá en el colegio." + +QuestScript131_1 = "Oh, gracias por las tizas.\a¡¿Qué?!\a"+TheCogs+" me han robado la pizarra. Derrótales y recupera mi pizarra.\aCuando la encuentres, tráemela." + +QuestScript140_1 = "¡Que bueno, encontraste el tranvía!\aPor cierto, mi amigo Leopoldo es todo un devorador de libros.\aLa última vez que estuve en Puerto Donald le recogí este libro.\a¿Podrías llevárselo? Suele estar en la biblioteca." + +QuestScript141_1 = "Oh, sí, con este libro casi completaré mi colección.\aDéjame ver...\aVaya...\a¿Dónde habré puesto las gafas?\aLas tenía justo antes de que los bots ocupasen mi edificio.\aDerrótales y recupera mis gafas.\aCuando las encuentres, tráemelas y te daré una recompensa." + +QuestScript145_1 = "I see you had no problem with the trolley!\aListen, the Cogs have stolen our blackboard eraser.\aGo into the streets and fight Cogs until you recover the eraser.\aTo reach the streets go through one of the tunnels like this:" +QuestScript145_2 = "When you find our eraser, bring it back here.\aDon't forget, if you need gags, ride the trolley.\aAlso, if you need to recover Laff Points, collect ice cream cones in the Playground." + +QuestScript150_1 = "Oh... ¡Es posible que la tarea siguiente sea demasiado difícil para que la hagas solo!" +QuestScript150_2 = "Para hacerte amigo de alguien, busca a otro jugador y pulsa el botón Amigo nuevo." +QuestScript150_3 = "Cuando tengas un amigo nuevo, vuelve aquí." +QuestScript150_4 = "¡Algunas tareas son demasiado difíciles para hacerlas solo!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +BossCogName = "VIP mayor." +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "Os declaro ascendidos a %s hechos y derechos. ¡Enhorabuena!" +BossCogDoobersAway = { 's' : "¡Adiós! ¡Y a cerrar ventas!" } +BossCogWelcomeToons = "¡Bienvenidos, bots nuevos!" +BossCogPromoteToons = "Os declaro ascendidos a %s hechos y derechos. Enhora--" +CagedToonInterruptBoss = "¡Eh! ¡Oye! ¡Oye, aquí!" +CagedToonRescueQuery = "¡Eh, dibus! ¿Habéis venido a rescatarme?" +BossCogDiscoverToons = "¿Cómo? ¡Dibus disfrazados!" +BossCogAttackToons = "¡Al ataque!" +CagedToonDrop = [ + "¡Muy bien! ¡Estáis a punto de acabar con él!", + "¡Seguidlo! ¡Que se da a la fuga!", + "¡Sois estupendos!", + "¡Así, así! ¡Ya es prácticamente vuestro!", + ] +CagedToonPrepareBattleTwo = "¡Cuidado, que se escapa!\aAyudadme todos: ¡subid y detenedlo!" +CagedToonPrepareBattleThree = "¡Yupii, ya me estoy viendo libre!\aAhora ataca al bot VIP directamente.\aTengo aquí un montón de tartas que puedes lanzarle.\aSalta y toca la base de la jaula para que te las dé.\aPulsa la tecla Insertar para lanzar las tartas una vez que las tengas." +BossBattleNeedMorePies = "¡Necesitas más tartas!" +BossBattleHowToGetPies = "Salta para tocar la jaula y conseguir tartas." +BossBattleHowToThrowPies = "Pulsa la tecla Insertar para lanzar las tartas!" +CagedToonYippee = "¡Hurra!" +CagedToonThankYou = "¡Por fin libre!\a¡Muchísimas gracias por tu ayuda!\aEstoy en deuda contigo.\aSi alguna vez necesitas que te eche una mano luchando, no dudes en llamarme con el botón SOS." +CagedToonPromotion = "\a¡Eh! Ese bot VIP se ha dejado los documentos de tu ascenso.\aLos archivaré a la salida para que no te quedes sin el ascenso." +CagedToonLastPromotion = "\¡Hala!, has alcanzado el nivel %s de traje bot.\aEs el máximo en la escala de ascensos bot.\aYa no puedes seguir mejorando el traje, pero sí que puedes seguir rescatando dibus." +CagedToonHPBoost = "\aHas rescatado a un montón de dibus de este cuartel general.\aEl Consejo Dibu ha decidido darte otro punto de risa. ¡Enhorabuena!" +CagedToonMaxed = "\aVeo que tienes un traje bot del nivel %s. Estamos muy impresionados.\aEn nombre del Consejo Dibu, te damos las gracias por regresar para rescatar más dibus." +CagedToonGoodbye = "¡Nos vemos!" + + +CagedToonBattleThree = { + 10: "¡Qué salto, %(toon)s! Aquí tienes las tartas.", + 11: "¡Hola, %(toon)s! Ahí van las tartas.", + 12: "¡Oye, %(toon)s! Mira la de tartas que tienes ahora.", + + 20: "¡Oye, %(toon)s! Salta a la jaula que te doy unas tartitas para que las tires.", + 21: "¡Hola, %(toon)s! Dale a la tecla Ctrl para saltar y tocar la jaula.", + + 100: "Pulsa la tecla Insertar para lanzar las tartas.", + 101: "El contador azul indica a qué altura llegará la tarta.", + 102: "Primero, intenta colarle una tarta dentro del mecanismo de las piernas para bloquearlo.", + 103: "Espera a que se abra la puerta y lanza la tarta por el hueco.", + 104: "Cuando esté mareado, túmbalo dándole en la cara o el pecho.", + 105: "Si la tarta salpica en colores, es que el tiro ha sido perfecto.", + 106: "Si le das a un dibu con una tarta, recibirá un punto de risa.", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +BossElevatorRejectMessage = "No puedes montarte en este ascensor hasta que no te merezcas un ascenso." + +# Types of catalog items +FurnitureTypeName = "Mueble" +PaintingTypeName = "Cuadro" +ClothingTypeName = "Ropa" +ChatTypeName = "Frase de Charla rápida" +EmoteTypeName = "Clases de teatro" +PoleTypeName = "Caña de pescar" +WindowViewTypeName = "Vista desde la ventana" + +FurnitureYourOldCloset = "tu antiguo armario" +FurnitureYourOldBank = "tu antigua hucha" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py +FurnitureNames = { + 100 : "Sillón", + 110 : "Silla", + 120 : "Silla de escritorio", + 130 : "Silla de troncos", + 140 : "Silla de langosta", + 145 : "Silla de chaleco salvavidas", + 150 : "Saddle Stool", + 200 : "Cama", + 210 : "Cama", + 220 : "Cama de bañera", + 230 : "Cama de hoja", + 240 : "Cama de barco", + 250 : "Cactus Hammock", + 300 : "Pianola", + 310 : "Órgano de iglesia", + 400 : "Chimenea", + 410 : "Chimenea", + 420 : "Chimenea redonda", + 430 : "Chimenea", + 500 : "Armario", + 502 : "Armario con 15 prendas", + 510 : "Armario", + 512 : "Armario con 15 prendas", + 600 : "Lámpara baja", + 610 : "Lámpara alta", + 620 : "Lámpara de mesa", + 630 : "Lámpara de Daisy?", + 640 : "Lámpara de Daisy?", + 650 : "Lámpara de medusa", + 660 : "Lámpara de medusa", + 700 : "Butaca", + 710 : "Sofá", + 720 : "Hay Couch", + 800 : "Escritorio", + 810 : "Escritorio de troncos", + 900 : "Paragüero", + 910 : "Perchero", + 920 : "Cubo de basura", + 930 : "Taburete rojo", + 940 : "Taburete amarillo", + 950 : "Perchero", + 960 : "Barrel Stand", + 970 : "Cactus", + 1000 : "Alfombra grande", + 1010 : "Alfombra redonda", + 1020 : "Alfombra pequeña", + 1030 : "Felpudo de hoja", + 1100 : "Vitrina", + 1110 : "Vitrina", + 1120 : "Estantería alta", + 1130 : "Estantería baja", + 1200 : "Mesita lateral", + 1210 : "Mesita", + 1220 : "Mesa de centro", + 1230 : "Mesa de centro", + 1240 : "Mesa de buceador", + 1300 : "Hucha para 1.000 gominolas", + 1310 : "Hucha para 2.500 gominolas", + 1320 : "Hucha para 5.000 gominolas", + 1399 : "Teléfono", + 1400 : "Dibu Cezanne", + 1410 : "Flores", + 1420 : "Mickey moderno", + 1430 : "Dibu Rembrandt", + 1440 : "Paisaje dibu", + 1441 : "Escena hípica", + 1442 : "Estrella dibu", + 1443 : "¿No es una tarta?", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Televisión", + 1600 : "Jarrón pequeño", + 1610 : "Jarrón alto", + 1620 : "Jarrón pequeño", + 1630 : "Jarrón alto", + 1640 : "Jarrón pequeño", + 1650 : "Jarrón pequeño", + 1660 : "Jarrón de coral", + 1661 : "Jarrón de concha", + 1700 : "Carrito de palomitas", + 1710 : "Mariquita", + 1720 : "Fuente", + 1725 : "Lavadora", + 1800 : "Pecera", + 1810 : "Pecera", + 1900 : "Pez espada", + 1910 : "Pez martillo", + 1920 : "Hanging Horns", + 1930 : "Simple Sombrero", + 1940 : "Fancy Sombrero", + 10000 : "Calabaza pequeña", + 10010 : "Calabaza alargada", + } + +# CatalogClothingItem.py +ClothingArticleNames = ( + "Camisa", + "Camisa", + "Camisa", + "Pantalón corto", + "Pantalón corto", + "Falda", + "Pantalón corto", + ) + +ClothingTypeNames = { + 1400 : "Matthew's Shirt", + 1401 : "Jessica's Shirt", + 1402 : "Marissa's Shirt", + } + +# CatalogSurfaceItem.py +SurfaceNames = ( + "Papel pintado", + "Moldura", + "Suelo", + "Revestimiento de pared", + "Cenefa", + ) + +WallpaperNames = { + 1000 : "Pergamino", + 1100 : "Milán", + 1200 : "Marítimo", + 1300 : "Victoria", + 1400 : "Puerto Nuevo", + 1500 : "Pastoral", + 1600 : "Arlequín", + 1700 : "Luna", + 1800 : "Estrellas", + 1900 : "Flores", + 2000 : "Jardín de primavera", + 2100 : "Jardín ornamental", + 2200 : "Un día en las carreras", + 2300 : "Rugby", + 2400 : "Nube 9", + 2500 : "Parras", + 2600 : "Primavera", + 2700 : "Muñequitas japonesas", + 2800 : "Ramilletes", + 2900 : "Tiburón angelote", + 3000 : "Pompas", + 3100 : "Pompas", + 3200 : "Adelante pez", + 3300 : "Detente pez", + 3400 : "Caballito de mar", + 3500 : "Conchas marinas", + 3600 : "Mundo submarino", + 10100 : "Gatos", + 10200 : "Murciélagos", + 11000 : "Copos de nieve", + 11100 : "Hoja de acebo", + 11200 : "Muñeco de nieve", + 13000 : "Trébol", + 13100 : "Trébol", + 13200 : "Arco iris", + 13300 : "Trébol", + } + +FlooringNames = { + 1000 : "Madera", + 1010 : "Moqueta", + 1020 : "Baldosas romboides", + 1030 : "Baldosas romboides", + 1040 : "Hierba", + 1050 : "Ladrillos grises", + 1060 : "Ladrillos rojos", + 1070 : "Baldosas cuadradas", + 1080 : "Piedra", + 1090 : "Tarima", + 1100 : "Tierra", + 1110 : "Parqué", + 1120 : "Baldosas", + 1130 : "Nido de abeja", + 1140 : "Agua", + 1150 : "Baldosas de playa", + 1160 : "Baldosas de playa", + 1170 : "Baldosas de playa", + 1180 : "Baldosas de playa", + 1190 : "Arena", + 10000 : "Cubito de hielo", + 10010 : "Iglú", + 11000 : "Trébol", + 11010 : "Trébol", + } + +MouldingNames = { + 1000 : "Nudos", + 1010 : "Pintada", + 1020 : "Dientes", + 1030 : "Flores", + 1040 : "Flores", + 1050 : "Mariquita", + } + +WainscotingNames = { + 1000 : "Pintado", + 1010 : "Paneles de madera", + 1020 : "Madera", + } + +# CatalogWindowItem.py +WindowViewNames = { + 10 : "Jardín grande", + 20 : "Jardín agreste", + 30 : "Jardín griego", + 40 : "Paisaje urbano", + 50 : "Oeste americano", + 60 : "Mundo submarino", + 70 : "Isla tropical", + 80 : "Noche estrellada", + 90 : "Piscina con toboganes", + 100 : "Paisaje polar", + 110 : "Tierras de labranza", + } + +NewCatalogNotify = "Han llegado artículos nuevos que puedes pedir llamando desde tu teléfono." +NewDeliveryNotify = "Te acaba de llegar un pedido al buzón." +CatalogNotifyFirstCatalog = "¡Te ha llegado el primer catálogo Tolón-tolón! Lo puedes usar para hacer pedidos de artículos nuevos para ti o para la casa." +CatalogNotifyNewCatalog = "¡Te ha llegado el catálogo Tolón-tolón nº #%s! Haz pedidos llamando desde tu teléfono." +CatalogNotifyNewCatalogNewDelivery = "Te acaba de llegar un pedido al buzón. También ha llegado el catálogo Tolón-tolón nº #%s." +CatalogNotifyNewDelivery = "Te acaba de llegar un pedido al buzón." +CatalogNotifyNewCatalogOldDelivery = "Te ha llegado el catálogo Tolón-tolón nº #%s, pero todavía tienes pedidos en el buzón esperando a que los recojas." +CatalogNotifyOldDelivery = "Todavía tienes pedidos en el buzón esperando a que los recojas." +CatalogNotifyInstructions = "Haz clic en el botón \"Ir a casa\" de la página del libro de pegatinas donde aparece el plano y vete al teléfono de tu casa." +CatalogNewDeliveryButton = "¡Nueva\nentrega!" +CatalogNewCatalogButton = "¡Nuevo\ncatálogo Tolón-tolón!" + +DistributedMailboxEmpty = "El buzón está vacío. Vuelve después de haber hecho pedidos por teléfono para recogerlos." +DistributedMailboxWaiting = "El buzón está vacío, pero el pedido que hiciste ya está de camino. Vuelve más tarde." +DistributedMailboxReady = "¡Te ha llegado el pedido!" +DistributedMailboxNotOwner = "Me temo que éste no es tu buzón." +DistributedPhoneEmpty = "Puedes pedir artículos especiales para ti o para la casa desde cualquier teléfono. Cada cierto tiempo irán apareciendo artículos nuevos para pedir.\n\nAhora mismo no puedes pedir ningún artículo. Vuelve a intentarlo más tarde." + +Clarabelle = "Clarabella" +MailboxExitButton = "Cerrar buzón" +MailboxAcceptButton = "Recoger este artículo" +MailboxOneItem = "Tienes 1 artículo en el buzón." +MailboxNumberOfItems = "Tienes %s artículos en el buzón." +MailboxGettingItem = "Recogiendo %s del buzón." +MailboxItemNext = "Artículo\nsiguiente" +MailboxItemPrev = "Artículo\nanterior" +CatalogCurrency = "gominolas" +CatalogHangUp = "Colgar" +CatalogNew = "NUEVO" +CatalogBackorder = "PEDIDO PENDIENTE" +CatalogPagePrefix = "Página" +CatalogGreeting = "¡Buenas! Gracias por llamar al catálogo Tolón-tolón de Clarabella. ¿Qué deseas?" +CatalogGoodbyeList = ["¡Adiós!", + "Esperamos tu próxima llamada.", + "Gracias por llamarnos.", + "Muy bien. Hasta luego.", + "¡Adiós!", + ] +CatalogHelpText1 = "Pasa la página para ver los artículos que están a la venta." +CatalogSeriesLabel = "Serie %s" +CatalogPurchaseItemAvailable = "¡Que disfrutes de tu compra! Puedes empezar a usarla ahora mismo." +CatalogPurchaseItemOnOrder = "Muy bien. En breve enviaremos tu compra al buzón." +CatalogAnythingElse = "¿Quieres alguna otra cosa?" +CatalogPurchaseClosetFull = "Tienes el armario lleno. Puedes comprar esta prenda, pero tendrás que deshacerte de otra del armario para poder meterla cuando la recibas.\n\n¿Seguro que quieres comprarla?" +CatalogAcceptClosetFull = "Tienes el armario lleno. Antes de recoger este artículo del buzón tendrás que abrirlo y deshacerte de una de las prendas para que quepa la nueva." +CatalogAcceptShirt = "Llevas una camisa nueva. La que llevabas antes está ahora en tu armario." +CatalogAcceptShorts = "Llevas un pantalón corto nuevo. Lo que llevabas antes está ahora en tu armario." +CatalogAcceptSkirt = "Llevas una falda nueva. Lo que llevabas antes está ahora en tu armario." +CatalogAcceptPole = "Ahora, con tu caña nueva ya puedes pescar a lo grande." +CatalogAcceptPoleUnneeded = "La caña de pescar que tienes es mejor que ésta." +CatalogPurchaseHouseFull = "Tienes la casa llena. Puedes comprar este artículo, pero tendrás que deshacerte de otro de la casa para poder meterlo cuando lo recibas.\n\n¿Seguro que quieres comprarlo?" +CatalogAcceptHouseFull = "Tienes la casa llena. Antes de recoger este artículo del buzón tendrás que entrar en la casa y deshacerte de uno de los artículos para que quepa el nuevo." +CatalogAcceptInAttic = "El artículo recién adquirido está en el desván de tu casa. Para colocarlo en la casa, entra y dale al botón \"Cambiar mobiliario de sitio\"." +CatalogAcceptInAtticP = "Los artículos recién adquiridos están en el desván de tu casa. Para colocarlos en la casa, entra y dale al botón \"Cambiar mobiliario de sitio\"." +CatalogPurchaseMailboxFull = "¡Tienes el buzón lleno! No puedes comprar este artículo hasta que no saques algo del buzón para dejar hueco." +CatalogPurchaseOnOrderListFull = "Ya has pedido muchos artículos. No puedes pedir más hasta que no te lleguen los que están pendientes de entrega." +CatalogPurchaseGeneralError = "No has podido comprar el artículo debido a un error interno del juego: código de error %s." +CatalogAcceptGeneralError = "No se ha podido sacar el artículo del buzón debido a un error interno del juego: código de error %s." + +HDMoveFurnitureButton = "Cambiar\nmobiliario\nde sitio" +HDStopMoveFurnitureButton = "Mobiliario\ncambiado" +HDAtticPickerLabel = "En el desván" +HDInRoomPickerLabel = "En la habitación" +HDInTrashPickerLabel = "En la basura" +HDDeletePickerLabel = "¿Eliminar?" +HDInAtticLabel = "Desván" +HDInRoomLabel = "Habitación" +HDInTrashLabel = "Basura" +HDToAtticLabel = "Llevar\nal desván" +HDMoveLabel = "Mover" +HDRotateCWLabel = "Girar a la derecha" +HDRotateCCWLabel = "Girar a la izquierda" +HDReturnVerify = "¿Quieres llevar este artículo de nuevo al desván?" +HDReturnFromTrashVerify = "¿Quieres rescatar este artículo de la basura y llevarlo de nuevo al desván?" +HDDeleteItem = "Haz clic en Aceptar para tirar este artículo a la basura, o en Cancelar para no tirarlo." +HDNonDeletableItem = "No puedes deshacerte de este tipo de artículo." +HDNonDeletableBank = "No puedes deshacerte de la hucha." +HDNonDeletableCloset = "No puedes deshacerte del armario." +HDNonDeletablePhone = "No puedes deshacerte del teléfono." +HDNonDeletableNotOwner = "No puedes deshacerte de las cosas de %s." +HDHouseFull = "Tienes la casa llena. Antes de rescatar este artículo de la basura tendrás que deshacerte de otra cosa de la casa o el desván." + +HDHelpDict = { + "DoneMoving" : "Acaba de decorar la habitación.", + "Attic" : "Muestra una lista de los objetos del desván. En el desván se guardan los objetos que no están en tu habitación.", + "Room" : "Muestra una lista de los objetos de tu habitación. Resulta práctico para encontrar objetos perdidos.", + "Trash" : "Muestra los objetos de la basura. Los objetos más antiguos desaparecen al cabo del tiempo o cuando la basura empieza a rebosar.", + "ZoomIn" : "Para ver la habitación desde más cerca.", + "ZoomOut" : "Para ver la habitación desde más lejos.", + "SendToAttic" : "Para guardar un mueble en el desván.", + "RotateLeft" : "Para girar a la izquierda.", + "RotateRight" : "Para girar a la derecha.", + "DeleteEnter" : "Para cambiar al modo de eliminar.", + "DeleteExit" : "Para salir del modo de eliminar.", + "FurnitureItemPanelDelete" : "Para tirar %s a la basura.", + "FurnitureItemPanelAttic" : "Para colocar %s en la habitación.", + "FurnitureItemPanelRoom" : "Para devolver %s al desván.", + "FurnitureItemPanelTrash" : "Para devolver %s al desván.", + } + + + +MessagePickerTitle = "Tienes demasiadas frases. Si quieres comprar \n\"%s\"\n debes elegir una para borrarla:" +MessagePickerCancel = "Cancelar" +MessageConfirmDelete = "¿Seguro que quieres borrar \"%s\" del menú de Charla rápida?" + + +CatalogBuyText = "Comprar" +CatalogOnOrderText = "Ya pedido" +CatalogPurchasedText = "Ya comprado" +CatalogPurchasedMaxText = "Máximo permitido\nya comprado" +CatalogVerifyPurchase = "¿Quieres comprar %(item)s por %(price)s gominolas?" +CatalogOnlyOnePurchase = "Sólo puedes tener uno de estos artículos a la vez. Si compras éste, sustituirá a %(old)s.\n\n¿Seguro que quieres comprar %(item)s por %(price)s gominolas?" + +CatalogExitButtonText = "Colgar" +CatalogCurrentButtonText = "Ir a artículos actuales" +CatalogPastButtonText = "Ir a artículos antiguos" + +TutorialHQOfficerName = "Funcionario Enrique" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tato Tutorial", + 999 : "Dibu Sastre", + 1000 : "Cuartel General Dibu", + 20001 : Flippy, + + # Toontown Central Fisherman + # Toontown Central + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Pecunio el banquero", + 2003 : "Pedro el maestro", + 2004 : "Calixta la modista", + 2005 : "Leopoldo el bibliotecario", + 2006 : "Dependiente Vicente", + 2011 : "Dependienta Vicenta", + 2007 : "Funcionario del cuartel general", + 2008 : "Funcionario del cuartel general", + 2009 : "Funcionaria del cuartel general", + 2010 : "Funcionaria del cuartel general", + # NPCFisherman + 2012 : "Dependiente de la tienda de animales", + + # Silly Street + 2101 : "Bautista el dentista", + 2102 : "Lucía la policía", + 2103 : "Camaleón Atuéndez", + 2104 : "Funcionario del cuartel general", + 2105 : "Funcionario del cuartel general", + 2106 : "Funcionaria del cuartel general", + 2107 : "Funcionaria del cuartel general", + 2108 : "Fagucia Carasucia", + 2109 : "Burbujo Irujo", + 2110 : "Óscar Tel", + 2111 : "Agustín el bailarín", + 2112 : "Dr. Tomás", + 2113 : "El increíble Esnafro", + 2114 : "Chiquito Lepero", + 2115 : "Flexia Papírez", + 2116 : "Pepe Puños", + 2117 : "Facta Putre ", + 2118 : "Inocencio Santos", + 2119 : "Hilaria Jajá", + 2120 : "Profesor Nino", + 2121 : "Sra. Orni Mans", + 2122 : "Orán Guto", + 2123 : "Marivi Guasona", + 2124 : "Bromi Stilla", + 2125 : "Morgan Dul", + 2126 : "Profesor Carcajada", + 2127 : "Mauro Euro", + 2128 : "Luis el Chiflado", + 2129 : "Carmelo Cotón", + 2130 : "Calambrita Ampérez", + 2131 : "Cosquilla Plumón", + 2132 : "Chucho Chúchez", + 2133 : "Dr. Rico", + 2134 : "Mónica Llada", + 2135 : "María Corremillas", + 2136 : "Dexter Nillo", + 2137 : "Mari Fe Liz", + 2138 : "Pepote Cobos", + 2139 : "Dani Dea", + + # Loopy Lane + 2201 : "Pepe el cartero", + 2202 : "Jocosa Risa", + 2203 : "Funcionario del cuartel general", + 2204 : "Funcionario del cuartel general", + 2205 : "Funcionaria del cuartel general", + 2206 : "Funcionaria del cuartel general", + 2207 : "Cholo Calandracas", + 2208 : "Luisillo Pegajosillo", + 2209 : "Chancho La Monda", + 2210 : "Pirulí", + 2211 : "Carca Ajada ", + 2212 : "Roque Raro", + 2213 : "Pepi Bielas", + 2214 : "Pancho Quemancho", + 2215 : "Benito Latónez", + 2216 : "Noa Ynada", + 2217 : "Tiburcio Algas", + 2218 : "Mari Jo Cosa", + 2219 : "Chef Pánfilo", + 2220 : "Tancredo Cabezarroca", + 2221 : "Clovinia Dherente", + 2222 : "Corto Méchez", + 2223 : "Guasa Tomasa", + 2224 : "Sacha Muscado", + + # Punchline Place + 2301 : "Dr. Tronchaespinazo", + 2302 : "Profesor Cosquillas", + 2303 : "Enfermera Mondi", + 2304 : "Funcionario del cuartel general", + 2305 : "Funcionario del cuartel general", + 2306 : "Funcionaria del cuartel general", + 2307 : "Funcionaria del cuartel general", + 2308 : "Mullida Mullídez", + 2309 : "Desmoño Ruinez", + 2311 : "Paco Gotazo", + 2312 : "Dra. Sensible", + 2313 : "Lila Lamparón", + 2314 : "Disco Bolo", + 2315 : "Francisco Reoso", + 2316 : "Cindi Charachera", + 2318 : "Toni Chichón", + 2319 : "Zipi", + 2320 : "Alfredo Pastosi", + + # Donald's Dock + 1001 : "Dependiente Pipe", + 1002 : "Dependiente Pape", + 1003 : "Funcionario del cuartel general", + 1004 : "Funcionaria del cuartel general", + 1005 : "Funcionario del cuartel general", + 1006 : "Funcionaria del cuartel general", + 1007 : "Fifo Pretaporter", + 1008 : "Pet Shop Clerk", + + # Barnacle Blvd. + 1101 : "Beto Buque", + 1102 : "Capitán Doblón", + 1103 : "Raspo Espínez", + 1104 : "Dr. Rompecubiertas", + 1105 : "Almirante Garfio", + 1106 : "Sra. Almidónez", + 1107 : "Nemo Mancuerna", + 1108 : "Funcionario del cuartel general", + 1109 : "Funcionaria del cuartel general", + 1110 : "Funcionario del cuartel general", + 1111 : "Funcionaria del cuartel general", + 1112 : "Pepe Glubglub", + 1113 : "Chiqui Quillas", + 1114 : "Magdaleno Yapican", + 1115 : "Abogada Tentácula Calamar", + 1116 : "Pili Percebe", + 1117 : "Billy Yates", + 1118 : "Nelson Tintes", + 1121 : "Lisa Mástiles", + 1122 : "Titánico Iceberg", + 1123 : "Electra Anguílez", + 1124 : "Astillo Muéllez", + 1125 : "Tunanta Estribor", + + # Seaweed Street + 1201 : "Perci Percebe", + 1202 : "Pasma Rote", + 1203 : "Ajab", + 1204 : "Claus Anclas", + 1205 : "Funcionario del cuartel general", + 1206 : "Funcionaria del cuartel general", + 1207 : "Funcionario del cuartel general", + 1208 : "Funcionaria del cuartel general", + 1209 : "Profesor Curasardinas", + 1210 : "Pong Peyeng", + 1211 : "Velo Dromo", + 1212 : "Pico Tazos", + 1213 : "Rémoro Tiburcio ", + 1214 : "Catalina Timoneles", + 1215 : "María del Mar Salada", + 1216 : "Carrete Cañas", + 1217 : "Marina Naval", + 1218 : "Paco Pacífico", + 1219 : "Alvar Cabeza de Playa", + 1220 : "Isabel Segunda", + 1221 : "Blasco Báñez", + 1222 : "Alberto Abordaje", + 1223 : "Sepio Calamárez", + 1224 : "Emilia Anguila", + 1225 : "Gonzo Friegacubiertas", + 1226 : "Izo Velamen", + 1227 : "Coral Bisturí", + + # Lighthouse Lane + 1301 : "Pepa Sastre", + 1302 : "Isidoro Subemástiles", + 1303 : "Clodovico Cromañón", + 1304 : "Santiaguiña Nécorez", + 1305 : "Funcionario del cuartel general", + 1306 : "Funcionaria del cuartel general", + 1307 : "Funcionario del cuartel general", + 1308 : "Funcionaria del cuartel general", + 1309 : "Mar Océana", + 1310 : "Trucho Cañete", + 1311 : "Goyo Gorrotocho", + 1312 : "Quillo Quilla", + 1313 : "Gordi Gordios", + 1314 : "Jorge Rumbre", + 1315 : "Catedrático Áncora", + 1316 : "Canuta Canoas", + 1317 : "Juana Salvadora Gaviota", + 1318 : "Salva Vidas", + 1319 : "Quique Diqueseco", + 1320 : "Mario Almejo", + 1321 : "Dina Atraque", + 1322 : "Estibora Pópez", + 1323 : "Pericles Cabezabuque", + 1324 : "Concha Coquínez ", + 1325 : "Vaporeto Misisipi", + 1326 : "Jurela Besúguez", + 1327 : "Gabi Ota", + 1328 : "Carlitos Lenguado", + 1329 : "Flora Marínez", + 1330 : "Fredo Barbarrala", + 1331 : "Timón Bocanegra", + + # The Brrrgh + 3001 : "Adela Dita", + 3002 : "Funcionario del cuartel general", + 3003 : "Funcionaria del cuartel general", + 3004 : "Funcionario del cuartel general", + 3005 : "Funcionario del cuartel general", + 3006 : "Dependiente Poli", + 3007 : "Dependienta Pili", + 3008 : "Giorgio Armiño", + # NPCFisherman + 3009 : "Dependiente de la tienda de animales", + + # Walrus Way + 3101 : "Juanjo Escárchez", + 3102 : "Tía Ritona", + 3103 : "Pepe Tundra", + 3104 : "Geli da Pinto", + 3105 : "Pipe Pelado", + 3106 : "Frigo Sabañón", + 3107 : "Maite Aterida", + 3108 : "Doroteo Escarcha", + 3109 : "Pati", + 3110 : "Lucas Friolero", + 3111 : "Kevin Kelvin", + 3112 : "Fredo Dedo", + 3113 : "Cris Térico", + 3114 : "Beto Tomba", + 3115 : "Funcionario del cuartel general", + 3116 : "Funcionaria del cuartel general", + 3117 : "Funcionario del cuartel general", + 3118 : "Funcionario del cuartel general", + 3119 : "Carlos Congelado", + 3120 : "Mito Mitón", + 3121 : "Voltio Ampérez", + 3122 : "Bebé Bob", + 3123 : "Ricardo Bofrigo", + 3124 : "Manfredo Carámbanez", + 3125 : "Tiritono Quelog", + 3126 : "Pescanovo Martínez", + 3127 : "Popoco Mecaigo", + 3128 : "Pipo Polar", + 3129 : "Dina Frigomiga", + 3130 : "Roberta", + 3131 : "Lorenzo Rascafría", + 3132 : "Cenicilla", + 3133 : "Dr. Congelaimagen", + 3134 : "Paco Gelado", + 3135 : "Empa Pada", + 3136 : "Felicia Simpática", + 3137 : "Kevin Ator", + 3138 : "Chef Sopacarámbano ", + 3139 : "Abuelita Polosur", + + # Sleet Street + 3201 : "Tía Ártica", + 3202 : "Antonio Tiritonio", + 3203 : "Eugenio Criogenio", + 3204 : "Dra. Soplillos", + 3205 : "Perico Arenque", + 3206 : "Fernando Anchoa", + 3207 : "Dr. Bocamaraca", + 3208 : "Felipe el gruñón", + 3209 : "Garmillo Panterquircho", + 3210 : "Nutrio Cenutrio", + 3211 : "Pega Moide", + 3212 : "Federico Gelado", + 3213 : "Funcionario del cuartel general", + 3214 : "Funcionaria del cuartel general", + 3215 : "Funcionario del cuartel general", + 3216 : "Funcionario del cuartel general", + 3217 : "Pedro Glaciares", + 3218 : "Pepe Napiazul", + 3219 : "Tomás Bufandilla", + 3220 : "Estornio Atchís", + 3221 : "Inés Carcha", + 3222 : "Ventiscona Copogordo", + 3223 : "Macario Ajillo", + 3224 : "Madame Glacé", + 3225 : "Gregorio Sabañón ", + 3226 : "Papá Noés", + 3227 : "Solario Ráyez", + 3228 : "Escalo Frío", + 3229 : "Hernia Tarúguez", + 3230 : "Optimisto Peláez ", + 3231 : "Pedro Picahielo", + + # Minnie's Melody Land + 4001 : "Moli Melódica", + 4002 : "Funcionario del cuartel general", + 4003 : "Funcionaria del cuartel general", + 4004 : "Funcionaria del cuartel general", + 4005 : "Funcionaria del cuartel general", + 4006 : "Dependienta Dina", + 4007 : "Dependiente Dino", + 4008 : "Modista Armonía", + 4009 : "Dependiente de la tienda de animales", + + # Alto Ave. + 4101 : "Ropo Pompom", + 4102 : "Bibi", + 4103 : "Dr. Pavo Rotti", + 4104 : "Funcionario del cuartel general", + 4105 : "Funcionaria del cuartel general", + 4106 : "Funcionaria del cuartel general", + 4107 : "Funcionaria del cuartel general", + 4108 : "Sordino Quena", + 4109 : "Carlos", + 4110 : "Metrónoma Diapasón", + 4111 : "Camilo Séptimo", + 4112 : "Fa", + 4113 : "Madame Modales", + 4114 : "Dum Dúmez", + 4115 : "Bárbara Sevilla", + 4116 : "Pizzicato", + 4117 : "Lina Mando", + 4118 : "Rapsodio Sonata", + 4119 : "Beto Ven", + 4120 : "Tara Reo", + 4121 : "Bemolio Sostenido", + 4122 : "Noa Ybemoles", + 4123 : "Cornetín Gaita", + 4124 : "Rifi Rafe", + 4125 : "Clinia Eastwood", + 4126 : "Tortillo Tenorio", + 4127 : "Vivaracho Vivaldi", + 4128 : "Plácido Lunes", + 4129 : "Fina Desafinada", + 4130 : "Ironio Máidez", + 4131 : "Abraham Zambomba", + 4132 : "Teresa Benicanta", + 4133 : "Impo Luto", + 4134 : "DJ Diborio", + 4135 : "Teresito Sopranillo", + 4136 : "Fusa Patidifusa", + 4137 : "Roy Bate", + 4138 : "Octavo Seisillo", + 4139 : "Ada Gio", + 4140 : "Trémolo Torpe", + + # Baritone Blvd. + 4201 : "Tina Sonatina", + 4202 : "Barbo", + 4203 : "Chopo Chopín", + 4204 : "Funcionario del cuartel general", + 4205 : "Funcionaria del cuartel general", + 4206 : "Funcionaria del cuartel general", + 4207 : "Funcionaria del cuartel general", + 4208 : "Lírica Tástrofe", + 4209 : "Sissy de Triana", + 4211 : "José Carrerilla", + 4212 : "Filarmonio Opereto", + 4213 : "Ambulancio Raudo", + 4214 : "Arnesia Mifasol", + 4215 : "Corneto Claxon", + 4216 : "Amadeo Parche", + 4217 : "Juan Strauss", + 4218 : "Octava Pianola", + 4219 : "Trombón Charango", + 4220 : "Sartenio Batuta", + 4221 : "Pipo Madrigal", + 4222 : "Fulano de Tal", + 4223 : "Felisa Obelisco", + 4224 : "Jonás Clarinete", + 4225 : "Cuchi Cheo", + 4226 : "Paca Canora", + 4227 : "Betina Trompetilla", + 4228 : "Nabuco Donosor", + 4229 : "Melodia Pasón", + 4230 : "Rigo Letto", + 4231 : "Acordia Deona", + 4232 : "Fígaro Casamentero", + 4233 : "Arpo Marx", + 4234 : "Coro Vocález", + + # Tenor Terrace + 4301 : "Uki", + 4302 : "Juanola", + 4303 : "Leo", + 4304 : "Funcionario del cuartel general", + 4305 : "Funcionaria del cuartel general", + 4306 : "Funcionaria del cuartel general", + 4307 : "Funcionaria del cuartel general", + 4308 : "Felisa Felina", + 4309 : "Isidoro Tápiez", + 4310 : "Marta Falla", + 4311 : "Corneta Camarera", + 4312 : "Horacio Pianola", + 4313 : "Renato Enquequedamos", + 4314 : "Vilma Mascota", + 4315 : "Rola Roles", + 4316 : "Pachi Zapatillo", + 4317 : "Piero Pereta", + 4318 : "Bob Marlene", + 4319 : "Urraca Grájez", + 4320 : "Irene Fervescente", + 4321 : "Pepe Palmira", + 4322 : "Aitor Poloneso", + 4323 : "Ana Balada", + 4324 : "Elisa", + 4325 : "Banquero Ramón", + 4326 : "Morlana Chicharra", + 4327 : "Flautilla Hamelín", + 4328 : "Wagner", + 4329 : "Maruja Televenta", + 4330 : "Maestro Soniquete", + 4331 : "Celo Costelo", + 4332 : "Tato Timbal", + 4333 : "Chiflo Chiflete", + 4334 : "Maraco Marchoso", + + # Daisy Gardens + 5001 : "Funcionario del cuartel general", + 5002 : "Funcionario del cuartel general", + 5003 : "Funcionaria del cuartel general", + 5004 : "Funcionaria del cuartel general", + 5005 : "Dependienta Azucena", + 5006 : "Dependiente Jacinto", + 5007 : "Florinda Rosales", + 5008 : "Dependiente de la tienda de animales", + + # Elm Street + 5101 : "Pepe Pino", + 5102 : "Alca Chofa", + 5103 : "Federico Tilla", + 5104 : "Polillo Maripósez", + 5105 : "Comino Pérez Gil", + 5106 : "Patillo Siegacogotes", + 5107 : "Cartero Felipe", + 5108 : "Posadera Piti", + 5109 : "Funcionario del cuartel general", + 5110 : "Funcionario del cuartel general", + 5111 : "Funcionaria del cuartel general", + 5112 : "Funcionaria del cuartel general", + 5113 : "Dr. Zanahorio", + 5114 : "Marchito Mate", + 5115 : "Mimosa Nomeolvides", + 5116 : "Lucho Borrajo", + 5117 : "Pétalo", + 5118 : "Palomo Maíz", + 5119 : "Seto Podal", + 5120 : "Cardamomo", + 5121 : "Grosella Falsa", + 5122 : "Trufo Champiñón", + 5123 : "Tempranilla Móstez", + 5124 : "Hinojo Hinault", + 5125 : "Burbujo Chof", + 5126 : "Madre Selva", + 5127 : "Pili Polen", + 5128 : "Barba Coa", + + # Maple Street + 5201 : "Pulgarcillo", + 5202 : "Edel Weiss", + 5203 : "Campanilla", + 5204 : "Pipe Barroso", + 5205 : "León Mondadientes", + 5206 : "Cardo Borriquero", + 5207 : "Flor Chorro", + 5208 : "Lisa Buesa", + 5209 : "Funcionario del cuartel general", + 5210 : "Funcionario del cuartel general", + 5211 : "Funcionaria del cuartel general", + 5212 : "Funcionaria del cuartel general", + 5213 : "Al Cornoque", + 5214 : "Hortensia Comopincha", + 5215 : "Josefa Repera", + 5216 : "Luisillo Membrillo", + 5217 : "Aníbal Nomeolvides", + 5218 : "Rufo Segadora", + 5219 : "Sacha Cachas", + 5220 : "Lenti Juela", + 5221 : "Flamenca Rosa", + 5222 : "Belladona Jazmín", + 5223 : "Frido Quememojo", + 5224 : "Guido Castañapilonga", + 5225 : "Pipa Girasólez", + 5226 : "Rumio Cuentauvas", + 5227 : "Petunia Barricarroble", + 5228 : "Prior Primo Prelado", + + # Oak street + 5301 : "Funcionario del cuartel general", + 5302 : "Funcionario del cuartel general", + 5303 : "Funcionario del cuartel general", + 5304 : "Funcionario del cuartel general", + 5305 : "Doña Hortensia", + 5306 : "Cartero don Lentejo", + 5307 : "Cris Antemo", + 5308 : "Juanita Pimentón", + 5309 : "Marga y Rita", + 5310 : "Narciso", + 5311 : "Doña Zanahoria", + 5312 : "Eugenio", + 5313 : "Don Silvestre", + 5314 : "Tía Petunia", + 5315 : "Tío Calabacín", + 5316 : "Tío Jacinto", + 5317 : "Doña Citronia", + 5318 : "Don Lirio", + 5319 : "Malva Rosa", + 5320 : "Doña Azucena", + 5321 : "Profesor Hiedra", + + # Dreamland + 9001 : "Belinda Traspuesta", + 9002 : "Bello Durmiente", + 9003 : "Plancho Rejas", + 9004 : "Funcionaria del cuartel general", + 9005 : "Funcionaria del cuartel general", + 9006 : "Funcionario del cuartel general", + 9007 : "Funcionario del cuartel general", + 9008 : "Dependienta Modorra", + 9009 : "Dependiente Modorro", + 9010 : "Almohado Edredón", + 9011 : "Dependiente de la tienda de animales", + + # Lullaby Lane + 9101 : "Vito", + 9102 : "Plúmbea Triz", + 9103 : "Cafeíno Paloseco ", + 9104 : "Copito de Nieve", + 9105 : "Profesor Bostezo", + 9106 : "Soporífero Indolente", + 9107 : "Acurruco Paloma", + 9108 : "Soño Liento", + 9109 : "Dafne Marmota", + 9110 : "Adela Duermevela", + 9111 : "Apagón Plómez", + 9112 : "Marqués de la Nana", + 9113 : "Jaime Cuco", + 9114 : "Máscara Pepínez", + 9115 : "Hiberno Cuandoquiero", + 9116 : "Mariano Cuentaovejas", + 9117 : "Madrugona Grogui", + 9118 : "Estrella Vespertina", + 9119 : "Tronco Sópez", + 9120 : "Lirona Frita", + 9121 : "Serena Vigilia", + 9122 : "Trasnocho Pocho", + 9123 : "Osito Pelúchez", + 9124 : "Vela Zascandil", + 9125 : "Dr. Vigíliez", + 9126 : "Pluma Oca", + 9127 : "Pili Piltra", + 9128 : "Juanjo Manitas", + 9129 : "Beltrán Puesto", + 9130 : "Siesto Buenasnóchez", + 9131 : "Letárgica Catorcehoras", + + 9132 : "Funcionaria del cuartel general", + 9133 : "Funcionaria del cuartel general", + 9134 : "Funcionaria del cuartel general", + 9135 : "Funcionaria del cuartel general", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Ayuntamiento", "el"), + 2514 : ("Banco de Toontown", "el"), + 2516 : ("Colegio de Toontown", "el"), + 2518 : ("Biblioteca de Toontown", "la"), + 2519 : ("Tienda de Bromas", "la"), + 2520 : ("Cuartel General Dibu", "el"), + 2521 : ("Tienda de Ropa de Toontown", "la"), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Clínica Dental Piñatasana", "la"), + 2602 : ("", ""), + 2603 : ("Mineros Carboneros", "los"), + 2604 : ("Limpiezas por Piezas", "las"), + 2605 : ("Fábrica de Carteles de Toontown", "la"), + 2606 : ("", ""), + 2607 : ("Habas Saltarinas", "las"), + 2610 : ("Dr. Tomás Todonte", "el"), + 2611 : ("", ""), + 2616 : ("Tienda de Disfraces Barbarrara", "la"), + 2617 : ("Acrobacias a Granel", "las"), + 2618 : ("Chistes Lepetitivos", "los"), + 2621 : ("Aviones de Papel", "los"), + 2624 : ("Perros Gamberros", "los"), + 2625 : ("Pastelería Moho Feliz", "la"), + 2626 : ("Reparación de Bromas", "las"), + 2629 : ("El Rincón de la Risa", "el"), + 2632 : ("Academia de Payasos", "la"), + 2633 : ("Salón de Té La Tetera", "el"), + 2638 : ("Willie, el Barco de Vapor", ""), + 2639 : ("Travesuras Simiescas", "las"), + 2643 : ("Botellas Enlatadas", "las"), + 2644 : ("Bromas Ligeras", "las"), + 2649 : ("Tienda de Juegos", "la"), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Clases de Reír", "las"), + 2655 : ("Caja de Ahorros Pastagansa", "la"), + 2656 : ("Coches Usados de Payasos", "los"), + 2657 : ("Bromas a Tutiplén", "las"), + 2659 : ("Calambres Reunidos Ampérez", "los"), + 2660 : ("Cosquilladores Automáticos", "los"), + 2661 : ("Chuches Chucho", ""), + 2662 : ("Dr. Eufo Rico", "el"), + 2663 : ("Don Ratón Se Va De Vacaciones", ""), + 2664 : ("Mimos Mimosos", "los"), + 2665 : ("Agencia de Viajes Corremillas", "la"), + 2666 : ("Gasolinera Desternillante", "la"), + 2667 : ("Tiempos Felices", "los"), + 2669 : ("Globos Cobos", "los"), + 2670 : ("Tenedores de Sopa", "los"), + 2671 : ("Cuartel General Dibu", "el"), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Un Marinerito Valiente", ""), + 2705 : ("Matracas Calandracas", "las"), + 2708 : ("Cola Azul", "la"), + 2711 : ("Oficina de Correos de Toontown", "la"), + 2712 : ("Café La Monda", "el"), + 2713 : ("Café Chachi", "el"), + 2714 : ("Un Tranvía En Apuros", ""), + 2716 : ("Calditos Carcajada", "los"), + 2717 : ("Latas Embotelladas", "las"), + 2720 : ("Taller Despilporre", "el"), + 2725 : ("", ""), + 2727 : ("Botellas y Latas Latónez", "las"), + 2728 : ("Nata Invisible", "la"), + 2729 : ("Peces Dorados de 14 Kilates", "los"), + 2730 : ("Noticias Jocosas", "las"), + 2731 : ("", ""), + 2732 : ("Pasta Pánfilo", "la"), + 2733 : ("Cometas de Plomo", "los"), + 2734 : ("Platillos y Ventosas", "los"), + 2735 : ("Detonaciones a Domicilio", "las"), + 2739 : ("Reparación de Chistes", "las"), + 2740 : ("Petardos Usados", "los"), + 2741 : ("", ""), + 2742 : ("Cuartel General Dibu", "el"), + 2743 : ("Limpieza En Seco En Un Minueto", "la"), + 2744 : ("", ""), + 2747 : ("Tinta Visible", "la"), + 2748 : ("Risa Deprisa", "la"), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Cojines Mullídez", "los"), + 2802 : ("Bolas de Demolición Inflables", "las"), + 2803 : ("El Chico Del Carnaval", ""), + 2804 : ("Clínica de Fisioterapia Tronchalomo", "la"), + 2805 : ("", ""), + 2809 : ("Gimnasio Capirotazo", "el"), + 2814 : ("Un Dia De Atasco", ""), + 2818 : ("Tartas Volantes", "las"), + 2821 : ("", ""), + 2822 : ("Sándwiches de Pollo de Goma", "los"), + 2823 : ("Heladería El Cucurucho Agudo", "la"), + 2824 : ("Las Locuras De Mickey", ""), + 2829 : ("Salchichones y Chichones", "los"), + 2830 : ("Melodías de Zipi", "las"), + 2831 : ("Casa de las Risillas del Profesor Cosquillas", "la"), + 2832 : ("Cuartel General Dibu", "el"), + 2833 : ("", ""), + 2834 : ("Sala de Traumatología del Hueso de la Risa", "la"), + 2836 : ("", ""), + 2837 : ("Seminarios Sobre Risa Persistente", "los"), + 2839 : ("Pasta Pastosa", "la"), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Tienda de Bromas", "la"), + 1507 : ("Cuartel General Dibu", "el"), + 1508 : ("Tienda de Ropa", "la"), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Flotadores Usados", "los"), + 1604 : ("Limpieza en Seco de Ropa Chorreantes", "la"), + 1606 : ("Relojería Garfio", "la"), + 1608 : ("Quillas y Cosquillas", "las"), + 1609 : ("Echa el Cebo Magdaleno", ""), + 1612 : ("Banco Galeón Hundido", "el"), + 1613 : ("Bufete Calamar, Pulpo & Sepia", "el"), + 1614 : ("Boutique Uña Deslumbrante", "la"), + 1615 : ("Yates Todo a cien", "el"), + 1616 : ("Salón de Belleza Barbanegra", "el"), + 1617 : ("Óptica El Vigía Miope", "la"), + 1619 : ("Clínica Quirúrgica de Arboles", "la"), + 1620 : ("De Popa a Proa", ""), + 1621 : ("Gimnasio Estibadores Fornidos", "el"), + 1622 : ("Accesorios Eléctricos Rayas y Centollos", "los"), + 1624 : ("Reparación de Suelas de Buque", "la"), + 1626 : ("Ropa de etiqueta El Salmón Coqueto", "la"), + 1627 : ("Surtido de Bitácoras de Beto Buque", "el"), + 1628 : ("Atún de la Tuna", "el"), + 1629 : ("Cuartel General Dibu", "el"), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Escuela de Enfermería La Boya Pocha", "la"), + 1703 : ("Restaurante Chino Cocochas de Dragón", "el"), + 1705 : ("Velas y Bielas", "las"), + 1706 : ("Medusas para Bañeras", "las"), + 1707 : ("Regalos Tiburcio", "los"), + 1709 : ("Cata de Maranes", "la"), + 1710 : ("Surtido de Percebes", "el"), + 1711 : ("Restaurante Sal Gorda", "el"), + 1712 : ("Gimnasio Levad Anclas", "el"), + 1713 : ("Mapas Pasma", "las"), + 1714 : ("Posada Suelta el Carrete", "la"), + 1716 : ("Bañadores La Sirena con Piernas", "las"), + 1717 : ("Telas y Botones Océano Pacífico", "las"), + 1718 : ("Servicio de Taxi Callo Encallado", "el"), + 1719 : ("Compañía de Aguas Lomo de Pato", "la"), + 1720 : ("Cañas y Barro", "las"), + 1721 : ("A Toda Máquina", ""), + 1723 : ("Algas Sepio", "las"), + 1724 : ("Anguilas Gustativas", "las"), + 1725 : ("Cangrejos y Aparejos Ajab", "los"), + 1726 : ("Con Cien Gaseosas por Banda", ""), + 1727 : ("Los Remos del Volga", ""), + 1728 : ("Centollos El Meollo", "los"), + 1729 : ("Cuartel General Dibu", "el"), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Náutico y Aséptico", "el"), + 1804 : ("Gimnasio El Crustáceo Cachas", "el"), + 1805 : ("Comidas El Carrete Audaz", "las"), + 1806 : ("Sombrerería Gorrotocho", "la"), + 1807 : ("Quillas al Pil Pil", "las"), + 1808 : ("Nudos Imposibles", "los"), + 1809 : ("Cubos Oxidados", "los"), + 1810 : ("Máster en Gestión de Anclas", "el"), + 1811 : ("Canoas y Anchoas", "las"), + 1813 : ("Asesoría Vuela más Alto", "la"), + 1814 : ("Apeadero Amarras Salvajes", "el"), + 1815 : ("Consulta del Dr. Diqueseco", "la"), + 1818 : ("Cafetería Los Siete Mares", "la"), + 1819 : ("El Estibador Gourmet", ""), + 1820 : ("Artículos de Broma El Simpático Maremoto", "los"), + 1821 : ("Fábrica de Conservas Dibuque", "la"), + 1823 : ("Restaurante El Molusco Feroz", "el"), + 1824 : ("Paletas y Maletas", "las"), + 1825 : ("Caballas Pura Sangre Pescadería", "las"), + 1826 : ("Sastrería Cromañón", "la"), + 1828 : ("Lastre Pepa Sastre", "el"), + 1829 : ("Estatuas de Gaviotas", "las"), + 1830 : ("Objetos Náuticos Perdidos", "los"), + 1831 : ("Algas de Compañía", "las"), + 1832 : ("Surtido de Mástiles", "el"), + 1833 : ("Trajes a Medida El Corsario Elegante", "los"), + 1834 : ("Timones de diseño", "los"), + 1835 : ("Cuartel General Dibu", "el"), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Tienda de Bromas", "la"), + 4504 : ("Cuartel General Dibu", "el"), + 4506 : ("Tienda de Ropa de Toontown", "la"), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tambores Ropopompom", "los"), + 4604 : ("Compás de Dos por Cuatro", "el"), + 4605 : ("Violines Bibi", "los"), + 4606 : ("Casa de las Castañuelas", "la"), + 4607 : ("Dibumoda Septiminod", "la"), + 4609 : ("Teclas de Piano Dorremí", "las"), + 4610 : ("No Pierdan los Estribillos", ""), + 4611 : ("A Bombo y Platillos Volantes", ""), + 4612 : ("Clínica Dental Rotti", "la"), + 4614 : ("Peluquería de Corcheas", "la"), + 4615 : ("Pizzería Pizzicato", "la"), + 4617 : ("Mandolinas Mandonas", "las"), + 4618 : ("Cuartos para Cuartetos", "los"), + 4619 : ("Pentagramas a la Carta", "los"), + 4622 : ("Almohadas Acompasadas", "las"), + 4623 : ("Bemoles a Cien", "los"), + 4625 : ("Tuba de Pasta de Dientes", "la"), + 4626 : ("Solfeo a Granel", ""), + 4628 : ("Seguros a Tercetos", "los"), + 4629 : ("Platillos de Papel", "los"), + 4630 : ("Tocata y Fuga de Alcatraz", "la"), + 4631 : ("Tocatas de Tortilla", "las"), + 4632 : ("Tienda Abierta los 24 Tiempos", "la"), + 4635 : ("Tenores de Alquiler", "los"), + 4637 : ("Afinado de Platillos", "el"), + 4638 : ("Cuartetos de Heavy Metal", "los"), + 4639 : ("Antigüedades La Flauta de Noé", "las"), + 4641 : ("Noticiero La Voz de la Soprano", "el"), + 4642 : ("Limpieza en Seco en un Minueto", "la"), + 4645 : ("Dibudisco", "el"), + 4646 : ("", ""), + 4648 : ("Mudanzas Berganza", "las"), + 4649 : ("", ""), + 4652 : ("Regalos Clave de Luna", "los"), + 4653 : ("", ""), + 4654 : ("Puertas Tannhäuser", "las"), + 4655 : ("Escuela de Cocina Sonata a la Sal", "la"), + 4656 : ("", ""), + 4657 : ("Barbería El Cuarteto Afeitador", "la"), + 4658 : ("Pianos en Caída Libre", "los"), + 4659 : ("Cuartel General Dibu", "el"), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("Escuela de Baile El Pasodoble Zapateado", "la"), + 4702 : ("¡Más Madera! Leñadores Melódicos", ""), + 4703 : ("Tenor al por Menor", "el"), + 4704 : ("Sonatas y Sonatinas", "las"), + 4705 : ("Cítaras al Peso", "las"), + 4707 : ("Estudio de Efectos Doppler", "el"), + 4709 : ("Escala Escalada Aparejos de Escalada", "la"), + 4710 : ("Escuela de Conducción Marcha y Contrafuga", "la"), + 4712 : ("Reparación de Pinchazos de Oboes", "la"), + 4713 : ("Vaqueros Strauss", "los"), + 4716 : ("Armónicas Polifónicas", "las"), + 4717 : ("Seguros de Coches Un Acordeón en la Guantera", "los"), + 4718 : ("Utensilios de Cocina Cascanueces", "los"), + 4719 : ("Caravanas Madrigal", "las"), + 4720 : ("Tararea esa Dibucanción", "la"), + 4722 : ("Oberturas para Oboe", "las"), + 4723 : ("Juguetería El Xilófono de Plastilina", "la"), + 4724 : ("Cacofonías a Domicilio", "las"), + 4725 : ("El Barbero Barítono", ""), + 4727 : ("Planchado de Cuerdas Vocales", "el"), + 4728 : ("Ablandamiento de Oídos Duros", "el"), + 4729 : ("Librería El Violoncelo Celoso", "la"), + 4730 : ("Letras Patéticas", "las"), + 4731 : ("Melodibus", "los"), + 4732 : ("Compañía de Teatro El Flautín de Venecia", "la"), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Acordeones para Amenizar Reuniones", "las"), + 4736 : ("Bodas Fígaro", "las"), + 4737 : ("Arpas de Esparto", "las"), + 4738 : ("Regalos La Balalaica Silvestre", "los"), + 4739 : ("Cuartel General Dibu", "el"), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Organillos en Estéreo", "los"), + 4803 : ("Floristería El Sombrero de Tres Ficus", "la"), + 4804 : ("Escuela de Hostelería El Platillo Danzarín", "la"), + 4807 : ("Trémolos Embotellados", "los"), + 4809 : ("Allegros Tristes", "los"), + 4812 : ("", ""), + 4817 : ("Pajarería Pedro y el Lobo", "la"), + 4819 : ("Ukeleles de Uki", "los"), + 4820 : ("", ""), + 4821 : ("Gramolas Juanola", "las"), + 4827 : ("Relojería La Danza de las Horas", "la"), + 4828 : ("Zapatería Masculina Claqué para Ciempies", "la"), + 4829 : ("Cañones Pachelbel", "los"), + 4835 : ("Cascabeles para Gatos", "los"), + 4836 : ("Regalos Reggae", "los"), + 4838 : ("Academia de Canto Grájez", "la"), + 4840 : ("Bebidas Musicales Cocapiano Cola", "las"), + 4841 : ("Liras Palmira", "las"), + 4842 : ("Síncopas Hechas a Mano", "las"), + 4843 : ("", ""), + 4844 : ("Motocicletas Harley Mendelson", "las"), + 4845 : ("Elegías Elegantes de Elisa", "las"), + 4848 : ("Caja de ahorros Guita Ramón", "la"), + 4849 : ("", ""), + 4850 : ("Empeños La Cuerda Prestada", "los"), + 4852 : ("Fundas para Flautas", "las"), + 4853 : ("Guitarras a Vapor Leo", "las"), + 4854 : ("Vídeos de Valquirias y Violines", "los"), + 4855 : ("Címbalos a Domicilio", "los"), + 4856 : ("", ""), + 4862 : ("Pasodobles, Pasotriples y Pasocuádruples", "los"), + 4867 : ("Liquidación de Violoncelos", "la"), + 4868 : ("", ""), + 4870 : ("Timbales y Tambores de Titanio", "los"), + 4871 : ("Clarines, Clarinetes y Chifletes", "los"), + 4872 : ("Marimbas, Matracas y Maracas", "las"), + 4873 : ("Cuartel General Dibu", "el"), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Tienda de Bromas", "la"), + 5502 : ("Cuartel General Dibu", "el"), + 5503 : ("Tienda de Ropa", "la"), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Óptica Zanahoria a Tutiplén", "la"), + 5602 : ("Corbatas Pino", "las"), + 5603 : ("Lechuga a Granel", "la"), + 5604 : ("Listas de Bodas Nomeolvides", "las"), + 5605 : ("Compañía de Aguas de Borrajas", "la"), + 5606 : ("Pétalos", "los"), + 5607 : ("Correos Florestales", "los"), + 5608 : ("Palomitas y Palomitos de Maíz", "las"), + 5609 : ("Enredaderas de Compañía", "las"), + 5610 : ("Betunes El Tulipán Negro", "los"), + 5611 : ("Bromas Cardamomo", "las"), + 5613 : ("Peluqueros Siegacogotes", "los"), + 5615 : ("Semillas para Cotillas", "las"), + 5616 : ("Posada Coli Flor de Pitiminí", "la"), + 5617 : ("Mariposas de Encargo", "las"), + 5618 : ("Guisantes Farsantes", "los"), + 5619 : ("Comino Importante", "el"), + 5620 : ("Hierbabuenas Tardes", "las"), + 5621 : ("Viñas Lejanas", "las"), + 5622 : ("Bicicletas Hinojo Hinault", "las"), + 5623 : ("Jacuzzis para Gorriones", "los"), + 5624 : ("Madreselva Tropical", "la"), + 5625 : ("Pañales para Panales", "los"), + 5626 : ("Zarzaparrillas de Carbón", "las"), + 5627 : ("Cuartel General Dibu", "el"), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Espinacas de Diseño", "las"), + 5702 : ("Rastrillos Miga de Pan", "los"), + 5703 : ("Fotografía La Flor de un Día", "la"), + 5704 : ("Coches Usados Campanilla", "los"), + 5705 : ("Colchones Suavecáctus", "los"), + 5706 : ("Joyería La Pulsera de Chopo", "la"), + 5707 : ("Fruta Musical", "la"), + 5708 : ("Agencia de Viajes Villadiego", "la"), + 5709 : ("Cortacésped Amor de Hortelano", "el"), + 5710 : ("Gimnasio Espantalobos", "el"), + 5711 : ("Calcetería Lentejuela Guisada", "la"), + 5712 : ("Estatuas Bobas", "las"), + 5713 : ("Jabones de Higo Chumbo", "los"), + 5714 : ("Agua de Lluvia Embotellada", "el"), + 5715 : ("Noticiario Telecastaña", "el"), + 5716 : ("Caja de Ahorros y Monte de Orégano", "la"), + 5717 : ("La Flor Chorreante", ""), + 5718 : ("Animales Exóticos Diente de León", "los"), + 5719 : ("Agencia de Detectives Azotalenguas", "la"), + 5720 : ("Ropa Masculina Borriquero", "la"), + 5721 : ("Comidas Alfalfa Romeo", "las"), + 5725 : ("Destilería Malta Cibelina", "la"), + 5726 : ("Barro a Granel", "el"), + 5727 : ("Préstamos y Empréstitos Praderas Primitivas", "los"), + 5728 : ("Cuartel General Dibu", "el"), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : ("Cuartel General Dibu", "el"), + 5804 : ("Just Vase It", ""), + 5805 : ("Snail Mail", ""), + 5809 : ("Fungi Clown School", ""), + 5810 : ("Honeydew This", ""), + 5811 : ("Lettuce Inn", ""), + 5815 : ("Grass Roots", ""), + 5817 : ("Apples and Oranges", ""), + 5819 : ("Green Bean Jeans", ""), + 5821 : ("Squash and Stretch Gym", ""), + 5826 : ("Ant Farming Supplies", ""), + 5827 : ("Dirt. Cheap.", ""), + 5828 : ("Couch Potato Furniture", ""), + 5830 : ("Spill the Beans", ""), + 5833 : ("The Salad Bar", ""), + 5835 : ("Flower Bed and Breakfast", ""), + 5836 : ("April’s Showers and Tubs", ""), + 5837 : ("School of Vine Arts", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Biblioteca Sobetotal", "la"), + 9503 : ("Bar La Cabezadita Tonta", "el"), + 9504 : ("Tienda de Bromas", "la"), + 9505 : ("Cuartel General Dibu", "el"), + 9506 : ("Tienda de Ropa de Toontown", "la"), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Posada Pluma de Ganso", "la"), + 9602 : ("Siestas a Domicilio", "las"), + 9604 : ("Fundas Nórdicas para Pinreles", "las"), + 9605 : ("Avenida de la Canción de Cuna, 323", "la"), + 9607 : ("Pijamas de Plomo para Dormir de Pie", "el"), + 9608 : ("", ""), + 9609 : ("Arrullos a Granel", "los"), + 9613 : ("Los Limpiadores Del Reloj", ""), + 9616 : ("Compañía Eléctrica Luces Fuera", "la"), + 9617 : ("Avenida de la Canción de Cuna, 212", "la"), + 9619 : ("Sopas Soporíferas", "las"), + 9620 : ("Servicio de Taxis Insomnes", "el"), + 9622 : ("Relojería El Cuco Dormido", "la"), + 9625 : ("Salón de Belleza El Ronquido Alegre", "el"), + 9626 : ("Avenida de la Canción de Cuna, 818", "la"), + 9627 : ("Mecedoras Automáticas", "las"), + 9628 : ("Calendarios Nocturnos", "los"), + 9629 : ("Avenida de la Canción de Cuna, 310", "la"), + 9630 : ("Serrería Como un Tronco", "la"), + 9631 : ("Arreglo de Relojes Estoysopa", "el"), + 9633 : ("La Siesta De Pluto", ""), + 9634 : ("Colchones La Pluma Audaz", "los"), + 9636 : ("Seguro Contra Insomnios", "el"), + 9639 : ("Conservas Ultrahibernadas", "las"), + 9640 : ("Avenida de la Canción de Cuna, 805", "la"), + 9642 : ("Ganadería Cuentaovejas", "la"), + 9643 : ("Óptica Nopegojo", "la"), + 9644 : ("Peleas de Almohadas Organizadas", "las"), + 9645 : ("Posada Todos al Sobre", "la"), + 9647 : ("¡Hazte la cama! Ferretería", ""), + 9649 : ("Ronquidos Lejanos", "los"), + 9650 : ("Avenida de la Canción de Cuna, 714", "la"), + 9651 : ("Martillos para Despertadores", "los"), + 9652 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Tienda de Bromas", "la"), + 3508 : ("Cuartel General Dibu", "el"), + 3509 : ("Tienda de Ropa", "la"), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Compañía Eléctrica Polo Norte", "la"), + 3602 : ("Gorros de Nieve Geli", "los"), + 3605 : ("", ""), + 3607 : ("Ventisca a la Vista", "la"), + 3608 : ("Bobsled para Lactantes", ""), + 3610 : ("Hipermercado Esquimal de Mito", "el"), + 3611 : ("Quitanieves Escárchez", "la"), + 3612 : ("Diseño de Iglús", "el"), + 3613 : ("Bicicletas Carámbanez", "las"), + 3614 : ("Cereales Copos de Nieve", "los"), + 3615 : ("Arenques en Almíbar", "los"), + 3617 : ("Dirigibles de Aire Frío", "los"), + 3618 : ("¿Avalancha? Sin Problemas Gestión de Crisis", "la"), + 3620 : ("Clínica de Esquí", "la"), + 3621 : ("Bar El Deshielo", "el"), + 3622 : ("", ""), + 3623 : ("Panes Criogenizados Frigomiga", "los"), + 3624 : ("Bocadillos Bajocero", "los"), + 3625 : ("Radiadores de la Tía Ritona", "los"), + 3627 : ("Adiestramiento de San Bernardos", "el"), + 3629 : ("Cafetería El Braserillo que Ríe", "la"), + 3630 : ("Agencia de Viajes Témpano Tenaz", "la"), + 3634 : ("Remontes Rascafría", "los"), + 3635 : ("Leña Usada", "la"), + 3636 : ("Surtido de Sabañones", "el"), + 3637 : ("Patines Pati", "los"), + 3638 : ("Trineos y Cuatrineos", "los"), + 3641 : ("Camas Heladas Pepe Tundra", "las"), + 3642 : ("Óptica El Yeti Tuerto", "la"), + 3643 : ("Salón de la Bola de Nieve", "el"), + 3644 : ("Cubitos de Hielo Fundidos", "los"), + 3647 : ("Alquiler de Chaqués El Pingüino Beduino", "el"), + 3648 : ("Hielo Instantáneo", "el"), + 3649 : ("Hambrrrrguesas", "las"), + 3650 : ("Antigüedades Antárticas", "las"), + 3651 : ("Perritos Helados Pipe", "los"), + 3653 : ("Joyería Frío como el Diamante", "la"), + 3654 : ("Cuartel General Dibu", "el"), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Almacén de Invierno", "el"), + 3703 : ("", ""), + 3705 : ("Carámbanos a Granel", "los"), + 3706 : ("Batidos El Tembleque", "los"), + 3707 : ("Hogar, Gélido Hogar", ""), + 3708 : ("Plutón en tu Casa", ""), + 3710 : ("Comidas Alaska en Enero", "las"), + 3711 : ("", ""), + 3712 : ("Tuberías Muy Muy Frías", "las"), + 3713 : ("Dentista El Castañeteo Perpetuo", "el"), + 3715 : ("Surtido de Sopitas de la Tía Ártica", "el"), + 3716 : ("Sal Gorda para Carreteras", "la"), + 3717 : ("Polos y Helados Variados", "los"), + 3718 : ("Calefacción a Domicilio", "la"), + 3719 : ("Paté de Cubitos", "el"), + 3721 : ("Trineos de Ocasión", "los"), + 3722 : ("Tienda de Esquí Anchoa", "la"), + 3723 : ("Guantes de Nieve Tiritonio", "los"), + 3724 : ("La Voz de la Tundra", "la"), + 3725 : ("Me Mareo en Trineo", ""), + 3726 : ("Mantas Eléctricas Solares", "las"), + 3728 : ("Quitanieves Cenutrio", "el"), + 3729 : ("", ""), + 3730 : ("Desguace de Muñecos de Nieve", "el"), + 3731 : ("Chimeneas Portátiles", "las"), + 3732 : ("La Nariz Helada", ""), + 3734 : ("Tímpanos como Témpanos Otorrino", "los"), + 3735 : ("Forros Polares de Papel", "los"), + 3736 : ("Cucuruchos de Hielo Picado", "los"), + 3737 : ("Comidas Eslalon", "las"), + 3738 : ("Escondites Frío Frío", "los"), + 3739 : ("Cuartel General Dibu", "el"), + } + +# DistributedCloset.py +ClosetTimeoutMessage = "Lo siento, el tiempo se\n te ha acabado." +ClosetNotOwnerMessage = "Este no es tu clóset, pero te puedes probar la ropa." +ClosetPopupOK = "Muy bien" +ClosetPopupCancel = "Cancelar" +ClosetDiscardButton = "Remover" +ClosetAreYouSureMessage = "Tú has borrado algunas prendas. ¿Realmente quieres borrarlas?" +ClosetYes = "Sí" +ClosetNo = "No" +ClosetVerifyDelete = "Borrar %s?" +ClosetShirt = "Esta camiseta" +ClosetShorts = "Estos shorts" +ClosetSkirt = "Esta falda" +ClosetDeleteShirt = "Borrar\ncamiseta" +ClosetDeleteShorts = "Borrar\nshorts" +ClosetDeleteSkirt = "Borrar\nfalda" + +# EstateLoader.py +EstateOwnerLeftMessage = "Lo siento, el dueño de esta propiedad se ha ido. Tú vas a ser enviado al dibuparque en %s segundos" +EstatePopupOK = "Muy bien" +EstateTeleportFailed = "No pude irme a la casa. ¡Trata de nuevo!" +EstateTeleportFailedNotFriends = "Lo siento, %s esta en una propiedad en la cual tú no tienes amigos." + +# DistributedHouse.py +AvatarsHouse = "Casa %s" + +# BankGui.py +BankGuiCancel = "Cancelar" +BankGuiOk = "Aceptar" + +# DistributedBank.py +DistributedBankNoOwner = "Lo siento, este no es tú banco." +DistributedBankNotOwner = "Lo siento, este no es tú banco." + +# FishSellGui.py +FishGuiCancel = "Cancelar" +FishGuiOk = "Vender todo" +FishTankValue = "¡Hola, %(name)s! En el cubo llevas %(num)s peces, que tienen un valor total de %(value)s gominolas. ¿Quieres venderlos todos?" + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name + "'" + else: + possesive = name + "'s" + return possesive + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = "Cuartel general: Para ver mejor, pulsa la tecla \"Re Pág\". " + +FireworksJuly4Beginning = "Cuartel general: ¡Bienvenido a los fuegos artificiales de verano! ¡Disfruta del espectáculo!" +FireworksJuly4Ending = "Cuartel general: ¡Esperamos que te haya gustado! ¡Que pases un verano estupendo!" +FireworksNewYearsEveBeginning = "Cuartel general: ¡Feliz Año! ¡Bienvenido a los fuegos artificiales del año nuevo! ¡Disfruta del espectáculo!" +FireworksNewYearsEveEnding = "Cuartel general: ¡Esperamos que te haya gustado! ¡Feliz Año 2005!" +FireworksBeginning = "Cuartel general: ¡Feliz Año! ¡Bienvenido a los fuegos artificiales del año nuevo! ¡Disfruta del espectáculo!" +FireworksEnding = "Cuartel general: ¡Esperamos que te haya gustado! ¡Feliz Año 2005!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "UN DIBUCONSEJO:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Para comprobar rápidamente el estado de tu dibutarea, pulsa la tecla \"Fin\".", + "Para echar un vistazo rápido a tu página de bromas, pulsa la tecla la tecla \"Inicio\".", + "Pulsa la tecla \"F7\" para abrir la Lista de amigos.", + "Para abrir o cerrar el dibucuaderno, pulsa la tecla \"F8\".", + "Para mirar hacia arriba, pulsa la tecla \"Re Pág\", y para mirar hacia abajo, pulsa la tecla \"Av Pág\".", + "Pulsa la tecla \"Control\" para saltar.", + "Si pulsas la tecla \"F9\", obtendrás una captura de pantalla que quedará almacenada en la carpeta Toontown de tu ordenador.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Puedes intercambiar códigos de Amigos secretos con personas que conozcas en la vida real para poder charlar con ellas en Toontown.", + "En la página Opciones del dibucuaderno podrás cambiar la resolución de la pantalla, modificar el sonido y ajustar otras opciones.", + "Pruébate la ropa de tus amigos en el armario de su casa.", + "Para ir a tu casa, usa el botón \"Ir a casa\" del mapa.", + "Cada vez que completes una dibutarea, tus puntos de risa se rellenarán automáticamente.", + "Puedes examinar el surtido de las tiendas de ropa sin necesidad de tener un ticket de ropa.", + "Las recompensas de ciertas dibutareas te permiten llevar más bromas y golosinas.", + "En tu Lista de amigos puedes tener hasta 50 usuarios.", + "Algunas recompensas de dibutareas te permitirán teletransportarte a los dibuparques de Toontown con la página del mapa del dibucuaderno.", + "Para aumentar tus puntos de risa en los dibuparques, reúne tesoros, como estrellas y cucuruchos de helado.", + "Si tienes prisa para curarte después de un duro combate, ve a tu propiedad y recoge cucuruchos de helado.", + "Para cambiar las vistas de tu dibu, pulsa la tecla Tab.", + "A veces verás que varias dibutareas distintas ofrecen la misma recompensa. ¡Compáralas!", + "Una forma divertida de avanzar en el juego consiste en buscar amigos que tengan dibutareas parecidas.", + "Nunca te hará falta guardar la partida en Toontown. Los servidores de Toontown almacenan toda la información necesaria de manera continua.", + "Si quieres susurrar a otros dibus, haz clic en ellos o selecciónalos en tu Lista de amigos.", + "Algunas frases de Charla rápida hacen que tu dibu muestre emociones.", + "Si la zona en la que estás está demasiado llena, trata de cambiar de distrito. Acude a la página de Distritos del dibucuaderno y selecciona otro distrito.", + "Si recuperas edificios aparecerá una estrella de bronce, plata u oro sobre tu dibu.", + "Si recuperas los edificios suficientes para conseguir que aparezca una estrella sobre tu cabeza, quizá veas tu nombre escrito en la pizarra de un cuartel general dibu.", + "A veces, los bots vuelven a capturar edificios recuperados. ¡La única manera de conservar tu estrella consiste en ir a recuperar más edificios!", + "Los nombres de tus amigos secretos aparecen en azul.", + # Fishing + "¡A ver si consigues pescar todos los peces de Toontown!", + "Cada estanque tiene peces diferentes. ¡Prueba con todos!", + "Cuando tengas el cubo lleno de peces, véndeselos al dependiente de la tienda de animales que hay en el dibuparque.", + "Las cañas más resistentes sirven para pescar peces más gordos, pero para usarlas hay que gastar más gominolas.", + "Puedes comprar cañas más resistentes en el Vacatálogo.", + "Mientras más grande sean los peces, más gominolas te dará la tienda de animales por ellos.", + "Mientras menos comunes sean los peces, más gominolas te dará la tienda de animales por ellos.", + "A veces encontrarás bolsas de gominolas mientras pescas.", + "En algunas dibutareas tendrás que pescar objetos en los estanques.", + "Los estanques de los dibuparques tienen peces diferentes de los de los estanques de las calles.", + "Algunos peces son muy raros. Mantente pescando hasta que los obtengas todos.", + "El estanque de tu propiedad tiene peces que solo se encuentran allí.", + "¡Por cada 10 especies que pesques conseguirás un trofeo de pesca!", + "En el dibucuaderno podrás ver qué peces has pescado.", + "Algunos trofeos de pesca te dan un aumento de risómetro.", + "La pesca es un método estupendo para conseguir más gominolas.", + ), + + TIP_STREET : ( + "Hay cuatro tipos de bots: Abogabots, chequebots, vendebots y jefebots.", + "Cada circuito de bromas tiene diferentes cantidades de precisión y daños.", + "Las bromas de sonido afectan a todos los bots, pero despiertan a los bots que siguen cebos.", + "Derrota a los bots siguiendo un orden estratégico, aumentara tús posibilidades en ganar los combates.", + "El circuito de broma curadibu te permite sanar a otros dibus durante los combates.", + "¡Durante las invasiones de bots, los puntos de experiencia de bromas se duplican!", + "Si formas un equipo con otros dibus y usas el mismo circuito de bromas en el combate, conseguirás una bonificación de daños a los bots.", + "Las bromas se usan por orden, de arriba a abajo, según aparecen en el menú de bromas del combate.", + + "La fila de luces circulares que hay sobre los ascensores de los edificios bot muestra cuántos pisos tienen.", + "Haz clic en un bot para ver más detalles sobre él.", + "Si usas bromas de alto nivel contra bots de bajo nivel no conseguirás puntos de experiencia.", + "Las bromas que te dan experiencia tienen un fondo azul en el menú de bromas del combate.", + "La experiencia que te dan las bromas se multiplica en el interior de los edificios bot. Mientras más elevado sea el piso, mayor será el factor de multiplicación.", + "Al ganar a un bot, todos los dibus que hayan participado conseguirán puntos cuando termine el combate.", + "Todas las calles de Toontown tienen distintos tipos y niveles de bots.", + "En las aceras estarás a salvo de los bots.", + "En las calles, las puertas de las casas cuentan chistes cuando te acercas.", + "Algunas dibutareas te proporcionan entrenamiento para nuevos circuitos de bromas. ¡Solo podrás escoger seis de los siete circuitos de bromas, así que elige con cuidado!", + "Las trampas solo son útiles cuando tú coordines con tus amigos el uso de los cebos en el combate.", + "Los cebos de alto nivel tienen menos posibilidades de fallar.", + "Las bromas de bajo nivel tienen poca precisión contra los bots de alto nivel.", + TheCogs+" no pueden atacar cuando han seguido un cebo en un combate.", + "Cuando tus amigos y tú recuperen un edificio bot, ustedes serán recompensados con retratos dentro del edificio rescatado.", + "Si usas una broma curadibu en un dibu que tenga el risómetro completo, no conseguirás experiencia de curadibu.", + TheCogs+" quedarán aturdidos durante un instante al ser alcanzados por cualquier broma. Esto aumenta la probabilidad de que otras bromas los alcancen en la misma ronda.", + "Las bromas de caída tienen menos probabilidad de alcanzar su objetivo, pero su precisión aumenta cuando el bot ha sido alcanzado antes por otra broma en la misma ronda.", + "Cuando hayas derrotado suficientes bots, podrás usar el \"radar de bots\" haciendo clic en los iconos de bots de la página Galería de bots del dibucuaderno.", + "Durante los combates podrás saber a qué bot tus compañeros de equipo están atacando, mirando a los guiones (-) y a las X.", + "Durante los combates, los bots llevan una luz que indica su estado de salud: verde significa que está sano y rojo que casi está destruido.", + "Solo pueden combatir a la vez un máximo de cuatro dibus.", + "En la calle, los bots tienen más tendencia a luchar contra varios dibus que contra uno solo.", + "Los últimos dos bots de cada tipo que son más difíciles de encontrar, los encuentrarás en el interior de los edificios.", + "Las bromas de caída no funcionan contra los bots que siguen un cebo.", + TheCogs+" tienden a atacar al dibu que les ha causado más daños.", + "Las bromas de sonido no dan bonificación por daños contra los bots que siguen cebos.", + "Si esperas demasiado para atacar a un bot que sigue un cebo, se despertará. Mientras mayor sea el nivel del cebo, mayor será su duración.", + ), + + TIP_MINIGAME : ( + "Cuando tengas llena la jarra de gominolas, las que consigas en los Juegos del tranvía irán a parar directamente a tu banco.", + "Puedes usar las teclas de flecha en lugar del ratón en el Juego del tranvía \"Imita a Minnie\".", + "En el juego del cañón, usa las teclas de flecha para mover el cañón y pulsa la tecla \"Control\" para disparar.", + "En el juego de los anillos, conseguirás puntos de bonificación cuando todo el grupo consiga atravesar sus anillos.", + "Si juegas perfectamente a Imita a Minnie, duplicarás los puntos.", + "En el juego de la cuerda, conseguirás más gominolas si te enfrentas a un bot más grande.", + "La dificultad de los Juegos del tranvía varían según el barrio: En el Centro de Toontown están los más fáciles, y en Sueñolandia de Donald, los más difíciles.", + "A algunos Juegos del tranvía solo se pueden jugar en grupo.", + ), + + TIP_COGHQ : ( + "Antes de entrar en el edificio jefe debes acabar de reunir todo el disfraz de bot.", + "Puedes saltar encima de los matones bots para dejarlos temporalmente incapacitados.", + "Reúne méritos bot luchando contra bots y venciéndolos.", + "Se te otorgarán más méritos si luchas contra bots de mayor nivel.", + "Cuando reúnas los méritos bot necesarios para recibir un ascenso, vete a ver al VIP vendebot.", + "Cuando llevas el disfraz de bot puedes hablar como uno de ellos.", + "Hasta ocho dibus pueden unirse para luchar contra el VIP vendebot.", + "El VIP vendebot está en el ático del cuartel general bot.", + "Una vez dentro de las fábricas bot, sube las escaleras hasta llegar al capataz.", + "A medida que luchas en la fábrica vas ganando piezas del disfraz bot.", + "Puedes comprobar cuánto te falta para completar el disfraz bot con el libro de pegatinas.", + "Puedes comprobar cuántos méritos llevas en la página del disfraz del libro de pegatinas.", + "Antes de enfrentarte al VIP, comprueba que vas bien cargado de bromas y puntos de risa.", + "El disfraz de bot irá cambiando a medida que recibas ascensos.", + "Debes derrotar al capataz de la fábrica para recuperar una pieza del disfraz de bot.", + ), + + } + +FishGenusNames = { + 0 : "Pez globo", + 2 : "Pez gato", + 4 : "Pez payaso", + 6 : "Pez congelado", + 8 : "Estrella de mar", + 10 : "Merluza de colar", + 12 : "Pez perro", + 14 : "Anguila amorosa", + 16 : "Tiburón matrona", + 18 : "Rey centollo", + 20 : "Pez luna", + 22 : "Caballito de mar", + 24 : "Tiburón billarista", + 26 : "Barboso", + 28 : "Trucha pirata", + 30 : "Atún piano", + 32 : "Bocadidusa", + 34 : "Raya diablo", + } + +FishSpeciesNames = { + 0 : ( "Pez globo", + "Pez globo aerostático", + "Pez globo meteorológico", + "Pez globo de agua", + "Pez globo rojo", + ), + 2 : ( "Pez gato", + "Pez gato siamés", + "Pez gato callejero", + "Pez gato atigrado", + "Pez gato montés", + ), + 4 : ( "Pez payaso", + "Pez payaso triste", + "Pez payaso cumpleañero", + "Pez payaso circense", + ), + 6 : ( "Pez congelado", + ), + 8 : ( "Estrella de mar", + "Estrella marina de cinco puntas", + "Estrella marina de rock", + "Estrella marina del alba", + "Estrella marina fugaz", + ), + 10 : ( "Merluza de colar", + ), + 12 : ( "Pez perro", + "Pez perro de presa", + "Pez perro caliente", + "Pez perro dálmata", + "Pez perro cachorro", + ), + 14 : ( "Anguila amorosa", + "Anguila eléctrica amorosa", + ), + 16 : ( "Tiburón matrona", + "Tiburón matrona Clara", + "Tiburón matrona Feliciana", + ), + 18 : ( "Rey Centollo", + "Principe Centollo", + "Regente Centollo", + ), + 20 : ( "Pez luna", + "Pez luna llena", + "Pez media luna", + "Pez luna nueva", + "Pez luna creciente", + "Pez luna de miel", + ), + 22 : ( "Caballito de mar", + "Caballito de Troya de mar", + "Caballito percherón de mar", + "Caballito árabe de mar", + ), + 24 : ( "Tiburón billarista", + "Tiburón billarista aprendiz", + "Tiburón billarista experto", + "Tiburón billarista olímpico", + ), + 26 : ( "Barboso pardo", + "Barboso negro", + "Barboso koala", + "Barboso madroñero", + "Barboso polar", + "Barboso panda", + "Barboso pardo", + "Barboso gris", + ), + 28 : ( "Trucha pirata", + "Trucha pirata corsaria", + "Trucha pirata bucanera", + ), + 30 : ( "Atún piano", + "Atún piano de cola", + "Atún clavicordio", + "Atún piano vertical", + "Atún piano eléctrico", + ), + 32 : ( "Bocadidusa", + "Bocadidusa de queso", + "Bocadidusa crujiente", + "Bocadidusa de mermelada", + "Concord Grape PB&J Fish", + ), + 34 : ( "Raya diablo", + ), + } + +FishFirstNames = ( + "", + "Anchoa", + "Anguilo", + "Anzuela", + "Arenquilla", + "Besuguete", + "Beto", + "Bocartín", + "Brequita", + "Caballero", + "Castañeta", + "Cazonio", + "Chicharra", + "Chicho", + "Congriano", + "Corroncho", + "Curro", + "Deslenguada", + "Don", + "Doña", + "Dorada", + "Emperadora", + "Escarcho", + "Escorpina", + "Escualo", + "Esturiona", + "Fanequillo", + "Florinda", + "Ilustrísima", + "Jurelio", + "Kiko", + "Lisa", + "Lubino", + "Lucio", + "Nelson", + "Nemo", + "Neptuno", + "Nina", + "Palmira", + "Palometa", + "Perco", + "Pintarroja", + "Rapero", + "Robaldiño", + "Salmonillo", + "Sardino", + "Sargonete", + "Señora", + "Señorita", + "Simbad", + "Su Eminencia", + "Su Excelencia", + "Su Majestad", + "Tintorero", + "Tita", + "Torpedo", + "Tritón", + "Trucho", + "Zompo", + ) + +FishLastPrefixNames = ( + "", + "Agalla", + "Agua", + "Aleta", + "Alga", + "Ancla", + "Angula", + "Arena", + "Bahía", + "Ballena", + "Banda", + "Barbilla", + "Boca", + "Bonito", + "Branquia", + "Cabeza", + "Caña", + "Cara", + "Carnaza", + "Chalupa", + "Chepa", + "Cinta", + "Cococha", + "Concha", + "Corbeta", + "Corriente", + "Curricana", + "Escama", + "Espalda", + "Espina", + "Fragata", + "Gaviota", + "LagunaRojo", + "Mar", + "Marejada", + "Medusa", + "Merluza", + "Nariz", + "Ola", + "Orilla", + "Panza", + "Pecera", + "Pescadilla", + "Pinza", + "Plancton", + "Playa", + "Raspa", + "Raya", + "Rémora", + "Ria", + "Ribera", + "RocaSaludar", + "Salta", + "Sirena", + "Vela", + "Zambullida", + ) + +FishLastSuffixNames = ( + "", + "del norte", + "del sur", + "afilada", + "ahumada", + "apestosa", + "arénquez", + "atigrada", + "azul", + "besúguez", + "blanca", + "doble", + "dorada", + "fantasma", + "fina", + "fresca", + "frita", + "gállez", + "gato", + "gorda", + "gris", + "limón", + "lubinez", + "morada", + "moteada", + "naranja", + "occidental", + "oriental", + "plana", + "plateada", + "profunda", + "rraya", + "rrayada", + "rrodaballez", + "rroja", + "rrosa", + "salada", + "surfera", + "tropical", + "trúchez", + "verde", + ) + + +CogPartNames = ( + "Muslo izquierdo ", "Pantorrilla izquierda", "Pie izquierdo", + "Muslo derecho", "Pantorrilla derecha", "Pie derecho", + "Hombro izquierdo", "Hombro derecho", "Pecho", "Indicador de salud", "Caderas", + "Brazo izquierdo", "Antebrazo izquierdo", "Mano izquierda", + "Brazo derecho", "Antebrazo derecho", "Mano derecha", + ) + +CogPartNamesSimple = ( + "Torso superior", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrada Principal" +SellbotLegFactorySpecLobby = "Vestíbulo" +SellbotLegFactorySpecLobbyHallway = "Entrada del Vestíbulo" +SellbotLegFactorySpecGearRoom = "Sala de Máquinas" +SellbotLegFactorySpecBoilerRoom = "Sala de Calderas" +SellbotLegFactorySpecEastCatwalk = "Pasarela Este" +SellbotLegFactorySpecPaintMixer = "Mezcladora de Pintura" +SellbotLegFactorySpecPaintMixerStorageRoom = "Sala de la Mezcladora de Pintura" +SellbotLegFactorySpecWestSiloCatwalk = "Pasarela del Silo Oeste" +SellbotLegFactorySpecPipeRoom = "Sala de Tuberías" +SellbotLegFactorySpecDuctRoom = "Sala de Conductos" +SellbotLegFactorySpecSideEntrance = "Entrada de Servicio" +SellbotLegFactorySpecStomperAlley = "Callejón del Pisotón" +SellbotLegFactorySpecLavaRoomFoyer = "Entrada a la Sala de la Lava" +SellbotLegFactorySpecLavaRoom = "Sala de la Lava" +SellbotLegFactorySpecLavaStorageRoom = "Sala donde se guarda la Lava" +SellbotLegFactorySpecWestCatwalk = "Pasarela Oeste" +SellbotLegFactorySpecOilRoom = "Sala del Aceite" +SellbotLegFactorySpecLookout = "Cabina de Vigilancia" +SellbotLegFactorySpecWarehouse = "Almacén" +SellbotLegFactorySpecOilRoomHallway = "Entrada a la Sala del Aceite" +SellbotLegFactorySpecEastSiloControlRoom = "Sala de Control del Silo Este" +SellbotLegFactorySpecWestSiloControlRoom = "Sala de Control del Silo Oeste" +SellbotLegFactorySpecCenterSiloControlRoom = "Sala de Control del Silo Central" +SellbotLegFactorySpecEastSilo = "Silo Este" +SellbotLegFactorySpecWestSilo = "Silo Oeste" +SellbotLegFactorySpecCenterSilo = "Silo Central" +SellbotLegFactorySpecEastSiloCatwalk = "Pasarela del Silo Este" +SellbotLegFactorySpecWestElevatorShaft = "Hueco del Ascensor del Silo Oeste" +SellbotLegFactorySpecEastElevatorShaft = "Hueco del Ascensor del Silo Este" + diff --git a/toontown/src/toonbase/TTLocalizerEnglish.py b/toontown/src/toonbase/TTLocalizerEnglish.py new file mode 100644 index 0000000..85543a5 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizerEnglish.py @@ -0,0 +1,11805 @@ +from toontown.toonbase.TTLocalizerEnglishProperty import * + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +FancyFont = 'phase_3/models/fonts/Comedy' +NametagFonts = ('phase_3/models/fonts/AnimGothic', #0 * + 'phase_3/models/fonts/Aftershock', #1 * + 'phase_3/models/fonts/JiggeryPokery', #2 * + 'phase_3/models/fonts/Ironwork', #3 * + 'phase_3/models/fonts/HastyPudding', #4 * + 'phase_3/models/fonts/Comedy', #5 * + 'phase_3/models/fonts/Humanist', #6 * + 'phase_3/models/fonts/Portago', #7 * + 'phase_3/models/fonts/Musicals', #8 * + 'phase_3/models/fonts/Scurlock', #9 * + 'phase_3/models/fonts/Danger', #10 * + 'phase_3/models/fonts/Alie', #11 * + 'phase_3/models/fonts/OysterBar', #12 * + 'phase_3/models/fonts/RedDogSaloon', #13 * + ) +NametagFontNames = ('Member', #0 * + 'Shivering', #1 * + 'Wonky', #2 * + 'Fancy', #3 * + 'Silly', #4 * + 'Zany', #5 * + 'Practical', #6 * + 'Nautical', #7 * + 'Whimsical', #8 * + 'Spooky', #9 * + 'Action', #10 * + 'Poetic', #11 * + 'Boardwalk', #12 * + 'Western', #13 * + ) + +NametagLabel = " Nametag" + +UnpaidNameTag = "Basic" + +# GM nametags +GM_1 = "TOON COUNCIL" +GM_2 = "TOON TROOP" + +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "Mickey" +VampireMickey = "VampireMickey" +Minnie = "Minnie" +WitchMinnie = "WitchMinnie" +Donald = "Donald" +DonaldDock = "DonaldDock" +Daisy = "Daisy" +Goofy = "Goofy" +SuperGoofy = "SuperGoofy" +Pluto = "Pluto" +WesternPluto = "WesternPluto" +Flippy = "Flippy" +Chip = "Chip" +Dale = "Dale" + +# common locations +lTheBrrrgh = 'The Brrrgh' +lDaisyGardens = 'Daisy Gardens' +lDonaldsDock = "Donald's Dock" +lDonaldsDreamland = "Donald's Dreamland" +lMinniesMelodyland = "Minnie's Melodyland" +lToontownCentral = 'Toontown Central' +lToonHQ = 'Toon HQ' +lSellbotHQ = 'Sellbot HQ' +lGoofySpeedway = "Goofy Speedway" +lOutdoorZone = "Chip 'n Dale's Acorn Acres" +lGolfZone = "Chip 'n Dale's MiniGolf" +lPartyHood = "Party Grounds" + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("to", "on", "Tutorial Terrace"), # Tutorial + 1000 : ("to the", "in the", "Playground"), + 1100 : ("to", "on", "Barnacle Boulevard"), + 1200 : ("to", "on", "Seaweed Street"), + 1300 : ("to", "on", "Lighthouse Lane"), + 2000 : ("to the", "in the", "Playground"), + 2100 : ("to", "on", "Silly Street"), + 2200 : ("to", "on", "Loopy Lane"), + 2300 : ("to", "on", "Punchline Place"), + 3000 : ("to the", "in the", "Playground"), + 3100 : ("to", "on", "Walrus Way"), + 3200 : ("to", "on", "Sleet Street"), + 3300 : ("to", "on", "Polar Place"), + 4000 : ("to the", "in the", "Playground"), + 4100 : ("to", "on", "Alto Avenue"), + 4200 : ("to", "on", "Baritone Boulevard"), + 4300 : ("to", "on", "Tenor Terrace"), + 5000 : ("to the", "in the", "Playground"), + 5100 : ("to", "on", "Elm Street"), + 5200 : ("to", "on", "Maple Street"), + 5300 : ("to", "on", "Oak Street"), + 9000 : ("to the", "in the", "Playground"), + 9100 : ("to", "on", "Lullaby Lane"), + 9200 : ("to", "on", "Pajama Place"), + 10000 : ("to", "in", "Bossbot HQ Country Club"), + 10100 : ("to the", "in the", "Bossbot HQ Lobby"), + 10200 : ("to the", "in the", "The Clubhouse"), + 10500 : ("to the", "in the", "The Front Three"), + 10600 : ("to the", "in the", "The Middle Six"), + 10700 : ("to the", "in the", "The Back Nine"), + 11000 : ("to the", "in the", "Sellbot HQ Courtyard"), + 11100 : ("to the", "in the", "Sellbot HQ Lobby"), + 11200 : ("to the", "in the", "Sellbot Factory"), + 11500 : ("to the", "in the", "Sellbot Factory"), + 12000 : ("to", "in", "Cashbot Train Yard"), + 12100 : ("to the", "in the", "Cashbot HQ Lobby"), + 12500 : ("to the", "in the", "Cashbot Coin Mint"), + 12600 : ("to the", "in the", "Cashbot Dollar Mint"), + 12700 : ("to the", "in the", "Cashbot Bullion Mint"), + 13000 : ("to", "in", "Lawbot HQ Courtyard"), + 13100 : ("to the", "in the", "Courthouse Lobby"), + 13200 : ("to the", "in the", "DA's Office Lobby"), + 13300 : ("to the", "in the", "Lawbot A Office"), + 13400 : ("to the", "in the", "Lawbot B Office"), + 13500 : ("to the", "in the", "Lawbot C Office"), + 13600 : ("to the", "in the", "Lawbot D Office"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("to", "in", lDonaldsDock) +ToontownCentral = ("to", "in", lToontownCentral) +TheBrrrgh = ("to", "in", lTheBrrrgh) +MinniesMelodyland = ("to", "in", lMinniesMelodyland) +DaisyGardens = ("to", "in", lDaisyGardens) +OutdoorZone = ("to", "in", lOutdoorZone) +FunnyFarm = ("to the", "in the", "Funny Farm") +GoofySpeedway = ("to", "in", lGoofySpeedway) +DonaldsDreamland = ("to", "in", lDonaldsDreamland) +BossbotHQ = ("to", "in", "Bossbot HQ") +SellbotHQ = ("to", "in", "Sellbot HQ") +CashbotHQ = ("to", "in", "Cashbot HQ") +LawbotHQ = ("to", "in", "Lawbot HQ") +Tutorial = ("to the", "in the", "Toon-torial") +MyEstate = ("to", "in", "your house") +WelcomeValley = ("to", "in", "Welcome Valley") +GolfZone = ("to", "in", lGolfZone) +PartyHood = ("to the", "in the", lPartyHood) + +Factory = 'Factory' +Headquarters = 'Headquarters' +SellbotFrontEntrance = 'Front Entrance' +SellbotSideEntrance = 'Side Entrance' +Office = 'Office' + +FactoryNames = { + 0 : 'Factory Mockup', + 11500 : 'Sellbot Cog Factory', + 13300 : 'Lawbot Cog Office', #remove me JML + } + +FactoryTypeLeg = 'Leg' +FactoryTypeArm = 'Arm' +FactoryTypeTorso = 'Torso' + +MintFloorTitle = 'Floor %s' + +# common strings +lCancel = 'Cancel' +lClose = 'Close' +lOK = 'OK' +lNext = 'Next' +lQuit = 'Quit' +lYes = 'Yes' +lNo = 'No' + +sleep_auto_reply = "%s is sleeping right now" + +lHQOfficerF = 'HQ Officer' +lHQOfficerM = 'HQ Officer' + +MickeyMouse = "Mickey Mouse" + +AIStartDefaultDistrict = "Sillyville" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "a Cog" +TheCogs = "The Cogs" +ASkeleton = "a Skelecog" +Skeleton = "Skelecog" +SkeletonP = "Skelecogs" +Av2Cog = "a Version 2.0 Cog" +v2Cog = "Version 2.0 Cog" +v2CogP = "Version 2.0 Cogs" +ASkeleton = "a Skelecog" +Foreman = "Factory Foreman" +ForemanP = "Factory Foremen" +AForeman = "a Factory Foreman" +CogVP = Cog + " V.P." +CogVPs = "Cog V.P.'s" +ACogVP = ACog + " V.P." +Supervisor = "Mint Supervisor" +SupervisorP = "Mint Supervisors" +ASupervisor = "a Mint Supervisor" +CogCFO = Cog + " C.F.O." +CogCFOs = "Cog C.F.O.'s" +ACogCFO = ACog + " C.F.O." + +# Quests.py +TheFish = "the Fish" +AFish = "a fish" +Level = 'Level' +QuestsCompleteString = "Complete" +QuestsNotChosenString = "Not chosen" +Period = "." + +Laff = "Laff" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Hello, _avName_!", + "Hi, _avName_!", + "Hey there, _avName_!", + "Say there, _avName_!", + "Welcome, _avName_!", + "Howdy, _avName_!", + "How are you, _avName_?", + "Greetings _avName_!", + ) +QuestsDefaultIncomplete = ("How's that task coming, _avName_?", + "Looks like you still have more work to do on that task!", + "Keep up the good work, _avName_!", + "Keep trying to finish that task. I know you can do it!", + "Keep trying to complete that task, we are counting on you!", + "Keep working on that ToonTask!", + ) +QuestsDefaultIncompleteProgress = ("You came to the right place, but you need to finish your ToonTask first.", + "When you are finished with that ToonTask, come back here.", + "Come back when you are finished with your ToonTask.", + ) +QuestsDefaultIncompleteWrongNPC = ("Nice work on that ToonTask. You should go visit _toNpcName_._where_", + "Looks like you are ready to finish your ToonTask. Go see _toNpcName_._where_.", + "Go see _toNpcName_ to finish your ToonTask._where_", + ) +QuestsDefaultComplete = ("Nice work! Here is your reward...", + "Great job, _avName_! Take this reward...", + "Wonderful job, _avName_! Here is your reward...", + ) +QuestsDefaultLeaving = ("Bye!", + "Goodbye!", + "So long, _avName_.", + "See ya, _avName_!", + "Good luck!", + "Have fun in Toontown!", + "See you later!", + ) +QuestsDefaultReject = ("Hello.", + "Can I help you?", + "How are you?", + "Hello there.", + "I'm a little busy now, _avName_.", + "Yes?", + "Howdy, _avName_!", + "Welcome, _avName_!", + "Hey, _avName_! How's it going?", + # Game Hints + "Did you know you can open your Shticker Book by hitting F8?", + "You can use your map to teleport back to the playground!", + "You can make friends with other players by clicking on them.", + "You can discover more about a " + Cog + " by clicking on him.", + "Gather treasures in the playgrounds to fill your Laff meter.", + Cog + " buildings are dangerous places! Do not go in alone!", + "When you lose a battle, the " + Cogs + " take all your gags.", + "To get more gags, play Trolley games!", + "You can get more Laff points by completing ToonTasks.", + "Every ToonTask gives you a reward.", + "Some rewards let you carry more gags.", + "If you win a battle, you get ToonTask credit for every " + Cog + " defeated.", + "If you recapture a " + Cog + " building, go back inside to see a special thank-you from its owner!", + "If you press the Page Up key, you can look up!", + "If you press the Tab key, you can see different views of your surroundings!", + "To show True Friends what you're thinking, enter a '.' before your thought.", + "If a " + Cog + " is stunned, it is more difficult for them to avoid falling objects.", + "Each kind of " + Cog + " building has a distinct look.", + "Defeating " + Cogs + " on the higher floors of a building will give you greater skill rewards.", + ) +QuestsDefaultTierNotDone = ("Hello, _avName_! You must finish your current ToonTasks before getting a new one.", + "Hi there! You need to finish the ToonTasks you are working on in order to get a new one.", + "Hi, _avName_! Before I can give you a new ToonTask, you need to finish the ones you have.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("I heard _toNpcName_ is looking for you._where_", + "Stop by and see _toNpcName_ when you get a chance._where_", + "Pay a visit to _toNpcName_ next time you are over that way._where_", + "If you get a chance, stop in and say hi to _toNpcName_._where_", + "_toNpcName_ will give you your next ToonTask._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s of %(numCogs)s defeated" +QuestsCogQuestHeadline = "WANTED" +QuestsCogQuestSCStringS = "I need to defeat %(cogName)s%(cogLoc)s." +QuestsCogQuestSCStringP = "I need to defeat some %(cogName)s%(cogLoc)s." +QuestsCogQuestDefeat = "Defeat %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Help a new Toon defeat %s" +QuestsCogNewNewbieQuestCaption = "Help a new Toon %d Laff or less" +QuestsCogOldNewbieQuestObjective = "Help a Toon with %(laffPoints)d Laff or less defeat %(objective)s" +QuestsCogOldNewbieQuestCaption = "Help a Toon %d Laff or less" +QuestsCogNewbieQuestAux = "Defeat:" +QuestsNewbieQuestHeadline = "APPRENTICE" + +QuestsCogTrackQuestProgress = "%(progress)s of %(numCogs)s defeated" +QuestsCogTrackQuestHeadline = "WANTED" +QuestsCogTrackQuestSCStringS = "I need to defeat %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestSCStringP = "I need to defeat some %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Defeat %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s of %(numCogs)s defeated" +QuestsCogLevelQuestHeadline = "WANTED" +QuestsCogLevelQuestDefeat = "Defeat %s" +QuestsCogLevelQuestDesc = "a Level %(level)s+ %(name)s" +QuestsCogLevelQuestDescC = "%(count)s Level %(level)s+ %(name)s" +QuestsCogLevelQuestDescI = "some Level %(level)s+ %(name)s" +QuestsCogLevelQuestSCString = "I need to defeat %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'two+', 'three+', 'four+', 'five+') +QuestsBuildingQuestBuilding = "Building" +QuestsBuildingQuestBuildings = "Buildings" +QuestsBuildingQuestHeadline = "DEFEAT" +QuestsBuildingQuestProgressString = "%(progress)s of %(num)s defeated" +QuestsBuildingQuestString = "Defeat %s" +QuestsBuildingQuestSCString = "I need to defeat %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "a %(type)s Building" +QuestsBuildingQuestDescF = "a %(floors)s story %(type)s Building" +QuestsBuildingQuestDescC = "%(count)s %(type)s Buildings" +QuestsBuildingQuestDescCF = "%(count)s %(floors)s story %(type)s Buildings" +QuestsBuildingQuestDescI = "some %(type)s Buildings" +QuestsBuildingQuestDescIF = "some %(floors)s story %(type)s Buildings" + +QuestFactoryQuestFactory = "Factory" +QuestsFactoryQuestFactories = "Factories" +QuestsFactoryQuestHeadline = "DEFEAT" +QuestsFactoryQuestProgressString = "%(progress)s of %(num)s defeated" +QuestsFactoryQuestString = "Defeat %s" +QuestsFactoryQuestSCString = "I need to defeat %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "a %(type)s Factory" +QuestsFactoryQuestDescC = "%(count)s %(type)s Factories" +QuestsFactoryQuestDescI = "some %(type)s Factories" + +QuestMintQuestMint = "Mint" +QuestsMintQuestMints = "Mints" +QuestsMintQuestHeadline = "DEFEAT" +QuestsMintQuestProgressString = "%(progress)s of %(num)s defeated" +QuestsMintQuestString = "Defeat %s" +QuestsMintQuestSCString = "I need to defeat %(objective)s%(location)s." + +QuestsMintQuestDesc = "a Cog Mint" +QuestsMintQuestDescC = "%(count)s Cog Mints" +QuestsMintQuestDescI = "some Cog Mints" + +QuestsRescueQuestProgress = "%(progress)s of %(numToons)s rescued" +QuestsRescueQuestHeadline = "RESCUE" +QuestsRescueQuestSCStringS = "I need to rescue a Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "I need to rescue some Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Rescue %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "a Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Rescue:" + +QuestsRescueNewNewbieQuestObjective = "Help a new Toon rescue %s" +QuestsRescueOldNewbieQuestObjective = "Help a Toon with %(laffPoints)d Laff or less rescue %(objective)s" + +QuestCogPartQuestCogPart = "Cog Suit Part" +QuestsCogPartQuestFactories = "Factories" +QuestsCogPartQuestHeadline = "RETRIEVE" +QuestsCogPartQuestProgressString = "%(progress)s of %(num)s retrieved" +QuestsCogPartQuestString = "Retrieve %s" +QuestsCogPartQuestSCString = "I need to retrieve %(objective)s%(location)s." +QuestsCogPartQuestAux = "Retrieve:" + +QuestsCogPartQuestDesc = "a Cog Suit Part" +QuestsCogPartQuestDescC = "%(count)s Cog Suit Parts" +QuestsCogPartQuestDescI = "some Cog Suit Parts" + +QuestsCogPartNewNewbieQuestObjective = 'Help a new Toon retrieve %s' +QuestsCogPartOldNewbieQuestObjective = 'Help a Toon with %(laffPoints)d Laff or less retrieve %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s of %(numGags)s delivered" +QuestsDeliverGagQuestHeadline = "DELIVER" +QuestsDeliverGagQuestToSCStringS = "I need to deliver %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "I need to deliver some %(gagName)s." +QuestsDeliverGagQuestSCString = "I need make a delivery." +QuestsDeliverGagQuestString = "Deliver %s" +QuestsDeliverGagQuestStringLong = "Deliver %s to _toNpcName_." +QuestsDeliverGagQuestInstructions = "You can buy this gag in the Gag Shop once you earn access to it." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "DELIVER" +QuestsDeliverItemQuestSCString = "I need to deliver %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Deliver %s" +QuestsDeliverItemQuestStringLong = "Deliver %s to _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISIT" +QuestsVisitQuestStringShort = "Visit" +QuestsVisitQuestStringLong = "Visit _toNpcName_" +QuestsVisitQuestSeeSCString = "I need to see %s." + +QuestsRecoverItemQuestProgress = "%(progress)s of %(numItems)s recovered" +QuestsRecoverItemQuestHeadline = "RECOVER" +QuestsRecoverItemQuestSeeHQSCString = "I need to see an "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToHQSCString = "I need to return %s to an "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToSCString = "I need to return %(item)s to %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "I need to go to a Toon HQ." +QuestsRecoverItemQuestGoToPlaygroundSCString = "I need to go to %s Playground." +QuestsRecoverItemQuestGoToStreetSCString = "I need to go %(to)s %(street)s in %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "I need to visit %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Where is %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "I need to recover %(item)s from %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Recover %(item)s from %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "CHOOSE" +QuestsTrackChoiceQuestSCString = "I need to choose between %(trackA)s and %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Maybe I should choose %s." +QuestsTrackChoiceQuestString = "Choose between %(trackA)s and %(trackB)s" + +QuestsFriendQuestHeadline = "FRIEND" +QuestsFriendQuestSCString = "I need to make a friend." +QuestsFriendQuestString = "Make a friend" + +QuestsMailboxQuestHeadline = "MAIL" +QuestsMailboxQuestSCString = "I need to check my mail." +QuestsMailboxQuestString = "Check your mail" + +QuestsPhoneQuestHeadline = "CLARABELLE" +QuestsPhoneQuestSCString = "I need to call Clarabelle." +QuestsPhoneQuestString = "Call Clarabelle" + +QuestsFriendNewbieQuestString = "Make %d friends %d laff or less" +QuestsFriendNewbieQuestProgress = "%(progress)s of %(numFriends)s made" +QuestsFriendNewbieQuestObjective = "Make friends with %d new Toons" + +QuestsTrolleyQuestHeadline = "TROLLEY" +QuestsTrolleyQuestSCString = "I need to ride the trolley." +QuestsTrolleyQuestString = "Ride on the trolley" +QuestsTrolleyQuestStringShort = "Ride the trolley" + +QuestsMinigameNewbieQuestString = "%d Minigames" +QuestsMinigameNewbieQuestProgress = "%(progress)s of %(numMinigames)s Played" +QuestsMinigameNewbieQuestObjective = "Play %d minigames with new Toons" +QuestsMinigameNewbieQuestSCString = "I need to play minigames with new Toons." +QuestsMinigameNewbieQuestCaption = "Help a new Toon %d laff or less" +QuestsMinigameNewbieQuestAux = "Play:" + +QuestsMaxHpReward = "Your Laff limit has been increased by %s." +QuestsMaxHpRewardPoster = "Reward: %s point Laff boost" + +QuestsMoneyRewardSingular = "You get 1 jellybean." +QuestsMoneyRewardPlural = "You get %s jellybeans." +QuestsMoneyRewardPosterSingular = "Reward: 1 jellybean" +QuestsMoneyRewardPosterPlural = "Reward: %s jellybeans" + +QuestsMaxMoneyRewardSingular = "You can now carry 1 jellybean." +QuestsMaxMoneyRewardPlural = "You can now carry %s jellybeans." +QuestsMaxMoneyRewardPosterSingular = "Reward: Carry 1 jellybean" +QuestsMaxMoneyRewardPosterPlural = "Reward: Carry %s jellybeans" + +QuestsMaxGagCarryReward = "You get a %(name)s. You can now carry %(num)s gags." +QuestsMaxGagCarryRewardPoster = "Reward: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "You can now have %s ToonTasks." +QuestsMaxQuestCarryRewardPoster = "Reward: Carry %s ToonTasks" + +QuestsTeleportReward = "You now have teleport access to %s." +QuestsTeleportRewardPoster = "Reward: Teleport access to %s" + +QuestsTrackTrainingReward = "You can now train for \"%s\" gags." +QuestsTrackTrainingRewardPoster = "Reward: Gag training" + +QuestsTrackProgressReward = "You now have frame %(frameNum)s of the %(trackName)s track animation." +QuestsTrackProgressRewardPoster = "Reward: \"%(trackName)s\" track animation frame %(frameNum)s" + +QuestsTrackCompleteReward = "You may now carry and use \"%s\" gags." +QuestsTrackCompleteRewardPoster = "Reward: Final %s track training" + +QuestsClothingTicketReward = "You can change your clothes" +QuestsClothingTicketRewardPoster = "Reward: Clothing Ticket" + +TIPQuestsClothingTicketReward = "You can change your shirt for a TIP shirt" +TIPQuestsClothingTicketRewardPoster = "Reward: TIP Clothing Ticket" + +QuestsCheesyEffectRewardPoster = "Reward: %s" + +QuestsCogSuitPartReward = "You now have a %(cogTrack)s %(part)s Cog Suit Part." +QuestsCogSuitPartRewardPoster = "Reward: %(cogTrack)s %(part)s Part" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "in this playground" +QuestsStreetLocationThisStreet = "on this street" +QuestsStreetLocationNamedPlayground = "in the %s playground" +QuestsStreetLocationNamedStreet = "on %(toStreetName)s in %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "%s's building is called" +QuestsLocationBuildingVerb = "which is" +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "I need to finish a ToonTask." + +# MaxGagCarryReward names +QuestsMediumPouch = "Medium Pouch" +QuestsLargePouch = "Large Pouch" +QuestsSmallBag = "Small Bag" +QuestsMediumBag = "Medium Bag" +QuestsLargeBag = "Large Bag" +QuestsSmallBackpack = "Small Backpack" +QuestsMediumBackpack = "Medium Backpack" +QuestsLargeBackpack = "Large Backpack" +QuestsItemDict = { + 1 : ["Pair of Glasses", "Pairs of Glasses", "a "], + 2 : ["Key", "Keys", "a "], + 3 : ["Blackboard", "Blackboards", "a "], + 4 : ["Book", "Books", "a "], + 5 : ["Candy Bar", "Candy Bars", "a "], + 6 : ["Piece of Chalk", "Pieces of Chalk", "a "], + 7 : ["Recipe", "Recipes", "a "], + 8 : ["Note", "Notes", "a "], + 9 : ["Adding machine", "Adding machines", "an "], + 10 : ["Clown car tire", "Clown car tires", "a "], + 11 : ["Air pump", "Air pumps", "an "], + 12 : ["Octopus ink", "Octopus inks", "some "], + 13 : ["Package", "Package", "a "], + 14 : ["Goldfish receipt", "Goldfish receipts", "a "], + 15 : ["Goldfish", "Goldfish", "a "], + 16 : ["Oil", "Oils", "some "], + 17 : ["Grease", "Greases", "some "], + 18 : ["Water", "Waters", "some "], + 19 : ["Gear report", "Gear reports", "a "], + 20 : ["Blackboard Eraser", "Blackboard Erasers", "a "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 110 : ["TIP Clothing Ticket", "Clothing Tickets", "a "], + 1000 : ["Clothing Ticket", "Clothing Tickets", "a "], + + # Donald's Dock quest items + 2001 : ["Inner Tube", "Inner Tubes", "an "], + 2002 : ["Monocle Prescription", "Monocle Prescriptions", "a "], + 2003 : ["Eyeglass Frames", "Eyeglass Frames", "some "], + 2004 : ["Monocle", "Monocles", "a "], + 2005 : ["Big White Wig", "Big White Wigs", "a "], + 2006 : ["Bushel of Ballast", "Bushels of Ballast", "a "], + 2007 : ["Cog Gear", "Cog Gears", "a "], + 2008 : ["Sea Chart", "Sea Charts", "a "], + 2009 : ["Cruddy Clovis", "Cruddy Clovi", "a "], + 2010 : ["Clean Clovis", "Clean Clovi", "a "], + 2011 : ["Clock Spring", "Clock Springs", "a "], + 2012 : ["Counter Weight", "Counter Weights", "a "], + + # Minnie's Melodyland quest items + 4001 : ["Tina's Inventory", "Tina's Inventories", ""], + 4002 : ["Yuki's Inventory", "Yuki's Inventories", ""], + 4003 : ["Inventory Form", "Inventory Forms", "an "], + 4004 : ["Fifi's Inventory", "Fifi's Inventories", ""], + 4005 : ["Lumber Jack's Ticket", "Lumber Jack's Tickets", ""], + 4006 : ["Tabitha's Ticket", "Tabitha's Tickets", ""], + 4007 : ["Barry's Ticket", "Barry's Tickets", ""], + 4008 : ["Cloudy Castanet", "Cloudy Castanets", ""], + 4009 : ["Blue Squid Ink", "Blue Squid Ink", "some "], + 4010 : ["Clear Castanet", "Clear Castanets", "a "], + 4011 : ["Leo's Lyrics", "Leo's Lyrics", ""], + + # Daisy's Gardens quest items + 5001 : ["Silk necktie", "Silk neckties", "a "], + 5002 : ["Pinstripe Suit", "Pinstripe Suits", "a "], + 5003 : ["Pair of Scissors", "Pairs of Scissors", "a "], + 5004 : ["Postcard", "Postcards", "a "], + 5005 : ["Pen", "Pens", "a "], + 5006 : ["Inkwell", "Inkwells", "an "], + 5007 : ["Notepad", "Notepads", "a "], + 5008 : ["Office Lockbox", "Office Lockboxes", "an "], + 5009 : ["Bag of Bird Seed", "Bags of Bird Seed", "a "], + 5010 : ["Sprocket", "Sprockets", "a "], + 5011 : ["Salad", "Salads", "a "], + 5012 : ["Key to "+lDaisyGardens, "Keys to "+lDaisyGardens, "a "], + 5013 : [lSellbotHQ+" Blueprints", lSellbotHQ+" HQ Blueprints", "some "], + 5014 : [lSellbotHQ+" Memo", lSellbotHQ+" Memos", "a "], + 5015 : [lSellbotHQ+" Memo", lSellbotHQ+" Memos", "a "], + 5016 : [lSellbotHQ+" Memo", lSellbotHQ+" Memos", "a "], + 5017 : [lSellbotHQ+" Memo", lSellbotHQ+" Memos", "a "], + + # The Brrrgh quests + 3001 : ["Soccer ball", "Soccer balls", "a "], + 3002 : ["Toboggan", "Toboggans", "a "], + 3003 : ["Ice cube", "Ice cubes", "an "], + 3004 : ["Love letter", "Love letters", "a "], + 3005 : ["Wiener dog", "Wiener dogs", "a "], + 3006 : ["Engagement ring", "Engagement rings", "an "], + 3007 : ["Sardine whiskers", "Sardine whiskers", "some "], + 3008 : ["Calming potion", "Calming potion", "a "], + 3009 : ["Broken tooth", "Broken teeth", "a "], + 3010 : ["Gold tooth", "Gold teeth", "a "], + 3011 : ["Pine cone bread", "Pine cone breads", "a "], + 3012 : ["Lumpy cheese", "Lumpy cheeses", "some "], + 3013 : ["Simple spoon", "Simple spoons", "a "], + 3014 : ["Talking toad", "Talking toad", "a "], + 3015 : ["Ice cream cone", "Ice cream cones", "an "], + 3016 : ["Wig powder", "Wig powders", "some "], + 3017 : ["Rubber ducky", "Rubber duckies", "a "], + 3018 : ["Fuzzy dice", "Fuzzy dice", "some "], + 3019 : ["Microphone", "Microphones", "a "], + 3020 : ["Electric keyboard", "Electric keyboards", "an "], + 3021 : ["Platform shoes", "Platform shoes", "some "], + 3022 : ["Caviar", "Caviar", "some "], + 3023 : ["Make-up powder", "Make-up powders", "some "], + 3024 : ["Yarn", "Yarn", "some " ], + 3025 : ["Knitting Needle", "Knitting Needles", "a "], + 3026 : ["Alibi", "Alibis", "an "], + 3027 : ["External Temperature Sensor", "External Temperature Sensors", "an "], + + #Dreamland Quests + 6001 : ["Cashbot HQ Plans", "Cashbot HQ Plans", "some "], + 6002 : ["Rod", "Rods", "a "], + 6003 : ["Drive Belt", "Drive Belts", "a "], + 6004 : ["Pair of Pincers", "Pairs of Pincers", "a "], + 6005 : ["Reading Lamp", "Reading Lamps", "a "], + 6006 : ["Zither", "Zithers", "a "], + 6007 : ["Zamboni", "Zambonis", "a "], + 6008 : ["Zebra Zabuton", "Zebra Zabutons", "a "], + 6009 : ["Zinnias", "Zinnias", "some "], + 6010 : ["Zydeco Records", "Zydeco Records", "some "], + 6011 : ["Zucchini", "Zucchinis", "a "], + 6012 : ["Zoot Suit", "Zoot Suits", "a "], + + #Dreamland+1 quests + 7001 : ["Plain Bed", "Plain Beds", "a "], + 7002 : ["Fancy Bed", "Fancy Beds", "a "], + 7003 : ["Blue Bedspread", "Blue Bedspreads", "a "], + 7004 : ["Paisley Bedspread", "Paisley Bedspreads", "a "], + 7005 : ["Pillows", "Pillows", "some "], + 7006 : ["Hard Pillows", "Hard Pillows", "some "], + 7007 : ["Pajamas", "Pajamas", "a pair of "], + 7008 : ["Footie Pajamas", "Footie Pajamas", "a pair of "], + 7009 : ["Puce Footie Pajamas", "Puce Footie Pajamas", "a pair of "], + 7010 : ["Fuchsia Footie Pajamas", "Fuchsia Footie Pajamas", "a pair of "], + 7011 : ["Cauliflower Coral", "Cauliflower Coral", "some "], + 7012 : ["Slimy Kelp", "Slimy Kelp", "some "], + 7013 : ["Pestle", "Pestles", "a "], + 7014 : ["Jar of Wrinkle Cream", "Jars of Wrinkle Cream", "a "], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "in any neighborhood" + +QuestsTailorFillin = "Tailor" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Clothing Store" +QuestsTailorLocationNameFillin = "in any neighborhood" +QuestsTailorQuestSCString = "I need to see a Tailor." + +QuestMovieQuestChoiceCancel = "Come back later if you need a ToonTask! Bye!" +QuestMovieTrackChoiceCancel = "Come back when you are ready to decide! Bye!" +QuestMovieQuestChoice = "Choose a ToonTask." +QuestMovieTrackChoice = "Ready to decide? Choose a track, or come back later." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Now you are ready.\aGo out and walk the earth until you know which track you would like to choose.\aChoose wisely, because this is your final track.\aWhen you are certain, return to me.", + INCOMPLETE_PROGRESS : "Choose wisely.", + INCOMPLETE_WRONG_NPC : "Choose wisely.", + COMPLETE : "Very wise choice!", + LEAVING : "Good luck. Return to me when you have mastered your new skill.", + } + +QuestDialog_3225 = { + QUEST : "Oh, thanks for coming, _avName_!\aThe Cogs in the neighborhood frightened away my delivery person.\aI don't have anyone to deliver this salad to _toNpcName_!\aCan you do it for me? Thanks so much!_where_" + } + +QuestDialog_2910 = { + QUEST : "Back so soon?\aGreat job on the spring.\aThe final item is a counter weight.\aStop by and see _toNpcName_ and bring back whatever you can get._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "Ok, now I think you are ready for something more rewarding.\aIf you can defeat 3 Bossbots I'll give you a little bonus.", + INCOMPLETE_PROGRESS : TheCogs+" are out in the streets, through the tunnels.", + INCOMPLETE_WRONG_NPC : "Good job defeating those Cogs. Now go to the Toon Headquarters for your next step!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "Ok, now I think you are ready for something more rewarding.\aCome back after you defeat 3 Lawbots and I'll have a little something for you.", + INCOMPLETE_PROGRESS : TheCogs+" are out in the streets, through the tunnels.", + INCOMPLETE_WRONG_NPC : "Good job defeating those Cogs. Now go to the Toon Headquarters for your next step!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "Ok, now I think you are ready for something more rewarding.\aDefeat 3 Cashbots and come back here to claim the bounty.", + INCOMPLETE_PROGRESS : TheCogs+" are out in the streets, through the tunnels.", + INCOMPLETE_WRONG_NPC : "Good job defeating those Cogs. Now go to the Toon Headquarters for your next step!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "Ok, now I think you are ready for something more rewarding.\aCome see us after you defeat 3 Sellbots and we'll hook you up.", + INCOMPLETE_PROGRESS : TheCogs+" are out in the streets, through the tunnels.", + INCOMPLETE_WRONG_NPC : "Good job defeating those Cogs. Now go to the Toon Headquarters for your next step!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "You look like you could use some new gags.\aGo see %s, maybe he can help you out._where_" % Flippy }, + 165 : {QUEST : "Hi there.\aLooks like you need to practice training your gags.\aEvery time you hit a Cog with one of your gags, your experience increases.\aWhen you get enough experience, you will be able to use an even better gag.\aGo practice your gags by defeating 4 Cogs."}, + 166 : {QUEST : "Nice work defeating those Cogs.\aYou know, the Cogs come in four different types.\aThey are Lawbots, Cashbots, Sellbots, and Bossbots.\aYou can tell them apart by their coloring and their name labels.\aFor practice go defeat 4 Bossbots."}, + 167 : {QUEST : "Nice work defeating those Cogs.\aYou know, the Cogs come in four different types.\aThey are Lawbots, Cashbots, Sellbots, and Bossbots.\aYou can tell them apart by their coloring and their name labels.\aFor practice go defeat 4 Lawbots."}, + 168 : {QUEST : "Nice work defeating those Cogs.\aYou know, the Cogs come in four different types.\aThey are Lawbots, Cashbots, Sellbots, and Bossbots.\aYou can tell them apart by their coloring and their name labels.\aFor practice go defeat 4 Sellbots."}, + 169 : {QUEST : "Nice work defeating those Cogs.\aYou know, the Cogs come in four different types.\aThey are Lawbots, Cashbots, Sellbots, and Bossbots.\aYou can tell them apart by their coloring and their name labels.\aFor practice go defeat 4 Cashbots."}, + 170 : {QUEST : "Nice work, now you know the difference between the 4 types of Cogs.\aI think you are ready to start training for your third gag track.\aGo talk to _toNpcName_ to choose your next gag track - he can give you some expert advice._where_" }, + 171 : {QUEST : "Nice work, now you know the difference between the 4 types of Cogs.\aI think you are ready to start training for your third gag track.\aGo talk to _toNpcName_ to choose your next gag track - he can give you some expert advice._where_" }, + 172 : {QUEST : "Nice work, now you know the difference between the 4 types of Cogs.\aI think you are ready to start training for your third gag track.\aGo talk to _toNpcName_ to choose your next gag track - she can give you some expert advice._where_" }, + + 175 : {GREETING : "", + QUEST : "Did you know you have your very own Toon house?\aClarabelle Cow runs a phone catalog where you can order furniture to decorate your house.\aYou can also buy SpeedChat phrases, clothing, and other fun things!\aI'll tell Clarabelle to send you your first catalog now.\aYou get a catalog with new items every week!\aGo to your home and use your phone to call Clarabelle.", + INCOMPLETE_PROGRESS : "Go home and use your phone to call Clarabelle.", + COMPLETE : "Hope you have fun ordering things from Clarabelle!\aI just finished redecorating my house. It looks Toontastic!\aKeep doing ToonTasks to get more rewards!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Throw and Squirt are great, but you will need more gags to fight higher level Cogs.\aWhen you team up with other Toons against the Cogs, you can combine attacks for even more damage.\aTry different combinations of gags to see what works best.\aFor your next track, choose between Sound and Toonup.\aSound is special because when it hits, it damages all Cogs.\aToonup lets you heal other Toons in battle.\aWhen you are ready to decide, come back here and choose.", + INCOMPLETE_PROGRESS : "Back so soon? Okay, are you ready to choose?", + INCOMPLETE_WRONG_NPC : "Think about your decision before choosing.", + COMPLETE : "Good decision. Now before you can use those gags, you must train for them.\aYou must complete a series of ToonTasks for training.\aEach task will give you a single frame of your gag attack animation.\aWhen you collect all 15, you can get the Final Gag Training task that will allow you to use your new gags.\aYou can check your progress in the Shticker Book.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Visit _toNpcName_ if you want to get around town more easily._where_" }, + 1040 : { QUEST : "Visit _toNpcName_ if you want to get around town more easily._where_" }, + 1041 : { QUEST : "Hi! What brings you here?\aEverybody uses their portable hole to travel around Toontown.\aWhy, you can teleport to your friends using the Friends List, or to any neighborhood using the map in the Shticker Book.\aOf course, you have to earn that!\aSay, I can turn on your teleport access to "+lToontownCentral+" if you help out a friend of mine.\aSeems the Cogs are causing trouble over on Loopy Lane. Go visit _toNpcName_._where_" }, + 1042 : { QUEST : "Hi! What brings you here?\aEverybody uses their portable hole to travel around Toontown.\aWhy, you can teleport to your friends using the Friends List, or to any neighborhood using the map in the Shticker Book.\aOf course, you have to earn that!\aSay, I can turn on your teleport access to "+lToontownCentral+" if you help out a friend of mine.\aSeems the Cogs are causing trouble over on Loopy Lane. Go visit _toNpcName_._where_" }, + 1043 : { QUEST : "Hi! What brings you here?\aEverybody uses their portable hole to travel around Toontown.\aWhy, you can teleport to your friends using the Friends List, or to any neighborhood using the map in the Shticker Book.\aOf course, you have to earn that!\aSay, I can turn on your teleport access to "+lToontownCentral+" if you help out a friend of mine.\aSeems the Cogs are causing trouble over on Loopy Lane. Go visit _toNpcName_._where_" }, + 1044 : { QUEST : "Oh, thanks for stopping by. I really need some help.\aAs you can see, I have no customers.\aMy secret recipe book is lost and nobody comes to my restaurant anymore.\aI last saw it just before those Cogs took over my building.\aCan you help me by recovering four of my famous recipes?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my recipes?" }, + 1045 : { QUEST : "Thank you so much!\aBefore long I will have the entire collection and can reopen my restaurant.\aOh, I have a note here for you - something about teleport access?\aIt says thanks for helping my friend and to deliver this to Toon Headquarters.\aWell, thanks indeed - bye!", + LEAVING : "", + COMPLETE : "Ah, yes, says here you have been a great help to some of the fine folks out on Loopy Lane.\aSays you need teleport access to "+lToontownCentral+".\aWell, consider it done.\aNow you can teleport back to the playground from almost anywhere in Toontown.\aJust open your map and click on "+lToontownCentral+"." }, + 1046 : { QUEST : "The Cashbots have really been bothering the Funny Money Savings and Loan.\aStop by there and see if there is anything you can do._where_" }, + 1047 : { QUEST : "Cashbots have been sneaking into the bank and stealing our machines.\aPlease recover 5 adding machines from Cashbots.\aTo save you from running back and forth, just bring them all back at once.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Still looking for adding machines?" }, + 1048 : { QUEST : "Wow! Thanks for finding our adding machines.\aHm... They look a little damaged.\aSay, could you take them over to _toNpcName_ over at her shop, \"Tickle Machines\" on this street?\aSee if she can fix them.", + LEAVING : "", }, + 1049 : { QUEST : "What's that? Broken adding machines?\aCashbots you say?\aWell, let's have a look see...\aYep, gears are stripped, but I'm out of that part...\aYou know what might work - some Cog gears, large ones, from larger Cogs...\aLevel 3 Cog gears should do the trick. I'll need 2 for each machine, so 10 total.\aBring them back all at once and I'll fix em up!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Remember, I need 10 gears to fix the machines." }, + 1053 : { QUEST : "Ah yes, that should do the trick indeedy.\aAll fixed now, free of charge.\aTake these back to Funny Money, and tell 'im I said howdy.", + LEAVING : "", + COMPLETE : "Adding machines all fixed up?\aNice work. I'm sure I've got something around here to reward you with..." }, + 1054 : { QUEST : "_toNpcName_ needs some help with his clown cars._where_" }, + 1055 : { QUEST : "Yowza! I can't find the tires to this here clown car anywhere!\aDo ya think you could help me out?\aI think Loopy Bob may have tossed them in the pond in the "+lToontownCentral+" playground.\aIf you stand on one of the docks there you can try and fish out the tires for me.", + GREETING : "Woohoo!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Are you having trouble fishing out all 4 tires?" }, + 1056 : { QUEST : "Fan-flying-tastic! Now I can get this old clown car on the road again!\aHey, I thought I had an air pump around here to inflate these tires...\aMaybe _toNpcName_ borrowed it?\aCould you go ask for it back for me?_where_", + LEAVING : "" }, + 1057 : { QUEST : "Hi there.\aA tire pump you say?\aI'll tell you what - you help clean up the streets of some of those high level Cogs for me...\aAnd I'll let you have the tire pump.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Is that the best you can do?" }, + 1058 : { QUEST : "Good job - I knew you could do it.\aHere's the pump. I'm sure _toNpcName_ will be glad to get it back.", + LEAVING : "", + GREETING : "", + COMPLETE : "Yeehaw! Now I'm good to go!\aBy the way, thanks for helping me out.\aHere, take this." }, + 1059 : { QUEST : "_toNpcName_ is running low on supplies. Maybe you can give him a hand?_where_" }, + 1060 : { QUEST : "Thanks for stopping by!\aThose Cogs have been stealing my ink, so I'm running very low.\aCould you fish some octopus ink out of the pond for me?\aJust stand on a dock near the pond to fish.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Are you having trouble fishing?" }, + 1061 : { QUEST : "Great - thanks for the ink!\aYou know what, maybe if you cleared away some of those Pencil Pushers...\aI wouldn't run out of ink again so quickly.\aDefeat 6 Pencil Pushers in "+lToontownCentral+" for your reward.", + LEAVING : "", + COMPLETE : "Thanks! Let me reward you for your help.", + INCOMPLETE_PROGRESS : "I just saw some more Pencil Pushers." }, + 1062 : { QUEST : "Great - thanks for the ink!\aYou know what, maybe if you cleared away some of those Bloodsuckers...\aI wouldn't run out of ink again so quickly.\aDefeat 6 Bloodsuckers in "+lToontownCentral+" for your reward.", + LEAVING : "", + COMPLETE : "Thanks! Let me reward you for your help.", + INCOMPLETE_PROGRESS : "I just saw some more Bloodsuckers." }, + 900 : { QUEST : "I hear _toNpcName_ needs help with a package._where_" }, + 1063 : { QUEST : "Hi - thanks for coming in.\aA Cog stole a very important package from right under my nose.\aPlease see if you can get it back. I think he was a level 3...\aSo, defeat level 3 Cogs until you find my package.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding the package, huh?" }, + 1067 : { QUEST : "That's it, all right!\aHey, the address is smudged...\aAll I can read is that it's for a Dr. - the rest is all blurry.\aMaybe it's for _toNpcName_? Could you take it to him?_where_", + LEAVING : "" }, + 1068 : { QUEST : "I wasn't expecting a package. Maybe it's for Dr. I.M. Euphoric?\aMy assistant was going over there today anyway, so I'll have him check for you.\aIn the meantime, would you mind getting rid of some of the Cogs on my street?\aDefeat 10 Cogs in "+lToontownCentral+".", + LEAVING : "", + INCOMPLETE_PROGRESS : "My assistant isn't back yet." }, + 1069 : { QUEST : "Dr. Euphoric says he wasn't expecting a package either.\aUnfortunately, a Cashbot stole it from my assistant on the way back.\aCould you try and get it back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding the package, huh?" }, + 1070 : { QUEST : "Dr. Euphoric says he wasn't expecting a package either.\aUnfortunately, a Sellbot stole the package from my assistant on the way back.\aI'm sorry, but you'll have to find that Sellbot and get it back.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding the package, huh?" }, + 1071 : { QUEST : "Dr. Euphoric says he wasn't expecting a package either.\aUnfortunately, a Bossbot stole it from my assistant on the way back.\aCould you try and get it back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding the package, huh?" }, + 1072 : { QUEST : "Great - you got it back!\aMaybe you should try _toNpcName_, it could be for him._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, thanks for bringing me my packages.\aWait a second, I was expecting two. Could you check with _toNpcName_ and see if he has the other one?", + INCOMPLETE : "Were you able to find my other package?", + LEAVING : "" }, + 1074 : { QUEST : "He said there was another package? Maybe the Cogs stole it too.\aDefeat Cogs until you find the second package.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding the other package, huh?" }, + 1075 : { QUEST : "I guess there was a second package after all!\aHurry and take it over to _toNpcName_ with my apologies.", + COMPLETE : "Hey, my package is here!\aSince you seem to be such a helpful Toon, this should come in handy.", + LEAVING : "" }, + 1076 : { QUEST : "There's been some trouble over at 14 Karat Goldfish.\a_toNpcName_ could probably use a hand._where_" }, + 1077 : { QUEST : "Thanks for coming - the Cogs stole all my goldfish.\aI think the Cogs want to sell them to make a quick buck.\aThose 5 fish have been my only companions in this tiny store for so many years...\aIf you could get them back for me I'd really appreciate it.\aI'm sure one of the Cogs has my fish.\aDefeat Cogs until you find my goldfish.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Please return my goldfish to me." }, + 1078 : { QUEST : "Oh, you have my fish!\aHuh? What's this - a receipt?\aSigh, I guess they are Cogs, after all.\aI can't make heads or tails out of this receipt. Could you take it to _toNpcName_ and see if he can read it?_where_", + INCOMPLETE : "What did _toNpcName_ have to say about the receipt?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, let me see that receipt.\a...Ah Yes, it says that 1 goldfish was sold to a Flunky.\aIt doesn't seem to mention what happened to the other 4 fish.\aMaybe you should try and find that Flunky.", + LEAVING : "", + INCOMPLETE_PROGRESS : "I don't think there's anything else I can help you with.\aWhy don't you try and find that goldfish?" }, + 1092 : { QUEST : "Mmm, let me see that receipt.\a...Ah Yes, it says that 1 goldfish was sold to a Short Change.\aIt doesn't seem to mention what happened to the other 4 fish.\aMaybe you should try and find that Short Change.", + LEAVING : "", + INCOMPLETE_PROGRESS : "I don't think there's anything else I can help you with.\aWhy don't you try and find that goldfish?" }, + 1080 : { QUEST : "Oh thank heavens! You found Oscar - he's my favorite.\aWhat's that, Oscar? Uh huh... they did? ... they are?\aOscar says the other 4 escaped into the pond in the playground.\aCould you go round them up for me?\aJust fish them out of the pond.", + LEAVING : "", + COMPLETE : "Ahh, I am sooo happy! To be reunited with my little buddies!\aYou deserve a handsome reward for this!", + INCOMPLETE_PROGRESS : "Are you having trouble finding those fish?" }, + 1081 : { QUEST : "_toNpcName_ appears to be in a sticky situation. He sure could use a hand._where_" }, + 1082 : { QUEST : "I spilled quick dry glue and I'm stuck - stuck cold!\aIf there were a way out, I sure would be sold.\aThat gives me an idea, if you are feeling loyal.\aDefeat some Sellbots and bring back some oil.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Can you help me get un-stuck?" }, + 1083 : { QUEST : "Well, oil helped a little, but I still cannot budge,\aWhat else would help? It's hard to judge.\aThat gives me an idea; it's worth a try at least.\aDefeat some Lawbots and bring back some grease.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Can you help me get un-stuck?" }, + 1084 : { QUEST : "Nope, that didn't help. This is really not funny.\aI put the grease right there on the money,\aThat gives me an idea, before I forget it.\aDefeat some Cashbots; bring back water to wet it.", + LEAVING : "", + GREETING : "", + COMPLETE : "Hooray, I'm free of this quick drying glue,\aAs a reward I give this gift to you,\aYou can laugh a little longer while battling and then...\aOh, no! I'm already stuck here again!", + INCOMPLETE_PROGRESS : "Can you help me get un-stuck?" }, + 1085 : { QUEST : "_toNpcName_ is conducting some research on the Cogs.\aGo talk to him if you want to help out._where_" }, + 1086 : { QUEST : "That's right, I'm conducting a study of the Cogs.\aI want to know what makes them tick.\aIt sure would help me if you could gather some gears from Cogs.\aMake sure they're from at least level 2 Cogs so they're big enough to examine.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Can't find enough gears?" }, + 1089 : { QUEST : "Okay, let's take a look. These are excellent specimens!\aMmmm...\aOkay, here's my report. Take this back to Toon Headquarters right away.", + INCOMPLETE : "Have you delivered my report to Headquarters?", + COMPLETE : "Good work _avName_, we'll take this one from here.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ has some useful information for you._where_" }, + 1091 : { QUEST : "I hear that Toon Headquarters is working on a sort of Cog Radar.\aIt will let you see where the Cogs are so that it will be easier to find them.\aThat Cog Page in your Shticker Book is the key.\aBy defeating enough Cogs, you can tune in to their signals and actually track where they are.\aKeep defeating Cogs, so you will be ready.", + COMPLETE : "Good work! You could probably use this...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Now you get to choose the next gag track you want to learn.\aTake your time deciding, and come back here when you are ready to choose.", + INCOMPLETE_PROGRESS : "Think about your decision before choosing.", + INCOMPLETE_WRONG_NPC : "Think about your decision before choosing.", + COMPLETE : "A wise decision...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Those sneaky Cogs are at it again.\a_toNpcName_ has reported another missing item. Stop by and see if you can straighten it out._where_" }, + 2202 : { QUEST : "Hi, _avName_. Thank goodness you're here. A mean looking Penny Pincher was just in here and he made off with an inner tube.\aI fear they may use it for their vile purposes.\aPlease see if you can find him and bring it back.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my inner tube?", + COMPLETE : "You found my inner tube! You ARE good. Here, take your reward...", + }, + 2203 : { QUEST : TheCogs+" are wreaking havoc over at the bank.\aGo see Captain Carl and see what you can do._where_" }, + 2204 : { QUEST : "Welcome aboard, matey.\aArgh! Those rapscallion Cogs smashed my monocle and I can't sort me change without it.\aBe a good landlubber and take this prescription to _toNpcName_ and fetch me a new one._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "What's this?\aOh, I'd love to fill this prescription but the Cogs have been pilfering my supplies.\aIf you can get me the eyeglass frames off a flunky I can probably help you out.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sorry. No flunky frames, no monocle.", + }, + 2206: { QUEST : "Excellent!\aJust a second...\aYour prescription is filled. Please take this monocle straight to Captain Carl._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Avast Ye!\aYou're gonna earn your sea legs after all.\aHere ye be.", + }, + 2207 : { QUEST : "Barnacle Barbara has a Cog in her shop!\aYou'd better get over there pronto._where_" }, + 2208 : { QUEST : "Gosh! You just missed him, sweetie.\aThere was a Back Stabber in here. He took my big white wig.\aHe said it was for his boss and something about 'legal precedent.'\aIf you can get it back I'd be forever grateful.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Still haven't found him?\aHe's tall and has a pointy head", + COMPLETE : "You found it!?!?\aAren't you a darling!\aYou've more than earned this...", + }, + 2209 : { QUEST : "Melville is preparing for an important voyage.\aPop in and see what you can do to help sort him out._where_"}, + 2210 : { QUEST : "I can use your help.\aI've been asked by Toon HQ to take a voyage and see if I can find where the Cogs are coming from.\aI'll need a few things for my ship but I don't have many jellybeans.\aStop by and pick up some ballast from Alice. You'll have to do a favor for her to get it._where_", + GREETING : "Howdy, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "So Melville wants ballast, does he?\aHe still owes me for the last bushel.\aI'll give it to you if you can clear five Micromanagers off my street.", + INCOMPLETE_PROGRESS : "No, silly! I said FIVE micromanagers...", + GREETING : "What can I do for you?", + LEAVING : "", + }, + 2212 : { QUEST : "A deal's a deal.\aHere's your ballast for that cheapskate Melville._where_", + GREETING : "Well, look what the cat dragged in...", + LEAVING : "", + }, + 2213 : { QUEST : "Excellent work. I knew she'd be reasonable.\aNext I'll need a sailing chart from Art.\aI don't think my credit is good there either so you'll have to work something out with him._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Yes, I have the sea chart Melville wants.\aAnd if you're willing to work for it I'll let you have it.\aI'm trying to build an astrolabe to navigate by the stars.\aI could use three Cog gears to build it.\aCome back when you've found them.", + INCOMPLETE_PROGRESS: "How's it coming with those Cog gears?", + GREETING : "Welcome!", + LEAVING : "Good luck!", + }, + 2215 : { QUEST : "Ooh! These gears will do rather nicely.\aHere's the chart. Give it to Melville with my compliments._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Well, that just about does it. I'm ready to sail!\aI'd take you with me if you weren't so green. Take this instead.", + }, + 901 : { QUEST : "If you're up for it Ahab could use some assistance over at his place..._where_", + }, + 2902 : { QUEST : "Are you the new recruit?\aGood, good. Maybe you can help me.\aI'm building a giant prefab crab to confuse the Cogs.\aI could use a clovis though. Go see Claggart and bring one back, please._where_", + }, + 2903 : { QUEST : "Hi there!\aYes, I heard about the giant crab Ahab's working on.\aThe best clovis I have is a little on the dirty side though.\aBe a sport and run it by the cleaners for me before you drop it off._where_", + LEAVING : "Thanks!" + }, + 2904 : { QUEST : "You must be the one that Claggart sent over.\aI think I can clean that up in short order.\aJust a minute...\aThere you are. Good as new!\aTell Ahab I said hello._where_", + }, + 2905 : { QUEST : "Ah, now this is exactly what I was looking for.\aWhile you're here, I'm also going to need a very large clock spring.\aTake a walk over to Hook's place and see if he has one._where_", + }, + 2906 : { QUEST : "A large spring, eh?\aI'm sorry but the largest spring I have is still quite small.\aPerhaps I could assemble one out of squirt gun trigger springs.\aBring me three of these gags and I'll see what I can do.", + }, + 2907 : { QUEST : "Let's have a look then...\aSmashing. Simply Smashing.\aSometimes I even surprise myself.\aHere you go: one large spring for Ahab!_where_", + LEAVING : "Bon Voyage!", + }, + 2911 : { QUEST : "I'd be happy to help the cause, _avName_.\aBut I'm afraid the streets are no longer safe.\aWhy don't you go take out some Cashbot Cogs and we'll talk.", + INCOMPLETE_PROGRESS : "I still think you need to make the streets safer.", + }, + 2916 : { QUEST : "Yes, I have a weight that Ahab can have.\aI think it would be safer if you defeated a couple sellbots first though.", + INCOMPLETE_PROGRESS : "Not yet. Defeat some more sellbots.", + }, + 2921 : { QUEST : "Hmmm, I suppose I could give up a weight.\aI'd feel a lot better about it if there weren't so many Bossbot Cogs creeping around.\aDefeat six and then come see me.", + INCOMPLETE_PROGRESS : "I don't think its safe yet...", + }, + 2925 : { QUEST : "All done?\aWell, I guess it's safe enough now.\aHere's the counter weight for Ahab._where_" + }, + 2926 : {QUEST : "Well, that's everything.\aLet's see if it works.\aHmmm, one small problem.\aI'm not getting any power because that Cog building is blocking my solar panel.\aCould you retake it for me?", + INCOMPLETE_PROGRESS : "Still no power. How about that building?", + COMPLETE : "Super! You are one heck of a Cog crusher! Here, take this as your reward...", + }, + 3200 : { QUEST : "I just got a call in from _toNpcName_.\aHe's having a hard day. Maybe you can help him out!\aDrop by and see what he needs._where_" }, + 3201 : { QUEST : "Oh, thanks for coming!\aI need someone to take this new silk tie to _toNpcName_.\aWould you be able to do that for me?_where_" }, + 3203 : { QUEST : "Oh, this must be the tie I ordered! Thanks!\aIt matches a pinstripe suit I just finished, right over here.\aHey, what happened to that suit?\aOh no! The Cogs must have stolen my new suit!\aDefeat Cogs until you find my suit, and bring it back to me.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Have you found my suit yet? I'm sure the Cogs took it!", + COMPLETE : "Hooray! You found my new suit!\aSee, I told you the Cogs had it! Here is your reward...", + }, + + 3204 : { QUEST : "_toNpcName_ just called to report a theft.\aWhy don't you stop by and see if you can sort things out?_where_" }, + 3205 : { QUEST : "Hello, _avName_! Have you come to help me?\aI just chased a Bloodsucker out of my shop. Whew! That was scary.\aBut now I can't find my scissors anywhere! I'm sure that Bloodsucker took them.\aFind that Bloodsucker, and recover my scissors for me.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Are you still looking for my scissors?", + COMPLETE : "My scissors! Thank you so much! Here is your reward...", + }, + + 3206 : { QUEST : "It sounds like _toNpcName_ is having problems with some Cogs.\aGo see if you can help him out._where_" }, + 3207 : { QUEST : "Hi, _avName_! Thanks for coming by!\aA bunch of Double Talkers just broke in and stole a stack of postcards from my counter.\aPlease go out and defeat all those Double Talkers to get my postcards back!", + INCOMPLETE_PROGRESS : "That's not enough postcards! Keep looking!", + COMPLETE : "Oh, thank you! Now I can deliver the mail on time! Here is your reward...", + }, + + 3208 : { QUEST : "We've been getting complaints from the residents lately about all of the Cold Callers.\aSee if you can defeat 10 Cold Callers to help out your fellow Toons in "+lDaisyGardens+"." }, + 3209 : { QUEST : "Thanks for taking care of those Cold Callers!\aBut now the Telemarketers have gotten out of hand.\aDefeat 10 Telemarketers in "+lDaisyGardens+" and come back here for your reward." }, + + 3247 : { QUEST : "We've been getting complaints from the residents lately about all of the Bloodsuckers.\aSee if you can defeat 20 Bloodsuckers to help out your fellow Toons in "+lDaisyGardens+"." }, + + + 3210 : { QUEST : "Oh no, The Squirting Flower on Maple Street just ran out of flowers!\aTake them ten of your own squirting flowers to help out.\aMake sure you have 10 squirting flowers in your inventory first.", + LEAVING: "", + INCOMPLETE_PROGRESS : "I need to have 10 squirting flowers. You don't have enough!" }, + 3211 : { QUEST : "Oh, thank you so much! Those squirting flowers will save the day.\aBut I'm scared of the Cogs outside.\aCan you help me out and defeat some of those Cogs?\aCome back to me after you have defeated 20 Cogs on this street.", + INCOMPLETE_PROGRESS : "There are still Cogs out there to defeat! Keep it up!", + COMPLETE : "Oh, thank you! That helps a lot. Your reward is...", + }, + + 3212 : { QUEST : "_toNpcName_ needs some help looking for something she lost.\aGo visit her and see what you can do._where_" }, + 3213 : { QUEST : "Hi, _avName_. Can you help me?\aI seem to have misplaced my pen. I think maybe some Cogs took it.\aDefeat Cogs to find my stolen pen.", + INCOMPLETE_PROGRESS : "Have you found my pen yet?" }, + 3214 : { QUEST : "Yes, that's my pen! Thanks so much!\aBut while you were gone I realized my inkwell was missing too.\aDefeat Cogs to find my inkwell.", + INCOMPLETE_PROGRESS : "I'm still looking for my inkwell!" }, + 3215 : { QUEST : "Great! Now I have my pen and my inkwell back!\aBut wouldn't you know it?\aMy notepad is gone! They must have stolen it too!\aDefeat Cogs to find my stolen notepad, and then bring it back for your reward.", + INCOMPLETE_PROGRESS : "Any word on that notepad yet?" }, + 3216 : { QUEST : "That's my notepad! Hooray! Your reward is...\aHey! Where did it go?\aI had your reward right here in my office lockbox. But the whole lockbox is gone!\aCan you believe it? Those Cogs stole your reward!\aDefeat Cogs to recover my lockbox.\aWhen you bring it back to me I'll give you your reward.", + INCOMPLETE_PROGRESS : "Keep looking for that lockbox! It has your reward inside it!", + COMPLETE : "Finally! I had your new gag bag in that lockbox. Here it is...", + }, + + 3217 : { QUEST : "We've been performing some studies on Sellbot mechanics.\aWe still need to study some pieces more closely.\aBring us a sprocket from a Name Dropper.\aYou can catch one when the Cog is exploding." }, + 3218 : { QUEST : "Good job! Now we need a sprocket from a Glad Hander for comparison.\aThese sprockets are harder to catch, so keep trying." }, + 3219 : { QUEST : "Great! Now we need just one more sprocket.\aThis time, we need a sprocket from a Mover & Shaker.\aYou might need to look inside some Sellbot buildings to find these Cogs.\aWhen you catch one, bring it back for your reward." }, + + 3244 : { QUEST : "We've been performing some studies on Lawbot mechanics.\aWe still need to study some pieces more closely.\aBring us a sprocket from an Ambulance Chaser.\aYou can catch one when the Cog is exploding." }, + 3245 : { QUEST : "Good job! Now we need a sprocket from a Back Stabber for comparison.\aThese sprockets are harder to catch, so keep trying." }, + 3246 : { QUEST : "Great! Now we need just one more sprocket.\aThis time, we need a sprocket from a Spin Doctor.\aWhen you catch one, bring it back for your reward." }, + + 3220 : { QUEST : "I just heard that _toNpcName_ was asking around for you.\aWhy don't you drop by and see what she wants?_where_" }, + 3221 : { QUEST : "Hi, _avName_! There you are!\aI heard you were quite an expert in squirt attacks.\aI need someone to set a good example for all the Toons in "+lDaisyGardens+".\aUse your squirt attacks to defeat a bunch of Cogs.\aEncourage your friends to use squirt too.\aWhen you have defeated 20 Cogs, come back here for a reward!" }, + + 3222 : { QUEST : "It's time to demonstrate your Toonmanship.\aIf you successfully reclaim a number of Cog buildings, you'll earn the right to carry three quests.\aFirst, defeat any two Cog buildings.\aFeel free to call on your friends to help you out."}, + 3223 : { QUEST : "Great job on those buildings!\aNow, defeat two more buildings.\aThese buildings must be at least two stories high, or higher." }, + 3224 : { QUEST : "Fantastic!\aNow just defeat two more buildings.\aThese buildings must be at least three stories high.\aWhen you finish, come back for your reward!", + COMPLETE : "You did it, _avName_!\aYou demonstrated your superior Toonmanship.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ says she needs some help.\aWhy don't you go see what you can do to help out?_where_" }, + 3235 : { QUEST : "Oh, this is the salad I ordered!\aThank you for bringing it to me.\aAll those Cogs must have frightened away _toNpcName_'s regular delivery person again.\aWhy don't you do us a favor and defeat some of the Cogs out there?\aDefeat 10 Cogs in "+lDaisyGardens+" and then report back to _toNpcName_.", + INCOMPLETE_PROGRESS : "You're working on defeating Cogs for me?\aThat's wonderful! Keep up the good work!", + COMPLETE : "Oh, thank you so much for defeating those Cogs!\aNow maybe I can keep my regular delivery schedule.\aYour reward is...", + INCOMPLETE_WRONG_NPC : "Go tell _toNpcName_ about the Cogs you've defeated._where_" }, + + 3236 : { QUEST : "There are far too many Lawbots out there.\aYou can do your part to help!\aDefeat 3 Lawbot buildings." }, + 3237 : { QUEST : "Great job on those Lawbot buildings!\aBut now there are too many Sellbots!\aDefeat 3 Sellbot buildings, then come back for your reward." }, + + 3238 : { QUEST : "Oh no! A \"Mingler\" Cog has stolen the Key to "+lDaisyGardens+"!\aSee if you can recover it.\aRemember, The Mingler can be found only inside Sellbot buildings." }, + 3239 : { QUEST : "You found a key all right, but it isn't the right one!\aWe need the Key to "+lDaisyGardens+".\aKeep looking! A \"Mingler\" Cog still has it!" }, + + 3242 : { QUEST : "Oh no! A Legal Eagle Cog has stolen the Key to "+lDaisyGardens+"!\aSee if you can recover it.\aRemember, Legal Eagles can be found only inside Lawbot buildings." }, + 3243 : { QUEST : "You found a key all right, but it isn't the right one!\aWe need the Key to "+lDaisyGardens+".\aKeep looking! A Legal Eagle Cog still has it!" }, + + 3240 : { QUEST : "I've just heard from _toNpcName_ that a Legal Eagle stole a bag of his bird seed.\aDefeat Legal Eagles until you recover Bud's bird seed, and take it to him.\aLegal Eagles are only found inside Lawbot buildings._where_", + COMPLETE : "Oh, thank you so much for finding my bird seed!\aYour reward is...", + INCOMPLETE_WRONG_NPC : "Good job getting that bird seed back!\aNow take it to _toNpcName_._where_", + }, + + 3241 : { QUEST : "Some of the Cog buildings out there are getting too tall for our comfort.\aSee if you can bring down some of the tallest buildings.\aRescue 5 3-story buildings or taller and come back for your reward.", + }, + + 3250 : { QUEST : "Detective Lima over on Oak Street has heard some reports of a Sellbot Headquarters.\aHead over there and help her investigate.", + }, + 3251 : { QUEST : "There is something strange going on around here.\aThere are so many Sellbots!\aI've heard they have organized their own headquarters at the end of this street.\aHead down the street and see if you can get to the bottom of this.\aFind Sellbot Cogs in their headquarters, defeat 5 of them, and report back.", + }, + 3252 : { QUEST : "Ok, spill the beans.\aWhat's that you say?\aSellbot Headquarters?? Oh no!!! Something must be done.\aWe must notify Judge McIntosh - she'll know what to do.\aGo at once and tell her what you have found out. She's just down the street.", + }, + 3253 : { QUEST : "Yes, can I help you? I'm very busy.\aEh? Cog Headquarters?\aEh? Nonsense. That could never happen.\aYou must be mistaken. Preposterous.\aEh? Don't argue with me.\aOk then, bring back some proof.\aIf Sellbots really are building this Cog HQ, any Cog there will be carrying blueprints.\aCogs love paperwork, you know?\aDefeat Sellbots in there until you find blueprints.\aBring them back here and maybe I'll believe you.", + }, + 3254 : { QUEST : "You again, eh? Blueprints? You have them?\aLet me see those! Hmmm... A factory?\aThat must be where they are building the Sellbots... And what's this?\aYes, just what I suspected. I knew it all along.\aThey are building a Sellbot Cog Headquarters.\aThis is not good. Must make some phone calls. Very busy. Goodbye!\aEh? Oh yes, take these blueprints back to Detective Lima.\aShe can make more sense of them.", + COMPLETE : "What did Judge McIntosh say?\aWe were right? Oh no. Let's see those blueprints.\aHmmm... Looks like Sellbots constructed a factory with machinery for building Cogs.\aSounds very dangerous. Stay out until you have more Laff points.\aWhen you have more Laff points, we have much more to learn about Sellbot HQ.\aFor now, nice work, here is your reward.", + }, + + + 3255 : { QUEST : "_toNpcName_ is investigating Sellbot Headquarters.\aGo see if you can help._where_" }, + 3256 : { QUEST : "_toNpcName_ is investigating Sellbot Headquarters.\aGo see if you can help._where_" }, + 3257 : { QUEST : "_toNpcName_ is investigating Sellbot Headquarters.\aGo see if you can help._where_" }, + 3258 : { QUEST : "There is much confusion about what the Cogs are up to in their new headquarters.\aI need you to bring back some information directly from them.\aIf we can get four internal memos from Sellbots inside their HQ, that will clear things up.\aBring back your first memo to me so we can learn more.", + }, + 3259 : { QUEST : "Great! This let's see what the memo says....\a\"Attn Sellbots:\"\a\"I'll be in my office at the top of Sellbot Towers promoting Cogs to higher levels.\"\a\"When you earn enough merits enter the elevator in the lobby to see me.\"\a\"Break time's over - back to work!\"\a\"Signed, Sellbot V.P.\"\aAha.... Flippy will want to see this. I'll send it to him right now.\aPlease go get your second memo and bring it back.", + }, + 3260 : { QUEST : "Oh good, you're back. Let's see what you found....\a\"Attn Sellbots:\"\a\"Sellbot Towers has installed a new security system to keep all Toons out.\"\a\"Toons caught in Sellbot Towers will be detained for questioning.\"\a\"Please meet in the lobby for appetizers to discuss.\"\a\"Signed, Mingler\"\aVery interesting... I'll pass on this information immediately.\aPlease bring a third memo back.", + }, + 3261 : { QUEST : "Excellent job _avName_! What does the memo say?\a\"Attn Sellbots:\"\a\"Toons have somehow found a way to infiltrate Sellbot Towers.\"\a\"I'll call you tonight during dinner to give you the details.\"\a\"Signed, Telemarketer\"\aHmmm... I wonder how Toons are breaking in....\aPlease bring back one more memo and I think we'll have enough info for now.", + COMPLETE : "I knew you could do it! Ok, the memo says....\a\"Attn Sellbots:\"\a\"I was having lunch with Mr. Hollywood yesterday.\"\a\"He reports that the V.P. is very busy these days.\"\a\"He will only be taking appointments from Cogs that deserve a promotion.\"\a\"Forgot to mention, Gladhander is golfing with me on Sunday.\"\a\"Signed, Name Dropper\"\aWell... _avName_, this has been very helpful.\aHere is your reward.", + }, + + 3262 : { QUEST : "_toNpcName_ has some new information about the Sellbot HQ Factory.\aGo see what he's got._where_" }, + 3263 : { GREETING : "Hi buddy!", + QUEST : "I'm Coach Zucchini, but you can just call me Coach Z.\aI put the \"squash\" in squash and stretch, if you know what I mean.\aListen, Sellbots have finished an enormous factory to pump out Sellbots 24 hours a day.\aGet a group of Toon buddies together and squash the factory!\aInside Sellbot HQ, look for the tunnel to the Factory then board the Factory elevator.\aMake sure you have full gags, full Laff points, and some strong Toons as guides.\aDefeat the Foreman inside the factory to slow the Sellbot progress.\aSounds like a real workout, if you know what I mean.", + LEAVING : "See ya buddy!", + COMPLETE : "Hey buddy, nice work on that Factory!\aLooks like you found part of a Cog suit.\aIt must be left over from their Cog manufacturing process.\aThat may come in handy. Keep collecting these when you have spare time.\aMaybe when you collect an entire Cog suit it could be useful for something....", + }, + + 4001 : {GREETING : "", + QUEST : "Now you get to choose the next gag track you want to learn.\aTake your time deciding, and come back here when you are ready to choose.", + INCOMPLETE_PROGRESS : "Think about your decision before choosing.", + INCOMPLETE_WRONG_NPC : "Think about your decision before choosing.", + COMPLETE : "A wise decision...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Now you get to choose the next gag track you want to learn.\aTake your time deciding, and come back here when you are ready to choose.", + INCOMPLETE_PROGRESS : "Think about your decision before choosing.", + INCOMPLETE_WRONG_NPC : "Think about your decision before choosing.", + COMPLETE : "A wise decision...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "I bet Tom could use some help with some research he's doing._where_", + }, + 4201 : { GREETING: "Howdy!", + QUEST : "I'm very concerned about a rash of musical instrument theft.\aI'm conducting a survey among my fellow merchants.\aPerhaps I can find a pattern to help me crack this case.\aStop by and ask Tina for a concertina inventory._where_", + }, + 4202 : { QUEST : "Yes, I talked to Tom this morning.\aI have the inventory right here.\aBring it right back to him, ok?_where_" + }, + 4203 : { QUEST : "Great! One down...\aNow swing by and get Yuki's._where_", + }, + 4204 : { QUEST : "Oh! The inventory!\aI forgot all about it.\aI bet I can have it done by the time you defeat 10 Cogs.\aStop in after that and I promise it will be ready.", + INCOMPLETE_PROGRESS : "31, 32... DOH!\aYou made me lose count!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, there you are.\aThanks for giving me some time.\aTake this to Tom and tell him I said Hello._where_", + }, + 4206 : { QUEST : "Hmmm, very interesting.\aNow we are getting somewhere.\aOk, the last inventory is Fifi's._where_", + }, + 4207 : { QUEST : "Inventory?\aHow can I do an inventory if I don't have the form?\aGo see Cleff and see if he has one for me._where_", + INCOMPLETE_PROGRESS : "Any sign of that form yet?", + }, + 4208 : { QUEST : "Sure I got an inventory form, mon!\aBut dey ain't free, you know.\aI'll tell you woht. I trade you for a whole cream pie.", + GREETING : "Hey, mon!", + LEAVING : "Cool runnings...", + INCOMPLETE_PROGRESS : "A slice won't do.\aI be hungry, mon. I need de WHOLE pie.", + }, + 4209 : { GREETING : "", + QUEST : "Mmmm...\aDem mighty nice!\aHere be your form for Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Thank you. That's a big help.\aLet's see...Fiddles: 2\aAll done! Off you go!_where_", + COMPLETE : "Great work, _avName_.\aI'm sure I'll get to the bottom of these thefts now.\aWhy don't you get to the bottom of this!", + }, + + 4211 : { QUEST : "Say, Dr. Fret keeps calling every five minutes. Can you go see what his problem is?_where_", + }, + 4212 : { QUEST : "Whew! I'm glad Toon HQ finally sent somebody.\aI haven't had a customer in days.\aIt's these darned Number Crunchers every where.\aI think they are teaching our residents bad oral hygiene.\aDefeat ten of them and let's see if business picks up.", + INCOMPLETE_PROGRESS : "Still no customers. But keep it up!", + }, + 4213 : { QUEST : "You know maybe it wasn't the Number Crunchers after all.\aMaybe it's just the Cashbots in general.\aTake out twenty of them and hopefully someone will come in for at least a checkup.", + INCOMPLETE_PROGRESS : "I know twenty is a lot. But I'm sure it's going to pay off in spades.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "I just don't understand it!\aStill not a SINGLE customer.\aMaybe we need to go to the source.\aTry reclaiming a Cashbot Cog building.\aThat Should do the trick...", + INCOMPLETE_PROGRESS : "Oh, please! Just one little building...", + COMPLETE : "Still not a soul in here.\aBut you know, come to think of it.\aI didn't have any customers before the Cogs invaded either!\aI really appreciate all your help though.\aThis should help you get around." + }, + + 4215 : { QUEST : "Anna desperately needs someone to help her.\aWhy don't you drop in and see what you can do._where_", + }, + 4216 : { QUEST : "Thanks for coming so quickly!\aSeems like the Cogs have made off with several of my customers' cruise tickets.\aYuki said she saw a Glad Hander leaving here with his glad hands full of them.\aSee if you can get Lumber Jack's ticket to Alaska back.", + INCOMPLETE_PROGRESS : "Those Glad Handers could be anywhere now...", + }, + 4217 : { QUEST : "Oh, great. You found it!\aNow be a trooper and run in by Jack's for me, would you?_where_", + }, + 4218 : { QUEST : "Great Googely Moogely!\aAlaska here I come!\aI can't take these infernal Cogs anymore.\aSay, I think Anna needs you again._where_", + }, + 4219 : { QUEST : "Yup, you guessed it.\aI need you to shake down those pesky Glad Handers for Tabitha's ticket to Jazzfest.\aYou know the procedure...", + INCOMPLETE_PROGRESS : "There's more out there somewhere...", + }, + 4220 : { QUEST : "Sweet!\aCould you swing this one by his place for me too?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Be cool...", + QUEST : "Cool, daddio!\aNow I'm in fat city, _avName_.\aBefore you split, you better go check out Anna Banana again..._where_", + }, + 4222 : { QUEST : "This is the last one, I promise!\aNow you are looking for Barry's ticket to the big singing contest.", + INCOMPLETE_PROGRESS : "C'mon, _avName_.\aBarry is counting on you.", + }, + 4223 : { QUEST : "This should put a smile on Barry's face._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Hello, Hello, HELLO!\aTerrific!\aI just know me and the boys are going to clean up this year.\aAnna says to swing back by and get your reward._where_\aGoodbye, Goodbye, GOODBYE!", + COMPLETE : "Thanks for all your help, _avName_.\aYou really are an asset here in Toontown.\aSpeaking of assets...", + }, + + 902 : { QUEST : "Go see Leo.\aHe needs someone to deliver a message for him._where_", + }, + 4903 : { QUEST : "Dude!\aMy castanets are all cloudy and I have a big show tonight.\aTake them to Carlos and see if he can polish them up._where_", + }, + 4904 : { QUEST : "Jes, I tink I can polish dees.\aBut I need soma de blue ink from de squid.", + GREETING : "Hola!", + LEAVING : "Adios!", + INCOMPLETE_PROGRESS : "Juo can find de squid wherever dere's a fishing pier", + }, + 4905 : { QUEST : "Jes! Dat's it!\aNow I need a leetle time to polish dees.\aWhy don juo go takeover a one story beelding while I work?", + GREETING : "Hola!", + LEAVING : "Adios!", + INCOMPLETE_PROGRESS : "Jest anodder minute...", + }, + 4906 : { QUEST : "Bery good!\aHere are de castanets for Leo._where_", + }, + 4907 : { GREETING : "", + QUEST : "Cool, dude!\aThey look awesome!\aNow I need you to get a copy of the lyrics to 'A Beat Christmas' from Hedy._where_", + }, + 4908 : { QUEST: "Hello there!\aHmmm, I don't have a copy of that song handy.\aIf you give me a little while I could transcribe it from memory.\aWhy don't you run along and reclaim a two story building while I write?", + }, + 4909 : { QUEST : "I'm sorry.\aMy memory is getting a little fuzzy.\aIf you go reclaim a three story building I'm sure I'll be done when you get back...", + }, + 4910 : { QUEST : "All done!\aSorry it took so long.\aTake this back to Leo._where_", + GREETING : "", + COMPLETE : "Awesome, dude!\aMy concert is gonna rock!\aSpeaking of rock, you can rock some Cogs with this..." + }, + 5247 : { QUEST : "This neighborhood is pretty tough...\aYou might want to learn some new tricks.\a_toNpcName_ taught me everything I know, so maybe he can help you too._where_" }, + 5248 : { GREETING : "Ahh, yes.", + LEAVING : "", + INCOMPLETE_PROGRESS : "You appear to be struggling with my assignment.", + QUEST : "Ahh, so welcome, new apprentice.\aI know all there is to know about the pie game.\aBut before we can begin your training, a small demonstration is necessary.\aGo out and defeat ten of the largest Cogs." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "Excellent!\aNow demonstrate your skill as a fisherman.\aI dropped three fuzzy dice in the pond yesterday.\aFish them out and bring them to me.", + LEAVING : "", + INCOMPLETE_PROGRESS : "It seems you may not be so clever with the rod and reel." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! These dice will look great hanging from the rearview mirror of my ox cart!\aNow, show me that you can tell your enemies from one another.\aReturn when you have restored two of the tallest Lawbot buildings.", + INCOMPLETE_PROGRESS : "Do the buildings give you trouble?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! These dice will look great hanging from the rearview mirror of my ox cart!\aNow, show me that you can tell your enemies from one another.\aReturn when you have restored two of the tallest Bossbot buildings.", + INCOMPLETE_PROGRESS : "Do the buildings give you trouble?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! These dice will look great hanging from the rearview mirror of my ox cart!\aNow, show me that you can tell your enemies from one another.\aReturn when you have restored two of the tallest Cashbot buildings.", + INCOMPLETE_PROGRESS : "Do the buildings give you trouble?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! These dice will look great hanging from the rearview mirror of my ox cart!\aNow, show me that you can tell your enemies from one another.\aReturn when you have restored two of the tallest Sellbot buildings.", + INCOMPLETE_PROGRESS : "Do the buildings give you trouble?", }, + 5200 : { QUEST : "Those sneaky Cogs are at it again.\a_toNpcName_ has reported another missing item. Stop by and see if you can straighten it out._where_" }, + 5201 : { GREETING: "", + QUEST : "Hi, _avName_. I reckon I should thank you for coming.\aA group of those Head Hunters came in and stole my soccer ball.\aThe leader told me that I had to make some cutbacks and just grabbed it away from me!\aCan you get my ball back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my soccer ball?", + COMPLETE : "Yeehaw! You found it!\a Here, take your reward...", + }, + 5261 : { GREETING: "", + QUEST : "Hi, _avName_. I reckon I should thank you for coming.\aA group of those Two-Faces came in and stole my soccer ball.\aThe leader told me that I had to make some cutbacks and just grabbed it away from me!\aCan you get my ball back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my soccer ball?", + COMPLETE : "Yeehaw! You found it!\a Here, take your reward...", + }, + 5262 : { GREETING: "", + QUEST : "Hi, _avName_. I reckon I should thank you for coming.\aA group of those Money Bags came in and stole my soccer ball.\aThe leader told me that I had to make some cutbacks and just grabbed it away from me!\aCan you get my ball back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my soccer ball?", + COMPLETE : "Yeehaw! You found it!\a Here, take your reward...", + }, + 5263 : { GREETING: "", + QUEST : "Hi, _avName_. I reckon I should thank you for coming.\aA group of those Spin Doctors came in and stole my soccer ball.\aThe leader told me that I had to make some cutbacks and just grabbed it away from me!\aCan you get my ball back?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck finding my soccer ball?", + COMPLETE : "Yeehaw! You found it!\a Here, take your reward...", + }, + 5202 : { QUEST : lTheBrrrgh+" has been overrun with some of the toughest Cogs we've seen yet.\aYou will probably want to carry more gags around here.\aI hear _toNpcName_ may have a large bag you can use to carry more gags._where_" }, + 5203 : { GREETING: "Huh? Are you on my sledding team?", + QUEST : "What's that? You want a bag?\aI had one somewhere around here... maybe it's in my toboggan?\aOnly... I haven't seen my toboggan since the big race!\aMaybe one of those Cogs took it?", + LEAVING : "Have you seen my toboggan?", + INCOMPLETE_PROGRESS : "Who are you again? Sorry, I'm a little woozy from the crash." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Is that my toboggan? I don't see any bag here.\aI think Bumpy Noggin was on the team... maybe he has it?_where_" }, + 5205 : { GREETING : "Ohhh, my head!", + LEAVING : "", + QUEST : "Huh? Ted who? A bag?\aOh, maybe he was on our toboggan team?\aMy head hurts so much I can't think straight.\aCould you fish me out some ice cubes from the frozen pond for my head?", + INCOMPLETE_PROGRESS : "Oww, my head's killing me! Got any ice?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Ahhh, that feels much better!\aSo you're looking for Ted's bag, huh?\aI think it ended up on Sam Simian's head after the crash._where_" }, + 5207 : { GREETING : "Eeeep!", + LEAVING : "", + QUEST : "What is bag? Who is Bompy?\aMe scared of buildings! You beat buildings, I give you bag!", + INCOMPLETE_PROGRESS : "More buildings! Me still scared!", + COMPLETE : "Ooooh! Me like you!" }, + 5208 : { GREETING : "", + LEAVING : "Eeeek!", + QUEST : "Ooooh! Me like you!\aGo to Ski Clinic. Bag there." }, + 5209 : { GREETING : "Dude!", + LEAVING : "Later!", + QUEST : "Man, that Simian Sam is crazy!\aIf you're wild like Sam, I'll give you your bag, man.\aGo bag some Cogs for your bag, man! Hey now!", + INCOMPLETE_PROGRESS : "Are you sure you're extreme enough? Go bag some more Cogs.", + COMPLETE : "Hey, you are pretty wild! That was a heap of Cogs you bagged!\aHere's your bag!" }, + + 5210 : { QUEST : "_toNpcName_ is secretly in love with someone in the neighborhood.\aIf you help her, she may reward you handsomely._where_" }, + 5211 : { GREETING: "Boo hoo.", + QUEST : "I spent all last night writing a letter to the dog I love.\aBut before I could deliver it, one of those nasty Cogs with a beak came in and took it.\aCan you get it back for me?", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "Please find my letter." }, + 5264 : { GREETING: "Boo hoo.", + QUEST : "I spent all last night writing a letter to the dog I love.\aBut before I could deliver it, one of those nasty Cogs with a fin came in and took it.\aCan you get it back for me?", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "Please find my letter." }, + 5265 : { GREETING: "Boo hoo.", + QUEST : "I spent all last night writing a letter to the dog I love.\aBut before I could deliver it, one of those nasty Mingler Cogs came in and took it.\aCan you get it back for me?", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "Please find my letter." }, + 5266 : { GREETING: "Boo hoo.", + QUEST : "I spent all last night writing a letter to the dog I love.\aBut before I could deliver it, one of those nasty Corporate Raiders came in and took it.\aCan you get it back for me?", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "Please find my letter." }, + 5212 : { QUEST : "Oh, thank you for finding my letter!\aPlease, please, please could you deliver it to the most handsome dog in the neighborhood?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "You didn't deliver my letter, did you?", + }, + 5213 : { GREETING : "Charmed, I'm sure.", + QUEST : "I can't be bothered with your letter, you see.\aAll my doggies have been taken from me!\aIf you bring them back, maybe we can talk then.", + LEAVING : "", + INCOMPLETE_PROGRESS : "My poor little doggies!" }, + 5214 : { GREETING : "", + LEAVING : "Toodleloo!", + QUEST : "Thank you for bringing back my little beauties.\aLet's take a look at your letter now...\nMmmm, it seems I have yet another secret admirer.\aThis calls for a trip to my dear friend Carl.\aI'm sure you'll like him immensely._where_" }, + 5215 : { GREETING : "Heh, heh...", + LEAVING : "Come back, yes, yes.", + INCOMPLETE_PROGRESS : "There are still some big ones around. Comes back to us when they're gone.", + QUEST : "Who sent you to us? We don't like Snootsies much, we don't...\aBut we likes Cogs even less...\aRun the big ones off and we'll helps you we will." }, + 5216 : { QUEST : "We told you we would helps you.\aSo take this ring to the girl.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "You still haves the ring???", + COMPLETE : "Oh darrrling!!! Thank you!!!\aOh, and I have something special for you as well.", + }, + 5217 : { QUEST : "It sounds like _toNpcName_ could use some help._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I'm sure there are more Minglers around somewhere.", + QUEST : "Help!!! Help!!! I can't take it anymore!\aThose Minglers are driving me batty!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "That can't be all of them. I just saw one!!!", + QUEST : "Oh, thanks, but now it's the Corporate Raiders!!!\aYou've got to help me!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "No, no, no there was one just here!", + QUEST : "I realize now that it's those Loan Sharks!!!\aI thought you were going to save me!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "You know what, maybe it isn't the Cogs at all!\aCould you ask Fanny to make me a soothing potion? Maybe that would help...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Oh, that Harry, he sure is a card!\aI'll whip up something that will fix him right up!\aOh, I appear to be out of sardine whiskers...\aBe a dear and run down to the pond and catch some for me.", + INCOMPLETE_PROGRESS : "Got those whiskers for me yet?", }, + 5223 : { QUEST : "Okay. Thanks, hon.\aHere, now take this to Harry. It should calm him right down.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Go on now, take the potion to Harry.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Go get those Legal Eagles for me, will you?", + QUEST : "Oh thank goodness you're back!\aGive me the potion, quick!!!\aGlug, glug, glug...\aThat tasted awful!\aYou know, what, though? I feel much calmer. Now that I can think clearly, I realize that...\aIt was the Legal Eagles that were driving me crazy all this time!!!", + COMPLETE : "Oh boy! Now I can relax!\aI'm sure there's something here I can give you. Oh, take this!" }, + 5225 : { QUEST : "Ever since the incident with the turnip bread, Grumpy Phil has been mad at _toNpcName_.\aMaybe you could help Gus fix things between them?_where_" }, + 5226 : { QUEST : "Yeah, you probably heard Grumpy Phil is mad at me...\aI was just trying to be nice with that turnip bread.\aMaybe you can help cheer him up.\aPhil really hates those Cashbot Cogs, especially their buildings.\aIf you reclaim some Cashbot buildings, it might help.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Maybe a few more buildings?", }, + 5227 : { QUEST : "That's terrific! Go tell Phil what you've done._where_" }, + 5228 : { QUEST : "Oh he did, did he?\aThat Gus thinks he can get off so easy, does he?\aOnly broke my tooth, he did, with that turnip bread of his!\aMaybe if you took my tooth to Dr. Mumbleface for me he could fix it.", + GREETING : "Mmmmrrphh.", + LEAVING : "Grumble, grumble.", + INCOMPLETE_PROGRESS : "You again? I thought you were going to get my tooth fixed for me.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I'm still working on the tooth. It will be a bit longer.", + QUEST : "Yes, that tooth looks pretty bad, alrighty.\aMaybe I can do something, but it will be a little while.\aMaybe you could clear some of those Cashbot Cogs off the streets while you're waiting?\aThey're scaring off my customers." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I'm still working on the tooth. It will be a bit longer.", + QUEST : "Yes, that tooth looks pretty bad, alrighty.\aMaybe I can do something, but it will be a little while.\aMaybe you could clear some of those Sellbot Cogs off the streets while you're waiting?\aThey're scaring off my customers." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I'm still working on the tooth. It will be a bit longer.", + QUEST : "Yes, that tooth looks pretty bad, alrighty.\aMaybe I can do something, but it will be a little while.\aMaybe you could clear some of those Lawbot Cogs off the streets while you're waiting?\aThey're scaring off my customers." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I'm still working on the tooth. It will be a bit longer.", + QUEST : "Yes, that tooth looks pretty bad, alrighty.\aMaybe I can do something, but it will be a little while.\aMaybe you could clear some of those Bossbot Cogs off the streets while you're waiting?\aThey're scaring off my customers." }, + 5230 : { GREETING: "", + QUEST : "I'm glad you're back!\aI gave up trying to fix that old tooth, and made a new gold tooth for Phil instead.\aUnfortunately a Robber Baron came in and took it from me.\aMaybe you can catch him if you hurry.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find that tooth yet?" }, + 5270 : { GREETING: "", + QUEST : "I'm glad you're back!\aI gave up trying to fix that old tooth, and made a new gold tooth for Phil instead.\aUnfortunately a Big Cheese came in and took it from me.\aMaybe you can catch him if you hurry.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find that tooth yet?" }, + 5271 : { GREETING: "", + QUEST : "I'm glad you're back!\aI gave up trying to fix that old tooth, and made a new gold tooth for Phil instead.\aUnfortunately Mr. Hollywood came in and took it from me.\aMaybe you can catch him if you hurry.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find that tooth yet?" }, + 5272 : { GREETING: "", + QUEST : "I'm glad you're back!\aI gave up trying to fix that old tooth, and made a new gold tooth for Phil instead.\aUnfortunately a Big Wig came in and took it from me.\aMaybe you can catch him if you hurry.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find that tooth yet?" }, + 5231 : { QUEST : "Great, that's the tooth alrighty!\aWhy don't you just run it over to Phil for me?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I bet Phil would like to see his new tooth.", + }, + 5232 : { QUEST : "Oh, thanks.\aMmmrrrphhhh\aHow's that look, huh?\aOkay, you can tell Gus that I forgive him.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, that's great to hear.\aI figured old Phil couldn't stay mad at me.\aAs a gesture of goodwill, I baked him this Pine cone bread.\aCould you run it over to him for me?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Better hurry. Pine cone bread is better when it's hot.", + COMPLETE : "Oh, what's this? For me?\aMunch, munch...\aOwwww! My tooth! That Gus Gooseburger!\aOh well, it wasn't your fault. Here, you can have this for your trouble.", + }, + 903 : { QUEST : "You may be ready to see _toNpcName_ the Blizzard Wizard for your final test._where_", }, + 5234 : { GREETING: "", + QUEST : "Aha, you are back.\aBefore we begin, we must eat.\aBring us some lumpy cheese for our broth.\aLumpy cheese can only be gathered from Big Cheese Cogs.", + LEAVING : "", + INCOMPLETE_PROGRESS : "We still need lumpy cheese" }, + 5278 : { GREETING: "", + QUEST : "Aha, you are back.\aBefore we begin, we must eat.\aBring us some caviar for our broth.\aCaviar can only be gathered from Mr. Hollywood Cogs.", + LEAVING : "", + INCOMPLETE_PROGRESS : "We still need caviar" }, + 5235 : { GREETING: "", + QUEST : "A simple man eats with a simple spoon.\aA Cog took my simple spoon, so I simply can not eat.\aReturn my spoon to me. I think a Robber Baron took it.", + LEAVING : "", + INCOMPLETE_PROGRESS : "I simply must have my spoon." }, + 5279 : { GREETING: "", + QUEST : "A simple man eats with a simple spoon.\aA Cog took my simple spoon, so I can not eat.\aReturn my spoon to me. I think a Big Wig took it.", + LEAVING : "", + INCOMPLETE_PROGRESS : "I simply must have my spoon." }, + 5236 : { GREETING: "", + QUEST : "Many thanks.\aSlurp, slurp...\aAhhh, now you must catch a talking toad. Try fishing in the pond.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Where is that talking toad?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "You have not yet obtained dessert.", + QUEST : "Oh, that is certainly a talking toad. Give him to me.\aWhat's that you say, toad?\aUh huh.\aUh huh...\aThe toad has spoken. We need dessert.\aBring us some ice cream cones from _toNpcName_.\aThe toad likes red bean flavored ice cream for some reason._where_", }, + 5238 : { GREETING: "", + QUEST : "So the wizard sent you. I'm sad to say we're fresh out of red bean ice cream cones.\aYou see, a bunch of Cogs came in and just took them.\aThey said they were for Mr. Hollywood, or some such nonsense.\aI'd sure appreciate if you could round them back up for me.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Have you found all my ice cream cones yet?" }, + 5280 : { GREETING: "", + QUEST : "So the wizard sent you. I'm sad to say we're fresh out of red bean ice cream cones.\aYou see, a bunch of Cogs came in and just took them.\aThey said they were for The Big Cheese, or some such nonsense.\aI'd sure appreciate if you could round them back up for me.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Have you found all my ice cream cones yet?" }, + 5239 : { QUEST : "Thanks for bringing back my ice cream cones!\aHere's one for Lil Oldman.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "You better bring that ice cream to Lil Oldman before it melts.", }, + 5240 : { GREETING: "", + QUEST : "Very good. Here you go toad...\aSlurp, slurp...\aOkay, now we are almost ready.\aIf you can just bring me some powder to dry my hands.\aI think those Big Wig Cogs sometimes have powder from their wigs.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find any powder?" }, + 5281 : { GREETING: "", + QUEST : "Very good. Here you go toad...\aSlurp, slurp...\aOkay, now we are almost ready.\aIf you can just bring me some powder to dry my hands.\aI think those Mr. Hollywood Cogs sometimes keep powder for their noses.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Did you find any powder?" }, + 5241 : { QUEST : "Okay.\aAs I once said, to truly throw a pie, you must throw not with the hand...\a...but with the soul.\aI know not what that means, so I will sit and contemplate while you restore buildings.\aReturn when you have completed your task.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Your task is not yet complete.", }, + 5242 : { GREETING: "", + QUEST : "Although I still know not what I am talking about, you truly are worthy.\aI give you a final task...\aThe talking toad would like a girlfriend.\aFind another talking toad. The toad has spoken.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Where is that other talking toad?", + COMPLETE : "Whew! I am tired from all this effort. I must rest now.\aHere, take your reward and be off." }, + + 5243 : { QUEST : "Sweaty Pete is starting to stink up the street.\aCan you talk him into taking a shower or something?_where_" }, + 5244 : { GREETING: "", + QUEST : "Yeah, I guess I do work up quite a sweat in here.\aMmmm, maybe if I could fix that leaky pipe in my shower...\aI figure a gear from one of those tiny Cogs would do the trick.\aGo find a gear from a Micromanager and we'll try it.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Where's that gear you were going to get?" }, + 5245 : { GREETING: "", + QUEST : "Yup, that seemed to do the trick.\aBut I get lonely when I shower...\aCould you go fish me up a rubber ducky to keep me company?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Any luck with that duck?" }, + 5246 : { QUEST : "The ducky's great, but...\aAll those buildings around here make me nervous.\aI'd feel a lot more relaxed if there were fewer buildings around.", + LEAVING : "", + COMPLETE : "Okay, I'll shower up now. And here's something for you too.", + INCOMPLETE_PROGRESS : "I'm still worried about buildings.", }, + 5251 : { QUEST : "Lounge Lassard is supposed to be playing a gig tonight.\aI hear he might be having some trouble with his equipment._where_" }, + 5252 : { GREETING: "", + QUEST : "Oh yeah! I could sure use some help.\aThose Cogs came in and swiped all my gear while I was unloading the van.\aCan you give me a hand and get back my microphone?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hey man, I can't sing without my microphone." }, + 5253 : { GREETING: "", + QUEST : "Yeah, that's my microphone all right.\aThanks for getting it for me, but...\aI really need my keyboard so I can tickle the ivories.\aI think one of those Corporate Raiders got my keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding my keboard?" }, + 5273 : { GREETING: "", + QUEST : "Yeah, that's my microphone all right.\aThanks for getting it for me, but...\aI really need my keyboard so I can tickle the ivories.\aI think one of those Minglers got my keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding my keboard?" }, + 5274 : { GREETING: "", + QUEST : "Yeah, that's my microphone all right.\aThanks for getting it for me, but...\aI really need my keyboard so I can tickle the ivories.\aI think one of those Loan Sharks got my keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding my keboard?" }, + 5275 : { GREETING: "", + QUEST : "Yeah, that's my microphone all right.\aThanks for getting it for me, but...\aI really need my keyboard so I can tickle the ivories.\aI think one of those Legal Eagles got my keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No luck finding my keboard?" }, + 5254 : { GREETING: "", + QUEST : "All right! Now I'm in business.\aIf only they hadn't taken my platform shoes...\aThose shoes probably ended up with a Mr. Hollywood, if I had to guess.", + LEAVING : "", + COMPLETE : "Allright!! I'm ready now.\aHello Brrrgh!!!\aHuh? Where is everyone?\aOkay, take this and round me up some fans, huh?", + INCOMPLETE_PROGRESS : "I can't perform barefoot, can I?" }, + 5282 : { GREETING: "", + QUEST : "All right! Now I'm in business.\aIf only they hadn't taken my platform shoes...\aThose shoes probably ended up with a Big Cheese, if I had to guess.", + LEAVING : "", + COMPLETE : "Allright!! I'm ready now.\aHello Brrrgh!!!\aHuh? Where is everyone?\aOkay, take this and round me up some fans, huh?", + INCOMPLETE_PROGRESS : "I can't perform barefoot, can I?" }, + 5283 : { GREETING: "", + QUEST : "All right! Now I'm in business.\aIf only they hadn't taken my platform shoes...\aThose shoes probably ended up with a Robber Baron, if I had to guess.", + LEAVING : "", + COMPLETE : "Allright!! I'm ready now.\aHello Brrrgh!!!\aHuh? Where is everyone?\aOkay, take this and round me up some fans, huh?", + INCOMPLETE_PROGRESS : "I can't perform barefoot, can I?" }, + 5284 : { GREETING: "", + QUEST : "All right! Now I'm in business.\aIf only they hadn't taken my platform shoes...\aThose shoes probably ended up with a Big Wig, if I had to guess.", + LEAVING : "", + COMPLETE : "Allright!! I'm ready now.\aHello Brrrgh!!!\aHuh? Where is everyone?\aOkay, take this and round me up some fans, huh?", + INCOMPLETE_PROGRESS : "I can't perform barefoot, can I?" }, + + 5255 : { QUEST : "You look like you could use more Laff points.\aMaybe _toNpcName_ could sort you out._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "A deal's a deal.", + QUEST : "So you're looking for Laff points, huh?\aHave I got a deal for you!\aSimply take care of a few Bossbot Cogs for me...\aAnd I'll make it worth your while." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "A deal's a deal.", + QUEST : "So you're looking for Laff points, huh?\aHave I got a deal for you!\aSimply take care of a few Lawbot Cogs for me...\aAnd I'll make it worth your while." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Okay, but I'm certain I told you to round up some Lawbot Cogs.\aWell, if you say so, but you owe me one.", + INCOMPLETE_PROGRESS : "I don't think you're done yet.", + QUEST : "You say you're done? Defeated all the Cogs?\aYou must have misunderstood, our deal was for Sellbot Cogs.\aI'm sure I told you to defeat some Sellbot Cogs for me." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Okay, but I'm certain I told you to round up some Lawbot Cogs.\aWell, if you say so, but you owe me one.", + INCOMPLETE_PROGRESS : "I don't think you're done yet.", + QUEST : "You say you're done? Defeated all the Cogs?\aYou must have misunderstood, our deal was for Cashbot Cogs.\aI'm sure I told you to defeat some Cashbot Cogs for me." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "I can't help you with Laff points, but maybe _toNpcName_ will cut you a deal.\aHe's a little on tempermental side though..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "I told you what?!?!\aThanks a bunch! Here's your Laff point!", + INCOMPLETE_PROGRESS : "Hi!\aWhat are you doing in here again!", + QUEST : "A Laff point? I dont think so!\aSure, but only if you clear out some of these pesky Lawbots first." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" is teeming with very dangerous Cogs.\aIf I were you, I'd carry more gags around here.\aI hear _toNpcName_ can make you a large bag if you are willing to do the legwork._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "There should be plenty of Lawbots out there.\aSo get to it!" , + QUEST : "A bigger bag?\aI could probably whip one up for ya.\aI'll need some yarn though.\aSome Lawbots made off with mine yesterday morning." }, + 5305 : { GREETING : "Howdy!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Go get some more cogs.\aThis color hasn't taken yet.", + QUEST : "That there's some fine yarn!\aNot my first choice of color though.\aTell you what...\aYou go out there and beat up some of the tougher cogs...\aAnd I'll get to work dyeing this yarn." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "They gotta be down there somewhere...", + QUEST : "Well, the yarn is all dyed. But we've got a small problem.\aI can't find my knitting needles anywhere.\aLast place I saw them was down at the pond." }, + 5307 : { GREETING : "", + LEAVING : "Much obliged!", + INCOMPLETE_PROGRESS : "Rome wasn't knit in a day!" , + QUEST : "Those are my needles alright.\aWhile I'm knitting, why don't you go clear some of them big buildings?", + COMPLETE : "Great work!\aAnd speaking of great work...\aHere's your new bag!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "I hear _toNpcName_ is having some legal troubles.\aCan you stop by and check it out?_where_" }, + 5309 : { GREETING : "I'm glad you're here...", + LEAVING : "", + INCOMPLETE_PROGRESS : "Please hurry! The street is crawling with them!", + QUEST : "The Lawbots have really taken over out there.\aI'm afraid they are going to take me to court.\aDo you think you could help get them off of this street?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "I think I hear them coming for me...", + QUEST : "Thanks. I feel a little better now.\a But there is one more thing...\aCould you drop by _toNpcName_'s and get me an alibi?_where_" }, + 5311 : { GREETING : "WHAAAA!!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "I can't help him if you can't find it!", + QUEST : "Alibi?! Why that's a great idea!\aYou'd better make it two!\aI bet a Legal Eagle would have some..." }, + 5312 : { GREETING : "Finally!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "Whew! Am I ever relieved to have this.\aHere's your reward...", + QUEST : "Super! You'd better run these back to _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Powers Erge needs some help. Could you drop by and lend her a hand?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, a customer! Great! What can I do for you?\aWhat do you mean, what can you do for me? OH! You're not a customer.\aI remember now. You're here to help with those dreadful Cogs.\aWell I could certainly use the help even if you're not a customer.\aIf you clean up the streets a bit, I'll have a little something for you.", + INCOMPLETE_PROGRESS : "If you don't want electricity, I can't help you until you defeat those Cogs.", + COMPLETE : "Good job on those Cogs, _avName_.\aNow, are you sure I can't interest you in some electricity? Might come in handy....\aNo? OK, suit yourself.\aHunh? Oh yeah, I remember. Here ya go. This is sure to help with those nasty Cogs.\aKeep up the good work!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Well, _avName_, I don't have anything for you right now.\aWait! I think Susan Siesta was looking for help. Why don't you go see her?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "I'll never get rich with those darn Cogs driving away all my business!\aYou've got to help me, _avName_.\aClear out a few Cog buildings for the sake of the neighborhood and I'll add to your riches.", + INCOMPLETE_PROGRESS : "Poor me! Can't you get rid of those buildings?", + COMPLETE : "Now I'll be in the money! I can see it now!\aI'll spend all my time fishing. Now, let me enrich your life a little.\aThere you go!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "Hey _avName_! I heard Lawful Linda was looking for you.\aYou should stop by and pay her a visit._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "Hi there! Wow, am I glad to see you!\aI've been working on this answering machine in my spare time but I'm short a couple of parts.\aI need three more rods and the ones from Bean Counters seem to work pretty well.\aCould you see if you could find some rods for me?", + INCOMPLETE_PROGRESS : "Still trying to find those rods?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, those will do nicely.\aThat's funny. I was sure I had a spare drive belt around here but I can't find it.\aCould you please get one from a Money Bags for me? Thanks!", + INCOMPLETE : "Well, I can't help you until I get that drive belt.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, that's it. Now it should run like a charm.\aWhere'd my pliers go? I can't tighten this up without the pliers.\aMaybe pincers from a Penny Pincher would do the job?\aIf you'd go find one, I could give you a little something to help you with those Cogs.", + INCOMPLETE_PROGRESS : "No pincers yet, hunh? Keep looking.", + COMPLETE : "Great! Now I'll just tighten this up.\aIt seems to be working now. Back in business!\aWell, except that we don't have a phone. But I'm glad for the help, anyway.\aI think this'll help you out with those Cogs. Good luck!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "I heard Rocco was looking for help. See what you can do for him._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "Yo! Youse came to da right place. I ain't too happy.\aYeah, I was lookin for some help wid dose Cogs. Dey always come and boss me around.\aIf you can retire some of dem Bossbots, I'll make it worth your while.", + INCOMPLETE_PROGRESS : "Hey, _avName_, what's up wid youse?\aYou gotta keep after dem Bossbots. We got a deal, remember?\aRocco always keeps his word.", + COMPLETE : "Yo, _avName_! Youse ok in my book.\aDem Bossbots ain't so bossy now, is they?\aHere ya go! A nice big boost. Now, you stay outta trouble, ya hear!", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "Nat over on Pajama Place heard rumors about a Cashbot Headquarters.\aHead over there and see if you can help him out._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "I got a nibble about some strange goings on.\aWell, maybe it's the fleas but something is going on anyway.\aAll these Cashbots!\aI think they've opened another headquarters right off Pajama Place.\aP.J. knows his way around.\aGo see _toNpcName_ _where_ Ask him if he's heard anything.", + INCOMPLETE_PROGRESS : "You haven't seen P.J. yet? What's keeping you?\aOh, these darn fleas!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "Hey there _avName_, where are you headed?\aCashbot Headquarters?? I haven't seen anything.\aCould you go to the end of Pajama Place and see if it's true?\aFind some Cashbot Cogs in their headquarters, defeat a few of them, and come tell me about it.", + INCOMPLETE_PROGRESS : "Found the HQ yet? You'll need to defeat some Cashbots there to scope it out.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "What?! There really IS a Cashbot HQ?\aYou better go tell Nat right away!\aWho would have guessed there'd be a Cog HQ right down the street from him?", + INCOMPLETE_PROGRESS : "What did Nat have to say? You haven't seen him yet?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "So, I'm itching to hear what P.J. had to say.\aHmm...we need more information about this Cog business but I've got to get rid of these fleas!\aI know! YOU can go find out more!\aGo defeat Cashbots at the HQ until you find some plans then come right back!", + INCOMPLETE_PROGRESS : "No plans yet? Keep searching those Cogs!\aThey're bound to have some plans!", + COMPLETE : "You got the plans?\aGreat! Let's see what they say.\aI see... the Cashbots built a Mint to make Cogbucks.\aIt must be FULL of Cashbots. We should find out more about this.\aMaybe if you had a disguise. Hmmm...wait! I think I've got part of a Cog suit here somewhere....\aHere it is! Why don't you take this for your trouble? Thanks again for your help!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "The Countess has been looking everywhere for you! Please pay her a visit so she'll stop calling._where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, I'm counting on you to help me!\aYou see, these Cogs are making so much noise that I simply can't concentrate.\aI keep losing count of my sheep!\aIf you'll cut down on the noise, I'll help you out too! You can count on it!\aNow, where was I? Right, one hundred thirty-six, one hundred thirty-seven....", + INCOMPLETE_PROGRESS : "Four hundred forty-two...four hundred forty-three...\aWhat? You're back already? But it's still so noisy!\aOh no, I've lost count again.\a One...two...three....", + COMPLETE : "Five hundred ninety-three...five hundred ninety-four...\aHello? Oh, I knew I could count on you! It's much quieter now.\aHere you go, for all those Number Crunchers.\aNumber? Now I need to start counting all over again! One...two....", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "Poor Zari broke her zipper and now she can't make deliveries to her customers. She could sure use your help._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, hi _avName_. You're here to help with my deliveries?\aThat's terrific! This broken zipper makes it tough to zip around.\aLet me see...ok, this should be easy. Cowboy George ordered a zither last week.\aCould you please bring it over to him? _where_", + INCOMPLETE_PROGRESS : "Oh, hi! Did you forget something? Cowboy George is waiting for that zither.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "My zither! At last! Gosh, I can't wait to play it.\aGo tell Zari that I said thanks, would you?", + INCOMPLETE_PROGRESS : "Thanks again for the zither. Doesn't Zari have more deliveries for you to do?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "That was fast. What's next on my list?\aRight. Master Mike ordered a Zamboni. That zany guy.\aCould you bring this to him, please?_where_", + INCOMPLETE_PROGRESS : "That Zamboni needs to go to Master Mike._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "All-right! The Zamboni I ordered!\aNow, if only there weren't so many Cogs around, I might have some time to use it.\aBe a sport and take care of a few of those Cashbots for me, would you?", + INCOMPLETE_PROGRESS : "Those Cashbots are tough, hunh? They make it hard to test my Zamboni.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "Excellent! Now I can go try out my Zamboni.\aTell Zari that I'll be in next week to place my next order, please.", + INCOMPLETE_PROGRESS : "That's all I need for now. Isn't Zari waiting for you?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "So, Master Mike was happy with his Zamboni? Great.\aWho's next? Oh, Zen Glen ordered a zebra-striped zabuton.\aHere it is! Could you zoom over to his place, please?_where_", + INCOMPLETE_PROGRESS : "I think Zen Glen needs that zabuton to meditate.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, my zabuton at last. Now I can meditate.\aWho could focus with that racket going on? All those Cogs!\aSince you're already here, maybe you could take care of some of these Cogs?\aThen I could use my zabuton in peace.", + INCOMPLETE_PROGRESS : "Still so noisy with those Cogs! Who could focus?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "Peace and quiet at last. Thanks, _avName_.\aPlease tell Zari how happy I am. OM....", + INCOMPLETE_PROGRESS : "Zari called looking for you. You should go see what she needs.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "I'm glad to hear that Zen Glen is happy with his zebra zabuton.\aOh, these zinnias just came in for Rose Petals.\aSince you seem to have some zeal for deliveries, perhaps you could take them over to her?_where_", + INCOMPLETE_PROGRESS : "Those zinnias will wilt if you don't deliver them soon.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "What lovely zinnias! Zari sure does deliver.\aOh, well, I guess YOU deliver, _avName_. Please thank Zari for me!", + INCOMPLETE_PROGRESS : "Don't forget to thank Zari for the zinnias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Welcome back, _avName_. You're pretty zippy.\aLet's see...what's next on my list to deliver? Zydeco records for Wyda Wake._where_", + INCOMPLETE_PROGRESS : "I'm sure Wyda Wake is waiting for those Zydeco records.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "Zydeco records? I don't remember asking for Zydeco records.\aOh, I bet Lullaby Lou ordered them._where_", + INCOMPLETE_PROGRESS : "No, those Zydeco records are for Lullaby Lou._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "At last, my Zydeco records! I thought Zari had forgotten.\aCould you please bring this zucchini to her? She'll find someone who wants one. Thanks!", + INCOMPLETE_PROGRESS : "Oh, I've got plenty of zucchini already. Take that one to Zari.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "Zucchini? Hmm. Well, someone will want it, I'm sure.\aOk, we're nearly done with my list. One more delivery to make.\aBabyface MacDougal ordered a zoot suit._where_", + INCOMPLETE_PROGRESS : "If you don't deliver that zoot suit to Babyface MacDougal,\a it'll get all wrinkled.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Once upon a time...oh! You're not here for a story, are you?\aYou're delivering my zoot suit? Great! Wow, that's something.\aHey, could you give Zari a message for me? I'll be needing zircon cufflinks to go with the suit. Thanks!", + INCOMPLETE_PROGRESS : "Did you give Zari my message?", + COMPLETE : "Zircon cufflinks, hunh? Well, I'll see what I can do for him.\aAnyway, you've been the very zenith of helpfulness and I can't let you leave with zilch.\aHere's a BIG boost to help you zap those Cogs!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "Drowsy Dave is having some trouble that you might be able to help with. Why don't you stop by his shop?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "What? Huh? Oh, I must've fallen asleep.\aYou know, those Cogs buildings are full of machinery that makes me really sleepy.\aI listen to them humming all day and...\aHuh? Oh, yeah, right. If you could get rid of some of those Cog buildings, I could stay awake.", + INCOMPLETE_PROGRESS : "Zzzzz...huh? Oh, it's you, _avName_.\aBack already? I was just taking a little nap.\aCome back when you're done with those buildings.", + COMPLETE : "What? I dropped off to sleep for a minute there.\aNow that those Cog buildings are gone I can finally relax.\aThanks for your help, _avName_.\aSee you later! I think maybe I'll take a little nap.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Head over and call on Teddy Blair. He's got a job for you._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "What did you say? No, I don't have a fob for you.\aOh, a job! Why didn't you say so? You'll need to speak up.\aThose Cogs make it hard to hibernate. If you'll help make Dreamland quieter,\aI'll give you a little something.", + INCOMPLETE_PROGRESS: "You beat the bogs? What bogs?\aOh, the Cogs! Why didn't you say so?\aHmm, it's still pretty loud. How 'bout you defeat a few more?", + COMPLETE : "You had fun? Huh? Oh!\aYou're done! Great. Really nice of you to help out this way.\aI found this in the back room but I don't have any use for it.\aMaybe you'll find something to do with it. So long, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "Cogs broke into the First Security Blanket Bank! Go see William Teller and see if you can help.", + }, + 6292 : { QUEST : "Oh those darn Cashbot Cogs! They stole my reading lamps!\aI need them back right away. Can you go look for them?\aIf you can get my reading lamps, I might be able to help you get into see the C.F.O.\aHurry!", + INCOMPLETE_PROGRESS : "I need those lamps back. Keep looking for them!", + COMPLETE : "You're back! And you got my lamps!\aI can't thank you enough but I can give you this.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Nina Nightlight was looking for you, _avName_. She needs some help._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "Oh! I'm so glad to see you, _avName_. I could use some help!\aThose darn Cogs have kept the delivery folks away and I have no beds in stock.\aCould you go see Hardy O'Toole and bring me back a bed?_where_ ", + INCOMPLETE_PROGRESS : "Did Hardy have any beds? I was sure he'd have one.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "A bed? Sure, here's one all ready to go.\aJust bring it over to her for me, would you? Get it?\a\"WOOD\" you? Hee-hee!\aPretty funny. No? Well, take it over there anyway, please?", + INCOMPLETE_PROGRESS : "Did Nina like the bed?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "This bed isn't right. It's much too plain.\aGo see if he has anything fancier, would you?\aI'm sure it won't take but a minute.", + INCOMPLETE_PROGRESS : "I'm certain that Hardy has a fancier bed.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "Didn't hit the nail on the head with that bed, huh? I've got one here that will do the job.\aOne small problem though - it needs to be assembled first.\aWhile I hammer out this problem, could you get rid of some of those Cogs that are outside?\aThose awful Cogs throw a wrench in the works.\aCome back when you're done and the bed will be ready.", + INCOMPLETE_PROGRESS : "Not quite done with assembling the bed.\aWhen you're done with the Cogs, it'll be ready.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "Hey there _avName_!\aYou did a bang-up job on those Cogs.\aThe bed is all ready. Could you please deliver it for me?\aNow that those Cogs are gone, business will be brisk!", + INCOMPLETE_PROGRESS : "I think Nina's waiting for that bed delivery.", + COMPLETE : "What a lovely bed!\aNow my customers will be happy. Thanks, _avName_.\aSay, you might be able to use this. Someone left it here.", + }, + + 7209 : { QUEST : "Go see Honey Moon. She needs some help._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "Oh! I'm so glad to see you, _avName_. I really need some help!\aI haven't been able to get my beauty sleep for ages. You see, those Cogs stole my bedspread.\aSay, could you please run over and see if Ed's got anything in blue?_where_", + INCOMPLETE_PROGRESS : "What did Ed have to say about a blue bedspread?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "So, Honey wants a bedspread, huh?\aWhat color? BLUE?!\aWell, I'd have to make that for her special. Everything I've got is red.\aTell ya what...if you'll go deal with some of those Cogs out there, I'll make a special blue bedspread just for her.\aBlue bedspreads...what'll it be next?", + INCOMPLETE_PROGRESS : "Still working on this blue bedspread, _avName_. Keep at those Cogs!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Nice to see you again. I've got something for you!\aHere's the bedspread and it's blue. She'll love it.", + INCOMPLETE_PROGRESS : "Did Honey like the bedspread?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "My bedspread? No, that's not right.\aIt's PLAID! How can anyone sleep with such a LOUD pattern?\aYou'll just have to take it back and get a different one.\aI'm sure he'll have others.", + INCOMPLETE_PROGRESS : "I simply will not accept a plaid bedspread. See what Ed can do about it.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "What? She doesn't like PLAID?\aHmm...let me see what we've got here.\aThis will take a while. Why don't you go take care of a few Cogs while I try to find something else?\aI'll have something by the time you get back here.", + INCOMPLETE_PROGRESS : "I'm still looking for another bedspread. How's it going with the Cogs?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "Hey, good job on those Cogs!\aHere you go, it's blue and it's not plaid.\aSure hope she likes paisley.\aBring the bedspread back to Honey.", + INCOMPLETE_PROGRESS : "That's all I've got for you right now.\aPlease bring that bedspread to Honey.", + COMPLETE : "Oh! That's lovely! Paisley suits me quite well.\aTime for my beauty sleep, then! So long, _avName_.\aWhat? You're still here? Can't you see I'm trying to sleep?\aHere, take this and let me rest. I must look a fright!", + }, + + 7218 : { QUEST : "Dreamy Daphne could use a hand._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, _avName_, I'm glad to see you! Those Cogs took my pillows.\aCould you go see if Tex has some pillows?_where_\aI'm sure he can help.", + INCOMPLETE_PROGRESS : "Does Tex have any pillows for me? ", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "Howdy! Daphne needs some pillows, huh? Well, you came to the right place, pardner!\aMore pillows in here than there's spines on a cactus.\aHere you go, _avName_. Take these back over to Daphne with my compliments.\aAlways glad to help a gal out.", + INCOMPLETE_PROGRESS : "Were those pillows soft enough for the little lady?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "You got the pillows! Great!\aHey, wait a second! These pillows are awfully soft.\aMuch too soft for me. I need harder pillows.\aTake these back to Tex and see what else he's got. Thanks.", + INCOMPLETE_PROGRESS : "Nope! Too soft. Ask Tex for different pillows.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Too soft, huh? Well, let me see what I've got....\aHmm...seems I had me a whole passel of hard pillows. Where'd they get to?\aOh! I remember. I was fixing to send them back so they're in storage.\aHow 'bout you clean up some of those Cog buildings out there while I get 'em out of storage, pardner?", + INCOMPLETE_PROGRESS : "Cog buildings are hard. But these pillows aren't.\aI'll keep looking.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "Back already? Well, that's jess fine. See, I found those pillows Daphne wanted.\aNow, you jess take these over to her. They're hard enough to break a tooth on!", + INCOMPLETE_PROGRESS : "Yeah, those pillows are mighty hard. I hope Daphne fancies 'em.", + COMPLETE : "I knew Tex would have some harder pillows.\aOh yes, those are perfect. Nice and hard.\aWould you have a use for this piece of a Cog suit? Might as well take it with you.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Drop by to see Sandy Sandman. She's lost her pajamas._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "I have no pajamas! They're missing!\aWhat will I do? Oh! I know!\aGo see Big Mama. She'll have pajamas for me._where_", + INCOMPLETE_PROGRESS : "Does Big Mama have pajamas for me?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "Hey there, little toon! Big Mama's got the best pajamas from the Bahamas.\aOh, something for Sandy Sandman, huh? Well, let me see what I've got.\aHere's a little something. Now she can sleep in style!\aWould you run these back over to her for me? I can't leave the shop just now.\aThanks, _avName_. See you around!", + INCOMPLETE_PROGRESS : "You need to take those pajamas to Sandy._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "Big Mama sent these for me? Oh...\aDoesn't she have any pajamas with feet on them?\aI always wear pajamas with feet. Doesn't everybody?\aTake these back and ask her to find some with feet.", + INCOMPLETE_PROGRESS : "My pajamas must have feet. See what Big Mama can do.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "Feet? Let me think....\aWait! I've got just the thing!\aTa-dah! Pajamas with feet. Nice blue pajamas with feet. Best ones on any island.\aPlease take them back to her, would you? Thanks!", + INCOMPLETE_PROGRESS : "Did Sandy like the blue footie pajamas?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "Well, these DO have feet, but I can't wear blue pajamas!\aAsk Big Mama if she has a different color.", + INCOMPLETE_PROGRESS : "I'm sure Big Mama has footie pajamas in a different color.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "That's too bad. These are the only pajamas with feet I have.\aOh, I've got an idea. Go ask Cat. She may have some pajamas with feet._where_", + INCOMPLETE_PROGRESS : "Nope, those are all the pajamas I've got. Go see what Cat has._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "Pajamas with feet? Sure thing.\aWhat do you mean, these are blue? She doesn't want blue?\aOh, that's a little trickier. Here, try these.\aThey're not blue and they DO have feet.", + INCOMPLETE_PROGRESS : "I just love puce, don't you?\aI hope Sandy likes them....", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "No, these aren't blue but no one with my complexion could possibly wear puce.\aAbsolutely not. Back they go and you with them! See what else Cat has.", + INCOMPLETE_PROGRESS : "Cat must have more pajamas. No puce for me!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Not puce either. Hmm....\aBy my whiskers, I know I have some other ones.\aThey'll take a little while to find. Let's make a deal.\aI'll find the other pajamas if you'll get rid of some of these Cog buildings. They're very unsettling.\aI'll have the pajamas ready when you get back, _avName_.", + INCOMPLETE_PROGRESS : "You need to clear out a few more Cog buildings while I look for other pajamas.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "You did a great job on those Cogs! Thanks!\aI found those pajamas for Sandy; hope she likes them.\aBring them over to her. Thank you.", + INCOMPLETE_PROGRESS : "Sandy's waiting for those pajamas, _avName_.", + COMPLETE : "Fuchsia pajamas with feet! Purr-fect!\aAh, now I'm all set. Let's see....\aOh, I suppose I should give you something for helping me out.\aMaybe you can use this. Someone left it here.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Go see Smudgy Mascara. She's been looking for some help._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "Those darn Cogs took my wrinkle cream!\aMy customers simply MUST have wrinkle cream while I work on them.\aGo see Rip and see if he has my special formula in stock._where_", + INCOMPLETE_PROGRESS : "I refuse to work on anyone without wrinkle cream.\aSee what Rip has for me.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, that Smudgy's a picky character. She won't settle for my usual formula.\aThat means I'll need some cauliflower coral, my super-secret special ingredient. But I haven't any in stock.\aCould you go fish some out of the pond for me? As soon as you get the coral, I'll whip up a batch for Smudgy.", + INCOMPLETE_PROGRESS : "I'll need that cauliflower coral to make a batch of wrinkle cream.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "Wow, that's a nice cauliflower coral!\aOk, let's see...a little of this and a splash of that...now, just a dollop of kelp.\aHuh, where's the kelp? Looks like I'm out of kelp, too.\aCould you pop down to the pond and fish me out some nice, slimy kelp?", + INCOMPLETE_PROGRESS : "Not a strip of slimy kelp in the shop.\aCan't make the cream without it.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "Oooh! Very slimy kelp you've got there, _avName_.\aNow, I just crush some pearls with the mortar and pestle.\aUm, where's my pestle? What good is a mortar without a pestle?\aI bet that darn Loan Shark took it when he came in here!\aYou need to help me find it! He was headed for Cashbot HQ!", + INCOMPLETE_PROGRESS : "I simply cannot crush the pearls without a pestle.\aDarn those Loan Sharks!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "Alright! You got my pestle!\aNow we're in business. Crush that...stir this up and...\aThere ya go! Tell Smudgy's it's good and fresh.", + INCOMPLETE_PROGRESS : "You should bring this over to Smudgy while it's fresh.\aShe's very picky.", + COMPLETE : "Didn't Rip have a bigger jar of wrinkle cream than this? No?\aWell, I guess I'll just order more when I run out.\aSo long, _avName_.\aWhat? You're still here? Can't you see I'm trying to work?\aHere, take this.", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "If you are interested in Lawbot disguise parts you should visit _toNpcName_.\aI hear he could use some help with his weather research._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Yes, yes. I have Lawbot disguise parts.\aBut they are of no interest to me.\aThe focus of my research is fluctuations in the ambient temperature of Toontown.\aI will gladly trade you disguise parts for cog temperature sensors.\aYou can start on %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "Have you tried looking on %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "Ah, excellent!\aJust as I feared...\aOh, yes! Here is your disguise part.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "For more Lawbot disguise parts you should visit _toNpcName_ again.\aI hear he needs more research assistants._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "More Lawbot disguise parts?\aWell, if you insist...\abut I will require another cog temperature sensor.\aThis time look on %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "You are looking on %s, right?" % GlobalStreetNames[2200][-1], + COMPLETE : "Thank you!\aAnd here is your disguise part.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "If you need more Lawbot disguise parts you should return to _toNpcName_.\aI hear he still needs help with his weather reasearch._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "You're proving yourself quite useful!\aCan you take a look on %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "Are you sure you're looking on %s?" % GlobalStreetNames[2300][-1], + COMPLETE : "Hmmm, I don't like the looks of this...\abut here is your disguise part...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "You-know-who needs more temperature readings.\aStop by if you would like another disguise part._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "Back again?\aYou are very dedicated...\aThe next stop is %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "Have you tried looking on %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "Good! You seem to be getting the hang of this!\aYour disguise part...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "If you're up for another Lawbot disguise part..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "Fancy seeing you here!\aNow I need readings on %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "You are looking on %s, right?" % GlobalStreetNames[1200][-1], + COMPLETE : "Thank you very much.\aYour disguise must be getting close...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "I believe _toNpcName_ has more work for you._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "Good to see you again, _avName_!\aCan you get a reading on %s, please?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "Have you tried looking on %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "Great work!\aHere's your well earned reward!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "You know the drill._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, my dear friend!\aCan you go to %s and find another temperature sensor?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "Are you sure you're looking on %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "Excellent!\aWith your help my research is coming quickly!\aHere's your reward.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ was asking for you by name.\aIt appears you've made quite an impression!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "Welcome back!\aI've been waiting for you.\aThe next reading I need is on %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "You are looking on %s, right?" % GlobalStreetNames[5200][-1], + COMPLETE : "Thanks!\aHere's your reward.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "If you need to finish your Lawbot disguise...\a_toNpcName_ can help you out._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "Hello, Junior Research Scientist!\aWe still need readings from %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "Have you tried looking on %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "Excellent job!\aHere is your Lawbot thingy...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ has another job for you.\aIf you're not sick of him yet..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Well, then.\aAre you ready for another recovery?\aThis time try %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "Are you sure you're looking on %s?" % GlobalStreetNames[4100][-1], + COMPLETE : "Another one!\aMy you are the picture of efficiency!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "Are you still after Lawbot disguise parts?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "You could probably guess by now...\abut I need readings from %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "You are looking on %s, right?" % GlobalStreetNames[4200][-1], + COMPLETE : "Almost there!\aHere you go...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "I hate to say it, but..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "What do you think about %s? Could you get a sensor from there too?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "Have you tried looking on %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Another excellent job, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Go visit the Professor if you still need disguise parts._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "I believe we still need a reading from %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "Are you sure you're looking on %s?" % GlobalStreetNames[9100][-1], + COMPLETE : "Good work!\aI think we are getting very close...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ has one final mission for you._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "Back so soon?\aThe final reading is on %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "You are looking on %s, right?" % GlobalStreetNames[9200][-1], + COMPLETE : "You're all done!\aNow you are ready to infiltrate the District Attorney's Office and collect Jury Notices.\aGood luck and thanks for all your help!", + }, + + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "If you are interested in Bossbot disguise parts you should visit _toNpcName_._where_", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "Yes, I can get you Bossbot parts.\aBut I'll need you to help me complete my Bossbot collection.\aGo out there and defeat a Flunky.", + INCOMPLETE_PROGRESS : "You can't find a Flunky? For shame...", + COMPLETE : "You didn't flunk that, now did you?\aHere's your first disguise part.", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ needs more help, if you're up for it._where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "Another disguise part?\aCertainly...\abut only if you defeat a Pencil Pusher.", + INCOMPLETE_PROGRESS : "Pencil Pushers can be found in the streets.", + COMPLETE : "He was a real pushover!\aHere's your second disguise part.", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "There's really only one place to go for Bossbot parts._where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "Now I need a Yesman...", + INCOMPLETE_PROGRESS : "Yesmen can be found in the streets.", + COMPLETE : "Yes! Man, you are good.\aHere's your third disguise part.", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ has more parts for you...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "If you defeat a Micromanager I'll give you another part.", + INCOMPLETE_PROGRESS : "Try looking on %s" % GlobalStreetNames[1100][-1], + COMPLETE : "You managed that quite well!\aHere's your fourth disguise part.", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "Head on over to..._where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "I'm after a Downsizer now...", + INCOMPLETE_PROGRESS : "Having trouble? Try looking on %s" % GlobalStreetNames[3100][-1], + COMPLETE : "He went down hard!\aHere's your fifth disguise part.", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "I think you know where to go by now..._where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "A Head Hunter is next on my list.", + INCOMPLETE_PROGRESS : "You might have better luck looking buildings.", + COMPLETE : "I see you had no problem hunting one down.\aHere's your sixth disguise part.", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ needs more Bossbots.", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "Next I'll need you to track down a Corporate Raider.", + INCOMPLETE_PROGRESS : "You might have better luck looking buildings.", + COMPLETE : "You're quite the little raider yourself!\aHere's your seventh disguise part.", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "If you want more disguise parts, go to..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "Now the coup de grace: The Big Cheese!", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "I knew I could count on you to cut...\aAh, never mind.\aHere's your next disguise part.", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ was looking for you...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "Now I need you to defeat one of the new, more treacherous Bossbot Cogs.", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "They are tougher than they look, huh?\aI guess I owe you a disguise part.", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "Could you swing by..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "These Version 2.0 Cogs are very interesting.\aPlease go defeat another one.", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Thanks!\aAnother disguise part coming right up.", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "If you get a chance, stop by and see _toNpcName_.", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "I wonder if they can keep regenerating...", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "I guess not.\aHere's your part...", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "You know..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "Maybe they aren't Bossbots at all...", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Hmmm, I guess they are Bossbots after all.\aHelp yourself to another part.", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "You probably know what I'm going to say already...", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "Perhaps they are related to the Skelecogs somehow...", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "That was inconclusive...\aHere's your disguise part.", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "Please go see _toNpcName_ again.", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "I'm still not convinced they aren't some type of Skelecog...", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Well, maybe not.\aHere's your next part.", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "It's probably the last place you want to go. but...", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "I am still quite baffled by these new cogs.\aCould you go defeat another, please?", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Fascinating. Simply fascinating.\aA disguise part for your troubles.", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ is starting to sound like a broken record...", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "I've almost determined what these new Cogs are.\aJust one more...", + INCOMPLETE_PROGRESS : "Try looking in %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Yes, I think I'm onto something.\aOh, yes.\aThis is for you...", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "You need to go tell Flippy about this...", + INCOMPLETE_PROGRESS : "Flippy can be found in Toon Hall", + COMPLETE : "A new type of Cog!\aGood work!\aHere is your final disguise part.", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["woof", "arf", "rruff"] +ChatGarblerCat = ["meow", "mew"] +ChatGarblerMouse = ["squeak", "squeaky", "squeakity"] +ChatGarblerHorse = ["neigh", "brrr"] +ChatGarblerRabbit = ["eek", "eepr", "eepy", "eeky"] +ChatGarblerDuck = ["quack", "quackity", "quacky"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerBear = ["growl", "grrr"] +ChatGarblerPig = ["oink", "oik", "snort"] +ChatGarblerDefault = ["blah"] + +# AvatarDNA.py +Bossbot = "Bossbot" +Lawbot = "Lawbot" +Cashbot = "Cashbot" +Sellbot = "Sellbot" +BossbotS = "a Bossbot" +LawbotS = "a Lawbot" +CashbotS = "a Cashbot" +SellbotS = "a Sellbot" +BossbotP = "Bossbots" +LawbotP = "Lawbots" +CashbotP = "Cashbots" +SellbotP = "Sellbots" +BossbotSkelS = "a Bossbot Skelecog" +LawbotSkelS = "a Lawbot Skelecog" +CashbotSkelS = "a Cashbot Skelecog" +SellbotSkelS = "a Sellbot Skelecog" +BossbotSkelP = "Bossbot Skelecogs" +LawbotSkelP = "Lawbot Skelecogs" +CashbotSkelP = "Cashbot Skelecogs" +SellbotSkelP = "Sellbot Skelecogs" +SkeleRevivePostFix = " v2.0" + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Looking up details for %s." +AvatarDetailPanelFailedLookup = "Unable to get details for %s." +#AvatarDetailPanelPlayer = "Player: %(player)s\nWorld: %(world)s\nLocation: %(location)s" +# sublocation is not working now +AvatarDetailPanelPlayer = "Player: %(player)s\nWorld: %(world)s" +AvatarDetailPanelPlayerShort = "%(player)s\nWorld: %(world)s\nLocation: %(location)s" +AvatarDetailPanelRealLife = "Offline" +AvatarDetailPanelOnline = "District: %(district)s\nLocation: %(location)s" +AvatarDetailPanelOnlinePlayer = "District: %(district)s\nLocation: %(location)s\nPlayer: %(player)s" +AvatarDetailPanelOffline = "District: offline\nLocation: offline" +AvatarShowPlayer = "Show Player" +OfflineLocation = "Offline" + +#PlayerDetailPanel +PlayerToonName = "Toon: %(toonname)s" +PlayerShowToon = "Show Toon" +PlayerPanelDetail = "Player Details" + + +# AvatarPanel.py +AvatarPanelFriends = "Friends" +AvatarPanelWhisper = "Whisper" +AvatarPanelSecrets = "True Friends" +AvatarPanelGoTo = "Go To" +AvatarPanelPet = "Show Doodle" +AvatarPanelIgnore = "Ignore" +AvatarPanelIgnoreCant = "Okay" +AvatarPanelStopIgnoring = "Stop Ignoring" +AvatarPanelReport = "Report" +#AvatarPanelCogDetail = "Dept: %s\nLevel: %s\n" +AvatarPanelCogLevel = "Level: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Toon Details" +AvatarPanelGroupInvite = "Invite" +AvatarPanelGroupRetract = "Retract Invitation" +AvatarPanelGroupMember = "Already In Group" +AvatarPanelGroupMemberKick = "Remove" + +# Report Panel +ReportPanelTitle = "Report A Player" +ReportPanelBody = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Use \"Ignore\" on the toon's panel\n\nDo you really want to report %s to a Moderator?" +ReportPanelBodyFriends = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Break your friendship\n\nDo you really want to report %s to a Moderator?\n\n(This will also break your friendship)" +ReportPanelCategoryBody = "You are about to report %s. A Moderator will be alerted to your complaint and will take appropriate action for anyone breaking our rules. Please choose the reason you are reporting %s:" +ReportPanelBodyPlayer = "This feature is stilling being worked on and will be coming soon. In the meantime you can do the following:\n\n - Go to DXD and break the friendship there.\n - Tell a parent about what happened." + +ReportPanelCategoryLanguage = "Foul Language" +ReportPanelCategoryPii = "Sharing/Requesting Personal Info" +ReportPanelCategoryRude = "Rude or Mean Behavior" +ReportPanelCategoryName = "Bad Name" + +ReportPanelConfirmations = ( + "You are about to report that %s has used obscene, bigoted or sexually explicit language.", + "You are about to report that %s is being unsafe by giving out or requesting a phone number, address, last name, email address, password or account name.", + "You are about to report that %s is bullying, harassing, or using extreme behavior to disrupt the game.", + "You are about to report that %s has created a name that does not follow Disney's House Rules.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "We take reporting very seriously. Your report will be viewed by a Moderator who will take appropriate action for anyone breaking our rules. If your account is found to have participated in breaking the rules, or if you make false reports or abuse the 'Report a Player' system, a Moderator may take action against your account. Are you absolutely sure you want to report this player?" + +ReportPanelThanks = "Thank you! Your report has been sent to a Moderator for review. There is no need to contact us again about the issue. The moderation team will take appropriate action for a player found breaking our rules." + +ReportPanelRemovedFriend = "We have automatically removed %s from your Toon Friends List." +ReportPanelRemovedPlayerFriend = "We have automatically removed %s as a Player friend so as such you will not see them as your friend in any Disney product." + +ReportPanelAlreadyReported = "You have already reported %s during this session. A Moderator will review your previous report." + +# Report Panel +IgnorePanelTitle = "Ignore A Player" +IgnorePanelAddIgnore = "Would you like to ignore %s for the rest of this session?" +IgnorePanelIgnore = "You are now ignoring %s." +IgnorePanelRemoveIgnore = "Would you like to stop ignoring %s?" +IgnorePanelEndIgnore = "You are no longer ignoring %s." +IgnorePanelAddFriendAvatar = "%s is your friend, you cannot ignore them while you are friends." +IgnorePanelAddFriendPlayer = "%s (%s)is your friend, you cannot ignore them while you are friends." + +# PetAvatarPanel.py +PetPanelFeed = "Feed" +PetPanelCall = "Call" +PetPanelGoTo = "Go To" +PetPanelOwner = "Show Owner" +PetPanelDetail = "Pet Details" +PetPanelScratch = "Scratch" + +# PetDetailPanel.py +PetDetailPanelTitle = "Trick Training" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Jump', + 1: 'Beg', + 2: 'Play dead', + 3: 'Rollover', + 4: 'Backflip', + 5: 'Dance', + 6: 'Speak', + } + + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutral', + 'hunger': 'hungry', + 'boredom': 'bored', + 'excitement': 'excited', + 'sadness': 'sad', + 'restlessness': 'restless', + 'playfulness': 'playful', + 'loneliness': 'lonely', + 'fatigue': 'tired', + 'confusion': 'confused', + 'anger': 'angry', + 'surprise': 'surprised', + 'affection': 'affectionate', + } + +SpokenMoods = { + 'neutral': 'neutral', + 'hunger': ["I'm tired of JellyBeans! How'bout giving me a slice of pie?", + "How'bout a Red JellyBean? I'm tired of the Green ones!", + "Oh, those JellyBeans were for planting?!! But I'm hungry!", + ], + 'boredom': ["I'm dying of boredom over here!", + "You didn't think I understood you, huh?", + "Could we, like, DO something already?", + ], + 'excitement': ["Wow, it's you, it's you, it's you!", + "mmm, jellybeans, mmm!", + "Does it GET any better than this?", + "Happy April Toons' Week!", + ], + 'sadness': ["Don't go, Don't go, Don't go, Don't go, Don't go, Don't go, Don't go, Don't go, Don't go, Don't go, Don't go...", + "I'll be good, I promise!", + "I don't know WHY I'm sad, I just am!!!", + ], + 'restlessness': ["I'm sooo restless!!!",], + 'playfulness': ["Let's play, Let's play, Let's play, Let's play, Let's play, Let's play, Let's play, Let's play, Let's play...", + "Play with me or I dig up some flowers!", + "Lets run around and around and around and around and around and around...", + ], + 'loneliness': ["Where have you been?", + "Wanna cuddle?", + "I want to go with you when you fight Cogs!", + ], + 'fatigue': ["That swim in the pond really tired me out!", + "Being a Doodle is exhausting!", + "I gotta get to Dreamland!", + ], + 'confusion': ["Where am I? Who are you again?", + "What's a Toon-up again?", + "Whoa, I'm standing between you and the Cogs! Run away!", + ], + 'anger': ["... and you wonder why I never give you a Toon-up?!!!", + "You always leave me behind!", + "You love your gags more than you love me!" + ], + 'surprise': ["Of course Doodles can talk!", + "Toons can talk?!!", + "Whoa, where did you come from?", + ], + 'affection': ["You're the best Toon EVER!!!!!!!!!!", + "Do you even KNOW how great you are?!?", + "I am SO lucky to be with you!!!", + ], + } + +# DistributedAvatar.py +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Friends" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Trying to go to %s." +TeleportPanelNotAvailable = "%s is busy right now; try again later." +TeleportPanelIgnored = "%s is ignoring you." +TeleportPanelNotOnline = "%s isn't online right now." +TeleportPanelWentAway = "%s went away." +TeleportPanelUnknownHood = "You don't know how to get to %s!" +TeleportPanelUnavailableHood = "%s is not available right now; try again later." +TeleportPanelDenySelf = "You can't go to yourself!" +TeleportPanelOtherShard = "%(avName)s is in district %(shardName)s, and you're in district %(myShardName)s. Do you want to switch to %(shardName)s?" +TeleportPanelBusyShard = "%(avName)s is in a full District. Playing in a full District can severely slow down game performance. Are you sure you want to switch districts?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "I'm the boss." + +# DistributedBattleFactory.py +FactoryBossTaunt = "I'm the Foreman." +FactoryBossBattleTaunt = "Let me introduce you to the Foreman." +MintBossTaunt = "I'm the Supervisor." +MintBossBattleTaunt = "You need to talk to the Supervisor." +StageBossTaunt = "My Justice isn't Blind" +StageBossBattleTaunt = "I am above the Law" +CountryClubBossTaunt = "I'm the Club President." +CountryClubBossBattleTaunt = "You need to talk to the Club President." +ForcedLeaveCountryClubAckMsg = "The Club President was defeated before you could reach him. You did not recover any Stock Options." + +# HealJokes.py +ToonHealJokes = [ + ["What goes TICK-TICK-TICK-WOOF?", + "A watchdog! "], + ["Why do male deer need braces?", + "Because they have 'buck teeth'!"], + ["Why is it hard for a ghost to tell a lie?", + "Because you can see right through him."], + ["What did the ballerina do when she hurt her foot?", + "She called the toe truck!"], + ["What has one horn and gives milk?", + "A milk truck!"], + ["Why don't witches ride their brooms when they're angry?", + "They don't want to fly off the handle!"], + ["Why did the dolphin cross the ocean?", + "To get to the other tide."], + ["What kind of mistakes do spooks make?", + "Boo boos."], + ["Why did the chicken cross the playground?", + "To get to the other slide!"], + ["Where does a peacock go when he loses his tail?", + "A retail store."], + ["Why didn't the skeleton cross the road?", + "He didn't have the guts."], + ["Why wouldn't they let the butterfly into the dance?", + "Because it was a moth ball."], + ["What's gray and squirts jam at you?", + "A mouse eating a doughnut."], + ["What happened when 500 hares got loose on the main street?", + "The police had to comb the area."], + ["What's the difference between a fish and a piano?", + "You can tune a piano, but you can't tuna fish!"], + ["What do people do in clock factories?", + "They make faces all day."], + ["What do you call a blind dinosaur?", + "An I-don't-think-he-saurus."], + ["If you drop a white hat into the Red Sea, what does it become?", + "Wet."], + ["Why was Cinderella thrown off the basketball team?", + "She ran away from the ball."], + ["Why was Cinderella such a bad player?", + "She had a pumpkin for a coach."], + ["What two things can't you have for breakfast?", + "Lunch and dinner."], + ["What do you give an elephant with big feet?", + "Big shoes."], + ["Where do baby ghosts go during the day?", + "Day-scare centers."], + ["What did Snow White say to the photographer?", + "Some day my prints will come."], + ["What's Tarzan's favorite song?", + "Jungle bells."], + ["What's green and loud?", + "A froghorn."], + ["What's worse than raining cats and dogs?", + "Hailing taxis."], + ["When is the vet busiest?", + "When it's raining cats and dogs."], + ["What do you call a gorilla wearing ear-muffs?", + "Anything you want, he can't hear you."], + ["Where would you weigh a whale?", + "At a whale-weigh station."], + ["What travels around the world but stays in the corner?", + "A stamp."], + ["What do you give a pig with a sore throat?", + "Oinkment."], + ["What did the hat say to the scarf?", + "You hang around while I go on a head."], + ["What's the best parting gift?", + "A comb."], + ["What kind of cats like to go bowling?", + "Alley cats."], + ["What's wrong if you keep seeing talking animals?", + "You're having Disney spells."], + ["What did one eye say to the other?", + "Between you and me, something smells."], + ["What's round, white and giggles?", + "A tickled onion."], + ["What do you get when you cross Bambi with a ghost?", + "Bamboo."], + ["Why do golfers take an extra pair of socks?", + "In case they get a hole in one."], + ["What do you call a fly with no wings?", + "A walk."], + ["Who did Frankenstein take to the prom?", + "His ghoul friend."], + ["What lies on its back, one hundred feet in the air?", + "A sleeping centipede."], + ["How do you keep a bull from charging?", + "Take away his credit card."], + ["What do you call a chicken at the North Pole?", + "Lost."], + ["What do you get if you cross a cat with a dog?", + "An animal that chases itself."], + ["What did the digital watch say to the grandfather clock?", + "Look dad, no hands."], + ["Where does Ariel the mermaid go to see movies?", + "The dive-in."], + ["What do you call a mosquito with a tin suit?", + "A bite in shining armor."], + ["What do giraffes have that no other animal has?", + "Baby giraffes."], + ["Why did the man hit the clock?", + "Because the clock struck first."], + ["Why did the apple go out with a fig?", + "Because it couldn't find a date."], + ["What do you get when you cross a parrot with a monster?", + "A creature that gets a cracker whenever it asks for one."], + ["Why didn't the monster make the football team?", + "Because he threw like a ghoul!"], + ["What do you get if you cross a Cocker Spaniel with a Poodle and a rooster?", + "A cockapoodledoo!"], + ["What goes dot-dot-dash-dash-squeak?", + "Mouse code."], + ["Why aren't elephants allowed on beaches?", + "They can't keep their trunks up."], + ["What is at the end of everything?", + "The letter G."], + ["How do trains hear?", + "Through the engineers."], + ["What does the winner of a marathon lose?", + "His breath."], + ["Why did the pelican refuse to pay for his meal?", + "His bill was too big."], + ["What has six eyes but cannot see?", + "Three blind mice."], + ["What works only when it's fired?", + "A rocket."], + ["Why wasn't there any food left after the monster party?", + "Because everyone was a goblin!"], + ["What bird can be heard at mealtimes?", + "A swallow."], + ["What goes Oh, Oh, Oh?", + "Santa walking backwards."], + ["What has green hair and runs through the forest?", + "Moldy locks."], + ["Where do ghosts pick up their mail?", + "At the ghost office."], + ["Why do dinosaurs have long necks?", + "Because their feet smell."], + ["What do mermaids have on toast?", + "Mermarlade."], + ["Why do elephants never forget?", + "Because nobody ever tells them anything."], + ["What's in the middle of a jellyfish?", + "A jellybutton."], + ["What do you call a very popular perfume?", + "A best-smeller."], + ["Why can't you play jokes on snakes?", + "Because you can never pull their legs."], + ["Why did the baker stop making donuts?", + "He got sick of the hole business."], + ["Why do mummies make excellent spies?", + "They're good at keeping things under wraps."], + ["How do you stop an elephant from going through the eye of a needle?", + "Tie a knot in its tail."], + ["What goes 'Ha Ha Ha Thud'?", + "Someone laughing his head off."], + ["My friend thinks he's a rubber band.", + "I told him to snap out of it."], + ["My sister thinks she's a pair of curtains.", + "I told her to pull herself together!"], + ["Did you hear about the dentist that married the manicurist?", + "Within a month they were fighting tooth and nail."], + ["Why do hummingbirds hum?", + "Because they don't know the words."], + ["Why did the baby turkey bolt down his food?", + "Because he was a little gobbler."], + ["Where did the whale go when it was bankrupt?", + "To the loan shark."], + ["How does a sick sheep feel?", + "Baah-aahd."], + ["What's gray, weighs 10 pounds and squeaks?", + "A mouse that needs to go on a diet."], + ["Why did the dog chase his tail?", + "To make ends meet."], + ["Why do elephants wear running shoes?", + "For jogging of course."], + ["Why are elephants big and gray?", + "Because if they were small and yellow they'd be canaries."], + ["If athletes get tennis elbow what do astronauts get?", + "Missile toe."], + ["Did you hear about the man who hated Santa?", + "He suffered from Claustrophobia."], + ["Why did " + Donald + " sprinkle sugar on his pillow?", + "Because he wanted to have sweet dreams."], + ["Why did " + Goofy + " take his comb to the dentist?", + "Because it had lost all its teeth."], + ["Why did " + Goofy + " wear his shirt in the bath?", + "Because the label said wash and wear."], + ["Why did the dirty chicken cross the road?", + "For some fowl purpose."], + ["Why didn't the skeleton go to the party?", + "He had no body to go with."], + ["Why did the burglar take a shower?", + "To make a clean getaway."], + ["Why does a sheep have a woolly coat?", + "Because he'd look silly in a plastic one."], + ["Why do potatoes argue all the time?", + "They can't see eye to eye."], + ["Why did " + Pluto + " sleep with a banana peel?", + "So he could slip out of bed in the morning."], + ["Why did the mouse wear brown sneakers?", + "His white ones were in the wash."], + ["Why are false teeth like stars?", + "They come out at night."], + ["Why are Saturday and Sunday so strong?", + "Because the others are weekdays."], + ["Why did the archaeologist go bankrupt?", + "Because his career was in ruins."], + ["What do you get if you cross the Atlantic on the Titanic?", + "Very wet."], + ["What do you get if you cross a chicken with cement?", + "A brick-layer."], + ["What do you get if you cross a dog with a phone?", + "A golden receiver."], + ["What do you get if you cross an elephant with a shark?", + "Swimming trunks with sharp teeth."], + ["What did the tablecloth say to the table?", + "Don't move, I've got you covered."], + ["Did you hear about the time " + Goofy + " ate a candle?", + "He wanted a light snack."], + ["What did the balloon say to the pin?", + "Hi Buster."], + ["What did the big chimney say to the little chimney?", + "You're too young to smoke."], + ["What did the carpet say to the floor?", + "I got you covered."], + ["What did the necklace say to the hat?", + "You go ahead, I'll hang around."], + ["What goes zzub-zzub?", + "A bee flying backwards."], + ["How do you communicate with a fish?", + "Drop him a line."], + ["What do you call a dinosaur that's never late?", + "A prontosaurus."], + ["What do you get if you cross a bear and a skunk?", + "Winnie-the-phew."], + ["How do you clean a tuba?", + "With a tuba toothpaste."], + ["What do frogs like to sit on?", + "Toadstools."], + ["Why was the math book unhappy?", + "It had too many problems."], + ["Why was the school clock punished?", + "It tocked too much."], + ["What's a polygon?", + "A dead parrot."], + ["What needs a bath and keeps crossing the street?", + "A dirty double crosser."], + ["What do you get if you cross a camera with a crocodile?", + "A snap shot."], + ["What do you get if you cross an elephant with a canary?", + "A very messy cage."], + ["What do you get if you cross a jeweler with a plumber?", + "A ring around the bathtub."], + ["What do you get if you cross an elephant with a crow?", + "Lots of broken telephone poles."], + ["What do you get if you cross a plum with a tiger?", + "A purple people eater."], + ["What's the best way to save water?", + "Dilute it."], + ["What's a lazy shoe called?", + "A loafer."], + ["What's green, noisy and dangerous?", + "A thundering herd of cucumbers."], + ["What color is a shout?", + "Yellow!"], + ["What do you call a sick duck?", + "A mallardy."], + ["What's worse then a giraffe with a sore throat?", + "A centipede with athlete's foot."], + ["What goes ABC...slurp...DEF...slurp?", + "Someone eating alphabet soup."], + ["What's green and jumps up and down?", + "Lettuce at a dance."], + ["What's a cow after she gives birth?", + "De-calf-inated."], + ["What do you get if you cross a cow and a camel?", + "Lumpy milk shakes."], + ["What's white with black and red spots?", + "A Dalmatian with measles."], + ["What's brown has four legs and a trunk?", + "A mouse coming back from vacation."], + ["What does a skunk do when it's angry?", + "It raises a stink."], + ["What's gray, weighs 200 pounds and says, Here Kitty, kitty?", + "A 200 pound mouse."], + ["What's the best way to catch a squirrel?", + "Climb a tree and act like a nut."], + ["What's the best way to catch a rabbit?", + "Hide in a bush and make a noise like lettuce."], + ["What do you call a spider that just got married?", + "A newly web."], + ["What do you call a duck that robs banks?", + "A safe quacker."], + ["What's furry, meows and chases mice underwater?", + "A catfish."], + ["What's a funny egg called?", + "A practical yolker."], + ["What's green on the outside and yellow inside?", + "A banana disguised as a cucumber."], + ["What did the elephant say to the lemon?", + "Let's play squash."], + ["What weighs 4 tons, has a trunk and is bright red?", + "An embarrassed elephant."], + ["What's gray, weighs 4 tons, and wears glass slippers?", + "Cinderelephant."], + ["What's an elephant in a fridge called?", + "A very tight squeeze."], + ["What did the elephant say to her naughty child?", + "Tusk! Tusk!"], + ["What did the peanut say to the elephant?", + "Nothing -- Peanuts can't talk."], + ["What do elephants say when they bump into each other?", + "Small world, isn't it?"], + ["What did the cashier say to the register?", + "I'm counting on you."], + ["What did the flea say to the other flea?", + "Shall we walk or take the cat?"], + ["What did the big hand say to the little hand?", + "Got a minute."], + ["What does the sea say to the sand?", + "Not much. It usually waves."], + ["What did the stocking say to the shoe?", + "See you later, I gotta run."], + ["What did one tonsil say to the other tonsil?", + "It must be spring, here comes a swallow."], + ["What did the soil say to the rain?", + "Stop, or my name is mud."], + ["What did the puddle say to the rain?", + "Drop in sometime."], + ["What did the bee say to the rose?", + "Hi, bud."], + ["What did the appendix say to the kidney?", + "The doctor's taking me out tonight."], + ["What did the window say to the venetian blinds?", + "If it wasn't for you it'd be curtains for me."], + ["What did the doctor say to the sick orange?", + "Are you peeling well?"], + ["What do you get if you cross a chicken with a banjo?", + "A self-plucking chicken."], + ["What do you get if you cross a hyena with a bouillon cube?", + "An animal that makes a laughing stock of itself."], + ["What do you get if you cross a rabbit with a spider?", + "A hare net."], + ["What do you get if you cross a germ with a comedian?", + "Sick jokes."], + ["What do you get if you cross a hyena with a mynah bird?", + "An animal that laughs at its own jokes."], + ["What do you get if you cross a railway engine with a stick of gum?", + "A chew-chew train."], + ["What would you get if you crossed an elephant with a computer?", + "A big know-it-all."], + ["What would you get if you crossed an elephant with a skunk?", + "A big stinker."], + ["Why did " + MickeyMouse + " take a trip to outer space?", + "He wanted to find " + Pluto + "."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","heh","ha","harr harr") +MovieHealLaughterHits1= ("Ha Ha Ha","Hee Hee","Tee Hee","Ha Ha") +MovieHealLaughterHits2= ("BWAH HAH HAH!","HO HO HO!","HA HA HA!") + +# MovieSOS.py +MovieSOSCallHelp = "%s HELP!" +MovieSOSWhisperHelp = "%s needs help in battle!" +MovieSOSObserverHelp = "HELP!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Hi %s! Glad to help!" +MovieNPCSOSGoodbye = "See you later!" +MovieNPCSOSToonsHit = "Toons Always Hit!" +MovieNPCSOSCogsMiss = "Cogs Always Miss!" +MovieNPCSOSRestockGags = "Restocking %s gags!" +MovieNPCSOSHeal = "Heal" +MovieNPCSOSTrap = "Trap" +MovieNPCSOSLure = "Lure" +MovieNPCSOSSound = "Sound" +MovieNPCSOSThrow = "Throw" +MovieNPCSOSSquirt = "Squirt" +MovieNPCSOSDrop = "Drop" +MovieNPCSOSAll = "All" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Sigh" +MoviePetSOSTrickSucceedBoy = "Good boy!" +MoviePetSOSTrickSucceedGirl = "Good girl!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "CANCELLED\nCANCELLED\nCANCELLED" + +# RewardPanel.py +RewardPanelToonTasks = "ToonTasks" +RewardPanelItems = "Items Recovered" +RewardPanelMissedItems = "Items Not Recovered" +RewardPanelQuestLabel = "Quest %s" +RewardPanelCongratsStrings = ["Yeah!", "Congratulations!", "Wow!", + "Cool!", "Awesome!", "Toon-tastic!"] +RewardPanelNewGag = "New %(gagName)s gag for %(avName)s!" +RewardPanelUberGag = "%(avName)s earned the %(gagName)s gag with %(exp)s experience points!" +RewardPanelEndTrack = "Yay! %(avName)s has reached the end of the %(gagName)s Gag Track!" +RewardPanelMeritsMaxed = "Maxed" +RewardPanelMeritBarLabels = [ "Stock Options", "Jury Notices", "Cogbucks", "Merits" ] +RewardPanelMeritAlert = "Ready for promotion!" + +RewardPanelCogPart = "You gained a Cog disguise part!" +RewardPanelPromotion = "Ready for promotion in %s track!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Normal Toon", "you will be normal"), + ("Big head", "you will have a big head"), + ("Small head", "you will have a small head"), + ("Big legs", "you will have big legs"), + ("Small legs", "you will have small legs"), + ("Big toon", "you will be a little bigger"), + ("Small toon", "you will be a little smaller"), + ("Flat portrait", "you will be two-dimensional"), + ("Flat profile", "you will be two-dimensional"), + ("Transparent", "you will be transparent"), + ("No color", "you will be colorless"), + ("Invisible toon", "you will be invisible"), + ] +CheesyEffectIndefinite = "Until you choose another effect, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "For the next %(time)s minutes, %(effectName)s%(whileIn)s." +CheesyEffectHours = "For the next %(time)s hours, %(effectName)s%(whileIn)s." +CheesyEffectDays = "For the next %(time)s days, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " while you are in %s" +CheesyEffectExceptIn = ", except in %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Flunky" +SuitPencilPusher = "Pencil Pusher" +SuitYesman = "Yesman" +SuitMicromanager = "Micro\3manager" +SuitDownsizer = "Downsizer" +SuitHeadHunter = "Head Hunter" +SuitCorporateRaider = "Corporate Raider" +SuitTheBigCheese = "The Big Cheese" +SuitColdCaller = "Cold Caller" +SuitTelemarketer = "Tele\3marketer" +SuitNameDropper = "Name Dropper" +SuitGladHander = "Glad Hander" +SuitMoverShaker = "Mover & Shaker" +SuitTwoFace = "Two-Face" +SuitTheMingler = "The Mingler" +SuitMrHollywood = "Mr. Hollywood" +SuitShortChange = "Short Change" +SuitPennyPincher = "Penny Pincher" +SuitTightwad = "Tightwad" +SuitBeanCounter = "Bean Counter" +SuitNumberCruncher = "Number Cruncher" +SuitMoneyBags = "Money Bags" +SuitLoanShark = "Loan Shark" +SuitRobberBaron = "Robber Baron" +SuitBottomFeeder = "Bottom Feeder" +SuitBloodsucker = "Blood\3sucker" +SuitDoubleTalker = "Double Talker" +SuitAmbulanceChaser = "Ambulance Chaser" +SuitBackStabber = "Back Stabber" +SuitSpinDoctor = "Spin Doctor" +SuitLegalEagle = "Legal Eagle" +SuitBigWig = "Big Wig" + +# Singular versions (indefinite article) +SuitFlunkyS = "a Flunky" +SuitPencilPusherS = "a Pencil Pusher" +SuitYesmanS = "a Yesman" +SuitMicromanagerS = "a Micromanager" +SuitDownsizerS = "a Downsizer" +SuitHeadHunterS = "a Head Hunter" +SuitCorporateRaiderS = "a Corporate Raider" +SuitTheBigCheeseS = "a The Big Cheese" +SuitColdCallerS = "a Cold Caller" +SuitTelemarketerS = "a Telemarketer" +SuitNameDropperS = "a Name Dropper" +SuitGladHanderS = "a Glad Hander" +SuitMoverShakerS = "a Mover & Shaker" +SuitTwoFaceS = "a Two-Face" +SuitTheMinglerS = "a The Mingler" +SuitMrHollywoodS = "a Mr. Hollywood" +SuitShortChangeS = "a Short Change" +SuitPennyPincherS = "a Penny Pincher" +SuitTightwadS = "a Tightwad" +SuitBeanCounterS = "a Bean Counter" +SuitNumberCruncherS = "a Number Cruncher" +SuitMoneyBagsS = "a Money Bags" +SuitLoanSharkS = "a Loan Shark" +SuitRobberBaronS = "a Robber Baron" +SuitBottomFeederS = "a Bottom Feeder" +SuitBloodsuckerS = "a Bloodsucker" +SuitDoubleTalkerS = "a Double Talker" +SuitAmbulanceChaserS = "an Ambulance Chaser" +SuitBackStabberS = "a Back Stabber" +SuitSpinDoctorS = "a Spin Doctor" +SuitLegalEagleS = "a Legal Eagle" +SuitBigWigS = "a Big Wig" + +# Plural versions +SuitFlunkyP = "Flunkies" +SuitPencilPusherP = "Pencil Pushers" +SuitYesmanP = "Yesmen" +SuitMicromanagerP = "Micromanagers" +SuitDownsizerP = "Downsizers" +SuitHeadHunterP = "Head Hunters" +SuitCorporateRaiderP = "Corporate Raiders" +SuitTheBigCheeseP = "The Big Cheeses" +SuitColdCallerP = "Cold Callers" +SuitTelemarketerP = "Telemarketers" +SuitNameDropperP = "Name Droppers" +SuitGladHanderP = "Glad Handers" +SuitMoverShakerP = "Movers & Shakers" +SuitTwoFaceP = "Two-Faces" +SuitTheMinglerP = "The Minglers" +SuitMrHollywoodP = "Mr. Hollywoods" +SuitShortChangeP = "Short Changes" +SuitPennyPincherP = "Penny Pinchers" +SuitTightwadP = "Tightwads" +SuitBeanCounterP = "Bean Counters" +SuitNumberCruncherP = "Number Crunchers" +SuitMoneyBagsP = "Money Bags" +SuitLoanSharkP = "Loan Sharks" +SuitRobberBaronP = "Robber Barons" +SuitBottomFeederP = "Bottom Feeders" +SuitBloodsuckerP = "Bloodsuckers" +SuitDoubleTalkerP = "Double Talkers" +SuitAmbulanceChaserP = "Ambulance Chasers" +SuitBackStabberP = "Back Stabbers" +SuitSpinDoctorP = "Spin Doctors" +SuitLegalEagleP = "Legal Eagles" +SuitBigWigP = "Big Wigs" + +SuitFaceoffDefaultTaunts = ['Boo!'] + +SuitAttackDefaultTaunts = ['Take that!', 'Take a memo on this!'] + +SuitAttackNames = { + 'Audit' : 'Audit!', + 'Bite' : 'Bite!', + 'BounceCheck' : 'Bounce Check!', + 'BrainStorm' : 'Brain Storm!', + 'BuzzWord' : 'Buzz Word!', + 'Calculate' : 'Calculate!', + 'Canned' : 'Canned!', + 'Chomp' : 'Chomp!', + 'CigarSmoke' : 'Cigar Smoke!', + 'ClipOnTie' : 'Clip On Tie!', + 'Crunch' : 'Crunch!', + 'Demotion' : 'Demotion!', + 'Downsize' : 'Downsize!', + 'DoubleTalk' : 'Double Talk!', + 'EvictionNotice' : 'Eviction Notice!', + 'EvilEye' : 'Evil Eye!', + 'Filibuster' : 'Filibuster!', + 'FillWithLead' : 'Fill With Lead!', + 'FiveOClockShadow' : "Five O'Clock Shadow!", + 'FingerWag' : 'Finger Wag!', + 'Fired' : 'Fired!', + 'FloodTheMarket' : 'Flood The Market!', + 'FountainPen' : 'Fountain Pen!', + 'FreezeAssets' : 'Freeze Assets!', + 'Gavel' : 'Gavel!', + 'GlowerPower' : 'Glower Power!', + 'GuiltTrip' : 'Guilt Trip!', + 'HalfWindsor' : 'Half Windsor!', + 'HangUp' : 'Hang Up!', + 'HeadShrink' : 'Head Shrink!', + 'HotAir' : 'Hot Air!', + 'Jargon' : 'Jargon!', + 'Legalese' : 'Legalese!', + 'Liquidate' : 'Liquidate!', + 'MarketCrash' : 'Market Crash!', + 'MumboJumbo' : 'Mumbo Jumbo!', + 'ParadigmShift' : 'Paradigm Shift!', + 'PeckingOrder' : 'Pecking Order!', + 'PickPocket' : 'Pick Pocket!', + 'PinkSlip' : 'Pink Slip!', + 'PlayHardball' : 'Play Hardball!', + 'PoundKey' : 'Pound Key!', + 'PowerTie' : 'Power Tie!', + 'PowerTrip' : 'Power Trip!', + 'Quake' : 'Quake!', + 'RazzleDazzle' : 'Razzle Dazzle!', + 'RedTape' : 'Red Tape!', + 'ReOrg' : 'Re-Org!', + 'RestrainingOrder' : 'Restraining Order!', + 'Rolodex' : 'Rolodex!', + 'RubberStamp' : 'Rubber Stamp!', + 'RubOut' : 'Rub Out!', + 'Sacked' : 'Sacked!', + 'SandTrap' : 'Sand Trap!', + 'Schmooze' : 'Schmooze!', + 'Shake' : 'Shake!', + 'Shred' : 'Shred!', + 'SongAndDance' : 'Song And Dance!', + 'Spin' : 'Spin!', + 'Synergy' : 'Synergy!', + 'Tabulate' : 'Tabulate!', + 'TeeOff' : 'Tee Off!', + 'ThrowBook' : 'Throw Book!', + 'Tremor' : 'Tremor!', + 'Watercooler' : 'Watercooler!', + 'Withdrawal' : 'Withdrawal!', + 'WriteOff' : 'Write Off!', + } + +SuitAttackTaunts = { + 'Audit': ["I believe your books don't balance.", + "Looks like you're in the red.", + "Let me help you with your books.", + "Your debit column is much too high.", + "Let's check your assets.", + "This will put you in debt.", + "Let's take a close look at what you owe.", + "This should drain your account.", + "Time for you to account for your expenses.", + "I've found an error in your books.", + ], + 'Bite': ["Would you like a bite?", + "Try a bite of this!", + "You're biting off more than you can chew.", + "My bite is bigger than my bark.", + "Bite down on this!", + "Watch out, I may bite.", + "I don't just bite when I'm cornered.", + "I'm just gonna grab a quick bite.", + "I haven't had a bite all day.", + "I just want a bite. Is that too much to ask?", + ], + 'BounceCheck': ["Ah, too bad, you're funless.", + "You have a payment due.", + "I believe this check is yours.", + "You owed me for this.", + "I'm collecting on this debt.", + "This check isn't going to be tender.", + "You're going to be charged for this.", + "Check this out.", + "This is going to cost you.", + "I'd like to cash this in.", + "I'm just going to kick this back to you.", + "This is one sour note.", + "I'm deducting a service charge.", + ], + 'BrainStorm':["I forecast rain.", + "Hope you packed your umbrella.", + "I want to enlighten you.", + "How about a few rain DROPS?", + "Not so sunny now, are you Toon?", + "Ready for a down pour?", + "I'm going to take you by storm.", + "I call this a lightning attack.", + "I love to be a wet blanket.", + ], + 'BuzzWord':["Pardon me if I drone on.", + "Have you heard the latest?", + "Can you catch on to this?", + "See if you can hum this Toon.", + "Let me put in a good word for you.", + "I'll \"B\" perfectly clear.", + "You should \"B\" more careful.", + "See if you can dodge this swarm.", + "Careful, you're about to get stung.", + "Looks like you have a bad case of hives.", + ], + 'Calculate': ["These numbers do add up!", + "Did you count on this?", + "Add it up, you're going down.", + "Let me help you add this up.", + "Did you register all your expenses?", + "According to my calculations, you won't be around much longer.", + "Here's the grand total.", + "Wow, your bill is adding up.", + "Try fiddling with these numbers!", + Cogs + ": 1 Toons: 0", + ], + 'Canned': ["Do you like it out of the can?", + "\"Can\" you handle this?", + "This one's fresh out of the can!", + "Ever been attacked by canned goods before?", + "I'd like to donate this canned good to you!", + "Get ready to \"Kick the can\"!", + "You think you \"can\", you think you \"can\".", + "I'll throw you in the can!", + "I'm making me a can o' toon-a!", + "You don't taste so good out of the can.", + ], + 'Chomp': ["Take a look at these chompers!", + "Chomp, chomp, chomp!", + "Here's something to chomp on.", + "Looking for something to chomp on?", + "Why don't you chomp on this?", + "I'm going to have you for dinner.", + "I love to feed on Toons!", + ], + 'ClipOnTie': ["Better dress for our meeting.", + "You can't go OUT without your tie.", + "The best dressed " + Cogs + " wear them.", + "Try this on for size.", + "You should dress for success.", + "No tie, no service.", + "Do you need help putting this on?", + "Nothing says powerful like a good tie.", + "Let's see if this fits.", + "This is going to choke you up.", + "You'll want to dress up before you go OUT.", + "I think I'll tie you up.", + ], + 'Crunch': ["Looks like you're in a crunch.", + "It's crunch time!", + "I'll give you something to crunch on!", + "Crunch on this!", + "I pack quite a crunch.", + "Which do you prefer, smooth or crunchy?", + "I hope you're ready for crunch time.", + "It sounds like you're getting crunched!", + "I'll crunch you like a can." + ], + 'Demotion': ["You're moving down the corporate ladder.", + "I'm sending you back to the Mail Room.", + "Time to turn in your nameplate.", + "You're going down, clown.", + "Looks like you're stuck.", + "You're going nowhere fast.", + "You're in a dead end position.", + "You won't be moving anytime soon.", + "You're not going anywhere.", + "This will go on your permanent record.", + ], + 'Downsize': ["Come on down!", + "Do you know how to get down?", + "Let's get down to business.", + "What's wrong? You look down.", + "Going down?", + "What's goin' down? You!", + "Why pick on people my own size?", + "Why don't I size you up, or should I say, down?", + "Would you like a smaller size for just a quarter more?", + "Try this on for size!", + "You can get this in a smaller size.", + "This attack is one size fits all!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["It's moving time.", + "Pack your bags, Toon.", + "Time to make some new living arrangements.", + "Consider yourself served.", + "You're behind on your lease.", + "This will be extremely unsettling.", + "You're about to be uprooted.", + "I'm going to send you packing.", + "You're out of place.", + "Prepare to be relocated.", + "You're in a hostel position.", + ], + 'EvilEye': ["I'm giving you the evil eye.", + "Could you eye-ball this for me?", + "Wait. I've got something in my eye.", + "I've got my eye on you!", + "Could you keep an eye on this for me?", + "I've got a real eye for evil.", + "I'll poke you in the eye!", + "\"Eye\" am as evil as they come!", + "I'll put you in the eye of the storm!", + "I'm rolling my eye at you.", + ], + 'Filibuster':["Shall I fill 'er up?", + "This is going to take awhile.", + "I could do this all day.", + "I don't even need to take a breath.", + "I keep going and going and going.", + "I never get tired of this one.", + "I can talk a blue streak.", + "Mind if I bend your ear?", + "I think I'll shoot the breeze.", + "I can always get a word in edgewise.", + ], + 'FingerWag': ["I have told you a thousand times.", + "Now see here Toon.", + "Don't make me laugh.", + "Don't make me come over there.", + "I'm tired of repeating myself.", + "I believe we've been over this.", + "You have no respect for us " + Cogs + ".", + "I think it's time you pay attention.", + "Blah, Blah, Blah, Blah, Blah.", + "Don't make me stop this meeting.", + "Am I going to have to separate you?", + "We've been through this before.", + ], + 'Fired': ["I hope you brought some marshmallows.", + "It's going to get rather warm around here.", + "This should take the chill out of the air.", + "I hope you're cold blooded.", + "Hot, hot and hotter.", + "You better stop, drop, and roll!", + "You're outta here.", + "How does \"well-done\" sound?", + "Can you say ouch?", + "Hope you wore sunscreen.", + "Do you feel a little toasty?", + "You're going down in flames.", + "You'll go out in a blaze.", + "You're a flash in the pan.", + "I think I have a bit of a flare about me.", + "I just sparkle, don't I?", + "Oh look, a crispy critter.", + "You shouldn't run around half baked.", + ], + 'FountainPen': ["This is going to leave a stain.", + "Let's ink this deal.", + "Be prepared for some permanent damage.", + "You're going to need a good dry cleaner.", + "You should change.", + "This fountain pen has such a nice font.", + "Here, I'll use my pen.", + "Can you read my writing?", + "I call this the plume of doom.", + "There's a blot on your performance.", + "Don't you hate when this happens?", + ], + 'FreezeAssets': ["Your assets are mine.", + "Do you feel a draft?", + "Hope you don't have plans.", + "This should keep you on ice.", + "There's a chill in the air.", + "Winter is coming early this year.", + "Are you feeling a little blue?", + "Let me crystallize my plan.", + "You're going to take this hard.", + "This should cause freezer burn.", + "I hope you like cold cuts.", + "I'm very cold blooded.", + ], + 'GlowerPower': ["You looking at me?", + "I'm told I have very piercing eyes.", + "I like to stay on the cutting edge.", + "Jeepers, Creepers, don't you love my peepers?", + "Here's looking at you kid.", + "How's this for expressive eyes?", + "My eyes are my strongest feature.", + "The eyes have it.", + "Peeka-boo, I see you.", + "Look into my eyes...", + "Shall we take a peek at your future?", + ], + 'GuiltTrip': ["I'll lay a real guilt trip on you!", + "Feeling guilty?", + "It's all your fault!", + "I always blame everything on you.", + "Wallow in your own guilt!", + "I'm never speaking to you again!", + "You had better say you're sorry.", + "I'm would forgive you in a million years!", + "Are you ready for your trip?", + "Call me when you get back from your trip.", + "When do you get back from your trip?", + ], + 'HalfWindsor': ["This is the fanciest tie you'll ever see!", + "Try not to get too winded.", + "This isn't even half the trouble you're in.", + "You're lucky I don't have a whole windsor.", + "You can't afford this tie.", + "I bet you've never even SEEN a half windsor!", + "This tie is out of your league.", + "I shouldn't even waste this tie on you.", + "You're not even worth half of this tie!", + ], + 'HangUp': ["You've been disconnected.", + "Good bye!", + "It's time I end our connection.", + "...and don't call back!", + "Click!", + "This conversation is over.", + "I'm severing this link.", + "I think you have a few hang ups.", + "It appears you've got a weak link.", + "Your time is up.", + "I hope you receive this loud and clear.", + "You got the wrong number.", + ], + 'HeadShrink': ["Looks like you're seeing a shrink.", + "Honey, I shrunk the toon.", + "Hope this doesn't shrink your pride.", + "Do you shrink in the wash?", + "I shrink therefore I am.", + "It's nothing to lose your head over.", + "Are you going out of your head?", + "Heads up! Or should I say, down.", + "Objects may be larger than they appear.", + "Good Toons come in small packages.", + ], + 'HotAir':["We're having a heated discussion.", + "You're experiencing a heat wave.", + "I've reached my boiling point.", + "This should cause some wind burn.", + "I hate to grill you, but...", + "Always remember, where there's smoke, there's fire.", + "You're looking a little burned out.", + "Another meeting up in smoke.", + "Guess it's time to add fuel to the fire.", + "Let me kindle a working relationship.", + "I have some glowing remarks for you.", + "Air Raid!!!", + ], + 'Jargon':["What nonsense.", + "See if you can make sense of this.", + "I hope you get this loud and clear.", + "Looks like I'm going to have to raise my voice.", + "I insist on having my say.", + "I'm very outspoken.", + "I must pontificate on this subject.", + "See, words can hurt you.", + "Did you catch my meaning?", + "Words, words, words, words, words.", + ], + 'Legalese':["You must cease and desist.", + "You will be defeated, legally speaking.", + "Are you aware of the legal ramifications?", + "You aren't above the law!", + "There should be a law against you.", + "There's no ex post facto with me!", + "The opinions expressed in this attack are not those of Disney's Toontown Online.", + "We cannot be held responsible for damages suffered in this attack.", + "Your results for this attack may vary.", + "This attack is void where prohibited.", + "You don't fit into my legal system!", + "You can't handle the legal matters.", + ], + 'Liquidate':["I like to keep things fluid.", + "Are you having some cash flow problems?", + "I'll have to purge your assets.", + "Time for you to go with the flow.", + "Remember it's slippery when wet.", + "Your numbers are running.", + "You seem to be slipping.", + "It's all crashing down on you.", + "I think you're diluted.", + "You're all washed up.", + ], + 'MarketCrash':["I'm going to crash your party.", + "You won't survive the crash.", + "I'm more than the market can bear.", + "I've got a real crash course for you!", + "Now I'll come crashing down.", + "I'm a real bull in the market.", + "Looks like the market is going down.", + "You had better get out quick!", + "Sell! Sell! Sell!", + "Shall I lead the recession?", + "Everybody's getting out, shouldn't you?", + ], + 'MumboJumbo':["Let me make this perfectly clear.", + "It's as simple as this.", + "This is how we're going to do this.", + "Let me supersize this for you.", + "You might call this technobabble.", + "Here are my five-dollar words.", + "Boy, this is a mouth full.", + "Some call me bombastic.", + "Let me just interject this.", + "I believe these are the right words.", + ], + 'ParadigmShift':["Watch out! I'm rather shifty.", + "Prepare to have your paradigm shifted!", + "Isn't this an interesting paradigm.", + "You'll get shifted out of place.", + "I guess it's your shift now.", + "Your shift is up!", + "You've never shifted this much in your life.", + "I'm giving you the bad shift!", + "Look into my shifty eyes!", + ], + 'PeckingOrder':["This one's for the birds.", + "Get ready for a bird bath.", + "Looks like you're going to hit a birdie.", + "Some think this attack is fowl.", + "You're on the bottom of the pecking order.", + "I bird in my hand is worth ten on your head!", + "Your order is up; the pecking order!", + "Why don't I peck on someone my own size? Nah.", + "Birds of a feather strike together.", + ], + 'PickPocket': ["Let me check your valuables.", + "Hey, what's that over there?", + "Like taking candy from a baby.", + "What a steal.", + "I'll hold this for you.", + "Watch my hands at all times.", + "The hand is quicker than the eye.", + "There's nothing up my sleeve.", + "The management is not responsible for lost items.", + "Finder's keepers.", + "You'll never see it coming.", + "One for me, none for you.", + "Don't mind if I do.", + "You won't be needing this...", + ], + 'PinkSlip': ["Try not to slip up.", + "Are you frightened? You've turned pink!", + "This one will surely slip you up.", + "Oops, I guess you slipped there, huh?", + "Watch yourself, wouldn't want to slip!", + "This one's slippery when wet.", + "I'll just slip this one in.", + "Don't mind if you slip by, do you?", + "Pink isn't really your color.", + "Here's your pink slip, you're outta here!", + ], + 'PlayHardball': ["So you wanna play hardball?", + "You don't wanna play hardball with me.", + "Batter up!", + "Hey batter, batter!", + "And here's the pitch...", + "You're going to need a relief pitcher.", + "I'm going to knock you out of the park.", + "Once you get hit, you'll run home.", + "This is your final inning!", + "You can't play with me!", + "I'll strike you out.", + "I'm throwing you a real curve ball!", + ], + 'PoundKey': ["Time to return some calls.", + "I'd like to make a collect call.", + "Ring-a-ling - it's for you!", + "I've been wanting to drop a pound or two.", + "I have a lot of clout.", + "This may cause a slight pounding sensation.", + "I'll just punch in this number.", + "Let me call up a little surprise.", + "I'll ring you up.", + "O.K. Toon, it's the pound for you.", + ], + 'PowerTie': ["I'll call later, you looked tied up.", + "Are you ready to tie die?", + "Ladies and gentlemen, it's a tie!", + "You had better learn how to tie.", + "I'll have you tongue-tied!", + "This is the worst tie you'll ever get!", + "Can you feel the power?", + "My powers are far too great for you!", + "I've got the power!", + "By the powers vested in me, I'll tie you up.", + ], + 'PowerTrip': ["Pack your bags, we're taking a little trip.", + "Did you have a nice trip?", + "Nice trip, I guess I'll see you next fall.", + "How was your trip?", + "Sorry to trip you up there!", + "You look a little tripped up.", + "Now you see who's in power!", + "I am much more powerful than you.", + "Who's got the power now?", + "You can't fight the power.", + "Power corrupts, especially in my hands!", + ], + 'Quake': ["Let's quake, rattle, and roll.", + "I've got a whole lot of quakin' goin' on!", + "I see you quakin' in your shoes.", + "Here it comes, it's the big one!", + "This one's off the Richter scale.", + "Now the earth will quake!", + "Hey, what's shakin'? You!", + "Ever been in an earthquake?", + "You're on shaky ground now!", + ], + 'RazzleDazzle': ["Read my lips.", + "How about these choppers?", + "Aren't I charming?", + "I'm going to wow you.", + "My dentist does excellent work.", + "Blinding aren't they?", + "Hard to believe these aren't real.", + "Shocking, aren't they?", + "I'm going to cap this off.", + "I floss after every meal.", + "Say Cheese!", + ], + 'RedTape': ["This should wrap things up.", + "I'm going to tie you up for awhile.", + "You're on a roll.", + "See if you can cut through this.", + "This will get sticky.", + "Hope you're claustrophobic.", + "I'll make sure you stick around.", + "Let me keep you busy.", + "Just try to unravel this.", + "I want this meeting to stick with you.", + ], + 'ReOrg': ["You don't like the way I reorganized things!", + "Perhaps a little reorganization is in order.", + "You're not that bad, you just need to be reorganized.", + "Do you like my organizational skills.", + "I just thought I'd give things a new look.", + "You need to get organized!", + "You're looking a little disorganized.", + "Hold on while I reorganize your thoughts.", + "I'll just wait for you to get a little organized.", + "You don't mind if I just reorganize a bit?", + ], + 'RestrainingOrder': ["You should show a little restraint.", + "I'm slapping you with a restraining order!", + "You can't come within five feet of me.", + "Perhaps you better keep your distance.", + "You should be restrained.", + Cogs + "! Restrain that Toon!", + "Try and restrain yourself.", + "I hope I'm being too much of a restraint on you.", + "See if you can lift these restraints!", + "I'm ordering you to restrain!", + "Why don't we start with basic restraining?" + ], + 'Rolodex': ["Your card's in here somewhere.", + "Here's the number for a pest exterminator.", + "I want to give you my card.", + "I've got your number right here.", + "I've got you covered from a-z.", + "You'll flip over this.", + "Take this for a spin.", + "Watch out for paper cuts.", + "I'll let my fingers do the knocking.", + "Is this how I can contact you?", + "I want to make sure we stay in touch.", + ], + 'RubberStamp': ["I always make a good impression.", + "It's important to apply firm and even pressure.", + "A perfect imprint every time.", + "I want to stamp you out.", + "You must be RETURNED TO SENDER.", + "You've been CANCELLED.", + "You have a PRIORITY delivery.", + "I'll make sure you RECEIVED my message.", + "You're not going anywhere - you have POSTAGE DUE.", + "I'll need a response ASAP.", + ], + 'RubOut': ["And now for my disappearing act.", + "I sense I've lost you somewhere.", + "I decided to leave you out.", + "I always rub out all obstacles.", + "I'll just erase this error.", + "I can make any nuisance disappear.", + "I like things neat and tidy.", + "Please try and stay animated.", + "Now I see you... now I don't.", + "This will cause some fading.", + "I'm going to eliminate the problem.", + "Let me take care of your problem areas.", + ], + 'Sacked':["Looks like you're getting sacked.", + "This one's in the bag.", + "You've been bagged.", + "Paper or plastic?", + "My enemies shall be sacked!", + "I hold the Toontown record in sacks per game.", + "You're no longer wanted around here.", + "Your time is up around here, you're being sacked!", + "Let me bag that for you.", + "No defense can match my sack attack!", + ], + 'Schmooze':["You'll never see this coming.", + "This will look good on you.", + "You've earned this.", + "I don't mean to gush.", + "Flattery will get me everywhere.", + "I'm going to pile it on now.", + "Time to lay it on thick.", + "I'm going to get on your good side.", + "That deserves a good slap on the back.", + "I'm going to ring your praises.", + "I hate to knock you off your pedestal, but...", + ], + 'Shake': ["You're right on the epicenter.", + "You're standing on a fault line.", + "It's going to be a bumpy ride.", + "I think of this as a natural disaster.", + "It's a disaster of seismic proportions.", + "This one's off the Richter scale.", + "Time to duck and cover.", + "You seem disturbed.", + "Ready for a jolt?", + "I'll have you shaken, not stirred.", + "This will shake you up.", + "I suggest a good escape plan.", + ], + 'Shred': ["I need to get rid of some hazardous waste.", + "I'm increasing my throughput.", + "I think I'll dispose of you right now.", + "This will get rid of the evidence.", + "There's no way to prove it now.", + "See if you can put this back together.", + "This should cut you down to size.", + "I'm going to rip that idea to shreds.", + "We don't want this to fall into the wrong hands.", + "Easy come, easy go.", + "Isn't this your last shred of hope?", + ], + 'Spin': ["What do you say we go for a little spin?", + "Do you use the spin cycle?", + "This'll really make your head spin!", + "Here's my spin on things.", + "I'll take you for a spin.", + "How do you like to \"spin\" your time?", + "Watch it. Wouldn't want to spin out of control!", + "Oh what a spin you're in!", + "My attacks will make your head spin!", + ], + 'Synergy': ["I'm taking this to committee.", + "Your project's been cancelled.", + "Your budget's been cut.", + "We're restructuring your division.", + "I put it to a vote, and you lose.", + "I just received the final approval.", + "A good team can get rid of any problem.", + "I'll get back to you on this.", + "Let's get right to business.", + "Consider this a Synergy crisis.", + ], + 'Tabulate': ["This doesn't add up.", + "By my count, you lose.", + "You're racking up quite a tab.", + "I'll have you totaled in a moment.", + "Are you ready for these numbers?", + "Your bill is now due and payable.", + "Time for the reckoning.", + "I like to put things in order.", + "And the tally is...", + "These numbers should prove to be quite powerful.", + ], + 'TeeOff': ["You're not up to par.", + "Fore!", + "I'm getting teed off.", + "Caddie, I'll need my driver!", + "Just try and avoid this hazard.", + "Swing!", + "This is a sure hole in one.", + "You're in my fairway.", + "Notice my grip.", + "Watch the birdie!", + "Keep your eye on the ball!", + "Mind if I play through?", + ], + 'Tremor': ["Did you feel that?", + "Not afraid of a little tremor are you?", + "A tremor is only the beginning.", + "You look jittery.", + "I'll shake things up a bit!", + "Are you ready to rumble?", + "What's wrong? You look shaken.", + "Tremor with fear!", + "Why are you tremoring with fear?", + ], + 'Watercooler': ["This ought to cool you off.", + "Isn't this refreshing?", + "I deliver.", + "Straight from the tap - into your lap.", + "What's the matter, it's just spring water.", + "Don't worry, it's purified.", + "Ah, another satisfied customer.", + "It's time for your daily delivery.", + "Hope your colors don't run.", + "Care for a drink?", + "It all comes out in the wash.", + "The drink's on you.", + ], + 'Withdrawal': ["I believe you're overdrawn.", + "I hope your balance is high enough for this.", + "Take that, with interest.", + "Your balance is dropping.", + "You're going to need to make a deposit soon.", + "You've suffered an economic collapse.", + "I think you're in a slump.", + "Your finances have taken a decline.", + "I foresee a definite downturn.", + "It's a reversal of fortune.", + ], + 'WriteOff': ["Let me increase your losses.", + "Let's make the best of a bad deal.", + "Time to balance the books.", + "This won't look good on your books.", + "I'm looking for some dividends.", + "You must account for your losses.", + "You can forget about a bonus.", + "I'll shuffle your accounts around.", + "You're about to suffer some losses.", + "This is going to hurt your bottom line.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Waiting for other players...", + +# Elevator.py +ElevatorHopOff = "Hop off" +ElevatorStayOff = "If you hop off, you'll need to wait\nfor the elevator to leave or empty." +ElevatorLeaderOff = "Only your leader can decide when to hop off." +ElevatorHoppedOff = "You need to wait for the next elevator." +ElevatorMinLaff = "You need %s laff points to ride this elevator." +ElevatorHopOK = "Okay" +ElevatorGroupMember = "Only your group leader can\n decide when to board." + +# DistributedCogKart.py +KartMinLaff = "You need %s laff points to ride this kart" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Inc." +CogsIncModifier = "%s" + CogsIncExt +CogsInc = Cogs.upper() + CogsIncExt +CogdominiumsExt = " Cogdominiums" +Cogdominiums = Cog.upper() + CogdominiumsExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Knock, knock." +DoorWhosThere = "Who's there?" +DoorWhoAppendix = " who?" +DoorNametag = "Door" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "You need gags! Go talk to Tutorial Tom!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Come back here when you have defeated the Flunky!" +FADoorCodes_TALK_TO_HQ = "Go get your reward from HQ Harry!" +FADoorCodes_WRONG_DOOR_HQ = "Wrong door! Take the other door to the playground!" +FADoorCodes_GO_TO_PLAYGROUND = "Wrong way! You need to go to the playground!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Walk up to that Flunky to battle him!" +FADoorCodes_TALK_TO_HQ_TOM = "Go get your reward from Toon Headquarters!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Watch out! There's a Cog in there!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "You'll get caught going in there as a Toon! You need to complete your Sellbot Disguise first!\n\nBuild your Sellbot Disguise out of parts from the Factory." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "You'll get caught going in there as a Toon! You need to complete your Cashbot Disguise first!\n\nBuild your Cashbot Disguise by doing ToonTasks in Donald's Dreamland." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "You'll get caught going in there as a Toon! You need to complete your Lawbot Disguise first!\n\nBuild your Lawbot Disguise by doing the ToonTasks after Donald's Dreamland." +FADoorCodes_BB_DISGUISE_INCOMPLETE = "You'll get caught going in there as a Toon! You need to complete your Bossbot Disguise first!\n\nBuild your Bossbot Disguise by doing the ToonTasks after Donald's Dreamland." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Wally", + "Wally's not looking, hit him with a pie!"], + + # 2009 April fools contest Jokes. First few doors of Loopy lane + 2200 : {28:["Biscuit", + "Biscuit out of here the Cogs are coming!"], + 41:["Dewey", + "Dewey want to go defeat some more Cogs?"], + 40:["Minnie", + "Minnie people have asked that, and it's driving me crazy!"], +## 25:["Biscuit25", +## "Biscuit out of here the Cogs are coming!"], + 27:["Disguise", + "Disguise where the Cogs fly!"]}, + + 2300: ["Justin", + "Justin other couple of Cog parts and off we go!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdin HQ wants a word with you."], + 6 : ["Weirdo", + "Weirdo all these Cogs come from?"], + 30 : ["Bacon", + "Bacon a cake to throw at the Cogs."], + 28: ["Isaiah", + "Isaiah we go ride the trolley."], + 12: ["Juliet", + "Juliet me in that Cog building with you and I'll give you a Toon-Up."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Who", + "Bad echo in here, isn't there?"], + + ["Dozen", + "Dozen anybody want to let me in?"], + + ["Freddie", + "Freddie or not, here I come."], + + ["Dishes", + "Dishes your friend, let me in."], + + ["Wooden shoe", + "Wooden shoe like to know."], + + ["Betty", + "Betty doesn't know who I am."], + + ["Kent", + "Kent you tell?"], + + ["Noah", + "Noah don't know who either."], + + ["I don't know", + "Neither do I, I keep telling you that."], + + ["Howard", + "Howard I know?"], + + ["Emma", + "Emma so glad you asked me that."], + + ["Auto", + "Auto know, but I've forgotten."], + + ["Jess", + "Jess me and my shadow."], + + ["One", + "One-der why you keep asking that?"], + + ["Alma", + "Alma not going to tell you!"], + + ["Zoom", + "Zoom do you expect?"], + + ["Amy", + "Amy fraid I've forgotten."], + + ["Arfur", + "Arfur got."], + + ["Ewan", + "No, just me"], + + ["Cozy", + "Cozy who's knocking will you?"], + + ["Sam", + "Sam person who knocked on the door last time."], + + ["Fozzie", + "Fozzie hundredth time, my name is " + Flippy + "."], + + ["Deduct", + Donald + " Deduct."], + + ["Max", + "Max no difference, just open the door."], + + ["N.E.", + "N.E. body you like, let me in."], + + ["Amos", + "Amos-quito bit me."], + + ["Alma", + "Alma candy's gone."], + + ["Bruce", + "I Bruce very easily, don't hit me."], + + ["Colleen", + "Colleen up your room, it's filthy."], + + ["Elsie", + "Elsie you later."], + + ["Hugh", + "Hugh is going to let me in?"], + + ["Hugo", + "Hugo first - I'm scared."], + + ["Ida", + "Ida know. Sorry!"], + + ["Isabel", + "Isabel on a bike really necessary?"], + + ["Joan", + "Joan call us, we'll call you."], + + ["Kay", + "Kay, L, M, N, O, P."], + + ["Justin", + "Justin time for dinner."], + + ["Liza", + "Liza wrong to tell."], + + ["Luke", + "Luke and see who it is."], + + ["Mandy", + "Mandy the lifeboats, we're sinking."], + + ["Max", + "Max no difference - just open the door!"], + + ["Nettie", + "Nettie as a fruitcake."], + + ["Olivia", + "Olivia me alone!"], + + ["Oscar", + "Oscar stupid question, you get a stupid answer."], + + ["Patsy", + "Patsy dog on the head, he likes it."], + + ["Paul", + "Paul hard, the door's stuck again."], + + ["Thea", + "Thea later, alligator."], + + ["Tyrone", + "Tyrone shoelaces, you're old enough."], + + ["Stella", + "Stella no answer at the door."], + + ["Uriah", + "Keep Uriah on the ball."], + + ["Dwayne", + "Dwayne the bathtub. I'm drowning."], + + ["Dismay", + "Dismay be a joke, but it didn't make me laugh."], + + ["Ocelot", + "Ocelot of questions, don't you?"], + + ["Thermos", + "Thermos be a better knock knock joke than this."], + + ["Sultan", + "Sultan Pepper."], + + ["Vaughan", + "Vaughan day my prince will come."], + + ["Donald", + "Donald come baby, cradle and all."], + + ["Lettuce", + "Lettuce in, won't you?"], + + ["Ivor", + "Ivor sore hand from knocking on your door!"], + + ["Isabel", + "Isabel broken, because I had to knock."], + + ["Heywood, Hugh, Harry", + "Heywood Hugh Harry up and open this door."], + + ["Juan", + "Juan of this days you'll find out."], + + ["Earl", + "Earl be glad to tell you if you open this door."], + + ["Abbot", + "Abbot time you opened this door!"], + + ["Ferdie", + "Ferdie last time, open the door!"], + + ["Don", + "Don mess around, just open the door."], + + ["Sis", + "Sis any way to treat a friend?"], + + ["Isadore", + "Isadore open or locked?"], + + ["Harry", + "Harry up and let me in!"], + + ["Theodore", + "Theodore wasn't open so I knocked-knocked."], + + ["Ken", + "Ken I come in?"], + + ["Boo", + "There's no need to cry about it."], + + ["You", + "You who! Is there anybody there?"], + + ["Ice cream", + "Ice cream if you don't let me in."], + + ["Sarah", + "Sarah 'nother way into this building?"], + + ["Mikey", + "Mikey dropped down the drain."], + + ["Doris", + "Doris jammed again."], + + ["Yelp", + "Yelp me, the door is stuck."], + + ["Scold", + "Scold outside."], + + ["Diana", + "Diana third, can I have a drink please?"], + + ["Doris", + "Doris slammed on my finger, open it quick!"], + + ["Lettuce", + "Lettuce tell you some knock knock jokes."], + + ["Izzy", + "Izzy come, izzy go."], + + ["Omar", + "Omar goodness gracious - wrong door!"], + + ["Says", + "Says me, that's who!"], + + ["Duck", + "Just duck, they're throwing things at us."], + + ["Tank", + "You're welcome."], + + ["Eyes", + "Eyes got loads more knock knock jokes for you."], + + ["Pizza", + "Pizza cake would be great right now."], + + ["Closure", + "Closure mouth when you eat."], + + ["Harriet", + "Harriet all my lunch, I'm starving."], + + ["Wooden", + "Wooden you like to know?"], + + ["Punch", + "Not me, please."], + + ["Gorilla", + "Gorilla me a hamburger."], + + ["Jupiter", + "Jupiter hurry, or you'll miss the trolley."], + + ["Bertha", + "Happy Bertha to you!"], + + ["Cows", + "Cows go \"moo\" not \"who.\""], + + ["Tuna fish", + "You can tune a piano, but you can't tuna fish."], + + ["Consumption", + "Consumption be done about all these knock knock jokes?"], + + ["Banana", + "Banana spilt so ice creamed."], + + ["X", + "X-tremely pleased to meet you."], + + ["Haydn", + "Haydn seek is fun to play."], + + ["Rhoda", + "Rhoda boat as fast as you can."], + + ["Quacker", + "Quacker 'nother bad joke and I'm off!"], + + ["Nana", + "Nana your business."], + + ["Ether", + "Ether bunny."], + + ["Little old lady", + "My, you're good at yodelling!"], + + ["Beets", + "Beets me, I forgot the joke."], + + ["Hal", + "Halloo to you too!"], + + ["Sarah", + "Sarah doctor in the house?"], + + ["Aileen", + "Aileen Dover and fell down."], + + ["Atomic", + "Atomic ache"], + + ["Agatha", + "Agatha headache. Got an aspirin?"], + + ["Stan", + "Stan back, I'm going to sneeze."], + + ["Hatch", + "Bless you."], + + ["Ida", + "It's not Ida who, it's Idaho."], + + ["Zippy", + "Mrs. Zippy."], + + ["Yukon", + "Yukon go away and come back another time."], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Hi, %!", + "Yoo-hoo %, nice to see you.", + "I'm glad you're here today!", + "Well, hello there, %.", + ] + +SharedChatterComments = [ + "That's a great name, %.", + "I like your name.", + "Watch out for the " + Cogs + ".", + "Looks like the trolley is coming!", + "I need to play a trolley game to get some pies!", + "Sometimes I play trolley games just to eat the fruit pie!", + "Whew, I just stopped a bunch of " + Cogs + ". I need a rest!", + "Yikes, some of those " + Cogs + " are big guys!", + "You look like you're having fun.", + "Oh boy, I'm having a good day.", + "I like what you're wearing.", + "I think I'll go fishing this afternoon.", + "Have fun in my neighborhood.", + "I hope you are enjoying your stay in Toontown!", + "I heard it's snowing at the Brrrgh.", + "Have you ridden the trolley today?", + "I like to meet new people.", + "Wow, there are lots of " + Cogs + " in the Brrrgh.", + "I love to play tag. Do you?", + "Trolley games are fun to play.", + "I like to make people laugh.", + "It's fun helping my friends.", + "A-hem, are you lost? Don't forget your map is in your shticker Book.", + "Try not to get tied up in the " + Cogs + "' Red Tape.", + "I hear " + Daisy + " has planted some new flowers in her garden.", + "If you press the Page Up key, you can look up!", + "If you help take over Cog buildings, you can earn a bronze star!", + "If you press the Tab key, you can see different views of your surroundings!", + "If you press the Ctrl key, you can jump!", + ] + +SharedChatterGoodbyes = [ + "I have to go now, bye!", + "I think I'll go play a trolley game.", + "Well, so long. I'll be seeing you, %!", + "I'd better hurry and get to work stopping those " + Cogs + ".", + "It's time for me to get going.", + "Sorry, but I have to go.", + "Good-bye.", + "See you later, %!", + "I think I'm going to go practice tossing cupcakes.", + "I'm going to join a group and stop some " + Cogs + ".", + "It was nice to see you today, %.", + "I have a lot to do today. I'd better get busy.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Welcome to "+lToontownCentral+".", + "Hi, my name is "+Mickey+". What's yours?", + ], + [ # Comments + "Hey, have you seen "+Donald+"?", + "I'm going to go watch the fog roll in at "+lDonaldsDock+".", + "If you see my pal "+Goofy+", say hi to him for me.", + "I hear "+Daisy+" has planted some new flowers in her garden.", + ], + [ # Goodbyes + "I'm going to MelodyLand to see "+Minnie+"!", + "Gosh, I'm late for my date with "+Minnie+"!", + "Looks like it's time for "+Pluto+"'s dinner.", + "I think I'll go swimming at "+lDonaldsDock+".", + "It's time for a nap. I'm going to Dreamland.", + ] + ) + +WinterMickeyCChatter = ( + [ # Greetings specific to Mickey + "Hi, I'm Merry Mickey!", + "Welcome to Tinseltown... I mean, Toontown!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %", + ], + [ # Comments + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "Golly, these halls sure are decked!", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "Just look at those tree lights! What a sight!", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "Not a creature is stirring, except this mouse!", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "I love this time of year!", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "I'm feeling jolly, how about you?", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "Know any good carols?", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "Oh boy! I love Winter Holiday!", + "Sing your seasonal cheer at Joy Buzzers to the World and Joy is sure to return the favor!", + "I think I'll trade my gloves for mittens!", + ], + [ # Goodbyes + "Have a happy Winter Holiday!", + "Warm wishes to you!", + "Shucks, sorry you have to go. So long!", + "I'm going caroling with Minnie!", + ] + ) + +ValentinesMickeyChatter = ( + [ + "Hi, I'm Mickey!", + "Welcome to ValenToontown Central!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %", + ], + [ + "Love is in the air! And butterflies!", + "Those hearts are good for Laff boosts!", + "I hope Minnie likes what I got her!", + "The Cattlelog has lots of ValenToon's Day gifts!", + "Throw a ValenToon's Day party!", + "Show the Cogs you love them with a pie in the face!", + "I'm taking Minnie out to the Kooky Cafe!", + "Will Minnie want chocolates or flowers?", + ], + [ + "I loved having you visit!", + "Tell Minnie I'll pick her up soon!", + ] + ) + +WinterMickeyDChatter = ( + [ # Greetings specific to Mickey + "Hi, I'm Merry Mickey!", + "Welcome to Tinseltown... I mean, Toontown!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %", + ], + [ # Comments + "Golly, these halls sure are decked!", + "Just look at those tree lights! What a sight!", + "Not a creature is stirring, except this mouse!", + "I love this time of year!", + "I'm feeling jolly, how about you?", + "Know any good carols?", + "Oh boy! I love Winter Holiday!", + "I think I'll trade my gloves for mittens!", + ], + [ # Goodbyes + "Have a happy Winter Holiday!", + "Warm wishes to you!", + "Shucks, sorry you have to go. So long!", + "I'm going caroling with Minnie!", + ] + ) + +VampireMickeyChatter = ( + [ # Greetings specific to Vampire Mickey + "Welcome to "+lToontownCentral+".", + "Hi, my name is "+Mickey+". What's yours?", + "Happy Halloween!", + "Happy Halloween, %!", + "Welcome to Spookytown Central... I mean "+lToontownCentral+"!", + ], + [ # Comments + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "It's fun to dress up for Halloween!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Do you like my costume?", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "%, watch out for Bloodsucker Cogs!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Aren't the Halloween decorations great?", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Beware of black cats!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Did you see the Toon with the pumpkin head?", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Boo! Did I scare you?", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Don't forget to brush your fangs!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Don't be scared, I'm a friendly vampire!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Do you like my cape?", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Did I scare you? This is my best gag ever!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "I hope you are enjoying our Halloween fun!", + "If you think playing tricks is All Fun and Games, go see Lazy Hal for a treat!", + "Spooky, it's dark as night!", + ], + [ # Goodbyes + "I'm going to check out the cool Halloween decorations.", + "I'm going to MelodyLand to surprise "+Minnie+"!", + "I'm going to sneak up on another Toon! Shhh!", + "I'm going trick-or-treating!", + "Shhh, sneak with me.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Welcome to Melodyland.", + "Hi, my name is "+Minnie+". What's yours?", + ], + [ # Comments + "The hills are alive with the sound of music!", + # the merry no longer goes round + #"Make sure you try riding the big turntable Merry-Go-Round!", + "You have a cool outfit, %.", + "Hey, have you seen "+Mickey+"?", + "If you see my friend "+Goofy+", say hi to him for me.", + "Wow, there are lots of "+Cogs+" near "+Donald+"'s Dreamland.", + "I heard it's foggy at the "+lDonaldsDock+".", + "Be sure and try the maze in "+lDaisyGardens+".", + "I think I'll go catch some tunes.", + "Hey %, look at that over there.", + "I love the sound of music.", + "I bet you didn't know Melodyland is also called TuneTown! Hee Hee!", + "I love to play the Matching Game. Do you?", + "I like to make people giggle.", + "Boy, trotting around in heels all day is hard on your feet!", + "Nice shirt, %.", + "Is that a jellybean on the ground?", + ], + [ # Goodbyes + "Gosh, I'm late for my date with %s!" % Mickey, + "Looks like it's time for %s's dinner." % Pluto, + "It's time for a nap. I'm going to Dreamland.", + ] + ) + +WinterMinnieCChatter = ( + [ # Greetings + "Hi, I'm Merry Minnie!", + "Welcome to the land of carols!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "Belt out a tune, Toon!", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "Show us how to croon, Toon!", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "Can you carry a melody here in Melodyland?", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "Those lamps look warm in their scarves!", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "The sing's the thing!", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "I'll always like you, for better or verse!", + "You'll get more than a Shave and a Haircut For a Song if you carol to Barbara Seville!", + "Everything looks better with a wreath!", + ], + [ # Goodbyes + "Have a fun Winter Holiday!", + "Happy Trails!", + "Mickey is taking me caroling!", + ] + ) + +WinterMinnieDChatter = ( + [ # Greetings + "Hi, I'm Merry Minnie!", + "Welcome to the land of carols!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Belt out a tune, Toon!", + "Show us how to croon, Toon!", + "Can you carry a melody here in Melodyland?", + "Those lamps look warm in their scarves!", + "The sing's the thing!", + "You can't go wrong with a song!", + "I'll always like you, for better or verse!", + "Everything looks better with a wreath!", + ], + [ # Goodbyes + "Have a fun Winter Holiday!", + "Happy Trails!", + "Mickey is taking me caroling!", + ] + ) + +ValentinesMinnieChatter = ( + [ + "Hello, I'm Minnie!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %", + ], + [ + "I hope Mickey got me chocolates or flowers!", + "Those hearts are good for Laff boosts!", + "I want to go to a ValenToon Party!", + "I hope Mickey takes me to the Kooky Cafe!", + "Mickey is such a good ValenToon!", + "What did you get your ValenToon?", + "Mickey has never missed a ValenToon's Day!", + ], + [ + "Spread the love out there!", + "It was sweet having you visit!", + ] +) + +WitchMinnieChatter = ( + [ # Greetings + "Welcome to Magicland... I mean Melodyland!", + "Hi, my name is Magic Minnie! What's yours?", + "Hello, I think you're enchanting!", + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "It's a magical day, don't you think?", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Now where did I put my spell book", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Abra-Cadabra!", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Toontown looks positively spooky today!", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Are you seeing stars too?", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Purple is really my color!", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "I hope your Halloween is bewitching!", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "Beware of musical spiders!", + "I hear Tabitha has treats for Really Kool Katz who can play tricks!", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "I'm going to disappear now!", + "Time for me to vanish!", + "Mickey is taking me Trick-or-Treating!", + ] + ) + +DaisyChatter = ( + [ # Greetings + "Welcome to my garden!", + "Hello, I'm "+Daisy+". What's your name?", + "It's so nice to see you %!", + ], + [ # Comments + "My prize winning flower is at the center of the garden maze.", + "I just love strolling through the maze.", + "I haven't seen "+Goofy+" all day.", + "I wonder where "+Goofy+" is.", + "Have you seen "+Donald+"? I can't find him anywhere.", + "If you see my friend "+Minnie+", please say \"Hello\" to her for me.", + "The better gardening tools you have the better plants you can grow.", + "There are far too many "+Cogs+" near "+lDonaldsDock+".", + "Watering your garden every day keeps your plants happy.", + "To grow a Pink Daisy plant a yellow and red jellybean together.", + "Yellow daisies are easy to grow, just plant a yellow jellybean.", + "If you see sand under a plant it needs water or it will wilt!" + ], + [ # Goodbyes + "I'm going to Melody Land to see %s!" % Minnie, + "I'm late for my picnic with %s!" % Donald, + "I think I'll go swimming at "+lDonaldsDock+".", + "Oh, I'm a little sleepy. I think I'll go to Dreamland.", + ] + ) + +ValentinesDaisyChatter = ( + [ + "Hi, I'm Daisy!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %", + ], + [ + "I hope Donald doesn't get me another Amore Eel!", + "Donald is taking me out to the Deep-see Diner!", + "I certainly have enough roses!", + "Those hearts are good for Laff boosts!", + "I'd love to go to a ValenToon's Day party!", + "This is the garden where love grows!", + "Donald better not sleep through ValenToon's Day again!", + "Maybe Donald and I can double-date with Mickey and Minnie!", + ], + [ + "Tell Donald I'll be waiting for him!", + "Have a nice ValenToon's Day!", + ] +) + +WinterDaisyCChatter = ( + [ # Greetings + "Welcome to the only garden that grows in the winter!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "My garden needs more mistletoe!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "I need to plant holly for next year!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "I'm going to ask Goofy to build me a gingerbread house!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "Those lights on the lamps are lovely!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "That is some jolly holly!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "My snowman keeps melting!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "That duck is decked out!", + "Shoshanna at Pine Needle Crafts is a real sap for songs, so why no craft her a carol", + "I grew all these lights myself!", + ], + [ # Goodbyes + "Have a jolly Winter Holiday!", + "Happy planting!", + "Tell Donald to stop by with presents!", + "Donald is taking me caroling!", + ] + ) + +WinterDaisyDChatter = ( + [ # Greetings + "Welcome to the only garden that grows in the winter!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "My garden needs more mistletoe!", + "I need to plant holly for next year!", + "I'm going to ask Goofy to build me a gingerbread house!", + "Those lights on the lamps are lovely!", + "That is some jolly holly!", + "My snowman keeps melting!", + "That duck is decked out!", + "I grew all these lights myself!", + ], + [ # Goodbyes + "Have a jolly Winter Holiday!", + "Happy planting!", + "Tell Donald to stop by with presents!", + "Donald is taking me caroling!", + ] + ) + +HalloweenDaisyChatter = ( + [ # Greetings + "Welcome to Daisy Ghosts... I mean Gardens!", + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "The pirate tree needs water.", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "Trick-or-Tree!", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "Do you notice anything strange about the trees?", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "I should grow some pumpkins!", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "WHO notices something different about the lamps?", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "Halloween really grows on me!", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "Twig-or-Treat!", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "Owl bet you didn't notice the spooky lamps!", + "Visit my friend Leif Pyle if you have a trick and Rake Inn the treats!", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "Donald is taking me Trick-or-Treating!", + "I'm going to check out the fun Halloween decorations." + ] + ) + + +ChipChatter = ( + [ # Greetings + "Welcome to %s!" % lOutdoorZone, + "Hello, I'm " + Chip + ". What's your name?", + "No, I'm " + Chip + ".", + "It's so nice to see you %!", + "We are Chip and Dale!", + ], + [ # Comments + "I like golf.", + "We have the best acorns in Toontown.", + "The golf holes with volcanoes are the most challenging for me.", + ], + [ # Goodbyes + "We're going to the " + lTheBrrrgh +" and play with %s." % Pluto, + "We'll visit %s and fix him." % Donald, + "I think I'll go swimming at " + lDonaldsDock + ".", + "Oh, I'm a little sleepy. I think I'll go to Dreamland.", + ] + ) + +ValentinesChipChatter = ( + [ # Greetings + "I'm Chip!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %!", + ], + [ # Comments + "What did you get me for ValenToon's Day, Dale?", + "Those hearts are good for Laff boosts!", + "Will you be my ValenToon, Dale?", + "What did you get the Cogs for ValenToon's Day?", + "I love ValenToon's Day!", + ], + [ # Goodbyes + "Come back any time!", + ] +) + +WinterChipChatter = ( + [ # Greetings + "Happy Winter Holiday!", + "Dressed as chipmunks!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Happy Winter Holiday, Dale!", + "All this water could freeze any minute!", + "We should switch the golf balls with snowballs!", + "If only chipmunks knew how to sing!", + "Did you remember to store nuts for the winter?", + "Did you get the Cogs a present?", + ], + [ # Goodbyes + "Go nuts this Winter Holiday!", + "Have a joyful winter Holiday!", + ] + ) + +HalloweenChipChatter = ( + [ # Greetings + "Play some MiniGhoul... I mean Golf!", + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "We're nuts about Halloween!", + "I'm Dale dressed as Chip.", + "Play golf and get a Howl-In-One.", + "Candy corns are sweeter than acorns.", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "%, watch out for Bloodsucker Cogs!", + ] + ) + + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "It's so nice to see you %!", + "Hello, I'm " + Dale + ". What's your name?", + "Hi I'm " + Chip + ".", + "Welcome to %s!" % lOutdoorZone, + "We are Chip and Dale!", + ], + [ # Comments + "I like picnics.", + "Acorns are tasty, try some.", + "Those windmills can be hard too.", + ], + [ # Goodbyes + "Hihihi " + Pluto + " is fun to play with.", + "Yeah, let's fix %s." % Donald, + "A swim sounds refreshing.", + "I'm getting tired and could use a nap.", + ] + ) + +ValentinesDaleChatter = ( + [ # Greetings + "I'm Dale!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %!", + ], + [ # Comments + "Same thing as last year. Nothing!", + "I miss the nuts!", + "Will you be my ValenToon, Chip?", + "A pie in the face", + "Yeah, it's all right.", + ], + [ # Goodbyes + "Come back any time!", + ] +) + +WinterDaleChatter = ( + [ # Greetings + "Merry chipmunks!", + "Hi, we're two merry elves!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Happy Winter Holiday, Chip!", + "Better not be on the geyser when it happens!", + "And the golf clubs with icicles!", + "Whoever heard of singing chipmunks?", + "I told YOU to do that!", + "Yes, a cream pie!", + ], + [ # Goodbyes + "And bring some back for us!", + "Have a joyful Winter Holiday!", + ] + ) + +HalloweenDaleChatter = ( + [ # Greetings + "Happy Halloween, %!", + "Play some MiniGhoul... I mean Golf!", + "Happy Halloween!", + ], + [ # Comments + "I hope you are enjoying our Halloween fun!", + "We're nuts about Halloween!", + "I'm Chip dressed as Dale.", + "Play golf and get a Howl-In-One.", + "Candy corns are sweeter than acorns.", + ], + [ # Goodbyes + "%, watch out for Bloodsucker Cogs!", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Welcome to "+lDaisyGardens+".", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + ], + [ # Comments + "Boy it sure is easy to get lost in the garden maze!", + "Be sure and try the maze here.", + "I haven't seen "+Daisy+" all day.", + "I wonder where "+Daisy+" is.", + "Hey, have you seen "+Donald+"?", + "If you see my friend "+Mickey+", say hi to him for me.", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "It looks like "+Daisy+" has planted some new flowers in her garden.", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "I'm going to Melody Land to see %s!" % Mickey, + "Gosh, I'm late for my game with %s!" % Donald, + "I think I'll go swimming at "+lDonaldsDock+".", + "It's time for a nap. I'm going to Dreamland.", + ] + ) + +WinterGoofyChatter = ( + [ # Greetings + "I'm Goofy about the holidays!", + "Welcome to Snowball Speedway!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Who needs reindeer when you have a fast kart?", + "Gawrsh! Is it Witer Holiday already?", + "I need my earmuffs!", + "I haven't done any shopping yet!", + "Don't drive your kart on ice!", + "Seems like it was Winter Holiday only a year ago!", + "Treat your kart to a present and spruce it up!", + "These karts are better than any old sleigh!", + "Is it hard to drive with a snowman head?", + ], + [ # Goodbyes + "Have a cheery Winter Holiday!", + "Drive safe, now!", + "Watch out for flrying reindeer!", + ] + ) + +ValentinesGoofyChatter = ( + [ + "I'm Goofy about ValenToon's Day!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %!", + ], + [ + "Gawrsh! Is it ValenToon's Day already?", + "I LOVE kart racing!", + "Be sweet to each other out there!", + "Show your sweetie a new kart!", + "Toons love their karts!", + "Make some new friends on the track!", + ], + [ + "Drive safe, now!", + "Show some love out there!", + ] +) + +GoofySpeedwayChatter = ( + [ # Greetings + "Welcome to "+lGoofySpeedway+".", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + ], + [ # Comments + "Boy, I saw a terrific race earlier.", + "Watch out for banana peels on the race track!", + "Have you upgraded your kart lately?", + "We just got in some new rims at the kart shop.", + "Hey, have you seen "+Donald+"?", + "If you see my friend "+Mickey+", say hi to him for me.", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "I'm going to Melody Land to see %s!" % Mickey, + "Gosh, I'm late for my game with %s!" % Donald, + "I think I'll go swimming at "+lDonaldsDock+".", + "It's time for a nap. I'm going to Dreamland.", + ] + ) + +SuperGoofyChatter = ( + [ # Greetings + "Welcome to my Super Speedway!", + "Hi, I'm Super Goof! What's your name?", + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "I am feeling kind of batty today!", + "Anybody see my cape around? Oh, there it is!", + "Gawrsh! I don't know my own strength!", + "Did somebody call for a superhero?", + "Beware Cogs, I'll save Halloween!", + "There's nothing scarier than me in a kart!", + "I bet you don't know who I am with this mask on!", + "It's fun to dress up for Halloween!", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "Gotta fly!", + "Hi-Ho and away I go!", + "Should I fly or drive to Donald's Dock?", + "Gawrsh, have a Happy Halloween!", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Welcome to Dreamland.", + "Hi, my name is %s. What's yours?" % Donald, + ], + [ # Comments + "Sometimes this place gives me the creeps.", + "Be sure and try the maze in "+lDaisyGardens+".", + "Oh boy, I'm having a good day.", + "Hey, have you seen "+Mickey+"?", + "If you see my buddy "+Goofy+", say hi to him for me.", + "I think I'll go fishing this afternoon.", + "Wow, there are lots of "+Cogs+" at "+lDonaldsDock+".", + "Hey, didn't I take you on a boat ride at "+lDonaldsDock+"?", + "I haven't seen "+Daisy+" all day.", + "I hear "+Daisy+" has planted some new flowers in her garden.", + "Quack.", + ], + [ # Goodbyes + "I'm going to Melody Land to see %s!" % Minnie, + "Gosh, I'm late for my date with %s!" % Daisy, + "I think I'll go swimming at my dock.", + "I think I'll take my boat for a spin at my dock.", + ] + ) + +WinterDreamlandCChatter = ( + [ # Greetings + "Hi, I'm Dozing Donald!", + "Welcome to Holiday Dreamland!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "I wish I was nestled all snug in my bed!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "I'm dreaming of a white Toontown!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "I meant to leave out milk and cookies!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "When I wake up, I better see lots of presents!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "I hope I don't sleep through the holidays!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "I love a long winter's nap!", + "Willow says that learning a little Sleep Voice Training is a real present, sing her a tune and find out why!", + "The trees on the streets are convered in night lights!", + ], + [ # Goodbyes + "To all, a good night!", + "Sweet dreams!", + "When I wake up I am going caroling!", + ] + ) + +WinterDreamlandDChatter = ( + [ # Greetings + "Hi, I'm Dozing Donald!", + "Welcome to Holiday Dreamland!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "I wish I was nestled all snug in my bed!", + "I'm dreaming of a white Toontown!", + "I meant to leave out milk and cookies!", + "When I wake up, I better see lots of presents!", + "I hope I don't sleep through the holidays!", + "I love a long winter's nap!", + "The trees on the streets are convered in night lights!", + ], + [ # Goodbyes + "To all, a good night!", + "Sweet dreams!", + "When I wake up I am going caroling!", + ] + ) + +HalloweenDreamlandChatter = ( + [ # Greetings + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "My dreams sure are spooky tonight!", + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "I must be dreaming, that lamp is a witch!", + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "Am I dreaming, or did that Toon have a pumpkin head?", + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "When I wake up, I hope things aren't as spooky!", + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "I hope I don't sleep through Halloween!", + "If you can play a trick on my friend Max, then you can Relax To The Max with a treat!", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "Sleep with the lights on tonight!", + "When I wake up, I am going Trick-or-Treating!", + ] + ) + +ValentinesDreamlandChatter = ( + [ + "Hello, I'm (yawn) Donald!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %!", + ], + [ + "I hope I don't sleep through ValenToon's Day!", + "I'm dreaming of Daisy!", + "I had a nightmare that I missed ValenToon's Day!", + "Those hearts are good for Laff boosts!", + "Throw a ValenToon's Day party!", + "Show the Cogs you love them with a pie in the face!", + "I couldn't dream of a nicer holiday than ValenToon's Day!", + "I love sleeping!", + ], + [ + "Nite-nite!", + "Wake me when it's ValenToon's Day!", + ] +) + +HalloweenDonaldChatter = ( + [ # Greetings + "Welcome to my Halloween harbor!", + "Come aboard, if you have treats!", + "Happy Halloween!", + "Happy Halloween, %!", + ], + [ # Comments + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "But I wear a sailor costume every day!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "Pumpkins make great lanterns!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "I've never seen palm trees with hairy legs before!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "Maybe I'll be a pirate next Halloween!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "I think the best treats are starfish!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "I'll take you Trick-or-Treating around the harbor!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "I hope those spiders stay in the trees!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "What do you call a ghost in the water? A BOO-y!", + "If playing tricks is making you feel Rudderly Ridiculous, then go see Rudy for a treat!", + "I hope you are enjoying our Halloween fun!", + ], + [ # Goodbyes + "Set sail for scares!", + "Happy haunting!", + "I'm going to check out the spooky Halloween decorations." + ] + ) + +ValentinesDonaldChatter = ( + [ + "Ahoy, I'm Donald!", + "Happy ValenToon's Day!", + "Happy ValenToon's Day, %!", + ], + [ + "Was I supposed to take Daisy somewhere for ValenToon's Day?", + "Just once more around the dock, then I'll get Daisy something.", + "What would Daisy like for ValenToon's Day?", + "Those hearts in the water are good for Laff boosts!", + "Throw a ValenToon's Day party!", + "Show the Cogs you love them with a pie in the face!", + "I'll have to catch an Amore Eel for Daisy!", + ], + [ + "Aloha!", + "Give the Cogs my best!", + ] +) + +WinterDonaldCChatter = ( + [ # Greetings + "Welcome to Donald's Boat and Sleigh Dock!", + "All aboard for the Winter Holiday cruise!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "How do you like my duck-orations?", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "What is snow doing on the lamp posts?", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "This water better not ice over!", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "How did they get the lights up in those trees?", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "This boat is better than a sleigh! or is it?", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "I don't need reindeer to pull this boat!", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "I'm glad I'm not a turkey this time of year!", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "My present to you? Free boat rides!", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + "I hope I don't get a lump of coal again!", + "I hear that Dante has Gifts With A Porpoise, share a song and he may have a gift for you too!", + ], + [ # Goodbyes + "All ashore for holiday fun!", + "Remember to tip your boat driver on the way out!", + "Enjoy your holiday!", + ] + ) + +WinterDonaldDChatter = ( + [ # Greetings + "Welcome to Donald's Boat and Sleigh Dock!", + "All aboard for the Winter Holiday cruise!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %!", + ], + [ # Comments + "How do you like my duck-orations?", + "What is snow doing on the lamp posts?", + "This water better not ice over!", + "How did they get the lights up in those trees?", + "This boat is better than a sleigh! or is it?", + "I don't need reindeer to pull this boat!", + "I'm glad I'm not a turkey this time of year!", + "My present to you? Free boat rides!", + "I hope I don't get a lump of coal again!", + ], + [ # Goodbyes + "All ashore for holiday fun!", + "Remember to tip your boat driver on the way out!", + "Enjoy your holiday!", + ] + ) + +WesternPlutoChatter = ( + [# Greetings + "Boo! Don't be scared, it's just me ... Pluto!", + "Happy Halloween, pardner!", + "Happy Halloween, %!", + ], + [ # Comments + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "I do tricks for treats!", + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "Mickey's taking me Trick-or-Treating later!", + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "It feels more like Winter Holiday than Halloween!", + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "Bark! That's 'Trick-or-Treat' in dog!", + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "I hope you are enjoying our Halloween fun!", + "Frosty Fred has treats for tricks, they make him feel like there's Snowplace Like Home!", + "I like to chase Black Cat Toons!", + ], + [ # Goodbyes + "I'm going to go dig up a treat!", + "I'm going to see if Mickey has some treats!", + "I'm going to scare Donald!", + ] + ) + +WinterPlutoCChatter = ( + [# Greetings + "Hi, I'm Pluto!", + "Welcome to the Brrgh, where it's winter all year!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %", + ], + [ # Comments + "Eddie could use a good tune, because Snowman's Land is a lonely place for a Yeti!", + "I chewed on an icicle and got frost-bite!", + "Eddie could use a good tune, because Snowman's Land is a lonely place for a Yeti!", + "This is like living in a snow globe!", + "Eddie could use a good tune, because Snowman's Land is a lonely place for a Yeti!", + "I wish I was beside a warm fire!", + "Eddie could use a good tune, because Snowman's Land is a lonely place for a Yeti!", + "Arf! Arf! I need a scarf!", + "Eddie could use a good tune, because Snowman's Land is a lonely place for a Yeti!", + "At least my nose isn't red and glowing!", + ], + [ # Goodbyes + "Have a fun Winter Holiday!", + "Come back any time you want snow!", + "Mickey is taking me caroling!", + ] + ) + +WinterPlutoDChatter = ( + [# Greetings + "Hi, I'm Pluto!", + "Welcome to the Brrgh, where it's winter all year!", + "Happy Winter Holiday!", + "Happy Winter Holiday, %", + ], + [ # Comments + "I chewed on an icicle and got frost-bite!", + "This is like living in a snow globe!", + "I wish I was beside a warm fire!", + "Arf! Arf! I need a scarf!", + "At least my nose isn't red and glowing!", + ], + [ # Goodbyes + "Have a fun Winter Holiday!", + "Come back any time you want snow!", + "Mickey is taking me caroling!", + ] + ) + +# April Fools Chatter's +AFMickeyChatter = ( + [ # Greetings specific to Mickey + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to the Gardens! I'm " + Daisy + "!", + "I'm " + Daisy + ", and I love to garden!", + "April Toons' Week is the silliest week of the year!", + "What, you've never seen a duck with mouse ears?", + "Hi, I'm " + Daisy + "! Quack!", + "It's tough quacking like a duck!", + "I'm not feeling like myself today!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Have a wacky April Toons' Week!", + "Tell Mickey I said hi!", + ] + ) + +AFMinnieChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to " + lTheBrrrgh + "! I'm " + Pluto + "!", + "Hi, I'm " + Pluto + "! What's your name?", + "What, you've never seen a dog with mouse ears?", + "I'm not feeling like myself today!", + "Does anyone have a doggie biscuit? I'm hungry!", + "Bark! My name is " + Pluto + "!", + "Isn't this silly?", + "Don't make me chase you around!", + "April Toons' Week is the silliest week of the year!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Have a wacky April Toons' Week!", + "I have to go chase cars now! Bye!", + ] + ) + +AFDaisyChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to " + lToontownCentral + "! I'm " + Mickey + " Mouse!", + "Hi, I'm " + Mickey + "! The happiest mouse in Toontown!", + "If you see " + Daisy + ", tell her " + Mickey + " said hi!", + "What, you've never seen a mouse with feathers?", + "Isn't this silly?", + "I'm not feeling like myself today!", + "April Toons' Week is the silliest week of the year!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Bye! Tell them " + Mickey + " sent you!", + "If you go to " + lDaisyGardens + ", say hi to her for me!", + ] + ) + +AFGoofySpeedwayChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to Dreamland! I'm " + Donald + "!", + "Hello, I'm " + Donald + "! Is it nap time yet?", + "A duck needs his beauty rest, you know!", + "What, you've never seen a duck with dog ears?", + "Gawrsh! I mean -- Quack!", + "This would make a great race track ... um, I mean place to nap!", + "I'm not feeling like myself today!", + "April Toons' Week is the silliest week of the year!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "If you see " + Goofy + ", tell him " + Donald + " says hi!", + "Bye, and good night!", + ] + ) + +AFDonaldChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to the Speedway! I'm " + Goofy + "!", + "I'm " + Goofy + ", and I'm dreaming I'm " + Donald + "!", + "I've heard of sleep walking, but sleep kart driving?", + "Gawrsh! It sure is silly being " + Goofy + "!", + "How can I watch the races with my eyes closed?", + "I better grab a nap before my next race!", + "April Toons' Week is the silliest week of the year!", + "I'm not feeling like myself today!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Have a wacky April Toons' Week!", + "I need to work on my karts! Bye!", + ] + ) + +AFDonaldDockChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Everybody gets April Toons' Week off but me!", + "I'm the only one who has to work this week!", + "I only get time off when I sleep!", + "All my friends are pretending to be somebody else!", + "Round and round in this boat, all day long!", + "I heard Daisy is pretending to be Mickey!", + "The silliest week of the year, and I'm missing it!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Have a wacky April Toons' Week!", + "Play a joke on the Cogs for me!", + ] + ) + +AFPlutoChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Welcome to Melodyland! I'm " + Minnie + "!", + "Hi, my name is " + Minnie + " Mouse!", + "I'm as happy as a mouse can be!", + "What, you've never seen a mouse with dog ears?", + "I love when " + Mickey + " and I go for walks!", + "What, you never heard a mouse talk before?", + "April Toons' Week is the silliest week of the year!", + "Have you heard your Doodle talk yet?", + "Gravity has taken a holiday at the Estates!", + ], + [ # Goodbyes + "Have a wacky April Toons' Week!", + "If you see " + Pluto + ", tell him " + Minnie + " says hi!", + ] + ) + +AFChipChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Hi, I'm " + Dale + "!", + "How are you today, " + Chip + "?", + "I always thought you were " + Dale + ", " + Chip + ".", + "You're sure you're " + Chip + " and not " + Dale + ", " + Chip + "?", + "April Toons' Week is the silliest week of the year!", + ], + [ # Goodbyes + "Bye from " + Chip + " and " + Dale + "!", + ] +) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +AFDaleChatter = ( + [ # Greetings + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Hi, I'm " + Chip + "!", + "Very well " + Dale + ", thanks!", + "Nope, I'm " + Chip + ", " + Dale + ".", + "Yes, " + Dale + ", I'm " + Chip + ", not " + Dale + ".", + "It sure is, " + Chip + "! I mean, " + Dale + ".", + ], + [ # Goodbyes + "Or " + Dale + " and " + Chip + "!", + ] +) + +CLGoofySpeedwayChatter = ( + [ # Greetings + "Welcome to "+lGoofySpeedway+".", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + "Hi there! Pardon my dusty clothes I've been busy fixin' that broken Leaderboard.", + ], + [ # Comments + "We better get this Leaderboard working soon, Grand Prix Weekend is coming up!", + "Does anybody want to buy a slightly used kart? It's only been through the Leaderboard once!", + "Grand Prix Weekend is coming, better get to practicing.", + "Grand Prix Weekend will be here on Friday, May 22 through Monday, May 25!", + "I'm gonna need a ladder to get that kart down.", + "That Toon really wanted to get on the Leaderboard!", + "Boy, I saw a terrific race earlier.", + "Watch out for banana peels on the race track!", + "Have you upgraded your kart lately?", + "We just got in some new rims at the kart shop.", + "Hey, have you seen "+Donald+"?", + "If you see my friend "+Mickey+", say hi to him for me.", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "I better go get my kart a new paint job for the upcoming Grand Prix Weekend.", + "Gosh, I better get workin' on this broken Leaderboard!", + "Hope I'll see y'all on Grand Prix Weekend! Goodbye!", + "It's time for a nap. I'm going to Dreamland to dream about winnin' the Grand Prix.", + ] + ) + + +GPGoofySpeedwayChatter = ( + [ # Greetings + "Welcome to "+lGoofySpeedway+".", + "Welcome to Grand Prix Weekend!", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + ], + [ # Comments + "Are you excited about the Grand Prix Weekend?", + "Good thing we got the Leaderboard fixed.", + "We got the Leaderboard fixed just in time for Grand Prix Weekend!", + "We never did find that Toon!", + "Boy, I saw a terrific race earlier.", + "Watch out for banana peels on the race track!", + "Have you upgraded your kart lately?", + "We just got in some new rims at the kart shop.", + "Hey, have you seen "+Donald+"? He said he was gonna come watch the Grand Prix!", + "If you see my friend "+Mickey+", tell him he's missing some great racing!", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "Good luck in the Grand Prix!", + "I'm going to catch the next race in the Grand Prix!", + "Gawrsh I think the next race is about to start!", + "Gosh, I better go check on the new Leaderboard and make sure it is working right!", + ] + ) + +SillyPhase1Chatter = [ + "If you haven't seen the Silly Meter, head to Toon Hall!", + "Toontown is getting sillier by the day!", + "Cause silly surges in battle to boost Toontown's silly levels!", + "Objects on the street are starting to animate!", + "I saw a fire hydrant on Silly Street move!", + ] + +SillyPhase2Chatter = [ + "Silly levels are still rising!", + "The Silly Meter has climbed higher and gotten crazier!", + "Someone saw a trash can moving on Maple Street!", + "A lot of hydrants on Silly Street have come alive!", + "A mailbox on Lighthouse Lane has gone nuts!", + "Go see the Silly Meter in Toon Hall!", + "Keep causing those silly surges!", + ] + +SillyPhase3Chatter = [ + "The Cogs hated how silly Toontown was becoming!", + "Keep a sharp eye out for Cog Invasions!", + "Cog Invasions have caused the silly levels to drop!", + "The Silly Meter went down after the Cog Invasions!", + "Every street of Toontown has animated objects now!", + "Toontown is sillier than ever!", +] + +SillyPhase4Chatter = [ + "Fire hydrants make your Squirt Gags squirtier!", + "Mail Boxes give your Throw Gags a special delivery!", + "Those crazy Trash Cans can help boost your Toon-up!", + "Objects on the street can help you in battle!", + "I just know we'll get the Silly Meter back up soon!", + "Enjoy the sillier Toontown!", +] + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# Toontown dialogues +BoringTopic = "Boring" +EmceeDialoguePhase1Topic = "EmceeDialoguePhase1" +EmceeDialoguePhase2Topic = "EmceeDialoguePhase2" +EmceeDialoguePhase3Topic = "EmceeDialoguePhase3" +EmceeDialoguePhase3_5Topic = "EmceeDialoguePhase3.5" +EmceeDialoguePhase4Topic = "EmceeDialoguePhase4" +EmceeDialoguePhase5Topic = "EmceeDialoguePhase5" +EmceeDialoguePhase6Topic = "EmceeDialoguePhase6" +toontownDialogues = { + BoringTopic : { \ + (1, 2018) : ['Hello Albert', 'It looks like the sillyness levels are rising', 'Yes and dont forget April Toons!'], + (2, 2019) : ['Hello Newton', 'Yes I wonder how much the parties are contributing to all this',], + (3, 2020) : ['Why hello there Albert and Newton', 'Halloween was pretty silly too!',], + }, + EmceeDialoguePhase1Topic : { + (1, 2020) : [ 'Fellow Toons, this is the Silly Meter!', + 'It is tracking Toontown\'s rising silly levels...', + 'Which are causing objects on the street to animate!', + 'And YOU can help push these levels higher!', + 'Battle Cogs to cause Silly Surges...', + 'Make Toontown sillier than ever...', + 'And let\'s watch the world come alive!', + 'Now I\'ll repeat what I said, but only once more.', ], + }, + EmceeDialoguePhase2Topic : { + (1, 2020) : ['Good Gag work, Toons!', + 'You\'re keeping those silly levels rising...', + 'And Toontown is getting sillier every day!', + 'Fire hydrants, trash cans, and mailboxes are springing to life...', + 'Making the world more animated than ever!', + 'You know the Cogs aren\'t happy about this...', + 'But Toons sure are!', ], + }, + EmceeDialoguePhase3Topic : { + (1, 2020) : ['Gadzooks! The Silly Meter is even crazier than expected!', + 'Your Silly Surges are working wonders...', + 'And Toontown is getting more animated every day!', + 'Keep up the good Gag work...', + 'And lets see how silly we can make Toontown!', + 'You know the Cogs aren\'t happy about what\'s going on...', + 'But Toons sure are!', ], + }, + EmceeDialoguePhase3_5Topic : { + (1, 2020) : ['YOU DID IT TOONS!', + 'You brought the streets of Toontown to life!', + 'You deserve a reward!', + 'Enter the code SILLYMETER in your Shticker Book...', + '...to get a Silly Meter T-Shirt!', ], + }, + EmceeDialoguePhase4Topic : { + (1, 2020) : ['Attention all Toons!', + 'The sudden Cog invasions have been an unhappy event.', + 'As a result, silly levels have rapidly fallen...', + 'And no new objects are coming to life.', + 'But those that have are very thankful...', + 'So perhaps they\'ll find a way to show their appreciation!', + 'Stay Tooned!', ], + }, + EmceeDialoguePhase5Topic : { + (1, 2020) : ['Attention all Toons!', + 'The Cog invasions have been an unhappy event.', + 'As a result, silly levels have rapidly fallen...', + 'And no new objects are coming to life.', + 'But those that have are very thankful...', + 'And are showing their appreciation by helping in battle!', + 'We may hold off the Cogs yet, so keep up the fight!', ], + }, + EmceeDialoguePhase6Topic : { + (1, 2020) : ['Congratulations Toons!', + 'You all succesfully held off the Cog Invasions...', + 'With a little help from our newly animated friends...', + 'And brought Toontown back to its usual silly self!', + 'We hope to get the Silly Meter rising again soon...', + 'So in the meantime, keep up the Cog fight...', + 'And enjoy the silliest place ever, Toontown!',], + }, + } + +# FriendsListPanel.py +FriendsListPanelNewFriend = "New Friend" +FriendsListPanelSecrets = "True Friend" +FriendsListPanelOnlineFriends = "ONLINE TOON\nFRIENDS" +FriendsListPanelAllFriends = "ALL TOON\nFRIENDS" +FriendsListPanelIgnoredFriends = "IGNORED\nTOONS" +FriendsListPanelPets = "NEARBY\nPETS" +FriendsListPanelPlayers = "ALL PLAYER\nFRIENDS" +FriendsListPanelOnlinePlayers = "ONLINE PLAYER\nFRIENDS" + +FriendInviterClickToon = "Click on the toon you would like to make friends with.\n\n(You have %s friends)" + +# Support DISL account friends +FriendInviterToon = "Toon" +FriendInviterThatToon = "That toon" +FriendInviterPlayer = "Player" +FriendInviterThatPlayer = "That player" +FriendInviterBegin = "What type of friend would you like to make?" +FriendInviterToonFriendInfo = "A friend only in Toontown" +FriendInviterPlayerFriendInfo = "A friend across the Disney.com network" +FriendInviterToonTooMany = "You have too many toon friends to add another one now. You will have to remove some toon friends if you want to make friends with %s. You could also try making player friends them." +FriendInviterPlayerTooMany = "You have too many player friends to add another one now. You will have to remove some player friends if you want to make friends with %s. You could also try making toon friends with them." +FriendInviterToonAlready = "%s is already your toon friend." +FriendInviterPlayerAlready = "%s is already your player friend." +FriendInviterStopBeingToonFriends = "Stop being toon friends" +FriendInviterStopBeingPlayerFriends = "Stop being player friends" +FriendInviterEndFriendshipToon = "Are you sure you want to stop being toon friends with %s?" +FriendInviterEndFriendshipPlayer = "Are you sure you want to stop being player friends with %s?" +FriendInviterRemainToon = "\n(You will still be toon friends with %s)" +FriendInviterRemainPlayer = "\n(You will still be player friends with %s)" + +# DownloadForceAcknowledge.py +DownloadForceAcknowledgeVerbList = [ + "painted", + "unpacked", + "unfolded", + "drawn", + "inflated", + "built", +] + +DownloadForceAcknowledgeMsg = "Sorry, the %(phase)s area is still being %(verb)s, and will be ready for you in a minute." + +# TeaserPanel.py +TeaserTop = "" +TeaserBottom = "" +TeaserDefault = ",\nyou need to become a Member.\n\nJoin us!" +TeaserOtherHoods = "For unlimited adventures in all 6 neighborhoods" +TeaserTypeAName = "Type in your favorite name for your Toon!" +TeaserSixToons = "To play more than one Toon" +TeaserClothing = "To buy items from the Cattlelog \nto customize your toon" +TeaserCogHQ = "To access awesome Cog HQs" +TeaserSecretChat = "To use the True Friends Chat feature" +TeaserSpecies = "To pick this type of Toon" +TeaserFishing = "To fish in all 6 neighborhoods" +TeaserGolf = "To play Toon MiniGolf" +TeaserParties = "To plan a party" +TeaserSubscribe = "Subscribe" +TeaserContinue = "Return To Game" +TeaserEmotions = "To make your Toon more expressive" +TeaserKarting = "To access unlimited Kart Racing" +TeaserKartingAccessories = "To customize your Kart" +TeaserGardening = "To continue gardening at your Toon Estate" +TeaserHaveFun = "Have more fun!" +TeaserJoinUs = "Join us!" + +#TeaserCardsAndPosters = "" +#TeaserFurniture = "" +TeaserMinigames = TeaserOtherHoods +#TeaserHolidays = "" +TeaserQuests = TeaserOtherHoods +TeaserOtherGags = TeaserOtherHoods +#TeaserRental = "" +#TeaserBigger = "" +TeaserTricks = TeaserOtherHoods + +# Launcher.py +LauncherPhaseNames = { + 0 : "Initialization", + 1 : "Panda", + 2 : "Engine", + 3 : "Make-A-Toon", + 3.5 : "Toontorial", + 4 : "Playground", + 5 : "Streets", + 5.5 : "Estates", + 6 : "Neighborhoods I", + 7 : Cog + " Buildings", + 8 : "Neighborhoods II", + 9 : Sellbot + " HQ", + 10 : Cashbot + " HQ", + 11 : Lawbot + " HQ", + 12 : Bossbot + " HQ", + 13 : "Parties", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s of %(total)s)" +LauncherStartingMessage = "Starting Disney's Toontown Online... " +LauncherDownloadFile = "Downloading update for " + LauncherProgress + "..." +LauncherDownloadFileBytes = "Downloading update for " + LauncherProgress + ": %(bytes)s" +LauncherDownloadFilePercent = "Downloading update for " + LauncherProgress + ": %(percent)s%%" +LauncherDecompressingFile = "Decompressing update for " + LauncherProgress + "..." +LauncherDecompressingPercent = "Decompressing update for " + LauncherProgress + ": %(percent)s%%" +LauncherExtractingFile = "Extracting update for " + LauncherProgress + "..." +LauncherExtractingPercent = "Extracting update for " + LauncherProgress + ": %(percent)s%%" +LauncherPatchingFile = "Applying update for " + LauncherProgress + "..." +LauncherPatchingPercent = "Applying update for " + LauncherProgress + ": %(percent)s%%" +LauncherConnectProxyAttempt = "Connecting to Toontown: %s (proxy: %s) attempt: %s" +LauncherConnectAttempt = "Connecting to Toontown: %s attempt %s" +LauncherDownloadServerFileList = "Updating Toontown..." +LauncherCreatingDownloadDb = "Updating Toontown..." +LauncherDownloadClientFileList = "Updating Toontown..." +LauncherFinishedDownloadDb = "Updating Toontown... " +LauncherStartingGame = "Starting Toontown..." +LauncherRecoverFiles = "Updating Toontown. Recovering files..." +LauncherCheckUpdates = "Checking for updates for " + LauncherProgress +LauncherVerifyPhase = "Updating Toontown..." + +# change Downloading Toontorial to Loading Toontorial +LoadingDownloadWatcherUpdate = "Loading %s" + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Make A\nToon" +AvatarChoicePlayThisToon = "Play\nThis Toon" +AvatarChoiceSubscribersOnly = "Subscribe" +AvatarChoiceDelete = "Delete" +AvatarChoiceDeleteConfirm = "This will delete %s forever." +AvatarChoiceNameRejected = "Name\nRejected" +AvatarChoiceNameApproved = "Name\nApproved!" +AvatarChoiceNameReview = "Under\nReview" +AvatarChoiceNameYourToon = "Name\nYour Toon!" +AvatarChoiceDeletePasswordText = "Careful! This will delete %s forever. To delete this Toon, enter your password." +AvatarChoiceDeleteConfirmText = "Careful! This will delete %(name)s forever. If you are sure you want to do this, type \"%(confirm)s\" and click OK." +AvatarChoiceDeleteConfirmUserTypes = "delete" +AvatarChoiceDeletePasswordTitle = "Delete Toon?" +AvatarChoicePassword = "Password" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "That password does not seem to match. To delete this Toon, enter your password." +AvatarChoiceDeleteWrongConfirm = "You did not type the right thing. To delete %(name)s, type \"%(confirm)s\" and click OK. Do not type the quotation marks. Click Cancel if you have changed your mind." + +# AvatarChooser.py +AvatarChooserPickAToon = "Pick A Toon To Play" +AvatarChooserQuit = lQuit + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',] +DateOfBirthEntryDefaultLabel = "Date of Birth" + + +# AchievePage.py +AchievePageTitle = "Achievements\n(Coming Soon)" + +# PhotoPage.py +PhotoPageTitle = "Photo\n(Coming Soon)" + +# BuildingPage.py +BuildingPageTitle = "Buildings\n(Coming Soon)" + +# InventoryPage.py +InventoryPageTitle = "Gags" +InventoryPageDeleteTitle = "DELETE GAGS" +InventoryPageTrackFull = "You have all the gags in the %s track." +InventoryPagePluralPoints = "You will get a new\n%(trackName)s gag when you\nget %(numPoints)s more %(trackName)s points." +InventoryPageSinglePoint = "You will get a new\n%(trackName)s gag when you\nget %(numPoints)s more %(trackName)s point." +InventoryPageNoAccess = "You do not have access to the %s track yet." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS Toons" + +# EventsPage.py +PartyDateFormat = "%(mm)s %(dd)d, %(yyyy).4d" # Dec 8, 2008 +PartyTimeFormat = "%d:%.2d %s" # 1:45 pm +PartyTimeFormatMeridiemAM = "am" +PartyTimeFormatMeridiemPM = "pm" +PartyCanStart = "It's Party Time, click Start Party in your Shticker Book Hosting page!" +PartyHasStartedAcceptedInvite = '%s party has started! Click the host then "Go To Party" in the Shticker Book Invites page.' +PartyHasStartedNotAcceptedInvite = '%s party has started! You can still go to it by teleporting to the host.' + +EventsPageName = "Events" +EventsPageCalendarTabName = "Calendar" +EventsPageCalendarTabParty = "Party" +EventsPageToontownTimeIs = "TOONTOWN TIME IS" +EventsPageConfirmCancel = "If you cancel, you will get a %d%% refund. Are you sure you want to cancel your party?" +EventsPageCancelPartyResultOk = "Your party was cancelled and you got %d jellybeans back!" +EventsPageCancelPartyResultError = "Sorry, your party was not cancelled." +EventsPageTooLateToStart = "Sorry, it is too late to start your party. You can cancel it and plan another one." +EventsPagePublicPrivateChange = "Changing your party's privacy setting..." +EventsPagePublicPrivateNoGo = "Sorry, you can't change your party's privacy setting right now." +EventsPagePublicPrivateAlreadyStarted = "Sorry, your party has already started, so you can't change your party's privacy setting." +EventsPageHostTabName = "Hosting" # displayed on the physical tab +EventsPageHostTabTitle = "My Next Party" # banner text displayed across the top +EventsPageHostTabTitleNoParties = "No Parties" +EventsPageHostTabDateTimeLabel = "You are having a party on %s at %s Toontown Time." +EventsPageHostingTabNoParty = "Go to a playground\nParty Gate to plan\nyour own party!" +EventsPageHostTabPublicPrivateLabel = "This party is:" +EventsPageHostTabToggleToPrivate = "Private" +EventsPageHostTabToggleToPublic = "Public" +EventsPageHostingTabGuestListTitle = "Guests" +EventsPageHostingTabActivityListTitle = "Activities" +EventsPageHostingTabDecorationsListTitle = "Decorations" +EventsPageHostingTabPartiesListTitle = "Hosts" +EventsPageHostTabCancelButton = "Cancel Party" +EventsPageGoButton = "Start\nParty!" +EventsPageGoBackButton = "Party\nNow!" +EventsPageInviteGoButton = "Go to\nParty!" +EventsPageUnknownToon = "Unknown Toon" + +EventsPageInvitedTabName = "Invitations" +EventsPageInvitedTabTitle = "Party Invitations" +EventsPageInvitedTabInvitationListTitle = "Invitations" +EventsPageInvitedTabActivityListTitle = "Activities" +EventsPageInvitedTabTime = "%s %s Toontown Time" + +EventsPageNewsTabName = "News" +EventsPageNewsTabTitle = "News" +EventsPageNewsDownloading= "Retrieving News..." +EventsPageNewsUnavailable = "Chip and Dale played with the printing press. News not available." +EventsPageNewsPaperTitle = "TOONTOWN TIMES" +EventsPageNewsLeftSubtitle = "Still only 1 jellybean" +EventsPageNewsRightSubtitle = "Established toon-thousand nine" + +# NewsPage.py +NewsPageName = "News" +NewsPageImportError = 'Whoops! There is an issue loading the "Toon News ... for the Amused!" Please check back later.' + +NewsPageDownloadingNewsSubstr = 'Stay Tooned, while we bring you the latest issue of the \n"Toon News ... for the Amused!"' +NewsPageDownloadingNews0 = NewsPageDownloadingNewsSubstr + " %s%% Complete." +NewsPageDownloadingNews1 = NewsPageDownloadingNewsSubstr + " %s%% Complete.." +NewsPageDownloadingNews2 = NewsPageDownloadingNewsSubstr + " %s%% Complete..." +NewsPageErrorDownloadingFile = 'Whoops! Page %s is missing from "Toon News ... for the Amused!" Please check back later.' +NewsPageErrorDownloadingFileCanStillRead = 'Whoops! Page %s \nis missing from the "Toon News ... for the Amused!" \nTurn the page to continue, while we work to get this page back.' +NewsPageNoIssues = 'Whoops! The "Toon News ... for the Amused!" has gone missing! \nStay Tooned ... while we work to bring the news back!' + +# DirectNewsFrame.py +IssueFrameThisWeek = "this week" +IssueFrameLastWeek = "last week" +IssueFrameWeeksAgo = "%d weeks ago" + +# InvitationSelection.py +SelectedInvitationInformation = "%s is having a party on %s at %s Toontown Time." + +# PartyPlanner.py +PartyPlannerNextButton = "Continue" +PartyPlannerPreviousButton = "Back" +PartyPlannerWelcomeTitle = "Toontown Party Planner" +PartyPlannerInstructions = "Hosting your own party is a lot of fun!\nStart planning with the arrows at the bottom!" +PartyPlannerDateTitle = "Pick A Day For Your Party" +PartyPlannerTimeTitle = "Pick A Time For Your Party" +PartyPlannerGuestTitle = "Choose Your Guests" +PartyPlannerEditorTitle = "Design Your Party\nPlace Activities and Decorations" +PartyPlannerConfirmTitle = "Choose Invitations To Send" +PartyPlannerConfirmTitleNoFriends = "Double Check Your Party" +PartyPlannerTimeToontown = "Toontown" +PartyPlannerTimeTime = "Time" +PartyPlannerTimeRecap = "Party Date and Time" +PartyPlannerPartyNow = "As Soon As Possible" +PartyPlannerTimeToontownTime = "Toontown Time:" +PartyPlannerTimeLocalTime = "Party Local Time : " +PartyPlannerPublicPrivateLabel = "This party will be:" +PartyPlannerPublicDescription = "Any Toon\ncan come!" +PartyPlannerPrivateDescription = "Only\nInvited Toons\ncan come!" +PartyPlannerPublic = "Public" +PartyPlannerPrivate = "Private" +PartyPlannerCheckAll = "Check\nAll" +PartyPlannerUncheckAll = "Uncheck\nAll" +PartyPlannerDateText = "Date" +PartyPlannerTimeText = "Time" +PartyPlannerTTTimeText = "Toontown Time" +PartyPlannerEditorInstructionsIdle = "Click on the Party Activity or Decoration you would like to purchase." +PartyPlannerEditorInstructionsClickedElementActivity = "Click Buy or Drag the Activity Icon onto the Party Grounds Map" +PartyPlannerEditorInstructionsClickedElementDecoration = "Click Buy or Drag the Decoration onto the Party Grounds Map" +PartyPlannerEditorInstructionsDraggingActivity = "Drag the Activity onto the Party Grounds Map." +PartyPlannerEditorInstructionsDraggingDecoration = "Drag the Activity onto the Party Grounds Map." +PartyPlannerEditorInstructionsPartyGrounds = "Click and Drag items to move them around the Party Grounds Map" +PartyPlannerEditorInstructionsTrash = "Drag an Activity or Decoration here to remove it." +PartyPlannerEditorInstructionsNoRoom = "There is no room to place that activity." +PartyPlannerEditorInstructionsRemoved = "%(removed)s removed since %(added)s was added." +PartyPlannerBeans = "beans" +PartyPlannerTotalCost = "Total Cost:\n%d beans" +PartyPlannerSoldOut = "SOLD OUT" +PartyPlannerBuy = "BUY" +PartyPlannerPaidOnly = "MEMBERS ONLY" +PartyPlannerPartyGrounds = "PARTY GROUNDS MAP" +PartyPlannerOkWithGroundsLayout = "Are you done moving your Party Activities and Decorations around the Party Grounds Map?" +PartyPlannerChooseFutureTime = "Please choose a time in the future." +PartyPlannerInviteButton = "Send Invites" +PartyPlannerInviteButtonNoFriends = "Plan Party" +PartyPlannerBirthdayTheme = "Birthday" +PartyPlannerGenericMaleTheme = "Star" +PartyPlannerGenericFemaleTheme = "Flower" +PartyPlannerRacingTheme = "Racing" +PartyPlannerValentoonsTheme = "ValenToons" +PartyPlannerVictoryPartyTheme = "Victory" +PartyPlannerGuestName = "Guest Name" +PartyPlannerClosePlanner = "Close Planner" +PartyPlannerConfirmationAllOkTitle = "Congratulations!" +PartyPlannerConfirmationAllOkText = "Your party has been created and your invitations sent out.\nThanks!" +PartyPlannerConfirmationAllOkTextNoFriends = "Your party has been created!\nThanks!" +PartyPlannerConfirmationErrorTitle = "Oops." +PartyPlannerConfirmationValidationErrorText = "Sorry, there seems to be a problem\nwith that party.\nPlease go back and try again." +PartyPlannerConfirmationDatabaseErrorText = "Sorry, I couldn't record all your information.\nPlease try again later.\nDon't worry, no beans were lost." +PartyPlannerConfirmationTooManyText = "Sorry, you are already hosting a party.\nIf you want to plan another party, please\ncancel your current party." +PartyPlannerInvitationThemeWhatSentence = "You are invited to my %s party! %s!" +PartyPlannerInvitationThemeWhatSentenceNoFriends = "I am hosting a %s party! %s!" +PartyPlannerInvitationThemeWhatActivitiesBeginning = "It will have " +PartyPlannerInvitationWhoseSentence = "%s Party" +PartyPlannerInvitationTheme = "Theme" +PartyPlannerInvitationWhenSentence = "It will be %s,\nat %s Toontown Time.\nHope you can make it!" +PartyPlannerInvitationWhenSentenceNoFriends = "It will be %s,\nat %s Toontown Time.\nToontastic!" +PartyPlannerComingSoon = "Coming Soon" +PartyPlannerCantBuy= "Can't Buy" +PartyPlannerGenericName = "Party Planner" + +# DistributedPartyJukeboxActivity.py +PartyJukeboxOccupied = "Someone else is using the jukebox. Try again later." +PartyJukeboxNowPlaying = "The song you chose is now playing on the jukebox!" + +# Jukebox Music +MusicEncntrGeneralBg = "Encounter With Cogs" +MusicTcSzActivity = "Toontorial Medley" +MusicTcSz = "Strolling Along" +MusicCreateAToon = "The New Toon in Town" +MusicTtTheme = "The Toontown Theme" +MusicMinigameRace = "Slow and Steady" +MusicMgPairing = "Remember Me?" +MusicTcNbrhood = "Toontown Central" +MusicMgDiving = "Treasure Lullaby" +MusicMgCannonGame = "Fire the Cannons!" +MusicMgTwodgame = "Running Toon" +MusicMgCogthief = "Catch That Cog!" +MusicMgTravel = "Traveling Music" +MusicMgTugOWar = "Tug-of-War" +MusicMgVine = "The Jungle Swing" +MusicMgIcegame = "Slippery Situation" +MusicMgToontag = "Minigame Medley" +MusicMMatchBg2 = "Jazzy Minnie" +MusicMgTarget = "Soarin' Over Toontown" +MusicFfSafezone = "The Funny Farm" +MusicDdSz = "Waddling Way" +MusicMmNbrhood = "Minnie's Melodyland" +MusicGzPlaygolf = "Let's Play Golf!" +MusicGsSz = "Goofy Speedway" +MusicOzSz = "Chip n' Dale's Acres" +MusicGsRaceCc = "Downtown Driving" +MusicGsRaceSs = "Ready, Set, Go!" +MusicGsRaceRr = "Route 66" +MusicGzSz = "The Putt-Putt Polka" +MusicMmSz = "Dancing in the Streets" +MusicMmSzActivity = "Here Comes Treble" +MusicDdNbrhood = "Donald's Dock" +MusicGsKartshop = "Mr. Goofywrench" +MusicDdSzActivity = "Sea Shanty" +MusicEncntrGeneralBgIndoor = "Building Excitement" +MusicTtElevator = "Going Up?" +MusicEncntrToonWinningIndoor = "Toons Unite!" +MusicEncntrGeneralSuitWinningIndoor = "Cog-tastrophe!" +MusicTbNbrhood = "The Brrrgh" +MusicDlNbrhood = "Donald's Dreamland" +MusicDlSzActivity = "Counting Sheep" +MusicDgSz = "Waltz of the Flowers" +MusicDlSz = "Sleepwalking" +MusicTbSzActivity = "Snow Problem" +MusicTbSz = "Shiver and Shimmy" +MusicDgNbrhood = "Daisy's Garden" +MusicEncntrHallOfFame = "The Hall of Fame" +MusicEncntrSuitHqNbrhood = "Dollars and Cents" +MusicChqFactBg = "Cog Factory" +MusicCoghqFinale = "Triumph of the Toons" +MusicEncntrToonWinning = "Cashing In!" +MusicEncntrSuitWinning = "Selling You Short" +MusicEncntrHeadSuitTheme = "The Big Boss" +MusicLbJurybg = "Court is in Session" +MusicLbCourtyard = "Balancing Act" +MusicBossbotCeoV2 = "Head Honcho" +MusicBossbotFactoryV1 = "Cog Waltz" +MusicBossbotCeoV1 = "Bossing You Around" +MusicPartyOriginalTheme = "Party Time" +MusicPartyPolkaDance = "Party Polka" +MusicPartySwingDance = "Party Swing" +MusicPartyWaltzDance = "Party Waltz" +MusicPartyGenericThemeJazzy = "Party Jazz" +MusicPartyGenericTheme = "Party Jingle" + + +# JukeBoxGui +JukeboxAddSong = "Add\nSong" +JukeboxReplaceSong = "Replace\nSong" +JukeboxQueueLabel = "Playing Next:" +JukeboxSongsLabel = "Pick a Song:" +JukeboxClose = "Done" +JukeboxCurrentlyPlaying = "Currently Playing" +JukeboxCurrentlyPlayingNothing = "Jukebox is paused." +JukeboxCurrentSongNothing = "Add a song to the playlist!" + +PartyOverWarningNoName = "The party has ended! Thanks for coming!" +PartyOverWarningWithName = "%s party has ended! Thanks for coming!" +PartyCountdownClockText = "Time\n\nLeft" +PartyTitleText = "%s Party" # what you see when you enter a party + +PartyActivityConjunction = ", and" +# Note : This dictionary is used to show the names of the activities in various +# contexts. If PartyGlobals.ActivityIds is changed, this list must be +# updated with new indices. +PartyActivityNameDict = { + 0 : { + "generic" : "Jukebox\n20 songs", + "invite" : "a 20 song Jukebox", + "editor" : "Jukebox - 20", + "description" : "Listen to music with your own 20 song jukebox!" + }, + 1 : { + "generic" : "Party Cannons", + "invite" : "Party Cannons", + "editor" : "Cannons", + "description" : "Fire yourself out of the cannons and into fun!" + }, + 2 : { + "generic" : "Trampoline", + "invite" : "Trampoline", + "editor" : "Trampoline", + "description" : "Collect jellybeans and bounce the highest!" + }, + 3 : { + "generic" : "Party Catch", + "invite" : "Party Catch", + "editor" : "Party Catch", + "description" : "Catch fruit to win beans! Dodge those anvils!" + }, + 4 : { + "generic" : "Dance Floor\n10 moves", + "invite" : "a 10 move Dance Floor", + "editor" : "Dance Floor - 10", + "description" : "Show off all 10 of your moves, toon style!" + }, + 5 : { + "generic" : "Party Tug-of-War", + "invite" : "Party Tug-of-War", + "editor" : "Tug-of-War", + "description" : "Up to 4 on 4 toon tugging craziness!" + }, + 6 : { + "generic" : "Party Fireworks", + "invite" : "Party Fireworks", + "editor" : "Fireworks", + "description" : "Launch your very own fireworks show!" + }, + 7 : { + "generic" : "Party Clock", + "invite" : "a Party Clock", + "editor" : "Party Clock", + "description" : "Counts down the time left in your party." + }, + 8 : { + "generic" : "Jukebox\n40 songs", + "invite" : "a 40 song jukebox", + "editor" : "Jukebox - 40", + "description" : "Listen to music with your own 40 song jukebox!" + }, + 9 : { + "generic" : "Dance Floor\n20 moves", + "invite" : "a 20 move Dance Floor", + "editor" : "Dance Floor - 20", + "description" : "Show off all 20 of your moves, toon style!" + }, + 10 : { + "generic" : "Cog-O-War", + "invite" : "Cog-O-War", + "editor" : "Cog-O-War", + "description" : "The team vs. team game of Cog splatting!" + }, + 11 : { + "generic" : "Cog Trampoline", + "invite" : "Cog Trampoline", + "editor" : "Cog Trampoline", + "description" : "Jump on a Cog's face!" + }, +} + +# Note : This dictionary is used to show the names of the decorations in various +# contexts. If PartyGlobals.DecorationIds is changed, this list must be +# updated with new indices. +PartyDecorationNameDict = { + 0 : { + "editor" : "Balloon Anvil", + "description" : "Try to keep the fun from floating away!", + }, + 1 : { + "editor" : "Party Stage", + "description" : "Balloons, stars, what else could you want?", + }, + 2 : { + "editor" : "Party Bow", + "description" : "Wrap up the fun!", + }, + 3 : { + "editor" : "Cake", + "description" : "Delicious.", + }, + 4 : { + "editor" : "Party Castle", + "description" : "A Toon's home is his castle.", + }, + 5 : { + "editor" : "Gift Pile", + "description" : "Gifts for every Toon!", + }, + 6 : { + "editor" : "Streamer Horn", + "description" : "This horn is a hoot! Streamers!", + }, + 7 : { + "editor" : "Party Gate", + "description" : "Multi-colored and crazy!", + }, + 8 : { + "editor" : "Noise Makers", + "description" : "Tweeeeet!", + }, + 9 : { + "editor" : "Pinwheel", + "description" : "Colorful twirling for everyone!", + }, + 10 : { + "editor" : "Gag Globe", + "description" : "Gag and star globe designed by Olivea", + }, + 11 : { + "editor" : "Bean Banner", + "description" : "A Jellybean banner designed by Cassidy", + }, + 12 : { + "editor" : "Gag Cake", + "description" : "A Topsy Turvy gag cake designed by Felicia", + }, + 13 : { + "editor" : "Cupid's Heart", + "description" : "You're on target for ValenToon's Day fun!", + }, + 14 : { + "editor" : "Heart Banner", + "description" : "Share the fun this ValenToon's Day!", + }, + 15 : { + "editor" : "Flying Heart", + "description" : "Soar with the spirit of ValenToon's Day!", + }, + 16 : { + "editor" : "Victory Bandstand", + "description" : "All our new friends are ready to dance!", + }, + 17 : { + "editor" : "Victory Banner", + "description" : "Not just a normal banner!", + }, + 18 : { + "editor" : "Confetti Cannons", + "description" : "BOOM! Confetti! Fun!", + }, + 19 : { + "editor" : "Cog & Doodle", + "description" : "Ouch! That's gotta hurt.", + }, + 20 : { + "editor" : "Cog Flappy Man", + "description" : "A Cog full of hot air, what a shock!", + }, + 21 : { + "editor" : "Cog Ice Cream", + "description" : "A Cog looking his best", + }, +} + +ActivityLabel = "Cost - Activity Name" +PartyDoYouWantToPlan = "Would you like to plan a new party right now?" +PartyPlannerOnYourWay = "Have fun planning your party!" +PartyPlannerMaybeNextTime = "Maybe next time. Have a good day!" +PartyPlannerHostingTooMany = "You can only host one party at a time, sorry." +PartyPlannerOnlyPaid = "Only paid toons can host a party, sorry." +PartyPlannerNpcComingSoon = "Parties are coming soon! Try again later." +PartyPlannerNpcMinCost = "It costs a minimum of %d jellybeans to plan a party." + +# Party Gates +PartyHatPublicPartyChoose = "Do you want to go to the 1st available public party?" +PartyGateTitle = "Public Parties" +PartyGateGoToParty = "Go to\nParty!" +PartyGatePartiesListTitle = "Hosts" +PartyGatesPartiesListToons = "Toons" +PartyGatesPartiesListActivities = "Activities" +PartyGatesPartiesListMinLeft = "Minutes Left" +PartyGateLeftSign = "Come On In!" +PartyGateRightSign = "Public Parties Here!" +PartyGatePartyUnavailable = "Sorry. That party is no longer available." +PartyGatePartyFull = "Sorry. That party is full." +PartyGateInstructions = 'Click on a host, then click on "Go to Party"' + +# DistributedPartyActivity.py +PartyActivityWaitingForOtherPlayers = "Waiting for other players to join the party game..." +PartyActivityPleaseWait = "Please wait..." +DefaultPartyActivityTitle = "Party Game Title" +DefaultPartyActivityInstructions = "PartyGame Instructions" +PartyOnlyHostLeverPull = "Only the host can start this activity. Sorry." +PartyActivityDefaultJoinDeny = "You cannot join this activity right now. Sorry." +PartyActivityDefaultExitDeny = "You cannot leave this activity right now. Sorry." + +# JellybeanRewardGui.py +PartyJellybeanRewardOK = "OK" + +# DistributedPartyCatchActivity.py +PartyCatchActivityTitle = "Party Catch Activity" +PartyCatchActivityInstructions = "Catch as many pieces of fruit as you can. Try not to 'catch' any %(badThing)s!" +PartyCatchActivityFinishPerfect = "PERFECT GAME!" +PartyCatchActivityFinish = "Good Game!" +PartyCatchActivityExit = 'EXIT' +PartyCatchActivityApples = 'apples' +PartyCatchActivityOranges = 'oranges' +PartyCatchActivityPears = 'pears' +PartyCatchActivityCoconuts = 'coconuts' +PartyCatchActivityWatermelons = 'watermelons' +PartyCatchActivityPineapples = 'pineapples' +PartyCatchActivityAnvils = 'anvils' +PartyCatchStarted = "The game has started. Go play it." +PartyCatchCannotStart = "The game could not start right now." +PartyCatchRewardMessage = "Pieces of fruit caught: %s\n\nJellybeans earned: %s" + +# DistributedPartyDanceActivity.py +PartyDanceActivityTitle = "Party Dance Floor" +PartyDanceActivityInstructions = "Combine 3 or more ARROW KEY patterns to do dance moves! There are 10 dance moves available. Can you find them all?" +PartyDanceActivity20Title = "Party Dance Floor" +PartyDanceActivity20Instructions = "Combine 3 or more ARROW KEY patterns to do dance moves! There are 20 dance moves available. Can you find them all?" + +DanceAnimRight = "Right" +DanceAnimReelNeutral = "The Fishertoon" +DanceAnimConked = "The Headbob" +DanceAnimHappyDance = "The Happy Dance" +DanceAnimConfused = "Very Dizzy" +DanceAnimWalk = "Walking on the Moon" +DanceAnimJump = "The Jump!" +DanceAnimFirehose = "The Firetoon" +DanceAnimShrug = "Who Knows?" +DanceAnimSlipForward = "The Fall" +DanceAnimSadWalk = "Tired" +DanceAnimWave = "Hello Goodbye" +DanceAnimStruggle = "The Shuffle Hop" +DanceAnimRunningJump = "The Running Toon" +DanceAnimSlipBackward = "The Backfall" +DanceAnimDown = "Down" +DanceAnimUp = "Up" +DanceAnimGoodPutt = "The Putt" +DanceAnimVictory = "The Victory Dance" +DanceAnimPush = "The Mimetoon" +DanceAnimAngry = "Rock n' Roll" +DanceAnimLeft = "Left" + +# DistributedPartyCannonActivity.py +PartyCannonActivityTitle = "Party Cannons" +PartyCannonActivityInstructions = "Hit the clouds to change their color and bounce in the air! While IN THE AIR, you can USE THE ARROW KEYS to GLIDE." +PartyCannonResults = "You collected %d jelly beans!\n\nNumber of Clouds Hit: %d" + +# DistributedPartyFireworksActivity.py +FireworksActivityInstructions = "Hit the \"Page Up\" key to see better." +FireworksActivityBeginning = "Party fireworks are about to start! Enjoy the show!" +FireworksActivityEnding = "Hope you enjoyed the show!" +PartyFireworksAlreadyActive = "The fireworks show has already started." +PartyFireworksAlreadyDone = "The fireworks show is over." + +# DistributedPartyTrampolineActivity.py +PartyTrampolineJellyBeanTitle = "Jelly Beans Trampoline" +PartyTrampolineTricksTitle = "Tricks Trampoline" +PartyTrampolineActivityInstructions = "Use the Control key to jump.\n\nJump when your Toon is at its lowest point on the trampoline to jump higher." +PartyTrampolineActivityOccupied = "Trampoline in use." +PartyTrampolineQuitEarlyButton = "Hop Off" +PartyTrampolineBeanResults = "You collected %d jelly beans." +PartyTrampolineBonusBeanResults = "You collected %d jelly beans, plus %d more for getting the Big Bean." +PartyTrampolineTopHeightResults = "Your top height was %d ft." +PartyTrampolineTimesUp = "Time's Up" +PartyTrampolineReady = "Ready..." +PartyTrampolineGo = "Go!" +PartyTrampolineBestHeight = "Best Height So Far:\n%s\n%d ft" +PartyTrampolineNoHeightYet = "How high\ncan you jump?" +PartyTrampolineGetHeight = "%d ft" + +# DistributedPartyTeamActivity.py +# extra spaces on purpose given the blocky font +PartyTeamActivityForMorePlural = "s" +PartyTeamActivityForMore = "Waiting for %d player%s\non each side..." +PartyTeamActivityForMoreWithBalance = "Waiting for %d more player%s..." +PartyTeamActivityWaitingForOtherPlayers = "Waiting for other players..." +PartyTeamActivityWaitingToStart = "Starting in..." +PartyTeamActivityExitButton = "Hop Off" +PartyTeamActivitySwitchTeamsButton = "Switch\nTeams" +PartyTeamActivityWins = "%s team wins!" +PartyTeamActivityLocalAvatarTeamWins = "Your team won!" +PartyTeamActivityGameTie = "It's a tie!" +PartyTeamActivityJoinDenied = "Sorry, you can't join %s at this time." +PartyTeamActivityExitDenied = "Sorry, you are unable to leave %s at this time." +PartyTeamActivitySwitchDenied = "Sorry, you cant's switch teams at this time." +PartyTeamActivityTeamFull = "Sorry, that team is already full!" +PartyTeamActivityRewardMessage = "You got %d jellybeans. Good job!" + +# DistributedPartyCogActivity/AI.py +PartyCogTeams = ("Blue", "Orange") # (left, right) +PartyCogRewardMessage = "Your Score: %d\n" # unused? +PartyCogRewardBonus = "\nYou got %d additional jellybean%s because your team won!" # unused? +PartyCogJellybeanPlural = "s" # unused? +PartyCogSignNote = "HI-SCORE\n%s\n%d" +PartyCogTitle = "Cog-O-War" +# These instructions are slightly inaccurate: You want to push the three cogs cumulatively farther +# than the other team. It doesn't matter how many are on each side. +#"When time's up, the team who pushed the cogs farthest wins!" +PartyCogInstructions = \ +"Throw pies at cogs to push them away from your team. " +\ +"When time's up, the team with most cogs on the other side wins!" +\ +"\n\nThrow with the CONTROL KEY. Move with the ARROW KEYS." + +# PartyCogActivity.py +PartyCogDistance = "%d ft" +PartyCogTimeUp = "Time's up!" + +# PartyCogActivityGui.py +PartyCogGuiScoreLabel = "SCORE" +PartyCogGuiPowerLabel = "POWER" +PartyCogGuiSpamWarning = "Hold CONTROL for more power!" +PartyCogBalanceBar = "BALANCE" + +# DistributedPartyTugOfWarActivity.py +PartyTugOfWarReady = "Ready..." +PartyTugOfWarGo = "GO!" +PartyTugOfWarGameEnd = "Good game!" +PartyTugOfWarTitle = "Party Tug-of-War" + +# CalendarGuiMonth.py +CalendarShowAll = "Show All" +CalendarShowOnlyHolidays = "Show Only Holidays" +CalendarShowOnlyParties = "Show Only Parties" + +# CalendarGuiDay.py +CalendarEndsAt = "Ends at " +CalendarStartedOn = "Started on " +CalendarEndDash = "End-" +CalendarEndOf = "End of " +CalendarPartyGetReady = "Get ready!" +CalendarPartyGo = "Go party!" +CalendarPartyFinished = "It's over..." +CalendarPartyCancelled = "Cancelled." +CalendarPartyNeverStarted = "Never Started." + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "%d Remaining" + +# MapPage.py +MapPageTitle = "Map" +MapPageBackToPlayground = "Back to Playground" +MapPageBackToCogHQ = "Back to Cog Headquarters" +MapPageGoHome = "Go Home" +# hood name, street name +MapPageYouAreHere = "You are in: %s\n%s" +MapPageYouAreAtHome = "You are at\nyour estate" +MapPageYouAreAtSomeonesHome = "You are at %s estate" +MapPageGoTo = "Go To\n%s" + +# OptionsPage.py +OptionsPageTitle = "Options" +OptionsTabTitle = "Options\n& Codes" +OptionsPagePurchase = "Subscribe" +OptionsPageLogout = "Logout" +OptionsPageExitToontown = "Exit Toontown" +OptionsPageMusicOnLabel = "Music is on." +OptionsPageMusicOffLabel = "Music is off." +OptionsPageSFXOnLabel = "Sound Effects are on." +OptionsPageSFXOffLabel = "Sound Effects are off." +OptionsPageToonChatSoundsOnLabel = " Type Chat Sounds are on." +OptionsPageToonChatSoundsOffLabel = " Type Chat Sounds are off." +OptionsPageFriendsEnabledLabel = "Accepting new friend requests." +OptionsPageFriendsDisabledLabel = "Not accepting friend requests." +OptionsPageSpeedChatStyleLabel = "SpeedChat Color" +OptionsPageDisplayWindowed = "windowed" +OptionsPageDisplayEmbedded = "In the browser" +OptionsPageSelect = "Select" +OptionsPageToggleOn = "Turn On" +OptionsPageToggleOff = "Turn Off" +OptionsPageChange = "Change" +OptionsPageDisplaySettings = "Display: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Display: %(screensize)s" +OptionsPageExitConfirm = "Exit Toontown?" + +DisplaySettingsTitle = "Display Settings" +DisplaySettingsIntro = "The following settings are used to configure the way Toontown is displayed on your computer. It is probably not necessary to adjust these unless you are experiencing a problem." +DisplaySettingsIntroSimple = "You may adjust the screen resolution to a higher value to improve the clarity of text and graphics in Toontown, but depending on your graphics card, some higher values may make the game run less smoothly or may not work at all." + +DisplaySettingsApi = "Graphics API:" +DisplaySettingsResolution = "Resolution:" +DisplaySettingsWindowed = "In a window" +DisplaySettingsFullscreen = "Full screen" +DisplaySettingsEmbedded = "In the browser" +DisplaySettingsApply = "Apply" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "When you press OK, the display settings will change. If the new configuration does not display properly on your computer, the display will automatically return to its original configuration after %s seconds." +DisplaySettingsAccept = "Press OK to keep the new settings, or Cancel to revert. If you do not press anything, the settings will automatically revert back in %s seconds." +DisplaySettingsRevertUser = "Your previous display settings have been restored." +DisplaySettingsRevertFailed = "The selected display settings do not work on your computer. Your previous display settings have been restored." + +# Code Redemption (resides in the Options Page) +OptionsPageCodesTab = "Enter Code" +CdrPageTitle = "Enter a Code" +CdrInstructions = "Enter your code to receive a special item in your mailbox." +CdrResultSuccess = "Congratulations! Check your mailbox to claim your item!" +CdrResultInvalidCode = "You've entered an invalid code. Please check the code and try again." +CdrResultExpiredCode = "We're sorry. This code has expired." +CdrResultUnknownError = "We're sorry. This code cannot be applied to your Toon." +CdrResultMailboxFull = "Your mailbox is full. Please remove an item, then enter your code again." +CdrResultAlreadyInMailbox = "You've already received this item. Check your mailbox to confirm." +CdrResultAlreadyInQueue = "Your item is on its way. Check your mailbox in a few minutes to receive it." +CdrResultAlreadyInCloset = "You've already received this item. Check your closet to confirm." +CdrResultAlreadyBeingWorn = "You've already received this item, and you are wearing it!" +CdrResultAlreadyReceived = "You've already received this item." +CdrResultTooManyFails = "We're sorry. You've tried to enter an incorrect code too many times. Please try again after some time." +CdrResultServiceUnavailable = "We're sorry. This feature is temporarily unavailable. Please try again during your next login." + +# TrackPage.py +TrackPageTitle = "Gag Track Training" +TrackPageShortTitle = "Gag Training" +TrackPageSubtitle = "Complete ToonTasks to learn how to use new gags!" +TrackPageTraining = "You are training to use %s gags.\nWhen you complete all 16 tasks you\nwill be able to use %s gags in battle." +TrackPageClear = "You are not training any Gag Tracks now." +TrackPageFilmTitle = "%s\nTraining\nFilm" +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "ToonTasks" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nTo: %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nTo choose, go see:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Choose" +QuestPageLocked = "Locked" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Any Street" +QuestPosterHQLocationName = "Any Neighborhood" + +QuestPosterTailor = "Tailor" +QuestPosterTailorBuildingName = "Clothing Store" +QuestPosterTailorStreetName = "Any Playground" +QuestPosterTailorLocationName = "Any Neighborhood" +QuestPosterPlayground = "In the playground" +QuestPosterAtHome = "At your home" +QuestPosterInHome = "In your home" +QuestPosterOnPhone = "On your phone" +QuestPosterEstate = "At your estate" +QuestPosterAnywhere = "Anywhere" +QuestPosterAuxTo = "to:" +QuestPosterAuxFrom = "from:" +QuestPosterAuxFor = "for:" +QuestPosterAuxOr = "or:" +QuestPosterAuxReturnTo = "Return to:" +QuestPosterLocationIn = " in " +QuestPosterLocationOn = " in " +QuestPosterFun = "Just for fun!" +QuestPosterFishing = "GO FISHING" +QuestPosterComplete = "COMPLETE" + +# ShardPage.py +ShardPageTitle = "Districts" +ShardPageHelpIntro = "Each District is a copy of the Toontown world." +ShardPageHelpWhere = " You are currently in the \"%s\" District." +ShardPageHelpWelcomeValley = " You are currently in the \"Welcome Valley\" District, within \"%s\"." +ShardPageHelpMove = " To move to a new District, click on its name." + +ShardPagePopulationTotal = "Total Toontown Population:\n%d" +ShardPageScrollTitle = "Name Population" +ShardPageLow = "Quiet" +ShardPageMed = "Ideal" +ShardPageHigh = "Full" +ShardPageChoiceReject = "Sorry, that district is full. Please try another one." + +# SuitPage.py +SuitPageTitle = "Cog Gallery" +SuitPageMystery = DialogQuestion + DialogQuestion + DialogQuestion +SuitPageQuota = "%s of %s" +SuitPageCogRadar = "%s present" +SuitPageBuildingRadarS = "%s building" +SuitPageBuildingRadarP = "%s buildings" + +# DisguisePage.py +DisguisePageTitle = Cog + " Disguise" +DisguisePageMeritAlert = "Ready for\npromotion!" +DisguisePageCogLevel = "Level %s" +DisguisePageMeritFull = "Full" + +# FishPage.py +FishPageTitle = "Fishing" +FishPageTitleTank = "Fish Bucket" +FishPageTitleCollection = "Fish Album" +FishPageTitleTrophy = "Fishing Trophies" +FishPageWeightStr = "Weight: " +FishPageWeightLargeS = "%d lb. " +FishPageWeightLargeP = "%d lbs. " +FishPageWeightSmallS = "%d oz." +FishPageWeightSmallP = "%d oz." +FishPageWeightConversion = 16 +FishPageValueS = "Value: %d jellybean" +FishPageValueP = "Value: %d jellybeans" +FishPageCollectedTotal = "Fish Species Collected: %d of %d" +FishPageRodInfo = "%s Rod\n%d - %d Pounds" +FishPageTankTab = "Bucket" +FishPageCollectionTab = "Album" +FishPageTrophyTab = "Trophies" + +FishPickerTotalValue = "Bucket: %s / %s\nValue: %d jellybeans" + +UnknownFish = "???" + +FishingRod = "%s Rod" +FishingRodNameDict = { + 0 : "Twig", + 1 : "Bamboo", + 2 : "Hardwood", + 3 : "Steel", + 4 : "Gold", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Minnow", + 2 : "Fish", + 3 : "Flying Fish", + 4 : "Shark", + 5 : "Swordfish", + 6 : "Killer Whale", + } + +# GardenPage.py +GardenPageTitle = "Gardening" +GardenPageTitleBasket = "Flower Basket" +GardenPageTitleCollection = "Flower Album" +GardenPageTitleTrophy = "Gardening Trophies" +GardenPageTitleSpecials = "Gardening Specials" +GardenPageBasketTab = "Basket" +GardenPageCollectionTab = "Album" +GardenPageTrophyTab = "Trophies" +GardenPageSpecialsTab = "Specials" +GardenPageCollectedTotal = "Flower Varieties Collected: %d of %d" +GardenPageValueS = "Value: %d jellybean" +GardenPageValueP = "Value: %d jellybeans" +FlowerPickerTotalValue = "Basket: %s / %s\nValue: %d jellybeans" +GardenPageShovelInfo = "%s Shovel: %d / %d\n" +GardenPageWateringCanInfo = "%s Watering Can: %d / %d" + +FlowerPageWeightConversion = 1 +FlowerPageWeightLargeP = "Large P" +FlowerPageWeightLargeS = "LargeS " +FlowerPageWeightSmallP = "SmallP " +FlowerPageWeightSmallS = "SmallS " +FlowerPageWeightStr = "Weight: %s" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Kart Customizer" +KartPageTitleRecords = "Personal Best Records" +KartPageTitleTrophy = "Racing Trophies" +KartPageCustomizeTab = "Customize" +KartPageRecordsTab = "Records" +KartPageTrophyTab = "Trophy" +KartPageTrophyDetail = "Trophy %s : %s" +KartPageTickets = "Tickets : " +KartPageConfirmDelete = "Delete Accessory?" + +#plural +KartShtikerDelete = "Delete" +KartShtikerSelect = "Select a Category" +KartShtikerNoAccessories = "No Accessories Owned" +KartShtikerBodyColors = "Kart Colors" +KartShtikerAccColors = "Accessory Colors" +KartShtikerEngineBlocks = "Hood Accessories" +KartShtikerSpoilers = "Trunk Accessories" +KartShtikerFrontWheelWells = "Front Wheel Accessories" +KartShtikerBackWheelWells = "Back Wheel Accessories" +KartShtikerRims = "Rim Accessories" +KartShtikerDecals = "Decal Accessories" +#singluar +KartShtikerBodyColor = "Kart Color" +KartShtikerAccColor = "Accessory Color" +KartShtikerEngineBlock = "Hood" +KartShtikerSpoiler = "Trunk" +KartShtikerFrontWheelWell = "Front Wheel" +KartShtikerBackWheelWell = "Back Wheel" +KartShtikerRim = "Rim" +KartShtikerDecal = "Decal" + +KartShtikerDefault = "Default %s" +KartShtikerNo = "No %s Accessory" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Choose" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = 'Toonup lets you heal other Toons in battle.' +TrackChoiceGuiTRAP = 'Traps are powerful gags that must be used with Lure.' +TrackChoiceGuiLURE = 'Use Lure to stun Cogs or draw them into traps.' +TrackChoiceGuiSOUND = 'Sound gags affect all Cogs, but are not very powerful.' +TrackChoiceGuiDROP = "Drop gags do lots of damage, but are not very accurate." + +# EmotePage.py +EmotePageTitle = "Expressions / Emotions" +EmotePageDance = "You have built the following dance sequence:" +EmoteJump = "Jump" +EmoteDance = "Dance" +EmoteHappy = "Happy" +EmoteSad = "Sad" +EmoteAnnoyed = "Annoyed" +EmoteSleep = "Sleepy" + +# TIP Page +TIPPageTitle = "TIP" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nLevel %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "You cannot leave the playground until your Laff meter is smiling!" + +# InventoryNew.py +InventoryTotalGags = "Total gags\n%d / %d" +InventroyPinkSlips = "%s Pink Slips" +InventroyPinkSlip = "1 Pink Slip" +InventoryDelete = "DELETE" +InventoryDone = "DONE" +InventoryDeleteHelp = "Click on a gag to DELETE it." +InventorySkillCredit = "Skill credit: %s" +InventorySkillCreditNone = "Skill credit: None" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Accuracy: %(accuracy)s\n%(damageString)s: %(damage)d%(bonus)s\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "%(nextExp)s to Go!" +InventoryGuestExp = "Guest Limit" +GuestLostExp = " Over Guest Limit" +InventoryAffectsOneCog = "Affects: One " + Cog +InventoryAffectsOneToon = "Affects: One Toon" +InventoryAffectsAllToons = "Affects: All Toons" +InventoryAffectsAllCogs = "Affects: All " + Cogs +InventoryHealString = "Toon-up" +InventoryDamageString = "Damage" +InventoryBattleMenu = "BATTLE MENU" +InventoryRun = "RUN" +InventorySOS = "SOS" +InventoryPass = "PASS" +InventoryFire = "FIRE" +InventoryClickToAttack = "Click a\ngag to\nattack" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "You must ride the trolley before leaving.\n\n\n\n\n\n\n\n\nYou can find the trolley next to Goofy's Gag Shop." +NPCForceAcknowledgeMessage2 = "You must return to Toon Headquarters before leaving.\n\n\n\n\n\n\n\n\n\nToon Headquarters is located near the center of the playground." +NPCForceAcknowledgeMessage3 = "Remember to ride the trolley.\n\n\n\n\n\n\n\nYou can find the trolley next to Goofy's Gag Shop." +NPCForceAcknowledgeMessage4 = "Congratulations! You found and rode the trolley!\n\n\n\n\n\n\n\n\n\nNow report back to Toon Headquarters." +NPCForceAcknowledgeMessage5 = "Don't forget your ToonTask!\n\n\n\n\n\n\n\n\n\n\nYou can find Cogs to defeat on the other side of tunnels like this." +NPCForceAcknowledgeMessage6 = "Great job defeating those Cogs!\n\n\n\n\n\n\n\n\nHead back to Toon Headquarters as soon as possible." +NPCForceAcknowledgeMessage7 = "Don't forget to make a friend!\n\n\n\n\n\n\nClick on another player and use the New Friend button." +NPCForceAcknowledgeMessage8 = "Great! You made a new friend!\n\n\n\n\n\n\n\n\nYou should go back at Toon Headquarters now." +NPCForceAcknowledgeMessage9 = "Good job using the phone!\n\n\n\n\n\n\n\n\nReturn to Toon Headquarters to claim your reward." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "You received 1 Throw point! When you get 10, you will get a new gag!" +MovieTutorialReward2 = "You received 1 Squirt point! When you get 10, you will get a new gag!" +MovieTutorialReward3 = "Good job! You completed your first ToonTask!" +MovieTutorialReward4 = "Go to Toon Headquarters for your reward!" +MovieTutorialReward5 = "Have fun!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toon-up', 'trap', 'lure', 'sound', 'throw', 'squirt', 'drop'] +BattleGlobalNPCTracks = ['restock', 'toons hit', 'cogs miss'] +BattleGlobalAvPropStrings = ( + ('Feather', 'Megaphone', 'Lipstick', 'Bamboo Cane', 'Pixie Dust', 'Juggling Balls', 'High Dive'), + ('Banana Peel', 'Rake', 'Marbles', 'Quicksand', 'Trapdoor', 'TNT', 'Railroad'), + ('$1 bill', 'Small Magnet', '$5 bill', 'Big Magnet', '$10 bill', 'Hypno-goggles', 'Presentation'), + ('Bike Horn', 'Whistle', 'Bugle', 'Aoogah', 'Elephant Trunk', 'Foghorn', 'Opera Singer'), + ('Cupcake', 'Fruit Pie Slice', 'Cream Pie Slice', 'Whole Fruit Pie', 'Whole Cream Pie', 'Birthday Cake', 'Wedding Cake'), + ('Squirting Flower', 'Glass of Water', 'Squirt Gun', 'Seltzer Bottle', 'Fire Hose', 'Storm Cloud', 'Geyser'), + ('Flower Pot', 'Sandbag', 'Anvil', 'Big Weight', 'Safe', 'Grand Piano', 'Toontanic') + ) +BattleGlobalAvPropStringsSingular = ( + ('a Feather', 'a Megaphone', 'a Lipstick', 'a Bamboo Cane', 'a Pixie Dust', 'a set of Juggling Balls', 'a High Dive'), + ('a Banana Peel', 'a Rake', 'a set of Marbles', 'a patch of Quicksand', 'a Trapdoor', 'a TNT', 'a Railroad'), + ('a $1 bill', 'a Small Magnet', 'a $5 bill', 'a Big Magnet', 'a $10 bill', 'a pair of Hypno-goggles', 'a Presentation'), + ('a Bike Horn', 'a Whistle', 'a Bugle', 'an Aoogah', 'an Elephant Trunk', 'a Foghorn', 'an Opera Singer'), + ('a Cupcake', 'a Fruit Pie Slice', 'a Cream Pie Slice', 'a Whole Fruit Pie', 'a Whole Cream Pie', 'a Birthday Cake', 'a Wedding Cake'), + ('a Squirting Flower', 'a Glass of Water', 'a Squirt Gun', 'a Seltzer Bottle', 'a Fire Hose', 'a Storm Cloud', 'a Geyser'), + ('a Flower Pot', 'a Sandbag', 'an Anvil', 'a Big Weight', 'a Safe', 'a Grand Piano', 'the Toontanic') + ) +BattleGlobalAvPropStringsPlural = ( + ('Feathers', 'Megaphones', 'Lipsticks', 'Bamboo Canes', 'Pixie Dusts', 'sets of Juggling Balls', 'High Dives'), + ('Banana Peels', 'Rakes', 'sets of Marbles', 'patches of Quicksand', 'Trapdoors','TNTs', 'Railroads'), + ('$1 bills', 'Small Magnets', '$5 bills', 'Big Magnets','$10 bills', 'pairs of Hypno-goggles', 'Presentations'), + ('Bike Horns', 'Whistles', 'Bugles', 'Aoogahs', 'Elephant Trunks', 'Foghorns', 'Opera Singers'), + ('Cupcakes', 'Fruit Pie Slices', 'Cream Pie Slices','Whole Fruit Pies', 'Whole Cream Pies', 'Birthday Cakes', 'Wedding cakes'), + ('Squirting Flowers', 'Glasses of Water', 'Squirt Guns','Seltzer Bottles', 'Fire Hoses', 'Storm Clouds', 'Geysers'), + ('Flower Pots', 'Sandbags', 'Anvils', 'Big Weights', 'Safes','Grand Pianos', 'Oceanliners') + ) +BattleGlobalAvTrackAccStrings = ("Medium", "Perfect", "Low", "High", "Medium", "High", "Low") +BattleGlobalLureAccLow = "Low" +BattleGlobalLureAccMedium = "Medium" + +AttackMissed = "MISSED" + +NPCCallButtonLabel = 'CALL' + +# ToontownLoader.py +LoaderLabel = "Loading..." + +# PlayGame.py +HeadingToHood = "Heading %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "Heading to your estate..." +HeadingToEstate = "Heading to %s's estate..." # avatar name +HeadingToFriend = "Heading to %s's friend's estate..." # avatar name + +# Hood.py +HeadingToPlayground = "Heading to the Playground..." +HeadingToStreet = "Heading %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Run all the way back to the playground?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "WHICH TOON?" +TownBattleChooseAvatarCogTitle = "WHICH " + Cog.upper() + "?" +TownBattleChooseAvatarBack = "BACK" + +#firecogpanel +FireCogTitle = "PINK SLIPS LEFT:%s\nFIRE WHICH COG?" +FireCogLowTitle = "PINK SLIPS LEFT:%s\nNOT ENOUGH SLIPS!" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "No friends to call!" +TownBattleSOSWhichFriend = "Call which friend?" +TownBattleSOSNPCFriends = "Rescued Toons" +TownBattleSOSBack = "BACK" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "Fire" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Waiting for\nother players..." +TownSoloBattleWaitTitle = "Please wait..." +TownBattleWaitBack = "BACK" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Searching for doodle\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s is %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "You may not board the trolley until your Laff meter is smiling." +TrolleyTFAMessage = "You may not board the trolley until " + Mickey + " says so." +TrolleyHopOff = "Hop off" + +# DistributedFishingSpot.py +FishingExit = "Exit" +FishingCast = "Cast" +FishingAutoReel = "Auto Reel" +FishingItemFound = "You caught:" +FishingCrankTooSlow = "Too\nslow" +FishingCrankTooFast = "Too\nfast" +FishingFailure = "You didn't catch anything!" +FishingFailureTooSoon = "Don't start to reel in the line until you see a nibble. Wait for your float to bob up and down rapidly!" +FishingFailureTooLate = "Be sure to reel in the line while the fish is still nibbling!" +FishingFailureAutoReel = "The auto-reel didn't work this time. Turn the crank by hand, at just the right speed, for your best chance to catch something!" +FishingFailureTooSlow = "You turned the crank too slowly. Some fish are faster than others. Try to keep the speed bar centered!" +FishingFailureTooFast = "You turned the crank too quickly. Some fish are slower than others. Try to keep the speed bar centered!" +FishingOverTankLimit = "Your fish bucket is full. Go sell your fish to the Pet Shop Clerk and come back." +FishingBroke = "You do not have any more jellybeans for bait! Ride the trolley or sell fish to the Pet Shop Clerks to earn more jellybeans." +FishingHowToFirstTime = "Click and drag down from the Cast button. The farther down you drag, the stronger your cast will be. Adjust your angle to hit the fish targets.\n\nTry it now!" +FishingHowToFailed = "Click and drag down from the Cast button. The farther down you drag, the stronger your cast will be. Adjust your angle to hit the fish targets.\n\nTry it again now!" +FishingBootItem = "An old boot" +FishingJellybeanItem = "%s jellybeans" +FishingNewEntry = "New Species!" +FishingNewRecord = "New Record!" + +# FishPoker +FishPokerCashIn = "Cash In\n%s\n%s" +FishPokerLock = "Lock" +FishPokerUnlock = "Unlock" +FishPoker5OfKind = "5 of a Kind" +FishPoker4OfKind = "4 of a Kind" +FishPokerFullHouse = "Full House" +FishPoker3OfKind = "3 of a Kind" +FishPoker2Pair = "2 Pair" +FishPokerPair = "Pair" + +# DistributedTutorial.py +TutorialGreeting1 = "Hi %s!" +TutorialGreeting2 = "Hi %s!\nCome over here!" +TutorialGreeting3 = "Hi %s!\nCome over here!\nUse the arrow keys!" +TutorialMickeyWelcome = "Welcome to Toontown!" +TutorialFlippyIntro = "Let me introduce you to my friend %s..." % Flippy +TutorialFlippyHi = "Hi, %s!" +TutorialQT1 = "You can talk by using this." +TutorialQT2 = "You can talk by using this.\nClick it, then choose \"Hi\"." +TutorialChat1 = "You can talk using either of these buttons." +TutorialChat2 = "The blue button lets you chat with the keyboard." +TutorialChat3 = "Be careful! Most other players won't understand what you say you when you use the keyboard." +TutorialChat4 = "The green button opens the %s." +TutorialChat5 = "Everyone can understand you if you use the %s." +TutorialChat6 = "Try saying \"Hi\"." +TutorialBodyClick1 = "Very good!" +TutorialBodyClick2 = "Pleased to meet you! Want to be friends?" +TutorialBodyClick3 = "To make friends with %s, click on him..." % Flippy +TutorialHandleBodyClickSuccess = "Good Job!" +TutorialHandleBodyClickFail = "Not quite. Try clicking right on %s..." % Flippy +TutorialFriendsButton = "Now click the 'Friends' button under %s's picture in the right hand corner." % Flippy +TutorialHandleFriendsButton = "And then click on the 'Yes' button.." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Would you like to make friends with %s?" % Flippy +TutorialFriendsPanelMickeyChat = "%s has agreed to be your friend. Click 'Ok' to finish up." % Flippy +TutorialFriendsPanelYes = "%s said yes!" % Flippy +TutorialFriendsPanelNo = "That's not very friendly!" +TutorialFriendsPanelCongrats = "Congratulations! You made your first friend." +TutorialFlippyChat1 = "Come see me when you are ready for your first ToonTask!" +TutorialFlippyChat2 = "I'll be in ToonHall!" +TutorialAllFriendsButton = "You can view all your friends by clicking the friends button. Try it out..." +TutorialEmptyFriendsList = "Right now your list is empty because %s isn't a real player." % Flippy +TutorialCloseFriendsList = "Click the 'Close'\nbutton to make the\nlist go away" +TutorialShtickerButton = "The button in the lower, right corner opens your Shticker Book. Try it..." +TutorialBook1 = "The book contains lots of useful information like this map of Toontown." +TutorialBook2 = "You can also check the progress of your ToonTasks." +TutorialBook3 = "When you are done click the book button again to make it close" +TutorialLaffMeter1 = "You will also need this..." +TutorialLaffMeter2 = "You will also need this...\nIt's your Laff meter." +TutorialLaffMeter3 = "When " + Cogs + " attack you, it gets lower." +TutorialLaffMeter4 = "When you are in playgrounds like this one, it goes back up." +TutorialLaffMeter5 = "When you complete ToonTasks, you will get rewards, like increasing your Laff limit." +TutorialLaffMeter6 = "Be careful! If the " + Cogs + " defeat you, you will lose all your gags." +TutorialLaffMeter7 = "To get more gags, play trolley games." +TutorialTrolley1 = "Follow me to the trolley!" +TutorialTrolley2 = "Hop on board!" +TutorialBye1 = "Play some games!" +TutorialBye2 = "Play some games!\nBuy some gags!" +TutorialBye3 = "Go see %s when you are done!" % Flippy + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "You are going the wrong way! Go find %s!" % Mickey + +PetTutorialTitle1 = "The Doodle Panel" +PetTutorialTitle2 = "Doodle SpeedChat" +PetTutorialTitle3 = "Doodle Cattlelog" +PetTutorialNext = "Next Page" +PetTutorialPrev = "Previous Page" +PetTutorialDone = "Done" +PetTutorialPage1 = "Click on a Doodle to display the Doodle panel. From here you can feed, scratch, and call the Doodle." +PetTutorialPage2 = "Use the new 'Pets' area in the SpeedChat menu to get a Doodle to do a trick. If he does it, reward him and he'll get better!" +PetTutorialPage3 = "Purchase new Doodle tricks from Clarabelle's Cattlelog. Better tricks give better Toon-Ups!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Gardening" +GardenTutorialTitle2 = "Flowers" +GardenTutorialTitle3 = "Trees" +GardenTutorialTitle4 = "How-to" +GardenTutorialTitle5 = "Statues" +GardenTutorialNext = "Next Page" +GardenTutorialPrev = "Previous Page" +GardenTutorialDone = "Done" +GardenTutorialPage1 = "Toon up your Estate with a garden! You can plant flowers, grow trees, harvest super-powerful gags, and decorate with statues!" +GardenTutorialPage2 = "Flowers are finicky and require unique jellybean recipes. Once grown, put them in the wheelbarrow to sell them and work toward Laff boosts!" +GardenTutorialPage3 = "Use a gag from your inventory to plant a tree. After a few days, that gag will do more damage! Remember to keep it healthy or the damage boost will go away." +GardenTutorialPage4 = "Walk up to these spots to plant, water, dig up or harvest your garden." +GardenTutorialPage5 = "Statues can be purchased in Clarabelle's Cattlelog. Increase your skill to unlock the more extravagant statues!" + +# Playground.py +PlaygroundDeathAckMessage = TheCogs+" took all your gags!\n\nYou are sad. You may not leave the playground until you are happy." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "The "+Foreman+" was defeated before you could reach him. You did not recover any Cog parts." + +# MintInterior +ForcedLeaveMintAckMsg = "The Mint Floor Supervisor was defeated before you could reach him. You did not recover any Cogbucks." + +# DistributedFactory.py +HeadingToFactoryTitle = "Heading to %s..." +ForemanConfrontedMsg = "%s is battling the "+Foreman+"!" + +# DistributedMint.py +MintBossConfrontedMsg = "%s is battling the Supervisor!" + +# DistributedStage.py +StageBossConfrontedMsg = "%s is battling the Clerk!" +stageToonEnterElevator = "%s \nhas entered the elevator" +ForcedLeaveStageAckMsg = "The Law Clerk was defeated before you could reach him. You did not recovI:\beta\toons\maya\work\character_moods\temp\ModelFixes\Textureser any Jury Notices." + + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Waiting for other players to join..." +MinigamePleaseWait = "Please wait..." +DefaultMinigameTitle = "Minigame Title" +DefaultMinigameInstructions = "Minigame Instructions" +HeadingToMinigameTitle = "Heading to %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Power Meter" +MinigamePowerMeterTooSlow = "Too\nslow" +MinigamePowerMeterTooFast = "Too\nfast" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Minigame Template" +MinigameTemplateInstructions = "This is a template minigame. Use it to create new minigames." + +# DistributedCannonGame.py +CannonGameTitle = "Cannon Game" +CannonGameInstructions = "Shoot your toon into the water tower as quickly as you can. Use the mouse or the arrow keys to aim the cannon. Be quick and win a big reward for everyone!" +CannonGameReward = "REWARD" + +# DistributedTwoDGame.py +TwoDGameTitle = "Toon Escape" +TwoDGameInstructions = "Escape from the " + Cog + " den as soon as you can. Use arrow keys to run/jump and Ctrl to squirt a " + Cog + ". Collect " + Cog + " treasures to gain even more points." +TwoDGameElevatorExit = "EXIT" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Tug-of-War" +TugOfWarInstructions = "Alternately tap the left and right arrow keys just fast enough to line up the green bar with the red line. Don't tap them too slow or too fast, or you'll end up in the water!" +TugOfWarGameGo = "GO!" +TugOfWarGameReady = "Ready..." +TugOfWarGameEnd = "Good game!" +TugOfWarGameTie = "You tied!" +TugOfWarPowerMeter = "Power meter" + +# DistributedPatternGame.py +PatternGameTitle = "Match %s" % Minnie +PatternGameInstructions = Minnie + " will show you a dance sequence. " + \ + "Try to repeat "+Minnie+"'s dance just the way you see it using the arrow keys!" +PatternGameWatch = "Watch these dance steps..." +PatternGameGo = "GO!" +PatternGameRight = "Good, %s!" +PatternGameWrong = "Oops!" +PatternGamePerfect = "That was perfect, %s!" +PatternGameBye = "Thanks for playing!" +PatternGameWaitingOtherPlayers = "Waiting for other players..." +PatternGamePleaseWait = "Please wait..." +PatternGameFaster = "You were\nfaster!" +PatternGameFastest = "You were\nthe fastest!" +PatternGameYouCanDoIt = "Come on!\nYou can do it!" +PatternGameOtherFaster = "\nwas faster!" +PatternGameOtherFastest = "\nwas the fastest!" +PatternGameGreatJob = "Great Job!" +PatternGameRound = "Round %s!" # Round 1! Round 2! .. +PatternGameImprov = "You did great! Now Improv!" + +# DistributedRaceGame.py +RaceGameTitle = "Race Game" +RaceGameInstructions = "Click a number. Choose wisely! You only advance if no one else picked the same number." +RaceGameWaitingChoices = "Waiting for other players to choose..." +RaceGameCardText = "%(name)s draws: %(reward)s" +RaceGameCardTextBeans = "%(name)s receives: %(reward)s" +RaceGameCardTextHi1 = "%(name)s is one Fabulous Toon!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " forward 1 space" +RaceGameForwardTwoSpaces = " forward 2 spaces" +RaceGameForwardThreeSpaces = " forward 3 spaces" +RaceGameBackOneSpace = " back 1 space" +RaceGameBackTwoSpaces = " back 2 spaces" +RaceGameBackThreeSpaces = " back 3 spaces" +RaceGameOthersForwardThree = " all others forward \n3 spaces" +RaceGameOthersBackThree = "all others back \n3 spaces" +RaceGameInstantWinner = "Instant Winner!" +RaceGameJellybeans2 = "2 jellybeans" +RaceGameJellybeans4 = "4 jellybeans" +RaceGameJellybeans10 = "10 jellybeans!" + +# DistributedRingGame.py +RingGameTitle = "Ring Game" +# color +RingGameInstructionsSinglePlayer = "Try to swim through as many of the %s rings as you can. Use the arrow keys to swim." +# color +RingGameInstructionsMultiPlayer = "Try to swim through the %s rings. Other players will try for the other colored rings. Use the arrow keys to swim." +RingGameMissed = "MISSED" +RingGameGroupPerfect = "GROUP\nPERFECT!!" +RingGamePerfect = "PERFECT!" +RingGameGroupBonus = "GROUP BONUS" + +# RingGameGlobals.py +ColorRed = "red" +ColorGreen = "green" +ColorOrange = "orange" +ColorPurple = "purple" +ColorWhite = "white" +ColorBlack = "black" +ColorYellow = "yellow" + +# DistributedDivingGame.py +DivingGameTitle = "Treasure Dive" +# color +DivingInstructionsSinglePlayer = "Treasures will appear at the bottom of the lake. Use the arrow keys to swim. Avoid the fish and get the treasures up to the boat!" +# color +DivingInstructionsMultiPlayer = "Treasures will appear at the bottom of the lake. Use the arrow keys to swim. Work together to get the treasures up to the boat!" +DivingGameTreasuresRetrieved = "Treasures Retrieved" + +#Distributed Target Game +TargetGameTitle = "Toon Slingshot" +TargetGameInstructionsSinglePlayer = "Land on targets to score points" +TargetGameInstructionsMultiPlayer = "Land on targets to score points" +TargetGameBoard = "Round %s - Keeping Best Score" +TargetGameCountdown = "Forced launch in %s seconds" +TargetGameCountHelp = "Pound left and right arrows for power, stop to launch" +TargetGameFlyHelp = "Press down to open umbrella" +TargetGameFallHelp = "Use the arrow keys to land on target" +TargetGameBounceHelp = " Bouncing can knock you off target" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nYou: %s" + +PhotoGameScoreBlank = "Score: %s" +PhotoGameScoreOther = "\n%s"#"Score: %s\n%s" +PhotoGameScoreYou = "\nBest Bonus!"#"Score: %s\nBest Bonus!" + + +# DistributedTagGame.py +TagGameTitle = "Tag Game" +TagGameInstructions = "Collect the treasures. You cannot collect treasure when you are IT!" +TagGameYouAreIt = "You Are IT!" +TagGameSomeoneElseIsIt = "%s is IT!" + +# DistributedMazeGame.py +MazeGameTitle = "Maze Game" +MazeGameInstructions = "Collect the treasures. Try to get them all, but look out for the " + Cogs + "!" + +# DistributedCatchGame.py +CatchGameTitle = "Catching Game" +CatchGameInstructions = "Catch as many %(fruit)s as you can. Watch out for the " + Cogs + ", and try not to 'catch' any %(badThing)s!" +CatchGamePerfect = "PERFECT!" +CatchGameApples = 'apples' +CatchGameOranges = 'oranges' +CatchGamePears = 'pears' +CatchGameCoconuts = 'coconuts' +CatchGameWatermelons = 'watermelons' +CatchGamePineapples = 'pineapples' +CatchGameAnvils = 'anvils' + +# DistributedPieTossGame.py +PieTossGameTitle = "Pie Toss Game" +PieTossGameInstructions = "Toss pies at the targets." + +# DistributedPhotoGame.py +PhotoGameInstructions = "Capture photos matching the toons shown at the bottom. Aim the camera with the mouse, and left click to take a picture. Press Ctrl to zoom in/out, and look around with the arrow keys. Pictures with higher ratings get more points!" +PhotoGameTitle = "Photo Fun" +PhotoGameFilm = "FILM" +PhotoGameScore = "Team Score: %s\n\nBest Photos: %s\n\nTotal Score: %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + " Thief" +CogThiefGameInstructions = "Keep the " + Cogs + " from stealing our gag barrels! Press the Ctrl key to throw a pie. Use the arrow keys to move. Tip: you can move diagonally." +CogThiefBarrelsSaved = "%(num)d Barrels\nSaved!" +CogThiefBarrelSaved = "%(num)d Barrel\nSaved!" +CogThiefNoBarrelsSaved = "No Barrels\nSaved" +CogThiefPerfect = "PERFECT!" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "PLAY" + +# Purchase.py +GagShopName = "Goofy's Gag Shop" +GagShopPlayAgain = "PLAY\nAGAIN" +GagShopBackToPlayground = "EXIT BACK TO\nPLAYGROUND" +GagShopYouHave = "You have %s jellybeans to spend" +GagShopYouHaveOne = "You have 1 jellybean to spend" +GagShopTooManyProps = "Sorry, you have too many props" +GagShopDoneShopping = "DONE\nSHOPPING" +# name of a gag +GagShopTooManyOfThatGag = "Sorry, you have enough %s already" +GagShopInsufficientSkill = "You do not have enough skill for that yet" +# name of a gag +GagShopYouPurchased = "You purchased %s" +GagShopOutOfJellybeans = "Sorry, you are all out of jellybeans!" +GagShopWaitingOtherPlayers = "Waiting for other players..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s has disconnected" +GagShopPlayerExited = "%s has exited" +GagShopPlayerPlayAgain = "Play Again" +GagShopPlayerBuying = "Buying" + +# MakeAToon.py +GenderShopQuestionMickey = "To make a boy toon, click on me!" +GenderShopQuestionMinnie = "To make a girl toon, click on me!" +GenderShopFollow = "Follow me!" +GenderShopSeeYou = "See you later!" +GenderShopBoyButtonText = "Boy" +GenderShopGirlButtonText = "Girl" + +# BodyShop.py +BodyShopHead = "Head" +BodyShopBody = "Body" +BodyShopLegs = "Legs" + +# ColorShop.py +ColorShopToon = "Toon Color" +ColorShopHead = "Head" +ColorShopBody = "Body" +ColorShopLegs = "Legs" +ColorShopParts = "Multi Color" +ColorShopAll = "Single Color" + +# ClothesShop.py +ClothesShopShorts = "Shorts" +ClothesShopShirt = "Shirts" +ClothesShopBottoms = "Bottoms" + +# MakeAToon +PromptTutorial = "Congratulations!!\nYou are Toontown's newest citizen!\n\nWould you like to continue to the Toontorial or teleport directly to Toontown Central?" +MakeAToonSkipTutorial = "Skip Toontorial" +MakeAToonEnterTutorial = "Enter Toontorial" +MakeAToonDone = "Done" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Back" +CreateYourToon = "Click the arrows to create your toon." +CreateYourToonTitle = "Choose Boy or Girl" +ShapeYourToonTitle = "Choose Your Type" +PaintYourToonTitle = "Choose Your Color" +PickClothesTitle = "Choose Your Clothes" +NameToonTitle = "Choose Your Name" +CreateYourToonHead = "Click the 'head' arrows to pick different animals." +MakeAToonClickForNextScreen = "Click the arrow below to go to the next screen." +PickClothes = "Click the arrows to pick clothes!" +PaintYourToon = "Click the arrows to paint your toon!" +MakeAToonYouCanGoBack = "You can go back to change your body too!" +MakeAFunnyName = "Choose a funny name for your toon with my Pick-A-Name game!" +MustHaveAFirstOrLast1 = "Your toon should have a first or last name, don't you think?" +MustHaveAFirstOrLast2 = "Don't you want your toon to have a first or last name?" +ApprovalForName1 = "That's it, your toon deserves a great name!" +ApprovalForName2 = "Toon names are the best kind of names!" +MakeAToonLastStep = "Last step before going to Toontown!" +PickANameYouLike = "Pick a name you like!" +TitleCheckBox = "Title" +FirstCheckBox = "First" +LastCheckBox = "Last" +RandomButton = "Random" +ShuffleButton = "Shuffle" +NameShopSubmitButton = "Submit" +TypeANameButton = "Type-A-Name" +TypeAName = "Don't like these names?\nClick here -->" +PickAName = "Try the PickAName game!\nClick here -->" +PickANameButton = "Pick-A-Name" +RejectNameText = "That name is not allowed. Please try again." +WaitingForNameSubmission = "Submitting your name..." + +# PetshopGUI.py +PetNameMaster = "PetNameMasterEnglish.txt" +PetshopUnknownName = "Name: ???" +PetshopDescGender = "Gender:\t%s" +PetshopDescCost = "Cost:\t%s jellybeans" +PetshopDescTrait = "Traits:\t%s" +PetshopDescStandard = "Standard" +PetshopCancel = lCancel +PetshopSell = "Sell Fish" +PetshopAdoptAPet = "Adopt a Doodle" +PetshopReturnPet = "Return your Doodle" +PetshopAdoptConfirm = "Adopt %s for %d jellybeans?" +PetshopGoBack = "Go Back" +PetshopAdopt = "Adopt" +PetshopReturnConfirm = "Return %s?" +PetshopReturn = "Return" +PetshopChooserTitle = "TODAY'S DOODLES" +PetshopGoHomeText = 'Would you like to go to your estate to play with your new Doodle?' + +# NameShop.py +NameShopNameMaster = "NameMasterEnglish.txt" +NameShopPay = "Subscribe" +NameShopPlay = "Free Trial" +NameShopOnlyPaid = "Only paid users\nmay name their Toons.\nUntil you subscribe\nyour name will be\n" +NameShopContinueSubmission = "Continue Submission" +NameShopChooseAnother = "Choose Another Name" +NameShopToonCouncil = "The Toon Council\nwill review your\nname. " + \ + "Review may\ntake a few days.\nWhile you wait\nyour name will be\n " +PleaseTypeName = "Please type your name:" +AllNewNames = "All new names must be\napproved by the Toon Council." +NameMessages = "Be creative and remember:\nno Disney-related names, please." +NameShopNameRejected = "The name you\nsubmitted has\nbeen rejected." +NameShopNameAccepted = "Congratulations!\nThe name you\nsubmitted has\nbeen accepted!" +NoPunctuation = "You can't use punctuation marks in your name!" +PeriodOnlyAfterLetter = "You can use a period in your name, but only after a letter." +ApostropheOnlyAfterLetter = "You can use an apostrophe in your name, but only after a letter." +NoNumbersInTheMiddle = "Numeric digits may not appear in the middle of a word." +ThreeWordsOrLess = "Your name must be three words or fewer." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "donald duck", + "donaldduck", + "pluto", + "goofy", + ) +NumToColor = ['White', 'Peach', 'Bright Red', 'Red', 'Maroon', + 'Sienna', 'Brown', 'Tan', 'Coral', 'Orange', + 'Yellow', 'Cream', 'Citrine', 'Lime', 'Sea Green', + 'Green', 'Light Blue', 'Aqua', 'Blue', + 'Periwinkle', 'Royal Blue', 'Slate Blue', 'Purple', + 'Lavender', 'Pink', 'Plum', 'Black'] +AnimalToSpecies = { + 'dog' : 'Dog', + 'cat' : 'Cat', + 'mouse' : 'Mouse', + 'horse' : 'Horse', + 'rabbit' : 'Rabbit', + 'duck' : 'Duck', + 'monkey' : 'Monkey', + 'bear' : 'Bear', + 'pig' : 'Pig' + } +NameTooLong = "That name is too long. Please try again." +ToonAlreadyExists = "You already have a toon named %s!" +NameAlreadyInUse = "That name is already used!" +EmptyNameError = "You must enter a name first." +NameError = "Sorry. That name will not work." + +# NameCheck.py +NCTooShort = 'That name is too short.' +NCNoDigits = 'Your name cannot contain numbers.' +NCNeedLetters = 'Each word in your name must contain some letters.' +NCNeedVowels = 'Each word in your name must contain some vowels.' +NCAllCaps = 'Your name cannot be all capital letters.' +NCMixedCase = 'That name has too many capital letters.' +NCBadCharacter = "Your name cannot contain the character '%s'" +NCGeneric = 'Sorry, that name will not work.' +NCTooManyWords = 'Your name cannot be more than four words long.' +NCDashUsage = ("Dashes may only be used to connect two words together " + "(like in 'Boo-Boo').") +NCCommaEdge = "Your name may not begin or end with a comma." +NCCommaAfterWord = "You may not begin a word with a comma." +NCCommaUsage = ('That name does not use commas properly. Commas must ' + 'join two words together, like in the name "Dr. Quack, MD". ' + 'Commas must also be followed by a space.') +NCPeriodUsage = ('That name does not use periods properly. Periods are ' + 'only allowed in words like "Mr.", "Mrs.", "J.T.", etc.') +NCApostrophes = 'That name has too many apostrophes.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+": "+TheCogs+" took over one of the buildings you rescued!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Need more time to think?' +STOREOWNER_GOODBYE = 'See you later!' +STOREOWNER_NEEDJELLYBEANS = 'You need to ride the Trolley to get some jellybeans.' +STOREOWNER_GREETING = 'Choose what you want to buy.' +STOREOWNER_BROWSING = 'You can browse, but you need a clothing ticket to buy.' +STOREOWNER_NOCLOTHINGTICKET = 'You need a clothing ticket to shop for clothes.' +# translate +STOREOWNER_NOFISH = 'Come back here to sell fish to the Pet Shop for jellybeans.' +STOREOWNER_THANKSFISH = 'Thanks! The Pet Shop will love these. Bye!' +STOREOWNER_THANKSFISH_PETSHOP = "These are some fine specimens! Thanks." +STOREOWNER_PETRETURNED = "Don't worry. We'll find a good home for your Doodle." +STOREOWNER_PETADOPTED = "Congratulations on purchasing a Doodle! You can play with your new friend at your estate." +STOREOWNER_PETCANCELED = "Remember, if you see a Doodle you like, make sure to adopt him before someone else does!" + +STOREOWNER_NOROOM = "Hmm...you might want to make room in your closet before you buy new clothes.\n" +STOREOWNER_CONFIRM_LOSS = "Your closet is full. You will lose the clothes you were wearing." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Wow! You collected %s of %s fish. That deserves a trophy and a Laff boost!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": A Cog Invasion has begun!!!" +SuitInvasionBegin2 = lToonHQ+": %s have taken over Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": The %s Invasion has ended!!!" +SuitInvasionEnd2 = lToonHQ+": The Toons have saved the day once again!!!" +SuitInvasionUpdate1 = lToonHQ+": The Cog Invasion is now at %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": We must defeat those %s!!!" +SuitInvasionBulletin1 = lToonHQ+": There is a Cog Invasion in progress!!!" +SuitInvasionBulletin2 = lToonHQ+": %s have taken over Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Toon Platoon" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown has a new citizen! Do you have some extra gags?" +QuestScriptTutorialMickey_2 = "Sure, %s!" +QuestScriptTutorialMickey_3 = "Tutorial Tom will tell you all about the Cogs.\aGotta go!" +QuestScriptTutorialMickey_4 = "Come here! Use the arrow keys to move." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown has a new citizen! Do you have some extra gags?" +QuestScriptTutorialMinnie_2 = "Sure, %s!" +QuestScriptTutorialMinnie_3 = "Tutorial Tom will tell you all about the Cogs.\aGotta go!" + +QuestScript101_1 = "These are Cogs. They are robots that are trying to take over Toontown." +QuestScript101_2 = "There are many different kinds of Cogs and..." +QuestScript101_3 = "...they turn happy Toon buildings..." +QuestScript101_4 = "...into ugly Cog buildings!" +QuestScript101_5 = "But Cogs can't take a joke!" +QuestScript101_6 = "A good gag will stop them." +QuestScript101_7 = "There are lots of gags, but take these to start." +QuestScript101_8 = "Oh! You also need a Laff meter!" +QuestScript101_9 = "If your Laff meter gets too low, you'll be sad!" +QuestScript101_10 = "A happy Toon is a healthy Toon!" +QuestScript101_11 = "OH NO! There's a Cog outside my shop!" +QuestScript101_12 = "HELP ME, PLEASE! Defeat that Cog!" +QuestScript101_13 = "Here is your first ToonTask!" +QuestScript101_14 = "Hurry up! Go defeat that Flunky!" + +QuestScript110_1 = "Good work defeating that Flunky. Let me give you a Shticker Book..." +QuestScript110_2 = "The book is full of good stuff." +QuestScript110_3 = "Open it, and I'll show you." +QuestScript110_4 = "The map shows where you've been." +QuestScript110_5 = "Turn the page to see your gags..." +QuestScript110_6 = "Uh oh! You have no gags! I will assign you a task." +QuestScript110_7 = "Turn the page to see your tasks." +QuestScript110_8 = "Take a ride on the trolley, and earn jelly beans to buy gags!" +QuestScript110_9 = "To get to the trolley, go out the door behind me and head for the playground." +QuestScript110_10 = "Now, close the book and find the trolley!" +QuestScript110_11 = "Return to Toon HQ when you are done. Bye!" + +QuestScriptTutorialBlocker_1 = "Why, hello there!" +QuestScriptTutorialBlocker_2 = "Hello?" +QuestScriptTutorialBlocker_3 = "Oh! You don't know how to use SpeedChat!" +QuestScriptTutorialBlocker_4 = "Click on the button to say something." +QuestScriptTutorialBlocker_5 = "Very good!\aWhere you are going there are many Toons to talk to." +QuestScriptTutorialBlocker_6 = "If you want to chat with other Toons using the keyboard, there's another button you can use." +QuestScriptTutorialBlocker_7 = "It's called the Chat button. You need to turn on Speedchat Plus in your Account Manager on the Toontown Web site to use it." +QuestScriptTutorialBlocker_8 = "Good luck! See you later!" + +""" +GagShopTut + +You will also earn the ability to use other types of gags. + +""" + +QuestScriptGagShop_1 = "Welcome to the Gag Shop!" +QuestScriptGagShop_1a = "This is where Toons come to buy gags to use against the Cogs." +#QuestScriptGagShop_2 = "This jar shows how many jellybeans you have." +#QuestScriptGagShop_3 = "To buy a gag, click on a gag button. Try it now!" +QuestScriptGagShop_3 = "To buy gags, click on the gag buttons. Try getting some now!" +QuestScriptGagShop_4 = "Good! You can use these gags in battle against the Cogs." +QuestScriptGagShop_5 = "Here's a peek at the advanced throw and squirt gags..." +QuestScriptGagShop_6 = "When you're done buying gags, click this button to return to the Playground." +QuestScriptGagShop_7 = "Normally you can use this button to play another Trolley Game..." +QuestScriptGagShop_8 = "...but there's no time for another game right now. You're needed in Toon HQ!" + +QuestScript120_1 = "Good job finding the trolley!\aBy the way, have you met Banker Bob?\aHe has quite a sweet tooth.\aWhy don't you introduce yourself by taking him this candy bar as a gift." +QuestScript120_2 = "Banker Bob is over in the Toontown Bank." + +QuestScript121_1 = "Yum, thank you for the Candy Bar.\aSay, if you can help me, I'll give you a reward.\aThose Cogs stole the keys to my safe. Defeat Cogs to find a stolen key.\aWhen you find a key, bring it back to me." + +QuestScript130_1 = "Good job finding the trolley!\aBy the way, I received a package for Professor Pete today.\aIt must be his new chalk he ordered.\aCan you please take it to him?\aHe is over in the school house." + +QuestScript131_1 = "Oh, thanks for the chalk.\aWhat?!?\aThose Cogs stole my blackboard. Defeat Cogs to find my stolen blackboard.\aWhen you find it, bring it back to me." + +QuestScript140_1 = "Good job finding the trolley!\aBy the way, I have this friend, Librarian Larry, who is quite a book worm.\aI picked this book up for him last time I was over in "+lDonaldsDock+".\aCould you take it over to him, he is usually in the Library." + +QuestScript141_1 = "Oh, yes, this book almost completes my collection.\aLet me see...\aUh oh...\aNow where did I put my glasses?\aI had them just before those Cogs took over my building.\aDefeat Cogs to find my stolen glasses.\aWhen you find them, bring them back to me for a reward." + +QuestScript145_1 = "I see you had no problem with the trolley!\aListen, the Cogs have stolen our blackboard eraser.\aGo into the streets and fight Cogs until you recover the eraser.\aTo reach the streets go through one of the tunnels like this:" +QuestScript145_2 = "When you find our eraser, bring it back here.\aDon't forget, if you need gags, ride the trolley.\aAlso, if you need to recover Laff points, collect ice cream cones in the Playground." + +QuestScript150_1 = "Great work!\aToontown is more fun when you have friends!" +QuestScript150_2 = "To make friends, find another player, and use the New Friend button." +QuestScript150_3 = "Once you have made a friend, come back here." +QuestScript150_4 = "Some tasks are too difficult to do alone!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +SellbotBossName = "Senior V. P." +CashbotBossName = "C. F. O." +LawbotBossName = "Chief Justice" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "You are hereby promoted to full-fledged %s. Congratulations!" +BossCogDoobersAway = { 's' : "Go! And make that sale!" } +BossCogWelcomeToons = "Welcome, new Cogs!" +BossCogPromoteToons = "You are hereby promoted to full-fledged %s. Congratu--" +CagedToonInterruptBoss = "Hey! Hiya! Hey over there!" +CagedToonRescueQuery = "So, did you Toons come to rescue me?" +BossCogDiscoverToons = "Huh? Toons! In disguise!" +BossCogAttackToons = "Attack!!" +CagedToonDrop = [ + "Great job! You're wearing him down!", + "Keep after him! He's on the run!", + "You guys are doing great!", + "Fantastic! You've almost got him now!", + ] +CagedToonPrepareBattleTwo = "Look out, he's trying to get away!\aHelp me, everyone--get up here and stop him!" +CagedToonPrepareBattleThree = "Hooray, I'm almost free!\aNow you need to attack the V.P. Cog directly.\aI've got a whole bunch of pies you can use!\aJump up and touch the bottom of my cage and I'll give you some pies.\aPress the Delete key to throw pies once you've got them!" +BossBattleNeedMorePies = "You need to get more pies!" +BossBattleHowToGetPies = "Jump up to touch the cage to get pies." +BossBattleHowToThrowPies = "Press the Delete key to throw pies!" +CagedToonYippee = "Yippee!" +CagedToonThankYou = "It's great to be free!\aThanks for all your help!\aI am in your debt.\aIf you ever need help in battle, just give me a call!\aJust click on the SOS button to call me." +CagedToonPromotion = "\aSay--that V.P. Cog left behind your promotion papers.\aI'll file them for you on the way out, so you'll get your promotion!" +CagedToonLastPromotion = "\aWow, you've reached level %s on your Cog suit!\aCogs don't get promoted higher than that.\aYou can't upgrade your Cog suit anymore, but you can certainly keep rescuing Toons!" +CagedToonHPBoost = "\aYou've rescued a lot of Toons from this HQ.\aThe Toon Council has decided to give you another Laff point. Congratulations!" +CagedToonMaxed = "\aI see that you have a level %s Cog suit. Very impressive!\aOn behalf of the Toon Council, thank you for coming back to rescue more Toons!" +CagedToonGoodbye = "See ya!" + + +CagedToonBattleThree = { + 10: "Nice jump, %(toon)s. Here are some pies!", + 11: "Hi, %(toon)s! Have some pies!", + 12: "Hey there, %(toon)s! You've got some pies now!", + + 20: "Hey, %(toon)s! Jump up to my cage and get some pies to throw!", + 21: "Hi, %(toon)s! Use the Ctrl key to jump up and touch my cage!", + + 100: "Press the Delete key to throw a pie.", + 101: "The blue power meter shows how high your pie will go.", + 102: "First try to lob a pie inside his undercarriage to gum up his works.", + 103: "Wait for the door to open, and throw a pie straight inside.", + 104: "When he's dizzy, hit him in the face or chest to knock him back!", + 105: "You'll know you've got a good hit when you see the splat in color.", + 106: "If you hit a Toon with a pie, it gives that Toon a Laff point!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "That's it. I've had enough of these pesky Toons!" +CashbotBossOuttaHere = "I've got a train to catch!" +ResistanceToonName = "Mata Hairy" +ResistanceToonCongratulations = "You did it! Congratulations!\aYou're an asset to the Resistance!\aHere's a special phrase you can use in a tight spot:\a%s\aWhen you say it, %s.\aBut you can only use it once, so choose that time well!" +ResistanceToonToonupInstructions = "all the Toons near you will gain %s Laff points" +ResistanceToonToonupAllInstructions = "all the Toons near you will gain full Laff points" +ResistanceToonMoneyInstructions = "all the Toons near you will gain %s jellybeans" +ResistanceToonMoneyAllInstructions = "all the Toons near you will fill their jellybean jars" +ResistanceToonRestockInstructions = "all the Toons near you will restock their \"%s\" gags" +ResistanceToonRestockAllInstructions = "all the Toons near you will restock all their gags" + +ResistanceToonLastPromotion = "\aWow, you've reached level %s on your Cog suit!\aCogs don't get promoted higher than that.\aYou can't upgrade your Cog suit anymore, but you can certainly keep working for the Resistance!" +ResistanceToonHPBoost = "\aYou've done a lot of work for the Resistance.\aThe Toon Council has decided to give you another Laff point. Congratulations!" +ResistanceToonMaxed = "\aI see that you have a level %s Cog suit. Very impressive!\aOn behalf of the Toon Council, thank you for coming back to rescue more Toons!" + +CashbotBossCogAttack = "Get them!!!" +ResistanceToonWelcome = "Hey, you made it! Follow me to the main vault before the C.F.O. finds us!" +ResistanceToonTooLate = "Blast it! We're too late!" +CashbotBossDiscoverToons1 = "Ah-HAH!" +CashbotBossDiscoverToons2 = "I thought I smelled something a little toony in here! Imposters!" +ResistanceToonKeepHimBusy = "Keep him busy! I'm going to set a trap!" +ResistanceToonWatchThis = "Watch this!" +CashbotBossGetAwayFromThat = "Hey! Get away from that!" +ResistanceToonCraneInstructions1 = "Control a magnet by stepping up to a podium." +ResistanceToonCraneInstructions2 = "Use the arrow keys to move the crane, and press the Ctrl key to grab an object." +ResistanceToonCraneInstructions3 = "Grab a safe with a magnet and knock the C.F.O.'s safe-ty helmet off." +ResistanceToonCraneInstructions4 = "Once his helmet is gone, grab a disabled goon and hit him in the head!" +ResistanceToonGetaway = "Eek! Gotta run!" +CashbotCraneLeave = "Leave Crane" +CashbotCraneAdvice = "Use the arrow keys to move the overhead crane." +CashbotMagnetAdvice = "Hold down the control key to pick things up." +CashbotCraneLeaving = "Leaving crane" + +MintElevatorRejectMessage = "You cannot enter the Mints until you have completed your %s Cog Suit." +BossElevatorRejectMessage = "You cannot board this elevator until you have earned a promotion." +NotYetAvailable = "This elevator is not yet available." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Furniture" +PaintingTypeName = "Painting" +ClothingTypeName = "Clothing" +ChatTypeName = "SpeedChat Phrase" +EmoteTypeName = "Acting Lessons" +BeanTypeName = "Jellybeans" +PoleTypeName = "Fishing Pole" +WindowViewTypeName = "Window View" +PetTrickTypeName = "Doodle Training" +GardenTypeName = "Garden Supplies" +RentalTypeName = "Rental Item" +GardenStarterTypeName = "Gardening Kit" +NametagTypeName = "Name tag" + + +# Make sure numbers match up to CatalogItemTypes.py +CatalogItemTypeNames = { + 0 : "INVALID_ITEM", + 1 : FurnitureTypeName, + 2 : ChatTypeName, + 3 : ClothingTypeName, + 4 : EmoteTypeName, + 5 : "WALLPAPER", + 6 : "Window View", + 7 : "FLOORING", + 8 : "MOULDING", + 9 : "WAINSCOTING", + 10: PoleTypeName, + 11: PetTrickTypeName, + 12: BeanTypeName, + 13: GardenTypeName, + 14: RentalTypeName, + 15: GardenStarterTypeName, + 16: NametagTypeName, + 17: "TOON_STATUE", + 18: "ANIMATED FURNITURE", +} + + +# Make sure this is in sync with ToonDNA.ShirtStyles +ShirtStylesDescriptions = { + # ------------------------------------------------------------------------- + # Boy styles + # ------------------------------------------------------------------------- + 'bss1' : "solid", + 'bss2' : "single stripe", + 'bss3' : "collar", + 'bss4' : "double stripe", + 'bss5' : "multiple stripes", + 'bss6' : "collar w/ pocket", + 'bss7' : "hawaiian", + 'bss8' : "collar w/ 2 pockets", + 'bss9' : "bowling shirt", + 'bss10' : "vest (special)", + 'bss11' : "collar w/ ruffles", + 'bss12' : "soccer jersey (special)", + 'bss13' : "lightning bolt (special)", + 'bss14' : "jersey 19 (special)", + 'bss15' : "guayavera", + + # ------------------------------------------------------------------------- + # Girl styles + # ------------------------------------------------------------------------- + 'gss1' : "girl solid", + 'gss2' : "girl single stripe", + 'gss3' : "girl collar", + 'gss4' : "girl double stripes", + 'gss5' : "girl collar w/ pocket", + 'gss6' : "girl flower print", + 'gss7' : "girl flower trim (special)", + 'gss8' : "girl collar w/ 2 pockets", + 'gss9' : "girl denim vest (special)", + 'gss10' : "girl peasant", + 'gss11' : "girl peasant w/ mid stripe", + 'gss12' : "girl soccer jersey (special)", + 'gss13' : "girl hearts", + 'gss14' : "girl stars (special)", + 'gss15' : "girl flower", + + # ------------------------------------------------------------------------- + # Special Catalog-only shirts. + # ------------------------------------------------------------------------- + # yellow hooded - Series 1 + 'c_ss1' : "yellow hooded - Series 1", + 'c_ss2' : "yellow with palm tree - Series 1", + 'c_ss3' : "purple with stars - Series 2", + 'c_bss1' : "blue stripes (boys only) - Series 1", + 'c_bss2' : "orange (boys only) - Series 1", + 'c_bss3' : "lime green with stripe (boys only) - Series 2", + 'c_bss4' : "red kimono with checkerboard (boys only) - Series 2", + 'c_gss1' : "girl blue with yellow stripes (girls only) - Series 1", + 'c_gss2' : "girl pink and beige with flower (girls only) - Series 1", + 'c_gss3' : "girl Blue and gold with wavy stripes (girls only) - Series 2", + 'c_gss4' : "girl Blue and pink with bow (girls only) - Series 2", + 'c_gss5' : "girl Aqua kimono white stripe (girls only) - UNUSED", + 'c_ss4' : "Tie dye shirt (boys and girls) - Series 3", + 'c_ss5' : "light blue with blue and white stripe (boys only) - Series 3", + 'c_ss6' : "cowboy shirt 1 : Series 4", + 'c_ss7' : "cowboy shirt 2 : Series 4", + 'c_ss8' : "cowboy shirt 3 : Series 4", + 'c_ss9' : "cowboy shirt 4 : Series 4", + 'c_ss10' : "cowboy shirt 5 : Series 4", + 'c_ss11' : "cowboy shirt 6 : Series 4", + + # Special Holiday-themed shirts. + 'hw_ss1' : "Halloween ghost", + 'hw_ss2' : "Halloween pumpkin", + 'wh_ss1' : "Winter Holiday 1", + 'wh_ss2' : "Winter Holiday 2", + 'wh_ss3' : "Winter Holiday 3", + 'wh_ss4' : "Winter Holiday 4", + + 'vd_ss1' : "girl Valentines day, pink with red hearts (girls)", + 'vd_ss2' : "Valentines day, red with white hearts", + 'vd_ss3' : "Valentines day, white with winged hearts (boys)", + 'vd_ss4' : " Valentines day, pink with red flamed heart", + 'vd_ss5' : "2009 Valentines day, white with red cupid", + 'vd_ss6' : "2009 Valentines day, blue with green and red hearts", + 'vd_ss7' : "2010 Valentines day, red with white wings", + 'sd_ss1' : "St Pat's Day, four leaf clover shirt", + 'sd_ss2' : "St Pat's Day, pot o gold shirt", + 'tc_ss1' : "T-Shirt Contest, Fishing Vest", + 'tc_ss2' : "T-Shirt Contest, Fish Bowl", + 'tc_ss3' : "T-Shirt Contest, Paw Print", + 'tc_ss4' : "T-Shirt Contest, Backpack", + 'tc_ss5' : "T-Shirt Contest, Lederhosen ", + 'tc_ss6' : "T-Shirt Contest, Watermelon ", + 'tc_ss7' : "T-Shirt Contest, Race Shirt", + 'j4_ss1' : "July 4th, Flag", + 'j4_ss2' : "July 4th, Fireworks", + 'c_ss12' : "Catalog series 7, Green w/ yellow buttons", + 'c_ss13' : "Catalog series 7, Purple w/ big flower", + + 'pj_ss1' : "Blue Banana Pajama shirt", + 'pj_ss2' : "Red Horn Pajama shirt", + 'pj_ss3' : "Purple Glasses Pajama shirt", + + # Special award clothes + 'sa_ss1' : "Award Striped Shirt", + 'sa_ss2' : "Award Fishing Shirt 1", + 'sa_ss3' : "Award Fishing Shirt 2", + 'sa_ss4' : "Award Gardening Shirt 1", + 'sa_ss5' : "Award Gardening Shirt 2", + 'sa_ss6' : "Award Party Shirt 1", + 'sa_ss7' : "Award Party Shirt 2", + 'sa_ss8' : "Award Racing Shirt 1", + 'sa_ss9' : "Award Racing Shirt 2", + 'sa_ss10' : "Award Summer Shirt 1", + 'sa_ss11' : "Award Summer Shirt 2", + 'sa_ss12' : "Award Golf Shirt 1", + 'sa_ss13' : "Award Golf Shirt 2", + 'sa_ss14' : "Award Halloween Costume Shirt 1", + 'sa_ss15' : "Award Halloween Costume Shirt 2", + 'sa_ss16' : "Award Matathon Shirt 1", + 'sa_ss17' : "Award Save Building Shirt 1", + 'sa_ss18' : "Award Save Building Shirt 2", + 'sa_ss19' : "Award Toontask Shirt 1", + 'sa_ss20' : "Award Toontask Shirt 2", + 'sa_ss21' : "Award Trolley Shirt 1", + 'sa_ss22' : "Award Trolley Shirt 2", + 'sa_ss23' : "Award Winter Shirt 1", + 'sa_ss24' : "Award Halloween Costume Shirt 3", + 'sa_ss25' : "Award Halloween Costume Shirt 4", + 'sa_ss26' : "Award Most Cogs Defeated Shirt", + + # Scientists + 'sc_1' : "Scientist top 1", + 'sc_2' : "Scientist top 2", + 'sc_3' : "Scientist top 3", + + # Silly Story Shirts + 'sil_1' : "Silly Mailbox Shirt", + 'sil_2' : "Silly Trash Can Shirt", + 'sil_3' : "Loony Labs Shirt", + 'sil_4' : "Silly Hydrant Shirt", + 'sil_5' : "Sillymeter Whistle Shirt", + 'sil_6' : "Silly Cog-Crusher Shirt", + 'sil_7' : "Victory Party Shirt 1", + 'sil_8' : "Victory Party Shirt 2", + + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + } + +# Make sure this is in sync with ToonDNA.BottomStyles +BottomStylesDescriptions = { + # name : [ bottomIdx, [bottomColorIdx, ...]] + # ------------------------------------------------------------------------- + # Boy styles (shorts) + # ------------------------------------------------------------------------- + 'bbs1' : "plain w/ pockets", + 'bbs2' : "belt", + 'bbs3' : "cargo", + 'bbs4' : "hawaiian", + 'bbs5' : "side stripes (special)", + 'bbs6' : "soccer shorts", + 'bbs7' : "side flames (special)", + 'bbs8' : "denim", + 'vd_bs1' : "Valentines shorts", + 'vd_bs2' : "Green with red heart", + 'vd_bs3' : "Blue denim with green and red heart", + + # Catalog only shorts + 'c_bs1' : "Orange with blue side stripes", + 'c_bs2' : "Blue with gold cuff stripes", + 'c_bs5' : 'Green stripes - series 7', + 'sd_bs1' : 'St. Pats leprechaun shorts', + 'pj_bs1' : 'Blue Banana Pajama pants', + 'pj_bs2' : 'Red Horn Pajama pants', + 'pj_bs3' : 'Purple Glasses Pajama pants', + 'wh_bs1' : 'Winter Holiday Shorts Style 1', + 'wh_bs2' : 'Winter Holiday Shorts Style 2', + 'wh_bs3' : 'Winter Holiday Shorts Style 3', + 'wh_bs4' : 'Winter Holiday Shorts Style 4', + + # Silly Story Shorts + 'sil_bs1' : 'Silly Cog-Crusher Shorts', + + # ------------------------------------------------------------------------- + # Girl styles (shorts and skirts) + # ------------------------------------------------------------------------- + # skirts + # ------------------------------------------------------------------------- + 'gsk1' : 'solid', + 'gsk2' : 'polka dots (special)', + 'gsk3' : 'vertical stripes', + 'gsk4' : 'horizontal stripe', + 'gsk5' : 'flower print', + 'gsk6' : '2 pockets (special) ', + 'gsk7' : 'denim skirt', + + # shorts + # ------------------------------------------------------------------------- + 'gsh1' : 'plain w/ pockets', + 'gsh2' : 'flower', + 'gsh3' : 'denim shorts', + # Special catalog-only skirts and shorts. + 'c_gsk1' : 'blue skirt with tan border and button', + 'c_gsk2' : 'purple skirt with pink and ribbon', + 'c_gsk3' : 'teal skirt with yellow and star', + + # Valentines skirt + 'vd_gs1' : 'red skirt with hearts', + 'vd_gs2' : 'Pink flair skirt with polka hearts', + 'vd_gs3' : 'Blue denim skirt with green and red heart', + 'c_gsk4' : 'rainbow skirt - Series 3', + 'sd_gs1' : 'St. Pats day shorts', + 'c_gsk5' : 'Western skirts 1', + 'c_gsk6' : 'Western skirts 2', + # Western shorts + 'c_bs3' : 'Western shorts 1', + 'c_bs4' : 'Western shorts 2', + 'j4_bs1' : 'July 4th shorts', + 'j4_gs1' : 'July 4th Skirt', + 'c_gsk7' : 'Blue with flower - series 7', + 'pj_gs1' : 'Blue Banana Pajama pants', + 'pj_gs2' : 'Red Horn Pajama pants', + 'pj_gs3' : 'Purple Glasses Pajama pants', + 'wh_gsk1' : 'Winter Holiday Skirt Style 1', + 'wh_gsk2' : 'Winter Holiday Skirt Style 2', + 'wh_gsk3' : 'Winter Holiday Skirt Style 3', + 'wh_gsk4' : 'Winter Holiday Skirt Style 4', + + 'sa_bs1' : "Award Fishing Shorts", + 'sa_bs2' : "Award Gardening Shorts", + 'sa_bs3' : "Award Party Shorts", + 'sa_bs4' : "Award Racing Shorts", + 'sa_bs5' : "Award Summer Shorts", + 'sa_bs6' : "Award Golf Shorts 1", + 'sa_bs7' : "Award Halloween Costume Shorts 1", + 'sa_bs8' : "Award Halloween Costume Shorts 2", + 'sa_bs9' : "Award Save Building Shorts 1", + 'sa_bs10' : "Award Trolley Shorts 1", + 'sa_bs11' : "Award Halloween Shorts 3", + 'sa_bs12' : "Award Halloween Shorts 4", + + 'sa_gs1' : "Award Fishing Skirt", + 'sa_gs2' : "Award Gardening Skirt", + 'sa_gs3' : "Award Party Skirt", + 'sa_gs4' : "Award Racing Skirt", + 'sa_gs5' : "Award Summer Skirt", + 'sa_gs6' : "Award Golf Skirt 1", + 'sa_gs7' : "Award Halloween Costume Skirt 1", + 'sa_gs8' : "Award Halloween Cosmtume Skirt 2", + 'sa_gs9' : "Award Save Building Skirt 1", + 'sa_gs10' : "Award Trolley Skirt 1", + 'sa_gs11' : "Award Halloween Skirt 3", + 'sa_gs12' : "Award Halloween Skirt 4", + + 'sc_bs1' : "Scientist bottom male 1", + 'sc_bs2' : "Scientist bottom male 2", + 'sc_bs3' : "Scientist bottom male 3", + + 'sc_gs1' : "Scientist bottom female 1", + 'sc_gs2' : "Scientist bottom female 2", + 'sc_gs3' : "Scientist bottom female 3", + + 'sil_bs1' : "Silly Cog-Crusher Shorts male", + 'sil_gs1' : "Silly Cog-Crusher Shorts female", + } + +AwardMgrBoy = "boy" +AwardMgrGirl = "girl" +AwardMgrUnisex = "unisex" +AwardMgrShorts = "shorts" +AwardMgrSkirt = "skirt" +AwardMgrShirt = "shirt" + +# Special Event Strings to display in mailbox screen +SpecialEventMailboxStrings = { + 1 : "A special item from the Toon Council just for you!", + 2 : "Here is your Melville's Fishing Tournament prize! Congratulations!", + 3 : "Here is your Billy Budd's Fishing Tournament prize! Congratulations!", + 4 : "Here is your Acorn Acres April Invitational prize! Congratulations!", + 5 : "Here is your Acorn Acres C.U.P. Championship prize! Congratulations!", + 6 : "Here is your Gift-Giving Extravaganza prize! Congratulations!", + 7 : "Here is your Top Toons New Year's Day Marathon prize! Congratulations!", + 8 : "Here is your Perfect Trolley Games Weekend prize! Congratulations!", + 9 : "Here is your Trolley Games Madness prize! Congratulations!", + 10 : "Here is your Grand Prix Weekend prize! Congratulations!", + 11 : "Here is your ToonTask Derby prize! Congratulations!", + 12 : "Here is your Save a Building Marathon prize! Congratulations!", + 13 : "Here is your Most Cogs Defeated Tournament prize! Congratulations!", + } + +# Rental items +RentalHours = "Hours" +RentalOf = "Of" +RentalCannon = "Cannons!" +RentalGameTable = "Game Table!" + +EstateCannonGameEnd = "The Cannon Game rental is over." +GameTableRentalEnd = "The Game Table rental is over." + +MessageConfirmRent = "Begin rental? Cancel to save the rental for later" +MessageConfirmGarden = "Are you sure you want to start a garden?" + +#nametag Names +NametagPaid = "Citizen Name Tag" +NametagAction = "Action Name Tag" +NametagFrilly = "Frilly Name Tag" + +FurnitureYourOldCloset = "your old wardrobe" +FurnitureYourOldBank = "your old bank" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +# these gets shown in the catalog guis, descriptions must be short and can be duplicated +FurnitureNames = { + 100 : "Armchair", + 105 : "Armchair", + 110 : "Chair", + 120 : "Desk Chair", + 130 : "Log Chair", + 140 : "Lobster Chair", + 145 : "Lifejacket Chair", + 150 : "Saddle Stool", + 160 : "Native Chair", + 170 : "Cupcake Chair", + 200 : "Bed", + 205 : "Bed", + 210 : "Bed", + 220 : "Bathtub Bed", + 230 : "Leaf Bed", + 240 : "Boat Bed", + 250 : "Cactus Hammock", + 260 : "Ice Cream Bed", + 270 : "Olivia Erin & Cat's Bed", + 300 : "Player Piano", + 310 : "Pipe Organ", + 400 : "Fireplace", + 410 : "Fireplace", + 420 : "Round Fireplace", + 430 : "Fireplace", + 440 : "Apple Fireplace", + 450 : "Erin's Fireplace", + 460 : "Erin's Lit Fireplace", + 470 : "Lit Fireplace", + 480 : "Round Lit Fireplace", + 490 : "Lit Fireplace", + 491 : "Lit Fireplace", + 492 : "Apple Lit Fireplace", + 500 : "Wardrobe", + 502 : "15 item Wardrobe", + 504 : "20 item Wardrobe", + 506 : "25 item Wardrobe", + 510 : "Wardrobe", + 512 : "15 item Wardrobe", + 514 : "20 item Wardrobe", + 516 : "25 item Wardrobe", + 600 : "Short Lamp", + 610 : "Tall Lamp", + 620 : "Table Lamp", + 625 : "Table Lamp", + 630 : "Daisy Lamp", + 640 : "Daisy Lamp", + 650 : "Jellyfish Lamp", + 660 : "Jellyfish Lamp", + 670 : "Cowboy Lamp", + 680 : "Candle", + 681 : "Lit Candle", + 700 : "Cushioned Chair", + 705 : "Cushioned Chair", + 710 : "Couch", + 715 : "Couch", + 720 : "Hay Couch", + 730 : "Shortcake Couch", + 800 : "Desk", + 810 : "Log Desk", + 900 : "Umbrella Stand", + 910 : "Coat Rack", + 920 : "Trash Can", + 930 : "Red Mushroom", + 940 : "Yellow Mushroom", + 950 : "Coat Rack", + 960 : "Barrel Stand", + 970 : "Cactus Plant", + 980 : "Teepee", + 990 : "Juliette's Fan", + 1000 : "Large Rug", + 1010 : "Round Rug", + 1015 : "Round Rug", + 1020 : "Small Rug", + 1030 : "Leaf Mat", + 1040 : "Presents", + 1050 : "Sled", + 1100 : "Display Cabinet", + 1110 : "Display Cabinet", + 1120 : "Tall Bookcase", + 1130 : "Low Bookcase", + 1140 : "Sundae Chest", + 1200 : "End Table", + 1210 : "Small Table", + 1215 : "Small Table", + 1220 : "Coffee Table", + 1230 : "Coffee Table", + 1240 : "Snorkeler's Table", + 1250 : "Cookie Table", + 1260 : "Bedroom Table", + 1300 : "1000 Bean Bank", + 1310 : "2500 Bean Bank", + 1320 : "5000 Bean Bank", + 1330 : "7500 Bean Bank", + 1340 : "10000 Bean Bank", + 1399 : "Telephone", + 1400 : "Cezanne Toon", + 1410 : "Flowers", + 1420 : "Modern Mickey", + 1430 : "Rembrandt Toon", + 1440 : "Toonscape", + 1441 : "Whistler's Horse", + 1442 : "Toon Star", + 1443 : "Not a Pie", + 1450 : "Mickey and Minnie", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Television", + 1600 : "Short Vase", + 1610 : "Tall Vase", + 1620 : "Short Vase", + 1630 : "Tall Vase", + 1640 : "Short Vase", + 1650 : "Short Vase", + 1660 : "Coral Vase", + 1661 : "Shell Vase", + 1670 : "Rose Vase", + 1680 : "Rose Watercan", + 1700 : "Popcorn Cart", + 1710 : "Ladybug", + 1720 : "Fountain", + 1725 : "Washing Machine", + 1800 : "Fish Bowl", + 1810 : "Fish Bowl", + 1900 : "Swordfish", + 1910 : "Hammerhead", + 1920 : "Hanging Horns", + 1930 : "Simple Sombrero", + 1940 : "Fancy Sombrero", + 1950 : "Dream Catcher", + 1960 : "Horseshoe", + 1970 : "Bison Portrait", + 2000 : "Candy Swing Set", + 2010 : "Cake Slide", + 3000 : "Banana Split Tub", + 10000 : "Short Pumpkin", + 10010 : "Tall Pumpkin", + 10020 : "Winter Tree", + 10030 : "Winter Wreath" + } + +# these gets shown in the award manager web page, descriptions must be unique +AwardManagerFurnitureNames = { + 100 : "Armchair A - Series 1", + 105 : "Armchair A - Series 7", + 110 : "Chair - Series 1", + 120 : "Desk Chair - Series 2", + 130 : "Log Chair - Series 2", + 140 : "Lobster Chair - Series 3", + 145 : "Lifejacket Chair - Series 3", + 150 : "Saddle Stool - Series 4", + 160 : "Native Chair - Series 4", + 170 : "Cupcake Chair - Series 6", + 200 : "Bed Boy's bed - Initial Furniture", + 205 : "Bed Boy's bed Series 7", + 210 : "Bed Girl's bed - Series 1", + 220 : "Bathtub Bed", + 230 : "Leaf Bed", + 240 : "Boat Bed", + 250 : "Cactus Hammock", + 260 : "Ice Cream Bed", + 270 : "Olivia Erin & Cat's Bed - Trolley Bed", + 300 : "Player Piano", + 310 : "Pipe Organ", + 400 : "Fireplace - Square Fireplace Initial Furniture", + 410 : "Fireplace - Girly Fireplace Series 1", + 420 : "Round Fireplace", + 430 : "Fireplace - bug room series 2", + 440 : "Apple Fireplace", + 450 : "Erin's Fireplace - coral", + 460 : "Erin's Lit Fireplace - coral", + 470 : "Lit Fireplace - square fireplace with fire", + 480 : "Round Lit Fireplace", + 490 : "Lit Fireplac - girl fireplace with firee", + 491 : "Lit Fireplace - bug room fireplace", + 492 : "Apple Lit Fireplace", + 500 : "boy Wardrobe - 10 items initial", + 502 : "boy 15 item Wardrobe", + 504 : "boy 20 item Wardrobe", + 506 : "boy 25 item Wardrobe", + 510 : "girl Wardrobe - 10 items initial", + 512 : "girl 15 item Wardrobe", + 514 : "girl 20 item Wardrobe", + 516 : "girl 25 item Wardrobe", + 600 : "Short Lamp", + 610 : "Tall Lamp", + 620 : "Table Lamp - Series 1", + 625 : "Table Lamp - Series 7", + 630 : "Daisy Lamp 1", + 640 : "Daisy Lamp 2", + 650 : "Jellyfish Lamp 1", + 660 : "Jellyfish Lamp 2", + 670 : "Cowboy Lamp", + 680 : "Candle", + 681 : "Lit Candle", + 700 : "Cushioned Chair - Series 1", + 705 : "Cushioned Chair - Series 7", + 710 : "Couch - series 1", + 715 : "Couch - series 7", + 720 : "Hay Couch", + 730 : "Shortcake Couch", + 800 : "Desk", + 810 : "Log Desk", + 900 : "Umbrella Stand", + 910 : "Coat Rack - series 1", + 920 : "Trash Can", + 930 : "Red Mushroom", + 940 : "Yellow Mushroom", + 950 : "Coat Rack - underwater", + 960 : "Barrel Stand", + 970 : "Cactus Plant", + 980 : "Teepee", + 990 : "Juliette's Fan - gag fan", + 1000 : "Large Rug", + 1010 : "Round Rug - Series 1", + 1015 : "Round Rug - Series 7", + 1020 : "Small Rug", + 1030 : "Leaf Mat", + 1040 : "Presents", + 1050 : "Sled", + 1100 : "Display Cabinet - Red", + 1110 : "Display Cabinet - Yellow", + 1120 : "Tall Bookcase", + 1130 : "Low Bookcase", + 1140 : "Sundae Chest", + 1200 : "End Table", + 1210 : "Small Table - series 1 ", + 1215 : "Small Table - series 7", + 1220 : "Coffee Table sq", + 1230 : "Coffee Table bw", + 1240 : "Snorkeler's Table", + 1250 : "Cookie Table", + 1260 : "Bedroom Table", + 1300 : "1000 Bean Bank", + 1310 : "2500 Bean Bank", + 1320 : "5000 Bean Bank", + 1330 : "7500 Bean Bank", + 1340 : "10000 Bean Bank", + 1399 : "Telephone", + 1400 : "Cezanne Toon", + 1410 : "Flowers", + 1420 : "Modern Mickey", + 1430 : "Rembrandt Toon", + 1440 : "Toonscape", + 1441 : "Whistler's Horse", + 1442 : "Toon Star", + 1443 : "Not a Pie", + 1450 : "Mickey and Minnie", + 1500 : "Radio A series 2", + 1510 : "Radio B series 1", + 1520 : "Radio C series 2", + 1530 : "Television", + 1600 : "Short Vase A", + 1610 : "Tall Vase A", + 1620 : "Short Vase B", + 1630 : "Tall Vase B", + 1640 : "Short Vase C", + 1650 : "Short Vase D", + 1660 : "Coral Vase", + 1661 : "Shell Vase", + 1670 : "Rose Vase", + 1680 : "Rose Watercan", + 1700 : "Popcorn Cart", + 1710 : "Ladybug", + 1720 : "Fountain", + 1725 : "Washing Machine", + 1800 : "Fish Bowl skull", + 1810 : "Fish Bowl lizard", + 1900 : "Swordfish", + 1910 : "Hammerhead", + 1920 : "Hanging Horns", + 1930 : "Simple Sombrero", + 1940 : "Fancy Sombrero", + 1950 : "Dream Catcher", + 1960 : "Horseshoe", + 1970 : "Bison Portrait", + 2000 : "Candy Swing Set", + 2010 : "Cake Slide", + 3000 : "Banana Split Tub", + 10000 : "Short Pumpkin", + 10010 : "Tall Pumpkin", + 10020 : "Winter Tree", + 10030 : "Winter Wreath" + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "Shirt", + "Shirt", + "Shirt", + "Shorts", + "Shorts", + "Skirt", + "Shorts", + ) + +ClothingTypeNames = { + 1400 : "Matthew's Shirt", + 1401 : "Jessica's Shirt", + 1402 : "Marissa's Shirt", + 1600 : "Trap Outfit", + 1601 : "Sound Outfit", + 1602 : "Lure Outfit", + 1603 : "Trap Outfit", + 1604 : "Sound Outfit", + 1605 : "Lure Outfit", + 1606 : "Trap Outfit", + 1607 : "Sound Outfit", + 1608 : "Lure Outfit", + 1749 : "Silly Mailbox Shirt", + 1750 : "Silly Trash Can Shirt", + 1751 : "Loony Labs Shirt", + 1752 : "Silly Hydrant Shirt", + 1753 : "Silly Meter Shirt", + 1754 : "Cog-Crusher Shirt", + 1755 : "Cog-Crusher Shorts", + 1756 : "Cog-Crusher Shorts", + 1757 : "Victory Party Shirt", + 1758 : "Relaxed Victory Shirt", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "Wallpaper", + "Moulding", + "Flooring", + "Wainscoting", + "Border", + ) + +WallpaperNames = { + 1000 : "Parchment", + 1100 : "Milan", + 1200 : "Dover", + 1300 : "Victoria", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Harlequin", + 1700 : "Moon", + 1800 : "Stars", + 1900 : "Flowers", + 2000 : "Spring Garden", + 2100 : "Formal Garden", + 2200 : "Race Day", + 2300 : "Touchdown!", + 2400 : "Cloud 9", + 2500 : "Climbing Vine", + 2600 : "Springtime", + 2700 : "Kokeshi", + 2800 : "Posies", + 2900 : "Angel Fish", + 3000 : "Bubbles", + 3100 : "Bubbles", + 3200 : "Go Fish", + 3300 : "Stop Fish", + 3400 : "Sea Horse", + 3500 : "Sea Shells", + 3600 : "Underwater", + 3700 : "Boots", + 3800 : "Cactus", + 3900 : "Cowboy Hat", + 10100 : "Cats", + 10200 : "Bats", + 11000 : "Snowflakes", + 11100 : "Hollyleaf", + 11200 : "Snowman", + 12000 : "ValenToons", + 12100 : "ValenToons", + 12200 : "ValenToons", + 12300 : "ValenToons", + 13000 : "Shamrock", + 13100 : "Shamrock", + 13200 : "Rainbow", + 13300 : "Shamrock", + } + +FlooringNames = { + 1000 : "Hardwood Floor", + 1010 : "Carpet", + 1020 : "Diamond Tile", + 1030 : "Diamond Tile", + 1040 : "Grass", + 1050 : "Beige Bricks", + 1060 : "Red Bricks", + 1070 : "Square Tile", + 1080 : "Stone", + 1090 : "Boardwalk", + 1100 : "Dirt", + 1110 : "Wood Tile", + 1120 : "Tile", + 1130 : "Honeycomb", + 1140 : "Water", + 1150 : "Beach Tile", + 1160 : "Beach Tile", + 1170 : "Beach Tile", + 1180 : "Beach Tile", + 1190 : "Sand", + 10000 : "Ice Cube", + 10010 : "Igloo", + 11000 : "Shamrock", + 11010 : "Shamrock", + } + + +MouldingNames = { + 1000 : "Knotty", + 1010 : "Painted", + 1020 : "Dental", + 1030 : "Flowers", + 1040 : "Flowers", + 1050 : "Ladybug", + 1060 : "ValenToons", + 1070 : "Beach", + 1080 : "Winter Lights 1", + 1085 : "Winter Lights 2", + 1090 : "Winter Lights 3", + 1100 : "ValenToon's Cupid", + 1110 : "ValenToon's Heart 1", + 1120 : "ValenToon's Heart 2", + } + +WainscotingNames = { + 1000 : "Painted", + 1010 : "Wood Panel", + 1020 : "Wood", + 1030 : "ValenToons", + 1040 : "Underwater", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Large Garden", + 20 : "Wild Garden", + 30 : "Greek Garden", + 40 : "Cityscape", + 50 : "Wild West", + 60 : "Under the Sea", + 70 : "Tropical Island", + 80 : "Starry Night", + 90 : "Tiki Pool", + 100 : "Frozen Frontier", + 110 : "Farm Country", + 120 : "Native Camp", + 130 : "Main Street", + } + +SpecialEventNames = { + 1: "Generic Award", + 2: "Melville's Fishing Tournament", + 3: "Billy Budd's Fishing Tournament", + 4: "Acorn Acres April Invitational", + 5: "Acorn Acres C.U.P. Championship", + 6: "Gift-Giving Extravaganza", + 7: "Top Toons New Year's Day Marathon", + 8: "Perfect Trolley Games Weekend", + 9: "Trolley Games Madness", + 10: "Grand Prix Weekend", + 11: "ToonTask Derby", + 12: "Save a Building Marathon", + 13: "Most Cogs Defeated", +} + + +# don't translate yet +NewCatalogNotify = "There are new items available to order at your phone!" +NewDeliveryNotify = "A new delivery has just arrived at your mailbox!" +CatalogNotifyFirstCatalog = "Your first cattlelog has arrived! You may use this to order new items for yourself or for your house." +CatalogNotifyNewCatalog = "Your cattlelog #%s has arrived! You can go to your phone to order items from this cattlelog." +CatalogNotifyNewCatalogNewDelivery = "A new delivery has arrived at your mailbox! Also, your cattlelog #%s has arrived!" +CatalogNotifyNewDelivery = "A new delivery has arrived at your mailbox!" +CatalogNotifyNewCatalogOldDelivery = "Your cattlelog #%s has arrived, and there are still items waiting in your mailbox!" +CatalogNotifyOldDelivery = "There are still items waiting in your mailbox for you to pick up!" +CatalogNotifyInstructions = "Click the \"Go home\" button on the map page in your Shticker Book, then walk up to the phone inside your house." +CatalogNewDeliveryButton = "New\nDelivery!" +CatalogNewCatalogButton = "New\nCattlelog" +CatalogSaleItem = "Sale! " + +# don't translate yet +DistributedMailboxEmpty = "Your mailbox is empty right now. Come back here to look for deliveries after you place an order from your phone!" +DistributedMailboxWaiting = "Your mailbox is empty right now, but the package you ordered is on its way. Check back later!" +DistributedMailboxReady = "Your order has arrived!" +DistributedMailboxNotOwner = "Sorry, this is not your mailbox." +DistributedPhoneEmpty = "You can use any phone to order special items for you and your house. New items will become available to order over time.\n\nYou don't have any items available to order right now, but check back later!" + +# don't translate yet +Clarabelle = "Clarabelle" +MailboxExitButton = "Close Mailbox" +MailboxAcceptButton = "Take this item" +MailBoxDiscard = "Discard this item" +MailboxAcceptInvite = "Accept this invite" +MailBoxRejectInvite = "Reject this invite" +MailBoxDiscardVerify = "Are you sure you want to Discard %s?" +MailBoxRejectVerify = "Are you sure you want to Reject %s?" +MailboxOneItem = "Your mailbox contains 1 item." +MailboxNumberOfItems = "Your mailbox contains %s items." +MailboxGettingItem = "Taking %s from mailbox." +MailboxGiftTag = "Gift From: %s" +MailboxGiftTagAnonymous = "Anonymous" +MailboxItemNext = "Next\nItem" +MailboxItemPrev = "Previous\nItem" +MailboxDiscard = "Discard" +MailboxReject = "Reject" +MailboxLeave = "Keep" +CatalogCurrency = "beans" +CatalogHangUp = "Hang Up" +CatalogNew = "NEW" +CatalogBackorder = "BACKORDER" +CatalogLoyalty = "SPECIAL" +CatalogPagePrefix = "Page" +CatalogGreeting = "Hello! Thanks for calling Clarabelle's Cattlelog. Can I help you?" +CatalogGoodbyeList = ["Bye now!", + "Call back soon!", + "Thanks for calling!", + "Ok, bye now!", + "Bye!", + ] +CatalogHelpText1 = "Turn the page to see items for sale." +CatalogSeriesLabel = "Series %s" +CatalogGiftFor = "Buy Gift for:" +CatalogGiftTo = "To: %s" +CatalogGiftToggleOn = "Stop Gifting" +CatalogGiftToggleOff = "Buy Gifts" +CatalogGiftToggleWait = "Trying!..." +CatalogGiftToggleNoAck = "Unavailable" +CatalogPurchaseItemAvailable = "Congratulations on your new purchase! You can start using it right away." +CatalogPurchaseGiftItemAvailable = "Excellent! %s can start using your gift right away." +CatalogPurchaseItemOnOrder = "Congratulations! Your purchase will be delivered to your mailbox soon." +CatalogPurchaseGiftItemOnOrder = "Excellent! Your gift to %s will be delivered to their mailbox." +CatalogAnythingElse = "Anything else I can get you today?" +CatalogPurchaseClosetFull = "Your closet is full. You may purchase this item anyway, but if you do you will need to delete something from your closet to make room for it when it arrives.\n\nDo you still want to purchase this item?" +CatalogAcceptClosetFull = "Your closet is full. You must go inside and delete something from your closet to make room for this item before you can take it out of your mailbox." +CatalogAcceptShirt = "You are now wearing your new shirt. What you were wearing before has been moved to your closet." +CatalogAcceptShorts = "You are now wearing your new shorts. What you were wearing before has been moved to your closet." +CatalogAcceptSkirt = "You are now wearing your new skirt. What you were wearing before has been moved to your closet." +CatalogAcceptPole = "You're now ready to go catch some bigger fish with your new pole!" +CatalogAcceptPoleUnneeded = "You already have a better pole than this one!" +CatalogAcceptChat = "You now have a new SpeedChat!" +CatalogAcceptEmote = "You now have a new Emotion!" +CatalogAcceptBeans = "You received some jelly beans!" +CatalogAcceptRATBeans = "Your Toon recruit reward has arrived!" +CatalogAcceptNametag = "Your new name tag has arrived!" +CatalogAcceptGarden = "Your garden supplies have arrived!" +CatalogAcceptPet = "You now have a new Pet Trick!" +CatalogPurchaseHouseFull = "Your house is full. You may purchase this item anyway, but if you do you will need to delete something from your house to make room for it when it arrives.\n\nDo you still want to purchase this item?" +CatalogAcceptHouseFull = "Your house is full. You can not accept this item until you free up some room. Would you like to discard this item now?" +CatalogAcceptInAttic = "Your new item is now in your attic. You can put it in your house by going inside and clicking on the \"Move Furniture\" button." +CatalogAcceptInAtticP = "Your new items are now in your attic. You can put them in your house by going inside and clicking on the \"Move Furniture\" button." +CatalogPurchaseMailboxFull = "Your mailbox is full! You can't purchase this item until you take some items out of your mailbox to make room." +CatalogPurchaseGiftMailboxFull = "%s's mailbox is full! You can't purchase this item." +CatalogPurchaseOnOrderListFull = "You have too many items currently on order. You can't order any more items until some of the ones you have already ordered arrive." +CatalogPurchaseGiftOnOrderListFull = "%s has too many items currently on order." +CatalogPurchaseGeneralError = "The item could not be purchased because of some internal game error: error code %s." +CatalogPurchaseGiftGeneralError = "The item could not be gifted to %(friend)s because of some internal game error: error code %(error)s." +CatalogPurchaseGiftNotAGift = "This item could not be sent to %s because it would be an unfair advantage." +CatalogPurchaseGiftWillNotFit = "This item could not be sent to %s because it doesn't fit them." +CatalogPurchaseGiftLimitReached = "This item could not be sent to %s because they've already have it." +CatalogPurchaseGiftNotEnoughMoney = "This item could not be sent to %s because you can't afford it." +CatalogAcceptGeneralError = "The item could not be removed from your mailbox because of some internal game error: error code %s." +CatalogAcceptRoomError = "You don't have any place to put this. You'll have to get rid of something." +CatalogAcceptLimitError = "You already have as many of these as you can handle. You'll have to get rid of something." +CatalogAcceptFitError = "This won't fit you! You donate it to needy toons." +CatalogAcceptInvalidError = "This item has gone out of style! You donate it to needy toons." + +MailboxOverflowButtonDicard = "Discard" +MailboxOverflowButtonLeave = "Leave" + +# don't translate yet +HDMoveFurnitureButton = "Move\nFurniture" +HDStopMoveFurnitureButton = "Done\nMoving" +HDAtticPickerLabel = "In the attic" +HDInRoomPickerLabel = "In the room" +HDInTrashPickerLabel = "In the trash" +HDDeletePickerLabel = "Delete?" +HDInAtticLabel = "Attic" +HDInRoomLabel = "Room" +HDInTrashLabel = "Trash" +HDToAtticLabel = "Send\nto attic" +HDMoveLabel = "Move" +HDRotateCWLabel = "Rotate Right" +HDRotateCCWLabel = "Rotate Left" +HDReturnVerify = "Return this item to the attic?" +HDReturnFromTrashVerify = "Return this item to the attic from the trash?" +HDDeleteItem = "Click OK to send this item to the trash, or Cancel to keep it." +HDNonDeletableItem = "You can't delete items of this type!" +HDNonDeletableBank = "You can't delete your bank!" +HDNonDeletableCloset = "You can't delete your wardrobe!" +HDNonDeletablePhone = "You can't delete your phone!" +HDNonDeletableNotOwner = "You can't delete %s's things!" +HDHouseFull = "Your house is full. You have to delete something else from your house or attic before you can return this item from the trash." + +HDHelpDict = { + "DoneMoving" : "Finish room decorating.", + "Attic" : "Show list of items in attic. The attic stores items that are not in your room.", + "Room" : "Show list of items in room. Useful for finding lost items.", + "Trash" : "Show items in trash. Oldest items are deleted after a while or when trash overflows.", + "ZoomIn" : "Get a closer view of room.", + "ZoomOut" : "Get a farther view of room.", + "SendToAttic" : "Send the current furniture item to attic for storage.", + "RotateLeft" : "Turn left.", + "RotateRight" : "Turn right.", + "DeleteEnter" : "Change to delete mode.", + "DeleteExit" : "Exit delete mode.", + "FurnitureItemPanelDelete" : "Send %s to trash.", + "FurnitureItemPanelAttic" : "Place %s in room.", + "FurnitureItemPanelRoom" : "Return %s to attic.", + "FurnitureItemPanelTrash" : "Return %s to attic.", + } + + + +# don't translate yet +MessagePickerTitle = "You have too many phrases. In order to purchase\n\"%s\"\n you must choose one to remove:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Are you sure you want to remove \"%s\" from your SpeedChat menu?" + + +# don't translate yet +CatalogBuyText = "Buy" +CatalogRentText = "Rent" +CatalogGiftText = "Gift" +CatalogOnOrderText = "On Order" +CatalogPurchasedText = "Already\nPurchased" +CatalogCurrent = "Current" +CatalogGiftedText = "Gifted\nTo You" +CatalogPurchasedGiftText = "Already\nOwned" +CatalogMailboxFull = "No Room" +CatalogNotAGift = "Not a Gift" +CatalogNoFit = "Doesn't\nFit" +CatalogMembersOnly = "Members\nOnly!" +CatalogSndOnText = "Snd On" +CatalogSndOffText = "Snd Off" + +CatalogPurchasedMaxText = "Already\nPurchased Max" +CatalogVerifyPurchase = "Purchase %(item)s for %(price)s jellybeans?" +CatalogVerifyRent = "Rent %(item)s for %(price)s jellybeans?" +CatalogVerifyGift = "Purchase %(item)s for %(price)s jellybeans as a gift for %(friend)s?" +CatalogOnlyOnePurchase = "You may only have one of these items at a time. If you purchase this one, it will replace %(old)s.\n\nAre you sure you want to purchase %(item)s for %(price)s jellybeans?" + +# don't translate yet +CatalogExitButtonText = "Hang Up" +CatalogCurrentButtonText = "To Current Items" +CatalogPastButtonText = "To Past Items" + + + +TutorialHQOfficerName = "HQ Harry" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tutorial Tom", + 999 : "Toon Tailor", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Banker Bob", + 2003 : "Professor Pete", + 2004 : "Tammy the Tailor", + 2005 : "Librarian Larry", + 2006 : "Clerk Clark", + 2011 : "Clerk Clara", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Fisherman Freddy", + 2018 : "Duff..err..TIP Man", + # NPCPetClerks + 2013 : "Clerk Poppy", + 2014 : "Clerk Peppy", + 2015 : "Clerk Pappy", + # NPCPartyPerson + 2016 : "Party Planner Pumpkin", + 2017 : "Party Planner Polly", + 2018 : "Doctor Surlee", + 2019 : "Doctor Dimm", + 2020 : "Professor Prepostera", + + # Silly Street + 2101 : "Dentist Daniel", + 2102 : "Sheriff Sherry", + 2103 : "Sneezy Kitty", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canary Coalmine", + 2109 : "Babbles Blowhard", + 2110 : "Bill Board", + 2111 : "Dancing Diego", + 2112 : "Dr. Tom", + 2113 : "Rollo The Amazing", + 2114 : "Roz Berry", + 2115 : "Patty Papercut", + 2116 : "Bruiser McDougal", + 2117 : "Ma Putrid", + 2118 : "Jesse Jester", + 2119 : "Honey Haha", + 2120 : "Professor Binky", + 2121 : "Madam Chuckle", + 2122 : "Harry Ape", + 2123 : "Spamonia Biggles", + 2124 : "T.P. Rolle", + 2125 : "Lazy Hal", + 2126 : "Professor Guffaw", + 2127 : "Woody Nickel", + 2128 : "Loony Louis", + 2129 : "Frank Furter", + 2130 : "Joy Buzzer", + 2131 : "Feather Duster", + 2132 : "Daffy Don", + 2133 : "Dr. Euphoric", + 2134 : "Silent Simone", + 2135 : "Mary", + 2136 : "Sal Snicker", + 2137 : "Happy Heikyung", + 2138 : "Muldoon", + 2139 : "Dan Dribbles", + 2140 : "Fisherman Billy", + + # Loopy Lane + 2201 : "Postmaster Pete", + 2202 : "Shirley U. Jest", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Will Wiseacre", + 2208 : "Sticky Lou", + 2209 : "Charlie Chortle", + 2210 : "Tee Hee", + 2211 : "Sally Spittake", + 2212 : "Weird Warren", + 2213 : "Lucy Tires", + 2214 : "Sam Stain", + 2215 : "Sid Seltzer", + 2216 : "Nona Seeya", + 2217 : "Sharky Jones", + 2218 : "Fanny Pages", + 2219 : "Chef Knucklehead", + 2220 : "Rick Rockhead", + 2221 : "Clovinia Cling", + 2222 : "Shorty Fuse", + 2223 : "Sasha Sidesplitter", + 2224 : "Smokey Joe", + 2225 : "Fisherman Droopy", + + # Punchline Place + 2301 : "Dr. Pulyurleg", + 2302 : "Professor Wiggle", + 2303 : "Nurse Nancy", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Gas", + 2309 : "Big Bruce", + 2311 : "Franz Neckvein", + 2312 : "Dr. Sensitive", + 2313 : "Lucy Shirtspot", + 2314 : "Ned Slinger", + 2315 : "Chewy Morsel", + 2316 : "Cindy Sprinkles", + 2318 : "Tony Maroni", + 2319 : "Zippy", + 2320 : "Crunchy Alfredo", + 2321 : "Fisherman Punchy", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Clerk Will", + 1002 : "Clerk Bill", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Longjohn Leroy", + # NPCFisherman + 1008 : "Fisherman Furball", + # NPCPetClerks + 1009 : "Clerk Barky", + 1010 : "Clerk Purr", + 1011 : "Clerk Bloop", + # NPCPartyPerson + 1012 : "Party Planner Pickles", + 1013 : "Party Planner Patty", + + # Barnacle Blvd. + 1101 : "Billy Budd", + 1102 : "Captain Carl", + 1103 : "Fishy Frank", + 1104 : "Doctor Squall", + 1105 : "Admiral Hook", + 1106 : "Mrs. Starch", + 1107 : "Cal Estenicks", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gary Glubglub", + 1113 : "Lisa Luff", + 1114 : "Charlie Chum", + 1115 : "Sheila Squid, Atty", + 1116 : "Barnacle Bessie", + 1117 : "Captain Yucks", + 1118 : "Choppy McDougal", + 1121 : "Linda Landlubber", + 1122 : "Salty Stan", + 1123 : "Electra Eel", + 1124 : "Flappy Docksplinter", + 1125 : "Eileen Overboard", + 1126 : "Fisherman Barney", + + # Seaweed Street + 1201 : "Barnacle Barbara", + 1202 : "Art", + 1203 : "Ahab", + 1204 : "Rocky Shores", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professor Plank", + 1210 : "Gang Wei", + 1211 : "Wynn Bag", + 1212 : "Toby Tonguestinger", + 1213 : "Dante Dolphin", + 1214 : "Gusty Kate", + 1215 : "Dinah Down", + 1216 : "Rod Reel", + 1217 : "CC Weed", + 1218 : "Pacific Tim", + 1219 : "Brian Beachead", + 1220 : "Carla Canal", + 1221 : "Blisters McKee", + 1222 : "Shep Ahoy", + 1223 : "Sid Squid", + 1224 : "Emily Eel", + 1225 : "Bonzo Bilgepump", + 1226 : "Heave Ho", + 1227 : "Coral Reef", + 1228 : "Fisherman Reed", + + # Lighthouse Lane + 1301 : "Alice", + 1302 : "Melville", + 1303 : "Claggart", + 1304 : "Svetlana", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Seafoam", + 1310 : "Ted Tackle", + 1311 : "Topsy Turvey", + 1312 : "Ethan Keel", + 1313 : "William Wake", + 1314 : "Rusty Ralph", + 1315 : "Doctor Drift", + 1316 : "Wilma Wobble", + 1317 : "Paula Pylon", + 1318 : "Dinghy Dan", + 1319 : "Davey Drydock", + 1320 : "Ted Calm", + 1321 : "Dinah Docker", + 1322 : "Whoopie Cushion", + 1323 : "Stinky Ned", + 1324 : "Pearl Diver", + 1325 : "Ned Setter", + 1326 : "Felicia Chips", + 1327 : "Cindy Splat", + 1328 : "Fred Flounder", + 1329 : "Shelly Seaweed", + 1330 : "Porter Hole", + 1331 : "Rudy Rudder", + 1332 : "Fisherman Shane", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Betty Freezes", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Clerk Lenny", + 3007 : "Clerk Penny", + 3008 : "Warren Bundles", + # NPCFisherman + 3009 : "Fisherman Frizzy", + # NPCPetClerks + 3010 : "Clerk Skip", + 3011 : "Clerk Dip", + 3012 : "Clerk Kipp", + # NPCPartyPerson + 3013 : "Party Planner Pete", + 3014 : "Party Planner Penny", + + # Walrus Way + 3101 : "Mr. Cow", + 3102 : "Auntie Freeze", + 3103 : "Fred", + 3104 : "Bonnie", + 3105 : "Frosty Freddy", + 3106 : "Gus Gooseburger", + 3107 : "Patty Passport", + 3108 : "Toboggan Ted", + 3109 : "Kate", + 3110 : "Chicken Boy", + 3111 : "Snooty Sinjin", + 3112 : "Lil Oldman", + 3113 : "Hysterical Harry", + 3114 : "Henry the Hazard", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Creepy Carl", + 3120 : "Mike Mittens", + 3121 : "Joe Shockit", + 3122 : "Lucy Luge", + 3123 : "Frank Lloyd Ice", + 3124 : "Lance Iceberg", + 3125 : "Colonel Crunchmouth", + 3126 : "Colestra Awl", + 3127 : "Ifalla Yufalla", + 3128 : "Sticky George", + 3129 : "Baker Bridget", + 3130 : "Sandy", + 3131 : "Lazy Lorenzo", + 3132 : "Ashy", + 3133 : "Dr. Friezeframe", + 3134 : "Lounge Lassard", + 3135 : "Soggy Nell", + 3136 : "Happy Sue", + 3137 : "Mr. Freeze", + 3138 : "Chef Bumblesoup", + 3139 : "Granny Icestockings", + 3140 : "Fisherman Lucille", + + # Sleet Street + 3201 : "Aunt Arctic", + 3202 : "Shakey", + 3203 : "Walt", + 3204 : "Dr. Ivanna Cee", + 3205 : "Bumpy Noggin", + 3206 : "Vidalia VaVoom", + 3207 : "Dr. Mumbleface", + 3208 : "Grumpy Phil", + 3209 : "Giggles McGhee", + 3210 : "Simian Sam", + 3211 : "Fanny Freezes", + 3212 : "Frosty Fred", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Sweaty Pete", + 3218 : "Blue Lou", + 3219 : "Tom Tandemfrost", + 3220 : "Mr. Sneeze", + 3221 : "Nelly Snow", + 3222 : "Mindy Windburn", + 3223 : "Chappy", + 3224 : "Freida Frostbite", + 3225 : "Blake Ice", + 3226 : "Santa Paws", + 3227 : "Solar Ray", + 3228 : "Wynne Chill", + 3229 : "Hernia Belt", + 3230 : "Balding Benjy", + 3231 : "Choppy", + 3232 : "Fisherman Albert", + + # Polar Place + 3301 : "Paisley Patches", + 3302 : "Bjorn Bord", + 3303 : "Dr. Peepers", + 3304 : "Eddie the Yeti", + 3305 : "Mack Ramay", + 3306 : "Paula Behr", + # NPC Fisherman + 3307 : "Fisherman Fredrica", + 3308 : "Donald Frump", + 3309 : "Bootsy", + 3310 : "Professor Flake", + 3311 : "Connie Ferris", + 3312 : "March Harry", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Kissy Krissy", + 3318 : "Johnny Cashmere", + 3319 : "Sam Stetson", + 3320 : "Fizzy Lizzy", + 3321 : "Pickaxe Paul", + 3322 : "Flue Lou", + 3323 : "Dallas Borealis", + 3324 : "Snaggletooth Stu", + 3325 : "Groovy Garland", + 3326 : "Blanche", + 3327 : "Chuck Roast", + 3328 : "Shady Sadie", + 3329 : "Treading Ed", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Molly Molloy", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Clerk Doe", + 4007 : "Clerk Ray", + 4008 : "Tailor Harmony", + # NPCFisherman + 4009 : "Fisherman Fanny", + # NPCPetClerks + 4010 : "Clerk Chris", + 4011 : "Clerk Neil", + 4012 : "Clerk Westin Girl", + # NPCPartyPerson + 4013 : "Party Planner Preston", + 4014 : "Party Planner Penelope", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr. Fret", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Cleff", + 4109 : "Carlos", + 4110 : "Metra Gnome", + 4111 : "Tom Hum", + 4112 : "Fa", + 4113 : "Madam Manners", + 4114 : "Offkey Eric", + 4115 : "Barbara Seville", + 4116 : "Piccolo", + 4117 : "Mandy Lynn", + 4118 : "Attendant Abe", + 4119 : "Moe Zart", + 4120 : "Viola Padding", + 4121 : "Gee Minor", + 4122 : "Minty Bass", + 4123 : "Lightning Ted", + 4124 : "Riff Raff", + 4125 : "Melody Wavers", + 4126 : "Mel Canto", + 4127 : "Happy Feet", + 4128 : "Luciano Scoop", + 4129 : "Tootie Twostep", + 4130 : "Metal Mike", + 4131 : "Abraham Armoire", + 4132 : "Lowdown Sally", + 4133 : "Scott Poplin", + 4134 : "Disco Dave", + 4135 : "Sluggo Songbird", + 4136 : "Patty Pause", + 4137 : "Tony Deff", + 4138 : "Cliff Cleff", + 4139 : "Harmony Swell", + 4140 : "Clumsy Ned", + 4141 : "Fisherman Jed", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Lumber Jack", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Hedy", + 4209 : "Corny Canter", + 4211 : "Carl Concerto", + 4212 : "Detective Dirge", + 4213 : "Fran Foley", + 4214 : "Tina Toehooks", + 4215 : "Tim Tailgater", + 4216 : "Gummy Whistle", + 4217 : "Handsome Anton", + 4218 : "Wilma Wind", + 4219 : "Sid Sonata", + 4220 : "Curtis Finger", + 4221 : "Moe Madrigal", + 4222 : "John Doe", + 4223 : "Penny Prompter", + 4224 : "Jungle Jim", + 4225 : "Holly Hiss", + 4226 : "Thelma Throatreacher", + 4227 : "Quiet Francesca", + 4228 : "August Winds", + 4229 : "June Loon", + 4230 : "Julius Wheezer", + 4231 : "Steffi Squeezebox", + 4232 : "Hedly Hymn", + 4233 : "Charlie Carp", + 4234 : "Leed Guitar", + 4235 : "Fisherman Larry", + + # Tenor Terrace + 4301 : "Yuki", + 4302 : "Anna", + 4303 : "Leo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tabitha", + 4309 : "Marshall", + 4310 : "Martha Mopp", + 4311 : "Sea Shanty", + 4312 : "Moe Saj", + 4313 : "Dumb Dolph", + 4314 : "Dana Dander", + 4315 : "Karen Clockwork", + 4316 : "Tim Tango", + 4317 : "Stubby Toe", + 4318 : "Bob Marlin", + 4319 : "Rinky Dink", + 4320 : "Cammy Coda", + 4321 : "Luke Lute", + 4322 : "Randy Rythm", + 4323 : "Hanna Hogg", + 4324 : "Ellie", + 4325 : "Banker Bran", + 4326 : "Fran Fret", + 4327 : "Flim Flam", + 4328 : "Wagner", + 4329 : "Telly Prompter", + 4330 : "Quentin", + 4331 : "Mellow Costello", + 4332 : "Ziggy", + 4333 : "Harry", + 4334 : "Fast Freddie", + 4335 : "Fisherman Walden", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Clerk Peaches", + 5006 : "Clerk Herb", + 5007 : "Bonnie Blossom", + # NPCFisherman + 5008 : "Fisherman Flora", + # NPCPetClerks + 5009 : "Clerk Bo Tanny", + 5010 : "Clerk Tom A. Dough", + 5011 : "Clerk Doug Wood", + # NPCPartyPerson + 5012 : "Party Planner Pierce", + 5013 : "Party Planner Peggy", + + # Elm Street + 5101 : "Artie", + 5102 : "Susan", + 5103 : "Bud", + 5104 : "Flutterby", + 5105 : "Jack", + 5106 : "Barber Bjorn", + 5107 : "Postman Felipe", + 5108 : "Innkeeper Janet", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr. Spud", + 5114 : "Wilt", + 5115 : "Honey Dew", + 5116 : "Vegetable Vern", + 5117 : "Petal", + 5118 : "Pop Corn", + 5119 : "Barry Medly", + 5120 : "Gopher", + 5121 : "Paula Peapod", + 5122 : "Leif Pyle", + 5123 : "Diane Vine", + 5124 : "Soggy Bottom", + 5125 : "Sanjay Splash", + 5126 : "Madam Mum", + 5127 : "Polly Pollen", + 5128 : "Shoshanna Sap", + 5129 : "Fisherman Sally", + + # Maple Street + 5201 : "Jake", + 5202 : "Cynthia", + 5203 : "Lisa", + 5204 : "Bert", + 5205 : "Dan D. Lion", + 5206 : "Vine Green", + 5207 : "Sofie Squirt", + 5208 : "Samantha Spade", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Big Galoot", + 5214 : "Itchie Bumps", + 5215 : "Tammy Tuber", + 5216 : "Stinky Jim", + 5217 : "Greg Greenethumb", + 5218 : "Rocky Raspberry", + 5219 : "Lars Bicep", + 5220 : "Lacy Underalls", + 5221 : "Pink Flamingo", + 5222 : "Whiny Wilma", + 5223 : "Wet Will", + 5224 : "Uncle Bumpkin", + 5225 : "Pamela Puddle", + 5226 : "Pete Moss", + 5227 : "Begonia Biddlesmore", + 5228 : "Digger Mudhands", + 5229 : "Fisherman Lily", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Crystal", + 5306 : "S. Cargo", + 5307 : "Fun Gus", + 5308 : "Naggy Nell", + 5309 : "Ro Maine", + 5310 : "Timothy", + 5311 : "Judge McIntosh", + 5312 : "Eugene", + 5313 : "Coach Zucchini", + 5314 : "Aunt Hill", + 5315 : "Uncle Mud", + 5316 : "Uncle Spud", + 5317 : "Detective Lima", + 5318 : "Caesar", + 5319 : "Rose", + 5320 : "April", + 5321 : "Professor Ivy", + 5322 : "Fisherman Rose", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "Graham Pree", + 8002 : "Ivona Race", + 8003 : "Anita Winn", + 8004 : "Phil Errup", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Snoozin' Susan", + 9002 : "Sleeping Tom", + 9003 : "Drowsy Dennis", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Clerk Jill", + 9009 : "Clerk Phil", + 9010 : "Worn Out Waylon", + # NPCFisherman + 9011 : "Fisherman Freud", + # NPCPetClerks + 9012 : "Clerk Sarah Snuze", + 9013 : "Clerk Kat Knap", + 9014 : "Clerk R. V. Winkle", + # NPCPartyPerson + 9015 : "Party Planner Pebbles", + 9016 : "Party Planner Pearl", + + # Lullaby Lane + 9101 : "Ed", + 9102 : "Big Mama", + 9103 : "P.J.", + 9104 : "Sweet Slumber", + 9105 : "Professor Yawn", + 9106 : "Max", + 9107 : "Snuggles", + 9108 : "Winky Wilbur", + 9109 : "Dreamy Daphne", + 9110 : "Kathy Nip", + 9111 : "Powers Erge", + 9112 : "Lullaby Lou", + 9113 : "Jacques Clock", + 9114 : "Smudgy Mascara", + 9115 : "Babyface MacDougal", + 9116 : "Dances with Sheep", + 9117 : "Afta Hours", + 9118 : "Starry Knight", + 9119 : "Rocco", + 9120 : "Sarah Slumber", + 9121 : "Serena Shortsheeter", + 9122 : "Puffy Ayes", + 9123 : "Teddy Blair", + 9124 : "Nina Nitelight", + 9125 : "Dr. Bleary", + 9126 : "Wyda Wake", + 9127 : "Tabby Tucker", + 9128 : "Hardy O'Toole", + 9129 : "Bertha Bedhog", + 9130 : "Charlie Chamberpot", + 9131 : "Susan Siesta", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Fisherman Taylor", + + # Pajama Place + 9201 : "Bernie", + 9202 : "Orville", + 9203 : "Nat", + 9204 : "Claire de Loon", + 9205 : "Zen Glen", + 9206 : "Skinny Ginny", + 9207 : "Jane Drain", + 9208 : "Drowsy Dave", + 9209 : "Dr. Floss", + 9210 : "Master Mike", + 9211 : "Dawn", + 9212 : "Moonbeam", + 9213 : "Rooster Rick", + 9214 : "Dr. Blinky", + 9215 : "Rip", + 9216 : "Cat", + 9217 : "Lawful Linda", + 9218 : "Waltzing Matilda", + 9219 : "The Countess", + 9220 : "Grumpy Gordon", + 9221 : "Zari", + 9222 : "Cowboy George", + 9223 : "Mark the Lark", + 9224 : "Sandy Sandman", + 9225 : "Fidgety Bridget", + 9226 : "William Teller", + 9227 : "Bed Head Ted", + 9228 : "Whispering Willow", + 9229 : "Rose Petals", + 9230 : "Tex", + 9231 : "Harry Hammock", + 9232 : "Honey Moon", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "Fisherman Jung", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Toon Hall", ""), + 2514 : ("Toontown Bank", ""), + 2516 : ("Toontown School House", ""), + 2518 : ("Toontown Library", ""), + 2519 : ("Gag Shop", ""), + 2520 : (lToonHQ, ""), + 2521 : ("Clothing Shop", ""), + 2522 : ("Pet Shop", ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("All Smiles Tooth Repair", ""), + 2602 : ("", ""), + 2603 : ("One-Liner Miners", ""), + 2604 : ("Hogwash & Dry", ""), + 2605 : ("Toontown Sign Factory", ""), + 2606 : ("", ""), + 2607 : ("Jumping Beans", ""), + 2610 : ("Dr. Tom Foolery", ""), + 2611 : ("", ""), + 2616 : ("Weird Beard's Disguise Shop", ""), + 2617 : ("Silly Stunts", ""), + 2618 : ("All That Razz", ""), + 2621 : ("Paper Airplanes", ""), + 2624 : ("Happy Hooligans", ""), + 2625 : ("House of Bad Pies", ""), + 2626 : ("Jesse's Joke Repair", ""), + 2629 : ("The Laughin' Place", ""), + 2632 : ("Clown Class", ""), + 2633 : ("Tee-Hee Tea Shop", ""), + 2638 : ("Toontown Playhouse", ""), + 2639 : ("Monkey Tricks", ""), + 2643 : ("Canned Bottles", ""), + 2644 : ("Impractical Jokes", ""), + 2649 : ("All Fun and Games Shop", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Laughing Lessons", ""), + 2655 : ("Funny Money Savings & Loan", ""), + 2656 : ("Used Clown Cars", ""), + 2657 : ("Frank's Pranks", ""), + 2659 : ("Joy Buzzers to the World", ""), + 2660 : ("Tickle Machines", ""), + 2661 : ("Daffy Taffy", ""), + 2662 : ("Dr. I.M. Euphoric", ""), + 2663 : ("Toontown Cinerama", ""), + 2664 : ("The Merry Mimes", ""), + 2665 : ("Mary's Go Around Travel Company", ""), + 2666 : ("Laughing Gas Station", ""), + 2667 : ("Happy Times", ""), + 2669 : ("Muldoon's Maroon Balloons", ""), + 2670 : ("Soup Forks", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Movie Multiplex", ""), + 2705 : ("Wiseacre's Noisemakers", ""), + 2708 : ("Blue Glue", ""), + 2711 : ("Toontown Post Office", ""), + 2712 : ("Chortle Cafe", ""), + 2713 : ("Laughter Hours Cafe", ""), + 2714 : ("Kooky CinePlex", ""), + 2716 : ("Soup and Crack Ups", ""), + 2717 : ("Bottled Cans", ""), + 2720 : ("Crack Up Auto Repair", ""), + 2725 : ("", ""), + 2727 : ("Seltzer Bottles and Cans", ""), + 2728 : ("Vanishing Cream", ""), + 2729 : ("14 Karat Goldfish", ""), + 2730 : ("News for the Amused", ""), + 2731 : ("", ""), + 2732 : ("Spaghetti and Goofballs", ""), + 2733 : ("Cast Iron Kites", ""), + 2734 : ("Suction Cups and Saucers", ""), + 2735 : ("The Kaboomery", ""), + 2739 : ("Sidesplitter's Mending", ""), + 2740 : ("Used Firecrackers", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("Ragtime Dry Cleaners", ""), + 2744 : ("", ""), + 2747 : ("Visible Ink", ""), + 2748 : ("Jest for Laughs", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Sofa Whoopee Cushions", ""), + 2802 : ("Inflatable Wrecking Balls", ""), + 2803 : ("The Karnival Kid", ""), + 2804 : ("Dr. Pulyurleg, Chiropractor", ""), + 2805 : ("", ""), + 2809 : ("The Punch Line Gym", ""), + 2814 : ("Toontown Theatre", ""), + 2818 : ("The Flying Pie", ""), + 2821 : ("", ""), + 2822 : ("Rubber Chicken Sandwiches", ""), + 2823 : ("Sundae Funnies Ice Cream", ""), + 2824 : ("Punchline Movie Palace", ""), + 2829 : ("Phony Baloney", ""), + 2830 : ("Zippy's Zingers", ""), + 2831 : ("Professor Wiggle's House of Giggles", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("Funny Bone Emergency Room", ""), + 2836 : ("", ""), + 2837 : ("Hardy Harr Seminars", ""), + 2839 : ("Barely Palatable Pasta", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Gag Shop", ""), + 1507 : ("Toon Headquarters", ""), + 1508 : ("Clothing Shop", ""), + 1510 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Used Life Preservers", ""), + 1604 : ("Wet Suit Dry Cleaners", ""), + 1606 : ("Hook's Clock Repair", ""), + 1608 : ("Luff 'N Stuff", ""), + 1609 : ("Every Little Bait", ""), + 1612 : ("Dime & Quarterdeck Bank", ""), + 1613 : ("Squid Pro Quo, Attorneys at Law", ""), + 1614 : ("Trim the Nail Boutique", ""), + 1615 : ("Yacht's All, Folks!", ""), + 1616 : ("Blackbeard's Beauty Parlor", ""), + 1617 : ("Out to See Optics", ""), + 1619 : ("Disembark! Tree Surgeons", ""), + 1620 : ("From Fore to Aft", ""), + 1621 : ("Poop Deck Gym", ""), + 1622 : ("Bait and Switches Electrical Shop", ""), + 1624 : ("Soles Repaired While U Wait", ""), + 1626 : ("Salmon Chanted Evening Formal Wear", ""), + 1627 : ("Billy Budd's Big Bargain Binnacle Barn", ""), + 1628 : ("Piano Tuna", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Buoys and Gulls Nursery School", ""), + 1703 : ("Wok the Plank Chinese Food", ""), + 1705 : ("Sails for Sale", ""), + 1706 : ("Peanut Butter and Jellyfish", ""), + 1707 : ("Gifts With a Porpoise", ""), + 1709 : ("Windjammers and Jellies", ""), + 1710 : ("Barnacle Bargains", ""), + 1711 : ("Deep Sea Diner", ""), + 1712 : ("Able-Bodied Gym", ""), + 1713 : ("Art's Smart Chart Mart", ""), + 1714 : ("Reel 'Em Inn", ""), + 1716 : ("Mermaid Swimwear", ""), + 1717 : ("Be More Pacific Ocean Notions", ""), + 1718 : ("Run Aground Taxi Service", ""), + 1719 : ("Duck's Back Water Company", ""), + 1720 : ("The Reel Deal", ""), + 1721 : ("All For Nautical", ""), + 1723 : ("Squid's Seaweed", ""), + 1724 : ("That's a Moray!", ""), + 1725 : ("Ahab's Prefab Sea Crab Center", ""), + 1726 : ("Root Beer Afloats", ""), + 1727 : ("This Oar That", ""), + 1728 : ("Good Luck Horseshoe Crabs", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Nautical But Nice", ""), + 1804 : ("Mussel Beach Gymnasium", ""), + 1805 : ("Tackle Box Lunches", ""), + 1806 : ("Cap Size Hat Store", ""), + 1807 : ("Keel Deals", ""), + 1808 : ("Knots So Fast", ""), + 1809 : ("Rusty Buckets", ""), + 1810 : ("Anchor Management", ""), + 1811 : ("What's Canoe With You?", ""), + 1813 : ("Pier Pressure Plumbing", ""), + 1814 : ("The Yo Ho Stop and Go", ""), + 1815 : ("What's Up, Dock?", ""), + 1818 : ("Seven Seas Cafe", ""), + 1819 : ("Docker's Diner", ""), + 1820 : ("Hook, Line, and Sinker Prank Shop", ""), + 1821 : ("King Neptoon's Cannery", ""), + 1823 : ("The Clam Bake Diner", ""), + 1824 : ("Dog Paddles", ""), + 1825 : ("Wholly Mackerel! Fish Market", ""), + 1826 : ("Claggart's Clever Clovis Closet", ""), + 1828 : ("Alice's Ballast Palace", ""), + 1829 : ("Seagull Statue Store", ""), + 1830 : ("Lost and Flounder", ""), + 1831 : ("Kelp Around the House", ""), + 1832 : ("Melville's Massive Mizzenmast Mart", ""), + 1833 : ("This Transom Man Custom Tailored Suits", ""), + 1834 : ("Rudderly Ridiculous!", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Gag Shop", ""), + 4504 : ("Toon Headquarters", ""), + 4506 : ("Clothing Shop", ""), + 4508 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tom-Tom's Drums", ""), + 4604 : ("In Four-Four Time", ""), + 4605 : ("Fifi's Fiddles", ""), + 4606 : ("Casa De Castanets", ""), + 4607 : ("Catchy Toon Apparel", ""), + 4609 : ("Do, Rae, Me Piano Keys", ""), + 4610 : ("Please Refrain", ""), + 4611 : ("Tuning Forks and Spoons", ""), + 4612 : ("Dr. Fret's Dentistry", ""), + 4614 : ("Shave and a Haircut for a Song", ""), + 4615 : ("Piccolo's Pizza", ""), + 4617 : ("Happy Mandolins", ""), + 4618 : ("Rests Rooms", ""), + 4619 : ("More Scores", ""), + 4622 : ("Chin Rest Pillows", ""), + 4623 : ("Flats Sharpened", ""), + 4625 : ("Tuba Toothpaste", ""), + 4626 : ("Notations", ""), + 4628 : ("Accidental Insurance", ""), + 4629 : ("Riff's Paper Plates", ""), + 4630 : ("Music Is Our Forte", ""), + 4631 : ("Canto Help You", ""), + 4632 : ("Dance Around the Clock Shop", ""), + 4635 : ("Tenor Times", ""), + 4637 : ("For Good Measure", ""), + 4638 : ("Hard Rock Shop", ""), + 4639 : ("Four Score Antiques", ""), + 4641 : ("Blues News", ""), + 4642 : ("Ragtime Dry Cleaners", ""), + 4645 : ("Club 88", ""), + 4646 : ("", ""), + 4648 : ("Carry a Toon Movers", ""), + 4649 : ("", ""), + 4652 : ("Full Stop Shop", ""), + 4653 : ("", ""), + 4654 : ("Pitch Perfect Roofing", ""), + 4655 : ("The Treble Chef's Cooking School", ""), + 4656 : ("", ""), + 4657 : ("Barbershop Quartet", ""), + 4658 : ("Plummeting Pianos", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("The Schmaltzy Waltz School of Dance", ""), + 4702 : ("Timbre! Equipment for the Singing Lumberjack", ""), + 4703 : ("I Can Handel It!", ""), + 4704 : ("Tina's Concertina Concerts", ""), + 4705 : ("Zither Here Nor There", ""), + 4707 : ("Doppler's Sound Effects Studio", ""), + 4709 : ("On Ballet! Climbing Supplies", ""), + 4710 : ("Hurry Up, Slow Polka! School of Driving", ""), + 4712 : ("C-Flat Tire Repair", ""), + 4713 : ("B-Sharp Fine Menswear", ""), + 4716 : ("Four-Part Harmonicas", ""), + 4717 : ("Sonata Your Fault! Discount Auto Insurance", ""), + 4718 : ("Chopin Blocks and Other Kitchen Supplies", ""), + 4719 : ("Madrigal Motor Homes", ""), + 4720 : ("Name That Toon", ""), + 4722 : ("Overture Understudies", ""), + 4723 : ("Haydn Go Seek Playground Supplies", ""), + 4724 : ("White Noise for Girls and Boys", ""), + 4725 : ("The Baritone Barber", ""), + 4727 : ("Vocal Chords Braided", ""), + 4728 : ("Sing Solo We Can't Hear You", ""), + 4729 : ("Double Reed Bookstore", ""), + 4730 : ("Lousy Lyrics", ""), + 4731 : ("Toon Tunes", ""), + 4732 : ("Etude Brute? Theatre Company", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Accordions, If You Want In, Just Bellow!", ""), + 4736 : ("Her and Hymn Wedding Planners", ""), + 4737 : ("Harp Tarps", ""), + 4738 : ("Canticle Your Fancy Gift Shop", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Marshall's Stacks", ""), + 4803 : ("What a Mezzo! Maid Service", ""), + 4804 : ("Mixolydian Scales", ""), + 4807 : ("Relax the Bach", ""), + 4809 : ("I Can't Understanza!", ""), + 4812 : ("", ""), + 4817 : ("The Ternary Pet Shop", ""), + 4819 : ("Yuki's Ukeleles", ""), + 4820 : ("", ""), + 4821 : ("Anna's Cruises", ""), + 4827 : ("Common Time Watches", ""), + 4828 : ("Schumann's Shoes for Men", ""), + 4829 : ("Pachelbel's Canonballs", ""), + 4835 : ("Ursatz for Kool Katz", ""), + 4836 : ("Reggae Regalia", ""), + 4838 : ("Kazoology School of Music", ""), + 4840 : ("Coda Pop Musical Beverages", ""), + 4841 : ("Lyre, Lyre, Pants on Fire!", ""), + 4842 : ("The Syncopation Corporation", ""), + 4843 : ("", ""), + 4844 : ("Con Moto Cycles", ""), + 4845 : ("Ellie's Elegant Elegies", ""), + 4848 : ("Lotsa Lute Savings & Loan", ""), + 4849 : ("", ""), + 4850 : ("The Borrowed Chord Pawn Shop", ""), + 4852 : ("Flowery Flute Fleeces", ""), + 4853 : ("Leo's Fenders", ""), + 4854 : ("Wagner's Vocational Violin Videos", ""), + 4855 : ("The Teli-Caster Network", ""), + 4856 : ("", ""), + 4862 : ("Quentin's Quintessen\3tial Quadrilles", ""), + 4867 : ("Mr. Costello's Yellow Cellos", ""), + 4868 : ("", ""), + 4870 : ("Ziggy's Zoo of Zigeuner\3musik", ""), + 4871 : ("Harry's House of Harmonious Humbuckers", ""), + 4872 : ("Fast Freddie's Fretless Fingerboards", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Gag Shop", ""), + 5502 : (lToonHQ, ""), + 5503 : ("Clothing Shop", ""), + 5505 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Eye of the Potato Optometry", ""), + 5602 : ("Artie Choke's Neckties", ""), + 5603 : ("Lettuce Alone", ""), + 5604 : ("Cantaloupe Bridal Shop", ""), + 5605 : ("Vege-tables and Chairs", ""), + 5606 : ("Petals", ""), + 5607 : ("Compost Office", ""), + 5608 : ("Mom and Pop Corn", ""), + 5609 : ("Berried Treasure", ""), + 5610 : ("Black-eyed Susan's Boxing Lessons", ""), + 5611 : ("Gopher's Gags", ""), + 5613 : ("Crop Top Barbers", ""), + 5615 : ("Bud's Bird Seed", ""), + 5616 : ("Dew Drop Inn", ""), + 5617 : ("Flutterby's Butterflies", ""), + 5618 : ("Peas and Q's", ""), + 5619 : ("Jack's Beanstalks", ""), + 5620 : ("Rake It Inn", ""), + 5621 : ("Grape Expectations", ""), + 5622 : ("Petal Pusher Bicycles", ""), + 5623 : ("Bubble Bird Baths", ""), + 5624 : ("Mum's the Word", ""), + 5625 : ("Leaf It Bees", ""), + 5626 : ("Pine Needle Crafts", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("From Start to Spinach", ""), + 5702 : ("Jake's Rakes", ""), + 5703 : ("Photo Cynthia's Camera Shop", ""), + 5704 : ("Lisa Lemon Used Cars", ""), + 5705 : ("Poison Oak Furniture", ""), + 5706 : ("14 Carrot Jewelers", ""), + 5707 : ("Musical Fruit", ""), + 5708 : ("We'd Be Gone Travel Agency", ""), + 5709 : ("Astroturf Mowers", ""), + 5710 : ("Tuft Guy Gym", ""), + 5711 : ("Garden Hosiery", ""), + 5712 : ("Silly Statues", ""), + 5713 : ("Trowels and Tribulations", ""), + 5714 : ("Spring Rain Seltzer Bottles", ""), + 5715 : ("Hayseed News", ""), + 5716 : ("Take It or Leaf It Pawn Shop", ""), + 5717 : ("The Squirting Flower", ""), + 5718 : ("The Dandy Lion Exotic Pets", ""), + 5719 : ("Trellis the Truth! Private Investigators", ""), + 5720 : ("Vine and Dandy Menswear", ""), + 5721 : ("Root 66 Diner", ""), + 5725 : ("Barley, Hops, and Malt Shop", ""), + 5726 : ("Bert's Dirt", ""), + 5727 : ("Gopher Broke Savings & Loan", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("Just Vase It", ""), + 5805 : ("Snail Mail", ""), + 5809 : ("Fungi Clown School", ""), + 5810 : ("Honeydew This", ""), + 5811 : ("Lettuce Inn", ""), + 5815 : ("Grass Roots", ""), + 5817 : ("Apples and Oranges", ""), + 5819 : ("Green Bean Jeans", ""), + 5821 : ("Squash and Stretch Gym", ""), + 5826 : ("Ant Farming Supplies", ""), + 5827 : ("Dirt. Cheap.", ""), + 5828 : ("Couch Potato Furniture", ""), + 5830 : ("Spill the Beans", ""), + 5833 : ("The Salad Bar", ""), + 5835 : ("Flower Bed and Breakfast", ""), + 5836 : ("April's Showers and Tubs", ""), + 5837 : ("School of Vine Arts", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Lullaby Library", ""), + 9503 : ("The Snooze Bar", ""), + 9504 : ("Gag Shop", ""), + 9505 : (lToonHQ, ""), + 9506 : ("Clothing Shop", ""), + 9508 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Snuggle Inn", ""), + 9602 : ("Forty Winks for the Price of Twenty", ""), + 9604 : ("Ed's Red Bed Spreads", ""), + 9605 : ("Cloud Nine Design", ""), + 9607 : ("Big Mama's Bahama Pajamas", ""), + 9608 : ("Cat Nip for Cat Naps", ""), + 9609 : ("Deep Sleep for Cheap", ""), + 9613 : ("Clock Cleaners", ""), + 9616 : ("Lights Out Electric Co.", ""), + 9617 : ("Crib Notes - Music to Sleep By", ""), + 9619 : ("Relax to the Max", ""), + 9620 : ("PJ's Taxi Service", ""), + 9622 : ("Sleepy Time Pieces", ""), + 9625 : ("Curl Up Beauty Parlor", ""), + 9626 : ("Bed Time Stories", ""), + 9627 : ("The Sleepy Teepee", ""), + 9628 : ("Call It a Day Calendars", ""), + 9629 : ("Silver Lining Jewelers", ""), + 9630 : ("Rock to Sleep Quarry", ""), + 9631 : ("Down Time Watch Repair", ""), + 9633 : ("The Dreamland Screening Room", ""), + 9634 : ("Mind Over Mattress", ""), + 9636 : ("Insomniac Insurance", ""), + 9639 : ("House of Hibernation", ""), + 9640 : ("Nightstand Furniture Company", ""), + 9642 : ("Sawing Wood Slumber Lumber", ""), + 9643 : ("Shut-Eye Optometry", ""), + 9644 : ("Pillow Fights Nightly", ""), + 9645 : ("The All Tucked Inn", ""), + 9647 : ("Make Your Bed! Hardware Store", ""), + 9649 : ("Snore or Less", ""), + 9650 : ("Crack of Dawn Repairs", ""), + 9651 : ("For Richer or Snorer", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Fly By Night Travel Agency", ""), + 9704 : ("Night Owl Pet Shop", ""), + 9705 : ("Asleep At The Wheel Car Repair", ""), + 9706 : ("Tooth Fairy Dentistry", ""), + 9707 : ("Dawn's Yawn & Garden Center", ""), + 9708 : ("Bed Of Roses Florist", ""), + 9709 : ("Pipe Dream Plumbers", ""), + 9710 : ("REM Optometry", ""), + 9711 : ("Wake-Up Call Phone Company", ""), + 9712 : ("Counting Sheep - So You Don't Have To!", ""), + 9713 : ("Wynken, Blynken & Nod, Attorneys at Law", ""), + 9714 : ("Dreamboat Marine Supply", ""), + 9715 : ("First Security Blanket Bank", ""), + 9716 : ("Wet Blanket Party Planners", ""), + 9717 : ("Baker's Dozin' Doughnuts", ""), + 9718 : ("Sandman's Sandwiches", ""), + 9719 : ("Armadillo Pillow Company", ""), + 9720 : ("Talking In Your Sleep Voice Training", ""), + 9721 : ("Snug As A Bug Rug Dealer", ""), + 9722 : ("Dream On Talent Agency", ""), + 9725 : ("Cat's Pajamas", ""), + 9727 : ("You Snooze, You Lose", ""), + 9736 : ("Dream Jobs Employment Agency", ""), + 9737 : ("Waltzing Matilda's Dance School", ""), + 9738 : ("House of Zzzzzs", ""), + 9740 : ("Hit The Sack Fencing School", ""), + 9741 : ("Don't Let The Bed Bugs Bite Exterminators", ""), + 9744 : ("Rip Van Winkle's Wrinkle Cream", ""), + 9752 : ("Midnight Oil & Gas Company", ""), + 9753 : ("Moonbeam's Ice Creams", ""), + 9754 : ("Sleepless in the Saddle All Night Pony Rides", ""), + 9755 : ("Bedknobs & Broomsticks Movie House", ""), + 9756 : ("", ""), + 9759 : ("Sleeping Beauty Parlor", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Gag Shop", ""), + 3508 : (lToonHQ, ""), + 3509 : ("Clothing Shop", ""), + 3511 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Northern Lights Electric Company", ""), + 3602 : ("Nor'easter Bonnets", ""), + 3605 : ("", ""), + 3607 : ("The Blizzard Wizard", ""), + 3608 : ("Nothing to Luge", ""), + 3610 : ("Mike's Massive Mukluk Mart", ""), + 3611 : ("Mr. Cow's Snow Plows", ""), + 3612 : ("Igloo Design", ""), + 3613 : ("Ice Cycle Bikes", ""), + 3614 : ("Snowflakes Cereal Company", ""), + 3615 : ("Fried Baked Alaskas", ""), + 3617 : ("Cold Air Balloon Rides", ""), + 3618 : ("Snow Big Deal! Crisis Management", ""), + 3620 : ("Skiing Clinic", ""), + 3621 : ("The Melting Ice Cream Bar", ""), + 3622 : ("", ""), + 3623 : ("The Mostly Toasty Bread Company", ""), + 3624 : ("Subzero Sandwich Shop", ""), + 3625 : ("Auntie Freeze's Radiator Supply", ""), + 3627 : ("St. Bernard Kennel Club", ""), + 3629 : ("Pea Soup Cafe", ""), + 3630 : ("Icy London, Icy France Travel Agency", ""), + 3634 : ("Easy Chair Lifts", ""), + 3635 : ("Used Firewood", ""), + 3636 : ("Affordable Goosebumps", ""), + 3637 : ("Kate's Skates", ""), + 3638 : ("Toboggan or Not Toboggan", ""), + 3641 : ("Fred's Red Sled Beds", ""), + 3642 : ("Eye of the Storm Optics", ""), + 3643 : ("Snowball Hall", ""), + 3644 : ("Melted Ice Cubes", ""), + 3647 : ("The Sanguine Penguin Tuxedo Shop", ""), + 3648 : ("Instant Ice", ""), + 3649 : ("Hambrrrgers", ""), + 3650 : ("Antarctic Antiques", ""), + 3651 : ("Frosty Freddy's Frozen Frankfurters", ""), + 3653 : ("Ice House Jewelry", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Winter Storage", ""), + 3703 : ("", ""), + 3705 : ("Icicles Built for Two", ""), + 3706 : ("Shiverin' Shakes Malt Shop", ""), + 3707 : ("Snowplace Like Home", ""), + 3708 : ("Pluto's Place", ""), + 3710 : ("Dropping Degrees Diner", ""), + 3711 : ("", ""), + 3712 : ("Go With the Floe", ""), + 3713 : ("Chattering Teeth, Subzero Dentist", ""), + 3715 : ("Aunt Arctic's Soup Shop", ""), + 3716 : ("Road Salt and Pepper", ""), + 3717 : ("Juneau What I Mean?", ""), + 3718 : ("Designer Inner Tubes", ""), + 3719 : ("Ice Cube on a Stick", ""), + 3721 : ("Noggin's Toboggan Bargains", ""), + 3722 : ("Snow Bunny Ski Shop", ""), + 3723 : ("Shakey's Snow Globes", ""), + 3724 : ("The Chattering Chronicle", ""), + 3725 : ("You Sleigh Me", ""), + 3726 : ("Solar Powered Blankets", ""), + 3728 : ("Lowbrow Snowplows", ""), + 3729 : ("", ""), + 3730 : ("Snowmen Bought & Sold", ""), + 3731 : ("Portable Fireplaces", ""), + 3732 : ("The Frozen Nose", ""), + 3734 : ("Icy Fine, Do You? Optometry", ""), + 3735 : ("Polar Ice Caps", ""), + 3736 : ("Diced Ice at a Nice Price", ""), + 3737 : ("Downhill Diner", ""), + 3738 : ("Heat-Get It While It's Hot", ""), + 3739 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : ("Toon HQ", ""), + 3806 : ("Alpine Chow Line", ""), + 3807 : ("Used Groundhog Shadows", ""), + 3808 : ("The Sweater Lodge", ""), + 3809 : ("Ice Saw It Too", ""), + 3810 : ("A Better Built Quilt", ""), + 3811 : ("Your Snow Angel", ""), + 3812 : ("Mittens for Kittens", ""), + 3813 : ("Snowshoes You Can't Refuse", ""), + 3814 : ("Malt in Your Mouth Soda Fountain", ""), + 3815 : ("The Toupee Chalet", ""), + 3816 : ("Just So Mistletoe", ""), + 3817 : ("Winter Wonderland Walking Club", ""), + 3818 : ("The Shovel Hovel", ""), + 3819 : ("Clean Sweep Chimney Service", ""), + 3820 : ("Snow Whitening", ""), + 3821 : ("Hibernation Vacations", ""), + 3823 : ("Precipitation Foundation", ""), + 3824 : ("Open Fire Chestnut Roasting", ""), + 3825 : ("Cool Cat Hats", ""), + 3826 : ("Oh My Galoshes!", ""), + 3827 : ("Choral Wreaths", ""), + 3828 : ("Snowman's Land", ""), + 3829 : ("Pinecone Zone", ""), + 3830 : ("Wait and See Goggle Defogging", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Sorry, you ran out\n of time." +ClosetNotOwnerMessage = "This isn't your closet, but you may try on the clothes." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Remove" +ClosetAreYouSureMessage = "You have deleted some clothes. Do you really want to delete them?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Really delete %s?" +ClosetShirt = "this shirt" +ClosetShorts = "these shorts" +ClosetSkirt = "this skirt" +ClosetDeleteShirt = "Delete\nshirt" +ClosetDeleteShorts = "Delete\nshorts" +ClosetDeleteSkirt = "Delete\nskirt" + +# EstateLoader.py +EstateOwnerLeftMessage = "Sorry, the owner of this estate left. You'll be sent to the playground in %s seconds" +EstatePopupOK = lOK +EstateTeleportFailed = "Couldn't go home. Try again!" +EstateTeleportFailedNotFriends = "Sorry, %s is in a toon's estate that you are not friends with." + +# DistributedTarget.py +EstateTargetGameStart = "The Toon-up Target game has started!" +EstateTargetGameInst = "The more you hit the red target, the more you'll get Tooned up." +EstateTargetGameEnd = "The Toon-up Target game is now over..." + +# DistributedHouse.py +AvatarsHouse = "%s\n House" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Sorry, this is not your bank." +DistributedBankNotOwner = "Sorry, this is not your bank." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Sell All" +FishTankValue = "Hi, %(name)s! You have %(num)s fish in your bucket worth a total of %(value)s jellybeans. Do you want to sell them all?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Sell All" +FlowerBasketValue = "%(name)s, you have %(num)s flowers in your basket worth a total of %(value)s jellybeans. Do you want to sell them all?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name + "'" + else: + possesive = name + "'s" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('Always Hungry', 'Often Hungry', + 'Sometimes Hungry', 'Rarely Hungry',), + 'boredomThreshold': ('Always Bored', 'Often Bored', + 'Sometimes Bored', 'Rarely Bored',), + 'angerThreshold': ('Always Grumpy', 'Often Grumpy', + 'Sometimes Grumpy', 'Rarely Grumpy'), + 'forgetfulness': ('Always Forgets', 'Often Forgets', + 'Sometimes Forgets', 'Rarely Forgets',), + 'excitementThreshold': ('Very Calm', 'Pretty Calm', + 'Pretty Excitable', 'Very Excitable',), + 'sadnessThreshold': ('Always Sad', 'Often Sad', + 'Sometimes Sad', 'Rarely Sad',), + 'restlessnessThreshold': ('Always Restless', 'Often Restless', + 'Sometimes Restless', 'Rarely Restless',), + 'playfulnessThreshold': ('Rarely Playful', 'Sometimes Playful', + 'Often Playful', 'Always Playful',), + 'lonelinessThreshold': ('Always Lonely', 'Often Lonely', + 'Sometimes Lonely', 'Rarely Lonely',), + 'fatigueThreshold': ('Always Tired', 'Often Tired', + 'Sometimes Tired', 'Rarely Tired',), + 'confusionThreshold': ('Always Confused', 'Often Confused', + 'Sometimes Confused', 'Rarely Confused',), + 'surpriseThreshold': ('Always Surprised', 'Often Surprised', + 'Sometimes Surprised', 'Rarely Surprised',), + 'affectionThreshold': ('Rarely Affectionate', 'Sometimes Affectionate', + 'Often Affectionate', 'Always Affectionate',), + } + + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Hit the \"Page Up\" key to see better." +startFireworksResponse = "Usage: startFireworksShow [\'num\']\n \ + \'num\' = %s - New Years\n \ + %s - Party Summer \n \ + %s - 4th of July" + +FireworksJuly4Beginning = lToonHQ+": Welcome to summer fireworks! Enjoy the show!" +FireworksJuly4Ending = lToonHQ+": Hope you enjoyed the show! Have a great summer!" +FireworksNewYearsEveBeginning = lToonHQ+": Happy New Year! Enjoy the fireworks show!" +FireworksNewYearsEveEnding = lToonHQ+": Hope you enjoyed the show! Happy New Year!" + +# ToontownLoadingBlocker.py +BlockerTitle = "LOADING TOONTOWN..." +BlockerLoadingTexts = [ + "Scrubbing pie tins", + "Baking pie crusts", + "Heating pie filling", + "Loading Doodle chow", + "Stringing Jungle Vines", + "Uncaging those spiders who crawl down jungle vines", + "Planting squirting flower seeds", + "Stretching trampolines", + "Herding pigs", + "Tweaking 'SPLAT' sounds", + "Cleaning Hypno-glasses", + "Unbottling ink for Toon News", + "Clipping TNT fuses", + "Setting up 'Under Construction' sign in Acorn Acres", + "Waking Donald Duck", + "Teaching new moves to dancing fire hydrants", + "Binding Shticker Books", + "Analyzing quacks", + "Harvesting jellybean pods", + "Emptying fish buckets", + "Corralling trashcan trash", + "Spreading Cog grease", + "Polishing kart trophies", + "Balancing scale for weighing 1 Ton weights", + "Practicing Victory Dances", + "Preparing wackiness", + "Giving Mickey Mouse the 'five minutes' sign", + "Testing white gloves", + "Bending underwater rings", + "Spooling red tape", + "Freezing Brrrgh ice", + "Tuning falling pianos", + ] + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "TOON TIP:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Quickly check your ToonTask progress by holding down the \"End\" key.", + "Quickly check your Gag page by holding down the \"Home\" key.", + "Open your Friends List by pressing the \"F7\" key.", + "Open or close your Shticker Book by pressing the \"F8\" key.", + "You can look up by pressing the \"Page Up\" key and look down by pressing the \"Page Down\" key.", + "Press the \"Control\" key to jump.", + "Press the \"F9\" key to take a screenshot, which will be saved in your Toontown folder on your computer.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "You can exchange Secret Friend Codes with somebody you know outside Toontown to enable open chat with them in Toontown.", + "You can change your screen resolution, adjust audio, and control other options on the Options Page in the Shticker Book.", + "Try on your friend's clothing at the closet in their house.", + "You can go to your house using the \"Go Home\" button on your map.", + "Every time you turn in a completed ToonTask your Laff points are automatically refilled.", + "You can browse the selection at Clothing Stores even without a clothing ticket.", + "Rewards for some ToonTasks allow you to carry more gags and jellybeans.", + "You can have up to 50 friends on your Friends List.", + "Some ToonTask rewards let you teleport to playgrounds in Toontown by using the Map Page in the Shticker Book.", + "Increase your Laff points in the Playgrounds by collecting treasures like stars and ice cream cones.", + "To heal quickly after a battle, go to your estate and play with your Doodle.", + "Change to different views of your Toon by pressing the Tab Key.", + "Sometimes you can find several different ToonTasks offered for the same reward. Shop around!", + "Finding friends with similar ToonTasks is a fun way to progress through the game.", + "You never need to save your Toontown progress. The Toontown servers continually save all the necessary information.", + "You can whisper to other Toons either by clicking on them or by selecting them from your Friends List.", + "Some SpeedChat phrases play emotion animations on your Toon.", + "If the area you are in is crowded, try changing Districts. Go to the District Page in the Shticker Book and select a different one.", + "If you actively rescue buildings you will get a bronze, silver, or gold star above your Toon.", + "If you rescue enough buildings to get a star above your head you may find your name on the blackboard in a Toon HQ.", + "Rescued buildings are sometimes recaptured by the Cogs. The only way to keep your star is to go out and rescue more buildings!", + "The names of your True Friends will appear in Blue.", + # Fishing + "See if you can collect all the fish in Toontown!", + "Different ponds hold different fish. Try them all!", + "When your fishing bucket is full sell your fish to the Fishermen in the Playgrounds.", + "You can sell your fish to the Fishermen or inside Pet Shops.", + "Stronger fishing rods catch heavier fish but cost more jellybeans to use.", + "You can purchase stronger fishing rods in the Cattlelog.", + "Heavier fish are worth more jellybeans to the Pet Shop.", + "Rare fish are worth more jellybeans to the Pet Shop.", + "You can sometimes find bags of jellybeans while fishing.", + "Some ToonTasks require fishing items out of the ponds.", + "Fishing ponds in the Playgrounds have different fish than ponds on the streets.", + "Some fish are really rare. Keep fishing until you collect them all!", + "The pond at your estate has fish that can only be found there.", + "For every 10 species you catch, you will get a fishing trophy!", + "You can see what fish you have collected in your Shticker Book.", + "Some fishing trophies reward you with a Laff boost.", + "Fishing is a good way to earn more jellybeans.", + # Doodles + "Adopt a Doodle at the Pet Shop!", + "Pet Shops get new Doodles to sell every day.", + "Visit the Pet Shops every day to see what new Doodles they have.", + "Different neighborhoods have different Doodles offered for adoption.", + # Karting + "Show off your stylin' ride and turbo-boost your Laff limit at Goofy Speedway.", + "Enter Goofy Speedway through the tire-shaped tunnel in Toontown Central Playground.", + "Earn Laff points at Goofy Speedway.", + "Goofy Speedway has six different race tracks. " + ), + + TIP_STREET : ( + "There are four types of Cogs: Lawbots, Cashbots, Sellbots, and Bossbots.", + "Each Gag Track has different amounts of accuracy and damage.", + "Sound gags will affect all Cogs but will wake up any lured Cogs.", + "Defeating Cogs in strategic order can greatly increase your chances of winning battles.", + "The Toon-Up Gag Track lets you heal other Toons in battle.", + "Gag experience points are doubled during a Cog Invasion!", + "Multiple Toons can team up and use the same Gag Track in battle to get bonus Cog damage.", + "In battle, gags are used in order from top to bottom as displayed on the Gag Menu.", + "The row of circular lights over Cog Building elevators show how many floors will be inside.", + "Click on a Cog to see more details.", + "Using high level gags against low level Cogs will not earn any experience points.", + "A gag that will earn experience has a blue background on the Gag Menu in battle.", + "Gag experience is multiplied when used inside Cog Buildings. Higher floors have higher multipliers.", + "When a Cog is defeated, each Toon in that round will get credit for the Cog when the battle is over.", + "Each street in Toontown has different Cog levels and types.", + "Sidewalks are safe from Cogs.", + "On the streets, side doors tell knock-knock jokes when approached.", + "Some ToonTasks train you for new Gag Tracks. You only get to choose six of the seven Gag Tracks, so choose carefully!", + "Traps are only useful if you or your friends coordinate using Lure in battle.", + "Higher level Lures are less likely to miss.", + "Lower level gags have a lower accuracy against high level Cogs.", + "Cogs cannot attack once they have been lured in battle.", + "When you and your friends defeat a Cog building you are rewarded with portraits inside the rescued Toon Building.", + "Using a Toon-Up gag on a Toon with a full Laff meter will not earn Toon-Up experience.", + "Cogs will be briefly stunned when hit by any gag. This increases the chance that other gags in the same round will hit.", + "Drop gags have low chance of hitting, but accuracy is increased when Cogs are first hit by another gag in the same round.", + "When you've defeated enough Cogs, use the \"Cog Radar\" by clicking the Cog icons on the Cog Gallery page in your Shticker Book.", + "During a battle, you can tell which Cog your teammates are attacking by looking at the dashes (-) and Xs.", + "During a battle, Cogs have a light on them that displays their health; green is healthy, red is nearly destroyed.", + "A maximum of four Toons can battle at once.", + "On the street, Cogs are more likely to join a fight against multiple Toons than just one Toon.", + "The two most difficult Cogs of each type are only found in buildings.", + "Drop gags never work against lured Cogs.", + "Cogs tend to attack the Toon that has done them the most damage.", + "Sound gags do not get bonus damage against lured Cogs.", + "If you wait too long to attack a lured Cog, it will wake up. Higher level lures last longer.", + "There are fishing ponds on every street in Toontown. Some streets have unique fish.", + ), + + TIP_MINIGAME : ( + "After you fill up your jellybean jar, any jellybeans you get from Trolley Games automatically spill over into your bank.", + "You can use the arrow keys instead of the mouse in the \"Match Minnie\" Trolley Game.", + "In the Cannon Game you can use the arrow keys to move your cannon and press the \"Control\" key to fire.", + "In the Ring Game, bonus points are awarded when the entire group successfully swims through its rings.", + "A perfect game of Match Minnie will double your points.", + "In the Tug-of-War you are awarded more jellybeans if you play against a tougher Cog.", + "Trolley Game difficulty varies by neighborhood; "+lToontownCentral+" has the easiest and "+lDonaldsDreamland+" has the hardest.", + "Certain Trolley Games can only be played in a group.", + ), + + TIP_COGHQ : ( + "You must complete your Sellbot Disguise before visiting the V.P.", + "You must complete your Cashbot Disguise before visiting the C.F.O.", + "You must complete your Lawbot Disguise before visiting the Chief Justice.", + "You can jump on Cog Goons to temporarily disable them.", + "Collect Cog Merits by defeating Sellbot Cogs in battle.", + "Collect Cogbucks by defeating Cashbot Cogs in battle.", + "Collect Jury Notices by defeating Lawbot Cogs in battle.", + "Collect Stock Options by defeating Bossbot Cogs in battle.", + "You get more Merits, Cogbucks, Jury Notices, or Stock Options from higher level Cogs.", + "When you collect enough Cog Merits to earn a promotion, go see the Sellbot V.P.!", + "When you collect enough Cogbucks to earn a promotion, go see the Cashbot C.F.O.!", + "When you collect enough Jury Notices to earn a promotion, go see the Lawbot Chief Justice!", + "When you collect enough Stock Options to earn a promotion, go see the Bossbot C.E.O.!", + "You can talk like a Cog when you are wearing your Cog Disguise.", + "Up to eight Toons can join together to fight the Sellbot V.P.", + "Up to eight Toons can join together to fight the Cashbot C.F.O.", + "Up to eight Toons can join together to fight the Lawbot Chief Justice.", + "Up to eight Toons can join together to fight the Bossbot C.E.O.", + "Inside Cog Headquarters follow stairs leading up to find your way.", + "Each time you battle through a Sellbot HQ factory, you will gain one part of your Sellbot Cog Disguise.", + "You can check the progress of your Cog Disguise in your Shticker Book.", + "You can check your promotion progress on your Disguise Page in your Shticker Book.", + "Make sure you have full gags and a full Laff Meter before going to Cog Headquarters.", + "As you get promoted, your Cog disguise updates.", + "You must defeat the "+Foreman+" to recover a Sellbot Cog Disguise part.", + "Earn Cashbot disguise suit parts as rewards for completing ToonTasks in Donald's Dreamland.", + "Cashbots manufacture and distribute their currency, Cogbucks, in three Mints - Coin, Dollar and Bullion.", + "Wait until the C.F.O. is dizzy to throw a safe, or he will use it as a helmet! Hit the helmet with another safe to knock it off.", + "Earn Lawbot disguise suit parts as rewards for completing ToonTasks for Professor Flake.", + "It pays to be puzzled: the virtual Cogs in Lawbot HQ won't reward you with Jury Notices.", + ), + TIP_ESTATE : ( + # Doodles + "Doodles can understand some SpeedChat phrases. Try them!", + "Use the \"Pet\" SpeedChat menu to ask your Doodle to do tricks.", + "You can teach Doodles tricks with training lessons from Clarabelle's Cattlelog.", + "Reward your Doodle for doing tricks.", + "If you visit a friend's estate, your Doodle will come too.", + "Feed your Doodle a jellybean when it is hungry.", + "Click on a Doodle to get a menu where you can Feed, Scratch, and Call him.", + "Doodles love company. Invite your friends over to play!", + "All Doodles have unique personalities.", + "You can return your Doodle and adopt a new one at the Pet Shops.", + "When a Doodle performs a trick, the Toons around it heal.", + "Doodles become better at tricks with practice. Keep at it!", + "More advanced Doodle tricks heal Toons faster.", + "Experienced Doodles can perform more tricks before getting tired.", + "You can see a list of nearby Doodles in your Friends List.", + # Furniture / Cattlelog + "Purchase furniture from Clarabelle's Cattlelog to decorate your house.", + "The bank inside your house holds extra jellybeans.", + "The closet inside your house holds extra clothes.", + "Go to your friend's house and try on his clothes.", + "Purchase better fishing rods from Clarabelle's Cattlelog.", + "Purchase larger banks from Clarabelle's Cattlelog.", + "Call Clarabelle using the phone inside your house.", + "Clarabelle sells a larger closet that holds more clothing.", + "Make room in your closet before using a Clothing Ticket.", + "Clarabelle sells everything you need to decorate your house.", + "Check your mailbox for deliveries after ordering from Clarabelle.", + "Clothing from Clarabelle's Cattlelog takes one hour to be delivered.", + "Wallpaper and flooring from Clarabelle's Cattlelog take one hour to be delivered.", + "Furniture from Clarabelle's Cattlelog takes a full day to be delivered.", + "Store extra furniture in your attic.", + "You will get a notice from Clarabelle when a new Cattlelog is ready.", + "You will get a notice from Clarabelle when a Cattlelog delivery arrives.", + "New Cattlelogs are delivered each week.", + "Look for limited-edition holiday items in the Cattlelog.", + "Move unwanted furniture to the trash can.", + # Fish + "Some fish, like the Holey Mackerel, are more commonly found in Toon Estates.", + # Misc + "You can invite your friends to your Estate using SpeedChat.", + "Did you know the color of your house matches the color of your Pick-A-Toon panel?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Buy a Roadster, TUV, or Cruiser kart in Goofy's Auto Shop.", + "Customize your kart with decals, rims and more in Goofy's Auto Shop.", + "Earn tickets by kart racing at Goofy Speedway.", + "Tickets are the only currency accepted at Goofy's Auto Shop.", + "Tickets are required as deposits to race.", + "A special page in the Shticker Book allows you to customize your kart.", + "A special page in the Shticker Book allows you to view records on each track.", + "A special page in the Shticker Book allows you to display trophies.", + "Screwball Stadium is the easiest track at Goofy Speedway.", + "Airborne Acres has the most hills and jumps of any track at Goofy Speedway.", + "Blizzard Boulevard is the most challenging track at Goofy Speedway.", + ), + TIP_GOLF: ( + # Golfing specific + "Press the Tab key to see a top view of the golf course.", + "Press the Up Arrow key to point yourself towards the golf hole.", + "Swinging the club is just like throwing a pie.", + ), + } + +FishGenusNames = { + 0 : "Balloon Fish", + 2 : "Cat Fish", + 4 : "Clown Fish", + 6 : "Frozen Fish", + 8 : "Star Fish", + 10 : "Holey Mackerel", + 12 : "Dog Fish", + 14 : "Amore Eel", + 16 : "Nurse Shark", + 18 : "King Crab", + 20 : "Moon Fish", + 22 : "Sea Horse", + 24 : "Pool Shark", + 26 : "Bear Acuda", + 28 : "Cutthroat Trout", + 30 : "Piano Tuna", + 32 : "Peanut Butter & Jellyfish", + 34 : "Devil Ray", + } + +FishSpeciesNames = { + 0 : ( "Balloon Fish", + "Hot Air Balloon Fish", + "Weather Balloon Fish", + "Water Balloon Fish", + "Red Balloon Fish", + ), + 2 : ( "Cat Fish", + "Siamese Cat Fish", + "Alley Cat Fish", + "Tabby Cat Fish", + "Tom Cat Fish", + ), + 4 : ( "Clown Fish", + "Sad Clown Fish", + "Party Clown Fish", + "Circus Clown Fish", + ), + 6 : ( "Frozen Fish", + ), + 8 : ( "Star Fish", + "Five Star Fish", + "Rock Star Fish", + "Shining Star Fish", + "All Star Fish", + ), + 10 : ( "Holey Mackerel", + ), + 12 : ( "Dog Fish", + "Bull Dog Fish", + "Hot Dog Fish", + "Dalmatian Dog Fish", + "Puppy Dog Fish", + ), + 14 : ( "Amore Eel", + "Electric Amore Eel", + ), + 16 : ( "Nurse Shark", + "Clara Nurse Shark", + "Florence Nurse Shark", + ), + 18 : ( "King Crab", + "Alaskan King Crab", + "Old King Crab", + ), + 20 : ( "Moon Fish", + "Full Moon Fish", + "Half Moon Fish", + "New Moon Fish", + "Crescent Moon Fish", + "Harvest Moon Fish", + ), + 22 : ( "Sea Horse", + "Rocking Sea Horse", + "Clydesdale Sea Horse", + "Arabian Sea Horse", + ), + 24 : ( "Pool Shark", + "Kiddie Pool Shark", + "Swimming Pool Shark", + "Olympic Pool Shark", + ), + 26 : ( "Brown Bear Acuda", + "Black Bear Acuda", + "Koala Bear Acuda", + "Honey Bear Acuda", + "Polar Bear Acuda", + "Panda Bear Acuda", + "Kodiac Bear Acuda", + "Grizzly Bear Acuda", + ), + 28 : ( "Cutthroat Trout", + "Captain Cutthroat Trout", + "Scurvy Cutthroat Trout", + ), + 30 : ( "Piano Tuna", + "Grand Piano Tuna", + "Baby Grand Piano Tuna", + "Upright Piano Tuna", + "Player Piano Tuna", + ), + 32 : ( "Peanut Butter & Jellyfish", + "Grape PB&J Fish", + "Crunchy PB&J Fish", + "Strawberry PB&J Fish", + "Concord Grape PB&J Fish", + ), + 34 : ( "Devil Ray", + ), + } + +CogPartNames = ( + "Upper Left Leg", "Lower Left Leg", "Left Foot", + "Upper Right Leg", "Lower Right Leg", "Right Foot", + "Left Shoulder", "Right Shoulder", "Chest", "Health Meter", "Pelvis", + "Upper Left Arm", "Lower Left Arm", "Left Hand", + "Upper Right Arm", "Lower Right Arm", "Right Hand", + ) + +CogPartNamesSimple = ( + "Upper Torso", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = 'Front Entrance' +SellbotLegFactorySpecLobby = 'Lobby' +SellbotLegFactorySpecLobbyHallway = 'Lobby Hallway' +SellbotLegFactorySpecGearRoom = 'Gear Room' +SellbotLegFactorySpecBoilerRoom = 'Boiler Room' +SellbotLegFactorySpecEastCatwalk = 'East Catwalk' +SellbotLegFactorySpecPaintMixer = 'Paint Mixer' +SellbotLegFactorySpecPaintMixerStorageRoom = 'Paint Mixer Storage Room' +SellbotLegFactorySpecWestSiloCatwalk = 'West Silo Catwalk' +SellbotLegFactorySpecPipeRoom = 'Pipe Room' +SellbotLegFactorySpecDuctRoom = 'Duct Room' +SellbotLegFactorySpecSideEntrance = 'Side Entrance' +SellbotLegFactorySpecStomperAlley = 'Stomper Alley' +SellbotLegFactorySpecLavaRoomFoyer = 'Lava Room Foyer' +SellbotLegFactorySpecLavaRoom = 'Lava Room' +SellbotLegFactorySpecLavaStorageRoom = 'Lava Storage Room' +SellbotLegFactorySpecWestCatwalk = 'West Catwalk' +SellbotLegFactorySpecOilRoom = 'Oil Room' +SellbotLegFactorySpecLookout = 'Lookout' +SellbotLegFactorySpecWarehouse = 'Warehouse' +SellbotLegFactorySpecOilRoomHallway = 'Oil Room Hallway' +SellbotLegFactorySpecEastSiloControlRoom = 'East Silo Control Room' +SellbotLegFactorySpecWestSiloControlRoom = 'West Silo Control Room' +SellbotLegFactorySpecCenterSiloControlRoom = 'Center Silo Control Room' +SellbotLegFactorySpecEastSilo = 'East Silo' +SellbotLegFactorySpecWestSilo = 'West Silo' +SellbotLegFactorySpecCenterSilo = 'Center Silo' +SellbotLegFactorySpecEastSiloCatwalk = 'East Silo Catwalk' +SellbotLegFactorySpecWestElevatorShaft = 'West Elevator Shaft' +SellbotLegFactorySpecEastElevatorShaft = 'East Elevator Shaft' + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "VICTORY!!" +FishBingoJackpot = "JACKPOT!" +FishBingoGameOver = "GAME OVER" +FishBingoIntermission = "Intermission\nEnds In:" +FishBingoNextGame = "Next Game\nStarts In:" +FishBingoTypeNormal = "Classic" +FishBingoTypeCorners = "Four Corners" +FishBingoTypeDiagonal = "Diagonals" +FishBingoTypeThreeway = "Three Way" +FishBingoTypeBlockout = "BLOCKOUT!" +FishBingoStart = "It's time for Fish Bingo! Go to any available pier to play!" +FishBingoOngoing = "Welcome! Fish Bingo is currently in progress." +FishBingoEnd = "Hope you had fun playing Fish Bingo." +FishBingoHelpMain = "Welcome to Toontown Fish Bingo! Everyone at the pond works together to fill the card before time runs out." +FishBingoHelpFlash = "When you catch a fish, click on one of the flashing squares to mark the card." +FishBingoHelpNormal = "This is a Classic Bingo card. Mark any row down, across or diagonally to win." +FishBingoHelpDiagonals = "Mark both of the diagonals to win." +FishBingoHelpCorners = "An easy Corners card. Mark all four corners to win." +FishBingoHelpThreeway = "Three-way. Mark both diagonals and the middle row to win. This one isn't easy!" +FishBingoHelpBingo = "Bingo!" +FishBingoHelpBlockout = "Blockout!. Mark the entire card to win. You are competing against all the other ponds for a huge jackpot!" +FishBingoOfferToSellFish = "Your fish bucket is full. Would you like to sell your fish?" +FishBingoJackpotWin = "Win %s jellybeans!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Toon-up" +ResistanceToonupItem = "%s Toon-up" +ResistanceToonupItemMax = "Max" +ResistanceToonupChat = "Toons of the World, Toon-up!" +ResistanceRestockMenu = "Gag-up" +ResistanceRestockItem = "Gag-up %s" +ResistanceRestockItemAll = "All" +ResistanceRestockChat = "Toons of the World, Gag-up!" +ResistanceMoneyMenu = "jellybeans" +ResistanceMoneyItem = "%s jellybeans" +ResistanceMoneyChat = "Toons of the World, Spend Wisely!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": Welcome to the Resistance!" +ResistanceEmote2 = NPCToonNames[9228] + ": Use your new emote to identify yourself to other members." +ResistanceEmote3 = NPCToonNames[9228] + ": Good luck!" + +# Kart racing +KartUIExit = "Leave Kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Buy Kart" +KartShop_BuyAccessories = "Buy Accessories" +KartShop_BuyAccessory = "Buy Accessory" +KartShop_Cost = "Cost: %d Tickets" +KartShop_ConfirmBuy = "Buy the %s for %d Tickets?" +KartShop_NoAvailableAcc = "No available accessories of this type" +KartShop_FullTrunk = "Your trunk is full." +KartShop_ConfirmReturnKart = "Are you sure you want to return your current Kart?" +KartShop_ConfirmBoughtTitle = "Congratulations!" +KartShop_NotEnoughTickets = "Not Enough Tickets!" + +KartView_Rotate = "Rotate" +KartView_Right = "Right" +KartView_Left = "Left" + +# starting block +StartingBlock_NotEnoughTickets = "You don't have enough tickets! Try a practice race instead." +StartingBlock_NoBoard = "Boarding has ended for this race. Please wait for the next race to begin." +StartingBlock_NoKart = "You need a kart first! Try asking one of the clerks in the Kart Shop." +StartingBlock_Occupied = "This block is currently occupied! Please try another spot." +StartingBlock_TrackClosed = "Sorry, this track is closed for remodeling." +StartingBlock_EnterPractice = "Would you like to enter a practice race?" +StartingBlock_EnterNonPractice = "Would you like to enter a %s race for %s tickets?" +StartingBlock_EnterShowPad = "Would you like to park your car here?" +StartingBlock_KickSoloRacer = "Toon Battle and Grand Prix races require two or more racers." +StartingBlock_Loading = "Going to the Race!" + +#stuff for leader boards +LeaderBoard_Time = "Time" +LeaderBoard_Name = "Racer Name" +LeaderBoard_Daily = "Daily Scores" +LeaderBoard_Weekly = "Weekly Scores" +LeaderBoard_AllTime = "All Time Best Scores" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Practice", + "Toon Battle", + "Grand Prix", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "Go!" +KartRace_Reverse = " Rev" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Screwball Stadium", + RaceGlobals.RT_Speedway_1_rev : "Screwball Stadium" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Rustic Raceway", + RaceGlobals.RT_Rural_1_rev : "Rustic Raceway" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "City Circuit", + RaceGlobals.RT_Urban_1_rev : "City Circuit" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Corkscrew Coliseum", + RaceGlobals.RT_Speedway_2_rev : "Corkscrew Coliseum" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Airborne Acres", + RaceGlobals.RT_Rural_2_rev : "Airborne Acres" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Blizzard Boulevard", + RaceGlobals.RT_Urban_2_rev : "Blizzard Boulevard" + KartRace_Reverse, + } + +KartRace_Unraced = "N/A" + +KartDNA_KartNames = { + 0:"Cruiser", + 1:"Roadster", + 2:"Toon Utility Vehicle" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Air Cleaner", + 1001: "Four Barrel", + 1002: "Flying Eagle", + 1003: "Steer Horns", + 1004: "Straight Six", + 1005: "Small Scoop", + 1006: "Single Overhead", + 1007: "Medium Scoop", + 1008: "Single Barrel", + 1009: "Flugle Horn", + 1010: "Striped Scoop", + #spoiler accessory names + 2000: "Space Wing", + 2001: "Patched Spare", + 2002: "Roll Cage", + 2003: "Single Fin", + 2004: "Double-decker Wing", + 2005: "Single Wing", + 2006: "Standard Spare", + 2007: "Single Fin", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "Dueling Horns", + 3001: "Freddie's Fenders", + 3002: "Cobalt Running Boards", + 3003: "Cobra Sidepipes", + 3004: "Straight Sidepipes", + 3005: "Scalloped Fenders", + 3006: "Carbon Running Boards", + 3007: "Wood Running Boards", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "Curly Tailpipes", + 4001: "Splash Fenders", + 4002: "Dual Exhaust", + 4003: "Plain Dual Fins", + 4004: "Plain Mudflaps", + 4005: "Quad Exhaust", + 4006: "Dual Flares", + 4007: "Mega Exhaust", + 4008: "Striped Dual Fins", + 4009: "Bubble Duals Fins", + 4010: "Striped Mudflaps", + 4011: "Mickey Mudflaps", + 4012: "Scalloped Mudflaps", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "Turbo", + 5001: "Moon", + 5002: "Patched", + 5003: "Three Spoke", + 5004: "Paint Lid", + 5005: "Heart", + 5006: "Mickey", + 5007: "Five Bolt", + 5008: "Daisy", + 5009: "Basketball", + 5010: "Hypno", + 5011: "Tribal", + 5012: "Gemstone", + 5013: "Five Spoke", + 5014: "Knockoff", + #decal accessory names + 6000: "Number Five", + 6001: "Splatter", + 6002: "Checkerboard", + 6003: "Flames", + 6004: "Hearts", + 6005: "Bubbles", + 6006: "Tiger", + 6007: "Flowers", + 6008: "Lightning", + 6009: "Angel", + #paint accessory names + 7000: "Chartreuse", + 7001: "Peach", + 7002: "Bright Red", + 7003: "Red", + 7004: "Maroon", + 7005: "Sienna", + 7006: "Brown", + 7007: "Tan", + 7008: "Coral", + 7009: "Orange", + 7010: "Yellow", + 7011: "Cream", + 7012: "Citrine", + 7013: "Lime", + 7014: "Sea Green", + 7015: "Green", + 7016: "Light Blue", + 7017: "Aqua", + 7018: "Blue", + 7019: "Periwinkle", + 7020: "Royal Blue", + 7021: "Slate Blue", + 7022: "Purple", + 7023: "Lavender", + 7024: "Pink", + 7025: "Plum", + 7026: "Black", + } + +RaceHoodSpeedway = "Speedway" +RaceHoodRural = "Rural" +RaceHoodUrban = "Urban" +RaceTypeCircuit = "Tournament" +RaceQualified = "qualified" +RaceSwept = "swept" +RaceWon = "won" +Race = "race" +Races = "races" +Total = "total" +GrandTouring = "Grand Touring" + +def getTrackGenreString(genreId): + genreStrings = [ "Speedway", + "Country", + "City" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " Kart Racing trophies won! Laff point boost!", + str(RaceGlobals.TrophiesPerCup * 2) + " Kart Racing trophies won! Laff point boost!", + str(RaceGlobals.TrophiesPerCup * 3) + " Kart Racing trophies won! Laff point boost!", + ] + +KartRace_TitleInfo = "Get Ready to Race" +KartRace_SSInfo = "Welcome to Screwball Stadium!\nPut the pedal to the metal and hang on tight!\n" +KartRace_CoCoInfo = "Welcome to Corkscrew Coliseum!\nUse the banked turns to keep your speed up!\n" +KartRace_RRInfo = "Welcome to Rustic Raceway!\nPlease be kind to the fauna and stay on the track!\n" +KartRace_AAInfo = "Welcome to Airborne Acres!\nHold onto your hats! It looks bumpy up ahead...\n" +KartRace_CCInfo = "Welcome to City Circuit!\nWatch out for pedestrians as you speed through downtown!\n" +KartRace_BBInfo = "Welcome to Blizzard Boulevard!\nWatch your speed. There might be ice out there.\n" +KartRace_GeneralInfo = "Use Control to throw gags you pick up on the track, and the arrow keys to control your kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'daily', + RaceGlobals.Weekly : 'weekly', + RaceGlobals.AllTime : 'all time', + } + +KartRace_FirstSuffix = 'st' +KartRace_SecondSuffix = ' nd' +KartRace_ThirdSuffix = ' rd' +KartRace_FourthSuffix = ' th' +KartRace_WrongWay = 'Wrong\nWay!' +KartRace_LapText = "Lap %s" +KartRace_FinalLapText = "Final Lap!" +KartRace_Exit = "Exit Race" +KartRace_NextRace = "Next Race" +KartRace_Leave = "Leave Race" +KartRace_Qualified = "Qualified!" +KartRace_Record = "Record!" +KartRace_RecordString = 'You have set a new %s record for %s! Your bonus is %s tickets.' +KartRace_Tickets = "Tickets" +KartRace_Exclamations = "!" +KartRace_Deposit = "Deposit" +KartRace_Winnings = "Winnings" +KartRace_Bonus = "Bonus" +KartRace_RaceTotal = "Race Total" +KartRace_CircuitTotal = "Circuit Total" +KartRace_Trophies = "Trophies" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s " + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "Qualify:\n" +KartRace_RaceTimeout = "You timed out of that race. Your tickets have been refunded. Keep trying!" +KartRace_RaceTimeoutNoRefund = "You timed out of that race. Your tickets have not been refunded because the Grand Prix had already started. Keep trying!" +KartRace_RacerTooSlow = "You took too long to finish the race. Your tickets have not been refunded. Keep trying!" +KartRace_PhotoFinish = "Photo Finish!" +KartRace_CircuitPoints = 'Circuit Points' + +CircuitRaceStart = "The Toontown Grand Prix at Goofy Speedway is about to begin! To win, collect the most points in three consecutive races!" +CircuitRaceOngoing = "Welcome! The Toontown Grand Prix is currently in progress." +CircuitRaceEnd = "That's all for today's Toontown Grand Prix at Goofy Speedway. See you next week!" + +# Scavenger hunt holidays +TrickOrTreatMsg = 'You have already\nfound this treat!' + +WinterCarolingMsg = 'You have already been caroling here!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Hmmm what's on the docket today?" +LawbotBossTempIntro1 = "Aha, we have a Toon on trial!" +LawbotBossTempIntro2 = "The prosecution's case is strong." +LawbotBossTempIntro3 = "And here are the public defenders." +LawbotBossTempIntro4 = "Wait a minute... You're Toons!" +LawbotBossTempJury1 = "Jury selection will now commence." +LawbotBossHowToGetEvidence = "Touch the witness stand to get evidence." +LawbotBossTrialChat1 = "Court is now in session" +LawbotBossHowToThrowPies = "Press the Delete key to throw the evidence\n at the lawyers or into the scale!" +LawbotBossNeedMoreEvidence = "You need to get more evidence!" +LawbotBossDefenseWins1 = "Impossible! The defense won?" +LawbotBossDefenseWins2 = "No. I declare a mistrial! A new one will be scheduled." +LawbotBossDefenseWins3 = "Hrrmpphh. I'll be in my chambers." +LawbotBossProsecutionWins = "I find in favor of the plaintiff" +LawbotBossReward = "I award a promotion and the ability to summon Cogs" +LawbotBossLeaveCannon = "Leave cannon" +LawbotBossPassExam = "Bah, so you passed the bar exam." +LawbotBossTaunts = [ + "%s, I find you in contempt of court!", + "Objection sustained!", + "Strike that from the record.", + "Your appeal has been rejected. I sentence you to sadness!", + "Order in the court!", + ] +LawbotBossAreaAttackTaunt = "You're all in contempt of court!" + + +WitnessToonName = "Bumpy Bumblebehr" +WitnessToonPrepareBattleTwo = "Oh no! They're putting only Cogs on the jury!\aQuick, use the cannons and shoot some Toon jurors into the jury chairs.\aWe need %d to get a balanced scale." +WitnessToonNoJuror = "Oh oh, no Toon jurors. This will be a tough trial." +WitnessToonOneJuror = "Cool! There is 1 Toon in the jury!" +WitnessToonSomeJurors = "Cool! There are %d Toons in the jury!" +WitnessToonAllJurors = "Awesome! All the jurors are Toons!" +WitnessToonPrepareBattleThree = "Hurry, touch the witness stand to get evidence.\aPress the Delete key to throw the evidence at the lawyers, or at the defense pan." +WitnessToonCongratulations = "You did it! Thank you for a spectacular defense!\aHere, take these papers the Chief Justice left behind.\aWith it you'll be able to summon Cogs from your Cog Gallery page." + +WitnessToonLastPromotion = "\aWow, you've reached level %s on your Cog Suit!\aCogs don't get promoted higher than that.\aYou can't upgrade your Cog Suit anymore, but you can certainly keep working for the Resistance!" +WitnessToonHPBoost = "\aYou've done a lot of work for the Resistance.\aThe Toon Council has decided to give you another Laff point. Congratulations!" +WitnessToonMaxed = "\aI see that you have a level %s Cog Suit. Very impressive!\aOn behalf of the Toon Council, thank you for coming back to defend more Toons!" +WitnessToonBonus = "Wonderful! All the lawyers are stunned. Your evidence weight is %s times heavier for %s seconds" + +WitnessToonJuryWeightBonusSingular = { + 6: 'This is a tough case. You seated %d Toon juror, so your evidence has a bonus weight of %d.', + 7: 'This is a very tough case. You seated %d Toon juror, so your evidence has a bonus weight of %d.', + 8: 'This is the toughest case. You seated %d Toon juror, so your evidence has a bonus weight of %d.', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'This is a tough case. You seated %d Toon jurors, so your evidence has a bonus weight of %d.', + 7: 'This is a very tough case. You seated %d Toon jurors, so your evidence has a bonus weight of %d.', + 8: 'This is the toughest case. You seated %d Toon jurors, so your evidence has a bonus weight of %d.', +} + +# Cog Summons stuff +IssueSummons = "Summon" +SummonDlgTitle = "Issue a Cog Summons" +SummonDlgButton1 = "Summon a Cog" +SummonDlgButton2 = "Summon a Cog Building" +SummonDlgButton3 = "Summon a Cog Invasion" +SummonDlgSingleConf = "Would you like to issue a summons to a %s?" +SummonDlgBuildingConf = "Would you like to summon a %s to a nearby Toon building?" +SummonDlgInvasionConf = "Would you like to summon a %s invasion?" +SummonDlgNumLeft = "You have %s left." +SummonDlgDelivering = "Delivering Summons..." +SummonDlgSingleSuccess = "You have successfully summoned the Cog." +SummonDlgSingleBadLoc = "Sorry, Cogs aren't allowed here. Try somewhere else." +SummonDlgBldgSuccess = "You have successfully summoned the Cogs. %s has agreed to let them temporarily take over %s!" +SummonDlgBldgSuccess2 = "You have successfully summoned the Cogs. A Shopkeeper has agreed to let them temporarily take over their building!" +SummonDlgBldgBadLoc = "Sorry, there are no Toon buildings nearby for the Cogs to take over." +SummonDlgInvasionSuccess = "You have successfully summoned the Cogs. It's an invasion!" +SummonDlgInvasionBusy = "A %s cannot be found now. Try again when the Cog invasion is over." +SummonDlgInvasionFail = "Sorry, the Cog invasion has failed." +SummonDlgShopkeeper = "The Shopkeeper " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": Welcome to Polar Place!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Try this on for size." +PolarPlaceEffect3 = NPCToonNames[3306] + ": Your new look will only work in " + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "Skull Finder!" +LaserGameRoll = "Matching" +LaserGameAvoid = "Avoid the Skulls" +LaserGameDrag = "Drag three of a color in a row" +LaserGameDefault = "Unknown Game" + +# Pinball text +#PinballHiScore = "High Score: %d %s\n" +#PinballYourBestScore = "Your Best Score: %d\n" +#PinballScore = "Score: %d x %d : %d" +PinballHiScore = "High Score: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Your Best Score:\n" +PinballScore = "Score: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Feather Gag Tree" +GagTreeJugglingBalls = "Juggling Balls Gag Tree" +StatuaryFountain = "Fountain" +StatuaryDonald = "Donald Statue" +StatuaryMinnie = "Minnie Statue" +StatuaryMickey1 = "Mickey Statue" +StatuaryMickey2 = "Mickey Fountain" +StatuaryToon = "Toon Statue" +StatuaryToonWave = "Toon Wave Statue" +StatuaryToonVictory = "Toon Victory Statue" +StatuaryToonCrossedArms = 'Toon Authority Statue' +StatuaryToonThinking = 'Toon Embrace Statue' +StatuaryMeltingSnowman = 'Melting Snowman' +StatuaryGardenAccelerator = "Insta-Grow Fertilizer" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Red','Orange','Violet','Blue','Pink','Yellow','White','Green'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Daisy', + 50: 'Tulip', + 51: 'Carnation', + 52: 'Lily', + 53: 'Daffodil', + 54: 'Pansy', + 55: 'Petunia', + 56: 'Rose', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('School Daisy', + 'Lazy Daisy', + 'Midsummer Daisy', + 'Freshasa Daisy', + 'Whoopsie Daisy', + 'Upsy Daisy', + 'Crazy Daisy', + 'Hazy Dazy', + ), + 50: ('Onelip', + 'Twolip', + 'Threelip', + ), + 51: ('What-in Carnation', + 'Instant Carnation', + 'Hybrid Carnation', + 'Side Carnation', + 'Model Carnation', + ), + 52: ('Lily-of-the-Alley', + 'Lily Pad', + 'Tiger Lily', + 'Livered Lily', + 'Chili Lily', + 'Silly Lily', + 'Indubitab Lily', + 'Dilly Lilly', + ), + 53: ('Laff-o-dil', + 'Daffy Dill', + 'Giraff-o-dil', + 'Time and a half-o-dil', + ), + 54: ('Dandy Pansy', + 'Chim Pansy', + 'Potsen Pansy', + 'Marzi Pansy', + 'Smarty Pansy' + ), + 55: ('Car Petunia', + 'Platoonia', + ), + 56: ("Summer's Last Rose", + 'Corn Rose', + 'Tinted Rose', + 'Stinking Rose', + 'Istilla Rose', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +FloweringNewEntry = "New Entry" +ShovelNameDict = { + 0 : "Tin", + 1 : "Bronze", + 2 : "Silver", + 3 : "Gold", + } +WateringCanNameDict = { + 0 : "Small", + 1 : "Medium", + 2 : "Large", + 3 : "Huge", + } +GardeningPlant = "Plant" +GardeningWater = "Water" +GardeningRemove = "Remove" +GardeningPick = "Pick" +GardeningFull = "Full" +GardeningSkill = "Skill" +GardeningWaterSkill = "Water Skill" +GardeningShovelSkill = "Shovel Skill" +GardeningNoSkill = "No Skill Up" +GardeningPlantFlower = "Plant\nFlower" +GardeningPlantTree = "Plant\nTree" +GardeningPlantItem = "Plant\nItem" +PlantingGuiOk = "Plant" +PlantingGuiCancel = "Cancel" +PlantingGuiReset = "Reset" +GardeningChooseBeans = "Choose the jellybeans you want to plant." +GardeningChooseBeansItem = "Choose the jellybeans / item you want to plant." +GardeningChooseToonStatue = "Choose the toon you want to create a statue of." +GardenShovelLevelUp = "Congratulations you've earned a %(shovel)s! You've mastered the %(oldbeans)d bean flower! To progress you should pick %(newbeans)d bean flowers." +GardenShovelSkillLevelUp = "Congratulations! You've mastered the %(oldbeans)d bean flower! To progress you should pick %(newbeans)d bean flowers." +GardenShovelSkillMaxed = "Amazing! You've maxed out your shovel skill!" + +GardenWateringCanLevelUp = "Congratulations you've earned a new watering can!" +GardenMiniGameWon = "Congratulations you've watered the plant!" +ShovelTin = "Tin Shovel" +ShovelSteel = "Bronze Shovel" +ShovelSilver = "Silver Shovel" +ShovelGold = "Gold Shovel" +WateringCanSmall = "Small Watering Can" +WateringCanMedium = "Medium Watering Can" +WateringCanLarge = "Large Watering Can" +WateringCanHuge = "Huge Watering Can" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('red', 'green', 'orange','violet','blue','pink','yellow', + 'cyan','silver') +PlantItWith = " Plant with %s." +MakeSureWatered = " Make sure all your plants are watered first." +UseFromSpecialsTab = " Use from the specials tab of the garden page." +UseSpecial = "Use Special" +UseSpecialBadLocation = 'You can only use that in your garden.' +UseSpecialSuccess = 'Success! Your watered plants just grew.' +ConfirmWiltedFlower = "%(plant)s is wilted. Are you sure you want to remove it? It will not go into your flower basket, nor will you get an increase in skill." +ConfirmUnbloomingFlower = "%(plant)s is not blooming. Are you sure you want to remove it? It will not go into your flower basket, nor will you get an increase in skill." +ConfirmNoSkillupFlower = "Are you sure you want to pick the %(plant)s? It will go into your flower basket, but you will NOT get an increase in skill." +ConfirmSkillupFlower = "Are you sure you want to pick the %(plant)s? It will go into your flower basket. You will also get an increase in skill." +ConfirmMaxedSkillFlower = "Are you sure you want to pick the %(plant)s? It will go into your flower basket. You will NOT get an increase in skill since you've maximized it already." +ConfirmBasketFull = "Your flower basket is full. Sell some flowers first." +ConfirmRemoveTree = "Are you sure you want to remove the %(tree)s?" +ConfirmWontBeAbleToHarvest = " If you remove this tree, you won't be able to harvest gags from the higher level trees." +ConfirmRemoveStatuary = "Are you sure you want to permanently delete the %(item)s?" +ResultPlantedSomething = "Congratulations! You just planted a %s." +ResultPlantedSomethingAn = "Congratulations! You just planted an %s." +ResultPlantedNothing = "That didn't work. Please try a different combination of jellybeans." + +GardenGagTree = " Gag Tree" +GardenUberGag = "Uber Gag" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s jellybeans" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "a %s jellybean" % BeanColorWords[beanTuple[0]] + else: + retval += 'a' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " and %s jellybean" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Magic Beans" +GardenTextMagicBeansB = "Some Other Beans" +GardenSpecialDiscription = "This text should explain how to use a certain garden special" +GardenSpecialDiscriptionB = "This text should explain how to use a certain garden special, in yo face foo!" +GardenTrophyAwarded = "Wow! You collected %s of %s flowers. That deserves a trophy and a Laff boost!" +GardenTrophyNameDict = { + 0 : "Wheelbarrow", + 1 : "Shovels", + 2 : "Flower", + 3 : "Watering Can", + 4 : "Shark", + 5 : "Swordfish", + 6 : "Killer Whale", + } +SkillTooLow = "Skill\nToo Low" +NoGarden = "No\nGarden" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "Trolley Tracks" +TravelGameInstructions = "Click up or down to set your number of votes. Click the vote button to cast it. Reach your secret goal to get bonus beans. Earn more votes by doing well in the other games." +TravelGameRemainingVotes = "Remaining Votes:" +TravelGameUse = "Use" +TravelGameVotesWithPeriod = "votes." +TravelGameVotesToGo = "votes to go" +TravelGameVoteToGo = "vote to go" +TravelGameUp = "UP." +TravelGameDown = "DOWN." +TravelGameVoteWithExclamation = "Vote!" +TravelGameWaitingChoices = "Waiting for other players to vote..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['UP', 'DOWN'] +TravelGameTotals = 'Totals ' +TravelGameReasonVotes = 'The trolley is moving %(dir)s, winning by %(numVotes)d votes.' +TravelGameReasonVotesPlural = 'The trolley is moving %(dir)s, winning by %(numVotes)d votes.' +TravelGameReasonVotesSingular = 'The trolley is moving %(dir)s, winning by %(numVotes)d vote.' +TravelGameReasonPlace = '%(name)s breaks the tie. The trolley is moving %(dir)s.' +TravelGameReasonRandom = 'The trolley is randomly moving %(dir)s.' +TravelGameOneToonVote = "%(name)s used %(numVotes)s votes to go %(dir)s\n" +TravelGameBonusBeans = "%(numBeans)d Beans" +TravelGamePlaying = 'Up next, the %(game)s trolley game.' +TravelGameGotBonus = '%(name)s got a bonus of %(numBeans)s jellybeans!' +TravelGameNoOneGotBonus = "No one reached their secret goal. Everyone gets 1 jellybean." +TravelGameConvertingVotesToBeans = "Converting some votes to jellybeans..." +TravelGameGoingBackToShop ="Only 1 player left. Going to Goofy's Gag Shop." + +PairingGameTitle = "Toon Memory Game" +PairingGameInstructions = "Press Delete to open a card. Match 2 cards to score a point. Make a match with the bonus glow and earn an extra point. Earn more points by keeping the flips low." +PairingGameInstructionsMulti = "Press Delete to open a card. Press Control to signal another player to open a card. Match 2 cards to score a point. Make a match with the bonus glow and earn an extra point. Earn more points by keeping the flips low." +PairingGamePerfect = 'PERFECT!!' +PairingGameFlips = 'Flips:' +PairingGamePoints = 'Points:' + +TrolleyHolidayStart = "Trolley Tracks is about to begin! Board any trolley with 2 or more toons to play." +TrolleyHolidayOngoing = "Welcome! Trolley Tracks is currently in progress." +TrolleyHolidayEnd = "That's all for today's Trolley Tracks. See you next week!" + +TrolleyWeekendStart = "Trolley Tracks Weekend is about to begin! Board any trolley with 2 or more toons to play." +TrolleyWeekendEnd = "That's all for Trolley Tracks Weekend." + +VineGameTitle = "Jungle Vines" +VineGameInstructions = "Get to the rightmost vine in time. Press Up or Down to climb the vine. Press Left or Right to change facing and jump. The lower you are on the vine, the faster you jump off. Collect the bananas if you can, but avoid the bats and spiders." + +ValentinesDayStart = "Happy ValenToon's Day!" +ValentinesDayEnd = "That's all for ValenToon's Day!" + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "Walk In The Par", + 1: "Hole Some Fun", + 2: "The Hole Kit And Caboodle" + } + +GolfHoleNames = { + 0: 'Whole In Won', + 1: 'No Putts About It', + 2: 'Down The Hatch', + 3: 'Seeing Green', + 4: 'Hot Links', + 5: 'Peanut Putter', + 6: 'Swing-A-Long', + 7: 'Afternoon Tee', + 8: 'Hole In Fun', + 9: 'Rock And Roll In', + 10: 'Bogey Nights', + 11: 'Tea Off Time', + 12: 'Holey Mackerel!', + 13: 'One Little Birdie', + 14: 'At The Drive In', + 15: 'Swing Time', + 16: 'Hole On The Range', + 17: 'Second Wind', + 18: 'Whole In Won-2', + 19: 'No Putts About It-2', + 20: 'Down The Hatch-2', + 21: 'Seeing Green-2', + 22: 'Hot Links-2', + 23: 'Peanut Putter-2', + 24: 'Swing-A-Long-2', + 25: 'Afternoon Tee-2', + 26: 'Hole In Fun-2', + 27: 'Rock And Roll In-2', + 28: 'Bogey Nights-2', + 29: 'Tea Off Time-2', + 30: 'Holey Mackerel!-2', + 31: 'One Little Birdie-2', + 32: 'At The Drive In-2', + 33: 'Swing Time-2', + 34: 'Hole On The Range-2', + 35: 'Second Wind-2', + } + +GolfHoleInOne = "Hole In One" +GolfCondor = "Condor" # four Under Par +GolfAlbatross = "Albatross" # three under par +GolfEagle = "Eagle" # two under par +GolfBirdie = "Birdie" # one under par +GolfPar = "Par" +GolfBogey = "Bogey" # one over par +GolfDoubleBogey = "Double Bogey" # two over par +GolfTripleBogey = "Triple Bogey" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "Courses Completed" +CoursesUnderPar = "Courses Under Par" +HoleInOneShots = "Hole In One Shots" +EagleOrBetterShots = "Eagle Or Better Shots" +BirdieOrBetterShots = "Birdie Or Better Shots" +ParOrBetterShots = "Par Or Better Shots" +MultiPlayerCoursesCompleted = "Multiplayer Courses Completed" +TwoPlayerWins = "Two Player Wins" +ThreePlayerWins = "Three Player Wins" +FourPlayerWins = "Four Player Wins" +CourseZeroWins = GolfCourseNames[0] + " Wins" +CourseOneWins = GolfCourseNames[1] + " Wins" +CourseTwoWins = GolfCourseNames[2] + " Wins" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + " Trophies won", + str(GolfGlobals.TrophiesPerCup * 2) + " Trophies won", + str(GolfGlobals.TrophiesPerCup * 3) + " Trophies won", +] + +GolfAvReceivesHoleBest = "%(name)s scored a new hole best at %(hole)s!" +GolfAvReceivesCourseBest = "%(name)s scored a new course best at %(course)s!" +GolfAvReceivesCup = "%(name)s receives the %(cup)s cup!! Laff point boost!" +GolfAvReceivesTrophy = "%(name)s receives the %(award)s trophy!!" +GolfRanking = "Ranking: \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "Press Left or Right to change tee spot.\nPress Control to select." +GolfWarningMustSwing = "Warning: You must press Control on your next swing." +GolfAimInstructions = "Press Left or Right to aim.\nPress and hold Control to swing." +GolferExited = "%s has left the golf course." +GolfPowerReminder = "Hold Down Control Longer to\nHit the Ball Further" + + +# GolfScoreBoard.py +GolfPar = "Par" +GolfHole = "Hole" +GolfTotal = "Total" +GolfExitCourse = "Exit Course" +GolfUnknownPlayer = "???" + +# GolfPage.py +GolfPageTitle = "Golf" +GolfPageTitleCustomize = "Golf Customizer" +GolfPageTitleRecords = "Personal Best Records" +GolfPageTitleTrophy = "Golfing Trophies" +GolfPageCustomizeTab = "Customize" +GolfPageRecordsTab = "Records" +GolfPageTrophyTab = "Trophy" +GolfPageTickets = "Tickets : " +GolfPageConfirmDelete = "Delete Accessory?" +GolfTrophyTextDisplay = "Trophy %(number)s : %(desc)s" +GolfCupTextDisplay = "Cup %(number)s : %(desc)s" +GolfCurrentHistory = "Current %(historyDesc)s : %(num)s" +GolfTieBreakWinner = "%(name)s wins the random tie breaker!" +GolfSeconds = " - %(time).2f seconds" +GolfTimeTieBreakWinner = "%(name)s wins the total aiming time tie breaker!!!" + + + +RoamingTrialerWeekendStart = "Tour Toontown is starting! Free players may now enter any neighborhood!" +RoamingTrialerWeekendOngoing = "Welcome to Tour Toontown! Free players may now enter any neighborhood!" +RoamingTrialerWeekendEnd = "That's all for Tour Toontown." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Good news! Exclusive Test Toon double gag experience time has started." +MoreXpHolidayOngoing = "Welcome! Exclusive Test Toon double gag experience time is currently ongoing." +MoreXpHolidayEnd = "Exclusive Test Toon double gag experience time has ended. Thanks for helping us Test things!" + +JellybeanDayHolidayStart = "It's Jellybean Day! Get Double Jellybean rewards at Parties!" +JellybeanDayHolidayEnd = "That's all for Jellybean Day. See you next year." +PartyRewardDoubledJellybean = "Double Jellybeans!" + +GrandPrixWeekendHolidayStart = "It's Grand Prix Weekend at Goofy Speedway! Free and paid players collect the most points in three consecutive races." +GrandPrixWeekendHolidayEnd = "That's all for Grand Prix Weekend. See you next year." + +LogoutForced = "You have done something wrong\n and are being logged out automatically,\n additionally your account may be frozen.\n Try going on a walk outside, it is fun." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \nhas jumped in the golf kart." +CountryClubBossConfrontedMsg = "%s is battling the Club President!" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "All challenges must be defeated first." + +# DistributedMolefield.py +MolesLeft = "Moles Left: %d" +MolesInstruction = "Mole Stomp!\nJump on the red moles!" +MolesFinished = "Mole Stomp successful!" +MolesPityWin = "Stomp Failed! But the moles left." +MolesRestarted = "Stomp Failed! Restarting..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "Remove the cog ball!" +BustACogExit = "Exit for Now" +BustACogHowto = "How to Play" +BustACogFailure = "Out of Time!" +BustACogSuccess = "Success!" + +# bossbot golf green games +GolfGreenGameScoreString = "Puzzles Left: %s" +GolfGreenGamePlayerScore = "Solved %s" +GolfGreenGameBonusGag = "You won %s!" +GolfGreenGameGotHelp = "%s solved a Puzzle!" + +GolfGreenGameDirections = "Shoot balls using the the mouse\n\n\nMatching three of a color causes the balls to fall\n\n\nRemove all Cog balls from the board" + +# DistributedMaze.py +enterHedgeMaze = "Race through the Hedge Maze\n for a laff bonus!" +toonFinishedHedgeMaze = "%s \n finished in %s place!" +hedgeMazePlaces = ["first","second","third","Fourth"] +mazeLabel = "Maze Race!" + +# Boarding Group +BoardingPartyReadme = 'Boarding Group?' +BoardingGroupHide = 'Hide' +BoardingGroupShow = 'Show Boarding Group' +BoardingPartyInform = 'Create an elevator Boarding Group by clicking on another Toon and Inviting them.\nIn this area Boarding Groups cannot have more than %s Toons.' +BoardingPartyTitle = 'Boarding Group' +QuitBoardingPartyLeader = 'Disband' +QuitBoardingPartyNonLeader = 'Leave' +QuitBoardingPartyConfirm = 'Are you sure you want to quit this Boarding Group?' +BoardcodeMissing = 'Something went wrong; try again later.' +BoardcodeMinLaffLeader = 'Your group cannot board because you have less than %s laff points.' +BoardcodeMinLaffNonLeaderSingular = 'Your group cannot board because %s has less than %s laff points.' +BoardcodeMinLaffNonLeaderPlural = 'Your group cannot board because %s have less than %s laff points.' +BoardcodePromotionLeader = 'Your group cannot board because you do not have enough promotion merits.' +BoardcodePromotionNonLeaderSingular = 'Your group cannot board because %s does not have enough promotion merits.' +BoardcodePromotionNonLeaderPlural = 'Your group cannot board because %s do not have enough promotion merits.' +BoardcodeSpace = 'Your group cannot board because there is not enough space.' +BoardcodeBattleLeader = 'Your group cannot board because you are in battle.' +BoardcodeBattleNonLeaderSingular = 'Your group cannot board because %s is in battle.' +BoardcodeBattleNonLeaderPlural = 'Your group cannot board because %s are in battle.' +BoardingInviteMinLaffInviter = 'You need %s Laff Points before being a member of this Boarding Group.' +BoardingInviteMinLaffInvitee = '%s needs %s Laff Points before being a member of this Boarding Group.' +BoardingInvitePromotionInviter = 'You need to earn a promotion before being a member of this Boarding Group.' +BoardingInvitePromotionInvitee = '%s needs to earn a promotion before being a member of this Boarding Group.' +BoardingInviteNotPaidInvitee = '%s needs to be a paid Member to be a part of your Boarding Group.' +BoardingInviteeInDiffGroup = '%s is already in a different Boarding Group.' +BoardingInviteeInKickOutList = '%s had been removed by your leader. Only the leader can re-invite removed members.' +BoardingInviteePendingIvite = '%s has a pending invite; try again later.' +BoardingInviteeInElevator = '%s is currently busy; try again later.' +BoardingInviteGroupFull = 'Your Boarding Group is already full.' +BoardingAlreadyInGroup = 'You cannot accept this invitation because you are part of another Boarding Group.' +BoardingGroupAlreadyFull = 'You cannot accept this invitation because the group is already full.' +BoardingKickOutConfirm = 'Are you sure you want to remove %s?' +BoardingPendingInvite = 'You need to deal with the\n pending invitation first.' +BoardingCannotLeaveZone = 'You cannot leave this area because you are part of a Boarding Group.' +BoardingInviteeMessage = "%s would like you to join their Boarding Group." +BoardingInvitingMessage = "Inviting %s to your Boarding Group." +BoardingInvitationRejected = "%s has rejected to join your Boarding Group." +BoardingMessageKickedOut = "You have been removed from the Boarding Group." +BoardingMessageInvited = "%s has invited %s to the Boarding Group." +BoardingMessageLeftGroup = "%s has left the Boarding Group." +BoardingMessageGroupDissolved = "Your Boarding Group was disbanded by the group leader." +BoardingMessageGroupDisbandedGeneric = "Your Boarding Group was disbanded." +BoardingMessageInvitationFailed = "%s tried to invite you to their Boarding Group." +BoardingMessageGroupFull = "%s tried to accept your invitation but your group was full." +BoardingGo = 'GO' +BoardingCancelGo = 'Click Again to\nCancel Go' +And = 'and' +BoardingGoingTo = 'Going To:' +BoardingTimeWarning = 'Boarding the elevator in ' +BoardingMore = 'more' +BoardingGoShow = 'Going to\n%s in ' +BoardingGoPreShow = 'Confirming...' + +# DistributedBossbotBoss.py +BossbotBossName = "C.E.O." +BossbotRTWelcome = "You toons will need different disguises." +BossbotRTRemoveSuit = "First take off your cog suits..." +BossbotRTFightWaiter = "and then fight these waiters." +BossbotRTWearWaiter = "Good Job! Now put on the waiters' clothes." +BossbotBossPreTwo1 = "What's taking so long? " +BossbotBossPreTwo2 = "Get cracking and serve my banquet!" +BossbotRTServeFood1 = "Hehe, serve the food I place on these conveyor belts." +BossbotRTServeFood2 = "If you serve a cog three times in a row it will explode." +BossbotResistanceToonName = "Good ol' Gil Giggles" +BossbotPhase3Speech1 = "What's happening here?!" +BossbotPhase3Speech2 = "These waiters are toons!" +BossbotPhase3Speech3 = "Get them!!!" +BossbotPhase4Speech1 = "Hrrmmpph. When I need a job done right..." +BossbotPhase4Speech2 = "I'll do it myself." +BossbotRTPhase4Speech1 = "Good Job! Now squirt the C.E.O. with the water on the tables..." +BossbotRTPhase4Speech2 = "or use golf balls to slow him down." +BossbotPitcherLeave = "Leave Bottle" +BossbotPitcherLeaving = "Leaving Bottle" +BossbotPitcherAdvice = "Use the left and right keys to rotate.\nHold down Ctrl increase power.\nRelease Ctrl to fire." +BossbotGolfSpotLeave = "Leave Golf Ball" +BossbotGolfSpotLeaving = "Leaving Golf Ball" +BossbotGolfSpotAdvice = "Use the left and right keys to rotate.\nCtrl to fire." +BossbotRewardSpeech1 = "No! The Chairman won't like this." +BossbotRewardSpeech2 = "Arrrggghhh!!!!" +BossbotRTCongratulations = "You did it! You've demoted the C.E.O.!\aHere, take these pink slips the C.E.O. left behind.\aWith it you'll be able to fire Cogs in a battle.""" +BossbotRTLastPromotion = "\aWow, you've reached level %s on your Cog Suit!\aCogs don't get promoted higher than that.\aYou can't upgrade your Cog Suit anymore, but you can certainly keep working for the Resistance!" +BossbotRTHPBoost = "\aYou've done a lot of work for the Resistance.\aThe Toon Council has decided to give you another Laff point. Congratulations!" +BossbotRTMaxed = "\aI see that you have a level %s Cog Suit. Very impressive!\aOn behalf of the Toon Council, thank you for coming back to defend more Toons!" +GolfAreaAttackTaunt = "Fore!" +OvertimeAttackTaunts = [ "It's time to reorganize.", + "Now let's downsize."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "C.E.O Battle" +ElevatorBossBotCourse0 = "The Front Three" +ElevatorBossBotCourse1 = "The Middle Six" +ElevatorBossBotCourse2 = "The Back Nine" +ElevatorCashBotBoss = "C.F.O Battle" +ElevatorCashBotMint0 = "Coin Mint" +ElevatorCashBotMint1 = "Dollar Mint" +ElevatorCashBotMint2 = "Bullion Mint" +ElevatorSellBotBoss = "Senior V.P Battle" +ElevatorSellBotFactory0 = "Front Entrance" +ElevatorSellBotFactory1 = "Side Entrance" +ElevatorLawBotBoss = "Chief Justice Battle" +ElevatorLawBotCourse0 = "Office A" +ElevatorLawBotCourse1 = "Office B" +ElevatorLawBotCourse2 = "Office C" +ElevatorLawBotCourse3 = "Office D" + + + +# CatalogNameTagItem.py +DaysToGo = "Wait\n%s Days" + +# DistributedIceGame.py +IceGameTitle = "Ice Slide" +IceGameInstructions = "Get as close to the center by the end of the second round. Use arrow keys to change direction and force. Press Ctrl to launch your toon. Hit barrels for extra points and avoid the TNT!" +IceGameInstructionsNoTnt = "Get as close to the center by the end of the second round. Use arrow keys to change direction and force. Press Ctrl to launch your toon. Hit barrels for extra points." +IceGameWaitingForPlayersToFinishMove = "Waiting for other players..." +IceGameWaitingForAISync = "Waiting for other players..." +IceGameInfo= "Match %(curMatch)d/%(numMatch)d, Round %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="Remember to press the Ctrl key!" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "Join" +PicnicTableObserveButton = "Observe" +PicnicTableCancelButton = "Cancel" +PicnicTableTutorial = "How To Play" +PicnicTableMenuTutorial = "What game do you want to learn?" +PicnicTableMenuSelect = "What game do you want to play?" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = "Get Up" +ChineseCheckersStartButton = "Start Game" +ChineseCheckersQuitButton = "Quit Game" +ChineseCheckersIts = "It's " + +ChineseCheckersYourTurn = "Your Turn" +ChineseCheckersGreenTurn = "Green's Turn" +ChineseCheckersYellowTurn = "Yellow's Turn" +ChineseCheckersPurpleTurn = "Purple's Turn" +ChineseCheckersBlueTurn = "Blue's Turn" +ChineseCheckersPinkTurn = "Pink's Turn" +ChineseCheckersRedTurn = "Red's Turn" + +ChineseCheckersColorG = "You are Green" +ChineseCheckersColorY = "You are Yellow" +ChineseCheckersColorP = "You are Purple" +ChineseCheckersColorB = "You are Blue" +ChineseCheckersColorPink = "You are Pink" +ChineseCheckersColorR = "You are Red" +ChineseCheckersColorO = "You are Observing" + +ChineseCheckersYouWon = "You just won a game of Chinese Checkers!" +ChineseCheckers = "Chinese Checkers." +ChineseCheckersGameOf = " has just won a game of " + +#GameTutorials.py +ChineseTutorialTitle1 = "Objective" +ChineseTutorialTitle2 = "How to Play" +ChineseTutorialPrev = "Previous Page" +ChineseTutorialNext = "Next Page" +ChineseTutorialDone = "Done" +ChinesePage1 = "The goal of Chinese Checkers is to be the first player to move all of your marbles from the bottom triangle across the board and into the triangle at the top. The first player to do so wins!" +ChinesePage2 = "Players take turns moving any marble of their own color. A marble can move into an adjacent hole or it can hop over other marbles. Hops must go over a marble and end in an empty hole. It is possible to chain hops together for longer moves!" + +CheckersPage1 = "The goal of Checkers is to leave the opponent without any possible moves. To do this you can either capture all of his peices or block them in such that he has no available moves." +CheckersPage2 = "Players take turns moving any peice of their own color. A peice can move one square diagonal and forward. A peice can only move into a square that is not occupied by another peice. Kings follow the same rules but are allowed to move backwards." +CheckersPage3 = "To capture an opponents peice your peice must jump over it diagonally into the vacant square beyond it. If you have any jump moves during a turn, you must do one of them. You can chain jump moves together as long as it is with the same peice." +CheckersPage4 = "A peice becomes a king when it reaches the last row on the board. A peice that has just become a king cannot continue jumping until the next turn. Additionally, kings are allowed to move all directions and are allowed to change directions while jumping." + + + +#DistributedCheckers.py +CheckersGetUpButton = "Get Up" +CheckersStartButton = "Start Game" +CheckersQuitButton = "Quit Game" + +CheckersIts = "It's " +CheckersYourTurn = "Your Turn" +CheckersWhiteTurn = "White's Turn" +CheckersBlackTurn = "Black's Turn" + +CheckersColorWhite = "You are White" +CheckersColorBlack = "You are Black" +CheckersObserver = "You are Observing" +RegularCheckers = "Checkers." +RegularCheckersGameOf = " has just won a game of " +RegularCheckersYouWon = "You just won a game of Checkers!" + +MailNotifyNewItems = "You've got mail!" +MailNewMailButton = "Mail" +MailSimpleMail = "Note" +MailFromTag = "Note From: %s" + +AwardNotifyNewItems = "You have a new award in your mailbox!" +AwardNotifyOldItems = "There are still awards waiting in your mailbox for you to pick up!" + +# MailboxScreen.py +InviteInvitation = "the invitation" +InviteAcceptInvalidError = "The invitation is no longer valid." +InviteAcceptPartyInvalid = "That party has been cancelled." +InviteAcceptAllOk = "The host has been informed of your reply." +InviteRejectAllOk = "The host has been informed that you declined the invitation." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "JANUARY", + 2: "FEBRUARY", + 3: "MARCH", + 4: "APRIL", + 5: "MAY", + 6: "JUNE", + 7: "JULY", + 8: "AUGUST", + 9: "SEPTEMBER", +10: "OCTOBER", +11: "NOVEMBER", +12: "DECEMBER" +} + +# Note 0 for Monday to match datetime +DayNames = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") +DayNamesAbbrev = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Summer Fireworks", "Celebrate Summer with a fireworks show every hour in each playground!"), + 2: ("New Year Fireworks", "Happy New Year! Enjoy a fireworks show every hour in each playground!"), + 3: ("Bloodsucker Invasion", "Happy Halloween! Stop the Bloodsucker Cogs from invading Toontown!"), + 4: ("Winter Holidays Decor", "Celebrate the Winter Holidays with Toontastic trees and streetlights!"), + 5: ("Skelecog Invasion", "Stop the Skelecogs from invading Toontown!"), + 6: ("Mr. Hollywood Invasion", "Stop the Mr. Hollywood Cogs from invading Toontown!"), + 7: ("Fish Bingo", "Fish Bingo Wednesday! Everyone at the pond works together to complete the card before time runs out."), + 8: ("Toon Species Election", "Vote on the new Toon species! Will it be Goat? Will it be Pig?"), + 9: ("Black Cat Day", "Happy Halloween! Create a Toontastic Black Cat Toon - Today Only!"), + 13: ("Trick or Treat", "Happy Halloween! Trick or treat throughout Toontown to get a nifty Halloween pumpkin head reward!"), + 14: ("Grand Prix", "Grand Prix Monday at Goofy Speedway! To win, collect the most points in three consecutive races!"), + 16: ("Grand Prix Weekend", "Free and Paid players compete in circuit races at Goofy Speedway!"), + 17: ("Trolley Tracks", "Trolley Tracks Thursday! Board any Trolley with two or more Toons to play."), + 19: ("Silly Saturdays", "Saturdays are silly with Fish Bingo, Grand Prix, and Trolley Tracks throughout the day!"), + 24: ("Ides of March", "Beware the Ides of March! Stop the Backstabber Cogs from invading Toontown!"), + 26: ("Halloween Decor", "Celebrate Halloween as spooky trees and streetlights transform Toontown!"), + 28: ("Winter Invasion", "The sellbots are on the loose spreading their cold sales tactics!"), + 29: ("April Toons' Week", "Celebrate April Toons' Week - a holiday built by Toons for Toons!"), + 33: ("Sellbot Surprise 1", "Sellbot Surprise! Stop the Cold Caller Cogs from invading Toontown!"), + 34: ("Sellbot Surprise 2", "Sellbot Surprise! Stop the Name Dropper Cogs from invading Toontown!"), + 35: ("Sellbot Surprise 3", "Sellbot Surprise! Stop the Gladhander Cogs from invading Toontown!"), + 36: ("Sellbot Surprise 4", "Sellbot Surprise! Stop the Mover & Shaker Cogs from invading Toontown!"), + 37: ("A Cashbot Conundrum 1", "A Cashbot Conundrum. Stop the Short Change Cogs from invading Toontown!"), + 38: ("A Cashbot Conundrum 2", "A Cashbot Conundrum. Stop the Penny Pincher Cogs from invading Toontown!"), + 39: ("A Cashbot Conundrum 3", "A Cashbot Conundrum. Stop the Bean Counter Cogs from invading Toontown!"), + 40: ("A Cashbot Conundrum 4", "A Cashbot Conundrum. Stop the Number Cruncher Cogs from invading Toontown!"), + 41: ("The Lawbot Gambit 1", "The Lawbot Gambit. Stop the Bottomfeeder Cogs from invading Toontown!"), + 42: ("The Lawbot Gambit 2", "The Lawbot Gambit. Stop the Double Talker Cogs from invading Toontown!"), + 43: ("The Lawbot Gambit 3", "The Lawbot Gambit. Stop the Ambulance Chaser Cogs from invading Toontown!"), + 44: ("The Lawbot Gambit 4", "The Lawbot Gambit. Stop the Backstabber Cogs from invading Toontown!"), + 45: ("The Trouble With Bossbots 1", "The Trouble with Bossbots. Stop the Flunky Cogs from invading Toontown!"), + 46: ("The Trouble With Bossbots 2", "The Trouble with Bossbots. Stop the Pencil Pusher Cogs from invading Toontown!"), + 47: ("The Trouble With Bossbots 3", "The Trouble with Bossbots. Stop the Micromanager Cogs from invading Toontown!"), + 48: ("The Trouble With Bossbots 4", "The Trouble with Bossbots. Stop the Downsizer Cogs from invading Toontown!"), + 49: ("Jellybean Day", "Celebrate Jellybean Day with double Jellybean rewards at parties!"), + 53: ("Cold Caller Invasion", "Stop the Cold Caller Cogs from invading Toontown!"), + 54: ("Bean Counter Invasion", "Stop the Bean Counter Cogs from invading Toontown!"), + 55: ("Double Talker Invasion", "Stop the Double Talker Cogs from invading Toontown!"), + 56: ("Downsizer Invasion", "Stop the Downsizer Cogs from invading Toontown!"), + 57: ("Toon Caroling", "Celebrate Winter Holiday by caroling around Toontown for a \"cool\" reward!"), + 59: ("ValenToon's Day", "Celebrate ValenToon's Day from Feb 09 to Feb 16!"), + 72: ("Yes Men Invasion", "Stop the Yes Men Cogs from invading Toontown!"), + 73: ("Tightwad Invasion", "Stop the Tightwad Cogs from invading Toontown!"), + 74: ("Telemarketers Invasion", "Stop the Telemarketer Cogs from invading Toontown!"), + 75: ("Head Hunter Invasion", "Stop the Head Hunter Cogs from invading Toontown!"), + 76: ("Spin Doctor Invasion", "Stop the Spin Doctor Cogs from invading Toontown!"), + 77: ("Moneybags Invasion", "Stop the Moneybags from invading Toontown!"), + 78: ("Two-faces Invasion", "Stop the Two-faces from invading Toontown!"), + 79: ("Mingler Invasion", "Stop the Mingler Cogs from invading Toontown!"), + 80: ("Loan Shark Invasion", "Stop the Loanshark Cogs from invading Toontown!"), + 81: ("Corporate Raider Invasion", "Stop the Corporate Raider Cogs from invading Toontown!"), + 82: ("Robber Baron Invasion", "Stop the Robber Baron Cogs from invading Toontown!"), + 83: ("Legal Eagle Invasion", "Stop the Legal Eagle Cogs from invading Toontown!"), + 84: ("Big Wig Invasion", "Stop the Big Wig Cogs from invading Toontown!"), + 85: ("Big Cheese Invasion", "Stop the Big Cheese from invading Toontown!"), + 86: ("Down Sizer Invasion", "Stop the Down Sizer Cogs from invading Toontown!"), + 87: ("Mover And Shaker Invasion", "Stop the Mover and Shaker Cogs from invading Toontown!"), + 88: ("Double Talker Invasion", "Stop the Double Talkers Cogs from invading Toontown!"), + 89: ("Penny Pincher Invasion", "Stop the Penny Pinchers Cogs from invading Toontown!"), + 90: ("Name Dropper Invasion", "Stop the Name Dropper Cogs from invading Toontown!"), + 91: ("Ambulance Chaser Invasion", "Stop the Ambulance Chaser Cogs from invading Toontown!"), + 92: ("Micro Manager Invasion", "Stop the Micro Manager Cogs from invading Toontown!"), + 93: ("Number Cruncher Invasion", "Stop the Number Cruncher Cogs from invading Toontown!"), + 95: ("Victory Parties", "Celebrate our historic triumph against the Cogs!"), # placeholder + } + +UnknownHoliday = "Unknown Holiday %d" +HolidayFormat = "%b %d " + +# parties/ToontownTimeManager.py +TimeZone = "US/Pacific" + +# Cogdo Boardroom Game +BoardroomGameTitle = "Boardroom Hijinks" +BoardroomGameInstructions = ("The COGS are having a meeting to decide what to do with stolen gags. " + "Slide on through and grab as many gag-destruction memos as you can!") + +# Cogdo Crane Game +CogdoCraneGameTitle = "Vend-A-Stomper" +CogdoCraneGameInstructions = ("The COGS are using a coin-operated machine to destroy laff barrels. " + "Use the cranes to pick up and throw money bags, in order to prevent " + "barrel destruction!") + +# Silly Surge Terms +SillySurgeTerms = { + 1: "Amusing Ascent!", + 2: "Silly Surge!", + 3: "Ridiculous Rise!", + 4: "Giggle Growth!", + 5: "Funny Fueling!", + 6: "Batty Boost!", + 7: "Crazy Climb!", + 8: "Jolly Jump!", + 9: "Loony Lift!", + 10: "Hilarity Hike!", + 11: "Insanity Increase!", + 12: "Cracked-Uptick!" + } +# Interactive Prop Text +InteractivePropTrackBonusTerms = { + 0: "Super Toon-Up!", + 1: "", + 2: "", + 3: "", + 4: "Super Throw!", + 5: "Super Squirt!", + 6: "", +} + +PlayingCardUnknown = "Card Name is unknown" diff --git a/toontown/src/toonbase/TTLocalizerEnglishProperty.py b/toontown/src/toonbase/TTLocalizerEnglishProperty.py new file mode 100644 index 0000000..0796076 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizerEnglishProperty.py @@ -0,0 +1,511 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.2 + +#battle/RewardPanel.py +RPdirectFrame = (1.75, 1, 0.75) +RPtrackLabels = 0.05 +RPmeritBarLabels = 0.165 + +#battle/RewardPanel.py +RPmeritLabelXPosition = 0.55 +RPmeritBarsXPosition = 0.825 + +#battle/BattleBase.py +BBbattleInputTimeout = 20.0 + +#battle/FireCogPanel.py +FCPtextFrameScale = 0.08 + +#building/DistributedHQInterior.py +DHtoonName = 0.9 +DHtoonNamePos = (-4,0,0) +DHscorePos = (-4.6,0,0) +DHtrophyPos = (-6.6,0,0.3) + +#building/Elevator.py +EelevatorHopOff = 0.8 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogItemPanel.py +CIPnameLabel = 1 +CIPwordwrapOffset = 0 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.13) +CSgiftToggle = 0.08 +CSbackCatalogButton = 0.065 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = .055 +CISCtopLevelOverlap = 0. + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 18 +CMunpaidChatWarning = 0.06 +CMunpaidChatWarning_text_z = 0.30 +CMpayButton = 0.06 +CMpayButton_pos_z = -0.13 +CMopenChatWarning = 0.06 +CMactivateChat = 0.05 +CMchatActivated = 0.06 +CMNoPasswordContinue_z = -0.28 + +#coghq/LawbotCogHQLoader.py +LCLdgSign = 0.1 # the scale of the gate name + +#coghq/SellbotCogHQLoader.py +SCLfdSign = .075 +SCLdgSign = 0.1 # the scale of the gate name + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 0.8 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 2 + +#coghq/BossbotCogHQLoader.py +BCHQLmakeSign = 1.12 + +#coghq/DistributedGolfGreenGame.py +DGGGquitButton = 0.045 +DGGGhowToButton = 0.045 +DGGGscoreLabel = 0.075 + +#estate/houseDesign.py +HDhelpText = 0.8 +HDatticButton = 1.0 +HDroomButton = 1.0 +HDtrashButton = 1.0 +HDscrolledList = 0.1 + +#estate/PlantingGUI.py +GardeningInstructionScale = 0.07 + +#estate/FlowerPanel.py +FPBlankLabelPos = -0.35 +FPBlankLabelTextScale = 0.05 + +#estate/FlowerPicker.py +FPFlowerValueTotal = 0.055 + +#estate/FlowerSellGUI.py +FSGDFTextScale = .06 +FSGCancelBtnTextScale = 0.06 +FSGOkBtnTextScale = 0.06 + +#estate/GardenTutorial.py +GardenTutorialPage2Wordwrap = 13.5 +GardenTutorialPage4Wordwrap = 16.5 + +#fishing/BingoCardGui.py +BCGjpText = (0.04) +BCGjpTextWordwrap = 10.5 +BCGnextGame = 1.71 + +#fishing/FishSellGUI.py +FSGokButton = 0.06 +FSGcancelButton = 0.06 + +#fishing/FishPanel.py +FPnewEntry = 0.08 +FPnewRecord = 0.08 + +#fishing/GenusPanel.py +GPgenus = 0.045 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.045 +FLPsecrets = 0.045 +FLPsecretsPos = (0.152, 0.0, 0.14) +FLPtitleScale = 0.04 + +#friends/FriendInviter.py +FIstopButton = 0.05 +FIdialog = 0.06 +FIcancelButtonPosition = (0.20, 0.0, -0.1) +FIcancelButtonPositionX = 0.20 +FIstopButtonPosition = (-0.2, 0.0, 0.05) +FIstopButtonPositionX = -0.2 +FIstopTextPosition = (0.075, -0.015) +FIstopTextPositionY = -0.015 +FIyesButtonPositionX = -0.15 +FIdirectFrameTextWorkWrap = 14 +FIdirectFrameTextPosZ = 0.2 + +#golf/DistributedGolfHole.py +DGHpowerReminder = 0.09 +DGHaimInstructions = 0.065 +DGHteeInstructions = 0.065 + +#golf/DistributedGolfHole.py +DGHAimInstructScale = 0.1 +DGHTeeInstructScale = 0.1 + +#golf/GolfScoreBoard.py +GSBExitCourseBTextPose = (0.15, -.01) +GSBtitleLabelScale = 0.07 + +#golf/GolfScoreBoard.py +GSBexitCourseBPos = (0.19, -.01) +GSBtitleLabel = 0.06 + +#hood/EstateHood.py +EHpopupInfo = .08 + +#hood/Hood.py +HDenterTitleTextScale = 0.16 + +#login/AvatarChoice.py +ACplayThisToon = 0.12 +ACmakeAToon = 0.12 +ACsubscribersOnly = 0.115 +ACdeleteWithPassword = 0.06 +ACstatusText = 1.0 + +#login/AvatarChooser.py +ACtitle = 0.15 +ACquitButton = 0.1 +AClogoutButton = 0.1 +ACquitButton_pos = -0.035 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.1 +MASPnameText = 0.055 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.055 +MRPinstructionsText = 0.07 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.07 +MPMtooSlow = 0.07 +MPMtooFast = 0.07 +MPMgaugeA = .35 +MPMgaugeTargetTop = .35 +MPMgaugeTargetBot = .35 + +#minigame/Purchase.py +PstatusLabel = 0.08 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.08 + +#makeatoon/NameShop.py +NSmaxNameWidth = 8.0 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.1 +NSmakeCheckBox = 0.8 +NSnameEntry = 0.08 +NStypeANameButton = 0.06 +NStypeANameButton_pos = -0.02 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.08 +NScolorPrecede = True + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.18 +MATenterBodyShop = 0.18 +MATenterColorShop = 0.18 +MATenterClothesShop = 0.16 +MATenterNameShop = 0.15 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.08 + +#makeatoon\ShuffleButton.py +SBshuffleBtn = 0.08 + +#minigame/DistributedPairingGame.py +DPGPointsFrameTextScale = 0.7 +DPGFlipsFrameTextScale = 0.7 + +#minigame/DistributedTravelGame.py +DTGVoteBtnTextScale = 0.07 +DTGUseLabelTextScale = 0.1 +DTGVotesPeriodLabelTextScale = 0.1 +DTGVotesToGoLabelTextScale = 0.1 +DTGUpLabelTextScale = 0.125 +DTGDownLabelTextScale = 0.125 +DTGRemainingVotesFrameTextScale = 0.7 + +#minigame/MinigameRulesPanel.py +MRPGameTitleTextScale = 0.11 +MRPGameTitleTextPos = (-0.046, 0.2, 0.092) +MRPInstructionsTextWordwrap = 26.5 +MRPInstructionsTextPos = (-0.115, 0.05, 0) + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.65 + +#parties/InviteVisual.py +IVwhenTextLabel = 0.06 +IVactivityTextLabel = 0.06 + +#parties/PartyPlanner.py +PPDescriptionScale = 0.06 +PPelementTitleLabelScale = 0.07 +PPelementBuyButtonTextScale = 0.055 +PPtitleScale = 0.1 +PPpbulicDescriptionLabel = 0.065 +PPprivateDescriptionLabel = 0.065 +PPpublicButton = 0.05 +PPprivateButton = 0.05 +PPcostLabel = 0.065 +PPpartyGroundsLabel = 1.0 +PPinstructionLabel = 0.07 +PPelementPrice = 0.065 + +#parties/DistributedParty.py +DPpartyCountdownClockTextScale = 1.1 +DPpartyCountdownClockMinutesScale = 1.1 +DPpartyCountdownClockColonScale = 1.1 +DPpartyCountdownClockSecondScale = 1.1 +DPpartyCountdownClockMinutesPosY = 0.0 +DPpartyCountdownClockColonPosY = 0.0 +DPpartyCountdownClockSecondPosY = 0.0 + +#parties/PublicPartyGui.py +PPGpartyStartButton = 0.065 +PPGinstructionsLabel = 0.065 +PPGcreatePartyListAndLabel = 0.06 + +#parties/JukeboxGui.py +JGcurrentlyPlayingLabel = 0.07 +JGsongNameLabel = 0.13 +JGaddSongButton = 0.1 +JGnumItemsVisible = 9 +JGlistItem = 1.0 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.5 +PAPcall = 0.5 +PAPowner = 0.35 +PAPscratch = 0.5 +PAPstateLabel = 0.4 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.17 +PDPlaff = 0.17 +PDPlaffPos = (0.0,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 1 +PGUIchooserTitle = .10 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = .07 +PGUIpetsopAdopt = 0.6 +PGUIadoptSubmit = 0.8 +PGUIpetsopAdoptPos = (-0.21,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.13 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.16) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.04 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0625 + +#racing/DistributedLeaderBoard.py +DLBtitleRowScale = .4 + +#racing/DistributedRace.py +DRenterWaiting = .2 +DRrollScale = .5 + +#raceing/DistributedRacePad.py +DRPnodeScale = 0.65 + +#racing/KartShopGui.py +KSGtextSizeBig = 0.088 +KSGtextSizeSmall = 0.055 +KSGaccDescriptionWordwrap = 11 + +#racing/RaceEndPanels.py +REPraceEnd = 0.08 +REPraceExit = 0.04 +REPticket_text_x = -0.6 + +#racing/RaceGUI.py +RGphotoFinish = 0.25 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#racing/DistributedRaceAI.py +DRAwaitingForJoin = 60 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.06 + +#safezone/Playground.py +PimgLabel = 1.0 + +#safezone/GZSafeZoneLoader.py +GSZLbossbotSignScale = 1.5 + +#shtiker/EventsPage.py +EPtitleLabel = 0.12 +EPhostTab = 0.07 +EPinvitedTab = 0.07 +EPcalendarTab = 0.07 +EPnewsTab = 0.07 +EPhostingCancelButton = 0.04 +EPhostingDateLabel = 0.05 +EPpartyGoButton = 0.045 +EPpublicPrivateLabel = 0.05 +EPpublicButton= 0.5 +EPprivateButton = 0.5 +EPinvitePartyGoButton = 0.045 +EPdecorationItemLabel = 0.055 +EPactivityItemLabel = 0.055 +EPcreateListAndLabel = 0.055 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDembeddedButtonPos = (0.097, 0, -0.411) +DSDcancel = 0.06 +DSDcancelButtonPositionX = 0 + +#shtiker/DisguisePage.py +DPtab = 0.1 +DPdeptLabel = 0.17 +DPcogName = 0.093 + +#shtiker/TrackPage.py +TPstartFrame = 0.12 +TPendFrame = 0.12 + +#shtiker/ShtikerBook.py +SBpageTab = 0.75 + +#shtiker/OptionsPage.py +OPoptionsTab = 0.07 +OPCodesInstructionPanelTextPos = (0, -0.01) +OPCodesInstructionPanelTextWordWrap = 6 +OPCodesResultPanelTextPos = (0, .35) +OPCodesResultPanelTextScale = 0.06 +OPCodesResultPanelTextWordWrap = 9 +OPCodesInputTextScale = 0.8 +OPCodesSubmitTextScale = 0.07 +OPCodesSubmitTextPos = (0, -0.02) + +#shtiker/MapPage.py +MPbackToPlayground = 0.055 +MPgoHome = 0.055 +MPhoodLabel = 0.06 +MPhoodWordwrap = 14 + +#shtiker/KartPage.py +KPkartTab = 0.07 +KPdeleteButton = 0.06 +KProtateButton = 0.035 + +#shtiker/GardenPage.py +GPBasketTabTextScale = 0.07 +GPCollectionTabTextScale = 0.07 +GPTrophyTabTextScale = 0.07 +GPSpecialsTabTextScale = 0.07 + +#shtiker/GolfPage.py +GFPRecordsTabTextScale = 0.07 +GFPRecordsTabPos = (0.92, 0, 0.1) +GFPTrophyTabTextScale = 0.07 +GFPRecordsTabTextPos = (0.03, 0.0, 0.0) +GFPRTrophyTabPos = (0.92, 0, -0.3) + +#toon/AvatarPanelBase.py +APBignorePanelAddIgnoreTextScale = 0.06 +APBignorePanelTitlePosY = 0 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.045 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 +TAPgroupFrameScale = 0.05 +TAPgroupButtonScale = 0.055 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.066 +TADPcancelButton = 0.05 + +#toon/GroupPanel.py +GPdestFrameScale = 0.05 +GPdestScrollListScale = 0.05 +GPgoButtonScale = 0.06 + +#toon/InventoryNew.py +INtrackNameLabels = 0.05 +INclickToAttack = 1.0 +INpassButton = 0.05 +INrunButton = 0.05 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 1.0 + +#toon/PlayerInfoPanel.py +PIPsecretsButtonScale=0.045 +PIPwisperButton = 0.06 +PIPdetailButton = 0.05 + +#toon/ToonAvatarPanel.py +TAPsecretsButtonScale=0.045 +TAPwisperButtonScale=0.06 + +#toon/ToonAvatarDetailPanel.py +TADPcancelPos = (-0.865, 0.0, -0.78) +TADtrackLabelPosZ = 0.08 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.18 + +#toontowngui/TeaserPanel.py +TSRPdialogWordwrap = 22 +TSRPtop = 0.05 +TSRPpanelScale = 0.08 +TSRPpanelPos = (0., -0.7) +TSRPbrowserPosZ = -0.45 +TSRPbutton = 0.05 +TSRPteaserBottomScale = 0.06 +TSRPhaveFunText = 0.1 +TSRPjoinUsText = 0.1 + +#toontowngui/TeaserPanel.py (OLD) +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.1 + +#trolley/Trolley.py +TtrolleyHopOff = 0.8 diff --git a/toontown/src/toonbase/TTLocalizerFrench.py b/toontown/src/toonbase/TTLocalizerFrench.py new file mode 100644 index 0000000..ec397a8 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizerFrench.py @@ -0,0 +1,8957 @@ +import string +from LocalizerFrenchProperty import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BoldFont = 'phase_3/models/fonts/MickeyFont' +BoldShadow = None + +# common names +Mickey = "Mickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Dingo" +Pluto = "Pluto" +Flippy = "Flippy" + +# common locations +lTheBrrrgh = 'Glagla' +lDaisyGardens = 'Jardin de Daisy' +lDaisyGardensNC = 'jardin de Daisy' +lDonaldsDock = 'Quais Donald' +lDonaldsDockNC = 'quais Donald' +lDonaldsDreamland = 'Pays des rêves de Donald' +lMinniesMelodyland = 'Pays musical de Minnie' +lToontownCentral = 'Toontown centre' +lToonHQ = 'QG des Toons' + +# common strings +lCancel = 'Annuler' +lClose = 'Fermer' +lOK = 'OK' +lNext = 'Suivant' +lQuit = 'Quitter' +lYes = 'Oui' +lNo = 'Non' + +lHQOfficerF = 'Officier QG' +lHQOfficerM = 'Officier QG' + +MickeyMouse = "Mickey" + +AIStartDefaultDistrict = "Idioville" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "un Cog" +TheCogs = "Les Cogs" +theCogs = "les Cogs" +Skeleton = "Skelecog" +SkeletonP = "Skelecogs" +ASkeleton = "un Skelecog" +Foreman = "Contremaître de l'usine" +ForemanP = "Contremaîtres de l'usine" +AForeman = "un contremaître de l'usine" +CogVP = "Vice-Président " + Cog +CogVPs = "Vice-Présidents " + Cogs +ACogVP = "Un Vice-Président " + Cog + +# Quests.py +TheFish = "les poissons" +AFish = "un poisson" +Level = "niveau" +QuestsCompleteString = "Terminé" +QuestsNotChosenString = "Non choisi" +Period = "." + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Bonjour, _avName_!", + "Ohé, _avName_!", + "Coucou, _avName_!", + "Eh, _avName_!", + "Bienvenue, _avName_!", + "Salut, _avName_!", + "Comment ça va, _avName_?", + "Quoi de neuf, _avName_?", + ) +QuestsDefaultIncomplete = ("Comment est-ce que ce défi se présente, _avName_?", + "On dirait que tu as encore du travail à faire pour ce défi!", + "Continue à bien travailler, _avName_!", + "Essaie de finir ce défi. Je sais que tu peux le faire!", + "Essaie de terminer ce défi, nous comptons sur toi!", + "Continue à travailler sur ce défitoon!", + ) +QuestsDefaultIncompleteProgress = ("Tu es au bon endroit, mais tu dois d'abord finir ton défitoon.", + "Quand tu auras terminé ton défitoon, reviens ici.", + "Reviens quand tu auras terminé ton défitoon.", + ) +QuestsDefaultIncompleteWrongNPC = ("Joli travail pour ce défitoon. Tu devrais aller voir _toNpcName_._where_", + "On dirait que tu as presque fini ton défitoon. Va voir _toNpcName_._where_.", + "Va voir _toNpcName_ pour finir ton défitoon._where_", + ) +QuestsDefaultComplete = ("Bon travail! Voilà ta récompense...", + "Super boulot, _avName_! Prends cette récompense...", + "Excellent boulot, _avName_! Voilà ta récompense...", + ) +QuestsDefaultLeaving = ("Salut!", + "Au revoir!", + "Bon vent, _avName_!", + "À plus, _avName_!", + "Bonne chance!", + "Amuse-toi bien à Toontown!", + "À plus tard!", + ) +QuestsDefaultReject = ("Bonjour.", + "Puis-je t'aider?", + "Comment ça va?", + "Bien le bonjour.", + "Je suis un peu occupé là, _avName_.", + "Oui?", + "Salut, _avName_!", + "Bienvenue, _avName_!", + "Hé, _avName_! Comment ça va?", + # Game Hints + "Sais-tu que tu peux ouvrir ton journal de bord en appuyant sur la touche F8?", + "Tu peux utiliser ta carte pour te téléporter jusqu'au terrain de jeux!", + "Tu peux devenir ami(e) avec les autres joueurs en cliquant sur eux.", + "Tu peux en savoir plus sur un " + Cog + " en cliquant sur lui." + "Trouve des trésors sur les terrains de jeux pour remplir ton rigolmètre.", + "Les immeubles " + Cog + " sont dangereux! N'y va pas tout seul!", + "Lorsque tu perds un combat, les " + Cogs + " prennent tous tes gags." + "Pour avoir plus de gags, joue aux jeux du tramway!", + "Tu peux accumuler des rigolpoints en effectuant des défitoons.", + "Chaque défitoon te vaudra une récompense.", + "Certaines récompenses te permettent d'avoir plus de gags.", + "Si tu gagnes un combat, ton défitoon est crédité pour chaque " + Cog + " vaincu." + "Si tu regagnes un bâtiment " + Cog + ", retourne à l'intérieur pour recevoir un remerciement spécial de la part de son propriétaire!" + "Si tu appuies sur la touche \" page précédente \", tu peux regarder vers le haut!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Pour montrer à tes amis secrets ce que tu penses, entre un '.' avant ta pensée.", + "Si un " + Cog + " est assommé, il lui est plus difficile d'éviter les objets qui tombent." + "Chaque type de bâtiment " + Cog + " a un aspect différent." + "Tu obtiens plus de récompenses d'habileté si tu vaincs des " + Cogs + " aux plus hauts étages des bâtiments." + ) +QuestsDefaultTierNotDone = ("Bonjour, _avName_! Tu dois terminer tes défitoons commencés avant d'en obtenir un autre.", + "Salut! Tu dois terminer les défitoons sur lesquels tu es en train de travailler pour en obtenir un nouveau.", + "Ohé, _avName_! Pour que je puisse te donner un autre défitoon, tu dois finir ceux que tu as déjà.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("J'ai entendu dire que _toNpcName_ te cherchait._where_", + "Arrête-toi voir _toNpcName_ quand tu pourras._where_", + "Va donc voir _toNpcName_ la prochaine fois que tu passes par là-bas._where_", + "Si tu peux, arrête-toi dire bonjour à _toNpcName_._where_", + "_toNpcName_ va te donner ton prochain défitoon._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogQuestHeadline = "RECHERCHE" +QuestsCogQuestSCStringS = "Je dois vaincre %(cogName)s%(cogLoc)s" +QuestsCogQuestSCStringP = "Je dois vaincre quelques %(cogName)s%(cogLoc)s" +QuestsCogQuestDefeat = "Tu dois vaincre %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewbieQuestObjective = "Aide un nouveau Toon à vaincre %s" +QuestsCogNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins" +QuestsCogNewbieQuestAux = "Tu dois vaincre :" +QuestsNewbieQuestHeadline = "APPRENTI" + +QuestsCogTrackQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogTrackQuestHeadline = "RECHERCHE" +QuestsCogTrackQuestSCStringS = "Je dois vaincre %(cogText)s%(cogLoc)s" +QuestsCogTrackQuestSCStringP = "Je dois vaincre quelques %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Tu dois vaincre %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogLevelQuestHeadline = "RECHERCHE" +QuestsCogLevelQuestDefeat = "Tu dois vaincre %s" +QuestsCogLevelQuestDesc = "un Cog de niveau %(level)s+" +QuestsCogLevelQuestDescC = "%(count)s les Cogs de niveau %(level)s+" +QuestsCogLevelQuestDescI = "des Cogs de niveau %(level)s+" +QuestsCogLevelQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'deux+', 'trois+', 'quatre+', 'cinq+') +QuestsBuildingQuestBuilding = "Bâtiment" +QuestsBuildingQuestBuildings = "Bâtiments" +QuestsBuildingQuestHeadline = "VAINCRE" +QuestsBuildingQuestProgressString = "%(progress)s sur %(num)s sont vaincus" +QuestsBuildingQuestString = "Tu dois vaincre %s" +QuestsBuildingQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "un bâtiment %(type)s" +QuestsBuildingQuestDescF = "un bâtiment %(type)s de %(floors)s étages" +QuestsBuildingQuestDescC = "%(count)s bâtiments %(type)s " +QuestsBuildingQuestDescCF = "%(count)s bâtiments %(type)s de %(floors)s étages" +QuestsBuildingQuestDescI = "des bâtiments %(type)s" +QuestsBuildingQuestDescIF = "des bâtiments %(type)s de %(floors)s étages" + +QuestFactoryQuestFactory = "Usine" +QuestsFactoryQuestFactories = "Usines" +QuestsFactoryQuestHeadline = "VAINCRE" +QuestsFactoryQuestProgressString = "%(progress)s sur%(num)s sont vaincus" +QuestsFactoryQuestString = "Tu dois vaincre %s" +QuestsFactoryQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "une usine %(type)s" +QuestsFactoryQuestDescC = "%(count)s usines %(type)s " +QuestsFactoryQuestDescI = "des usines %(type)s" + +QuestsRescueQuestProgress = "%(progress)s sur %(numToons)s sont sauvés" +QuestsRescueQuestHeadline = "SAUVER" +QuestsRescueQuestSCStringS = "Je dois sauver un Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "Je dois sauver des Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Tu dois sauver %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "un Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Tu dois sauver :" + +QuestsRescueNewbieQuestObjective = "Aide un nouveau Toon à sauver %s" + +QuestCogPartQuestCogPart = "Pièce de costume de Cog" +QuestsCogPartQuestFactories = "Usines" +QuestsCogPartQuestHeadline = "RÉCUPÉRER" +QuestsCogPartQuestProgressString = "%(progress)s sur %(num)s sont récupérés" +QuestsCogPartQuestString = "Récupérer %s" +QuestsCogPartQuestSCString = "Je dois récupérer %(objective)s%(location)s." +QuestsCogPartQuestAux = "Tu dois récupérer :" + +QuestsCogPartQuestDesc = "une pièce de costume de Cog" +QuestsCogPartQuestDescC = "%(count)s pièces de costume de Cog" +QuestsCogPartQuestDescI = "des pièces de costume de Cog" + +QuestsCogPartNewbieQuestObjective = 'Aide un nouveau Toon à récupérer %s' + +QuestsDeliverGagQuestProgress = "%(progress)s sur %(numGags)s sont livrés" +QuestsDeliverGagQuestHeadline = "LIVRER" +QuestsDeliverGagQuestToSCStringS = "Je dois livrer %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Je dois livrer des %(gagName)s." +QuestsDeliverGagQuestSCString = "Je dois faire une livraison." +QuestsDeliverGagQuestString = "Tu dois livrer %s" +QuestsDeliverGagQuestStringLong = "Tu dois livrer %s à _toNpcName_." +QuestsDeliverGagQuestInstructions = "Tu pourras acheter ce gag à la Boutique à gags une fois que tu en auras gagné le droit." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "LIVRER" +QuestsDeliverItemQuestSCString = "Je dois livrer %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Tu dois livrer %s" +QuestsDeliverItemQuestStringLong = "Tu dois livrer %s à _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITER" +QuestsVisitQuestStringShort = "Tu dois rendre visite" +QuestsVisitQuestStringLong = "Rends visite à _toNpcName_" +QuestsVisitQuestSeeSCString = "Je dois voir %s." + +QuestsRecoverItemQuestProgress = "%(progress)s sur %(numItems)s sont repris" +QuestsRecoverItemQuestHeadline = "REPRENDRE" +QuestsRecoverItemQuestSeeHQSCString = "Je dois voir un officier du QG" +QuestsRecoverItemQuestReturnToHQSCString = "Je dois rendre %s à un officier du QG." +QuestsRecoverItemQuestReturnToSCString = "Je dois rendre %(item)s à %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Je dois aller à un QG des Toons." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Je dois aller au terrain de jeux de %s." +QuestsRecoverItemQuestGoToStreetSCString = "Je dois aller %(to)s %(street)s dans %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Je dois rendre visite à %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Où est %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Je dois reprendre %(item)s à %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Reprendre %(item)s à %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "CHOISIR" +QuestsTrackChoiceQuestSCString = "Je dois choisir entre %(trackA)s et %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Je devrais peut-être choisir %s." +QuestsTrackChoiceQuestString = "Choisis entre %(trackA)s et %(trackB)s." + +QuestsFriendQuestHeadline = "AMI" +QuestsFriendQuestSCString = "Je dois trouver un(e) ami(e)." +QuestsFriendQuestString = "Trouve un(e) ami(e)" + +QuestsFriendNewbieQuestString = " Trouve %d amis de %d rigolpoints ou moins" +QuestsFriendNewbieQuestProgress = "%(progress)s sur %(numFriends)s sont trouvés" +QuestsFriendNewbieQuestObjective = "Deviens ami(e) avec %d nouveaux Toons" + +QuestsTrolleyQuestHeadline = "TRAMWAY" +QuestsTrolleyQuestSCString = "Je dois faire un tour de tramway." +QuestsTrolleyQuestString = "Fais un tour de tramway" +QuestsTrolleyQuestStringShort = "Prends le tramway" + +QuestsMinigameNewbieQuestString = "%d Mini-jeux" +QuestsMinigameNewbieQuestProgress = "%(progress)s sur %(numMinigames)s ont été joués" +QuestsMinigameNewbieQuestObjective = "Jouer à %d mini-jeux avec de nouveaux Toons" +QuestsMinigameNewbieQuestSCString = "Je dois jouer aux mini-jeux avec de nouveaux Toons." +QuestsMinigameNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins" +QuestsMinigameNewbieQuestAux = "Tu dois jouer :" + +QuestsMaxHpReward = "Ta rigo-limite a été augmentée de %s." +QuestsMaxHpRewardPoster = "Récompense : Rigol-augmentation de %s points" + +QuestsMoneyRewardSingular = "Tu obtiens 1 bonbon." +QuestsMoneyRewardPlural = "Tu obtiens %s bonbons." +QuestsMoneyRewardPosterSingular = "Récompense : 1 bonbon" +QuestsMoneyRewardPosterPlural = "Récompense : %s bonbons" + +QuestsMaxMoneyRewardSingular = "Tu peux maintenant avoir 1 bonbon." +QuestsMaxMoneyRewardPlural = ".Tu peux maintenant avoir %s bonbons" +QuestsMaxMoneyRewardPosterSingular = "Récompense : Tu as 1 bonbon" +QuestsMaxMoneyRewardPosterPlural = "Récompense : Tu as %s bonbons" + +QuestsMaxGagCarryReward = "Tu as un %(name)s. Tu peux maintenant avoir %(num)s gags." +QuestsMaxGagCarryRewardPoster = "Récompense : (%(num)s) %(name)s" + +QuestsMaxQuestCarryReward = " Tu peux maintenant avoir %s défitoons." +QuestsMaxQuestCarryRewardPoster = "Récompense : Tu as %s défitoons" + +QuestsTeleportReward = "Tu peux maintenant accéder par téléportation à %s." +QuestsTeleportRewardPoster = "Récompense : Accès par téléportation à %s" + +QuestsTrackTrainingReward = "Tu peux maintenant t'entraîner pour les gags \"%s\"." +QuestsTrackTrainingRewardPoster = "Récompense : Entraînement aux gags" + +QuestsTrackProgressReward = "Tu as maintenant l'image %(frameNum)s de l'animation de la série %(trackName)s." +QuestsTrackProgressRewardPoster = "Récompense : image %(frameNum)s de l'animation de la série \"%(trackName)s\"" + +QuestsTrackCompleteReward = "Tu peux maintenant avoir et utiliser des gags \"%s\"." +QuestsTrackCompleteRewardPoster = "Récompense : Entraînement final aux séries %s" + +QuestsClothingTicketReward = "Tu peux changer de vêtements" +QuestsClothingTicketRewardPoster = "Récompense : Ticket d'habillement" + +QuestsCheesyEffectRewardPoster = "Récompense : %s" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "sur ce terrain de jeux" +QuestsStreetLocationThisStreet = "sur cette rue" +QuestsStreetLocationNamedPlayground = "sur le terrain de jeux de %s" +QuestsStreetLocationNamedStreet = "sur %(toStreetName)s dans %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "Le bâtiment de %s est appelé" +QuestsLocationBuildingVerb = "qui est " +QuestsLocationParagraph = "\a\"%(buildingName)s \" %(building)s...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Je dois terminer un défitoon." + +# MaxGagCarryReward names +QuestsMediumPouch = "Bourse moyenne" +QuestsLargePouch = "Grande bourse" +QuestsSmallBag = "Petit sac" +QuestsMediumBag = "Sac moyen" +QuestsLargeBag = "Grand sac" +QuestsSmallBackpack = "Petit sac à dos" +QuestsMediumBackpack = "Sac à dos moyen" +QuestsLargeBackpack = "Grand sac à dos" +QuestsItemDict = { + 1 : ["Paire de lunettes", "Paires de lunettes", "une"], + 2 : ["Clé", "Clés", "une "], + 3 : ["Tableau", "Tableaux", "un "], + 4 : ["Livre", "Livres", "un "], + 5 : ["Sucre d'orge", "Sucres d'orge", "un "], + 6 : ["Craie", "Craies", "une "], + 7 : ["Recette", "Recettes", "une "], + 8 : ["Note", "Notes", "une "], + 9 : ["Machine à calculer", "Machines à calculer", "une "], + 10 : ["Pneu de voiture de clown", "Pneus de voiture de clown", "un "], + 11 : ["Pompe à air", "Pompes à air ", "une "], + 12 : ["Encre de seiche", "Encres de seiche", "de l'"], + 13 : ["Paquet", "Paquets", "un "], + 14 : ["Reçu de poisson doré", "Reçus de poissons dorés", "un "], + 15 : ["Poisson doré", "Poissons dorés", "un "], + 16 : ["Huile", "Huiles", "de l'"], + 17 : ["Graisse", "Graisses", "de la "], + 18 : ["Eau", "Eaux", "de l'"], + 19 : ["Rapport de pignons", "Rapports de pignons", "un "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Ticket d'habillement", "Tickets d'habillement", "un "], + + # Donald's Dock quest items + 2001 : ["Chambre à air", "Chambres à air", "une "], + 2002 : ["Ordonnance de monocle", "Ordonnances de monocles", "une "], + 2003 : ["Monture de monocle", "Montures de monocles", "une "], + 2004 : ["Monocle", "Monocles", "un "], + 2005 : ["Grande perruque blanche", "Grandes perruques blanches", "une "], + 2006 : ["Boisseau de lest", "Boisseaux de lest", "un "], + 2007 : ["Équipement de Cog", "Équipements de Cog", "un "], + 2008 : ["Carte marine", "Cartes marines", "une "], + 2009 : ["Manille crado", "Manilles crados", "un "], + 2010 : ["Manille propre", "Manilles propres", "un "], + 2011 : ["Ressort d'horloge", "Ressorts d'horloge", "un "], + 2012 : ["Contrepoids", "Contrepoids", "un "], + + # Minnie's Melodyland quest items + 4001 : ["Inventaire de Tina", "Inventaires de Tina", ""], + 4002 : ["Inventaire de Yuki", "Inventaires de Yuki", ""], + 4003 : ["Formulaire d'inventaire", "Formulaires d'inventaire", "un "], + 4004 : ["Inventaire de Fifi", "Inventaires de Fifi", ""], + 4005 : ["Ticket de Jack Bûcheron", "Tickets de Jack Bûcheron", ""], + 4006 : ["Ticket de Tabatha", "Tickets de Tabatha", ""], + 4007 : ["Ticket de Barry", "Tickets de Barry", ""], + 4008 : ["Castagnette ternie", "Castagnettes ternies", ""], + 4009 : ["Encre de seiche bleue", "Encre de seiche bleue", "de l'"], + 4010 : ["Castagnette brillante", "Castagnettes brillantes", "une "], + 4011 : ["Paroles de Léo", "Paroles de Léo", ""], + + # Daisy's Gardens quest items + 5001 : ["Cravate en soie", "Cravates en soie", "une "], + 5002 : ["Costume à rayures", "Costumes à rayures", "un "], + 5003 : ["Paire de ciseaux", "Paires de ciseaux", "une "], + 5004 : ["Carte postale", "Cartes postales", "une "], + 5005 : ["Crayon", "Crayons", "un "], + 5006 : ["Encrier", "Encriers", "un "], + 5007 : ["Bloc-notes", "Blocs-notes", "un "], + 5008 : ["Coffre de bureau", "Coffres de bureau", "un "], + 5009 : ["Sac de graines pour oiseaux", "Sacs de graines pour oiseaux", "un "], + 5010 : ["Pignon", "Pignons", "un "], + 5011 : ["Salade", "Salades", "une "], + 5012 : ["Clé du "+lDaisyGardensNC, "Clés du "+lDaisyGardensNC, "une "], + 5013 : ["Plans du QG Vendibot", "Plans du QG Vendibot", "des "], + 5014 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5015 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5016 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5017 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + + # The Brrrgh quests + 3001 : ["Ballon de foot", "Ballons de foot", "un "], + 3002 : ["Luge", "Luges", "une "], + 3003 : ["Glaçon", "Glaçons", "un "], + 3004 : ["Lettre d'amour", "Lettres d'amour", "une "], + 3005 : ["Teckel", "Teckels", "un "], + 3006 : ["Bague de fiançailles", "Bagues de fiançailles", "une "], + 3007 : ["Moustaches de sardine", "Moustaches de sardines", "des "], + 3008 : ["Potion calmante", "Potions calmantes", "une "], + 3009 : ["Dent cassée", "Dents cassées", "une "], + 3010 : ["Dent en or", "Dents en or", "une "], + 3011 : ["Pain aux pommes de pin", "Pains aux pommes de pin", "un "], + 3012 : ["Fromage grumeleux", "Fromages grumeleux", "du "], + 3013 : ["Cuillère ordinaire", "Cuillères ordinaires", "une "], + 3014 : ["Crapaud parlant", "Crapauds parlants", "un "], + 3015 : ["Cône de glace", "Cônes de glace", "un "], + 3016 : ["Poudre à perruque", "Poudres à perruques", "de la "], + 3017 : ["Canard en plastique", "Canards en plastique", "un "], + 3018 : ["Dés en peluche", "Dés en peluche", "des "], + 3019 : ["Micro", "Micros", "un "], + 3020 : ["Clavier électrique", "Claviers électriques", "un "], + 3021 : ["Chaussures à plate-forme", "Chaussures à plate-forme", "des "], + 3022 : ["Caviar", "Caviar", "du "], + 3023 : ["Poudre de maquillage", "Poudres de maquillage", "de la "], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "dans n'importe quel quartier" + +QuestsTailorFillin = "Tailleur" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Boutique de prêt-à-porter" +QuestsTailorLocationNameFillin = "dans n'importe quel quartier" +QuestsTailorQuestSCString = "J'ai besoin de voir un tailleur." + +QuestMovieQuestChoiceCancel = "Reviens plus tard si tu as besoin d'un défitoon! Salut!" +QuestMovieTrackChoiceCancel = "Reviens quand tu es prêt à te décider!! Salut!" +QuestMovieQuestChoice = "Choisis un défitoon." +QuestMovieTrackChoice = "Prêt à te décider? Choisis une série, ou reviens plus tard." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Maintenant tu es prêt(e).\aSors et fais un tour avant de décider quelle série tu voudras choisir.\aChoisis bien, parce que c'est ta dernière série.\aQuand tu auras décidé, reviens me voir.", + INCOMPLETE_PROGRESS : "Choisis bien.", + INCOMPLETE_WRONG_NPC : "Choisis bien.", + COMPLETE : "Choix très sage!", + LEAVING : "Bonne chance. Reviens me voir quand tu as maîtrisé ta nouvelle habileté.", + } + +QuestDialog_3225 = { + QUEST : "Oh, merci pour ta visite, _avName_!\a"+TheCogs+" du quartier ont effrayé mon livreur.\aJe n'ai personne pour livrer cette salade à _toNpcName_!\aPeux-tu le faire pour moi? Merci beaucoup!_where_" + } + +QuestDialog_2910 = { + QUEST : "Déjà de retour?\aSuper travail avec le ressort.\aLe dernier article est un contrepoids.\aVa donc voir _toNpcName_ et ramène tout ce que tu peux._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Chefbots ", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Chefbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Loibots.", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Loibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Caissbots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Caissbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Vendibots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Vendibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Il semble que tu as besoin de nouveaux gags.\aVa voir %s, peut-être pourra-t-il t'aider._where_" % Flippy }, + 165 : {QUEST : "Salut!\aOn dirait que tu as besoin de t'entraîner à utiliser tes gags.\aChaque fois que tu atteins un Cog avec l'un de tes gags, ton expérience augmente.\aQuand tu auras assez d'expérience, tu pourras utiliser un gag encore meilleur.\aVa t'entraîner à utiliser tes gags en battant 4 Cogs."}, + 166 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Chefbots."}, + 167 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Loibots."}, + 168 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Vendibots."}, + 169 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Caissbots.}"}, + 170 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 171 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 172 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - elle peut te donner des conseils avisés._where_" }, + 400 : {GREETING : "", + QUEST : "Le lancer et l'éclaboussure sont super, mais tu auras besoin de plus de gags pour battre les Cogs de plus haut niveau.\aLorsque tu fais équipe avec d'autres Toons contre les Cogs, vous pouvez combiner vos attaques pour faire encore plus de dégâts.\aEssayez différentes combinaisons de gags pour voir ce qui marche le mieux.\aPour ta prochaine série, choisis entre tapage et toonique.\aTapage est particulier parce que lorsqu'il frappe, il endommage tous les Cogs.\aToonique te permet de soigner les autres Toons lors d'un combat.\aLorsque tu es prêt(e) à te décider, reviens ici faire ton choix.", + INCOMPLETE_PROGRESS : "Déjà de retour? OK, quel est ton choix?", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Bonne décision. Maintenant tu dois t'entraîner avant de pouvoir utiliser ces gags.\aTu dois effectuer une série de défitoons pour t'entraîner.\aChaque défi te donnera une seule image de ton animation d'attaque avec les gags.\aLorsque tu auras les 15 images, tu pourras faire le dernier défi d'entraînement qui te permettra d'utiliser tes nouveaux gags.\aTu peux suivre tes progrès dans ton journal de bord.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1040 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1041 : { QUEST : "Salut! Qu'est-ce qui t'amène?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes amis en utilisant la liste d'amis, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes amis.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1042 : { QUEST : "Salut! Qu'est-ce qui t'amène?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes amis en utilisant la liste d'amis, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes amis.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1043 : { QUEST : "Salut! Qu'est-ce qui t'amène?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes amis en utilisant la liste d'amis, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes amis.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1044 : { QUEST : "Oh, merci de passer par ici. J'ai vraiment besoin d'aide.\aComme tu peux voir, je n'ai pas de clients.\aMon livre de recettes secret est perdu et personne ne vient plus dans mon restaurant.\aLa dernière fois que je l'ai vu, c'était avant que ces Cogs ne prennent mon bâtiment.\aEst-ce que tu peux m'aider à retrouver quatre de mes célèbres recettes?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as pu retrouver mes recettes?" }, + 1045 : { QUEST : "Merci beaucoup!\aD'ici peu, j'aurai retrouvé toutes mes recettes et je pourrai rouvrir mon restaurant.\aOh, j'ai une petite note ici pour toi - quelque chose à propos de l'accès par téléportation?\aC'est écrit, merci d'avoir aidé mon ami et d'avoir livré ceci au quartier général des Toons. \aEh bien, merci vraiment - au revoir!", + LEAVING : "", + COMPLETE : "Ah oui, c'est écrit que tu as été d'une grande aide à de braves gens de l'avenue des Fondus.\aEt que tu as besoin d'un accès par téléportation à Toontown centre.\aBon, c'est comme si c'était fait.\aMaintenant tu peux revenir au terrain de jeux par téléportation depuis presque partout dans Toontown.\aOuvre simplement ta carte et clique sur "+lToontownCentral+"." }, + 1046 : { QUEST : "Les Caissbots ont vraiment ennuyé la Caisse d'épargne Drôle d'argent.\aVa donc y faire un tour et vois si tu peux faire quelque chose._where_" }, + 1047 : { QUEST : "Les Caissbots se sont introduits dans la banque et ont volé nos machines.\aS'il te plaît, reprends 5 machines à calculer aux Caissbots.\aPour t'éviter de faire des allers et retours, rapporte-les toutes en une seule fois.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore des machines à calculer?" }, + 1048 : { QUEST : "Oh là là! >Merci d'avoir trouvé nos machines à calculer.\aHmm... Elles ont l'air un peu abîmées.\aDis donc, pourrais-tu les amener à _toNpcName_ à son magasin, \"Machines à chatouilles\", dans cette rue?\aVoir si elle peut les réparer.", + LEAVING : "", }, + 1049 : { QUEST : "Qu'est-ce que c'est? Des machines à calculer cassées?\aDes Caissbots dis-tu?\aBon, regardons ça...\aMouais, les pignons sont cassés, mais je n'en vends pas...\aTu sais ce qui pourrait marcher - des pignons de Cog, des gros, de gros Cogs...\aDes pignons de Cog de niveau 3 devraient faire l'affaire. J'en aurai besoin de 2 pour chaque machine, donc 10 au total.\aRapporte-les moi tous ensemble et je ferai la réparation!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Souviens-toi, j'ai besoin de 10 pignons pour réparer les machines." }, + 1053 : { QUEST : "Ah oui, ça devrait bien faire l'affaire.\aTout est réparé maintenant, gratuitement.\aRapporte-les à Drôle d'argent, et dis-leur bonjour de ma part.", + LEAVING : "", + COMPLETE : "Toutes les machines à calculer sont réparées?\aJoli travail. Je crois bien que j'ai quelque chose par là pour te récompenser..." }, + 1054 : { QUEST : "_toNpcName_ a besoin d'aide pour ses voitures de clown._where_" }, + 1055 : { QUEST : "Bon sang! Je n'arrive pas à trouver les pneus de cette voiture de clown!\aTu crois que tu pourrais m'aider?\aJe crois que Bob Fondu les a lancés dans la mare du terrain de jeux de Toontown centre.\aSi tu vas sur les pontons, de là tu peux essayer de repêcher les pneus.", + GREETING : "Youhouu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as du mal à repêcher les 4 pneus?" }, + 1056 : { QUEST : "Fanta-super-tastique! Maintenant je vais pouvoir remettre en marche cette vieille voiture de clown!\aHé, je croyais que j'avais une pompe par ici pour gonfler ces pneus...\aC'est peut-être _toNpcName_ qui l'a empruntée?\aTu peux aller lui demander de me la rendre?_where_", + LEAVING : "" }, + 1057 : { QUEST : "Salut!\aUne pompe à pneus tu dis?\aJe vais te dire - tu me nettoies les rues de quelques-uns de ces Cogs de haut niveau...\aEt je te donne la pompe à pneus.", + LEAVING : "", + INCOMPLETE_PROGRESS : "C'est tout ce que tu peux faire?" }, + 1058 : { QUEST : "Bon travail - je savais que tu pouvais le faire.\aVoilà la pompe. Je suis certain que_toNpcName_ sera content de la récupérer.", + LEAVING : "", + GREETING : "", + COMPLETE : "Youpiii! Maintenant ça va marcher!\aEt dis donc, merci de m'avoir aidé.\aTiens, prends ça." }, + 1059 : { QUEST : "_toNpcName_ est à court de fournitures. Tu peux peut-être lui donner un coup de main?_where_" }, + 1060 : { QUEST : "Merci d'être passé par ici!\aCes Cogs ont volé mon encre ; je n'en ai presque plus.\a Pourrais-tu me pêcher de l'encre de seiche dans la mare?\aTu n'as qu'à rester sur un ponton près de la mare pour pêcher.", + LEAVING : "", + INCOMPLETE_PROGRESS : "{Tu as des problèmes pour pêcher?" }, + 1061 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Gratte-papiers...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Gratte-papiers dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Gratte-papiers." }, + 1062 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Pique-au-sang...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Pique-au-sang dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Pique-au-sang." }, + 900 : { QUEST : "Je crois comprendre que_toNpcName_ a besoin d'aide avec un paquet._where_" }, + 1063 : { QUEST : "Salut, merci d'être là.\aUn Cog a volé un paquet très important juste sous mon nez.\aPeux-tu le récupérer? Je crois que c'était un Cog de niveau 3...\aDonc, tu dois vaincre des Cogs de niveau 3 jusqu'à ce que tu retrouves mon paquet.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein?" }, + 1067 : { QUEST : "C'est ça, très bien!\aOh, l'adresse est toute tachée...\aTout ce que j'arrive à lire, c'est Docteur... - le reste est brouillé.\aC'est peut-être pour_toNpcName_? Peux-tu lui porter?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Je n'attendais pas de paquet. C'est peut-être pour le Dr E. Phorique?\aMon assistant doit aller le voir aujourd'hui, je me charge de lui remettre.\aEn attendant, est-ce que tu voudrais bien débarrasser ma rue de quelques Cogs?\aTu dois vaincre 10 Cogs dans Toontown centre.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mon assistant n'est pas encore revenu." }, + 1069 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Caissbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein?" }, + 1070 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Vendibot l'a volé à mon assistant alors qu'il revenait.\aJe suis désolé, mais il va falloir que tu retrouves ce Vendibot pour le récupérer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein?" }, + 1071 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Chefbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein?" }, + 1072 : { QUEST : "Super - tu l'as retrouvé!\aTu devrais peut-être essayer _toNpcName_, cela pourrait être pour lui._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, merci de m'avoir apporté mes paquets.\aJuste une seconde, j'en attendais deux. Pourrais-tu vérifier avec _toNpcName_ voir s'il a l'autre?", + INCOMPLETE : "Est-ce que tu as trouvé mon autre paquet?", + LEAVING : "" }, + 1074 : { QUEST : "Il a dit qu'il y avait un autre paquet? Les Cogs l'ont peut-être aussi volé.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves le second paquet. ", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein?" }, + 1075 : { QUEST : "Finalement je crois qu'il y avait un second paquet!\aVa vite le porter à _toNpcName_ avec mes excuses.", + COMPLETE : "Eh, mon paquet est là!\aPuisque tu es un Toon aussi serviable, cela devrait aider.", + LEAVING : "" }, + 1076 : { QUEST : "Il y a un problème au Ornithorynques 14 carats.\a_toNpcName_ serait sans doute content d'avoir de l'aide._where_" }, + 1077 : { QUEST : "Merci d'être venu - "+theCogs+" ont volé tous mes poissons dorés.\aJe crois que les Cogs veulent les vendre pour se faire de l'argent facilement.\aCes 5 poissons ont été mes seuls compagnons dans cette petite boutique depuis tant d'années...\aSi tu pouvais me les retrouver, je t'en serais vraiment reconnaissant.\aJe suis certain qu'un des Cogs a mes poissons.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mes poissons dorés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "S'il te plaît, ramène-moi mes poissons." }, + 1078 : { QUEST : "Oh, tu as mes poissons!\aEh? Qu'est-ce que c'est que ça?\aAah, ce sont bien les Cogs, après tout.\aJe ne comprends rien à ce reçu. Peux-tu l'emmener à _toNpcName_ voir s'il peut le lire?_where_", + INCOMPLETE : "Qu'est-ce que _toNpcName_ a dit à propos du reçu?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Laquaistic.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Laquaistic.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1092 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Gardoseille.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Gardoseille.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1080 : { QUEST : "Oh, Dieu merci! Tu as trouvé Oscar - c'est mon préféré.\aQu'est-ce que c'est, Oscar? Hein, hein...ils ont quoi? ... Ils sont?\aOscar dit que les 4 autres se sont échappés dans la mare du terrain de jeux.\aPeux-tu aller me les chercher?\aTu n'as qu'à les pêcher dans la mare.", + LEAVING : "", + COMPLETE : "Ahh, je suis si content! Avoir retrouvé mes petits camarades!\aTu mérites une belle récompense pour cela!", + INCOMPLETE_PROGRESS : "Tu as des problèmes pour trouver ces poissons?" }, + 1081 : { QUEST : "_toNpcName_ a l'air d'être dans une situation difficile. Elle serait sûrement contente d'avoir de l'aide._where_" }, + 1082 : { QUEST : "J'ai renversé de la colle à séchage rapide, et je suis collée - complètement collée!\aS'il y avait une façon de m'en sortir, je prends tout de suite.\aCela me donne une idée, si tu veux bien.\aVa vaincre quelques Vendibots et ramène-moi de l'huile.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller?" }, + 1083 : { QUEST : "Bon, l'huile a fait un peu d'effet, mais je ne peux toujours pas bouger,\aQuoi d'autre pourrait m'aider? C'est difficile à dire.\aCela me donne une idée ; on peut au moins essayer.\aVa vaincre quelques Loibots et ramène-moi de la graisse.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller?" }, + 1084 : { QUEST : "Non, ça n'a rien fait. Ce n'est vraiment pas drôle.\aJe mets la graisse juste là sur l'argent,\aÇa me donne une idée, avant que j'oublie.\aVa vaincre quelques Caissbots ; et rapporte de l'eau pour l'humecter.", + LEAVING : "", + GREETING : "", + COMPLETE : "Hourrah, je suis libérée de cette colle rapide,\aComme récompense, je te donne ce cadeau,\aTu peux rire un peu plus longtemps lorsque tu es en train de te battre, et puis...\aOh, non! Je suis de nouveau collée là!", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller?" }, + 1085 : { QUEST : "_toNpcName_ est en train de faire des recherches sur les Cogs.\aVa lui parler si tu veux l'aider._where_" }, + 1086 : { QUEST : "C'est cela, je fais une étude sur les Cogs.\aJe veux savoir ce qui les fait tiquer.\aCela m'aiderait certainement si tu pouvais me trouver des pignons de Cogs.\aAssure-toi qu'il s'agit de Cogs de niveau 2 au minimum, qu'ils soient assez gros pour être examinés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ne peux pas trouver assez de pignons?" }, + 1089 : { QUEST : "OK, regardons un peu ça. Ce sont d'excellents spécimens!\aMmmm...\aOK, voilà mon rapport. Emmène-le tout de suite au quartier général des Toons.", + INCOMPLETE : "As-tu porté mon rapport au quartier général?", + COMPLETE : "Bon travail _avName_, on va s'occuper de ça.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ a des informations importantes pour toi._where_" }, + 1091 : { QUEST : "J'ai entendu dire que le quartier général des Toons travaille sur une sorte de détecteur de Cogs.\aIl te permettra de voir où sont les Cogs afin de les repérer plus facilement.\aCette page des Cogs dans ton journal de bord en est la clé.\aEn battant assez de Cogs, tu pourras te régler sur leurs signaux et détecter leur emplacement.\aContinue à vaincre des Cogs, afin d'être prêt.", + COMPLETE : "Bon travail! Tu pourras probablement utiliser ceci...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 2202 : { QUEST : "Salut, _avName_. Dieu merci, tu es là. Un Radino à l'air méchant était là à l'instant et il est parti avec une chambre à air.\aJe crains qu'ils ne l'utilisent pour leurs sombres desseins.\aS'il te plaît, essaie de le retrouver et ramène-la moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé ma chambre à air?", + COMPLETE : "Tu as trouvé ma chambre à air! Tu es vraiment très doué. Tiens, prends ta récompense...", + }, + 2203 : { QUEST : TheCogs+" sont en train de mettre la banque sens dessus dessous.\aVa voir le Capitaine Carl et vois ce que tu peux faire._where_" }, + 2204 : { QUEST : "Bienvenue à bord, moussaillon.\aGrrr! Ces fripons de Cogs ont cassé mon monocle et je n'arrive plus à compter la monnaie sans lui.\aGarde les pieds sur terre et porte cette ordonnance au Dr. Queequeg puis rapporte m'en un nouveau._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "Qu'est-ce que c'est?\aOh, je voudrais bien préparer cette ordonnance mais les Cogs ont chapardé mes réserves.\aSi tu peux reprendre la monture à un Laquaistic je pourrai probablement t'aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Désolé. Pas de monture du Laquaistic, pas de monocle.", + }, + 2206: { QUEST : "Excellent!\aUne seconde...\aTon ordonnance est prête. Emmène tout de suite ce monocle au Capitaine Carl._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Hisse et ho!\aTu vas finir par gagner du galon après tout.\aEt voilà.", + }, + 2207 : { QUEST : "Barbara Bernache a un Cog dans son magasin!\aIl vaudrait mieux que tu y ailles tout de suite._where_" }, + 2208 : { QUEST : "Ça alors! Tu viens de le rater, mon chou.\aIl y avait un Frappedos ici. Il a pris ma grande perruque blanche.\aIl a dit que c'était pour son chef et quelque chose à propos de \" jurisprudence \".\aSi tu pouvais me la rapporter, je t'en serais toujours reconnaissante.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Tu ne l'as toujours pas trouvé?\aIl est grand avec une tête pointue", + COMPLETE : "Tu l'as trouvé!?!?\aTu es vraiment un ange!\aTu as bien gagné ceci...", + }, + 2209 : { QUEST : "Ginette se prépare pour un voyage important.\aVas-y faire un tour et vois ce que tu peux faire pour l'aider._where_"}, + 2210 : { QUEST : "Tu peux m'aider.\aLe quartier général des Toons m'a demandé de faire un voyage pour voir si je peux trouver d'où viennent les Cogs.\aJ'aurai besoin de quelques affaires pour mon bateau mais je n'ai pas beaucoup de bonbons.\aVa et ramène-moi du lest de chez Ernest. Il faudra que tu lui rendes un service pour l'avoir._where_", + GREETING : "Salut, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Comme ça, Ginette veut du lest, n'est-ce pas?\aElle me doit encore de l'argent pour le dernier boisseau.\aJe te le donnerai si tu peux faire partir cinq Microchefs de ma rue.", + INCOMPLETE_PROGRESS : "Non, idiot! J'ai dit CINQ Microchefs...", + GREETING : "Que puis-je faire pour toi?", + LEAVING : "", + }, + 2212 : { QUEST : "Un marché est un marché.\aVoilà ton lest pour cette pingre de Ginette._where_", + GREETING : "Eh bien, regarde ce qui arrive là...", + LEAVING : "", + }, + 2213 : { QUEST : "Excellent travail. Je savais qu'il serait raisonnable.\aEnsuite il me faudra une carte marine de chez Art.\aJe ne crois pas avoir beaucoup de crédit là-bas non plus ; il faudra que tu t'arranges avec lui._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Oui, j'ai la carte marine que veut Ginette.\aEt tu l'auras en échange d'un petit travail.\aJ'essaie de construire un astrolabe pour naviguer dans les étoiles.\aJ'aurais besoin de trois pignons de Cog pour le construire.\aReviens quand tu les auras trouvés.", + INCOMPLETE_PROGRESS: "Alors ils arrivent ces pignons de Cogs?", + GREETING : "Bienvenue!", + LEAVING : "Bonne chance!", + }, + 2215 : { QUEST : "Ooh! Ces pignons feront très bien l'affaire.\aVoilà la carte. Donne-la à Ginette avec mes compliments._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bon, on y est presque. Je suis prête à prendre la mer!\aJe t'emménerais avec moi si tu n'avais pas un teint si vert. Prends plutôt ceci.", + }, + 2901 : { QUEST : "Si tu es d'accord, Ahab a besoin d'aide, chez lui..._where_", + }, + 2902 : { QUEST : "Tu es la nouvelle recrue?\aBien, bien. Tu peux peut-être m'aider.\aJe suis en train de construire un crabe géant préfabriqué pour dérouter les Cogs.\aJe pourrais quand même utiliser une manille. Va voir Gérard et rapportes-en un, s'il te plaît._where_", + }, + 2903 : { QUEST : "Salut!\aOui, j'ai entendu parler du crabe géant qu'Ahab est en train de fabriquer.\aLa meilleure manille que j'aie est un peu sale quand même.\aSois sympa, passe chez un blanchisseur avant de la déposer._where_", + LEAVING : "Merci!" + }, + 2904 : { QUEST : "Tu dois être la personne que Gérard a envoyée.\aJe crois que je peux faire ça assez vite.\aJuste une minute...\aEt voilà. Comme neuf!\aTu salueras Ahab de ma part._where_", + }, + 2905 : { QUEST : "Ah, c'est exactement ce que je cherchais.\aPendant que tu es là, je vais aussi avoir besoin d'un très gros ressort d'horloge.\aVa donc voir chez Crochet voir s'il en a un._where_", + }, + 2906 : { QUEST : "Un gros ressort, hein?\aJe suis désolé mais le plus gros ressort que j'aie est quand même plutôt petit.\aJe pourrais peut-être en fabriquer un avec des ressorts de gâchette de pistolet éclabousseur.\aApporte-moi trois de ces gags et je vais voir ce que je peux faire.", + }, + 2907 : { QUEST : "Regardons ça...\aGénial. Vraiment génial.\aQuelquefois je me surprends moi-même.\aEt voilà : un gros ressort pour Ahab!_where_", + LEAVING : "Bonne route!", + }, + 2911 : { QUEST : "Je serais très content de pouvoir aider, _avName_.\aMalheureusement, les rues ne sont plus sûres.\aVa donc éliminer quelques Cogs Caissbots et on pourra parler.", + INCOMPLETE_PROGRESS : "Je crois que tu peux rendre les rues encore plus sûres.", + }, + 2916 : { QUEST : "Oui, j'ai un contrepoids que je pourrais donner à Ahab.\aJe crois que ce serait plus sûr si tu pouvais vaincre deux Vendibots d'abord.", + INCOMPLETE_PROGRESS : "Pas encore. Tu dois vaincre plus de Vendibots.", + }, + 2921 : { QUEST : "Hmmm, je pensais que je pourrais me débarrasser d'un poids.\aJe me sentirais bien mieux s'il n'y avait pas autant de Cogs Chefbots à rôder par ici.\aVa donc en vaincre six et reviens me voir.", + INCOMPLETE_PROGRESS : "Je crois qu'on est toujours pas en sécurité...", + }, + 2925 : { QUEST : "Ça y est?\aBon, je suppose qu'on est suffisamment en sécurité maintenant.\aVoilà le contrepoids pour Ahab._where_" + }, + 2926 : {QUEST : "Bon, c'est tout.\aVoyons si ça marche.\aHmmm, il y a un petit problème.\aJe n'ai plus de courant parce que ce bâtiment Cog bloque mon capteur solaire.\aPeux-tu le reprendre pour moi?", + INCOMPLETE_PROGRESS : "Toujours pas de courant. Où en es-tu avec ce bâtiment?", + COMPLETE : "Super! Tu es une sacrée terreur pour les Cogs! Tiens, prends ta récompense...", + }, + 3200 : { QUEST : "Je viens d'avoir un appel de _toNpcName_.\aCe n'est pas son jour. Tu pourrais peut-être l'aider.!\aVas-y faire un tour et vois ce dont il a besoin._where_" }, + 3201 : { QUEST : "Oh, merci d'être là!\aJ'ai besoin de quelqu'un pour emporter cette nouvelle cravate en soie à _toNpcName_.\aEst-ce que tu peux faire ça pour moi?_where_" }, + 3203 : { QUEST : "Oh, ça doit être la cravate que j'ai commandée! Merci!\aElle va avec un costume à rayures que je viens de finir, juste là.\aHé, qu'est ce qui est arrivé à ce costume?\aOh non! Les Cogs ont dû voler mon nouveau costume!\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mon costume, et que tu me le rapportes.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon costume? Je suis certain que les Cogs l'ont pris!", + COMPLETE : "Youpii! Tu as trouvé mon nouveau costume!\aTu vois, je t'avais dit que les Cogs l'avaient! Voilà ta récompense...", + }, + + 3204 : { QUEST : "_toNpcName_ vient d'appeler pour signaler un vol.\aPourquoi n'irais-tu pas voir si tu peux arranger l'affaire?_where_" }, + 3205 : { QUEST : "Bonjour, _avName_! Tu es là pour m'aider?\aJe viens de chasser un Pique-au-sang de mon magasin. Houlala! C'était effrayant.\aMais maintenant je ne trouve plus mes ciseaux! Je suis certain que ce Pique-au-sang les a pris.\aTrouve-le, et ramène-moi mes ciseaux.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore mes ciseaux?", + COMPLETE : "Mes ciseaux! Merci beaucoup! Voilà ta récompense...", + }, + + 3206 : { QUEST : "On dirait que _toNpcName_ a des problèmes avec des Cogs.\aVa voir si tu peux l'aider._where_" }, + 3207 : { QUEST : "Ohé, _avName_! Merci d'être venu!\aUne bande de Charabieurs est arrivée et a volé une pile de cartes postales sur mon comptoir.\aS'il te plaît, sors vaincre tous ces Charabieurs et rapporte-moi mes cartes postales!", + INCOMPLETE_PROGRESS : "Il n'y a pas assez de cartes postales! Continue de chercher!", + COMPLETE : "Oh, merci! Maintenant je vais pouvoir livrer le courrier à temps! Voilà ta récompense...", + }, + + 3208 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Cassepieds.\aEssaie de vaincre 10 Cassepieds pour aider tes camarades Toons du "+lDaisyGardens+"." }, + 3209 : { QUEST : "Merci d'avoir battu ces Cassepieds!\aMais maintenant ce sont les Télévendeurs qui sont incontrôlables.\aVa vaincre 10 Télévendeurs au "+lDaisyGardens+" et reviens ici pour ta récompense." }, + + 3247 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Pique-au-sang.\aEssaie de vaincre 20 Pique-au-sang pour aider tes camarades Toons du "+lDaisyGardens+". " }, + + + 3210 : { QUEST : "Oh non, la Fleur qui mouille, rue des Érables, n'a plus de fleurs!\aEmmène-leur dix de tes fleurs à éclabousser pour les aider.\aVérifie que tu as bien 10 fleurs à éclabousser dans ton inventaire d'abord.", + LEAVING: "", + INCOMPLETE_PROGRESS : "J'ai besoin de 10 fleurs à éclabousser. Tu n'en as pas assez!" }, + 3211 : { QUEST : "Oh, merci beaucoup! Ces fleurs à éclabousser vont nous tirer d'embarras.\aMais j'ai peur des Cogs qui sont dehors.\aPeux-tu m'aider et vaincre quelques-uns de ces Cogs?\aReviens me voir après avoir vaincu 20 Cogs dans cette rue.", + INCOMPLETE_PROGRESS : "Il reste encore des Cogs à vaincre par ici! Continue!", + COMPLETE : "Oh, merci! Cela m'aide beaucoup. Ta récompense est...", + }, + + 3212 : { QUEST : "_toNpcName_ a besoin d'aide pour chercher quelque chose qu'elle a perdu.\aVa la voir et vois ce que tu peux faire._where_" }, + 3213 : { QUEST : "Salut, _avName_. Peux-tu m'aider?\aJe crois que j'ai égaré mon stylo. Je pense que les Cogs l'ont peut-être pris.\aVa vaincre des Cogs pour retrouver le stylo qu'ils m'ont volé.", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon stylo?" }, + 3214 : { QUEST : "Oui, c'est mon stylo! Merci beaucoup!\aMais après ton départ, j'ai réalisé que mon encrier manquait aussi.\aVa vaincre des Cogs pour retrouver mon encrier.", + INCOMPLETE_PROGRESS : "Je cherche encore mon encrier!" }, + 3215 : { QUEST : "Super! Maintenant j'ai retrouvé mon stylo et mon encrier!\aMais tu ne devineras jamais!\aMon bloc-notes a disparu! Ils ont dû le voler aussi!\aVa vaincre des Cogs pour retrouver mon bloc-notes volé, puis reviens pour ta récompense.", + INCOMPLETE_PROGRESS : "Tu as des nouvelles de mon bloc-notes?" }, + 3216 : { QUEST : "C'est mon bloc-notes! Youpii! Ta récompense est...\aHé! Mais où est-elle?\aJ'avais ta récompense là, dans le coffre de mon bureau. Mais le coffre entier a disparu!\aIncroyable! Ces Cogs ont volé ta récompense!\aVa vaincre des Cogs pour retrouver mon coffre.\aQuand tu me le ramèneras, je te donnerai ta récompense.", + INCOMPLETE_PROGRESS : "Continue de chercher ce coffre! Ta récompense est dedans!", + COMPLETE : "Enfin! J'avais ton nouveau sac à gags dans ce coffre. Le voilà...", + }, + + 3217 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Vendibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Cafteur.\aTu peux en attraper un quand le Cog explose." }, + 3218 : { QUEST : "Bon travail! Maintenant, nous avons besoin d'un pignon de Passetout pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3219 : { QUEST : "Super! Maintenant on n'a plus besoin que d'un pignon en plus.\aCette fois, il nous faut un pignon de Secousse-cousse.\aTu devras peut-être chercher à l'intérieur des bâtiments Vendibots pour trouver cette sorte de Cogs.\aQuand tu en auras attrapé un, rapporte-le pour recevoir ta récompense." }, + + 3244 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Loibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Charognard.\aTu peux en attraper un quand le Cog explose." }, + 3245 : { QUEST : "Bon travail! Maintenant nous avons besoin d'un pignon de Frappedos pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3246 : { QUEST : "Super! Encore un pignon et c'est bon.\aCette fois, il nous faut un pignon de Tournegris.\aQuand tu en auras attrapé un, rapporte-le pour avoir ta récompense." }, + + 3220 : { QUEST : "Je viens d'apprendre que _toNpcName_ te cherchait.\aPourquoi ne vas-tu pas voir ce qu'elle veut?_where_" }, + 3221 : { QUEST : "Ohé, _avName_! Et voilà!\aJ'ai entendu dire que tu étais expert(e) en éclaboussures.\aJ'ai besoin de quelqu'un pour montrer l'exemple à tous les Toons du "+lDaisyGardens+".\aUtilise tes attaques par éclaboussure pour vaincre un groupe de Cogs.\aEncourage tes amis à utiliser aussi les éclaboussures.\aLorque tu auras vaincu 20 Cogs, reviens ici pour ta récompense!" }, + + 3222 : { QUEST : "C'est le moment de faire preuve de ta Toonmaîtrise.\aSi tu réussis à reprendre un certain nombre de bâtiments aux Cogs, tu gagneras le droit à trois quêtes.\aD'abord, tu dois prendre deux bâtiments aux Cogs.\aN'hésite pas à demander l'aide de tes amis."}, + 3223 : { QUEST : "Super travail pour ces bâtiments!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins deux étages." }, + 3224 : { QUEST : "Fantastique!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins trois étages.\aQuand tu auras fini, reviens chercher ta récompense!", + COMPLETE : "Tu as réussi, _avName_!\aTu as fait preuve d'une excellente Toonmaîtrise.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ dit qu'elle a besoin d'aide.\aVa voir si tu peux donner un coup de main?_where_" }, + 3235 : { QUEST : "Oh, c'est la salade que j'ai commandée!\aMerci de me l'avoir apportée.\aTous ces Cogs ont dû effrayer le livreur habituel de _toNpcName_ encore une fois.\aTu pourrais nous rendre service et vaincre quelques-uns des Cogs qui traînent par ici?\aVa vaincre 10 Cogs dans le "+lDaisyGardens+" et reviens voir _toNpcName_.", + INCOMPLETE_PROGRESS : "Tu es en train de vaincre des Cogs pour moi?\aC'est super!! Continue comme ça!", + COMPLETE : "Oh, merci beaucoup d'avoir vaincu ces Cogs!\aMaintenant je vais peut-être pouvoir reprendre mon programme habituel de livraisons.\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Va raconter à _toNpcName_ tous les Cogs que tu as vaincus._where_" }, + + 3236 : { QUEST : "Il y a beaucoup trop de Loibots par ici.\aTu peux faire ta part de travail!\aVa vaincre 3 bâtiments Loibot." }, + 3237 : { QUEST : "Super travail pour ces bâtiments Loibot!\aMais maintenant il y a beaucoup trop de Vendibots!\aVa vaincre 3 bâtiments Vendibot, puis reviens chercher ta récompense." }, + + 3238 : { QUEST : "Oh non! Un Cog Circulateur a volé la clé du "+lDaisyGardens+"!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Circulateurs ne se trouvent que dans les bâtiments Vendibot." }, + 3239 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du "+lDaisyGardens+".\aContinue de chercher! Un Cog Circulateur l'a encore!" }, + + 3242 : { QUEST : "Oh non! Un Cog Avocageot a volé la clé du "+lDaisyGardens+"!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Avocageots ne se trouvent que dans les bâtiments Loibot." }, + 3243 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du "+lDaisyGardens+".\aContinue de chercher! Un Cog Avocageot l'a encore!" }, + + 3240 : { QUEST : "_toNpcName_ vient de me dire qu'un Avocageot lui a volé un sac de graines pour oiseaux.\aVa vaincre des Avocageots jusqu'à ce que tu retrouves les graines pour oiseaux de Piaf, et rapporte-les lui.\aLes Avocageots ne se trouvent que dans les bâtiments Loibot._where_", + COMPLETE : "Oh, merci beaucoup d'avoir retrouvé mes graines pour oiseaux!\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Bien, tu as retrouvé ces graines pour oiseaux!\aMaintenant emmène-les à _toNpcName_._where_", + }, + + 3241 : { QUEST : "Certains des bâtiments des Cogs deviennent beaucoup trop hauts.\aVa voir si tu peux réduire de hauteur certains des immeubles les plus hauts.\aReprends 5 immeubles de 3 étages ou plus et reviens ici pour ta récompense.", + }, + + 3250 : { QUEST : "Lima, la détective de la rue du Chêne, a entendu parler d'un quartier général Vendibot. \aVa donc voir et aide-la à enquêter.", + }, + 3251 : { QUEST : "Il y a quelque chose de bizarre par ici.\aIl y a tant de Vendibots!\aJ'ai entendu dire qu'ils ont installé leur propre quartier général au bout de cette rue.\aVa au bout de la rue voir ce qu'il en est.\aTrouve des Cogs Vendibots dans leur quartier général, vaincs-en 5 et reviens me le dire.", + }, + 3252 : { QUEST : "OK, annonce la couleur\aQu'est-ce que tu dis?\aAh, le quartier général des Vendibots?? Oh non!!! Il faut faire quelque chose.\aNous devons le dire au Juge Ticot - il saura quoi faire.\aVa le voir tout de suite et dis-lui ce que tu as trouvé. Il est juste au bout de la rue.", + }, + 3253 : { QUEST : "Oui, puis-je t'aider? Je suis très occupé.\aHein? Un quartier général Cog?\aHein? Sottises. Ça n'est pas possible.\aTu dois te tromper. C'est grotesque.\aHein? Ne discute pas avec moi.\aOk, alors ramène des preuves.\aSi les Vendibots sont vraiment en train de construire ce quartier général Cog, les Cogs du quartier auront des plans sur eux.\aLes Cogs adorent la paperasserie, tu le savais?\aVa vaincre des Vendibots par là-bas jusqu'à ce que tu trouves des plans.\aRapporte-les moi, alors je te croirai peut-être.", + }, + 3254 : { QUEST : "Encore toi, hein? Des plans? Tu les as?\aLaisse-moi regarder ça! Hmmm... Une usine?\aCela doit être là qu'ils fabriquent les Vendibots... Et qu'est-ce que c'est que ça?\aOui, exactement ce que je pensais. Je le savais depuis le départ.\aIls sont en train de construire un quartier général des Cogs Vendibots.\aCe n'est pas bon signe. Je dois passer quelques appels. Très occupé. Au revoir!\aHein? Oh oui, retourne ces plans à la détective Lima.\aElle saura les lire mieux que quiconque.", + COMPLETE : "Qu'a dit le Juge Ticot?\aOn avait raison? Oh non. Regardons ces plans.\aHmmm... On dirait que les Vendibots ont installé une usine avec l'outillage pour construire des Cogs.\aÇa a l'air très dangereux. N'y va pas tant que tu n'as pas plus de rigolpoints.\aQuand tu auras plus de rigolpoints, nous en aurons beaucoup à apprendre sur le quartier général des Vendibots.\aPour l'instant, bon travail, voilà ta récompense.", + }, + + + 3255 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3256 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3257 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3258 : { QUEST : "Personne ne sait au juste ce que les Cogs sont en train de faire dans leur nouveau quartier général.\aJ'ai besoin que tu nous ramènes des informations venant directement d'eux.\aSi nous pouvons trouver quatre notes de service internes des Vendibots à l'intérieur de leur quartier général, cela mettrait un peu les choses au clair.\aRamène-moi la première note de service que tu pourras afin qu'on en sache un peu plus.", + }, + 3259 : { QUEST : "Super! Voyons ce que dit cette note de service...\a\" À l'attention des Vendibots : \aJe serai dans mon bureau tout en haut des Tours Vendibot pour faire monter en grade les Cogs. \aLorsque vous aurez gagné suffisamment de mérites, montez me voir par l'ascenseur du hall. \aLa pause est terminée - tout le monde au travail! \"\aSigné, Vice-Président des Vendibots \"\aAah.... Flippy sera content de voir ça. Je lui envoie ça tout de suite.\aVa chercher une seconde note de service et rapporte-la moi.", + }, + 3260 : { QUEST : "Oh, bien, tu es de retour. Voyons ce que tu as trouvé....\a\" À l'attention des Vendibots :\aLes Tours Vendibot ont été équipées d'un nouveau système de sécurité pour empêcher les Toons de pénétrer à l'intérieur. \aLes Toons qui seront attrapés dans les Tours Vendibot seront retenus pour interrogatoire. \aVeuillez en discuter dans le hall autour d'un apéritif. \aSigné, Le Circulateur \"\aTrès intéressant... Je communique l'information immédiatement.\aS'il te plaît, rapporte-moi une troisième note de service.", + }, + 3261 : { QUEST : "Excellent travail, _avName_! Que dit cette note de service?\a\" À l'attention des Vendibots : \aLes Toons sont parvenus à trouver une façon d'infiltrer les Tours Vendibot. \aJe vous appellerai ce soir pendant le dîner pour vous donner des détails. \aSigné, Télévendeur \"\aHmmm... Je me demande comment les Toons se sont infiltrés....\aRapporte-moi une note de service supplémentaire et je crois que nous aurons assez d'informations pour l'instant.", + COMPLETE : "Je savais que tu pouvais le faire! OK, voilà ce que dit la note de service....\a\" À l'attention des Vendibots : \aJ'ai déjeûné avec M. Hollywood hier. \aIl dit que le Vice-Président est très occupé en ce moment. \aIl ne prendra de rendez-vous qu'avec les Cogs qui méritent une promotion. \aJ'allais oublier, Passetout joue au golf avec moi dimanche. \aSigné, Cafteur \"\aBon... _avName_, voilà qui est bien utile.\aVoilà ta récompense.", + }, + + 3262 : { QUEST : "_toNpcName_ a de nouvelles informations à propos de l'usine du quartier général Vendibot.\aVa donc voir ce que c'est._where_" }, + 3263 : { GREETING : "Salut, mon pote!", + QUEST : "Je suis Zucchini l'entraîneur, mais tu peux simplement m'appeler Coach Z.\aJe mets la gomme sur le squash et les étirements, si tu vois ce que je veux dire.\aÉcoute, les Vendibots ont terminé une énorme usine qui sort des Vendibots 24 heures sur 24.\aPrends une équipe de potes Toons et va me réduire cette usine à néant!\aÀ l'intérieur du quartier général Vendibot, cherche le tunnel qui mène à l'usine puis monte par l'ascenseur de l'usine.\aVérifie que tu as fait le plein de gags, de rigolpoints et que tu as quelques Toons costauds comme guides.\aVa vaincre le contremaître dans l'usine pour ralentir la progression des Vendibots.\aCe sera une vraie séance d'entraînement, si tu vois ce que je veux dire.", + LEAVING : "À plus, mon pote!", + COMPLETE : "Hé mon pote, bon boulot pour cette usine!\aOn dirait que tu as trouvé un morceau de costume de Cog.\aIl doit venir de la chaîne de fabrication des Cogs.\aÇa peut être pratique. Continue à en ramasser quand tu as du temps de libre.\aPeut-être que si tu récupères un costume de Cog complet, ça pourrait être utile à quelque chose....", + }, + + 4001 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Je parie que Tom aimerait de l'aide pour ses recherches._where_", + }, + 4201 : { GREETING: "Salut!", + QUEST : "Je suis très embêté au sujet d'une vague de vols d'instruments.\aJe fais une enquête parmi mes confrères commerçants.\aJe vais peut-être pouvoir trouver une constante qui me permettra de résoudre ce cas.\aVa voir Tina et demande-lui un inventaire des concertinas._where_", + }, + 4202 : { QUEST : "Oui, j'ai parlé à Tom ce matin.\aJ'ai l'inventaire ici.\aTu vas lui apporter tout de suite, ok?_where_" + }, + 4203 : { QUEST : "Super! Et de un...\aMaintenant va chercher celui de Yuki._where_", + }, + 4204 : { QUEST : "Oh! L'inventaire!\aJ'avais complètement oublié.\aJe parie que je peux le faire le temps que tu aies vaincu 10 Cogs.\aRepasse après ça et je promets que ce sera prêt.", + INCOMPLETE_PROGRESS : "31, 32... OUPS!\aTu m'as fait perdre mon compte!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, et voilà.\aMerci de m'avoir laissé un peu de temps.\aEmmène ça à Tom et dis-lui bonjour de ma part._where_", + }, + 4206 : { QUEST : "Hmm, très intéressant.\aÇa commence à ressembler à quelque chose.\aOK, le dernier inventaire est celui de Fifi._where_", + }, + 4207 : { QUEST : "Inventaire?\aComment est-ce que je pourrais faire un inventaire sans formulaire?\aVa voir Clément de sol et demande-lui s'il en a un pour moi._where_", + INCOMPLETE_PROGRESS : "Alors, ce formulaire?", + }, + 4208 : { QUEST : "Ah ça oui j'ai un formulaire d'inventaire!\aMais c'est pas gratuit, tu vois.\aJe vais te dire. Je te le vends pour une tarte à la crème entière.", + GREETING : "Allez, mon petit!", + LEAVING : "Chouette...", + INCOMPLETE_PROGRESS : "Une seule tranche c'est pas assez.\aJ'ai faim, mon petit! Je veux la tarte TOUTE ENTIÈRE.", + }, + 4209 : { GREETING : "", + QUEST : "Mmmm...\aSuper bon!\aVoilà ton formulaire pour Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Merci. Ça va bien m'aider.\aVoyons...violons : 2\aÇa y est! Et voilà!", + COMPLETE : "Bon travail, _avName_!\aJe suis sûr de pouvoir attraper ces voleurs maintenant.\aOn va pouvoir creuser cette affaire!", + }, + + 4211 : { QUEST : "Dis donc, le Dr Tefaispasdebile appelle toutes les cinq minutes. Tu pourrais aller voir quel est son problème?_where_", + }, + 4212 : { QUEST : "Houlala! Je suis content que le quartier général des Toons ait fini par envoyer quelqu'un.\aÇa fait des jours que je n'ai pas vu un client.\aCe sont ces satanés Gobechiffres qui sont partout.\aJe crois qu'ils enseignent une mauvaise hygiène buccale à nos résidents.\aVa donc en vaincre dix et nous verrons si les affaires reprennent.", + INCOMPLETE_PROGRESS : "Toujours pas de patients. Mais continue!", + }, + 4213 : { QUEST : "Tu sais après tout peut-être que ce n'était pas les Gobechiffres.\aPeut-être que ce sont simplement les Caissbots en général.\aDébarrasse-nous de vingt d'entre eux et j'espère que quelqu'un viendra au moins pour un bilan de santé.", + INCOMPLETE_PROGRESS : "Je sais que vingt ça fait beaucoup. Mais je suis certain que ça va rapporter des tonnes.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne comprends rien du tout!\aToujours pas un SEUL client.\aPeut-être qu'on devrait remonter jusqu'à la source.\aEssaie de reprendre un bâtiment Cog Caissbot.\aÇa devrait faire l'affaire...", + INCOMPLETE_PROGRESS : "Oh, s'il te plaît! Juste un tout petit bâtiment...", + COMPLETE : "Toujours personne.\aMais tu vois, maintenant que j'y pense.\aJe n'avais pas non plus de clients avant l'invasion des Cogs!\aJe te remercie quand même beaucoup pour ton aide.\aCela devrait te rendre service." + }, + + 4215 : { QUEST : "Anna a désespérément besoin de quelqu'un pour l'aider.\aPourquoi ne vas-tu pas voir ce que tu peux faire?_where_", + }, + 4216 : { QUEST : "Merci d'être là aussi vite!\aOn dirait que les Cogs sont partis avec les tickets de croisière de plusieurs de mes clients.\aYuki a dit qu'elle avait vu un Passetout sortir d'ici avec des tickets plein les mains.\aVa voir si tu peux retrouver le ticket pour l'Alaska de Jack Bûcheron.", + INCOMPLETE_PROGRESS : "Ces Passetouts pourraient être n'importe où maintenant...", + }, + 4217 : { QUEST : "Oh, super. Tu l'as trouvé!\aPuisque je peux compter sur toi, va le porter à Jack pour moi, tu veux bien?_where_", + }, + 4218 : { QUEST : "Tralala!\aAlaska, me voilà!\aJe ne peux plus supporter ces Cogs infernaux.\aDis donc, je crois qu'Anna a encore besoin de toi._where_", + }, + 4219 : { QUEST : "Ouais, tu as deviné.\aJ'aurais besoin que tu secoues ces satanés Passetouts pour récupérer le ticket de Tabatha pour la fête du Jazz.\aTu sais comment ça marche...", + INCOMPLETE_PROGRESS : "Il y en a d'autres qui rôdent...", + }, + 4220 : { QUEST : "Adorable!\aTu peux lui emmener ça pour moi aussi?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Sois sympa...", + QUEST : "Super, mon petit!\aMaintenant je suis à la fête, _avName_.\aAvant de partir, tu ferais mieux d'aller voir Anna Banane encore une fois..._where_", + }, + 4222 : { QUEST : "C'est la dernière fois, je te promets!\aMaintenant tu vas chercher le ticket de Barry pour le concours de chant.", + INCOMPLETE_PROGRESS : "Allez, _avName_.\aBarry compte sur toi.", + }, + 4223 : { QUEST : "Ça devrait redonner le sourire à Barry._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Bonjour, Bonjour, BONJOUR!\aSuper!\aJe suis sûr que les gars et moi on a va ramasser le gros lot cette année.\aAnna demande que tu repasses la voir pour récupérer ta récompense._where_\aAu revoir, au revoir, AU REVOIR!", + COMPLETE : "Merci pour toute ton aide, _avName_.\aTu es vraiment un atout pour nous à Toontown.\aEn parlant d'atouts...", + }, + + 4902 : { QUEST : "Va donc voir Léo.\aIl a besoin de quelqu'un pour porter un message._where_", + }, + 4903 : { QUEST : "Pote!\aMes castagnettes sont toutes ternies et j'ai un grand spectacle ce soir. \aEmporte-les donc à Carlos voir s'il peut me les faire reluire._where_", + }, + 4904 : { QUEST : "Voui, yé crois que yé peux réluire ça.\aMé yé bézoin d'encre de seiche bleue.", + GREETING : "¡ Holà!", + LEAVING : "¡ Adiós!", + INCOMPLETE_PROGRESS : "Tou pé trrouver la seiche partout sour lé pontons de pêche.", + }, + 4905 : { QUEST : "Voui! Souperr!\aAhóra yé bézoin d'un peu de temps pour réluire ça.\aTou pé aller récoupérer un bâtiment de oun étage pendant qué yé trravaille?", + GREETING : "¡ Holà!", + LEAVING : "¡ Adiós!", + INCOMPLETE_PROGRESS : "Oun pitite minute...", + }, + 4906 : { QUEST : "Trrès bien!\aVoilà les castagnettes pour Léo._where_", + }, + 4907 : { GREETING : "", + QUEST : "Super, mon petit!\aElles sont superbes!\aMaintenant j'ai besoin que tu me rapportes une copie des paroles de \" Un Noël toon \" de chez Élise._where_", + }, + 4908 : { QUEST: "Holà par ici!\aHmmm, je n'ai pas de copie de cette chanson.\aSi tu me laisses un peu de temps je pourrai la retranscrire de mémoire.\aPourquoi tu n'irais pas faire un tour et reprendre un bâtiment de deux étages pendant que j'écris?", + }, + 4909 : { QUEST : "Je suis désolée.\aMa mémoire est un peu floue.\aSi tu vas reprendre un bâtiment de trois étages, je suis sûre que ce sera fait quand tu reviendras...", + }, + 4910 : { QUEST : "Ça y est!\aDésolée d'avoir mis si longtemps.\aRapporte-ça à Léo._where_", + GREETING : "", + COMPLETE : "Génial, mon petit!\aMon concert va casser la baraque!\aÀ propos de casser, tu pourras utiliser ça sur quelques Cogs..." + }, + 5247 : { QUEST : "Le quartier est assez chaud...\aTu pourrais avoir besoin d'apprendre quelques nouveaux trucs.\a_toNpcName_ m'a appris tout ce que je sais, il peut peut-être t'aider aussi._where_" }, + 5248 : { GREETING : "Ahh, oui.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as l'air d'avoir des difficultés avec cette mission.", + QUEST : "Aah, bienvenue, nouvel apprenti.\aJe sais tout ce qu'on peut savoir à propos du jeu de tartes.\aMais avant qu'on ne commence ton entraînement, une petite démonstration s'impose.\aVa donc faire un tour et vaincre dix des plus gros Cogs." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "Excellent!\aMaintenant tu vas nous montrer ce que tu sais faire à la pêche.\aJ'ai fait tomber trois dés en peluche dans la mare hier.\aVa les pêcher et rapporte-les moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "On dirait que tu n'es pas si habile avec la canne et le moulinet." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Loibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Chefbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Caissbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Vendibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5200 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 5201 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être venu.\aUn groupe de ces Chassetêtes est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5261 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Bifaces est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5262 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Sacasous est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5263 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Tournegris est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5202 : { QUEST : "Le "+lTheBrrrgh+" a été envahi par des Cogs parmi les plus robustes qu'on ait vus.\aTu auras probablement besoin d'emporter plus de gags là-bas.\aJ'ai entendu dire que _toNpcName_ pourrait te prêter un grand sac pour emporter plus de gags._where_" }, + 5203 : { GREETING: "Eh? Tu es dans mon équipe de luge?", + QUEST : "Qu'est-ce que c'est? Tu veux un sac?\aJ'en avais un par là...peut-être qu'il est dans ma luge?\aMais c'est que... Je n'ai pas vu ma luge depuis la grande course!\aPeut-être qu'un de ces Cogs l'a prise?", + LEAVING : "As-tu vu ma luge?", + INCOMPLETE_PROGRESS : "Rappelle-moi qui tu es? Désolé, je suis un peu étourdi depuis l'accident." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Est-ce que c'est ma luge? Je ne vois pas de sac par ici.\aJe crois que Boris Tourne était dans l'équipe...c'est peut-être lui qui l'a?_where_" }, + 5205 : { GREETING : "Oooh, ma tête!", + LEAVING : "", + QUEST : "Hein? Ted qui? Un sac?\aAh, peut-être qu'il était dans notre équipe?\aJ'ai tellement mal à la tête que je n'arrive plus à réfléchir.\aPourrais-tu aller me pêcher des glaçons dans la mare gelée pour ma tête?", + INCOMPLETE_PROGRESS : "Aïe, ma tête me fait mal! Tu as de la glace?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Aah, ma tête va beaucoup mieux!\aAlors tu cherches le sac de Ted, hein?\aJe crois qu'il a atterri sur la tête de Sam Simiesque après l'accident._where_" }, + 5207 : { GREETING : "Hé-ho!", + LEAVING : "", + QUEST : "Quoi c'est ça un sac? Qui c'est ça Bouris?\aMoi avoir peur bâtiments! Toi battre bâtiments, moi te donner sac!", + INCOMPLETE_PROGRESS : "Encore bâtiments! Moi encore peur!", + COMPLETE : "Ooooh! Moi t'aime!" }, + 5208 : { GREETING : "", + LEAVING : "Hein!", + QUEST : "Ooooh! Moi t'aime!\aVa Atelier de ski. Sac là-bas." }, + 5209 : { GREETING : "Pote!", + LEAVING : "'plus!", + QUEST : "Bon sang, ce Sam Simiesque est fou!\aSi tu es aussi malade que Sam, je te donne ton sac.\aVa démolir des Cogs pour ton sac, mon pote! Salut!", + INCOMPLETE_PROGRESS : "Es-tu certain(e) d'être au point? Va donc démolir plus de Cogs. ", + COMPLETE : "Ouah! T'es vachement chouette! C'est un sacré tas de Cogs que tu as bousillés!\aVoilà ton sac!" }, + + 5210 : { QUEST : "_toNpcName_ aime quelqu'un du quartier en secret.\aSi tu l'aides, elle pourrait te donner une belle récompense._where_" }, + 5211 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un bec me l'a dérobée.\aPeux-tu me la rapporter?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + + 5264 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un aileron me l'a dérobée.\aPeux-tu me la rapporter?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5265 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Circulateurs me l'a dérobée.\aPeux-tu me la rapporter?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5266 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Attactics me l'a dérobée.\aPeux-tu me la rapporter?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5212 : { QUEST : "Oh, merci d'avoir retrouvé ma lettre!\aS'il te plaît, s'il te plaît, peux-tu la remettre au plus beau chien du quartier?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas remis ma lettre, n'est-ce pas?", + }, + 5213 : { GREETING : "Charmé, certainement.", + QUEST : "Je ne peux pas m'occuper de ta lettre, tu vois.\aTous mes chiots m'ont été pris!\aSi tu les ramènes, peut-être qu'on pourra parler.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mes pauvres petits chiots!" }, + 5214 : { GREETING : "", + LEAVING : "Youhouuu!", + QUEST : "Merci de m'avoir rapporté mes petits choux.\aRegardons cette lettre maintenant...Mmmm, il semblerait que j'ai une autre admiratrice secrète.\aIl est temps de rendre visite à mon cher ami Carl.\aTu l'aimeras beaucoup, c'est certain._where_" }, + 5215 : { GREETING : "Hé, hé...", + LEAVING : "Reviens, oui, oui.", + INCOMPLETE_PROGRESS : "Il y en a encore des gros par ici. Reviens nous voir quand il n'y en aura plus.", + QUEST : "Qui est-ce qui t'envoie? On aime pas trop les bêcheurs, non...\aMais on aime encore moins les Cogs...\aDébarrasse-nous donc des gros et on t'aidera, oui on t'aidera." }, + 5216 : { QUEST : "On t'avait bien dit qu'on t'aiderait.\aTu peux emmener cette bague à la fille.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as encore la bague???", + COMPLETE : "Oh, tu es un amour!!! Merci!!!\aOh, et j'ai quelque chose de spécial pour toi aussi.", + }, + 5217 : { QUEST : "On dirait que _toNpcName_ pourrait avoir besoin d'aide._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je crois bien qu'il y a d'autres Circulateurs par ici.", + QUEST : "À l'aide!!! À l'aide!!! Je n'en peux plus!\aCes Circulateurs me rendent dingue!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ce n'est pas possible qu'il n'y ait que ça. Je viens d'en voir un!!!", + QUEST : "Oh, merci, mais maintenant ce sont les Attactics!!!\aIl faut que tu m'aides!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Non, non, il y en avait un juste là!", + QUEST : "Je réalise maintenant que ce sont les Usuriers!!!\aJe croyais que tu allais me sauver!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais quoi, peut-être finalement que ce ne sont pas du tout les Cogs!!!\aPourrais-tu demander à Gaëlle de me préparer une potion calmante? Ça m'aiderait peut-être...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Oh, ce Harry, c'est quelqu'un!\aJe vais concocter quelque chose qui le remettra sur pied!\aBon, on dirait que je n'ai plus de moustaches de sardine...\aSois un ange et cours à la mare m'en attraper.", + INCOMPLETE_PROGRESS : "Tu les as, ces moustaches de sardine?", }, + 5223 : { QUEST : "OK. Merci, mon ange.\aVoilà, maintenant porte ça à Harry. Ça devrait le calmer tout de suite.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vas-y maintenant, emporte la potion à Harry.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu vas attraper ces Avocageots pour moi, n'est-ce pas?", + QUEST : "Oh merci mon Dieu tu es de retour!\aDonne-moi la potion, vite!!!\aGlou, glou, glou...\aBerk, c'est dégoûtant!!\aMais tu sais quoi? Je me sens bien plus calme. Maintenant que j'ai les idées claires, je réalise que...\aC'est les Avocageots qui me rendaient malade pendant tout ce temps!!!", + COMPLETE : "Bon sang! Maintenant je peux me détendre!\aJ'ai sûrement quelque chose à te donner. Oh, prends ça!" }, + 5225 : { QUEST : "Depuis l'incident avec le pain de navets, Phil Électrique est furieux après _toNpcName_.\aTu pourrais peut-être aider Paul à les réconcilier?_where_" }, + 5226 : { QUEST : "Ouais, tu as sans doute entendu dire que Phil Électrique est furieux contre moi...\aJ'essayais juste d'être gentil avec ce pain de navets.\aPeut-être que tu pourrais le remettre de bonne humeur.\aPhil a horreur de ces Cogs Caissbots, surtout leurs bâtiments.\aSi tu reprends des bâtiments Caissbot, ça pourrait aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Peut-être quelques bâtiments de plus?", }, + 5227 : { QUEST : "C'est formidable! Va dire à Phil ce que tu as fait._where_" }, + 5228 : { QUEST : "Oh il a fait ça?\aCe Paul croit qu'il peut s'en tirer comme ça, hein?\aIl m'a cassé ma dent, oui, avec son fichu pain de navets!\aPeut-être que si tu amenais ma dent au Dr Marmotter, il pourrait la réparer.", + GREETING : "Mmmmrrphh.", + LEAVING : "Grrr, grrr.", + INCOMPLETE_PROGRESS : "Encore toi? Je pensais que tu allais faire réparer ma dent.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Caissbots?\aIls effraient mes clients." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Vendibots?\aIls effraient mes clients." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Loibots?\aIls effraient mes clients." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Chefbots?\aIls effraient mes clients." }, + 5230 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Pillard me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent?" }, + 5270 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Gros Blochon me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent?" }, + 5271 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement M. Hollywood me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent?" }, + 5272 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Chouffleur me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent?" }, + 5231 : { QUEST : "Super, voilà la dent!\aPourquoi ne filerais-tu pas chez Phil pour lui porter?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je parie que Phil serait content de voir sa nouvelle dent.", + }, + 5232 : { QUEST : "Oh, merci.\aMmmrrrphhhh\aÇa a l'air de quoi, hein?\aOK, tu peux dire à Paul que je lui pardonne.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, bonne nouvelle.\aJe savais bien que ce vieux Phil ne pourrait pas rester fâché contre moi.\aPour prouver ma bonne volonté, je lui ai fait cuire ce pain de pommes de pin.\aPourrais-tu lui porter, s'il te plaît?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Presse-toi donc. Le pain de pommes de pin est meilleur chaud.", + COMPLETE : "Oh, qu'est-ce que c'est que ça? Pour moi?\aGromp, gromp...\aAïïïaïïe! Ma dent! Ce Paul Poulemouillée!\aOh, après tout ce n'est pas ta faute. Voilà, prends ça pour ta peine.", + }, + 903 : { QUEST : "Tu dois te préparer à voir _toNpcName_ le vieillard du blizzard pour ton test final._where_", }, + 5234 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du fromage grumeleux pour notre bouillon.\aLe fromage grumeleux ne se trouve que sur les Cogs Gros Blochons.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de fromage grumeleux." }, + 5278 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du caviar pour notre bouillon.\aLe caviar ne se trouve que dans les Cogs M. Hollywood.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de caviar." }, + 5235 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Pillard l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5279 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Chouffleur l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5236 : { GREETING: "", + QUEST : "Merci beaucoup.\aSlurp, slurp...\aAhhh, maintenant tu dois attraper un crapaud parlant. Essaie d'en pêcher dans la mare.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce crapaud parlant?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore gagné ton dessert.", + QUEST : "Oh, c'est vraiment un crapaud parlant. Donne-le moi.\aQu'est-ce que tu dis, crapaud?\aCouac.\aCouac.\aLe crapaud a parlé. Nous avons besoin de dessert.\aRapporte-nous des cônes de glace de chez _toNpcName_.\aLe crapaud aime la glace aux haricots rouges pour une raison inconnue._where_", }, + 5238 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour M. Hollywood ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu déjà trouvé tous mes cônes de glace?" }, + 5280 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour le Gros Blochon ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé tous mes cônes de glace?" }, + 5239 : { QUEST : "Merci de m'avoir rapporté mes cônes de glace!\aEn voilà un pour Allan Bic.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ferais mieux de porter cette glace à Allan Bic avant qu'elle ne fonde.", }, + 5240 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe pense que ces Cogs Chouffleurs ont quelquefois de la poudre dans leurs perruques.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre?" }, + 5281 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe crois que ces Cogs M. Hollywood ont quelquefois de la poudre pour se poudrer le nez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre?" }, + 5241 : { QUEST : "OK.\aComme je l'ai déjà dit, pour bien lancer une tarte, tu ne dois pas la lancer avec la main...\a...mais avec ton âme.\aJe ne sais pas ce que cela veut dire, alors je vais m'asseoir et réfléchir pendant que tu récupères des bâtiments.\aReviens quand tu as terminé ton défi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ton défi n'est pas terminé.", }, + 5242 : { GREETING: "", + QUEST : "Bien que je ne sache toujours pas de quoi je suis en train de parler, tu es vraiment quelqu'un de valeur.\aJe te donne un dernier défi...\aLe crapaud parlant voudrait une petite amie.\aTrouve un autre crapaud parlant. Le crapaud a parlé.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est cet autre crapaud parlant?", + COMPLETE : "Houlala! Je suis fatigué par tous ces efforts. Je dois me reposer maintenant.\aTiens, prends ta récompense et va t'en." }, + + 5243 : { QUEST : "Pierre Lasueur commence à empester dans la rue.\aPeux-tu essayer de le convaincre de prendre une douche par exemple?_where_" }, + 5244 : { GREETING: "", + QUEST : "Oui, je crois que je dois commencer à transpirer pas mal.\aMmmm, peut-être que si je pouvais réparer ce tuyau qui fuit dans ma douche...\aJe crois qu'un pignon de l'un de ces tous petits Cogs ferait l'affaire.\aVa trouver un pignon de Microchef et on va essayer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce pignon que tu étais parti chercher?" }, + 5245 : { GREETING: "", + QUEST : "Ouaip, on dirait que ça va.\aMais je me sens seul quand je prends ma douche...\aPourrais-tu aller me pêcher un canard en plastique pour me tenir compagnie?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors ce canard?" }, + 5246 : { QUEST : "Le canard en plastique est génial, mais...\aTous ces bâtiments tout autour me rendent nerveux.\aJe me sentirais beaucoup plus détendu s'il y avait moins de bâtiments.", + LEAVING : "", + COMPLETE : "Ok, je vais prendre ma douche maintenant. Et voilà aussi quelque chose pour toi.", + INCOMPLETE_PROGRESS : "Je suis toujours embêté au sujet des bâtiments.", }, + 5251 : { QUEST : "Sébastien Toutseul est censé faire un concert ce soir.\aJ'ai entendu dire qu'il pourrait avoir des problèmes avec son matériel._where_" }, + 5252 : { GREETING: "", + QUEST : "Oh ouais! Sûr que j'aurais besoin d'aide.\aCes Cogs sont arrivés et ont piqué tout mon matériel pendant que je déchargeais la camionnette.\aTu pourrais me donner un coup de main pour retrouver mon micro?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hé mon pote, je ne peux pas chanter sans micro." }, + 5253 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Attactics a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier?" }, + 5273 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Circulateurs a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier?" }, + 5274 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Usuriers a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier?" }, + 5275 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Avocageots a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier?" }, + 5254 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont sûrement aux pieds d'un M Hollywood.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le "+lTheBrrrgh+" ce soir?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5282 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Gros Blochon.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le "+lTheBrrrgh+" ce soir?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5283 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Pillard.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le "+lTheBrrrgh+" ce soir?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5284 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Chouffleur.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le "+lTheBrrrgh+" ce soir?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + + 5255 : { QUEST : "On dirait que tu as besoin de plus de rigolpoints.\aPeut-être que tu pourrais passer un marché avec _toNpcName_.\aVérifie que c'est fait par écrit..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Chefbots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Loibots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Vendibots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Vendibots." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Caissbots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Caissbots." }, + } + +# ChatGarbler.py +ChatGarblerDog = ["ouaf", "ouarf", "rrrgh"] +ChatGarblerCat = ["miaou", "maaou"] +ChatGarblerMouse = ["couic", "couiiic", "iiiiic"] +ChatGarblerHorse = ["hihiii", "brrr"] +ChatGarblerRabbit = ["ouic", "pouip", "plouik", "bouip"] +ChatGarblerFowl = ["coin", "couac", "coiinc"] +ChatGarblerDefault = ["bla bla"] + +# AvatarDNA.py +Bossbot = "Chefbot" +Lawbot = "Loibot" +Cashbot = "Caissbot" +Sellbot = "Vendibot" +BossbotS = "un Chefbot" +LawbotS = "un Loibot" +CashbotS = "un Caissbot" +SellbotS = "un Vendibot" +BossbotP = "des Chefbots" +LawbotP = "des Loibots" +CashbotP = "des Caissbots" +SellbotP = "des Vendibots" +BossbotSkelS = "un Chefbot Skelecog" +LawbotSkelS = "un Loibot Skelecog" +CashbotSkelS = "un Caissbot Skelecog" +SellbotSkelS = "un Vendibot Skelecog" +BossbotSkelP = "des Chefbots Skelecogs" +LawbotSkelP = "des Loibots Skelecogs" +CashbotSkelP = "des Caissbots Skelecogs" +SellbotSkelP = "des Vendibots Skelecogs" + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Recherche de coordonnées pour %s." +AvatarDetailPanelFailedLookup = "Impossible d'obtenir les coordonnées de %s." +AvatarDetailPanelOnline = "District : %(district)s\nLocation: %(location)s" +AvatarDetailPanelOffline = "District : hors-ligne\nLieu : hors-ligne" + +# AvatarPanel.py +AvatarPanelFriends = "Amis" +AvatarPanelWhisper = "Chuchoter" +AvatarPanelSecrets = "Secrets" +AvatarPanelGoTo = "Aller à" +AvatarPanelIgnore = "Ignorer" +#AvatarPanelCogDetail = "Dépt : %s\nNiveau: %s\n" +AvatarPanelCogLevel = "Niveau : %s" +AvatarPanelCogDetailClose = lClose + +# DistributedAvatar.py +WhisperNoLongerFriend = "%s a quitté ta liste d'amis." +WhisperNowSpecialFriend = "%s est maintenant ton ami(e) secret(e)!" +WhisperComingToVisit = "%s vient te voir." +WhisperFailedVisit = "%s a essayé de venir te voir." +WhisperTargetLeftVisit = "%s est parti ailleurs. Essaie encore!" +WhisperGiveupVisit = "%s n'a pas pu te trouver parce que tu te déplaces!" +WhisperIgnored = "%s t'ignore!" +TeleportGreeting = "Salut, %s." +DialogSpecial = "ooo" +DialogExclamation = "!" +DialogQuestion = "?" +# Cutoff string lengths to determine how much barking to play +DialogLength1 = 6 +DialogLength2 = 12 +DialogLength3 = 20 + +# LocalAvatar.py +FriendsListLabel = "Amis" +WhisperFriendComingOnline = "%s se connecte!" +WhisperFriendLoggedOut = "%s s'est déconnecté." + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Essaie d'aller à %s." +TeleportPanelNotAvailable = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelIgnored = "%s t'ignore" +TeleportPanelNotOnline = "%s n'est pas en ligne en ce moment." +TeleportPanelWentAway = "%s est parti(e)." +TeleportPanelUnknownHood = "Tu ne sais pas aller jusqu'à %s!" +TeleportPanelUnavailableHood = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelDenySelf = "Tu ne peux pas aller te voir toi-même!" +TeleportPanelOtherShard = "%(avName)s est dans le district %(shardName)s, et tu es dans le district %(myShardName)s. Veux-tu aller à %(shardName)s?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Je suis le chef." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Je suis le contremaître." +FactoryBossBattleTaunt = "Je te présente le contremaître." + +# HealJokes.py +ToonHealJokes = [ + ["Qu'est ce qui fait PIOU-PIOU?", + "Un poussin de 500 kilos! "], + ["Que dit un pneu qui va voir un médecin?", + "Docteur, je me sens crevé."], + ["Pourquoi est-ce difficile pour un fantôme de mentir?", + "Parce qu'il est cousu de fil blanc."], + ["Vous connaissez l'histoire de la chaise?", + "Dommage, elle est pliante!"], + ["Qu'est-ce qui est vert et qui monte et qui descend?", + "Un petit pois dans un ascenseur!"], + ["Quel est le comble de l'électricien?", + "De ne pas être au courant."], + ["Que font deux chiens qui se rencontrent à Tokyo?", + "Ils se jappent au nez."], + ["Quel est le futur de \" je baille \"?", + "Je dors."], + ["Quel est l'animal le plus rapide?", + "Le pou car il est toujours en tête!"], + ["Quel animal n'a jamais soif?", + "Le zébu, parce que quand zébu zé plus soif!"], + ["Quel est le comble pour un myope?", + "De manger des lentilles."], + ["Pourquoi as-tu mis le journal dans le réfrigérateur?", + "Pour avoir des nouvelles fraîches!"], + ["Qu'est-ce qui est gris et qui t'éclabousse de confiture?", + "Une souris qui mange un beignet."], + ["Que demande un douanier à un cochon qui passe la frontière?", + "Son passe-porc."], + ["Que dit un bébé souris à sa maman quand il voit passer une chauve-souris?", + "Maman, un ange!"], + ["Comment appelle-t-on un ascenseur au Japon?", + "En appuyant sur le bouton."], + ["Comment appelle-t-on un poisson pas encore né?", + "Un poisson pané."], + ["Si tu fais tomber un chapeau blanc dans la mer rouge, comment ressort-il?", + "Mouillé."], + ["Que demande un chat qui entre dans une pharmacie?", + "Du sirop pour matou."], + ["Quel est le comble pour un jockey?", + "D'être à cheval sur les principes."], + ["Quelles sont les deux choses que tu ne peux pas prendre au petit-déjeuner?", + "Le déjeuner et le dîner."], + ["Qu'est ce qu'on donne à un éléphant qui a de grands pieds?", + "De grandes chaussures."], + ["Comment sait-on qu'un éléphant est caché dans le réfrigérateur?", + "Aux empreintes de pattes dans le beurre."], + ["Quelle est la différence entre un instituteur et un thermomètre?", + "Aucune, on tremble toujours quand ils marquent zéro!"], + ["Qu'est-ce qui est petit, carré et vert?", + "Un petit carré vert."], + ["Quel est le comble pour un éléphant?", + "D'être sans défense."], + ["Que dit le 0 au 8?", + "Tiens, tu as mis ta ceinture!"], + ["Qu'est ce qu'il ne faut jamais faire devant un poisson-scie?", + "La planche!"], + ["Pourquoi est-ce que certaines personnes travaillent la nuit?", + "Pour mettre leur travail à jour."], + ["Quel est le comble de la patience?", + "Trier des petits pois avec des gants de boxe."], + ["Qu'est ce qui voyage tout autour du monde en restant dans son coin?", + "Un timbre."], + ["Quel est le comble pour une souris?", + "Avoir un chat dans la gorge."], + ["Quel est le comble pour un canard?", + "En avoir marre!"], + ["Quel est le comble pour un magicien?", + "Se nourrir d'illusions."], + ["Quel est le comble de la clé?", + "Se faire mettre à la porte."], + ["Quel est le comble pour un cordonnier?", + "Avoir les dents qui se déchaussent."], + ["De quelle couleur sont les petits pois?", + "Les petits poissons rouges."], + ["Qu'est-ce qui baille et qui ne dort jamais?", + "Une porte."], + ["Tu sais ce que c'est un canif?", + "C'est un p'tit fien!"], + ["Qu'est-ce qu'un chou au fond d'une baignoire?", + "Un choumarin!"], + ["Quel est le comble pour le propriétaire d'un champ de pommiers?", + "Travailler pour des prunes!"], + ["Qu'est-ce qui est aussi grand que l'Arc de Triomphe mais ne pèse rien?", + "Son ombre."], + ["Comment s'appelle un boomerang qui ne revient pas?", + "Un bout de bois."], + ["Pourquoi est-ce que les éléphants se déplacent en troupeau compact?", + "Parce que c'est celui du milieu qui a la radio."], + ["De quelle couleur sont les parapluies quand il pleut?", + "Ils sont tout verts."], + ["Quel est le comble du torero?", + "Que le taureau soit vache."], + ["Quel est l'animal le plus heureux?", + "Le hibou, parce que sa femme est chouette."], + ["Que dit un vitrier à son fils?", + "Tiens-toi à carreau si tu veux une glace."], + ["Comment appelle-t-on un chien sans pattes?", + "On ne l'appelle pas, on va le chercher."], + ["Qu'ont les girafes que n'ont pas les autres animaux?", + "Des bébés girafes."], + ["Un chameau peut-il avoir 3 bosses?", + "Oui, s'il se cogne la tête contre le mur."], + ["Pourquoi les musiciens aiment-ils prendre le train?", + "Parce que la voie fait ré."], + ["Deux fourmis sont sur un âne, laquelle va doubler l'autre?", + "Aucune, il est interdit de doubler sur un dos d'âne."], + ["Quelle est la note la plus basse?", + "Le sol."], + ["Pourquoi les trains électriques vont-ils plus vite que les trains à vapeur?", + "Parce qu'ils ont arrêté de fumer."], + ["Qu'est ce qu'un arbuste dit à un géranium?", + "Espèce d'empoté!"], + ["Que recommande la maman allumette à ses enfants?", + "Surtout, ne vous grattez pas la tête!"], + ["Qu'est-ce qu'il y a à la fin de tout?", + "La lettre T."], + ["Pourquoi les poissons-chats s'ennuient-ils?", + "Parce qu'il n'y a pas de poissons-souris."], + ["Qu'est-ce que le vainqueur du marathon a perdu?", + "Son souffle."], + ["Comment appelle-t-on un spectacle qui rend propre?", + "Un ballet."], + ["Qu'est-ce qui fait 999 fois \" Tic \" et une fois \" Toc \"?", + "Un mille-pattes avec une jambe de bois."], + ["Comment reconnaît-on un écureuil d'une fourchette?", + "En les mettant au pied d'un arbre, celui qui monte est l'écureuil."], + ["Pourquoi les flamants roses lèvent-ils une patte en dormant?", + "Parce qu'ils tomberaient s'ils levaient les deux."], + ["Qu'est-ce qui est noir quand il est propre et blanc quand il est sale?", + "Un tableau noir!"], + ["Qu'est-ce qui fait Oh, Oh, Oh?", + "Le Père Noël qui marche en arrière."], + ["Qu'est-ce qui peut voyager jour et nuit sans quitter son lit?", + "La rivière."], + ["Quel arbre n'aime pas la vitesse?", + "Le frêne."], + ["Pourquoi est-ce que les dinosaures ont de longs cous?", + "Parce que leurs pieds sentent mauvais."], + ["Qu'est-ce qui est jaune et qui court très vite?", + "Un citron pressé."], + ["Pourquoi est-ce que les éléphants n'oublient jamais?", + "Parce qu'on ne leur dit jamais rien."], + ["Quel animal peut changer de tête facilement?", + "Un pou."], + ["Qu'est-ce qu'un steak caché derrière un arbre?", + "Un steak caché."], + ["Pourquoi est-ce que les serpents ne sont pas susceptibles?", + "Parce qu'on ne peut pas leur casser les pieds."], + ["Pourquoi dit-on que les boulangers travaillent rapidement?", + "Parce qu'ils travaillent en un éclair."], + ["Que dit un fantôme quand il est ennuyé?", + "Je suis dans de beaux draps!"], + ["Comment peut-on arrêter un éléphant qui veut passer dans le chas d'une aiguille?", + "On fait un nœud à sa queue."], + ["Pourquoi est-ce que les pompiers ont des bretelles rouges?", + "Pour tenir leurs pantalons!"], + ["Que prend un éléphant lorsqu'il rentre dans un bar?", + "De la place!"], + ["Savez-vous que votre chien aboie toute la nuit?", + "Ça ne fait rien, il dort toute la journée!"], + ["Savez-vous que le vétérinaire a épousé la manucure?", + "Au bout d'un mois ils se battaient becs et ongles."], + ["Tu sais que nous sommes sur terre pour travailler?", + "Bon, alors plus tard je serai marin."], + ["Quand je dis \" il pleuvait \", de quel temps s'agit-il?", + "D'un sale temps."], + ["À quoi reconnaît-on un motard heureux?", + "Aux moustiques collés sur ses dents."], + ["Son succès lui est monté à la tête.", + "C'est normal, c'est là qu'il y avait le plus de place libre."], + ["Qu'est-ce qui est gris, pousse de petits cris et fait 5 kilos?", + "Une souris qui a besoin de se mettre au régime."], + ["Que dit-on à un croque-mort qui rentre dans un café?", + "\" Je vous sers une bière? \""], + ["Connais-tu l'histoire du lit vertical?", + "C'est une histoire à dormir debout."], + ["Pourquoi est-ce que les éléphants sont gros et gris?", + "Parce que s'ils étaient petits et jaunes ce seraient des canaris."], + ["Combien coûte cet aspirateur?", + "750 et des poussières."], + ["Quel est le comble pour un juge gourmand?", + "De manger des avocats."], + ["Pourquoi "+ Donald + " regarde-t-il à droite et à gauche lorsqu'il rentre dans une pièce?", + "Parce qu'il ne peut pas regarder des deux côtés à la fois."], + ["Pourquoi est-ce que "+ Goofy + " emmène son peigne chez le dentiste?", + "Parce qu'il a perdu toutes ses dents."], + ["Quel bruit fait la fourmi?", + "La fourmi cro-onde."], + ["Si les sorties étaient surveillées, comment le voleur a-t-il pu s'échapper?", + "Par l'entrée!"], + ["Que dit un haut-parleur à un autre haut-parleur?", + "Tu veux une baffle?"], + ["Pourquoi les lézards aiment-ils les vieux murs?", + "Parce qu'ils ont des lézardes."], + ["Pourquoi est-ce que les moutons ont des pelages en laine?", + "Parce qu'ils auraient l'air idiots avec des pelages en synthétique."], + ["Où trouve-t-on le dimanche avant le jeudi?", + "Dans le dictionnaire."], + ["Pourquoi est-ce que "+ Pluto + " a dormi avec une peau de banane?", + "Pour pouvoir se glisser hors de son lit le lendemain matin."], + ["Pourquoi est-ce que la souris portait des chaussons noirs?", + "Parce que les blancs étaient à la lessive."], + ["Quel est le point commun entre les fausses dents et les étoiles?", + "Elles sortent la nuit."], + ["Pourquoi est-ce que les chats aiment se faire photographier?", + "Parce qu'on leur dit \" souris! \"."], + ["Pourquoi est-ce que l'archéologue a fait faillite?", + "Parce que sa carrière était en ruines."], + ["Qui boit l'eau sans jamais l'avaler?", + "L'éponge."], + ["Quelle est la couleur du virus de la grippe?", + "Gris pâle."], + ["Pourquoi faut-il craindre le soleil?", + "Parce que c'est le plus grand des astres."], + ["Quel est le comble d'un avion?", + "C'est d'avoir un antivol."], + ["Que dit la nappe à la table?", + "Ne crains rien, je te couvre."], + ["Que fait "+ Goofy + " quand il tombe dans l'eau?", + "PLOUF!"], + ["Quel est le comble pour un crayon?", + "Se tailler pour avoir bonne mine."], + ["Que dit la grosse cheminée à la petite cheminée?", + "Tu es trop jeune pour fumer."], + ["Que dit le tapis au carrelage?", + "Ne t'inquiète pas, je te couvre."], + ["Quelle est la différence entre le cancre et le premier de la classe?", + "Quand le cancre redouble, c'est rarement d'attention."], + ["Qu'est-ce qui fait zzzb zzzb?", + "Une guêpe qui vole à l'envers."], + ["Comment appelle-t-on quelqu'un qui tue son beau-frère?", + "Un insecticide, car il tue l'époux de sa sœur."], + ["Comment appelle-t-on un dinosaure qui n'est jamais en retard?", + "Un promptosaure."], + ["On ne devrait pas dire \" un chapitre \".", + "On devrait dire \" un chat rigolo \"."], + ["On ne devrait pas dire \" un perroquet \".", + "On devrait dire \" mon papa est d'accord \"."], + ["On ne devrait pas dire \" bosser à la chaîne \".", + "On devrait dire \" travailler à la télé \"."], + ["Pourquoi est-ce que le livre de maths était malheureux?", + "Parce qu'il avait trop de problèmes."], + ["On ne devrait pas dire \" un match interminable \".", + "On devrait dire \" une rencontre de mauvais joueurs \"."], + ["On ne devrait pas dire \" la maîtresse d'école \".", + "On devrait dire \" l'institutrice prend l'avion \"."], + ["Que voit-on quand deux mille-pattes se serrent la main?", + "Une fermeture-éclair."], + ["Comment appelle-t-on un journal publié au Sahara?", + "Un hebdromadaire."], + ["Que doit planter un agriculteur frileux?", + "Un champ d'ail."], + ["Quel est le comble du chauve?", + "Avoir un cheveu sur la langue."], + ["Qu'est-ce que tu trouves si tu croises un éléphant avec un corbeau?", + "Des tas de poteaux téléphoniques cassés."], + ["Combien gagne un fakir?", + "Des clous!"], + ["Quelle est la meilleure manière d'économiser l'eau?", + "La diluer."], + ["Quelle différence y a t il entre un horloger et une girouette?", + "L'horloger vend des montres et la girouette montre le vent."], + ["Pourquoi est-ce que les ordinateurs se grattent?", + "Parce qu'ils sont pleins de puces."], + ["Qu'est-ce qui a un chapeau et pas de tête, un pied mais pas de souliers?", + "Un champignon."], + ["Pourquoi est-ce que le ciel est haut?", + "Pour éviter que les oiseaux ne se cognent la tête en volant."], + ["Qu'est ce qui est pire qu'une girafe qui a mal à la gorge?", + "Un mille-pattes avec des cors aux pieds."], + ["Qu'est-ce qui fait ABC...gloups...DEF...gloups?", + "Quelqu'un qui mange de la soupe aux pâtes alphabet."], + ["Qu'est-ce qui est blanc et qui va vite?", + "Un frigo de course."], + ["Quel est le fruit que les poissons n'aiment pas?", + "La pêche!"], + ["Comment font les éléphants pour traverser un étang?", + "Ils sautent de nénuphar en nénuphar."], + ["Qu'est-ce qui est noir et blanc à pois rouges?", + "Un Dalmatien qui a la rougeole."], + ["Qu'est-ce qu'un chalumeau?", + "Un drolumadaire à 2 bosses."], + ["Pourquoi les éléphants sont-ils gris?", + "Pour ne pas les confondre avec les fraises."], + ["Qu'est-ce qui est gris, fait 100 kilos et appelle \" Minou, Minou! \"?", + "Une souris de 100 kilos."], + ["Quel est le point commun entre un pâtissier et un ciel d'orage?", + "Tous les deux font des éclairs."], + ["Quel bruit font les esquimaux lorsqu'ils boivent?", + "Iglou, iglou, iglou"], + ["Comment appelle-t-on une chauve-souris avec une perruque?", + "Une souris."], + ["Pourquoi les aiguilles sont-elles moins intelligentes que les épingles?", + "Parce qu'elles n'ont pas de tête."], + ["Qu'est-ce qui a de la fourrure, miaule et chasse les souris sous l'eau?", + "Un poisson-chat."], + ["Comment fait-on aboyer un chat?", + "Si on lui donne une tasse de lait il la boit."], + ["Qu'est-ce qui est vert à l'extérieur et jaune à l'intérieur?", + "Une banane déguisée en concombre."], + ["Qu'est-ce qu'un ingrat?", + "Le contraire d'un géant maigre."], + ["Qu'est-ce qui pèse 4 tonnes, a une trompe et est rouge vif?", + "Un éléphant qui a honte."], + ["Dans un virage à 60 degrés à droite, quelle est la roue qui tourne le moins vite?", + "La roue de secours."], + ["Comment reconnaît-on un idiot dans un magasin de chaussures?", + "C'est celui qui essaie les boîtes."], + ["Que dit-on d'un enfant qui ramène le pain à la maison?", + "C'est le petit calepin."], + ["Que dit la cacahuète à l'éléphant?", + "Rien, les cacahuètes ne parlent pas."], + ["Que dit un éléphant lorsqu'il se heurte à un autre éléphant?", + "Le monde est petit, n'est-ce pas?"], + ["Que dit la comptable à la machine à calculer?", + "Je compte sur toi."], + ["Que dit la puce à une autre puce?", + "On y va à pied ou on prend le chat?"], + ["Que dit la grande aiguille à la petite aiguille?", + "Attends une minute."], + ["Que dit une poule quand elle rencontre une autre poule?", + "Tu viens, on va prendre un ver?"], + ["Que dit le collant à la chaussure?", + "À plus tard, je dois filer."], + ["Papa kangourou demande à sa fille qui rentre de l'école : \" Alors, cet examen? \"", + "\" C'est dans la poche, pas de problème! \""], + ["Quelle est la ville de France la plus féroce?", + "Lyon."], + ["Quelle est la ville de France la moins légère?", + "Lourdes."], + ["Pourquoi porte-t-on des vêtements?", + "Parce qu'ils ne peuvent pas marcher tout seuls."], + ["Que dit une pomme de terre quand elle en voit une autre se faire écraser dans la rue?", + "\" Oh, purée! \""], + ["Que dit un petit fakir quand il arrive en retard à l'école?", + "\" Pardon maîtresse, je me suis endormi sur le passage clouté! \""], + ["Que dit un marin-pêcheur s'il se dispute avec un autre marin-pêcheur?", + "Je ne veux pas que tu me parles sur ce thon!"], + ["Pourquoi les cultivateurs disent-ils des gros mots à leurs tomates?", + "Pour les faire rougir."], + ["Que disent deux vers de terre s'ils se rencontrent au milieu d'une pomme?", + "\" Vous habitez dans le quartier? \""], + ["Qu'est-ce que se disent deux serpents qui se rencontrent?", + "\" Quelle heure reptile? \""], + ["Pourquoi les mille-pattes ne peuvent-ils pas jouer au hockey?", + "Le temps d'enfiler leurs patins, la partie est déjà terminée!"], + ["Comment fait-on cuire un poisson dans un piano?", + "On fait Do, Ré, La, Sol."], + ["Connaissez-vous l'histoire du chauffeur d'autobus?", + "Moi non plus, j'étais à l'arrière!"], + ["Crois-tu aux girafes?", + "Non, c'est un cou monté."], + ["Que dit un crocodile s'il rencontre un chien?", + "Salut, sac à puces!"], + ["Que dit un chien quand il rencontre un crocodile?", + "Salut, sac à main!"], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","hou","ha","rhaa") +MovieHealLaughterHits1= ("Ha ha ha","Hi hi","Hé hé","Ha ha") +MovieHealLaughterHits2= ("OUARF OUARF OUARF!","HO HO HO!","HA HA HA!") + +# MovieSOS.py +MovieSOSCallHelp = "%s À L'AIDE!" +MovieSOSWhisperHelp = "%s a besoin d'aide pour un combat!" +MovieSOSObserverHelp = "À L'AIDE!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Salut, %s! C'est un plaisir de pouvoir t'aider!" +MovieNPCSOSGoodbye = "À plus tard!" +MovieNPCSOSToonsHit = "Les Toons font toujours mouche!" +MovieNPCSOSCogsMiss = "Les Cogs ratent toujours leurs cibles!" +MovieNPCSOSRestockGags = "En train de faire le plein de gags %s!" +MovieNPCSOSHeal = "Guérison" +MovieNPCSOSTrap = "Piégeage" +MovieNPCSOSLure = "Leurre" +MovieNPCSOSSound = "Tapage" +MovieNPCSOSThrow = "Lancer" +MovieNPCSOSSquirt = "Éclaboussure" +MovieNPCSOSDrop = "Chute" +MovieNPCSOSAll = "Tout" + +# MovieSuitAttacks.py +MovieSuitCancelled = "ANNULÉ\nANNULÉ\nANNULÉ" + +# RewardPanel.py +RewardPanelToonTasks = "Défitoons" +RewardPanelItems = "Objets récupérés" +RewardPanelMissedItems = "Objets non récupérés" +RewardPanelQuestLabel = "Quête %s" +RewardPanelCongratsStrings = ["Ouais!", "Bravo!", "Ouah!", + "Sympa!", "Atmosphérique!", "Toontastique!"] +RewardPanelNewGag = "Nouveau gag %(gagName)s pour %(avName)s!" +RewardPanelMeritsMaxed = "Au maximum" +RewardPanelMeritBarLabel = "Mérites" +RewardPanelMeritAlert = "Prêt pour la promotion!" + +RewardPanelCogPart = "Tu as gagné un morceau de déguisement de Cog!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Toon normal", "tu seras normal(e)"), + ("Grosse tête", "tu auras une grosse tête"), + ("Petite tête", "tu auras une petite tête"), + ("Grosses jambes", "tu auras de grosses jambes"), + ("Petites jambes", "tu auras de petites jambes"), + ("Gros Toon", "tu seras un peu plus gros(se)"), + ("Petit Toon", "tu seras un peu plus petit(e)"), + ("À plat", "tu seras en deux dimensions"), + ("Profil plat", "tu seras en deux dimensions"), + ("Transparent", "tu seras transparent(e)"), + ("Sans couleur", "tu seras incolore"), + ("Toon invisible", "tu seras invisible"), + ] +CheesyEffectIndefinite = "Jusqu'à ce que tu choisisses un autre effet, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Pendant les %(time)s prochaines minutes, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Pendant les %(time)s prochaines heures, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Pendant les %(time)s prochains jours, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " pendant que tu es dans %s" +CheesyEffectExceptIn = ", excepté dans %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Laquaistic" +SuitPencilPusher = "Gratte-\npapier" +SuitYesman = "Béniouioui" +SuitMicromanager = "Micro\3chef" +SuitDownsizer = "Touptisseur" +SuitHeadHunter = "Chassetête" +SuitCorporateRaider = "Attactic" +SuitTheBigCheese = "Gros Blochon" +SuitColdCaller = "Cassepied" +SuitTelemarketer = "Télé\3vendeur" +SuitNameDropper = "Cafteur" +SuitGladHander = "Passetout" +SuitMoverShaker = "Secousse-\ncousse" +SuitTwoFace = "Biface" +SuitTheMingler = "Le Circulateur" +SuitMrHollywood = "M. Hollywood" +SuitShortChange = "Gardoseille" +SuitPennyPincher = "Radino" +SuitTightwad = "Grippesou" +SuitBeanCounter = "Pince Menu" +SuitNumberCruncher = "Gobechiffre" +SuitMoneyBags = "Sacasous" +SuitLoanShark = "Usurier" +SuitRobberBaron = "Pillard" +SuitBottomFeeder = "Volebas" +SuitBloodsucker = "Pique-\nau-sang" +SuitDoubleTalker = "Charabieur" +SuitAmbulanceChaser = "Charognard" +SuitBackStabber = "Frappedos" +SuitSpinDoctor = "Tournegris" +SuitLegalEagle = "Avocageot" +SuitBigWig = "Chouffleur" + +# Singular versions (indefinite article) +SuitFlunkyS = "un Laquaistic" +SuitPencilPusherS = "un Gratte-Papier" +SuitYesmanS = "un Béniouioui" +SuitMicromanagerS = "un Microchef" +SuitDownsizerS = "un Touptisseur" +SuitHeadHunterS = "un Chassetête" +SuitCorporateRaiderS = "un Attactic" +SuitTheBigCheeseS = "un Gros Blochon" +SuitColdCallerS = "un Cassepied" +SuitTelemarketerS = "un Télévendeur" +SuitNameDropperS = "un Cafteur" +SuitGladHanderS = "un Passetout" +SuitMoverShakerS = "un Secousse-cousse" +SuitTwoFaceS = "un Biface" +SuitTheMinglerS = "un Circulateur" +SuitMrHollywoodS = "un M. Hollywood" +SuitShortChangeS = "un Gardoseille" +SuitPennyPincherS = "un Radino" +SuitTightwadS = "un Grippesou" +SuitBeanCounterS = "un Pince-Menu" +SuitNumberCruncherS = "un Gobechiffre" +SuitMoneyBagsS = "un Sacasous" +SuitLoanSharkS = "un Usurier" +SuitRobberBaronS = "un Pillard" +SuitBottomFeederS = "un Volebas" +SuitBloodsuckerS = "un Pique-au-sang" +SuitDoubleTalkerS = "un Charabieur" +SuitAmbulanceChaserS = "un Charognard" +SuitBackStabberS = "un Frappedos" +SuitSpinDoctorS = "un Tournegris" +SuitLegalEagleS = "un Avocageot" +SuitBigWigS = "un Chouffleur" + +# Plural versions +SuitFlunkyP = "Laquaistics" +SuitPencilPusherP = "Gratte-Papiers" +SuitYesmanP = "Béniouiouis" +SuitMicromanagerP = "Microchefs" +SuitDownsizerP = "Touptisseurs" +SuitHeadHunterP = "Chassetêtes" +SuitCorporateRaiderP = "Attactics" +SuitTheBigCheeseP = "Gros Blochons" +SuitColdCallerP = "Cassepieds" +SuitTelemarketerP = "Télévendeurs" +SuitNameDropperP = "Cafteurs" +SuitGladHanderP = "Passetouts" +SuitMoverShakerP = "Secousse-cousses" +SuitTwoFaceP = "Bifaces" +SuitTheMinglerP = "Les Circulateurs" +SuitMrHollywoodP = "MM. Hollywood" +SuitShortChangeP = "Gardoseilles" +SuitPennyPincherP = "Radinos" +SuitTightwadP = "Grippesous" +SuitBeanCounterP = "Pince Menus" +SuitNumberCruncherP = "Gobechiffres" +SuitMoneyBagsP = "Sacasous" +SuitLoanSharkP = "Usuriers" +SuitRobberBaronP = "Pillards" +SuitBottomFeederP = "Volebas" +SuitBloodsuckerP = "Pique-au-sang" +SuitDoubleTalkerP = "Charabieurs" +SuitAmbulanceChaserP = "Charognards" +SuitBackStabberP = "Frappedos" +SuitSpinDoctorP = "Tournegris" +SuitLegalEagleP = "Avocageots" +SuitBigWigP = "Chouffleurs" + +SuitFaceOffDefaultTaunts = ['Bouh!'] + +SuitFaceoffTaunts = { + 'b': ["Est-ce que tu as un don à me faire?", + "Je vais faire de toi un mauvais perdant.", + "Je vais te mettre à sec.", + "Je suis \" A-Positif \", je vais gagner.", + "Ne sois pas si \" O-Négatif \".", + "Je suis surpris que tu m'aies trouvé, je suis très mobile.", + "Je vais devoir te faire une addition rapide.", + "Tu vas bientôt avoir besoin d'un en-cas.", + "Quand j'aurai fini tu auras une grosse fatigue.", + "Ça ne fait mal qu'un instant.", + "Je vais te faire tourner la tête.", + "Tu arrives à point, je suis en hypo.", + ], + 'm': ["Circule, y'a rien à voir.", + "Tu fréquentes les gens comme moi?", + "Bien, il faut être deux pour avoir de la compagnie.", + "Allons voir la compagnie.", + "Cela a l'air d'un bon endroit pour voir du monde.", + "Bon, est-ce qu'on n'est pas bien ici?", + "Tu frôles la défaite.", + "Je vais me mêler de tes affaires.", + "Est-ce que tu es sûr de vouloir voir du monde?", + ], + 'ms': ["Attends-toi à une bonne secousse.", + "Tu ferais mieux de ne pas rester dans le passage.", + "Tu déménages ou tu perds.", + "Je crois que c'est mon tour.", + "Ça devrait te remuer.", + "Prépare-toi à déménager.", + "Ça va détonner.", + "Attention, Toon, le terrain est instable.", + "Ça va déménager.", + "Je suis tout remué de te battre.", + "Alors, tu trembles?", + ], + 'hh': ["Je suis bien en tête.", + "Tu t'entêtes à tort.", + "Tu as la tête dure.", + "Oh, bien, je te cherchais.", + "J'aurai ta tête.", + "Relève la tête!", + "On dirait que tu as une tête à chercher les ennuis.", + "Tu te payes ma tête?", + "Un trophée parfait pour ma collection.", + "Tu vas avoir un vrai mal de tête.", + "Ne perds pas la tête à cause de moi.", + ], + 'tbc': ["Attention, je vais te faire fondre.", + "Je fais partie du gratin.", + "Je t'ai gru, hier. Je peux être un roc fort quelquefois.", + "Ah, finalement j'avais peur que tu en fasses tout un fromage.", + "Je vais t'écrémer.", + "Tu ne penses pas que je vieillis bien?", + "Je vais te transformer en pâte à tartiner.", + "On me dit que je suis très fort.", + "Fais attention, je connais ta date de péremption.", + "Fais attention, ma force est mon état menthal.", + "Je vais te mouler à la louche.", + ], + 'cr': ["À L'ATTAQUE!", + "Tu n'as pas la culture d'entreprise.", + "Prépare-toi à une descente.", + "On dirait que tu fais l'objet d'une OPA.", + "Ce n'est pas une tenue correcte pour l'entreprise.", + "Tu as l'air vulnérable.", + "Il est temps de transférer tes capitaux.", + "Je suis engagé dans une croisade anti-Toons.", + "Tu es sans défense face à mes idées.", + "Calme-toi, tu verras que tout est pour le mieux.", + ], + 'mh': ["Tu es prêt pour la prise?", + "Lumières, action!", + "Silence, on tourne.", + "Aujourd'hui le rôle du Toon vaincu sera joué par - TOI!", + "Cette scène va être coupée au montage.", + "J'ai déjà une idée de ma motivation pour cette scène.", + "Tu es prêt pour ta scène finale?", + "Je suis prêt à signer ton générique de fin.", + "Je t'ai dit de ne pas m'appeler.", + "Que le spectacle continue!", + "C'est un métier en or!", + "J'espère que tu n'as pas oublié ton texte.", + ], + 'nc': ["On dirait que ton numéro est terminé.", + "J'espère que tu préfères les soustractions.", + "Maintenant tu es vraiment en infériorité numérique.", + "C'est déjà l'heure des comptes?", + "Nous comptons sur quelque chose.", + "Sur quoi voudrais-tu compter aujourd'hui?", + "Ce que tu dis à de l'intérêt.", + "Ça ne va pas être une opération facile.", + "Vas-y, choisis un nombre.", + "Je me contenterais d'un bon chiffre. ", + ], + 'ls': ["C'est le moment de payer tes mensualités.", + "Tu vis sur un emprunt.", + "Ton prêt arrive à échéance.", + "C'est le moment de payer.", + "Tu as demandé une avance et elle est accordée.", + "Tu vas payer ça cher.", + "Il est temps de rembourser.", + "Épargne-moi tes simagrées!", + "C'est bien que tu sois là, je suis dans tous mes états.", + "On en prend un pourcentage?", + "Laisse-moi en profiter.", + ], + 'mb': ["Par ici la monnaie.", + "Je peux empocher ça.", + "Papier ou plastique?", + "Tu as ta sacoche?", + "N'oublie pas, l'argent ne fait pas le bonheur.", + "Attention, j'ai de la réserve.", + "Tu vas avoir des ennuis d'argent.", + "L'argent fait tourner le monde.", + "Je suis trop riche pour ton cholestérol.", + "On n'a jamais trop d'argent!", + ], + 'rb': ["Tu t'es fait voler.", + "Je te dépouillerai de cette victoire.", + "Je t'ennuie royalement!", + "Il faudra tout prendre avec le sourire.", + "Tu devras signaler ce vol.", + "Haut les mains.", + "Je suis un adversaire de valeur.", + "Je vais prendre tout ce que tu as.", + "Tu peux appeler ça un vol de quartier.", + "Tu devrais savoir qu'il ne faut pas parler aux étrangers.", + ], + 'bs': ["Ne me tourne jamais le dos.", + "Tu ne t'en retourneras pas.", + "Tu vas me mettre à dos!", + "Je suis bon pour réduire les frais.", + "Je fais des choses dans ton dos.", + "Tu as le dos au mur.", + "Je suis le meilleur et je peux le prouver sur dossier.", + "Allez, arrière Toon.", + "Laisse-moi te mettre à dos.", + "Tu vas avoir un mal de dos lancinant d'ici peu.", + "C'est le coup parfait.", + ], + 'bw': ["Ne me mets pas à la raie.", + "Tu me fais friser.", + "Je peux te faire une permanente si tu veux.", + "On dirait que tu vas avoir des fourches.", + "Tu ne peux pas affronter la vérité.", + "C'est ton tour de te faire teindre.", + "Je suis content que tu sois à l'heure pour ta coupe.", + "Tu as de gros ennuis.", + "Je vais me mettre un poil en colère.", + "J'ai un gros poil sur la conscience, petit Toon.", + ], + 'le': ["Attention, mon avocat est un peu dur.", + "Je suis vert de rage.", + "Je suis couvert par la loi.", + "Tu devais savoir que j'ai des instincts meurtriers.", + "Je vais te donner des cauchemars judiciaires.", + "Tu ne gagneras pas ce procès.", + "Ça devrait être interdit tellement c'est marrant.", + "Légalement, tu es trop minuscule pour moi.", + "Mon avidité ne connaît aucune limite.", + "C'est une arrestation citoyenne.", + ], + 'sd': ["Tu ne sauras jamais quand j'arrêterai de tourner.", + "Laisse-moi te faire faire un tour.", + "Le docteur va te voir dès qu'il aura fini sa tournée.", + "Je vais te faire tourner.", + "On dirait que tu as besoin d'aller faire un tour.", + "Chacun son tour, le Toon s'en va.", + "Tu n'aimeras pas mon tour de main.", + "Tu vas tourner à fond.", + "Tu veux faire quelques tours avec moi?", + "J'ai un tour de main particulier.", + ], + 'f': ["Je vais parler de toi au patron!", + "Je suis peut-être juste un larbin - mais je suis un vrai dur.", + "Je t'utilise pour monter les échelons.", + "Tu n'aimeras pas la manière dont je travaille.", + "Le patron compte sur moi pour te barrer la route.", + "Tu feras bien sur mon CV.", + "Tu devras me passer sur le corps.", + "Voyons comment tu évalues mon rendement au travail.", + "J'excelle dans la détoonisation.", + "Tu ne verras jamais mon patron.", + "Je te renvoie au terrain de jeux.", + ], + 'p': ["Je vais t'effacer.", + "Tu ne peux pas me gommer.", + "Je suis un numéro 2!", + "Je vais te supprimer de mes listes.", + "Je vais te l'écrire plus clairement.", + "Allons droit au but.", + "Dépêchons-nous, je fais rapidement des taches.", + "Je m'inscris en faux.", + "Tu veux t'inscrire?", + "Tu m'as inscrit sur la liste?", + "Attention, je peux laisser des taches.", + ], + 'ym': ["Je suis certain que tu ne vas pas aimer ça.", + "Je ne connais pas la signification du mot \" non \".", + "Tu veux me voir? C'est quand tu veux.", + "Tu as besoin d'une mise à exécution positive.", + "Je vais te faire une impression positive.", + "Je n'ai encore jamais eu tort.", + "Oui, je suis prêt pour toi.", + "C'est vraiment ce que tu veux?", + "Je suis certain de terminer ça sur une note positive.", + "Je confirme notre rendez-vous.", + "Je n'accepte pas les refus.", + ], + 'mm': ["Je vais me mêler de tes affaires!", + "Quelquefois les gros ennuis ont l'air tout petits.", + "Il n'y a pas de trop petit travail pour moi.", + "Je veux que le travail soit bien fait, donc je le ferai moi-même.", + "Tu as besoin de quelqu'un pour s'occuper de ton capital.", + "Oh, bien, un projet.", + "Chapeau, tu as réussi à me trouver.", + "Je crois que tu as besoin d'un peu de gestion.", + "Je vais m'occuper de toi dans peu de temps.", + "Je surveille le moindre de tes mouvements.", + "C'est vraiment ce que tu veux faire?", + "On va faire ça à ma façon.", + "Je vais espionner tout ce que tu fais.", + "Je peux être très intimidant.", + ], + 'ds': ["Tu descends!", + "Tu as de moins en moins d'options.", + "Attends-toi à des bénéfices en diminution.", + "Tu viens juste de devenir réductible.", + "Ne me demande pas de licencier.", + "Je vais devoir faire quelques coupes claires.", + "Les choses n'ont pas l'air d'aller bien fort pour toi.", + "Tu as l'air tout ratatiné!", + ], + 'cc': ["Surpris de me voir?", + "Tu as appelé?", + "Tu te prépares à accepter ma facture?", + "Ce Casse-pieds ramasse toujours.", + "Je suis un petit malin.", + "Reste en ligne -- je suis là.", + "Tu attendais mon appel?", + "J'espérais que tu répondrais à mon appel.", + "Je vais te faire une impression du diable.", + "J'appelle toujours directement.", + "Eh bien, ta ligne a été transférée.", + "Cet appel va te coûter cher.", + "Il y a de la friture sur la ligne.", + ], + 'tm': ["Je crains que ça ne soit peu pratique pour toi.", + "Est-ce que mon contrat d'assurance pourrait t'intéresser?", + "Tu n'aurais pas dû répondre.", + "Tu ne pourras pas te débarrasser de moi comme ça.", + "Un moment difficile? Bien.", + "J'avais l'intention de te rencontrer.", + "Je vais t'appeler en PCV.", + "J'ai des articles coûteux pour toi aujourd'hui.", + "Dommage pour toi - je démarche à domicile.", + "Je suis préparé à conclure cette affaire rapidement.", + "Je vais utiliser une bonne partie de tes ressources.", + ], + 'nd': ["Je vais traîner ton nom dans la boue.", + "J'espère que tu ne m'en voudras pas si je donne ton nom.", + "On ne s'est pas déjà rencontrés?", + "Dépêchons-nous, je mange avec M. Hollywood.", + "Je t'ai déjà dit que je connaissais Le Circulateur?", + "Tu ne m'oublieras jamais.", + "Je connais tous les gens qu'il faut pour démolir ta réputation.", + "Je crois que je vais rester une minute.", + "Je suis d'humeur à faire tomber des Toons.", + "Tu le dis, je le répète.", + ], + 'gh': ["Mets ça là, Toon.", + "Serrons-nous la main.", + "Ça va me plaire.", + "Tu remarqueras que j'ai une poignée de main très ferme.", + "Concluons ce marché.", + "Occupons-nous des affaires que nous avons à portée de main.", + "Je te dirais sans ménagements que tu as des ennuis.", + "Tu te rendras compte que je suis plutôt manuel.", + "Je peux être pratique.", + "Je suis un gars très pratique, j'ai tout sous la main.", + "Tu veux des vêtements de deuxième main?", + "Laisse-moi te montrer mon travail manuel.", + "Je crois que c'est fait main.", + ], + 'sc': ["Je vais faire un petit échange avec toi.", + "Tu vas avoir des ennuis d'argent.", + "Tu vas être en surtaxe d'ici peu.", + "Ce sera une mission de courte durée.", + "J'en aurai bientôt fini avec toi.", + "Tu vas bientôt te trouver à court.", + "Arrêtons-nous tout net.", + "Je crois que tu es un peu à court.", + "Je ne suis pas économe avec les Toons.", + "Tu seras bientôt sous bonne garde.", + "Tu vas recevoir une facture d'ici peu.", + ], + 'pp': ["Ça va piquer un peu.", + "Je vais te donner une chance d'économiser.", + "Tu ne veux pas garder ta chance rien que pour toi!", + "Je vais figer ton sourire.", + "Parfait, j'ai une ouverture pour toi.", + "Laisse-moi ajouter mon grain de sel.", + "On m'a demandé de faire un remplacement.", + "Je te prouverai que tu ne rêves pas.", + "Pile tu perds, face je gagne.", + "Un petit sou pour tes gags.", + ], + 'tw': ["Le budget est de plus en plus serré.", + "C'est M. Grippesou qui te parle.", + "Je vais réduire tes subventions.", + "C'est ce que tu peux offrir de mieux?", + "Ne perdons pas de temps - le temps c'est de l'argent.", + "Tu te rendras compte que je suis plutôt économe.", + "Tu n'as pas les coudées franches.", + "Prépare-toi à suivre une voie difficile.", + "J'espère que c'est dans tes moyens.", + "Je vais mettre la pression sur le budget.", + "Je vais faire une compression sur ton budget.", + ], + 'bc': ["J'aime soustraire les Toons.", + "Tu peux compter sur moi pour te faire payer.", + "Pince qui peut.", + "Je peux te pincer là où ça fait mal.", + "La menue monnaie compte aussi.", + "Ta note de frais arrive trop tard.", + "C'est le moment de faire un audit.", + "Allons dans mon bureau.", + "Il y a quoi au menu?", + "J'en pince pour toi.", + "Tu vas te faire pincer.", + ], + 'bf': ["On dirait que ton moral est bas.", + "Je suis prêt à m'envoler!", + "Je suis un pigeon pour les Toons.", + "Oh, un vol-au-vent pour déjeuner.", + "Ça suffira, j'ai un appétit de moineau.", + "J'ai besoin d'un retour sur mes performances. ", + "Parlons un peu du fond de la question.", + "Tu te rendras compte que mes talents sont insondables.", + "Bien, j'ai besoin d'un petit remontant.", + "J'aimerais bien t'avoir pour déjeuner.", + ], + 'tf': ["C'est le moment de se dévoiler!", + "Tu ferais mieux de regarder la défaite en face.", + "Prépare-toi à faire face à ton pire cauchemar!", + "Regarde-le en face, je suis meilleur que toi.", + "Deux têtes valent mieux qu'une.", + "Il faut être deux pour danser, tu veux danser?", + "Tu sur le chemin d'avoir deux fois plus d'ennuis.", + "Quelle joue veux-tu tendre en premier?", + "Je suis deux de trop pour toi.", + "Tu ne sais pas qui tu as en face de toi.", + "Tu te prépare à regarder ton destin en face?", + ], + 'dt': ["Je vais te créer des ennuis incompréhensibles.", + "Arrête-moi si tu ne comprends pas.", + "Je suis si mystérieux.", + "C'est le moment de parler à tort et à travers.", + "J'envisage d'utiliser un double langage.", + "Tu ne vas pas aimer mon double jeu.", + "Tu devrais y réfléchir à deux fois.", + "Attends-toi à te ne pas tout comprendre.", + "Tu veux m'embrouiller.", + "Garçon, la même chose!", + ], + 'ac': ["Je vais te chasser de la ville!", + "Tu entends la sirène?", + "Ça va me plaire.", + "J'aime l'ambiance de la chasse.", + "Laisse-moi t'épuiser.", + "Tu as une assurance?", + "J'espère que tu as apporté une civière avec toi.", + "Je doute que tu puisses te mesurer à moi.", + "Ça grimpe à partir d'ici.", + "Tu vas bientôt avoir besoin de soins.", + "Il n'y a pas de quoi rire.", + "Je vais te donner de quoi t'occuper.", + ] + } + + +SuitAttackDefaultTaunts = ['Prends ça!', 'Garde des notes là-dessus!'] + +SuitAttackNames = { + 'Audit' : 'Audit!', + 'Bite' : 'Morsure!', + 'BounceCheck' : 'Chèque refusé!', + 'BrainStorm' : 'Remue-méninges!', + 'BuzzWord' : 'Mot à la mode!', + 'Calculate' : 'Évaluation!', + 'Canned' : 'En conserve!', + 'Chomp' : 'Mastication!', + 'CigarSmoke' : 'Fumée de cigare!', + 'ClipOnTie' : 'Cravate toute faite!', + 'Crunch' : 'Écrasement!', + 'Demotion' : 'Rétrogradation!', + 'Downsize' : 'Rapetissement!', + 'DoubleTalk' : 'Charabia!', + 'EvictionNotice' : "Ordre d'expulsion!", + 'EvilEye' : 'Mauvais œil!', + 'Filibuster' : 'Obstruction!', + 'FillWithLead' : 'Plombage!', + 'FiveOClockShadow' : "Barbe naissante!", + 'FingerWag' : 'Montré du doigt!', + 'Fired' : 'Liquidé!', + 'FloodTheMarket' : 'Invasion du marché!', + 'FountainPen' : 'Stylo-plume!', + 'FreezeAssets' : 'Capital gelé!', + 'Gavel' : 'Adjugé!', + 'GlowerPower' : 'Regard furieux!', + 'GuiltTrip' : 'Culpabilisation!', + 'HalfWindsor' : 'Nœud de cravate!', + 'HangUp' : 'Interruption!', + 'HeadShrink' : 'Rétrécissement de la tête!', + 'HotAir' : 'Air chaud!', + 'Jargon' : 'Jargon!', + 'Legalese' : 'Expression juridique!', + 'Liquidate' : 'Liquidation!', + 'MarketCrash' : 'Krach boursier!', + 'MumboJumbo' : 'Baragouinage!', + 'ParadigmShift' : 'Changement radical!', + 'PeckingOrder' : 'Hiérarchie!', + 'PickPocket' : 'Vol à la tire!', + 'PinkSlip' : 'Avis de licenciement!', + 'PlayHardball' : 'Grands moyens!', + 'PoundKey' : 'Touche dièse!', + 'PowerTie' : 'Cravate rayée!', + 'PowerTrip' : 'Mégalomanie!', + 'Quake' : 'Tremblement!', + 'RazzleDazzle' : 'Bringue!', + 'RedTape' : 'Paperasserie!', + 'ReOrg' : 'Réorganisation!', + 'RestrainingOrder' : 'Injonction!', + 'Rolodex' : 'Fichier rotatif!', + 'RubberStamp' : 'Tampon!', + 'RubOut' : 'Effacement!', + 'Sacked' : 'Licenciement!', + 'SandTrap' : 'Ensablement!', + 'Schmooze' : 'Jacasserie!', + 'Shake' : 'Secousse!', + 'Shred' : 'Déchiquetage!', + 'SongAndDance' : 'Couplet habituel!', + 'Spin' : 'Tournoiement!', + 'Synergy' : 'Synergie!', + 'Tabulate' : 'Tabulation!', + 'TeeOff' : 'Fâcherie!', + 'ThrowBook' : 'Maximum!', + 'Tremor' : 'Frémissement!', + 'Watercooler' : 'Boissons fraîches!', + 'Withdrawal' : 'Retrait!', + 'WriteOff' : 'Pertes et profits!', + } + +SuitAttackTaunts = { + 'Audit': ["Je crois que ton bilan n'est pas équilibré.", + "On dirait que tu es dans le rouge.", + "Laisse-moi t'aider à faire ta comptabilité.", + "Tes débits sont beaucoup trop élevés.", + "Vérifions ton capital", + "Tu vas avoir des dettes.", + "Regardons de plus près ce que tu dois.", + "Cela devrait mettre ton compte à sec.", + "Il est temps que tu comptabilises tes dépenses.", + "J'ai trouvé une erreur dans ton bilan.", + ], + 'Bite': ["Tu en veux une bouchée?", + "Essaye d'en mordre un morceau!", + "Tu as les yeux plus gros que le ventre.", + "Je mords plus que je n'aboie.", + "Avale donc ça!", + "Attention, je pourrais mordre.", + "Je ne fais pas que mordre quand je suis coincé.", + "J'en veux juste une petite bouchée.", + "Je n'ai rien avalé de la journée.", + "Je ne veux qu'un petit morceau. C'est trop demander?", + ], + 'BounceCheck': ["Dommage, tu n'as pas d'humour.", + "Tu as une échéance de retard.", + "Je crois que ce chèque est à toi.", + "Tu m'es redevable.", + "Je recouvre cette créance.", + "Ce chèque ne va pas être un cadeau.", + "Tu vas être facturé pour ça.", + "Vérifie ce chèque.", + "Ça va te coûter cher.", + "J'aimerais bien encaisser ça.", + "Je vais simplement te renvoyer ton chèque.", + "Voilà une facture salée.", + "Je déduis des frais de service.", + ], + 'BrainStorm':["Je prévois des perturbations.", + "J'adore les casse-tête.", + "Je voudrais t'éclairer.", + "Qu'est-ce que tu penserais de la CHUTE de tes facultés?", + "Que de médiocrité!", + "Tu es prêt(e) pour le grand déménagement?", + "J'ai les neurones en feu.", + "Ça casse des briques.", + "Rien de tel qu'un remue-méninges.", + ], + 'BuzzWord':["Excuse-moi si je radote.", + "Tu connais la dernière?", + "Tu peux piger ça?", + "Toonicoton!", + "Laisse-moi en placer une.", + "Je serai incontournablement clair.", + "Tu as dit un mot de trop.", + "Voyons si tu te situes en transversalité.", + "Fais attention, ça va être ringard.", + "Je crois que tu vas faire de l'urticaire.", + ], + 'Calculate': ["Le compte est bon!", + "Tu comptais là-dessus?", + "Ajoutes-en un peu, tu es en train de diminuer.", + "Je peux t'aider à faire cette addition?", + "Tu as bien enregistré toutes tes dépenses?", + "D'après mes calculs, tu n'en as plus pour longtemps.", + "Voilà le total général.", + "Houlà, ton addition est bien longue.", + "Essaie de trafiquer ces chiffres!", + Cogs + " : 1 Toons : 0", + ], + 'Canned': ["Tu aimes quand c'est en boîte?", + "Tu peux t'occuper des boîtes?", + "Celui-là vient de sortir de sa boîte!", + "Tu as déjà été attaqué par des boîtes de conserve?", + "J'aimerais te faire un cadeau qui se conserve!", + "Tu es prêt pour la mise en boîte?", + "Tu crois que tu es bien conservé?", + "Tu vas être emballé!", + "Je me fais du Toon à l'huile pour dîner!", + "Tu n'es pas si mangeable que ça en conserve.", + ], + 'Chomp': ["Tu as une mine de papier mâché!", + "Croc, croc, croc!", + "On va pouvoir se mettre quelque chose sous la dent.", + "Tu as besoin de grignoter quelque chose?", + "Tu pourrais grignoter ça!", + "Je vais te manger pour le dîner.", + "Je me nourrirais bien de Toons!", + ], + 'ClipOnTie': ["Il faut s'habiller pour la réunion.", + "Tu ne peux PAS sortir sans ta cravate.", + "C'est ce que portent "+theCogs+" les plus élégants." + "Essaie pour voir si la taille te va.", + "Tu devrais mieux t'habiller pour réussir.", + "On ne sert que les clients portant une cravate.", + "Tu as besoin d'aide pour enfiler ça?", + "Rien n'est plus flatteur qu'une belle cravate.", + "Voyons si ça te va.", + "Ça va te bouleverser.", + "Il va falloir que tu t'habilles avant de SORTIR.", + "Je crois que je vais te faire un nœud de cravate.", + ], + 'Crunch': ["On dirait que tu es écrasé(e) par les événements.", + "C'est l'heure d'en écraser!", + "Je vais te donner quelque chose à pulvériser.", + "Je vais broyer tout ça.", + "J'ai tout écrasé.", + "Tu préfères tendre ou croquant?", + "J'espère que tu aimes les croque-monsieur.", + "On dirait que tu es en train de te faire écraser!", + "Je vais te réduire en miettes." + ], + 'Demotion': ["Tu descends sur l'échelle de la hiérarchie.", + "Je te renvoie à trier le courrier.", + "Il est temps de rendre tes galons.", + "Tu descends, petit clown!", + "On dirait qu'il y a un blocage.", + "Tu progresses lentement.", + "Tu es dans une voie sans issue.", + "Tu n'iras nulle part dans l'immédiat.", + "Tu ne vas nulle part.", + "Cela sera porté sur ta fiche d'assiduité.", + ], + 'Downsize': ["Redescends donc de là!", + "Tu sais comment redescendre?", + "Revenons à nos affaires.", + "Qu'est-ce qui ne va pas? Tu as l'air d'avoir le moral dans les chaussettes.", + "Tu descends?", + "Qu'est-ce qui te chiffonne? Toi!", + "Pourquoi est-ce que tu choisis des gens de ma taille?", + "Pourquoi es-tu si terre-à-terre?", + "Est-ce que tu voudrais un modèle plus petit pour seulement dix cents de plus?", + "Essaie pour voir si la taille te va!", + "Ce modèle est disponible dans une plus petite taille.", + "C'est une attaque à taille unique!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["C'est l'heure de partir!", + "Fais tes bagages, Toon.", + "C'est le moment d'aller habiter ailleurs.", + "Disons que ton bail est terminé.", + "Tu as un loyer de retard.", + "Cela va être très déstabilisant.", + "Tu vas être déraciné d'ici peu.", + "Je vais t'envoyer sous les ponts.", + "Tu n'es pas à ta place.", + "Prépare-toi à une délocalisation.", + "Tu vas subir un placement d'office.", + ], + 'EvilEye': ["Je te donne le mauvais œil.", + "Tu peux donner un coup d'œil à ça pour moi?", + "Attends. J'ai quelque chose dans l'œil.", + "J'ai l'œil sur toi!", + "Tu pourrais garder un œil sur ça?", + "J'ai vraiment l'œil pour voir ce qui cloche.", + "Je vais te taper dans l'œil.", + "J'ai le regard méchant!", + "Tu vas te retrouver dans l'œil du cyclone!", + "Je te regarde en roulant des yeux.", + ], + 'Filibuster':["Est-ce que je dois te barrer la route?", + "Ça va nous bloquer pendant un moment.", + "Je pourrais rester coincé là toute la journée.", + "Je n'ai même pas besoin de respirer.", + "J'avance et j'avance et j'avance.", + "Je ne m'en fatigue jamais.", + "On ne peut pas m'arrêter de parler.", + "Tu peux te boucher les oreilles?", + "Je crois que je vais te tenir la jambe.", + "Je finis toujours par placer un mot.", + ], + 'FingerWag': ["Ça fait mille fois que je te le répète!", + "Regarde bien là, Toon.", + "Ne me fais pas rire.", + "Ne m'oblige pas à y aller.", + "J'en ai assez de répéter la même chose.", + "Je crois qu'on en a déjà parlé.", + "Tu n'as aucun respect pour nous les " + Cogs + ".", + "Il est grand temps de faire attention.", + "Blablablablabla.", + "Ne m'oblige pas à mettre fin à cette réunion.", + "Est-ce que je vais devoir te séparer?", + "On est déjà passés par là.", + ], + 'Fired': ["J'espère que tu as apporté des rafraîchissements.", + "On s'embête solide.", + "Ça va nous rafraîchir.", + "J'espère que tu as le sang froid.", + "J'ai la gorge sèche.", + "Va donc nager un peu!", + "Tu es déjà sur le départ.", + "Encore un peu de sauce?", + "Tu peux dire \" aïe \"?", + "J'espère que tu sais nager.", + "Tu es en phase de déshydratation?", + "Je vais te liquider!", + "Tu vas finir en bouillie.", + "Tu n'es qu'un feu de paille.", + "Je me trouve fondant.", + "Je suis d'une limpidité!", + "Et on n'en parle plus!.", + "Un Toon à la mer!.", + ], + 'FountainPen': ["Ça va tacher.", + "Mettons ça par écrit.", + "Prépare-toi à des ennuis indélébiles.", + "Tu vas avoir besoin d'un bon nettoyage à sec.", + "Tu devrais corriger.", + "Ce stylo écrit si bien.", + "Voilà, je prends mon crayon.", + "Tu peux lire mon écriture?", + "Et voilà la plume de l'apocalypse.", + "Ta performance est entachée.", + "Tu n'as pas envie de tout effacer?", + ], + 'FreezeAssets': ["Ton capital est le mien.", + "Tu ne sens pas un appel de fonds?", + "J'espère que tu n'as pas de projets.", + "Cela devrait te mettre sur la paille.", + "Le fond de l'air est frais.", + "L'hiver va venir tôt cette année.", + "Tu as froid?", + "Je vais geler mes projets.", + "Tu vas trouver ça froid.", + "Tu vas avoir des engelures.", + "J'espère que tu aimes la viande froide.", + "Je garde mon sang-froid.", + ], + 'GlowerPower': ["Tu me regardes?", + "On me dit que j'ai une vue perçante.", + "J'aime bien que tu sois à portée de mon regard.", + "Tu n'aimes pas que je te regarde?", + "Voilà, je te regarde.", + "Tu ne trouves pas que j'ai un regard expressif?", + "Mon regard est mon point fort.", + "C'est le regard qui compte.", + "Coucou, je te vois.", + "Regarde-moi dans les yeux...", + "Est-ce que tu voudrais voir ton avenir?", + ], + 'GuiltTrip': ["Tu vas vraiment te sentir coupable!", + "Tu te sens coupable!", + "C'est entièrement de ta faute!", + "C'est toujours ta faute.", + "Tu te complais dans la culpabilité!", + "Je ne te reparlerai plus jamais!", + "Tu ferais mieux de t'excuser.", + "Jamais je ne te pardonnerai!", + "Tu veux bien te faire de la bile?", + "Rappelle-moi quand tu ne te sentiras plus coupable.", + "Quand finiras-tu par te pardonner à toi-même?", + ], + 'HalfWindsor': ["Tu ne t'es encore jamais fait cravater comme ça!", + "Essaye de ne pas trop faire de nœuds.", + "Tu es dans une situation inextricable.", + "Tu as de la chance, j'aurais pu faire un nœud plus serré.", + "Cette cravate est trop chère pour toi.", + "Je crois que tu n'as jamais même VU de nœud de cravate!", + "Cette cravate est trop chère pour toi.", + "Cette cravate serait gâchée, sur toi.", + "Tu ne vaux même pas la moitié du prix de cette cravate!", + ], + 'HangUp': ["Tu as été déconnecté(e).", + "Au revoir!", + "C'est l'heure de mettre fin à notre conversation.", + "...et ne me rappelle pas!", + "Clic!", + "La conversation est terminée.", + "Je vais couper cette ligne.", + "Je crois que nous allons être coupés.", + "On dirait que la ligne est défectueuse.", + "Ton forfait est terminé.", + "J'espère que tu m'entends clairement.", + "Tu as fait le mauvais numéro.", + ], + 'HeadShrink': ["On dirait que tu as besoin de te faire soigner la tête.", + "Chérie, j'ai rétréci le Toon.", + "J'espère que ça ne t'est pas monté à la tête.", + "Tu rétrécis au lavage?", + "Je rétrécis donc je suis.", + "Il n'y a pas de quoi perdre la tête.", + "Où as-tu la tête?", + "Relève la tête! Ou plutôt, mets-la par terre.", + "Les choses sont parfois plus grandes qu'elles ne paraissent.", + "Les bons Toons se vendent par petits paquets.", + ], + 'HotAir':["Nous avons de chaudes discussions.", + "Tu subis une vague de chaleur.", + "J'ai atteint mon point d'ébullition.", + "Cela pourrait te brûler.", + "Je détesterais te passer au gril, mais...", + "N'oublie pas qu'il n'y a pas de fumée sans feu.", + "Tu m'as l'air un peu grillé(e).", + "C'est encore un écran de fumée.", + "C'est le moment de mettre de l'huile sur le feu.", + "Allumons le feu de l'amitié.", + "J'ai des remarques brûlantes à te faire.", + "Air chaud!", + ], + 'Jargon':["Quel non-sens.", + "Regarde si tu peux trouver du sens à tout ça.", + "J'espère que tu m'entends clairement.", + "On dirait que je vais devoir élever la voix.", + "J'ai vraiment mon mot à dire.", + "J'ai mon franc-parler.", + "Je vais pontifier sur ce sujet.", + "Tu sais, les mots peuvent faire mal.", + "Tu as compris ce que je voulais dire?", + "Des mots, rien que des mots.", + ], + 'Legalese':["Tu dois cesser d'être et renoncer.", + "Tu vas être débouté(e), légalement parlant.", + "Tu es au courant des implications légales?", + "Tu n'es pas au-dessus des lois!", + "Il devrait y avoir une loi contre toi.", + "Il n'y a rien de postérieur aux faits!", + "Toontown en ligne de Disney n'est pas légalement responsable des opinions exprimées dans cette attaque.", + "Nous ne serons pas tenus responsables des dommages subis suite à cette attaque.", + "Les résultats de cette attaque peuvent différer.", + "Cette attaque est nulle là où elle n'est pas autorisée.", + "Tu ne rentres pas dans mon système législatif.", + "Tu ne peux pas gérer les questions juridiques.", + ], + 'Liquidate':["J'aime bien que les choses restent fluides.", + "As-tu des problèmes de liquidités?", + "Je dois purger ton capital.", + "Il est temps pour toi de suivre le flux monétaire.", + "N'oublie pas que ça glisse quand c'est mouillé.", + "Il y a des fuites dans ta comptabilité.", + "Tu as l'air de perdre pied.", + "Tout te tombe dessus.", + "Je crois que tu vas subir une dilution.", + "Tu es lessivé(e).", + ], + 'MarketCrash':["Tu vas avoir un choc.", + "Tu ne survivras pas au choc.", + "C'est plus que la bourse ne peut en supporter.", + "J'ai un traitement de choc pour toi!", + "Maintenant je vais te faire un choc.", + "Je m'attends à un choc boursier.", + "On dirait que le marché est sur la pente descendante.", + "Il vaudrait mieux que tu te retires du jeu!", + "Vends! Vends! Vends!", + "Est-ce que je dois mener la récession?", + "Tout le monde s'enfuit, tu devrais peut-être en faire autant?", + ], + 'MumboJumbo':["Que ce soit parfaitement clair.", + "C'est aussi simple que ça.", + "C'est comme cela que nous allons procéder.", + "Laisse-moi te l'écrire en grosses lettres.", + "C'est du jargon technique.", + "Ma parole est d'argent.", + "J'en ai plein la bouche.", + "On dit que je suis grandiloquent.", + "Je vais interjeter ça.", + "Je crois que ce sont les mots adéquats.", + ], + 'ParadigmShift':["Fais attention! Je suis plutôt changeant.", + "Prépare-toi pour un changement radical!", + "Voilà donc des substitutions intéressantes.", + "Tu n'es pas à ta place.", + "C'est ton tour de changer de place.", + "Ton temps de présence est terminé.", + "Tu n'as encore jamais autant changé dans ta vie.", + "Voilà qui est radical!", + "La lumière est changeante!", + ], + 'PeckingOrder':["Pauvre sous-fifre!", + "Tu vas te retrouver le bec dans l'eau.", + "Tu vas te retrouver en bas de l'échelle.", + "Ce n'est pas une attaque de débutant.", + "Tu es tout en bas de la hiérarchie.", + "Je vaux bien plus cher que toi!", + "La hiérarchie, il n'y a que ça de vrai!", + "Pourquoi est-ce que je ne trouve pas d'adversaire à ma taille? Bof.", + "À moi le pouvoir!", + ], + 'PickPocket': ["Laisse-moi vérifier tes valeurs.", + "Eh, c'est quoi par ici?", + "C'est comme faucher les jouets d'un enfant.", + "C'est du vol.", + "Je te garde ça.", + "Ne lâche pas mes mains des yeux.", + "Mes mains sont plus rapides que tes yeux.", + "Je n'ai rien dans la manche.", + "La direction n'est pas responsable des objets perdus.", + "Qui trouve garde.", + "Tu ne le verras jamais revenir.", + "Tout pour moi, rien pour toi.", + "Ça ne te gêne pas que ça me gêne?", + "Tu n'en auras plus besoin...", + ], + 'PinkSlip': ["On n'a pas besoin de ton avis.", + "Tu as peur de cette vague de licenciements?.", + "Celui-là va sûrement être d'un avis contraire.", + "Oh, tu as une licence de quoi?", + "Fais attention, si tu veux mon avis!", + "N'oublie pas que ça glisse à mon avis.", + "Je vais juste te renvoyer celui-là.", + "Tu ne te fâcheras pas si je te donne mon avis?", + "Tu ne vois pas l'avis en rose.", + "Tu peux sortir, je te licencie.", + ], + 'PlayHardball': ["Tu veux employer les grands moyens? ", + "N'essaie pas d'employer tous les moyens avec moi.", + "Ne prends pas tes grands airs!", + "Tu es vraiment moyen(ne).", + "Et voilà le bon moyen...", + "Tu vas avoir besoin d'un bon moyen pour t'en sortir.", + "Je vais te chasser d'ici à grande vitesse.", + "Une fois que je t'aurai touché(e), tu rentreras en courant chez toi.", + "C'est ton grand départ!", + "Ton jeu est très moyen.", + "Je vais tout faire pour que tu sortes.", + "Je t'envoie promener dans les grandes largeurs!", + ], + 'PoundKey': ["Il est temps que je réponde à quelques appels.", + "J'aimerais faire un appel en PCV.", + "Dring dring, c'est pour toi!", + "Je vais bien toucher quelque chose.", + "Je devais te rappeler.", + "Cela devrait provoquer une sonnerie.", + "Je vais juste faire ce numéro.", + "Je t'appelle pour te faire une surprise.", + "Je vais t'appeler.", + "Allo Toon, c'est pour toi.", + ], + 'PowerTie': ["Je t'appellerai plus tard, tu as l'air d'avoir un nœud à l'estomac.", + "Tu te prépares à faire un trait là-dessus?", + "Tu vas te faire cravater.", + "Tu ferais mieux d'apprendre à faire un nœud de cravate.", + "Je vais te nouer la langue!", + "Tu n'as encore jamais vu quelqu'un se faire cravater comme ça!", + "Tu fais attention aux rayures?", + "Je vais te rayer de la carte!", + "Je rayonne!", + "Par les pouvoirs qui me sont conférés, je te raie de la liste.", + ], + 'PowerTrip': ["Fais tes valises, on fait un méga voyage.", + "Tu n'as pas perdu tes manies?", + "C'est une manie que tu as de partir en vacances.", + "Comment se sont passées les vacances?", + "C'est une vraie manie!", + "Ça a l'air méga ennuyeux.", + "Maintenant tu vois qui est le plus puissant!", + "Je suis bien plus puissant que toi.", + "Qui a les méga pouvoirs maintenant?", + "Tu ne peux pas te battre contre ma puissance.", + "La puissance est corrompue, en particulier dans mon cas.", + ], + 'Quake': ["Tremblons, mes frères.", + "J'ai la tremblote!", + "Je te vois trembler dans tes chaussures.", + "Voilà la terre qui tremble!", + "Celui-ci est en-dehors de l'échelle de Richter.", + "La terre va trembler!", + "Hé, qu'est-ce qui tremble comme ça? Toi!", + "Tu as déjà ressenti un tremblement de terre?", + "Tu es sur un terrain instable!", + ], + 'RazzleDazzle': ["Chante avec moi.", + "Tu as peur de perdre ton dentier?", + "Je ne suis pas charmant?", + "Je vais t'impressionner.", + "Mon dentiste fait un excellent travail.", + "Ils ne sont pas épatants?", + "Difficile de croire qu'ils ne sont pas réels.", + "Ils ne sont pas choquants?", + "Ça va décoiffer.", + "Je me lave les dents après tous les repas.", + "Dis \" Cheese! \"", + ], + 'RedTape': ["Ça va être bien emballé.", + "Tu vas rester collé(e) là un bon moment.", + "J'en ai un plein rouleau.", + "On va voir si tu peux y couper.", + "Ça va devenir collant.", + "J'espère que tu es claustrophobe.", + "Tu es d'un tempérament collant!", + "Je vais t'occuper un peu.", + "Essaie donc de sortir de là.", + "On va voir si ça colle entre nous.", + ], + 'ReOrg': ["Tu n'aimes pas la manière dont j'ai réorganisé les choses?", + "Peut-être qu'un peu plus d'organisation serait de mise.", + "Tout n'est pas si mauvais, tu as juste un peu besoin de réorganisation.", + "Est-ce que tu apprécies mes capacités d'organisation?", + "J'essaye juste de donner un nouvel aspect aux choses.", + "Tu dois t'organiser!", + "Tu m'as l'air de faire dans la désorganisation.", + "Reste là pendant que je te réorganise.", + "Je vais attendre que tu aies le temps de t'organiser.", + "Ça ne te dérange pas si je réorganise un peu?", + ], + 'RestrainingOrder': ["Tu devrais faire la jonction.", + "Je t'assène une injonction!", + "Tu n'as pas le droit de t'approcher à moins de deux mètres de moi.", + "Tu ferais peut-être mieux de garder tes distances.", + "Tu devrais avoir une injonction.", + Cogs + "! Maîtrisez ce Toon!" + "Essaie de te maîtriser.", + "J'espère que je ne suis pas trop une contrainte pour toi.", + "Voyons si tu peux te libérer de ces contraintes!", + "Je te donne l'injonction de te maîtriser!", + "Pourquoi ne commençons-nous pas par les contraintes de base?" + ], + 'Rolodex': ["Ta fiche est quelque part là-dedans.", + "Voilà la fiche de la chasse aux nuisibles.", + "Je vais te donner une fiche.", + "Ton numéro est juste là.", + "Je te couvre de A à Z.", + "Tu vas avoir la tête qui tourne.", + "Va donc faire un tour.", + "Attention aux bouts de papier.", + "J'ai des doigts pour trier.", + "Est-ce que c'est comme ça que je peux te contacter?", + "Je voudrais être certain que nous allons rester en contact.", + ], + 'RubberStamp': ["Je fais toujours bonne impression.", + "Il est important de bien appuyer.", + "Une impression parfaite à chaque fois.", + "Je voudrais que tu imprimes.", + "Tu dois être RETOURNÉ à L'ENVOYEUR.", + "Tu es dans la pile ANNULÉ.", + "Tu es en livraison PRIORITAIRE.", + "Je voudrais être certain que tu as REÇU mon message!", + "Tu ne vas nulle part - tu es en PORT PAYÉ par le DESTINATAIRE.", + "Je veux une réponse URGENTE.", + ], + 'RubOut': ["Et maintenant un acte de disparition.", + "J'ai l'impression de t'avoir perdu quelque part.", + "J'ai décidé de te gommer.", + "Je gomme toujours tous les obstacles.", + "Je vais simplement effacer cette erreur.", + "Je peux faire disparaître tous les ennuis.", + "J'aime les choses nettes et propres.", + "Essaie de mettre la gomme.", + "Je te vois...je ne te vois plus.", + "Cela va finir par pâlir.", + "Je vais éliminer le problème.", + "Laisse moi m'occuper de tes zones à problèmes.", + ], + 'Sacked':["On dirait que tu vas te faire licencier.", + "L'affaire est dans le sac.", + "Tu as une licence de vol?", + "De chasse ou de pêche?", + "Mes ennemis vont être à la porte!", + "J'ai le record de Toontown pour les licenciements.", + "On n'a plus besoin de toi ici.", + "Tu as passé assez de temps ici, tu es renvoyé(e)!", + "Laisse-moi te mettre en boîte.", + "Tu ne peux pas te défendre si je veux te mettre dehors!", + ], + 'Schmooze':["Tu ne verras jamais ça venir.", + "Ça fera bien sur toi.", + "Tu as gagné ça.", + "Je ne voulais pas baver.", + "La flatterie mène partout.", + "Je vais en rajouter une couche.", + "C'est le moment d'en rajouter.", + "Je vais me mettre de ton bon côté!", + "Ça mérite une bonne tape dans le dos.", + "Je vais chanter tes louanges.", + "Je suis navré de te faire tomber de ton piédestal, mais...", + ], + 'Shake': ["Tu es juste à l'épicentre.", + "Tu es juste sur une faille.", + "Ça va secouer.", + "Je crois que c'est une catastrophe naturelle.", + "C'est un désastre de proportions sismiques.", + "Celui-ci est en dehors de l'échelle de Richter.", + "C'est le moment de se mettre à l'abri.", + "Tu as un air troublé.", + "Attention la secousse!", + "Je vais te secouer, pas te faire tourner.", + "Ça devrait te secouer.", + "J'ai un bon plan pour s'échapper.", + ], + 'Shred': ["Je dois me débarrasser de quelques déchets.", + "J'augmente ma capacité de traitement.", + "Je crois que je vais me débarrasser de toi maintenant.", + "On va pouvoir détruire les preuves.", + "Il n'y a plus aucune façon de prouver ça maintenant.", + "Vois si tu peux assembler toutes les pièces.", + "Cela devrait te remettre à la bonne taille.", + "Je vais jeter cette idée.", + "Il ne faut pas que ça tombe entre de mauvaises mains.", + "Vite venu, vite parti.", + "Ce n'est pas ton dernier fragment d'espoir?", + ], + 'Spin': ["Tu veux qu'on aille faire un tour?", + "À quelle vitesse tournes-tu?", + "Ça va te faire tourner la tête!", + "C'est le tour que prennent les choses.", + "Je vais t'emmener faire un tour.", + "Que feras-tu quand ce sera ton tour?", + "Surveille-moi ça. Je ne voudrais pas que ça tourne trop vite!", + "Tu vas tourner longtemps comme ça?", + "Mes attaques vont te donner le tournis!", + ], + 'Synergy': ["Je transmets cela au comité.", + "Ton projet a été annulé.", + "Ton budget a été réduit.", + "Nous allons restructurer ton service.", + "J'ai mis ça au vote et tu as perdu.", + "Je viens de recevoir l'accord final.", + "Il n'y a pas de problèmes, il n'y a que des solutions.", + "Je te recontacte à ce sujet.", + "Revenons à cette affaire.", + "Considère que c'est un manque de synergie.", + ], + 'Tabulate': ["Ça ne s'additionne pas!", + "Si je compte bien, tu as perdu.", + "Tu comptes bien toutes les colonnes.", + "Je te fais le total dans un instant.", + "Tu es prêt(e) à compter tout ça?", + "Ta facture est payable dès maintenant.", + "Il est temps de faire une estimation.", + "J'aime bien mettre les choses en ordre.", + "Et les résultats au pointage sont...", + "Ces chiffres devraient être très puissants.", + ], + 'TeeOff': ["Tu ne fais pas le poids.", + "Gare à toi!", + "Je suis vexé.", + "Pourquoi es-tu en colère?", + "Essaye simplement d'éviter le danger.", + "Scrongneugneu!", + "Tu vas prendre la mouche à tous les coups.", + "Tu es sur mon chemin.", + "J'ai une bonne prise sur la situation.", + "Attention le petit oiseau va se fâcher!", + "Garde un œil sur moi!", + "Ça te dérange si je joue?", + ], + 'Tremor': ["Tu as senti ça?", + "Tu n'as pas peur d'un petit frémissement n'est-ce pas?", + "Au commencement était le frémissement.", + "Tu as l'air de trembler.", + "Je vais un peu secouer les choses!", + "Tu te prépare à sursauter?", + "Qu'est-ce qui ne va pas? Tu as l'air d'accuser la secousse.", + "Crainte et tremblements!", + "Pourquoi trembles-tu de peur?", + ], + 'Watercooler': ["Ça devrait te rafraîchir.", + "Tu ne trouves pas ça rafraîchissant?", + "Je livre les boissons.", + "Directement du robinet dans ton gosier.", + "C'est quoi le problème, c'est juste de l'eau de source.", + "Ne t'inquiète pas, c'est filtré.", + "Ah, un autre client satisfait.", + "C'est l'heure de ta livraison quotidienne.", + "J'espère que les couleurs ne vont pas déteindre.", + "Tu as envie de boire?", + "Tout s'en va à la lessive.", + "C'est toi qui payes à boire.", + ], + 'Withdrawal': ["Je crois que tu es à découvert.", + "J'espère que ton compte est suffisamment approvisionné.", + "Prends ça, avec les intérêts.", + "Ton solde n'est pas en équilibre. ", + "Tu vas bientôt devoir faire un dépôt.", + "Tu as souffert de la récession économique.", + "Je crois que tu as un passage à vide.", + "Tes finances sont sur le déclin.", + "Je prévois une baisse définitive.", + "C'est un revers de fortune.", + ], + 'WriteOff': ["Laisse-moi augmenter tes pertes.", + "Profitons d'une mauvaise affaire.", + "C'est l'heure d'équilibrer les comptes.", + "Ça ne va pas faire bien dans ton bilan.", + "Je suis à la recherche de quelques dividendes.", + "Tu dois tenir compte de tes pertes.", + "Tu peux oublier les bonus.", + "Je vais mélanger tes comptes.", + "Tu vas avoir quelques pertes.", + "Ça va te faire mal au solde.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "En attente des autres joueurs...", + +# Elevator.py +ElevatorHopOff = lQuit + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Inc." +CogsIncModifier = "%s "+ CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "Qui est là?" +DoorWhoAppendix = "qui?" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Tu as besoin de gags! Va en parler à Tom Tuteur!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Reviens ici quand tu auras vaincu le Laquaistic!" +FADoorCodes_TALK_TO_HQ = "Va chercher ta récompense auprès d'Harry au QG!" +FADoorCodes_WRONG_DOOR_HQ = "Mauvaise porte! Prends l'autre porte pour aller au terrain de jeux!" +FADoorCodes_GO_TO_PLAYGROUND = "Mauvais chemin! Tu dois aller au terrain de jeux!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Marche jusqu'à ce Laquaistic pour te battre avec lui!" +FADoorCodes_TALK_TO_HQ_TOM = "Va chercher ta récompense au QG des Toons!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Fais attention! Il y a un COG là-dedans!" +FADoorCodes_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Cog!n\nConstruis ton déguisement de Cog avec des pièces de l'usine." + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Qui", + "Il y a un mauvais écho par ici, n'est-ce pas?"], + + ["Douglas", + "Douglas à la vanille ça t'intéresse?"], + + ["Geoffrey", + "Geoffrey bien une petite sieste, laisse-moi entrer."], + + ["Justin", + "Justin petit moment."], + + ["Adhémar", + "Adhémar pas ta voiture?"], + + ["Annie", + "Annie rien comprendre, pourquoi tu n'ouvres pas?"], + + ["Omer", + "Omer veille, j'ai fini par te trouver."], + + ["Thérèse", + "Thérèse, t'es là sans bouger depuis tout ce temps?"], + + ["Sylvie", + "Sylvie c'est un miracle, laisse-le au moins entrer."], + + ["Aude", + "Aude toilette à la lavande ce matin?"], + + ["Alex", + "Alex Térieur, j'ai froid dehors."], + + ["Alain", + "Alain Térieur, je voudrais entrer!"], + + ["Justine", + "Justine petite minute, je n'en ai pas pour longtemps."], + + ["Vincent", + "Vincent rien, et repart sans rien."], + + ["Jean", + "Jean ai marre que tu n'ouvres pas cette porte!"], + + ["Firmin", + "Firmin peu la radio tu m'entendrais mieux."], + + ["Geoffroy", + "Geoffroy dehors laisse-moi entrer."], + + ["Jessica", + "Jessica difficiles à traiter, dépêche-toi un peu."], + + ["Djamila", + "Djamila clé sous la porte."], + + ["Emma", + "Emma claqué la porte au nez!!"], + + ["Nicole", + "Nicole rien du tout ça doit rester propre."], + + ["Yann-Adam", + "Yann-Adam le frigo je peux entrer?"], + + ["Louis", + "Louis pas trop fine, décidément."], + + ["Mélusine", + "Mélusine des Cogs en faillite, au lieu de dormir."], + + ["Kim", + "Kim énerve, à ne pas ouvrir."], + + ["Ella", + "Ella pas envie de descendre ouvrir?"], + + ["Jean", + "Jean file un pull et j'arrive."], + + ["Roger", + "Roger plus rien dans le frigo, tu peux aller faire les courses?"], + + ["John", + "John Dœuf est déjà passé vendre de la mayonnaise?"], + + ["Alain", + "Alain d'Issoire! C'est ça, bon dimanche."], + + ["Steve", + "Steve a, j'y vais aussi."], + + ["Elvire", + "Elvire pas sur ses gonds, ta porte."], + + ["Jean", + "Jean, bon, je peux entrer finalement?"], + + ["Sarah", + "Sarah fraîchit dernièrement, j'ai froid dehors."], + + ["Aïcha", + "Aïcha fait mal aux mains de frapper à ta porte."], + + ["Sarah", + "Sarah croche toujours au téléphone, tu ne veux vraiment pas me parler?"], + + ["Déborah", + "Déborah, dis, qu'il y a dans ton jardin, je peux les voir?"], + + ["Eddy", + "Eddy donc toi là-bas, tu vas finir par venir?"], + + ["Élie ", + "Élie quoi? Le journal est déjà arrivé?"], + + ["Mandy", + "Mandy donc tu fais quoi là? "], + + ["Yvon", + "Yvon pas revenir plus tard si tu n'ouvres pas!"], + + ["Isabelle", + "Isabelle toujours à n'importe quelle heure."], + + ["Robin", + "Robin, dis donc, c'est maintenant que tu arrives?"], + + ["Oscar", + "Oscar, il n'est jamais à l'heure, je prendrai le train la prochaine fois."], + + ["Léonard", + "Léonard j'aime pas, j'aime mieux les langoustines - merci quand même pour ton invitation."], + + ["Gérard", + "Gérard, mais rarement vu ça."], + + ["Théa", + "Théa l'heure, pour une fois?"], + + ["Médor", + "Médor, Médor, mais comment veux-tu que je dorme si tu ne me laisses pas entrer?"], + + ["Stella", + "Stella mais c'est plus là."], + + ["Isidore", + "Isidore que la nuit, il est parti à l'heure qu'il est."], + + ["Élodie", + "Élodie, donc? C'est pas fini?"], + + ["Julien", + "Julien du tout à te donner."], + + ["Yvan", + "Yvan quoi? J'ai besoin de rien."], + + ["Eugène", + "Eugène pas du tout, prend ton temps."], + + ["Sultan", + "Sultan de travail, je ne peux pas dormir."], + + ["André", + "Mais André donc."], + + ["Alphonse", + "Alphonse pas dans l'escalier en venant ouvrir."], + + ["Amélie", + "Amélie donc ce qui est écrit au lieu de redemander."], + + ["Angèle", + "Angèle pas du tout, il ne fait pas froid."], + + ["Aubin", + "Aubin dis donc, quand est-ce que tu arrives?"], + + ["Cécile", + "Cécile est de bonne humeur qu'il vient ouvrir la porte?"], + + ["Djemila", + "Djemila clé dans la serrure mais ça ne marche pas."], + + ["Éléonore", + "Éléonore maintenant mais j'ai pas sa nouvelle adresse."], + + ["Huguette", + "Huguette si quelqu'un d'autre arrive?"], + + ["Isolde", + "Isolde pas, tout est au prix fort."], + + ["Jenny", + "Jenny figues ni raisin, l'épicerie a déménagé."], + + ["Jérémie", + "Jérémie le courrier à la poste, maintenant je suis rentré."], + + ["Jimmy.", + "Jimmy ton courrier dans la boîte"], + + ["Johnny.", + "Johnny connais rien du tout, viens donc voir ça."], + + ["Julie", + "Julie pas très bien ce qui est écrit sur la porte."], + + ["Cathy", + "Cathy donc dit?"], + + ["Léo", + "Léo lit encore à cette heure-là?"], + + ["Léon", + "Léon-dit, ça ne m'intéresse pas. Je préfère que tu me dises la vérité."], + + ["Maël", + "Maël dit toujours la même chose!"], + + ["Marin", + "Marin du tout, je veux juste te dire bonjour."], + + ["Quentin", + "Quentin est là, on ouvre."], + + ["Sacha", + "Sacha pas, demande-lui directement."], + + ["Stella", + "Stella tu ouvres. Réponds!"], + + ["Théophile", + "Théophile encore une fois, tu ne fais que téléphoner."], + + ["Tudor", + "Tudor tout le temps quand je passe te voir."], + + ["Véra", + "Véra bien qui c'est si tu descends ouvrir."], + + ["Xavier", + "Xavier pas une sonnette la dernière fois?"], + + ["Yann", + "Yann a plus, y'en aura la prochaine fois."], + + ["Yvon", + "Yvon bien, merci de prendre des nouvelles!"], + + ["Odyssée", + "Odyssée quoi toutes ces questions?"], + + ["Thor", + "Thor ait le temps de descendre ouvrir?"], + + ["Édith", + "Édith a vu l'heure, il est bien temps d'arriver."], + + ["Jean-Aymar", + "Jean-Aymar d'attendre."], + + ["Aubin", + "Aubin dis donc, tu en mets un temps!"], + + ["Ahmed", + "Ahmed dépens, j'ai fini par comprendre."], + + ["Henri", + "Henri encore, de ta dernière blague."], + + ["Aude", + "Aude désespoir, ô rage."], + + ["Ali", + "Ali qu'a tort, comme d'habitude."], + + ["Gilles", + "Gilles est de sauvetage aujourd'hui."], + + ["Hans", + "Hans qui me concerne, j'aimerais bien que tu ouvres la porte."], + + ["Roméo", + "Roméo lendemain ce que tu ne peux pas faire aujourd'hui."], + + ["Hildéphonse", + "Hildéphonse la porte."], + + ["Helmut", + "Helmut le pain de la bouche!"], + + ["Hercule", + "Hercule la voiture au fond de la cour."], + + ["Mylène", + "Mylène, mi-coton."], + + ["Célestin", + "Célestin? Non c'est l'ouest."], + + ["Ondine", + "Ondine où ce soir?"], + + ["Laurent", + "Laurent-Outang, je cherche le zoo?"], + + ["Anne", + "Anne pas dire."], + + ["Edgar", + "Edgar pas là, tu gênes."], + + ["José", + "José pas le dire."], + + ["Samira", + "Samira pas c'est trop petit."], + + ["Humphrey", + "Humphrey peur celui-là!"], + + ["Saturnin", + "Saturnin peu trop vite."], + + ["Juste", + "Juste pour voir."], + + ["Aziza", + "Aziza pouvait durer!"], + + ["Jonathan", + "Jonathan que toi."], + + ["Aubin", + "Aubin, ça alors! Je ne comptais pas sur toi."], + + ["Yamamoto", + "Yamamoto qu'a dérapé, je cherche un garage."], + + ["Stanislav", + "Stanislav tous les matins sous sa douche."], + + ["Yvan-Dédé", + "Yvan-Dédé, voitures d'occasion."], + + ["Céline", + "Céline évitable."], + + ["Jean-Philémon", + "Jean-Philémon blouson et je viens."], +] + +# ChatInputNormal.py +ChatInputNormalSayIt = "Dis-le" +ChatInputNormalCancel = lCancel +ChatInputNormalWhisper = "Chuchoter" +ChatInputWhisperLabel = "À %s" + +# ChatInputSpeedChat.py +SCEmoteNoAccessMsg = "Tu n'as pas encore accès\nà cette émotion." +SCEmoteNoAccessOK = lOK + +# ChatManager.py +ChatManagerChat = "Chat" +ChatManagerWhisperTo = "Chuchoter à :" +ChatManagerWhisperToName = "Chuchoter à :\n%s" +ChatManagerCancel = lCancel +ChatManagerWhisperOffline = "%s est déconnecté(e)." +OpenChatWarning = "Tu n'as pas encore d'\" amis secrets \"! Tu ne peux pas discuter avec les autres Toons s'ils ne sont pas tes amis secrets.\n\nPour devenir ami(e) secret(e) avec quelqu'un, clique sur lui ou sur elle, et sélectionne \" Secrets \" dans le panneau d'informations. Bien entendu, tu peux toujours parler à n'importe qui avec le Chat rapide." +OpenChatWarningOK = lOK +UnpaidChatWarning = "Une fois que tu es inscrit(e), tu peux utiliser ce bouton pour discuter avec tes amis à l'aide du clavier. Avant cela, tu dois discuter avec les autres Toons à l'aide du Chat rapide." +UnpaidChatWarningPay = "S'inscrire maintenant!" +UnpaidChatWarningContinue = "Continuer l'essai gratuit" +PaidNoParentPasswordWarning = "Une fois que tu as choisi un mot de passe \" parent \", tu peux utiliser ce bouton pour discuter avec tes amis à l'aide du clavier. Avant cela, tu dois discuter avec les autres Toons à l'aide du Chat rapide." +PaidNoParentPasswordWarningSet = "Établir le mot de passe \"parent\"!" +PaidNoParentPasswordWarningContinue = "Continuer à jouer" +PaidParentPasswordUKWarning = "Une fois le Chat activé vous pourrez discuter avec vos amis en utilisant le clavier. Si vous decidez de ne pas activer le Chat vous pourrez continuer à utiliser le Chat Rapide pour discuter avec les autres Toons." +PaidParentPasswordUKWarningSet = "Activer le Chat!" +PaidParentPasswordUKWarningContinue = "Continuer à jouer." +NoSecretChatAtAllTitle = "Chat \" amis secrets \"." +NoSecretChatAtAll = "Pour discuter avec un(e) ami(e), la fonction \" amis secrets \" doit être activée. La fonction \" amis secrets \" permet à un membre de discuter avec un autre membre uniquement à l'aide d'un code secret qui doit être communiqué en dehors du jeu.\n\nPour activer cette fonction ou pour en savoir plus à propos de son fonctionnement, sortez du jeu Toontown et cliquez sur \" Options de compte \" sur la page Internet de Toontown." +NoSecretChatAtAllOK = lOK +NoSecretChatWarningTitle = "Contrôle parental" +NoSecretChatWarning = "Pour discuter avec un ami, la fonction \" amis secrets \" doit être activée. Les enfants doivent demander à leurs parents de s'identifier avec leur mot de passe \" parent \" pour en savoir plus sur la fonction \" amis secrets \" et avoir accès au contrôle parental." +NoSecretChatWarningOK = lOK +NoSecretChatWarningCancel = lCancel +NoSecretChatWarningWrongPassword = "Ce n'est pas le mot de passe correct. Veuillez entrer le mot de passe \" parent \" créé lorsque vous avez acheté ce compte. Ce mot de passe n'est pas celui utilisé pour jouer." + +ActivateChat = """La fonction " amis secrets " permet à un membre de discuter avec un autre membre uniquement à l'aide d'un code secret qui doit être communiqué en dehors du jeu. Pour une description complète, cliquez ici : + +La fonction " amis secrets " n'est ni modérée ni surveillée. Si les parents autorisent leurs enfants à utiliser leur compte avec la fonction " amis secrets " activée, nous les encourageons à surveiller leurs enfants lorsqu'ils jouent. Une fois activée, la fonction " amis secrets " est disponible jusqu'à ce qu'elle soit désactivée. + +En activant la fonction " amis secrets ", vous reconnaissez qu'elle comporte des risques inhérents, que vous avez été informés de ceux-ci, et que vous acceptez lesdits risques.""" + +ActivateChatYes = "Activer" +ActivateChatNo = lCancel +ActivateChatMoreInfo = "Plus d'infos" +ActivateChatPrivacyPolicy = "Politique de confidentialité" + +LeaveToPay = """De manière à pouvoir effectuer votre achat, vous allez automatiquement quitter le jeu et être redirigé sur le site Toontown.""" +LeaveToPayYes = "Acheter" +LeaveToPayNo = lCancel + +LeaveToSetParentPassword = """Afin de définir votre mot de passe " parent ", vous allez automatiquement quitter le jeu et être redirigé(e) sur le siteToontown.""" +LeaveToSetParentPasswordYes = "Définir le mot de passe" +LeaveToSetParentPasswordNo = lCancel + +LeaveToEnableChatUK = """ Pour activer le Chat le jeu devra quitter le site Toontown.""" +LeaveToEnableChatUKYes = "Activer le Chat!" +LeaveToEnableChatUKNo = lCancel + +ChatMoreInfoOK = lOK +SecretChatActivated = "La fonction \" amis secrets \" a été activée!\n\nSi vous changez d'avis et décidez de désactiver cette fonction ultérieurement, cliquez sur \" Options de compte \" sur la page Internet de Toontown." +SecretChatActivatedOK = lOK +ProblemActivatingChat = "Désolé! Nous n'avons pas pu activer la fonction de chat \" amis secrets \".n\n%s\n\nRessayez plus tard." +ProblemActivatingChatOK = lOK + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Salut, %!", + "Youhouu %,\nravi de te voir.", + "Je suis content que tu sois là aujourd'hui!", + "Bien le bonjour, %.", + ] + +SharedChatterComments = [ + "C'est un super nom, %.", + "J'aime bien ton nom.", + "Fais attention aux " + Cogs + "." + "On dirait que le tramway arrive!", + "Je dois jouer à un jeu du tramway pour avoir quelques morceaux de tarte!", + "Quelquefois, je joue aux jeux du tramway juste pour manger de la tarte aux fruits!", + "Ouf, je viens d'arrêter un groupe de " + Cogs + ". J'ai besoin de repos!", + "Aïe, certains de ces " + Cogs + " sont costauds!", + "On dirait que tu t'amuses.", + "Oh bon sang, quelle bonne journée.", + "J'aime bien ce que tu portes.", + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Amuse-toi bien dans mon quartier.", + "J'espère que tu profites bien de ton séjour à Toontown!", + "J'ai entendu dire qu'il neigeait dans le "+lTheBrrrgh+".", + "Est-ce que tu as fait un tour de tramway aujourd'hui?", + "J'aime bien rencontrer des nouveaux.", + "Aïe, il y a beaucoup de " + Cogs + " dans le "+lTheBrrrgh+".", + "J'aime bien jouer à chat. Et toi?", + "Les jeux du tramway sont amusants.", + "J'aime bien faire rire les gens.", + "J'adore aider mes amis.", + "Hum, serais-tu perdu(e)? N'oublie pas que ta carte est dans ton journal de bord.", + "Essaie de ne pas te noyer dans la paperasserie des " + Cogs + ".", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + "Si tu appuies sur la touche \" page précédente \", tu peux regarder vers le haut!", + "Si tu aides à reprendre des bâtiments aux Cogs, tu peux gagner une étoile de bronze!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Si tu appuies sur la touche Ctrl, tu peux sauter!", + ] + +SharedChatterGoodbyes = [ + "Je dois partir maintenant, au revoir!", + "Je crois que je vais aller faire un jeu du tramway.", + "Eh bien, au revoir. À bientôt, %!", + "Il vaudrait mieux que je me dépêche et que je m'occupe d'arrêter ces " + Cogs + ".", + "C'est l'heure d'y aller.", + "Désolé, je dois partir.", + "Au revoir.", + "À plus tard,%!", + "Je crois que je vais aller m'entraîner à lancer des petits gâteaux.", + "Je vais me joindre à un groupe et arrêter des " + Cogs + ".", + "Je suis content(e) de t'avoir vu(e) aujourd'hui, %.", + "J'ai beaucoup de choses à faire. Je ferais mieux de m'y mettre.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bienvenue à "+lToontownCentral+".", + "Salut, je m'appelle ""+Mickey+". Et toi?", + ], + [ # Comments + "Dis donc, as-tu vu "+Donald+"?", + "Je vais aller regarder le brouillard se lever sur les "+lDonaldsDockNC+".", + "Si tu vois mon copain "+Goofy+", dis-lui bonjour de ma part.", + "J'ai entendu dire que "+Daisy+" a planté de nouvelles fleurs dans son jardin.", + ], + [ # Goodbyes + "Je vais au pays musical voir "+Minnie+"!", + "Aïe, je suis en retard pour mon rendez-vous avec "+Minnie+"!", + "On dirait que c'est l'heure du dîner pour "+Pluto+".", + "Je crois que je vais aller nager aux "+lDonaldsDockNC+".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bienvenue au Pays musical.", + "Salut, je m'appelle "+Minnie+". Et toi?", + ], + [ # Comments + "Les collines sont animées par les notes de musique!", + "N'oublie pas d'essayer le grand manège tourne-disques!", + "Tu as une chouette tenue, %.", + "Dis donc, as-tu vu "+Mickey+"?", + "Si tu vois mon ami "+Goofy+", dis-lui bonjour de ma part.", + "Aïe, il y a beaucoup de "+Cogs+" près du Pays des rêves de "+Donald+".", + "J'ai entendu dire qu'il y a du brouillard sur les "+lDonaldsDockNC+".", + "N'oublie pas d'essayer le labyrinthe dans le "+lDaisyGardensNC+".", + "Je crois bien que je vais aller chercher quelques airs de musique.", + "Hé %, regarde donc par là-bas.", + "J'aime bien entendre de la musique.", + "Je parie que tu ne savais pas que le Pays musical de Minnie est aussi appelé le Haut-Bois? Hi hi!", + "J'aime bien jouer aux imitations. Et toi?", + "J'aime bien faire rire les gens.", + "Oh là là, ça fait mal aux pieds de trotter toute la journée avec des talons!", + "Belle chemise, %.", + "Est-ce que c'est un bonbon par terre?", + ], + [ # Goodbyes + "Aïe, je suis en retard pour mon rendez-vous avec %s!" % Mickey, + "On dirait que c'est l'heure du dîner pour %s." % Pluto, + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bienvenue au "+lDaisyGardensNC+".", + "Salut, je m'appelle "+Goofy+". Et toi?", + "Wof, je suis content de te voir, %!", + ], + [ # Comments + "Bon sang, c'est facile de se perdre dans le labyrinthe!", + "N'oublie pas d'essayer le labyrinthe tant que tu es ici.", + "Je n'ai pas vu "+Daisy+" de la journée.", + "Je me demande où se trouve " + Daisy + ".", + "Dis donc, as-tu vu "+Donald+"?", + "Si tu vois mon ami "+Mickey+", dis-lui bonjour de ma part.", + "Oh! J'ai oublié le petit déjeuner de "+Mickey+"!", + "Wof, il y a beaucoup de "+Cogs+" près des "+lDonaldsDockNC+".", + "On dirait que "+Daisy+" a planté de nouvelles fleurs dans son jardin.", + "À la succursale du Glagla de ma boutique à gags, les lunettes hypnotiques sont en vente pour seulement 1 bonbon!", + "La boutique à gags de Dingo propose les meilleures blagues, astuces et chatouilles de tout Toontown!", + "À la boutique à gags de Dingo, chaque tarte à la crème est garantie faire rire ou vos bonbons seront remboursés!" + ], + [ # Goodbyes + "Je vais au Pays musical voir %s!" % Minnie, + "Aïe, je suis en retard pour mon rendez-vous avec %s!" % Donald, + "Je crois que je vais aller nager aux "+lDonaldsDockNC+".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bienvenue au Pays des rêves.", + "Salut, je m'appelle %s. Et toi?" % Donald, + ], + [ # Comments + "Cet endroit me donne quelquefois la chair de poule.", + "N'oublie pas d'essayer le labyrinthe dans le "+lDaisyGardensNC+".", + "Oh bon sang, quelle bonne journée.", + "Dis donc, as-tu vu "+Mickey+"?", + "Si tu vois mon copain "+Goofy+", dis-lui bonjour de ma part." + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Aïe, il y a beaucoup de "+Cogs+" près des "+lDonaldsDockNC+".", + "Hé dis donc, tu n'as pas encore fait un tour de bateau avec moi aux "+lDonaldsDockNC+"?", + "Je n'ai pas vu "+Daisy+" de la journée.", + "J'ai entendu dire que "+Daisy+" a planté de nouvelles fleurs dans son jardin.", + "Coin coin.", + ], + [ # Goodbyes + "Je vais au Pays musical voir %s!" % Minnie, + "Aïe, je suis en retard pour mon rendez-vous avec %s!" % Daisy, + "Je crois que je vais aller nager près de mes quais.", + "Je crois que je vais aller faire un tour de bateau près de mes quais.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + + +# ToontownClientRepository.py +TCRConnecting = "En cours de connexion..." +# host, port +TCRNoConnectTryAgain = "Impossible de se connecter à %s:%s. Ressayer?" +TCRNoConnectProxyNoPort = "Impossible de se connecter à %s:%s.\n\nVous êtes connecté(e) à Internet par un proxy, mais ce proxy ne permet pas les connexions par le port %s.\n\nVous devez ouvrir ce port ou désactiver votre proxy pour pouvoir jouer à Toontown. Si votre proxy vous est fourni par votre fournisseur d'accès, contactez ce dernier et demandez-lui d'ouvrir ce port." +TCRNoDistrictsTryAgain = "Aucun district de Toontown n'est disponible. Ressayer?" +TCRLostConnection = "Votre connexion Internet à Toontown s'est inopinément interrompue." +TCRBootedReasons = { + 1: "Un problème inattendu est survenu. Votre connexion est perdue, mais vous devriez pouvoir vous reconnecter et retourner directement dans le jeu.", + 100: "Vous avez été déconnecté(e) parce que quelqu'un d'autre vient d'ouvrir une session avec votre compte sur un autre ordinateur.", + 120: "Vous avez été déconnecté du fait d'un problème avec votre autorisation d'utilisation du chat \" clavier \".", + 122: "Un problème inattendu est survenu lors de votre connexion à Toontown. Veuillez contacter le service clients de Toontown.", + 125: "Vos fichiers Toontown installés ne sont pas valides. Utilisez le bouton \" Jouer \" sur le site officiel de Toontown pour lancer Toontown.", + 126: "Vous n'êtes pas autorisé(e) à utiliser les fonctions réservées aux administrateurs.", + 151: "Vous avez été déconnecté(e) par un administrateur travaillant sur les serveurs de Toontown.", + 153: "Le district de Toontown sur lequel vous étiez en train de jouer a été réinitialisé. Toutes les personnes jouant dans ce district ont été déconnectées. Vous devriez toutefois pouvoir vous reconnecter et revenir directement dans le jeu.", + 288: "Désolé, vous avez utilisé toutes vos minutes disponibles dans Toontown pour ce mois-ci.", + 349: "Désolé, vous avez utilisé toutes vos minutes disponibles dans Toontown pour ce mois-ci.", + } +TCRBootedReasonUnknownCode = "Un problème inattendu s'est produit (code d'erreur %s). Votre connexion est perdue, mais vous devriez pouvoir vous reconnecter et retourner directement dans le jeu." +TCRTryConnectAgain = "\n\nEssayer de se reconnecter?" +# avName +TCRTutorialAckQuestion = "%s vient d'arriver à Toontown.\n\nEst-ce que tu voudrais que " + Mickey + " te fasse visiter?" +TCRTutorialAckOk = lYes +TCRTutorialAckCancel = lNo +TCRToontownUnavailable = "Toontown est momentanément indisponible, nouvelle tentative..." +TCRToontownUnavailableCancel = lCancel +TCRNameCongratulations = "FÉLICITATIONS!!" +TCRNameAccepted = "Ton nom a été\napprouvé par le Conseil de Toontown.\n\nÀ partir de ce jour\ntu t'appelleras\n\"%s\"" +TCRServerConstantsProxyNoPort = "Impossible de contacter %s.\n\nVous êtes connecté à Internet par un proxy, mais ce proxy ne permet pas les connexions par le port %s.\n\nVous devez ouvrir ce port ou désactiver votre proxy pour pouvoir jouer à Toontown. Si votre proxy vous est fourni par votre fournisseur d'accès, contactez ce dernier et demandez-lui d'ouvrir ce port." +TCRServerConstantsProxyNoCONNECT = "Impossible de contacter %s.\n\nVous êtes connecté(e) à Internet par un proxy, mais ce proxy ne prend pas en charge la méthode CONNECT.\n\nVous devez activer cette fonction ou désactiver votre proxy pour pouvoir jouer à Toontown. Si votre proxy vous est fourni par votre fournisseur d'accès, contactez ce dernier et demandez-lui d'activer cette méthode." +TCRServerConstantsTryAgain = "Impossible de contacter %s.\n\nLe serveur de comptes de Toontown peut être temporairement hors service, ou votre connexion Internet est défaillante.\n\nRessayer?" +TCRServerDateTryAgain = "Impossible de trouver la date du serveur depuis %s. Ressayer?" +AfkForceAcknowledgeMessage = "Ton Toon s'est assoupi et est parti au lit." +PeriodTimerWarning = "Ta limite de temps dans Toontown pour ce mois-ci est presque atteinte!" +PeriodForceAcknowledgeMessage = "Tu as utilisé toutes tes minutes disponibles dans Toontown pour ce mois-ci. Reviens jouer de nouveau le mois prochain!" +TCREnteringToontown = "Accès à Toontown..." + +# FriendInvitee.py +FriendInviteeTooManyFriends = "%s voudrait être ton ami(e) mais tu as déjà trop d'amis sur ta liste!" +FriendInviteeInvitation = "%s voudrait être ton ami(e)." +FriendInviteeOK = lOK +FriendInviteeNo = lNo + +# FriendInviter.py +FriendInviterOK = lOK +FriendInviterCancel = lCancel +FriendInviterStopBeingFriends = "Arrêter d'être ami(e)." +FriendInviterYes = lYes +FriendInviterNo = lNo +FriendInviterClickToon = "Clique sur le Toon avec lequel tu voudrais devenir ami(e)." +FriendInviterTooMany = "Tu as trop d'amis sur ta liste pour pouvoir en ajouter un de plus maintenant. Tu vas devoir retirer des amis de ta liste si tu veux devenir ami(e) avec %s." +FriendInviterNotYet = "Veux-tu devenir ami(e) avec %s?" +FriendInviterCheckAvailability = "Recherche de la disponibilité de %s." +FriendInviterNotAvailable = "%s est occupé(e) en ce moment, ressaie plus tard." +FriendInviterWentAway = "%s est parti(e)." +FriendInviterAlready = "%s est déjà ton ami(e)." +FriendInviterAskingCog = "Demande à %s d'être ton ami(e)." +FriendInviterEndFriendship = "Es-tu certain de vouloir cesser d'être ami(e) avec %s?" +FriendInviterFriendsNoMore = "%s n'est plus ton ami(e)." +FriendInviterSelf = "Tu es déjà \" ami(e) \" avec toi-même!" +FriendInviterIgnored = "%s t'ignore." +FriendInviterAsking = "Demande à %s d'être ton ami(e)." +FriendInviterFriendSaidYes = "%s a dit oui!" +FriendInviterFriendSaidNo = "%s a dit non, merci." +FriendInviterFriendSaidNoNewFriends = "%s ne cherche pas de nouveaux amis pour l'instant." +FriendInviterTooMany = "%s a déjà trop d'amis!" +FriendInviterMaybe = "%s n'a pas pu répondre." +FriendInviterDown = "Ne peut pas se faire d'amis pour l'instant." + +# FriendSecret.py +FriendSecretIntro = "Si tu joues à Toontown en ligne de Disney avec quelqu'un que tu connais réellement, vous pouvez devenir amis secrets. Tu peux communiquer avec tes amis secrets à l'aide du clavier. Les autres Toons ne comprendront pas ce que vous êtes en train de dire.\n\nPour cela, il faut échanger un secret. Dis le secret à ton ami(e), mais à personne d'autre. Lorsque ton ami(e) écrit ton secret sur son écran, vous pourrez être amis secrets dans Toontown!" +FriendSecretGetSecret = "Obtenir un secret" +FriendSecretEnterSecret = "Si quelqu'un t'a donné un secret, écris-le ici." +FriendSecretOK = lOK +FriendSecretCancel = lCancel +FriendSecretGettingSecret = "Recherche du secret... ." +FriendSecretGotSecret = "Voilà ton nouveau secret. N'oublie pas de l'écrire!\n\nTu ne peux donner ce secret qu'à une seule personne. Une fois que quelqu'un aura écrit ton secret, il ne pourra fonctionner pour personne d'autre. Si tu veux donner un secret à plus d'une personne, demande un autre secret.\n\nLe secret ne fonctionnera que dans les deux jours suivants. Ton ami(e) devra l'entrer sur son écran avant la fin de cette période pour qu'il puisse fonctionner.\n\nTon secret est :" +FriendSecretTooMany = "Désolé, tu ne peux plus avoir de secrets aujourd'hui. Tu en as déjà eu assez!n\nEssaie encore demain." +FriendSecretTryingSecret = "Recherche du secret..." +FriendSecretEnteredSecretSuccess = "Tu es maintenant ami(e) secret(e) avec %s!" +FriendSecretEnteredSecretUnknown = "Ce n'est le secret de personne. Es-tu certain(e) de l'avoir épelé correctement?\n\nSi tu l'as épelé correctement, il peut être périmé. Demande un nouveau secret à ton ami(e), ou prends-en un nouveau toi-même et donne-le à ton ami(e)." +FriendSecretEnteredSecretFull = "Tu ne peux pas être ami(e) avec %s parce que l'un(e) de vous a déjà trop d'amis sur sa liste." +FriendSecretEnteredSecretFullNoName = "Vous ne pouvez pas être amis parce que l'un de vous a déjà trop d'amis sur sa liste." +FriendSecretEnteredSecretSelf = "Tu viens juste d'écrire ton propre secret! Maintenant, personne d'autre ne peut plus utiliser ce secret." +FriendSecretNowFriends = "Tu es maintenant ami(e) secret(e) avec %s!" +FriendSecretNowFriendsNoName = "Vous êtes maintenant amis secrets!" + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Nouvel(le) ami(e)" +FriendsListPanelSecrets = "Secrets" +FriendsListPanelOnlineFriends = "AMIS\nEN LIGNE" +FriendsListPanelAllFriends = "TOUS\nLES AMIS" +FriendsListPanelIgnoredFriends = "TOONS\nIGNORÉS" + +# TeaserPanel.py +TeaserTop = "  Désolé! Tu n’as pas accès à ceci pendant l’essai gratuit.\n\nInscris-toi maintenant et profite de ces super fonctions :" +TeaserOtherHoods = "Visite les 6 quartiers exceptionnels!" +TeaserTypeAName = "Inscris le nom que tu préfères pour ton Toon!" +TeaserSixToons = "Crée jusqu’à 6 Toons par compte!" +TeaserOtherGags = "Additionne 6 niveaux d’habileté\ndans 6 séries de gags différentes!" +TeaserClothing = "Achète des vêtements originaux\npour personnaliser ton Toon!" +TeaserFurniture = "Achète et dispose des meubles dans ta maison!" +TeaserCogHQ = "Infiltre des zones dangereuses sur\nle territoire des Cogs!" +TeaserSecretChat = "Échange des secrets avec tes amis\npour pouvoir discuter en ligne avec eux!" +TeaserCardsAndPosters = "Participez aux compétitions avec les autres joueurs \net devenez l’un des Top Toons! \nVotre nom apparaîtra sur www.toontown.fr" +TeaserHolidays = "Participe à des événements spéciaux et\npassionnants et à des fêtes!" +TeaserQuests = "Relève des centaines de défitoons pour sauver Toontown!" +TeaserEmotions = "Achète des émotions pour rendre ton\nToon plus expressif!" +TeaserMinigames = "Joue aux 8 sortes de mini jeux!" +TeaserSubscribe = "S’inscrire Maintenant" +TeaserContinue = "Continuer l’essai" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Désolé, tu ne peux pas avancer parce que le téléchargement de %(phase)s n'en est qu'à %(percent)s% %.\n\nRéessaie plus tard." + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Téléchargement de: %s" +DownloadWatcherInitializing = "Initialisation du téléchargement..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Initialisation", + 3 : "Faire un Toon", + 3.5 : "Toontoriel", + 4 : "Terrain de jeux", + 5 : "Rues", + 5.5 : "Domaines", + 6 : "Quartiers I", + 7 : "Bâtiments " + Cog, + 8 : "Quartiers II", + 9 : "Quartiers généraux " + Cog, + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s sur %(total)s)" +LauncherStartingMessage = "Lancement de Toontown en ligne de Disney... " +LauncherDownloadFile = "Téléchargement des mises à jour: " + LauncherProgress + "..." +LauncherDownloadFileBytes = "Téléchargement des mises à jour: " + LauncherProgress + " : %(bytes)s" +LauncherDownloadFilePercent = "Téléchargement des mises à jour: " + LauncherProgress + " : %(percent)s% %" +LauncherDecompressingFile = "Décompression des mises à jour: " + LauncherProgress + "..." +LauncherDecompressingPercent = "Décompression des mises à jour: " + LauncherProgress + ". : %(percent)s% %" +LauncherExtractingFile = "Extraction des mises à jour: " + LauncherProgress + "..." +LauncherExtractingPercent = "Extraction des mises à jour: " + LauncherProgress + " : %(percent)s% %" +LauncherPatchingFile = "Application des mises à jour: " + LauncherProgress + "..." +LauncherPatchingPercent = "Application des mises à jour: " + LauncherProgress + " : %(percent)s% %" +LauncherConnectProxyAttempt = "En cours de connexion à Toontown: %s (proxy : %s) essai : %s" +LauncherConnectAttempt = "En cours de connexion à Toontown: %s essai %s" +LauncherDownloadServerFileList = "Mise à jour de Toontown..." +LauncherCreatingDownloadDb = "Mise à jour de Toontown..." +LauncherDownloadClientFileList = "Mise à jour de Toontown..." +LauncherFinishedDownloadDb = "Mise à jour de Toontown... " +LauncherStartingToontown = "Lancement de Toontown..." +LauncherRecoverFiles = "Mise à jour de Toontown. Récupération des fichiers..." +LauncherCheckUpdates = "Recherche de mises à jour pour "+ LauncherProgress +LauncherVerifyPhase = "Mise à jour de Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Faire un\nToon" +AvatarChoicePlayThisToon = "Jouer\navec ce Toon" +AvatarChoiceSubscribersOnly = "S’inscrire\n\n\n\nMaintenant!" +AvatarChoiceDelete = "Supprimer" +AvatarChoiceDeleteConfirm = "Cela va supprimer %s pour toujours." +AvatarChoiceNameRejected = "Nom\nrefusé" +AvatarChoiceNameApproved = "Nom\naccordé!" +AvatarChoiceNameReview = "En cours\nd'examen" +AvatarChoiceNameYourToon = "Donne un nom\nà ton Toon!" +AvatarChoiceDeletePasswordText = "Attention! Cela va supprimer %s pour toujours. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteConfirmText = "Attention! Cela va supprimer %(name)s pour toujours. Si tu es certain(e) de vouloir faire cela, entre \"%(confirm)s\" et clique sur OK." +AvatarChoiceDeleteConfirmUserTypes = "supprimer" +AvatarChoiceDeletePasswordTitle = "Supprimer le Toon?" +AvatarChoicePassword = "Mot de passe" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Ce mot de passe ne semble pas correspondre. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteWrongConfirm = "Tu n'as pas entré le bon mot. Pour supprimer %(name)s, entre \"%(confirm)s\" et clique sur OK. N'entre pas les guillemets. Clique sur Annuler si tu as changé d'avis." + +# AvatarChooser.py +AvatarChooserPickAToon = "Choisis un Toon pour jouer" +AvatarChooserQuit = lQuit + +# MultiPageTextFrame.py +MultiPageTextFrameNext = lNext +MultiPageTextFramePrev = 'Précédent' +MultiPageTextFramePage = 'Page %s/%s' + +# MemberAgreementScreen.py +MemberAgreementScreenTitle = 'Contrat de membre du service' +MemberAgreementScreenAgree = "J'accepte" +MemberAgreementScreenDisagree = 'Je refuse' +MemberAgreementScreenCancel = lCancel +MemberAgreementScreenWelcome = "Bienvenue!" +MemberAgreementScreenOnYourWay = "Vous êtes sur le point de devenir officiellement membre de" +MemberAgreementScreenToontown = "Toontown en ligne de Disney" +MemberAgreementScreenPricing = "Toontown en ligne de Disney coûte pour\nle premier mois. Chaque mois supplémentaire coûte .\nEt l'inscription est facile : lisez et remplissez simplement\nle formulaire d'informations ci-dessous et c'est parti!" +MemberAgreementScreenCCUpFrontPricing = "Inscrivez-vous maintenant pour votre essai GRATUIT de? jours. Vous pouvez annuler à tout moment\npendant votre période d'essai gratuit sans aucune obligation. À la fin de\nvotre période d'essai gratuit, le premier mois\nvous sera automatiquement facturé et ensuite chaque mois supplémentaire." +MemberAgreementScreenGetParents = "Vous devez avoir 18 ans ou plus pour acheter un abonnement à Toontown en ligne de Disney. Demandez de l'aide à un parent ou tuteur." +MemberAgreementScreenGetParentsUnconditional = "Vous devez avoir 18 ans ou plus pour acheter un abonnement à Toontown en ligne de Disney. Si vous avez moins de 18 ans, demandez de l'aide à un parent ou tuteur." +MemberAgreementScreenMustBeOlder = "Vous devez avoir 18 ans ou plus pour acheter un abonnement à Toontown en ligne de Disney. Demandez de l'aide à un parent ou tuteur." +MemberAgreementScreenYouMustAgree = "Pour acheter un abonnement à Toontown en ligne de Disney, vous devez accepter le contrat de membre du service." +MemberAgreementScreenYouMustAgreeOk = lOK +MemberAgreementScreenYouMustAgreeQuit = lQuit +MemberAgreementScreenAgreementTitle = "Contrat de membre du service " +MemberAgreementScreenClickNext = "Cliquez sur \""+lNext+"\" pour avancer d'une page." +# this is useful for tweaking the member agreement: +#import LocalizerEnglish; import Localizer +#reload(LocalizerEnglish);reload(Localizer);page=toonbase.tcr.memberAgreementScreen.memAgreement.getCurPage();toonbase.tcr.loginFSM.request('freeTimeInform');toonbase.tcr.loginFSM.request('memberAgreement');toonbase.tcr.memberAgreementScreen.memAgreement.setPage(page) +MemberAgreementScreenLegalText = [ +""" + + + + + +""" # spacing for graphics; start next section on a new line (i.e. """\nText) +""" +CONTRAT DE MEMBRE DU SERVICE TOONTOWN EN LIGNE DE DISNEY + +Bienvenue sur Toontown en ligne de Disney (ci-après désigné comme le " Service "). Veuillez LIRE ATTENTIVEMENT CE CONTRAT DE MEMBRE (ci-après désigné comme le " CONTRAT ") AVANT D'UTILISER CE SERVICE. Ce Service est détenu et géré par Disney Online (ci-après désigné comme " Disney ", " nous " ou " notre société "). +""",""" +Par le simple fait d'utiliser ce Service, vous acceptez ces conditions, les Conditions d'Utilisation et les Conditions Générales Disney affichées sur notre site Web. Si vous ne les acceptez pas, il est préférable que vous n'utilisiez pas ce Service. Veuillez noter que, dans ce Contrat, vous pouvez parfois être désigné comme " Membre ". De la même manière, la personne qui s'abonne initialement au Service peut être désignée comme " Compte Parent ". Par " Compte ", on entend le compte de tout Membre ouvert conformément aux modalités d'inscription au Service. Les conditions du présent Contrat s'appliquent à tous les Membres, qu'ils soient ou non Compte Parent. Le Compte Parent est tenu de porter les termes du présent Contrat à la connaissance de chacun des membres de sa famille (ainsi que de toute autre personne qu'il autorise à jouer en utilisant son Compte) et d'en assurer le respect par ces derniers. Le Compte Parent d'un Compte est entièrement responsable de toutes les activités effectuées par l'intermédiaire dudit Compte. +""",""" +Nous nous réservons le droit, à tout moment et à notre entière discrétion, de changer, modifier, d'ajouter ou ôter des parties de ce Contrat. Les changements apportés à ce Contrat seront publiés sur le Service ou vous seront adressés par courrier électronique ou postal. + +Si tout changement à venir dans ce Contrat vous semble inacceptable ou fait que vous ne respectez plus les termes dudit Contrat, vous pouvez résilier votre Compte. Le simple fait de continuer à utiliser le Service après la notification des changements apportés à ce Contrat (y compris les Conditions d'Utilisation et les Conditions Générales Disney) vaut acceptation desdits changements. +""",""" +Nous sommes susceptibles de modifier, changer, suspendre ou d'interrompre tout aspect de ce Service à tout moment, y compris, sans que la liste soit limitative, la disponibilité de toute fonction de ce Service, l'équipement requis pour y accéder, la base de données ou le contenu, ou encore les heures de disponibilité. Nous nous réservons également le droit d'imposer des limites à certaines fonctions ou de restreindre l'accès à certaines parties du Service ou à tout le Service pendant de longues périodes, sans aucun préavis ni aucune responsabilité. + +Chaque Membre est tenu de fournir l'équipement téléphonique et autre nécessaire pour accéder au Service, y compris, sans que la liste soit limitative, les modems et logiciels d'accès à Internet, et il en est seul responsable. +""",""" +RESTRICTIONS SUR L'UTILISATION DES DOCUMENTS + +Tous les documents publiés par Disney, y compris, sans que la liste soit limitative, les sources d'information, photographies, images, illustrations, clips audio et vidéo (désignés collectivement comme " Contenu ") sont protégés par copyright et détenus et contrôlés par Disney, sa société mère, ses filiales ou un fournisseur tiers. Vous êtes tenu de vous conformer à toutes les mentions de copyright, informations ou restrictions figurant dans tout Contenu auquel vous accédez par le biais du Service. +""",""" +Ce Service est protégé par copyright en tant qu'œuvre collective et/ou compilation selon la législation américaine sur les droits d'auteur, les conventions internationales et d'autres lois sur le copyright. Aucun des documents provenant de ce Service ou d'un site Web détenu, géré, concédé ou contrôlé par Disney, ne peut être copié, reproduit, republié, téléchargé, affiché ou transmis et aucune œuvre dérivée ne peut être créée ou distribuée d'aucune manière que ce soit, sauf dans les conditions suivantes : il vous est possible de télécharger une copie des documents sur un micro-ordinateur pour votre utilisation personnelle et à des fins non commerciales, pourvu que vous conserviez intacts les copyrights et autres mentions de propriété. L'utilisation du Contenu dans un autre but constitue une infraction au copyright de Disney et à ses autres droits de propriété. Dans le cadre de l'application de ce Contrat, il vous est interdit d'utiliser tout élément de notre Contenu sur un autre site Web ou dans un environnement informatique en réseau. Vous n'êtes pas autorisé à vendre ni à mettre aux enchères des personnages ou articles Disney, ni des documents protégés par copyright. +""",""" +Si vous décidez de procéder au téléchargement à partir de ce Service, le logiciel, y compris les fichiers, les images qui y sont intégrées ou qui sont créées par lui, ainsi que les données qui l'accompagnent (tous ces éléments étant collectivement dénommés comme " Logiciel ") vous sont concédés par Disney. Par les présentes, nous vous concédons une licence d'utilisation du Logiciel non exclusive, uniquement dans le cadre de ce Service par le biais d'un Compte autorisé et intégralement payé (ou d'un essai gratuit autorisé). Le Compte Parent reconnaît, autorise et s'engage à respecter : (a) qu'aucun des documents, de quelque nature que ce soit, soumis via le Compte Parent ne pourra (i) enfreindre, plagier ou annexer les droits d'un tiers, y compris les copyrights, marques, données confidentielles ou d'autres droits individuels ou droits de propriété ; (ii) ni contenir des documents diffamatoires ou illégaux ; (b) que les données bancaires fournies à Disney sont valides, que le Compte Parent est autorisé à les utiliser et que le Compte Parent est âgé de 18 ans au moins ; (c) que Disney est autorisé à effectuer des prélèvements sur le compte bancaire dont les coordonnées lui ont été fournies, ainsi qu'il est stipulé dans la partie " Prix et paiement " ci-dessous ; et (d) que le Compte Parent et tous ses Membres respecteront toutes les dispositions du présent Contrat. +""",""" +Par les présentes, vous, le Compte Parent, vous engagez à protéger, défendre et dégager Disney, sa société mère et ses filiales, ainsi que tous les directeurs, responsables, propriétaires, agents, fournisseurs d'informations, affiliés, concédants et détenteurs de licences (collectivement dénommés les " Parties Indemnisées ") de toute responsabilité et de tous frais encourus par les Parties Indemnisées en raison de toute action résultant d'une rupture du contrat par vous ou tout autre Membre sur votre Compte. Disney ne peut être tenu pour responsable de l'exactitude ou de la fiabilité des déclarations, opinions, conseils ou autres informations, quels qu'ils soient, publiés, téléchargés ou distribués via le Service par un Membre, un fournisseur d'informations ou toute autre personne ou entité. Vous reconnaissez que la prise en considération de ces conseils, opinions, déclarations, notes ou informations se fera entièrement à vos propres risques. Disney se réserve le droit, à sa seule discrétion, de corriger toute erreur ou omission dans toute partie du Service. +""",""" +DÉCHARGE + +LES DOCUMENTS DE CE SERVICE SONT FOURNIS " EN L'ÉTAT " ET SANS AUCUNE GARANTIE, EXPRESSE OU TACITE. DANS TOUTE LA MESURE AUTORISÉE PAR LA LÉGISLATION APPLICABLE, DISNEY DÉCLINE TOUTES GARANTIES, EXPRESSES OU TACITES, Y COMPRIS, MAIS SANS QUE CETTE ÉNUMÉRATION SOIT LIMITATIVE, LES GARANTIES TACITES DE COMMERCIALISATION ET D'APTITUDE À UN BUT DÉTERMINÉ. DISNEY NE GARANTIT PAS QUE LES FONCTIONS CONTENUES DANS CE SERVICE SERONT ASSURÉES SANS INTERRUPTION ET SANS ERREUR, QUE LES DÉFAUTS SERONT CORRIGÉS, NI QUE CE SITE OU LE SERVEUR QUI LE MET À DISPOSITION EST EXEMPT DE VIRUS ET AUTRES COMPOSANTS DANGEREUX. DISNEY NE PREND AUCUN ENGAGEMENT ET NE PROMET AUCUNE GARANTIE EN CE QUI CONCERNE L'UTILISATION DES DOCUMENTS DE CE SERVICE NI LES RÉSULTATS DE CETTE UTILISATION, EN TERMES D'EXACTITUDE, DE PRÉCISION, DE FIABILITÉ, ETC. +""",""" +LE COÛT INTÉGRAL DES PRESTATIONS NÉCESSAIRES, DE LA RÉPARATION OU DE LA CORRECTION VOUS INCOMBE ENTIÈREMENT (ET NON À DISNEY). IL EST POSSIBLE QUE LA LÉGISLATION APPLICABLE NE PERMETTE PAS L'EXCLUSION DES GARANTIES TACITES ; IL EST DONC POSSIBLE QUE LES EXCLUSIONS CI-DESSUS NE VOUS SOIENT PAS APPLICABLES. + +SANS PRÉJUDICE DE CE QUI PRÉCÈDE, VOUS RECONNAISSEZ QUE, POUR RENDRE SERVICE AUX UTILISATEURS DE CE SERVICE DISNEY, DISNEY INCLUT DES LIENS VERS D'AUTRES SITES WEB SUR LA PORTION WORLD WIDE WEB DU RÉSEAU INTERNET ET QUE DISNEY N'EXERCE AUCUN CONTRÔLE D'AUCUNE SORTE SUR LE CONTENU OU L'ADÉQUATION DU CONTENU DE CES SITES WEB ET NE PREND AUCUN ENGAGEMENT QUEL QU'IL SOIT À CET ÉGARD. PAR LES PRÉSENTES, VOUS RENONCEZ IRRÉVOCABLEMENT À EXERCER TOUTE ACTION CONTRE DISNEY EN LIEN AVEC CES SITES WEB. +""",""" +En outre, Disney décline explicitement toute responsabilité quant à l'exactitude, le contenu et la disponibilité des informations trouvées sur les sites de tiers, non partenaires de Disney, ayant des liens vers Toontown en ligne de Disney ou depuis ce Service. Disney vous recommande la discrétion quand vous naviguez sur Internet en utilisant son Service ou celui d'un tiers. Comme certains sites utilisent des résultats de recherche automatisés ou vous relient à des sites contenant des informations qui peuvent sembler inappropriées ou choquantes, Disney ne peut être tenu pour responsable de l'exactitude, la légalité ou la décence des informations contenues dans les sites tiers, ni du respect des copyrights par ces sites. Par les présentes, vous renoncez irrévocablement à exercer toute action contre Disney en lien avec ces sites. Disney ne peut vous garantir que vous serez satisfait des produits ou services que vous achetez à des tiers disposant de liens vers ou depuis Toontown en ligne de Disney, étant donné que les autres circuits commerciaux sont détenus et gérés par des commerçants indépendants. +""",""" +Disney ne recommande aucun des articles, et n'a pas pris de mesures en vue de confirmer l'exactitude ou la fiabilité des informations contenues dans lesdits sites tiers. Disney ne fait aucune observation ni ne garantit la sécurité des informations, notamment, de façon non limitative, les cartes de crédit ou toute autre information personnelle que vous pourriez être amené à fournir à des tierces parties et par la présente se dégage de toute responsabilité concernant lesdits sites tiers. Nous vous encourageons fortement à procéder à toute recherche que vous pourriez trouver nécessaire ou adéquate avant de traiter avec lesdites tierces parties que ce soit pour des transactions en ligne ou hors-ligne. +""",""" +LIMITATION DE RESPONSABILITÉ + +EN AUCUNE CIRCONSTANCE, Y COMPRIS, SANS QUE CETTE PRÉCISION SOIT LIMITATIVE, LA NÉGLIGENCE, DISNEY NE POURRA ÊTRE TENU POUR RESPONSABLE DES DOMMAGES SPÉCIAUX OU INDIRECTS RÉSULTANT DE L'UTILISATION DES DOCUMENTS DE CE SERVICE OU DE TOUT AUTRE SITE (OU DE L'IMPOSSIBILITÉ DE LES UTILISER), MÊME SI DISNEY OU UN REPRÉSENTANT AUTORISÉ DE CETTE SOCIÉTÉ AVAIT ÉTÉ AVERTI DE LA POSSIBILITÉ DE TELS DOMMAGES. LA LÉGISLATION APPLICABLE PEUT INTERDIRE LA LIMITATION OU L'EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES INDIRECTS OU SUBSÉQUENTS. IL EST DONC POSSIBLE QUE LES LIMITATIONS ET EXCLUSIONS SUSMENTIONNÉES NE VOUS SOIENT PAS APPLICABLES. EN AUCUN CAS, LA RESPONSABILITÉ FINANCIÈRE TOTALE DE DISNEY VIS-À-VIS DE VOUS, POUR LA TOTALITÉ DES DOMMAGES, PERTES ET CAUSES D'ACTION (CONTRACTUELLE, DÉLICTUELLE OU AUTRE, Y COMPRIS, MAIS SANS QUE CETTE PRÉCISION SOIT LIMITATIVE, LA NÉGLIGENCE) NE SAURAIT EXCÉDER LE PRIX PAYÉ PAR VOUS, LE CAS ÉCHÉANT, POUR AVOIR ACCÈS AU SERVICE. +""",""" +SÉCURITÉ + +Dans le cadre de la procédure d'inscription, les Membres doivent choisir un mot de passe, un mot de passe parent et un nom de Membre (ci-après désigné comme " Nom de Membre "). Vous êtes tenu de fournir à Disney des informations exactes, complètes et mises à jour sur votre Compte. Le non-respect de cette disposition constitue une rupture du Contrat, qui peut entraîner la résiliation immédiate de votre Compte. Vous n'êtes pas autorisé à (i) choisir ou utiliser le Nom de Membre d'une autre personne avec l'intention d'usurper l'identité de ladite personne ; (ii) utiliser sans autorisation un nom sur lequel une autre personne a des droits ; ou (iii) utiliser un Nom de Membre que Disney juge, à sa seule discrétion, inapproprié ou choquant. +""",""" +Vous vous engagez à avertir Disney, par l'envoi d'un e-mail à toontown@disneyonline.com, de toute utilisation interdite, connue ou soupçonnée, de votre Compte, quelle qu'elle soit, ou de toute infraction à la sécurité connue ou soupçonnée, y compris la perte, le vol ou la communication sans votre accord de votre mot de passe ou de votre mot de passe parent. Vous êtes tenu de protéger la confidentialité de votre mot de passe et de votre mot de passe parent. + +Tout Compte Parent doit être âgé de 18 ans au moins pour ouvrir un Compte. Si Disney apprend qu'un Compte Parent a moins de 18 ans, Disney se réserve le droit d'annuler le Compte. + +Toute activité frauduleuse, abusive ou illégale peut constituer un motif de résiliation de votre Compte, à la seule discrétion de Disney, et pourra être signalée aux autorités judiciaires compétentes. +""",""" +PRIX ET PAIEMENT + +Disney se réserve le droit à tout moment de faire payer des montants supplémentaires pour l'accès au Service. Disney se réserve le droit de changer le montant, ou la base de calcul, de tout montant ou de toute charge pour le Service, et de mettre en place de nouveaux montants ou charges effectifs après avertissement préalable des Membres. Disney se réserve le droit de permettre l'accès au Service gratuitement pour raisons promotionnelles ou autres (telles qu'un essai gratuit). + +Chaque Compte Parent accepte de payer tous les frais applicables audit Compte, y compris les taxes, conformément aux conditions de facturation en vigueur au moment où le prix et les frais deviennent exigibles. Les Comptes Parents sont tenus de fournir à Disney un numéro de carte bancaire valide au moment de la procédure d'inscription. +""",""" +Disney effectue le prélèvement sur le compte bancaire du Compte Parent à la date où le Compte Parent s'abonne au Service. Ensuite, Disney renouvellera automatiquement le Compte Parent et effectuera un prélèvement sur ledit Compte Parent comme suit : + +-Pour les abonnements mensuels, chaque mois pour le Service du mois suivant + +-Tous les trois (3) mois à compter de la date anniversaire de la première date de facturation pour les inscriptions trimestrielles. + +-Tous les six (6) mois à compter de la date anniversaire de la première date de facturation pour les inscriptions semestrielles. + +-Tous les ans à compter de la date anniversaire de la première date de facturation pour les inscriptions annuelles. +""",""" +Les charges de renouvellement seront égales ou inférieures au prix initial de l'inscription, sauf avertissement préalable par Disney. Vous pouvez à tout moment informer Disney de votre souhait d'annuler votre abonnement. Disney s'engage à résilier votre Compte sur réception d'une notification provenant du Compte parent, comme indiqué ci-dessous. + +Pour les inscriptions mensuelles : Si la notification d'annulation est reçue dans les 15 premiers jours suivant le premier jour de la facturation initiale, vous serez habilité à recevoir un remboursement de toutes les charges d'inscription au Service, mais serez tenu de régler toute autre charge qui s'ensuivrait. Si vous annulez l'inscription au Service plus de 15 jours après la facturation initiale, votre Compte sera annulé à la fin de la période de facturation en cours et aucun remboursement pour la période inutilisée ne sera consenti. +""",""" +Pour les inscriptions trimestrielles : Si la notification d'annulation est reçue dans les 30 premiers jours suivant le premier jour de la facturation initiale, vous serez habilité à recevoir un remboursement de toutes les charges d'inscription au Service, mais serez tenu de régler toute autre charge qui s'ensuivrait. Si vous annulez l'inscription au Service plus de 30 jours après la facturation initiale, aucun remboursement pour la période inutilisée ne sera consenti. + +Pour les inscriptions semestrielles : Si la notification d'annulation est reçue dans les 30 premiers jours suivant le premier jour de la facturation initiale, vous serez habilité à recevoir un remboursement de toutes les charges d'inscription au Service, mais serez tenu de régler toute autre charge qui s'ensuivrait. Si vous annulez l'inscription au Service plus de 30 jours après la facturation initiale, aucun remboursement pour la période inutilisée ne sera consenti. +""",""" +Pour les inscriptions annuelles : Si la notification d'annulation est reçue dans les 30 premiers jours suivant le premier jour de la facturation initiale, vous serez habilité à recevoir un remboursement de toutes les charges d'inscription au Service, mais serez tenu de régler toute autre charge qui s'ensuivrait. Si vous annulez l'inscription au Service plus de 30 jours après la facturation initiale, aucun remboursement pour la période inutilisée ne sera consenti. + +Votre droit d'utiliser le Service est soumis à toute restriction établie par Disney ou par votre fournisseur de carte bancaire. Si le paiement ne peut pas être débité sur vote carte bancaire ou si le débit revient impayé à Disney pour quelque raison que ce soit, y compris les rejets de débits, Disney se réserve le droit de résilier votre accès et votre Compte, mettant fin par là à l'Accord et à toutes les obligations de Disney ici énumérées. +""",""" +Si vous êtes redevable de sommes sur un Compte Disney, quel qu'il soit, vous acceptez que Disney puisse prélever ces sommes non réglées sur votre compte bancaire. Disney se réserve le droit de fixer une limite de crédit (le " Plafond ") pour chaque Membre. Si un Compte de Membre atteint le Plafond, Disney pourra immédiatement prélever sur la carte bancaire de ce Membre toutes les sommes encore exigibles sur son compte. Sauf avis contraire, le Plafond pour chaque Membre est fixé à 100 $. + +Si vous avez des raisons de croire que votre Compte n'est plus sécurisé (par exemple, dans le cas de perte, vol, divulgation non autorisée ou utilisation de votre Nom d'utilisateur, Mot de passe ou numéro de carte de crédit, débit ou autre enregistré sur le Service), vous devez rapidement changer de Mot de passe et avertir Disney du problème (par notification décrite à la section Notice ci-dessous) afin d'éviter toute responsabilité possible concernant des charges imputées à votre Compte. +""",""" +ACCORD PARENTAL + +Conformément au Children's Online Privacy Protection Act (" COPPA "), l'accord parental est exigé pour le recueil, l'utilisation et/ou la communication d'informations à caractère personnel en ligne, lorsque celles-ci ont été obtenues auprès d'un enfant de moins de 13 ans. Dans le cadre de la procédure d'inscription au Service, le Compte Parent sera invité à fournir un numéro de carte bancaire valide. Les parents et représentants légaux pourront créer jusqu'à 6 Toons (un Toon est un personnage que vous créez et que vous utilisez pour jouer sur le Service) sur le Compte Parent. Il faut donc qu'un des parents ou le représentant légal de l'enfant soit titulaire d'un Compte Parent pour que l'enfant puisse ensuite créer son propre Toon à l'intérieur de ce Compte Parent. +""",""" +En donnant son numéro de carte bancaire, le titulaire du Compte Parent : (a) déclare et garantit qu'il est le parent ou le représentant légal de tous les enfants de moins de 13 ans autorisés à utiliser son Compte Parent ; et (b) accepte que nous puissions recueillir, utiliser et communiquer, conformément à notre charte sur le Respect de la vie privée, les informations à caractère personnel sur les enfants de moins de 13 ans autorisés à utiliser le Compte Parent par le titulaire de ce Compte Parent. + +Le Service inclut une fonction interactive intitulée " Amis secrets ". Le Compte Parent a la possibilité de désactiver cette fonction Amis secrets une fois qu'il est dans le Service. Amis secrets permet à chaque membre de bavarder avec un autre membre, uniquement au moyen d'un code secret qui doit être communiqué en dehors du jeu. Amis secrets n'est ni animé ni supervisé. +""",""" +Si le Compte Parent autorise un enfant à utiliser son compte avec la fonction Amis secrets activée, nous invitons les parents à surveiller leur(s) enfant(s) pendant qu'il(s) utilise(nt) le Service. Le Compte Parent reconnaît que l'activation de la fonction Amis secrets entraîne certains risques inhérents à la fonction elle-même, qu'il a été averti desdits risques et qu'il les accepte. Vous aurez la possibilité d'obtenir davantage d'informations sur la fonction Secret Friends et de l'activer une fois que vous serez à l'intérieur du Service. +""",""" +AVIS + +Le Compte Parent est tenu de soumettre et de conserver une adresse e-mail exacte et d'autres informations sur son Compte. Nous pouvons adresser des avis au Compte Parent par avertissement général publié sur le Service, par courrier électronique envoyé à l'adresse figurant dans nos informations sur le Compte ou par courrier affranchi au tarif normal et envoyé à l'adresse figurant dans nos informations sur le Compte. Vous pouvez aussi adresser des avis à Disney. Ces avis seront supposés avoir été donnés lorsqu'ils auront été reçus par Disney par e-mail sur toontown@disneyonline.com. +""",""" +NON CESSIBILITÉ DE LA QUALITÉ DE MEMBRE + +Disney vous accorde une licence personnelle, non exclusive, non cessible et non transférable vous permettant d'utiliser et d'afficher le Logiciel Disney sur la machine ou toutes les machines dont vous êtes le principal utilisateur. Les copies non autorisées du Logiciel ou la reproduction du Logiciel, de quelque manière que ce soit, y compris par modification, fusion ou intégration à un autre logiciel, ainsi que des documents imprimés liés au Logiciel sont expressément interdites. Vous vous engagez à ne pas sous-licencier, transférer, vendre ou céder cette licence ou le Logiciel. Toute tentative visant à sous-licencier, transférer, vendre ou céder la licence sera nulle et non avenue. +""",""" +JURIDICTION + +Ce Service est contrôlé et géré par Disney à partir de ses bureaux de L'État de Californie (États-Unis). Disney ne garantit pas que les documents du Service sont appropriés ou disponibles pour une utilisation dans d'autres régions. Toute personne qui choisit d'accéder au Service à partir d'une autre région le fait de sa propre initiative et sera, le cas échéant, responsable du respect de la législation locale. Le Logiciel de ce Service est donc soumis aux contrôles d'exportation des États-Unis. Aucun Logiciel de ce Service ne peut être téléchargé ni autrement exporté ou réexporté (i) dans les pays suivants (ni à un ressortissant ou un résident de ces pays) : Cuba, Irak, Libye, Corée du Nord, Iran, Syrie ni aucun autre pays sur les marchandises duquel les États-Unis ont pris une décision d'embargo ; ni (ii) à quiconque dont le nom est porté sur la liste du Département de trésorerie des États-Unis (Specially Designated Nationals) ou sur la Table of Deny Orders du Département du Commerce des États-Unis. +""",""" +En téléchargeant ou en utilisant le Logiciel, vous certifiez et vous garantissez que vous n'habitez pas dans un des pays susmentionnés, que vous ne vous trouvez pas sous son contrôle, que vous n'êtes ni un ressortissant ni un résident d'aucune de ces régions, et que vous ne faites pas partie de l'une de ces listes. Certains logiciels que les Membres installent depuis un CD-Rom ou téléchargent pour les utiliser sont classés " Restricted Computer Software ". L'utilisation, la reproduction ou la communication par le Gouvernement des États-Unis sont soumises aux restrictions définies dans ce Contrat et stipulées dans les DFARS 227.7202-1(a) et 227.7202-3(a) (1995), DFARS 252.227-7013 (octobre 1988), FAR 12.212(a) (1995), FAR 52.227-19, ou FAR 52.227-14, le cas échéant. +""",""" +RÉSILIATION DU SERVICE + +Ce Contrat demeure en vigueur jusqu'à sa résiliation par l'une des parties. Vous pouvez résilier ce Contrat et votre droit d'utiliser le Service à tout moment, en adressant un e-mail à toontown@disneyonline.com. Disney peut résilier votre Compte ou vos droits d'accès à ce Service immédiatement, sans avis préalable, si Disney juge, à sa seule discrétion, que vous ne respectez pas l'une des dispositions ou conditions de ce Contrat (y compris les Règles d'Utilisation et les Conditions Générales Disney). Lors de la résiliation, vous serez tenu de détruire tous les documents obtenus avec ce Service et toutes les copies de ces documents, qu'elles étaient faites selon les termes du Contrat ou autrement. +""",""" +AUTRES + +Cet accord sera régi et interprété conformément avec les lois de l'État de Californie, sans considération de quelque règle relative aux éventuels conflits de loi. Dans le cas où une disposition de ce contrat serait illégale, nulle ou inapplicable pour quelque raison que ce soit, elle serait considérée comme ne faisant pas partie de ce contrat et n'affecterait ni la validité ni l'application des autres dispositions. La présente constitue l'Accord complet existant entre les parties sur l'objet qui y est indiqué, il ne pourra être modifié que par un écrit conforme aux indications ci-dessous. +""",""" +TOTALITÉ DE L'ACCORD + +Cet Accord constitue la totalité du contrat entre les parties en ce qui touche les questions visées par les présentes ; il annule et remplace tout accord, proposition ou communication précédent ou contemporain, oral ou écrit, entre les représentants de Disney et vous. Disney peut rectifier ou modifier cet Accord ou imposer de nouvelles conditions à tout moment sur avertissement par Disney, qui vous sera communiqué de la manière décrite dans la Section intitulée " Notice " ci-dessus. Toute utilisation par vous du Service après un tel avertissement implique l'acceptation par le Membre de ces rectifications, modifications ou nouvelles conditions. + +DERNIÈRE MISE À JOUR +10/18/2002 +""" +] + +# BillingScreen.py +BillingScreenCCTypeInitialText = 'Choisissez' +BillingScreenCreditCardTypes = ['Visa', 'American Express', 'MasterCard'] +BillingScreenTitle = "Veuillez indiquer vos informations de facturation" +BillingScreenAccountName = "Nom du compte" +BillingScreenEmail = "Adresse électronique de facturation/du parent" +BillingScreenEmailConfirm = "Confirmez votre adresse électronique" +BillingScreenCreditCardType = "Type de carte de crédit" +BillingScreenCreditCardNumber = "Numéro de carte de crédit" +BillingScreenCreditCardExpires = "Date d'expiration" +BillingScreenCreditCardName = "Nom tel qu'il est indiqué sur la carte de crédit" +#BillingScreenAgreementText = """*Par le simple fait de communiquer mon numéro de carte bancaire et de cliquer sur " Acheter ", je vous autorise, conformément à mon Contrat de Membre, (1) à effectuer des prélèvements sur ma carte bancaire et (2) à recueillir, utiliser et communiquer les informations à caractère personnel sur mon/mes enfant(s) conformément à la charte sur le Respect de la vie privée.""" +BillingScreenAgreementText = """Par le simple fait de cliquer sur " Acheter ", j'autorise mon/mes enfant(s) à utiliser les fonctions interactives autorisées par le Mot de Passe Parent que je créerai sur l'écran suivant, conformément à la charte sur le Respect de la vie privée.""" +BillingScreenBillingAddress = "Adresse de facturation : Rue 1" +BillingScreenBillingAddress2 = "Rue 2 (si applicable)" +BillingScreenCity = "Ville" +BillingScreenCountry = "Pays" +BillingScreenState = "Département" +BillingScreenZipCode = "Code postal" +BillingScreenCAProvince = "Province ou territoire" +BillingScreenProvince = "Province (si applicable)" +BillingScreenPostalCode = "Code postal" +BillingScreenPricing = (' pour le premier mois, puis' + ' par mois') +BillingScreenSubmit = "Acheter" +BillingScreenCancel = lCancel +BillingScreenConfirmCancel = "Annuler l'achat?" +BillingScreenConfirmCancelYes = lYes +BillingScreenConfirmCancelNo = lNo +BillingScreenPleaseWait = "Veuillez patienter..." +BillingScreenConnectionErrorSuffix = ".\nRessayez plus tard." +BillingScreenEnterEmail = "Indiquez votre adresse électronique." +BillingScreenEnterEmailConfirm = "Confirmez votre adresse électronique." +BillingScreenEnterValidEmail = "Entrez une adresse électronique valide." +BillingScreenEmailMismatch = "Les adresses électroniques indiquées ne correspondent pas. Ressayez." +BillingScreenEnterAddress = "Entrez votre adresse de facturation complète." +BillingScreenEnterValidState = "Entrez l'abréviation en deux lettres du nom de votre État." +BillingScreenChooseCreditCardType = "Choisissez un type de carte de crédit." +BillingScreenEnterCreditCardNumber = "Entrez votre numéro de carte de crédit." +BillingScreenEnterValidCreditCardNumber = "Vérifiez votre numéro de carte de crédit." +BillingScreenEnterValidSpecificCreditCardNumber = "Entrez un numéro de carte de crédit %s valide." +BillingScreenEnterValidCreditCardExpDate = "Entrez une date d'expiration de carte de crédit valide." +BillingScreenEnterNameOnCard = "Entrez votre nom tel qu'il apparaît sur votre carte de crédit." +BillingScreenCreditCardProblem = "Une erreur est survenue lors du traitement de votre carte de crédit." +BillingScreenTryAnotherCC = "Voulez-vous essayer une autre carte?" +# Fill in %s with phone number from account server +BillingScreenCustomerServiceHelp = "\n\nSi vous avez besoin d'aide, vous pouvez appeler le service clients au %s." +BillingScreenCCProbQuit = lQuit +BillingScreenWhySafe = "Sécurité de la carte de crédit" +BillingScreenWhySafeTitle = "Sécurité de la carte de crédit" +BillingScreenWhySafeCreditCardGuarantee = "GARANTIE DE LA CARTE DE CRÉDIT" +BillingScreenWhySafeJoin = "DEVENIR MEMBRE" +BillingScreenWhySafeToontown = "TOONTOWN EN LIGNE DE DISNEY" +BillingScreenWhySafeToday = "DÈS AUJOURD'HUI!" +BillingScreenWhySafeClose = lClose +BillingScreenWhySafeText = [ +""" + + + + +Nous utilisons la technologie SSL (Secure Sockets Layer) qui chiffre vos informations de carte de crédit, préservant leur confidentialité et leur protection. Cette technologie sécurise l'entrée et la transmission de vos informations de carte de crédit sur Internet. +Cette technologie de sécurisation protège vos communications Internet avec : + + Authentification du serveur (mise en échec des imposteurs) + Confidentialité grâce au chiffrement (mise en échec des indiscrétions) + Intégrité des données (mise en échec du vandalisme) + +Afin de vous offrir un niveau de sécurité complémentaire, les numéros de carte de crédit sont stockés sur un ordinateur qui n'est pas connecté à Internet. Après que vous l'ayez inscrit, votre numéro de carte de crédit complet est transféré sur cet ordinateur sécurisé par l'intermédiaire d'une interface de technologie exclusive. Votre numéro de carte de crédit n'est conservé nulle part ailleurs. + + + +Ainsi, vos informations de carte de crédit sont plus qu'en sûreté avec Toontown en ligne de Disney -- nous pouvons le garantir! +Nous couvrons toute inscription à Toontown en ligne de Disney avec notre garantie de carte de crédit. Si, bien que vous n'en soyez pas responsable, des débits non autorisés apparaissent sur votre relevé bancaire résultant directement de votre communication d'informations de carte de crédit à Toontown en ligne de Disney, nous couvrirons le montant pour lequel votre banque vous tient responsable, jusqu'à un maximum de 50 $. + +Si vous suspectez un problème, suivez les procédures normales de signalement telles qu'elles sont définies par votre fournisseur de carte bancaire et contactez-nous également immédiatement. La plupart des organismes de cartes bancaires couvrent toutes les charges résultant d'un usage non autorisé, mais elles peuvent légalement vous tenir responsable pour des montants allant jusqu'à 50 $. Nous couvrirons ce montant non couvert par votre organisme de carte bancaire. +Qu'est-ce que tout cela signifie? Cela signifie que vous pouvez avoir confiance dans la sécurité et le support relatifs à votre inscription à Toontown en ligne de Disney. + +Alors, qu'attendez-vous? +""", +] +BillingScreenPrivacyPolicy = "Politique de confidentialité" +BillingScreenPrivacyPolicyClose = lClose +BillingScreenPrivacyPolicyText = [ +""" +Politique de confidentialité + +Q1 Quels sont les types d'informations que recueillent les sites de WDIG, et de quelle façon les recueillent-ils? + +La majorité des superbes produits et services présentés sur nos sites sont proposés sans que nous recueillions d'informations à caractère personnel à votre sujet. Vous pouvez naviguer sur les sites Internet de WDIG et voir une grande partie de nos superbes contenus de manière anonyme. Par exemple, vous pouvez consulter les titres de l'actualité sur ABCNEWS.com sans fournir d'informations à caractère personnel. + +Les informations que vous fournissez +Quelques activités sur nos sites requièrent une collecte d'informations à caractère personnel. Ces activités incluent par exemple la participation à un concours ou tirage au sort, des achats ou le fait de nous contacter. Lorsque des informations à caractère personnel sont recueillies, vous le saurez puisque vous devrez remplir un formulaire. Pour la plupart des activités, nous recueillons uniquement vos nom, adresse électronique, date de naissance, sexe et code postal. Lorsque vous faites un achat, nous recueillons également vos adresses postale et de facturation, votre numéro de téléphone et vos informations de carte de crédit. En fonction du type d'achat effectué, nous devons parfois recueillir d'autres informations personnelles, telles que votre taille d'habillement. +""",""" +Les informations recueillies par l'intermédiaire de la technologie +Les sites de WDIG recueillent des informations vous concernant par l'intermédiaire d'outils technologiques, de sorte que vous ne saurez pas immédiatement qu'elles sont recueillies. Par exemple, lorsque vous venez sur notre site, votre adresse IP est recueillie afin que nous puissions savoir où envoyer les informations que vous demandez. Une adresse IP est souvent associée avec la manière dont vous vous connectez à Internet, tel que votre fournisseur d'accès Internet, votre entreprise ou votre université. Ces informations ne sont pas à caractère personnel. Les sites de WDIG utilisent les informations recueillies par l'intermédiaire d'outils technologiques dans l'objectif de rendre nos sites plus intéressants et utiles pour vous. Cela inclut une aide aux annonceurs de notre site à concevoir des publicités que nos visiteurs peuvent apprécier. Nous n'associons généralement pas ce type d'informations avec des informations à caractère personnel. Cependant, nous associons ces informations avec des informations à caractère personnel afin d'identifier un visiteur dans l'objectif de faire respecter les règlements ou termes d'utilisation ou pour protéger notre service, notre site, les visiteurs ou autres. + +Que sont les cookies, et de quelle manière WDIG les utilise-t-il? +Les cookies sont des informations envoyées par un site Internet à votre ordinateur lorsque vous consultez ledit site. Ces informations permettent au site Internet de mémoriser des informations importantes qui permettront d'améliorer votre usage du site. WDIG et d'autres entreprises sur Internet utilisent les cookies pour de nombreux usages. Par exemple, DisneyStore.com utilise les cookies pour mémoriser et traiter les articles de votre caddie, et tous les sites de WDIG utilisent les cookies pour s'assurer que les enfants n'accèdent pas aux salles de chat sans restriction. + +Vous pouvez choisir d'afficher un avertissement sur votre écran à chaque fois qu'un cookie est envoyé, ou vous pouvez choisir de désactiver tous les cookies. Utilisez pour cela les paramètres de votre navigateur (tel que Netscape Navigator ou Internet Explorer) Chaque navigateur est légèrement différent des autres ; consultez le menu d'aide de votre navigateur pour connaître la méthode adéquate pour modifier le comportement de vos cookies. Si vous désactivez les cookies, vous n'aurez plus accès à de nombreuses fonctions de WDIG, utilisées pour rendre votre utilisation d'Internet plus efficace - telles que les fonctions indiquées ci-dessus et certains de nos services ne fonctionneront plus correctement. +""",""" +Q2 Comment est-ce que WDIG utilise les informations à caractère personnel qui ont été recueillies? + +WDIG utilise les informations à caractère personnel d'une manière limitée. Nous utilisons les informations pour l'exécution des transactions. Par exemple, si vous achetez une équipe imaginaire sur ESPN.com, nous utilisons vos informations pour traiter votre commande. De même, si vous nous contactez pour demander de l'aide, nous utiliserons ces informations pour vous contacter. Nous utilisons les informations recueillies pour vous avertir si vous avez gagné un jeu ou un concours. Les informations que nous recueillons sont utilisées pour vous envoyer par courrier électronique des mises à jour et des bulletins d'informations à propos de nos sites. Nous utilisons également les informations que vous nous communiquez pour vous envoyer par courrier électronique des promotions de WDIG et des offres spéciales proposées par nos sponsors tiers. +""",""" +Q3 Est-ce que WDIG partage ses informations avec d'autres entreprises ou organisations ne faisant pas partie de la famille de sites de WDIG? + +Le capital le plus important de notre entreprise, c'est vous. Nous ne faisons pas commerce des informations concernant nos visiteurs. Cependant, si c'est dans l'intérêt de nos visiteurs, nous partagerons vos informations ou nous vous enverrons des messages de la part d'autres entreprises de la manière décrite ci-dessous. Nous pouvons aussi partager des informations pour des raisons de sécurité. +Sociétés ayant la même position que WDIG +Parfois, nous faisons appel à d'autres sociétés pour nous aider à fournir les produits et services, à l'instar des sociétés de messagerie qui livrent des colis. Dans ce cas, nous devons partager les informations avec elles. Ces sociétés ont fondamentalement la même position que WDIG et ne sont autorisées à utiliser ces informations que pour livrer des produits ou services. +""",""" +Entreprises proposant des promotions, produits ou services +Occasionnellement, nous offrons des promotions - telles que des tirages au sort ou des inscriptions gratuites - en liaison avec un sponsor. Nous partageons vos informations avec les sponsors s'ils en ont besoin pour vous envoyer un produit, tel qu'un abonnement à un magazine. Nous pouvons partager vos informations avec ces sponsors afin qu'ils puissent vous envoyer d'autres promotions particulières qu'ils proposent, mais seulement si vous nous autorisez à le faire, et nous ne les partagerons qu'avec ce sponsor particulier. De plus, WDIG envoie occasionnellement des promotions par courrier électronique à ses visiteurs de la part de sponsors tiers. Dans ce cas, nous ne communiquons pas votre identité à la partie tierce - nous nous occupons pour eux du publipostage. De la même façon, nous ne vous enverrons ces promotions que si vous nous autorisez à le faire. + +Partenaires de contenu +Sur certains de nos sites, nous proposons des contenus créés par des sites Internet partenaires tiers. Par exemple, ESPN.com propose des offres commerciales de tierces parties. Dans certains cas, les sites tiers recueillent des informations afin de faciliter la transaction ou de rendre l'utilisation de leur contenu plus productive et efficace. Dans ces circonstances, les informations recueillies sont partagées entre WDIG et nos sponsors tiers. + +Annonceurs tiers et annonceurs de réseau +Afin d'améliorer la protection de la confidentialité de nos visiteurs, WDIG autorise la publicité sur nos sites venant uniquement d'entreprises possédant leur propre politique de confidentialité. Une fois que vous avez cliqué sur une publicité et quitté les sites de WDIG, notre politique de confidentialité ne s'applique plus. Vous devez lire la politique de confidentialité de l'annonceur afin de savoir de quelle manière vos informations personnelles seront traitées sur son site. +""",""" +De plus, nombre de publicités commerciales sont gérées et placées sur notre site par des sociétés tierces, que l'on appelle des " annonceurs de réseau ". Les annonceurs de réseau recueillent des informations à caractère non personnel lorsque vous balayez avec votre curseur une de leurs bannières publicitaires ou cliquez dessus. Ces informations sont recueillies à l'aide d'outils technologiques, de sorte que vous pouvez ne pas réaliser qu'elles sont recueillies. Les annonceurs de réseau recueillent ces informations afin de pouvoir afficher sur votre écran les publicités qui sont les plus adaptées et intéressantes pour vous. Pour en savoir plus à propos des annonceurs de réseau, ou si vous ne voulez pas que les annonceurs de réseau recueillent ces informations à caractère non personnel sur vous, cliquez ici. + +Achat ou vente d'entreprises +Le commerce en ligne est encore à un stade de balbutiement, il change et évolue rapidement. WDIG cherche continuellement de nouvelles façons de s'améliorer, et est susceptible de vendre ou acheter une entreprise. Si nous achetons ou vendons une entreprise, les données recueillies seront probablement transférées comme faisant partie de la vente. Les informations concernant les inscrits seront incluses dans l'ensemble de la transaction. Cependant, si nous achetons une entreprise, nous honorerons les demandes faites par les clients auprès de cette entreprise concernant les communications par courrier électronique. Dans l'éventualité où nous vendrions une entreprise, nous ferons tout ce qui est en notre pouvoir pour nous assurer que les demandes que vous nous avez faites concernant les communications par courrier électronique soient respectées. + +Les organisations qui aident à protéger la sûreté et la sécurité de nos visiteurs et de nos sites. +Nous fournirons des informations personnelles si la loi nous en fait obligation, par exemple pour obéir à une ordonnance ou une assignation ; pour faire respecter nos Conditions d'utilisation, règles du jeu ; ou pour protéger la sécurité de nos visiteurs et de nos sites. +""",""" +Q4 Quels sont les choix dont je dispose à propos du recueil, de l'utilisation et du partage de mes informations par WDIG? + +Il vous est possible d'utiliser une grande partie de nos sites sans transmettre d'informations à caractère personnel. Lorsque vous vous inscrivez chez nous ou que vous nous fournissez des informations à caractère personnel, vous avez la possibilité au moment où nous recueillons ces informations, de limiter les communications par courrier électronique provenant de WDIG ou de nos partenaires tiers. Vous pouvez demander à tout moment que WDIG ne vous envoie plus de courrier électronique en vous désinscrivant de toute communication ou en nous contactant à memberservices@help.go.com. De plus, comme il est indiqué ci-dessus, vous pouvez limiter les informations recueillies par l'intermédiaire d'outils technologiques, bien que certaines de nos fonctions puissent ne plus être disponibles si vous décidez de procéder de la sorte. +""",""" +Q5 Quel est le type de sécurité offert par WDIG? + +Le souci de la sécurité pour toutes les informations à caractère personnel associées à nos visiteurs est d'une extrême importance pour nous. WDIG prend toutes les mesures de sécurité techniques, contractuelles, administratives et physiques pour protéger les informations de tous les visiteurs. Lorsque vous nous fournissez des informations bancaires, nous utilisons la technologie de chiffrement SSL (Secure Socket Layer) pour les protéger. Vous avez également la possibilité de prendre des mesures afin de protéger la sécurité de vos informations. Par exemple, ne communiquez jamais votre mot de passe, parce qu'il est utilisé pour accéder à toutes vos informations de compte. Pensez également à fermer votre compte et la fenêtre de votre navigateur lorsque vous avez terminé de naviguer sur Internet, de sorte que d'autres personnes utilisant le même ordinateur ne puissent pas avoir accès à vos informations. +""",""" +Q6 Comment puis-je avoir accès aux informations de mon compte? + +Vous pouvez accéder aux informations à caractère personnel que vous nous avez données pendant votre inscription auprès du centre des Options de compte, accessible depuis (http://play.toontown). Identifiez-vous avec votre nom de compte et votre mot de passe " parent ". La page de démarrage contient des instructions destinées à vous aider à récupérer votre mot de passe si vous l'avez oublié. +Vous pouvez aussi nous contacter en cliquant sur "Contact Us" en bas de chaque page WDIG et en sélectionnant "Registration/Personalization" dans la liste déroulante, ou nous envoyer directement un courrier électronique à memberservices@help.go.com. Merci d'inclure les informations dans ce messsage qui nous aideront à identifier votre compte afin que nous puissions répondre à votre question ou votre demande. +""",""" +Q7 Qui puis-je contacter pour toute question concernant cette politique de confidentialité? + +Si vous avez besoin d'aide, envoyez un courrier électronique avec vos questions ou commentaires à memberservices@help.go.com +écrivez-nous à : + +Member Services +Walt Disney Internet Group +506 2nd Avenue +Suite 2100 +Seattle, WA 98104, États-Unis + +Walt Disney Internet Group est titulaire d'une licence du programme de confidentialité TRUSTe. Si vous pensez que WDIG n'a pas répondu à votre demande, ou que vous n'avez pas obtenu de réponse satisfaisante à votre demande, contactez TRUSTe http://www.truste.org/users/users_watchdog.html. +*Vous devez avoir plus de 18 ans ou obtenir la permission de votre parent ou tuteur pour composer ce numéro. +""",""" +Politique de confidentialité relative aux enfants : +Nous reconnaissons la nécessité de fournir une protection de la confidentialité plus importante pour les enfants qui visitent nos sites. + +Q1 Quels types d'informations les sites de WGIG recueillent-ils à propos des enfant âgés de 12 ans et moins? + +Les enfants peuvent naviguer sur Disney.com et les autres sites de WDIG, visualiser des contenus et jouer à des jeux sans qu'aucune information à caractère personnel ne soit recueillie. De plus, nous hébergeons de manière occasionnelle des salles de chat modérées où aucune information à caractère personnel n'est recueillie ni postée. Cependant, dans certaines zones, il est nécessaire de recueillir des informations à caractère personnel auprès des enfants pour leur permettre de participer à une activité (comme participer à un concours) ou pour communiquer avec notre communauté (par courrier électronique ou affichage de messages). +WDIG estime qu'il est préférable de ne pas recueillir auprès des enfants de 12 ans et moins plus d'informations à caractère personnel qu'il n'est nécessaire pour qu'ils participent à nos activités en ligne. De plus, vous devez savoir qu'il est légalement interdit par la loi à tous les sites qui sont destinés aux enfants de 12 ans et moins de recueillir plus d'informations qu'il n'est nécessaire. + +Les seules informations à caractère personnel que nous recueillons des enfants sont leur prénom, l'adresse électronique de leurs parents, et la date de naissance de l'enfant. Nous recueillons la date de naissance pour valider l'âge d'un visiteur. Nous pouvons aussi recueillir des informations à caractère personnel, telles que le nom d'un animal familier, pour aider les visiteurs à se remémorer leur nom d'utilisateur et leur mot de passe s'ils les oublient. + + +Nous autorisons aussi les parents à demander à tout moment que les informations recueillies au sujet de leur enfant soient retirées de notre base de données. Si vous voulez désactiver le compte de votre enfant, envoyez un message électronique à ms_support@help.go.com indiquant le nom d'utilisateur de votre enfant et son mot de passe, demandant que son compte soit annulé. +""",""" +Q2 Comment WDIG utilise et partage les informations à caractère personnel qui ont été recueillies? + +Aucune information recueillie auprès de visiteurs de 12 ans et moins n'est utilisée à des fins de marketing ou de publicité quels qu'ils soient, que ce soit à l'intérieur ou à l'extérieur de la famille des sites de Walt Disney Internet Group. +Les informations recueillies auprès d'enfants de 12 ans et moins sont utilisées uniquement par les sites Web WDIG pour offrir des services (tels que des calendriers) ou organiser des jeux ou concours. Bien que les visiteurs de 12 ans et moins puissent être autorisés à participer à certains concours pour lesquels des informations sont collectées, les notifications et les prix sont envoyés à l'adresse électronique des parents ou tuteurs, fournie lors du processus d'inscription initial. La publication des nom complet, âge ou photo des gagnants des concours lorsqu'il s'agit d'individus de 12 ans et moins requiert le consentement des parents ou des tuteurs. Il arrive qu'une version non identifiable du nom de l'enfant soit publiée. Dans ce cas, les parents peuvent ne pas être contactés de nouveau pour une demande de permission. + +Nous n'autorisons pas les enfants de 12 ans et moins à participer aux chats non modérés. + +Nous fournirons des informations personnelles au sujet des enfants si la loi nous en fait obligation, par exemple pour obéir à une ordonnance ou une assignation ; pour faire respecter nos Conditions d'utilisation, règles du jeu ; ou pour protéger la sécurité de nos visiteurs et de nos sites. +""",""" +Q3 Est-ce que WDIG notifie les parents au sujet du recueil d'informations sur des enfants de 12 ans et moins? + +À chaque fois que des enfants de 12 ans et moins s'inscrivent chez nous, nous envoyons un courrier électronique de notification à leurs parents ou tuteurs. De plus, nous demandons aux parents de donner leur permission expresse avant d'autoriser leurs enfants à utiliser le courrier électronique, les affichages de messages et autres fonctions dans le cadre desquels des informations à caractère personnel peuvent être rendues publiques sur Internet et partagées avec des utilisateurs de tous âges. +Nous donnons aussi 48 heures aux parents pour refuser toute inscription faite par l'enfant dans le but de jouer à des jeux et des concours. Si nous n'avons pas de réponse, nous considérons que l'enfant est autorisé à s'inscrire chez nous. Une fois qu'un enfant est inscrit, il ou elle est par la suite autorisé à participer à tout jeu ou concours exigeant une inscription, et les parents ne sont pas notifiés une nouvelle fois. Dans ce cas, nous utilisons les informations recueillies uniquement pour avertir les parents lorsqu'un enfant a gagné un jeu ou concours. Nous n'utilisons pas ces informations pour aucun autre usage. +""",""" +Q4 Comment les parents peuvent-ils accéder aux informations relatives à leurs enfants? + +Trois méthodes permettent de visualiser les informations qui ont été recueillies à propos des enfants de 12 ans et moins. + +Lorsque les parents accordent à leurs enfants l'accès à des fonctions interactives telles que les messages affichés, il leur est demandé d'établir un compte familial. Une fois qu'un compte familial est établi, le titulaire du compte peut visionner les informations à caractère personnel de tous les comptes des membres de la famille, y compris ceux d'un enfant. Vous pouvez accéder à ces informations en ouvrant votre compte familial sur la page d'accueil, " Votre compte ". + +Si vous n'êtes membre d'aucun des sites WDIG, vous pouvez visionner les informations à caractère personnel de votre enfant en ouvrant le compte de celui-ci sur la page d'accueil des " Options de compte ". Vous aurez besoin du nom d'utilisateur et du mot de passe de votre enfant. La page " Votre compte " contient des instructions pour vous aider à récupérer le mot de passe de votre enfant s'il l'a oublié. + +Vous pouvez également contacter le Service clients pour visionner les informations qui ont été recueillies sur ou auprès de votre enfant en envoyant un courrier électronique à ms_support@help.go.com. Si vous n'avez pas encore de compte familial, vous aurez besoin du nom d'utilisateur et du mot de passe de votre enfant. Merci d'inclure les informations (nom d'utilisateur de votre enfant, adresse électronique des parents) dans le message, qui nous permettront d'identifier le compte de votre enfant afin que nous puissions répondre à votre question ou demande. +""",""" +Q5 Quel est le type de sécurité offert par WDIG? + +Le souci de la sécurité pour toutes les informations à caractère personnel associées à nos visiteurs est d'une extrême importance pour nous. WDIG prend toutes les mesures de sécurité techniques, contractuelles, administratives et physiques pour protéger les informations de tous les visiteurs. Lorsque vous nous fournissez des informations de carte de crédit, nous utilisons la technologie de chiffrement SSL (Secure Socket Layer) pour les protéger. Vous avez également la possibilité de prendre des mesures afin de protéger la sécurité de vos informations. Par exemple, ne communiquez jamais votre mot de passe, parce qu'il est utilisé pour accéder à toutes vos informations de compte. Pensez également à fermer votre compte et la fenêtre de votre navigateur lorsque vous avez terminé de naviguer sur Internet, de sorte que d'autres personnes utilisant le même ordinateur ne puissent pas avoir accès à vos informations. +""",""" +Q6 Comment WDIG notifie les parents si la politique de confidentialité est modifiée? + +Si WDIG modifie sa politique de confidentialité, nous avertissons les parents par courrier électronique. + +Q7 Qui puis-je contacter pour toute question concernant cette politique de confidentialité? + +Si vous avez besoin d'aide, envoyez-nous un courrier électronique avec vos questions ou commentaires à ms_support@help.go.com +écrivez-nous à : + +Member Services +Walt Disney Internet Group +506 2nd Avenue +Suite 2100 +Seattle, WA 98104, États-Unis +ou appelez-nous au 00 1 (509) 742-4698 + +Walt Disney Internet Group est titulaire d'une licence du programme de confidentialité TRUSTe. Si vous pensez que WDIG n'a pas répondu à votre demande, ou que vous n'avez pas obtenu de réponse satisfaisante à votre demande, contactez TRUSTe http://www.truste.org/users/users_watchdog.html. +*Vous devez avoir plus de 18 ans ou obtenir la permission de votre parent ou tuteur pour composer ce numéro. +""", +] +BillingScreenCountryNames = { + "US" : "États-Unis", + "CA" : "Canada", + "AF" : "Afghanistan", + "AL" : "Albanie", + "DZ" : "Algérie", + "AS" : "Samoa Américaines", + "AD" : "Andorre", + "AO" : "Angola", + "AI" : "Anguilla", + "AQ" : "Antarctique", + "AG" : "Antigua-et-Barbuda", + "AR" : "Argentine", + "AM" : "Arménie", + "AW" : "Aruba", + "AU" : "Australie", + "AT" : "Autriche", + "AZ" : "Azerbaïdjan", + "BS" : "Bahamas", + "BH" : "Bahreïn", + "BD" : "Bangladesh", + "BB" : "Barbade", + "BY" : "Bélarus", + "BE" : "Belgique", + "BZ" : "Belize", + "BJ" : "Bénin", + "BM" : "Bermuda", + "BT" : "Bhoutan", + "BO" : "Bolivie", + "BA" : "Bosnie-Herzégovine", + "BW" : "Botswana", + "BV" : "Île Bouvet", + "BR" : "Brésil", + "IO" : "Territoire britannique de l'océan Indien", + "BN" : "Brunei Darussalam", + "BG" : "Bulgarie", + "BF" : "Burkina Faso", + "BI" : "Burundi", + "KH" : "Cambodge", + "CM" : "Cameroun", + "CV" : "Cap-Vert", + "KY" : "Îles Cayman", + "CF" : "République Centrafricaine", + "TD" : "Tchad", + "CL" : "Chili", + "CN" : "Chine", + "CX" : "Île Christmas", + "CC" : "Îles Cocos (Keeling)", + "CO" : "Colombie", + "KM" : "Comores", + "CG" : "Congo", + "CK" : "Îles Cook", + "CR" : "Costa Rica", + "CI" : "Côte d'Ivoire", + "HR" : "Croatie", + "CU" : "Cuba", + "CY" : "Chypre", + "CZ" : "République Tchèque", + "CS" : "Ex-Tchécoslovaquie", + "DK" : "Danemark", + "DJ" : "Djibouti", + "DM" : "Dominique", + "DO" : "République Dominicaine", + "TP" : "Timor-Oriental", + "EC" : "Équateur", + "EG" : "Égypte", + "SV" : "Salvador", + "GQ" : "Guinée équatoriale", + "ER" : "Érythrée ", + "EE" : "Estonie", + "ET" : "Éthiopie", + "FK" : "Îles Malouines", + "FO" : "Îles Féroé", + "FJ" : "Fidji", + "FI" : "Finlande", + "FR" : "France", + "FX" : "France métropolitaine", + "GF" : "Guyane Française", + "PF" : "Polynésie Française", + "TF" : "Terres Australes Françaises", + "GA" : "Gabon", + "GM" : "Gambie", + "GE" : "Géorgie", + "DE" : "Allemagne", + "GH" : "Ghana", + "GI" : "Gibraltar", + "GB" : "Grande-Bretagne (Royaume-Uni)", + "GR" : "Grèce", + "GL" : "Groenland", + "GD" : "Grenade", + "GP" : "Guadeloupe", + "GU" : "Guam", + "GT" : "Guatemala", + "GN" : "Guinée", + "GW" : "Guinée-Bissau", + "GY" : "Guyana", + "HT" : "Haïti", + "HM" : "Îles Heard et McDonald", + "HN" : "Honduras", + "HK" : "Hong Kong", + "HU" : "Hongrie", + "IS" : "Islande", + "IN" : "Inde", + "ID" : "Indonésie", + "IR" : "Iran", + "IQ" : "Irak", + "IE" : "Irlande", + "IL" : "Israël", + "IT" : "Italie", + "JM" : "Jamaïque", + "JP" : "Japon", + "JO" : "Jordanie", + "KZ" : "Kazakhstan", + "KE" : "Kenya", + "KI" : "Kiribati", + "KP" : "Corée du Nord", + "KR" : "Corée du Sud", + "KW" : "Koweït", + "KG" : "Kirghizistan", + "LA" : "Laos", + "LV" : "Lettonie", + "LB" : "Liban", + "LS" : "Lesotho", + "LR" : "Libéria", + "LY" : "Libye", + "LI" : "Liechtenstein", + "LT" : "Lituanie", + "LU" : "Luxembourg", + "MO" : "Macao", + "MK" : "Macédoine", + "MG" : "Madagascar", + "MW" : "Malawi", + "MY" : "Malaisie", + "MV" : "Maldives", + "ML" : "Mali", + "MT" : "Malte", + "MH" : "Îles Marshall", + "MQ" : "Martinique", + "MR" : "Mauritanie", + "MU" : "Maurice", + "YT" : "Mayotte", + "MX" : "Mexique", + "FM" : "Micronésie", + "MD" : "Moldavie", + "MC" : "Monaco", + "MN" : "Mongolie", + "MS" : "Montserrat", + "MA" : "Maroc", + "MZ" : "Mozambique", + "MM" : "Myanmar (Birmanie)", + "NA" : "Namibie", + "NR" : "Nauru", + "NP" : "Népal", + "NL" : "Pays-Bas", + "AN" : "Antilles Néerlandaises", + "NT" : "Zone neutre", + "NC" : "Nouvelle-Calédonie", + "NZ" : "Nouvelle-Zélande (Aotearoa)", + "NI" : "Nicaragua", + "NE" : "Niger", + "NG" : "Nigéria", + "NU" : "Niue", + "NF" : "Île Norfolk ", + "MP" : "Îles Mariannes du Nord", + "NO" : "Norvège", + "OM" : "Oman", + "PK" : "Pakistan", + "PW" : "Palaos", + "PA" : "Panama", + "PG" : "Papouasie-Nouvelle-Guinée", + "PY" : "Paraguay", + "PE" : "Pérou", + "PH" : "Philippines", + "PN" : "Île Pitcairn", + "PL" : "Pologne", + "PT" : "Portugal", + "PR" : "Puerto Rico", + "QA" : "Qatar", + "RE" : "Réunion", + "RO" : "Roumanie", + "RU" : "Fédération de Russie", + "RW" : "Rwanda", + "GS" : "Îles de Géorgie du Sud et Sandwich du Sud", + "KN" : "Saint Kitts and Nevis", + "LC" : "Sainte-Lucie", + "VC" : "Saint-Vincent-et-les-Grenadines", + "WS" : "Samoa", + "SM" : "Saint-Marin", + "ST" : "Sao Tomé et Principe", + "SA" : "Arabie saoudite", + "SN" : "Sénégal", + "SC" : "Seychelles", + "SL" : "Sierra Leone", + "SG" : "Singapour", + "SK" : "Slovaquie", + "SI" : "Slovénie", + "Sb" : "Îles Salomon", + "SO" : "Somalie", + "ZA" : "Afrique du Sud", + "ES" : "Espagne", + "LK" : "Sri Lanka", + "SH" : "Sainte-Hélène", + "PM" : "St-Pierre-et-Miquelon", + "SD" : "Soudan", + "SR" : "Surinam", + "SJ" : "Îles Svalbard et Jan Mayen", + "SZ" : "Swaziland", + "SE" : "Suède", + "CH" : "Suisse", + "SY" : "Syrie", + "TW" : "Taïwan", + "TJ" : "Tadjikistan", + "TZ" : "Tanzanie", + "TH" : "Thaïlande ", + "TG" : "Togo", + "TK" : "Tokelau", + "TO" : "Tonga", + "TT" : "Trinidad et Tobago", + "TN" : "Tunisie", + "TR" : "Turquie", + "TM" : "Turkménistan", + "TC" : "Îles Turks et Caicos", + "TV" : "Tuvalu", + "UG" : "Ouganda", + "UA" : "Ukraine", + "AE" : "Émirats Arabes Unis", + "UK" : "Royaume-Uni", + "UY" : "Uruguay", + "UM" : "Îles Mineures Américaines ", + "SU" : "Ex-URSS", + "UZ" : "Ouzbékistan", + "VU" : "Vanuatu", + "VA" : "État de la Cité du Vatican (Saint-Siège)", + "VE" : "Venezuela", + "VN" : "Vietnam", + "VG" : "Îles Vierges (Britanniques)", + "VI" : "Îles Vierges (Américaines)", + "WF" : "Îles Wallis et Futuna", + "EH" : "Sahara Occidental", + "YE" : "Yémen", + "YU" : "Yougoslavie", + "ZR" : "Zaïre", + "ZM" : "Zambie", + "ZW" : "Zimbabwe", + } +BillingScreenStateNames = { + "AL" : "Alabama", + "AK" : "Alaska", + "AR" : "Arkansas", + "AZ" : "Arizona", + "CA" : "Californie", + "CO" : "Colorado", + "CT" : "Connecticut", + "DE" : "Delaware", + "FL" : "Floride", + "GA" : "Géorgie", + "HI" : "Hawaï", + "IA" : "Iowa", + "ID" : "Idaho", + "IL" : "Illinois", + "IN" : "Indiana", + "KS" : "Kansas", + "KY" : "Kentucky", + "LA" : "Louisiane", + "MA" : "Massachusetts", + "MD" : "Maryland", + "ME" : "Maine", + "MI" : "Michigan", + "MN" : "Minnesota", + "MO" : "Missouri", + "MS" : "Mississippi", + "MT" : "Montana", + "NE" : "Nebraska", + "NC" : "Caroline du Nord", + "ND" : "Dakota du Nord", + "NH" : "New Hampshire", + "NJ" : "New Jersey", + "NM" : "Nouveau-Mexique", + "NV" : "Nevada", + "NY" : "New York", + "OH" : "Ohio", + "OK" : "Oklahoma", + "OU" : "Oregon", + "PA" : "Pennsylvanie", + "RI" : "Rhode Island", + "SC" : "Caroline du Sud", + "SD" : "Dakota du Sud", + "TN" : "Tennessee", + "TX" : "Texas", + "UT" : "Utah", + "VA" : "Virginie", + "VT" : "Vermont", + "WA" : "Washington", + "WI" : "Wisconsin", + "WV" : "Virginie-Occidentale", + "WY" : "Wyoming", + "DC" : "District fédéral de Columbia", + "AS" : "Samoa Américaines", + "GU" : "Guam", + "MP" : "Îles Mariannes du Nord", + "PR" : "Puerto Rico", + "VI" : "Îles Vierges", + "FPO" : ["Îles Midway", + "Kingman Reef", + ], + "APO" : ["Île de Wake", + "Île Johnston", + ], + "MH" : "Îles Marshall", + "PW" : "Palaos", + "FM" : "Micronésie", + } +BillingScreenCanadianProvinces = { + 'AB' : 'Alberta', + 'BC' : 'Colombie-Britannique', + 'MB' : 'Manitoba', + 'NB' : 'Nouveau-Brunswick', + 'NF' : 'Terre-Neuve', + 'NT' : 'Territoires du Nord-Ouest', + 'NS' : 'Nouvelle-Écosse', + #'XX' : 'Nunavut', + 'ON' : 'Ontario', + 'PE' : 'Île du Prince-Édouard', + 'QC' : 'Québec', + 'SK' : 'Saskatchewan', + 'YT' : 'Yukon', + } + +ParentPassword = "Mot de passe parent" + +# WelcomeScreen.py +WelcomeScreenHeading = "Bienvenue!" +WelcomeScreenOk = "JOUONS!" +WelcomeScreenSentence1 = "Vous êtes maintenant officiellement membre de" +WelcomeScreenToontown = "Toontown en ligne de Disney" +WelcomeScreenSentence2 = "N'oubliez pas de vérifier votre courrier électronique régulièrement pour découvrir des nouvelles palpitantes au sujet de Toontown en ligne de Disney!" + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Appelez le Service clients au %s. " +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nSi vous avez besoin d'aide, vous pouvez appeler le service clients au %s." +TTAccountIntractibleError = "Une erreur s'est produite." + +# LoginScreen.py +LoginScreenUserName = "Nom du compte" +LoginScreenPassword = "Mot de passe" +LoginScreenLogin = "Ouvrir une session" +LoginScreenCreateAccount = "Créer un compte" +LoginScreenForgotPassword = "Mot de passe oublié?" +LoginScreenQuit = lQuit +LoginScreenLoginPrompt = "Entrez un nom d'utilisateur et un mot de passe." +LoginScreenBadPassword = "Mot de passe erroné.\nRessayez." +LoginScreenInvalidUserName = "Nom d'utilisateur incorrect.\nRessayez." +LoginScreenUserNameNotFound = "Utilisateur introuvable.\nRessayez ou créez un nouveau compte." +LoginScreenPeriodTimeExpired = "Désolé, vous avez déjà utilisé toutes vos minutes disponibles dans Toontown pour ce mois-ci. Revenez au début du mois prochain." +LoginScreenNoNewAccounts = "Nous sommes désolé, nous n'acceptons pas de nouveaux comptes pour le moment." +LoginScreenTryAgain = "Ressayez" + +# NewPlayerScreen.py +NewPlayerScreenNewAccount = "Commencer l'essai gratuit" +NewPlayerScreenLogin = "Membre existant" +NewPlayerScreenQuit = lQuit + +# FreeTimeInformScreen.py +FreeTimeInformScreenDontForget = "N'oubliez pas que votre essai gratuit\nse termine dans " +FreeTimeInformScreenNDaysLeft = FreeTimeInformScreenDontForget + "seulement %s jours!" +FreeTimeInformScreenOneDayLeft = FreeTimeInformScreenDontForget + "1 jour!" +FreeTimeInformScreenNHoursLeft = FreeTimeInformScreenDontForget + "seulement %s heures!" +FreeTimeInformScreenOneHourLeft = FreeTimeInformScreenDontForget + "1 heure!" +FreeTimeInformScreenLessThanOneHourLeft = FreeTimeInformScreenDontForget + "moins d'une heure!" +FreeTimeInformScreenSecondSentence = "Mais il est encore temps de devenir \nofficiellement membre de Toontown en ligne de Disney!" +FreeTimeInformScreenOops = "OH LÀ LÀ" +FreeTimeInformScreenExpired = " , votre essai gratuit est maintenant terminé!\nVous voulez devenir officiellement membre de Toontown en ligne de Disney?\nInscrivez-vous maintenant et revenez vous amuser!" +FreeTimeInformScreenExpiredQuitText = "Ce n'est pas possible maintenant? Ne vous inquiétez pas, nous \ngardons votre Toon! Mais revenez vite! Nous \ngardons votre Toon une seule semaine à \nl'issue de votre essai gratuit." +FreeTimeInformScreenExpiredCCUF = "Vous n'avez pas encore acheté Toontown\n de Disney en ligne. Pour utiliser ce compte, \nvous devez vous inscrire avec une carte de crédit.\nInscrivez-vous maintenant et venez vous amuser!" +FreeTimeInformScreenExpiredQuitCCUFText = "Ce n'est pas possible maintenant? Ne vous inquiétez pas, nous \nnous gardons votre compte! Mais revenez vite! Nous \ngardons votre compte une seule semaine." +FreeTimeInformScreenPurchase = "Inscrivez-vous!" +FreeTimeInformScreenFreePlay = "Continuer l'essai gratuit" +FreeTimeInformScreenQuit = lQuit + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', + 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc',] +DateOfBirthEntryDefaultLabel = "Date de naissance" + +# CreateAccountScreen.py +CreateAccountScreenUserName = "Nom du compte" +CreateAccountScreenPassword = "Mot de passe" +CreateAccountScreenConfirmPassword = "Confirmation du mot de passe" +CreateAccountScreenFree = "GRATUIT" +CreateAccountScreenFreeTrialLength = "Pour commencer votre essai de %s jours,n\vous devez créer un compte." +CreateAccountScreenInstructionsUsername = "Indiquez le nom de compte que vous voulez utiliser :" +CreateAccountScreenInstructionsPassword = "Entrez un mot de passe :" +CreateAccountScreenInstructionsConfirmPassword = "Confirmez votre mot de passe pour plus de sûreté :" +CreateAccountScreenInstructionsDob = "Entrez votre date de naissance :" +CreateAccountScreenCancel = lCancel +CreateAccountScreenSubmit = lNext +CreateAccountScreenConnectionErrorSuffix = ".\n\nRessayez plus tard." +CreateAccountScreenNoAccountName = "Choisissez un nom de compte." +CreateAccountScreenAccountNameTooShort = "Le nom de votre compte doit comporter au moins %s caractères. Ressayez." +CreateAccountScreenPasswordTooShort = "Votre mot de passe doit comporter au moins %s caractères. Ressayez." +CreateAccountScreenPasswordMismatch = "Les mots de passe que vous avez entrés ne sont pas identiques. Ressayez." +CreateAccountScreenInvalidDob = "Entrez votre date de naissance." +CreateAccountScreenUserNameTaken = "Ce nom d'utilisateur est déjà pris. Ressayez." +CreateAccountScreenInvalidUserName = "Nom d'utilisateur incorrect.\nRessayez." +CreateAccountScreenUserNameNotFound = "Nom d'utilisateur introuvable.\nRessayez ou créez un nouveau compte." +CreateAccountScreenEmailInstructions = "Entrez votre adresse électronique.\nPourquoi? Pour deux raisons :\n1. Si vous avez oublié votre mot de passe, nous pouvons vous l'envoyer!\n2. Nous pourrons vous envoyer les dernières nouvelles de\nToontown en ligne de Disney." +CreateAccountScreenEmailInstructionsUnder13 = "Tu as indiqué que tu as moins de 13 ans.\nPour créer un compte, nous avons besoin de l'adresse électronique d'un de tes parents ou tuteurs." +CreateAccountScreenEmailConfirm = "Confirme l'adresse électronique pour plus de sûreté :" +CreateAccountScreenEmailPanelSubmit = lNext +CreateAccountScreenEmailPanelCancel = lCancel +CreateAccountScreenInvalidEmail = "Entre l'adresse électronique complète." +CreateAccountScreenEmailMismatch = "Les adresses électroniques que tu as entrées ne correspondent pas. Essaie encore." + +# SecretFriendsInfoPanel.py +SecretFriendsInfoPanelOk = lOK +SecretFriendsInfoPanelText = [""" +La fonction " Amis secrets " + +La fonction " Amis secrets " permet à un membre de s'adresser directement à un autre membre sur Toontown en ligne de Disney (le " Service "), une fois que ces membres ont établi une connexion " Amis secrets ". Si votre enfant veut utiliser la fonction " Amis secrets ", nous vous demandons de nous indiquer que vous l'autorisez à utiliser cette fonction en entrant votre Mot de Passe Parent. Voici une description détaillée de la procédure permettant de créer une connexion " Amis secrets " entre deux membres, que nous avons appelés "Sally" et "Mike." +1. Les parents de Sally et de Mike activent chacun de leur côté la fonction " Amis secrets " en entrant leurs mots de passe parent respectifs, soit (a) dans les " Options de compte " du service, soit (b) lorsque celui-ci leur est demandé par une fenêtre contextuelle de contrôle parental. +2. Sally demande un secret (décrit ci-dessous) depuis le service. +""",""" +3. Le secret de Sally est communiqué à Mike en dehors du service. (Il peut lui être communiqué soit directement par Sally, soit indirectement lorsque Sally révèle son secret à une autre personne.) +4. Mike soumet le secret de Sally au service dans les 48 heures après que Sally a fait la demande du secret. +5. Le service avertit alors Mike que Sally est devenue son amie secrète. Le service avertit également Sally que Mike est devenu son ami secret. +6. Sally et Mike peuvent alors discuter directement entre eux jusqu'à ce que l'un des deux choisisse de mettre fin à leur amitié secrète, ou que l'un des parents respectifs de Sally ou Mike mette fin à la fonction " Ami secret ". Le lien " Ami secret " peut donc être désactivé à tout moment par soit : (a) un membre désactivant son ou ses amis secrets de sa liste d'amis (comme il est décrit dans le service) ; soit, (b) le parent de ce membre désactivant la fonction " Ami secret " en allant dans la zone " Options de compte " du service et en suivant les étapes détaillées ici. +""",""" +Un secret est un code aléatoire généré par ordinateur assigné à un membre particulier. Le secret doit être utilisé pour activer un lien d'ami secret dans les 48 heures à partir du moment où le membre demande le secret ; faute de quoi le secret arrive à expiration et ne peut plus être utilisé. De plus, un secret ne peut être utilisé que pour établir un seul lien d'ami secret. Pour créer d'autres liens d'amis secrets, le membre doit demander un nouveau secret pour chaque ami secret supplémentaire. + +L'amitié secrète n'est pas transférable. Par exemple, si Sally devient amie secrète de Mike, et que Mike devient ami secret de Jessica, Sally ne devient pas automatiquement amie secrète de Jessica. Pour que Sally et Jessica deviennent amies secrètes, l'une d'entre elles doit demander un nouveau secret au service et le communiquer à l'autre. +""",""" +Les amis secrets communiquent entre eux par un chat interactif de forme libre. Le contenu de ce chat est rédigé directement par le membre participant et est traité par le service, qui est exploité par Walt Disney Internet Group ("WDIG"), 506 2nd Avenue, Suite 2100, Seattle, WA 98104 (téléphone 00 1 (509) 742-4698 ; courrier électronique ms_support@help.go.com). Nous conseillons à nos membres de ne pas échanger d'informations personnelles telles que leurs noms, prénoms, adresses électroniques, adresses postales ou numéros de téléphone lors de l'utilisation de la fonction " Amis secrets ". Cependant, nous ne pouvons pas garantir que ce type d'échange d'informations personnelles ne se produira pas. Bien que le chat " Amis secrets " fasse l'objet d'un filtrage automatique pour la plupart des mots grossiers, il n'est ni modéré ni supervisé par nous. Si les parents autorisent leurs enfants à utiliser leur compte avec la fonction " Amis secrets " activée, nous les encourageons à surveiller leurs enfants lorsqu'ils jouent dans le cadre de nos services. +""",""" +WDIG n'utilise le contenu du chat " Amis secrets " pour aucun autre usage que la communication de ce contenu à l'ami secret du membre, et ne révèle ce contenu à aucun tiers excepté : (1) si cela est légalement nécessaire, par exemple pour exécuter une ordonnance de tribunal ou une assignation ; (2) pour faire respecter les Conditions d'utilisation applicables au service (consultables sur la page d'accueil) ; ou (3) pour protéger la sûreté et la sécurité des membres du service et le service lui-même. Sur demande adressée à WDIG, le parent d'un enfant peut consulter et faire supprimer tout contenu de chat d'ami secret produit par cet enfant, dans la mesure où ledit contenu n'a pas déjà été supprimé des fichiers de WDIG. En vertu de la loi américaine sur la protection de la confidentialité des enfants (Children's Online Privacy Protection Act), il nous est interdit de subordonner, et nous ne subordonnons pas, la participation d'un enfant à quelque activité que ce soit (y compris les amis secrets) à la communication par l'enfant de plus d'informations qu'il n'est raisonnablement nécessaire à la participation à une telle activité. +""",""" +De plus, comme il est indiqué ci-dessus, nous reconnaissons le droit d'un parent de nous refuser l'autorisation de permettre à un enfant l'utilisation de la fonction " Amis secrets ". En activant la fonction " Amis secrets ", vous reconnaissez qu'il existe des risques inhérents à la capacité des membres à discuter les uns avec les autres par la fonction " Amis secrets ", que vous avez été informés de ceux-ci, et voulez bien accepter lesdits risques. +""" +] + +# ParentPasswordScreen.py +ParentPasswordScreenTitle = "Contrôle parental" +ParentPasswordScreenPassword = "Créer un mot de passe \" parent \"" +ParentPasswordScreenConfirmPassword = "Confirmer le mot de passe \" parent \"" +ParentPasswordScreenSubmit = "Établir le mot de passe \" parent \"" +ParentPasswordScreenConnectionErrorSuffix = ".\nRessayez plus tard." +ParentPasswordScreenPasswordTooShort = "Votre mot de passe doit comporter au moins %s caractères. Ressayez." +ParentPasswordScreenPasswordMismatch = "Les mots de passe que vous avez entrés ne sont pas identiques. Ressayez." +ParentPasswordScreenConnectionProblemJustPaid = "Un problème est survenu lors du contact avec le serveur des comptes, mais ne vous inquiétez pas ; votre achat est bien pris en compte.\n\nIl vous sera de nouveau demandé d'établir votre mot de passe \" parent \" la prochaine fois que vous vous connectez." +ParentPasswordScreenConnectionProblemJustLoggedIn = "Un problème est survenu lors du contact avec le serveur des comptes. Ressayez ultérieurement." +ParentPasswordScreenSecretFriendsMoreInfo = "Plus d'infos" +ParentPasswordScreenInstructions = """ Veuillez créer un \"mot de passe parent \" pour ce compte - le mot de passe parent vous sera demandé ultérieurement: + + 1. Quand nous vous demandons d'autoriser votre/vos enfant(s) à + utiliser certaines fonctions interactives de Toontown, + comme la fonction " Amis secrets ". Pour obtenir une + description complète de cette fonction et de la façon dont + elle permet à votre/vos enfant(s) de communiquer en ligne + avec d'autres membres de Toontown, cliquez sur le bouton + '"""+ParentPasswordScreenSecretFriendsMoreInfo+"""' ci-dessous. Votre autorisation + est nécessaire pour activer cette fonction. + + +2. Pour mettre à jour, sur la page Web de Toontown, + les informations concernant votre compte et la facturation. +""" +ParentPasswordScreenAdvice = "Attention de ne pas divulguer ce Mot de Passe Parent. Il est capital que vous protégiez la confidentialité de votre Mot de Passe Parent si vous voulez contrôler l'utilisation par votre/vos enfants des fonctions interactives de votre compte." +ParentPasswordScreenPrivacyPolicy = "Respect de la vie privée" + + +# ForgotPasswordScreen.py +ForgotPasswordScreenTitle = "Si vous avez oublié votre mot de passe, nous pouvons vous l'envoyer!" +ForgotPasswordScreenInstructions = "Entrez votre nom de compte OU l'adresse électronique que vous nous avez fournie." +ForgotPasswordScreenEmailEntryLabel = "Adresse électronique" +ForgotPasswordScreenOr = "OU" +ForgotPasswordScreenAcctNameEntryLabel = "Nom du compte" +ForgotPasswordScreenSubmit = "Envoyer" +ForgotPasswordScreenCancel = lCancel +ForgotPasswordScreenEmailSuccess = "Votre mot de passe a été envoyé à '%s'." +ForgotPasswordScreenEmailFailure = "Adresse électronique introuvable : '%s'." +ForgotPasswordScreenAccountNameSuccess = "Votre mot de passe a été envoyé à l'adresse électronique que vous avez fournie lorsque vous avez créé votre compte." +ForgotPasswordScreenAccountNameFailure = "Compte introuvable : %s" +ForgotPasswordScreenNoEmailAddress = "Ce compte a été créé par une personne âgée de moins de 13 ans, et n'a pas d'adresse électronique. Nous ne pouvons pas vous envoyer votre mot de passe.\n\nVous pouvez créer un autre compte!" +ForgotPasswordScreenInvalidEmail = "Entrez une adresse électronique valide." + +# GuiScreen.py +GuiScreenToontownUnavailable = "Toontown semble momentanément indisponible, nouvelle tentative..." +GuiScreenCancel = lCancel + +# AchievePage.py +AchievePageTitle = "Réussites\n (Bientôt disponible)" + +# PhotoPage.py +PhotoPageTitle = "Photo\n (Bientôt disponible)" + +# BuildingPage.py +BuildingPageTitle = "Bâtiments\n (Bientôt disponible)" + +# InventoryPage.py +InventoryPageTitle = "Gags" +InventoryPageDeleteTitle = "SUPPRIMER LES GAGS" +InventoryPageTrackFull = "Tu as tous les gags de la série %s. " +InventoryPagePluralPoints = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageSinglePoint = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageNoAccess = "Tu n'as pas encore accès à la série %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS Toons" + +# MapPage.py +MapPageTitle = "Carte" +MapPageBackToPlayground = "au terrain de jeux" +MapPageBackToCogHQ = "Retour au QG des Cogs" +MapPageGoHome = "à la maison" +# hood name, street name +MapPageYouAreHere = "Tu es à : %s\n%s" +MapPageYouAreAtHome = "Tu es dans\nta propriété." +MapPageYouAreAtSomeonesHome = "Tu es chez %s." +MapPageGoTo = "Aller chez\n%s." + +# OptionsPage.py +OptionsPageTitle = "Options" +OptionsPagePurchase = "S'inscrire!" +OptionsPageLogout = "Se déconnecter" +OptionsPageExitToontown = "Quitter Toontown" +OptionsPageMusicOnLabel = "Musique activée." +OptionsPageMusicOffLabel = "Musique désactivée." +OptionsPageSFXOnLabel = "Effets sonores activés." +OptionsPageSFXOffLabel = "Effets sonores désactivés." +OptionsPageFriendsEnabledLabel = "Demandes de nouveaux amis acceptées." +OptionsPageFriendsDisabledLabel = "Demandes de nouveaux amis non acceptées." +OptionsPageSpeedChatStyleLabel = "Couleur du Chat rapide" +OptionsPageDisplayWindowed = "dans une fenêtre" +OptionsPageSelect = "Choisir" +OptionsPageToggleOn = "Activer" +OptionsPageToggleOff = "Désactiver" +OptionsPageChange = "Modifier" +OptionsPageDisplaySettings = "Affichage : %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Affichage : %(screensize)s" +OptionsPageExitConfirm = "Quitter Toontown?" + +DisplaySettingsTitle = "Réglages d'affichage" +DisplaySettingsIntro = "Les réglages suivants sont utilisés pour configurer l'affichage de Toontown sur votre ordinateur. Il n'est sans doute pas indispensable de les modifier sauf si vous avez un problème." +DisplaySettingsIntroSimple = "Vous pouvez accroître la résolution d'écran pour améliorer la lisibilité du texte et des graphiques de Toontown, mais en fonction de votre carte graphique, certaines valeurs plus élevées risquent d'affecter le bon fonctionnement du jeu, voire de l'empêcher complètement de fonctionner." + +DisplaySettingsApi = "Interface graphique :" +DisplaySettingsResolution = "Résolution :" +DisplaySettingsWindowed = "Dans une fenêtre" +DisplaySettingsFullscreen = "Plein écran" +DisplaySettingsApply = "Appliquer" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Lorsque vous cliquez sur OK, les réglages d'affichage sont modifiés. Si la nouvelle configuration ne s'affiche pas correctement sur votre ordinateur, l'affichage revient automatiquement à sa configuration d'origine après %s secondes." +DisplaySettingsAccept = "Cliquez sur OK pour conserver les nouveaux réglages ou sur Annuler pour revenir aux valeurs précédentes. Si vous ne cliquez sur rien, les réglages reviennent automatiquement aux valeurs précédentes après %s secondes." +DisplaySettingsRevertUser = "Vos précédents réglages d'affichage ont été restaurés." +DisplaySettingsRevertFailed = "Les réglages d'affichage sélectionnés ne peuvent pas fonctionner sur votre ordinateur. Vos précédents réglages d'affichage ont été restaurés." + + +# TrackPage.py +TrackPageTitle = "Entraînement à une série de gags" +TrackPageShortTitle = "Entraînement aux gags" +TrackPageSubtitle = "Termine des défitoons pour apprendre à utiliser de nouveaux gags!" +TrackPageTraining = "Tu t'entraînes pour utiliser les gags %s. \nLorsque tu auras terminé les 16 défis, tu\npourras utiliser les gags %s lors des combats." +TrackPageClear = "Tu ne t'entraînes pour aucune série de gags actuellement." +TrackPageFilmTitle = "Entraînement\naux gags %s\n." +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "Défitoons" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nTo: %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nTo choose, go see:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Choisis" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Une rue" +QuestPosterHQLocationName = "Un quartier" + +QuestPosterTailor = "Tailleur" +QuestPosterTailorBuildingName = "Boutique de prêt-à-porter" +QuestPosterTailorStreetName = "Un terrain de jeux" +QuestPosterTailorLocationName = "Un quartier" +QuestPosterPlayground = "Sur le terrain de jeux " +QuestPosterAnywhere = "N'importe où" +QuestPosterAuxTo = "à :" +QuestPosterAuxFrom = "depuis :" +QuestPosterAuxFor = "pour :" +QuestPosterAuxOr = "ou :" +QuestPosterAuxReturnTo = "Retourner à :" +QuestPosterLocationIn = "" +QuestPosterLocationOn = "" +QuestPosterFun = "Juste pour s'amuser!" +QuestPosterFishing = "ALLER PÊCHER" +QuestPosterComplete = "TERMINÉ" + +# ShardPage.py +ShardPageTitle = "Districts" +ShardPageHelpIntro = "Chaque district est une copie du monde de Toontown." +ShardPageHelpWhere = "Tu es actuellement dans le district de \"%s\"." +ShardPageHelpWelcomeValley = " Tu es actuellement dans le district de la \" Vallée de la Bienvenue \", dans \"%s\"." +ShardPageHelpMove = "Pour aller dans un nouveau district, clique sur son nom." + +ShardPagePopulationTotal = "Population totale de Toontown :\n%d" +ShardPageScrollTitle = "Nom Population" + +# SuitPage.py +SuitPageTitle = "Galerie des Cogs" +SuitPageMystery = DialogQuestion + DialogQuestion + DialogQuestion +SuitPageQuota = "%s sur %s" +SuitPageCogRadar = "%s présents" +SuitPageBuildingRadarS = "Bâtiment %s" +SuitPageBuildingRadarP = "Bâtiments %s" + +# DisguisePage.py +DisguisePageTitle = "Déguisement de " + Cog + " Disguise" +DisguisePageMeritAlert = "Prêt pour la\npromotion!" +DisguisePageCogLevel = "Niveau %s" +DisguisePageMeritFull = "Plein" +# 1_1_8_branch +DisguisePageMeritBar = "Merit Progress" +DisguisePageCogPartRatio = "%d/%d" + +# FishPage.py +FishPageTitle = "Pêche" +FishPageTitleTank = "Seau de pêche" +FishPageTitleCollection = "Album de pêche" +FishPageTitleTrophy = "Trophées de pêche" +FishPageWeightStr = "Poids : " +FishPageWeightLargeS = "%d kg " +FishPageWeightLargeP = "%d kg " +FishPageWeightSmallS = "%d g" +FishPageWeightSmallP = "%d g" +FishPageWeightConversion = 16 +FishPageValueS = "Valeur: %d bonbon" +FishPageValueP = "Valeur: %d bonbons" +FishPageTotalValue = "" +FishPageCollectedTotal = "Espèces de poissons pêchées: %d sur %d" +FishPageRodInfo = "Canne %s \n%d - %d livres" +FishPageTankTab = "Seau" +FishPageCollectionTab = "Album" +FishPageTrophyTab = "Trophées" + +FishPickerTotalValue = "Seau : %s / %s\nValeur: %d bonbons" + +UnknownFish = "???" + +FishingRod = "Canne %s" +FishingRodNameDict = { + 0 : "Brindille", + 1 : "Bambou", + 2 : "Bois dur", + 3 : "Acier", + 4 : "Or", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Vairon", + 2 : "Poisson", + 3 : "Poisson volant", + 4 : "Requin", + } + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Choisir" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = "Toonique te permet de soigner les autres Toons lors d'une bataille." +TrackChoiceGuiTRAP = "Les pièges sont des gags puissants qui doivent être utilisés avec les leurres." +TrackChoiceGuiLURE = "Utilise les leurres pour assommer les Cogs ou les attirer dans des pièges." +TrackChoiceGuiSOUND = "Les gags de tapage affectent tous les Cogs mais ne sont pas très puissants." +TrackChoiceGuiDROP = "Les gags de chute font beaucoup de dégâts mais ne sont pas très précis." + +# EmotePage.py +EmotePageTitle = "Expressions / Émotions" +EmotePageDance = "Tu as construit la séquence de danse suivante :" +EmoteJump = "Saut" +EmoteDance = "Danse" +EmoteHappy = "Content(e)" +EmoteSad = "Triste" +EmoteAnnoyed = "Agacement" +EmoteSleep = "Sommeil" + +# Emote.py +# List of emotes in the order they should appear in the SpeedChat. +# Must be in the same order as the function list (EmoteFunc) in Emote.py +EmoteList = [ + "Signe de la main", + "Content(e)", + "Triste", + "En colère", + "Sommeil", + "Haussement d'épaules", + "Danse", + "Clin d'œil", + "Ennuyé(e)", + "Applaudissements", + "Surpris(e)", + "Désorienté(e)", + "Moquerie", + "Révérence", + "Vraiment triste", + "Grand sourire", + "Rire", + lYes, + lNo, + lOK, + ] + +EmoteWhispers = [ + "%s fait un signe de la main.", + "%s est content(e).", + "%s est triste.", + "%s est en colère.", + "%s a sommeil.", + "%s hausse les épaules.", + "%s danse.", + "%s fait un clin d'œil.", + "%s s'ennuie.", + "%s applaudit.", + "%s est surpris(e).", + "%s est désorienté(e).", + "%s se moque de toi.", + "%s te fait une révérence.", + "%s est vraiment triste.", + "%s sourit.", + "%s rit.", + "%s dit \" "+lYes+" \".", + "%s dit \" "+lNo+" \".", + "%s dit \" "+lOK+" \".", + ] + +# Reverse lookup: get the index from the name. +EmoteFuncDict = { + "Signe de la main" : 0, + "Content(e)" : 1, + "Triste" : 2, + "En colère" : 3, + "Sommeil" : 4, + "Haussement d'épaules" : 5, + "Danse" : 6, + "Clin d'œil" : 7, + "Ennuyé(e)" : 8, + "Applaudissements" : 9, + "Surpris(e)" : 10, + "Désorienté(e)" : 11, + "Moquerie" : 12, + "Révérence" : 13, + "Vraiment triste" : 14, + "Grand sourire" : 15, + "Rire" : 16, + lYes : 17, + lNo : 18, + lOK : 19, + } + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNiveau %(level)s" + +# SuitDialog.py +SuitBrushOffs = { + 'f': ["Je suis en retard à un rendez-vous.", + ], + 'p': ["Dégage.", + ], + 'ym': ['Béniouioui dit NON.', + ], + None: ["C'est mon jour de congé.", + "Je crois que tu es dans le mauvais bureau.", + "Dis à tes collaborateurs d'appeler les miens.", + "Tu n'es pas en situation de me rencontrer.", + "Parles-en à mon assistant."] + } + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Tu ne peux pas quitter le terrain de jeux tant que ton rigolmètre ne sourit pas!" + +# InventoryNew.py +InventoryTotalGags = "Total des gags\n%d / %d" +InventoryDelete = "SUPPRIMER" +InventoryDone = "TERMINÉ" +InventoryDeleteHelp = "Clique sur un gag pour le SUPPRIMER." +InventorySkillCredit = "Crédit d'habileté : %s" +InventorySkillCreditNone = "Crédit d'habileté : Aucun" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Précision : %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryAffectsOneCog = "Affecte : Un "+ Cog +InventoryAffectsOneToon = "Affecte : Un Toon" +InventoryAffectsAllToons = "Affecte : Tous les Toons" +InventoryAffectsAllCogs = "Affecte : Tous les "+ Cogs +InventoryHealString = "Toonique" +InventoryDamageString = "Dommages" +InventoryBattleMenu = "MENU DU COMBAT" +InventoryRun = "COURIR" +InventorySOS = "SOS" +InventoryPass = "PASSER" +InventoryClickToAttack = "Clique sur\nun gag pour\nattaquer." + +# NPCForceAcknowledge.py +#NPCForceAcknowledgeMessage = "Rend visite à " + Flippy + " pour obtenir ton premier DéfiToon avant de partir.\n\n\n\nTu peux trouver\n" + Flippy + " à l'intérieur de\nla Mairie de ToonTown." +NPCForceAcknowledgeMessage = "Tu dois faire un tour de tramway avant de partir.\n\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage2 = "Bien, tu as terminé ta recherche dans le tramway!\nVa voir le quartier général des Toons pour recevoir ta récompense.\n\n\n\n\n\nLe quartier général des Toons est situé près du centre du terrain de jeux." +NPCForceAcknowledgeMessage3 = "N'oublie pas de faire un tour de tramway.\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage4 = "Bravo! Tu as terminé ton premier défitoon!\n\n\n\n\nVa voir le quartier général des Toons pour recevoir ta récompense." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Tu as reçu 1 point de lancer! Quand tu en auras 10, tu pourras recevoir un nouveau gag!" +MovieTutorialReward2 = "Tu as reçu 1 point d'éclaboussure! Quand tu en auras 10, tu pourras avoir un nouveau gag!" +MovieTutorialReward3 = "Bon travail! Tu as terminé ton premier défitoon!" +MovieTutorialReward4 = "Va chercher ta récompense au quartier général des Toons!" +MovieTutorialReward5 = "Amuse-toi!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toonique', 'piège', 'leurre', 'tapage', 'lancer', 'éclaboussure', 'chute'] +BattleGlobalNPCTracks = ['rechargement', 'Toons marquent', 'Cogs ratent'] +BattleGlobalAvPropStrings = ( + ('Plume', 'Mégaphone', 'Tube de rouge à lèvres', 'Canne en bambou', 'Poussière de fée', 'Balles de jonglage'), + ('Peau de banane', 'Râteau', 'Billes', 'Sable mouvant', 'Trappe', 'TNT'), + ('Billet de 1 dollar', 'Petit aimant', 'Billet de 5 dollars', 'Gros aimant', 'Billet de 10 dollars', 'Lunettes hypnotiques'), + ('Sonnette de vélo', 'Sifflet', 'Clairon', 'Klaxon', "Trompe d'éléphant", 'Corne de brume'), + ('Petit gâteau', 'Tranche de tarte aux fruits', 'Tranche de tarte à la crème', 'Tarte aux fruits entière', 'Tarte à la crème entière', "Gâteau d'anniversaire"), + ('Fleur à éclabousser', "Verre d'eau", 'Pistolet à eau', "Bouteille d'eau gazeuse", "Lance d'incendie", "Nuage d'orage"), + ('Pot de fleurs', 'Sac de sable', 'Enclume', 'Gros poids', 'Coffre-fort', 'Piano à queue') + ) +BattleGlobalAvPropStringsSingular = ( + ('une plume', 'un mégaphone', 'un tube de rouge à lèvres', 'une canne en bambou', 'de la poussière de fée', 'un jeu de balles de jonglage'), + ('une peau de banane', 'un râteau', 'un jeu de billes', 'un peu de sable mouvant', 'une trappe', 'du TNT'), + ('un billet de 1 dollar', 'un petit aimant', 'un billet de 5 dollars', 'un gros aimant', 'un billet de 10 dollars', 'une paire de lunettes hypnotiques'), + ('une sonnette de vélo', 'un sifflet', 'un clairon', 'un klaxon', "une trompe d'éléphant", 'une corne de brume'), + ('un petit gâteau', 'une tranche de tarte aux fruits', 'une tranche de tarte à la crème', 'une tarte aux fruits entière', 'une tarte à la crème entière', "un gâteau d'anniversaire"), + ('une fleur à éclabousser', "un verre d'eau", 'un pistolet à eau', "une bouteille d'eau gazeuse", "une lance d'incendie", "un nuage d'orage"), + ('un pot de fleurs', 'un sac de sable', 'une enclume', 'un gros poids', 'un coffre-fort', 'un piano à queue') + ) +BattleGlobalAvPropStringsPlural = ( + ('Plumes', 'Mégaphones', 'Tubes de rouge à lèvres', 'Cannes en bambou', 'Poussières de fée', 'jeux de balles de jonglage'), + ('Peaux de bananes', 'Râteaux', 'jeux de billes', 'morceaux de sable mouvant', 'Trappes','bâtons de TNT'), + ('Billets de 1 dollar', 'Petits aimants', 'Billets de 5 dollars', 'Gros aimants','Billets de 10 dollars', 'Paires de lunettes hypnotiques'), + ('Sonnettes de vélo', 'Sifflets', 'Clairons', 'Klaxons', "Trompes d'éléphants", 'Cornes de brume'), + ('Petits gâteaux', 'Tranches de tarte aux fruits', 'Tranches de tarte à la crème','Tartes aux fruits entières', 'Tartes à la crème entières', "Gâteaux d'anniversaire"), + ('Fleurs à éclabousser', "Verres d'eau", 'Pistolets à eau',"Bouteilles d'eau gazeuse", "Lances d'incendie", "Nuages d'orage"), + ('Pots de fleurs', 'Sacs de sable', 'Enclumes', 'Gros poids', 'Coffres-forts','Pianos à queue') + ) +BattleGlobalAvTrackAccStrings = ("Moyen", "Parfait", "Faible", "Fort", "Moyen", "Fort", "Faible") + +AttackMissed = "RATÉ" + +NPCCallButtonLabel = 'APPEL' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("vers la", "sur la", "terrasse du Tourbillon"), # Tutorial + 1000 : ("vers le", "sur le", "Terrain de jeux"), + 1100 : ("vers le", "sur le", "Boulevard de la Bernache"), + 1200 : ("vers la", "sur la", "Rue des Récifs"), + 1300 : ("vers l'", "sur l'", "Allée des Marées"), + 2000 : ("vers le", "sur le", "Terrain de jeux"), + 2100 : ("vers la", "sur la", "Rue Béta"), + 2200 : ("vers l'", "sur l'", "Avenue des Fondus"), + 2300 : ("vers la", "sur la", "Place des Blagues"), + 3000 : ("vers le", "sur le", "Terrain de jeux"), + 3100 : ("vers le", "sur le", "Chemin du Marin"), + 3200 : ("vers la", "sur la", "Rue de la Neige fondue"), + 4000 : ("vers le", "sur le", "Terrain de jeux"), + 4100 : ("vers l'", "sur l'", "Avenue du Contralto "), + 4200 : ("vers le", "sur le", "Boulevard du Baryton "), + 4300 : ("vers la", "sur la", "Terrasse des Ténors"), + 5000 : ("vers le", "sur le", "Terrain de jeux"), + 5100 : ("vers la", "sur la", "Rue des Ormes"), + 5200 : ("vers la", "sur la", "Rue des Érables"), + 5300 : ("vers la", "sur la", "Rue du Chêne"), + 9000 : ("vers le", "sur le", "Terrain de jeux"), + 9100 : ("vers le", "sur le", "Boulevard de la Berceuse"), + 10000 : ("vers le", "au", "QG Chefbot"), + 10100 : ("vers le", "dans le", "hall du QG des Chefbots"), + 11000 : ("vers la", "sur la", "cour du QG Chefbot"), + 11100 : ("vers le", "dans le", "hall du QG Vendibot"), + 11200 : ("vers l'", "à l'", "usine Vendibot"), + 11500 : ("vers l'", "à l'", "usine Vendibot"), + 12000 : ("vers le", "au", "QG Caissbot"), + 12100 : ("vers le", "dans le", "hall du QG Caissbot"), + 13000 : ("vers le", "au", "QG Loibot"), + 13100 : ("vers le", "dans le", "hall du QG Loibot"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("vers les", "sur les", lDonaldsDock) +ToontownCentral = ("vers", "à", lToontownCentral) +TheBrrrgh = ("vers", "dans", "le %s" % lTheBrrrgh) +MinniesMelodyland = ("vers le", "au", lMinniesMelodyland) +DaisyGardens = ("vers les", "au", lDaisyGardens) +ConstructionZone = ("vers la", "dans la", "Zone de construction") +FunnyFarm = ("vers la", "dans la", "Ferme farfelue") +GoofyStadium = ("vers le", "au", "Stade Dingo") +DonaldsDreamland = ("vers le", "au", lDonaldsDreamland) +BossbotHQ = ("vers le", "dans le", "QG des Chefbots") +SellbotHQ = ("vers le", "dans le", "QG Vendibot") +CashbotHQ = ("vers le", "dans le", "QG Caissbot") +LawbotHQ = ("vers le", "dans le", "QG Loibot") +Tutorial = ("vers les", "aux", "Travaux pratiques") +MyEstate = ("vers", "dans", "ta maison") +WelcomeValley = ("vers la", "dans la", "Bienvenue") + +Factory = 'Usine' +Headquarters = 'Quartiers généraux' +SellbotFrontEntrance = 'Entrée principale' +SellbotSideEntrance = 'Entrée latérale' + +FactoryNames = { + 0 : "Maquette d'usine", + 11500 : 'Usine des Cogs Vendibots', + } + +FactoryTypeLeg = 'Jambe' +FactoryTypeArm = 'Bras' +FactoryTypeTorso = 'Torse' + +# ToontownLoader.py +LoaderLabel = "Chargement..." + +# PlayGame.py +HeadingToHood = "En direction %(to)s %(hood)s... " # hood name +HeadingToYourEstate = "En direction de ta propriété..." +HeadingToEstate = "En direction de la propriété de %s..." # avatar name +HeadingToFriend = "En direction de la propriété de l'ami(e) de %s..." # avatar name + +# Hood.py +HeadingToPlayground = "En direction du terrain de jeux..." +HeadingToStreet = "En direction %(to)s %(street)s... " # Street name + +# ToontownDialog.py +ToontownDialogOK = lOK +ToontownDialogCancel = lCancel +ToontownDialogYes = lYes +ToontownDialogNo = lNo + +# TownBattle.py +TownBattleRun = "Revenir en courant au terrain de jeux?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "QUEL TOON?" +TownBattleChooseAvatarCogTitle = "QUEL "+ string.upper(Cog) +"?" +TownBattleChooseAvatarBack = "RETOUR" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Pas d'amis à appeler!" +TownBattleSOSWhichFriend = "Appeler quel(le) ami(e)?" +TownBattleSOSNPCFriends = "Toons sauvés" +TownBattleSOSBack = "RETOUR" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "En attente des\nautres joueurs..." +TownSoloBattleWaitTitle = "Patiente..." +TownBattleWaitBack = "RETOUR" + +# Trolley.py +TrolleyHFAMessage = "Tu ne peux pas monter dans le tramway avant que ton rigolmètre ne sourie." +TrolleyTFAMessage = "Tu ne peux pas monter dans le tramway avant que " + Mickey + " ne te le dise." +TrolleyHopOff = lQuit + +# DistributedFishingSpot.py +FishingExit = "Sortie" +FishingCast = "Lancer" +FishingAutoReel = "Moulinet automatique" +FishingItemFound = "Tu as attrapé :" +FishingCrankTooSlow = "Trop\nlent" +FishingCrankTooFast = "Trop\nrapide" +FishingFailure = "Tu n'as rien attrapé!" +FishingFailureTooSoon = "Ne commence pas à faire remonter ta ligne avant de voir une touche. Attends que ton flotteur se mette à s'enfoncer et à remonter rapidement!" +FishingFailureTooLate = "Remonte bien ta ligne avant que le poisson ne se décroche!" +FishingFailureAutoReel = "Le moulinet automatique n'a pas fonctionné cette fois-ci. Tourne la manivelle à la main, juste à la bonne vitesse, pour avoir les meilleures chances d'attraper quelque chose!" +FishingFailureTooSlow = "Tu as tourné la manivelle trop lentement. Certains poissons sont plus rapides que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingFailureTooFast = "Tu as tourné la manivelle trop rapidement. Certains poissons sont plus lents que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingOverTankLimit = "Ton seau de pêche est plein. Va vendre tes poissons au vendeur de l'animalerie et reviens." +FishingBroke = "Tu n'as plus de bonbons pour appâter! Va faire un tour de tramway ou vends des poissons aux vendeurs de l'animalerie pour avoir d'autres bonbons." +FishingHowToFirstTime = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie maintenant!" +FishingHowToFailed = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie encore maintenant!" +FishingBootItem = "Une vieille chaussure" +FishingJellybeanItem = "%s bonbons" +FishingNewEntry = "Nouvelle espèce!" +FishingNewRecord = "Nouveau record!" + +# FishPoker +FishPokerCashIn = "Encaisser\n%s\n%s" +FishPokerLock = "Bloquer" +FishPokerUnlock = "Débloquer" +FishPoker5OfKind = "5 identiques" +FishPoker4OfKind = "Carré" +FishPokerFullHouse = "Full" +FishPoker3OfKind = "Brelan" +FishPoker2Pair = "2 paires" +FishPokerPair = "Paire" + +# DistributedTutorial.py +TutorialGreeting1 = "Salut, %s!" +TutorialGreeting2 = "Salut, %s!\nViens par ici!" +TutorialGreeting3 = "Salut, %s!\nViens par ici!\nUtilise les flèches!" +TutorialMickeyWelcome = "Bienvenue à Toontown!" +TutorialFlippyIntro = "Je te présente mon ami %s..." % Flippy +TutorialFlippyHi = "Salut, %s!" +TutorialQT1 = "Tu peux parler en utilisant ceci." +TutorialQT2 = "Tu peux parler en utilisant ceci.\nClique dessus, puis choisis \" Salut \"." +TutorialChat1 = "Tu peux parler en utilisant l'un de ces boutons." +TutorialChat2 = "Le bouton bleu te permet de chatter avec le clavier." +TutorialChat3 = "Fais attention! La plupart des autres joueurs ne comprendront pas ce que tu dis lorsque tu utilises le clavier." +TutorialChat4 = "Le bouton vert ouvre le %s." +TutorialChat5 = "Tout le monde peut te comprendre si tu utilises le %s." +TutorialChat6 = "Essaie de dire \" salut \"." +TutorialBodyClick1 = "Très bien!" +TutorialBodyClick2 = "Ravi de t'avoir rencontré! Tu veux que nous soyons amis?" +TutorialBodyClick3 = "Pour devenir ami(e) avec %s, clique sur lui..." % Flippy +TutorialHandleBodyClickSuccess = "Bon travail!" +TutorialHandleBodyClickFail = "Ce n'est pas ça. Essaie de cliquer juste sur " + Flippy + "..." +TutorialFriendsButton = "Maintenant, clique sur le bouton \" amis \" sous l'image de " + Flippy + " dans l'angle droit." +TutorialHandleFriendsButton = "Ensuite, clique sur le bouton \" oui \"." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Veux-tu devenir ami(e) avec " + Flippy + "?" +TutorialFriendsPanelMickeyChat = Flippy + " veut bien être ton ami. Clique sur \" "+lOK+" \" pour terminer." +TutorialFriendsPanelYes = Flippy + " a dit oui!" +TutorialFriendsPanelNo = "Ça n'est pas très gentil!" +TutorialFriendsPanelCongrats = "Bravo! Tu t'es fait ton premier ami." +TutorialFlippyChat1 = "Reviens me voir quand tu seras prêt pour ton premier défitoon!" +TutorialFlippyChat2 = "Je serai à la Mairie de Toontown!" +TutorialAllFriendsButton = "Tu peux voir tous tes amis en cliquant sur le bouton \" amis \". Essaye donc..." +TutorialEmptyFriendsList = "Pour l'instant, ta liste est vide parce que " + Flippy + " n'est pas véritablement un joueur." +TutorialCloseFriendsList = "Clique sur le bouton \" Fermer \"\npour faire disparaître la liste." +TutorialShtickerButton = "Le bouton dans l'angle inférieur droit ouvre ton journal de bord. Essaye-le..." +TutorialBook1 = "Le journal contient de nombreuses informations utiles comme cette carte de Toontown." +TutorialBook2 = "Tu peux aussi y voir les progrès de tes défitoons." +TutorialBook3 = "Lorsque tu as fini, clique de nouveau sur le bouton représentant un livre pour le fermer." +TutorialLaffMeter1 = "Tu as aussi besoin de ça..." +TutorialLaffMeter2 = "Tu as aussi besoin de ça...\nC'est ton rigolmètre." +TutorialLaffMeter3 = "Lorsque les " + Cogs + " t'attaquent, il baisse." +TutorialLaffMeter4 = "Lorsque tu es sur les terrains de jeux comme celui-ci, il remonte." +TutorialLaffMeter5 = "Lorsque tu finis des défitoons, tu reçois des récompenses, comme l'augmentation de ta rigo-limite." +TutorialLaffMeter6 = "Fais attention! Si les " + Cogs + " te battent, tu perds tous tes gags." +TutorialLaffMeter7 = "Pour avoir plus de gags, joue aux jeux du tramway." +TutorialTrolley1 = "Suis-moi jusqu'au tramway!" +TutorialTrolley2 = "Monte à bord!" +TutorialBye1 = "Joue à des jeux!" +TutorialBye2 = "Joue à des jeux!\nAchète des gags!" +TutorialBye3 = "Va voir " + Flippy + " quand tu auras fini!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "Tu vas dans le mauvais sens! Va trouver " + Mickey + "!" + +# SpeedChat + +# Used several places in the game. Defined globally because +# we keep changing the name +GlobalSpeedChatName = "Chat rapide" + +SCMenuEmotions = "ÉMOTIONS" +SCMenuCustom = "MES EXPRESSIONS" +SCMenuCog = "COGGERIES" +SCMenuHello = "SALUT" +SCMenuBye = "AU REVOIR" +SCMenuHappy = "JOYEUX" +SCMenuSad = "TRISTE" +SCMenuFriendly = "AMICAL" +SCMenuSorry = "DÉSOLÉ" +SCMenuStinky = "DÉSAGRÉABLE" +SCMenuPlaces = "ENDROITS" +SCMenuToontasks = "DÉFITOONS" +SCMenuBattle = "COMBAT" +SCMenuGagShop = "BOUTIQUE À GAGS" +SCMenuFactory = "USINE" +SCMenuFactoryMeet = "RENCONTRE" +SCMenuFriendlyYou = "Toi..." +SCMenuFriendlyILike = "J'aime bien..." +SCMenuPlacesLetsGo = "Allons..." +SCMenuToontasksMyTasks = "MES DÉFITOONS" +SCMenuToontasksYouShouldChoose = "Je crois que tu devrais choisir..." +SCMenuBattleLetsUse = "Utilisons..." + +# These are all the standard SpeedChat phrases. +# The indices must fit into 16 bits (0..65535) +SpeedChatStaticText = { + # top-level + 1 : lYes, + 2 : lNo, + 3 : lOK, + + # Hello + 100 : "Salut!", + 101 : "Bonjour!", + 102 : "Salut la compagnie!", + 103 : "Hé!", + 104 : "Coucou!", + 105 : "Salut tout le monde!", + 106 : "Bienvenue à Toontown!", + 107 : "Quoi de neuf?", + 108 : "Comment ça va?", + 109 : "Bonjour?", + + # Bye + 200 : "Au revoir!", + 201 : "À plus!", + 202 : "À la prochaine!", + 203 : "Bonne journée!", + 204 : "Amuse-toi!", + 205 : "Bonne chance!", + 206 : "Je reviens tout de suite.", + 207 : "Je dois m'en aller.", + + # Happy + 300 : ":-)", + 301 : "Haa!", + 302 : "Hourra!", + 303 : "Sympa!", + 304 : "Youhouu!", + 305 : "Ouais!", + 306 : "Ha ha!", + 307 : "Hi hi!", + 308 : "Oh là là!", + 309 : "Super!", + 310 : "Ouii!", + 311 : "Bon sang!", + 312 : "Youpi!", + 313 : "Génial!", + 314 : "Hop-là!", + 315 : "Toontastique!", + + # Sad + 400 : ":-(", + 401 : "Oh non!", + 402 : "Oh oh!", + 403 : "Zut!", + 404 : "Mince!", + 405 : "Aïe!", + 406 : "Ffff!", + 407 : "Non!!!", + 408 : "Aïe aïe aïe!", + 409 : "Eh?", + 410 : "J'ai besoin de plus de rigolpoints.", + + # Friendly + 500 : "Merci!", + 501 : "De rien.", + 502 : "Je t'en prie!", + 503 : "Quand tu veux!", + 504 : "Non merci.", + 505 : "Bon travail d'équipe!", + 506 : "C'était amusant!", + 507 : "Sois mon ami(e) s'il te plaît.", + 508 : "Travaillons ensemble!", + 509 : "Vous êtes super les copains!", + 510 : "Tu viens d'arriver par ici?", + 511 : "Tu as gagné?", + 512 : "Je crois que c'est trop risqué pour toi.", + 513 : "Tu veux de l'aide?", + 514 : "Peux-tu m'aider?", + + # Friendly "Toi..." + 600 : "Tu as un air gentil.", + 601 : "Tu es adorable!", + 602 : "Tu es d'enfer!", + 603 : "Tu es un génie!", + + # Friendly "J'aime bien..." + 700 : "J'aime bien ton nom.", + 701 : "J'aime bien ton look.", + 702 : "J'aime bien ta chemise.", + 703 : "J'aime bien ta jupe.", + 704 : "J'aime bien ton short.", + 705 : "J'aime bien ce jeu!", + + # Sorry + 800 : "Désolé(e)!", + 801 : "Aïe!", + 802 : "Désolé(e), je suis occupé à combattre les Cogs!", + 803 : "Désolé(e), je suis occupé à trouver des bonbons!", + 804 : "Désolé(e), je suis occupé à terminer un défitoon!", + 805 : "Désolé(e), j'ai dû partir à l'improviste.", + 806 : "Désolé(e), j'ai été retardé.", + 807 : "Désolé(e), je ne peux pas.", + 808 : "Je ne pouvais plus attendre.", + 809 : "Je ne te comprends pas.", + 810 : "Utilise le %s." % GlobalSpeedChatName, + + # Stinky + 900 : "Hé!", + 901 : "S'il te plaît va t'en!", + 902 : "Arrête ça!", + 903 : "Ce n'était pas gentil!", + 904 : "Ne sois pas méchant!", + 905 : "Tu es nul!", + 906 : "Envoie un rapport d'erreur.", + 907 : "Je suis en panne.", + + # Places + 1000 : "Allons-y!", + 1001 : "Peux-tu me téléporter?", + 1002 : "On y va?", + 1003 : "Où devons-nous aller?", + 1004 : "Par quel chemin?", + 1005 : "Par là.", + 1006 : "Suis-moi.", + 1007 : "Attends-moi!", + 1008 : "Attendons mon ami(e).", + 1009 : "Trouvons d'autres Toons.", + 1010 : "Attends ici.", + 1011 : "Attends une minute.", + 1012 : "Retrouvons-nous ici.", + 1013 : "Peux-tu venir chez moi?", + + # Places "Allons-y..." + 1100 : "Allons faire un tour de tramway!", + 1101 : "Retournons au terrain de jeux!", + 1102 : "Allons combattre les %s!" % Cogs, + 1103 : "Allons reprendre un bâtiment %s!" % Cog, + 1104 : "Allons dans l'ascenseur!", + 1105 : "Allons à "+lToontownCentral+"!", + 1106 : "Allons aux "+lDonaldsDockNC+"!", + 1107 : "Allons au "+lMinniesMelodyland+"!", + 1108 : "Allons au "+lDaisyGardensNC+"!", + 1109 : "Allons au "+lTheBrrrgh+"!", + 1110 : "Allons au "+lDonaldsDreamland+"!", + 1111 : "Allons chez moi!", + + # Toontasks + 1200 : "Sur quel défitoon est-ce que tu travailles?", + 1201 : "Travaillons là-dessus.", + 1202 : "Ce n'est pas ce que je cherche.", + 1203 : "Je vais chercher ça.", + 1204 : "Ce n'est pas dans cette rue.", + 1205 : "Je ne l'ai pas encore trouvé.", + 1299 : "Je dois avoir un défitoon.", + + # Toontasks "Je crois que tu devrais choisir..." + 1300 : "Je crois que tu devrais choisir Toonique.", + 1301 : "Je crois que tu devrais choisir Tapage.", + 1302 : "Je crois que tu devrais choisir Chute.", + 1303 : "Je crois que tu devrais choisir Piégeage.", + 1304 : "Je crois que tu devrais choisir Leurre.", + + # Battle + 1400 : "Dépêche-toi!", + 1401 : "Joli coup!", + 1402 : "Gag sympa!", + 1403 : "Manqué!", + 1404 : "Tu as réussi!", + 1405 : "On a réussi!", + 1406 : "Amène ça!", + 1407 : "Du gâteau!", + 1408 : "C'était facile!", + 1409 : "Cours!", + 1410 : "À l'aide!", + 1411 : "Ouf!", + 1412 : "On a des ennuis.", + 1413 : "J'ai besoin de plus de gags.", + 1414 : "J'ai besoin d'un Toonique.", + 1415 : "Tu devrais passer", + + # Battle "Utilisons..." + 1500 : "Utilisons un toonique!", + 1501 : "Utilisons un piège!", + 1502 : "Utilisons un leurre!", + 1503 : "Utilisons un tapage!", + 1504 : "Lançons quelque chose!", + 1505 : "Utilisons une éclaboussure!", + 1506 : "Utilisons une chute!", + + # Gag Shop + 1600 : "J'ai assez de gags.", + 1601 : "J'ai besoin de plus de bonbons.", + 1602 : "Moi aussi.", + 1603 : "Dépêche-toi!", + 1604 : "Un de plus?", + 1605 : "Rejouer?", + 1606 : "Jouons encore.", + + # Factory + 1700 : "Séparons-nous.", + 1701 : "Restons ensemble!", + 1702 : "Allons vaincre les Cogs.", + 1703 : "Marche sur le sélecteur.", + 1704 : "Passe par la porte.", + + # Sellbot Factory + 1803 : "Je suis dans l'entrée principale.", + 1804 : "Je suis dans le hall.", + 1805 : "Je suis dans le couloir devant le hall.", + 1806 : "Je suis dans le couloir devant le hall.", + 1807 : "Je suis dans la salle des pignons.", + 1808 : "Je suis dans la chaufferie.", + 1809 : "Je suis sur la passerelle est.", + 1810 : "Je suis dans le mélangeur à peinture.", + 1811 : "Je suis dans la réserve du mélangeur à peinture.", + 1812 : "Je suis sur la passerelle ouest.", + 1813 : "Je suis dans la salle des tuyaux.", + 1814 : "Je suis dans l'escalier qui mène à la salle des tuyaux.", + 1815 : "Je suis dans la salle des canalisations.", + 1816 : "Je suis dans l'entrée latérale.", + 1817 : "Je suis dans l'allée des pas perdus.", + 1818 : "Je suis à l'extérieur des sanitaires.", + 1819 : "Je suis dans les sanitaires.", + 1820 : "Je suis dans la réserve des sanitaires.", + 1821 : "Je suis sur la passerelle ouest.", + 1822 : "Je suis dans la salle du pétrole.", + 1823 : "Je suis au poste d'observation de la réserve.", + 1824 : "Je suis dans la réserve.", + 1825 : "Je suis devant le mélangeur à peinture.", + 1827 : "Je suis devant la salle du pétrole.", + 1830 : "Je suis dans la salle de contrôle du silo est.", + 1831 : "Je suis dans la salle de contrôle du silo ouest.", + 1832 : "Je suis dans la salle de contrôle du silo central.", + 1833 : "Je suis au silo est.", + 1834 : "Je suis au silo ouest.", + 1835 : "Je suis au silo central.", + 1836 : "Je suis au silo ouest.", + 1837 : "Je suis au silo est.", + 1838 : "Je suis sur la passerelle du silo est.", + 1840 : "Je suis en haut du silo ouest.", + 1841 : "Je suis en haut du silo est.", + 1860 : "Je suis dans l'ascenseur du silo ouest.", + 1861 : "Je suis dans l'ascenseur du silo est.", + # Sellbot Factory continued + 1903 : "Retrouvons-nous à l'entrée principale.", + 1904 : "Retrouvons-nous dans le hall.", + 1905 : "Retrouvons-nous dans le couloir devant le hall.", + 1906 : "Retrouvons-nous dans le couloir devant le hall.", + 1907 : "Retrouvons-nous dans la salle des pignons.", + 1908 : "Retrouvons-nous dans la chaufferie.", + 1909 : "Retrouvons-nous sur la passerelle est.", + 1910 : "Retrouvons-nous au mélangeur à peinture.", + 1911 : "Retrouvons-nous dans la réserve du mélangeur à peinture.", + 1912 : "Retrouvons-nous sur la passerelle du silo ouest.", + 1913 : "Retrouvons-nous dans la salle des tuyaux.", + 1914 : "Retrouvons-nous dans l'escalier qui mène à la salle des tuyaux.", + 1915 : "Retrouvons-nous dans la salle des canalisations.", + 1916 : "Retrouvons-nous à l'entrée latérale.", + 1917 : "Retrouvons-nous dans l'allée des pas perdus.", + 1918 : "Retrouvons-nous devant les sanitaires.", + 1919 : "Retrouvons-nous dans les sanitaires.", + 1920 : "Retrouvons-nous dans la réserve des sanitaires.", + 1921 : "Retrouvons-nous sur la passerelle ouest.", + 1922 : "Retrouvons-nous dans la salle du pétrole.", + 1923 : "Retrouvons-nous au poste d'observation de la réserve.", + 1924 : "Retrouvons-nous dans la réserve.", + 1925 : "Retrouvons-nous devant le mélangeur à peinture.", + 1927 : "Retrouvons-nous devant la salle du pétrole.", + 1930 : "Retrouvons-nous dans la salle de contrôle du silo est.", + 1931 : "Retrouvons-nous dans la salle de contrôle du silo ouest.", + 1932 : "Retrouvons-nous dans la salle de contrôle du silo central.", + 1933 : "Retrouvons-nous au silo est.", + 1934 : "Retrouvons-nous au silo ouest.", + 1935 : "Retrouvons-nous au silo central.", + 1936 : "Retrouvons-nous au silo ouest.", + 1937 : "Retrouvons-nous au silo est.", + 1938 : "Retrouvons-nous sur la passerelle du silo est.", + 1940 : "Retrouvons-nous en haut du silo ouest.", + 1941 : "Retrouvons-nous en haut du silo est.", + 1960 : "Retrouvons-nous dans l'ascenseur du silo ouest.", + 1961 : "Retrouvons-nous dans l'ascenseur du silo est.", + + # These are used only for the style settings in the OptionsPage + # These should never actually be spoken or listed on the real speed chat + 2000 : "Violet", + 2001 : "Bleu", + 2002 : "Cyan", + 2003 : "Bleu-gris", + 2004 : "Vert", + 2005 : "Jaune", + 2006 : "Orange", + 2007 : "Rouge", + 2008 : "Rose", + 2009 : "Brun", + + # cog phrases for disguised toons + # (just references to cog dialog above) + + # common cog phrases + 20000 : SuitBrushOffs[None][0], + 20001 : SuitBrushOffs[None][1], + 20002 : SuitBrushOffs[None][2], + 20003 : SuitBrushOffs[None][3], + 20004 : SuitBrushOffs[None][4], + + # specific cog phrases + 20005: SuitFaceoffTaunts['bf'][0], + 20006: SuitFaceoffTaunts['bf'][1], + 20007: SuitFaceoffTaunts['bf'][2], + 20008: SuitFaceoffTaunts['bf'][3], + 20009: SuitFaceoffTaunts['bf'][4], + 20010: SuitFaceoffTaunts['bf'][5], + 20011: SuitFaceoffTaunts['bf'][6], + 20012: SuitFaceoffTaunts['bf'][7], + 20013: SuitFaceoffTaunts['bf'][8], + 20014: SuitFaceoffTaunts['bf'][9], + + 20015: SuitFaceoffTaunts['nc'][0], + 20016: SuitFaceoffTaunts['nc'][1], + 20017: SuitFaceoffTaunts['nc'][2], + 20018: SuitFaceoffTaunts['nc'][3], + 20019: SuitFaceoffTaunts['nc'][4], + 20020: SuitFaceoffTaunts['nc'][5], + 20021: SuitFaceoffTaunts['nc'][6], + 20022: SuitFaceoffTaunts['nc'][7], + 20023: SuitFaceoffTaunts['nc'][8], + 20024: SuitFaceoffTaunts['nc'][9], + + 20025: SuitFaceoffTaunts['ym'][0], + 20026: SuitFaceoffTaunts['ym'][1], + 20027: SuitFaceoffTaunts['ym'][2], + 20028: SuitFaceoffTaunts['ym'][3], + 20029: SuitFaceoffTaunts['ym'][4], + 20030: SuitFaceoffTaunts['ym'][5], + 20031: SuitFaceoffTaunts['ym'][6], + 20032: SuitFaceoffTaunts['ym'][7], + 20033: SuitFaceoffTaunts['ym'][8], + 20034: SuitFaceoffTaunts['ym'][9], + 20035: SuitFaceoffTaunts['ym'][10], + + 20036: SuitFaceoffTaunts['ms'][0], + 20037: SuitFaceoffTaunts['ms'][1], + 20038: SuitFaceoffTaunts['ms'][2], + 20039: SuitFaceoffTaunts['ms'][3], + 20040: SuitFaceoffTaunts['ms'][4], + 20041: SuitFaceoffTaunts['ms'][5], + 20042: SuitFaceoffTaunts['ms'][6], + 20043: SuitFaceoffTaunts['ms'][7], + 20044: SuitFaceoffTaunts['ms'][8], + 20045: SuitFaceoffTaunts['ms'][9], + 20046: SuitFaceoffTaunts['ms'][10], + + 20047: SuitFaceoffTaunts['bc'][0], + 20048: SuitFaceoffTaunts['bc'][1], + 20049: SuitFaceoffTaunts['bc'][2], + 20050: SuitFaceoffTaunts['bc'][3], + 20051: SuitFaceoffTaunts['bc'][4], + 20052: SuitFaceoffTaunts['bc'][5], + 20053: SuitFaceoffTaunts['bc'][6], + 20054: SuitFaceoffTaunts['bc'][7], + 20055: SuitFaceoffTaunts['bc'][8], + 20056: SuitFaceoffTaunts['bc'][9], + 20057: SuitFaceoffTaunts['bc'][10], + + 20058: SuitFaceoffTaunts['cc'][0], + 20059: SuitFaceoffTaunts['cc'][1], + 20060: SuitFaceoffTaunts['cc'][2], + 20061: SuitFaceoffTaunts['cc'][3], + 20062: SuitFaceoffTaunts['cc'][4], + 20063: SuitFaceoffTaunts['cc'][5], + 20064: SuitFaceoffTaunts['cc'][6], + 20065: SuitFaceoffTaunts['cc'][7], + 20066: SuitFaceoffTaunts['cc'][8], + 20067: SuitFaceoffTaunts['cc'][9], + 20068: SuitFaceoffTaunts['cc'][10], + 20069: SuitFaceoffTaunts['cc'][11], + 20070: SuitFaceoffTaunts['cc'][12], + + 20071: SuitFaceoffTaunts['nd'][0], + 20072: SuitFaceoffTaunts['nd'][1], + 20073: SuitFaceoffTaunts['nd'][2], + 20074: SuitFaceoffTaunts['nd'][3], + 20075: SuitFaceoffTaunts['nd'][4], + 20076: SuitFaceoffTaunts['nd'][5], + 20077: SuitFaceoffTaunts['nd'][6], + 20078: SuitFaceoffTaunts['nd'][7], + 20079: SuitFaceoffTaunts['nd'][8], + 20080: SuitFaceoffTaunts['nd'][9], + + 20081: SuitFaceoffTaunts['ac'][0], + 20082: SuitFaceoffTaunts['ac'][1], + 20083: SuitFaceoffTaunts['ac'][2], + 20084: SuitFaceoffTaunts['ac'][3], + 20085: SuitFaceoffTaunts['ac'][4], + 20086: SuitFaceoffTaunts['ac'][5], + 20087: SuitFaceoffTaunts['ac'][6], + 20088: SuitFaceoffTaunts['ac'][7], + 20089: SuitFaceoffTaunts['ac'][8], + 20090: SuitFaceoffTaunts['ac'][9], + 20091: SuitFaceoffTaunts['ac'][10], + 20092: SuitFaceoffTaunts['ac'][11], + + 20093: SuitFaceoffTaunts['tf'][0], + 20094: SuitFaceoffTaunts['tf'][1], + 20095: SuitFaceoffTaunts['tf'][2], + 20096: SuitFaceoffTaunts['tf'][3], + 20097: SuitFaceoffTaunts['tf'][4], + 20098: SuitFaceoffTaunts['tf'][5], + 20099: SuitFaceoffTaunts['tf'][6], + 20100: SuitFaceoffTaunts['tf'][7], + 20101: SuitFaceoffTaunts['tf'][8], + 20102: SuitFaceoffTaunts['tf'][9], + 20103: SuitFaceoffTaunts['tf'][10], + + 20104: SuitFaceoffTaunts['hh'][0], + 20105: SuitFaceoffTaunts['hh'][1], + 20106: SuitFaceoffTaunts['hh'][2], + 20107: SuitFaceoffTaunts['hh'][3], + 20108: SuitFaceoffTaunts['hh'][4], + 20109: SuitFaceoffTaunts['hh'][5], + 20110: SuitFaceoffTaunts['hh'][6], + 20111: SuitFaceoffTaunts['hh'][7], + 20112: SuitFaceoffTaunts['hh'][8], + 20113: SuitFaceoffTaunts['hh'][9], + 20114: SuitFaceoffTaunts['hh'][10], + + 20115: SuitFaceoffTaunts['le'][0], + 20116: SuitFaceoffTaunts['le'][1], + 20117: SuitFaceoffTaunts['le'][2], + 20118: SuitFaceoffTaunts['le'][3], + 20119: SuitFaceoffTaunts['le'][4], + 20120: SuitFaceoffTaunts['le'][5], + 20121: SuitFaceoffTaunts['le'][6], + 20122: SuitFaceoffTaunts['le'][7], + 20123: SuitFaceoffTaunts['le'][8], + 20124: SuitFaceoffTaunts['le'][9], + + 20125: SuitFaceoffTaunts['bs'][0], + 20126: SuitFaceoffTaunts['bs'][1], + 20127: SuitFaceoffTaunts['bs'][2], + 20128: SuitFaceoffTaunts['bs'][3], + 20129: SuitFaceoffTaunts['bs'][4], + 20130: SuitFaceoffTaunts['bs'][5], + 20131: SuitFaceoffTaunts['bs'][6], + 20132: SuitFaceoffTaunts['bs'][7], + 20133: SuitFaceoffTaunts['bs'][8], + 20134: SuitFaceoffTaunts['bs'][9], + 20135: SuitFaceoffTaunts['bs'][10], + + 20136: SuitFaceoffTaunts['cr'][0], + 20137: SuitFaceoffTaunts['cr'][1], + 20138: SuitFaceoffTaunts['cr'][2], + 20139: SuitFaceoffTaunts['cr'][3], + 20140: SuitFaceoffTaunts['cr'][4], + 20141: SuitFaceoffTaunts['cr'][5], + 20142: SuitFaceoffTaunts['cr'][6], + 20143: SuitFaceoffTaunts['cr'][7], + 20144: SuitFaceoffTaunts['cr'][8], + 20145: SuitFaceoffTaunts['cr'][9], + + 20146: SuitFaceoffTaunts['tbc'][0], + 20147: SuitFaceoffTaunts['tbc'][1], + 20148: SuitFaceoffTaunts['tbc'][2], + 20149: SuitFaceoffTaunts['tbc'][3], + 20150: SuitFaceoffTaunts['tbc'][4], + 20151: SuitFaceoffTaunts['tbc'][5], + 20152: SuitFaceoffTaunts['tbc'][6], + 20153: SuitFaceoffTaunts['tbc'][7], + 20154: SuitFaceoffTaunts['tbc'][8], + 20155: SuitFaceoffTaunts['tbc'][9], + 20156: SuitFaceoffTaunts['tbc'][10], + + 20157: SuitFaceoffTaunts['ds'][0], + 20158: SuitFaceoffTaunts['ds'][1], + 20159: SuitFaceoffTaunts['ds'][2], + 20160: SuitFaceoffTaunts['ds'][3], + 20161: SuitFaceoffTaunts['ds'][4], + 20162: SuitFaceoffTaunts['ds'][5], + 20163: SuitFaceoffTaunts['ds'][6], + 20164: SuitFaceoffTaunts['ds'][7], + + 20165: SuitFaceoffTaunts['gh'][0], + 20166: SuitFaceoffTaunts['gh'][1], + 20167: SuitFaceoffTaunts['gh'][2], + 20168: SuitFaceoffTaunts['gh'][3], + 20169: SuitFaceoffTaunts['gh'][4], + 20170: SuitFaceoffTaunts['gh'][5], + 20171: SuitFaceoffTaunts['gh'][6], + 20172: SuitFaceoffTaunts['gh'][7], + 20173: SuitFaceoffTaunts['gh'][8], + 20174: SuitFaceoffTaunts['gh'][9], + 20175: SuitFaceoffTaunts['gh'][10], + 20176: SuitFaceoffTaunts['gh'][11], + 20177: SuitFaceoffTaunts['gh'][12], + + 20178: SuitFaceoffTaunts['pp'][0], + 20179: SuitFaceoffTaunts['pp'][1], + 20180: SuitFaceoffTaunts['pp'][2], + 20181: SuitFaceoffTaunts['pp'][3], + 20182: SuitFaceoffTaunts['pp'][4], + 20183: SuitFaceoffTaunts['pp'][5], + 20184: SuitFaceoffTaunts['pp'][6], + 20185: SuitFaceoffTaunts['pp'][7], + 20186: SuitFaceoffTaunts['pp'][8], + 20187: SuitFaceoffTaunts['pp'][9], + + 20188: SuitFaceoffTaunts['b'][0], + 20189: SuitFaceoffTaunts['b'][1], + 20190: SuitFaceoffTaunts['b'][2], + 20191: SuitFaceoffTaunts['b'][3], + 20192: SuitFaceoffTaunts['b'][4], + 20193: SuitFaceoffTaunts['b'][5], + 20194: SuitFaceoffTaunts['b'][6], + 20195: SuitFaceoffTaunts['b'][7], + 20196: SuitFaceoffTaunts['b'][8], + 20197: SuitFaceoffTaunts['b'][9], + 20198: SuitFaceoffTaunts['b'][10], + 20199: SuitFaceoffTaunts['b'][11], + + 20200: SuitFaceoffTaunts['f'][0], + 20201: SuitFaceoffTaunts['f'][1], + 20202: SuitFaceoffTaunts['f'][2], + 20203: SuitFaceoffTaunts['f'][3], + 20204: SuitFaceoffTaunts['f'][4], + 20205: SuitFaceoffTaunts['f'][5], + 20206: SuitFaceoffTaunts['f'][6], + 20207: SuitFaceoffTaunts['f'][7], + 20208: SuitFaceoffTaunts['f'][8], + 20209: SuitFaceoffTaunts['f'][9], + 20210: SuitFaceoffTaunts['f'][10], + + 20211: SuitFaceoffTaunts['mm'][0], + 20212: SuitFaceoffTaunts['mm'][1], + 20213: SuitFaceoffTaunts['mm'][2], + 20214: SuitFaceoffTaunts['mm'][3], + 20215: SuitFaceoffTaunts['mm'][4], + 20216: SuitFaceoffTaunts['mm'][5], + 20217: SuitFaceoffTaunts['mm'][6], + 20218: SuitFaceoffTaunts['mm'][7], + 20219: SuitFaceoffTaunts['mm'][8], + 20220: SuitFaceoffTaunts['mm'][9], + 20221: SuitFaceoffTaunts['mm'][10], + 20222: SuitFaceoffTaunts['mm'][11], + 20223: SuitFaceoffTaunts['mm'][12], + 20224: SuitFaceoffTaunts['mm'][13], + + 20225: SuitFaceoffTaunts['tw'][0], + 20226: SuitFaceoffTaunts['tw'][1], + 20227: SuitFaceoffTaunts['tw'][2], + 20228: SuitFaceoffTaunts['tw'][3], + 20229: SuitFaceoffTaunts['tw'][4], + 20230: SuitFaceoffTaunts['tw'][5], + 20231: SuitFaceoffTaunts['tw'][6], + 20232: SuitFaceoffTaunts['tw'][7], + 20233: SuitFaceoffTaunts['tw'][8], + 20234: SuitFaceoffTaunts['tw'][9], + 20235: SuitFaceoffTaunts['tw'][10], + + 20236: SuitFaceoffTaunts['mb'][0], + 20237: SuitFaceoffTaunts['mb'][1], + 20238: SuitFaceoffTaunts['mb'][2], + 20239: SuitFaceoffTaunts['mb'][3], + 20240: SuitFaceoffTaunts['mb'][4], + 20241: SuitFaceoffTaunts['mb'][5], + 20242: SuitFaceoffTaunts['mb'][6], + 20243: SuitFaceoffTaunts['mb'][7], + 20244: SuitFaceoffTaunts['mb'][8], + 20245: SuitFaceoffTaunts['mb'][9], + + 20246: SuitFaceoffTaunts['m'][0], + 20247: SuitFaceoffTaunts['m'][1], + 20248: SuitFaceoffTaunts['m'][2], + 20249: SuitFaceoffTaunts['m'][3], + 20250: SuitFaceoffTaunts['m'][4], + 20251: SuitFaceoffTaunts['m'][5], + 20252: SuitFaceoffTaunts['m'][6], + 20253: SuitFaceoffTaunts['m'][7], + 20254: SuitFaceoffTaunts['m'][8], + + 20255: SuitFaceoffTaunts['mh'][0], + 20256: SuitFaceoffTaunts['mh'][1], + 20257: SuitFaceoffTaunts['mh'][2], + 20258: SuitFaceoffTaunts['mh'][3], + 20259: SuitFaceoffTaunts['mh'][4], + 20260: SuitFaceoffTaunts['mh'][5], + 20261: SuitFaceoffTaunts['mh'][6], + 20262: SuitFaceoffTaunts['mh'][7], + 20263: SuitFaceoffTaunts['mh'][8], + 20264: SuitFaceoffTaunts['mh'][9], + 20265: SuitFaceoffTaunts['mh'][10], + 20266: SuitFaceoffTaunts['mh'][11], + + 20267: SuitFaceoffTaunts['dt'][0], + 20268: SuitFaceoffTaunts['dt'][1], + 20269: SuitFaceoffTaunts['dt'][2], + 20270: SuitFaceoffTaunts['dt'][3], + 20271: SuitFaceoffTaunts['dt'][4], + 20272: SuitFaceoffTaunts['dt'][5], + 20273: SuitFaceoffTaunts['dt'][6], + 20274: SuitFaceoffTaunts['dt'][7], + 20275: SuitFaceoffTaunts['dt'][8], + 20276: SuitFaceoffTaunts['dt'][9], + + 20277: SuitFaceoffTaunts['p'][0], + 20278: SuitFaceoffTaunts['p'][1], + 20279: SuitFaceoffTaunts['p'][2], + 20280: SuitFaceoffTaunts['p'][3], + 20281: SuitFaceoffTaunts['p'][4], + 20282: SuitFaceoffTaunts['p'][5], + 20283: SuitFaceoffTaunts['p'][6], + 20284: SuitFaceoffTaunts['p'][7], + 20285: SuitFaceoffTaunts['p'][8], + 20286: SuitFaceoffTaunts['p'][9], + 20287: SuitFaceoffTaunts['p'][10], + + 20288: SuitFaceoffTaunts['tm'][0], + 20289: SuitFaceoffTaunts['tm'][1], + 20290: SuitFaceoffTaunts['tm'][2], + 20291: SuitFaceoffTaunts['tm'][3], + 20292: SuitFaceoffTaunts['tm'][4], + 20293: SuitFaceoffTaunts['tm'][5], + 20294: SuitFaceoffTaunts['tm'][6], + 20295: SuitFaceoffTaunts['tm'][7], + 20296: SuitFaceoffTaunts['tm'][8], + 20297: SuitFaceoffTaunts['tm'][9], + 20298: SuitFaceoffTaunts['tm'][10], + + 20299: SuitFaceoffTaunts['bw'][0], + 20300: SuitFaceoffTaunts['bw'][1], + 20301: SuitFaceoffTaunts['bw'][2], + 20302: SuitFaceoffTaunts['bw'][3], + 20303: SuitFaceoffTaunts['bw'][4], + 20304: SuitFaceoffTaunts['bw'][5], + 20305: SuitFaceoffTaunts['bw'][6], + 20306: SuitFaceoffTaunts['bw'][7], + 20307: SuitFaceoffTaunts['bw'][8], + 20308: SuitFaceoffTaunts['bw'][9], + + 20309: SuitFaceoffTaunts['ls'][0], + 20310: SuitFaceoffTaunts['ls'][1], + 20311: SuitFaceoffTaunts['ls'][2], + 20312: SuitFaceoffTaunts['ls'][3], + 20313: SuitFaceoffTaunts['ls'][4], + 20314: SuitFaceoffTaunts['ls'][5], + 20315: SuitFaceoffTaunts['ls'][6], + 20316: SuitFaceoffTaunts['ls'][7], + 20317: SuitFaceoffTaunts['ls'][8], + 20318: SuitFaceoffTaunts['ls'][9], + 20319: SuitFaceoffTaunts['ls'][10], + + 20320: SuitFaceoffTaunts['rb'][0], + 20321: SuitFaceoffTaunts['rb'][1], + 20322: SuitFaceoffTaunts['rb'][2], + 20323: SuitFaceoffTaunts['rb'][3], + 20324: SuitFaceoffTaunts['rb'][4], + 20325: SuitFaceoffTaunts['rb'][5], + 20326: SuitFaceoffTaunts['rb'][6], + 20327: SuitFaceoffTaunts['rb'][7], + 20328: SuitFaceoffTaunts['rb'][8], + 20329: SuitFaceoffTaunts['rb'][9], + + 20330: SuitFaceoffTaunts['sc'][0], + 20331: SuitFaceoffTaunts['sc'][1], + 20332: SuitFaceoffTaunts['sc'][2], + 20333: SuitFaceoffTaunts['sc'][3], + 20334: SuitFaceoffTaunts['sc'][4], + 20335: SuitFaceoffTaunts['sc'][5], + 20336: SuitFaceoffTaunts['sc'][6], + 20337: SuitFaceoffTaunts['sc'][7], + 20338: SuitFaceoffTaunts['sc'][8], + 20339: SuitFaceoffTaunts['sc'][9], + 20340: SuitFaceoffTaunts['sc'][10], + + 20341: SuitFaceoffTaunts['sd'][0], + 20342: SuitFaceoffTaunts['sd'][1], + 20343: SuitFaceoffTaunts['sd'][2], + 20344: SuitFaceoffTaunts['sd'][3], + 20345: SuitFaceoffTaunts['sd'][4], + 20346: SuitFaceoffTaunts['sd'][5], + 20347: SuitFaceoffTaunts['sd'][6], + 20348: SuitFaceoffTaunts['sd'][7], + 20349: SuitFaceoffTaunts['sd'][8], + 20350: SuitFaceoffTaunts['sd'][9], + } + +# These indexes, defined above, will construct a submenu in the FACTORY menu +# to allow the user to describe all the places he might want to meet +SCFactoryMeetMenuIndexes = (1903, 1904, 1906, 1907, 1908, 1910, 1913, + 1915, 1916, 1917, 1919, 1922, 1923, + 1924, 1932, 1940, 1941) + +# CustomSCStrings: SpeedChat phrases available for purchase. It is +# safe to remove entries from this list, which will disable them for +# use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +CustomSCStrings = { + # Series 1 + 10 : "Oh, bon.", + 20 : "Pourquoi pas?", + 30 : "Naturellement!", + 40 : "C'est comme ça que je fais.", + 50 : "Tout juste!", + 60 : "Qu'est-ce qui se passe?", + 70 : "Mais bien sûr!", + 80 : "Bingo!", + 90 : "Tu plaisantes...", + 100 : "Ça m'a l'air bien.", + 110 : "C'est loufoque!", + 120 : "Atmosphérique!", + 130 : "Bon sang!", + 140 : "Ne t'en fais pas.", + 150 : "Grrrr!", + 160 : "Quoi de neuf?", + 170 : "Hé hé hé!", + 180 : "À demain.", + 190 : "À la prochaine fois.", + 200 : "À plus tard, lézard.", + 210 : "Dans un moment, caïman.", + 220 : "Je dois m'en aller d'ici peu.", + 230 : "Je n'en sais rien!", + 240 : "Tu es déjà parti!", + 250 : "Aïe, ça pique!", + 260 : "Je t'ai eu!", + 270 : "S'il te plaît!", + 280 : "Merci vraiment beaucoup!", + 290 : "Tu te débrouilles bien!", + 300 : "Excuse moi!", + 310 : "Puis-je t'aider?", + 320 : "C'est ce que je dis!", + 330 : "Si tu as peur de te brûler, évite la cuisine.", + 340 : "Mille sabords!", + 350 : "Est-ce que ne n'est pas spécial!", + 360 : "Arrête de chahuter!", + 370 : "Le chat a mangé ta langue?", + 380 : "Maintenant tu es mal vu(e)!", + 390 : "Eh bien, regarde ce qui arrive là.", + 400 : "Je dois aller voir un Toon.", + 410 : "Ne t'énerve pas!", + 420 : "Ne te dégonfle pas!", + 430 : "Tu es une proie facile.", + 440 : "Peu importe!", + 450 : "Complètement!", + 460 : "Adorable!", + 470 : "C'est super!", + 480 : "Ouais, mon chou!", + 490 : "Attrape-moi si tu peux!", + 500 : "Il faut d'abord que tu te soignes.", + 510 : "Tu as besoin de plus de rigolpoints.", + 520 : "Je reviens dans une minute.", + 530 : "J'ai faim.", + 540 : "Ouais, t'as raison!", + 550 : "J'ai sommeil.", + 560 : "Je suis prêt(e) maintenant!", + 570 : "Ça m'ennuie.", + 580 : "J'adore ça!", + 590 : "C'était sensationnel!", + 600 : "Saute!", + 610 : "Tu as des gags?", + 620 : "Qu'est-ce qui ne va pas?", + 630 : "Doucement.", + 640 : "Qui va lentement va sûrement.", + 650 : "Marqué!", + 660 : "Prêt?", + 670 : "À vos marques!", + 680 : "Partez!", + 690 : "Allons par là!", + 700 : "Tu as gagné!", + 710 : "Je vote oui.", + 720 : "Je vote non.", + 730 : "J'en suis.", + 740 : "Je n'en suis pas.", + 750 : "On ne bouge pas, je reviens.", + 760 : "C'était rapide!", + 770 : "Qu'est-ce que c'est que ça?", + 780 : "Qu'est-ce que c'est que cette odeur?", + 790 : "Ça pue!", + 800 : "Je m'en fiche.", + 810 : "C'est exactement ce qu'il fallait.", + 820 : "Commençons la fête!", + 830 : "Par ici tout le monde!", + 840 : "Quoi de neuf?", + 850 : "Le chèque est parti.", + 860 : "J'ai entendu ce que tu as dit!", + 870 : "Tu me parles?", + 880 : "Merci, je serai ici toute la semaine.", + 890 : "Hmm.", + 900 : "Je prends celui-là.", + 910 : "Je l'ai!", + 920 : "C'est à moi!", + 930 : "S'il te plaît, prends-le.", + 940 : "On ne s'approche pas, ça pourrait être dangereux.", + 950 : "Pas de soucis!", + 960 : "Oh, non!", + 970 : "Houlala!", + 980 : "Youhouuu!", + 990 : "Tout le monde à bord!", + 1000 : "Nom d'un chien!", + 1010 : "La curiosité est un vilain défaut.", + # Series 2 + 2000 : "Ne fais pas le bébé!", + 2010 : "Si je suis content(e) de te voir!", + 2020 : "Je t'en prie.", + 2030 : "Tu as évité les ennuis?", + 2040 : "Mieux vaut tard que jamais!", + 2050 : "Bravo!", + 2060 : "Sérieusement, les copains...", + 2070 : "Tu veux te joindre à nous?", + 2080 : "À plus tard!", + 2090 : "Changé d'avis?", + 2100 : "Viens le prendre!", + 2110 : "Oh là là!", + 2120 : "Ravi(e) de faire ta connaissance.", + 2130 : "Ne fais rien que je ne ferais pas!", + 2140 : "N'y pense pas!", + 2150 : "N'abandonne pas le navire!", + 2160 : "Ne retiens pas ta respiration.", + 2170 : "No comment.", + 2180 : "C'est facile à dire.", + 2190 : "Assez c'est assez!", + 2200 : "Excellent!", + 2210 : "Content de te trouver ici!", + 2220 : "Arrête un peu.", + 2230 : "Content d'entendre ça.", + 2240 : "Continue, ça m'amuse!", + 2250 : "Vas-y!", + 2260 : "Bon travail!", + 2270 : "Content de te voir!", + 2280 : "Il faut que je bouge.", + 2290 : "Il faut que je m'en aille.", + 2300 : "Attends là.", + 2310 : "Attends une seconde.", + 2320 : "Va t'éclater!", + 2330 : "Amuse-toi!", + 2340 : "Je n'ai pas toute la journée!", + 2350 : "Retiens la vapeur!", + 2360 : "Nom d'un petit bonhomme!", + 2370 : "Je n'y crois pas!", + 2380 : "J'en doute.", + 2390 : "Je t'en dois un.", + 2400 : "Je te reçois 5 sur 5.", + 2410 : "Je crois aussi.", + 2420 : "Je crois que tu devrais passer un tour.", + 2430 : "C'est moi qui voulais le dire.", + 2440 : "Je ne ferais pas ça si j'étais toi.", + 2450 : "Ce serait avec plaisir!", + 2460 : "J'aide mon ami(e).", + 2470 : "Je suis là toute la semaine.", + 2480 : "Imagine ça!", + 2490 : "Juste à temps...", + 2500 : "Tant que ce n'est pas fini, ce n'est pas fini.", + 2510 : "Je pense tout haut.", + 2520 : "On reste en contact.", + 2530 : "Quel temps de chien!", + 2540 : "Et que ça saute!", + 2550 : "Fais comme chez toi.", + 2560 : "Une autre fois peut-être.", + 2570 : "Je peux me joindre à vous?", + 2580 : "C'est sympa ici.", + 2590 : "Je suis content de te parler.", + 2600 : "Ça ne fait aucun doute.", + 2610 : "Sans blague!", + 2620 : "Ni de près ni de loin.", + 2630 : "Quel culot!", + 2640 : "OK pour moi.", + 2650 : "D'accord.", + 2660 : "Dis \" Cheese! \"", + 2670 : "Tu dis quoi?", + 2680 : "Ta-daa!", + 2690 : "Doucement.", + 2700 : "À plus!", + 2710 : "Merci, mais non.", + 2720 : "C'est le bouquet!", + 2730 : "C'est marrant.", + 2740 : "Voilà exactement ce qu'il faut!", + 2750 : "Il y a une invasion de Cogs!", + 2760 : "Salut.", + 2770 : "Fais attention!", + 2780 : "Bravo!", + 2790 : "Qu'est-ce qui se prépare?", + 2800 : "Qu'est-ce qui se passe?", + 2810 : "Ça marche pour moi.", + 2820 : "Oui monseigneur.", + 2830 : "Tu paries.", + 2840 : "Tu fais le calcul.", + 2850 : "Tu t'en vas déjà?", + 2860 : "Tu me fais rire!", + 2870 : "Ça va bien.", + 2880 : "Tu descends!", + # Series 3 + 3000 : "Tout ce que tu diras.", + 3010 : "Je pourrais venir?", + 3020 : "Vérifie, s'il te plaît.", + 3030 : "Ne sois pas trop certain.", + 3040 : "Ça ne te fait rien si je le fais.", + 3050 : "Pas de panique!", + 3060 : "Tu ne le savais pas!", + 3070 : "Ne t'occupe pas de moi.", + 3080 : "Eureka!", + 3090 : "Voyez-vous ça!", + 3100 : "Oublie ça!", + 3110 : "Tu vas dans la même direction?", + 3120 : "Content(e) pour toi!", + 3130 : "Mon Dieu!", + 3140 : "Passe un bon moment!", + 3150 : "Réfléchissons!", + 3160 : "Et ça recommence.", + 3170 : "Et voilà!", + 3180 : "Ça te plaît?", + 3190 : "Je crois aussi.", + 3200 : "Je ne crois pas.", + 3210 : "Je te recontacte.", + 3220 : "Je suis toute ouïe.", + 3230 : "Je suis occupé(e).", + 3240 : "Je ne blague pas!", + 3250 : "J'en suis baba.", + 3260 : "Garde le sourire.", + 3270 : "Tiens-moi au courant!", + 3280 : "Laisse faire!", + 3290 : "De même, certainement.", + 3300 : "Remue-toi!", + 3310 : "Oh là là, comme le temps passe.", + 3320 : "Sans commentaire.", + 3330 : "Ah, on y vient!", + 3340 : "OK pour moi.", + 3350 : "Ravi(e) de te rencontrer.", + 3360 : "D'accord.", + 3370 : "Sûrement.", + 3380 : "Merci vraiment beaucoup.", + 3390 : "C'est plutôt ça.", + 3400 : "Voilà ce qu'il faut!", + 3410 : "C'est l'heure pour moi d'aller faire un somme.", + 3420 : "Crois-moi!", + 3430 : "Jusqu'à la prochaine fois.", + 3440 : "Ne t'endors pas!", + 3450 : "C'est comme ça qu'il faut faire!", + 3460 : "Qu'est-ce qui t'amène?", + 3470 : "Qu'est-ce qui s'est passé?", + 3480 : "Et quoi maintenant?", + 3490 : "Toi d'abord.", + 3500 : "Tu prends à gauche.", + 3510 : "Tu parles!", + 3520 : "Tu es grillé(e)!", + 3530 : "Tu es trop!", + # Series 4 + 4000 : "Vive les Toons! ", + 4010 : "Les Cogs en sont baba! ", + 4020 : "Tous les Toons du monde ensemble! ", + 4030 : "Salut, mon pote! ", + 4040 : "Merci beaucoup. ", + 4050 : "Fiche le camp, l’ami. ", + 4060 : "J’en peux plus, je vais dormir. ", + 4070 : "J’en croque un morceau! ", + 4080 : "La ville n’est pas assez grande pour nous deux! ", + 4090 : "En selle! ", + 4100 : "Dégaine!!! ", + 4110 : "Y’a bon... Tout ça pour moi! ", + 4120 : "Bonne route! ", + 4130 : "Et là, je m’en vais droit vers l’horizon... ", + 4140 : "On fiche le camp! ", + 4150 : "C’est une idée fixe? ", + 4160 : "Bon sang! ", + 4170 : "Impeccable. ", + 4180 : "Je crois bien. ", + 4190 : "Taillons-nous! ", + 4200 : "Eh, va savoir! ", + 4210 : "Coucou, me revoilou! ", + 4220 : "Comme on se retrouve... ", + 4230 : "Allez, hue! ", + 4240 : "Haut les mains. ", + 4250 : "J’y compte bien", + 4260 : "Retiens la vapeur! ", + 4270 : "Je raterais un éléphant dans un couloir. ", + 4280 : "À la prochaine. ", + 4290 : "C’est vraiment impressionnant! ", + 4300 : "Ne nous dis pas que tu as la trouille. ", + 4310 : "Tu crois que tu as de la chance? ", + 4320 : "Bon sang, mais qu’est-ce qui se passe ici? ", + 4330 : "Tu peux bien rouler des mécaniques! ", + 4340 : "Eh bien ça, c’est le bouquet. ", + 4350 : "C’est un vrai régal pour les yeux! ", + 4360 : "Quel trou à rats! ", + 4370 : "Ne t’en fais pas. ", + 4380 : "Quelle tronche! ", + 4390 : "Ça t’apprendra! ", + # Series 6 + 6000 : "Je veux des friandises! ", + 6010 : "J’ai un faible pour le sucré. ", + 6020 : "Ce n’est pas assez cuit. ", + 6030 : "C’est comme faucher les jouets d’un enfant! ", + 6040 : "Et treize à la douzaine. ", + 6050 : "Ils en ont voulu, ils en auront! ", + 6060 : "C’est la cerise sur le gateau", + 6070 : "On ne peut pas avoir le beurre et l’argent du beurre. ", + 6080 : "J’ai l’impression d’être un enfant dans un magasin de bonbons. ", + 6090 : "Six de celui-là, une demi-douzaine de l’autre... ", + 6100 : "Disons-le avec des mots tendres. ", + 6110 : "Concentre-toi sur ta pâte à gâteau. ", + 6120 : "Tu voudrais que j’avale ça? ", + 6130 : "C’est mince comme du papier alu. ", + 6140 : "Fais péter les cahuètes! ", + 6150 : "Tu es un dur à cuire! ", + 6160 : "Et voilà, c’est la déconfiture. ", + 6170 : "C’est comme l’eau et l’huile. ", + 6180 : "Tu me prends pour une poire? ", + 6190 : "Avec du miel, ça passera mieux. ", + 6200 : "Tu es fait de ce que tu manges! ", + 6210 : "C’est de la tarte! ", + 6220 : "Ne fais pas l’andouille! ", + 6230 : "Du sucre, de la cannelle et ce sera nickel. ", + 6240 : "C’est de la crème! ", + 6250 : "C’est le gâteau! ", + 6260 : "Y’en aura pour tout le monde! ", + 6270 : "C’est pas la peine d’en rajouter une couche. ", + 6280 : "Toc, toc... ", + 6290 : "Qui est là? ", + # Series 7 + 7000 : "Arrête de faire des singeries! ", + 7010 : "C’est vraiment mettre des bâtons dans les roues. ", + 7020 : "Tu singes tout. ", + 7030 : "Tu deviens espiègle comme un singe. ", + 7040 : "Ça sent la monnaie de singe. ", + 7050 : "Je te cherche les poux. ", + 7060 : "Qui est-ce qui fait le singe au milieu? ", + 7070 : "Tu m’enlèves une épine du pied... ", + 7080 : "C’est plus marrant qu’une armée de singes! ", + 7090 : "Sans rire... ", + 7100 : "Je suis malin comme un singe. ", + 7110 : "Et qu’est-ce qu’il a, le pingouin? ", + 7120 : "Je n’entends rien de mal. ", + 7130 : "Je ne vois rien de mal. ", + 7140 : "Je ne dis rien de mal. ", + 7150 : "Encore un truc à la noix de coco, on se casse. ", + 7160 : "C’est la jungle par ici. ", + 7170 : "T’es au top du top.", + 7180 : "Ça c’est super! ", + 7190 : "Je deviens dingue! ", + 7200 : "Entrons dans la danse! ", + 7210 : "Ça swingue par ici! ", + 7220 : "Je vais prendre racine. ", + 7230 : "On nous tourne en bourrique. ", + 7230 : "Allez, salut la compagnie. ", + 7240 : "Les bonbons ne poussent pas sur les cocotiers! ", + + # Halloween + 10000 : "Cet endroit est une ville fantôme.", + 10001 : "Joli costume!", + 10002 : "Je crois que cet endroit est hanté.", + 10003 : "Une farce ou des friandises!", + 10004 : "Bouh!", + 10005 : "Ici trouille!", + 10006 : "Joyeux Halloween!", + 10007 : "Je vais me transformer en citrouille.", + 10008 : "Fantômtastique!", + 10009 : "Sinistre!", + 10010 : "Ça fait froid dans le dos!", + 10011 : "Je déteste les araignées!", + 10012 : "Tu as entendu ça?", + 10013 : "Tu n'as pas l'ombre d'une chance!", + 10014 : "Tu m'as fait peur!", + 10015 : "C'est sinistre!", + 10016 : "C'est effrayant!", + 10017 : "C'était étrange....", + 10018 : "Des squelettes dans ton placard?", + 10019 : "Je t'ai fait peur?", + + # Fall Festivus + 11000 : "Bah! Balivernes!", + 11001 : "Mieux vaut ne pas bouder!", + 11002 : "Brrr!", + 11003 : "Glaçant!", + 11004 : "Viens le prendre!", + 11005 : "Ne prends pas cet air glacé.", + 11006 : "À la Sainte-Catherine, tout arbre prend racine!", + 11007 : "Bon réveillon!", + 11008 : "Bonne année!", + 11009 : "Chaud les marrons!", + 11010 : "Bon appétit pour la dinde!", + 11011 : "Ho! Ho! Ho!", + 11012 : "Ce neige pas un problème.", + 11013 : "Ce neige pas un mystère.", + 11014 : "Et que ça neige!", + 11015 : "On va en faire une pelletée de neige.", + 11016 : "Meilleurs vœux!", + 11017 : "Je n'en neige aucun doute!", + 11018 : "Jusque là, la neige est bonne!", + 11019 : "Tu vas le regretter!", + + # Valentines + 12000 : "Reste avec moi!", + 12001 : "Sois mon chouchou!", + 12002 : "Bonne Saint Valentin!", + 12003 : "Ooh, comme c'est mignon.", + 12004 : "J'ai le béguin pour toi.", + 12005 : "C'est une amourette.", + 12006 : "Je t'adore!", + 12007 : "C'est la Saint Valentin?", + 12008 : "Tu es un amour!", + 12009 : "Tu es mon canard en sucre.", + 12010 : "Tu es adorable.", + 12011 : "Tu as besoin d'un câlin.", + 12012 : "Adorable!", + 12013 : "C'est si mignon!", + 12014 : "Mignonne allons voir si la rose...", + 12015 : "Qui ce matin était éclose...", + 12016 : "C'est mignon!", + + # St. Patricks Day + 13000 : "Mes salutations fleuries!", + 13001 : "Vive le printemps!", + 13002 : "Tu n'es pas très printanier!", + 13003 : "C'est la chance qui éclôt.", + 13004 : "Je suis vert d'envie.", + 13005 : "Sacré veinard!", + 13006 : "Tu es mon trèfle à quatre feuilles!", + 13007 : "Tu es mon porte-bonheur!", + } + +# indices into cog phrase arrays +SCMenuCommonCogIndices = (20000, 20004) +SCMenuCustomCogIndices = { + 'bf' : (20005, 20014), + 'nc' : (20015, 20024), + 'ym' : (20025, 20035), + 'ms' : (20036, 20046), + 'bc' : (20047, 20057), + 'cc' : (20058, 20070), + 'nd' : (20071, 20080), + 'ac' : (20081, 20092), + 'tf' : (20093, 20103), + 'hh' : (20104, 20114), + 'le' : (20115, 20124), + 'bs' : (20125, 20135), + 'cr' : (20136, 20145), + 'tbc' : (20146, 20156), + 'ds' : (20157, 20164), + 'gh' : (20165, 20177), + 'pp' : (20178, 20187), + 'b' : (20188, 20199), + 'f' : (20200, 20210), + 'mm' : (20211, 20224), + 'tw' : (20225, 20235), + 'mb' : (20236, 20245), + 'm' : (20246, 20254), + 'mh' : (20255, 20266), + 'dt' : (20267, 20276), + 'p' : (20277, 20287), + 'tm' : (20288, 20298), + 'bw' : (20299, 20308), + 'ls' : (20309, 20319), + 'rb' : (20320, 20329), + 'sc' : (20330, 20331), + 'sd' : (20341, 20350), + } + +# Playground.py +PlaygroundDeathAckMessage = TheCogs+" ont pris tous tes gags!\n\nTu es triste. Tu ne peux pas quitter le terrain de jeux avant d'avoir retrouvé la joie de vivre." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "Le contremaître de l'usine a été vaincu avant que tu ne le trouves. Tu n'as pas récupéré de pièces de Cogs." + +# DistributedFactory.py +HeadingToFactoryTitle = "En direction de %s..." +ForemanConfrontedMsg = "%s est en train de combattre le contremaître de l'usine!" + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "En attente d'autres joueurs..." +MinigamePleaseWait = "Patiente un peu..." +DefaultMinigameTitle = "Nom du mini jeu" +DefaultMinigameInstructions = "Instructions du mini jeu" +HeadingToMinigameTitle = "En direction de %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Témoin de puissance" +MinigamePowerMeterTooSlow = "Trop\nlent" +MinigamePowerMeterTooFast = "Trop\nrapide" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Modèle de mini jeu" +MinigameTemplateInstructions = "C'est un modèle de mini jeu. Utilise-le pour créer des mini jeux." + +# DistributedCannonGame.py +CannonGameTitle = "Jeu du canon" +CannonGameInstructions = "Envoie ton Toon dans le château d'eau aussi vite que tu peux. Utilise les flèches du clavier ou la souris pour diriger le canon. Sois rapide et gagne une belle récompense pour tout le monde!" +CannonGameReward = "RÉCOMPENSE" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Tir à la corde" +TugOfWarInstructions = "Appuie alternativement sur les flèches gauche et droite à la vitesse qu'il faut pour aligner la barre verte avec la ligne rouge. N'appuie pas trop rapidement ou trop lentement, tu pourrais finir dans l'eau!" +TugOfWarGameGo = "PARTEZ!" +TugOfWarGameReady = "Prêt..." +TugOfWarGameEnd = "Bien joué!" +TugOfWarGameTie = "Égalité!" +TugOfWarPowerMeter = "Témoin de puissance" + +# DistributedPatternGame.py +PatternGameTitle = "Imite %s" % Minnie +PatternGameInstructions = Minnie +" va te montrer une suite de pas de danse. " + \ + "Essaie de reproduire la danse de "+Minnie+" comme tu la vois en utilisant les flèches!" +PatternGameWatch = "Regarde ces pas de danse..." +PatternGameGo = "PARTEZ!" +PatternGameRight = "Bien, %s!" +PatternGameWrong = "Aïe!" +PatternGamePerfect = "C'était parfait, %s!" +PatternGameBye = "Merci d'avoir joué!" +PatternGameWaitingOtherPlayers = "En attente d'autres joueurs..." +PatternGamePleaseWait = "Patiente un peu..." +PatternGameFaster = "Tu as été\nplus rapide!" +PatternGameFastest = "Tu as été\nle(la) plus rapide!" +PatternGameYouCanDoIt = "Allez!\nTu peux y arriver!" +PatternGameOtherFaster = "\na été plus rapide!" +PatternGameOtherFastest = "\na été le(la) plus rapide!" +PatternGameGreatJob = "Bon travail!" +PatternGameRound = "Partie %s!" # Round 1! Round 2! .. + +# DistributedRaceGame.py +RaceGameTitle = "Jeu de l'oie" +RaceGameInstructions = "Clique sur un nombre. Choisis bien! Tu n'avances que si personne d'autre n'a choisi le même nombre." +RaceGameWaitingChoices = "Attente du choix des autres joueurs..." +RaceGameCardText = "%(name)s tire : %(reward)s" +RaceGameCardTextBeans = "%(name)s reçoit : %(reward)s" +RaceGameCardTextHi1 = "%(name)s est un Toon fabuleux!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " avance d'une case" +RaceGameForwardTwoSpaces = " avance de 2 cases" +RaceGameForwardThreeSpaces = " avance de 3 cases" +RaceGameBackOneSpace = " recule d'une case" +RaceGameBackTwoSpaces = " recule de 2 cases" +RaceGameBackThreeSpaces = " recule de 3 cases" +RaceGameOthersForwardThree = " tous les autres avancent\nde 3 cases" +RaceGameOthersBackThree = "tous les autres reculent\nde 3 cases" +RaceGameInstantWinner = "Gagnant!" +RaceGameJellybeans2 = "2 bonbons" +RaceGameJellybeans4 = "4 bonbons" +RaceGameJellybeans10 = "10 bonbons!" + +# DistributedRingGame.py +RingGameTitle = "Jeu des anneaux" +# color +RingGameInstructionsSinglePlayer = "Essaie de nager en passant dans autant d'anneaux %s que tu pourras. Utilise les flèches pour nager." +# color +RingGameInstructionsMultiPlayer = "Essaie de nager en passant dans les anneaux %s. Les autres joueurs essaieront de passer les anneaux des autres couleurs. Utilise les flèches pour nager." +RingGameMissed = "RATÉ" +RingGameGroupPerfect = "GROUPE\nPARFAIT!!" +RingGamePerfect = "PARFAIT!" +RingGameGroupBonus = "BONUS DE GROUPE" + +# RingGameGlobals.py +ColorRed = "rouges" +ColorGreen = "verts" +ColorOrange = "orange" +ColorPurple = "violets" +ColorWhite = "blancs" +ColorBlack = "noirs" +ColorYellow = "jaunes" + +# DistributedTagGame.py +TagGameTitle = "Jeu du chat" +TagGameInstructions = "Récupère les trésors. Tu ne peux pas récupérer les trésors quand tu es chat!" +TagGameYouAreIt = "Tu es chat!" +TagGameSomeoneElseIsIt = "%s est chat!" + +# DistributedMazeGame.py +MazeGameTitle = "Jeu du labyrinthe" +MazeGameInstructions = "Récupère les trésors. Essaie de les avoir tous, mais fais attention aux " + Cogs + "!" + +# DistributedCatchGame.py +CatchGameTitle = "Jeu du verger" +CatchGameInstructions = "Attrape des %(fruit)s, autant que tu peux. Attention aux " + Cogs + ", et essaie de ne pas attraper des %(badThing)s!" +CatchGamePerfect = "PARFAIT!" +CatchGameApples = 'pommes' +CatchGameOranges = 'oranges' +CatchGamePears = 'poires' +CatchGameCoconuts = 'noix de coco' +CatchGameWatermelons = 'pastèques' +CatchGamePineapples = 'ananas' +CatchGameAnvils = 'enclumes' + +# DistributedPieTossGame.py +PieTossGameTitle = "Jeu du lancer de tartes" +PieTossGameInstructions = "Envoie des tartes dans les cibles." + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JOUER" + +# Purchase.py +GagShopName = "La boutique à gags de Dingo" +GagShopPlayAgain = "REJOUER\n" +GagShopBackToPlayground = "RETOUR AU\nTERRAIN DE JEUX" +GagShopYouHave = "Tu as %s bonbons à dépenser" +GagShopYouHaveOne = "Tu as 1 bonbon à dépenser" +GagShopTooManyProps = "Désolé, tu as trop d'accessoires" +GagShopDoneShopping = "ACHATS\n TERMINÉS" +# name of a gag +GagShopTooManyOfThatGag = "Désolé, tu as déjà assez de %s" +GagShopInsufficientSkill = "Tu n'es pas encore assez habile pour cela" +# name of a gag +GagShopYouPurchased = "Tu as acheté %s" +GagShopOutOfJellybeans = "Désolé, tu n'as plus de bonbons!" +GagShopWaitingOtherPlayers = "En attente des autres joueurs..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s est déconnecté(e)" +GagShopPlayerExited = "%s est parti(e)" +GagShopPlayerPlayAgain = "Jouer encore" +GagShopPlayerBuying = "Achat en cours" + +# MakeAToon.py +# +# The voices for GenderShopQuestionMickey and Minnie should not be played simultaneously. +# Options are as follows: +# 1: Mickey first and Minnie follow in a few second. +# 2: When player moves cursor onto the character, the voice to be played. +# But the voice shouldn't be played while other character is talking. +# Please choose whichever feasible. +# +GenderShopQuestionMickey = "Pour faire un garçon Toon, clique ici!" +GenderShopQuestionMinnie = "Pour faire une fille Toon, clique ici!" +GenderShopFollow = "Suis-moi!" +GenderShopSeeYou = "À plus tard!" +GenderShopBoyButtonText = "Garçon" +GenderShopGirlButtonText = "Fille" + +# BodyShop.py +BodyShopHead = "Tête" +BodyShopBody = "Corps" +BodyShopLegs = "Jambes" + +# ColorShop.py +ColorShopHead = "Tête" +ColorShopBody = "Corps" +ColorShopLegs = "Jambes" +ColorShopToon = "Toon" +ColorShopParts = "Parties" +ColorShopAll = "Tout" + +# ClothesShop.py +ClothesShopShorts = "Short" +ClothesShopShirt = "Chemise" +ClothesShopBottoms = "Bas" + +MakeAToonDone = "Fini" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Retour" +CreateYourToon = "Clique sur les flèches pour créer ton Toon." +CreateYourToonTitle = "Crée ton Toon" +CreateYourToonHead = "Clique sur les flèches \" tête \" pour choisir différents animaux." +MakeAToonClickForNextScreen = "Clique sur la flèche ci-dessous pour aller à l'écran suivant." +PickClothes = "Clique sur les flèches pour choisir des vêtements!" +PickClothesTitle = "Choisis tes vêtements" +PaintYourToon = "Clique sur les flèches pour peindre ton Toon!" +PaintYourToonTitle = "Peins ton Toon" +MakeAToonYouCanGoBack = "Tu peux aussi retourner en arrière pour changer ton corps!" +MakeAFunnyName = "Choisis un nom amusant pour ton Toon avec le jeu Choisis un nom!" +MustHaveAFirstOrLast1 = "Ton Toon devrait avoir un prénom ou un nom de famille, tu ne penses pas?" +MustHaveAFirstOrLast2 = "Tu ne veux pas que ton Toon ait de prénom ou de nom de famille?" +ApprovalForName1 = "C'est ça, ton Toon mérite un super nom!" +ApprovalForName2 = "Les noms Toon sont les meilleurs noms!" +MakeAToonLastStep = "Dernière étape avant d'aller à Toontown!" +PickANameYouLike = "Choisis un nom que tu aimes!" +NameToonTitle = "Donne un nom à ton Toon" +TitleCheckBox = "Titre" +FirstCheckBox = "Prénom" +LastCheckBox = "Nom" +RandomButton = "Aléatoire" +NameShopSubmitButton = "Envoyer" +TypeANameButton = "Entre un nom" +TypeAName = "Tu n'aimes pas ces noms?\nClique ici -->" +PickAName = "Essaie le jeu Choisis-un-nom!\nClique ici -->" +PickANameButton = "Choisis-un-nom" +RejectNameText = "Ce nom n'est pas autorisé. Essaie encore." +WaitingForNameSubmission = "Envoi de ton nom..." + +# NameShop.py +NameShopNameMaster = "NameMasterFrench.txt" +NameShopPay = "Inscris-toi!" +NameShopPlay = "Essai gratuit" +NameShopOnlyPaid = "Seuls les utilisateurs payants\npeuvent donner un nom à leurs Toons.\nJusqu'à ce que tu t'inscrives,\nton nom sera\n" +NameShopContinueSubmission = "Continuer l'envoi" +NameShopChooseAnother = "Choisir un autre nom" +NameShopToonCouncil = "Le Conseil de Toontown\nva examiner ton\nnom. "+ \ + "L'examen peut\nprendre quelques jours.\nPendant que tu attends,\nton nom sera\n " +PleaseTypeName = "Entre ton nom :" +AllNewNames = "Tous les noms\ndoivent être approuvés\npar le Conseil de Toontown." +NameShopNameRejected = "Le nom que tu as\nenvoyé a été refusé." +NameShopNameAccepted = "Félicitations!\nLe nom que tu as\nenvoyé a\nété accepté!" +NoPunctuation = "Tu ne peux pas utiliser de signes de ponctuation dans ton nom!" +PeriodOnlyAfterLetter = "Tu peux utiliser un point dans ton nom, mais seulement après une lettre." +ApostropheOnlyAfterLetter = "Tu peux utiliser une apostrophe dans ton nom, mais seulement après une lettre." +NoNumbersInTheMiddle = "Les caractères numériques ne peuvent pas apparaître au milieu d'un mot." +ThreeWordsOrLess = "Ton nom doit comporter trois mots maximum." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "donald duck", + "donaldduck", + "pluto", + "dingo", + ) +NumToColor = ['Blanc', 'Pêche', 'Rouge vif', 'Rouge', 'Bordeaux', + 'Terre de Sienne', 'Brun', 'Brun clair', 'Corail', 'Orange', + 'Jaune', 'Crème', 'Jaune-vert', 'Citron vert', 'Vert marin', + 'Vert', 'Bleu clair', 'Turquoise', 'Bleu', + 'Pervenche', 'Bleu roi', 'Bleu ardoise', 'Violet', + 'Lavande', 'Rose'] +AnimalToSpecies = { + 'dog' : 'Chien', + 'cat' : 'Chat', + 'mouse' : 'Souris', + 'horse' : 'Cheval', + 'rabbit' : 'Lapin', + 'duck' : 'Canard', + 'fowl' : 'Canard', + } +NameTooLong = "Ce nom est trop long. Essaie encore." +ToonAlreadyExists = "Tu as déjà un Toon qui s'appelle %s!" +NameAlreadyInUse = "Ce nom est déjà utilisé!" +EmptyNameError = "Tu dois indiquer un nom d'abord." +NameError = "Désolé. Ce nom ne pourra pas convenir." + +# NameCheck.py +NCTooShort = 'Ce nom est trop court.' +NCNoDigits = 'Ton nom ne peut pas contenir de chiffres.' +NCNeedLetters = 'Chaque mot de ton nom doit contenir des lettres.' +NCNeedVowels = 'Chaque mot de ton nom doit contenir des voyelles.' +NCAllCaps = 'Ton nom ne peut pas être entièrement en majuscules.' +NCMixedCase = 'Ton nom a trop de majuscules.' +NCBadCharacter = "Ton nom ne peut pas contenir le caractère \" %s \"" +NCGeneric = 'Désolé, ce nom ne pourra pas convenir.' +NCTooManyWords = 'Ton nom ne peut pas comporter plus de quatre mots.' +NCDashUsage = ("Les tirets ne peuvent être utilisés que pour relier deux mots ensemble." + "(comme dans \" Bou-Bou \").") +NCCommaEdge = "Ton nom ne peut pas commencer ou se terminer par une virgule." +NCCommaAfterWord = "Tu ne peux pas commencer un mot par une virgule." +NCCommaUsage = ("Ce nom n'utilise pas les virgules correctement. Les virgules doivent " + "assembler deux mots, comme dans le nom \" Dr Couac, médecin \". " + "Les virgules doivent aussi être suivies d'un espace.") +NCPeriodUsage = ("Ce nom n'utilise pas les points correctement. Les points sont " + "seulement autorisés dans des mots tels que \" M. \", \" doct. \", \" prof. \", etc.") +NCApostrophes = "Ton nom a trop d'apostrophes." + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+" : "+TheCogs+" ont repris un des bâtiments que tu avais sauvés!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Tu as besoin de plus de temps pour réfléchir?' +STOREOWNER_GOODBYE = 'À plus tard!' +STOREOWNER_NEEDJELLYBEANS = 'Tu dois faire un tour de tramway pour avoir des bonbons.' +STOREOWNER_GREETING = 'Choisis ce que tu veux acheter.' +STOREOWNER_BROWSING = "Tu peux regarder, mais tu auras besoin d'un ticket d'habillement pour acheter." +STOREOWNER_NOCLOTHINGTICKET = "Tu as besoin d'un ticket d'habillement pour acheter des vêtements." +# translate +STOREOWNER_NOFISH = "Reviens ici pour vendre des poissons à l'animalerie en échange de bonbons." +STOREOWNER_THANKSFISH = "Merci! L'animalerie va les adorer. Au revoir!" + +STOREOWNER_NOROOM = "Hmm...tu devrais faire de la place dans ton placard avant d'acheter de nouveaux vêtements.\n" +STOREOWNER_CONFIRM_LOSS = "Ton placard est plein. Tu vas perdre les vêtements que tu portais." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Oh là là! Tu as trouvé %s sur %s poissons. Ça mérite un trophée et une rigol-augmentation!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": Une invasion de Cogs a commencé!!!" +SuitInvasionBegin2 = lToonHQ+": Les %s ont pris Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": L'invasion des %s est terminée!!!" +SuitInvasionEnd2 = lToonHQ+": Les Toons nous ont sauvés une fois de plus!!!" +SuitInvasionUpdate1 = lToonHQ+": L'invasion de Cogs en est à %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": Nous devons battre ces %s!!!" +SuitInvasionBulletin1 = lToonHQ+": Il y a une invasion de Cogs en cours!!!" +SuitInvasionBulletin2 = lToonHQ+": Les %s ont pris Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Armée de Toons" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown compte un nouveau citoyen! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMickey_2 = "Bien sûr, %s!" +QuestScriptTutorialMickey_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" +QuestScriptTutorialMickey_4 = "Viens ici! Utilise les flèches pour te déplacer." + +# These are needed to correspond to the Japanese gender specific phrases +# +QuestScriptTutorialMinnie_1 = "Toontown compte une nouvelle citoyenne! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMinnie_2 = "Bien sûr, %s!" +QuestScriptTutorialMinnie_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" +# + +# +# If there is "\a" between the sentence, we would like to have one of the following sequence. +# 1: display 1st text with 1st voice -> when voice finished, arrow appear. -> if player pushes the arrow button, display 2nd text with 2nd voice. +# 2: display 1st text with 1st voice and altomatically display 2nd text with 2nd voice. +# 3: display 1st text and play 1st voice (arrow is displayed) -> whenever player pushes the button, the voice will be skipped and display 2nd text with 2nd voice. +# Anyway, we need to have some "Skip" rule while playing the voice because from DCV(Disney Character Voice)'s view, it is not preferrable to have voice skipped. +# + +QuestScript101_1 = "Ce sont les COGS. Ce sont des robots qui essaient de prendre Toontown." +QuestScript101_2 = "Il y a différentes sortes de COGS et..." +QuestScript101_3 = "...ils transforment de bons bâtiments Toon..." +QuestScript101_4 = "...en affreuses bâtisses Cog!" +QuestScript101_5 = "Mais les COGS ne comprennent pas les blagues!" +QuestScript101_6 = "Un bon gag les arrête." +QuestScript101_7 = "Il y a des quantités de gags ; prends ceux-là pour commencer." +QuestScript101_8 = "Oh! Tu as aussi besoin d'un rigolmètre!" +QuestScript101_9 = "Si ton rigolmètre descend trop bas, tu seras triste!" +QuestScript101_10 = "Un Toon heureux est un Toon en bonne santé!" +QuestScript101_11 = "OH NON! Il y a un COG devant ma boutique!" +QuestScript101_12 = "AIDE-MOI, S'IL TE PLAÎT! Va vaincre ce COG!" +QuestScript101_13 = "Voilà ton premier défitoon!" +QuestScript101_14 = "Dépêche-toi! Va battre ce Laquaistic!" + +QuestScript110_1 = "Bon travail pour avoir vaincu ce Laquaistic. Je vais te donner un journal de bord..." +QuestScript110_2 = "Ce journal est plein de choses intéressantes." +QuestScript110_3 = "Ouvre-le, et je vais te montrer." +QuestScript110_4 = "La carte montre où tu as été." +QuestScript110_5 = "Tourne la page pour voir tes gags..." +QuestScript110_6 = "Oh oh! Tu n'as pas de gags! Je vais te donner un défi." +QuestScript110_7 = "Tourne la page pour voir tes défis." +QuestScript110_8 = "Fais un tour de tramway, et gagne des bonbons pour acheter des gags!" +QuestScript110_9 = "Pour aller jusqu'au tramway, sors par la porte qui est derrière moi et va jusqu'au terrain de jeux." +QuestScript110_10 = "Maintenant, ferme le livre et trouve le tramway!" +QuestScript110_11 = "Retourne au QG des Toons quand tu as fini. Au revoir!" + +QuestScriptTutorialBlocker_1 = "Bien le bonjour!" +QuestScriptTutorialBlocker_2 = "Bonjour?" +QuestScriptTutorialBlocker_3 = "Oh! Tu ne sais pas utiliser le Chat rapide!" +QuestScriptTutorialBlocker_4 = "Clique sur le bouton pour dire quelque chose." +QuestScriptTutorialBlocker_5 = "Très bien!\aLà où tu vas, il y a plein de Toons à qui parler." +QuestScriptTutorialBlocker_6 = "Si tu veux chatter avec tes amis à l'aide du clavier, tu peux utiliser un autre bouton. " +QuestScriptTutorialBlocker_7 = "Ça s'appelle le bouton \" Chat \". Tu dois être officiellement citoyen de Toontown pour l'utiliser." +QuestScriptTutorialBlocker_8 = "Bonne chance! À plus tard!" + +QuestScript120_1 = "Bien, tu as trouvé le tramway!\aAu fait, as-tu rencontré Bob le Banquier?\aIl aime bien les sucreries.\aPourquoi n'irais-tu pas te présenter en lui emportant ce sucre d'orge comme cadeau?" +QuestScript120_2 = "Bob le Banquier est dans la banque de Toontown." + +QuestScript121_1 = "Miam, merci pour ce sucre d'orge.\aDis donc, si tu peux m'aider, je te donnerai une récompense.\aCes Cogs ont volé les clés de mon coffre. Va battre des Cogs pour trouver une clé volée.\aQuand tu auras trouvé une clé, ramène-la moi." + +QuestScript130_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai reçu un paquet pour le Professeur Pete aujourd'hui.\aÇa doit être la nouvelle craie qu'il a commandée.\aPeux-tu lui apporter s'il te plaît?\aIl est dans l'école." + +QuestScript131_1 = "Oh, merci pour la craie.\aQuoi?!?\aCes Cogs ont volé mon tableau. Va vaincre des Cogs pour retrouver le tableau qu'ils m'ont volé.\aQuand tu l'auras trouvé, ramène-le moi." + +QuestScript140_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai un ami, Larry le Libraire, qui est un rat de bibliothèque.\aJ'ai pris ce livre pour lui la dernière fois que j'ai été aux "+lDonaldsDockNC+".\aPourrais-tu lui apporter? Il est à la bibliothèque, d'habitude." + +QuestScript141_1 = "Oh, oui, ce livre complète presque ma collection.\aVoyons ça...\aAh, oh...\aMais où est-ce que j'ai mis mes lunettes?\aJe les avais juste avant que ces Cogs ne prennent mon bâtiment.\aVa vaincre des Cogs pour retrouver les lunettes qu'ils m'ont volées.\aQuand tu les auras retrouvées, reviens me voir pour avoir une récompense." + +QuestScript150_1 = "Oh... le prochain défi pourrait être trop difficile pour que tu le fasses tout(e) seul(e)!" +QuestScript150_2 = "Pour te faire des amis, trouve un autre joueur et utilise le bouton Nouvel ami." +QuestScript150_3 = "Une fois que tu t'es fait un(e) ami(e), reviens ici." +QuestScript150_4 = "Certains défis sont trop difficiles pour un Toon seul!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +BossCogName = "Premier vice-président" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade de %s. Félicitations!" +BossCogDoobersAway = { 's' : "Va! Et réalise cette vente!" } +BossCogWelcomeToons = "Bienvenue aux nouveaux Cogs!" +BossCogPromoteToons = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade de %s. Félici--" +CagedToonInterruptBoss = "Hé! Hou! Hé là bas!" +CagedToonRescueQuery = "Alors les Toons, vous êtes venus me sauver?" +BossCogDiscoverToons = "Eh? Des Toons! Déguisés!" +BossCogAttackToons = "À l'attaque!!" +CagedToonDrop = [ + "Bon travail! Tu l'épuises!", + "Ne le lâchez pas! Il va s'enfuir!", + "Vous êtes super les copains!", + "Fantastique! Vous l'avez presque maintenant!", + ] +CagedToonPrepareBattleTwo = "Attention, il essaie de s'enfuir!\aAidez-moi, tout le monde - montez jusque là et arrêtez-le!" +CagedToonPrepareBattleThree = "Youpi, je suis presque libre!\aMaintenant vous devez attaquer le vice-président des Cogs directement.\aJ'ai tout un lot de tartes que vous pouvez utiliser!\aSautez en l'air et touchez le fond de ma cage, je vous donnerai des tartes.\aAppuyez sur la touche \" Inser \" pour lancer les tartes une fois que vous les avez!" +BossBattleNeedMorePies = "Vous avez besoin de plus de tartes!" +BossBattleHowToGetPies = "Sautez en l'air pour toucher la cage et avoir des tartes." +BossBattleHowToThrowPies = "Appuyez sur la touche \" Inser \" pour lancer les tartes!" +CagedToonYippee = "Génial!" +CagedToonThankYou = "C'est super d'être libre!\aMerci pour toute votre aide!\aJe suis à votre service.\aSi jamais vous avez besoin d'aide pour un combat, vous pouvez m'appeler!\aCliquez simplement sur le bouton SOS pour m'appeler." +CagedToonPromotion = "\aDis donc - ce vice-président Cog a laissé derrière lui les papiers de ta promotion.\aJe vais les envoyer pour toi en sortant, pour que tu aies ta promotion!" +CagedToonLastPromotion = "\aWaou, tu as atteint le niveau %s sur ton costume de Cog!\aLes Cogs ne montent pas en grade plus haut que ça.\aTu ne peux plus monter ton costume de Cog en grade, mais tu peux évidemment continuer à sauver des Toons!" +CagedToonHPBoost = "\aTu as sauvé beaucoup de Toons dans ce QG.\aLe Conseil de Toontown a décidé de te donner un autre rigolpoint. Félicitations!" +CagedToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aDe la part du Conseil de Toontown, merci d'être revenu(e) sauver encore plus de Toons!" +CagedToonGoodbye = "À la prochaine!" + + +CagedToonBattleThree = { + 10: "Joli saut, %(toon)s. Voilà quelques tartes!", + 11: "Salut, %(toon)s! Prenez des tartes!", + 12: "Hé là,%(toon)s! Vous avez des tartes maintenant!", + + 20: "Hé, %(toon)s! Sautez jusqu'à ma cage et prenez des tartes à lancer!", + 21: "Hé, %(toon)s! Utilisez la touche \" Ctrl \" pour sauter et toucher ma cage!", + + 100: "Appuyez sur la touche \" Inser \" pour lancer une tarte!", + 101: "Le compteur bleu montre à quelle hauteur ta tarte va monter.", + 102: "Essaie d'abord de lancer une tarte sous son châssis pour bousiller son mécanisme.", + 103: "Attends que la porte s'ouvre, et lance une tarte à l'intérieur.", + 104: "Lorsqu'il est étourdi, frappe-le au visage ou au torse pour le renverser!", + 105: "Tu sauras que tu l'as frappé comme il faut quand tu verras une tache de couleur.", + 106: "Si tu frappes un Toon avec une tarte, cela donne à ce Toon un rigolpoint!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +BossElevatorRejectMessage = "Tu ne peux pas monter dans cet ascenseur avant d'avoir gagné une promotion." + +# Types of catalog items +FurnitureTypeName = "Meuble" +PaintingTypeName = "Tableau" +ClothingTypeName = "Vêtement" +ChatTypeName = "Phrase de Chat rapide" +EmoteTypeName = "Leçons de comédie" +PoleTypeName = "Canne à pêche" +WindowViewTypeName = "Vue de la fenêtre" + +FurnitureYourOldCloset = "ton ancienne armoire" +FurnitureYourOldBank = "ton ancienne tirelire" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py +FurnitureNames = { + 100 : "Fauteuil", + 110 : "Chaise", + 120 : "Chaise de bureau", + 130 : "Chaise en rondins", + 140 : "Chaise homard", + 145 : "Chaise de survie", + 150 : "Tabouret selle", + 160 : "Chaise locale", + 170 : "Chaise gâteau", + 200 : "Lit", + 210 : "Lit", + 220 : "Lit baignoire", + 230 : "Lit feuille", + 240 : "Lit bateau", + 250 : "Hamac cactus", + 260 : "Lit crème glacée", + 300 : "Piano mécanique", + 310 : "Orgue", + 400 : "Cheminée", + 410 : "Cheminée", + 420 : "Cheminée ronde", + 430 : "Cheminée", + 440 : "Cheminée pomme", + 500 : "Armoire", + 502 : "Armoire pour 15 vêtements", + 510 : "Armoire", + 512 : "Armoire pour 15 vêtements", + 600 : "Petite lampe", + 610 : "Lampe haute", + 620 : "Lampe de table", + 630 : "Lampe Daisy", + 640 : "Lampe Daisy", + 650 : "Lampe méduse", + 660 : "Lampe méduse", + 670 : "Lampe cow-boy", + 700 : "Chaise capitonnée", + 710 : "Divan", + 715 : "Divan", + 720 : "Divan foin", + 730 : "Divan sablé", + 800 : "Bureau", + 810 : "Bureau en rondins", + 900 : "Porte-parapluie", + 910 : "Portemanteau", + 920 : "Poubelle", + 930 : "Champignon rouge", + 940 : "Champignon jaune", + 950 : "Portemanteau", + 960 : "Étal onneau", + 970 : "Cactus", + 980 : "Tipi", + 1000 : "Grand tapis", + 1010 : "Tapis rond", + 1020 : "Petit tapis", + 1030 : "Paillasson", + 1100 : "Vitrine", + 1110 : "Vitrine", + 1120 : "Bibliothèque haute", + 1130 : "Bibliothèque basse", + 1140 : "Coffre Sundae", + 1200 : "Table d'appui", + 1210 : "Petite table", + 1220 : "Table de salon", + 1230 : "Table de salon", + 1240 : "Table de plongeur", + 1250 : "Table cookie", + 1260 : "Table de chevet", + 1300 : "Tirelire de 1 000 bonbons", + 1310 : "Tirelire de 2 500 bonbons", + 1320 : "Tirelire de 5 000 bonbons", + 1330 : "Tirelire de 7 500 bonbons", + 1340 : "Tirelire de 10 000 bonbons", + 1399 : "Téléphone", + 1400 : "Toon Cézanne", + 1410 : "Fleurs", + 1420 : "Mickey contemporain", + 1430 : "Toon Rembrandt", + 1440 : "Paysage Toon", + 1441 : "Cheval de Whistler", + 1442 : "Étoile Toon", + 1443 : "Pas une tarte", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Télévision", + 1600 : "Vase bas", + 1610 : "Vase haut", + 1620 : "Vase bas", + 1630 : "Vase haut", + 1640 : "Vase bas", + 1650 : "Vase bas", + 1660 : "Vase corail", + 1661 : "Vase coquillage", + 1700 : "Chariot de pop-corn", + 1710 : "Coccinelle", + 1720 : "Fontaine", + 1725 : "Machine à laver", + 1800 : "Aquarium", + 1810 : "Aquarium", + 1900 : "Poisson-scie", + 1910 : "Requin-marteau", + 1920 : "Cornes porte-manteau", + 1930 : "Sombrero classique", + 1940 : "Sombrero fantaisie", + 1950 : "Attrapeur de rêves", + 1960 : "Fer à cheval", + 1970 : "Portrait de bison", + 2000 : "Balançoire bonbon", + 2010 : "Toboggan gâteau", + 3000 : "Baignoire Banana Split", + 10000 : "Petite citrouille", + 10010 : "Grande citrouille", + } + +# CatalogClothingItem.py +ClothingArticleNames = ( + "Chemise", + "Chemise", + "Chemise", + "Short", + "Short", + "Jupe", + "Short", + ) + +ClothingTypeNames = { + 1400 : "Chemise de Mathieu", + 1401 : "Chemise de Jessica", + 1402 : "Chemise de Marissa", + } + +# CatalogSurfaceItem.py +SurfaceNames = ( + "Papier peint", + "Moulures", + "Revêtement de sol", + "Lambris", + "Bordure", + ) + +WallpaperNames = { + 1000 : "Parchemin", + 1100 : "Milan", + 1200 : "Douvres", + 1300 : "Victoria", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Arlequin", + 1700 : "Lune", + 1800 : "Étoiles", + 1900 : "Fleurs", + 2000 : "Jardin de printemps", + 2100 : "Jardin classique", + 2200 : "Jour de course", + 2300 : "Marqué!", + 2400 : "Nuage 9", + 2500 : "Vigne vierge", + 2600 : "Printemps", + 2700 : "Kokeshi", + 2800 : "Petits bouquets", + 2900 : "Poisson ange", + 3000 : "Bulles", + 3100 : "Bulles", + 3200 : "À la pêche", + 3300 : "Poisson stop", + 3400 : "Hippocampe", + 3500 : "Coquillages", + 3600 : "Sous l'eau", + 3700 : "Bottes", + 3800 : "Cactus", + 3900 : "Chapeau de cow-boy", + 10100 : "Chats", + 10200 : "Chauve-souris", + 11000 : "Flocons de neige", + 11100 : "Houx", + 11200 : "Bonhomme de neige", + 13000 : "Trèfle", + 13100 : "Trèfle", + 13200 : "Arc-en-ciel", + 13300 : "Trèfle", + } + +FlooringNames = { + 1000 : "Parquet", + 1010 : "Moquette", + 1020 : "Carrelage losange", + 1030 : "Carrelage losange", + 1040 : "Pelouse", + 1050 : "Briques beiges", + 1060 : "Briques rouges", + 1070 : "Carrelage carré", + 1080 : "Pierre", + 1090 : "Bois", + 1100 : "Terre", + 1110 : "Pavage de bois", + 1120 : "Carrelage", + 1130 : "Nid d'abeilles", + 1140 : "Eau", + 1150 : "Carrelage plage", + 1160 : "Carrelage plage", + 1170 : "Carrelage plage", + 1180 : "Carrelage plage", + 1190 : "Sable", + 10000 : "Glaçon", + 10010 : "Igloo", + 11000 : "Trèfle", + 11010 : "Trèfle", + } + +MouldingNames = { + 1000 : "Noueux", + 1010 : "Peint", + 1020 : "Denté", + 1030 : "Fleurs", + 1040 : "Fleurs", + 1050 : "Coccinelle", + } + +WainscotingNames = { + 1000 : "Peint", + 1010 : "Panneau de bois", + 1020 : "Bois", + } + +# CatalogWindowItem.py +WindowViewNames = { + 10 : "Grand jardin", + 20 : "Jardin sauvage", + 30 : "Jardin grec", + 40 : "Paysage urbain", + 50 : "Far West", + 60 : "Sous l'océan", + 70 : "Île tropicale", + 80 : "Nuit étoilée", + 90 : "Lagon Tiki", + 100 : "Frontière gelée", + 110 : "Pays fermier", + 120 : "Camp local", + 130 : "Grand rue", + } + +NewCatalogNotify = "De nouveaux articles sont prêts à être commandés par téléphone!" +NewDeliveryNotify = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyFirstCatalog = "Ton premier catalogue est arrivé! Tu peux l'utiliser pour commander de nouveaux objets pour toi ou pour ta maison." +CatalogNotifyNewCatalog = "Ton catalogue N°%s est arrivé! Tu peux utiliser ton téléphone pour commander des articles dans le catalogue de Clarabelle." +CatalogNotifyNewCatalogNewDelivery = "Un colis t'attend dans ta boîte aux lettres! Ton catalogue N°%s est aussi arrivé!" +CatalogNotifyNewDelivery = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyNewCatalogOldDelivery = "Ton catalogue N°%s est arrivé, et des objets t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyOldDelivery = "Des articles t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyInstructions = "Clique sur le bouton \" Retour à la maison \" sur la carte de ton journal de bord, puis va jusqu'au téléphone qui est dans ta maison." +CatalogNewDeliveryButton = "Nouvelle\nlivraison!" +CatalogNewCatalogButton = "Nouveau\ncatalogue" + +DistributedMailboxEmpty = "Ta boîte aux lettres est vide pour l'instant. Reviens ici chercher les articles que tu as commandés par téléphone quand ils seront livrés!" +DistributedMailboxWaiting = "Ta boîte aux lettres est vide pour l'instant, mais le paquet que tu as commandé est en chemin. Reviens voir plus tard!" +DistributedMailboxReady = "Ta commande est arrivée!" +DistributedMailboxNotOwner = "Désolé, ce n'est pas ta boîte aux lettres." +DistributedPhoneEmpty = "Tu peux utiliser n'importe quel téléphone pour commander des articles pour toi et pour ta maison. De nouveaux articles seront proposés dans l'avenir.\n\nAucun article n'est disponible à la commande maintenant, mais reviens voir plus tard!" + +Clarabelle = "Clarabelle" +MailboxExitButton = "Fermer la boîte aux lettres" +MailboxAcceptButton = "Prends cet objet" +MailboxOneItem = "Ta boîte aux lettres contient 1 objet." +MailboxNumberOfItems = "Ta boîte aux lettres contient %s objets." +MailboxGettingItem = "Récupération de %s dans la boîte aux lettres." +MailboxItemNext = "Objet\nsuivant" +MailboxItemPrev = "Objet\nprécédent" +CatalogCurrency = "bonbons" +CatalogHangUp = "Raccrocher" +CatalogNew = "NOUVEAUTÉ" +CatalogBackorder = "PRÉ-COMMANDE" +CatalogPagePrefix = "Page" +CatalogGreeting = "Bonjour! Merci d'avoir appelé le catalogue de Clarabelle. Que puis-je pour toi?" +CatalogGoodbyeList = ["Au revoir!", + "Rappelle bientôt!", + "Merci de ton appel!", + "OK, au revoir!", + "Au revoir!", + ] +CatalogHelpText1 = "Tourne la page pour voir les articles qui sont en vente." +CatalogSeriesLabel = "Série %s" +CatalogPurchaseItemAvailable = "Félicitations pour ton nouvel achat! Tu peux l'utiliser immédiatement." +CatalogPurchaseItemOnOrder = "Félicitations! Ton achat sera bientôt livré dans ta boîte aux lettres." +CatalogAnythingElse = "Puis-je autre chose pour toi?" +CatalogPurchaseClosetFull = "Ton placard est plein. Tu peux acheter cet article, mais tu devras supprimer quelque chose de ton placard pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article?" +CatalogAcceptClosetFull = "Ton placard est plein. Tu dois rentrer et supprimer quelque chose de ton placard pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptShirt = "Tu portes maintenant ta nouvelle chemise. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptShorts = "Tu portes maintenant ton nouveau short. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptSkirt = "Tu portes maintenant ta nouvelle jupe. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptPole = "Tu peux maintenant attraper des poissons plus gros avec ta nouvelle canne!" +CatalogAcceptPoleUnneeded = "Tu as déjà une canne meilleure que celle-ci!" +CatalogPurchaseHouseFull = "Ta maison est pleine. Tu peux acheter cet article, mais tu devras supprimer quelque chose dans ta maison pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article?" +CatalogAcceptHouseFull = "Ta maison est pleine. Tu dois rentrer et supprimer quelque chose dans ta maison pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptInAttic = "Ton nouvel article est maintenant dans ton grenier. Pour le placer dans ta maison, va à l'intérieur et clique sur le bouton \" Déplacer les meubles \"." +CatalogAcceptInAtticP = "Tes nouveaux articles sont maintenant dans ton grenier. Pour les placer dans ta maison, va à l'intérieur et clique sur le bouton \" Déplacer les meubles \"." +CatalogPurchaseMailboxFull = "Ta boîte aux lettres est pleine! Tu ne peux pas acheter cet article avant d'avoir sorti des articles de ta boîte aux lettres pour y faire de la place." +CatalogPurchaseOnOrderListFull = "Tu as trop d'articles en commande actuellement. Tu ne peux pas commander d'autres articles avant que ceux que tu as déjà commandés ne soient arrivés." +CatalogPurchaseGeneralError = "L'article n'a pas pu être acheté à cause d'une erreur interne au jeu : code d'erreur %s." +CatalogAcceptGeneralError = "L'article n'a pas pu être retiré de ta boîte aux lettres à cause d'une erreur interne au jeu : code d'erreur %s." + +HDMoveFurnitureButton = "Déplacer\nles meubles" +HDStopMoveFurnitureButton = "Meubles\nplacés" +HDAtticPickerLabel = "Dans le grenier" +HDInRoomPickerLabel = "Dans la pièce" +HDInTrashPickerLabel = "À la poubelle" +HDDeletePickerLabel = "Supprimer?" +HDInAtticLabel = "Grenier" +HDInRoomLabel = "Pièce" +HDInTrashLabel = "Poubelle" +HDToAtticLabel = "Mettre\nau grenier" +HDMoveLabel = "Déplacer" +HDRotateCWLabel = "Tourner vers la droite" +HDRotateCCWLabel = "Tourner vers la gauche" +HDReturnVerify = "Remettre cet objet dans le grenier?" +HDReturnFromTrashVerify = "Ressortir cet objet de la poubelle et le mettre dans le grenier?" +HDDeleteItem = "Clique sur OK pour mettre cet objet à la poubelle ou sur Annuler pour le garder." +HDNonDeletableItem = "Tu ne peux pas supprimer les objets de ce type!" +HDNonDeletableBank = "Tu ne peux pas supprimer ta tirelire!" +HDNonDeletableCloset = "Tu ne peux pas supprimer ton armoire!" +HDNonDeletablePhone = "Tu ne peux pas supprimer ton téléphone!" +HDNonDeletableNotOwner = "Tu ne peux pas supprimer les affaires de %s!" +HDHouseFull = "Ta maison est pleine. Tu dois supprimer quelque chose d'autre dans ta maison ou de ton grenier avant de pouvoir ressortir cet article de la poubelle." + +HDHelpDict = { + "DoneMoving" : "Terminer la décoration de la pièce.", + "Attic" : "Voir la liste des objets qui sont au grenier. Les objets qui ne sont pas dans ta pièce sont au grenier.", + "Room" : "Voir la liste des objets qui sont dans la pièce. Utile pour retrouver des objets perdus.", + "Trash" : "Voir les objets qui sont dans la poubelle. Les objets les plus anciens sont supprimés après un temps ou si la poubelle déborde.", + "ZoomIn" : "Agrandir la vue de la pièce.", + "ZoomOut" : "Éloigner la vue de la pièce.", + "SendToAttic" : "Stocker le meuble actuel dans le grenier.", + "RotateLeft" : "Tourner vers la gauche.", + "RotateRight" : "Tourner vers la droite.", + "DeleteEnter" : "Passer en mode suppression.", + "DeleteExit" : "Sortir du mode suppression.", + "FurnitureItemPanelDelete" : "Mettre %s à la poubelle.", + "FurnitureItemPanelAttic" : "Mettre %s dans la pièce.", + "FurnitureItemPanelRoom" : "Remettre %s au grenier.", + "FurnitureItemPanelTrash" : "Remettre %s au grenier.", + } + + + +MessagePickerTitle = "Tu as trop de phrases. Pour pouvoir acheter\n\"%s\"\n tu dois choisir une chose à retirer :" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Es-tu certain de vouloir retirer \"%s\" de ton menu de Chat rapide?" + + +CatalogBuyText = "Acheter" +CatalogOnOrderText = "En commande" +CatalogPurchasedText = "Déjà\nacheté" +CatalogPurchasedMaxText = "Maximum\ndéjà acheté" +CatalogVerifyPurchase = "Acheter %(item)s pour %(price)s bonbons?" +CatalogOnlyOnePurchase = "Tu ne peux avoir qu'un de ces articles à la fois. Si tu achètes celui-là, il remplacera %(old)s.\n\nEs-tu certain(e) de vouloir acheter %(item)s pour %(price)s bonbons?" + +CatalogExitButtonText = "Raccrocher" +CatalogCurrentButtonText = "Articles actuels" +CatalogPastButtonText = "Articles précédents" + +TutorialHQOfficerName = "Harry du QG" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tom Tuteur", + 999 : "Toon Tailleur", + 1000 : lToonHQ, + 20001 : Flippy, + + # Toontown Central Fisherman + # Toontown Central + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Bob le Banquier", + 2003 : "Professeur Pete", + 2004 : "Tammy le Tailleur", + 2005 : "Larry le Libraire", + 2006 : "Vincent - Vendeur", + 2011 : "Véronique - Vendeuse", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Vendeur de l'animalerie", + + # Silly Street + 2101 : "Daniel le Dentiste", + 2102 : "Sherry le Shérif", + 2103 : "Kitty Lerhume", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canary Minederien", + 2109 : "Souffle douleur", + 2110 : "A. Fiche", + 2111 : "Diego Ladanse", + 2112 : "Dr Tom", + 2113 : "Rollo le Magnifique", + 2114 : "Rose Dévent", + 2115 : "Dédé Coupage", + 2116 : "Costaud McDougal", + 2117 : "Madame Putride", + 2118 : "Jesse Jememoque", + 2119 : "Meryl Semarre", + 2120 : "Professeur Morderire", + 2121 : "Madame Marrante", + 2122 : "Harry Lesinge", + 2123 : "Emile Esime", + 2124 : "Gaëtan Pipourtoi", + 2125 : "Lazy Mut", + 2126 : "Professeur Lagaffe", + 2127 : "Woody Troissous", + 2128 : "Loulou Fifou", + 2129 : "Frank Fort", + 2130 : "Sylvie Brateur", + 2131 : "Jeanne Laplume", + 2132 : "Daffy Don", + 2133 : "Dr E. Phorique", + 2134 : "Simone Silence-on-tourne", + 2135 : "Marie Satourne", + 2136 : "Sal Amandre", + 2137 : "Heureux Kikomulisse", + 2138 : "Gaston", + 2139 : "Bernard Bavunpeu", + + # Loopy Lane + 2201 : "Pierre le Postier", + 2202 : "Paul Ochon", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Tony Truant", + 2208 : "Nicole Lacolle", + 2209 : "Henri Gole", + 2210 : "Valérie Golotte", + 2211 : "Sally Salive", + 2212 : "Max Imum", + 2213 : "Lucy Rustine", + 2214 : "Dino Zore", + 2215 : "Jean Aimarre", + 2216 : "Lady Sparue", + 2217 : "Jones Requin", + 2218 : "Fanny Larant", + 2219 : "Lanouille", + 2220 : "Louis Leroc", + 2221 : "Tina Pachangé", + 2222 : "Electre O'Cardiogramme", + 2223 : "Sasha Touille", + 2224 : "Joe Lefumeux", + + # Punchline Place + 2301 : "Dr Faismarcher", + 2302 : "Professeur Tortillard", + 2303 : "Nancy Nanny", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Gaz", + 2309 : "Gros Bruce", + 2311 : "Frank O'debord", + 2312 : "Dr Sensible", + 2313 : "Lucy Boulette", + 2314 : "Ned Lafronde", + 2315 : "Valérie Deveau", + 2316 : "Cindy Ka", + 2318 : "Mac Aroni", + 2319 : "Annick", + 2320 : "Alfonse Danslebrouillard", + + # Donald's Dock + 1001 : "Willy - Vendeur", + 1002 : "Billy - Vendeur", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Alain Térieur", + 1008 : "Vendeur d'animalerie", + + # Barnacle Boulevard + 1101 : "Sam Suffit", + 1102 : "Capitaine Carl", + 1103 : "Frank L'écaille", + 1104 : "Docteur Squale", + 1105 : "Amiral Crochet", + 1106 : "Mme Amidon", + 1107 : "Jim Nastic", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gary Glouglou", + 1113 : "Anna-Lise Deussan", + 1114 : "Mick Robe", + 1115 : "Sheila Seiche, Avocate", + 1116 : "Bernard Bernache", + 1117 : "Capitaine Hautlecoeur", + 1118 : "Choppy McDougal", + 1121 : "Marthe Aupiqueur", + 1122 : "Petit Salé", + 1123 : "Electre O'magnétique", + 1124 : "Simon Strueux", + 1125 : "Elvire Debord", + + # Seaweed Street + 1201 : "Barbara Bernache", + 1202 : "Art", + 1203 : "Ahab", + 1204 : "Rocky Roc", + 1205 : "Officier QG", + 1206 : "Officier QG", + 1207 : "Officier QG", + 1208 : "Officier QG", + 1209 : "Professeur Planche", + 1210 : "Yaka Sauté", + 1211 : "Sarah Lenti", + 1212 : "Loulou Languedebois", + 1213 : "Dante Dauphin", + 1214 : "Aimé Duse", + 1215 : "Jean Peuplu", + 1216 : "Seymour Linet", + 1217 : "Cécile Savet", + 1218 : "Tim Pacifique", + 1219 : "Yvon Alot", + 1220 : "Minnie Stair", + 1221 : "McKee Labulle", + 1222 : "A. Marre", + 1223 : "Sid Seiche", + 1224 : "Anna Conda", + 1225 : "Bonzo Boitrop", + 1226 : "Ho Hisse", + 1227 : "Coral", + + # Lighthouse Lane + 1301 : "Ernest", + 1302 : "Ginette", + 1303 : "Gérard", + 1304 : "Hillary Varien", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Ecume de mer", + 1310 : "Ted Tentacule", + 1311 : "Jean Reveux", + 1312 : "Gaëtan Coque", + 1313 : "Gérard Timon", + 1314 : "Ralph Rouillé", + 1315 : "Docteur Dérive", + 1316 : "Elodie Toire", + 1317 : "Paule Pylone", + 1318 : "Barnabé Bouée", + 1319 : "David Bienosec", + 1320 : "Aldo Plate", + 1321 : "Dinah Esservi", + 1322 : "Peter Coussin", + 1323 : "Ned Savon", + 1324 : "Perle Démer", + 1325 : "Ned Setter", + 1326 : "G. Lafritte", + 1327 : "Cindy Nosore", + 1328 : "Sam Ouraille", + 1329 : "Shelly Beaucoup", + 1330 : "Icare Bonize", + 1331 : "Guy Rlande", + + # The Brrrgh + 3001 : "Angèle Ici", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Lenny - Vendeur", + 3007 : "Penny - Vendeuse", + 3008 : "Warren Fagoté", + # NPCPêcheur + 3009 : "Vendeur d'animalerie", + + # Walrus Way + 3101 : "M. Lapin", + 3102 : "Tante Angèle", + 3103 : "Tanguy", + 3104 : "Bonnie", + 3105 : "Freddy Frigo", + 3106 : "Paul Poulemouillée", + 3107 : "Patty Toutseule", + 3108 : "Ted Tobogan", + 3109 : "Patricia", + 3110 : "Jack Pot", + 3111 : "O. Tain", + 3112 : "Allan Bic", + 3113 : "Harry Hystérique", + 3114 : "Nathan Pastrop", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carl Magne", + 3120 : "Mike Mouffles", + 3121 : "Joe Courant", + 3122 : "Lucy Luge", + 3123 : "Nicole Apon", + 3124 : "Lance Iceberg", + 3125 : "Colonel Mâchetout", + 3126 : "Colette Erol", + 3127 : "Alex Térieur", + 3128 : "George Lacolle", + 3129 : "Brigitte Boulanger", + 3130 : "Sandy", + 3131 : "Pablo Paresseux", + 3132 : "Braise Cendrar", + 3133 : "Dr Jevoismieux", + 3134 : "Sébastien Toutseul", + 3135 : "Nelly Quéfié", + 3136 : "Claude Iqué", + 3137 : "M. Gel", + 3138 : "M. Empoté", + 3139 : "Virginie Aimaitropaul", + + # Sleet Street + 3201 : "Tante Artique", + 3202 : "Tremblotte", + 3203 : "Walt", + 3204 : "Dr Ivan Deslunettes", + 3205 : "Boris Tourne", + 3206 : "Victoire Alarraché", + 3207 : "Dr Marmotter", + 3208 : "Phil Electrique", + 3209 : "Geoffroy Auxmains", + 3210 : "Sam Simiesque", + 3211 : "Gaelle Segèle", + 3212 : "Freddy Frigo", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Pierre Lasueur", + 3218 : "Lou Minaire", + 3219 : "Tom Tandem", + 3220 : "G. Ternue", + 3221 : "Nelly Neige", + 3222 : "Moricette Decuisine", + 3223 : "Chappy", + 3224 : "Agnes Kimo", + 3225 : "Frimas Ladouce", + 3226 : "Prospère Noël", + 3227 : "Ray Ondesoleil", + 3228 : "Maurice Quetout", + 3229 : "Hernie Discale", + 3230 : "Benjy Boule-à-zéro", + 3231 : "Choppy", + + # Minnie's Melody Land + 4001 : "Molly Masson", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Doe - Vendeur", + 4007 : "Ray - Vendeur", + 4008 : "Bernard Mony", + 4009 : "Vendeur de l'animalerie", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr Tefaispasdebile", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Clément de sol", + 4109 : "Carlos", + 4110 : "Métro Gnome", + 4111 : "Adam Levent", + 4112 : "Fa", + 4113 : "Madame Manière", + 4114 : "Eric Ochet", + 4115 : "Labelle Decadix", + 4116 : "Piccolo", + 4117 : "Mandy Lynn", + 4118 : "André Sansfrapper", + 4119 : "Moe Zart", + 4120 : "Viola Coussin", + 4121 : "Ray Mineur", + 4122 : "Armanthe Réglisse", + 4123 : "Ted l'éclair", + 4124 : "Riff Iffifi", + 4125 : "Mélodie Dantan", + 4126 : "Bel Canto", + 4127 : "Amédé Chausson", + 4128 : "Luciano Lescoop", + 4129 : "Terry Golo", + 4130 : "Rémi Crophone", + 4131 : "Abraham Armoire", + 4132 : "Sally Tristounet", + 4133 : "D. Taché", + 4134 : "Dave Disco", + 4135 : "Séraphin Ducompte", + 4136 : "Patty Pause", + 4137 : "Tony Doiseau", + 4138 : "Rémi Depain", + 4139 : "Harmony Ka", + 4140 : "Ned Maladroit", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Jack Bûcheron", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Elise", + 4209 : "Mo Végou", + 4211 : "Carl Concerto", + 4212 : "Funeste Funèbre", + 4213 : "Fran Chement", + 4214 : "Tina Crampon", + 4215 : "Tim Rouletroprès", + 4216 : "K. Outchouc", + 4217 : "Anton Beaugarçon", + 4218 : "Vanessa Vapasdutout", + 4219 : "Sid Sonate", + 4220 : "Jean-Bière", + 4221 : "Moe Madrigal", + 4222 : "John Deuf", + 4223 : "Penny Souffleur", + 4224 : "Jim Jongle", + 4225 : "Holly Stérie", + 4226 : "Georgina Gorge", + 4227 : "Francesca Taphonique", + 4228 : "August Ave", + 4229 : "June Comprendsrien", + 4230 : "Julius Césure", + 4231 : "Steffi Nalise", + 4232 : "Marie Toivite", + 4233 : "Charlie Lacarpe", + 4234 : "Guy Tare", + + # Tenor Terrace + 4301 : "Yuki", + 4302 : "Anna", + 4303 : "Léo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tabatha", + 4309 : "Mémé Chignon", + 4310 : "Marthe Ingale", + 4311 : "Charlie Mande", + 4312 : "Ma Sage", + 4313 : "Muget Muet", + 4314 : "Dino Dodo", + 4315 : "Karen Rouages", + 4316 : "Tim Tango", + 4317 : "Sue Bitto", + 4318 : "Bob Marlin", + 4319 : "K. Zou", + 4320 : "Camille Cloda", + 4321 : "Luky Luth", + 4322 : "Henry Thme", + 4323 : "Hanna Purna", + 4324 : "Ellie", + 4325 : "Braque Labanque", + 4326 : "Jonathan Plurien", + 4327 : "Flim Flam", + 4328 : "Wagner", + 4329 : "Tyler Prompteur", + 4330 : "Quentin", + 4331 : "M. Costello", + 4332 : "Ziggy", + 4333 : "Harry", + 4334 : "Freddie Fastoche", + + # Daisy Gardens + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Prune - Vendeuse", + 5006 : "Rose - Vendeuse", + 5007 : "Bonnie Menteuse", + 5008 : "Vendeur de l'animalerie", + + # Elm Street + 5101 : "Eugène", + 5102 : "Susan", + 5103 : "Piaf", + 5104 : "Parpaillon", + 5105 : "Jack", + 5106 : "Bjorn le Barbier", + 5107 : "Felipe le Postier", + 5108 : "Janette l'Aubergiste", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr Lacouenne", + 5114 : "Wilt", + 5115 : "Rosée Dumatin", + 5116 : "R. Noncule", + 5117 : "Pétale", + 5118 : "Victor Nemuse", + 5119 : "Barry Dicule", + 5120 : "La taupe", + 5121 : "Paula Roïd", + 5122 : "A. Masse", + 5123 : "Diane Avecnouscesoir", + 5124 : "Chen Avélo", + 5125 : "A. Sperge", + 5126 : "Madame Mère", + 5127 : "Polly Pollène", + 5128 : "Salma Range", + + # Maple Street + 5201 : "Jacquot", + 5202 : "Cynthia", + 5203 : "Citronelle", + 5204 : "Bert", + 5205 : "Omar Souin", + 5206 : "Ray Zainblanc", + 5207 : "Sophie Stiquée", + 5208 : "Samantha Pir", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Gros Balourd", + 5214 : "Sam Gratte", + 5215 : "Henry Chisson", + 5216 : "Jim Lassenteur", + 5217 : "Walter Ego", + 5218 : "Rocky Groseille", + 5219 : "Mo Viette", + 5220 : "Adam Telle", + 5221 : "Flamant rose", + 5222 : "Pétronille Hiliste", + 5223 : "Marc Assin", + 5224 : "Oncle Balourd", + 5225 : "Pamela Asaplace", + 5226 : "Pierre Mousse", + 5227 : "B. Gonia", + 5228 : "Avi Dité", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Crystelle", + 5306 : "S. Cargot", + 5307 : "Cyril Semarre", + 5308 : "Nell Ronchon", + 5309 : "Romaine", + 5310 : "Thimothé", + 5311 : "Jonas Ticot ", + 5312 : "Eugène", + 5313 : "Zucchini l'entraîneur", + 5314 : "Merlin Sect", + 5315 : "Oncle Boueux", + 5316 : "Oncle Patapouf", + 5317 : "Lima, détective", + 5318 : "César", + 5319 : "Rose", + 5320 : "J. Boulée", + 5321 : "Professeur Chèvrefeuille", + + # Dreamland + 9001 : "Mélusine Enfaillite", + 9002 : "Tom Pouce", + 9003 : "Denis Doiseau", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Jill - Vendeuse", + 9009 : "Phil - Vendeur", + 9010 : "U. Zure", + 9011 : "Vendeur de l'animalerie", + + # Lullaby Lane + 9101 : "Ed", + 9102 : "Big Mama", + 9103 : "P. J.", + 9104 : "Fay Debeauxrêves", + 9105 : "Professeur Baillebeaucoup", + 9106 : "Max", + 9107 : "Câline", + 9108 : "Matt Heula", + 9109 : "Daphné Puisé", + 9110 : "Kathy Mini", + 9111 : "Ali Mentation", + 9112 : "Lou Laberceuse", + 9113 : "Jacques Horloge", + 9114 : "Emma Scara", + 9115 : "Bébé MacDougal", + 9116 : "Celui qui danse avec les moutons", + 9117 : "Sam Suffit", + 9118 : "Stella Lune", + 9119 : "Rocco", + 9120 : "Aron Flebeaucoup", + 9121 : "Serena Dàlanuitombée", + 9122 : "Serge Souslesyeux", + 9123 : "Teddy Blaireau", + 9124 : "Nina Lamparo", + 9125 : "Dr Chassieux", + 9126 : "Thérèse Eveillé", + 9127 : "Tabby Tude", + 9128 : "Amédé Brouilletoitoutseul", + 9129 : "Amélie Decamp", + 9130 : "Paul Potdechambre", + 9131 : "Susan Sieste", + + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Mairie de Toontown", ""), + 2514 : ("Banque de Toontown", ""), + 2516 : ("École de Toontown", ""), + 2518 : ("Bibliothèque de Toontown", ""), + 2519 : ("Boutique à gags", ""), + 2520 : (lToonHQ, ""), + 2521 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Tout-sourire - Réparations dentaires", ""), + 2602 : ("", ""), + 2603 : ("Mineurs Pince-sans-rire", ""), + 2604 : ("Qui vivra, verrat", ""), + 2605 : ("Usine à pancartes de Toontown", ""), + 2606 : ("", ""), + 2607 : ("Haricots sauteurs", ""), + 2610 : ("Dr Tom Lepitre", ""), + 2611 : ("", ""), + 2616 : ("Barbefolle - Déguisements", ""), + 2617 : ("Cascades Comiques", ""), + 2618 : ("Nouba & Co", ""), + 2621 : ("Avions en papier", ""), + 2624 : ("Aux joyeux hooligans", ""), + 2625 : ("La maison du pâté raté", ""), + 2626 : ("Chez Jesse - Réparation de blagues", ""), + 2629 : ("Le coin du rire", ""), + 2632 : ("L'école des clowns", ""), + 2633 : ("Thé-hier - Salon de thé", ""), + 2638 : ("Salle des spectacles de Toontown", ""), + 2639 : ("Monnaie de singe", ""), + 2643 : ("Bouteilles en boîte", ""), + 2644 : ("Farces farcies", ""), + 2649 : ("Magasin de jeux", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Leçons de rire", ""), + 2655 : ("Drôle d'argent - Caisse d'épargne", ""), + 2656 : ("Voitures de clown d'occasion", ""), + 2657 : ("Pirouettes de Pierrette", ""), + 2659 : ("L'univers des vibrateurs", ""), + 2660 : ("Machines à chatouilles", ""), + 2661 : ("Daffy Taffy", ""), + 2662 : ("Dr E. Phorique", ""), + 2663 : ("Cinérama de Toontown", ""), + 2664 : ("Les mimes marrants", ""), + 2665 : ("Le Manège - Agence de voyages", ""), + 2666 : ("Bouteilles de gaz hilarant", ""), + 2667 : ("Au bon temps", ""), + 2669 : ("Chez Gaston - ballons pas folichons", ""), + 2670 : ("Fourchettes à soupe", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Multiplex de cinéma", ""), + 2705 : ("Tony Truant - Bruits en tout genre", ""), + 2708 : ("Colle bleue", ""), + 2711 : ("Bureau de poste de Toontown", ""), + 2712 : ("Café des gloussements", ""), + 2713 : ("Café du rire", ""), + 2714 : ("Cinéplex Louftingue", ""), + 2716 : ("Drôle de soupe", ""), + 2717 : ("Boîtes en bouteille", ""), + 2720 : ("Plaies et Bosses - Réparations de voitures", ""), + 2725 : ("", ""), + 2727 : ("Bouteilles et boîtes d'eau gazeuse", ""), + 2728 : ("Crème de jour évanescente", ""), + 2729 : ("Ornithorynques 14 carats", ""), + 2730 : ("La gazette du rire", ""), + 2731 : ("", ""), + 2732 : ("Spaghettis et barbituriques", ""), + 2733 : ("Cerfs-volants en fonte", ""), + 2734 : ("Tasses et soucoupes volantes", ""), + 2735 : ("Le Pétard mouillé", ""), + 2739 : ("Réparation de fou-rires", ""), + 2740 : ("Pétards d'occasion", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("D. Taché - Nettoyage à sec", ""), + 2744 : ("", ""), + 2747 : ("Encre visible", ""), + 2748 : ("Rions un peu", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Coussins sonores", ""), + 2802 : ("Boulets de démolition gonflables", ""), + 2803 : ("Le roi du carnaval", ""), + 2804 : ("Dr Faismarcher, chiropracteur", ""), + 2805 : ("", ""), + 2809 : ("Salle de gym Le Poids lent", ""), + 2814 : ("Théâtre de Toontown", ""), + 2818 : ("Au pâté volant", ""), + 2821 : ("", ""), + 2822 : ("Sandwiches au poulet synthétique", ""), + 2823 : ("Glaces hilarantes", ""), + 2824 : ("Cinéma des blagues", ""), + 2829 : ("Balivernes", ""), + 2830 : ("Les piques d'Annick", ""), + 2831 : ("La maison du rire du professeur Tortillard", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("Salle des urgences des morts de rire", ""), + 2836 : ("", ""), + 2837 : ("Hardi - Séminaires", ""), + 2839 : ("À la nouille amère", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Boutique à gags", ""), + 1507 : ("Quartier général des Toons", ""), + 1508 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Gilets de sauvetage d'occasion", ""), + 1604 : ("Costumes de bain - Nettoyage à sec", ""), + 1606 : ("Crochet - Réparation d'horloges", ""), + 1608 : ("Le Lof", ""), + 1609 : ("À l'appât rance", ""), + 1612 : ("Banque Sixsous", ""), + 1613 : ("La Pieuvre, cabinet d'avocats", ""), + 1614 : ("Toutes voiles devant - Boutique", ""), + 1615 : ("Yatch qu'à demander!", ""), + 1616 : ("Barbe Noire - Salon de beauté", ""), + 1617 : ("La mer à voir - Opticien", ""), + 1619 : ("L'écorçaire! Chirurgie arboricole", ""), + 1620 : ("Babord-tribord", ""), + 1621 : ("Salle de gym La poupe", ""), + 1622 : ("Gymnote - Électricité générale", ""), + 1624 : ("Réparation de couteaux et de peignes", ""), + 1626 : ("La perche rare - Tenues de soirée", ""), + 1627 : ("La cabane de Sam Suffit", ""), + 1628 : ("Accordeur de thons", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("École maternelle des p'tits loups", ""), + 1703 : ("Bar Accuda - Restaurant chinois", ""), + 1705 : ("Voiles à vendre", ""), + 1706 : ("La méduse médusée", ""), + 1707 : ("C'est assez - Boutique de cadeaux", ""), + 1709 : ("Gelée de méduse", ""), + 1710 : ("La belle bernache", ""), + 1711 : ("Restaurant de la pleine mer", ""), + 1712 : ("Salle de gymnote", ""), + 1713 : ("Chez Art - Cartes en tous genres", ""), + 1714 : ("Auberge du moulinet", ""), + 1716 : ("Maillots de bains pour sirènes", ""), + 1717 : ("Mi pacifique, mi raisin", ""), + 1718 : ("Société de taxi le Naufrage", ""), + 1719 : ("Société Je m'cache à l'eau", ""), + 1720 : ("Au requin malin", ""), + 1721 : ("Tout pour la mer", ""), + 1723 : ("Au royaume des algues", ""), + 1724 : ("Au mérou amoureux", ""), + 1725 : ("J'en pince pour toi - Crabes frais", ""), + 1726 : ("Bière à flots", ""), + 1727 : ("Je rame pour vous", ""), + 1728 : ("Limules porte-bonheur", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Les petits péchés", ""), + 1804 : ("Salle de gym Les mollusques", ""), + 1805 : ("Un petit ver pour le déjeuner", ""), + 1806 : ("Toucoule - Chapelier", ""), + 1807 : ("Coûte que soute", ""), + 1808 : ("Appât si vite!", ""), + 1809 : ("Seaux rouillés", ""), + 1810 : ("L'ancre noire", ""), + 1811 : ("Mérou tu vas chercher tout ça?", ""), + 1813 : ("À mâts couverts, conseiller", ""), + 1814 : ("Le Ho Hisse", ""), + 1815 : ("Quoi de neuf dockteur?", ""), + 1818 : ("Café des sept mers", ""), + 1819 : ("Au dîner des dockers", ""), + 1820 : ("L'hameçon gobé - Farces et attrapes", ""), + 1821 : ("Chez Neptoon", ""), + 1823 : ("À la pomme de mât", ""), + 1824 : ("Au chien pas gai", ""), + 1825 : ("Le hareng sort! Marché aux poissons", ""), + 1826 : ("Le placard de Gérard", ""), + 1828 : ("Palais du lest d'Ernest", ""), + 1829 : ("Merlan l'enchanteur", ""), + 1830 : ("O sole et mio - Objets trouvés", ""), + 1831 : ("Une perle à domicile", ""), + 1832 : ("Supérette La Goélette", ""), + 1833 : ("Costumes pour gaillards d'avant", ""), + 1834 : ("Tranchement ridicule!", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Boutique à gags", ""), + 4504 : ("Quartier général des Toons", ""), + 4506 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tom-Tom - Tambours", ""), + 4604 : ("À quatre temps", ""), + 4605 : ("Fifi - Violons d'ingre", ""), + 4606 : ("La case des castagnettes", ""), + 4607 : ("Vêtements Toon branchés", ""), + 4609 : ("Dot, Raie, Mie - Pianos", ""), + 4610 : ("Attention refrain!", ""), + 4611 : ("Diapasons à l'unisson", ""), + 4612 : ("Dr Tefaispasdebile - Dentiste", ""), + 4614 : ("On rase gratis pour une chanson", ""), + 4615 : ("Pizzéria chez Piccolo", ""), + 4617 : ("La mandoline joyeuse", ""), + 4618 : ("Salles des césures", ""), + 4619 : ("En avant la musique!", ""), + 4622 : ("Oreillers à mentonnière", ""), + 4623 : ("Bémols à la dièse", ""), + 4625 : ("Tuba de dentifrice", ""), + 4626 : ("Notations", ""), + 4628 : ("Assurance accidentelle", ""), + 4629 : ("Riff - Assiettes en papier", ""), + 4630 : ("La musique est notre forte", ""), + 4631 : ("Canto de vous connaître!", ""), + 4632 : ("Boutique de la danse des heures", ""), + 4635 : ("Le quotidien des cantatrices", ""), + 4637 : ("Pour la bonne mesure", ""), + 4638 : ("Boutique Hard Rock", ""), + 4639 : ("Les quatre saisons - Antiquités", ""), + 4641 : ("L'actualité du yéyé", ""), + 4642 : ("D. Taché - Nettoyage à sec", ""), + 4645 : ("Club 88", ""), + 4646 : ("", ""), + 4648 : ("Le Toon siffleur - Déménageurs", ""), + 4649 : ("", ""), + 4652 : ("Boutique des doubles-croches", ""), + 4653 : ("", ""), + 4654 : ("Haut perché - Toitures", ""), + 4655 : ("La clé de sol - École de cuisine", ""), + 4656 : ("", ""), + 4657 : ("Quatuor du barbier", ""), + 4658 : ("Pianos en chute libre", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("L'eau de rose - École de valse", ""), + 4702 : ("Timbre de bois! Fournitures pour bûcherons", ""), + 4703 : ("Gros Bizet à tous!", ""), + 4704 : ("Tina - concerts de concertina", ""), + 4705 : ("Il est déjà cithare?", ""), + 4707 : ("Studio d'effets sonores Doppler", ""), + 4709 : ("Pirouettes - Magasin d'alpinisme", ""), + 4710 : ("Polka tu roules si vite? Auto-école!", ""), + 4712 : ("Mets un bémol! Réparation de pneus", ""), + 4713 : ("Dos dièse - Vêtements de luxe pour hommes", ""), + 4716 : ("Harmonicas à quatre voix", ""), + 4717 : ("Sonate pas ta faute! Assurance automobile", ""), + 4718 : ("Chopins de bière et autres ustensiles de cuisine", ""), + 4719 : ("Camping-cars Madrigal", ""), + 4720 : ("Le bon Toon", ""), + 4722 : ("Doublures pour ouvertures", ""), + 4723 : ("Bach à toi! Jeux et balançoires", ""), + 4724 : ("(Cale)sons blancs pour filles et garçons", ""), + 4725 : ("Le barbier baryton", ""), + 4727 : ("Cordes vocales tressées", ""), + 4728 : ("Chante en sourdine!", ""), + 4729 : ("Librairie J'aime lyre", ""), + 4730 : ("Lettre à un pou", ""), + 4731 : ("Des Toons de bon ton", ""), + 4732 : ("Étude brute? Troupe de théâtre", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Soufflet pour accordéons", ""), + 4736 : ("Hyminnent - Préparatifs de mariage", ""), + 4737 : ("Harpe Hônneur", ""), + 4738 : ("Mécanique cantique - Cadeaux", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Crêp'chignon", ""), + 4803 : ("Quelle Mezzo! Service de domestiques", ""), + 4804 : ("École myxolidienne pour serveurs de barres", ""), + 4807 : ("Massage des Brahms et des jambes", ""), + 4809 : ("C'est une cata-strophe!", ""), + 4812 : ("", ""), + 4817 : ("Magasin d'animaux ternaires", ""), + 4819 : ("Chez Yuki - Ukulélés", ""), + 4820 : ("", ""), + 4821 : ("Chez Anna - Croisières", ""), + 4827 : ("Montres Lamesure", ""), + 4828 : ("Ravel - Réveils et horloges", ""), + 4829 : ("Chez Pachelbel - Obus pour canons et fugues", ""), + 4835 : ("Ursatz pour Kool Katz", ""), + 4836 : ("Reggae royal", ""), + 4838 : ("École de kazoologie", ""), + 4840 : ("Coda Pop - Boissons musicales", ""), + 4841 : ("Lyre et Lyre", ""), + 4842 : ("Société Lasyncope", ""), + 4843 : ("", ""), + 4844 : ("Con moto - deux roues", ""), + 4845 : ("Les élégies élégantes d'Ellie", ""), + 4848 : ("De haute luth - Caisse d'épargne", ""), + 4849 : ("", ""), + 4850 : ("L'accord emprunté - Prêteur sur gages", ""), + 4852 : ("Flasques fleuris pour flûtes", ""), + 4853 : ("Chez Léo - Garde-feu", ""), + 4854 : ("Chez Wagner - Vidéos de violons voilés", ""), + 4855 : ("Réseau de radeau-diffusion", ""), + 4856 : ("", ""), + 4862 : ("Les quadrilles quintessencielles de Quentin", ""), + 4867 : ("M. Costello - Kazoos à gogo", ""), + 4868 : ("", ""), + 4870 : ("Chez Ziggy - Zoo et Zigeunermusik", ""), + 4871 : ("Chez Harry - Harmonies Harmonieuses", ""), + 4872 : ("Freddie Fastoche - Touches de piano", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Boutique à gags", ""), + 5502 : (lToonHQ, ""), + 5503 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("L'œil de bouillon - Optométrie", ""), + 5602 : ("Eugène Coulissant - Cravates", ""), + 5603 : ("Arrête tes salades!", ""), + 5604 : ("Gai, gai, marions-les!", ""), + 5605 : ("Sols et meubles", ""), + 5606 : ("Pétales", ""), + 5607 : ("Bureau de composte", ""), + 5608 : ("Pop corn yéyé", ""), + 5609 : ("La baie au trésor", ""), + 5610 : ("L'œil au beurre noir - Cours de boxe", ""), + 5611 : ("Les gags de la Taupe", ""), + 5613 : ("La meule à zéro - Barbier", ""), + 5615 : ("Chez Piaf - Graines pour oiseaux", ""), + 5616 : ("Auberge de la goutte", ""), + 5617 : ("Chez Parpaillon - Papillons", ""), + 5618 : ("Deux pois deux mesures", ""), + 5619 : ("Chez Jack - Haricots géants", ""), + 5620 : ("Auberge du râteau", ""), + 5621 : ("La critique du Raisin pur", ""), + 5622 : ("La petite reine-claude - Bicyclettes", ""), + 5623 : ("Bains moussants pour oiseaux", ""), + 5624 : ("Écoute ta mère", ""), + 5625 : ("Dur de la feuille", ""), + 5626 : ("Travaux d'aiguille (de pin)", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Le bambou du tunnel", ""), + 5702 : ("Les râteaux de Jacquot", ""), + 5703 : ("Cynthia - Magasin de photosynthèses", ""), + 5704 : ("Citronelle Citron - Voitures d'occasion", ""), + 5705 : ("Meubles en herbe à puce", ""), + 5706 : ("14 carottes - Bijoutiers", ""), + 5707 : ("Fruit musical", ""), + 5708 : ("Sans soucis - Agence de voyages", ""), + 5709 : ("Astroturf - Tondeuses", ""), + 5710 : ("Gym des narcisses", ""), + 5711 : ("Bonneterie de jardin", ""), + 5712 : ("Statues squottes", ""), + 5713 : ("Buis clos", ""), + 5714 : ("Bouteilles d'eau de roche", ""), + 5715 : ("La Meule nouvelle", ""), + 5716 : ("Qui s'y frotte s'y pique - Prêteur sur gages", ""), + 5717 : ("La fleur qui mouille", ""), + 5718 : ("Le chèvre-feuille - Animalerie", ""), + 5719 : ("Sauge d'une nuit d'été! Détectives privés", ""), + 5720 : ("La feuille de vigne - Prêt-à-porter masculin", ""), + 5721 : ("Routabaga 66 - Restaurant", ""), + 5725 : ("Boutique du grain d'orge", ""), + 5726 : ("Bert", ""), + 5727 : ("Le trou sans fond - Caisse d'épargne", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("La vase de Soisson", ""), + 5805 : ("Le cerveau lent", ""), + 5809 : ("Drôle d'oiseaux - École de clowns", ""), + 5810 : ("Ça ne rom à rain!", ""), + 5811 : ("Auberge Inn", ""), + 5815 : ("Des racines & des herbes", ""), + 5817 : ("Pommes et oranges", ""), + 5819 : ("Pantalons vert citron", ""), + 5821 : ("Centre de squash", ""), + 5826 : ("Matériel d'élevage de fourmis", ""), + 5827 : ("Terre bon marché.", ""), + 5828 : ("Meubles Molasson", ""), + 5830 : ("Vide ton sac (de patates)", ""), + 5833 : ("Bar à salades", ""), + 5835 : ("Séjour en pots chez l'habitant", ""), + 5836 : ("Salles de bain J. Boulée", ""), + 5837 : ("L'école de la vie(gne)", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Bibliothèque des berceuses", ""), + 9503 : ("Bar du roupillon", ""), + 9504 : ("Boutique à gags", ""), + 9505 : (lToonHQ, ""), + 9506 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Auberge des câlins", ""), + 9602 : ("Sommes au rabais", ""), + 9604 : ("Chez Ed - Edredons redondants", ""), + 9605 : ("323 Rue de la Berceuse", ""), + 9607 : ("Big Mama - Pyjamas des Bahamas", ""), + 9608 : ("Quand le chat dort, les souris dansent", ""), + 9609 : ("Roupillon pour trois ronds", ""), + 9613 : ("Horloges à nettoyer", ""), + 9616 : ("La veilleuse - Électricité générale", ""), + 9617 : ("212 Rue de la Berceuse", ""), + 9619 : ("Relax Max", ""), + 9620 : ("PJ - Service de taxi", ""), + 9622 : ("Horloges du sommeil", ""), + 9625 : ("Histoire en boucle - Salon de beauté", ""), + 9626 : ("818 Rue de la Berceuse", ""), + 9627 : ("Le tipi endormi", ""), + 9628 : ("Sam Suffit - Calendriers", ""), + 9629 : ("310 Rue de la Berceuse", ""), + 9630 : ("Marchand de sable", ""), + 9631 : ("Temps d'arrêt - Horloger", ""), + 9633 : ("Salle de projection du pays des rêves", ""), + 9634 : ("Je ronfle, donc je suis", ""), + 9636 : ("Assurance pour insomniaques", ""), + 9639 : ("Maison de l'hibernation", ""), + 9640 : ("805 Rue de la Berceuse", ""), + 9642 : ("À la sciure de mon front", ""), + 9643 : ("Les yeux clos - Optométrie", ""), + 9644 : ("Combats d'oreillers nocturnes", ""), + 9645 : ("Auberge Viensmeborder", ""), + 9647 : ("Fais ton lit! Magasin de bricolage", ""), + 9649 : ("Bonnet blanc et blanc bonnet", ""), + 9650 : ("714 Rue de la Berceuse", ""), + 9651 : ("La vie est un ronflement tranquille", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Boutique à gags", ""), + 3508 : (lToonHQ, ""), + 3509 : ("Boutique de prêt-à-porter", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Aurore boréale - Électricité générale", ""), + 3602 : ("Bonnets de pâques", ""), + 3605 : ("", ""), + 3607 : ("Le vieillard du blizzard", ""), + 3608 : ("À en perdre la boule (de neige)!", ""), + 3610 : ("Supérette Les Mirettes", ""), + 3611 : ("M. Lapin - Chasse-neige", ""), + 3612 : ("Conception d'igloos", ""), + 3613 : ("Glaces et miroirs", ""), + 3614 : ("Fabricant de flocons d'avoine", ""), + 3615 : ("Omelettes norvégiennes", ""), + 3617 : ("Voyages en ballon à air froid", ""), + 3618 : ("Boule de neige! Gestion de crise", ""), + 3620 : ("Atelier de ski", ""), + 3621 : ("Glacier La fonte des neiges", ""), + 3622 : ("", ""), + 3623 : ("Croque-monsieur", ""), + 3624 : ("Sandwichs froids", ""), + 3625 : ("Tante Angèle - Radiateurs", ""), + 3627 : ("Chenil St Bernard", ""), + 3629 : ("La soupe aux pois - Café", ""), + 3630 : ("Agence de voyage Laglisse", ""), + 3634 : ("Télésièges rembourrés", ""), + 3635 : ("Bois de chauffage d'occasion", ""), + 3636 : ("Chair de poule bon marché", ""), + 3637 : ("Les patins de Patricia", ""), + 3638 : ("Hêtre ou ne pas hêtre", ""), + 3641 : ("Chez Tanguy - Bateaux à dormir debout", ""), + 3642 : ("L'œil du cyclone - Opticien", ""), + 3643 : ("Chambre (froide) de danse", ""), + 3644 : ("Glaçons fondus", ""), + 3647 : ("Au pingouin sanguin - Magasin de smokings", ""), + 3648 : ("Glace instantanée", ""), + 3649 : ("Hambrrghers", ""), + 3650 : ("Articlités", ""), + 3651 : ("Freddy Frigo - Saucisses congelées", ""), + 3653 : ("Bijoux glacés", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Stockage hivernal", ""), + 3703 : ("", ""), + 3705 : ("Glaçons pour deux", ""), + 3706 : ("Babas au rhume", ""), + 3707 : ("Mon igloo est mon royaume", ""), + 3708 : ("Chez Pluto", ""), + 3710 : ("Restaurant en chute libre", ""), + 3711 : ("", ""), + 3712 : ("Au royaume du déluge", ""), + 3713 : ("Les dents qui claquent - Dentiste polaire", ""), + 3715 : ("Les bonnes soupes de Tante Artique", ""), + 3716 : ("Salage et poivrage des routes", ""), + 3717 : ("Juneau sais pas ce que vous voulez dire", ""), + 3718 : ("Inventeur de chambres à air", ""), + 3719 : ("Glaçon en cornet", ""), + 3721 : ("Aux bonnes affaires glissantes", ""), + 3722 : ("Boutique d'après-ski", ""), + 3723 : ("Chez Tremblotte - globes des neiges", ""), + 3724 : ("La chronique des rhumeurs", ""), + 3725 : ("Alluge-toi un instant", ""), + 3726 : ("Couvertures solaires", ""), + 3728 : ("Chasse-neige à la pelle", ""), + 3729 : ("", ""), + 3730 : ("Achat et vente de bonhommes de neige", ""), + 3731 : ("Cheminées portatives", ""), + 3732 : ("Au nez gelé", ""), + 3734 : ("Regards glacés - Optométrie", ""), + 3735 : ("Calottes glaciaires", ""), + 3736 : ("Cubes de glace bon marché", ""), + 3737 : ("Restaurant de la pente", ""), + 3738 : ("Chaud devant!", ""), + 3739 : ("", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Désolé, tu n'as plus\n le temps." +ClosetNotOwnerMessage = "Ce n'est pas ton placard, mais tu peux essayer les vêtements." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Supprimer" +ClosetAreYouSureMessage = "Tu as supprimé des vêtements. Veux-tu vraiment les supprimer?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Vraiment supprimer %s?" +ClosetShirt = "cette chemise" +ClosetShorts = "ce short" +ClosetSkirt = "cette jupe" +ClosetDeleteShirt = "Supprimer\nchemise" +ClosetDeleteShorts = "Supprimer\nshort" +ClosetDeleteSkirt = "Supprimer\njupe" + +# EstateLoader.py +EstateOwnerLeftMessage = "Désolé, le(la) propriétaire de cette maison est parti(e). Retour au terrain de jeux dans %s secondes" +EstatePopupOK = "OK" +EstateTeleportFailed = "Impossible de retourner à la maison. Essaie encore!" +EstateTeleportFailedNotFriends = "Désolé, %s est chez un Toon avec qui tu n'es pas ami(e)." + +# DistributedHouse.py +AvatarsHouse = "Maison %s\n" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Désolé, ce n'est pas ta tirelire." +DistributedBankNotOwner = "Désolé, ce n'est pas ta tirelire." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Tout vendre" +FishTankValue = "Salut,%(name)s! Tu as %(num)s poissons dans ton seau qui valent au total %(num)s bonbons. Veux-tu tous les vendre?" + +def GetPossesive(name): + if name[-1:] == 's': + possesive = "de " + name + else: + possesive = "de " + name + return possesive + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+" : Appuie sur la touche \" Page précédente \" pour mieux voir." + +FireworksJuly4Beginning = lToonHQ+" : Feux d’artifices du 14 Juillet : Profitez du spectacle!" +FireworksJuly4Ending = lToonHQ+" : Nous espérons que vous avez profité du spectacle! Passez un excellent été!" +FireworksNewYearsEveBeginning = lToonHQ+" : Bonne année! Profitez du feu d'artifice!" +FireworksNewYearsEveEnding = lToonHQ+" : Nous espérons que vous avez profité du spectacle! Bonne année 2006!" +FireworksBeginning = lToonHQ+" : Feux d’artifices du 14 Juillet : Profitez du spectacle!" +FireworksEnding = lToonHQ+" : Nous espérons que vous avez profité du spectacle! Passez un excellent été!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "ASTUCE TOON :" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Pour vérifier rapidement les progrès de ton défitoon, maintiens enfoncée la touche \" Fin \".", + "Pour vérifier rapidement ta page de gags, maintiens enfoncée la touche \" Première page \".", + "Pour ouvrir ta liste d'amis, appuie sur la touche \" F7 \".", + "Pour ouvrir ou fermer ton journal de bord, appuie sur la touche \" F8 \".", + "Pour regarder vers le haut, appuie sur la touche \" Page précédente \" ; pour regarder vers le bas, appuie sur la touche \" Page suivante \".", + "Appuie sur la touche \" Contrôle \" pour sauter.", + "Appuie sur la touche \" F9 \" pour faire une capture d'écran, qui sera enregistrée dans le dossier Toontown de ton ordinateur.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Tu peux échanger des codes d'ami secret avec des personnes que tu connais en dehors de Toontown pour pouvoir chatter avec eux dans Toontown.", + "Tu peux changer ta résolution d'écran, régler le son et d'autres options dans la page d'options du journal de bord.", + "Essaie les vêtements de tes amis, qui sont dans les placards de leurs maisons.", + "Tu peux rentrer chez toi grâce au bouton \" Retour à la maison \" sur ta carte.", + "Chaque fois que tu termines un défitoon avec succès, tes rigolpoints sont automatiquement ajoutés.", + "Tu peux voir la collection dans les boutiques de prêt-à-porter même sans ticket d'habillement.", + "Les récompenses de certains défitoons te permettent d'avoir plus de gags et de bonbons.", + "Tu peux avoir jusqu'à 50 amis sur ta liste d'amis.", + "La récompense de certains défitoons te permet de te téléporter jusqu'aux terrains de jeux de Toontown par la carte du journal de bord.", + "Augmente tes rigolpoints sur les terrains de jeux en ramassant des trésors tels que des étoiles et des cornets de glace.", + "Si tu as besoin de te soigner rapidement après un combat difficile, va chez toi et ramasse des cornets de glace.", + "Pour changer la visualisation de ton Toon, appuie sur la touche de tabulation.", + "Quelquefois tu peux trouver plusieurs défitoons différents proposés pour la même récompense. Fais ton choix!", + "Trouver des amis qui font le même défitoon est une manière amusante de progresser dans le jeu.", + "Tu n'as jamais besoin d'enregistrer ta progression dans Toontown. Les serveurs de Toontown enregistrent toutes les informations nécessaires en continu.", + "Tu peux parler en chuchotant à d'autres Toons en cliquant sur eux ou en les sélectionnant dans ta liste d'amis.", + "Certaines phrases du Chat rapide provoquent une émotion animée sur ton Toon.", + "Si tu te trouves dans une zone où il y a trop de monde, tu peux essayer de changer de district. Va à la page des districts dans le journal de bord et choisis-en un autre.", + "Si tu sauves activement des bâtiments, une étoile de bronze, d'argent ou d'or s'affichera au-dessus de ton Toon.", + "Si tu sauves assez de bâtiments pour avoir une étoile au-dessus de ta tête, tu pourras trouver ton nom affiché sur le tableau d'un Quartier général des Toons.", + "Les bâtiments sauvés sont quelquefois recapturés par les Cogs. La seule façon de conserver ton étoile est d'aller sauver plus de bâtiments.", + "Les noms de tes amis secrets apparaîtront en bleu.", + # Fishing + "Essaie d'avoir toutes les espèces de poisson de Toontown!", + "Chaque mare recèle différentes sortes de poissons. Essaie-les toutes!", + "Lorsque ton seau de pêche est plein, tu peux vendre tes poissons aux vendeurs de l'animalerie sur les terrains de jeux.", + "Les cannes à pêche plus solides attrapent de plus gros poissons mais requièrent plus de bonbons.", + "Tu peux acheter des cannes à pêche plus solides dans le catalogue.", + "Les plus gros poissons valent plus de bonbons à l'animalerie.", + "Les poissons plus rares valent plus de bonbons à l'animalerie.", + "Tu peux quelquefois trouver des sacs de bonbons en pêchant.", + "Certains défitoons nécessitent de pêcher des objets dans les mares.", + "Les mares des terrains de jeux ont des poissons différents de ceux des mares des rues.", + "Certains poissons sont vraiment rares. Continue à pêcher jusqu'à ce que tu les aies tous!", + "La mare que tu as chez toi contient des poissons qui ne peuvent pas être trouvés ailleurs.", + "À chaque fois que tu as attrapé 10 espèces, tu gagnes un trophée de pêche!", + "Tu peux voir quels poissons tu as pêchés dans ton journal de bord.", + "Certains trophées de pêche te valent un rigol-augmentation.", + "La pêche est une bonne façon de gagner plus de bonbons.", + ), + + TIP_STREET : ( + "Il existe quatre types de Cogs : Les Loibots, les Caissbots, les Vendibots et les Chefbots.", + "Chaque série de gags est associée à différents niveaux de précision et de dégâts.", + "Les gags de tapage affectent tous les Cogs mais réveillent les Cogs leurrés.", + "Battre les Cogs en ordre stratégique peut grandement augmenter tes chances de gagner les batailles.", + "La série de gags \" toonique \" te permet de soigner les autres Toons lors d'une bataille.", + "Les points d'expérience des gags sont doublés pendant une invasion de Cogs!", + "Plusieurs Toons peuvent faire équipe et utiliser la même série de gags lors d'une bataille pour infliger plus de dégâts aux Cogs.", + "Lors des batailles, les gags sont utilisés dans l'ordre affiché sur le menu des gags, de haut en bas.", + "La rangée de lumières circulaires sur les ascenseurs des bâtiments des Cogs indiquent combien d'étages ils contiennent.", + "Clique sur un Cog pour avoir plus de détails.", + "L'utilisation de gags de haut niveau contre des Cogs de bas niveau ne donne pas de points d'expérience.", + "Un gag qui donnera de l'expérience s'affiche sur fond bleu sur le menu des gags lors de la bataille.", + "L'expérience des gags est multipliée lorsqu'ils sont utilisés à l'intérieur des bâtiments des Cogs. Les étages les plus hauts ont des coefficients de multiplication plus grands.", + "Lorsqu'un Cog est vaincu, chacun des Toons ayant participé est crédité de la victoire sur ce Cog lorsque la bataille est terminée.", + "Chaque rue de Toontown a différents types et niveaux de Cogs.", + "Il n'y a pas de Cogs sur les trottoirs.", + "Sur les rues, les portes latérales racontent des blagues lorsque tu t'en approches.", + "Certains défitoons t'entraînent à de nouvelles séries de gags. Tu ne pourras choisir que six des sept séries de gags, alors choisis bien!", + "Les pièges ne sont utiles que si toi ou tes amis vous accordez pour utiliser les leurres lors d'une bataille.", + "Les leurres de plus haut niveau sont moins susceptibles de manquer leur cible.", + "Les gags de plus bas niveau ont une précision moindre contre les Cogs de haut niveau.", + "Les Cogs ne peuvent plus attaquer une fois qu'ils ont été leurrés lors d'un combat.", + "Lorsque tes amis et toi aurez repris un bâtiment aux Cogs, vos portraits seront affichés à l'intérieur du bâtiment en guise de récompense.", + "L'utilisation d'un gag toonique sur un Toon qui a un rigolmètre au maximum ne donne pas d'expérience toonique.", + "Les Cogs sont brièvement assommés lorsqu'ils sont frappés par un gag. Cela augmente la chance que les autres gags du même tour le frappent.", + "Les gags de chute ont de faibles chances d'atteindre leur but, mais la précision est accrue lorsque les Cogs ont auparavant été frappés par un autre gag lors du même tour.", + "Lorsque tu as vaincu suffisamment de Cogs, tu peux utiliser le détecteur de Cogs en cliquant sur les icônes du détecteur sur la page de la galerie des Cogs dans ton journal de bord.", + "Pendant une bataille, les tirets (-) et les X indiquent quel Cog tes équipiers sont en train d'attaquer.", + "Pendant une bataille, un voyant lumineux sur les Cogs indique leur état de santé : vert signifie en bonne santé, rouge au bord de la destruction.", + "Un maximum de quatre Toons peuvent combattre simultanément.", + "Dans la rue, les Cogs prendront plus facilement part à une bataille contre plusieurs Toons qu'à une bataille contre un seul Toon.", + "Les deux Cogs les plus difficiles de chaque type ne se trouvent que dans les bâtiments.", + "Les gags de chute ne fonctionnent jamais contre les Cogs leurrés.", + "Les Cogs ont tendance à attaquer le Toon qui leur a causé le plus de dégâts.", + "Les gags de tapage ne donnent pas de bonus contre les Cogs leurrés.", + "Si tu attends trop longtemps avant d'attaquer un Cog leurré, il se réveille. Les leurres de plus haut niveau durent plus longtemps.", + "Il y a des mares dans toutes les rues de Toontown. Certaines rues ont des espèces de poisson uniques.", + ), + + TIP_MINIGAME : ( + "Après avoir rempli ton pot de bonbons, tous les bonbons que tu gagnes aux jeux du tramway sont automatiquement versés dans ta tirelire.", + "Tu peux utiliser les flèches du clavier au lieu de la souris dans le jeu du tramway \" Imite Minnie \".", + "Dans le jeu du canon, tu peux utiliser les flèches du clavier pour déplacer ton canon et appuyer sur la touche \" Contrôle \" pour tirer.", + "Dans le jeu des anneaux, des points supplémentaires sont attribués quand le groupe entier réussit à nager dans les anneaux.", + "Un jeu parfait d'\" Imite Minnie \" double tes points.", + "Dans le tir à la corde, tu reçois plus de bonbons si tu joues contre un Cog plus fort.", + "La difficulté des jeux du tramway varie selon les quartiers, Toontown centre a les plus faciles et le Pays des rêves de Donald les plus difficiles.", + "Certains jeux du tramway ne peuvent être joués qu'en groupe.", + ), + + TIP_COGHQ : ( + "Tu dois terminer ton déguisement de Cog avant d'entrer dans le bâtiment du Chef.", + "Tu peux sauter sur les gardes du corps des Cogs pour les désactiver temporairement.", + "Additionne les mérites Cogs par tes victoires sur les Cogs.", + "Tu obtiens plus de mérites avec des Cogs de plus haut niveau.", + "Lorsque tu as additionné assez de mérites Cogs pour gagner une promotion, va voir le vice-président des Vendibots!", + "Tu peux parler comme un Cog lorsque tu portes ton déguisement de Cog.", + "Jusqu'à huit Toons peuvent faire équipe pour combattre le vice-président des Vendibots.", + "Le vice-président des Vendibots est tout en haut du quartier général des Cogs.", + "À l'intérieur des usines des Cogs, monte les escaliers pour arriver jusqu'au contremaître.", + "Chaque fois que tu te bats dans l'usine, tu gagnes une pièce de ton déguisement de Cog.", + "Tu peux visualiser le progrès de ton déguisement de Cog dans ton journal de bord.", + "Tu peux visualiser le progrès de tes mérites sur ta page de déguisements dans ton journal de bord.", + "Assure-toi d'avoir suffisamment de gags et un rigolmètre au maximum avant d'aller voir le vice-président.", + "Si tu as une promotion, ton déguisement de Cog est mis à jour.", + "Tu dois vaincre le contremaître de l'usine pour récupérer une pièce du déguisement de Cog.", + ), + + } + +FishGenusNames = { + 0 : "Baudruche", + 2 : "Poisson-chat", + 4 : "Poisson-clown", + 6 : "Poisson surgelé", + 8 : "Étoile de mer", + 10 : "Hareng saur", + 12 : "Poisson chien", + 14 : "Anguille douce", + 16 : "Requin nourrice", + 18 : "Crabe-roi", + 20 : "Poisson-lune", + 22 : "Hippocampe", + 24 : "Requin d'eau douce", + 26 : "Bar à coudas", + 28 : "Truite coupe-gorge", + 30 : "Thon tonléon", + 32 : "Méduse médusée", + 34 : "Raie tissante", + } + +FishSpeciesNames = { + 0 : ( "Poisson baudruche", + "Baudruche à air chaud", + "Baudruche météo", + "Baudruche à eau", + "Baudruche rouge", + ), + 2 : ( "Poisson-chat", + "Poisson-chat siamois", + "Poisson-chat piteau", + "Poisson-chat de gouttière", + "Poisson matou", + ), + 4 : ( "Poisson-clown", + "Poisson-clown triste", + "Poisson-pitre", + "Poisson-cirque", + ), + 6 : ( "Poisson surgelé", + ), + 8 : ( "Étoile de mer", + "Étoile de mer lu", + "Étoile de mer sédès", + "Étoile de mer credi", + "Étoile de mer ciatous ", + ), + 10 : ( "Hareng saur", + ), + 12 : ( "Poisson chien", + "Poisson-chien de traîneau", + "Poisson-chien-chien", + "Poisson dalmatien", + "Poisson chiot", + ), + 14 : ( "Anguille douce", + "Anguille rette électrique", + ), + 16 : ( "Requin nourrice", + "Requin nourrice tique", + "Requin nourrice tourne", + ), + 18 : ( "Crabe-roi", + "Crabe roi d'Alaska", + "Vieux crabe roi", + ), + 20 : ( "Poisson-lune", + "Poisson pleine lune", + "Poisson demi-lune", + "Poisson nouvelle-lune", + "Poisson croissant de lune", + "Poisson équinoxe", + ), + 22 : ( "Hippocampe", + "Hippocampe oscillant", + "Hippocampe percheron", + "Hippocampe oriental", + ), + 24 : ( "Requin d'eau douce", + "Requin de baignoire", + "Requin de piscine", + "Requin olympique", + ), + 26 : ( "Bar tabba", + "Bar amine", + "Bar ratin", + "Bar ricade", + "Bar sovie", + "Bar racé", + "Bar cadaire", + "Bar bouillé", + ), + 28 : ( "Truite coupe-gorge", + "Truite capitaine", + "Truite scorbut", + ), + 30 : ( "Thon tonléon", + "Thon-clave", + "Thon-suret", + "Thon bola", + "Thon durasé", + ), + 32 : ( "Méduse médusée", + "Poisson-cacahuète", + "Poisson pané", + "Poisson fraise", + ), + 34 : ( "Raie tissante", + ), + } + +FishFirstNames = ( + "", + "Angéline", + "Arctique", + "Bébé", + "Bermuda", + "Grand", + "Fontaine", + "Bubule", + "Buster", + "Candy", + "Capitaine", + "Ciboulette", + "Choupette", + "Corail", + "Docteur", + "Toussale", + "Empereur", + "Mâchefer", + "Gros", + "Filou", + "Palmyre", + "Polochon", + "Totoche", + "Doudou", + "Jack", + "Roi", + "P'tit", + "Marin", + "Mamzelle", + "Monsieur", + "Pomme", + "Petit-Doigt", + "Prince", + "Princesse", + "Professeur", + "Bouboule", + "Reine", + "Mirage", + "Ray", + "Rosie", + "Robert", + "Poivre", + "Nicole", + "Sandy", + "Écaille", + "Dent d'or", + "Sire", + "Sacha", + "Pantoufle", + "Chipeur", + "Mini", + "Sébastien", + "P'tit-Pois", + "Étoile", + "Sucre d'orge", + "Super", + "Tigre", + "Microbe", + "Moustache", + ) + +FishLastPrefixNames = ( + "", + "Laplage", + "Noir", + "Bleu", + "Marcassin", + "Lavache", + "Minou", + "Aufond", + "Double", + "Est", + "Chichi", + "Écaille", + "Plat", + "Frais", + "Géant", + "Dorpur", + "Doré", + "Gris", + "Vert", + "Goinfre", + "Jacasse", + "Gelée", + "Dame", + "Cuir", + "Citron", + "Long", + "Nord", + "Océan", + "Octo", + "Huile", + "Perle", + "Mousse", + "Rouge", + "Ruban", + "Fleuve", + "Roc", + "Rubis", + "Barre", + "Sel", + "Mer", + "Argent", + "Tuba", + "Semelle", + "Sud", + "Hérisse", + "Surf", + "Sabre", + "Tigre", + "Triple", + "Tropical", + "Thon", + "Coucou", + "Faible", + "Ouest", + "Blanc", + "Jaune", + ) + +FishLastSuffixNames = ( + "", + "balle", + "basse", + "ventre", + "punaise", + "vole", + "beurre", + "dents", + "botte", + "crabe", + "ronchon", + "tambour", + "palme", + "poisson", + "nette", + "nageoire", + "flou", + "grogne", + "tête", + "veste", + "saut", + "sardine", + "lune", + "bouche", + "mulet", + "cou", + "nez", + "perche", + "rauque", + "coureur", + "voile", + "requin", + "coquille", + "soie", + "bave", + "vif", + "pue", + "queue", + "crapaud", + "truite", + "eau", + ) + + +CogPartNames = ( + "Cuisse gauche", "Tibia gauche", "Pied gauche", + "Cuisse droite", "Tibia droit", "Pied droit", + "Épaule gauche", "Épaule droite", "Poitrine", "Compteur de santé", "Bassin", + "Bras gauche", "Avant-bras gauche", "Main gauche", + "Bras droit", "Avant-bras droit", "Main droite", + ) + +CogPartNamesSimple = ( + "Haut du torse", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrée principale" +SellbotLegFactorySpecLobby = "Accueil" +SellbotLegFactorySpecLobbyHallway = "Couloir de l'accueil" +SellbotLegFactorySpecGearRoom = "Salle des pignons" +SellbotLegFactorySpecBoilerRoom = "Chaufferie" +SellbotLegFactorySpecEastCatwalk = "Passerelle est" +SellbotLegFactorySpecPaintMixer = "Mélangeur à peinture" +SellbotLegFactorySpecPaintMixerStorageRoom = "Réserve du mélangeur à peinture" +SellbotLegFactorySpecWestSiloCatwalk = "Passerelle du silo ouest" +SellbotLegFactorySpecPipeRoom = "Salle des tuyaux" +SellbotLegFactorySpecDuctRoom = "Salle des canalisations" +SellbotLegFactorySpecSideEntrance = "Entrée latérale" +SellbotLegFactorySpecStomperAlley = "Allée des pas perdus" +SellbotLegFactorySpecLavaRoomFoyer = "Accueil des sanitaires" +SellbotLegFactorySpecLavaRoom = "Sanitaires" +SellbotLegFactorySpecLavaStorageRoom = "Réserve des sanitaires" +SellbotLegFactorySpecWestCatwalk = "Passerelle ouest" +SellbotLegFactorySpecOilRoom = "Salle du pétrole" +SellbotLegFactorySpecLookout = "Poste d'observation" +SellbotLegFactorySpecWarehouse = "Réserve" +SellbotLegFactorySpecOilRoomHallway = "Entrée de la salle du pétrole" +SellbotLegFactorySpecEastSiloControlRoom = "Salle de contrôle du silo est" +SellbotLegFactorySpecWestSiloControlRoom = "Salle de contrôle du silo ouest" +SellbotLegFactorySpecCenterSiloControlRoom = "Salle de contrôle du silo central" +SellbotLegFactorySpecEastSilo = "Silo est" +SellbotLegFactorySpecWestSilo = "Silo ouest" +SellbotLegFactorySpecCenterSilo = "Silo central" +SellbotLegFactorySpecEastSiloCatwalk = "Passerelle du silo est" +SellbotLegFactorySpecWestElevatorShaft = "Puits de l'ascenseur ouest" +SellbotLegFactorySpecEastElevatorShaft = "Puits de l'ascenseur est" + diff --git a/toontown/src/toonbase/TTLocalizer_castillian.py b/toontown/src/toonbase/TTLocalizer_castillian.py new file mode 100644 index 0000000..4980790 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_castillian.py @@ -0,0 +1,10411 @@ +import string +import time +from toontown.toonbase.TTLocalizer_castillian_Property import * + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +FancyFont = 'phase_3/models/fonts/Comedy' +NametagFonts = ('phase_3/models/fonts/AnimGothic', #0 * + 'phase_3/models/fonts/Aftershock', #1 * + 'phase_3/models/fonts/JiggeryPokery', #2 * + 'phase_3/models/fonts/Ironwork', #3 * + 'phase_3/models/fonts/HastyPudding', #4 * + 'phase_3/models/fonts/Comedy', #5 * + 'phase_3/models/fonts/Humanist', #6 * + 'phase_3/models/fonts/Portago', #7 * + 'phase_3/models/fonts/Musicals', #8 * + 'phase_3/models/fonts/Scurlock', #9 * + 'phase_3/models/fonts/Danger', #10 * + 'phase_3/models/fonts/Alie', #11 * + 'phase_3/models/fonts/OysterBar', #12 * + 'phase_3/models/fonts/RedDogSaloon', #13 * + ) +NametagFontNames = ('Miembro', #0 * + 'Tembloroso', #1 * + 'Agitado', #2 * + 'Elegante', #3 * + 'Absurdo', #4 * + 'Estrafalario', #5 * + 'Práctico', #6 * + 'Náutico', #7 * + 'Enigmático', #8 * + 'Espeluznante', #9 * + 'Acción', #10 * + 'Poético', #11 * + 'Paseo marítimo', #12 * + 'Oeste', #13 * + ) + +NametagLabel = " Etiqueta del nombre" + +UnpaidNameTag = "Básica" + +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "Mickey" +VampireMickey = "VampiroMickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Goofy" +Pluto = "Pluto" +Flippy = "Flipi" +Chip = "Chip" +Dale = "Dale" + +# common locations +lTheBrrrgh = 'Frescolandia' +lDaisyGardens = 'Jardines de Daisy' +lDonaldsDock = "Puerto de Donald" +lDonaldsDreamland = "Sueñolandia de Donald" +lMinniesMelodyland = "Melodilandia de Minnie" +lToontownCentral = 'Centro de Toontown' +lToonHQ = 'Cuartel general' +lSellbotHQ = 'Cuartel general vendebot' +lGoofySpeedway = "Estadio de Goofy" +lOutdoorZone = "Acres de bellota de Chip y Dale" +lGolfZone = "Minigolf de Chip y Dale" +lPartyHood = "Dibuparque de la fiesta" +lToonHQfull = 'Cuartel general' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("a la", "", "Calle del Tutorial"), # Tutorial + 1000 : ("al", "", "Dibuparque"), + 1100 : ("al", "", "Paseo del Percebe"), + 1200 : ("a la", "", "Avenida de las Algas"), + 1300 : ("a la", "", "Calle del Faro"), + 2000 : ("al", "", "Dibuparque"), + 2100 : ("a la", "", "Calle Boba"), + 2200 : ("a la", "", "Calle Locuela"), + 2300 : ("a la", "", "Avenida del Chiste"), + 3000 : ("al", "", "Dibuparque"), + 3100 : ("a la", "", "Calle de la Morsa"), + 3200 : ("a la", "", "Travesía del Trineo"), + 3300 : ("al", "en el", "Punto Polar"), + 4000 : ("al", "", "Dibuparque"), + 4100 : ("a la", "", "Travesía de la Melodía"), + 4200 : ("al", "", "Boulevard del Barítono"), + 4300 : ("a la", "", "Calle del Tenor"), + 5000 : ("al", "", "Dibuparque"), + 5100 : ("a la", "", "Calle del Chopo"), + 5200 : ("a la", "", "Calle Arce"), + 5300 : ("a la", "", "Calle de Los Robles"), # translate + 9000 : ("al", "", "Dibuparque"), + 9100 : ("a la", "", "Avenida de la Nana"), + 9200 : ("al", "en el", "Centro Pijama"), + 10000 : ("al", "", "Cuartel general jefebot"), + 10100 : ("al", "", "vestíbulo del cuartel general jefebot"), + 10200 : ("a la", "en la", "Casa de Mickey Mouse"), + 10500 : ("al", "en el", "El Tres Frontal"), + 10600 : ("al", "en el", "El Seis Central"), + 10700 : ("al", "en el", "El Nueve Trasero"), + 11000 : ("al", "", "patio del cuartel general vendebot"), + 11100 : ("al", "", "vestíbulo del cuartel general vendebot"), + 11200 : ("a la", "", "fábrica vendebot"), + 11500 : ("a la", "", "fábrica vendebot"), + 12000 : ("al", "", "cuartel general chequebot"), + 12100 : ("al", "", "vestíbulo del cuartel general chequebot"), + 12500 : ("a la", "en la", "Fabrica de moneditas Chequebot"), + 12600 : ("a la", "en la", "Fabrica de dólares Chequebot"), + 12700 : ("a la", "en la", "Fabrica de lingotes Chequebot"), + 13000 : ("al", "", "cuartel general abogabot"), + 13100 : ("al", "", "vestíbulo del cuartel general abogabot"), + 13200 : ("al", "en el", "vestíbulo de la oficina del fiscal del distrito"), + 13300 : ("a la", "en la", "Oficina del abogabot A"), + 13400 : ("a la", "en la", "Oficina del abogabot B"), + 13500 : ("a la", "en la", "Oficina del abogabot C"), + 13600 : ("a la", "en la", "Oficina del abogabot D"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("a", "", lDonaldsDock) +ToontownCentral = ("al", "", lToontownCentral) +TheBrrrgh = ("a", "", lTheBrrrgh) +MinniesMelodyland = ("a", "", lMinniesMelodyland) +DaisyGardens = ("a", "", "los %s" % lDaisyGardens) +OutdoorZone = ("a los", "en los", lOutdoorZone) +FunnyFarm = ("a la", "", "Granja Jolgorio") +GoofySpeedway = ("al", "", "Estadio de Goofy") +DonaldsDreamland = ("a", "", lDonaldsDreamland) +BossbotHQ = ("al", "", lToonHQ+" jefebot") +SellbotHQ = ("al", "", lToonHQ+" vendebot") +CashbotHQ = ("al", "", lToonHQ+" chequebot") +LawbotHQ = ("al", "", lToonHQ+" abogabot") +Tutorial = ("al", "", "Dibututorial") +MyEstate = ("a", "", "Tu hacienda") +WelcomeValley = ("a", "", "Valle Bienvenido") +GolfZone = ("al", "en el", lGolfZone) +PartyHood = ("al", "en el", lPartyHood) + +Factory = 'Fábrica' +Headquarters = 'Cuartel general' +SellbotFrontEntrance = 'Entrada principal' +SellbotSideEntrance = 'Entrada de servicio' +Office = 'Oficina' + +FactoryNames = { + 0 : 'Maqueta de la fábrica', + 11500 : 'Fábrica de bots vendebot', + 13300 : 'Oficina de bots abogabots', #remove me JML + } + +FactoryTypeLeg = 'Pierna' +FactoryTypeArm = 'Brazo' +FactoryTypeTorso = 'Torso' + +MintFloorTitle = 'Planta %s' + +# common strings +lCancel = 'Cancelar' +lClose = 'Cerrar' +lOK = 'Ok' +lNext = 'Siguiente' +lQuit = 'Salir' +lYes = 'Sí' +lNo = 'No' +lBack = 'Atrás' +lHQ = "" + +sleep_auto_reply = "%s está durmiendo" + +lHQOfficerF = 'Funcionaria del cuartel general' +lHQOfficerM = 'Funcionario del cuartel general' + +MickeyMouse = "Mickey Mouse" + +AIStartDefaultDistrict = "Villaboba" + +Cog = "Bot" +Cogs = "Bots" +ACog = "un bot" +TheCogs = "Los bots" +ASkeleton = "un esquelebot" +Skeleton = "esquelebot" +SkeletonP = "esquelebots" +Av2Cog = "un bot versión 2.0" +v2Cog = "Bot versión 2.0" +v2CogP = "Bots versión 2.0" +ASkeleton = "un esquelebot" +Foreman = "capataz de la fábrica" +ForemanP = "capataces" +AForeman = "un capataz" +CogVP = Cog + " VIP" +CogVPs = "bots VIPS" +ACogVP = ACog + " un VIP" +Supervisor = "Supervisor de la fabrica" +SupervisorP = "Supervisores de la fabrica" +ASupervisor = "un supervisor de la fabrica" +CogCFO = Cog + " Director financiero" +CogCFOs = "Director financiero bot" +ACogCFO = ACog + " Director financiero" + +# Quests.py +TheFish = "los peces" +AFish = "un pez" +Level = "nivel" +QuestsCompleteString = "Completada" +QuestsNotChosenString = "No está elegida" +Period = "." + +Laff = "Risa" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("¡Hola, _avName_!", + "¡Buenas, _avName_!", + "¿Qué tal, _avName_?", + "¿Qué tal todo, _avName_?", + "¿Cómo te va, _avName_?", + "¿Qué hay, _avName_?", + "¿Cómo estás, _avName_?", + "Saludos _avName_!", + ) +QuestsDefaultIncomplete = ("¿Qué tal va la tarea, _avName_?", + "¡Parece que todavía te queda trabajo por hacer con esa tarea!", + "¡Sigue trabajando así, _avName_!", + "Intenta terminar la tarea. ¡Sé que puedes hacerlo!", + "¡Sigue intentando terminar la tarea, contamos contigo!", + "¡Sigue trabajando en tu dibutarea!", + ) +QuestsDefaultIncompleteProgress = ("Viniste al lugar adecuado, pero antes tienes que terminar la dibutarea.", + "Cuando hayas terminado con la dibutarea, vuelve aquí.", + "Vuelve cuando hayas terminado la dibutarea.", + ) +QuestsDefaultIncompleteWrongNPC = ("Buen trabajo con esa dibutarea. Deberías ir a ver a _toNpcName_._where_", + "Parece que estás a punto de acabar tu dibutarea. Ve a ver a _toNpcName_._where_.", + "Ve a ver a _toNpcName_ para terminar la dibutarea._where_", + ) +QuestsDefaultComplete = ("¡Buen trabajo! Aquí está tu recompensa...", + "¡Buen trabajo, _avName_! Toma, tu recompensa...", + "¡Muy bien, _avName_! Aquí está tu recompensa...", + ) +QuestsDefaultLeaving = ("¡Ciao!", + "¡Adiós!", + "Nos vemos, _avName_.", + "¡Hasta la vista, _avName_!", + "¡Buena suerte!", + "¡Diviértete en Toontown!", + "¡Hasta luego!", + ) +QuestsDefaultReject = ("Hola.", + "¿En qué puedo ayudarte?", + "¿Cómo estás?", + "Muy buenas.", + "Ahora estoy un poco ocupado, _avName_.", + "¿Sí?", + "¿Qué hay, _avName_?", + "¿Cómo te va, _avName_?", + "¡Eh, _avName_! ¿Cómo va todo?", + # Game Hints + "¿Sabías que puedes abrir el dibucuaderno pulsando la tecla F8?", + "¡Puedes usar el mapa para teletransportarte de vuelta al dibuparque!", + "Para hacerte amigo de otros jugadores, haz clic en ellos.", + "Para averiguar más sobre un " + Cog + ", haz clic en él.", + "Reúne tesoros del dibuparque para llenar el risómetro.", + "¡Los edificios" + Cog + " son lugares peligrosos! ¡No entres en ellos solo!", + "Cuando pierdas un combate, los " + Cogs + " se llevarán todas tus bromas.", + "¡Para conseguir más bromas, juega a los juegos del tranvía!", + "Para conseguir más puntos de risa, completa las dibutareas.", + "Todas las dibutareas proporcionan recompensas.", + "Algunas recompensas sirven para poder llevar más bromas.", + "Si ganas un combate, consigues créditos de dibutareas por cada " + Cog + " derrotado.", + "Si recuperas un edificio " + Cog + " vuelve a entrar para ver el agradecimiento especial de su propietario.", + "Para mirar hacia arriba, mantén pulsada la tecla Re Pág.", + "Si pulsas la tecla Tab, podrás contemplar diferentes vistas de los alrededores.", + "Para mostrar lo que piensas a tus amigos secretos, escribe un '.' antes del pensamiento.", + "Cuando aturdas a un " + Cog + ", le será más difícil esquivar los objetos que caen.", + "Cada tipo de edificio " + Cog + " tiene un aspecto distinto.", + "Cuando derrotes a los " + Cogs + " de los pisos altos de un edificio, obtendrás habilidades superiores.", + ) +QuestsDefaultTierNotDone = ("¡Hola, _avName_! Antes de acceder a una nueva dibutarea debes terminar la actual.", + "¡Muy buenas! Tienes que terminar las dibutareas actuales para acceder a una nueva.", + "¡Buenas, _avName_! Para poderte asignar una nueva dibutarea, tienes que terminar las dibutareas actuales.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("Dicen que _toNpcName_ te anda buscando._where_", + "Visita a _toNpcName_ cuando tengas la ocasión._where_", + "Ve a ver a _toNpcName_ cuando pases por ahí._where_", + "Si tienes la ocasión, pásate a saludar a _toNpcName_._where_", + "_toNpcName_ te asignará tu próxima dibutarea._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogQuestHeadline = "SE BUSCA" +QuestsCogQuestSCStringS = "Tengo que derrotar a %(cogName)s%(cogLoc)s." +QuestsCogQuestSCStringP = "Tengo que derrotar algunos %(cogName)s%(cogLoc)s." +QuestsCogQuestDefeat = "Derrotar a %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Ayuda a los dibus con %d puntos de risas o derrota a algunos %s" +QuestsCogNewNewbieQuestCaption = "Ayuda a un nuevo dibu con %d puntos de risas o menos" +QuestsCogOldNewbieQuestObjective = "Ayuda a un dibu con %(laffPoints)d puntos de risa o menos a derrotar a %(objective)s" +QuestsCogOldNewbieQuestCaption = "Ayuda a un dibu con %d puntos de risa o menos" +QuestsCogNewbieQuestAux = "Derrotar:" +QuestsNewbieQuestHeadline = "Aprendiz" + +QuestsCogTrackQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogTrackQuestHeadline = "SE BUSCA" +QuestsCogTrackQuestSCStringS = "Tengo que derrotar un %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestSCStringP = "Tengo que derrotar algunos %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Derrotar a %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogLevelQuestHeadline = "SE BUSCA" +QuestsCogLevelQuestDefeat = "Derrotar a %s" +QuestsCogLevelQuestDesc = "un " + Cog + " de nivel %(level)s+" +QuestsCogLevelQuestDescC = "%(count)s " + Cogs + " de nivel %(level)s+" +QuestsCogLevelQuestDescI = "algunos " + Cogs + " de nivel %(level)s+" +QuestsCogLevelQuestSCString = "Tengo que derrotar %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'dos+', 'tres+', 'cuatro+', 'cinco+') +QuestsBuildingQuestBuilding = "Edificio" +QuestsBuildingQuestBuildings = "Edificios" +QuestsBuildingQuestHeadline = "DERROTAR" +QuestsBuildingQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsBuildingQuestString = "Derrotar a %s" +QuestsBuildingQuestSCString = "Tengo que derrotar %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "un edificio %(type)s" +QuestsBuildingQuestDescF = "un edificio %(type)s de %(floors)s pisos" +QuestsBuildingQuestDescC = "%(count)s edificios %(type)s" +QuestsBuildingQuestDescCF = "%(count)s edificios %(type)s de %(floors)s pisos" +QuestsBuildingQuestDescI = "algunos edificios %(type)s" +QuestsBuildingQuestDescIF = "algunos edificios %(type)s de %(floors)s pisos" + +QuestFactoryQuestFactory = "Fábrica" +QuestsFactoryQuestFactories = "Fábricas" +QuestsFactoryQuestHeadline = "DERROTAR" +QuestsFactoryQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsFactoryQuestString = "Derrotar a %s" +QuestsFactoryQuestSCString = "Tengo que derrotar a %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "una fábrica %(type)s" +QuestsFactoryQuestDescC = "%(count)s fábricas %(type)s" +QuestsFactoryQuestDescI = "algunas fábricas %(type)s" + +QuestMintQuestMint = "Fabrica de monedas" +QuestsMintQuestMints = "Fabrica de monedas" +QuestsMintQuestHeadline = "DERROTAR" +QuestsMintQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsMintQuestString = "Derrotar %s" +QuestsMintQuestSCString = "Necesito derrotar %(objective)s%(location)s." + +QuestsMintQuestDesc = "moneda bot" +QuestsMintQuestDescC = "%(count)s fabrica de monedas bot" +QuestsMintQuestDescI = "algunas fabrica de monedas bot" + +QuestsRescueQuestProgress = "%(progress)s de %(numToons)s rescatados" +QuestsRescueQuestHeadline = "RESCATAR" +QuestsRescueQuestSCStringS = "Tengo que rescatar a un dibu %(toonLoc)s." +QuestsRescueQuestSCStringP = "Tengo que rescatar a algunos dibus %(toonLoc)s." +QuestsRescueQuestRescue = "Rescatar a %s" +QuestsRescueQuestRescueDesc = "%(numToons)s dibus" +QuestsRescueQuestToonS = "un dibu" +QuestsRescueQuestToonP = "dibus" +QuestsRescueQuestAux = "Rescatar a:" + +QuestsRescueNewNewbieQuestObjective = "Ayuda a un nuevo dibu a rescatar a %s" +QuestsRescueOldNewbieQuestObjective = "Ayuda a un dibu con %(laffPoints)d puntos de risa o menos a rescatar a %(objective)s" + +QuestCogPartQuestCogPart = "Pieza de traje bot" +QuestsCogPartQuestFactories = "Fábricas" +QuestsCogPartQuestHeadline = "RECUPERAR" +QuestsCogPartQuestProgressString = "%(progress)s de %(num)s recuperadas" +QuestsCogPartQuestString = "Recuperar %s" +QuestsCogPartQuestSCString = "Tengo que recuperar %(objective)s%(location)s." +QuestsCogPartQuestAux = "Recuperar:" + +QuestsCogPartQuestDesc = "una pieza de traje bot" +QuestsCogPartQuestDescC = "%(count)s piezas de traje bot" +QuestsCogPartQuestDescI = "algunas piezas de traje bot" + +QuestsCogPartNewNewbieQuestObjective = 'Ayuda a un nuevo dibu a recuperar %s' +QuestsCogPartOldNewbieQuestObjective = 'Ayuda a un dibu con %(laffPoints)d puntos de risa o menos a conseguir %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s de %(numGags)s entregados" +QuestsDeliverGagQuestHeadline = "ENTREGAR" +QuestsDeliverGagQuestToSCStringS = "Tengo que entregar %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Tengo que entregar algunos %(gagName)s." +QuestsDeliverGagQuestSCString = "Tengo que hacer una entrega." +QuestsDeliverGagQuestString = "Entregar %s" +QuestsDeliverGagQuestStringLong = "Entregar %s a _toNpcName_." +QuestsDeliverGagQuestInstructions = "Tú puedes comprar esta broma en la Tienda de Bromas una vez que tengas acceso a la broma." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "ENTREGAR" +QuestsDeliverItemQuestSCString = "Tengo que entregar %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Entregar %s" +QuestsDeliverItemQuestStringLong = "Entregar %s a _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITAR" +QuestsVisitQuestStringShort = "Visitar" +QuestsVisitQuestStringLong = "Ir a ver a _toNpcName_" +QuestsVisitQuestSeeSCString = "Tengo que ver a %s." + +QuestsRecoverItemQuestProgress = "%(progress)s de %(numItems)s recuperados" +QuestsRecoverItemQuestHeadline = "RECUPERAR" +QuestsRecoverItemQuestSeeHQSCString = "Tengo que ver a un funcionario del cuartel general." +QuestsRecoverItemQuestReturnToHQSCString = "Tengo que devolver %s a un funcionario del cuartel general." +QuestsRecoverItemQuestReturnToSCString = "Tengo que devolver %(item)s a %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Tengo que ir al cuartel general." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Tengo que ir al dibuparque %s." +QuestsRecoverItemQuestGoToStreetSCString = "Tengo que ir %(to)s %(street)s en %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Tengo que ir a %s %s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "¿Dónde está %s %s?" +QuestsRecoverItemQuestRecoverFromSCString = "Tengo que recuperar: %(item)s de %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Recuperar %(item)s de %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "ELIGE" +QuestsTrackChoiceQuestSCString = "Tengo que escoger entre %(trackA)s y %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Quizá deba escoger %s." +QuestsTrackChoiceQuestString = "Elige entre %(trackA)s y %(trackB)s" + +QuestsFriendQuestHeadline = "AMIGO" +QuestsFriendQuestSCString = "Necesito hacer amigos." +QuestsFriendQuestString = "Hacer un amigo" + +QuestsMailboxQuestHeadline = "CORREO" +QuestsMailboxQuestSCString = "Necesito leer mi correo." +QuestsMailboxQuestString = "Lee tu correo" + +QuestsPhoneQuestHeadline = "CLARABEL" +QuestsPhoneQuestSCString = "Necesito llamar a Clarabel." +QuestsPhoneQuestString = "Llamar a Clarabel" + +QuestsFriendNewbieQuestString = "Haz %d amigos con %d puntos de risa o menos" +QuestsFriendNewbieQuestProgress = "%(progress)s de %(numFriends)s hechos" +QuestsFriendNewbieQuestObjective = "Hazte amigo de %d dibus que tengan %d puntos de risa o menos" + +QuestsTrolleyQuestHeadline = "TRANVÍA" +QuestsTrolleyQuestSCString = "Tengo que subir al tranvía." +QuestsTrolleyQuestString = "Subir al tranvía." +QuestsTrolleyQuestStringShort = "¿Quieres subir al tranvía?" + +QuestsMinigameNewbieQuestString = "%d Minijuegos" +QuestsMinigameNewbieQuestProgress = "%(progress)s de %(numMinigames)s jugados" +QuestsMinigameNewbieQuestObjective = "Juega %d minijuegos con dibus que tienen %d puntos en el risómetro o menos." +QuestsMinigameNewbieQuestSCString = "Necesito jugar en los minijuegos con dibus nuevos." +QuestsMinigameNewbieQuestCaption = "Ayuda a un dibu Nuevo con %d puntos en el risómetro o menos." +QuestsMinigameNewbieQuestAux = "Juega:" + +QuestsMaxHpReward = "Tu risómetro aumentó en %s." +QuestsMaxHpRewardPoster = "Recompensa: %s punto(s) de aumento en el risómetro" + +QuestsMoneyRewardSingular = "Conseguiste 1 golosina." +QuestsMoneyRewardPlural = "Conseguiste %s golosinas." +QuestsMoneyRewardPosterSingular = "Recompensa: 1 golosina" +QuestsMoneyRewardPosterPlural = "Recompensa: %s golosinas" + +QuestsMaxMoneyRewardSingular = "Ahora puedes llevarte 1 golosina." +QuestsMaxMoneyRewardPlural = "Ahora puedes llevarte %s golosinas." +QuestsMaxMoneyRewardPosterSingular = "Recompensa: 1 golosina" +QuestsMaxMoneyRewardPosterPlural = "Recompensa: %s golosinas" + +QuestsMaxGagCarryReward = "Consigues un %(name)s. Ahora puedes llevarte %(num)s bromas." +QuestsMaxGagCarryRewardPoster = "Recompensa: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "Ahora puedes tener %s dibutareas." +QuestsMaxQuestCarryRewardPoster = "Recompensa: %s dibutareas" + +QuestsTeleportReward = "Ahora puedes teletransportarte a %s." +QuestsTeleportRewardPoster = "Recompensa: Acceso por teletransporte a %s" + +QuestsTrackTrainingReward = "Ahora puedes entrenar las bromas de \"%s\"." +QuestsTrackTrainingRewardPoster = "Recompensa: Entrenamiento de bromas" + +QuestsTrackProgressReward = "Ahora tienes el fotograma %(frameNum)s de la animación del circuito %(trackName)s." +QuestsTrackProgressRewardPoster = "Recompensa: Fotograma %(frameNum)s de la animación de circuito \"%(trackName)s\"" + +QuestsTrackCompleteReward = "Ahora puedes llevarte y usar las bromas de \"%s\"." +QuestsTrackCompleteRewardPoster = "Recompensa: Entrenamiento de circuito %s final" + +QuestsClothingTicketReward = "Puedes cambiarte de ropa" +QuestsClothingTicketRewardPoster = "Recompensa: boleto de ropa" + +QuestsCheesyEffectRewardPoster = "Recompensa: %s" + +QuestsCogSuitPartReward = "Ahora tienes una pieza %(part)s del traje de bot de %(cogTrack)s." +QuestsCogSuitPartRewardPoster = "Recompensa: Pieza %(cogTrack)s %(part)s" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "en este parque" +QuestsStreetLocationThisStreet = "en esta calle" +QuestsStreetLocationNamedPlayground = "en el parque de %s" +QuestsStreetLocationNamedStreet = "en %(toStreetName)s de %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "El edificio de %s se llama" +QuestsLocationBuildingVerb = "el cual está " +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Necesito terminar la dibutarea." + +# MaxGagCarryReward names +QuestsMediumPouch = "Bolsita mediana" +QuestsLargePouch = "Bolsita grande" +QuestsSmallBag = "Bolsa pequeña" +QuestsMediumBag = "Bolsa mediana" +QuestsLargeBag = "Bolsa grande" +QuestsSmallBackpack = "Mochila pequeña" +QuestsMediumBackpack = "Mochila mediana" +QuestsLargeBackpack = "Mochila grande" +QuestsItemDict = { + 1 : ["Gafas", "Gafas", "unas "], + 2 : ["Llave", "Llaves", "una "], + 3 : ["Pizarra", "Pizarras", "una "], + 4 : ["Libro", "Libros", "un "], + 5 : ["Chocolate", "Chocolates", "un "], + 6 : ["Tiza", "Tizas", "una "], + 7 : ["Receta", "Recetas", "una "], + 8 : ["Nota", "Notas", "una "], + 9 : ["Calculadora", "Calculadoras", "una "], + 10 : ["Neumático de auto de payasos", "Neumáticos de auto de payasos", "un "], + 11 : ["Bomba de aire", "Bombas de aire", "una "], + 12 : ["Tinta de pulpo", "Tinta de pulpo", "un poco de "], + 13 : ["Paquete", "Paquetes", "un "], + 14 : ["Recibo de pez de acuario", "Recibos de pez de acuario", "un "], + 15 : ["Pez dorado", "Peces dorados", "un "], + 16 : ["Aceite", "Aceite", "un poco de "], + 17 : ["Grasa", "Grasas", "un poco de "], + 18 : ["Agua", "Aguas", "un poco de "], + 19 : ["Informe de equipo", "Informes de equipo", "un "], + 20 : ["Borrador de pizarra ", "Borrador de pizarra", "un "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 110 : ["TIP Clothing Ticket", "Clothing Tickets", "a "], + 1000 : ["Boleto de ropa", "Boletos de ropa", "un "], + + # Donald's Dock quest items + 2001 : ["Tubo interno", "Tubos internos", "un "], + 2002 : ["Prescripción de monóculo", "Prescripciones de monóculo", "una "], + 2003 : ["Montura de monóculo", "Monturas de monóculo", "una "], + 2004 : ["Monóculo", "Monóculos", "un "], + 2005 : ["Peluca blanca grande", "Pelucas blancas grandes", "una "], + 2006 : ["Granel de lastre", "Graneles de lastre", "una "], + 2007 : ["Engranaje de bot", "Engranajes de bot", "un "], + 2008 : ["Carta náutica", "Cartas náuticas", "una "], + 2009 : ["Coraza repugnante", "Corazas repugnantes", "una "], + 2010 : ["Coraza limpia", "Corazas limpias", "una "], + 2011 : ["Resorte de reloj", "Resortes de reloj", "un "], + 2012 : ["Contrapeso", "Contrapesos", "un "], + + # Minnie's Melodyland quest items + 4001 : ["Inventario de Tina", "Inventarios de Tina", ""], + 4002 : ["Inventario de Uki", "Inventarios de Uki", ""], + 4003 : ["Formulario de inventario", "Formularios de inventario", "un "], + 4004 : ["Inventario de Bibi", "Inventarios de Bibi", ""], + 4005 : ["Billete de Chopo Chopín", "Billetes de Chopo Chopín", ""], + 4006 : ["Billete de Felisa Felina", "Billetes de Felisa Felina", ""], + 4007 : ["Billete de Barbo", "Billetes de Barbo", ""], + 4008 : ["Castañuela sucia", "Castañuelas sucias", ""], + 4009 : ["Tinta azul de pulpo", "Tinta azul de pulpo", "un poco de "], + 4010 : ["Castañuela transparente", "Castañuelas transparentes", "una "], + 4011 : ["Letra de Leo", "Letras de Leo", ""], + + # Daisy's Gardens quest items + 5001 : ["Corbata de seda", "Corbatas de seda", "una "], + 5002 : ["Traje mil rayas", "Trajes mil rayas", "un "], + 5003 : ["Tijeras", "Tijeras", "unas "], + 5004 : ["Postal", "Postales", "una "], + 5005 : ["Pluma", "Plumas", "una "], + 5006 : ["Tintero", "Tinteros", "un "], + 5007 : ["Libreta", "Libretas", "una "], + 5008 : ["Caja de seguridad", "Cajas de seguridad", "una "], + 5009 : ["Bolsa de alpiste", "Bolsas de alpiste", "una "], + 5010 : ["Rueda dentada", "Ruedas dentadas", "una "], + 5011 : ["Ensalada", "Ensaladas", "una "], + 5012 : ["Llave de los "+lDaisyGardens, "Llaves de los "+lDaisyGardens, "una "], + 5013 : ["Planos del cuartel general vendebot", "Planos del cuartel general vendebot", "unos "], + 5014 : ["Nota del cuartel general vendebot", "Notas del cuartel general vendebot", "una "], + 5015 : ["Nota del cuartel general vendebot", "Notas del cuartel general vendebot", "una "], + 5016 : ["Nota del cuartel general vendebot", "Notas del cuartel general vendebot", "una "], + 5017 : ["Nota del cuartel general vendebot", "Notas del cuartel general vendebot", "una "], + + # The Brrrgh quests + 3001 : ["Balón de fútbol", "Balones de fútbol", "un "], + 3002 : ["Tobogán", "Toboganes", "un "], + 3003 : ["Cubito de hielo", "Cubitos de hielo", "un "], + 3004 : ["Carta de amor", "Cartas de amor", "una "], + 3005 : ["Dibuperrito caliente", "Dibuperritos calientes", "un "], + 3006 : ["Anillo de compromiso", "Anillos de compromiso", "un "], + 3007 : ["Aleta de sardina", "Aletas de sardina", "una "], + 3008 : ["Poción calmante", "Pociones calmantes", "una "], + 3009 : ["Diente roto", "Dientes rotos", "un "], + 3010 : ["Diente de oro", "Dientes de oro", "un "], + 3011 : ["Pan de piñones", "Panes de piñones", "un "], + 3012 : ["Queso abultado", "Quesos abultados", "un "], + 3013 : ["Cuchara sencilla", "Cucharas sencillas", "una "], + 3014 : ["Sapo parlanchín", "Sapos parlanchines", "un "], + 3015 : ["Helado de cucurucho", "Helados de cucurucho ", "un "], + 3016 : ["Talco para peluca", "Talco para pelucas", "un poco de "], + 3017 : ["Patito de goma", "Patitos de goma", "un "], + 3018 : ["Dados de goma", "Dados de goma", "unos "], + 3019 : ["Micrófono", "Micrófonos", "un "], + 3020 : ["Teclado electrónico", "Teclados electrónicos", "un "], + 3021 : ["Zapatos con plataforma", "Zapatos con plataforma", "unos "], + 3022 : ["Caviar", "Caviar", "un poco de "], + 3023 : ["Maquillaje", "Maquillaje", "un poco de "], + 3024 : ["Hilo", " Hilo", "un poco de " ], + 3025 : ["Aguja de punto", "Agujas de punto", "una "], + 3026 : ["Coartada", "Coartadas", "una "], + 3027 : ["Sensor de temperatura exterior", "Sensores de temperatura exterior", "un "], + + #Dreamland Quests + 6001 : ["Planos del cuartel general chequebot", "Planos del cuartel general chequebot", "unos "], + 6002 : ["Caña", "Cañas", "una "], + 6003 : ["Correa de transmisión", "Correas de transmisión", "una "], + 6004 : ["Tenazas", " Tenazas", "unas "], + 6005 : ["Lámpara portátil", "Lámparas portátiles", "una "], + 6006 : ["Banjo", "Banjos", "un "], + 6007 : ["Pulidora de hielo", "Pulidoras de hielo", "un "], + 6008 : ["Esterilla cebra", "Esterilla cebra", "una "], + 6009 : ["Zinnias", "Zinnias", "unas "], + 6010 : ["Discos de Zydeco", "Discos de Zydeco", "algunos "], + 6011 : ["Calabacín", "Calabacines", "un "], + 6012 : ["Traje de remo", "Trajes de remo", "un "], + + #Dreamland+1 quests + 7001 : ["Cama básica", "Camas básicas", "una "], + 7002 : ["Cama sofisticada", "Camas sofisticadas", "una "], + 7003 : ["Colcha azul", " Colchas azules", "una "], + 7004 : ["Colcha de cachemir", "Colchas de cachemir", "una "], + 7005 : ["Almohada", "Almohadas", "una "], + 7006 : ["Almohada dura", " Almohadas duras", "una "], + 7007 : ["Pijama", "Pijamas", "un "], + 7008 : ["Pijama con pies", "Pijamas con pies", "un "], + 7009 : ["Pijama colorado con pies", "Pijamas colorado con pies", "un "], + 7010 : ["Pijama fucsia con pies", "Pijamas fucsia con pies", "un "], + 7011 : ["Coral coliflor", " Corales coliflor", "un "], + 7012 : ["Alga pegajosa", "Algas pegajosas", "un "], + 7013 : ["Mazo de mortero", " Mazos de mortero", "una "], + 7014 : ["Tarro de crema antiarrugas ", " Tarro de crema antiarrugas", "un "], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQfull +QuestsHQLocationNameFillin = "en cualquier barrio" + +QuestsTailorFillin = "Sastre" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Tienda de Ropa" +QuestsTailorLocationNameFillin = "en cualquier barrio" +QuestsTailorQuestSCString = "Tengo que ir al sastre." + +QuestMovieQuestChoiceCancel = "¡Vuelve más tarde si necesitas una dibutarea! ¡Ciao!" +QuestMovieTrackChoiceCancel = "¡Vuelve más tarde cuando te hayas decidido! ¡Ciao!" +QuestMovieQuestChoice = "Elige una dibutarea." +QuestMovieTrackChoice = "¿Te decidiste? Elige un circuito o vuelve más tarde." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Ya estás listo.\aSal y ponte a caminar hasta que decidas qué circuito elegir.\aPiénsalo bien, porque este será tu circuito final.\aCuando estés seguro, vuelve conmigo.", + INCOMPLETE_PROGRESS : "Piénsalo bien.", + INCOMPLETE_WRONG_NPC : "Piénsalo bien.", + COMPLETE : "¡Muy buena elección!", + LEAVING : "Buena suerte. Vuelve conmigo cuando tengas dominada tu nueva habilidad.", + } + +QuestDialog_3225 = { + QUEST : "¡Gracias por venir, _avName_!\a"+TheCogs+" del vecindario asustaron a mi repartidor.\a¡No tengo a nadie que entregue esta ensalada a _toNpcName_!\a¿Puedes encargarte tú? ¡Muchas gracias!_where_" + } + +QuestDialog_2910 = { + QUEST : "¿Ya regresaste?\aBuen trabajo con el resorte.\aEl objeto final es un contrapeso.\aPásate a ver a _toNpcName_ y tráete lo que encuentres._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 jefebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, derrotaste a los jefebots. ¡Ve al cuartel general para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 abogabots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, derrotaste a los abogabots. ¡Ve al cuartel general para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 chequebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, derrotaste a los chequebots. ¡Ve al cuartel general para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "Bueno, creo que ya estás listo para algo más complicado.\aDerrota a 3 vendebots.", + INCOMPLETE_PROGRESS : "Los " + Cogs + " están en las calles, atravesando los túneles.", + INCOMPLETE_WRONG_NPC : "Muy bien, derrotaste a los vendebots. ¡Ve al cuartel general para recibir una recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Creo que te vendrán bien unas cuantas bromas nuevas.\aVe a ver a %s, quizá te pueda ayudar._where_" % Flippy }, + 165 : {QUEST : "Muy buenas.\aCreo que tienes que practicar con las bromas.\aCada vez que alcances a un bot con una de las bromas, aumentará tu experiencia.\aCuando tengas la suficiente experiencia, podrás usar bromas mejores.\aPractica ahora con tus bromas derrotando a 4 bots."}, + 166 : {QUEST : "Te felicito, derrotaste a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 jefebots."}, + 167 : {QUEST : "Te felicito, derrotaste a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 abogabots."}, + 168 : {QUEST : "Te felicito, derrotaste a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 vendebots."}, + 169 : {QUEST : "Te felicito, derrotaste a esos bots.\a¿Sabías que hay cuatro tipos diferentes de bots?\aHay abogabots, chequebots, vendebots y jefebots.\aSe diferencian en el color y en las etiquetas con su nombre.\aEntrénate derrotando a 4 chequebots."}, + 170 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; él te aconsejará bien._where_" }, + 171 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; él te aconsejará bien._where_" }, + 172 : {QUEST : "Muy bien, ya sabes en qué se diferencian los cuatro tipos distintos de bots.\aCreo que ya estás listo para empezar a entrenarte en el tercer circuito de trucos.\aVe a ver a _toNpcName_ para elegir el próximo circuito de trucos; ella te aconsejará bien._where_" }, + + 175 : {GREETING : "", + QUEST : "¿Sabías que tienes tu propia casa dibu?\aLa vaca Clarabel lleva un catálogo telefónico donde puedes encargar muebles para decorar tu casa.\a¡También puedes comprar frases, ropa y otros artículos divertidos de SpeedChat!\aLe diré a Clarabel que te mande tu primer catálogo.\a¡Recibirás cada semana una catálogo de novedades!\aVete a casa y usa el teléfono para llamar a Clarabel.", + INCOMPLETE_PROGRESS : "Vete a casa y usa el teléfono para llamar a Clarabel.", + COMPLETE : "¡Que te diviertas encargándole cosas a Clarabel!\aAcabo de terminar de redecorar mi casa. ¡Quedó dibufantástica!\a¡Sigue haciendo dibutareas para conseguir más recompensas!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Las bromas de lanzamiento y chorro son estupendas, pero te harán falta otras para enfrentarte a los bots de niveles superiores.\aCuando te juntes con otros dibus para luchar contra los bots, podrás combinar ataques para infligirles más daños. \aPrueba con distintas combinaciones de trucos para ver cuáles funcionan mejor.\aEn el siguiente circuito, escoge entre Sonido y Curadibu.\aSonido es una broma especial que causa daños a todos los bots al hacer impacto.\aCuradibu te permite sanar a otros dibus durante el combate.\aCuando te hayas decidido, regresa para elegir la broma que desees.", + INCOMPLETE_PROGRESS : "¿Ya regresaste? Ok, ¿te decidiste ya?", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Buena decisión. Antes de usar esas bromas, deberás entrenarte con ellas.\aEn el entrenamiento tienes que completar una serie de dibutareas.\aCada tarea te proporcionará un fotograma de la animación del ataque con la broma.\aCuando reúnas los 15, conseguirás la tarea final de entrenamiento, que te permitirá usar la nueva broma.\aRevisa cómo vas en el dibucuaderno.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Si quieres recorrer la ciudad más fácilmente, ve a ver a _toNpcName_._where_" }, + 1040 : { QUEST : "Si quieres recorrer la ciudad más fácilmente, ve a ver a _toNpcName_._where_" }, + 1041 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amigo mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1042 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amigo mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1043 : { QUEST : "¡Hola! ¿Qué te trae por aquí?\aTodo el mundo usa los agujeros portátiles para viajar en Toontown.\aPuedes teletransportarte al lugar donde están tus amigos mediante la Lista de amigos o a cualquier barrio con el mapa del dibucuaderno.\a¡Desde luego, tendrás que ganártelo!\aActivaré tu acceso por teletransporte al centro de Toontown si ayudas a un amigo mío.\aParece que los bots están dando guerra en la calle Locuela. Ve a ver a _toNpcName_._where_" }, + 1044 : { QUEST : "Ey, gracias por pasar por aquí. La verdad es que necesito ayuda.\aComo ves, no tengo clientes.\aPerdí mi libro secreto de recetas y ya nadie viene a mi restaurante.\aLo vi por última vez justo antes de que los bots ocupasen mi edificio.\a¿Puedes ayudarme a recuperar cuatro de mis famosas recetas?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Conseguiste encontrar mis recetas?" }, + 1045 : { QUEST : "¡Muchas gracias!\aEn poco tiempo tendré todo el recetario y podré volver a abrir mi restaurante.\aAh, tengo una nota para ti: algo sobre el teletransporte.\aDice 'Gracias por ayudar a mi amigo. Entrega esto en el cuartel general'.\aDe verdad, muchas gracias.\a¡Adiós!", + LEAVING : "", + COMPLETE : "Ah, sí, aquí dice que fuiste de gran ayuda para algunos de los amigos de la calle Locuela.\aDice que necesitas teletransportarte al centro de Toontown.\aPues bien, eso está hecho.\aAhora puedes teletransportarte para volver al dibuparque desde casi cualquier lugar de Toontown.\aAbre tu mapa y haz clic en "+lToontownCentral+"." }, + 1046 : { QUEST : "Los chequebots estuvieron dando la lata en la Caja de Ahorros Dine Rodríguez.\aPásate por ahí para ver si puedes hacer algo._where_" }, + 1047 : { QUEST : "Los chequebots entraron en el banco para robar nuestras calculadoras.\aRecupera cinco calculadoras que robaron los chequebots.\aPara no tener que estar yendo y viniendo, tráelas todas de una vez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Sigues buscando las calculadoras?" }, + 1048 : { QUEST : "¡Uau! Gracias por recuperar nuestras calculadoras.\aMmm... Están un poco estropeadas.\a¿Puedes llevárselas a _toNpcName_ a su tienda, \"Cosquilladores automáticos\", en esta calle?\aTal vez las pueda arreglar.", + LEAVING : "", }, + 1049 : { QUEST : "¿Qué es eso? ¿Calculadoras rotas?\a¿Chequebots, dices?\aUm, veamos...\aSí, quitaron los engranajes, pero no me quedan repuestos...\a¿Sabes qué podría servirnos? Unos engranajes de bots, grandes, de bots grandotes.\aLos engranajes de bots de nivel 3 valdrán. Necesito dos para cada máquina, así que son diez en total.\a¡Tráelos enseguida y las arreglaré!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Recuerda que necesito diez engranajes para arreglar las calculadoras." }, + 1053 : { QUEST : "Muy bien, con esto seguro que vale.\aTodas arregladas, sin cargo.\aDevuélveselas a Dine Rodríguez y dale un saludo de mi parte.", + LEAVING : "", + COMPLETE : "¿Están arregladas todas las calculadoras?\aBuen trabajo. Seguro que tengo algo por aquí para recompensarte..." }, + 1054 : { QUEST : "_toNpcName_ necesita ayuda con sus autos de payasos._where_" }, + 1055 : { QUEST : "¡Hola! ¡No puedo encontrar por ningún sitio los neumáticos de este auto de payasos!\a¿Crees que podrás echarme una mano?\aMe parece que Perico Pirado los tiró al estanque del dibuparque del centro de Toontown. \aSi te colocas encima de uno de los amarraderos del estanque podrás intentar pescar los neumáticos para traérmelos.", + GREETING : "¡Jujujú!", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Algún problemita pescando los cuatro neumáticos?" }, + 1056 : { QUEST : "¡Estupendísimo! ¡Ahora puedo volver a conducir este viejo auto de payasos!\aEh, creía que tenía una vieja bomba de aire por aquí para inflar estos neumáticos...\a¿Se la habrá llevado prestada _toNpcName_?\a¿Podrías hacerme el favor de pedirle que me la devuelva?_where_", + LEAVING : "" }, + 1057 : { QUEST : "¿Qué tal?\a¿Una bomba de aire, dices?\a¿Sabes qué? Si me ayudas a limpiar las calles de algunos de esos bots de nivel alto...\ate daré la bomba de aire.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Eso es todo lo que sabes hacer?" }, + 1058 : { QUEST : "Buen trabajo, sabía que lo conseguirías.\aAquí está la bomba. Seguro que _toNpcName_ se alegra de recuperarla.", + LEAVING : "", + GREETING : "", + COMPLETE : "¡Yujuuu! ¡Ya puedo conducir!\aPor cierto, gracias por ayudarme.\aToma esto." }, + 1059 : { QUEST : "_toNpcName_ se está quedando sin suministros. ¿Puedes echarle una mano?_where_" }, + 1060 : { QUEST : "¡Gracias por venir!\aEsos bots me robaron la tinta y casi no me queda nada.\a¿Podrías pescar un poco de tinta de pulpo en el estanque?\aPara pescar, basta con que te sitúes en el amarradero de la orilla.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Tuviste problemas para pescar?" }, + 1061 : { QUEST : "¡Estupendo, gracias por la tinta!\a¿Sabes qué? Si quitases de en medio a unos cuantos de esos chupatintas...\ano me quedaría sin tinta tan rápidamente.\aDerrota a seis chupatintas en el centro de Toontown para llevarte una recompensa.", + LEAVING : "", + COMPLETE : "¡Gracias! Te recompensaré por tu ayuda.", + INCOMPLETE_PROGRESS : "Vi unos cuantos chupatintas más." }, + 1062 : { QUEST : "¡Estupendo, gracias por la tinta!\a¿Sabes qué? Si quitases de en medio a unos cuantos de esos chupasangres...\ano me quedaría sin tinta tan rápidamente.\aDerrota a seis chupasangres en el centro de Toontown para llevarte una recompensa.", + LEAVING : "", + COMPLETE : "¡Gracias! Te recompensaré por tu ayuda.", + INCOMPLETE_PROGRESS : "Vi unos cuantos chupasangres más." }, + 900 : { QUEST : "Dicen que _toNpcName_ necesita ayuda con un paquete._where_" }, + 1063 : { QUEST : "¡Hola, gracias por venir!\aUn bot me robó un paquete muy importante delante de mis narices.\aPor favor, intenta recuperarlo. Creo que era de nivel 3...\aAsí que tendrás que derrotar a bots de nivel 3 hasta que encuentres mi paquete.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No hubo suerte con el paquete, ¿eh?" }, + 1067 : { QUEST : "¡Ese es, muy bien!\aEh, la dirección está borrosa...\aSolo se lee que es para un doctor, el resto está emborronado.\a¿Será para _toNpcName_? ¿Puedes llevárselo?_where_", + LEAVING : "" }, + 1068 : { QUEST : "No esperaba ningún paquete. Quizá sea para el doctor Eufo Rico.\aMi ayudante iba a ir ahí hoy de todas formas, así que le diré que se lo pregunte.\aMientras tanto, ¿te importa deshacerte de unos cuantos bots en mi calle?\aDerrota a diez bots en el centro de Toontown.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mi ayudante no volvió todavía." }, + 1069 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un chequebot se lo robó a mi ayudante cuando volvía.\a¿Podrías intentar recuperarlo?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No hubo suerte con el paquete, ¿eh?" }, + 1070 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un vendebot se lo robó a mi ayudante cuando volvía.\aLo siento, pero tendrás que encontrar a ese vendebot para recuperarlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No hubo suerte con el paquete, ¿eh?" }, + 1071 : { QUEST : "El doctor Eufo Rico no esperaba un paquete, tampoco.\aPor desgracia, un jefebot se lo robó a mi ayudante cuando volvía.\a¿Podrías intentar recuperarlo?", + LEAVING : "", + INCOMPLETE_PROGRESS : "No hubo suerte con el paquete, ¿eh?" }, + 1072 : { QUEST : "¡Estupendo, lo recuperaste!\aDeberías probar con _toNpcName_, tal vez sea para él._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, gracias por traerme mis paquetes.\aEspera un momento, estaba esperando dos. ¿Podrías ir a ver a _toNpcName_ para preguntarle si tiene el otro?", + INCOMPLETE : "¿Conseguiste encontrar mi otro paquete?", + LEAVING : "" }, + 1074 : { QUEST : "¿Dijiste que había otro paquete? A lo mejor lo robaron también los bots.\aDerrota a los bots hasta que encuentres el segundo paquete.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No hubo suerte con el otro paquete, ¿eh?" }, + 1075 : { QUEST : "¡Al final resulta que sí había un segundo paquete!\aDeprisa, llévaselo a _toNpcName_ y pídele disculpas de mi parte.", + COMPLETE : "¡Eh, llegó mi paquete!\aComo pareces ser un dibu muy servicial, esto te vendrá bien.", + LEAVING : "" }, + 1076 : { QUEST : "Hubo problemas en Peces Dorados, 14.\aA _toNpcName_ le vendrá bien una ayudita._where_" }, + 1077 : { QUEST : "Gracias por venir. "+TheCogs+" robaron todos mis peces dorados.\aCreo que quieren venderlos para sacar un dinero rápido.\aEsos cinco peces fueron mi única compañía en esta tiendita durante tantos años...\aSi pudieses hacerme el favor de recuperarlos, te lo agradecería eternamente.\aSeguro que uno de los bots tiene mis peces.\aDerrota a los bots hasta que encuentres mis peces.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Por favor, recupera mis peces dorados." }, + 1078 : { QUEST : "¡Oh, tienes mis peces!\a¿Eh? ¿Qué es eso? ¿Un recibo?\aAh, sí son los bots, a fin de cuentas.\aNo consigo averiguar qué diantre es este recibo. ¿Podrías llevárselo a _toNpcName_ para ver si él lo entiende?_where_", + INCOMPLETE : "¿Qué dijo _toNpcName_ del recibo?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, déjame ver ese recibo.\a... Ah, sí, dice que le vendieron un pez dorado a un secuaz.\aNo menciona para nada qué fue de los otros cuatro peces.\aQuizá debas ponerte a buscar a ese secuaz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No creo que te pueda ayudar más.\a¿Por qué no te pones a buscar ese pez dorado?" }, + 1092 : { QUEST : "Mmm, déjame ver ese recibo.\a... Ah, sí, dice que le vendieron un pez dorado a un moneditas.\aNo menciona para nada qué fue de los otros cuatro peces.\aQuizá debas ponerte a buscar a ese moneditas.", + LEAVING : "", + INCOMPLETE_PROGRESS : "No creo que te pueda ayudar más.\a¿Por qué no te pones a buscar ese pez dorado?" }, + 1080 : { QUEST : "¡Oh, gracias a Dios! Encontraste a Oscar. Es mi favorito.\a¿Qué pasa, Oscar? Oh, vaya... ¿De verdad? ... ¿Están ahí?\aOscar dice que los otros cuatro escaparon y se metieron en el estanque del dibuparque.\a¿Me haces el favor de ir a recogerlos? \aBasta con que los pesques en el estanque.", + LEAVING : "", + COMPLETE : "¡Oooh, qué contento estoy! ¡Por fin vuelvo a estar junto a mis amiguitos!\a¡Te mereces una estupenda recompensa!", + INCOMPLETE_PROGRESS : "¿Te está costando encontrar a los peces?" }, + 1081 : { QUEST : "Parece ser que _toNpcName_ se encuentra en una situación pegajosa. Seguro que le vendrá bien una ayudita._where_" }, + 1082 : { QUEST : "¡Se me derramó el pegamento rápido y me quedé pegado!\aSi pudiera hacer algo para liberarme...\aSe me ocurre una idea, si te sientes valiente.\aDerrota a unos vendebots y tráeme un poco de aceite.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1083 : { QUEST : "El aceite resultó útil, pero sigo sin despegarme del todo.\a¿Qué puedo probar? Nada funciona.\aSe me ocurre una idea, no pierdo nada probándola.\aDerrota a unos abogabots y tráeme un poco de grasa.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1084 : { QUEST : "No, no sirvió de nada. Esto no me hace gracia.\aPuse la grasa y no hubo suerte.\aSe me ocurre una idea para sacarme de aquí.\aDerrota a unos chequebots y trae agua para mojarme.", + LEAVING : "", + GREETING : "", + COMPLETE : "¡Hurra! Me libré de ese pegamento rápido.\aComo recompensa, toma este obsequio.\aPodrás disfrutar de una combativa velada...\a¡Oh, no! ¡Volví a quedarme pegado!", + INCOMPLETE_PROGRESS : "¿Puedes ayudarme a despegarme?" }, + 1085 : { QUEST : "_toNpcName_ está llevando a cabo ciertas investigaciones sobre los bots.\aVete a hablar con él si quieres ayudarle._where_" }, + 1086 : { QUEST : "Efectivamente, estoy realizando un estudio sobre los bots.\aQuiero saber cómo funcionan.\aMe sería de gran ayuda que consiguieses algunos engranajes de bot.\aAsegúrate de que pertenezcan a bots de nivel 2 al menos, para que tengan el tamaño suficiente para examinarlos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿No pudiste conseguir suficientes engranajes?" }, + 1089 : { QUEST : "Muy bien, veamos. ¡Excelentes especímenes!\aMmm...\aDe acuerdo, aquí está mi informe. Llévalo al cuartel general de inmediato.", + INCOMPLETE : "¿Llevaste mi informe al cuartel general?", + COMPLETE : "Buen trabajo, _avName_, a partir de ahora nos ocuparemos nosotros.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ tiene información útil para ti._where_" }, + 1091 : { QUEST : "Dicen que en el cuartel general están trabajando en una especie de radar de bots.\aTe permitirá ver dónde están los bots y así poder encontrarlos más fácilmente.\aLa página Bot del dibucuaderno es la clave.\aSi derrotas suficientes bots, podrás sintonizar sus señales y detectar su paradero.\aSigue derrotando a los bots para estar listo.", + COMPLETE : "¡Buen trabajo! Seguro que esto te viene bien...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsalo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Esos bots tan pesados están dando problemas otra vez.\a_toNpcName_ comunicó que falta otro objeto. Pásate por ahí, a ver si puedes arreglar la situación._where_" }, + 2202 : { QUEST : "Hola, _avName_. Menos mal que viniste. Un cacomatraco acaba de pasar por aquí y se largó corriendo con un tubo interno.\aTemo que lo usen para sus malvados propósitos.\aPor favor, busca al bot y recupera el tubo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Conseguiste encontrar mi tubo interno?", + COMPLETE : "¡Encontraste mi tubo interno! Eres SENSACIONAL. Aquí tienes tu recompensa...", + }, + 2203 : { QUEST : TheCogs+" están sembrando el caos en el banco.\aVe a ver al capitán Doblón, a ver qué puedes hacer._where_" }, + 2204 : { QUEST : "Bienvenido a bordo, grumete.\a¡Arg! Esa escoria de bots aplastaron mi monóculo y no me puedo apañar sin él.\aSé un buen marinero y lleva esta prescripción al doctor Rompecubiertas para que me haga uno nuevo._where_.", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "¿Qué es esto?\aMe encantaría hacer este monóculo, pero los bots saquearon mis pertenencias.\aSi consigues arrebatarle las monturas de monóculo a un secuaz, me serás de gran ayuda.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Lo siento. Sin las monturas del secuaz, no hay monóculo.", + }, + 2206: { QUEST : "¡Excelente!\aUn momento...\aAquí tienes el monóculo de la receta. Llévaselo al capitán Doblón._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "¡Viento en popa!\aNo, si al final te vas a ganar los galones y todo.\aAquí tienes.", + }, + 2207 : { QUEST : "¡Perci Percebe tiene un bot en la tienda!\aMás vale que vayas para allá enseguida._where_" }, + 2208 : { QUEST : "¡Vaya! Se te acaba de escapar, cariño.\aAquí había un apuñalaespaldas. Se llevó mi peluca blanca grande.\aDijo que era para su jefe y no sé qué sobre un \"precedente legal\".\aSi pudieses recuperarla, te estaría eternamente agradecida.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "¿Todavía no la encontraste?\aEs alta y puntiaguda.", + COMPLETE : "¡La encontraste!\a¡Eres todo un encanto!\aTe ganaste esto, no hay duda...", + }, + 2209 : { QUEST : "Isidoro se está preparando para un viaje importante.\aAcércate a ver en qué puedes ayudarle._where_"}, + 2210 : { QUEST : "Me vendrá bien tu ayuda.\aEn el cuartel general me pidieron que haga un viaje para averiguar de dónde proceden los bots.\aNecesito unas cuantas cosas para mi barco, pero ando escaso de golosinas.\aPásate a ver a Pepa Sastre para que te dé algo de lastre. Para conseguirlo, tendrás que hacerle un favor._where_", + GREETING : "¿Qué hay, _avName_?", + LEAVING : "", + }, + 2211 : { QUEST : "Así que Isidoro quiere lastre, ¿eh?\aTodavía me debe la última fanega.\aTe la daré si consigues limpiar mi calle de cinco microgerentes.", + INCOMPLETE_PROGRESS : "¡No, necio! ¡Dije CINCO microgerentes!", + GREETING : "¿Qué puedo hacer por ti?", + LEAVING : "", + }, + 2212 : { QUEST : "Un trato es un trato.\aAquí tienes el lastre para ese tacaño de Isidoro._where_", + GREETING : "Vaya, mira lo que aparece por aquí...", + LEAVING : "", + }, + 2213 : { QUEST : "Buen trabajo. Sabía que se atendría a razones. \aAhora necesito una carta náutica de Pasma Rote.\aNo creo que me fíen ahí tampoco, así que tendrás que llegar a un acuerdo con él._where_.", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Sí, tengo la carta de navegación que necesita Isidoro.\aY te la daré si estás dispuesto a trabajar para conseguirla.\aEstoy intentando construir un astrolabio para orientarme con las estrellas.\aPara hacerlo, necesito tres engranajes de bot.\aVuelve cuando los hayas conseguido.", + INCOMPLETE_PROGRESS: "¿Qué tal te va con los engranajes de bot?", + GREETING : "¡Bienvenido!", + LEAVING : "¡Buena suerte!", + }, + 2215 : { QUEST : "¡Oooh! Estos engranajes me vendrán muy bien.\aAquí tienes la carta. Dásela a Isidoro, y dale recuerdos._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bueno, esto es todo. ¡Estoy listo para zarpar!\aTe dejaría acompañarme si no estuvieses tan verde. A cambio, toma esto.", + }, + 901 : { QUEST : "Si estás disponible, a Ajab le vendría bien una ayuda._where_", + }, + 2902 : { QUEST : "¿Tú eres el nuevo recluta?\aBien, bien. Tal vez puedas ayudarme.\aEstoy construyendo un cangrejo prefabricado gigante para confundir a los bots.\aAunque también me serviría una coraza. Ve a ver a Clodovico Cromañón y pídele una, por favor._where_", + }, + 2903 : { QUEST : "¡Muy buenas!\aSí, oí hablar del cangrejo gigante en el que trabaja Ajab.\aSin embargo, la mejor coraza que tengo está un poco sucia.\aPórtate bien y llévala a la tintorería antes de entregarla._where_", + LEAVING : "¡Gracias!" + }, + 2904 : { QUEST : "Debes de ser el que viene de parte de Clodovico.\aCreo que puedo limpiar eso en un santiamén.\aDame un minuto...\aAquí tienes. ¡Como nuevo!\aSaluda a Ajab de mi parte._where_", + }, + 2905 : { QUEST : "Vaya, esto es exactamente lo que andaba buscando.\aYa que estás aquí, también voy a necesitar un resorte de reloj muy grande.\aPásate por la tienda de Garfio para ver si tiene uno._where_", + }, + 2906 : { QUEST : "Un resorte grande, ¿eh?\aLo siento, pero el más grande que tengo es bastante pequeño, en realidad.\aQuizás pueda hacer uno con los resortes de los gatillos de pistola de agua.\aTráeme tres de esas y veré qué puedo hacer.", + }, + 2907 : { QUEST : "Veamos...\aImpresionante. Simplemente impresionante.\aA veces me sorprendo a mí mismo.\aAquí tienes: un resorte grande para Ajab._where_", + LEAVING : "¡Buen viaje!", + }, + 2911 : { QUEST : "Me encantaría contribuir a la causa, _avName_.\aPero me temo que las calles ya no son seguras.\a¿Por qué no acabas con unos cuantos chequebots? Después hablaremos.", + INCOMPLETE_PROGRESS : "Creo que las calles todavía no son muy seguras.", + }, + 2916 : { QUEST : "Sí, tengo un contrapeso que le vendría bien a Ajab.\aSin embargo, creo que sería mejor que antes derrotases a un par de vendebots.", + INCOMPLETE_PROGRESS : "Todavía no. Derrota a unos cuantos vendebots más.", + }, + 2921 : { QUEST : "Mmm, se supone que tengo que darte un contrapeso.\aPero me sentiría mucho más seguro si no hubiese tantos jefebots rondando por aquí.\aDerrota a seis y ven a verme.", + INCOMPLETE_PROGRESS : "Creo que esta zona todavía no es segura...", + }, + 2925 : { QUEST : "¿Acabaste?\aBien, supongo que la zona ya es bastante segura.\aAquí tienes el contrapeso para Ajab._where_" + }, + 2926 : {QUEST : "Bueno, eso es todo.\aVeamos si funciona.\aMmm, hay un pequeño problema.\aNo puedo encenderlo porque ese edificio bot está tapando mi panel solar.\a¿Puedes hacerme el favor de reconquistarlo?", + INCOMPLETE_PROGRESS : "Sigo sin tener electricidad. ¿Qué hay de ese edificio?", + COMPLETE : "¡Estupendo! ¡Tienes talento para romper bots! Toma, aquí tienes tu recompensa...", + }, + 3200 : { QUEST : "Acabo de recibir una llamada de _toNpcName_.\aEstá teniendo un día de perros. Tal vez puedas ayudarle.\aPásate por ahí para ver qué necesita._where_" }, + 3201 : { QUEST : "¡Ey, gracias por venir!\aNecesito que alguien lleve esta corbata de seda nueva a _toNpcName_.\a¿Me harías tú ese favor?_where_" }, + 3203 : { QUEST : "¡Ah, esta debe de ser la corbata que encargué! ¡Gracias!\aHace juego con el traje mil rayas que acabo de terminar, ese de ahí.\aEh, ¿qué pasó con el traje?\a¡Oh, no! ¡"+TheCogs+" deben de haberme robado el traje nuevo!\aLucha con los bots hasta que encuentres el traje y tráemelo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya mi traje? ¡Seguro que se lo llevaron los bots!", + COMPLETE : "¡Hurra! ¡Encontraste mi traje nuevo!\a¿Ves? Te dije que los bots lo tenían. Toma tu recompensa...", + }, + + 3204 : { QUEST : "_toNpcName_ acaba de llamar para comunicar un robo.\a¿Por qué no te pasas por ahí para ver si puedes arreglar la situación?_where_" }, + 3205 : { QUEST : "¡Hola, _avName_! ¿Viniste a ayudarme?\aAcabo de ahuyentar a un chupasangres de mi tienda. ¡Guau! Daba mucho miedo.\a¡Pero ahora no encuentro las tijeras! Seguro que se las llevó el chupasangres.\aPor favor, busca al chupasangres y recupera las tijeras.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Sigues buscando las tijeras?", + COMPLETE : "¡Mis tijeras! ¡Muchas gracias! Toma tu recompensa...", + }, + + 3206 : { QUEST : "Parece ser que _toNpcName_ está teniendo algún que otro problema con los bots.\aVe a ver si puedes ayudarle._where_" }, + 3207 : { QUEST : "¡Buenas, _avName_! ¡Gracias por venir!\aUnos cuantos embaucadores entraron y se llevaron un taco de postales del mostrador.\a¡Por favor, derrota a esos embaucadores y recupera mis postales!", + INCOMPLETE_PROGRESS : "¡No hay postales suficientes! ¡Sigue buscando!", + COMPLETE : "¡Oh, gracias! ¡Ahora puedo entregar el correo a tiempo! Toma tu recompensa...", + }, + + 3208 : { QUEST : "Recibimos quejas de los vecinos por todos esos gorrones.\aPor favor, intenta derrotar a diez aprovechados para ayudar a tus amigos, los dibus de los "+lDaisyGardens+". " }, + 3209 : { QUEST : "¡Gracias por ocuparte de los gorrones!\a¡Ahora se desmadraron los televendedores!\aDerrota a diez televendedores en los "+lDaisyGardens+" y vuelve para llevarte una recompensa." }, + + 3247 : { QUEST : "Recibimos quejas de los vecinos sobre todos esos chupasangres.\aPor favor, intenta derrotar a veinte chupasangres para ayudar a tus amigos, los dibus de los "+lDaisyGardens+". " }, + + + 3210 : { QUEST : "¡Oh, no, la flor chorreante de la calle Arce se quedó sin flores!\aLlévales diez flores chorreantes de las tuyas para ayudarles. \aPrimero comprueba que tienes diez flores chorreantes en el inventario.", + LEAVING: "", + INCOMPLETE_PROGRESS : "Necesito diez flores chorreantes. ¡No tienes suficientes!" }, + 3211 : { QUEST : "¡Oh, muchas gracias! Esas flores arreglarán la situación.\aPero los bots que hay fuera me dan miedo.\a¿Puedes ayudarme derrotando a unos cuantos?\aVuelve cuando hayas derrotado a veinte bots en esta calle.", + INCOMPLETE_PROGRESS : "¡Todavía quedan bots ahí fuera! ¡Sigue luchando!", + COMPLETE : "¡Oh, gracias! Me fuiste de gran ayuda. Tu recompensa es...", + }, + + 3212 : { QUEST : "_toNpcName_ necesita ayuda para encontrar una cosa que perdió.\aVe a verla, tal vez puedas ayudarle._where_" }, + 3213 : { QUEST : "Hola, _avName_. ¿Puedes ayudarme?\aParece que perdí mi pluma estilográfica. Creo que se la llevaron unos bots.\aDerrótalos y recupera la pluma, por favor.", + INCOMPLETE_PROGRESS : "¿Encontraste ya mi pluma?" }, + 3214 : { QUEST : "¡Sí, es esa! ¡Muchas gracias!\aPero en cuanto te marchaste me di cuenta de que también me faltaba el tintero.\aVence a los bots y recupera el tintero, por favor.", + INCOMPLETE_PROGRESS : "¡Sigo buscando el tintero!" }, + 3215 : { QUEST : "¡Fantástico! Ya recuperé la pluma y el tintero.\aPero ¿a que no te lo imaginas?\a¡No encuentro la libreta! ¡La deben de haber robado también!\aDerrota a los bots hasta que encuentres la libreta y tráemela para que te dé una recompensa.", + INCOMPLETE_PROGRESS : "¿Alguna novedad sobre la libreta?" }, + 3216 : { QUEST : "¡Esa es mi libreta! ¡Hurra! Tu recompensa es...\a¡Eh! ¿Dónde está?\aTenía tu recompensa justo aquí, en la caja de seguridad. ¡Se la llevaron !\a¿Puedes creerlo? ¡"+TheCogs+" robaron tu recompensa!\aVence a los bots para recuperar la caja de seguridad.\aCuando me la traigas, te daré tu recompensa.", + INCOMPLETE_PROGRESS : "¡Sigue buscando la caja de seguridad! ¡Tu recompensa está dentro!", + COMPLETE : "¡Por fin! Tenía tu nueva bolsa de bromas en la caja. Aquí está...", + }, + + 3217 : { QUEST : "Estuvimos estudiando la mecánica de los vendebots.\aQueremos estudiar más de cerca algunas piezas.\aTráenos la rueda dentada de un fanfarrón.\aPuedes atraparlas cuando los bots estallan." }, + 3218 : { QUEST : "¡Buen trabajo! Ahora tenemos que compararla con la rueda dentada de un efusivo.\aEsas ruedas dentadas son más difíciles de atrapar, así que sigue intentándolo." }, + 3219 : { QUEST : "¡Fantástico! Ahora sólo necesitamos una rueda dentada más.\aEsta vez necesitamos la rueda de un mandamás.\aPara encontrar a estos bots, tal vez tengas que buscar en el interior de algunos edificios de vendebots.\aCuando la tengas, tráela y a cambio obtendrás tu recompensa." }, + + 3244 : { QUEST : "Estuvimos estudiando la mecánica de los abogabots.\aQueremos estudiar más de cerca algunas piezas.\aTráenos la rueda dentada de un persigueambulancias.\aPuedes atraparla cuando los bots estallan." }, + 3245 : { QUEST : "¡Buen trabajo! Ahora tenemos que compararla con la rueda dentada de un apuñalaespaldas.\aEsas ruedas dentadas son más difíciles de atrapar, así que sigue intentándolo." }, + 3246 : { QUEST : "¡Fantástico! Ahora sólo necesitamos una rueda dentada más.\aEsta vez necesitamos la rueda de un portavoz.\aCuando la tengas, tráela para obtener a cambio tu recompensa." }, + + 3220 : { QUEST : "Acabo de oír que _toNpcName_ estuvo preguntando por ti.\a¿Por qué no pasas a verla para ver qué quiere?_where_" }, + 3221 : { QUEST : "¡Buenas, _avName_! ¡Aquí estás!\aHe oído que eres todo un experto en ataques chorreantes.\aNecesito a alguien que dé un buen ejemplo a todos los dibus de "+lDaisyGardens+".\aUsa tus ataques chorreantes para derrotar a un montón de bots.\aAnima a tus amigos a usar este tipo de ataques.\aCuando hayas derrotado a veinte bots, vuelve para llevarte una recompensa." }, + + 3222 : { QUEST : "Llegó el momento de demostrar tu dibupuntería.\aSi consigues recuperar cierto número de edificios bot, tendrás el privilegio de asumir tres tareas a la vez.\aPrimero, reconquista dos edificios bot cualesquiera.\aLlama a los amigos que quieras para que te ayuden."}, + 3223 : { QUEST : "¡Un gran trabajo con los edificios! \aAhora reconquista dos edificios más.\aLos edificios deben de tener al menos dos pisos de altura." }, + 3224 : { QUEST : "¡Fantástico!\aAhora basta con que recuperes dos edificios más.\aLos edificios deben tener al menos tres pisos de altura.\aCuando termines, vuelve para obtener tu recompensa.", + COMPLETE : "¡Lo lograste, _avName_!\a¡Tienes una dibupuntería increíble!", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ dice que necesita ayuda.\a¿Por qué no vas a verla para ver en qué la puedes ayudar?_where_" }, + 3235 : { QUEST : "¡Ah, esta debe de ser la ensalada que encargué!\aGracias por traérmela.\aTodos esos bots deben de haber asustado al repartidor habitual de _toNpcName_.\a¿Por qué no nos haces un favor y derrotas a unos cuantos bots de ahí fuera?\aDerrota a diez bots en los "+lDaisyGardens+" y vuelve con _toNpcName_.", + INCOMPLETE_PROGRESS : "¿No estabas venciendo a los bots por mí?\a¡Maravilloso! ¡Sigue así!", + COMPLETE : "¡Oh, muchas gracias por derrotar a esos bots!\aAhora quizás pueda seguir con mis repartos normales.\aTu recompensa es...", + INCOMPLETE_WRONG_NPC : "Informa a _toNpcName_ sobre los bots a los que derrotate._where_" }, + + 3236 : { QUEST : "Hay demasiados abogabots ahí.\a¡Ayuda en lo que puedas!\aRecupera tres edificios de abogabots." }, + 3237 : { QUEST : "¡Un gran trabajo con los edificios de abogabots! \a¡Pero ahora hay demasiados vendebots!\aRecupera tres edificios de vendebots y vuelve a buscar tu recompensa." }, + + 3238 : { QUEST : "¡Oh, no! ¡Un bot \"confraternizador\" robó la llave de los "+lDaisyGardens+"!\aIntenta recuperarla.\aRecuerda, sólo encontrarás al confraternizador en el interior de edificios de vendebots. " }, + 3239 : { QUEST : "Sí, encontraste una llave, pero no es la correcta.\aNecesitamos la llave de los "+lDaisyGardens+".\a¡Sigue buscando! ¡La tiene un bot \"confraternizador\"!" }, + + 3242 : { QUEST : "¡Oh, no! ¡Un bot picapleitos robó la llave de "+lDaisyGardens+"!\aIntenta recuperarla.\aRecuerda, solo encontrarás al picapleitos en el interior de edificios de abogabots. " }, + 3243 : { QUEST : "Sí, encontraste una llave, pero no es la correcta.\aNecesitamos la llave de los "+lDaisyGardens+".\a¡Sigue buscando! ¡La tiene un bot picapleitos!" }, + + 3240 : { QUEST : "_toNpcName_ me acaba de decir que un picapleitos le robó una bolsa de alpiste.\aDerrota a los picapleitos hasta que encuentres el alpiste de Federico Tilla y llévaselo.\aSólo encontrarás a los picapleitos en el interior de los edificios de abogabots._where_", + COMPLETE : "¡Oh, muchas gracias por encontrar el alpiste!\aTu recompensa es...", + INCOMPLETE_WRONG_NPC : "¡Muy bien, lograste recuperar el alpiste!\aAhora llévaselo a _toNpcName_._where_", + }, + + 3241 : { QUEST : "Algunos edificios bot están creciendo demasiado para nuestro gusto.\aIntenta recuperar algunos de los edificios más altos.\aReconquista cinco edificios de tres plantas o más y vuelve para llevarte una recompensa.", + }, + + 3250 : { QUEST : "Doña Citronia recibió información sobre la existencia de un cuartel general vendebot en la calle Arce.\aReúnete con ella para investigarlo.", + }, + 3251 : { QUEST : "Aquí se cuece algo.\aHay muchísimos vendebots.\aDicen que construyeron su propio cuartel general al final de la calle.\aRecórrela a ver si averiguas lo que está pasando.\aBusca vendebots por su cuartel general, derrota a cinco de ellos y regresa.", + }, + 3252 : { QUEST : "Vamos, suéltala.\a¿Qué dices?\a¿Un cuartel general vendebot? ¡Dios mío! Hay que actuar.\aDebemos poner al corriente a Doña Zanahoria, ella sabrá qué hacer.\aVete corriendo y cuéntale lo que sabes. Está ahí mismo, bajando la calle.", + }, + 3253 : { QUEST : "¿Qué quieres? Estoy muy ocupada.\a¿Cómo? ¿Un cuartel general bot?\aQué bobada. Es imposible.\aTiene que ser un error. Es inconcebible.\a¿Cómo? No me lo discutas.\aBueno, mira, si quieres, tráeme pruebas.\aSi los vendebots están construyendo su cuartel general, todos los bots que anden por ahí llevarán planos.\aA los bots les encanta la burocracia, ¿no sabías? \aVe derrotando vendebots en el lugar que me dices hasta que encuentres los planos.\aLuego me los traes y ya veremos si te creo o no.", + }, + 3254 : { QUEST : "¿Otra vez tú por aquí? ¿Cómo? ¿Planos? ¿Los conseguiste?\aDéjame que los vea. Vaya, ¿una fábrica?\aDebe de ser la planta donde construyen los vendebots... ¿Y esto qué es?\aJusto lo que pensaba.\aComo sospechaba, están construyendo un cuartel general vendebot.\aQué problema. Voy a tener que hacer unas cuantas llamadas. Mira, estoy muy ocupada. Adiós.\a¿Cómo? Sí, sí. Llévale los planos a Doña Citronia.\aSeguro que ella los entenderá mejor que yo.", + COMPLETE : "¿Qué te dijo Doña Zanahoria?\aEntonces, teníamos razón. Esto es muy peligroso. ¿A ver los planos?\aVaya, parece que los vendebots construyeron una planta con maquinaria para la fabricación de bots.\aEsto me huele muy mal. Mantente al margen hasta que consigas más puntos de risa.\aCuando reúnas más podremos seguir con la investigación.\aPor ahora, aquí tienes tu recompensa. ¡Muy bien!", + }, + + + 3255 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3256 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3257 : { QUEST : "_toNpcName_ está investigando el asunto del cuartel general vendebot.\aVete a ver si le puedes prestar tu ayuda._where_" }, + 3258 : { QUEST : "No sabemos realmente qué es lo que están tramando los bots en su nuevo cuartel general.\aQuiero que me traigas información, pero obtenida directamente de los propios bots.\aSi conseguimos cuatro notas de oficina de los vendebots sacadas del mismo cuartel, podremos hacernos una idea más clara.\aTráeme la primera nota en cuanto la consigas así voy estudiando la información a medida que aparezca.", + }, + 3259 : { QUEST : "¡Estupendo! A ver qué dice la nota.\a\"Estimados vendebots:\"\a\"Estaré en mi oficina del penthouse de las torres vendebots ascendiendo bots a niveles superiores.\"\a\"Cuando reúnan los méritos necesarios, suban a mi oficina en el ascensor del vestíbulo de entrada.\"\a\"Se acabó el descanso. ¡A trabajar!\"\a\"Firmado, vendebot VIP\"\a¡Ajá! A "+Flippy+" le va a interesar esto. Se lo voy a mandar ahora mismo.\aMárchate ya en busca de la segunda nota y tráemela en cuanto la tengas.", + }, + 3260 : { QUEST : "Ya regresaste, qué bien. A ver qué encontraste...\a\"Estimados vendebots:\"\a\"Se instaló un sistema de seguridad nuevo en las torres vendebots para impedir el acceso a los dibus.\"\a\"Los dibus que se encuentren en las torres serán detenidos para su interrogatorio.\"\a\"Nos reuniremos en el vestíbulo para discutir el asunto durante el aperitivo.\"\a\"Firmado, Confraternizador\"\aMuy, muy interesante. Tengo que pasar esta información inmediatamente.\aTráeme una tercera nota, por favor.", + }, + 3261 : { QUEST : "¡Muy bien _avName_! ¿Qué dice la nota?\a\"Estimados vendebots:\"\a\"Los dibus se las ingeniaron para infiltrarse en las torres vendebot.\"\a\"Esta noche los llamaré a la hora de la cena para darles más datos.\"\a\"Firmado, Televendedor\"\aMmm, Me pregunto cómo estarán colándose los dibus....\aTráeme una última nota. Creo que con eso bastará para hacerme una idea.", + COMPLETE : "¡Sabía que lo lograrías! A ver, la nota dice...\a\"Estimados vendebots:\"\a\"Ayer comí con el Sr. Hollywood y me dijo que VIP ha estado muy ocupado.\"\a\"Sólo recibirá bots que merezcan un ascenso.\"\a\"Se me olvidaba, quedé con Efusivo para jugar al golf el domingo.\"\a\"Firmado, Fanfarrón\"\aBueno, _avName_, muchas gracias por tu valiosa ayuda.\aToma, aquí tienes tu recompensa.", + }, + + 3262 : { QUEST : "_toNpcName_ posee información reciente sobre la fábrica-cuartel general vendebot.\aVe a ver de qué se trata._where_" }, + 3263 : { GREETING : "¡Qué hay de nuevo, camarada!", + QUEST : "Me llamo Don Silvestre, Silvestre para los amigos.\aVoy al grano, no me gusta andarme por las ramas.\aMira, los vendebots acaban de construir una gran fábrica para reproducirse como hormigas.\aÚnete a unos cuantos camaradas dibus y destruye la dichosa fábrica.\aDentro del cuartel general vendebot podrás localizar el túnel que lleva a la fábrica y luego subir en el ascensor.\aAntes que nada, comprueba que vas bien cargado de bromas y puntos de risa y que te acompañan unos dibus bien fornidos.\aDerrota al capataz dentro de la fábrica y así detendrás el avance vendebot.\a¡Vamos! Ahí tienes tu tablita de ejercicios. Un, dos, un, dos.", + LEAVING : "¡Nos vemos!", + COMPLETE : "¿Qué hay de nuevo, camarada? ¡Haz hecho un buen trabajo en la fábrica!\aAsí que encontraste una pieza de traje bot.\aDebe de ser un excedente de la cadena de montaje bot.\aNos podría venir de lujo. Recoge todas las que puedas siempre que tengas un huequito.\aA lo mejor, si consigues un traje completo reuniendo todas las piezas, podemos sacarle algún provecho...." + }, + + 4001 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsalo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Ahora tienes que elegir el nuevo circuito de trucos que quieres aprender.\aPiénsalo todo lo que quieras y vuelve cuando hayas tomado una decisión.", + INCOMPLETE_PROGRESS : "Antes de elegir, medita tu decisión.", + INCOMPLETE_WRONG_NPC : "Antes de elegir, medita tu decisión.", + COMPLETE : "Muy buena decisión...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Seguro que a Ropo Pompón le viene bien un poco de ayuda en su investigación._where_", + }, + 4201 : { GREETING: "¡Hola!", + QUEST : "Estoy muy preocupado con una racha de robos de instrumentos musicales.\aEstoy llevando a cabo un estudio entre mis compañeros comerciantes.\aTal vez pueda encontrar una pauta que me ayude a resolver el caso.\aPásate por Sonatas y Sonatinas para que Tina te dé su inventario de concertinas. _where_", + }, + 4202 : { QUEST : "Sí, hablé con Ropo esta mañana.\aTengo el inventario aquí mismo.\aLlévaselo, ¿sí?_where_" + }, + 4203 : { QUEST : "¡Fantástico! Uno menos...\aAhora ve a buscar el de Uki._where_", + }, + 4204 : { QUEST : "¡Oh! ¡El inventario!\aMe olvidé de él.\aSeguro que lo tengo terminado para cuando hayas derrotado a diez bots.\aPásate por aquí después y te prometo que estará listo.", + INCOMPLETE_PROGRESS : "31, 32... ¡Uy!\a¡Me hiciste perder la cuenta!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, ahí estás.\aGracias por darme algo de tiempo.\aLlévale esto a Ropo y salúdalo de mi parte._where_", + }, + 4206 : { QUEST : "Mmm, muy interesante.\aAhora sí que estoy encaminado.\aMuy bien, el último inventario es el de Bibi._where_", + }, + 4207 : { QUEST : "¿Inventario?\a¿Cómo voy a hacerlo si no tengo el formulario?\aVe a ver si Sordino Quena puede darme uno._where_", + INCOMPLETE_PROGRESS : "¿Alguna novedad sobre el formulario?", + }, + 4208 : { QUEST : "¡Pues claro que tengo un formulario de inventario!\aPero no es gratis, ¿sabes?\a¿Sabes qué? Te lo daré a cambio de una tarta de nata entera.", + GREETING : "¡Muy buenas!", + LEAVING : "Hasta luego...", + INCOMPLETE_PROGRESS : "No me alcanza con un trozo.\aMe quedaré con hambre. Quiero TODA la tarta.", + }, + 4209 : { GREETING : "", + QUEST : "Mmm...\a¡Qué rica!\aAquí tienes el formulario para Bibi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Gracias. Fuiste de gran ayuda.\aVeamos... Violines Bibi: 2\a¡Ya está! ¡Aquí tienes!", + COMPLETE : "Buen trabajo, _avName_.\aSeguro que ahora llego al fondo de la cuestión de los robos.\a¿Por qué no llegas tú al fondo de esto?", + }, + + 4211 : { QUEST : "Mira, el doctor Pavo Rotti está llamando cada cinco minutos. ¿Puedes ir a ver qué problema tiene?_where_", + }, + 4212 : { QUEST : "¡Guau! Me alegro de que el cuartel general mandara por fin a alguien.\aNo tuve un solo cliente durante días.\aSon estos malditos contables que hay por todas partes.\aCreo que están propagando una mala higiene bucal entre los vecinos.\aDerrota a diez de ellos y veamos si el negocio mejora.", + INCOMPLETE_PROGRESS : "Sigo sin tener clientes. ¡Pero sigue luchando!", + }, + 4213 : { QUEST : "¿Sabes? A lo mejor resulta que los culpables no eran los contables.\aIgual son los chequebots en general.\aAcaba con veinte de ellos y tal vez venga alguien a mi clínica de una vez.", + INCOMPLETE_PROGRESS : "Sé que veinte son muchos. Pero seguro que valdrá la pena.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "¡No lo entiendo!\aSigo sin tener ni un solo cliente.\aA lo mejor hay que atacar a la raíz del problema.\aIntenta reconquistar un edificio de chequebots.\aEso debería bastar.", + INCOMPLETE_PROGRESS : "¡Por favor! ¡Sólo un pequeño edificio nomás!", + COMPLETE : "Sigue sin venir un alma por aquí.\aPero la verdad es que, pensándolo bien...\a¡Tampoco venían clientes antes de que los bots nos invadiesen!\aSin embargo, aprecio de veras tu ayuda.\aSeguro que esto te viene bien." + }, + + 4215 : { QUEST : "Olga necesita desesperadamente a alguien que la ayude.\a¿Por qué no pasas a verla para ver qué puedes hacer?_where_", + }, + 4216 : { QUEST : "¡Gracias por venir tan pronto!\aParece que los bots se hicieron con muchos de los billetes de crucero de mis clientes.\aUki dice que vio a un efusivo irse de aquí con un montón.\aTrata de recuperar el billete a Alaska de Chopo Chopín.", + INCOMPLETE_PROGRESS : "Los efusivos podrían estar en cualquier sitio ya...", + }, + 4217 : { QUEST : "¡Estupendo! ¡Lo encontraste!\a¿Ahora me harías el favor de llevárselo a Chopo Chopín?_where_", + }, + 4218 : { QUEST : "¡Estupendo, estupendísimo!\a¡Alaska, voy para allá!\aYa no soporto a estos malditos bots.\aOye, creo que Olga te vuelve a necesitar._where_", + }, + 4219 : { QUEST : "Sí, lo adivinaste.\aNecesito que sacudas a los pesados de los efusivos para recuperar el boleto de Felisa Felina al festival de Jazz. \aYa sabes cómo funciona...", + INCOMPLETE_PROGRESS : "Hay más en alguna parte...", + }, + 4220 : { QUEST : "¡Estupendo!\a¿Podrías alcanzarle el boleto también?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Hasta luego...", + QUEST : "¡Hola!\aMe voy de viaje, _avName_.\aAntes de irte, más vale que te pases de nuevo a ver a Olga..._where_", + }, + 4222 : { QUEST : "¡Este es el último, lo prometo!\aAhora hay que buscar el boleto de Barbo para el gran concurso de canto.", + INCOMPLETE_PROGRESS : "Vamos, _avName_.\aBarbo cuenta contigo.", + }, + 4223 : { QUEST : "Esto hará que Barbo se alegre mucho._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "¡Sí, sí, SÍ!\a¡Sensacional!\a¿Sabes? Este año, los chicos y yo vamos a arrasar en el concurso.\aOlga dice que vuelvas por ahí para recoger tu recompensa._where_\a¡Adiós, adiós, ADIÓS!", + COMPLETE : "Gracias por todo, _avName_.\aEres muy valioso aquí en Toontown.\aHablando de cosas valiosas...", + }, + + 902 : { QUEST : "Ve a ver a Leo.\aNecesita que alguien entregue un mensaje._where_", + }, + 4903 : { QUEST : "¡Amigo!\aMis castañuelas están empañadas y tengo una gran actuación esta noche.\aLlévaselas a Carlos para ver si las puede limpiar._where_", + }, + 4904 : { QUEST : "Sí, creo que puedo limpiarlas.\aPero necesito un poco de tinta azul de un pulpo.", + GREETING : "¡Hola!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Los pulpos están cerca de los amarraderos.", + }, + 4905 : { QUEST : "¡Sí! ¡Eso es!\aAhora necesito un poco de tiempo para limpiarlas.\a¿Por qué no vas a recuperar un edificio de un piso mientras trabajo?", + GREETING : "¡Hola!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Sólo un poco más...", + }, + 4906 : { QUEST : "¡Muy bien!\aAquí tienes las castañuelas para Leo._where_", + }, + 4907 : { GREETING : "", + QUEST : "¡Muy bien, amigo!\aTienen una pinta estupenda.\aAhora necesito que consigas una copia de la letra de \"Navidades felices\" de Lírica Tástrofe._where_", + }, + 4908 : { QUEST: "¡Muy buenas!\aMmm, no tengo un ejemplar de esa letra a mano.\aSi me dieses algo de tiempo, la podría escribir de memoria.\a¿Por qué no te vas a recuperar un edificio de dos plantas mientras la escribo?", + }, + 4909 : { QUEST : "Lo siento.\aMi memoria ya no es lo que era.\aSi vas a recuperar un edificio de tres plantas, seguro que tendré la letra lista para cuando vuelvas.", + }, + 4910 : { QUEST : "¡Ya está!\aSiento haber tardado tanto.\aLlévasela a Leo._where_", + GREETING : "", + COMPLETE : "¡Genial, amigo!\a¡Mi concierto va a ser la bomba!\aAh, que no se me olvide. Toma, esto te servirá para los bots." + }, + 5247 : { QUEST : "Este barrio es bastante duro...\aTe vendría bien aprender unos cuantos trucos nuevos.\a_toNpcName_ me enseñó todo lo que sé, así que a lo mejor te puede enseñar a ti también._where_" }, + 5248 : { GREETING : "Aah, sí.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que mi tarea te está causando problemas.", + QUEST : "Aaah, un nuevo aprendiz, bienvenido.\aYo sé todo lo que hay que saber sobre tartas.\aPero antes de empezar con tu entrenamiento, tienes que hacerme una pequeña demostración.\aSal fuera y derrota a diez de los bots más grandes." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "¡Excelente!\aAhora, demuéstrame tu habilidad como pescador.\aAyer tiré tres dados de goma al estanque.\aPéscalos y tráemelos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que la caña y y la línea no son tu fuerte." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán estupendos colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de abogabots más grandes.", + INCOMPLETE_PROGRESS : "¿Tienes problemas con los edificios?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán excelentes colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de jefebots más grandes.", + INCOMPLETE_PROGRESS : "¿Tienes problemas con los edificios?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán excelentes colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de chequebots más grandes.", + INCOMPLETE_PROGRESS : "¿Tienes problemas con los edificios?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ajá! Estos dados quedarán excelentes colgados del retrovisor de mi carreta de bueyes.\aAhora demuéstrame que sabes distinguir a los enemigos.\aVuelve cuando hayas reconquistado dos de los edificios de vendebots más grandes.", + INCOMPLETE_PROGRESS : "¿Tienes problemas con los edificios?", }, + 5200 : { QUEST : "Esos bots tan pesados están otra vez dando problemas.\a_toNpcName_ dijo que falta otro objeto. Pásate por ahí, a ver si puedes arreglar la situación._where__where_" }, + 5201 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de cazacabezas estuvo aquí y se llevó mi balón de fútbol.\a¡Su jefe me dijo que tenía que hacer un recorte de plantilla y me lo quitó!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Lograste encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo lograste! Aquí tienes tu recompensa...", + }, + 5261 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de doscaras estuvo aquí y se llevó mi balón de fútbol.\a¡Su jefe me dijo que tenía que hacer un recorte de plantilla y me lo quitó!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Lograste encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo lograste! Aquí tienes tu recompensa...", + }, + 5262 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de monederos estuvo aquí y se llevó mi balón de fútbol.\a¡Su jefe me dijo que tenía que hacer un recorte de plantilla y me lo quitó!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Lograste encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo lograste! Aquí tienes tu recompensa...", + }, + 5263 : { GREETING: "", + QUEST : "Hola, _avName_. Creo que debo darte las gracias por venir.\aUn grupo de portavoces estuvo aquí y se llevó mi balón de fútbol.\a¡Su jefe me dijo que tenía que hacer un recorte de plantilla y me lo quitó!\a¿Puedes recuperar el balón?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Lograste encontrar mi balón?", + COMPLETE : "¡Yujuuu! ¡Lo lograste! Aquí tienes tu recompensa...", + }, + 5202 : { QUEST : lTheBrrrgh+" fue invadida por los bots más duros de pelar que vi en mi vida.\aMás vale que cargues más bromas.\aMe dijeron que es posible que _toNpcName_ tenga una bolsa más grande en la que te cabrán más bromas._where_" }, + 5203 : { GREETING: "¿Eh? ¿Estás en mi equipo de trineo?", + QUEST : "¿Qué? ¿Quieres una bolsa?\aEl caso es que tenía una por aquí... ¿Estará en mi trineo?\aPero... ¡No veo mi trineo desde la gran carrera!\a¿Se lo habrá llevado uno de esos bots?", + LEAVING : "¿Viste mi trineo?", + INCOMPLETE_PROGRESS : "¿Quién decías que eras? Lo siento, estoy un poco mareado por el choque." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "¿Es ese mi trineo? No veo ninguna bolsa por aquí.\aCreo que Perico Arenque estaba en el equipo... ¿La tendrá él?_where_" }, + 5205 : { GREETING : "¡Oooh, la cabeza!", + LEAVING : "", + QUEST : "¿Eh? ¿Doroteo qué? ¿Una bolsa?\aAh, ¿no era miembro de nuestro equipo de trineo?\aMe duele tanto la cabeza que no puedo pensar bien.\a¿Puedes pescar unos cuantos cubitos de hielo en el estanque para que me los ponga en la cabeza?", + INCOMPLETE_PROGRESS : "¡Ayyy, la cabeza me va a estallar! ¿Tienes un poco de hielo?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "¡Aaah, mucho mejor!\aAsí que buscas la bolsa de Doroteo, ¿eh?\aCreo que terminó en la cabeza de Nutrio Cenutrio después del choque._where_" }, + 5207 : { GREETING : "¡Eeeh!", + LEAVING : "", + QUEST : "¿Bolsa? ¿Quién ser Perico?\a¡Yo tener miedo de edificios! ¡Tú derrotar edificios, yo darte bolsa!", + INCOMPLETE_PROGRESS : "¡Más edificios! ¡Yo todavía tener miedo!", + COMPLETE : "¡Oooh! ¡Tú gustarme!" }, + 5208 : { GREETING : "", + LEAVING : "¡Eeeh!", + QUEST : "¡Oooh! ¡Tú gustarme!\aTú ir a clínica de esquí. Bolsa ahí." }, + 5209 : { GREETING : "¡Amigo!", + LEAVING : "¡Nos vemos!", + QUEST : "¡Amigo, ese tal Nutrio Cenutrio está loco!\aCompadre, si estás loco como Nutrio, la bolsa será tuya.\a¡Embólsate a unos cuantos bots y te harás con la bolsa, compañero! ¡Vamos!", + INCOMPLETE_PROGRESS : "¿Estás seguro de que eres bastante salvaje? Ve a destruir a los bots.", + COMPLETE : "¡Eh, eres todo un campeón! ¡Les diste de lo lindo a un montón de bots!\a¡Aquí tienes la bolsa!" }, + + 5210 : { QUEST : "_toNpcName_ está enamorada en secreto de alguien del barrio.\aSi la ayudas, te recompensará de lo lindo._where_" }, + 5211 : { GREETING: "¡Buaaaa!", + QUEST : "Me pasé toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots con pico entró y se la llevó.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5264 : { GREETING: "¡Buaaaa!", + QUEST : "Me pasé toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots con aleta entró y se la llevó.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5265 : { GREETING: "¡Buaaaa!", + QUEST : "Me pasé toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots confraternizadores entró y se la llevó.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5266 : { GREETING: "¡Buaaaa!", + QUEST : "Me pasé toda la noche escribiendo al perro que amo.\aPero antes de que pudiera entregar la carta, uno de esos apestosos bots corporativistas entró y se la llevó.\a¿Me haces el favor de recuperarla?", + LEAVING : "¡Buaaaa!", + INCOMPLETE_PROGRESS : "Por favor, encuentra mi carta." }, + 5212 : { QUEST : "¡Oh, gracias por encontrar la carta!\aPor favor, ¿podrías entregársela al perro más bonito de todo el barrio?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "No entregaste la carta, ¿verdad?", + }, + 5213 : { GREETING : "Encantado de verte.", + QUEST : "Lo siento, pero no puedo molestarme con tu carta.\a¡Me quitaron a todos mis dibuperritos!\aTráemelos y hablaremos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡Mis pobres dibuperritos!" }, + 5214 : { GREETING : "", + LEAVING : "¡Hasta luego!", + QUEST : "Gracias por devolverme a mis preciosidades.\aEchemos un vistazo a tu carta...\nMmmm, parece que tengo otra admiradora secreta.\aEsto pide a gritos una visita a mi querido amigo Carlos Congelado.\a¡Seguro que te cae muy bien!_where_" }, + 5215 : { GREETING : "Je, je...", + LEAVING : "Vuelve, sí, sí.", + INCOMPLETE_PROGRESS : "Todavía quedan unos cuantos grandullones. Vuelve cuando ya no estén.", + QUEST : "¿Quién te envió? No me gustan los forasteros, no...\aPero menos me gustan los bots...\aAcaba con los grandotes y ya veremos si te ayudo." }, + 5216 : { QUEST : "Te dije que te vamos a ayudar.\aAsí que llévale este anillo a la chica.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡¿Sigues teniendo el anillo?!", + COMPLETE : "¡Oh, queriiiido! ¡¡¡Gracias!!!\aAh, también tengo algo especial para ti.", + }, + 5217 : { QUEST : "Parece que a _toNpcName_ le vendría bien algo de ayuda._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguro que hay confraternizadores por algún sitio.", + QUEST : "¡¡¡Socorro!!! ¡¡¡Socorro!!! ¡Ya no lo aguanto más!\a¡Esos confraternizadores me están enloqueciendo!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "No pueden ser todos. ¡Sólo vi a uno!", + QUEST : "¡Ey, gracias, pero ahora son los corporativistas!\a¡Tienes que ayudarme!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡No, no, no, había uno justo aquí!", + QUEST : "¡Ahora me doy cuenta de que son esos prestamistas despiadados!\a¡Creía que ibas a salvarme!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "¿Sabes qué? ¡Tal vez la culpa no es de los bots!\a¿Puedes pedirle a Pega Moide que me prepare una poción calmante? A lo mejor eso ayuda..._where_" }, + 5222 : { LEAVING : "", + QUEST : "¡El tal Cris Térico es todo un personaje!\a¡Voy a prepararle algo que le pondrá a tono!\aVaya, parece que me quedé sin aletas de sardina...\aPórtate bien y ve al estanque a pescarme unas cuantas.", + INCOMPLETE_PROGRESS : "¿Ya conseguiste las aletas?", }, + 5223 : { QUEST : "Bien. Gracias, cariño.\aToma, ahora llévale esto a Cris. Seguro que se calma enseguida.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vamos, llévale la poción a Cris.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hazme el favor de acabar con los picapleitos ¿sí?", + QUEST : "¡Oh, gracias a Dios que regresaste!\a¡¡¡Dame la poción, rápido!!!\aGlu, glu, glu...\a¡Puaj, qué mal sabía!\aPero ¿sabes qué? Me siento mucho más tranquilo. Ahora que puedo pensar con claridad, me doy cuenta de que...\a¡¡¡Eran los picapleitos los que me volvían loco todo el rato!!!", + COMPLETE : "¡Es estupendo! ¡Ahora puedo relajarme!\aSeguro que por aquí hay algo que pueda darte. ¡Toma!" }, + 5225 : { QUEST : "Desde el incidente del sándwich de nabos, Felipe el Gruñón estuvo enojado con _toNpcName_. \aA lo mejor puedes ayudar a Frigo a arreglar las cosas entre ellos._where_" }, + 5226 : { QUEST : "Sí, seguro que te contaron que Felipe el gruñón está enojado conmigo...\aSolo intentaba ser amable regalándole un sándwich de nabos.\aA lo mejor puedes animarle.\aFelipe odia a los chequebots, sobre todo sus edificios. \aSi reconquistas unos cuantos edificios de chequebots, tal vez sirva de algo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Y si lo intentas con unos cuantos edificios más?", }, + 5227 : { QUEST : "¡Es fantástico! Ve a contar a Felipe lo que hiciste._where_" }, + 5228 : { QUEST : "¿Eso hizo?\aEse Frigo cree que puede arreglarlo todo así de fácil, ¿eh?\a¡Me rompí una muela con ese sándwich de nabos que me dio!\aQuizá si le llevas la muela al doctor Bocamaraca, él pueda arreglarla.", + GREETING : "Brrrrr.", + LEAVING : "Grrñññ, grññññ.", + INCOMPLETE_PROGRESS : "¿Tú otra vez? ¡Creía que ibas a arreglarme la muela!", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela está bastante mal, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos chequebots?\aEstán asustando a mis pacientes." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela está bastante mal, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos vendebots?\aEstán asustando a mis pacientes." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela está bastante mal, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos abogabots?\aEstán asustando a mis pacientes." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sigo trabajando en la muela. Tardaré un poquito más.", + QUEST : "Sí, la muela está bastante mal, la verdad.\aA lo mejor puedo hacer algo, pero tardaré un poco.\aMientras tanto, ¿podrías limpiar la zona de esos jefebots?\aEstán asustando a mis pacientes." }, + 5230 : { GREETING: "", + QUEST : "¡Me alegro de que hayas regresado!\aDesistí de intentar arreglar la muela y, en su lugar, le fabriqué a Felipe una nueva, de oro.\aPor desgracia, un barón ladrón me la robó.\aSi te das prisa, a lo mejor consigues atraparlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya la muela?" }, + 5270 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aDesistí de intentar arreglar la muela y, en su lugar, le fabriqué a Felipe una nueva, de oro.\aPor desgracia, un pez gordo me la robó.\aSi te das prisa, a lo mejor consigues atraparlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya la muela?" }, + 5271 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aHe desistido de intentar arreglar la muela y, en su lugar, le fabriqué a Felipe una nueva, de oro.\aPor desgracia, un Sr. Hollywood me la robó.\aSi te das prisa, a lo mejor consigues atraparlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya la muela?" }, + 5272 : { GREETING: "", + QUEST : "¡Me alegro de que hayas vuelto!\aDesistí de intentar arreglar la muela y, en su lugar, le fabriqué a Felipe una nueva, de oro.\aPor desgracia, un pelucón me la robó.\aSi te das prisa, a lo mejor consigues atraparlo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya la muela?" }, + 5231 : { QUEST : "¡Estupendo, esa es la muela!\a¿Por qué no me haces el favor de llevársela a Felipe?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguro que Felipe está impaciente por ver su nueva muela.", + }, + 5232 : { QUEST : "Oh, gracias.\aUmmmmf\a¿Qué tal estoy, eh?\aBueno, puedes decirle a Frigo que le perdono.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, me alegro muchísimo de oír eso.\aMe imaginaba que el cascarrabias de Felipe no podría seguir enojado conmigo.\aComo gesto de amistad, le preparé este sándwich de piñones.\a¿Me haces el favor de llevárselo? ", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Date prisa, por favor. El sándwich de piñones está más rico cuando está calentito.", + COMPLETE : "Vaya, ¿qué es esto? ¿Es para mí?\aÑam, ñam....\a¡Aaay! ¡Mi muela! ¡Ese Frigo Sabañón!\aBueno, no fue culpa tuya. Toma, llévate esto como recompensa por tu esfuerzo.", + }, + 903 : { QUEST : "Creo que estás listo para ir a ver a _toNpcName_, en Ventisca a la Vista, para tu prueba final._where_", }, + 5234 : { GREETING: "", + QUEST : "Ah, regresaste.\aAntes de empezar, tenemos que comer algo.\aTráenos un poco de queso abultado para el caldo.\aEl queso abultado sólo se puede conseguir de los bots peces gordos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguimos necesitando queso abultado." }, + 5278 : { GREETING: "", + QUEST : "Ah, regresaste.\aAntes de empezar, tenemos que comer algo.\aTráenos un poco de caviar para el caldo.\aEl caviar sólo se puede conseguir de los bots Sr. Hollywood.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Seguimos necesitando caviar." }, + 5235 : { GREETING: "", + QUEST : "Los hombres sencillos comen con cucharas sencillas.\aUn bot se llevó mi cuchara sencilla, así que sencillamente, no puedo comer.\aTráeme mi cuchara. Creo que un barón ladrón se la llevó.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Es sencillo: debo recuperar la cuchara." }, + 5279 : { GREETING: "", + QUEST : "Los hombres sencillos comen con cucharas sencillas.\aUn bot se llevó mi cuchara sencilla, así que no puedo comer.\aTráeme mi cuchara. Creo que un pelucón se la llevó.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Es sencillo: debo recuperar la cuchara." }, + 5236 : { GREETING: "", + QUEST : "Oh, gracias.\aSlurp, slurp...\aAaah, ahora tienes que atrapar a un sapo parlanchín. Ponte a pescar en el estanque.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el sapo parlanchín?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Todavía no conseguiste el postre.", + QUEST : "Vaya, no cabe duda de que es un sapo parlanchín. Dámelo.\a¿Qué dices, sapo?\aAjá.\aEntiendo...\aEl sapo habló. Necesitamos un postre.\aTráenos unos cuantos cucuruchos de helado de _toNpcName_.\aPor algún motivo, al sapo le gusta el helado de judías pintas._where_", }, + 5238 : { GREETING: "", + QUEST : "Así que te envía Fredo Dedo. Siento decirte que nos quedamos sin cucuruchos de helado de judías pintas.\aVerás, un grupo de bots entró y se los llevó.\aDijeron que eran para un señor de Hollywood o algo así.\aTe agradecería mucho que los recuperases.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya los cucuruchos de helado?" }, + 5280 : { GREETING: "", + QUEST : "Así que te envía Fredo Dedo. Siento decirte que nos quedamos sin cucuruchos de helado de judías pintas.\aVerás, un grupo de bots entró y se los llevó.\aDijeron que eran para el pez gordo o algo así.\aTe agradecería mucho que los recuperases.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste ya los cucuruchos de helado?" }, + 5239 : { QUEST : "¡Gracias por recuperar los cucuruchos de helado!\aAquí tienes uno para Fredo Dedo.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Más vale que le lleves el helado a Fredo Dedo antes de que se derrita.", }, + 5240 : { GREETING: "", + QUEST : "Muy bien. Aquí tienes, sapo...\aSlurp, slurp...\aMuy bien, estamos casi listos.\aSi pudieses traerme un poco de talco para secarme las manos...\aCreo que los bots pelucones llevan a veces polvos de talco en la peluca.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el talco?" }, + 5281 : { GREETING: "", + QUEST : "Muy bien. Aquí tienes, sapo...\aSlurp, slurp...\aMuy bien, estamos casi listos.\aSi pudieses traerme un poco de talco para secarme las manos...\aCreo que los bots Sr. Hollywood llevan a veces polvos de talco para empolvarse la nariz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el talco?" }, + 5241 : { QUEST : "Está bien.\aComo dije en su día, para lanzar bien una tarta, no se debe usar la mano...\a... sino el alma.\aNo sé qué significa eso, así que me sentaré a contemplar cómo reconquistas edificios.\aVuelve cuando hayas terminado tu tarea.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu tarea todavía no terminó.", }, + 5242 : { GREETING: "", + QUEST : "Aunque sigo sin saber de qué hablo, no cabe duda de que eres de gran valor.\aTe asigno una tarea final...\aAl sapo parlanchín le gustaría tener una novia.\aBusca otro sapo parlanchín. El sapo ha hablado.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el otro sapo parlanchín?", + COMPLETE : "¡Uau! Estoy cansado de todo este esfuerzo. Voy a descansar.\aToma, ten tu recompensa y vete." }, + + 5243 : { QUEST : "Pedro Glaciares está empezando a apestar la calle.\a¿Puedes convencerle de que se dé una ducha?_where_" }, + 5244 : { GREETING: "", + QUEST : "Sí, supongo que suelo sudar bastante.\aMmm, a lo mejor si pudiese arreglar la tubería que gotea en mi ducha...\aSupongo que un engranaje de esos bots diminutos me servirá.\aVe a buscar un engranaje de un microgerente y lo intentaremos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Dónde está el engranaje ese que me ibas a traer?" }, + 5245 : { GREETING: "", + QUEST : "Sí, parece que eso funcionó.\aPero cuando me ducho me siento solo...\a¿Podrías ir a pescarme un patito de goma para que me haga compañía?", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el patito?" }, + 5246 : { QUEST : "El patito es estupendo, pero...\atodos esos edificios alrededor me ponen nervioso.\aMe sentiría mucho más relajado si hubiese menos edificios cerca.", + LEAVING : "", + COMPLETE : "Bueno, me voy a dar una ducha. Toma, esto es para ti.", + INCOMPLETE_PROGRESS : "Siguen preocupándome los edificios.", }, + 5251 : { QUEST : "Creo que Pago Gelado va a dar un recital esta noche.\aMe dijeron que el material del concierto le está dando problemas._where_" }, + 5252 : { GREETING: "", + QUEST : "¡Ah, sí! Pues claro que me viene bien algo de ayuda.\a"+TheCogs+" vinieron y me robaron todo el equipo mientras descargaba la furgoneta.\a¿Puedes echarme una mano recuperando el micrófono?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Oye, compañero, no puedo cantar sin un micrófono." }, + 5253 : { GREETING: "", + QUEST : "¡Sí, ese es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\alo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo llevó uno de esos corporativistas.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el teclado?" }, + 5273 : { GREETING: "", + QUEST : "¡Sí, ese es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\alo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo llevó uno de esos confraternizadores.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el teclado?" }, + 5274 : { GREETING: "", + QUEST : "¡Sí, ese es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\alo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo llevó uno de esos prestamistas despiadados.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el teclado?" }, + 5275 : { GREETING: "", + QUEST : "¡Sí, ese es mi micrófono, muy bien!\aGracias por recuperarlo, pero...\alo que necesito de verdad es el teclado para poder tocar unas cuantas notas.\aCreo que se lo llevó uno de esos picapleitos.", + LEAVING : "", + INCOMPLETE_PROGRESS : "¿Encontraste el teclado?" }, + 5254 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que acabaron en manos de un tal Sr. Hollywood.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, "+lTheBrrrgh+"!\a¿Eh? ¿Dónde está la gente?\aBueno, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5282 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que acabaron en manos de un pez gordo.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, "+lTheBrrrgh+"!\a¿Eh? ¿Dónde está la gente?\aBueno, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5283 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que acabaron en manos de un barón ladrón.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, "+lTheBrrrgh+"!\a¿Eh? ¿Dónde está la gente?\aBueno, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + 5284 : { GREETING: "", + QUEST : "¡Muy bien! Ahora podré actuar.\aSi no se hubiesen llevado mis zapatos de plataforma...\aSeguro que acabaron en manos de un pelucón.", + LEAVING : "", + COMPLETE : "¡¡Fantástico!! Ahora sí que estoy listo.\a¡Hola, "+lTheBrrrgh+"!\a¿Eh? ¿Dónde está la gente?\aBueno, toma esto y tráeme unos cuantos fans, ¿de acuerdo?", + INCOMPLETE_PROGRESS : "No querrás que actúe descalzo, ¿no? " }, + + 5255 : { QUEST : "Creo que te vendrían bien más puntos de risa.\aQuizá puedas hacer un trato con _toNpcName_.\aNo te olvides de ponerlo por escrito..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un trato es un trato.", + QUEST : "Así que quieres puntos de risa, ¿eh?\a¡Te propongo un trato!\aSi te ocupas de unos cuantos jefebots...\aYo te recompensaré por ello." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un trato es un trato.", + QUEST : "Así que quieres puntos de risa, ¿eh?\a¡Te propongo un trato!\aSi te ocupas de unos cuantos abogabots...\aYo te recompensaré por ello." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Bueno, pero estoy seguro de que te dije que te ocupases de unos cuantos abogabots.\aBueno, si tú lo dices... Pero me debes una.", + INCOMPLETE_PROGRESS : "Creo que todavía no terminaste.", + QUEST : "¿Dices que terminaste? ¿Derrotaste a todos los bots?\aMe entenderías mal; nuestro trato se refería a los vendebots.\aEstoy segurísimo de que te dije que te ocupases de unos cuantos vendebots." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Bueno, pero estoy seguro de que te dije que te ocupases de unos cuantos abogabots.\aBueno, si tú lo dices... Pero me debes una.", + INCOMPLETE_PROGRESS : "Creo que todavía no terminaste.", + QUEST : "¿Dices que terminaste? ¿Derrotaste a todos los bots?\aMe entenderías mal, nuestro trato se refería a los chequebots.\aEstoy segurísimo de que te dije que te ocupases de unos cuantos chequebots." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "No puedo ayudarte con puntos de risa, pero quizás _toNpcName_ haga un trato contigo.\aAunque es un poco temperamental..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "¡¿Que yo te dije... qué?!\a¡Muchas gracas! ¡Toma tu punto de risa!", + INCOMPLETE_PROGRESS : "¡Hola!\a¿Qué haces aquí otra vez?", + QUEST : "¿Un punto de risa? ¡No lo creo!\aClaro, pero solo si antes quitas de en medio a unos cuantos de estos abogabots tan molestos." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" se está aliando con bots muy peligrosos.\aYo en tu lugar, llevaría más bromas.\aDicen que _toNpcName_ puede conseguirte una bolsa grande si estás dispuesto a hacer el trabajo._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tiene que haber muchos abogabots ahí fuera.\a¡Vamos!" , + QUEST : "¿Una bolsa más grande?\aProblemente te pueda conseguir una.\aPero voy a necesitar hilo.\aUnos abogabots se llevaron el mío ayer por la mañana." }, + 5305 : { GREETING : "¡Ah, del barco!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ve a buscar más bots.\aEste color todavía no se filtró.", + QUEST : "¡Qué hilo tan bueno!\aAunque el color no sea precisamente mi favorito.\aTe diré una cosa...\aSal y derrota a algunos de esos bots tan duros...\aY yo me pondré a teñir el hilo." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tienen que estar por aquí abajo...", + QUEST : "Bueno, el hilo ya está teñido. Pero tenemos un pequeño problema.\aNo encuentro mis agujas de punto.\aLa última vez que las vi estaban en el estanque." }, + 5307 : { GREETING : "", + LEAVING : "¡Será un placer!", + INCOMPLETE_PROGRESS : "¡Roma no se construyó en un día!" , + QUEST : "Esas son mis agujas.\aMientras hilo, ¿por qué no vas a eliminar algunos de esos edificios grandes?", + COMPLETE : "¡Buen trabajo!\aY hablando de buen trabajo...\a¡Toma tu nueva bolsa!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "Dicen que _toNpcName_ tiene problemas legales.\a¿Puedes pasarte a ver de qué se trata?_where_" }, + 5309 : { GREETING : "Me alegro de que estés aquí...", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡Date prisa! ¡Las calles están llenas!", + QUEST : "Los abogabots se hicieron con todo ahí fuera.\aMe temo que me van a llevar a juicio.\a¿Crees que podrías ayudar a sacarlos de esta calle?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Creo que los oigo como vienen a por mí...", + QUEST : "Gracias. Me siento un poco mejor.\a Pero hay una cosa más...\a¿Puedes ir a ver a _toNpcName_ y conseguirme una coartada?_where_" }, + 5311 : { GREETING : "¡¡¡UAAAAA!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "¡Si no lo encuentras, no puedo ayudarle!", + QUEST : "¡¿Coartada?! ¡Qué idea tan buena!\a¡Mejor que sean dos!\aSeguro que el picapleitos tiene unas cuantas..." }, + 5312 : { GREETING : "¡Por fin!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "¡Uf! Qué alivio contar con esto.\aToma tu recompensa...", + QUEST : "¡Fantástico! ¡Será mejor que se las lleves a _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Apagona Plómez necesita ayuda. ¿Puedes pasarte a echarle una mano?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "¡Oh, un cliente! ¡Estupendo! ¿Qué puedo hacer por ti?\a¿Qué quieres decir con eso de qué puedes hacer por mí? ¡OH! Si no eres un cliente.\aAhora recuerdo. Viniste a ayudar con esos horribles bots.\aBueno, me vendría muy bien la ayuda, aunque no seas un cliente.\aSi limpias un poco las calles, tengo una cosita para ti.", + INCOMPLETE_PROGRESS : "Si no quieres electricidad, no puedo ayudarte hasta que derrotes a esos bots.", + COMPLETE : "Buen trabajo con esos bots, _avName_.\a¿Seguro que no quieres un poco de electricidad? Podría resultarte útil....\a¿No? OK, como quieras.\a¿Eh? Oh sí, ya recuerdo. Toma. Seguro que te resulta útil con esos bots tan horribles.\a¡Sigue así!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Bueno, _avName_, en este momento no tengo nada para ti.\a¡Espera! Creo que Susana Siesta buscaba ayuda. ¿Por qué no vas a verla?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "¡Nunca haré plata con todos esos malditos bots espantándome clientes!\aTienes que ayudarme, _avName_.\aElimina algunos edificios bot por el bien del vecindario y yo contribuiré a tu riqueza.", + INCOMPLETE_PROGRESS : "¡Pobre de mí! ¿No puedes deshacerte de esos edificios?", + COMPLETE : "¡Voy a hacer mucho dinero! ¡Ahora lo veo!\a¡Pasaré todo mi tiempo pescando. Deja que enriquezca un poco tu vida.\a¡Toma!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "¡Eh, _avName_! Dicen que Linda Legal te buscaba.\aDeberías hacerle una visita._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "¡Hola! ¡Qué alegría verte!\aEn mi tiempo libre, estoy trabajando con esta contestadora, pero me faltan un par de piezas.\aNecesito tres barras más y los de agarrados funcionan muy bien.\a¿Podías conseguirme algunos?", + INCOMPLETE_PROGRESS : "¿Todavía estás intentando encontrar las barras?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, esto me será muy útil.\aQué raro, seguro que tenía una correa de transmisión por aquí, pero no la encuentro.\a¿Podrías conseguir una de un monedero? ¡Gracias!", + INCOMPLETE : "Bueno, no te puedo ayudar hasta que consiga la correa de transmisión.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, eso es. Ahora irá como la seda.\a¿Dónde está mi pinza? No puedo apretar esto sin pinza.\aA lo mejor las tenazas de un cacomatraco servirán.\aSi las encuentras, te daré algo para ayudarte con esos bots.", + INCOMPLETE_PROGRESS : "¿Todavía no tienes las tenazas? Sigue buscando.", + COMPLETE : "¡Genial! Ahora solo tengo que apretar esto.\aParece que ya funciona. ¡El negocio vuelve a funcionar!\aBueno, excepto que no tenemos teléfono. De todas formas, gracias por tu ayuda.\aCreo que esto te ayudará con esos bots. ¡Suerte!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "Dicen que Tronco Sópez buscaba ayuda. Ve a ver qué puedes hacer por él._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "¡Eh! Viniste al lugar perfesto. No me van mu' bien las cosas.\aSí, necesito un poco d’ayuda con esos bots. Siempre vienen y me menean pá aquí y pá allá.\aSi pués quitarme a unos cuantos de esos jefebots, te recompensaré.", + INCOMPLETE_PROGRESS : "Eh, _avName_, ¿qué pasa contigo?\aTienes que perserguí a esos jefebots. Tenemos un trato, ¿t'acuerdas?\aTronco Sópez es un tipo de honor.", + COMPLETE : "¡Eh, _avName_! Estás en mi lista de güenos.\aLos jefebots esos ya no son tan pesaos, ¿eh?\a¡Toma! Esto te vendrá que ni pintao. ¡Y no te metas en líos, diantre!", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "Nat, en el Centro Pijama, oyó rumores sobre un Cuartel General chequebot.\aVete para allá a ver si puedes ayudarle._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "Tengo la sensación de que pasa algo extraño.\aQuizás sean las pulgas, pero bueno, algo pasa.\a¡Todos estos chequebots!\aCreo que abrieron otro cuartel general justo a las afueras de Centro Pijama.\aP.J. conoce la zona.\aVe a ver a _toNpcName_ _where_ Pregúntale si sabe algo.", + INCOMPLETE_PROGRESS : "¿Todavía no fuiste a ver a P.J.? ¿A qué esperas?\a¡Oh, malditas pulgas!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "Eh, _avName_, ¿a dónde vas?\a¿Al cuartel general de los chequebots? Yo no vi nada.\a¿Podrías ir al final de Centro Pijama a ver si es cierto?\aBusca chequebots en su cuartel general, derrota a unos cuantos y regresa a contármelo.", + INCOMPLETE_PROGRESS : "¿Ya encontraste el cuartel general? Para poder investigarlo, tendrás que derrotar a unos cuantos chequebots.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "¡¿Qué?! ¿Es VERDAD que existe un cuartel general de chequebot?\a¡Ve inmediatamente a contárselo a Nat!\a¿Quién le iba a decir que tendría un cuartel general de bots tan cerca?", + INCOMPLETE_PROGRESS : "¿Qué dijo Natsay? ¿Todavía no le viste?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "Me muero de ganas por saber lo que dijo P.J.\aUmm... ¡necesitamos más información sobre ese asunto de los bots, pero todavía tengo que deshacerme de estas pulgas!\a¡Ya sé! ¡TÚ puedes ir a investigar!\a¡Ve al cuartel general, derrota a unos cuantos chequebots, encuentra unos planos y regresa!", + INCOMPLETE_PROGRESS : "¿Todavía no tienes los planos? ¡Sigue buscando!\a¡Tienen que tener algunos planos!", + COMPLETE : "¿Ya tienes los planos?\a¡Fantástico! A ver que dicen.\aYa veo... los chequebots construyeron una fabrica de monedas para fabricar botdólares.\aDebe de estar llena de chequebots. Tenemos que investigar más.\aQuizás, si fueras disfrazado... ¡Espera! Creo que tengo parte de un traje bot por alguna parte...\a¡Ya lo tengo! ¿Por qué no aceptas esto, por las molestias? ¡Y gracias de nuevo por tu ayuda!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "¡La condesa te estuvo buscando por todas partes! Por favor, ve a visitarla para que deje de llamar._where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, ¡cuento con tu ayuda!\aVerás, estos bots están haciendo tanto ruido que no me puedo concentrar.\a¡Todo el rato pierdo la cuenta de mis ovejas!\a¡Si consigues reducir el ruido, yo también te ayudaré! ¡Cuenta con ello!\aEeh... ¿por dónde iba? Ah, sí: ciento treinta y seis, ciento treinta y siete...", + INCOMPLETE_PROGRESS : "Cuatrocientos cuarenta y dos... cuatrocientos cuarenta y tres...\a¿Qué? ¿Ya estás de vuelta? ¡Pero si todavía hay mucho ruido!\aOh no, volví a desconcentrarme.\a Uno... dos... tres....", + COMPLETE : "Quinientos noventa y tres... quinientos noventa y cuatro...\a¿Hola? Oh, ¡sabía que podía contar contigo! Ahora está todo mucho más tranquilo.\aToma, por todos esos contables.\a¿Números? ¡Oh, ahora tengo que volver a empezar desde el principio! Uno... dos...", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "La pobre Zari tiene el vehículo estropeado y ahora no puede hacer entregas a sus clientes. Le vendría bien un poco de ayuda._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, hola, _avName_. ¿Viniste a ayudarme con las entregas?\a¡Fantástico! Con este trasto estropeado es difícil moverse por ahí.\aDéjame ver... ok, esto parece fácil. Cowboy Jorge encargó un banjo la semana pasada.\a¿Podrías llevárselo? _where_", + INCOMPLETE_PROGRESS : "¡Oh, hola! ¿Olvidaste algo? Cowboy Jorge está esperando su banjo.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "¡Mi banjo! ¡Por fin! Estoy deseando empezar a tocar.\aDale las gracias a Zari de mi parte, ¿quieres?", + INCOMPLETE_PROGRESS : "Gracias otra vez por el banjo. ¿Zari no tiene más entregas para ti?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "Qué rápido. ¿Qué es lo siguiente de mi lista?\aAh, sí. El Maestro Marcos encargó una pulidora de hielo. Qué tipo tan estrafalario.\a¿Podrías llevarle esto, por favor?_where_", + INCOMPLETE_PROGRESS : "Esa pulidora de hielo es para el Maestro Marcos._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "¡Bien! ¡La pulidora de hielo que encargué!\aSi no hubiera tantos bots por ahí, tendría tiempo de usarla.\aVamos, sé bueno y encárgate de unos cuantos de estos chequebots, ¿quieres?", + INCOMPLETE_PROGRESS : "Esos chequebots son duros, ¿eh? Me va a ser difícil probar la pulidora de hielo.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "¡Extupendo! Ya puedo probar mi pulidora de hielo.\aDile a Zari que la semana que viene iré a hacer mi siguiente encargo.", + INCOMPLETE_PROGRESS : "Eso es todo por ahora. ¿No te está esperando Zari?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "¿Quedó satisfecho el Maestro Marcos con su pulidora de hielo? Bien.\a¿Quién va ahora? Ah, Zen Glen encargó un esterilla cebra.\a¡Aquí está! ¿Podrías pasar por su casa, por favor?_where_", + INCOMPLETE_PROGRESS : "Creo que Zen Glen necesita esa esterilla para meditar.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, por fin, mi esterilla. Ahora ya puedo meditar.\aPero, ¿quién se puede concentrar con todo ese ruido? ¡Tantos bots!\aYa que estás aquí, quizás puedas encargarte de unos cuantos...\aAsí podré usar mi esterilla tranquilamente.", + INCOMPLETE_PROGRESS : "Esos bots todavía hacen mucho ruido. ¿Quién se puede concentrar así?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "Por fin, un poco de calma. Gracias, _avName_.\aPor favor, dile a Zari que estoy feliz. Estoy en paz. OM....", + INCOMPLETE_PROGRESS : "Zari llamó, te andaba buscando. Deberías ir a ver qué necesita.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "Me alegra escuchar que Zen Glen está contento con su esterilla cebra.\aOh, esas zinnias acaban de entrar para Rosa Pétalo.\aYa que eres tan bueno para las entregas, quizás no te importaría ir a llevárselas..._where_", + INCOMPLETE_PROGRESS : "Si no llevas las zinnias pronto, empezarán a marchitarse.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "¡Qué zinnias tan lindas! Zari tiene buen género.\aOh, bueno, TÚ eres quien hace las entregas, _avName_. ¡Por favor, dale las gracias a Zari de mi parte!", + INCOMPLETE_PROGRESS : "¡No olvides darle las gracias a Zari por las zinnias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Hola otra vez, _avName_. Eres rápido.\aVeamos... ¿qué es lo siguiente en mi lista de entregas? Discos de Zydeco para Pluma Oca._where_", + INCOMPLETE_PROGRESS : "Seguro que Pluma Oca está esperando esos discos de Zydeco.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "¿Discos de Zydeco? No recuerdo haber pedido discos de Zydeco.\aOh, seguro que los encargó el Marqués de la Nana._where_", + INCOMPLETE_PROGRESS : "No, esos discos de Zydeco son para el Marqués de la Nana._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "¡Por fin, mis discos de Zydeco! Pensé que Zari se había olvidado.\a¿Podrías llevarle este calabacín? Encontrará a alguien que quiera uno. ¡Gracias!", + INCOMPLETE_PROGRESS : "Oh, ya tengo muchos calabacines. Llévale ese a Zari.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "¿Calabacín? Umm. Bueno, alguien lo querrá, estoy segura.\aOk, la lista está casi completa. Sólo falta una entrega.\aHiberno Cuandoquiero encargó un traje de remo._where_", + INCOMPLETE_PROGRESS : "Si no le entregas ese traje de remo a Hiberno Cuandoquiero,\a se arrugará todo.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Érase una vez... ¡oh! No viniste por el cuento, ¿verdad?\a¿Traes mi traje de remo? ¡Estupendo! Uau, es muy lindo.\aEh, ¿podrías darle a Zari un mensaje de mi parte? Necesito gemelos de zircón para el traje. ¡Gracias!", + INCOMPLETE_PROGRESS : "¿Le diste mi mensaje a Zari?", + COMPLETE : "Gemelos de zircón, ¿eh? Bueno, veré qué puedo hacer por él.\aFuiste todo un modelo de atenciones, y no puedo dejarte marchar sin nada.\a¡Toma, una GRAN ayuda para acabar con esos bots!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "David Modorra tiene problemas y quizás tú puedas ayudarle. ¿Por qué no pasas por su tienda?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "¿Qué? ¿Eh? Oh, me quedé dormido.\aEsos edificios bot están llenos de máquinas y el ruido me da mucho sueño.\aTodo el día están con ese ronroneo y...\a¿Eh? Oh, sí, claro. Si pudieras deshacerte de algunos de esos edificios bot, podría mantenerme despierto.", + INCOMPLETE_PROGRESS : "Zzzzz... ¿eh? Ah, eres tú, _avName_.\a¿Ya regresaste? Estaba echando una siesta.\aVuelve cuando termines con esos edificios.", + COMPLETE : "¿Qué? Me quedé dormido un minuto.\aAhora que esos edificios bot ya no están, por fin puedo relajarme.\aGracias a ti, _avName_.\a¡Hasta luego! Creo que voy a echar una siesta.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Ve a hacerle una visita a Teddy Blair. Tiene un trabajo para ti._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "¿Qué dijiste? Pero no, cómo voy a tener un tabaco para ti.\a¡Oh, un trabajo! ¿Por qué no lo dijiste antes? Tienes que hablar más alto.\aCon esos bots, resulta difícil hibernar. Si me ayudas a hacer que Sueñolandia sea un lugar más tranquilo,\ate daré una cosa.", + INCOMPLETE_PROGRESS: "¿Derrotaste a los botas? ¿Qué botas?\a¡Ah, los bots! ¿Por qué no lo dijiste antes?\aUmm, todavía hay mucho ruido. ¿Qué te parece si derrotas a unos cuantos más?", + COMPLETE : "¿Te divertiste? ¿Eh? ¡Oh!\a¡Terminaste! Fantástico. Eres muy amable.\aEncontré esto en el galpón, pero a mí no me sirve para nada.\aA lo mejor a ti te resulta útil. ¡Hasta otra, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "Los bots entraron en el Banco Tupido Velo! ¡Ve a ver a Mostra Dor a ver si puedes ayudar.", + }, + 6292 : { QUEST : "¡Esos malditos chequebots! ¡Me robaron las lámparas de lectura!\aNecesito recuperarlas inmediatamente. ¿Puedes ir a buscarlas?\aSi recuperas mis lámparas, quizás pueda hacer algo para que veas al director financiero.\a¡Apresúrate!", + INCOMPLETE_PROGRESS : "Necesito recuperar las lámparas. ¡Sigue buscando!", + COMPLETE : "¡Ya regresaste! ¡Y con mis lámparas!\aNo sé cómo agradecértelo, pero sí sé que puedo darte esto.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Vela Zascandil te andaba buscando, _avName_. Necesita ayuda._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "¡Oh! Cuánto me alegro de verte, _avName_. ¡Me vendría bien un poco de ayuda!\aEsos malditos bots no permiten que lleguen los muchachos del reparto y no me quedan camas en stock.\a¿Podrías ir a ver a Juanjo Manitas y traerme una cama?_where_ ", + INCOMPLETE_PROGRESS : "¿Juanjo tenía camas? Estaba segura de que tendría una.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "¿Una cama? Claro, ésta está lista para salir.\aLlévasela de mi parte, ¿tienes madera de transportista? ¿Entiendes?\a¡\"MADERA\" de transportista! ¡Je je!\aMuy gracioso. ¿No? Bueno, llévasela de todas formas, por favor.", + INCOMPLETE_PROGRESS : "¿Le gustó la cama a Vela?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "Esta cama no me sirve. Es demasiado básica.\aVe a ver si tiene algo más sofisticado, ¿ok?\aSeguro que no te lleva más de un minuto.", + INCOMPLETE_PROGRESS : "Seguro que Juanjo tiene una cama más sofisticada.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "No acertamos con esa cama, ¿eh? Tengo aquí una que servirá.\aPero hay un pequeño problema: primero hay que armarla.\aMientras yo lo arreglo con el martillo, ¿puedes deshacerte de algunos de esos bots de ahí fuera?\aEsos horribles bots están haciendo de las suyas.\aVuelve cuando termines y la cama estará lista.", + INCOMPLETE_PROGRESS : "Todavía no terminé con la cama.\aCuando acabes con los bots, estará preparada.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "¡Eh, _avName_!\aHiciste un gran trabajo con esos bots.\aLa cama está lista. ¿Podrías entregarla de mi parte?\aAhora que no están los bots, seguro que el negocio es un éxito.", + INCOMPLETE_PROGRESS : "Creo que Vela está esperando la cama.", + COMPLETE : "¡Qué cama tan linda!\aAhora, mis clientes estarán contentos. Gracias, _avName_.\aEsto quizás te resulte útil. Alguien se lo olvidó aquí.", + }, + 7209 : { QUEST : "Ve a ver a Luna Miel. Necesita ayuda._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "¡Oh! Cuánto me alegro de verte, _avName_. ¡Necesito ayuda!\aHace mucho tiempo que no consigo hacer mi cura de belleza y sueño. Verás, esos bots me robaron la colcha.\a¿Podrías ir a ver si Vito tiene algo en azul?_where_", + INCOMPLETE_PROGRESS : "¿Tenía Vito una colcha azul?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "Así que Luna quiere una colcha, ¿eh?\a¿De qué color? ¡¿AZUL?!\aTendré que fabricársela especialmente. Todo lo que tengo es rojo.\aTe diré lo que haremos... si te encargas de algunos de esos bots de ahí fuera, yo le fabricaré una colcha azul especial.\aColchas azules... ¿Con qué me saldrán luego?", + INCOMPLETE_PROGRESS : "Todavía estoy trabajando en la colcha, _avName_. ¡Sigue con los bots!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Me alegra volver a verte. ¡Tengo algo para ti!\aToma la colcha, es azul. Le encantará.", + INCOMPLETE_PROGRESS : "¿Le gustó la colcha a Luna?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "¿Mi colcha? No, no es lo que quería.\a¡Es a CUADROS! ¿Cómo va alguien a dormir con un diseño tan llamativo?\aTendrás que devolverla y traerme otra.\aSeguro que tiene más.", + INCOMPLETE_PROGRESS : "Me niego a aceptar una colcha a cuadros. Ve a ver qué puede hacer Vito.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "¿Qué? ¿Que no le gustan los CUADROS?\aUmm... veamos qué tenemos aquí.\aEsto me tomará un poco de tiempo. ¿Por qué no vas y te encargas de unos cuantos bots mientras busco otra cosa?\aCuando vuelvas, tendré algo para ti.", + INCOMPLETE_PROGRESS : "Todavía estoy buscando otra colcha. ¿Qué tal con los bots?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "¡Buen trabajo con los bots!\aAquí tienes, es azul y no lleva cuadros.\aEspero que le guste el cachemir.\aLlévale la colcha a Luna.", + INCOMPLETE_PROGRESS : "Es todo lo que tengo en estos momentos.\aPor favor, llévale esa colcha a Luna.", + COMPLETE : "¡Oh! ¡Qué lindo! El cachemir me encanta.\a¡Y ahora, mi cura de belleza y sueño! Hasta otra, _avName_.\a¿Qué? ¿Sigues aquí? ¿No ves que estoy intentando dormir?\aToma, llévate esto y déjame descansar. ¡Debo de estar horrorosa!", + }, + + 7218 : { QUEST : "A Dafne Marmota le vendría bien una ayudita._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, _avName_, ¡me alegro de verte! Esos bots se llevaron mis almohadas.\a¿Puedes ir a ver si Tex tiene alguna?_where_\aEstoy segura de que me podrá ayudar.", + INCOMPLETE_PROGRESS : "¿Tenía Tex alguna almohada? ", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "¡Ah, del barco! Dafne necesita almohadas, ¿eh? Bueno, has venido al lugar perfecto, marinero.\aAquí hay más almohadas que pinchos tiene un cactus.\aToma, _avName_. Llévaselas a Dafne y salúdala de mi parte.\aEs un placer ayudar a una señorita.", + INCOMPLETE_PROGRESS : "¿Las almohadas eran lo bastante mullidas para la dama?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "¡Tienes las almohadas! ¡Fantástico!\a¡Eh, espera un momento! Estas almohadas son demasiado mullidas.\aDemasiado blandas para mí. Necesito almohadas más duras.\aDevuélvele estas a Tex a ver qué más tiene. Gracias.", + INCOMPLETE_PROGRESS : "¡No! Demasiado blandas. Dile a Tex que te dé otras almohadas.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Demasiado blandas, ¿eh? Bueno, veamos qué tenemos aquí...\aUmm... yo tenía un lote entero de almohadas duras. ¿Dónde estarán?\a¡Oh! Ya lo recuerdo. Iba a devolverlas, así que estarán en el almacén.\a¿Qué tal si tú destruyes algunos de esos edificios bot de ahí fuera mientras las saco, marinero?", + INCOMPLETE_PROGRESS : "Los edificios bot son duros, pero estas almohadas no.\aSeguiré buscando.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "¿Ya regresaste? Bueno, perfecto. Encontré las almohadas que Dafne quería.\aLlévaselas, ¡son tan duras que podrían usarse para picar almendras!", + INCOMPLETE_PROGRESS : "Sí, estas almohadas son durísimas. Espero que a Dafne le gusten.", + COMPLETE : "Sabía que Tex tendría almohadas más duras.\aOh sí, son perfectas. Duras y cómodas.\a¿Te sirve de algo una pieza de traje bot? Bueno, llévatela por si acaso.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Ve a visitar a Sandra Salamandra. Perdió su pijama._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "¡No tengo pijama! ¡Desapareció!\a¿Qué voy a hacer? ¡Oh! ¡Ya lo sé!\aVe a ver a Plúmbea Triz. Seguro que tiene un pijama para mí._where_", + INCOMPLETE_PROGRESS : "¿Tenía Plúmbea Triz pijamas?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "¡Eh, pequeño dibu! Plúmbea Triz tiene los mejores pijamas de todas las Bahamas.\aOh, algo para Sandra Salamandra, ¿eh? Veamos qué tenemos por aquí.\aAquí hay algo. ¡Un modelo con mucho estilo!\a¿Podrías ir a llevárselo de mi parte? Ahora no puedo dejar la tienda.\aGracias, _avName_. ¡Nos vemos!", + INCOMPLETE_PROGRESS : "Tienes que llevarle este pijama a Sandra._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "¿Plúmbea Triz me envió esto? Oh...\a¿Es que no tiene pijamas con pies?\aYo siempre uso pijamas con pies. ¿No lo hace así todo el mundo?\aDevuélvelo y dile que busque unos con pies.", + INCOMPLETE_PROGRESS : "Mi pijama tiene que tener pies. Ve a ver qué puede hacer Plúmbea Triz.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "¿Pies? Déjame pensar...\a¡Espera! ¡Tengo uno perfecto!\a¡Taráaan! Pijama con pies. Un lindo pijama azul con pies. De los mejores de la isla.\aLlévaselo, por favor. ¡Gracias!", + INCOMPLETE_PROGRESS : "¿Le gustó a Sandra el pijama con pies?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "Pues sí, estos tienen pies, ¡pero no puedo ponerme un pijama azul!\aPregúntale a Plúmbea Triz si tiene otro color.", + INCOMPLETE_PROGRESS : "Seguro que Plúmbea Triz tiene pijamas con pies de otro color.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "Qué pena. Es el único pijama con pies que tengo.\aOh, tengo una idea. Ve a preguntarle a Cato. A lo mejor ella tiene pijamas con pies._where_", + INCOMPLETE_PROGRESS : "No, este es el único pijama que tengo. Ve a ver lo que tiene Cato._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "¿Un pijama con pies? Por supuesto.\a¿Qué quieres decir con que son azules? ¿No le gusta el azul?\aOh, eso es un poco más complicado. Toma, prueba con este.\aNo es azul y tiene pies.", + INCOMPLETE_PROGRESS : "Me encanta el color rojo, ¿a ti no?\aEspero que a Sandra le guste...", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "No, este no es azul, pero nadie con esta complexión se atrevería a ponerse algo rojo.\aDe ninguna manera. ¡Devuélvelo! A ver qué más tiene Cato.", + INCOMPLETE_PROGRESS : "Seguro que Cato tiene más pijamas. ¡Y nada rojo!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Rojo tampoco. Umm....\aBueno, estoy segura de que tengo más.\aTardaré un poco en encontrarlos. Hagamos un trato.\aYo busco otro pijama y tú te deshaces de algunos de esos edificios bot. Me alteran mucho.\aEl pijama estará listo cuando vuelvas, _avName_.", + INCOMPLETE_PROGRESS : "Tienes que eliminar más edificios bot mientras busco otro pijama.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "¡Hiciste un gran trabajo con esos bots! ¡Gracias!\aEncontré el pijama para Sandra; espero que le guste.\aLlévaselo. Gracias.", + INCOMPLETE_PROGRESS : "Sandra está esperando el pijama, _avName_.", + COMPLETE : "¡Un pijama fucsia con pies! ¡Perrrrfecto!\aAh, se acabó el problema. Veamos...\aOh, supongo que debería darte algo por ayudarme.\aQuizás esto te resulte útil. Alguien lo olvidó aquí.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Ve a ver a Máscara Pepínez. Dijo que necesitaba ayuda._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "¡Esos malditos se llevaron mi crema antiarrugas!\aMis clientes NECESITAN crema antiarrugas.\aVe a ver a Ripón a ver si tiene en stock mi fórmula especial._where_", + INCOMPLETE_PROGRESS : "Me niego a trabajar si no tengo crema antiarrugas.\aVe a ver lo que Ripón puede ofrecerme.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, esa Máscara es muy molesta. No se conforma con mi fórmula habitual.\aEso significa que necesitaré coral de coliflor, mi ingrediente súper especial, pero no me queda nada.\a¿Podrías ir a pescarlo en el estanque? En cuanto tengas el coral, haré un preparado para Máscara.", + INCOMPLETE_PROGRESS : "Necesito ese coral de coliflor para hacer un preparado de crema antiarrugas.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "¡Qué coral de coliflor tan hermoso!\aOk, veamos... un poco de esto y unas gotas de lo otro... y ahora, una cucharada de algas.\aVaya, ¿dónde están las algas? Tampoco me quedan.\a¿Puedes bajar al lago y buscar unas lindas algas pegajosas?", + INCOMPLETE_PROGRESS : "En la tienda no quedan algas pegajosas.\aSin ellas no puedo preparar la crema.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "¡Oooh! Qué algas tan pegajosas tienes ahí, _avName_.\aAhora solo tengo que moler unas perlas con el mortero.\aPero, ¿dónde está el mazo? ¿De qué me sirve un mortero sin mazo?\a¡Seguro que ese maldito prestamista despiadado se lo llevó cuando vino!\a¡Tienes que ayudarme a encontrarlo! ¡Iba en dirección al cuartel general chequebot!", + INCOMPLETE_PROGRESS : "No puedo moler las perlas sin el mazo de mortero.\a¡Malditos prestamistas despiadados!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "¡Estupendo! ¡Tienes mi mazo de mortero!\aYa podemos poner manos a la obra. Picamos eso... lo movemos un poco y...\a¡ya está! Dile a Máscara que está recién preparada.", + INCOMPLETE_PROGRESS : "Deberías llevárselo a Máscara mientras esté fresca.\aEs muy quisquillosa.", + COMPLETE : "¿Es que Ripón no tenía un bote de crema antiarrugas más grande? ¿No?\aBueno, ya encargaré más cuando se termine.\aHasta luego, _avName_.\a¿Qué? ¿Sigues ahí? ¿No ves que estoy intentando trabajar?\aToma, llévate esto.", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "Si te interesan piezas de disfraz de abogabot, deberías visitar a _toNpcName_.\aDicen que necesita ayuda con sus investigaciones sobre el clima._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Sí, sí. Tengo piezas para disfraz de abogabot.\aPero a mí no me interesan.\aEl tema de mi investigación es la fluctuación de la temperatura ambiental en Toontown.\aSerá un placer intercambiar piezas de disfraz por sensores de temperatura bot.\aPuedes empezar en %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "¿Miraste en %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "¡Ah, excelente!\aJusto como me temía...\a¡Oh, sí! Toma tu pieza del disfraz.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "Si quieres más piezas de disfraz de abogabot, visita otra vez a _toNpcName_.\aDicen que necesita más ayudantes de investigación._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "¿Más piezas de disfraz de abogabot?\aBueno, si insistes...\apero necesitaré otro sensor de temperatura bot.\aEsta vez, quiero que mires en %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "Estás buscando en %s, ¿verdad?" % GlobalStreetNames[2200][-1], + COMPLETE : "¡Gracias!\aToma tu pieza de disfraz.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "Si necesitas más piezas de disfraz de abogabot, visita otra vez a _toNpcName_.\aDicen que todavía necesita ayuda con su investigación climática._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "¡Has probado ser de gran utilidad!\a¿Puedes echar un vistazo en %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "¿Seguro que estás buscando por %s?" % GlobalStreetNames[2300][-1], + COMPLETE : "Ummm, esto no me gusta...\apero toma, tu pieza de disfraz...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "'Tú sabes quién' necesita más sensores de temperatura.\aPásate a verle si quieres otra pieza de disfraz._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "¿Regresaste otra vez?\aEres muy persistente...\aLa próxima parada es %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "¿Miraste en %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "¡Bien! ¡Parece que ya dominas la situación!\aTu pieza de disfraz...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "Si quieres otra pieza de disfraz..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "¡Me alegro de verte!\aAhora, necesito lecturas de %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "Estás buscando por %s, ¿verdad?" % GlobalStreetNames[1200][-1], + COMPLETE : "Muchas gracias.\aTu disfraz debe de estar ya bastante completo...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "Creo que _toNpcName_ tiene más trabajo para ti._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "¡Me alegra volver a verte, _avName_!\a¿Puedes conseguirme un sensor de %s?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "¿Miraste en %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "¡Buen trabajo!\a¡Toma tu recompensa, te la ganaste!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "Ya sabes cómo es esto._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "¡_avName_, querido amigo!\a¿Puedes ir a %s a buscar otro sensor de temperatura?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "¿Seguro que estás buscando por %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "¡Excelente!\aCon tu ayuda, ¡mi investigación avanza rápidamente!\aToma, tu recompensa.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ te ha estado buscnado mucho.\a¡Parece qué le dejaste una buena impresión!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "¡Hola otra vez!\aTe estaba esperando.\aLa siguiente lectura está en %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "Estás mirando en %s, ¿verdad?" % GlobalStreetNames[5200][-1], + COMPLETE : "¡Gracias!\aToma, tu recompensa.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "Si necesitas terminar tu disfraz de abogabot...\a_toNpcName_ puede ayudarte._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "¡Hola, aprendiz de científico!\aTodavía necesitamos lecturas de %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "¿Miraste en %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "¡Un trabajo excelente!\aAquí tienes tu coso de abogabot...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tiene otro trabajo para ti.\aSi no te hartaste aún de él..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Bueno.\a¿Estás listo para otro hallazgo?\aEsta vez, prueba en %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "¿Seguro que estás buscando en %s?" % GlobalStreetNames[4100][-1], + COMPLETE : "¡Otro!\a¡Eres un auténtico sabueso!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "¿Sigues buscando piezas de disfraz de abogabot?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "Seguramente ya lo sabrás...\apero necesito lecturas de %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "Estás buscando en %s, ¿verdad?" % GlobalStreetNames[4200][-1], + COMPLETE : "¡Ya casi está!\aToma...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "Odio admitirlo, pero..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "¿Qué piensas de %s? ¿Puedes conseguir ahí también un sensor?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "¿Miraste en %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Otro trabajo excelente, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Si todavía necesitas piezas de disfraz, ve a visitar al Profesor._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "Creo que todavía necesitamos lecturas de %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "¿Seguro que estás mirando en %s?" % GlobalStreetNames[9100][-1], + COMPLETE : "¡Buen trabajo!\aCreo que estamos muy cerca...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tiene una última misión para ti._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "¿Ya regresaste?\aLa lectura final está en %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "Estás buscando en %s, ¿verdad?" % GlobalStreetNames[9200][-1], + COMPLETE : "¡Ya terminaste!\aYa estás listo para infiltrarte en la oficina del fiscal del distrito y recoger Notificaciones del tribunal.\a¡Buena suerte y gracias por tu ayuda!", + }, + + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "Si te interesan las piezas de disfraz de jefebot, deberías visitar a _toNpcName_._where_", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "Sí, puedo conseguirte piezas de jefebot.\aPero necesito que me ayudes a completar mi colección jefebot.\aSal y derrota a un secuaz.", + INCOMPLETE_PROGRESS : "¿No encuentras a un secuaz? Qué vergüenza...", + COMPLETE : "No fallaste, ¿verdad?\aToma, tu primera pieza del disfraz.", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ necesita más ayuda, si quieres._where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "¿Otra pieza de disfraz?\aPor supuesto...\apero solo si derrotas a un chupatintas.", + INCOMPLETE_PROGRESS : "Los chupatintas están por las calles.", + COMPLETE : "¡Fue muy fácil!\aAquí tienes la segunda pieza de tu disfraz.", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "Para conseguir piezas de jefebot, solo hay un sitio donde buscar._where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "Ahora, necesito un sonriente...", + INCOMPLETE_PROGRESS : "Los sonrientes se pueden encontrar por las calles.", + COMPLETE : "¡Sí! Eres un as.\aToma, la tercera pieza de tu disfraz.", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tiene más piezas para ti...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "Si derrotas a un microgerente, te daré otra pieza.", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[1100][-1], + COMPLETE : "¡Te salió bastante bien!\aToma, la cuarta pieza de tu disfraz.", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "Dirígete a..._where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "Estoy buscando a un regulador de empleo...", + INCOMPLETE_PROGRESS : "¿Tienes problemas? Prueba buscando en %s" % GlobalStreetNames[3100][-1], + COMPLETE : "¡No fue fácil!\aToma, tu quinta pieza de disfraz.", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "Creo que ya sabes dónde ir..._where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "Lo siguiente en mi lista Cazacabezas.", + INCOMPLETE_PROGRESS : "Creo que tendrás más suerte si miras en los edificios.", + COMPLETE : "Veo que no tuviste problemas para encontrar uno.\aToma, la sexta pieza del disfraz.", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ necesita más jefebots.", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "Ahora necesito que encuentres a un corporativista.", + INCOMPLETE_PROGRESS : "Creo que tendrás más suerte si miras en los edificios.", + COMPLETE : "¡Tú sí que estás hecho todo un corporativista!\aToma, la séptima pieza del disfraz.", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "Si quieres más piezas de disfraz, ve a..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "Y ahora, el broche de oro: ¡el pez gordo!", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Sabía que podía contar contigo para cortar...\aAh, da igual.\aToma, la siguiente pieza del disfraz.", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ te andaba buscando...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "Ahora necesito que derrotes a uno de los nuevos jefebots; son más peligrosos.", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Son más duros de lo que parecen, ¿eh?\aSupongo que te debo una pieza de disfraz.", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "¿Podrías pasar por..._where_?", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "Estos bots versión 2.0 son muy interesantes.\aPor favor, ve y derrota a uno.", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "¡Gracias!\aMarche otra pieza de disfraz.", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "Si tienes la ocasión, pasa a ver a _toNpcName_.", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "Me pregunto si pueden seguir regenerándose...", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Supongo que no.\aToma, tu pieza...", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "Ya sabes..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "Quizá ni siquiera sean jefebots...", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Ummm, parece que, después de todo, sí que son jefebots.\aToma otra pieza.", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "Seguramente ya sabrás lo que te voy a decir...", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "Quizás estén relacionados con los esquelebots...", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "El resutlado no fue concluyente...\aToma, otra pieza de disfraz.", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "Por favor, ve a ver a _toNpcName_ otra vez.", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "Aún pienso que podría tratarse de algún tipo de esquelebot...", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Bueno, quizás no.\aToma la pieza siguiente.", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "Seguramente es el último sitio al que te gustaría ir, pero...", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "Estos nuevos bots todavía me alteran un poco.\a¿Podrías derrotar a otro, por favor?", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Fascinante. Simplemente, fascinante.\aUna pieza del disfraz, por las molestias.", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ empieza a sonar como un disco rallado...", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "Ya casi decidí lo que son esos nuevos bots.\aSólo uno más...", + INCOMPLETE_PROGRESS : "Busca en %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Sí, creo que descubrí algo.\aAh, sí.\aEsto es para ti...", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "Tienes que ir a contarle esto a Flipi...", + INCOMPLETE_PROGRESS : "Encontrarás a Flipi en el Ayuntamiento", + COMPLETE : "¡Un nuevo tipo de bot!\a¡Buen trabajo!\aToma, la última pieza de tu disfraz.", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["guau", "arf", "grrrr"] +ChatGarblerCat = ["miau", "miao"] +ChatGarblerMouse = ["ñic", "ñiiiic", "ñic ñic"] +ChatGarblerHorse = ["Yiiiijijii", "brrr"] +ChatGarblerRabbit = ["iiik", "ipr", "iiipi", "iiiki"] +ChatGarblerDuck = ["cuac", "cuaaac ", "cuac cuac"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerBear = ["Grraaargg", "grrr"] +ChatGarblerPig = ["oink", "oik", "resoplido"] +ChatGarblerDefault = ["bla"] + +# AvatarDNA.py +Bossbot = "Jefebot" +Lawbot = "Abogabot" +Cashbot = "Chequebot" +Sellbot = "Vendebot" +BossbotS = "un jefebot" +LawbotS = "un abogabot" +CashbotS = "un chequebot" +SellbotS = "un vendebot" +BossbotP = "Jefebots" +LawbotP = "Abogabots" +CashbotP = "Chequebots" +SellbotP = "Vendebots" +BossbotSkelS = "un esquelebot jefebot" +LawbotSkelS = "un esquelebot abogabot" +CashbotSkelS = "un esquelebot chequebot" +SellbotSkelS = "un esquelebot vendebot" +BossbotSkelP = "esquelebots jefebots" +LawbotSkelP = "esquelebots abogabots" +CashbotSkelP = "esquelebots chequebots" +SellbotSkelP = "esquelebots vendebots" +SkeleRevivePostFix = " v2.0" + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Buscando datos de %s." +AvatarDetailPanelFailedLookup = "Imposible obtener datos de %s." +#AvatarDetailPanelPlayer = "Jugador: %(player)s\nMundo: %(world)s\nLugar: %(location)s" +# sublocation is not working now +AvatarDetailPanelPlayer = "Jugador: %(player)s\nMundo: %(world)s" +AvatarDetailPanelPlayerShort = "%(player)s\nMundo: %(world)s\nLugar: %(location)s" +AvatarDetailPanelRealLife = "Sin conexión" +AvatarDetailPanelOnline = "Distrito: %(district)s\nLugar: %(location)s" +AvatarDetailPanelOnlinePlayer = "Distrito: %(district)s\nLugar: %(location)s\nPlayer: %(player)s" +AvatarDetailPanelOffline = "Distrito: sin conexión\nLocalidad: sin conexión" +AvatarShowPlayer = "Mostrar jugador" +OfflineLocation = "Sin conexión" + +#PlayerDetailPanel +PlayerToonName = "Dibu: %(toonname)s" +PlayerShowToon = "Mostrar dibu" +PlayerPanelDetail = "Datos del jugador" + + +# AvatarPanel.py +AvatarPanelFriends = "Amigos" +AvatarPanelWhisper = "Susurrar" +AvatarPanelSecrets = "Secretos" +AvatarPanelGoTo = "Ir a" +AvatarPanelPet = "Mostrar dibuperrito" +AvatarPanelIgnore = "Ignorar" +AvatarPanelIgnoreCant = "Okay" +AvatarPanelStopIgnoring = "Dejar de ignorar" +AvatarPanelReport = "Informar" +#AvatarPanelCogDetail = "Dept: %s\nNivel: %s\n" +AvatarPanelCogLevel = "Nivel: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Datos del dibu" +AvatarPanelGroupInvite = "Invitar" +AvatarPanelGroupRetract = "Retirar invitación" +AvatarPanelGroupMember = "Ya está en el grupo" +AvatarPanelGroupMemberKick = "Eliminar" + +# Report Panel +ReportPanelTitle = "Informar sobre un jugador" +ReportPanelBody = "Esta función enviará un informe completo a un moderador. En lugar de enviar un informe, quizás prefieras una de las siguientes opciones:\n\n - Teletransportarte a otro distrito\n - Usar \"Ignorar\" en el panel del dibu \n\n¿Seguro que quieres informar sobre %s al moderador?" +ReportPanelBodyFriends = "Esta función enviará un informe completo a un moderador. En lugar de enviar un informe, quizás prefieras una de las siguientes opciones:\n\n - Teletransportarte a otro distrito\n - Romper la amistad\n\n¿Seguro que quieres informar sobre %s al moderador?\n\n(Al hacerlo, también se romperá tu amistad)" +ReportPanelCategoryBody = "Estás a punto de enviar un informe sobre %s. Un moderador será informado de tu queja y tomará las medidas correspondientes para casos en que alguien infringe las reglas. Por favor, elige los motivos por los que quieres presentar un informe sobre %s:" +ReportPanelBodyPlayer = "Está función todavía está en proceso de desarrollo; pronto podrás acceder a ella. Mientras tanto, puedes elegir una de las siguientes opciones:\n\n - Ve a DXD y rompe con la amistad.\n – Habla con uno de tus padres y explícale lo que sucedió." + +ReportPanelCategoryLanguage = "Malas palabras" +ReportPanelCategoryPii = "Compartir/solicitar datos personales" +ReportPanelCategoryRude = "Comportamiento grosero o incorrecto" +ReportPanelCategoryName = "Insulto" + +ReportPanelConfirmations = ( + "Estás a punto de informar de que %s utilizó un lenguaje obsceno, discriminatorio o explícitamente sexual.", + "Estás a punto de informar de que %s no cumple las normas de seguridad al proporcionar o solicitar un número de teléfono, dirección, apellido, dirección de e-mail, contraseña o nombre de cuenta.", + "Estás a punto de informar de que %s está acosando a alguien o comportándose de un modo extremo y alterando el juego.", + "Estás a punto de informar de que %s creó un nombre que no respeta las reglas de Disney's House.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "Nos tomamos las quejas muy en serio. Tu informe será revisado por un moderador, quien tomará las medidas adecuadas para los casos en que alguien infringe nuestras normas. Si se descubre que tu cuenta está involucrada en uno de estos casos, o si presentas informes falsos o haces un uso abusivo de del sistema Informar sobre un jugador, el moderador podría tomar medidas contra tu cuenta. ¿Estás totalmente seguro de que quieres presentar un informe sobre este jugador?" + +ReportPanelThanks = "¡Gracias! Tu informe fue enviado a un moderador para proceder a su revisión. No es necesario que te vuelvas a poner en contacto en relación a este asunto. El equipo moderador tomará las medidas apropiadas para los casos en que un jugador infringe las normas." + +ReportPanelRemovedFriend = "%s fue eliminado automáticamente de tu lista de amigos." +ReportPanelRemovedPlayerFriend = "%s fue eliminado automáticamente de tus amigos jugadores para que ya no aparezca como amigo tuyo en ningún producto de Disney." + +ReportPanelAlreadyReported = "Ya presentaste un informe sobre %s durante esta sesión. El moderador revisará tu informe anterior." + +# Report Panel +IgnorePanelTitle = "Ignorar a un jugador" +IgnorePanelAddIgnore = "¿Quieres ignorar a %s durante el resto de esta sesión?" +IgnorePanelIgnore = "Estás ignorando a %s." +IgnorePanelRemoveIgnore = "¿Quieres dejar de ignorar a %s?" +IgnorePanelEndIgnore = "Ya no estás ignorando a %s." +IgnorePanelAddFriendAvatar = "%s es tu amigo o amiga, no puedes ignorarle mientras sean amigos." +IgnorePanelAddFriendPlayer = "%s (%s)es tu amigo o amiga, no puedes ignorarle mientras sean amigos." + +# PetAvatarPanel.py +PetPanelFeed = "Alimentar" +PetPanelCall = "Llamar" +PetPanelGoTo = "Ir a" +PetPanelOwner = "Mostrar propietario" +PetPanelDetail = "Datos de la mascota" +PetPanelScratch = "Rascar" + +# PetDetailPanel.py +PetDetailPanelTitle = "Entrenamiento de acrobacias" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Saltar', + 1: 'Pedir', + 2: 'Hacerse el muerto', + 3: 'Rodar', + 4: 'Hacer voltereta', + 5: 'Saltar', + 6: 'Hablar', + } + + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutral', + 'hunger': 'hambriento', + 'boredom': 'aburrido', + 'excitement': 'emocionado', + 'sadness': 'triste', + 'restlessness': 'inquieto', + 'playfulness': 'jueguetón', + 'loneliness': 'solo', + 'fatigue': 'cansado', + 'confusion': 'confundido', + 'anger': 'enfadado', + 'surprise': 'sorprendido', + 'affection': 'cariñoso', + } + +SpokenMoods = { + 'neutral': 'neutral', + 'hunger': 'Estoy harto de las golosinas. ¿Por qué no me das un trozo de tarta?', + 'boredom': 'No pensarás que te entendí, ¿verdad?', + 'excitement': '¡Toontástico!', + 'sadness': 'Quiero pasar tiempo con mi dibuperrito', + 'restlessness': 'Estoy muy inquieto', + 'playfulness': '¡Juega conmigo o empezaré a escarbar en las flores!', + 'loneliness': '¡Quiero ir contigo a luchar contra los bots!', + 'fatigue': '¡Las acrobacias de Dibuperrito son agotadoras! ¿Por qué no descansamos?', + 'confusion': '¿Dónde estoy? ¿Dónde estás?', + 'anger': 'Siempre me dejas atrás', + 'surprise': 'Uau, ¿de dónde saliste?', + 'affection': '¡Eres un dibu estupendo!', + } + +# DistributedAvatar.py +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Amigos" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Intentando ir a %s." +TeleportPanelNotAvailable = "%s está ocupado en este momento. Inténtalo más tarde." +TeleportPanelIgnored = "%s te está ignorando." +TeleportPanelNotOnline = "%s no está conectado en este momento." +TeleportPanelWentAway = "%s se marchó." +TeleportPanelUnknownHood = "¡No sabes cómo llegar a %s!" +TeleportPanelUnavailableHood = "%s no está disponible en este momento. Inténtalo más tarde." +TeleportPanelDenySelf = "¡No puedes ir tú solo!" +TeleportPanelOtherShard = "%(avName)s está en el distrito %(shardName)s, y tú estás en el distrito %(myShardName)s. ¿Quieres cambiarte a %(shardName)s?" +TeleportPanelBusyShard = "%(avName)s está en un distrito lleno. Jugar en un distrito lleno puede ralentizar notablemente el rendimiento del juego. ¿Seguro que quieres intercambiar distritos?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Soy el jefe." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Soy el capataz." +FactoryBossBattleTaunt = "Te presento al capataz." +MintBossTaunt = "Soy el supervisor." +MintBossBattleTaunt = "Necesitas hablar con el supervisor." +StageBossTaunt = "Mi justicia no es igual para todos." +StageBossBattleTaunt = "Estoy por encima de la ley." +CountryClubBossTaunt = "Soy el presidente del club." +CountryClubBossBattleTaunt = "Necesitas hablar con el presidente del club." +ForcedLeaveCountryClubAckMsg = "El presidente del club fue derrotado antes de que pudieras llegar a él. No recuperaste ninguna acción." + +# HealJokes.py +ToonHealJokes = [ + ["¿Qué le dice un dos a un cero?", + "¡Vente conmigo! "], + ["¿Qué es una brújula?", + "Una viéjula montada en una escóbula."], + ["Qué es un lóbulo?", + "Un perro grándulo que se come a las ovéjulas."], + ["¿Qué es una orilla?", + "Sesenta minutillos."], + ["¿Qué es una oreja?", + "Sesenta minutejos."], + ["¿Qué es un código?", + "Por donde se dobla el brácigo."], + ["¿Qué le dice un cero a otro cero?", + "No valemos nada."], + ["¿Qué le dice un jaguar a otro?", + "¿Jaguar you?"], + ["¿Cuántos astrónomos hacen falta para cambiar una bombilla?", + "Ninguno, los astrónomos prefieren la oscuridad."], + ["¿Cuál es el hombre con pensamientos más profundo?", + "El minero."], + ["¿Cómo se dice suspenso en chino?", + "¡Cha cha cha chaaaaan!"], + ["¿Qué le dice el pingüino a la pingüina?", + "Te quiero como a ningüina."], + ["¿Qué le dice la pelota a la raqueta?", + "Lo nuestro es imposible, siempre me estás pegando..."], + ["¿Qué le dice un poste a otro poste?", + "Póstate bien."], + ["¿Qué le dice un caimán mexicano a otro?", + "¡Cai-manito!"], + ["¿Cuál es la sal que peor huele?", + "La sal-pargatas."], + ["¿Qué es un punto verde en un rincón?", + "Un guisante castigado."], + ["¿Cómo se dice 99 en chino?", + "Cachi-chien."], + ["¿Por qué los gallegos ponen ajos en la carretera nacional?", + "Porque son buenos para la circulación."], + ["¿Qué es más asqueroso que encontrarse un gusano en una manzana?", + "Encontrar sólo medio gusano."], + ["¿Quién inventó las fracciones?", + "Enrique Octavo."], + ["¿Quién mató al libro de lengua?", + "El sujeto."], + ["¿Qué hora es cuando un elefante se sienta en una valla?", + "Hora de cambiar la valla."], + ["¿Por qué los elefantes no pueden montar en bicicleta?", + "Porque no tienen dedo gordo para tocar la campanita."], + ["¿Cómo se meten cuatro elefantes en un Mini?", + "Pues dos delante y dos detrás."], + ["¿Cuántos psicoanalistas hacen falta para cambiar una bombilla?", + "Uno, pero la bombilla tiene que querer ser cambiada."], + ["¿Por qué los flamencos se paran sobre una pata?", + "Porque si no se caen."], + ["¿Cuál es la patrona de los informáticos? ", + "Santa Tecla."], + ["¿Cuál es el patrón de los náufragos?", + "San Están Aislados de la Costa."], + ["¿Quién es el patrón de los profesores de educación física?", + "San Gimnasio de Loyola."], + ["¿Cuál es el grupo musical más oscuro? ", + "Estatos Qúo."], + ["¿Cuál es el vaquero más sucio del Oeste?", + "Johny Melabo."], + ["¿Cuál es el queso favorito de Sherlock Holmes?", + "El emmental, querido Watson."], + ["¿En qué se parece un elefante a una cama?", + "En que el elefante es paquidermo y la cama paquiduermas."], + ["¿En qué se parece una camisa vieja a un hotel barato? ", + "En que ninguno tiene botones."], + ["¿En qué se parece un boxeador a un telescopio?", + "En que los dos hacen ver las estrellas."], + ["¿En que se parece una diligencia a una silla?", + "La diligencia va para Kansas City y la silla es para \"siti\" cansas."], + ["¿En qué se parecen una escopeta y una gata?", + "En que las dos tienen gatillos."], + ["¿En qué se parecen un panadero y un político?", + "En que los dos mueven masas."], + ["¿En qué se parece una bruja a un fin de semana?", + "En que los dos pasan volando."], + ["¿En que se parecen los limones a los terratenientes?", + "En que los dos tienen muchas propiedades."], + ["¿En qué se parece una casa incendiándose a una casa vacía?", + "En que de la casa incendiándose \"salen llamas\" y en la casa vacía \"llamas y nadie sale\"."], + ["¿En qué se parece una pistola a un panadero?", + "En que los dos hacen pan."], + ["¿En qué se parece un ladrón a una pulga?", + "En que la pulga salta y el ladrón asalta."], + ["¿En qué se parece una estufa a un avión?", + "En que los dos tienen piloto."], + ["¿Qué le dice un chinche a una chinche?", + "Te amo chincheramente."], + ["¿Qué le dice un semáforo a otro?", + "No me mires que me estoy cambiando."], + ["¿Qué le dice el número 3 al número 30?", + "Para ser como yo, tienes que ser sincero."], + ["¿Qué le dice un semáforo a otro?", + "No me mires que me pongo rojo."], + ["¿Qué le dice el timbre al dedo?", + "Si me tocas, grito."], + ["¿Qué le dice un zapato al otro?", + "¡Qué vida más arrastrada llevamos!"], + ["¿Qué le dice un diente a otro?", + "Ojo por ojo y hoy por ti, mañana por mí."], + ["¿Qué le dice un ojo a otro?", + "Si este tipo fuera bizco te extrañaría menos."], + ["¿Cuál es el último animal acuático?", + "El delfín."], + ["¿Por qué es la cebra el animal más antiguo de la selva?", + "Porque está en blanco y negro."], + ["¿Por qué las películas de Chaplin eran mudas?", + "Porque el director siempre decía: ¡No charles, Chaplin! "], + ["¿Por qué los perros persiguen a los autos?", + "Porque llevan un gato en el portaequipaje."], + ["¿Por qué los franceses comen caracoles? ", + "Porque no les gusta la comida rápida."], + ["¿Por qué se esconden los animales de la selva? ", + "Porque los elefantes están haciendo paracaidismo."], + ["Ring, ring - Muy buenas, ¿es el uno-uno-uno-uno-uno-uno? ", + "- No, éste es el once-once-once."], + ["Ring, ring - Oiga, ¿es la Real Academia de la Lengua? ", + "- No, pero como si lo seriese."], + ["Ring, ring - ¿Es Otto? ", + "- No, soy el de siempre."], + ["Ring, ring - Hola, ¿está Agustín?", + "- Pues sí, estoy aquí calentito y cómodo."], + ["Ring, ring - Hola, ¿está Cholo?", + "No, estoy acompañado."], + ["Ring, ring - Hola, ¿está Alberto?", + "- ¡No, está celado!"], + ["Ring, ring - Hola, ¿ahí lavan la ropa?", + "- No. - Pues qué sucios."], + ["¿Cuál es el colmo de la lírica?", + "Haber tenido un plácido domingo."], + ["¿Cuál es el colmo de un carnicero?", + "Tener un hijo chorizo."], + ["¿Cuál es el colmo de un forzudo?", + "Doblar una esquina."], + ["¿Cuál es el colmo de un sastre?", + "Tener un hijo botones."], + ["¿Cuál es el colmo de un carpintero?", + "Tener una hija cómoda."], + ["¿Cuál es el colmo de un arquitecto?", + "Construir castillos en el aire."], + ["¿Cuál es el colmo de un peluquero?", + "Perder el tren por los pelos."], + ["¿Cuál es el colmo de un caballo?", + "Tener silla y no poder sentarse."], + ["¿Cuál es el colmo de los colmos?", + "Que un mudo le diga a un sordo que un ciego lo está mirando feo."], + ["¿Cuál es el colmo de un fotógrafo?", + "Que se le rebelen los hijos."], + ["¿Cuál es el colmo de la pereza?", + "Levantarse dos horas antes para estar más tiempo sin hacer nada."], + ["¿Cuál es el colmo de un jardinero?", + "Que su novia se llame Rosa y lo deje plantado."], + ["¿Cuál es el colmo de los colmos?", + "Sentarse en un pajar y pincharse con la aguja."], + ["¿Cuál es el colmo de un electricista?", + "Que su mujer se llame Luz y sus hijos no le sigan la corriente."], + ["¿Cuál es el colmo de un libro?", + "Que en otoño se le caigan las hojas."], + ["¿Cuál es el colmo de una ballena?", + "Ir vacía."], + ["¿Cuál es el colmo de un policía?", + "Detener a un huracán por exceso de velocidad. "], + ["¿Cuál es el colmo de un robot?", + "Tener nervios de acero."], + ["¿Cuál es el colmo de una jirafa?", + "Tener dolor de garganta."], + ["¿Cuál es el colmo de un pez?", + "Quejarse porque no llueve."], + ["¿Cuál es el colmo de Aladino?", + "Tener mal genio."], + ["¿Cuál es el colmo de un astronauta?", + "Quejarse de no tener espacio."], + ["¿Cuál es el colmo de un dinamitero?", + "Que lo exploten en su trabajo."], + ["¿Cuál es el colmo de los colmos?", + "Que dos palomas de la paz se peleen por la ramita de olivo."], + ["¿Cuál es el colmo de Santa Claus?", + "No poder bajar por la chimenea debido a la claustrofobia."], + ["¿Por qué echa Donald azúcar en la almohada?", + "Porque quiere tener dulces sueños."], + ["¿Por qué llevó Goofy el peine al dentista?", + "Porque perdió todos los dientes."], + ["¿Por qué se sienta Goofy en la última fila de los cines?", + "Porque el que ríe el último, ríe mejor."], + ["¿Cuál es el colmo de un escritor?", + "Que su esposa le dé sopa de letras."], + ["¿Cuál es el colmo de un calvo?", + "Tener ideas descabelladas."], + ["¿Cuál es el colmo de una maleta?", + "Pedir vacaciones porque está cansada de tanto viajar."], + ["¿Cuál es el colmo de un gallo?", + "Que se le ponga la piel de gallina."], + ["¿Cuál es el colmo de un carnicero?", + "Tener un perro salchicha."], + ["¿Cuál es el colmo de una silla? ", + "Tener patas y no poder caminar."], + ["¿Cuál es el colmo de un gato?", + "Vivir una vida de perros."], + ["¿Cuál es el colmo más pequeño?", + "El colmillo."], + ["¿Cuál es el colmo de un paracaidista?", + "Tener la moral por los suelos."], + ["¿Cuál es el colmo de la tacañería?", + "Contarse los dedos cada vez que se le da la mano a alguien."], + ["¿Cuál es el colmo de un dálmata?", + "Que le saquen una foto y salga en blanco y negro."], + ["¿Cuál es el colmo de un pastor?", + "Quedarse dormido contando ovejas."], + ["¿Cuál es el colmo de un vidriero?", + "Que quiebre su negocio."], + ["¿Cuál es el colmo de un cerrajero? ", + "Dejarse las llaves dentro de casa."], + ["¿Cuál es el colmo de la paciencia?", + "Tirar una moneda al agua y esperar a que la cara pida socorro."], + ["¿Cuál es colmo de una costurera?", + "Perder el hilo de la conversación."], + ["¿Cuál es el colmo de un escritor?", + "Querer poner siempre punto final a todas las reuniones."], + ["¿Cuál es el colmo de un leñador?", + "Dormir como un tronco."], + ["¿Cuál es el colmo de un músico?", + "Que su hijo dé la nota."], + ["¿Cuál es el colmo de Saturno?", + "Tener anillos y no tener dedos."], + ["¿Cuál es el colmo de un comediante?", + "Que le digan que es un artista serio."], + ["¿Cuál es el colmo de un vanidoso?", + "Que su juego favorito sea el yo-yo."], + ["¿Cuál es el colmo de un plumero?", + "Ser alérgico al polvo."], + ["¿Cuál es el colmo de una cocinera?", + "Llamar a la policía porque los fideos se están pegando."], + ["¿Cuál es el colmo de un dinamitero?", + "Que lo exploten en su trabajo."], + ["¿Cuál es el colmo de un dentista?", + "Quitarle los dientes a un serrucho."], + ["¿Cuál es el colmo de un matemático?", + "Tener muchos problemas."], + ["¿Cuál es el colmo de un pez?", + "Morir ahogado."], + ["¿Cuál es el colmo de un cazador?", + "Querer cazar la Osa Mayor."], + ["¿Cuál es el colmo de un bombero?", + "Llevarse trabajo a casa."], + ["¿Cual es el colmo de la paciencia?", + "Meter una zapatilla en una jaula y esperar a que cante."], + ["¿Cuál es el colmo de un desdentado? ", + "Estar armado hasta los dientes."], + ["¿Cuál es el colmo de una enfermera?", + "Llamarse Dolores de Cabeza."], + ["¿Cuál es el colmo de un cobarde? ", + "Salirse de la cocina cuando se pegan los fideos."], + ["¿Cuál es el colmo de Pinocho?", + "No tener madera de estudiante."], + ["¿Cuál es el colmo de una enfermera?", + "Ponerle un apósito a la leche cortada."], + ["¿Cuál es el colmo de un camello?", + "Vivir toda la vida jorobado."], + ["¿Cuál es colmo de un cementerio?", + "Estar cerrado por luto."], + ["¿Cuál es el colmo de una sardina?", + "Que le den lata."], + ["¿Cuál es el colmo de un cóctel?", + "Sentirse agitado."], + ["¿Cuál es el colmo de un granjero?", + "Dejar abierta la puerta del corral para que se ventile."], + ["¿Cuál es la palabra más larga del mundo?", + "Arroz, porque empieza con A y termina con Z."], + ["¿Cuál es el animal que ve menos?", + "La venada."], + ["¿Cuál es el animal más fiero? ", + "El lopintan, porque el león no es tan fiero como lopintan."], + ["¿Cuál es el perro más explosivo?", + "El vol-can."], + ["¿Cuál es el animal que juega al ajedrez?", + "El caballo."], + ["¿Qué es un codo? ", + "Un gdupo de niñods cantodes."], + ["¿En que se parece una cueva a una alacena? ", + "La cueva tiene estalactitas y estalagmitas y la alacena esta latita de atún, esta latita de anchoas..."], + ["- Doctor, me siento mal.", + "- Pues siéntese bien."], + ["- Hoy tose usted mejor que ayer.", + "- Sí, doctor... Estuve ensayando toda la noche."], + ["- Doctor, doctor... ¿cómo sé si estoy perdiendo la memoria? ", + "- Eso ya se lo dije ayer."], + ["- Doctor, ¿qué me aconseja para evitar resfriarme de nuevo? ", + "Conservar el resfriado que tiene ahora."], + ["- Doctor, doctor, sigo pensando que soy invisible.", + "- ¿Quién dijo eso?"], + ["- Doctor, doctor, el pelo se me está cayendo. ¿Cómo puedo conservarlo?", + "- Use una caja de zapatos."], + ["- Doctor, doctor, hace dos semanas que no como ni duermo. ¿Qué tengo? ", + "- Seguramente sueño y hambre."], + ["- Doctor, doctor, vengo a que me haga una revisión completa.", + "- ¿Le cambio el aceite también?"], + ["- Doctor, doctor, es que tengo un hueso fuera...", + "- Pues dígale que pase."], + ["- Doctor, doctor tengo paperas.", + "- Tome algo de plata y ya tiene pa platanos..."], + ["- Doctor, doctor, cuando tomo café me duele un ojo.", + "- ¿Probó a apartar la cucharilla?"], + ["- ¡¡Doctor, doctor, vengo a que me osculte!!", + "- ¡Rápido, detrás del sillón!"], + ["- Doctor, tengo los dientes muy amarillos, ¿qué me recomienda?", + "- Una corbata marrón."], + ["- Mamá, mamá, en el cole me llaman despistado.", + "- Tú vives en la casa de al lado, niñito."], + ["- Mamá, mamá, en el colegio me llaman despistado.", + "- Reza dos Ave marías y ve con Dios, amén."], + ["- Mamá, mamá, ¿cuándo vamos a comer pan de hoy?", + "- Mañana, hijo, mañana."], + ["- ¡Mamá, mamá, están golpeando la puerta!", + "- Déjala que se defienda sola."], + ["- Mamá, mamá... ¿por qué me llaman pie grande en el cole?", + "No lo sé, pero ¿guardaste los zapatos en el garaje?"], + ["- Mamá, ¿qué es la amnesia?", + "- ¿Qué? ¿Y tú quién eres?"], + ["- Camarero, hay una mosca en mi sopa.", + "- ¿Y cuánto pudo haber bebido una mosquita?"], + ["- Camarero, tráigame un té sin leche.", + "- Disculpe, no tenemos leche, ¿qué le parece un té sin crema?"], + ["- ¿Cómo encontró el señor el solomillo?", + "- Pues levanté un guisante y ahí estaba."], + ["- Camarero, camarero, está usted metiendo la corbata en mi sopa.", + "- No se preocupe, señor, no encoge."], + ["- Camarero, ¡ya le pedí cien veces un vaso de agua! ", + "- ¡Cien vasos de agua para el señor!"], + ["- Camarero, camarero, hay una mosca muerta en mi sopa.", + "- ¿Y qué esperaba por este precio? ¿Una viva?"], + ["- Camarero, ¿el pescado viene solo?", + "- No, se lo traigo yo."], + ["- Camarero, un café solo, por favor.", + "- ¡Todo el mundo fuera!"], + ["- ¡Capitán, capitán, que vamos a pique! ", + "-¡Dije yo que vamos a Acapulco y allí es donde vamos!"], + ["- Capitán, capitán, ¡nos hundimos!", + "- ¡Pero bobo, si estamos en un submarino!"], + ["- ¡Capitán, capitán, perdimos la guerra! ", + "- Pues búsquenla enseguida."], + ["- ¡Soldado, ice la bandera!", + "- Le felicito mi general, le quedó muy hermosa."], + ["- ¡Soldados, presenten armas!", + "- Mi general: Mi fusil. Mi fusil: mi general."], + ["- Mi capitán, los soldados no soportan más, estamos a 42º a la sombra.", + "- Está bien sargento, pueden descansar diez minutos al sol."], + ["- Por favor, ¿la calle Sagasta?", + "- Y... Si pisa fuerte..."], + ["- Pero este reloj no anda.", + "- Claro, todavía no tiene ni un año."], + ["- Camarero, ¿tiene emparedado de hipopótamo?", + "- No, lo siento. Se nos acabó el pan."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("ji","je","ja","jua, jua") +MovieHealLaughterHits1= ("Ja, ja, ja","ji, ji","Je, je, je","Ja, ja") +MovieHealLaughterHits2= ("¡JUA, JUA, JUA!","¡JUO, JUO, JUO!","¡JA, JA, JA!") + +# MovieSOS.py +MovieSOSCallHelp = "¡SOCORRO, %s!" +MovieSOSWhisperHelp = "¡%s necesita que le ayuden en el combate!" +MovieSOSObserverHelp = "¡SOCORRO!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "¡Hola, %s! ¡Me alegra poder ayudarte!" +MovieNPCSOSGoodbye = "¡Nos vemos!" +MovieNPCSOSToonsHit = "¡Los dibus aciertan siempre!" +MovieNPCSOSCogsMiss = "¡"+TheCogs+" fallan siempre!" +MovieNPCSOSRestockGags = "Reaprovisionamiento de bromas %s" +MovieNPCSOSHeal = "Curadibu" +MovieNPCSOSTrap = "Trampa" +MovieNPCSOSLure = "Cebo" +MovieNPCSOSSound = "Sonido" +MovieNPCSOSThrow = "Lanzamiento" +MovieNPCSOSSquirt = "Chorro" +MovieNPCSOSDrop = "Caída" +MovieNPCSOSAll = "Todo" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Suspiro" +MoviePetSOSTrickSucceedBoy = "¡Buen chico!" +MoviePetSOSTrickSucceedGirl = "¡Buena chica!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "CANCELADO\nCANCELADO\nCANCELADO" + +# RewardPanel.py +RewardPanelToonTasks = "Dibutareas" +RewardPanelItems = "Objetos recuperados" +RewardPanelMissedItems = "Objetos no recuperados" +RewardPanelQuestLabel = "Tarea %s" +RewardPanelCongratsStrings = ["¡Así se hace!", "¡Felicitaciones!", "¡Guau!", + "¡Chupi!", "¡Impresionante!", "¡Dibufantástico!"] +RewardPanelNewGag = "¡Nueva broma %(gagName)s para %(avName)s!" +RewardPanelUberGag = "¡%(avName)s consiguió la broma %(gagName)s con %(exp)s puntos de experiencia!" +RewardPanelEndTrack = "¡Sí! ¡%(avName)s llegó al final del circuito de %(gagName)s!" +RewardPanelMeritsMaxed = "Máximo puntaje" +RewardPanelMeritBarLabels = [ "Cartas de despido", "Citaciones", "Botdólares", "Méritos" ] +RewardPanelMeritAlert = "¡Listo para un ascenso!" + +RewardPanelCogPart = "Conseguiste una pieza de disfraz de bot" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Dibu normal", "serás normal"), + ("Cabezota grande", "tendrás la cabeza grande"), + ("Cabecita pequeña", "tendrás la cabeza pequeña"), + ("Piernotas grandes", "tendrás las piernas grandes"), + ("Piernecitas pequeñas", "tendrás las piernas pequeñas"), + ("Dibu grandote", "serás un poco más grande"), + ("Dibu pequeñito", "serás un poco más pequeño"), + ("Imagen plana", "tendrás sólo dos dimensiones"), + ("Perfil plano", "tendrás sólo dos dimensiones"), + ("Transparente", "serás transparente"), + ("Incoloro", "no tendrás color"), + ("Dibu invisible", "serás invisible"), + ] +CheesyEffectIndefinite = "hasta que elijas otro efecto, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Durante los próximos %(time)s minutos, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Durante las próximas %(time)s horas, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Durante los próximos %(time)s días, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " mientras estás en %s" +CheesyEffectExceptIn = ", excepto en %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Secuaz" +SuitPencilPusher = "Chupatintas" +SuitYesman = "Sonriente" +SuitMicromanager = "Microgerente" +SuitDownsizer = "Regulador de empleo" +SuitHeadHunter = "Cazacabezas" +SuitCorporateRaider = "Corporativista" +SuitTheBigCheese = "Pez gordo" +SuitColdCaller = "Aprovechado" +SuitTelemarketer = "Televendedor" +SuitNameDropper = "Fanfarrón" +SuitGladHander = "Efusivo" +SuitMoverShaker = "Mandamás" +SuitTwoFace = "Doscaras" +SuitTheMingler = "Confraternizador" +SuitMrHollywood = "Sr. Hollywood" +SuitShortChange = "Moneditas" +SuitPennyPincher = "Cacomatraco" +SuitTightwad = "Roñoso" +SuitBeanCounter = "Agarrado" +SuitNumberCruncher = "Contable" +SuitMoneyBags = "Monedero" +SuitLoanShark = "Prestamista despiadado" +SuitRobberBaron = "Barón ladrón" +SuitBottomFeeder = "Caradura" +SuitBloodsucker = "Chupasangres" +SuitDoubleTalker = "Embaucador" +SuitAmbulanceChaser = "Persigueambulancias" +SuitBackStabber = "Apuñalaespaldas" +SuitSpinDoctor = "Portavoz" +SuitLegalEagle = "Picapleitos" +SuitBigWig = "Pelucón" + +# Singular versions (indefinite article) +SuitFlunkyS = "un secuaz" +SuitPencilPusherS = "un chupatintas" +SuitYesmanS = "un sonriente" +SuitMicromanagerS = "un microgerente" +SuitDownsizerS = "un regulador de empleo" +SuitHeadHunterS = "un cazacabezas" +SuitCorporateRaiderS = "un corporativista" +SuitTheBigCheeseS = "un pez gordo" +SuitColdCallerS = "un aprovechado" +SuitTelemarketerS = "un televendedor" +SuitNameDropperS = "un fanfarrón" +SuitGladHanderS = "un efusivo" +SuitMoverShakerS = "un mandamás" +SuitTwoFaceS = "un doscaras" +SuitTheMinglerS = "un confraternizador" +SuitMrHollywoodS = "un Sr. Hollywood" +SuitShortChangeS = "un moneditas" +SuitPennyPincherS = "un cacomatraco" +SuitTightwadS = "un roñoso" +SuitBeanCounterS = "un agarrado" +SuitNumberCruncherS = "un contable" +SuitMoneyBagsS = "un monedero" +SuitLoanSharkS = "un prestamista despiadado" +SuitRobberBaronS = "un barón ladrón" +SuitBottomFeederS = "un caradura" +SuitBloodsuckerS = "un chupasangres" +SuitDoubleTalkerS = "un embaucador" +SuitAmbulanceChaserS = "un persigueambulancias" +SuitBackStabberS = "un apuñalaespaldas" +SuitSpinDoctorS = "un portavoz" +SuitLegalEagleS = "un picapleitos" +SuitBigWigS = "un pelucón" + +# Plural versions +SuitFlunkyP = "Secuaces" +SuitPencilPusherP = "Chupatintas" +SuitYesmanP = "Sonrientes" +SuitMicromanagerP = "Microgerentes" +SuitDownsizerP = "Reguladores de empleo" +SuitHeadHunterP = "Cazacabezas" +SuitCorporateRaiderP = "Corporativistas" +SuitTheBigCheeseP = "Peces Gordos" +SuitColdCallerP = "Aprovechados" +SuitTelemarketerP = "Televendedores" +SuitNameDropperP = "Fanfarrones" +SuitGladHanderP = "Efusivos" +SuitMoverShakerP = "Mandamases" +SuitTwoFaceP = "Doscaras" +SuitTheMinglerP = "Confraternizador" +SuitMrHollywoodP = "Sres. Hollywood" +SuitShortChangeP = "Moneditas" +SuitPennyPincherP = "Cacomatracos" +SuitTightwadP = "Roñosos" +SuitBeanCounterP = "Agarrados" +SuitNumberCruncherP = "Contables" +SuitMoneyBagsP = "Monederos" +SuitLoanSharkP = "Prestamistas despiadados" +SuitRobberBaronP = "Barones ladrones" +SuitBottomFeederP = "Caraduras" +SuitBloodsuckerP = "Chupasangres" +SuitDoubleTalkerP = "Embaucadores" +SuitAmbulanceChaserP = "Persigueambulancias" +SuitBackStabberP = "Apuñalaespaldas" +SuitSpinDoctorP = "Portavoces" +SuitLegalEagleP = "Picapleitos" +SuitBigWigP = "Pelucones" + +SuitFaceOffDefaultTaunts = ['¡Buuu!'] + +SuitAttackDefaultTaunts = ['¡¡Toma esto!!', '¡Fíjate bien en esto!'] + +SuitAttackNames = { + 'Audit' : '¡Auditoría!', + 'Bite' : '¡Mordisco!', + 'BounceCheck' : 'Cheque sin fondos!', + 'BrainStorm' : '¡Aguacero!', + 'BuzzWord' : '¡Charlatán!', + 'Calculate' : '¡Calculadora!', + 'Canned' : '¡Enlatado!', + 'Chomp' : '¡Zampón!', + 'CigarSmoke' : '¡Humo de cigarro!', + 'ClipOnTie' : '¡Corbatón!', + 'Crunch' : '¡Crujido!', + 'Demotion' : '¡Degradación!', + 'Downsize' : '¡Recorte de plantilla!', + 'DoubleTalk' : '¡Embaucar!', + 'EvictionNotice' : '¡Deshaucio!', + 'EvilEye' : '¡Mal de ojo!', + 'Filibuster' : '¡Discurso plasta!', + 'FillWithLead' : '¡Lleno de plomo!', + 'FiveOClockShadow' : "¡Barbudo!", + 'FingerWag' : '¡Regañado!', + 'Fired' : '¡Despedido!', + 'FloodTheMarket' : '¡Saturar el mercado!', + 'FountainPen' : '¡Manchón de tinta!', + 'FreezeAssets' : '¡Activos congelados!', + 'Gavel' : '¡Martillo!', + 'GlowerPower' : '¡Mirada feroz!', + 'GuiltTrip' : '¡Culpable!', + 'HalfWindsor' : '¡Nudo imposible!', + 'HangUp' : '¡Corte de línea!', + 'HeadShrink' : '¡Reducción de cabeza!', + 'HotAir' : '¡Aire caliente!', + 'Jargon' : '¡Jerga jurídica!', + 'Legalese' : '¡Parrafada legal!', + 'Liquidate' : '¡Liquidación!', + 'MarketCrash' : '¡Desplome de bolsa!', + 'MumboJumbo' : '¡Rollo total!', + 'ParadigmShift' : '¡Cambio de rumbo!', + 'PeckingOrder' : '¡Pajarraco!', + 'PickPocket' : '¡Ladrón!', + 'PinkSlip' : '¡Carta de despido!', + 'PlayHardball' : '¡Última partida!', + 'PoundKey' : '¡Factura de teléfono!', + 'PowerTie' : '¡Corbata feroz!', + 'PowerTrip' : '¡Viajecito!', + 'Quake' : '¡Terremoto!', + 'RazzleDazzle' : '¡Sonrisote!', + 'RedTape' : '¡Cinta roja!', + 'ReOrg' : '¡Reorganización!', + 'RestrainingOrder' : '¡Orden de alejamiento!', + 'Rolodex' : '¡Agenda!', + 'RubberStamp' : '¡Sellazo!', + 'RubOut' : '¡Borrado!', + 'Sacked' : '¡TeSaco!', + 'SandTrap' : '¡Arenas movedizas!', + 'Schmooze' : '¡Adulación!', + 'Shake' : '¡Sacudida!', + 'Shred' : '¡Triturador!', + 'SongAndDance' : '¡Canto y danza!', + 'Spin' : '¡Giro loco!', + 'Synergy' : 'Sinergia!', + 'Tabulate' : '¡Contabilidad!', + 'TeeOff' : '¡Bolazo!', + 'ThrowBook' : '¡Tirar el libro!', + 'Tremor' : '¡Trepidación!', + 'Watercooler' : '¡Bidón de agua!', + 'Withdrawal' : '¡Retirada de fondos!', + 'WriteOff' : '¡Agujero contable!', + } + +SuitAttackTaunts = { + 'Audit': ["Creo que no te cuadran las cuentas.", + "Parece que estás en números rojos.", + "Deja que te ayude con la contabilidad.", + "Tu columna de débito está por las nubes. ", + "Voy a echar un vistazo a tus activos.", + "Esto te va a desequilibrar las cuentas.", + "Voy a examinar de cerca lo que debes.", + "Con esto voy a dejar tu cuenta a cero.", + "Es la hora de contabilizar tus gastos.", + "Encontré un error en tu libro de contabilidad.", + ], + 'Bite': ["¿Te apetece un mordisquito?", + "¡Prueba un poco de esto!", + "Perro ladrador, poco mordedor.", + "¡Hoy estoy que muerdo!", + "¡Vas a morder el polvo!", + "Cuidado que muerdo.", + "Muerdo siempre que puedo.", + "Voy a morderte un poquito.", + "No me pienso morder la lengua contigo.", + "Sólo un mordisquito... ¿Es pedir demasiado?", + ], + 'BounceCheck': ["Qué lástima, no tienes fondo.", + "Tienes pendiente un pago.", + "Creo que este cheque es tuyo.", + "Me debes este cheque.", + "Estoy cobrando deudas atrasadas.", + "Este cheque está al rojo vivo.", + "Te voy a pasar un buen recargo.", + "Echa un vistazo a esto.", + "Esto te va a costar carísimo.", + "Me gustaría cobrar esto.", + "Voy a devolverte este regalito.", + "Este cheque será tu perdición.", + "Voy a incluir una penalización.", + ], + 'BrainStorm':["Mi predicción es que va a llover.", + "Espero que lleves paraguas.", + "Voy a remojarte un poco.", + "¿Qué te parecen unas cuantas GOTITAS?", + "Ya no hace un día tan bueno, ¿eh, dibu?", + "¿Estás listo para un aguacero?", + "Vas a ver lo que es una buena tormenta.", + "A esto lo llamo ataque relámpago.", + "Me encanta ser un aguafiestas.", + ], + 'BuzzWord':["Deja que te diga unas palabras.", + "¿Te enteraste de lo último?", + "A ver si entiendes esto.", + "Intenta pronunciar esto, dibu.", + "Deja que te ponga los puntos sobre las íes.", + "Te doy mi palabra de que seré claro.", + "Deberías medir mejor tus palabras.", + "A ver cómo esquivas esto.", + "Cuidado, esto no es un juego de palabras.", + "Déjate de palabrería y prueba esto.", + ], + 'Calculate': ["¡Con esto te van a salir las cuentas!", + "¿Habías contado con esto?", + "Suma y sigue; estás acabado.", + "Espera; te voy a ayudar a sumar esto.", + "¿Sumaste todos tus gastos?", + "Según mis cálculos, no estarás aquí mucho tiempo.", + "Aquí tienes el total.", + "Vaya; tu factura no para de aumentar.", + "¡Ponte a sumar esto!", + Cogs + ": 1; Dibus: 0", + ], + 'Canned': ["¿Te gustan las conservas?", + "\"Conserva\" esto como recuerdo.", + "¡Esto está recién salido de la lata!", + "¿Te dijeron lo bien que te conservas?", + "¡Voy a hacerte una donación de alimentos en conserva!", + "¡Prepárate para una buena lata!", + "Me gusta que \"conserves\" todos tus ánimos.", + "¡Te voy a poner en conserva!", + "¡El menú de hoy va a ser dibu enlatado!", + "\"Conserva\" esto para el recuerdo...", + ], + 'Chomp': ["¿Quieres hacer el favor de masticar bien?", + "¡Ñam, ñam, ñam!", + "No te olvides de cerrar la boca al comer.", + "¿Tienes ganas de masticar algo?", + "Prueba a masticar esto.", + "¡Vas a ser mi cena!", + "¡Me encantan las dietas a base de dibus!", + ], + 'ClipOnTie': ["¿Por qué no te arreglas un poco?", + "¡Tienes que llevar corbata a las reuniones!", + TheCogs + "elegantes siempre se ponen una de éstas...", + "Pruébate ésta, a ver qué tal te queda.", + "La imagen es fundamental para tener éxito en la vida.", + "Aquí no se admite a nadie sin corbata.", + "¿Quieres que te ayude a ponerte esto?", + "Una buena corbata dice mucho de ti.", + "Veamos qué tal te queda esto.", + "Esto a lo mejor te aprieta un poco.", + "Es mejor que te arregles antes de MARCHARTE.", + "Toma, con esto serás el dibu más apuesto del dibuparque.", + ], + 'Crunch': ["Parece que estás un poco crujido.", + "¡Es hora de crujirse un poco!", + "Con esto te van a crujir las articulaciones.", + "¡Mira qué crujiente tan delicioso!", + "¿No oyes un crujido?", + "¿Qué prefieres, blandito o crujiente?", + "Esto está crujiente y apetitoso.", + "¡Prepárate para que te crujan los huesos!", + "¡Me encantan los postres crujientes!" + ], + 'Demotion': ["Vas a bajar puestos en la empresa. ", + "Vas a volver a trabajar de botones.", + "Me parece que te quedaste sin despacho.", + "¡Amigo, vas para abajo!", + "Creo que tu puesto peligra.", + "Tienes poco futuro en esta empresa.", + "Laboralmente, estás en un callejón sin salida.", + "Tu puesto en la empresa se está tambaleando.", + "Te veo preparando café bien pronto.", + "Esto va a ir directo a tu expediente.", + ], + 'Downsize': ["¿Qué tal unos cuantos recortes?", + "A veces hay que aplicar las tijeras.", + "Yo que tú iría pidiendo entrevistas de trabajo.", + "¿Guardaste el suplemento de empleo de tu periódico?", + "¿Nunca te dijeron que eres prescindible?", + "Tu perfil no se ajusta a nuestras necesidades actuales.", + "¿Oíste hablar de las reestructuraciones?", + "Me temo que ya no nos eres útil.", + "¿Por qué no te buscas un trabajo en otro sitio?", + "No pasas de este año en esta empresa.", + "Me temo que los cambios en la empresa te van a afectar.", + "Me temo que nos sobra algo de personal.", + ], + 'EvictionNotice': ["¡Llegó la hora de la mudanza!", + "Haz las maletas, dibu.", + "Creo que vas a tener que cambiar de residencia.", + "Dicen que debajo del puente se está muy bien.", + "Me temo que no pagaste el alquiler.", + "¿Habías pensado en redecorar tu vivienda?", + "A partir de ahora vas a disfrutar del aire libre.", + "¿No decías que te gustaban los espacios abiertos?", + "¡Estás fuera de lugar!", + "Prepárate para ser reubicado.", + "Tranquilo; ahora vas a conocer a más gente.", + "Vas a hacer un tour de hostales.", + ], + 'EvilEye': ["Te voy a echar el mal de ojo.", + "¿Puedes echarle un ojo a esto?", + "Espera. Se me metió algo en el ojo.", + "¡Te eché el ojo!", + "En la vida hay que tener ojo para todo.", + "Tengo mucho ojo para el mal.", + "¡Te voy a meter el dedo en el ojo!", + "¡Ten mucho ojo conmigo!", + "¡Te voy a meter en el ojo del huracán!", + "¡No te pienso quitar ojo!", + ], + 'Filibuster':["¿Te gustan los discursos?", + "Esto va a durar un ratito.", + "Podría estar así todo el día.", + "No me hace falta tomarme ni un respiro.", + "Puedo seguir y seguir.", + "Nunca me canso de hacer esto.", + "Soy capaz de aburrir a las ovejas.", + "¿Te importa si digo unas palabras?", + "Creo que voy a soltar un discursito.", + "Tengo preparadas unas frases para ti.", + ], + 'FingerWag': ["Te lo dije un millón de veces.", + "Dibu, te estoy hablando a ti.", + "No me hagas reír.", + "No me hagas ir hasta ahí.", + "Estoy harto de repetírtelo.", + "Creo que ya te dije esto.", + "No nos tienes ningún respeto a los bots.", + "Es hora de que empieces a prestar atención.", + "Bla, bla, bla, bla, bla.", + "Voy a tener que aplicarte un correctivo.", + "¿Cuántas veces te lo tengo que decir?", + "No es la primera vez que pasa esto.", + ], + 'Fired': ["Espero que te trajeras algo para la barbacoa.", + "Esto va a ponerse calentito.", + "Seguro que con esto entras en calor.", + "Espero que seas un animal de sangre fría.", + "¡Caliente, caliente!", + "Creo que te va a hacer falta un extintor.", + "¡Vas a quedarte chamuscado!", + "¡Vas a quedar muy doradito!", + "Esto le da otro significado a la expresión \"a punto\".", + "Espero que te pusieras protección solar.", + "Avísame cuando estés crujiente.", + "La cosa está que arde.", + "Vas a arder en deseos de volver al dibuparque.", + "Creo que tienes un temperamento ardiente.", + "A ver, déjame que te ponga el termómetro...", + "¡Tienes mucha chispa!", + "El que juega con fuego...", + "¿Nunca te dijeron que te sale humo de las orejas?", + ], + 'FountainPen': ["Esta mancha no va a salir. ", + "Vas a tener que ir a la tintorería.", + "Prepárate para comprar detergente.", + "Esto no es precisamente tinta invisible.", + "Vas a tener que cambiarte de ropa.", + "La tinta de esta pluma no se acaba nunca.", + "Toma, usa mi pluma.", + "¿Lees bien mi letra?", + "Ya no hacen plumas como las de antes.", + "Vaya, hay un borrón en tu expediente.", + "Te dije que no cargaras las tintas.", + ], + 'FreezeAssets': ["¿Te sirvo un poco de hielo?", + "Te voy a juzgar por tus acciones.", + "Me parece que voy a pasar a la acción.", + "Voy a congelar la imagen.", + "El ambiente está muy frío.", + "Este año se va a adelantar el invierno.", + "Esto te enfriará los ánimos.", + "Mi plan está a punto de cristalizar.", + "Te vas a quedar petrificado.", + "Se te van a congelar las ideas.", + "¿Te gustan las cenas frías?", + "Voy a refrescarte la memoria.", + ], + 'GlowerPower': ["¿Me miras a mí?", + "Me dijeron que tengo una mirada penetrante.", + "Mírame fijamente a los ojos...", + "¿Te gusta mi caída de ojos?", + "Tengo una mirada arrebatadora.", + "¿No te parecen unos ojos muy expresivos?", + "Siempre me dijeron que tengo unos ojos preciosos.", + "El secreto está en la mirada.", + "¡Veo, veo! ¡Veo un dibu en apuros!", + "Deja que te mire bien...", + "¿Echamos una mirada a tu futuro?", + ], + 'GuiltTrip': ["¡Vas a cargar con toda la culpa!", + "¿Te sientes culpable?", + "¡Todo es culpa tuya!", + "¡Te pienso culpar por todo!", + "¡Declarado culpable!", + "¡No pienso volver a hablarte!", + "¡Más te valdría pedir perdón!", + "¡No te pienso perdonar en la vida!", + "¿No crees que te portaste mal?", + "¡No intentes echarme la culpa!", + "¡Eres un culpable con causa!", + ], + 'HalfWindsor': ["¡Esta es la corbata más hermosa que jamás viste!", + "Intenta no hacerte un nudo.", + "Se te va a hacer un nudo en el estómago.", + "Tienes suerte de que sea un nudo fácil.", + "¿No se te hace un nudo en la garganta?", + "¡Seguro que no sabes ni hacerte el nudo!", + "¡Después de esto te voy a anudar la lengua!", + "No debería malgastar esta corbata contigo.", + "¡No te mereces esta corbata tan linda!", + ], + 'HangUp': ["Se cortó tu llamada.", + "¡Adiós!", + "Es el momento de terminar la conexión.", + "¡...Y no vuelvas a llamarme!", + "¡Clic!", + "Se acabó la conversación.", + "Voy a cortar la línea.", + "Creo que tienes la línea en mal estado.", + "Me parece que no tienes línea.", + "Finalizó tu llamada.", + "Espero que me escuches alto y claro.", + "Te equivocaste de número.", + ], + 'HeadShrink': ["¿Estuviste recientemente en el Amazonas?", + "Cariño, encogí a un dibu.", + "Espero que tu orgullo no se quede encogido.", + "¿Encogiste en la lavadora?", + "Te dije que no te laves con agua caliente.", + "No pierdas la cabeza por esto.", + "¿Es que perdiste la cabeza?", + "¡Pero qué poca cabeza tienes!", + "¡Eres un cabeza de chorlito!", + "No sabía que pasaste una temporada con los jíbaros.", + ], + 'HotAir':["El ambiente se está acalorando.", + "Vas a sufrir una ola de calor.", + "Llegué al punto de ebullición.", + "Me parece que te vas a achicharrar un poco.", + "Vas a quedar un poco doradito...", + "Recuerda: si ves humo, es que hay fuego.", + "Te veo un poco quemado.", + "Creo que hoy va a haber fumata blanca.", + "Supongo que es la hora de avivar un poco el fuego.", + "Permíteme que encienda la llama del amor.", + "¡Esto se va a poner al rojo vivo!", + "¿Te seco un poco el pelo?", + ], + 'Jargon':["Qué cantidad de tonterías se dicen...", + "A ver si adivinas qué significa esto.", + "Espero que me recibas alto y claro.", + "Me parece que voy a tener que hablar más alto.", + "Insisto; es mi turno para hablar.", + "Te voy a ser muy sincero.", + "Debo pontificar sobre este asunto.", + "¿Ves? Las palabras pueden hacer daño.", + "¿Comprendiste lo que quiero decir?", + "Palabras, palabras, palabras...", + ], + 'Legalese':["Debes cejar en tu empeño.", + "En términos legales, vas a perder el juicio.", + "¿Estás al tanto de las connotaciones legales?", + "¡No puedes situarte al margen de la ley!", + "Deberían hacer una ley expresamente contra ti.", + "Me reservo el derecho de modificar tu contrato.", + "Las opiniones expresadas en este ataque no coinciden con las de Toontown Online.", + "No me hago responsable de los daños derivados de este ataque.", + "Vas a asumir los costes directos e indirectos de este ataque.", + "Me reservo el derecho de prolongar este ataque.", + "¡Estás fuera de mi sistema legal!", + "No vas a poder asumir los costes legales de este ataque.", + ], + 'Liquidate':["Me encanta que nuestra relación sea fluida.", + "¿Estás teniendo problemas de liquidez?", + "Voy a tener que ponerte en remojo.", + "Hay que dar fluidez a este proceso.", + "¡Recuerda que el suelo está resbaladizo!", + "Tu dinero es papel mojado.", + "En esta vida hay que mojarse un poco.", + "Te voy a poner en venta al 50 %.", + "Te vas a diluir un poco...", + "Me apetece un dibu pasado por agua.", + ], + 'MarketCrash':["Me parece que tus acciones se desplomaron.", + "No vas a sobrevivir al cierre de sesión.", + "Tus valores caen en picado.", + "Tu cartera de acciones va a quedarse temblando.", + "Va a ser todo un lunes negro para ti.", + "Hoy estoy de lo más alcista.", + "Creo que me voy a desprender de tus acciones.", + "¡Más vale que vendas todo pronto!", + "¡Vende, rápido!", + "Vas a iniciar una tendencia bajista.", + "El mercado se va a desplomar encima de ti.", + ], + 'MumboJumbo':["Voy a ver si me expreso con claridad.", + "Es así de sencillo.", + "Te voy a explicar cómo vamos a resolver esto.", + "Permíteme que te resuma esto.", + "A lo mejor esto te suena a discurso.", + "No quiero soltarte una parrafada, pero...", + "Uy, se me llena la boca.", + "Odio alargarme en mis peroratas.", + "Permíteme unas palabritas.", + "Tengo preparado un discursito para ti.", + ], + 'ParadigmShift':["¡Cuidado! Hoy estoy de lo más cambiante.", + "¡Prepárate para un buen golpe de timón!", + "Creo que hay que enderezar tu rumbo.", + "Vas a tener un ligero cambio de perspectiva.", + "Acabarás cambiando de lugar.", + "¡Perdiste el norte!", + "Seguro que nunca cambiaste tanto de orientación.", + "¡Creo que no te va a gustar este cambio!", + "¡No me hagas cambiar de parecer!", + ], + 'PeckingOrder':["Sí; soy todo un pájaro.", + "Más vale pájaro en mano...", + "Me dijo un pajarito que vas a volver de un golpe al dibuparque.", + "¡No huyas como un gallina!", + "Creo que tienes la cabeza llena de pájaros.", + "¡Cría cuervos y tendrás muchos!", + "¡Ya volaste bastante, pajarito!", + "Me encanta salir a picar algo.", + "Voy a hacer un buen caldo de gallina contigo.", + ], + 'PickPocket': ["Deja que me haga cargo de tus objetos personales.", + "¿A ver qué llevas ahí?", + "Esto es como quitarle un caramelo a un niño.", + "Qué robo...", + "Espera, yo te lo tengo.", + "No pierdas de vista mis manos.", + "La mano es más rápida que el ojo...", + "Nada por aquí...", + "La dirección no se hace responsable de los objetos perdidos.", + "Santa Rita, Rita...", + "No te vas a dar ni cuenta.", + "Te voy a dejar en cueros.", + "¿Te importa si me quedo con esto?", + "Te voy a aligerar de peso.", + ], + 'PinkSlip': ["Tengo algo de correspondencia para ti.", + "¿Estás asustado? ¡Te pusiste pálido!", + "Esta carta te va a hacer mucha ilusión.", + "Vaya; creo que alguien va a tener que hacer las maletas.", + "¡Eh, no te vayas sin despedirte!", + "¿Te despediste ya de todo el mundo?", + "¡Mira, una carta de amor!", + "Creo que este papel es para ti...", + "Qué tristes son las despedidas.", + "¡Aquí tienes tu carta de despido. Largo de aquí!", + ], + 'PlayHardball': ["¿Así que quieres jugar al béisbol?", + "No te recomiendo que juegues conmigo.", + "¡Batea de una vez!", + "¡Vamos, batea esto!", + "¡Y aquí viene el lanzamiento...!", + "Vas a tener que mandarla lejos.", + "Te voy a sacar del estadio de un batazo.", + "Te voy a mandar al dibuparque de un batazo.", + "¡Esta es tu carrera final!", + "¡No vas a poder jugar conmigo!", + "¡Te voy a poner en órbita!", + "¡Vas a ver qué bola te lanzo!", + ], + 'PoundKey': ["Es hora de devolver algunas llamadas.", + "¿Qué tal una llamada gratis?", + "¡Ring, ring! ¡Es para ti!", + "Toma, para que me llames cuando quieras.", + "Me sobra una almohadilla...", + "Espero que tu teléfono sea de tonos.", + "Déjame que marque este número.", + "Voy a hacer una llamada sorpresa.", + "Espera, te voy a dar un toque.", + "Dibu, sólo puedes hacer una llamada.", + ], + 'PowerTie': ["Te veré más tarde; parece que se te hizo un nudo en la garganta.", + "Voy a atar unos cuantos cabos sueltos.", + "¡Con esto vas a estar muy elegante!", + "Para que vayas practicando los nudos...", + "Ya es hora de que empieces a vestir bien.", + "¡Esta es la corbata más fea que jamás viste!", + "¿No te sientes importante usando esto?", + "¡Vas a ver cómo cambia tu aspecto!", + "¡Nada mejor que una corbata de regalo!", + "No sabía qué regalarte, así que ¡toma!", + ], + 'PowerTrip': ["Haz las maletas, que nos vamos de excursión.", + "¿Tuviste un buen viaje?", + "Lindo viaje; te veré el año que viene.", + "¿Qué tal tu viaje?", + "¡Siento que hayas tenido que venir hasta aquí!", + "¡Me parece que vas a irte de viaje!", + "¡Vas a ver lo que es viajar!", + "¿Te gusta la astrología?", + "¿Quieres tener una experiencia astral?", + "¡Vas a ver las estrellas!", + "Prepara las maletas, te vas de viaje... ¡astral!", + ], + 'Quake': ["¿Qué tal una sacudidita de tierra?", + "Me encantan los modelos a escala... Richter.", + "Eres todo un terremoto, ¿eh?", + "¿A que no sabes dónde está el epicentro?", + "Este se va a salir de la escala Richter.", + "¡Con esto se van a sacudir los cimientos!", + "¡Vas a ver qué sacudón!", + "¿Estuviste alguna vez en un terremoto?", + "¡Cuidado; la tierra se agita bajo tus pies!", + ], + 'RazzleDazzle': ["Léeme los labios.", + "¿Te gustan mis dientes?", + "¿No te parezco encantador?", + "Disfruta de mi encantadora sonrisa...", + "Mi dentista es un gran profesional.", + "Una sonrisa cegadora, ¿eh?", + "Parece mentira que sean postizos, ¿eh?", + "Una sonrisa arrebatadora, ¿eh?", + "Suelo anunciar dentífricos, ¿sabes?", + "Siempre uso hilo dental después de comer.", + "¡Di \"Whisky \"!", + ], + 'RedTape': ["Te voy a envolver para regalo.", + "Voy a dejar todo atado y bien atado.", + "¡Cómo te enrollas!", + "A ver si puedes cortar esta cinta roja.", + "Vas a estar un poco estrecho ahí.", + "Espero que no tengas claustrofobia.", + "Me aseguraré de que no vayas a ninguna parte.", + "Espera; te voy a aislar.", + "¡Vamos a inaugurar un nuevo dibu!", + "Quiero que sientas apego por mí.", + ], + 'ReOrg': ["¡No te va a gustar la forma en que lo reorganicé todo!", + "Creo que hace falta un poco de reorganización.", + "No estás tan mal, sólo hay que reorganizarte.", + "¿Te gusta mi capacidad de reorganización?", + "Pensé que le iría bien una nueva imagen a todo.", + "¡Hay que reorganizarte!", + "Pareces un poco desorganizado.", + "Espera; voy a reorganizar tus pensamientos.", + "Voy a esperar a que te reorganices un poco.", + "¿Te importa si organizo un poco todo esto?", + ], + 'RestrainingOrder': ["Deberías alejarte un poco.", + "¡Me encargaron que te dé una orden de alejamiento!", + "No te puedes acercar a menos de dos metros de mí.", + "Creo que deberías guardar las distancias.", + "Debería alejarte para siempre.", + "¡Hay que alejar a este dibu!", + "Intenta alejar tu mente de todo esto.", + "Espero que todo esto no te aleje demasiado de la realidad.", + "¡A ver si consigues acercarte ahora!", + "¡Te ordeno que te alejes!", + "¿Por qué no empezamos por alejar posiciones?" + ], + 'Rolodex': ["Tengo tu dirección en algún sitio.", + "Aquí tengo el número de la perrera. ", + "Espera; te voy a dar mi tarjeta.", + "Tengo tu número aquí mismo.", + "Te tengo controlado de la A a la Z.", + "Te tengo más que fichado.", + "¡Esquiva esto!", + "Cuidado; no te cortes con los bordes.", + "Voy a darte unas cuantas direcciones útiles.", + "Toma; llámame a estos números.", + "Quiero asegurarme de que sigamos en contacto.", + ], + 'RubberStamp': ["Me gusta dar siempre una buena impresión.", + "Es importante aplicar una presión firme.", + "Dejo siempre una buena huella.", + "Te voy a dejar más plano que un sello.", + "Hay que DEVOLVERTE AL REMITENTE.", + "Fuiste CANCELADO.", + "Voy a mandarte al dibuparque con sello URGENTE.", + "Creo que te vas a sentir muy RECHAZADO.", + "Para mandarte al dibuparque no hará falta FRANQUEO.", + "Vas a ir al dibuparque POR AVIÓN.", + ], + 'RubOut': ["Y ahora, la desaparición final.", + "Me parece que te perdí.", + "Decidí que te quedas fuera.", + "Siempre elimino todos los obstáculos.", + "Vaya; voy a borrar este error.", + "Me gusta que todo lo molesto desaparezca.", + "Me gusta que todo esté limpio y ordenado.", + "Por favor, intenta seguir animado.", + "Ahora te veo... Ahora no te veo.", + "Creo que vas a ponerte borroso.", + "Voy a eliminar el problema.", + "Me voy a ocupar de tus áreas problemáticas.", + ], + 'Sacked':["Cuidado; viene el hombre del saco.", + "¡Te tengo en el saco!", + "¿Te apetece echar una carrera de sacos?", + "¿Sacas tú o saco yo?", + "¡Voy a ponerte a buen recaudo!", + "Tengo el récord de carreras de sacos.", + "Te voy a sacar de aquí...", + "¡Se acabó; te voy a meter en el saco!", + "¡No nos metas a todos los bots en el mismo saco!", + "Todos me dicen que tengo un buen saque.", + ], + 'Schmooze':["Seguro que no te esperabas esto.", + "Esto te va a quedar muy bien.", + "¡Te lo ganaste!", + "No quiero aburrirte con mi discurso.", + "Adular a la gente me da buenos resultados.", + "Voy a exagerar un poquito.", + "Es hora de animar un poco al personal.", + "Ahora hablemos un poco de ti.", + "Te mereces una palmadita en la espalda.", + "Llegó el momento de alabar tu trayectoria.", + "Siento tener que bajarte de tu pedestal, pero...", + ], + 'Shake': ["Estás justo en el epicentro.", + "Estás pisando una falla.", + "Esto va a estar movidito...", + "Creo que va a ocurrir un desastre natural.", + "Es un desastre de proporciones sísmicas.", + "Éste se va a salir de la escala Richter.", + "Es hora de ponerse a cubierto.", + "Pareces alterado.", + "¿Listo para bailar el meneíto?", + "No te agites demasiado, por favor.", + "Esto te va a poner patas arriba.", + "Te recomiendo un buen plan de escape.", + ], + 'Shred': ["Tengo que deshacerme de bastante basura.", + "Voy a reciclar un poco de papel.", + "Creo que me voy a deshacer de ti ahora mismo.", + "Con esto me desharé de las pruebas.", + "Ya no hay manera de demostrar nada.", + "A ver si consigues recomponer esto.", + "Esto te va a hacer trizas.", + "Voy a triturar esa idea.", + "No quiero que esto caiga en malas manos.", + "Adiós a las pruebas.", + "Creo que esta era tu última esperanza.", + ], + 'Spin': ["¿Te apetece ir a dar una vuelta?", + "¿Qué tal si te centrifugo un poco?", + "¡Tu cabeza va a dar vueltas con esto!", + "Voy a dar otra vuelta de tuerca.", + "Te voy a dar una vuelta.", + "Yo siempre estoy de vuelta de todo.", + "Cuidado. La vida da muchas vueltas.", + "¡Vamos a hacer un doble giro mortal!", + "Un poco más y de vuelta al dibuparque.", + ], + 'Synergy': ["Voy a presentar esto en el comité.", + "Tu proyecto fue cancelado.", + "Cortamos tus fondos.", + "Estamos reestructurando tu división.", + "Lo sometí a votación y perdiste.", + "Acabo de recibir la aprobación final.", + "Un buen equipo puede superar cualquier problema.", + "Luego me ocuparé de esto contigo.", + "Vamos a ir al grano.", + "Considera esto una crisis sinergética.", + ], + 'Tabulate': ["Esto no me cuadra.", + "No te salen las cuentas.", + "Te va a salir una cuenta enorme.", + "Voy a desglosarte en un momento.", + "¿Estás listo para unas cifras aterradoras?", + "Es hora de que pagues la dolorosa.", + "Hora de saldar cuentas...", + "Me encanta tener las cuentas claras.", + "Las cuentas claras conservan la enemistad.", + "Estos números van a dejarte con la boca abierta.", + ], + 'TeeOff': ["Tienes un par nefasto.", + "¡Bola!", + "Vas a ver qué bien le pego.", + "¡Caddie, dame un hierro!", + "A ver si mejoras este golpe.", + "¡Mira qué swing!", + "Ésta bola va a entrar de un solo golpe.", + "Estás pisando mi green.", + "Fíjate qué buen grip tengo.", + "¡Mira qué birdie!", + "¡No pierdas de vista la bola!", + "¿Te importa si juego un poco?", + ], + 'Tremor': ["¿Sentiste eso?", + "No te dan miedo los temblores, ¿verdad?", + "Los temblores suelen ser el principio.", + "Pareces tembloroso.", + "¡Voy a agitar un poco las cosas!", + "¿Estás listo para bailar la rumba?", + "¿Qué te pasa? Pareces agitado.", + "¡Tiembla de miedo!", + "¿Por qué tiemblas?", + ], + 'Watercooler': ["Esto te refrescará un poco.", + "¿No es refrescante?", + "Venía a entregar un pedido.", + "¡Pruébala; está fresquita!", + "¿Qué pasa? ¡Es sólo agua!", + "No te preocupes; es agua potable.", + "Qué bien; otro cliente satisfecho.", + "Es la hora de entregar el pedido diario.", + "Espero que no destiñas.", + "¿Te apetece un trago?", + "Hay que dar de beber al sediento.", + "¿No tenías sed? ¡Pues toma!", + ], + 'Withdrawal': ["Creo que te quedaste sin fondos", + "Espero que tengas suficientes fondos en tu cuenta.", + "¡Toma, con intereses!", + "Te estás quedando sin liquidez.", + "Pronto vas a tener que hacer un depósito.", + "Estás al borde del colapso económico.", + "Creo que estás en recesión.", + "Tus números se van a ver afectados.", + "Preveo una crisis inminente.", + "¡Vas a tener un agujero en tu cuenta!", + ], + 'WriteOff': ["Permíteme que incremente tus deudas.", + "Vamos a intentar sanear tu situación.", + "Es hora de hacer el balance de tus cuentas.", + "Esto no va a quedar nada bien en tu libro de contabilidad.", + "Estoy buscando dividendos.", + "¿Por qué no haces balance de tus pérdidas?", + "Olvídate de la gratificación que te toca.", + "Voy a poner patas arriba tus cuentas.", + "Creo que tus pérdidas van a ser cuantiosas.", + "Tu saldo se va a ver un poco afectado.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Esperando a otros jugadores...", + +# Elevator.py +ElevatorHopOff = "Bajarse" +ElevatorStayOff = "Si te bajas, tendrás que esperar\na que el ascensor marche o se vacíe." +ElevatorLeaderOff = "Sólo tu líder puede decidir cuándo bajarse." +ElevatorHoppedOff = "Tienes que esperar al siguiente ascensor." +ElevatorMinLaff = "Necesitas %s puntos de risa para viajar en este ascensor." +ElevatorHopOK = "OK" +ElevatorGroupMember = "Sólo tu líder de grupo puede\n decidir cuando subir." + +# DistributedCogKart.py +KartMinLaff = "Necesitas %s puntos de risa para subirte a este kart" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Inc." +CogsIncModifier = "%s" + CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "¿Quién es?" +DoorWhoAppendix = " qué?" +DoorNametag = "Puerta" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "¡Tú necesitas bromas! Ve a hablar con Tato Tutorial" +FADoorCodes_DEFEAT_FLUNKY_HQ = "¡Vuelve cuando hayas derrotado al Secuaz!" +FADoorCodes_TALK_TO_HQ = "¡Ve y consigue tu recompensa del funcionario Enrique!" +FADoorCodes_WRONG_DOOR_HQ = "¡Puerta incorrecta! ¡Sal por la otra puerta al dibuparque!" +FADoorCodes_GO_TO_PLAYGROUND = "¡Salida equivocada! ¡Necesitas salir al dibuparque!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "¡Acércate al Secuaz para combatir con él!" +FADoorCodes_TALK_TO_HQ_TOM = "¡Ve y consigue tu recompensa en el cuartel general!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "¡Cuidado! ¡Hay un bot dentro!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "Si vas como dibu te van a pescar. Primero tendrás que armarte un disfraz de bot completo.\n\nHáztelo con piezas de la fábrica." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "¡Si vas como dibu te van a pescar! ¡Primero, necesitas completar tu disfraz de chequebot!\n\nCrea tu disfraz de chequebot haciendo las dibutareas en Sueñolandia de Donald." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "¡Si vas como dibu te van a pescar! ¡Primero, necesitas completar tu disfraz de abogabot!\n\nCrea tu disfraz de abogabot haciendo las dibutareas después de Sueñolandia de Donald." +FADoorCodes_BB_DISGUISE_INCOMPLETE = "¡Si vas como dibu te van a pescar! ¡Primero, necesitas completar tu disfraz de jefebot!\n\nCrea tu disfraz de jefebot haciendo las dibutareas después de Sueñolandia de Donald." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Eloy", + "¡Eloy es importante, el mañana también!"], + + # 2009 April fools contest Jokes. First few doors of Loopy lane + 2200 : {28:["Pasta", + "¡Pasta de bromas, que vienen los bots!"], + 41:["Tomas", + "¿Qué es de Tomas? ¿Qué Tomas? Un café."], + 40:["Mickey", + "¡Mickedado sin nada, es una locura!"], +## 25:["Pasta25", +## "¡Pasta de bromas, que vienen los bots!"], + 27:["Traje", + "¡Yo no traje traje!"]}, + + 2300: ["Carlos", + "¡A los bots, hay que bus-Carlos por todas partes!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladino", + "Hasta A-la-dino sauria le llegó su hora."], + 6 : ["Tipo", + "¿Tipo nemos más piezas de bot?"], + 30 : ["Mesa", + "Mesa caído un trozo de tarta."], + 28: ["Tele", + "¿Tele vas a comer?"], + 12: ["Harta", + "¿Harta donde vamos a llegar con esto?"], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Aitor ", + "¡Aitor Tilla de Patatas!"], + + ["Adela", + "¡Adela Miendo!"], + + ["Abraham", + "¡Abraham Lapuerta!"], + + ["Amira", + "¡Amira Quienhavenido!"], + + ["Aquiles", + "¡Aquiles Dejoporhoy!"], + + ["Archibaldo", + "¡Archibaldo Enlascarpetas!"], + + ["Armando", + "¡Armando Lío!"], + + ["Alicia", + "Alicia Nuro"], + + ["Augusto", + "¡Augusto de Conocerle!"], + + ["Azucena", + "¡Azucena Estaservida!"], + + ["Baldomero", + "¡Baldomero Alaplancha!"], + + ["Baltasar", + "¡Baltasar Ysecayó!"], + + ["Belinda", + "¡Belinda Flordeljardín!"], + + ["Beltrán", + "¡Beltrán Kilo!"], + + ["Bernabé", + "¡Bernabé Abrirlapuerta!"], + + ["Blasa", + "¿Blasa Brironó?"], + + ["Carmen", + "¡Carmen Tolada y Fresca!"], + + ["Calixto", + "¡Calixto Queesuno!"], + + ["Camila", + "¡Camila Groquehayavenido!"], + + ["Cintia", + "¡Cintia Palpelo!"], + + ["Clemente", + "¡Clemente Privilegiada!"], + + ["Cleopatra", + "¡Cleopatra Jeado!"], + + ["Clotilde", + "¡Clotilde Monio!"], + + ["Consuelo", + "¡Consuelo Fregado!"], + + ["Crisóstomo", + "¡Crisóstomo Uncafeconleche!"], + + ["Demetrio", + "¡Demetrio Doloquetenga!"], + + ["Bernabé", + "¡Bernabé Quiénvino!"], + + ["Edmundo", + "¡Edmundo Esunpañuelo!"], + + ["Engracia", + "¡Engracia Porabrirlapuerta!"], + + ["Estela", + "¡Estela Marinera!"], + + ["Eugenio", + "¡Eugenio de la Lámpara!"], + + ["Ezequiel", + "¿Ezequiel Es?"], + + ["Fabiola", + "¡Fabiola Iadiós!"], + + ["Filomeno", + "Filomeno Malquehellegado."], + + ["Florinda", + "¡Florinda Se y Arribalasmanos!"], + + ["Gaspar", + "¡Gaspar Ecequevallover!"], + + ["Genoveva", + "¡Genoveva Desabotella!"], + + ["Jairo", + "¡Jairo Níasdelavida!"], + + ["Jazmín", + "¡Jazmín Nero del Carbón!"], + + ["Jeremías", + "¡Jeremías Sonpasiempre!"], + + ["Jessica", + "¡Jessica Nálisis Gratuito!"], + + ["Jesús", + "¡Jesús Piros Románticos!"], + + ["Rafa", + "Rafa Bricante"], + + ["Kevin", + "¿Kevin Olesirvo?"], + + ["Leonor", + "¡Leonor Gulloso de su Melena!"], + + ["Menchu", + "Menchu Letón"], + + ["Macarena", + "¡Macarena Blanca de la Playa!"], + + ["Magdalena", + "¡Magdalena Ycafé Conleche!"], + + ["Maite", + "¡Maite Digoquesoyyó!"], + + ["Marcos", + "¡Marcos Tilla!"], + + ["Armando", + "¡Armando de la Tropa!"], + + ["Olimpia", + "¡Olimpia Osevá!"], + + ["Olivia", + "¡Olivia Tuabuela!"], + + ["Omar", + "¡Omar Ejada!"], + + ["Oscar", + "¡Oscar Naval de Río!"], + + + ["Pánfilo", + "¡Pánfilo de la Navaja!"], + + ["Pascual", + "¿Pascual Esugracia?"], + + ["Luisa", + "Luisa Belotodo"], + + ["Ramiro", + "¡Raimiro Ynoteveo!"], + + ["Ramona", + "¡Ramona Vestida de Seda!"], + + ["Raúl", + "¡Raúl Timo de la Fila!"], + + ["Renato", + "¡Renato Londrado!"], + + ["Juan Ricardo", + "¡Juan Ricardo Borriquero!"], + + ["Rubén", + "Rubén Tana"], + + ["Sabina", + "Sabina Gre"], + + ["Samanta", + "¡Samanta de Lana!"], + + ["Sandra", + "¡Sandra Josa!"], + + ["Serafín", + "¡Serafín de la Historia!"], + + ["Serena", + "¡Serena y Tranquila!"], + + + ["Servando", + "¡Servando Lero!"], + + ["Silvio", + "¡Silvio Lento!"], + + ["Jacobo", + "Jacobo Rego"], + + ["Elena", + "Elena Nomaldito"], + + ["Socorro", + "¡Socorro Carreras!"], + + ["Gala", + "¡Gala Rdonado!"], + + + + ["Soledad", + "¡Soledad y Compañía!"], + + ["Enrique", + "¡Enrique Cimiento!"], + + ["Tamara", + "¡Tamara Villosa!"], + + ["Teobaldo", + "¡Teobaldo Mingo!"], + + ["Ulices", + "¡Ulices Todepapel!"], + + ["Virgilio", + "¡Virgilío Mehasmetido!"], + + ["Vladimir", + "¡Vladimir Ador!"], + + ["Wenceslao", + "¡Wenceslao de Vainilla!"], + + ["Wenceslao", + "¡Wenceslao de Chocolate!"], + + ["Amira", + "¡Amira Quiensoy!"], + + ["Saúl", + "¡Saúl Timavezquetellamo!"], + + ["Noé", + "¡Noé Nadieconocido!"], + + ["Noé", + "¿Noé Verdad?"], + + ["Nadia", + "¡Nadia Importante!"], + + ["Otto", + "¡Otto Quenomeconoce!"], + + ["Abel", + "¡Abel Quienés!"], + + ["Aitana", + "¡Aitana Sustao!"], + + ["Apolo", + "¡Apolo Menosavisa!"], + + ["Romualdo", + "¿Romualdo Lordecabeza?"], + + + ["Dolores", + "¿Dolores Fuertes?"], + + + ["Eleazar", + "¡Eleazar Aleatóriez!"], + + ["Elijah", + "¡Elijah Elquemasleguste!"], + + ["Elmer", + "¡Elmer Cader de Venecia!"], + + ["Elpida", + "¡Elpida Loqueleapetezca!"], + + ["Quique", + "¡Quique Cosasdice!"], + + ["Euridice", + "¡Euridice Quelellames!"], + + ["Calvino", + "¡Calvino Tinto de Verano!"], + + ["Mercedes", + "¡Mercedes Elpaso!"], + + ["Antón", + "¡Antón Tería!"], + + ["Merlín", + "¡Merlin Gote!"], + + ["Minerva", + "¡Minerva Minfada y Mixaspera!"], + + ["Miranda", + "¡Miranda Paraotrolado!"], + + ["Morgana", + "¡Morgana Unoacero!"], + + ["Morfeo", + "¡Morfeo Quelainjusticia!"], + + ["Pancho", + "¡Pancho Colate!"], + + ["Parker", + "¡Parker Levoyacontar!"], + + ["Pasha", + "¿Pasha Contigo?"], + + ["Patty", + "Patty Todo Noquieronada."], + + ["Stan", + "¡Stan Todosdetenidos!"], + + ["Pierre", + "¡Pierre Naspacorrer!"], + + ["Ida", + "¡Ida Yvuelta!"], + + ["Savannah", + "¡Savannah de Lino Blanco!"], + + ["Renata", + "¡Renata Conchocolate!"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "¡Hola, %!", + "Eh, %, me alegro de verte.", + "¡Me alegro de que vinieras hoy!", + "¿Cómo estás, %?", + ] + +SharedChatterComments = [ + "Es un nombre estupendo, %.", + "Me gusta tu nombre.", + "Cuidado con los bots.", + "¡Parece que llega el tranvía!", + "¡Tengo que subir al tranvía para jugar y conseguir tartas!", + "A veces me subo al tranvía sólo para conseguir la tarta de frutas.", + "Guau, acabo de deshacerme de un montón de bots. ¡Necesito descansar un poco!", + "¡Vaya, hay algunos bots que son muy grandotes!", + "Parece que la pasas muy bien.", + "Caramba, qué buen día estoy teniendo.", + "Me gusta lo que llevas puesto.", + "Creo que esta tarde me voy a ir de pesca.", + "Diviértete en mi barrio.", + "¡Espero que lo estés pasando en grande en Toontown!", + "Me dijeron que en "+lTheBrrrgh+" está nevando.", + "¿Subiste hoy al tranvía?", + "Me gustaría conocer a más gente.", + "Caramba, en "+lTheBrrrgh+" hay un montón de bots.", + "Me encanta jugar a \"Las traes\". ¿Y a ti?", + "Los juegos del tranvía son divertidísimos.", + "Me encanta hacer que la gente se ría.", + "Ayudar a los amigos es muy divertido.", + "Ejem, ¿te perdiste? No olvides que tienes un mapa en el dibucuaderno.", + "¡Que no te enrieden los bots con su cinta roja!", + "Me dijeron que Daisy plantó flores nuevas en su jardín.", + "¡Para mirar hacia arriba, mantén pulsada la tecla Re Pág!", + "¡Si ayudas a reconquistar los edificios bot, podrás ganar una estrella de bronce!", + "Si pulsas la tecla Tab, podrás contemplar diferentes vistas de los alrededores.", + "¡Si pulsas la tecla Ctrl, podrás saltar!", + ] + +SharedChatterGoodbyes = [ + "Me tengo que ir; adiós.", + "Creo que voy a subir al tranvía para jugar.", + "Bueno, hasta otra. Te veo luego, %.", + "Más me vale darme prisa para acabar con los bots.", + "Es hora de ponerse en marcha.", + "Lo siento, pero tengo que irme.", + "Adiós.", + "¡Hasta luego, %!", + "Creo que voy a practicar el lanzamiento de magdalenas.", + "Voy a unirme a un grupo para acabar con unos cuantos bots.", + "Me alegro de haberte visto hoy, %.", + "Hoy tengo mucho que hacer. Voy a ponerme en marcha.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bienvenido al "+lToontownCentral+".", + "Hola, me llamo "+Mickey+". ¿Cómo te llamas?", + ], + [ # Comments + "Eh, ¿viste a "+Donald+"?", + "Voy a ver cómo sube la marea en "+lDonaldsDock+".", + "Si ves a mi amiguito "+Goofy+", dale recuerdos de mi parte.", + "Me dijeron que "+Daisy+" plantó flores nuevas en su jardín.", + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a "+Minnie+"!", + "¡Dios mío, llego tarde a mi cita con "+Minnie+"!", + "Parece que es hora de darle de comer a "+Pluto+".", + "Creo que voy a "+lDonaldsDock+" a nadar un poco.", + "Es la hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +VampireMickeyChatter = ( + [ # Greetings specific to Vampire Mickey + "Bienvenido al "+lToontownCentral+".", + "Hola, me llamo "+Mickey+". ¿Cómo te llamas?", + "¡Feliz día de Halloween!", + "¡Feliz día de Halloween, %!", + "Bienvenido al Centro del Miedo... Digo... ¡al "+lToontownCentral+"!", + ], + [ # Comments + "¡Qué divertido es disfrazarse para Halloween!", + "¿Te gusta mi traje?", + "¡%, cuidado con los bots chupasangres!", + "¿No te encantan los adornos de Halloween?", + "¡Cuidado con los gatos negros!", + "¿Viste al dibu con la cabeza de calabaza?", + "¡Buuu! ¿Te asusté?", + "¡No olvides cepillarte los colmillos!", + "¡No te asustes, es un vampiro amigo!", + "¿Te gusta mi capa?", + "¿Te asusté? ¡Es la mejor broma que tengo!", + "¡Espero que estés disfrutando nuestro Halloween!", + "¡Qué espeluznante, está tan oscuro como la noche!", + ], + [ # Goodbyes + "Voy a ver esos adornos tan lindos de Halloween.", + "¡Voy a Melodilandia a sorprender a "+Minnie+"!", + "¡Voy a darle un susto a otro dibu! ¡Shhh!", + "¡Voy a hacer truco o trato!", + "Chsss, ven a asustar gente conmigo.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bienvenido a Melodilandia.", + "Hola, me llamo "+Minnie+". ¿Cómo te llamas?", + ], + [ # Comments + "¡La música se siente por todas partes!", + "No te olvides de montarte en el gran tiovivo.", + "Llevas un disfraz muy lindo, %.", + "Eh, ¿viste a "+Mickey+"?", + "Si ves a mi amigo "+Goofy+", dale recuerdos de mi parte.", + "Caramba, en Sueñolandia de "+Donald+" hay un montón de "+Cogs+".", + "Me dijeron que en "+lDonaldsDock+" hay niebla.", + "No te olvides de probar el laberinto de los "+lDaisyGardens+".", + "Creo que voy a escuchar música.", + "Eh, %, mira eso.", + "Me encanta la música.", + "¿A que no sabías que a Melodilandia también la llaman Cancioncity? ¡Ji, ji!", + "Me encanta jugar al juego de imitar movimientos. ¿Y a ti?", + "Me encanta hacer reír a la gente.", + "¡Uf, andar todo el día con tacones acaba haciendo daño a los pies!", + "Qué camisa más linda, %.", +# "¿No te encanta el juego de imitar movimientos?", # minnie prefers the Match game of course! + "¿Eso del suelo es una golosina?", + ], + [ # Goodbyes + "¡Vaya, llego tarde a mi cita con %s!" % Mickey, + "Creo que es hora de darle de comer a %s." % Pluto, + "Es la hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +DaisyChatter = ( + [ # Greetings + "¡Bienvenido a mi jardín!", + "Hola, soy "+Daisy+". ¿Cómo te llamas?", + "¡Me alegro de verte, %!", + ], + [ # Comments + "Mi flor premiada está en el centro del laberinto del jardín.", + "Me encanta pasear por el laberinto.", + "No vi a "+Goofy+" en todo el día.", + "Me pregunto dónde estará "+Goofy+".", + "¿Viste a "+Donald+"? No lo encuentro.", + "Si ves a mi amiga "+Minnie+", salúdala de mi parte.", + "Cuanto mejor sean tus herramientas de jardinería, mejores serán tus plantas.", + "Hay demasiados "+Cogs+" cerca de "+lDonaldsDock+".", + "Si riegas tu jardín todos los días, las plantas estarán felices.", + "Para cultivar una margarita rosa, planta juntas una golosina roja y otra amarilla.", + "Las margaritas amarillas son fáciles de cultivar, solo tienes que plantar una golosina amarilla.", + "¡Si hay arena debajo de una planta es porque tienes que regarla para que no se marchite!" + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a %s!" % Minnie, + "¡Llego tarde a mi picnic con %s!" % Donald, + "Creo que iré a nadar a "+lDonaldsDock+".", + "Oh, tengo sueño. Me voy a Sueñolandia.", + ] + ) + +ChipChatter = ( + [ # Greetings + "¡Bienvenido a %s!" % lOutdoorZone, + "Hola, me llamo" + Chip + ". ¿Cómo te llamas?", + "No, yo soy" + Chip + ".", + "¡Me alegro de verte, %!", + "¡Somos Chip y Dale!", + ], + [ # Comments + "Me gusta el golf.", + "Tenemos las mejores bellotas de todo Toontown.", + "Los hoyos de golf con volcanes son todo un reto.", + ], + [ # Goodbyes + "Nos vamos a " + lTheBrrrgh +" a jugar con %s." % Pluto, + "Vamos a visitar a %s y a arreglarle las cosas." % Donald, + "Creo que me voy a ir a nadar a " + lDonaldsDock + ".", + "Oh, tengo sueño. Me voy a Sueñolandia.", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "¡Qué alegría verte, %!", + "hola, me llamo " + Dale + ". ¿Cómo te llamas?", + "Hola, soy " + Chip + ".", + "¡Bienvenido a los %s!" % lOutdoorZone, + "¡Somos Chip y Dale!", + ], + [ # Comments + "Me gustan los picnics.", + "Las bellotas están muy ricas, pruébalas.", + "Esos molinos de viento también pueden resultar difíciles.", + ], + [ # Goodbyes + "Jijiji " + Pluto + " es un compañero de juegos divertido.", + "Sí, vamos a arreglar a %s." % Donald, + "Nadar, qué agradable.", + "Necesito descansar, quizás me eche una siesta.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bienvenido a "+lDaisyGardens+".", + "Hola, me llamo "+Goofy+". ¿Cómo te llamas?", + "¡Me alegro de verte %!", + ], + [ # Comments + "¡Qué fácil es perderse en el laberinto del jardín!", + "Tienes que probar este laberinto.", + "Hace mucho rato que no veo a "+Daisy+".", + "Me pregunto dónde estará "+Daisy+".", + "Eh, ¿viste a "+Donald+"?", + "Si ves a mi amigo "+Mickey+", salúdale de mi parte.", + "¡Se me olvidó prepararle el desayuno a "+Mickey+"!", + "Hay un montón de "+Cogs+" cerca de "+lDonaldsDock+".", + "Parece que "+Daisy+" plantó flores nuevas en su jardín.", + "¡En la sección Frescolandia de mi tienda de bromas, las gafas hipnóticas cuestan solo 1 golosina!", + "¡Las tiendas de bromas de Goofy tienen los mejores chistes, trucos y gracias de todo Toontown!", + "¡En las tiendas de bromas de Goofy, te garantizamos una buena dosis de risa por cada tarta en la cara o te devolvemos tus golosinas!" + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a %s!" % Mickey, + "¡Llego tarde a jugar con %s!" % Donald, + "Creo que me iré a nadar a "+lDonaldsDock+".", + "Es hora de la siesta. Me voy a Sueñolandia.", + ] + ) + + + +GoofySpeedwayChatter = ( + [ # Greetings + "Bienvenido a "+lGoofySpeedway+".", + "Hola, me llamo "+Goofy+". ¿Cómo te llamas?", + "¡Me alegro de verte %!", + ], + [ # Comments + "Chico, hoy vi una carrera fantástica.", + "¡Cuidado con las cáscaras de banana en el circuito!", + "¿Actualizaste tu kart recientemente?", + "Acabamos de comprar nuevas llantas en la tienda de karts.", + "¿Eh, viste a "+Donald+"?", + "Si ves a mi amigo "+Mickey+", salúdale de mi parte.", + "¡Olvidé preparar el desayuno de "+Mickey+"!", + "¡Hay un montón de "+Cogs+" cerca del "+lDonaldsDock+".", + "¡En la sección Frescolandia de mi tienda de bromas, las gafas hipnóticas cuestan solo 1 golosina!", + "¡Las tiendas de bromas de Goofy tienen los mejores chistes, trucos y gracias de todo Toontown!", + "¡En las tiendas de bromas de Goofy, te garantizamos una buena dosis de risa por cada tarta en la cara o te devolvemos tus golosinas!" + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a %s!" % Mickey, + "¡Llego tarde a jugar con %s!" % Donald, + "Creo que me iré a nadar a "+lDonaldsDock+".", + "Es hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bienvenido a Sueñolandia.", + "Hola, me llamo %s. ¿Y tú?" % Donald, + ], + [ # Comments + "A veces, este sitio me da escalofríos.", + "No te olvides de probar el laberinto de los "+lDaisyGardens+".", + "Caramba, qué buen día estoy teniendo.", + "Eh, ¿viste a "+Mickey+"?", + "Si ves a mi buen amigo "+Goofy+", dale recuerdos de mi parte.", + "Creo que esta tarde me voy a ir de pesca.", + "Caramba, en "+lDonaldsDock+" hay un montón de "+Cogs+".", + "Eh, ¿no te llevé en barco en "+lDonaldsDock+"?", + "No vi a "+Daisy+" en todo el día.", + "Me dijeron que "+Daisy+" plantó flores nuevas en su jardín.", + "Cuac.", + ], + [ # Goodbyes + "¡Me voy a Melodilandia a ver a %s!" % Minnie, + "¡Vaya! ¡Llego tarde a mi cita con %s!" % Daisy, + "Creo que voy a mi puerto a nadar un poco.", + "Creo que voy a darme una vuelta en mi barco en "+lDonaldsDock+".", + ] + ) + +# April Fools Chatter's +AFMickeyChatter = ( + [ # Greetings specific to Mickey + "¡Feliz Semana de los Dibus Inocentes!", + "¡Feliz Semana de los Dibus Inocentes, %!", + "Hola, me llamo "+Mickey+". ¿y tú?", + ], + [ # Comments + "¿Viste a Daisy?", + "¡Quiero saludar a Daisy por la Semana de los Dibus Inocentes!", + "¿Escuchaste hablar al dibuperrito?", + "¡Qué lindas son estas flores!", + "¡Daisy debe saberse unos buenos trucos de jardinería!", + ], + [ # Goodbyes + "Hola, estoy buscando a Daisy. ¿La viste?", + "Es hora de la siesta. Me voy a Sueñolandia.", + ] + ) + +AFMinnieChatter = ( + [ # Greetings + "Hola, me llamo "+Minnie+". ¿Y tú?", + "¡Feliz Semana de los Dibus Inocentes!", + "¡Feliz Semana de los Dibus Inocentes, %!", + ], + [ # Comments + "Hola. Tengo que darle la comida a Pluto. ¿Lo viste?", + "¡Quiero saludar a Pluto por la Semana delos Dibus Inocentes con un obsequio especial!", + "¿Escuchaste hablar a un dibuperrito?", + ], + [ # Goodbyes + "Hola. Tengo que darle la comida a Pluto. ¿Lo viste?", + "¡Llego tarde a mi cita con %s!" % Mickey, + ] + ) + +AFDaisyChatter = ( + [ # Greetings + "Hola, soy "+Daisy+". ¿Y tú?", + "¡Feliz Semana de los Dibus Inocentes!", + "¡Feliz Semana de los Dibus Inocentes, %!", + ], + [ # Comments + "Me pregunto si Mickey se habrá ido a luchar contra los bots.", + "¿Viste a Mickey?", + "Quiero saludar a Mickey por la Semana de los Dibus Inocentes!", + "¿Escuchaste a un dibuperrito hablar o me pareció a mí?", + ], + [ # Goodbyes + "Hola, necesito hablar con Mickey. ¿Lo viste?", + "Creo que me iré a nadar a "+lDonaldsDock+".", + "Oh, tengo un poco de sueño. Creo que me iré a Sueñolandia.", + ] + ) + +AFGoofySpeedwayChatter = ( + [ # Greetings + "¡Feliz Sueño... esto... Semana de los Dibus Inocentes!", + "¡Feliz Semana de los Dibus Inocentes, %!", + "Hola, me llamo "+Goofy+". ¿Y tú?", + ], + [ # Comments + "¿Viste a Donald? Creo que volvió a andar sonámbulo.", + "¡Quiero saludar a Donald por la Semana de los Dibus Inocentes!", + "¿Escuchaste a un dibuperrito hablar, o es que veo cosas?", + "Espero que todo vaya bien en el Estadio.", + ], + [ # Goodbyes + "¡Llego tarde a la partida con %s!" % Donald, + ] + ) + +AFDonaldChatter = ( + [ # Greetings + "¡Feliz Sueño... esto... Semana de los Inocentes dibu!", + "¡Feliz Semana de los Inocentes dibu, %!", + "Hola, me llamo %s. ¿Y tú?" % Donald, + ], + [ # Comments + "¿Viste a Goofy?", + "¡Quiero saludar a Goofy por la Semana de los Dibus Inocentes!", + "¿Escuchaste a un dibuperrito hablar, o es que estoy soñando?", + "¿De dónde salió ese kart?", + ], + [ # Goodbyes + "¿De dónde vienen de repente todos esos ruidos de autos?", + "¡Me voy a Melodilandia a ver a %s!" % Minnie, + ] + ) + +CLGoofySpeedwayChatter = ( + [ # Greetings + "Bienvenido a "+lGoofySpeedway+".", + "Hola, me llamo "+Goofy+". ¿Y tú?", + "¡Me alegro de verte %!", + "¡Hola! Perdona que lleve tanto polvo, estuve arreglando el marcador roto.", + ], + [ # Comments + "Será mejor que arreglemos pronto el marcador, ¡se acerca el fin de semana Grand Prix!", + "¿Alguien quiere comprar un kart un poco usado? ¡Sólo pasó una vez por el marcador!", + "Se acerca el fin de semana Grand Prix, conviene practicar.", + "¡El fin de semana Grand Prix empieza el viernes 22 de mayo y termina el lunes 25 de mayo!", + "Voy a necesitar una escalera para bajar ese kart.", + "¡Ese dibu estaba desesperado por llegar al marcador!", + "Chico, hoy vi una carrera fabulosa.", + "¡Cuidado con las cáscaras de banana en el circuito!", + "¿Actualizaste recientemente tu kart?", + "Acabamos de comprar nuevas llantas en la tienda de karts.", + "Eh, ¿viste a "+Donald+"?", + "Si ves a mi amigo "+Mickey+", salúdale de mi parte.", + "¡Uau! ¡Olvidé prepararle el desayuno a "+Mickey+"!", + "Hay un montón de "+Cogs+" cerca del "+lDonaldsDock+".", + "¡En la sección Frescolandia de mi tienda de bromas, las gafas hipnóticas cuestan solo 1 golosina!", + "¡Las tiendas de bromas de Goofy tienen los mejores chistes, trucos y gracias de todo Toontown!", + "¡En las tiendas de bromas de Goofy, te garantizamos una buena dosis de risa por cada tarta en la cara o te devolvemos tus golosinas!" + ], + [ # Goodbyes + "Conviene que pinte mi kart para el próximo fin de semana Grand Prix.", + "¡Será mejor que me ponga a trabajar con este marcador roto!", + "¡Espero verte el fin de semana Grand Prix! ¡Adiós!", + "Es hora de la siesta. Me voy a Sueñolandia a soñar con el Grand Prix.", + ] + ) + + +GPGoofySpeedwayChatter = ( + [ # Greetings + "Bienvenido al "+lGoofySpeedway+".", + "¡Bienvenido al fin de semana Grand Prix!", + "Hola, me llamo "+Goofy+". ¿Y tú?", + "¡Me alegro de verte, %!", + ], + [ # Comments + "¿Estás nervioso por el fin de semana Grand Prix?", + "Menos mal que arreglaste el marcador.", + "¡Arreglamos el marcador justo a tiempo para el fin de semana Grand Prix!", + "¡Nunca encontramos a ese dibu!", + "Chico, hoy vi una carrera fantástica.", + "¡Cuidado con las cáscaras de banana del circuito!", + "¿Actualizaste tu kart recientemente?", + "Acabamos de comprar nuevas llantas en la tienda de karts.", + "Eh, ¿viste a "+Donald+"? ¡Dijo que vendría a ver el Grand Prix!", + "Si ves a mi amigo "+Mickey+", ¡dile que se está perdiendo unas carreras estupendas!", + "¡Olvidé prepararle el dessayuno a "+Mickey+"!", + "¡Hay un montón de "+Cogs+" cerca del "+lDonaldsDock+".", + "¡En la sección Frescolandia de mi tienda de bromas, las gafas hipnóticas cuestan solo 1 golosina!", + "¡Las tiendas de bromas de Goofy tienen los mejores chistes, trucos y gracias de todo Toontown!", + "¡En las tiendas de bromas de Goofy, te garantizamos una buena dosis de risa por cada tarta en la cara o te devolvemos tus golosinas!" + ], + [ # Goodbyes + "¡Suerte en el Grand Prix!", + "¡Voy a ver la siguiente carrera del Grand Prix!", + "¡Creo que la próxima carrera está a punto de comenzar!", + "¡Será mejor que vaya a ver si el nuevo marcador funciona bien!", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Amigo nuevo" +FriendsListPanelSecrets = "Secretos" +FriendsListPanelOnlineFriends = "AMIGOS\nCONECTADOS" +FriendsListPanelAllFriends = "TODOS LOS\nAMIGOS" +FriendsListPanelIgnoredFriends = "DIBUS\nIGNORADOS" +FriendsListPanelPets = "MASCOTAS\nCERCANAS" +FriendsListPanelPlayers = "TODOS LOS JUGADORES\nAMIGOS" +FriendsListPanelOnlinePlayers = "AMIGOS JUGADORES\nONLINE" + +FriendInviterClickToon = "Haz clic en el dibu del que quieres hacerte amigo.\n\n(Tienes %s amigos)" + +# Support DISL account friends +FriendInviterToon = "Dibu" +FriendInviterThatToon = "Ese dibu" +FriendInviterPlayer = "Jugador" +FriendInviterThatPlayer = "Ese jugador" +FriendInviterBegin = "¿Qué tipo de amigo quieres tener?" +FriendInviterToonFriendInfo = "Un amigo sólo de Toontown" +FriendInviterPlayerFriendInfo = "Un amigo en la red Disney.com" +FriendInviterToonTooMany = "Tienes demasiados amigos dibu para añadir otro. Para hacerte amigo de %s, tendrás que eliminar a algún amigo dibu. También puedes intentar hacerte amigo jugador." +FriendInviterPlayerTooMany = "Tienes demasiados amigos jugador para añadir otro. Para hacerte amigo de %s, tendrás que eliminar a algún amigo jugador. También puedes intentar hacerte amigo dibu." +FriendInviterToonAlready = "%s ya es tu amigo dibu." +FriendInviterPlayerAlready = "%s ya es tu amigo jugador." +FriendInviterStopBeingToonFriends = "Dejar de ser amigos dibus" +FriendInviterStopBeingPlayerFriends = "Dejar de ser amigos jugadores" +FriendInviterEndFriendshipToon = "¿Seguro que quieres dejar de ser amigo dibu de %s?" +FriendInviterEndFriendshipPlayer = "Seguro que quieres dejar de ser amigo jugador de %s?" +FriendInviterRemainToon = "\n(Seguirás siendo amigo dibu de %s)" +FriendInviterRemainPlayer = "\n(Seguirás siendo amigo jugador de %s)" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Lo siento, no puedes avanzar porque de la descarga de %(phase)s sólo se completó un %(percent)s%%.\n\nVuelve a intentarlo más tarde." + +# TeaserPanel.py +TeaserTop = "" +TeaserBottom = "" +TeaserDefault = ",\nyou need to become a Member.\n\nJoin us!" +TeaserOtherHoods = "Visita los 6 extraordinarios barrios." +TeaserTypeAName = "Ponle a tu dibu el nombre que más te guste." +TeaserSixToons = "Crea hasta 6 dibus distintos en una sola cuenta." +TeaserClothing = "Compra ropa exclusiva para darle un toque único a tu dibu." +TeaserCogHQ = "Infíltrate en zonas bot superpeligrosas." +TeaserSecretChat = "Intercambia secretos con amigos\ncharlando con ellos en línea." +TeaserSpecies = "Para elegir este tipo de dibu" +TeaserFishing = "Para pescar en los 6 barrios" +TeaserGolf = "Para jugar al minigolf dibu" +TeaserParties = "Para organizar una fiesta" +TeaserSubscribe = "Suscríbete" +TeaserContinue = "Continuar" +TeaserEmotions = "Compra emociones para dotar a tu dibu de más expresividad." +TeaserKarting = "Para acceder a carreras de kart ilimitadas" +TeaserKartingAccessories = "Para personalizar tu kart" +TeaserGardening = "Para seguir practicando jardinería en tu casa dibu" +TeaserHaveFun = "Have more fun!" +TeaserJoinUs = "Join us!" + +#TeaserCardsAndPosters = "" +#TeaserFurniture = "" +TeaserMinigames = TeaserOtherHoods +#TeaserHolidays = "" +TeaserQuests = TeaserOtherHoods +TeaserOtherGags = TeaserOtherHoods +#TeaserRental = "" +#TeaserBigger = "" +TeaserTricks = TeaserOtherHoods + +# Launcher.py +LauncherPhaseNames = { + 0 : "Iniciando", + 1 : "Panda", + 2 : "Motor", + 3 : "Crear un dibu", + 3.5 : "Dibututorial", + 4 : "Dibuparque", + 5 : "Calles", + 5.5 : "Propiedades", + 6 : "Barrio I", + 7 : "Edificios " + Cog, + 8 : "Barrio II", + 9 : lToonHQ + " " + Cog, + 10 : Cashbot + " Cuartel general", + 11 : Lawbot + " Cuartel general", + 12 : Bossbot + " Cuartel general", + 13 : "Fiestas", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s de %(total)s)" +LauncherStartingMessage = "Iniciando Disney's Toontown Online... " +LauncherDownloadFile = "Descargando actualización para " + LauncherProgress + "..." +LauncherDownloadFileBytes = "Descargando actualización para " + LauncherProgress + ": %(bytes)s" +LauncherDownloadFilePercent = "Descargando actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherDecompressingFile = "Descomprimiendo actualización para " + LauncherProgress + "..." +LauncherDecompressingPercent = "Descomprimiendo actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherExtractingFile = "Extrayendo actualización para " + LauncherProgress + "..." +LauncherExtractingPercent = "Extrayendo actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherPatchingFile = "Aplicando actualización para " + LauncherProgress + "..." +LauncherPatchingPercent = "Aplicando actualización para " + LauncherProgress + ": %(percent)s%%" +LauncherConnectProxyAttempt = "Conectando con Toontown: %s (proxy: %s) intento: %s" +LauncherConnectAttempt = "Conectando con Toontown: %s intento %s" +LauncherDownloadServerFileList = "Actualizando Toontown..." +LauncherCreatingDownloadDb = "Actualizando Toontown..." +LauncherDownloadClientFileList = "Actualizando Toontown..." +LauncherFinishedDownloadDb = "Actualizando Toontown... " +LauncherStartingGame = "Iniciando Toontown..." +LauncherRecoverFiles = "Actualizando Toontown... Recuperando archivos..." +LauncherCheckUpdates = "Comprobando actualizaciones para " + LauncherProgress + "..." +LauncherVerifyPhase = "Actualizando Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Crea un\ndibu" +AvatarChoicePlayThisToon = "Juega con\neste dibu" +AvatarChoiceSubscribersOnly = "¡Suscríbete\n\n\n\nya mismo!" +AvatarChoiceDelete = "Borrar" +AvatarChoiceDeleteConfirm = "Con esto borrarás a %s para siempre." +AvatarChoiceNameRejected = "Nombre\nrechazado" +AvatarChoiceNameApproved = "¡Nombre\naprobado!" +AvatarChoiceNameReview = "En proceso\nde revisión" +AvatarChoiceNameYourToon = "¡Pon un nombre\na tu dibu!" +AvatarChoiceDeletePasswordText = "¡Cuidado! Con esto borrarás a %s para siempre. Para borrar este dibu, escribe tu contraseña." +AvatarChoiceDeleteConfirmText = "¡Cuidado! Con esto borrarás a %(name)s para siempre. Para confirmar esto, escribe \"%(confirm)s\" y haz clic en OK." +AvatarChoiceDeleteConfirmUserTypes = "Borrar" +AvatarChoiceDeletePasswordTitle = "¿Quieres borrar este dibu?" +AvatarChoicePassword = "Contraseña" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "La contraseña no coincide. Para borrar este dibu, escribe tu contraseña." +AvatarChoiceDeleteWrongConfirm = "No escribiste la palabra correcta. Para borrar a %(name)s, escribe \"%(confirm)s\" y haz clic en OK. No escribas los apóstrofes. Haz clic en Cancelar si cambiaste de parecer." + +# AvatarChooser.py +AvatarChooserPickAToon = "Elige el dibu con el que vas a jugar" +AvatarChooserQuit = lQuit + + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Para ponerte en contacto con el Servicio de atención al cliente, llama al %s." +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\n\nSi necesitas ayuda, ponte en contacto con el Servicio de atención al cliente, en el número %s." +TTAccountIntractibleError = "Se produjo un error." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', + 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic',] +DateOfBirthEntryDefaultLabel = "Fecha de nacimiento" + + +# AchievePage.py +AchievePageTitle = "Logros\n(próximamente)" + +# PhotoPage.py +PhotoPageTitle = "Foto\n(próximamente)" + +# BuildingPage.py +BuildingPageTitle = "Edificios\n(próximamente)" + +# InventoryPage.py +InventoryPageTitle = "Bromas" +InventoryPageDeleteTitle = "BORRAR BROMAS" +InventoryPageTrackFull = "Tienes todas las bromas del circuito %s. " +InventoryPagePluralPoints = "Conseguirás una \nbroma nueva de %(trackName)s cuando\nconsigas %(numPoints)s puntos más de %(trackName)s." +InventoryPageSinglePoint = "Conseguirás una \nbroma nueva de %(trackName)s cuando\nconsigas %(numPoints)s punto más de %(trackName)s." +InventoryPageNoAccess = "Todavía no tienes acceso al circuito %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS dibus" + +# EventsPage.py +PartyDateFormat = "%(mm)s %(dd)d, %(yyyy).4d" # Dec 8, 2008 +PartyTimeFormat = "%d:%.2d %s" # 1:45 pm +PartyTimeFormatMeridiemAM = "am" +PartyTimeFormatMeridiemPM = "pm" +PartyCanStart = "¡Llegó la fiesta, haz clic en Comenzar fiesta en tu dibucuaderno!" +PartyHasStartedAcceptedInvite = '¡La fiesta de %s ya comenzó! Haz clic en el anfitrión y después en "Ir a la fiesta" en la página de invitaciones del dibucuaderno.' +PartyHasStartedNotAcceptedInvite = '¡La fiesta de %s ya comenzó! Todavía estás a tiempo de llegar si te teletransportas hasta el anfitrión' + +EventsPageName = "Eventos" +EventsPageCalendarTabName = "Calendario" +EventsPageCalendarTabParty = "Fiesta" +EventsPageToontownTimeIs = "LA HORA TOONTOWN ES" +EventsPageConfirmCancel = "Si cancelas, recibirás un reembolso de %d%%. ¿Seguro que quieres cancelar la fiesta?" +EventsPageCancelPartyResultOk = "¡Tu fiesta fue cancelada y recibiste un reembolso de %d golosinas!" +EventsPageCancelPartyResultError = "Lo siento, tu fiesta no fue cancelada." +EventsPageTooLateToStart = "Lo siento, es demasiado tarde para empezar tu fiesta. Puedes cancelarla y organizar otra." +EventsPagePublicPrivateChange = "Modificando configuración de privacidad de tu fiesta..." +EventsPagePublicPrivateNoGo = "Lo siento, en este momento no puedes cambiar la configuración de privacidad de tu fiesta." +EventsPagePublicPrivateAlreadyStarted = "Lo siento, tu fiesta ya comenzó, por lo que no puedes cambiar la configuración de privacidad de la misma." +EventsPageHostTabName = "Anfitrión" # displayed on the physical tab +EventsPageHostTabTitle = "Mi próxima fiesta" # banner text displayed across the top +EventsPageHostTabTitleNoParties = "Ninguna fiesta" +EventsPageHostTabDateTimeLabel = "Vas a celebrar una fiesta en %s a la hora Toontown %s." +EventsPageHostingTabNoParty = "¡Ve al portón de fiestas\ndel dibuparque para organizar\ntu propia fiesta!" +EventsPageHostTabPublicPrivateLabel = "Esta fiesta es:" +EventsPageHostTabToggleToPrivate = "Privada" +EventsPageHostTabToggleToPublic = "Pública" +EventsPageHostingTabGuestListTitle = "Invitados" +EventsPageHostingTabActivityListTitle = "Actividades" +EventsPageHostingTabDecorationsListTitle = "Adornos" +EventsPageHostingTabPartiesListTitle = "Anfitriones" +EventsPageHostTabCancelButton = "Cancelar fiesta" +EventsPageGoButton = "¡Comenzar\nfiesta!" +EventsPageGoBackButton = "¡Fiesta\nya!" +EventsPageInviteGoButton = "¡Ir a la\nfiesta!" +EventsPageUnknownToon = "Dibu desconocido" + +EventsPageInvitedTabName = "Invitaciones" +EventsPageInvitedTabTitle = "Invitaciones a fiestas" +EventsPageInvitedTabInvitationListTitle = "Invitaciones" +EventsPageInvitedTabActivityListTitle = "Actividades" +EventsPageInvitedTabTime = "%s %s Hora Toontown" + +EventsPageNewsTabName = "Noticias" +EventsPageNewsTabTitle = "Noticias" +EventsPageNewsDownloading= "Retrieving News..." +EventsPageNewsUnavailable = "Chip and Dale played with the printing press. News not available." +EventsPageNewsPaperTitle = "TOONTOWN TIMES" +EventsPageNewsLeftSubtitle = "Still only 1 jellybean" +EventsPageNewsRightSubtitle = "Established toon-thousand nine" + +# InvitationSelection.py +SelectedInvitationInformation = "%s va a celebrar una fiesta en %s a las %s hora Toontown." + +# PartyPlanner.py +PartyPlannerNextButton = "Continuar" +PartyPlannerPreviousButton = "Volver" +PartyPlannerWelcomeTitle = "Organizador de fiestas Toontown" +PartyPlannerInstructions = "¡Celebrar tu propia fiesta es muy divertido!\n¡Empieza a organizarla con las flechas de abajo!" +PartyPlannerDateTitle = "Elige un día para tu fiesta" +PartyPlannerTimeTitle = "Elige una hora para tu fiesta" +PartyPlannerGuestTitle = "Elige a tus invitados" +PartyPlannerEditorTitle = "Diseña tu fiesta:\nlugar, actividades y adornos" +PartyPlannerConfirmTitle = "Elige las invitaciones que vas a enviar" +PartyPlannerConfirmTitleNoFriends = "Comprueba la organización de tu fiesta" +PartyPlannerTimeToontown = "Toontown" +PartyPlannerTimeTime = "Hora" +PartyPlannerTimeRecap = "Fecha y hora de la fiesta" +PartyPlannerPartyNow = "Lo antes posible" +PartyPlannerTimeToontownTime = "hora Toontown:" +PartyPlannerTimeLocalTime = "Hora local de la fiesta: " +PartyPlannerPublicPrivateLabel = "Esta fiesta será:" +PartyPlannerPublicDescription = "¡Todos los dibus\nson bienvenidos!" +PartyPlannerPrivateDescription = "¡Solo pueden\nvenir dibus\ninvitados!" +PartyPlannerPublic = "Pública" +PartyPlannerPrivate = "Privada" +PartyPlannerCheckAll = "Marcar\ntodos" +PartyPlannerUncheckAll = "Desmarcar\ntodos" +PartyPlannerDateText = "Fecha" +PartyPlannerTimeText = "Hora" +PartyPlannerTTTimeText = "Hora Toontown" +PartyPlannerEditorInstructionsIdle = "Haz clic en la actividad o adorno de fiesta que quieres comprar." +PartyPlannerEditorInstructionsClickedElementActivity = "Haz clic en comprar o arrastra el icono de la actividad a el plano" +PartyPlannerEditorInstructionsClickedElementDecoration = "Haz clic en comprar o arrastra el adorno hasta el plano del lugar de la fiesta" +PartyPlannerEditorInstructionsDraggingActivity = "Arrastra el adorno hasta el plano del lugar de la fiesta" +PartyPlannerEditorInstructionsDraggingDecoration = "Arrastra la actividad hasta el plano del lugar de la fiesta." +PartyPlannerEditorInstructionsPartyGrounds = "Haz clic sobre los objetos y arrástralos para moverlos por el lugar de la fiesta" +PartyPlannerEditorInstructionsTrash = "Arrastra una actividad o un adorno hasta aquí para eliminarlo." +PartyPlannerEditorInstructionsNoRoom = "No hay espacio para colocar esa actividad." +PartyPlannerEditorInstructionsRemoved = "%(removed)s eliminado desde que se añadió %(added)s." +PartyPlannerBeans = "golosinas" +PartyPlannerTotalCost = "Coste total:\n%d golosinas" +PartyPlannerSoldOut = "AGOTADO" +PartyPlannerBuy = "COMPRAR" +PartyPlannerPaidOnly = "SOLO MIEMBROS" +PartyPlannerPartyGrounds = "PLANO DEL LUGAR DE LA FIESTA" +PartyPlannerOkWithGroundsLayout = "¿Terminaste de mover las actividades y los adornos de tu fiesta por el plano del lugar de la fiesta?" +PartyPlannerChooseFutureTime = "Elige una hora futura." +PartyPlannerInviteButton = "Enviar invitaciones" +PartyPlannerInviteButtonNoFriends = "Organizar fiesta" +PartyPlannerBirthdayTheme = "Cumpleaños" +PartyPlannerGenericMaleTheme = "Estrella" +PartyPlannerGenericFemaleTheme = "Flor" +PartyPlannerRacingTheme = "Carrera" +PartyPlannerGuestName = "Nombre del invitado" +PartyPlannerClosePlanner = "Cerrar organizador" +PartyPlannerConfirmationAllOkTitle = "¡Felicidades!" +PartyPlannerConfirmationAllOkText = "La fiesta fue creada y las invitaciones fueron enviadas.\n¡Gracias!" +PartyPlannerConfirmationAllOkTextNoFriends = "¡Tu fiesta fue creada!\n¡Gracias!" +PartyPlannerConfirmationErrorTitle = "Uy." +PartyPlannerConfirmationValidationErrorText = "Lo siento, hay un\nproblema con la fiesta.\nVuelve atrás e inténtalo de nuevo." +PartyPlannerConfirmationDatabaseErrorText = "Lo siento, no se pudo registrar toda la información.\nInténtalo de nuevo más tarde.\nNo te preocupes, no perdiste golosinas." +PartyPlannerConfirmationTooManyText = "Ya eres el anfitrión de una fiesta.\nSi quieres organizar otra fiesta,\ncancela tu fiesta actual." +PartyPlannerInvitationThemeWhatSentence = "¡Estás invitado a mi fiesta %s! ¡%s!" +PartyPlannerInvitationThemeWhatSentenceNoFriends = "¡Voy a celebrar una fiesta %s! ¡%s!" +PartyPlannerInvitationThemeWhatActivitiesBeginning = "Voy a tener " +PartyPlannerInvitationWhoseSentence = "Fiesta %s" +PartyPlannerInvitationTheme = "Tema" +PartyPlannerInvitationWhenSentence = "Será %s,\na las %s Hora Toontown.\n¡Espero que puedas venir!" +PartyPlannerInvitationWhenSentenceNoFriends = "Será %s,\na las %s Hora Toontown.\n¡Dibugnífico!" +PartyPlannerComingSoon = "Muy pronto" +PartyPlannerCantBuy= "No se puede comprar" +PartyPlannerGenericName = "Organizador de fiestas" + +# DistributedPartyJukeboxActivity.py +PartyJukeboxOccupied = "Alguien está utilizando el Tocadiscos. Inténtalo más tarde." +PartyJukeboxNowPlaying = "¡La canción que elegiste está sonando en el Tocadiscos!" + +# Jukebox Music +MusicEncntrGeneralBg = "Encuentro con bots" +MusicTcSzActivity = "Popurrí Dibutorial" +MusicTcSz = "Paseando" +MusicCreateAToon = "Nuevo dibu en la ciudad" +MusicTtTheme = "Tema de Toontown" +MusicMinigameRace = "Lento y pausado" +MusicMgPairing = "¿Me recuerdas?" +MusicTcNbrhood = "Centro de Toontown" +MusicMgDiving = "Nana del tesoro" +MusicMgCannonGame = "¡Disparen los cañones!" +MusicMgTwodgame = "Dibu al mando" +MusicMgCogthief = "¡Atrapa al bot!" +MusicMgTravel = "Música viajera" +MusicMgTugOWar = "Juego de la cuerda" +MusicMgVine = "El swing de la selva" +MusicMgIcegame = "Situación dudosa" +MusicMgToontag = "Popurrí de minijuegos" +MusicMMatchBg2 = "Minnie Jazz" +MusicMgTarget = "Sobrevolando Toontown" +MusicFfSafezone = "La granja divertida" +MusicDdSz = "Camino del ánade" +MusicMmNbrhood = "Melodilandia de Minnie" +MusicGzPlaygolf = "¡Jugiemos al golf!" +MusicGsSz = "Estadio de Goofy" +MusicOzSz = "Acres de Chip y Dale" +MusicGsRaceCc = "Conducir por la ciudad" +MusicGsRaceSs = "¡Preparados, listos, ya!" +MusicGsRaceRr = "Ruta 66" +MusicGzSz = "La polka del hoyo" +MusicMmSz = "Bailando en las calles" +MusicMmSzActivity = "Aquí llega el trío" +MusicDdNbrhood = "Puerto de Donald" +MusicGsKartshop = "Don Goofytuercas" +MusicDdSzActivity = "Saloma Marina" +MusicEncntrGeneralBgIndoor = "Acumulando emoción" +MusicTtElevator = "¿Subes?" +MusicEncntrToonWinningIndoor = "¡Dibus, únanse!" +MusicEncntrGeneralSuitWinningIndoor = "¡Catástrofe bot!" +MusicTbNbrhood = "Frescolandia" +MusicDlNbrhood = "Sueñolandia de Donald" +MusicDlSzActivity = "Contar ovejas" +MusicDgSz = "Vals de las flores" +MusicDlSz = "Sonámbulo" +MusicTbSzActivity = "Problema de nieve" +MusicTbSz = "Temblores y Amores" +MusicDgNbrhood = "Jardín de Daisy" +MusicEncntrHallOfFame = "El salón de la fama" +MusicEncntrSuitHqNbrhood = "Dólares y centavos" +MusicChqFactBg = "Fábrica bot" +MusicCoghqFinale = "Triunfo de los dibus" +MusicEncntrToonWinning = "¡Canjear!" +MusicEncntrSuitWinning = "¡No te hace justicia!" +MusicEncntrHeadSuitTheme = "El gran jefe" +MusicLbJurybg = "Sesión judicial" +MusicLbCourtyard = "Acta de equilibrio" +MusicBossbotCeoV2 = "Jefazo" +MusicBossbotFactoryV1 = "Vals bot" +MusicBossbotCeoV1 = "Dominando" +MusicPartyOriginalTheme = "Fiesta" +MusicPartyPolkaDance = "Polka de fiesta" +MusicPartySwingDance = "Swing de fiesta" +MusicPartyWaltzDance = "Vals de fiesta" +MusicPartyGenericThemeJazzy = "Jazz de fiesta" +MusicPartyGenericTheme = "Tintineo de fiesta" + + +# JukeBoxGui +JukeboxAddSong = "Añadir\ncanción" +JukeboxReplaceSong = "Sustituir\ncanción" +JukeboxQueueLabel = "Siguiente canción:" +JukeboxSongsLabel = "Elige una canción:" +JukeboxClose = "Completado" +JukeboxCurrentlyPlaying = "Está sonando" +JukeboxCurrentlyPlayingNothing = "Tocadiscos en pausa" +JukeboxCurrentSongNothing = "¡Añade una canción a la lista!" + +PartyOverWarningNoName = "¡La fiesta terminó! ¡Gracias por venir!" +PartyOverWarningWithName = "¡La Fiesta de %s ha terminado! ¡Gracias por participar!" +PartyCountdownClockText = "Tiempo\n\nrestante" +PartyTitleText = "Fiesta %s" # what you see when you enter a party + +PartyActivityConjunction = " y" +# Note : This dictionary is used to show the names of the activities in various +# contexts. If PartyGlobals.ActivityIds is changed, this list must be +# updated with new indices. +PartyActivityNameDict = { + 0 : { + "generic" : "Tocadiscos\n20 canciones", + "invite" : "un Tocadiscos de 20 canciones", + "editor" : "Tocadiscos - 20", + "description" : "¡Escucha música con tu propio Tocadiscos de 20 canciones!" + }, + 1 : { + "generic" : "Cañones de fiesta", + "invite" : "Cañones de fiesta", + "editor" : "Cañones", + "description" : "¡Lánzate con los cañones directo a la diversión!" + }, + 2 : { + "generic" : "Trampolín", + "invite" : "Trampolín", + "editor" : "Trampolín", + "description" : "¡Recoge golosinas y salta más alto que nadie!" + }, + 3 : { + "generic" : "Atrápalo", + "invite" : "Atrápalo", + "editor" : "Atrápalo", + "description" : "¡Atrapa la fruta para ganar golosinas! ¡Tienes que esquivar los yunques!" + }, + 4 : { + "generic" : "Pista de baile\n10 pasos", + "invite" : "una pista de baile de 10 pasos", + "editor" : "Pista de baile - 10", + "description" : "¡Demuestra tu arte con los 10 pasos al auténtico estilo dibu!" + }, + 5 : { + "generic" : "Juego de la cuerda", + "invite" : "Juego de la cuerda", + "editor" : "Juego de la cuerda", + "description" : "¡A tirar de la cuerda como locos, 4 contra 4 dibus máximo!" + }, + 6 : { + "generic" : "Fuegos artificiales", + "invite" : "Fuegos artificiales", + "editor" : "Fuegos artificiales", + "description" : "¡Lanza tu propio espectáculo de fuegos artificiales!" + }, + 7 : { + "generic" : "El reloj", + "invite" : "un reloj", + "editor" : "El reloj", + "description" : "Cuenta atrás del tiempo que le queda a tu fiesta." + }, + 8 : { + "generic" : "Tocadiscos\n40 canciones", + "invite" : "un Tocadiscos de 40 canciones", + "editor" : "Tocadiscos - 40", + "description" : "¡Escucha música con tu propio Tocadiscos de 40 canciones!" + }, + 9 : { + "generic" : "Pista de baile\n20 pasos", + "invite" : "una pista de baile para 20 pasos", + "editor" : "Pista de baile - 20", + "description" : "¡¡Demuestra tu arte con los 20 pasos al auténtico estilo dibu!" + }, +} + +# Note : This dictionary is used to show the names of the decorations in various +# contexts. If PartyGlobals.DecorationIds is changed, this list must be +# updated with new indices. +PartyDecorationNameDict = { + 0 : { + "editor" : "Yunque y globos", + "description" : "¡Intenta evitar que la diversión se escape volando!", + }, + 1 : { + "editor" : "Escenario de fiesta", + "description" : "Globos, estrellas, ¿qué más se puede pedir?", + }, + 2 : { + "editor" : "Lazo festivo", + "description" : "¡Envuelve la diversión!", + }, + 3 : { + "editor" : "Pastel", + "description" : "Delicioso.", + }, + 4 : { + "editor" : "Castillo", + "description" : "El hogar de todo dibu es su castillo.", + }, + 5 : { + "editor" : "Montón de regalos", + "description" : "¡Regalos para todos los dibus!", + }, + 6 : { + "editor" : "Matasuegras", + "description" : "¡Este matasuegras es la bomba!", + }, + 7 : { + "editor" : "Portón de fiesta", + "description" : "¡Multicolor, una locura!", + }, + 8 : { + "editor" : "Pitos", + "description" : "¡Prrriiiiii!", + }, + 9 : { + "editor" : "Molinillo", + "description" : "¡Vueltas de color para todos!", + }, + 10 : { + "editor" : "Globo de bromas", + "description" : "Globo de bromas y estrellas diseñado por Olivea", + }, + 11 : { + "editor" : "Póster de golosinas", + "description" : "Un póster de golosinas diseñado por Cassidy", + }, + 12 : { + "editor" : "Pastel de bromas", + "description" : "Un pastel de bromas del revés diseñado por Felicia", + }, +} + +ActivityLabel = "Precio – Nombre de la actividad" +PartyDoYouWantToPlan = "¿Quieres organizar una fiesta nueva ahora?" +PartyPlannerOnYourWay = "¡Que te diviertas organizando tu fiesta!" +PartyPlannerMaybeNextTime = "Otra vez será. ¡Que pases un buen día!" +PartyPlannerHostingTooMany = "Sólo puedes ser el anfitrión de una fiesta a la vez." +PartyPlannerOnlyPaid = "Sólo los dibus pagados pueden organizar fiestas." +PartyPlannerNpcComingSoon = "¡Pronto se celebrarán otras fiestas! Inténtalo más tarde." +PartyPlannerNpcMinCost = "Organizar una fiesta cuesta un mínimo de %d golosinas." + +# Party Gates +PartyHatPublicPartyChoose = "¿Quieres ir a la 1ª fiesta pública disponible?" +PartyGateTitle = "Fiestas públicas" +PartyGateGoToParty = "¡Ir a la\nfiesta!" +PartyGatePartiesListTitle = "Anfitriones" +PartyGatesPartiesListToons = "Dibus" +PartyGatesPartiesListActivities = "Actividades" +PartyGatesPartiesListMinLeft = "Minutos restantes" +PartyGateLeftSign = "¡Entra!" +PartyGateRightSign = "¡Fiestas públicas!" +PartyGatePartyUnavailable = "Lo siento, esa fiesta ya no está disponible." +PartyGatePartyFull = "Lo siento, esa fiesta está llena." +PartyGateInstructions = 'Haz clic sobre un anfitrión y después sobre "¡Ir a la fiesta!"' + +# DistributedPartyActivity.py +PartyActivityWaitingForOtherPlayers = "Esperando a que otros jugadores se unan al juego de la fiesta..." +PartyActivityPleaseWait = "Espera un momento..." +DefaultPartyActivityTitle = "Título del juego de la fiesta" +DefaultPartyActivityInstructions = "Instrucciones del juego de la fiesta" +PartyOnlyHostLeverPull = "El anfitrión es el único que puede iniciar esta actividad." +PartyActivityDefaultJoinDeny = "En este momento no puedes unirte a esta actividad." +PartyActivityDefaultExitDeny = "En este momento no puedes abandonar esta actividad." + +# JellybeanRewardGui.py +PartyJellybeanRewardOK = "OK" + +# DistributedPartyCatchActivity.py +PartyCatchActivityTitle = "Actividad Atrápalo" +PartyCatchActivityInstructions = "Atrapa todas las piezas de fruta que puedas. ¡Intenta no 'atrapar' %(badThing)s!" +PartyCatchActivityFinishPerfect = "¡PERFECTO!" +PartyCatchActivityFinish = "¡Bien!" +PartyCatchActivityExit = 'EXIT' +PartyCatchActivityApples = 'manzanas' +PartyCatchActivityOranges = 'naranjas' +PartyCatchActivityPears = 'peras' +PartyCatchActivityCoconuts = 'cocos' +PartyCatchActivityWatermelons = 'sandías' +PartyCatchActivityPineapples = 'piñas' +PartyCatchActivityAnvils = 'yunques' +PartyCatchStarted = "El juego ya empezó. Ve a jugar." +PartyCatchCannotStart = "El juego no pudo iniciarse en este momento." +PartyCatchRewardMessage = "Piezas de fruta atrapadas: %s\n\nGolosinas ganadas: %s" + +# DistributedPartyDanceActivity.py +PartyDanceActivityTitle = "Pista de baile" +PartyDanceActivityInstructions = "¡Combina 3 o más dibujos con las TECLAS DE FLECHA para hacer pasos de baile! Hay 10 pasos de baile diferentes. ¿Podrás encontrarlos todos?" +PartyDanceActivity20Title = "Pista de baile" +PartyDanceActivity20Instructions = "¡Combina 3 o más dibujos con las TECHAS DE FLECHA para hacer pasos de baile! Hay 20 pasos de baile diferentes. ¿Podrás encontrarlos todos?" + +DanceAnimRight = "Derecha" +DanceAnimReelNeutral = "El dibu pescador" +DanceAnimConked = "El saludo" +DanceAnimHappyDance = "El baile feliz" +DanceAnimConfused = "Mareado" +DanceAnimWalk = "Paseo lunar" +DanceAnimJump = "¡El salto!" +DanceAnimFirehose = "El dibu de fuego" +DanceAnimShrug = "¿Quién sabe?" +DanceAnimSlipForward = "La caída" +DanceAnimSadWalk = "Cansado" +DanceAnimWave = "Hola y adiós" +DanceAnimStruggle = "El salto arrastrado" +DanceAnimRunningJump = "Carrera dibu" +DanceAnimSlipBackward = "Caída hacia atrás" +DanceAnimDown = "Abajo" +DanceAnimUp = "Arriba" +DanceAnimGoodPutt = "El golpe" +DanceAnimVictory = "El baile de la victoria" +DanceAnimPush = "Dibu mimo" +DanceAnimAngry = "Rock n' Roll" +DanceAnimLeft = "Izquierda" + +# DistributedPartyCannonActivity.py +PartyCannonActivityTitle = "Cañones" +PartyCannonActivityInstructions = "¡Golpea las nubes para que cambien de color y reboten en el aire! Desde EL AIRE, puedes usar las TECLAS DE FLECHA para DESLIZARTE." +PartyCannonResults = "¡Recogiste %d golosinas!\n\nNúmero de nubes golpeadas: %d" + +# DistributedPartyFireworksActivity.py +FireworksActivityInstructions = "Pulsa la tecla \"Repág.\" para ver mejor." +FireworksActivityBeginning = "¡Los fuegos artificiales están a punto de comenzar! ¡Que disfrutes del despectáculo!" +FireworksActivityEnding = "¡Espero que te haya gustado el espectáculo!" +PartyFireworksAlreadyActive = "El espectáculo de fuegos artificiales ya comenzó." +PartyFireworksAlreadyDone = "El espectáculo de fuegos artificiales ya terminó." + +# DistributedPartyTrampolineActivity.py +PartyTrampolineJellyBeanTitle = "Trampolín de golosinas" +PartyTrampolineTricksTitle = "Trampolín de acrobacias" +PartyTrampolineActivityInstructions = "Utiliza la tecla Control para saltar.\n\nSalta cuando tu dibu esté en el punto más bajo del trampolín para saltar más alto." +PartyTrampolineActivityOccupied = "Trampolín ocupado." +PartyTrampolineQuitEarlyButton = "Salir" +PartyTrampolineBeanResults = "Conseguiste %d golosinas." +PartyTrampolineBonusBeanResults = "Conseguiste %d golosinas, más %d extra por conseguir la Gran Golosina." +PartyTrampolineTopHeightResults = "La altura máxima fue de %d pies." +PartyTrampolineTimesUp = "Se acabó el tiempo" +PartyTrampolineReady = "Preparado..." +PartyTrampolineGo = "¡Ya!" +PartyTrampolineBestHeight = "Mejor altura hasta ahora:\n%s\n%d pies" +PartyTrampolineNoHeightYet = "¿Hasta dónde\npuedes saltar?" + +# DistributedPartyTugOfWarActivity.py +PartyTugOfWarJoinDenied = "Lo siento, en este momento no puedes unirte al juego de la cuerda." +PartyTugOfWarTeamFull = "Lo siento, este equipo está completo." +PartyTugOfWarExitButton = "Salir" +PartyTugOfWarWaitingForMore = "Esperando a más jugadores" # extra spaces on purpose given the blocky font +PartyTugOfWarWaitingToStart = "Esperando para comenzar" +PartyTugOfWarWaitingForOtherPlayers = "Esperando a otros jugadores" +PartyTugOfWarReady = "Preparados..." +PartyTugOfWarGo = "¡YA!" +PartyTugOfWarGameEnd = "¡Bien!" +PartyTugOfWarGameTie = "¡Empate!" +PartyTugOfWarRewardMessage = "Conseguiste %d golosinas. ¡Buen trabajo!" +PartyTugOfWarTitle = "Juego de la cuerda" + +# CalendarGuiMonth.py +CalendarShowAll = "Mostrar todos" +CalendarShowOnlyHolidays = "Sólo mostrar festivos" +CalendarShowOnlyParties = "Sólo mostrar fiestas" + +# CalendarGuiDay.py +CalendarEndsAt = "Termina en " +CalendarStartedOn = "Comenzó en " +CalendarEndDash = "Final de " +CalendarEndOf = "Final de " +CalendarPartyGetReady = "¡Prepárate!" +CalendarPartyGo = "¡Fiesta!" +CalendarPartyFinished = "Se acabó..." +CalendarPartyCancelled = "Cancelada." +CalendarPartyNeverStarted = "Nunca comenzó." + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Te restan %s" + +# MapPage.py +MapPageTitle = "Mapa" +MapPageBackToPlayground = "Volver al dibuparque" +MapPageBackToCogHQ = "Regresar al cuartel general bot" +MapPageGoHome = "Ir a casa" +# hood name, street name +MapPageYouAreHere = "Estás en: %s\n%s" +MapPageYouAreAtHome = "Estás en\ntu propiedad" +MapPageYouAreAtSomeonesHome = "Estás en\nla propiedad %s" +MapPageGoTo = "Ir a\n%s" + +# OptionsPage.py +OptionsPageTitle = "Opciones" +OptionsPagePurchase = "¡Suscríbete ya!" +OptionsPageLogout = "Cerrar sesión" +OptionsPageExitToontown = "Salir de Toontown" +OptionsPageMusicOnLabel = "La música está activada." +OptionsPageMusicOffLabel = "La música está desactivada." +OptionsPageSFXOnLabel = "Los efectos de sonido están activados." +OptionsPageSFXOffLabel = "Los efectos de sonido están desactivados." +OptionsPageToonChatSoundsOnLabel = "Los sonidos del chat están activados." +OptionsPageToonChatSoundsOffLabel = "Los sonidos del chat están desactivados." +OptionsPageFriendsEnabledLabel = "Se aceptan solicitudes de nuevos amigos." +OptionsPageFriendsDisabledLabel = "No se aceptan solicitudes de nuevos amigos." +OptionsPageSpeedChatStyleLabel = "Color para SpeedChat" +OptionsPageDisplayWindowed = "en ventana" +OptionsPageSelect = "Escoger" +OptionsPageToggleOn = "Activar" +OptionsPageToggleOff = "Desactivar" +OptionsPageChange = "Cambiar" +OptionsPageDisplaySettings = "Pantalla: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Pantalla: %(screensize)s" +OptionsPageExitConfirm = "¿Quieres salir de Toontown?" + +DisplaySettingsTitle = "Configuración de la pantalla" +DisplaySettingsIntro = "Los siguientes parámetros sirven para configurar el aspecto de Toontown en tu computadora. Lo más probable es que no haga falta modificarlos a no ser que surja algún problema." +DisplaySettingsIntroSimple = "Puedes ajustar la resolución de la pantalla a un valor más alto para mejorar la claridad gráfica y de texto, pero todo depende de tu tarjeta de video: un valor más alto puede hacer que el juego vaya menos fluido o que no funcione." + +DisplaySettingsApi = "Interfaz gráfica:" +DisplaySettingsResolution = "Resolución:" +DisplaySettingsWindowed = "En ventana" +DisplaySettingsFullscreen = "Pantalla completa" +DisplaySettingsApply = "Aplicar" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Cuando pulses OK cambiará la configuración gráfica. Si la nueva configuración no se representa correctamente en tu computadora, la pantalla volverá automáticamente a la configuración original transcurridos %s segundos." +DisplaySettingsAccept = "Pulsa OK para conservar la nueva configuración o Cancelar para volver a la anterior. Si no pulsas nada, se volverá a la configuración anterior pasados %s segundos." +DisplaySettingsRevertUser = "Se restableció la configuración anterior de la pantalla. " +DisplaySettingsRevertFailed = "La configuración de pantalla seleccionada no funciona en tu computadora. Se restableció la configuración anterior de la pantalla. " + +# TrackPage.py +TrackPageTitle = "Circuito Entrenador de Bromas" +TrackPageShortTitle = "Entrenador de Bromas" +TrackPageSubtitle = "¡Completa las dibutareas para aprender a usar las bromas nuevas!" +TrackPageTraining = "Estás entrenándote para usar las bromas de %s.\nCuando completes las 16 tareas,\npodrás usar las bromas de %s en los combates." +TrackPageClear = "En este momento no te estás entrenando en ningún circuito de bromas." +TrackPageFilmTitle = "%s\nPelícula de\nentrenamiento" +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "Dibutareas" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nPara: %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nPara elegir, ve a:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Elegir" +QuestPageLocked = "Bloqueado" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQfull +QuestPosterHQStreetName = "Cualquier calle" +QuestPosterHQLocationName = "Cualquier barrio" + +QuestPosterTailor = "Sastre" +QuestPosterTailorBuildingName = "Tienda de ropa" +QuestPosterTailorStreetName = "Cualquier dibuparque" +QuestPosterTailorLocationName = "Cualquier barrio" +QuestPosterPlayground = "En el dibuparque" +QuestPosterAtHome = "En tu casa" +QuestPosterInHome = "En tu casa" +QuestPosterOnPhone = "En tu teléfono" +QuestPosterEstate = "En tu hacienda" +QuestPosterAnywhere = "Cualquier parte" +QuestPosterAuxTo = "hacia:" +QuestPosterAuxFrom = "de:" +QuestPosterAuxFor = "para:" +QuestPosterAuxOr = "o:" +QuestPosterAuxReturnTo = "Vuelve a:" +QuestPosterLocationIn = " en " +QuestPosterLocationOn = " en " +QuestPosterFun = "¡Diviértete!" +QuestPosterFishing = "Ve a pescar" +QuestPosterComplete = "COMPLETADA" + +# ShardPage.py +ShardPageTitle = "Distritos" +ShardPageHelpIntro = "Cada distrito es una copia del mundo de Toontown." +ShardPageHelpWhere = "Ahora estás en el distrito \"%s\"." +ShardPageHelpWelcomeValley = "Ahora estás en el distrito \"Valle Bienvenido\", dentro de \"%s\"." +ShardPageHelpMove = "Para desplazarte a otro distrito, haz clic en su nombre." + +ShardPagePopulationTotal = "N.º total de habitantes de Toontown:\n%d" +ShardPageScrollTitle = "Nombre Habitantes" +ShardPageLow = "Tranquilo" +ShardPageMed = "Ideal" +ShardPageHigh = "Lleno" +ShardPageChoiceReject = "Ese distrito está lleno. Prueba con otro." + +# SuitPage.py +SuitPageTitle = "Galería de bots" +SuitPageMystery = "???" +SuitPageQuota = "%s de %s" +SuitPageCogRadar = "%s presente" +SuitPageBuildingRadarS = "%s edificio" +SuitPageBuildingRadarP = "%s edificios" + +# DisguisePage.py +DisguisePageTitle = "Disfraces de" + Cog +DisguisePageMeritAlert = "¡Listo para un ascenso!" +DisguisePageCogLevel = "Nivel %s" +DisguisePageMeritFull = "Lleno" + +# FishPage.py +FishPageTitle = "Pecera" +FishPageTitleTank = "Cubeta para los peces" +FishPageTitleCollection = "Álbum de peces" +FishPageTitleTrophy = "Trofeos de pesca" +FishPageWeightStr = "Peso: " +FishPageWeightLargeS = "%d kg. " +FishPageWeightLargeP = "%d kg. " +FishPageWeightSmallS = "%d gr." +FishPageWeightSmallP = "%d gr." +FishPageWeightConversion = 16 +FishPageValueS = "Valor: %d golosina" +FishPageValueP = "Valor: %d golosinas" +FishPageCollectedTotal = "Especies de peces recogidas: %d de %d" +FishPageRodInfo = "Caña de %s\n%d - %d kilos" +FishPageTankTab = "Cubeta" +FishPageCollectionTab = "Álbum" +FishPageTrophyTab = "Trofeos" + +FishPickerTotalValue = "Cubeta: %s / %s\nValor: %d golosinas" + +UnknownFish = "???" + +FishingRod = "Caña de %s" +FishingRodNameDict = { + 0 : "rama", + 1 : "bambú", + 2 : "madera", + 3 : "acero", + 4 : "oro", + } +FishTrophyNameDict = { + 0 : "Boquerón", + 1 : "Salmonete", + 2 : "Merluza", + 3 : "Pez volador", + 4 : "Tiburón", + 5 : "Pez espada", + 6 : "Ballena asesina", + } + +# GardenPage.py +GardenPageTitle = "Jardinería" +GardenPageTitleBasket = "Cesta de flores" +GardenPageTitleCollection = "Álbum de flores" +GardenPageTitleTrophy = "Trofeos de jardinería" +GardenPageTitleSpecials = "Especiales de jardinería" +GardenPageBasketTab = "Cesta" +GardenPageCollectionTab = "Álbum" +GardenPageTrophyTab = "Trofeos" +GardenPageSpecialsTab = "Especiales" +GardenPageCollectedTotal = "Variedades de flores recogidas: %d of %d" +GardenPageValueS = "Valor: %d golosinas" +GardenPageValueP = "Valor: %d golosinas" +FlowerPickerTotalValue = "Cesta: %s / %s\nValor: %d golosinas" +GardenPageShovelInfo = "%s Pala: %d / %d\n" +GardenPageWateringCanInfo = "%s Regadera: %d / %d" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Personalizador de kart" +KartPageTitleRecords = "Récords personales" +KartPageTitleTrophy = "Trofeos de carreras" +KartPageCustomizeTab = "Personalizar" +KartPageRecordsTab = "Récords" +KartPageTrophyTab = "Trofeo" +KartPageTrophyDetail = "Trofeo %s : %s" +KartPageTickets = "Boletos : " +KartPageConfirmDelete = "¿Borrar accesorio?" + +#plural +KartShtikerDelete = "Borrar" +KartShtikerSelect = "Selecciona una categoría" +KartShtikerNoAccessories = "No tienes accesorios" +KartShtikerBodyColors = "Colores de karts" +KartShtikerAccColors = "Colores de accesorios" +KartShtikerEngineBlocks = "Colores del capó" +KartShtikerSpoilers = "Colores de los portaequipaje" +KartShtikerFrontWheelWells = "Accesorios rueda delantera" +KartShtikerBackWheelWells = "Accesorios rueda trasera" +KartShtikerRims = "Accesorios llantas" +KartShtikerDecals = "Accesorios adhesivos" +#singluar +KartShtikerBodyColor = "Color del kart" +KartShtikerAccColor = "Color del accesorio" +KartShtikerEngineBlock = "Capó" +KartShtikerSpoiler = "Portaequipaje" +KartShtikerFrontWheelWell = "Rueda delantera" +KartShtikerBackWheelWell = "Rueda trasera" +KartShtikerRim = "Llanta" +KartShtikerDecal = "Adhesivo" + +KartShtikerDefault = "%s predeterminado" +KartShtikerNo = "No hay accesorios de %s" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Elige" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = "El curadibu te permite sanar a otros dibus durante el combate." +TrackChoiceGuiTRAP = "Las trampas son potentes bromas que se deben usar con cebos." +TrackChoiceGuiLURE = "Los cebos permiten aturdir a los bots y atraerlos a las trampas." +TrackChoiceGuiSOUND = "Las bromas de sonido afectan a todos los bots, pero no son muy potentes." +TrackChoiceGuiDROP = "Las bromas de caída causan un montón de daños, pero no son muy precisas." + +# EmotePage.py +EmotePageTitle = "Expresiones / Emociones" +EmotePageDance = "Creaste la siguiente secuencia de baile:" +EmoteJump = "Saltar" +EmoteDance = "Bailar" +EmoteHappy = "Feliz" +EmoteSad = "Triste" +EmoteAnnoyed = "Fastidiado" +EmoteSleep = "Soñoliento" + +# TIP Page +TIPPageTitle = "TIP" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNivel %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "¡No puedes irte del dibuparque hasta que tu risómetro esté sonriendo!" + +# InventoryNew.py +InventoryTotalGags = "Bromas totales\n%d / %d" +InventroyPinkSlips = "%s Cartas de despido" +InventroyPinkSlip = "1 carta de despido" +InventoryDelete = "BORRAR" +InventoryDone = "LISTO" +InventoryDeleteHelp = "Haz clic en una broma para BORRARLA." +InventorySkillCredit = "Habilidad: %s" +InventorySkillCreditNone = "Habilidad: Ninguna" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Precisión: %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "¡Quedan %(nextExp)s!" +InventoryGuestExp = "Límite de invitados" +GuestLostExp = "Por encima del límite de invitados" +InventoryAffectsOneCog = "Afecta a: un " + Cog +InventoryAffectsOneToon = "Afecta a: un dibu" +InventoryAffectsAllToons = "Afecta a: todos los dibus" +InventoryAffectsAllCogs = "Afecta a: todos los " + Cogs +InventoryHealString = "Curadibu" +InventoryDamageString = "Daños" +InventoryBattleMenu = "MENÚ DE COMBATE" +InventoryRun = "CORRER" +InventorySOS = "S.O.S." +InventoryPass = "PASE" +InventoryFire = "DISPARO" +InventoryClickToAttack = "Haz clic en \nuna broma \npara atacar" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Antes de marcharte debes subirte al tranvía.\n\n\n\n\n\nEl tranvía esta al lado de la tienda de bromas de Goofy." +NPCForceAcknowledgeMessage2 = "¡Felicitaciones por completar tu tarea del tranvía!\nVe al cuartel general para recibir tu recompensa.\n\n\n\n\n\n\nEl cuartel general esta cerca del centro del dibuparque." +NPCForceAcknowledgeMessage3 = "Recuerda que tienes que subirte al tranvía.\n\n\n\n\n\nEl tranvía esta al lado de la tienda de bromas de Goofy." +NPCForceAcknowledgeMessage4 = "!Felicitaciones! ¡Completaste la primera dibutarea!\n\n\n\n\n\nVe al cuartel general para recibir tu recompensa." +NPCForceAcknowledgeMessage5 = "No olvides tu dibutarea!\n\n\n\n\n\n\n\n\n\n\nAl otro lado de túneles como este, encontrarás bots que podrás derrotar." +NPCForceAcknowledgeMessage6 = "Buen trabajo, derrotaste a esos bots!\n\n\n\n\n\n\n\n\nRegresa al cuartel general lo antes posible." +NPCForceAcknowledgeMessage7 = "¡No olvides hacerte amigo de alguien!\n\n\n\n\n\n\nHaz clic en otro jugador y utiliza el botón Amigo Nuevo." +NPCForceAcknowledgeMessage8 = "¡Fantástico! ¡Tienes un amigo nuevo!\n\n\n\n\n\n\n\n\nAhora debes regresar al cuartel general." +NPCForceAcknowledgeMessage9 = "¡Usaste bien el teléfono!\n\n\n\n\n\n\n\n\nRegresa al cuartel general para pedir tu recompensa." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "¡Recibiste 1 punto de lanzamiento! ¡Cuando hayas recibido 10, tendrás una broma nueva!" +MovieTutorialReward2 = "¡Recibiste 1 punto de chorro! ¡Cuando hayas recibido 10, tendrás una broma nueva!" +MovieTutorialReward3 = "¡Buen trabajo! ¡Completaste tu primera dibutarea!" +MovieTutorialReward4 = "¡Ve al cuartel general para recibir tu premio!" +MovieTutorialReward5 = "¡Que te diviertas!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['curadibu', 'trampa', 'cebo', 'sonido', 'lanzamiento', 'chorro', 'caída'] +BattleGlobalNPCTracks = ['reaprovisionamiento', 'dibus aciertan', 'bots fallan'] +BattleGlobalAvPropStrings = ( + ('Pluma', 'Megáfono', 'Pintalabios', 'Caña de bambú', 'Polvo de hadas', 'Bolas de malabarista', 'Salto vertical'), + ('Cáscara de banana', 'Rastrillo', 'Canicas', 'Arena movediza', 'Trampilla', 'TNT', 'Ferrocarril'), + ('Billete de 1 dólar', 'Imán pequeño', 'Billete de 5 dólares', 'Imán grande', 'Billete de 10 dólares', 'Gafas hipnóticas', 'Presentación'), + ('Bocina de bicicleta', 'Silbato', 'Corneta', 'Sirena', 'Trompa de elefante', 'Sirena de niebla', 'Cantante de ópera'), + ('Magdalena', 'Trozo de tarta de frutas', 'Trozo de tarta de nata', 'Tarta de frutas entera', 'Tarta de nata entera', 'Tarta de cumpleaños', 'Tarta nupcial'), + ('Flor chorreante', 'Vaso de agua', 'Pistola de agua', 'Botella de soda', 'Manguera', 'Nube tormentosa', 'Géiser'), + ('Maceta', 'Saco de arena', 'Yunque', 'Pesa grande', 'Caja fuerte', 'Piano de cola', 'Dibutanic') + ) +BattleGlobalAvPropStringsSingular = ( + ('una pluma', 'un megáfono', 'un pintalabios', 'una caña de bambú', 'un polvo de hadas', 'unas bolas de malabarista', 'un salto vertical'), + ('una cáscara de banana', 'un rastrillo', 'unas canicas', 'una arena movediza', 'una trampilla', 'un TNT', 'un ferrocarril'), + ('un billete de 1 dólar', 'un imán pequeño', 'un billete de 5 dólares', 'un imán grande', 'un billete de 10 dólares', 'unas gafas hipnóticas', 'una presentación'), + ('una bocina de bicicleta', 'un silbato', 'una corneta', 'una sirena', 'una trompa de elefante', 'una sirena de niebla', 'un cantante de ópera'), + ('una magdalena', 'un trozo de tarta de frutas', 'un trozo de tarta de nata', 'una tarta de frutas entera', 'una tarta de nata entera', 'una tarta de cumpleaños', 'una tarta nupcial'), + ('una flor chorreante', 'un vaso de agua', 'una pistola de agua', 'una botella de soda', 'una manguera', 'una nube tormentosa', 'un géiser'), + ('una maceta', 'un saco de arena', 'un yunque', 'una pesa grande', 'una caja fuerte', 'un piano de cola', 'el Dibutanic') + ) +BattleGlobalAvPropStringsPlural = ( + ('Plumas', 'Megáfonos', 'Pintalabios', 'Cañas de bambú', 'Polvos de hadas', 'Bolas de malabarista', 'Saltos verticales'), + ('Cáscaras de banana', 'Rastrillos', 'Canicas', 'Arenas movedizas', 'Trampillas','TNT', 'Ferrocarriles'), + ('Billetes de 1 dólar', 'Imanes pequeños', 'Billetes de 5 dólares', 'Imanes grandes','Billetes de 10 dólares', 'Gafas hipnóticas', 'Presentaciones'), + ('Bocinas de bicicleta', 'Silbatos', 'Cornetas', 'Sirenas', 'Trompas de elefante', 'Sirenas de niebla', 'Cantantes de ópera'), + ('Magdalenas', 'Trozos de tarta de frutas', 'Trozos de tarta de nata','Tartas de frutas enteras', 'Tartas de nata enteras', 'Tartas de cumpleaños', 'Tartas nupciales'), + ('Flores chorreantes', 'Vasos de agua', 'Pistolas de agua','Botellas de soda', 'Mangueras', 'Nubes tormentosas', 'Géisers'), + ('Macetas', 'Sacos de arena', 'Yunques', 'Pesas grandes', 'Cajas fuertes','Pianos de cola', 'Trasatlánticos') + ) +BattleGlobalAvTrackAccStrings = ("Media", "Perfecta", "Baja", "Alta", "Media", "Alta", "Baja") +BattleGlobalLureAccLow = "Baja" +BattleGlobalLureAccMedium = "Media" + +AttackMissed = "Fallaste" + +NPCCallButtonLabel = 'LLAMAR' + +# ToontownLoader.py +LoaderLabel = "Cargando..." + +# PlayGame.py +HeadingToHood = "Entrando %(to)s %(hood)s..." # to phrase, hood name +HeadingToYourEstate = "Entrando a tu propiedad..." +HeadingToEstate = "Entrando a la propiedad de %s..." # avatar name +HeadingToFriend = "Entrando a la propiedad del amigo %s..." # avatar name + +# Hood.py +HeadingToPlayground = "Entrando al dibuparque..." +HeadingToStreet = "Entrando %(to)s %(street)s..." # to phrase, street name + +# TownBattle.py +TownBattleRun = "¿Quieres volver corriendo al dibuparque?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "¿QUÉ DIBU?" +TownBattleChooseAvatarCogTitle = "¿QUÉ " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "ATRÁS" + +#firecogpanel +FireCogTitle = "CARTAS DE DESPIDO RESTANTES:%s\n¿DISPARAR A QUÉ BOT?" +FireCogLowTitle = "CARTAS DE DESPIDO RESTANTES:%s\n¡NO HAY SUFICIENTES CARTAS!" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "¡No tienes amigos a los que llamar!" +TownBattleSOSWhichFriend = "¿A qué amigo quieres llamar?" +TownBattleSOSNPCFriends = "Dibus rescatados" +TownBattleSOSBack = "ATRÁS" + +# TownBattleToonPanel.py +TownBattleToonSOS = "S.O.S." +TownBattleToonFire = "Disparo" +TownBattleUndecided = "¿?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Esperando a\notros jugadores..." +TownSoloBattleWaitTitle = "Espera..." +TownBattleWaitBack = "ATRÁS" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Buscando al Dibuperrito\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s es %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "No puedes subirte al tranvía hasta que el risómetro esté sonriendo." +TrolleyTFAMessage = "No puedes subirte al tranvía hasta que lo diga Mickey." +TrolleyHopOff = "Bajarse" + +# DistributedFishingSpot.py +FishingExit = "Listo" +FishingCast = "Lanzar" +FishingAutoReel = "Carrete automático" +FishingItemFound = "Pescaste:" +FishingCrankTooSlow = "Muy\nlento" +FishingCrankTooFast = "Muy\nrápido" +FishingFailure = "¡No pescaste nada!" +FishingFailureTooSoon = "No empieces a enrollar el carrete hasta que veas que pican. ¡Espera a que la boya se mueva hacia arriba y abajo rápidamente!" +FishingFailureTooLate = "¡Es importante que enrolles la línea mientras el pez está mordiendo el anzuelo!" +FishingFailureAutoReel = "El carrete automático no funcionó esta vez. Enrolla a mano el carrete a la velocidad correcta para poder pescar algo." +FishingFailureTooSlow = "Enrollaste el carrete demasiado despacio. Algunos peces son más rápidos que otros. ¡Intenta mantener centrada la barra de velocidad!" +FishingFailureTooFast = "Enrollaste el carrete demasiado deprisa. Algunos peces son más lentos que otros. ¡Intenta mantener centrada la barra de velocidad!" +FishingOverTankLimit = "La cubeta de peces está lleno. Vende unos cuantos peces y vuelve." +FishingBroke = "¡No tienes nada que poner en el anzuelo! Súbete al tranvía para conseguir más golosinas." +FishingHowToFirstTime = "Pulsa el botón Lanzar y arrastra hacia abajo. Cuanto más lejos arrastres, más fuerte será el lanzamiento. Ajusta el ángulo para acertar en los peces.\n\n¡Pruébalo ya!" +FishingHowToFailed = "Pulsa el botón para Lanzar y arrastra hacia abajo. Cuanto más lejos arrastres, más fuerte será el lanzamiento. Ajusta el ángulo para acertar en los peces.\n\n¡Prueba de nuevo!" +FishingBootItem = "Una bota vieja" +FishingJellybeanItem = "%s golosinas" +FishingNewEntry = "¡Una nueva\nespecie!" +FishingNewRecord = "¡Nuevo\nrécord!" + +# FishPoker +FishPokerCashIn = "Canjear\n%s\n%s" +FishPokerLock = "Fijar" +FishPokerUnlock = "Liberar" +FishPoker5OfKind = "Escalera de color" +FishPoker4OfKind = "Póquer" +FishPokerFullHouse = "Full" +FishPoker3OfKind = "Trío" +FishPoker2Pair = "Doble pareja" +FishPokerPair = "Pareja" + +# DistributedTutorial.py +TutorialGreeting1 = "¡Hola, %s!" +TutorialGreeting2 = "¡Hola, %s!\n¡Ven aquí!" +TutorialGreeting3 = "¡Hola, %s!\n¡Ven aquí!\n¡Usa las flechas del teclado!" +TutorialMickeyWelcome = "¡Bienvenido a Toontown!" +TutorialFlippyIntro = "Te voy a presentar a mi amigo %s." % Flippy +TutorialFlippyHi = "¡Hola, %s!" +TutorialQT1 = "Puedes usar esto para hablar." +TutorialQT2 = "Puedes usar esto para hablar.\nHaz clic y elige \"Hola\"." +TutorialChat1 = "Puedes hablar con cualquiera de estos botones." +TutorialChat2 = "El botón azul te permite charlar en línea por medio del teclado." +TutorialChat3 = "¡Ten cuidado! Muchos de los jugadores no entenderán lo que dices si usas el teclado." +TutorialChat4 = "El botón verde abre: %s." +TutorialChat5 = "Todos entienden lo que dices cuando usas: %s." +TutorialChat6 = "Prueba decir \"Hola\"." +TutorialBodyClick1 = "¡Muy bien!" +TutorialBodyClick2 = "¡Encantado de conocerte! ¿Quieres ser mi amigo?" +TutorialBodyClick3 = "Para ser amigo de %s, haz clic en él..." % Flippy +TutorialHandleBodyClickSuccess = "¡Buen trabajo!" +TutorialHandleBodyClickFail = "Todavía no. Prueba a hacer clic en %s..." % Flippy +TutorialFriendsButton = "Ahora pulsa el botón \"Amigos\", situado debajo de la imagen de %s, en la esquina derecha." % Flippy +TutorialHandleFriendsButton = "Después, pulsa el botón \"Sí\"." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "¿Quieres ser amigo de %s?" % Flippy +TutorialFriendsPanelMickeyChat = "%s aceptó ser tu amigo. Pulsa \"Ok\" para terminar." % Flippy +TutorialFriendsPanelYes = "¡%s dijo que sí!" % Flippy +TutorialFriendsPanelNo = "¡No es que seas muy amable!" +TutorialFriendsPanelCongrats = "¡Felicitaciones! Acabas de hacer tu primer amigo." +TutorialFlippyChat1 = "Ven a verme cuando estés preparado para tu primera dibutarea." +TutorialFlippyChat2 = "Estaré en el Ayuntamiento." +TutorialAllFriendsButton = "Para ver a todos tus amigos, haz clic en el botón Amigos. Pruébalo..." +TutorialEmptyFriendsList = "Ahora mismo la lista está vacía porque %s no es un jugador de verdad." % Flippy +TutorialCloseFriendsList = "Pulsa el botón\nCerrar para que la\nlista se cierre." +TutorialShtickerButton = "El botón de la esquina inferior derecha sirve para abrir el dibucuaderno. Prúebalo..." +TutorialBook1 = "El dibucuaderno contiene un montón de información útil, como este mapa de Toontown." +TutorialBook2 = "También puedes ver cómo van tus dibutareas." +TutorialBook3 = "Cuando termines, vuelve a pulsar el botón del libro para que se cierre." +TutorialLaffMeter1 = "También vas a necesitar esto..." +TutorialLaffMeter2 = "También vas a necesitar esto...\nEs tu risómetro." +TutorialLaffMeter3 = "Cuando los bots te ataquen, irá disminuyendo." +TutorialLaffMeter4 = "Cuando estés en dibuparques como este, subirá de nuevo." +TutorialLaffMeter5 = "Cuando completes dibutareas obtendrás recompensas, como por ejemplo un aumento del límite del risómetro." +TutorialLaffMeter6 = "¡Ten cuidado! Si los bots te vencen, perderás todas las bromas." +TutorialLaffMeter7 = "Para conseguir más bromas, juega a los juegos del tranvía." +TutorialTrolley1 = "¡Sígueme hasta el tranvía!" +TutorialTrolley2 = "¡Súbete!" +TutorialBye1 = "¡Juega a unos cuantos juegos!" +TutorialBye2 = "¡Juega a unos cuantos juegos!\n¡Compra unas cuantas bromas!" +TutorialBye3 = "Cuando termines, ve a ver a %s" % Flippy + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "¡Vas en dirección contraria! ¡Ve a buscar a %s!" % Mickey + +PetTutorialTitle1 = "El panel Dibuperrito" +PetTutorialTitle2 = "Dibuperrito de SpeedChat" +PetTutorialTitle3 = "Catálogo tolón-tolón" +PetTutorialNext = "Página siguiente" +PetTutorialPrev = "Página anterior" +PetTutorialDone = "Completado" +PetTutorialPage1 = "Haz clic en un Dibuperrito para abrir el panel Dibuperrito. Desde aquí, podrás alimentar, rascar y llamar al Dibuperrito." +PetTutorialPage2 = "Utiliza la nueva zona 'Mascotas' en el menú SpeedChat para hacer que un Doodle haga una acrobacia. Si la hace, ¡dale un premio y aprenderá a hacerlo mejor!" +PetTutorialPage3 = "Compra nuevas acrobacias para el Dibuperrito desde el catálogo tolón-tolón de Clarabel. ¡Las mejores acrobacias dan mejores curadibus!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Jardinería" +GardenTutorialTitle2 = "Flores" +GardenTutorialTitle3 = "Árboles" +GardenTutorialTitle4 = "Cómo hacerlo" +GardenTutorialTitle5 = "Estatuas" +GardenTutorialNext = "Página siguiente" +GardenTutorialPrev = "Página anterior" +GardenTutorialDone = "Completado" +GardenTutorialPage1 = "¡Mejora tu hacienda con un jardín! ¡Puedes plantar flores, árboles, cultivar bromas súper poderosas y decorarlo con estatuas!" +GardenTutorialPage2 = "Las flores son complicadas y necesitan exclusivas recetas de golosina. Cuando crezcan, colócalas en la carretilla para venderlas y conseguir más subidas del risómetro." +GardenTutorialPage3 = "Utiliza una broma de tu inventario para plantar un árbol. Pasados unos días, ¡la broma provocará más daños! Recuerda que tiene que estar fuerte y sana para que no desaparezca el aumento de daños." +GardenTutorialPage4 = "Camina hasta estos puntos para plantar, regar, cavar o recolectar tu jardín." +GardenTutorialPage5 = "Las estatuas se pueden comprar en el catálogo tolón-tolón de Clarabel. Mejora tu habilidad para desbloquear las estatuas más extravagantes." + +# Playground.py +PlaygroundDeathAckMessage = "¡"+TheCogs+" se llevaron todas tus bromas!\n\nEstás triste. No puedes irte del dibuparque hasta que estés feliz." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "El "+Foreman+" fue derrotado antes de que llegaras. No recuperaste ninguna pieza bot." + +# MintInterior +ForcedLeaveMintAckMsg = "El supervisor de planta de la fabrica de monedas fue derrotado antes de que llegaras hasta él. No recuperaste ningún botdólar." + +# DistributedFactory.py +HeadingToFactoryTitle = "De camino a %s..." +ForemanConfrontedMsg = "¡%s está luchando contra el "+Foreman+"!" + +# DistributedMint.py +MintBossConfrontedMsg = "¡%s está luchando contra el supervisor!" + +# DistributedStage.py +StageBossConfrontedMsg = "¡%s está luchando contra el empleado!" +stageToonEnterElevator = "%s \nentró en el ascensor" +ForcedLeaveStageAckMsg = "El empleado legal fue derrotado antes de que llegaras a él. No recuperaste ninguna Notificación del tribunal." + + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Esperando a que se unan otros jugadores..." +MinigamePleaseWait = "Espera..." +DefaultMinigameTitle = "Título del minijuego" +DefaultMinigameInstructions = "Instrucciones del minijuego" +HeadingToMinigameTitle = "Entrando a: %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Indicador\nde fuerza" +MinigamePowerMeterTooSlow = "Muy\nlento" +MinigamePowerMeterTooFast = "Muy\nrápido" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Plantilla del minijuego" +MinigameTemplateInstructions = "Esto es una plantilla de minijuegos. Úsala para crear nuevos minijuegos." + +# DistributedCannonGame.py +CannonGameTitle = "El Cañón" +CannonGameInstructions = "Dispara a tu dibu para meterlo en el depósito de agua tan rápido como puedas. Usa el mouse o las teclas de flecha para apuntar el cañón. ¡Date prisa y consigue una gran recompensa para todos!" +CannonGameReward = "RECOMPENSA" + +# DistributedTwoDGame.py +TwoDGameTitle = "Dibuevasión" +TwoDGameInstructions = "Escapa de la guarida " + Cog + " en cuanto puedas. Usa las teclas de flecha para correr/saltar y Ctrl para mojar a un " + Cog + ". Recoge tesoros " + Cog + " para conseguir más puntos." +TwoDGameElevatorExit = "SALIR" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Juego de la Cuerda" +TugOfWarInstructions = "Pulsa alternativamente las teclas de flecha izquierda y derecha con la suficiente rapidez para alinear la barra verde con la línea roja. ¡No pulses demasiado deprisa ni demasiado despacio o acabarás en el agua!" +TugOfWarGameGo = "¡YA!" +TugOfWarGameReady = "Listo..." +TugOfWarGameEnd = "¡Muy bien!" +TugOfWarGameTie = "¡Empate!" +TugOfWarPowerMeter = "Indicador de fuerza" + +# DistributedPatternGame.py +PatternGameTitle = "Imita a %s" % Minnie +PatternGameInstructions = Minnie + " te enseñará una secuencia de baile " + \ + "Intenta repetir con las teclas de flecha el baile de "+Minnie+" justo igual que lo hace ella." +PatternGameWatch = "Observa estos pasos de baile..." +PatternGameGo = "¡YA!" +PatternGameRight = "¡Bien, %s!" +PatternGameWrong = "¡Vaya!" +PatternGamePerfect = "¡Perfecto, %s!" +PatternGameBye = "¡Gracias por jugar!" +PatternGameWaitingOtherPlayers = "Esperando a otros jugadores..." +PatternGamePleaseWait = "Espera..." +PatternGameFaster = "¡Fuiste\nmuy rápido!" +PatternGameFastest = "¡Fuiste\nel más rápido!" +PatternGameYouCanDoIt = "¡Vamos!\n¡Puedes hacerlo!" +PatternGameOtherFaster = "\nfue más rápido." +PatternGameOtherFastest = "\nfue el más rápido." +PatternGameGreatJob = "¡Buen trabajo!" +PatternGameRound = "¡Asalto nº %s!" # Round 1! Round 2! .. +PatternGameImprov = "¡Lo hiciste muy bien! ¡Ahora, supérate!" + +# DistributedRaceGame.py +RaceGameTitle = "Carrera" +RaceGameInstructions = "Haz clic en un número. ¡Piénsalo bien! Sólo avanzarás si nadie más escogió ese mismo número." +RaceGameWaitingChoices = "Esperando a que elijan otros jugadores..." +RaceGameCardText = "%(name)s saca: %(reward)s" +RaceGameCardTextBeans = "%(name)s recibe: %(reward)s" +RaceGameCardTextHi1 = "¡%(name)s es un dibu fabuloso!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " adelante 1 espacio" +RaceGameForwardTwoSpaces = " adelante 2 espacios" +RaceGameForwardThreeSpaces = " adelante 3 espacios" +RaceGameBackOneSpace = " atrás 1 espacio" +RaceGameBackTwoSpaces = " atrás 2 espacios" +RaceGameBackThreeSpaces = " atrás 3 espacios" +RaceGameOthersForwardThree = " todos los demás, adelante \n3 espacios" +RaceGameOthersBackThree = " todos los demás, atrás \n3 espacios" +RaceGameInstantWinner = "¡Ganador instantáneo!" +RaceGameJellybeans2 = "2 golosinas" +RaceGameJellybeans4 = "4 golosinas" +RaceGameJellybeans10 = "¡10 golosinas!" + +# DistributedRingGame.py +RingGameTitle = "Los anillos" +# color +RingGameInstructionsSinglePlayer = "Intenta atravesar nadando todos los anillos que puedas de color %s. Usa las flechas del teclado para nadar." +# color +RingGameInstructionsMultiPlayer = "Intenta atravesar nadando los anillos de color %s. Los demás jugadores intentarán atravesar el resto de los anillos. Usa las flechas del teclado para nadar." +RingGameMissed = "FALLÓ" +RingGameGroupPerfect = "GRUPO\n¡¡PERFECTO!!" +RingGamePerfect = "¡PERFECTO!" +RingGameGroupBonus = "BONIFICACIÓN POR GRUPO" + +# RingGameGlobals.py +ColorRed = "rojo" +ColorGreen = "verde" +ColorOrange = "naranja" +ColorPurple = "morado" +ColorWhite = "blanco" +ColorBlack = "negro" +ColorYellow = "amarillo" + +# DistributedDivingGame.py +DivingGameTitle = "Inmersión del tesoro" +# color +DivingInstructionsSinglePlayer = "Irán apareciendo tesoros en el fondo del lago. Utiliza las techas de flechas para nadar. ¡Esquiva los peces y sube los tesoros al barco!" +# color +DivingInstructionsMultiPlayer = "Irán apareciendo tesoros en el fondo del lago. Utiliza las techas de flechas para nadar. ¡Se debe trabajar en equipo para subir los tesoros al barco!" +DivingGameTreasuresRetrieved = "Tesoros conseguidos" + +#Distributed Target Game +TargetGameTitle = "Catapulta dibu" +TargetGameInstructionsSinglePlayer = "Aterriza sobre los objetivos para conseguir puntos" +TargetGameInstructionsMultiPlayer = "Aterriza sobre los objetivos para conseguir puntos" +TargetGameBoard = "Ronda %s: mantener el mejor puntaje" +TargetGameCountdown = "Lanzamiento forzado en %s segundos" +TargetGameCountHelp = "Oprime repetidas veces las flechas izquierda y derecha\npara ganar potencia y suelta para lanzar" +TargetGameFlyHelp = "Pulsa abajo para abrir el paraguas" +TargetGameFallHelp = "Utiliza las teclas de flecha para aterrizar sobre el objetivo" +TargetGameBounceHelp = "Rebotar puede hacer que falles el objetivo" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nTú: %s" +PhotoGameScoreBlank = "Puntaje: %s" +PhotoGameScoreOther = "\n%s"#"Puntaje: %s\n%s" +PhotoGameScoreYou = "\n¡Mejor bonificación!"#"Puntaje: %s\n¡Mejor bonificación!" + + +# DistributedTagGame.py +TagGameTitle = "Las traes" +TagGameInstructions = "Recoge los tesoros. ¡No podrás recoger tesoros cuando LAS TRAIGAS!" +TagGameYouAreIt = "¡Tú las TRAES!" +TagGameSomeoneElseIsIt = "¡%s LAS TRAE!" + +# DistributedMazeGame.py +MazeGameTitle = "El laberinto" +MazeGameInstructions = "Recoge los tesoros. ¡Intenta recogerlos todos, pero ten cuidado con los bots!" + +# DistributedCatchGame.py +CatchGameTitle = "Atrápalo" +CatchGameInstructions = "Atrapa todas las piezas de %(fruit)s que puedas. ¡Ten cuidado con los " + Cogs + " e intenta no ‘atrapar’ %(badThing)s!" +CatchGamePerfect = "¡PERFECTO!" +CatchGameApples = 'todas las manzanas' +CatchGameOranges = 'todas las naranjas' +CatchGamePears = 'todas las peras' +CatchGameCoconuts = 'todos los cocos' +CatchGameWatermelons = 'todas las sandías' +CatchGamePineapples = 'todas las piñas' +CatchGameAnvils = 'yunque' + +# DistributedPieTossGame.py +PieTossGameTitle = "Lanzatartas" +PieTossGameInstructions = "Prueba tu puntería lanzando tartas." + +# DistributedPhotoGame.py +PhotoGameInstructions = "Toma fotografías que coincidan con los dibus que se muestran abajo. Dirige la cámara con el mouse y haz clic con el botón izquierdo para tomar una fotografía. Pulsa Ctrl para acercar o alejar la imagen y mira a tu alrededor con las teclas de flecha. ¡Las fotografías con una calificación más alta te darán más puntos!" +PhotoGameTitle = "Diver Foto" +PhotoGameFilm = "PELÍCULA" +PhotoGameScore = "Puntaje del equipo: %s\n\nMejores fotos: %s\n\nPuntaje total: %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + " Ladrón" +CogThiefGameInstructions = "¡Impide que los " + Cogs + " roben los barriles de bromas! Pulsa la tecla Ctrl para lanzar una tarta. Utiliza las teclas de flecha para moverte. Consejo: te puedes mover en diagonal." +CogThiefBarrelsSaved = "¡%(num)d barriles\nsalvados!" +CogThiefBarrelSaved = "¡%(num)d barril\nsalvado!" +CogThiefNoBarrelsSaved = "¡Ningún barril\nsalvado" +CogThiefPerfect = "¡PERFECTO!" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JUGAR" + +# Purchase.py +GagShopName = "Tienda de bromas de Goofy" +GagShopPlayAgain = "JUGAR\nOTRA VEZ" +GagShopBackToPlayground = "VOLVER AL\nDIBUPARQUE" +GagShopYouHave = "Tienes %s golosinas para gastar" +GagShopYouHaveOne = "Tienes 1 golosina para gastar" +GagShopTooManyProps = "Lo siento, tienes demasiados accesorios" +GagShopDoneShopping = "COMPRAS\nFINALIZADAS" +# name of a gag +GagShopTooManyOfThatGag = "Lo siento, ya tienes suficientes %s." +GagShopInsufficientSkill = "Todavía no tienes suficiente habilidad para eso" +# name of a gag +GagShopYouPurchased = "Compraste %s" +GagShopOutOfJellybeans = "¡Lo siento, te quedaste sin golosinas!" +GagShopWaitingOtherPlayers = "Esperando a otros jugadores..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s se desconectó" +GagShopPlayerExited = "%s se marchó" +GagShopPlayerPlayAgain = "Jugar de nuevo" +GagShopPlayerBuying = "Comprando" + +# MakeAToon.py +GenderShopQuestionMickey = "¡Para crear un dibuchico, haz clic en mí!" +GenderShopQuestionMinnie = "¡Para crear una dibuchica, haz clic en mí!" +GenderShopFollow = "¡Sígueme!" +GenderShopSeeYou = "¡Hasta luego!" +GenderShopBoyButtonText = "Chico" +GenderShopGirlButtonText = "Chica" + +# BodyShop.py +BodyShopHead = "Cabeza" +BodyShopBody = "Cuerpo" +BodyShopLegs = "Piernas" + +# ColorShop.py +ColorShopToon = "Color del dibu" +ColorShopHead = "Cabeza" +ColorShopBody = "Cuerpo" +ColorShopLegs = "Piernas" +ColorShopParts = "Multicolor" +ColorShopAll = "Un color" + +# ClothesShop.py +ClothesShopShorts = "shorts" +ClothesShopShirt = "Camiseta" +ClothesShopBottoms = "Falda" + +# MakeAToon +PromptTutorial = "¡¡Felicidades!!\n¡Eres el nuevo ciudadano de Toontown!\n\n¿Quieres continuar el Dibututorial o teletransportarte directamente al Centro de Toontown?" +MakeAToonSkipTutorial = "Omitir Dibututorial" +MakeAToonEnterTutorial = "Acceder al Dibututorial" +MakeAToonDone = "Listo" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Atrás" +CreateYourToon = "Haz clic en las flechas para crear a tu dibu." +CreateYourToonTitle = "Crea a tu dibu" +ShapeYourToonTitle = "Elige tu tipo" +PaintYourToonTitle = "Elige tu color" +PickClothesTitle = "Elige tu ropa" +NameToonTitle = "Elige tu nombre" +CreateYourToonHead = "Haz clic en las flechas de la \"cabeza\" para escoger diferentes animales." +MakeAToonClickForNextScreen = "Haz clic en la flecha de abajo para ir a la pantalla siguiente." +PickClothes = "¡Haz clic en las flechas para escoger prendas!" +PaintYourToon = "¡Haz clic en las flechas para pintar a tu dibu!" +MakeAToonYouCanGoBack = "¡También puedes volver para cambiar tu cuerpo!" +MakeAFunnyName = "¡Elige un nombre divertido para el dibu con el juego de los nombres!" +MustHaveAFirstOrLast1 = "Tu dibu debería tener un nombre o un apellido, ¿no crees?" +MustHaveAFirstOrLast2 = "¿No quieres que tu dibu tenga un nombre o un apellido?" +ApprovalForName1 = "¡Eso es, tu dibu se merece un gran nombre!" +ApprovalForName2 = "¡Los nombres de dibus son los mejores!" +MakeAToonLastStep = "¡Último paso antes de ir a Toontown!" +PickANameYouLike = "¡Escoge un nombre que te guste!" +TitleCheckBox = "Título" +FirstCheckBox = "Nombre" +LastCheckBox = "Apellido" +RandomButton = "Al azar" +ShuffleButton = "Mezclar" +NameShopSubmitButton = "Enviar" +TypeANameButton = "Escribe un nombre" +TypeAName = "¿No te gustan estos nombres?\nHaz clic aquí -->" +PickAName = "¡Prueba con el juego de los nombres!\nHaz clic aquí -->" +PickANameButton = "Juego de los nombres" +RejectNameText = "Ese nombre no está permitido. Inténtalo de nuevo." +WaitingForNameSubmission = "Se está enviando tu nombre..." + +# PetshopGUI.py +PetNameMaster = "PetNameMasterEnglish.txt" +PetshopUnknownName = "Nombre: ???" +PetshopDescGender = "Género:\t%s" +PetshopDescCost = "Precio:\t%s golosinas" +PetshopDescTrait = "Premios:\t%s" +PetshopDescStandard = "Estándar" +PetshopCancel = lCancel +PetshopSell = "Vender pez" +PetshopAdoptAPet = "Adoptar a un Dibuperrito" +PetshopReturnPet = "Devolver a tu Dibuperrito" +PetshopAdoptConfirm = "¿Adoptar una mascota %s por %d golosinas?" +PetshopGoBack = "Volver" +PetshopAdopt = "Adoptar" +PetshopReturnConfirm = "¿Devolver a tu %s?" +PetshopReturn = "Regresar" +PetshopChooserTitle = "PERRITOS DE HOY" +PetshopGoHomeText = '¿Quieres ir a tu hacienda a jugar con tu nuevo Dibuperrito?' + +# NameShop.py +NameShopNameMaster = "NameMaster_castillian.txt" +NameShopPay = "¡Suscríbete ya!" +NameShopPlay = "Prueba gratuita" +NameShopOnlyPaid = "Solo los usuarios abonados\npueden poner nombre a sus dibus.\nHasta que te suscribas,\ntu nombre será\n" +NameShopContinueSubmission = "Continuar envío" +NameShopChooseAnother = "Elegir otro nombre" +NameShopToonCouncil = "El Consejo Dibu\nrevisará tu\nnombre. " + \ + "La revisión puede\ntardar unos días.\nMientras esperas,\ntu nombre será\n " +PleaseTypeName = "Escribe tu nombre:" +AllNewNames = "Todos los nombres nuevos\ndeben ser aprobados\npor el Consejo Dibu." +NameMessages = "Sé creativo y recuerda:\nningún nombre relacionado con Disney." +NameShopNameRejected = "El nombre que \nenviaste fue \nrechazado." +NameShopNameAccepted = "¡Felicitaciones!\nEl nombre que \nenviaste fue \naceptado." +NoPunctuation = "¡No puedes usar signos de puntuación en tu nombre!" +PeriodOnlyAfterLetter = "Tu nombre puede incluir un punto, pero solo después de una letra." +ApostropheOnlyAfterLetter = "Tu nombre puede incluir un apóstrofo, pero solo después de una letra." +NoNumbersInTheMiddle = "Es posible que los números no aparezcan si están en medio de una palabra." +ThreeWordsOrLess = "Tu nombre debe tener un máximo de tres palabras." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "pato donald", + "patodonald", + "pluto", + "goofy", + ) +NumToColor = ['Blanco', 'Melocotón', 'Rojo brillante', 'Rojo', 'Castaño', + 'Siena', 'Café', 'Café claro', 'Coral', 'Naranja', + 'Amarillo', 'Crema', 'Topacio', 'Lima', 'Verde mar', + 'Verde', 'Azul claro', 'Aguamarina', 'Azul', + 'Hierba', 'Azul marino', 'Azul pizarra', 'Morado', + 'Lavanda', 'Rosa', 'Ciruela', 'Negro'] +AnimalToSpecies = { + 'dog' : 'Perro', + 'cat' : 'Gato', + 'mouse' : 'Ratón', + 'horse' : 'Caballo', + 'rabbit' : 'Conejo', + 'duck' : 'Pato', + 'monkey' : 'Mono', + 'bear' : 'Oso', + 'pig' : 'Cerdo' + } +NameTooLong = "Ese nombre es demasiado largo. Inténtalo de nuevo." +ToonAlreadyExists = "¡Ya tienes un dibu llamado %s!" +NameAlreadyInUse = "¡Ese nombre ya está seleccionado!" +EmptyNameError = "Primero debes introducir un nombre." +NameError = "Lo siento. Ese nombre no sirve." + +# NameCheck.py +NCTooShort = 'Ese nombre es demasiado corto.' +NCNoDigits = 'Tu nombre no puede contener números.' +NCNeedLetters = 'Todas las palabras de tu nombre deben contener letras.' +NCNeedVowels = 'Todas las palabras de tu nombre deben contener vocales.' +NCAllCaps = 'Tu nombre no puede estar por completo en mayúsculas.' +NCMixedCase = 'Ese nombre tiene demasiadas mayúsculas.' +NCBadCharacter = "Tu nombre no puede contener el caracter '%s'" +NCGeneric = 'Lo siento, ese nombre no sirve.' +NCTooManyWords = 'Tu nombre no puede tener más de cuatro palabras.' +NCDashUsage = ("Solo puedes usar los guiones para unir dos palabras " + "(como en \"Bu-bu\").") +NCCommaEdge = "Tu nombre no puede comenzar ni terminar con una coma." +NCCommaAfterWord = "No puedes comenzar una palabra con una coma." +NCCommaUsage = ('Ese nombre no emplea las comas correctamente. Las comas deben ' + 'unir dos palabras, como en el nombre \"Dr. Pato, cirujano\"". ' + 'Además, las comas deben ir seguidas por un espacio.') +NCPeriodUsage = ('Ese nombre no emplea los puntos correctamente. Sólo se permiten ' + 'los puntos en palabras como \"Sr.\", \"Sra.\", \"J.T.\", etc.') +NCApostrophes = 'Ese nombre tiene demasiados apóstrofos.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+" dibu: ¡"+TheCogs+" tomaron el control de uno de los edificios que recuperaste!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = '¿Necesitas más tiempo para pensarlo?' +STOREOWNER_GOODBYE = '¡Hasta luego!' +STOREOWNER_NEEDJELLYBEANS = 'Tienes que subir al tranvía para conseguir golosinas.' +STOREOWNER_GREETING = 'Elige lo que quieras comprar.' +STOREOWNER_BROWSING = 'Puedes mirar pero para comprar necesitas un boleto de ropa.' +STOREOWNER_NOCLOTHINGTICKET = 'Para comprar prendas necesitas un boleto de ropa.' + +STOREOWNER_NOFISH = 'Vuelve aquí para vender peces a la tienda de animales a cambio de golosinas.' +STOREOWNER_THANKSFISH = '¡Gracias! A la tienda de animales le van a encantar. ¡Adiós!' +STOREOWNER_THANKSFISH_PETSHOP = "¡Qué especímenes tan interesantes! Gracias." +STOREOWNER_PETRETURNED = "No te preocupes. Encontraremos un hogar lindo para tu Dibuperrito." +STOREOWNER_PETADOPTED = "¡Felicidades por tu nuevo Dibuperrito! Puedes jugar con él en tu hacienda." +STOREOWNER_PETCANCELED = "Recuerda, si ves un Dibuperrito que te guste, ¡adóptalo antes de que lo haga otro!" + +STOREOWNER_NOROOM = "Mmm... Tienes que tener mas sitio en tu clóset antes de comprar mas ropa.\n" +STOREOWNER_CONFIRM_LOSS = "Tu closet esta lleno. Vas a perder la ropa que estabas usando." +STOREOWNER_OK = "Muy bien" +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "¡Uau! Conseguiste %s de %s peces. ¡Te mereces un trofeo y una subida del risómetro!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": ¡¡Hay una invasión de bots!!" +SuitInvasionBegin2 = lToonHQ+": ¡¡Los %s tomaron Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": ¡¡¡La invasión de %s terminó!!!" +SuitInvasionEnd2 = lToonHQ+": ¡¡¡Los dibus volvieron a salvarnos!!!" +SuitInvasionUpdate1 = lToonHQ+": ¡¡¡La invasión de bots consta ahora de %s bots!!!" +SuitInvasionUpdate2 = lToonHQ+": ¡¡¡Debemos derrotar a esos %s!!!" +SuitInvasionBulletin1 = lToonHQ+": ¡¡¡Se está produciendo una invasión de bots!!!" +SuitInvasionBulletin2 = lToonHQ+": ¡¡Los %s tomaron Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Pelotón de dibus" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "¡Toontown tiene un nuevo habitante! ¿Tienes alguna broma de sobra?" +QuestScriptTutorialMickey_2 = "¡Claro, %s!" +QuestScriptTutorialMickey_3 = "Tato Tutorial te explicará todo sobre los bots.\a¡Tengo que irme!" +QuestScriptTutorialMickey_4 = "¡Ven aquí! Usa las flechas del teclado para moverte." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "¡Toontown tiene un nuevo habitante! ¿Tienes alguna broma de sobra?" +QuestScriptTutorialMinnie_2 = "¡Claro, %s!" +QuestScriptTutorialMinnie_3 = "Tato Tutorial te explicará todo sobre los bots.\a¡Tengo que irme!" + +QuestScript101_1 = "Esos son los BOTS. Son robots que intentan tomar el control de Toontown." +QuestScript101_2 = "Hay muchos tipos distintos de BOTS, que..." +QuestScript101_3 = "...convierten los alegres edificios de los dibus..." +QuestScript101_4 = "...¡en horribles edificios bot!" +QuestScript101_5 = "¡Pero los BOTS no soportan las bromas!" +QuestScript101_6 = "Una buena broma acaba con ellos." +QuestScript101_7 = "Hay un montón de bromas, pero puedes empezar con estas." +QuestScript101_8 = "¡Oh! ¡También necesitas un risómetro!" +QuestScript101_9 = "Si tu risómetro disminuye demasiado, te pondrás triste." +QuestScript101_10 = "¡Cuanto más contento está un dibu, más sano está!" +QuestScript101_11 = "¡OH, NO! ¡Hay un BOT fuera de la tienda!" +QuestScript101_12 = "¡AYÚDAME, POR FAVOR! ¡Derrota a ese BOT!" +QuestScript101_13 = "¡Aquí tienes tu primera dibutarea!" +QuestScript101_14 = "¡Deprisa! ¡Derrota a ese secuaz!" + +QuestScript110_1 = "Gracias por derrotar a ese bot. Te daré un dibucuaderno..." +QuestScript110_2 = "Es un cuaderno lleno de cosas lindas." +QuestScript110_3 = "Ábrelo y te iré enseñando cosas." +QuestScript110_4 = "El mapa te muestra dónde estuviste." +QuestScript110_5 = "Pasa la página para ver tus bromas..." +QuestScript110_6 = "¡Uy! ¡No tienes bromas! Te voy a asignar una tarea." +QuestScript110_7 = "Pasa la página para ver las tareas." +QuestScript110_8 = "Para comprar bromas tienes que subirte al tranvía y conseguir golosinas." +QuestScript110_9 = "Para subirte al tranvía, sal por la puerta que hay detrás de mí y dirígete al dibuparque." +QuestScript110_10 = "Ahora, cierra el dibucuaderno y busca el tranvía." +QuestScript110_11 = "Vuelve al cuartel general cuando termines. ¡Adiós!" + +QuestScriptTutorialBlocker_1 = "¡Eh, hola!" +QuestScriptTutorialBlocker_2 = "¿Hola?" +QuestScriptTutorialBlocker_3 = "¡Oh! ¡No sabes cómo se usa SpeedChat!" +QuestScriptTutorialBlocker_4 = "Haz clic en el botón para decir algo." +QuestScriptTutorialBlocker_5 = "¡Muy bien!\aEn el sitio al que vas hay muchos dibus con los que puedes hablar." +QuestScriptTutorialBlocker_6 = "Si quieres charlar con tus amigos mediante el teclado, tienes que usar otro botón." +QuestScriptTutorialBlocker_7 = "Es el botón \"Charla\". Para poder usarlo tienes que ser ciudadano de Toontown." +QuestScriptTutorialBlocker_8 = "¡Buena suerte! ¡Hasta luego!" + +""" +GagShopTut + +You will also earn the ability to use other types of gags. + +""" + +QuestScriptGagShop_1 = "¡Bienvenido a la tienda de bromas!" +QuestScriptGagShop_1a = "Aquí es donde vienen los dibus a comprar bromas para usar contra los bots." +#QuestScriptGagShop_2 = "En este bote se muestran las golosinas que tienes." +#QuestScriptGagShop_3 = "Para comprar una broma, haz clic sobre el botón de una broma. ¡Prueba ahora!" +QuestScriptGagShop_3 = "Para comprar bromas, haz clic sobre los botones de bromas. ¡Intenta conseguir algunas ahora!" +QuestScriptGagShop_4 = "¡Bien! Puedes usar estas bromas en las batallas contra los bots." +QuestScriptGagShop_5 = "Echa un vistazo a las bromas avanzadas de lanzamiento y chorro..." +QuestScriptGagShop_6 = "Cuando termines de comprar bromas, haz clic en este botón para regresar al dibuparque." +QuestScriptGagShop_7 = "Normalmente, puedes utilizar este botón para jugar otro juego del tranvía..." +QuestScriptGagShop_8 = "...pero ahora no hay tiempo para otro juego. ¡Te necesitan en el cuartel general!" + +QuestScript120_1 = "¡Qué bien, encontraste el tranvía!\aPor cierto, ¿conoces ya a Billetón el banquero?\aEs bastante goloso.\a¿Por qué no te presentas llevándole este chocolate como regalo?" +QuestScript120_2 = "Billetón el banquero está en el Banco de Toontown." + +QuestScript121_1 = "Mmm, gracias por el chocolate.\aOye, si me ayudas, te daré una recompensa.\a"+TheCogs+" robaron las llaves de mi caja fuerte. Derrota a los bots hasta recuperar la llave robada.\aCuando encuentres la llave, tráemela." + +QuestScript130_1 = "¡Que bien, encontraste el tranvía!\aPor cierto, hoy recibí un paquete para Pedro el maestro.\aDeben de ser las nuevas tizas que encargó.\a¿Puedes llevárselas?\aEstá en el colegio." + +QuestScript131_1 = "Oh, gracias por las tizas.\a¡¿Qué?!\a"+TheCogs+" me robaron la pizarra. Véncelos y recupera mi pizarra.\aCuando la encuentres, tráemela." + +QuestScript140_1 = "¡Que bien, encontraste el tranvía!\aPor cierto, mi amigo Leopoldo es todo un devorador de libros.\aLa última vez que estuve en "+lDonaldsDock+" traje este libro para él.\a¿Podrías llevárselo? Suele estar en la biblioteca." + +QuestScript141_1 = "Oh, sí, con este libro casi completaré mi colección.\aDéjame ver...\aEy...\a¿Dónde dejé las gafas?\aLas tenía justo antes de que los bots ocupasen mi edificio.\aDerrótales y recupera mis gafas.\aCuando las encuentres, tráemelas y te daré una recompensa." + +QuestScript145_1 = "¡Veo que no tuviste problemas con el tranvía!\aEscucha, los bots nos robaron el borrador de la pizarra.\aSal a la calle y lucha contra los bots hasta que recuperes el borrador.\aPara llegar a las calles, atraviesa uno de los túneles del siguiente modo:" +QuestScript145_2 = "Cuando encuentres nuestro borrador, tráelo aquí.\aNo olvides que, si necesitas bromas, tienes que subir al tranvía.\aAdemás, si necesitas recuperar puntos de risa, consigue conos de helado en el dibuparque." + +QuestScript150_1 = "Oh... ¡Es posible que la tarea siguiente sea demasiado difícil para que la hagas solo!" +QuestScript150_2 = "Para hacerte amigo de alguien, busca a otro jugador y pulsa el botón Amigo nuevo." +QuestScript150_3 = "Cuando tengas un amigo nuevo, vuelve aquí." +QuestScript150_4 = "¡Algunas tareas son demasiado difíciles para hacerlas solo!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignórame" + +SellbotBossName = "VIP mayor." +CashbotBossName = "C. F. O." +LawbotBossName = "Juez" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "Los declaro ascendidos a %s hechos y derechos. ¡Felicitaciones!" +BossCogDoobersAway = { 's' : "¡Adiós! ¡Y a cerrar ventas!" } +BossCogWelcomeToons = "¡Bienvenidos, bots nuevos!" +BossCogPromoteToons = "Los declaro ascendidos a %s hechos y derechos. Felicit--" +CagedToonInterruptBoss = "¡Eh! ¡Oye! ¡Oye, aquí!" +CagedToonRescueQuery = "¡Eh, dibus! ¿Vinieron a rescatarme?" +BossCogDiscoverToons = "¿Cómo? ¡Dibus disfrazados!" +BossCogAttackToons = "¡Al ataque!" +CagedToonDrop = [ + "¡Muy bien! ¡Casi está destruido!", + "¡Hay que seguirlo! ¡Se escapa!", + "¡Genial!", + "¡Así, así! ¡Ya casi está!", + ] +CagedToonPrepareBattleTwo = "¡Cuidado, se escapa!\aAyuda: ¡Hay que subir y detenerlo!" +CagedToonPrepareBattleThree = "¡Yupii, ya me estoy viendo libre!\aAhora ataca al bot VIP directamente.\aTengo aquí un montón de tartas que puedes lanzarle.\aSalta y toca la base de la jaula para que te las dé.\aPulsa la tecla Borrar para lanzar las tartas una vez las tengas." +BossBattleNeedMorePies = "¡Necesitas más tartas!" +BossBattleHowToGetPies = "Salta para tocar la jaula y conseguir tartas." +BossBattleHowToThrowPies = "Pulsa la tecla Borrar para lanzar las tartas!" +CagedToonYippee = "¡Hurra!" +CagedToonThankYou = "¡Por fin libre!\a¡Muchísimas gracias por tu ayuda!\aEstoy en deuda contigo.\aSi alguna vez necesitas que te eche una mano luchando, no dudes en llamarme con el botón SOS." +CagedToonPromotion = "\a¡Eh! Ese bot VIP se olvidó los documentos de tu ascenso.\aLos archivaré a la salida para que no te quedes sin el ascenso." +CagedToonLastPromotion = "\¡Bien! Alcanzaste el nivel %s de traje bot.\aEs el máximo en la escala de ascensos bot.\aYa no puedes seguir mejorando el traje, pero sí puedes seguir rescatando dibus." +CagedToonHPBoost = "\aHas rescatado a un montón de dibus de este cuartel general.\aEl Consejo Dibu decidió darte otro punto de risa. ¡Felicitaciones!" +CagedToonMaxed = "\aVeo que tienes un traje bot nivel %s. Estamos muy impresionados.\aEn nombre del Consejo Dibu, te damos las gracias por regresar para rescatar más dibus." +CagedToonGoodbye = "¡Nos vemos!" + + +CagedToonBattleThree = { + 10: "¡Qué salto, %(toon)s! Aquí tienes las tartas.", + 11: "¡Hola, %(toon)s! Ahí van las tartas.", + 12: "¡Oye, %(toon)s! Mira cuántas tartas que tienes ahora.", + + 20: "¡Oye, %(toon)s! Salta a la jaula que te doy unas tartitas para que las tires.", + 21: "¡Hola, %(toon)s! Dale a la tecla Ctrl para saltar y tocar la jaula.", + + 100: "Pulsa la tecla Borrar para lanzar las tartas.", + 101: "El contador azul indica a qué altura llegará la tarta.", + 102: "Primero, intenta colarle una tarta dentro del mecanismo de las piernas para bloquearlo.", + 103: "Espera a que se abra la puerta y lanza la tarta por el hueco.", + 104: "Cuando esté mareado, túmbalo dándole en la cara o el pecho.", + 105: "Si la tarta salpica en colores, es que el tiro fue perfecto.", + 106: "Si le das a un dibu con una tarta, recibirá un punto de risa.", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "Ya está bien. ¡Me harté de esos dibus tan molestos!" +CashbotBossOuttaHere = "¡Tengo que tomar un tren!" +ResistanceToonName = "Mata Hairy" +ResistanceToonCongratulations = "¡Lo lograste! ¡Felicidades!\a¡Tu ayuda mejorará la Resistencia!\aToma esta frase especial que puedes usar en un punto difícil:\a%s\aCuando la digas, %s.\aPero sólo puedes usarla una vez,´¡así que elige bien el momento!" +ResistanceToonToonupInstructions = "todos los dibus que estén cerca ganarán %s puntos de risa" +ResistanceToonToonupAllInstructions = "todos los dibus que estén cerca ganarán el máximo de puntos de risa" +ResistanceToonMoneyInstructions = "todos los dibus que estén cerca ganarán %s golosinas" +ResistanceToonMoneyAllInstructions = "todos los dibus que estén cerca llenarán sus tarros de golosinas" +ResistanceToonRestockInstructions = "todos los dibus que estén cerca se reabastecerán de bromas \"%s\"" +ResistanceToonRestockAllInstructions = "todos los dibus que estén cerca reabastecerán todas sus bromas" + +ResistanceToonLastPromotion = "\a¡Uau, alcanzaste el nivel %s con tu traje bot!\aLos bots no pueden ascender más alto.\aYa no puedes mejorar más tu traje bot ¡pero puedes seguir trabajando para la Resistencia!" +ResistanceToonHPBoost = "\aHas trabajado mucho para la Resistencia.\aEl Consejo Dibu decidió darte otro punto de risa. ¡Felicidades!" +ResistanceToonMaxed = "\aVeo que tienes un traje bot de nivel %s. ¡Impresionante!\aEn nombre del Consejo Dibu, ¡gracias por volver a rescatar más dibus!" + +CashbotBossCogAttack = "¡¡¡A ellos!!!" +ResistanceToonWelcome = "¡Lo lograste! ¡Sígueme hasta la cámara principal antes de que nos encuentre el director financiero!" +ResistanceToonTooLate = "¡Rayos!¡Es demasiado tarde!" +CashbotBossDiscoverToons1 = "¡A-JÁ!" +CashbotBossDiscoverToons2 = "¡Ya me parecía que aquí olía a dibu! ¡Impostores!" +ResistanceToonKeepHimBusy = "¡Mantenlo ocupado! ¡Voy a preparar una trampa!" +ResistanceToonWatchThis = "¡Cuidado con esto!" +CashbotBossGetAwayFromThat = "¡Eh! ¡Aléjate de eso!" +ResistanceToonCraneInstructions1 = "Controla un imán subiendo a un podio." +ResistanceToonCraneInstructions2 = "Usa las teclas de flecha para mover la grúa y pulsa la tecla Ctrl para tomar un objeto." +ResistanceToonCraneInstructions3 = "Toma una caja fuerte con un imán y haz que se caiga el casco de seguridad del director financiero." +ResistanceToonCraneInstructions4 = "¡Una vez quitado el casco, toma un matón desactivado y dále en la cabeza!" +ResistanceToonGetaway = "¡Uff! ¡Tengo que correr!" +CashbotCraneLeave = "Salir de la grúa" +CashbotCraneAdvice = "Usa las teclas de flecha para mover la grúa." +CashbotMagnetAdvice = "Mantén pulsada la tecla Ctrl para agarrar cosas." +CashbotCraneLeaving = "Saliendo de la grúa..." + +MintElevatorRejectMessage = "No puedes entrar a las fabrica de monedas hasta haber completado tu traje bot %s." +BossElevatorRejectMessage = "No puedes subir a este ascensor hasta que no te merezcas un ascenso." +NotYetAvailable = "Este ascensor todavía no está disponible." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Mueble" +PaintingTypeName = "Cuadro" +ClothingTypeName = "Ropa" +ChatTypeName = "Frase de SpeedChat" +EmoteTypeName = "Clases de teatro" +BeanTypeName = "Golosinas" +PoleTypeName = "Caña de pescar" +WindowViewTypeName = "Vista desde la ventana" +PetTrickTypeName = "Entrenamiento de Dibuperrito" +GardenTypeName = "Suministros para el jardín" +RentalTypeName = "Objeto de alquiler" +GardenStarterTypeName = "Kit de jardinería" +NametagTypeName = "Etiqueta para el nombre" + +# Make sure numbers match up to CatalogItemTypes.py +CatalogItemTypeNames = { + 0 : "INVALID_ITEM", + 1 : FurnitureTypeName, + 2 : ChatTypeName, + 3 : ClothingTypeName, + 4 : EmoteTypeName, + 5 : "WALLPAPER_ITEM", + 6 : "WindowViewTypeName", + 7 : "FLOORING_ITEM", + 8 : "MOULDING_ITEM", + 9 : "WAINSCOTING_ITEM", + 10 : PoleTypeName, + 11: PetTrickTypeName, + 12: BeanTypeName, + 13: GardenTypeName, + 14: RentalTypeName, + 15: GardenStarterTypeName, + 16: NametagTypeName, + 17: "TOON_STATUE_ITEM", + 18: "ANIMATED_FURNITURE_ITEM", +} + + +# Make sure this is in sync with ToonDNA.ShirtStyles +ShirtStylesDescriptions = { + # ------------------------------------------------------------------------- + # Boy styles + # ------------------------------------------------------------------------- + 'bss1' : "solid", + 'bss2' : "single stripe", + 'bss3' : "collar", + 'bss4' : "double stripe", + 'bss5' : "multiple stripes", + 'bss6' : "collar w/ pocket", + 'bss7' : "hawaiian", + 'bss8' : "collar w/ 2 pockets", + 'bss9' : "bowling shirt", + 'bss10' : "vest (special)", + 'bss11' : "collar w/ ruffles", + 'bss12' : "soccer jersey (special)", + 'bss13' : "lightning bolt (special)", + 'bss14' : "jersey 19 (special)", + 'bss15' : "guayavera", + + # ------------------------------------------------------------------------- + # Girl styles + # ------------------------------------------------------------------------- + 'gss1' : "girl solid", + 'gss2' : "girl single stripe", + 'gss3' : "girl collar", + 'gss4' : "girl double stripes", + 'gss5' : "girl collar w/ pocket", + 'gss6' : "girl flower print", + 'gss7' : "girl flower trim (special)", + 'gss8' : "girl collar w/ 2 pockets", + 'gss9' : "girl denim vest (special)", + 'gss10' : "girl peasant", + 'gss11' : "girl peasant w/ mid stripe", + 'gss12' : "girl soccer jersey (special)", + 'gss13' : "girl hearts", + 'gss14' : "girl stars (special)", + 'gss15' : "girl flower", + + # ------------------------------------------------------------------------- + # Special Catalog-only shirts. + # ------------------------------------------------------------------------- + # yellow hooded - Series 1 + 'c_ss1' : "yellow hooded - Series 1", + 'c_ss2' : "yellow with palm tree - Series 1", + 'c_ss3' : "purple with stars - Series 2", + 'c_bss1' : "blue stripes (boys only) - Series 1", + 'c_bss2' : "orange (boys only) - Series 1", + 'c_bss3' : "lime green with stripe (boys only) - Series 2", + 'c_bss4' : "red kimono with checkerboard (boys only) - Series 2", + 'c_gss1' : "girl blue with yellow stripes (girls only) - Series 1", + 'c_gss2' : "girl pink and beige with flower (girls only) - Series 1", + 'c_gss3' : "girl Blue and gold with wavy stripes (girls only) - Series 2", + 'c_gss4' : "girl Blue and pink with bow (girls only) - Series 2", + 'c_gss5' : "girl Aqua kimono white stripe (girls only) - UNUSED", + 'c_ss4' : "Tie dye shirt (boys and girls) - Series 3", + 'c_ss5' : "light blue with blue and white stripe (boys only) - Series 3", + 'c_ss6' : "cowboy shirt 1 : Series 4", + 'c_ss7' : "cowboy shirt 2 : Series 4", + 'c_ss8' : "cowboy shirt 3 : Series 4", + 'c_ss9' : "cowboy shirt 4 : Series 4", + 'c_ss10' : "cowboy shirt 5 : Series 4", + 'c_ss11' : "cowboy shirt 6 : Series 4", + + # Special Holiday-themed shirts. + 'hw_ss1' : "Halloween ghost", + 'hw_ss2' : "Halloween pumpkin", + 'wh_ss1' : "Winter Holiday 1", + 'wh_ss2' : "Winter Holiday 2", + 'wh_ss3' : "Winter Holiday 3", + 'wh_ss4' : "Winter Holiday 4", + + 'vd_ss1' : "girl Valentines day, pink with red hearts (girls)", + 'vd_ss2' : "Valentines day, red with white hearts", + 'vd_ss3' : "Valentines day, white with winged hearts (boys)", + 'vd_ss4' : " Valentines day, pink with red flamed heart", + 'vd_ss5' : "2009 Valentines day, white with red cupid", + 'vd_ss6' : "2009 Valentines day, blue with green and red hearts", + 'sd_ss1' : "St Pat's Day, four leaf clover shirt", + 'sd_ss2' : "St Pat's Day, pot o gold shirt", + 'tc_ss1' : "T-Shirt Contest, Fishing Vest", + 'tc_ss2' : "T-Shirt Contest, Fish Bowl", + 'tc_ss3' : "T-Shirt Contest, Paw Print", + 'tc_ss4' : "T-Shirt Contest, Backpack", + 'tc_ss5' : "T-Shirt Contest, Lederhosen ", + 'tc_ss6' : "T-Shirt Contest, Watermelon ", + 'tc_ss7' : "T-Shirt Contest, Race Shirt", + 'j4_ss1' : "July 4th, Flag", + 'j4_ss2' : "July 4th, Fireworks", + 'c_ss12' : "Catalog series 7, Green w/ yellow buttons", + 'c_ss13' : "Catalog series 7, Purple w/ big flower", + + 'pj_ss1' : "Blue Banana Pajama shirt", + 'pj_ss2' : "Red Horn Pajama shirt", + 'pj_ss3' : "Purple Glasses Pajama shirt", + + # Special award clothes + 'sa_ss1' : "Striped Shirt", + 'sa_ss2' : "Fishing Shirt 1", + 'sa_ss3' : "Fishing Shirt 2", + 'sa_ss4' : "Gardening Shirt 1", + 'sa_ss5' : "Gardening Shirt 2", + 'sa_ss6' : "Party Shirt 1", + 'sa_ss7' : "Party Shirt 2", + 'sa_ss8' : "Racing Shirt 1", + 'sa_ss9' : "Racing Shirt 2", + 'sa_ss10' : "Summer Shirt 1", + 'sa_ss11' : "Summer Shirt 2", + + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + } + +# Make sure this is in sync with ToonDNA.BottomStyles +BottomStylesDescriptions = { + # name : [ bottomIdx, [bottomColorIdx, ...]] + # ------------------------------------------------------------------------- + # Boy styles (shorts) + # ------------------------------------------------------------------------- + 'bbs1' : "plain w/ pockets", + 'bbs2' : "belt", + 'bbs3' : "cargo", + 'bbs4' : "hawaiian", + 'bbs5' : "side stripes (special)", + 'bbs6' : "soccer shorts", + 'bbs7' : "side flames (special)", + 'bbs8' : "denim", + 'vd_bs1' : "Valentines shorts", + 'vd_bs2' : "Green with red heart", + 'vd_bs3' : "Blue denim with green and red heart", + + # Catalog only shorts + 'c_bs1' : "Orange with blue side stripes", + 'c_bs2' : "Blue with gold cuff stripes", + 'c_bs5' : 'Green stripes - series 7', + 'sd_bs1' : 'St. Pats leprechaun shorts', + 'pj_bs1' : 'Blue Banana Pajama pants', + 'pj_bs2' : 'Red Horn Pajama pants', + 'pj_bs3' : 'Purple Glasses Pajama pants', + 'wh_bs1' : 'Winter Holiday Shorts Style 1', + 'wh_bs2' : 'Winter Holiday Shorts Style 2', + 'wh_bs3' : 'Winter Holiday Shorts Style 3', + 'wh_bs4' : 'Winter Holiday Shorts Style 4', + + # ------------------------------------------------------------------------- + # Girl styles (shorts and skirts) + # ------------------------------------------------------------------------- + # skirts + # ------------------------------------------------------------------------- + 'gsk1' : 'solid', + 'gsk2' : 'polka dots (special)', + 'gsk3' : 'vertical stripes', + 'gsk4' : 'horizontal stripe', + 'gsk5' : 'flower print', + 'gsk6' : '2 pockets (special) ', + 'gsk7' : 'denim skirt', + + # shorts + # ------------------------------------------------------------------------- + 'gsh1' : 'plain w/ pockets', + 'gsh2' : 'flower', + 'gsh3' : 'denim shorts', + # Special catalog-only skirts and shorts. + 'c_gsk1' : 'blue skirt with tan border and button', + 'c_gsk2' : 'purple skirt with pink and ribbon', + 'c_gsk3' : 'teal skirt with yellow and star', + + # Valentines skirt + 'vd_gs1' : 'red skirt with hearts', + 'vd_gs2' : 'Pink flair skirt with polka hearts', + 'vd_gs3' : 'Blue denim skirt with green and red heart', + 'c_gsk4' : 'rainbow skirt - Series 3', + 'sd_gs1' : 'St. Pats day shorts', + 'c_gsk5' : 'Western skirts 1', + 'c_gsk6' : 'Western skirts 2', + # Western shorts + 'c_bs3' : 'Western shorts 1', + 'c_bs4' : 'Western shorts 2', + 'j4_bs1' : 'July 4th shorts', + 'j4_gs1' : 'July 4th Skirt', + 'c_gsk7' : 'Blue with flower - series 7', + 'pj_gs1' : 'Blue Banana Pajama pants', + 'pj_gs2' : 'Red Horn Pajama pants', + 'pj_gs3' : 'Purple Glasses Pajama pants', + 'wh_gsk1' : 'Winter Holiday Skirt Style 1', + 'wh_gsk2' : 'Winter Holiday Skirt Style 2', + 'wh_gsk3' : 'Winter Holiday Skirt Style 3', + 'wh_gsk4' : 'Winter Holiday Skirt Style 4', + + 'sa_bs1' : "Fishing Shorts", + 'sa_bs2' : "Gardening Shorts", + 'sa_bs3' : "Party Shorts", + 'sa_bs4' : "Racing Shorts", + 'sa_bs5' : "Summer Shorts", + 'sa_gs1' : "Fishing Skirt", + 'sa_gs2' : "Gardening Skirt", + 'sa_gs3' : "Party Skirt", + 'sa_gs4' : "Racing Skirt", + 'sa_gs5' : "Summer Skirt", + } + +AwardMgrBoy = "boy" +AwardMgrGirl = "girl" +AwardMgrUnisex = "unisex" +AwardMgrShorts = "shorts" +AwardMgrSkirt = "skirt" +AwardMgrShirt = "shirt" + +# Special Event Strings to display in mailbox screen +SpecialEventMailboxStrings = { + 1 : "A special item from the Toon council", + 2 : "Prize for Melville's Fishing Tournament", + 3 : "Prize for Billy Bud's Fishing Tournament", + } + +# Rental items +RentalHours = "Horas" +RentalOf = "De" +RentalCannon = "¡Cañones!" +RentalTime = "Horas de" + +EstateCannonGameEnd = "The Cannon Game rental is over." +GameTableRentalEnd = "The Game Table rental is over." + +MessageConfirmRent = "¿Iniciar alquiler? Cancela y el alquiler se pospondrá para más tarde" +MessageConfirmGarden = "¿Seguro que quieres empezar un jardín?" + +#nametag Names +NametagPaid = "Etiqueta para el nombre del ciudadano" +NametagAction = "Etiqueta para el nombre de la acción" +NametagFrilly = "Etiqueta para el nombre del adorno" + +FurnitureYourOldCloset = "tu clóset antiguo" +FurnitureYourOldBank = "tu alcancía antigua" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py +FurnitureNames = { + 100 : "Sillón", + 105 : "Sillón", + 110 : "Silla", + 120 : "Silla de escritorio", + 130 : "Silla de troncos", + 140 : "Silla de langosta", + 145 : "Silla de chaleco salvavidas", + 150 : "Taburete de montar", + 160 : "Silla indígena", + 170 : "Silla de magdalena", + 200 : "Cama", + 205 : "Cama", + 210 : "Cama", + 220 : "Cama de bañera", + 230 : "Cama de hoja", + 240 : "Cama de barco", + 250 : "Hamaca de cactus", + 260 : "Cama de helado", + 270 : "Cama de Erin y el gato", + 300 : "Pianola", + 310 : "Órgano de iglesia", + 400 : "Chimenea", + 410 : "Chimenea", + 420 : "Chimenea redonda", + 430 : "Chimenea", + 440 : "Chimenea de manzana", + 450 : "Chimenea de Erin", + 460 : "Chimenea encendida de Erin", + 470 : "Chimenea encendida", + 480 : "Chimenea redonda encendida", + 490 : "Chimenea encendida", + 491 : "Chimenea encendida", + 492 : "Chimenea encendida manzana", + 500 : "Clóset", + 502 : "Clóset con 15 prendas", + 504 : "Clóset con 20 prendas", + 506 : "Clóset con 25 prendas", + 510 : "Clóset", + 512 : "Clóset con 15 prendas", + 514 : "Clóset con 20 prendas", + 516 : "Clóset con 25 prendas", + 600 : "Lámpara baja", + 610 : "Lámpara alta", + 620 : "Lámpara de mesa", + 625 : "Lámpara de mesa", + 630 : "Lámpara de Daisy", + 640 : "Lámpara de Daisy", + 650 : "Lámpara de medusa", + 660 : "Lámpara de medusa", + 670 : "Lámpara de vaquero", + 700 : "Butaca", + 705 : "Butaca", + 710 : "Sofá", + 715 : "Sofá", + 720 : "Sofá de heno", + 730 : "Sofá de tarta de fruta", + 800 : "Escritorio", + 810 : "Escritorio de troncos", + 900 : "Paragüero", + 910 : "Perchero", + 920 : "Cubo de basura", + 930 : "Taburete rojo", + 940 : "Taburete amarillo", + 950 : "Perchero", + 960 : "Perchero de barril", + 970 : "Cactus", + 980 : "Tipi", + 990 : "Ventilador de Julieta", + 1000 : "Alfombra grande", + 1010 : "Alfombra redonda", + 1015 : "Alfombra redonda", + 1020 : "Alfombra pequeña", + 1030 : "Felpudo de hoja", + 1100 : "Vitrina", + 1110 : "Vitrina", + 1120 : "Estantería alta", + 1130 : "Estantería baja", + 1140 : "Cómoda de copa de helado", + 1200 : "Mesita lateral", + 1210 : "Mesita", + 1215 : "Mesita", + 1220 : "Mesa de centro", + 1230 : "Mesa de centro", + 1240 : "Mesa de buceador", + 1250 : "Mesa de galleta", + 1260 : "Mesita de noche", + 1300 : "Alcancía para 1.000 golosinas", + 1310 : "Alcancía para 2.500 golosinas", + 1320 : "Alcancía para 5.000 golosinas", + 1330 : "Alcancía para 7.500 golosinas", + 1340 : "Alcancía para 10.000 golosinas", + 1399 : "Teléfono", + 1400 : "Dibu Cezanne", + 1410 : "Flores", + 1420 : "Mickey moderno", + 1430 : "Dibu Rembrandt", + 1440 : "Paisaje dibu", + 1441 : "Escena hípica", + 1442 : "Estrella dibu", + 1443 : "¿No es una tarta?", + 1450 : "Mickey y Minnie", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Televisión", + 1600 : "Jarrón pequeño", + 1610 : "Jarrón alto", + 1620 : "Jarrón pequeño", + 1630 : "Jarrón alto", + 1640 : "Jarrón pequeño", + 1650 : "Jarrón pequeño", + 1660 : "Jarrón de coral", + 1661 : "Jarrón de concha", + 1670 : "Jarrón de rosa", + 1680 : "Regadera de rosa", + 1700 : "Carrito de palomitas", + 1710 : "Mariquita", + 1720 : "Fuente", + 1725 : "Lavadora", + 1800 : "Pecera", + 1810 : "Pecera", + 1900 : "Pez espada", + 1910 : "Pez martillo", + 1920 : "Percha de cuernos", + 1930 : "Sombrero sencillo", + 1940 : "Sombrero adornado", + 1950 : "Atrapasueños", + 1960 : "Herradura", + 1970 : "Retrato de bisonte", + 2000 : "Columpios de caramelo", + 2010 : "Tobogán de tarta", + 3000 : "Bañera de banana split", + 10000 : "Calabaza pequeña", + 10010 : "Calabaza alargada", + 10020 : "Árbol de invierno", + 10030 : "Guirnalda de invierno" + } + +# CatalogClothingItem.py +ClothingArticleNames = ( + "Camisa", + "Camisa", + "Camisa", + "Pantalón corto", + "Pantalón corto", + "Falda", + "Pantalón corto", + ) + +ClothingTypeNames = { + 1400 : "Camisa de Mateo", + 1401 : "Camisa de Jessica", + 1402 : "Camisa de Marisa", + 1600 : "Traje trampa", + 1601 : "Traje sonido", + 1602 : "Traje cebo", + 1603 : "Traje trampa", + 1604 : "Traje sonido", + 1605 : "Traje cebo", + 1606 : "Traje trampa", + 1607 : "Traje sonido", + 1608 : "Traje cebo", + } + +# CatalogSurfaceItem.py +SurfaceNames = ( + "Papel de pared", + "Moldura", + "Suelo", + "Revestimiento de pared", + "Borde", + ) + +WallpaperNames = { + 1000 : "Pergamino", + 1100 : "Milán", + 1200 : "Marítimo", + 1300 : "Victoria", + 1400 : "Puerto Nuevo", + 1500 : "Pastoral", + 1600 : "Arlequín", + 1700 : "Luna", + 1800 : "Estrellas", + 1900 : "Flores", + 2000 : "Jardín de primavera", + 2100 : "Jardín ornamental", + 2200 : "Un día en las carreras", + 2300 : "Rugby", + 2400 : "Nube 9", + 2500 : "Parras", + 2600 : "Primavera", + 2700 : "Muñequitas japonesas", + 2800 : "Ramilletes", + 2900 : "Tiburón angelote", + 3000 : "Pompas", + 3100 : "Pompas", + 3200 : "Adelante pez", + 3300 : "Detente pez", + 3400 : "Caballito de mar", + 3500 : "Conchas marinas", + 3600 : "Mundo submarino", + 3700 : "Botas", + 3800 : "Cactus", + 3900 : "Sombrero de vaquero", + 10100 : "Gatos", + 10200 : "Murciélagos", + 11000 : "Copos de nieve", + 11100 : "Hoja de acebo", + 11200 : "Muñeco de nieve", + 13000 : "Trébol", + 13100 : "Trébol", + 13200 : "Arcoiris", + 13300 : "Trébol", + } + +FlooringNames = { + 1000 : "Madera", + 1010 : "Moqueta", + 1020 : "Baldosas romboides", + 1030 : "Baldosas romboides", + 1040 : "Hierba", + 1050 : "Ladrillos grises", + 1060 : "Ladrillos rojos", + 1070 : "Baldosas cuadradas", + 1080 : "Piedra", + 1090 : "Tarima", + 1100 : "Tierra", + 1110 : "Parqué", + 1120 : "Baldosas", + 1130 : "Colmena", + 1140 : "Agua", + 1150 : "Baldosas de playa", + 1160 : "Baldosas de playa", + 1170 : "Baldosas de playa", + 1180 : "Baldosas de playa", + 1190 : "Arena", + 10000 : "Cubito de hielo", + 10010 : "Iglú", + 11000 : "Trébol", + 11010 : "Trébol", + } + +MouldingNames = { + 1000 : "Nudos", + 1010 : "Pintada", + 1020 : "Dientes", + 1030 : "Flores", + 1040 : "Flores", + 1050 : "Mariquita", + } + +WainscotingNames = { + 1000 : "Pintado", + 1010 : "Paneles de madera", + 1020 : "Madera", + } + +# CatalogWindowItem.py +WindowViewNames = { + 10 : "Jardín grande", + 20 : "Jardín agreste", + 30 : "Jardín griego", + 40 : "Paisaje urbano", + 50 : "Oeste americano", + 60 : "Mundo submarino", + 70 : "Isla tropical", + 80 : "Noche estrellada", + 90 : "Piscina con toboganes", + 100 : "Paisaje polar", + 110 : "Tierras de labranza", + 120 : "Campamento indígena", + 130 : "Calle principal", + } + +# don't translate yet +NewCatalogNotify = "Llegaron artículos nuevos que puedes pedir llamando desde tu teléfono." +NewDeliveryNotify = "Te acaba de llegar un pedido al buzón." +CatalogNotifyFirstCatalog = "¡Te llegó el primer catálogo tolón-tolón! Lo puedes usar para hacer pedidos de artículos nuevos para ti o para la casa." +CatalogNotifyNewCatalog = "¡Te llegó el catálogo tolón-tolón nº #%s! Haz pedidos llamando desde tu teléfono." +CatalogNotifyNewCatalogNewDelivery = "Te acaba de llegar un pedido al buzón. También llegó el catálogo tolón-tolón nº #%s." +CatalogNotifyNewDelivery = "Te acaba de llegar un pedido al buzón." +CatalogNotifyNewCatalogOldDelivery = "Te llegó el catálogo tolón-tolón nº #%s, pero todavía tienes pedidos en el buzón esperando a que los recojas." +CatalogNotifyOldDelivery = "Todavía tienes pedidos en el buzón esperando a que los recojas." +CatalogNotifyInstructions = "Haz clic en el botón \"Ir a casa\" de la página del dibucuaderno donde aparece el plano y vete al teléfono de tu casa." +CatalogNewDeliveryButton = "¡Nueva\nentrega!" +CatalogNewCatalogButton = "¡Nuevo\ncatálogo\ntolón-tolón!" +CatalogSaleItem = "¡Oferta! " + +# don't translate yet +DistributedMailboxEmpty = "El buzón está vacío. Vuelve después de hacer pedidos por teléfono para recogerlos." +DistributedMailboxWaiting = "El buzón está vacío, pero el pedido que hiciste ya está de camino. Vuelve más tarde." +DistributedMailboxReady = "¡Te llegó el pedido!" +DistributedMailboxNotOwner = "Me temo que este no es tu buzón." +DistributedPhoneEmpty = "Puedes pedir artículos especiales para ti o para la casa desde cualquier teléfono. Cada tanto irán apareciendo artículos nuevos para pedir.\n\nAhora mismo no puedes pedir ningún artículo. Vuelve a intentarlo más tarde." + +# don't translate yet +Clarabelle = "Clarabel" +MailboxExitButton = "Cerrar buzón" +MailboxAcceptButton = "Recoger este artículo" +MailBoxDiscard = "Descartar este artículo" +MailboxAcceptInvite = "Aceptar esta invitación" +MailBoxRejectInvite = "Rechazar esta invitación" +MailBoxDiscardVerify = "¿Seguro que quieres descartar: %s?" +MailBoxRejectVerify = "Seguro que quieres rechazar: %s?" +MailboxOneItem = "Tienes 1 artículo en el buzón." +MailboxNumberOfItems = "Tienes %s artículos en el buzón." +MailboxGettingItem = "Recogiendo %s del buzón." +MailboxGiftTag = "Regalo de: %s" +MailboxGiftTagAnonymous = "Anónimo" +MailboxItemNext = "Artículo\nsiguiente" +MailboxItemPrev = "Artículo\nanterior" +MailboxDiscard = "Descartar" +MailboxReject = "Rechazar" +MailboxLeave = "Conservar" +CatalogCurrency = "golosinas" +CatalogHangUp = "Colgar" +CatalogNew = "NUEVO" +CatalogBackorder = "PEDIDO PENDIENTE" +CatalogLoyalty = "ESPECIAL" +CatalogPagePrefix = "Página" +CatalogGreeting = "¡Hola! Gracias por llamar al catálogo tolón-tolón de Clarabel. ¿Qué deseas?" +CatalogGoodbyeList = ["¡Adiós!", + "Esperamos tu próxima llamada.", + "Gracias por llamarnos.", + "Muy bien. Hasta luego.", + "¡Adiós!", + ] +CatalogHelpText1 = "Pasa la página para ver los artículos que están a la venta." +CatalogSeriesLabel = "Serie %s" +CatalogGiftFor = "Comprar regalo para:" +CatalogGiftTo = "Para: %s" +CatalogGiftToggleOn = "Dejar de regalar" +CatalogGiftToggleOff = "Comprar regalos" +CatalogGiftToggleWait = "¡Intentándolo...!" +CatalogGiftToggleNoAck = "No disponible" +CatalogPurchaseItemAvailable = "¡Que disfrutes de tu compra! Puedes empezar a usar el artículo ahora mismo." +CatalogPurchaseGiftItemAvailable = "¡Excelente! %s puede empezar a utilizar tu regalo inmediatamente." +CatalogPurchaseItemOnOrder = "Muy bien. En breve enviaremos tu compra al buzón." +CatalogPurchaseGiftItemOnOrder = "¡Excelente! Tu regalo para %s será enviado a su buzón." +CatalogAnythingElse = "¿Quieres alguna otra cosa?" +CatalogPurchaseClosetFull = "Tienes el clóset lleno. Puedes comprar esta prenda, pero tendrás que deshacerte de otra del clóset para poder meterla cuando la recibas.\n\n¿Seguro que quieres comprarla?" +CatalogAcceptClosetFull = "Tienes el clóset lleno. Antes de recoger este artículo del buzón tendrás que abrir el clóset y deshacerte de una de las prendas para que quepa la nueva." +CatalogAcceptShirt = "Tienes puesta una camisa nueva. La que tenías antes está ahora en tu clóset." +CatalogAcceptShorts = "Tienes puesto un pantalón corto nuevo. Lo que tenías antes está ahora en tu clóset." +CatalogAcceptSkirt = "Tienes puesto una falda nueva. Lo que tenías antes está ahora en tu clóset." +CatalogAcceptPole = "Ahora, con tu caña nueva ya puedes pescar a lo grande." +CatalogAcceptPoleUnneeded = "La caña de pescar que tienes es mejor que ésta." +CatalogAcceptChat = "¡Tienes un nuevo SpeedChat!" +CatalogAcceptEmote = "¡Tienes una nueva Emoción!" +CatalogAcceptBeans = "¡Recibiste golosinas!" +CatalogAcceptRATBeans = "¡Llegó tu premio por reclutar dibus!" +CatalogAcceptNametag = "¡Llegó la nueva etiqueta de tu nombre!" +CatalogAcceptGarden = "¡Llegaron los suministros para el jardín!" +CatalogAcceptPet = "¡Tienes una nueva acrobacia de mascota!" +CatalogPurchaseHouseFull = "Tienes la casa llena. Puedes comprar este artículo, pero tendrás que deshacerte de otro de la casa para poder meterlo cuando lo recibas.\n\n¿Seguro que quieres comprarlo?" +CatalogAcceptHouseFull = "Tienes la casa llena. Antes de recoger este artículo del buzón tendrás que entrar en la casa y deshacerte de uno de los artículos para que quepa el nuevo." +CatalogAcceptInAttic = "El artículo recién adquirido está en el desván de tu casa. Para colocarlo en la casa, entra y dale al botón \"Cambiar mobiliario de sitio\"." +CatalogAcceptInAtticP = "Los artículos recién adquiridos están en el desván de tu casa. Para colocarlos en la casa, entra y dale al botón \"Cambiar mobiliario de sitio\"." +CatalogPurchaseMailboxFull = "¡Tienes el buzón lleno! No puedes comprar este artículo hasta que no saques algo del buzón para hacer espacio." +CatalogPurchaseGiftMailboxFull = "¡El buzón de %s está lleno! No puedes comprar este artículo." +CatalogPurchaseOnOrderListFull = "Ya pediste muchos artículos. No puedes pedir más hasta que no te lleguen los que están pendientes de entrega." +CatalogPurchaseGiftOnOrderListFull = "%s tiene demasiados pedidos de artículos." +CatalogPurchaseGeneralError = "No pudiste comprar el artículo debido a un error interno del juego: código de error %s." +CatalogPurchaseGiftGeneralError = "El artículo no se le pudo regalar a %(friend)s por un error interno del juego: código de error %(error)s." +CatalogPurchaseGiftNotAGift = "El artículo no se le pudo enviar a %s porque supondría una desventaja injusta." +CatalogPurchaseGiftWillNotFit = "El artículo no se le pudo enviar a %s porque no le cabe." +CatalogPurchaseGiftLimitReached = "El artículo no se le pudo enviar a %s porque ya lo tiene." +CatalogPurchaseGiftNotEnoughMoney = "El artículo no se le pudo enviar a %s porque no te lo puedes permitir." +CatalogAcceptGeneralError = "No se pudo sacar el artículo del buzón debido a un error interno del juego: código de error %s." +CatalogAcceptRoomError = "No tienes espacio para colocar esto. Tendrás que deshacerte de algo." +CatalogAcceptLimitError = "Ya tienes el máximo posible de artículos de este tipo. Tendrás que deshacerte de algo." +CatalogAcceptFitError = "¡Esto no te cabe! Dónalo a dibus necesitados." +CatalogAcceptInvalidError = "¡Este objeto está pasado de moda! Dónalo a dibus necesitados." + +MailboxOverflowButtonDicard = "Descartar" +MailboxOverflowButtonLeave = "Dejar" + +# don't translate yet +HDMoveFurnitureButton = "Cambiar\nmobiliario\nde sitio" +HDStopMoveFurnitureButton = "Cambio de\nmobiliario" +HDAtticPickerLabel = "En el desván" +HDInRoomPickerLabel = "En la habitación" +HDInTrashPickerLabel = "En la basura" +HDDeletePickerLabel = "¿Eliminar?" +HDInAtticLabel = "Desván" +HDInRoomLabel = "Habitación" +HDInTrashLabel = "Basura" +HDToAtticLabel = "Llevar\nal desván" +HDMoveLabel = "Mover" +HDRotateCWLabel = "Girar a la derecha" +HDRotateCCWLabel = "Girar a la izquierda" +HDReturnVerify = "¿Quieres llevar este artículo de nuevo al desván?" +HDReturnFromTrashVerify = "¿Quieres rescatar este artículo de la basura y llevarlo de nuevo al desván?" +HDDeleteItem = "Haz clic en OK para tirar este artículo a la basura o en Cancelar para no tirarlo." +HDNonDeletableItem = "No puedes deshacerte de este tipo de artículo." +HDNonDeletableBank = "No puedes deshacerte de la alcancía." +HDNonDeletableCloset = "No puedes deshacerte del clóset." +HDNonDeletablePhone = "No puedes deshacerte del teléfono." +HDNonDeletableNotOwner = "No puedes deshacerte de las cosas de %s." +HDHouseFull = "Tienes la casa llena. Antes de rescatar este artículo de la basura, tendrás que deshacerte de otra cosa de la casa o el desván." + +HDHelpDict = { + "DoneMoving" : "Acaba de decorar la habitación.", + "Attic" : "Muestra una lista de los objetos del desván. En el desván se guardan los objetos que no están en tu habitación.", + "Room" : "Muestra una lista de los objetos de tu habitación. Resulta práctico para encontrar objetos perdidos.", + "Trash" : "Muestra los objetos de la basura. Los objetos más antiguos desaparecen al cabo del tiempo o cuando la basura empieza a rebosar.", + "ZoomIn" : "Para ver la habitación desde más cerca.", + "ZoomOut" : "Para ver la habitación desde más lejos.", + "SendToAttic" : "Para guardar un mueble en el desván.", + "RotateLeft" : "Para girar a la izquierda.", + "RotateRight" : "Para girar a la derecha.", + "DeleteEnter" : "Para cambiar al modo de eliminar.", + "DeleteExit" : "Para salir del modo de eliminar.", + "FurnitureItemPanelDelete" : "Para tirar %s a la basura.", + "FurnitureItemPanelAttic" : "Para colocar %s en la habitación.", + "FurnitureItemPanelRoom" : "Para devolver %s al desván.", + "FurnitureItemPanelTrash" : "Para devolver %s al desván.", + } + + + +# don't translate yet +MessagePickerTitle = "Tienes demasiadas frases. Si quieres comprar \n\"%s\"\n debes elegir una y borrarla:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "¿Seguro que quieres borrar \"%s\" del menú de SpeedChat?" + + +# don't translate yet +CatalogBuyText = "Comprar" +CatalogRentText = "Alquilar" +CatalogGiftText = "Regalar" +CatalogOnOrderText = "Ya pedido" +CatalogPurchasedText = "Ya comprado" +CatalogGiftedText = "Recibido\nde regalo" +CatalogPurchasedGiftText = "Ya en\npropiedad" +CatalogMailboxFull = "No hay espacio" +CatalogNotAGift = "No es un regalo" +CatalogNoFit = "No\ncabe" +CatalogMembersOnly = "¡Solo\nmiembros!" +CatalogSndOnText = "Snd Sí" +CatalogSndOffText = "Snd No" + +CatalogPurchasedMaxText = "Máximo permitido\nya comprado" +CatalogVerifyPurchase = "¿Quieres comprar %(item)s por %(price)s golosinas?" +CatalogVerifyRent = "¿Alquilar %(item)s por %(price)s golosinas?" +CatalogVerifyGift = "¿Comprar %(item)s por %(price)s golosinas para hacerle un regalo a %(friend)s?" +CatalogOnlyOnePurchase = "Solo puedes tener uno de estos artículos a la vez. Si compras este, sustituirá a %(old)s.\n\n¿Seguro que quieres comprar %(item)s por %(price)s golosinas?" + +# don't translate yet +CatalogExitButtonText = "Colgar" +CatalogCurrentButtonText = "Ir a artículos actuales" +CatalogPastButtonText = "Ir a artículos antiguos" + +TutorialHQOfficerName = "Funcionario Enrique" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tato Tutorial", + 999 : "Dibu Sastre", + 1000 : lToonHQfull, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Billetón el banquero", + 2003 : "Pedro el maestro", + 2004 : "Calixta la modista", + 2005 : "Leopoldo el bibliotecario", + 2006 : "Dependiente Vicente", + 2011 : "Dependiente Vicenta", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Dependiente de la tienda de animales", + 2018 : "Duff..err..TIP Man", + # NPCPetClerks + 2013 : "Dependiente Papo", + 2014 : "Dependiente Pepo", + 2015 : "Dependiente Pino", + # NPCPartyPerson + 2016 : "Calabaza organizadora de fiestas", + 2017 : "Trini la organizadora de fiestas", + + # Silly Street + 2101 : "Bautista el dentista", + 2102 : "Lucía la policía", + 2103 : "Camaleón Atuéndez", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Fagucia Carasucia", + 2109 : "Burbujo Irujo", + 2110 : "Oscar Tel", + 2111 : "Agustín el bailarín", + 2112 : "Dr. Tomás", + 2113 : "El increíble Esnafro", + 2114 : "Chiquito Gallego", + 2115 : "Flexia Papírez", + 2116 : "Pepe Puños", + 2117 : "Facta Putre ", + 2118 : "Inocencio Santos", + 2119 : "Hilaria Jajá", + 2120 : "Profesor Nino", + 2121 : "Sra. Risita", + 2122 : "Orán Guto", + 2123 : "Marivi Guasona", + 2124 : "Bromi Stilla", + 2125 : "Morgan Dul", + 2126 : "Profesor Carcajada", + 2127 : "Mauro Peso", + 2128 : "Luis el Chiflado", + 2129 : "Carmelo Cotón", + 2130 : "Calambrita Ampérez", + 2131 : "Cosquilla Plumón", + 2132 : "Chucho Chúchez", + 2133 : "Dr. Rico", + 2134 : "Mónica Llada", + 2135 : "María Corremillas", + 2136 : "Dexter Nillo", + 2137 : "Mari Fe Liz", + 2138 : "Pepote Cobos", + 2139 : "Dani Dea", + 2140 : "Basilio Pescador", + + # Loopy Lane + 2201 : "Pepe el cartero", + 2202 : "Jocosa Risa", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Cholo Calandracas", + 2208 : "Luisillo Pegajosillo", + 2209 : "Chancho La Monda", + 2210 : "Pirulí", + 2211 : "Carca Ajada", + 2212 : "Roque Raro", + 2213 : "Pepi Bielas", + 2214 : "Pancho Quemancho", + 2215 : "Benito Latónez", + 2216 : "Noa Ynada", + 2217 : "Tiburcio Algas", + 2218 : "Mari Jo Cosa", + 2219 : "Chef Pánfilo", + 2220 : "Tancredo Cabezarroca", + 2221 : "Clovinia Dherente", + 2222 : "Corto Méchez", + 2223 : "Guasa Tomasa", + 2224 : "Sacha Muscado", + 2225 : "Jorobádez Pescador", + + # Punchline Place + 2301 : "Dr. Tronchaespinazo", + 2302 : "Profesor Cosquillas", + 2303 : "Enfermera Mondi", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Mullida Mullídez", + 2309 : "Desmoño Ruinez", + 2311 : "Paco Gotazo", + 2312 : "Dra. Sensible", + 2313 : "Lila Lamparón", + 2314 : "Disco Bolo", + 2315 : "Francisco Reoso", + 2316 : "Cindi Charachera", + 2318 : "Toni Chichón", + 2319 : "Zipi", + 2320 : "Alfredo Pastosi", + 2321 : "Fescor Pescador", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Dependiente Pipe", + 1002 : "Dependiente Pape", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Fifo Pretaporter", + # NPCFisherman + 1008 : "Dependiente de la tienda de animales", + # NPCPetClerks + 1009 : "Dependiente Guaguau", + 1010 : "Dependiente MiauMiau", + 1011 : "Dependiente Blop", + # NPCPartyPerson + 1012 : "Tremadal el Organizador de fiestas", + 1013 : "Parti la organizadora de fiestas", + + # Barnacle Blvd. + 1101 : "Beto Buque", + 1102 : "Capitán Doblón", + 1103 : "Raspo Espínez", + 1104 : "Dr. Rompecubiertas", + 1105 : "Almirante Garfio", + 1106 : "Sra. Almidónez", + 1107 : "Nemo Mancuerna", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Pepe Glubglub", + 1113 : "Chiqui Quillas", + 1114 : "Magdaleno Yapican", + 1115 : "Abogada Tentácula Calamar", + 1116 : "Pili Percebe", + 1117 : "Billy Yates", + 1118 : "Nelson Tintes", + 1121 : "Lisa Mástiles", + 1122 : "Titánico Iceberg", + 1123 : "Electra Anguílez", + 1124 : "Astillo Muéllez", + 1125 : "Tunanta Estribor", + 1126 : "Bartolo Pescador", + + # Seaweed Street + 1201 : "Perci Percebe", + 1202 : "Pasma Rote", + 1203 : "Ajab", + 1204 : "Claus Anclas", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Profesor Curasardinas", + 1210 : "Pong Peyeng", + 1211 : "Velo Dromo", + 1212 : "Pico Tazos", + 1213 : "Rémoro Tiburcio ", + 1214 : "Catalina Timoneles", + 1215 : "María del Mar Salada", + 1216 : "Carrete Cañas", + 1217 : "Marina Naval", + 1218 : "Paco Pacífico", + 1219 : "Alvar Cabeza de Playa", + 1220 : "Isabel Segunda", + 1221 : "Blasco Sido", + 1222 : "Alberto Abordaje", + 1223 : "Sepio Calamárez", + 1224 : "Emilia Anguila", + 1225 : "Gonzo Friegacubiertas", + 1226 : "Izo Velamen", + 1227 : "Coral Bisturí", + 1228 : "Cañero Pescador", + + # Lighthouse Lane + 1301 : "Pepa Sastre", + 1302 : "Isidoro Subemástiles", + 1303 : "Clodovico Cromañón", + 1304 : "Santiaguiña Nécorez", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Mar Océana", + 1310 : "Trucho Cañete", + 1311 : "Goyo Gorrotocho", + 1312 : "Quillo Quilla", + 1313 : "Gordi Gordios", + 1314 : "Jorge Rumbre", + 1315 : "Catedrático Áncora", + 1316 : "Canuta Canoas", + 1317 : "Juana Salvadora Gaviota", + 1318 : "Salva Vidas", + 1319 : "Quique Diqueseco", + 1320 : "Mario Almejo", + 1321 : "Dina Atraque", + 1322 : "Estibora Pópez", + 1323 : "Pericles Cabezabuque", + 1324 : "Concha Coquínez ", + 1325 : "Vaporeto Misisipi", + 1326 : "Jurela Besúguez", + 1327 : "Gabi Ota", + 1328 : "Carlitos Lenguado", + 1329 : "Flora Marínez", + 1330 : "Fredo Barbarrala", + 1331 : "Timón Bocanegra", + 1332 : "Samuel Pescador", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Adela Dita", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Dependiente Poli", + 3007 : "Dependiente Pili", + 3008 : "Giorgio Armiño", + # NPCFisherman + 3009 : "Dependiente de la tienda de animales", + # NPCPetClerks + 3010 : "Dependiente Tito", + 3011 : "Dependiente Tuto", + 3012 : "Dependiente Tato", + # NPCPartyPerson + 3013 : "Pedro el organizador de fiestas", + 3014 : "Pedra la organizadora de fiestas", + + # Walrus Way + 3101 : "Juanjo Escárchez", + 3102 : "Tía Ritona", + 3103 : "Pepe Tundra", + 3104 : "Geli da Pinto", + 3105 : "Pipe Pelado", + 3106 : "Fríguez Sabañón", + 3107 : "Maite Aterida", + 3108 : "Doroteo Escarcha", + 3109 : "Pati", + 3110 : "Lucas Friolero", + 3111 : "Kevin Kelvin", + 3112 : "Fredo Dedo", + 3113 : "Cris Térico", + 3114 : "Beto Tomba", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carlos Congelado", + 3120 : "Mito Mitón", + 3121 : "Voltio Ampérez", + 3122 : "Bebé Bob", + 3123 : "Ricardo Bofrigo", + 3124 : "Manfredo Carámbanez", + 3125 : "Tiritono Quelog", + 3126 : "Pescanovo Martínez", + 3127 : "Popoco Mecaigo", + 3128 : "Pipo Polar", + 3129 : "Dina Fríomiga", + 3130 : "Roberta", + 3131 : "Lorenzo Rascafría", + 3132 : "Cenicilla", + 3133 : "Dr. Congelaimagen", + 3134 : "Paco Gelado", + 3135 : "Empa Pada", + 3136 : "Felicia Simpática", + 3137 : "Kevin Ator", + 3138 : "Chef Sopacarámbano ", + 3139 : "Abuelita Polosur", + 3140 : "Lucilia Pescador", + + # Sleet Street + 3201 : "Tía Ártica", + 3202 : "Antonio Tiritonio", + 3203 : "Eugenio Criogenio", + 3204 : "Dra. Soplillos", + 3205 : "Perico Arenque", + 3206 : "Fernando Anchoa", + 3207 : "Dr. Bocamaraca", + 3208 : "Felipe el gruñón", + 3209 : "Garmillo Panterquircho", + 3210 : "Nutrio Cenutrio", + 3211 : "Pega Pegón", + 3212 : "Federico Gelado", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Pedro Glaciares", + 3218 : "Pepe Napiazul", + 3219 : "Tomás Bufandilla", + 3220 : "Estornio Atchís", + 3221 : "Inés Carcha", + 3222 : "Ventiscona Copogordo", + 3223 : "Macario Ajillo", + 3224 : "Madame Glacé", + 3225 : "Gregorio Sabañón ", + 3226 : "Papá Noés", + 3227 : "Solario Ráyez", + 3228 : "Escalo Frío", + 3229 : "Hernia Tarúguez", + 3230 : "Optimisto Peláez ", + 3231 : "Pedro Picahielo", + 3232 : "Alberto Pescador", + + # Polar Place + 3301 : "Parchesa Cachemira", + 3302 : "Conge Ladito", + 3303 : "Dr. Muñón", + 3304 : "Eduardo el Yeti", + 3305 : "Emilio Bilio", + 3306 : "Paula Iglulas", + # NPC Fisherman + 3307 : "Federica Pescador", + 3308 : "Espantajo Donaldo", + 3309 : "Piperín", + 3310 : "Profesor Copos", + 3311 : "Carla Ferrán", + 3312 : "Juan Marzo", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Besina Besón", + 3318 : "Juanito Cachemir", + 3319 : "Mateo Reo", + 3320 : "Luisa Brisa", + 3321 : "Pablo Hachazo", + 3322 : "Pili Broviejo", + 3323 : "Dallas Borealis", + 3324 : "Esteban Colibre", + 3325 : "Fiestona Casona", + 3326 : "Blanch", + 3327 : "Marco Milona", + 3328 : "Sandra Sabores", + 3329 : "Eusebio Talón", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Moli Melódica", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Dependiente Dina", + 4007 : "Dependiente Dino", + 4008 : "Modista Armonía", + # NPCFisherman + 4009 : "Dependiente de la tienda de animales", + # NPCPetClerks + 4010 : "Dependiente Cristian", + 4011 : "Dependiente Nacho", + 4012 : "Dependiente Romualda", + # NPCPartyPerson + 4013 : "Ignacio el organizador de fiestas", + 4014 : "Penélope la organizadora de fiestas", + + # Alto Ave. + 4101 : "Ropo Pompom", + 4102 : "Bibi", + 4103 : "Dr. Pavo Rotti", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Sordino Quena", + 4109 : "Carlos", + 4110 : "Metrónoma Diapasón", + 4111 : "Camilo Séptimo", + 4112 : "Fa", + 4113 : "Madame Modales", + 4114 : "Dum Dúmez", + 4115 : "Bárbara Sevilla", + 4116 : "Pizzicato", + 4117 : "Lina Mando", + 4118 : "Rapsodio Sonata", + 4119 : "Beto Ven", + 4120 : "Tara Reo", + 4121 : "Bemolio Sostenido", + 4122 : "Noa Ybemoles", + 4123 : "Cornetín Gaita", + 4124 : "Rifi Rafe", + 4125 : "Notas Peinao", + 4126 : "Tortillo Tenorio", + 4127 : "Vivaracho Vivaldi", + 4128 : "Plácido Lunes", + 4129 : "Fina Desafinada", + 4130 : "Ironio Máidez", + 4131 : "Abraham Zambomba", + 4132 : "Teresa Benicanta", + 4133 : "Impo Luto", + 4134 : "DJ Diborio", + 4135 : "Teresito Sopranillo", + 4136 : "Fusa Patidifusa", + 4137 : "Rafael Sostenido", + 4138 : "Octavo Seisillo", + 4139 : "Ada Gio", + 4140 : "Trémolo Torpe", + 4141 : "Juanito Pescador", + + # Baritone Blvd. + 4201 : "Tina Sonatina", + 4202 : "Barbo", + 4203 : "Chopo Chopín", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Lírica Tástrofe", + 4209 : "Sissy Samba", + 4211 : "José Carrerilla", + 4212 : "Filarmonio Opereto", + 4213 : "Ambulancio Raudo", + 4214 : "Arnesia Mifasol", + 4215 : "Corneto Claxon", + 4216 : "Amadeo Parche", + 4217 : "Juan Strauss", + 4218 : "Octava Pianola", + 4219 : "Trombón Charango", + 4220 : "Sartenio Batuta", + 4221 : "Pipo Madrigal", + 4222 : "Fulano de Tal", + 4223 : "Felisa Obelisco", + 4224 : "Jonás Clarinete", + 4225 : "Cuchi Cheo", + 4226 : "Paca Canora", + 4227 : "Betina Trompetilla", + 4228 : "Nabuco Donosor", + 4229 : "Melodia Pasón", + 4230 : "Rigo Letto", + 4231 : "Acordia Deona", + 4232 : "Fígaro Casamentero", + 4233 : "Arpo Marx", + 4234 : "Coro Vocález", + 4235 : "Lorenzo Pescador", + + # Tenor Terrace + 4301 : "Uki", + 4302 : "Juanola", + 4303 : "Leo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Felisa Felina", + 4309 : "Isidoro Tápiez", + 4310 : "Marta Falla", + 4311 : "Corneta Camarera", + 4312 : "Horacio Pianola", + 4313 : "Renato Enquequedamos", + 4314 : "Vilma Mascota", + 4315 : "Rola Roles", + 4316 : "Pachi Zapatillo", + 4317 : "Piero Pereta", + 4318 : "Bob Marlene", + 4319 : "Urraca Grájez", + 4320 : "Irene Fervescente", + 4321 : "Pepe Palmira", + 4322 : "Aitor Poloneso", + 4323 : "Ana Balada", + 4324 : "Elisa", + 4325 : "Banquero Ramón", + 4326 : "Morlana Chicharra", + 4327 : "Flautilla Hamelín", + 4328 : "Wagner", + 4329 : "Maruja Televenta", + 4330 : "Maestro Soniquete", + 4331 : "Celo Costelo", + 4332 : "Tato Timbal", + 4333 : "Chiflo Chiflete", + 4334 : "Maraco Marchoso", + 4335 : "Gualdo Pescador", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Dependiente Azucena", + 5006 : "Dependiente Jacinto", + 5007 : "Florinda Rosales", + # NPCFisherman + 5008 : "Dependiente de la tienda de animales", + # NPCPetClerks + 5009 : "Dependiente Botánica", + 5010 : "Dependiente Tomás Ablanda", + 5011 : "Dependiente Tomás Adura", + # NPCPartyPerson + 5012 : "Ramón el organizador de fiestas", + 5013 : "Felisa la organizadora de fiestas", + + # Elm Street + 5101 : "Pepe Pino", + 5102 : "Alca Chofa", + 5103 : "Federico Tilla", + 5104 : "Polillo Maripósez", + 5105 : "Comino Pérez Gil", + 5106 : "Patillo Siegacogotes", + 5107 : "Cartero Felipe", + 5108 : "Posadera Piti", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr. Zanahorio", + 5114 : "Marchito Mate", + 5115 : "Mimosa Nomeolvides", + 5116 : "Lucho Borrajo", + 5117 : "Pétalo", + 5118 : "Palomo Maíz", + 5119 : "Seto Podal", + 5120 : "Cardamomo", + 5121 : "Grosella Falsa", + 5122 : "Trufo Champiñón", + 5123 : "Tempranilla Móstez", + 5124 : "Hinojo Hinault", + 5125 : "Burbujo Chof", + 5126 : "Madre Selva", + 5127 : "Pili Polen", + 5128 : "Barba Coa", + 5129 : "Sabina Pescador", + + # Maple Street + 5201 : "Pulgarcillo", + 5202 : "Edel Weiss", + 5203 : "Campanilla", + 5204 : "Pipe Barroso", + 5205 : "León Mondadientes", + 5206 : "Cardo Borriquero", + 5207 : "Flor Chorro", + 5208 : "Lisa Buesa", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Al Cornoque", + 5214 : "Hortensia Comopincha", + 5215 : "Josefa Repera", + 5216 : "Luisillo Membrillo", + 5217 : "Aníbal Nomeolvides", + 5218 : "Rufo Segadora", + 5219 : "Sacha Cachas", + 5220 : "Lenti Juela", + 5221 : "Flamenca Rosa", + 5222 : "Belladona Jazmín", + 5223 : "Frido Quememojo", + 5224 : "Guido Castañapilonga", + 5225 : "Pipa Girasólez", + 5226 : "Rumio Cuentauvas", + 5227 : "Petunia Barricarroble", + 5228 : "Prior Primo Prelado", + 5229 : "Lirio Pescador", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Doña Hortensia", + 5306 : "Cartero don Lentejo", + 5307 : "Cris Antemo", + 5308 : "Juanita Pimentón", + 5309 : "Marga y Rita", + 5310 : "Narciso", + 5311 : "Doña Zanahoria", + 5312 : "Eugenio", + 5313 : "Don Silvestre", + 5314 : "Tía Petunia", + 5315 : "Tío Calabacín", + 5316 : "Tío Jacinto", + 5317 : "Doña Citronia", + 5318 : "Don Lirio", + 5319 : "Malva Rosa", + 5320 : "Doña Azucena", + 5321 : "Profesor Hiedra", + 5322 : "Rosa Pescador", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "Enrique Motores", + 8002 : "Ivón Carreras", + 8003 : "Victoria Repetidez", + 8004 : "Fabio Gas", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Belinda Traspuesta", + 9002 : "Bello Durmiente", + 9003 : "Plancho Rejas", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Dependiente Modorra", + 9009 : "Dependiente Modorro", + 9010 : "Almohado Edredón", + # NPCFisherman + 9011 : "Dependiente de la tienda de animales", + # NPCPetClerks + 9012 : "Dependiente Sasasueños", + 9013 : "Dependiente Sista Siesta", + 9014 : "Dependiente Sisto Siesto", + # NPCPartyPerson + 9015 : "Paco el organizador de fiestas", + 9016 : "Diamante la organizadora de fiestas", + + # Lullaby Lane + 9101 : "Vito", + 9102 : "Plúmbea Triz", + 9103 : "Cafeíno Paloseco ", + 9104 : "Copito de Nieve", + 9105 : "Profesor Bostezo", + 9106 : "Soporífero Indolente", + 9107 : "Acurruco Paloma", + 9108 : "Soño Liento", + 9109 : "Dafne Marmota", + 9110 : "Adela Duermevela", + 9111 : "Apagona Plómez", + 9112 : "Marqués de la Nana", + 9113 : "Jaime Cuco", + 9114 : "Máscara Pepínez", + 9115 : "Hiberno Cuandoquiero", + 9116 : "Mariano Cuentaovejas", + 9117 : "Madrugona Grogui", + 9118 : "Estrella Vespertina", + 9119 : "Tronco Sópez", + 9120 : "Lirona Frita", + 9121 : "Serena Vigilia", + 9122 : "Trasnocho Pocho", + 9123 : "Osito Pelúchez", + 9124 : "Vela Zascandil", + 9125 : "Dr. Vigíliez", + 9126 : "Pluma Oca", + 9127 : "Pili Piltra", + 9128 : "Juanjo Manitas", + 9129 : "Beltrán Puesto", + 9130 : "Siesto Buenasnóchez", + 9131 : "Letárgica Catorcehoras", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Pablo Pescador", + + # Pajama Place + 9201 : "Bernat", + 9202 : "Oniro", + 9203 : "Nat", + 9204 : "Clara de Luna", + 9205 : "Zen Ben", + 9206 : "Domi Lona", + 9207 : "Juana Pijama", + 9208 : "David Modorra", + 9209 : "Dr. Seda", + 9210 : "Maestro Marcos", + 9211 : "Amanecer", + 9212 : "Rayo de luna", + 9213 : "Ricardo Gallón", + 9214 : "Dr. Legañas", + 9215 : "Ripón", + 9216 : "Cato", + 9217 : "Linda Legal", + 9218 : "Matilda Vals", + 9219 : "La condesa", + 9220 : "Fabián Gruñón", + 9221 : "Zari", + 9222 : "Cowboy Jorge", + 9223 : "Ramón el Guasón", + 9224 : "Sandra Salamandra", + 9225 : "Martina Cocina", + 9226 : "Mostra Dor", + 9227 : "Teterín Soporín", + 9228 : "Sauce Susurros", + 9229 : "Rosa Pétalo", + 9230 : "Tex", + 9231 : "Harry Hamaca", + 9232 : "Luna Miel", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "Patricio Pescador", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Ayuntamiento", "el"), + 2514 : ("Banco de Toontown", "el"), + 2516 : ("Colegio de Toontown", "el"), + 2518 : ("Biblioteca de Toontown", "la"), + 2519 : ("Tienda de Bromas", "la"), + 2520 : ("Cuartel general", "el"), + 2521 : ("Tienda de Ropa de Toontown", "la"), + 2522 : ("Tienda de animales", "la"), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Clínica Dental Piñatasana", "la"), + 2602 : ("", ""), + 2603 : ("Mineros Carboneros", "los"), + 2604 : ("Limpiezas por Piezas", "las"), + 2605 : ("Fábrica de Carteles de Toontown", "la"), + 2606 : ("", ""), + 2607 : ("Habas Saltarinas", "las"), + 2610 : ("Dr. Tomás Todonte", "el"), + 2611 : ("", ""), + 2616 : ("Tienda de Disfraces Barbarrara", "la"), + 2617 : ("Acrobacias a Granel", "las"), + 2618 : ("Chistes Lepetitivos", "los"), + 2621 : ("Aviones de Papel", "los"), + 2624 : ("Perros Gamberros", "los"), + 2625 : ("Pastelería Moho Feliz", "la"), + 2626 : ("Reparación de Bromas", "las"), + 2629 : ("El Rincón de la Risa", "el"), + 2632 : ("Academia de Payasos", "la"), + 2633 : ("Salón de Té La Tetera", "el"), + 2638 : ("Willie, el Barco de Vapor", ""), + 2639 : ("Travesuras Simiescas", "las"), + 2643 : ("Botellas Enlatadas", "las"), + 2644 : ("Bromas Ligeras", "las"), + 2649 : ("Tienda de Juegos", "la"), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Clases de Reír", "las"), + 2655 : ("Caja de Ahorros Dine Rodríguez", "la"), + 2656 : ("Autos Usados de Payasos", "los"), + 2657 : ("Bromas a Tutiplén", "las"), + 2659 : ("Calambres Reunidos Ampérez", "los"), + 2660 : ("Cosquilladores Automáticos", "los"), + 2661 : ("Chuches Chucho", ""), + 2662 : ("Dr. Eufo Rico", "el"), + 2663 : ("Don Ratón Se Va De Vacaciones", ""), + 2664 : ("Mimos Mimosos", "los"), + 2665 : ("Agencia de Viajes Corremillas", "la"), + 2666 : ("Gasolinera Desternillante", "la"), + 2667 : ("Tiempos Felices", "los"), + 2669 : ("Globos Cobos", "los"), + 2670 : ("Tenedores de Sopa", "los"), + 2671 : ("Cuartel general", "el"), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Un Marinerito Valiente", ""), + 2705 : ("Matracas Calandracas", "las"), + 2708 : ("Cola Azul", "la"), + 2711 : ("Oficina de Correos de Toontown", "la"), + 2712 : ("Café La Monda", "el"), + 2713 : ("Café Chachi", "el"), + 2714 : ("Un Tranvía En Apuros", ""), + 2716 : ("Calditos Carcajada", "los"), + 2717 : ("Latas Embotelladas", "las"), + 2720 : ("Taller Despilporre", "el"), + 2725 : ("", ""), + 2727 : ("Botellas y Latas Latónez", "las"), + 2728 : ("Nata Invisible", "la"), + 2729 : ("Peces Dorados, 14", "los"), + 2730 : ("Noticias Jocosas", "las"), + 2731 : ("", ""), + 2732 : ("Pasta Pánfilo", "la"), + 2733 : ("Cometas de Plomo", "los"), + 2734 : ("Platillos y Ventosas", "los"), + 2735 : ("Detonaciones a Domicilio", "las"), + 2739 : ("Reparación de Chistes", "las"), + 2740 : ("Petardos Usados", "los"), + 2741 : ("", ""), + 2742 : ("Cuartel general", "el"), + 2743 : ("Limpieza En Seco En Un Minueto", "la"), + 2744 : ("", ""), + 2747 : ("Tinta Visible", "la"), + 2748 : ("Risa Deprisa", "la"), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Cojines Mullídez", "los"), + 2802 : ("Bolas de Demolición Inflables", "las"), + 2803 : ("El Chico Del Carnaval", ""), + 2804 : ("Clínica de Fisioterapia Tronchalomo", "la"), + 2805 : ("", ""), + 2809 : ("Gimnasio Capirotazo", "el"), + 2814 : ("Un Día De Atasco", ""), + 2818 : ("Tartas Volantes", "las"), + 2821 : ("", ""), + 2822 : ("Sándwiches de Pollo de Goma", "los"), + 2823 : ("Heladería El Cucurucho Agudo", "la"), + 2824 : ("Las Locuras De %s" % Mickey, ""), + 2829 : ("Salchichones y Chichones", "los"), + 2830 : ("Melodías de Zipi", "las"), + 2831 : ("Casa de las Risillas del Profesor Cosquillas", "la"), + 2832 : ("Cuartel general", "el"), + 2833 : ("", ""), + 2834 : ("Sala de Traumatología del Hueso de la Risa", "la"), + 2836 : ("", ""), + 2837 : ("Seminarios Sobre Risa Persistente", "los"), + 2839 : ("Pasta Pastosa", "la"), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Tienda de Bromas", "la"), + 1507 : ("Cuartel general", "el"), + 1508 : ("Tienda de Ropa", "la"), + 1510 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Flotadores Usados", "los"), + 1604 : ("Limpieza en Seco de Ropa Chorreantes", "la"), + 1606 : ("Relojería Garfio", "la"), + 1608 : ("Quillas y Cosquillas", "las"), + 1609 : ("Echa el Cebo Magdaleno", ""), + 1612 : ("Banco Galeón Hundido", "el"), + 1613 : ("Bufete Calamar, Pulpo & Sepia", "el"), + 1614 : ("Boutique Uña Deslumbrante", "la"), + 1615 : ("Yates Todo a cien", "el"), + 1616 : ("Salón de Belleza Barbanegra", "el"), + 1617 : ("Óptica El Vigía Miope", "la"), + 1619 : ("Clínica Quirúrgica de Arboles", "la"), + 1620 : ("De Popa a Proa", ""), + 1621 : ("Gimnasio Estibadores Fornidos", "el"), + 1622 : ("Accesorios Eléctricos Rayas y Centollos", "los"), + 1624 : ("Reparación de Suelas de Buque", "la"), + 1626 : ("Ropa de etiqueta El Salmón Coqueto", "la"), + 1627 : ("Surtido de Bitácoras de Beto Buque", "el"), + 1628 : ("Atún de la Tuna", "el"), + 1629 : ("Cuartel general", "el"), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Escuela de Enfermería La Boya Pocha", "la"), + 1703 : ("Restaurante Chino Cocochas de Dragón", "el"), + 1705 : ("Velas y Bielas", "las"), + 1706 : ("Medusas para Bañeras", "las"), + 1707 : ("Regalos Tiburcio", "los"), + 1709 : ("Cata de Maranes", "la"), + 1710 : ("Surtido de Percebes", "el"), + 1711 : ("Restaurante Sal Gorda", "el"), + 1712 : ("Gimnasio Levad Anclas", "el"), + 1713 : ("Mapas Pasma", "las"), + 1714 : ("Posada Suelta el Carrete", "la"), + 1716 : ("Bañadores La Sirena con Piernas", "las"), + 1717 : ("Telas y Botones Océano Pacífico", "las"), + 1718 : ("Servicio de Taxi Callo Encallado", "el"), + 1719 : ("Compañía de Aguas Lomo de Pato", "la"), + 1720 : ("Cañas y Barro", "las"), + 1721 : ("A Toda Máquina", ""), + 1723 : ("Algas Sepio", "las"), + 1724 : ("Anguilas Gustativas", "las"), + 1725 : ("Cangrejos y Aparejos Ajab", "los"), + 1726 : ("Con Cien Gaseosas por Banda", ""), + 1727 : ("Los Remos del Volga", ""), + 1728 : ("Centollos El Meollo", "los"), + 1729 : ("Cuartel general", "el"), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Náutico y Aséptico", "el"), + 1804 : ("Gimnasio El Crustáceo Cachas", "el"), + 1805 : ("Comidas El Carrete Audaz", "las"), + 1806 : ("Sombrerería Gorrotocho", "la"), + 1807 : ("Quillas al Pil Pil", "las"), + 1808 : ("Nudos Imposibles", "los"), + 1809 : ("Cubos Oxidados", "los"), + 1810 : ("Máster en Gestión de Anclas", "el"), + 1811 : ("Canoas y Anchoas", "las"), + 1813 : ("Asesoría Vuela más Alto", "la"), + 1814 : ("Apeadero Amarras Salvajes", "el"), + 1815 : ("Consulta del Dr. Diqueseco", "la"), + 1818 : ("Cafetería Los Siete Mares", "la"), + 1819 : ("El Estibador Gourmet", ""), + 1820 : ("Artículos de Broma El Simpático Maremoto", "los"), + 1821 : ("Fábrica de Conservas dibuque", "la"), + 1823 : ("Restaurante El Molusco Feroz", "el"), + 1824 : ("Paletas y Maletas", "las"), + 1825 : ("Caballas Pura Sangre Pescadería", "las"), + 1826 : ("Sastrería Cromañón", "la"), + 1828 : ("Lastre Pepa Sastre", "el"), + 1829 : ("Estatuas de Gaviotas", "las"), + 1830 : ("Objetos Náuticos Perdidos", "los"), + 1831 : ("Algas de Compañía", "las"), + 1832 : ("Surtido de Mástiles", "el"), + 1833 : ("Trajes a Medida El Corsario Elegante", "los"), + 1834 : ("Timones de diseño", "los"), + 1835 : ("Cuartel general", "el"), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Tienda de Bromas", "la"), + 4504 : ("Cuartel general", "el"), + 4506 : ("Tienda de Ropa de Toontown", "la"), + 4508 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tambores Ropopompom", "los"), + 4604 : ("Compás de Dos por Cuatro", "el"), + 4605 : ("Violines Bibi", "los"), + 4606 : ("Casa de las Castañuelas", "la"), + 4607 : ("Dibumoda Septiminod", "la"), + 4609 : ("Teclas de Piano Dorremí", "las"), + 4610 : ("No Pierdan los Estribillos", ""), + 4611 : ("A Bombo y Platillos Voladores", ""), + 4612 : ("Clínica Dental Rotti", "la"), + 4614 : ("Peluquería de Corcheas", "la"), + 4615 : ("Pizzería Pizzicato", "la"), + 4617 : ("Mandolinas Mandonas", "las"), + 4618 : ("Cuartos para Cuartetos", "los"), + 4619 : ("Pentagramas a la Carta", "los"), + 4622 : ("Almohadas Acompasadas", "las"), + 4623 : ("Bemoles a Cien", "los"), + 4625 : ("Tuba de Pasta de Dientes", "la"), + 4626 : ("Solfeo a Granel", ""), + 4628 : ("Seguros a Tercetos", "los"), + 4629 : ("Platillos de Papel", "los"), + 4630 : ("Tocata y Fuga de Alcatraz", "la"), + 4631 : ("Tocatas de Tortilla", "las"), + 4632 : ("Tienda Abierta los 24 Tiempos", "la"), + 4635 : ("Tenores de Alquiler", "los"), + 4637 : ("Afinado de Platillos", "el"), + 4638 : ("Cuartetos de Heavy Metal", "los"), + 4639 : ("Antigüedades La Flauta de Noé", "las"), + 4641 : ("Noticiero La Voz de la Soprano", "el"), + 4642 : ("Limpieza en Seco en un Minueto", "la"), + 4645 : ("Dibudisco", "el"), + 4646 : ("", ""), + 4648 : ("Mudanzas Berganza", "las"), + 4649 : ("", ""), + 4652 : ("Regalos Clave de Luna", "los"), + 4653 : ("", ""), + 4654 : ("Puertas Tannhäuser", "las"), + 4655 : ("Escuela de Cocina Sonata a la Sal", "la"), + 4656 : ("", ""), + 4657 : ("Barbería El Cuarteto Afeitador", "la"), + 4658 : ("Pianos en Caída Libre", "los"), + 4659 : ("Cuartel general", "el"), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("Escuela de Baile El Pasodoble Zapateado", "la"), + 4702 : ("¡Más Madera! Leñadores Melódicos", ""), + 4703 : ("Tenor al por Menor", "el"), + 4704 : ("Sonatas y Sonatinas", "las"), + 4705 : ("Cítaras al Peso", "las"), + 4707 : ("Estudio de Efectos Doppler", "el"), + 4709 : ("Escala Escalada Aparejos de Escalada", "la"), + 4710 : ("Escuela de Conducción Marcha y Contrafuga", "la"), + 4712 : ("Reparación de Pinchazos de Oboes", "la"), + 4713 : ("Vaqueros Strauss", "los"), + 4716 : ("Armónicas Polifónicas", "las"), + 4717 : ("Seguros de Auto Un Acordeón en la Guantera", "los"), + 4718 : ("Utensilios de Cocina Cascanueces", "los"), + 4719 : ("Caravanas Madrigal", "las"), + 4720 : ("Tararea esa dibucanción", "la"), + 4722 : ("Oberturas para Oboe", "las"), + 4723 : ("Juguetería El Xilófono de Plastilina", "la"), + 4724 : ("Cacofonías a Domicilio", "las"), + 4725 : ("El Barbero Barítono", ""), + 4727 : ("Planchado de Cuerdas Vocales", "el"), + 4728 : ("Ablandamiento de Oídos Duros", "el"), + 4729 : ("Librería El Violoncelo Celoso", "la"), + 4730 : ("Letras Patéticas", "las"), + 4731 : ("Melodibus", "los"), + 4732 : ("Compañía de Teatro El Flautín de Venecia", "la"), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Acordeones para Amenizar Reuniones", "las"), + 4736 : ("Bodas Fígaro", "las"), + 4737 : ("Arpas de Esparto", "las"), + 4738 : ("Regalos La Balalaica Silvestre", "los"), + 4739 : ("Cuartel general", "el"), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Organillos en Estéreo", "los"), + 4803 : ("Floristería El Sombrero de Tres Ficus", "la"), + 4804 : ("Escuela de Hostelería El Platillo Danzarín", "la"), + 4807 : ("Trémolos Embotellados", "los"), + 4809 : ("Allegros Tristes", "los"), + 4812 : ("", ""), + 4817 : ("Pajarería Pedro y el Lobo", "la"), + 4819 : ("Ukeleles de Uki", "los"), + 4820 : ("", ""), + 4821 : ("Gramolas Juanola", "las"), + 4827 : ("Relojería La Danza de las Horas", "la"), + 4828 : ("Zapatería Masculina Claqué para Ciempies", "la"), + 4829 : ("Cañones Pachelbel", "los"), + 4835 : ("Cascabeles para Gatos", "los"), + 4836 : ("Regalos Reggae", "los"), + 4838 : ("Academia de Canto Grájez", "la"), + 4840 : ("Bebidas Musicales Cocapiano Cola", "las"), + 4841 : ("Liras Palmira", "las"), + 4842 : ("Síncopas Hechas a Mano", "las"), + 4843 : ("", ""), + 4844 : ("Motocicletas Harley Mendelson", "las"), + 4845 : ("Elegías Elegantes de Elisa", "las"), + 4848 : ("Caja de ahorros Guita Ramón", "la"), + 4849 : ("", ""), + 4850 : ("Empeños La Cuerda Prestada", "los"), + 4852 : ("Fundas para Flautas", "las"), + 4853 : ("Guitarras a Vapor Leo", "las"), + 4854 : ("Vídeos de Valquirias y Violines", "los"), + 4855 : ("Címbalos a Domicilio", "los"), + 4856 : ("", ""), + 4862 : ("Pasodobles, Pasotriples y Pasocuádruples", "los"), + 4867 : ("Liquidación de Violoncelos", "la"), + 4868 : ("", ""), + 4870 : ("Timbales y Tambores de Titanio", "los"), + 4871 : ("Clarines, Clarinetes y Chifletes", "los"), + 4872 : ("Marimbas, Matracas y Maracas", "las"), + 4873 : ("Cuartel general", "el"), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Tienda de Bromas", "la"), + 5502 : ("Cuartel general", "el"), + 5503 : ("Tienda de Ropa", "la"), + 5505 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Óptica Zanahoria a Tutiplén", "la"), + 5602 : ("Corbatas Pino", "las"), + 5603 : ("Lechuga a Granel", "la"), + 5604 : ("Listas de Bodas Nomeolvides", "las"), + 5605 : ("Compañía de Aguas de Borrajas", "la"), + 5606 : ("Pétalos", "los"), + 5607 : ("Correos Florestales", "los"), + 5608 : ("Palomitas y Palomitos de Maíz", "las"), + 5609 : ("Enredaderas de Compañía", "las"), + 5610 : ("Betunes El Tulipán Negro", "los"), + 5611 : ("Bromas Cardamomo", "las"), + 5613 : ("Peluqueros Siegacogotes", "los"), + 5615 : ("Semillas con Coronilla", "las"), + 5616 : ("Posada Coli Flor de Pitiminí", "la"), + 5617 : ("Mariposas de Encargo", "las"), + 5618 : ("Guisantes Farsantes", "los"), + 5619 : ("Comino Importante", "el"), + 5620 : ("Hierbabuenas Tardes", "las"), + 5621 : ("Viñas Lejanas", "las"), + 5622 : ("Bicicletas Hinojo Hinault", "las"), + 5623 : ("Jacuzzis para Gorriones", "los"), + 5624 : ("Madreselva Tropical", "la"), + 5625 : ("Pañales para Panales", "los"), + 5626 : ("Zarzaparrillas de Carbón", "las"), + 5627 : ("Cuartel general", "el"), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Espinacas de Diseño", "las"), + 5702 : ("Rastrillos Miga de Pan", "los"), + 5703 : ("Fotografía La Flor de un Día", "la"), + 5704 : ("Autos Usados Campanita", "los"), + 5705 : ("Colchones Suavecáctus", "los"), + 5706 : ("Joyería La Pulsera de Chopo", "la"), + 5707 : ("Fruta Musical", "la"), + 5708 : ("Agencia de Viajes Villadiego", "la"), + 5709 : ("Cortacésped Amor de Hortelano", "el"), + 5710 : ("Gimnasio Espantalobos", "el"), + 5711 : ("Calcetería Lentejuela Guisada", "la"), + 5712 : ("Estatuas Bobas", "las"), + 5713 : ("Jabones de Higo Chumbo", "los"), + 5714 : ("Agua de Lluvia Embotellada", "el"), + 5715 : ("Noticiario Telecastaña", "el"), + 5716 : ("Caja de Ahorros y Monte de Orégano", "la"), + 5717 : ("La Flor Chorreante", ""), + 5718 : ("Animales Exóticos Diente de León", "los"), + 5719 : ("Agencia de Detectives Azotalenguas", "la"), + 5720 : ("Ropa Masculina Borriquero", "la"), + 5721 : ("Comidas Alfalfa Romeo", "las"), + 5725 : ("Destilería Malta Cibelina", "la"), + 5726 : ("Barro a Granel", "el"), + 5727 : ("Préstamos y Empréstitos Praderas Primitivas", "los"), + 5728 : ("Cuartel general", "el"), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : ("Cuartel general", ""), + 5804 : ("Jarrones Porrones", ""), + 5805 : ("Correo Caracol", ""), + 5809 : ("Escuela de Payasos Champi", ""), + 5810 : ("Rocío la Miel", ""), + 5811 : ("Posada Lechuga", ""), + 5815 : ("Raíces de Césped", ""), + 5817 : ("Manzanas y Naranjas", ""), + 5819 : ("Tejanos Hermanos", ""), + 5821 : ("Gimnasio Tira y Afloja", ""), + 5826 : ("Suministros Granjeros la Hormiga", ""), + 5827 : ("Baratijas Pijas", ""), + 5828 : ("Muebles el Comodín", ""), + 5830 : ("Judías Pintas", ""), + 5833 : ("El Bar Verde", ""), + 5835 : ("Hostal YCual", ""), + 5836 : ("Duchas y Baños los Caños", ""), + 5837 : ("Escuela de Parra y Jarra", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Biblioteca Sabetotal", "la"), + 9503 : ("Bar La Cabezadita Tonta", "el"), + 9504 : ("Tienda de Bromas", "la"), + 9505 : ("Cuartel general", "el"), + 9506 : ("Tienda de Ropa de Toontown", "la"), + 9508 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Posada Pluma de Ganso", "la"), + 9602 : ("Siestas a Domicilio", "las"), + 9604 : ("Fundas Nórdicas para Pinreles", "las"), + 9605 : ("Diseño Séptimo Cielo", ""), + 9607 : ("Pijamas de Plomo para Dormir de Pie", "el"), + 9608 : ("", ""), + 9609 : ("Arrullos a Granel", "los"), + 9613 : ("Los Limpiadores Del Reloj", ""), + 9616 : ("Compañía Eléctrica Luces Fuera", "la"), + 9617 : ("Notas de Cuna - Música para Descansar", ""), + 9619 : ("Sopas Soporíferas", "las"), + 9620 : ("Servicio de Taxis Insomnes", "el"), + 9622 : ("Relojería El Cuco Dormido", "la"), + 9625 : ("Salón de Belleza El Ronquido Alegre", "el"), + 9626 : ("Cuentos para Dormir", " "), + 9627 : ("Mecedoras Automáticas", "las"), + 9628 : ("Calendarios Nocturnos", "los"), + 9629 : ("Joyería Sábanas de Oro", "la"), + 9630 : ("Serrería Como un Tronco", "la"), + 9631 : ("Arreglo de Relojes Estoysopa", "el"), + 9633 : ("La Siesta De Pluto", ""), + 9634 : ("Colchones La Pluma Audaz", "los"), + 9636 : ("Seguro Contra Insomnios", "el"), + 9639 : ("Conservas Ultrahibernadas", "las"), + 9640 : ("Muebles Soporte Total", ""), + 9642 : ("Ganadería Cuentaovejas", "la"), + 9643 : ("Óptica Nopegojo", "la"), + 9644 : ("Peleas de Almohadas Organizadas", "las"), + 9645 : ("Posada Todos al Sobre", "la"), + 9647 : ("¡Hazte la cama! Ferretería", ""), + 9649 : ("Ronquidos Lejanos", "los"), + 9650 : ("Reparaciones Amanecer", ""), + 9651 : ("Martillos para Despertadores", "los"), + 9652 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Agencia de Viajes Vuelos Cruzados", ""), + 9704 : ("Tienda de Animales la Lechuza", ""), + 9705 : ("Taller Dormido al Volante", ""), + 9706 : ("Dentista Ratoncito Pérez", ""), + 9707 : ("Jardinería Flor de Primavera", ""), + 9708 : ("Floristería Comes Flores", ""), + 9709 : ("Fontanería Tubo Tubular", ""), + 9710 : ("Óptica REM", ""), + 9711 : ("Telefonía Llamada Perdida", ""), + 9712 : ("Cuenta Ovejas, ¡para que tú no tengas que hacerlo!", ""), + 9713 : ("Cintio, Lucio y Picio, Abogados de Oficio", ""), + 9714 : ("Suministros Marinos Barcaza", ""), + 9715 : ("Banco el Gigante Caja Fuerte", ""), + 9716 : ("Organizadores de Fiestas la Guirnalda Mojada", ""), + 9717 : ("Panadería el Bollo Tostado", ""), + 9718 : ("Sándwiches Suela Sandalio", ""), + 9719 : ("Tienda de Almohadas Armadillo", ""), + 9720 : ("Entrenamiento de Voz Profunda", ""), + 9721 : ("Tienda de Alfombras la Sombra", ""), + 9722 : ("Agencia de Talentos Lentos", ""), + 9725 : ("Pijamas el Gato", ""), + 9727 : ("Si Duermes Pierdes", ""), + 9736 : ("Agencia de Empleo el Trabajo Ideal", ""), + 9737 : ("Escuela de Baile el Vals de Matilde", ""), + 9738 : ("Casa de Zzzzzs", ""), + 9740 : ("Escuela Esgrima el Filo", ""), + 9741 : ("Exterminadores la Pulga Saltarina", ""), + 9744 : ("Crema Antiarrugas la Arruga es Bella", ""), + 9752 : ("Comañía de Gas la Ciudad", ""), + 9753 : ("Helados el Frío Polar", ""), + 9754 : ("Excursiones en Poni el Estribo", ""), + 9755 : ("Productora de Cine Palomitas", ""), + 9756 : ("", ""), + 9759 : ("Centro de Belleza la Bella Durmiente", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Tienda de Bromas", "la"), + 3508 : ("Cuartel general", "el"), + 3509 : ("Tienda de Ropa", "la"), + 3511 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Compañía Eléctrica Polo Norte", "la"), + 3602 : ("Gorros de Nieve Geli", "los"), + 3605 : ("", ""), + 3607 : ("Ventisca a la Vista", "la"), + 3608 : ("Bobsled para Lactantes", ""), + 3610 : ("Hipermercado Esquimal de Mito", "el"), + 3611 : ("Quitanieves Escárchez", "la"), + 3612 : ("Diseño de Iglús", "el"), + 3613 : ("Bicicletas Carámbanez", "las"), + 3614 : ("Cereales Copos de Nieve", "los"), + 3615 : ("Arenques en Almíbar", "los"), + 3617 : ("Dirigibles de Aire Frío", "los"), + 3618 : ("¿Avalancha? Sin Problemas Gestión de Crisis", "la"), + 3620 : ("Clínica de Esquí", "la"), + 3621 : ("Bar El Deshielo", "el"), + 3622 : ("", ""), + 3623 : ("Panes Criogenizados Frigomiga", "los"), + 3624 : ("Sándwiches Bajocero", "los"), + 3625 : ("Radiadores de la Tía Ritona", "los"), + 3627 : ("Adiestramiento de San Bernardos", "el"), + 3629 : ("Cafetería El Braserillo que Ríe", "la"), + 3630 : ("Agencia de Viajes Témpano Tenaz", "la"), + 3634 : ("Remontes Rascafría", "los"), + 3635 : ("Leña Usada", "la"), + 3636 : ("Surtido de Sabañones", "el"), + 3637 : ("Patines Pati", "los"), + 3638 : ("Trineos y Cuatrineos", "los"), + 3641 : ("Camas Heladas Pepe Tundra", "las"), + 3642 : ("Óptica El Yeti Tuerto", "la"), + 3643 : ("Salón de la Bola de Nieve", "el"), + 3644 : ("Cubitos de Hielo Fundidos", "los"), + 3647 : ("Alquiler de Chaqués El Pingüino Beduino", "el"), + 3648 : ("Hielo Instantáneo", "el"), + 3649 : ("Hambrrrrguesas", "las"), + 3650 : ("Antigüedades Antárticas", "las"), + 3651 : ("Dibuperritos Helados Pipe", "los"), + 3653 : ("Joyería Frío como el Diamante", "la"), + 3654 : ("Cuartel general", "el"), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Almacén de Invierno", "el"), + 3703 : ("", ""), + 3705 : ("Carámbanos a Granel", "los"), + 3706 : ("Batidos El Tembleque", "los"), + 3707 : ("Hogar, Gélido Hogar", ""), + 3708 : ("Plutón en tu Casa", ""), + 3710 : ("Comidas Alaska en Enero", "las"), + 3711 : ("", ""), + 3712 : ("Tuberías Muy Muy Frías", "las"), + 3713 : ("Dentista El Castañeteo Perpetuo", "el"), + 3715 : ("Surtido de Sopitas de la Tía Ártica", "el"), + 3716 : ("Sal Gorda para Carreteras", "la"), + 3717 : ("Polos y Helados Variados", "los"), + 3718 : ("Calefacción a Domicilio", "la"), + 3719 : ("Paté de Cubitos", "el"), + 3721 : ("Trineos de Ocasión", "los"), + 3722 : ("Tienda de Esquí Anchoa", "la"), + 3723 : ("Guantes de Nieve Tiritonio", "los"), + 3724 : ("La Voz de la Tundra", "la"), + 3725 : ("Me Mareo en Trineo", ""), + 3726 : ("Mantas Eléctricas Solares", "las"), + 3728 : ("Quitanieves Cenutrio", "el"), + 3729 : ("", ""), + 3730 : ("Desguace de Muñecos de Nieve", "el"), + 3731 : ("Chimeneas Portátiles", "las"), + 3732 : ("La Nariz Helada", ""), + 3734 : ("Tímpanos como Témpanos Otorrino", "los"), + 3735 : ("Forros Polares de Papel", "los"), + 3736 : ("Cucuruchos de Hielo Picado", "los"), + 3737 : ("Comidas Eslalon", "las"), + 3738 : ("Escondites Frío Frío", "los"), + 3739 : ("Cuartel general", "el"), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : ("Cuartel general", ""), + 3806 : ("Alimentos Alpinos", ""), + 3807 : ("Sombras de la Marmota", ""), + 3808 : ("El Sweater No Es Eterno", ""), + 3809 : ("Hel-lado Equivocado", ""), + 3810 : ("Edredón Algodón", ""), + 3811 : ("Ángel de Nieve", ""), + 3812 : ("Mitones para Ratones", ""), + 3813 : ("Botas de Nieve para Nueve", ""), + 3814 : ("Sodas el Buen Trago", ""), + 3815 : ("El Chalet Tupé", ""), + 3816 : ("Muérdago Dragón", ""), + 3817 : ("Club de Montaña el Bello Invierno", ""), + 3818 : ("El Local de las Palas", ""), + 3819 : ("Servicio de Limpieza el Esplendor", ""), + 3820 : ("Blancura de Nieve", ""), + 3821 : ("Vacación e Hibernación", ""), + 3823 : ("Cimientos Aspavientos", ""), + 3824 : ("Castañas Asadas la Hoguera", ""), + 3825 : ("Elegantes Sombreros el Gatito", ""), + 3826 : ("¡Mis Chanclas!", ""), + 3827 : ("Coronas de Coral", ""), + 3828 : ("El Hombre de las Nieves", ""), + 3829 : ("Terreno de las Piñas", ""), + 3830 : ("Desempañado de Gafas Tolomiro", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Lo siento, el tiempo se\n acabó." +ClosetNotOwnerMessage = "Este no es tu clóset, pero te puedes probar la ropa." +ClosetPopupOK = "Muy bien" +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Rechazar" +ClosetAreYouSureMessage = "Borraste algunas prendas. ¿Realmente quieres eliminarlas?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "¿Borrar %s?" +ClosetShirt = "Esta camiseta" +ClosetShorts = "Este pantalón corto" +ClosetSkirt = "Esta falda" +ClosetDeleteShirt = "Borrar\ncamiseta" +ClosetDeleteShorts = "Borrar\npantalón corto" +ClosetDeleteSkirt = "Borrar\nfalda" + +# EstateLoader.py +EstateOwnerLeftMessage = "Lo siento, el dueño de esta propiedad se marchó. Serás enviado al dibuparque en %s segundos" +EstatePopupOK = "Muy bien" +EstateTeleportFailed = "No pudiste irte a casa. ¡Inténtalo de nuevo!" +EstateTeleportFailedNotFriends = "Lo siento, %s está en una dibuhacienda que no es amiga tuya." + +# DistributedTarget.py +EstateTargetGameStart = "¡El juego del objetivo curadibu ya comenzó!" +EstateTargetGameInst = "Cuanto más golpees el objetivo rojo, mejor será tu nivel de mejora curadibu." +EstateTargetGameEnd = "El juego del objetivo curadibu ya terminó..." + +# DistributedCannon.py +EstateCannonGameEnd = "El alquiler del juego de Cañones ya terminó." + +# DistributedHouse.py +AvatarsHouse = "Casa de %s" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Lo siento, este no es tu banco." +DistributedBankNotOwner = "Lo siento, este no es tú banco." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Vender todo" +FishTankValue = "¡Hola, %(name)s! En la cubeta llevas %(num)s peces, que tienen un valor total de %(value)s golosinas. ¿Quieres venderlos todos?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Vender todo" +FlowerBasketValue = "%(name)s, tienes %(num)s flores en tu cesta por un valor total de %(value)s golosinas. ¿Quieres venderlas todas?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name + "'" + else: + possesive = name + "" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('Siempre hambriento', 'A menudo hambriento', + 'A veces hambriento', 'Raramente hambriento',), + 'boredomThreshold': ('Siempre aburrido', 'A menudo aburrido', + 'A veces aburrido', 'Raramente aburrido',), + 'angerThreshold': ('Siempre gruñón', 'A menudo gruñón', + 'A veces gruñón', 'Raramente gruñón'), + 'forgetfulness': ('Siempre olvida', 'A menudo olvida', + 'A veces olvida', 'Raramente olvida',), + 'excitementThreshold': ('Muy tranquilo', 'Bastante tranquilo', + 'Bastante nervioso', 'Muy nervioso',), + 'sadnessThreshold': ('Siempre triste', 'A menudo triste', + 'A veces triste', 'Raramente triste',), + 'restlessnessThreshold': ('Siempre inquieto', 'A menudo inquieto', + 'A veces inquieto', 'Raramente inquieto',), + 'playfulnessThreshold': ('Raramente juguetón', 'A veces juguetón', + 'A menudo juguetón', 'Siempre juguetón',), + 'lonelinessThreshold': ('Siempre solitario', 'A menudo solitario', + 'A veces solitario', 'Raramente solitario',), + 'fatigueThreshold': ('Siempre cansado', 'A menudo cansado', + 'A veces cansado', 'Raramente cansado',), + 'confusionThreshold': ('Siempre confundido', 'A menudo confundido', + 'A veces confundido', 'Raramente confundido',), + 'surpriseThreshold': ('Siempre sorprendido', 'A menudo sorprendido', + 'A veces sorprendido', 'Raramente sorprendido',), + 'affectionThreshold': ('Raramente cariñoso', 'A veces cariñoso', + 'A menudo cariñoso', 'Siempre cariñoso',), + } + + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Para ver mejor, pulsa la tecla \"Re Pág\". " + +FireworksJuly4Beginning = lToonHQ+": ¡Bienvenido a los fuegos artificiales de verano! ¡Disfruta del espectáculo!" +FireworksJuly4Ending = lToonHQ+": ¡Espero que te haya gustado el espectáculo! ¡Que pases un buen verano!" +FireworksNewYearsEveBeginning = lToonHQ+": ¡Feliz año nuevo! ¡Que disfrutes de los fuegos artificiales!" +FireworksNewYearsEveEnding = lToonHQ+": ¡Espero que te haya gustado el espectáculo! ¡Feliz año nuevo!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "UN DIBUCONSEJO:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Para comprobar rápidamente el estado de tu dibutarea, pulsa la tecla \"Fin\".", + "Para echar un vistazo rápido a tu página de bromas, pulsa la tecla \"Inicio\".", + "Pulsa la tecla \"F7\" para abrir la Lista de amigos.", + "Para abrir o cerrar el dibucuaderno, pulsa la tecla \"F8\".", + "Para mirar hacia arriba, pulsa la tecla \"Re Pág\" y para mirar hacia abajo, pulsa la tecla \"Av Pág\".", + "Pulsa la tecla \"Control\" para saltar.", + "Si pulsas la tecla \"F9\", obtendrás una captura de pantalla que quedará almacenada en la carpeta Toontown de tu ordenador.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Puedes intercambiar códigos de Amigos secretos con personas que conozcas en la vida real para poder charlar con ellas en Toontown.", + "En la página Opciones del dibucuaderno podrás cambiar la resolución de la pantalla, modificar el sonido y ajustar otras opciones.", + "Pruébate la ropa de tus amigos en el clóset de su casa.", + "Para ir a tu casa, usa el botón \"Ir a casa\" del mapa.", + "Cada vez que completes una dibutarea, tus puntos de risa se rellenarán automáticamente.", + "Puedes examinar el surtido de las tiendas de ropa sin necesidad de tener un boleto de ropa.", + "Las recompensas de ciertas dibutareas te permiten llevar más bromas y golosinas.", + "En tu Lista de amigos puedes tener hasta 50 usuarios.", + "Algunas recompensas de dibutareas te permitirán teletransportarte a los dibuparques de Toontown con la página del mapa del dibucuaderno.", + "Para aumentar tus puntos de risa en los dibuparques, reúne tesoros, como estrellas y cucuruchos de helado.", + "Si tienes prisa para curarte después de un duro combate, ve a tu propiedad y recoge cucuruchos de helado.", + "Para cambiar las vistas de tu dibu, pulsa la tecla Tab.", + "A veces verás que varias dibutareas distintas ofrecen la misma recompensa. ¡Compáralas!", + "Una forma divertida de avanzar en el juego consiste en buscar amigos que tengan dibutareas parecidas.", + "Nunca te hará falta guardar la partida en Toontown. Los servidores de Toontown almacenan toda la información necesaria de manera continua.", + "Si quieres susurrar a otros dibus, haz clic en ellos o selecciónalos en tu Lista de amigos.", + "Algunas frases de SpeedChat hacen que tu dibu muestre emociones.", + "Si la zona en la que estás está demasiado llena, trata de cambiar de distrito. Acude a la página de Distritos del dibucuaderno y selecciona otro distrito.", + "Si recuperas edificios aparecerá una estrella de bronce, plata u oro sobre tu dibu.", + "Si recuperas edificios suficientes para conseguir que aparezca una estrella sobre tu cabeza, quizá veas tu nombre escrito en la pizarra de un cuartel general.", + "A veces, los bots vuelven a capturar edificios recuperados. ¡La única manera de conservar tu estrella consiste en ir a recuperar más edificios!", + "Los nombres de tus amigos secretos aparecen en azul.", + # Fishing + "¡A ver si puedes conseguir todos los peces de Toontown!", + "En cada estanque hay peces distintos. ¡Prueba en todos!", + "Cuando tu cubeta de pesca esté llena, vende tus peces a los pescadores de los dibuparques.", + "Puedes vender tus peces a los pescadores o en las tiendas de animales.", + "Las cañas de pescar más pesadas pescan peces más pesados, pero para usarlas necesitas gastar más golosinas.", + "Puedes comprar cañas de pescar más fuertes en el catálogo tolón-tolón.", + "Con los peces más pesados, conseguirás más golosinas en la tienda de animales.", + "Con los peces más extraños, conseguirás más golosinas en la tienda de animales.", + "Al pescar, a veces puedes encontrar bolsas de golosinas.", + "En algunas dibutareas hay que pescar objetos de los estanques.", + "Los estanques de pesca de los dibuparques tienen peces diferentes a los de los estanques de las calles.", + "Algunos peces son muy poco comunes. ¡Sigue pescando hasta que los tengas todos!", + "El estanque de tu hacienda tiene peces que solo se pueden pescar ahí.", + "¡Por cada 10 especies que pesques recibirás un trofeo de pesca!", + "En el dibucuaderno podrás ver los peces que pescaste.", + "Algunos trofeos de pesca te premian con una subida del risómetro.", + "Pescar es una manera muy buena de conseguir más golosinas.", + # Doodles + "¡Adopta un Dibuperrito en la tienda de animales!", + "Las tiendas de animales reciben cada día nuevos Dibuperritos para vender.", + "Visita las tiendas de animales cada día para ver los nuevos Dibuperritos que recibieron.", + "Diferentes barrios tienen Dibuperritos distintos en adopción.", + # Karting + "Demuestra tu estilo al volante y aumenta tu límite de puntos de risa en el Estadio de Goofy.", + "Accede al Estadio de Goofy a través del túnel en forma de neumático del dibuparque del Centro de Toontown.", + "Consigue puntos de risa en el Estadio de Goofy.", + "El estadio de Goofy tiene seis circuitos de carreras distintos. " + ), + + TIP_STREET : ( + "Hay cuatro tipos de bots: abogabots, chequebots, vendebots y jefebots.", + "Cada circuito de bromas tiene distintas cantidades de precisión y daños.", + "Las bromas de sonido afectan a todos los bots, pero despiertan a los bots que persiguen cebos.", + "Derrota a los bots siguiendo un orden estratégico: aumentará tus posibilidades en ganar los combates.", + "El circuito de broma curadibu te permite sanar a otros dibus durante los combates.", + "¡Durante las invasiones de bots, los puntos de experiencia de bromas se duplican!", + "Si formas un equipo con otros dibus y usas el mismo circuito de bromas en el combate, conseguirás una bonificación por daños a los bots.", + "Las bromas se usan por orden, de arriba abajo, según aparecen en el menú de bromas del combate.", + + "La fila de luces circulares que hay sobre los ascensores de los edificios bot muestra cuántos pisos tienen.", + "Haz clic en un bot para ver más detalles sobre él.", + "Si usas bromas de alto nivel contra bots de bajo nivel, no conseguirás puntos de experiencia.", + "Las bromas que te dan experiencia tienen un fondo azul en el menú de bromas del combate.", + "La experiencia que te dan las bromas se multiplica en el interior de los edificios bot. Cuanto más elevado sea el piso, mayor será el factor de multiplicación.", + "Al ganarle a un bot, todos los dibus que hayan participado conseguirán puntos cuando termine el combate.", + "Todas las calles de Toontown tienen distintos tipos y niveles de bots.", + "En las aceras estarás a salvo de los bots.", + "En las calles, las puertas de las casas cuentan chistes cuando te acercas.", + "Algunas dibutareas te proporcionan entrenamiento para nuevos circuitos de bromas. ¡Sólo podrás escoger seis de los siete circuitos de bromas, así que elige con cuidado!", + "Las trampas solo son útiles cuando tú coordines con tus amigos el uso de los cebos en el combate.", + "Los cebos de alto nivel tienen menos posibilidades de fallar.", + "Las bromas de bajo nivel tienen poca precisión contra los bots de alto nivel.", + TheCogs+" no pueden atacar cuando siguieron un cebo hasta un combate.", + "Cuando tus amigos y tú recuperen un edificio bot, serán recompensados con retratos dentro del edificio rescatado.", + "Si usas una broma curadibu en un dibu que tenga el risómetro completo, no conseguirás experiencia de curadibu.", + TheCogs+" quedarán aturdidos durante un instante al ser alcanzados por cualquier broma. Esto aumenta la probabilidad de que otras bromas los alcancen en la misma ronda.", + "Las bromas de caída tienen menos probabilidad de alcanzar su objetivo, pero su precisión aumenta cuando el bot fue alcanzado antes por otra broma en la misma ronda.", + "Cuando hayas derrotado suficientes bots, podrás usar el \"radar de bots\" haciendo clic en los iconos de bots de la página galería de bots del dibucuaderno.", + "Durante los combates, podrás saber a qué bot están atacando tus compañeros de equipo mirando los guiones (-) y las X.", + "Durante los combates, los bots llevan una luz que indica su estado de salud: verde significa que está sano y rojo que casi está destruido.", + "Solo pueden combatir a la vez un máximo de cuatro dibus.", + "En la calle, los bots tienen más tendencia a luchar contra varios dibus que contra uno solo.", + "Los dos bots más difíciles de cada tipo se encuentran en el interior de los edificios.", + "Las bromas de caída no funcionan contra los bots que siguen un cebo.", + TheCogs+" tienden a atacar al dibu que les causó más daños.", + "Las bromas de sonido no dan bonificación por daños contra los bots que siguen cebos.", + "Si esperas demasiado para atacar a un bot que sigue un cebo, se despertará. Cuanto mayor sea el nivel del cebo, mayor será su duración.", + ), + + TIP_MINIGAME : ( + "Cuando tengas llena la jarra de golosinas, las que consigas en los juegos del tranvía irán a parar directamente a tu banco.", + "Puedes usar las teclas de flecha en lugar del mouse en el juego del tranvía \"Imita a Minnie\".", + "En el juego del cañón, usa las teclas de flecha para mover el cañón y pulsa la tecla \"Control\" para disparar.", + "En el juego de los anillos, conseguirás puntos de bonificación cuando todo el grupo consiga atravesar sus anillos.", + "Si juegas perfectamente a Imita a Minnie, duplicarás los puntos.", + "En el juego de la cuerda, conseguirás más golosinas si te enfrentas a un bot más grande.", + "La dificultad de los juegos del tranvía varían según el barrio: en el "+lToontownCentral+" están los más fáciles, y en "+lDonaldsDreamland+", los más difíciles.", + "En algunos juegos del tranvía sólo se puede jugar en grupo.", + ), + + TIP_COGHQ : ( + "Antes de entrar en el edificio jefe debes completar el disfraz de bot.", + "Antes de visitar al director financiero debes completar el disfraz de chequebot.", + "Antes de visitar al Juez debes completar el disfraz de abogabot.", + "Puedes saltar encima de los matones bots para dejarlos temporalmente incapacitados.", + "Reúne méritos bot luchando contra bots y venciéndolos.", + "Recoge botdólares derrotando chequebots en batalla.", + "Recoge Notificaciones del tribunal derrotando abogabots en batalla.", + "Recoge acciones derrotando jefebots en batalla.", + "Se te otorgarán más méritos si luchas contra bots de mayor nivel.", + "Cuando reúnas los méritos bot necesarios para recibir un ascenso, vete a ver al vendebot VIP.", + "Cuando consigas suficientes botdólares para ganarte un ascenso, ve a ver al director financiero chequebot.", + "Cuando consigas suficientes Notificaciones del tribunal para ganarte un ascenso, ve a ver al Juez abogabot!", + "Cuando consigas suficientes acciones para ganarte un ascenso, ve a ver al director general jefebot!", + "Cuando llevas el disfraz de bot puedes hablar como uno de ellos.", + "Hasta ocho dibus pueden unirse para luchar contra el vendebot VIP.", + "Hasta ocho dibus pueden unirse para luchar contra el director financiero chequebot", + "Hasta ocho dibus pueden unirse para luchar contra el Juez abogabot.", + "Hasta ocho dibus pueden unirse para luchar contra el director general jefebot.", + "Una vez dentro del cuartel general bot, sube las escaleras hasta llegar al capataz.", + "Cada vez que luches en el cuartel general vendebot recibirás una pieza del disfraz de vendebot.", + "Puedes comprobar cuánto te falta para completar el disfraz bot con el dibucuaderno.", + "Puedes comprobar cuántos méritos llevas en la página del disfraz del dibucuaderno.", + "Antes de enfrentarte al VIP, comprueba que vas bien cargado de bromas y puntos de risa.", + "El disfraz de bot irá cambiando a medida que recibas ascensos.", + "Debes derrotar al "+Foreman+" para obtener una pieza del disfraz de bot.", + "Consigue piezas del disfraz de chequebot como recompensa por completar dibutareas en Sueñolandia de Donald.", + "Los chequebots fabrican y distribuyen su moneda, los botdólares, en tres fabricas de monedas: Moneditas, Dólar y Lingote.", + "Espera a que el director financiero esté mareado para lanzar una caja fuerte, ¡si no esperas la usará de casco! Golpea el casco con otra caja fuerte para quitárselo.", + "Consigue piezas del disfraz de abogabot como recompensa por completar dibutareas para el Profesor Copos.", + "Sorprenderse tiene sus ventajas: los bots virtuales del cuartel general abogabot no te recompensarán con Notificaciones del tribunal.", + ), + TIP_ESTATE : ( + # Doodles + "Los Dibuperritos entienden algunas frases de SpeedChat. ¡Pruébalas!", + "Utiliza el menú \"Mascota\" de SpeedChat para decirle a tu Dibuperrito que haga acrobacias.", + "Puedes enseñarles a los Dibuperritos a hacer acrobacias con clases del catálogo tolón-tolón de Clarabel.", + "Premia a tu Dibuperrito cuando haga una acrobacia.", + "Si visitas la hacienda de un amigo, tu Dibuperrito también irá.", + "Cuando tu Dibuperrito tenga hambre, dale una golosina.", + "Haz clic sobre un Dibuperrito para acceder a un menú donde podrás alimentarlo, rascarlo y llamarlo.", + "A los Dibuperritos les encanta la compañía. ¡Invita a tus amigos a jugar!", + "Cada Dibuperrito tiene su propia personalidad.", + "Puedes devolver a tu Dibuperrito y adoptar a otro en la tienda de animales.", + "Cuando un Dibuperrito haga una actobacia, los dibus de alrededor se curarán.", + "Los Dibuperritos hacen las acrobacias mejor cuando practican. ¡Insiste!", + "Las acrobacias más avanzadas de tu Dibuperrito curan a los dibus más rápido.", + "Los Dibuperritos más experimentados pueden hacer más acrobacias y tardan más en cansarse.", + "En tu lista de amigos podrás ver una lista con los Dibuperritos más cercanos.", + # Furniture / Cattlelog + "Compra muebles del catálogo tolón-tolón de Clarabel para decorar tu casa.", + "En el banco que tienes dentro de tu casa hay más golosinas.", + "En el clóset dentro de tu casa hay más ropa.", + "Ve a la casa de tu amigo y pruébate su ropa.", + "Compra cañas de pescar mejores en el catálogo tolón-tolón de Clarabel.", + "Compra bancos más grandes en el catálogo tolón-tolón de Clarabel.", + "Llama a Clarabel con el teléfono que tienes en tu casa.", + "Clarabel vende clósets más grandes donde cabe más ropa.", + "Antes de usar un Boleto de ropa, haz lugar en tu clóset.", + "Clarabel vende todo lo que necesitas para decorar tu casa.", + "Busca en tu buzón los paquetes con encargos que le hayas hecho a Clarabel.", + "La ropa del catálogo tolón-tolón de Clarabel tarda una hora en llegar.", + "El papel de pared y el suelo del catálogo tolón-tolón de Clarabel tardan una hora en llegar.", + "Los muebles del catálogo tolón-tolón de Clarabel tardan un día entero en llegar.", + "Almacena los muebles que te sobren en el desván.", + "Cuando salga un nuevo catálogo tolón-tolón, recibirás un aviso de Clarabel.", + "Cuando llegue una entrega del catálogo tolón-tolón de Clarabel, recibirás un aviso.", + "Cada semana salen nuevos catálogos tolón-tolón.", + "Busca los artículos de vacaciones de edición limitada en el catálogo tolón-tolón.", + "Lleva los muebles que no quieras a la papelera.", + # Fish + "Algunos peces, como la merluza de colar, son más comunes en las haciendas dibu.", + # Misc + "Puedes invitar a tus amigos a tu hacienda utilizando SpeedChat.", + "¿Sabías que el color de tu casa coincide con el color de tu panel Eligedibu?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Compra un kart Roadster, Utilitario dibu o Cruiser en la tienda de autos de Goofy.", + "Personaliza tu kart con adhesivos, llantas y más cosas en la tienda de autos de Goofy.", + "Consigue boletos compitiendo con otros karts en el Estadio de Goofy.", + "Los boletos son la única moneda de cambio que se acepta en la tienda de autos de Goofy.", + "Para competir, deben entregarse boletos como depósito.", + "Una página especial del dibucuaderno te permite personalizar tu kart.", + "Una página especial del dibucuaderno te permite ver los récords en cada circuito.", + "Una página especial del dibucuaderno te permite exhibir tus trofeos.", + "El Estadio Bola Loca es el circuito más sencillo del Estadio de Goofy.", + "Acres Aéreos tiene más colinas y saltos que ningún otro circuito del Estadio de Goofy.", + "Boulevard Ventisca es el circuito más difícil del Estadio de Goofy.", + ), + TIP_GOLF: ( + # Golfing specific + "Pulsa la tecla Tab para ver una imagen aérea del campo de golf.", + "Pulsa la tecla de flecha arriba para colocarte en dirección al hoyo de golf.", + "El palo de golf se mueve igual que se lanza una tarta.", + ), + } + +FishGenusNames = { + 0 : "Pez globo", + 2 : "Pez gato", + 4 : "Pez payaso", + 6 : "Pez congelado", + 8 : "Estrella de mar", + 10 : "Merluza de colar", + 12 : "Pez perro", + 14 : "Anguila amorosa", + 16 : "Tiburón matrona", + 18 : "Rey centollo", + 20 : "Pez luna", + 22 : "Caballito de mar", + 24 : "Tiburón billarista", + 26 : "Barboso", + 28 : "Trucha pirata", + 30 : "Atún piano", + 32 : "Bocadidusa", + 34 : "Raya diablo", + } + +FishSpeciesNames = { + 0 : ( "Pez globo", + "Pez globo aerostático", + "Pez globo meteorológico", + "Pez globo de agua", + "Pez globo rojo", + ), + 2 : ( "Pez gato", + "Pez gato siamés", + "Pez gato callejero", + "Pez gato atigrado", + "Pez gato montés", + ), + 4 : ( "Pez payaso", + "Pez payaso triste", + "Pez payaso cumpleañero", + "Pez payaso circense", + ), + 6 : ( "Pez congelado", + ), + 8 : ( "Estrella de mar", + "Estrella de mar de 5 puntas", + "Estrella de mar de rock", + "Estrella de mar del alba", + "Estrella de mar fugaz", + ), + 10 : ( "Merluza de colar", + ), + 12 : ( "Pez perro", + "Pez perro de presa", + "Pez perro caliente", + "Pez perro dálmata", + "Pez perro cachorro", + ), + 14 : ( "Anguila amorosa", + "Anguila eléctrica amorosa", + ), + 16 : ( "Tiburón matrona", + "Tiburón matrona Clara", + "Tiburón matrona Feliciana", + ), + 18 : ( "Rey Centollo", + "Príncipe Centollo", + "Regente Centollo", + ), + 20 : ( "Pez luna", + "Pez luna llena", + "Pez media luna", + "Pez luna nueva", + "Pez luna creciente", + "Pez luna de miel", + ), + 22 : ( "Caballito de mar", + "Caballito de Troya de mar", + "Caballito percherón de mar", + "Caballito árabe de mar", + ), + 24 : ( "Tiburón billarista", + "Tiburón billarista aprendiz", + "Tiburón billarista experto", + "Tiburón billarista olímpico", + ), + 26 : ( "Barboso pardo", + "Barboso negro", + "Barboso koala", + "Barboso madroñero", + "Barboso polar", + "Barboso panda", + "Barboso pardo", + "Barboso gris", + ), + 28 : ( "Trucha pirata", + "Trucha pirata corsaria", + "Trucha pirata bucanera", + ), + 30 : ( "Atún piano", + "Atún piano de cola", + "Atún clavicordio", + "Atún piano vertical", + "Atún piano eléctrico", + ), + 32 : ( "Bocadidusa", + "Bocadidusa de queso", + "Bocadidusa crujiente", + "Bocadidusa de mermelada", + "Bocadidusa de uva", + ), + 34 : ( "Raya diablo", + ), + } + +FishFirstNames = ( + "", + "Anchoa", + "Anguila", + "Anzuela", + "Arenquilla", + "Besuguito", + "Beto", + "Bocartín", + "Brequita", + "Caballero", + "Castañeta", + "Cazonio", + "Chicharra", + "Chicho", + "Congriano", + "Corroncho", + "Curro", + "Deslenguada", + "Don", + "Doña", + "Dorada", + "Emperadora", + "Escarcho", + "Escorpina", + "Escualo", + "Esturiona", + "Fanequillo", + "Florinda", + "Ilustrísima", + "Jurelio", + "Kiko", + "Lisa", + "Lubino", + "Lucio", + "Nelson", + "Nemo", + "Neptuno", + "Nina", + "Palmira", + "Palometa", + "Perco", + "Pintarroja", + "Rapero", + "Robaldiño", + "Salmonillo", + "Sardino", + "Sargonete", + "Señora", + "Señorita", + "Simbad", + "Su Eminencia", + "Su Excelencia", + "Su Majestad", + "Tintorero", + "Tita", + "Torpedo", + "Tritón", + "Trucha", + "Zompo", + ) + +FishLastPrefixNames = ( + "", + "Agalla", + "Agua", + "Aleta", + "Alga", + "Ancla", + "Angula", + "Arena", + "Bahía", + "Ballena", + "Banda", + "Barbilla", + "Boca", + "Lindo", + "Branquia", + "Cabeza", + "Caña", + "Cara", + "Carnaza", + "Chalupa", + "Chepa", + "Cinta", + "Cococha", + "Concha", + "Corbeta", + "Corriente", + "Curricana", + "Escama", + "Espalda", + "Espina", + "Fragata", + "Gaviota", + "Laguna Rojo", + "Mar", + "Marejada", + "Medusa", + "Merluza", + "Nariz", + "Ola", + "Orilla", + "Panza", + "Pecera", + "Pescadilla", + "Pinza", + "Plancton", + "Playa", + "Raspa", + "Raya", + "Rémora", + "Ría", + "Ribera", + "Roca Saludar", + "Salta", + "Sirena", + "Vela", + "Zambullida", + ) + +FishLastSuffixNames = ( + "", + "del norte", + "del sur", + "con filo", + "en ahumado", + "de olor", + "arénquez", + "atigrado", + "azul", + "besúguez", + "en blanco", + "doble", + "oro", + "fantasma", + "finura", + "frescor", + "en frito", + "gállez", + "gato", + "gordura", + "gris", + "limón", + "lubinez", + "en morado", + "a motas", + "naranja", + "occidental", + "oriental", + "en plano", + "plata", + "profundidad", + "raya", + "a rayas", + "rrodabállez", + "rroja", + "rrosa", + "en sal", + "surf", + "tropical", + "trúchez", + "verde", + ) + +CogPartNames = ( + "Muslo izquierdo", "Pantorrilla izquierda", "Pie izquierdo", + "Muslo derecho", "Pantorrilla derecha", "Pie derecho", + "Hombro izquierdo", "Hombro derecho", "Pecho", "Indicador de salud", "Caderas", + "Brazo izquierdo", "Antebrazo izquierdo", "Mano izquierda", + "Brazo derecho", "Antebrazo derecho", "Mano derecha", + ) + +CogPartNamesSimple = ( + "Torso", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrada principal" +SellbotLegFactorySpecLobby = "Vestíbulo" +SellbotLegFactorySpecLobbyHallway = "Entrada del vestíbulo" +SellbotLegFactorySpecGearRoom = "Sala de máquinas" +SellbotLegFactorySpecBoilerRoom = "Sala de calderas" +SellbotLegFactorySpecEastCatwalk = "Pasarela este" +SellbotLegFactorySpecPaintMixer = "Mezcladora de pintura" +SellbotLegFactorySpecPaintMixerStorageRoom = "Sala de la mezcladora de pintura" +SellbotLegFactorySpecWestSiloCatwalk = "Pasarela del silo oeste" +SellbotLegFactorySpecPipeRoom = "Sala de tuberías" +SellbotLegFactorySpecDuctRoom = "Sala de conductos" +SellbotLegFactorySpecSideEntrance = "Entrada de servicio" +SellbotLegFactorySpecStomperAlley = "Callejón del pisotón" +SellbotLegFactorySpecLavaRoomFoyer = "Entrada a la sala de la lava" +SellbotLegFactorySpecLavaRoom = "Sala de la lava" +SellbotLegFactorySpecLavaStorageRoom = "Sala donde se guarda la lava" +SellbotLegFactorySpecWestCatwalk = "Pasarela oeste" +SellbotLegFactorySpecOilRoom = "Sala del aceite" +SellbotLegFactorySpecLookout = "Cabina de vigilancia" +SellbotLegFactorySpecWarehouse = "Almacén" +SellbotLegFactorySpecOilRoomHallway = "Entrada a la sala del aceite" +SellbotLegFactorySpecEastSiloControlRoom = "Sala de control del silo este" +SellbotLegFactorySpecWestSiloControlRoom = "Sala de control del silo oeste" +SellbotLegFactorySpecCenterSiloControlRoom = "Sala de control del silo central" +SellbotLegFactorySpecEastSilo = "Silo este" +SellbotLegFactorySpecWestSilo = "Silo oeste" +SellbotLegFactorySpecCenterSilo = "Silo central" +SellbotLegFactorySpecEastSiloCatwalk = "Pasarela del silo este" +SellbotLegFactorySpecWestElevatorShaft = "Hueco del ascensor del silo oeste" +SellbotLegFactorySpecEastElevatorShaft = "Hueco del ascensor del silo este" + +#FISH BINGO +FishBingoBingo = "¡BINGO!" +FishBingoVictory = "¡¡VICTORIA!!" +FishBingoJackpot = "¡POZO!" +FishBingoGameOver = "FIN DEL JUEGO" +FishBingoIntermission = "Descanso\ntermina en:" +FishBingoNextGame = "Siguiente juego\nempieza en:" +FishBingoTypeNormal = "Clásico" +FishBingoTypeCorners = "Cuatro esquinas" +FishBingoTypeDiagonal = "Diagonales" +FishBingoTypeThreeway = "A tres líneas" +FishBingoTypeBlockout = "¡COMPLETO!" +FishBingoStart = "¡Es hora de jugar al bingo de los peces! ¡Ve a cualquier muelle disponible para jugar!" +FishBingoOngoing = "¡Bienvenido! El bingo de los peces ya comenzó." +FishBingoEnd = "Espero que te hayas divertido con el bingo de los peces." +FishBingoHelpMain = "¡Bienvenido al bingo de los peces de Toontown! En el estanque, todo el mundo colabora para llenar la tarjeta antes de que se acabe el tiempo." +FishBingoHelpFlash = "Cuando atrapes un pez, haz clic en una de las casillas que parpadean para marcar la tarjeta." +FishBingoHelpNormal = "Esto es una tarjeta de bingo clásica. Marca cualquier casilla en vertical, horizontal o diagonal para ganar." +FishBingoHelpDiagonals = "Marca las dos diagonales para ganar." +FishBingoHelpCorners = "Una tarjeta sencilla de esquinas. Marca las cuatro esquinas para ganar." +FishBingoHelpThreeway = "Tres líneas. Marca las dos diagonales y la fila del medio para ganar. ¡Esta no es fácil!" +FishBingoHelpBlockout = "¡Completo! Marca toda la tarjeta para ganar. ¡Compites con el resto de estanques para hacerte con un gran premio!" +FishBingoOfferToSellFish = "Tu cubeta de peces está lleno. ¿Quieres vender tus peces?" +FishBingoJackpotWin = "¡Gana %s golosinas!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Curadibu" +ResistanceToonupItem = "Curadibu %s" +ResistanceToonupItemMax = "Máx." +ResistanceToonupChat = "¡Dibus del mundo, al curadibu!" +ResistanceRestockMenu = "Mejorar broma" +ResistanceRestockItem = "Mejorar broma %s" +ResistanceRestockItemAll = "Todo" +ResistanceRestockChat = "¡Dibus del mundo, a mejorar esas bromas!" +ResistanceMoneyMenu = "golosinas" +ResistanceMoneyItem = "%s golosinas" +ResistanceMoneyChat = "¡Dibus del mundo, a gastar con cabeza!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": ¡Bienvenido a la resistencia!" +ResistanceEmote2 = NPCToonNames[9228] + ": Utiliza tu nuevo emoticono para identificarte ante el resto de miembros." +ResistanceEmote3 = NPCToonNames[9228] + ": ¡Buena suerte!" + +# Kart racing +KartUIExit = "Dejar el kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Comprar kart" +KartShop_BuyAccessories = "Comprar accesorios" +KartShop_BuyAccessory = "Comprar accesorio" +KartShop_Cost = "Precio: %d boletos" +KartShop_ConfirmBuy = "¿Comprar %s por %d boletos?" +KartShop_NoAvailableAcc = "No hay accesorios de este tipo disponibles" +KartShop_FullTrunk = "Tienes el portaequipaje lleno." +KartShop_ConfirmReturnKart = "¿Seguro que quieres devolver tu kart?" +KartShop_ConfirmBoughtTitle = "¡Felicidades!" +KartShop_NotEnoughTickets = "¡No tienes suficientes boletos!" + +KartView_Rotate = "Girar" +KartView_Right = "Derecha" +KartView_Left = "Izquierda" + +# starting block +StartingBlock_NotEnoughTickets = "¡No tienes suficientes boletos! Puedes probar una carrera de práctica." +StartingBlock_NoBoard = "Ya no se aceptan participantes para esta carrera. Espera a que comience la siguiente." +StartingBlock_NoKart = "¡Primero necesitas un kart! Habla con uno de los dependientes de la tienda de karts." +StartingBlock_Occupied = "¡Este bloque está ocupado! Prueba en otro lugar." +StartingBlock_TrackClosed = "Este circuito está cerrado por reformas." +StartingBlock_EnterPractice = "¿Quieres participar en una carrera de práctica?" +StartingBlock_EnterNonPractice = "¿Quieres participar en una carrera por %s boletos?" +StartingBlock_EnterShowPad = "¿Quieres aparcar aquí tu auto?" +StartingBlock_KickSoloRacer = "Para las carreras Batalla Dibu y Grand Prix se necesitan dos o más corredores." +StartingBlock_Loading = "¡A la carrera!" + +#stuff for leader boards +LeaderBoard_Time = "Tiempo" +LeaderBoard_Name = "Nombre del piloto" +LeaderBoard_Daily = "Puntuaciones diarias" +LeaderBoard_Weekly = "Puntuaciones semanales" +LeaderBoard_AllTime = "Récords generales" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Práctica", + "Batalla Dibu", + "Grand Prix", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "¡Ya!" +KartRace_Reverse = " Rev" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Estadio Bola Loca", + RaceGlobals.RT_Speedway_1_rev : "Estadio Bola Loca" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Circuito Rústico", + RaceGlobals.RT_Rural_1_rev : "Circuito Rústico" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "Circuito Urbano", + RaceGlobals.RT_Urban_1_rev : "Circuito Urbano" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Coliseo Sacacorchos", + RaceGlobals.RT_Speedway_2_rev : "Coliseo Sacacorchos" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Acres Aéreos", + RaceGlobals.RT_Rural_2_rev : "Acres Aéreos" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Boulevard Ventisca", + RaceGlobals.RT_Urban_2_rev : "Boulevard Ventisca" + KartRace_Reverse, + } + +KartRace_Unraced = "N/A" + +KartDNA_KartNames = { + 0:"Cruiser", + 1:"Roadster", + 2:"Utilitario dibu" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Filtro de aire", + 1001: "Cuatro barriles", + 1002: "Águila voladora", + 1003: "Cono de dirección", + 1004: "Seis directa", + 1005: "Pala pequeña", + 1006: "Cubierta simple", + 1007: "Pala mediana", + 1008: "Barril simple", + 1009: "Corneta", + 1010: "Pala a rayas", + #spoiler accessory names + 2000: "Ala espacial", + 2001: "Repuesto parche", + 2002: "Jaula rodillo", + 2003: "Aleta simple", + 2004: "Ala doble", + 2005: "Ala simple", + 2006: "Repuesto estándar", + 2007: "Aleta simple", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "Cono de duelo", + 3001: "Guardabarros Freddie", + 3002: "Estribos de cobalto", + 3003: "Tubos laterales Cobra", + 3004: "Tubos laterales rectos", + 3005: "Guardabarros festoneados", + 3006: "Estribos de carbón", + 3007: "Estribos de madera", + 3008: "rd9", + 3009: "rd10", + #rear wheel well accessory names (twisty twisty) + 4000: "Tubos de escape enrulado", + 4001: "Guardabarros splash", + 4002: "Tubo de escape doble", + 4003: "Aletas dobles sin adorno", + 4004: "Faldones sin adornos", + 4005: "Tubo de escape quad", + 4006: "Extensiones dobles", + 4007: "Mega tubo de escape", + 4008: "Aletas dobles a rayas", + 4009: "Aletas dobles burbuja", + 4010: "Faldones a rayas", + 4011: "Faldones Mickey", + 4012: "Faldones festoneados", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "Turbo", + 5001: "Luna", + 5002: "Parches", + 5003: "Tres radios", + 5004: "Tapa de pintura", + 5005: "Corazón", + 5006: "Mickey", + 5007: "Cinco rayos", + 5008: "Daisy", + 5009: "Basket", + 5010: "Hipno", + 5011: "Tribal", + 5012: "Piedra preciosa", + 5013: "Cinco radios", + 5014: "Derribo", + #decal accessory names + 6000: "Número cinco", + 6001: "Salpicado", + 6002: "Ajedrez", + 6003: "Llamas", + 6004: "Corazones", + 6005: "Burbujas", + 6006: "Tigre", + 6007: "Flores", + 6008: "Rayo", + 6009: "Ángel", + #paint accessory names + 7000: "Chartreuse", + 7001: "Melocotón", + 7002: "Rojo brillante", + 7003: "Rojo", + 7004: "Granate", + 7005: "Siena", + 7006: "Marrón", + 7007: "Tostado", + 7008: "Coral", + 7009: "Naranja", + 7010: "Amarillo", + 7011: "Crema", + 7012: "Citrina", + 7013: "Lima", + 7014: "Verde marino", + 7015: "Verde", + 7016: "Azul claro", + 7017: "Aqua", + 7018: "Azul", + 7019: "Violeta", + 7020: "Azul real", + 7021: "Azul pizarra", + 7022: "Morado", + 7023: "Lavanda", + 7024: "Rosa", + 7025: "Ciruela", + 7026: "Negro", + } + +RaceHoodSpeedway = "Estadio" +RaceHoodRural = "Rural" +RaceHoodUrban = "Urbano" +RaceTypeCircuit = "Campeonato" +RaceQualified = "clasificado" +RaceSwept = "barrido" +RaceWon = "ganado" +Race = "carrera" +Races = "carreras" +Total = "total" +GrandTouring = "Grand Tour" + +def getTrackGenreString(genreId): + genreStrings = [ "Estadio", + "Campo", + "Ciudad" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " trofeos de carreras de kart conseguidos! ¡Aumento del risómetro!", + str(RaceGlobals.TrophiesPerCup * 2) + " trofeos de carreras de kart conseguidos! ¡Aumento del risómetro!", + str(RaceGlobals.TrophiesPerCup * 3) + " trofeos de carreras de kart conseguidos! ¡Aumento del risómetro!", + ] + +KartRace_TitleInfo = "Prepárate para la carrera" +KartRace_SSInfo = "¡Bienvenido al Estadio Bola Loca!\n¡Pisa a fondo y agárrate fuerte!\n" +KartRace_CoCoInfo = "¡Bienvenido al Coliseo Sacacorchos!\n¡Apovecha los giros inclinados para mantener tu aceleración!\n" +KartRace_RRInfo = "¡Bienvenido al Circuito Rústico!\n¡Sé amable con la fauna y no te salgas del circuito!\n" +KartRace_AAInfo = "¡Bienvenido a Acres Aéreos!\n¡Agárrate fuerte! Esto está lleno de baches...\n" +KartRace_CCInfo = "¡Bienvenido al Circuito Urbano!\n¡Cuidado con los peatones cuando atravieses la ciudad como un rayo!\n" +KartRace_BBInfo = "¡Bienvenido al Boulevard Ventisca!\nVigila tu velocidad. Podría haber placas de hielo.\n" +KartRace_GeneralInfo = "Utiliza Control para lanzar las bromas que recoges en el circuito y las teclas de flecha para controlar tu kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'diario', + RaceGlobals.Weekly : 'semanal', + RaceGlobals.AllTime : 'todo el tiempo', + } + +KartRace_FirstSuffix = 'º' +KartRace_SecondSuffix = ' º' +KartRace_ThirdSuffix = ' º' +KartRace_FourthSuffix = ' º' +KartRace_WrongWay = '¡Dirección\nEquivocada!' +KartRace_LapText = "Vuelta %s" +KartRace_FinalLapText = "¡Úlitma vuelta!" +KartRace_Exit = "Salir de la carrera" +KartRace_NextRace = "Siguiente carrera" +KartRace_Leave = "Abandonar la carrera" +KartRace_Qualified = "¡Clasificaste!" +KartRace_Record = "¡Récord!" +KartRace_RecordString = '¡Batiste un nuevo récord %s para %s! Recibes una bonificación de %s boletos.' +KartRace_Tickets = "Boletos" +KartRace_Exclamations = "¡!" +KartRace_Deposit = "Depósito" +KartRace_Winnings = "Ganancias" +KartRace_Bonus = "Bonificación" +KartRace_RaceTotal = "Total de la carrera" +KartRace_CircuitTotal = "Total del circuito" +KartRace_Trophies = "Trofeos" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s " + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "Clasificación:\n" +KartRace_RaceTimeout = "Se te agotó el tiempo de espera en la carrera. Tus boletos fueron devueltos. ¡Sigue intentándolo!" +KartRace_RaceTimeoutNoRefund = "Se te agotó el tiempo de espera en la carrera. Tus boletos no fueron devueltos porque el Grand Prix ya había comenzado. ¡Sigue intentándolo!" +KartRace_RacerTooSlow = "Tardaste demasiado en completar la carrera. Tus boletos no fueron devueltos. ¡Sigue intentándolo!" +KartRace_PhotoFinish = "¡Final fotográfico!" +KartRace_CircuitPoints = "Puntos del circuit" + +CircuitRaceStart = "El Grand Prix de Toontown en el Estadio de Goofy está a punto de comenzar! ¡Para ganar, obtén más puntos que nadie en tres carreras consecutivas!" +CircuitRaceOngoing = "¡Bienvenido! El Grand Prix de Toontown ya comenzó." +CircuitRaceEnd = "Eso es todo por hoy en el Grand Prix de Toontown del Estadio de Goofy. ¡Hasta la semana que viene!" + +# Trick-or-Treat holiday +TrickOrTreatMsg = '¡Ya encontraste este\npremio!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Veamos, ¿qué tenemos hoy en la lista?" +LawbotBossTempIntro1 = "¡Ajá, tenemos a un dibu procesado!" +LawbotBossTempIntro2 = "La acusación tiene buenos argumentos." +LawbotBossTempIntro3 = "Y aquí está el jurado." +LawbotBossTempIntro4 = "Un momento... ¡Son dibus!" +LawbotBossTempJury1 = "A continuación, se inicia la selección del jurado." +LawbotBossHowToGetEvidence = "Toca la tribuna de testigos para conseguir pruebas." +LawbotBossTrialChat1 = "La sesión del juicio comenzó" +LawbotBossHowToThrowPies = "¡Pulsa la tecla Borrar para lanzar las pruebas\n a los abogados o sobre la balanza!" +LawbotBossNeedMoreEvidence = "¡Necesitas conseguir más pruebas!" +LawbotBossDefenseWins1 = "¡Imposible! ¿La defensa ganó?" +LawbotBossDefenseWins2 = "No. ¡Se anula el juicio! Se programará uno nuevo." +LawbotBossDefenseWins3 = "Ummm, estaré en mi despacho." +LawbotBossProsecutionWins = "Fallo a favor del demandante" +LawbotBossReward = "Concedo un ascenso y la capacidad para invocar bots" +LawbotBossLeaveCannon = "Salir del cañón" +LawbotBossPassExam = "Bah, así que pasaste el examen de letrados." +LawbotBossTaunts = [ + "¡%s, te acuso de desacato al tribunal!", + "¡Protesta aceptada!", + "Que eso no conste en acta.", + "Tu apelación fue rechazada. ¡Te condeno a la tristeza!", + "¡Orden en la sala!", + ] +LawbotBossAreaAttackTaunt = "¡Desacato al tribunal!" + + +WitnessToonName = "Golpetón Agitado" +WitnessToonPrepareBattleTwo = "¡Oh no! ¡En el jurado son todos bots!\a¡Rápido, utiliza los cañones y lanza a algunos dibus hacia las sillas del jurado.\aNecesitamos %d para equilibrar la balanza." +WitnessToonNoJuror = "Oh oh, no hay dibus en el jurado. El juicio va a estar difícil." +WitnessToonOneJuror = "¡Genial! ¡Hay 1 dibu en el jurado!" +WitnessToonSomeJurors = "¡Genial! ¡Hay %d dibus en el jurado!" +WitnessToonAllJurors = "¡Fantástico! ¡Todos los miembros del jurado son dibus!" +WitnessToonPrepareBattleThree = "Rápido, toca la tribuna de testigos para conseguir pruebas.\aPulsa la tecla Borrar para lanzar las pruebas hacia los abogados, o hacia el panel de defensa." +WitnessToonCongratulations = "¡Lo lograste! ¡Gracias por esa defensa tan espectacular!\aToma, llévate estos papeles que se dejó el Juez.\aTe permitirán invocar bots de tu página de galería de bots." + +WitnessToonLastPromotion = "\a¡Uau, alcanzaste el nivel %s con tu traje bot!\aLos bots no pueden ascender más alto.\aYa no puedes mejorar tu traje de bot, ¡pero puedes seguir trabajando para la Resistencia!" +WitnessToonHPBoost = "\aHiciste un gran trabajo para la Resistencia.\aEl Consejo Dibu decidió concederte otro punto de risa. ¡Felicidades!" +WitnessToonMaxed = "\aVeo que tienes un traje de bot de nivel %s. ¡Impresionante!\a¡En nombre del Consejo Dibu: gracias por regresar para defender a más dibus!" +WitnessToonBonus = "¡Fantástico! Todos los abogados están aturdidos. El peso de tus pruebas es %s veces más pesado durante %s segundos" + +WitnessToonJuryWeightBonusSingular = { + 6: 'Este es un caso difícil. Sentaste a %d dibu en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', + 7: 'Este es un caso muy difícil. Sentaste a %d dibu en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', + 8: 'Este es el caso más difícil. Sentaste a %d dibu en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'Este es un caso difícil. Sentaste a %d dibus en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', + 7: 'Este es un caso muy difícil. Sentaste a %d dibus en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', + 8: 'Este es el caso más difícil. Sentaste a %d dibus en el jurado, por lo que tus pruebas tienen una bonificación de peso de %d.', +} + +# Cog Summons stuff +IssueSummons = "Invocar" +SummonDlgTitle = "Iniciar una invocación bot" +SummonDlgButton1 = "Invocar a un bot" +SummonDlgButton2 = "Invocar un edificio bot" +SummonDlgButton3 = "Invocar una invasión bot" +SummonDlgSingleConf = "¿Quieres iniciar una invocación para %s?" +SummonDlgBuildingConf = "¿Quieres invocar a un %s en un edificio dibu cercano?" +SummonDlgInvasionConf = "¿Quieres invocar una invasión %s?" +SummonDlgNumLeft = "Te quedan %s." +SummonDlgDelivering = "Entregando invocaciones..." +SummonDlgSingleSuccess = "Lograste invocar al bot." +SummonDlgSingleBadLoc = "Lo siento, aquí no se permite la presencia de un bot. Prueba en otro sitio." +SummonDlgBldgSuccess = "Lograste invocar a los bots. ¡%s aceptó dejarles ocupar %s temporalmente!" +SummonDlgBldgSuccess2 = "Lograste invocar a los bots. ¡Un dependiente aceptó dejarles ocupar su edificio temporalmente!" +SummonDlgBldgBadLoc = "Lo siento, no hay edificios dibu cerca que puedan ocupar los bots." +SummonDlgInvasionSuccess = "Lograste invocar a los bots. ¡Es una invasión!" +SummonDlgInvasionBusy = "No se encontró ningún %s. Inténtalo de nuevo cuando haya terminado la invasión bot." +SummonDlgInvasionFail = "La invasión bot fracasó." +SummonDlgShopkeeper = "El dependiente " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": ¡Bienvenido al Punto Polar!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Prueba el tamaño de este." +PolarPlaceEffect3 = NPCToonNames[3306] + ": Tu nueva imagen sólo funcionará en " + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "¡Busca cráneos!" +LaserGameRoll = "Coincidencia" +LaserGameAvoid = "Evita los cráneos" +LaserGameDrag = "Arrastra en fila tres de un color" +LaserGameDefault = "Juego desconocido" + +# Pinball text +#PinballHiScore = "Récord: %d %s\n" +#PinballYourBestScore = "Tu mejor puntaje: %d\n" +#PinballScore = "Puntaje: %d x %d : %d" +PinballHiScore = "Récord: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Tu mejor puntaje:\n" +PinballScore = "Puntaje: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Árbol de bromas emplumado" +GagTreeJugglingBalls = "Árbol de bromas malabarista" +StatuaryFountain = "Fuente" +StatuaryDonald = "Estatua de Donald" +StatuaryMinnie = "Estatua de Minnie" +StatuaryMickey1 = "Estatua de Mickey" +StatuaryMickey2 = "Fuente de Mickey" +StatuaryToon = "Estatua dibu" +StatuaryToonWave = "Estatua ola dibu" +StatuaryToonVictory = "Estatua victoria dibu" +StatuaryToonCrossedArms = 'Estatua autoridad dibu' +StatuaryToonThinking = 'Estatua abrazo dibu' +StatuaryMeltingSnowman = 'Muñeco de nieve derretido' +StatuaryGardenAccelerator = "Fertilizante instantáneo" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Rojo','Naranja','Morado','Azul','Rosa','Amarillo','Blanco','Verde'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Margarita', + 50: 'Tulipán', + 51: 'Clavel', + 52: 'Lirio', + 53: 'Narciso', + 54: 'Pensamiento', + 55: 'Petunia', + 56: 'Rosa', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('Margarita Escolar', + 'Margarita Perezosa', + 'Margarita Veraniega', + 'Margarita Frescura', + 'Margarita Rita', + 'Margarita Pajarita', + 'Margarita Pita', + 'Marga Larga', + ), + 50: ('Tulipano', + 'Tulidós', + 'Tulitres', + ), + 51: ('Clavelito', + 'Clavel de un Día', + 'Clavel Híbrido', + 'Clavel Clavón', + 'Clavel Modelo', + ), + 52: ('Lirio Cirio', + 'Lirio Lirondo', + 'Lirio Lírico', + 'Lirio Hepático', + 'Lirio Picante', + 'Lirio De-Lirio', + 'Lirió Mucho', + 'Lirio Martirio', + ), + 53: ('Narciso Narcisista', + 'Narciso Omiso', + 'Nardo Narciso', + 'Cenar-Ciso', + ), + 54: ('Pensamiento Pasajero', + 'Pensa Miento', + 'Pensamiento Impuro', + 'Pienso Momento', + 'Pensamiento Talmente' + ), + 55: ('Petunia Petón', + 'Platunia', + ), + 56: ("Última Rosa del Verano", + 'Rosa Lía', + 'Olo Rosa', + 'Rosa Aromática', + 'Amo Rosa', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "Hojalata", + 1 : "Bronce", + 2 : "Plata", + 3 : "Oro", + } +WateringCanNameDict = { + 0 : "Pequeña", + 1 : "Mediana", + 2 : "Grande", + 3 : "Enorme", + } +GardeningPlant = "Plantar" +GardeningWater = "Regar" +GardeningRemove = "Eliminar" +GardeningPick = "Agarrar" +GardeningFull = "Lleno" +GardeningSkill = "Habilidad" +GardeningWaterSkill = "Habilidad de riego" +GardeningShovelSkill = "Habilidad con la pala" +GardeningNoSkill = "No mejora de habilidad" +GardeningPlantFlower = "Plantar\nflor" +GardeningPlantTree = "Plantar\nárbol" +GardeningPlantItem = "Plantar\nobjeto" +PlantingGuiOk = "Plantar" +PlantingGuiCancel = "Cancelar" +PlantingGuiReset = "Restablecer" +GardeningChooseBeans = "Elige las golosinas que quieres plantar." +GardeningChooseBeansItem = "Elige las golosinas / el objeto que quieras plantar." +GardeningChooseToonStatue = "Elige el dibu al que quieras hacerle una estatua." +GardenShovelLevelUp = "¡Felicidades, te ganaste una %(shovel)s! ¡Dominaste la flor de la golosina %(oldbeans)d! Para progresar, deberías recoger %(newbeans)d flores de golosina." +GardenShovelSkillLevelUp = "¡Felicidades! ¡Dominaste la flor de la golosina %(oldbeans)d! Para progresar, deberías recoger %(newbeans)d flores de golosina." +GardenShovelSkillMaxed = "¡Increíble! ¡Lograste el máximo nivel de habilidad con la pala!" + +GardenWateringCanLevelUp = "¡Felicitaciones, ganaste una regadera nueva!" +GardenMiniGameWon = "¡Felicitaciones, regaste la planta!" +ShovelTin = "Pala de hojalata" +ShovelSteel = "Pala de bronce" +ShovelSilver = "Pala de plata" +ShovelGold = "Pala de oro" +WateringCanSmall = "Regadera pequeña" +WateringCanMedium = "Regadera mediana" +WateringCanLarge = "Regadera grande" +WateringCanHuge = "Regadera enorme" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('roja', 'verde', 'naranja','morada','azul','rosa','amarilla', + 'celeste','plateada') +PlantItWith = " Plantar con %s." +MakeSureWatered = " Asegúrate primero de que todas tus plantas están regadas." +UseFromSpecialsTab = " Úsalo desde la pestaña de objetos especiales de la página del jardín." +UseSpecial = "Usar objeto especial" +UseSpecialBadLocation = 'Eso sólo lo puedes usar en tu jardín.' +UseSpecialSuccess = '¡Conseguido! Las plantas que regaste acaban de crecer.' +ConfirmWiltedFlower = "%(plant)s se marchitó. ¿Seguro que quieres retirarla? No irá a tu cesta de flores ni mejorará tu habilidad." +ConfirmUnbloomingFlower = "%(plant)s no está floreciendo. ¿Seguro que quieres quitarla? No irá a tu cesta de flores ni mejorará tu habilidad." +ConfirmNoSkillupFlower = "¿Seguro que quieres quitar el ejemplar de %(plant)s? Irá a tu cesta de flores, pero NO mejorará tu habilidad." +ConfirmSkillupFlower = "¿Seguro que quieres quitar el ejemplar de %(plant)s? Irá a tu cesta de flores, y mejorará tu habilidad." +ConfirmMaxedSkillFlower = "¿Seguro que quieres quitar el ejemplar de %(plant)s? Irá a tu cesta de flores. NO mejorará tu habilidad, pues ya está al máximo nivel." +ConfirmBasketFull = "Tu cesta de flores está llena. Vende primero algunas flores." +ConfirmRemoveTree = "¿Seguro que quieres quitar el árbol %(tree)s?" +ConfirmWontBeAbleToHarvest = " Si quitas este árbol, no podrás recolectar bromas de los árboles de mayor nivel." +ConfirmRemoveStatuary = "¿Seguro que quieres eliminar permanentemente el objeto %(item)s?" +ResultPlantedSomething = "¡Felicitaciones! Acabas de plantar un ejemplar de %s." +ResultPlantedSomethingAn = "¡Felicitaciones! Acabas de plantar un ejemplar de %s." +ResultPlantedNothing = "No funcionó. Prueba con una combinación de golosinas distinta." + +GardenGagTree = " Árbol de bromas" +GardenUberGag = "Súper broma" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s golosinas" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "a %s golosina" % BeanColorWords[beanTuple[0]] + else: + retval += 'un' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " y %s golosina" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Golosinas mágicas" +GardenTextMagicBeansB = "Otras golosinas" +GardenSpecialDiscription = "Este texto explica cómo utilizar un objeto especial de jardín determinado" +GardenSpecialDiscriptionB = "Este texto explica cómo utilizar un objeto especial de jardín determinado." +GardenTrophyAwarded = "¡Uau! Recogiste %s de %s flores. ¡Eso se merece un trofeo y una aumento en el risómetro!" +GardenTrophyNameDict = { + 0 : "Carretilla", + 1 : "Palas", + 2 : "Flor", + 3 : "Regadera", + 4 : "Tiburón", + 5 : "Pez espada", + 6 : "Ballena asesina", + } +SkillTooLow = "Habilidad\ndemasiando baja" +NoGarden = "Ningún\njardín" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "Circuito del tranvía" +TravelGameInstructions = "Haz clic arriba o abajo para definir tu número de votos. Haz clic en el botón del voto para canjearlo. Alcanza tu objetivo secreto para recibir bonificaciones de golosinas. Consigue más votos logrando buenos resultados en los otros juegos." +TravelGameRemainingVotes = "Votos restantes:" +TravelGameUse = "Usar" +TravelGameVotesWithPeriod = "votos." +TravelGameVotesToGo = "votos que faltan" +TravelGameVoteToGo = "votos que faltan" +TravelGameUp = "ARRIBA." +TravelGameDown = "ABAJO." +TravelGameVoteWithExclamation = "¡Vota!" +TravelGameWaitingChoices = "Esperando a que voten los otros jugadores..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['ARRIBA', 'ABAJO'] +TravelGameTotals = 'Totales ' +TravelGameReasonVotesPlural = 'El tranvía se está moviendo hacia %(dir)s, ganando por %(numVotes)d votos.' +TravelGameReasonVotesSingular = 'El tranvía se está moviendo hacia %(dir)s, ganando por %(numVotes)d voto.' +TravelGameReasonPlace = '%(name)s desempata. El tranvía se está moviendo hacia %(dir)s.' +TravelGameReasonRandom = 'El tranvía se está moviendo al azar hacia %(dir)s.' +TravelGameOneToonVote = "%(name)s utilizó %(numVotes)s votos para ir hacia %(dir)s\n" +TravelGameBonusBeans = "%(numBeans)d golosinas" +TravelGamePlaying = 'A continuación, el juego del tranvía %(game)s.' +TravelGameGotBonus = '¡%(name)s tiene una bonificación de %(numBeans)s golosinas!' +TravelGameNoOneGotBonus = "Nadie alcanzó su objetivo secreto. Cada uno recibe 1 golosina." +TravelGameConvertingVotesToBeans = "Transformando votos en golosinas..." +TravelGameGoingBackToShop ="Queda 1 jugador. Accediendo a la tienda de bromas de Goofy." + +PairingGameTitle = "Juego de memoria dibu" +PairingGameInstructions = "Pulsa la tecla Borrar para dar vuelta una carta. Encuentra 2 cartas iguales para ganar un punto. Si haces que coincida la que tiene el brillo de bonificación, ganarás un punto extra. Cuantas menos cartas des vuelta, más puntos ganarás." +PairingGameInstructionsMulti = "Pulsa la tecla Borrar para dar vuelta una carta. Pulsa Control para decirle a otro jugador que de vuelta una carta. Encuentra 2 cartas iguales para conseguir un punto. Si haces que coincida la que tiene el brillo de bonificación, ganarás un punto extra. Cuantas menos cartas des vuelta, más puntos ganarás." +PairingGamePerfect = '¡¡PERFECTO!!' +PairingGameFlips = 'Giros:' +PairingGamePoints = 'Puntos:' + +TrolleyHolidayStart = "¡Está a punto de comenzar el Circuito del tranvía! Para jugar, súbete cualquier tranvía con 2 dibus o más." +TrolleyHolidayOngoing = "¡Bienvenido! El Circuito del tranvía ya comenzó." +TrolleyHolidayEnd = "Eso es todo por hoy en el Circuito del tranvía. ¡Hasta la semana que viene!" + +TrolleyWeekendStart = "¡El fin de semana del Circuito del tranvía está a punto de comenzar! Para jugar, súbete a cualquier tranvía con 2 dibus o más." +TrolleyWeekendEnd = "Eso es todo en el fin de semana del Circuito del tranvía." + +VineGameTitle = "Parras de la selva" +VineGameInstructions = "Llega a tiempo a la viña que está a la derecha. Pulsa arriba o abajo para subir por la viña. Pulsa izquierda o derecha para cambiar de dirección y saltar. Cuanto más abajo de la viña estés, más rápido saltarás. Si puedes, recoge las bananas pero esquiva a los murciélagos y las arañas." + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "Para por el par", + 1: "El hoyo del bollo", + 2: "El palo palomino" + } + +GolfHoleNames = { + 0: 'Tres bajo par-tida', + 1: 'Caddie día algo nuevo', + 2: 'Alcance en trance', + 3: 'Vista en verde', + 4: 'Recorrido barrido', + 5: 'Put Trefacción', + 6: 'Lanza balanza', + 7: 'Para Tee', + 8: 'Arroyo del hoyo', + 9: 'Rueda y ruega', + 10: 'Noches de Bogey', + 11: 'Saque mate', + 12: '¡Olla al hoyo!', + 13: 'El tope del topo', + 14: 'Pali troque', + 15: 'Swing', + 16: 'Hoyo seguido', + 17: 'Segunda ala', + 18: 'Tres bajo par-tida-2', + 19: 'Caddie día algo nuevo-2', + 20: 'Alcance en trance-2', + 21: 'Vista en verde-2', + 22: 'Recorrido barrido-2', + 23: 'Put trefacción-2', + 24: 'Lanza balanza-2', + 25: 'Para Tee-2', + 26: 'Arroyo del hoyo-2', + 27: 'Rueda y ruega-2', + 28: 'Noches de Bogey-2', + 29: 'Saque mate-2', + 30: '¡Olla al hoyo!-2', + 31: 'El tope del topo-2', + 32: 'Pali troque-2', + 33: 'Swing-2', + 34: 'Hoyo seguido-2', + 35: 'Segunda ala-2', + } + +GolfHoleInOne = "Hoyo en uno" +GolfCondor = "Cóndor" # four Under Par +GolfAlbatross = "Albatros" # three under par +GolfEagle = "Águila" # two under par +GolfBirdie = "Pájaro" # one under par +GolfPar = "Par" +GolfBogey = "Bogey" # one over par +GolfDoubleBogey = "Bogey doble" # two over par +GolfTripleBogey = "Bogey triple" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "Recorridos completados" +CoursesUnderPar = "Recorridos bajo par" +HoleInOneShots = "Tiros de hoyo en uno" +EagleOrBetterShots = "Tiros águila o mejores" +BirdieOrBetterShots = "Tiros pájaro o mejores" +ParOrBetterShots = "Tiros par o mejores" +MultiPlayerCoursesCompleted = "Recorridos multijugador completados" +TwoPlayerWins = "Victorias dos jugadores" +ThreePlayerWins = "Victorias tres jugadores" +FourPlayerWins = "Victorias cuatro jugadores" +CourseZeroWins = GolfCourseNames[0] + " Victorias" +CourseOneWins = GolfCourseNames[1] + " Victorias" +CourseTwoWins = GolfCourseNames[2] + " Victorias" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + " Trofeos ganados", + str(GolfGlobals.TrophiesPerCup * 2) + " Trofeos ganados", + str(GolfGlobals.TrophiesPerCup * 3) + " Trofeos ganados", +] + +GolfAvReceivesHoleBest = "¡%(name)s logró un nuevo récord de hoyo en %(hole)s!" +GolfAvReceivesCourseBest = "%(name)s logró un nuevo récord de recorrido en %(course)s!" +GolfAvReceivesCup = "¡%(name)s recibe la copa %(cup)s! ¡Aumento en el risómetro!" +GolfAvReceivesTrophy = "¡%(name)s recibe el trofeo %(award)s!" +GolfRanking = "Ranking: \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "Pulsa izquierda o derecha para cambiar el punto de tee.\nPulsa Control para seleccionar." +GolfWarningMustSwing = "Advertencia: debes pulsar Control en tu próximo swing." +GolfAimInstructions = "Pulsa izquierda o derecha para apuntar.\nPulsa y mantén Control pulsado para hacer el swing." +GolferExited = "%s abandonó el campo de golf." +GolfPowerReminder = "Mantén Control pulsado más tiempo para\nque la pelota llegue más lejos" + + +# GolfScoreBoard.py +GolfPar = "Par" +GolfHole = "Hoyo" +GolfTotal = "Total" +GolfExitCourse = "Salir del recorrido" +GolfUnknownPlayer = "???" + +# GolfPage.py +GolfPageTitle = "Golf" +GolfPageTitleCustomize = "Personalizador de golf" +GolfPageTitleRecords = "Récords personales" +GolfPageTitleTrophy = "Trofeos de golf" +GolfPageCustomizeTab = "Personalizar" +GolfPageRecordsTab = "Récords" +GolfPageTrophyTab = "Trofeo" +GolfPageTickets = "Boletos : " +GolfPageConfirmDelete = "¿Borrar accesorio?" +GolfTrophyTextDisplay = "Trofeo %(number)s : %(desc)s" +GolfCupTextDisplay = "Copa %(number)s : %(desc)s" +GolfCurrentHistory = "%(historyDesc)s actual : %(num)s" +GolfTieBreakWinner = "¡%(name)s gana el desempate aleatorio!" +GolfSeconds = " - %(time).2f segundos" +GolfTimeTieBreakWinner = "¡¡¡%(name)s gana el desempate de tiempo total de puntería!!!" + + + +RoamingTrialerWeekendStart = "¡Empieza el Tour Toontown! ¡Los jugadores libres ya pueden acceder a cualquier barrio!" +RoamingTrialerWeekendOngoing = "¡Bienvenido al Tour Toontown! ¡Los jugadores libres ya puedne acceder a cualquier barrio!" +RoamingTrialerWeekendEnd = "Eso es todo en Tour Toontown." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "¡Buenas noticias! Ya comenzó la experiencia por el doble de bromas Test Toon." +MoreXpHolidayOngoing = "¡Bienvenido! La experiencia exclusiva por el doble de bromas Test Toon está en marcha." +MoreXpHolidayEnd = "La experiencia exclusiva por el doble de bromas Test Toon ya finalizó. ¡Gracias por ayudarnos con los tests!" + +JellybeanDayHolidayStart = "¡Es el Día de la Golosina! ¡Consigue el doble de golosinas en las fiestas!" +JellybeanDayHolidayEnd = "Eso es todo en el Día de la Golosina. Hasta el año que viene." +PartyRewardDoubledJellybean = "¡El doble de golosinas!" + +GrandPrixWeekendHolidayStart = "¡Empieza el fin de semana Grand Prix en el Estadio de Goofy! Los jugadores libres y los pagados acumulan la mayor cantidad de puntos en tres carreras consecutivas." +GrandPrixWeekendHolidayEnd = "Eso es todo en el fin de semana Grand Prix. Hasta el año que viene." + +LogoutForced = "Hiciste algo indebido\n y serás desconectado automáticamente;\n además, es posible que tu cuenta sea congelada.\n Sal a dar un paseo, será divertido." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \nsaltó al carrito de golf." +CountryClubBossConfrontedMsg = "¡%s se está peleando con el presidente del club!" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "Primero hay que superar todos los desafíos." + +# DistributedMolefield.py +MolesLeft = "Topos restantes: %d" +MolesInstruction = "¡Pisotón de topo!\n¡Salta sobre los topos rojos!" +MolesFinished = "¡Pisotón de topo conseguido!" +MolesRestarted = "¡Fallo en el Pisotón! Reiniciando..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "¡Quita la bola bot!" +BustACogExit = "Salir por ahora" +BustACogHowto = "Cómo se juega" +BustACogFailure = "¡Se acabó el tiempo!" +BustACogSuccess = "¡Conseguido!" + +# bossbot golf green games +GolfGreenGameScoreString = "Instancias de ingenio restantes: %s" +GolfGreenGamePlayerScore = "Resuelto %s" +GolfGreenGameBonusGag = "¡Ganaste %s!" +GolfGreenGameGotHelp = "¡%s resolvió una instancia de ingenio!" + +GolfGreenGameDirections = "Dispara bolas utilizando el mouse\n\n\nAl juntar tres del mismo color, las bolas caerán\n\n\nQuita todas las bolas bot del tablero" + +# DistributedMaze.py +enterHedgeMaze = "¡Compite a través del Laberinto\n para conseguir bonificaciones de risa!" +toonFinishedHedgeMaze = "¡%s \n terminó en el %s puesto!" +hedgeMazePlaces = ["primer","segundo","tercer","cuarto"] +mazeLabel = "¡Laberinto!" + +# Boarding Group +BoardingPartyReadme = '¿Grupo de embarque?' +BoardingGroupHide = 'Ocultar' +BoardingGroupShow = 'Mostrar grupo de embarque' +BoardingPartyInform = 'Crea un grupo de embarque para el ascensor haciendo clic sobre otro dibu e invitándolo.\nEn esta área, los grupos de embarque no pueden tener más de %s dibus.' +BoardingPartyTitle = 'Grupo de embarque' +QuitBoardingPartyLeader = 'Disolver' +QuitBoardingPartyNonLeader = 'Abandonar' +QuitBoardingPartyConfirm = '¿Seguro que quieres salir de este grupo de embarque?' +BoardcodeMissing = 'Algo falló; inténtalo de nuevo más tarde.' +BoardcodeMinLaffLeader = 'Tu grupo no puede embarcar porque tienes menos de %s puntos de risa.' +BoardcodeMinLaffNonLeaderSingular = 'Tu grupo no puede embarcar porque %s tiene menos de %s puntos de risa.' +BoardcodeMinLaffNonLeaderPlural = 'Tu grupo no puede embarcar porque %s tienen menos de %s puntos de risa.' +BoardcodePromotionLeader = 'Tu grupo no puede embarcar porque no tienes suficientes méritos de ascenso.' +BoardcodePromotionNonLeaderSingular = 'Tu grupo no puede embarcar porque %s no tiene suficientes méritos de ascenso.' +BoardcodePromotionNonLeaderPlural = 'Tu grupo no puede embarcar porque %s no tienen suficientes méritos de ascenso.' +BoardcodeSpace = 'Tu grupo no puede embarcar porque no hay espacio suficiente.' +BoardcodeBattleLeader = 'Tu grupo no puede embarcar porque estás en una batalla.' +BoardcodeBattleNonLeaderSingular = 'Tu grupo no puede embarcar porque %s está en una batalla.' +BoardcodeBattleNonLeaderPlural = 'Tu grupo no puede embarcar porque %s están en una batalla.' +BoardingInviteMinLaffInviter = 'Necesitas %s puntos de risa para poder ser miembro de este grupo de embarque.' +BoardingInviteMinLaffInvitee = '%s necesita %s puntos de risa para poder ser miembro de este grupo de embarque.' +BoardingInvitePromotionInviter = 'Necesitas conseguir un ascenso para poder ser miembro de este grupo de embarque.' +BoardingInvitePromotionInvitee = '%s necesita conseguir un ascenso para poder ser miembro de este grupo de embarque.' +BoardingInviteNotPaidInvitee = '%s necesita ser miembro pagado para formar parte de tu grupo de embarque.' +BoardingInviteeInDiffGroup = '%s ya está en otro grupo de embarque.' +BoardingInviteeInKickOutList = '%s fue expulsado por tu líder. El líder es el único que puede volver a invitar a miembros expulsados' +BoardingInviteePendingIvite = '%s tiene una invitación pendiente; inténtalo de nuevo más tarde.' +BoardingInviteeInElevator = '%s está ocupado; inténtalo de nuevo más tarde.' +BoardingInviteGroupFull = 'Tu grupo de embarque ya está lleno.' +BoardingAlreadyInGroup = 'No puedes aceptar esta invitación porque formas parte de otro grupo de embarque.' +BoardingGroupAlreadyFull = 'No puedes aceptar esta invitación porque el grupo ya está lleno.' +BoardingKickOutConfirm = '¿Seguro que quieres expulsar a %s?' +BoardingPendingInvite = 'Primero tienes que resolver la\n invitación pendiente.' +BoardingCannotLeaveZone = 'No puedes salir de esta zona porque formas parte de un grupo de embarque.' +BoardingInviteeMessage = "%s quiere que te unas a su grupo de embarque." +BoardingInvitingMessage = "Invitando a %s a tu grupo de embarque." +BoardingInvitationRejected = "%s rechazó unirse a tu grupo de embarque." +BoardingMessageKickedOut = "Fuiste expulsado del grupo de embarque." +BoardingMessageInvited = "%s invitó a %s al grupo de embarque." +BoardingMessageLeftGroup = "%s abandonó el grupo de embarque." +BoardingMessageGroupDissolved = "Tu grupo de embarque fue disuelto por el líder del grupo." +BoardingMessageGroupDisbandedGeneric = "Tu grupo de embarque fue disuelto." +BoardingMessageInvitationFailed = "%s intentó invitarte a su grupo de embarque." +BoardingMessageGroupFull = "%s intentó aceptar tu invitación, pero tu grupo estaba lleno." +BoardingGo = 'IR' +BoardingCancelGo = 'Haz clic otra vez\npara cancelar Ir' +And = 'y' +BoardingGoingTo = 'En dirección a:' +BoardingTimeWarning = 'Embarcando al ascensor en ' +BoardingMore = 'más' +BoardingGoShow = 'En dirección a\n%s en ' +BoardingGoPreShow = 'Confirmando...' + +# DistributedBossbotBoss.py +BossbotBossName = "Director general" +BossbotRTWelcome = "Tus dibus necesitarán disfraces diferentes." +BossbotRTRemoveSuit = "Primero hay que quitarse los trajes de bot..." +BossbotRTFightWaiter = "y después luchar contra estos camareros." +BossbotRTWearWaiter = "¡Buen trabajo! Ahora, a colocarse la ropa de los camareros." +BossbotBossPreTwo1 = "¿A qué se debe tanta tardanza?" +BossbotBossPreTwo2 = "¡Vamos, sírvanme el banquete!" +BossbotRTServeFood1 = "Jeje, hay que servir la comida que coloco en estas cintas transportadoras." +BossbotRTServeFood2 = "Si sirves a un bot tres veces seguidas, explotará." +BossbotResistanceToonName = "El Viejo Aletas" +BossbotPhase3Speech1 = "¡¿Qué está pasando aquí?!" +BossbotPhase3Speech2 = "¡Estos camareros son dibus!" +BossbotPhase3Speech3 = "¡¡¡A ellos!!!" +BossbotPhase4Speech1 = "Grrrr. Si necesitas que algo se haga bien..." +BossbotPhase4Speech2 = "Tienes que hacerlo tú mismo." +BossbotRTPhase4Speech1 = "¡Buen trabajo! Ahora, a mojar al director general con el agua de las mesas..." +BossbotRTPhase4Speech2 = "o se pueden usar las bolas de golf para frenarle." +BossbotPitcherLeave = "Dejar botella" +BossbotPitcherLeaving = "Dejando botella..." +BossbotPitcherAdvice = "Usa las teclas izquierda y derecha para girar.\nMantén Ctrl pulsado para aumentar la potencia.\nSuelta Ctrl para disparar." +BossbotGolfSpotLeave = "Dejar bola de golf" +BossbotGolfSpotLeaving = "Dejando bola de golf..." +BossbotGolfSpotAdvice = "Usa las teclas izquierda y derecha para girar.\nCtrl para disparar." +BossbotRewardSpeech1 = "¡No! Al presidente no le va a gustar esto." +BossbotRewardSpeech2 = "¡¡¡Arrrggghhh!!!" +BossbotRTCongratulations = "¡Lo lograste! ¡Bajaste de categoría al director general!\aToma estos papeles rosas que se dejó el director general.\aCon ellos, podrás disparar contra los bots en batalla.""" +BossbotRTLastPromotion = "\a¡Uau, alcanzaste el nivel %s con tu traje bot!\aLos bots no pueden ascender más de nivel.\aYa no puedes mejorar tu traje bot, ¡pero puedes seguir trabajando para la Resistencia!" +BossbotRTHPBoost = "\aHiciste un gran trabajo para la Resistencia.\aEl Consejo Dibu decidió concederte otro punto de risa. ¡Felicitaciones!" +BossbotRTMaxed = "\aVeo que tienes un traje de bot de nivel %s. ¡Impresionante!\aEn nombre del Consejo Dibu, ¡gracias por regresar a defender a más dibus!" +GolfAreaAttackTaunt = "¡Adelante!" +OvertimeAttackTaunts = [ "Es hora de reorganizarse.", + "Es hora de reducir el personal."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "Batalla del director general" +ElevatorBossBotCourse0 = "Los tres delanteros" +ElevatorBossBotCourse1 = "Los seis centrales" +ElevatorBossBotCourse2 = "Los nueve traseros" +ElevatorCashBotBoss = "Batalla del director financiero" +ElevatorCashBotMint0 = "Fabrica de moneditas" +ElevatorCashBotMint1 = "Fabrica de dólares" +ElevatorCashBotMint2 = "Fabrica de lingotes" +ElevatorSellBotBoss = "Batalla del vicepresidente" +ElevatorSellBotFactory0 = "Entrada delantera" +ElevatorSellBotFactory1 = "Entrada de servicio" +ElevatorLawBotBoss = "Batalla del Juez" +ElevatorLawBotCourse0 = "Oficina A" +ElevatorLawBotCourse1 = "Oficina B" +ElevatorLawBotCourse2 = "Oficina C" +ElevatorLawBotCourse3 = "Oficina D" + + + +# CatalogNameTagItem.py +DaysToGo = "Espera\n%s días" + +# DistributedIceGame.py +IceGameTitle = "Tobogán de hielo" +IceGameInstructions = "Acércate todo lo que puedas al centro antes de que finalice la segunda ronda. Utiliza las teclas de flecha para cambiar la dirección y la fuerza. Pulsa Ctrl para lanzar a tu dibu. ¡Conseguirás más puntos si golpeas los barriles y evitas el TNT!" +IceGameInstructionsNoTnt = "Acércate todo lo que puedas al centro antes de que finalice la segunda ronda. Utiliza las teclas de flecha para cambiar la dirección y la fuerza. Pulsa Ctrl para lanzar a tu dibu. Golpea barriles y conseguirás puntos extra." +IceGameWaitingForPlayersToFinishMove = "Esperando a otros jugadores..." +IceGameWaitingForAISync = "Esperando a otros jugadores..." +IceGameInfo= "Partida %(curMatch)d/%(numMatch)d, Ronda %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="¡No olvides pulsar la tecla Ctrl!" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "Unirse" +PicnicTableObserveButton = "Observar" +PicnicTableCancelButton = "Cancelar" +PicnicTableTutorial = "Cómo se juega" +PicnicTableMenuTutorial = "¿Qué juego quieres aprender?" +PicnicTableMenuSelect = "¿A qué juego quieres jugar?" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = "Levantarse" +ChineseCheckersStartButton = "Empezar a jugar" +ChineseCheckersQuitButton = "Abandonar el juego" +ChineseCheckersIts = "Es " + +ChineseCheckersYourTurn = "Tu turno" +ChineseCheckersGreenTurn = "Turno de las verdes" +ChineseCheckersYellowTurn = "Turno de las amarillas" +ChineseCheckersPurpleTurn = "Turno de las moradas" +ChineseCheckersBlueTurn = "Turno de las azules" +ChineseCheckersPinkTurn = "Turno de las rosas" +ChineseCheckersRedTurn = "Turno de las rojas" + +ChineseCheckersColorG = "Eres las verdes" +ChineseCheckersColorY = "Eres las amarillas" +ChineseCheckersColorP = "Eres las moradas" +ChineseCheckersColorB = "Eres las azules" +ChineseCheckersColorPink = "Eres las rosas" +ChineseCheckersColorR = "Eres las rojas" +ChineseCheckersColorO = "Estás de observador" + +ChineseCheckersYouWon = "¡Acabas de ganar una partida de Damas chinas!" +ChineseCheckers = "Damas chinas." +ChineseCheckersGameOf = " acaba de ganar una partida de " + +#GameTutorials.py +ChineseTutorialTitle1 = "Objetivo" +ChineseTutorialTitle2 = "Cómo se juega" +ChineseTutorialPrev = "Página anterior" +ChineseTutorialNext = "Página siguiente" +ChineseTutorialDone = "Completado" +ChinesePage1 = "El objetivo de las Damas chinas es ser el primero en mover todas tus canicas desde tu triángulo a través del tablero hasta el triángulo opuesto al tuyo. ¡Gana el primer jugador que lo logre!" +ChinesePage2 = "Por turnos, los jugadores van moviendo cualquier canica de su color. Las canicas pueden moverse a cualquier hoyo vacío contiguo o pueden saltar sobre otras canicas. Los saltos deben hacerse siempre sobre una canica y terminar en un hoyo vacío. ¡Se pueden hacer saltos en cadena para avanzar más!" + +CheckersPage1 = "El objetivo de las Damas es dejar al contrario sin movimientos. Para ello, puedes capturar todas sus fichas o bloquearle el paso de manera que no pueda mover." +CheckersPage2 = "Por turnos, los jugadores van moviendo cualquier ficha de su color. Las fichas se mueven de a un casillero en diagonal y hacia delante. Una ficha sólo podrá moverse a un casillero que no esté ocupado por otra ficha. Las reinas se rigen por las mismas reglas pero también se mueven hacia atrás." +CheckersPage3 = "Para capturar una ficha contraria, tu ficha deberá saltar sobre ella en diagonal y colocarse en el casillero vacío siguiente. Si tienes la posibilidad de saltar durante tu turno, estás obligado a hacerlo. Puedes hacer saltos en cadena siempre que los hagas con la misma ficha y en la misma dirección." +CheckersPage4 = "Una ficha se convierte en reina cuando llega a la última fila del tablero. Cuando una ficha se convierte en reina no podrá seguir saltando hasta el siguiente turno. Además, las reinas pueden moverse en cualquier dirección y pueden cambiar de dirección durante los saltos." + + + +#DistributedCheckers.py +CheckersGetUpButton = "Levantarse" +CheckersStartButton = "Empezar partida" +CheckersQuitButton = "Abandonar partida" + +CheckersIts = "It's " +CheckersYourTurn = "Tu turno" +CheckersWhiteTurn = "Turno de las blancas" +CheckersBlackTurn = "Turno de las negras" + +CheckersColorWhite = "Eres las blancas" +CheckersColorBlack = "Eres las negras" +CheckersObserver = "Estás de observador" +RegularCheckers = "Damas." +RegularCheckersGameOf = " ganó una partida de " +RegularCheckersYouWon = "¡Ganaste una partida de Damas!" + +MailNotifyNewItems = "¡Tienes correo!" +MailNewMailButton = "Correo" +MailSimpleMail = "Nota" +MailFromTag = "Nota de: %s" + +# MailboxScreen.py +InviteInvitation = "la invitación" +InviteAcceptInvalidError = "La invitación ya no es válida." +InviteAcceptPartyInvalid = "La fiesta fue cancelada." +InviteAcceptAllOk = "El anfitrión fue informado de tu respuesta." +InviteRejectAllOk = "El anfitrión fue informado de que rechazaste la invitación." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "ENERO", + 2: "FEBRERO", + 3: "MARZO", + 4: "ABRIL", + 5: "MAYO", + 6: "JUNIO", + 7: "JULIO", + 8: "AGOSTO", + 9: "SEPTIEMBRE", +10: "OCTUBRE", +11: "NOVIEMBRE", +12: "DICIEMBRE" +} + +# Note 0 for Monday to match datetime +DayNames = ("Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo") +DayNamesAbbrev = ("LUN", "MAR", "MIÉ", "JUE", "VIE", "SÁB", "DOM") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Fuegos artificiales de verano", "¡Celebra el verano con un espectáculo de fuegos artificiales por hora en todos los dibuparques!"), + 2: ("Fuegos artificiales de Año Nuevo", "¡Feliz Año Nuevo! ¡Disfruta de un espectáculo de fuegos artificiales por hora en todos los dibuparques!"), + 3: ("Invasión Chupasangres", "¡Feliz día de Halloween! ¡Evita que los bots chupasangres invadan Toontown!"), + 4: ("Adornos de invierno", "¡Celebra las vacaciones de invierno con árboles y luces dibufantásticas!"), + 5: ("Invasión esquelebot", "¡Impide que los esquelebots invadan Toontown!"), + 6: ("Invasión del Sr. Hollywood", "Impide que los bots del Sr. Hollywood invadan Toontown!"), + 7: ("Bingo de peces", "¡Miércoles de bingo de peces! En el estanque, todos trabajan juntos para completar la tarjeta antes de que se agote el tiempo."), + 8: ("Elección especies dibu", "¡Vota por la nueva especie de dibu! ¿Será una cabra? ¿Será un cerdo?"), + 9: ("Día del gato negro", "¡Feliz día de Halloween! Crea un dibufantástico dibu gato negro, ¡sólo por hoy!"), + 13: ("Truco o trato", "¡Feliz día de Halloween! ¡Pasea por todo Toontown haciendo truco o trato y consigue una calabaza de Halloween genial!"), + 14: ("Grand Prix", "¡Lunes Grand Prix en el Estadio de Goofy! ¡Para ganar, consigue más puntos que nadie en tres carreras consecutivas!"), + 16: ("Fin de semana Grand Prix", "¡Jugadores libres y pagados compiten en las carreras del circuito del Estadio de Goofy!"), + 17: ("Circuito del tranvía", "¡Jueves de Circuito del tranvía! Para jugar, súbete a cualquier tranvía con dos o más dibus."), + 19: ("Sábados absurdos", "¡El sábado es un día loco, pues se celebra durante todo el día el bingo de peces, el Grand Prix y el Circuito del tranvía!"), + 24: ("Idus de marzo", "¡Cuidado con los idus de marzo! ¡Impide que los bots apuñalaespaldas invadan Toontown!"), + 26: ("Adornos de Halloween", "¡Celebra Halloween transformando Toontown con árboles y luces escalofriantes!"), + 28: ("Invasión de invierno", "¡Los vendebots andan sueltos, esparciendo sus frías tácticas de venta!"), + 33: ("Sorpresa vendebot 1", "¡Sorpresa vendebot! ¡Impide que los bots aprovechados invadan Toontown!"), + 34: ("Sorpresa vendebot 2", "¡Sorpresa vendebot! ¡Impide que los bots fanfarrones invadan Toontown!"), + 35: ("Sorpresa vendebot 3", "¡Sorpresa vendebot! ¡Impide que los bots efusivos invadan Toontown!"), + 36: ("Sorpresa vendebot 4", "¡Sorpresa vendebot! ¡Impide que los bots mandamases invadan Toontown!"), + 37: ("Enigma chequebot 1", "Un enigma chequebot. ¡Impide que los bots modeditas invadan Toontown!"), + 38: ("Enigma chequebot 2", "Un enigma chequebot. ¡Impide que los bots cacomatracos invadan Toontown!"), + 39: ("Enigma chequebot 3", "Un enigma chequebot. ¡Impide que los bots agarrados invadan Toontown!"), + 40: ("Enigma chequebot 4", "Un enigma chequebot. ¡Impide que los bots contables invadan Toontown!"), + 41: ("Táctica abogabot 1", "La táctica abogabot. ¡Impide que los bots caraduras invadan Toontown!"), + 42: ("Táctica abogabot 2", "La táctica abogabot. ¡Impide que los bots embaucadores invadan Toontown!"), + 43: ("Táctica abogabot 3", "La táctica abogabot. ¡Impide que los bots persigueambulancias invadan Toontown!"), + 44: ("Táctica abogabot 4", "La táctica abogabot. ¡Impide que los bots apuñalaespaldas invadan Toontown!"), + 45: ("Problema con jefebots 1", "El problema con los jefebots. ¡Impide que los bots secuaces invadan Toontown!"), + 46: ("Problema con jefebots 2", "El problema con los jefebots. ¡Impide que los bots chupatintas invadan Toontown!"), + 47: ("Problema con jefebots 3", "El problema con los jefebots. ¡Impide que los microgerentes invadan Toontown!"), + 48: ("Problema con jefebots 4", "El problema con los jefebots. ¡Impide que los bots reguladores de empleo invadan Toontown!"), + 49: ("Día de la Golosina", "¡Celebra el día de la golosina recibiendo como recompensa el doble de golosinas en las fiestas!"), + 53: ("Invasión de los aprovechados", "¡Impide que los bots aprovechados invadan Toontown!"), + 54: ("Invasión de los agarrados", "¡Impide que los bots agarrados invadan Toontown!"), + 55: ("Invasión de los embaucadores", "¡Impide que los bots embaucadores invadan Toontown!"), + 56: ("Invasión de los reguladores de empleo", "¡Impide que los bots reguladores de empleo invadan Toontown!"), + + } + +UnknownHoliday = "Celebración desconocida %d" +HolidayFormat = "%m/%d " + +# parties/ToontownTimeManager.py +TimeZone = "US/Pacific" diff --git a/toontown/src/toonbase/TTLocalizer_castillian_Property.py b/toontown/src/toonbase/TTLocalizer_castillian_Property.py new file mode 100644 index 0000000..97f35af --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_castillian_Property.py @@ -0,0 +1,496 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.045 +RPmeritBarLabels = 0.15 + +#battle/RewardPanel.py +RPmeritLabelXPosition = 0.68 +RPmeritBarsXPosition = 0.955 + +#battle/BattleBase.py +BBbattleInputTimeout = 50.0 + +#battle/FireCogPanel.py +FCPtextFrameScale = 0.05 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogItemPanel.py +CIPnameLabel = 0.85 +CIPwordwrapOffset = 2 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.10) +CSgiftToggle = 0.07 +CSbackCatalogButton = 0.06 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 20 +CMunpaidChatWarning = 0.055 +CMunpaidChatWarning_text_z = 0.27 +CMpayButton = 0.05 +CMpayButton_pos_z = -0.10 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 +CMNoPasswordContinue_z = -0.25 + +#coghq/LawbotCogHQLoader.py +LCLdgSign = 0.1 # the scale of the gate name + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.1 # the scale of the gate name + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 0.8 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 1.5 + +#coghq/BossbotCogHQLoader.py +BCHQLmakeSign = 0.6 + +#coghq/DistributedGolfGreenGame.py +DGGGquitButton = 0.025 +DGGGhowToButton = 0.035 +DGGGscoreLabel = 0.05 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#estate/PlantingGUI.py +GardeningInstructionScale = 0.07 + +#estate/FlowerPanel.py +FPBlankLabelPos = -0.25 +FPBlankLabelTextScale = 0.025 + +#estate/FlowerPicker.py +FPFlowerValueTotal = 0.045 + +#estate/FlowerSellGUI.py +FSGDFTextScale = 0.048 +FSGCancelBtnTextScale = 0.04 +FSGOkBtnTextScale = 0.04 + +#estate/GardenTutorial.py +GardenTutorialPage2Wordwrap = 14.5 +GardenTutorialPage4Wordwrap = 22.5 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.45 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.06 +FPnewRecord = 0.06 + +#fishing/GenusPanel.py +GPgenus = 0.035 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) +FLPtitleScale = 0.04 + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 +FIcancelButtonPositionX = 0.0 +FIstopTextPositionY = -0.015 +FIstopButtonPositionX = -0.2 +FIyesButtonPositionX = -0.15 +FIdirectFrameTextWorkWrap = 14 +FIdirectFrameTextPosZ = 0.2 + +#golf/DistributedGolfHole.py +DGHpowerReminder = 0.09 +DGHaimInstructions = 0.065 +DGHteeInstructions = 0.065 + +#golf/DistributedGolfHole.py +DGHAimInstructScale = 0.075 +DGHTeeInstructScale = 0.075 + +#golf/GolfScoreBoard.py +GSBExitCourseBTextPose = (0.20, -.01) +GSBtitleLabelScale = 0.06 + +#golf/GolfScoreBoard.py +GSBexitCourseBPos = (0.19, -.01) +GSBtitleLabel = 0.06 + +#hood/EstateHood.py +EHpopupInfo = .08 + +#hood/Hood.py +HDenterTitleTextScale = 0.12 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.095 +ACdeleteWithPassword = 0.06 +ACstatusText = 1.0 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 +ACquitButton_pos = -0.024 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 +MRPinstructionsText = 0.05 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.05 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.05 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.01 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.1 +NScolorPrecede = False + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.07 + +#makeatoon\ShuffleButton.py +SBshuffleBtn = 0.08 + +#minigame/DistributedPairingGame.py +DPGPointsFrameTextScale = 0.45 +DPGFlipsFrameTextScale = 0.45 + +#minigame/DistributedTravelGame.py +DTGVoteBtnTextScale = 0.05 +DTGUseLabelTextScale = 0.07 +DTGVotesPeriodLabelTextScale = 0.07 +DTGVotesToGoLabelTextScale = 0.07 +DTGUpLabelTextScale = 0.07 +DTGDownLabelTextScale = 0.07 +DTGRemainingVotesFrameTextScale = 0.6 + +#minigame/MinigameRulesPanel.py +MRPGameTitleTextScale = 0.10 +MRPGameTitleTextPos = (-0.12, 0.2, 0.092) +MRPInstructionsTextWordwrap = 32 +MRPInstructionsTextPos = (-0.12, 0.05, 0) + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.65 + +#parties/InviteVisual.py +IVwhenTextLabel = 0.06 +IVactivityTextLabel = 0.06 + +#parties/PartyPlanner.py +PPDescriptionScale = 0.06 +PPelementTitleLabelScale = 0.07 +PPelementBuyButtonTextScale = 0.055 +PPtitleScale = 0.1 +PPpbulicDescriptionLabel = 0.065 +PPprivateDescriptionLabel = 0.065 +PPpublicButton = 0.05 +PPprivateButton = 0.05 +PPcostLabel = 0.065 +PPpartyGroundsLabel = 1.0 +PPinstructionLabel = 0.07 +PPelementPrice = 0.065 + +#parties/DistributedParty.py +DPpartyCountdownClockTextScale = 1.1 +DPpartyCountdownClockMinutesScale = 1.1 +DPpartyCountdownClockColonScale = 1.1 +DPpartyCountdownClockSecondScale = 1.1 +DPpartyCountdownClockMinutesPosY = 0.0 +DPpartyCountdownClockColonPosY = 0.0 +DPpartyCountdownClockSecondPosY = 0.0 + +#parties/PublicPartyGui.py +PPGpartyStartButton = 0.065 +PPGinstructionsLabel = 0.065 +PPGcreatePartyListAndLabel = 0.06 + +#parties/JukeboxGui.py +JGcurrentlyPlayingLabel = 0.07 +JGsongNameLabel = 0.13 +JGaddSongButton = 0.1 +JGnumItemsVisible = 9 +JGlistItem = 1.0 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.4 +PAPcall = 0.4 +PAPowner = 0.30 +PAPscratch = 0.4 +PAPstateLabel = 0.35 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.08 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.095 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0625 + +#racing/DistributedLeaderBoard.py +DLBtitleRowScale = 0.3 + +#racing/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.3 + +#raceing/DistributedRacePad.py +DRPnodeScale = 0.65 + +#racing/KartShopGui.py +KSGtextSizeBig = 0.06 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 22 + +#racing/RaceEndPanels.py +REPraceEnd = 0.065 +REPraceExit = 0.025 +REPticket_text_x = -0.7 + +#racing/RaceGUI.py +RGphotoFinish = 0.20 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#racing/DistributedRaceAI.py +DRAwaitingForJoin = 90 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.06 + +#safezone/Playground.py +PimgLabel = 0.6 + +#safezone/GZSafeZoneLoader.py +GSZLbossbotSignScale = 0.8 + +#shtiker/EventPage.py +EPtitleLabel = 0.12 +EPhostTab = 0.07 +EPinvitedTab = 0.07 +EPcalendarTab = 0.07 +EPnewsTab = 0.07 +EPhostingCancelButton = 0.04 +EPhostingDateLabel = 0.05 +EPpartyGoButton = 0.045 +EPpublicPrivateLabel = 0.05 +EPpublicButton= 0.5 +EPprivateButton = 0.5 +EPinvitePartyGoButton = 0.045 +EPdecorationItemLabel = 0.045 +EPactivityItemLabel = 0.045 +EPcreateListAndLabel = 0.045 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 +DSDcancelButtonPositionX = 0 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.08 +TPendFrame = 0.08 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.05 +MPhoodWordwrap = 20 + +#shtiker/KartPage.py +KPkartTab = 0.06 +KPdeleteButton = 0.035 +KProtateButton = 0.03 + +#shtiker/GardenPage.py +GPBasketTabTextScale = 0.06 +GPCollectionTabTextScale = 0.06 +GPTrophyTabTextScale = 0.06 +GPSpecialsTabTextScale = 0.06 + +#shtiker/GolfPage.py +GFPRecordsTabTextScale = 0.06 +GFPRecordsTabPos = (0.82, 0, 0.1) +GFPTrophyTabTextScale = 0.06 +GFPRecordsTabTextPos = (0.03, 0.0, 0.0) +GFPRTrophyTabPos = (0.82, 0, -0.3) + +#toon/AvatarPanelBase.py +APBignorePanelAddIgnoreTextScale = 0.06 +APBignorePanelTitlePosY = 0 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 +TAPgroupFrameScale = 0.05 +TAPgroupButtonScale = 0.055 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/GroupPanel.py +GPdestFrameScale = 0.035 +GPdestScrollListScale = 0.035 +GPgoButtonScale = 0.06 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.045 +INrunButton = 0.045 +INdetailNameLabel = 0.8 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.6 + +#toon/PlayerInfoPanel.py +PIPsecretsButtonScale=0.045 +PIPwisperButton = 0.06 +PIPdetailButton = 0.05 + +#toon/ToonAvatarPanel.py +TAPsecretsButtonScale=0.045 +TAPwisperButtonScale=0.06 + +#toon/ToonAvatarDetailPanel.py +TADPcancelPos = (-0.865, 0.0, -0.765) +TADtrackLabelPosZ = 0.17 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TSRPdialogWordwrap = 20 +TSRPtop = 0.07 +TSRPpanelScale = 0.05 +TSRPpanelPos = (-0.65, -0.70) +TSRPbrowserPosZ = -0.65 +TSRPbutton = 0.06 +TSRPteaserBottomScale = 0.06 +TSRPhaveFunText = 0.08 +TSRPjoinUsText = 0.08 + +#toontowngui/TeaserPanel.py (OLD) +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.08 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/TTLocalizer_french.py b/toontown/src/toonbase/TTLocalizer_french.py new file mode 100644 index 0000000..beeace1 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_french.py @@ -0,0 +1,9394 @@ +import string +import time +from toontown.toonbase.french.TTLocalizer_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "Mickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Dingo" +Pluto = "Pluto" +Flippy = "Flippy" +Chip = "Tic" +Dale = "Tac" + +# common locations +lTheBrrrgh = 'Le Glagla' +lDaisyGardens = 'Le Jardin de Daisy' +lDonaldsDock = "Quais Donald" +lDonaldsDreamland = "Le Pays des Rêves de Donald" +lMinniesMelodyland = "Le Pays Musical de Minnie" +lToontownCentral = 'Toontown Centre' +lToonHQ = 'QG des Toons' +lOutdoorZone = "Forêt de glands de Tic et Tac" +lGoofySpeedway = "Circuit Dingo" +lGolfZone = "Minigolf de Tic et Tac" + +lGagShop = 'Gag Shop' +lClothingShop = 'En tant que magasin de marchandises sèches' +lPetShop = 'Pet Shop' + +# common strings +lBack = 'Retour' +lCancel = 'Annuler' +lClose = 'Fermer' +lOK = 'OK' +lNext = 'Suivant' +lQuit = 'Quitter' +lYes = 'Oui' +lNo = 'Non' +lHQ = '' + +lHQOfficerF = 'Officier QG' +lHQOfficerM = 'Officier QG' + +MickeyMouse = "Mickey" + +AIStartDefaultDistrict = "Idioville" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "un Cog" +TheCogs = "les Cogs" +ASkeleton = "un Skelecog" +Skeleton = "Skelecog" +SkeletonP = "Skelecogs" +Av2Cog = "a Version 2.0 Cog" +v2Cog = "Version 2.0 Cog" +v2CogP = "Version 2.0 Cogs" +Foreman = "Contremaître de l'usine" +ForemanP = "Contremaîtres de l'usine" +AForeman = "un contremaître de l'usine" +CogVP = "Vice-\nPrésident " + Cog +CogVPs = "Vice-\nPrésidents Cogs" +ACogVP = "Un Vice-\nPrésident " + Cog +Supervisor = "Superviseur de la Fabrique à Sous" +SupervisorP = "Superviseurs de la Fabrique à Sous" +ASupervisor = "un Superviseur de la Fabrique à Sous" +CogCFO = Cog + " Vice-\nPrésident" +CogCFOs = "Vice-\nPrésidents Cog" +ACogCFO = ACog + " Vice-\nPrésident" + +# AvatarDNA.py +Bossbot = "Chefbot" +Lawbot = "Loibot" +Cashbot = "Caissbot" +Sellbot = "Vendibot" +BossbotS = "un Chefbot" +LawbotS = "un Loibot" +CashbotS = "un Caissbot" +SellbotS = "un Vendibot" +BossbotP = "des Chefbots" +LawbotP = "des Loibots" +CashbotP = "des Caissbots" +SellbotP = "des Vendibots" +BossbotSkelS = "un Chefbot Skelecog" +LawbotSkelS = "un Loibot Skelecog" +CashbotSkelS = "un Caissbot Skelecog" +SellbotSkelS = "un Vendibot Skelecog" +BossbotSkelP = "des Chefbots Skelecogs" +LawbotSkelP = "des Loibots Skelecogs" +CashbotSkelP = "des Caissbots Skelecogs" +SellbotSkelP = "des Vendibots Skelecogs" + +#lToonHQ = 'トゥーン'+lHQ #check +lBossbotHQ = Bossbot+lHQ +lLawbotHQ = Lawbot+lHQ +lCashbotHQ = Cashbot+lHQ +lSellbotHQ = Sellbot+lHQ + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("vers la", "sur la", "terrasse du Tourbillon"), # Tutorial + 1000 : ("vers le", "sur le", "Terrain de jeux"), + 1100 : ("vers le", "sur le", "Boulevard de la Bernache"), + 1200 : ("vers la", "sur la", "Rue des Récifs"), + 1300 : ("vers l'", "sur l'", "Allée des Marées"), + 2000 : ("vers le", "sur le", "Terrain de jeux"), + 2100 : ("vers la", "sur la", "Rue Béta"), + 2200 : ("vers l'", "sur l'", "Avenue des Fondus"), + 2300 : ("vers la", "sur la", "Place des Blagues"), + 3000 : ("vers le", "sur le", "Terrain de jeux"), + 3100 : ("vers le", "sur le", "Chemin du Marin"), + 3200 : ("vers la", "sur la", "Rue de la Neige fondue"), + 3300 : ("vers la", "sur la", "Place Polaire"), + 4000 : ("vers le", "sur le", "Terrain de jeux"), + 4100 : ("vers l'", "sur l'", "Avenue du Contralto"), + 4200 : ("vers le", "sur le", "Boulevard du Baryton"), + 4300 : ("vers la", "sur la", "Terrasse des Ténors"), + 5000 : ("vers le", "sur le", "Terrain de jeux"), + 5100 : ("vers la", "sur la", "Rue des Ormes"), + 5200 : ("vers la", "sur la", "Rue des Érables"), + 5300 : ("vers la", "sur la", "Rue du Chêne"), + 9000 : ("vers le", "sur le", "Terrain de jeux"), + 9100 : ("vers le", "sur le", "Boulevard de la Berceuse"), + 9200 : ("", "", "Place de la Couette"), + 10000 : ("vers le", "au", "QG Chefbot"), + 10100 : ("vers le", "dans le", "hall du QG des Chefbots"), + 10200 : ("à", "dans", "Le Clubhouse"), + 10500 : ("à", "dans", "Les trois premiers à l'avant"), + 10600 : ("à", "dans", "Les six du milieu"), + 10700 : ("à", "dans", "Les neuf du fond"), + 11000 : ("vers la", "sur la", "cour du QG Vendibot"), + 11100 : ("vers le", "dans le", "hall du QG Vendibot"), + 11200 : ("vers l'", "à l'", "usine Vendibot"), + 11500 : ("vers l'", "à l'", "usine Vendibot"), + 12000 : ("vers le", "au", "QG Caissbot"), + 12100 : ("vers le", "dans le", "hall du QG Caissbot"), + 12500 : ("", "", "Fabrique à Sous Caissbot"), + 12600 : ("", "", "Fabrique à Euros Caissbot"), + 12700 : ("", "", "Fabrique à Lingots Caissbot"), + 13000 : ("vers le", "au", "QG Loibot"), + 13100 : ("vers le", "dans le", "hall du QG Loibot"), + 13200 : ("vers le", "au", "hall du bureau du Procureur"), + 13300 : ("vers le", "au", "bureau Loibot A"), + 13400 : ("vers le", "au", "bureau Loibot B"), + 13500 : ("vers le", "au", "bureau Loibot C"), + 13600 : ("vers le", "au", "bureau Loibot D"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("vers les", "sur les", "Quais Donald") +ToontownCentral = ("vers", "à", "Toontown centre") +TheBrrrgh = ("vers", "dans", "le Glagla") +MinniesMelodyland = ("vers le", "au", "Pays musical de Minnie") +DaisyGardens = ("vers les", "au", "Jardins de Daisy") +ConstructionZone = ("vers la", "dans la", "Zone de construction") +OutdoorZone = ("", "", lOutdoorZone) +FunnyFarm = ("vers la", "dans la", "Ferme farfelue") +GoofySpeedway = ("vers le", "au", "Circuit Dingo") +DonaldsDreamland = ("vers le", "au", "Pays des rêves de Donald") +BossbotHQ = ("vers le", "dans le", "QG des Chefbots") +SellbotHQ = ("vers le", "dans le", "QG Vendibot") +CashbotHQ = ("vers le", "dans le", "QG Caissbot") +LawbotHQ = ("vers le", "dans le", "QG Loibot") +Tutorial = ("vers les", "aux", "Travaux pratiques") +MyEstate = ("vers", "dans", "ta maison") +WelcomeValley = ("vers la", "dans la", "Bienvenue") +GolfZone = ("à", "dans", lGolfZone) + +Factory = 'Usine' +Headquarters = 'Quartiers généraux' +SellbotFrontEntrance = 'Entrée principale' +SellbotSideEntrance = 'Entrée latérale' +Office = 'Officier' + +FactoryNames = { + 0 : "Maquette d'usine", + 11500 : 'Usine des Cogs Vendibots', + 13300 : 'Bureau des Cogs Loibot', #remove me JML + } + +FactoryTypeLeg = 'Jambe' +FactoryTypeArm = 'Bras' +FactoryTypeTorso = 'Torse' + +MintFloorTitle = 'Étage %s' + +# Quests.py +TheFish = "les poissons" +AFish = "un poisson" +Level = "niveau" +QuestsCompleteString = "Terminé" +QuestsNotChosenString = "Non choisi" +Period = "." + +Laff = "Rigolpoints" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Bonjour, _avName_!", + "Ohé, _avName_!", + "Coucou, _avName_!", + "Eh, _avName_!", + "Bienvenue, _avName_!", + "Salut, _avName_!", + "Comment ça va, _avName_?", + "Quoi de neuf, _avName_?", + ) +QuestsDefaultIncomplete = ("Comment est-ce que ce défi se présente, _avName_?", + "On dirait que tu as encore du travail à faire pour ce défi!", + "Continue à bien travailler, _avName_!", + "Essaie de finir ce défi. Je sais que tu peux le faire!", + "Essaie de terminer ce défi, nous comptons sur toi!", + "Continue à travailler sur ce défitoon!", + ) +QuestsDefaultIncompleteProgress = ("Tu es au bon endroit, mais tu dois d'abord finir ton défitoon.", + "Quand tu auras terminé ton défitoon, reviens ici.", + "Reviens quand tu auras terminé ton défitoon.", + ) +QuestsDefaultIncompleteWrongNPC = ("Joli travail pour ce défitoon. Tu devrais aller voir _toNpcName_._where_", + "On dirait que tu as presque fini ton défitoon. Va voir _toNpcName_._where_.", + "Va voir _toNpcName_ pour finir ton défitoon._where_", + ) +QuestsDefaultComplete = ("Bon travail! Voilà ta récompense...", + "Super boulot, _avName_! Prends cette récompense...", + "Excellent boulot, _avName_! Voilà ta récompense...", + ) +QuestsDefaultLeaving = ("Salut!", + "Au revoir!", + "Bon vent, _avName_!", + "À plus, _avName_!", + "Bonne chance!", + "Amuse-toi bien à Toontown!", + "À plus tard!", + ) +QuestsDefaultReject = ("Bonjour.", + "Puis-je t'aider ?", + "Comment ça va?", + "Bien le bonjour.", + "Je suis un peu occupé là, _avName_.", + "Oui?", + "Salut, _avName_!", + "Bienvenue, _avName_!", + "Hé, _avName_! Comment ça va?", + # Game Hints + "Sais-tu que tu peux ouvrir ton journal de bord en appuyant sur la touche F8?", + "Tu peux utiliser ta carte pour te téléporter jusqu'au terrain de jeux!", + "Tu peux devenir ami(e) avec les autres joueurs en cliquant sur eux.", + "Tu peux en savoir plus sur un " + Cog + " en cliquant sur lui.", + "Trouve des trésors sur les terrains de jeux pour remplir ton rigolmètre.", + "Les immeubles " + Cog + " sont dangereux! N'y va pas tout seul!", + "Lorsque tu perds un combat, les " + Cogs + " prennent tous tes gags.", + "Pour avoir plus de gags, joue aux jeux du tramway!", + "Tu peux accumuler des rigolpoints en effectuant des défitoons.", + "Chaque défitoon te vaudra une récompense.", + "Certaines récompenses te permettent d'avoir plus de gags.", + "Si tu gagnes un combat, ton défitoon est crédité pour chaque " + Cog + " vaincu.", + "Si tu regagnes un bâtiment " + Cog + ", retourne à l'intérieur pour recevoir un remerciement spécial de la part de son propriétaire!", + "Si tu appuies sur la touche \"page précédente\", tu peux regarder vers le haut!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Pour montrer à tes amis ce que tu penses, entre un '.' avant ta pensée.", + "Si un " + Cog + " est assommé, il lui est plus difficile d'éviter les objets qui tombent.", + "Chaque type de bâtiment " + Cog + " a un aspect différent.", + "Tu obtiens plus de récompenses d'habileté si tu vaincs des " + Cogs + " aux plus hauts étages des bâtiments." + ) +QuestsDefaultTierNotDone = ("Bonjour, _avName_! Tu dois terminer tes défitoons commencés avant d'en obtenir un autre.", + "Salut! Tu dois terminer les défitoons sur lesquels tu es en train de travailler pour en obtenir un nouveau.", + "Ohé, _avName_! Pour que je puisse te donner un autre défitoon, tu dois finir ceux que tu as déjà.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("J'ai entendu dire que _toNpcName_ te cherchait._where_", + "Arrête-toi voir _toNpcName_ quand tu pourras._where_", + "Va donc voir _toNpcName_ la prochaine fois que tu passes par là-bas._where_", + "Si tu peux, arrête-toi dire bonjour à _toNpcName_._where_", + "_toNpcName_ va te donner ton prochain défitoon._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogQuestHeadline = "RECHERCHE" +QuestsCogQuestSCStringS = "Je dois vaincre %(cogName)s%(cogLoc)s" +QuestsCogQuestSCStringP = "Je dois vaincre quelques %(cogName)s%(cogLoc)s" +QuestsCogQuestDefeat = "Tu dois vaincre %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Aide un nouveau Toon à vaincre %s" +QuestsCogNewNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins" +QuestsCogOldNewbieQuestObjective = "Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s" +QuestsCogOldNewbieQuestCaption = "Aide un Toon avec %d rigolpoints ou moins" +QuestsCogNewbieQuestAux = "Tu dois\nvaincre:" +QuestsNewbieQuestHeadline = "APPRENTI" + +QuestsCogTrackQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogTrackQuestHeadline = "RECHERCHE" +QuestsCogTrackQuestSCStringS = "Je dois vaincre %(cogText)s%(cogLoc)s" +QuestsCogTrackQuestSCStringP = "Je dois vaincre quelques %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Tu dois vaincre %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogLevelQuestHeadline = "RECHERCHE" +QuestsCogLevelQuestDefeat = "Tu dois vaincre %s" +QuestsCogLevelQuestDesc = "un Cog de niveau %(level)s+" +QuestsCogLevelQuestDescC = "%(count)s Cogs de niveau %(level)s+" +QuestsCogLevelQuestDescI = "des Cogs de niveau %(level)s+" +QuestsCogLevelQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'deux+', 'trois+', 'quatre+', 'cinq+') +QuestsBuildingQuestBuilding = "Bâtiment" +QuestsBuildingQuestBuildings = "Bâtiments" +QuestsBuildingQuestHeadline = "VAINCRE" +QuestsBuildingQuestProgressString = "%(progress)s sur %(num)s sont vaincus" +QuestsBuildingQuestString = "Tu dois vaincre %s" +QuestsBuildingQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "un bâtiment %(type)s" +QuestsBuildingQuestDescF = "un bâtiment %(type)s de %(floors)s étages" +QuestsBuildingQuestDescC = "%(count)s bâtiments %(type)s " +QuestsBuildingQuestDescCF = "%(count)s bâtiments %(type)s de %(floors)s étages" +QuestsBuildingQuestDescI = "des bâtiments %(type)s" +QuestsBuildingQuestDescIF = "des bâtiments %(type)s de %(floors)s étages" + +QuestFactoryQuestFactory = "Usine" +QuestsFactoryQuestFactories = "Usines" +QuestsFactoryQuestHeadline = "VAINCRE" +QuestsFactoryQuestProgressString = "%(progress)s sur%(num)s sont vaincus" +QuestsFactoryQuestString = "Tu dois vaincre %s" +QuestsFactoryQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "une usine %(type)s" +QuestsFactoryQuestDescC = "%(count)s usines %(type)s" +QuestsFactoryQuestDescI = "des usines %(type)s" + +QuestMintQuestMint = "Fabrique à Sous" +QuestsMintQuestMints = "Fabriques à Sous" +QuestsMintQuestHeadline = "VAINCRE" +QuestsMintQuestProgressString = "%(progress)s de %(num)s vaincus" +QuestsMintQuestString = "Vaincre %s" +QuestsMintQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsMintQuestDesc = "une Fabrique à Sous Cog" +QuestsMintQuestDescC = "%(count)s Fabriques à Sous Cog" +QuestsMintQuestDescI = "des Fabriques à Sous Cog" + +QuestsRescueQuestProgress = "%(progress)s sur %(numToons)s sont sauvés" +QuestsRescueQuestHeadline = "SAUVER" +QuestsRescueQuestSCStringS = "Je dois sauver un Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "Je dois sauver des Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Tu dois sauver %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "un Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Tu dois sauver:" + +QuestsRescueNewNewbieQuestObjective = "Aide un nouveau Toon à sauver %s" +QuestsRescueOldNewbieQuestObjective = "Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s" + +QuestCogPartQuestCogPart = "Pièce de costume de Cog" +QuestsCogPartQuestFactories = "Usines" +QuestsCogPartQuestHeadline = "RÉCUPÉRER" +QuestsCogPartQuestProgressString = "%(progress)s sur %(num)s sont récupérés" +QuestsCogPartQuestString = "Récupérer %s" +QuestsCogPartQuestSCString = "Je dois récupérer %(objective)s%(location)s." +QuestsCogPartQuestAux = "Tu dois récupérer:" + +QuestsCogPartQuestDesc = "une pièce de costume de Cog" +QuestsCogPartQuestDescC = "%(count)s pièces de costume de Cog" +QuestsCogPartQuestDescI = "des pièces de costume de Cog" + +QuestsCogPartNewNewbieQuestObjective = 'Aide un nouveau Toon à récupérer %s' +QuestsCogPartOldNewbieQuestObjective = 'Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s sur %(numGags)s sont livrés" +QuestsDeliverGagQuestHeadline = "LIVRER" +QuestsDeliverGagQuestToSCStringS = "Je dois livrer %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Je dois livrer des %(gagName)s." +QuestsDeliverGagQuestSCString = "Je dois faire une livraison." +QuestsDeliverGagQuestString = "Tu dois livrer %s" +QuestsDeliverGagQuestStringLong = "Tu dois livrer %s à _toNpcName_." +QuestsDeliverGagQuestInstructions = "Tu pourras acheter ce gag à la Boutique à gags une fois que tu en auras gagné le droit." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "LIVRER" +QuestsDeliverItemQuestSCString = "Je dois livrer %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Tu dois livrer %s" +QuestsDeliverItemQuestStringLong = "Tu dois livrer %s à _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITER" +QuestsVisitQuestStringShort = "Tu dois rendre visite" +QuestsVisitQuestStringLong = "Rends visite à _toNpcName_" +QuestsVisitQuestSeeSCString = "Je dois voir %s." + +QuestsRecoverItemQuestProgress = "%(progress)s sur %(numItems)s sont repris" +QuestsRecoverItemQuestHeadline = "REPRENDRE" +QuestsRecoverItemQuestSeeHQSCString = "Je dois voir un officier du QG." +QuestsRecoverItemQuestReturnToHQSCString = "Je dois rendre %s à un officier du QG." +QuestsRecoverItemQuestReturnToSCString = "Je dois rendre %(item)s à %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Je dois aller à un QG des Toons." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Je dois aller au terrain de jeux de %s." +QuestsRecoverItemQuestGoToStreetSCString = "Je dois aller %(to)s %(street)s dans %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Je dois rendre visite à %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Où est %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Je dois reprendre %(item)s à %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Reprendre %(item)s à %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "CHOISIR" +QuestsTrackChoiceQuestSCString = "Je dois choisir entre %(trackA)s et %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Je devrais peut-être choisir %s." +QuestsTrackChoiceQuestString = "Choisis entre %(trackA)s et %(trackB)s." + +QuestsFriendQuestHeadline = "AMI" +QuestsFriendQuestSCString = "Je dois trouver un(e) ami(e)." +QuestsFriendQuestString = "Trouve un(e) ami(e)." + +QuestsMailboxQuestHeadline = "COURRIER" +QuestsMailboxQuestSCString = "Je dois vérifier mon courrier." +QuestsMailboxQuestString = "Vérifie ton courrier." + +QuestsPhoneQuestHeadline = "CLARABELLE" +QuestsPhoneQuestSCString = "Je dois appeler Clarabelle." +QuestsPhoneQuestString = "Appelle Clarabelle." + +QuestsFriendNewbieQuestString = " Trouve %d contacts de %d rigolpoints ou moins" +QuestsFriendNewbieQuestProgress = "%(progress)s sur %(numFriends)s sont trouvés." +QuestsFriendNewbieQuestObjective = "Deviens ami(e) avec %d nouveaux Toons." + +QuestsTrolleyQuestHeadline = "TRAMWAY" +QuestsTrolleyQuestSCString = "Je dois faire un tour de tramway." +QuestsTrolleyQuestString = "Fais un tour de tramway." +QuestsTrolleyQuestStringShort = "Prends le tramway." + +QuestsMinigameNewbieQuestString = "%d Mini jeux" +QuestsMinigameNewbieQuestProgress = "%(progress)s sur %(numMinigames)s ont été joués." +QuestsMinigameNewbieQuestObjective = "Jouer à %d mini jeux avec de nouveaux Toons" +QuestsMinigameNewbieQuestSCString = "Je dois jouer aux mini jeux avec de nouveaux Toons." +QuestsMinigameNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins." +QuestsMinigameNewbieQuestAux = "Tu dois jouer:" + +QuestsMaxHpReward = "Ta rigo-limite a été augmentée de %s." +QuestsMaxHpRewardPoster = "Récompense: Rigol-augmentation de %s point(s)" + +QuestsMoneyRewardSingular = "Tu obtiens 1 bonbon." +QuestsMoneyRewardPlural = "Tu obtiens %s bonbons." +QuestsMoneyRewardPosterSingular = "Récompense: 1 bonbon" +QuestsMoneyRewardPosterPlural = "Récompense: %s bonbons" + +QuestsMaxMoneyRewardSingular = "Tu peux maintenant avoir 1 bonbon." +QuestsMaxMoneyRewardPlural = "Tu peux maintenant avoir %s bonbons." +QuestsMaxMoneyRewardPosterSingular = "Récompense: Tu as 1 bonbon." +QuestsMaxMoneyRewardPosterPlural = "Récompense: Tu as %s bonbons." + +QuestsMaxGagCarryReward = "Tu as un %(name)s. Tu peux maintenant avoir %(num)s gags." +QuestsMaxGagCarryRewardPoster = "Récompense: (%(num)s) %(name)s" + +QuestsMaxQuestCarryReward = " Tu peux maintenant avoir %s défitoons." +QuestsMaxQuestCarryRewardPoster = "Récompense: Tu as %s défitoons" + +QuestsTeleportReward = "Tu peux maintenant accéder par téléportation à %s." +QuestsTeleportRewardPoster = "Récompense: Accès par téléportation à %s" + +QuestsTrackTrainingReward = "Tu peux maintenant t'entraîner pour les gags \"%s\"." +QuestsTrackTrainingRewardPoster = "Récompense: Entraînement aux gags" + +QuestsTrackProgressReward = "Tu as maintenant l'image %(frameNum)s de l'animation de la série %(trackName)s." +QuestsTrackProgressRewardPoster = "Récompense: image %(frameNum)s de l'animation de la série \"%(trackName)s\"" + +QuestsTrackCompleteReward = "Tu peux maintenant avoir et utiliser des gags \"%s\"." +QuestsTrackCompleteRewardPoster = "Récompense: Entraînement final aux séries %s" + +QuestsClothingTicketReward = "Tu peux changer de vêtements." +QuestsClothingTicketRewardPoster = "Récompense: Ticket d'habillement" + +QuestsCheesyEffectRewardPoster = "Récompense: %s" + +QuestsCogSuitPartReward = "Tu as maintenant une %(cogTrack)s %(part)s pièce de costume de Cog." +QuestsCogSuitPartRewardPoster = "Récompense: %(cogTrack)s %(part)s pièce" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "sur ce terrain de jeux" +QuestsStreetLocationThisStreet = "sur cette rue" +QuestsStreetLocationNamedPlayground = "sur le terrain de jeux de %s" +QuestsStreetLocationNamedStreet = "sur %(toStreetName)s dans %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "Le bâtiment de %s est appelé" +QuestsLocationBuildingVerb = "qui est" +QuestsLocationParagraph = "\a %(building)s \"%(buildingName)s \"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Je dois terminer un défitoon." + +# MaxGagCarryReward names +QuestsMediumPouch = "Bourse moyenne" +QuestsLargePouch = "Grande bourse" +QuestsSmallBag = "Petit sac" +QuestsMediumBag = "Sac moyen" +QuestsLargeBag = "Grand sac" +QuestsSmallBackpack = "Petit sac à dos" +QuestsMediumBackpack = "Sac à dos moyen" +QuestsLargeBackpack = "Grand sac à dos" +QuestsItemDict = { + 1 : ["Paire de lunettes", "Paires de lunettes", "une"], + 2 : ["Clé", "Clés", "une"], + 3 : ["Tableau", "Tableaux", "un"], + 4 : ["Livre", "Livres", "un"], + 5 : ["Sucre d'orge", "Sucres d'orge", "un"], + 6 : ["Craie", "Craies", "une"], + 7 : ["Recette", "Recettes", "une"], + 8 : ["Note", "Notes", "une"], + 9 : ["Machine à calculer", "Machines à calculer", "une"], + 10 : ["Pneu de voiture de clown", "Pneus de voiture de clown", "un"], + 11 : ["Pompe à air", "Pompes à air", "une"], + 12 : ["Encre de seiche", "Encres de seiche", "de l'"], + 13 : ["Paquet", "Paquets", "un "], + 14 : ["Reçu de poisson doré", "Reçus de poissons dorés", "un "], + 15 : ["Poisson doré", "Poissons dorés", "un "], + 16 : ["Huile", "Huiles", "de l'"], + 17 : ["Graisse", "Graisses", "de la "], + 18 : ["Eau", "Eaux", "de l'"], + 19 : ["Rapport de pignons", "Rapports de pignons", "un "], + 20 : ["Brosse à Tableaux", "Brosses à Tableaux", "une "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Ticket d'habillement", "Tickets d'habillement", "un "], + + # Donald's Dock quest items + 2001 : ["Chambre à air", "Chambres à air", "une "], + 2002 : ["Ordonnance de monocle", "Ordonnances de monocles", "une "], + 2003 : ["Monture de monocle", "Montures de monocles", "une "], + 2004 : ["Monocle", "Monocles", "un "], + 2005 : ["Grande perruque blanche", "Grandes perruques blanches", "une "], + 2006 : ["Boisseau de lest", "Boisseaux de lest", "un "], + 2007 : ["Équipement de Cog", "Équipements de Cog", "un "], + 2008 : ["Carte marine", "Cartes marines", "une "], + 2009 : ["Manille crado", "Manilles crados", "un "], + 2010 : ["Manille propre", "Manilles propres", "un "], + 2011 : ["Ressort d'horloge", "Ressorts d'horloge", "un "], + 2012 : ["Contrepoids", "Contrepoids", "un "], + + # Minnie's Melodyland quest items + 4001 : ["Inventaire de Tina", "Inventaires de Tina", ""], + 4002 : ["Inventaire de Yuki", "Inventaires de Yuki", ""], + 4003 : ["Formulaire d'inventaire", "Formulaires d'inventaire", "un "], + 4004 : ["Inventaire de Fifi", "Inventaires de Fifi", ""], + 4005 : ["Ticket de Jack Bûcheron", "Tickets de Jack Bûcheron", ""], + 4006 : ["Ticket de Tabatha", "Tickets de Tabatha", ""], + 4007 : ["Ticket de Barry", "Tickets de Barry", ""], + 4008 : ["Castagnette ternie", "Castagnettes ternies", ""], + 4009 : ["Encre de seiche bleue", "Encre de seiche bleue", "de l'"], + 4010 : ["Castagnette brillante", "Castagnettes brillantes", "une "], + 4011 : ["Paroles de Léo", "Paroles de Léo", ""], + + # Daisy's Gardens quest items + 5001 : ["Cravate en soie", "Cravates en soie", "une "], + 5002 : ["Costume à rayures", "Costumes à rayures", "un "], + 5003 : ["Paire de ciseaux", "Paires de ciseaux", "une "], + 5004 : ["Carte postale", "Cartes postales", "une "], + 5005 : ["Crayon", "Crayons", "un "], + 5006 : ["Encrier", "Encriers", "un "], + 5007 : ["Bloc-notes", "Blocs-notes", "un "], + 5008 : ["Coffre de bureau", "Coffres de bureau", "un "], + 5009 : ["Sac de graines pour oiseaux", "Sacs de graines pour oiseaux", "un "], + 5010 : ["Pignon", "Pignons", "un "], + 5011 : ["Salade", "Salades", "une "], + 5012 : ["Clé du jardin de Daisy", "Clés du jardin de Daisy", "une "], + 5013 : ["Plans du QG Vendibot", "Plans du QG Vendibot", "des "], + 5014 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5015 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5016 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5017 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + + # The Brrrgh quests + 3001 : ["Ballon de foot", "Ballons de foot", "un "], + 3002 : ["Luge", "Luges", "une "], + 3003 : ["Glaçon", "Glaçons", "un "], + 3004 : ["Lettre d'amour", "Lettres d'amour", "une "], + 3005 : ["Teckel", "Teckels", "un "], + 3006 : ["Bague de fiançailles", "Bagues de fiançailles", "une "], + 3007 : ["Moustaches de sardine", "Moustaches de sardines", "des "], + 3008 : ["Potion calmante", "Potions calmantes", "une "], + 3009 : ["Dent cassée", "Dents cassées", "une "], + 3010 : ["Dent en or", "Dents en or", "une "], + 3011 : ["Pain aux pommes de pin", "Pains aux pommes de pin", "un "], + 3012 : ["Fromage grumeleux", "Fromages grumeleux", "du "], + 3013 : ["Cuillère ordinaire", "Cuillères ordinaires", "une "], + 3014 : ["Crapaud parlant", "Crapauds parlants", "un "], + 3015 : ["Cône de glace", "Cônes de glace", "un "], + 3016 : ["Poudre à perruque", "Poudres à perruques", "de la "], + 3017 : ["Canard en plastique", "Canards en plastique", "un "], + 3018 : ["Dés en peluche", "Dés en peluche", "des "], + 3019 : ["Micro", "Micros", "un "], + 3020 : ["Clavier électrique", "Claviers électriques", "un "], + 3021 : ["Chaussures à plate-forme", "Chaussures à plate-forme", "des "], + 3022 : ["Caviar", "Caviar", "du "], + 3023 : ["Poudre de maquillage", "Poudres de maquillage", "de la "], + 3024 : ["Fil", "Fil", "du " ], + 3025 : ["Aiguille à tricoter", "Aiguilles à tricoter", "une "], + 3026 : ["Alibi", "Alibis", "un "], + 3027 : ["Thermomètre extérieur", "Thermomètres extérieurs", "un "], + + #Dreamland Quests + 6001 : ["Plans du QG Caissbot ", "Plans du QG Caissbot ", "des "], + 6002 : ["Tige", "Tiges", "une "], + 6003 : ["Courroie", "Courroies", "une "], + 6004 : ["Tenaille", "Tenailles", "une "], + 6005 : ["Lampe de lecture", "Lampes de lecture", "une "], + 6006 : ["Cithare", "Cithares", "une "], + 6007 : ["Surfaceuse", "Surfaceuses", "une "], + 6008 : ["Coussin zèbre", "Coussins zèbre", "un "], + 6009 : ["Zinnia", "Zinnias", "quelques "], + 6010 : ["Disques de Zydeco", "Disques de Zydeco", "des "], + 6011 : ["Courgette", "Courgettes", "une "], + 6012 : ["Costume de zazou", "Costumes de zazou", "un "], + + #Dreamland+1 quests + 7001 : ["Lit ordinaire", "Lits ordinaires", "un "], + 7002 : ["Lit fantaisie", "Lits fantaisie", "un "], + 7003 : ["Dessus-de-lit bleu", "Dessus-de-lit bleus", "un "], + 7004 : ["Dessus-de-lit motif cachemire", "Dessus-de-lit motif cachemire", "un "], + 7005 : ["Oreillers", "Oreillers", "des "], + 7006 : ["Oreillers durs", "Oreillers durs", "des "], + 7007 : ["Pyjama", "Pyjamas", "un "], + 7008 : ["Grenouillère", "Grenouillères", "une "], + 7009 : ["Grenouillère puce", "Grenouillères puce", "une "], + 7010 : ["Grenouillère fuchsia", "Grenouillères fuchsia", "une "], + 7011 : ["Corail chou-fleur", "Coraux chou-fleur", "du "], + 7012 : ["Algue gluante", "Algues gluantes", "de l'"], + 7013 : ["Pilon", "Pilons", "un "], + 7014 : ["Pot de crème antirides", "Pots de crème antirides", "un "], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "dans n'importe quel quartier" + +QuestsTailorFillin = "Tailleur" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Boutique de prêt-à-porter" +QuestsTailorLocationNameFillin = "dans n'importe quel quartier" +QuestsTailorQuestSCString = "J'ai besoin de voir un tailleur." + +QuestMovieQuestChoiceCancel = "Reviens plus tard si tu as besoin d'un défitoon! Salut!" +QuestMovieTrackChoiceCancel = "Reviens quand tu es prêt à te décider!! Salut!" +QuestMovieQuestChoice = "Choisis un défitoon." +QuestMovieTrackChoice = "Prêt à te décider ? Choisis une série, ou reviens plus tard." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Maintenant tu es prêt(e).\aSors et fais un tour avant de décider quelle série tu voudras choisir.\aChoisis bien, parce que c'est ta dernière série.\aQuand tu auras décidé, reviens me voir.", + INCOMPLETE_PROGRESS : "Choisis bien.", + INCOMPLETE_WRONG_NPC : "Choisis bien.", + COMPLETE : "Choix très sage!", + LEAVING : "Bonne chance. Reviens me voir quand tu as maîtrisé ta nouvelle habileté.", + } + +QuestDialog_3225 = { + QUEST : "Oh, merci pour ta visite, _avName_!\aLes Cogs du quartier ont effrayé mon livreur.\aJe n'ai personne pour livrer cette salade à _toNpcName_!\aPeux-tu le faire pour moi? Merci beaucoup!_where_" + } + +QuestDialog_2910 = { + QUEST : "Déjà de retour ?\aSuper travail avec le ressort.\aLe dernier article est un contrepoids.\aVa donc voir _toNpcName_ et ramène tout ce que tu peux._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Chefbots.", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Chefbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Loibots.", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Loibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Caissbots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Caissbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Vendibots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Vendibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Il semble que tu as besoin de nouveaux gags.\aVa voir Flippy, peut-être pourra-t-il t'aider._where_" }, + 165 : {QUEST : "Salut!\aOn dirait que tu as besoin de t'entraîner à utiliser tes gags.\aChaque fois que tu atteins un Cog avec l'un de tes gags, ton expérience augmente.\aQuand tu auras assez d'expérience, tu pourras utiliser un gag encore meilleur.\aVa t'entraîner à utiliser tes gags en battant 4 Cogs."}, + 166 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Chefbots."}, + 167 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Loibots."}, + 168 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Vendibots."}, + 169 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Caissbots."}, + 170 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 171 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 172 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - elle peut te donner des conseils avisés._where_" }, + + 175 : {GREETING : "", + QUEST : "Est-ce que tu savais que tu as une maison Toon à toi?\aClarabelle la vache s'occupe d'un catalogue par téléphone ou tu peux commander des meubles pour décorer ta maison.\aTu peux aussi y acheter des mots de Chat rapide, des vêtements et d'autres choses amusantes!\aJe vais dire à Clarabelle de t'envoyer ton premier catalogue maintenant.\aTu recevras un catalogue avec les nouveaux articles chaque semaine!\aRentre a la maison et appelle Clarabelle avec ton téléphone.", + INCOMPLETE_PROGRESS : "Rentre à la maison et appelle Clarabelle avec ton téléphone.", + COMPLETE : "J'espère que tu t'amuses en commandant des choses chez Clarabelle!\aJe viens tout juste de redécorer ma maison. Elle est toontastique!\aContinue à relever les défitoons pour avoir plus de récompenses!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Le lancer et l'éclaboussure sont super, mais tu auras besoin de plus de gags pour battre les Cogs de plus haut niveau.\aLorsque tu fais équipe avec d'autres Toons contre les Cogs, vous pouvez combiner vos attaques pour faire encore plus de dégâts.\aEssayez différentes combinaisons de gags pour voir ce qui marche le mieux.\aPour ta prochaine série, choisis entre tapage et toonique.\aTapage est particulier parce que lorsqu'il frappe, il endommage tous les Cogs.\aToonique te permet de soigner les autres Toons lors d'un combat.\aLorsque tu es prêt(e) à te décider, reviens ici faire ton choix.", + INCOMPLETE_PROGRESS : "Déjà de retour ? OK, quel est ton choix?", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Bonne décision. Maintenant tu dois t'entraîner avant de pouvoir utiliser ces gags.\aTu dois effectuer une série de défitoons pour t'entraîner.\aChaque défi te donnera une seule image de ton animation d'attaque avec les gags.\aLorsque tu auras les 15 images, tu pourras faire le dernier défi d'entraînement qui te permettra d'utiliser tes nouveaux gags.\aTu peux suivre tes progrès dans ton journal de bord.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1040 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1041 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1042 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1043 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1044 : { QUEST : "Oh, merci de passer par ici. J'ai vraiment besoin d'aide.\aComme tu peux voir, je n'ai pas de clients.\aMon livre de recettes secret est perdu et personne ne vient plus dans mon restaurant.\aLa dernière fois que je l'ai vu, c'était avant que ces Cogs ne prennent mon bâtiment.\aEst-ce que tu peux m'aider à retrouver quatre de mes célèbres recettes?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as pu retrouver mes recettes?" }, + 1045 : { QUEST : "Merci beaucoup!\aD'ici peu, j'aurai retrouvé toutes mes recettes et je pourrai rouvrir mon restaurant.\aOh, j'ai une petite note ici pour toi - quelque chose à propos de l'accès par téléportation ?\aC'est écrit, merci d'avoir aidé mon ami et d'avoir livré ceci au quartier général des Toons. \aEh bien, merci vraiment - au revoir!", + LEAVING : "", + COMPLETE : "Ah oui, c'est écrit que tu as été d'une grande aide à de braves gens de l'avenue des Fondus.\aEt que tu as besoin d'un accès par téléportation à Toontown centre.\aBon, c'est comme si c'était fait.\aMaintenant tu peux revenir au terrain de jeux par téléportation depuis presque partout dans Toontown.\aOuvre simplement ta carte et clique sur Toontown centre." }, + 1046 : { QUEST : "Les Caissbots ont vraiment ennuyé la Caisse d'épargne Drôle d'argent.\aVa donc y faire un tour et vois si tu peux faire quelque chose._where_" }, + 1047 : { QUEST : "Les Caissbots se sont introduits dans la banque et ont volé nos machines.\aS'il te plaît, reprends 5 machines à calculer aux Caissbots.\aPour t'éviter de faire des allers et retours, rapporte-les toutes en une seule fois.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore des machines à calculer ?" }, + 1048 : { QUEST : "Oh là là! >Merci d'avoir trouvé nos machines à calculer.\aHmm... Elles ont l'air un peu abîmées.\aDis donc, pourrais-tu les amener à _toNpcName_ à son magasin, \"Machines à chatouilles\", dans cette rue ?\aVoir si elle peut les réparer.", + LEAVING : "", }, + 1049 : { QUEST : "Qu'est-ce que c'est ? Des machines à calculer cassées?\aDes Caissbots dis-tu?\aBon, regardons ça...\aMouais, les pignons sont cassés, mais je n'en vends pas...\aTu sais ce qui pourrait marcher - des pignons de Cog, des gros, de gros Cogs...\aDes pignons de Cog de niveau 3 devraient faire l'affaire. J'en aurai besoin de 2 pour chaque machine, donc 10 au total.\aRapporte-les moi tous ensemble et je ferai la réparation!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Souviens-toi, j'ai besoin de 10 pignons pour réparer les machines." }, + 1053 : { QUEST : "Ah oui, ça devrait bien faire l'affaire.\aTout est réparé maintenant, gratuitement.\aRapporte-les à Drôle d'argent, et dis-leur bonjour de ma part.", + LEAVING : "", + COMPLETE : "Toutes les machines à calculer sont réparées?\aJoli travail. Je crois bien que j'ai quelque chose par là pour te récompenser..." }, + 1054 : { QUEST : "_toNpcName_ a besoin d'aide pour ses voitures de clown._where_" }, + 1055 : { QUEST : "Bon sang! Je n'arrive pas à trouver les pneus de cette voiture de clown!\aTu crois que tu pourrais m'aider ?\aJe crois que Bob Fondu les a lancés dans la mare du terrain de jeux de Toontown centre.\aSi tu vas sur les pontons, de là tu peux essayer de repêcher les pneus.", + GREETING : "Youhouu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as du mal à repêcher les 4 pneus?" }, + 1056 : { QUEST : "Fanta-super-tastique! Maintenant je vais pouvoir remettre en marche cette vieille voiture de clown!\aHé, je croyais que j'avais une pompe par ici pour gonfler ces pneus...\aC'est peut-être _toNpcName_ qui l'a empruntée ?\aTu peux aller lui demander de me la rendre ?_where_", + LEAVING : "" }, + 1057 : { QUEST : "Salut!\aUne pompe à pneus tu dis?\aJe vais te dire - tu me nettoies les rues de quelques-uns de ces Cogs de haut niveau...\aEt je te donne la pompe à pneus.", + LEAVING : "", + INCOMPLETE_PROGRESS : "C'est tout ce que tu peux faire ?" }, + 1058 : { QUEST : "Bon travail - je savais que tu pouvais le faire.\aVoilà la pompe. Je suis certain que_toNpcName_ sera content de la récupérer.", + LEAVING : "", + GREETING : "", + COMPLETE : "Youpiii! Maintenant ça va marcher!\aEt dis donc, merci de m'avoir aidé.\aTiens, prends ça." }, + 1059 : { QUEST : "_toNpcName_ est à court de fournitures. Tu peux peut-être lui donner un coup de main ?_where_" }, + 1060 : { QUEST : "Merci d'être passé par ici!\aCes Cogs ont volé mon encre je n'en ai presque plus.\a Pourrais-tu me pêcher de l'encre de seiche dans la mare ?\aTu n'as qu'à rester sur un ponton près de la mare pour pêcher.", + LEAVING : "", + INCOMPLETE_PROGRESS : "{Tu as des problèmes pour pêcher ?" }, + 1061 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Gratte-papiers...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Gratte-papiers dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Gratte-papiers." }, + 1062 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Pique-au-sang...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Pique-au-sang dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Pique-au-sang." }, + 900 : { QUEST : "Je crois comprendre que_toNpcName_ a besoin d'aide avec un paquet._where_" }, + 1063 : { QUEST : "Salut, merci d'être là.\aUn Cog a volé un paquet très important juste sous mon nez.\aPeux-tu le récupérer ? Je crois que c'était un Cog de niveau 3...\aDonc, tu dois vaincre des Cogs de niveau 3 jusqu'à ce que tu retrouves mon paquet.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1067 : { QUEST : "C'est ça, très bien!\aOh, l'adresse est toute tachée...\aTout ce que j'arrive à lire, c'est Docteur... - le reste est brouillé.\aC'est peut-être pour_toNpcName_? Peux-tu lui porter ?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Je n'attendais pas de paquet. C'est peut-être pour le Dr E. Phorique ?\aMon assistant doit aller le voir aujourd'hui, je me charge de lui remettre.\aEn attendant, est-ce que tu voudrais bien débarrasser ma rue de quelques Cogs?\aTu dois vaincre 10 Cogs dans Toontown centre.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mon assistant n'est pas encore revenu." }, + 1069 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Caissbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1070 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Vendibot l'a volé à mon assistant alors qu'il revenait.\aJe suis désolé, mais il va falloir que tu retrouves ce Vendibot pour le récupérer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1071 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Chefbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1072 : { QUEST : "Super - tu l'as retrouvé!\aTu devrais peut-être essayer _toNpcName_, cela pourrait être pour lui._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, merci de m'avoir apporté mes paquets.\aJuste une seconde, j'en attendais deux. Pourrais-tu vérifier avec _toNpcName_ voir s'il a l'autre ?", + INCOMPLETE : "Est-ce que tu as trouvé mon autre paquet ?", + LEAVING : "" }, + 1074 : { QUEST : "Il a dit qu'il y avait un autre paquet ? Les Cogs l'ont peut-être aussi volé.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves le second paquet.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1075 : { QUEST : "Finalement je crois qu'il y avait un second paquet!\aVa vite le porter à _toNpcName_ avec mes excuses.", + COMPLETE : "Eh, mon paquet est là!\aPuisque tu es un Toon aussi serviable, cela devrait aider.", + LEAVING : "" }, + 1076 : { QUEST : "Il y a un problème au Ornithorynques 14 carats.\a_toNpcName_ serait sans doute content d'avoir de l'aide._where_" }, + 1077 : { QUEST : "Merci d'être venu - les Cogs ont volé tous mes poissons dorés.\aJe crois que les Cogs veulent les vendre pour se faire de l'argent facilement.\aCes 5 poissons ont été mes seuls compagnons dans cette petite boutique depuis tant d'années...\aSi tu pouvais me les retrouver, je t'en serais vraiment reconnaissant.\aJe suis certain qu'un des Cogs a mes poissons.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mes poissons dorés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "S'il te plaît, ramène-moi mes poissons." }, + 1078 : { QUEST : "Oh, tu as mes poissons!\aEh? Qu'est-ce que c'est que ça?\aAah, ce sont bien les Cogs, après tout.\aJe ne comprends rien à ce reçu. Peux-tu l'emmener à _toNpcName_ voir s'il peut le lire ?_where_", + INCOMPLETE : "Qu'est-ce que _toNpcName_ a dit à propos du reçu?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Laquaistic.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Laquaistic.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1092 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Gardoseille.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Gardoseille.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1080 : { QUEST : "Oh, Dieu merci! Tu as trouvé Oscar - c'est mon préféré.\aQu'est-ce que c'est, Oscar ? Hein, hein...ils ont quoi? ... Ils sont ?\aOscar dit que les 4 autres se sont échappés dans la mare du terrain de jeux.\aPeux-tu aller me les chercher ?\aTu n'as qu'à les pêcher dans la mare.", + LEAVING : "", + COMPLETE : "Ahh, je suis si content! Avoir retrouvé mes petits camarades!\aTu mérites une belle récompense pour cela!", + INCOMPLETE_PROGRESS : "Tu as des problèmes pour trouver ces poissons?" }, + 1081 : { QUEST : "_toNpcName_ a l'air d'être dans une situation difficile. Elle serait sûrement contente d'avoir de l'aide._where_" }, + 1082 : { QUEST : "J'ai renversé de la colle à séchage rapide, et je suis collée - complètement collée!\aSi seuleument je trouvais une façon de m'en sortir...\aCela me donne une idée, si tu veux bien.\aVa vaincre quelques Vendibots et ramène-moi de l'huile.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1083 : { QUEST : "Bon, l'huile a fait un peu d'effet, mais je ne peux toujours pas bouger.\aQu'est-ce qui pourrait bien m'aider ? C'est difficile à dire.\aCela me donne une idée on peut au moins essayer.\aVa vaincre quelques Loibots et ramène-moi de la graisse.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1084 : { QUEST : "Non, ça n'a rien fait. Ce n'est vraiment pas drôle.\aJ'ai pourtant mis de la graisse partout.\aÇa me donne une idée, avant que j'oublie.\aVa vaincre quelques Caissbots et rapporte de l'eau pour l'humecter.", + LEAVING : "", + GREETING : "", + COMPLETE : "Hourrah, je suis libérée de cette colle rapide.\aComme récompense, je te donne ce cadeau.\aTu peux rire un peu plus longtemps lorsque tu es en train de te battre, et puis...\aOh, non! Je suis de nouveau collée là!", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1085 : { QUEST : "_toNpcName_ est en train de faire des recherches sur les Cogs.\aVa lui parler si tu veux l'aider._where_" }, + 1086 : { QUEST : "C'est cela, je fais une étude sur les Cogs.\aJe veux savoir ce qui les fait tiquer.\aCela m'aiderait certainement si tu pouvais me trouver des pignons de Cogs.\aAssure-toi qu'il s'agit de Cogs de niveau 2 au minimum, qu'ils soient assez gros pour être examinés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ne peux pas trouver assez de pignons?" }, + 1089 : { QUEST : "OK, regardons un peu ça. Ce sont d'excellents spécimens!\aMmmm...\aOK, voilà mon rapport. Emmène-le tout de suite au quartier général des Toons.", + INCOMPLETE : "As-tu porté mon rapport au quartier général?", + COMPLETE : "Bon travail _avName_, on va s'occuper de ça.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ a des informations importantes pour toi._where_" }, + 1091 : { QUEST : "J'ai entendu dire que le quartier général des Toons travaille sur une sorte de détecteur de Cogs.\aIl te permettra de voir où sont les Cogs afin de les repérer plus facilement.\aLa page des Cogs dans ton journal de bord en est la clé.\aEn battant assez de Cogs, tu pourras te régler sur leurs signaux et détecter leur emplacement.\aContinue à vaincre des Cogs, afin d'être prêt.", + COMPLETE : "Bon travail! Tu pourras probablement utiliser ceci...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 2202 : { QUEST : "Salut, _avName_. Dieu merci, tu es là. Un Radino à l'air méchant était là à l'instant et il est parti avec une chambre à air.\aJe crains qu'ils ne l'utilisent pour leurs sombres desseins.\aS'il te plaît, essaie de le retrouver et ramène-la moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé ma chambre à air ?", + COMPLETE : "Tu as trouvé ma chambre à air! Tu es vraiment très doué. Tiens, prends ta récompense...", + }, + 2203 : { QUEST : "Les Cogs sont en train de mettre la banque sens dessus dessous.\aVa voir le Capitaine Carl et vois ce que tu peux faire._where_" }, + 2204 : { QUEST : "Bienvenue à bord, moussaillon.\aGrrr! Ces fripons de Cogs ont cassé mon monocle et je n'arrive plus à compter la monnaie sans lui.\aGarde les pieds sur terre et porte cette ordonnance au Dr. Queequeg puis rapporte m'en un nouveau._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "Qu'est-ce que c'est ?\aOh, je voudrais bien préparer cette ordonnance mais les Cogs ont chapardé mes réserves.\aSi tu peux reprendre la monture à un Laquaistic je pourrai probablement t'aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Désolé. Pas de monture du Laquaistic, pas de monocle.", + }, + 2206: { QUEST : "Excellent!\aUne seconde...\aTon ordonnance est prête. Emmène tout de suite ce monocle au Capitaine Carl._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Hisse et ho!\aTu vas finir par gagner du galon après tout.\aEt voilà.", + }, + 2207 : { QUEST : "Barbara Bernache a un Cog dans son magasin!\aIl vaudrait mieux que tu y ailles tout de suite._where_" }, + 2208 : { QUEST : "Ça alors! Tu viens de le rater, mon chou.\aIl y avait un Frappedos ici. Il a pris ma grande perruque blanche.\aIl a dit que c'était pour son chef et quelque chose à propos de \"jurisprudence\".\aSi tu pouvais me la rapporter, je t'en serais toujours reconnaissante.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Tu ne l'as toujours pas trouvé?\aIl est grand avec une tête pointue.", + COMPLETE : "Tu l'as trouvé!?!?\aTu es vraiment un ange!\aTu as bien mérité ceci...", + }, + 2209 : { QUEST : "Ginette se prépare pour un voyage important.\aVa y faire un tour et vois ce que tu peux faire pour l'aider._where_"}, + 2210 : { QUEST : "Tu peux m'aider.\aLe quartier général des Toons m'a demandé de faire un voyage pour voir si je peux trouver d'où viennent les Cogs.\aJ'aurais besoin de quelques affaires pour mon bateau mais je n'ai pas beaucoup de bonbons.\aVa et ramène-moi du lest de chez Ernest. Il faudra que tu lui rendes un service pour l'avoir._where_", + GREETING : "Salut, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Comme ça, Ginette veut du lest, n'est-ce pas?\aElle me doit encore de l'argent pour le dernier boisseau.\aJe te le donnerai si tu peux faire partir cinq Microchefs de ma rue.", + INCOMPLETE_PROGRESS : "Non, idiot! J'ai dit CINQ Microchefs...", + GREETING : "Que puis-je faire pour toi?", + LEAVING : "", + }, + 2212 : { QUEST : "Un marché est un marché.\aVoilà ton lest pour cette pingre de Ginette._where_", + GREETING : "Eh bien, regarde ce qui arrive là...", + LEAVING : "", + }, + 2213 : { QUEST : "Excellent travail. Je savais qu'il serait raisonnable.\aEnsuite il me faudra une carte marine de chez Art.\aJe ne crois pas avoir beaucoup de crédit là-bas non plus il faudra que tu t'arranges avec lui._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Oui, j'ai la carte marine que veut Ginette.\aEt tu l'auras en échange d'un petit travail.aJ'essaie de construire un astrolabe pour naviguer dans les étoiles.aJ'aurais besoin de trois pignons de Cog pour le construire.\aReviens quand tu les auras trouvés.", + INCOMPLETE_PROGRESS: "Alors ils arrivent ces pignons de Cogs?", + GREETING : "Bienvenue!", + LEAVING : "Bonne chance!", + }, + 2215 : { QUEST : "Ooh! Ces pignons feront très bien l'affaire.\aVoilà la carte. Donne-la à Ginette avec mes compliments._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bon, on y est presque. Je suis prête à prendre la mer!\aJe t'emmènerais avec moi si tu n'avais pas un teint si vert. Prends plutôt ceci.", + }, + 901 : { QUEST : "Si tu es d'accord, Ahab a besoin d'aide, chez lui..._where_", + }, + 2902 : { QUEST : "Tu es la nouvelle recrue ?\aBien, bien. Tu peux peut-être m'aider.\aJe suis en train de construire un crabe géant préfabriqué pour dérouter les Cogs.\aJe pourrais quand même utiliser une manille. Va voir Gérard et rapportes-en une, s'il te plaît._where_", + }, + 2903 : { QUEST : "Salut!\aOui, j'ai entendu parler du crabe géant qu'Ahab est en train de fabriquer.\aLa meilleure manille que j'aie est un peu sale quand même.\aSois sympa, passe chez un blanchisseur avant de la déposer._where_", + LEAVING : "Merci!" + }, + 2904 : { QUEST : "Tu dois être la personne que Gérard a envoyée.\aJe crois que je peux faire ça assez vite.\aJuste une minute...\aEt voilà. Comme neuf!\aTu salueras Ahab de ma part._where_", + }, + 2905 : { QUEST : "Ah, c'est exactement ce que je cherchais.\aPendant que tu es là, je vais aussi avoir besoin d'un très gros ressort d'horloge.\aVa donc chez Crochet voir s'il en a un._where_", + }, + 2906 : { QUEST : "Un gros ressort, hein ?\aJe suis désolé mais le plus gros ressort que j'aie est quand même plutôt petit.\aJe pourrais peut-être en fabriquer un avec des ressorts de gâchette de pistolet éclabousseur.\aApporte-moi trois de ces gags et je vais voir ce que je peux faire.", + }, + 2907 : { QUEST : "Regardons ça...\aGénial. Vraiment génial.\aQuelquefois je me surprends moi-même.\aEt voilà : un gros ressort pour Ahab!_where_", + LEAVING : "Bonne route!", + }, + 2911 : { QUEST : "Je serais très content de pouvoir aider, _avName_.\aMalheureusement, les rues ne sont plus sûres.\aVa donc éliminer quelques Cogs Caissbots et on pourra parler.", + INCOMPLETE_PROGRESS : "Je crois que tu peux rendre les rues encore plus sûres.", + }, + 2916 : { QUEST : "Oui, j'ai un contrepoids que je pourrais donner à Ahab.\aJe crois que ce serait plus sûr si tu pouvais vaincre deux Vendibots d'abord.", + INCOMPLETE_PROGRESS : "Pas encore. Tu dois vaincre plus de Vendibots.", + }, + 2921 : { QUEST : "Hmmm, je pensais que je pourrais me débarrasser d'un poids.\aJe me sentirais bien mieux s'il n'y avait pas autant de Cogs Chefbots à rôder par ici.\aVa donc en vaincre six et reviens me voir.", + INCOMPLETE_PROGRESS : "Je crois qu'on n'est toujours pas en sécurité...", + }, + 2925 : { QUEST : "Ça y est ?\aBon, je suppose qu'on est suffisamment en sécurité maintenant.\aVoilà le contrepoids pour Ahab._where_" + }, + 2926 : {QUEST : "Bon, c'est tout.\aVoyons si ça marche.\aHmmm, il y a un petit problème.\aJe n'ai plus de courant parce que ce bâtiment Cog bloque mon capteur solaire.\aPeux-tu le reprendre pour moi?", + INCOMPLETE_PROGRESS : "Toujours pas de courant. Où en es-tu avec ce bâtiment ?", + COMPLETE : "Super! Tu es une sacrée terreur pour les Cogs! Tiens, prends ta récompense...", + }, + 3200 : { QUEST : "Je viens d'avoir un appel de _toNpcName_.\aCe n'est pas son jour. Tu pourrais peut-être l'aider.!\aVa y faire un tour et vois ce dont il a besoin._where_" }, + 3201 : { QUEST : "Oh, merci d'être là!\aJ'ai besoin de quelqu'un pour emporter cette nouvelle cravate en soie à _toNpcName_.\aEst-ce que tu peux faire ça pour moi?_where_" }, + 3203 : { QUEST : "Oh, ça doit être la cravate que j'ai commandée! Merci!\aElle va avec un costume à rayures que je viens de finir, juste là.\aHé, qu'est ce qui est arrivé à ce costume ?\aOh non! Les Cogs ont dû voler mon nouveau costume!\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mon costume, et que tu me le rapportes.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon costume ? Je suis certain que les Cogs l'ont pris!", + COMPLETE : "Youpii! Tu as trouvé mon nouveau costume!\aTu vois, je t'avais dit que les Cogs l'avaient! Voilà ta récompense...", + }, + + 3204 : { QUEST : "_toNpcName_ vient d'appeler pour signaler un vol.\aPourquoi n'irais-tu pas voir si tu peux arranger l'affaire ?_where_" }, + 3205 : { QUEST : "Bonjour, _avName_! Tu es là pour m'aider ?\aJe viens de chasser un Pique-au-sang de mon magasin. Houlala! C'était effrayant.\aMais maintenant je ne trouve plus mes ciseaux! Je suis certain que ce Pique-au-sang les a pris.\aTrouve-le, et ramène-moi mes ciseaux.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore mes ciseaux?", + COMPLETE : "Mes ciseaux! Merci beaucoup! Voilà ta récompense...", + }, + + 3206 : { QUEST : "On dirait que _toNpcName_ a des problèmes avec des Cogs.\aVa voir si tu peux l'aider._where_" }, + 3207 : { QUEST : "Ohé, _avName_! Merci d'être venu!\aUne bande de Charabieurs est arrivée et a volé une pile de cartes postales sur mon comptoir.\aS'il te plaît, sors vaincre tous ces Charabieurs et rapporte-moi mes cartes postales!", + INCOMPLETE_PROGRESS : "Il n'y a pas assez de cartes postales! Continue de chercher!", + COMPLETE : "Oh, merci! Maintenant je vais pouvoir livrer le courrier à temps! Voilà ta récompense...", + }, + + 3208 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Cassepieds.\aEssaie de vaincre 10 Cassepieds pour aider tes camarades Toons du Jardin de Daisy." }, + 3209 : { QUEST : "Merci d'avoir battu ces Cassepieds!\aMais maintenant ce sont les Télévendeurs qui sont incontrôlables.\aVa vaincre 10 Télévendeurs au Jardin de Daisy et reviens ici pour ta récompense." }, + + 3247 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Pique-au-sang.\aEssaie de vaincre 20 Pique-au-sang pour aider tes camarades Toons du Jardin de Daisy." }, + + + 3210 : { QUEST : "Oh non, la Fleur qui mouille, rue des Érables, n'a plus de fleurs!\aEmmène-leur dix de tes fleurs à éclabousser pour les aider.\aVérifie que tu as bien 10 fleurs à éclabousser dans ton inventaire d'abord.", + LEAVING: "", + INCOMPLETE_PROGRESS : "J'ai besoin de 10 fleurs à éclabousser. Tu n'en as pas assez!" }, + 3211 : { QUEST : "Oh, merci beaucoup! Ces fleurs à éclabousser vont nous tirer d'embarras.\aMais j'ai peur des Cogs qui sont dehors.\aPeux-tu m'aider et vaincre quelques-uns de ces Cogs?\aReviens me voir après avoir vaincu 20 Cogs dans cette rue.", + INCOMPLETE_PROGRESS : "Il reste encore des Cogs à vaincre par ici! Continue!", + COMPLETE : "Oh, merci! Cela m'aide beaucoup. Ta récompense est...", + }, + + 3212 : { QUEST : "_toNpcName_ a besoin d'aide pour chercher quelque chose qu'elle a perdu.\aVa la voir et vois ce que tu peux faire._where_" }, + 3213 : { QUEST : "Salut, _avName_. Peux-tu m'aider ?\aJe crois que j'ai égaré mon stylo. Je pense que les Cogs l'ont peut-être pris.\aVa vaincre des Cogs pour retrouver le stylo qu'ils m'ont volé.", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon stylo?" }, + 3214 : { QUEST : "Oui, c'est mon stylo! Merci beaucoup!\aMais après ton départ, j'ai réalisé que mon encrier manquait aussi.\aVa vaincre des Cogs pour retrouver mon encrier.", + INCOMPLETE_PROGRESS : "Je cherche encore mon encrier!" }, + 3215 : { QUEST : "Super! Maintenant j'ai retrouvé mon stylo et mon encrier!\aMais tu ne devineras jamais!\aMon bloc-notes a disparu! Ils ont dû le voler aussi!\aVa vaincre des Cogs pour retrouver mon bloc-notes volé, puis reviens pour ta récompense.", + INCOMPLETE_PROGRESS : "Tu as des nouvelles de mon bloc-notes?" }, + 3216 : { QUEST : "C'est mon bloc-notes! Youpii! Ta récompense est...\aHé! Mais où est-elle ?\aJ'avais ta récompense là, dans le coffre de mon bureau. Mais le coffre entier a disparu!\aIncroyable! Ces Cogs ont volé ta récompense!\aVa vaincre des Cogs pour retrouver mon coffre.\aQuand tu me le ramèneras, je te donnerai ta récompense.", + INCOMPLETE_PROGRESS : "Continue de chercher ce coffre! Ta récompense est dedans!", + COMPLETE : "Enfin! J'avais ton nouveau sac à gags dans ce coffre. Le voilà...", + }, + + 3217 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Vendibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Cafteur.\aTu peux en attraper un quand le Cog explose." }, + 3218 : { QUEST : "Bon travail! Maintenant, nous avons besoin d'un pignon de Passetout pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3219 : { QUEST : "Super! Maintenant on n'a plus besoin que d'un pignon en plus.\aCette fois, il nous faut un pignon de Secousse-cousse.\aTu devras peut-être chercher à l'intérieur des bâtiments Vendibots pour trouver cette sorte de Cogs.\aQuand tu en auras attrapé un, rapporte-le pour recevoir ta récompense." }, + + 3244 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Loibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Charognard.\aTu peux en attraper un quand le Cog explose." }, + 3245 : { QUEST : "Bon travail! Maintenant nous avons besoin d'un pignon de Frappedos pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3246 : { QUEST : "Super! Encore un pignon et c'est bon.\aCette fois, il nous faut un pignon de Tournegris.\aQuand tu en auras attrapé un, rapporte-le pour avoir ta récompense." }, + + 3220 : { QUEST : "Je viens d'apprendre que _toNpcName_ te cherchait.\aPourquoi ne vas-tu pas voir ce qu'elle veut ?_where_" }, + 3221 : { QUEST : "Ohé, _avName_! Et voilà!\aJ'ai entendu dire que tu étais expert(e) en éclaboussures.\aJ'ai besoin de quelqu'un pour montrer l'exemple à tous les Toons du Jardin de Daisy.\aUtilise tes attaques par éclaboussure pour vaincre un groupe de Cogs.\aEncourage tes contacts à utiliser aussi les éclaboussures.\aLorque tu auras vaincu 20 Cogs, reviens ici pour ta récompense!" }, + + 3222 : { QUEST : "C'est le moment de faire preuve de ta Toonmaîtrise.\aSi tu réussis à reprendre un certain nombre de bâtiments aux Cogs, tu gagneras le droit à trois quêtes.\aD'abord, tu dois prendre deux bâtiments aux Cogs.\aN'hésite pas à demander l'aide de tes contacts."}, + 3223 : { QUEST : "Super travail pour ces bâtiments!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins deux étages." }, + 3224 : { QUEST : "Fantastique!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins trois étages.\aQuand tu auras fini, reviens chercher ta récompense!", + COMPLETE : "Tu as réussi, _avName_!\aTu as fait preuve d'une excellente Toonmaîtrise.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ dit qu'elle a besoin d'aide.\aVa voir si tu peux lui donner un coup de main ?_where_" }, + 3235 : { QUEST : "Oh, c'est la salade que j'ai commandée!\aMerci de me l'avoir apportée.\aTous ces Cogs ont dû effrayer le livreur habituel de _toNpcName_ encore une fois.\aTu pourrais nous rendre service et vaincre quelques-uns des Cogs qui traînent par ici?\aVa vaincre 10 Cogs dans le Jardin de Daisy et reviens voir _toNpcName_.", + INCOMPLETE_PROGRESS : "Tu es en train de vaincre des Cogs pour moi?\aC'est super!! Continue comme ça!", + COMPLETE : "Oh, merci beaucoup d'avoir vaincu ces Cogs!\aMaintenant je vais peut-être pouvoir reprendre mon programme habituel de livraisons.\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Va raconter à _toNpcName_ tous les Cogs que tu as vaincus._where_" }, + + 3236 : { QUEST : "Il y a beaucoup trop de Loibots par ici.\aTu peux faire ta part de travail!\aVa vaincre 3 bâtiments Loibot." }, + 3237 : { QUEST : "Super travail pour ces bâtiments Loibot!\aMais maintenant il y a beaucoup trop de Vendibots!\aVa vaincre 3 bâtiments Vendibot, puis reviens chercher ta récompense." }, + + 3238 : { QUEST : "Oh non! Un Cog Circulateur a volé la clé du Jardin de Daisy!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Circulateurs ne se trouvent que dans les bâtiments Vendibot." }, + 3239 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du Jardin de Daisy.\aContinue de chercher! Un Cog Circulateur l'a encore!" }, + + 3242 : { QUEST : "Oh non! Un Cog Avocageot a volé la clé du Jardin de Daisy!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Avocageots ne se trouvent que dans les bâtiments Loibot." }, + 3243 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du Jardin de Daisy.\aContinue de chercher! Un Cog Avocageot l'a encore!" }, + + 3240 : { QUEST : "_toNpcName_ vient de me dire qu'un Avocageot lui a volé un sac de graines pour oiseaux.\aVa vaincre des Avocageots jusqu'à ce que tu retrouves les graines pour oiseaux de Piaf, et rapporte-les lui.\aLes Avocageots ne se trouvent que dans les bâtiments Loibot._where_", + COMPLETE : "Oh, merci beaucoup d'avoir retrouvé mes graines pour oiseaux!\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Bien, tu as retrouvé ces graines pour oiseaux!\aMaintenant apporte-les à _toNpcName_._where_", + }, + + 3241 : { QUEST : "Certains des bâtiments des Cogs deviennent beaucoup trop hauts.\aVa voir si tu peux réduire de hauteur certains des immeubles les plus hauts.\aReprends 5 immeubles de 3 étages ou plus et reviens ici pour ta récompense.", + }, + + 3250 : { QUEST : "Lima, la détective de la rue du Chêne, a entendu parler d'un quartier général Vendibot. \aVa donc la voir et aide-la à enquêter.", + }, + 3251 : { QUEST : "Il y a quelque chose de bizarre par ici.\aIl y a tant de Vendibots!\aJ'ai entendu dire qu'ils ont installé leur propre quartier général au bout de cette rue.\aVa au bout de la rue voir ce qu'il en est.\aTrouve des Cogs Vendibots dans leur quartier général, vaincs-en 5 et reviens me le dire.", + }, + 3252 : { QUEST : "OK, annonce la couleur\aQu'est-ce que tu dis?\aAh, le quartier général des Vendibots?? Oh non!!! Il faut faire quelque chose.\aNous devons le dire au Juge Ticot - il saura quoi faire.\aVa le voir tout de suite et dis-lui ce que tu as trouvé. Il est juste au bout de la rue.", + }, + 3253 : { QUEST : "Oui, puis-je t'aider ? Je suis très occupé.\aHein ? Un quartier général Cog?\aHein ? Sottises. Ça n'est pas possible.\aTu dois te tromper. C'est grotesque.\aHein ? Ne discute pas avec moi.\aOk, alors ramène des preuves.\aSi les Vendibots sont vraiment en train de construire ce quartier général Cog, les Cogs du quartier auront des plans sur eux.\aLes Cogs adorent la paperasserie, tu le savais?\aVa vaincre des Vendibots par là-bas jusqu'à ce que tu trouves des plans.\aRapporte-les moi, alors je te croirai peut-être.", + }, + 3254 : { QUEST : "Encore toi, hein ? Des plans? Tu les as?\aLaisse-moi regarder ça! Hmmm... Une usine ?\aCe doit être là qu'ils fabriquent les Vendibots... Et qu'est-ce que c'est que ça?\aOui, exactement ce que je pensais. Je le savais depuis le départ.\aIls sont en train de construire un quartier général des Cogs Vendibots.\aCe n'est pas bon signe. Je dois passer quelques appels. Très occupé. Au revoir!\aHein ? Oh oui, retourne ces plans à la détective Lima.\aElle saura les lire mieux que quiconque.", + COMPLETE : "Qu'a dit le Juge Ticot ?\aOn avait raison ? Oh non. Regardons ces plans.\aHmmm... On dirait que les Vendibots ont installé une usine avec l'outillage pour construire des Cogs.\aÇa a l'air très dangereux. N'y va pas tant que tu n'as pas plus de rigolpoints.\aQuand tu auras plus de rigolpoints, nous en aurons beaucoup à apprendre sur le quartier général des Vendibots.\aPour l'instant, bon travail, voilà ta récompense.", + }, + + + 3255 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3256 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3257 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux lui donner un coup de main._where_" }, + 3258 : { QUEST : "Personne ne sait au juste ce que les Cogs sont en train de faire dans leur nouveau quartier général.\aJ'ai besoin que tu nous ramènes des informations venant directement d'eux.\aSi nous pouvons trouver quatre notes de service internes des Vendibots à l'intérieur de leur quartier général, cela mettrait un peu les choses au clair.\aRamène-moi la première note de service que tu pourras afin qu'on en sache un peu plus.", + }, + 3259 : { QUEST : "Super! Voyons ce que dit cette note de service...\a\"À l'attention des Vendibots :\aJe serai dans mon bureau tout en haut des Tours Vendibot pour faire monter en grade les Cogs. \aLorsque vous aurez gagné suffisamment de mérites, montez me voir par l'ascenseur du hall.\aLa pause est terminée - tout le monde au travail!\"\aSigné, Vice-Président des Vendibots\"\aAah.... Flippy sera content de voir ça. Je lui envoie ça tout de suite.\aVa chercher une seconde note de service et rapporte-la moi.", + }, + 3260 : { QUEST : "Oh, bien, tu es de retour. Voyons ce que tu as trouvé....\a\"À l'attention des Vendibots :\aLes Tours Vendibot ont été équipées d'un nouveau système de sécurité pour empêcher les Toons de pénétrer à l'intérieur.\aLes Toons qui seront attrapés dans les Tours Vendibot seront retenus pour interrogatoire.\aVeuillez en discuter dans le hall autour d'un apéritif.\aSigné, Le Circulateur \"\aTrès intéressant... Je communique l'information immédiatement.\aS'il te plaît, rapporte-moi une troisième note de service.", + }, + 3261 : { QUEST : "Excellent travail, _avName_! Que dit cette note de service ?\a\"À l'attention des Vendibots :\aLes Toons sont parvenus à trouver une façon d'infiltrer les Tours Vendibot.\aJe vous appellerai ce soir pendant le dîner pour vous donner des détails.\aSigné, Télévendeur\"\aHmmm... Je me demande comment les Toons se sont infiltrés....\aRapporte-moi une note de service supplémentaire et je crois que nous aurons assez d'informations pour l'instant.", + COMPLETE : "Je savais que tu pouvais le faire! OK, voilà ce que dit la note de service....\a\"À l'attention des Vendibots :\aJ'ai déjeûné avec M. Hollywood hier.\aIl dit que le Vice-Président est très occupé en ce moment.\aIl ne prendra de rendez-vous qu'avec les Cogs qui méritent une promotion.\aJ'allais oublier, Passetout joue au golf avec moi dimanche.\aSigné, Cafteur\"\aBon... _avName_, voilà qui est bien utile.\aVoilà ta récompense.", + }, + + 3262 : { QUEST : "_toNpcName_ a de nouvelles informations à propos de l'usine du quartier général Vendibot.\aVa donc voir ce que c'est._where_" }, + 3263 : { GREETING : "Salut, mon pote!", + QUEST : "Je suis Zucchini l'entraîneur, mais tu peux simplement m'appeler Coach Z.\aJe mets la gomme sur le squash et les étirements, si tu vois ce que je veux dire.\aÉcoute, les Vendibots ont terminé une énorme usine qui sort des Vendibots 24 heures sur 24.\aPrends une équipe de potes Toons et va me réduire cette usine à néant!\aÀ l'intérieur du quartier général Vendibot, cherche le tunnel qui mène à l'usine puis monte par l'ascenseur de l'usine.\aVérifie que tu as fait le plein de gags, de rigolpoints et que tu as quelques Toons costauds comme guides.\aVa vaincre le contremaître dans l'usine pour ralentir la progression des Vendibots.\aCe sera une vraie séance d'entraînement, si tu vois ce que je veux dire.", + LEAVING : "À plus, mon pote!", + COMPLETE : "Hé mon pote, bon boulot pour cette usine!\aOn dirait que tu as trouvé un morceau de costume de Cog.\aIl doit venir de la chaîne de fabrication des Cogs.\aÇa peut être pratique. Continue à en ramasser quand tu as du temps de libre.\aPeut-être que si tu récupères un costume de Cog complet, ça pourrait être utile à quelque chose....", + }, + + 4001 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Je parie que Tom aimerait de l'aide pour ses recherches._where_", + }, + 4201 : { GREETING: "Salut!", + QUEST : "Je suis très embêté au sujet d'une vague de vols d'instruments.\aJe fais une enquête parmi mes confrères commerçants.\aJe vais peut-être pouvoir trouver une constante qui me permettra de résoudre ce cas.\aVa voir Tina et demande-lui un inventaire des concertinas._where_", + }, + 4202 : { QUEST : "Oui, j'ai parlé à Tom ce matin.\aJ'ai l'inventaire ici.\aTu vas lui apporter tout de suite, ok?_where_" + }, + 4203 : { QUEST : "Super! Et de un...\aMaintenant va chercher celui de Yuki._where_", + }, + 4204 : { QUEST : "Oh! L'inventaire!\aJ'avais complètement oublié.\aJe parie que je peux le faire le temps que tu aies vaincu 10 Cogs.\aRepasse après ça et je promets que ce sera prêt.", + INCOMPLETE_PROGRESS : "31, 32... OUPS!\aTu m'as fait perdre mon compte!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, et voilà.\aMerci de m'avoir laissé un peu de temps.\aEmmène ça à Tom et dis-lui bonjour de ma part._where_", + }, + 4206 : { QUEST : "Hmm, très intéressant.\aÇa commence à ressembler à quelque chose.\aOK, le dernier inventaire est celui de Fifi._where_", + }, + 4207 : { QUEST : "Inventaire ?\aComment est-ce que je pourrais faire un inventaire sans formulaire ?\aVa voir Clément de sol et demande-lui s'il en a un pour moi._where_", + INCOMPLETE_PROGRESS : "Alors, ce formulaire ?", + }, + 4208 : { QUEST : "Ah ça oui j'ai un formulaire d'inventaire!\aMais c'est pas gratuit, tu vois.\aJe vais te dire. Je te le vends pour une tarte à la crème entière.", + GREETING : "Allez, mon petit!", + LEAVING : "Chouette...", + INCOMPLETE_PROGRESS : "Une seule tranche c'est pas assez.\aJ'ai faim, mon petit! Je veux la tarte TOUTE ENTIÈRE.", + }, + 4209 : { GREETING : "", + QUEST : "Mmmm...\aSuper bon!\aVoilà ton formulaire pour Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Merci. Ça va bien m'aider.\aVoyons...violons: 2\aÇa y est! Et voilà!", + COMPLETE : "Bon travail, _avName_!\aJe suis sûr de pouvoir attraper ces voleurs maintenant.\aOn va pouvoir creuser cette affaire!", + }, + + 4211 : { QUEST : "Dis donc, le Dr Tefaispasdebile appelle toutes les cinq minutes. Tu pourrais aller voir quel est son problème ?_where_", + }, + 4212 : { QUEST : "Houlala! Je suis content que le quartier général des Toons ait fini par envoyer quelqu'un.\aÇa fait des jours que je n'ai pas vu un client.\aCe sont ces satanés Gobechiffres qui sont partout.\aJe crois qu'ils enseignent une mauvaise hygiène buccale à nos résidents.\aVa donc en vaincre dix et nous verrons si les affaires reprennent.", + INCOMPLETE_PROGRESS : "Toujours pas de patients. Mais continue!", + }, + 4213 : { QUEST : "Tu sais après tout peut-être que ce n'était pas les Gobechiffres.\aPeut-être que ce sont simplement les Caissbots en général.\aDébarrasse-nous de vingt d'entre eux et j'espère que quelqu'un viendra au moins pour un bilan de santé.", + INCOMPLETE_PROGRESS : "Je sais que vingt ça fait beaucoup. Mais je suis certain que ça va rapporter des tonnes.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne comprends rien du tout!\aToujours pas un SEUL client.\aPeut-être qu'on devrait remonter jusqu'à la source.\aEssaie de reprendre un bâtiment Cog Caissbot.\aÇa devrait faire l'affaire...", + INCOMPLETE_PROGRESS : "Oh, s'il te plaît! Juste un tout petit bâtiment...", + COMPLETE : "Toujours personne.\aMais tu vois, maintenant que j'y pense.\aJe n'avais pas non plus de clients avant l'invasion des Cogs!\aJe te remercie quand même beaucoup pour ton aide.\aCela devrait te rendre service." + }, + + 4215 : { QUEST : "Anna a désespérément besoin de quelqu'un pour l'aider.\aPourquoi ne vas-tu pas voir ce que tu peux faire ?_where_", + }, + 4216 : { QUEST : "Merci d'être là aussi vite!\aOn dirait que les Cogs sont partis avec les tickets de croisière de plusieurs de mes clients.\aYuki a dit qu'elle avait vu un Passetout sortir d'ici avec des tickets plein les mains.\aVa voir si tu peux retrouver le ticket pour l'Alaska de Jack Bûcheron.", + INCOMPLETE_PROGRESS : "Ces Passetouts pourraient être n'importe où maintenant...", + }, + 4217 : { QUEST : "Oh, super. Tu l'as trouvé!\aPuisque je peux compter sur toi, va le porter à Jack pour moi, tu veux bien ?_where_", + }, + 4218 : { QUEST : "Tralala!\aAlaska, me voilà!\aJe ne peux plus supporter ces Cogs infernaux.\aDis donc, je crois qu'Anna a encore besoin de toi._where_", + }, + 4219 : { QUEST : "Ouais, tu as deviné.\aJ'aurais besoin que tu secoues ces satanés Passetouts pour récupérer le ticket de Tabatha pour la fête du Jazz.\aTu sais comment ça marche...", + INCOMPLETE_PROGRESS : "Il y en a d'autres qui rôdent...", + }, + 4220 : { QUEST : "Adorable!\aTu peux lui emmener ça pour moi aussi?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Sois sympa...", + QUEST : "Super, mon petit!\aMaintenant je suis à la fête, _avName_.\aAvant de partir, tu ferais mieux d'aller voir Anna Banane encore une fois..._where_", + }, + 4222 : { QUEST : "C'est la dernière fois, je te promets!\aMaintenant tu vas chercher le ticket de Barry pour le concours de chant.", + INCOMPLETE_PROGRESS : "Allez, _avName_.\aBarry compte sur toi.", + }, + 4223 : { QUEST : "Ça devrait redonner le sourire à Barry._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Bonjour, Bonjour, BONJOUR!\aSuper!\aJe suis sûr que les gars et moi on a va ramasser le gros lot cette année.\aAnna demande que tu repasses la voir pour récupérer ta récompense._where_\aAu revoir, au revoir, AU REVOIR!", + COMPLETE : "Merci pour toute ton aide, _avName_.\aTu es vraiment un atout pour nous à Toontown.\aEn parlant d'atouts...", + }, + + 902 : { QUEST : "Va donc voir Léo.\aIl a besoin de quelqu'un pour porter un message._where_", + }, + 4903 : { QUEST : "Pote!\aMes castagnettes sont toutes ternies et j'ai un grand spectacle ce soir. \aEmporte-les donc à Carlos voir s'il peut me les faire reluire._where_", + }, + 4904 : { QUEST : "Voui, yé crois que yé peux réluire ça.\aMé yé bézoin d'encre de seiche bleue.", + GREETING : "¡Holà!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Tou pé trrouver la seiche partout sour lé pontons de pêche.", + }, + 4905 : { QUEST : "Voui! Souperr!\aAhóra yé bézoin d'un peu de temps pour réluire ça.\aTou pé aller récoupérer un bâtiment de oun étage pendant qué yé trravaille ?", + GREETING : "¡Holà!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Oun pitite minute...", + }, + 4906 : { QUEST : "Trrès bien!\aVoilà les castagnettes pour Léo._where_", + }, + 4907 : { GREETING : "", + QUEST : "Super, mon petit!\aElles sont superbes!\aMaintenant j'ai besoin que tu me rapportes une copie des paroles de \"Un Noël toon\" de chez Élise._where_", + }, + 4908 : { QUEST: "Holà par ici!\aHmmm, je n'ai pas de copie de cette chanson.\aSi tu me laisses un peu de temps je pourrai la retranscrire de mémoire.\aPourquoi tu n'irais pas faire un tour et reprendre un bâtiment de deux étages pendant que j'écris?", + }, + 4909 : { QUEST : "Je suis désolée.\aMa mémoire est un peu floue.\aSi tu vas reprendre un bâtiment de trois étages, je suis sûre que ce sera fait quand tu reviendras...", + }, + 4910 : { QUEST : "Ça y est!\aDésolée d'avoir mis si longtemps.\aRapporte-ça à Léo._where_", + GREETING : "", + COMPLETE : "Génial, mon petit!\aMon concert va casser la baraque!\aÀ propos de casser, tu pourras utiliser ça sur quelques Cogs..." + }, + 5247 : { QUEST : "Le quartier est assez chaud...\aTu pourrais avoir besoin d'apprendre quelques nouveaux trucs.\a_toNpcName_ m'a appris tout ce que je sais, il peut peut-être t'aider aussi._where_" }, + 5248 : { GREETING : "Ahh, oui.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as l'air d'avoir des difficultés avec cette mission.", + QUEST : "Aah, bienvenue, nouvel apprenti.\aJe sais tout ce qu'on peut savoir à propos du jeu de tartes.\aMais avant qu'on ne commence ton entraînement, une petite démonstration s'impose.\aVa donc faire un tour et vaincre dix des plus gros Cogs." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "Excellent!\aMaintenant tu vas nous montrer ce que tu sais faire à la pêche.\aJ'ai fait tomber trois dés en peluche dans la mare hier.\aVa-les pêcher et rapporte-les moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "On dirait que tu n'es pas si habile avec la canne et le moulinet." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Loibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Chefbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Caissbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Vendibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5200 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 5201 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être venu.\aUn groupe de ces Chassetêtes est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5261 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Bifaces est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5262 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Sacasous est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5263 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Tournegris est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5202 : { QUEST : "Le Glagla a été envahi par des Cogs parmi les plus robustes qu'on ait vus.\aTu auras probablement besoin d'emporter plus de gags là-bas.\aJ'ai entendu dire que _toNpcName_ pourrait te prêter un grand sac pour emporter plus de gags._where_" }, + 5203 : { GREETING: "Eh? Tu es dans mon équipe de luge ?", + QUEST : "Qu'est-ce que c'est ? Tu veux un sac?\aJ'en avais un par là...peut-être qu'il est dans ma luge ?\aMais c'est que... Je n'ai pas vu ma luge depuis la grande course!\aPeut-être qu'un de ces Cogs l'a prise ?", + LEAVING : "As-tu vu ma luge ?", + INCOMPLETE_PROGRESS : "Rappelle-moi qui tu es? Désolé, je suis un peu étourdi depuis l'accident." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Est-ce que c'est ma luge ? Je ne vois pas de sac par ici.\aJe crois que Boris Tourne était dans l'équipe...c'est peut-être lui qui l'a?_where_" }, + 5205 : { GREETING : "Oooh, ma tête!", + LEAVING : "", + QUEST : "Hein ? Ted qui? Un sac?\aAh, peut-être qu'il était dans notre équipe ?\aJ'ai tellement mal à la tête que je n'arrive plus à réfléchir.\aPourrais-tu aller me pêcher des glaçons dans la mare gelée pour ma tête ?", + INCOMPLETE_PROGRESS : "Aïe, ma tête me fait mal! Tu as de la glace ?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Aah, ma tête va beaucoup mieux!\aAlors tu cherches le sac de Ted, hein ?\aJe crois qu'il a atterri sur la tête de Sam Simiesque après l'accident._where_" }, + 5207 : { GREETING : "Hé-ho!", + LEAVING : "", + QUEST : "Quoi c'est ça un sac? Qui c'est ça Bouris?\aMoi avoir peur bâtiments! Toi battre bâtiments, moi te donner sac!", + INCOMPLETE_PROGRESS : "Encore bâtiments! Moi encore peur!", + COMPLETE : "Ooooh! Moi t'aime!" }, + 5208 : { GREETING : "", + LEAVING : "Hein!", + QUEST : "Ooooh! Moi t'aime!\aVa Atelier de ski. Sac là-bas." }, + 5209 : { GREETING : "Pote!", + LEAVING : "'plus!", + QUEST : "Bon sang, ce Sam Simiesque est fou!\aSi tu es aussi malade que Sam, je te donne ton sac.\aVa démolir des Cogs pour ton sac, mon pote! Salut!", + INCOMPLETE_PROGRESS : "Es-tu certain(e) d'être au point ? Va donc démolir plus de Cogs.", + COMPLETE : "Ouah! T'es vachement chouette! C'est un sacré tas de Cogs que tu as bousillés!\aVoilà ton sac!" }, + + 5210 : { QUEST : "_toNpcName_ aime quelqu'un du quartier en secret.\aSi tu l'aides, elle pourrait te donner une belle récompense._where_" }, + 5211 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un bec me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5264 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un aileron me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5265 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Circulateurs me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5266 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Attactics me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5212 : { QUEST : "Oh, merci d'avoir retrouvé ma lettre!\aS'il te plaît, peux-tu la remettre au plus beau chien du quartier ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas remis ma lettre, n'est-ce pas?", + }, + 5213 : { GREETING : "Charmé, certainement.", + QUEST : "Je ne peux pas m'occuper de ta lettre, tu vois.\aTous mes chiots m'ont été pris!\aSi tu les ramènes, peut-être qu'on pourra parler.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mes pauvres petits chiots!" }, + 5214 : { GREETING : "", + LEAVING : "Youhouuu!", + QUEST : "Merci de m'avoir rapporté mes petits choux.\aRegardons cette lettre maintenant...Mmmm, il semblerait que j'ai une autre admiratrice secrète.\aIl est temps de rendre visite à mon cher ami Carl.\aTu l'aimeras beaucoup, c'est certain._where_" }, + 5215 : { GREETING : "Hé, hé...", + LEAVING : "Reviens, oui, oui.", + INCOMPLETE_PROGRESS : "Il y en a encore des gros par ici. Reviens nous voir quand il n'y en aura plus.", + QUEST : "Qui est-ce qui t'envoie ? On aime pas trop les bêcheurs, non...\aMais on aime encore moins les Cogs...\aDébarrasse-nous donc des gros et on t'aidera, oui on t'aidera." }, + 5216 : { QUEST : "On t'avait bien dit qu'on t'aiderait.\aTu peux emmener cette bague à la fille.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as encore la bague ???", + COMPLETE : "Oh, tu es un amour!!! Merci!!!\aOh, et j'ai quelque chose de spécial pour toi aussi.", + }, + 5217 : { QUEST : "On dirait que _toNpcName_ pourrait avoir besoin d'aide._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je crois bien qu'il y a d'autres Circulateurs par ici.", + QUEST : "À l'aide!!! À l'aide!!! Je n'en peux plus!\aCes Circulateurs me rendent dingue!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ce n'est pas possible qu'il n'y ait que ça. Je viens d'en voir un!!!", + QUEST : "Oh, merci, mais maintenant ce sont les Attactics!!!\aIl faut que tu m'aides!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Non, non, il y en avait un juste là!", + QUEST : "Je réalise maintenant que ce sont les Usuriers!!!\aJe croyais que tu allais me sauver!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais quoi, peut-être finalement que ce ne sont pas du tout les Cogs!!!\aPourrais-tu demander à Gaëlle de me préparer une potion calmante ? Ça m'aiderait peut-être...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Oh, ce Harry, c'est quelqu'un!\aJe vais concocter quelque chose qui le remettra sur pied!\aBon, on dirait que je n'ai plus de moustaches de sardine...\aSois un ange et cours à la mare m'en attraper.", + INCOMPLETE_PROGRESS : "Tu les as, ces moustaches de sardine ?", }, + 5223 : { QUEST : "OK. Merci, mon ange.\aVoilà, maintenant porte ça à Harry. Ça devrait le calmer tout de suite.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vas-y maintenant, emporte la potion à Harry.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu vas attraper ces Avocageots pour moi, n'est-ce pas?", + QUEST : "Oh merci mon Dieu tu es de retour!\aDonne-moi la potion, vite!!!\aGlou, glou, glou...\aBerk, c'est dégoûtant!!\aMais tu sais quoi? Je me sens bien plus calme. Maintenant que j'ai les idées claires, je réalise que...\aC'est les Avocageots qui me rendaient malade pendant tout ce temps!!!", + COMPLETE : "Bon sang! Maintenant je peux me détendre!\aJ'ai sûrement quelque chose à te donner. Oh, prends ça!" }, + 5225 : { QUEST : "Depuis l'incident avec le pain de navets, Phil Électrique est furieux après _toNpcName_.\aTu pourrais peut-être aider Paul à les réconcilier ?_where_" }, + 5226 : { QUEST : "Ouais, tu as sans doute entendu dire que Phil Électrique est furieux contre moi...\aJ'essayais juste d'être gentil avec ce pain de navets.\aPeut-être que tu pourrais le remettre de bonne humeur.\aPhil a horreur de ces Cogs Caissbots, surtout leurs bâtiments.\aSi tu reprends des bâtiments Caissbot, ça pourrait aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Peut-être quelques bâtiments de plus?", }, + 5227 : { QUEST : "C'est formidable! Va dire à Phil ce que tu as fait._where_" }, + 5228 : { QUEST : "Oh il a fait ça?\aCe Paul croit qu'il peut s'en tirer comme ça, hein ?\aIl m'a cassé ma dent, oui, avec son fichu pain de navets!\aPeut-être que si tu amenais ma dent au Dr Marmotter, il pourrait la réparer.", + GREETING : "Mmmmrrphh.", + LEAVING : "Grrr, grrr.", + INCOMPLETE_PROGRESS : "Encore toi? Je pensais que tu allais faire réparer ma dent.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Caissbots?\aIls effraient mes clients." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Vendibots?\aIls effraient mes clients." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Loibots?\aIls effraient mes clients." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Chefbots?\aIls effraient mes clients." }, + 5230 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Pillard me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5270 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Gros Blochon me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5271 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement M. Hollywood me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5272 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Chouffleur me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5231 : { QUEST : "Super, voilà la dent!\aPourquoi ne filerais-tu pas chez Phil pour lui porter ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je parie que Phil serait content de voir sa nouvelle dent.", + }, + 5232 : { QUEST : "Oh, merci.\aMmmrrrphhhh\aÇa a l'air de quoi, hein ?\aOK, tu peux dire à Paul que je lui pardonne.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, bonne nouvelle.\aJe savais bien que ce vieux Phil ne pourrait pas rester fâché contre moi.\aPour prouver ma bonne volonté, je lui ai fait cuire ce pain de pommes de pin.\aPourrais-tu lui porter, s'il te plaît ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Presse-toi donc. Le pain de pommes de pin est meilleur chaud.", + COMPLETE : "Oh, qu'est-ce que c'est que ça? Pour moi?\aGromp, gromp...\aAïïïaïïe! Ma dent! Ce Paul Poulemouillée!\aOh, après tout ce n'est pas ta faute. Voilà, prends ça pour ta peine.", + }, + 903 : { QUEST : "Tu dois te préparer à voir _toNpcName_ le vieillard du blizzard pour ton test final._where_", }, + 5234 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du fromage grumeleux pour notre bouillon.\aLe fromage grumeleux ne se trouve que sur les Cogs Gros Blochons.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de fromage grumeleux." }, + 5278 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du caviar pour notre bouillon.\aLe caviar ne se trouve que dans les Cogs M. Hollywood.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de caviar." }, + 5235 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Pillard l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5279 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Chouffleur l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5236 : { GREETING: "", + QUEST : "Merci beaucoup.\aSlurp, slurp...\aAhhh, maintenant tu dois attraper un crapaud parlant. Essaie d'en pêcher dans la mare.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce crapaud parlant ?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore gagné ton dessert.", + QUEST : "Oh, c'est vraiment un crapaud parlant. Donne-le moi.\aQu'est-ce que tu dis, crapaud?\aCouac.\aCouac.\aLe crapaud a parlé. Nous avons besoin de dessert.\aRapporte-nous des cônes de glace de chez _toNpcName_.\aLe crapaud aime la glace aux haricots rouges pour une raison inconnue._where_", }, + 5238 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour M. Hollywood ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu déjà trouvé tous mes cônes de glace ?" }, + 5280 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour le Gros Blochon ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé tous mes cônes de glace ?" }, + 5239 : { QUEST : "Merci de m'avoir rapporté mes cônes de glace!\aEn voilà un pour Allan Bic.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ferais mieux de porter cette glace à Allan Bic avant qu'elle ne fonde.", }, + 5240 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe pense que ces Cogs Chouffleurs ont quelquefois de la poudre dans leurs perruques.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre ?" }, + 5281 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe crois que ces Cogs M. Hollywood ont quelquefois de la poudre pour se poudrer le nez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre ?" }, + 5241 : { QUEST : "OK.\aComme je l'ai déjà dit, pour bien lancer une tarte, tu ne dois pas la lancer avec la main...\a...mais avec ton âme.\aJe ne sais pas ce que cela veut dire, alors je vais m'asseoir et réfléchir pendant que tu récupères des bâtiments.\aReviens quand tu as terminé ton défi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ton défi n'est pas terminé.", }, + 5242 : { GREETING: "", + QUEST : "Bien que je ne sache toujours pas de quoi je suis en train de parler, tu es vraiment quelqu'un de valeur.\aJe te donne un dernier défi...\aLe crapaud parlant voudrait une petite amie.\aTrouve un autre crapaud parlant. Le crapaud a parlé.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est cet autre crapaud parlant ?", + COMPLETE : "Houlala! Je suis fatigué par tous ces efforts. Je dois me reposer maintenant.\aTiens, prends ta récompense et va t'en." }, + + 5243 : { QUEST : "Pierre Lasueur commence à empester dans la rue.\aPeux-tu essayer de le convaincre de prendre une douche par exemple ?_where_" }, + 5244 : { GREETING: "", + QUEST : "Oui, je crois que je dois commencer à transpirer pas mal.\aMmmm, peut-être que si je pouvais réparer ce tuyau qui fuit dans ma douche...\aJe crois qu'un pignon de l'un de ces tous petits Cogs ferait l'affaire.\aVa trouver un pignon de Microchef et on va essayer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce pignon que tu étais parti chercher ?" }, + 5245 : { GREETING: "", + QUEST : "Ouaip, on dirait que ça va.\aMais je me sens seul quand je prends ma douche...\aPourrais-tu aller me pêcher un canard en plastique pour me tenir compagnie ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors ce canard?" }, + 5246 : { QUEST : "Le canard en plastique est génial, mais...\aTous ces bâtiments tout autour me rendent nerveux.\aJe me sentirais beaucoup plus détendu s'il y avait moins de bâtiments.", + LEAVING : "", + COMPLETE : "Ok, je vais prendre ma douche maintenant. Et voilà aussi quelque chose pour toi.", + INCOMPLETE_PROGRESS : "Je suis toujours embêté au sujet des bâtiments.", }, + 5251 : { QUEST : "Sébastien Toutseul est censé faire un concert ce soir.\aJ'ai entendu dire qu'il pourrait avoir des problèmes avec son matériel._where_" }, + 5252 : { GREETING: "", + QUEST : "Oh ouais! Sûr que j'aurais besoin d'aide.\aCes Cogs sont arrivés et ont piqué tout mon matériel pendant que je déchargeais la camionnette.\aTu pourrais me donner un coup de main pour retrouver mon micro?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hé mon pote, je ne peux pas chanter sans micro." }, + 5253 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Attactics a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5273 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Circulateurs a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5274 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Usuriers a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5275 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Avocageots a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5254 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont sûrement aux pieds d'un M Hollywood.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5282 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Gros Blochon.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5283 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Pillard.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5284 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Chouffleur.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + + 5255 : { QUEST : "On dirait que tu as besoin de plus de rigolpoints.\aPeut-être que tu pourrais passer un marché avec _toNpcName_.\aVérifie que c'est fait par écrit..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein ?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Chefbots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein ?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Loibots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait ? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Vendibots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Vendibots." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait ? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Caissbots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Caissbots." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "Je ne peux pas t'aider pour les rigolpoints, mais peut-être que _toNpcName_ pourra t'arranger.\aAttention: il est un peu caractériel..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "Je t'ai dit quoi?!?!\aMerci bien! Voilà ton rigolpoint!", + INCOMPLETE_PROGRESS : "Salut!\aQu'est-ce que tu fais encore là?!", + QUEST : "Un rigolpoint? Je ne crois pas!\aSans problème, mais il va d'abord falloir que tu me débarrasses de quelques-uns de ces fichus Loibots." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" est envahi de Cogs très dangereux.\aSi j'étais toi, j'irais là-bas avec plus de gags.\aJ'ai entendu dire que _toNpcName_ peut te faire un grand sac si tu n'as pas peur de marcher._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Il devrait y avoir plein de Loibots par là-bas.\aAlors, vas-y!" , + QUEST : "Un sac plus grand?\aJe pourrais sûrement t'en coudre un en vitesse.\aMais je vais avoir besoin de fil.\aDes Loibots m'ont volé le mien hier matin." }, + 5305 : { GREETING : "Coucou!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Va donc chercher quelques Cogs de plus.\aLa couleur n'a pas encore pris.", + QUEST : "En voilà du beau fil!\aBon, ce n'est pas ma couleur préférée.\aÉcoute-moi bien...\aTu vas là-bas et tu bousilles quelques-uns des Cogs les plus costauds...\aEt pendant ce temps-là, je vais teindre ton fil." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ils doivent bien être quelque part par là...", + QUEST : "Voilà, le fil est teint. Mais nous avons un petit problème.\aJe n'arrive pas à trouver mes aiguilles à tricoter.\aLa dernière fois que je les ai vues, c'était près de la mare." }, + 5307 : { GREETING : "", + LEAVING : "Merci beaucoup!", + INCOMPLETE_PROGRESS : "Rome ne s'est pas tricoté en un jour!" , + QUEST : "Ce sont bien mes aiguilles.\aPendant que je tricote, va faire un peu de nettoyage de Cogs dans ces grands bâtiments.", + COMPLETE : "Excellent travail!\aEt en parlant de bon travail...\aVoilà ton nouveau sac!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai entendu dire que _toNpcName_ a des problèmes avec la justice.\aEst-ce que tu pourrais aller le voir et lui demander?_where_" }, + 5309 : { GREETING : "Je suis content de te voir...", + LEAVING : "", + INCOMPLETE_PROGRESS : "Dépêche-toi! La rue en est envahie!", + QUEST : "Les Loibots ont vraiment pris le pouvoir dans le quartier.\aJ'ai bien peur qu'ils ne me traînent en justice.\aTu pourrais pas les faire dégager de cette rue?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je crois que je les entends qui viennent me chercher...", + QUEST : "Merci. Je me sens un peu mieux.\a Mais il y a autre chose...\aEst-ce que tu pourrais passer chez _toNpcName_ pour me trouver un alibi?_where_" }, + 5311 : { GREETING : "HOUAAA!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne peux pas l'aider si tu n'en trouves pas!", + QUEST : "Un alibi?! Génial!\aTu n'en as pas une autre comme ça?\aJe parie qu'un Avocageot aurait..." }, + 5312 : { GREETING : "Enfin!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "Houlala! Je suis vraiment soulagé d'avoir ça.\aVoilà ta récompense...", + QUEST : "Super! Tu ferais mieux de rapporter ça en vitesse à _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Ali Mentation a besoin d'aide. Peux-tu y faire un saut et lui donner un coup de main ?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, un client! Super! Que puis-je faire pour toi?\aComment ça, que peux-tu faire pour moi? OH! Tu n'es pas un client.\aJe m'en souviens maintenant. Tu es là pour m'aider avec ces affreux Cogs.\aEh bien, ton aide me sera certainement bien utile, même si tu n'es pas un client.\aSi tu nettoies un peu les rues, je te réserve un petit quelque chose.", + INCOMPLETE_PROGRESS : "Si tu ne veux pas d'électricité, je ne peux rien faire pour toi tant que tu n'as pas vaincu ces Cogs.", + COMPLETE : "Bon boulot avec ces Cogs, _avName_.\aTu es vraiment sûr(e) que tu n'as pas besoin d'électricité? Ça pourrait t'être utile...\aNon ? OK, comme tu voudras.\aQuoi? Ah oui, je me souviens. Et voilà. Ça te sera sûrement utile contre ces méchants Cogs.\aContinue à bien travailler!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Eh bien, _avName_, je n'ai rien pour toi pour le moment.\aAttends! Je crois que Susan Sieste cherchait de l'aide. Pourquoi n'irais-tu pas la voir ?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne serai jamais riche avec ces satanés Cogs qui ruinent mes affaires!\aIl faut que tu m'aides, _avName_.\aNettoie quelques bâtiments Cog pour le bien de tout le voisinage et je te rendrai plus riche.", + INCOMPLETE_PROGRESS : "Pauvre de moi! Tu ne peux pas te débarrasser de ces bâtiments?", + COMPLETE : "À moi la fortune! Je vois ça d'ici!\aJe passerai tout mon temps à la pêche. Maintenant, laisse-moi t'enrichir un peu.\aEt voilà!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "Hé _avName_! J'ai entendu dire que Linda Kapok te cherchait.\aTu devrais passer lui rendre visite._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "Bonjour! Waouh, ce que je suis contente de te voir!\aJ'ai passé mon temps à réparer ce répondeur pendant mon temps libre mais il me manque des pièces.\aJ'ai besoin de trois tiges supplémentaires et celles des Pince Menus ont l'air de bien marcher.\aPourrais-tu m'en trouver quelques-unes?", + INCOMPLETE_PROGRESS : "Toujours en train de chercher ces tiges?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, ces tiges iront très bien.\aC'est drôle. J'étais sûre d'avoir une courroie de rechange quelque part mais je n'arrive pas à la trouver.\aPourrais-tu m'en rapporter une de chez Sacasous, s'il te plaît ? Merci!", + INCOMPLETE : "Non, je ne peux pas t'aider avant d'avoir cette courroie.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Voilà, c'est ça. Maintenant ça devrait marcher comme sur des roulettes.\aOù sont passées mes pinces? Je ne peux pas fixer ça sans mes pinces.\aPeut-être qu'une tenaille de Radino ferait l'affaire ?\aSi tu vas m'en chercher une, je te donnerai un petit quelque chose qui t'aidera contre les Cogs.", + INCOMPLETE_PROGRESS : "Toujours pas de tenaille, hein ? Continue à chercher.", + COMPLETE : "Génial! Maintenant il ne me reste plus qu'à fixer tout ça.\aÇa a l'air de marcher maintenant. Me voilà de retour aux affaires!\aEuh, sauf que nous n'avons pas de téléphone. Mais merci quand même de ton aide.\aJe pense que ça t'aidera contre les Cogs. Bonne chance!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "J'ai entendu dire que Rocco avait besoin d'aide. Va voir ce que tu peux faire pour lui._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "Yo! Tu tombes à pic. Moi, ça va pas mieux.\aOuais, j'aurais besoin d'un coup de main avec ces Cogs. Ils sont tout le temps là, à essayer de me donner des leçons.\aSi tu pouvais mettre hors d'état de nuire certains de ces Chefbots, je f'rais en sorte que t'aies pas perdu ton temps.", + INCOMPLETE_PROGRESS : "Eh, _avName_, qu'est-ce que tu fiches?\aFaut qu'tu fasses la chasse à ces Chefbots. On a un accord, tu te rappelles?\aRocco tient toujours sa parole.", + COMPLETE : "Yo, _avName_! Toi, t'es OK pour moi.\aCes Chefbots ils font moins les malins maintenant, pas vrai?\aEh voilà! Un bon petit coup de boost. Maintenant, évite les ennuis, t'entends?", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "Place de la couette, Plume a entendu des rumeurs à propos du quartier général Caissbot.\aVa y faire un tour et vois si tu peux l'aider._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai entendu dire qu'il se passait de drôles de choses.\aBon, c'est peut-être un coup des puces mais il se passe quelque chose de toute façon.\aTous ces Caissbots!\aIJe pense qu'ils ont installé un nouveau quartier général tout près de la Place de la Couette.\aP.J. connaît bien le coin.\aVa voir _toNpcName_ _where_ Demande-lui s'il a entendu quelque chose.", + INCOMPLETE_PROGRESS : "Tu n'as pas encore vu P.J.? Qu'est-ce qui t'en empêche ?\aAh, ces satanées puces!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "Salut _avName_, où vas-tu?\aUn quartier général Caissbot ?? Je n'ai rien vu.\aTu pourrais aller au bout de la place de la Couette et voir si c'est vrai?\aTrouve quelques Caissbots dans leur quartier général, bats-en quelques-uns et reviens me le dire.", + INCOMPLETE_PROGRESS : "Pas encore trouvé le QG? Tu dois y aller, vaincre des Caissbots et voir ce qui s'y passe.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi?! Il y a DÉJÀ un QG Caissbot ?\aTu ferais mieux d'aller tout de suite le dire à Plume!\aQui aurait pu deviner qu'il y aurait un QG Cog à deux pas de sa rue ?", + INCOMPLETE_PROGRESS : "Qu'est-ce que Plume t'a dit ? Tu ne l'as pas encore vu?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis impatient de savoir ce que P.J. a dit.\aHmm... on a besoin de plus d'informations sur cette affaire de Cogs mais je dois me débarrasser de ces puces!\aJe sais! TOI, tu peux essayer d'en savoir plus!\aVa vaincre des Caissbots au QG jusqu'à ce que tu trouves des plans. Après, tu reviens me voir!", + INCOMPLETE_PROGRESS : "Toujours pas de plans? Continue à chercher les Cogs!\aIls doivent avoir des plans!", + COMPLETE : "Tu as les plans?\aGénial! Voyons voir ce qu'ils disent.\aJe vois... Les Caissbots ont construit une Fabrique à Sous pour fabriquer des euros Cog.\aÇa doit être PLEIN de Caissbots. On devrait essayer d'en savoir plus.\aPeut-être que si tu avais un déguisement... Hmmm... attends! Je crois que j'ai une pièce de costume de Cog quelque part par là....\aLa voilà! Prends-la en récompense de tes efforts! Merci encore de ton aide!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "La comtesse te cherchait partout! S'il te plaît, va lui rendre visite, comme ça elle arrêtera d'appeler._where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, je compte sur toi pour m'aider!\aTu vois, ces Cogs font tellement de bruit que je ne peux tout simplement pas me concentrer.\aJe n'arrête pas de perdre le compte de mes moutons!\aSi tu fais diminuer ce bruit, je t'aiderai aussi! Tu peux compter là-dessus!\aBon, où en étais-je ? C'est ça, cent trente-six, cent trente-sept...", + INCOMPLETE_PROGRESS : "Quatre cent quarante-deux... quatre cent quarante-trois...\aQuoi? Tu es déjà de retour ? Mais il y a toujours trop de bruit!\aAh non, j'ai encore perdu le compte.\a Un...deux...trois...", + COMPLETE : "Cinq cent quatre-vingt-treize... cinq cent quatre-vingt-quatorze..\aHello! Ah, je savais que je pouvais compter sur toi! C'est beaucoup plus calme maintenant.\aEt voilà, pour tous ces Gobechiffres.\aLe nombre ? Maintenant il faut que je recommence à compter depuis le début! Un...deux....", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "Ce pauvre père San a cassé son zipper et maintenant il ne peut plus livrer ses clients. Ton aide lui sera certainement utile._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, bonjour _avName_. Tu es là pour m'aider à faire mes livraisons?\aC'est génial! Avec ce zipper cassé, c'est difficile de se déplacer.\aVoyons voir... OK, ça devrait être facile. Ron Chonneau a commandé une cithare la semaine dernière.\aPourrais-tu la lui apporter ? _where_", + INCOMPLETE_PROGRESS : "Ah, salut! Tu as oublié quelque chose ? Ron Chonneau attend sa cithare.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "Ma cithare! Enfin! Bon sang, je suis impatient d'en jouer.\aVa dire au père San que je le remercie, tu veux?", + INCOMPLETE_PROGRESS : "Merci encore pour la cithare. Le père San n'a pas d'autres livraisons pour toi?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "Quelle rapidité! Quelle est la prochaine livraison sur ma liste ?\aBon. Mike Mac a commandé une surfaceuse. Quel drôle de type.\aTu peux la lui apporter, s'il te plaît ?_where_", + INCOMPLETE_PROGRESS : "Cette surfaceuse est pour Mike Mac._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "Super! La surfaceuse que j'avais commandée!\aMaintenant, si seulement il n'y avait pas autant de Cogs dans les environs, je pourrais avoir le temps de m'en servir.\aSois sympa et occupe-toi de certains de ces Caissbots pour moi, tu veux?", + INCOMPLETE_PROGRESS : "Ces Caissbots résistent, hein ? Avec eux, pas facile d'essayer ma surfaceuse.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "Excellent! Maintenant je peux essayer ma surfaceuse.\aS'il te plaît, dis au père San que je viendrai la semaine prochaine passer ma prochaine commande.", + INCOMPLETE_PROGRESS : "C'est tout ce dont j'ai besoin pour le moment. Est-ce que le père San n'est pas en train de t'attendre ?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "Alors, est-ce que Mike Mac a été content de sa surfaceuse ? Génial.\aÀ qui le tour ? Ah, Olivier Daure a commandé un coussin zèbre.\aLe voilà! Pourrais-tu faire un saut chez lui, s'il te plaît ?_where_", + INCOMPLETE_PROGRESS : "Je crois qu'Olivier Daure a besoin de ce coussin pour méditer.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, mon coussin, enfin. Maintenant je peux méditer.\aComment se concentrer avec un tel vacarme ? Tous ces Cogs!\aComme tu es là, peut-être que tu pourrais t'occuper de certains de ces Cogs?\aAprès ça je pourrai utiliser mon coussin en paix.", + INCOMPLETE_PROGRESS : "Il y a toujours tellement de bruit avec ces Cogs! Comment se concentrer ?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "La paix et le calme, enfin. Merci, _avName_.\aS'il te plaît, va dire au père San que je suis très content. OMMM....", + INCOMPLETE_PROGRESS : "Le père San t'as appelé. Tu devrais aller voir ce qu'il veut.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis heureux de voir qu'Olivier Daure est content de son coussin zèbre.\aOh, ces zinnias viennent juste d'arriver pour Eva Sandor-Mir.\aComme tu as l'air d'être un livreur zélé, peut-être que tu pourrais les lui apporter ?_where_", + INCOMPLETE_PROGRESS : "Ces zinnias vont faner si tu ne les livres pas rapidement.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "Quels jolis zinnias! Ca c'est sûr, le père San s'y connaît en livraison.\aOh, eh bien, je suppose que c'est TOI qui fais les livraisons, _avName_. Tu remercieras le père San pour moi!", + INCOMPLETE_PROGRESS : "N'oublie pas de remercier le père San pour les zinnias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Te voilà de retour, _avName_. Tu es sacrément rapide.\aVoyons... Quelle est la prochaine livraison sur ma liste ? Des disques de Zydeco pour Thérèse Eveillé._where_", + INCOMPLETE_PROGRESS : "Je suis sûr que Thérèse Eveillé attend ses disques de Zydeco.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "Des disques de Zydeco? Je ne me rappelle pas avoir commandé de disques de Zydeco.\aOh, je parie que c'est Lou Laberceuse qui les a commandés._where_", + INCOMPLETE_PROGRESS : "Non, ces disques de Zydeco sont pour Lou Laberceuse._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, enfin, mes disques de Zydeco! Je pensais que le père San avait oublié.\aPourrais-tu lui apporter cette courgette ? Il trouvera bien quelqu'un qui en veut une. Merci!", + INCOMPLETE_PROGRESS : "Oh, j'ai déjà plein de courgettes. Apporte-la au père San.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "Une courgette ? Hmm. Eh bien, je trouverai sûrement quelqu'un qui en voudra.\aOK, nous avons presque fini ma liste. Plus qu'une livraison à faire.\aBébé MacDougal a commandé un costume zazou._where_", + INCOMPLETE_PROGRESS : "Si tu ne livres pas ce costume zazou à Bébé MacDougal,\a il va être tout froissé.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Il était une fois... oh! Tu n'es pas là pour écouter une histoire, hein ?\aTu es là pour me livrer mon costume zazou? Super! Waouh, c'est quelque chose.\aEh, tu pourrais transmettre un message au père San pour moi? J'aurais besoin de boutons de manchette en zircon pour aller avec le costume. Merci!", + INCOMPLETE_PROGRESS : "Tu as transmis mon message au père San ?", + COMPLETE : "Des boutons de manchette en zircon, hein ? Eh bien, je vais voir ce que je peux faire pour lui.\aBon, tu m'as été d'une aide précieuse et je ne peux pas te laisser partir sans rien.\aVoici un GROS coup de boost pour t'aider à zapper ces Cogs!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "Dave Bigleau a des problèmes et tu peux peut-être l'aider. Pourquoi ne pas passer à sa boutique ?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi? Hein ? Oh, j'ai dû m'endormir.\aTu sais, ces bâtiments Cog sont remplis de machines qui me donnent vraiment sommeil.\aJe les entends ronronner toute la journée et...\aHein ? Ah, ouais, d'accord. Si tu pouvais te débarrasser de certains de ces bâtiments Cog, je pourrais rester éveillé.", + INCOMPLETE_PROGRESS : "Zzzzz...hein ? Oh, c'est toi, _avName_.\aDéjà de retour ? Je faisais juste une petite sieste.\aReviens quand tu en auras fini avec ces bâtiments.", + COMPLETE : "Quoi? Je me suis juste assoupi une minute.\aMaintenant que ces bâtiments Cog ont disparu, je peux enfin me détendre.\aMerci de ton aide, _avName_.\aA plus tard! Je crois que je vais faire un petit somme.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Va voir Teddy Blaireau. Il a un boulot pour toi._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "Qu'est-ce que tu dis? Non, je n'ai pas de goulot pour toi.\aOh, un boulot! Pourquoi ne pas l'avoir dit plus tôt ? Il faudrait que tu parles plus fort.\aAvec ces Cogs, ce n'est pas facile d'hiberner. Si tu ramènes un peu de calme au Pays des Rêves,\aje te donnerai un petit quelque chose.", + INCOMPLETE_PROGRESS: "Tu as vaincu les bogs? Quels bogs?\aOh, les Cogs! Pourquoi ne pas l'avoir dit plus tôt ?\aHmm, il y a encore pas mal de bruit. Pourquoi ne pas en vaincre quelques autres?", + COMPLETE : "Tu t'es bien amusé? Hein ? Oh!\aTu as fini! Super. C'est sympa de ta part de donner un coup de main comme ça.\aJ'ai trouvé ça dans la pièce du fond mais ça ne m'est d'aucune utilité.\aPeut-être que tu pourras en faire quelque chose. À plus, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece + 6291 : { QUEST : "Les Cogs ont pénétré dans la Banque du Doudou d'Or! Va voir Laurent Lauronpat et vois si tu peux l'aider.", + }, + 6292 : { QUEST : "Ah ces satanés Caissbots! Ils ont volé mes lampes de lecture!\aJ'en ai besoin tout de suite. Tu peux aller les chercher ?\aSi tu me rapportes mes lampes de lecture, je pourrai peut-être t'aider à rencontrer le Vice-Président.\aFais vite!", + INCOMPLETE_PROGRESS : "Il me faut ces lampes. Continue de les chercher!", + COMPLETE : "Te voilà revenu! Et tu as mes lampes!\aJe ne peux pas te remercier comme il le faudrait mais je peux te donner ça.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Nina Lamparo te cherchait, _avName_. Elle a besoin d'aide._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Je suis si contente de te voir, _avName_. J'aurais bien besoin d'aide!\aCes fichus Cogs ont chassé les livreurs et je n'ai plus aucun lit en stock.\aPeux-tu aller voir Amédé Brouilletoitoutseul et me rapporter un lit ?_where_", + INCOMPLETE_PROGRESS : "Amédé n'avait pas de lit ? J'étais sûre qu'il en avait un.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "Un lit ? Bien sûr, en voilà un de prêt.\aApporte-le-lui pour boi, tu veux? Tu as compris? Pour \a\" BOIS \"? Hi-hi!\aTrès drôle, non ? Eh bien, amène-le quand même là-bas s'il te plaît.", + INCOMPLETE_PROGRESS : "Est-ce que le lit a plu à Nina?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "Ce lit ne convient pas. Il est beaucoup trop ordinaire.\aVa voir s'il a quelque chose de plus fantaisie, tu veux?\aJe suis sûre que ça ne te prendra qu'une minute.", + INCOMPLETE_PROGRESS : "Je suis sûre qu'Amédé a un lit plus fantaisie.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "On n'est pas tombé pile avec ce lit, hein ? J'en ai un ici qui devrait faire l'affaire.\aMais il y a un petit problème - il faut d'abord l'assembler.\aPendant que je m'en charge avec mon marteau, pourrais-tu te débarrasser de certains des Cogs, là-dehors?\aCes affreux Cogs ruinent mon travail.\aReviens quand tu auras fini et le lit sera prêt.", + INCOMPLETE_PROGRESS : "Je n'ai pas tout à fait fini d'assembler le lit.\aQuand tu en auras fini avec les Cogs, il sera prêt.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "Salut _avName_!\aTu as fait du sacré bon boulot avec ces Cogs.\aLe lit est prêt. Pourrais-tu le livrer pour moi?\aMaintenant que tous ces Cogs sont partis, les affaires vont reprendre!", + INCOMPLETE_PROGRESS : "Je pense que Nina attend la livraison de ce lit.", + COMPLETE : "Quel joli lit!\aMaintenant mes clients vont être contents. Merci, _avName_.\aTiens, ceci pourra peut-être t'être utile. Quelqu'un l'a laissé ici.", + }, + 7209 : { QUEST : "Va voir Rosée de Lune. Elle a besoin d'aide._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "Oh! Comme je suis contente de te voir, _avName_. J'ai vraiment besoin d'aide!\aJe n'ai pas eu mon compte de sommeil depuis bien longtemps. Tu vois, les Cogs m'ont volé mon dessus-de-lit.\aTu pourrais faire un saut voir si Ed n'aurait rien dans les tons bleus?_where_", + INCOMPLETE_PROGRESS : "Qu'est-ce qu'Ed a dit à propos de ce dessus-de-lit bleu?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "Alors comme ça, Rosée veut un dessus-de-lit, hein ?\aDe quelle couleur ? BLEU?!\aEh bien, je vais devoir le fabriquer spécialement pour elle. Tout ce que j'ai, c'est du rouge.\aTu sais quoi? Si tu vas t'occuper de certains des Cogs là-dehors, je fabriquerai un dessus-de-lit bleu spécialement pour elle.\aDes dessus-de-lit bleus... et puis quoi encore ?", + INCOMPLETE_PROGRESS : "Je travaille toujours sur ce dessus-de-lit bleu, _avName_. Continue de t'occuper de ces Cogs!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te revoir. J'ai quelque chose pour toi!\aVoilà le dessus-de-lit et il est bleu. Elle va l'adorer.", + INCOMPLETE_PROGRESS : "Est-ce que Rosée a aimé le dessus-de-lit ?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "C'est mon dessus-de-lit ? Non, ça ne va pas.\aC'est un tissu ÉCOSSAIS! Qui pourrait dormir avec un motif aussi CRIARD?\aTu vas devoir le rapporter et m'en ramener un autre.\aJe suis sûre qu'il en a d'autres.", + INCOMPLETE_PROGRESS : "Il est hors de question que j'accepte un dessus-de-lit écossais. Va voir ce qu'Ed peut faire.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi? Elle n'aime pas l'ÉCOSSAIS?\aHmm... Voyons ce que nous avons par ici.\aÇa va prendre un certain temps. Pourquoi tu n'irais pas t'occuper de quelques Cogs pendant que j'essaie de trouver autre chose ?\aJ'aurai trouvé quand tu reviendras.", + INCOMPLETE_PROGRESS : "Je suis toujours en train de chercher un autre dessus-de-lit. Comment ça se passe avec les Cogs?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "Hé, bon travail avec ces Cogs!\aEt voilà, il est bleu et il n'est pas écossais.\aReste à espérer qu'elle aime le cachemire.\aApporte ce dessus-de-lit à Rosée.", + INCOMPLETE_PROGRESS : "C'est tout ce que j'ai pour toi pour l'instant.\aS'il te plaît, va apporter ce dessus-de-lit à Rosée.", + COMPLETE : "Oh! Que c'est joli! Le cachemire me va vraiment bien.\aIl est temps pour moi de prendre un peu de repos! À plus tard, _avName_.\aQuoi? Tu es encore là? Tu ne vois pas que j'essaie de dormir ?\aTiens, prends ça et laisse-moi me reposer. Je dois être à faire peur!", + }, + + 7218 : { QUEST : "Daphné Puisé aurait bien besoin d'un coup de main._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, _avName_, je suis contente de te voir! Les Cogs ont pris mes oreillers.\aPourrais-tu aller voir si Pierrot en a?_where_\aJe suis sûre qu'il peut m'aider.", + INCOMPLETE_PROGRESS : "Est-ce que Pierrot a des oreillers pour moi ?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "Salut! Daphné a besoin d'oreillers, hein ? Eh bien, tu as frappé à la bonne porte, partenaire!\aIl y a plus d'oreillers ici que d'épines sur un cactus.\aEt voilà, _avName_. Apporte-les à Daphné, avec mes compliments.\aToujours heureux de donner un coup de main à une demoiselle.", + INCOMPLETE_PROGRESS : "Ces oreillers sont-ils assez doux pour cette jeune dame ?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as les oreillers! Génial!\aEh, attends une seconde! Ces oreillers sont affreusement mous.\aBeaucoup trop mous pour moi. J'ai besoin d'oreillers plus durs.\aRamène-les à Pierrot et vois ce qu'il a d'autre. Merci.", + INCOMPLETE_PROGRESS : "Non! Trop mous. Demande d'autres oreillers à Pierrot.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Trop mous, hein ? Eh bien, laisse-moi voir ce que j'ai d'autre....\aHmm... Il me semblait que j'avais un bon paquet d'oreillers durs. Où sont-ils passés?\aOh! Je me rappelle. Je pensais les renvoyer, donc ils sont à l'entrepôt.\aPourquoi tu ne nettoierais pas quelques bâtiments Cog là-dehors pendant que je les sors de l'entrepôt, partenaire ?", + INCOMPLETE_PROGRESS : "Dur, dur les bâtiments Cog. C'est pas comme ces oreillers.\aContinue à chercher.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "Déjà de retour ? Eh bien, c'est parfait. Tu vois, j'ai trouvé les oreillers que Daphné voulait.\aMaintenant, va les lui apporter. Ils sont tellement durs qu'on s'y casserait les dents!", + INCOMPLETE_PROGRESS : "Ouais, ces oreillers sont bien durs. J'espère qu'ils plairont à Daphné.", + COMPLETE : "Je savais bien que Pierrot aurait des oreillers plus durs.\aAh oui, ils sont parfaits. Bien durs, juste comme je les aime.\aTu aurais besoin de cette pièce de costume de Cog? Tu n'as qu'à la prendre.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Passe voir Sandie Marchand. Elle a perdu son pyjama._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "Je n'ai plus de pyjama! Je ne le trouve plus!\aQu'est-ce que je vais faire ? Oh! Je sais!\aVa voir Big Mama. Elle aura sûrement un pyjama pour moi._where_", + INCOMPLETE_PROGRESS : "Est-ce que Big Mama a un pyjama pour moi?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "Te voilà, petit Toon! Big Mama a les plus beaux pyjamas des Bahamas.\aOh, quelque chose pour Sandie Marchand, hein ? Bon, voyons voir ce que j'ai.\aVoilà un petit quelque chose. Maintenant elle peut dormir en toute élégance!\aVoudrais-tu courir le lui apporter pour moi? Je ne peux pas quitter la boutique pour l'instant.\aMerci, _avName_. À plus tard!", + INCOMPLETE_PROGRESS : "Tu dois apporter ce pyjama à Sandie._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "C'est Big Mama qui me l'envoie ? Oh...\aEst-ce qu'elle n'a pas de pyjama avec des pieds?\aJe porte toujours des pyjamas avec des pieds. Comme tout le monde, non ?\aRamène celui-là et demande-lui de m'en trouver un avec des pieds.", + INCOMPLETE_PROGRESS : "Mon pyjama doit avoir des pieds. Va voir si Big Mama peut m'aider.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "Des pieds? Laisse-moi réfléchir....\aAttends un peu! J'ai ce qu'il te faut!\aTa-dam! Un pyjama avec des pieds. Une jolie grenouillère bleue avec des pieds. La meilleure de toutes les îles.\aS'il te plaît, va-la-lui porter, tu veux? Merci!", + INCOMPLETE_PROGRESS : "Est-ce que Sandie a aimé la grenouillère bleue ?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "OK, elle a EFFECTIVEMENT des pieds, mais je ne peux pas porter une grenouillère bleue!\aDemande à Big Mama si elle n'a pas une autre couleur.", + INCOMPLETE_PROGRESS : "Je suis sûre que Big Mama a une grenouillère d'une autre couleur.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "Quel dommage. C'est la seule grenouillère que j'aie.\aOh, j'ai une idée. Va demander à Tartine. Elle aura peut-être des pyjamas avec des pieds._where_", + INCOMPLETE_PROGRESS : "Non, ce sont les seuls pyjamas que j'aie. Va voir si Tartine en a._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "Des pyjamas avec des pieds? Bien sûr.\aQu'est-ce que tu veux dire, il est bleu? Elle n'aime pas le bleu?\aOh, alors là, c'est plus compliqué. Tiens, essaie ça.\aIl n'est pas bleu et il A des pieds.", + INCOMPLETE_PROGRESS : "Moi j'adore la couleur puce, pas toi?\aJ'espère que Sandie l'aimera....", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "Non, il n'est pas bleu mais personne avec mon teint ne peut porter de couleur puce.\aAbsolument impossible. Retourne là-bas et rapporte-le! Va voir ce que Tartine a d'autre.", + INCOMPLETE_PROGRESS : "Tartine doit avoir d'autres pyjamas. La couleur puce, hors de question pour moi!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Pas de puce non plus. Hmm....\aPar ma barbe, je sais que j'en ai d'autres.\aIl va me falloir un moment pour les trouver. Faisons un marché.\aJe cherche d'autres grenouillères si tu te débarrasses de quelques bâtiments Cog. Ils sont vraiment gênants.\aLa grenouillère sera prête quand tu reviendras, _avName_.", + INCOMPLETE_PROGRESS : "Tu dois éliminer d'autres bâtiments Cog pendant que je cherche d'autres grenouillères.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as fait de l'excellent travail avec ces Cogs! Merci!\aJ'ai trouvé cette grenouillère pour Sandie, j'espère que ça lui plaira.\aApporte-la-lui. Merci.", + INCOMPLETE_PROGRESS : "Sandie attend sa grenouillère, _avName_.", + COMPLETE : "Une grenouillère fuchsia! Parr-fait!\aAh, maintenant je suis parfaitement bien. Voyons voir....\aOh, je suppose que je devrais te donner quelque chose pour te remercier de ton aide.\aPeut-être que ceci te sera utile. Quelqu'un l'a laissé ici.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Va voir Emma Scara. Elle demande de l'aide._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "Ces satanés Cogs ont pris ma crème antirides!\aMes clients DOIVENT absolument avoir de la crème antirides quand je m'occupe d'eux.\aVa voir Honoré et demande-lui s'il a toujours ma recette spéciale en stock._where_", + INCOMPLETE_PROGRESS : "Je refuse de m'occuper de quelqu'un qui n'a pas de crème antirides.\aVa voir si Honoré en a.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, cette Emma n'est pas facile. Elle ne se contente pas de ma recette habituelle.\aCe qui veut dire que je vais avoir besoin de corail chou-fleur, mon ingrédient spécial ultrasecret. Mais je n'en ai pas en stock.\aPourrais-tu aller m'en pêcher dans l'étang? Dès que tu auras le corail, je préparerai un mélange de crème pour Emma.", + INCOMPLETE_PROGRESS : "J'ai besoin de corail chou-fleur pour préparer ma crème antirides.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "Waouh, quel beau corail chou-fleur!\aOk, voyons voir... Un peu de ceci et une petite giclée de cela... Et maintenant, une cuillerée d'algues.\aHé, où sont les algues? On dirait que je suis aussi à court d'algues.\aPeux-tu faire un saut à l'étang et me ramasser une belle algue gluante ?", + INCOMPLETE_PROGRESS : "Plus un brin d'algue gluante dans cette boutique.\aImpossible de préparer la crème sans algue.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "Oooh! Voilà une algue gluante à souhait, _avName_.\aMaintenant, je vais juste écraser quelques perles dans le mortier avec le pilon.\aHum, où est mon pilon ? À quoi sert un mortier sans un pilon ?\aJe parie que ce fichu Usurier l'a pris quand il est venu ici!\aIl faut que tu m'aides à le trouver! Il se dirigeait vers le QG Caissbot!", + INCOMPLETE_PROGRESS : "Je ne peux tout simplement pas écraser mes perles sans un pilon.\aFichus Usuriers!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "Parfait! Tu as mon pilon!\aMaintenant on va pouvoir travailler. Écraser ça... Touiller un peu et...\aÇa y est! Va dire à Emma que c'est de la bonne crème, fraîchement préparée.", + INCOMPLETE_PROGRESS : "Tu devrais apporter cette crème à Emma tant qu'elle est encore fraîche.\aC'est une cliente très difficile.", + COMPLETE : "Honoré n'avait pas un pot de crème antirides plus gros que ça? Non ?\aEh bien, je suppose qu'il faudra simplement que j'en recommande quand je n'en aurai plus.\aÀ un de ces quatre, _avName_.\aQuoi? Tu es toujours là? Tu ne vois pas que j'essaie de travailler ?\aTiens, prends ça.", + }, + +# Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu es intéressé par les pièces de déguisement de Loibot, tu devrais aller voir _toNpcName_.\aJ'ai entendu dire qu'il a grand besoin d'aide pour ses recherches météorologiques._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Oui, oui. J'ai des pièces de déguisement de Loibot.\aMais elles sont sans intérêt pour moi.\aMes recherches portent sur les fluctuations de la température ambiante de Toontown.\aJ'échangerais volontiers des pièces de déguisement contre des sondes de température Cog.\aTu peux commencer par aller voir %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "Ah, parfait!\aC'est ce que je craignais...\aOh, oui! Voilà ta pièce de déguisement.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "Pour obtenir d'autres pièces de déguisement de Loibot, tu devrais retourner voir _toNpcName_.\aJ'ai entendu dire qu'il avait besoin d'assistants de recherche._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "Plus de pièces de déguisement de Loibot?\aBon, si tu insistes...\amais j'ai besoin d'une autre sonde de température Cog.\aCette fois-ci, va sur %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[2200][-1], + COMPLETE : "Merci!\aVoilà ta pièce de déguisement.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as besoin de pièces de déguisement Loibot supplémentaires, tu devrais retourner voir _toNpcName_.\aApparemment, il a toujours besoin d'aide pour ses recherches météorologiques._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais te montrer utile!\aEst-ce que tu peux aller jeter un oeil sur %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : " Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[2300][-1], + COMPLETE : "Hmmm, je n'aime pas trop ça...\amais voici ta pièce de déguisement...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : " Qui-tu-sais a besoin de relevés de température supplémentaires.\aPasse le voir si tu veux une autre pièce de déguisement._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "Encore toi?\aTu as vraiment envie de travailler...\aLa prochaine destination, c'est %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "Bon! On dirait que tu t'en sors plutôt bien!\aTa pièce de déguisement...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as envie d'une autre pièce de déguisement de Loibot..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te trouver ici!\aMaintenant, j'ai besoin de relevés sur %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[1200][-1], + COMPLETE : "Merci beaucoup!\aTu n'es probablement pas loin d'avoir tout ton déguisement...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "Je crois que _toNpcName_ a encore du travail pour toi._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : " Content de te revoir, _avName_!\aEst-ce que tu peux faire un relevé sur %s?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "Super boulot!\aVoici ta récompense. Tu l'as bien méritée!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais ce qu'il faut faire._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, mon ami!\aEst-ce que tu pourrais aller à %s et me trouver une autre sonde de température?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu es vraiment en train de chercher sur %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "Excellent!\aGrâce à ton aide, mes recherches avancent très vite!\aVoici ta récompense.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a parlé de toi.aOn dirait que tu as fait une sacrée impression!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te revoir!\aJe t'attendais.\aLe prochain relevé dont j'ai besoin, c'est sur %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[5200][-1], + COMPLETE : "Merci!\aVoici ta récompense.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as besoin de terminer ton déguisement de Loibot...\a_toNpcName_ peut t'aider._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "Salut, jeune chercheur!\aNous avons encore besoin de relevés de %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "Excellent travail!\aVoilà ton machin de Loibot...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a un autre travail pour toi.\aSi tu n'en as pas assez de le voir..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Bon, très bien.\aTu te sens d'attaque pour aller chercher autre chose?\aCette fois-ci, essaie %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[4100][-1], + COMPLETE : "Et un de plus!\aQuelle efficacité!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "Tu es toujours à la recherche de pièces de déguisement de Loibot?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as sans doute déjà deviné...\amais j'ai besoin de relevés de %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[4200][-1], + COMPLETE : "On y est presque!\aEt voilà...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai presque honte de le dire, mais..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "Qu'est-ce que tu penses de %s? Est-ce que tu crois que tu pourrais aller chercher une sonde là-bas aussi?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Encore du bon travail, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Va voir le Professeur, si tu as encore besoin de pièces de déguisement._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "Je crois qu'on a encore besoin d'un relevé de %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[9100][-1], + COMPLETE : "Bon travail!\aJe crois qu'on se rapproche...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a une dernière mission pour toi._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "Déjà de retour?\aLe dernier relevé est sur %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[9200][-1], + COMPLETE : "Ça y est enfin!\aMaintenant, tu vas pouvoir t'introduire dans le bureau du Procureur et ramasser des convocations du jury.\aBonne chance et merci pour ton aide!", + }, + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "If you are interested in Bossbot disguise parts you should visit _toNpcName_._where_", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "Oui, je peux te trouver des pièces de Chefbot.\aMais tu devras m'aider à terminer ma collection de Chefbot.\aVa défier un Laquaistic.", + INCOMPLETE_PROGRESS : "Tu n'as pas trouvé de Laquaistic ? Quel dommage !", + COMPLETE : "Tu n'as pas été recalé j'espère ?\aVoici ta première pièce de déguisement.", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a encore besoin d'aide, si ça te dit._where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "Une autre pièce de déguisement ?\aAbsolument...\amais seulement si tu parviens à vaincre un Gratte-papier.", + INCOMPLETE_PROGRESS : "Les Gratte-papiers sont dans les rues.", + COMPLETE : "Un vrai jeu d'enfant !\aVoici ta seconde pièce de déguisement.", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "Il y a vraiment un seul endroit où trouver des pièces de Chefbot._where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "Maintenant j'ai besoin d'un Béniouioui...", + INCOMPLETE_PROGRESS : "Les Béniouiouis sont dans les rues.", + COMPLETE : "Super, l'ami, tu es bon.\aVoici ta troisième pièce de déguisement.", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a d'autres pièces pour toi...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu triomphes d'un Microchef, je te donnerai une autre pièce de déguisement.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[1100][-1], + COMPLETE : "Tu t'es bien défendu !\aVoici ta quatrième pièce de déguisement.", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "Rends-toi dans..._where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis à la recherche d'un Touptisseur maintenant...", + INCOMPLETE_PROGRESS : "Des problèmes ? Va voir dans %s" % GlobalStreetNames[3100][-1], + COMPLETE : "Quelle terrible défaite !\aVoici ta cinquième pièce de déguisement.", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "Je pense que tu sais où aller maintenant..._where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "Prochain sur ma liste : un Chassetête.", + INCOMPLETE_PROGRESS : "Tu auras peut-être plus de chance en allant faire un tour dans les bâtiments.", + COMPLETE : "Je vois que tu n'as eu aucun mal à en trouver un.\aVoici ta sixième pièce de déguisement.", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a besoin de plus de Chefbots.", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "À présent, j'ai besoin que tu me déniches un Attactic.", + INCOMPLETE_PROGRESS : "Tu auras peut-être plus de chance en allant faire un tour dans les bâtiments.", + COMPLETE : "Tu fais un excellent chasseur !\aVoici ta septième pièce de déguisement.", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu souhaites obtenir plus de pièces de déguisement, va dans..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "Et maintenant, le coup de grâce : le Gros Blochon !", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Je savais que je pouvais compter sur toi pour...\apeu importe !\aVoici une autre pièce de déguisement.", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ était à ta recherche...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "À présent, j'aimerais que tu t'attaques à l'un des nouveaux Cogs Chefbot, qui sont plus dangereux.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Ils sont plus forts qu'ils en ont l'air, hein ?\aEnfin, je te dois quand même une pièce de déguisement.", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "Pourrais-tu aller faire un tour à..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "Ces Cogs version 2.0 sont très intéressants.\aS'il te plaît, va en attaquer un autre.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Merci !\aUne autre pièce de déguisement pour toi.", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu peux, arrête-toi et va voir _toNpcName_.", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "Je me demande s'ils peuvent se régénérer...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "J'imagine que non.\aVoici ta pièce de déguisement...", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "Peut-être que ce ne sont pas des Chefbots du tout...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Finalement, je crois bien que ce sont des Chefbots.\aChoisis une autre pièce de déguisement.", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais sans doute déjà ce que je vais te dire...", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "Peut-être que d'une certaine manière, ils sont parents avec les Skelecogs...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Ce fut peu concluant...\aVoici ta pièce de déguisement.", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "S'il te plaît, retourne voir _toNpcName_.", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne suis toujours pas convaincu qu'il ne s'agit pas d'une espèce de Skelecog...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Peut-être pas.\aVoici une autre pièce de déguisement.", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "C'est sans doute l'endroit où tu as le moins envie d'aller, mais...", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "Ces nouveaux Cogs me laissent vraiment perplexe.\aPourrais-tu aller en attaquer un autre, s'il te plaît ?", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Vraiment fascinant.\aUne pièce de déguisement pour te remercier de tes efforts.", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ commence à sonner comme un disque rayé ...", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai presque trouvé ce que sont ces Cogs.\aJuste encore un...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Oui, je crois que je suis sur la bonne voie.\aAh oui.\aÇa, c'est pour toi...", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "Tu dois aller raconter tout ça à Flippy...", + INCOMPLETE_PROGRESS : "Flippy se trouve dans Toon Hall", + COMPLETE : "Une nouvelle espèce de Cog !\aBon travail !\aVoici ta dernière pièce de déguisement.", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["ouaf", "ouarf", "rrrgh"] +ChatGarblerCat = ["miaou", "maaou"] +ChatGarblerMouse = ["couic", "couiiic", "iiiiic"] +ChatGarblerHorse = ["hihiii", "brrr"] +ChatGarblerRabbit = ["ouic", "pouip", "plouik", "bouip"] +ChatGarblerDuck = ["coin", "couac", "coiinc"] +ChatGarblerMonkey = ["oh", "hou", "ah"] +ChatGarblerBear = ["grrr", "grrr"] +ChatGarblerPig = ["rrrrr", "ouing", "ouing"] +ChatGarblerDefault = ["blabla"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Recherche de coordonnées pour %s." +AvatarDetailPanelFailedLookup = "Impossible d'obtenir les coordonnées de %s." +AvatarDetailPanelPlayer = "Joueur : %(player)s\nMonde : %(world)s" +AvatarDetailPanelPlayerShort = "%(player)s\nMonde : %(world)s\nLieu : %(location)s" +AvatarDetailPanelRealLife = "Hors ligne" +AvatarDetailPanelOffline = "District: hors-ligne\nLieu : hors-ligne" +AvatarDetailPanelOnline = "District : %(district)s\nLieu : %(location)s" +AvatarDetailPanelOnlinePlayer = "District : %(district)s\nLieu : %(location)s\nJoueur : %(player)s" +AvatarDetailPanelOffline = "District: hors-ligne\nLieu : hors-ligne" +AvatarShowPlayer = "Montrer joueur" +OfflineLocation = "Hors ligne" + +#PlayerDetailPanel +PlayerToonName = "Toon : %(toonname)s" +PlayerShowToon = "Montrer Toon" +PlayerPanelDetail = "Informations joueur" + + +# AvatarPanel.py +AvatarPanelFriends = "Contacts" +AvatarPanelWhisper = "Chuchoter" +AvatarPanelSecrets = "Secrets" +AvatarPanelGoTo = "Aller à" +AvatarPanelPet = "Montrer le Doudou" +AvatarPanelIgnore = "Ignorer" +AvatarPanelIgnoreCant = "OK" +AvatarPanelStopIgnoring = "Arrête d'ignorer" +AvatarPanelReport = "Signaler" +#AvatarPanelCogDetail = "Dépt : %s\nNiveau: %s\n" +AvatarPanelCogLevel = "Niveau: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Détails du Toon" +AvatarPanelGroupInvite = "Inviter dans le groupe" +AvatarPanelGroupRetract = "Retirer invitation" +AvatarPanelGroupMember = "Déjà dans le groupe" +AvatarPanelGroupMemberKick = "Kick Out" + +# grouping messages +groupInviteMessage = "%s aimerait que tu rejoignes son groupe" + + +# Report Panel +ReportPanelTitle = "Signaler un joueur" +ReportPanelBody = "Cette fonction permet d'envoyer un rapport complet à un modérateur. Au lieu d'envoyer un rapport, tu peux opter pour l'une des options suivantes :\n\n - Te téléporter dans un autre district\n - Utiliser \" Ignorer \" sur le panneau de commande du Toon\n\nSouhaites-tu vraiment signaler %s à un modérateur ?" +ReportPanelBodyFriends = "Cette fonction permet d'envoyer un rapport complet à un modérateur. Au lieu d'envoyer un rapport, tu peux opter pour l'une des options suivantes :\n\n - Te téléporter dans un autre district \n - Rompre votre amitié\n\n Souhaites-tu vraiment signaler %s à un modérateur ?\n\n(Cela rompra également votre amitié)" +ReportPanelCategoryBody = "Tu es sur le point de signaler %s. Un modérateur sera alerté de ta plainte et prendra les mesures appropriées envers toute personne ayant enfreint nos règles. Sélectionne la raison pour laquelle tu signales %s:" +ReportPanelBodyPlayer = "Cette fonction est en cours de développement et sera bientôt disponible. En attendant, tu peux :\n\n - Te rendre dans DXD pour rompre votre amitié.\n - Raconter ce qui s'est passé à un parent." + +ReportPanelCategoryLanguage = "Propos grossiers" +ReportPanelCategoryPii = "Partage ou demande d'informations personnelles" +ReportPanelCategoryRude = "Impoli ou méchant" +ReportPanelCategoryName = "Mauvais nom" + +ReportPanelConfirmations = ( + "Tu es sur le point de signaler que %s a utilisé des propos obscènes, sectaires ou sexuellement explicites.", + "Tu es sur le point de signaler que %s n'est pas prudent, et donne ou demande un numéro de téléphone, une adresse, un nom de famille, une adresse e-mail, un mot de passe ou un nom de compte.", + "Tu es sur le point de signaler que %s tyrannise, harcèle ou manifeste un comportement extrême pour perturber le jeu.", + "Tu es sur le point de signaler que %s a créé un nom qui n'est pas conforme aux Règles du jeu de Disney.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "Nous prenons les rapports très au sérieux. Ton rapport sera examiné par un modérateur, qui prendra les mesures appropriées envers toute personne ayant enfreint nos règles. S'il s'avère que ton compte a lui-même enfreint les règles, ou si tu fais de faux signalements ou utilises la fonction « Signaler un joueur » de manière abusive, un modérateur pourrait prendre des mesures contre ton compte. Es-tu sûr de vouloir signaler ce joueur ?" + +ReportPanelThanks = "Merci. Ton rapport a été envoyé à un modérateur qui se chargera de son examen. Tu n'as pas besoin de nous contacter à nouveau concernant cet incident. L'équipe chargée de la modération prendra les mesures qui s'imposent à l'encontre de tout joueur ayant enfreint nos règles." + +ReportPanelRemovedFriend = "Nous avons automatiquement supprimé %s de ta Liste d'amis." + +ReportPanelAlreadyReported = "Tu as déjà signalé %s durant cette session. Un modérateur se chargera d'examiner ton précédent rapport." + +# Report Panel +IgnorePanelTitle = "Ignorer un joueur" +IgnorePanelAddIgnore = "Veux-tu ignorer %s pour le reste de cette session ?" +IgnorePanelIgnore = "À présent, tu ignores %s." +IgnorePanelRemoveIgnore = "Veux-tu arrêter d'ignorer %s ?" +IgnorePanelEndIgnore = "Tu n'ignores plus %s." +IgnorePanelAddFriendAvatar = "%s est ton ami(e). Tu ne peux pas l'ignorer tant que vous êtes amis. %s (%s) est ton ami(e). Tu ne peux pas l'ignorer tant que vous êtes amis." +IgnorePanelAddFriendPlayer = "" + +# PetAvatarPanel.py +PetPanelFeed = "Nourrir" +PetPanelCall = "Appeler" +PetPanelGoTo = "Aller à" +PetPanelOwner = "Montrer le propriétaire" +PetPanelDetail = "Détails de l'animalerie" +PetPanelScratch = "Cajoler" + +# PetDetailPanel.py +PetDetailPanelTitle = "Apprentissage des tours" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Saute', + 1: 'Fais le beau', + 2: 'Fais le mort', + 3: 'Fais une roulade', + 4: 'Saute en arrière', + 5: 'Danse', + 6: 'Parle', + } + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutre', + 'hunger': 'affamé', + 'boredom': "s'ennuie", + 'excitement': 'excité', + 'sadness': 'triste', + 'restlessness': 'agité', + 'playfulness': 'joueur', + 'loneliness': 'solitaire', + 'fatigue': 'fatigué', + 'confusion': 'perplexe', + 'anger': 'en colère', + 'surprise': 'surpris', + 'affection': 'affectueux', + } + +# DistributedAvatar.py +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Contacts" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Essaie d'aller à %s." +TeleportPanelNotAvailable = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelIgnored = "%s t'ignore" +TeleportPanelNotOnline = "%s n'est pas en ligne en ce moment." +TeleportPanelWentAway = "%s est parti(e)." +TeleportPanelUnknownHood = "Tu ne sais pas aller jusqu'à %s!" +TeleportPanelUnavailableHood = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelDenySelf = "Tu ne peux pas aller te voir toi-même!" +TeleportPanelOtherShard = "%(avName)s est dans le district %(shardName)s, et tu es dans le district %(myShardName)s. Veux-tu aller à %(shardName)s?" +TeleportPanelBusyShard = "%(avName)s se trouve dans un district qui affiche complet. Jouer dans un district complet peut considérablement ralentir les performances de jeu. Es-tu sûr de vouloir changer de district ?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Je suis le chef." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Je suis le contremaître." +FactoryBossBattleTaunt = "Je te présente le contremaître." +MintBossTaunt = "Je suis le Superviseur." +MintBossBattleTaunt = "Vous devez parler au Superviseur." +StageBossTaunt = "Ma justice n'est pas aveugle" +StageBossBattleTaunt = "Je suis au-dessus des lois" +CountryClubBossTaunt = "Je suis le Président du Club." +CountryClubBossBattleTaunt = "Tu dois t'adresser au Président du Club." +ForcedLeaveCountryClubAckMsg = "Le Président du Club a été vaincu avant que tu n'arrives jusqu'à lui. Tu n'as pas récupéré d'actions." + +# HealJokes.py +ToonHealJokes = [ + ["Qu'est ce qui fait PIOU-PIOU?", + "Un poussin de 500 kilos!"], + ["Que dit un pneu qui va voir un médecin ?", + "Docteur, je me sens crevé."], + ["Pourquoi est-ce difficile pour un fantôme de mentir ?", + "Parce qu'il est cousu de fil blanc."], + ["Vous connaissez l'histoire de la chaise ?", + "Dommage, elle est pliante!"], + ["Qu'est-ce qui est vert et qui monte et qui descend?", + "Un petit pois dans un ascenseur!"], + ["Quel est le comble de l'électricien ?", + "De ne pas être au courant."], + ["Que font deux chiens qui se rencontrent à Tokyo?", + "Ils se jappent au nez."], + ["Quel est le futur de \"je baille\"?", + "Je dors."], + ["Quel est l'animal le plus rapide ?", + "Le pou car il est toujours en tête!"], + ["Quel animal n'a jamais soif?", + "Le zébu, parce que quand zébu zé plus soif!"], + ["Quel est le comble pour un myope ?", + "De manger des lentilles."], + ["Pourquoi as-tu mis le journal dans le réfrigérateur ?", + "Pour avoir des nouvelles fraîches!"], + ["Qu'est-ce qui est gris et qui t'éclabousse de confiture ?", + "Une souris qui mange un beignet."], + ["Que demande un douanier à un cochon qui passe la frontière ?", + "Son passe-porc."], + ["Que dit un bébé souris à sa maman quand il voit passer une chauve-souris?", + "Maman, un ange!"], + ["Comment appelle-t-on un ascenseur au Japon ?", + "En appuyant sur le bouton."], + ["Comment appelle-t-on un poisson pas encore né?", + "Un poisson pané."], + ["Si tu fais tomber un chapeau blanc dans la mer rouge, comment ressort-il?", + "Mouillé."], + ["Que demande un chat qui entre dans une pharmacie ?", + "Du sirop pour matou."], + ["Quel est le comble pour un jockey?", + "D'être à cheval sur les principes."], + ["Quelles sont les deux choses que tu ne peux pas prendre au petit-déjeuner ?", + "Le déjeuner et le dîner."], + ["Qu'est ce qu'on donne à un éléphant qui a de grands pieds?", + "De grandes chaussures."], + ["Comment sait-on qu'un éléphant est caché dans le réfrigérateur ?", + "Aux empreintes de pattes dans le beurre."], + ["Quelle est la différence entre un instituteur et un thermomètre ?", + "Aucune, on tremble toujours quand ils marquent zéro!"], + ["Qu'est-ce qui est petit, carré et vert ?", + "Un petit carré vert."], + ["Quel est le comble pour un éléphant ?", + "D'être sans défense."], + ["Que dit le 0 au 8?", + "Tiens, tu as mis ta ceinture!"], + ["Qu'est ce qu'il ne faut jamais faire devant un poisson-scie ?", + "La planche!"], + ["Pourquoi est-ce que certaines personnes travaillent la nuit ?", + "Pour mettre leur travail à jour."], + ["Quel est le comble de la patience ?", + "Trier des petits pois avec des gants de boxe."], + ["Qu'est ce qui voyage tout autour du monde en restant dans son coin ?", + "Un timbre."], + ["Quel est le comble pour une souris?", + "Avoir un chat dans la gorge."], + ["Quel est le comble pour un canard?", + "En avoir marre!"], + ["Quel est le comble pour un magicien ?", + "Se nourrir d'illusions."], + ["Quel est le comble de la clé?", + "Se faire mettre à la porte."], + ["Quel est le comble pour un cordonnier ?", + "Avoir les dents qui se déchaussent."], + ["De quelle couleur sont les petits pois?", + "Les petits poissons rouges."], + ["Qu'est-ce qui baille et qui ne dort jamais?", + "Une porte."], + ["Tu sais ce que c'est un canif?", + "C'est un p'tit fien!"], + ["Qu'est-ce qu'un chou au fond d'une baignoire ?", + "Un choumarin!"], + ["Quel est le comble pour le propriétaire d'un champ de pommiers?", + "Travailler pour des prunes!"], + ["Qu'est-ce qui est aussi grand que l'Arc de Triomphe mais ne pèse rien ?", + "Son ombre."], + ["Comment s'appelle un boomerang qui ne revient pas?", + "Un bout de bois."], + ["Pourquoi est-ce que les éléphants se déplacent en troupeau compact ?", + "Parce que c'est celui du milieu qui a la radio."], + ["De quelle couleur sont les parapluies quand il pleut ?", + "Ils sont tout verts."], + ["Quel est le comble du torero?", + "Que le taureau soit vache."], + ["Quel est l'animal le plus heureux?", + "Le hibou, parce que sa femme est chouette."], + ["Que dit un vitrier à son fils?", + "Tiens-toi à carreau si tu veux une glace."], + ["Comment appelle-t-on un chien sans pattes?", + "On ne l'appelle pas, on va le chercher."], + ["Qu'ont les girafes que n'ont pas les autres animaux?", + "Des bébés girafes."], + ["Un chameau peut-il avoir 3 bosses?", + "Oui, s'il se cogne la tête contre le mur."], + ["Pourquoi les musiciens aiment-ils prendre le train ?", + "Parce que la voie fait ré."], + ["Deux fourmis sont sur un âne, laquelle va doubler l'autre ?", + "Aucune, il est interdit de doubler sur un dos d'âne."], + ["Quelle est la note la plus basse ?", + "Le sol."], + ["Pourquoi les trains électriques vont-ils plus vite que les trains à vapeur ?", + "Parce qu'ils ont arrêté de fumer."], + ["Qu'est ce qu'un arbuste dit à un géranium?", + "Espèce d'empoté!"], + ["Que recommande la maman allumette à ses enfants?", + "Surtout, ne vous grattez pas la tête!"], + ["Qu'est-ce qu'il y a à la fin de tout ?", + "La lettre T."], + ["Pourquoi les poissons-chats s'ennuient-ils?", + "Parce qu'il n'y a pas de poissons-souris."], + ["Qu'est-ce que le vainqueur du marathon a perdu?", + "Son souffle."], + ["Comment appelle-t-on un spectacle qui rend propre ?", + "Un ballet."], + ["Qu'est-ce qui fait 999 fois \"Tic\" et une fois \"Toc\"?", + "Un mille-pattes avec une jambe de bois."], + ["Comment reconnaît-on un écureuil d'une fourchette ?", + "En les mettant au pied d'un arbre, celui qui monte est l'écureuil."], + ["Pourquoi les flamants roses lèvent-ils une patte en dormant ?", + "Parce qu'ils tomberaient s'ils levaient les deux."], + ["Qu'est-ce qui est noir quand il est propre et blanc quand il est sale ?", + "Un tableau noir!"], + ["Qu'est-ce qui fait Oh, Oh, Oh?", + "Le Père Noël qui marche en arrière."], + ["Qu'est-ce qui peut voyager jour et nuit sans quitter son lit ?", + "La rivière."], + ["Quel arbre n'aime pas la vitesse ?", + "Le frêne."], + ["Pourquoi est-ce que les dinosaures ont de longs cous?", + "Parce que leurs pieds sentent mauvais."], + ["Qu'est-ce qui est jaune et qui court très vite ?", + "Un citron pressé."], + ["Pourquoi est-ce que les éléphants n'oublient jamais?", + "Parce qu'on ne leur dit jamais rien."], + ["Quel animal peut changer de tête facilement ?", + "Un pou."], + ["Qu'est-ce qu'un steak caché derrière un arbre ?", + "Un steak caché."], + ["Pourquoi est-ce que les serpents ne sont pas susceptibles?", + "Parce qu'on ne peut pas leur casser les pieds."], + ["Pourquoi dit-on que les boulangers travaillent rapidement ?", + "Parce qu'ils travaillent en un éclair."], + ["Que dit un fantôme quand il est ennuyé?", + "Je suis dans de beaux draps!"], + ["Comment peut-on arrêter un éléphant qui veut passer dans le chas d'une aiguille ?", + "On fait un nœud à sa queue."], + ["Pourquoi est-ce que les pompiers ont des bretelles rouges?", + "Pour tenir leurs pantalons!"], + ["Que prend un éléphant lorsqu'il rentre dans un bar ?", + "De la place!"], + ["Savez-vous que votre chien aboie toute la nuit ?", + "Ça ne fait rien, il dort toute la journée!"], + ["Savez-vous que le vétérinaire a épousé la manucure ?", + "Au bout d'un mois ils se battaient becs et ongles."], + ["Tu sais que nous sommes sur terre pour travailler ?", + "Bon, alors plus tard je serai marin."], + ["Quand je dis \"il pleuvait\", de quel temps s'agit-il?", + "D'un sale temps."], + ["À quoi reconnaît-on un motard heureux?", + "Aux moustiques collés sur ses dents."], + ["Son succès lui est monté à la tête.", + "C'est normal, c'est là qu'il y avait le plus de place libre."], + ["Qu'est-ce qui est gris, pousse de petits cris et fait 5 kilos?", + "Une souris qui a besoin de se mettre au régime."], + ["Que dit-on à un croque-mort qui rentre dans un café?", + "\"Je vous sers une bière ?\""], + ["Connais-tu l'histoire du lit vertical?", + "C'est une histoire à dormir debout."], + ["Pourquoi est-ce que les éléphants sont gros et gris?", + "Parce que s'ils étaient petits et jaunes ce seraient des canaris."], + ["Combien coûte cet aspirateur ?", + "750 et des poussières."], + ["Quel est le comble pour un juge gourmand?", + "De manger des avocats."], + ["Pourquoi"+ Donald + " regarde-t-il à droite et à gauche lorsqu'il rentre dans une pièce ?", + "Parce qu'il ne peut pas regarder des deux côtés à la fois."], + ["Pourquoi est-ce que"+ Goofy + " emmène son peigne chez le dentiste ?", + "Parce qu'il a perdu toutes ses dents."], + ["Quel bruit fait la fourmi?", + "La fourmi cro-onde."], + ["Si les sorties étaient surveillées, comment le voleur a-t-il pu s'échapper ?", + "Par l'entrée!"], + ["Que dit un haut-parleur à un autre haut-parleur ?", + "Tu veux une baffle ?"], + ["Pourquoi les lézards aiment-ils les vieux murs?", + "Parce qu'ils ont des lézardes."], + ["Pourquoi est-ce que les moutons ont des pelages en laine ?", + "Parce qu'ils auraient l'air idiots avec des pelages en synthétique."], + ["Où trouve-t-on le dimanche avant le jeudi?", + "Dans le dictionnaire."], + ["Pourquoi est-ce que"+ Pluto + " a dormi avec une peau de banane ?", + "Pour pouvoir se glisser hors de son lit le lendemain matin."], + ["Pourquoi est-ce que la souris portait des chaussons noirs?", + "Parce que les blancs étaient à la lessive."], + ["Quel est le point commun entre les fausses dents et les étoiles?", + "Elles sortent la nuit."], + ["Pourquoi est-ce que les chats aiment se faire photographier ?", + "Parce qu'on leur dit \"souris!\"."], + ["Pourquoi est-ce que l'archéologue a fait faillite ?", + "Parce que sa carrière était en ruine."], + ["Qui boit l'eau sans jamais l'avaler ?", + "L'éponge."], + ["Quelle est la couleur du virus de la grippe ?", + "Gris pâle."], + ["Pourquoi faut-il craindre le soleil?", + "Parce que c'est le plus grand des astres."], + ["Quel est le comble d'un avion ?", + "C'est d'avoir un antivol."], + ["Que dit la nappe à la table ?", + "Ne crains rien, je te couvre."], + ["Que fait"+ Goofy + " quand il tombe dans l'eau?", + "PLOUF!"], + ["Quel est le comble pour un crayon ?", + "Se tailler pour avoir bonne mine."], + ["Que dit la grosse cheminée à la petite cheminée ?", + "Tu es trop jeune pour fumer."], + ["Que dit le tapis au carrelage ?", + "Ne t'inquiète pas, je te couvre."], + ["Quelle est la différence entre le cancre et le premier de la classe ?", + "Quand le cancre redouble, c'est rarement d'attention."], + ["Qu'est-ce qui fait zzzb zzzb?", + "Une guêpe qui vole à l'envers."], + ["Comment appelle-t-on quelqu'un qui tue son beau-frère ?", + "Un insecticide, car il tue l'époux de sa sœur."], + ["Comment appelle-t-on un dinosaure qui n'est jamais en retard?", + "Un promptosaure."], + ["On ne devrait pas dire \"un chapitre\".", + "On devrait dire \"un chat rigolo\"."], + ["On ne devrait pas dire \"un perroquet\".", + "On devrait dire \"mon papa est d'accord\"."], + ["On ne devrait pas dire \"bosser à la chaîne\".", + "On devrait dire \"travailler à la télé\"."], + ["Pourquoi est-ce que le livre de maths était malheureux?", + "Parce qu'il avait trop de problèmes."], + ["On ne devrait pas dire \"un match interminable\".", + "On devrait dire \"une rencontre de mauvais joueurs\"."], + ["On ne devrait pas dire \"la maîtresse d'école\".", + "On devrait dire \"l'institutrice prend l'avion\"."], + ["Que voit-on quand deux mille-pattes se serrent la main ?", + "Une fermeture-éclair."], + ["Comment appelle-t-on un journal publié au Sahara?", + "Un hebdromadaire."], + ["Que doit planter un agriculteur frileux?", + "Un champ d'ail."], + ["Quel est le comble du chauve ?", + "Avoir un cheveu sur la langue."], + ["Qu'est-ce que tu trouves si tu croises un éléphant avec un corbeau?", + "Des tas de poteaux téléphoniques cassés."], + ["Combien gagne un fakir ?", + "Des clous!"], + ["Quelle est la meilleure manière d'économiser l'eau?", + "La diluer."], + ["Quelle différence y a-t-il entre un horloger et une girouette ?", + "L'horloger vend des montres et la girouette montre le vent."], + ["Pourquoi est-ce que les ordinateurs se grattent ?", + "Parce qu'ils sont pleins de puces."], + ["Qu'est-ce qui a un chapeau et pas de tête, un pied mais pas de souliers?", + "Un champignon."], + ["Pourquoi est-ce que le ciel est haut ?", + "Pour éviter que les oiseaux ne se cognent la tête en volant."], + ["Qu'est ce qui est pire qu'une girafe qui a mal à la gorge ?", + "Un mille-pattes avec des cors aux pieds."], + ["Qu'est-ce qui fait ABC...gloups...DEF...gloups?", + "Quelqu'un qui mange de la soupe aux pâtes alphabet."], + ["Qu'est-ce qui est blanc et qui va vite ?", + "Un frigo de course."], + ["Quel est le fruit que les poissons n'aiment pas?", + "La pêche!"], + ["Comment font les éléphants pour traverser un étang?", + "Ils sautent de nénuphar en nénuphar."], + ["Qu'est-ce qui est noir et blanc à pois rouges?", + "Un Dalmatien qui a la rougeole."], + ["Qu'est-ce qu'un chalumeau?", + "Un drolumadaire à 2 bosses."], + ["Pourquoi les éléphants sont-ils gris?", + "Pour ne pas les confondre avec les fraises."], + ["Qu'est-ce qui est gris, fait 100 kilos et appelle \"Minou, Minou!\"?", + "Une souris de 100 kilos."], + ["Quel est le point commun entre un pâtissier et un ciel orageux?", + "Tous les deux font des éclairs."], + ["Quel bruit font les esquimaux lorsqu'ils boivent ?", + "Iglou, iglou, iglou"], + ["Comment appelle-t-on une chauve-souris avec une perruque ?", + "Une souris."], + ["Pourquoi les aiguilles sont-elles moins intelligentes que les épingles?", + "Parce qu'elles n'ont pas de tête."], + ["Qu'est-ce qui a de la fourrure, miaule et chasse les souris sous l'eau?", + "Un poisson-chat."], + ["Comment fait-on aboyer un chat ?", + "Si on lui donne une tasse de lait il la boit."], + ["Qu'est-ce qui est vert à l'extérieur et jaune à l'intérieur ?", + "Une banane déguisée en concombre."], + ["Qu'est-ce qu'un ingrat ?", + "Le contraire d'un géant maigre."], + ["Qu'est-ce qui pèse 4 tonnes, a une trompe et est rouge vif?", + "Un éléphant qui a honte."], + ["Dans un virage à 60 degrés à droite, quelle est la roue qui tourne le moins vite ?", + "La roue de secours."], + ["Comment reconnaît-on un idiot dans un magasin de chaussures?", + "C'est celui qui essaie les boîtes."], + ["Que dit-on d'un enfant qui ramène le pain à la maison ?", + "C'est le petit calepin."], + ["Que dit la cacahuète à l'éléphant ?", + "Rien, les cacahuètes ne parlent pas."], + ["Que dit un éléphant lorsqu'il se heurte à un autre éléphant ?", + "Le monde est petit, n'est-ce pas?"], + ["Que dit la comptable à la machine à calculer ?", + "Je compte sur toi."], + ["Que dit la puce à une autre puce ?", + "On y va à pied ou on prend le chat ?"], + ["Que dit la grande aiguille à la petite aiguille ?", + "Attends une minute."], + ["Que dit une poule quand elle rencontre une autre poule ?", + "Tu viens, on va prendre un ver ?"], + ["Que dit le collant à la chaussure ?", + "À plus tard, je dois filer."], + ["Papa kangourou demande à sa fille qui rentre de l'école: \"Alors, cet examen ?\"", + "\"C'est dans la poche, pas de problème!\""], + ["Quelle est la ville de France la plus féroce ?", + "Lyon."], + ["Quelle est la ville de France la moins légère ?", + "Lourdes."], + ["Pourquoi porte-t-on des vêtements?", + "Parce qu'ils ne peuvent pas marcher tout seuls."], + ["Que dit une pomme de terre quand elle en voit une autre se faire écraser dans la rue ?", + "\"Oh, purée!\""], + ["Que dit un petit fakir quand il arrive en retard à l'école ?", + "\"Pardon maîtresse, je me suis endormi sur le passage clouté!\""], + ["Que dit un marin-pêcheur s'il se dispute avec un autre marin-pêcheur ?", + "Je ne veux pas que tu me parles sur ce thon!"], + ["Pourquoi les cultivateurs disent-ils des gros mots à leurs tomates?", + "Pour les faire rougir."], + ["Que disent deux vers de terre s'ils se rencontrent au milieu d'une pomme ?", + "\"Vous habitez dans le quartier ?\""], + ["Qu'est-ce que se disent deux serpents qui se rencontrent ?", + "\"Quelle heure reptile ?\""], + ["Pourquoi les mille-pattes ne peuvent-ils pas jouer au hockey?", + "Le temps d'enfiler leurs patins, la partie est déjà terminée!"], + ["Comment fait-on cuire un poisson dans un piano?", + "On fait Do, Ré, La, Sol."], + ["Connaissez-vous l'histoire du chauffeur d'autobus?", + "Moi non plus, j'étais à l'arrière!"], + ["Crois-tu aux girafes?", + "Non, c'est un cou monté."], + ["Que dit un crocodile s'il rencontre un chien ?", + "Salut, sac à puces!"], + ["Que dit un chien quand il rencontre un crocodile ?", + "Salut, sac à main!"], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","hou","ha","rhaa") +MovieHealLaughterHits1= ("Ha ha ha","Hi hi","Hé hé","Ha ha") +MovieHealLaughterHits2= ("OUARF OUARF OUARF!","HO HO HO!","HA HA HA!") + +# MovieSOS.py +MovieSOSCallHelp = "%s À L'AIDE!" +MovieSOSWhisperHelp = "%s a besoin d'aide pour un combat!" +MovieSOSObserverHelp = "À L'AIDE!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Salut, %s! C'est un plaisir de pouvoir t'aider!" +MovieNPCSOSGoodbye = "À plus tard!" +MovieNPCSOSToonsHit = "Les Toons font toujours mouche!" +MovieNPCSOSCogsMiss = "Les Cogs ratent toujours leurs cibles!" +MovieNPCSOSRestockGags = "En train de faire le plein de gags %s!" +MovieNPCSOSHeal = "Guérison" +MovieNPCSOSTrap = "Piégeage" +MovieNPCSOSLure = "Leurre" +MovieNPCSOSSound = "Tapage" +MovieNPCSOSThrow = "Lancer" +MovieNPCSOSSquirt = "Éclaboussure" +MovieNPCSOSDrop = "Chute" +MovieNPCSOSAll = "Tout" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Soupir" +MoviePetSOSTrickSucceedBoy = "Bon garçon!" +MoviePetSOSTrickSucceedGirl = "Brave fifille!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "ANNULÉ\nANNULÉ\nANNULÉ" + +# RewardPanel.py +RewardPanelToonTasks = "Défitoons" +RewardPanelItems = "Objets récupérés" +RewardPanelMissedItems = "Objets non récupérés" +RewardPanelQuestLabel = "Quête %s" +RewardPanelCongratsStrings = ["Ouais!", "Bravo!", "Ouah!", + "Sympa!", "Atmosphérique!", "Toontastique!"] +RewardPanelNewGag = "Nouveau gag %(gagName)s pour %(avName)s!" +RewardPanelUberGag = "%(avName)s earned the %(gagName)s gag with %(exp)s experience points!" +RewardPanelEndTrack = "Haa! %(avName)s a atteint la fin de la série de gags %(gagName)s!" +RewardPanelMeritsMaxed = "Au maximum" +RewardPanelMeritBarLabels = [ "Avis de licenciement", "Citations à comparaître", "Euros Cog", "Mérites" ] +RewardPanelMeritAlert = "Prêt pour la promotion!" + +RewardPanelCogPart = "Tu as gagné un morceau de déguisement de Cog!" +RewardPanelPromotion = "Préparez pour la promotion %s voie!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Toon normal", "tu seras normal(e)"), + ("Grosse tête", "tu auras une grosse tête"), + ("Petite tête", "tu auras une petite tête"), + ("Grosses jambes", "tu auras de grosses jambes"), + ("Petites jambes", "tu auras de petites jambes"), + ("Gros Toon", "tu seras un peu plus gros(se)"), + ("Petit Toon", "tu seras un peu plus petit(e)"), + ("À plat", "tu seras en deux dimensions"), + ("Profil plat", "tu seras en deux dimensions"), + ("Transparent", "tu seras transparent(e)"), + ("Sans couleur", "tu seras incolore"), + ("Toon invisible", "tu seras invisible"), + ] +CheesyEffectIndefinite = "Jusqu'à ce que tu choisisses un autre effet, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Pendant les %(time)s prochaines minutes, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Pendant les %(time)s prochaines heures, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Pendant les %(time)s prochains jours, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " pendant que tu es dans %s" +CheesyEffectExceptIn = ", excepté dans %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Laquaistic" +SuitPencilPusher = "Gratte-\npapier" +SuitYesman = "Béniouioui" +SuitMicromanager = "Micro\3chef" +SuitDownsizer = "Touptisseur" +SuitHeadHunter = "Chassetête" +SuitCorporateRaider = "Attactic" +SuitTheBigCheese = "Gros Blochon" +SuitColdCaller = "Cassepied" +SuitTelemarketer = "Télé\3vendeur" +SuitNameDropper = "Cafteur" +SuitGladHander = "Passetout" +SuitMoverShaker = "Secousse-\ncousse" +SuitTwoFace = "Biface" +SuitTheMingler = "Le Circulateur" +SuitMrHollywood = "M. Hollywood" +SuitShortChange = "Gardoseille" +SuitPennyPincher = "Radino" +SuitTightwad = "Grippesou" +SuitBeanCounter = "Pince Menu" +SuitNumberCruncher = "Gobechiffre" +SuitMoneyBags = "Sacasous" +SuitLoanShark = "Usurier" +SuitRobberBaron = "Pillard" +SuitBottomFeeder = "Volebas" +SuitBloodsucker = "Pique-\nau-sang" +SuitDoubleTalker = "Charabieur" +SuitAmbulanceChaser = "Charognard" +SuitBackStabber = "Frappedos" +SuitSpinDoctor = "Tournegris" +SuitLegalEagle = "Avocageot" +SuitBigWig = "Chouffleur" + +# Singular versions (indefinite article) +SuitFlunkyS = "un Laquaistic" +SuitPencilPusherS = "un Gratte-Papier" +SuitYesmanS = "un Béniouioui" +SuitMicromanagerS = "un Microchef" +SuitDownsizerS = "un Touptisseur" +SuitHeadHunterS = "un Chassetête" +SuitCorporateRaiderS = "un Attactic" +SuitTheBigCheeseS = "un Gros Blochon" +SuitColdCallerS = "un Cassepied" +SuitTelemarketerS = "un Télévendeur" +SuitNameDropperS = "un Cafteur" +SuitGladHanderS = "un Passetout" +SuitMoverShakerS = "un Secousse-cousse" +SuitTwoFaceS = "un Biface" +SuitTheMinglerS = "un Circulateur" +SuitMrHollywoodS = "un M. Hollywood" +SuitShortChangeS = "un Gardoseille" +SuitPennyPincherS = "un Radino" +SuitTightwadS = "un Grippesou" +SuitBeanCounterS = "un Pince-Menu" +SuitNumberCruncherS = "un Gobechiffre" +SuitMoneyBagsS = "un Sacasous" +SuitLoanSharkS = "un Usurier" +SuitRobberBaronS = "un Pillard" +SuitBottomFeederS = "un Volebas" +SuitBloodsuckerS = "un Pique-au-sang" +SuitDoubleTalkerS = "un Charabieur" +SuitAmbulanceChaserS = "un Charognard" +SuitBackStabberS = "un Frappedos" +SuitSpinDoctorS = "un Tournegris" +SuitLegalEagleS = "un Avocageot" +SuitBigWigS = "un Chouffleur" + +# Plural versions +SuitFlunkyP = "Laquaistics" +SuitPencilPusherP = "Gratte-Papiers" +SuitYesmanP = "Béniouiouis" +SuitMicromanagerP = "Microchefs" +SuitDownsizerP = "Touptisseurs" +SuitHeadHunterP = "Chassetêtes" +SuitCorporateRaiderP = "Attactics" +SuitTheBigCheeseP = "Gros Blochons" +SuitColdCallerP = "Cassepieds" +SuitTelemarketerP = "Télévendeurs" +SuitNameDropperP = "Cafteurs" +SuitGladHanderP = "Passetouts" +SuitMoverShakerP = "Secousse-cousses" +SuitTwoFaceP = "Bifaces" +SuitTheMinglerP = "Les Circulateurs" +SuitMrHollywoodP = "MM. Hollywood" +SuitShortChangeP = "Gardoseilles" +SuitPennyPincherP = "Radinos" +SuitTightwadP = "Grippesous" +SuitBeanCounterP = "Pince-Menus" +SuitNumberCruncherP = "Gobechiffres" +SuitMoneyBagsP = "Sacasous" +SuitLoanSharkP = "Usuriers" +SuitRobberBaronP = "Pillards" +SuitBottomFeederP = "Volebas" +SuitBloodsuckerP = "Pique-au-sang" +SuitDoubleTalkerP = "Charabieurs" +SuitAmbulanceChaserP = "Charognards" +SuitBackStabberP = "Frappedos" +SuitSpinDoctorP = "Tournegris" +SuitLegalEagleP = "Avocageots" +SuitBigWigP = "Chouffleurs" + +SuitFaceOffDefaultTaunts = ['Bouh!'] + +SuitAttackDefaultTaunts = ['Prends ça!', 'Garde des notes là-dessus!'] + +SuitAttackNames = { + 'Audit' : 'Audit!', + 'Bite' : 'Morsure!', + 'BounceCheck' : 'Chèque refusé!', + 'BrainStorm' : 'Remue-méninges!', + 'BuzzWord' : 'Mot à la mode!', + 'Calculate' : 'Évaluation!', + 'Canned' : 'En conserve!', + 'Chomp' : 'Mastication!', + 'CigarSmoke' : 'Fumée de cigare!', + 'ClipOnTie' : 'Cravate toute faite!', + 'Crunch' : 'Écrasement!', + 'Demotion' : 'Rétrogradation!', + 'Downsize' : 'Rapetissement!', + 'DoubleTalk' : 'Charabia!', + 'EvictionNotice' : "Ordre d'expulsion!", + 'EvilEye' : 'Mauvais œil!', + 'Filibuster' : 'Obstruction!', + 'FillWithLead' : 'Plombage!', + 'FiveOClockShadow' : "Barbe naissante!", + 'FingerWag' : 'Montré du doigt!', + 'Fired' : 'Liquidé!', + 'FloodTheMarket' : 'Invasion du marché!', + 'FountainPen' : 'Stylo-plume!', + 'FreezeAssets' : 'Capital gelé!', + 'Gavel' : 'Adjugé!', + 'GlowerPower' : 'Regard furieux!', + 'GuiltTrip' : 'Culpabilisation!', + 'HalfWindsor' : 'Nœud de cravate!', + 'HangUp' : 'Interruption!', + 'HeadShrink' : 'Rétrécissement de la tête!', + 'HotAir' : 'Air chaud!', + 'Jargon' : 'Jargon!', + 'Legalese' : 'Expression juridique!', + 'Liquidate' : 'Liquidation!', + 'MarketCrash' : 'Krach boursier!', + 'MumboJumbo' : 'Baragouinage!', + 'ParadigmShift' : 'Changement radical!', + 'PeckingOrder' : 'Hiérarchie!', + 'PickPocket' : 'Vol à la tire!', + 'PinkSlip' : 'Avis de licenciement!', + 'PlayHardball' : 'Grands moyens!', + 'PoundKey' : 'Touche dièse!', + 'PowerTie' : 'Cravate rayée!', + 'PowerTrip' : 'Mégalomanie!', + 'Quake' : 'Tremblement!', + 'RazzleDazzle' : 'Bringue!', + 'RedTape' : 'Paperasserie!', + 'ReOrg' : 'Réorganisation!', + 'RestrainingOrder' : 'Injonction!', + 'Rolodex' : 'Fichier rotatif!', + 'RubberStamp' : 'Tampon!', + 'RubOut' : 'Effacement!', + 'Sacked' : 'Licenciement!', + 'SandTrap' : 'Ensablement!', + 'Schmooze' : 'Jacasserie!', + 'Shake' : 'Secousse!', + 'Shred' : 'Déchiquetage!', + 'SongAndDance' : 'Couplet habituel!', + 'Spin' : 'Tournoiement!', + 'Synergy' : 'Synergie!', + 'Tabulate' : 'Tabulation!', + 'TeeOff' : 'Fâcherie!', + 'ThrowBook' : 'Maximum!', + 'Tremor' : 'Frémissement!', + 'Watercooler' : 'Boissons fraîches!', + 'Withdrawal' : 'Retrait!', + 'WriteOff' : 'Pertes et profits!', + } + +SuitAttackTaunts = { + 'Audit': ["Je crois que ton bilan n'est pas équilibré.", + "On dirait que tu es dans le rouge.", + "Laisse-moi t'aider à faire ta comptabilité.", + "Tes débits sont beaucoup trop élevés.", + "Vérifions ton capital", + "Tu vas avoir des dettes.", + "Regardons de plus près ce que tu dois.", + "Cela devrait mettre ton compte à sec.", + "Il est temps que tu comptabilises tes dépenses.", + "J'ai trouvé une erreur dans ton bilan.", + ], + 'Bite': ["Tu en veux une bouchée ?", + "Essaye d'en mordre un morceau!", + "Tu as les yeux plus gros que le ventre.", + "Je mords plus que je n'aboie.", + "Avale donc ça!", + "Attention, je pourrais mordre.", + "Je ne fais pas que mordre quand je suis coincé.", + "J'en veux juste une petite bouchée.", + "Je n'ai rien avalé de la journée.", + "Je ne veux qu'un petit morceau. C'est trop demander ?", + ], + 'BounceCheck': ["Dommage, tu n'as pas d'humour.", + "Tu as une échéance de retard.", + "Je crois que ce chèque est à toi.", + "Tu m'es redevable.", + "Je recouvre cette créance.", + "Ce chèque ne va pas être un cadeau.", + "Tu vas être facturé pour ça.", + "Vérifie ce chèque.", + "Ça va te coûter cher.", + "J'aimerais bien encaisser ça.", + "Je vais simplement te renvoyer ton chèque.", + "Voilà une facture salée.", + "Je déduis des frais de service.", + ], + 'BrainStorm':["Je prévois des perturbations.", + "J'adore les casse-tête.", + "Je voudrais t'éclairer.", + "Qu'est-ce que tu penserais de la CHUTE de tes facultés?", + "Que de médiocrité!", + "Tu es prêt(e) pour le grand déménagement ?", + "J'ai les neurones en feu.", + "Ça casse des briques.", + "Rien de tel qu'un remue-méninges.", + ], + 'BuzzWord':["Excuse-moi si je radote.", + "Tu connais la dernière ?", + "Tu peux piger ça?", + "Toonicoton!", + "Laisse-moi en placer une.", + "Je serai incontournablement clair.", + "Tu as dit un mot de trop.", + "Voyons si tu te situes en transversalité.", + "Fais attention, ça va être ringard.", + "Je crois que tu vas faire de l'urticaire.", + ], + 'Calculate': ["Le compte est bon!", + "Tu comptais là-dessus?", + "Ajoutes-en un peu, tu es en train de diminuer.", + "Je peux t'aider à faire cette addition ?", + "Tu as bien enregistré toutes tes dépenses?", + "D'après mes calculs, tu n'en as plus pour longtemps.", + "Voilà le total général.", + "Houlà, ton addition est bien longue.", + "Essaie de trafiquer ces chiffres!", + Cogs + " : 1 Toons: 0", + ], + 'Canned': ["Tu aimes quand c'est en boîte ?", + "Tu peux t'occuper des boîtes?", + "Celui-là vient de sortir de sa boîte!", + "Tu as déjà été attaqué par des boîtes de conserve ?", + "J'aimerais te faire un cadeau qui se conserve!", + "Tu es prêt pour la mise en boîte ?", + "Tu crois que tu es bien conservé?", + "Tu vas être emballé!", + "Je me fais du Toon à l'huile pour dîner!", + "Tu n'es pas si mangeable que ça en conserve.", + ], + 'Chomp': ["Tu as une mine de papier mâché!", + "Croc, croc, croc!", + "On va pouvoir se mettre quelque chose sous la dent.", + "Tu as besoin de grignoter quelque chose ?", + "Tu pourrais grignoter ça!", + "Je vais te manger pour le dîner.", + "Je me nourrirais bien de Toons!", + ], + 'ClipOnTie': ["Il faut s'habiller pour la réunion.", + "Tu ne peux PAS sortir sans ta cravate.", + "C'est ce que portent les " + Cogs + " les plus élégants.", + "Essaie pour voir si la taille te va.", + "Tu devrais mieux t'habiller pour réussir.", + "On ne sert que les clients portant une cravate.", + "Tu as besoin d'aide pour enfiler ça?", + "Rien n'est plus flatteur qu'une belle cravate.", + "Voyons si ça te va.", + "Ça va te bouleverser.", + "Il va falloir que tu t'habilles avant de SORTIR.", + "Je crois que je vais te faire un nœud de cravate.", + ], + 'Crunch': ["On dirait que tu es écrasé(e) par les événements.", + "C'est l'heure d'en écraser!", + "Je vais te donner quelque chose à pulvériser.", + "Je vais broyer tout ça.", + "J'ai tout écrasé.", + "Tu préfères tendre ou croquant ?", + "J'espère que tu aimes les croque-monsieur.", + "On dirait que tu es en train de te faire écraser!", + "Je vais te réduire en miettes." + ], + 'Demotion': ["Tu descends sur l'échelle de la hiérarchie.", + "Je te renvoie à trier le courrier.", + "Il est temps de rendre tes galons.", + "Tu descends, petit clown!", + "On dirait qu'il y a un blocage.", + "Tu progresses lentement.", + "Tu es dans une voie sans issue.", + "Tu n'iras nulle part dans l'immédiat.", + "Tu ne vas nulle part.", + "Cela sera porté sur ta fiche d'assiduité.", + ], + 'Downsize': ["Redescends donc de là!", + "Tu sais comment redescendre ?", + "Revenons à nos affaires.", + "Qu'est-ce qui ne va pas? Tu as l'air d'avoir le moral dans les chaussettes.", + "Tu descends?", + "Qu'est-ce qui te chiffonne ? Toi!", + "Pourquoi est-ce que tu choisis des gens de ma taille ?", + "Pourquoi es-tu si terre-à-terre ?", + "Est-ce que tu voudrais un modèle plus petit pour seulement dix cents de plus?", + "Essaie pour voir si la taille te va!", + "Ce modèle est disponible dans une plus petite taille.", + "C'est une attaque à taille unique!", + ], + # Hmmm - where is double talker ? + 'EvictionNotice': ["C'est l'heure de partir!", + "Fais tes bagages, Toon.", + "C'est le moment d'aller habiter ailleurs.", + "Disons que ton bail est terminé.", + "Tu as un loyer de retard.", + "Cela va être très déstabilisant.", + "Tu vas être déraciné d'ici peu.", + "Je vais t'envoyer sous les ponts.", + "Tu n'es pas à ta place.", + "Prépare-toi à une délocalisation.", + "Tu vas subir un placement d'office.", + ], + 'EvilEye': ["Je te donne le mauvais œil.", + "Tu peux donner un coup d'œil à ça pour moi?", + "Attends. J'ai quelque chose dans l'œil.", + "J'ai l'œil sur toi!", + "Tu pourrais garder un œil sur ça?", + "J'ai vraiment l'œil pour voir ce qui cloche.", + "Je vais te taper dans l'œil.", + "J'ai le regard méchant!", + "Tu vas te retrouver dans l'œil du cyclone!", + "Je te regarde en roulant des yeux.", + ], + 'Filibuster':["Est-ce que je dois te barrer la route ?", + "Ça va nous bloquer pendant un moment.", + "Je pourrais rester coincé là toute la journée.", + "Je n'ai même pas besoin de respirer.", + "J'avance et j'avance et j'avance.", + "Je ne m'en fatigue jamais.", + "On ne peut pas m'arrêter de parler.", + "Tu peux te boucher les oreilles?", + "Je crois que je vais te tenir la jambe.", + "Je finis toujours par placer un mot.", + ], + 'FingerWag': ["Ça fait mille fois que je te le répète!", + "Regarde bien là, Toon.", + "Ne me fais pas rire.", + "Ne m'oblige pas à y aller.", + "J'en ai assez de répéter la même chose.", + "Je crois qu'on en a déjà parlé.", + "Tu n'as aucun respect pour nous les" + Cogs + ".", + "Il est grand temps de faire attention.", + "Blablablablabla.", + "Ne m'oblige pas à mettre fin à cette réunion.", + "Est-ce que je vais devoir te séparer ?", + "On est déjà passés par là.", + ], + 'Fired': ["J'espère que tu as apporté des rafraîchissements.", + "On s'embête solide.", + "Ça va nous rafraîchir.", + "J'espère que tu as le sang froid.", + "J'ai la gorge sèche.", + "Va donc nager un peu!", + "Tu es déjà sur le départ.", + "Encore un peu de sauce ?", + "Tu peux dire \"aïe\"?", + "J'espère que tu sais nager.", + "Tu es en phase de déshydratation ?", + "Je vais te liquider!", + "Tu vas finir en bouillie.", + "Tu n'es qu'un feu de paille.", + "Je me trouve fondant.", + "Je suis d'une limpidité!", + "Et on n'en parle plus !", + "Un Toon à la mer !", + ], + 'FountainPen': ["Ça va tacher.", + "Mettons ça par écrit.", + "Prépare-toi à des ennuis indélébiles.", + "Tu vas avoir besoin d'un bon nettoyage à sec.", + "Tu devrais corriger.", + "Ce stylo écrit si bien.", + "Voilà, je prends mon crayon.", + "Tu peux lire mon écriture ?", + "Et voilà la plume de l'apocalypse.", + "Ta performance est entachée.", + "Tu n'as pas envie de tout effacer ?", + ], + 'FreezeAssets': ["Ton capital est le mien.", + "Tu ne sens pas un appel de fonds?", + "J'espère que tu n'as pas de projets.", + "Cela devrait te mettre sur la paille.", + "Le fond de l'air est frais.", + "L'hiver va venir tôt cette année.", + "Tu as froid?", + "Je vais geler mes projets.", + "Tu vas trouver ça froid.", + "Tu vas avoir des engelures.", + "J'espère que tu aimes la viande froide.", + "Je garde mon sang-froid.", + ], + 'GlowerPower': ["Tu me regardes?", + "On me dit que j'ai une vue perçante.", + "J'aime bien que tu sois à portée de mon regard.", + "Tu n'aimes pas que je te regarde ?", + "Voilà, je te regarde.", + "Tu ne trouves pas que j'ai un regard expressif?", + "Mon regard est mon point fort.", + "C'est le regard qui compte.", + "Coucou, je te vois.", + "Regarde-moi dans les yeux...", + "Est-ce que tu voudrais voir ton avenir ?", + ], + 'GuiltTrip': ["Tu vas vraiment te sentir coupable!", + "Tu te sens coupable!", + "C'est entièrement de ta faute!", + "C'est toujours ta faute.", + "Tu te complais dans la culpabilité!", + "Je ne te reparlerai plus jamais!", + "Tu ferais mieux de t'excuser.", + "Jamais je ne te pardonnerai!", + "Tu veux bien te faire de la bile ?", + "Rappelle-moi quand tu ne te sentiras plus coupable.", + "Quand finiras-tu par te pardonner à toi-même ?", + ], + 'HalfWindsor': ["Tu ne t'es encore jamais fait cravater comme ça!", + "Essaye de ne pas trop faire de nœuds.", + "Tu es dans une situation inextricable.", + "Tu as de la chance, j'aurais pu faire un nœud plus serré.", + "Cette cravate est trop chère pour toi.", + "Je crois que tu n'as jamais même VU de nœud de cravate!", + "Cette cravate est trop chère pour toi.", + "Cette cravate serait gâchée, sur toi.", + "Tu ne vaux même pas la moitié du prix de cette cravate!", + ], + 'HangUp': ["Tu as été déconnecté(e).", + "Au revoir!", + "C'est l'heure de mettre fin à notre conversation.", + "...et ne me rappelle pas!", + "Clic!", + "La conversation est terminée.", + "Je vais couper cette ligne.", + "Je crois que nous allons être coupés.", + "On dirait que la ligne est défectueuse.", + "Ton forfait est terminé.", + "J'espère que tu m'entends clairement.", + "Tu as fait le mauvais numéro.", + ], + 'HeadShrink': ["On dirait que tu as besoin de te faire soigner la tête.", + "Chérie, j'ai rétréci le Toon.", + "J'espère que ça ne t'est pas monté à la tête.", + "Tu rétrécis au lavage ?", + "Je rétrécis donc je suis.", + "Il n'y a pas de quoi perdre la tête.", + "Où as-tu la tête ?", + "Relève la tête! Ou plutôt, mets-la par terre.", + "Les choses sont parfois plus grandes qu'elles ne paraissent.", + "Les bons Toons se vendent par petits paquets.", + ], + 'HotAir':["Nous avons de chaudes discussions.", + "Tu subis une vague de chaleur.", + "J'ai atteint mon point d'ébullition.", + "Cela pourrait te brûler.", + "Je détesterais te passer au gril, mais...", + "N'oublie pas qu'il n'y a pas de fumée sans feu.", + "Tu m'as l'air un peu grillé(e).", + "C'est encore un écran de fumée.", + "C'est le moment de mettre de l'huile sur le feu.", + "Allumons le feu de l'amitié.", + "J'ai des remarques brûlantes à te faire.", + "Air chaud!", + ], + 'Jargon':["Quel non-sens.", + "Regarde si tu peux trouver du sens à tout ça.", + "J'espère que tu m'entends clairement.", + "On dirait que je vais devoir élever la voix.", + "J'ai vraiment mon mot à dire.", + "J'ai mon franc-parler.", + "Je vais pontifier sur ce sujet.", + "Tu sais, les mots peuvent faire mal.", + "Tu as compris ce que je voulais dire ?", + "Des mots, rien que des mots.", + ], + 'Legalese':["Tu dois cesser d'être et renoncer.", + "Tu vas être débouté(e), légalement parlant.", + "Tu es au courant des implications légales?", + "Tu n'es pas au-dessus des lois!", + "Il devrait y avoir une loi contre toi.", + "Il n'y a rien de postérieur aux faits!", + "Toontown Online de Disney n'est pas légalement responsable des opinions exprimées dans cette attaque.", + "Nous ne serons pas tenus responsables des dommages subis suite à cette attaque.", + "Les résultats de cette attaque peuvent différer.", + "Cette attaque est nulle là où elle n'est pas autorisée.", + "Tu ne rentres pas dans mon système législatif.", + "Tu ne peux pas gérer les questions juridiques.", + ], + 'Liquidate':["J'aime bien que les choses restent fluides.", + "As-tu des problèmes de liquidités?", + "Je dois purger ton capital.", + "Il est temps pour toi de suivre le flux monétaire.", + "N'oublie pas que ça glisse quand c'est mouillé.", + "Il y a des fuites dans ta comptabilité.", + "Tu as l'air de perdre pied.", + "Tout te tombe dessus.", + "Je crois que tu vas subir une dilution.", + "Tu es lessivé(e).", + ], + 'MarketCrash':["Tu vas avoir un choc.", + "Tu ne survivras pas au choc.", + "C'est plus que la bourse ne peut en supporter.", + "J'ai un traitement de choc pour toi!", + "Maintenant je vais te faire un choc.", + "Je m'attends à un choc boursier.", + "On dirait que le marché est sur la pente descendante.", + "Il vaudrait mieux que tu te retires du jeu!", + "Vends! Vends! Vends!", + "Est-ce que je dois mener la récession ?", + "Tout le monde s'enfuit, tu devrais peut-être en faire autant ?", + ], + 'MumboJumbo':["Que ce soit parfaitement clair.", + "C'est aussi simple que ça.", + "C'est comme cela que nous allons procéder.", + "Laisse-moi te l'écrire en grosses lettres.", + "C'est du jargon technique.", + "Ma parole est d'argent.", + "J'en ai plein la bouche.", + "On dit que je suis grandiloquent.", + "Je vais interjeter ça.", + "Je crois que ce sont les mots adéquats.", + ], + 'ParadigmShift':["Fais attention! Je suis plutôt changeant.", + "Prépare-toi pour un changement radical!", + "Voilà donc des substitutions intéressantes.", + "Tu n'es pas à ta place.", + "C'est ton tour de changer de place.", + "Ton temps de présence est terminé.", + "Tu n'as encore jamais autant changé dans ta vie.", + "Voilà qui est radical!", + "La lumière est changeante!", + ], + 'PeckingOrder':["Pauvre sous-fifre!", + "Tu vas te retrouver le bec dans l'eau.", + "Tu vas te retrouver en bas de l'échelle.", + "Ce n'est pas une attaque de débutant.", + "Tu es tout en bas de la hiérarchie.", + "Je vaux bien plus cher que toi!", + "La hiérarchie, il n'y a que ça de vrai!", + "Pourquoi est-ce que je ne trouve pas d'adversaire à ma taille ? Bof.", + "À moi le pouvoir!", + ], + 'PickPocket': ["Laisse-moi vérifier tes valeurs.", + "Eh, c'est quoi par ici?", + "C'est comme faucher les jouets d'un enfant.", + "C'est du vol.", + "Je te garde ça.", + "Ne lâche pas mes mains des yeux.", + "Mes mains sont plus rapides que tes yeux.", + "Je n'ai rien dans la manche.", + "La direction n'est pas responsable des objets perdus.", + "Qui trouve garde.", + "Tu ne le verras jamais revenir.", + "Tout pour moi, rien pour toi.", + "Ça ne te gêne pas que ça me gêne ?", + "Tu n'en auras plus besoin...", + ], + 'PinkSlip': ["On n'a pas besoin de ton avis.", + "Tu as peur de cette vague de licenciements?.", + "Celui-là va sûrement être d'un avis contraire.", + "Oh, tu as une licence de quoi?", + "Fais attention, si tu veux mon avis!", + "N'oublie pas que ça glisse à mon avis.", + "Je vais juste te renvoyer celui-là.", + "Tu ne te fâcheras pas si je te donne mon avis?", + "Tu ne vois pas l'avis en rose.", + "Tu peux sortir, je te licencie.", + ], + 'PlayHardball': ["Tu veux employer les grands moyens?", + "N'essaie pas d'employer tous les moyens avec moi.", + "Ne prends pas tes grands airs!", + "Tu es vraiment moyen(ne).", + "Et voilà le bon moyen...", + "Tu vas avoir besoin d'un bon moyen pour t'en sortir.", + "Je vais te chasser d'ici à grande vitesse.", + "Une fois que je t'aurai touché(e), tu rentreras en courant chez toi.", + "C'est ton grand départ!", + "Ton jeu est très moyen.", + "Je vais tout faire pour que tu sortes.", + "Je t'envoie promener dans les grandes largeurs!", + ], + 'PoundKey': ["Il est temps que je réponde à quelques appels.", + "J'aimerais faire un appel en PCV.", + "Dring dring, c'est pour toi!", + "Je vais bien toucher quelque chose.", + "Je devais te rappeler.", + "Cela devrait provoquer une sonnerie.", + "Je vais juste faire ce numéro.", + "Je t'appelle pour te faire une surprise.", + "Je vais t'appeler.", + "Allô Toon, c'est pour toi.", + ], + 'PowerTie': ["Je t'appellerai plus tard, tu as l'air d'avoir un nœud à l'estomac.", + "Tu te prépares à faire un trait là-dessus?", + "Tu vas te faire cravater.", + "Tu ferais mieux d'apprendre à faire un nœud de cravate.", + "Je vais te nouer la langue!", + "Tu n'as encore jamais vu quelqu'un se faire cravater comme ça!", + "Tu fais attention aux rayures?", + "Je vais te rayer de la carte!", + "Je rayonne!", + "Par les pouvoirs qui me sont conférés, je te raie de la liste.", + ], + 'PowerTrip': ["Fais tes valises, on fait un méga voyage.", + "Tu n'as pas perdu tes manies?", + "C'est une manie que tu as de partir en vacances.", + "Comment se sont passées les vacances?", + "C'est une vraie manie!", + "Ça a l'air méga ennuyeux.", + "Maintenant tu vois qui est le plus puissant!", + "Je suis bien plus puissant que toi.", + "Qui a les méga pouvoirs maintenant ?", + "Tu ne peux pas te battre contre ma puissance.", + "La puissance est corrompue, en particulier dans mon cas.", + ], + 'Quake': ["Tremblons, mes frères.", + "J'ai la tremblote!", + "Je te vois trembler dans tes chaussures.", + "Voilà la terre qui tremble!", + "Celui-ci est en-dehors de l'échelle de Richter.", + "La terre va trembler!", + "Hé, qu'est-ce qui tremble comme ça? Toi!", + "Tu as déjà ressenti un tremblement de terre ?", + "Tu es sur un terrain instable!", + ], + 'RazzleDazzle': ["Chante avec moi.", + "Tu as peur de perdre ton dentier ?", + "Je ne suis pas charmant ?", + "Je vais t'impressionner.", + "Mon dentiste fait un excellent travail.", + "Ils ne sont pas épatants?", + "Difficile de croire qu'ils ne sont pas réels.", + "Ils ne sont pas choquants?", + "Ça va décoiffer.", + "Je me lave les dents après tous les repas.", + "Dis \"Cheese!\"", + ], + 'RedTape': ["Ça va être bien emballé.", + "Tu vas rester collé(e) là un bon moment.", + "J'en ai un plein rouleau.", + "On va voir si tu peux y couper.", + "Ça va devenir collant.", + "J'espère que tu es claustrophobe.", + "Tu es d'un tempérament collant!", + "Je vais t'occuper un peu.", + "Essaie donc de sortir de là.", + "On va voir si ça colle entre nous.", + ], + 'ReOrg': ["Tu n'aimes pas la manière dont j'ai réorganisé les choses?", + "Peut-être qu'un peu plus d'organisation serait de mise.", + "Tout n'est pas si mauvais, tu as juste un peu besoin de réorganisation.", + "Est-ce que tu apprécies mes capacités d'organisation ?", + "J'essaye juste de donner un nouvel aspect aux choses.", + "Tu dois t'organiser!", + "Tu m'as l'air de faire dans la désorganisation.", + "Reste là pendant que je te réorganise.", + "Je vais attendre que tu aies le temps de t'organiser.", + "Ça ne te dérange pas si je réorganise un peu?", + ], + 'RestrainingOrder': ["Tu devrais faire la jonction.", + "Je t'assène une injonction!", + "Tu n'as pas le droit de t'approcher à moins de deux mètres de moi.", + "Tu ferais peut-être mieux de garder tes distances.", + "Tu devrais avoir une injonction.", + Cogs + "! Maîtrisez ce Toon!" + "Essaie de te maîtriser.", + "J'espère que je ne suis pas trop une contrainte pour toi.", + "Voyons si tu peux te libérer de ces contraintes!", + "Je te donne l'injonction de te maîtriser!", + "Pourquoi ne commençons-nous pas par les contraintes de base ?" + ], + 'Rolodex': ["Ta fiche est quelque part là-dedans.", + "Voilà la fiche de la chasse aux nuisibles.", + "Je vais te donner une fiche.", + "Ton numéro est juste là.", + "Je te couvre de A à Z.", + "Tu vas avoir la tête qui tourne.", + "Va donc faire un tour.", + "Attention aux bouts de papier.", + "J'ai des doigts pour trier.", + "Est-ce que c'est comme ça que je peux te contacter ?", + "Je voudrais être certain que nous allons rester en contact.", + ], + 'RubberStamp': ["Je fais toujours bonne impression.", + "Il est important de bien appuyer.", + "Une impression parfaite à chaque fois.", + "Je voudrais que tu imprimes.", + "Tu dois être RETOURNÉ à L'ENVOYEUR.", + "Tu es dans la pile ANNULÉ.", + "Tu es en livraison PRIORITAIRE.", + "Je voudrais être certain que tu as REÇU mon message!", + "Tu ne vas nulle part - tu es en PORT PAYÉ par le DESTINATAIRE.", + "Je veux une réponse URGENTE.", + ], + 'RubOut': ["Et maintenant un acte de disparition.", + "J'ai l'impression de t'avoir perdu quelque part.", + "J'ai décidé de te gommer.", + "Je gomme toujours tous les obstacles.", + "Je vais simplement effacer cette erreur.", + "Je peux faire disparaître tous les ennuis.", + "J'aime les choses nettes et propres.", + "Essaie de mettre la gomme.", + "Je te vois...je ne te vois plus.", + "Cela va finir par pâlir.", + "Je vais éliminer le problème.", + "Laisse-moi m'occuper de tes zones à problèmes.", + ], + 'Sacked':["On dirait que tu vas te faire licencier.", + "L'affaire est dans le sac.", + "Tu as une licence de vol?", + "De chasse ou de pêche ?", + "Mes ennemis vont être à la porte!", + "J'ai le record de Toontown pour les licenciements.", + "On n'a plus besoin de toi ici.", + "Tu as passé assez de temps ici, tu es renvoyé(e)!", + "Laisse-moi te mettre en boîte.", + "Tu ne peux pas te défendre si je veux te mettre dehors!", + ], + 'Schmooze':["Tu ne verras jamais ça venir.", + "Ça fera bien sur toi.", + "Tu as gagné ça.", + "Je ne voulais pas baver.", + "La flatterie mène partout.", + "Je vais en rajouter une couche.", + "C'est le moment d'en rajouter.", + "Je vais me mettre de ton bon côté!", + "Ça mérite une bonne tape dans le dos.", + "Je vais chanter tes louanges.", + "Je suis navré de te faire tomber de ton piédestal, mais...", + ], + 'Shake': ["Tu es juste à l'épicentre.", + "Tu es juste sur une faille.", + "Ça va secouer.", + "Je crois que c'est une catastrophe naturelle.", + "C'est un désastre de proportions sismiques.", + "Celui-ci est en dehors de l'échelle de Richter.", + "C'est le moment de se mettre à l'abri.", + "Tu as un air troublé.", + "Attention la secousse!", + "Je vais te secouer, pas te faire tourner.", + "Ça devrait te secouer.", + "J'ai un bon plan pour s'échapper.", + ], + 'Shred': ["Je dois me débarrasser de quelques déchets.", + "J'augmente ma capacité de traitement.", + "Je crois que je vais me débarrasser de toi maintenant.", + "On va pouvoir détruire les preuves.", + "Il n'y a plus aucune façon de prouver ça maintenant.", + "Vois si tu peux assembler toutes les pièces.", + "Cela devrait te remettre à la bonne taille.", + "Je vais jeter cette idée.", + "Il ne faut pas que ça tombe entre de mauvaises mains.", + "Vite venu, vite parti.", + "Ce n'est pas ton dernier fragment d'espoir ?", + ], + 'Spin': ["Tu veux qu'on aille faire un tour ?", + "À quelle vitesse tournes-tu?", + "Ça va te faire tourner la tête!", + "C'est le tour que prennent les choses.", + "Je vais t'emmener faire un tour.", + "Que feras-tu quand ce sera ton tour ?", + "Surveille-moi ça. Je ne voudrais pas que ça tourne trop vite!", + "Tu vas tourner longtemps comme ça?", + "Mes attaques vont te donner le tournis!", + ], + 'Synergy': ["Je transmets cela au comité.", + "Ton projet a été annulé.", + "Ton budget a été réduit.", + "Nous allons restructurer ton service.", + "J'ai mis ça au vote et tu as perdu.", + "Je viens de recevoir l'accord final.", + "Il n'y a pas de problèmes, il n'y a que des solutions.", + "Je te recontacte à ce sujet.", + "Revenons à cette affaire.", + "Considère que c'est un manque de synergie.", + ], + 'Tabulate': ["Ça ne s'additionne pas!", + "Si je compte bien, tu as perdu.", + "Tu comptes bien toutes les colonnes.", + "Je te fais le total dans un instant.", + "Tu es prêt(e) à compter tout ça?", + "Ta facture est payable dès maintenant.", + "Il est temps de faire une estimation.", + "J'aime bien mettre les choses en ordre.", + "Et les résultats au pointage sont...", + "Ces chiffres devraient être très puissants.", + ], + 'TeeOff': ["Tu ne fais pas le poids.", + "Gare à toi!", + "Je suis vexé.", + "Pourquoi es-tu en colère ?", + "Essaye simplement d'éviter le danger.", + "Scrongneugneu!", + "Tu vas prendre la mouche à tous les coups.", + "Tu es sur mon chemin.", + "J'ai une bonne prise sur la situation.", + "Attention le petit oiseau va se fâcher!", + "Garde un œil sur moi!", + "Ça te dérange si je joue ?", + ], + 'Tremor': ["Tu as senti ça?", + "Tu n'as pas peur d'un petit frémissement n'est-ce pas?", + "Au commencement était le frémissement.", + "Tu as l'air de trembler.", + "Je vais un peu secouer les choses!", + "Tu te prépare à sursauter ?", + "Qu'est-ce qui ne va pas? Tu as l'air d'accuser la secousse.", + "Crainte et tremblements!", + "Pourquoi trembles-tu de peur ?", + ], + 'Watercooler': ["Ça devrait te rafraîchir.", + "Tu ne trouves pas ça rafraîchissant ?", + "Je livre les boissons.", + "Directement du robinet dans ton gosier.", + "C'est quoi le problème, c'est juste de l'eau de source.", + "Ne t'inquiète pas, c'est filtré.", + "Ah, un autre client satisfait.", + "C'est l'heure de ta livraison quotidienne.", + "J'espère que les couleurs ne vont pas déteindre.", + "Tu as envie de boire ?", + "Tout s'en va à la lessive.", + "C'est toi qui paies à boire.", + ], + 'Withdrawal': ["Je crois que tu es à découvert.", + "J'espère que ton compte est suffisamment approvisionné.", + "Prends ça, avec les intérêts.", + "Ton solde n'est pas en équilibre.", + "Tu vas bientôt devoir faire un dépôt.", + "Tu as souffert de la récession économique.", + "Je crois que tu as un passage à vide.", + "Tes finances sont sur le déclin.", + "Je prévois une baisse définitive.", + "C'est un revers de fortune.", + ], + 'WriteOff': ["Laisse-moi augmenter tes pertes.", + "Profitons d'une mauvaise affaire.", + "C'est l'heure d'équilibrer les comptes.", + "Ça ne va pas faire bien dans ton bilan.", + "Je suis à la recherche de quelques dividendes.", + "Tu dois tenir compte de tes pertes.", + "Tu peux oublier les bonus.", + "Je vais mélanger tes comptes.", + "Tu vas avoir quelques pertes.", + "Ça va te faire mal au solde.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "En attente des autres joueurs...", + +# Elevator.py +ElevatorHopOff = "Quitter" +ElevatorStayOff = "Si tu descends, tu devras attendre \nque l'ascenseur se vide ou parte" +ElevatorLeaderOff = "Seul ton chef peut décider du moment où descendre." +ElevatorHoppedOff = "Tu dois attendre le prochain ascenseur" +ElevatorMinLaff = "Il te faut %s rigolpoints pour prendre cet ascenseur" +ElevatorHopOK = "OK" +ElevatorGroupMember = "Seul le chef de ton groupe peut\n décider quand monter" + +# DistributedCogKart.py +KartMinLaff = "Il te faut %s rigolpoints pour monter dans ce kart" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = " SA" +CogsIncModifier = "%s"+ CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "Qui est là?" +DoorWhoAppendix = "qui?" +DoorNametag = "Porte" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Tu as besoin de gags! Va en parler à Tom Tuteur!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Reviens ici quand tu auras vaincu le Laquaistic!" +FADoorCodes_TALK_TO_HQ = "Va chercher ta récompense auprès d'Harry au QG!" +FADoorCodes_WRONG_DOOR_HQ = "Mauvaise porte! Prends l'autre porte pour aller au terrain de jeux!" +FADoorCodes_GO_TO_PLAYGROUND = "Mauvais chemin! Tu dois aller au terrain de jeux!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Marche jusqu'à ce Laquaistic pour te battre avec lui!" +FADoorCodes_TALK_TO_HQ_TOM = "Va chercher ta récompense au QG des Toons!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Fais attention! Il y a un COG là-dedans!" +FADoorCodes_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Cog!n\nConstruis ton déguisement de Cog avec des pièces de l'usine." +FADoorCodes_SB_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Cog!n\nConstruis ton déguisement de Cog avec des pièces de l'usine." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "Tu vas te faire prendre si tu entres ici en Toon! Tu dois d'abord terminer ton déguisement de Caissbot!\n\nTermine ton déguisement de Caissbot en réussissant des défitoons au Pays des Rêves." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Loibot !\n\nAssemble ton déguisement de Loibot en terminant les défitoons qui sont après le Pays des Rêves de Donald." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Tank", + "Tank il ne regarde pas, lance-lui un gâteau!"], + + 2200 : ["Audrey", + "Audrey mieux sortir d'ici, voilà les Cogs qui arrivent!"], + + 2300: ["Hadrien", + "Hadrien que quelques pièces Cog et on y va!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdin mauvais goût..."], + 6 : ["Bidule", + "Bidule sais pas, d'où ils viennent tous ces Cogs?"], + 30 : ["Jambon", + "Jambon, ils sont même très bons ces gâteaux pour les Cogs."], + 28: ["Isaïe", + "Isaïe à la gare pour aller faire un tour de tramway."], + 12: ["Jules", + "Jules aurait parié, tu vas me laisser entrer dans un bâtiment Cog et je te donnerai un toonique."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Qui", + "Il y a un mauvais écho par ici, n'est-ce pas?"], + + ["Douglas", + "Douglas à la vanille ça t'intéresse ?"], + + ["Geoffrey", + "Geoffrey bien une petite sieste, laisse-moi entrer."], + + ["Justin", + "Justin petit moment."], + + ["Adhémar", + "Adhémar pas ta voiture ?"], + + ["Annie", + "Annie rien comprendre, pourquoi tu n'ouvres pas?"], + + ["Omer", + "Omer veille, j'ai fini par te trouver."], + + ["Thérèse", + "Thérèse, t'es là sans bouger depuis tout ce temps?"], + + ["Sylvie", + "Sylvie c'est un miracle, laisse-le au moins entrer."], + + ["Aude", + "Aude toilette à la lavande ce matin ?"], + + ["Alex", + "Alex Térieur, j'ai froid dehors."], + + ["Alain", + "Alain Térieur, je voudrais entrer!"], + + ["Justine", + "Justine petite minute, je n'en ai pas pour longtemps."], + + ["Vincent", + "Vincent rien, et repart sans rien."], + + ["Jean", + "Jean ai marre que tu n'ouvres pas cette porte!"], + + ["Firmin", + "Firmin peu la radio tu m'entendrais mieux."], + + ["Geoffroy", + "Geoffroy dehors laisse-moi entrer."], + + ["Jessica", + "Jessica difficiles à traiter, dépêche-toi un peu."], + + ["Djamila", + "Djamila clé sous la porte."], + + ["Emma", + "Emma claqué la porte au nez!!"], + + ["Nicole", + "Nicole rien du tout ça doit rester propre."], + + ["Yann-Adam", + "Yann-Adam le frigo je peux entrer ?"], + + ["Louis", + "Louis pas trop fine, décidément."], + + ["Mélusine", + "Mélusine des Cogs en faillite, au lieu de dormir."], + + ["Kim", + "Kim énerve, à ne pas ouvrir."], + + ["Ella", + "Ella pas envie de descendre ouvrir ?"], + + ["Jean", + "Jean file un pull et j'arrive."], + + ["Roger", + "Roger plus rien dans le frigo, tu peux aller faire les courses?"], + + ["John", + "John Dœuf est déjà passé vendre de la mayonnaise ?"], + + ["Alain", + "Alain d'Issoire! C'est ça, bon dimanche."], + + ["Steve", + "Steve a, j'y vais aussi."], + + ["Elvire", + "Elvire pas sur ses gonds, ta porte."], + + ["Jean", + "Jean, bon, je peux entrer finalement ?"], + + ["Sarah", + "Sarah fraîchit dernièrement, j'ai froid dehors."], + + ["Aïcha", + "Aïcha fait mal aux mains de frapper à ta porte."], + + ["Sarah", + "Sarah croche toujours au téléphone, tu ne veux vraiment pas me parler ?"], + + ["Déborah", + "Déborah, dis, qu'il y a dans ton jardin, je peux les voir ?"], + + ["Eddy", + "Eddy donc toi là-bas, tu vas finir par venir ?"], + + ["Élie", + "Élie quoi? Le journal est déjà arrivé?"], + + ["Mandy", + "Mandy donc tu fais quoi là?"], + + ["Yvon", + "Yvon pas revenir plus tard si tu n'ouvres pas!"], + + ["Isabelle", + "Isabelle toujours à n'importe quelle heure."], + + ["Robin", + "Robin, dis donc, c'est maintenant que tu arrives?"], + + ["Oscar", + "Oscar, il n'est jamais à l'heure, je prendrai le train la prochaine fois."], + + ["Léonard", + "Léonard j'aime pas, j'aime mieux les langoustines - merci quand même pour ton invitation."], + + ["Gérard", + "Gérard, mais rarement vu ça."], + + ["Théa", + "Théa l'heure, pour une fois?"], + + ["Médor", + "Médor, Médor, mais comment veux-tu que je dorme si tu ne me laisses pas entrer ?"], + + ["Stella", + "Stella mais c'est plus là."], + + ["Isidore", + "Isidore que la nuit, il est parti à l'heure qu'il est."], + + ["Élodie", + "Élodie, donc? C'est pas fini?"], + + ["Julien", + "Julien du tout à te donner."], + + ["Yvan", + "Yvan quoi? J'ai besoin de rien."], + + ["Eugène", + "Eugène pas du tout, prend ton temps."], + + ["Sultan", + "Sultan de travail, je ne peux pas dormir."], + + ["André", + "Mais André donc."], + + ["Alphonse", + "Alphonse pas dans l'escalier en venant ouvrir."], + + ["Amélie", + "Amélie donc ce qui est écrit au lieu de redemander."], + + ["Angèle", + "Angèle pas du tout, il ne fait pas froid."], + + ["Aubin", + "Aubin dis donc, quand est-ce que tu arrives?"], + + ["Cécile", + "Cécile est de bonne humeur qu'il vient ouvrir la porte ?"], + + ["Djemila", + "Djemila clé dans la serrure mais ça ne marche pas."], + + ["Éléonore", + "Éléonore maintenant mais j'ai pas sa nouvelle adresse."], + + ["Huguette", + "Huguette si quelqu'un d'autre arrive ?"], + + ["Isolde", + "Isolde pas, tout est au prix fort."], + + ["Jenny", + "Jenny figues ni raisin, l'épicerie a déménagé."], + + ["Jérémie", + "Jérémie le courrier à la poste, maintenant je suis rentré."], + + ["Jimmy", + "Jimmy ton courrier dans la boîte"], + + ["Johnny", + "Johnny connais rien du tout, viens donc voir ça."], + + ["Julie", + "Julie pas très bien ce qui est écrit sur la porte."], + + ["Cathy", + "Cathy donc dit ?"], + + ["Léo", + "Léo lit encore à cette heure-là?"], + + ["Léon", + "Léon-dit, ça ne m'intéresse pas. Je préfère que tu me dises la vérité."], + + ["Maël", + "Maël dit toujours la même chose!"], + + ["Marin", + "Marin du tout, je veux juste te dire bonjour."], + + ["Quentin", + "Quentin est là, on ouvre."], + + ["Sacha", + "Sacha pas, demande-lui directement."], + + ["Stella", + "Stella tu ouvres. Réponds!"], + + ["Théophile", + "Théophile encore une fois, tu ne fais que téléphoner."], + + ["Tudor", + "Tudor tout le temps quand je passe te voir."], + + ["Véra", + "Véra bien qui c'est si tu descends ouvrir."], + + ["Xavier", + "Xavier pas une sonnette la dernière fois?"], + + ["Yann", + "Yann a plus, y'en aura la prochaine fois."], + + ["Yvon", + "Yvon bien, merci de prendre des nouvelles!"], + + ["Odyssée", + "Odyssée quoi toutes ces questions?"], + + ["Thor", + "Thor ait le temps de descendre ouvrir ?"], + + ["Édith", + "Édith a vu l'heure, il est bien temps d'arriver."], + + ["Jean-Aymar", + "Jean-Aymar d'attendre."], + + ["Aubin", + "Aubin dis donc, tu en mets un temps!"], + + ["Ahmed", + "Ahmed dépens, j'ai fini par comprendre."], + + ["Henri", + "Henri encore, de ta dernière blague."], + + ["Aude", + "Aude désespoir, ô rage."], + + ["Ali", + "Ali qu'a tort, comme d'habitude."], + + ["Gilles", + "Gilles est de sauvetage aujourd'hui."], + + ["Hans", + "Hans qui me concerne, j'aimerais bien que tu ouvres la porte."], + + ["Roméo", + "Roméo lendemain ce que tu ne peux pas faire aujourd'hui."], + + ["Hildéphonse", + "Hildéphonse la porte."], + + ["Helmut", + "Helmut le pain de la bouche!"], + + ["Hercule", + "Hercule la voiture au fond de la cour."], + + ["Mylène", + "Mylène, mi-coton."], + + ["Célestin", + "Célestin ? Non c'est l'ouest."], + + ["Ondine", + "Ondine où ce soir ?"], + + ["Laurent", + "Laurent-Outang, je cherche le zoo?"], + + ["Anne", + "Anne pas dire."], + + ["Edgar", + "Edgar pas là, tu gênes."], + + ["José", + "José pas le dire."], + + ["Samira", + "Samira pas c'est trop petit."], + + ["Humphrey", + "Humphrey peur celui-là!"], + + ["Saturnin", + "Saturnin peu trop vite."], + + ["Juste", + "Juste pour voir."], + + ["Aziza", + "Aziza pouvait durer!"], + + ["Jonathan", + "Jonathan que toi."], + + ["Aubin", + "Aubin, ça alors! Je ne comptais pas sur toi."], + + ["Yamamoto", + "Yamamoto qu'a dérapé, je cherche un garage."], + + ["Stanislav", + "Stanislav tous les matins sous sa douche."], + + ["Yvan-Dédé", + "Yvan-Dédé, voitures d'occasion."], + + ["Céline", + "Céline évitable."], + + ["Jean-Philémon", + "Jean-Philémon blouson et je viens."], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Salut, %!", + "Youhouu %,\nravi de te voir.", + "Je suis content que tu sois là aujourd'hui!", + "Bien le bonjour, %.", + ] + +SharedChatterComments = [ + "C'est un super nom, %.", + "J'aime bien ton nom.", + "Fais attention aux" + Cogs + "." + "On dirait que le tramway arrive!", + "Je dois jouer à un jeu du tramway pour avoir quelques morceaux de tarte!", + "Quelquefois, je joue aux jeux du tramway juste pour manger de la tarte aux fruits!", + "Ouf, je viens d'arrêter un groupe de" + Cogs + ". J'ai besoin de repos!", + "Aïe, certains de ces" + Cogs + " sont costauds!", + "On dirait que tu t'amuses.", + "Oh bon sang, quelle bonne journée.", + "J'aime bien ce que tu portes.", + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Amuse-toi bien dans mon quartier.", + "J'espère que tu profites bien de ton séjour à Toontown!", + "J'ai entendu dire qu'il neigeait dans le Glagla.", + "Est-ce que tu as fait un tour de tramway aujourd'hui?", + "J'aime bien rencontrer des nouveaux.", + "Aïe, il y a beaucoup de " + Cogs + " dans le Glagla.", + "J'aime bien jouer à chat. Et toi ?", + "Les jeux du tramway sont amusants.", + "J'aime bien faire rire les gens.", + "J'adore aider mes contacts.", + "Hum, serais-tu perdu(e)? N'oublie pas que ta carte est dans ton journal de bord.", + "Essaie de ne pas te noyer dans la paperasserie des " + Cogs + ".", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + "Si tu appuies sur la touche \"page précédente\", tu peux regarder vers le haut!", + "Si tu aides à reprendre des bâtiments aux Cogs, tu peux gagner une étoile de bronze!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Si tu appuies sur la touche Ctrl, tu peux sauter!", + ] + +SharedChatterGoodbyes = [ + "Je dois partir maintenant, au revoir!", + "Je crois que je vais aller faire un jeu du tramway.", + "Eh bien, au revoir. À bientôt, %!", + "Il vaudrait mieux que je me dépêche et que je m'occupe d'arrêter ces " + Cogs + ".", + "C'est l'heure d'y aller.", + "Désolé, je dois partir.", + "Au revoir.", + "À plus tard,%!", + "Je crois que je vais aller m'entraîner à lancer des petits gâteaux.", + "Je vais me joindre à un groupe et arrêter des " + Cogs + ".", + "Je suis content(e) de t'avoir vu(e) aujourd'hui, %.", + "J'ai beaucoup de choses à faire. Je ferais mieux de m'y mettre.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bienvenue à Toontown centre.", + "Salut, je m'appelle " + Mickey + ". Et toi ?", + ], + [ # Comments + "Dis donc, as-tu vu " + Donald + "?", + "Je vais aller regarder le brouillard se lever sur les quais " + Donald + ".", + "Si tu vois mon copain " + Goofy + ", dis-lui bonjour de ma part.", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + ], + [ # Goodbyes + "Je vais au pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Minnie +"!", + "On dirait que c'est l'heure du dîner pour " + Pluto + ".", + "Je crois que je vais aller nager aux quais " + Donald + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bienvenue au Pays musical.", + "Salut, je m'appelle " + Minnie + ". Et toi ?" + ], + [ # Comments + "Les collines sont animées par les notes de musique!", + # the merry no longer goes round + #"N'oublie pas d'essayer le grand manège tourne-disques!", + "Tu as une chouette tenue, %.", + "Dis donc, as-tu vu " + Mickey + "?", + "Si tu vois mon ami " + Goofy + ", dis-lui bonjour de ma part.", + "Aïe, il y a beaucoup de " + Cogs + " près du Pays des rêves de " + Donald + ".", + "J'ai entendu dire qu'il y a du brouillard sur les quais " + Donald + ".", + "N'oublie pas d'essayer le labyrinthe dans le jardin de " + Daisy + ".", + "Je crois bien que je vais aller chercher quelques airs de musique.", + "Hé %, regarde donc par là-bas.", + "J'aime bien entendre de la musique.", + "Je parie que tu ne savais pas que le Pays musical de Minnie est aussi appelé le Haut-Bois? Hi hi!", + "J'aime bien jouer aux imitations. Et toi ?", + "J'aime bien faire rire les gens.", + "Oh là là, ça fait mal aux pieds de trotter toute la journée avec des talons!", + "Belle chemise, %.", + "Est-ce que c'est un bonbon par terre ?", + ], + [ # Goodbyes + "Aïe, je suis en retard pour mon rendez-vous avec " + Mickey + "!", + "On dirait que c'est l'heure du dîner pour " + Pluto + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +DaisyChatter = ( + [ # Greetings + "Bienvenue dans mon jardin!", + "Bonjour, je m'appelle"+Daisy+". Comment t'appelles-tu?", + "Ravi de faire ta connaissance, %!", + ], + [ # Comments + "Ma fleur qui a gagné le prix est au milieu du labyrinthe.", + "J'adore me promener dans le labyrinthe.", + "Je n'ai pas vu"+Goofy+" de la journée.", + "Je me demande où"+Goofy+" se trouve.", + "As-tu vu"+Donald+"?Il est introuvable.", + "Si tu vois mon ami"+Minnie+", dis-lui \"Bonjour\" de ma part.", + "Meilleurs sont tes outils de jardinage, et plus belles seront tes plantes.", + "Il y a beaucoup trop de"+Cogs+" par ici"+lDonaldsDock+".", + "Tu feras le bonheur de tes plantes si tu les arroses tous les jours.", + "Pour faire pousser une pâquerette rose, plante un bonbon jaune et un bonbon rouge ensemble.", + "C'est facile de faire pousser une pâquerette jaune, tu n'as qu'à planter un bonbon jaune.", + "Si tu vois du sable sous une plante, c'est qu'elle a besoin d'eau - faute de quoi elle va se faner!" + ], + [ # Goodbyes + "Je vais au Pays musical pour voir %s!" % Minnie, + "Je suis en retard pour mon pique-nique avec %s!" % Donald, + "Je crois que je vais aller nager à"+lDonaldsDock+".", + "Oh, je commence à avoir sommeil. Je crois que je vais aller au Pays des Rêves", + ] + ) + +ChipChatter = ( + [ # Greetings + "Bienvenue dans %s !" % lOutdoorZone, + "Bonjour, je m'appelle" + Chip + ". Et toi, comment t'appelles-tu ?", + "Non, je suis" + Chip + ".", + "Je suis content de te voir % !", + "Nous sommes Tic et Tac !", + ], + [ # Comments + "J'aime le golf.", + "Nous avons les meilleurs glands de Toontown.", + "Les trous de golf dotés de volcans sont les plus difficiles pour moi.", + ], + [ # Goodbyes + "Nous allons dans le" + lTheBrrrgh +"pour jouer avec %s." % Pluto, + "Nous rendrons visite à %s et pourrons le réparer." % Donald, + "Je crois que je vais aller me baigner dans" + lDonaldsDock + ".", + "Oh, j'ai un peu sommeil. Je pense que je vais aller au Pays des Rêves.", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "Je suis content de te voir % !", + "Bonjour, je m'appelle" + Dale + ". Et toi, comment t'appelles-tu ?", + "Salut, je suis" + Chip + ".", + "Bienvenue dans %s !" % lOutdoorZone, + "Nous sommes Tic et Tac !", + ], + [ # Comments + "J'aime les pique-niques.", + "Les glands sont délicieux. Goûte.", + "Ces moulins à vent sont difficiles aussi.", + ], + [ # Goodbyes + "Hihihi" + Pluto + "est un compagnon de jeu amusant.", + "D'accord, réparons %s." % Donald, + "Une baignade. Quelle idée rafraîchissante !", + "Je suis de plus en plus fatigué et ferais bien une petite sieste.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bienvenue au jardin de " + Daisy + ".", + "Salut, je m'appelle " + Goofy + ". Et toi ?", + "Wof, je suis content de te voir, %!", + ], + [ # Comments + "Bon sang, c'est facile de se perdre dans le labyrinthe!", + "N'oublie pas d'essayer le labyrinthe tant que tu es ici.", + "Je n'ai pas vu " + Daisy + " de la journée.", + "Je me demande où se trouve " + Daisy + ".", + "Dis donc, as-tu vu " + Donald + "?", + "Si tu vois mon ami " + Mickey + ", dis-lui bonjour de ma part.", + "Oh! J'ai oublié le petit déjeuner de " + Mickey + "!", + "Wof, il y a beaucoup de " + Cogs + " près des quais " + Donald + ".", + "On dirait que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + "À la succursale du Glagla de ma boutique à gags, les lunettes hypnotiques sont en vente pour seulement 1 bonbon!", + "La boutique à gags de Dingo propose les meilleurs blagues, astuces et chatouilles de tout Toontown!", + "À la boutique à gags de Dingo, chaque tarte à la crème est garantie faire rire ou tes bonbons te seront remboursés!" + ], + [ # Goodbyes + "Je vais au Pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Donald + "!", + "Je crois que je vais aller nager aux quais " + Donald + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +GoofySpeedwayChatter = ( + [ # Greetings + "Bienvenue au "+lGoofySpeedway+".", + "Salut, je m'appelle "+Goofy+". Et toi ?", + "Ouah, sympa de te voir %!", + ], + [ # Comments + "Bon sang, j'ai vu une super course tout à l'heure.", + "Attention aux peaux de banane sur la piste!", + "Est-ce que tu as fait des améliorations sur ton kart récemment ?", + "Nous venons d'acheter de nouvelles jantes dans le magasin de karts.", + "Dis-donc, tu as vu "+Donald+"?", + "Si tu vois mon ami "+Mickey+", dis-lui bonjour de ma part.", + "Oh! J'ai oublié de préparer le petit déjeuner de "+Mickey+"!", + "Bon sang, c'est vrai qu'il y a un tas de "+Cogs+" sur les "+lDonaldsDock+".", + "À la succursale du Glagla de ma boutique à gags, les lunettes hypnotiques sont en vente pour seulement 1 bonbon!", + "La boutique à gags de Dingo propose les meilleurs blagues, astuces et chatouilles de tout Toontown!", + "À la boutique à gags de Dingo, chaque tarte à la crème est garantie de te faire rire ou tes bonbons te seront remboursés !" + ], + [ # Goodbyes + "Je vais au Pays Musical pour voir %s!" % Mickey, + "Aïe, je suis en retard pour mon rendez-vous avec %s!" % Donald, + "Je crois que je vais aller nager aux "+lDonaldsDock+".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bienvenue au Pays des rêves.", + "Salut, je m'appelle " + Donald + ". Et toi ?" + ], + [ # Comments + "Cet endroit me donne quelquefois la chair de poule.", + "N'oublie pas d'essayer le labyrinthe dans le jardin de" + Daisy + ".", + "Oh, bon sang! Quelle bonne journée.", + "Dis donc, as-tu vu" + Mickey + "?", + "Si tu vois mon copain" + Goofy + ", dis-lui bonjour de ma part." + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Aïe, il y a beaucoup de " + Cogs + " près des quais " + Donald + ".", + "Hé dis donc, tu n'as pas encore fait un tour de bateau avec moi aux quais " + Donald + "?" + "Je n'ai pas vu " + Daisy + " de la journée.", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin." + "Coin coin.", + ], + [ # Goodbyes + "Je vais au Pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Daisy + "!", + "Je crois que je vais aller nager près de mes quais.", + "Je crois que je vais aller faire un tour de bateau près de mes quais.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Nouvel(le) ami(e)" +FriendsListPanelSecrets = "Secrets" +FriendsListPanelOnlineFriends = "CONTACTS\nEN LIGNE" +FriendsListPanelAllFriends = "TOUS\nLES CONTACTS" +FriendsListPanelIgnoredFriends = "TOONS\nIGNORÉS" +FriendsListPanelPets = "ANIMAUX FAMILIERS\nA PROXIMITÉ" +FriendsListPanelPlayers = "TOUS LES AMIS\nDU JOUEUR" +FriendsListPanelOnlinePlayers = "AMIS DU JOUEUR\nEN LIGNE" + +FriendInviterClickToon = "Clique sur le Toon avec lequel tu souhaites devenir ami.\n\n(Tu as %s amis)" + +# Support DISL account friends +FriendInviterToon = "Toon" +FriendInviterThatToon = "Ce Toon" +FriendInviterPlayer = "Joueur" +FriendInviterThatPlayer = "Ce joueur" +FriendInviterBegin = "Quel genre d'ami aimerais-tu te faire ?" +FriendInviterToonFriendInfo = "Un ami seulement dans Toontown" +FriendInviterPlayerFriendInfo = "Un ami à travers le réseau Disney.com" +FriendInviterToonTooMany = "Tu as trop d'amis Toon pour pouvoir en ajouter un nouveau. Tu devras supprimer des amis Toon si tu veux devenir ami avec %s. Tu pourrais aussi essayer de t'en faire un ami de jeu." +FriendInviterPlayerTooMany = "Tu as trop d'amis de jeu pour pouvoir en ajouter un nouveau. Tu devras supprimer des amis de jeu si tu veux devenir ami avec %s. Tu pourrais aussi essayer de t'en faire un ami Toon." +FriendInviterToonAlready = "%s est déjà ton ami Toon." +FriendInviterPlayerAlready = "%s est déjà ton ami de jeu." +FriendInviterStopBeingToonFriends = "Cesser d'être ami Toon" +FriendInviterStopBeingPlayerFriends = "Cesser d'être ami de jeu" +FriendInviterEndFriendshipToon = "Es-tu sûr de vouloir cesser d'être ami Toon avec %s ?" +FriendInviterEndFriendshipPlayer = "Es-tu sûr de vouloir cesser d'être ami de jeu avec %s ?" +FriendInviterRemainToon = "\n(Tu resteras néanmoins ami Toon avec %s)" +FriendInviterRemainPlayer = "\n(Tu resteras néanmoins ami de jeu avec %s)" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Désolé, tu ne peux pas avancer parce que le téléchargement de %(phase)s n'en est qu'à %(percent)s% %.\n\nRéessaie plus tard." + +# TeaserPanel.py +TeaserTop = "Désolé! Tu n'as pas accès à ceci pendant l'essai gratuit.\n\nInscris-toi maintenant et profite de ces super fonctionnalités :" +TeaserBottom = "Subscribe now and enjoy these great features:" +TeaserOtherHoods = "Visite les 6 quartiers exceptionnels!" +TeaserTypeAName = "Inscris le nom que tu préfères pour ton Toon!" +TeaserSixToons = "Crée jusqu'à 6 Toons par compte!" +TeaserOtherGags = "Additionne 6 niveaux d'habileté\ndans 6 séries de gags différentes!" +TeaserClothing = "Achète des vêtements originaux\npour personnaliser ton Toon!" +TeaserFurniture = "Achète et dispose des meubles dans ta maison!" +TeaserCogHQ = "Infiltre des zones dangereuses sur\nle territoire des Cogs!" +TeaserSecretChat = "Échange des secrets avec tes contacts\npour pouvoir discuter en ligne avec eux!" +TeaserCardsAndPosters = "Participe aux concours et compétitions gagne des trophées et \naugmente ta reserve des rigolpoints! \nTon nom apparaîtra sur www.toontown.fr" +TeaserHolidays = "Participe à des événements spéciaux et\npassionnants et à des fêtes!" +TeaserQuests = "Relève des centaines de défitoons pour sauver Toontown!" +TeaserEmotions = "Achète des émotions pour rendre ton\nToon plus expressif!" +TeaserMinigames = "Joue aux 8 sortes de mini jeux!" +TeaserKarting = "Fais la course contre d'autres Toons dans de super karts!" +TeaserKartingAccessories = " Personnaliseton kart avec des accessoiressuper cool." +TeaserTricks = " Entraîne ton Doudouà faire des tourspour\nqu'il t'aide dans les combats !" +TeaserGardening = "Plante des fleurs, des statues et des arbres à gags pour embellir\n ta propriété." +TeaserRental = "Loue des articles de fête amusants pour ta propriété !" +TeaserBigger = "Achète des articles Toon meilleurs et plus gros !" +TeaserTricks = "Entraîne ton Doudou à faire des tours pour t'aider dans le combat !" +TeaserSpecies = "Crée des Toons singe, cheval et ours, et joue avec !" +TeaserFishing = "Collectionne toutes les espèces de poissons !" +TeaserGolf = "Joue sur des terrains de golf complètement dingues !" +TeaserSubscribe = "S'inscrire maintenant" +TeaserContinue = "Continuer l'essai" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Téléchargement de: %s" +DownloadWatcherInitializing = "Initialisation du téléchargement..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Initialisation", + 1 : "Panda", + 2 : "Moteur", + 3 : "Faire un Toon", + 3.5 : "Toontoriel", + 4 : "Terrain de jeux", + 5 : "Rues", + 5.5 : "Domaines", + 6 : "Quartiers I", + 7 : "Bâtiments" + Cog, + 8 : "Quartiers II", + 9 : "QG Vendibot", + 10 : "QG Caissbot", + 11 : lLawbotHQ, + 12 : Bossbot + " HQ", + 13 : "Parties", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s sur %(total)s)" +LauncherStartingMessage = "Lancement de Toontown en ligne de Disney..." +LauncherDownloadFile = "Téléchargement des mises à jour:" + LauncherProgress + "..." +LauncherDownloadFileBytes = "Téléchargement des mises à jour:" + LauncherProgress + " : %(bytes)s" +LauncherDownloadFilePercent = "Téléchargement des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherDecompressingFile = "Décompression des mises à jour:" + LauncherProgress + "..." +LauncherDecompressingPercent = "Décompression des mises à jour:" + LauncherProgress + ". : %(percent)s% %" +LauncherExtractingFile = "Extraction des mises à jour:" + LauncherProgress + "..." +LauncherExtractingPercent = "Extraction des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherPatchingFile = "Application des mises à jour:" + LauncherProgress + "..." +LauncherPatchingPercent = "Application des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherConnectProxyAttempt = "En cours de connexion à Toontown: %s (proxy : %s) essai : %s" +LauncherConnectAttempt = "En cours de connexion à Toontown: %s essai %s" +LauncherDownloadServerFileList = "Mise à jour de Toontown..." +LauncherCreatingDownloadDb = "Mise à jour de Toontown..." +LauncherDownloadClientFileList = "Mise à jour de Toontown..." +LauncherFinishedDownloadDb = "Mise à jour de Toontown..." +LauncherStartingToontown = "Lancement de Toontown..." +LauncherStartingGame = "Lancement de Toontown..." +LauncherRecoverFiles = "Mise à jour de Toontown. Récupération des fichiers..." +LauncherCheckUpdates = "Recherche de mises à jour pour "+ LauncherProgress +LauncherVerifyPhase = "Mise à jour de Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Faire un\nToon" +AvatarChoicePlayThisToon = "Jouer\navec ce Toon" +AvatarChoiceSubscribersOnly = "S'inscrire\n\n\n\nMaintenant!" +AvatarChoiceDelete = "Supprimer" +AvatarChoiceDeleteConfirm = "Cela va supprimer %s pour toujours." +AvatarChoiceNameRejected = "Nom\nrefusé" +AvatarChoiceNameApproved = "Nom\naccordé!" +AvatarChoiceNameReview = "En cours\nd'examen" +AvatarChoiceNameYourToon = "Donne un nom\nà ton Toon!" +AvatarChoiceDeletePasswordText = "Attention! Cela va supprimer %s pour toujours. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteConfirmText = "Attention! Cela va supprimer %(name)s pour toujours. Si tu es certain(e) de vouloir faire cela, entre \"%(confirm)s\" et clique sur OK." +AvatarChoiceDeleteConfirmUserTypes = "supprimer" +AvatarChoiceDeletePasswordTitle = "Supprimer le Toon ?" +AvatarChoicePassword = "Mot de passe" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Ce mot de passe ne semble pas correspondre. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteWrongConfirm = "Tu n'as pas entré le bon mot. Pour supprimer %(name)s, entre \"%(confirm)s\" et clique sur OK. N'entre pas les guillemets. Clique sur Annuler si tu as changé d'avis." + +# AvatarChooser.py +AvatarChooserPickAToon = "Choisis un Toon pour jouer" +AvatarChooserQuit = lQuit + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Appelez le Service clients au %s. " +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nSi vous avez besoin d'aide, vous pouvez appeler le service clients au %s." +TTAccountIntractibleError = "Une erreur s'est produite." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', + 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc',] +DateOfBirthEntryDefaultLabel = "Date de naissance" + + +# AchievePage.py +AchievePageTitle = "Réussites\n (Bientôt disponible)" + +# PhotoPage.py +PhotoPageTitle = "Photo\n (Bientôt disponible)" + +# BuildingPage.py +BuildingPageTitle = "Bâtiments\n (Bientôt disponible)" + +# InventoryPage.py +InventoryPageTitle = "Gags" +InventoryPageDeleteTitle = "SUPPRIMER LES GAGS" +InventoryPageTrackFull = "Tu as tous les gags de la série %s." +InventoryPagePluralPoints = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageSinglePoint = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageNoAccess = "Tu n'as pas encore accès à la série %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "Toons SOS" + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Restant %s" +# PartiesPage.py +PartiesPageTitle = "Fêtes" +PartiesPageHostTab = "Organiser" +PartiesPageInvitedTab = "Invitations" +PartiesPageTitleHost = "Ma prochaine fête" +PartiesPageTitleInvited = "Invitations pour la fête" + +# MapPage.py +MapPageTitle = "Carte" +MapPageBackToPlayground = "au terrain de jeux" +MapPageBackToCogHQ = "Retour au QG des Cogs" +MapPageGoHome = "à la maison" +# hood name, street name +MapPageYouAreHere = "Tu es à: %s\n%s" +MapPageYouAreAtHome = "Tu es dans\nta propriété." +MapPageYouAreAtSomeonesHome = "Tu es chez %s." +MapPageGoTo = "Aller chez\n%s." + +# OptionsPage.py +OptionsPageTitle = "Options" +OptionsPagePurchase = "S'inscrire!" +OptionsPageLogout = "Se déconnecter" +OptionsPageExitToontown = "Quitter Toontown" +OptionsPageMusicOnLabel = "Musique activée." +OptionsPageMusicOffLabel = "Musique désactivée." +OptionsPageSFXOnLabel = "Effets sonores activés." +OptionsPageSFXOffLabel = "Effets sonores désactivés." +OptionsPageFriendsEnabledLabel = "Demandes de nouveaux contacts acceptées." +OptionsPageFriendsDisabledLabel = "Demandes de nouveaux contacts non acceptées." +OptionsPageSpeedChatStyleLabel = "Couleur du Chat rapide" +OptionsPageDisplayWindowed = "dans une fenêtre" +OptionsPageSelect = "Choisir" +OptionsPageToggleOn = "Activer" +OptionsPageToggleOff = "Désactiver" +OptionsPageChange = "Modifier" +OptionsPageDisplaySettings = "Affichage: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Affichage: %(screensize)s" +OptionsPageExitConfirm = "Quitter Toontown ?" + +DisplaySettingsTitle = "Réglages d'affichage" +DisplaySettingsIntro = "Les réglages suivants sont utilisés pour configurer l'affichage de Toontown sur votre ordinateur. Il n'est sans doute pas indispensable de les modifier sauf si vous avez un problème." +DisplaySettingsIntroSimple = "Vous pouvez accroître la résolution d'écran pour améliorer la lisibilité du texte et des graphiques de Toontown, mais en fonction de votre carte graphique, certaines valeurs plus élevées risquent d'affecter le bon fonctionnement du jeu, voire de l'empêcher complètement de fonctionner." + +DisplaySettingsApi = "Interface graphique:" +DisplaySettingsResolution = "Résolution:" +DisplaySettingsWindowed = "Dans une fenêtre" +DisplaySettingsFullscreen = "Plein écran" +DisplaySettingsApply = "Appliquer" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Lorsque vous cliquez sur OK, les réglages d'affichage sont modifiés. Si la nouvelle configuration ne s'affiche pas correctement sur votre ordinateur, l'affichage revient automatiquement à sa configuration d'origine après %s secondes." +DisplaySettingsAccept = "Cliquez sur OK pour conserver les nouveaux réglages ou sur Annuler pour revenir aux valeurs précédentes. Si vous ne cliquez sur rien, les réglages reviennent automatiquement aux valeurs précédentes après %s secondes." +DisplaySettingsRevertUser = "Vos précédents réglages d'affichage ont été restaurés." +DisplaySettingsRevertFailed = "Les réglages d'affichage sélectionnés ne peuvent pas fonctionner sur votre ordinateur. Vos précédents réglages d'affichage ont été restaurés." + +# TrackPage.py +TrackPageTitle = "Entraînement à une série de gags" +TrackPageShortTitle = "Entraînement\naux gags" +TrackPageSubtitle = "Termine des défitoons pour apprendre à utiliser de nouveaux gags!" +TrackPageTraining = "Tu t'entraînes pour utiliser les gags %s. \nLorsque tu auras terminé les 16 défis, tu\npourras utiliser les gags %s lors des combats." +TrackPageClear = "Tu ne t'entraînes pour aucune série de gags actuellement." +TrackPageFilmTitle = "Entraînement\naux gags %s\n." +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "Défitoons" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nTo: %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nTo choose, go see:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Choisis" +QuestPageLocked = "Locked" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Une rue" +QuestPosterHQLocationName = "Un quartier" + +QuestPosterTailor = "Tailleur" +QuestPosterTailorBuildingName = "Boutique de prêt-à-porter" +QuestPosterTailorStreetName = "Un terrain de jeux" +QuestPosterTailorLocationName = "Un quartier" +QuestPosterPlayground = "Sur le terrain de jeux" +QuestPosterAtHome = "Chez toi" +QuestPosterInHome = "Dans ta maison" +QuestPosterOnPhone = "Sur ton téléphone" +QuestPosterEstate = "Dans ta propriété" +QuestPosterAnywhere = "N'importe où" +QuestPosterAuxTo = "à:" +QuestPosterAuxFrom = "depuis:" +QuestPosterAuxFor = "pour:" +QuestPosterAuxOr = "ou:" +QuestPosterAuxReturnTo = "Retourner à:" +QuestPosterLocationIn = " à" +QuestPosterLocationOn = " à" +QuestPosterFun = "Juste pour s'amuser!" +QuestPosterFishing = "ALLER PÊCHER" +QuestPosterComplete = "TERMINÉ" + +# ShardPage.py +ShardPageTitle = "Districts" +ShardPageHelpIntro = "Chaque district est une copie du monde de Toontown." +ShardPageHelpWhere = " Tu es actuellement dans le district de \"%s\"." +ShardPageHelpWelcomeValley = " Tu es actuellement dans le district de la \"Vallée de la Bienvenue\", dans \"%s\"." +ShardPageHelpMove = " Pour aller dans un nouveau district, clique sur son nom." + +ShardPagePopulationTotal = "Population totale de Toontown:\n%d" +ShardPageScrollTitle = "Nom Population" +ShardPageLow = "Calme" +ShardPageMed = "Idéal" +ShardPageHigh = "Complet" +ShardPageChoiceReject = "Désolé, ce district est complet. Merci d'en essayer un autre." + +# SuitPage.py +SuitPageTitle = "Galerie des Cogs" +SuitPageMystery = "???" +SuitPageQuota = "%s sur %s" +SuitPageCogRadar = "%s présents" +SuitPageBuildingRadarS = "Bâtiment %s" +SuitPageBuildingRadarP = "Bâtiments %s" + +# DisguisePage.py +DisguisePageTitle = "Déguisement de\n" + Cog +DisguisePageMeritBar = "Avancement au mérite" +DisguisePageMeritAlert = "Prêt pour la\npromotion!" +DisguisePageCogLevel = "Niveau %s" +DisguisePageMeritFull = "Plein" +DisguisePageMeritBar = "Avancement au mérite" +DisguisePageCogPartRatio = "%d/%d" + +# FishPage.py +FishPageTitle = "Pêche" +FishPageTitleTank = "Seau de pêche" +FishPageTitleCollection = "Album de pêche" +FishPageTitleTrophy = "Trophées de pêche" +FishPageWeightStr = "Poids:" +FishPageWeightLargeS = "%dkg" +FishPageWeightLargeP = "%dkg" +FishPageWeightSmallS = " %dg" +FishPageWeightSmallP = " %dg" +FishPageWeightConversion = 16 +FishPageValueS = "Valeur: %d bonbon" +FishPageValueP = "Valeur: %d bonbons" +FishPageTotalValue = "" +FishPageCollectedTotal = "Espèces de poissons pêchées: %d sur %d" +FishPageRodInfo = "Canne %s \n%d - %d livres" +FishPageTankTab = "Seau" +FishPageCollectionTab = "Album" +FishPageTrophyTab = "Trophées" + +FishPickerTotalValue = "Seau: %s / %s\nValeur: %d bonbons" + +UnknownFish = "???" + +FishingRod = "Canne %s" +FishingRodNameDict = { + 0 : "Brindille", + 1 : "Bambou", + 2 : "Bois dur", + 3 : "Acier", + 4 : "Or", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Vairon", + 2 : "Poisson", + 3 : "Poisson volant", + 4 : "Requin", + 5 : "Espadons", + 6 : "Épaulard", + } + +# GardenPage.py +GardenPageTitle = "Gardening" +GardenPageTitleBasket = "Panier de fleurs" +GardenPageTitleCollection = "Album de fleurs" +GardenPageTitleTrophy = "Trophées de jardinage" +GardenPageTitleSpecials = "Offres spéciales jardinage" +GardenPageBasketTab = "Panier" +GardenPageCollectionTab = "Album" +GardenPageTrophyTab = "Trophées" +GardenPageSpecialsTab = "Offres spéciales" +GardenPageCollectedTotal = "Variétés de fleurs rassemblées: %d sur %d" +GardenPageValueS = "Valeur: %d bonbon" +GardenPageValueP = "Valeur: %d bonbons" +FlowerPickerTotalValue = "Panier: %s / %s\nValeur: %d bonbons" +GardenPageShovelInfo = "%s Pelle: %d / %d\n" +GardenPageWateringCanInfo = "%s Arrosoir: %d / %d" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Customiser mon kart" +KartPageTitleRecords = "Meilleurs records personnels" +KartPageTitleTrophy = "Trophées de course" +KartPageCustomizeTab = "Customiser" +KartPageRecordsTab = "Records" +KartPageTrophyTab = "Trophée" +KartPageTrophyDetail = "Trophée %s : %s" +KartPageTickets = "Tickets:" +KartPageConfirmDelete = "Supprimer l'accessoire ?" + +#plural +KartShtikerDelete = "Supprimer" +KartShtikerSelect = "Choisir une catégorie" +KartShtikerNoAccessories = "Aucun accessoire acheté" +KartShtikerBodyColors = "Couleurs du kart" +KartShtikerAccColors = "Couleurs des accessoires" +KartShtikerEngineBlocks = "Accessoires du capot" +KartShtikerSpoilers = "Accessoires du coffre" +KartShtikerFrontWheelWells = "Accessoires des roues avant" +KartShtikerBackWheelWells = "Accessoires des roues arrière" +KartShtikerRims = "Accessoires des jantes" +KartShtikerDecals = "Accessoires décalcomanie" +#singluar +KartShtikerBodyColor = "Couleur du kart" +KartShtikerAccColor = "Couleur de l'accessoire" +KartShtikerEngineBlock = "Capot" +KartShtikerSpoiler = "Coffre" +KartShtikerFrontWheelWell = "Roue avant" +KartShtikerBackWheelWell = "Roue arrière" +KartShtikerRim = "Jante" +KartShtikerDecal = "Décalcomanie" + +KartShtikerDefault = "%s par défaut" +KartShtikerNo = "Aucun accessoire de %s" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Choisir" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = "Toonique te permet de soigner les autres Toons lors d'une bataille." +TrackChoiceGuiTRAP = "Les pièges sont des gags puissants qui doivent être utilisés avec les leurres." +TrackChoiceGuiLURE = "Utilise les leurres pour assommer les Cogs ou les attirer dans des pièges." +TrackChoiceGuiSOUND = "Les gags de tapage affectent tous les Cogs mais ne sont pas très puissants." +TrackChoiceGuiDROP = "Les gags de chute font beaucoup de dégâts mais ne sont pas très précis." + +# EmotePage.py +EmotePageTitle = "Expressions / Émotions" +EmotePageDance = "Tu as construit la séquence de danse suivante:" +EmoteJump = "Saut" +EmoteDance = "Danse" +EmoteHappy = "Content(e)" +EmoteSad = "Triste" +EmoteAnnoyed = "Agacement" +EmoteSleep = "Sommeil" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNiveau %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Tu ne peux pas quitter le terrain de jeux tant que ton rigolmètre ne sourit pas!" + +# InventoryNew.py +InventoryTotalGags = "Total des gags\n%d / %d" +InventroyPinkSlips = "%s Avis de licenciement" +InventroyPinkSlip = "1 Avis de licenciement" +InventoryDelete = "SUPPRIMER" +InventoryDone = "TERMINÉ" +InventoryDeleteHelp = "Clique sur un gag pour le SUPPRIMER." +InventorySkillCredit = "Crédit d'habileté: %s" +InventorySkillCreditNone = "Crédit d'habileté: Aucun" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Précision : %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "%(nextExp)s à terminer !" +InventoryGuestExp = "Nombre maxi d'invités" +GuestLostExp = "Plus que le nombre maxi d'invités" +InventoryAffectsOneCog = "Affecte : Un"+ Cog +InventoryAffectsOneToon = "Affecte : Un Toon" +InventoryAffectsAllToons = "Affecte : tous les Toons" +InventoryAffectsAllCogs = "Affecte : tous les"+ Cogs +InventoryHealString = "Toonique" +InventoryDamageString = "Dommages" +InventoryBattleMenu = "MENU DU COMBAT" +InventoryRun = "COURIR" +InventorySOS = "SOS" +InventoryPass = "PASSER" +InventoryFire = "FIRE" +InventoryClickToAttack = "Clique sur\nun gag pour\nattaquer." +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Tu dois faire un tour de tramway avant de partir.\n\n\n\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage2 = "Bien, tu as terminé ta recherche dans le tramway!\nVa voir le quartier général des Toons pour recevoir ta récompense.\n\n\n\n\n\n\n\nLe quartier général des Toons est situé près du centre du terrain de jeux." +NPCForceAcknowledgeMessage3 = "N'oublie pas de faire un tour de tramway.\n\n\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage4 = "Bravo! Tu as terminé ton premier défitoon!\n\n\n\n\n\n\nVa voir le quartier général des Toons pour recevoir ta récompense." +NPCForceAcknowledgeMessage5 = "N'oublie pas ton défitoon!\n\n\n\n\n\n\n\n\n\n\nTu peux trouver des Cogs a vaincre de l'autre côté de tunnels comme celui-ci." +NPCForceAcknowledgeMessage6 = "Félicitations pour avoir vaincu ces Cogs!\n\n\n\n\n\n\n\n\n\nReviens au quartier général des Toons aussi vite que possible." +NPCForceAcknowledgeMessage7 = "N'oublie pas de te faire un(e) ami(e)!\n\n\n\n\n\n\n\nClique sur un autre joueur et utilise le bouton Nouvel(le) ami(e)." +NPCForceAcknowledgeMessage8 = "Super! Tu t'es fait un(e) nouvel(le) ami(e)!\n\n\n\n\n\n\n\n\nTu dois retourner au quartier général des Toons maintenant." +NPCForceAcknowledgeMessage9 = "Tu as bien utilisé le téléphone!\n\n\n\n\n\n\n\n\nRetourne au quartier général des Toons pour demander ta récompense." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Tu as reçu 1 point de lancer! Quand tu en auras 10, tu pourras recevoir un nouveau gag!" +MovieTutorialReward2 = "Tu as reçu 1 point d'éclaboussure! Quand tu en auras 10, tu pourras avoir un nouveau gag!" +MovieTutorialReward3 = "Bon travail! Tu as terminé ton premier défitoon!" +MovieTutorialReward4 = "Va chercher ta récompense au quartier général des Toons!" +MovieTutorialReward5 = "Amuse-toi!" + +# BattleBase.py +Battle_Input_Timeout = 50.0 + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toonique', 'piège', 'leurre', 'tapage', 'lancer', 'éclaboussure', 'chute'] +BattleGlobalNPCTracks = ['rechargement', 'Toons marquent', 'Cogs ratent'] +BattleGlobalAvPropStrings = ( + ('Plume', 'Mégaphone', 'Tube de rouge à lèvres', 'Canne en bambou', 'Poussière de fée', 'Balles de jonglage', 'Plongeon'), + ('Peau de banane', 'Râteau', 'Billes', 'Sable mouvant', 'Trappe', 'TNT', 'Chemin de fer'), + ('Billet de 1 euro', 'Petit aimant', 'Billet de 5 euros', 'Gros aimant', 'Billet de 10 euros', 'Lunettes hypnotiques', 'Présentation'), + ('Sonnette de vélo', 'Sifflet', 'Clairon', 'Klaxon', "Trompe d'éléphant", 'Corne de brume', 'Chanteuse d’opéra'), + ('Petit gâteau', 'Tranche de tarte aux fruits', 'Tranche de tarte à la crème', 'Tarte aux fruits entière', 'Tarte à la crème entière', "Gâteau d'anniversaire", 'Gâteau de mariage'), + ('Fleur à éclabousser', "Verre d'eau", 'Pistolet à eau', "Bouteille d'eau gazeuse", "Lance d'incendie", "Nuage d'orage", 'Geyser'), + ('Pot de fleurs', 'Sac de sable', 'Enclume', 'Gros poids', 'Coffre-fort', 'Piano à queue', 'Toontanic') + ) +BattleGlobalAvPropStringsSingular = ( + ('une plume', 'un mégaphone', 'un tube de rouge à lèvres', 'une canne en bambou', 'de la poussière de fée', 'un jeu de balles de jonglage', 'un Plongeon'), + ('une peau de banane', 'un râteau', 'un jeu de billes', 'un peu de sable mouvant', 'une trappe', 'du TNT', 'un Chemin de fer'), + ('un billet de 1 euro', 'un petit aimant', 'un billet de 5 euros', 'un gros aimant', 'un billet de 10 euros', 'une paire de lunettes hypnotiques', 'une Présentation'), + ('une sonnette de vélo', 'un sifflet', 'un clairon', 'un klaxon', "une trompe d'éléphant", 'une corne de brume', 'une Chanteuse d’opéra'), + ('un petit gâteau', 'une tranche de tarte aux fruits', 'une tranche de tarte à la crème', 'une tarte aux fruits entière', 'une tarte à la crème entière', "un gâteau d'anniversaire", 'un Gâteau de mariage'), + ('une fleur à éclabousser', "un verre d'eau", 'un pistolet à eau', "une bouteille d'eau gazeuse", "une lance d'incendie", "un nuage d'orage", 'un Geyser'), + ('un pot de fleurs', 'un sac de sable', 'une enclume', 'un gros poids', 'un coffre-fort', 'un piano à queue', 'le Toontanic') + ) +BattleGlobalAvPropStringsPlural = ( + ('Plumes', 'Mégaphones', 'Tubes de rouge à lèvres', 'Cannes en bambou', 'Poussières de fée', 'jeux de balles de jonglage', 'Plongeons'), + ('Peaux de bananes', 'Râteaux', 'jeux de billes', 'morceaux de sable mouvant', 'Trappes','bâtons de TNT', 'Chemins de fer'), + ('Billets de 1 euro', 'Petits aimants', 'Billets de 5 euros', 'Gros aimants','Billets de 10 euros', 'Paires de lunettes hypnotiques', 'Présentations'), + ('Sonnettes de vélo', 'Sifflets', 'Clairons', 'Klaxons', "Trompes d'éléphants", 'Cornes de brume', 'Chanteuses d’opéra'), + ('Petits gâteaux', 'Tranches de tarte aux fruits', 'Tranches de tarte à la crème','Tartes aux fruits entières', 'Tartes à la crème entières', "Gâteaux d'anniversaire", 'Gâteaux de mariage'), + ('Fleurs à éclabousser', "Verres d'eau", 'Pistolets à eau',"Bouteilles d'eau gazeuse", "Lances d'incendie", "Nuages d'orage", 'Geysers'), + ('Pots de fleurs', 'Sacs de sable', 'Enclumes', 'Gros poids', 'Coffres-forts','Pianos à queue', 'Paquebots') + ) +BattleGlobalAvTrackAccStrings = ("Moyenne", "Parfaite", "Faible", "Forte", "Moyenne", "Forte", "Faible") +BattleGlobalLureAccLow = "Faible" +BattleGlobalLureAccMedium = "Moyen" + +AttackMissed = "RATÉ" + +NPCCallButtonLabel = 'APPEL' + +# ToontownLoader.py +LoaderLabel = "Chargement..." + +# PlayGame.py +HeadingToHood = "En route %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "En direction de ta propriété..." +HeadingToEstate = "En direction de la propriété de %s..." # avatar name +HeadingToFriend = "En direction de la propriété de l'ami(e) de %s..." # avatar name + +# Hood.py +HeadingToPlayground = "En direction du terrain de jeux..." +HeadingToStreet = "En route %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Revenir en courant au terrain de jeux?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "QUEL TOON ?" +TownBattleChooseAvatarCogTitle = "QUEL "+ string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "RETOUR" + +#firecogpanel +FireCogTitle = "PINK SLIPS LEFT:%s\nFIRE WHICH COG?" +FireCogLowTitle = "PINK SLIPS LEFT:%s\nNOT ENOUGH SLIPS !" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Pas d'contacts à appeler!" +TownBattleSOSWhichFriend = "Appeler quel(le) ami(e)?" +TownBattleSOSNPCFriends = "Toons sauvés" +TownBattleSOSBack = "RETOUR" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "Fire" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "En attente des\nautres joueurs..." +TownSoloBattleWaitTitle = "Patiente..." +TownBattleWaitBack = "RETOUR" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Recherche du Doudou\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s est %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "Tu ne peux pas monter dans le tramway avant que ton rigolmètre ne sourie." +TrolleyTFAMessage = "Tu ne peux pas monter dans le tramway avant que " + Mickey + " ne te le dise." +TrolleyHopOff = lQuit + +# DistributedFishingSpot.py +FishingExit = "Sortie" +FishingCast = "Lancer" +FishingAutoReel = "Moulinet automatique" +FishingItemFound = "Tu as attrapé :" +FishingCrankTooSlow = "Trop\nlent" +FishingCrankTooFast = "Trop\nrapide" +FishingFailure = "Tu n'as rien attrapé!" +FishingFailureTooSoon = "Ne commence pas à faire remonter ta ligne avant de voir une touche. Attends que ton flotteur se mette à s'enfoncer et à remonter rapidement!" +FishingFailureTooLate = "Remonte bien ta ligne avant que le poisson ne se décroche!" +FishingFailureAutoReel = "Le moulinet automatique n'a pas fonctionné cette fois-ci. Tourne la manivelle à la main, juste à la bonne vitesse, pour avoir les meilleurs chances d'attraper quelque chose!" +FishingFailureTooSlow = "Tu as tourné la manivelle trop lentement. Certains poissons sont plus rapides que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingFailureTooFast = "Tu as tourné la manivelle trop rapidement. Certains poissons sont plus lents que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingOverTankLimit = "Ton seau de pêche est plein. Va vendre tes poissons au vendeur de l'animalerie et reviens." +FishingBroke = "Tu n'as plus de bonbons pour appâter! Va faire un tour de tramway ou vends des poissons aux vendeurs de l'animalerie pour avoir d'autres bonbons." +FishingHowToFirstTime = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie maintenant!" +FishingHowToFailed = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie encore maintenant!" +FishingBootItem = "Une vieille chaussure" +FishingJellybeanItem = "%s bonbons" +FishingNewEntry = "Nouvelle espèce!" +FishingNewRecord = "Nouveau record!" + +# FishPoker +FishPokerCashIn = "Encaisser\n%s\n%s" +FishPokerLock = "Bloquer" +FishPokerUnlock = "Débloquer" +FishPoker5OfKind = "5 identiques" +FishPoker4OfKind = "Carré" +FishPokerFullHouse = "Plein" +FishPoker3OfKind = "Brelan" +FishPoker2Pair = "2 paires" +FishPokerPair = "Paire" + +# DistributedTutorial.py +TutorialGreeting1 = "Salut,%s!" +TutorialGreeting2 = "Salut,%s!\nViens par ici!" +TutorialGreeting3 = "Salut,%s!\nViens par ici!\nUtilise les flèches!" +TutorialMickeyWelcome = "Bienvenue à Toontown!" +TutorialFlippyIntro = "Je te présente mon ami " + Flippy + "..." +TutorialFlippyHi = "Salut,%s!" +TutorialQT1 = "Tu peux parler en utilisant ceci." +TutorialQT2 = "Tu peux parler en utilisant ceci.\nClique dessus, puis choisis \"Salut\"." +TutorialChat1 = "Tu peux parler en utilisant l'un de ces boutons." +TutorialChat2 = "Le bouton bleu te permet de chatter avec le clavier." +TutorialChat3 = "Fais attention! La plupart des autres joueurs ne comprendront pas ce que tu dis lorsque tu utilises le clavier." +TutorialChat4 = "Le bouton vert ouvre le %s." +TutorialChat5 = "Tout le monde peut te comprendre si tu utilises le %s." +TutorialChat6 = "Essaie de dire \"salut\"." +TutorialBodyClick1 = "Très bien!" +TutorialBodyClick2 = "Ravi de t'avoir rencontré! Tu veux que nous soyons contacts?" +TutorialBodyClick3 = "Pour devenir ami(e) avec " + Flippy + ", clique sur lui..." +TutorialHandleBodyClickSuccess = "Bon travail!" +TutorialHandleBodyClickFail = "Ce n'est pas ça. Essaie de cliquer juste sur " + Flippy + "..." +TutorialFriendsButton = "Maintenant, clique sur le bouton \"contacts\" sous l'image de " + Flippy + " dans l'angle droit." +TutorialHandleFriendsButton = "Ensuite, clique sur le bouton \"oui\"." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Veux-tu devenir ami(e) avec " + Flippy + "?" +TutorialFriendsPanelMickeyChat = Flippy + " veut bien être ton ami. Clique sur \"OK\" pour terminer." +TutorialFriendsPanelYes = Flippy + " a dit oui!" +TutorialFriendsPanelNo = "Ça n'est pas très gentil!" +TutorialFriendsPanelCongrats = "Bravo! Tu t'es fait ton premier ami." +TutorialFlippyChat1 = "Reviens me voir quand tu seras prêt pour ton premier défitoon!" +TutorialFlippyChat2 = "Je serai à la Mairie de Toontown!" +TutorialAllFriendsButton = "Tu peux voir tous tes contacts en cliquant sur le bouton \"contacts\". Essaye donc..." +TutorialEmptyFriendsList = "Pour l'instant, ta liste est vide parce que " + Flippy + " n'est pas véritablement un joueur." +TutorialCloseFriendsList = "Clique sur le bouton \" Fermer \"\npour faire disparaître la liste." +TutorialShtickerButton = "Le bouton dans l'angle inférieur droit ouvre ton journal de bord. Essaye-le..." +TutorialBook1 = "Le journal contient de nombreuses informations utiles comme cette carte de Toontown." +TutorialBook2 = "Tu peux aussi y voir les progrès de tes défitoons." +TutorialBook3 = "Lorsque tu as fini, clique de nouveau sur le bouton représentant un livre pour le fermer." +TutorialLaffMeter1 = "Tu as aussi besoin de ça..." +TutorialLaffMeter2 = "Tu as aussi besoin de ça...\nC'est ton rigolmètre." +TutorialLaffMeter3 = "Lorsque les " + Cogs + " t'attaquent, il baisse." +TutorialLaffMeter4 = "Lorsque tu es sur les terrains de jeux comme celui-ci, il remonte." +TutorialLaffMeter5 = "Lorsque tu finis des défitoons, tu reçois des récompenses, comme l'augmentation de ta rigo-limite." +TutorialLaffMeter6 = "Fais attention! Si les " + Cogs + " te battent, tu perds tous tes gags." +TutorialLaffMeter7 = "Pour avoir plus de gags, joue aux jeux du tramway." +TutorialTrolley1 = "Suis-moi jusqu'au tramway!" +TutorialTrolley2 = "Monte à bord!" +TutorialBye1 = "Joue à des jeux!" +TutorialBye2 = "Joue à des jeux!\nAchète des gags!" +TutorialBye3 = "Va voir " + Flippy + " quand tu auras fini!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "Tu vas dans le mauvais sens! Va trouver " + Mickey + "!" + +PetTutorialTitle1 = "Le panneau des Doudous" +PetTutorialTitle2 = "Chat rapide des Doudous" +PetTutorialTitle3 = "Catalogue des Doudous" +PetTutorialNext = "Page suivante" +PetTutorialPrev = "Page précédente" +PetTutorialDone = "Terminé" +PetTutorialPage1 = "Clique sur un Doudou pour afficher le panneau des Doudous. Là tu pourras nourrir, cajoler et appeler le Doudou." +PetTutorialPage2 = "Utilise la nouvelle zone 'Animaux familiers' dans le menu de Chat rapide pour que le Doudou fasse un tour. S'il le fait, récompense-le et il s'améliorera!" +PetTutorialPage3 = "Achète de nouveaux tours pour les Doudous dans le catalogue de Clarabelle. De meilleures tours donnent de meilleures tooniques!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Jardinage" +GardenTutorialTitle2 = "Fleurs" +GardenTutorialTitle3 = "Arbres" +GardenTutorialTitle4 = "Comment faire" +GardenTutorialTitle5 = "Statues" +GardenTutorialNext = "Page suivante" +GardenTutorialPrev = "Page précédente" +GardenTutorialDone = "Terminé" +GardenTutorialPage1 = "Embellis ta propriété avec un jardin! Tu peux y planter des fleurs, y faire pousser des arbres, y récolter des gags super puissants et le décorer avec des statues!" +GardenTutorialPage2 = "Les fleurs sont délicates et demandent des recettes très particulières à base de bonbons. Une fois qu'elles ont poussé, tu peux les mettre dans une brouette pour aller les vendre et faire gonfler ton rigolmètre." +GardenTutorialPage3 = "Utilise un gag que tu as dans ton inventaire pour planter un arbre. Après quelques jours, ce gag sera encore plus puissant! N'oublie pas d'en prendre soin, ou tu perdras l'augmentation de puissance." +GardenTutorialPage4 = "Marche jusqu'à ces endroits pour planter, arroser, bêcher ou faire la cueillette dans ton jardin." +GardenTutorialPage5 = "Les statues sont vendues dans le Catalogue vachement branché de Clarabelle. Améliore ton habileté pour avoir accès aux statues les plus extravagantes!" + +# Playground.py +PlaygroundDeathAckMessage = "Les " + Cogs + " ont pris tous tes gags!\n\nTu es triste. Tu ne peux pas quitter le terrain de jeux avant d'avoir retrouvé la joie de vivre." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "Le contremaître de l'usine a été vaincu avant que tu ne le trouves. Tu n'as pas récupéré de pièces de Cogs." + +# MintInterior +ForcedLeaveMintAckMsg = "Le Superviseur de cet étage de la Fabrique à Sous a été vaincu avant que tu ne puisses l'atteindre. Tu n'as pas récupéré de euros Cog." + +# DistributedFactory.py +HeadingToFactoryTitle = "En route %s..." +ForemanConfrontedMsg = "%s est en train de combattre le contremaître de l'usine!" + +# DistributedMint.py +MintBossConfrontedMsg = "%s est en train de combattre le Superviseur!" + +# DistributedStage.py +StageBossConfrontedMsg = "%s se bat contre le juriste!" +stageToonEnterElevator = "%s \nest maintenant dans l'ascenseur" +ForcedLeaveStageAckMsg = "Le juriste a été vaincu avant que tu ne le trouves. Tu n'as pas récupéré de convocations du jury." + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "En attente d'autres joueurs..." +MinigamePleaseWait = "Patiente un peu..." +DefaultMinigameTitle = "Nom du mini jeu" +DefaultMinigameInstructions = "Instructions du mini jeu" +HeadingToMinigameTitle = "En route vers: %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Témoin de puissance" +MinigamePowerMeterTooSlow = "Trop\nlent" +MinigamePowerMeterTooFast = "Trop\nrapide" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Modèle de mini jeu" +MinigameTemplateInstructions = "C'est un modèle de mini jeu. Utilise-le pour créer des mini jeux." + +# DistributedCannonGame.py +CannonGameTitle = "Jeu du canon" +CannonGameInstructions = "Envoie ton Toon dans le château d'eau aussi vite que tu peux. Utilise les flèches du clavier ou la souris pour diriger le canon. Sois rapide et gagne une belle récompense pour tout le monde!" +CannonGameReward = "RÉCOMPENSE" + +# DistributedTwoDGame.py +TwoDGameTitle = "Toon Escape" +TwoDGameInstructions = "Echappe-toi de cette" + Cog + "tannière dès que possible. Utilise les flèches du clavier pour courir/sauter et la touche Ctrl pour faire gicler un" + Cog + ". Ramasse" + Cog + "les trésors pour gagner encore plus de points." +TwoDGameElevatorExit = "SORTIE" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Tir à la corde" +TugOfWarInstructions = "Appuie alternativement sur les flèches gauche et droite à la vitesse qu'il faut pour aligner la barre verte avec la ligne rouge. N'appuie pas trop rapidement ou trop lentement, tu pourrais finir dans l'eau!" +TugOfWarGameGo = "PARTEZ!" +TugOfWarGameReady = "Prêt..." +TugOfWarGameEnd = "Bien joué!" +TugOfWarGameTie = "Égalité!" +TugOfWarPowerMeter = "Témoin de puissance" + +# DistributedPatternGame.py +PatternGameTitle = "Imite "+ Minnie +PatternGameInstructions = Minnie +" va te montrer une suite de pas de danse. "+ \ + "Essaie de reproduire la danse de "+ Minnie + " comme tu la vois en utilisant les flèches!" +PatternGameWatch = "Regarde ces pas de danse..." +PatternGameGo = "PARTEZ!" +PatternGameRight = "Bien, %s!" +PatternGameWrong = "Aïe!" +PatternGamePerfect = "C'était parfait, %s!" +PatternGameBye = "Merci d'avoir joué!" +PatternGameWaitingOtherPlayers = "En attente d'autres joueurs..." +PatternGamePleaseWait = "Patiente un peu..." +PatternGameFaster = "Tu as été\nplus rapide!" +PatternGameFastest = "Tu as été\nle(la) plus rapide!" +PatternGameYouCanDoIt = "Allez!\nTu peux y arriver!" +PatternGameOtherFaster = "\na été plus rapide!" +PatternGameOtherFastest = "\na été le(la) plus rapide!" +PatternGameGreatJob = "Bon travail!" +PatternGameRound = "Partie %s!" # Round 1! Round 2! .. +PatternGameImprov = "Bien joué ! Maintenant monte !" + +# DistributedRaceAI.py +WaitingForJoin = 90 + +# DistributedRaceGame.py +RaceGameTitle = "Jeu de l'oie" +RaceGameInstructions = "Clique sur un nombre. Choisis bien! Tu n'avances que si personne d'autre n'a choisi le même nombre." +RaceGameWaitingChoices = "Attente du choix des autres joueurs..." +RaceGameCardText = "%(name)s tire: %(reward)s" +RaceGameCardTextBeans = "%(name)s reçoit: %(reward)s" +RaceGameCardTextHi1 = "%(name)s est un Toon fabuleux!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " avance d'une case" +RaceGameForwardTwoSpaces = " avance de 2 cases" +RaceGameForwardThreeSpaces = " avance de 3 cases" +RaceGameBackOneSpace = " recule d'une case" +RaceGameBackTwoSpaces = " recule de 2 cases" +RaceGameBackThreeSpaces = " recule de 3 cases" +RaceGameOthersForwardThree = " tous les autres avancent\nde 3 cases" +RaceGameOthersBackThree = "tous les autres reculent\nde 3 cases" +RaceGameInstantWinner = "Gagnant!" +RaceGameJellybeans2 = "2 bonbons" +RaceGameJellybeans4 = "4 bonbons" +RaceGameJellybeans10 = "10 bonbons!" + +# DistributedRingGame.py +RingGameTitle = "Jeu des anneaux" +# color +RingGameInstructionsSinglePlayer = "Essaie de nager en passant dans autant d'anneaux %s que tu pourras. Utilise les flèches pour nager." +# color +RingGameInstructionsMultiPlayer = "Essaie de nager en passant dans les anneaux %s. Les autres joueurs essaieront de passer les anneaux des autres couleurs. Utilise les flèches pour nager." +RingGameMissed = "RATÉ" +RingGameGroupPerfect = "GROUPE\nPARFAIT!!" +RingGamePerfect = "PARFAIT!" +RingGameGroupBonus = "BONUS DE GROUPE" + +# RingGameGlobals.py +ColorRed = "rouges" +ColorGreen = "verts" +ColorOrange = "orange" +ColorPurple = "violets" +ColorWhite = "blancs" +ColorBlack = "noirs" +ColorYellow = "jaunes" + +# DistributedDivingGame.py +DivingGameTitle = "Chasse aux trésors aquatique" +# color +DivingInstructionsSinglePlayer = "Les trésors apparaissent au fond du lac. Utilise les flèches du clavier pour nager. Évite les poissons et rapporte les trésors dans le bateau." +# color +DivingInstructionsMultiPlayer = "Les trésors apparaissent au fond du lac. Utilise les flèches de ton clavier pour nager. Travailler ensemble pour rapporter les trésors dans le bateau." +DivingGameTreasuresRetrieved = "Recherché Trésors" + +#Distributed Target Game +TargetGameTitle = "Jeu du parapluie" +TargetGameInstructionsSinglePlayer = "Atterris sur les cibles pour marquer des points" +TargetGameInstructionsMultiPlayer = "Atterris sur les cibles pour marquer des points" +TargetGameBoard = "Manche %s - Garder le meilleur score" +TargetGameCountdown = "Lancement forcé dans %s secondes" +TargetGameCountHelp = "Touche dièse et flèches droite et gauche pour allumer, stop pour lancer" +TargetGameFlyHelp = "Appuyer pour ouvrir le parapluie" +TargetGameFallHelp = "Utilise les flèches du clavier pour atterrir sur les cibles" +TargetGameBounceHelp = "En rebondissant, tu peux t'écarter de la cible" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nToi : %s" +PhotoGameScoreBlank = "Score : %s" +PhotoGameScoreOther = ""#"Score: %s\n%s" +PhotoGameScoreYou = "\nMeilleur bonus !" + + +# DistributedTagGame.py +TagGameTitle = "Jeu du chat" +TagGameInstructions = "Récupère les trésors. Tu ne peux pas récupérer les trésors quand tu es chat!" +TagGameYouAreIt = "Tu es chat!" +TagGameSomeoneElseIsIt = "%s est chat!" + +# DistributedMazeGame.py +MazeGameTitle = "Jeu du labyrinthe" +MazeGameInstructions = "Récupère les trésors. Essaie de les avoir tous, mais fais attention aux " + Cogs + "!" + +# DistributedCatchGame.py +CatchGameTitle = "Jeu du verger" +CatchGameInstructions = "Attrape des %(fruit)s, autant que tu peux. Attention aux " + Cogs + ", et essaie de ne pas attraper des %(badThing)s!" +CatchGamePerfect = "PARFAIT!" +CatchGameApples = 'pommes' +CatchGameOranges = 'oranges' +CatchGamePears = 'poires' +CatchGameCoconuts = 'noix de coco' +CatchGameWatermelons = 'pastèques' +CatchGamePineapples = 'ananas' +CatchGameAnvils = 'enclumes' + +# DistributedPieTossGame.py +PieTossGameTitle = "Jeu du lancer de tartes" +PieTossGameInstructions = "Envoie des tartes dans les cibles." + +# DistributedPhotoGame.py +PhotoGameInstructions = "Prends des photos correspondant aux Toons apparaissant en bas. Oriente l'appareil photo avec la souris et fais un clic gauche pour prendre une photo. Appuie sur la touche Ctrl pour faire un zoom avant/arrière, et déplace-toi avec les flèches du clavier. Les photos les mieux classées rapportent le plus de points." +PhotoGameTitle = "Délire photo" +PhotoGameFilm = "FILM" +PhotoGameScore = "Score par équipe : %s\n\nMeilleures photos: %s\n\nScore total : %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + "Voleur" +CogThiefGameInstructions = "Empêche-le" + Cogs + "de voler nos tonneaux de gags ! Appuie sur la touche Ctrl pour lancer une tarte. Utilise les flèches du clavier pour te déplacer. Astuce : tu peux te déplacer en diagonale." +CogThiefBarrelsSaved = "%(num)d Tonneaux\nsauvés !" +CogThiefBarrelSaved = "%(num)d Tonneaux\nsauvés !" +CogThiefNoBarrelsSaved = "Aucun tonneau\nsauvé" +CogThiefPerfect = "PARFAIT !" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JOUER" + +# Purchase.py +GagShopName = "La boutique à gags de Dingo" +GagShopPlayAgain = "REJOUER\n" +GagShopBackToPlayground = "RETOUR AU\nTERRAIN DE JEUX" +GagShopYouHave = "Tu as %s à dépenser" +GagShopYouHaveOne = "Tu as 1 bonbon à dépenser" +GagShopTooManyProps = "Désolé, tu as trop d'accessoires" +GagShopDoneShopping = "ACHATS\n TERMINÉS" +# name of a gag +GagShopTooManyOfThatGag = "Désolé, tu as déjà assez de %s" +GagShopInsufficientSkill = "Tu n'es pas encore assez habile pour cela" +# name of a gag +GagShopYouPurchased = "Tu as acheté %s" +GagShopOutOfJellybeans = "Désolé, tu n'as plus de bonbons!" +GagShopWaitingOtherPlayers = "En attente des autres joueurs..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s est déconnecté(e)" +GagShopPlayerExited = "%s est parti(e)" +GagShopPlayerPlayAgain = "Jouer encore" +GagShopPlayerBuying = "Achat en cours" + +# MakeAToon.py +GenderShopQuestionMickey = "Pour faire un garçon Toon, clique ici!" +GenderShopQuestionMinnie = "Pour faire une fille Toon, clique ici!" +GenderShopFollow = "Suis-moi!" +GenderShopSeeYou = "À plus tard!" +GenderShopBoyButtonText = "Garçon" +GenderShopGirlButtonText = "Fille" + +# BodyShop.py +BodyShopHead = "Tête" +BodyShopBody = "Corps" +BodyShopLegs = "Jambes" + +# ColorShop.py +ColorShopHead = "Tête" +ColorShopBody = "Corps" +ColorShopLegs = "Jambes" +ColorShopToon = "Toon" +ColorShopParts = "Parties" +ColorShopAll = "Tout" + +# ClothesShop.py +ClothesShopShorts = "Short" +ClothesShopShirt = "Chemise" +ClothesShopBottoms = "Bas" + +# MakeAToon +MakeAToonDone = "Fini" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Retour" +CreateYourToon = "Clique sur les flèches pour créer ton Toon." +CreateYourToonTitle = "Crée ton Toon" +CreateYourToonHead = "Clique sur les flèches \"tête\" pour choisir différents animaux." +MakeAToonClickForNextScreen = "Clique sur la flèche ci-dessous pour aller à l'écran suivant." +PickClothes = "Clique sur les flèches pour choisir des vêtements!" +PickClothesTitle = "Choisis tes vêtements" +PaintYourToon = "Clique sur les flèches pour peindre ton Toon!" +PaintYourToonTitle = "Peins ton Toon" +MakeAToonYouCanGoBack = "Tu peux aussi retourner en arrière pour changer ton corps!" +MakeAFunnyName = "Choisis un nom amusant pour ton Toon!" +MustHaveAFirstOrLast1 = "Ton Toon devrait avoir un prénom ou un nom de famille, tu ne penses pas?" +MustHaveAFirstOrLast2 = "Tu ne veux pas que ton Toon ait de prénom ou de nom de famille ?" +ApprovalForName1 = "C'est ça, ton Toon mérite un super nom!" +ApprovalForName2 = "Les noms Toon sont les meilleurs noms!" +MakeAToonLastStep = "Dernière étape avant d'aller à Toontown!" +PickANameYouLike = "Choisis un nom que tu aimes!" +NameToonTitle = "Donne un nom à ton Toon" +TitleCheckBox = "Titre" +FirstCheckBox = "Prénom" +LastCheckBox = "Nom" +RandomButton = "Aléatoire" +NameShopSubmitButton = "Envoyer" +TypeANameButton = "Entre un nom" +TypeAName = "Tu n'aimes pas ces noms?\nClique ici -->" +PickAName = "Essaie le jeu Choisis-un-nom!\nClique ici -->" +PickANameButton = "Choisis un nom" +RejectNameText = "Ce nom n'est pas autorisé. Essaie encore." +WaitingForNameSubmission = "Envoi de ton nom..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_french.txt" +PetshopUnknownName = "Nom:???" +PetshopDescGender = "Sexe:\t%s" +PetshopDescCost = "Coûte:\t%s bonbons" +PetshopDescTrait = "Caractère:\t%s" +PetshopDescStandard = "Standard" +PetshopCancel = lCancel +PetshopSell = "Vendre tes poissons" +PetshopAdoptAPet = "Adopter un Doudou" +PetshopReturnPet = "Rapporter ton Doudou" +PetshopAdoptConfirm = "Adopter %s pour %d bonbons?" +PetshopGoBack = "Retourner" +PetshopAdopt = "Adopter" +PetshopReturnConfirm = "Rapporter %s?" +PetshopReturn = "Rapporter" +PetshopChooserTitle = "LES DOUDOUS DU JOUR" +PetshopGoHomeText = 'Est-ce que tu veux aller dans ta propriété pour jouer avec ton nouveau Doudou?' + +# NameShop.py +NameShopNameMaster = "NameMaster_french.txt" +NameShopPay = "Inscris-toi!" +NameShopPlay = "Essai gratuit" +NameShopOnlyPaid = "Seuls les utilisateurs payants\npeuvent donner un nom à leurs Toons.\nJusqu'à ce que tu t'inscrives,\nton nom sera\n" +NameShopContinueSubmission = "Continuer l'envoi" +NameShopChooseAnother = "Choisir un autre nom" +NameShopToonCouncil = "Le Conseil de Toontown\nva examiner ton\nnom. "+ \ + "L'examen peut\nprendre quelques jours.\nPendant que tu attends,\nton nom sera\n " +PleaseTypeName = "Entre ton nom:" +AllNewNames = "Tous les noms\ndoivent être approuvés\npar le Conseil de Toontown." +NameMessages = "Be creative and remember:\nno Disney-related names, please." +NameShopNameRejected = "Le nom que tu as\nenvoyé a été refusé." +NameShopNameAccepted = "Félicitations!\nLe nom que tu as\nenvoyé a\nété accepté!" +NoPunctuation = "Tu ne peux pas utiliser de signes de ponctuation dans ton nom!" +PeriodOnlyAfterLetter = "Tu peux utiliser un point dans ton nom, mais seulement après une lettre." +ApostropheOnlyAfterLetter = "Tu peux utiliser une apostrophe dans ton nom, mais seulement après une lettre." +NoNumbersInTheMiddle = "Les caractères numériques ne peuvent pas apparaître au milieu d'un mot." +ThreeWordsOrLess = "Ton nom doit comporter trois mots maximum." +CopyrightedNames = ( + "Mickey", + "Mickey Mouse", + "Mickey Mouse", + "Minnie Mouse", + "Minnie", + "Minnie Mouse", + "Minnie Mouse", + "Donald", + "Donald Duck", + "Donald Duck", + "Pluto", + "Dingo", + ) +NumToColor = ['Blanc', 'Pêche', 'Rouge vif', 'Rouge', 'Bordeaux', + 'Terre de Sienne', 'Brun', 'Brun clair', 'Corail', 'Orange', + 'Jaune', 'Crème', 'Jaune-vert', 'Citron vert', 'Vert marin', + 'Vert', 'Bleu clair', 'Turquoise', 'Bleu', + 'Pervenche', 'Bleu roi', 'Bleu ardoise', 'Violet', + 'Lavande', 'Rose'] +AnimalToSpecies = { + 'dog' : 'Chien', + 'cat' : 'Chat', + 'mouse' : 'Souris', + 'horse' : 'Cheval', + 'rabbit' : 'Lapin', + 'duck' : 'Canard', + 'monkey' : 'Singe', + 'bear' : 'Ours', + 'pig' : 'Cochon' + } +NameTooLong = "Ce nom est trop long. Essaie encore." +ToonAlreadyExists = "Tu as déjà un Toon qui s'appelle %s!" +NameAlreadyInUse = "Ce nom est déjà utilisé!" +EmptyNameError = "Tu dois indiquer un nom d'abord." +NameError = "Désolé. Ce nom ne pourra pas convenir." + +# NameCheck.py +NCTooShort = 'Ce nom est trop court.' +NCNoDigits = 'Ton nom ne peut pas contenir de chiffres.' +NCNeedLetters = 'Chaque mot de ton nom doit contenir des lettres.' +NCNeedVowels = 'Chaque mot de ton nom doit contenir des voyelles.' +NCAllCaps = 'Ton nom ne peut pas être entièrement en majuscules.' +NCMixedCase = 'Ton nom a trop de majuscules.' +NCBadCharacter = "Ton nom ne peut pas contenir le caractère \"%s\"" +NCGeneric = 'Désolé, ce nom ne pourra pas convenir.' +NCTooManyWords = 'Ton nom ne peut pas comporter plus de quatre mots.' +NCDashUsage = ("Les tirets ne peuvent être utilisés que pour relier deux mots ensemble." + "(comme dans \"Bou-Bou\").") +NCCommaEdge = "Ton nom ne peut pas commencer ou se terminer par une virgule." +NCCommaAfterWord = "Tu ne peux pas commencer un mot par une virgule." +NCCommaUsage = ("Ce nom n'utilise pas les virgules correctement. Les virgules doivent" + "assembler deux mots, comme dans le nom \"Dr Couac, médecin\"." + "Les virgules doivent aussi être suivies d'un espace.") +NCPeriodUsage = ("Ce nom n'utilise pas les points correctement. Les points sont" + "seulement autorisés dans des mots tels que \"M.\",\"doct.\",\"prof.\", etc.") +NCApostrophes = "Ton nom a trop d'apostrophes." + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+" : Les " + Cogs + " ont repris un des bâtiments que tu avais sauvés!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Tu as besoin de plus de temps pour réfléchir ?' +STOREOWNER_GOODBYE = 'À plus tard!' +STOREOWNER_NEEDJELLYBEANS = 'Tu dois faire un tour de tramway pour avoir des bonbons.' +STOREOWNER_GREETING = 'Choisis ce que tu veux acheter.' +STOREOWNER_BROWSING = "Tu peux regarder, mais tu auras besoin d'un ticket d'habillement pour acheter." +STOREOWNER_NOCLOTHINGTICKET = "Tu as besoin d'un ticket d'habillement pour acheter des vêtements." +# translate +STOREOWNER_NOFISH = "Reviens ici pour vendre des poissons à l'animalerie en échange de bonbons." +STOREOWNER_THANKSFISH = "Merci! L'animalerie va les adorer. Au revoir!" +STOREOWNER_THANKSFISH_PETSHOP = "Ce sont de beaux spécimens! Merci." +STOREOWNER_PETRETURNED = "Ne t'inquiete pas. Nous trouverons une bonne maison pour ton Doudou." +STOREOWNER_PETADOPTED = "Félicitations pour ton nouveau Doudou! Tu peux jouer avec lui dans ta propriété." +STOREOWNER_PETCANCELED = "N'oublie pas: si tu vois un Doudou qui te plaît, adopte-le avant que quelqu'un d'autre ne le fasse!" + +STOREOWNER_NOROOM = "Hmm...tu devrais faire de la place dans ton placard avant d'acheter de nouveaux vêtements.\n" +STOREOWNER_CONFIRM_LOSS = "Ton placard est plein. Tu vas perdre les vêtements que tu portais." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Oh là là! Tu as trouvé %s sur %s poissons. Ça mérite un trophée et une rigol-augmentation!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": Une invasion de Cogs a commencé!!!" +SuitInvasionBegin2 = lToonHQ+": Les %s ont pris Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": L'invasion des %s est terminée!!!" +SuitInvasionEnd2 = lToonHQ+": Les Toons nous ont sauvés une fois de plus!!!" +SuitInvasionUpdate1 = lToonHQ+": L'invasion de Cogs en est à %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": Nous devons battre ces %s!!!" +SuitInvasionBulletin1 = lToonHQ+": Il y a une invasion de Cogs en cours!!!" +SuitInvasionBulletin2 = lToonHQ+": Les %s ont pris Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Armée de Toons" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown compte un nouveau citoyen! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMickey_2 = "Bien sûr, %s!" +QuestScriptTutorialMickey_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" +QuestScriptTutorialMickey_4 = "Viens ici! Utilise les flèches pour te déplacer." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown compte une nouvelle citoyenne! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMinnie_2 = "Bien sûr, %s!" +QuestScriptTutorialMinnie_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" + +QuestScript101_1 = "Ce sont les COGS. Ce sont des robots qui essaient de prendre Toontown." +QuestScript101_2 = "Il y a différentes sortes de COGS et..." +QuestScript101_3 = "...ils transforment de bons bâtiments Toon..." +QuestScript101_4 = "...en affreuses bâtisses Cog!" +QuestScript101_5 = "Mais les COGS ne comprennent pas les blagues!" +QuestScript101_6 = "Un bon gag les arrête." +QuestScript101_7 = "Il y a des quantités de gags; prends ceux-là pour commencer." +QuestScript101_8 = "Oh! Tu as aussi besoin d'un rigolmètre!" +QuestScript101_9 = "Si ton rigolmètre descend trop bas, tu seras triste!" +QuestScript101_10 = "Un Toon heureux est un Toon en bonne santé!" +QuestScript101_11 = "OH NON! Il y a un COG devant ma boutique!" +QuestScript101_12 = "AIDE-MOI, S'IL TE PLAÎT! Va vaincre ce COG!" +QuestScript101_13 = "Voilà ton premier défitoon!" +QuestScript101_14 = "Dépêche-toi! Va battre ce Laquaistic!" + +QuestScript110_1 = "Bon travail pour avoir vaincu ce Laquaistic. Je vais te donner un journal de bord..." +QuestScript110_2 = "Ce journal est plein de choses intéressantes." +QuestScript110_3 = "Ouvre-le, et je vais te montrer." +QuestScript110_4 = "La carte montre où tu as été." +QuestScript110_5 = "Tourne la page pour voir tes gags..." +QuestScript110_6 = "Oh oh! Tu n'as pas de gags! Je vais te donner un défi." +QuestScript110_7 = "Tourne la page pour voir tes défis." +QuestScript110_8 = "Fais un tour de tramway, et gagne des bonbons pour acheter des gags!" +QuestScript110_9 = "Pour aller jusqu'au tramway, sors par la porte qui est derrière moi et va jusqu'au terrain de jeux." +QuestScript110_10 = "Maintenant, ferme le livre et trouve le tramway!" +QuestScript110_11 = "Retourne au QG des Toons quand tu as fini. Au revoir!" + +QuestScriptTutorialBlocker_1 = "Bien le bonjour!" +QuestScriptTutorialBlocker_2 = "Bonjour ?" +QuestScriptTutorialBlocker_3 = "Oh! Tu ne sais pas utiliser le Chat rapide!" +QuestScriptTutorialBlocker_4 = "Clique sur le bouton pour dire quelque chose." +QuestScriptTutorialBlocker_5 = "Très bien!\aLà où tu vas, il y a plein de Toons à qui parler." +QuestScriptTutorialBlocker_6 = "Si tu veux chatter avec tes contacts à l'aide du clavier, tu peux utiliser un autre bouton." +QuestScriptTutorialBlocker_7 = "Ça s'appelle le bouton \"Chat\". Tu dois être officiellement citoyen de Toontown pour l'utiliser." +QuestScriptTutorialBlocker_8 = "Bonne chance! À plus tard!" + +""" +GagShopTut + +Tu gagneras aussi la possibilité d'utiliser d'autres types de gags. + +""" + +QuestScriptGagShop_1 = "Bienvenue à la Boutique à gags!" +QuestScriptGagShop_1a = "C'est là que viennent les Toons pour acheter des gags qu'ils utiliseront contre les Cogs." +#QuestScriptGagShop_2 = "Ce pot indique combien de bonbons tu as." +#QuestScriptGagShop_3 = "Pour acheter un gag, clique sur le bouton " Gag ". Essaie maintenant!" +QuestScriptGagShop_3 = "Pour acheter des gags, clique sur les boutons de gag. Essaie d'en avoir maintenant!" +QuestScriptGagShop_4 = "Super! Tu peux utiliser ces gags lors des combats contre les Cogs." +QuestScriptGagShop_5 = "Voila un aperçu des gags avancés de lancer et d'éclaboussure..." +QuestScriptGagShop_6 = "Quand tu as fini d'acheter des gags, clique sur ce bouton pour retourner au terrain de jeu." +QuestScriptGagShop_7 = "Normalement, tu peux utiliser ce bouton pour jouer à un autre jeu du tramway..." +QuestScriptGagShop_8 = "...mais tu n'as pas le temps de faire un autre jeu maintenant. On t'attend au quartier général des Toons!" + +QuestScript120_1 = "Bien, tu as trouvé le tramway!\aAu fait, as-tu rencontré Bob le Banquier ?\aIl aime bien les sucreries.\aPourquoi n'irais-tu pas te présenter en lui emportant ce sucre d'orge comme cadeau?" +QuestScript120_2 = "Bob le Banquier est dans la banque de Toontown." + +QuestScript121_1 = "Miam, merci pour ce sucre d'orge.\aDis donc, si tu peux m'aider, je te donnerai une récompense.\aCes Cogs ont volé les clés de mon coffre. Va battre des Cogs pour trouver une clé volée.\aQuand tu auras trouvé une clé, ramène-la moi." + +QuestScript130_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai reçu un paquet pour le Professeur Pete aujourd'hui.\aÇa doit être la nouvelle craie qu'il a commandée.\aPeux-tu lui apporter s'il te plaît ?\aIl est dans l'école." + +QuestScript131_1 = "Oh, merci pour la craie.\aQuoi?!?\aCes Cogs ont volé mon tableau. Va vaincre des Cogs pour retrouver le tableau qu'ils m'ont volé.\aQuand tu l'auras trouvé, ramène-le moi." + +QuestScript140_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai un ami, Larry le Libraire, qui est un rat de bibliothèque.\aJ'ai pris ce livre pour lui la dernière fois que j'ai été aux quais Donald.\aPourrais-tu lui apporter ? Il est à la bibliothèque, d'habitude." + +QuestScript141_1 = "Oh, oui, ce livre complète presque ma collection.\aVoyons ça...\aAh, oh...\aMais où est-ce que j'ai mis mes lunettes?\aJe les avais juste avant que ces Cogs ne prennent mon bâtiment.\aVa vaincre des Cogs pour retrouver les lunettes qu'ils m'ont volées.\aQuand tu les auras retrouvées, reviens me voir pour avoir une récompense." + +QuestScript145_1 = "Je vois que tu n'as pas eu de problèmes avec le tramway!\aÉcoute, les Cogs ont volé notre brosse à tableaux.\aVa dans les rues et combats les Cogs jusqu'a ce que tu retrouves la brosse.\aPour atteindre les rues, passe par un des tunnels comme celui-ci :" +QuestScript145_2 = "Quand tu auras retrouvé notre brosse, ramene-la ici.\aN'oublie pas : si tu as besoin de gags, va faire un tour de tramway.\aDe meme, si tu as besoin de récupérer des rigolpoints, ramasse des cônes de glace sur le terrain de jeu." + +QuestScript150_1 = "Oh... le prochain défi pourrait être trop difficile pour que tu le fasses tout(e) seul(e)!" +QuestScript150_2 = "Pour te faire des contacts, trouve un autre joueur et utilise le bouton Nouvel ami." +QuestScript150_3 = "Une fois que tu t'es fait un(e) ami(e), reviens ici." +QuestScript150_4 = "Certains défis sont trop difficiles pour un Toon seul!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignorer" + +SellbotBossName = "Premier Vice-\nPrésident" +CashbotBossName = "Vice-\nPrésident" +LawbotBossName = "Chief Justice" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade %s. Félicitations!" +BossCogDoobersAway = { 's' : "Va! Et réalise cette vente!" } +BossCogWelcomeToons = "Bienvenue aux nouveaux Cogs!" +BossCogPromoteToons = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade %s. Félicitations!" +CagedToonInterruptBoss = "Hé! Hou! Hé là-bas!" +CagedToonRescueQuery = "Alors les Toons, vous êtes venus me sauver ?" +BossCogDiscoverToons = "Eh? Des Toons! Déguisés!" +BossCogAttackToons = "À l'attaque!!" +CagedToonDrop = [ + "Bon travail! Tu l'épuises!", + "Ne le lâchez pas! Il va s'enfuir!", + "Vous êtes super les copains!", + "Fantastique! Vous l'avez presque maintenant!", + ] +CagedToonPrepareBattleTwo = "Attention, il essaie de s'enfuir!\aAidez-moi, tout le monde - montez jusque là et arrêtez-le!" +CagedToonPrepareBattleThree = "Youpi, je suis presque libre!\aMaintenant vous devez attaquer le vice-président des Cogs directement.\aJ'ai tout un lot de tartes que vous pouvez utiliser!\aSautez en l'air et touchez le fond de ma cage, je vous donnerai des tartes.\aAppuyez sur la touche \"Inser\" pour lancer les tartes une fois que vous les avez!" +BossBattleNeedMorePies = "Vous avez besoin de plus de tartes!" +BossBattleHowToGetPies = "Sautez en l'air pour toucher la cage et avoir des tartes." +BossBattleHowToThrowPies = "Appuyez sur la touche \"Inser\" pour lancer les tartes!" +CagedToonYippee = "Génial!" +CagedToonThankYou = "C'est super d'être libre!\aMerci pour toute votre aide!\aJe suis à votre service.\aSi jamais vous avez besoin d'aide pour un combat, vous pouvez m'appeler!\aCliquez simplement sur le bouton SOS pour m'appeler." +CagedToonPromotion = "\aDis donc - ce vice-président Cog a laissé derrière lui les papiers de ta promotion.\aJe vais les envoyer pour toi en sortant, pour que tu aies ta promotion!" +CagedToonLastPromotion = "\aWaou, tu as atteint le niveau %s sur ton costume de Cog!\aLes Cogs ne montent pas en grade plus haut que ça.\aTu ne peux plus monter ton costume de Cog en grade, mais tu peux évidemment continuer à sauver des Toons!" +CagedToonHPBoost = "\aTu as sauvé beaucoup de Toons dans ce QG.\aLe Conseil de Toontown a décidé de te donner un autre rigolpoint. Félicitations!" +CagedToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aDe la part du Conseil de Toontown, merci d'être revenu(e) sauver encore plus de Toons!" +CagedToonGoodbye = "À la prochaine!" + + +CagedToonBattleThree = { + 10: "Joli saut, %(toon)s. Voilà quelques tartes!", + 11: "Salut, %(toon)s! Prenez des tartes!", + 12: "Hé là,%(toon)s! Vous avez des tartes maintenant!", + + 20: "Hé, %(toon)s! Sautez jusqu'à ma cage et prenez des tartes à lancer!", + 21: "Hé, %(toon)s! Utilisez la touche \"Ctrl\" pour sauter et toucher ma cage!", + + 100: "Appuyez sur la touche \"Inser\" pour lancer une tarte!", + 101: "Le compteur bleu montre à quelle hauteur ta tarte va monter.", + 102: "Essaie d'abord de lancer une tarte sous son châssis pour bousiller son mécanisme.", + 103: "Attends que la porte s'ouvre, et lance une tarte à l'intérieur.", + 104: "Lorsqu'il est étourdi, frappe-le au visage ou au torse pour le renverser!", + 105: "Tu sauras que tu l'as frappé comme il faut quand tu verras une tache de couleur.", + 106: "Si tu frappes un Toon avec une tarte, cela donne à ce Toon un rigolpoint!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "Ça suffit. J'en ai assez de ces Toons si énervants!" +CashbotBossOuttaHere = "J'ai un train à prendre!" +ResistanceToonName = "Inès Pionne" +ResistanceToonCongratulations = "Tu y es arrivé(e)! Félicitations!\aTu es un membre de valeur de la Résistance!\aVoici une phrase très spéciale que tu peux utiliser en cas de situation difficile :\a%s\aQuand tu la prononces, %s.\aMais tu ne peux l'utiliser qu'une seule fois, alors choisis bien ton moment!" +ResistanceToonToonupInstructions = "Tous les Toons qui sont près de toi vont gagner %s rigolpoints." +ResistanceToonToonupAllInstructions = "Tous les Toons qui sont près de toi vont gagner un renouvellement de tout leur stock de rigolpoints." +ResistanceToonMoneyInstructions = "Tous les Toons qui sont près de toi vont gagner %s bonbons." +ResistanceToonMoneyAllInstructions = "Tous les Toons qui sont près de toi vont remplir leurs pots de bonbons." +ResistanceToonRestockInstructions = "Tous les Toons qui sont près de toi vont compléter leur stock de \"%s\" gags." +ResistanceToonRestockAllInstructions = "Tous les Toons qui sont près de toi vont compléter entièrement leur stock de gags." + +ResistanceToonLastPromotion = "\aWaouh, tu as atteint le niveau %s de ton costume de Cog!\aLes Cogs ne vont jamais plus haut que ce niveau.\aTu ne peux plus rien ajouter à ton costume de Cog mais tu peux bien sûr continuer à travailler pour la Résistance!" +ResistanceToonHPBoost = "\aTu as beaucoup fait pour la Résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations!" +ResistanceToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aDe la part du Conseil des Toons, merci d'être revenu pour secourir encore plus de Toons!" + +CashbotBossCogAttack = "Attrapez-les!!!" +ResistanceToonWelcome = "Ça y est, tu y es arrivé! Suis-moi jusqu'au coffre-fort principal avant que le Vice-Président ne nous trouve!" +ResistanceToonTooLate = "Zut alors! Nous arrivons trop tard!" +CashbotBossDiscoverToons1 = "Ah-AH!" +CashbotBossDiscoverToons2 = "Il me semblait bien que ça sentait le Toon par ici! Imposteurs!" +ResistanceToonKeepHimBusy = "Occupe-le! Je vais préparer un piège!" +ResistanceToonWatchThis = "Regarde ça!" +CashbotBossGetAwayFromThat = "Eh! Ne touche pas à ça!" +ResistanceToonCraneInstructions1 = "Prends le contrôle d'un aimant en montant sur un podium." +ResistanceToonCraneInstructions2 = "Utilise les flèches de ton clavier pour déplacer la grue et appuie sur la touche Ctrl pour attraper un objet." +ResistanceToonCraneInstructions3 = "Attrape un coffre-fort avec un aimant et fais tomber le casque de sécurité du Vice-Président." +ResistanceToonCraneInstructions4 = "Une fois que le casque est tombé, prends un goon désactivé et frappe-le à la tête!" +ResistanceToonGetaway = "Eek! Courons!" +CashbotCraneLeave = "Quitter la grue" +CashbotCraneAdvice = "Utilise les flèches de ton clavier pour déplacer la grue au-dessus." +CashbotMagnetAdvice = "Maintiens la touche Ctrl enfoncée pour attraper des objets." +CashbotCraneLeaving = "En train de quitter la grue" + +MintElevatorRejectMessage = "Tu ne peux pas entrer dans les Fabriques à Sous avant d'avoir complété ton %s costume de Cog." +BossElevatorRejectMessage = "Tu ne peux pas monter dans cet ascenseur avant d'avoir gagné une promotion." +NotYetAvailable = "Cet ascenseur n'est pas encore disponible." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Meuble" +PaintingTypeName = "Tableau" +ClothingTypeName = "Vêtement" +ChatTypeName = "Phrase de Chat rapide" +EmoteTypeName = "Leçons de comédie" +BeanTypeName = "Bonbons" +PoleTypeName = "Canne à pêche" +WindowViewTypeName = "Vue de la fenêtre" +PetTrickTypeName = "Entraînement du Doudou" +GardenTypeName = "Matériaux de jardinage" +RentalTypeName = "Article à louer" +GardenStarterTypeName = "Kit de jardinage" +NametagTypeName = "Badge" + +#rental names +RentalHours = "Heures" +RentalOf = "De" +RentalCannon = "Canons !" +RentalGameTable = "Table de jeu !" +RentalTime = "Heures de" + +EstateCannonGameEnd = "La location du jeu du canon est terminée." +GameTableRentalEnd = "La location de la table de jeu est terminée." + +MessageConfirmRent = "Commencer à louer? Annule pour enregistrer la location pour plus tard" +MessageConfirmGarden = "Veux-tu vraiment commencer un jardin?" + +#nametag Names +NametagPaid = "Badge Citoyen" +NametagAction = "Badge Action" +NametagFrilly = "Badge à fanfreluches" + +FurnitureYourOldCloset = "ton ancienne armoire" +FurnitureYourOldBank = "ton ancienne tirelire" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +FurnitureNames = { + 100 : "Fauteuil", + 105 : "Fauteuil", + 110 : "Chaise", + 120 : "Chaise de bureau", + 130 : "Chaise en rondins", + 140 : "Chaise homard", + 145 : "Chaise de survie", + 150 : "Tabouret selle", + 160 : "Chaise locale", + 170 : "Chaise gâteau", + 200 : "Lit", + 205 : "Lit", + 210 : "Lit", + 220 : "Lit baignoire", + 230 : "Lit feuille", + 240 : "Lit bateau", + 250 : "Hamac cactus", + 260 : "Lit crème glacée", + 270 : "Olivia Erin & Cat's Bed", + 300 : "Piano mécanique", + 310 : "Orgue", + 400 : "Cheminée", + 410 : "Cheminée", + 420 : "Cheminée ronde", + 430 : "Cheminée", + 440 : "Cheminée pomme", + 450 : "Erin's Fireplace", + 500 : "Armoire", + 502 : "Armoire pour 15 vêtements", + 504 : "Armoire 20 articles", + 506 : "Armoire 25 articles", + 510 : "Armoire", + 512 : "Armoire pour 15 vêtements", + 514 : "Armoire 20 articles", + 516 : "Armoire 25 articles", + 600 : "Petite lampe", + 610 : "Lampe haute", + 620 : "Lampe de table", + 625 : "Lampe de table", + 630 : "Lampe Daisy", + 640 : "Lampe Daisy", + 650 : "Lampe méduse", + 660 : "Lampe méduse", + 670 : "Lampe cow-boy", + 700 : "Chaise capitonnée", + 705 : "Chaise capitonnée", + 710 : "Divan", + 715 : "Divan", + 720 : "Divan foin", + 730 : "Divan sablé", + 800 : "Bureau", + 810 : "Bureau en rondins", + 900 : "Porte-parapluie", + 910 : "Portemanteau", + 920 : "Poubelle", + 930 : "Champignon rouge", + 940 : "Champignon jaune", + 950 : "Portemanteau", + 960 : "Étal onneau", + 970 : "Cactus", + 980 : "Tipi", + 990 : "Juliette's Fan", + 1000 : "Grand tapis", + 1010 : "Tapis rond", + 1015 : "Tapis rond", + 1020 : "Petit tapis", + 1030 : "Paillasson", + 1100 : "Vitrine", + 1110 : "Vitrine", + 1120 : "Bibliothèque haute", + 1130 : "Bibliothèque basse", + 1140 : "Coffre Sundae", + 1200 : "Table d'appui", + 1210 : "Petite table", + 1215 : "Petite table", + 1220 : "Table de salon", + 1230 : "Table de salon", + 1240 : "Table de plongeur", + 1250 : "Table cookie", + 1260 : "Table de chevet", + 1300 : "Tirelire de 1000 bonbons", + 1310 : "Tirelire de 2500 bonbons", + 1320 : "Tirelire de 5000 bonbons", + 1330 : "Tirelire de 7500 bonbons", + 1340 : "Tirelire de 10000 bonbons", + 1399 : "Téléphone", + 1400 : "Toon Cézanne", + 1410 : "Fleurs", + 1420 : "Mickey contemporain", + 1430 : "Toon Rembrandt", + 1440 : "Paysage Toon", + 1441 : "Cheval de Whistler", + 1442 : "Étoile Toon", + 1443 : "Pas une tarte", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Télévision", + 1600 : "Vase bas", + 1610 : "Vase haut", + 1620 : "Vase bas", + 1630 : "Vase haut", + 1640 : "Vase bas", + 1650 : "Vase bas", + 1660 : "Vase corail", + 1661 : "Vase coquillage", + 1700 : "Chariot de pop-corn", + 1710 : "Coccinelle", + 1720 : "Fontaine", + 1725 : "Machine à laver", + 1800 : "Aquarium", + 1810 : "Aquarium", + 1900 : "Poisson-scie", + 1910 : "Requin-marteau", + 1920 : "Cornes porte-manteau", + 1930 : "Sombrero classique", + 1940 : "Sombrero fantaisie", + 1950 : "Attrapeur de rêves", + 1960 : "Fer à cheval", + 1970 : "Portrait de bison", + 2000 : "Balançoire bonbon", + 2010 : "Toboggan gâteau", + 3000 : "Baignoire Banana Split", + 10000 : "Petite citrouille", + 10010 : "Grande citrouille", + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "Chemise", + "Chemise", + "Chemise", + "Short", + "Short", + "Jupe", + "Short", + ) + +ClothingTypeNames = { + 1400 : "Chemise de Mathieu", + 1401 : "Chemise de Jessica", + 1402 : "Chemise de Marissa", + 1600 : "Tenue Piège", + 1601 : "Tenue Tapage", + 1602 : "Tenue Leurre", + 1603 : "Tenue Piège", + 1604 : "Tenue Tapage", + 1605 : "Tenue Leurre", + 1606 : "Tenue Piège", + 1607 : "Tenue Tapage", + 1608 : "Tenue Leurre", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "Papier peint", + "Moulures", + "Revêtement de sol", + "Lambris", + "Bordure", + ) + +WallpaperNames = { + 1000 : "Parchemin", + 1100 : "Milan", + 1200 : "Douvres", + 1300 : "Victoria", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Arlequin", + 1700 : "Lune", + 1800 : "Étoiles", + 1900 : "Fleurs", + 2000 : "Jardin de printemps", + 2100 : "Jardin classique", + 2200 : "Jour de course", + 2300 : "Marqué!", + 2400 : "Nuage 9", + 2500 : "Vigne vierge", + 2600 : "Printemps", + 2700 : "Kokeshi", + 2800 : "Petits bouquets", + 2900 : "Poisson ange", + 3000 : "Bulles", + 3100 : "Bulles", + 3200 : "À la pêche", + 3300 : "Poisson stop", + 3400 : "Hippocampe", + 3500 : "Coquillages", + 3600 : "Sous l'eau", + 3700 : "Bottes", + 3800 : "Cactus", + 3900 : "Chapeau de cow-boy", + 10100 : "Chats", + 10200 : "Chauve-souris", + 11000 : "Flocons de neige", + 11100 : "Houx", + 11200 : "Bonhomme de neige", + 13000 : "Trèfle", + 13100 : "Trèfle", + 13200 : "Arc-en-ciel", + 13300 : "Trèfle", + } + +FlooringNames = { + 1000 : "Parquet", + 1010 : "Moquette", + 1020 : "Carrelage losange", + 1030 : "Carrelage losange", + 1040 : "Pelouse", + 1050 : "Briques beiges", + 1060 : "Briques rouges", + 1070 : "Carrelage carré", + 1080 : "Pierre", + 1090 : "Bois", + 1100 : "Terre", + 1110 : "Pavage de bois", + 1120 : "Carrelage", + 1130 : "Nid d'abeilles", + 1140 : "Eau", + 1150 : "Carrelage plage", + 1160 : "Carrelage plage", + 1170 : "Carrelage plage", + 1180 : "Carrelage plage", + 1190 : "Sable", + 10000 : "Glaçon", + 10010 : "Igloo", + 11000 : "Trèfle", + 11010 : "Trèfle", + } + +MouldingNames = { + 1000 : "Noueux", + 1010 : "Peint", + 1020 : "Denté", + 1030 : "Fleurs", + 1040 : "Fleurs", + 1050 : "Coccinelle", + } + +WainscotingNames = { + 1000 : "Peint", + 1010 : "Panneau de bois", + 1020 : "Bois", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Grand jardin", + 20 : "Jardin sauvage", + 30 : "Jardin grec", + 40 : "Paysage urbain", + 50 : "Far West", + 60 : "Sous l'océan", + 70 : "Île tropicale", + 80 : "Nuit étoilée", + 90 : "Lagon Tiki", + 100 : "Frontière gelée", + 110 : "Pays fermier", + 120 : "Camp local", + 130 : "Grand rue", + } + +# don't translate yet +NewCatalogNotify = "De nouveaux articles sont prêts à être commandés par téléphone!" +NewDeliveryNotify = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyFirstCatalog = "Ton premier catalogue est arrivé! Tu peux l'utiliser pour commander de nouveaux objets pour toi ou pour ta maison." +CatalogNotifyNewCatalog = "Ton catalogue N°%s est arrivé! Tu peux utiliser ton téléphone pour commander des articles dans le catalogue de Clarabelle." +CatalogNotifyNewCatalogNewDelivery = "Un colis t'attend dans ta boîte aux lettres! Ton catalogue N°%s est aussi arrivé!" +CatalogNotifyNewDelivery = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyNewCatalogOldDelivery = "Ton catalogue N°%s est arrivé, et des objets t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyOldDelivery = "Des articles t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyInstructions = "Clique sur le bouton \"Retour à la maison\" sur la carte de ton journal de bord, puis va jusqu'au téléphone qui est dans ta maison." +CatalogNewDeliveryButton = "Nouvelle\nlivraison!" +CatalogNewCatalogButton = "Nouveau\ncatalogue" +CatalogSaleItem = "Vente!" + +# don't translate yet +DistributedMailboxEmpty = "Ta boîte aux lettres est vide pour l'instant. Reviens ici chercher les articles que tu as commandés par téléphone quand ils seront livrés!" +DistributedMailboxWaiting = "Ta boîte aux lettres est vide pour l'instant, mais le paquet que tu as commandé est en chemin. Reviens voir plus tard!" +DistributedMailboxReady = "Ta commande est arrivée!" +DistributedMailboxNotOwner = "Désolé, ce n'est pas ta boîte aux lettres." +DistributedPhoneEmpty = "Tu peux utiliser n'importe quel téléphone pour commander des articles pour toi et pour ta maison. De nouveaux articles seront proposés dans l'avenir.\n\nAucun article n'est disponible à la commande maintenant, mais reviens voir plus tard!" + +# don't translate yet +Clarabelle = "Clarabelle" +MailboxExitButton = "Fermer boîte\naux lettres" +MailboxAcceptButton = "Accepter" +MailBoxDiscard = "Refuser" +MailboxAcceptInvite = "Accept this invite" +MailBoxRejectInvite = "Reject this invite" +MailBoxDiscardVerify = "Es-tu sûr de vouloir rejeter %s ?" +MailboxOneItem = "Ta boîte aux lettres contient 1 objet." +MailboxNumberOfItems = "Ta boîte aux lettres contient %s objets." +MailboxGettingItem = "Récupération de %s dans la boîte aux lettres." +MailboxGiftTag = "Cadeau de : %s" +MailboxGiftTagAnonymous = "Anonyme" +MailboxItemNext = "Objet\nsuivant" +MailboxItemPrev = "Objet\nprécédent" +MailboxDiscard = "Rejeter" +MailboxLeave = "Accepter" +CatalogCurrency = "bonbons" +CatalogHangUp = "Raccrocher" +CatalogNew = "NOUVEAUTÉ" +CatalogBackorder = "PRÉ-COMMANDE" +CatalogLoyalty = "SPECIAL" +CatalogPagePrefix = "Page" +CatalogGreeting = "Bonjour! Merci d'avoir appelé le catalogue de Clarabelle. Que puis-je pour toi?" +CatalogGoodbyeList = ["Au revoir!", + "Rappelle bientôt!", + "Merci de ton appel!", + "OK, au revoir!", + "Au revoir!", + ] +CatalogHelpText1 = "Tourne la page pour voir les articles qui sont en vente." +CatalogSeriesLabel = "Série %s" +CatalogGiftFor = "Acheter un cadeau pour :" +CatalogGiftTo = "Pour : %s" +CatalogGiftToggleOn = "Arrêter d'acheter\ndes cadeaux" +CatalogGiftToggleOff = "Acheter des\ncadeaux" +CatalogGiftToggleWait = "En train d'essayer!..." +CatalogGiftToggleNoAck = "Indisponible" +CatalogPurchaseItemAvailable = "Parfait ! Peut commencer à utiliser ton cadeau dès maintenant." +CatalogPurchaseGiftItemAvailable = "Parfait ! Ton cadeau pour %s sera livré dans sa boîte aux lettres." +CatalogPurchaseItemOnOrder = "Félicitations! Ton achat sera bientôt livré dans ta boîte aux lettres." +CatalogPurchaseGiftItemOnOrder = " Parfait ! Ton cadeau pour %s sera livré dans sa boîte aux lettres." +CatalogAnythingElse = "Puis-je autre chose pour toi?" +CatalogPurchaseClosetFull = "Ton placard est plein. Tu peux acheter cet article, mais tu devras supprimer quelque chose de ton placard pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article ?" +CatalogAcceptClosetFull = "Ton placard est plein. Tu dois rentrer et supprimer quelque chose de ton placard pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptShirt = "Tu portes maintenant ta nouvelle chemise. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptShorts = "Tu portes maintenant ton nouveau short. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptSkirt = "Tu portes maintenant ta nouvelle jupe. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptPole = "Tu peux maintenant attraper des poissons plus gros avec ta nouvelle canne!" +CatalogAcceptPoleUnneeded = "Tu as déjà une canne meilleure que celle-ci!" +CatalogAcceptChat = "Tu possèdes maintenant une nouvelle phrase de Chat rapide." +CatalogAcceptEmote = "Tu possèdes maintenant une nouvelle émotion !" +CatalogAcceptBeans = "Tu as reçu des bonbons !" +CatalogAcceptRATBeans = "Ta récompense de recrue Toon est arrivée !" + +CatalogAcceptNametag = "Your new name tag has arrived !" +CatalogAcceptGarden = "Tes matériaux de jardinage sont arrivés !" +CatalogAcceptPet = "Tu possèdes maintenant un nouveau tour pour ton Doodle !" +CatalogPurchaseHouseFull = "Ta maison est pleine. Tu peux acheter cet article, mais tu devras supprimer quelque chose dans ta maison pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article ?" +CatalogAcceptHouseFull = "Ta maison est pleine. Tu dois rentrer et supprimer quelque chose dans ta maison pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptInAttic = "Ton nouvel article est maintenant dans ton grenier. Pour le placer dans ta maison, va à l'intérieur et clique sur le bouton \"Déplacer les meubles\"." +CatalogAcceptInAtticP = "Tes nouveaux articles sont maintenant dans ton grenier. Pour les placer dans ta maison, va à l'intérieur et clique sur le bouton \"Déplacer les meubles\"." +CatalogPurchaseMailboxFull = "Ta boîte aux lettres est pleine! Tu ne peux pas acheter cet article avant d'avoir sorti des articles de ta boîte aux lettres pour y faire de la place." +CatalogPurchaseGiftMailboxFull = "La boîte aux lettres de %s est pleine ! Tu ne peux pas acheter cet article." +CatalogPurchaseOnOrderListFull = "Tu as trop d'articles en commande actuellement. Tu ne peux pas commander d'autres articles avant que ceux que tu as déjà commandés ne soient arrivés." +CatalogPurchaseGiftOnOrderListFull = "%s a actuellement trop d'articles en commande." +CatalogPurchaseGeneralError = "L'article n'a pas pu être acheté à cause d'une erreur interne au jeu: code d'erreur %s." +CatalogPurchaseGiftGeneralError = "Le cadeau n'a pas pu être offert à ton(tes) %(friend) en raison d'une erreur interne %(error) au jeu." +CatalogPurchaseGiftNotAGift = "Cet article n'a pas pu être envoyé à %s parce qu'il n'est pas assez avancé dans le jeu." +CatalogPurchaseGiftWillNotFit = "Cet article n'a pas pu être envoyé à %s parce qu'il ne lui correspond pas." +CatalogPurchaseGiftLimitReached = "Cet article n'a pas pu être envoyé à %s parce qu'il le possède déjà." +CatalogPurchaseGiftNotEnoughMoney = "Cet article n'a pas pu être envoyé à %s parce que tu n'as pas les moyens de l'acheter." +CatalogAcceptGeneralError = "L'article n'a pas pu être retiré de ta boîte aux lettres à cause d'une erreur interne au jeu: code d'erreur %s." +CatalogAcceptRoomError = "Tu n'as pas de place pour mettre cet article. Tu vas devoir te débarasser de quelquechose." +CatalogAcceptLimitError = "Tu possèdes déjà beaucoup d'exemplaires de cet article. Tu vas devoir te débarasser de quelquechose." +CatalogAcceptFitError = "Cela ne t'ira pas ! Tu dois en faire don à un toon qui en a besoin." +CatalogAcceptInvalidError = "Cet article n'est plus à la mode. Tu dois en faire don à un toon qui en a besoin." + +MailboxOverflowButtonDicard = "Supprimer" +MailboxOverflowButtonLeave = "Garder" + +# don't translate yet +HDMoveFurnitureButton = "Déplacer\nles meubles" +HDStopMoveFurnitureButton = "Meubles\nplacés" +HDAtticPickerLabel = "Dans le grenier" +HDInRoomPickerLabel = "Dans la pièce" +HDInTrashPickerLabel = "À la poubelle" +HDDeletePickerLabel = "Supprimer ?" +HDInAtticLabel = "Grenier" +HDInRoomLabel = "Pièce" +HDInTrashLabel = "Poubelle" +HDToAtticLabel = "Mettre\nau grenier" +HDMoveLabel = "Déplacer" +HDRotateCWLabel = "Tourner vers la droite" +HDRotateCCWLabel = "Tourner vers la gauche" +HDReturnVerify = "Remettre cet objet dans le grenier ?" +HDReturnFromTrashVerify = "Ressortir cet objet de la poubelle et le mettre dans le grenier ?" +HDDeleteItem = "Clique sur OK pour mettre cet objet à la poubelle ou sur Annuler pour le garder." +HDNonDeletableItem = "Tu ne peux pas supprimer les objets de ce type!" +HDNonDeletableBank = "Tu ne peux pas supprimer ta tirelire!" +HDNonDeletableCloset = "Tu ne peux pas supprimer ton armoire!" +HDNonDeletablePhone = "Tu ne peux pas supprimer ton téléphone!" +HDNonDeletableNotOwner = "Tu ne peux pas supprimer les affaires de %s!" +HDHouseFull = "Ta maison est pleine. Tu dois supprimer quelque chose d'autre dans ta maison ou ton grenier avant de pouvoir ressortir cet article de la poubelle." + +HDHelpDict = { + "DoneMoving" : "Terminer la décoration de la pièce.", + "Attic" : "Voir la liste des objets qui sont au grenier. Les objets qui ne sont pas dans ta pièce sont au grenier.", + "Room" : "Voir la liste des objets qui sont dans la pièce. Utile pour retrouver des objets perdus.", + "Trash" : "Voir les objets qui sont dans la poubelle. Les objets les plus anciens sont supprimés après un temps ou si la poubelle déborde.", + "ZoomIn" : "Agrandir la vue de la pièce.", + "ZoomOut" : "Éloigner la vue de la pièce.", + "SendToAttic" : "Stocker le meuble actuel dans le grenier.", + "RotateLeft" : "Tourner vers la gauche.", + "RotateRight" : "Tourner vers la droite.", + "DeleteEnter" : "Passer en mode suppression.", + "DeleteExit" : "Sortir du mode suppression.", + "FurnitureItemPanelDelete" : "Mettre %s à la poubelle.", + "FurnitureItemPanelAttic" : "Mettre %s dans la pièce.", + "FurnitureItemPanelRoom" : "Remettre %s au grenier.", + "FurnitureItemPanelTrash" : "Remettre %s au grenier.", + } + +# don't translate yet +MessagePickerTitle = "Tu as trop de phrases. Pour pouvoir acheter\n\"%s\"\n tu dois choisir une chose à retirer:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Es-tu certain de vouloir retirer \"%s\" de ton menu de Chat rapide ?" + +# don't translate yet +CatalogBuyText = "Acheter" +CatalogRentText = "Louer" +CatalogGiftText = "Cadeau" +CatalogOnOrderText = "En commande" +CatalogPurchasedText = "Déjà\nacheté" +CatalogGiftedText = "Offert\nà toi" +CatalogPurchasedGiftText = "Déjà\nPossédé" +CatalogMailboxFull = "Pas de place" +CatalogNotAGift = "N'est pas un cadeau" +CatalogNoFit = "ne va pas" +CatalogMembersOnly = "Réservé aux membres\n !" +CatalogSndOnText = "Connecté" +CatalogSndOffText = "Non connecté" + +CatalogPurchasedMaxText = "Maximum\ndéjà acheté" +CatalogVerifyPurchase = "Acheter %(item)s pour %(price)s bonbons?" +CatalogVerifyRent = "Louer %(item)s pour le prix de %(price)s bonbons?" +CatalogVerifyGift = "Acheter %(item)s pour %(price)s bonbons comme cadeau pour %(friend)s?" +CatalogOnlyOnePurchase = "Tu ne peux avoir qu'un de ces articles à la fois. Si tu achètes celui-là, il remplacera %(old)s.\n\nEs-tu certain(e) de vouloir acheter %(item)s pour %(price)s bonbons?" + +CatalogExitButtonText = "Raccrocher" +CatalogCurrentButtonText = "Articles actuels" +CatalogPastButtonText = "Articles précédents" + +TutorialHQOfficerName = "Harry du QG" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tom Tuteur", + 999 : "Toon Tailleur", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Bob le Banquier", + 2003 : "Professeur Pete", + 2004 : "Tammy le Tailleur", + 2005 : "Larry le Libraire", + 2006 : "Vincent - Vendeur", + 2011 : "Véronique - Vendeuse", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Vendeur de l'animalerie", + # NPCPetClerks + 2013 : "M. Vacarme", + 2014 : "Melle Vadrouille", + 2015 : "M. Vagabond", + # NPCPartyPerson + 2016 : "Party Planner Pete", + 2017 : "Party Planner Penny", + + # Silly Street + 2101 : "Daniel le Dentiste", + 2102 : "Sherry le Shérif", + 2103 : "Kitty Lerhume", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canary Minederien", + 2109 : "Souffle douleur", + 2110 : "A. Fiche", + 2111 : "Diego Ladanse", + 2112 : "Dr Tom", + 2113 : "Rollo le Magnifique", + 2114 : "Rose Dévent", + 2115 : "Dédé Coupage", + 2116 : "Costaud McDougal", + 2117 : "Madame Putride", + 2118 : "Jesse Jememoque", + 2119 : "Meryl Semarre", + 2120 : "Professeur Morderire", + 2121 : "Madame Marrante", + 2122 : "Harry Lesinge", + 2123 : "Emile Esime", + 2124 : "Gaëtan Pipourtoi", + 2125 : "Lazy Mut", + 2126 : "Professeur Lagaffe", + 2127 : "Woody Troissous", + 2128 : "Loulou Fifou", + 2129 : "Frank Fort", + 2130 : "Sylvie Brateur", + 2131 : "Jeanne Laplume", + 2132 : "Daffy Don", + 2133 : "Dr E. Phorique", + 2134 : "Simone Silence-on-tourne", + 2135 : "Marie Satourne", + 2136 : "Sal Amandre", + 2137 : "Heureux Kikomulisse", + 2138 : "Gaston", + 2139 : "Bernard Bavunpeu", + 2140 : "Billy le pêcheur", + + # Loopy Lane + 2201 : "Pierre le Postier", + 2202 : "Paul Ochon", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Tony Truant", + 2208 : "Nicole Lacolle", + 2209 : "Henri Gole", + 2210 : "Valérie Golotte", + 2211 : "Sally Salive", + 2212 : "Max Imum", + 2213 : "Lucy Rustine", + 2214 : "Dino Zore", + 2215 : "Jean Aimarre", + 2216 : "Lady Sparue", + 2217 : "Jones Requin", + 2218 : "Fanny Larant", + 2219 : "Lanouille", + 2220 : "Louis Leroc", + 2221 : "Tina Pachangé", + 2222 : "Electre O'Cardiogramme", + 2223 : "Sasha Touille", + 2224 : "Joe Lefumeux", + 2225 : "Toumou le pêcheur", + + # Punchline Place + 2301 : "Dr Faismarcher", + 2302 : "Professeur Tortillard", + 2303 : "Nancy Nanny", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Gaz", + 2309 : "Gros Bruce", + 2311 : "Frank O'Debord", + 2312 : "Dr Sensible", + 2313 : "Lucy Boulette", + 2314 : "Ned Lafronde", + 2315 : "Valérie Deveau", + 2316 : "Cindy Ka", + 2318 : "Mac Aroni", + 2319 : "Annick", + 2320 : "Alfonse Danslebrouillard", + 2321 : "Vif le pêcheur", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Willy - Vendeur", + 1002 : "Billy - Vendeur", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Alain Térieur", + # NPCFisherman + 1008 : "Vendeur de l'animalerie", + # NPCPetClerks + 1009 : "M. Ouahouah", + 1010 : "Melle Ronron", + 1011 : "Mme Glouglou", + # NPCPartyPerson + 1012 : "Party Planner Phil", + 1013 : "Party Planner Patty", + + # Barnacle Boulevard + 1101 : "Sam Suffit", + 1102 : "Capitaine Carl", + 1103 : "Frank L'écaille", + 1104 : "Docteur Squale", + 1105 : "Amiral Crochet", + 1106 : "Mme Amidon", + 1107 : "Jim Nastic", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gary Glouglou", + 1113 : "Anna-Lise Deussan", + 1114 : "Mick Robe", + 1115 : "Sheila Seiche, Avocate", + 1116 : "Bernard Bernache", + 1117 : "Capitaine Hautlecoeur", + 1118 : "Choppy McDougal", + 1121 : "Marthe Aupiqueur", + 1122 : "Petit Salé", + 1123 : "Electre O'Magnétique", + 1124 : "Simon Strueux", + 1125 : "Elvire Debord", + 1126 : "Barnabé le pêcheur", + + # Seaweed Street + 1201 : "Barbara Bernache", + 1202 : "Art", + 1203 : "Ahab", + 1204 : "Rocky Roc", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professeur Planche", + 1210 : "Yaka Sauté", + 1211 : "Sarah Lenti", + 1212 : "Loulou Languedebois", + 1213 : "Dante Dauphin", + 1214 : "Aimé Duse", + 1215 : "Jean Peuplu", + 1216 : "Seymour Linet", + 1217 : "Cécile Savet", + 1218 : "Tim Pacifique", + 1219 : "Yvon Alot", + 1220 : "Minnie Stair", + 1221 : "McKee Labulle", + 1222 : "A. Marre", + 1223 : "Sid Seiche", + 1224 : "Anna Conda", + 1225 : "Bonzo Boitrop", + 1226 : "Ho Hisse", + 1227 : "Coral", + 1228 : "Rozo le pêcheur", + + # Lighthouse Lane + 1301 : "Ernest", + 1302 : "Ginette", + 1303 : "Gérard", + 1304 : "Hillary Varien", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Ecume de mer", + 1310 : "Ted Tentacule", + 1311 : "Jean Reveux", + 1312 : "Gaëtan Coque", + 1313 : "Gérard Timon", + 1314 : "Ralph Rouillé", + 1315 : "Docteur Dérive", + 1316 : "Elodie Toire", + 1317 : "Paule Pylone", + 1318 : "Barnabé Bouée", + 1319 : "David Bienosec", + 1320 : "Aldo Plate", + 1321 : "Dinah Esservi", + 1322 : "Peter Coussin", + 1323 : "Ned Savon", + 1324 : "Perle Démer", + 1325 : "Ned Setter", + 1326 : "G. Lafritte", + 1327 : "Cindy Nosore", + 1328 : "Sam Ouraille", + 1329 : "Shelly Beaucoup", + 1330 : "Icare Bonize", + 1331 : "Guy Rlande", + 1332 : "Martin le pêcheur", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Angèle Ici", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Lenny - Vendeur", + 3007 : "Penny - Vendeuse", + 3008 : "Warren Fagoté", + # NPCPêcheur + 3009 : "Vendeur de l'animalerie", + # NPCPetClerks + 3010 : "M. Cabo", + 3011 : "Melle Cabriole", + 3012 : "M. Cadichon", + # NPCPartyPerson + 3013 : "Party Planner Paul", + 3014 : "Party Planner Polly", + + # Walrus Way + 3101 : "M. Lapin", + 3102 : "Tante Angèle", + 3103 : "Tanguy", + 3104 : "Bonnie", + 3105 : "Freddy Frigo", + 3106 : "Paul Poulemouillée", + 3107 : "Patty Touteseule", + 3108 : "Ted Tobogan", + 3109 : "Patricia", + 3110 : "Jack Pot", + 3111 : "O. Tain", + 3112 : "Allan Bic", + 3113 : "Harry Hystérique", + 3114 : "Nathan Pastrop", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carl Magne", + 3120 : "Mike Mouffles", + 3121 : "Joe Courant", + 3122 : "Lucy Luge", + 3123 : "Nicole Apon", + 3124 : "Lance Iceberg", + 3125 : "Colonel Mâchetout", + 3126 : "Colette Erol", + 3127 : "Alex Térieur", + 3128 : "George Lacolle", + 3129 : "Brigitte Boulanger", + 3130 : "Sandy", + 3131 : "Pablo Paresseux", + 3132 : "Braise Cendrar", + 3133 : "Dr Jevoismieux", + 3134 : "Sébastien Toutseul", + 3135 : "Nelly Quéfié", + 3136 : "Claude Iqué", + 3137 : "M. Gel", + 3138 : "M. Empoté", + 3139 : "Virginie Aimaitropaul", + 3140 : "Lucile la pêcheuse", + + # Sleet Street + 3201 : "Tante Artique", + 3202 : "Tremblotte", + 3203 : "Walt", + 3204 : "Dr Ivan Deslunettes", + 3205 : "Boris Tourne", + 3206 : "Victoire Alarraché", + 3207 : "Dr Marmotter", + 3208 : "Phil Electrique", + 3209 : "Geoffroy Auxmains", + 3210 : "Sam Simiesque", + 3211 : "Gaelle Segèle", + 3212 : "Freddy Frigo", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Pierre Lasueur", + 3218 : "Lou Minaire", + 3219 : "Tom Tandem", + 3220 : "G. Ternue", + 3221 : "Nelly Neige", + 3222 : "Moricette Decuisine", + 3223 : "Chappy", + 3224 : "Agnes Kimo", + 3225 : "Frimas Ladouce", + 3226 : "Prospère Noël", + 3227 : "Ray Ondesoleil", + 3228 : "Maurice Quetout", + 3229 : "Hernie Discale", + 3230 : "Benjy Boule-à-zéro", + 3231 : "Choppy", + 3232 : "Albert le pêcheur", + + #Polar Place + 3301 : "Cathou Coupet", + 3302 : "Bjorn Bord", + 3303 : "Dr Flic-Flac", + 3304 : "Eddie le Yéti", + 3305 : "Mac Ramée", + 3306 : "Paul Hère", + # NPC Fisherman + 3307 : "Pêcheuse Frédérique", + 3308 : "Marcel Glassault", + 3309 : "Théo Citron", + 3310 : "Professeur Flocon", + 3311 : "Cella Glasse", + 3312 : "J. Boulet de Mars", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Chris Crisse", + 3318 : "Alan Sthiver", + 3319 : "Bo Nedlaine", + 3320 : "Lisette Frisquette", + 3321 : "Cédric Piolet", + 3322 : "Corinne Za", + 3323 : "Aurore Beau-Réal", + 3324 : "Mandra Gore", + 3325 : "Alban Quise", + 3326 : "Blanche", + 3327 : "J. Gault", + 3328 : "Rémi Taine", + 3329 : "Isaure Betière", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Molly Masson", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Doe - Vendeur", + 4007 : "Ray - Vendeur", + 4008 : "Bernard Mony", + # NPCFisherman + 4009 : "Vendeur de l'animalerie", + # NPCPetClerks + 4010 : "M. Chris", + 4011 : "M. Neil", + 4012 : "Melle Western", + # NPCPartyPerson + 4013 : "Party Planner Preston", + 4014 : "Party Planner Penelope", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr Tefaispasdebile", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Clément de Sol", + 4109 : "Carlos", + 4110 : "Métro Gnome", + 4111 : "Adam Levent", + 4112 : "Fa", + 4113 : "Madame Manière", + 4114 : "Eric Ochet", + 4115 : "Labelle Decadix", + 4116 : "Piccolo", + 4117 : "Mandy Lynn", + 4118 : "André Sansfrapper", + 4119 : "Moe Zart", + 4120 : "Viola Coussin", + 4121 : "Ray Mineur", + 4122 : "Armanthe Réglisse", + 4123 : "Ted l'éclair", + 4124 : "Riff Iffifi", + 4125 : "Mélodie Dantan", + 4126 : "Bel Canto", + 4127 : "Amédé Chausson", + 4128 : "Luciano Lescoop", + 4129 : "Terry Golo", + 4130 : "Rémi Crophone", + 4131 : "Abraham Armoire", + 4132 : "Sally Tristounet", + 4133 : "D. Taché", + 4134 : "Dave Disco", + 4135 : "Séraphin Ducompte", + 4136 : "Patty Pause", + 4137 : "Tony Doiseau", + 4138 : "Rémi Depain", + 4139 : "Harmony Ka", + 4140 : "Ned Maladroit", + 4141 : "Jojo le pêcheur", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Jack Bûcheron", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Elise", + 4209 : "Mo Végou", + 4211 : "Carl Concerto", + 4212 : "Funeste Funèbre", + 4213 : "Fran Chement", + 4214 : "Tina Crampon", + 4215 : "Tim Rouletroprès", + 4216 : "K. Outchouc", + 4217 : "Anton Beaugarçon", + 4218 : "Vanessa Vapasdutout", + 4219 : "Sid Sonate", + 4220 : "Jean-Bière", + 4221 : "Moe Madrigal", + 4222 : "John Deuf", + 4223 : "Penny Souffleur", + 4224 : "Jim Jongle", + 4225 : "Holly Stérie", + 4226 : "Georgina Gorge", + 4227 : "Francesca Taphonique", + 4228 : "August Ave", + 4229 : "June Comprendsrien", + 4230 : "Julius Césure", + 4231 : "Steffi Nalise", + 4232 : "Marie Toivite", + 4233 : "Charlie Lacarpe", + 4234 : "Guy Tare", + 4235 : "Larry le pêcheur", + + # Tenor Terrace + 4301 : "Yuki", + 4302 : "Anna", + 4303 : "Léo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tabatha", + 4309 : "Mémé Chignon", + 4310 : "Marthe Ingale", + 4311 : "Charlie Mande", + 4312 : "Ma Sage", + 4313 : "Muget Muet", + 4314 : "Dino Dodo", + 4315 : "Karen Rouages", + 4316 : "Tim Tango", + 4317 : "Sue Bitto", + 4318 : "Bob Marlin", + 4319 : "K. Zou", + 4320 : "Camille Cloda", + 4321 : "Luky Luth", + 4322 : "Henry Thme", + 4323 : "Hanna Purna", + 4324 : "Ellie", + 4325 : "Braque Labanque", + 4326 : "Jonathan Plurien", + 4327 : "Flim Flam", + 4328 : "Wagner", + 4329 : "Tyler Prompteur", + 4330 : "Quentin", + 4331 : "M. Costello", + 4332 : "Ziggy", + 4333 : "Harry", + 4334 : "Freddie Fastoche", + 4335 : "Serge le pêcheur", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Prune - Vendeuse", + 5006 : "Rose - Vendeuse", + 5007 : "Bonnie Menteuse", + # NPCFisherman + 5008 : "Vendeur de l'animalerie", + # NPCPetClerks + 5009 : "Mme Flore Halie", + 5010 : "M. Tom Hatte", + 5011 : "M. Ray Glisse", + # NPCPartyPerson + 5012 : "Party Planner Pierce", + 5013 : "Party Planner Peggy", + + # Elm Street + 5101 : "Eugène", + 5102 : "Susan", + 5103 : "Piaf", + 5104 : "Parpaillon", + 5105 : "Jack", + 5106 : "Bjorn le Barbier", + 5107 : "Felipe le Postier", + 5108 : "Janette l'Aubergiste", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr Lacouenne", + 5114 : "Affaiblissement", + 5115 : "Rosée Dumatin", + 5116 : "R. Noncule", + 5117 : "Pétale", + 5118 : "Victor Nemuse", + 5119 : "Barry Dicule", + 5120 : "La taupe", + 5121 : "Paula Roïd", + 5122 : "A. Masse", + 5123 : "Diane Avecnouscesoir", + 5124 : "Chen Avélo", + 5125 : "A. Sperge", + 5126 : "Madame Mère", + 5127 : "Polly Pollène", + 5128 : "Salma Range", + 5129 : "Sally la pêcheuse", + + # Maple Street + 5201 : "Jacquot", + 5202 : "Cynthia", + 5203 : "Citronelle", + 5204 : "Bert", + 5205 : "Omar Souin", + 5206 : "Ray Zainblanc", + 5207 : "Sophie Stiquée", + 5208 : "Samantha Pir", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Gros Balourd", + 5214 : "Sam Gratte", + 5215 : "Henry Chisson", + 5216 : "Jim Lassenteur", + 5217 : "Walter Ego", + 5218 : "Rocky Groseille", + 5219 : "Mo Viette", + 5220 : "Adam Telle", + 5221 : "Flamant rose", + 5222 : "Pétronille Hiliste", + 5223 : "Marc Assin", + 5224 : "Oncle Balourd", + 5225 : "Pamela Asaplace", + 5226 : "Pierre Mousse", + 5227 : "B. Gonia", + 5228 : "Avi Dité", + 5229 : "Lili la pêcheuse", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Crystelle", + 5306 : "S. Cargot", + 5307 : "Cyril Semarre", + 5308 : "Nell Ronchon", + 5309 : "Romaine", + 5310 : "Thimothé", + 5311 : "Jonas Ticot", + 5312 : "Eugène", + 5313 : "Zucchini l'entraîneur", + 5314 : "Merlin Sect", + 5315 : "Oncle Boueux", + 5316 : "Oncle Patapouf", + 5317 : "Lima, détective", + 5318 : "César", + 5319 : "Rose", + 5320 : "J. Boulée", + 5321 : "Professeur Chèvrefeuille", + 5322 : "Rose la pêcheuse", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "Benjamin Salor", + 8002 : "Yvon Affond-Lacaisse", + 8003 : "Emma Nicourt", + 8004 : "Phil Assent", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Mélusine Enfaillite", + 9002 : "Tom Pouce", + 9003 : "Denis Doiseau", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Jill - Vendeuse", + 9009 : "Phil - Vendeur", + 9010 : "U. Zure", + # NPCFisherman + 9011 : "Vendeur de l'animalerie", + # NPCPetClerks + 9012 : "Melle Isabelle Bulle", + 9013 : "Mme Dorothée Dor", + 9014 : "M. Pierre Pionce", + # NPCPartyPerson + 9015 : "Party Planner Patrick", + 9016 : "Party Planner Pearl", + + # Lullaby Lane + 9101 : "Ed", + 9102 : "Big Mama", + 9103 : "P. J.", + 9104 : "Fay Debeauxrêves", + 9105 : "Professeur Baillebeaucoup", + 9106 : "Max", + 9107 : "Câline", + 9108 : "Matt Heula", + 9109 : "Daphné Puisé", + 9110 : "Kathy Mini", + 9111 : "Ali Mentation", + 9112 : "Lou Laberceuse", + 9113 : "Jacques Horloge", + 9114 : "Emma Scara", + 9115 : "Bébé MacDougal", + 9116 : "Celui qui danse avec les moutons", + 9117 : "Sam Suffit", + 9118 : "Stella Lune", + 9119 : "Rocco", + 9120 : "Aron Flebeaucoup", + 9121 : "Serena Dàlanuitombée", + 9122 : "Serge Souslesyeux", + 9123 : "Teddy Blaireau", + 9124 : "Nina Lamparo", + 9125 : "Dr Chassieux", + 9126 : "Thérèse Eveillé", + 9127 : "Tabby Tude", + 9128 : "Amédé Brouilletoitoutseul", + 9129 : "Amélie Decamp", + 9130 : "Paul Potdechambre", + 9131 : "Susan Sieste", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Titine la pêcheuse", + + # Pajama Place + 9201 : "Nesdor", + 9202 : "Orville", + 9203 : "Plume", + 9204 : "Claire de Moune", + 9205 : "Olivier Daure", + 9206 : "Phèdre Don", + 9207 : "Sacha Lumea", + 9208 : "Dave Bigleau", + 9209 : "Dr Drin", + 9210 : "Mike Mac", + 9211 : "Aurore", + 9212 : "Phœbe Lancre", + 9213 : "Fortuné Dargent", + 9214 : "Dr Ouffe", + 9215 : "Honoré", + 9216 : "Tartine", + 9217 : "Linda Kapok", + 9218 : "Rita Thasse", + 9219 : "La comtesse", + 9220 : "Matt Thuvu", + 9221 : "Père San", + 9222 : "Ron Chonneau", + 9223 : "Fay Dodeau", + 9224 : "Sandie Marchand", + 9225 : "Élodie Dont", + 9226 : "Laurent Lauronpat", + 9227 : "Édouard Sagrate", + 9228 : "Michu Chotte", + 9229 : "Eva Sandor-Mir", + 9230 : "Pierrot", + 9231 : "Léo Galleau", + 9232 : "Rosée de Lune", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "S. André", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Mairie de Toontown", ""), + 2514 : ("Banque de Toontown", ""), + 2516 : ("Ecole de Toontown", ""), + 2518 : ("Bibliothèque de Toontown", ""), + 2519 : ("Boutique à gags", ""), + 2520 : ("Quartier Général des Toons", ""), + 2521 : ("Boutique de prêt-à-porter", ""), + 2522 : ("ANIMALERIE", ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Tout-sourire - Réparations dentaires", ""), + 2602 : ("", ""), + 2603 : ("Mineurs Pince-sans-rire", ""), + 2604 : ("Qui vivra, verrat", ""), + 2605 : ("Usine à pancartes de Toontown", ""), + 2606 : ("", ""), + 2607 : ("Haricots sauteurs", ""), + 2610 : ("Dr. Tom Lepitre", ""), + 2611 : ("", ""), + 2616 : ("Barbefolle - Déguisements", ""), + 2617 : ("Cascades Comiques", ""), + 2618 : ("Nouba & Co", ""), + 2621 : ("Avions en papier", ""), + 2624 : ("Aux joyeux hooligans", ""), + 2625 : ("La maison du pâté raté", ""), + 2626 : ("Chez Jesse - Réparation de blagues", ""), + 2629 : ("Le coin du rire", ""), + 2632 : ("L'école des clowns", ""), + 2633 : ("Thé-hier - Salon de thé", ""), + 2638 : ("Théâtre de Toontown", ""), + 2639 : ("Monnaie de singe", ""), + 2643 : ("Bouteilles en boîte", ""), + 2644 : ("Farces farcies", ""), + 2649 : ("Magasin de jeux", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Leçons de rire", ""), + 2655 : ("Drôle d'argent - Caisse d'épargne", ""), + 2656 : ("Voitures de clown d'occasion", ""), + 2657 : ("Pirouettes de Pierrette", ""), + 2659 : ("L'univers des vibrateurs", ""), + 2660 : ("Machines à chatouilles", ""), + 2661 : ("Daffy Taffy", ""), + 2662 : ("Dr E. Phorique", ""), + 2663 : ("Théâtre de Toontown", ""), + 2664 : ("Les mimes marrants", ""), + 2665 : ("Le Manège - Agence de voyages", ""), + 2666 : ("Bouteilles de gaz hilarant", ""), + 2667 : ("Au bon temps", ""), + 2669 : ("Chez Gaston - ballons pas folichons", ""), + 2670 : ("Fourchettes à soupe", ""), + 2671 : ("Quartier Général des Toons", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Théâtre de Toontown", ""), + 2705 : ("Tony Truant - Bruits en tout genre", ""), + 2708 : ("Colle bleue", ""), + 2711 : ("Bureau de poste de Toontown", ""), + 2712 : ("Café des gloussements", ""), + 2713 : ("Café du rire", ""), + 2714 : ("Théâtre de Toontown", ""), + 2716 : ("Dr Ãle de soupe", ""), + 2717 : ("Boîtes en bouteille", ""), + 2720 : ("Plaies et Bosses - Réparations de voitures", ""), + 2725 : ("", ""), + 2727 : ("Bouteilles et boîtes Selter", ""), + 2728 : ("Crème de jour évanescente", ""), + 2729 : ("Ornithorynques 14 carats", ""), + 2730 : ("La gazette du rire", ""), + 2731 : ("", ""), + 2732 : ("Spaghettis et barbituriques", ""), + 2733 : ("Cerf-volants en fonte", ""), + 2734 : ("Tasses et soucoupes volantes", ""), + 2735 : ("Le Pétard mouillé", ""), + 2739 : ("Réparation de fous rires", ""), + 2740 : ("Pétards d'occasion", ""), + 2741 : ("", ""), + 2742 : ("Quartier Général des Toons", ""), + 2743 : ("", ""), + 2744 : ("", ""), + 2747 : ("Encre visible", ""), + 2748 : ("Rions un peu", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Coussins sonores", ""), + 2802 : ("Boulets de démolition gonflables", ""), + 2803 : ("Théâtre de Toontown", ""), + 2804 : ("Dr. Faismarcher, chiropracteur", ""), + 2805 : ("", ""), + 2809 : ("Salle de gym Le Poids lent", ""), + 2814 : ("Théâtre de Toontown", ""), + 2818 : ("Au pâté volant", ""), + 2821 : ("", ""), + 2822 : ("Sandwichs au poulet synthétique", ""), + 2823 : ("Glaces hilarantes", ""), + 2824 : ("Cinéma des blagues", ""), + 2829 : ("Balivernes", ""), + 2830 : ("Les piques d'Annick", ""), + 2831 : ("La maison du rire du professeur Tortillard", ""), + 2832 : ("Quartier Général des Toons", ""), + 2833 : ("", ""), + 2834 : ("Salle des urgences des morts de rire", ""), + 2836 : ("", ""), + 2837 : ("Hardi - Séminaires", ""), + 2839 : ("A la nouille amère", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Boutique à gags", ""), + 1507 : ("Quartier Général des Toons", ""), + 1508 : ("Boutique de prêt-à-porter", ""), + 1510 : ("ANIMALERIE", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Gilets de sauvetage d'occasion", ""), + 1604 : ("Costumes de bain - Nettoyage à sec", ""), + 1606 : ("Crochet - Réparation d'horloges", ""), + 1608 : ("Le Lof", ""), + 1609 : ("A l'appât rance", ""), + 1612 : ("Banque Sixsous", ""), + 1613 : ("La Pieuvre, cabinet d'avocats", ""), + 1614 : ("Toutes voiles devant - Boutique", ""), + 1615 : ("Yatch qu'à demander!", ""), + 1616 : ("Barbe Noire - Salon de beauté", ""), + 1617 : ("La mer à voir - Opticien", ""), + 1619 : ("L'écorcaire - Chirurgie arboricole", ""), + 1620 : ("Babord-tribord", ""), + 1621 : ("Salle de gym La poupe", ""), + 1622 : ("Gymnote - Electricité générale", ""), + 1624 : ("Réparation de couteaux et de peignes", ""), + 1626 : ("La perche rare - Tenues de soirée", ""), + 1627 : ("La cabane de Sam Suffit", ""), + 1628 : ("Accordeur de thons", ""), + 1629 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Ecole maternelle des p'tits loups", ""), + 1703 : ("Bar Accuda - Restaurant chinois", ""), + 1705 : ("Voiles à vendre", ""), + 1706 : ("La méduse médusée", ""), + 1707 : ("C'est assez - Boutique de cadeaux", ""), + 1709 : ("Gélée de méduse", ""), + 1710 : ("La belle bernache", ""), + 1711 : ("Restaurant de la pleine mer", ""), + 1712 : ("Salle de gymnote", ""), + 1713 : ("Chez Art - Cartes en tous genres", ""), + 1714 : ("Auberge du moulinet", ""), + 1716 : ("Maillots de bains pour sirènes", ""), + 1717 : ("Mi pacifique, mi raisin", ""), + 1718 : ("Société de taxi le Naufrage", ""), + 1719 : ("Société Je m'cache à l'eau", ""), + 1720 : ("Au requin malin", ""), + 1721 : ("Tout pour la mer", ""), + 1723 : ("Au royaume des algues", ""), + 1724 : ("Au mérou amoureux", ""), + 1725 : ("J'en pince pour toi - Crabes frais", ""), + 1726 : ("Bière à flots", ""), + 1727 : ("Je rame pour vous", ""), + 1728 : ("Limules porte-bonheur", ""), + 1729 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Les petits péchés", ""), + 1804 : ("Salle de gym Les mollusques", ""), + 1805 : ("Un petit ver pour le déjeuner", ""), + 1806 : ("Toucoule - Chapelier", ""), + 1807 : ("Coûte que soute", ""), + 1808 : ("Appât si vite!", ""), + 1809 : ("Seaux rouillés", ""), + 1810 : ("L'ancre noire", ""), + 1811 : ("Mérou tu vas chercher tout ça?", ""), + 1813 : ("A mâts couverts, conseiller", ""), + 1814 : ("Le Ho Hisse", ""), + 1815 : ("Quoi de neuf dockteur ?", ""), + 1818 : ("Café des sept mers", ""), + 1819 : ("Au dîner des dockers", ""), + 1820 : ("L'hameçon gobé - Farces et attrapes", ""), + 1821 : ("Chez Neptoon", ""), + 1823 : ("A la pomme de mât", ""), + 1824 : ("Au chien pas gai", ""), + 1825 : ("Le hareng sort! Marché aux poissons", ""), + 1826 : ("Le placard de Gérard", ""), + 1828 : ("Palais du lest d'Ernest", ""), + 1829 : ("Merlan l'enchanteur", ""), + 1830 : ("O sole et mio - Objets trouvés", ""), + 1831 : ("Une perle à domicile", ""), + 1832 : ("Supérette La Goélette", ""), + 1833 : ("Costumes pour gaillards d'avant", ""), + 1834 : ("Tranchement ridicule!", ""), + 1835 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Boutique à gags", ""), + 4504 : ("Quartier Général des Toons", ""), + 4506 : ("Boutique de prêt-à-porter", ""), + 4508 : ("ANIMALERIE", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tom-Tom - Tambours", ""), + 4604 : ("A quatre temps", ""), + 4605 : ("Fifi - Violons d'Ingres", ""), + 4606 : ("La case des castagnettes", ""), + 4607 : ("Vêtements Toon branchés", ""), + 4609 : ("Dot, Raie, Mie - Pianos", ""), + 4610 : ("Attention refrain!", ""), + 4611 : ("Diapasons à l'unisson", ""), + 4612 : ("Dr. Tefaispasdebile - Dentiste", ""), + 4614 : ("On rase gratis pour une chanson", ""), + 4615 : ("Pizzéria chez Piccolo", ""), + 4617 : ("La mandoline joyeuse", ""), + 4618 : ("Salles des césures", ""), + 4619 : ("En avant la musique!", ""), + 4622 : ("Oreillers à mentonnière", ""), + 4623 : ("Bémols à la dièse", ""), + 4625 : ("Tuba de dentifrice", ""), + 4626 : ("Notations", ""), + 4628 : ("Assurance accidentelle", ""), + 4629 : ("Riff - Assiettes en papier", ""), + 4630 : ("La musique est notre force", ""), + 4631 : ("Canto de vous connaître!", ""), + 4632 : ("Boutique de la danse des heures", ""), + 4635 : ("Le quotidien des cantatrices", ""), + 4637 : ("Pour la bonne mesure", ""), + 4638 : ("Boutique Hard Rock", ""), + 4639 : ("Les quatre saisons - Antiquités", ""), + 4641 : ("L'actualité du yéyé", ""), + 4642 : ("D. Taché - Nettoyage à sec", ""), + 4645 : ("Club 88", ""), + 4646 : ("", ""), + 4648 : ("Le Toon siffleur - Déménageurs", ""), + 4649 : ("Quartier Général des Toons", ""), + 4652 : ("Boutique des doubles-croches", ""), + 4653 : ("", ""), + 4654 : ("Haut perché - Toitures", ""), + 4655 : ("La clé de sol - Ecole de cuisine", ""), + 4656 : ("", ""), + 4657 : ("Quatuor du barbier", ""), + 4658 : ("Pianos en chute libre", ""), + 4659 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("L'eau de rose - Ecole de valse", ""), + 4702 : (" Timbre de bois - Fournitures pour bûcherons", ""), + 4703 : ("Gros Bizet à tous!", ""), + 4704 : ("Tina - Concerts de concertina", ""), + 4705 : ("Il est déjà cithare ?", ""), + 4707 : ("Studio d'effets sonores Doppler", ""), + 4709 : ("Pirouettes - Magasin d'alpinisme", ""), + 4710 : ("Polka tu routes si vite ? Auto-école", ""), + 4712 : ("Mets un bémol! Réparation de pneus", ""), + 4713 : ("Dos dièse - Vêtements de luxe pour hommes", ""), + 4716 : ("Harmonicas à quatre voix", ""), + 4717 : ("Sonate pas ta faute! Assurance automobile", ""), + 4718 : ("Chopins de bière et autres ustensiles de cuisine", ""), + 4719 : ("Camping-cars Madrigal", ""), + 4720 : ("Le bon Toon", ""), + 4722 : ("Doublures pour ouvertures", ""), + 4723 : ("Bach à toi! Jeux et balançoires", ""), + 4724 : ("(Cale)sons blancs pour filles et garçons", ""), + 4725 : ("Le barbier baryton", ""), + 4727 : ("Cordes vocales tressées", ""), + 4728 : ("Chante en sourdine!", ""), + 4729 : ("Librairie J'aime lyre", ""), + 4730 : ("Lettre à un pou", ""), + 4731 : ("Des Toons de bon ton", ""), + 4732 : ("Etude brute ? Troupe de théâtre", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Soufflet pour accordéons", ""), + 4736 : ("Hyminent - Préparatifs de mariage", ""), + 4737 : ("Harpe Hônneur", ""), + 4738 : ("Mécanique cantique - Cadeaux", ""), + 4739 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Crêp'chignon", ""), + 4803 : ("Quelle Mezzo! Service de domestiques", ""), + 4804 : ("Ecole myxolidienne pour serveurs de barres", ""), + 4807 : ("Massage des Brahms et des jambes", ""), + 4809 : ("C'est une cata-strophe!", ""), + 4812 : ("", ""), + 4817 : ("Magasin d'animaux ternaires", ""), + 4819 : ("Chez Yuki - Ukélélés", ""), + 4820 : ("", ""), + 4821 : ("Chez Anna - Croisières", ""), + 4827 : ("Montres Lamesure", ""), + 4828 : ("Ravel - Réveils et horloges", ""), + 4829 : ("Chez Pachelbel - Obus pour canons et fugues", ""), + 4835 : ("Ursatz pour Kool Katz", ""), + 4836 : ("Reggae royal", ""), + 4838 : ("Ecole de kazoologie", ""), + 4840 : ("Coda Pop - Boissons musicales", ""), + 4841 : ("Lyre et Lyre", ""), + 4842 : ("Société Lasyncope", ""), + 4843 : ("", ""), + 4844 : ("Moto - deux roues", ""), + 4845 : ("Les élégies élégantes d'Ellie", ""), + 4848 : ("De haute luth - Caisse d'épargne", ""), + 4849 : ("", ""), + 4850 : ("L'accord emprunté - Prêteur sur gages", ""), + 4852 : ("Flasques fleuries pour flûtes", ""), + 4853 : ("Chez Léo - Garde-feu", ""), + 4854 : ("Chez Wagner - Vidéos de violons voilés", ""), + 4855 : ("Réseau de radeau-diffusion", ""), + 4856 : ("", ""), + 4862 : ("Les quadrilles quintessencielles de Quentin", ""), + 4867 : ("M. Costello - Kazoos à gogo", ""), + 4868 : ("", ""), + 4870 : ("Chez Ziggy - Zoo et Zigeunermusik", ""), + 4871 : ("Chez Harry - Harmonies harmonieuses", ""), + 4872 : ("Freddie Fastoche - Touches de piano", ""), + 4873 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Boutique à gags", ""), + 5502 : ("Quartier Général des Toons", ""), + 5503 : ("Boutique de prêt-à-porter", ""), + 5505 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("L'œil de bouillon - Optométrie", ""), + 5602 : ("Eugène Coulissant - Cravates", ""), + 5603 : ("Arrête tes salades!", ""), + 5604 : ("Gai, gai, marions-les!", ""), + 5605 : ("Sols et meubles", ""), + 5606 : ("Pétales", ""), + 5607 : ("Bureau de composte", ""), + 5608 : ("Pop corn yéyé", ""), + 5609 : ("La baie au trésor", ""), + 5610 : ("L'œil au beurre noir - Cours de boxe", ""), + 5611 : ("Les gags de la Taupe", ""), + 5613 : ("La meule à zéro - Barbier", ""), + 5615 : ("Chez Piaf - Graines pour oiseaux", ""), + 5616 : ("Auberge de la goutte", ""), + 5617 : ("Chez Parpaillon - Papillons", ""), + 5618 : ("Deux pois deux mesures", ""), + 5619 : ("Chez Jack - Haricots géants", ""), + 5620 : ("Auberge du rateau", ""), + 5621 : ("La critique du Raisin pur", ""), + 5622 : ("La petite reine - claude - Bicyclettes", ""), + 5623 : ("Bains moussants pour oiseaux", ""), + 5624 : ("Ecoute ta mère", ""), + 5625 : ("Dur de la feuille", ""), + 5626 : ("Travaux d'aiguille (de pin)", ""), + 5627 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Le bambou du tunnel", ""), + 5702 : ("Les rateaux de Jacquot", ""), + 5703 : ("Cynthia - Magasin de photosynthèses", ""), + 5704 : ("Citronelle Citron - Voitures d'occasion", ""), + 5705 : ("Meubles en herbe à puce", ""), + 5706 : (" 14 carottes - Bijoutiers", ""), + 5707 : ("Fruit musical", ""), + 5708 : ("Sans soucis - Agence de voyages", ""), + 5709 : ("Astroturf - Tondeuses", ""), + 5710 : ("Gym des narcisses", ""), + 5711 : ("Bonneterie de jardin", ""), + 5712 : ("Statues squottes", ""), + 5713 : ("Buis clos", ""), + 5714 : ("Bouteilles d'eau de roche", ""), + 5715 : ("La Meule nouvelle", ""), + 5716 : ("Qui s'y frotte s'y pique - Prêteur sur gages", ""), + 5717 : ("La fleur qui mouille", ""), + 5718 : ("Le chèvre-feuille - Animalerie", ""), + 5719 : ("Sauge d'une nuit d'été - Détective privé", ""), + 5720 : ("La feuille de vigne - Prêt-à-porter masculin", ""), + 5721 : ("Routabaga 66 - Restaurant", ""), + 5725 : ("Boutique du grain d'orge", ""), + 5726 : ("Bert", ""), + 5727 : ("Le trou sans fond - Caisse d'épargne", ""), + 5728 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : ("Quartier Général des Toons", ""), + 5804 : ("La vase de Soisson", ""), + 5805 : ("Le cerveau lent", ""), + 5809 : ("Drôle d'oiseau - Ecole de clowns", ""), + 5810 : ("Ca ne rom à rain!", ""), + 5811 : ("Auberge Inn", ""), + 5815 : ("Des racines & des herbes", ""), + 5817 : ("Pommes et oranges", ""), + 5819 : ("Pantalons vert citron", ""), + 5821 : ("Centre de squash", ""), + 5826 : ("Matériel d'élevage de fourmis", ""), + 5827 : ("Terre bon marché", ""), + 5828 : ("Meubles Molasson", ""), + 5830 : ("Vide ton sac (de patates)", ""), + 5833 : ("Bar à salades", ""), + 5835 : ("Séjour en pots chez l'habitant", ""), + 5836 : ("Salles de bain J. Boulée", ""), + 5837 : ("L'école de la vigne", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Bibliothèque des berceuses", ""), + 9503 : ("Bar du roupillon", ""), + 9504 : ("Boutique à gags", ""), + 9505 : ("Quartier Général des Toons", ""), + 9506 : ("Boutique de prêt-à-porter", ""), + 9508 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Auberge des câlins", ""), + 9602 : ("Sommes au rabais", ""), + 9604 : ("Chez Ed - Edredons redondants", ""), + 9605 : ("Confection de bonnets de nuit", ""), + 9607 : ("Big Mama - Pyjamas des Bahamas", ""), + 9608 : ("Quand le chat dort, les souris dansent", ""), + 9609 : ("Roupillon pour trois ronds", ""), + 9613 : ("Théâtre du pays des rêves", ""), + 9616 : ("La veilleuse - Electricité générale", ""), + 9617 : ("L'enfant do - Petites musiques de nuit", ""), + 9619 : ("Relax Max", ""), + 9620 : ("PJ - Service de taxi", ""), + 9622 : ("Horloges du sommeil", ""), + 9625 : ("Histoire en boucle - Salon de beauté", ""), + 9626 : ("Histoiries Dodo", ""), + 9627 : ("Le tipi endormi", ""), + 9628 : ("Sam Suffit - Calendriers", ""), + 9629 : ("À l'édredon d'argent", ""), + 9630 : ("Marchand de sable", ""), + 9631 : ("Temps d'arrêt - Horloger", ""), + 9633 : ("Théâtre du pays des réves", ""), + 9634 : ("Je ronfle, donc je suis", ""), + 9636 : ("Assurance pour insomniaques", ""), + 9639 : ("Maison de l'hibernation", ""), + 9640 : ("Nous meublons vos rêves", ""), + 9642 : ("A la sciure de mon front", ""), + 9643 : ("Les yeux clos - Optométrie", ""), + 9644 : ("Combats d'oreillers nocturnes", ""), + 9645 : ("Auberge Viensmeborder", ""), + 9647 : ("Fais ton lit! Magasin de bricolage", ""), + 9649 : ("Bonnet blanc et blanc bonnet", ""), + 9650 : ("Réparateur de soupirs", ""), + 9651 : ("La vie est un ronflement tranquille", ""), + 9652 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Agence de voyages Vol de Nuit", ""), + 9704 : ("Animalerie du Hibou", ""), + 9705 : ("Garage de la Panne d'Oreiller", ""), + 9706 : ("Cabinet dentaire La Petite Souris", ""), + 9707 : ("Jardinerie de La Bâillerie", ""), + 9708 : ("Le Lys Douillet - Fleuriste", ""), + 9709 : ("Au Sommeil de Plomb - Plombier", ""), + 9710 : ("Rev'optique", ""), + 9711 : ("Service de réveil par téléphone", ""), + 9712 : ("Nous comptons les moutons pour vous!", ""), + 9713 : ("Roupille & Pionce, Avocats", ""), + 9714 : ("Croisière de rêve - Accastillage", ""), + 9715 : ("Banque du Doudou d'Or", ""), + 9716 : ("Le Lit en Cathédrale, farces at attrapes", ""), + 9717 : ("Pâtisserie du Croissant de Lune", ""), + 9718 : ("Sandwiches du Marchand de Sable", ""), + 9719 : ("Tout pour l'Oreiller", ""), + 9720 : ("Cours d'élocution pour somnambules", ""), + 9721 : ("Tapis du Loir", ""), + 9722 : ("Les Yeux Fermés - Spectacles en tous genres", ""), + 9725 : ("Pyjamas du Chat", ""), + 9727 : ("Ronflé, perdu", ""), + 9736 : ("Agence pour l'emploi Métiers de Rêve", ""), + 9737 : ("Au Tutu qui dort - École de danse", ""), + 9738 : ("Maison Ronflon", ""), + 9740 : ("Le Sabre Nocturne - Salle d'armes", ""), + 9741 : ("À l'Acarien Vorace - Destructeur de nuisibles", ""), + 9744 : ("Crème antirides Hicule", ""), + 9752 : ("Carburants Soporifiques", ""), + 9753 : ("Crèmes de Luna Glacées", ""), + 9754 : ("Randonnées équestres - Poney de Nuit", ""), + 9755 : ("Cinéma La Ronflette", ""), + 9756 : ("", ""), + 9759 : ("Institut de beauté du Bois - Dormant", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Boutique à gags", ""), + 3508 : ("Quartier général des Toons", ""), + 3509 : ("Boutique de prêt-à-porter", ""), + 3511 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Aurore boréale - Electricité générale", ""), + 3602 : ("Bonnets de pâques", ""), + 3605 : ("", ""), + 3607 : ("Le vieillard du blizzard", ""), + 3608 : ("A en perdre la boule (de neige)!", ""), + 3610 : ("Supérette Les Mirettes", ""), + 3611 : ("M. Lapin - Chasse-neige", ""), + 3612 : ("Conception d'igloos", ""), + 3613 : ("Glaces et miroirs", ""), + 3614 : ("Fabriquant de flocons d'avoine", ""), + 3615 : ("Omelettes norvégiennes", ""), + 3617 : ("Voyages en ballon à air froid", ""), + 3618 : ("Boule de neige! Gestion de crise", ""), + 3620 : ("Atelier de ski", ""), + 3621 : ("Glacier La fonte des neiges", ""), + 3622 : ("", ""), + 3623 : ("Croque-monsieur", ""), + 3624 : ("Sandwichs froids", ""), + 3625 : ("Tante Angèle - Radiateurs", ""), + 3627 : ("Chenil St Bernard", ""), + 3629 : ("La soupe aux pois - Café", ""), + 3630 : ("Agence de voyage Laglisse", ""), + 3634 : ("Télésièges rembourrés", ""), + 3635 : ("Bois de chauffage d'occasion", ""), + 3636 : ("Chair de poule bon marché", ""), + 3637 : ("Les patins de Patricia", ""), + 3638 : ("Hêtre ou ne pas hêtre", ""), + 3641 : ("Chez Tanguy - Bâteaux à dormir debout", ""), + 3642 : ("L'œil du cyclone - Opticien", ""), + 3643 : ("Chambre (froide) de danse", ""), + 3644 : ("Glaçons fondus", ""), + 3647 : ("Au pingouin sanguin - Magasin de smokings", ""), + 3648 : ("Glace instantanée", ""), + 3649 : ("Hambrrghers", ""), + 3650 : ("Articlités", ""), + 3651 : ("Freddy Frigo - Saucisses congelées", ""), + 3653 : ("Bijoux glacés", ""), + 3654 : ("Quartier général des Toons", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Stockage hivernal", ""), + 3703 : ("", ""), + 3705 : ("Glaçons pour deux", ""), + 3706 : ("Babas au rhume", ""), + 3707 : ("Mon igloo est mon royaume", ""), + 3708 : ("Chez Pluto", ""), + 3710 : ("Restaurant en chute libre", ""), + 3711 : ("", ""), + 3712 : ("Au royaume du déluge", ""), + 3713 : ("Les dents qui claquent - Dentiste polaire", ""), + 3715 : ("Les bonnes soupes de Tante Artique", ""), + 3716 : ("Salage et poivrage des routes", ""), + 3717 : ("Juneau sais pas ce que vous voulez dire", ""), + 3718 : ("Inventeur de chambres à air", ""), + 3719 : ("Glaçon en cornet", ""), + 3721 : ("Aux bonnes affaires glissantes", ""), + 3722 : ("Boutique d'après-ski", ""), + 3723 : ("Chez Tremblotte - globes des neiges", ""), + 3724 : ("La chronique des rhumeurs", ""), + 3725 : ("Alluge-toi un instant", ""), + 3726 : ("Couvertures solaires", ""), + 3728 : ("Chasse-neige à la pelle", ""), + 3729 : ("", ""), + 3730 : ("Achat et vente de bonhommes de neige", ""), + 3731 : ("Cheminées portatives", ""), + 3732 : ("Au nez gelé", ""), + 3734 : ("Regards glacés - Optométrie", ""), + 3735 : ("Calottes glaciaires", ""), + 3736 : ("Cubes de glace bon marché", ""), + 3737 : ("Restaurant de la pente", ""), + 3738 : ("Chaud devant!", ""), + 3739 : ("Quartier général des Toons", ""), +# titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : (lToonHQ, ""), + 3806 : ("Croisières Tartiflette", ""), + 3807 : ("Nuages d'occasion", ""), + 3808 : ("Gîte Gilet", ""), + 3809 : ("Glaces de découverte", ""), + 3810 : ("Pelisses Municipales", ""), + 3811 : ("L'Ange Neige", ""), + 3812 : ("Chaussons pour chatons", ""), + 3813 : ("Après-skis biodégradables", ""), + 3814 : ("Pailles à glaçons", ""), + 3815 : ("Chalet Frisquet", ""), + 3816 : ("Au gui tout neuf", ""), + 3817 : ("Club Le Verglas", ""), + 3818 : ("La pelle des cîmes", ""), + 3819 : ("Ramonage et dégivrage", ""), + 3820 : ("Blanchisserie de neige", ""), + 3821 : ("Sports d'hibernation", ""), + 3823 : ("Fondation des Pluies", ""), + 3824 : ("Froids les marrons!", ""), + 3825 : ("Chapeaux tout frais", ""), + 3826 : ("Saperlichaussette!", ""), + 3827 : ("Couronnes de gui", ""), + 3828 : ("Le potager du bonhomme de neige", ""), + 3829 : ("Frigo Déco", ""), + 3830 : ("Voyons Voir, dégivrage de monocles", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Désolé, tu n'as plus\n le temps." +ClosetNotOwnerMessage = "Ce n'est pas ton placard, mais tu peux essayer les vêtements." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Supprimer" +ClosetAreYouSureMessage = "Tu as supprimé des vêtements. Veux-tu vraiment les supprimer?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Vraiment supprimer %s?" +ClosetShirt = "cette chemise" +ClosetShorts = "ce short" +ClosetSkirt = "cette jupe" +ClosetDeleteShirt = "Supprimer\nchemise" +ClosetDeleteShorts = "Supprimer\nshort" +ClosetDeleteSkirt = "Supprimer\njupe" + +# EstateLoader.py +EstateOwnerLeftMessage = "Désolé, le(la) propriétaire de cette maison est parti(e). Retour au terrain de jeux dans %s secondes" +EstatePopupOK = lOK +EstateTeleportFailed = "Impossible de retourner à la maison. Essaie encore!" +EstateTeleportFailedNotFriends = "Désolé, %s est chez un Toon avec qui tu n'es pas ami(e)." + +# DistributedTarget.py +EstateTargetGameStart = "Le jeu des cibles tooniques a commencé!" +EstateTargetGameInst = "Plus tu tires dans la cible rouge, et plus tu remportes de tooniques." +EstateTargetGameEnd = "le jeu des cibles tooniques est maintenant terminé..." + +# DistributedCannon.py +EstateCannonGameEnd = "La location du jeu de canon est terminée." + +# DistributedHouse.py +AvatarsHouse = "Maison %s\n" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Désolé, ce n'est pas ta tirelire." +DistributedBankNotOwner = "Désolé, ce n'est pas ta tirelire." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Tout vendre" +FishTankValue = "Salut,%(name)s! Tu as %(num)s poisson(s) dans ton seau pour une valeur totale de %(value)s bonbon(s). Veux-tu vendre le tout ?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Tout vendre" +FlowerBasketValue = "%(name)s, tu as %(num)s fleurs dans ton panier d'une valeur totale de %(value)s bonbons. Veux-tu toutes les vendre?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = "de " + name + else: + possesive = "de " + name + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold' : ('A toujours faim', 'A souvent faim', + 'A quelquefois faim', 'A rarement faim',), + 'boredomThreshold' : ("S'ennuie toujours", "S'ennuie souvent", + "S'ennuie quelquefois", "S'ennuie rarement",), + 'angerThreshold' : ('Toujours ronchon', 'Souvent ronchon', + 'Quelquefois ronchon', 'Rarement ronchon',), + 'forgetfulness' : ('Oublie toujours', 'Oublie souvent', + 'Oublie quelquefois', 'Oublie rarement',), + 'excitementThreshold' : ('Très calme', 'Plutôt calme', + 'Plutôt excité', 'Très excité',), + 'sadnessThreshold' : ('Toujours triste', 'Souvent triste', + 'Quelquefois triste', 'Rarement triste',), + 'restlessnessThreshold' : ('Toujours agité', 'Souvent agité', + 'Quelquefois agité', 'Rarement agité',), + 'playfulnessThreshold' : ('Rarement joueur', 'Quelquefois joueur', + 'Souvent joueur', 'Toujours joueur',), + 'lonelinessThreshold' : ('Toujours solitaire', 'Souvent solitaire', + 'Quelquefois solitaire', 'Rarement solitaire',), + 'fatigueThreshold' : ('Toujours fatigué', 'Souvent fatigué', + 'Quelquefois fatigué', 'Rarement fatigué',), + 'confusionThreshold' : ('Toujours perplexe', 'souvent perplexe', + 'Quelquefois perplexe', 'Rarement perplexe',), + 'surpriseThreshold' : ('Toujours surpris', 'souvent surpris', + 'Quelquefois surpris', 'Rarement surpris',), + 'affectionThreshold' : ('Rarement affectueux', 'Quelquefois affectueux', + 'Souvent affectueux', 'Toujours affectueux',), + } + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Appuie sur la touche \"Page précédente\" pour mieux voir." + +FireworksJuly4Beginning = lToonHQ+": Welcome to summer fireworks! Enjoy the show!" +FireworksJuly4Ending = lToonHQ+": Hope you enjoyed the show! Have a great summer!" +FireworksFebruary14Beginning = lToonHQ+": Joyeuse Saint Valentin à tous les amoureux!" +FireworksFebruary14Ending = lToonHQ+": Joyeuse Saint Valentin à tous les amoureux!" +FireworksJuly14Beginning = lToonHQ+": Feux d'artifices du 14 Juillet: Profitez du spectacle!" +FireworksJuly14Ending = lToonHQ+": Nous espérons que vous avez profité du spectacle!" +FireworksOctober31Beginning = lToonHQ+": Bons feux d'artifice!" +FireworksOctober31Ending = lToonHQ+": Nous espérons que vous avez aimé les feux d'artifice!" +FireworksNewYearsEveBeginning = lToonHQ+": Bonne année! Profitez du feu d'artifice!" +FireworksNewYearsEveEnding = lToonHQ+": Nous espérons que vous avez profité du spectacle! Bonne année!" +FireworksBeginning = lToonHQ+": Bons feux d'artifice!" +FireworksEnding = lToonHQ+": Nous espérons que vous avez aimé les feux d'artifice!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "ASTUCE TOON:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Pour vérifier rapidement les progrès de ton défitoon, maintiens enfoncée la touche \"Fin\".", + "Pour vérifier rapidement ta page de gags, maintiens enfoncée la touche \"Première page\".", + "Pour ouvrir ta liste d'contacts, appuie sur la touche \"F7\".", + "Pour ouvrir ou fermer ton journal de bord, appuie sur la touche \"F8\".", + "Pour regarder vers le haut, appuie sur la touche \"Page précédente\"; pour regarder vers le bas, appuie sur la touche \"Page suivante\".", + "Appuie sur la touche \"Contrôle\" pour sauter.", + "Appuie sur la touche \"F9\" pour faire une capture d'écran qui sera enregistrée dans le dossier Toontown de ton ordinateur.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Tu peux échanger des codes d'ami secret avec des personnes que tu connais en dehors de Toontown pour pouvoir chatter avec eux dans Toontown.", + "Tu peux changer ta résolution d'écran, régler le son et d'autres options dans la page d'options du journal de bord.", + "Essaie les vêtements de tes contacts, qui sont dans les placards de leur maison.", + "Tu peux rentrer chez toi grâce au bouton \"Retour à la maison\" sur ta carte.", + "Chaque fois que tu termines un défitoon avec succès, tes rigolpoints sont automatiquement ajoutés.", + "Tu peux voir la collection dans les boutiques de prêt-à-porter même sans ticket d'habillement.", + "Les récompenses de certains défitoons te permettent d'avoir plus de gags et de bonbons.", + "Tu peux avoir jusqu'à 50 contacts sur ta liste d'contacts.", + "La récompense de certains défitoons te permet de te téléporter jusqu'aux terrains de jeux de Toontown par la carte du journal de bord.", + "Récupère tes rigolpoints sur les terrains de jeux en ramassant des trésors tels que des étoiles et des cornets de glace.", + "Si tu as besoin de te soigner rapidement après un combat difficile, va chez toi et ramasse des cornets de glace.", + "Pour changer la visualisation de ton Toon, appuie sur la touche de tabulation.", + "Quelquefois tu peux trouver plusieurs défitoons différents proposés pour la même récompense. Fais ton choix!", + "Trouver des contacts qui font le même défitoon que toi est une manière amusante de progresser dans le jeu.", + "Tu n'as jamais besoin d'enregistrer ta progression dans Toontown. Les serveurs de Toontown enregistrent toutes les informations nécessaires en continu.", + "Tu peux parler en chuchotant à d'autres Toons en cliquant sur eux ou en les sélectionnant dans ta liste d'contacts.", + "Certaines phrases du Chat rapide provoquent une émotion animée sur ton Toon.", + "Si tu te trouves dans une zone où il y a trop de monde, tu peux essayer de changer de district. Va à la page des districts dans le journal de bord et choisis-en un autre.", + "Si tu sauves activement des bâtiments, une étoile de bronze, d'argent ou d'or s'affichera au-dessus de ton Toon.", + "Si tu sauves assez de bâtiments pour avoir une étoile au-dessus de la tête, tu pourras trouver ton nom affiché sur le tableau d'un Quartier Général des Toons.", + "Les bâtiments sauvés sont quelquefois recapturés par les Cogs. La seule façon de conserver ton étoile est d'aller sauver plus de bâtiments.", + "Les noms de tes amis apparaîtront en bleu.", + # Fishing + "Essaie d'avoir toutes les espèces de poisson de Toontown!", + "Chaque mare recèle différentes sortes de poissons. Essaie-les toutes!", + "Lorsque ton seau de pêche est plein, tu peux vendre tes poissons aux vendeurs de l'animalerie sur les terrains de jeux.", + "Tu peux vendre tes poissons au vendeur de l'animalerie, près des mares ou dans les animaleries même.", + "Les cannes à pêche plus solides attrapent de plus gros poissons mais requièrent plus de bonbons.", + "Tu peux acheter des cannes à pêche plus solides dans le catalogue.", + "Les plus gros poissons valent plus de bonbons à l'animalerie.", + "Les poissons plus rares valent plus de bonbons à l'animalerie.", + "Tu peux quelquefois trouver des sacs de bonbons en pêchant.", + "Certains défitoons nécessitent de pêcher des objets dans les mares.", + "Les mares des terrains de jeux ont des poissons différents de ceux des mares des rues.", + "Certains poissons sont vraiment rares. Continue à pêcher jusqu'à ce que tu les aies tous!", + "La mare que tu as chez toi contient des poissons qui ne peuvent pas être trouvés ailleurs.", + "À chaque fois que tu as attrapé 10 espèces, tu gagnes un trophée de pêche!", + "Tu peux voir quels poissons tu as pêchés dans ton journal de bord.", + "Certains trophées de pêche te valent une rigol-augmentation.", + "La pêche est une bonne façon de gagner plus de bonbons.", + # Doudous + "Adopte un Doudou au magasin d'animaux!", + "Les magasins d'animaux ont de nouveaux Doudous à vendre tous les jours.", + "Rends-toi dans les magasins d'animaux tous les jours pour voir quels nouveaux Doudous ils ont.", + "Dans les différents quartiers, il y a des Doudous différents à adopter.", + # Karting + "Fais chauffer ton super moteur et mets un coup de turbo à ta rigo-limite.", + "Rends-toi dans le Circuit Dingo par le tunnel en forme de pneu qui se trouve dans Toontown Central.", + "Gagne des rigolpoints au Circuit Dingo.", + "Le Circuit Dingo a six pistes de course différentes." + ), + + TIP_STREET : ( + "Il existe quatre types de Cogs : les Loibots, les Caissbots, les Vendibots et les Chefbots.", + "Chaque série de gags est associée à différents niveaux de précision et de dégâts.", + "Les gags de tapage affectent tous les Cogs mais réveillent les Cogs leurrés.", + "Battre les Cogs en ordre stratégique peut grandement augmenter tes chances de gagner les batailles.", + "La série de gags \"toonique\" te permet de soigner les autres Toons lors d'une bataille.", + "Les points d'expérience des gags sont doublés pendant une invasion de Cogs!", + "Plusieurs Toons peuvent faire équipe et utiliser la même série de gags lors d'une bataille pour infliger plus de dégâts aux Cogs.", + "Lors des batailles, les gags sont utilisés dans l'ordre affiché sur le menu des gags, de haut en bas.", + "La rangée de points lumineux sur les ascenseurs des bâtiments des Cogs indiquent combien d'étages ils contiennent.", + "Clique sur un Cog pour avoir plus de détails.", + "L'utilisation de gags de haut niveau contre des Cogs de bas niveau ne donne pas de points d'expérience.", + "Un gag qui donnera de l'expérience s'affiche sur fond bleu sur le menu des gags lors de la bataille.", + "L'expérience des gags est multipliée lorsqu'ils sont utilisés à l'intérieur des bâtiments des Cogs. Les étages les plus hauts ont des coefficients de multiplication plus grands.", + "Lorsqu'un Cog est vaincu, chacun des Toons ayant participé est crédité de la victoire sur ce Cog lorsque la bataille est terminée.", + "Chaque rue de Toontown a différents types et niveaux de Cogs.", + "Il n'y a pas de Cogs sur les trottoirs.", + "Dans les rues, tu peux entendre des blagues en t'approchant des portes latérales.", + "Certains défitoons t'entraînent à de nouvelles séries de gags. Tu ne pourras choisir que six des sept séries de gags, alors choisis bien!", + "Les pièges ne sont utiles que si toi ou tes contacts vous mettez d'accord pour utiliser les leurres lors d'une bataille.", + "Les leurres de plus haut niveau sont moins susceptibles de manquer leur cible.", + "Les gags de plus bas niveau ont une précision moindre contre les Cogs de haut niveau.", + "Les Cogs ne peuvent plus attaquer une fois qu'ils ont été leurrés lors d'un combat.", + "Lorsque tes contacts et toi aurez repris un bâtiment aux Cogs, vos portraits seront affichés à l'intérieur du bâtiment en guise de récompense.", + "L'utilisation d'un gag toonique sur un Toon qui a un rigolmètre au maximum ne donne pas d'expérience toonique.", + "Les Cogs sont brièvement assommés lorsqu'ils sont frappés par un gag. Cela augmente la chance que les autres gags du même tour le frappent.", + "Les gags de chute ont de faibles chances d'atteindre leur but, mais la précision est accrue lorsque les Cogs ont auparavant été frappés par un autre gag lors du même tour.", + "Lorsque tu as vaincu suffisamment de Cogs, tu peux utiliser le détecteur de Cogs en cliquant sur les icônes du détecteur sur la page de la galerie des Cogs dans ton journal de bord.", + "Pendant une bataille, les tirets (-) et les X indiquent quel Cog tes équipiers sont en train d'attaquer.", + "Pendant une bataille, un voyant lumineux sur les Cogs indique leur état de santé : vert signifie en bonne santé, rouge au bord de la destruction.", + "Un maximum de quatre Toons peuvent combattre simultanément.", + "Dans la rue, les Cogs prendront plus facilement part à une bataille contre plusieurs Toons qu'à une bataille contre un seul Toon.", + "Les deux Cogs les plus difficiles de chaque type ne se trouvent que dans les bâtiments.", + "Les gags de chute ne fonctionnent jamais contre les Cogs leurrés.", + "Les Cogs ont tendance à attaquer le Toon qui leur a causé le plus de dégâts.", + "Les gags de tapage ne donnent pas de bonus contre les Cogs leurrés.", + "Si tu attends trop longtemps avant d'attaquer un Cog leurré, il se réveille. Les leurres de plus haut niveau durent plus longtemps.", + "Il y a des mares dans toutes les rues de Toontown. Certaines rues ont des espèces de poissons uniques.", + ), + + TIP_MINIGAME : ( + "Après avoir rempli ton pot de bonbons, tous les bonbons que tu gagnes aux jeux du tramway sont automatiquement versés dans ta tirelire.", + "Tu peux utiliser les flèches du clavier au lieu de la souris dans le jeu du tramway \"Imite Minnie\".", + "Dans le jeu du canon, tu peux utiliser les flèches du clavier pour déplacer ton canon et appuyer sur la touche \"Contrôle\" pour tirer.", + "Dans le jeu des anneaux, des points supplémentaires sont attribués quand le groupe entier réussit à nager dans les anneaux.", + "Un jeu parfait d'\"Imite Minnie\" double tes points.", + "Dans le tir à la corde, tu reçois plus de bonbons si tu joues contre un Cog plus fort.", + "La difficulté des jeux du tramway varie selon les quartiers, Toontown centre a les plus faciles et le Pays des rêves de Donald les plus difficiles.", + "Certains jeux du tramway ne peuvent être joués qu'en groupe.", + ), + + TIP_COGHQ : ( + "Tu dois terminer ton déguisement de Cog avant d'entrer dans le bâtiment du Chef.", + "Tu peux sauter sur les gardes du corps des Cogs pour les désactiver temporairement.", + "Tu dois faire entièrement ton déguisement Loibot avant d'aller voir le Juge.", + "Additionne les mérites Cogs par tes victoires sur les Cogs.", + "Tu obtiens plus de mérites avec des Cogs de plus haut niveau.", + "Lorsque tu as additionné assez de mérites Cogs pour gagner une promotion, va voir le vice-président des Vendibots !", + "Tu peux parler comme un Cog lorsque tu portes ton déguisement de Cog.", + "Jusqu'à huit Toons peuvent faire équipe pour combattre le vice-président des Vendibots.", + "Le vice-président des Vendibots est tout en haut du quartier général des Cogs.", + "À l'intérieur des usines des Cogs, monte les escaliers pour arriver jusqu'au contremaître.", + "Chaque fois que tu te bats dans l'usine, tu gagnes une pièce de ton déguisement de Cog.", + "Tu peux visualiser le progrès de ton déguisement de Cog dans ton journal de bord.", + "Tu peux visualiser le progrès de tes mérites sur ta page de déguisements dans ton journal de bord.", + "Assure-toi d'avoir suffisamment de gags et un rigolmètre au maximum avant d'aller voir le vice-président.", + "Si tu as une promotion, ton déguisement de Cog est mis à jour.", + "Tu dois vaincre le contremaître de l'usine pour récupérer une pièce du déguisement de Cog.", + "Récupère des Convocations du Jury en défiant des Loibots.", + "Tu reçois plus de Mérites, d'euros Cog ou de Convocations du Jury en combattant des Cogs de plus haut niveau.", + "Quand tu as récupéré assez de Convocations du Jury pour gagner une promotion, va voir le Juge !", + "Tu dois faire entièrement ton déguisement Loibot avant d'aller voir le Juge.", + "Jusqu'à huit Toons peuvent combattre ensemble le Juge Loibot.", + "Cela paie d'être perplexe : Les Cogs virtuels dans le QG Loibot ne t'accableront pas de Convocations du Jury.", + " Gagne des pièces de costume de Caissbot comme récompense en terminant les défitoons qui sont proposés dans le Pays des Rêves de Donald.", + " Les Caissbots fabriquent et font circuler leur argent, les euros Cogs, à partir de trois Fabriques à Sous - Pièce, Euro et Lingot.", + " Attends que le directeur financier soit étourdi avant de lui lancer un coffre dessus, ou il pourrait l'utiliser comme casque. Frapper le casque avec un autre coffre est la seule manière de le faire tomber.", + " Gagne des pièces de costume de Loibot comme récompense en terminant les défitoons pour le professeur Flocon.", + " Ca paie de résoudre les problèmes : les Cogs virtuels du QG Loibot ne vont pas te récompenser avec des notices du jury.", + ), + TIP_ESTATE : ( + # Doodles + "Les Doudous peuvent comprendre certaines expressions de Chat rapide. Essaie-les!", + "Utilise le menu \"Animaux familiers\" du Chat rapide pour demander à ton Doudou de faire des tours.", + "Tu peux apprendre des tours aux Doudous avec les leçons du catalogue vachement branché de Clarabelle.", + "Récompense ton Doudou quand il fait des tours.", + "Si tu te rends chez un ami, ton Doudou viendra aussi.", + "Donne un bonbon à ton Doudou quand il a faim.", + "Clique sur un Doudou pour afficher un menu grâce auquel tu pourras le nourrir, le cajoler et l'appeler.", + "Les Doudous aiment la compagnie. Invite tes contacts à venir jouer!", + "Chaque Doudou a une personnalité unique.", + "Tu peux rapporter ton Doudou et en adopter un nouveau à l'animalerie.", + "Quand un Doudou fait un tour, les Toons qui sont aux alentours sont soignés.", + "Les Doudous font leurs tours de mieux en mieux avec de l'entraînement. Un peu de persévérance!", + "Les tours plus avancés des Doudous soignent plus vite les Toons.", + "Les Doudous expérimentés peuvent faire plus de tours avant de se fatiguer.", + "Tu peux voir une liste des Doudous qui sont à proximité dans ta liste d'contacts.", + # Furniture / Cattlelog + "Achète des fournitures dans le catalogue de Clarabelle pour décorer ta maison.", + "La tirelire de ta maison contient des bonbons supplémentaires.", + "Le placard de ta maison contient des vêtements supplémentaires.", + "Rends-toi dans la maison de ton ami et essaie ses vêtements.", + "Achète de meilleures cannes à pêche dans le catalogue de Clarabelle.", + "Achète de plus grandes tirelires dans le catalogue de Clarabelle.", + "Appelle Clarabelle avec le téléphone qui est dans ta maison.", + "Clarabelle vend un placard plus grand qui contient plus de vêtements.", + "Fais de la place dans ton placard avant d'utiliser un ticket d'habillement.", + "Clarabelle vend tout ce dont tu as besoin pour décorer ta maison.", + "Vérifie ta boîte aux lettres pour trouver ta livraison après avoir commandé chez Clarabelle.", + "Les vêtements du catalogue de Clarabelle sont livrés dans l'heure.", + "Le papier peint et le revêtement de sol du catalogue de Clarabelle sont livrés dans l'heure.", + "Les meubles du catalogue de Clarabelle sont livrés un jour plus tard.", + "Stocke plus de meubles dans ton grenier.", + "Tu seras averti(e) par Clarabelle quand un nouveau catalogue sera prêt.", + "Tu seras averti(e) par Clarabelle quand un nouveau catalogue sera prêt.", + "Les nouveaux catalogues sont livrés chaque semaine.", + "Cherche les articles de vacances en édition limitée dans le catalogue.", + "Mets les meubles dont tu ne veux plus à la poubelle.", + # Fish + "Certains poissons, comme le hareng saur, sont plus communs dans les propriétés des Toons.", + # Misc + "Tu peux inviter tes contacts sur ta propriété en utilisant le Chat rapide.", + "Est-ce que tu savais que la couleur de ta maison est assortie à celle de ton panneau Choisis un Toon ?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Achète un Roadster, un Utilitoon ou une Berline au Centre Auto Dingo.", + "Customise ton kart avec des autocollants, des baguettes et plein d'autres déco au Centre Auto Dingo.", + "Gagne des tickets en faisant la course sur le Circuit Dingo.", + "Les tickets sont la seule monnaie acceptée par le Centre Auto Dingo.", + " Tu dois déposer des tickets pour pouvoir faire la course.", + "Une page spéciale de ton journal de bord te permet de customiser ton kart.", + "Une page spéciale de ton journal de bord te permet de consulter tes scores sur chaque piste.", + "Une page spéciale de ton journal de bord te permet d'afficher tes trophées.", + "Le Colisée Tortillé est la piste la plus facile du Circuit Dingo.", + " Les Landes Légères est la piste qui a le plus de collines et de bosses du Circuit Dingo.", + "Le Boulevard du Blizzard est la piste la plus excitante du Circuit Dingo.", + ), + TIP_GOLF: ( + # Golfing specific + "Appuie sur la touche de tabulation pour obtenir une vue de dessus du terrain de golf.", + "Appuie sur la flèche de déplacement vers le haut pour t'orienter vers le trou.", + "Faire un swing avec un club, c'est comme un peu comme lancer une tarte.", + ), + } + +FishGenusNames = { + 0 : "Baudruche", + 2 : "Poisson-chat", + 4 : "Poisson-clown", + 6 : "Poisson surgelé", + 8 : "Étoile de mer", + 10 : "Hareng saur", + 12 : "Poisson chien", + 14 : "Anguille douce", + 16 : "Requin nourrice", + 18 : "Crabe-roi", + 20 : "Poisson-lune", + 22 : "Hippocampe", + 24 : "Requin d'eau douce", + 26 : "Bar à coudas", + 28 : "Truite coupe-gorge", + 30 : "Thon tonléon", + 32 : "Méduse médusée", + 34 : "Raie tissante", + } + +FishSpeciesNames = { + 0 : ( "Poisson baudruche", + "Baudruche à air chaud", + "Baudruche météo", + "Baudruche à eau", + "Baudruche rouge", + ), + 2 : ( "Poisson-chat", + "Poisson-chat siamois", + "Poisson-chat piteau", + "Poisson-chat de gouttière", + "Poisson matou", + ), + 4 : ( "Poisson-clown", + "Poisson-clown triste", + "Poisson-pitre", + "Poisson-cirque", + ), + 6 : ( "Poisson surgelé", + ), + 8 : ( "Étoile de mer", + "Étoile de mer lu", + "Étoile de mer sédès", + "Étoile de mer credi", + "Étoile de mer ciatous", + ), + 10 : ( "Hareng saur", + ), + 12 : ( "Poisson chien", + "Poisson-chien de traîneau", + "Poisson-chien-chien", + "Poisson dalmatien", + "Poisson chiot", + ), + 14 : ( "Anguille douce", + "Anguille rette électrique", + ), + 16 : ( "Requin nourrice", + "Requin nourrice tique", + "Requin nourrice tourne", + ), + 18 : ( "Crabe-roi", + "Crabe roi d'Alaska", + "Vieux crabe roi", + ), + 20 : ( "Poisson-lune", + "Poisson pleine lune", + "Poisson demi-lune", + "Poisson nouvelle lune", + "Poisson croissant de lune", + "Poisson équinoxe", + ), + 22 : ( "Hippocampe", + "Hippocampe oscillant", + "Hippocampe percheron", + "Hippocampe oriental", + ), + 24 : ( "Requin d'eau douce", + "Requin de baignoire", + "Requin de piscine", + "Requin olympique", + ), + 26 : ( "Bar tabba", + "Bar amine", + "Bar ratin", + "Bar ricade", + "Bar sovie", + "Bar racé", + "Bar cadaire", + "Bar bouillé", + ), + 28 : ( "Truite coupe-gorge", + "Truite capitaine", + "Truite scorbut", + ), + 30 : ( "Thon tonléon", + "Thon-clave", + "Thon-suret", + "Thon bola", + "Thon durasé", + ), + 32 : ( "Méduse médusée", + "Poisson-cacahuète", + "Poisson pané", + "Poisson fraise", + "Poisson raisin", + ), + 34 : ( "Raie tissante", + ), + } + +CogPartNames = ( + "Cuisse gauche", "Tibia gauche", "Pied gauche", + "Cuisse droite", "Tibia droit", "Pied droit", + "Épaule gauche", "Épaule droite", "Poitrine", "Compteur de santé", "Bassin", + "Bras gauche", "Avant-bras gauche", "Main gauche", + "Bras droit", "Avant-bras droit", "Main droite", + ) + +CogPartNamesSimple = ( + "Haut du torse", + ) + +FishFirstNames = ( + "", + "Angéline", + "Arctique", + "Bébé", + "Bermuda", + "Grand", + "Fontaine", + "Bubule", + "Buster", + "Candy", + "Capitaine", + "Ciboulette", + "Choupette", + "Corail", + "Docteur", + "Toussale", + "Empereur", + "Mâchefer", + "Gros", + "Filou", + "Palmyre", + "Polochon", + "Totoche", + "Doudou", + "Jack", + "Roi", + "P'tit", + "Marin", + "Mamzelle", + "Monsieur", + "Pomme", + "Petit-Doigt", + "Prince", + "Princesse", + "Professeur", + "Bouboule", + "Reine", + "Mirage", + "Ray", + "Rosie", + "Robert", + "Poivre", + "Nicole", + "Sandy", + "Écaille", + "Dent d'or", + "Sire", + "Sacha", + "Pantoufle", + "Chipeur", + "Mini", + "Sébastien", + "P'tit-Pois", + "Étoile", + "Sucre d'orge", + "Super", + "Tigre", + "Microbe", + "Moustache", + ) + +FishLastPrefixNames = ( + "", + "Laplage", + "Noir", + "Bleu", + "Marcassin", + "Lavache", + "Minou", + "Aufond", + "Double", + "Est", + "Chichi", + "Écaille", + "Plat", + "Frais", + "Géant", + "Dorpur", + "Doré", + "Gris", + "Vert", + "Goinfre", + "Jacasse", + "Gelée", + "Dame", + "Cuir", + "Citron", + "Long", + "Nord", + "Océan", + "Octo", + "Huile", + "Perle", + "Mousse", + "Rouge", + "Ruban", + "Fleuve", + "Roc", + "Rubis", + "Barre", + "Sel", + "Mer", + "Argent", + "Tuba", + "Semelle", + "Sud", + "Hérisse", + "Surf", + "Sabre", + "Tigre", + "Triple", + "Tropical", + "Thon", + "Coucou", + "Faible", + "Ouest", + "Blanc", + "Jaune", + ) + +FishLastSuffixNames = ( + "", + "balle", + "basse", + "ventre", + "punaise", + "vole", + "beurre", + "dents", + "botte", + "crabe", + "ronchon", + "tambour", + "palme", + "poisson", + "nette", + "nageoire", + "flou", + "grogne", + "tête", + "veste", + "saut", + "sardine", + "lune", + "bouche", + "mulet", + "cou", + "nez", + "perche", + "rauque", + "coureur", + "voile", + "requin", + "coquille", + "soie", + "bave", + "vif", + "pue", + "queue", + "crapaud", + "truite", + "eau", + ) + + +CogPartNames = ( + "Cuisse gauche", "Tibia gauche", "Pied gauche", + "Cuisse droite", "Tibia droit", "Pied droit", + "Épaule gauche", "Épaule droite", "Poitrine", "Compteur de santé", "Bassin", + "Bras gauche", "Avant-bras gauche", "Main gauche", + "Bras droit", "Avant-bras droit", "Main droite", + ) + +CogPartNamesSimple = ( + "Haut du torse", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrée principale" +SellbotLegFactorySpecLobby = "Accueil" +SellbotLegFactorySpecLobbyHallway = "Couloir de l'accueil" +SellbotLegFactorySpecGearRoom = "Salle des pignons" +SellbotLegFactorySpecBoilerRoom = "Chaufferie" +SellbotLegFactorySpecEastCatwalk = "Passerelle est" +SellbotLegFactorySpecPaintMixer = "Mélangeur à peinture" +SellbotLegFactorySpecPaintMixerStorageRoom = "Réserve du mélangeur à peinture" +SellbotLegFactorySpecWestSiloCatwalk = "Passerelle du silo ouest" +SellbotLegFactorySpecPipeRoom = "Salle des tuyaux" +SellbotLegFactorySpecDuctRoom = "Salle des canalisations" +SellbotLegFactorySpecSideEntrance = "Entrée latérale" +SellbotLegFactorySpecStomperAlley = "Allée des pas perdus" +SellbotLegFactorySpecLavaRoomFoyer = "Accueil des sanitaires" +SellbotLegFactorySpecLavaRoom = "Sanitaires" +SellbotLegFactorySpecLavaStorageRoom = "Réserve des sanitaires" +SellbotLegFactorySpecWestCatwalk = "Passerelle ouest" +SellbotLegFactorySpecOilRoom = "Salle du pétrole" +SellbotLegFactorySpecLookout = "Poste d'observation" +SellbotLegFactorySpecWarehouse = "Réserve" +SellbotLegFactorySpecOilRoomHallway = "Entrée de la salle du pétrole" +SellbotLegFactorySpecEastSiloControlRoom = "Salle de contrôle du silo est" +SellbotLegFactorySpecWestSiloControlRoom = "Salle de contrôle du silo ouest" +SellbotLegFactorySpecCenterSiloControlRoom = "Salle de contrôle du silo central" +SellbotLegFactorySpecEastSilo = "Silo est" +SellbotLegFactorySpecWestSilo = "Silo ouest" +SellbotLegFactorySpecCenterSilo = "Silo central" +SellbotLegFactorySpecEastSiloCatwalk = "Passerelle du silo est" +SellbotLegFactorySpecWestElevatorShaft = "Puits de l'ascenseur ouest" +SellbotLegFactorySpecEastElevatorShaft = "Puits de l'ascenseur est" + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "VICTOIRE!!" +FishBingoJackpot = "JACKPOT!" +FishBingoGameOver = "JEU TERMINÉ" +FishBingoIntermission = "La pause\nse termine dans :" +FishBingoNextGame = "Le prochain jeu\ncommence dans :" +FishBingoTypeNormal = "Classique" +FishBingoTypeCorners = "Quatre coins" +FishBingoTypeDiagonal = "Diagonales" +FishBingoTypeThreeway = "Trois voies" +FishBingoTypeBlockout = "GRILLE ENTIERE!" +FishBingoStart = "C'est l'heure du loto des poissons! Rends-toi sur n'importe quel ponton libre pour jouer!" +FishBingoOngoing = "" +FishBingoEnd = "J'espère que le loto des poissons t'a plu." +FishBingoHelpMain = "Bienvenue au loto des poissons de Toontown! Tout le monde à la mare s'active pour remplir la grille avant la fin du temps imparti." +FishBingoHelpFlash = "Quand tu attrapes un poisson, clique sur un des carrés clignotants pour marquer la grille." +FishBingoHelpNormal = "C'est une grille de loto classique. Tu gagnes si tu remplis n'importe quel rangée verticalement, horizontalement ou diagonalement." +FishBingoHelpDiagonals = "Remplis les deux diagonales pour gagner." +FishBingoHelpCorners = "Une grille de coins facile. Remplis les quatre coins pour gagner." +FishBingoHelpThreeway = "Trois voies. Remplis les deux diagonales et la rangée du milieu pour gagner. Ça n'est pas facile!" +FishBingoHelpBlockout = "Grille entière! Remplis la grille entière pour gagner. Tu joues contre toutes les autres mares pour remporter un énorme jackpot!" +FishBingoOfferToSellFish = "Ton seau est plein de poissons. Est-ce que tu voudrais en vendre ?" +FishBingoJackpot = "Gain: %s bonbons!" +FishBingoJackpotWin = "Gain: %s bonbons!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Toonique" +ResistanceToonupItem = "%s Toonique" +ResistanceToonupItemMax = "Max" +ResistanceToonupChat = "Toons du Monde entier, Toonique!" +ResistanceRestockMenu = "À vos gags" +ResistanceRestockItem = "À vos gags %s" +ResistanceRestockItemAll = "Tous" +ResistanceRestockChat = "Toons du Monde entier, à vos gags!" +ResistanceMoneyMenu = "Bonbons" +ResistanceMoneyItem = "%s bonbons" +ResistanceMoneyChat = "Toons du Monde entier, dépensez avec sagesse!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": Bienvenue dans la résistance!" +ResistanceEmote2 = NPCToonNames[9228] + ": Utilise ton nouvel émoticone pour t'identifier auprès des autres membres." +ResistanceEmote3 = NPCToonNames[9228] + ": Bonne chance!" + +# Kart racing +KartUIExit = "Laisser le kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Acheter un kart" +KartShop_BuyAccessories = "Acheter des accessoires" +KartShop_BuyAccessory = "Acheter un accessoire" +KartShop_Cost = "Prix: %d tickets" +KartShop_ConfirmBuy = "Acheter cette %s pour %d tickets?" +KartShop_NoAvailableAcc = "Aucun accessoire de ce type n'est disponible." +KartShop_FullTrunk = "Ton coffre est plein." +KartShop_ConfirmReturnKart = "Tu veux vraiment rendre ton kart actuel?" +KartShop_ConfirmBoughtTitle = "Bravo!" +KartShop_NotEnoughTickets = "Pas assez de tickets!" + +KartView_Rotate = "Faire tourner" +KartView_Right = "Droite" +KartView_Left = "Gauche" + +# starting block +StartingBlock_NotEnoughTickets = "Tu n'as pas assez de tickets! Fais plutôt une course d'entraînement." +StartingBlock_NoBoard = "Les inscriptions sont terminées pour cette course. Tu dois attendre que la prochaine course commence." +StartingBlock_NoKart = "Il te faut d'abord un kart! Va donc voir un des vendeurs du magasin de kart." +StartingBlock_Occupied = "Ce plot de départ est actuellement occupé! Essaie un autre endroit." +StartingBlock_TrackClosed = "Nous sommes désolés, cette piste est fermée pour cause de réfection." +StartingBlock_EnterPractice = "Tu veux participer à une course d'entraînement ?" +StartingBlock_EnterNonPractice = "Veux-tu participer à une course %s pour %s tickets?" +StartingBlock_EnterShowPad = "Veux-tu garer ta voiture ici?" +StartingBlock_KickSoloRacer = "Les combats de Toons et les Grands Prix requièrent deux pilotes ou plus." +StartingBlock_Loading = "Allons à la course!" + +#stuff for leader boards +LeaderBoard_Time = "Temps" +LeaderBoard_Name = "Nom du pilote" +LeaderBoard_Daily = "Scores quotidiens" +LeaderBoard_Weekly = "Scores hebdomadaires" +LeaderBoard_AllTime = "Meilleurs scores de tous les temps" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Entraînement", + "Combat de Toons", + "Tournoi", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "Partez!" +KartRace_Reverse = " Inversé" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Stade Cinglette", + RaceGlobals.RT_Speedway_1_rev : "Stade Cinglette" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Piste Champêtre", + RaceGlobals.RT_Rural_1_rev : "Piste Champêtre" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "Circuit de la Ville", + RaceGlobals.RT_Urban_1_rev : "Circuit de la Ville" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Colisée Tortillé", + RaceGlobals.RT_Speedway_2_rev : "Colisée Tortillé" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Landes Légères", + RaceGlobals.RT_Rural_2_rev : "Landes Légères" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Bld du Blizzard", + RaceGlobals.RT_Urban_2_rev : "Bld du Blizzard" + KartRace_Reverse, + } + +KartRace_Unraced = "S/O" + +KartDNA_KartNames = { + 0:"Berline", + 1:"Roadster", + 2:"Utilitoon" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Filtre à air", + 1001: "Carburateur quadruple", + 1002: "Aigle en vol", + 1003: "Cornes de bœuf", + 1004: "Six cylindres en ligne", + 1005: "Petit déflecteur", + 1006: "Arbre à cames simple", + 1007: "Déflecteur moyen", + 1008: "Carburateur monocorps", + 1009: "Klaxon à soufflet", + 1010: "Déflecteur rayé", + #spoiler accessory names + 2000: "Aileron espace", + 2001: "Roue de secours avec rustines", + 2002: "Arceau de sécurité", + 2003: "Ailette simple", + 2004: "Double aileron", + 2005: "Aileron simple", + 2006: "Roue de secours standard", + 2007: "Ailette simple", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "Klaxon 2 tons", + 3001: "Pare-chocs de Freddie", + 3002: "Bas de caisse Cobalt", + 3003: "Pots latéraux Cobra", + 3004: "Pots latéraux droits", + 3005: "Pare-chocs dentelés", + 3006: "Bas de caisse carbone", + 3007: "Bas de caisse bois", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "Pots arrières courbés", + 4001: "Pare-chocs Splash", + 4002: "Double échappement", + 4003: "Doubles ailettes simples", + 4004: "Bavettes simples", + 4005: "Échappement de quad", + 4006: "Doubles élargisseurs de caisse", + 4007: "Méga échappement", + 4008: "Doubles ailettes rayées", + 4009: "Doubles ailettes bulle", + 4010: "Bavettes rayées", + 4011: "Bavettes Mickey", + 4012: "Bavettes dentelées", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "Turbo", + 5001: "Lune", + 5002: "Roue avec rustine", + 5003: "Trois rayons", + 5004: "Couvercle peinture", + 5005: "Cœur", + 5006: "Mickey", + 5007: "Cinq boulons", + 5008: "Daisy", + 5009: "Basket-ball", + 5010: "Hypno", + 5011: "Tribal", + 5012: "Pierre précieuse", + 5013: "Cinq rayons", + 5014: "Pacotille", + #decal accessory names + 6000: "Numéro cinq", + 6001: "Éclaboussure", + 6002: "Damiers", + 6003: "Flammes", + 6004: "Cœurs", + 6005: "Bulles", + 6006: "Tigre", + 6007: "Fleurs", + 6008: "Éclair", + 6009: "Ange", + #paint accessory names + 7000: "Vertanis", + 7001: "Pêche", + 7002: "Rouge vif", + 7003: "Rouge", + 7004: "Bordeaux", + 7005: "Sienne", + 7006: "Marron", + 7007: "Havane", + 7008: "Corail", + 7009: "Orange", + 7010: "Jaune", + 7011: "Crème", + 7012: "Citrine", + 7013: "Citron vert", + 7014: "Vert marin", + 7015: "Vert", + 7016: "Bleu clair", + 7017: "Bleuaqua", + 7018: "Bleu", + 7019: "Bleupervenche", + 7020: "Bleu roi", + 7021: "Bleu ardoise", + 7022: "Violet", + 7023: "Lavande", + 7024: "Rose", + 7025: "Gris", + 7026: "Noir", + } + +RaceHoodSpeedway = "Circuit" +RaceHoodRural = "Champêtre" +RaceHoodUrban = "Ville" +RaceTypeCircuit = "Tournoi" +RaceQualified = "Tu es qualifié(e)" +RaceSwept = "Tu les as balayés" +RaceWon = "Tu as gagné" +Race = "parcours" +Races = "parcours" +Total = "total" +GrandTouring = "Grand Tour" + +def getTrackGenreString(genreId): + genreStrings = [ "Circuit", + "Pays", + "Ville" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "panneau ville1_tunnel" + elif trackId == 1 and padId == 0: + return "panneau campagne_tunnel1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "panneau %s_%stunnel" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.TotalQualifiedRaces) + " " + Races + " au " + Total, + # won race trophies + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.TotalWonRaces) + " " + Races + " au " + Total, + #qualified circuit races + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # won circuit race trophies + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # swept circuit races + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + str(RaceGlobals.TrophiesPerCup * 2) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + str(RaceGlobals.TrophiesPerCup * 3) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + ] + +KartRace_TitleInfo = "Prépare-toi pour la course" +KartRace_SSInfo = "Bienvenue au stade Cinglette!\nPied au plancher, et on s'accroche. Ça va secouer!\n" +KartRace_CoCoInfo = "Bienvenue au Colisée Tortillé ! Utilise l'inclinaison des virages pour maintenir ta vitesse !\n" +KartRace_RRInfo = "Bienvenue sur la piste Champêtre!\nAttention aux animaux, reste bien sur la piste!\n" +KartRace_AAInfo = "Bienvenue aux Landes légères ! Tiens bien ton chapeau ! Ça a l'air d'être plein de bosses par ici...\n" +KartRace_CCInfo = "Bienvenue sur le circuit de la Ville!\nAttention aux piétons quand tu fonces à travers la ville!\n" +KartRace_BBInfo = "Bienvenue au Boulevard du Blizzard ! Attention à ta vitesse. Il se peut qu'il y ait de la glace par là-bas.\n" +KartRace_GeneralInfo = "Utilise la touche Contrôle pour lancer les gags que tu ramasses sur la piste, et les flèches pour diriger ton kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'quotidien', + RaceGlobals.Weekly : 'hebdomadaire', + RaceGlobals.AllTime : 'de tous les temps', + } + +KartRace_FirstSuffix = 'er' +KartRace_SecondSuffix = 'ème' +KartRace_ThirdSuffix = ' rd' +KartRace_FourthSuffix = ' th' +KartRace_WrongWay = 'Sens\ninterdit!' +KartRace_LapText = "Tour %s" +KartRace_FinalLapText = "Dernier tour!" +KartRace_Exit = "Sortir de la course" +KartRace_NextRace = "Course suivante" +KartRace_Leave = "Quitter la course" +KartRace_Qualified = "Qualifié(e)!" +KartRace_Record = "Record!" +KartRace_RecordString = 'Tu as établi un nouveau %s record pour %s! Ton bonus est de %s tickets.' +KartRace_Tickets = " Tickets" +KartRace_Exclamations = "!" +KartRace_Deposit = "Dépôt" +KartRace_Winnings = "Gains" +KartRace_Bonus = "Bonus" +KartRace_RaceTotal = "Total course" +KartRace_CircuitTotal = "Circuit entier" +KartRace_Trophies = "Trophées" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s" + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "Qualifié:\n" +KartRace_RaceTimeout = "Tu as fini après la fin de la course. Tes tickets ont été remboursés. Essaie encore!" +KartRace_RaceTimeoutNoRefund = "Tu as mis trop de temps à finir la course. Tes tickets n'ont pas été remboursés parce que le Grand Prix a déjà commencé. Essaie à nouveau !" +KartRace_RacerTooSlow = "Tu as mis trop de temps à finir la course. Tes tickets ne te sont pas remboursés. Fais une autre course !" +KartRace_PhotoFinish = "Photo à l'arrivée" +KartRace_CircuitPoints = "Score" + +CircuitRaceStart = "Le Grand Prix Toontown au Circuit Dingo va commencer ! Pour gagner la compétition, remporte le maximum de points en trois courses consécutives !" +CircuitRaceOngoing = "Bienvenue ! Le Grand Prix de Toontown bat son plein." +CircuitRaceEnd = "Le Grand Prix Toontown est terminé pour aujourd'hui. Rendez-vous lundi prochain pour une nouvelle édition." + +# Trick-or-Treat holiday +TrickOrTreatMsg = "Tu as déjà\ntrouvé cette friandise." + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Bon, on a quoi au registre aujourd'hui ?" +LawbotBossTempIntro1 = "Ha, on a le procès d'un Toon !" +LawbotBossTempIntro2 = "L'accusation a de bonnes cartes." +LawbotBossTempIntro3 = "Et voilà les avocats commis d'office." +LawbotBossTempIntro4 = "Attendez une minute... Vous êtes des Toons !" +LawbotBossTempJury1 = "La sélection du jury va maintenant commencer." +LawbotBossHowToGetEvidence = "Touche la barre des témoins pour obtenir des preuves." +LawbotBossTrialChat1 = "La séance est ouverte." +LawbotBossHowToThrowPies = "Appuie sur la touche « Inser » pour envoyer les preuves\n sur les avocats ou dans la balance !" +LawbotBossNeedMoreEvidence = "Il te faut plus de preuves !" +LawbotBossDefenseWins1 = "Ce n'est pas possible ! La défense a gagné ?" +LawbotBossDefenseWins2 = "Non. Je déclare le procès nul ! Un nouveau procès va être programmé." +LawbotBossDefenseWins3 = "Hmmmpfff. Je serai dans mon cabinet !" +LawbotBossProsecutionWins = "Je suis en faveur du plaignant" +LawbotBossReward = "Je décerne une promotion et le pouvoir de convoquer des Cogs" +LawbotBossLeaveCannon = "Laisse le canon" +LawbotBossPassExam = "Alors comme ça, tu as réussi le concours du barreau." +LawbotBossTaunts = [ + "%s, je te trouve coupable d'outrage à la cour !", + "Objection accordée !", + "Rayez ça du procès-verbal.", + "Ton appel a été rejeté. Je te condamne à la tristesse !", + "Silence dans l'audience !", + ] +LawbotBossAreaAttackTaunt = "Vous êtes tous coupables d'outrage à la cour!" +WitnessToonName = "Bumpy Bourdonnette" +WitnessToonPrepareBattleTwo = "Oh non! Il n'y a que des Cogs dans le jury!\aVite, utilise les canons et tire sur des jurés Toons sur le banc des jurés.\aNous avons besoin de %d pour équilibrer la balance." +WitnessToonNoJuror = "Oh là là, aucun juré Toon. Ça va être un procès difficile." +WitnessToonOneJuror = "Super! Il y a 1Toon parmi les jurés!" +WitnessToonSomeJurors = "Super! Il y a %d Toons parmi les jurés!" +WitnessToonAllJurors = "Fantastique! Tous les jurés sont des Toons!" +WitnessToonPrepareBattleThree = "Dépêche-toi de toucher la barre des témoins pour obtenir des preuves.\aAppuie sur la touche «Inser» pour envoyer les preuves sur les avocats ou sur la défense." +WitnessToonCongratulations = "Tu as réussi! Merci pour cette défense spectaculaire!\aPrends ces papiers que le Juge a oubliés.\aAvec ça, tu pourras convoquer des Cogs à partir de ta page de Galerie de Cogs." +WitnessToonLastPromotion = "\aWow, tu as atteint le niveau %s sur ton costume de Cog!\aC'est la plus haute promotion que peuvent atteindre les Cogs.\aTu ne peux plus monter ton costume de Cog en grade, mais tu peux évidemment continuer à travailler pour la résistance!" +WitnessToonHPBoost = "\aTu as fait beaucoup de travail pour la résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations!" +WitnessToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aLe Conseil des Toons te remercie d'être revenu défendre encore plus de Toons!" +WitnessToonBonus = "Merveilleux! Tous les avocats sont étourdis. Le poids de tes preuves est %s fois plus lourd pendant %s secondes." + +WitnessToonJuryWeightBonusSingular = { + 6: "C'est un cas difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 7: "C'est un cas très difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 8: "C'est le cas le plus difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", +} + +WitnessToonJuryWeightBonusPlural = { + 6: "C'est un cas difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 7: "C'est un cas très difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 8: "C'est le cas le plus difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", +} + +# Cog Summons stuff +IssueSummons = "Convocation" +SummonDlgTitle = "Convoquer un Cog" +SummonDlgButton1 = "Convoquer un Cog" +SummonDlgButton2 = "Assigner un bâtiment Cog" +SummonDlgButton3 = "Convoquer une invasion de Cogs" +SummonDlgSingleConf = "Veux-tu convoquer un %s?" +SummonDlgBuildingConf = "Veux-tu convoquer un %s à se rendre dans un bâtiment Toon à proximité?" +SummonDlgInvasionConf = "Veux-tu convoquer une invasion de %s?" +SummonDlgNumLeft = "Il t'en reste %s." +SummonDlgDelivering = "Envoi des convocations..." +SummonDlgSingleSuccess = "Tu as réussi à convoquer le Cog." +SummonDlgSingleBadLoc = "Malheureusement, les Cogs ne sont pas autorisés à entrer ici. Essaie un autre endroit." +SummonDlgBldgSuccess = "Tu as réussi à convoquer les Cogs. %s a accepté de les laisser prendre provisoirement le contrôle de %s!" +SummonDlgBldgSuccess2 = "Tu as réussi à convoquer les Cogs. Un commerçant a accepté de les laisser prendre provisoirement le contrôle de son magasin!" +SummonDlgBldgBadLoc = "Malheureusement, il n'y a aucun bâtiment Toon à proximité que les Cogs peuvent prendre." +SummonDlgInvasionSuccess = "Tu as réussi à convoquer les Cogs. C'est une invasion!" +SummonDlgInvasionBusy = "On ne trouve pas de %s pour l'instant. Essaie à nouveau quand l'invasion de Cogs sera terminée." +SummonDlgInvasionFail = "Désolé. L'invasion de Cogs a échoué." +SummonDlgShopkeeper = "Le commerçant" + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": Bienvenue à la Place Polaire!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Essaie pour voir si la taille te va." +PolarPlaceEffect3 = NPCToonNames[3306] + ": Ton nouveau look ne marchera que" + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "Recherche de crâne!" +LaserGameRoll = "Correspondance" +LaserGameAvoid = "Évite les crânes" +LaserGameDrag = "Mets en trois de la même\ncouleur sur une rangée" +LaserGameDefault = "Jeu inconnu" + +# Pinball text +#PinballHiScore = "High Score: %d %s\n" +#PinballYourBestScore = "Your Best Score: %d\n" +#PinballScore = "Score: %d x %d : %d" +PinballHiScore = "Score élevé: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Ton meilleur score:\n" +PinballScore = "Score: %d x %d =" +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Arbre à gags à plumes" +GagTreeJugglingBalls = "Arbre à gags à balles de jonglage" +StatuaryFountain = "Fontaine" +StatuaryToonStatue = "Statue de Toon" +StatuaryDonald = "Statue de Donald" +StatuaryMinnie = "Statue de Minnie" +StatuaryMickey1 = "Statue de Mickey" +StatuaryMickey2 = "Fontaine de Mickey" +StatuaryToonStatue = "Statue de Toon" +StatuaryToon = "Toon Statue" +StatuaryToonWave = "Statue Toon Geste" +StatuaryToonVictory = "Statue Toon Victoire" +StatuaryToonCrossedArms = 'Statue Toon Autorité' +StatuaryToonThinking = 'Statue Toon Étreinte' +StatuaryMeltingSnowman = 'Melting Snowman' +StatuaryGardenAccelerator = "Engrais Pousse-Instantanée" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Rouge','Orange','Violet','Bleu','Rose','Jaune','Blanc','Vert'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Pâquerette', + 50: 'Tulipe', + 51: 'Œillet', + 52: 'Lys', + 53: 'Jonquille', + 54: 'Pensée', + 55: 'Pétunia', + 56: 'Rose', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ("Pâquerette d'école", + 'Pâquerette paresseuse', + "Pâquerette d'été", + 'Pâquerette frisquette', + 'Pâquerette houplàlà', + 'Pâquerette guillerette', + 'Pâquerette follette', + 'Pâquerette brumette', + ), + 50: ('Unelipe', + 'Tulipe', + 'Trilipe', + ), + 51: ('Œillet myope', + 'Œillet rapide', + 'Œillet hybride', + 'Œillet louche', + 'Œillet modèle', + ), + 52: ('Mugatine', + 'Lys téria', + 'Lys tigri', + 'Lys poire', + 'Lys pique', + 'Pneu-lys', + 'Lys tère', + 'Lys bis', + ), + 53: ('Jonquirille', + 'Jonquifolle', + 'Jonquirafe', + 'Jonquipasse', + ), + 54: ('Pensée à rien', + 'Chim-pensée', + 'Pensée zy', + 'Pensargarine', + 'Pensée folle' + ), + 55: ('Pétugniagnian', + 'Régitunia', + ), + 56: ("Rose estivale", + 'Rose des blés', + 'Rose colorante', + 'Rose malodorante', + 'Rose distillée', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "Étain", + 1 : "Bronze", + 2 : "Argent", + 3 : "Or", + } +WateringCanNameDict = { + 0 : "Petit", + 1 : "Moyen", + 2 : "Grand", + 3 : "Énorme", + } +GardeningPlant = "Plante" +GardeningWater = "Eau" +GardeningRemove = "Retirer" +GardeningPick = "Cueillir" +GardeningFull = "Full" +GardeningSkill = "Habileté" +GardeningWaterSkill = "Habileté à arroser" +GardeningShovelSkill = "Habileté avec la pelle" +GardeningNoSkill = "Pas d'habileté améliorée" +GardeningPlantFlower = "Plante\nFleur" +GardeningPlantTree = "Plante\nArbre" +GardeningPlantItem = "Plante\nArticle" +PlantingGuiOk = "Plante" +PlantingGuiCancel = "Annuler" +PlantingGuiReset = "Tout effacer" +GardeningChooseBeans = "Choisis les bonbons que tu veux planter" +GardeningChooseBeansItem = "Choisis les bonbons que tu veux planter." +GardeningChooseToonStatue = "Choisis le Toon dont tu veux créer la statue." +GardenShovelLevelUp = "Félicitations, tu as gagné une pelle %(shovel)s ! Tu as maîtrisé les fleurs de %(oldbeans)d bonbons ! Pour avancer, tu dois cueillir des fleurs de %(newbeans)d bonbons." +GardenShovelSkillLevelUp = "Félicitations ! Tu as maîtrisé les fleurs de %(oldbeans)d bonbons ! Pour avancer, tu dois cueillir des fleurs de %(newbeans)d bonbons." +GardenShovelSkillMaxed = "Extraordinaire ! Tu as explosé ton habileté avec la pelle !" + +GardenWateringCanLevelUp = "Félicitations, tu as gagné un nouvel arrosoir!" +GardenMiniGameWon = "Félicitations, tu as arrosé la plante!" +ShovelTin = "Pelle d'étain" +ShovelSteel = "Pelle de bronze" +ShovelSilver = "Pelle d'argent" +ShovelGold = "Pelle d'or" +WateringCanSmall = "Petit arrosoir" +WateringCanMedium = "Arrosoir moyen" +WateringCanLarge = "Grand arrosoir" +WateringCanHuge = "Énorme arrosoir" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('rouge', 'vert', 'orange','violet','bleu','rose','jaune', + 'bleu de cyan','argenté') +PlantItWith = " Plante avec %s." +MakeSureWatered = " Prends d'abord soin d`arroser toutes tes plantes." +UseFromSpecialsTab = "Utilise les onglets spéciaux de ta page de jardinage." +UseSpecial = "Utilise l'outil spécial" +UseSpecialBadLocation = 'Tu ne peux utiliser cela que dans ton jardin.' +UseSpecialSuccess = 'Bravo! Les plantes que tu as arrosées viennent de pousser.' +ConfirmWiltedFlower = "Le plant de %(plant)s est fané. Veux-tu vraiment le retirer? Ce plant n'ira pas dans ton panier de fleurs, et ton habileté n'augmentera pas." +ConfirmUnbloomingFlower = "Le plant de %(plant)s ne fleurit pas. Veux-tu vraiment le retirer? Ce plant n'ira pas dans ton panier de fleurs, et ton habileté n'augmentera pas." +ConfirmNoSkillupFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Ce plant ira dans ton panier de fleurs, mais ton habileté n'augmentera PAS." +ConfirmSkillupFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Il ira dans ton panier de fleurs. Ton habileté augmentera aussi." +ConfirmMaxedSkillFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Il ira dans ton panier de fleurs. Ton habileté n'augmentera PAS car elle est déjà au maximum." +ConfirmBasketFull = "Ton panier de fleurs est plein. Tu dois d'abord vendre des fleurs." +ConfirmRemoveTree = "Veux-tu vraiment retirer le pied de %(tree)s?" +ConfirmWontBeAbleToHarvest = " Si tu retires cet arbre, tu ne pourras pas récolter de gags dans les arbres de plus haut niveau." +ConfirmRemoveStatuary = "Veux-tu vraiment supprimer définitivement le plant de %(plant)s?" +ResultPlantedSomething = "Félicitations ! Tu viens de planter un %s." +ResultPlantedSomethingAn = "Félicitations ! Tu viens de mettre en terre un plant de %s." +ResultPlantedNothing = "Ça n'a pas marché. Essaie une nouvelle combinaison de bonbons." + +GardenGagTree = "Arbre à gags" +GardenUberGag = "Über Gag" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d bonbons %s" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "un bonbon %s" % BeanColorWords[beanTuple[0]] + else: + retval += 'un' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " et un bonbon %s" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Bonbons magiques" +GardenTextMagicBeansB = "Quelques autres bonbons" +GardenSpecialDiscription = "Ce texte doit expliquer comment utiliser un certain outil spécial pour le jardin" +GardenSpecialDiscriptionB = "Ce texte doit expliquer comment utiliser un certain outil spécial pour le jardin, en pleine face !" +GardenTrophyAwarded = "Oh là là! Tu as cueilli %s sur %s fleurs. Ça mérite un trophée et une rigol-augmentation!" +GardenTrophyNameDict = { + 0 : "Brouette", + 1 : "Pelles", + 2 : "Fleur", + 3 : "Arrosoir", + 4 : "Requin", + 5 : "Poisson-scie", + 6 : "Orque", + } +SkillTooLow = "Habileté\ntrop faible" +NoGarden = "Pas de\njardi" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "Les Jeudis du Tramway" +TravelGameInstructions = "Clique vers le haut ou vers le bas pour définir ton nombre de votes. Clique sur le bouton pour voter. Atteins ton objectif secret pour remporter des bonus de bonbons. Gagne plus de votes en obtenant de bons résultats dans les autres jeux." +TravelGameRemainingVotes = "Votes restants :" +TravelGameUse = "Utiliser" +TravelGameVotesWithPeriod = "votes." +TravelGameVotesToGo = "votes restants" +TravelGameVoteToGo = "votes restants" +TravelGameUp = "" +TravelGameDown = "BAS." +TravelGameVoteWithExclamation = "Vote !" +TravelGameWaitingChoices = "Attendre que les autres joueurs votent..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['HAUT', 'BAS'] +TravelGameTotals = 'Totaux' +TravelGameReasonVotesPlural = 'Le tramway se dirige vers le %(dir)s, avec une avance de %(numVotes)d votes.' +TravelGameReasonVotesSingular = 'Le tramway se dirige vers le %(dir)s, avec une avance de %(numVotes)d vote.' +TravelGameReasonPlace = '%(name)s brise le lien. Le tramway se dirige vers le %(dir)s.' +TravelGameReasonRandom = 'Le tramway se dirige de manière aléatoire vers le %(dir)s.' +TravelGameOneToonVote = "%(name)s a utilisé %(numVotes)s votes restants %(dir)s\n" +TravelGameBonusBeans = "%(numBeans)d Bonbons" +TravelGamePlaying = 'Ensuite, le %(game)s Jeu du Tramway.' +TravelGameGotBonus = '%(name)s a obtenu un bonus de %(numBeans)s bonbons !' +TravelGameNoOneGotBonus = "Personne n'a atteint son objectif secret. Chacun remporte 1 bonbon." +TravelGameConvertingVotesToBeans = "" +TravelGameGoingBackToShop ="Il reste un seul joueur. En route pour la boutique à gags de Dingo." + +PairingGameTitle = "Jeu de mémoire Toon" +PairingGameInstructions = "Appuie sur Effacer pour ouvrir une carte. Pour remporter un point, il faut assortir deux cartes. Fais une combinaison avec l'éclat bonus et remporte un point en plus. Remporte des points supplémentaires en effectuant de petits lancers." +PairingGameInstructionsMulti = "Appuie sur Effacer pour ouvrir une carte. Appuie sur Ctrl pour demander à un autre joueur d'ouvrir une carte. Pour remporter un point, il faut assortir deux cartes. Fais une combinaison avec l'éclat bonus et remporte un point en plus. Remporte des points supplémentaires en effectuant de petits lancers." +PairingGamePerfect = 'PARFAIT !' +PairingGameFlips = 'Lancers :' +PairingGamePoints = 'Points :' + +TrolleyHolidayStart = "Les Jeudis du Tramway est sur le point de commencer. Pour jouer, monte à bord de n'importe quel tramway contenant au moins deux Toons." +TrolleyHolidayOngoing = "Bienvenue ! Les Jeudis du Tramway est en cours d'exécution." +TrolleyHolidayEnd = "Les Jeudis du Tramway est terminé pour aujourd'hui. À la semaine prochaine !" + +TrolleyWeekendStart = "Le Weekend du Tramway est sur le point de commencer ! Pour jouer, monte à bord de n'importe quel tramway contenant au moins deux Toons." +TrolleyWeekendEnd = "Le Weekend du Tramway est terminé pour aujourd'hui." + +VineGameTitle = "Jeu des Lianes" +VineGameInstructions = "Atteins la liane la plus à droite à temps. Appuie sur les flèches Haut ou Bas du clavier pour grimper le long de la liane. Appuie sur les flèches Droite ou Gauche pour changer de direction et sauter. Plus tu es en bas de la liane, plus il est facile de sauter. Ramasse les bananes si tu peux, mais évite les chauves-souris et les araignées." + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "Marche dans le Par", + 1: "Trou joyeux", + 2: "Trou ce qu'il faut et plus encore" + } + +GolfHoleNames = { + 0: 'Trou-en-Un', + 1: 'Putt à choux', + 2: 'Cul sec', + 3: 'Ver de green', + 4: 'Liens chauds', + 5: 'Putter Pan', + 6: 'Club de Swing', + 7: 'A P Tee', + 8: 'Planter de Tee', + 9: 'Rock And Roll', + 10: 'Trou de boguey', + 11: 'Amor Tee', + 12: 'Tee Sage', + 13: 'Par Deux Nez', + 14: 'Au Drive In', + 15: 'Cours de Swing', + 16: "Terrain d'entraînement", + 17: 'Second Souffle', + 18: 'Trou-en-Un-2', + 19: 'Putt à choux -2', + 20: 'Cul sec-2', + 21: 'Ver de green-2', + 22: 'Liens chauds-2', + 23: 'Putter Pan-2', + 24: 'Club de Swing-2', + 25: 'A P Tee-2', + 26: 'Planter de Tee-2', + 27: 'Rock And Roll -2', + 28: 'Trou de boguey-2', + 29: 'Amor Tee-2', + 30: 'Tee Sage-2', + 31: 'Par Deux Nez-2', + 32: 'Au Drive In-2', + 33: 'Cours de Swing-2', + 34: "Terrain d'entraînement-2", + 35: 'Second Souffle-2', + } + +GolfHoleInOne = "Trou-en-un" +GolfCondor = "Condor" # four Under Par +GolfAlbatross = "Albatros" # three under par +GolfEagle = "Aigle" # two under par +GolfBirdie = "Birdie" # one under par +GolfPar = "Par" +GolfBogey = "Boguey" # one over par +GolfDoubleBogey = "Double Bougey" # two over par +GolfTripleBogey = "Triple Boguey" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "Parcours terminés" +CoursesUnderPar = "Parcours sous par" +HoleInOneShots = "Trous-en-un" +EagleOrBetterShots = "Aigle ou meilleurs tirs" +BirdieOrBetterShots = "Birdie ou meilleurs tirs" +ParOrBetterShots = "Par ou meilleurs tirs" +MultiPlayerCoursesCompleted = "Parcours multijoueurs terminés" +TwoPlayerWins = "Victoires à deux joueurs" +ThreePlayerWins = "Victoires à trois joueurs" +FourPlayerWins = "Victoires à quatre joueurs" +CourseZeroWins = GolfCourseNames[0] + " Victoires" +CourseOneWins = GolfCourseNames[1] + " Victoires" +CourseTwoWins = GolfCourseNames[2] + " Victoires" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + " Trophées remportés", + str(GolfGlobals.TrophiesPerCup * 2) + " Trophées remportés", + str(GolfGlobals.TrophiesPerCup * 3) + " Trophées remportés", +] + +GolfAvReceivesHoleBest = "%(name)s a établi un nouveau record de parcours au %(hole)s !" +GolfAvReceivesCourseBest = "%(name)s a établi un nouveau record de parcours au %(course)s !" +GolfAvReceivesCup = "%(name)s remporte la %(cup)s coupe ! Rigol-augmentation !" +GolfAvReceivesTrophy = "%(name)s remporte le %(award)s trophée !" +GolfRanking = "Classement : \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "Appuie sur la flèche gauche ou droite pour changer la position du tee.\nAppuie sur Ctrl pour sélectionner." +GolfWarningMustSwing = "Avertissement : tu dois appuyer sur la touche Ctrl lors de ton prochain swing." +GolfAimInstructions = "Appuie sur la flèche gauche ou droite pour viser.\nAppuie sur la touche Ctrl et maintiens-la enfoncée pour faire ton swing." +GolferExited = "%s a quitté le terrain de golf." +GolfPowerReminder = "Maintiens la touche Ctrl enfoncée plus longtemps pour \nEnvoyer la balle plus loin" + + +# GolfScoreBoard.py +GolfPar = "Par" +GolfHole = "Trou" +GolfTotal = "Total" +GolfExitCourse = "Quitter parcours" +GolfUnknownPlayer = "???" + +# GolfPage.py +GolfPageTitle = "Golf" +GolfPageTitleCustomize = "Personnaliseur de golf" +GolfPageTitleRecords = "Meilleurs records personnels" +GolfPageTitleTrophy = "Trophées de golf" +GolfPageCustomizeTab = "Personnaliser" +GolfPageRecordsTab = "Records" +GolfPageTrophyTab = "Trophée" +GolfPageTickets = "Tickets :" +GolfPageConfirmDelete = "Effacer un accessoire ?" +GolfTrophyTextDisplay = "Trophée %(number)s : %(desc)s" +GolfCupTextDisplay = "Coupe %(number)s : %(desc)s" +GolfCurrentHistory = "Actuel %(historyDesc)s : %(num)s" +GolfTieBreakWinner = "%(name)s remporte le jeu décisif aléatoire !" +GolfSeconds = " - %(time))2f secondes" +GolfTimeTieBreakWinner = "%(name)s remporte le jeu décisif de temps de visée total !!!" + + + +RoamingTrialerWeekendStart = "Visiter Toontown va commencer ! Les joueurs libres peuvent à présent se rendre dans n'importe quel quartier !" +RoamingTrialerWeekendOngoing = "Bienvenue dans Visiter Toontown ! Les joueurs libres peuvent à présent se rendre dans n'importe quel quartier !" +RoamingTrialerWeekendEnd = "Visiter Toontown est maintenant terminé." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Bonne nouvelle ! L'expérience exclusive de double gag Test Toon a commencé." +MoreXpHolidayOngoing = "Bienvenue ! L'expérience exclusive de double gag Test Toon est en cours." +MoreXpHolidayEnd = "L'expérience exclusive de double gag Test Toon est terminée. Merci de nous avoir aidé à tester des trucs !" + + +LogoutForced = "Tu as fait une erreur\n et as été automatiquement déconnecté(e).\n Il se peut également que ton compte soit gelé.\n Sors et va faire une balade. C'est amusant." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \na sauté dans la voiturette de golf." +CountryClubBossConfrontedMsg = "%s se bat contre le Président du Club !" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "Tous les adversaires doivent être vaincus." + +# DistributedMolefield.py +MolesLeft = "Taupes restantes : %d" +MolesInstruction = "Écrasement de taupes !\nSaute sur les taupes rouges !" +MolesFinished = "Écrasement de taupe réussi !" +MolesRestarted = "Écrasement de taupe manqué ! Recommence..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "Retirer la balle de Cog !" +BustACogExit = "Quitter pour le moment" +BustACogHowto = "Comment jouer" +BustACogFailure = "Temps expiré !" +BustACogSuccess = "Bien joué !" + +# bossbot golf green games +GolfGreenGameScoreString = "Énigmes restantes : %s" +GolfGreenGamePlayerScore = "Résolu %s" +GolfGreenGameBonusGag = "Tu as gagné %s !" +GolfGreenGameGotHelp = "%s a résolu une énigme !" + +GolfGreenGameDirections = "Lance les balles à l'aide de la souris\n\n\nSi tu parviens à regrouper trois balles de la même couleur, les balles tombent\n\n\nFais disparaître toutes les balles de Cog du tableau" + +# DistributedMaze.py +enterHedgeMaze = "Retrouve vite la sortie du labyrinthe\n pour obtenir un bonus de rigolpoints !" +toonFinishedHedgeMaze = "%s \n a fini en %s position !" +hedgeMazePlaces = ["première","deuxième","troisième","quatrième"] +mazeLabel = "Le jeu du labyrinthe !" + +# Boarding Party +BoardingPartyReadme = "グループを設定する?" +BoardingGroupHide = 'Hide' +BoardingGroupShow = 'Show Boarding Group' +BoardingPartyInform = "他のトゥーンをクリックして、一緒にエレベーターに乗るグループのメンバーに招待しよう。\nメンバーは%s人までだよ。" +BoardingPartyTitle = 'Boarding Group' +QuitBoardingPartyLeader = 'Disband' +QuitBoardingPartyNonLeader = 'Leave' +QuitBoardingPartyConfirm = 'Are you sure you want to quit this Boarding Group?' +BoardcodeMissing = 'Your group cannot board because something was missing.' +BoardcodeMinLaffLeader = 'Your group cannot board because you have less than %s laff points.' +BoardcodeMinLaffNonLeaderSingular = 'Your group cannot board because %s has less than %s laff points.' +BoardcodeMinLaffNonLeaderPlural = 'Your group cannot board because %s have less than %s laff points.' +BoardcodePromotionLeader = 'Your group cannot board because you do not have enough promotion merits.' +BoardcodePromotionNonLeaderSingular = 'Your group cannot board because %s does not have enough promotion merits.' +BoardcodePromotionNonLeaderPlural = 'Your group cannot board because %s do not have enough promotion merits.' +BoardcodeSpace = 'Your group cannot board because there is not enough space.' +BoardcodeBattleLeader = 'Your group cannot board beacause you are in battle.' +BoardcodeBattleNonLeaderSingular = 'Your group cannot board beacause %s is in battle.' +BoardcodeBattleNonLeaderPlural = 'Your group cannot board beacause %s are in battle.' +BoardingInviteMinLaffInviter = 'You need %s Laff Points before being a member of this Boarding Group.' +BoardingInviteMinLaffInvitee = '%s needs %s Laff Points before being a member of this Boarding Group.' +BoardingInvitePromotionInviter = 'You need to earn a promotion before being a member of this Boarding Group.' +BoardingInvitePromotionInvitee = '%s needs to earn a promotion before being a member of this Boarding Group.' +BoardingGo = 'GO' +And = 'and' +BoardingGoingTo = 'Going To:' + +# DistributedBossbotBoss.py +BossbotBossName = "DirecteurDirecteur" +BossbotRTWelcome = "Tes Toons auront besoin de différents déguisements." +BossbotRTRemoveSuit = "Tout d'abord, enlève les costumes de Cog" +BossbotRTFightWaiter = "puis attaque les serveurs." +BossbotRTWearWaiter = "Bon travail ! À présent, enfile les vêtements du serveur." +BossbotBossPreTwo1 = "Pourquoi mets-tu autant de temps ?" +BossbotBossPreTwo2 = "Dépêche-toi et sers mon banquet !" +BossbotRTServeFood1 = "Hé, sers les plats que je pose sur ces tapis déroulants." +BossbotRTServeFood2 = "Si tu sers un Cog trois fois de suite, il explose." +BossbotResistanceToonName = "Ce bon vieux Gilles Giggles" +BossbotPhase3Speech1 = "Qu'est-ce qui se passe ici ?!" +BossbotPhase3Speech2 = "Ces serveurs sont des Toons !" +BossbotPhase3Speech3 = "Attrapez-les !!!" +BossbotPhase4Speech1 = "Si je veux que le travail soit bien fait…" +BossbotPhase4Speech2 = "je le fais moi-même." +BossbotRTPhase4Speech1 = "Bon travail ! À présent, éclabousse le Directeur avec l'eau placée sur les tables..." +BossbotRTPhase4Speech2 = "ou utilise des balles de golf pour le ralentir." +BossbotPitcherLeave = "Laisser bouteille" +BossbotPitcherLeaving = "Laisse bouteille" +BossbotPitcherAdvice = "Utilise les flèches droite et gauche pour pivoter.\nMaintiens la touche Ctrl pour augmenter la puissance.\nRelâche la touche Ctrl pour tirer." +BossbotGolfSpotLeave = "Laisser balle de golf" +BossbotGolfSpotLeaving = "Laisse balle de golf\nUtilise les flèches droite et gauche pour pivoter.\nCtrl pour tirer." +BossbotGolfSpotAdvice = " " +BossbotRewardSpeech1 = "Non ! Le Président ne va pas apprécier." +BossbotRewardSpeech2 = "Arrrggghhh !!!" +BossbotRTCongratulations = "Tu as réussi. Tu a rétrogradé le Directeur !\aTiens, prends ces Avis de licenciement oubliés par le Directeur.\aTu pourras les utiliser pour licencier les Cogs durant un combat.""" +BossbotRTLastPromotion = "\aOuah, tu as atteint le niveau %s de costume de Cog !\aLes Cogs ne peuvent pas monter plus en grade. \aTu ne peux plus mettre ton costume de Cog à niveau mais tu peux continuer de travailler pour la Résistance !""" +BossbotRTHPBoost = "\aTu as fait beaucoup pour la Résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations !""" +BossbotRTMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant !\aLe Conseil des Toons te remercie de revenir pour défendre d'autres Toons !""" +GolfAreaAttackTaunt = "Attention !" +OvertimeAttackTaunts = [ "Il est temps de nous réorganiser.", + "Réduisons les effectifs."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "Combat Directeur" +ElevatorBossBotCourse = "Parcour de Golf Cog" +ElevatorBossBotCourse0 = "The Front Three" +ElevatorBossBotCourse1 = "The Middle Six" +ElevatorBossBotCourse2 = "The Back Nine" +ElevatorCashBotBoss = "C.F.O Battle" +ElevatorCashBotMint0 = "Coin Mint" +ElevatorCashBotMint1 = "Dollar Mint" +ElevatorCashBotMint2 = "Bullion Mint" +ElevatorSellBotBoss = "Sellbot Battle" +ElevatorSellBotFactory0 = "Front Entrance" +ElevatorSellBotFactory1 = "Back Entrance" +ElevatorLawBotBoss = "Chief Justice Battle" +ElevatorLawBotCourse0 = "Office A" +ElevatorLawBotCourse1 = "Office B" +ElevatorLawBotCourse2 = "Office C" +ElevatorLawBotCourse3 = "Office D" + + +# CatalogNameTagItem.py +DaysToGo = "Attendre\n%s Jours" + +# DistributedIceGame.py +IceGameTitle = "Glissade sur glace" +IceGameInstructions = "Rapproche-toi le plus possible du centre vers la fin de la seconde manche. Utilise les flèches du clavier pour changer de direction et de puissance. Appuie sur la touche Ctrl pour propulser ton Toon. Touche les tonneaux pour remporter des points supplémentaires et évite le TNT !" +IceGameInstructionsNoTnt = "Rapproche-toi le plus possible du centre vers la fin de la seconde manche. Utilise les flèches du clavier pour changer de direction et de puissance. Appuie sur la touche Ctrl pour propulser ton Toon. Touche les tonneaux pour remporter des points supplémentaires." +IceGameWaitingForPlayersToFinishMove = "En attente des autres joueurs..." +IceGameWaitingForAISync = "En attente des autres joueurs..." +IceGameInfo= "Match %(curMatch)d/%(numMatch)d, Manche %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="N'oublie pas d'appuyer sur la touche Ctrl !" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "Rejoindre" +PicnicTableObserveButton = "Observer" +PicnicTableCancelButton = "Annnuler" +PicnicTableTutorial = "Comment jouer" +PicnicTableMenuTutorial = "À quel jeu veux-tu apprendre à jouer ?" +PicnicTableMenuSelect = "À quel jeu veux-tu jouer ?\nSe lever" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = " " +ChineseCheckersStartButton = "Commencer jeu" +ChineseCheckersQuitButton = "Quitter jeu" +ChineseCheckersIts = "C'est" + +ChineseCheckersYourTurn = "Ton tour" +ChineseCheckersGreenTurn = "Le tour des verts" +ChineseCheckersYellowTurn = "Le tour des jaunes" +ChineseCheckersPurpleTurn = "Le tour des violets" +ChineseCheckersBlueTurn = "Le tour des bleus" +ChineseCheckersPinkTurn = "Le tour des roses" +ChineseCheckersRedTurn = "Le tour des rouges" + +ChineseCheckersColorG = "Tu es vert" +ChineseCheckersColorY = "Tu es jaune" +ChineseCheckersColorP = "Tu es violet" +ChineseCheckersColorB = "Tu es bleu" +ChineseCheckersColorPink = "Tu es rose" +ChineseCheckersColorR = "Tu es rouge" +ChineseCheckersColorO = "Tu observes" + +ChineseCheckersYouWon = "Tu viens de remporter une partie de dames chinoises !" +ChineseCheckers = "Dames chinoises." +ChineseCheckersGameOf = "vient de remporter une partie de" + +#GameTutorials.py +ChineseTutorialTitle1 = "But" +ChineseTutorialTitle2 = "Comment jouer" +ChineseTutorialPrev = "Page précédente" +ChineseTutorialNext = "Page suivante" +ChineseTutorialDone = "Terminé" +ChinesePage1 = "Le but du jeu des dames chinoises est d'être le premier joueur à déplacer toutes ses billes du triangle en bas du tableau vers le triangle en haut du tableau. Le premier joueur qui réussit a gagné. \n" +ChinesePage2 = "Chacun à son tour, les joueurs déplacent une bille de leur couleur. Celle-ci peut être placée dans un trou adjacent ou sauter par-dessus d'autres billes. Les sauts doivent passer au-dessus d'une bille et atterrir dans un trou vide. Il est possible d'enchaîner des sauts pour des mouvements plus longs." + +CheckersPage1 = "Le but du jeu de dames est de coincer l'adversaire pour qu'il ne puisse plus bouger. Pour ce faire, tu peux capturer toutes ses pièces ou les bloquer de manière à ce qu'il soit coincé et ne puisse plus bouger." +CheckersPage2 = "Chacun leur tour, les joueurs déplacent une pièce de leur couleur. Celle-ci peut être déplacée en diagonale ou vers l'avant. Elle peut uniquement avancer sur un carré ne contenant pas de pièce. Les règles sont les mêmes pour les dames, mais elles peuvent aller en arrière." +CheckersPage3 = "Pour capturer la pièce d'un adversaire, tu dois sauter par-dessus en diagonale et te placer dans le carré vide de l'autre côté. Si tu as la possibilité de faire des sauts durant un tour, tu dois exécuter l'un d'entre eux. Tu peux enchaîner les sauts, dans la mesure où tu utilises la même pièce." +CheckersPage4 = "Une pièce devient dame lorsqu'elle atteint la dernière rangée du tableau. Une pièce qui vient de devenir dame doit attendre le prochain tour pour pouvoir sauter. En outre, les dames ont le droit de se déplacer dans toutes les directions et peuvent changer de direction durant un saut." + + + +#DistributedCheckers.py +CheckersGetUpButton = "Se lever" +CheckersStartButton = "Commencer jeu" +CheckersQuitButton = "Quitter jeu" +CheckersIts = "C'est" +CheckersYourTurn = "Ton tour" +CheckersWhiteTurn = "Le tour des blancs" +CheckersBlackTurn = "Le tour des noirs" + +CheckersColorWhite = "Tu es blanc" +CheckersColorBlack = "Tu es noir" +CheckersObserver = "Tu observes" +RegularCheckers = "Jeu de dames." +RegularCheckersGameOf = "vient de remporter une partie de" +RegularCheckersYouWon = "Tu viens de remporter une partie de dames !" + +MailNotifyNewItems = "Tu as reçu un e-mail !" +MailNewMailButton = "E-mail" +MailSimpleMail = "Note" +MailFromTag = "Note de : %s" + +# MailboxScreen.py +InviteInvitation = "the invitation" +InviteAcceptInvalidError = "L'invitation n'est plus valide." +InviteAcceptPartyInvalid = "La fête a été annulée." +InviteAcceptAllOk = "L'hôte a été informé de ta réponse" +InviteRejectAllOk = "The host has been informed that you declined the invitation." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "JANUARY", + 2: "FEBRUARY", + 3: "MARCH", + 4: "APRIL", + 5: "MAY", + 6: "JUNE", + 7: "JULY", + 8: "AUGUST", + 9: "SEPTEMBER", +10: "OCTOBER", +11: "NOVEMBER", +12: "DECEMBER" +} + +# Note 0 for Monday to match datetime +DayNames = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") +DayNamesAbbrev = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Summer Fireworks", "Celebrate Summer with a fireworks show every hour in each playground!"), + 2: ("New Year Fireworks", "Happy New Year! Enjoy a fireworks show every hour in each playground!"), + 3: ("Bloodsucker Invasion", "Happy Halloween! Stop the Bloodsucker Cogs from invading Toontown!"), + 4: ("Winter Holidays Decor", "Celebrate the Winter Holidays with Toontastic trees and streetlights!"), + 5: ("Skelecog Invasion", "Stop the Skelecogs from invading Toontown!"), + 6: ("Mr. Hollywood Invasion", "Stop the Mr. Hollywood Cogs from invading Toontown!"), + 7: ("Fish Bingo", "Fish Bingo Wednesday! Everyone at the pond works together to complete the card before time runs out."), + 8: ("Toon Species Election", "Vote on the new Toon species! Will it be Goat? Will it be Pig?"), + 9: ("Black Cat Day", "Happy Halloween! Create a Toontastic Black Cat Toon - Today Only!"), + 13: ("Trick or Treat", "Happy Halloween! Trick or treat throughout Toontown to get a nifty Halloween pumpkin head reward!"), + 14: ("Grand Prix", "Grand Prix Monday at Goofy Speedway! To win, collect the most points in three consecutive races!"), + 17: ("Trolley Tracks", "Trolley Tracks Thursday! Board any Trolley with two or more Toons to play."), + 19: ("Silly Saturdays", "Saturdays are silly with Fish Bingo, Grand Prix, and Trolley Tracks throughout the day!"), + 24: ("Ides of March", "Beware the Ides of March! Stop the Backstabber Cogs from invading Toontown!"), + 26: ("Halloween Decor", "Celebrate Halloween as spooky trees and streetlights transform Toontown!"), + } +UnknownHoliday = "Unknown Holiday %d" diff --git a/toontown/src/toonbase/TTLocalizer_french_Property.py b/toontown/src/toonbase/TTLocalizer_french_Property.py new file mode 100644 index 0000000..4378e04 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_french_Property.py @@ -0,0 +1,414 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.045 +RPmeritBarLabels = 0.15 + +#battle/RewardPanel.py +RPmeritLabelXPosition = 0.68 +RPmeritBarsXPosition = 0.955 + +#battle/BattleBase.py +BBbattleInputTimeout = 50.0 + +#battle/FireCogPanel.py +FCPtextFrameScale = 0.06 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogItemPanel.py +CIPnameLabel = 0.85 +CIPwordwrapOffset = 2 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.10) +CSgiftToggle = 0.07 +CSbackCatalogButton = 0.06 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 20 +CMunpaidChatWarning = 0.055 +CMunpaidChatWarning_text_z = 0.27 +CMpayButton = 0.05 +CMpayButton_pos_z = -0.10 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 +CMNoPasswordContinue_z = -0.25 + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.1 + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 0.8 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 1.5 + +#coghq/BossbotCogHQLoader.py +BCHQLmakeSign = 0.6 + +#coghq/DistributedGolfGreenGame.py +DGGGquitButton = 0.025 +DGGGhowToButton = 0.035 +DGGGscoreLabel = 0.05 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#estate/PlantingGUI.py +GardeningInstructionScale = 0.07 + +#estate/FlowerPanel.py +FPBlankLabelPos = -0.25 +FPBlankLabelTextScale = 0.025 + +#estate/FlowerPicker.py +FPFlowerValueTotal = 0.045 + +#estate/FlowerSellGUI.py +FSGDFTextScale = 0.048 +FSGCancelBtnTextScale = 0.04 +FSGOkBtnTextScale = 0.04 + +#estate/GardenTutorial.py +GardenTutorialPage2Wordwrap = 14.5 +GardenTutorialPage4Wordwrap = 22.5 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.45 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.06 +FPnewRecord = 0.06 + +#fishing/GenusPanel.py +GPgenus = 0.035 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 +FIcancelButtonPositionX = 0.0 +FIstopTextPositionY = -0.015 +FIstopButtonPositionX = -0.2 +FIyesButtonPositionX = -0.15 + +#golf/DistributedGolfHole.py +DGHpowerReminder = 0.09 +DGHaimInstructions = 0.065 +DGHteeInstructions = 0.065 + +#golf/DistributedGolfHole.py +DGHAimInstructScale = 0.075 +DGHTeeInstructScale = 0.075 + +#golf/GolfScoreBoard.py +GSBExitCourseBTextPose = (0.20, -.01) +GSBtitleLabelScale = 0.06 + +#golf/GolfScoreBoard.py +GSBexitCourseBPos = (0.19, -.01) +GSBtitleLabel = 0.06 + +#hood/EstateHood.py +EHpopupInfo = .08 + +#hood/Hood.py +HDenterTitleTextScale = 0.12 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.095 +ACdeleteWithPassword = 0.06 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 +ACquitButton_pos = -0.024 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 +MRPinstructionsText = 0.05 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.05 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.05 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.01 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.1 +NScolorPrecede = False + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.07 + +#minigame/DistributedPairingGame.py +DPGPointsFrameTextScale = 0.45 +DPGFlipsFrameTextScale = 0.45 + +#minigame/DistributedTravelGame.py +DTGVoteBtnTextScale = 0.05 +DTGUseLabelTextScale = 0.07 +DTGVotesPeriodLabelTextScale = 0.07 +DTGVotesToGoLabelTextScale = 0.07 +DTGUpLabelTextScale = 0.07 +DTGDownLabelTextScale = 0.07 +DTGRemainingVotesFrameTextScale = 0.6 + +#minigame/MinigameRulesPanel.py +MRPGameTitleTextScale = 0.10 +MRPGameTitleTextPos = (-0.12, 0.2, 0.092) +MRPInstructionsTextWordwrap = 32 +MRPInstructionsTextPos = (-0.12, 0.05, 0) + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.65 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.4 +PAPcall = 0.4 +PAPowner = 0.30 +PAPscratch = 0.4 +PAPstateLabel = 0.35 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.08 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.095 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0625 + +#racing/DistributedLeaderBoard.py +DLBtitleRowScale = 0.3 + +#racing/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.3 + +#raceing/DistributedRacePad.py +DRPnodeScale = 0.65 + +#racing/KartShopGui.py +KSGtextSizeBig = 0.06 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 22 + +#racing/RaceEndPanels.py +REPraceEnd = 0.065 +REPraceExit = 0.025 +REPticket_text_x = -0.7 + +#racing/RaceGUI.py +RGphotoFinish = 0.20 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#racing/DistributedRaceAI.py +DRAwaitingForJoin = 90 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.06 + +#safezone/Playground.py +PimgLabel = 0.6 + +#safezone/GZSafeZoneLoader.py +GSZLbossbotSignScale = 0.8 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 +DSDcancelButtonPositionX = 0 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.08 +TPendFrame = 0.08 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.05 +MPhoodWordwrap = 20 + +#shtiker/KartPage.py +KPkartTab = 0.06 +KPdeleteButton = 0.035 +KProtateButton = 0.03 + +#shtiker/GardenPage.py +GPBasketTabTextScale = 0.06 +GPCollectionTabTextScale = 0.06 +GPTrophyTabTextScale = 0.06 +GPSpecialsTabTextScale = 0.06 + +#shtiker/GolfPage.py +GFPRecordsTabTextScale = 0.06 +GFPRecordsTabPos = (0.82, 0, 0.1) +GFPTrophyTabTextScale = 0.06 +GFPRecordsTabTextPos = (0.03, 0.0, 0.0) +GFPRTrophyTabPos = (0.82, 0, -0.3) + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.032 +INrunButton = 0.045 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.6 + +#toon/PlayerInfoPanel.py +PIPsecretsButtonScale=0.045 + +#toon/ToonAvatarPanel.py +TAPsecretsButtonScale=0.045 + +#toon/ToonAvatarDetailPanel.py +TADPcancelPos = (-0.865, 0.0, -0.765) +TADtrackLabelPosZ = 0.17 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TSRPdialogWordwrap = 20 +TSRPtop = 0.07 +TSRPpanelScale = 0.05 +TSRPpanelPos = (-0.65, -0.70) +TSRPbrowserPosZ = -0.65 +TSRPbutton = 0.06 +TSRPteaserBottomScale = 0.06 + +#toontowngui/TeaserPanel.py (OLD) +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.08 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/TTLocalizer_german.py b/toontown/src/toonbase/TTLocalizer_german.py new file mode 100644 index 0000000..dd424b8 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_german.py @@ -0,0 +1,6559 @@ +import string +from toontown.toonbase.TTLocalizer_german_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# common names +Mickey = "Micky" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Goofy" +Pluto = "Pluto" +Flippy = "Flippy" + +# common locations +lTheBrrrgh = 'Das Brrr' +lDaisyGardens = 'Daisys Gärten' +lDonaldsDock = "Donalds Dock" +lDonaldsDreamland = "Donalds Traumland" +lMinniesMelodyland = "Minnies Melodienland" +lToontownCentral = 'Toontown Mitte' +lToonHQ = 'Toontown-\nZentrale' + +# common strings +lCancel = 'Abbrechen' +lClose = 'Schließen' +lOK = 'OK' +lNext = 'Weiter' +lNo = 'Nein' +lQuit = 'Beenden' +lYes = 'Ja' + +lHQOfficerF = 'Mitarbeiter der Zentrale' +lHQOfficerM = 'Mitarbeiter der Zentrale' + +MickeyMouse = "Micky Maus" + +AIStartDefaultDistrict = "Maushöhe" + +Cog = "Bot" +Cogs = "Bots" +ACog = "ein Bot" +TheCogs = "den Bots" +Skeleton = "Skeletobot" +SkeletonP = "Skeletobots" +ASkeleton = "ein Skeletobot" +Foreman = "Vorarbeiter" +ForemanP = "Vorarbeiter" +AForeman = "ein Vorarbeiter" +CogVP = "Bot-VP " +CogVPs = "Bot-VPs" +ACogVP = "ein Bot-VP" + +# Quests.py +TheFish = "der Fisch" +AFish = "ein Fisch" +Level = "Level " +QuestsCompleteString = "Beendet " +QuestsNotChosenString = "Nicht ausgewählt" +Period = "." + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Hallo, _avName_!", + "Hi, _avName_!", + "Na du, _avName_!", + "Wie steht's, _avName_!", + "Willkommen, _avName_!", + "Tag, _avName_!", + "Wie geht's, _avName_?", + "Guten Tag _avName_!", + ) +QuestsDefaultIncomplete = ("Wie geht's mit der Aufgabe voran, _avName_?", + "Sieht aus, als müsstest du an dieser Aufgabe noch etwas arbeiten!", + "Weiter so, _avName_!", + "Bleib weiter an dieser Aufgabe dran. Ich weiß, du schaffst das!", + "Versuch weiter, diese Aufgabe zu lösen, wir zählen auf dich!", + "Arbeite weiter an dieser Toon-Aufgabe!", + ) +QuestsDefaultIncompleteProgress = ("Du bist zum richtigen Ort gekommen, aber du musst erst noch deine Toon-Aufgabe lösen!", + "Komm wieder her, wenn du mit deiner Toon-Aufgabe fertig bist.", + "Komm wieder, wenn du mit deiner Toon-Aufgabe fertig bist.", + ) +QuestsDefaultIncompleteWrongNPC = ("Gut gelöst, diese Toon-Aufgabe. Du solltest mal _toNpcName_ besuchen._where_", + "Sieht aus, als wärst du gleich mit deiner Toon-Aufgabe fertig. Besuch mal _toNpcName_._where_.", + "Besuche _toNpcName_ um deine Toon-Aufgabe zu lösen._where_", + ) +QuestsDefaultComplete = ("Gute Arbeit! Hier deine Belohnung ...", + "Gut gemacht, _avName_! Nimm das hier als Belohnung ...", + "Spitzenleistung, _avName_! Hier deine Belohnung ...", + ) +QuestsDefaultLeaving = ("Tschüss!", + "Auf Wiedersehen!", + "Mach's gut, _avName_.", + "Bis bald, _avName_!", + "Viel Glück!", + "Viel Spaß in Toontown!", + "Bis demnächst!", + ) +QuestsDefaultReject = ("Hallo!", + "Kann ich helfen?", + "Wie geht's?", + "Na du!", + "Hab grad zu tun, _avName_.", + "Ja?", + "Tag, _avName_!", + "Willkommen, _avName_!", + "Hi, _avName_! Wie steht's?", + # Game Hints + "Weißt du schon, dass du mit F8 dein Sticker-Buch öffnen kannst? ", + "Du kannst dich mit deinem Stadtplan wieder zum Spielplatz teleportieren!", + "Du kannst mit anderen Spielern Freundschaft schließen, indem du sie anklickst.", + "Du kannst mehr über einen " + Cog + " erfahren, indem du ihn anklickst.", + "Sammle Schätze auf den Spielplätzen, um dein Lach-O-Meter zu füllen.", + Cog +"-Gebäude sind gefährlich! Geh nicht alleine rein!", + "Wenn du einen Kampf verlierst, nehmen dir die " + Cogs + " alle Gags ab.", + "Fahr mit dem Toon-Express zum Spielplatz, verdiene beim Spielen Jellybeans, um Gags zu kaufen.!", + "Du kannst noch mehr Lach-Punkte gewinnen, indem du Toon-Aufgaben löst.", + "Für jede gelöste Toon-Aufgabe erhältst du eine Belohnung.", + "Einige Belohnungen helfen dir, mehr Gags mit dir zu führen.", + "Wenn du einen Kampf gewinnst, bekommst du für jeden vertriebenen " + Cog + " eine Gutschrift für erledigte Toon-Aufgaben.", + "Wenn du ein "+ Cog + "-Gebäude zurückeroberst, geh wieder hinein und schau nach, was der Besitzer dir als spezielles Dankeschön hinterlassen hat!", + "Mit der Taste 'Bild Hoch' kannst du nach oben schauen!", + "Mit der Tab-Taste kannst du verschiedene Ansichten deiner Umgebung sehen!", + "Um geheimen Freunden zu zeigen, was du gerade denkst, gib vor deinem Gedanken ein '.' ein.", + " Wenn ein" + Cog + " angeschlagen ist, fällt es ihm schwerer, herunterfallenden Gegenständen auszuweichen.. ", + "Jede Art von "+ Cog + "-Gebäude hat ein eigenes Aussehen.", + "Wenn du "+ Cogs + " auf den höheren Stockwerken eines Gebäudes besiegst, bringt dir das höhere Geschicklichkeitspunkte ein.", + ) +QuestsDefaultTierNotDone = ("Hallo, _avName_! Du musst erst deine derzeitigen Toon-Aufgaben lösen, bevor du eine neue bekommst.", + "Hallo! Du musst erst die Toon-Aufgaben lösen, an denen du gerade arbeitest, um eine neue zu bekommen.", + "Hi, _avName_! Bevor ich dir eine neue Toon-Aufgabe geben kann, musst du erst die lösen, die du schon hast.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("Ich habe gehört, _toNpcName_ sucht dich._where_", + "Schau mal bei _toNpcName_ rein, wenn du kannst._where_", + "Besuche mal _toNpcName_ , wenn du das nächste Mal in der Nähe bist._where_", + "Schau mal bei Gelegenheit bei _toNpcName_._where_ vorbei", + "_toNpcName_ wird dir deine nächste Toon-Aufgabe geben._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s von %(numCogs)s vertrieben" +QuestsCogQuestHeadline = "GESUCHT" +QuestsCogQuestSCStringS = "Ich muss %(cogName)s%(cogLoc)s erledigen." +QuestsCogQuestSCStringP = "Ich muss ein paar %(cogName)s%(cogLoc)s vertreiben." +QuestsCogQuestDefeat = "%s vertreiben" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Hilf einem neuen Toon %s zu vertreiben" +QuestsCogNewNewbieQuestCaption = "Hilf einem neuen Toon %d Lach oder weniger" +QuestsCogOldNewbieQuestObjective = "Hilf einem Toon mit %(laffPoints)d Lachpunkten oder weniger im Kampf gegen %(objective)s" +QuestsCogOldNewbieQuestCaption = "Hilf einem Toon mit <%d Lachpunkten" +QuestsCogNewbieQuestAux = "Vertreiben" +QuestsNewbieQuestHeadline = "LEHRLING" + +QuestsCogTrackQuestProgress = "%(progress)s von %(numCogs)s vertrieben" +QuestsCogTrackQuestHeadline = "GESUCHT" +QuestsCogTrackQuestSCStringS = "Ich muss %(cogText)s%(cogLoc)s vertreiben." +QuestsCogTrackQuestSCStringP = "Ich muss ein paar %(cogText)s%(cogLoc)s vertreiben." +QuestsCogTrackQuestDefeat = "%s vertreiben" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s von %(numCogs)s vertrieben" +QuestsCogLevelQuestHeadline = "GESUCHT" +QuestsCogLevelQuestDefeat = "%s vertreiben" +QuestsCogLevelQuestDesc = "ein Level %(level)s+ %(name)s" +QuestsCogLevelQuestDescC = "%(count)s Level %(level)s+ %(name)s" +QuestsCogLevelQuestDescI = "ein Level %(level)s+ %(name)s " +QuestsCogLevelQuestSCString = "Ich muss %(objective)s%(location)s vertreiben." + +QuestsBuildingQuestFloorNumbers = ('', '> zwei', '> drei', '> vier', '> fünf') +QuestsBuildingQuestBuilding = "Gebäude" +QuestsBuildingQuestBuildings = "Gebäude" +QuestsBuildingQuestHeadline = "ERLEDIGEN" +QuestsBuildingQuestProgressString = "%(progress)s von %(num)s erledigt" +QuestsBuildingQuestString = "%s erledigen" +QuestsBuildingQuestSCString = "Ich muss %(objective)s%(location)s erledigen." + +QuestsBuildingQuestDesc = "ein %(type)s -Gebäude" +QuestsBuildingQuestDescF = "ein %(floors)s-stöckiges %(type)s-Gebäude" +QuestsBuildingQuestDescC = "%(count)s %(type)s Gebäude" +QuestsBuildingQuestDescCF = "%(count)s %(floors)s-stöckige %(type)s Gebäude" +QuestsBuildingQuestDescI = "einige %(type)s -Gebäude" +QuestsBuildingQuestDescIF = "einige %(floors)s-stöckige %(type)s-Gebäude" + +QuestFactoryQuestFactory = "Fabrik" +QuestsFactoryQuestFactories = "Fabriken" +QuestsFactoryQuestHeadline = "ERLEDIGEN" +QuestsFactoryQuestProgressString = "%(progress)s von %(num)s erledigt" +QuestsFactoryQuestString = "%s erledigen" +QuestsFactoryQuestSCString = "Ich muss %(objective)s%(location)s erledigen." + +QuestsFactoryQuestDesc = "eine %(type)s -Fabrik" +QuestsFactoryQuestDescC = "%(count)s %(type)s -Fabriken" +QuestsFactoryQuestDescI = "einige %(type)s-Fabriken" + +QuestsRescueQuestProgress = "%(progress)s von %(numToons)s gerettet" +QuestsRescueQuestHeadline = "RETTEN" +QuestsRescueQuestSCStringS = "Ich muss einen Toon%(toonLoc)s retten." +QuestsRescueQuestSCStringP = "Ich muss ein paar Toons%(toonLoc)s retten." +QuestsRescueQuestRescue = "%s retten" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "ein Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Retten:" + +QuestsRescueNewNewbieQuestObjective = "Hilf einem neuen Toon beim Retten von %s" +QuestsRescueOldNewbieQuestObjective = "Help a Toon with %(laffPoints)d Laff or less rescue %(objective)s" + +QuestCogPartQuestCogPart = "Bot-Anzugteil" +QuestsCogPartQuestFactories = "Fabriken" +QuestsCogPartQuestHeadline = "ZURÜCKHOLEN" +QuestsCogPartQuestProgressString = "%(progress)s von %(num)s zurückgeholt" +QuestsCogPartQuestString = "%s zurückholen" +QuestsCogPartQuestSCString = "Ich muss %(objective)s%(location)s zurückholen." +QuestsCogPartQuestAux = "Zurückholen:" + +QuestsCogPartQuestDesc = "ein Bot-Anzugteil" +QuestsCogPartQuestDescC = "%(count)s Bot-Anzugteile" +QuestsCogPartQuestDescI = "einige Bot-Anzugteile" + +QuestsCogPartNewbieQuestObjective = 'Hilf einem neuen Toon beim Zurückholen von %s' +QuestsCogPartOldNewbieQuestObjective = 'Help a Toon with %(laffPoints)d Laff or less retrieve %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s von %(numGags)s abgeliefert" +QuestsDeliverGagQuestHeadline = "ABLIEFERN" +QuestsDeliverGagQuestToSCStringS = "Ich muss %(gagName)s abliefern." +QuestsDeliverGagQuestToSCStringP = "Ich muss ein paar %(gagName)s abliefern." +QuestsDeliverGagQuestSCString = "Ich muss etwas abliefern." +QuestsDeliverGagQuestString = "%s abliefern" +QuestsDeliverGagQuestStringLong = "%s an _toNpcName_ abliefern." +QuestsDeliverGagQuestInstructions = "Du kannst diesen Gag im Gag-Shop kaufen, wenn du dir den Zugang verdient hast." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "ABLIEFERN" +QuestsDeliverItemQuestSCString = "Ich muss %(article)s%(itemName)s abliefern." +QuestsDeliverItemQuestString = "%s abliefern" +QuestsDeliverItemQuestStringLong = "%s an _toNpcName_ abliefern." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "BESUCHEN" +QuestsVisitQuestStringShort = "Besuchen" +QuestsVisitQuestStringLong = "_toNpcName_ besuchen" +QuestsVisitQuestSeeSCString = "Ich muss %s besuchen." + +QuestsRecoverItemQuestProgress = "%(progress)s von %(numItems)s zurückgeholt" +QuestsRecoverItemQuestHeadline = "ZURÜCKHOLEN" +QuestsRecoverItemQuestSeeHQSCString = "Ich muss einen Mitarbeiter in der Toontown-Zentrale aufsuchen" +QuestsRecoverItemQuestReturnToHQSCString = "Ich muss %s zu einem Mitarbeiter in Toontown-Zentrale zurückgeben." +QuestsRecoverItemQuestReturnToSCString = "Ich muss %(npcName)s %(item)s zurückgeben." +QuestsRecoverItemQuestGoToHQSCString = "Ich muss zur Toontown-Zentrale gehen." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Ich muss zum %s -Spielplatz." +QuestsRecoverItemQuestGoToStreetSCString = "Ich muss %(to)s %(street)s in %(hood)s gehen." +QuestsRecoverItemQuestVisitBuildingSCString = "Ich muss %s%s besuchen." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Wo ist %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Ich muss %(item)s von %(holder)s%(loc)s abholen." +QuestsRecoverItemQuestString = "%(item)s von %(holder)s abholen." +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "WÄHLEN" +QuestsTrackChoiceQuestSCString = "Ich muss zwischen %(trackA)s und %(trackB)s wählen." +QuestsTrackChoiceQuestMaybeSCString = "Vielleicht sollte ich %s wählen." +QuestsTrackChoiceQuestString = "Wähle zwischen %(trackA)s und %(trackB)s" + +QuestsFriendQuestHeadline = "FREUND" +QuestsFriendQuestSCString = "Ich muss einen Freund gewinnen." +QuestsFriendQuestString = "Einen Freund gewinnen" + +QuestsMailboxQuestHeadline = "POST" +QuestsMailboxQuestSCString = "Ich muss mal nach meiner Post schauen." +QuestsMailboxQuestString = "In den Briefkasten schauen" + +QuestsPhoneQuestHeadline = "KLARABELLA" +QuestsPhoneQuestSCString = "Ich muss Klarabella anrufen." +QuestsPhoneQuestString = "Klarabella anrufen" + +QuestsFriendNewbieQuestString = "Gewinne %d Freunde %d Lach oder weniger " +QuestsFriendNewbieQuestProgress = "%(progress)s von %(numFriends)s gewonnen" +QuestsFriendNewbieQuestObjective = "Mit %d neuen Toons anfreunden" + +QuestsTrolleyQuestHeadline = "TOON-EXPRESS" +QuestsTrolleyQuestSCString = "Ich muss mit dem Toon-Express fahren." +QuestsTrolleyQuestString = "Mit dem Toon-Express fahren." +QuestsTrolleyQuestStringShort = "Toon-Express fahren" + +QuestsMinigameNewbieQuestString = "%d Minigames" +QuestsMinigameNewbieQuestProgress = "%(progress)s von %(numMinigames)s gespielt" +QuestsMinigameNewbieQuestObjective = "%d Minigames mit neuen Toons gespielt" +QuestsMinigameNewbieQuestSCString = "Ich muss mit neuen Toons Minigames spielen." +QuestsMinigameNewbieQuestCaption = "Hilf einem neuen Toon mit %d Lach oder weniger" +QuestsMinigameNewbieQuestAux = "Spielen:" + +QuestsMaxHpReward = "Deine Lachstärke hat sich um %s erhöht." +QuestsMaxHpRewardPoster = "Belohnung: %s Punkt mehr Lachstärke" + +QuestsMoneyRewardSingular = "Du bekommst 1 Jelly Bean." +QuestsMoneyRewardPlural = "Du bekommst %s Jelly Beans." +QuestsMoneyRewardPosterSingular = "Belohnung: 1 Jelly Bean" +QuestsMoneyRewardPosterPlural = "Belohnung: %s Jelly Beans" + +QuestsMaxMoneyRewardSingular = "Du kannst jetzt 1 Jelly Bean mit dir führen." +QuestsMaxMoneyRewardPlural = "Du kannst jetzt %s Jelly Beans mit dir führen." +QuestsMaxMoneyRewardPosterSingular = "Belohnung: 1 Jelly Bean mit dir führen." +QuestsMaxMoneyRewardPosterPlural = "Belohnung: %s Jelly Beans mit dir führen." + +QuestsMaxGagCarryReward = "Du bekommst %(name)s. Du kannst jetzt %(num)s Gags mit dir führen." +QuestsMaxGagCarryRewardPoster = "Belohnung: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "Du kannst jetzt %s Toon-Aufgaben bekommen." +QuestsMaxQuestCarryRewardPoster = "Belohnung: %s Toon-Aufgaben mit dir führen." + +QuestsTeleportReward = "Du hast jetzt Teleport-Zugang zu %s." +QuestsTeleportRewardPoster = "Belohnung: Teleport-Zugang zu %s" + +QuestsTrackTrainingReward = "Du kannst jetzt für \"%s\" Gags trainieren." +QuestsTrackTrainingRewardPoster = "Belohnung: Ein Gag-Training" + +QuestsTrackProgressReward = "Du hast jetzt Bild %(frameNum)s der %(trackName)s Ablauf-Animation." +QuestsTrackProgressRewardPoster = "Belohnung: \"%(trackName)s\" Ablauf-Animationsbild %(frameNum)s" + +QuestsTrackCompleteReward = "Du darfst jetzt \"%s\" Gags mit dir führen und benutzen." +QuestsTrackCompleteRewardPoster = "Belohnung: %s -Ablauf-Abschlusstraining" + +QuestsClothingTicketReward = "Du darfst deine Kleidung wechseln" +QuestsClothingTicketRewardPoster = "Belohnung: Eine Kleidermarke" + +QuestsCheesyEffectRewardPoster = "Belohnung: %s" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "auf diesem Spielplatz" +QuestsStreetLocationThisStreet = "in dieser Straße" +QuestsStreetLocationNamedPlayground = "auf dem Spielplatz %s " +QuestsStreetLocationNamedStreet = "auf %(toStreetName)s in %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "%s's Gebäude heißt" +QuestsLocationBuildingVerb = "und das ist" +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Ich muss eine Toon-Aufgabe lösen." + +# MaxGagCarryReward names +QuestsMediumPouch = "einen mittelgroßer Beutel" +QuestsLargePouch = "einen großer Beutel" +QuestsSmallBag = "eine kleine Tasche" +QuestsMediumBag = "eine mittelgroße Tasche" +QuestsLargeBag = "eine große Tasche" +QuestsSmallBackpack = "ein kleiner Rucksack" +QuestsMediumBackpack = "ein mittelgroßer Rucksack" +QuestsLargeBackpack = "ein großer Rucksack" +QuestsItemDict = { + 1 : ["Brille", "Brillen", "eine "], + 2 : ["Schlüssel", "Schlüssel", "einen "], + 3 : ["Tafel", "Tafeln", "eine "], + 4 : ["Buch", "Bücher", "ein "], + 5 : ["Lutscher", "Lutscher", "einen "], + 6 : ["Kreide", "Kreiden", "eine "], + 7 : ["Rezept", "Rezepte", "ein "], + 8 : ["Notiz", "Notizen", "eine "], + 9 : ["Rechenmaschine", "Rechenmaschinen", "eine "], + 10 : ["Clownautoreifen", "Clownautoreifen", "einen"], + 11 : ["Luftpumpe ", "Luftpumpen", "eine "], + 12 : ["Tintenfischtinte", "Tintenfischtinten", "irgendeine "], + 13 : ["Paket", "Pakete", "ein "], + 14 : ["Goldfischquittung", "Goldfischquittungen", "eine "], + 15 : ["Goldfisch", "Goldfische", "einen "], + 16 : ["Öl", "Öle", "etwas "], + 17 : ["Fett", "Fette", "etwas "], + 18 : ["Wasser", "Wasser", "etwas "], + 19 : ["Getriebebericht", "Getriebeberichte", "einen "], + 20 : ["Schwamm", "Schwämme", "einen "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Kleidermarke", "Kleidermarken", "eine "], + + # Donald's Dock quest items + 2001 : ["Schlauch", "Schläuche", "einen "], + 2002 : ["Monokelrezept", "Monokelrezepte", "ein "], + 2003 : ["Brillengestell", "Brillengestelle", "irgendein "], + 2004 : ["Monokel", "Monokel", "einen "], + 2005 : ["große weiße Perücke", "große weiße Perücken", "eine "], + 2006 : ["Ballastbündel", "Ballastbündel", "ein "], + 2007 : ["Bot-Zahnrad", "Bot-Zahnräder", "ein "], + 2008 : ["Seekarte", "Seekarten", "eine "], + 2009 : ["verschmutzten Webeleinstek", "verschmutzte Webeleinsteke", "einen "], + 2010 : ["sauberen Webeleinstek", "saubere Webeleinsteke", "einen "], + 2011 : ["Uhrfeder", "Uhrfedern", "eine "], + 2012 : ["Gegengewicht", "Gegengewichte", "ein "], + + # Minnie's Melodienland quest items + 4001 : ["Tinas Inventarliste", "Tinas Inventarlisten", ""], + 4002 : ["Yukis Inventarliste", "Yukis Inventarlisten", ""], + 4003 : ["Inventarlistenformular", "Inventarlistenformulare", "ein "], + 4004 : ["Fifis Inventarliste", "Fifis Inventarlisten", ""], + 4005 : ["Holzmichels Ticket", "Holzmichels Tickets", ""], + 4006 : ["Tabithas Ticket", "Tabithas Tickets", ""], + 4007 : ["Grizzlys Ticket", "Grizzlys Tickets", ""], + 4008 : ["Matte Kastagnette", "Matte Kastagnetten", "eine "], + 4009 : ["Blaue Tintenfischtinte", "Blaue Tintenfischtinte", "irgendeine "], + 4010 : ["glänzende Kastagnette", "glänzende Kastagnetten", "eine "], + 4011 : ["Leos Reim", "Leos Reime", ""], + + # Daisy's Garden quest items + 5001 : ["Seidenkrawatte", "Seidenkrawatten", "eine "], + 5002 : ["Nadelstreifenanzug", "Nadelstreifenanzüge", "einen "], + 5003 : ["Schere", "Scheren", "eine "], + 5004 : ["Postkarte", "Postkarten", "eine "], + 5005 : ["Stift", "Stifte", "einen "], + 5006 : ["Tintenfass", "Tintenfässer", "ein "], + 5007 : ["Schreibblock", "Schreibblöcke", "einen "], + 5008 : ["verschließbare Kassette", "verschließbare Kassetten", "eine "], + 5009 : ["Tüte mit Vogelfutter", "Tüten mit Vogelfutter", "eine "], + 5010 : ["Kettenradzahn", "Kettenradzähne", "einen "], + 5011 : ["Salat", "Salate", "einen "], + 5012 : ["Schlüssel zu Daisys Gärten", "Schlüssel zu Daisys Gärten", "einen "], + 5013 : ["Blaupause des Schachermat-Hauptquartiers", "Blaupausen des Schachermat-Hauptquartiers", "irgendeine "], + 5014 : ["Memo des Schachermat-Hauptquartier", "Memo des Schachermat-Hauptquartiers", "ein "], + 5015 : ["Memo des Schachermat-Hauptquartier", "Memo des Schachermat-Hauptquartiers", "ein "], + 5016 : ["Memo des Schachermat-Hauptquartier", "Memo des Schachermat-Hauptquartiers", "ein "], + 5017 : ["Memo des Schachermat-Hauptquartier", "Memo des Schachermat-Hauptquartiers", "ein "], + + # The Brrrgh quests + 3001 : ["Fußball", "Fußbälle", "einen "], + 3002 : ["Rodelschlitten", "Rodelschlitten", "einen "], + 3003 : ["Eiswürfel", "Eiswürfel", "einen "], + 3004 : ["Liebesbrief", "Liebesbriefe", "einen "], + 3005 : ["Wiener Würstchen", "Wiener Würstchen", "ein "], + 3006 : ["Verlobungsring", "Verlobungsringe", "einen "], + 3007 : ["Stein des Weisen", "Steine des Weisen", "einen "], + 3008 : ["Beruhigungstrank", "Beruhigungstrank", "einen "], + 3009 : ["kaputten Zahn", "kaputte Zähne", "einen "], + 3010 : ["Goldzahn", "Goldzähne", "einen "], + 3011 : ["Kiefernzapfenbrot", "Kiefernzapfenbrote", "einen "], + 3012 : ["Krümelkäse", "Krümelkäse", "irgendeinen "], + 3013 : ["einfachen Löffel", "einfache Löffel", "einen "], + 3014 : ["sprechende Kröte", "sprechende Kröten", "eine "], + 3015 : ["Eistüte", "Eistüten", "eine "], + 3016 : ["Perückenpuder", "Perückenpuder", "irgendein "], + 3017 : ["Quietschentchen", "Quietschentchen", "ein "], + 3018 : ["Fellwürfel", "Fellwürfel", "irgendeinen "], + 3019 : ["Mikrofon", "Mikrofone", "ein "], + 3020 : ["elektrisches Keyboard", "elektrische Keyboards", "ein "], + 3021 : ["Plateauschuhe", "Plateauschuhe", "etwas "], + 3022 : ["Kaviar", "Kaviar", "etwas "], + 3023 : ["Make-up-Puder", "Make-up-Puder", "irgendein "], + } +QuestsHQOfficerFillin = "Mitarbeiter in der Zentrale" +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = "Toontown-Zentrale" +QuestsHQLocationNameFillin = "In einer beliebigen Gegend" + +QuestsTailorFillin = "Schneider" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Bekleidungsgeschäft" +QuestsTailorLocationNameFillin = "In einer beliebigen Gegend" +QuestsTailorQuestSCString = "Ich muss zu einem Schneider." + +QuestMovieQuestChoiceCancel = "Komm später wieder, wenn du eine neue Toon-Aufgabe brauchst! Tschüss!" +QuestMovieTrackChoiceCancel = "Komm wieder, wenn du dich entscheiden kannst! Tschüss!" +QuestMovieQuestChoice = "Wähle eine Toon-Aufgabe." +QuestMovieTrackChoice = "Bereit zum Wählen? Wähle einen Ablauf oder komm später wieder." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Jetzt bist du fertig.\aZiehe nun in die Welt und wandere umher, bis du weißt, welchen Ablauf du wählen möchtest.\aWähle klug, denn dies ist dein letzter Track.\aWenn du dir sicher bist, kehre zu mir zurück.", + INCOMPLETE_PROGRESS : "Wähle mit Verstand.", + INCOMPLETE_WRONG_NPC : "Wähle mit Verstand.", + COMPLETE : "Sehr kluge Entscheidung!", + LEAVING : "Viel Glück! Komm wieder zu mir, wenn du deine neue Fähigkeit beherrschst.", + } + +QuestDialog_3225 = { + QUEST : "Oh, danke, dass du gekommen bist, _avName_!\aDie Bots in dieser Gegend haben mein Lieferanten verschreckt.\aIch habe niemanden, der diesen Salat an _toNpcName_ausliefert!\aKannst du das für mich tun? Hab vielen Dank!_where_" + } + +QuestDialog_2910 = { + QUEST : "Schon wieder da?\aDas mit der Feder hast du gut gemacht.\aDer letzte Gegenstand ist ein Gegengewicht.\aSchau mal bei _toNpcName_ vorbei und bring alles mit, was du kriegen kannst._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "OK, jetzt bist du wohl für etwas Interessanteres bereit.\aWenn du 3 Chefomaten vertreiben kannst, bekommst du von mir ein kleines Extra.", + INCOMPLETE_PROGRESS : "Die "+ Cogs + " sind draußen auf der Straße, durch die Tunnel.", + INCOMPLETE_WRONG_NPC : "Tolle Leistung, dein Sieg über die Chefomaten. Geh jetzt zur Toontown-Zentrale, um deine Belohnung abzuholen.", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "OK, jetzt bist du wohl bereit für etwas Interessanteres.\aKomm wieder her, wenn du 3 Rechtomaten vertrieben hast - dann hab ich ein kleines Geschenk für dich.", + INCOMPLETE_PROGRESS : "Die "+ Cogs + " sind draußen auf der Straße, durch die Tunnel.", + INCOMPLETE_WRONG_NPC : "Tolle Leistung, dein Sieg über die Rechtomaten. Geh jetzt zur Toontown-Zentrale, um deine Belohnung abzuholen!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "OK, jetzt bist du wohl bereit für etwas Interessanteres.\aBesiege 3 Monetomaten und komm wieder her, um deine Prämie einzufordern.", + INCOMPLETE_PROGRESS : "Die "+ Cogs + " sind draußen auf der Straße, durch die Tunnel.", + INCOMPLETE_WRONG_NPC : "Tolle Leistung, dein Sieg über die Monetomaten. Geh jetzt zu eine Toontown-Zentrale, um deine Belohnung abzuholen!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "OK, jetzt bist du wohl bereit für etwas Interessanteres.\aKomm wieder her, wenn du 3 Schachermaten vertrieben hast, und du bekommst eine Belohnung und eine neue Aufgabe.", + INCOMPLETE_PROGRESS : "Die "+ Cogs + " sind draußen auf der Straße, durch die Tunnel.", + INCOMPLETE_WRONG_NPC : "Tolle Leistung, dein Sieg über die Schachermaten. Geh jetzt zur Toontown-Zentrale, um deine Belohnung abzuholen!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + + 164 : {QUEST : "Du siehst aus, als könntest du ein paar neue Gags gebrauchen.\aGeh mal zu Flippy, der kann dir vielleicht aushelfen._where_" }, + 165 : {QUEST : "Hallo!\aSieht aus, als müsstest du deine Gags mal in der Praxis trainieren.\aJedes Mal, wenn du einem Bot einen deiner Gags um die Ohren haust, wächst deine Erfahrung.\aWenn du genug Erfahrung hast, kannst du dann einen noch besseren Gag einsetzen.\aGeh deine Gags üben und vertreibe dabei 4 Bots."}, + 166 : {QUEST : "Toll, wie du diese Bots vertrieben hast!\aWeißt du, es gibt vier Arten von Bots.\aRechtomaten, Monetomaten, Schachermaten und Chefomaten.\aDu kannst sie an ihrer Färbung und ihren Namensschildern erkennen.\aGeh mal los und besiege zur Übung 4 Chefomaten."}, + 167 : {QUEST : "Toll, wie du diese Bots vertrieben hast!\aWeißt du, es gibt vier Arten von Bots.\a Rechtomaten, Monetomaten, Schachermaten und Chefomaten.\aDu kannst sie an ihrer Färbung und ihren Namensschildern erkennen.\aGeh mal los und besiege zur Übung 4 Rechtomaten."}, + 168 : {QUEST : "Toll, wie du diese Bots vertrieben hast!\aWeißt du, es gibt vier Arten von Bots.\a Rechtomaten, Monetomaten, Schachermaten und Chefomaten.\aDu kannst sie an ihrer Färbung und ihren Namensschildern erkennen.\aGeh mal los und besiege zur Übung 4 Schachermaten."}, + 169 : {QUEST : "Toll, wie du diese Bots vertrieben hast!\aWeißt du, es gibt vier Arten von Bots.\a Rechtomaten, Monetomaten, Schachermaten und Chefomaten.\aDu kannst sie an ihrer Färbung und ihren Namensschildern erkennen.\aGeh mal los und besiege zur Übung 4 Monetomaten."}, + 170 : {QUEST : "Toll, jetzt kennst du den Unterschied zwischen den 4 Bot-Arten.\aIch glaube, du kannst jetzt für deinen dritten Gag-Ablauf trainieren.\aSprich mal mit _toNpcName_, bevor du deinen nächsten Gag-Ablauf wählst - er kann dich fachkundig beraten._where_" }, + 171 : {QUEST : "Toll, jetzt kennst du den Unterschied zwischen den 4 Bot-Arten.\aIch glaube, du kannst jetzt für deinen dritten Gag-Ablauf trainieren.\aSprich mal mit _toNpcName_, bevor du deinen nächsten Gag-Ablauf wählst - er kann dich fachkundig beraten._where_" }, + 172 : {QUEST : "Toll, jetzt kennst du den Unterschied zwischen den 4 Bot-Arten.\aIch glaube, du kannst jetzt für deinen dritten Gag-Ablauf trainieren.\aSprich mal mit _toNpcName_, bevor du deinen nächsten Gag-Ablauf wählst - sie kann dich fachkundig beraten._where_" }, + + 175 : {GREETING : "", + QUEST : "Wusstest du schon, dass du dein eigenes Toon-Haus besitzt?\aKlarabella Kuh betreibt einen Katalog, aus dem du per Telefon Möbel zum Einrichten deines Hauses bestellen kannst.\aDu kannst aber auch Schnell-Chat-Sprüche, Kleidung und andere lustige Dinge kaufen.\aIch sage Klarabella, dass sie dir sofort deinen ersten Katalog schicken soll.\aJede Woche erhältst du einen Katalog mit neuen Gegenständen!\aGeh in dein Haus und rufe von dort aus Klarabella an.", + INCOMPLETE_PROGRESS : "Geh nach Hause und rufe von dort Klarabella an.", + COMPLETE : "Es macht dir bestimmt Spaß, bei Klarabella etwas zu bestellen!\aIch habe gerade mein Haus neu eingerichtet. Es sieht toontastisch aus!\aLöse weiterhin Toon-Aufgaben, um noch mehr Belohnungen zu bekommen!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Werfen und Spritzen ist toll, aber du wirst noch mehr Gags brauchen, wenn du gegen höhere Bots kämpfen willst.\aWenn du dich mit anderen Toons gegen die Bots zusammenschließt, dann könnt ihr die Angriffe kombinieren und dadurch noch mehr Schaden anrichten.\aProbiert verschiedene Gag-Kombinationen aus, um herauszufinden, was am besten funktioniert.\aWähle für deinen nächsten Ablauf zwischen Volldröhnen und Aufheitern.\aVolldröhnen ist etwas Besonders, weil alle Bots beschädigt werden, wenn es trifft.\aMit Aufheitern kannst du andere Toons im Kampf heilen.\aWenn du für deine Entscheidung bereit bist, komm wieder her und wähle.", + INCOMPLETE_PROGRESS : "Schon wieder hier? Okay, bist du zum Auswählen bereit?", + INCOMPLETE_WRONG_NPC : "Denke gut nach, ehe du wählst.", + COMPLETE : "Gute Wahl. Bevor du nun diese Gags einsetzen kannst, musst du dafür trainieren.\aDazu musst du eine Reihe von Toon-Aufgaben lösen.\aBei jeder Aufgabe erhältst du ein Bild deines Gag-Ablaufs.\aWenn du alle 15 sammelst, kannst du die Aufgabe für das Gag-Abschlusstraining erhalten, bei der du alle deine neuen Gags einsetzen kannst.\aDeine Fortschritte kannst du im Sticker-Buch sehen.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Besuche _toNpcName_, wenn du dich leichter durch die Stadt bewegen willst._where_" }, + 1040 : { QUEST : "Besuche _toNpcName_, wenn du dich leichter durch die Stadt bewegen willst._where_" }, + 1041 : { QUEST : "Hi! Was führt dich hierher?\aAlle benutzen ihr tragbares Loch, um sich durch die Stadt zu bewegen.\aAlso, du kannst dich mit der Freunde-Liste zu deinen Freunden teleportieren oder auch mit dem Stadtplan im Sticker-Buch in jede andere Gegend.\aNatürlich musst du dir das erst verdienen!\aHör mal, ich kann deinen Teleport-Zugang zu Toontown Mitte einschalten, wenn du einem Freund von mir hilfst.\aAnscheinend machen die Bots drüben in der Hohlgasse Ärger. Geh mal zu _toNpcName_._where_" }, + 1042 : { QUEST : "Hi! Was führt dich hierher?\aAlle benutzen ihr tragbares Loch, um sich durch die Stadt zu bewegen.\aAlso, du kannst dich mit der Freunde-Liste zu deinen Freunden teleportieren oder auch mit dem Stadtplan im Sticker-Buch in jede andere Gegend.\aNatürlich musst du dir das erst verdienen!\aHör mal, ich kann deinen Teleport-Zugang zu Toontown Mitte einschalten, wenn du einem Freund von mir hilfst.\aAnscheinend machen die Bots drüben in der Hohlgasse Ärger. Geh mal zu _toNpcName_._where_" }, + 1043 : { QUEST : "Hi! Was führt dich hierher?\aAlle benutzen ihr tragbares Loch, um sich durch die Stadt zu bewegen.\aAlso, du kannst dich mit der Freunde-Liste zu deinen Freunden teleportieren oder auch mit dem Stadtplan im Sticker-Buch in jede andere Gegend.\aNatürlich musst du dir das erst verdienen!\aHör mal, ich kann deinen Teleport-Zugang zu Toontown Mitte einschalten, wenn du einem Freund von mir hilfst.\aAnscheinend machen die Bots drüben in der Hohlgasse Ärger. Geh mal zu _toNpcName_._where_" }, + 1044 : { QUEST : "Oh, danke, dass du vorbeikommst. Ich brauche wirklich Hilfe.\aWie du sehen kannst, habe ich keine Kunden.\aMein geheimes Rezeptbuch ist weg und keiner kommt mehr in mein Restaurant.\aIch habe es zuletzt kurz bevor diese Bots mein Gebäude übernahmen gesehen.\aKannst du mir helfen und vier meiner berühmten Rezepte zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hattest du schon Glück mit meinen Rezepten?" }, + 1045 : { QUEST : "Herzlichen Dank!\aBald werde ich die gesamte Sammlung zurückhaben und mein Restaurant wieder aufmachen.\aOh, ich habe hier eine Nachricht für dich - irgendwas über Teleport-Zugang?\aDa steht - danke, dass du meinem Freund geholfen hast, gib das hier jetzt in Toontown-Zentrale ab.\aAlso wirklich vielen Dank - Tschüss!", + LEAVING : "", + COMPLETE : "Ach ja, es heißt hier, dass du den netten Leute in der Hohlgasse einen großen Dienst erwiesen hast.\aDa steht, dass du einen Teleport-Zugang nach Toontown Mitte brauchst.\aAlso, du kannst das als erledigt betrachten.\aDu kannst dich jetzt von fast überall aus Toontown zum Spielplatz zurück teleportieren.\aSchlag einfach deinen Stadtplan auf und klicke auf Toontown Mitte." }, + 1046 : { QUEST : "Die Monetomaten belästigen die ganze Zeit die Spielgeld-Bausparkasse.\aSchau mal dort vorbei und sieh zu, ob du irgendetwas tun kannst._where_" }, + 1047 : { QUEST : "Monetomaten haben sich immer wieder in die Bank geschlichen und unsere Maschinen gestohlen.\aBitte hole 5 Rechenmaschinen von den Monetomaten zurück.\aDamit du nicht immer hin und zurück rennen musst, bring sie einfach alle auf einmal zurück.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Suchst du immer noch Rechenmaschinen?" }, + 1048 : { QUEST : "Wow! Danke, dass du unsere Rechenmaschinen gefunden hast.\aHm ... Die sehen ein bißchen beschädigt aus.\aSag mal, könntest du sie rüber zu _toNpcName_ bringen, in ihren Laden \"Kitzelmaschinen\" hier in der Straße?\aVielleicht bekommt sie die wieder hin.", + LEAVING : "", }, + 1049 : { QUEST : "Was ist denn das? Kaputte Rechenmaschinen? \aMonetomaten, sagst du?\aNaja, woll'n mal nachsehen...\aTja, Getriebe rausgenommen, aber ich hab diese Teile nicht mehr ...\aWeißt du, was gehen könnte - ein paar Bot-Zahnräder, große, von größeren Bots ...\aBot-Zahnräder von Level 3 müssten gehen. Ich brauch 2 für jede Maschine, also insgesamt 10.\aBring sie alle auf einmal her, dann mach ich die Dinger klar!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Denk dran, ich brauch 10 Zahnräder, um die Maschinen zu reparieren." }, + 1053 : { QUEST : "Ah ja, das dürfte jetzt klappen.\aAlles fertig, und das kostenlos.\aNimm sie wieder mit zu Spielgeld und sag ihnen `nen schönen Gruß von mir.", + LEAVING : "", + COMPLETE : "Rechenmaschinen alle repariert?\aGut gemacht. Ich bin sicher, dass ich hier irgendwo was habe, womit ich dich belohnen kann ... " }, + 1054 : { QUEST : "_toNpcName_ braucht Hilfe bei seinen Clownautos._where_" }, + 1055 : { QUEST : "Mannomann! Kann die Reifen zu diesem komischen Clownauto nicht finden!\aMeinste, du könntest mir mal helfen?\aDussel-Bob hat sie wohl in den Teich auf'm Spielplatz von Toontown Mitte geschmissen.\aWenn du dich da auf so'n Dock stellst, kannst du die Reifen vielleicht für mich rausfischen.", + GREETING : "Huhu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du Probleme, alle 4 Reifen rauszufischen?" }, + 1056 : { QUEST : "Fan-kuchen-tastisch! Jetzt krieg ich dieses olle Clownauto wieder ins Rollen!\aHey, ich dachte, ich hätte hier mal `ne Luftpumpe gehabt, um diese Reifen aufzupumpen ...\aVielleicht hat _toNpcName_ sie sich ausgeliehen?\aKönntest du mal hingehen und sie für mich zurückholen?_where_", + LEAVING : "" }, + 1057 : { QUEST : "Tag.\aEine Reifenpumpe, sagst du?\aIch hab `ne Idee - du hilfst mir, ein paar von diesen höheren Bots von der Straße zu räumen ...\aUnd ich geb dir die Reifenpumpe.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Besser geht's nicht?" }, + 1058 : { QUEST : "Gute Arbeit - ich wusste, dass du das schaffst.\aHier ist die Pumpe. Ich bin sicher, _toNpcName_ wird sich freuen, sie wieder zurück zu bekommen.", + LEAVING : "", + GREETING : "", + COMPLETE : "Jippieh! Jetzt kann's losgehen!\aÜbrigens vielen Dank für deine Hilfe.\aHier, nimm das." }, + 1059 : { QUEST : "_toNpcName_ gehen die Vorräte aus. Vielleicht kannst du ihm mal helfen?_where_" }, + 1060 : { QUEST : "Danke, dass du vorbeikommst!\aDiese Bots haben meine Tinte gestohlen, jetzt geht sie mir fast aus.\aKönntest du für mich etwas Tintenfischtinte aus dem Teich fischen?\aStell dich zum Fischen einfach auf ein Dock am Teich.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du ein Problem beim Fischen?" }, + 1061 : { QUEST : "Große Klasse - danke für die Tinte!\aWeißt du was, wenn du vielleicht ein paar von diesen Griffelschiebern aus dem Weg räumen könntest ...\aDann würde mir die Tinte nicht wieder so schnell ausgehen.\aFür deine Belohnung musst du 6 Griffelschieber in Toontown Mitte besiegen.", + LEAVING : "", + COMPLETE : "Danke! Ich möchte dich für deine Hilfe belohnen.", + INCOMPLETE_PROGRESS : "Ich hab grad noch ein paar Griffelschieber gesehen." }, + 1062 : { QUEST : "Große Klasse - danke für die Tinte!\aWeißt du was, wenn du vielleicht ein paar von diesen Blutsaugern aus dem Weg räumen könntest ...\aDann würde mir die Tinte nicht wieder so schnell ausgehen.\aFür deine Belohnung musst du 6 Blutsauger in Toontown Mitte besiegen.", + LEAVING : "", + COMPLETE : "Danke! Ich möchte dich für deine Hilfe belohnen.", + INCOMPLETE_PROGRESS : "Ich hab grad noch ein paar Blutsauger gesehen. " }, + 900 : { QUEST : "Ich habe gehört, _toNpcName_ braucht Hilfe mit einem Paket._where_" }, + 1063 : { QUEST : "Hi - danke, dass du rein gekommen bist. Ein Bot hat mir ein sehr wichtiges Paket direkt unter der Nase weg gestohlen.\aBitte sieh doch mal zu, ob du es zurückholen kannst. Ich glaube, es war einer von Level 3 ...\aAlso erledige Bots von Level 3, bis du mein Paket findest.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Erfolg bei der Suche nach meinem Paket, was?" }, + 1067 : { QUEST : "Na, da ist es ja!\aHey, die Adresse ist verschmiert ...\aIch kann nur noch entziffern, dass es für einen Dr. ist - der Rest ist unleserlich.\aVielleicht ist es für _toNpcName_? Könntest du es zu ihm bringen?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Ich erwarte kein Paket. Vielleicht ist es für Dr. B. Geistert?\aMein Assistent geht sowieso heute rüber, da lasse ich ihn das für dich klären.\aWärst du vielleicht so nett, ein paar von den Bots in meiner Straße zu verjagen?\aVertreibe 10 Bots in Toontown Mitte.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mein Assistent ist noch nicht zurück." }, + 1069 : { QUEST : "Dr. B. Geistert sagt, er erwarte auch kein Paket.\aLeider hat ein Monetomat es meinem Assistenten auf dem Rückweg weggenommen.\aKönntest du versuchen, es zurückzubekommen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Erfolg bei der Suche nach dem Paket, was?" }, + 1070 : { QUEST : "Dr. B. Geistert sagt, er erwarte auch kein Paket.\aLeider hat ein Schachermat es meinem Assistenten auf dem Rückweg weggenommen.\aTut mir leid, aber du wirst diesen Schachermaten finden und das Paket zurückholen müssen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Erfolg bei der Suche nach dem Paket, was?" }, + 1071 : { QUEST : "Dr. B. Geistert sagt, er erwarte auch kein Paket.\aLeider hat ein Chefomat es meinem Assistenten auf dem Rückweg weggenommen.\aKönntest du versuchen, es zurückzubekommen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Erfolg bei der Suche nach dem Paket, was?" }, + 1072 : { QUEST : "Großartig - du hast es wieder!\aVielleicht solltest du es mal bei _toNpcName_ versuchen, es könnte für ihn sein._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, danke, dass du mir meine Pakete bringst.\aWarte mal, ich habe zwei erwartet. Könntest du mal bei _toNpcName_ nachprüfen, ob er vielleicht das andere hat?", + INCOMPLETE : "Hast du mein anderes Paket finden können?", + LEAVING : "" }, + 1074 : { QUEST : "Er hat gesagt, dass es noch ein Paket gab? Vielleicht haben das auch die Bot gestohlen.\aErledige Bots, bis du das zweite Paket findest.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Erfolg bei der Suche nach dem anderen Paket, was?" }, + 1075 : { QUEST : "Offenbar gab es nun doch ein zweites Paket!\aBringe es schnell rüber zu _toNpcName_ und sage ihm, es täte mir Leid.", + COMPLETE : "Hey, da ist ja mein Paket!\aDa du anscheinend ein sehr hilfsbereiter Toon bist, wirst du das hier brauchen können.", + LEAVING : "" }, + 1076 : { QUEST : "Drüben beim 14-Karat-Goldfisch hat es Ärger gegeben.\a_toNpcName_ könnte wahrscheinlich Hilfe gebrauchen._where_" }, + 1077 : { QUEST : "Danke, dass du gekommen bist - die Bots haben alle meine Goldfische gestohlen.\aIch vermute, die Bots möchten sie verkaufen, um schnell Kohle zu machen.\aDiese 5 Fische waren so viele Jahre lang meine einzige Gesellschaft in diesem winzigen Laden ...\aWenn du sie für mich zurückholen könntest, wäre ich dir wirklich sehr dankbar.\aIch bin sicher, dass einer der Bots meine Fische hat.\aVerjage Bots, bis du meine Goldfische findest.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Bitte bring mir meine Goldfische wieder." }, + 1078 : { QUEST : "Oh, du hast meine Fische!\aHä? Was ist das - eine Quittung?\aSeufz, naja, am Ende handelt es sich ja um Bots.\aIch werd aus dieser Quittung einfach nicht schlau. Könntest du sie mal zu _toNpcName_ bringen, vielleicht kann er sie lesen?_where_", + INCOMPLETE : "Was hat _toNpcName_ zu der Quittung gesagt?", + LEAVING : "" }, + 1079 : { QUEST : "Mmh, lass mich mal diese Quittung sehen.\a...Ah ja, hier steht, dass 1 Goldfisch an einen gewissen Kriecher verkauft wurde.\aDie anderen 4 Fische werden aber anscheinend nicht erwähnt.\aVielleicht solltest du versuchen, diesen Kriecher zu finden.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich glaube nicht, dass es noch etwas gibt, womit ich dir helfen kann.\aWarum versuchst du nicht, diesen Goldfisch zu finden?" }, + 1092 : { QUEST : "Mmh, lass mich mal diese Quittung sehen.\a...Ah ja, hier steht, dass 1 Goldfisch an einen gewissen Keinmünz verkauft wurde.\aDie anderen 4 Fische werden aber anscheinend nicht erwähnt.\aVielleicht solltest du versuchen, diesen Keinmünz zu finden.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich glaube nicht, dass es noch etwas gibt, womit ich dir helfen kann.\aWarum versuchst du nicht, diesen Goldfisch zu finden?" }, + 1080 : { QUEST : "Oh, dem Himmel sei Dank! Du hast Oscar gefunden - er ist mein Liebling.\aWas sagst du, Oscar? Aha ... wirklich? ... da sind sie?\aOscar sagt, die anderen 4 sind in den Teich auf dem Spielplatz entwischt.\aKönntest du sie für mich einfangen?\aFische sie einfach aus dem Teich.", + LEAVING : "", + COMPLETE : "Ach, ich bin ja sooo froh! Wieder vereint zu sein mit meinen kleinen Freunden!\aDafür verdienst du eine hübsche Belohnung!", + INCOMPLETE_PROGRESS : "Hast du Probleme, diese Fische zu finden?" }, + 1081 : { QUEST : "_toNpcName_ scheint festzusitzen. Sie könnte bestimmt eine helfende Hand gebrauchen._where_" }, + 1082 : { QUEST : "Ich hab Kleber verkleckert und jetzt steck ich fest - ei, der Daus!\aIch würd alles drum geben, käm ich hier raus.\aIch hab `ne Idee, vielleicht hilfst du mir'n Stück.\aSchlag ein paar Schachermaten und komm mit Öl zurück.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Kannst du mir helfen, mich zu entkleben?" }, + 1083 : { QUEST : "Öl war schon gut, doch komm ich nicht los.\aWas würde noch helfen, was mach ich denn bloß?\aIch hab `ne Idee, wärst du wohl so nett.\aSchlag ein paar Rechtomaten und bringe mir Fett.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Kannst du mir helfen, mich zu entkleben?" }, + 1084 : { QUEST : "Nö, keine Chance - nichts hat sich bewegt.\aIch hab das Fett auf die Moneten gelegt.\aApropos Moneten, wir machen es nasser.\aSchlag ein paar Monetomaten und bringe mir Wasser.", + LEAVING : "", + GREETING : "", + COMPLETE : "Hurra, ich bin frei von diesem Leim!\aZur Belohnung wird dies Geschenk jetzt dein,\aDu kannst länger lachen beim Kampfe, und dann ...\aOh nein! Ich kleb ja schon wieder an!", + INCOMPLETE_PROGRESS : "Kannst du mir helfen, mich zu entkleben?" }, + 1085 : { QUEST : "_toNpcName_ führt Forschungen über die Bots durch.\aWenn du helfen willst, geh hin und sprich mal mit ihm._where_" }, + 1086 : { QUEST : "Das ist richtig, ich führe eine Studie zu den Bots durch.\aIch möchte wissen, was sie in Gang setzt.\aEs würde mir auf jeden Fall helfen, wenn du ein paar Bot-Zahnräder sammeln könntest.\aAchte darauf, dass sie mindestens von Bots des Level 2 sind, damit sie für die Untersuchung groß genug sind.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kannst du nicht genügend Zahnräder auftreiben?" }, + 1089 : { QUEST : "Okay, da wollen wir mal sehen. Das sind ja hervorragende Exemplare!\aMmm...\aOkay, hier ist mein Bericht. Bringe ihn gleich zur Toontown-Zentrale.", + INCOMPLETE : "Hast du meinen Bericht in Toontown-Zentrale gebracht?", + COMPLETE : "Gute Arbeit, _avName_, wir übernehmen jetzt.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ hat nützliche Informationen für dich._where_" }, + 1091 : { QUEST : "Ich habe gehört, dass die Toontown-Zentrale an einer Art Bot-Radar arbeitet.\aDamit sieht man, wo sich die Bots aufhalten, so dass man sie leichter finden kann.\aDie Bot-Seite in deinem Sticker-Buch ist der Schlüssel dazu.\aWenn du genügend Bots bezwingst, kannst du ihre Signale empfangen und verfolgen, wo sie sind.\aBesiege weiterhin Bots, dann bist du dafür bereit.", + COMPLETE : "Gute Arbeit! Das hier kannst du wahrscheinlich brauchen ...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Jetzt kannst du den nächsten Gag-Ablauf auswählen, den du lernen möchtest.\aNimm dir Zeit für die Entscheidung und komm zurück, wenn du bereit bist zu wählen.", + INCOMPLETE_PROGRESS : "Denke gut nach, ehe du wählst.", + INCOMPLETE_WRONG_NPC : "Denke gut nach, ehe du wählst.", + COMPLETE : "Eine kluge Entscheidung ...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Diese hinterlistigen Bots haben wieder zugeschlagen.\a_toNpcName_ hat einen weiteren verschwundenen Gegenstand gemeldet. Schau mal dort vorbei, ob du das regeln kannst._where_" }, + 2202 : { QUEST : "Hi, _avName_. Gott sei Dank bist du hier. Ein gemeiner Pfennigfuchser kam grad hier rein und machte sich mit einem Schlauch davon.\aIch habe den Verdacht, dass sie den für ihre üblen Zwecke verwenden wollen.\aBitte versuche ihn zu finden und bring den Schlauch zurück.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du meinen Schlauch schon gefunden?", + COMPLETE : "Du hast meinen Schlauch gefunden! Du BIST gut! Hier, deine Belohnung ...", + }, + 2203 : { QUEST : "Die Bots verursachen drüben in der Bank das totale Chaos.\aSuche Käpt'n Karl auf und schau mal, was du tun kannst._where_" }, + 2204 : { QUEST : "Willkommen an Bord, Kamerad!\aMist! Diese Halunken von Bots haben mein Monokel zerschmissen und ich kann so mein Kleingeld nicht sortieren. \aSei eine nette Landratte und bring dieses Rezept zu Dr. Queequeg und hol mir ein neues._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "Was ist das?\aAch, ich würde das Rezept ja gern einlösen, aber die Bots haben mein Lager geplündert.\aWenn du mir das Brillengestell von einem Kriecher bringst, kann ich vielleicht helfen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tut mir Leid. Kein Kriechergestell, kein Monokel.", + }, + 2206: { QUEST : "Ausgezeichnet!\aEinen Moment ...\aDein Rezept ist hiermit eingelöst. Bitte nimm dieses Monokel gleich mit zu Käpt'n Karl._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Fest!\aDa verdienst du dir ja tatsächlich deine Seefestigkeit.\aHier hast du.", + }, + 2207 : { QUEST : "Bernikel-Barbara hat einen Bot in ihrem Laden!\aDu solltest mal rübergehen, und zwar pronto._where_" }, + 2208 : { QUEST : "Menschenskind! Du hast ihn genau verpasst, Schätzchen.\aEs war ein Heimtücker hier. Er nahm meine große weiße Perücke mit.\aEr sagte, sie sei für seinen Chef, und irgendwas von 'Präzedenzfall.'\aWenn du sie zurückbringen könntest, wäre ich dir ewig dankbar.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Hast du ihn immer noch nicht gefunden?\aEr ist groß und hat einen Eierkopf.", + COMPLETE : "Gefunden!?!?\aDu bist ein echter Schatz!\aDu hast dir das hier mehr als verdient ...", + }, + 2209 : { QUEST : "Melville bereitet sich auf eine wichtige Reise vor.\aGeh hin und sieh mal, wie du ihm helfen kannst._where_"}, + 2210 : { QUEST : "Ich kann deine Hilfe brauchen.\aDie Toon-Zentrale hat mich gebeten, eine Reise zu machen und herauszufinden, woher die Bots kommen.\aIch brauch ein paar Sachen für mein Schiff, aber ich habe nicht viele Jelly Beans.\aGeh mal zu Alice und hol etwas Ballast. Du wirst ihr einen Gefallen tun müssen, damit du ihn bekommst._where_", + GREETING : "Wie geht's, wie steht's, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Melville will also Ballast?\aEr schuldet mir noch was für das letzte Bündel.\aIch geb's dir aber, wenn du fünf Mikromanager aus meiner Straße entfernen kannst.", + INCOMPLETE_PROGRESS : "Nein, Dummchen! Ich sagte FÜNF Mikromanager ...", + GREETING : "Was kann ich für dich tun?", + LEAVING : "", + }, + 2212 : { QUEST : "Abgemacht ist abgemacht.\aHier ist dein Ballast für diesen Geizhals Melville._where_", + GREETING : "Na, wer kommt denn da ...", + LEAVING : "", + }, + 2213 : { QUEST : "Hervorragende Arbeit. Ich wusste, dass sie vernünftig sein wird.\aAls nächstes brauche ich eine Seekarte von Art.\aIch glaube, mit meinem Kredit sieht es dort auch nicht so günstig aus. Du wirst wohl was mit ihm aushandeln müssen._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Ja, ich habe die Seekarte, die Melville braucht.\aUnd wenn du bereit bist, dafür zu arbeiten, bekommst du sie auch.\aIch versuche gerade, ein Astrolabium zu bauen, um mich an den Sternen zu orientieren.\aIch könnte dafür drei Bot-Zahnräder gebrauchen.\aKomm wieder, wenn du sie hast.", + INCOMPLETE_PROGRESS: "Wie steht's mit den Bot-Zahnrädern?", + GREETING : "Willkommen!", + LEAVING : "Viel Glück!", + }, + 2215 : { QUEST : "Oha! Diese Zahnräder sind wirklich gut geeignet.\aHier ist die Karte. Gib sie Melville mit freundlichen Grüßen._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Nun, damit hätten wir's. Ich bin fertig zum Ablegen!\aWenn du nicht so grün wärst, würde ich dich mitnehmen. Nimm stattdessen das hier.", + }, + 901 : { QUEST : "Wenn du meinst du kannst das - Ahab könnte drüben bei sich ein bisschen Unterstützung gebrauchen ..._where_", + }, + 2902 : { QUEST : "Bist du der Neue?\aGut, gut. Vielleicht kannst du mir helfen.\aIch baue gerade eine riesige fabelhafte Seekrabbe, um die Bots zu verwirren.\aIch könnte allerdings noch einen Webeleinstek brauchen. Geh mal bitte zu Gert und bring einen mit._where_", + }, + 2903 : { QUEST : "Tagchen!\aJa, ich habe von der Riesenkrabbe gehört, an der Ahab arbeitet.\aDer beste Webeleinstek, den ich habe, ist aber ein wenig angeschmuddelt.\aSei so nett und lass ihn erst reinigen, bevor du ihn dort abgibst._where_", + LEAVING : "Danke!" + }, + 2904 : { QUEST : "Du musst der sein, den Gert geschickt hat.\aIch denke, ich kann das schnell reinigen.\aEinen Moment ...\aBitte schön. So gut wie neu!\aSag Ahab schönen Gruß von mir._where_", + }, + 2905 : { QUEST : "Ah, das ist ja genau, was ich suche.\aDa du einmal hier bist, ich brauche auch noch eine sehr große Uhrfeder.\aSpazier doch mal rüber zu Hook und frag ihn, ob er eine hat._where_", + }, + 2906 : { QUEST : "Eine große Sprungfeder, was?\aTut mir leid, aber die größte Feder, die ich habe, ist immer noch ziemlich klein.\aVielleicht könnte ich eine aus Spritzpistolenabzugsfedern zusammenbauen.\aBring mir drei von diesen Gagdingern, und ich seh zu, was ich tun kann.", + }, + 2907 : { QUEST : "Na, woll'n mal schauen ...\aToll. Einfach toll.\aManchmal bin ich von mir selbst überrascht.\aBitteschön: Eine große Sprungfeder für Ahab!_where_", + LEAVING : "Bon Voyage!", + }, + 2911 : { QUEST : "Ich würde ja gern was für die gute Sache tun, _avName_.\aAber ich fürchte, die Straßen sind nicht mehr sicher.\aWarum bezwingst du nicht ein paar Monetomaten-Bots, dann reden wir drüber.", + INCOMPLETE_PROGRESS : "Ich glaube immer noch, du musst die Straßen sicherer machen.", + }, + 2916 : { QUEST : "Ja, ich habe ein Gewicht, das Ahab haben kann.\aAber ich denke, es wäre sicherer, wenn du erst ein paar Schachermaten vertreibst.", + INCOMPLETE_PROGRESS : "Noch nicht. Erledige erst noch ein paar Schachermaten.", + }, + 2921 : { QUEST : "Hmmm, ich denke, ich könnte ein Gewicht abgeben.\aIch hätte aber ein besseres Gefühl dabei, wenn hier nicht so viele Chefomaten herumschleichen würden.\aBezwinge sechs und komm dann wieder zu mir.", + INCOMPLETE_PROGRESS : "Ich glaube nicht, dass es schon sicher ist ... ", + }, + 2925 : { QUEST : "Fertig?\aNa, ich denke, jetzt ist es sicher genug.\aHier ist das Gegengewicht für Ahab._where_" + }, + 2926 : {QUEST : "Nun, das ist alles.\aWoll'n mal sehen, ob es jetzt geht.\aHmmm, noch ein kleines Problem.\aIch habe keinen Strom, weil das Bot-Gebäude meine Solarzellen blockiert.\aKannst du es für mich zurückerobern?", + INCOMPLETE_PROGRESS : "Immer noch kein Strom. Was ist mit dem Gebäude?", + COMPLETE : "Super! Du bist ja ein echter Bot-Zerstörer! Hier, nimm das als Belohnung ...", + }, + 3200 : { QUEST : "Ich hab grad einen Anruf von _toNpcName_ bekommen.\aEr hat einen schweren Tag. Vielleicht kannst du ihm helfen!\aGeh mal hin und schau, was er braucht._where_" }, + 3201 : { QUEST : "Oh, danke, dass du gekommen bist!\aIch brauche jemanden, der diese neue Seidenkrawatte zu _toNpcName_ bringt.\aKönntest du das für mich tun?_where_" }, + 3203 : { QUEST : "Oh, das muss die Krawatte sein, die ich bestellt habe! Danke!\aSie passt zu einem Nadelstreifenanzug, den ich gerade fertiggestellt habe, da drüben.\aHe, was ist denn mit dem Anzug passiert?\aOch nein! Die Bots müssen meinen neuen Anzug gestohlen haben!\aJage Bots, bis du meinen Anzug findest, und bring ihn zu mir zurück.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du meinen Anzug schon gefunden? Ich bin sicher, dass die Bots ihn geholt haben!", + COMPLETE : "Hurra! Du hast meinen neuen Anzug gefunden!\aSiehste, ich hab dir doch gesagt, dass die Bots ihn haben! Hier ist deine Belohnung ... ", + }, + + 3204 : { QUEST : "_toNpcName_ hat grad angerufen, um einen Diebstahl zu melden.\aGeh doch mal rüber und schau, ob du die Sache wieder in Ordnung bringen kannst?_where_" }, + 3205 : { QUEST : "Hallo, _avName_! Willst du mir helfen?\aIch habe gerade einen Blutsauger aus meinem Laden gejagt. Hui, das war vielleicht gruselig!\aAber jetzt kann ich meine Schere nirgends finden! Ich bin sicher, der Blutsauger hat sie genommen.\aFinde den Blutsauger und erobere mir meine Schere zurück.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Suchst du noch nach meiner Schere?", + COMPLETE : "Meine Schere! Hab vielen Dank! Hier ist deine Belohnung ... ", + }, + + 3206 : { QUEST : "Es klingt, als hätte _toNpcName_ gerade ein Problem mit ein paar Bots.\aSchau mal nach, ob du ihm helfen kannst._where_" }, + 3207 : { QUEST : "Hi, _avName_! Danke für's Herkommen!\aEin paar Dummschwätzer sind grad bei mir eingebrochen und haben einen Stapel Postkarten von meiner Theke geklaut.\aBitte geh los und jag alle Dummschwätzer meine Postkarten zurück bekomme!", + INCOMPLETE_PROGRESS : "Das sind noch nicht alle Postkarten! Such weiter!", + COMPLETE : "Oh, danke! Jetzt kann ich die Post pünktlich ausliefern! Hier ist deine Belohnung ... ", + }, + + 3208 : { QUEST : "Wir bekommen in letzter Zeit Beschwerden von den Anwohnern über diese ganzen Aufschwatzer.\aSieh mal zu, ob du 10 Aufschwatzer vertreiben kannst, um deinen Mit-Toons in Daisys Gärten zu helfen." }, + 3209 : { QUEST : "Danke, dass du dich um diese Aufschwatzer gekümmert hast!\aJetzt sind aber die Telemarketer außer Kontrolle geraten.\aErledige 10 Telemarketer in Daisys Gärten und komm wieder her, um deine Belohnung abzuholen." }, + + 3247 : { QUEST : "Wir bekommen in letzter Zeit Beschwerden von den Anwohnern über diese ganzen Blutsauger.\aSieh mal zu, ob du 20 Blutsauger vertreiben kannst, um deinen Mit-Toons in Daisys Gärten zu helfen." }, + + + 3210 : { QUEST : "Oh nein, der Spritzblume in der Ahornstraße sind gerade die Blumen ausgegangen!\aBring ihnen zehn von deinen eigenen Spritzblumen hin, um ihnen zu helfen.\aDu musst aber erst 10 Spritzblumen in deinem Lager haben.", + LEAVING: "", + INCOMPLETE_PROGRESS : "Ich brauche 10 Spritzblumen. Du hast nicht genug!" }, + 3211 : { QUEST : "Oh, vielen Dank! Diese Spritzblumen sind unsere Rettung.\aAber ich fürchte mich vor den Bots da draußen.\aKannst du mir helfen und ein paar von den Bots vertreiben?\aKomm wieder zu mir, wenn du 20 Bots in dieser Straße erledigt hast.", + INCOMPLETE_PROGRESS : "Es sind da draußen immer noch Bots übrig! Mach weiter!", + COMPLETE : "Oh, vielen Dank! Das ist eine große Hilfe. Deine Belohnung ist ...", + }, + + 3212 : { QUEST : "_toNpcName_ hat etwas verloren und braucht Hilfe bei der Suche.\aGeh mal hin und schau, was du tun kannst._where_" }, + 3213 : { QUEST : "Hi, _avName_. Kannst du mir helfen?\aIch habe anscheinend meinen Stift verlegt. Möglicherweise haben ihn die Bots weggenommen.\aVertreibe Bots, bis du meinen gestohlenen Stift wiederfindest.", + INCOMPLETE_PROGRESS : "Hast du meinen Stift schon gefunden?" }, + 3214 : { QUEST : "Ja, das ist mein Stift! Vielen Dank!\aAls du weg warst, habe ich aber gemerkt, dass auch mein Tintenfass fehlt.\aVertreibe Bots, um mein Tintenfass zu finden.", + INCOMPLETE_PROGRESS : "Ich suche immer noch nach meinem Tintenfass!" }, + 3215 : { QUEST : "Großartig! Jetzt habe ich meinen Stift und mein Tintenfass wieder.\aAber weißt du was?\aJetzt ist mein Schreibblock weg! Sie müssen ihn auch gestohlen haben!\aSuche die Bots, um meinen gestohlenen Schreibblock zu finden, und dann bringe ihn zu mir zurück und hole dir deine Belohnung ab.", + INCOMPLETE_PROGRESS : "Irgendetwas Neues vom Schreibblock? " }, + 3216 : { QUEST : "Das ist mein Schreibblock! Hurra! Deine Belohnung ist ...\aHe! Wo ist sie denn hin?\aIch hatte deine Belohnung direkt hier in meiner verschließbaren Kassette. Aber die Kassette ist weg!\aDas ist doch nicht zu glauben! Diese Bots haben deine Belohnung gestohlen!\aJage die Bots und hole meine Kassette zurück.\aWenn du sie mir zurückbringst, gebe ich dir deine Belohnung.", + INCOMPLETE_PROGRESS : "Such weiter nach dieser Kassette! Da ist deine Belohnung drin!", + COMPLETE : "Na endlich! Ich hatte deine neue Gagtasche in der Kassette. Hier ist sie ...", + }, + + 3217 : { QUEST : "Wir haben ein paar Studien zur Schachermat-Mechanik durchgeführt.\aWir müssen uns einige Teile noch näher ansehen.\aBring uns einen Kettenradzahn von einem Wichtigtuer.\aDu kannst dir eins holen, wenn der Bot explodiert." }, + 3218 : { QUEST : "Gute Arbeit! Wir brauchen jetzt zum Vergleich einen Zahn von einem Glückshändchen.\aDiese Zähne sind schwerer zu holen, aber lass dich nicht entmutigen." }, + 3219 : { QUEST : "Großartig! Jetzt brauchen wir nur noch einen Zahn.\aDiesmal brauchen wir einen von einem Aufbauscher.\aDu musst möglicherweise in ein paar Schachermat-Gebäude hineinschauen, um diese Bots zu finden.\aWenn du einen Zahn hast, bring ihn her und hol dir deine Belohnung ab." }, + + 3244 : { QUEST : "Wir haben ein paar Studien zur Rechtomat-Mechanik durchgeführt.\aWir müssen uns einige Teile noch näher ansehen.\aBring uns einen Kettenradzahn von einem Unfallabzocker.\aDu kannst dir eins holen, wenn der Bot explodiert." }, + 3245 : { QUEST : "Gute Arbeit! Wir brauchen jetzt zum Vergleich einen Zahn von einem Heimtücker.\aDiese Zähne sind schwerer zu holen, aber lass dich nicht entmutigen." }, + 3246 : { QUEST : "Großartig! Jetzt brauchen wir nur noch einen Zahn.\aDiesmal brauchen wir einen von einem Schönredner.\aWenn du einen hast, bring ihn her und hol dir deine Belohnung ab." }, + + 3220 : { QUEST : "Ich habe gerade gehört, dass _toNpcName_ überall nach dir gefragt hat.\aWarum gehst du nicht mal hin und fragst, was sie will?_where_" }, + 3221 : { QUEST : "Hi, _avName_! Da bist du ja!\aIch habe gehört, dass du ein ziemlicher Experte für Spritzattacken sein sollst.\aIch brauche jemanden, der allen Toons in Daisys Gärten mal ein gutes Beispiel gibt.\aSetz deine Spritzattacken ein, um ein paar Bots zu vertreiben.\aErmutige auch deine Freunde, mit zu spritzen.\aWenn du 20 Bots vertreiben hast, komm wieder her und hol dir deine Belohnung ab!" }, + + 3222 : { QUEST : "Es ist höchste Zeit, deine Toonhaftigkeit unter Beweis zu stellen.\aWenn du erfolgreich eine Reihe von Bot-Gebäuden zurückholst, erwirbst du dir das Recht, drei Aufgaben zu tragen.\aErobere zunächst zwei beliebige Bot-Gebäude.\aDu darfst deine Freunde um Hilfe bitten."}, + 3223 : { QUEST : "Das mit den Gebäuden hast du gut gemacht!\aErobere nun zwei weitere Gebäude.\aDiese Gebäude müssen mindestens zwei Stockwerke hoch sein." }, + 3224 : { QUEST : "Fantastisch!\aJetzt brauchst du nur noch zwei weitere Gebäude erkämpfen.\aDiese Gebäude müssen mindestens drei Stockwerke hoch sein.\aWenn du fertig bist, komm zurück und hol dir deine Belohnung ab!", + COMPLETE : "Du hast es geschafft, _avName_!\aDu hast deine überaus große Toonhaftigkeit bewiesen.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ sagt, dass sie Hilfe braucht.\aGeh doch mal hin und frag, wie du ihr helfen kannst?_where_" }, + 3235 : { QUEST : "Oh, das ist der Salat, den ich bestellt habe!\aDanke, dass du ihn mir bringst.\aDiese Bots haben wohl _toNpcName_s eigentliches Lieferanten wieder mal vergrault.\aTu uns doch einen Gefallen und verjage ein paar von den Bots da draußen.\aBezwinge 10 Bots in Daisys Gärten und melde dich dann wieder bei _toNpcName_.", + INCOMPLETE_PROGRESS : "Du bist noch dabei, Bots für mich zu vertreiben?\aDas ist großartig! Mach so weiter!", + COMPLETE : "Oh, vielen Dank, dass du diese Bots vertrieben hast!\aJetzt kann ich vielleicht meinen normalen Lieferplan einhalten.\aDeine Belohnung ist ... ", + INCOMPLETE_WRONG_NPC : "Geh mal zu _toNpcName_ und berichte von den Bots, die du vertrieben hast._where_" }, + + 3236 : { QUEST : "Es gibt viel zu viele Rechtomaten da draußen.\aDu kannst deinen Teil zur Rettung beitragen!\aErobere 3 Rechtomaten-Gebäude." }, + 3237 : { QUEST : "Das mit den Rechtomaten-Gebäuden hast du gut gemacht!\aJetzt gibt es aber zu viele Schachermaten!\aErobere 3 Schachermaten-Gebäude, dann komm zurück und hol dir deine Belohnung ab." }, + + 3238 : { QUEST : "Oh nein! Ein 'Einmischer'-Bot hat den Schlüssel zu Daisys Gärten gestohlen!\aVersuche doch, ihn zurückzuholen.\aDenk dran, den Einmischer kann man nur in Schachermaten-Gebäuden finden." }, + 3239 : { QUEST : "Du hast zwar einen Schlüssel gefunden, aber es ist nicht der richtige!\aWir brauchen den Schlüssel zu Daisys Gärten.\aSuche weiter! Ein \"Einmischer\"-Bot hat ihn noch!" }, + + 3242 : { QUEST : "Oh nein! Ein Prozessgeier-Bot hat den Schlüssel zu Daisys Gärten gestohlen!\aVersuche mal, ihn zurückzuholen.\aDenk daran, Prozessgeier kann man nur in Rechtomaten-Gebäuden finden. " }, + 3243 : { QUEST : "Du hast zwar einen Schlüssel gefunden, aber es ist nicht der richtige!\aWir brauchen den Schlüssel zu Daisys Gärten.\aSuche weiter! Ein Prozessgeier-Bot hat ihn noch!" }, + + 3240 : { QUEST : "Ich habe grad von _toNpcName_ gehört, dass ein Prozessgeier eine Tüte Vogelfutter gestohlen hat.\aVertreibe Prozessgeier, bis du Volkers Vogelfutter zurückgeholt hast, und bringe es ihm dann.\aProzessgeier findet man nur in Rechtomaten-Gebäuden._where_", + COMPLETE : "Oh, vielen Dank, dass du mein Vogelfutter gefunden hast!\aDeine Belohnung ist ... ", + INCOMPLETE_WRONG_NPC : "Das mit dem Vogelfutter hast du gut gemacht!\aBring es jetzt zu _toNpcName_._where_", + }, + + 3241 : { QUEST : "Ein paar von den Bot-Gebäuden da draußen werden höher, als uns lieb ist.\aSieh mal zu, ob du ein paar von den höchsten Gebäuden erobern kannst.\aErobere 5 Gebäude mit drei oder mehr Stockwerken und komm´dann wieder, um dir deine Belohnung abzuholen.", + }, + + 3250 : { QUEST : "Detektivin Lima drüben in der Eichenstraße hat Meldungen über ein Schachermat-Hauptquartier erhalten.\aSpring mal rüber und hilf ihr bei den Untersuchungen.", + }, + 3251 : { QUEST : "Hier geht etwas Seltsames vor.\aEs gibt hier so viele Schachermaten!\aIch habe gehört, dass sie eine eigene Toontown-Zentrale am Ende dieser Straße eingerichtet haben.\aGeh mal die Straße runter und schau, ob du was rauskriegen kannst.\aFinde Schachermaten-Bots in ihrem Hauptquartier, besiege 5 und melde dich zurück.", + }, + 3252 : { QUEST : "OK, spuck's aus.\aWas sagst du da?\aSchachermaten-Hauptquartier?? Ach du Schreck!!! Es muss was passieren.\aWir müssen Richterin McIntosh Bescheid geben - sie wird wissen, was zu tun ist.\aGeh sofort los und erzähle ihr, was du herausgefunden hast. Du findest sie weiter die Straße runter.", + }, + 3253 : { QUEST : "Ja, kann ich helfen? Ich bin sehr beschäftigt.\aWas? Bot-Zentrale?\aWas? Unsinn. Das könnte nie passieren.\aDu musst dich irren. Völlig absurd.\aWas? Widersprich mir nicht.\aNa gut, dann bring mir Beweise.\aWenn Schachermaten wirklich dieses Bot-Zentrale bauen, dann trägt jeder Bot dort Blaupausen mit sich herum.\aBots lieben Papierkram, weißt du?\aJage Schachermaten, bis du Blaupausen findest.\aBring sie her, dann glaube ich dir vielleicht.", + }, + 3254 : { QUEST : "Du schon wieder, was? Blaupausen? Du hast sie?\aLass mich mal sehen! Hmmm... Eine Fabrik?\aDort bauen sie wohl die Schachermaten ... Und was ist das?\aJa, genau wie ich vermutete. Ich hab's ja immer gewusst.\aSie bauen ein Schachermaten-Bot-Hauptquartier.\aDas ist nicht gut. Muss telefonieren. Sehr viel zu tun. Auf Wiedersehen!\aWas? Ach ja, nimm diese Blaupausen mit zu Detektivin Lima.\aSie kann mehr damit anfangen.", + COMPLETE : "Was hat Richterin McIntosh gesagt?\aWir hatten Recht? Oh nein. Lass mal diese Blaupausen sehen.\aHmmm... Sieht aus, als würden die Schachermaten eine Fabrik mit Maschinen zur Herstellung von Bots bauen.\aKlingt sehr gefährlich. Halte dich raus, bis du mehr Lach-Punkte hast.\aWenn du mehr Lach-Punkte hast, müssen wir noch viel mehr über das Schachermaten-Hauptquartier rauskriegen.\aBisher aber gut gemacht, hier ist deine Belohnung.", + }, + + + 3255 : { QUEST : "_toNpcName_ untersucht das Schachermaten-Hauptquartier.\aSchau mal, ob du helfen kannst._where_" }, + 3256 : { QUEST : "_toNpcName_ untersucht das Schachermaten-Hauptquartier.\aSchau mal, ob du helfen kannst._where_" }, + 3257 : { QUEST : "_toNpcName_ untersucht das Schachermaten-Hauptquartier.\aSchau mal, ob du helfen kannst._where_" }, + 3258 : { QUEST : "Es gibt verschiedenste Vermutungen darüber, was die Bots in ihrem neuen Hauptquartier vorhaben.\aIch möchte, dass du direkt von ihnen ein paar Informationen holst.\aWenn wir vier interne Memos von Schachermaten aus ihrem Hauptquartiers bekommen können, dann werden wir klarer sehen.\aBringe dein erstes Memo zu mir, damit wir mehr erfahren.", + }, + 3259 : { QUEST : "Großartig! Lass mal sehen, was in dem Memo steht ... \a\"An alle Schachermaten:\aIch sitze in meinem Büro oben im Schachermat Tower und befördere Bots.\aWer genügend Verdienste gesammelt hat, steige in den Fahrstuhl in der Lobby und komme zu mir.\aDie Ferien sind vorbei - nun wieder an die Arbeit!\aUnterschrift: Schachermat VP\"\aAha ... das wird Flippy interessieren. Ich lasse es ihm sofort zukommen.\aBitte hole jetzt dein zweites Memo und bring es her.", + }, + 3260 : { QUEST : "Ach, gut, dass du zurückkommst. Mal sehen, was du gefunden hast ...\a\"An alle Schachermaten:\aSchachermat Towers hat ein neues Sicherheitssystem installiert, um die Toons fern zu halten.\aWenn Toons in Schachermat Towers aufgegriffen werden, werden sie zum Verhör festgehalten.\aAlles weitere kann in der Lobby bei einem Aperitif besprochen werden.\aUnterschrift: Einmischer\"\aSehr interessant ... Ich gebe diese Information sofort weiter.\aBitte bringe ein drittes Memo her.", + }, + 3261 : { QUEST : "Ausgezeichnete Arbeit, _avName_! Was steht da drin?\a\'An alle Schachermaten:\aToons haben einen Weg gefunden, um in Schachermat Towers einzudringen.\aIch werde Sie heute Abend beim Essen anrufen und Ihnen die Einzelheiten mitteilen.\aUnterschrift:Telemarketer'\aHmmm... ich frage mich, wie Toons da einbrechen ...\aBitte bring noch ein Memo, dann wissen wir erstmal genug, denke ich.", + COMPLETE : "Ich wusste, dass du es schaffen würdest! OK, in dem Memo heißt es ...\a\"An alle Schachermaten:\aIch war gestern mit Mr. Hollywood beim Lunch.\aEr berichtete, dass der VP zur Zeit sehr beschäftigt ist.\aEr macht nur Termine mit Bots, die eine Beförderung verdienen.\aNoch was vergessen: Glückshändchen spielt am Sonntag mit mir Golf.\aUnterschrift: Wichtigtuer\"\aAlso ... _avName_, das war sehr hilfreich.\aHier ist deine Belohnung.", + }, + + 3262 : { QUEST : "_toNpcName_ hat neue Informationen über die Fabrik des Schachermaten-Hauptquartier.\aGeh mal hin und schau, was er hat._where_" }, + 3263 : { GREETING : "Hi Sportsfreund!", + QUEST : "Ich bin Trainer Bemoost, aber du kannst Trainer B. zu mir sagen.\aVon mir stammen die Flechten in Hauen und Flechten, aber auch das Hauen, wenn du weißt, was ich meine.\aHör mal, die Schachermaten haben eine riesige Fabrik fertiggestellt, die 24 Stunden am Tag Schachermaten ausspuckt.\aHol mal ein paar Toon-Sportsfreunde zusammen und hau denen eins drauf!\aIhr müsst im Schachermaten-Hauptquartier nach dem Tunnel zur Fabrik Ausschau halten, und dann den Fabrikfahrstuhl nehmen.\aAchtet darauf, dass ihr volle Gags, volle Lach-Punkte und ein paar starke Toons als Führer habt.\aBesiegt den Vorarbeiter in der Fabrik, um das Vorankommen der Schachermaten aufzuhalten.\aKlingt wie `ne schweißtreibende Angelegenheit, wenn du weißt, was ich meine.", + LEAVING : "Mach's gut, Sportsfreund!", + COMPLETE : "He Sportsfreund, gute Arbeit da in der Fabrik!\aSieht aus, als hättest du da ein Bot-Anzugteil gefunden.\aDer muss wohl bei ihrer Bot-Herstellung übrig geblieben sein.\aDer kann noch gute Dienste leisten. Wenn du mal zu viel Zeit hast, sammle mehr von denen.\aWenn du einen ganzen Bot-Anzug zusammen hast, kann der vielleicht noch zu irgend etwas gut sein ...", + }, + + 4001 : {GREETING : "", + QUEST : "Du kannst jetzt den nächsten Gag-Alauf wählen, den du erlernen möchtest.\aNimm dir Zeit für die Entscheidung und komm zurück, wenn du bereit zum Wählen bist.", + INCOMPLETE_PROGRESS : "Denke gut nach, ehe du wählst.", + INCOMPLETE_WRONG_NPC : "Denke gut nach, ehe du wählst.", + COMPLETE : "Eine kluge Entscheidung ...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Du kannst jetzt den nächsten Gag-Ablauf wählen, den du erlernen möchtest.\aNimm dir Zeit für die Entscheidung und komm zurück, wenn du bereit zum Wählen bist.", + INCOMPLETE_PROGRESS : "Denke gut nach, ehe du wählst.", + INCOMPLETE_WRONG_NPC : "Denke gut nach, ehe du wählst.", + COMPLETE : "Eine kluge Entscheidung ...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Ich wette, Tom könnte bei seinen Forschungen etwas Hilfe gebrauchen._where_", + }, + 4201 : { GREETING: "Wie geht's, wie steht's?", + QUEST : "Ich bin sehr besorgt über eine Flut von Instrumentendiebstählen.\aIch mache gerade eine Umfrage bei meinen Händlerkollegen.\aVielleicht kann ich ein Muster erkennen, das mir beim Knacken dieses Falls hilft.\aGeh mal rüber zu Tina und frag sie nach einer Konzertina-Inventarliste._where_", + }, + 4202 : { QUEST : "Ja, ich habe heute früh mit Tom gesprochen.\aIch hab die Inventarliste hier.\aBring sie ihm gleich rüber, ja?_where_" + }, + 4203 : { QUEST : "Großartig! Eine weniger ...\aJetzt spring mal rüber und hole die von Yuki._where_", + }, + 4204 : { QUEST : "Oh! Die Inventarliste!\aHab ich ja völlig vergessen.\aIch wette, ich hab sie fertig, bis du 10 Bots vertrieben hast.\aKomm danach wieder rein, und ich verspreche dir, dass ich dann fertig bin.", + INCOMPLETE_PROGRESS : "31, 32... MI-st!\aWegen dir hab ich mich verzählt!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, da bist du ja.\aDanke, dass du mir etwas Zeit gegeben hast.\aNimm das hier mit zu Tom und grüß ihn schön von mir._where_", + }, + 4206 : { QUEST : "Hmmm, sehr interessant.\aJetzt kommen wir doch langsam voran.\aOK, die letzte Inventarliste ist die von Fifi._where_", + }, + 4207 : { QUEST : "Inventarliste?\aWie soll ich denn eine Inventarliste schreiben, wenn ich kein Formular habe?\aGeh mal zu Quint und frag ihn, ob er eins für mich hat._where_", + INCOMPLETE_PROGRESS : "Schon was Neues in Sachen Formular?", + }, + 4208 : { QUEST : "Nu, klar `abe isch Inventarformular!\aAber du musse bezahlen, weisstu?\aIsch mach dir Vorschlag. Isch tausche Formular für ganze Sahnetorte.", + GREETING : "Hey, was' los, man!", + LEAVING : "Hey, cool, man.", + INCOMPLETE_PROGRESS : "Ein Stücke reiche nischt.\aIsch 'abe sehr hungrig, man. Isch brauche GANZES Torte!", + }, + 4209 : { GREETING : "", + QUEST : "Mmmm...\aDas ist serrr gutt!\aHier deine Formular für Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Danke. Das ist eine große Hilfe.\aWolln mal sehen ...Fiedeln: 2\aSchon fertig! Bitteschön!", + COMPLETE : "Gute Arbeit, _avName_.\aIch bin sicher, dass ich diesen Diebstählen jetzt auf den Grund komme.\aKümmer du dich doch um das hier!", + }, + + 4211 : { QUEST : "Sag mal, Dr. Unsauber ruft alle fünf Minuten an. Kannst du mal hingehen und nachsehen, was er für ein Problem hat?_where_", + }, + 4212 : { QUEST : "Ui! Ich bin froh, dass die Toontown-Zentrale endlich jemanden geschickt hat.\aIch habe seit Tagen keine Kundschaft mehr.\aEs sind diese nervigen Erbsenzähler überall.\aIch glaube, die bringen unseren Einwohnern eine schlechte Mundhygiene bei.\aVertreibe zehn von ihnen, dann wollen wir doch mal sehen, ob das Geschäft wieder läuft.", + INCOMPLETE_PROGRESS : "Immer noch keine Kundschaft. Aber mach weiter!", + }, + 4213 : { QUEST : "Vielleicht waren es ja am Ende gar nicht die Erbsenzähler.\aVielleicht sind es die Monetomaten überhaupt.\aNimm dir mal zwanzig von ihnen vor, und dann kommt hoffentlich mal jemand wenigstens zur Kontrolle rein.", + INCOMPLETE_PROGRESS : "Ich weiß, zwanzig sind eine ganze Menge. Aber ich bin sicher, dass es sich mit Zins und Zinseszins auszahlen wird.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Ich verstehe das einfach nicht!\aImmer noch kein EINZIGER Patient!\aVielleicht müssen wir das Übel an der Wurzel anpacken.\aVersuche, ein Monetomaten-Bot-Gebäude zu erobern.\aDas dürfte funktionieren.", + INCOMPLETE_PROGRESS : "Ach, bitte! Nur ein ganz kleines Gebäude ...", + COMPLETE : "Immer noch kein Mensch hier.\aAber wenn ich es mir recht überlege ...\aIch hatte ja auch keine Kundschaft, bevor die Bots hier eingedrungen sind!\aIch bin dir für deine Hilfe wirklich dankbar.\aDas hier dürfte dir ein bisschen weiterhelfen." + }, + + 4215 : { QUEST : "Anna braucht dringend HIlfe.\aGeh doch mal rüber und schau, was du tun kannst._where_", + }, + 4216 : { QUEST : "Danke, dass du so schnell gekommen bist!\aEs scheint so, als hätten sich die Bots mit ein paar Kreuzfahrttickets meiner Kunden davongemacht.\aYuki sagt, sie hätte ein Glückshändchen gesehen, der hier rauskam und in seinen Glückshändchen lauter Tickets hatte.\aSchau doch mal, ob du Holzmichels Fahrkarte für Alaska zurückholen kannst.", + INCOMPLETE_PROGRESS : "Diese Glückshändchen können ja inzwischen sonstwo sein ...", + }, + 4217 : { QUEST : "Oh, großartig! Du hast es gefunden!\aJetzt sei so nett und flitz schnell zum Michel rüber, ja?_where_", + }, + 4218 : { QUEST : "Großer Goglmohsch!\aAlaska, ich komme!\aIch halte diese teuflischen Bots nicht mehr aus.\aDu, ich glaube, Anna braucht dich nochmal._where_", + }, + 4219 : { QUEST : "Jawoll, erraten.\aIch brauch dich, um diese vermaledeiten Glückshändchen nochmal zu filzen - wegen Tabithas Ticket zum Jazzfest.\aDu weißt ja jetzt, wie's geht ... ", + INCOMPLETE_PROGRESS : "Irgendwo ist da noch mehr ... ", + }, + 4220 : { QUEST : "Süß!\aKönntest du das auch noch bei ihm abgeben? _where_", + }, + 4221 : { GREETING : "", + LEAVING : "Bleib cool ...", + QUEST : "Cool, Daddy!\aJetzt bin ich wieder voll dabei, _avName_.\aBevor du abhaust, solltest du nochmal bei Anna Banana reinschauen ..._where_", + }, + 4222 : { QUEST : "Das ist das letzte Mal, Ehrenwort!\aJetzt suchst du nach Grizzlys Ticket für den großen Gesangswettbewerb.", + INCOMPLETE_PROGRESS : "Ach, komm schon, _avName_.\aGrizzly zählt auf dich.", + }, + 4223 : { QUEST : "Das dürfte ein Lächeln auf Grizzlys Gesicht zaubern._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Hallo, Hallo, HALLO!\aKlasse!\aIch weiß, dass die Jungs und ich dieses Jahr groß abräumen werden.\aAnna sagt, du sollst vorbeikommen und deine Belohnung holen._where_\aWiedersehen, Wiedersehn, WIEDER-WIEDERSEHN!", + COMPLETE : "Danke für deine große Hilfe, _avName_.\aDu bist wirklich ein Gewinn für Toontown.\aApropos Gewinn ...", + }, + + 902 : { QUEST : "Geh mal zu Leo.\aEr braucht jemanden, der für ihn eine Nachricht überbringt._where_", + }, + 4903 : { QUEST : "Alter!\aMeine Kastagnetten sehen ganz matt aus und ich habe heute einen großen Auftritt.\aBring sie zu Carlos, der kann sie vielleicht aufpolieren._where_", + }, + 4904 : { QUEST : "Ja, isch glaubä isch kann dissä polieren.\aAbbär isch brauchä das blauä Tintä aus Tintänfischä.", + GREETING : "Hola!", + LEAVING : "Adios!", + INCOMPLETE_PROGRESS : "Du kannst Tintänfischä findän, wo Angälstäg ist.", + }, + 4905 : { QUEST : "Ja! Dass gutt!\aNun ich brauchä biesschen Zeit für Polierän dissä.\aDu kannst gehän ein Einstockgebäudä übärnähmän, während isch arbeitä, gutt? ", + GREETING : "Hola!", + LEAVING : "Adios!", + INCOMPLETE_PROGRESS : "Eino Momento ...", + }, + 4906 : { QUEST : "Särr gutt!\aHier sind Kastagnättän für Läo._where_", + }, + 4907 : { GREETING : "", + QUEST : "Cool, Alter!\aDie sehen echt kastanig aus!\aJetzt musst du mal noch bei Hedy den Text von 'Beat nun ist Weihnachtszeit' für mich holen._where_", + }, + 4908 : { QUEST: "Tag!\aHmmm, ich hab das Lied grad nicht zur Hand.\aWenn du mir einen Moment Zeit gibst, notiere ich es aus dem Gedächtnis.\aDu könntest doch mal losgehen und ein zweistöckiges Gebäude eroberst, während ich schreibe!", + }, + 4909 : { QUEST : "Tut mir Leid.\aMein Gedächtnis lässt ein bisschen zu wünschen übrig.\aWenn du noch ein dreistöckiges Gebäude zurück eroberst, bin ich bestimmt fertig, wenn du zurückkommst ...", + }, + 4910 : { QUEST : "Fertig!\aTut mir Leid, dass es so lange gedauert hat.\aNimm das hier mit zu Leo._where_", + GREETING : "", + COMPLETE : "Klasse, Alter!\aMein Konzert wird dermaßen rocken, dass es alle umhaut!\aApropos umhauen, hiermit kannst du ein paar Bots umhauen ... " + }, + 5247 : { QUEST : "Diese Gegend ist schon ziemlich heftig ...\aVielleicht magst du ein paar neue Tricks lernen.\a_toNpcName_ hat mir alles Notwendige beigebracht; vielleicht kann er dir auch helfen._where_" }, + 5248 : { GREETING : "Ahh, ja.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Du scheinst ein Problem mit meiner Anweisung zu haben?", + QUEST : "Ahh, also willkommen, neuer Lehrling.\aIch weiß alles, was man über die Sache mit den Torten wissen muss.\aBevor wir aber mit deinem Training anfangen, ist eine kleine Demonstration vonnöten.\aGehe hinaus und erledige zehn von den größten Bots." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "Ausgezeichnet!\aNun beweise noch deine Fähigkeiten als Angler.\aIch habe gestern drei Fellwürfel in den Teich geworfen.\aFisch sie raus und bring sie mir. ", + LEAVING : "", + INCOMPLETE_PROGRESS : "Anscheinend stellst du dich mit Rute und Rolle nicht ganz so geschickt an." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! Diese Würfel werden sich am Rückspiegel meines Ochsenkarrens gut machen!\aJetzt zeige mir doch noch, dass du deine Feinde unterscheiden kannst.\aKomm wieder her, wenn du zwei der größten Rechtomaten-Gebäude zurückgeholt hast.", + INCOMPLETE_PROGRESS : "Gibt's Probleme mit den Gebäuden?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! Diese Würfel werden sich am Rückspiegel meines Ochsenkarrens gut machen!\aJetzt zeige mir doch noch, dass du deine Feinde unterscheiden kannst.\aKomm wieder her, wenn du zwei der größten Chefomaten-Gebäude zurückgeholt hast.", + INCOMPLETE_PROGRESS : "Gibt's Probleme mit den Gebäuden?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! Diese Würfel werden sich am Rückspiegel meines Ochsenkarrens gut machen!\aJetzt zeige mir doch noch, dass du deine Feinde unterscheiden kannst.\aKomm wieder her, wenn du zwei der größten Monetomaten-Gebäude zurückgeholt hast.", + INCOMPLETE_PROGRESS : "Gibt's Probleme mit den Gebäuden?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Aha! Diese Würfel werden sich am Rückspiegel meines Ochsenkarrens gut machen!\aJetzt zeige mir doch noch, dass du deine Feinde unterscheiden kannst.\aKomm wieder her, wenn du zwei der größten Schachermaten-Gebäude zurückgeholt hast.", + INCOMPLETE_PROGRESS : "Gibt's Probleme mit den Gebäuden?", }, + 5200 : { QUEST : "Diese hinterlistigen Bots haben wieder zugeschlagen.\a_toNpcName_ hat noch einen verschwundenen Gegenstand gemeldet. Schau mal dort vorbei, ob du das regeln kannst._where_" }, + 5201 : { GREETING: "", + QUEST : "Hi, _avName_. Ich schätze, ich sollte dir für dein Kommen danken.\aEin paar von diesen Köpfchenjägern kamen hier rein und stahlen meinen Fußball.\aDer Anführer meinte zu mir, ich müsste ein paar Abstriche machen, und dann riss er ihn mir einfach aus der Hand!\aKannst du meinen Ball zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Na schon irgendein Erfolg bei der Suche nach meinem Fußball?", + COMPLETE : "Jippieh! Du hast ihn gefunden! Hier, nimm deine Belohnung ...", + }, + 5261 : { GREETING: "", + QUEST : "Hi, _avName_. Ich schätze, ich sollte dir für dein Kommen danken.\aEin paar von diesen Falschgesichtern kamen hier rein und stahlen meinen Fußball.\aDer Anführer meinte zu mir, ich müsste ein paar Abstriche machen, und dann riss er ihn mir einfach aus der Hand!\aKannst du meinen Ball zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Na schon irgendein Erfolg bei der Suche nach meinem Fußball?", + COMPLETE : "Jippieh! Du hast ihn gefunden! Hier, nimm deine Belohnung ...", + }, + 5262 : { GREETING: "", + QUEST : "Hi, _avName_. Ich schätze, ich sollte dir für dein Kommen danken.\aEin paar von diesen Geldsäcken kamen hier rein und stahlen meinen Fußball.\aDer Anführer meinte zu mir, ich müsste ein paar Abstriche machen, und dann riss er ihn mir einfach aus der Hand!\aKannst du meinen Ball zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Na schon irgendein Erfolg bei der Suche nach meinem Fußball?", + COMPLETE : "Jippieh! Du hast ihn gefunden! Hier, nimm deine Belohnung ...", + }, + 5263 : { GREETING: "", + QUEST : "Hi, _avName_. Ich schätze, ich sollte dir für dein Kommen danken.\aEin paar von diesen Schönrednern kamen hier rein und stahlen meinen Fußball.\aDer Anführer meinte zu mir, ich müsste ein paar Abstriche machen, und dann riss er ihn mir einfach aus der Hand!\aKannst du meinen Ball zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Na schon irgendein Erfolg bei der Suche nach meinem Fußball?", + COMPLETE : "Jippieh! Du hast ihn gefunden! Hier, nimm deine Belohnung ...", + }, + 5202 : { QUEST : "Einige der härtesten Bots, die wir bisher kennengelernt haben, sind in Das Brrr eingefallen.\aDu solltest hier wahrscheinlich lieber ein paar mehr Gags bei dir tragen.\aIch habe gehört, dass _toNpcName_ vielleicht eine große Tasche hat, die du dafür verwenden kannst._where_" }, + 5203 : { GREETING: "Hä? Bist du von meiner Schlittenmannschaft?", + QUEST : "Was ist los? Du willst eine Tasche?\aIch hatte hier irgendwo eine ... vielleicht ist sie in meinem Schlitten?\aNur ... ich hab meinen Schlitten seit dem großen Rennen nicht mehr gesehen!\aVielleicht hat ihn einer von diesen Bots mitgenommen?", + LEAVING : "Hast du meinen Schlitten gesehen?", + INCOMPLETE_PROGRESS : "Wer bist du nochmal? Tut mir Leid, ich bin noch etwas wirr im Kopf von dem Zusammenstoß." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Ist das mein Schlitten? Ich seh hier keine Tasche.\aIch glaube, Huckelberry Schlitzauge war in der Mannschaft ... vielleicht hat er sie?_where_" }, + 5205 : { GREETING : "Ooooh, mein Kopf!", + LEAVING : "", + QUEST : "Hä? Schorsch wer? Eine Tasche?\aOh, vielleicht war er in unserer Schlittenmannschaft?\aMein Kopf tut so weh, dass ich nicht klar denken kann..\aKönntest du aus dem zugefrorenen Teich ein paar Eiswürfel für meinen Kopf fischen?", + INCOMPLETE_PROGRESS : "Auuu, mein Kopf bringt mich noch um! Hast du Eis?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Ahhh, das ist schon viel besser!\aDu suchst also nach Schorschs Tasche, hm?\aIch glaube, die ist nach dem Zusammenstoß auf Halbaffen-Sams Kopf gelandet._where_" }, + 5207 : { GREETING : "Iiiiiip!", + LEAVING : "", + QUEST : "Was Tasche? Wer Hockelberry?\aIch Angst vor Gebäude! Du hauen Gebäude, ich geben dir Tasche!", + INCOMPLETE_PROGRESS : "Mehr Gebäude! Ich noch Angst!", + COMPLETE : "Ooooh! Ich haben dich gern!" }, + 5208 : { GREETING : "", + LEAVING : "Iiiiik!", + QUEST : "Ooooh! Ich haben dich gern!\aGehen zu Schiklinik. Tasche dort." }, + 5209 : { GREETING : "Alter!", + LEAVING : "Bis später!", + QUEST : "Mann, dieser Halbaffen-Sam ist vielleicht verrückt!\aWenn du nur halb so verrückt bist wie Sam, geb ich dir die Tasche, Mann.\aHau mal paar Bots für deine Tasche in die Tasche, Mann! Na los!", + INCOMPLETE_PROGRESS : "Bist du sicher, dass das extrem genug war? Hau noch ein paar Bots in die Tasche.", + COMPLETE : "He, du bist ja ganz schön verrückt! Das war vielleicht ein Haufen Bots, die du da eingetütet hast!\aHier ist deine Tasche!" }, + + 5210 : { QUEST : "_toNpcName_ ist heimlich in jemanden aus der Nachbarschaft verliebt.\aWenn du ihr hilfst, bekommst du vielleicht eine hübsche Belohnung._where_" }, + 5211 : { GREETING: "Huu-huuu.", + QUEST : "Ich hab die ganze letzte Nacht einen Brief an den Burschen, den ich liebe, geschrieben.\aDoch bevor ich ihn hinbringen konnte, kam einer dieser hässlichen Bots mit Schnabel und nahm ihn weg.\aKannst du ihn für mich zurückholen?", + LEAVING : "Huu-huuu.", + INCOMPLETE_PROGRESS : "Bitte finde meinen Brief." }, + + 5264 : { GREETING: "Huu-huuu.", + QUEST : "Ich hab die ganze letzte Nacht einen Brief an den Burschen, den ich liebe, geschrieben.\aDoch bevor ich ihn hinbringen konnte, kam einer dieser hässlichen Bots mit Flosse und nahm ihn weg.\aKannst du ihn für mich zurückholen?", + LEAVING : "Huu-huuu.", + INCOMPLETE_PROGRESS : "Bitte finde meinen Brief." }, + 5265 : { GREETING: "Huu-huuu", + QUEST : "Ich hab die ganze letzte Nacht einen Brief an den Burschen, den ich liebe, geschrieben.\aDoch bevor ich ihn hinbringen konnte, kam einer dieser hässlichen Einmischer-Bots und nahm ihn weg.\aKannst du ihn für mich zurückholen?", + LEAVING : "Huu-huuu.", + INCOMPLETE_PROGRESS : "Bitte finde meinen Brief." }, + 5266 : { GREETING: "Huu-huuu.", + QUEST : "Ich hab die ganze letzte Nacht einen Brief an den Burschen, den ich liebe, geschrieben.\aDoch bevor ich ihn hinbringen konnte, kam einer dieser hässlichen Unternehmensräuber mit Schnabel und nahm ihn weg.\aKannst du ihn für mich zurückholen?", + LEAVING : "Huu-huuu.", + INCOMPLETE_PROGRESS : "Bitte finde meinen Brief." }, + 5212 : { QUEST : "Oh, danke, dass du meinen Brief gefunden hast!\aBitte, bitte, bitte, könntest du ihn zum hübschesten Burschen der Gegend bringen?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Du hast meinen Brief noch nicht abgegeben, oder?", + }, + 5213 : { GREETING : "Entzückend, natürlich.", + QUEST : "Ich hab jetzt keinen Nerv für deinen Brief.\aMir hat jemand all meine Hündchen weggenommen!\aWenn du sie zurückbringst, können wir nochmal drüber reden.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Meine armen kleinen Hündchen!" }, + 5214 : { GREETING : "", + LEAVING : "Tudelu!", + QUEST : "Danke, dass du meine kleinen Schönen zurückgebracht hast.\aDann wollen wir uns mal deinen Brief ansehen ... \a Mmmm, es scheint, als hätte ich noch eine heimliche Verehrerin.\aDa ist wohl ein Besuch bei meinem lieben Freund Karl angesagt.\aIch bin sicher, du wirst ihn unheimlich mögen._where_" }, + 5215 : { GREETING : "Hehe ...", + LEAVING : "Komm wieder, jaja.", + INCOMPLETE_PROGRESS : "Da sind immer noch ein paar Große unterwegs. Komm zu uns zurück, wenn die weg sind.", + QUEST : "Wer hat dich zu uns geschickt? Wir mögen Schnautzies nicht besonders, nein, nein ...\aAber wir tun Bots noch weniger mögen ...\aTu du die Großen vertreiben und wir helfen dir, ja, ja." }, + 5216 : { QUEST : "Wir haben dir ja gesagt, dass wir dir helfen tun.\aAlso tu diesen Ring zu dem Mädel bringen.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Du tust diesen Ring immer noch haben???", + COMPLETE : "Oh Liiiiebling!!! Danke!!!\aOh, und ich habe auch etwas Besonderes für dich.", + }, + 5217 : { QUEST : "Es klingt, als könnte _toNpcName_ Hilfe gebrauchen._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich bin sicher, dass da irgendwo noch mehr Einmischer unterwegs sind.", + QUEST : "Hilfe!!! Hilfe!!! Ich kann nicht mehr!\aDiese Einmischer machen mich wahnsinnig!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Das können nicht alle gewesen sein. Ich habe gerade einen gesehen!!!", + QUEST : "Oh, danke, aber jetzt sind es die Unternehmensräuber!!!\aDu musst mir helfen!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nein, nein, nein, es war grad einer hier!", + QUEST : "Ich merke jetzt, dass es diese Kredithaie sind!!!\aIch dachte, du wolltest mich retten!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Weißt du was, vielleicht sind es gar nicht die Bots!\aKönntest du Fanny bitten, mir einen Beruhigungstrank zu mixen? Vielleicht hilft das ... _where_" }, + 5222 : { LEAVING : "", + QUEST : "Oh, dieser Harry, der ist schon ein Spaßvogel!\aIch mix was zusammen, das ihm hilft!\aOh, anscheinend habe ich keinen Stein des Weisen mehr...\aSei so lieb, lauf runter zum Teich und hol mir was.", + INCOMPLETE_PROGRESS : "Hast du schon einen Stein des Weisen für mich?", }, + 5223 : { QUEST : "Okay. Danke, Schatz.\aHier, bring das zu Harry. Das dürfte ihn voll beruhigen.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nun geh schon, bring den Trank zu Harry.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Los, schnapp dir diese Prozessgeier für mich, ja?", + QUEST : "Oh, Gott sei dank Du bist zurück!\aGib mir den Trank, schnell!!!\aGluckgluckgluck ...\aScheußlich!\aAber weißt du was? Ich bin schon viel ruhiger. Jetzt, da ich klar denken kann, merke ich ...\aEs waren die Prozessgeier, die mich die ganze Zeit verrückt gemacht haben!", + COMPLETE : "Jungejunge! Jetzt kann ich mich entspannen!\aIch bin sicher, dass es hier irgend etwas gibt, was ich dir geben kann. Oh, nimm das!" }, + 5225 : { QUEST : "Seit dem Vorfall mit dem Rübenbrot ist Phil Mürrisch stinksauer auf _toNpcName_.\aVielleicht kannst du Gert helfen, die Sache zwischen ihnen ins Lot zu bringen?_where_" }, + 5226 : { QUEST : "Ja, du hast ja vielleicht gehört, dass Phil Mürrisch stinkesauer auf mich ist...\aIch wollte ja nur nett sein mit dem Rübenbrot.\aVielleicht kannst du mir helfen, ihn aufzumuntern.\aPhil hasst diese Monetomaten-Bots, besonders ihre Gebäude.\aWenn du ein paar Monetomaten-Gebäude zurückholst, hilft das vielleicht.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vielleicht noch ein paar Gebäude?", }, + 5227 : { QUEST : "Das ist unglaublich! Geh zu Phil und erzähle ihm, was du getan hast._where_" }, + 5228 : { QUEST : "Ach, das hat er getan, ja?\aDieser Gert denkt wohl, er kommt so einfach davon, was?\aIch hab mir ja nur einen Zahn abgebrochen an seinem blöden Rübenbrot!\aVielleicht könntest du meinen Zahn zu Dr. Mummelgesicht bringen, damit der ihn wieder hinkriegt.", + GREETING : "Mmmmrrrfff.", + LEAVING : "Grummelgrummel.", + INCOMPLETE_PROGRESS : "Du schon wieder? Ich dachte, du wolltest meinen Zahn reparieren lassen.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich arbeite noch an dem Zahn. Es dauert noch etwas.", + QUEST : "Ja, dieser Zahn sieht wirklich ziemlich bös aus, hihi.\aVielleicht kann ich ja was machen, aber das wird etwas dauern.\aVielleicht kannst du ja währenddessen ein paar von diesen Monetomaten-Bots von der Straße räumen?\aDie verschrecken meine Kundschaft." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich arbeite noch an dem Zahn. Es dauert noch etwas.", + QUEST : "Ja, dieser Zahn sieht wirklich ziemlich bös aus, hihi.\aVielleicht kann ich ja was machen, aber das wird etwas dauern.\aVielleicht kannst du ja währenddessen ein paar von diesen Schachermaten-Bots von der Straße räumen?\aDie verschrecken meine Kundschaft." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich arbeite noch an dem Zahn. Es dauert noch etwas.", + QUEST : "Ja, dieser Zahn sieht wirklich ziemlich bös aus, hihi.\aVielleicht kann ich ja was machen, aber das wird etwas dauern.\aVielleicht kannst du ja währenddessen ein paar von diesen Rechtomaten-Bots von der Straße räumen?\aDie verschrecken meine Kundschaft." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich arbeite noch an dem Zahn. Es dauert noch etwas.", + QUEST : "Ja, dieser Zahn sieht wirklich ziemlich bös aus.\aVielleicht kann ich ja was machen, aber das wird etwas dauern.\aVielleicht kannst du ja währenddessen ein paar von diesen Chefomaten-Bots von der Straße räumen?\aDie verschrecken meine Kundschaft." }, + 5230 : { GREETING: "", + QUEST : "Ich bin froh, dass du wieder da bist!\aIch hab es aufgegeben, diesen alten Zahn zu reparieren, und habe Phil dafür einen neuen Goldzahn gemacht.\aLeider kam ein Ausbeuter herein und nahm in mir ab.\aDu holst ihn vielleicht noch ein, wenn du dich beeilst.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du den Zahn schon gefunden?" }, + 5270 : { GREETING: "", + QUEST : "Ich bin froh, dass du wieder da bist!\aIch hab es aufgegeben, diesen alten Zahn zu reparieren, und habe Phil dafür einen neuen Goldzahn gemacht.\aLeider kam ein Großhirn herein und nahm in mir ab.\aDu holst es vielleicht noch ein, wenn du dich beeilst.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du den Zahn schon gefunden?" }, + 5271 : { GREETING: "", + QUEST : "Ich bin froh, dass du wieder da bist!\aIch hab es aufgegeben, diesen alten Zahn zu reparieren, und habe Phil dafür einen neuen Goldzahn gemacht.\aLeider kam Mr. Hollywood herein und nahm in mir ab.\aDu holst ihn vielleicht noch ein, wenn du dich beeilst.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du den Zahn schon gefunden?" }, + 5272 : { GREETING: "", + QUEST : "Ich bin froh, dass du wieder da bist!\aIch hab es aufgegeben, diesen alten Zahn zu reparieren, und habe Phil dafür einen neuen Goldzahn gemacht.\aLeider kam ein Großkotz herein und nahm in mir ab.\aDu holst ihn vielleicht noch ein, wenn du dich beeilst.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du den Zahn schon gefunden?" }, + 5231 : { QUEST : "Großartig, das ist der Zahn, hihi!\aBring ihn doch für mich zu Phil rüber, ja?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich wette, Phil möchte seinen neuen Zahn sehen.", + }, + 5232 : { QUEST : "Oh, danke.\aMmmrrrfff.\aWie sieht das aus, hm?\aOkay, du kannst Gert sagen, dass ich ihm verzeihe.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, das freut mich zu hören.\aIch dachte mir schon, dass der alte Phil nicht ewig auf mich sauer sein kann.\aAls Geste des guten Willens hab ich ihm dieses Kiefernzapfenbrot gebacken.\aKönntest du es zu ihm rüberbringen?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mach lieber schnell. Kiefernzapfenbrot ist besser, wenn es noch warm ist.", + COMPLETE : "Oh, was ist das denn? Für mich?\aMampf-mampf...\aAuuuu! Mein Zahn! Dieser Gert Gänsburger!\aNaja, ist ja nicht deine Schuld. Hier, du kannst das für deine Mühe haben.", + }, + 903 : { QUEST : "Du bist jetzt vielleicht soweit, _toNpcName_ den Schnee-Weisen wegen deiner Abschlussprüfung aufzusuchen._where_", }, + 5234 : { GREETING: "", + QUEST : "Aha, da bist du ja wieder.\aBevor wir anfangen, müssen wir was essen.\aBring uns etwas Krümelkäse für unsere Brühe.\aKrümelkäse kann man nur von Großhirn-Bots holen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Wir brauchen immer noch Krümelkäse. " }, + 5278 : { GREETING: "", + QUEST : "Aha, da bist du ja wieder.\aBevor wir anfangen, müssen wir was essen.\aBring uns etwas Kaviar für unsere Brühe.\aKaviar kann man nur von Mr.-Hollywood-Bots holen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Wir brauchen immer noch Kaviar." }, + 5235 : { GREETING: "", + QUEST : "Ein einfacher Mann isst mit einem einfachen Löffel.\aEin Bot hat mir meinen einfachen Löffel weggenommen, also kann ich nicht essen.\aBring mir meinen Löffel wieder. Ich glaube, ein Ausbeuter hat ihn weggenommen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich muss einfach meinen Löffel haben." }, + 5279 : { GREETING: "", + QUEST : "Ein einfacher Mann isst mit einem einfachen Löffel.\aEin Bot hat mir meinen einfachen Löffel weggenommen, also kann ich nicht essen.\aBring mir meinen Löffel wieder. Ich glaube, ein Großkotz hat ihn weggenommen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ich muss einfach meinen Löffel haben." }, + 5236 : { GREETING: "", + QUEST : "Vielen Dank.\aSchlürf-schlürf...\aAhhh, nun musst du eine sprechende Kröte einfangen. Versuche mal, im Teich zu fischen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Wo ist die sprechende Kröte?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Du hast noch keinen Nachtisch geholt.", + QUEST : "Oh, das ist tatsächlich eine sprechende Kröte. Gib sie mir mal.\aWas sagst du da, Kröte?\aAha.\aAha...\aDie Kröte hat gesprochen. Wir brauchen Nachtisch.\aBring uns ein paar Eistüten von _toNpcName_.\aDie Kröte möchte aus irgendwelchen Gründen Eistüten mit Rote-Bohnen-Geschmack._where_", }, + 5238 : { GREETING: "", + QUEST : "Der Weise hat dich also geschickt. Es tut mir Leid, aber wir haben seit kurzem keine Eistüten mit Rote-Bohnen-Geschmack mehr.\aWeißt du, ein paar Bots kamen rein und nahmen alle mit.\aSie sagten, sie wären für Mr. Hollywood, oder irgend so einen Quatsch.\aIch wäre sehr froh, wenn du sie mir zurückholen könntest.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du schon alle meine Eistüten gefunden?" }, + 5280 : { GREETING: "", + QUEST : "Der Weise hat dich also geschickt. Es tut mir Leid, aber wir haben seit kurzem keine Eistüten mit Rote-Bohnen-Geschmack mehr.\aWeißt du, ein paar Bots kamen rein und nahmen alles mit.\aSie sagten, sie wären für das Großhirn, oder irgend so einen Quatsch.\aIch wäre sehr froh, wenn du sie mir zurückholen könntest.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du schon alle meine Eistüten gefunden?" }, + 5239 : { QUEST : "Danke, dass du meine Eistüten zurückgebracht hast!\aHier ist eine für Lil Altmann.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Bring das Eis lieber zu Lil Altmann, bevor es schmilzt.", }, + 5240 : { GREETING: "", + QUEST : "Sehr gut. Hier, für dich, Kröte ...\aSchlürf-schlürf ...\aOkay, jetzt sind wir fast fertig.\aWenn du mir nur noch etwas Puder bringen könntest, um meine Hände zu trocknen.\aIch denke, dass diese Großkotze manchmal Puder von ihren Perücken haben könnten.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du Puder gefunden?" }, + 5281 : { GREETING: "", + QUEST : "Sehr gut. Hier, für dich, Kröte ...\aSchlürf-schlürf ...\aOkay, jetzt sind wir fast fertig.\aWenn du mir nur noch etwas Puder bringen könntest, um meine Hände zu trocknen.\aIch denke, dass diese Mr.-Hollywood-Bots manchmal Puder für ihre Nasen dabei haben.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hast du Puder gefunden?" }, + 5241 : { QUEST : "Okay.\aWie ich einmal sagte, um wirklich eine Torte zu werfen, darf man nicht mit der Hand werfen ...\a... sondern mit der Seele.\aIch weiß nicht, was das bedeutet, und deshalb werde ich hier sitzen und darüber nachdenken, während du Gebäude zurück eroberst.\aKomm wieder her, wenn du deine Aufgabe abgeschlossen hast.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Deine Aufgabe ist noch nicht abgeschlossen.", }, + 5242 : { GREETING: "", + QUEST : "Obgleich ich immer noch nicht weiß, wovon ich rede, bist du wahrhaft würdig.\aIch gebe dir eine letzte Aufgabe ...\aDie sprechende Kröte ist ein Er und sucht eine Freundin.\aFinde ein weitere sprechende Kröte. Die Kröte hat gesprochen.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Wo ist die andere sprechende Kröte?", + COMPLETE : "Hui! Ich bin müde von all diesen Anstrengungen. Ich muss jetzt ruhen.\aHier, nimm deine Belohnung und hebe dich hinweg." }, + + 5243 : { QUEST : "Schwitze-Peter fängt schon an, die ganze Straße hinauf zu stinken.\aKannst du ihn vielleicht überreden, mal zu duschen oder so?_where_" }, + 5244 : { GREETING: "", + QUEST : "Ja, ich komm hier drin anscheinend ganz schön ins Schwitzen.\aMmmm, wenn ich vielleicht das undichte Rohr in meiner Dusche reparieren könnte ...\aIch glaube, ein Zahnrad von einem dieser winzigen Bots könnte helfen.\aGeh los und suche ein Zahnrad von einem Mikromanager, dann versuchen wir's.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Wo ist das Zahnrad, das du holen wolltest?" }, + 5245 : { GREETING: "", + QUEST : "Jau, das scheint zu funktionieren.\aAber ich fühle mich so allein, wenn ich dusche ...\aKönntest du mir vielleicht ein Quietschentchen fischen gehen, damit es mir Gesellschaft leistet?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Kein Talentchen für das Entchen?" }, + 5246 : { QUEST : "Das Entchen ist toll, aber ...\aDiese ganzen Gebäude hier machen mich nervös.\aIch wäre viel entspannter, wenn es weniger Gebäude gäbe.", + LEAVING : "", + COMPLETE : "Okay, ich dusch mich jetzt mal. Und hier ist auch was für dich.", + INCOMPLETE_PROGRESS : "Ich hab immer noch ein Problem mit Gebäuden.", }, + 5251 : { QUEST : "Ladewig Mädelsturm soll heute Abend auftreten.\aIch habe gehört, dass er irgendein Problem mit seinem Equipment hat._where_" }, + 5252 : { GREETING: "", + QUEST : "Oh yeah! Ich kann Hilfe gut gebrauchen.\aDiese Bots kamen rein und klauten mein ganzes Zeug, als ich den Transporter auslud.\aKannst du mir helfen und mein Mikrofon zurückholen?", + LEAVING : "", + INCOMPLETE_PROGRESS : "He Mann, ich kann ohne mein Mikro nicht singen!" }, + 5253 : { GREETING: "", + QUEST : "Yeah, das ist mein Mikrofon.\aDanke, dass du es besorgt hast, aber...\aIch brauch echt noch mein Keyboard, um in die Tasten zu hauen.\aIch glaube, einer von diesen Unternehmensräubern hat mein Keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Noch keinen Erfolg mit meinem Keyboard?" }, + 5273 : { GREETING: "", + QUEST : "Yeah, das ist mein Mikrofon.\aDanke, dass du es besorgt hast, aber...\aIch brauch echt noch mein Keyboard, um in die Tasten zu hauen.\aIch glaube, einer von diesen Einmischern hat mein Keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Noch keinen Erfolg mit meinem Keyboard?" }, + 5274 : { GREETING: "", + QUEST : "Yeah, das ist mein Mikrofon.\aDanke, dass du es besorgt hast, aber...\aIch brauch echt noch mein Keyboard, um in die Tasten zu hauen.\aIch glaube, einer von diesen Kredithaien hat mein Keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Noch keinen Erfolg mit meinem Keyboard?" }, + 5275 : { GREETING: "", + QUEST : "Yeah, das ist mein Mikrofon.\aDanke, dass du es besorgt hast, aber...\aIch brauch echt noch mein Keyboard, um in die Tasten zu hauen.\aIch glaube, einer von diesen Prozessgeiern hat mein Keyboard.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Noch keinen Erfolg mit meinem Keyboard?" }, + 5254 : { GREETING: "", + QUEST : "Okay! Jetzt bin ich wieder voll dabei.\aWenn die nur nicht meine Plateauschuhe mitgenommen hätten ...\aDiese Schuhe sind wahrscheinlich bei einem Mr. Hollywood gelandet, wenn du mich fragst.", + LEAVING : "", + COMPLETE : "Okay! Jetzt geht's los.\aHallo Brrr!!!\aHä? Wo sind die denn alle?\aOkay, nimm das hier und trommle ein paar Fans für mich zusammen, ja?", + INCOMPLETE_PROGRESS : "Ich kann ja wohl nicht barfuß auftreten, oder!?" }, + 5282 : { GREETING: "", + QUEST : "Okay! Jetzt bin ich wieder voll dabei.\aWenn die nur nicht meine Plateauschuhe mitgenommen hätten ...\aDiese Schuhe sind wahrscheinlich bei einem Großhirn gelandet, wenn du mich fragst.", + LEAVING : "", + COMPLETE : "Okay!! Jetzt geht's los.\aHallo Brrr!!!\aHä? Wo sind die denn alle?\aOkay, nimm das hier und trommle ein paar Fans für mich zusammen, ja?", + INCOMPLETE_PROGRESS : "Ich kann ja wohl nicht barfuß auftreten, oder!?" }, + 5283 : { GREETING: "", + QUEST : "Okay! Jetzt bin ich wieder voll dabei.\aWenn die nur nicht meine Plateauschuhe mitgenommen hätten ...\aDiese Schuhe sind wahrscheinlich bei einem Ausbeuter gelandet, wenn du mich fragst.", + LEAVING : "", + COMPLETE : "Okay!! Jetzt geht's los.\aHallo Brrr!!!\aHä? Wo sind die denn alle?\aOkay, nimm das hier und trommle ein paar Fans für mich zusammen, ja?", + INCOMPLETE_PROGRESS : "Ich kann ja wohl nicht barfuß auftreten, oder!?" }, + 5284 : { GREETING: "", + QUEST : "Okay! Jetzt bin ich wieder voll dabei.\aWenn die nur nicht meine Plateauschuhe mitgenommen hätten ...\aDiese Schuhe sind wahrscheinlich bei einem Großkotz gelandet, wenn du mich fragst.", + LEAVING : "", + COMPLETE : "Okay!! Jetzt geht's los.\aHallo Brrr!!!\aHä? Wo sind die denn alle?\aOkay, nimm das hier und trommle ein paar Fans für mich zusammen, ja?", + INCOMPLETE_PROGRESS : "Ich kann ja wohl nicht barfuß auftreten, oder!?" }, + + 5255 : { QUEST : "Du siehst aus, als könntest du mehr Lach-Punkte gebrauchen.\aVielleicht macht dir _toNpcName_ ein gutes Angebot.\aLass es dir aber schriftlich geben ..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Abgemacht ist abgemacht.", + QUEST : "Du brauchst also Lach-Punkte, was?\aKlar hab ich'n Deal für dich!\aKümmere dich einfach für mich um ein paar Chefomaten-Bots ...\aUnd ich sorge dafür, dass es sich für dich lohnt." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Abgemacht ist abgemacht.", + QUEST : "Du brauchst also Lach-Punkte, was?\aKlar hab ich'n Deal für dich!\aKümmere dich einfach für mich um ein paar Rechtomaten-Bots ...\aUnd ich sorge dafür, dass es sich für dich lohnt." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Okay, aber ich bin mir sicher, dass ich dir gesagt hatte, du sollst ein paar Rechtomaten-Bots zusammentreiben.\aTja, wenn du das sagst, aber du hast dich nicht dran gehalten. ", + INCOMPLETE_PROGRESS : "Ich glaube nicht, dass du fertig bist.", + QUEST : "Du sagst, du bist fertig? Alle Bots vertrieben?\aDu hast da was falsch verstanden. Unsere Abmachung galt für Schachermaten-Bots.\aIch bin sicher, dass ich gesagt habe, du sollst ein paar Schachermaten-Bots für mich vertreiben." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Okay, aber ich bin mir sicher, dass ich dir gesagt hatte, du sollst ein paar Rechtomaten-Bots zusammentreiben.\aTja, wenn du das sagst, aber du hast dich nicht dran gehalten.", + INCOMPLETE_PROGRESS : "Ich glaube nicht, dass du fertig bist.", + QUEST : "Du sagst, du bist fertig? Alle Bots vertrieben?\aDu hast da was falsch verstanden. Unsere Abmachung galt für Monetomaten-Bots.\aIch bin sicher, dass ich gesagt habe, du sollst ein paar Monetomaten-Bots für mich vertreiben." }, + } + +# ChatGarbler.py +ChatGarblerDog = ["wuff", "blaff", "rrr-wuff"] +ChatGarblerCat = ["miau", "muh"] +ChatGarblerMouse = ["quiek", "pieps", "quiekquiek"] +ChatGarblerHorse = ["wieher", "brrr"] +ChatGarblerRabbit = ["iiik", "schnuff", "schnuffschnuff", "trommel"] +ChatGarblerDuck = ["quak", "schnatter", "quakquak"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerDefault = ["blah"] + +# AvatarDNA.py +Bossbot = "Chefomat" +Lawbot = "Rechtomat" +Cashbot = "Monetomat" +Sellbot = "Schachermat" +BossbotS = "ein Chefomat" +LawbotS = "ein Rechtomat" +CashbotS = "ein Monetomat" +SellbotS = "ein Schachermat" +BossbotP = "Chefomaten" +LawbotP = "Rechtomaten" +CashbotP = "Monetomaten" +SellbotP = "Schachermaten" +BossbotSkelS = "ein Chefomat Skeletobot" +LawbotSkelS = "ein Rechtomat Skeletobot" +CashbotSkelS = "ein Monetomat Skeletobot" +SellbotSkelS = "ein Schachermat Skeletobot" +BossbotSkelP = "Chefomat Skeletobots" +LawbotSkelP = "Rechtomat Skeletobots" +CashbotSkelP = "Monetomat Skeletobots" +SellbotSkelP = "Schachermat Skeletobots" + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Details zu %s werden gesucht." +AvatarDetailPanelFailedLookup = "Kann keine Deteils zu %s finden." +AvatarDetailPanelOnline = "Bezirk: %(district)s\nLocation: %(location)s" +AvatarDetailPanelOffline = "Bezirk: offline\nOrt: offline" + +# AvatarPanel.py +AvatarPanelFriends = "Freunde" +AvatarPanelWhisper = "Flüstern" +AvatarPanelSecrets = "Geheimnisse" +AvatarPanelGoTo = "Gehe zu" +AvatarPanelPet = "Doodle zeigen" +AvatarPanelIgnore = "Ignorieren" +#AvatarPanelCogDetail = "Dept: %s\nLevel: %s\n" +AvatarPanelCogLevel = "Level: %s" +AvatarPanelCogDetailClose = lClose + +# PetAvatarPanel.py +PetPanelFeed = "Füttern" +PetPanelCall = "Rufen" +PetPanelGoTo = "Gehe zu" +PetPanelOwner = "Besitzer zeigen" +PetPanelScratch = "Kraulen" + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutral', + 'hunger': 'hungrig', + 'boredom': 'gelangweilt', + 'excitement': 'aufgeregt', + 'sadness': 'traurig', + 'restlessness': 'unruhig', + 'playfulness': 'verspielt', + 'loneliness': 'einsam', + 'fatigue': 'müde', + 'confusion': 'verwirrt', + 'anger': 'ärgerlich', + 'surprise': 'überrascht', + 'affection': 'zärtlich', + } + +# LocalAvatar.py +FriendsListLabel = "Freunde" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Versuche zu %s zu gehen." +TeleportPanelNotAvailable = "%s ist gerade beschäftigt, versuche es später noch einmal." +TeleportPanelIgnored = "%s ignoriert dich." +TeleportPanelNotOnline = "%s ist zur Zeit nicht online." +TeleportPanelWentAway = "%s ist weggegangen." +TeleportPanelUnknownHood = "Du weißt nicht, wie du zu %s kommst! " +TeleportPanelUnavailableHood = "%s ist zur Zeit nicht erreichbar, versuche es später noch einmal." +TeleportPanelDenySelf = "Du kannst nicht zu dir selbst gehen!" +TeleportPanelOtherShard = "%(avName)s ist im Bezirk %(shardName)s, und du bist im Bezirk %(myShardName)s. Möchtest du nach %(shardName)s wechseln?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Ich bin der Chef." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Ich bin der Vorarbeiter." +FactoryBossBattleTaunt = "Ich möchte dich mit dem Vorarbeiter bekannt machen." + +# HealJokes.py +ToonHealJokes = [ + ["Was hängt an der Wand und macht TICK-TOCK-TICK-TOCK-TICK-TOCK ...?", + "Eine Spinne mit Stöckelschuhen. "], + ["Was steht im Wald und macht Muh?", + "Ein Hirsch, der Fremdspachen lernt."], + ["Warum kann ein Gespenst schlecht lügen?", + "Weil man es so gut durchschauen kann."], + ["Wie kommt eine Ameise über den Fluss?", + "Sie nimmt das A weg und fliegt rüber."], + ["Welcher Vogel ist meistens traurig?", + "Der Pechvogel."], + ["Wo schmeckt der Apfelsaft am besten?", + "Beim Trinken."], + ["Was hat ein Mensch noch nie erzählt?", + "Dass er gestorben ist."], + ["Warum leben Eskimos länger?", + "Weil sie nicht ins Gras beißen können."], + ["Was spielen sportliche Schafe?", + "Wolle-Ball."], + ["Welche Biere schäumen am meisten?", + "Die Bar-Biere."], + ["Warum ging das Gerippe nicht über die Straße?", + "Weil es nicht den Nerv dazu hatte."], + ["Wie heißt das Reh mit Vornamen?", + "Kartoffelpü."], + ["Was ist grau und spritzt mit Marmelade?", + "Eine Maus, die einen Pfannkuchen frisst."], + ["In welchem Fall ist zwei mal zwei sechs?", + "In gar keinem Fall."], + ["Was kann man von einem Dreieck verwenden?", + "Das Ei, der Rest ist Dreck!"], + ["Wer hat es besser, der Kaffee oder der Tee?", + "Der Kaffee; er darf sich setzen, der Tee muss ziehen."], + ["Wie konnten die Leute im Mittelalter ohne Fernseher überleben?", + "Gar nicht, sie leben ja nicht mehr."], + ["Was passiert, wenn man einen weißen Hut ins Rote Meer taucht?", + "Er wird nass."], + ["Welche Zeiten sind die schönsten?", + "Die Mahlzeiten."], + ["Welches Spiel soll man immer anderen geben?", + "Das Beispiel."], + ["Was kann man nicht zum Frühstück essen?", + "Mittag und Abendbrot."], + ["Was gibt man einem Elefanten mit großen Füßen?", + "Große Schuhe."], + ["Was macht man, wenn man tiefer schlafen will?", + "Man sägt die Beine vom Bett ab."], + ["Welcher Satz hat keine Wörter?", + "Der Kaffeesatz."], + ["Was ergibt drei mal sieben?", + "Feinen Sand."], + ["Welchen Hang sollte man nicht hochsteigen?", + "Den Vorhang."], + ["Warum sind die Busse in Ostfriesland 10 Meter breit und 2 Meter lang?", + "Weil alle vorn sitzen wollen."], + ["Welche Hunde treten zu Weltmeisterschaften an?", + "Boxer."], + ["Was sagt man zu einem Gorilla mit Ohrenschützern?", + "Alles, was man will, er kann es sowieso nicht hören."], + ["Was mögen Katzen und Schwimmer?", + "Das Kraulen."], + ["Was reist um die ganze Welt und bleibt doch in der Ecke?", + "Eine Briefmarke."], + ["Was steht in der Mitte vom Stadion?", + "Das d."], + ["Wie lieben sich Igel?", + "Gaaanz vorsichtig!"], + ["Was ist schlimmer als ein tollwütiger Fuchs?", + "Zwei tollwütige Füchse."], + ["Was hängt mit verbranntem Hintern an der Wand?", + "Die Bratpfanne."], + ["Bei welchen Fischen stehen die Augen am engsten zusammen?", + "Bei den kleinen."], + ["Was steht mitten im Rhein und tutet?", + "Ein Elefant im Urlaub."], + ["In welche Richtung geht der Rauch einer E-Lok, die mit 100 kmh nach Westen fährt?", + "Eine E-Lok macht gar keinen Rauch!"], + ["Warum schauen die Schotten über den Rand ihrer Brille?", + "Damit sich die Gläser nicht so schnell abnutzen."], + ["Was ist am tiefsten, Bach Oder Tümpel?", + "Die Oder."], + ["Was passiert mit einem Engel, wenn er in einen Misthaufen fällt?", + "Er bekommt Kotflügel."], + ["Was hindert einen Reiter daran, auf dem Pferd zu sitzen?", + "Der Sattel."], + ["Wie sieht ein Anhalter aus, der Glück hatte?", + "Mitgenommen."], + ["Wie kriegt man einen Löwen in den Kühlschrank?", + "Tür auf, Löwe rein, Tür zu."], + ["Warum sieht man keine Elefanten auf Kirschbäumen?", + "Weil sie sich gut getarnt haben."], + ["Was kommt heraus, wenn man eine Katze mit einem Hund kreuzt?", + "Ein Tier, das sich selbst jagt."], + ["Worunter leiden Luftballons?", + "Platzangst."], + ["Woran merkt man, dass Ebbe ist?", + "Wenn es beim Rudern staubt."], + ["Warum ist Rätselraten gefährlich?", + "Weil man sich den Kopf zerbricht."], + ["Was haben nur Giraffen und andere Tiere nicht?", + "Giraffenbabys."], + ["Warum schlug der Mann die Uhr?", + "Weil die Uhr zuerst schlug."], + ["Welcher Peter macht den meisten Krach?", + "Der Trompeter."], + ["Was kommt heraus, wenn man einen Papagei mit einem Monster kreuzt?", + "Ein Wesen, das immer einen Keks bekommt, wenn es einen verlangt."], + ["Was sieht aus wie eine Katze, ist aber keine?", + "Ein Kater."], + ["Wann sagt ein Chinese Guten Morgen?", + "Wenn er Deutsch gelernt hat."], + ["Wo geht nachts das Licht hin?", + "Schau mal im Kühlschrank nach!"], + ["Wie hoch ist der höchste Berg?", + "Höher als alle anderen."], + ["Was ist das Ende von Allem?", + "Der Buchstabe M."], + ["Was liegt zwischen Berg und Tal?", + "Und."], + ["Womit würfeln die Eskimos?", + "Mit Eiswürfeln."], + ["Wohin fliegt die Wolke, wenn es sie juckt?", + "Zum Wolkenkratzer."], + ["Was hat sechs Augen und kann doch nicht sehen?", + "Drei blinde Mäuse."], + ["Was arbeitet nur, wenn es gefeuert wird?", + "Eine Rakete."], + ["Was macht der Wurm im Salat?", + "Er schmeißt die Schnecken raus."], + ["Was ist weiß und hüpft von Baum zu Baum?", + "Tarzan im Nachthemd."], + ["Was kommt heraus, wenn man einen Pinguin mit einem Tausendfüßler kreuzt?", + "Tausend kalte Füße."], + ["Wie viele Erbsen gehen in einen Topf?", + "Erbsen können gar nicht gehen!"], + ["Was ist beim Elefanten klein und beim Floh groß?", + "Das F."], + ["Warum haben Dinosaurier so einen langen Hals?", + "Weil ihre Füße riechen."], + ["Warum können Nilpferde nicht Fahrrad fahren?", + "Weil sie keinen Daumen zum Klingeln haben."], + ["Warum vergessen Elefanten nie etwas?", + "Weil ihnen nie jemand etwas erzählt."], + ["Woher wissen wir, dass die Erde rund ist?", + "Weil wir uns die Sohlen schief laufen."], + ["Warum hat der Elefant rote Augen?", + "Damit er sich besser im Kirschbaum verstecken kann."], + ["Warum hat der Schwan so einen langen Hals?", + "Damit er bei Hochwasser nicht ertrinkt."], + ["Kennst du den Sekundenwitz?", + "Schon vorbei."], + ["Was ist das älteste Instrument?", + "Das Akkordeon, es hat die meisten Falten."], + ["Wie verhindert man, dass ein Elefant durch ein Nadelöhr geht?", + "Man macht einen Knoten in seinen Schwanz."], + ["Was macht Ha-ha-ha-peng?", + "Jemand, der sich kaputt lacht."], + ["Warum legen Hühner Eier?", + "Wenn sie sie werfen würden, würden sie kaputt gehen."], + ["Warum sind die Fußstapfen vom Elefanten so groß?", + "Weil sonst seine Füße nicht reinpassen!"], + ["Was essen Elektriker zu Mittag?", + "Kabelsalat."], + ["Warum zieht der Wellensittich beim Schlafen ein Bein hoch?", + "Wenn er beide hochzieht, fällt er von der Stange."], + ["Woran erkennt man, dass ein Elefant im Kühlschrank war?", + "Am Fußabdruck in der Butter."], + ["Was ist der Unterschied zwischen einem Bäcker und einem Teppich?", + "Der Bäcker muss um 3 aufstehen, der Teppich kann liegen bleiben."], + ["Welchen Satz hört der Hai am liebsten?", + "Mann über Bord!"], + ["Was ist grau, wiegt 10 Pfund und quiekt?", + "Eine Maus, die dringend mal Diät halten muss."], + ["Welchen Vogel kann man mit zwei Buchstaben schreiben?", + "NT"], + ["Wie erpresst ein Hase einen Schneemann?", + "Möhre her, sonst Fön!"], + ["Warum sind Elefanten groß und grau?", + "Wenn sie klein und gelb wären, wären sie Kanarienvögel."], + ["Wann ist es gefährlich, in den Garten zu gehen?", + "Wenn der Salat schießt."], + ["Welche Tomaten kann man nicht essen?", + "Die Automaten."], + ["Warum hat "+ Donald + " Zucker auf sein Kopfkissen gestreut?", + "Er wollte süße Träume haben."], + ["Warum brachte "+ Goofy + " seinen Kamm zum Zahnarzt?", + "Weil er alle Zähne verloren hatte."], + ["Warum ließ "+ Goofy + " das Gartentor offen?", + "Damit die Blumen frische Luft kriegen."], + ["Wie heißt die Frau vom Papagei?", + "Mamagei."], + ["Was hat Flügel, läuft aber lieber?", + "Die Nase."], + ["Warum duschte der Einbrecher?", + "Um einen sauberen Abgang zu haben."], + ["Was brennt Tag und Nacht?", + "Die Brennnessel."], + ["Was ist, wenn ein Schornsteinfeger in den Schnee fällt?", + "Winter."], + ["Warum ging "+ Pluto + " mit einem Stein und Streichhölzern ins Bett?", + "Mit dem Stein schmiss er das Licht aus, dann schaute er nach, ob es aus ist."], + ["Was macht man, wenn man in der Wüste eine Schlange sieht?", + "Man stellt sich hinten an."], + ["Warum sind falsche Zähne wie Sterne?", + "Beide kommen nachts heraus."], + ["Was kann in der Hosentasche sein, selbst wenn nichts drin ist?", + "Ein Loch."], + ["Welches Tier ist das lustigste?", + "Das Pferd, es veräppelt die Straße."], + ["Welcher Bus überquerte als erster den Atlantik?", + "Kolumbus."], + ["Was ist schwarz und dreht sich immer im Kreis?", + "Ein Maulwurf beim Hammerwerfen."], + ["Was ist schwarz und hüpft auf einem Bein?", + "Ein Maulwurf, dem beim Hammerwerfen der Hammer auf den Fuß fiel."], + ["Warum sollte man nachts nicht in den Urwald gehen?", + "Weil dann die Elefanten Fallschirmspringen üben."], + ["Was ist der Unterschied zwischen einem Wasserfall?", + "Je höher desto plumps."], + ["Warum nahm "+ Goofy + " eine Semmel mit aufs Klo?", + "Um die WC-Ente zu füttern."], + ["Warum trinken Katzen so gerne?", + "Damit sie einen Kater kriegen."], + ["Was sagte der große Schornstein zum kleinen Schornstein?", + "Du bist doch zum Rauchen noch zu klein!"], + ["Was ist schwarz-weiß gestreift und wird rot?", + "Ein Zebra, das sich schämt."], + ["Was ist der Unterschied zwischen einem Knochen und Schule?", + "Der Knochen ist für den Hund und Schule für die Katz."], + ["Was macht mmus-mmus?", + "Eine Fliege, die rückwärts fliegt."], + ["Was macht der Glaser, wenn er kein Glas mehr hat?", + "Er trinkt aus der Flasche."], + ["Was ist ein Anto?", + "Ein Druckfehler, es sollte Auto heißen."], + ["Was kommt heraus, wenn man einen Bären und ein Stinktier kreuzt?", + "Puh der Bär."], + ["Wie macht man eine Tuba sauber?", + "Mit Tuben-Zahnpasta."], + ["Was war am 6. Dezember 1924?", + "Nikolaus."], + ["Was ist klein, grün und dreieckig?", + "Ein kleines grünes Dreieck."], + ["Es hängt an der Wand und wenn's runterfällt, geht die Tür auf. Was ist das?", + "Zufall."], + ["Warum heißen Teigwaren Teigwaren?", + "Weil sie einmal Teig waren."], + ["Wie sagt man 'Postbote' ohne o?", + "Briefträger."], + ["Was kommt heraus, wenn man eine Kamera mit einem Krokodil kreuzt?", + "Ein Schnappschuss."], + ["Was ist braun und rennt durch den Wald?", + "Ein ferngesteuertes Rennschnitzel."], + ["Wo fängt ein Kreis an?", + "Beim K."], + ["Was kommt heraus, wenn man einen Elefanten mit einer Krähe kreuzt?", + "Massenhaft umgeknickte Telefonmasten."], + ["Schon mal einen Kühlschrank durch den Wald rennen sehen?", + "Nein? Siehste, so schnell sind die!"], + ["Was ist eine Karotte?", + "Eine Kartoffel mit zu hohem Blutdruck."], + ["Was ist weiß und hat sechs Ecken?", + "Ist doch einfach! Ein Pingpongklötzchen."], + ["Was ist grün, laut und gefährlich?", + "Eine herandonnernde Herde Gurken."], + ["Was ist, wenn sich zwei Jäger treffen?", + "Dann sind beide tot."], + ["Ein Elefant sitzt auf einem Ahornblatt. Wie kommt er wieder runter?", + "Er wartet bis im Herbst sein Blatt abfällt."], + ["Was ist schlimmer dran als eine Giraffe mit Halsweh?", + "Ein Tausendfüßler mit Fußpilz."], + ["Was macht ABC...schlürf...DEF...schlürf?", + "Jemand, der Buchstabensuppe isst."], + ["Wann hoppelt ein Hase über einen Baum?", + "Wenn der Baum gefällt ist."], + ["Was ist ein eisernes Abführmittel?", + "Handschellen."], + ["Was ist eine gesellige Hülsenfrucht?", + "Eine Kontaktlinse."], + ["Was ist weiß mit schwarzen und roten Punkten?", + "Ein Dalmatiner, der die Masern hat."], + ["Was sind Früchte des Zorns?", + "Ohrfeigen."], + ["Was macht ein Kaugummi, wenn er um die Ecke geht?", + "Er bleibt kleben."], + ["Was ist grau, wiegt 200 Pfund und sagt: Na, Miez-Miez-Miez?", + "Eine 200-Pfund-Maus. "], + ["Wie fängt man am besten ein Reh?", + "Man schleicht sich an und streut ihm Salz auf den Schwanz."], + ["Wie fängt man am besten ein Kaninchen?", + "Man hockt sich hinter einen Busch und macht ein Geräusch wie ein Salatblatt. "], + ["Wo kommt der Juli vor dem Juni?", + "Im Duden."], + ["Welches Tier dreht sich nach seinem Tod noch 150 Mal?", + "Das Grillhähnchen."], + ["Was hat ein Fell, miaut und jagt Mäuse unter Wasser?", + "Ein Katzenfisch."], + ["Monikas Vater hat fünf Töchter. Sie heißen Lala, Lele, Lili und Lulu. Wie heißt die fünfte?", + "Na, Monika!"], + ["Was ist außen grün und innen gelb?", + "Eine als Gurke verkleidete Banane."], + ["Was macht ein Ostfriese, der mit einem Messer auf dem Deich steht?", + "Er sticht in See."], + ["Was wiegt 4 Tonnen, hat einen Rüssel und ist leuchtend rot?", + "Ein Elefant, dem etwas peinlich ist."], + ["Was ist, wenn ein Schwein ums Eck geht?", + "Es ist weg."], + ["Was wird eine Tomate, wenn sie auf die Straße geht?", + "Ketchup."], + ["Was ist ein Cowboy, der sein Pferd verloren hat?", + "Ein Sattelschlepper."], + ["Was sagt die Erdnuss zum Elefanten?", + "Gar nichts. Erdnüsse können nicht reden."], + ["Was sagen Elefanten, wenn sie aufeinanderprallen?", + "Die Welt ist klein, was?"], + ["Was ist groß, grau, und telefoniert aus Afrika?", + "Ein Telefant."], + ["Was sagt der eine Floh zum anderen?", + "Sollen wir laufen oder eine Katze nehmen?"], + ["Was ist grün, glücklich, und hüpft von Grashalm zu Grashalm?", + "Eine Freuschrecke."], + ["Was ist schlimmer, als ein Wurm in dem Apfel zu finden, in den man gerade gebissen hat?", + "Einen halben Wurm zu finden."], + ["Was fängt mit Z an und kann schwimmen?", + "Zwei Enten."], + ["Was kommt heraus, wenn man eine Brieftaube mit einem Papagei kreuzt?", + "Ein Vogel, der beim Fliegen nach dem Weg fragen kann."], + ["Was ist der Unterschied zwischen einem Briefkasten und einem Elefanten?", + "Schon mal versucht, einen Brief in einen Elefanten einzuwerfen?"], + ["Was ist der Unterschied zwischen einem Sack Mehl und einem Saxofon?", + "Blas mal rein!"], + ["Was machst du, damit dein Computer nicht abstürzt?", + "Ihn auf den Boden stellen."], + ["Warum laufen Dudelsackspieler beim Spielen hin und her?", + "Sie versuchen, den Tönen zu entkommen."], + ["Warum tragen Elefanten Turnschuhe?", + "Damit es nicht so plumpst, wenn sie aus den Bäumen springen."], + ["Warum ist Ostfriesland so flach?", + "Damit die Ostfriesen schon am Mittwoch sehen, wer Sonntag zu Besuch kommt."], + ["Warum flog Aschenputtel aus dem Basketballteam?", + "Es lief immer vom Ball weg."], + ["Warum heißt der Löwe Löwe?", + "Weil er durch die Wüste löwt."], + ["Warum heißt die Haut Haut?", + "Weil man drauf haut."], + ["Warum macht der Hahn beim Krähen die Augen zu? ", + "Er will die Hühner damit beeindrucken, dass er es auswendig kann."], + ["Was sagt ein Papst zum anderen?", + "Es gibt immer nur einen Papst ..."], + ["Was sagt ein Magnet zum anderen?", + "Ich finde dich echt anziehend ..."], + ["Wie alt kann man in Japan werden?", + "Mitsubishi Glück wird man Honda."], + ["Was sagt ein Japaner zu seiner Frau, nachdem sie einen Sohn geboren hat?", + "Its a Sony."], + ["Warum ist "+ MickeyMouse + " ins Weltall geflogen?", + "Er wollte "+ Pluto + "finden."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","he","ha","Höhö") +MovieHealLaughterHits1= ("Hahaha","Hihi","Kicher","Ha Ha") +MovieHealLaughterHits2= ("MUA-HA-HA!","HO HO HO!","HA HA HA!") + +# MovieSOS.py +MovieSOSCallHelp = "%s HILFE!" +MovieSOSWhisperHelp = "%s braucht Hilfe im Kampf!" +MovieSOSObserverHelp = "HILFE!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Hi %s! Ich helfe gern!" +MovieNPCSOSGoodbye = "Bis später!" +MovieNPCSOSToonsHit = "Toons Treffen Immer!" +MovieNPCSOSCogsMiss = "Bots Hauen Immer Daneben!" +MovieNPCSOSRestockGags = "%s Gags werden aufgefüllt!" +MovieNPCSOSHeal = "Heilen" +MovieNPCSOSTrap = "Fallen stellen" +MovieNPCSOSLure = "Ködern" +MovieNPCSOSSound = "Volldröhnen" +MovieNPCSOSThrow = "Werfen" +MovieNPCSOSSquirt = "Spritzen" +MovieNPCSOSDrop = "Fallen lassen" +MovieNPCSOSAll = "Alle" + +# MovieSuitAttacks.py +MovieSuitCancelled = "ABGEBROCHEN\nABGEBROCHEN\nABGEBROCHEN" + +# RewardPanel.py +RewardPanelToonTasks = "Toon-Aufgaben" +RewardPanelItems = "Zurückeroberte Gegenstände" +RewardPanelMissedItems = "Nicht zurückeroberte Gegenstände" +RewardPanelQuestLabel = "Aufgabe %s" +RewardPanelCongratsStrings = ["Yeah!", "Glückwunsch!", "Wow!", + "Cool!", "Toll!", "Toon-tastisch!"] +RewardPanelNewGag = "Neuer %(gagName)s Gag für %(avName)s!" +RewardPanelMeritsMaxed = "Maximal" +RewardPanelMeritBarLabel = "Verdienste" +RewardPanelMeritAlert = "Beförderungsfähig!" + +RewardPanelCogPart = "Du hast ein Teil eines Bot-Kostüms erhalten!" +RewardPanelPromotion = "Im Ablauf %s, Beförderungsfähig!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Normaler Toon", "du wirst normal"), + ("Großer Kopf", "du bekommst einen großen Kopf"), + ("Kleiner Kopf", "du bekommst einen kleinen Kopf"), + ("Große Beine", "du bekommst große Beine"), + ("Kleine Beine", "du bekommst kleine Beine"), + ("Großer Toon", "du wirst etwas größer"), + ("Kleiner Toon ", "du wirst etwas kleiner"), + ("Flachporträt", "du wirst zweidimensional"), + ("Flachprofil", "du wirst zweidimensional"), + ("Durchsichtig", "du wirst durchsichtig"), + ("Keine Farbe", "du wirst farblos"), + ("Unsichtbarer Toon", "du wirst unsichtbar"), + ] +CheesyEffectIndefinite = "Bis du einen anderen Effekt wählst, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Die nächsten %(time)s Minuten, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Die nächsten %(time)s Stunden, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Dieie nächsten %(time)s Tage, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " während du in %s bist" +CheesyEffectExceptIn = ", außer in %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Kriecher" +SuitPencilPusher = "Griffel\3schieber" +SuitYesman = "Jasager" +SuitMicromanager = "Mikro\3manager" +SuitDownsizer = "Niedermacher" +SuitHeadHunter = "Köpfchen\3jäger" +SuitCorporateRaider = "Unternehmens\3räuber" +SuitTheBigCheese = "Großhirn" +SuitColdCaller = "Aufschwatzer" +SuitTelemarketer = "Tele\3marketer" +SuitNameDropper = "Wichtig\3tuer" +SuitGladHander = "Glücks\3händchen" +SuitMoverShaker = "Aufbauscher" +SuitTwoFace = "Falsch\3gesicht" +SuitTheMingler = "Einmischer" +SuitMrHollywood = "Mr. Hollywood" +SuitShortChange = "Keinmünz" +SuitPennyPincher = "Pfennig\3fuchser" +SuitTightwad = "Geizhals" +SuitBeanCounter = "Kieszähler" +SuitNumberCruncher = "Erbsen\3zähler" +SuitMoneyBags = "Geldsack" +SuitLoanShark = "Kredithai" +SuitRobberBaron = "Ausbeuter" +SuitBottomFeeder = "Schnäppchen\3jäger" +SuitBloodsucker = "Blut\3sauger" +SuitDoubleTalker = "Dumm\3schwätzer" +SuitAmbulanceChaser = "Unfall\3abzocker" +SuitBackStabber = "Heimtücker" +SuitSpinDoctor = "Schönredner" +SuitLegalEagle = "Prozessgeier" +SuitBigWig = "Großkotz" + +# Singular versions (indefinite article) +SuitFlunkyS = "einen Kriecher" +SuitPencilPusherS = "einen Griffelschieber" +SuitYesmanS = "einen Jasager" +SuitMicromanagerS = "einen Mikromanager" +SuitDownsizerS = "einen Niedermacher" +SuitHeadHunterS = "einen Köpfchenjäger" +SuitCorporateRaiderS = "einen Unternehmensräuber" +SuitTheBigCheeseS = "einen Großhirn" +SuitColdCallerS = "einen Aufschwatzer" +SuitTelemarketerS = "einen Telemarketer" +SuitNameDropperS = "einen Wichtigtuer" +SuitGladHanderS = "eine Glückshändchen" +SuitMoverShakerS = "einen Aufbauscher" +SuitTwoFaceS = "einem Falschgesicht" +SuitTheMinglerS = "einen Einmischer" +SuitMrHollywoodS = "einen Mr. Hollywood" +SuitShortChangeS = "eine Keinmünz" +SuitPennyPincherS = "einen Pfennigfuchser" +SuitTightwadS = "einen Geizhals" +SuitBeanCounterS = "einen Kieszähler" +SuitNumberCruncherS = "einen Erbsenzähler" +SuitMoneyBagsS = "einen Geldsack" +SuitLoanSharkS = "einen Kredithai" +SuitRobberBaronS = "einen Ausbeuter" +SuitBottomFeederS = "einen Schnäppchenjäger" +SuitBloodsuckerS = "einen Blutsauger" +SuitDoubleTalkerS = "einen Dummschwätzer" +SuitAmbulanceChaserS = "einen Unfallabzocker" +SuitBackStabberS = "einem Heimtücker" +SuitSpinDoctorS = "einen Schönredner" +SuitLegalEagleS = "einen Prozessgeier" +SuitBigWigS = "einem Großkotz" + +# Plural versions +SuitFlunkyP = "Kriecher" +SuitPencilPusherP = "Griffelschieber" +SuitYesmanP = "Jasager" +SuitMicromanagerP = "Mikromanager" +SuitDownsizerP = "Niedermacher" +SuitHeadHunterP = "Köpfchenjäger" +SuitCorporateRaiderP = "Unternehmensräuber" +SuitTheBigCheeseP = "Großhirne" +SuitColdCallerP = "Aufschwatzer" +SuitTelemarketerP = "Telemarketer" +SuitNameDropperP = "Wichtigtuer" +SuitGladHanderP = "Glückshändchen" +SuitMoverShakerP = "Aufbauscher" +SuitTwoFaceP = "Falschgesichter" +SuitTheMinglerP = "Einmischer" +SuitMrHollywoodP = "Mr. Hollywoods" +SuitShortChangeP = "Keinmünze" +SuitPennyPincherP = "Pfennigfuchser" +SuitTightwadP = "Geizhälse" +SuitBeanCounterP = "Kieszähler" +SuitNumberCruncherP = "Erbsenzähler" +SuitMoneyBagsP = "Geldsack" +SuitLoanSharkP = "Kredithaie" +SuitRobberBaronP = "Ausbeuter" +SuitBottomFeederP = "Schnäppchenjäger" +SuitBloodsuckerP = "Blutsauger" +SuitDoubleTalkerP = "Dummschwätzer" +SuitAmbulanceChaserP = "Unfallabzocker" +SuitBackStabberP = "Heimtücker" +SuitSpinDoctorP = "Schönredner" +SuitLegalEagleP = "Prozessgeier" +SuitBigWigP = "Großkotze" + +SuitFaceOffDefaultTaunts = ['Huh!'] + +SuitAttackDefaultTaunts = ['Da hast du\'s!', 'Aufschreiben!'] + +SuitAttackNames = { + 'Audit' : 'Buchprüfung!', + 'Bite' : 'Beißen!', + 'BounceCheck' : 'Geplatzter Scheck', + 'BrainStorm' : 'Brainstorm!', + 'BuzzWord' : 'Schlagwort!', + 'Calculate' : 'Kalkulieren!', + 'Canned' : 'Rausgeschmissen!', + 'Chomp' : 'Mampfen!', + 'CigarSmoke' : 'Zigarrenrauch!', + 'ClipOnTie' : 'Ansteckkrawatte!', + 'Crunch' : 'Zermalmen!', + 'Demotion' : 'Zurückversetzung!', + 'Downsize' : 'Niedermachen!', + 'DoubleTalk' : 'Dummschwätzen!', + 'EvictionNotice' : 'Zwangsräumung!', + 'EvilEye' : 'Böser Blick!', + 'Filibuster' : 'Verschleppungstaktik!', + 'FillWithLead' : 'Mit Blei füllen!', + 'FiveOClockShadow' : "Augenringe!", + 'FingerWag' : 'Tz-Tz!', + 'Fired' : 'Gefeuert!', + 'FloodTheMarket' : 'Markt überfluten!', + 'FountainPen' : 'Füllfederhalter!', + 'FreezeAssets' : 'Vermögen einfrieren!', + 'Gavel' : 'Hammer!', + 'GlowerPower' : 'Finsterblick!', + 'GuiltTrip' : 'Bußgang!', + 'HalfWindsor' : 'Einfacher Windsor!', + 'HangUp' : 'Auflegen!', + 'HeadShrink' : 'Irrenarzt!', + 'HotAir' : 'Heiße Luft!', + 'Jargon' : 'Fachchinesisch!', + 'Legalese' : 'Juristenjargon!', + 'Liquidate' : 'Liquidieren!', + 'MarketCrash' : 'Börsenkrach!', + 'MumboJumbo' : 'Fauler Zauber!', + 'ParadigmShift' : 'Paradigmenwechsel!', + 'PeckingOrder' : 'Hackordnung!', + 'PickPocket' : 'Taschendieb!', + 'PinkSlip' : 'Blauer Brief!', + 'PlayHardball' : 'Hart durchgreifen!', + 'PoundKey' : 'Tasten drücken!', + 'PowerTie' : 'Kräfte messen!', + 'PowerTrip' : 'Ego-Trip!', + 'Quake' : 'Beben!', + 'RazzleDazzle' : 'Tamtam!', + 'RedTape' : 'Bürokratie!', + 'ReOrg' : 'Re-Org!', + 'RestrainingOrder' : 'Einstweilige Verfügung!', + 'Rolodex' : 'Rolodex!', + 'RubberStamp' : 'Abstempeln!', + 'RubOut' : 'Ausradieren!', + 'Sacked' : 'Entlassen!', + 'SandTrap' : 'Bunker!', + 'Schmooze' : 'Schmus!', + 'Shake' : 'Schütteln!', + 'Shred' : 'Shreddern!', + 'SongAndDance' : 'Getue!', + 'Spin' : 'Schönreden!', + 'Synergy' : 'Synergie!', + 'Tabulate' : 'Tabellarisieren!', + 'TeeOff' : 'Loslegen!', + 'ThrowBook' : 'Verurteilen!', + 'Tremor' : 'Schaudern!', + 'Watercooler' : 'Wasserkühler!', + 'Withdrawal' : 'Abhebung!', + 'WriteOff' : 'Abschreiben!', + } + +SuitAttackTaunts = { + 'Audit': ["Ich glaube, deine Bilanz stimmt nicht.", + "Du bist wohl in den roten Zahlen.", + "Lass mich mal bei der Buchhaltung helfen.", + "Deine Sollseite ist viel zu hoch.", + "Wir wollen mal dein Vermögen prüfen.", + "Damit hast du jetzt Schulden.", + "Sehen wir uns mal an, wie hoch die Forderungen sind.", + "Damit dürfte dein Konto leer sein.", + "Höchste Zeit, mal deine Ausgaben abzurechnen.", + "Ich habe einen Fehler in deinen Büchern gefunden.", + ], + 'Bite': ["Möchtest du einen Bissen?", + "Versuch mal einen Bissen hiervon!", + "Du beißt mehr ab, als du schlucken kannst.", + "Hunde die beißen, bellen nicht.", + "Da wirst du dir die Zähne dran ausbeißen.", + "Achtung, ich beiße!", + "Ich beiße nicht nur, wenn man mich in die Enge treibt.", + "Ich hol mir nur schnell einen Bissen. ", + "Ich habe den ganzen Tag noch keinen Bissen gegessen.", + "Ich möchte nur ein Bissen essen. Ist das zuviel verlangt?", + ], + 'BounceCheck': ["Ach, zu blöd, du bist witzlos.", + "Von dir steht noch eine Zahlung aus.", + "Ich glaube, das ist dein Scheck.", + "Du schuldest mir was.", + "Ich treibe diese Forderung ein.", + "Dieser Scheck ist kein gültiges Zahlungsmittel.", + "Man wird dir das in Rechnung stellen", + "Bezahle das.", + "Das wird dich was kosten.", + "Ich möchte das in bar kassieren.", + "Das fällt dir wieder auf die Füße.", + "Das ist ein falscher Fuffziger.", + "Ich ziehe eine Bearbeitungsgebühr ab.", + ], + 'BrainStorm':["Ich habe Regen vorhergesagt.", + "Hoffentlich hast du deinen Schirm dabei.", + "Ich möchte dich erleuchten.", + "Vielleicht fällt ja was vom Himmel?", + "Na, du strahlst ja gar nicht mehr, Toon?", + "Fertig für einen ordentlichen Guss?", + "Ich werde dich im Sturm nehmen.", + "Das nenn ich eine Blitzattacke.", + "Ich verteile gern kalte Duschen.", + ], + 'BuzzWord':["Entschuldigung, wenn ich weiter auf dich einhämmere.", + "Hast du das Neueste schon gehört?", + "Kannst du da mithalten?", + "Kannst du es schon auswendig, Toon?", + "Soll ich ein gutes Wort für dich einlegen?", + "Jeder Schlag ein Treffer.", + "Du solltest schlagende Argumente sammeln.", + "Versuch mal, diesem Schlag auszuweichen.", + "Pass auf, sonst kriegst du eins auf's Dach.", + "Du hast anscheinend einen Schlaganfall.", + ], + 'Calculate': ["Diese Zahlen ergeben doch einen Sinn!", + "Hast du darauf gezählt?", + "Wenn wir alles zusammenzählen, geht's abwärts mit dir.", + "Ich helfe dir mal, eins und eins zusammenzuzählen.", + "Hast du alle Ausgaben eingetragen?", + "Nach meiner Kalkulation wirst du dich nicht mehr lange halten können.", + "Hier ist das Endergebnis", + "Wow, deine Rechnung wird immer höher.", + "Versuch bloß nicht, diese Zahlen zu manipulieren! ", + Cogs +" : 1 Toons: 0", + ], + 'Canned': ["Na, wie sieht's draußen aus?", + "Du kannst dich gleich wegschmeißen.", + "Frisch rausgeschmissen ist halb verloren.", + "Du schmeißt mit dem Schinken nach der Wurst.", + "Der Rausschmiss lauert überall.", + "Mensch, ärgere dich nicht.", + "Eene-meene-maus, und du bist raus.", + "Ich schmeiß dich raus!", + "Ich schmeiß dich raus und mach mir nichts draus!", + "Raus!!", + ], + 'Chomp': ["Schau dir diese Raffzähne an!", + "Mampf-mampf-mampf!", + "Hier ist was zu mampfen.", + "Suchst du nach was zu mampfen?", + "Da wirst du ordentlich dran zu kauen haben.", + "Dich verspeis ich zum Abendessen.", + "Ich fress gern kleine Toons!", + ], + 'ClipOnTie': ["Du solltest dich für unser Treffen lieber warm anziehen.", + "Ohne deinen Schlips kommst du nicht RAUS.", + "Die elegantesten "+ Cogs + " tragen sowas.", + "Probier mal die Größe.", + "Für den Erfolg musst du dich gut anziehen.", + "Keine Krawatte, keine Bedienung.", + "Brauchst du Hilfe beim Anlegen?", + "Nichts strahlt so viel Macht aus wie eine gute Krawatte.", + "Schaun wir, mal, ob diese passt.", + "Die wird dir die Luft abdrücken.", + "Du solltest dich gut anziehen, bevor du RAUS gehst.", + "Ich denke, ich ziehe jetzt mal den Knoten fest.", + ], + 'Crunch': ["Du reibst dich wohl gerade auf.", + "Malmzeit!", + "Ich gebe dir mal was zu kauen!", + "Hier, zermalm das mal!", + "Da kann gut zermalmen.", + "Magst du es lieber weich oder bissfest?", + "Ich hoffe, du bist bereit für die Malmzeit.", + "Siehst aus, als solltest du zermalmt werden!", + "Ich zermalm dich wie einen Käfer." + ], + 'Demotion': ["Du steigst auf der Karriereleiter ab.", + "Ich schick dich zurück in die Poststelle.", + "Gib dein Namensschild zurück.", + "Ab geht er, der Peter!", + "Du sitzt wohl fest.", + "Geh dorthin zurück, woher du gekommen bist.", + "Du sitzt in einer Sackgasse.", + "In nächster Zeit wird sich bei dir gar nichts bewegen.", + "Du kommst nirgendwo mehr hin.", + "Das wird ein schwarzer Fleck in deiner Personalakte.", + ], + 'Downsize': ["Komm runter!", + "Weißt du, wie du runter kommst?", + "Da wollen wir uns doch mal dran machen.", + "Was ist los? Du siehst so niedergeschlagen aus.", + "Geht's bergab?", + "Was geht nieder? Du!", + "Ich nehme mir gern Leute vor, die niedriger stehen als ich.", + "Warum mach ich dich nicht einfach fertig, um nicht zu sagen, nieder?", + "Wart ab, bis ich niederfahre!", + "Wirf dich nieder, du Wurm!", + "Du wirst gleich was Niederschmetterndes erleben.", + "Dieser Angriff macht alles nieder, was sich bewegt!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["Zeit für einen Umzug.", + "Pack deine Taschen, Toon.", + "Du solltest mal über eine neue Umgebung nachdenken.", + "Betrachte dich als abserviert.", + "Du bist mit der Miete im Rückstand.", + "Das wird extrem ungemütlich werden.", + "Ich werde dich mit der Wurzel ausgraben.", + "Ich schicke dich Packen.", + "Du bist hier fehl am Platz.", + "Bereite dich auf eine Verlegung vor.", + "Du bist reif für die Insel.", + ], + 'EvilEye': ["Dich trifft gleich mein böser Blick.", + "Augenblick mal.", + "Wart mal. Ich habe was im Auge.", + "Ich lasse dich nicht aus den Augen!", + "Könntest du das mal für mich im Auge behalten?", + "Ich habe einen Blick für das Böse.", + "Ich hau dir eins auf's Auge!", + "Ich bin so bösartig, wie's nur geht!", + "Ich pack dich direkt ins Auge des Sturms!", + "Sieh dich vor, ich rolle schon mit den Augen!", + ], + 'Filibuster':["Soll ich vollmachen?", + "Das hier wird eine Weile dauern.", + "Ich könnte den ganzen Tag damit zubringen.", + "Ich muss noch nicht mal zwischendurch Luft holen.", + "Es läuft und läuft und läuft ... ", + "Davon kann ich gar nicht genug kriegen.", + "Ich kann reden, bis dir schwarz vor Augen wird.", + "Ich kann reden, bis dir die Ohren abfallen?", + "Dann werd ich mal loslegen.", + "Ich krieg immer noch das eine oder andere Wort dazwischen.", + ], + 'FingerWag': ["Ich hab dir's schon tausend Mal gesagt.", + "Reiß dich zusammen, Toon.", + "Das ist nicht zum Lachen.", + "Ich komme gleich rüber!", + "Ich hab's satt, mich ständig zu wiederholen.", + "Ich denke doch, wir hatten das schon mal.", + "Du hast keinen Respekt vor uns"+ Cogs + ".", + "Jetzt pass mal auf.", + "Bla-bla-bla-bla-bla.", + "Lege es nicht darauf an, dieses Treffen abzubrechen.", + "Muss ich selbst dazwischen gehen?", + "Wir haben das alles schon mal besprochen.", + ], + 'Fired': ["Ich hoffe, du hast was zum Naschen mitgebracht.", + "Hier wird's gleich ziemlich warm werden.", + "Das dürfte dir ein wenig einheizen.", + "Ich hoffe, du bist kaltblütig.", + "Heiß, heißer, am heißesten.", + "Spiel lieber nicht mit dem Feuer.", + "Wage es, die Glut zu schüren!", + "Das feuert vielleicht!", + "Kannst du noch Aua sagen?", + "Ich hoffe, du hattest Sonnenschutz drauf.", + "Jetzt kommst du dir wohl verbraten vor?", + "Du wirst in Flammen aufgehen.", + "Du wirst direkt in der Hölle landen.", + "Du bist nichts als ein Strohfeuer.", + "Ich glaube, ich kriege grad die flammende Wut.", + "Gleich funkt's!", + "Na, da brat mir einer'n Storch!", + "Ich mach dir ordentlich Feuer unter'm Hintern.", + ], + 'FountainPen': ["Das wird böse Spuren hinterlassen.", + "Jetzt wollen wir dich mal in die Tinte reiten.", + "Ich steck dich kopfüber ins Tintenfass.", + "Du wirst eine gute Reinigung nötig haben.", + "Du solltest dich mal umziehen.", + "Es sprudelt so schön aus diesem Füllfederhalter.", + "Hier, ich nehme meinen Stift.", + "Kannst du meine Schrift lesen?", + "Ich nenne das die Feder des Schicksals.", + "Deine Akte ist ein wenig befleckt ... ", + "Du sitzt ganz schön in der Tinte, was?", + ], + 'FreezeAssets': ["Dein Vermögen ist meins.", + "Kriegst du schon kalte Füße?", + "Ich hoffe, du hast keine Pläne.", + "Damit bist du erst mal auf Eis gelegt.", + "Es weht ein kalter Wind.", + "Der Winter schlägt diesjahr zeitig zu.", + "Das lässt mich völlig kalt.", + "Mein Plan hat sich jetzt herauskristallisiert.", + "Es wird dich ganz kalt erwischen.", + "Geh lieber nicht auf dem Eis tanzen.", + "Ich hoffe, du magst Kalte Platte.", + "Ich bewahre kühles Blut.", + ], + 'GlowerPower': ["Schaust du mich an?", + "Man sagt, ich hätte einen stechenden Blick.", + "Ich riskiere gern mal einen Blick.", + "Finster, finster, Nachtgespinster!", + "Schau mir in die Augen, Kleines!", + "Was hältst du von diesen ausdrucksvollen Augen?", + "Meine Augen sind das Beste an mir.", + "Diese Augen haben was.", + "Kuckuck, ich seh dich!", + "Sieh mich an ...", + "Wollen wir mal einen Blick auf deine Zukunft werfen?", + ], + 'GuiltTrip': ["Ich werde dir einen Bußgang verordnen!", + "Fühlst du dich schuldig?", + "Das ist alles deine Schuld!", + "Für mich bist du immer der Schuldige!", + "Wate im Sumpf deiner eigenen Schuld!", + "Ich spreche nie wieder mit dir!", + "Du solltest lieber sagen, dass es dir Leid tut.", + "Du hast es mit mir verdorben bis in die Steinzeit und zurück!", + "Fertig zum Gehen?", + "Ruf mich an, wenn du von deinem Gang zurück bist.", + "Wann kommst du von deinem Gang zurück?", + ], + 'HalfWindsor': ["Das ist die modischste Krawatte, die dir je unterkommen wird! ", + "Versuch mal, dich nicht zu verknoten.", + "Das ist erst der Anfang vom Ende.", + "Du hast Glück, dass ich nicht noch einen doppelten Windsor habe!", + "Diese Krawatte kannst du dir gar nicht leisten. ", + "Ich wette, du hast einen einfachen Windsor noch nicht mal GESEHEN! ", + "Diese Krawatte ist nicht in deiner Liga.", + "Diese Krawatte wäre bei dir reine Verschwendung.", + "Du bist noch nicht mal die Hälfte dieser Krawatte wert!", + ], + 'HangUp': ["Deine Verbindung ist unterbrochen worden.", + "Auf Wiederhören!", + "Es ist Zeit, dass ich unsere Verbindung beende.", + "... und ruf mich ja nicht zurück!", + "Klick!", + "Dieses Gespräch ist beendet.", + "Ich trenne diese Verbindung.", + "Bei dir legen wohl die meisten wieder auf.", + "Anscheinend hast du eine schwache Verbindung.", + "Deine Zeit ist abgelaufen.", + "Ich hoffe, dass das klar und deutlich bei dir ankommt.", + "Falsch verbunden.", + ], + 'HeadShrink': ["Du siehst ein wenig irre aus.", + "Schatz, ich hab den Toon verarztet.", + "Ärztlichen Glückwunsch.", + "Irren, bis der Arzt kommt.", + "Ich irre, daher bin ich.", + "Darüber musst du doch den Kopf nicht verlieren.", + "Bist du jetzt ganz verrückt geworden?", + "Kopf hoch! Oder lieber runter?", + "Manche Dinge sind verrückter, als sie scheinen.", + "Gute Toons kommen und irren.", + ], + 'HotAir':["Wir haben grad eine heiße Diskussion.", + "Du hast wohl eine Hitzewallung.", + "Ich koche gleich über.", + "Nichts wird so heiß gegessen, wie es gekocht wird.", + "Ess wäre besser für dich, du hättest heiße Neuigkeiten ...", + "Immer dran denken, wo Rauch ist, ist auch Feuer.", + "Du siehst ein bisschen ausgebrannt aus.", + "Da löst sich wieder eine Versammlung in Rauch auf.", + "Jetzt sollte ich wohl noch etwas Benzin ins Feuer gießen.", + "Ich werde die Beziehung am Köcheln halten.", + "Ich hab ein paar heiße Tipps für dich.", + "Luftangriff!!!", + ], + 'Jargon':["Was für ein Unsinn.", + "Schau mal, ob du das kapierst.", + "Ich hoffe, ich drücke mich klar und deutlich aus.", + "Ich werde wohl lauter sprechen müssen.", + "Ich bestehe darauf, meine Meinung zu sagen!", + "Ich bin sehr direkt.", + "Zu diesem Thema muss ich meine alleingültige Meinung ausführlich darstellen.", + "Siehst du, Worte können doch verletzen.", + "Verstehst du, was ich meine?", + "Worte, Worte, nichts als Worte.", + ], + 'Legalese':["Ich habe eine Unterlassungsanordnung für dich.", + "Du wirst rechtlich gesehen verlieren.", + "Bist du dir über die gesetzlichen Folgen im Klaren?", + "Du stehst nicht über dem Gesetz!", + "Du solltest gesetzlich verboten werden.", + "Bei mir gibt's kein rückwirkendes Strafgesetz!", + "Die Meinungen in diesem Schlagabtausch sind nicht die von Disneys Toontown Online.", + "Wir haften nicht für Schäden, die durch diesen Schlagabtausch entstehen.", + "Die Ergebnisse dieses Schlagabtauschs können unterschiedlich sein.", + "Dieser Schlagabtausch verliert seine Gültigkeit soweit ein Verbot vorhanden ist.", + "Du passt nicht in mein Rechtssystem!", + "Du kannst mit Rechtsangelegenheiten nicht umgehen.", + ], + 'Liquidate':["Ich halte die Dinge gern im Fluss.", + "Hast du vielleicht gerade Liquiditätsprobleme?", + "Ich werde dein Vermögen etwas bereinigen müssen.", + "Höchste Zeit, mit dem Strom zu schwimmen.", + "Denk dran, wenn es nass ist, rutscht man leicht aus.", + "Deine Zahlen schwimmen davon.", + "Du scheinst wegzurutschen.", + "Es fällt dir alles auf die Füße.", + "Ich würde sagen, deine Sache wird wässrig.", + "Du wirst weggespült.", + ], + 'MarketCrash':["Ich werde es hier mächtig krachen lassen.", + "Du wirst den Krach nicht überleben.", + "Ich bin mehr, als der Aktienmarkt vertragen kann.", + "Ich habe einen echten Crashkurs für dich!", + "Jetzt komme ich herabgestürzt!", + "Ich wüte wie ein Bulle.", + "Sieht aus, als würde die Börse krachen.", + "Du solltest lieber aussteigen!", + "Verkaufen! Schnell verkaufen!", + "Soll ich die Rezession anführen?", + "Alle steigen aus, solltest du das nicht auch tun?", + ], + 'MumboJumbo':["Ich will das mal ganz deutlich sagen.", + "So einfach ist das.", + "Genau so machen wir's.", + "Ich werde das mal für dich aufblasen.", + "Du sagst vielleicht Technikgelaber dazu.", + "Hier sind meine unbescheidenen Worte.", + "Junge, du nimmst den Mund vielleicht voll!", + "Manche nennen mich bombastisch.", + "Ich will nur mal was einwerfen.", + "Ich denke, das sind die richtigen Worte.", + ], + 'ParadigmShift':["Sieh dich vor! Ich bin ziemlich wechselhaft.", + "Bereite dich darauf vor, dass dein Paradigma ausgewechselt wird.", + "Das ist doch mal ein interessantes Paradigma.", + "Du wirst einfach ausgewechselt werden.", + "Ich glaube, jetzt musst du wechseln.", + "Dein Wechsel ist geplatzt!", + "Du hast in deinem ganzen Leben noch keinen solchen Wechsel erlebt! ", + "Ich wechsle dich aus wie nichts.", + "Der Wechsel ist perfekt, ob du's glaubst oder nicht.", + ], + 'PeckingOrder':["Da lachen ja die Hühner!", + "Mit dir habe ich noch ein Hühnchen zu rupfen.", + "Ich mach mit dir nicht viel Federlesen.", + "Ich werde ganz sicher den Vogel abschießen!", + "Du stehst in der Hackordnung ganz unten.", + "Lieber einen Vogel in meiner Hand als zehn auf deinem Dach!", + "Wir schrägen Vögel müssen zusammenhalten! ", + "Warum ich nicht auf jemandem rumhacke, der genauso groß ist wie ich? Nö.", + "Eine Krähe hackt der anderen kein Auge aus, dann schon lieber dir!", + ], + 'PickPocket': ["Ich möchte mal deine Wertsachen prüfen.", + "Guck mal, was ist denn das da drüben?", + "Als würde man einem Baby den Schnuller wegnehmen.", + "Was für ein Diebstahl!", + "Ich halte das mal für dich.", + "Behalte meine Hände immer gut im Auge.", + "Die Hand ist schneller als das Auge.", + "In meinem Ärmel ist gar nichts.", + "Die Geschäftsleitung übernimmt keine Verantwortung für verlorene Gegenstände.", + "Wer's findet, dem gehörts auch.", + "Du wirst überhaupt nichts merken.", + "Eins für mich, keins für dich.", + "Ich bin so frei.", + "Das wirst du nicht mehr brauchen ...", + ], + 'PinkSlip': ["Mach nur nicht blau.", + "Ist dir kalt? Du bist ganz blau.", + "Du wirst dein blaues Wunder erleben.", + "Na hoppla, bist du blau?", + "Pass nur auf, dass du nicht blau wirst!", + "Ich geb's dir mit Brief und Siegel!", + "Ich hau dich grün und blau.", + "Das hast du doch verbrieft, oder.", + "Blau steht dir nicht.", + "Hier ist dein blauer Brief, verschwinde hier!", + ], + 'PlayHardball': ["Du willst es also darauf anlegen?", + "Ich rate dir, dich nicht mit mir anzulegen.", + "Härte zeigen!", + "Na schlag doch, los!", + "Durch dich kann ich doch durchgreifen ...", + "Ich schaff hier gleich mal Ordnung.", + "Hart, aber gerecht.", + "Benimm dich, sonst fliegst du raus.", + "Wenn's hart auf hart kommt, verlierst du!", + "Wird ganz schön hart für dich!", + "Bist du hart im Nehmen?", + "Ich geb dir eine harte Nuss zu knacken!", + ], + 'PoundKey': ["Höchste Zeit für ein paar Rückrufe.", + "Verdrück dich!", + "Diese Sache wird dich unter Druck setzen.", + "Da will jemand die Preise drücken.", + "Ich habe eine Menge Druck zu bieten.", + "Jetzt bin ich am Drücker!", + "Ich geb nur mal diese Zahl ein.", + "Gleich mach ich dir Druck.", + "Du hast überhaupt keinen Tastsinn.", + "OK, Toon, ich drück dich an die Wand.", + ], + 'PowerTie': ["Ich komm später nochmal, du siehst ziemlich kraftlos aus.", + "Fertig zum Armdrücken?", + "Meine Damen und Herren, es steht unentschieden!", + "Du solltest lernen, deine Kräfte richtig zu messen.", + "Pass auf, ich nehme Maß!", + "Gleich ist das Maß voll!", + "Was maßt du dir an!?", + "Das gibt eine kräftige Abreibung!", + "Kraft meiner Wassersuppe werde ich dich erledigen!", + "Ich bin ein Teil von jener Kraft, die das Böse will und das Böse schafft!", + ], + 'PowerTrip': ["Pack deine Sachen, wir machen einen kleinen Trip.", + "Guter Trip?", + "Wie geht's dem Ego?", + "Wie war der Trip?", + "Lass dich nicht aufhalten!", + "Außer Spesen nichts gewesen.", + "Warum denn in die Ferne schweifen!", + "Besser schlecht gefahren als gut gelaufen. ", + "Mein Ego hält das aus.", + "Ego ist Ego.", + "Mein Ego ist größer als deins!", + ], + 'Quake': ["Beben und beben lassen.", + "Hier bebt eine ganze Menge!", + "Ich sehe dich in deinen Schuhen erbeben.", + "Da kommt es schon, und es ist gewaltig!", + "Das sprengt die Richter-Skala.", + "Jetzt wird die Erde beben!", + "He, was zittert da? Du!", + "Schon mal ein Erdbeben erlebt?", + "Du bewegst dich auf unsicherem Boden!", + ], + 'RazzleDazzle': ["Viel Lärm um nichts.", + "Heute haun wir auf die Pauke!", + "Du kannst schon mal alle zusammentrommeln.", + "Ich mache gleich einen riesigen Aufriß!", + "Du wirst nach meiner Trommel tanzen!", + "Jetzt wird ein Fass aufgemacht.", + "Dir wird Hören und Sehen vergehen.", + "Wer wird denn hier so ein TamTam machen!", + "Willst du das an die große Glocke hängen?", + "Klappern gehört zum Handwerk.", + "Du wirst mit Pauken und Trompeten durchfallen!", + ], + 'RedTape': ["Besser Bürokratie als nie.", + "Ich werde dich ein wenig aufhalten.", + "Das geht alles seinen geregelten Gang.", + "Ein Stempelchen gefällig?", + "Dein Vorgang kommt in die Ablage P.", + "Je länger, je lieber.", + "Was lange währt, wird gut.", + "Ich werde dich schon beschäftigen.", + "Versuch nur mal, dich da durchzukämpfen.", + "Du hast vergessen das Formular auszufüllen.", + ], + 'ReOrg': ["Dir gefällt nicht, wie ich hier reorganisiert habe?", + "Ein bisschen Reorganisation muss sein.", + "Du bist ja gar nicht so schlecht, du musst nur umorganisiert werden.", + "Gefallen dir meine organisatorischen Fähigkeiten?", + "Ich dachte mir, ich sollte die Dinge mal anders aussehen lassen.", + "Du musst dich besser organisieren!", + "Du siehst ein bisschen desorganisiert aus.", + "Halt mal still, während ich deine Gedanken reorganisiere. ", + "Ich warte nur, bis du dich mal ein bisschen organisiert hast.", + "Du hast doch nichts dagegen, wenn ich nur mal ein bisschen reorganisiere?", + ], + 'RestrainingOrder': ["Ist ja nur einstweilig. ", + "Ich hau dir eine Einstweilige Verfügung um die Ohren!", + "Du darfst dich mir nicht mal auf zwei Meter nähern.", + "Vielleicht solltest du dich lieber fern halten.", + "Man sollte über dich verfügen.", + Cogs +" ! Haltet diesen Toon zurück!", + "Versuch doch mal, dich zurückzuhalten.", + "Ich hoffe, dass ich eine starke Verfügung bin für dich.", + "Probier mal, ob du diese Verfügung aufheben kannst!", + "Ich verfüge, dass du dich einstweilen zurückhalten sollst!", + "Warum fangen wir nicht mit einer Grundausbildung im Zusammenreißen an?" + ], + 'Rolodex': ["Deine Karte ist hier irgendwo drin.", + "Hier ist die Nummer eines Schädlingsbekämpfers.", + "Ich möchte dir meine Karte geben.", + "Ich habe deine Nummer immer dabei.", + "Ich habe alle Informationen über dich.", + "Organisation ist alles.", + "Schluss mit der Zettelwirtschaft!", + "Pass auf, dass du nicht aus meiner Rotationskartei fliegst!", + "Deine Schutzhülle nützt dir gar nichts.", + "Kann ich dich so kontaktieren?", + "Ich möchte sichergehen, dass wir in Verbindung bleiben.", + ], + 'RubberStamp': ["Ich hinterlasse stets einen guten Eindruck.", + "Es ist wichtig, einen festen und gleichmäßigen Druck auszuüben.", + "Jedes Mal ein perfekter Abdruck.", + "Ich möchte dich abstempeln.", + "Du wirst ZURÜCK AN DEN ABSENDER geschickt.", + "Du wurdest für UNGÜLTIG erklärt.", + "Du wirst per EXPRESS verschickt.", + "Ich sorge dafür, dass du meine Mitteilung ERHALTEN wirst.", + "Du wirst dich nirgendwo hin verdrücken - erst die NACHNAHME bezahlen.", + "Ich brauche eine Antwort - es EILT! ", + ], + 'RubOut': ["Hokuspokus, Verschwindibus!", + "Ich habe das Gefühl, dass du irgendwie weg bist.", + "Ich habe beschlossen, dich auszulassen.", + "Ich radiere immer aus, was mir nicht passt.", + "Ich radiere nur mal diesen kleinen Fehler weg.", + "Wenn ich will, verschwindet alles Störende.", + "Ich gern alles schön sauber und ordentlich.", + "Aus den Augen, aus dem Sinn!", + "Grad sah ich dich noch, schon bist du weg ... ", + "Schon wirst du blass und blässer ...", + "Ich werde das Problem eliminieren.", + "Ich werde mich mal um deine Problemzonen kümmern.", + ], + 'Sacked':["Sieht aus, als würdest du entlassen werden.", + "Und ab dafür.", + "Du fliegst in hohem Bogen raus.", + "Nimm deine Papiere und marschiere ab.", + "Meine Feinde werden entlassen!", + "Ich halte den Toontown-Rekord im Entlassen.", + "Du wirst hier nicht mehr gebraucht.", + "Deine Zeit ist um, du bist entlassen!", + "Meinem Entlassungsangriff kann keine Verteidigung standhalten.", + "Wieder einer weniger.", + ], + 'Schmooze':["Du wirst das gar nicht merken.", + "Das wird dir gut stehen.", + "Das hast du dir verdient.", + "Ich meine das ganz ehrlich.", + "Mit Schmeicheleien komme ich überall hin.", + "Ein Lob der Torheit!", + "Jetzt wird aber richtig dick aufgetragen.", + "Ich werde mich über deine guten Seiten auslassen.", + "Dafür sollte man dir ordentlich auf die Schulter klopfen.", + "Ich werd dir Loblieder singen, dass dir die Ohren klingen.", + "Ich will dich ja nicht vom Sockel hauen, aber ... ", + ], + 'Shake': ["Pass auf, ich schüttel gleich mit deinem Kopf!", + "Bist du schüttelfest?", + "Das wird eine Schüttel-Tour.", + "Du wirst durch mein Schüttelsieb fallen.", + "Muss man dich vor Gebrauch schütteln?", + "Ich bin der Schüttler vom Dienst.", + "Höchste Zeit in Deckung zu gehen.", + "Du scheinst etwas zerschüttelt.", + "Fertig zum Hände schütteln?", + "Ich werde dich schütteln, nicht rühren!", + "Das wird dich ordentlich durchschütteln.", + "An meiner Macht lass ich nicht schütteln! ", + ], + 'Shred': ["Ich muss ein paar gefährliche Dinge loswerden.", + "Ich erhöhe meinen Durchsatz.", + "Ich glaube, ich werde dich gleich mal entsorgen.", + "Dadurch werden Beweise vernichtet.", + "Jetzt kann man nichts mehr beweisen.", + "Schau mal, ob du dir das wieder zusammensetzen kannst.", + "Das dürfte dich auf eine angemessene Größe bringen.", + "Ich werde diese Idee in Stücke reißen.", + "Wir wollen doch nicht, dass das in falsche Hände gerät.", + "Wie gewonnen, so zerronnen.", + "Da fährt dein letztes Schnipselchen Hoffnung dahin!", + ], + 'Spin': ["Wie wär's mit einer kleinen Redetour?", + "Das kannst auch du nicht schönreden!", + "Das wird ganz schön wehtun!", + "Ich sag's dir mal vor.", + "Pass auf. Das sind nur schöne Worte.", + "Meinem Redeschwall ist keiner gewachsen!", + "Da läuft dir ja die Zunge über ...", + "Schön, dass du noch sprechen kannst.", + "Nichts als Worte - aber schön.", + ], + 'Synergy': ["Ich werde das vor den Ausschuss bringen.", + "Dein Projekt ist gestrichen worden.", + "Dein Budget wurde gekürzt.", + "Wir strukturieren deine Abteilung um.", + "Ich lasse darüber abstimmen, und du verlierst.", + "Ich habe gerade die endgültige Zustimmung erhalten.", + "Ein gutes Team kann sich jedes Problems entledigen.", + "Ich komme wegen dieser Sache wieder auf dich zurück.", + "Dann fangen wir mal an. ", + "Betrachte das als Synergiekrise.", + ], + 'Tabulate': ["Diese Zahlen ergeben keinen Sinn.", + "Nach meiner Rechnung verlierst du.", + "Jetzt wird tabula rasa gemacht.", + "Deine Rechnung geht nicht auf.", + "Bist du für diese Zahlen bereit?", + "Jetzt kriegst du die Quittung.", + "Zeit für die Endabrechnung.", + "Ich bringe die Dinge gern in Ordnung.", + "Und der aktuelle Stand ist ...", + "Diese Zahlen dürften sich als ziemlich wirksam erweisen.", + ], + 'TeeOff': ["Du bist noch nicht bereit.", + "Jetzt geht's los!", + "Jetzt fang ich erst mal richtig an.", + "Hol schon mal den Wagen, Harry.", + "Nichts überstürzen!", + "Sieben auf einen Schlag!", + "Der Sieg ist gewiss.", + "Du stehst mir im Weg.", + "Pass auf, wie ich Anlauf nehme ...", + "Auch der längste Weg beginnt mit dem ersten Schritt.", + "Aller Anfang ist schwer.", + "Das ist der Anfang vom Ende.", + ], + 'Tremor': ["Hast du das gespürt?", + "Du hast doch keine Angst vor einem kleinen Zittern, oder?", + "Das Zittern ist erst der Anfang.", + "Du siehst zittrig aus.", + "Ein kleines Beben hat noch niemandem geschadet.", + "Bist du bereit zu zittern und zu zagen?", + "Was ist los? Du siehst erschüttert aus.", + "Das schaudert's dich, was?", + "Was zitterst du vor Furcht?", + ], + 'Watercooler': ["Das sollte dich abkühlen.", + "Ist das nicht erfrischend?", + "Das ist Wasser auf meine Mühlen.", + "Wasser gepredigt und Wein getrunken.", + "Mach keine Panik, das ist doch nur Mineralwasser.", + "Keine Angst, es ist gereinigt.", + "Aha, noch ein zufriedener Kunde.", + "Ich denke, dein Plan wird ins Wasser fallen.", + "Ich hoffe, deine Farben laufen nicht weg.", + "Kleine Abkühlung gefällig?", + "Das wäscht sich aus!", + "Die nächste Runde zahlst du.", + ], + 'Withdrawal': ["Ich glaube, du hast überzogen.", + "Ich hoffe, dass dein Kontostand hierfür ausreicht.", + "Das geht auf mein Konto.", + "Dein Konto ist gesperrt.", + "Du wirst bald was einzahlen müssen.", + "Du hast einen wirtschaftlichen Zusammenbruch erlitten.", + "Ich glaube, du steckst in der Krise.", + "Deine Finanzen sind abgestürzt.", + "Ich sehe eine absolute Talfahrt voraus.", + "Das Glück wendet sich.", + ], + 'WriteOff': ["Lass mich deine Verluste vergrößern.", + "Wir wollen aus einem schlechten Deal das Beste machen.", + "Es ist Zeit, die Bücher abzuschließen.", + "Das wird sich in deinen Büchern nicht gut ausnehmen.", + "Ich hoffe auf Dividenden.", + "Du musst deine Verluste nachweisen.", + "Einen Bonus kannst du vergessen.", + "Ich werde deine Zahlen ein wenig hin und her schieben.", + "Du wirst ein paar Verluste erleiden.", + "Das wird deinen Gewinn beeinträchtigen.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Auf andere Spieler warten ...", + +# Elevator.py +ElevatorHopOff = "Aussteigen" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", AG " +CogsIncModifier = "%s"+ CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Poch-poch." +DoorWhosThere = "Wer da?" +DoorWhoAppendix = " wer?" +DoorNametag = "Tür" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Du brauchst Gags! Sprich mal mit Einweiser Eddie!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Komm wieder her, wenn du den Kriecher vertreiben hast!" +FADoorCodes_TALK_TO_HQ = "Hol dir deine Belohnung bei Mitarbeiter Harry!" +FADoorCodes_WRONG_DOOR_HQ = "Falsche Tür! Nimm die andere Tür zum Spielplatz!" +FADoorCodes_GO_TO_PLAYGROUND = "Falsch! Du musst doch zum Spielplatz!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Geh zu diesem Kriecher hin und schlage ihn!" +FADoorCodes_TALK_TO_HQ_TOM = "Hol dir deine Belohnung aus der Toontown-Zentrale!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Pass auf! Da ist ein BOT drin!" +FADoorCodes_DISGUISE_INCOMPLETE = "Man wird dich schnappen, wenn du da als Toon reingehst! Du musst erst deine Verkleidung als Bot vervollständigen!\n\nStelle deine Bot-Verkleidung aus Teilen aus der Fabrik zusammen." + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Leiter", + "Leiter ist heute geschlossen."], + + ["Albert", + "Albert hier jemand rum?"], + + ["Andre", + "Andre Tür, hier bist du falsch."], + + ["Anke", + "Anke-tten schützt vor Fahrraddiebstahl."], + + ["Anna", + "Anna-nas essen macht Spaß!"], + + ["Armin", + "Armin-Arm gehen sie durch den Park."], + + ["Axel", + "Axel Schweiß stinkt."], + + ["Basti", + "Na, Basti Hose?"], + + ["Benno", + "Wie soll dein Lehrer denn das Benno-ten?"], + + ["Ben", + "Ben-zin wird auch immer teurer."], + + ["Boris", + "Boris mir heiß!!"], + + ["Britt", + "Gib mir mal deinen Britt-Stift zum Kleben!"], + + ["Clair", + "Mein Bruder ist in die Clair-Grube gefallen."], + + ["Cindy", + "Oh, Perlen - Cindy echt?"], + + ["Connie", + "Habt ihr auch Connie-feren vorm Haus?"], + + ["Cora", + "Cora-llenriffe gibt's in der Südsee."], + + ["Diana", + "Ich war das nicht - Diana-re wars."], + + ["Dieter", + "Dieter-miten zerfressen ganze Häuser."], + + ["Dirk", + "Dirk-laubt sowieso keiner mehr was."], + + ["Ellen", + "Ellen-bogenschützer sind gut für Skater."], + + ["Erich", + "Erichten wir hier wollen wir unser Lager."], + + ["Ernst", + "Ernst-haft, ich heiße wirklich "+ Flippy + "."], + + ["Duck", + Donald +" Duck-uckst du, was!"], + + ["Max", + "Maxtu noch was essen?"], + + ["Ernest", + "Ernest noch ins Bett!!"], + + ["Amos", + "Amos-kito hat mich gestochen."], + + ["Alma", + "Almaufwärts sieht's noch schöner aus."], + + ["Ernie", + "Ernie-ste laut und heftig."], + + ["Esra", + "Esra-icht nicht, was ich eingekauft habe."], + + ["Frank", + "Du musst den Brief noch Frank-ieren."], + + ["Franz", + "Franz-ösisch kann ich auch!"], + + ["Gerrit", + "Er Gerrit dazwischen."], + + ["Ida", + "W-Idas denn!"], + + ["Gitta", + "Gitta-stäbe biegt man nicht so leicht auf."], + + ["Holger", + "Ich Holger-ne ein Eis für euch."], + + ["Kaviar", + "Kaviar keene Zähne mehr."], + + ["Kasimir", + "Kasimir alle ausjeschlagen."], + + ["Inga", + "Die Luftpumpe ist grad Inga-brauch."], + + ["Ingolf", + "Ingolf bin ich ganz gut."], + + ["Iris", + "Iris schlecht vom Essen."], + + ["Isolde", + "Isolde baden, bin ganz dreckig."], + + ["Jan", + "Janeinweißnicht."], + + ["Joe Kurt", + "Joe-Kurt ess ich für mein Leben gern!"], + + ["Lasse", + "Lasse reinfalln!"], + + ["Karl", + "Muss mal auf den Karl-ender schauen."], + + ["Keith", + "Kies kommt aus der Keith-Grube."], + + ["Thea", + "Thea-terkarten sind ausverkauft."], + + ["Knut", + "Knut-schen verboten!"], + + ["Lars", + "Lars gut sein, ich hol's mir selber."], + + ["Lotte", + "Lotte-rielose gibt's am Kiosk."], + + ["Marga", + "Marga-rine ist keine mehr da."], + + ["Marina", + "Machst du noch die Marina-de für das Fleisch?"], + + ["Martha", + "Wenn du frech wirst, kommst du an den Martha-Pfahl!"], + + ["Moni", + "Wie lange sitzt du schon vor dem Moni-tor?"], + + ["Naomi", + "Naomi, müde?"], + + ["Neal", + "Fahr doch mit ins Neal-Delta!"], + + ["Nick", + "Papa las Zeitung und Nick-te ein."], + + ["Paul", + "Wo ist denn auf diese Baustelle die Paul-eitung?"], + + ["Pepe", + "Vorsicht, da sind Pepe-roni dran!"], + + ["Peter", + "Peter-silie wächst im Garten."], + + ["Polly", + "Polly-zei! Hilfe!"], + + ["Rainer", + "Rainer Zufall, dass wir uns treffen."], + + ["Rosi", + "Rosi-nen ess ich nicht."], + + ["Rudi", + "Rudi-ch erst mal aus!"], + + ["Ruth", + "Ruth ruth sich auch aus."], + + ["Sarah", + "Er flog in den Schlamm und Sarah-benschwarz aus."], + + ["Steve", + "Ist das dein richtiger Vater oder dein Steve-Vater?"], + + ["Sue", + "Kommste mit in den Sue-permarkt?"], + + ["Theo", + "Mein Theo-dorant wirkt nicht mehr!"], + + ["Tom", + "Schmeiß nicht mit Tom-aten."], + + ["Till", + "Till-siter schmeckt lecker, aber stinkt."], + + ["Vince", + "So ein Vince-ling!"], + + ["Du", + "Du - wer! Ist hier etwa jemand?"], + + ["Walter", + "Walter-beiter arbeiten im Wald."], + + ["Wanda", + "Da geht's steil runter, bleib lieber auf dem Wanda-Weg."], + + ["Wayne", + "Na und, Wayne stört's?"], + + ["Werner", + "Werner-vt hier?"], + + ["Willi", + "Und bist du nicht Willi-g ..."], + + ["Wotan", + "Wotan-nen wachsen, fallen Tannenzapfen."], + + ["Evi", + "Evi eklig!!"], + + ["Izmir", + "Izmir schlecht!"], + + ["Mitsubishi!", + "Gesundheit!"], + + ["Komma", + "Komma schnell rein!"], + + ["Fenster", + "Fenster Lehrer merkt, kriegste Ärger!"], + + ["Eintritt", + "Eintrittel davon reicht."], + + ["Purzel", + "Du benimmst dich wie ein Elefant im Purzelanladen."], + + ["Tank", + "Bitte schön."], + + ["Reh", + "Iss schnell dein Erbsenpü-Reh."], + + ["Pizza", + "Ich hab schon pizzahlt."], + + ["Denken", + "Denken ich doch?"], + + ["Schaf", + "Mann, das ist aber schaf gewürzt!"], + + ["Fahrrad", + "Fahrrad ich dir nicht."], + + ["Lamm", + "Lammentier hier nicht rum!"], + + ["Zunge", + "Zunge, komm bald wieder ..."], + + ["Q", + "Du blöde Q!"], + + ["Chris", + "Chris du nicht mit, wer ich bin?"], + + ["Acht", + "Achtung, ich komm jetzt rein."], + + ["Eishockey", + "Eishockey mit dir?"], + + ["Kanufahrn", + "Ich hab nichts, getrunken, ich kanufahrn."], + + ["Wirsing", + "Machs gut und Auf Wirsing."], + + ["X", + "Xtreme Temperaturen habt ihr hier."], + + ["Haydn", + "Das macht `nen Haydnspaß."], + + ["Ente", + "Enteweder - oder."], + + ["Feder", + "Wenn die Feder mit ihren Söhnen ..."], + + ["Arm", + "Lieber arm dran als Arm ab."], + + ["Nachname", + "Schick das Paket per Nachname."], + + ["Gans", + "Du bist gans schön frech!"], + + ["Ekel", + "Schon mal so'n fetten Blutekel gesehen?"], + + ["Halligen", + "Tolles Auto mit Halligen-Scheinwerfern!"], + + ["Schwein", + "Schwein' ja garnicht mehr."], + + ["Fass", + "Fass mich bloß nicht an!"], + + ["Welke Blume", + "Welke Blume hättest du denn gern?"], + + ["Agatha", + "Ich hab uns was schönes Agathat."], + + ["Ungarn", + "Ich geb dir mein Fahrrad nur Ungarn."], + + ["Polen", + "Du musst das Gerät umpolen."], + + ["Kanada ", + "Tut mir Leid, Kanada."], + + ["Mrs.", + "Mrs. Sippi und Miss Ouri"], + + ["Rhein", + "Das war vielleicht ein Rheinfall!"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Hi, %!", + "Huhu %, schön dich zu sehen.", + "Ich freue mich, dass du heute da bist!", + "Na hallo, %.", + ] + +SharedChatterComments = [ + "Das ist ein toller Name, %.", + "Dein Name gefällt mir.", + "Nimm dich in Acht vor den "+ Cogs + ".", + "Ach da kommt ja der Toon-Express!", + "Ich muss ein Spiel der Toon-Express Spiel spielen machen, um ein paar Torten zu bekommen!", + "Manchmal spiele ich ein Spiel der Toon-Express Spiele, nur um die Obsttorte zu essen!", + "Puh, ich hab grad ein paar "+ Cogs + " aufgehalten. Ich muss mich ausruhen!", + "Ihh, manche von diesen "+ Cogs + " sind ganz schön groß!", + "Du siehst aus, als würde es dir Spaß machen.", + "Oh Mann, ich mach mir grad `nen schönen Tag.", + "Gefällt mir, was du da anhast.", + "Ich glaube, ich gehe heute Nachmittag angeln.", + "Viel Spaß in meiner Gegend.", + "Ich hoffe, du genießt deinen Aufenthalt in Toontown.", + "Wie ich höre, schneit es im Brrr.", + "Bist du heute schon mit dem Toon-Express gefahren?", + "Ich treffe gern neue Leute.", + "Wow, da sind ja massenhaft "+ Cogs + " im Brrr.", + "Ich spiele gern Fangen. Du auch?", + "Spiele beim Toon-Express machen Spaß.", + "Ich bring gern andere zum Lachen.", + "Es macht mir Spaß meinen Freunden zu helfen.", + "Ähem, hast du dich verlaufen? Denk dran, dein Stadtplan befindet sich in deinem Sticker-Buch.", + "Versuche, dich nicht in der Bürokratie der "+ Cogs + " zu verheddern.", + "Ich habe gehört, "+ Daisy + " hat ein paar neue Blumen in ihren Garten gepflanzt.", + "Wenn du die Taste 'Bild-Hoch' drückst, kannst du nach oben schauen!", + "Wenn du bei der Übernahme von Bot-Gebäuden hilfst, kannst du dir einen Bronze-Stern verdienen!", + "Wenn du die Tab-Taste drückst, kannst du verschiedene Ansichten deiner Umgebung sehen!", + "Wenn du die Strg-Taste drückst, kannst du springen!", + ] + +SharedChatterGoodbyes = [ + "Ich muss jetzt weg, tschüss!", + "Ich werde wohl mal ein Toon-Express Spiel spielen gehen.", + "Also mach's gut bis später, %!", + "Ich mach mich mal lieber an die Arbeit, diese "+ Cogs + " zu stoppen.", + "Jetzt muss ich mich mal in Bewegung setzen.", + "Entschuldige, muss leider weg.", + "Auf Wiedersehen.", + "Bis später, %!", + "Ich werde jetzt mal Napfkuchen werfen üben.", + "Ich werde mich einer Gruppe anschließen und ein paar "+ Cogs + " stoppen.", + "War schön, dass ich dich heute getroffen habe, %.", + "Ich hab heute viel zu tun. Da fang ich mal lieber an.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Willkommen in Toontown Mitte.", + "Hi, ich heiße "+ Mickey + ". Und du?", + ], + [ # Comments + "Hey, hast du "+ Donald + " gesehen?", + "Ich seh mir mal an, wie der Nebel bei "+ Donald + "s Dock hereinzieht.", + "Wenn du meinen Kumpel "+ Goofy + " siehst, grüß ihn von mir.", + "Ich habe gehört "+ Daisy + " hat ein paar neue Blumen in ihren Garten gepflanzt.", + ], + [ # Goodbyes + "Ich gehe zum Melodienland, " + Minnie + " besuchen!", + "Mensch, ich komme zu spät zu meiner Verabredung mit "+ Minnie + "!", + "Scheint Zeit zu sein für "+ Pluto + "s Dinner.", + "Ich denke, ich werde bei "+ Donald + "s Dock schwimmen gehen.", + "Es ist Zeit für ein Nickerchen. Ich gehe ins Traumland.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Willkommen im Melodienland.", + "Hi, ich heiße "+ Minnie + ". Und du?", + ], + [ # Comments + "Der Klang der Musik hallt von den Bergen wider!", + "Du musst unbedingt mal auf dem großen Plattenteller-Karussell fahren!", + "Du hast ein cooles Outfit, %.", + "Hey, hast du "+ Mickey + " gesehen?", + "Wenn du meinen Freund "+ Goofy + " siehst, Gruß ihn von mir.", + "Wow, da sind massenhaft "+ Cogs + " in der Nähe von "+ Donald + "s Traumland.", + "Ich habe gehört, dass es bei "+ Donald + "s Dock sehr neblig sein soll.", + "Du musst unbedingt das Labyrinth in "+ Daisy + "s Gärten ausprobieren.", + "Ich glaube, ich geh mal ein paar Melodien einfangen.", + "Hey %, schau mal da drüben.", + "Ich liebe den Klang der Musik.", + "Ich wette, du hast nicht gewusst, dass Melodienland auch manchmal Ton-Town genannt wird! Hihi!", + "Ich spiele sehr gern das Pantomime-Spiel. Du auch?", + "Ich bringe gern andere zum Kichern.", + "Junge, den ganzen Tag in Pumps herumlaufen geht vielleicht auf die Füße!", + "Hübsches Oberteil, %.", + "Ist das da auf dem Boden ein Jelly Bean?", + ], + [ # Goodbyes + "Mensch, ich komme zu spät zu meiner Verabredung mit "+ Mickey + "!", + "Scheint Zeit zu sein für "+ Pluto + "s Dinner.", + "Es ist Zeit für ein Nickerchen. Ich gehe ins Traumland.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Willkommen in "+ Daisy + "s Gärten.", + "Hi, ich heiße "+ Goofy + ". Und du?", + "Möönsch, schön dich zu sehen %!", + ], + [ # Comments + "Junge, in diesem Gartenlabyrinth kann man sich ganz schön leicht verlaufen! ", + "Probier auf jeden Fall mal das Labyrinth hier aus. ", + "Ich habe "+ Daisy + " den ganzen Tag nicht gesehen.", + "Ich frage mich, wo "+ Daisy + " ist.", + "Hey, hast du "+ Donald + " gesehen?", + "Wenn du meinen Freund "+ Mickey + " siehst, grüß ihn von mir.", + "Oje! Ich hab vergessen "+ Mickey + " sein Frühstück hinzustellen!", + "Möönsch da sind ja wirklich massenhaft "+ Cogs + " in der Nähe von "+ Donald + "s Dock.", + "Es sieht aus, als ob "+ Daisy + " ein paar neue Blumen in ihrem Garten gepflanzt hat.", + "In der Brrr-Zweigstelle meines Gag-Ladens gibt es Hypno-Brillen im Angebot für nur 1 Jelly Bean!", + "Goofys Gag-Läden bieten die besten Witze, Tricks und Zwerchfellkitzler von ganz Toontown!", + "In Goofys Gag-Läden bringt jede Torte im Gesicht garantiert einen Lacher, oder du bekommst deine Jelly Beans zurück!" + ], + [ # Goodbyes + "Ich gehe zum Melodienland, um " + Minnie + " zu besuchen!", + "Mensch, ich komme zu spät zu meinem Spiel mit "+ Donald + "!", + "Ich glaube, ich werde bei "+ Donald + "s Dock schwimmen gehen.", + "Es ist Zeit für ein Nickerchen. Ich gehe ins Traumland.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Willkommen im Traumland.", + "Hi, ich heiße "+ Donald + ". Und du?", + ], + [ # Comments + "Manchmal läuft es mir hier kalt den Rücken hinunter.", + "Probier auf jeden Fall das Labyrinth in "+ Daisy + "s Gärten aus.", + "Junge, geht's mir heute gut.", + "Hey, hast du "+ Mickey + " gesehen?", + "Wenn du meinen Kumpel "+ Goofy + " siehst, grüß ihn von mir.", + "Ich werde wohl heute Nachmittag angeln gehen.", + "Wow, da sind massenhaft "+ Cogs + " bei "+ Donald + "s Dock.", + "Hey, habe ich dich nicht mal bei "+ Donald + "s Dock mit dem Boot mitgenommen?", + "Ich habe "+ Daisy + " den ganzen Tag nicht gesehen.", + "Ich habe gehört, dass "+ Daisy + " ein paar neue Blumen in ihren Garten gepflanzt hat.", + "Quak.", + ], + [ # Goodbyes + "Ich gehe zum Melodienland, um " + Minnie + " zu besuchen!", + "Mensch, ich komme zu spät zu meiner Verabredung mit "+ Daisy + "!", + "Ich glaube, ich werde bei meinem Dock schwimmen gehen.", + "Ich glaube, ich kurve ein bisschen mit meinem Boot bei meinem Dock rum.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Neuer Freund" +FriendsListPanelSecrets = "Geheimnisse" +FriendsListPanelOnlineFriends = "FREUNDE\nONLINE" +FriendsListPanelAllFriends = "ALLE\nFREUNDE" +FriendsListPanelIgnoredFriends = "IGNORIERTE\nTOONS" +FriendsListPanelPets = "NAHE\nHAUSTIERE" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Du kannst leider nicht weitergehen, weil der Download vom %(phase)s erst zu %(percent)s%% abgeschlossen ist.\n\nBitte versuch es später noch einmal." + +# TeaserPanel.py +TeaserTop = "Leider ist das im Rahmen des Test-Zugangs nicht möglich.\nWenn du jetzt abonnierst, kannst du diese coolen Features nutzen:" +TeaserOtherHoods = "Besuche alle sechs einzigartigen Stadtteile!" +TeaserTypeAName = "Nimm teil an toontastischen Wettbewerben!" +TeaserSixToons = "Baue dir mit einem einzigen Account bis zu sechs Toons!" +TeaserOtherGags = "Erwirb sechs Fähigkeitsstufen für sechs verschiedene Gag-Tracks!" +TeaserClothing = "Entwirf spezielle Kleidungsstücke für\ndeinen ganz persönlichen Toon!" +TeaserFurniture = "Kaufe Möbelstücke und richte damit dein Haus ein!" +TeaserCogHQ = "Dringe heimlich in gefährliche von\nBots beherrschte Bereiche vor!" +TeaserSecretChat = "Tausche mit Freunden Geheimnisse aus,\ndamit du mit ihnen online chatten kannst!" +TeaserCardsAndPosters = "Als offizieller Einwohner erhältst du den Toontown Willkommensbrief\nmit einem coolen Bot-Poster und schicken Toontown Aufklebern!" +TeaserHolidays = "Nimm an tollen Events der Stadt teil!" +TeaserQuests = "Löse Hunderte von Toon-Aufgaben, um Toontown mit zu retten!" +TeaserEmotions = "Kaufe Emotionen, damit dein Toon mehr Ausdruck bekommt!" +TeaserMinigames = "Spiele alle 8 Minigames!" +TeaserSubscribe = "Jetzt abonnieren" +TeaserContinue = "Probezeit fortsetzen" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "%s wird heruntergeladen " +DownloadWatcherInitializing = "Download wird vorbereitet ..." + +# Launcher.py +LauncherPhaseNames = { + 0 : " Vorbereitung", + 3 : " Toon Kreieren", + 3.5 : " Anleitung", + 4 : " Spielplatz", + 5 : " Straße", + 5.5 : " Grundstück", + 6 : " Viertel I", + 7 : Cog + "-Gebäude", + 8 : " Viertel II", + 9 : " Bot-Hauptquartier", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s von %(total)s)" +LauncherStartingMessage = "Disneys Toontown Online wird gestartet ... " +LauncherDownloadFile = "Update für "+ LauncherProgress + " wird heruntergeladen ..." +LauncherDownloadFileBytes = "Update für "+ LauncherProgress + " wird heruntergeladen: %(bytes)s" +LauncherDownloadFilePercent = "Update für "+ LauncherProgress + " wird heruntergeladen: %(percent)s%%" +LauncherDecompressingFile = "Update für "+ LauncherProgress + " wird entkomprimiert ..." +LauncherDecompressingPercent = "Update für "+ LauncherProgress + " wird entkomprimiert: %(percent)s%%" +LauncherExtractingFile = "Update für "+ LauncherProgress + " wir extrahiert ..." +LauncherExtractingPercent = "Update für "+ LauncherProgress + " wird extrahiert: %(percent)s%%" +LauncherPatchingFile = "Update für "+ LauncherProgress + " wird implementiert ..." +LauncherPatchingPercent = "Update für "+ LauncherProgress + " wird implementiert: %(percent)s%%" +LauncherConnectProxyAttempt = "Verbindung zu Toontown: %s (Proxy: %s) Versuch: %s" +LauncherConnectAttempt = "Verbindung zu Toontown: %s Versuch %s" +LauncherDownloadServerFileList = "Update Toontown läuft ..." +LauncherCreatingDownloadDb = "Update Toontown läuft ..." +LauncherDownloadClientFileList = "UpdateToontown läuft ..." +LauncherFinishedDownloadDb = "Update Toontown läuft ... " +LauncherStartingToontown = "Toontown wird gestartet ..." +LauncherRecoverFiles = "Update Toontown läuft. Dateien werden wiederhergestellt ..." +LauncherCheckUpdates = "Updates für" + LauncherProgress +LauncherVerifyPhase = "Update Toontown läuft ..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Toon\nkreieren" +AvatarChoicePlayThisToon = "Toon\neinsetzen" +AvatarChoiceSubscribersOnly = " Jetzt\n\n\n\n\nabonnieren!" +AvatarChoiceDelete = "Löschen" +AvatarChoiceDeleteConfirm = "Hiermit wird %s für immer gelöscht." +AvatarChoiceNameRejected = "Name\nabgewiesen" +AvatarChoiceNameApproved = "Name\nbestätigt!" +AvatarChoiceNameReview = "Wird\ngeprüft" +AvatarChoiceNameYourToon = "Gib\ndeinem Toon einen Namen!" +AvatarChoiceDeletePasswordText = "Achtung! Damit wird %s für immer gelöscht. Gib dein Passwort ein um diesen Toon zu löschen." +AvatarChoiceDeleteConfirmText = "Achtung! Damit wird %(name)s für immer gelöscht. Wenn du sicher bist, dass du dies tun willst, gib \"%(confirm)s\" ein und klicke auf OK." +AvatarChoiceDeleteConfirmUserTypes = "Löschen" +AvatarChoiceDeletePasswordTitle = "Toon löschen?" +AvatarChoicePassword = "Passwort" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Das Passwort scheint nicht richtig zu sein. Gib dein Passwort ein um diesen Toon zu löschen." +AvatarChoiceDeleteWrongConfirm = "Du hast nicht das Richtige eingegeben. Um %(name)s zu löschen gib \"%(confirm)s\" ein und klicke auf OK. Die Anführungszeichen nicht tippen. Klicke auf Abbrechen, wenn du es dir anders überlegt hast." + +# AvatarChooser.py +AvatarChooserPickAToon = "Such dir einen Toon zum Spielen aus" +AvatarChooserQuit = lQuit + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Bitte Kundendienst anrufen unter %s." +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nWenn du Hilfe brauchst, ruf bitte den Kundendienst an unter %s." +TTAccountIntractibleError = "Es ist ein Fehler aufgetreten." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan.', 'Febr.', 'März', 'Apr.', 'Mai', 'Juni', + 'Juli', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.',] +DateOfBirthEntryDefaultLabel = "Geburtsdatum" + + +# AchievePage.py +AchievePageTitle = "Erfolge\n(Demnächst)" + +# PhotoPage.py +PhotoPageTitle = "Foto\n(Demnächst)" + +# BuildingPage.py +BuildingPageTitle = "Gebäude\n(Demnächst)" + +# InventoryPage.py +InventoryPageTitle = "Gags" +InventoryPageDeleteTitle = "GAGS LÖSCHEN" +InventoryPageTrackFull = "Du hast alle Gags im Ablauf %s." +InventoryPagePluralPoints = "Du bekommst einen neuen %(trackName)s-Gag, \nwenn du noch %(numPoints)s\n%(trackName)s-Punkte machst." +InventoryPageSinglePoint = "Du bekommst einen neuen\n%(trackName)s-Gag, wenn du noch\n%(numPoints)s %(trackName)s-Punkte machst." +InventoryPageNoAccess = "Du hast noch keinen Zugang zu Ablauf %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS Toons" + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Bleiben %s" + +# MapPage.py +MapPageTitle = "Stadtplan" +MapPageBackToPlayground = "Zurück zum Spielplatz" +MapPageBackToCogHQ = "Zurück zum Bot-Hauptquartier" +MapPageGoHome = "Nach Hause" +# hood name, street name +MapPageYouAreHere = "Du bist in: %s\n%s" +MapPageYouAreAtHome = "Du bist auf\ndeinem Grundstück" +MapPageYouAreAtSomeonesHome = "Du bist auf dem Grundstück von %s " +MapPageGoTo = "Gehe zu\n%s" + +# OptionsPage.py +OptionsPageTitle = "Optionen" +OptionsPagePurchase = "Jetzt abonnieren!" +OptionsPageLogout = "Logout" +OptionsPageExitToontown = "Toontown verlassen" +OptionsPageMusicOnLabel = "Musik an." +OptionsPageMusicOffLabel = "Musik aus." +OptionsPageSFXOnLabel = "Soundeffekte an." +OptionsPageSFXOffLabel = "Soundeffekte aus." +OptionsPageFriendsEnabledLabel = "Anfragen neuer Freunde akzeptieren." +OptionsPageFriendsDisabledLabel = "Anfragen neuer Freunde nicht akzeptieren." +OptionsPageSpeedChatStyleLabel = "Schnell-Chat-Farbe" +OptionsPageDisplayWindowed = "Im Fenster" +OptionsPageSelect = "Auswählen" +OptionsPageToggleOn = "Einschalten" +OptionsPageToggleOff = "Ausschalten" +OptionsPageChange = "Wechseln" +OptionsPageDisplaySettings = "Bildschirm: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Bildschirm: %(screensize)s" +OptionsPageExitConfirm = "Toontown verlassen?" + +DisplaySettingsTitle = "Anzeigeeinstellungen" +DisplaySettingsIntro = "Mit den folgenden Einstellungen kannst du die Darstellung von Toontown auf deinem Computer konfigurieren. Wenn bei dir keine Probleme auftreten, müssen sie wahrscheinlich nicht geändert werden." +DisplaySettingsIntroSimple = "Du kannst die Bildschirmauflösung auf einen höheren Wert einstellen, damit Text und Bild in Toontown deutlicher dargestellt werden. Bei manchen Grafikkarten kann dies aber dazu führen, dass das Spiel nicht so gut läuft oder überhaupt nicht funktioniert." + +DisplaySettingsApi = "Grafik-API:" +DisplaySettingsResolution = "Auflösung:" +DisplaySettingsWindowed = "In einem Fenster" +DisplaySettingsFullscreen = "Ganzer Bildschirm" +DisplaySettingsApply = "Verwenden" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Wenn du OK drückst, ändern sich die Einstellungen für die Anzeige. Wenn die neue Konfiguration für deinen Computer nicht geeignet ist, kehrt die Anzeige nach %s Sekunden zur ursprünglichen Konfiguration zurück." +DisplaySettingsAccept = "Drücke OK, um die neuen Einstellungen zu behalten, oder Abbrechen, um sie rückgängig zu machen. Wenn du gar nichts tust, gehen die Einstellungen nach %s Sekunden automatisch zurück." +DisplaySettingsRevertUser = "Deine vorherigen Anzeigeeinstellungen wurden wiederhergestellt." +DisplaySettingsRevertFailed = "Die gewählten Anzeigeeinstellungen funktionieren auf deinem Computer nicht. Deine vorherigen Anzeigeeinstellungen wurden wiederhergestellt." + + +# TrackPage.py +TrackPageTitle = "Gag-Ablauf Training" +TrackPageShortTitle = "Gag-Training" +TrackPageSubtitle = "Löse Toon-Aufgaben um zu lernen, wie man neue Gags einsetzt!" +TrackPageTraining = "Du übst das Einsetzen von %s Gags.\nWenn du alle 16 Aufgaben bewältigst, wirst du\nin der Lage sein, %s Gags im Kampf einzusetzen." +TrackPageClear = "Du übst im Moment keinen Gag-Typ." +TrackPageFilmTitle = "%s\nTraining\nFilm" +TrackPageDone = "ENDE" + +# QuestPage.py +QuestPageToonTasks = "Toon-Aufgaben" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nTo: %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nTo choose, go see:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Wählen" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = "Mitarbeiter in der Zentrale" +QuestPosterHQBuildingName = "Toontown-Zentrale" +QuestPosterHQStreetName = "In irgendeiner Straße" +QuestPosterHQLocationName = "In irgendeinem Viertel" + +QuestPosterTailor = "Schneider" +QuestPosterTailorBuildingName = "Bekleidungsgeschäft" +QuestPosterTailorStreetName = "Irgendwo auf dem Spielplatz" +QuestPosterTailorLocationName = "In irgendeinem Viertel" +QuestPosterPlayground = "Auf dem Spielplatz" +QuestPosterAtHome = "Bei deinem Haus" +QuestPosterInHome = "In deinem Haus" +QuestPosterOnPhone = "An deinem Telefon" +QuestPosterEstate = "Auf deinem Grundstück" +QuestPosterAnywhere = "Irgendwo in den\nStraßen der Stadt" +QuestPosterAuxTo = "nach:" +QuestPosterAuxFrom = "von:" +QuestPosterAuxFor = "für:" +QuestPosterAuxOr = "oder:" +QuestPosterAuxReturnTo = "Zurückkehren\nzu:" +QuestPosterFun = "Nur so zum Spaß!" +QuestPosterFishing = "ANGELN GEHEN" +QuestPosterComplete = "Komplett" + +# ShardPage.py +ShardPageTitle = "Bezirke" +ShardPageHelpIntro = "Jeder Bezirk ist ein Abbild der Toontown-Welt." +ShardPageHelpWhere = "Du bist zurzeit im Bezirk \"%s\"." +ShardPageHelpWelcomeValley = "Du bist zurzeit im Bezirk 'Willkommens-Tal' in \"%s\"." +ShardPageHelpMove = "Um zu einem anderen Bezirk zu kommen, klicke auf seinen Namen." + +ShardPagePopulationTotal = "Gesamtbevölkerung Toontown:\n%d" +ShardPageScrollTitle = "Bezirk Bevölkerungszahl" + +# SuitPage.py +SuitPageTitle = "Bot-Galerie" +SuitPageMystery = "???" +SuitPageQuota = "%s von %s" +SuitPageCogRadar = "%s anwesend" +SuitPageBuildingRadarS = "%s Gebäude" +SuitPageBuildingRadarP = "%s Gebäude" + +# DisguisePage.py +DisguisePageTitle = "Bot-Verkleidung" +DisguisePageMeritAlert = "Bereit zur\nBeförderung!" +DisguisePageCogLevel = "Level %s" +DisguisePageMeritFull = "Voll" + +# FishPage.py +FishPageTitle = "Angeln" +FishPageTitleTank = "Fischeimer" +FishPageTitleCollection = "Fischalbum" +FishPageTitleTrophy = "Angeltrophäen" +FishPageWeightStr = "Gewicht: " +FishPageWeightLargeS = "%d kg" +FishPageWeightLargeP = "%d kg " +FishPageWeightSmallS = "%d g" +FishPageWeightSmallP = "%d g" +FishPageWeightConversion = 16 +FishPageValueS = "Wert: %d Jelly Bean" +FishPageValueP = "Wert: %d Jelly Beans" +FishPageTotalValue = "" +FishPageCollectedTotal = "Gesammelte Fischarten: %d von %d" +FishPageRodInfo = "%s Angelrute\n%d - %d Pfund" +FishPageTankTab = "Eimer" +FishPageCollectionTab = "Album" +FishPageTrophyTab = "Trophäen" + +FishPickerTotalValue = "Fische: %s / %s\nWert: %d Jelly Beans" + +UnknownFish = "???" + +FishingRod = "%s Angelrute" +FishingRodNameDict = { + 0 : "Zweig", + 1 : "Bambus", + 2 : "Hartholz", + 3 : "Stahl", + 4 : "Gold", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Elritze", + 2 : "Fisch", + 3 : "Fliegender Fisch", + 4 : "Hai", + 5 : "Swordfish", + 6 : "Killer Whale", + } + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Auswählen" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = 'Durch Aufheitern kannst du andere Toons im Kampf heilen.' +TrackChoiceGuiTRAP = 'Fallen Stellen sind mächtige Gags, die zusammen mit einem Köder verwendet werden müssen.' +TrackChoiceGuiLURE = 'Du kannst Köder verwenden, um Bots zu betäuben oder in Fallen Stellen zu locken.' +TrackChoiceGuiSOUND = 'Sound-Gags wirken bei allen Bots, aber nicht besonders stark.' +TrackChoiceGuiDROP = "Fall-Gags richten viel Schaden an, sind jedoch nicht besonders genau." + +# EmotePage.py +EmotePageTitle = "Ausdrucksmöglichkeiten / Gefühle" +EmotePageDance = "Du hast die folgende Tanzfolge aufgestellt:" +EmoteJump = "Springen" +EmoteDance = "Tanzen" +EmoteHappy = "Glücklich" +EmoteSad = "Traurig" +EmoteAnnoyed = "Verärgert" +EmoteSleep = "Schläfrig" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nLevel %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Du kannst den Spielplatz erst verlassen, wenn dein Lach-O-Meter lächelt!" + +# InventoryNew.py +InventoryTotalGags = "Gags insgesamt\n%d / %d" +InventoryDelete = "LÖSCHEN" +InventoryDone = "FERTIG" +InventoryDeleteHelp = "Klicke auf einen Gag um ihn zu LÖSCHEN." +InventorySkillCredit = "Erfahrung Punkte: %s" +InventorySkillCreditNone = "Erfahrung Punkte: Keine" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Genauigkeit: %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryAffectsOneCog = "Wirkt auf: Einen "+ Cog +InventoryAffectsOneToon = "Wirkt auf: Einen Toon" +InventoryAffectsAllToons = "Wirkt auf: Alle Toons" +InventoryAffectsAllCogs = "Wirkt auf: Alle "+ Cogs +InventoryHealString = "AUFHEITERN" +InventoryDamageString = "Schaden" +InventoryBattleMenu = "KAMPFMENÜ " +InventoryRun = "RENNEN" +InventorySOS = "SOS" +InventoryPass = "AUSSETZEN" +InventoryClickToAttack = "Klick auf einen\nGag um\nanzugreifen" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Du musst mit dem Toon-Express fahren, ehe du gehst.\n\n\n\n\n\nDu findest den Toon-Express neben Goofys Gag-Laden." +NPCForceAcknowledgeMessage2 = "Du hast die Suche nach dem Toon-Express wirklich hervorragend gemeistert!\nMelde dich nun in der Toontown-Zentrale zurück.\n\n\n\n\n\nDie Toontown-Zentrale befindet sich in der Nähe der Spielplatzmitte." +NPCForceAcknowledgeMessage3 = "Vergiss nicht, mit dem Toon-Express zu fahren.\n\n\n\n\n\nDu findest ihn neben Goofys Gag-Laden." +NPCForceAcknowledgeMessage4 = "Glückwunsch! Du hast den Toon-Express gefunden und bist damit gefahren!\n\n\n\n\n\nMelde dich nun in der Toontown-Zentrale zurück." +NPCForceAcknowledgeMessage5 = "Vergiss deine Toon-Aufgabe nicht!\n\n\n\n\n\n\n\n\n\n\nAuf der anderen Seite von Tunnels wie diesem findest du Bots, gegen die du kämpfen kannst." +NPCForceAcknowledgeMessage6 = "Toll, wie du diese Bots erledigt hast!\n\n\n\n\n\n\n\n\nGehe nun so schnell wie möglich zurück zur Toontown-Zentrale." +NPCForceAcknowledgeMessage7 = "Vergiss nicht, Freundschaft zu schließen!!\n\n\n\n\n\n\nKlicke auf einen anderen Spieler und auf die Schaltfläche Neuer Freund." +NPCForceAcknowledgeMessage8 = "Prima! Du hast jetzt einen neuen Freund!\n\n\n\n\n\n\n\n\nJetzt solltest du zur Toontown-Zentrale zurückgehen." +NPCForceAcknowledgeMessage9 = "Gut, dass du das Telefon benutzt hast!\n\n\n\n\n\n\n\n\nGehe nun zur Toontown-Zentrale zurück, um dir deine Belohnung abzuholen." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Du hast 1 Wurfpunkt erhalten! Wenn du 10 hast, bekommst du einen neuen Gag!" +MovieTutorialReward2 = "Du hast 1 Spritzpunkt erhalten! Wenn du 10 hast, bekommst du einen neuen Gag!" +MovieTutorialReward3 = "Gute Arbeit! Gut gemacht! Du hast deine erste Toon-Aufgabe erledigt!" +MovieTutorialReward4 = "Gehe zur Toontown-Zentrale, um deine Belohnung abzuholen!" +MovieTutorialReward5 = "Viel Spaß!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['Aufheitern', 'Fallen Stellen', 'Ködern', 'Volldröhnen', 'Werfen', 'Spritzen', 'Fallenlassen'] +BattleGlobalNPCTracks = ['neu auffüllen', 'Toons treffen', 'Bots verfehlen'] +BattleGlobalAvPropStrings = ( + ('Feder', 'Megafon', 'Lippenstift', 'Bambusrohr', 'Zauberpuder', 'Jonglierbälle'), + ('Bananenschale', 'Harke', 'Murmeln', 'Treibsand', 'Falltür', 'TNT'), + ('1-Euro-Schein', 'Kleiner Magnet', '5-Euro-Schein', 'Großer Magnet', '10-Euro-Schein', 'Hypno-Brille'), + ('Motorradhupe', 'Trillerpfeife', 'Signalhorn', 'Auugah', 'Elefantenrüssel', 'Nebelhorn'), + ('Napfkuchen', 'Obsttortenstück', 'Kremtortenstück', 'Ganze Obsttorte', 'Ganze Kremtorte', 'Geburtstagstorte'), + ('Spritzblume', 'Glas Wasser', 'Spritzpistole', 'Seltersflasche', 'Feuerwehrschlauch', 'Sturmwolke'), + ('Blumentopf', 'Sandsack', 'Amboss', 'Großes Gewicht', 'Safe', 'Konzertflügel') + ) +BattleGlobalAvPropStringsSingular = ( + ('eine Feder', 'ein Megafon', 'ein Lippenstift', 'ein Bambusrohr', 'ein Zauberpuder', 'ein Satz Jonglierbälle '), + ('eine Bananenschale', 'eine Harke', 'ein Satz Murmeln', 'eine Treibsandstelle', 'eine Falltür', 'ein TNT'), + ('ein 1-Euro-Schein', 'ein kleiner Magnet', 'ein 5-Euro-Schein', 'ein großer Magnet', 'ein 10-Euro-Schein', 'eine Hypno-Brille'), + ('eine Motorradhupe', 'eine Trillerpfeife', 'ein Signalhorn', 'ein Auugah', 'ein Elefantenrüssel', 'ein Nebelhorn'), + ('ein Napfkuchen', 'ein Obsttortenstück', 'ein Kremtortenstück', 'eine ganze Obsttorte', 'eine ganze Kremtorte', 'eine Geburtstagstorte'), + ('eine Spritzblume', 'ein Glas Wasser', 'eine Spritzpistole', 'eine Seltersflasche', 'ein Feuerwehrschlauch', 'eine Sturmwolke'), + ('ein Blumentopf', 'ein Sandsack', 'ein Amboss', 'ein großes Gewicht', 'ein Safe', 'ein Konzertflügel') + ) +BattleGlobalAvPropStringsPlural = ( + ('Federn', 'Megafone', 'Lippenstifte', 'Bambusrohre', 'Zauberpuders', 'Jonglierballsätze'), + ('Bananenschalen', 'Harken', 'Murmelsätze', 'Treibsandstellen', 'Falltüren','TNTs'), + ('1-Euro-Scheine', 'Kleine Magneten', '5-Euro-Scheine', 'Große Magneten','10-Euro-Scheine', 'Hypno-Brillen'), + ('Motorradhupen', 'Trillerpfeifen', 'Signalhörner', 'Auugahs', 'Elefantenrüssel', 'Nebelhörner'), + ('Napfkuchen', 'Obsttortenstücke', 'Kremtortenstücke','Ganze Obsttorten', 'Ganze Kremtorten', 'Geburtstagstorten '), + ('Spritzblumen', 'Gläser mit Wasser', 'Spritzpistolen','Seltersflaschen', 'Feuerwehrschläuche', 'Sturmwolken'), + ('Blumentöpfe', 'Sandsäcke', 'Ambosse', 'Große Gewichte', 'Safes','Konzertflügel') + ) +BattleGlobalAvTrackAccStrings = ("Mittel", "Perfekt", "Niedrig", "Hoch", "Mittel", "Hoch", "Niedrig") + +AttackMissed = "VORBEI" + +NPCCallButtonLabel = 'RUFEN' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("zur", "in der", "Einweisungsgasse"), # Tutorial + 1000 : ("zum", "auf dem", "Spielplatz"), + 1100 : ("zum", "auf dem", "Muschel-Boulevard"), + 1200 : ("zur", "in der", "Seetangstraße"), + 1300 : ("zur", "auf der", "Leuchtturmgasse"), + 2000 : ("zum", "auf dem", "Spielplatz"), + 2100 : ("zur", "in der", "Albernstraße"), + 2200 : ("zur", "in der", "Hohlgasse"), + 2300 : ("zum", "auf dem", "Kasperwinkel"), + 3000 : ("zum", "auf dem", "Spielplatz"), + 3100 : ("zum", "im", "Walrossweg"), + 3200 : ("zur", "in der", "Schneestraße"), + 4000 : ("zum", "auf dem", "Spielplatz"), + 4100 : ("zur", "auf der", "Sopran-Allee"), + 4200 : ("zum", "auf dem", "Bass-Promenade"), + 4300 : ("zur", "auf der", "Tenor-Terrasse"), + 5000 : ("zum", "auf dem", "Spielplatz"), + 5100 : ("zur", "in der", "Ulmenstraße"), + 5200 : ("zur", "in der", "Ahornstraße"), + 5300 : ("zur", "in der", "Eichenstraße"), + 9000 : ("zum", "auf dem", "Spielplatz"), + 9100 : ("zur", "in der", "Schlafliedgasse"), + 10000 : ("zum", "im", "Chefomat-Hauptquartier"), + 10100 : ("zur", "in der", "Chefomat-Hauptquartier-Lobby"), + 11000 : ("zum", "auf dem", "Schachermat-HQ-Hof"), + 11100 : ("zur", "in der", "Schachermat-HQ-Lobby"), + 11200 : ("zur", "in der", "Schachermat-Fabrik"), + 11500 : ("zur", "in der", "Schachermat-Fabrik"), + 12000 : ("zum", "im", "Monetomat-Hauptquartier"), + 12100 : ("zur", "in der", "Monetomat-Hauptquartier-Lobby"), + 13000 : ("zum", "im", "Gesetzomat-Hauptquartier"), + 13100 : ("zur", "in der", "Gesetzomat-Hauptquartier-Lobby"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("zu", "in", "Donalds Dock") +ToontownCentral = ("nach", "in", "Toontown Mitte") +TheBrrrgh = ("zu", "in", "Das Brrr") +MinniesMelodyland = ("zu", "in", "Minnies Melodienland") +DaisyGardens = ("zu", "in", "Daisys Gärten") +ConstructionZone = ("zur", "in der", "Bauzone") +FunnyFarm = ("zur", "auf der", "Spaßfarm") +GoofyStadium = ("zum", "im", "Goofy-Stadion") +DonaldsDreamland = ("zu", "in", "Donalds Traumland") +BossbotHQ = ("zum", "im", "Chefomat-Hauptquartier") +SellbotHQ = ("zum", "im", "Schachermat-HQ") +CashbotHQ = ("zum", "im", "Monetomat-Hauptquartier") +LawbotHQ = ("zum", "im", "Gesetzomat-Hauptquartier") +Tutorial = ("zur", "in der ", "Einweisung") +MyEstate = ("zu", "in", "Dein Haus") +WelcomeValley = ("zum", "im", "Willkommens-Tal") + +Factory = 'Fabrik' +Headquarters = 'Toontown-Zentrale' +SellbotFrontEntrance = 'Vordereingang' +SellbotSideEntrance = 'Seiteneingang' + +FactoryNames = { + 0 : 'Fabrik-Nachbildung', + 11500 : 'Schachermat-Bot-Fabrik', + } + +FactoryTypeLeg = 'Bein' +FactoryTypeArm = 'Arm' +FactoryTypeTorso = 'Oberkörper' + +# ToontownLoader.py +LoaderLabel = "Laden ..." + +# PlayGame.py +HeadingToHood = "Läuft %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "Läuft zu deinem Grundstück ..." +HeadingToEstate = "Läuft zu %ss Grundstück ..." # avatar name +HeadingToFriend = "Läuft zum Grundstück von %ss Freund ..." # avatar name + +# Hood.py +HeadingToPlayground = "Läuft zum Spielplatz ..." +HeadingToStreet = "Läuft %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Den ganzen Weg zurück zum Spielplatz rennen?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "WELCHER TOON?" +TownBattleChooseAvatarCogTitle = "WELCHER "+ string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "ZURÜCK" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Es sind keine Freunde da!" +TownBattleSOSWhichFriend = "Welchen Freund rufen?" +TownBattleSOSNPCFriends = "Gerettete Toons" +TownBattleSOSBack = "ZURÜCK" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Warten auf\nandere Spieler ..." +TownSoloBattleWaitTitle = "Bitte warten ..." +TownBattleWaitBack = "ZURÜCK" + +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "Du darfst erst in den Toon-Express einsteigen, wenn dein Lach-O-Meter lächelt." +TrolleyTFAMessage = "Du darfst erst in den Toon-Express einsteigen, wenn" + Mickey + " es sagt." +TrolleyHopOff = "Aussteigen" + +# DistributedFishingSpot.py +FishingExit = "Ausgang" +FishingCast = "Werfen" +FishingAutoReel = "Automatik-Rolle" +FishingItemFound = "Du hast was gefangen" +FishingCrankTooSlow = "Zu\nlangsam" +FishingCrankTooFast = "Zu\nschnell" +FishingFailure = "Du hast nichts gefangen!" +FishingFailureTooSoon = "Hol die Schnur nicht ein, ehe etwas angebissen hat. Warte, bis der Schwimmer sich schnell rauf und runter bewegt!" +FishingFailureTooLate = "Achte darauf die Schnur einzuholen, während der Fisch noch knabbert!" +FishingFailureAutoReel = "Die Automatik-Rolle hat diesmal nicht funktioniert. Dreh die Kurbel mit der Hand, genau mit der richtigen Geschwindigkeit, damit du etwas fängst!" +FishingFailureTooSlow = "Du hast die Kurbel zu langsam gedreht. Einige Fische sind schneller als andere. Versuche, den Geschwindigkeitsanzeiger in der Mitte zu halten!" +FishingFailureTooFast = "Du hast die Kurbel zu schnell gedreht. Einige Fische sind langsamer als andere. Versuche, den Geschwindigkeitsanzeiger in der Mitte zu halten!" +FishingOverTankLimit = "Dein Fischeimer ist voll. Verkaufe deine Fische an die Tierhandlung und komm zurück." +FishingBroke = "Du hast keine Jelly Beans mehr als Ködern! Fahr mit dem Toon-Express oder verkaufe Fische an die Tierhandlung und verdiene dir dadurch mehr Jelly Beans." +FishingHowToFirstTime = "Die Schaltfläche Werfen anklicken und herunterziehen. Je weiter du nach unten ziehst, desto kräftiger ist dein Wurf. Stell deine Angel so ein, dass du die Fischziele triffst.\n\nVersuch's mal!" +FishingHowToFailed = "Die Schaltfläche Werfen anklicken und herunterziehen. Je weiter du nach unten ziehst, desto kräftiger ist dein Wurf. Stell deine Angel so ein, dass du die Fischziele triffst.\n\nVersuch's nochmal!" +FishingBootItem = "Ein alter Stiefel" +FishingJellybeanItem = "%s Jelly Beans" +FishingNewEntry = "Neue Art!" +FishingNewRecord = "Neuer Rekord!" + +# FishPoker +FishPokerCashIn = "Kassiere\n%s\n%s" +FishPokerLock = "Sperren" +FishPokerUnlock = "Freigeben" +FishPoker5OfKind = "5 von einer Sorte" +FishPoker4OfKind = "4 von einer Sorte" +FishPokerFullHouse = "Full House" +FishPoker3OfKind = "3 von einer Sorte" +FishPoker2Pair = "2 Paar" +FishPokerPair = "Paar" + +# DistributedTutorial.py +TutorialGreeting1 = "Hi %s!" +TutorialGreeting2 = "Hi %s!\nKomm mal her!" +TutorialGreeting3 = "Hi %s!\nKomm mal her!\nVerwende dazu die Pfeil-Tasten!" +TutorialMickeyWelcome = "Willkommen in Toontown!" +TutorialFlippyIntro = "Ich möchte dir meinen Freund"+ Flippy + " vorstellen ..." +TutorialFlippyHi = "Hi, %s!" +TutorialQT1 = "Zum Sprechen kannst du das hier benutzen." +TutorialQT2 = "Zum Sprechen kannst du das hier benutzen.\nKlick darauf und wähle dann 'Hi'." +TutorialChat1 = "Zum Sprechen kannst einen von diesen Schaltflächen benutzen." +TutorialChat2 = "Die blaue Schaltfläche erlaubt dir, über die Tastatur chatten." +TutorialChat3 = "Vorsicht! Die meisten Mitspieler werden dich nicht verstehen, wenn du die Tastatur benutzt." +TutorialChat4 = "Die grüne Schaltfläche öffnet den %s." +TutorialChat5 = "Jeder kann dich verstehen, wenn du den %s benutzt." +TutorialChat6 = "Versuch mal, 'Hi' zu sagen." +TutorialBodyClick1 = "Sehr gut!" +TutorialBodyClick2 = "Ich freue mich, dich kennen zu lernen. Wollen wir Freunde werden?" +TutorialBodyClick3 = "Wenn du mit"+ Flippy + "Freundschaft schließen willst, klicke ihn an ..." +TutorialHandleBodyClickSuccess = "Gut gemacht!" +TutorialHandleBodyClickFail = "Knapp daneben. Versuche mal, direkt auf"+ Flippy + "zu klicken ..." +TutorialFriendsButton = "Nun klicke auf die Schaltfläche 'Freunde' unter"+ Flippy + "s Bild in der rechten Ecke." +TutorialHandleFriendsButton = "Und dann klicke auf die Schaltfläche 'Ja'." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Möchtest du gern mit "+ Flippy + " Freundschaft schließen?" +TutorialFriendsPanelMickeyChat = Flippy +" ist einverstanden, dein Freund zu sein. Klicke auf 'OK', um abzuschließen." +TutorialFriendsPanelYes = Flippy +" hat Ja gesagt!" +TutorialFriendsPanelNo = "Das ist nicht besonders freundlich!" +TutorialFriendsPanelCongrats = "Glückwunsch! Du hast deinen ersten Freund." +TutorialFlippyChat1 = "Komm mich besuchen, wenn du für deine erste Toon-Aufgabe bereit bist!" +TutorialFlippyChat2 = "Ich bin in der Toonhalle!" +TutorialAllFriendsButton = "Du kannst alle deine Freunde sehen, wenn du auf die Schaltfläche 'Freunde' klickst. Probiere es aus ... " +TutorialEmptyFriendsList = "Im Moment ist deine Liste leer, weil "+ Flippy + " kein richtiger Mitspieler ist." +TutorialCloseFriendsList = "Klicke auf den die Schaltfläche 'Schließen', damit die\nListe verschwindet" +TutorialShtickerButton = "Die Schaltfläche in der Ecke rechts unten öffnet dein Sticker-Buch. Probier es aus ..." +TutorialBook1 = "Das Buch enthält viele nützliche Informationen, zum Beispiel diesen Stadtplan von Toontown." +TutorialBook2 = "Du kannst auch die Fortschritte bei deinen Toon-Aufgaben kontrollieren." +TutorialBook3 = "Wenn du fertig bist, klicke wieder auf die Buch-Schaltfläche, damit es sich schließt." +TutorialLaffMeter1 = "Auch das hier wirst du brauchen ..." +TutorialLaffMeter2 = "Auch das hier wirst du brauchen ...\nEs ist dein Lach-O-Meter." +TutorialLaffMeter3 = "Wenn dich"+ Cogs + " angreifen, sinkt er." +TutorialLaffMeter4 = "Wenn du auf einem Spielplatz wie diesem hier bist, geht er wieder nach oben." +TutorialLaffMeter5 = "Wenn du Toon-Aufgaben löst, bekommst du Belohnungen, wie zum Beispiel eine Erhöhung deiner Lachstärke." +TutorialLaffMeter6 = "Pass gut auf! Wenn die "+ Cogs + " dich besiegen, verlierst du alle deine Gags." +TutorialLaffMeter7 = "Um mehr Gags zu bekommen, musst du die Toon-Express Spiele spielen." +TutorialTrolley1 = "Folge mir zum Toon-Express!" +TutorialTrolley2 = "Spring auf!" +TutorialBye1 = "Spiel ein paar Spiele!" +TutorialBye2 = "Spiel ein paar Spiele!\nKaufe ein paar Gags!" +TutorialBye3 = "Geh zu "+ Flippy + ", wenn du fertig bist!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "Du gehst in die falsche Richtung! Suche"+ Mickey + "!" + +PetTutorialTitle1 = "Das Doodle-Menü" +PetTutorialTitle2 = "Doodle-Schnell-Chat" +PetTutorialTitle3 = "Doodle-Kuhtalog" +PetTutorialNext = "Nächste Seite" +PetTutorialPrev = "Vorherige Seite" +PetTutorialDone = "Fertig" +PetTutorialPage1 = "Wenn du auf ein Doodle klickst, wird das Doodle-Menü angezeigt. Von dort aus kannst du das Doodle füttern, kraulen und rufen." +PetTutorialPage2 = "Mit dem neuen Bereich 'Haustiere' im Schnell-Chat kannst du ein Doodle dazu bringen, einen Trick vorzuführen. Wenn es das tut, gib ihm eine Belohnung, dann wird es noch besser!" +PetTutorialPage3 = "Kaufe neue Doodle-Tricks aus Klarabellas Kuhtalog. Bessere Tricks bringen besseres Toonen!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +# Playground.py +PlaygroundDeathAckMessage = "Die "+ Cogs + " haben all deine Gags weggenommen!\n\nDu bist traurig. Du darfst den Spielplatz nicht verlassen, bis du fröhlich bist." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "Der Vorarbeiter war erledigt, bevor du ihn erreichen konntest. Du hast keine Bot-Teile erbeuten können." + +# DistributedFactory.py +HeadingToFactoryTitle = "Auf dem Weg zu %s..." +ForemanConfrontedMsg = "%s kämpft gegen den Vorarbeiter!" + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Warte auf andere Spieler ..." +MinigamePleaseWait = "Bitte warten ..." +DefaultMinigameTitle = "Minigame-Name" +DefaultMinigameInstructions = "Minigame-Anleitungen" +HeadingToMinigameTitle = "Auf dem Weg zu %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Kraftmesser" +MinigamePowerMeterTooSlow = "Zu\nlangsam" +MinigamePowerMeterTooFast = "Zu\nschnell" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Minigame-Vorlage" +MinigameTemplateInstructions = "Das ist eine Mustervorlage für ein Minigame. Verwende sie, um neue Minigames zu erfinden." + +# DistributedCannonGame.py +CannonGameTitle = "Kanonen-Spiel" +CannonGameInstructions = "Schieße deinen Toon so schnell du kannst in den Wasserturm. Zum Zielen kannst du die Maus oder die Pfeiltasten verwenden. Sei schnell, dann gewinnst du eine große Belohnung für alle!" +CannonGameReward = "BELOHNUNG" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Tauziehen" +TugOfWarInstructions = "Tippe abwechselnd auf die linke und rechte Pfeiltaste. Versuche das genau so schnell zu machen, dass der grüne Strich auf gleicher Höhe mit der roten Linie liegt. Wenn du zu schnell oder zu langsam tippst, landest du im Wasser!" +TugOfWarGameGo = "LOS!" +TugOfWarGameReady = "Fertig ..." +TugOfWarGameEnd = "Gut gespielt!" +TugOfWarGameTie = "Unentschieden!" +TugOfWarPowerMeter = "Kraftmesser" + +# DistributedPatternGame.py +PatternGameTitle = "Vorbild "+ Minnie +PatternGameInstructions = Minnie +" wird dir einen Tanz vormachen. "+ \ + "Versuche "+ Minnie + "s Tanz mit den Pfeiltasten genau so zu wiederholen, wie du ihn siehst!" +PatternGameWatch = "Achte genau auf die Tanzschritte ..." +PatternGameGo = "LOS!" +PatternGameRight = "Gut, %s!" +PatternGameWrong = "Hoppla!" +PatternGamePerfect = "Das war ausgezeichnet, %s!" +PatternGameBye = "Danke für's Mitspielen!" +PatternGameWaitingOtherPlayers = "Warte auf andere Spieler ..." +PatternGamePleaseWait = "Bitte warten ..." +PatternGameFaster = "Du warst\nschneller!" +PatternGameFastest = "Du warst\nam schnellsten!" +PatternGameYouCanDoIt = "Na los!\nDu kannst das!" +PatternGameOtherFaster = "\nwar schneller!" +PatternGameOtherFastest = "\nwar am schnellsten!" +PatternGameGreatJob = "Gut gemacht!" +PatternGameRound = "Runde %s!" # Round 1! Round 2! .. + +# DistributedRaceGame.py +RaceGameTitle = "Renn-Spiel" +RaceGameInstructions = "Klicke auf eine Zahl. Wähle klug! Du kommst nur voran, wenn niemand anders dieselbe Zahl gewählt hat." +RaceGameWaitingChoices = "Warten, bis die Mitspieler gewählt haben ..." +RaceGameCardText = "%(name)s gewinnt: %(reward)s" +RaceGameCardTextBeans = "%(name)s erhält: %(reward)s" +RaceGameCardTextHi1 = "%(name)s ist ein toller Toon!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " 1 Feld vorwärts" +RaceGameForwardTwoSpaces = " 2 Felder vorwärts" +RaceGameForwardThreeSpaces = " 3 Felder vorwärts" +RaceGameBackOneSpace = " 1 Feld zurück" +RaceGameBackTwoSpaces = " 2 Felder zurück" +RaceGameBackThreeSpaces = " 3 Felder zurück" +RaceGameOthersForwardThree = " alle anderen \n3 Felder vorwärts" +RaceGameOthersBackThree = " alle anderen \n3 Felder zurück" +RaceGameInstantWinner = "Sieger auf einen Schlag!" +RaceGameJellybeans2 = "2 Jelly Beans" +RaceGameJellybeans4 = "4 Jelly Beans" +RaceGameJellybeans10 = "10 Jelly Beans!" + +# DistributedRingGame.py +RingGameTitle = "Ringe-Spiel" +# color +RingGameInstructionsSinglePlayer = "Versuche, durch so viele der %s Ringe zu schwimmen, wie du kannst. Benutze zum Schwimmen die Pfeiltasten." +# color +RingGameInstructionsMultiPlayer = "Versuche, durch die %s Ringe zu schwimmen. Andere Spieler werden es mit den Ringen in anderen Farben versuchen. Benutze zum Schwimmen die Pfeiltasten." +RingGameMissed = "VORBEI" +RingGameGroupPerfect = "GRUPPE\nPERFEKT!!" +RingGamePerfect = "PERFEKT!" +RingGameGroupBonus = "GRUPPENBONUS" + +# RingGameGlobals.py +ColorRed = "roten" +ColorGreen = "grünen" +ColorOrange = "orangen" +ColorPurple = "violetten" +ColorWhite = "weißen" +ColorBlack = "schwarzen" +ColorYellow = "gelben" + +# DistributedTagGame.py +TagGameTitle = "Fangen-Spiel" +TagGameInstructions = "Sammle die Münzen. Du kannst keine Münze einsammeln, wenn du fangen musst." +TagGameYouAreIt = "Du bist dran!" +TagGameSomeoneElseIsIt = "%s ist dran!" + +# DistributedMazeGame.py +MazeGameTitle = "Labyrinth-Spiel" +MazeGameInstructions = "Sammle die Münzen. Versuche, sie alle zu bekommen, aber hüte dich vor den "+ Cogs + "!" + +# DistributedCatchGame.py +CatchGameTitle = "Achtung Fallobst" +CatchGameInstructions = "Fange so viele %(fruit)s auf, wie du kannst. Hüte dich vor den "+ Cogs + " und fang möglichst keine %(badThing)s!" +CatchGamePerfect = "PERFEKT!" +CatchGameApples = 'Äpfel' +CatchGameOranges = 'Orangen' +CatchGamePears = 'Birnen' +CatchGameCoconuts = 'Kokosnüsse' +CatchGameWatermelons = 'Wassermelonen' +CatchGamePineapples = 'Ananas' +CatchGameAnvils = 'Ambosse' + +# DistributedPieTossGame.py +PieTossGameTitle = "Tortenwurf-Spiel" +PieTossGameInstructions = "Wirf mit Torten nach den Zielen." + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "SPIELEN" + +# Purchase.py +GagShopName = "Goofys Gag-Laden" +GagShopPlayAgain = "NOCHMAL\nSPIELEN" +GagShopBackToPlayground = "ZURÜCK ZUM\nSPIELPLATZ" +GagShopYouHave = "Du hast %s Jelly Beans zur Verfügung" +GagShopYouHaveOne = "Du hast 1 Jelly Bean zur Verfügung" +GagShopTooManyProps = "Entschuldige, du hast zu viele Hilfsmittel" +GagShopDoneShopping = "EINKAUF\nFERTIG" +# name of a gag +GagShopTooManyOfThatGag = "Entschuldige, du hast schon genug %s" +GagShopInsufficientSkill = "Dafür bist du noch nicht geschickt genug" +# name of a gag +GagShopYouPurchased = "Du hast %s gekauft" +GagShopOutOfJellybeans = "Entschuldige, du hast keine Jelly Beans mehr!" +GagShopWaitingOtherPlayers = "Warte auf andere Spieler ..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s hat die Verbindung abgebrochen." +GagShopPlayerExited = "%s hat das Spiel verlassen" +GagShopPlayerPlayAgain = "Nochmal spielen" +GagShopPlayerBuying = "Kaufen" + +# MakeAToon.py +GenderShopQuestionMickey = "Klicke auf mich, um einen männlichen Toon zu erstellen!" +GenderShopQuestionMinnie = "Klicke auf mich, um einen weiblichen Toon zu erstellen!" +GenderShopFollow = "Folge mir!" +GenderShopSeeYou = "Bis später!" +GenderShopBoyButtonText = "Junge" +GenderShopGirlButtonText = "Mädchen" + +# BodyShop.py +BodyShopHead = "Kopf" +BodyShopBody = "Körper" +BodyShopLegs = "Beine" + +# ColorShop.py +ColorShopHead = "Kopf" +ColorShopBody = "Körper" +ColorShopLegs = "Beine" +ColorShopToon = "Farbe" +ColorShopParts = "Teile" +ColorShopAll = "Alle" + +# ClothesShop.py +ClothesShopShorts = "Shorts" +ClothesShopShirt = "Oberteil" +ClothesShopBottoms = "Unterteil" + +# MakeAToon +MakeAToonDone = "Fertig" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Zurück" +CreateYourToon = "Drücke auf die Pfeile, um deinen Toon zu erstellen." +CreateYourToonTitle = "Deinen Toon erstellen" +CreateYourToonHead = "Klicke auf die 'Kopf'-Pfeile, um verschiedene Tiere auszusuchen." +MakeAToonClickForNextScreen = "Klicke auf den Pfeil unten, um auf die nächste Seite zu gelangen." +PickClothes = "Drücke auf die Pfeile, um dir die Kleidungsstücke für deinen Toon auszusuchen!" +PickClothesTitle = "Suche deine Kleidung aus" +PaintYourToon = "Klicke auf die Pfeile, um deinen Toon zu färben!" +PaintYourToonTitle = "Deinen Toon färben" +MakeAToonYouCanGoBack = "Du kannst auch zurückgehen, um den Körper zu ändern!" +MakeAFunnyName = "Suche dir mit meiner Maschine einen lustigen Namen aus!" +MustHaveAFirstOrLast1 = "Dein Toon sollte einen Vor- oder Nachnamen haben, meinst du nicht?" +MustHaveAFirstOrLast2 = "Möchtest du nicht, dass dein Toon einen Vor- oder Nachnamen hat?" +ApprovalForName1 = "Genau, dein Toon hat einen tollen Namen verdient!" +ApprovalForName2 = "Toon-Namen sind die besten Namen von allen!" +MakeAToonLastStep = "Letzter Schritt vor dem Besuch von Toontown!" +PickANameYouLike = "Wähle einen Namen, der dir gefällt!" +NameToonTitle = "Gib deinem Toon einen Namen" +TitleCheckBox = "Titel" +FirstCheckBox = "Vorname" +LastCheckBox = "Nachname" +RandomButton = "Beliebig" +NameShopSubmitButton = "Absenden" +TypeANameButton = "Namenseingabe" +TypeAName = "Dir gefallen diese Namen nicht?\nHier klicken -->" +PickAName = "Probier's mit dem Namenwahl-Spiel!\nHier klicken -->" +PickANameButton = "Namenwahl" +RejectNameText = "Dieser Name ist nicht zulässig. Versuch's bitte noch einmal." +WaitingForNameSubmission = "Namen absenden ..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_german.txt" +PetshopUnknownName = "Name: ???" +PetshopDescGender = "Geschlecht:\t%s" +PetshopDescCost = "Preis:\t%s Jelly Beans" +PetshopDescTrait = "Charakter:\t%s" +PetshopDescStandard = "Standard" +PetshopCancel = lCancel +PetshopSell = "Fisch verkaufen" +PetshopAdoptAPet = "Ein Doodle aufnehmen" +PetshopReturnPet = "Doodle zurückbringen" +PetshopAdoptConfirm = "%s für %d Jelly Beans aufnehmen?" +PetshopGoBack = "Zurückgehen" +PetshopAdopt = "Aufnehmen" +PetshopReturnConfirm = "%s zurückbringen?" +PetshopReturn = "Zurückbringen" +PetshopChooserTitle = "DOODLE DES TAGES" +PetshopGoHomeText = 'Möchtest du auf dein Grundstück gehen und mit deinem neuen Doodle spielen?' + +# NameShop.py +NameShopNameMaster = "NameMaster_german.txt" +NameShopPay = "Jetzt abonnieren!" +NameShopPlay = "Kostenlose Probezeit" +NameShopOnlyPaid = "Nur Benutzer, die bezahlt haben,\ndürfen ihren Toons selbst Namen geben.\nBis du dich für ein Abonnement entschieden hast, wird\ndein Toon folgenden Namen haben:\n" +NameShopContinueSubmission = "Weiter absenden" +NameShopChooseAnother = "Anderen Namen wählen" +NameShopToonCouncil = "Der Rat von Toontown\nwird deinen\nNamen prüfen. "+ \ + "Die Prüfung kann\nein paar Tage dauern.\nInzwischen bekommst\ndu folgenden Namen zugeteilt:\n " +PleaseTypeName = "Bitte gib deinen Namen ein:" +AllNewNames = "Alle neuen Namen\nbedürfen der Genehmigung\ndurch den Rat von Toontown." +NameShopNameRejected = "Der Name, den du\nbeantragt hast,\nwurde abgelehnt." +NameShopNameAccepted = "Glückwunsch!\nDer Name, den du\nbeantragt hast,\nwurde angenommen!" +NoPunctuation = "Du kannst in deinem Namen keine Satzzeichen verwenden!" +PeriodOnlyAfterLetter = "Du kannst einen Punkt in deinem Namen verwenden, aber nur nach einem Buchstaben." +ApostropheOnlyAfterLetter = "Du kannst einen Apostroph in deinem Namen verwenden, aber nur nach einem Buchstaben." +NoNumbersInTheMiddle = "In der Mitte eines Wortes dürfen keine Ziffern erscheinen." +ThreeWordsOrLess = "Dein Name darf nur aus höchstens drei Wörtern bestehen." +CopyrightedNames = ( + "micky", + "micky maus", + "mickymaus", + "minnie", + "minnie maus", + "minniemaus", + "donald", + "donald duck", + "donaldduck", + "pluto", + "goofy", + ) +NumToColor = ['Weiß', 'Pfirsichorange', 'Hellrot', 'Rot', 'Kastanienbraun', + 'Ockergelb', 'Braun', 'Hautfarben', 'Korallenrot', 'Orange', + 'Gelb', 'Cremeweiß', 'Zitronengelb', 'Lindgrün', 'Meergrün', + 'Grün', 'Hellblau', 'Blaugrün', 'Blau', + 'Immergrün', 'Königsblau', 'Schieferblau', 'Lila', + 'Lavendellila', 'Pink'] +AnimalToSpecies = { + 'dog' : 'Hund', + 'cat' : 'Katze', + 'mouse' : 'Maus', + 'horse' : 'Pferd', + 'rabbit' : 'Kaninchen', + 'duck' : 'Ente', + 'fowl' : 'Ente', + } +NameTooLong = "Der Name ist zu lang. Bitte versuche es noch einmal." +ToonAlreadyExists = "Du hast schon einen Toon namens %s!" +NameAlreadyInUse = "Der Name ist schon vergeben!" +EmptyNameError = "Du musst erst einen Namen eingeben." +NameError = "Dieser Name geht leider nicht." + +# NameCheck.py +NCTooShort = 'Dieser Name ist zu kurz.' +NCNoDigits = 'Dein Name darf keine Zahlen enthalten.' +NCNeedLetters = 'Jedes Wort in deinem Namen muss mehrere Buchstaben enthalten.' +NCNeedVowels = 'Jedes Wort in deinem Namen muss einige Vokale (Selbstlaute) enthalten.' +NCAllCaps = 'Dein Name darf nicht nur aus Großbuchstaben bestehen.' +NCMixedCase = 'Dieser Name hat zu viele Großbuchstaben.' +NCBadCharacter = "Dein Name darf das Zeichen '%s' nicht enthalten." +NCGeneric = 'Dieser Name geht leider nicht.' +NCTooManyWords = 'Dein Name darf nicht mehr als vier Wörter lang sein.' +NCDashUsage = ("Bindestriche dürfen nur verwendet werden, um zwei Wörter zu verbinden" + "(wie bei 'Klaus-Dieter').") +NCCommaEdge = "Dein Name darf nicht mit einem Komma beginnen oder enden." +NCCommaAfterWord = "Du darfst ein Wort nicht mit einem Komma beginnen lassen." +NCCommaUsage = ('In diesem Namen sind Kommas nicht richtig eingesetzt. Kommas müssen zeigen,' + 'dass zwei Wörter zusammengehören, wie in dem Namen "Quiselda Quittung, RA". ' + 'Nach Kommas muss ein Leerzeichen folgen.') +NCPeriodUsage = ('In diesem Namen sind Punkte nicht richtig eingesetzt. Punkte sind' + 'nur gestattet in Wörtern wie "Dr.", "Frl.", "J.T." usw.') +NCApostrophes = 'Dieser Name hat zu viele Apostrophe.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = "Toontown-Zentrale: Die " + Cogs + " haben eines der von dir geretteten Gebäude eingenommen!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Brauchst du mehr Zeit zum Nachdenken?' +STOREOWNER_GOODBYE = 'Bis später!' +STOREOWNER_NEEDJELLYBEANS = 'Du musst mit dem Toon-Express fahren, dann bekommst du in paar Jelly Beans.' +STOREOWNER_GREETING = 'Wähle aus, was du kaufen möchtest.' +STOREOWNER_BROWSING = 'Du kannst stöbern, aber zum Kaufen brauchst du eine Kleidermarke.' +STOREOWNER_NOCLOTHINGTICKET = 'Du brauchst eine Kleidermarke, um Kleidung zu kaufen.' +# translate +STOREOWNER_NOFISH = 'Komm wieder her, um der Tierhandlung gegen Jelly Beans Fische zu verkaufen.' +STOREOWNER_THANKSFISH = 'Danke! Die Tierhandlung wird sich freuen. Tschüss!' +STOREOWNER_THANKSFISH_PETSHOP = "Das sind aber ein paar schöne Exemplare! Danke." +STOREOWNER_PETRETURNED = "Keine Sorge. Wir finden ein schönes Zuhause für dein Doodle." +STOREOWNER_PETADOPTED = "Glückwunsch zu deinem neuen Doodle! Du kannst auf deinem Grundstück mit ihm spielen." +STOREOWNER_PETCANCELED = "Denk daran: Wenn du ein Doodle siehst, das dir gefällt, solltest du es aufnehmen, bevor es jemand anders tut!" + +STOREOWNER_NOROOM = "Hmm... du solltest vielleicht in deinem Schrank erst etwas Platz schaffen, bevor du neue Kleidung kaufst.\n" +STOREOWNER_CONFIRM_LOSS = "Dein Schrank ist voll. Du wirst die Kleidung verlieren, die du gerade trägst." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Wow! Du hast %s von %s Fischen gesammelt. Dafür verdienst du eine Trophäe und eine Lach-Spritze!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = "Toontown-\nZentrale: Eine Bot-Invasion hat begonnen!!!" +SuitInvasionBegin2 = "Toontown-\nZentrale: %s haben Toontown eingenommen!!!" +SuitInvasionEnd1 = "Toontown-\nZentrale: Die %s-Invasion ist beendet!!!" +SuitInvasionEnd2 = "Toontown-\nZentrale: Die Toons haben wieder einmal gesiegt!!!" +SuitInvasionUpdate1 = "Toontown-\nZentrale: Die Bot-Invasion liegt jetzt bei %s Bots!!!" +SuitInvasionUpdate2 = "Toontown-\nZentrale: Wir müssen sie besiegen, diese %s!!!" +SuitInvasionBulletin1 = "Toontown-\nZentrale: Es ist eine Bot-Invasion im Gange!!!" +SuitInvasionBulletin2 = "Toontown-\nZentrale: %s haben Toontown eingenommen!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Toon-Aufgebot" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown hat einen neuen Einwohner! Hast du ein paar Extra-Gags?" +QuestScriptTutorialMickey_2 = "Klar, %s!" +QuestScriptTutorialMickey_3 = "Einweiser Ede wird dir alles über die Bots erzählen.\aIch muss jetzt los! Tschüss!" +QuestScriptTutorialMickey_4 = "Komm bitte näher! Verwende die Pfeiltasten, um dich zu bewegen." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown hat einen neuen Einwohner! Hast du ein paar Extra-Gags?" +QuestScriptTutorialMinnie_2 = "Klar, %s!" +QuestScriptTutorialMinnie_3 = "Einweise Ede wird dir alles über die Bots erzählen.\aIch muss jetzt los! Tschüss!" + +QuestScript101_1 = "Das hier sind BOTS. BOTS sind Roboter, die versuchen Toontown einzunehmen. " +QuestScript101_2 = "Es gibt viele verschiedene Arten von BOTS und ..." +QuestScript101_3 = "... sie verwandeln fröhliche Toon-Gebäude ..." +QuestScript101_4 = "... in hässliche Bot-Gebäude!" +QuestScript101_5 = "Aber BOTS vertragen keinen Spaß!" +QuestScript101_6 = "Ein guter Gag stoppt sie." +QuestScript101_7 = "Es gibt viele Gags, aber nimm für den Anfang erst mal diese." +QuestScript101_8 = "Ach ja! Du brauchst auch noch ein Lach-O-Meter!" +QuestScript101_9 = "Wenn dein Lach-O-Meter zu weit absinkt, wirst du traurig!" +QuestScript101_10 = "Nur ein fröhlicher Toon ist ein gesunder Toon!" +QuestScript101_11 = "OH NEIN! Vor meinem Laden steht ein BOT!" +QuestScript101_12 = "BITTE HILF MIR! Besiege diesen Bot!" +QuestScript101_13 = "Hier ist deine erste Toon-Aufgabe!" +QuestScript101_14 = "Beeil dich! Gehe los und besiege diesen Kriecher!" + +QuestScript110_1 = "Gute Arbeit, wie du diesen Kriecher besiegt hast. Ich werde dir dafür ein Sticker-Buch geben ... " +QuestScript110_2 = "In dem Buch sind lauter nützliche Sachen." +QuestScript110_3 = "Öffne es, dann zeig ich sie dir." +QuestScript110_4 = "Der Stadtplan zeigt, wo du warst." +QuestScript110_5 = "Blättere um, dann siehst du deine Gags ..." +QuestScript110_6 = "Oje du hast keine Gags! Ich gebe dir eine Aufgabe." +QuestScript110_7 = "Blättere um, dann siehst du deine Aufgaben." +QuestScript110_8 = "Gehe zum Toon-Express, spiele Spiele und verdiene Jelly Beans, um dir Gags zu kaufen. " +QuestScript110_9 = "Zum Toon-Express kommst du, wenn du durch die Tür hinter mir zum Spielplatz gehst." +QuestScript110_10 = "Mache bitte nun das Buch zu und suche den Toon-Express!" +QuestScript110_11 = "Komm bitte wieder zurück zur Toontown-Zentrale, wenn du fertig bist. Tschüss!" + +QuestScriptTutorialBlocker_1 = "Na hallo!" +QuestScriptTutorialBlocker_2 = "Hallo?" +QuestScriptTutorialBlocker_3 = "Oh, du weißt nicht, wie man den Schnell-Chat benutzt!" +QuestScriptTutorialBlocker_4 = "Klicke auf die Schaltfläche, um etwas zu sagen." +QuestScriptTutorialBlocker_5 = "Sehr gut!\aDort, wo du hingehst, sind viele Toons, mit denen man sich unterhalten kann." +QuestScriptTutorialBlocker_6 = "Wenn du mit deinen Freunden über die Tastatur chatten willst, kannst du eine andere Schaltfläche benutzen." +QuestScriptTutorialBlocker_7 = "Sie heißt 'Chat'-Schaltfläche. Du musst offizieller Einwohner von Toontown sein, um sie zu benutzen." +QuestScriptTutorialBlocker_8 = "Viel Glück! Bis später!" + +""" +GagShopTut + +Du wirst auch die Fähigkeit erwerben, andere Gag-Arten einzusetzen. +""" + +QuestScriptGagShop_1 = "Willkommen im Gag-Laden!" +QuestScriptGagShop_1a = "Hier kaufen die Toons die Gags, mit denen sie gegen die Bots kämpfen können." +#QuestScriptGagShop_2 = "Dieses Gefäß zeigt, wie viele Jelly Beans du hast." +#QuestScriptGagShop_3 = "Klicke auf eine Gag-Schaltfläche, um einen Gag zu kaufen. Versuche es gleich einmal!" +QuestScriptGagShop_3 = "Klicke auf die Gag-Schaltflächen, um Gags zu kaufen. Versuche gleich mal, welche zu bekommen!" +QuestScriptGagShop_4 = "Gut! Du kannst diese Gags im Kampf gegen die Bots einsetzen." +QuestScriptGagShop_5 = "Hier kannst du einen Blick auf die höheren Wurf- und Spritzgags werfen ..." +QuestScriptGagShop_6 = "Wenn du genug Gags gekauft hast, klicke diese Schaltfläche an um zum Spielplatz zurückzukehren." +QuestScriptGagShop_7 = "Normalerweise kannst du diese Schaltfläche verwenden, um noch ein Toon-Express-Spiel zu spielen ..." +QuestScriptGagShop_8 = "... aber im Moment ist gerade keine Zeit für ein weiteres Spiel. Man braucht dich in der Toontown-Zentrale!" + +QuestScript120_1 = "Klasse, du hast den Toon-Express gut gefunden!\aÜbrigens, kennst du Bankier Bob schon?\aEr ist ein ziemlicher Naschkater.\aFühr dich doch bei ihm gleich mal gut ein, indem du ihm diesen Schokoriegel als kleines Geschenk mitbringst." +QuestScript120_2 = "Bankier Bob sitzt drüben in der Toontown Bank." + +QuestScript121_1 = "Mmh, danke für den Schokoriegel.\aHör mal, wenn du mir helfen kannst, geb ich dir eine Belohnung.\aDiese Bots haben die Schlüssel zu meinem Safe gestohlen. Erledige Bots, um einen gestohlenen Schlüssel zu finden.\aWenn du einen Schlüssel findest, bring ihn wieder her zu mir." +QuestScript130_1 = " Klasse, du hast den Toon-Express gut gefunden!\aÜbrigens habe ich heute ein Paket für Professor Peter erhalten.\aEs muss wohl die neue Kreide sein, die er bestellt hat.\aKannst du es ihm bitte bringen?\aEr ist drüben in der Schule." + +QuestScript131_1 = "Oh, danke für die Kreide.\aWas?!?\aDiese Bots haben meine Tafel gestohlen. Erledige Bots, um meine gestohlene Tafel zu finden.\aWenn du sie findest, bring sie wieder her zu mir." + +QuestScript140_1 = " Klasse, du hast den Toon-Express gut gefunden!\aÜbrigens habe ich da einen Freund, Bibliothekar Bertie, der ein ziemlicher Bücherwurm ist.\aIch habe letztens dieses Buch für ihn gefunden, als ich drüben in Donalds Dock war.\aKönntest du es ihm bringen? Er ist normalerweise in der Bibliothek." + +QuestScript141_1 = "Oh ja, mit diesem Buch ist meine Sammlung fast vollständig.\aLass mal sehen ...\aÄhm, öh ...\aWo hab ich denn jetzt meine Brille hingelegt?\aIch hatte sie noch, kurz bevor diese Bots mein Gebäude einnahmen.\aErledige Bots, um meine gestohlene Brille zu finden.\aWenn du sie findest, bring sie wieder her zu mir und du bekommst eine Belohnung." + +QuestScript145_1 = "Wie ich sehe, hattest du kein Problem mit dem Toon-Express!\aHör mal, die Bots haben unseren Schwamm gestohlen.\aGeh raus auf die Straße und kämpfe gegen Bots, bis du den Schwamm zurückgeholt hast.\aZur Straße gelangst du so durch einen der Tunnel:" +QuestScript145_2 = "Wenn du unseren Schwamm findest, bring ihn hierher zurück.\aVergiss nicht: Wenn du Gags brauchst, fahr mit dem Toon-Express.\aUnd wenn du deine Lach-Punkte nachfüllen musst, sammle Eistüten auf dem Spielplatz." + +QuestScript150_1 = "Großartige Arbeit!\aDie nächste Aufgabe ist vielleicht für dich alleine zu schwer ..." +QuestScript150_2 = "Suche einen Mitspieler, mit dem du dich anfreunden kannst, und benutze die Schaltfläche 'Neuer Freund'." +QuestScript150_3 = "Wenn du einen Freund gefunden hast, komm wieder her." +QuestScript150_4 = "Manche Aufgaben sind für einen allein zu schwierig!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignorier mich" + +BossCogName = "Vize\npräzident" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "Ihr werdet hiermit zu richtigen %s ernannt. Glückwunsch!" +BossCogDoobersAway = { 's' : "Geh los! Und erledige das Geschäft!" } +BossCogWelcomeToons = "Willkommen, neue Bots!" +BossCogPromoteToons = "Ihr werdet hiermit zu richtigen %s ernannt. Glück ..." +CagedToonInterruptBoss = "He! Hallo! He, ihr da!" +CagedToonRescueQuery = "Seid ihr Toons also gekommen, um mich zu befreien?" +BossCogDiscoverToons = "Was? Toons! Getarnt!" +BossCogAttackToons = "Angriff!!" +CagedToonDrop = [ + "Großartig! Ihr macht ihn fertig!", + "Bleibt ihm auf den Fersen! Er flüchtet!", + "Ihr macht das prima!", + "Fantastisch! Jetzt habt ihr ihn gleich!", + ] +CagedToonPrepareBattleTwo = "Passt auf, er versucht, zu entwischen!\aHelft mir mal alle - kommt hier rauf und haltet ihn auf!" +CagedToonPrepareBattleThree = "Hurra, ich bin fast frei!\aJetzt musst du den Bot-VP direkt angreifen.\aIch hab einen ganzen Stapel Torten, die du nehmen kannst!\aSpring hoch und berühre den Boden meines Käfigs, dann gebe ich dir ein paar Torten.\aDrück die Taste Einfg., um Torten zu werfen, wenn du sie hast!" +BossBattleNeedMorePies = "Du brauchst mehr Torten!" +BossBattleHowToGetPies = "Spring hoch und berühre den Käfig, um Torten zu bekommen." +BossBattleHowToThrowPies = "Drücke die Taste Einfg., um Torten zu werfen!" +CagedToonYippee = "Jippieh!" +CagedToonThankYou = "Es ist toll, frei zu sein!\aDanke für deine Hilfe!\aIch stehe in deiner Schuld.\aWenn du jemals Hilfe im Kampf brauchst, ruf mich einfach!\aKlicke einfach auf die Schaltfläche SOS, um mich zu rufen." +CagedToonPromotion = "\aHör mal - dieser Bot-VP hat deine Beförderungspapiere zurückgelassen.\aIch reiche sie auf dem Weg nach draußen für dich ein, damit du befördert wirst!" +CagedToonLastPromotion = "\aWow, du hast auf deinem Bot-Anzug der Stufe %s erreicht!\aHöher wird kein Bot befördert.\aDu kannst keinen höheren Bot-Anzug mehr erreichen, aber du kannst auf jeden Fall weiter Toons retten!" +CagedToonHPBoost = "\aDu hast viele Toons aus diesem HQ gerettet.\aDer Rat von Toontown hat beschlossen, dir noch einen Lach-Punkt zu geben. Herzlichen Glückwunsch!" +CagedToonMaxed = "\aIch sehe, dass du einen Bot-Anzug der Stufe %s hast. Sehr beeindruckend!\aIm Namen des Rates von Toontown vielen Dank dafür, dass du zurückgekommen bist, um noch mehr Toons zu retten!" +CagedToonGoodbye = "Bis dann!" + + +CagedToonBattleThree = { + 10: "Gut gesprungen, %(toon)s. Hier sind ein paar Torten!", + 11: "Hi, %(toon)s! Nimm dir ein paar Torten!", + 12: "He, %(toon)s! Du hast jetzt ein paar Torten!", + + 20: "He, %(toon)s! Spring zu meinem Käfig hoch und hol dir ein paar Torten zum Werfen!", + 21: "Hi, %(toon)s! Benutze die Strg.-Taste, um hochzuspringen und meinen Käfig zu berühren!", + + 100: "Drücke die Einfg-Taste, um eine Torte zu werfen.", + 101: "Der blaue Kraftmesser zeigt an, wie hoch deine Torte fliegt.", + 102: "Versuche zuerst, eine Torte in sein Fahrgestell zu schmettern, um seinen Antrieb außer Gefecht zu setzen.", + 103: "Warte, bis die Tür aufgeht, und wirf eine Torte direkt hinein.", + 104: "Wenn er benommen ist, wirf sie in sein Gesicht oder gegen seine Brust, um ihn umzuschmeißen!", + 105: "Einen guten Treffer erkennst du daran, dass der Platscher farbig ist.", + 106: "Wenn du einen Toon mit einer Torte triffst, erhält der Toon dadurch einen Lach-Punkt!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +BossElevatorRejectMessage = "Du kannst erst in diesen Aufzug einsteigen, wenn du dir eine Beförderung verdient hast." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Möbel" +PaintingTypeName = "Gemälde" +ClothingTypeName = "Kleidung" +ChatTypeName = "Schnell-Chat\3Wendung" +EmoteTypeName = "Schauspiel\3Unterricht" +PoleTypeName = "Angelrute" +WindowViewTypeName = "Aussicht" +PetTrickTypeName = "Doodle-Training" + +FurnitureYourOldCloset = "dein alter Kleiderschrank" +FurnitureYourOldBank = "deine alte Sparbüchse" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +FurnitureNames = { + 100 : "Sessel", + 105 : "Sessel", + 110 : "Stuhl", + 120 : "Schreibtischsessel", + 130 : "Blockstuhl", + 140 : "Hummerstuhl", + 145 : "Rettungswesten\3Stuhl", + 150 : "Sattelstuhl", + 160 : "Eingeborenenstuhl", + 170 : "Kuchenstuhl", + 200 : "Bett", + 205 : "Bett", + 210 : "Bett", + 220 : "Badewannenbett ", + 230 : "Laubbett", + 240 : "Bootbett", + 250 : "Kaktushängematte", + 260 : "Eiskrembett", + 300 : "Altes Klavier", + 310 : "Orgel mit Pfeifen", + 400 : "Kamin", + 410 : "Kamin", + 420 : "Runder Kamin", + 430 : "Kamin", + 440 : "Apfelkamin", + 500 : "Kleiderschrank", + 502 : "Schrank f. 15 Kleidungsstücken", + 510 : "Kleiderschrank", + 512 : "Schrank f. 15 Kleidungsstücken", + 600 : "Niedrige Lampe", + 610 : "Hohe Lampe", + 620 : "Tischlampe", + 625 : "Tischlampe", + 630 : "Blumenlampe ", + 640 : "Blumenlampe", + 650 : "Quallenlampe", + 660 : "Quallenlampe", + 670 : "Cowboylampe", + 700 : "Polstersessel", + 705 : "Polstersessel", + 710 : "Couch", + 715 : "Couch", + 720 : "Heucouch", + 730 : "Kuchensofa", + 800 : "Schreibtisch", + 810 : "Blockschreibtisch", + 900 : "Schirmständer", + 910 : "Garderobe", + 920 : "Mülltonne", + 930 : "Roter Pilz", + 940 : "Gelber Pilz", + 950 : "Garderobe", + 960 : "Fassständer", + 970 : "Kaktuspflanze", + 980 : "Tipi", + 1000 : "Großer Teppisch", + 1010 : "Runder Teppich", + 1015 : "Runder Teppich", + 1020 : "Kleiner Teppich", + 1030 : "Laubmatte", + 1100 : "Vitrine", + 1110 : "Vitrine", + 1120 : "Hohes Bücherregal", + 1130 : "Niedriges Regal", + 1140 : "Eisbechertruhe", + 1200 : "Beistelltisch", + 1210 : "Kleiner Tisch", + 1215 : "Kleiner Tisch", + 1220 : "Couchtisch", + 1230 : "Couchtisch", + 1240 : "Schnorchlertisch", + 1250 : "Kekstisch", + 1260 : "Nachttisch", + 1300 : "Büchse f. 1000 Jelly Beans", + 1310 : "Büchse f. 2500 Jelly Beans", + 1320 : "Büchse f. 5000 Jelly Beans", + 1330 : "Büchse f. 7500 Jelly Beans", + 1340 : "Büchse f. 10000 Jelly Beans", + 1399 : "Telefon", + 1400 : "Cezanne-Toon", + 1410 : "Blumen", + 1420 : "Moderner Micky", + 1430 : "Rembrandt-Toon", + 1440 : "Toonschaft", + 1441 : "Whistlers Pferd", + 1442 : "Toon-Stern", + 1443 : "Keine Torte", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Fernseher", + 1600 : "Niedrige Vase", + 1610 : "Hohe Vase", + 1620 : "Niedrige Vase", + 1630 : "Hohe Vase", + 1640 : "Niedrige Vase", + 1650 : "Niedrige Vase", + 1660 : "Korallenvase", + 1661 : "Muschelvase", + 1700 : "Popcorn-Wagen", + 1710 : "Marienkäfer", + 1720 : "Springbrunnen", + 1725 : "Waschmaschine", + 1800 : "Aquarium", + 1810 : "Aquarium", + 1900 : "Schwertfisch", + 1910 : "Hammerhai", + 1920 : "Hängende Hörner", + 1930 : "Einfacher Sombrero", + 1940 : "Schicker Sombrero", + 1950 : "Traumfänger", + 1960 : "Hufeisen", + 1970 : "Bisonporträt", + 2000 : "Zuckerschaukel", + 2010 : "Kuchenrutsche", + 3000 : "Bananensplit-Wanne", + 10000 : "Kleiner Kürbis", + 10010 : "Großer Kürbis", + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "Oberteil", + "Oberteil", + "Oberteil", + "Hosen", + "Hosen", + "Rock", + "Hosen", + ) + +ClothingTypeNames = { + 1400 : "Matthias Hemd", + 1401 : "Jessicas Bluse", + 1402 : "Marissas Bluse", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "Tapete", + "Zierleiste", + "Bodenbelag", + "Wandvertäfelung", + "Einfassung", + ) + +WallpaperNames = { + 1000 : "Pergament", + 1100 : "Mailand", + 1200 : "Dover", + 1300 : "Victoria", + 1400 : "Newport", + 1500 : "Idylle", + 1600 : "Harlekin", + 1700 : "Mond", + 1800 : "Sterne", + 1900 : "Blumen", + 2000 : "Garten im Frühling", + 2100 : "Architektonischer Garten", + 2200 : "Renntag", + 2300 : "Treffer!", + 2400 : "7. Himmel", + 2500 : "Kletterranke", + 2600 : "Frühling", + 2700 : "Kokeshi-Puppe", + 2800 : "Sträußchen", + 2900 : "Engelhai", + 3000 : "Blasen", + 3100 : "Blasen", + 3200 : "Go-Fisch", + 3300 : "Stoppfisch", + 3400 : "Seepferdchen", + 3500 : "Meeresmuscheln", + 3600 : "Unterwasser", + 3700 : "Stiefel", + 3800 : "Kaktus", + 3900 : "Cowboyhut", + 10100 : "Katzen", + 10200 : "Fledermäuse", + 11000 : "Schneeflocken", + 11100 : "Stechpalmenblatt", + 11200 : "Schneemann", + 13000 : "Kleeblatt", + 13100 : "Kleeblatt", + 13200 : "Regenbogen", + 13300 : "Kleeblatt", + } + +FlooringNames = { + 1000 : "Hartholzboden", + 1010 : "Teppich", + 1020 : "Rhombische Fliese", + 1030 : "Rhombische Fliese", + 1040 : "Gras", + 1050 : "Beige Ziegel", + 1060 : "Rote Ziegel", + 1070 : "Quadratische Fliese", + 1080 : "Stein", + 1090 : "Plankenweg", + 1100 : "Schotterstraße", + 1110 : "Holzplatte", + 1120 : "Fliese", + 1130 : "Wabe", + 1140 : "Wasser", + 1150 : "Strandplatte", + 1160 : "Strandplatte", + 1170 : "Strandplatte", + 1180 : "Strandplatte", + 1190 : "Sand", + 10000 : "Eiswürfel", + 10010 : "Iglu", + 11000 : "Kleeblatt", + 11010 : "Kleeblatt", + } + +MouldingNames = { + 1000 : "Knorrig", + 1010 : "Angestrichen", + 1020 : "Gebiss", + 1030 : "Blumen ", + 1040 : "Blumen", + 1050 : "Marienkäfer", + } + +WainscotingNames = { + 1000 : "Angestrichen", + 1010 : "Holzpaneel", + 1020 : "Holz", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Großer Garten", + 20 : "Wilder Garten", + 30 : "Griechischer Garten", + 40 : "Stadtlandschaft", + 50 : "Wilder Westen", + 60 : "Unter Wasser", + 70 : "Tropische Insel", + 80 : "Sternenhimmel", + 90 : "Tiki-Pool", + 100 : "Eisige Grenze", + 110 : "Farmland", + 120 : "Eingeborenenlager", + 130 : "Hauptstraße", + } + +# don't translate yet +NewCatalogNotify = "Bei deinem Telefon gibt es neue Artikel zu bestellen!" +NewDeliveryNotify = "Eine neue Lieferung ist in deinem Briefkasten angekommen!" +CatalogNotifyFirstCatalog = "Dein erster Kuhtalog ist eingetroffen! Du kannst damit neue Sachen für dich oder dein Haus bestellen." +CatalogNotifyNewCatalog = "Dein Kuhtalog Nr. %s ist eingetroffen! Du kannst jetzt zu deinem Telefon gehen und Artikel aus diesem Kuhtalog bestellen." +CatalogNotifyNewCatalogNewDelivery = "Eine neue Lieferung ist in deinem Briefkasten angekommen! Und dein Kuhtalog Nr. %s ist auch eingetroffen!" +CatalogNotifyNewDelivery = "Eine neue Lieferung ist in deinem Briefkasten angekommen!" +CatalogNotifyNewCatalogOldDelivery = "Dein Kuhtalog Nr. %s ist eingetroffen und es warten immer noch Artikel in deinem Briefkasten!" +CatalogNotifyOldDelivery = "In deinem Briefkasten warten immer noch ein paar Artikel darauf, dass du sie abholst!" +CatalogNotifyInstructions = "Klicke auf der Stadtplanseite in deinem Sticker-Buch auf die Schaltfläche 'Nach Hause' und geh dann zum Telefon in deinem Haus." +CatalogNewDeliveryButton = "Neue\nLieferung!" +CatalogNewCatalogButton = "Neuer\nKuhtalog" +CatalogSaleItem = "Ausverkauf! " + +# don't translate yet +DistributedMailboxEmpty = "Dein Briefkasten ist derzeit leer. Komm wieder her, um nach Lieferungen zu sehen, wenn du von deinem Telefon aus eine Bestellung aufgegeben hast!" +DistributedMailboxWaiting = "Dein Briefkasten ist derzeit noch leer, aber das von dir bestellte Paket ist unterwegs. Schau später nochmal nach!" +DistributedMailboxReady = "Deine Bestellung ist angekommen!" +DistributedMailboxNotOwner = "Entschuldige, das ist nicht dein Briefkasten." +DistributedPhoneEmpty = "Du kannst von jedem Telefon aus spezielle Artikel für dich und dein Haus bestellen. Im Laufe der Zeit werden neue Artikel zur Bestellung angeboten.\n\nIm Moment gibt es für dich keine Artikel zu bestellen, aber schau später nochmal nach!" + +# don't translate yet +Clarabelle = "Klarabella" +MailboxExitButton = "Briefkasten schließen" +MailboxAcceptButton = "Diesen Artikel nehmen" +MailboxOneItem = "Dein Briefkasten enthält 1 Artikel." +MailboxNumberOfItems = "Dein Briefkasten enthält %s Artikel." +MailboxGettingItem = "%s wird aus dem Briefkasten genommen." +MailboxItemNext = "Nächster\nArtikel" +MailboxItemPrev = "Vorheriger\nArtikel" +CatalogCurrency = "Jelly Beans" +CatalogHangUp = "Auflegen" +CatalogNew = "NEU" +CatalogBackorder = "LIEFERRÜCKSTAND" +CatalogPagePrefix = "Seite" +CatalogGreeting = "Hallo! Danke für deinen Anruf bei Klarabellas Kuhtalog. Kann ich dir helfen?" +CatalogGoodbyeList = ["Wiederhören!", + "Bis zum nächsten Mal!", + "Vielen Dank für deinen Anruf!", + "OK, tschüss dann!", + "Tschüss!", + ] +CatalogHelpText1 = "Blättere um, dann kommst du zu den Verkaufsartikeln." +CatalogSeriesLabel = "Serie %s" +CatalogPurchaseItemAvailable = "Herzlichen Glückwunsch zum Neuerwerb! Du kannst ihn sofort benutzen." +CatalogPurchaseItemOnOrder = "Herzlichen Glückwunsch! Die gekauften Artikel werden demnächst an deinen Briefkasten geliefert." +CatalogAnythingElse = "Kann ich noch etwas für dich tun?" +CatalogPurchaseClosetFull = "Dein Schrank ist voll. Du kannst diesen Artikel trotzdem kaufen, aber dann wirst du etwas aus deinem Schrank entfernen müssen, damit er dann hineinpasst.\n\nMöchtest du diesen Artikel immer noch kaufen? " +CatalogAcceptClosetFull = "Dein Schrank ist voll. Du musst hinein gehen und etwas aus deinem Schrank entfernen, damit dieser Artikel Platz hat. Erst dann kannst du ihn aus dem Briefkasten holen." +CatalogAcceptShirt = "Du trägst jetzt dein neues Oberteil. Was du vorher anhattest, wurde in deinen Schrank verschoben." +CatalogAcceptShorts = "Du trägst jetzt deine neuen Hosen. Was du vorher anhattest, wurde in deinen Schrank verschoben." +CatalogAcceptSkirt = "Du trägst jetzt deinen neuen Rock. Was du vorher anhattest, wurde in deinen Schrank verschoben." +CatalogAcceptPole = "Mit deiner neuen Angelrute kannst du jetzt größere Fische angeln gehen!" +CatalogAcceptPoleUnneeded = "Du hast schon eine bessere Angelrute als diese hier! " +CatalogPurchaseHouseFull = "Dein Haus ist voll. Du kannst diesen neuen Artikel trotzdem kaufen, aber dann wirst du etwas aus deinem Haus entfernen müssen, damit er dann hineinpasst.\n\nMöchtest du diesen Artikel immer noch kaufen?" +CatalogAcceptHouseFull = "Dein Haus ist voll. Du musst hinein gehen und etwas aus deinem Haus entfernen, damit dieser Artikel Platz hat. Erst dann kannst du ihn aus dem Briefkasten holen." +CatalogAcceptInAttic = "Dein neuer Artikel ist jetzt auf deinem Dachboden. Du kannst ihn ins Haus holen, indem du hinein gehst und auf die Schaltfläche 'Möbel rücken' klickst." +CatalogAcceptInAtticP = "Deine neuen Artikel sind jetzt auf deinem Dachboden. Du kannst sie in deinem Haus aufstellen, indem du hinein gehst und auf die Schaltfläche 'Möbel rücken' klickst." +CatalogPurchaseMailboxFull = "Dein Briefkasten ist voll! Du kannst diesen Artikel erst kaufen, wenn du ein paar Artikel aus deinem Briefkasten herausgenommen hast." +CatalogPurchaseOnOrderListFull = "Du hast zur Zeit zu viele Bestellungen auf deiner Liste. Du kannst erst dann weitere Artikel bestellen, wenn einige der bereits bestellten eingetroffen sind." +CatalogPurchaseGeneralError = "Der Artikel konnte wegen eines spielinternen Fehlers nicht gekauft werden: Fehlercode %s." +CatalogAcceptGeneralError = "Der Artikel konnte wegen eines spielinternen Fehlers nicht aus deinem Briefkasten entfernt werden: Fehlercode %s." + +# don't translate yet +HDMoveFurnitureButton = "Möbel\nrücken" +HDStopMoveFurnitureButton = "Rücken\nfertig" +HDAtticPickerLabel = "Auf dem Dachboden" +HDInRoomPickerLabel = "Im Zimmer" +HDInTrashPickerLabel = "Im Müll" +HDDeletePickerLabel = "Löschen?" +HDInAtticLabel = "Dachboden" +HDInRoomLabel = "Zimmer" +HDInTrashLabel = "Müll" +HDToAtticLabel = "Auf den Dachboden\nstellen" +HDMoveLabel = "Rücken" +HDRotateCWLabel = "Drehung rechts" +HDRotateCCWLabel = "Drehung links" +HDReturnVerify = "Diesen Artikel wieder auf den Dachboden stellen?" +HDReturnFromTrashVerify = "Diesen Artikel aus dem Müll wieder auf den Dachboden stellen?" +HDDeleteItem = "OK anklicken, um diesen Artikel in den Müll zu befördern, oder Abbrechen, um ihn zu behalten." +HDNonDeletableItem = "Du kannst diese Teile nicht löschen!" +HDNonDeletableBank = "Du kannst deine Sparbüchse nicht löschen!" +HDNonDeletableCloset = "Du kannst deinen Kleiderschrank nicht löschen!" +HDNonDeletablePhone = "Du kannst dein Telefon nicht löschen!" +HDNonDeletableNotOwner = "Du kannst %s's Sachen nicht löschen!" +HDHouseFull = "Dein Haus ist voll. Du musst noch etwas aus deinem Haus oder von deinem Dachboden löschen, bevor du diesen Artikel wieder aus dem Müll holen kannst." + +HDHelpDict = { + "DoneMoving" : "Zimmer einrichten beenden.", + "Attic" : "Liste der Gegenstände auf dem Dachboden anzeigen. Auf dem Dachboden werden Gegenstände aufbewährt, die nicht in deinem Zimmer sind.", + "Room" : "Liste der Gegenstände im Zimmer anzeigen. Nützlich, um verlorene Gegenstände zu finden.", + "Trash" : "Gegenstände im Müll anzeigen. Die ältesten Gegenstände werden nach einer Weile oder wenn der Müll überquillt, gelöscht.", + "ZoomIn" : "Zimmeransicht vergrößern.", + "ZoomOut" : "Zimmeransicht verkleinern.", + "SendToAttic" : "Das aktuelle Möbelstück zum Lagern auf den Dachboden schicken.", + "RotateLeft" : "Nach links.", + "RotateRight" : "Nach rechts.", + "DeleteEnter" : "Zum Löschen-Modus wechseln.", + "DeleteExit" : "Löschen-Modus verlassen.", + "FurnitureItemPanelDelete" : "%s in den Müll werfen.", + "FurnitureItemPanelAttic" : "%s in das Zimmer stellen.", + "FurnitureItemPanelRoom" : "%s wieder auf den Dachboden stellen.", + "FurnitureItemPanelTrash" : "%s wieder auf den Dachboden stellen.", + } + + + +# don't translate yet +MessagePickerTitle = "Du hast zu viele Redewendungen. Um \n\"%s\"\n zu kaufen, musst du eine zum Entfernen auswählen:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Bist du sicher, dass du \"%s\" aus deinem Schnell-Chat-Menü entfernen möchtest?" + + +# don't translate yet +CatalogBuyText = "Kaufen" +CatalogOnOrderText = "Bestellt" +CatalogPurchasedText = "Schon\ngekauft" +CatalogPurchasedMaxText = "Schon\nMaximum gekauft" +CatalogVerifyPurchase = "%(item)s für %(price)s Jelly Beans kaufen?" +CatalogOnlyOnePurchase = "Du kannst nur jeweils einen dieser Artikel haben. Wenn du diesen kaufst, ersetzt er %(old)s.\n\nBist du sicher, dass du %(item)s für %(price)s Jelly Beans kaufen willst?" + +# don't translate yet +CatalogExitButtonText = "Auflegen" +CatalogCurrentButtonText = "Zu aktuellen Artikeln" +CatalogPastButtonText = "Zu früheren Artikeln" + +TutorialHQOfficerName = "Mitarbeiter Harry" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Einweiser-Ede", + 999 : "Toon-Schneider", + 1000 : "Toontown-Zentrale", + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Bankier Bob", + 2003 : "Professor Peter", + 2004 : "Schneiderin Flicka", + 2005 : "Bibliothekar Berti", + 2006 : "Angestellter Angelo", + 2011 : "Angestellte Angela", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 2013 : "Angestellter Bimmel", + 2014 : "Angestellte Bammel", + 2015 : "Angestellter Bummel", + + # Silly Street + 2101 : "Zahnarzt Zacharias", + 2102 : "Sheriff Sherry", + 2103 : "Nies-Kitty", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Kanarienvogel Kohlengrube", + 2109 : "Barbera Blubber", + 2110 : "Eddi Kett", + 2111 : "Dancing Diego", + 2112 : "Dr. Hein", + 2113 : "Rollo der Erstaunliche", + 2114 : "Drees Rum", + 2115 : "Sheila Scherenschnitt", + 2116 : "Haumichblau MacDougal", + 2117 : "Mutter Eklig", + 2118 : "Kaspar Kasper", + 2119 : "Hanni Haha", + 2120 : "Professor Pünktchen", + 2121 : "Madam Gicker", + 2122 : "Harry Afferei", + 2123 : "Spamonia Biggels", + 2124 : "T.P. Rolle", + 2125 : "Paul Felz", + 2126 : "Professor Lachsalv", + 2127 : "Hellwig Heller", + 2128 : "Bert Bekloppt", + 2129 : "Frank Furter", + 2130 : "Schöna Spötter", + 2131 : "Federa Wedel", + 2132 : "Bartel Dös", + 2133 : "Dr. B. Geistert", + 2134 : "Stille Simone", + 2135 : "Maria", + 2136 : "Pit Prust", + 2137 : "Heikyung Glücklich", + 2138 : "Maldon", + 2139 : "Thoralf Tropf", + 2140 : "Fischer Billy", + + # Loopy Lane + 2201 : "Postmeister Peter", + 2202 : "Mira Spaß", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Willy Weisacker", + 2208 : "Kleb Endreim", + 2209 : "Chlodewig Gluckser", + 2210 : "Tee Hee", + 2211 : "Sally Spuck", + 2212 : "Sebastian Seltsam", + 2213 : "Alla Rad", + 2214 : "Felix Fleck", + 2215 : "Sid Selters", + 2216 : "Verigissmein Machsgut", + 2217 : "Hainer Fressdich", + 2218 : "Isja Lustig", + 2219 : "Chefkoch Schafskopf", + 2220 : "Edwin Eisenmann", + 2221 : "Hanna Haft", + 2222 : "Kurtzi Schluss", + 2223 : "Zelina Zerfetzdich", + 2224 : "Qualm-Ede", + 2225 : "Fischer Droopy", + + # Punchline Place + 2301 : "Dr. Verdreht", + 2302 : "Professor Krümmdich", + 2303 : "Schwester Stefanie", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Gas", + 2309 : "Blau Fleck", + 2311 : "Franz Schwellader", + 2312 : "Dr. Sensibel", + 2313 : "Lucy Hemdenklecks", + 2314 : "Schleuder-Ned", + 2315 : "Kauma Bröckchen", + 2316 : "Cindy Streusel", + 2318 : "Tony Maroni", + 2319 : "Beppo", + 2320 : "Alfredo Hartgekocht", + 2321 : "Fischer Punchy", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Angestellter Willi", + 1002 : "Angestellter Billy", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Jacko Buxehude", + # NPCFisherman + 1008 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 1009 : "Angestellter Kleff", + 1010 : "Angestellte Schnurr", + 1011 : "Angestellter Pieps", + + # Barnacle Blvd. + 1101 : "Kalle Kiel", + 1102 : "Käpt'n Karl", + 1103 : "Frank Fischtran", + 1104 : "Doktor Weitblick", + 1105 : "Admiral Hook", + 1106 : "Frau Bleiche", + 1107 : "Herr Robiks", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gary Gluckgluck", + 1113 : "Bärbel Backbord", + 1114 : "Charlie Schluck", + 1115 : "Quiselda Quittung, RA", + 1116 : "Bernikel-Bessie", + 1117 : "Käpt'n Igitt", + 1118 : "Hacker Haarig", + 1121 : "Linde Rinde", + 1122 : "Seebär Stan", + 1123 : "Elektra Egel", + 1124 : "Schlappo Docknagel", + 1125 : "Eileen Überbord", + 1126 : "Fischer Barney", + + # Seaweed Street + 1201 : "Bernikel-Barbara", + 1202 : "Adalbert", + 1203 : "Achim", + 1204 : "Sturmi See", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professor Planke", + 1210 : "Geng Wei", + 1211 : "Wind Beutel", + 1212 : "Zeko Zungenbrenner", + 1213 : "Dante Delfin", + 1214 : "Stürmische Kate", + 1215 : "Unda Wassa", + 1216 : "Rod Rolle", + 1217 : "Meerlinde Tang", + 1218 : "Stiller Tim", + 1219 : "G. Strandet", + 1220 : "Karla Kanal", + 1221 : "Blasius McKee", + 1222 : "Chef Ahoi", + 1223 : "Cal Kalmar", + 1224 : "Aaltje Ritter", + 1225 : "Lobgott Lenzpumpe", + 1226 : "Hauke Ruck", + 1227 : "Cora Llenriff", + 1228 : "Fischer Reed", + + # Lighthouse Lane + 1301 : "Alice", + 1302 : "Mark", + 1303 : "Gerts", + 1304 : "Swetlana", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Gischt", + 1310 : "Max Made", + 1311 : "Florentina Schwipps", + 1312 : "Elmar Kiel", + 1313 : "Willie Woge", + 1314 : "Ralph Rostig", + 1315 : "Doktor Drift", + 1316 : "Wilma Wehr", + 1317 : "Paula Proporz", + 1318 : "Stephan Schlauchboot", + 1319 : "Trutz Trockendock", + 1320 : "Ted Stillsee", + 1321 : "Dina Docker", + 1322 : "Anka Kette", + 1323 : "Ned Stinktopf", + 1324 : "Perlchen Taucher", + 1325 : "Nobu Netz", + 1326 : "Felicia Chips", + 1327 : "Coralie Platsch", + 1328 : "Fred Flunder", + 1329 : "Shelly Seetang", + 1330 : "Porter Hohl", + 1331 : "Rudi Ruder", + 1332 : "Fischer Shane", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Betty Friert", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Angestellter Lenny", + 3007 : "Angestellte Penny", + 3008 : "Kord Hose", + # NPCFisherman + 3009 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 3010 : "Angestellter Hoppel", + 3011 : "Angestellte Poppel", + 3012 : "Angestellter Moppel", + + # Walrus Way + 3101 : "Herr Krug", + 3102 : "Tante Frostbeule", + 3103 : "Fred", + 3104 : "Huta", + 3105 : "Feinfrost-Freddy", + 3106 : "Gert Gänseburger", + 3107 : "Patty Passport", + 3108 : "Schlitten-Schorsch", + 3109 : "Kate", + 3110 : "Hähnchenjung", + 3111 : "Großschnauz Gandalf", + 3112 : "Lil Altmann", + 3113 : "Hysterie-Harry", + 3114 : "Gerald der Gefährliche", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Gruselkurt", + 3120 : "Mike Mück", + 3121 : "Joe Shocker", + 3122 : "Rudi Rödel", + 3123 : "Frank Lloyd Ice", + 3124 : "Egon Eisberg", + 3125 : "Oberst Oberlecker", + 3126 : "Colestra Belle", + 3127 : "Ichvall Duvällst", + 3128 : "George Klebrig", + 3129 : "Bäckers Brigitte", + 3130 : "Sandy", + 3131 : "Lorenzo Faulus", + 3132 : "Brennda", + 3133 : "Dr. Stuntbild", + 3134 : "Salomon Salonlöwe", + 3135 : "Nele Durchweicht", + 3136 : "Gilda Glücklich", + 3137 : "Herr Frier", + 3138 : "Chefkoch Pfuschsuppe", + 3139 : "Oma Eisstrumpf", + 3140 : "Fischerin Lucille", + + # Sleet Street + 3201 : "Tante Arktis", + 3202 : "Schütti", + 3203 : "Walter", + 3204 : "Dr. K.-Ann Gutsehen", + 3205 : "Huckelberry Schlitzauge", + 3206 : "Vitalia Wucht", + 3207 : "Dr. Mummelgesicht", + 3208 : "Felix Mürrisch", + 3209 : "Guido Kichererbs", + 3210 : "Halbaffen-Sam", + 3211 : "Fanny Friert", + 3212 : "Fred Frost", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Schwitze-Peter", + 3218 : "Blanka Blau", + 3219 : "Tom Tandemfrost", + 3220 : "Herr Schneuz", + 3221 : "Nell Schnee", + 3222 : "Mindy Kaltwind", + 3223 : "Chappy", + 3224 : "Frieda Frostbiss", + 3225 : "Glatt Eis", + 3226 : "Nico Laus", + 3227 : "Sonny Strahl", + 3228 : "Wynn Stoß", + 3229 : "Hernie Gurt", + 3230 : "Glatzen-Günthi", + 3231 : "Eisbrecher", + 3232 : "Fischer Albert", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Molly Molloy", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Angestellte Fa", + 4007 : "Angestellter Ray", + 4008 : "Schneiderin Harmony", + # NPCFisherman + 4009 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 4010 : "Angestellter Chris", + 4011 : "Angestellter Max", + 4012 : "Angestellte Mädchen für Alles", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr. Karies", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Quint", + 4109 : "Carlos", + 4110 : "Metra Gnom", + 4111 : "Tom Summ", + 4112 : "Tina", + 4113 : "Madam Benimm", + 4114 : "Der Verstimmte Erik", + 4115 : "Barbara Sevilla", + 4116 : "Piccolo", + 4117 : "Mandy Liehne", + 4118 : "Toilettenwart Tobi", + 4119 : "Moe Zart", + 4120 : "Viola Polster", + 4121 : "Gis Dur", + 4122 : "Minzie Bass", + 4123 : "Blitz-Ted", + 4124 : "Einar Tönig", + 4125 : "Melodie Weber", + 4126 : "Mel Canto", + 4127 : "Fulminante Füße", + 4128 : "Luciano Knüller", + 4129 : "Zenzi Zwiefacher", + 4130 : "Metal-Mike", + 4131 : "Abraham Armoire", + 4132 : "Louise Louise", + 4133 : "Scott Poplin", + 4134 : "Disco-Dave", + 4135 : "Beinhart Singvogel", + 4136 : "Patty Pause", + 4137 : "Tony Taub", + 4138 : "Violino Schlüssel", + 4139 : "Harmony Süßlich", + 4140 : "Ned Plump", + 4141 : "Fischer Jed", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Holz-Michel", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Hediheda", + 4209 : "Alma Abgedroschen", + 4211 : "Carl Concerto", + 4212 : "Detektiv Klagelied", + 4213 : "Tizia Tinnitus", + 4214 : "Fina Fußangel", + 4215 : "Veit Vibrato", + 4216 : "Gummy Pfeiffer", + 4217 : "Anton Schönherr", + 4218 : "Willma Pusten", + 4219 : "Abi Andante", + 4220 : "Kurt Finger", + 4221 : "Michi Madrigal", + 4222 : "Johann Doon", + 4223 : "Terry Taktstock", + 4224 : "Dschungel-Jim", + 4225 : "Zewa Zisch", + 4226 : "Herta Halslanger", + 4227 : "Die Stille Fancesca", + 4228 : "Susi Stimmt", + 4229 : "Belinda Blöd", + 4230 : "Julius Joculator", + 4231 : "Karla Quetschkommode", + 4232 : "Hedi Musi", + 4233 : "Karli Karpfen", + 4234 : "Johann Sträußchen", + 4235 : "Fischer Larry", + + # Tenor Terrace + 4301 : "Yuki", + 4302 : "Anna", + 4303 : "Leo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tabitha", + 4309 : "Marshall", + 4310 : "Martha Mopp", + 4311 : "Shanty Sänger", + 4312 : "Martin Satch", + 4313 : "Tauber Rudolf", + 4314 : "Dana Gander", + 4315 : "Undine Uhrwerk", + 4316 : "Tim Tango", + 4317 : "Dicky Zehe", + 4318 : "Bob Marlin", + 4319 : "Rinky Dink", + 4320 : "Cammy Coda", + 4321 : "Laurel Laute", + 4322 : "Randy Rhythmus", + 4323 : "Hanna Hogg", + 4324 : "Elli", + 4325 : "Bankier Bert", + 4326 : "Brenda Brett", + 4327 : "Flim Flam", + 4328 : "Wagner", + 4329 : "Nele Prompter", + 4330 : "Quentin", + 4331 : "Fabulo Costello", + 4332 : "Ziggy", + 4333 : "Harry", + 4334 : "Fast Freddie", + 4335 : "Fischer Walden", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Angestellte Anemone", + 5006 : "Angestellter Camillo", + 5007 : "Rosa Blüte", + # NPCFisherman + 5008 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 5009 : "Angestellte Ann Genehm", + 5010 : "Angestellter Tom A. Te", + 5011 : "Angestellter Johannes Beere", + + # Elm Street + 5101 : "Artie", + 5102 : "Susan", + 5103 : "Volker", + 5104 : "Schmetterding", + 5105 : "Jack", + 5106 : "Barbier Björn", + 5107 : "Postbote Felipe", + 5108 : "Gastwirtin Gastrinde", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr. Keim", + 5114 : "Welk", + 5115 : "Schleia Kraut", + 5116 : "Werner Vegetaro", + 5117 : "Früchtchen", + 5118 : "Pop Corn", + 5119 : "Grizzly Beer", + 5120 : "Gopher", + 5121 : "Erika Erbsschot", + 5122 : "Oswald Haufen", + 5123 : "Edda Ecker", + 5124 : "Pops Wund", + 5125 : "Pelikano Platsch", + 5126 : "Madam Mund", + 5127 : "Polly Pollen", + 5128 : "Susanna Setzling", + 5129 : "Fischerin Sally", + + # Maple Street + 5201 : "Hake", + 5202 : "Erika", + 5203 : "Lisa", + 5204 : "Bert", + 5205 : "Leopold Löwenzahn", + 5206 : "Rebert Grün", + 5207 : "Sofie Spritzer", + 5208 : "Silke Such", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Big Bauersmann", + 5214 : "Jukenda Ausschlag", + 5215 : "Karola Knolle", + 5216 : "Stinke-Jim", + 5217 : "Greg Gründaumen", + 5218 : "Rocky Rhododendron", + 5219 : "Lars Bizeps", + 5220 : "Lauf-Mascha", + 5221 : "Rosa Flamingo", + 5222 : "Heul-Suse", + 5223 : "Pfützen-Paule", + 5224 : "Onkel Landmann", + 5225 : "Pamela Pfanda", + 5226 : "Torf Moss", + 5227 : "Begonia Buddelbier", + 5228 : "Grabo Schmutzfink", + 5229 : "Fischerin Lily", + + # Oak street + 5301 : "Mitarbeiter der Zentrale", + 5302 : "Mitarbeiter der Zentrale", + 5303 : "Mitarbeiter der Zentrale", + 5304 : "Mitarbeiter der Zentrale", + 5305 : "Crystal", + 5306 : "B. Last", + 5307 : "Tiffany Lache", + 5308 : "Nelly Nörgel", + 5309 : "Ru Kola", + 5310 : "Timotheus", + 5311 : "Richterin McIntosh", + 5312 : "Bienhart", + 5313 : "Trainer Bemoost", + 5314 : "A. Meisenhügel", + 5315 : "Onkel Hollunder", + 5316 : "Onkel Keim", + 5317 : "Detektivin Lima", + 5318 : "Cäsar", + 5319 : "Rose", + 5320 : "April", + 5321 : "Professor Tausendschön", + 5322 : "Fischerin Rose", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Susan Dämmerts", + 9002 : "Tom Tiefschlaf", + 9003 : "Dennis Dösig", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Angestellte Jill", + 9009 : "Angestellter Phil", + 9010 : "Abigail Abgetragen", + # NPCFisherman + 9011 : "Tierhandlungs-\nAngestellter", + # NPCPetClerks + 9012 : "Angestellte Sarah Bande", + 9013 : "Angestellte Anne Mone", + 9014 : "Angestellter Steve Mütterchen", + + # Lullaby Lane + 9101 : "Ed", + 9102 : "Big Mama", + 9103 : "PJ", + 9104 : "Süße Schlummerei", + 9105 : "Professor Gähn", + 9106 : "Maxim", + 9107 : "Kuschel", + 9108 : "Zwinky Zwirbel", + 9109 : "Traum-Daphne", + 9110 : "Kathy Minz", + 9111 : "Feler Suche", + 9112 : "Wiegenlied-Wiegand", + 9113 : "Uri Uhrwerk", + 9114 : "Lida Schatten", + 9115 : "Babyface MacDougal", + 9116 : "Der Mit Den Schafen Tanzt", + 9117 : "Sissy Feierabend", + 9118 : "Klara Nacht", + 9119 : "Steini", + 9120 : "Sarah Schlummer", + 9121 : "Zuzanna Zukurzdeck", + 9122 : "Dickie Augen", + 9123 : "Teddy Behr", + 9124 : "Nina Nachtlicht", + 9125 : "Dr. Unscharf", + 9126 : "Hella Wach", + 9127 : "Betty Bettdeck", + 9128 : "Hartmut Hammer", + 9129 : "Bertha Bettschwein", + 9130 : "Nathaniel Nachttopf", + 9131 : "Susan Siesta", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Fischer Taylor", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Toontown Rathaus", ""), + 2514 : ("Toontown Bank", ""), + 2516 : ("Toontown Schule", ""), + 2518 : ("Bibliothek Toontown", ""), + 2519 : ("Gag-Laden", ""), + 2520 : ("Toontown Zentrale", ""), + 2521 : ("Bekleidungsgeschäft", ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Zahnklempnerei Breites Lächeln", ""), + 2602 : ("", ""), + 2603 : ("Eingleis-Bergbau", ""), + 2604 : ("Quatschwasch & Reinigung", ""), + 2605 : ("Toontown Schilderfabrik", ""), + 2606 : ("", ""), + 2607 : ("Springende Bohnen", ""), + 2610 : ("Dr. Hein Faltspinsel", ""), + 2611 : ("", ""), + 2616 : ("Kostümverleih Falschbart", ""), + 2617 : ("Verrückte Stunts", ""), + 2618 : ("Drehrumbum", ""), + 2621 : ("Papierflugzeuge", ""), + 2624 : ("Lustige Rowdys", ""), + 2625 : ("Haus des Schlechten Geschmacks", ""), + 2626 : ("Kaspars Witzreparaturen", ""), + 2629 : ("Der Lachplatz", ""), + 2632 : ("Clownschule", ""), + 2633 : ("Hehe-Tee-Laden", ""), + 2638 : ("Toontown Spielhaus", ""), + 2639 : ("Affereien", ""), + 2643 : ("Dosenflaschen", ""), + 2644 : ("Unpraktische Witze", ""), + 2649 : ("Spiel- und Spaßladen", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Lachlektionen", ""), + 2655 : ("Spielgeld-Bausparkasse", ""), + 2656 : ("Gebrauchte Clownautos", ""), + 2657 : ("Franks Faxen", ""), + 2659 : ("Freude Schöner Spötterfunken", ""), + 2660 : ("Kitzelmaschinen", ""), + 2661 : ("Dösbartel", ""), + 2662 : ("Dr. B. Geistert", ""), + 2663 : ("Toontown Cinerama", ""), + 2664 : ("Die Lustigen Mimen", ""), + 2665 : ("Reisebüro Hin & Weg", ""), + 2666 : ("Lachtankstelle", ""), + 2667 : ("Glückliche Zeiten", ""), + 2669 : ("Maldons Hohle Ballone", ""), + 2670 : ("Suppengabeln", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Multiplex-Kino", ""), + 2705 : ("Weisackers Krachmacher", ""), + 2708 : ("Schleimleim", ""), + 2711 : ("Toontown Postamt", ""), + 2712 : ("Gluckscafé", ""), + 2713 : ("Café Lachtniezu", ""), + 2714 : ("Spinners Cineplex", ""), + 2716 : ("Suppen und Beknacktes", ""), + 2717 : ("Flaschendosen", ""), + 2720 : ("Autoreparaturen Zerschmeißdich", ""), + 2725 : ("", ""), + 2727 : ("Seltersflaschen und -dosen", ""), + 2728 : ("Verschwindcreme", ""), + 2729 : ("14-Karat-Goldfisch", ""), + 2730 : ("Nachrichten zum Aufrichten", ""), + 2731 : ("", ""), + 2732 : ("Spaghetti und Blödhammel", ""), + 2733 : ("Gußeisendrachen", ""), + 2734 : ("Saugnäpfe und -teller", ""), + 2735 : ("Die Kawummerei", ""), + 2739 : ("Kaputtlacher-Flickerei", ""), + 2740 : ("Gebrauchte Feuerwerkskörper", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("Die fetzige Reinigung", ""), + 2744 : ("", ""), + 2747 : ("Sichtbare Tinte", ""), + 2748 : ("Du machst mir Spaß!", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Sofa-Rubbeldiekatz-Kissen", ""), + 2802 : ("Aufblasbare Abbruchkugeln", ""), + 2803 : ("Karneval Kid", ""), + 2804 : ("Dr. Verdreht, Chiropraktiker", ""), + 2805 : ("", ""), + 2809 : ("Sportstudio Schwitzeria", ""), + 2814 : ("Toontown Theater", ""), + 2818 : ("Die Fliegende Torte", ""), + 2821 : ("", ""), + 2822 : ("Gummiadler-Sandwiches", ""), + 2823 : ("Eiskrem zum fröhlichen Eisbechern", ""), + 2824 : ("Kinopalast Sparwitz", ""), + 2829 : ("Blödes Geschwafel", ""), + 2830 : ("Beppos Blödeleien", ""), + 2831 : ("Professor Krümmdichs Lachhaus", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("Lachanfall Notaufnahme", ""), + 2836 : ("", ""), + 2837 : ("Hartmuts Haha-Seminare", ""), + 2839 : ("Ungenießbare Pasta", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Gag-Laden", ""), + 1507 : ("Toontown Zentrale", ""), + 1508 : ("Bekleidungsgeschäft", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Gebrauchte Rettungswesten", ""), + 1604 : ("Regenjacken-Trockenreinigung", ""), + 1606 : ("Hooks Uhrenreparaturen", ""), + 1608 : ("Steuerbord & Backröhre", ""), + 1609 : ("Jedermanns Köder", ""), + 1612 : ("Heller und Kreuzer Bank", ""), + 1613 : ("Quitt Pro Quo, Rechtsanwälte", ""), + 1614 : ("Streich die Nägel Boutique", ""), + 1615 : ("Yacht nix, Leute!", ""), + 1616 : ("Schwarzbarts Schönheitssalon", ""), + 1617 : ("Land-In-Sicht-Optik", ""), + 1619 : ("Baumchirurgie & Piratung", ""), + 1620 : ("Von Bug bis Heck", ""), + 1621 : ("Verpeildeck-Sporthalle", ""), + 1622 : ("Schalter und Strömlinge Elektrogeschäft", ""), + 1624 : ("Hechtledersohlen-Schnellreparatur", ""), + 1626 : ("Edelfeine Bekleidung für festliche Anlässe", ""), + 1627 : ("Kalle Kiels Kaufrausch-Kompasshaus", ""), + 1628 : ("Kaviarstimmer", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Kindergarten ", ""), + 1703 : ("China-Imbiss Wok 8, Achtern Strom", ""), + 1705 : ("Gaumensegelverkauf", ""), + 1706 : ("Erdnussbutter und Quallengelee", ""), + 1707 : ("Geschenkideen mit Riff", ""), + 1709 : ("Karavellbonbons und Marzipan", ""), + 1710 : ("Bernikel-Billigschnäppchen", ""), + 1711 : ("Tiefsee-Kneipe", ""), + 1712 : ("Sporthalle Volle Kraft", ""), + 1713 : ("Adalberts Smarter Seekarten-Markt", ""), + 1714 : ("Hol-Sie-Inn", ""), + 1716 : ("Meerjungfrau-Badebekleidung", ""), + 1717 : ("Sei Stiller Ozean-Ansichten", ""), + 1718 : ("Taxiservice Gestrandet", ""), + 1719 : ("Ducks Stilles Wasser GmbH", ""), + 1720 : ("Angelruten-Rudi", ""), + 1721 : ("Nautisch um jeden Preis", ""), + 1723 : ("Kalmars Seetang", ""), + 1724 : ("Ritters Aalverkauf", ""), + 1725 : ("Achims Fabelhaftes Seekrabben-Center", ""), + 1726 : ("Flüssiggersteladungen", ""), + 1727 : ("Dies Ruder Das", ""), + 1728 : ("Pfiffige Pfeilschwanzkrebse", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Nautisch, aber nett", ""), + 1804 : ("Muckibude", ""), + 1805 : ("Frühstück aus der Köderbüchse", ""), + 1806 : ("Hutgeschäft Fesche Schlagseite", ""), + 1807 : ("Kiel-Deals", ""), + 1808 : ("Knot am Mann!", ""), + 1809 : ("Rosteimer", ""), + 1810 : ("Anker-Management", ""), + 1811 : ("Nicht zu überbooten!", ""), + 1813 : ("Anlegestellenberatung", ""), + 1814 : ("Ahoi-Shop", ""), + 1815 : ("Is' Was, Dock?", ""), + 1818 : ("Café Sieben Meere", ""), + 1819 : ("Dockers-Kneipe", ""), + 1820 : ("Haken, Schnur und Senkbleischmuckladen", ""), + 1821 : ("König Neptuns Konservenfabrik", ""), + 1823 : ("Speiselokal Muschelauflauf", ""), + 1824 : ("Hundepaddel", ""), + 1825 : ("Fischmarkt Absolut Makrelig", ""), + 1826 : ("Gerts Gewebeleinstek-Gewänder", ""), + 1828 : ("Alices Ballastpalast", ""), + 1829 : ("Möwenstatuenmarkt", ""), + 1830 : ("Verloren und Geflundern", ""), + 1831 : ("Klar Schiff!", ""), + 1832 : ("Marks Massiv-Marssegel-Markt", ""), + 1833 : ("Maßanzüge Ein Mann wie ein Mastbaum", ""), + 1834 : ("Jollig lächerlich!", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Gag-Laden", ""), + 4504 : ("Toontown Zentrale", ""), + 4506 : ("Bekleidungsgeschäft", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tom-Toms Trommeln", ""), + 4604 : ("Im Vier-Viertel-Takt", ""), + 4605 : ("Fifis Fiedeln", ""), + 4606 : ("Casa De Castanets", ""), + 4607 : ("Aparte Liedbekleidung", ""), + 4609 : ("Ta-Ke-Ti-Nasten-Pianotasten", ""), + 4610 : ("Bitte benimm' dich!", ""), + 4611 : ("Stimmgabeln und -löffel", ""), + 4612 : ("Dr. Karies Zahnarztpraxis", ""), + 4614 : ("Rasieren und Haarschneiden für ein Lied", ""), + 4615 : ("Piccolos Pizza", ""), + 4617 : ("Lustige Mandolinen", ""), + 4618 : ("Notendurft-Räume", ""), + 4619 : ("Mehr Punkte", ""), + 4622 : ("Kinnstütz-Kissen", ""), + 4623 : ("Entfernen von Kreuzen", ""), + 4625 : ("Zahnpasta in der Tuba", ""), + 4626 : ("Notationen", ""), + 4628 : ("Schlechte-Vorzeichen-Versicherung", ""), + 4629 : ("Buntlose Papierteller", ""), + 4630 : ("Musik ist unsere Lautstärke", ""), + 4631 : ("Canto mir helfen?", ""), + 4632 : ("Tanz rund um die Uhr-Macherei", ""), + 4635 : ("Tenor-Times", ""), + 4637 : ("Singende Schneiderei", ""), + 4638 : ("Hard Rock Shop", ""), + 4639 : ("Antiquitäten zum Viertel-Preis", ""), + 4641 : ("Blues News", ""), + 4642 : ("Die fetzige Reinigung", ""), + 4645 : ("Club 88", ""), + 4646 : ("", ""), + 4648 : ("Umzugsfirma Tonträger", ""), + 4649 : ("", ""), + 4652 : ("Große-Pause-Laden ", ""), + 4653 : ("", ""), + 4654 : ("Perfekte Ton-Dächer", ""), + 4655 : (" Kochschule des Notenschüssel-Kochs ", ""), + 4656 : ("", ""), + 4657 : ("Barbershop-Quartett", ""), + 4658 : ("Plumpsende Pianos", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("Die Schmalzwalzer-Tanzschule", ""), + 4702 : ("Holzmichelbedarf Singende Säge", ""), + 4703 : ("Ein Feines Händel für Gepäck", ""), + 4704 : ("Tinas Konzertinakonzerte", ""), + 4705 : ("Weder Klavier noch dort", ""), + 4707 : ("Dopplers Soundeffekt-Studio", ""), + 4709 : ("Hohes C Kletterbedarf", ""), + 4710 : ("Fahrschule Tempo, Kutscherpolka!", ""), + 4712 : ("Reparatur für punktierte Reifen", ""), + 4713 : ("Dissis Modische Herrenbekleidung", ""), + 4716 : ("Vier-Seiten-Mundharmonikas", ""), + 4717 : ("Auto-Rabattversicherung Schuld war der Andantere!", ""), + 4718 : ("Bachmaterial und anderes Küchenzubehör", ""), + 4719 : ("Madrigal Wohnmobile", ""), + 4720 : ("Der richtige Toon", ""), + 4722 : ("Ouvertüren-Untersuchungen", ""), + 4723 : ("Spielplatzbedarf Verspiel Dich", ""), + 4724 : ("Rauschen beim Anziehen und Plauschen", ""), + 4725 : ("Der Baritonbarbier", ""), + 4727 : ("Flechten von Stimmbändern", ""), + 4728 : ("Sing solo, wir hören Dich nicht", ""), + 4729 : ("Buchladen Leere Saite", ""), + 4730 : ("Törichte Texte", ""), + 4731 : ("Toon-Töne", ""), + 4732 : ("Theaterkompanie Etude Brute?", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Akkordeons, beim Eintreten nicht balgen!", ""), + 4736 : ("Hochzeitsplaner Auf in die Zitherwochen", ""), + 4737 : ("Harfenschoner", ""), + 4738 : ("Geschäft für Kunstmusik und Kunstgewerbe", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Marshalls Plattenstapel", ""), + 4803 : ("Dienstmädchenschule Mezzoprächtig", ""), + 4804 : ("Mixolydische Schule für Barkeeper", ""), + 4807 : ("Entspann den Bach", ""), + 4809 : ("Ich Nix Verstanza!", ""), + 4812 : ("", ""), + 4817 : ("Tierhandlung Basstölpel", ""), + 4819 : ("Yukis Ukulelen", ""), + 4820 : ("", ""), + 4821 : ("Annas Kreuzfahrten", ""), + 4827 : ("Tabulatuhren", ""), + 4828 : ("Schumanns Schuhe für den Mann", ""), + 4829 : ("Pachelbels Kanonenkugeln", ""), + 4835 : ("Ursatz für Kool Katz", ""), + 4836 : ("Reggae-Regale", ""), + 4838 : ("Musikschule für Kazoologie", ""), + 4840 : ("Coda Pop musikalische Getränke", ""), + 4841 : ("Lyra, Lyra, nix berühra!", ""), + 4842 : ("Die Synkopenierung Unternehmung", ""), + 4843 : ("", ""), + 4844 : ("Con-Moto-Räder", ""), + 4845 : ("Katrins kunterbunte Klagelieder", ""), + 4848 : ("Massenhaft Noten Bausparkasse", ""), + 4849 : ("", ""), + 4850 : ("Pfandhaus Leihakkord", ""), + 4852 : ("Verblümtes Flötenvlies", ""), + 4853 : ("Leos Fender", ""), + 4854 : ("Wagners Wohlerdachte Violinen-Videos", ""), + 4855 : ("Das Tele-Caster-Netzwerk", ""), + 4856 : ("", ""), + 4862 : ("Quentins Quintessenzielle Quadrillen", ""), + 4867 : ("Mr. Costellos Fabulöse Cellos", ""), + 4868 : ("", ""), + 4870 : ("Ziggys Zoo der Zigeunermusik", ""), + 4871 : ("Harrys Haus der Harmonischen Humbucker", ""), + 4872 : ("Fast Freddies Verbundlose Fingergriffbretter", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Gag-Laden", ""), + 5502 : ("Toontown Zentrale", ""), + 5503 : ("Bekleidungsgeschäft", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Kartoffelauge Sehkraftprüfung", ""), + 5602 : ("Artie Schocks Krawatten", ""), + 5603 : ("Da haben wir den Salat", ""), + 5604 : ("Schleierkraut Hochzeitsausstatter", ""), + 5605 : ("Vege-stabile Tische und Stühle", ""), + 5606 : ("Blüten", ""), + 5607 : ("Kompostamt", ""), + 5608 : ("Rock und Pop Corn", ""), + 5609 : ("Verbirkene Schätze", ""), + 5610 : ("Susan Matschauges Boxunterricht", ""), + 5611 : ("Gophers Gags", ""), + 5613 : ("Kahlschlag-Barbiere", ""), + 5615 : ("Volkers Vogelfutter", ""), + 5616 : ("Zaungasthaus", ""), + 5617 : ("Schmetterdings Schmetterlinge", ""), + 5618 : ("Kletten und Etiketten", ""), + 5619 : ("Jacks Bohnenstangen", ""), + 5620 : ("Gasthaus Ohne Harken und Ösen", ""), + 5621 : ("Buchweizen für Leseratten", ""), + 5622 : (" Drahtesel für Grünlandfahrer ", ""), + 5623 : ("Vogel-Schaumbäder", ""), + 5624 : ("Mund Halten!", ""), + 5625 : ("Lass es Wein!", ""), + 5626 : ("Fichtennadelarbeiten", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Von Anfang bis Ernte", ""), + 5702 : ("Hakes Marken-Harken", ""), + 5703 : ("Foto-Erikas Kameraladen ", ""), + 5704 : ("Lisa Limones Gebrauchtwagen", ""), + 5705 : ("Giftefeu-Möbel", ""), + 5706 : ("14-Karotten-Juweliere", ""), + 5707 : ("Musikalische Früchtchen", ""), + 5708 : ("Reisebüro Wäre Weg", ""), + 5709 : ("Astroturf-Mäher", ""), + 5710 : ("Sportstudio Beerenstarke Jungs", ""), + 5711 : ("Glühstrumpfwaren", ""), + 5712 : ("Komische Statuen", ""), + 5713 : ("Lot und Leiden", ""), + 5714 : ("Springbrunnen-Seltersflaschen", ""), + 5715 : ("Scheunen-Nachrichten", ""), + 5716 : ("Pfandhaus Nimms oder Lassos", ""), + 5717 : ("Die Spritzblume", ""), + 5718 : ("Löwenzahn Exoten-Tierhandlung", ""), + 5719 : ("Privatdetektei Hermit die Wahrheit!", ""), + 5720 : ("Reben und Gecken Herrenbekleidung", ""), + 5721 : ("Rute 66 Speiserestaurant", ""), + 5725 : ("Gerste-, Hopfen- und Malzgeschäft", ""), + 5726 : ("Berts Dreck", ""), + 5727 : ("Vergissmeingeldnicht Bausparkasse", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : ("Toontown Zentrale ", ""), + 5804 : ("Glas mal Sehen", ""), + 5805 : ("Schneckenpost ", ""), + 5809 : ("Clownschule Tiefe Lache", ""), + 5810 : ("Männertreu ist hier neu ", ""), + 5811 : ("Gasthaus Freundliche Einsaladung ", ""), + 5815 : ("Graswurzel", ""), + 5817 : ("Äpfel und Birnen", ""), + 5819 : ("Flotte-Bienen-Jeans ", ""), + 5821 : ("Sporthalle Hauen und Flechten", ""), + 5826 : ("Ameisenzuchtzubehör ", ""), + 5827 : ("Hollunder Wunderangebote", ""), + 5828 : ("Faulpelz Möbel", ""), + 5830 : ("Spuck's Aus", ""), + 5833 : ("Die Salatbar", ""), + 5835 : ("Blumenbed & Breakfast", ""), + 5836 : ("Aprilregenwasserduschen", ""), + 5837 : ("Schule der blumigen Künste ", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Schlafliedbibliothek", ""), + 9503 : ("Die Dämmer-Bar", ""), + 9504 : ("Gag-Laden", ""), + 9505 : ("Toontown Zentrale", ""), + 9506 : ("Bekleidungsgeschäft", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Kuschel Dich 'Inn", ""), + 9602 : ("Vierzigmal Zwinkern zum Preis von zwanzig", ""), + 9604 : ("Eds Bettlaken", ""), + 9605 : ("Schlafliedgasse 323", ""), + 9607 : ("Big Mamas Bahama-Pyjama", ""), + 9608 : ("Katzenminz' für Katzenschlummer", ""), + 9609 : ("Tiefschlaf mit Schaf", ""), + 9613 : ("Die Uhrenreiniger", ""), + 9616 : ("Elektrofirma Licht Aus", ""), + 9617 : ("Schlafliedgasse 212", ""), + 9619 : ("Maximale Entspannung", ""), + 9620 : (" PJs Taxiservice", ""), + 9622 : ("Schlafens-Zeiteisen", ""), + 9625 : ("Schönheitssalon Roll Dich Ein", ""), + 9626 : ("Schlafliedgasse 818", ""), + 9627 : ("Das Schlaftipi", ""), + 9628 : ("Sis-Feierahmd-Kalender", ""), + 9629 : ("Schlafliedgasse 310", ""), + 9630 : ("Schlafen-wie-ein-Stein-Bruch", ""), + 9631 : ("Auszeit Uhrenreparaturen", ""), + 9633 : ("Traumland-Kinosaal", ""), + 9634 : ("Ratzematratze", ""), + 9636 : ("Schlaflos-Versicherung", ""), + 9639 : ("Haus des Winterschlafs", ""), + 9640 : ("Schlafliedgasse 805", ""), + 9642 : ("Säge-Schlummerbretter", ""), + 9643 : ("Augen-Zu Sehprüfung", ""), + 9644 : ("Kissenschlacht jede Nacht", ""), + 9645 : ("Gasthaus Zur Warmen Bettdecke", ""), + 9647 : ("Eisenwarenhandlung Mach dein Bett!", ""), + 9649 : ("Schnarchkapsel", ""), + 9650 : ("Schlafliedgasse 714", ""), + 9651 : ("Für Reicher und Schnarcher", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Gag-Laden", ""), + 3508 : ("Toontown Zentrale", ""), + 3509 : ("Bekleidungsgeschäft", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Elektrofirma Nordlicht ", ""), + 3602 : ("Nordoster-Hüte", ""), + 3605 : ("", ""), + 3607 : ("Der Schnee-Weise", ""), + 3608 : ("Nichts zu Rodeln", ""), + 3610 : ("Mikes Mordsmäßiger Mukluk-Mart", ""), + 3611 : ("Herrn Krugs Schneepflugs", ""), + 3612 : ("Iglu Design", ""), + 3613 : ("Blitzeisgefahrräder", ""), + 3614 : ("Schneeflocken-Müsli-Firma", ""), + 3615 : ("Gefrosteter Kalter Hund", ""), + 3617 : ("Kaltluftballonfahrten", ""), + 3618 : ("Kein Schneema! Krisenmanagement", ""), + 3620 : ("Schiklinik", ""), + 3621 : ("Schmelz-Eisbar", ""), + 3622 : ("", ""), + 3623 : ("Umtoste Toastbrot-Firma", ""), + 3624 : ("Unter Null Sandwichladen", ""), + 3625 : ("Tante Frostbeules Heizkörper", ""), + 3627 : ("Bernhardinerhüttenclub", ""), + 3629 : ("Café Dicke Suppe", ""), + 3630 : ("(R)Eis(e)büro London-Frost, Frost-Frankreich", ""), + 3634 : ("Schaukelstuhllifte", ""), + 3635 : ("Gebrauchtes Feuerholz", ""), + 3636 : ("Gänsehaut für Jedermann", ""), + 3637 : ("Kates Skates", ""), + 3638 : ("Ins Ungewisse Schlitten", ""), + 3641 : ("Freds Geschätzte Schlittenbetten", ""), + 3642 : ("Sturmauge Optik", ""), + 3643 : ("Schneeballsaal", ""), + 3644 : ("Geschmolzene Eiswürfel", ""), + 3647 : ("Smokinggeschäft Heiterer Pinguin", ""), + 3648 : ("Pulverisiertes Trockeneis", ""), + 3649 : ("Hambrrrger", ""), + 3650 : ("Antarktische Antiquitäten", ""), + 3651 : ("Feinfrost-Freddys Gefrostete Frankfurter", ""), + 3653 : ("Kühlhaus-Schmuck", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Winterlagerung", ""), + 3703 : ("", ""), + 3705 : ("Eisgefahrräder für zwei", ""), + 3706 : ("Schüttelfrost-Shakes", ""), + 3707 : ("Zu Hause ist es am schneesten", ""), + 3708 : ("Plutos Laden", ""), + 3710 : ("Speiserestaurant Fallende Grade ", ""), + 3711 : ("", ""), + 3712 : ("Schwimm mit dem Eisstrom", ""), + 3713 : ("Klappernde Zähne, Unter-Null-Zahnarzt", ""), + 3715 : ("Tante Arktis' Suppenküche", ""), + 3716 : ("Streusalz und -pfeffer", ""), + 3717 : ("Verschneen Sie, was ich meine??", ""), + 3718 : ("Designer-Schlauchseelen", ""), + 3719 : ("Eiswürfel am Stiel", ""), + 3721 : ("Schlitzauges Schlitten-Schnäppchen", ""), + 3722 : ("Schneehasen-Skigeschäft", ""), + 3723 : ("Schüttis Schneekugeln", ""), + 3724 : ("Die Bibberchronik", ""), + 3725 : ("Zu erschlittern", ""), + 3726 : ("Sonnenenergie-Bettdecken", ""), + 3728 : ("Müde Schneepflüge ", ""), + 3729 : ("", ""), + 3730 : ("Schneemänner An- und Verkauf", ""), + 3731 : ("Transportable Kamine", ""), + 3732 : ("Die Frostnase", ""), + 3734 : ("Sehkraftprüfungen Ich sehe was, was du nicht siehst ", ""), + 3735 : ("Polereiskappen", ""), + 3736 : ("Würfeleis zum Schleuderpreis", ""), + 3737 : ("Gasthof Bergab", ""), + 3738 : ("Hitze - Hol sie dir, solange sie heiß ist ", ""), + 3739 : ("", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Entschuldige, deine\n Zeit ist abgelaufen." +ClosetNotOwnerMessage = "Das ist zwar nicht dein Schrank, aber du darfst die Sachen anprobieren." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Entfernen" +ClosetAreYouSureMessage = "Du hast einige Kleidungsstücke gelöscht. Möchtest du sie wirklich löschen?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "%s wirklich löschen?" +ClosetShirt = "dieses Oberteil" +ClosetShorts = "diese Shorts" +ClosetSkirt = "diesen Rock" +ClosetDeleteShirt = "Oberteil\nlöschen" +ClosetDeleteShorts = "Shorts\nlöschen" +ClosetDeleteSkirt = "Rock\nlöschen" + +# EstateLoader.py +EstateOwnerLeftMessage = "Leider ist der Eigentümer dieses Grundstückes nicht da. Du wirst in %s Sekunden zum Spielplatz zurück geschickt." +EstatePopupOK = lOK +EstateTeleportFailed = "Du konntest nicht nach Hause gehen. Versuche es nochmal!" +EstateTeleportFailedNotFriends = "Leider ist %s auf dem Grundstück eines Toons, mit dem du nicht befreundet bist." + +# DistributedHouse.py +AvatarsHouse = "%s\n Haus" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Entschuldige, das ist nicht deine Sparbüchse." +DistributedBankNotOwner = "Entschuldige, das ist nicht deine Sparbüchse." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Alles verkaufen" +FishTankValue = "Hi, %(name)s! Du hast %(num)s Fische in deinem Eimer, die insgesamt %(value)s Jelly Beans wert sind. Möchtest du sie alle verkaufen?" + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name +" '" + else: + possesive = name + "s" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('Immer hungrig', 'Oft hungrig', + 'Manchmal hungrig', 'Selten hungrig',), + 'boredomThreshold': ('Immer gelangweilt', 'Oft gelangweilt', + 'Manchmal gelangweilt', 'Selten gelangweilt',), + 'angerThreshold': ('Immer knurrig', 'Oft knurrig', + 'Manchmal knurrig', 'Selten knurrig'), + 'forgetfulness': ('Vergisst immer', 'Vergisst oft', + 'Vergisst manchmal', 'Vergisst selten',), + 'excitementThreshold': ('Sehr ruhig', 'Ziemlich ruhig', + 'Ziemlich erregbar', 'Sehr erregbar',), + 'sadnessThreshold': ('Immer traurig', 'Oft traurig', + 'Manchmal traurig', 'Selten traurig',), + 'restlessnessThreshold': ('Immer unruhig', 'Oft unruhig', + 'Manchmal unruhig', 'Selten unruhig',), + 'playfulnessThreshold': ('Selten verspielt', 'Manchmal verspielt', + 'Oft verspielt', 'Immer verspielt',), + 'lonelinessThreshold': ('Immer einsam', 'Oft einsam', + 'Manchmal einsam', 'Selten einsam',), + 'fatigueThreshold': ('Immer müde', 'Oft müde', + 'Manchmal müde', 'Selten müde',), + 'confusionThreshold': ('Immer verwirrt', 'Oft verwirrt', + 'Manchmal verwirrt', 'Selten verwirrt',), + 'surpriseThreshold': ('Immer überrascht', 'Oft überrascht', + 'Manchmal überrascht', 'Selten überrascht',), + 'affectionThreshold': ('Selten Zärtlich', 'Manchmal zärtlich', + 'Oft zärtlich', 'Immer zärtlich',), + } + + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = "Toontown-\nZentrale: Drücke die Taste 'Bild Hoch', um besser zu sehen." + +FireworksJuly4Beginning = "Toontown-\nZentrale: Frohes Tag der deutschen Einheit Feuerwerk! Viel Spaß!" +FireworksJuly4Ending = "Toontown-\nZentrale: Wir hoffen, es hat dir gefallen!" +FireworksOctober31Beginning = "Toontown-\nZentrale: Happy Halloween!" +FireworksOctober31Ending = "Toontown-\nZentrale: Wir hoffen, es hat dir gefallen!" +FireworksNovember19Beginning = "Toontown-\nZentrale: Happy Birthday! Toontown wird 1 Jahr alt!" +FireworksNovember19Ending = "Toontown-\nZentrale: Wir hoffen, es hat dir gefallen!" +FireworksNewYearsEveBeginning = "Toontown-\nZentrale: Frohes neues Jahr! Viel Spaß beim Feuerwerk!" +FireworksNewYearsEveEnding = "Toontown-\nZentrale: Wir hoffen, es hat dir gefallen! Ein frohes Jahr 2006!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "TOON-TIPP:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Wenn du deinen Spielstand bei den Toon-Aufgaben schnell kontrollieren willst, halte einfach die Taste 'Ende' gedrückt. ", + "Wenn du deine Gag-Seite schnell kontrollieren willst, halte einfach Taste 'Pos1' gedrückt.", + "Drücke die Taste 'F7', um deine Freunde-Liste zu öffnen.", + "Drücke die Taste 'F8', um dein Sticker-Buch zu öffnen oder zu schließen. ", + "Wenn du die Taste 'Bild Hoch' drückst, kannst du nach oben schauen, mit der Taste 'Bild Runter' nach unten.", + "Wenn du springen willst, drücke die Taste 'Strg'.", + "Drücke die Taste 'F9' um einen Screenshot, also eine Bildschirmansicht in deinem Toontown-Ordner auf deinem Computer zu speichern.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "You can exchange Secret Friend Codes with somebody you know outside Toontown to enable open chat with them in Toontown.", + "Auf der Seite Optionen in deinem Sticker-Buch kannst du die Bildschirmauflösung ändern sowie Audio und andere Optionen einstellen und steuern.", + "Probiere die Kleidung deines Freundes vor dem Schrank in seinem Haus an.", + "Mit der Schaltfläche 'Nach Hause gehen' auf deinem Stadtplan kommst du zu deinem Haus.", + "Immer, wenn du eine gelöste Toon-Aufgabe abgibst, werden deine Lach-Punkte automatisch aufgefüllt.", + "Im Angebot von Bekleidungsgeschäften kannst du auch ohne Kleidermarke stöbern.", + "Die Belohnung für manche Toon-Aufgaben ermöglicht dir, mehr Gags und Jelly Beans bei dir zu tragen.", + "Du kannst bis zu 50 Freunde auf deiner Freunde-Liste haben.", + "Einige Belohnungen für erledigte Toon-Aufgaben ermöglichen dir, dich mit Hilfe der Stadtplanseite im Sticker-Buch zu Spielplätzen zu teleportieren.", + "Erhöhe deine Lach-Punkte auf den Spielplätzen, indem du Schätze wie Sterne und Eistüten sammelst.", + "Wenn du nach einem harten Kampf schnell heilen musst, geh zu deinem Grundstück und sammle Eistüten.", + "Mit der Tab-Taste kannst du zwischen verschiedenen Ansichten deines Toons wechseln.", + "Manchmal werden verschiedene Toon-Aufgaben für dieselbe Belohnung angeboten. Vergleiche die Angebote!", + "Das Spiel kann noch mehr Spaß machen, wenn du dir Freunde mit ähnlichen Toon-Aufgaben suchst.", + "Du brauchst deinen Spielstand nicht zu speichern. Die Toontown-Server speichern fortwährend alle notwendigen Informationen.", + "Du kannst anderen Toons etwas zuflüstern, indem du sie entweder anklickst oder sie in deiner Freunde-Liste auswählst.", + "Manche Schnell-Chat-Wendungen zeigen die Gefühle deines Toons als Animation.", + "Wenn die Gegend, in der du dich befindest, überfüllt ist, versuche den Bezirk zu wechseln. Gehe zur Bezirksseite in deinem Sticker-Buch und wähle einen anderen.", + "Wenn du aktiv Gebäude rettest, erhältst du einen Stern in Bronze, Silber oder Gold über deinem Toon.", + "Wenn du so viele Gebäude rettest, dass du einen Stern über deinem Kopf erhältst, findest du deinen Name wahrscheinlich auf der Tafel in eine Toontown-Zentrale.", + "Gerettete Gebäude werden manchmal von den Bots zurückerobert. Wenn du deinen Stern behalten willst, musst du loszuziehen und weitere Gebäude retten!", + "Die Namen deiner Geheimen Freunde erscheinen in Blau.", + # Fishing + "Versuche, alle Fische in Toontown zu fangen!", + "In verschiedenen Teichen schwimmen verschiedene Fische. Versuche es überall! ", + "Wenn dein Fischeimer voll ist, verkaufe deine Fische an die Tierhandlungs- Angestellten auf den Spielplätzen.", + "Du kannst deine Fische an die Tierhandlungen oder an die Tierhandlungs-Angestellten verkaufen.", + "Stärkere Angelruten fangen schwerere Fische, ihre Benutzung kostet aber mehr Jelly Beans. ", + "Du kannst stärkere Angelruten im Kuhtalog kaufen.", + "Schwerere Fische sind in der Tierhandlung mehr Jelly Beans wert.", + "Seltene Fische sind in der Tierhandlung mehr Jelly Beans wert.", + "Manchmal kann man beim Fischen Beutel mit Jelly Beans finden.", + "Bei manchen Toon-Aufgaben muss man Dinge aus den Teichen fischen.", + "In den Fischteichen auf den Spielplätzen gibt es andere Fische als in Teichen an Straßen.", + "Manche Fische sind überaus selten. Angle weiter, bis du alle gesammelt hast!", + "In dem Teich bei deinem Grundstück gibt es Fische, die man nur dort findet.", + "Für jeweils 10 Arten, die du fängst, erhältst du eine Angeltrophäe!", + "In deinem Sticker-Buch kannst du sehen, welche Fische du gesammelt hast.", + "Bei manchen Angeltrophäen bekommst du eine Lach-Spritze.", + "Angeln ist eine gute Möglichkeit, sich noch mehr Jelly Beans zu verdienen.", + # Doodles + "Nimm ein Doodle von der Tierhandlung auf!", + "Tierhandlungen bekommen täglich neue Doodles zum Verkauf herein.", + "Besuche täglich die Tierhandlungen um zu sehen, was sie an neuen Doodles da haben.", + "In den verschiedenen Stadtteilen sind verschiedene Doodles im Angebot.", + ), + + TIP_STREET : ( + "Es gibt vier Arten von Bots: Gesetzomaten, Monetomaten, Schachermaten und Chefomaten.", + "Jeder Gag-Ablauf hat einen anderen Grad an Genauigkeit und schädlicher Wirkung.", + "Sound-Gags wirken auf alle Bots, wecken aber geköderter Bots auf.", + "Bots in einer strategischen Folge zu bekämpfen erhöht die Chancen, Kämpfe zu gewinnen.", + "Mit dem Gag-Ablauf Aufheitern kannst du andere Toons im Kampf heilen.", + "Während einer Bot-Invasion verdoppeln sich die Gag-Erfahrungspunkte!", + "Mehrere Toons können sich zusammenschließen und denselben Gag-Ablauf im Kampf verwenden, um zusätzlichen Schaden bei den Bots anzurichten.", + "Im Kampf werden Gags von oben nach untern in der Reihenfolge benutzt, wie sie auf dem Gag-Menü erscheinen.", + "Die Reihe runder Lichter über den Aufzügen in Bot-Gebäuden zeigt an, wie viele Stockwerke es drinnen gibt.", + "Klicke auf einen Bot, um Einzelheiten zu sehen.", + "Die Verwendung von Gags höherer Level gegen Bots niedrigerer Level bringt keine Erfahrungspunkte ein.", + "Gags, die Erfahrungspunkte einbringen, haben im Kampf auf dem Gag-Menü einen blauen Hintergrund.", + "Die Gag-Erfahrung multipliziert sich beim Einsatz des Gags im Inneren von Gebäuden. Höhere Stockwerke haben höhere Multiplikationsfaktoren.", + "Wenn ein Bot besiegt wird, wird er nach dem Kampf jedem Toon in dieser Runde angerechnet.", + "Jede Straße in Toontown hat verschiedene Bot-Level und -Arten.", + "Fußwege sind frei von Bots.", + "Auf den Straßen geben die Seitentüren lustige Sprüche von sich, wenn man sich ihnen nähert.", + "Manche Toon-Aufgaben sind ein Training für neue Gag-Abläufe. Du darfst nur sechs der sieben Gag-Tracks auswählen. Wähle daher sorgfältig!", + "Fallen Stellen sind nur nützlich, wenn du oder deine Freunde sich beim Einsatz von Ködern im Kampf koordiniert.", + "Bei Köder-Gags höherer Level ist ein Versagen weniger wahrscheinlich.", + "Gags niedrigerer Level haben gegen Bots höherer Level eine geringere Genauigkeit.", + "Bots können nicht angreifen, wenn sie im Kampf geködert wurden.", + "Wenn du mit deinen Freunden ein Bot-Gebäude erobert hast, dann werdet ihr mit Porträts im geretteten Toon-Gebäude belohnt.", + "Die Anwendung eines Tooning-Gags auf einen Toon mit vollem Lach-O-Meter bringt keine Tooning-Erfahrung.", + "Bots sind kurz betäubt, wenn sie von einem Gag getroffen werden. Das erhöht die Trefferwahrscheinlichkeit für andere Gags in derselben Runde.", + "Fallen lassen-Gags haben eine geringe Trefferwahrscheinlichkeit, aber die Genauigkeit wird erhöht, wenn Bots in derselben Runde zuerst von einem anderen Gag getroffen werden.", + "Wenn du genügend Bots besiegt hast, verwende den 'Bot-Radar', indem du die Bot-Symbole auf der Bot-Galerie-Seite in deinem Sticker-Buch anklickst.", + "Während eines Kampfes kannst du an den Strichen (-) und X-en sehen, welchen Bot deine Teamkameraden gerade angreifen.", + "Im Kampf zeigt bei den Bots ein Licht ihren Gesundheitszustand an: Grün bedeutet gesund, Rot bedeutet fast zerstört.", + "Es können maximal vier Toons auf einmal kämpfen.", + "Auf der Straße treten Bots eher in einen Kampf gegen mehrere Toons als gegen nur einen Toon ein.", + "Die beiden schwierigsten Bot jeder Art findet man nur innerhalb von Gebäuden.", + "Fallen lassen-Gags wirken niemals gegen geköderte Bots.", + "Bots greifen meist den Toon an, der ihnen den größten Schaden zugefügt hat.", + "Sound-Gags richten bei geköderten Bots keinen zusätzlichen Schaden an.", + "Wenn du allzu lange mit dem Angriff auf einen geköderten Bot wartest, wacht er wieder auf. Der Köder wirkt auf höheren Leveln länger.", + "Jede Straße in Toontown hat einen Fischteich. Manche Straßen haben einzigartige Fische, die es nur dort gibt.", + ), + + TIP_MINIGAME : ( + "Wenn du dein Jelly Beans-Glas aufgefüllt hast, werden alle Jelly Beans, die du bei den kleinen Spielen des Toon-Expresses gewinnst, automatisch in deine Sparbüchse in deinem Haus transferiert.", + "Im Spiel 'Minnies Tanzstunde' kannst du anstelle der Maus auch die Pfeiltasten benutzen.", + "Im Kanonen-Spiel kannst du die Kanone mit den Pfeiltasten bewegen und mit der 'Strg'- Taste abfeuern.", + "Im Ringspiel werden Bonuspunkte vergeben, wenn die ganze Gruppe erfolgreich durch ihre Ringe schwimmt.", + "Ein fehlerloser Durchlauf von Minnies Tanzstunde verdoppelt deine Punkte.", + "Beim Tauziehen erhältst du mehr Jelly Beans, wenn du gegen einen stärkeren Bot spielst.", + "Der Schwierigkeitsgrad der Spiele, die man mit dem Toon-Express erreicht, hängt von der Gegend ab: Toontown Mitte hat die leichtesten und Donalds Traumland die schwersten.", + "Bestimmte Spiele, die man mit dem Toon-Express erreicht, kann man nur in einer Gruppe spielen.", + ), + + TIP_COGHQ : ( + "Du musst deine Bot-Verkleidung vervollständigen, bevor du ein Chef-Gebäude betrittst.", + "Du kannst auf Bot-Schläger springen, um sie vorübergehend außer Gefecht zu setzen.", + "Sammle Bot-Verdienste, indem du Bots im Kampf besiegst.", + "Bots höherer Level bringen mehr Verdienste ein.", + "Wenn du genügend Bot-Verdienste für eine Beförderung gesammelt hast, suche den Schachermat-VP auf!", + "Wenn du deine Bot-Verkleidung trägst, kannst du wie ein Bot reden. ", + "Bis zu acht Toons können sich zusammenschließen, um gegen den Schachermat-VP zu kämpfen.", + "Der Schachermat-VP sitzt ganz oben im Bot-Hauptquartier.", + "Folge in Bot-Fabriken den Treppen nach oben, um zum Vorarbeiter zu gelangen.", + "Jedes Mal, wenn du dich durch die Fabrik durchkämpfst, erhältst du ein Stück deiner Bot-Verkleidung.", + "Den aktuellen Stand deiner Bot-Verkleidung kannst du in deinem Sticker-Buch nachsehen.", + "Den aktuellen Stand deiner Verdienste kannst du auf der Verkleidungsseite in deinem Sticker-Buch nachsehen.", + "Achte darauf, dass du eine volle Ladung Gags und einen vollem Lach-O-Meter hast, bevor du zum VP gehst.", + "Wenn du befördert wirst, wird deine Bot-Verkleidung auf den aktuellen Stand gebracht. ", + "Du musst den Vorarbeiter der Fabrik besiegen, um ein Stück der Bot-Verkleidung zu erbeuten.", + ), + TIP_ESTATE : ( + # Doodles + "Doodles können manche Schnell-Chat-Sprüche verstehen. Probiere es aus!", + "Mit dem \"Haustier\"-Menü im Schnell-Chat kannst du dein Doodle dazu bringen, Tricks zu zeigen.", + "Mit Trainingslektionen aus Klarabellas Kuhtalog kannst du Doodles Tricks beibringen.", + "Belohne dein Doodle für gezeigte Tricks.", + "Wenn du einen Freund zu Hause besuchst, kommt dein Doodle immer mit.", + "Gib deinem Doodle eine Jelly Bean, wenn es Hunger hat.", + "Wenn du auf ein Doodle klickst, erscheint ein Menü, mit dem du es füttern, rufen und kraulen kannst.", + "Doodles sind gern in Gesellschaft. Lade deine Freunde zum Spielen ein!", + "Jedes Doodle hat eine einzigartige Persönlichkeit.", + "Du kannst dein Doodle zur Tierhandlung zurückbringen und dir ein neues holen.", + "Wenn ein Doodle einen Trick zeigt, werden die Toons in seiner Nähe geheilt.", + "Je mehr die Doodle einen Trick üben, desto besser werden sie. Also schön weiterüben!", + "Schwierigere Doodle-Tricks heilen Toons schneller.", + "Erfahrene Doodles können mehr Tricks zeigen, bevor sie ermüden.", + "Du findest in deiner Freunde-Liste eine Liste der Doodles in deiner Nähe.", + # Furniture / Cattlelog + "Kaufe Möbel aus Klarabellas Kuhtalog, um dein Haus einzurichten.", + "In der Sparbüchse in deinem Haus sind zusätzliche Jelly Beans.", + "Im Schrank in deinem Haus sind zusätzliche Kleidungsstücke.", + "Gehe zum Haus deines Freundes und probiere seine Sachen an.", + "Kaufe bessere Angelruten aus Klarabellas Kuhtalog.", + "Kaufe größere Sparbüchsen aus Klarabellas Kuhtalog.", + "Rufe Klarabella von dem Telefon in deinem Haus aus an.", + "Klarabella hat einen größeren Schrank im Angebot, in den mehr Kleidungsstücke passen.", + "Schaffe erst Platz in deinem Kleiderschrank, bevor du eine Kleidermarke einlöst.", + "Klarabella hat alles im Angebot, was du brauchst, um dein Haus einzurichten.", + "Schau in deinem Briefkasten nach, wenn du bei Klarabella bestellt hast.", + "Die Lieferzeit für Kleidungsstücke aus Klarabellas Kuhtalog beträgt eine Stunde.", + "Die Lieferzeit für Tapeten und Fußbodenbeläge aus Klarabellas Kuhtalog beträgt eine Stunde.", + "Die Lieferzeit für Möbel aus Klarabellas Kuhtalog beträgt einen Tag.", + "Bewahre zusätzliche Möbel auf dem Dachboden auf.", + "Klarabella wird dich benachrichtigen, wenn ein neuer Kuhtalog da ist.", + "Klarabella wird dich benachrichtigen, wenn eine Kuhtalog-Lieferung eintrifft.", + "Jede Woche werden neue Kuhtaloge geliefert.", + "Halte im Kuhtalog Ausschau nach limitierten Urlaubsgegenständen.", + "Wirf Möbel, die du nicht brauchst, in die Mülltonne.", + # Fish + "Manche Fische, zum Beispiel die Heilige Makrele, kommen auf Toon-Grundstücken häufiger vor.", + # Misc + "Du kannst mit dem Schnell-Chat deine Freunde auf dein Grundstück einladen.", + "Wusstest du schon, dass die Farbe deines Hauses der Farbe deines Toon-Auswahl-Menüs entspricht?", + ), + } + +FishGenusNames = { + 0 : "Ballonfisch", + 2 : "Katzenfisch", + 4 : "Clownfisch", + 6 : "Gefrierfisch", + 8 : "Seestern", + 10 : "Löchrige Makrele", + 12 : "Hundshai", + 14 : "Amoraal", + 16 : "Ammenhai", + 18 : "Königskrabbe", + 20 : "Mondfisch", + 22 : "Seepferdchen", + 24 : "Beckenhai", + 26 : "Bäracuda", + 28 : "Mordforelle", + 30 : "Kaviarstimmer", + 32 : "Geleequalle", + 34 : "Teufelsrochen", + } + +FishSpeciesNames = { + 0 : ( "Ballonfisch", + "Heißluftballonfisch", + "Wetterballonfisch", + "Wasserballonfisch", + "Roter Ballonfisch", + ), + 2 : ( "Katzenfisch", + "Siamesischer Katzenfisch", + "Streunender Katzenfisch", + "Tigerkatzenfisch", + "Katerfisch", + ), + 4 : ( "Clownfisch", + "Trauriger Clownfisch", + "Partyclownfisch", + "Zirkusclownfisch", + ), + 6 : ( "Gefrierfisch", + ), + 8 : ( "Seestern", + "Fünf-Stern", + "Schlagersternchen", + "Leuchtender Seestern", + "Nord-Seestern", + ), + 10 : ( "Löchrige Makrele", + ), + 12 : ( "Hundshai", + "Bulldoggenhai", + "Hotdoghai", + "Dalmatinerhai", + "Welpenhai", + ), + 14 : ( "Amoraal", + "Elektrischer Amoraal", + ), + 16 : ( "Ammenhai", + "Hebammenhai", + "Zündflammenhai", + ), + 18 : ( "Königskrabbe", + "Alaska-Königskrabbe", + "Altkönigskrabbe", + ), + 20 : ( "Mondfisch", + "Vollmondfisch", + "Halbmondfisch", + "Neumondfisch", + "Zunehmender Mondfisch", + "Wonnemondfisch", + ), + 22 : ( "Seepferdchen", + "Schaukel-Seepferdchen", + "Lipizzaner-Seepferdchen", + "Araber-Seepferdchen", + ), + 24 : ( "Beckenhai", + "Planschbeckenhai", + "Schwimmbeckenhai", + "Olympiabeckenhai", + ), + 26 : ( "Braunbäracuda", + "Schwarzbäracuda", + "Koalabäracuda", + "Honigbäracuda", + "Eisbäracuda", + "Pandabäracuda", + "Lippenbäracuda", + "Grizzlybäracuda", + ), + 28 : ( "Mordforelle", + "Piratenkapitänforelle", + "Gemeine Mordforelle", + ), + 30 : ( "Klavierstint", + "Konzertflügelstint", + "Flügelstint", + "Pianostint", + "Mechanischer Klavierstint", + ), + 32 : ( "Geleequalle", + "Quittengeleequalle", + "Grobe Geleequalle", + "Erdbeergeleequalle", + "Traubengeleequalle", + ), + 34 : ( "Teufelsrochen", + ), + } + +FishFirstNames = ( + "", + "Angelo", + "Arktis", + "Baby", + "Bermuda", + "Big", + "Dorsch", + "Bläschen", + "Meister", + "Zuckerle", + "Käpt'n", + "Winzig", + "Döbel", + "Korall", + "Doktor", + "Körnchen", + "Kaiser", + "Beißer", + "Dick", + "Forelli", + "Flipper", + "Flunder", + "Tüpfel", + "Schatzi", + "Hans", + "König", + "Kleini", + "Wels", + "Fräulein", + "Herr", + "Pippi", + "Rosa", + "Prinz", + "Prinzessin", + "Professor", + "Schnapp", + "Königin", + "Regenbogen", + "Rochen", + "Rosi", + "Ruben", + "Salzer", + "Scholle", + "Sandy", + "Schupp", + "Haichen", + "Sir", + "Hüpfer", + "Schleicher", + "Schnapper", + "Pünktchen", + "Stachel", + "Schecki", + "Star", + "Sugar", + "Super", + "Tiger", + "Winzling", + "Bartel", + ) + +FishLastPrefixNames = ( + "", + "Strand", + "Schwarz", + "Blau", + "Keiler", + "Bulle", + "Katze", + "Tief", + "Doppel", + "Ost", + "Fantasie", + "Flockig", + "Flach", + "Frisch", + "Riesen", + "Gold", + "Golden", + "Grau", + "Grün", + "Schwein", + "Geplapper", + "Gelee", + "Dame", + "Leder", + "Zitrone", + "Lang", + "Nord", + "Ozean", + "Okto", + "Öl", + "Perle", + "Bausch", + "Rot", + "Band", + "Fluss", + "Fels", + "Rubin", + "Ruder", + "Salz", + "Meer", + "Silber", + "Schnorchel", + "Seezunge", + "Süd", + "Spitz", + "Gischt", + "Schwert", + "Tiger", + "Dreifach", + "Tropisch", + "Tunfisch", + "Welle", + "Schwach", + "West", + "Weiß", + "Gelb", + ) + +FishLastSuffixNames = ( + "", + "ball", + "flussbarsch", + "bauch", + "wanze ", + "dieb", + "butter", + "klaue", + "pfuscher", + "krabbe", + "unker", + "trommel", + "finne", + "fisch", + "platscher", + "flosse", + "geist", + "grunzer", + "kopf", + "schnorchler", + "springer", + "makrele", + "mond", + "maul ", + "barbe", + "hals", + "nase", + "barsch", + "grobian", + "läufer", + "segel", + "hai", + "muschel", + "seide", + "schleim", + "schnäpper", + "gestank", + "schwanz", + "kröte", + "forelle", + "wasser", + ) + + +CogPartNames = ( + "Linker Oberschenkel", "Linker Unterschenkel", "Linker Fuß", + "Rechter Oberschenkel", "Rechter Unterschenkel", "Rechter Fuß", + "Linke Schulter", "Rechte Schulter", "Brust", "Gesundheitsmesser", "Becken", + "Linker Oberarm", "Linker Unterarm", "Linke Hand", + "Rechter Oberarm", "Rechter Unterarm", "Rechte Hand", + ) + +CogPartNamesSimple = ( + "Oberkörper", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Haupteingang" +SellbotLegFactorySpecLobby = "Lobby" +SellbotLegFactorySpecLobbyHallway = "Korridor Lobby" +SellbotLegFactorySpecGearRoom = "Getrieberaum" +SellbotLegFactorySpecBoilerRoom = "Kesselraum" +SellbotLegFactorySpecEastCatwalk = "Östlicher Laufsteg" +SellbotLegFactorySpecPaintMixer = "Farbmischer" +SellbotLegFactorySpecPaintMixerStorageRoom = "Farbmischer-Lagerraum" +SellbotLegFactorySpecWestSiloCatwalk = "Westsilo-Laufsteg" +SellbotLegFactorySpecPipeRoom = "Rohrleitungsraum" +SellbotLegFactorySpecDuctRoom = "Leitungskanalraum" +SellbotLegFactorySpecSideEntrance = "Seiteneingang" +SellbotLegFactorySpecStomperAlley = "Stampfer-Gang" +SellbotLegFactorySpecLavaRoomFoyer = "Foyer Lava-Raum " +SellbotLegFactorySpecLavaRoom = "Lava-Raum" +SellbotLegFactorySpecLavaStorageRoom = "Lava-Lagerraum" +SellbotLegFactorySpecWestCatwalk = "Westlicher Laufsteg" +SellbotLegFactorySpecOilRoom = "Ölraum" +SellbotLegFactorySpecLookout = "Beobachtungsstand" +SellbotLegFactorySpecWarehouse = "Lagerhaus" +SellbotLegFactorySpecOilRoomHallway = "Korridor Ölraum" +SellbotLegFactorySpecEastSiloControlRoom = "Ostsilo-Kontrollraum" +SellbotLegFactorySpecWestSiloControlRoom = "Westsilo-Kontrollraum" +SellbotLegFactorySpecCenterSiloControlRoom = "Mittelsilo-Kontrollraum" +SellbotLegFactorySpecEastSilo = "Ostsilo" +SellbotLegFactorySpecWestSilo = "Westsilo" +SellbotLegFactorySpecCenterSilo = "Mittelsilo" +SellbotLegFactorySpecEastSiloCatwalk = "Ostsilo-Laufsteg" +SellbotLegFactorySpecWestElevatorShaft = "West-Aufzugs-Schacht" +SellbotLegFactorySpecEastElevatorShaft = "Ost-Aufzugs-Schacht" + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "GEWONNEN!" +FishBingoJackpot = "JACKPOT!" +FishBingoGameOver = "SPIEL VORBEI!" +FishBingoIntermission = "Pause\nendet in:" +FishBingoNextGame = "Nächstes Spiel\nbeginnt in:" +FishBingoTypeNormal = "Klassisch" +FishBingoTypeCorners = "Vier Ecken" +FishBingoTypeDiagonal = "Diagonalen" +FishBingoTypeThreeway = "Drei Wege" +FishBingoTypeBlockout = "BLOCKOUT!" +FishBingoStart = "Es ist Zeit für Fisch-Bingo! Gehe zum Spielen zu einem beliebigen freien Steg!" +FishBingoEnd = "Wir hoffen, es hat dir Spaß gemacht, Fisch-Bingo zu spielen." +FishBingoHelpMain = "Willkommen beim Toontown-Fisch-Bingo. Alle am Teich arbeiten beim Ausfüllen der Karte vor Ablauf der Zeit zusammen." +FishBingoHelpFlash = "Wenn du einen Fisch fängst, klicke auf eines der blinkenden Kästchen, um die Karte auszufüllen." +FishBingoHelpNormal = "Das ist eine klassische Bingo-Karte. Markiere eine beliebige Reihe senkrecht, waagerecht oder diagonal, um zu gewinnen." +FishBingoHelpDiagonals = "Markiere beide Diagonalen, um zu gewinnen." +FishBingoHelpCorners = "Eine leichte Ecken-Karte. Markiere alle vier Ecken, um zu gewinnen." +FishBingoHelpThreeway = "Drei Wege. Markiere beide Diagonalen und die mittlere Reihe, um zu gewinnen. Diese Aufgabe ist nicht leicht!" +FishBingoHelpBlockout = "Blockout! Markiere die gesamte Karte, um zu gewinnen. Du trittst gegen alle anderen Teiche an, um einen gewaltigen Jackpot zu gewinnen!" +FishBingoOfferToSellFish = "Dein Fischeimer ist voll. Möchtest du deine Fische verkaufen?" +FishBingoJackpot = "Gewinne %s Jelly Beans!" diff --git a/toontown/src/toonbase/TTLocalizer_german_Property.py b/toontown/src/toonbase/TTLocalizer_german_Property.py new file mode 100644 index 0000000..0ac5b99 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_german_Property.py @@ -0,0 +1,218 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.05 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.1 + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 1 + +#catalog/CatalogChatItemPicker +CCIPmessagePickerCancel = 0.06 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 18 +CMunpaidChatWarning = 0.06 +CMpayButton = 0.05 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#fishing/BingoCardGui.py +BCGjpText = (0.04) +BCGjpTextWordwrap = 10.5 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.08 +FPnewRecord = 0.08 + +#fishing/GenusPanel.py +GPgenus = 0.035 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.095 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.07 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.08 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.7 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NSnameResult = 0.065 + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 + +#pets/PetAvatarPanel.py +PAPfeed = 0.4 +PAPcall = 0.4 +PAPowner = 0.30 +PAPscratch = 0.4 +PAPstateLabel = 0.35 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.09 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial +PTtitle = 0.11 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.05 + +#safezone/Playground.py +PimgLabel = 0.6 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.10 +TPendFrame = 0.10 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.06 +MPhoodWordwrap = 14 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/InventoryNew.py +INtrackNameLabels = 0.038 +INclickToAttack = 0.75 +INpassButton = 0.032 +INrunButton = 0.045 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.6 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TPtop = 0.067 +TPpanel = 0.047 +TPbutton = 0.045 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/TTLocalizer_japanese.py b/toontown/src/toonbase/TTLocalizer_japanese.py new file mode 100644 index 0000000..f8baa12 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_japanese.py @@ -0,0 +1,10420 @@ +import string +import time +from toontown.toonbase.TTLocalizer_japanese_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/HGHeiseiMarugothictaiW8.ttc' +ToonFont = 'phase_3/models/fonts/HGHeiseiMarugothictaiW8.ttc' +SuitFont = 'phase_3/models/fonts/HGHanKointai.ttc' +SignFont = 'phase_3/models/fonts/MickeyFont.bam' +MinnieFont = 'phase_3/models/fonts/MickeyFont.bam' +FancyFont = 'phase_3/models/fonts/Comedy' +NametagFonts = ('phase_3/models/fonts/DFKyG7.ttc:1', #0 * + 'phase_3/models/fonts/DCCry5.ttc:1', #1 * + 'phase_3/models/fonts/DCInl5.ttc:1', #2 * + 'phase_3/models/fonts/DFCry5.ttc:1', #3 * + 'phase_3/models/fonts/DFKai3.ttc:1', #4 * + 'phase_3/models/fonts/DFLis6.ttc:1', #5 * + 'phase_3/models/fonts/DFMimP3.ttc:1', #6 * + 'phase_3/models/fonts/DFMrg2.ttc:1', #7 * + 'phase_3/models/fonts/DFMrm5.ttc:1', #8 * + 'phase_3/models/fonts/DFPocl7.ttc:1', #9 * + 'phase_3/models/fonts/DFPococ.ttc:1', #10 * + 'phase_3/models/fonts/DFPost7.ttc:1', #11 * + 'phase_3/models/fonts/DFRys9.ttc:1', #12 * + 'phase_3/models/fonts/DFSht5.ttc:1', #13 * + ) +NametagFontNames = ('フリー・プラン', #0 * + 'クリスタル', #1 * + 'しろぬき', #2 * + 'かくもじ', #3 * + 'まじめ', #4 * + 'ナチュラル', #5 * + 'しんぶん', #6 * + 'シンプル', #7 * + 'まるもじ', #8 * + 'クリップ', #9 * + 'ポップ', #10 * + 'フロッグ', #11 * + 'せきひ', #12 * + 'エイジア', #13 * + ) + +NametagLabel = "ネームタグ" + +UnpaidNameTag = "シャドー" + +BuildingNametagFont = 'phase_3/models/fonts/DFKyG7.ttc:1' +BuildingNametagShadow = (0.05, 0.05) + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "ミッキー" +VampireMickey = "VampireMickey" +Minnie = "ミニー" +Donald = "ドナルド" +Daisy = "デイジー" +Goofy = "グーフィー" +Pluto = "プルート" +Flippy = "フリッピー" +Chip = "チップ" +Dale = "デール" + +# common locations +lTheBrrrgh = 'ブルブルランド' +lDaisyGardens = 'デイジーガーデン' +lDonaldsDock = "ドナルドのハトバ" +lDonaldsDreamland = "ドナルドのドリームランド" +lMinniesMelodyland = "ミニーのメロディーランド" +lToontownCentral = 'トゥーンタウンセントラル' +lToonHQ = 'トゥーンHQ' +lSellbotHQ = "セルボットほんぶ" +lGoofySpeedway = "グーフィー・サーキット" +lOutdoorZone = "チップとデールのドングリひろば" +lGolfZone = "チップとデールのミニ・ゴルフ" +lPartyHood = "パーティー会場" + +lGagShop = 'ギャグショップ' +lClothingShop = 'ようふくや' +lPetShop = 'ペットショップ' + +# common strings +lCancel = 'キャンセル' +lClose = 'とじる' +lOK = 'OK' +lNext = 'つぎへ' +lQuit = 'やめる' +lYes = 'はい' +lNo = 'いいえ' +lBack = '戻る' + +sleep_auto_reply = "%s is sleeping right now" +lHQ = '本部' + +lHQOfficerF = 'HQスタッフ' +lHQOfficerM = 'HQスタッフ' + +MickeyMouse = "ミッキーマウス" + +AIStartDefaultDistrict = "シリーヴィル" + +Cog = "コグ" +Cogs = "コグ" +ACog = "コグ" +TheCogs = "コグ" +ASkeleton = "ガイコグ" +Skeleton = "ガイコグ" +SkeletonP = "ガイコグ" +Av2Cog = "a Version 2.0 Cog" +v2Cog = "Version 2.0 Cog" +v2CogP = "Version 2.0 Cogs" +ASkeleton = "a Skelecog" +Foreman = "工場長" +ForemanP = "工場長" +AForeman = "工場長" +CogVP = "コグゼキュティブ" +CogVPs = "コグゼキュティブの" +ACogVP = "コグゼキュティブ" +Supervisor = "金庫番" +SupervisorP = "金庫番" +ASupervisor = "金庫番" +CogCFO = "マネーマネー" +CogCFOs = "マネーマネーの" +ACogCFO = "マネーマネー" + +# AvatarDNA.py +Bossbot = "ボスボット" +Lawbot = "ロウボット" +Cashbot = "マネーボット" +Sellbot = "セルボット" +BossbotS = "ボスボット" +LawbotS = "ロウボット" +CashbotS = "マネーボット" +SellbotS = "セルボット" +BossbotP = "ボスボット" +LawbotP = "ロウボット" +CashbotP = "マネーボット" +SellbotP = "セルボット" +BossbotSkelS = BossbotS+" "+Skeleton +LawbotSkelS = LawbotS+" "+Skeleton +CashbotSkelS = CashbotS+" "+Skeleton +SellbotSkelS = SellbotS+" "+Skeleton +BossbotSkelP = Bossbot+" "+Skeleton +LawbotSkelP = Lawbot+" "+Skeleton +CashbotSkelP = Cashbot+" "+Skeleton +SellbotSkelP = Sellbot+" "+Skeleton +SkeleRevivePostFix = " v2.0" + +lBossbotHQ = Bossbot+lHQ +lLawbotHQ = Lawbot+lHQ +lCashbotHQ = Cashbot+lHQ +lSellbotHQ = Sellbot+lHQ +lTutorial = 'トゥーントリアル' +lMyEstate = 'キミのおうち' +lWelcomeValley = 'ウェルカムバレー' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("", "", "チュートリアル・テラス"),# Tutorial + 1000 : ("", "", "プレイグラウンド"), + 1100 : ("", "", "バーナクル・ストリート"), + 1200 : ("", "", "シーウィード・ストリート"), + 1300 : ("", "", "ライトハウス・レーン"), + 2000 : ("", "", "プレイグラウンド"), + 2100 : ("", "", "シリー・ストリート"), + 2200 : ("", "", "ルーピー・ストリート"), + 2300 : ("", "", "パンチライン・ストリート"), + 3000 : ("", "", "プレイグラウンド"), + 3100 : ("", "", "セイウチ・ストリート"), + 3200 : ("", "", "スリート・ストリート"), + 3300 : ("", "", "ポーラープレイス"), + 4000 : ("", "", "プレイグラウンド"), + 4100 : ("", "", "アルト・アベニュー"), + 4200 : ("", "", "バリトン・ストリート"), + 4300 : ("", "", "テナー・ストリート"), + 5000 : ("", "", "プレイグラウンド"), + 5100 : ("", "", "エルム・ストリート"), + 5200 : ("", "", "メイプル・ストリート"), + 5300 : ("", "", "オーク・ストリート"), + 9000 : ("", "", "プレイグラウンド"), + 9100 : ("", "", "ララバイ・ストリート"), + 9200 : ("", "", "パジャマ・プレイス"), + 10000 : ("","", ""), + 10100 : ("","", lBossbotHQ+'ロビー'), + 10200 : ("", "", "クラブハウス"), + 10500 : ("", "", "フロント3"), + 10600 : ("", "", "ミドル6"), + 10700 : ("", "", "バック9"), + 11000 : ("","", ""), + 11100 : ("","", lSellbotHQ+'ロビー'), + 11200 : ("","", Sellbot+'ファクトリー'), + 11500 : ("","", Sellbot+'ファクトリー'), + 12000 : ("","", ""), + 12100 : ("","", lCashbotHQ+'ロビー'), + 12500 : ("","", Cashbot+' コイン工場'), + 12600 : ("","", Cashbot+' ドル工場'), + 12700 : ("","", Cashbot+' ゴールド工場'), + 13000 : ("","", ""), + 13100 : ("","", lLawbotHQ+'ロビー'), + 13200 : ("", "", "裁判所ロビー"), + 13300 : ("", "", "ロウボットAオフィス"), + 13400 : ("", "", "ロウボットBオフィス"), + 13500 : ("", "", "ロウボットCオフィス"), + 13600 : ("", "", "ロウボットDオフィス"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("", "", lDonaldsDock) +ToontownCentral = ("", "", lToontownCentral) +TheBrrrgh = ("", "", lTheBrrrgh) +MinniesMelodyland = ("", "", lMinniesMelodyland) +DaisyGardens = ("", "", lDaisyGardens) +OutdoorZone = ("", "", lOutdoorZone) +FunnyFarm = ("", "", "ファニー・ファーム") +GoofySpeedway = ("", "", lGoofySpeedway) +DonaldsDreamland = ("", "", lDonaldsDreamland) +BossbotHQ = ("", "", "ボスボットほんぶ") +SellbotHQ = ("", "", "セルボットほんぶ") +CashbotHQ = ("", "", "マネーボットほんぶ") +LawbotHQ = ("", "", "ロウボットほんぶ") +Tutorial = ("", "", "トゥーントリアル") +MyEstate = ("", "", "キミのおうち") +WelcomeValley = ("", "", "ウェルカムバレー") +GolfZone = ("", "", lGolfZone) +PartyHood = ("", "", lPartyHood) + +Factory = 'コグファクトリー' +Headquarters = '本部' +SellbotFrontEntrance = 'ファクトリー入口' +SellbotSideEntrance = 'ファクトリー裏口' +Office = '事務所' + +FactoryNames = { + 0 : '工場の模型', + 11500 : 'セルボット コグファクトリー', + 13300 : 'ロウボットコグオフィス', #remove me JML + } + +FactoryTypeLeg = 'レッグ' +FactoryTypeArm = 'アーム' +FactoryTypeTorso = 'ボディ' + +MintFloorTitle = '%s階' + +# Quests.py +TheFish = "魚" +AFish = "魚" +Level = "レベル" +QuestsCompleteString = "コンプリート" +QuestsNotChosenString = "選択されていません" +Period = "。" + +Laff = "ゲラゲラポイント" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("こんにちは、 _avName_!", + "やあ、 _avName_!", + "_avName_!", + "_avName_ じゃないか!", + "いらっしゃい、 _avName_!", + "ハロー、 _avName_!", + "ごきげんいかがかな、 _avName_?", + "あっ、 _avName_!", + ) +QuestsDefaultIncomplete = ("タスクは進んでるかい、_avName_?", + "そのタスクが終わるまで、まだまだみたいだね。", + "がんばってね、_avName_!", + "最後までがんばってね、君ならきっと大丈夫!", + "タスクをコンプリートするまでがんばってね、応援してるよ!", + "トゥーンタスクが終わるまで、がんばれ!", + ) +QuestsDefaultIncompleteProgress = ("ここに来たのは正解だけど、まずはトゥーンタスクを終わらせなきゃね。", + "そのトゥーンタスクが終わってからここに戻っておいで。", + "トゥーンタスクが終わったらここに戻ってきてね。", + ) +QuestsDefaultIncompleteWrongNPC = ("よく出来たね。次は_toNpcName_のところに行ってね。_where_", + "そのトゥーンタスクもそろそろ終わりかな。_toNpcName_に会うといい。_where_", + "_toNpcName_に会って、タスクを終わらせておいで。_where_", + ) +QuestsDefaultComplete = ("よくやったね!\nこれは君へのごほうびだよ。", + "やったね、_avName_! はい、ごほうびだよ。", + "素晴らしい出来だね、_avName_! これをごほうびにあげるよ。", + ) +QuestsDefaultLeaving = ("バイバイ!", + "さよなら!", + "じゃあね、_avName_", + "またね、_avName_!", + "がんばってね!", + "トゥーンタウンで楽しんでいってね!", + "それじゃまたね!", + ) +QuestsDefaultReject = ("こんにちは。", + "いらっしゃいませ。", + "調子はどう?", + "今日もいい天気ですね。", + "今ちょっと忙しいんですよ、_avName_さん。", + "はい?", + "こんにちは、 _avName_!", + "_avName_、 いらっしゃい!", + "あ、_avName_! こんにちは!", + # Game Hints + "トゥーンガイドを開くにはF8ボタンを押せばいいって知ってた?", + "地図を使うと、遊び場にワープすることができるよ!", + "他のプレイヤーと友達になりたかったら、彼らをクリックしてみるといいよ。", + "" + Cog + "をクリックすると、そいつの情報を得られるよ。", + "ゲラゲラメーターをいっぱいにするには遊び場でトレジャーを集めよう。", + "" + Cog + "の建物は危険だから、一人で行かないようにね。", + "バトルで負けちゃうと、" + Cogs + "がキミのギャグをぜんぶ持っていっちゃうんだ。", + "トロリーゲームをプレイするとギャグが集まりやすいよ!", + "トゥーンタスクを完了するとゲラゲラポイントをもらえるよ。", + "トゥーンタスクを終わらせるごとにごほうびがもらえるよ。", + "ギャグをもっと運べるようになる「ごほうび」もあるんだって", + "バトルで勝ったら、倒した" + Cog + "の数に応じてトゥーンタスクのポイントがもらえるのさ。", + "" + Cog + " の建物を奪回したら、中に入ってみよう。建物の所有者が感謝のことばを伝えてくれるよ。", + "PageUpボタンを押していると、上を見上げることができるよ!", + "Tabボタンを押すと、周りを違った視点から見ることができるよ。", + "自分の考えを友達にだけ伝えたかったら、発言の前に'.'をつけてね。", + "" + Cog + "がスタン状態になっていると、落下物をよけられなくなるらしいよ。", + "" + Cog + "の建物はひとつとして同じ外観のものがないんだって", + "建物の上の階にいる" + Cogs + "を倒すと、バトル後のスキルレベルがもっと高くなるよ。", + ) +QuestsDefaultTierNotDone = ("こんにちは、_avName_!新しいトゥーンタスクを始める前に、今のタスクを終わらせてね。", + "やあ!今もってるタスクを終わらせてから新しいトゥーンタスクをスタートしてね。", + "やあ、_avName_! 今もってるタスクを終わらせたら、新しいタスクをあげるよ。", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("_toNpcName_がキミを探してるらしいよ。_where_", + "時間がある時にでも_toNpcName_に会ってみて。_where_", + "もし_toNpcName_のいる方に行くことがあったら、会ってあげて。_where_", + "_toNpcName_に会いに行くといいよ。_where_", + "_toNpcName_が新しいタスクをくれるよ。_where_", + ) +# Quest dialog +QuestsLocationArticle = "で" +def getLocalNum(num): + if (num <=9): + return str(num) + "つ" + else: + return str(num) +QuestsItemNameAndNum = "%(name)s %(num)s" #★1月24日新規修正 by Gregさん + +QuestsCogQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogQuestHeadline = "ウォンテッド" +QuestsCogQuestSCStringS = "%(cogLoc)s%(cogName)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogQuestSCStringP = "%(cogLoc)s%(cogName)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogQuestDefeat = "%sを倒す" +QuestsCogQuestDefeatDesc = "%(cogName)s%(numCogs)s体" + +QuestsCogNewNewbieQuestObjective = "新しいトゥーンを助けて、%sをやっつけよう!" +QuestsCogNewNewbieQuestCaption = "ゲラゲラポイントが%d以下の新しいトゥーンを助けよう!" +QuestsCogOldNewbieQuestObjective = "ゲラゲラポイントが%(laffPoints)d以下のトゥーンを助けて%(objective)sをやっつける" +QuestsCogOldNewbieQuestCaption = "ゲラゲラポイントが%d以下のトゥーンを助けよう!" +QuestsCogNewbieQuestAux = "倒す相手:" +QuestsNewbieQuestHeadline = "みならい" + +QuestsCogTrackQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogTrackQuestHeadline = "ウォンテッド" +QuestsCogTrackQuestSCStringS = "%(cogLoc)s%(cogText)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogTrackQuestSCStringP = "%(cogLoc)s%(cogText)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogTrackQuestDefeat = "%sを倒す" +QuestsCogTrackDefeatDesc = "%(trackName)s%(numCogs)s体" #★新規修正(1月20日) + +QuestsCogLevelQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogLevelQuestHeadline = "ウォンテッド" +QuestsCogLevelQuestDefeat = "%sを倒す" +QuestsCogLevelQuestDesc = "レベル%(level)s以上の%(name)s" +QuestsCogLevelQuestDescC = "レベル%(level)s以上の%(name)s%(count)s体" +QuestsCogLevelQuestDescI = "レベル%(level)s以上の%(name)s" +QuestsCogLevelQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" #★「で」がAnywhereのときに不要 + +QuestsBuildingQuestFloorNumbers = ('', '2階建以上の', '3階建て以上の', '4階建て以上の', '5階建て以上の') +QuestsBuildingQuestBuilding = "ビル" +QuestsBuildingQuestBuildings = "ビル" +QuestsBuildingQuestHeadline = "とりもどす" +QuestsBuildingQuestProgressString = "とりもどした数:%(progress)s / %(num)s" +QuestsBuildingQuestString = "%sを倒す" +QuestsBuildingQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" #★「で」がAnywhereのときに不要 + +QuestsBuildingQuestDesc = "%(type)sビル" +QuestsBuildingQuestDescF = "%(floors)s%(type)sビル" +QuestsBuildingQuestDescC = "%(type)sビル×%(count)s軒" +QuestsBuildingQuestDescCF = "%(floors)s%(type)sビル×%(count)s軒" +QuestsBuildingQuestDescI = "%(type)sビル数軒" +QuestsBuildingQuestDescIF = "%(floors)s%(type)sビル数軒" + +QuestFactoryQuestFactory = "コグファクトリー" +QuestsFactoryQuestFactories = "コグファクトリー" +QuestsFactoryQuestHeadline = "やっつける" +QuestsFactoryQuestProgressString = "やっつけた数:%(progress)s / %(num)s" +QuestsFactoryQuestString = "%sを倒す" +QuestsFactoryQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" + +QuestsFactoryQuestDesc = "%(type)s 工場" +QuestsFactoryQuestDescC = "%(type)s 工場×%(count)s" +QuestsFactoryQuestDescI = "%(type)s 工場を数軒" + +QuestMintQuestMint = "マネーファクトリー" +QuestsMintQuestMints = "マネーファクトリー" +QuestsMintQuestHeadline = "やっつける" +QuestsMintQuestProgressString = "やっつけた数:%(progress)s / %(num)s" +QuestsMintQuestString = "%sを倒す" +QuestsMintQuestSCString = "%(objective)s%(location)sを倒さなくちゃ!" + +QuestsMintQuestDesc = "マネーファクトリー" +QuestsMintQuestDescC = "マネーファクトリー×%(count)s" +QuestsMintQuestDescI = "マネーファクトリー" + +QuestsRescueQuestProgress = "助けた数:%(progress)s / %(numToons)s" +QuestsRescueQuestHeadline = "たすける" +QuestsRescueQuestSCStringS = "%(toonLoc)sのトゥーンを助けなくちゃ!" +QuestsRescueQuestSCStringP = "%(toonLoc)sのトゥーンを何人か助けなくちゃ!" +QuestsRescueQuestRescue = "%sを助ける" +QuestsRescueQuestRescueDesc = "トゥーン%(numToons)s人" +QuestsRescueQuestToonS = "トゥーン" +QuestsRescueQuestToonP = "トゥーン" +QuestsRescueQuestAux = "たすける:" + +QuestsRescueNewNewbieQuestObjective = "新しいトゥーンの仲間と一緒に%sを助けよう!" +QuestsRescueOldNewbieQuestObjective = "ゲラゲラポイント%(laffPoints)d以下のトゥーンと%(objective)sを助けよう!" + +QuestCogPartQuestCogPart = "コグスーツの部品" +QuestsCogPartQuestFactories = "コグファクトリー" +QuestsCogPartQuestHeadline = "取り戻す" +QuestsCogPartQuestProgressString = "取り戻した数:%(progress)s / %(num)s" +QuestsCogPartQuestString = "%sを取り戻す" +QuestsCogPartQuestSCString = "%(location)sの%(objective)sを取り戻さなくちゃ!" +QuestsCogPartQuestAux = "取り戻す:" + +QuestsCogPartQuestDesc = "コグスーツの部品" +QuestsCogPartQuestDescC = "コグスーツの部品×%(count)sつ" +QuestsCogPartQuestDescI = "コグスーツの部品をいくつか" + +QuestsCogPartNewNewbieQuestObjective = '新しいトゥーンの仲間と一緒に%sを取り戻そう!' +QuestsCogPartOldNewbieQuestObjective = 'ゲラゲラポイント%(laffPoints)d以下のトゥーンと%(objective)sを取り戻そう!' + +QuestsDeliverGagQuestProgress = "デリバリーされた数:%(progress)s / %(numGags)s" +QuestsDeliverGagQuestHeadline = "デリバリー" +QuestsDeliverGagQuestToSCStringS = "%(gagName)sをデリバリーしなくちゃ!" +QuestsDeliverGagQuestToSCStringP = "%(gagName)sをデリバリーしなくちゃ!" +QuestsDeliverGagQuestSCString = "デリバリーしなくちゃ!" +QuestsDeliverGagQuestString = "%sをデリバリーする" +QuestsDeliverGagQuestStringLong = "%sを_toNpcName_にデリバリーする" +QuestsDeliverGagQuestInstructions = "アクセスを許可されたら、このギャグをギャグショップで買うことができるようになるよ。" + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "デリバリー" +QuestsDeliverItemQuestSCString = "%(article)s%(itemName)sをデリバリーしなくちゃ!" +QuestsDeliverItemQuestString = "%sをデリバリーする" +QuestsDeliverItemQuestStringLong = "%sを_toNpcName_にデリバリーする" + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "あいにいく" +QuestsVisitQuestStringShort = "あいにいく" +QuestsVisitQuestStringLong = "_toNpcName_に会いに行く" +QuestsVisitQuestSeeSCString = "%sに会いに行かなくちゃ!" +QuestsRecoverItemQuestProgress = "取り返した数:%(progress)s / %(numItems)s" +QuestsRecoverItemQuestHeadline = "とりかえす" +QuestsRecoverItemQuestSeeHQSCString = lHQOfficerM+"に会いに行かなくちゃ。" +QuestsRecoverItemQuestReturnToHQSCString = lHQOfficerM+"に%sを返しに行かなくちゃ。" +QuestsRecoverItemQuestReturnToSCString = "%(npcName)sに%(item)sを返しに行かなくちゃ。" +QuestsRecoverItemQuestGoToHQSCString = "%sに行かなくちゃ。" % lToonHQ +QuestsRecoverItemQuestGoToPlaygroundSCString = "%sのプレイグラウンドに行かなくちゃ。" +QuestsRecoverItemQuestGoToStreetSCString = "%(hood)sの%(street)s%(to)sに行かなくちゃ。" #★ +QuestsRecoverItemQuestVisitBuildingSCString = "%s%sに行かなくちゃ。" +QuestsRecoverItemQuestWhereIsBuildingSCString = "%s%sはどこですか?" +QuestsRecoverItemQuestRecoverFromSCString = "%(loc)s%(holder)sから%(item)sを取り返さなくちゃ。" +QuestsRecoverItemQuestString = "%(holder)sから%(item)sを取り返す。" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d以上 %(cogs)s" +QuestsTrackChoiceQuestHeadline = "えらぶ" +QuestsTrackChoiceQuestSCString = "%(trackA)sと%(trackB)sのどっちかを選ばなくちゃ" +QuestsTrackChoiceQuestMaybeSCString = "%sにしようかな" +QuestsTrackChoiceQuestString = "%(trackA)sと%(trackB)sのどちらかを選ぶ" +QuestsFriendQuestHeadline = "ともだち" +QuestsFriendQuestSCString = "ともだちを作らなくちゃ" +QuestsFriendQuestString = "ともだちを作る" +QuestsMailboxQuestHeadline = "メール" +QuestsMailboxQuestSCString = "メールをチェックしなくちゃ!" +QuestsMailboxQuestString = "メールをチェックする" +QuestsPhoneQuestHeadline = "クララベル" +QuestsPhoneQuestSCString = "クララベルに電話しなくちゃ!" +QuestsPhoneQuestString = "クララベルに電話する" +QuestsFriendNewbieQuestString = "ゲラゲラポイント%d以下のトゥーン%d人とともだちになる。" +QuestsFriendNewbieQuestProgress = "ともだちの数:%(progress)s / %(numFriends)s" +QuestsFriendNewbieQuestObjective = "ゲラゲラポイント%d以下のトゥーン%d人とともだちになる。" + +QuestsTrolleyQuestHeadline = "トロリー" +QuestsTrolleyQuestSCString = "トロリーに乗らなくちゃ" +QuestsTrolleyQuestString = "トロリーに乗る" +QuestsTrolleyQuestStringShort = "トロリーに乗る" +QuestsMinigameNewbieQuestString = "%dミニゲーム" +QuestsMinigameNewbieQuestProgress = "あそんだ数:%(progress)s / %(numMinigames)s" +QuestsMinigameNewbieQuestObjective = "ゲラゲラポイント%d以下のトゥーンと%d回、ミニゲームをする。" +QuestsMinigameNewbieQuestSCString = "新しいトゥーンとミニゲームをしなくちゃ!" +QuestsMinigameNewbieQuestCaption = "ゲラゲラポイント%d以下の新しいトゥーンを助ける。" +QuestsMinigameNewbieQuestAux = "あそぶ:" + +QuestsMaxHpReward = "ゲラゲラリミットが%sポイントふえました。" +QuestsMaxHpRewardPoster = "ごほうび:%sポイントのゲラゲラブースト" + +QuestsMoneyRewardSingular = "ジェリービーンを\n1コゲット" +QuestsMoneyRewardPlural = "ジェリービーンを\n%sコゲット!" +QuestsMoneyRewardPosterSingular = "ごほうび:ジェリービーン1コ" +QuestsMoneyRewardPosterPlural = "ごほうび:ジェリービーン%sコ" + +QuestsMaxMoneyRewardSingular = "ジェリービーン1コを持てるようになったよ。" +QuestsMaxMoneyRewardPlural = "ジェリービーン%sコ持てるようになったよ。" +QuestsMaxMoneyRewardPosterSingular = "ごほうび:ジェリービーン1コを持てる" +QuestsMaxMoneyRewardPosterPlural = "ごほうび:ジェリービーン%sコを持てる" + +QuestsMaxGagCarryReward = "%(name)sをゲット。ギャグを%(num)sコ持てるようになったよ。" +QuestsMaxGagCarryRewardPoster = "ごほうび:%(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "トゥーンタスクを%sつ持てるようにしよう。" +QuestsMaxQuestCarryRewardPoster = "ごほうび:トゥーンタスクを%sつ持てる" + +QuestsTeleportReward = "%sへのワープアクセスをゲット" +QuestsTeleportRewardPoster = "ごほうび:%sへのワープアクセス" + +QuestsTrackTrainingReward = "\"%s\"のギャグの練習ができるようになったよ。" +QuestsTrackTrainingRewardPoster = "ごほうび:ギャグの練習" + +QuestsTrackProgressReward = "\"%(trackName)s\"のアニメーション %(frameNum)sコマ目をゲット" +QuestsTrackProgressRewardPoster = "ごほうび:\"%(trackName)s\"のアニメーション %(frameNum)sコマ目" + +QuestsTrackCompleteReward = "ギャグ\"%s\"を使えるようになったよ。" +QuestsTrackCompleteRewardPoster = "ごほうび:最終トラック%sの練習" + +QuestsClothingTicketReward = "服を着替えられるようになったよ。" +QuestsClothingTicketRewardPoster = "ごほうび:ようふく券" + +QuestsCheesyEffectRewardPoster = "ごほうび:%s" + +QuestsCogSuitPartReward = "コグスーツの部品:%(cogTrack)sの%(part)sをゲット! " +QuestsCogSuitPartRewardPoster = "ごほうび: %(cogTrack)sの%(part)sパーツ" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "このプレイグラウンドにあるよ。" +QuestsStreetLocationThisStreet = "このストリートにあるよ。" +QuestsStreetLocationNamedPlayground = "%sのプレイグラウンドにあるよ。" +QuestsStreetLocationNamedStreet = "%(toHoodName)sの%(toStreetName)sだよ。" +QuestsLocationString = "%(location)s%(string)s" +QuestsLocationBuilding = "%sの建物は" +QuestsLocationBuildingVerb = "それは" +QuestsLocationParagraph = "\a%(building)s%(buildingName)sだよ。\a%(buildingVerb)s%(street)s" +QuestsGenericFinishSCString = "トゥーンタスクを終わらせなくちゃ。" + +# MaxGagCarryReward names +QuestsMediumPouch = "中くらいの袋" +QuestsLargePouch = "大きい袋" +QuestsSmallBag = "小さい袋" +QuestsMediumBag = "中くらいのバッグ" +QuestsLargeBag = "大きいバッグ" +QuestsSmallBackpack = "小さいリュック" +QuestsMediumBackpack = "中くらいのリュック" +QuestsLargeBackpack = "大きいリュック" +QuestsItemDict = { + 1 : ["めがね", "めがね", ""], + 2 : ["かぎ", "かぎ", ""], + 3 : ["こくばん", "こくばん", ""], + 4 : ["ほん", "ほん", ""], + 5 : ["チョコレート", "チョコレート", ""], + 6 : ["チョーク", "チョーク", ""], + 7 : ["レシピ", "レシピ", ""], + 8 : ["ノート", "ノート", ""], + 9 : ["ATM", "ATM", ""], + 10 : ["ピエロ車のタイヤ", "ピエロ車のタイヤ", ""], + 11 : ["くうきいれ", "くうきいれ", ""], + 12 : ["タコのすみ", "タコのすみ", ""], + 13 : ["つつみ", "つつみ", ""], + 14 : ["きんぎょのレシート", "きんぎょのレシート", ""], + 15 : ["きんぎょ", "きんぎょ", ""], + 16 : ["オイル", "オイル", ""], + 17 : ["あぶら", "あぶら", ""], + 18 : ["みず", "みず", ""], + 19 : ["ギアレポート", "ギアレポート", ""], + 20 : ["黒板消し", "黒板消し", ""], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 110 : ["ようふくのチケット", "ようふくのチケット", " "], + 1000 : ["ようふく券", "ようふく券", ""], + + # Donald's Dock quest items + 2001 : ["インナーチューブ", "インナーチューブ", ""], + 2002 : ["かためがねの処方せん", "かためがねの処方せん", ""], + 2003 : ["めがねのフレーム", "めがねのフレーム", ""], + 2004 : ["かためがね", "かためがね", ""], + 2005 : ["しろいカツラ", "しろいカツラ", ""], + 2006 : ["たくさんのじゃり", "たくさんのじゃり", ""], + 2007 : ["コグ・ギア", "コグ・ギア", ""], + 2008 : ["かいず", "かいず", ""], + 2009 : ["よごれたクロヴィス", "よごれたクロヴィス", ""], + 2010 : ["きれいなクロヴィス", "きれいなクロヴィス", ""], + 2011 : ["とけいのばね", "とけいのばね", ""], + 2012 : ["カウンターウェイト", "カウンターウェイト", ""], + + # Minnie's Melodyland quest items + 4001 : ["ティナのもくろく", "ティナのもくろく", ""], + 4002 : ["ユキのもくろく", "ユキのもくろく", ""], + 4003 : ["もくろくひょう", "もくろくひょう", ""], + 4004 : ["バイオレットのもくろく", "バイオレットのもくろく", ""], + 4005 : ["モックンのチケット", "モックンのチケット", ""], + 4006 : ["タバサのチケット", "タバサのチケット", ""], + 4007 : ["バリーのチケット", "バリーのチケット", ""], + 4008 : ["くもったカスタネット", "くもったカスタネット", ""], + 4009 : ["あおいイカのすみ", "あおいイカのすみ", ""], + 4010 : ["とうめいなカスタネット", "とうめいなカスタネット", ""], + 4011 : ["レオの歌詞カード", "レオの歌詞カード", ""], + + # Daisy's Gardens quest items + 5001 : ["シルクのネクタイ", "シルクのネクタイ", ""], + 5002 : ["しましまのスーツ", "しましまのスーツ", ""], + 5003 : ["はさみ", "はさみ", ""], + 5004 : ["はがき", "はがき", ""], + 5005 : ["ペン", "ペン", ""], + 5006 : ["インクつぼ", "インクつぼ", ""], + 5007 : ["メモちょう", "メモちょう", ""], + 5008 : ["かぎばこ", "かぎばこ", ""], + 5009 : ["とりのえさ", "とりのえさ", ""], + 5010 : ["スプロケット", "スプロケット", ""], + 5011 : ["サラダ", "サラダ", ""], + 5012 : [lDaisyGardens+"のかぎ", lDaisyGardens+"のかぎ", ""], + 5013 : [lSellbotHQ+' 設計図', lSellbotHQ+' 設計図', ''], + 5014 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5015 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5016 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5017 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + + # The Brrrgh quests + 3001 : ["サッカーボール", "サッカーボール", ""], + 3002 : ["そり", "そり", ""], + 3003 : ["こおり", "こおり", ""], + 3004 : ["ラブレター", "ラブレター", ""], + 3005 : ["ダックスフント", "ダックスフント", ""], + 3006 : ["こんやくゆびわ", "こんやくゆびわ", ""], + 3007 : ["いわしのひげ", "いわしのひげ", ""], + 3008 : ["いたみどめ", "いたみどめ", ""], + 3009 : ["ぬけおちた歯", "ぬけおちた歯", ""], + 3010 : ["きんの歯", "きんの歯", ""], + 3011 : ["まつぼっくりパン", "まつぼっくりパン", ""], + 3012 : ["でこぼこチーズ", "でこぼこチーズ", ""], + 3013 : ["ふつうのスプーン", "ふつうのスプーン", ""], + 3014 : ["しゃべるカエル", "しゃべるカエル", ""], + 3015 : ["アイスクリーム", "アイスクリーム", ""], + 3016 : ["カツラのこな", "カツラのこな", ""], + 3017 : ["アヒルのにんぎょう", "アヒルのにんぎょう", ""], + 3018 : ["もこもこサイコロ", "もこもこサイコロ", ""], + 3019 : ["マイクロフォン", "マイクロフォン", ""], + 3020 : ["キーボード", "キーボード", ""], + 3021 : ["あつぞこのくつ", "あつぞこのくつ", ""], + 3022 : ["キャビア", "キャビア", ""], + 3023 : ["メイクのこな", "メイクのこな", ""], + 3024 : ["毛糸", "毛糸", "" ], + 3025 : ["あみ針", "あみ針", ""], + 3026 : ["アリバイ", "アリバイ", ""], + 3027 : ["気温センサー", "気温センサー", ""], + + #Dreamland Quests + 6001 : ["マネーボット本部プラン", "マネーボット本部プラン", ""], + 6002 : ["ロッド", "ロッド", ""], + 6003 : ["自動車のベルト", "自動車のベルト", ""], + 6004 : ["ペンチ", "ペンチ", ""], + 6005 : ["読書ランプ", "読書ランプ", ""], + 6006 : ["シタール", "シタール", ""], + 6007 : ["せいひょうき", "せいひょうき", ""], + 6008 : ["しまうまのざぶとん", "しまうまのざぶとん", ""], + 6009 : ["ひゃくにちそう", "ひゃくにちそう", ""], + 6010 : ["ザイデコのレコード", "ザイデコのレコード", ""], + 6011 : ["ズッキーニ", "ズッキーニ", ""], + 6012 : ["ズート・スーツ", "ズート・スーツ", ""], + + #Dreamland+1 quests + 7001 : ["プレーンベッド", "プレーンベッド", ""], + 7002 : ["ファンシーベッド", "ファンシーベッド", ""], + 7003 : ["青いベッドカバー", "青いベッドカバー", ""], + 7004 : ["ペーズリーのベッドカバー", "ペーズリーのベッドカバー", ""], + 7005 : ["まくら", "まくら", ""], + 7006 : ["かたいまくら", "かたいまくら", ""], + 7007 : ["パジャマ", "パジャマ", ""], + 7008 : ["足つきパジャマ", "足つきパジャマ", ""], + 7009 : ["赤の足つきパジャマ", "赤の足つきパジャマ", ""], + 7010 : ["ピンクの足つきパジャマ", "ピンクの足つきパジャマ", ""], + 7011 : ["カリフラワーサンゴ", "カリフラワーサンゴ", ""], + 7012 : ["ねばねばコンブ", "ねばねばコンブ", ""], + 7013 : ["ごますり棒", "ごますり棒", ""], + 7014 : ["しわのばしクリーム", "しわのばしクリーム", ""], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "どのエリアでも" + +QuestsTailorFillin = "ようふく屋" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "ようふく屋" +QuestsTailorLocationNameFillin = "どんなエリアでも" +QuestsTailorQuestSCString = "ようふく屋に行かなくちゃ" + +QuestMovieQuestChoiceCancel = "トゥーンタスクが必要ならまた後で来てね!じゃあね!" +QuestMovieTrackChoiceCancel = "決められるようになったらまた来てね!バイバイ!" +QuestMovieQuestChoice = "トゥーンタスクを選んでね。" +QuestMovieTrackChoice = "トラックを選んだかい?選べないならまた後でおいで!" + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "準備は整った。\aさあ、選びたいトラックがわかるまで世界中を旅してみよう。\aキミにとって最後のトラックになるから、良いものを選んでね。\aトラックを選んだらまたここに戻っておいで。!", + INCOMPLETE_PROGRESS : "良いものを選んだほうがいいよ。", + INCOMPLETE_WRONG_NPC : "良いものを選んだほうがいいよ。", + COMPLETE : "いい選択だ!", + LEAVING : "新しいスキルをマスターしたらまたおいで。幸運を祈ってるよ!", + } + +QuestDialog_3225 = { + QUEST : "ああ、_avName_!\aこのサラダを_toNpcName_にデリバリーしなくちゃいけないのに、配達人がコグ達のせいで逃げ帰ってしまったんだ。\aキミにデリバリーをお願いしてもいいかな?助かるよ!_where_" + } + +QuestDialog_2910 = { + QUEST : "ずいぶん早かったね。\aバネの件、本当によくやった。\a最後のアイテムはカウンターウェイトだ。\a_toNpcName_のところに寄って、持って帰れるものを持って帰ってきてくれ。_where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aボスボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくボスボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aロウボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくロウボットを退治できたね。さ、トゥーンHQに行っててごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aマネーボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくマネーボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aセルボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくセルボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "そろそろ新しいギャグが必要ってとこかな?\aフリッピーなら手伝ってくれるかもしれないよ。_where_" }, + 165 : {QUEST : "持ってるギャグの練習が必要みたいだね。\aコグにギャグをくらわせるたびにキミの経験値はあがるんだ。\a十分に経験をつんだらもっといいギャグを使えるようになるよ。\aまずはコグを4体やっつけて、ギャグの練習をしよう。"}, + 166 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のボスボットを倒してみよう。"}, + 167 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のロウボットを倒してみよう。"}, + 168 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のセルボットを倒してみよう。"}, + 169 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のマネーボットを倒してみよう。"}, + 170 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + 171 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + 172 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + + 175 : {GREETING : "", + QUEST : "キミのトゥーン専用のおうちがあるって知ってた?\a牛のクララベルは電話のショッピングカタログで、キミのおうちをかざる家具を売ってるよ。\aスピードチャットのセリフや服、他にもたくさんの楽しいアイテムを買えるよ。\aクララベルに最初のカタログを送るように言っておくね。\a毎週、新しいアイテムのカタログが届くよ。\aおうちへ帰って、クララベルに電話してみよう!", + INCOMPLETE_PROGRESS : "おうちへ帰って、クララベルに電話してね。", + COMPLETE : "お買い物、楽しかったでしょ!\aわたしもちょうど、自分のおうちの模様替えをしたところなんだ。\aトゥーンタスクをこなして、もっとごほうびをもらおう!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "「なげる」と「みずでっぽう」もおもしろいけど、レベルの高いコグと戦うにはギャグがもっと必要だね。\a他のトゥーンと組んでコンビアタックをすればダメージも増大するよ。\aいろんなギャグのコンビネーションを試してみて、何が一番効果的か見てみるといい。\a次のトラックでは「サウンド」と「トゥーンナップ」から選んでみたらどうかな。\a「サウンド」は1回でその場にいる全てのコグにダメージを与えられるし、「トゥーンアップ」はバトル中に他のトゥーンを回復してあげられるよ。\aキミの決心がついたら、またここに戻ってきてね。", + INCOMPLETE_PROGRESS : "ずいぶん早かったね!どれにするか決めたかい?", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。そのギャグを使えるようになるには練習が必要だ。\a練習が出来るようになるのはトゥーンタスクをいくつかこなしてから。\aタスクひとつでギャグアタックのアニメーション1コマが与えられる。\a15個すべてをそろえたら、新しいギャグが使えるようになる『最後のギャグ練習』のタスクがもらえるよ。\aトゥーンガイドで進行ぐあいをチェックしよう。", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "街でもっと動けるようになりたかったら、_toNpcName_に会うといい。_where_" }, + 1040 : { QUEST : "街でもっと動けるようになりたかったら、_toNpcName_に会うといい。_where_" }, + 1041 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだ友達やトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1042 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだともだちやトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1043 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだともだちやトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1044 : { QUEST : "来てくれてありがとな、実は今助けが必要なんだ。\a見ての通り、ウチの店には客がいない。\a実は秘伝のレシピが盗まれてしまって、おかげでそれからお客が来なくなちゃったんだ。\a最後に見たのは、ぼくのビルがコグ達に乗っ取られる直前。\aお願いだ、ぼくの秘伝のレシピを取り返してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "レシピは見つかったかい?" }, + 1045 : { QUEST : "ああ、ありがとう!\aきっと近いうちに全部取り返してレストランを再開するよ。\aそうそう、キミにメモがあるんだった…ワープアクセスだっけ。\a『友達を助けてくれてありがとう、これをトゥーンHQに持って行ってくれ』だって。\a本当に助かったよ、じゃあね!", + LEAVING : "", + COMPLETE : "ふむ、なるほど、キミはルーピー・ストリートの住民の手助けをしてくれたようだな。\aトゥーンタウンセントラルへのワープアクセスが欲しいとの事だが、宜しい。\aアクセスを与えよう。\aこれからはトゥーンタウンのほとんどどこからでもプレイグラウンドにワープできるようになる。\a地図をひらいてトゥーンタウンセントラルをクリックするだけで移動できるようになるぞ。" }, + 1046 : { QUEST : "ここのところ、ファニマニ銀行はマネーボット達に悩まされている。\aもし良かったらそこに行って助けてやってくれないか。_where_" }, + 1047 : { QUEST : "マネーボット達が、銀行にこっそり入ってうちの機械を盗んでいるんです。\aお願い、ATMを5台、マネーボット達から取り返してきて。\a何回も往復しなくて済むように、一度に全部持ってきてくれるといいわ", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだATMを探してるの?" }, + 1048 : { QUEST : "マシンを探してくれたのね、ありがとう!\aえーと…少しキズがついてるみたい。\aねえ、これを\"くすぐりマシーン\"にいる_toNpcName_のところに持っていってもらえるかな。彼女なら直せるかもしれない。", + LEAVING : "", }, + 1049 : { QUEST : "こわれたATM?\aマネーボットにやられた?\aま、とにかく見てみようか。\aなるほど、ギアが取れてる…しかもウチには在庫がないわ。\a大きなコグ達が持ってるコグ・ギアなら使えるかもしれない。\aそうね、レベル3のコグ・ギアをマシン1台につき2つ使うから、全部で10コ取ってきて。\a一度に全部持ってきてね、マシンを一気に直してあげるわ", + LEAVING : "", + INCOMPLETE_PROGRESS : "マシンを直すには10コのギアが必要よ、忘れないようにね!" }, + 1053 : { QUEST : "いっちょあがり!\aさ、マシンも全部直ったわよ。お代はけっこう。\aこいつらをファニマニバンクに持ってった時に私からもヨロシク言っといて", + LEAVING : "", + COMPLETE : "ATMが全部直ったって?\aありがとう。ええと、なにかお礼にあげるものがあるといいんだけど…" }, + 1054 : { QUEST : "_toNpcName_がピエロ車のヘルプを欲しがっているみたいだよ。_where_" }, + 1055 : { QUEST : "オウノウ!ボクの愛しいピエロ車のタイヤがみつからないよ!\aねえキミ、ちょっと助けてくれないかい?\aもしかしたらルーピー・ボブがトゥーンタウンセントラルの遊び場の池に投げちゃったかも…\aどこかのドックに行ってつり上げてみてくれないかな?", + GREETING : "イヤッホーイ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "タイヤを4つともつり上げるのはやっぱりむずかしい?" }, + 1056 : { QUEST : "ウワオ、ワンダフル!おかげでこのピエロ車もまた路上で走ることができるよ。\aん?この辺に空気ポンプがあると思ったんだけど…\a_toNpcName_が借りていったのかな?\a彼が持ってるかどうか、聞いてきてくれないかい?_where_", + LEAVING : "" }, + 1057 : { QUEST : "よう!\aえ、空気ポンプ?\aそうだな、じゃあキミがこのへんのストリートを荒らしてるハイレベルのコグを倒してくれたら渡すってのはどうだい?", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミの実力はそんなものなのか?" }, + 1058 : { QUEST : "やったな!キミなら出来ると思ったよ。\aほら、約束の空気ポンプだ。_toNpcName_も喜ぶだろう。", + LEAVING : "", + GREETING : "", + COMPLETE : "ヤッホウ!旅立ちの時は来た!\aああそうだ、助けてくれてほんとうにありがとう。\aこれをあげるよ。" }, + 1059 : { QUEST : "_toNpcName_の店だが、在庫が足りなくて困っているらしい。助けてやってみるのも悪くないんじゃないか?_where_" }, + 1060 : { QUEST : "来てくれてありがとう!\aあのコグのやつらめ、私のインクを盗んでいってるので店の在庫がすごく減ってきてしまっているんだ。\a池のタコからスミをとってきてくれないか?タコをつる時は池の近くのドックに立つといい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "釣りは苦手なのかい?" }, + 1061 : { QUEST : "おお、スミだ、助かった!\aそうだ、これからもインクに困ることがないようにするためにもカリカリン達を退治してくれないか?\aトゥーンタウンセントラルでカリカリンを6体退治してくれたらお礼をあげるよ。", + LEAVING : "", + COMPLETE : "ありがとう!これはお礼だよ。", + INCOMPLETE_PROGRESS : "カリカリンがまだいるのを見たんだが…" }, + 1062 : { QUEST : "おお、スミだ、助かった!\aそうだ、これからもインクに困ることがないようにするためにもガッツキー達を退治してくれないか?\aトゥーンタウンセントラルでガッツキーを6体退治してくれたらお礼をあげるよ。", + LEAVING : "", + COMPLETE : "ありがとう!これはお礼だよ。", + INCOMPLETE_PROGRESS : "ガッツキーがまだいるのを見たんだが…" }, + 900 : { QUEST : "_toNpcName_が配達物で困ってるって聞いたよ。_where_" }, + 1063 : { QUEST : "やあ、来てくれてありがとう。\a実は手元にあった包みがコグに盗まれてしまったんだ。\a取り返してくれると助かるよ。\aやつはレベル3みたいだったから、包みが見つかるまでレベル3のコグを退治するといいだろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1067 : { QUEST : "ああ、これだ!ありがとう!\aっと、送り先が汚れちゃったな…\aナントカ博士あてだって事しか読み取れない。\a_toNpcName_のことかな、配達してくれないか?_where_", + LEAVING : "" }, + 1068 : { QUEST : "私に配達が来るはずはないんだが…ドクター・ハッピーの間違いじゃないかい?\a私の助手があちらに行く予定だから、その時にでも聞いてもらおう。\aところで、このストリートにいるコグを少々退治してもらって良いかな?\aトゥーンタウンセントラルにいるコグ10体ほどでも助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "助手がまだ戻ってきていないんだ" }, + 1069 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、マネーボットが助手の手から包みを奪ってしまったらしい…\a取り戻してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1070 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、セルボットが助手の手から包みを奪ってしまったらしい…\a申し訳ないが、そのセルボットを見つけて包みを取り返してくれないか。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1071 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、ボスボットが助手の手から包みを奪ってしまったらしい…\a取り戻してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1072 : { QUEST : "取り戻せたんだね、すばらしい!\aその包みはもしかしたら_toNpcName_に持って行くべきものかもしれないね。_where_", + LEAVING : "" }, + 1073 : { QUEST : "ああ、包みを持ってきてくれてありがとう。\aん?包みの数は2つだったはずだが…_toNpcName_がもうひとつを持っているか聞いてきてくれると助かるよ。", + INCOMPLETE : "もうひとつの包み、見つかったかい?", + LEAVING : "" }, + 1074 : { QUEST : "もうひとつあるはずだって?それもコグに盗まれたのかなぁ。\aコグを退治しつづけたらそのうち見つかるだろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "もうひとつの包みもなかなか見つけられないみたいだね。" }, + 1075 : { QUEST : "やっぱりふたつ目もあったんだなぁ。\aさ、急いでこれを_toNpcName_に届けてくれ、私の謝罪の言葉もそえて", + COMPLETE : "やっと届いた!\aキミは心優しいトゥーンだね。これをあげよう。きっと役に立つよ。", + LEAVING : "" }, + 1076 : { QUEST : "14金魚の店で何かあったようだ。\a_toNpcName_も助けが必要だろう。_where_" }, + 1077 : { QUEST : "ああ、来てくれてありがとう。コグが私の金魚をみんな盗んで行ってしまったんだ!\aあいつら、盗んだ金魚を売りさばいて小銭稼ぎするつもりなんだ。\aあの5匹の金魚は、この小さな店で私と何年も共に過ごした相棒達なんだ。\a取り返してくれると本当に、本当に助かるよ。\a1体のコグが5匹とも運んでいるだろう。\a金魚を見つけるまでコグをやっつけてくれ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "金魚を取り返してくれ~" }, + 1078 : { QUEST : "金魚が見つかったのか!?\aえ?これは…レシート?\aはぁ…コグのやつらめ。\aでもこれじゃ何が書いてあるかわからんな。_toNpcName_のところにこれを持って行って、読めるかどうか聞いてきてくれ。_where_", + INCOMPLETE : "_toNpcName_はなんて言ってた?", + LEAVING : "" }, + 1079 : { QUEST : "まずそのレシートを見せてくれ。\aふむ…なるほど、ここには1匹の金魚がオベッカーに買い取られた、とあるな。\a他の4匹のことは書いてないから、このオベッカーを見つけて問いただした方がよかろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "私に出来ることはもうないよ。金魚を探しに行った方がいいんじゃないかね?" }, + 1092 : { QUEST : "まずそのレシートを見せてくれ。\aふむ…なるほど、ここには1匹の金魚がチョロマカシーに買い取られた、とあるな。\a他の4匹のことは書いてないから、このチョロマカシーを見つけて問いただした方がよかろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "私に出来ることはもうないよ。金魚を探しに行った方がいいんじゃないかね?" }, + 1080 : { QUEST : "ああ、神様!オスカーを見つけてくれたんだね、彼は私の一番のお気に入りなんだ。\aえ、なんだって、オスカー?…そうなのか?\aオスカーが言うには、他の4匹遊び場の池に逃げたらしい。\a池に行って彼らを集めてくれないか?釣り上げるだけでいいはずだ。", + LEAVING : "", + COMPLETE : "ああ、なんと幸せなことなんだ!大事な大事な、小さな友達が帰ってきた!\aキミにはきちんとお礼をするべきだな。", + INCOMPLETE_PROGRESS : "金魚が見つからないのかい?" }, + 1081 : { QUEST : "どうやら_toNpcName_が面倒なシチュエーションにあるようだ。ねえ、良かったら彼女に手を貸してやってくれないか。_where_" }, + 1082 : { QUEST : "何が起こったって、速乾性のノリをこぼしちゃってこんなことになっちゃったのよ。\aこのシチュエーションをなんとかする手があったら聞いてみたいわよ。\a…あ、そうだわ、手伝ってくれない?\aセルボットを何体か倒してオイルを持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1083 : { QUEST : "うーん、オイルもあんまり役にたたないみたい…\a一体どうすればいいのかしら。\aあとは…そうね、アレならなんとかなるかも。\aロウボットを何体か倒して、油を持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1084 : { QUEST : "ああ、これもだめだわ!んもう!\a油に賭けてたのに…\aん?賭け?ねえ、思いついたわ。\aマネーボットを何体か倒して、お水を持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + COMPLETE : "やったわ、速乾ノリ地獄から脱出よ!\aありがとう、これはお礼よ。\nこれはバトル中にもうちょっと笑う時間が長く…\aあらやだ!またくっついちゃったわ!", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1085 : { QUEST : "_toNpcName_がコグについての研究をしているわ。\a助けたいなら彼に話してみるべきね。_where_" }, + 1086 : { QUEST : "いかにも、私はコグについての研究をしている。\a彼らがどのような作りになっているかを知りたいんだ。\aキミにコグのギアをいくつか集めてもらえると助かるんだがね。\a研究には大きめのギアが必要だから、レベル2以上で頼むよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか数が集まらないみたいだね?" }, + 1089 : { QUEST : "よし、さっそく見てみよう。ふむ、これは素晴らしいサンプルだ!\aふむふむ…\aさ、レポートができたぞ。これをトゥーンHQに大至急持って行ってくれ。", + INCOMPLETE : "私のレポートをトゥーンHQに持って行ってくれたかね?", + COMPLETE : "ありがとう、_avName_。ここからは我々に任せてくれ。", + LEAVING : "" }, + 1090 : { QUEST : "キミにとって役立ちそうな情報を_toNpcName_が持っているよ。_where_" }, + 1091 : { QUEST : "トゥーンHQは、いわゆるコグ・レーダーのようなものを開発しているらしい。\aそれを使えばコグを見つけるのも簡単になるってわけだ。\aキミのトゥーンガイドの中のコグページが重要なんだ。\aコグ達をある程度やっつけたら、コグのシグナルが入って来るようになる。 どこにいるかわかるようになるんだ。\aがんばってコグ達の退治を続けていれば、出来るようになるぞ。", + COMPLETE : "よくやった!これはきっとキミの役に立つだろう。", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "次に覚えたいギャグを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "コグのやつらめ、またやってくれたようだ。\a_toNpcName_がアイテムの盗難を報告してきたんだ、解決してやってくれないか。_where_" }, + 2202 : { QUEST : "こんにちは、_avName_。キミが来てくれて本当によかったよ!\a怖いツラしたセコビッチがついさっき、ボクのインナーチューブを盗んで走り去っていってしまったんだ。\aもしかしたらあいつらの悪巧みに使われてしまうかもしれない。\aお願いだ、どうか探し出して持ってきてほしい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "ボクのインナーチューブ、見つかった?", + COMPLETE : "見つけてくれたんだね、ボクのインナーチューブ!ほんとに腕がいいんだねぇ。はい、これはボクのキモチだよ。", + }, + 2203 : { QUEST : "コグ達が銀行で大暴れしてるらしい。\aキャプテン・カールがそこにいるから、何か手助けできるか聞いてあげて。_where_" }, + 2204 : { QUEST : "おお、ちょうどいいところに来てくれたな!\aあんのにっくきクズ鉄のガラクタ達め、我輩の片めがねを壊しおった!おかげで小銭が数えられなくなってしまったわい。\aそこでだ。キミにはドクター・キーケグにこの処方箋を持っていって、新しい片めがねを持ってきてもらいたいのだ。_where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "おお、これはこれは。\aこの処方箋の対処をしたいのはやまやまなんだが、コグ達が私のモノを盗んでいっていてな…\aオベッカーからめがねのフレームを取ってこられるのであれば、なんとかしてやれるかもしれないが。", + LEAVING : "", + INCOMPLETE_PROGRESS : "オベッカーのフレームがなけりゃ片めがねも作れんよ。", + }, + 2206: { QUEST : "持ってきたか、すばらしい!\aちょっとそこで待っていなさい…\aさ、処方箋通りに作ったぞ。さっそくキャプテン・カールに持って行ってやりなさい。_where_", + GREETING : "", + LEAVING : "", + COMPLETE : "ほほう!\aこれはまことにありがたい。\aこれは我輩の気持ちだ、受け取ってくれい。", + }, + 2207 : { QUEST : "バーバラ・シェルの店にコグが出た!\aさくっと行ってさくっとやっつけてやってくれ。_where_" }, + 2208 : { QUEST : "あらン、今ちょうど逃げてっちゃったのよね。\aウラギリンが入ってきて、あたしの白いカツラを盗んで逃げていったの。\aボスのためだとか、法的な先例だとかなんとか言ってたわよ。\a取り返してきてくれたらすっごぉく嬉しい!", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "まだ見つかってないのぉ?\aええとね、あいつは背が高くて頭がとんがってたわよ。", + COMPLETE : "見つかったのぉ!?\aああん、バーバラうれしい!\aこんなものじゃお礼にならないかもしれないけど…", + }, + 2209 : { QUEST : "メルヴィルが大事な航海にむけて準備中だって。\a忙しいだろうから、行って手伝ってあげて。_where_"}, + 2210 : { QUEST : "今、まさにネコの手も借りたい状態だよ。\aわしはコグ達が一体どこから来ているのかを調べるようトゥーンHQに依頼されたんだ\a船に必要なものがまだまだあるんだが、何しろジェリービーンが足らなくてな。\aアリスのところから砂利を持ってきてくれんか?もちろんタダじゃもらえんだろうが…頼む。_where_", + GREETING : "やあ、_avName_", + LEAVING : "", + }, + 2211 : { QUEST : "メルヴィルが砂利を欲しがってるって?\aったく、前回の分もまだ支払ってないのにさ。\aこのストリートでガミガミーナを5体やっつけたら砂利をあげるよ。", + INCOMPLETE_PROGRESS : "5体よ、ご・た・い。", + GREETING : "いらっしゃい!", + LEAVING : "", + }, + 2212 : { QUEST : "よし、追い払ってくれたね。\aほれ、この砂利をあのケチくさメルヴィルに持っていってやんな。_where_", + GREETING : "あれまあ、誰かと思ったら…", + LEAVING : "", + }, + 2213 : { QUEST : "思ったとおりだ、すばらしい。彼女も聞く耳を持ってるじゃないか。\aさて、次は海図だ。これはアートのところだな。\aここでもわしの評判はよくないだろうから、またタダ働きするはめになると思うが…。_where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "海図?ああ、持ってるよ。\aメルヴィルなんかのために働いてやるってんなら、その報酬に海図を渡してあげる。\a僕はいま、星空をたよりに航海できるよう天体観測儀を作っているところなんだ。\aコグ・ギアが3つ必要だから、それを取ってきてくれ。", + INCOMPLETE_PROGRESS: "3つのコグ・ギア、集められそうかい?", + GREETING : "いらっしゃい!", + LEAVING : "幸運を祈ってるよ!", + }, + 2215 : { QUEST : "うん!これならきっといいものが出来るよ。\aはい、これが海図だよ。メルヴィルに渡してやってくれ。_where_", + GREETING : "", + LEAVING : "", + COMPLETE : "よーし、これで準備は整ったぞ。旅立ちの時は来た!\aキミが経験者だったらこの旅に連れていくところだがな。かわりにこれを受け取ってくれ。", + }, + 901 : { QUEST : "エイハブが何か手助けを必要としているらしいぞ、キミにガッツがあるなら行ってみるがいい。_where_", + }, + 2902 : { QUEST : "おまえが例の新入りか?\aよろしい、ちょうど人手が必要だったところだ。\a私は今、コグ達をかくらんするのに使う巨大なカニのはりぼてを造っているんだ。\aクロヴィスが必要なんだが、ひとつクラッガーから借りてきてくれんか。_where_", + }, + 2903 : { QUEST : "いらっしゃい!\aああ、ボクもエイハブのカニのはりぼての事は聞いてるよ。\aでも、うちにあクロヴィスで一番いいものはちょっと汚れちゃってるんだ。\aまずこれをクリーニングに出してくれないかな、頼むよ。_where_", + LEAVING : "ありがとうね!" + }, + 2904 : { QUEST : "キミがクラッガーのおつかいのコだね。\aそれならすぐにきれいにクリーニングできるよ。\aちょっと待ってて…\aさ、できたよ。ぴっかぴかだ!\aエイハブによろしくね。_where_", + }, + 2905 : { QUEST : "これこれ、私が欲しかったのはまさにこれだよ。\aせっかく手伝ってくれてるんだ、次は大きな時計のバネを取ってきてもらおうか。\aフックの元へ行って、バネを持っているかどうか聞いてきてくれ。_where_", + }, + 2906 : { QUEST : "大きなバネねぇ。\aすまないが、うちにあるのは小さいものばかりだよ。\aでも、もしかしたら水鉄砲の引き金のバネをいくつか使って作れるかもしれないな。\a水鉄砲を3つ持ってきてくれ、なんとかやってみよう。", + }, + 2907 : { QUEST : "どれどれ…\aうーん、最高だね!これはいい!\aもう自分でもびっくりのいい出来だよ。\aはい、エイハブさまご注文の大きなバネだ。_where_", + LEAVING : "行ってらっしゃい!", + }, + 2911 : { QUEST : "手伝いたいのはやまやまだけど…\aこの辺はもう安全じゃなくなってしまったんだよ、_avName_。\aとりあえずマネーボットを何体か退治してくれないかな、話はそのあとだ。", + INCOMPLETE_PROGRESS : "まだまだ通りは危険だよ…", + }, + 2916 : { QUEST : "ああ、エイハブにあげられるような錘があるよ。\aでも、まずはセルボットを何体かやっつけた方が帰り道も安全なんじゃないかな。", + INCOMPLETE_PROGRESS : "まだまだセルボットがいるよ、倒したほうがいいよ。", + }, + 2921 : { QUEST : "まぁ、ひとつぐらいなら錘をあげてもいいかな。\aまわりでうろうろしているボスボット共をなんとかしてくれたら助かるけど。\a6体やっつけたらまた来てくれないかな。", + INCOMPLETE_PROGRESS : "うーん、まだ身の危険を感じるよ…", + }, + 2925 : { QUEST : "終わったかい?\aそうだね、もう危険は感じないかもしれない。\aこの錘をエイハブに持っていってくれ。_where_" + }, + 2926 : {QUEST : "よーし、航海に必要なものはすべてそろったぞ。\aうまくいくかな?……\aうむむ、問題がひとつだけあるな…\a船にエネルギーがないらしい。コグビルのせいでソーラーパネルに日光が来ていないんだな。\aちょっと奪回してきてくれんか?", + INCOMPLETE_PROGRESS : "まだエネルギーが来ないな。あっちの建物はどうだろう?", + COMPLETE : "おお!たいしたコグ退治の腕前だ。これはほんのお礼だよ。", + }, + 3200 : { QUEST : "たった今、_toNpcName_から連絡があったんだ。\aなんだかえらく大変そうなんだ、手伝ってあげてくれないか?_where_" }, + 3201 : { QUEST : "来てくれてありがとう!\aこのシルクネクタイを_toNpcName_に届けてくれないかな。\aお願いしてもいいかい?_where_" }, + 3203 : { QUEST : "おや、これは私が以前オーダーしたネクタイだね。ありがとう。\a私が仕立てたばっかりのあのしましまスーツにぴったりなん…\aあれ、スーツがないぞ。\aまさか!コグに盗まれてしまったのか!?\aやられた!\aスーツが見つかるまでコグを退治してくれ、頼んだぞ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スーツは見つかったか?コグ達が盗んだに違いないんだ!", + COMPLETE : "ばんざい!大事な新品のスーツが戻ってきた!\aやっぱりあいつらの仕業だったんだな。これは取り返してきてくれたお礼だよ。", + }, + + 3204 : { QUEST : "_toNpcName_がついさっき電話してきて、盗難に遭ったそうだ。\aちょっと様子を見てきてやってくれ。" }, + 3205 : { QUEST : "こんにちは、_avName_。助けにきてくれたのですね。\a今ちょうどガッツキーを店から追い出したところなんです…ふぅ。怖かったですよ。\aでも、混乱の中ハサミを無くしてしまいました。さっきのガッツキーが持って行ったに違いありません!\aお願いです、どうかハサミを取り返してください!", + LEAVING : "", + INCOMPLETE_PROGRESS : "ハサミはまだ見つかっていないのですか?", + COMPLETE : "ああ、ハサミだ!見つかったんだね、ありがとう!どうかこのお礼を受け取ってください!", + }, + + 3206 : { QUEST : "_toNpcName_がコグに悩まされてるみたいです。\a良かったら手助けしてあげてください。_where_" }, + 3207 : { QUEST : "やあ_avName_!来てくれてありがとな!\aニマイジタンの一団がやってきて、オレの担当カウンターからはがきを一山持ってっちまったんだよ。\aあいつらを全部やっつけて、はがきを取り返してきてくれ!", + INCOMPLETE_PROGRESS : "まだまだ取り残しがあるよ!", + COMPLETE : "サンキュー!これで時間通りにはがきを配達できるよ。これはお礼だ、受け取ってくれ。", + }, + + 3208 : { QUEST : "最近、デイジー・ガーデンでブアイソンについての苦情が出ているんだ。\a住民の安全のためにも、10体のブアイソンを倒してきてくれ" }, + 3209 : { QUEST : "ブアイソン退治、感謝しているよ。\aだが、今度はツーハーン達が暴れ始めてしまった。\aデイジー・ガーデンで10体のツーハーンを倒してきてくれれば、報酬を出す。頼んだよ。" }, + + 3247 : { QUEST : "最近、デイジー・ガーデンでガッツキーについての苦情が出ているんだ。\a住民の安全のためにも、20体のガッツキーを倒してきてくれ" }, + + + 3210 : { QUEST : "ありゃりゃ、メイプルストリートにあるフラワー・スプラッシュが品切れになってしまったみたいだぞ。\a自分で集めたフラワー・スプラッシュを持っていってやってくれないか。\a自分のもくろくにフラワー・スプラッシュが10本入っていることを確認してから行くんだぞ。", + LEAVING: "", + INCOMPLETE_PROGRESS : "それじゃ足りないわ、必要なのは10本のフラワー・スプラッシュよ。" }, + 3211 : { QUEST : "ああよかった、10本あれば今日はなんとかなるわ!ありがとう!\aでも、コグが外をうろついているのはやっぱり怖いわ。\aコグを20体ばかりやっつけてくれないかしら?", + INCOMPLETE_PROGRESS : "まだまだコグ達がいるわよ、やっつけて!お願い!", + COMPLETE : "よかったわ、これで少し安心よ。あなたへのお礼は…", + }, + + 3212 : { QUEST : "_toNpcName_が何か大事なものを無くしちゃったらしいの。\a探すのを手伝ってあげてくれない?_where_" }, + 3213 : { QUEST : "いらっしゃい、_avName_。\aペンが1本見当たらないんだが、もしかしたらコグが持って行ってしまったのではと思ってるんだ。\a探すのを手伝ってくれるかい?\aコグ達を倒してペンを見つけ出しておくれ。", + INCOMPLETE_PROGRESS : "ペンは見つかったかね?" }, + 3214 : { QUEST : "そうそう、それが私のペンだよ!感謝する!\aただね、キミがいない間にインクつぼも無いことに気付いて…\aコグ達をやっつけていけばきっと見つかるだろう。", + INCOMPLETE_PROGRESS : "インクつぼ、見つからないね…" }, + 3215 : { QUEST : "おお、これでペンとインクつぼが揃った!\aだがねキミ、実は…\a今度はメモ帳がなくなってしまったんだ。まったく、油断も隙もないね。\aメモ帳をコグ達から取り返してきてくれたら、お礼を差し上げるよ。", + INCOMPLETE_PROGRESS : "メモ帳は見つかった?" }, + 3216 : { QUEST : "ああ、メモ帳が見つかったんだね、やった!さて、キミへのお礼は…\aあれ?見当たらないぞ?\aオフィスのロックボックスの中に入れておいたのに、ロックボックスそのものがない!\aまったく信じられないな…コグのやつら、キミへのお礼まで盗んでいってしまったようだ。\aすまないが、ロックボックスを取り返してきてくれ。\aお礼はその後差し上げるよ。", + INCOMPLETE_PROGRESS : "ロックボックスを探し出してくれよ、キミへのお礼の品も入ってるんだから!", + COMPLETE : "やっと見つかったね、お疲れさま!このロックボックスにはキミにあげる新しいギャグバッグが入っているんだよ。", + }, + + 3217 : { QUEST : "我々はセルボットの構造について研究している者である。\aだがまだまだ詳細を知らない部分もある。\aそこでキミにはタッシャーナのスプロケットを採取してきてもらいたい!\aコグが爆発している時に入手できるようだ、任せたぞ。" }, + 3218 : { QUEST : "よくやった。今度はオオゲーサのスプロケットを比較材料として取ってきてくれ。\a先ほどより入手が難しいだろうから、根性で入手してきてくれ" }, + 3219 : { QUEST : "すばらしい!よし、あともうひとつ集めるだけだ。\a今度はクロマクールのスプロケットである。\aこやつらを見つけるにはセルボットビルの中を捜すのが良かろう。\aひとつ持って帰ってきてくれればキミにお礼を差し上げよう。" }, + + 3244 : { QUEST : "我々はロウボットの構造について研究している者である。\aだがまだまだ詳細を知らない部分もある。\aそこでキミにはツケコミンのスプロケットを採取してきてもらいたい!\aコグが爆発している時に入手できるようだ、任せたぞ。" }, + 3245 : { QUEST : "よくやった。今度はオオゲーサのスプロケットを比較材料として取ってきてくれ。\a先ほどより入手が難しいだろうから、根性で入手してきてくれ" }, + 3246 : { QUEST : "すばらしい!よし、あともうひとつ集めるだけだ。\a今度はドクター・トラブルのスプロケットである。\aこやつらを見つけるにはセルボットビルの中を捜すのが良かろう。\aひとつ持って帰ってきてくれればキミにお礼を差し上げよう。" }, + + 3220 : { QUEST : "ついさっき耳に入ったんだが、_toNpcName_がキミを探しているらしい。\a何事か聞いてみるとよかろう。_where_" }, + 3221 : { QUEST : "あら、_avName_!やっといたわ!\aあたし、あなたがみずでっぽう攻撃 のプロだって聞いたわ。\aデイジー・ガーデンのトゥーン達に自己防衛のいい例を見せてあげたいの。\aみずでっぽう攻撃でコグをたくさんやっつけてくれないかしら。\aそうすれば住民達もきっとみずでっぽうで反撃し始めるわ!\a20体のコグをやっつけてきたらお礼をあげるわ、がんばってね。" }, + + 3222 : { QUEST : "キミのトゥーン魂を見せてほしい。\aコグに占領された建物を何棟か奪回してくれたら、クエストを3つ持てるようにしてやろう。\aまず、どれか2つのコグビルを奪い返してくれ。\a友達に協力してもらうのもよかろう。"}, + 3223 : { QUEST : "よくやった。\aさあ、あと2棟だ。\a2階以上あるものを奪回してきなさい" }, + 3224 : { QUEST : "すばらしい!\aさあ、あと2棟だ。\a3階以上あるものを奪回してきなさい。\a終わったら戻っておいで、報酬が待っているよ。", + COMPLETE : "最後までやりとげたな、_avName_!\aキミのトゥーン魂、しかと見せてもらったぞ。", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_がヘルプを探しているらしいよ。\aキミならきっと彼女を助けられるだろう。_where_" }, + 3235 : { QUEST : "そう、これが私のオーダーしたサラダよ。\a持ってきてくれてありがとう。\aきっとコグ達が_toNpcName_の配達人を怯えさせてしまったのね。\aねえ、私達のために外を徘徊してるコグを倒していただけない?\aデイジー・ガーデンにいるコグを10体倒したら、_toNpcName_に報告してくださいな。", + INCOMPLETE_PROGRESS : "ボクのためにコグを倒しているんだって?\aありがたいよ、どんどん退治してくれ!", + COMPLETE : "コグ達を倒してくれてありがとう!\aこれで以前のように配達することが出来るよ。\aこれはお礼の気持ちさ。", + INCOMPLETE_WRONG_NPC : "コグ退治のことは_toNpcName_に伝えてあげてね。_where_" }, + + 3236 : { QUEST : "ここらでロウボットを倒そうにも、数が多すぎて!\aキミも手伝ってくれない?\aロウボットビルを3棟奪回してくれればいい" }, + 3237 : { QUEST : "よくやった!\aだが今度はセルボットが増えてきてしまったようだ。\aセルボットビルを3棟奪回して戻ってきてくれれば、お礼ははずむよ。" }, + + 3238 : { QUEST : "なんてこった!デイジー・ガーデンのカギが\"オマカセンヌ\"にぬすまれたらしい。\a取り返してきてくれるかい?\aそうそう、オマカセンヌはセルボットビルの中にしかいないから、覚えておいてね。" }, + 3239 : { QUEST : "あれれ、間違ったカギを持ってきてしまったみたいだよ。\aひつようなのはデイジー・ガーデンのカギ。\aがんばって探してね!持っているのは\"オマカセンヌ\"だよ!" }, + + 3242 : { QUEST : "なんてこった!デイジー・ガーデンのカギがホウノトリにぬすまれたらしい。\a取り返してきてくれるかい?\aそうそう、ホウノトリはロウボットビルの中にしかいないから、覚えておいてね。" }, + 3243 : { QUEST : "あれれ、間違ったカギを持ってきてしまったみたいだよ。\aひつようなのはデイジー・ガーデンのカギ。\aがんばって探してね!持っているのはホウノトリだよ!" }, + + 3240 : { QUEST : "_toNpcName_が言ってたんだけど、ホウノトリが彼の店の鳥のエサを盗んでいってしまったんだって。\aサッサの商品を持ってるホウノトリが見つかるまで、ホウノトリ達をやっつけてくれないかな。\aホウノトリがいるのはロウボットビルの中だけだよ。_where_", + COMPLETE : "ああ、僕の商品を見つけてくれたんだね、ありがとう!\aお礼にこれをあげるね。", + INCOMPLETE_WRONG_NPC : "鳥のエサを取り返すことができたね、ごくろうさま!\aさあ、今度はそれを_toNpcName_に持っていってあげてね。_where_", + }, + + 3241 : { QUEST : "この辺のコグビルは背が高くなりすぎて、なんだかこわいよ。\aそこでキミには背の高いビルを何棟かくずすことをお願いしたいんだ。\a3階建てのビルを5棟とりもどしたらごほうびをあげるよ。", + }, + + 3250 : { QUEST : "オークストリートにいるたんていのメイが「セルボット本部」の報告書の話を聞いたみたいなんだ。\aちょっと彼女のところに行って、彼女を手伝ってもらえないかな?", + }, + 3251 : { QUEST : "最近、この近所の様子がなんだか変なのよ!\aセルボット達がやたらにうろついてると思わない?\aこのストリートの先にコグたちが本部を作ったって話も聞いたし…\a悪いんだけど、ストリートの先に行って、何かヒントをつかんでくれないかしら。\aコグ本部にいるセルボットを見つけて、5体やっつけたら報告してちょうだい。", + }, + 3252 : { QUEST : "それじゃあ、教えてもらおうかしら!\aえっ、何ですって!!\aセルボット本部??\nそんなぁ!\n何とかしなくちゃ!\aミスター・ジャッジに急いで知らせないと!彼ならどうすればよいかを教えてくれるはず!\aすぐに行って、伝えてあげて!彼はこの先をちょっと行ったところにいるわよ!", + }, + 3253 : { QUEST : "ん?何かお困りかな?\nごらんの通り、私は忙しい…\aは?\nコグ本部?\aお?\nそれはありえん。\aきっとキミが間違っているのだ。\nまったくもってばかげた話だ。\aほ?\n私と議論してもムダだ。\aそれならば、証拠を見せてもらわなくては。\aもしセルボット達が本当にコグ本部を作っているのならば、設計図をもっているはずだ!\aなにしろコグたちはペーパーワークが好きだからな!\aその本部とやらにいるコグ達をやっつけて、設計図を持ってきたら、議論してやってもいいぞ。", + }, + 3254 : { QUEST : "また、キミか!\n設計図? 持ってきたのか?\aどれどれ…\nん? 工場?\aセルボット達を作るところに違いない… む? これは何だ?\aほう、思ったとおりだ。\a彼らはセルボットのコグ本部を建設中なのだ。\aこれは良くないな! 電話をしないと! あー忙しい、忙しい。それじゃあな!\aお? そうそう、たんていのメイのところに設計図を持っていってくれ。\a彼女ならなんらかのヒントがわかるかもしれん。", + COMPLETE : "ミスター・ジャッジは何だって?\a思ったとおりだったわけね。 どうしましょう? 設計図を見せて。\aふーん…\nコグを大量生産する工場を作っているって訳ね。\aとても危険そうね。\nゲラゲラメーターが増えるまでは立ち寄らないほうがよさそうね。\aゲラゲラポイントがたくさん増えるまでは、いろいろとコグ本部についていろいろ調べたほうが良さそうね。\a本当にありがとう。これはごほうびよ!", + }, + + + 3255 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3256 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3257 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3258 : { QUEST : "コグたちが本部の中で何をたくらんでるかわからなくて、みんな混乱しているんだ。\a彼らのところに行って直接、何か情報を取ってきてほしいんだ。\aもし、コグ本部の中でセルボット達からメモを4つ取ってきてくれれば、少しは混乱がなくなるんじゃないかな?\a最初のメモを手に入れたら、ここに持ってきてくれないかな?そうしたら何かわかるかもしれないし。", + }, + 3259 : { QUEST : "すばらしい!\nさてさて、メモには何て書いてあるかな…\a「セルボット達へ:」a\「私はセルボットのタワーの一番上のオフィスにいて、キミたちコグのレベルを『格上げ』する業務をしている。」\a「キミたちが『メリット』をたくさん集めたら、ロビーにあるエレベーターに乗って私に会いに着なさい。」\a「休み時間は終わりだ。さっさと仕事に戻りなさい!\a「セルボット\nコグゼキュティブ\nより」\aははーん。フリッピーにこれを見せないと!\a今すぐ、僕から送っておくから、キミは2番目のメモを取りにいってね。", + }, + 3260 : { QUEST : "ああ、よかった。\n戻ってきてくれて!\aえーと、次のメモには…\a\「セルボット達へ:」\a「セルボットのタワーはこの度、トゥーンを寄せ付けないために新しいセキュリティーシステムを導入したわ。」\a「セルボットタワーで捕まったトゥーン達は尋問のためにとらわれるようになったのよ。」\a「続きはロビーで会って、話し合いましょう!」\a「オマカセンヌより」\a非常に興味深い…\nさっそく、フリッピーに送らないと…\aキミは3番目のメモをよろしく頼むよ!", + }, + 3261 : { QUEST : "_avName_!\nメモは、っと。\a\「セルボット達へ:」\a「どうやらトゥーン達がセルボットタワーに入る方法を見つけたようだ。」\a「今晩の打ち合わせのときに詳しく話すことにするよ。」\a「ツーハーンより」\aほほう、色々わかってきたぞ!\aもう1つメモがあれば、十分な情報が集められるからよろしく頼むよ!", + COMPLETE : "キミならやれると信じてたよ!\nふむふむ、\a「セルボット達へ:」\a「昨日、ビッグスマイルとランチオンミーティングをしたよ。」\a「彼は、最近とても忙しいコグゼキュティブについて話をしてくれたんだけど、」\a「どうやら一生懸命働いて『格上げ』されたコグとしか会ってくれないらしいよ。」\a「あ、そうそう。\nオオゲーサと日曜日にゴルフに行くんだ。」\a「タッシャーナより」\aうーん、_avName_。これは非常に助かる情報だったね。\aこれはキミへのごほうびだよ。", + }, + + 3262 : { QUEST : "_toNpcName_ がセルボット本部の工場について何か新しい情報をつかんだみたいだよ。\a彼に会って、確認するといいよ!_where_" }, + 3263 : { GREETING : "やあ!", + QUEST : "わたしがコーチのヨーガだ。\aいいか、よーく聞いてくれ!\nセルボット達がとてつもなく大きな工場を完成させたみたいだ。1日24時間、セルボットを作り続けるつもりらしい。\aキミのトゥーン仲間たちと一緒に工場をたたきつぶしにいってくれ!\aセルボット本部の中で、工場へのトンネルを探し出し、工場のエレベーターに乗ってくれ!\aギャグやゲラゲラメーターを一杯にしてから、強いトゥーンたちと一緒に立ち向かおう!\a中にいる工場長を倒せば、セルボットの生産がきっと遅れるはずだ!\aどうだ?キミにできるかな?", + LEAVING : "それじゃあな!", + COMPLETE : "おー!やるじゃないか!\aちゃんとコグのパーツの一部を見つけたようだな。\aコグを作る途中で出来たものに違いない!\a持ち運びも出来る大きさだから、時間があるときに集めてみると良いかもな。\aひょっとしたら、コグのスーツのパーツ全てが集まるかもしれないしな。何かに使えるかもしれんし…", + }, + + 4001 : {GREETING : "", + QUEST : "次に覚えたいギャグトラックを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "次に覚えたいギャグトラックを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "トムの研究の手助けをしてあげるのもいいんじゃないかな?_where_", + }, + 4201 : { GREETING: "やあっ!", + QUEST : "最近、楽器がよく盗まれるようになってしまっていてね。\a店オーナーの仲間とちょっとした調査をしているんだ。\aどういう手口で行われているか解明できるかもしれない。\aティナの店に寄って、コンチェルティナのもくろくのことを聞いてくれないかな。_where_", + }, + 4202 : { QUEST : "うん、トムとは今朝会ったわ。\aもくろくならここにあるわよ。\aすぐに彼に持っていってあげてね。_where_" + }, + 4203 : { QUEST : "おお、さすがだね!これでひとつそろった。\a次はユキのところへ行って、彼女のもくろくを持ってきてくれ。_where_", + }, + 4204 : { QUEST : "ああ、もくろくね!\aすっかり忘れちゃってたわ。\aキミが10体のコグを倒している間に作り終えることができると思うんだけど…\aそれをやったらまた来てみて", + INCOMPLETE_PROGRESS : "31、32…ああっ!\aわからなくなっちゃったじゃない!", + GREETING : "", + }, + 4205 : { QUEST : "ああ、いたいた。\a時間かかっちゃってごめんね。\aこれをトムに持っていってあげてね。私からもよろしくって。_where_", + }, + 4206 : { QUEST : "ふむ、これはおもしろい。\aこれでなんとかなるかも…\aよし、あとはバイオレットのもくろくだけだ。_where_", + }, + 4207 : { QUEST : "え、もくろく?\aもくろくなんて、もくろく帳がなくちゃ作れないわよ。\aクレフのところに行って、もくろく帳を持っているか聞いてみて。_where_", + INCOMPLETE_PROGRESS : "もくろく帳はあったの?", + }, + 4208 : { QUEST : "もくろく帳?おれ様の店ならあるに決まってるよ~。\aでもタダじゃやれねえな~。\aどうだ、クリームパイまるごと1個と交換ってのは。", + GREETING : "いよう!", + LEAVING : "去る者は追わずってやつさ。", + INCOMPLETE_PROGRESS : "ひときれじゃあダメだ。\aおれ様はハラがへってんのよ。まるごとじゃなきゃあダメだ。", + }, + 4209 : { GREETING : "", + QUEST : "うぅ~ん!\aやっぱりうまいね~!\aほれ、約束のもくろく帳だ、バイオレットに持ってってやんな。_where_", + }, + 4210 : { GREETING : "", + QUEST : "ありがとう、これで何とかなりそうよ。\aさて、と…「バイオリン: 2」\aはい、できたわよ!", + COMPLETE : "おお、ありがとう、_avName_。\aこれで盗人たちをなんとかふんじばれるかも知れない。\aどうだ、もっと協力しないか?", + }, + + 4211 : { QUEST : "そういえばドクター・アイタタがひっきりなしに電話をかけてきてるんだ。いったいどうしたのか、聞いてきてくれないか?_where_", + }, + 4212 : { QUEST : "おやおや、トゥーンHQもやっと誰かよこしてくれたんだね。\aうちにはもう何日も患者が来ていないんだ。\aスウジスキーのやつらがうようよしているせいさ。\aこの辺の住民にもカネ至上主義の影響を与えかねんしな。\aスウジスキーを10体やっつけくれないか、患者が戻ってくるか試してみたいんだよ。", + INCOMPLETE_PROGRESS : "まだまだ患者が来ないなぁ。がんばってくれ!", + }, + 4213 : { QUEST : "もしかしたら原因はスウジスキーじゃないのかもしれんな。\aマネーボット全体がいかんのだろう。\aマネーボットを20体おっぱらってくれたら誰かが診療に来るかも…", + INCOMPLETE_PROGRESS : "20体はたしかに多いが、きっとなにかの結果を出してくれるだろう。", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "まったくもって理解できん!やっぱり患者が1人も来ないんだよ。\aこうなったら大元をたたくしかないのか…\aためしにマネーボットビルを倒してみてくれ。\aそれでダメならあきらめるさ。", + INCOMPLETE_PROGRESS : "マネーボットビルひと棟でいいんだ!たのむ!", + COMPLETE : "やっぱりだめだ、1人も来ない…\aだがな、ひとつ気がついたことがある。\a私の診療所には、コグが来る前にも患者が来たことがなかったのさ。\aだがあいつらをおっぱらってくれて感謝しているよ。\aお礼にこれをあげよう、きっと役にたつよ。" + }, + + 4215 : { QUEST : "どうやらアンナが困っているらしい。\a行って助けてあげてくれないか。_where_", + }, + 4216 : { QUEST : "さっそく来てくれてうれしいわ!\aどうやらコグが私のお客のクルーズチケットを盗んでいってしまったみたいなの。\aオオゲーサがチケットをたんまり持ってここから出て行くのをユキが見たらしいわ。\aモックンのアラスカ旅行のチケットを取り戻してくれないかしら", + INCOMPLETE_PROGRESS : "そろそろオオゲーサが出てくるんじゃないかしら…", + }, + 4217 : { QUEST : "まあ、みつけたのね!\aじゃあ、ついでにこれをモックンのところに届けてくれないかしら?_where_", + }, + 4218 : { QUEST : "うわぁ、ぼくのアラスカ旅行のチケットじゃないか!\aやった~!\aもうコグのやつらにはうんざりだよ。\aああそうだ、またアンナがキミの手をかりたいみたいだけど。_where_", + }, + 4219 : { QUEST : "オオゲーサのやつ!\a今度はタバサのジャズフェスティバルチケットを取り返してほしいの。\aさっきと同じようにさくっと、ね!", + INCOMPLETE_PROGRESS : "オオゲーサならそこら辺にいるはずよ。", + }, + 4220 : { QUEST : "やったわね!\aこれタバサのところにもっていってくれない?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "気をつけてね~", + QUEST : "イエーイ!やった!\aさーてジャズフェスタで踊りまくってやるわよ。\aそうそう、キミもどこかに行っちゃう前にまたアンナのとこに行ったほうがいいと思うよ。_where_", + }, + 4222 : { QUEST : "お願い、これで最後だから!\aこんどはバリーの歌唱コンクールのチケットを取り返してきてくれないかしら", + INCOMPLETE_PROGRESS : "お願いよ、_avName_。\aバリーも困ってるのよ~", + }, + 4223 : { QUEST : "よかった、これでバリーも安心して行けるわ!_where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "いやぁ、助かったよ!\aありがとう!\a今年こそ私達が1位をとれる気がするよ。\aアンナからの伝言だ、お礼をしたいので来てくれ、と。_where_\aそれではね、ありがと~う!", + COMPLETE : "助けてくれて本当にありがとう、_avName_。\aあなたはトゥーンタウンの宝だわ。\aそうそう、お宝といえば…", + }, + + 902 : { QUEST : "レオに会いに行ってあげて。\aメッセージの配達をしてくれる人を探しているみたいなの。_where_", + }, + 4903 : { QUEST : "いよっす!\aおれっちのカスタネットがなんだかくもっちまって、今夜のショーがうまく行くか心配なんだ。\aカルロスのところに持っていって、みがいてくれるかどうか聞いてみてくんないかな。_where_", + }, + 4904 : { QUEST : "エエ、みがけると思いマスよ。\aでもソレにはイカからとった青いインクが必要なんデス", + GREETING : "コニチハー!", + LEAVING : "サヨナーラ!", + INCOMPLETE_PROGRESS : "イカは、つり場があるトコロならドコでも見つけられマスよ。", + }, + 4905 : { QUEST : "エエ、これデス!\aサテ、あとはみがくジカンが必要なダケ…\aアナタ、どうせ待つならコグビルをヒトツたおしてきてみてはどーデスカ?", + GREETING : "コニチハー!", + LEAVING : "サヨナーラ!", + INCOMPLETE_PROGRESS : "ウーン、まだマダ…", + }, + 4906 : { QUEST : "ヨーシ!\aレオのカスタネット、みがきオワリましたヨ。_where_", + }, + 4907 : { GREETING : "", + QUEST : "やったー!\aいいカンジじゃーん!\aあとは…'ビート・クリスマス' の歌詞カードをヘイディからもらってこなきゃいけないんだ。_where_", + }, + 4908 : { QUEST: "こんにちは!\aうーん、その歌詞カードはウチにはないなぁ。\aちょっと時間をくれたら暗記してある歌詞を書き出せるんだけど。\aぼくがそれをやっている間、コグビルを2棟取り返すっていうのはどうだい?", + }, + 4909 : { QUEST : "うぅ~ん…\a思ったよりハッキリ覚えてないみたいだ。\a3階建てのコグビルを倒している間になんとかなりそうだと思うんだけど…", + }, + 4910 : { QUEST : "できたよ!\a待たせてしまってごめんね。\aこれをレオに持っていってあげて。_where_", + GREETING : "", + COMPLETE : "やったー!いいカンジじゃーん!\aこれでおれっちのショーもいいカンジじゃーん!\aそうそう、アンタにはお礼をしなきゃな" + }, + 5247 : { QUEST : "ここらも最近荒れてきてしまって…\aキミもいくつか新しい「トリック」を覚えたほうがいいよ。\a_toNpcName_が私にいろいろ教えてくれたんだけど、キミも会ってみるといいよ。_where_" }, + 5248 : { GREETING : "ああ、こんにちは…", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミへの宿題は、ちょっと難しかったかな?", + QUEST : "ああ、いらっしゃい、みならいくん。\aパイゲームのことなら私にまかせたまえ。\aだが訓練を始める前に、ちょっとキミの力をみせてくれ。\a外にいる一番おおきいコグを10体やっつけてみなさい" }, + 5249 : { GREETING: "ふむ…", + QUEST : "すばらしい。\a今度は釣り人としてのスキルを見せてくれ。\aきのう、池に3つのもこもこサイコロを落としておいた。\aそれらを釣って私に持ってきなさい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "釣竿の扱いはまだまだらしいな" }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いロウボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いボスボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いマネーボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いセルボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5200 : { QUEST : "コグめ、また悪さをしておる。\a_toNpcName_がコグに何かを盗まれたらしい。行って手助けしてやってくれ。_where_" }, + 5201 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきヘッドハンターの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5261 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきアイソマンの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5262 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきカネモッチンの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5263 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきドクター・トラブルの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5202 : { QUEST : "最近、ブルブルタウンが見たこともないような屈強なコグ達に襲われているんだ。\a危ないから、キミももっとギャグを持ち歩いていたほうがいいよ。\a_toNpcName_がもっとギャグを入れることができるふくろを持っているらしいから、相談してみたらどうかな。_where_" }, + 5203 : { GREETING: "ああん?キミ、私のソリチームにいたっけ?", + QUEST : "なんだって、ふくろが欲しい?\aうーん、この辺にあったような気がするんだが…トボガンに入ってるかもしれないなあ。\aだが、ずっと前のレース以降あのトボガンを見てないしなあ。\aもしかしたらコグにとられたのかもしれん。", + LEAVING : "私のトボガンを見かけたかい?", + INCOMPLETE_PROGRESS : "えーっと、キミは誰だったっけ?すまんね、事故にあって以来ちょっと記憶が怪しくてな~" }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、これが私のトボガンだよ。だがふくろが見当たらんなあ。\aバンピー・ノギンがチームメイトだったんだが、彼が持ってるのかもな。_where_" }, + 5205 : { GREETING : "あああ、頭が~!", + LEAVING : "", + QUEST : "ああ?テッドだって?ふくろ?\aあー、もしかしたらそんなのが私のトボガンチームにいたかも知れんな。\aうう、なんとも頭が痛くてろくに考え事もできんわい。\a凍った池から氷を少し釣りあげてくれんか?", + INCOMPLETE_PROGRESS : "あうう、頭が痛い~!氷をくれ~!", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、ずいぶん楽になったわい。\aえーと、それでキミはテッドのふくろを探しているんだったっけな。\aあれはレースでの事故の時にシミアン・サムの頭にかぶさってたぞ。_where_" }, + 5207 : { GREETING : "Eeeep!", + LEAVING : "", + QUEST : "ふくろ?バンピー?\aワタシはビルが怖いのヨ!ビルを崩したらふくろあげるヨ!", + INCOMPLETE_PROGRESS : "もっともっと!ビル倒して、怖いのヨ!", + COMPLETE : "オーウ!スバラシーイ!" }, + 5208 : { GREETING : "", + LEAVING : "キャー!!!", + QUEST : "オーウ!スバラシーイ!\aふくろはスキー・クリニックにあるヨ" }, + 5209 : { GREETING : "ちーす!", + LEAVING : "じゃあな!", + QUEST : "あのサム・シミアンもおもしろいキャラだよなあ。\aあんたもあいつぐらいワイルドだってんならふくろをあげてやってもいいぜ。\aコグを何体かやっつけてきな。じゃあな、行ってらっしゃい!", + INCOMPLETE_PROGRESS : "そんなんで満足しちゃいけねえよ!もっと倒してこなきゃダメだ。", + COMPLETE : "ふぅん、なかなか悪くねえな。かなりの数のコグをやっつけたじゃねえか。\aほい、やくそくのふくろだぜ" }, + + 5210 : { QUEST : "_toNpcName_がこの辺のだれかに恋してるらしいんだ。\a彼女の恋の手助けをしてやったら、お礼がもらえるかもな。_where_" }, + 5211 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にクチバシのあるコグが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5264 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にヒレのあるコグが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5265 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にオマカセンヌが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5266 : { GREETING: "Boo hoo.", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にデッパラーダが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5212 : { QUEST : "手紙をみつけてくれたのね、ありがとう!\aそれでね、あのう、ここらで一番ハンサムなワンちゃんに届けてくれないかしら…お願い!", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "手紙を届けてくれなかったのね…", + }, + 5213 : { GREETING : "あら、こんにちは。", + QUEST : "今手紙を持ってこられても困るんだよね。\aボクのワンちゃん達はみんなどこかに連れ去られてしまったから。\a全員連れて帰ってきてくれたら受け取ってあげてもいいけど", + LEAVING : "", + INCOMPLETE_PROGRESS : "ああ、かわいそうなワンちゃんたち!" }, + 5214 : { GREETING : "", + LEAVING : "ごきげんよう!", + QUEST : "ボクの粒ぞろいのワンちゃん達を連れ戻してくれてありがとう。\aそれではその手紙を見てみようか。なになに…\nおやおや、ボクに思いを寄せる人がまたひとり…か。\aキミにはボクのともだち、カールの所へ行ってもらいたい。\aキミもきっと彼を気に入るさ。_where_" }, + 5215 : { GREETING : "エッヘッヘ…", + LEAVING : "また来なさい、ね。", + INCOMPLETE_PROGRESS : "デカいやつらがまだいるから、そいつらをおっぱらったらまた来なさい。", + QUEST : "いったい誰がキミをここに送り込んだのだね?タカビシャな人間はどうも気にくわんのだよ。\aだがコグのほうがよっぽど気にくわん。\a特にデカいコグたちを追っ払ってきたら手伝ってやろう。" }, + 5216 : { QUEST : "やくそく通りキミを手伝ってやろう。\aこのゆびわを彼女のもとに持っていってやれ。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まーだゆびわを持っていってないのかい?", + COMPLETE : "きゃああ!すごいわすごいわ!ありがとう!\aそうだわ、お礼にこれをあなたにあげたいの。もらってくれる?", + }, + 5217 : { QUEST : "_toNpcName_がなんだか困っているらしいわよ。_where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "この辺にもっとオマカセンヌがいると思うんだが…", + QUEST : "たすけてくれぇー!もう耐えられない!\aオマカセンヌのせいでもう気が狂いそうだ!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだまだだよ、さっきこのへんで1体見たんだ!", + QUEST : "ありがとう。でも、こんどはデッパラーダ達が問題なのよ。\aお願い、なんとかして!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだまだだよ、さっきこのへんで1体見たんだ!", + QUEST : "わかったぞ、問題はシャークロンなんだ!絶対そうだ!\a手伝ってくれるんだろう?" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "もしかするともしかするんだけど、問題はコグじゃないのかも…。\aファニーにすっきりするポーションを作ってきてもらえないかな、それで解決するかも。_where_" }, + 5222 : { LEAVING : "", + QUEST : "まーったくもう、ハリーもすぐああなるんだから。\aアレを直すやつを作ってあげるから、ちょっと待っててね。\aあら、イワシのヒゲがないわ…\a池に行って少し釣ってきてくれない?", + INCOMPLETE_PROGRESS : "イワシのヒゲ、見つかった?", }, + 5223 : { QUEST : "よーっし、これでOKよ。\aはい、これをハリーに持っていってあげて。彼の興奮状態をすぐに落ち着かせるから", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "ほらほら、早くそのポーションをハリーに届けてやんな。", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "頼むよ、早くホウノトリたちをおっぱらってくれー!", + QUEST : "ああ、やっと戻ってきた!\aそのポーションをおくれ、早く!\aゴクゴクゴク…\aうええっ、まずい!\aでも…不思議だなぁ、なんだか落ち着いた気がするよ。これでやっとちゃんと考えられる気がする。\aそう、問題のコグはホウノトリだったんだ!", + COMPLETE : "やったぁ!これでリラックスできるよ!\aえーと、たくさんがんばってくれたお礼に…これこれ、これをあげるよ。" }, + 5225 : { QUEST : "あのだいこんパン事件以来、グランピー・フィルは_toNpcName_に対して怒ってるんだ。\aマモルだったら仲直りさせてあげられるかも…_where_" }, + 5226 : { QUEST : "キミも、グランピー・フィルが私に対して怒ってるのを聞いたみたいだね。\a私はただ、あのだいこんパンの件で私は悪気はなかったんだけど…\aキミなら彼をはげましてあげられるかもしれない。\aフィルはマネーボットがきらいで、特にやつらのビルは耐えられないらしい。\aマネーボットビルをいくつか倒したら少しはよくなるかも", + LEAVING : "", + INCOMPLETE_PROGRESS : "あと何棟かやってみてくれないか?", }, + 5227 : { QUEST : "すごいぞ!さ、フィルに報告してやってくれ。_where_" }, + 5228 : { QUEST : "ああ、そうなのかい。\aマモルのやつ、そんな事で許されると思ってんのかい。\aあいつのだいこんパンで歯が欠けたんだぞ!\aドクター・シンキクサーイだったら治せるかもしれんから、持っていってみてくれ。", + GREETING : "むにゃむにゃむにゃ", + LEAVING : "もごもごもご", + INCOMPLETE_PROGRESS : "またあんたかい。わしの歯を治してくれるんじゃなかったのかい。", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのマネーボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのセルボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのロウボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのボスボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5230 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ドロビッグが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5270 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグチーズが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5271 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグスマイルが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい??" }, + 5272 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグホワイトが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5231 : { QUEST : "そうそう、それだよ!\aさっそくフィルに持っていってあげてくれないか。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "早くフィルに歯を持っていってあげてくれ。", + }, + 5232 : { QUEST : "おう、ありがとな。\aんむぐぐ…\aよし、これでどうだ!\aそうだな、うん。マモルを許してやってもいいかな。", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "ああ、よかった。\aフィルがずっと怒りっぱなしでいるとは思わなかったけど…安心したよ。\a友情の証として、フィルにまつぼっくりパンを焼いたんだ。持っていってくれるかい?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "急いでおくれよ、まつぼっくりパンはあったかいほうがおいしいんだ。", + COMPLETE : "んん、これは何だね?わしにかい?\aむしゃむしゃ…\aあいたたた!は、歯が!まったく、あのマモルめが!\aいやしかし、キミのせいではないしな。ほれ、これはおだちんだよ。", + }, + 903 : { QUEST : "キミもそろそろブリザード・ウィザードの_toNpcName_の最後のテストに挑んでもいいかも知れないね。_where_", }, + 5234 : { GREETING: "", + QUEST : "おっ、戻ってきたね。\a始める前にまずは食事といこうか。\aスープに合うでこぼこのチーズを持ってきてくれ。\aでこぼこチーズはコグのビッグチーズからしか得られないよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "でこぼこチーズが必要だよ。" }, + 5278 : { GREETING: "", + QUEST : "おっ、戻ってきたね。\a始める前にまずは食事といこうか。\aスープに合うキャビアを持ってきてくれ。\aキャビアはコグのビッグスマイルからしか得られないよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "キャビアが必要だよ。" }, + 5235 : { GREETING: "", + QUEST : "ふつうの人間はふつうの食器を使うべきだ。\aコグが私の「ふつうのスプーン」を取っていってしまったからスープも食べられない。\aスプーンを取り返してきてくれ、持っているのはたぶんドロビッグだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スプーンがなきゃ食べられないよ。" }, + 5279 : { GREETING: "", + QUEST : "ふつうの人間はふつうの食器を使うべきだ。\aコグが私の「ふつうのスプーン」を取っていってしまったからスープも食べられない。\aスプーンを取り返してきてくれ、持っているのはたぶんビッグホワイトだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スプーンがなきゃ食べられないよ。" }, + 5236 : { GREETING: "", + QUEST : "ありがとう、ありがとう。\aズズッ、ゴクン…\aああ、うまい。さて、こんどはしゃべるカエルを池から獲ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "しゃべるカエルは見つかったかい?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだデザートを持ってきていないね。", + QUEST : "ほほう、それはたしかにしゃべるカエルだね。こっちにおくれ。\aなんだって、カエルくん?\aうんうん。\aふむふむ。\aカエルは言う、「デザートが必要だ」と。\aというわけで_toNpcName_のところからアイスクリームをいくつか持ってきてくれ。\aカエルくんはアズキ味をご所望らしい…_where_", }, + 5238 : { GREETING: "", + QUEST : "なるほど、魔術師がキミをここに送ったのだな。申し訳ないが、アズキ味は売り切れてしまった。\aというより、コグの一団が持っていってしまったのだ。\aビッグスマイルのためだとか何とか言っていたな。\a取り返してきてくれると助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "アイスクリームは見つかったかい?" }, + 5280 : { GREETING: "", + QUEST : "なるほど、魔術師がキミをここに送ったのだな。申し訳ないが、アズキ味は売り切れてしまった。\aというより、コグの一団が持っていってしまったのだ。\aビッグチーズのためだとか何とか言っていたな。\a取り返してきてくれると助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "アイスクリームは見つかったかい?" }, + 5239 : { QUEST : "取り返してきてくれてありがとう!\aはい、これがミニおじさんの分だよ。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "とけてしまう前にそれをミニおじさんに持っていったほうがいいよ。", }, + 5240 : { GREETING: "", + QUEST : "よろしい、よろしい。ほれ、カエルくんも。\aペロリ、ペロリ。\aよし、準備もあともう少しで整いそうだ。\a手をかわかす粉を持ってきてくれ。\aビッグホワイト達がカツラのメンテに使っている粉が良いから、それを取ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "粉は見つかったかね?" }, + 5281 : { GREETING: "", + QUEST : "よろしい、よろしい。ほれ、カエルくんも。\aペロリ、ペロリ。\aよし、準備もあともう少しで整いそうだ。\a手をかわかす粉を持ってきてくれ。\aビッグスマイル達が鼻のテカリをかくすために使っている粉が良いから、それを取ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "粉は見つかったかね?" }, + 5241 : { QUEST : "よし。\a前も言ったように、パイを投げるには手を使ってはいけないのだ。\a魂をこめて、念で投げる。\aこれは実に深い。深すぎて考える時間が必要だから、キミはコグビルをいくつか倒してきなさい。\aタスクが終わったらここに戻ってきなさい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミのタスクはまだ終わっていないよ。", }, + 5242 : { GREETING: "", + QUEST : "まだ考えがうまくまとまらない状態だが、キミが最終テストにふさわしい事はわかった。\aしゃべるカエルは言う、「ガールフレンドが欲しい」と。\a行ってしゃべるカエルちゃんを探してきなさい。それが最終タスクだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "しゃべるカエルちゃんはどうしたんだ?", + COMPLETE : "ふぅ!考えすぎてなんだか疲れてしまった。\aほれ、これをごほうびにあげるから私を休ませてくれ" }, + + 5243 : { QUEST : "あせかきピートのせいでここいらがくさくなってきちゃったんだよ。\aおふろに入るよう彼を説得してくれないか?_where_" }, + 5244 : { GREETING: "", + QUEST : "そうだねぇ、ボクは確かによく汗をかくかも。\aでもねぇ、うちのシャワーのこわれたパイプを直さなきゃ体も洗えないよ。\aちっちゃいコグならパイプを直せるギアを持っているかもね。\aガミガミーナからひとつ取ってきてくれない?", + LEAVING : "", + INCOMPLETE_PROGRESS : "ギアは見つかったの?" }, + 5245 : { GREETING: "", + QUEST : "うん、これなら何とかなるかも。\aでも、一人でお風呂に入るのはさみしいよ…\a池にいって、アヒルの人形をひとつ取ってきてくれない?", + LEAVING : "", + INCOMPLETE_PROGRESS : "アヒルは見つかったの?" }, + 5246 : { QUEST : "アヒルちゃんを取ってきてくれてうれしいけど…\aうちのまわりの建物がこわくて落ち着かないよ。\aもう少し建物の数が少なかったら安心できるんだけど…", + LEAVING : "", + COMPLETE : "うん、じゃあお風呂に入ることにするよ。これはキミにあげる。", + INCOMPLETE_PROGRESS : "まだ建物の存在がこわいよ…", }, + 5251 : { QUEST : "ラウンジ・ラッサーが今夜、ショーをやるんだって。\aでもなんだかトラブルがあってこまってるみたい。_where_" }, + 5252 : { GREETING: "", + QUEST : "おおっと、手伝いにきてくれたのかい?\aバンから機材を下ろしていたらコグどもがギアを持っていっちまったんだよ。\a盗まれたマイクを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "おいおい、マイクなしじゃ歌えないよ。" }, + 5253 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aデッパラーダが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5273 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aオマカセンヌが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5274 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aシャークロンが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5275 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aホウノトリが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5254 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグスマイルだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5282 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグチーズだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5283 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはドロビッグだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5284 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグホワイトだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + + 5255 : { QUEST : "あんた、もうちょっとゲラゲラポイントがあってもいいみたいだな。\a_toNpcName_なら安くしてくれるかもしれないぜ。\aちゃんと書き出してもらえよ。_where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "約束は守ってくださいよ。", + QUEST : "ゲラゲラポイントがほしいって?\aウチに来て正解ですよ、お客さん!\aボスボットを何体かたおしてくれたら…\aサービスしますよ。" }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "約束は守ってくださいよ。", + QUEST : "ゲラゲラポイントがほしいって?\aウチに来て正解ですよ、お客さん!\aロウボットを何体かたおしてくれたら…\aサービスしますよ。" }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "え?ロウボットをやっつけるようにお願いしませんでしたか?\aまあいいでしょう、でもこれであなたには私に借りができましたからね。", + INCOMPLETE_PROGRESS : "まだ終わっていませんよ。", + QUEST : "え、終わった?すべてのコグをやっつけた?\a何かの聞き間違いでしょう、私はセルボットを倒してくれと言ったのです。" }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "え?ロウボットをやっつけるようにお願いしませんでしたか?\aまあいいでしょう、でもこれであなたには私に借りができましたからね。", + INCOMPLETE_PROGRESS : "まだ終わっていませんよ。", + QUEST : "え、終わった?すべてのコグをやっつけた?\a何かの聞き間違いでしょう、私はマネーボットを倒してくれと言ったのです。" }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "ゲラゲラポイントs, でもひょっとしたら_toNpcName_が手伝ってくれるかも。\a彼はちょっと気むずかしいところがあるんだけどね。_where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "思ったとおりだ!\aありがとな!ゲラゲラポイントだ!", + INCOMPLETE_PROGRESS : "やあ!\aここでまた何しているんだい。", + QUEST : "ゲラゲラポイントが欲しいって?\aまず最初に悪いロウボット達をやっつけてからにしてくれ。" }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+"が危険なコグ達であふれかえっているんだ。\aもし僕がキミなら、ここではギャグをもっと持ち歩くね。\aもしキミが足を棒にして働くんだったら、_toNpcName_が大きいバッグを作ることができるみたいだよ。_where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだロウボット達がそこら中にうようよしている。\aやっつけてくれ!" , + QUEST : "大きいバッグ?\aキミのために一つ作ってあげられるかも。\a毛糸が必要だけど、\a残念ながら、昨日の朝、ロウボット達に盗まれたんだ。" }, + 5305 : { GREETING : "やあ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "コグ達をもっとやっつけて!\aまだ毛糸がたりないよ。", + QUEST : "上質の毛糸だね。\a色はちょっと好みじゃないけど。\aじゃあ、こうしよう。\aキミがもっと強いコグ達をやっつける間に、\a僕がこの毛糸を染めるね。" }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "もっとやっつけてくれないと!", + QUEST : "毛糸が全て染まったよ。でもちょとした問題があるんだ。\aあみ針がどこにも見つからないんだ。\a確か最後に見たのは池だったけなぁ。" }, + 5307 : { GREETING : "", + LEAVING : "本当に助かるよ!", + INCOMPLETE_PROGRESS : "ローマは一日にして編めず" , + QUEST : "確かに僕のあみ針だ。\aあみものをしている間に、コグビルをやっつけてくれないかい?", + COMPLETE : "本当にすごいね、キミは!\aそしてこれもすごいよ…\aキミの新しいバッグだよ!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_が何か問題をかかえてるみたいなんだ。\aちょっと立ち寄って聞いてきてくれない?_where_" }, + 5309 : { GREETING : "来てくれて本当にうれしいよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "急いで!ストリートにコグがあふれてるんだ!", + QUEST : "ロウボット達が乗っ取りを始めているんだ。\aひょっとしたら僕をサイバンショに連れて行くんじゃないかと心配なんだ。\aあいつらを街から追い出してくれないかい?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "彼らはきっと僕を連れてこうとしているんだ…", + QUEST : "ありがとう。ちょっと気が楽になったよ。\aでもまだ、もうひとつやることがあるんだ。\a_toNpcName_のところに行って、アリバイをゲットしてきてくれないかい?_where_" }, + 5311 : { GREETING : "ヤッター!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミが見つけないと彼を助けてあげられないよ。", + QUEST : "アリバイ?!それはいいアイデアだね!\aホウノトリがきっともっているはず。" }, + 5312 : { GREETING : "ついに!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "ほっ。気持ちがようやく落ち着いたよ。\aこれがキミへのごほうびだ。", + QUEST : "ほんとうにすばらしい!\a_toNpcName_のところへ急いでいってあげて!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "テティ・テーデンが助けを必要としてるみたい。彼女に手を貸してあげて?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "いらっしゃいませ!\aあら、お客様じゃないのね。\aわかった、恐ろしいコグたちから助けてくれるためにきてくれたのよね。\aもしコグたちからこの街を少し救ってくれたら、わたしが電気の力でキミのために何かしてあげられるんだけどな…。", + INCOMPLETE_PROGRESS : "コグたちをやっつけてくれないと、お手伝いできないのよね。よろしくね!", + COMPLETE : "_avName_、コグたちをやっつけてくれて、ありがとう!\aこれで電気が使えるわ。\aちょっと待ってね。この電気でキミを…。ビビビビビ!\aどう?ちょっとパワーアップした?これからもがんばってね!", + }, + + # Susan Siesta wants to get rich but the コグたち are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "やあ, _avName_, 今すぐには何もないよ。\aあ、ちょっと待って!スーザン・シエスタが助けが欲しいって言ってたよ。なんで、彼女に会いにいかないの?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "まったく!コグたちのせいで、商売があがったりなの。\a_avName_、助けてくれる? \aいくつかのコグビルを取り返してくれたら、ごほうびをあげるわ。", + INCOMPLETE_PROGRESS : "がっかり…。まだコグビルを取り返してないの?", + COMPLETE : "ありがとう!これで商売もうまくいくはず!そんな気がする。\aこれで楽しみな釣りの時間も取れるわ。じゃあ、キミの人生をちょっと豊かにしてあげるね。\aはい、これをどうぞ!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "こんにちは _avName_! ロウフル・リンダ があなたをさがしてたって聞いたよ。.\aちょっと立ち寄って、あいさつしてみなよ。_where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "こんにちは! ワオ、キミに会えてうれしいよ!\a今、るすばんでんわを直している最中なんだけど、部品が足りないんだ。\a3つの棒が必要なんだけど、1つはカッチリンが持っているみたいなんだ。「ロッド」を持ってきてくれる?", + INCOMPLETE_PROGRESS : "まだ、「ロッド」を探しているの?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "これでよし、っと!\a「自動車のベルト」のスペアのもっていたはずなんだけど見つからないなぁ。\aカネモッチンからベルトをひとつ、もってきてくれないかな?ありがとう!", + INCOMPLETE : "うーん。自動車のベルトを持ってきてくれないと、キミを助けられないよ。", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "これこれ!これでチチンプイプイ!ちゃんと動くはず。\aあれ、でも道具がないや。\aセコビッチを倒して、「ペンチ」を持ってきてくれないかな?\aそうしてくれれば、コグたちをやっつける手助けができるんだけどな。よろしくね。", + INCOMPLETE_PROGRESS : "ペンチはまだかい?探しつづけてね!", + COMPLETE : "すばらしい!ペンチでここをしめつけて、っと!\a直ったみたいだね。仕事に戻らないと!\aそういえば、電話がないけど、まあいいか。\aこれはお礼のしるし。グッド、ラック!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "ロッコが助けが必要だって聞いたよ。彼を手伝ってあげられる?_where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "よぉっ!ちょ~どい~とこにきたね~。ごきげんだね!\aオォ、イェ~っ!コグたちにこまってるんだよね~。ボスボット、やっつけてくれるとハッピ~なんだけど、できそ?", + INCOMPLETE_PROGRESS : "やぁ、_avName_。\aボスボット、ど~だい?やくそくしたろ~?\aロッコ、やくそくまもる。これ、きまり~。", + COMPLETE : "よぉ、_avName_!これでボスボットもいばらなくなったかもね~!\aほ~らよっと!どでかいごほうびだ!トラブルにはまきこまれるなよ~!", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "パジャマ・プレイスにいるナットがマネーボット本部のうわさを聞いたって。\aちょっと手伝えるかどうか彼のところに向かってくれる? where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "ちょっと変なうわさを小耳にはさんだんだよね。\aでもひょっとしたらデマかもしれないけど、きっと何かは起こっているんだよ。\aあのマネーボットたちのことさ!\aパジャマ・プレイスのすぐ近くに新しい本部を作ったみたいなんだよね。\aP.J.が知っているみたいなんだよね。\a_toNpcName_に会ってみて!_where_\aで、何か知らないか聞いてみて!", + INCOMPLETE_PROGRESS : "まだP.J.に会ってないの?\a何か別の用事でもあるの?", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、_avName_。どこにいたんだい?\aえっ、マネーボット本部?!何も見てないけどなぁ~。\aパジャマ・プレイスの奥まで行って本当かどうか見てきてくれない?\a本部があったら、中のマネーボットたちを倒して、戻ってきてよ。", + INCOMPLETE_PROGRESS : "マネーボット本部は見つけた?様子を知るために、中のコグたちをやっつけてよ。", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "えっ?!マネーボット本部が実在するって?\aナットに今すぐ伝えてくれない?\a彼のいるすぐ近くにコグ本部があっただなんて信じられる?", + INCOMPLETE_PROGRESS : "ナットはなんて言うかな?まだ彼に会ってないんでしょ?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "P.J.のいうことは聞きあきたんだよね。\aもっとちゃんとした情報がほしいなぁ。その前に、このノミたちをなんとかしないと。\aわかった!もう一回、やっつけに行って「マネーボット本部プラン」を取ってきてよ。\aそしたら信じるよ。", + INCOMPLETE_PROGRESS : "「プラン」はまだ?本部にいるコグたちならきっともっているはず!", + COMPLETE : "えっ、「プラン」を持ってきたって!\aすばらしい!ふむふむ…。\aマネーボットがお金工場で「コグドル」を作っているって!\aきっとマネーボットだらけなんだろうね!でもより深く調べないと!\aコグに変装できればなぁ…。ちょっと待てよ。どこかにコグスーツのパーツがあったはずなんだけど…。\aあった!これを持っていけば何か役に立つはず。手伝ってくれてありがとう!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "カウンテスがキミのことを探し回っていたよ。彼女のところにいって声をかけてあげて!_where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_ね。きっと私のことを助けてくれると信じているわ。\aコグたちがさわがしくて集中できないの。\aいつも寝る前にひつじを数えるんだけど、うるさくていつも数え間違えちゃうのよ。\aもしコグたちをやっつけてくれたら、キミを助けてあげられるんだけど。まかせて!\aで、何匹数えたっけ?あっ、そうそう。ひつじが136匹、ひつじが137匹、…。", + INCOMPLETE_PROGRESS : "ひつじが442匹、ひつじが443、…\aえっ?もう、戻ったの?でもまだ外はうるさいじゃない!\aあーっ、また忘れちゃった!\aひつじが1匹、ひつじが2匹、…", + COMPLETE : "ひつじが593匹、ひつじが594匹、…\aあら?どうやら私の目にくるいはなかったようね。このしずけさ…。\aはい、スウジスキーなわたしからどうぞ。\aスウジ?あらやだ、また忘れちゃった。ひつじが1匹、ひつじが2匹、…", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "かわいそうなザリがお客さんに配達をすることができないみたいなんだ。キミならきっと助けてあげられるはず。_where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "こんにちは _avName_! 配達を手伝ってくれるの?\aすごい!配達する機械がこわれて動けないの。\aちょっと待ってね。これならすぐできるはず!カウボーイ・ジョージが先週、「シタール」を注文したのよ。\a本当に悪いわね。彼に届けてくれる?_where_", + INCOMPLETE_PROGRESS : "うーん、何か忘れてな? カウボーイ・ジョージは「シタール」を待ってるよ。", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "ぼくのシタール! あー、はやく曲をひきたいなぁ。\aザリにありがとうって伝えてくれない?", + INCOMPLETE_PROGRESS : "シタールを見つけてくれてありがとう!ザリがほかに何か手伝ってもらいたいたいと思ってるはすだよ。", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "早かったわね。次は何をすれば良いかって?\aオッケー。マスター・マイクが「せいひょうき」を注文したのよ。\a今度はこれをよろしくね!_where_", + INCOMPLETE_PROGRESS : "「せいひょうき」をマスター・マイクへ持っていてくれないと。_where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "やったね!僕が注文した「せいひょうき」だ!\aそこらじゅうにコグたちがいなければ使えるんだけど…、マネーボットをちょっとやっつけてくれない?", + INCOMPLETE_PROGRESS : "マネーボットのやつらはてごわいでしょ。早く「せいひょうき」を使いたいなぁ。", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "すごいね!これで「せいひょうき」を動かせるよ。.\aザリに来週、また注文するって伝えてくれない?お願い!", + INCOMPLETE_PROGRESS : "今は特に用事はないけど、ザリがキミを待ってるんじゃない?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "で、マスター・マイクが、「せいひょうき」、よろこんでたんだ~。よかったわ!\a次は、えっと…。そうだ、ゼン・グレンが「しまうまのざぶとん」を注文したの。\aはい、どうぞ!彼のところにさっと届けにいってくれる?_where_", + INCOMPLETE_PROGRESS : "ゼン・グレンがきっと、その「しまうまのざぶとん」。待ちこがれていると思うよ。", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "ついに「しまうまのざぶとん」が手に入った! これでやっとメイソウができる。\a………………\aだけどコグたちがさわがしくて集中できやしない!\aコグたちをやっつけてくれるよね?\a幸せにメイソウしたいなぁ~。", + INCOMPLETE_PROGRESS : "まだコグたちがうるさいなぁ!キミはメイソウできる?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "んー。「平和」と「しずけさ」。キミにも幸せがおとずれるよ、_avName_。\aザリに僕がどれだけ喜んでたか伝えてね。ありがとう! ", + INCOMPLETE_PROGRESS : "ザリから電話があって、キミを探してるってさ。会いに行って何が欲しいか聞いてきなよ。", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "「しまうまのざぶとん」、ゼン・グレンがよろこんでくれたんだって!うれしいよ!\aおっと、ちょうど「ひゃくにちそう」が届いたよ。\aローズ・ペタルが待ちわびてるみたいだから、悪いんだけど彼女に届けてあげて!_where_", + INCOMPLETE_PROGRESS : "はやく届けないと「ひゃくにちそう」がかれちゃうよ。", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "何て素敵な「ひゃくにちそう」なの!ザリならきっと届けてくれると思った!\aいや、_avName_ならきっと!ってね。ザリにありがとうって伝えて!", + INCOMPLETE_PROGRESS : "ザリに「ひゃくにちそう」のお礼をちゃんと言ってね!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_、よく戻ってくれたわ。いつも本当にありがとう!キミは本当に元気よね!\さてリストにはなんて書いてあるかしら…。\aこれこれ!この「ザイデコのレコード」をアイ・パチーリに届けてくれる?_where_", + INCOMPLETE_PROGRESS : "きっとアイ・パチーリがザイデコのレコードを待ってるよ。", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "ザイデコのレコード? ザイデコのレコードを頼んだのを覚えてないよ。\aひょっとしたらララバイ・ルーが頼んだのかもしれないね。_where_", + INCOMPLETE_PROGRESS : "きっとザイデコのレコードはララバイ・ルーのものだよ。_where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "ザイデコのレコード! ザリが忘れてたかと思ってた。\a彼女にこの「ズッキーニ」を持って行ってくれる? 彼女なら誰がこれを必要としているか、わかるはず。ありがと!", + INCOMPLETE_PROGRESS : "「ズッキーニ」ならたくさん持ってるわ。ザリにひとつもっていってあげて!", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "ズッキーニ? きっと誰かがほしいはずね。.\a配達リストもあと少しね。\aベイビーフェイス・マクドゥーガルが「ズート・スーツ」を注文してたわ。よろしくお願いね。_where_", + INCOMPLETE_PROGRESS : "申し訳ないんだけど「ズート・スーツ」を早く届けないとしわくちゃになっちゃうわ。", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "むかしむかし、あるところに。おじいさんと…。おっと、むかし話を聞きにきたようではないね。\a「ズート・スーツ」を届けにきてくれたんだね。すばらしい!ワオ!\aザリに伝言をお願いできるかな?このスーツにあうジルコンのカフスボタンが欲しいって!", + INCOMPLETE_PROGRESS : "ザリにメッセージを伝えてくれた?", + COMPLETE : "ジルコンのカフスボタン? そうねぇ…。ちょっと探してみるわ。\aとにかく、とにかく!\aコグたちに立ち向かうのには、大きなゲラゲラポイントが必要ね!本当にいろいろ手伝ってくれてありがとう!はい、これが感謝の気持ち!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "ドロジー・デイブが困っているみたい。キミならきっと助けてあげられるはず。彼の店に立ち寄ってあげて!_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "Zzzzz...\aZz、何?えっ?寝てないよ!たぶん…\a知ってる?コグビルの中には僕を眠くさせる機械がたくさんあるって。\a耳をかたむけると、ほら。\a………………\aはっ!そうそう。眠くならないようにコグビルをやっつけてくれない?", + INCOMPLETE_PROGRESS : "Zzzzz...はっ!キミかぁ、_avName_。\a早かったね。ちょっと昼寝をしていたんだ。\aコグビルをやっつけてくれないと………。Zzzzz...", + COMPLETE : "あれっ!あれれれれっ!\aコグビルがなくなって、ようやくリラックスできるよ。\a_avName_、本当にありがとう。\aまたね。リラックスしたらおかげで昼寝したくなったよ。", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "テディ・ブレアのところに行って、キミの仕事をもらおう!_where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "何だって?キミの仕事なんてないよ。\a仕事!どうしてそう言ってくれなかったんだ!もっとちゃんと言ってれなきゃ。\aコグたちがじゃまして冬眠ができないんだ。もしキミがドリームランドを静かにしてくれたら、\aちょっとした何かをあげるよ。", + INCOMPLETE_PROGRESS: "「ゴグ」をやっつけた?「ゴグ」って何かって?\aああ、「コグ」ね! そうしてそう言ってくれなかったんだ!\aまだ静かになってないから、もう少しやっつけてくれないかい?", + COMPLETE : "たのしかった?えっ?「たおした」って?\aやったね。本当に助けてくれてありがとう!\a部屋の奥にあったんだけど、使わないからどうぞ!\aきっと何かのパーツだから、他のパーツと一緒に使うんじゃない?ありがとう、_avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "コグたちが第一ねんねタオル銀行に押し入ったんだ!ウィリアム・テラーの所に行って助けてあげて!", + }, + 6292 : { QUEST : "まったくマネーボットのやつらといったら!やつらは私の大切な読書ランプをぬすんでいった!\a今すぐ取り戻さないと。何とか取り戻してくれないか?\aもし読書ランプを取り戻してくれたら、私が「マネーマネー」に会えるようにしてあげよう。\a早く!", + INCOMPLETE_PROGRESS : "ランプが必要なんだ!お願いだから探し続けて!", + COMPLETE : "よく戻ったね!それに読書ランプも!\a感謝しつくしてもし尽くせないけど、お礼にこれをあげよう!", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "ニーナ・ナイトライトがキミを探してたよ、_avName_。彼女が助けが必要だって。_where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "わー! 会えてうれしいわ、_avName_。さっそくなんだけど、助けてくれない?\aコグたちがじゃまして、私の倉庫に商品のベッドのざいこがなくなっちゃってるの。\aハーディ・トゥールのところにいってベッドをもってきてくれる?_where_ ", + INCOMPLETE_PROGRESS : "ハーディからベッドを受け取った?彼女ならきっと持っているはず。", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "ベッド?もちろん、ここにあるわよ。\aニーナに届けてくれる?わかったかしら?\aニーナにな!ってね。\aジョーク、ジョーク!よろしく頼むわ。", + INCOMPLETE_PROGRESS : "ニーナはよろこんでくれた?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "残念ながらこのベッドじゃないの。こんなシンプルなベッドじゃなくて、もうすこしファンシーなベッドなのよ。\aそんなに時間はかからないと思うからよろしくね。", + INCOMPLETE_PROGRESS : "ハーディーならきっともっとファンシーなベッドを持っているはずよ。", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "どうやらお気にめさなかったようね。でもきっとこれなら大丈夫。\aでもちょっとした問題があるの。実はまだ出来上がってないのよ。\a外にいるコグたちをやっつけている間に完成させるから、よろしく頼むわ。", + INCOMPLETE_PROGRESS : "ベッドを作るにはまだ外がうるさいようね。\aやっつけたときが、ベッドの出来上がり!", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "おまたせ、_avName_!\aコグたちをやっつけてくれたおかげでベッドが出来たわ!\aニーナによろしくね。キミのおかげで仕事がはかどるわ!ありがとう!", + INCOMPLETE_PROGRESS : "ニーナに早くベッドを届けてあげて!", + COMPLETE : "なんて素敵なベッドなの!\aこれでお客さんも大満足間違いなし。_avName_、本当にありがとう!\aきっとキミならこれを使えるはず。ずっと前に誰かがわすれていったものみたい。", + }, + 7209 : { QUEST : "ハニー・ムーンに会いにいってごらん。手助けしてほしいみたいだよ。_where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "おー、_avName_!キミの助けが必要なのよ!\a長い間、ゆっくり眠れないの。それもコグたちが私の大切なベッドカバーをうばっていったからなの。\aねぇ、ふとんやトニーに会って、青いベッドカバーがないか聞いてきてくれない?_where_", + INCOMPLETE_PROGRESS : "トニーに青いベッドカバーのこと、聞いてくれた?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "ハニーがベッドカバーを欲しいって?\a何色?青だって?!\a今、手持ちなのは全部、「赤」だから特別に作らないと。\a外にいるコグたちをやっつけてくれたら、作ってあげるよ。", + INCOMPLETE_PROGRESS : "まだ青いベッドカバーを作っているんだ、_avName_。コグたちをやっつけて!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "また会えてうれしいよ!ちょっとまってね…。\aはい、ベッドカバー。青だよ。彼女も間違いなく気に入るよ!", + INCOMPLETE_PROGRESS : "ハニーはベッドカバーを気に入ってくれた?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "私のベッドカバー?違うわ、これじゃないの。\a「しましまもよう」のが欲しいの!こんなデザインじゃ、落ち着いて眠れないわ。\aこれを彼に返して、しましまのをお願い。\aきっとあるはずよ。", + INCOMPLETE_PROGRESS : "「しましまもよう」なの。トニーにお願いって伝えて!", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "なんだって!「しましまもよう」じゃ、だめだって?\aそっか…。何があるか調べてみるね。\aちょっと時間かかりそうだから、その間、コグたちの相手をしてもらっていいかな?\aキミが戻るころには、何か見つかるはずだから…。", + INCOMPLETE_PROGRESS : "まだ他のベッドカバーを探している最中さ。コグたちの様子はどう?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "よくコグたちの相手をしてくれたね!\aどうぞ!彼女もきっと気に入る青くてしましまのカバーさ!\a早くハニーにこれを届けてあげてよ。", + INCOMPLETE_PROGRESS : "これ以上のものはないよ。\aハニーがきっと待ってるよ。", + COMPLETE : "あら、まあ!なんてすてきなの!やっぱりこのデザインじゃないと!\aじゃあ、すてきな夢でもみるかしらね。じゃあね、_avName_。\aなあに?まだいるの?レディーが寝ようとしてるのがわからない?\aどうぞ、これを受け取って、私を休ませて。おやすみなさい!", + }, + + 7218 : { QUEST : "ドリーミー・ダフネが「誰かに手伝ってもらいたい」って言ってたよ。_where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、_avName_。会えてうれしいな! コグたちが「まくら」をうばっていったの。\aテックスのところに「まくら」がないか見てきてくれません?_where_\aきっとあるはずなんだけどな?お願いします。", + INCOMPLETE_PROGRESS : "テックスから「まくら」を受け取っていただけました?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "元気?ダフネが「まくら」を欲しいって?そりゃまさにここだよ、キミ。\aはいどうぞ、_avName_!ダフネのところに持っていってあげな!それからよろしく伝えてくれよ!\a女の子を助けるのが生きがいなんでね。", + INCOMPLETE_PROGRESS : "渡した「まくら」は女の子に合ってたのかな?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "「まくら」だわ!\aあれっ、ちょっと待ってくださる?このまくら、やわらかすぎるわ。\aもっとかたいまくらがいいのですが…。\a申し訳ありませんがテックスにこれを返して、違うまくらを持ってないかきいてくれません?おねがいします。", + INCOMPLETE_PROGRESS : "残念ながら違うの。やわらかすぎてしまって。テックスに違うまくらをたのんでください。", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "やわらかすぎるって?ちょっと考えさせて…\aそういや倉庫にかたいまくらがあったはずだったかな?\a後で取りに行くから、ここいらのコグたちをそうじしておいてくれるかな?", + INCOMPLETE_PROGRESS : "倉庫に行くためにもコグをやっつけて!", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "もう戻ったの?すんばらしい!ダフネが探している「まくら」を取ってきたよ。\a早くこれを届けてあげてね。まかせたよ!", + INCOMPLETE_PROGRESS : "このまくら、とってもかたいよ!ダフネもとっても気に入るはず。", + COMPLETE : "きっとテックスならかたいまくらを持っているって信じてたわ。\aさわりごこちといい、かたさといいパーフェクト!\aこのコグスーツのパーツを受け取ってくれるかしら。", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "サンディ・サンドマンのところによってあげて。彼女のパジャマがなくなって困っているみたいだよ。_where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "あらっ、パジャマがない!なくなっちゃった!\aどうしたらいいの?あっ、わかった!\aビッグ・ママに会いに行ってくれない?きっとパジャマがあるはずよ。_where_", + INCOMPLETE_PROGRESS : "ビッグ・ママからパジャマを受け取った?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "まぁ、可愛いトゥーンね!ビッグ・ママのパジャマはバハマから取り寄せた最高のものよ。\aサンディ・サンドマンのパジャマね?ちょっとまってね。これかな?あれかな?\aはい、どうぞ。これで彼女もおしゃれに安心して眠れるわね。\a私の代わりに走って彼女に渡してくれる?今は、お店から出ることができないのよ。\a本当にありがとうね、_avName_。また、会いましょう!", + INCOMPLETE_PROGRESS : "サンディーのためにパジャマを持っていかないと!_where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "ビッグ・ママがわたしにこれを?あらまぁ。\a足がついてないじゃない…\a私はいつも足つきパジャマを着て寝るのよ。みんなはどう?\a悪いけどこれを戻して足つきのやつをお願いできるかしら?", + INCOMPLETE_PROGRESS : "私のパジャマは足がついてないとだめなの。ビッグ・ママに相談して!", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "足つきだって?ちょっと考えさせて。\aこれじゃないし、あれじゃないし…\aジャーン!足つきパジャマ!きれいな青のパジャマパジャマよ。他でさがそうったって、なかなか見つからないんだから!\a彼女に届けてあげて!ありがとう!", + INCOMPLETE_PROGRESS : "サンディーは青い足つきパジャマを気に入ってくれた?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "たしかに足はついてるけど、青いパジャマは着れないわ!\aビッグ・ママに色違いがあるか聞いてくれる?", + INCOMPLETE_PROGRESS : "ビッグ・ママならきっと、色の違う足つきパジャマを持ってるはず。", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "それは残念ねぇ。足つきパジャマは青しか持ってないのよ。\aでもひょっとしてキャットならいくつか色のバリエーションを持ってるはずよ。_where_", + INCOMPLETE_PROGRESS : "手持ちのパジャマはこれらしかないのよ。キャットのところに行ってみなさいよ。_where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "足つきパジャマ?もちろん!\aサンディーは青じゃだめなの?\aちょっとやっかいね。これはどうかしら?\a青じゃないし、ちゃんと足もついてるし。", + INCOMPLETE_PROGRESS : "わたしは赤が好きよ。あなたは?\aサンディーも気に入ってくれるとうれしいんだけど。", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "いやよ。たしかに青じゃないけど、私の顔の色でこんな色のパジャマはきれると思う?\aそんなわけないわ。キャットに別の色をたのんで、お願い!", + INCOMPLETE_PROGRESS : "キャットならもっといろいろパジャマを持ってるはず。わたしには赤は似合わないの。", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "ふーん、赤でもないと…。\aふむふむ。僕のひげによると、他にもあるらしい。\aもうちょっと考えてみるけど、取引しよう。\a僕が別のパジャマをみつけるから、かわりにコグビルをやっつけてよ。不安なんだ。\aパジャマを探しておくからよろしくね、_avName_。", + INCOMPLETE_PROGRESS : "パジャマのためにも、ちゃんとコグビルをやっつけて。", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "コグたちをやっつけてくれてありがとう!\aサンディーのためのパジャマをみつけたよ。彼女が気に入ってくれるといいんだけど。\a急いで彼女に持っていってあげて。ありがとう!", + INCOMPLETE_PROGRESS : "ねぇ、_avName_。サンディーがきっと首を長くしてパジャマを待ってるよ、", + COMPLETE : "足つきのピンクのパジャマ! かーんぺき!\aそれにサイズもぴったし。\a手伝ってくれた御礼をしないとね!\aこれなんてどうかしら?道でひろったんだけどね。", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "スウィート・リップスのところに行こう!助けを必要としているよ。_where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "にっくきコグたちが大切な「しわのばしクリーム」をうばっていったのよ!\aお客様の大切なものなのに…。\aリップのところに行って、特別なクリームの予備がないか見てくれる? _where_", + INCOMPLETE_PROGRESS : "「しわのばしクリーム」がないと仕事ができないの。\aリップが何を持ってるか聞いてくれる。", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、クリームね。リップスからの特別なお願いだから、ふつうの作り方ではできないんだ。\aトップシークレットの材料のカリフラワーサンゴが必要なんだけど、あいにくざいこぎれでね…。\a釣りに行って、池から見つけてくれないかな?サンゴを見つけたらすぐにおいでよ。", + INCOMPLETE_PROGRESS : "カリフラワーサンゴを特別なレシピで「しわのばしクリーム」にするのさ!", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "ワオ、これはいい「カリフラワーサンゴ」だね!\aオッケー、こうして、ああして。そしてコンブをひとさじっと。\aあれコンブがないぞ?コンブもいるなぁ。\a悪いけどもう一度池に行って、「ねばねばコンブ」を取ってきてよ。", + INCOMPLETE_PROGRESS : "「ねばねばコンブ」はお店じゃ売ってないんだ。\aクリームを作るのにかかせないのさ。", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "おおおおっと。これはとってもねばねばなコンブだね。ありがとう、_avName_!\aそれじゃあ、シンジュをすりばちと「ごますり棒」で…。\aおや、今度は「ごますり棒」が見つからないぞ?棒がなきゃ、すりばちの意味がない…。\aきっとこの間、シャークロンが押し入ったときに取っていったのかも。\aマネーボット本部に行って「ごますり棒」を取り返してきて!", + INCOMPLETE_PROGRESS : "だから「ごますり棒」がないと、シンジュをすりつぶせないよ。\aまったくシャークロンのやつらといったら!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "そうそう、これこれ!\aこれですりつぶして、かきまぜて…。\aはい、出来上がり!この出来立てフレッシュなクリームをスウィート・リップスに届けてあげて!", + INCOMPLETE_PROGRESS : "彼女はいろいろこだわりがあるから早く、早く!", + COMPLETE : "もっと大きい「しわのばしクリーム」はなかったの?\a今度はちゃんと多くたのまないとね。すぐなくなっちゃうから。\aまたね、_avName_!\aなあに?まだ何かようかしら?今から大忙し。\aはい、これを受け取って!", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "もしロウボット変装パーツに興味があるなら、_toNpcName_をたずねてみて。\a彼は天気の研究の助けが必要みたいなんだ。_where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "はい、はい。確かにロウボット変装パーツなら持ってますよ。\a僕にとっては興味がないものなんですがね。\a僕はトゥーンタウン全体の天気の変化について研究をしているんだ。\aコグの持っている気温センサーとだったら、喜んで変装パーツと交換するよ。\aまずは%sから始めたらどうかな?" % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたかい?" % GlobalStreetNames[2100][-1], + COMPLETE : "これはすばらしい!\a恐れていた通りだ…\aあ、そうそう。これが変装パーツだよ。", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "もっとロウボット変装パーツがいるなら、_toNpcName_にもう一度、たずねてごらん。\a彼の研究のアシスタントが必要みたいなんだ。_where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツがまだ必要だって?\aキミがそこまで言うのなら…\aでももう一つセンサーが必要なんだ。\a今度は%sのを探してみて。" % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[2200][-1], + COMPLETE : "ありがとう!\a変装パーツをどうぞ!", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツがさらに必要なら_toNpcName_のところに戻ってみたら?\a天気の研究、まだ手助けがいるみたいだよ。_where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "キミは本当に優秀だね!\a今度は%sを調べてみてくれないかい?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べているのかい?" % GlobalStreetNames[2300][-1], + COMPLETE : "ふーん、それにしてもこのセンサー見た目はあまりよくないが…\aありがとう。これがキミへのごほうびのパーツだ。", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "さらに変装パーツが必要なんだよね。\a気温のデータがYou-know-who needs more temperature readings._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "また戻ったのかい?\a非常に熱心だね。\a次の場所は%sだ。" % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね?" % GlobalStreetNames[1100][-1], + COMPLETE : "おみごと!じゅんびがととのったね。\aほら、へんそうパーツだよ!", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツをお探しなら…。_where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "キミに会えてうれしいよ。\a次は%sのデータが欲しいんだ。お願いできるかな?" % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたのかい" % GlobalStreetNames[1200][-1], + COMPLETE : "どうもありがとう!\a変装パーツももうすぐ完成だね。", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "きっと_toNpcName_がもっと仕事があるみたい。_where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "また会えてうれしいよ、_avName_!\a%sのデータを取ってきてもらえるかな?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたのかい?" % GlobalStreetNames[1300][-1], + COMPLETE : "すばらしい仕事だったね。\aよくがんばったキミへのごほうびだよ。", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "もうわかっているよね。_where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_、僕の大切な友達!\a今度は%sへ行って別の温度センサーを見つけてくれないかい?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね??" % GlobalStreetNames[5100][-1], + COMPLETE : "すばらしい!\aキミのおかげで研究が本当にはかどるよ!\aはい、ごほうび!", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がキミを名差しでお願いしてきたよ。\aキミが頑張っているのが街のウワサになってるんだね。_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "よく戻ってきたね!\aキミのことを待っていたんだよ。\a次に必要なのが%sのデータさ。" % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[5200][-1], + COMPLETE : "ありがとう!\aはい、キミへのごほうび!", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "キミがロウボットにちゃんと変装したければ、\a_toNpcName_が助けになるはず。_where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、研究者みならい君!\aさらに%sのデータが必要なんだよね。" % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "%sだからね。" % GlobalStreetNames[5300][-1], + COMPLETE : "すばらしい仕事だったね!\aはい、ロウボットのパーツをどうぞ。", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がキミに別の仕事があるってさ。\a気に入ってくれるといいんだけど。_where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "そっか…。\aさらに必要なんだね。\aそれなら今度は%sを試してみて!" % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね?" % GlobalStreetNames[4100][-1], + COMPLETE : "もうひとつ!\aキミはとってもスマートだね。", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツを探しているのかい?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "大体、察しがついているとは思うけど、\a今度は%sのデータが必要なんだ。" % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[4200][-1], + COMPLETE : "あとちょっとだね!\aはいどうぞ。", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "ほんとうは言いたくないんだけど。。。_where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "キミは%sのことどう思う?センサーをゲットできると思う?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べた?" % GlobalStreetNames[4300][-1], + COMPLETE : "またいい仕事をしたね、_avName_。", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "もしまだ変装パーツが必要なら、教授のところにいってみたら?_where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "残念ながら、%sのデータがまだ入手できてないんだ。" % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "キミは%sをちゃんと調べているんだよね?" % GlobalStreetNames[9100][-1], + COMPLETE : "いい仕事をしたね。\aあともうちょっとのところだね。", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "キミへの最後のミッションの内容は_toNpcName_が知ってるよ。_where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "すぐもどってきたね。\a最後のデータは%s。" % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "%sだからね。" % GlobalStreetNames[9200][-1], + COMPLETE : "全部終わったね!\aこれでケンサツキョクに行って、ショウカンジョーを集められることができるね!\a今まで本当にありがとう。そして気をつけて!", + }, + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "もしもボスボットのパーツにきょうみがあるなら_toNpcName_._where_に聞くといいかも。", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "ボスボットのパーツかい?\aじゃぁ、ボスボット・コレクションに協力してくれるかな?\aなずはオベッカーからたのむよ。", + INCOMPLETE_PROGRESS : "オベッカーが見つからない? そんなはずないだろ…?", + COMPLETE : "もうたおしたのかい?\aほら、さいしょのへんそうパーツだ。", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "もっとパーツが必要ならやはり_toNpcName_に聞くべきだよ。_where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "もう一つパーツが必要なのかい?\aもちろん...\aカリカリンを倒してきたらあげるよ。", + INCOMPLETE_PROGRESS : "カリカリンはそのへんのストリートにいるだろうね。", + COMPLETE : "朝メシ前だっただろ?\aさぁ、二つ目のパーツだよ。", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "ボスボットのパーツなら…もうわかるよね?_where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "次はイエスマンだ…。", + INCOMPLETE_PROGRESS : "イエスマンもそのへんのストリートにいるだろう。", + COMPLETE : "イエス!なかなかやるね。\aさぁ、三つ目のパーツだ。", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がまだまだパーツを持ってるよ...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "ガミガミーナを倒したら次のをあげるよ。", + INCOMPLETE_PROGRESS : "%sを探してみたかい?" % GlobalStreetNames[1100][-1], + COMPLETE : "おみごと!\a四つ目のへんそうパーツだよ。", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "次も...だね。_where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "次はリストラマンだ。", + INCOMPLETE_PROGRESS : "見つからないのかい?%sを探してごらん。" % GlobalStreetNames[3100][-1], + COMPLETE : "やつはめんどうだったかい?\a五つ目のへんそうパーツだよ。", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "キミ、…もうわかってるでしょ?_where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "え~と、私のリストでは次はヘッドハンターだ。", + INCOMPLETE_PROGRESS : "こいつの場合はビルの中を探すほうがいいかも。", + COMPLETE : "早かったね!\aほら、六つ目のパーツだよ。", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がもっとボスボットがひつようだって…。", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "次はデッパラーダをつかまえてくれないか?", + INCOMPLETE_PROGRESS : "こいつもやはりビルの中だろうな。", + COMPLETE : "キミもなかなかやるねぇ。\a六つ目のパーツだ!", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "もっとパーツが必要なんだろ?さ、行っておいで..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "さぁ、とどめだ!ビッグチーズを!!", + INCOMPLETE_PROGRESS : "%sにいるはずだぞ。" % GlobalStreetNames[10000][-1], + COMPLETE : "キミはきたい通りのチーズ好き…\aあぁ、いや、なんでもない。\a次のへんそうパーツはこれだ。", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がさがしてましたよ...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "実はキミに新しくてずるがしこいボスボットを倒してほしいんだ。", + INCOMPLETE_PROGRESS : "%sをさがしてみてくれ。" % GlobalStreetNames[10000][-1], + COMPLETE : "ヤツらは見た目よりつよいようだな。\aさぁ、へんそうパーツが必要なんだろ?", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "さぁ、行っていって..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "このバージョン2.0コグはとてもきょうみ深い。\aもっとさがしてきてくれるかい?", + INCOMPLETE_PROGRESS : "%sにならいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "ありがとう!\aジャジャーン、へんそうパーツをどうぞ!", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "ついででいいんだけど、_toNpcName_のトコに行ってくれる?", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "ヤツらはもしかしてどんどん強くなっているのか?", + INCOMPLETE_PROGRESS : "%sを探してみてくれ。" % GlobalStreetNames[10000][-1], + COMPLETE : "アレ?ヤツらそんなに強くはないの?\aそら、いつものだ。", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "え~と..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "た~ぶ~ん、ヤツらはボスボットじゃなくってなんか別のぉ...", + INCOMPLETE_PROGRESS : "こいつらは%sにいるぞ。" % GlobalStreetNames[10000][-1], + COMPLETE : "あ、これはやっぱボスボットだわ。\aふむ、へんそうパーツはそこにあるから…", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "え~と、ちゃんと言わないとわかりませんか?", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "いや、た~ぶ~んなんだけど、…ヤツらってガイコグ系じゃない?", + INCOMPLETE_PROGRESS : "%sにいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "う~む、どうもはっきりしないなあ。\aあ、へんそうパーツを持っていってね。", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "フゥ~、_toNpcName_がよんでいますよ。", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "どうもまだヤツらがガイコグのナカマなのかわからなくってねぇ。", + INCOMPLETE_PROGRESS : "%sをさがすといいよ。" % GlobalStreetNames[10000][-1], + COMPLETE : "あぁ~っと、ちがう…かな?\a…はい、次のパーツ!", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "多分、もうここはうんざりだと思いますけど…。", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "いやぁ、スマン!ヤツらの事でなやんでしまっていてね。\aもう一体だけたのめないかな?", + INCOMPLETE_PROGRESS : "やはり%sにいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "「すばらしい!」の一言につきるよ!\aへんそうパーツだ。受け取ってくれ。", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_はもうこわれたレコード…って、意味わかります?", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "もうヤツらの事はなんでも私に聞いてくれ!\aところでそうだんなんだけど…", + INCOMPLETE_PROGRESS : "きっと%sにならいるんじゃないか?" % GlobalStreetNames[10000][-1], + COMPLETE : "よし、思った通りだ!\aあぁ、そうそう。\aこれはキミにだよ。", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "フリッピーにこの事を伝えてくれる?", + INCOMPLETE_PROGRESS : "フリッピーはトゥーンホールにいるよ。", + COMPLETE : "新しいコグだって?!\a教えてくれてありがとう。\aお礼にさいごのへんそうパーツをあげるよ!", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["わんわん", "きゅう~ん", "わふわふ"] +ChatGarblerCat = ["にゃ~", "みゃ~"] +ChatGarblerMouse = ["チュ~チュ~", "チチッ", "チュ~"] +ChatGarblerHorse = ["ひひひん", "ぶるるん"] +ChatGarblerRabbit = ["チチッ", "ふんふんふん", "くんかくんか", "チッチッ"] +ChatGarblerDuck = ["グワッ", "グワワ~", "グワグワッ"] +ChatGarblerMonkey = ["ウキッ", "キー", "ウッキー"] +ChatGarblerBear = ["ガウ~", "ガルルル"] +ChatGarblerPig = ["ブヒブヒ!", "ブーッ!", "ブホブホッ!"] +ChatGarblerDefault = ["フガー"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = "" +AvatarDetailPanelClose = "閉じる" +AvatarDetailPanelLookup = "%s の状態を調べています…" +AvatarDetailPanelFailedLookup = "%s の状態を調べられませんでした。" +#AvatarDetailPanelPlayer = "Player: %(player)s\nWorld: %(world)s\nLocation: %(location)s" +# sublocation is not working now +AvatarDetailPanelPlayer = "プレイヤー: %(player)s\nワールド: %(world)s\nロケーション: %(location)s" +AvatarDetailPanelPlayerShort = "%(player)s\nワールド: %(world)s\nロケーション: %(location)s" +AvatarDetailPanelRealLife = "オフライン" +AvatarDetailPanelOnline = "ロビー: %(district)s\nエリア: %(location)s" +AvatarDetailPanelOnlinePlayer = "ディストリクト: %(district)s\nロケーション: %(location)s\nプレイヤー: %(player)s" +AvatarDetailPanelOffline = "ロビー: オフライン\nエリア: オフライン" +AvatarShowPlayer = "プレイヤーをみる" +OfflineLocation = "Offline" + +#PlayerDetailPanel +PlayerToonName = "トゥーン: %(toonname)s" +PlayerShowToon = "トゥーンをみる" +PlayerPanelDetail = "詳細" + + +# AvatarPanel.py +AvatarPanelFriends = "ともだち" +AvatarPanelWhisper = "ささやく" +AvatarPanelSecrets = "ひみつ" +AvatarPanelGoTo = "ワープ" +AvatarPanelPet = "ドゥードゥルを見る"#▲ +AvatarPanelIgnore = "むしする" +AvatarPanelIgnoreCant = lOK +AvatarPanelStopIgnoring = "むしをやめる" +AvatarPanelReport = "ほうこくする" +#AvatarPanelCogDetail = "部署: %s\nレベル: %s\n" +AvatarPanelCogLevel = "レベル:%s" +AvatarPanelCogDetailClose = "閉じる" +AvatarPanelDetail = "トゥーン情報"#▲ +AvatarPanelGroupInvite = "グループに招待する" +AvatarPanelGroupRetract = "招待をやめる" +AvatarPanelGroupMember = "メンバー" +AvatarPanelGroupMemberKick = "おことわり" + +# grouping messages +groupInviteMessage = "%sがグループに招待したいって" + + +# Report Panel +ReportPanelTitle = "めいわくトゥーン" +ReportPanelBody = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Use \"Ignore\" on the toon's panel\n\nDo you really want to report %s to a Moderator?" +ReportPanelBodyFriends = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Break your friendship\n\nDo you really want to report %s to a Moderator?\n\n(This will also break your friendship)" +ReportPanelCategoryBody = "You are about to report %s. A Moderator will be alerted to your complaint and will take appropriate action for anyone breaking our rules. Please choose the reason you are reporting %s:" +ReportPanelBodyPlayer = "This feature is stilling being worked on and will be coming soon. In the meantime you can do the following:\n\n - Go to DXD and break the friendship there.\n - Tell a parent about what happened." + +ReportPanelCategoryLanguage = "Foul Language" +ReportPanelCategoryPii = "Sharing/Requesting Personal Info" +ReportPanelCategoryRude = "Rude or Mean Behavior" +ReportPanelCategoryName = "Bad Name" + +ReportPanelConfirmations = ( + "You are about to report that %s has used obscene, bigoted or sexually explicit language.", + "You are about to report that %s is being unsafe by giving out or requesting a phone number, address, last name, email address, password or account name.", + "You are about to report that %s is bullying, harassing, or using extreme behavior to disrupt the game.", + "You are about to report that %s has created a name that does not follow Disney's House Rules.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "We take reporting very seriously. Your report will be viewed by a Moderator who will take appropriate action for anyone breaking our rules. If your account is found to have participated in breaking the rules, or if you make false reports or abuse the 'Report a Player' system, a Moderator may take action against your account. Are you absolutely sure you want to report this player?" + +ReportPanelThanks = "Thank you! Your report has been sent to a Moderator for review. There is no need to contact us again about the issue. The moderation team will take appropriate action for a player found breaking our rules." + +ReportPanelRemovedFriend = "We have automatically removed %s from your Toon Friends List." +ReportPanelRemovedPlayerFriend = "We have automatically removed %s as a Player friend so as such you will not see them as your friend in any Disney product." + +ReportPanelAlreadyReported = "You have already reported %s during this session. A Moderator will review your previous report." + +# Report Panel +IgnorePanelTitle = "このトゥーンをむしする" +IgnorePanelAddIgnore = "今回のログインセッションの間は%sをむししますか?" +IgnorePanelIgnore = "%sをむししています" +IgnorePanelRemoveIgnore = "%sをむしするのをやめますか?" +IgnorePanelEndIgnore = " %sをむしするのをやめました" +IgnorePanelAddFriendAvatar = "%sはあなたのともだちです。ともだちをむしする事はできません。" +IgnorePanelAddFriendPlayer = "%s (%s)はあなたのともだちです。ともだちをむしする事はできません。" + +# PetAvatarPanel.py +PetPanelFeed = "えさをあげる" +PetPanelCall = "よぶ" +PetPanelGoTo = "行く" +PetPanelOwner = "かいぬし" +PetPanelDetail = "ペットの状態" +PetPanelScratch = "スクラッチ" + +# PetDetailPanel.py +PetDetailPanelTitle = "トリックの練習" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'ジャンプ', + 1: 'おじぎ', + 2: 'しんだふり', + 3: 'ころがる', + 4: 'ちゅうがえり', + 5: 'ダンス', + 6: 'はなす', + } + + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'ふつう', + 'hunger': 'おなかがすいた', + 'boredom': 'たいくつしている', + 'excitement': 'こうふんしている', + 'sadness': 'かなしんでいる', + 'restlessness': 'おちつかない', + 'playfulness': 'ようきな', + 'loneliness': 'さびしがっている', + 'fatigue': 'つかれている', + 'confusion': 'こんらんしている', + 'anger': 'おこっている', + 'surprise': 'おどろいている', + 'affection': 'ラブラブ', + } + +SpokenMoods = { + 'neutral': 'ふつう', + 'hunger': 'ジェリービーンはあきちゃったかも。パイは食べちゃだめ?', + 'boredom': 'ペットはなんにもわかってないと思ってるでしょ?', + 'excitement': 'トゥーンタスティック!', + 'sadness': 'なにかイイ事ないかなぁ…', + 'restlessness': 'なんだか落ち着かないよ', + 'playfulness': '遊んでくれないと花だんをほっちゃうゾ!', + 'loneliness': 'いっしょにコグを倒しに行こうよ~', + 'fatigue': 'トリックの練習は大変なんだよ。きゅうけいさせて!', + 'confusion': 'ん?あなたダレ?ここはドコ???', + 'anger': 'いつも私をおいて遊びに行っちゃうでしょ!', + 'surprise': 'ワオッ!いつ帰ってきたの?!', + 'affection': 'いっしょにいられてうれしいよ!', + } + +# DistributedAvatar.py +DialogExclamation = "!" +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "ともだち" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "%s に行こうとしています。" +TeleportPanelNotAvailable = "%s はいそがしいようです。またあとでトライしてね。" +TeleportPanelIgnored = "%s があなたをむししています!" +TeleportPanelNotOnline = "%s はオンラインにいません。" +TeleportPanelWentAway = "%s は行ってしまいました。" +TeleportPanelUnknownHood = "%sへの行き方がわかりません!" +TeleportPanelUnavailableHood = "%s はいそがしいようです。またあとでトライしてね。" +TeleportPanelDenySelf = "自分をみつけられません!" +TeleportPanelOtherShard = "%(avName)s は%(shardName)sにいて、キミは%(myShardName)sにいるよ。%(shardName)sに移動する?" +TeleportPanelBusyShard = "%(avName)sは今こんざつしているロビーにいるよ。こんでいるロビーではゲームの反応がおそかったり安定しない場合があるけど、それでもワープする?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "私がボスだ!" + +# DistributedBattleFactory.py +FactoryBossTaunt = "私が工場長だ!" +FactoryBossBattleTaunt = "われらの工場長を紹介しよう!" +MintBossTaunt = "私が金庫番だ!" +MintBossBattleTaunt = "われらの金庫番に話をしてもらおう!" +StageBossTaunt = "私が本当のせいぎなのだ!" +StageBossBattleTaunt = "私がほうりつなのだ!" +CountryClubBossTaunt = "私がこのクラブのオーナーだ!" +CountryClubBossBattleTaunt = "クラブのオーナーに会わせてやる。" +ForcedLeaveCountryClubAckMsg = "クラブのオーナーはキミがたどり着く前に倒されました。カイコツウチはもらえません。" + +# HealJokes.py +ToonHealJokes = [ + ["となりの家に囲いができたんだってなぁ", + "かっこいいー!"], + ["田んぼに稲を植えました", + "い~ね~"], + ["屋根が落ちてきたんだって", + "や~ね~"], + ["その和紙は誰のだ?", + "わしのだ"], + ["この植物臭わない", + "草っ!"], + ["橋のどこを走るんだい?", + "はしに決まってるじゃん!"], + ["牛はどこだ?", + "ウシろ~!"], + ["牛が笑った", + "ウシシシシ!"], + ["パンダが好きな食べ物は?", + "パンだ!"], + ["アシカの世話は誰がするの?", + "あっしか!?"], + ["誰にやってもらおう?", + "サイにまかせなサイ!"], + ["テントウ虫のけがの原因は?", + "転倒です"], + ["白鳥のくしゃみは?", + "ハクチョーン!"], + ["鮭が叫んだ!", + "裂ける~!"], + ["サバをさばけないのです…", + "サバイバルには向いてないな"], + ["なんの魚を食べようかなぁ?", + "タラでも食べタラ?"], + ["イルカは逆立ちすると空を飛べるらしいよ!", + "カルイってか!"], + ["イカダに乗って、何を釣ってるの?", + "イカだ!"], + ["このイカ、くさってるよね?", + "うーん、いかがわしい"], + ["彼が作ったカレーの味はどう?", + "かれぇ~!"], + ["バナナが青いんだけど…", + "そんなバナナな!?"], + ["いちじくは何時に食べる?", + "1時(に)食う!."], + ["このワインは誰の?", + "ワイんのだぁ~"], + ["ソースの総数はたくさんあるらしいよ。", + "へぇ、そーすか."], + ["アメの味はどう?", + "あめ~."], + ["谷が深くて、", + "困っタニ~"], + ["虹を見たのは何時だろ?", + "2時だろ?"], + ["キミに借りた斧を折っちゃった…", + "オーノー!"], + ["石が落ちたんだってさ", + "ストーンと…"], + ["リスの話を聞こうよ!", + "Listen!"], + ["この蕎麦はおいしくないなぁ", + "So bad!"], + ["送った紙はいつ届くの?", + "カミング・スーン"], + ["卵が泣いてるようだ…", + "エッグ、エッグ"], + ["重量級の赤ちゃんだね。", + "まさしくヘビー(ベビー)!"], + ["鴨が言った", + "カモーン!"], + ["あの塔が倒れてしまったよ。", + "そりゃ困っタワー."], + ["シェフが時間ギリギリに間に合った", + "シェーフ!!"], + ["カッターを買ったら高かった", + "じゃあ店は儲かったね。"], + ["あのコック、居眠りしてるよ。", + "コックりコックり"], + ["このカレンダーは誰んだー", + "俺んだー"], + ["行き過ぎてしまった、どうしよう?", + "大丈夫、モノレールでもどれーるよ!"], + ["このお面誰の?", + "おめぇんのだよ!"], + ["涼しくなる楽器は?", + "鈴~!"], + ["もれる~!", + "じゃあトイレに行っといれ"], + ["スイマーも睡魔に負けた…", + "すいません"], + ["後ろに何か来てないか?", + "じゃあ、バックミラーで見てみら~!"], + ["床って、ゆーかー!", + "ゆかいだな、キミは"], + ["あなたは配送業ですか??", + "はい、そうです"], + ["馬が、", + "ウマれた!"], + ["馬は走るのが、", + "ウマい!"], + ["猫が風邪で、", + "ねこんだ"], + ["逃げた虎を", + "捕らえた"], + ["豚が", + "ぶたれた?"], + ["犬が", + "いぬる"], + ["ラクダに乗ると", + "楽だ"], + ["象だ", + "ゾウー!?"], + ["トドが動物園に", + "トドいた"], + ["熊を", + "かくまう!"], + ["カバを", + "かばった"], + ["あのカバ、", + "かばいい!"], + ["ロバは", + "こロバない"], + ["ワニが", + "わになって遊んでる~"], + ["ネズミが", + "寝ずに(ネズミ)番をした"], + ["サルも", + "リハーサルをしてます"], + ["サルが", + "去る"], + ["かめない", + "亀"], + ["カエルが", + "池にカエル"], + ["ハチと", + "ハチ合わせ!"], + ["アブは", + "あぶない!"], + ["コウモリも", + "子守でたいへん!"], + ["ハエが", + "はえ~"], + ["鳥がエサを", + "トリ返した"], + ["カモメがこっちに", + "来るかもめ!"], + ["コンドルが", + "飛んどる"], + ["ハトが豆鉄砲うたれて", + "ハッとした!"], + ["アジを刺身を", + "アジわおう!"], + ["ヒラメが", + "ヒラメいた!"], + ["かれいに泳ぐ", + "カレイ"], + ["冷めてる", + "サメ"], + ["こぶりな", + "ブリ"], + ["マグロを焦がして", + "マッグロにしちゃった"], + ["鯛は", + "めでタイ!"], + ["コイも", + "恋するらしいよ!"], + ["フナも", + "船酔いするんだって!"], + ["カニは", + "いるかに?"], + ["貝は", + "おいしいカイ?"], + ["いかした", + "イカ~"], + ["イカの料理は、", + "イカがですか?"], + ["イクラは", + "いくら?"], + ["うめぼしが", + "うめ~!"], + ["のりを食べて…", + "ノリノリだぜ~!"], + ["ワカメは", + "よくかめ!"], + ["肉が", + "ニクい~!"], + ["このみそを", + "食べてみそ!"], + ["コーラを", + "コオラした"], + ["抹茶がしぶくて、", + "こまっちゃう…"], + ["プリンを食べて", + "しらんプリン!"], + ["栗にさわって", + "びっクリ!"], + ["ネギを", + "ねぎった!"], + ["秋の味覚は、", + "いつ食べてもアキないね~"], + ["暑さでビールの売り上げも、", + "のビール!"], + ["チョコを", + "ちょこっと食べる"], + ["コショウで", + "故障"], + ["ダイコン売り場は", + "いつもだいこんざつ!"], + ["稲をかるのに", + "誰もイネー"], + ["サクラが", + "さくらしい!"], + ["球があたって", + "タマげた!"], + ["棚が落ちてきて", + "まいっタナ~"], + ["板がぶつかってきて", + "イタかった…"], + ["アメリカの雨は", + "アメージング!"], + ["綱引きの勝負は、どろですべって", + "ドロー!"], + ["そりがぶつかて、", + "アイムソーリー!"], + ["月を見てうなった、", + "「ム~~ン」"], + ["これじゃ火星を見るのは、", + "マ~ズむり!"], + ["太陽が", + "サンサンと輝いている"], + ["台所は", + "キッチンとせいりせいとん!"], + ["マスカットを、", + "まずカット!"], + ["油だって日が経つと", + "老いる(オイル)"], + ["夏じゃなきゃ、", + "サマー(様)になんないよ"], + ["和尚が2人揃って、", + "オショウガツー!!"], + ["アリが十匹揃って", + "「ありがとう~!」"], + ["盲腸は", + "もう超ツライ。"], + ["ハサミの", + "ギャグはさみ"], + ["No!と言える", + "ノート"], + ["自称", + "辞書マニア"], + ["カサが多すぎて", + "カサばっちゃう"], + ["箱を", + "ハコんだ!"], + ["電話に", + "誰も出んわ~"], + ["時計は!", + "ほっとけい"], + ["僕さぁ", + "ボクサーなんだ"], + ["バスが凄いスピードで", + "すっ飛ばす"], + ["ライター工場で", + "はたライター"], + ["布団が", + "ふっとんだっ!"], + ["靴にガムが", + "くっついた"], + ["靴を脱いで", + "くつろいだ"], + ["下駄で", + "笑い転げた"], + ["ボートに乗って", + "ボーッとする"], + ["タイヤが当たると", + "痛いやー。"], + ["コロンを付けてて", + "転んだ"], + ["帽子で!", + "日焼け防止"], + ["このイス、", + "いーっすねぇ!"], + ["漢文は", + "チンプンカンブンですよ"], + ["ボスが", + "水をこぼす"], + ["奇怪な", + "機械…"], + ["銅像を", + "どうぞ~"], + ["内臓が", + "無いぞう…"], + ["魚の気持ちを", + "サカナでしないようにね"], + ["小判を交番に届けるのを", + "拒んだ"], + ["映画を", + "観に行ってもええが?"], + ["スキーが", + "好き~!"], + ["太陽出ないと", + "冷たいよう"], + ["月で", + "もちつき!"], + ["火星の", + "家政婦!?"], + ["岩は…", + "なにもイワン"], + ["池に", + "行け!"], + ["バレーで", + "頑張れ~!"], + ["鏡を見て、", + "かがみこんだ"], + ["窓が多いと", + "とまどう…"], + ["左遷だけは、", + "させん!"], + ["イカイヨウが", + "いたいよう!"], + ["うちの家内は~", + "おっかないよ"], + ["王子が", + "相談に応じた"], + ["マイルが貯まって、", + "スマイル!"], + ["紅葉を見に", + "行こうよう"], + ["メールが", + "読めーる!"], + ["最近はなんでもメールでさぁ、", + "全く気が滅入るよね"], + ["ラブレターが", + "破られたー"], + ["メーカーに聞かないと", + "だめーかー"], + ["デールが", + "呼んでーる"], + ["ドナルドだって、", + "怒鳴るど~!!"], + ["ドナルドダックが", + "汗だっく"], + ["イーヨーは", + "かっこいーよー!"], + ["プーさんが、", + "ハチミツをミツけた"], + ["サリーはいつも、", + "さりー気ないよね"], + ["ニューヨークで", + "入浴だってさ"], + ["隣の客はよく", + "ギャグ言う客だ"], + ["畑でレタスが", + "取れたっす!"], + ["そのつまらないギャグに、", + "ギャグ切れだ~!?"], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("う~む…","…さむいかも。","あいたた…","イマイチ!") +MovieHealLaughterHits1= ("はははっ!","えへへっ!","ププッ…","あははっ!") +MovieHealLaughterHits2= ("わっはっは!","あっはっは!","がっはっは!") + +# MovieSOS.py +MovieSOSCallHelp = "%s たすけて!" +MovieSOSWhisperHelp = "%sがバトルで助けが必要だって!" +MovieSOSObserverHelp = "たすけて!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "おまたせ%s!\n手助けするよ!" +MovieNPCSOSGoodbye = "また後で!" +MovieNPCSOSToonsHit = "ギャグがきまるよ!" +MovieNPCSOSCogsMiss = "コグはミスするよ!" +MovieNPCSOSRestockGags = "%sのギャグをチャージするよ!" +MovieNPCSOSHeal = "トゥーンアップ" +MovieNPCSOSTrap = "トラップ" +MovieNPCSOSLure = "おとり" +MovieNPCSOSSound = "サウンド" +MovieNPCSOSThrow = "なげる" +MovieNPCSOSSquirt = "みずでっぽう" +MovieNPCSOSDrop = "ドロップ" +MovieNPCSOSAll = "すべて" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "あ~あ!" +MoviePetSOSTrickSucceedBoy = "よくやったね!" +MoviePetSOSTrickSucceedGirl = "いいこ、いいこ!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "中止\n中止\n中止" + +# RewardPanel.py +RewardPanelToonTasks = "トゥーン・タスク" +RewardPanelItems = "とりかえしたアイテム" +RewardPanelMissedItems = "まだとりかえしていないアイテム" +RewardPanelQuestLabel = "クエスト:%s" +RewardPanelCongratsStrings = ["やったね!", "おめでとう!", "いいかんじ!", + "よくやったね!", "サイコー!", "かっこいいよ!"] +RewardPanelNewGag = "%(avName)sに新しいギャグ、\n%(gagName)sのごほうび!" +RewardPanelUberGag = "%(avName)sは %(gagName)sのギャグを%(exp)sのけいけんちでゲット!" +RewardPanelEndTrack = "やったね! %(avName)sは%(gagName)sのギャグを全部ゲットしたよ!" +RewardPanelMeritsMaxed = "まんたん" +RewardPanelMeritBarLabels = [ "カイコツウチ", "ショーカンジョー", "コグドル", "メリット" ] #▲あとで要チェック★★★★★★★★★★★★★★★★★★★★★ +RewardPanelMeritAlert = "格上げの準備OK!" + +RewardPanelCogPart = "コグ変装グッズをゲット!" +RewardPanelPromotion = "%sトラックで\n格上げ準備オーケー!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("ノーマル・トゥーン", "ふつうのトゥーンです。"), + ("ビッグ・ヘッド", "頭が大きくなります。"), + ("スモール・ヘッド", "頭が小さくなります。"), + ("ビッグ・レッグ", "がっしりとした足になります。"), + ("スモール・レッグ", "ほそい足になります。"), + ("ビッグ・トゥーン", "からだが少しおおきくなります。"), + ("スモール・トゥーン", "からだが少しちいさくなります。"), + ("ぺらぺらポートレート", "からだが平べったくなります。"), + ("ぺらぺらプロフィール", "かおが平べったくになります。"), + ("クリア", "ガラスのようにとうめいになります"), + ("ノーカラー", "無色になります"), + ("とうめい", "他の人から見えなくなります"), + ] +CheesyEffectIndefinite = "他のエフェクトを選択するまで、%(whileIn)s%(effectName)s" +CheesyEffectMinutes = "あと%(time)s分間、%(whileIn)s%(effectName)s" +CheesyEffectHours = "あと%(time)s時間、%(whileIn)s%(effectName)s" +CheesyEffectDays = "あと%(time)s日間、%(whileIn)s%(effectName)s" +CheesyEffectWhileYouAreIn = "%sにいる間だけ" +CheesyEffectExceptIn = "%s以外で" + + +# SuitBattleGlobals.py +SuitFlunky = "オベッカー" +SuitPencilPusher = "カリカリン" +SuitYesman = "イエスマン" +SuitMicromanager = "ガミガミーナ" +SuitDownsizer = "リストラマン" +SuitHeadHunter = "ヘッドハンター" +SuitCorporateRaider = "デッパラーダ" +SuitTheBigCheese = "ビッグチーズ" +SuitColdCaller = "ブアイソン" +SuitTelemarketer = "ツーハーン" +SuitNameDropper = "タッシャーナ" +SuitGladHander = "オオゲーサ" +SuitMoverShaker = "クロマクール" +SuitTwoFace = "アイソマン" +SuitTheMingler = "オマカセンヌ" +SuitMrHollywood = "ビッグスマイル" +SuitShortChange = "チョロマカシー" +SuitPennyPincher = "セコビッチ" +SuitTightwad = "ドケッチオ" +SuitBeanCounter = "カッチリン" +SuitNumberCruncher = "スウジスキー" +SuitMoneyBags = "カネモッチン" +SuitLoanShark = "シャークロン" +SuitRobberBaron = "ドロビッグ" +SuitBottomFeeder = "タイコモチー" +SuitBloodsucker = "ガッツキー" +SuitDoubleTalker = "ニマイジタン" +SuitAmbulanceChaser = "ツケコミン" +SuitBackStabber = "ウラギリン" +SuitSpinDoctor = "ドクター・\nトラブル" +SuitLegalEagle = "ホウノトリ" +SuitBigWig = "ビッグホワイト" + +# Singular versions (indefinite article) +SuitFlunkyS = "オベッカー" +SuitPencilPusherS = "カリカリン" +SuitYesmanS = "イエスマン" +SuitMicromanagerS = "ガミガミーナ" +SuitDownsizerS = "リストラマン" +SuitHeadHunterS = "ヘッドハンター" +SuitCorporateRaiderS = "デッパラーダ" +SuitTheBigCheeseS = "ビッグチーズ" +SuitColdCallerS = "ブアイソン" +SuitTelemarketerS = "ツーハーン" +SuitNameDropperS = "タッシャーナ" +SuitGladHanderS = "オオゲーサ" +SuitMoverShakerS = "クロマクール" +SuitTwoFaceS = "アイソマン" +SuitTheMinglerS = "オマカセンヌ" +SuitMrHollywoodS = "ビッグスマイル" +SuitShortChangeS = "チョロマカシー" +SuitPennyPincherS = "セコビッチ" +SuitTightwadS = "ドケッチオ" +SuitBeanCounterS = "カッチリン" +SuitNumberCruncherS = "スウジスキー" +SuitMoneyBagsS = "カネモッチン" +SuitLoanSharkS = "シャークロン" +SuitRobberBaronS = "ドロビッグ" +SuitBottomFeederS = "タイコモチー" +SuitBloodsuckerS = "ガッツキー" +SuitDoubleTalkerS = "ニマイジタン" +SuitAmbulanceChaserS = "ツケコミン" +SuitBackStabberS = "ウラギリン" +SuitSpinDoctorS = "ドクター・トラブル" +SuitLegalEagleS = "ホウノトリ" +SuitBigWigS = "ビッグホワイト" + +# Plural versions +SuitFlunkyP = "オベッカー" +SuitPencilPusherP = "カリカリン" +SuitYesmanP = "イエスマン" +SuitMicromanagerP = "ガミガミーナ" +SuitDownsizerP = "リストラマン" +SuitHeadHunterP = "ヘッドハンター" +SuitCorporateRaiderP = "デッパラーダ" +SuitTheBigCheeseP = "ビッグチーズ" +SuitColdCallerP = "ブアイソン" +SuitTelemarketerP = "ツーハーン" +SuitNameDropperP = "タッシャーナ" +SuitGladHanderP = "オオゲーサ" +SuitMoverShakerP = "クロマクール" +SuitTwoFaceP = "アイソマン" +SuitTheMinglerP = "オマカセンヌ" +SuitMrHollywoodP = "ビッグスマイル" +SuitShortChangeP = "チョロマカシー" +SuitPennyPincherP = "セコビッチ" +SuitTightwadP = "ドケッチオ" +SuitBeanCounterP = "カッチリン" +SuitNumberCruncherP = "スウジスキー" +SuitMoneyBagsP = "カネモッチン" +SuitLoanSharkP = "シャークロン" +SuitRobberBaronP = "ドロビッグ" +SuitBottomFeederP = "タイコモチー" +SuitBloodsuckerP = "ガッツキー" +SuitDoubleTalkerP = "ニマイジタン" +SuitAmbulanceChaserP = "ツケコミン" +SuitBackStabberP = "ウラギリン" +SuitSpinDoctorP = "ドクター・トラブル" +SuitLegalEagleP = "ホウノトリ" +SuitBigWigP = "ビッグホワイト" + +SuitFaceOffDefaultTaunts = ['ワッ!'] + +SuitAttackDefaultTaunts = ['これでもくらえ!', '私の前から消えろ!'] + +SuitAttackNames = { + 'Audit' : 'カンサ!', + 'Bite' : 'ガブット!', + 'BounceCheck' : 'フワタリコギッテ!', + 'BrainStorm' : 'ブレインストーム!', + 'BuzzWord' : 'リュウコウゴ!', + 'Calculate' : 'ケイサン!', + 'Canned' : 'カンヅメ!', + 'Chomp' : 'ムシャムシャ!', + 'CigarSmoke' : 'ハマキ!', + 'ClipOnTie' : 'クリップネクタイ!', + 'Crunch' : 'キンユウキキ!', + 'Demotion' : 'コウカク!', + 'Downsize' : 'シュクショウ!', + 'DoubleTalk' : 'オセジ!', + 'EvictionNotice' : 'オイタテツウチ!', + 'EvilEye' : 'コワイシセン!', + 'Filibuster' : 'ボウガイ!', + 'FillWithLead' : 'エンピツゼメ!', + 'FiveOClockShadow' : "ブショウヒゲ!", + 'FingerWag' : 'チッチッ!', + 'Fired' : 'ヒノクルマ!', + 'FloodTheMarket' : 'シジョウハンラン!', + 'FountainPen' : 'マンネンヒツ!', + 'FreezeAssets' : 'オサムイフトコロ!', + 'Gavel' : 'コヅチ!', + 'GlowerPower' : 'スルドイシセン!', + 'GuiltTrip' : 'ザイアクカン!', + 'HalfWindsor' : 'ハーフウィンザー!', + 'HangUp' : 'ガチャギリ!', + 'HeadShrink' : 'マメアタマ!', + 'HotAir' : 'ネップウ!', + 'Jargon' : 'センモンヨウゴ!', + 'Legalese' : 'ホウリツヨウゴ!', + 'Liquidate' : 'ミズノアワ', + 'MarketCrash' : 'ケイザイシンブン!', + 'MumboJumbo' : 'ミツダン!', + 'ParadigmShift' : 'パラダイムシフト!', + 'PeckingOrder' : 'ピーチクパーチク!', + 'PickPocket' : 'スティール!', + 'PinkSlip' : 'カイコツウチ!', + 'PlayHardball' : 'チョッキュウショウブ!', + 'PoundKey' : 'シャープキー!', + 'PowerTie' : 'チョウネクタイ!', + 'PowerTrip' : 'ショッケンランヨウ!', + 'Quake' : 'ジシン!', + 'RazzleDazzle' : 'ビジネススマイル!', + 'RedTape' : 'ガンジガラメ!', + 'ReOrg' : 'サイヘンセイ!', + 'RestrainingOrder' : 'キンシメイレイ!', + 'Rolodex' : 'ローロデックス!', + 'RubberStamp' : 'ハンコ!', + 'RubOut' : 'ナカッタコトニ!', + 'Sacked' : 'フクロヅメ!', + 'SandTrap' : 'ドロヌマ!', + 'Schmooze' : 'ホメゴロシ!', + 'Shake' : 'シェイク!', + 'Shred' : 'シュレッダー!', + 'SongAndDance' : 'ウタッテオドッテ!', + 'Spin' : 'スピン!', + 'Synergy' : 'シナジー!', + 'Tabulate' : 'データサクセイ!', + 'TeeOff' : 'セッタイゴルフ!', + 'ThrowBook' : 'ロッポウゼンショ!', + 'Tremor' : 'シンドウ', + 'Watercooler' : 'ウォータークーラー!', + 'Withdrawal' : 'ザンダカショウカイ!', + 'WriteOff' : 'チョウケシ!', + } + +SuitAttackTaunts = { + 'Audit': ["帳簿と数字が合っていませんよ~", + "おやおや、 赤字ですか?", + "帳簿なら私におまかせを。", + "負債が大きすぎるんじゃないですか~?", + "まずあなたの資産を見てみましょうか?", + "負債というものがなんだか知っていますか?", + "さーて、 あなたの負債は?", + "口座をスッカラカンにしてやる!", + "ムダ遣いは いけませんねぇ。", + "おや、帳簿にミスがありますよ。", + ], + 'Bite': ["かまれるのは好きかい?", + "こいつにかまれると痛いよ!", + "がっつくのは良くありませんよ。", + "行くよ、ガブガブアタック!", + "これでも食らいな!", + "気をつけな、ボットだってかむんだぜ。", + "ガブっといってやる!", + "腹へったな~!", + "今日はおながペコペコだ!", + "一口だけでも食わせてくれよ。", + ], + 'BounceCheck': ["こぎって小切手?", + "お支払いの徴収に参りました。", + "この小切手はあなたのですね?", + "あなた、私に借りがあるんですよ。", + "借金の徴収に来ましたよ。", + "この小切手、使えませんよ。", + "罰金です!", + "あなたに払いきれますかねぇ。", + "これは高くつきますよ。", + "これを現金化したいんです。", + "小切手を使うなんて、なまいきな!", + "手切れ金のつもりかな?", + "このサービス料は高くつくよ。", + ], + 'BrainStorm':["今日の予報:大雨!", + "カサは持ってきましたか?", + "ピカッとひとつ来そうだ!", + "ひと降りきたみたいだな。", + "落雷注意報だ!", + "雲行きが怪しくなってきたよ。", + "いいこと思いついたぞ!", + "電光石火とはこのことだ!", + "濡れるのはお好きかい?", + ], + 'BuzzWord':["この言葉を知ってるかな?", + "最新の流行語を聞いた?", + "さすがのキミもこれは知らんだろう。", + "キミ、遅れてるよ~。", + "耳をちょっと貸しなさい。", + "情報収集はちゃんとやらねば!", + "そんなんじゃ付いていけないよ?", + "遅れてるキミに情報のプレゼントだ!", + "これぐらい知ってなきゃダメだよ。", + "キミ、これぐらい知ってなきゃ~", + ], + 'Calculate': ["総額が大変なことになってますね。", + "ちゃんと計算しないとダメですよ。", + "数字をバカにしちゃいけませんね。", + "計算のひとつも出来ないようじゃダメだよ。", + "使ったお金はちゃんと記録したかい?", + "私の計算によれば…キミはもうおしまいだ!", + "これがあなたの負債総額です!", + "おやおや、ちょっと浪費しすぎなんじゃない?", + "数字遊びはお好きかい?", + Cogs + "=∞ トゥーン=0", + ], + 'Canned': ["カンヅメは好き?", + "どっ「カーン」!", + "私は生よりカンヅメが好きなのだよ。", + "カンヅメにされたことはあるかい?", + "このカンをキミにプレゼントだ!", + "カンケリゲームのスタートだ!", + "カンカンカン!", + "まるくてかたくてつめたいもの、なーんだ?", + "カンヅメ作業は得意だぜ。", + "キミはカンヅメにしてもまずそうだねぇ。", + ], + 'Chomp': ["どうだ、このりっぱな歯!", + "ムシャムシャムシャ!", + "食べるときはゴーカイに!", + "いただきまーす!", + "これでも食らえ!", + "キミが私のディナーさ!", + "トゥーンは私の大好物さ!", + ], + 'ClipOnTie': ["正装こそ、できる人間のたしなみさ。", + "勝負の場ではネクタイをつけなきゃね。", + "おしゃれな" + Cogs + "にネクタイは不可欠!", + "これでも試しに着けてみな。", + "サクセスストーリーは一本のネクタイから。", + "ネクタイなしじゃみっともないよ。", + "これをつけるのを手伝ってあげるよ。", + "ネクタイと書いて権力と読むのさ!", + "さて、キミにはこのサイズかな?", + "おやおや、これじゃ首がしまってしまうね。", + "その格好、ラフすぎるね。", + "ネクタイでクタクタにしてやる!", + ], + 'Crunch': ["キミ、危なっかしいなぁ。", + "金融危機の波が来る!", + "札束の味は苦い味!", + "危機がきた!", + "これを生き残れるかな?", + "これでキミも一文無しだ!", + "備えあれば憂いなし!", + "キミは危機に強いほうかい?", + "キミのお先は真っ暗さ!" + ], + 'Demotion': ["一直線に下っ端行きだ!", + "キミには日のよく当たる席を用意したよ。", + "威張れる肩書きもなくなったね。", + "キミを蹴落としてみせる!", + "おやおや、 行き詰まりかい?", + "あっという間に行き場なし!", + "キミの人生、 行き止まりさ!", + "しばらくそこであがいてなさい。", + "運も才能のうちってやつさ。", + "キミの履歴書、見ていて恥ずかしいよ。", + ], + 'Downsize': ["ちっちゃくなーれ。", + "くらえ、 縮小ショック!", + "これ、英語ではダウンサイズっていうんだよ。", + "見苦しいなぁ、ちょっと小さくなってくれ。", + "キミ、ちょっと態度が大きいんじゃないかい?", + "目障りだなぁ…", + "私はちっちゃい人間を見下すのがだーいすき!", + "おやゆび姫って聞いたことある?", + "お客様、50円増しでSサイズにできますが?", + "私はちいさい人が好きなんだよね。", + "もっと小さくすることだってできるんだよ?", + "このアタックの対象はフリーサイズだ!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["はいはい、どいたどいた!", + "あんたの肩書きにさようなら、だ!", + "そろそろ安アパートに引越しだ。", + "クビにならなかっただけマシだよ。", + "まったく…キミにはこれしかないみたいだね。", + "キミにこれをつきつけるのは心苦しいなぁ。", + "出直してきな!", + "そろそろキミにもさよならだ。", + "キミの居場所はもうないよ。", + "左遷だ!", + "今のキミはギリギリの状態にあるんだよ?", + ], + 'EvilEye': ["悪い子には大目玉くらわせるぞ!", + "私の目が黒いうちは好きにさせん!", + "ああ、何かが眼に入ってしまったようだ。", + "私はキミに眼を付けていたのさ!", + "眼力なら負けないぞ!", + "私はコワいものが大好き!", + "キミはどこに目がついてるんだ?", + "あなたは私がコワくなーる、コワくなーる。", + "「台風の目」に巻き込んでやる!", + "あー、眼がゴロゴロしてきた。", + ], + 'Filibuster':["ボーガイボーガイ!てっていそしだ!", + "こうなったら牛歩だ!", + "私は妨害がだーいすきなんだ。", + "キミの進路を妨害してやる!", + "どんな手を使ってでも妨害する!", + "しゃべってしゃべって邪魔してやる!", + "このヤジに耐えられるかな?", + "あげあし取りならまかせろ!", + "口げんかなら負けないぞ!", + "妨害なら私にまかせなさい。", + ], + 'FingerWag': ["ふん、まだまだだな。", + "ちょっとキミ、そこに座りなさい。", + "笑わせてくれるねえ、まったく。", + "この私を怒らせるなよ。", + "何度も同じ事を言わせるな!", + "人の話は聞くものだよ。", + "キミ、われわれ" + Cogs + "に対して失礼なんじゃないかい?", + "そろそろ私の言うことに耳を傾けなさい。", + "ペラペラペラペラ…よくしゃべるね、まったく。", + "そんな考えは通用しないよ。", + "だからキミは半人前なんだよ。", + "まったく、未熟者だねぇ。", + ], + 'Fired': ["バーベキューは好きかい?", + "私は熱いほうが好きなんだよ。", + "ちょっと寒くないかい?", + "ファイヤーーー!", + "ホット!ホット!", + "ホットでジューシーに仕上げてやるよ。", + "ジュッとコゲちゃえ!", + "焼きかげんはウェルダンで?", + "燃え尽きちゃった?", + "日焼け止めは塗ったかい?", + "コンガリ焼いてやる!", + "ハデに炎上と行こうぜ!", + "灰になれ!", + "キミ、けっこう熱いね。", + "頭から火が出そうだよ。", + "ここは強火で行ってみよう!", + "コンガリトゥーン、いっちょあがり!", + "ナマ焼けは身体によくないよ。", + ], + 'FountainPen': ["シミ抜きの準備はいいかい?", + "私のペンは書き味ばつぐん!", + "このシミは絶対抜けないよ~", + "失礼、インクの出が悪いようだ。", + "着替えは持ってきたかい?", + "えーと、どこにサインしたらいいのかな?", + "ほら、私のペンをお使いなさい。", + "私の字、読めるかな?", + "書き味はどうかな?", + "「万年」ヒラにはこれがお似合いさ!", + "おっと、インクを入れすぎちゃったようだ。", + ], + 'FreezeAssets': ["ふところが寒いようだねぇ。", + "なんだか寒くないかい?", + "私はぬくぬく、キミは寒々。", + "景気が冷え込んでますね。", + "お寒い世の中ですな。", + "世間の風は冷たいもんだよ。", + "景気も人も冷えきってるね。", + "こう見えて私はけっこうクールなのさ。", + "これで凍えてしまいなさい。", + "凍傷ってなんだか知ってるかい?", + "寒さで震えあがっちゃうね。", + "私はいわゆる「冷」血漢らしいんだよ。", + ], + 'GlowerPower': ["なにガンくれてやがんでぇ。", + "私の視線は鋭いらしい。", + "突き刺すような視線ってこういうことさ。", + "私のチャームポイントはこの目!", + "チラっと見じゃものたりないな。", + "ごらん、表情豊かな目だろ?", + "何見てんだよ!", + "見~~た~~な~~~", + "あっかんべー!", + "私の目をよーく見てみなさい。", + "あなたの未来が見える…それは絶望!", + ], + 'GuiltTrip': ["謝るまでせめ続けてやる!", + "ほんとに悪いトゥーンだね!", + "全部キミのせいだ!", + "まったく、キミときたらいつもいつも…", + "キミには罪の意識ってものがないのかい?", + "キミなんて嫌いだ、もう話さない!", + "「ごめん」と一言言ったらどうなんだ?", + "百年経ってもキミのことは許さないよ!", + "泣いて謝ってもダメだからね。", + "キミはひどいトゥーンだね。", + "キミはもっとできると思ってたよ。", + ], + 'HalfWindsor': ["こんなハデなネクタイ、見たことないだろう?", + "ハイハイ、巻きこまれすぎないようにね。", + "しめつけちゃうよ!", + "ネクタイのしめかたも知らないのか?", + "キミにこのネクタイを買う金はないだろうな。", + "トゥーンもたまにはネクタイをしてみなさい。", + "キミにはもったいない気もするが…", + "まったく、キミ相手に使うことになるとはね。", + "キミにはちょっと早すぎるかな。", + ], + 'HangUp': ["接続が切れました!", + "バイバイ!", + "そろそろキミとのつながりを切ろうかな。", + "もう電話してこないで!", + "ガチャン!", + "キミと話すことはもうない!", + "私の時間のムダだ!", + "はい、さよーなら!", + "ん?接続が弱いみたいだね?", + "ハイ、時間切れでーす。", + "ハッキリ聞こえるようにしてやろう。", + "間違い電話です!", + ], + 'HeadShrink': ["よくも私の顔をつぶしてくれたな!", + "ミクロキッズならぬミクロトゥーンだ!", + "プライドも一緒にちっちゃくなるか?", + "せんたくしたらいつもこうなるの?", + "「わがはいは縮小されている。名前はもうない」", + "大きいのは図体だけか!", + "キミ、頭がどうかしちゃってるんじゃないか?", + "考えが足りないのはいけないよ。", + "キミはスケールの小さいトゥーンだね。", + "トゥーンは小さければ小さいほどいいんだってよ?", + ], + 'HotAir':["だんだん議論が過熱してまいりました!", + "あー、あついあつい。", + "沸点を迎えそうだ!", + "くらえ、熱風!", + "熱く語り合おうじゃないか。", + "火がないところに煙はたたないってよ?", + "ヒートアップしてきたね。", + "思ったよりアツいヤツだな。", + "私の怒りに火を注いだね?", + "私はこの仕事に燃えてるんだ!", + "トゥーン相手にはつい熱くなってしまうな。", + "ゴミは燃やさなきゃね。", + ], + 'Jargon':["キミ、こんなことも知らないのかい。", + "私はその道のプロなんだよ。", + "プロがしゃべるとこうなる!", + "わからずやには言い聞かせてやる!", + "私は自己主張がはげしいらしいんだが…", + "私にヘタな言い訳は通用しないよ。", + "ま、キミにはわからないだろうけどね。", + "ほら、言葉で人は傷つくんだよ。", + "私の言いたいこと、理解できるかな?", + "言葉の戦いなら負けん!", + ], + 'Legalese':["無駄な抵抗はおやめなさい。", + "法律上、あなたは負けるのです。", + "規約をよくお読みになりましたか?", + "キミは法の下に管理されている!", + "キミを罰する法律があったはずだ。", + "私に過失はない!", + "この攻撃にて見られる表現はディズニー・トゥーンタウン・オンラインの意見ではありません。", + "この攻撃で生じたダメージに関して、当社は一切の責任を持たないものとします。", + "この攻撃による結果は抽選で決定させていただきます。", + "この攻撃は当社指定の場所以外では無効です。", + "キミの行いは認めるわけにはいかない!", + "法律ごとを知らんお前には負けない!", + ], + 'Liquidate':["今回の件はお流れですな。", + "時代の流れが読めないとおぼれますよ。", + "水に流してしまいますよ。", + "ムダな努力を…!", + "おおっと危ない、濡れちまうよ。", + "気をつけていないと呑まれるよ。", + "きれいさっぱり流れてしまえ。", + "沈んでしまえ!", + "ではスッキリ水に流しましょうかね。", + "ムダな努力はやめておきなさい。", + ], + 'MarketCrash':["情報収集はこまめにやらねば!", + "この重さに耐えられるかな?", + "新聞くらい毎日読まなきゃダメじゃないか。", + "これは大事件だ!", + "せめてこれぐらいは読んでおけ!", + "売りに出たほうがいいらしいぞ!", + "私のコラムも読みたまえ!", + "ほれ、一年分だ。", + "これはキミの専門外かな?", + "テレビ欄以外もちゃんと読みなさい。", + "今日の4コマ、おもしろいぞ!", + ], + 'MumboJumbo':["ごにょごにょごにょ…", + "ひそひそひそ…", + "もにょもにょもにょ…", + "もごもごもご…", + "こしょこしょこしょ…", + "むにゃむにゃむにゃ…", + "ぼそぼそぼそ…", + "もそもそもそ…", + "こそこそこそ…", + "…というわけでひとつよろしくお願いしますよ。", + ], + 'ParadigmShift':["時空のシフトを引き起こすぞ!", + "異世界への旅へご案内しますぞ!", + "ふむ、おもしろいパラダイムだ。", + "キミの知らない世界へつれていってあげよう。", + "行ってらっしゃーい!", + "こんな世界があるって知ってたかい?", + "これは生まれて始めてかな?", + "さあ、行った行った!", + "この世界にキミの居場所はないんだよ。", + ], + 'PeckingOrder':["小鳥たちよ、おいで!", + "鳥たちと和むのもたまには必要だよ。", + "みんな、かかれー!", + "幸せの青い鳥につつかれたい?", + "騒いでごまかすのもひとつの方法さ。", + "落し物に注意!", + "議会ではたまにこのくらいうるさくなるんだよ。", + "ピーピー!ピーチクパーチク!", + "一石二鳥を狙うならこいつらはどうだ?", + ], + 'PickPocket': ["いただき!", + "おっ、ちょっとアレ見てみなよ。", + "ちょろいもんだ。", + "重いだろ?持つのを手伝ってあげよう。", + "私は手が早いので有名なんだ。", + "私は手ぐせが悪くてね。", + "脇が甘い!", + "無用心だなぁ。", + "なくし物には気をつけな。", + "キミのモノは私のモノ!", + "おっ、いいもの持ってるじゃないか。", + "サンキュー!", + "ちょっと失礼。", + "これ、どうせいらないだろう?", + ], + 'PinkSlip': ["おおっと、ついに来たか!", + "だから言わんこっちゃない。", + "元気でな!", + "これで二度と会うことはないだろうな。", + "残念だったね。", + "気を落とすなよ。", + "これでキミも終わりだな。", + "人生どこで転ぶかわからないものだね。", + "さよ~なら~~", + "キミの運もつきたってことさ。", + ], + 'PlayHardball': ["ではストレートに言おう!", + "直球勝負なら負けん!", + "バッターアップ!", + "ねらうならど真ん中!", + "ピッチャー、投げました!", + "リリーフピッチャーが必要なんじゃないか?", + "打ち取ってやるさ。", + "真っ向勝負といこうじゃないか。", + "2アウト2ストライク!もう後はない!", + "キミなんかに変化球はもったいないね。", + "何事もストレートに!", + "私はまがったことが嫌いなんだ。", + ], + 'PoundKey': ["これがキミへの伝言だ!", + "コレクトコールをかけたいんだが~", + "おっ、キミにいたずら電話だぞ。", + "私はなんでもシャープなものが好きなのさ。", + "このシャープさは痛いかもな。", + "メッセージは一件あります。", + "キーはたたくものなんだよ!", + "こんな攻撃、見たことないだろ?", + "サイゴニ「#」ヲオシテクダサイ。", + "電話代はちゃんと払ってるかい?", + ], + 'PowerTie': ["蝶々はお好きかな?", + "チョーいいネクタイでしょう、これ。", + "おしゃれな" + Cogs + "に蝶ネクタイは不可欠!", + "これでも試しに着けてみな。", + "サクセスストーリーは一本のネクタイから…", + "ネクタイなしじゃみっともないよ。", + "これをつけるのを手伝ってあげるよ。", + "ネクタイと書いて権力と読む!", + "さて、キミにはこの色かな?", + "こいつみたいにねじまげてやる!", + ], + 'PowerTrip': ["とばされたいのか?", + "遠いところはお好きかな?", + "弱肉強食だよ。", + "トゥーンは私に勝てんよ。", + "権力の前にひれ伏せ!", + "それ、接待費で落とすから。", + "逆らったら左遷だぞ!", + "チカラを持っているのは私なんだよ。", + "逆らうのか?", + "私に逆らったらあとがコワいぞ~", + "身の程知らずなトゥーンだね。", + ], + 'Quake': ["グラグラっとくる時のこの興奮!たまらんね。", + "こんな地震は初めてかい?", + "キミを見てるとぐらっと来るよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "すべてを揺るがしてやる!", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + ], + 'RazzleDazzle': ["私の唇の動きをよーく読んで。", + "カチカチカチ!", + "やっぱり私の歯、最高にかっこいいよね。", + "これを見て驚くなよ~", + "やっぱり歯がきれいじゃないとね~", + "え、きれいすぎてまぶしい?", + "これが本物だなんて信じられないでしょう。", + "びっくりした?", + "どうだ、強くてたくましいこの歯!", + "毎食後の歯磨きがひけつさ!", + "はい、笑って笑って~!", + ], + 'RedTape': ["決まりは決まり。言うことを聞け!", + "規則でがんじがらめにしてやる。", + "しばらく静かになってもらおう。", + "これから抜け出せるかな?", + "べたべたなシチュエーションかも知れないね。", + "閉所恐怖症だって?そいつは好都合だ。", + "刃向かえるものなら刃向かってみろ。", + "しばりつけてやる。", + "ほどけるもんならほどいてみな。", + "手も足も出まい!", + ], + 'ReOrg': ["散らかっているのが嫌いでね。", + "このまちはトゥーンだらけで整理が必要だ。", + "ちょっと身の回りを整頓してみたらどうだ。", + "キミのようなゴミが嫌いなんだよ。", + "手始めにキミから整頓しよう。", + "身辺整理をしてみたら?", + "まったく、トゥーンばかりごちゃごちゃと…", + "年に一度の大掃除だ!", + "キミ、ジャマなんだよ。", + "まずはキミから掃除してやる!", + ], + 'RestrainingOrder': ["私を傷つける行為は禁止!", + "やっていい事とわるい事があるだろう!", + "私の3メートル以内に近づくことを禁止する!", + "距離を保ちなさい。", + "命令だ!", + Cogs + "!そのトゥーンを捕まえなさい!", + "あれもこれも、それもしちゃダメ!", + "禁止!", + "これには逆らえまい!", + "トゥーンはおとなしくしていなさい!", + "だめ!" + ], + 'Rolodex': ["キミの名刺、このへんに入ってたんだが…", + "よろしくお願いいたします。", + "ほれ、私の名刺だ。", + "私はこういうモノだが。", + "はじめまして!", + "今後もよろしくお見知りおきを。", + "どーも、どーも。", + "こいつらは切れ味するどいよ。", + "何かあったらいつでもこちらへどうぞ。", + "私の顔を知らないとは!", + "キミ、名刺すら持ってないのかい。", + ], + 'RubberStamp': ["どこに押せばいいのかな?", + "ハイハイ、どんどん書類持ってきて~", + "認め印でいい?", + "「たいへんよくできました」", + "「もっとがんばりましょう」", + "「あとすこしです」", + "「ふつうです」", + "「よくできました」", + "特別に押してやろう!", + "ポンポンっとな。", + ], + 'RubOut': ["おおっと、間違いまちがい。", + "んん?どこ行った?", + "間違いは誰にでもあるものだよ。", + "私の邪魔になるものは消す!", + "間違いはすぐに消す主義なんだ。", + "キミの存在を消しちゃうよ。", + "ゴミはいらないんだよ。", + "消えてなくなれ!", + "あれ、さっきは姿が見えたのに…まぁいいか。", + "フェードアウトってやつ?", + "問題解決にはこれがいちばん!", + "おっと、しまったしまった。", + ], + 'Sacked':["さーて、どこに送ってやろうか。", + "リボンもつけてやろうか。", + "袋だたきにしてやる。", + "ビニール袋と紙袋、どっちがいい?", + "私の前から消えなさい!", + "二度とその顔を見せるな!", + "キミのことはもう要らんのさ。", + "キミにもう用はないのさ、達者でな!", + "たしか明日がゴミの日だったな。", + "うーむ、これって生ゴミになるのかな。", + ], + 'Schmooze':["ああ、キミはトゥーンの鑑だ!", + "キミ、本当にサイコー!", + "あなたにお会いできて光栄です~!", + "まったくキミは素晴らしい!", + "すごいねキミ、いやホント!", + "いやはや、恐れいりました~!", + "いやいや、あなたにはかないませんよ!", + "いや、まったくもってうらやましいですな!", + "あなたのお噂はかねがね!", + "感激のあまり涙がとまりませ~ん!", + "ああ、神様トゥーン様!", + ], + 'Shake': ["シェイクする時のこの興奮!最高だね。", + "こんなシェイクは初めてかい?", + "キミを見てるとぐらっと来るよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "すべてをシェイクしてやる!", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + "シェイクシェイク!", + "私はシェイクが大好きでね。", + "ちょっと体重が増えたみたいだな。", + ], + 'Shred': ["こいつは極秘資料だからね。", + "キミに見られるわけにはいかないんだ。", + "処分しなくては。", + "見たな!?", + "証拠インメツだ!", + "もみ消しは手際が大事なんだよ。", + "キミのアイデア、使えないね~", + "コマギレにしてやる!", + "キミみたいなのが外に漏れるわけにはいかないんだ。", + "シュレッドしたらリサイクル。", + "「社外秘」", + ], + 'Spin': ["グルグル~~", + "まったく、目が回るようじゃないか!", + "回れ回れ~~", + "え、何?回りたい?", + "バレリーナも真っ青だな!", + "さあ、何回まわれるかな?", + "もう首がまわらないんじゃないか?", + "おお、コマのようだな。", + "いつもより多く回しております。", + ], + 'Synergy': ["役員会に連れて行く。", + "キミのプロジェクトは 中止だ。", + "キミの予算は カットされたよ。", + "キミのチームは 他チームに吸収だ。", + "賛成多数で キミの降格が決定した。", + "組織とは シビアなものなんだよ。", + "チームにとってのガンは早めに取り除かんと。", + "まあ、これについては また後日。", + "残念ながらキミの チカラにはなれないよ。", + "多少の犠牲は やむを得んだろうな。", + ], + 'Tabulate': ["おや、計算が合わないな。", + "計算上、キミは負ける。", + "こんなデータ、見たことない!", + "今キミのデータを計測中だ。", + "データを見てみるかい?", + "損得勘定は大事だよ。", + "データ命!", + "私は計算しなくては気が済まないんだ。", + "出たぞ、計測結果は…。", + "ふむ、素晴らしいデータだ。", + ], + 'TeeOff': ["いやいや、今日は天気もいいですなぁ。", + "ファ~~~~!", + "ナイスショット!", + "キャディーさん、1番アイアン!", + "ハザードは避けなきゃな…", + "よっと!", + "ホールインワン目指しますよ~~!", + "いやー、私なんぞはまだまだ。", + "ドラコン賞はいただき!", + "池ポチャかな~", + "風向きヨシ!", + "もう1ラウンド行きますか?", + ], + 'Tremor': ["まさかこれしきの震動がこわいとか?", + "今の感じたかい?", + "今のは余震にすぎないよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "恐怖に震えてるのか?", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + ], + 'Watercooler': ["これで頭を冷やしな!", + "顔洗って出直してこい!", + "みっともない、これできれいにしな!", + "ばっしゃーん!", + "シャワーでも浴びな。", + "心配しないで大丈夫、 ろ過してあるから。", + "ちゃんとお風呂入ってるのかい?", + "ちゃんと洗濯してるかい?", + "水遊びは好き?", + "ノド乾いてるんだろ?", + "服が色落ちしちゃうね。", + "水分補給が必要だな。", + ], + 'Withdrawal': ["もう残金がありませんよ。", + "口座にお金は残っていますか~?", + "利子が倍返しになるよ。", + "借金地獄になっちゃうよ。", + "そろそろ振り込みをしないとあぶないよ。", + "家計がまずいことになってるんじゃないか?", + "もしかして、赤字?", + "お買い物は計画的に!", + "破産が近いんじゃない?", + "口座がなくなっちゃうよ。", + ], + 'WriteOff': ["損失をチェックしてみようか。", + "こりゃー、分が悪い取引をしちゃってるね。", + "給料が来月出るかもあやしいかもね。", + "これはひどいな。", + "キミの負債を算出してるんだが…", + "生じた損害には責任を持ってもらう。", + "ボーナスのことは忘れたほうがいいね。", + "キミのアカウントを見てみよう。", + "全てはやりくり次第だよ。", + "限界ギリギリまで行くね、これは。", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "他のプレイヤーを待っています…", + +# Elevator.py +ElevatorHopOff = "おりる" +ElevatorStayOff = "一度おりたら、みんなもおりるのを待つか\n次のエレベーターを待ってね。" +ElevatorLeaderOff = "おりるタイミングはリーダーにまかせてね。" +ElevatorHoppedOff = "次のエレベーターを待ってね。" +ElevatorMinLaff = "このエレベーターに乗るにはゲラゲラポイントが%s必要です。" +ElevatorHopOK = "OK" +ElevatorGroupMember = "のるタイミングは\nリーダーにまかせてね。" + +# DistributedCogKart.py +KartMinLaff = "このカートに乗るには\nゲラゲラメーターが%s必要です。" + +# DistributedBuilding.py ★ +# DistributedElevatorExt.py ★ +CogsIncExt = "・インク" +CogsIncModifier = "%s" + CogsIncExt +CogsInc = Cogs.upper() + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "コンコン!" +DoorWhosThere = "そこにいるのはだーれ?" +DoorWhoAppendix = "ってだーれ?" +DoorNametag = "ドア" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "ギャグが必要だよ!チュートリアル・トムに話しかけてみてね。" +FADoorCodes_DEFEAT_FLUNKY_HQ = "オベッカーを倒したらまた来てね!" +FADoorCodes_TALK_TO_HQ = "ごほうびがHQスタッフのハリーからもらえるよ!" +FADoorCodes_WRONG_DOOR_HQ = "間違い!プレイグラウンドに行くドアはもうひとつの方だよ。" +FADoorCodes_GO_TO_PLAYGROUND = "間違い! プレイグランドに行かなくちゃ!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "バトルをはじめるには、オベッカーに近づいてみて!" +FADoorCodes_TALK_TO_HQ_TOM = "トゥーンHQでごほうびがもらえるよ!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "気をつけて!そこには「コグ」がいるよ!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとコグに変装しよう!\n\nコグファクトリーからパーツを手に入れて変装しよう!" +FADoorCodes_CB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとマネーボットに変装しよう!\n\nドリームランド内のタスクをして変装パーツを手に入よう!" +FADoorCodes_LB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとロウボットに変装しよう!\n\nドリームランド内のタスクをして変装パーツを手に入よう!" +FADoorCodes_BB_DISGUISE_INCOMPLETE = "トゥーンのままだとつかまっちゃうよ!まずコグの変装パーツを集めよう。\n\nドナルドのドリームランドでもらえるトゥーンタスクをやるとロウボットの変装ができるよ。" + +# KnockKnock joke contest winners ▲ +KnockKnockContestJokes = { + 2100 : ["ハイドン", + "こんな名前だけど音楽は苦手なんだ~"], + + # 2009 April fools contest Jokes. First few doors of Loopy lane + 2200 : {28:["Biscuit", + "Biscuit out of here the Cogs are coming!"], + 41:["Dewey", + "Dewey want to go defeat some more Cogs?"], + 40:["Minnie", + "Minnie people have asked that, and it's driving me crazy!"], +## 25:["Biscuit25", +## "Biscuit out of here the Cogs are coming!"], + 27:["Disguise", + "Disguise where the Cogs fly!"]}, + + 2300: ["ジャスティン", + "間にあったね。ジャスト・イン・タイム!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdin HQ wants a word with you."], + 6 : ["Weirdo", + "Weirdo all these Cogs come from?"], + 30 : ["Bacon", + "Bacon a cake to throw at the Cogs."], + 28: ["Isaiah", + "Isaiah we go ride the trolley."], + 12: ["Juliet", + "Juliet me in that Cog building with you and I'll give you a Toon-Up."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ +# ["ダズンDozen", +# ""], + +# ["フレディ", +# "Freddie or not, here I come."], + +# ["ディッシュDishes", +# "Dishes your friend, let me in."], + + ["ウッディーだよ", + "でも金物屋なんだ。"], + +# ["ベティBetty", +# "Betty doesn't know who I am."], + +# ["ケントKent", +# "Kent you tell?"], + +# ["ノアNoah", +# "Noah don't know who either."], + +# ["しらないI don't know", +# "Neither do I, I keep telling you that."], + +# ["ハワードHoward", +# "Howard I know?"], + +# ["エマEmma", +# "Emma so glad you asked me that."], + +# ["オートAuto", +# "Auto know, but I've forgotten."], + +# ["ジェスJess", +# "Jess me and my shadow."], + +# ["ワンOne", +# "One-der why you keep asking that?"], + +# ["アルマAlma", +# "Alma not going to tell you!"], + +# ["ズームZoom", +# "Zoom do you expect?"], + +# ["エイミーAmy", +# "Amy fraid I've forgotten."], + +# ["アーファーArfur", +# "Arfur got."], + +# ["ユアンEwan", +# "No, just me"], + +# ["コージーCozy", +# "Cozy who's knocking will you?"], + + ["サムだよ", + "でも本当はあつがりなんだ…"], + +# ["フォジーFozzie", +# "Fozzie hundredth time, my name is " + Flippy + "."], + +# ["ディダクトDeduct", +# Donald + " Deduct."], + +# ["マックスMax", +# "Max no difference, just open the door."], + +# ["N.E.N.E.", +# "N.E. body you like, let me in."], + +# ["アモス(エイモス)Amos", +# "Amos-quito bit me."], + +# ["アルマAlma", +# "Alma candy's gone."], + + ["ブルースだよ", + "ジャズが大好きなブルースだよ。"], + +# ["コリーンColleen", +# "Colleen up your room, it's filthy."], + +# ["エルシーElsie", +# "Elsie you later."], + +# ["ヒューHugh", +# "Hugh is going to let me in?"], + +# ["ヒューゴHugo", +# "Hugo first - I'm scared."], + +# ["アイダIda", +# "Ida know. Sorry!"], + +# ["イサベルIsabel", +# "Isabel on a bike really necessary?"], + +# ["ジョアンJoan", +# "Joan call us, we'll call you."], + + ["ケイだよ", + "ケイ、エル、エム、オー、ピー…"], + + ["ジャスティンだよ", + "間にあったね。ジャスト・イン・タイム!"], + +# ["ライザLiza", +# "Liza wrong to tell."], + +# ["ルークLuke", +# "Luke and see who it is."], + +# ["マンディMandy", +# "Mandy the lifeboats, we're sinking."], + + ["マックスだよ", + "そんなにマクスたてないで~"], + +# ["ネッティNettie", +# "Nettie as a fruitcake."], + +# ["オリビア", +# "へぇ~!"], + +# ["オスカーOscar", +# "Oscar stupid question, you get a stupid answer."], + +# ["パッツィPatsy", +# "Patsy dog on the head, he likes it."], + +# ["ポールPaul", +# "Paul hard, the door's stuck again."], + +# ["テアThea", +# "Thea later, alligator."], + +# ["タイローンTyrone", +# "Tyrone shoelaces, you're old enough."], + +# ["ステラStella", +# "Stella no answer at the door."], + +# ["ユリアUriah", +# "Keep Uriah on the ball."], + +# ["ドゥエインDwayne", +# "Dwayne the bathtub. I'm drowning."], + +# ["ディスメイDismay", +# "Dismay be a joke, but it didn't make me laugh."], + +# ["オセロットOcelot", +# "Ocelot of questions, don't you?"], + +# ["テルモスThermos", +# "Thermos be a better knock knock joke than this."], + +# ["スルタンSultan", +# "Sultan Pepper."], + +# ["ヴォーンVaughan", +# "Vaughan day my prince will come."], + +# ["ドナルドDonald", +# "Donald come baby, cradle and all."], + +# ["レタスLettuce", +# "Lettuce in, won't you?"], + +# ["イヴォールIvor", +# "Ivor sore hand from knocking on your door!"], + +# ["イサベルIsabel", +# "Isabel broken, because I had to knock."], + +# ["ヘイウッド、ヒュー、、ハリーHeywood, Hugh, Harry", +# "Heywood Hugh Harry up and open this door."], + +# ["フアンJuan", +# "Juan of this days you'll find out."], + + ["アールさ", + "なくてもア~ル!"], + + ["ジミーだよ。", + "でもハデ好きなんだ!"], + +# ["アボットAbbot", +# "Abbot time you opened this door!"], + +# ["ファーディーFerdie", +# "Ferdie last time, open the door!"], + +# ["ドンDon", +# "Don mess around, just open the door."], + +# ["シスSis", +# "Sis any way to treat a friend?"], + +# ["イサドアIsadore", +# "Isadore open or locked?"], + + ["ハリーです", + "でもよくのんきだねって言われるんだ…"], + +# ["テオドアTheodore", +# "Theodore wasn't open so I knocked-knocked."], + +# ["ケンKen", +# "Ken I come in?"], + +# ["ブーBoo", +# "There's no need to cry about it."], + +# ["ユーYou", +# "You who! Is there anybody there?"], + +# ["アイスクリームIce cream", +# "Ice cream if you don't let me in."], + +# ["サラSarah", +# "Sarah 'nother way into this building?"], + +# ["マイキーMikey", +# "Mikey dropped down the drain."], + +# ["ドリスDoris", +# "Doris jammed again."], + +# ["イエルプYelp", +# "Yelp me, the door is stuck."], + +# ["スコルドScold", +# "Scold outside."], + +# ["ダイアナDiana", +# "Diana third, can I have a drink please?"], + +# ["ドリスDoris", +# "Doris slammed on my finger, open it quick!"], + +# ["レタスLettuce", +# "Lettuce tell you some knock knock jokes."], + +# ["イージーだよ", +# "!"], + +# ["オマー", +# "Omar goodness gracious - wrong door!"], + + ["セズだよ", + "道草セズに来たよ~"], + +# ["ダックDuck", +# "Just duck, they're throwing things at us."], + +# ["タンクTank", +# "You're welcome."], + +# ["アイズEyes", +# "Eyes got loads more knock knock jokes for you."], + +# ["ピザ", +# "宅配じゃないよ、ぼくだよ~"], + +# ["クロージュアClosure", +# "Closure mouth when you eat."], + +# ["ハリエットHarriet", +# "Harriet all my lunch, I'm starving."], + +# ["ウッデンWooden", +# "Wooden you like to know?"], + +# ["パンチPunch", +# "Not me, please."], + + ["リッチで~す", + "お金持ちじゃないけど…"], + +# ["ジュピターJupiter", +# "Jupiter hurry, or you'll miss the trolley."], + +# ["ベルタBertha", +# "Happy Bertha to you!"], + +# ["カウCows", +# "Cows go \"moo\" not \"who.\""], + +# ["マグロ(ツナフィッシュ)Tuna fish", +# "You can tune a piano, but you can't tuna fish."], + +# ["コンサンプション(タベル)Consumption", +# "Consumption be done about all these knock knock jokes?"], + +# ["バナナBanana", +# "Banana spilt so ice creamed."], + +# ["エックスX", +# "X-tremely pleased to meet you."], + + ["ハイドンだよ", + "こんな名前だけど音楽は苦手なんだ~"], + +# ["ローダRhoda", +# "Rhoda boat as fast as you can."], + +# ["グワグワQuacker", +# "Quacker 'nother bad joke and I'm off!"], + + ["ナナよ", + "ラッキー!"], + +# ["エーテルEther", +# "Ether bunny."], + +# ["オバサンLittle old lady", +# "My, you're good at yodelling!"], + +# ["ビートBeets", +# "Beets me, I forgot the joke."], + +# ["ハルHal", +# "Halloo to you too!"], + +# ["サラSarah", +# "Sarah doctor in the house?"], + +# ["アイリーンAileen", +# "Aileen Dover and fell down."], + +# ["アトミックAtomic", +# "Atomic ache"], + +# ["アガサAgatha", +# "Agatha headache. Got an aspirin?"], + +# ["スタンStan", +# "Stan back, I'm going to sneeze."], + +# ["ハッチHatch", +# "Bless you."], + +# ["アイダIda", +# "It's not Ida who, it's Idaho."], + +# ["ジッピーZippy", +# "Mrs. Zippy."], + +# ["ユーコンYukon", +# "ユウコじゃなくてユーコン!"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "こんにちは、%!", + "%、会えてうれしいよ。", + "会いに来てくれてありがとう!", + "来てくれたんだね、%!", + ] + +SharedChatterComments = [ + "%って、ステキななまえだね!", + "ステキななまえだね。", + "" + Cogs + "に気をつけてね!", + "トロリーが来たみたいだよ。", + "トロリーゲームをプレイして、パイを集めたいんだ。", + "フルーツパイが食べたくてトロリーゲームをプレイするんだ~", + "" + Cogs + "の一団を追い払ってきたとこなんだ。ひとやすみしようっと!", + "" + Cogs + "の中にも大きいのがいるね、たいへんだ!", + "楽しんでるみたいだね。", + "ああ、今日はさいこうに楽しいなぁ!", + "すてきな服だね。", + "今日は釣りに行ってこようかな。", + "ウチの近所で楽しんでいってね!", + "トゥーンタウンでの生活、楽しんでる?", + "ブルブルランドで雪がふってるらしいよ。", + "今日、トロリーに乗った?", + "あたらしいともだちを作るのは楽しいよ。", + "ブルブルランドにたくさんの" + Cogs + "がいるんだって!", + "鬼ゴッコって楽しいよね!キミは鬼ゴッコ、好き?", + "トロリーゲームって楽しいな~!", + "他のひとを笑わせるのが好きなんだ。", + "ともだちを助けるのって楽しいよ。", + "ええっと、迷子になったの?トゥーンガイドに地図があるから見てみてね。", + "" + Cogs + "の「ガンジガラメ」こうげきはやっかいだよ~", + "" + Daisy + "がガーデンに新しい花を植えたんだって!", + "PageUpキーを押し続けると、上を向けるよ!", + "コグビルをたおすと、ブロンズの星がもらえるよ!", + "Tabキーを押し続けると、周りを自分の視点で見られるよ!", + "Ctrlキーを押すと、ジャンプできるよ!", + ] + +SharedChatterGoodbyes = [ + "そろそろ行かなきゃ。じゃあね!", + "トロリーゲームでもやってこようかな~", + "じゃあ、またね、%!", + "" + Cogs + "をやっつけてこなくちゃ…", + "そろそろ行かなくちゃ。またね。", + "じゃあね、また会おう!", + "バイバイ!", + "またね、%!", + "カップケーキ投げのれんしゅうをしてこようかな。", + "" + Cogs + "をそしするためにも、グループに入ってくるね。", + "会えて嬉しかったよ、%", + "今日はすっごくいそがしいんだ、行かなくちゃ!", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + lToontownCentral+"へようこそ!", #CC_mickey_chatter_greetings01.mp3 + "こんにちは!僕の名前は" + Mickey + "マウス。君の名前は?", #CC_mickey_chatter_greetings02.mp3 + ], + [ # Comments + "ねぇ、キミ!" + Donald + "を見かけなかった?", #CC_mickey_chatter_comments01.mp3 + "これから、うっすらと霧の立ち込めた、"+lDonaldsDock+"に行こうと思うんだ!", #CC_mickey_chatter_comments02.mp3 + "もし僕の友達の" + Goofy + "に会ったら、よろしく言っておいてね。", #CC_mickey_chatter_comments03.mp3 + "ははっ!どうやら" + Daisy + "がお庭に新しいお花を植えたらしいよ。", #CC_mickey_chatter_comments04.mp3 + ], + [ # Goodbyes + "これから、" + Minnie + "に会いにメロディーランドに行こうかなぁ。", #CC_mickey_chatter_goodbyes01.mp3 + "ああっ、" + Minnie + "とのデートにおくれちゃうよ!", #CC_mickey_chatter_goodbyes02.mp3 + "そろそろ" + Pluto + "に晩御飯の準備をしないと…", #CC_mickey_chatter_goodbyes03.mp3 + "キミは"+lDonaldsDock+"に泳ぎに行ったことある?", #CC_mickey_chatter_goodbyes04.mp3 + "ドリームランドにおひるねしに行こうかなぁ…", #CC_mickey_chatter_goodbyes05.mp3 + ] + ) + +VampireMickeyChatter = ( + [ # Greetings specific to Vampire Mickey + ""+lToontownCentral+"へようこそ!", + "ボクのなまえは"+Mickey+"。キミのなまえは?", + "ハッピー・ハロウィーン!", + "楽しいハロウィーンになるといいね、%!", + "今年も"+lToontownCentral+"がくろねこトゥーンでいっぱいになるよ!", + ], + [ # Comments + "ハロウィーンって楽しいよね!", + "このコスチューム、どうかなぁ?", + "%もガッツキーには気をつけてね!", + "ハロウィーンのデコレーション、気に入ってくれた?", + "くろねこトゥーン達とはなかよしなんだ♪", + "ねぇ、カボチャ頭のトゥーンを見た?", + "バァーッ!ハハッ、おどろいた?", + "ちゃんとキバを歯ブラシでみがこうね☆", + "だいじょうぶ。ボクはフレンドリーなドラキュラだから♪", + "ボクのマント、かっこいいでしょ?", + "おどろいた?ボクのセンス、なかなかでしょ?", + "トゥーンタウンのハロウィーンを楽しんでいってね!", + "今夜はきっともりあがるだろうね。", + ], + [ # Goodbyes + "ハロウィーンのすてきなデコレーションをみにゆこうよ。", + ""+Minnie+"をおどろかしにメロディーランドに行ってこようかな。", + "しぃ~、これから他のトゥーンをおどろかすんだ!", + "トリック・オア・トリート!", + "いっしょにみんなをおどろかしに行こうよ♪", + ] + ) + +MinnieChatter = ( + [ # Greetings + "メロディランドへようこそ!", #CC_minnie_chatter_greetings01.mp3 + "私は" + Minnie + "マウスよ。あなたのお名前は?", #CC_minnie_chatter_greetings02.mp3 + ], + [ # Comments + "ここはいろいろな楽器の音色であふれてるのよ!", #CC_minnie_chatter_comments01.mp3 + # the merry no longer goes round + #"大きなメリーゴーランドを是非試してみてね!", #CC_minnie_chatter_comments02.mp3 + "あら!おしゃれなお洋服ね!", #CC_minnie_chatter_comments03.mp3 + "ねぇ、" + Mickey + "を見かけなかった?", #CC_minnie_chatter_comments04.mp3 + "" + Goofy + "に会ったら、よろしくね。", #CC_minnie_chatter_comments05.mp3 + "たくさんの" + Cogs + "が" + Donald + "のドリームランドまわりにいるらしいわ。", #CC_minnie_chatter_comments06.mp3 + lDonaldsDock+"には霧が立ち込めているみたいよ。", #CC_minnie_chatter_comments07.mp3 + lDaisyGardens+"の迷路も試してみてね。", #CC_minnie_chatter_comments08.mp3 + "私も楽器を演奏してみようかしら。", #CC_minnie_chatter_comments091.mp3 + "ねぇ、あれを見て!", #CC_minnie_chatter_comments10.mp3 + "楽器の音色ってほんと素敵よね。", #CC_minnie_chatter_comments11.mp3 + "メロディーランドはトゥーンじゃなくてチューンタウンなの。うふっ。", #CC_minnie_chatter_comments12.mp3 + "マッチングゲームって面白いわよね。そう思わない?", #CC_minnie_chatter_comments13.mp3 + "みんなが笑ってくれるとわたし、とってもうれしいわ!", #CC_minnie_chatter_comments14.mp3 + "ねぇ、歩き回ってつかれたんじゃなぁーい?", #CC_minnie_chatter_comments15.mp3 + "まぁ。素敵なシャツね!", #CC_minnie_chatter_comments16.mp3 + "あらっ、そこにあるのはゼリービーンかしら?", #CC_minnie_chatter_comments17.mp3 + ], + [ # Goodbyes + "いっけなーい、" + Mickey + "と会う約束をしてたんだ。", #CC_minnie_chatter_goodbyes01.mp3 + "そろそろ" + Pluto + "の夕飯のしたくする時間だわっ。", #CC_minnie_chatter_goodbyes02.mp3 + "ふぁーっ、ドリームランドに行こうかしら?", #CC_minnie_chatter_goodbyes03.mp3 + ] + ) + +DaisyChatter = ( + [ # Greetings + "マイガーデンへようこそ!", + "こんにちは!わたしは"+Daisy+"。あなたの名前をおしえてちょうだい。", + "あなたにお会いできてうれしいわ!", + ], + [ # Comments + "賞品のお花は庭の迷路の中にあるわよ。", + "迷路をぶらぶら散歩するのが好きなの。", + "ずっと"+Goofy+"グーフィーを見てないのよね。", + "一体、"+Goofy+"はどこにいるのかしら?", + "ねぇ、"+Donald+"を見かけなかった?どこ探しても見つからないのよ。", + "私の友達の"+Minnie+"を見かけたら、よろしくって伝えてくださらない?", + "いいガーデニングツールがあれば、植物もよく育つのよ。", + "たくさんの"+Cogs+"が"+lDonaldsDock+"にいるらしいのよ。", + "毎日の水やりは植木をハッピーにするのよ!", + "ピンクデイジーを育てたければ、黄色と赤のジェリービーンを植えてね。", + "イエローデイジーを育てるには、黄色のジェリービーンを植えてね。", + "もし植木の下に砂が見えたら、水をあげてね。でないと、植木がかれちゃうよ!" + ], + [ # Goodbyes + "メロディーランドに%sに会いに行くところよ。" % Minnie, + "%sとのピクニックに遅れちゃうわ~!" % Donald, + "これから"+lDonaldsDock+"に泳ぎに行こうかしら。", + "ふぁ~っ。ちょっと眠くなったから、ドリームランドに行こうかしら。", + ] + ) + +ChipChatter = ( + [ # Greetings + "%sにようこそ!" % lOutdoorZone, + "やぁ、僕は" + Chip + "。キミの名前は?", + "僕が" + Chip + "だよ!", + "%、会えてほんとうにうれしいよ!", + "僕たちはチップとデールだよ!", + ], + [ # Comments + "ゴルフが大好きなんだ!", + "ここのドングリはトゥーンタウンで一番なのさ。", + "火山があるゴルフコースが一番むずかしいとおもうよ。", + ], + [ # Goodbyes + "これから" + lTheBrrrgh +"に行って%sとあそぶんだ!" % Pluto, + "これから%sに会いに行ってくるんだ。" % Donald, + "今日は" + lDonaldsDock + "までおよぎに行こうかなぁ♪", + "なんだかねむいなぁ…。ドリームランドでひとねむりしようかな。", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "%、よくきてくれたね!", + "こんにちは、僕" + Dale + "だよ。キミの名前は?", + "僕は" + Chip + "だよ。", + "%sへようこそ!" % lOutdoorZone, + "僕たちがチップとデールだよ。", + ], + [ # Comments + "ピクニックって楽しいよね。", + "ここのドングリはおいしいんだよ。", + "そこの風車はもうためした?", + ], + [ # Goodbyes + "ヒヒヒ、" + Pluto + "は遊びともだちなんだ!", + "よし、%sに行くじゅんびをしよう!" % Donald, + "のんびりと泳ぎにゆきたいなあ。", + "うん、そろそろつかれてきたからきゅうけいしようよ。", + ] + ) + +GoofyChatter = ( + [ # Greetings + "ようこそ、"+lDaisyGardens+"へ!", #CC_goofy_chatter_greetings01.mp3 + "僕の名前は" + Goofy + "。よろしくね。キミの名前は?", #CC_goofy_chatter_greetings02.mp3 + "おひょっ。キミにあえてうれしいよ。", #CC_goofy_chatter_greetings03.mp3 + ], + [ # Comments + "迷路に迷わないように気をつけてね。", #CC_goofy_chatter_comments01.mp3 + "キミもここの迷路を試してみない?楽しいよ!", #CC_goofy_chatter_comments02.mp3 + "ねぇ、キミ。" + Daisy + "を見かけなかった?", #CC_goofy_chatter_comments03.mp3 + "ありゃま、" + Daisy + "はどこに行ったのかなぁ。", #CC_goofy_chatter_comments04.mp3 + "ねぇ、" + Donald + "はどこにいるか知ってる?知ってたら教えてね。", #CC_goofy_chatter_comments05.mp3 + "もし僕の親友の" + Mickey + "にあったらよろしくね。", #CC_goofy_chatter_comments06.mp3 + "おなかすいたなぁ。", #CC_goofy_chatter_comments07.mp3 + "この街の外にはね。" + Cogs + "がたくさんいるんだよ。", #CC_goofy_chatter_comments08.mp3 + "ねぇ、見て見て。" + Daisy + "がお花を植えたのかなぁ。", #CC_goofy_chatter_comments09.mp3 + "おひょっ。いろんな種類のギャグがあるから集めてごらん。", #CC_goofy_chatter_comments10.mp3 + "街には必ずギャグショップがあるよ。楽しいよ。", #CC_goofy_chatter_comments11.mp3 + "ギャグショップにはたっくさんギャグがあるよ。笑い過ぎないように気をつけてね。" #CC_goofy_chatter_comments12.mp3 + ], + [ # Goodbyes + "これから、" + Minnie + "に会いに、メロディーランドに行くところなんだ。", #CC_goofy_chatter_goodbyes01.mp3 + "オヒョッ!急がないと遅れちゃう!" + Donald + "と約束してたんだ!", #CC_goofy_chatter_goodbyes02.mp3 + lDonaldsDock+"に泳ぎに行こうかなぁ。", #CC_goofy_chatter_goodbyes03.mp3 + "ふああ…お昼寝の時間みたい。ドリームランドに行かなくちゃ。", #CC_goofy_chatter_goodbyes04.mp3 + ] + ) + + + +GoofySpeedwayChatter = ( + [ # Greetings + "ようこそ!"+lGoofySpeedway+"へ!", + "やあ、僕の名前は"+Goofy+"だよ。キミの名前を教えてよ。", + "オヒョッ!キミに会えてうれしいよ%!", + ], + [ # Comments + "さっきすごいレースを見たんだよ!", + "コースにあるバナナの皮に気をつけてね!", + "最近、キミのカートをアップグレードしたかな?", + "カートショップに新しいパーツが入ったみたいだよ!", + "ねぇ、ちょっと!"+Donald+"を見なかった?", + "おっと!"+Mickey+"の朝ごはんの準備をするのをすっかり忘れてたよ!", + "もし僕のともだちの"+Mickey+"に会ったら、よろしく伝えてよ!", + "オヒョッ!"+lDonaldsDock+"に"+Cogs+"たちが、うようよしてるって!", + "ブルブルランドのギャグショップでは、ぐるぐるめがねがなんと1ジェリービーンで売ってるよ!", + "ボクのギャグショップではトゥーンタウン中で一番のジョークや笑いのたねを取りそろえてるんだよ!", + "ギャグショップのパイは笑いの保障つき!笑わなかったらジェリービーンをちゃんとキミに返すよ!" + ], + [ # Goodbyes + "ちょっと%sに会いにメロディーランドに行ってくるよ。" % Mickey, + "オヒョッ!%sとのゲームの約束におくれちゃう!" % Donald, + "ねえねえ、キミ!"+lDonaldsDock+"で泳ぎに行こうかな?", + "あっ、お昼寝の時間だ!ドリームランドに行こうかなー。", + ] + ) + +DonaldChatter = ( + [ # Greetings + "ドリームランドへようこそ!", #CC_donald_chatter_greeting01.mp3 + "僕は" + Donald + "!君の名前は?", #CC_donald_chatter_greeting02.mp3 + ], + [ # Comments + "ここではたまにこわいことがあるんだよ。", #CC_donald_chatter_comments01.mp3 + "ねぇ、"+lDaisyGardens+"に行った?", #CC_donald_chatter_comments02.mp3 + "今日もいい日だねっ!", #CC_donald_chatter_comments03.mp3 + "ねぇ、" + Mickey + "を見なかった?", #CC_donald_chatter_comments041.mp3 + "" + Goofy + "によろしくね。", #CC_donald_chatter_comments05.mp3 + "釣りに行こうかなぁ", #CC_donald_chatter_comments06.mp3 + "わぉ、町の外に、コグたちがたくさんいるよ。" #CC_donald_chatter_comments07.mp3 + "もうボートには乗った?", #CC_donald_chatter_comments08.mp3 + "" + Daisy + "を見なかった?", #CC_donald_chatter_comments09.mp3 + "" + Daisy + "がお庭にお花を植えたみたいだよ。", #CC_donald_chatter_comments10.mp3 + "クワッ!", #CC_donald_chatter_comments11.mp3 + ], + [ # Goodbyes + "" + Minnie + "に会いに行こうかな?", #CC_donald_chatter_goodbyes01.mp3 + "" + Daisy + "とのデートに遅れちゃう…", #CC_donald_chatter_goodbyes02.mp3 + "よおっし、ちょっと泳ごうかなぁ…", #CC_donald_chatter_goodbyes03.mp3 + "ボートは楽しいな!", #CC_donald_chatter_goodbyes04.mp3 + ] + ) + +# April Fools Chatter's +AFMickeyChatter = ( + [ # Greetings specific to Mickey + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + "Hi, my name is "+Mickey+". What's yours?", + ], + [ # Comments + "Have you seen Daisy around?", + "I want to wish Daisy a happy April Toons' Week!", + "Did you hear a Doodle talk?", + "My, aren't these flowers nice!", + "I bet Daisy has some great Gardening tips!", + ], + [ # Goodbyes + "Hi, I am looking for Daisy. Have you seen her?", + "It's time for a nap. I'm going to Dreamland.", + ] + ) + +AFMinnieChatter = ( + [ # Greetings + "Hi, my name is "+Minnie+". What's yours?", + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "Hi, I need to give Pluto his lunch. Have you seen him?", + "I want to wish Pluto a happy April Toons' Week with a doggie treat!", + "Did you hear a Doodle talk?", + ], + [ # Goodbyes + "Hi, I need to give Pluto his lunch. Have you seen him?", + "Gosh, I'm late for my date with %s!" % Mickey, + ] + ) + +AFDaisyChatter = ( + [ # Greetings + "Hello, I'm "+Daisy+". What's your name?", + "Happy April Toons' Week!", + "Happy April Toons' Week, %!", + ], + [ # Comments + "I wonder if Mickey went to fight some Cogs?", + "Have you seen Mickey around?", + "I want to wish Mickey a happy April Toons' Week!", + "Did you hear a Doodle talk, or am I hearing things?", + ], + [ # Goodbyes + "Hi, I need to talk with Micky. Have you seen him?", + "I think I'll go swimming at "+lDonaldsDock+".", + "Oh, I'm a little sleepy. I think I'll go to Dreamland.", + ] + ) + +AFGoofySpeedwayChatter = ( + [ # Greetings + "Happy Sleepy, er, April Toons' Week!", + "Happy April Toons' Week, %!", + "Hi, my name is "+Goofy+". What's yours?", + ], + [ # Comments + "Gawrsh, have you seen Donald? I think he's been sleep walking again.", + "I want to wish Donald a happy April Toons' Week!", + "Did you hear a Doodle talk, or am I seeing things?", + "I hope everything is okay at the Speedway.", + ], + [ # Goodbyes + "Gawrsh, I'm late for my game with %s!" % Donald, + ] + ) + +AFDonaldChatter = ( + [ # Greetings + "Happy Sleepy, er, April Toons' Week!", + "Happy April Toons' Week, %!", + "Hi, my name is %s. What's yours?" % Donald, + ], + [ # Comments + "Have you seen Goofy around?", + "I want to wish Goofy a happy April Toons' Week!", + "Did you hear a Doodle talk, or am I dreaming?", + "Where did the kart come from?", + ], + [ # Goodbyes + "Where are all those loud car noises suddenly coming from?", + "I'm going to Melody Land to see %s!" % Minnie, + ] + ) + +CLGoofySpeedwayChatter = ( + [ # Greetings + "Welcome to "+lGoofySpeedway+".", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + "Hi there! Pardon my dusty clothes I've been busy fixin' that broken Leaderboard.", + ], + [ # Comments + "We better get this Leaderboard working soon, Grand Prix Weekend is coming up!", + "Does anybody want to buy a slightly used kart? It's only been through the Leaderboard once!", + "Grand Prix Weekend is coming, better get to practicing.", + "Grand Prix Weekend will be here on Friday, May 22 through Monday, May 25!", + "I'm gonna need a ladder to get that kart down.", + "That Toon really wanted to get on the Leaderboard!", + "Boy, I saw a terrific race earlier.", + "Watch out for banana peels on the race track!", + "Have you upgraded your kart lately?", + "We just got in some new rims at the kart shop.", + "Hey, have you seen "+Donald+"?", + "If you see my friend "+Mickey+", say hi to him for me.", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "I better go get my kart a new paint job for the upcoming Grand Prix Weekend.", + "Gosh, I better get workin' on this broken Leaderboard!", + "Hope I'll see y'all on Grand Prix Weekend! Goodbye!", + "It's time for a nap. I'm going to Dreamland to dream about winnin' the Grand Prix.", + ] + ) + + +GPGoofySpeedwayChatter = ( + [ # Greetings + "Welcome to "+lGoofySpeedway+".", + "Welcome to Grand Prix Weekend!", + "Hi, my name is "+Goofy+". What's yours?", + "Gawrsh, it's nice to see you %!", + ], + [ # Comments + "Are you excited about the Grand Prix Weekend?", + "Good thing we got the Leaderboard fixed.", + "We got the Leaderboard fixed just in time for Grand Prix Weekend!", + "We never did find that Toon!", + "Boy, I saw a terrific race earlier.", + "Watch out for banana peels on the race track!", + "Have you upgraded your kart lately?", + "We just got in some new rims at the kart shop.", + "Hey, have you seen "+Donald+"? He said he was gonna come watch the Grand Prix!", + "If you see my friend "+Mickey+", tell him he's missing some great racing!", + "D'oh! I forgot to fix "+Mickey+"'s breakfast!", + "Gawrsh there sure are a lot of "+Cogs+" near "+lDonaldsDock+".", + "At the Brrrgh branch of my Gag Shop, Hypno-Goggles are on sale for only 1 jellybean!", + "Goofy's Gag Shops offer the best jokes, tricks, and funnybone-ticklers in all of Toontown!", + "At Goofy's Gag Shops, every pie in the face is guaranteed to make a laugh or you get your jellybeans back!" + ], + [ # Goodbyes + "Good luck in the Grand Prix!", + "I'm going to catch the next race in the Grand Prix!", + "Gawrsh I think the next race is about to start!", + "Gosh, I better go check on the new Leaderboard and make sure it is working right!", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "新しいともだち" +FriendsListPanelSecrets = "ひみつリスト" +FriendsListPanelOnlineFriends = "オンラインの\nともだち" +FriendsListPanelAllFriends = "すべての\nともだち" +FriendsListPanelIgnoredFriends = "むしする\nトゥーン" +FriendsListPanelPets = "となりの\nペット" +FriendsListPanelPlayers = "すべての\nともだち" +FriendsListPanelOnlinePlayers = "オンラインの\nともだち" + +FriendInviterClickToon = "ともだちになりたいトゥーンをクリックしてね。\n\n(げんざいの友だち%s人)" + +# Support DISL account friends +FriendInviterToon = "トゥーン" +FriendInviterThatToon = "このトゥーン" +FriendInviterPlayer = "プレイヤー" +FriendInviterThatPlayer = "このプレイヤー" +FriendInviterBegin = "どのともだちタイプにする?" +FriendInviterToonFriendInfo = "トゥーンタウンだけのともだち" +FriendInviterPlayerFriendInfo = "Disney.jpのともだち" +FriendInviterToonTooMany = "あなたのおともだちがおおすぎてついかできません。%sをついかするには、ほかのトゥーンをさくじょしなくてはなりません。プレイヤーのともだちになれるときもあるのでためしてね。" +FriendInviterPlayerTooMany = "あなたのおともだちがおおすぎてついかできません。%sをついかするには、ほかのプレイヤーともだちをさくじょしなくてはなりません。トゥーンのともだちになれるときもあるのでためしてね。" +FriendInviterToonAlready = "%sはすでにキミのともだちです。" +FriendInviterPlayerAlready = "%sはすでにキミのともだちです。" +FriendInviterStopBeingToonFriends = "ともだちをやめる" +FriendInviterStopBeingPlayerFriends = "ともだちをやめる" +FriendInviterEndFriendshipToon = "ほんとうに%sのともだちをやめてもいいかい?" +FriendInviterEndFriendshipPlayer = "ほんとうに%sのともだちをやめてもいいかい?" +FriendInviterRemainToon = "\n(%sとはまだおともだちトゥーンです。)" +FriendInviterRemainPlayer = "\n(%sとはまだおともだちプレイヤーです。)" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "%(phase)sのダウンロードが%(percent)s%%しかされていないので、先に進めません。\n\n後で再試行してください" + +# TeaserPanel.py +TeaserTop = "" +TeaserBottom = "" +TeaserDefault = "\nフルアクセス専用だよ。\n登録しよう!" +TeaserOtherHoods = "7つの変わったエリアで楽しもう!" +TeaserTypeAName = "自分のトゥーンに好きな名前をつけよう!" +TeaserSixToons = "1つのアカウントでトゥーンを6つ作れるよ!" +TeaserClothing = "ユニークなアイテムでキミのトゥーンを目立たせよう!" +TeaserCogHQ = "強いコグたちの危険なエリアに忍び込もう!" +TeaserSecretChat = "ともだちとパスワードを交換して、オンラインでチャットしよう!" +TeaserSpecies = "サルやウマ、クマのトゥーンを作って遊ぼう!" +TeaserFishing = "全種類のサカナを集めてみよう!" +TeaserGolf = "しかけが一杯のゴルフコースで楽しもう!" +TeaserParties = "パーティーを開こう" +TeaserSubscribe = "今すぐ申し込む" +TeaserContinue = "お試し体験を続ける" +TeaserEmotions = "カタログでは「手をふる」、「ほめる」といったトゥーンの\n 表現も買うことができるよ。表現ゆたかなトゥーンにしよう!" +TeaserKarting = "ともだちのカートと一緒に楽しくレースしよう!" +TeaserKartingAccessories = "かっこいいアクセサリーで、キミのカートをカスタマイズしよう!" +TeaserGardening = "キミのおうちの庭を花や像やギャグの木できれいにかざろう!" +TeaserHaveFun = "楽しんでね!" +TeaserJoinUs = "登録しよう!" + +#TeaserCardsAndPosters = "" +#TeaserFurniture = "" +TeaserMinigames = TeaserOtherHoods +#TeaserHolidays = "" +TeaserQuests = TeaserOtherHoods +TeaserOtherGags = TeaserOtherHoods +#TeaserRental = "" +#TeaserBigger = "" +TeaserTricks = TeaserOtherHoods + + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "%sをダウンロード中…" +DownloadWatcherInitializing = "ダウンロードを始めます…" + +# Launcher.py +LauncherPhaseNames = { + 0 : "初期化中", + 1 : "パンダ", + 2 : "エンジン", + 3 : "トゥーン", + 3.5 : "トゥーントリアル", + 4 : "プレイグラウンド", + 5 : "ストリート", + 5.5 : "おうち", + 6 : "エリア①", + 7 : Cog, + 8 : "エリア②", + 9 : Cog + lHQ, + 10 : lCashbotHQ, + 11 : lLawbotHQ, + 12 : Bossbot + " HQ", + 13 : "パーティー", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)sの (%(current)s/%(total)s)" +LauncherStartingMessage = "トゥーンタウンをスタートしています…" +LauncherDownloadFile = LauncherProgress + "のアップデート中…" +LauncherDownloadFileBytes = LauncherProgress + "のアップデートをダウンロード中: %(bytes)s" +LauncherDownloadFilePercent = LauncherProgress + "のアップデートをダウンロード中: %(percent)s%%" +LauncherDecompressingFile = LauncherProgress + "のアップデートを解凍中…" +LauncherDecompressingPercent = LauncherProgress + "のアップデートを解凍中: %(percent)s%%" +LauncherExtractingFile = LauncherProgress + "のアップデートを抽出中…" +LauncherExtractingPercent = LauncherProgress + "のアップデートを抽出中: %(percent)s%%" +LauncherPatchingFile = LauncherProgress + "のアップデートを適用中…" +LauncherPatchingPercent = LauncherProgress + "のアップデートを適用中: %(percent)s%%" +LauncherConnectProxyAttempt = "トゥーンタウンに接続中: %s (proxy: %s) 試行中: %s" +LauncherConnectAttempt = "トゥーンタウンに接続中: %s attempt %s" +LauncherDownloadServerFileList = "トゥーンタウンをアップデート中…" +LauncherCreatingDownloadDb = "トゥーンタウンをアップデート中…" +LauncherDownloadClientFileList = "トゥーンタウンをアップデート中…" +LauncherFinishedDownloadDb = "トゥーンタウンをアップデート中…" +LauncherStartingToontown = "トゥーンタウンをスタート中…" +LauncherStartingGame = "トゥーンタウンをスタート中…" +LauncherRecoverFiles = "トゥーンタウンをアップデートしています。ファイルをリカバリー中…" +LauncherCheckUpdates = LauncherProgress + "のアップデートを確認中…" +LauncherVerifyPhase = "トゥーンタウンをアップデート中…" + +# AvatarChoice.py +AvatarChoiceMakeAToon = "トゥーンを\nつくろう!" +AvatarChoicePlayThisToon = "このトゥーンを\nえらぶ" +AvatarChoiceSubscribersOnly = "\nいますぐ\nメンバーに\nなろう!" #★ +AvatarChoiceDelete = "消す" +AvatarChoiceDeleteConfirm = "%s が削除されるよ。いいのかな?" +AvatarChoiceNameRejected = "なまえが\n使えないよ!" +AvatarChoiceNameApproved = "なまえが\n使えるよ!" +AvatarChoiceNameReview = "なまえを\nチエック中" +AvatarChoiceNameYourToon = "なまえを\nつけよう!" +AvatarChoiceDeletePasswordText = "%s が完全に削除されてしまうよ。 このトゥーンを削除する場合は、 パスワードを入力してね。" +AvatarChoiceDeleteConfirmText = "%(name)s が完全に削除されてしまうよ。このトゥーンを削除したい場合は、\"%(confirm)s\"と入力してから「OK」をクリックしてね。" +AvatarChoiceDeleteConfirmUserTypes = "削除" +AvatarChoiceDeletePasswordTitle = "このトゥーンを削除しますか?" +AvatarChoicePassword = "パスワード" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "パスワードが合っていないようだよ。このトゥーンを削除したい場合は、キミのパスワードを入力してね。" +AvatarChoiceDeleteWrongConfirm = "入力されたものは間違っているよ。%(name)sを削除したい場合は\"%(confirm)s\"と入力してから「OK」をクリックしてね。引用符(" ")は入力しないでください。削除したくなくなった場合は「キャンセル」をクリックしてね。" + +# AvatarChooser.py +AvatarChooserPickAToon = "プレイするトゥーンをえらぶ" +AvatarChooserQuit = lQuit + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "ディズニー・インターネット・グループ・カスタマーセンター(%s)にごれんらくください。" +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nお問い合わせ等は、ディズニー・インターネット・グループ・カスタマーセンター(%s)までお願いします。" +TTAccountIntractibleError = "エラーが発生しました。" + +# DateOfBirthEntry.py ★「月」だけでなく「年」や「日」にも単位をつけたい +DateOfBirthEntryMonths = ['1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月',] +DateOfBirthEntryDefaultLabel = "生年月日" + + +# AchievePage.py +AchievePageTitle = "アチーブメント\n(近日公開予定)" + +# PhotoPage.py +PhotoPageTitle = "写真\n(近日公開予定)" + +# BuildingPage.py +BuildingPageTitle = "ビル\n(近日公開予定)" + +# InventoryPage.py +InventoryPageTitle = "ギャグ" +InventoryPageDeleteTitle = "ギャグを削除する" +InventoryPageTrackFull = "%sのギャグトラックはすべてそろってます。" +InventoryPagePluralPoints = "%(trackName)sのポイントを%(numPoints)sかせげば、新しい\n%(trackName)sのギャグを得ることができます。" +InventoryPageSinglePoint = "%(trackName)sのポイントを%(numPoints)sかせげば、新しい\n%(trackName)sのギャグを得ることができます。" +InventoryPageNoAccess = "まだ%sのトラックにはアクセスできません。" + +# NPCFriendPage.py +NPCFriendPageTitle = "SOSトゥーン" + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "のこり%s回" + +# EventsPage.py +#もともとMonthsに“1月”という表現だったので画面上で「1月月」と +#ならないようフォーマットを変更しました。2009年11月19日 +PartyDateFormat = "%(yyyy).4d年%(mm)s%(dd)d日" +PartyTimeFormat = "%d:%.2d %s" # 1:45 pm +PartyTimeFormatMeridiemAM = "AM" +PartyTimeFormatMeridiemPM = "PM" +PartyCanStart = "パーティータイム!トゥーンガイドのしゅさい者ページを開いて「パーティーを始める」をクリック!" +PartyHasStartedAcceptedInvite = '%s パーティーが始まったよ!トゥーンガイドの参加者ページを開いて「パーティーにゆく」をクリック!' +PartyHasStartedNotAcceptedInvite = '%s パーティーが始まったよ!しゅさい者にワープしても参加できるよ。' + +EventsPageName = "イベント" +EventsPageCalendarTabName = "カレンダー" +EventsPageCalendarTabParty = "パーティー" +EventsPageToontownTimeIs = "トゥーンタウン時間" +EventsPageConfirmCancel = "キャンセルするとジェリービーン%d%%が返ってきます。本当にパーティーをキャンセルする?" +EventsPageCancelPartyResultOk = "キミのパーティーはキャンセルされました。ジェリービーン%dが返ってきました。" +EventsPageCancelPartyResultError = "残念、キミのパーティーはキャンセルされませんでした。" +EventsPageTooLateToStart = "残念、パーティーを始めるにはおそすぎたよ。キャンセルして次のプランを立てよう。" +EventsPagePublicPrivateChange = "キミのパーティーのプライバシー設定を変えるよ..." +EventsPagePublicPrivateNoGo = "ごめんね、今はパーティーのプライバシー設定は変えられません。" +EventsPagePublicPrivateAlreadyStarted = "ごめんね、キミのパーティーはもう始まっているからプライバシー設定は変えられません。" +EventsPageHostTabName = "しゅさい者" # displayed on the physical tab +EventsPageHostTabTitle = "次のパーティー" # banner text displayed across the top +EventsPageHostTabTitleNoParties = "次のパーティー なし" +EventsPageHostTabDateTimeLabel = "次のパーティーは%s、トゥーンタウン時間の%sからです。" +EventsPageHostingTabNoParty = "パーティーを開くには\nプレイグラウンドの\nパーティーゲートにゆこう!" +EventsPageHostTabPublicPrivateLabel = "このパーティーは:" +EventsPageHostTabToggleToPrivate = "プライベート" +EventsPageHostTabToggleToPublic = "だれでも" +EventsPageHostingTabGuestListTitle = "ゲスト" +EventsPageHostingTabActivityListTitle = "アクティビティ" +EventsPageHostingTabDecorationsListTitle = "デコレーション" +EventsPageHostingTabPartiesListTitle = "しゅさい者" +EventsPageHostTabCancelButton = "パーティーをキャンセル" +EventsPageGoButton = "パーティーを\n始める" +EventsPageGoBackButton = "パーティーに\nようこそ!" +EventsPageInviteGoButton = "パーティーに\n行こう!" +EventsPageUnknownToon = "知らないトゥーン" + +EventsPageInvitedTabName = "招待" +EventsPageInvitedTabTitle = "招待状" +EventsPageInvitedTabInvitationListTitle = "招待リスト" +EventsPageInvitedTabActivityListTitle = "アクティビティ" +EventsPageInvitedTabTime = "トゥーンタウン時間 %s %s" + +EventsPageNewsTabName = "ニュース" +EventsPageNewsTabTitle = "ニュース" +EventsPageNewsDownloading= "ニュースをひょうじ中..." +EventsPageNewsUnavailable = "チップとデールがいんさつきをこわしちゃった!ニュースはとどかないよ。" +EventsPageNewsPaperTitle = "トゥーンタウン・マガジン" +EventsPageNewsLeftSubtitle = "ジェリービーンで買える!" +EventsPageNewsRightSubtitle = "トゥーン暦 2009年 創刊" + +# InvitationSelection.py +SelectedInvitationInformation = "%sが %sのトゥーンタウン時間の%sからパーティーを開くよ。" + +# PartyPlanner.py +PartyPlannerNextButton = "つづける" +PartyPlannerPreviousButton = "もどる" +PartyPlannerWelcomeTitle = "トゥーンタウン パーティープランナー" +PartyPlannerInstructions = "パーティーを開こう!まずは下の矢印からプランを立ててみよう。" +PartyPlannerDateTitle = "パーティーの日付は?" +PartyPlannerTimeTitle = "パーティーは何時から?" +PartyPlannerGuestTitle = "ゲストを選んでね" +PartyPlannerEditorTitle = "パーティーの場所・デコレーション\n・アクティビティを決めよう" +PartyPlannerConfirmTitle = "招待状をえらんでね" +PartyPlannerConfirmTitleNoFriends = "キミのパーティープラン" +PartyPlannerTimeToontown = "トゥーンタウン" +PartyPlannerTimeTime = "時刻" +PartyPlannerTimeRecap = "パーティーの日時" +PartyPlannerPartyNow = "今すぐ" +PartyPlannerTimeToontownTime = "トゥーンタウン時間:" +PartyPlannerTimeLocalTime = "日本時間:" +PartyPlannerPublicPrivateLabel = "このパーティーは…" +PartyPlannerPublicDescription = "だれでも\n参加できます" +PartyPlannerPrivateDescription = "招待された\nトゥーンだけ\n参加できます" +PartyPlannerPublic = "パブリック" +PartyPlannerPrivate = "プライベート" +PartyPlannerCheckAll = "全部\nえらぶ" +PartyPlannerUncheckAll = "全部\nやめる" +PartyPlannerDateText = "日付" +PartyPlannerTimeText = "時刻" +PartyPlannerTTTimeText = "トゥーンタウン時刻" +PartyPlannerEditorInstructionsIdle = "買いたいアクティビティやデコレーションをクリックしてね。" +PartyPlannerEditorInstructionsClickedElementActivity = "「買う」をクリックするか、ほしいアクティビティをマップにドラッグしてね。" +PartyPlannerEditorInstructionsClickedElementDecoration = "「買う」をクリックするか、ほしいデコレーションをマップにドラッグしてね。" +PartyPlannerEditorInstructionsDraggingActivity = "ほしいアクティビティをマップにドラッグしてね。" +PartyPlannerEditorInstructionsDraggingDecoration = "ほしいデコレーションをマップにドラッグしてね。" +PartyPlannerEditorInstructionsPartyGrounds = "アイテムを動かすには、マップ上でドラッグしてね。" +PartyPlannerEditorInstructionsTrash = "アクティビティやデコレーションをさくじょする時は、ここにドラッグしてね" +PartyPlannerEditorInstructionsNoRoom = "そのアクティビティに十分なスペースがありません。" +PartyPlannerEditorInstructionsRemoved = "%(removed)sは%(added)sが追加されたので削除されました。" +PartyPlannerBeans = "ビーン" +PartyPlannerTotalCost = "トータルコスト:\n%d ビーン" +PartyPlannerSoldOut = "売切れ" +PartyPlannerBuy = "買う" +PartyPlannerPaidOnly = "フルアクセス専用" +PartyPlannerPartyGrounds = "パーティー・グラウンド・マップ" +PartyPlannerOkWithGroundsLayout = "パーティーのアクティビティやデコレーションのプランはこれでOKかな?" +PartyPlannerChooseFutureTime = "今より先の時間を指定してね。" +PartyPlannerInviteButton = "招待状を送る" +PartyPlannerInviteButtonNoFriends = "パーティーを計画する" +PartyPlannerBirthdayTheme = "誕生日" +PartyPlannerGenericMaleTheme = "スター" +PartyPlannerGenericFemaleTheme = "フラワー" +PartyPlannerRacingTheme = "レーシング" +PartyPlannerGuestName = "ゲスト名" +PartyPlannerClosePlanner = "プランナーを閉じる" +PartyPlannerConfirmationAllOkTitle = "おめでとう!" +PartyPlannerConfirmationAllOkText = "キミのパーティーが設定されて、招待状が送られました。\n楽しみだね!" +PartyPlannerConfirmationAllOkTextNoFriends = "キミのパーティーが設定されました。\n楽しみだね!" +PartyPlannerConfirmationErrorTitle = "あれれ…" +PartyPlannerConfirmationValidationErrorText = "あれれ…、このパーティー設定には問題があるみたい。\n戻ってもう一度かくにんしてみてね。" +PartyPlannerConfirmationDatabaseErrorText = "ごめん、キミの情報が正しくほぞんできなかったみたい。\n後でもう一度ためしてね。\nジェリービーンはそのままだよ。" +PartyPlannerConfirmationTooManyText = "すでにパーティーを設定ずみみたい…。\nもし新しいパーティーを開きたい時は、\n先に今あるパーティーをキャンセルしてね。" +PartyPlannerInvitationThemeWhatSentence = "%sパーティーに招待するよ!来てくれるかい、%s" +PartyPlannerInvitationThemeWhatSentenceNoFriends = "%sパーティーを開くんだ!来てくれるかい、%s" +PartyPlannerInvitationThemeWhatActivitiesBeginning = "アクティビティ " +PartyPlannerInvitationWhoseSentence = "%sのパーティー" +PartyPlannerInvitationTheme = "テーマ" +PartyPlannerInvitationWhenSentence = "%s、\n トゥーンタウン時間の%sからスタート!\n来てくれたらうれしいな♪" +PartyPlannerInvitationWhenSentenceNoFriends = "%s、\n トゥーンタウン時間の%sからスタート!\nみんなで楽しもう♪" +PartyPlannerComingSoon = "近日公開" +PartyPlannerCantBuy= "買えないよ" +PartyPlannerGenericName = "パーティープランナー" + +# DistributedPartyJukeboxActivity.py +PartyJukeboxOccupied = "まだだれかがジュークボックスを使用中。また後でためそう…" +PartyJukeboxNowPlaying = "その曲は今流れている曲だよ" + +# Jukebox Music +MusicEncntrGeneralBg = "コグとのそうぐう" +MusicTcSzActivity = "トゥーントリアル・メドレー" +MusicTcSz = "タウンのさんぽ" +MusicCreateAToon = "タウンとニュー・トゥーン" +MusicTtTheme = "トゥーンタウンのテーマ" +MusicMinigameRace = "ゆっくり、しっかり…" +MusicMgPairing = "おぼえてる?" +MusicTcNbrhood = "トゥーンタウン・セントラル" +MusicMgDiving = "宝のララバイ" +MusicMgCannonGame = "キャノン砲、発射!" +MusicMgTwodgame = "ランニング・トゥーン" +MusicMgCogthief = "コグをとらえろ!" +MusicMgTravel = "トラベリング・ミュージック" +MusicMgTugOWar = "ひっぱれ!" +MusicMgVine = "ジャングル・ジャンプ" +MusicMgIcegame = "止まらない~!" +MusicMgToontag = "トロリーゲーム・メドレー" +MusicMMatchBg2 = "マッチ・ミニー" +MusicMgTarget = "ただ今タウン上空…" +MusicFfSafezone = "ファニー・ファーム" +MusicDdSz = "ワドリング・ウェイ" +MusicMmNbrhood = "ミニーのメロディーランド" +MusicGzPlaygolf = "今週末どうですか!" +MusicGsSz = "グーフィー・スピードウェイ" +MusicOzSz = "ドングリひろば" +MusicGsRaceCc = "ダウンタウン・ドライビング" +MusicGsRaceSs = "レディ、セット、Go!" +MusicGsRaceRr = "ルート66" +MusicGzSz = "パット・パット・ポルカ" +MusicMmSz = "ストリート・ダンサー" +MusicMmSzActivity = "ソプラノはまかせて" +MusicDdNbrhood = "ドナルドのハトバ" +MusicGsKartshop = "Mr. グーフィーレンチ" +MusicDdSzActivity = "海辺の小屋" +MusicEncntrGeneralBgIndoor = "ビルの中" +MusicTtElevator = "上へまいりま~す" +MusicEncntrToonWinningIndoor = "トゥーンよ、集結せよ" +MusicEncntrGeneralSuitWinningIndoor = "コグタストロフ!" +MusicTbNbrhood = "ブルルル…" +MusicDlNbrhood = "ドナルドのドリームランド" +MusicDlSzActivity = "ヒツジがいっぴき…" +MusicDgSz = "花のワルツ" +MusicDlSz = "ねてる…よね?" +MusicTbSzActivity = "ス・ノープロブレム!" +MusicTbSz = "がたがた、ブルブル…" +MusicDgNbrhood = "デイジー・ガーデン" +MusicEncntrHallOfFame = "めいよトゥーン!" +MusicEncntrSuitHqNbrhood = "ダラーズ アンド センツ" +MusicChqFactBg = "コグ・ファクトリー" +MusicCoghqFinale = "トゥーン達の勝利" +MusicEncntrToonWinning = "おいくら?" +MusicEncntrSuitWinning = "お値引きします…" +MusicEncntrHeadSuitTheme = "ビッグ・ボス" +MusicLbJurybg = "せいしゅくに!" +MusicLbCourtyard = "バランスをとって" +MusicBossbotCeoV2 = "コグの指導者" +MusicBossbotFactoryV1 = "コグ・ワルツ" +MusicBossbotCeoV1 = "こまった上司" +MusicPartyOriginalTheme = "パーティー・タイム" +MusicPartyPolkaDance = "パーティー・ポルカ" +MusicPartySwingDance = "パーティー・スウィング" +MusicPartyWaltzDance = "パーティー・ワルツ" +MusicPartyGenericThemeJazzy = "パーティー・ジャズ" +MusicPartyGenericTheme = "パーティー・ジャングル" + + +# JukeBoxGui +JukeboxAddSong = "曲を\nついか" +JukeboxReplaceSong = "曲の\nいれかえ" +JukeboxQueueLabel = "次をプレイ:" +JukeboxSongsLabel = "曲を選ぶ:" +JukeboxClose = "OK" +JukeboxCurrentlyPlaying = "今、再生中…" +JukeboxCurrentlyPlayingNothing = "ジュークボックス 一時停止" +JukeboxCurrentSongNothing = "プレイリストに曲を追加" + +PartyOverWarningNoName = "パーティーが終了しました。来てくれてありがとう!" +PartyOverWarningWithName = "%sのパーティーが終了しました。来てくれてありがとう!" +PartyCountdownClockText = "残り\n\n時間" +PartyTitleText = "%sのパーティー!" # what you see when you enter a party + +PartyActivityConjunction = ", " +# Note : This dictionary is used to show the names of the activities in various +# contexts. If PartyGlobals.ActivityIds is changed, this list must be +# updated with new indices. +PartyActivityNameDict = { + 0 : { + "generic" : "20曲入り\nジュークボックス", + "invite" : "20曲入りジュークボックス", + "editor" : "ジュークボックス - 20", + "description" : "キミだけの20曲入りジュークボックスを楽しもう!" + }, + 1 : { + "generic" : "パーティー・キャノン", + "invite" : "パーティー・キャノン", + "editor" : "キャノン", + "description" : "みんなでキャノンゲームを楽しもう♪" + }, + 2 : { + "generic" : "トランポリン", + "invite" : "トランポリン", + "editor" : "トランポリン", + "description" : "ジェリービーンを集めながらどこまでもジャンプ!" + }, + 3 : { + "generic" : "パーティー・キャッチ", + "invite" : "パーティー・キャッチ", + "editor" : "パーティー・キャッチ", + "description" : "いたぁ~いカナドコをよけて、甘~いフルーツをキャッチ!" + }, + 4 : { + "generic" : "10ムーブ\nダンスフロア", + "invite" : "10ムーブダンスフロア", + "editor" : "ダンスフロア - 10", + "description" : "10種類のムーブをくみあわせてスタイリッシュにダンス♪" + }, + 5 : { + "generic" : "パーティー・つなひき", + "invite" : "パーティー・つなひき", + "editor" : "つなひき", + "description" : "最大4対4のつなひきマッドネス!" + }, + 6 : { + "generic" : "プレイベート花火大会", + "invite" : "プレイベート花火大会", + "editor" : "花火大会", + "description" : "あこがれのプライベート花火大会を開こう!" + }, + 7 : { + "generic" : "パーティー・クロック", + "invite" : "パーティー・クロック", + "editor" : "パーティー・クロック", + "description" : "パーティーの残り時間をカウントダウンしてくれるよ。" + }, + 8 : { + "generic" : "40曲入り\nジュークボックス", + "invite" : "20曲入りジュークボックス", + "editor" : "ジュークボックス - 40", + "description" : "40曲入りなら大好きな曲を全部カバーできるね!" + }, + 9 : { + "generic" : "20ムーブ\nダンスフロア", + "invite" : "20ムーブダンスフロア", + "editor" : "ダンスフロア - 20", + "description" : "20種類のムーブを使いこなせば、キミはもうダンスマスター♪" + }, +} + +# Note : This dictionary is used to show the names of the decorations in various +# contexts. If PartyGlobals.DecorationIds is changed, this list must be +# updated with new indices. +PartyDecorationNameDict = { + 0 : { + "editor" : "ふうせん用のおもり", + "description" : "飛んでいっちゃったらイヤだものね!", + }, + 1 : { + "editor" : "メイン・ステージ", + "description" : "ふうせん、ホシ、他にほしいものは?", + }, + 2 : { + "editor" : "パーティー用リボン", + "description" : "パーティーではやっぱりおしゃれに☆", + }, + 3 : { + "editor" : "ケーキ", + "description" : "おいしいヨ♪", + }, + 4 : { + "editor" : "パーティー・キャッスル", + "description" : "トゥーンのおうちがお城に…!?", + }, + 5 : { + "editor" : "山積みのギフト", + "description" : "本日のお土産で~す。", + }, + 6 : { + "editor" : "ストリーマー・ホーン", + "description" : "ゆかいで楽しいホーンだよ。", + }, + 7 : { + "editor" : "パーティー・ゲート", + "description" : "カラフル&ポップ!もりあがるよね♪", + }, + 8 : { + "editor" : "ノイズ・メーカー", + "description" : "ぺちゃくちゃ、ざわざわ…!", + }, + 9 : { + "editor" : "かざぐるま", + "description" : "みんなが大好きなカラフルなかざぐるま。", + }, + 10 : { + "editor" : "ギャグ・グローブ", + "description" : "デザインがすばらしいギャグと星のグローブ", + }, + 11 : { + "editor" : "JBバナー", + "description" : "ジェリービーンのバナーも一味違っていい感じ☆", + }, + 12 : { + "editor" : "ギャグのケーキ", + "description" : "これがないとパーティーがもりあがらないよね。", + }, +} + +ActivityLabel = "コスト - アクティビティ名" +PartyDoYouWantToPlan = "新しくパーティーを\n設定する?" +PartyPlannerOnYourWay = "楽しいパーティーにしてみんなともりあがろう!" +PartyPlannerMaybeNextTime = "また次のきかいに設定しよう。またね!" +PartyPlannerHostingTooMany = "一度に1つのパーティーしかプランできません。" +PartyPlannerOnlyPaid = "パーティーのプランはフルアクセス・メンバー専用です。" +PartyPlannerNpcComingSoon = "もうすぐパーティーが設定できるようになるよ。" +PartyPlannerNpcMinCost = "パーティーのプランには、最低%dコのジェリービーンが必要です。" + +# Party Gates +PartyHatPublicPartyChoose = "次のパブリック・パーティー(参加自由)に行ってみる?" +PartyGateTitle = "パブリック\n・パーティー" +PartyGateGoToParty = "パーティーに\n行く!" +PartyGatePartiesListTitle = "しゅさい者" +PartyGatesPartiesListToons = "トゥーン" +PartyGatesPartiesListActivities = "アクティビティ" +PartyGatesPartiesListMinLeft = "残り時間(分)" +PartyGateLeftSign = "いらっしゃい!" +PartyGateRightSign = "パブリック\n・パーティー" +PartyGatePartyUnavailable = "残念、そのパーティーにはもう参加できないよ。" +PartyGatePartyFull = "残念、そのパーティーはもう満席だよ。" +PartyGateInstructions = 'しゅさい者をクリックして、"パーティーに行く!"をクリックしてね。' + +# DistributedPartyActivity.py +PartyActivityWaitingForOtherPlayers = "他のトゥーンが参加するのを待っています..." +PartyActivityPleaseWait = "ちょっと待ってね ..." +DefaultPartyActivityTitle = "パーティー・ゲーム名" +DefaultPartyActivityInstructions = "パーティー・ゲーム あそび方" +PartyOnlyHostLeverPull = "しゅさい者しかこのゲームを開始できません。" +PartyActivityDefaultJoinDeny = "残念、今は参加できません。" +PartyActivityDefaultExitDeny = "今はこのクティビティを中止できません。" + +# JellybeanRewardGui.py +PartyJellybeanRewardOK = "OK" + +# DistributedPartyCatchActivity.py +PartyCatchActivityTitle = "パーティー・キャッチ アクティビティ" +PartyCatchActivityInstructions = "たくさんのフルーツをキャッチしよう。%(badThing)sには気をつけてね。" +PartyCatchActivityFinishPerfect = "パーフェクト・ゲーム!" +PartyCatchActivityFinish = "よくやったね!" +PartyCatchActivityExit = '終了' +PartyCatchActivityApples = 'リンゴ' +PartyCatchActivityOranges = 'オレンジ' +PartyCatchActivityPears = '西洋ナシ' +PartyCatchActivityCoconuts = 'ココナツ' +PartyCatchActivityWatermelons = 'スイカ' +PartyCatchActivityPineapples = 'パイナップル' +PartyCatchActivityAnvils = 'カナドコ' +PartyCatchStarted = "ゲームが始まってるよ。いそごう!" +PartyCatchCannotStart = "ゲームが開始されませんでした。" +PartyCatchRewardMessage = "キャッチしたフルーツ: %s\n\nジェリービーン: %sコ" + +# DistributedPartyDanceActivity.py +PartyDanceActivityTitle = "パーティー ダンス・フロア" +PartyDanceActivityInstructions = "3つ以上の矢印キーの組合せでムーブをきめよう!ムーブは10種類あるよ。全部見つけてみよう。" +PartyDanceActivity20Title = "パーティー ダンス・フロア" +PartyDanceActivity20Instructions = "3つ以上の矢印キーの組合せでムーブをきめよう!ムーブは20種類あるよ。全部見つけてみよう。" + +DanceAnimRight = "ライト" +DanceAnimReelNeutral = "フィッシャー・トゥーン" +DanceAnimConked = "ヘッドボブ" +DanceAnimHappyDance = "ハッピーダンス" +DanceAnimConfused = "ベリー・ディジー" +DanceAnimWalk = "ムーン・ウォーキング" +DanceAnimJump = "ジャンプ!" +DanceAnimFirehose = "ファイヤートゥーン" +DanceAnimShrug = "フー・ノウズ?" +DanceAnimSlipForward = "フォール" +DanceAnimSadWalk = "タイアード" +DanceAnimWave = "ハロー・グッドバイ!" +DanceAnimStruggle = "シャッフル・ホップ" +DanceAnimRunningJump = "ランニング・トゥーン" +DanceAnimSlipBackward = "バック・フォール" +DanceAnimDown = "ダウン" +DanceAnimUp = "アップ" +DanceAnimGoodPutt = "パット" +DanceAnimVictory = "ビクトリー・ダンス" +DanceAnimPush = "マイム・トゥーン" +DanceAnimAngry = "ロックンロール" +DanceAnimLeft = "レフト" + +# DistributedPartyCannonActivity.py +PartyCannonActivityTitle = "パーティー・キャノン" +PartyCannonActivityInstructions = "雲に当たると色が変わってはね返されるよ。飛んでる間は矢印キーでコントロールしてみてね。" +PartyCannonResults = "%dコのジェリービーンをゲット!\n\nヒットした雲の数: %d" + +# DistributedPartyFireworksActivity.py +FireworksActivityInstructions = "\"Page Up(PgUp)キー\"を押すと空を見上げられるよ。" +FireworksActivityBeginning = "プライベート花火大会が始まるよ!楽しんでね!!" +FireworksActivityEnding = "楽しんでもらえたかな!" +PartyFireworksAlreadyActive = "花火大会がもう始まってるよ。" +PartyFireworksAlreadyDone = "花火大会が終了しました。" + +# DistributedPartyTrampolineActivity.py +PartyTrampolineJellyBeanTitle = "ジェリービーン・トランポリン" +PartyTrampolineTricksTitle = "トリック・トランポリン" +PartyTrampolineActivityInstructions = "コントロール(Ctrl)キーでジャンプ。\n\nトゥーンが一番低い位置に来た時にジャンプするとより高くとべるよ。" +PartyTrampolineActivityOccupied = "トランポリンは使用中です。" +PartyTrampolineQuitEarlyButton = "おりる" +PartyTrampolineBeanResults = "ジェリービーンを%dコかくとく" +PartyTrampolineBonusBeanResults = "ジェリービーンを%dコ + %dコ(ビッグ・ビーン ボーナス)をかくとく" +PartyTrampolineTopHeightResults = "キミのベスト・ジャンプ: %dメートル" +PartyTrampolineTimesUp = "ゲーム終了!" +PartyTrampolineReady = "ようい..." +PartyTrampolineGo = "スタート!" +PartyTrampolineBestHeight = "今までのベスト・ジャンプ: \n%s\n%dメートル" +PartyTrampolineNoHeightYet = "どこまで高く\nとべるかな?" + +# DistributedPartyTugOfWarActivity.py +PartyTugOfWarJoinDenied = "ごめん、今はつなひきには参加できないよ。" +PartyTugOfWarTeamFull = "残念、このチームはもう満員です。" +PartyTugOfWarExitButton = "おりる" +PartyTugOfWarWaitingForMore = "他のトゥーンを待っています。" # extra spaces on purpose given the blocky font +PartyTugOfWarWaitingToStart = "スタート待ち…" +PartyTugOfWarWaitingForOtherPlayers = "他のトゥーンを待っています。" +PartyTugOfWarReady = "ようい..." +PartyTugOfWarGo = "スタート!" +PartyTugOfWarGameEnd = "良い ゲームだったね!" +PartyTugOfWarGameTie = "ひき わけ!" +PartyTugOfWarRewardMessage = "ジェリービーン%dコかくとく。やったね!" +PartyTugOfWarTitle = "パーティー・つなひき" + +# CalendarGuiMonth.py +CalendarShowAll = "ぜんぶ見る" +CalendarShowOnlyHolidays = "お休みだけ見る" +CalendarShowOnlyParties = "パーティーだけ見る" + +# CalendarGuiDay.py +CalendarEndsAt = "おわる時間 " +CalendarStartedOn = "はじまる時間 " +CalendarEndDash = "おわり-" +CalendarEndOf = "おわりは " +CalendarPartyGetReady = "じゅんびしよう!" +CalendarPartyGo = "パーティーだ♪" +CalendarPartyFinished = "おわりだよ..." +CalendarPartyCancelled = "中止" +CalendarPartyNeverStarted = "スタートできません" + +# MapPage.py +MapPageTitle = "地図" +MapPageBackToPlayground = "プレイグラウンドに戻る" +MapPageBackToCogHQ = "コグ本部に戻る" +MapPageGoHome = "家に帰る" +# hood name, street name +MapPageYouAreHere = " %s\n%s" +MapPageYouAreAtHome = "\n自分のおうちにいます" +MapPageYouAreAtSomeonesHome = "%s のおうちにいます" +MapPageGoTo = "%s\nへ行く" + +# OptionsPage.py +OptionsPageTitle = "オプション" +OptionsPagePurchase = "今すぐ申し込む" +OptionsPageLogout = "ログアウト" +OptionsPageExitToontown = "ゲームを終了する" +OptionsPageMusicOnLabel = "おんがく: あり" +OptionsPageMusicOffLabel = "おんがく: なし" +OptionsPageSFXOnLabel = "こうかおん: あり" +OptionsPageSFXOffLabel = "こうかおん: なし" +OptionsPageToonChatSoundsOnLabel = "チャットおん: あり" +OptionsPageToonChatSoundsOffLabel = "チャットおん: なし" +OptionsPageFriendsEnabledLabel = "ともだち:うけつける" +OptionsPageFriendsDisabledLabel = "ともだち:うけつけない" +OptionsPageSpeedChatStyleLabel = "スピードチャットの色" +OptionsPageDisplayWindowed = "ウインドウ・モード" +OptionsPageSelect = "選択する" +OptionsPageToggleOn = "きりかえ" +OptionsPageToggleOff = "きりかえ" +OptionsPageChange = "へんこう" +OptionsPageDisplaySettings = "かいぞうど: %(screensize)s、 %(api)s" +OptionsPageDisplaySettingsNoApi = "かいぞうど: %(screensize)s" +OptionsPageExitConfirm = "トゥーンタウン・\nオンラインを\n終了しますか?" + +DisplaySettingsTitle = "がめんひょうじせってい" +DisplaySettingsIntro = "トゥーンタウン・オンラインのひょうじのせっていをします。(おうちのひとと見てね)\nトゥーンタウン・オンラインでのテキストやグラフィックレベルを向上するため、画面解像度を高めに設定してもかまいませんが、ご使用のグラフィックカードにより、いくつかの設定でゲームのスピードが遅くなったり、全く動かなくなったりする可能性がありますのであらかじめご了承ください。 " +DisplaySettingsIntroSimple = "トゥーンタウンでのテキストやグラフィックレベルを向上するため、画面解像度を高めに設定してもかまいませんが、ご使用のグラフィックカードにより、いくつかの設定は、ゲームのスピードが遅くなったり、全く動かなくなったりする可能性があります。" + +DisplaySettingsApi = "グラフィックス API:" +DisplaySettingsResolution = "かいぞうど:" +DisplaySettingsWindowed = "ウインドウ・モード" +DisplaySettingsFullscreen = "フルスクリーン・モード" +DisplaySettingsApply = "OK" +DisplaySettingsCancel = "キャンセル" +DisplaySettingsApplyWarning = "OKボタンを押すと、表示設定が変わります。 新しい設定がコンピュータ上で正常に表示されない場合、自動的に%s秒後、元の状態に戻ります。" +DisplaySettingsAccept = "これでよろしければOKボタンを押してください。何も押さないと、%s秒後に自動的に変更する前の設定に戻ります。" +DisplaySettingsRevertUser = "前の表示設定に戻しました。" +DisplaySettingsRevertFailed = "選択された表示設定はお客様のコンピュータでは作動しません。前の表示設定が復帰しました。" + +# TrackPage.py +TrackPageTitle = "ギャグ・トラック・トレーニング" +TrackPageShortTitle = "ギャグ\nトレーニング" +TrackPageSubtitle = "トゥーンタスクをこなして、新しい種類のギャグをおぼえよう!" +TrackPageTraining = "%s ギャグを使用するトレーニングをしています。\n16コマ分のタスクをすべて終了すると、\n バトルで%sギャグを使えるようになります。" +TrackPageClear = "現在、どのトラックのトレーニングも始めていません。" +TrackPageFilmTitle = "%s\nトレーニング\nフィルム" +TrackPageDone = "おわり" + +# QuestPage.py +QuestPageToonTasks = "トゥーンタスク" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nへ: %s\n %s\n %s\n %s\n\nから: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nから:%s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\n選びに、 \n %s\n %s\n %s\n %sn %sへ行ってね。" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "選んでね" +QuestPageLocked = "へいさ中" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "どの通りでも" +QuestPosterHQLocationName = "どのエリアでも" + +QuestPosterTailor = "したてやさん" +QuestPosterTailorBuildingName = "ようふくや" +QuestPosterTailorStreetName = "どのプレイグラウンドでも" +QuestPosterTailorLocationName = "どのエリアでも" +QuestPosterPlayground = "プレイグラウンドで" +QuestPosterAtHome = "おうちで" +QuestPosterInHome = "おうちの中で" +QuestPosterOnPhone = "電話で" +QuestPosterEstate = "おうちで" +QuestPosterAnywhere = "どこでも" #★ +QuestPosterAuxTo = "→" +QuestPosterAuxFrom = "←" +QuestPosterAuxFor = "" #★ +QuestPosterAuxOr = "または" +QuestPosterAuxReturnTo = "" #★ +QuestPosterLocationIn = " in " +QuestPosterLocationOn = " in " +QuestPosterFun = "楽しいよ!" +QuestPosterFishing = "つりにいく" +QuestPosterComplete = "完了" + +# ShardPage.py +ShardPageTitle = "ロビー" +ShardPageHelpIntro = "それぞれのロビーは、\nトゥーンタウンの世界の\nコピーなんだ。" +ShardPageHelpWhere = "いま、君は\"%s\" にいるよ。" +ShardPageHelpWelcomeValley = "いま、君は\"%s\"の\"ウェルカムバレー\"にいるよ。" +ShardPageHelpMove = "\n\n新しいロビーに行くには、ロビーの名前をクリックしてね。" + +ShardPagePopulationTotal = "全トゥーンタウンの人口:\n%d" +ShardPageScrollTitle = "名前 人口" +ShardPageLow = "すいてる" +ShardPageMed = "てきせつ" +ShardPageHigh = "こんざつ" +ShardPageChoiceReject = "このロビーはこんざつしています。他をえらんで下さい。" + +# SuitPage.py +SuitPageTitle = Cog + "ギャラリー" +SuitPageMystery = "???" +SuitPageQuota = "%s / %s" +SuitPageCogRadar = "%s体発見!" #★ +SuitPageBuildingRadarS = "%s 建物" +SuitPageBuildingRadarP = "%s 建物" + +# DisguisePage.py +DisguisePageTitle = Cog + "へんそう\nパーツ" +DisguisePageMeritAlert = "いよいよ昇格!" +DisguisePageCogLevel = "レベル%s" +DisguisePageMeritFull = "満タン" + +# FishPage.py +FishPageTitle = "魚のタンク" +FishPageTitleTank = "魚のタンク" +FishPageTitleCollection = "魚のコレクション" +FishPageTitleTrophy = "トロフィー" +FishPageWeightStr = "重さ: " +FishPageWeightLargeS = "%dパウンド" +FishPageWeightLargeP = "%dパウンド" +FishPageWeightSmallS = "%dオンス" +FishPageWeightSmallP = "%dオンス" +FishPageWeightConversion = 16 +FishPageValueS = "ジェリービーン%d個分" +FishPageValueP = FishPageValueS +FishPageCollectedTotal = "集めた魚: %d / %d種類" +FishPageRodInfo = "%s釣りざお:\n%d~%dパウンドの\n重さまでOK" +FishPageTankTab = "タンク" +FishPageCollectionTab = "アルバム" +FishPageTrophyTab = "トロフィー" + +FishPickerTotalValue = "バケツ:%s / %s匹\nジェリービーン%d個相当" + +UnknownFish = "???" + +FishingRod = "%s釣りざお" +FishingRodNameDict = { + 0 : "小枝の", + 1 : "竹の", + 2 : "木の", + 3 : "鉄の", + 4 : "金の", + } +FishTrophyNameDict = { + 0 : "クマノミ", + 1 : "キンギョ", + 2 : "コイ", + 3 : "トビウオ", + 4 : "サメ", + 5 : "カジキ", + 6 : "シャチ", + } + +# GardenPage.py #localize +GardenPageTitle = "ガーデニング" +GardenPageTitleBasket = "フラワー・バスケット" +GardenPageTitleCollection = "フラワー・アルバム" +GardenPageTitleTrophy = "ガーデニング・トロフィー" +GardenPageTitleSpecials = "スペシャル" +GardenPageBasketTab = "バスケット" +GardenPageCollectionTab = "アルバム" +GardenPageTrophyTab = "トロフィー" +GardenPageSpecialsTab = "スペシャル" +GardenPageCollectedTotal = "あつめた花の種類: %d / %d" +GardenPageValueS = "かち: ジェリービーン%dコ分" +GardenPageValueP = "かち: ジェリービーン%dコ分" +FlowerPickerTotalValue = "バスケット: %s / %s\nかち: ジェリービーン%dコ分" +GardenPageShovelInfo = "ショベル%s: %d / %d\n" +GardenPageWateringCanInfo = "ジョウロ%s: %d / %d" + +# KartPage.py +KartPageTitle = "カート" +KartPageTitleCustomize = "カートカスタマイズ" +KartPageTitleRecords = "個人ベスト" +KartPageTitleTrophy = "レーストロフィー" +KartPageCustomizeTab = "カスタマイズ" +KartPageRecordsTab = "レコード" +KartPageTrophyTab = "トロフィー" +KartPageTrophyDetail = "トロフィー %s : %s" +KartPageTickets = "チケット : " +KartPageConfirmDelete = "アクセサリーをけす?" + +#plural +KartShtikerDelete = "さくじょ" +KartShtikerSelect = "カテゴリーをえらぶ" +KartShtikerNoAccessories = "アクセサリーなし" +KartShtikerBodyColors = "カートの色" +KartShtikerAccColors = "アクセサリーの色" +KartShtikerEngineBlocks = "ボンネットアクセサリー" +KartShtikerSpoilers = "トランクアクセサリー" +KartShtikerFrontWheelWells = "ぜんりんアクセサリー" +KartShtikerBackWheelWells = "こうりんアクセサリー" +KartShtikerRims = "リムアクセサリー" +KartShtikerDecals = "デカールアクセサリー" +#singluar +KartShtikerBodyColor = "カートの色" +KartShtikerAccColor = "アクセサリーの色" +KartShtikerEngineBlock = "ボンネット" +KartShtikerSpoiler = "トランク" +KartShtikerFrontWheelWell = "ぜんりん" +KartShtikerBackWheelWell = "こうりん" +KartShtikerRim = "リム" +KartShtikerDecal = "デカール" + +KartShtikerDefault = "ひょうじゅん %s" +KartShtikerNo = "%s アクセサリーなし" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "選ぶ" +TrackChoiceGuiCancel = "やめる" +TrackChoiceGuiHEAL = 'トゥーンアップ はバトルで他のトゥーンを元気にすることができるよ。' +TrackChoiceGuiTRAP = 'トラップは、おとりと一緒に使われる強力なギャグだよ。' +TrackChoiceGuiLURE = 'コグを気絶かトラップに引き込むため、おとりを使って。' +TrackChoiceGuiSOUND = 'サウンドは、コグすべてに効くけど、そんなに強力じゃないんだ。' +TrackChoiceGuiDROP = "ドロップ・ギャグは、大きなダメージを与えるけど、そんなに正確じゃないんだ。" + +# EmotePage.py +EmotePageTitle = "表現・感情" +EmotePageDance = "次のダンスフォームをつくったよ:" +EmoteJump = "ジャンプする" +EmoteDance = "おどる" +EmoteHappy = "たのしい" +EmoteSad = "かなしい" +EmoteAnnoyed = "いらいらする" +EmoteSleep = "ねむい" + +# TIP Page +TIPPageTitle = "TIP" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nレベル %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "ゲラゲラメーターがいっぱいになるまで、プレイグラウンドから出ることはできないよ!" + +# InventoryNew.py +InventoryTotalGags = "ギャグごうけい\n%d / %d" +InventroyPinkSlips = "カイコツウチ %s 枚" +InventroyPinkSlip = "カイコツウチ1枚" +InventoryDelete = "すてる" +InventoryDone = "もどる" +InventoryDeleteHelp = "すてるギャグをクリックしてね。" +InventorySkillCredit = "スキルポイント: %s" +InventorySkillCreditNone = "スキルポイント: なし" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "めいちゅうりつ: %(accuracy)s\n%(damageString)s: %(damage)s\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "あと%(nextExp)s!" +InventoryGuestExp = "ゲスト・リミット" +GuestLostExp = "ゲスト・リミットです。" +InventoryAffectsOneCog = "たいしょう:" + Cog +"一体" +InventoryAffectsOneToon = "たいしょう: 仲間一人" +InventoryAffectsAllToons = "たいしょう: 仲間全員" +InventoryAffectsAllCogs = "たいしょう:" + Cogs +"全体" +InventoryHealString = "かいふく" +InventoryDamageString = "ダメージ" +InventoryBattleMenu = "バトルメニュー" +InventoryRun = "にげる" +InventorySOS = "SOS" +InventoryPass = "パス" +InventoryFire = "クビ" +InventoryClickToAttack = "使いたい\nギャグを\nクリック\nしてね!" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "終了する前にトロリーに乗らなきゃ!\n\n\n\n\n\n\nトロリーは、グーフィーのギャグショップのとなりだよ。" +NPCForceAcknowledgeMessage2 = "トロリーをよく見つけられたね!\nトゥーンHQに行ってごほうびをもらってね。\n\n\n\n\n\n\n\nトゥーンHQは、プレイグラウンドのまんなか近くにあるよ。" +NPCForceAcknowledgeMessage3 = "トロリーに乗るのをわすれないでね!\n\n\n\n\nグーフィーのギャグショップのとなりにあるからね!" +NPCForceAcknowledgeMessage4 = "おめでとう!最初のトゥーンタスク完了だよ!\n\n\n\n\n\n\nトゥーンHQに行ってごほうびをもらってね。" +NPCForceAcknowledgeMessage5 = "キミのトゥーンタスクを忘れずに!\n\n\n\n\n\n\n\n\n\n\nコグたちは、トンネルのむこうにもいるよ。" +NPCForceAcknowledgeMessage6 = "よくコグをやっつけたね!\n\n\n\n\n\n\n\n\n早くトゥーン本部に戻ろう!" +NPCForceAcknowledgeMessage7 = "ともだちを作るのを忘れずに!\n\n\n\n\n\n\n他のトゥーンをクリックして、ともだちボタンを押そう!" +NPCForceAcknowledgeMessage8 = "そう、それでOK!新しいともだちを作ったね。\n\n\n\n\n\n\n\n\n今すぐトゥーン本部に戻ろう!" +NPCForceAcknowledgeMessage9 = "電話はそんな感じに使うんだよ。\n\n\n\n\n\n\n\n\nトゥーン本部に戻って、ごほうびをもらおう!" + +# Toon.py +ToonSleepString = "…ぐ~ぐ~…" + +# Movie.py +MovieTutorialReward1 = "「なげる」ポイントを1つゲットしたね!\n10ポイントためると、\n次のレベルのギャグが手に入るよ!" +MovieTutorialReward2 = "「みずでっぽう」ポイントも1つ、ゲットしたね!\nこうやってコグを倒してポイントを貯めてギャグを\nレベルアップしよう!" +MovieTutorialReward3 = "よくやったね!最初のトゥーンタスク完了だよ!" #CC_tom_movie_tutorial_reward01.mp3 +MovieTutorialReward4 = "「トゥーンHQ」に\n行って、ごほうびをもらってね!" #CC_tom_movie_tutorial_reward02.mp3 +MovieTutorialReward5 = "楽しんでね!" #CC_tom_movie_tutorial_reward03.mp3 + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['トゥーンアップ', 'トラップ', 'おとり', 'サウンド', 'なげる', 'みずでっぽう', 'ドロップ'] +BattleGlobalNPCTracks = ['かいふく', 'トゥーン→ヒット', 'コグ→ミス'] +BattleGlobalAvPropStrings = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボール', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア', 'ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット', '10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ', 'まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップのみず', 'みずでっぽう', 'ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ', 'グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvPropStringsSingular = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボールのセット', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア', 'ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット', '10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ', 'まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップのみず', 'みずでっぽう', 'ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ ', 'グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvPropStringsPlural = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボールのセット', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア','ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット','10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ','まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップの水', 'みずでっぽう','ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ','グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvTrackAccStrings = ("ふつう", "100%", "ひくい", "たかい", "ふつう", "たかい", "ひくい") +BattleGlobalLureAccLow = "ひくい" +BattleGlobalLureAccMedium = "ふつう" + +AttackMissed = "しっぱい!" + +NPCCallButtonLabel = "よぶ" + +# ToontownLoader.py +LoaderLabel = "読み込み中…" + +# PlayGame.py +HeadingToHood = "%(hood)s%(to)sへ向かっているよ…" # hood name +HeadingToYourEstate = "キミのおうちに向かっているよ…" +HeadingToEstate = "%sのおうちに向かっているよ…" # avatar name +HeadingToFriend = "%sの友だちの土地に向かっているよ…" # avatar name + +# Hood.py +HeadingToPlayground = "プレイグラウンドに向かっているよ…" +HeadingToStreet = " %(street)s%(to)sへ向かっているよ…" #Street name + +# TownBattle.py +TownBattleRun = "さっきいたプレイグラウンドへ戻る?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "どのトゥーン?" +TownBattleChooseAvatarCogTitle = "どの " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = lBack + +#firecogpanel +FireCogTitle = "かいこ通知の数:%s\nどのコグをクビにする?" +FireCogLowTitle = "かいこ通知の数:%s\nたりないよ!" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "電話する友だちがいないよ!" +TownBattleSOSWhichFriend = "どの友だちに電話する?" +TownBattleSOSNPCFriends = "助けたトゥーンたち" +TownBattleSOSBack = lBack + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "クビ" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "他のプレイヤー\nを待ってます…" +TownSoloBattleWaitTitle = "待っててね…" +TownBattleWaitBack = lBack + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "ドゥードゥルを探しています\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%sは%s " +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "ゲラゲラメーターが笑うまで、トロリーには乗れないんだ。" +TrolleyTFAMessage = Mickey + "がOKを出すまで、トロリーに乗っちゃだめだよ。" +TrolleyHopOff = "おりる" + +# DistributedFishingSpot.py +FishingExit = "終了" +FishingCast = "キャスト" +FishingAutoReel = "オートリール" +FishingItemFound = "釣ったのは…" +FishingCrankTooSlow = "おそ\すぎる!" +FishingCrankTooFast = "はや\nすぎる!" +FishingFailure = "何も釣れなかったよ!" +FishingFailureTooSoon = "食いつきがあるまで、釣り糸を巻いちゃだめだよ。 うきがぴくぴく上下にすばやく動くまで待って!" +FishingFailureTooLate = "魚が食いついている間に、釣り糸を巻くんだよ!" +FishingFailureAutoReel = "今回はオートリールが動かなかったね。釣り上げる一番のタイミングに、ちょうどいい速さで手でクランクを回して!" +FishingFailureTooSlow = "クランクを回すのがおそすぎるよ。他の魚よりもすばしっこい魚もいるからね。スピードバーを中心にしておいてみて!" +FishingFailureTooFast = "クランクを回すのがはやすぎるよ。他の魚よりものろい魚もいるからね。スピードバーを中心にしておいてみて!" +FishingOverTankLimit = "タンクが一杯だよ。\n魚を売ってから\nもう一度きてね!" +FishingBroke = "釣り針につけるものがなくなっちゃったよ! トロリーにのって、ジェリービーンをもっとあつめてきてね!" +FishingHowToFirstTime = "キャストボタンをクリックして、下の方向にドラッグしてね。ドラッグすればするほど、より遠くに投げることができるよ。ターゲットに向けて角度も調節しよう。\n\n今すぐ、試そう!" +FishingHowToFailed = "キャストボタンをクリックして、下の方向にドラッグしてね。ドラッグすればするほど、より遠くに投げることができるよ。ターゲットに向けて角度も調節しよう。\n\nもう一度、試してみよう!" +FishingBootItem = "ボロぐつ" +FishingJellybeanItem = "%s ジェリービーン" +FishingNewEntry = "新種発見!" +FishingNewRecord = "新記録!" + +# FishPoker +FishPokerCashIn = "かけビーン\n%s\n%s" +FishPokerLock = "選ぶ" +FishPokerUnlock = "戻す" +FishPoker5OfKind = "5カード" +FishPoker4OfKind = "4カード" +FishPokerFullHouse = "フルハウス" +FishPoker3OfKind = "3カード" +FishPoker2Pair = "2ペア" +FishPokerPair = "1ペア" + +# DistributedTutorial.py ★台本チェック★ +TutorialGreeting1 = "やあ %s!" +TutorialGreeting2 = "やあ %s!\nこっちにおいでよ!" +TutorialGreeting3 = "やあ %s!\nこっちにおいでよ!\nやじるしキーを使ってね!" +TutorialMickeyWelcome = "トゥーンタウンへようこそ!" +TutorialFlippyIntro = "友だちの" + Flippy + "を紹介するよ。" +TutorialFlippyHi = "やあ、 %s!" +TutorialQT1 = "これを使って話してね。" +TutorialQT2 = "これを使って話せるよ。\nクリックして、\"やあ!\"を選んでね。" +TutorialChat1 = "ボタンのどちらかを使って話してね。" +TutorialChat2 = "あおいボタンは、キーボードを使ったチャット用だよ。" +TutorialChat3 = "気をつけて! キーボードを使ってる時、他のほとんどのプレイヤーは、キミの言ってることがわからないよ。" +TutorialChat4 = "みどりのボタンは、%sをひらくよ。" +TutorialChat5 = "%sを使えば、みんながキミのことわかってくれるようになるよ。" +TutorialChat6 = "\"やあ!\"って言ってみてごらん!" +TutorialBodyClick1 = "じょうずにできたね!" +TutorialBodyClick2 = "よろしくね!ねえ、ともだちにならない?" +TutorialBodyClick3 = Flippy + "と友だちになるには、その子をクリックしてね。" +TutorialHandleBodyClickSuccess = "いいカンジだね!" +TutorialHandleBodyClickFail = "まだまだだね。" + Flippy + "の真上をクリックして…" +TutorialFriendsButton = "右かどの"+ Flippy + "の下の'ともだち' ボタンをクリックして…" +TutorialHandleFriendsButton = "そして'はい' のボタンを押してね。" +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = Flippy + "とともだちになりたい?" +TutorialFriendsPanelMickeyChat = Flippy + " は、キミのともだちになりたいって。'OK' をクリックして終了してね。" +TutorialFriendsPanelYes = Flippy + "は、いいよって言ってるよ!" +TutorialFriendsPanelNo = "あんまり感じよくないね!" +TutorialFriendsPanelCongrats = "おめでとう!最初のともだちができたよ!" +TutorialFlippyChat1 = "最初のトゥーンタスクの準備ができたら、会いにきてね!" +TutorialFlippyChat2 = "タウンホールにいるね!" +TutorialAllFriendsButton = "ともだちボタンをクリックすると、キミの友だち全員をみることができるよ。やってみて…" +TutorialEmptyFriendsList = Flippy + " は実際のプレイヤーじゃないから、キミのリストは今からっぽだよ。" +TutorialCloseFriendsList = "リストを消すには、\n'閉じる'\nボタン をクリックしてね。" +TutorialShtickerButton = "下の右のかどのボタンは、トゥーンガイドを開くよ。 やってみて…" +TutorialBook1 = "このトゥーンガイドには、トゥーンタウンの地図のようにとっても役立つ情報がたくさん入っているんだ" +TutorialBook2 = "キミのトゥーンタスクの進歩もチェックできるよ。" +TutorialBook3 = "使い終わったら、とらの巻ボタンをもう一度クリックして、閉じておいてね。" +TutorialLaffMeter1 = "これも必要だよ…" +TutorialLaffMeter2 = "これも必要だよ…\nキミのゲラゲラメーター!" +TutorialLaffMeter3 = Cogs + "がキミを攻撃すると、ポイントが低くなっちゃうんだ" +TutorialLaffMeter4 = "こんな具合にプレイグラウンドにいると、ポイントが回復するよ。" +TutorialLaffMeter5 = "トゥーンタスクが終わったら、キミのゲラゲラリミットが上がったりするごほうびがもらえるよ。" +TutorialLaffMeter6 = "気をつけて! もし" + Cogs + "にたおされたら、そいつにキミの持ってるギャグが全部持っていかれちゃうんだ!" +TutorialLaffMeter7 = "トロリーゲームをして、ギャグをもっとゲットしよう!" +TutorialTrolley1 = "トロリーに行くから、ついてきて!" +TutorialTrolley2 = "乗るよ!" +TutorialBye1 = "ゲームをしよう!" +TutorialBye2 = "ゲームをしよう!\nギャグを買おう!" +TutorialBye3 = "終わったら、" + Flippy + " に会いにいこう!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "行き先がちがうよ! " + Mickey + "をさがしにいって!" + +PetTutorialTitle1 = "ドゥードゥル パネル" +PetTutorialTitle2 = "ドゥードゥル スピードチャット" +PetTutorialTitle3 = "ドゥードゥル カタログ" +PetTutorialNext = "次のページ" +PetTutorialPrev = "前のページ" +PetTutorialDone = "OK" +PetTutorialPage1 = "ドゥードゥルをクリックすると、ドゥードゥルパネルが表示されるよ。 エサをあげたり、なでたり、呼び出すことができるんだ。" +PetTutorialPage2 = "ドゥードゥルに「トリック」をさせたければ、スピードチャットの「ペット」の項目を使ってね。 「トリック」をしたら、ちゃんとごほうびをあげればごきげんになるよ。" +PetTutorialPage3 = "クララベルのショッピングカタログからドゥードゥルの新しい「トリック」を買ってね。 より良い「トリック」はより多くのトゥーンアップができるよ。" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ALeft + +GardenTutorialTitle1 = "ガーデニング" #localize +GardenTutorialTitle2 = "花" +GardenTutorialTitle3 = "木" +GardenTutorialTitle4 = "育て方" +GardenTutorialTitle5 = "ステータス" +GardenTutorialNext = "次ページ" +GardenTutorialPrev = "前ページ" +GardenTutorialDone = "わかった" +GardenTutorialPage1 = "キミのおうちをガーデニングでトゥーンアップ!お花や木を育ててデコレーションして、強力なギャグをしゅうかくしよう!" +GardenTutorialPage2 = "花の育ち方はジェリービーンのびみょうなまぜ方で決まるよ。うまく育ったらキミの庭にある手押し車で売りに行こう。続けるといいことがあるよ!" +GardenTutorialPage3 = "キミのもっているギャグを使って木をうえよう。何日かたつと、そのギャグが強力になってるよ!でも、その木の世話をしないとギャグはまたもとにもどっちゃうよ。" +GardenTutorialPage4 = "キミのおうちのまわりで花や木を育ててしゅうかくしてね。" +GardenTutorialPage5 = "花のぞうは、クララベルのカタログで買えるよ。スキルをあげて、もっとステキな花のぞうを手に入れよう!" + +# Playground.py +PlaygroundDeathAckMessage = "あーあ、 " + Cogs + "がキミの持ってたギャグを取ってっちゃったよ!\nゲラゲラメーターが笑うまではプレイグラウンドから出られないよ。" + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "工場長が倒されたので、コグパーツを手に入れることができませんでした。" + +# MintInterior +ForcedLeaveMintAckMsg = "金庫番が倒されたので、コグドルを手に入れることができませんでした。" #▲要チェック▲ + +# DistributedFactory.py +HeadingToFactoryTitle = "%sへ向かっているよ…" +ForemanConfrontedMsg = "%sは今、工場長と戦っているよ!" + +# DistributedMint.py +MintBossConfrontedMsg = "%sは今、金庫番と戦っているよ!" + +# DistributedStage.py #localize +StageBossConfrontedMsg = "%sがクラークとバトル中!" +stageToonEnterElevator = "%s \nがエレベーターにのったよ。" +ForcedLeaveStageAckMsg = "ロウクラークはキミがたどりつく前にたおされました。ショーカンジョーを取りもどせませんでした。" + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "他のプレイヤーを待っています…" +MinigamePleaseWait = "…" +DefaultMinigameTitle = "ミニゲームのタイトル" +DefaultMinigameInstructions = "ミニゲームの説明" +HeadingToMinigameTitle = "%sをしよう!" # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "パワーメーター" +MinigamePowerMeterTooSlow = "おそ\nすぎる" +MinigamePowerMeterTooFast = "はや\nすぎる" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "ミニゲームのテンプレート" +MinigameTemplateInstructions = "ミニゲームのテンプレートだよ。これを使って新しいミニゲームを作成してね。" + +# DistributedCannonGame.py +CannonGameTitle = "キャノンゲーム" +CannonGameInstructions = "キミのトゥーンを発射して、できるだけ早く給水塔に入れてあげてね。マウスかやじるしキーを使って目標をさだめられるよ。早く入れれば、みんなが大きなごほうびをもらえるからがんばろう!" +CannonGameReward = "ごほうび" + +# DistributedTwoDGame.py +TwoDGameTitle = "トゥーン・エスケープ" +TwoDGameInstructions = "いそいでコグの隠れ家から逃げるんだ!矢印キーで移動とジャンプ、Ctrlキーでみずでっぽうを発射できるよ。コグ・コインを集めるとボーナスポイントがつくよ。" +TwoDGameElevatorExit = "出口" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "つなひきゲーム" +TugOfWarInstructions = "右と左のやじるしキーをちょうどいい速さで交互にたたいて、あかい線といっしょにみどりのバーをおいてね。たたく速さがおそすぎたり、はやすぎたりすると水のなかに落っこちて終わっちゃうよ!" +TugOfWarGameGo = "スタート!" +TugOfWarGameReady = "よおい…" +TugOfWarGameEnd = "がんばったね!" +TugOfWarGameTie = "ひきわけ!" +TugOfWarPowerMeter = "パワーメーター" + +# DistributedPatternGame.py +PatternGameTitle = Minnie + "のダンスゲーム" +PatternGameInstructions = Minnie + " が、ダンスをみせてくれるよ。" + \ + "やじるしキーを使って、" + Minnie + "のダンスをいま見たようにやってみてね!" +PatternGameWatch = "ダンスステップをみて…" +PatternGameGo = "スタート!" +PatternGameRight = "じょうずね、%s!" +PatternGameWrong = "あ~あ!" +PatternGamePerfect = "かんぺきだったわ、%s!" +PatternGameBye = "ごくろうさま!" +PatternGameWaitingOtherPlayers = "他のプレイヤーを待っててね…" +PatternGamePleaseWait = "ちょっと待っててね…" +PatternGameFaster = "はやすぎちゃった!" +PatternGameFastest = "キミが\n一番はやかったわよ!" +PatternGameYouCanDoIt = "さぁ!\nキミならできるわよ!" +PatternGameOtherFaster = "\nがはやかったわね!" +PatternGameOtherFastest = "\nが一番はやかったよ!" +PatternGameGreatJob = "よくやったわ!" +PatternGameRound = "ラウンド%s!" +PatternGameImprov = "すばらしかったわ!次もがんばってね!" + +# DistributedRaceGame.py +RaceGameTitle = "きょうそうゲーム" +RaceGameInstructions = "数字をじょうずにえらんでクリックしてね。他にだれもえらんでない数字を選ばないと、先にいけないよ!" +RaceGameWaitingChoices = "他のプレイヤーを待っててね…" +RaceGameCardText = "%(name)sがゲットしたのは: %(reward)s" +RaceGameCardTextBeans = "%(name)s がもらえるごほうび: %(reward)s" +RaceGameCardTextHi1 = "すっごいね、%(name)s!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = "1マスすすむ" +RaceGameForwardTwoSpaces = "2マスすすむ" +RaceGameForwardThreeSpaces = "3マスすすむ" +RaceGameBackOneSpace = "1マスもどる" +RaceGameBackTwoSpaces = "2マスもどる" +RaceGameBackThreeSpaces = "3マスもどる" +RaceGameOthersForwardThree = " 他のひとたちは全員\n3マスすすめているよ。" +RaceGameOthersBackThree = "他のひとたちは全員\n3マス戻っているよ。" +RaceGameInstantWinner = "一気に勝ちをきめたー!" +RaceGameJellybeans2 = "ジェリービーン2コ" +RaceGameJellybeans4 = "ジェリービーン4コ" +RaceGameJellybeans10 = "ジェリービーン10コ!" + +# DistributedRingGame.py +RingGameTitle = "リングゲーム" +# color +RingGameInstructionsSinglePlayer = "できるだけ多くの%sうきわをくぐって泳いでみて! やじるしキーを使って泳いでね。" +# color +RingGameInstructionsMultiPlayer = " %sうきわをくぐって泳いでみて。ほかのプレイヤーはちがう色のうきわに挑戦するよ。 やじるしキーを使って泳いでね。" +RingGameMissed = "失敗" +RingGameGroupPerfect = "パーフェクト\nチームワーク!" +RingGamePerfect = "パーフェクト!" +RingGameGroupBonus = "グループボーナス" + +# RingGameGlobals.py +ColorRed = "あかい" +ColorGreen = "みどり色の" +ColorOrange = "オレンジ色の" +ColorPurple = "むらさき色の" +ColorWhite = "しろい" +ColorBlack = "くろい" +ColorYellow = "きいろの" + +# DistributedDivingGame.py #localize +DivingGameTitle = "ダイビングゲーム" +# color +DivingInstructionsSinglePlayer = "たからものはみずうみのそこにあるよ。矢印キーを使っておよいでね。サカナをさけながら、ボートまでたからをはこぼう!" +# color +DivingInstructionsMultiPlayer = "たからものはみずうみのそこにあるよ。矢印キーを使っておよいでね。みんなで力をあわせてボートまでたからものをはこぼう!" +DivingGameTreasuresRetrieved = "たからの数" + +#Distributed Target Game +TargetGameTitle = "スリング・ショット" +TargetGameInstructionsSinglePlayer = "発射の方向とスピードがポイントだよ。" +TargetGameInstructionsMultiPlayer = "まとの中心を目指して着地しよう!" +TargetGameBoard = "ラウンド%s - がんばってね!" +TargetGameCountdown = "%s秒でじどう的にはっしゃ!" +TargetGameCountHelp = "左右の矢印キーをこうごに連打してパワーを調節。やめると発射だよ。" +TargetGameFlyHelp = "下向き矢印でカサを開いてね。" +TargetGameFallHelp = "ターゲットには矢印キーでコントロールしながら着地しよう!" +TargetGameBounceHelp = "地面ではねるとターゲットからはずれちゃうかも。" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nキミ: %s" +PhotoGameScoreBlank = "スコア: %s" +PhotoGameScoreOther = "\n%s"#"スコア: %s\n%s" +PhotoGameScoreYou = "\nベスト・ボーナス!"#"スコア: %s\nベスト・ボーナス!" + + +# DistributedTagGame.py +TagGameTitle = "おにごっこゲーム" +TagGameInstructions = "できるだけ多くのアイスクリームを取ろう! オニになるとアイスは取れなくなるからね!" +TagGameYouAreIt = "キミがオニだよ!" +TagGameSomeoneElseIsIt = "%s がオニだよ!" + +# DistributedMazeGame.py +MazeGameTitle = "メイズゲーム" +MazeGameInstructions = "ミッキーマークをできるだけあつめよう!\nでも、" + Cogs + "には気をつけてね!" + +# DistributedCatchGame.py +CatchGameTitle = "キャッチゲーム" +CatchGameInstructions = "できるだけ多くの%(fruit)sを" + Cogs + "に気をつけながらキャッチして。 %(badThing)sはキャッチしないようにね!" +CatchGamePerfect = "パーフェクト!" +CatchGameApples = 'りんご' +CatchGameOranges = 'オレンジ' +CatchGamePears = 'ようナシ' +CatchGameCoconuts = 'ココナッツ' +CatchGameWatermelons = 'すいか' +CatchGamePineapples = 'バイナップル' +CatchGameAnvils = 'かなとこ' + +# DistributedPieTossGame.py +PieTossGameTitle = "パイ投げゲーム" +PieTossGameInstructions = "ターゲットにパイを投げよう!" + +# DistributedPhotoGame.py +PhotoGameInstructions = "下のトゥーン達を撮ろう!マウスでフレームを動かして左クリックで撮影だよ。Ctrlキーでズーム調整して、矢印キーで周りをみわたせるよ。☆の数が多い程高いポイントをゲット!" +PhotoGameTitle = "パパラトゥーン!" +PhotoGameFilm = "フィルム" +PhotoGameScore = "チームスコア: %s\n\nベストフォト: %s\n\nトータルスコア: %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + "バレル・スティール" +CogThiefGameInstructions = "コグ達からギャグ・バレルを守れ!矢印キーでトゥーンを操作して、Ctrlキーでパイを投げつけよう。ななめにも進めるよ!" +CogThiefBarrelsSaved = "%(num)d個のバレルを\n守った!" +CogThiefBarrelSaved = "%(num)d個のバレルを\n守った!" +CogThiefNoBarrelsSaved = "ぜんぶ\n盗まれちゃった…" +CogThiefPerfect = "パーフェクト!" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "プレイ" + +# Purchase.py +GagShopName = "グーフィーの ギャグショップ" +GagShopPlayAgain = "もういちど\nプレイする" +GagShopBackToPlayground = "プレイグラウ\nンドにもどる" +GagShopYouHave = "使えるジェリービーンは%sコだよ。" +GagShopYouHaveOne = "使えるジェリービーンは1コだよ。" +GagShopTooManyProps = "もうこれ以上もてないよ。" +GagShopDoneShopping = "ショッピング\n終了" +# name of a gag +GagShopTooManyOfThatGag = "すでに%sをたくさん持ってるよ。" +GagShopInsufficientSkill = "まだ使えないよ。" +# name of a gag +GagShopYouPurchased = "%sをゲット!" +GagShopOutOfJellybeans = "ジェリービーンがないよ!" +GagShopWaitingOtherPlayers = "他のプレイヤーを待っててね…" +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%sは、接続を切りました。" +GagShopPlayerExited = "%s は、店をでたよ。" +GagShopPlayerPlayAgain = "もう1回!" +GagShopPlayerBuying = "お買い物中" + +# MakeAToon.py +GenderShopQuestionMickey = "男の子のトゥーンをつくるには、\nぼくをおしてね!" #CC_mickey_create01.mp3 +GenderShopQuestionMinnie = "女の子のトゥーンをつくるには、わたしをおしてね!" #CC_minnie_create01.mp3 +GenderShopFollow = "ついてきて!" #CC_mickey_create02.mp3 (if Mickey) +GenderShopSeeYou = "またね!"#CC_mickey_create03.mp3 (if Mickey) +GenderShopBoyButtonText = "男の子" +GenderShopGirlButtonText = "女の子" + +# BodyShop.py +BodyShopHead = "あたま" +BodyShopBody = "おなか" +BodyShopLegs = "あし" + +# ColorShop.py +ColorShopHead = "あたま" +ColorShopBody = "おなか" +ColorShopLegs = "あし" +ColorShopToon = "いろ" +ColorShopParts = "パーツ" +ColorShopAll = "すべて" + +# ClothesShop.py +ClothesShopShorts = "ズボン" +ClothesShopShirt = "シャツ" +ClothesShopBottoms = "ボトム" + +# MakeAToon +PromptTutorial = "おめでとう!\nキミはトゥーンタウンで一番新しい住民だよ!\n\nこのままトゥーントリアルに進む?それともトゥーンタウンセントラルにワープする?" +MakeAToonSkipTutorial = "トゥーントリアルをやめる" +MakeAToonEnterTutorial = "トゥーントリアルに進む" +MakeAToonDone = "けってい" +MakeAToonCancel = "とりけす" +MakeAToonNext = "つぎへ" +MakeAToonLast = "もどる" +CreateYourToon = "左右のやじるしをクリックしてパーツを選んでね。" +CreateYourToonTitle = "トゥーンをつくる" +ShapeYourToonTitle = "しゅるいをえらんでね" +PaintYourToonTitle = "どんないろがいい?" +PickClothesTitle = "ようふくをえらぼう" +NameToonTitle = "なまえをきめよう" +CreateYourToonHead = "他のトゥーンを選ぶには、'あたま' のやじるしをクリックしてね。" +MakeAToonClickForNextScreen = "次のステップに進むには、右下のやじるしをクリックしてね。" +PickClothes = "やじるしをクリックして、ようふくを選んでね。" +PaintYourToon = "やじるしをクリックして、トゥーンに色をつけてね。" +MakeAToonYouCanGoBack = "前の画面に戻ってパーツを選び直すこともできるよ!" +MakeAFunnyName = "キミのトゥーン\nにおもしろい名\n前をつけよう!" +MustHaveAFirstOrLast1 = "トゥーンにはなまえかみょうじが必要だとおもわない?" +MustHaveAFirstOrLast2 = "キミのトゥーンになまえかみょうじほしくないの?" +ApprovalForName1 = "そうだよ、キミのトゥーンはすごくいいなまえをもつ価値があるよ!" +ApprovalForName2 = "トゥーンのなまえは、いいものしかそろえてないよ!" +MakeAToonLastStep = "トゥーンタウンへ行く前の最後のステップだからね!" +PickANameYouLike = "すきななまえを選んでね!" +TitleCheckBox = "かたがき" +FirstCheckBox = "なまえ" +LastCheckBox = "みょうじ" +RandomButton = "ランダム" +ShuffleButton = "シャッフル" +NameShopSubmitButton = "名前を申込む" +TypeANameButton = "なまえを入力" +TypeAName = "ここにあるなまえはすきじゃない?\nここをクリックして -->" +PickAName = "なまえをえらぼうゲームをやってみて!\nここをクリックして -->" +PickANameButton = "なまえをえらぶ" +RejectNameText = "このなまえは使えないよ。もういちどトライしてね。" +WaitingForNameSubmission = "名前を登録します..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_japanese.txt" +PetshopUnknownName = "名前: ???" +PetshopDescGender = "せいべつ:\t%s" +PetshopDescCost = "かかく:\t%sジェリービーン" +PetshopDescTrait = "とくせい:\t%s" +PetshopDescStandard = "スタンダード" +PetshopCancel = lCancel +PetshopSell = "魚を売る" +PetshopAdoptAPet = "ドゥードゥルを飼う" +PetshopReturnPet = "ドゥードゥルを返す" +PetshopAdoptConfirm = "%sを飼う(%dジェリービーン)" +PetshopGoBack = lBack +PetshopAdopt = "飼う" +PetshopReturnConfirm = "%sを返しますか?" +PetshopReturn = "返す" +PetshopChooserTitle = "今日のドゥードゥル" +PetshopGoHomeText = 'おうちに戻って、新しいドゥードゥルと遊びに行きたいですか?' + +# NameShop.py +NameShopNameMaster = "NameMaster_japanese.txt" +NameShopPay = "今すぐお申しこみを!" +NameShopPlay = "登録無料" +NameShopOnlyPaid = "フルアクセスメンバーだけが、\nトゥーンのなまえを変えることができるんだ。\nキミが申しこみするまでの\nトゥーンのなまえは\nだよ。" +NameShopContinueSubmission = "なまえをとどける" +NameShopChooseAnother = "なまえをつける" +NameShopToonCouncil = "キミのなまえが\n使えるかどうか調べるんだ\n" + \ + "調べるのには数日かかるよ。\nそれまでのなまえは:\n" +PleaseTypeName = "トゥーンになまえをつけてあげてね:" +AllNewNames = "全ての新しいなまえは、\nトゥーン評議会のOKが\n必要なんだよ。" +NameMessages = "" +NameShopNameRejected = "申込んだ\nなまえは\nだめだって。" +NameShopNameAccepted = "おめでとう!\n申込んだ\nなまえが\n使えるよ。" +NoPunctuation = "なまえに句読点や記号(。、・等)は使えないよ!" +PeriodOnlyAfterLetter = "なまえでは、文字のあと以外、ピリオド(.)は使えないよ。" +ApostropheOnlyAfterLetter = "なまえでは、文字のあと以外、アポストロフィー( ' )は使えないよ。" +NoNumbersInTheMiddle = "言葉の間に数字があるのはだめだよ。" +ThreeWordsOrLess = "キミのなまえは3つの言葉かそれ以下じゃないとだめだよ。" +CopyrightedNames = ( + "ミッキー", + "ミッキー・マウス", + "ミッキーマウス", + "ミニー", + "ミニー・マウス", + "ミニーマウス", + "ドナルド", + "ドナルド・ダッグ", + "ドナルドダッグ", + "プルート", + "グーフィー", + ) +NumToColor = ['ホワイト', 'ピーチ', 'ブライトレッド', 'レッド', 'マルーン', + 'シエンナ', 'ブラウン', 'タン', 'コーラル', 'オレンジ', + 'イエロー', 'クリーム', 'シトリーン', 'ライム', 'シーグリーン', + 'グリーン', 'ライトブルー', 'アクア', 'ブルー', + 'ペリウィンクル', 'ロイヤルブルー', 'スレートブルー', 'パープル', + 'ラベンダー', 'ピンク'] +AnimalToSpecies = { + 'dog': 'イヌ', + 'cat' : 'ネコ', + 'mouse' : 'ネズミ', + 'horse' : 'ウマ', + 'rabbit' : 'ウサギ', + 'duck' : 'アヒル', + 'monkey' : 'サル', + 'bear' : 'クマ', + 'pig' : 'ブタ' + } +NameTooLong = "なまえは全角8文字までだよ。もう一度入力してね。" +ToonAlreadyExists = "もうトゥーン名%sができてるよ!" +NameAlreadyInUse = "そのなまえはもう使われているよ!" +EmptyNameError = "なまえを先に入力してね。" +NameError = "ごめん、そのなまえじゃだめみたい" + +# NameCheck.py +NCTooShort = 'なまえは全角2文字以上にしてね' +NCNoDigits = 'なまえには数字をいれないでね。' +NCNeedLetters = 'なまえのそれぞれの言葉には、文字をいれてね。' +NCNeedVowels = 'なまえのそれぞれの言葉には、母音をいれてね。' +NCAllCaps = 'なまえは全部大文字にしないでね。' +NCMixedCase = 'なまえに大文字がおおすぎるね。' +NCBadCharacter = "なまえには'%s'をいれないでね。\n\n漢字・記号・ABC…は使わないでね。" +NCGeneric = 'ごめん、このなまえじゃだめみたい。\n\nなまえは全角8文字以下にしてね。' +NCTooManyWords = '半角スペースは三つまでにしてね。' +NCDashUsage = ("なまえにハイフン(-)は使えないよ。" + "") +NCCommaEdge = "なまえにコンマ(,)は使えないよ。" +NCCommaAfterWord = "なまえにコンマ(,)は使えないよ。" +NCCommaUsage = ('なまえにコンマ(,)は使えないよ。' + '' + '') +NCPeriodUsage = ('なまえにピリオド(.)は使えないよ。' + '') +NCApostrophes = "なまえにアポストロフィー(')は使えないよ。" + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+":キミが救った建物のひとつを" + Cogs + " にのっとられた!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'もっと考える時間がほしい?' +STOREOWNER_GOODBYE = 'またね!' +STOREOWNER_NEEDJELLYBEANS = 'ジェリービーンをとりにいくには、トロリーに乗らなきゃね。' +STOREOWNER_GREETING = '買いたいものを選んでね。' +STOREOWNER_BROWSING = 'ウィンドウショッピングもできるけど、ようふくを買うにはようふく券が必要だよ。' +STOREOWNER_NOCLOTHINGTICKET = 'ようふくを買うにはようふく券が必要だよ。' + +STOREOWNER_NOFISH = 'ここに戻って、釣った魚をジェリービーンと交換しよう!' +STOREOWNER_THANKSFISH = 'ありがとう!ペットショップがきっとよろこんでくれるよ。バイバイ!' +STOREOWNER_THANKSFISH_PETSHOP = "おっ、いい種類の魚がいるね。ありがとう!" +STOREOWNER_PETRETURNED = "安心して、僕たちがキミのドゥードゥルのためのおうちを探してあげるから!" +STOREOWNER_PETADOPTED = "新しいドゥードゥル、おめでとう! キミのおうちで一緒にあそべるよ。" +STOREOWNER_PETCANCELED = "もしお気に入りのドゥードゥルを見つけたら、ほかのトゥーンが飼う前にキミが飼おう!" + +STOREOWNER_NOROOM = "うーん…あたらしいようふくを買うまえに、キミはクローゼットの中を整理したほうがいいね。" +STOREOWNER_CONFIRM_LOSS = "キミのクローゼットはいっぱいだ!前にキミが着ていたようふくはなくなるよ。" +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "ワオ!キミは%s匹の魚(%s匹中)を釣ったね。ごほうびにトロフィーとゲラゲラブーストをあげよう!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": コグの侵略がはじまった!!!" +SuitInvasionBegin2 = lToonHQ+": %s にトゥーンタウンをのっとられた!!!" +SuitInvasionEnd1 = lToonHQ+": %s の侵略はおわった!!!" +SuitInvasionEnd2 = lToonHQ+": トゥーンがまた今日という日を救った!!!" +SuitInvasionUpdate1 = lToonHQ+": コグの侵略はいま%sコグになっている!!!" +SuitInvasionUpdate2 = lToonHQ+": %sを倒さねば!!!" +SuitInvasionBulletin1 = lToonHQ+": コグの侵略が進行中!!!" +SuitInvasionBulletin2 = lToonHQ+": %s にトゥーンタウンをのっとられた!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "トゥーン・プラトゥーン" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "こんにちは、トム!\nトゥーンタウンの新しい住人になにか面白いギャグ、持ってない?" #CC_mickey_tutorial02.mp3 ***DELETED "CC_mickey_tutorial01.mp3"*** +QuestScriptTutorialMickey_2 = "もちろん、%s!" #CC_tom_tutorial_mickey01.mp3 +QuestScriptTutorialMickey_3 = "彼がコグについて\nいろいろ教えてくれるって!\aそれじゃあ、\nまた後でね~!" #CC_mickey_tutorial03.mp3 \a CC_mickey_tutorial05.mp3 ***DELETED "CC_mickey_tutorial04.mp3"*** +QuestScriptTutorialMickey_4 = "やじるしキーを使ってこっちにおいで!" #CC_tom_tutorial_mickey02.mp3 + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "こんにちは、トム!\nトゥーンタウンの新しい住人になにか面白いギャグ、持ってない?" #CC_minnie_tutorial02.mp3 ***DELETED "CC_minnie_tutorial01.mp3"*** +QuestScriptTutorialMinnie_2 = "もちろん、%s!" #CC_tom_tutorial_minnie01.mp3 +QuestScriptTutorialMinnie_3 = "彼がコグについて\nいろいろ教えてくれるのよ!\aそれじゃあ、\nまたね~!" #CC_minnie_tutorial03.mp3 \a CC_minnie_tutorial05.mp3 *** DELETED "CC_minnie_tutorial04.mp3"*** + +QuestScript101_1 = "これらが「コグ」って言うんだ!\nトゥーンタウンをのっとろうとしているロボットたちなんだ。" #Please play "CC_tom_tutorial_questscript01.mp3" only / "CC_tom_tutorial_questscript02.mp3" is included. +QuestScript101_2 = "たくさんの種類のコグがいるんだけど…" #CC_tom_tutorial_questscript03.mp3 +QuestScript101_3 = "…ハッピーな\nトゥーンビルをね…" #CC_tom_tutorial_questscript041.mp3 +QuestScript101_4 = "…みにくいコグのビルにしてしまうんだ!" #CC_tom_tutorial_questscript05.mp3 +QuestScript101_5 = "でも頭のかったーいコグはギャグをまったく理解することができないんだ!" #CC_tom_tutorial_questscript06.mp3 +QuestScript101_6 = "だからトゥーンのおもしろいギャグで、 コグの動きを止めることができるんだよ。" #CC_tom_tutorial_questscript07.mp3 +QuestScript101_7 = "たくさんのギャグがあるけど、まずはこれとこれかな…" #CC_tom_tutorial_questscript08.mp3 +QuestScript101_8 = "そうだ、ゲラゲラメーターも必要だね!" #CC_tom_tutorial_questscript09.mp3 +QuestScript101_9 = "ゲラゲラメーターが低すぎると、かなしくなって落ち込んじゃうんだよ。" #CC_tom_tutorial_questscript10.mp3 +QuestScript101_10 = "つまりハッピーだと、トゥーンは健康ってことなんだ!" #CC_tom_tutorial_questscript11.mp3 +QuestScript101_11 = "あーっ! ぼくの店の外にコグがいる!" #CC_tom_tutorial_questscript12.mp3 +QuestScript101_12 = "たすけて、おねがい! コグをやっつけて!" #CC_tom_tutorial_questscript13.mp3 +QuestScript101_13 = "キミに最初のトゥーンタスクをあげるね!\n " #CC_tom_tutorial_questscript14.mp3 ***DELETED "CC_tom_tutorial_questscript15.mp3"*** +QuestScript101_14 = "外にいるオベッカーを倒そう!いそいで!" #CC_tom_tutorial_questscript16.mp3 + +QuestScript110_1 = "よくひとりでオベッカーを倒したね。 それじゃあ、ごほうびにトゥーンガイドをあげよう…" #CC_harry_tutorial_questscript01.mp3 +QuestScript110_2 = "トゥーンガイドには、たくさんの情報がはいってるよ。" #CC_harry_tutorial_questscript02.mp3 +QuestScript110_3 = "それを開いてごらん!ぼくがいろいろ教えてあげよう。" #CC_harry_tutorial_questscript03.mp3 +QuestScript110_4 = "地図はキミが行ったところを示してるんだ。" #CC_harry_tutorial_questscript04.mp3 +QuestScript110_5 = "ページをめくるとキミのギャグが…" #CC_harry_tutorial_questscript05.mp3 +QuestScript110_6 = "ん~!ギャグが残ってないね! キミに新しいタスクをあげよう。" #CC_harry_tutorial_questscript06.mp3 +QuestScript110_7 = "キミのやらなきゃいけないタスクは次のページに書いてあるよ。" #CC_harry_tutorial_questscript07.mp3 +QuestScript110_8 = "それじゃあトロリーに乗って、ギャグを買うためのジェリービーンを稼ぎに行こう!" #CC_harry_tutorial_questscript08.mp3 +QuestScript110_9 = "まずはトロリー乗り場に行こう。ぼくの後ろのドアからプレイグラウンドへ出られるよ。" #CC_harry_tutorial_questscript09.mp3 +QuestScript110_10 = "さぁ、トゥーンガイドをとじてトロリーをみつけて!" #CC_harry_tutorial_questscript10.mp3 +QuestScript110_11 = "それがすんだら、トゥーンHQに戻るんだ。 じゃあね!" #CC_harry_tutorial_questscript11.mp3 + +QuestScriptTutorialBlocker_1 = "やぁ、こんにちは!" #CC_flippy_tutorial_blocker01.mp3 +QuestScriptTutorialBlocker_2 = "あのぅ… こんにちは??" #CC_flippy_tutorial_blocker02.mp3 +QuestScriptTutorialBlocker_3 = "あ~、そうか! スピードチャットの使い方がわからないんだね!" #CC_flippy_tutorial_blocker03.mp3 +QuestScriptTutorialBlocker_4 = "そのボタンをクリックして、なにか言ってみて。" #CC_flippy_tutorial_blocker04.mp3 +QuestScriptTutorialBlocker_5 = "その調子!!\aキミがこれから行くところには、話せるトゥーンがたくさんいるからね。" #CC_flippy_tutorial_blocker05.mp3 \a CC_flippy_tutorial_blocker06.mp3 +QuestScriptTutorialBlocker_6 = "キミの友だちとキーボードを使ってチャットしたい場合は、となりの青いボタンをつかうんだよ。" #CC_flippy_tutorial_blocker07.mp3 +QuestScriptTutorialBlocker_7 = "\"チャット\"ボタンっていうんだけどこれを使うには、トゥーンタウンのオフィシャルメンバーになる必要があるんだ。" #CC_flippy_tutorial_blocker08.mp3 +QuestScriptTutorialBlocker_8 = "がんばって! じゃあまたあとでね!" #CC_flippy_tutorial_blocker09.mp3 + +""" +GagShopTut + +違うタイプのギャグを使う能力をかせぐこともできるぞ。 + +""" + +QuestScriptGagShop_1 = "ギャグショップへようこそ!" +QuestScriptGagShop_1a = "ここはトゥーンがコグと戦うためのギャグを買いに来る場所だよ。" +#QuestScriptGagShop_2 = "これはキミが持っているジェリービーンの数。" +#QuestScriptGagShop_3 = "ギャグを買うには、ギャグのボタンをクリックしてね。さっそくやってみて!" +QuestScriptGagShop_3 = "ギャグのボタンをクリックすれば、そのギャグを買えるよ!できるかな?" +QuestScriptGagShop_4 = "いいね!買ったギャグはコグと戦っているときに使えるよ!" +QuestScriptGagShop_5 = "「なげる」と「みずでっぽう」のレベルの高いギャグだよ!" #★ +QuestScriptGagShop_6 = "ギャグの買い物が終わったら、このボタンを押してプレイグラウンドに戻ろう!" +QuestScriptGagShop_7 = "普段ならこのボタンを押すと、もう一度トロリーゲームで楽しめるんだけど…" +QuestScriptGagShop_8 = "…今回は時間がないから、今度ためしてみよう。トゥーンHQに向かってね!" + +QuestScript120_1 = "よくトロリーをみつけたね!\aところで、銀行員のボブにあった?\aきれいな歯をしてるんだよ。\aこのチョコレートをおみやげに、彼に話しかけて自己紹介してみたら?" +QuestScript120_2 = "銀行員のボブは、トゥーンタウンバンクにいるよ。" + +QuestScript121_1 = "おいしいチョコレートをありがとう!\aねぇ、ぼくを助けてくれたらごほうびをあげるよ。\aコグのやつら、ぼくのきんこのかぎを盗んだんだ。\aコグたちを倒して、盗まれたかぎをみつけておくれよ。\aかぎをみつけたら、ぼくに持ってきて!" + +QuestScript130_1 = "よくトロリーをみつけたね!\aところで、今日ピート教授への荷物を受けとったんだ。\a彼が注文した新しいチョークにちがいないとおもうよ。\a彼にそれをとどけてくれないかなぁ?\a彼はスクールハウスにいるよ。" + +QuestScript131_1 = "ああ、チョークありがとう。\aなんだ!?!\aああ、コグたちがわたしの黒板を盗んでいってしまったのか…。コグたちを倒して、盗まれた黒板をみつけてくれないか。\aみつけたら、わたしに持ってきてくれ。" + +QuestScript140_1 = "よくトロリーをみつけたね!\aところで、ぼくにはかなりの読書中毒のとしょかんいんのラリーという友だちがいるんだ。\aこの間、ドナルドのハトバにいるとき、この本をひろったんだ。\a彼にこれを渡してくれないかい?彼はだいたいトゥーンタウンライブラリーにいるよ。" + +QuestScript141_1 = "ありがとう、この本でぼくのコレクションはだいぶそろったな。\aどれどれ…\aんー、あれー…\aめがねどこにおいたかなぁ?\aあのコグたちがぼくんちの建物をのっとる前まではあったんだ。\aコグを倒して、ぼくの盗まれためがねをみつけてくれよ。\aみつけてもってきてくれたら、ごほうびをあげる!" + +QuestScript145_1 = "トロリーは大丈夫だったみたいだね。\aよく聞いて!コグたちに「黒板消し」を盗まれてしまったんだ!\aストリートに出て、コグと戦って黒板消しを取り戻してくれない?\aストリートに行くにはこんな感じのトンネルを通ってね。" +QuestScript145_2 = "もし黒板消しを見つけたら、ここにもってきてくれないかな?\aギャグが必要だったら、トロリーに乗るんだよ。忘れないでね。\aそれと、ゲラゲラメーターをまんたんにしたかったら、プレイグラウンドのアイテムをひろってね!" + +QuestScript150_1 = "あー… つぎのタスクはキミひとりではむずかしすぎるなぁ!" +QuestScript150_2 = "友だちをつくるには、他のプレイヤーをみつけて、あたらしい友だちボタンを使って" +QuestScript150_3 = "友だちをつくったらすぐ、ここに戻ってきてね。" +QuestScript150_4 = "いくつかのタスクは、ひとりでやるのにはむずかしすぎるよ!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +SellbotBossName = "シニア コグゼキュティブ" +CashbotBossName = "マネーマネー" +LawbotBossName = "チーフ ジャスティス" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "キミは「格上げ」した!%s おめでとう!" +BossCogDoobersAway = { 's' : "さあ、急いでウリアゲを上げて!" } +BossCogWelcomeToons = "ようこそ、新しいコグの仲間達!" +BossCogPromoteToons = "キミは「格上げ」した!%s おめでとう!" +CagedToonInterruptBoss = "ねぇねぇ!\nこっちだよ!" +CagedToonRescueQuery = "キミたちは助けに来てくれたの?" +BossCogDiscoverToons = "は? トゥーンめが! ヘンソウしたってお見通しさ!" +BossCogAttackToons = "いざ!" +CagedToonDrop = [ + "やったね!彼を追い詰めたね。", + "彼の後を追いかけて! 逃げようとしているよ!", + "キミたちは本当にすごいね!", + "ファンタスティック! 彼をやっつけたも\n同然だね!", + ] +CagedToonPrepareBattleTwo = "ねぇ見て! 彼が逃げようとしているぞ!\aみんな、助けて!彼を止めて!" +CagedToonPrepareBattleThree = "ふーっ、\nもうすぐ自由だ!\aコグゼキュティブを\n直接、攻撃しよう!\aキミが使えるパイをたくさん手に入れたよ!\aジャンプして、オリの底にさわればキミにパイを渡せるんだ!\aパイを手に入れたら\nInsertキーを押してみよう!\aパイを投げることが出来るよ!" +BossBattleNeedMorePies = "もっとパイが必要だよ!" +BossBattleHowToGetPies = "オリのところまでジャンプして、パイを手に入れよう!" +BossBattleHowToThrowPies = "Insertキーを押すとパイを投げるぞ!" +CagedToonYippee = "いやっほう!" +CagedToonThankYou = "やった~!自由になったぞ!\a本当にありがとう!\a大きな借りができたね。\aもしバトルで助けが必要になったら、電話して!\aSOSボタンを押せば、呼ぶことができるよ!" +CagedToonPromotion = "\aコグゼキュティブがキミの「格上げ」の紙を置き忘れたみたいだよ。\aキミのために取っておくから、後で「格上げ」されるはずだよ!" +CagedToonLastPromotion = "\aワオ!キミのコグスーツ、とうとうレベル%sまできたね。\aそれ以上は格上げされないよ。\aこれ以上はスーツをアップグレードできないけど、もちろんトゥーンを助けに行けるよ。" +CagedToonHPBoost = "\aキミはこの本部からたくさんのトゥーンの仲間達を助けたね!\aトゥーン本部はキミにさらなるゲラゲラブーストをあげることにしたよ!おめでとう!" +CagedToonMaxed = "\aコグのスーツがレベル%sに達したね。本当に素晴らしい!\aトゥーン本部に代わって、もっとトゥーンを助けに戻ってきてくれたことに感謝するよ!" +CagedToonGoodbye = "それでは!" + + +CagedToonBattleThree = { + 10: "いいジャンプだよ、%(toon)s。 パイをどうぞ!", + 11: "やあ、%(toon)s! パイをどうぞ!", + 12: "こんにちは、%(toon)s! パイを手に入れたよ!", + + 20: "ねぇ、%(toon)s! オリのところまでジャンプして、パイを投げて!", + 21: "おーい、%(toon)s! Ctrlキーを使ってジャンプして、オリをさわって!", + + 100: "Insertキーを押すとパイを投げるよ!", + 101: "パイがどのくらい飛ぶかは青いパワーメーターでわかるよ!", + 102: "まず彼の土台に向けてパイを投げて、彼の動きを狂わせよう!", + 103: "ドアが開くのを待って、中にパイを投げ込もう!", + 104: "彼がうろたえているときに、顔をねらうか、おなかをねらってあとずさりさせよう!", + 105: "当たったときの色をみればちゃんとギャグが決まったかがわかるよ!", + 106: "トゥーンにパイがあたると、トゥーンのゲラゲラメーターが回復するよ!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "ええい!しぶといトゥーン達にはもううんざりだ!" +CashbotBossOuttaHere = "電車の時間に間に合わないから帰るぞ!!" +ResistanceToonName = "マタ・ヘアリー" +ResistanceToonCongratulations = "やったね!おめでとう!\aレジスタンスの仲間入りだ!\aピンチの時のあいことばを教えてあげるね。\a%s\a「%s」\a一度しか使えないから、ちゃんとタイミング考えよう!" +ResistanceToonToonupInstructions = "キミの近くのトゥーンみんなが%sゲラゲラポイントをゲット!" +ResistanceToonToonupAllInstructions = "キミの近くのトゥーンみんなのゲラゲラポイントが回復!" +ResistanceToonMoneyInstructions = "キミの近くのトゥーンみんなが%sジェリービーンをゲット!" +ResistanceToonMoneyAllInstructions = "キミの近くのトゥーンみんなのジェリービーンをゲット!" +ResistanceToonRestockInstructions = "キミの近くのトゥーンみんなの\"%s\"ギャグが回復!" +ResistanceToonRestockAllInstructions = "キミの近くのトゥーンみんなのギャグが回復!" + +ResistanceToonLastPromotion = "\aワオ!コグスーツがレベル%sになったね!\aコグは今のレベル以上にはなれないんだ。\aだからコグスーツのレベルも上がらないんだけど、レジスタンスのためにこれからもがんばって!" +ResistanceToonHPBoost = "\aレジスタンスのために本当にがんばってくれているね!\aトゥーンひょうぎかいがキミにゲラゲラポイントをあげるって!おめでとう!" +ResistanceToonMaxed = "\aレベル%sのコグスーツを手に入れたんだね!すばらしいよ!\aトゥーンひょうぎかいにかわって、トゥーン達を助けにもどってくれたことに心からかんしゃします!" + +CashbotBossCogAttack = "つかまえろ!!" +ResistanceToonWelcome = "キミ、やったね!マネーマネーが僕らを見つける前に金庫室まで行こう!" +ResistanceToonTooLate = "ちぇっ、おそすぎた!" +CashbotBossDiscoverToons1 = "クンクン…!" +CashbotBossDiscoverToons2 = "はーはっは!思ったとおりだ!トゥーンのにおいがしたんだよ。にせものめ!" +ResistanceToonKeepHimBusy = "マネーマネーの気をそらして!これからワナをしかけるから!" +ResistanceToonWatchThis = "よーく、見ててね!" +CashbotBossGetAwayFromThat = "ねぇ!それからはなれて!" +ResistanceToonCraneInstructions1 = "台にのぼって、じしゃくをコントロールしよう!" +ResistanceToonCraneInstructions2 = "やじるしキーを使ってクレーンを動かして、Ctrlキーでモノをつかめるよ。" +ResistanceToonCraneInstructions3 = "じしゃくで金庫をつかんで、マネーマネーの安全ヘルメットをはずそう!" +ResistanceToonCraneInstructions4 = "ヘルメットが取れたら、動かなくなったグーンをつかんで頭にあてよう!" +ResistanceToonGetaway = "おーっと!にげろ!" +CashbotCraneLeave = "クレーンからはなれる" +CashbotCraneAdvice = "やじるしキーを使うとクレーンのあたまが動くよ。" +CashbotMagnetAdvice = "CTRLキーを押すとモノをつかめるよ。" +CashbotCraneLeaving = "クレーンからはなれているところ" + +MintElevatorRejectMessage = "キミの%sコグスーツを完成させるまでは、中に入れないよ!" +BossElevatorRejectMessage = "キミのトゥーンが「格上げ」されるまでは、このエレベーターに乗ることはできません。" +NotYetAvailable = "このエレベーターにはまだ乗れないよ" + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "家具" +PaintingTypeName = "絵" +ClothingTypeName = "洋服" +ChatTypeName = "新しいフレーズ" +EmoteTypeName = "演技のレッスン" +BeanTypeName = "ジェリービーン" +PoleTypeName = "釣りざお" +WindowViewTypeName = "窓の景色" +PetTrickTypeName = "ドゥードゥル\nトレーニング" +GardenTypeName = "ガーデンアイテム" +RentalTypeName = "レンタルアイテム" +GardenStarterTypeName = "ガーデニングキット" +NametagTypeName = "ネームタグ" + + +# Make sure numbers match up to CatalogItemTypes.py +CatalogItemTypeNames = { + 0 : "INVALID_ITEM", + 1 : FurnitureTypeName, + 2 : ChatTypeName, + 3 : ClothingTypeName, + 4 : EmoteTypeName, + 5 : "WALLPAPER_ITEM", + 6 : "WindowViewTypeName", + 7 : "FLOORING_ITEM", + 8 : "MOULDING_ITEM", + 9 : "WAINSCOTING_ITEM", + 10 : PoleTypeName, + 11: PetTrickTypeName, + 12: BeanTypeName, + 13: GardenTypeName, + 14: RentalTypeName, + 15: GardenStarterTypeName, + 16: NametagTypeName, + 17: "TOON_STATUE_ITEM", + 18: "ANIMATED_FURNITURE_ITEM", +} + + +# Make sure this is in sync with ToonDNA.ShirtStyles +ShirtStylesDescriptions = { + # ------------------------------------------------------------------------- + # Boy styles + # ------------------------------------------------------------------------- + 'bss1' : "むじ", + 'bss2' : "ストライプ", + 'bss3' : "ポロシャツ", + 'bss4' : "ダブル ストライプ", + 'bss5' : "しまもよう", + 'bss6' : "ポケットポロ", + 'bss7' : "ハワイアン", + 'bss8' : "2 ポケットポロ", + 'bss9' : "ボーリングシャツ", + 'bss10' : "ベスト(スペシャル)", + 'bss11' : "ラッフル ポロ", + 'bss12' : "サッカー ジャージ(スペシャル)", + 'bss13' : "ライトニング(スペシャル)", + 'bss14' : "19ジャージ(スペシャル)", + 'bss15' : "メキシカン", + + # ------------------------------------------------------------------------- + # Girl styles + # ------------------------------------------------------------------------- + 'gss1' : "むじ", + 'gss2' : "ストライプ", + 'gss3' : "ポロシャツ", + 'gss4' : "ダブルストライプ", + 'gss5' : "ポケットポロ", + 'gss6' : "フラワープリント", + 'gss7' : "はながら(スペシャル)", + 'gss8' : "2ポケットポロ", + 'gss9' : "デニムベスト(スペシャル)", + 'gss10' : "チュニック", + 'gss11' : "ストライプ チュニック", + 'gss12' : "サッカージャージ(スペシャル)", + 'gss13' : "ハート", + 'gss14' : "スター(スペシャル)", + 'gss15' : "フラワー", + + # ------------------------------------------------------------------------- + # Special Catalog-only shirts. + # ------------------------------------------------------------------------- + # yellow hooded - Series 1 + 'c_ss1' : "シリーズ1 パーカー", + 'c_ss2' : "シリーズ1 やしの木", + 'c_ss3' : "シリーズ2 スター", + 'c_bss1' : "シリーズ1 ストライプ(男の子)", + 'c_bss2' : "シリーズ1 オレンジ (男の子) ", + 'c_bss3' : "シリーズ2 ストライプ(男の子)", + 'c_bss4' : "シリーズ2 チェック(男の子)", + 'c_gss1' : "シリーズ1 ストライプ(女の子)", + 'c_gss2' : "シリーズ1 フラワー(女の子)", + 'c_gss3' : "シリーズ2 ウェーブ(女の子)", + 'c_gss4' : "シリーズ2 リボン(女の子)", + 'c_gss5' : "アクアストライプ(女の子)", + 'c_ss4' : "シリーズ3 タイダイ", + 'c_ss5' : "シリーズ3 ストライプ(男の子)", + 'c_ss6' : "シリーズ4 カウボーイシャツ1", + 'c_ss7' : "シリーズ4 カウボーイシャツ 2", + 'c_ss8' : "シリーズ4 カウボーイシャツ 3", + 'c_ss9' : "シリーズ4 カウボーイシャツ 4", + 'c_ss10' : "シリーズ4 カウボーイシャツ 5", + 'c_ss11' : "シリーズ 4 カウボーイシャツ 6", + + # Special Holiday-themed shirts. + 'hw_ss1' : "ゴースト", + 'hw_ss2' : "パンプキン", + 'wh_ss1' : "ウィンターホリデー 1", + 'wh_ss2' : "ウィンターホリデー 2", + 'wh_ss3' : "ウィンターホリデー 3", + 'wh_ss4' : "ウィンターホリデー 4", + + 'vd_ss1' : "バレンタインデー、 赤いハート(女の子)", + 'vd_ss2' : "バレンタインデー、 白いハート", + 'vd_ss3' : "バレンタインデー、 はね付きハート(男の子)", + 'vd_ss4' : "バレンタインデー、 炎のハート", + 'vd_ss5' : "バレンタインデー、 キューピッド", + 'vd_ss6' : "バレンタインデー、 緑と赤のハート", + 'sd_ss1' : "セント パトリックデー、 四葉のクローバ", + 'sd_ss2' : "セント パトリックデー、 金のつぼ", + 'tc_ss1' : "T-シャツ コンテスト、 フィッシングベスト", + 'tc_ss2' : "T-シャツ コンテスト、 金魚ばち", + 'tc_ss3' : "T-シャツ コンテスト、 足あと", + 'tc_ss4' : "T-シャツ コンテスト、 バックパック", + 'tc_ss5' : "T-シャツ コンテスト、 短パン", + 'tc_ss6' : "T-シャツ コンテスト、 スイカ", + 'tc_ss7' : "T-シャツ コンテスト、 レース", + 'j4_ss1' : "July 4th, フラッグ", + 'j4_ss2' : "July 4th, 花火", + 'c_ss12' : "カタログシリーズ7, 黄ボタン", + 'c_ss13' : "カタログシリーズ7, ビッグ フラワー", + + 'pj_ss1' : "青 バナナシャツ", + 'pj_ss2' : "赤 ホーンシャツ", + 'pj_ss3' : "紫 メガネシャツ", + + # Special award clothes + 'sa_ss1' : "ストライプ シャツ", + 'sa_ss2' : "フィッシング シャツ1", + 'sa_ss3' : "フィッシング シャツ 2", + 'sa_ss4' : "ガーデニング シャツ 1", + 'sa_ss5' : "ガーデニング シャツ 2", + 'sa_ss6' : "パーティー シャツ 1", + 'sa_ss7' : "パーティー シャツ 2", + 'sa_ss8' : "レーシング シャツ 1", + 'sa_ss9' : "レーシング シャツ 2", + 'sa_ss10' : "サマー シャツ 1", + 'sa_ss11' : "サマー シャツ 2", + + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + } + +# Make sure this is in sync with ToonDNA.BottomStyles +BottomStylesDescriptions = { + # name : [ bottomIdx, [bottomColorIdx, ...]] + # ------------------------------------------------------------------------- + # Boy styles (shorts) + # ------------------------------------------------------------------------- + 'bbs1' : "ポケット付き", + 'bbs2' : "ベルト", + 'bbs3' : "カーゴ", + 'bbs4' : "ハワイアン", + 'bbs5' : "サイド ストライプ (スペシャル)", + 'bbs6' : "サッカー シャツ", + 'bbs7' : "サイド フレイム (スペシャル)", + 'bbs8' : "デニム", + 'vd_bs1' : "バレンタイン ショートパンツ", + 'vd_bs2' : "赤 ハート", + 'vd_bs3' : "デニム 緑・赤ハート", + + # Catalog only shorts + 'c_bs1' : "ブルーのサイド ストライプ", + 'c_bs2' : "ゴールド カフス ストライプ", + 'c_bs5' : 'シリーズ7 - グリーンストライプ', + 'sd_bs1' : 'レプラコーン パンツ', + 'pj_bs1' : 'バナナ パジャマ パンツ', + 'pj_bs2' : 'ホーン パジャマ パンツ', + 'pj_bs3' : 'メガネ パジャマ パンツ', + 'wh_bs1' : 'ウィンターホリデー 短パン1', + 'wh_bs2' : 'ウィンターホリデー 短パン2', + 'wh_bs3' : 'ウィンターホリデー 短パン3', + 'wh_bs4' : 'ウィンターホリデー 短パン4', + + + # ------------------------------------------------------------------------- + # Girl styles (shorts and skirts) + # ------------------------------------------------------------------------- + # skirts + # ------------------------------------------------------------------------- + 'gsk1' : '無地', + 'gsk2' : 'ポルカ ドット(スペシャル)', + 'gsk3' : 'タテシマ', + 'gsk4' : 'ヨコシマ', + 'gsk5' : 'フラワー', + 'gsk6' : '2-ポケット(スペシャル) ', + 'gsk7' : 'デニム スカート', + + # shorts + # ------------------------------------------------------------------------- + 'gsh1' : 'ポケット付', + 'gsh2' : 'フラワー', + 'gsh3' : 'デニム 短パン', + # Special catalog-only skirts and shorts. + 'c_gsk1' : 'ボーダー&ボタン付', + 'c_gsk2' : 'リボン付', + 'c_gsk3' : '星', + + # Valentines skirt + 'vd_gs1' : 'ハート', + 'vd_gs2' : 'ポルカ ハート', + 'vd_gs3' : '緑・赤のハート', + 'c_gsk4' : 'レインボー ? シリーズ3', + 'sd_gs1' : 'St. パトリック 短パン', + 'c_gsk5' : 'ウエスタン スカート1', + 'c_gsk6' : 'ウエスタン スカート2', + # Western shorts + 'c_bs3' : 'ウエスタン 短パン1', + 'c_bs4' : 'ウエスタン 短パン 2', + 'j4_bs1' : 'July 4th 短パン', + 'j4_gs1' : 'July 4th スカート', + 'c_gsk7' : 'フラワー ? シリーズ7', + 'pj_gs1' : 'バナナ パジャマ パンツ', + 'pj_gs2' : 'ホーン パジャマ パンツ', + 'pj_gs3' : 'メガネ パジャマ パンツ', + 'wh_gsk1' : 'ウィンターホリデー スカート1', + 'wh_gsk2' : 'ウィンターホリデー スカート2', + 'wh_gsk3' : 'ウィンターホリデー スカート3', + 'wh_gsk4' : 'ウィンターホリデー スカート4', + + 'sa_bs1' : "フィッシング 短パン", + 'sa_bs2' : "ガーデニング 短パン", + 'sa_bs3' : "パーティー 短パン", + 'sa_bs4' : "レーシング 短パン", + 'sa_bs5' : "サマー 短パン", + 'sa_gs1' : "フィッシング スカート", + 'sa_gs2' : "ガーデニング スカート", + 'sa_gs3' : "パーティースカート", + 'sa_gs4' : "レーシング スカート", + 'sa_gs5' : "サマー スカート", + } + +AwardMgrBoy = "男の子" +AwardMgrGirl = "女の子" +AwardMgrUnisex = "男女" +AwardMgrShorts = "短パン" +AwardMgrSkirt = "スカート" +AwardMgrShirt = "シャツ" + +# Special Event Strings to display in mailbox screen +SpecialEventMailboxStrings = { + 1 : "トゥーン評議会からのスペシャルアイテム", + 2 : "メルビルのフィッシングトーナメントの賞品", + 3 : "ビリー・バドのフィッシングトーナメントの商品", + } + +#rental names +RentalHours = "時間" +RentalOf = "の" +RentalCannon = "レンタルキャノン" +RentalGameTable = "ゲーム・テーブル!" + +EstateCannonGameEnd = "キャノンのレンタルが終わりました。" +GameTableRentalEnd = "ゲーム・テーブルのレンタルが終わりました。" + +MessageConfirmRent = "レンタルする?後でレンタルしたければキャンセルしてね。" +MessageConfirmGarden = "ガーデニングするかい?" + +#nametag Names +NametagPaid = "シチズン・ネームタグ" +NametagAction = "デザイン・ネームタグ" +NametagFrilly = "ポップ・ネームタグ" + +MessageConfirmRent = "レンタルする?後でレンタルしたければキャンセルしてね。" +MessageConfirmGarden = "ガーデニングするかい?" + +FurnitureYourOldCloset = "キミの古いクローゼット" +FurnitureYourOldBank = "キミの古い銀行" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +FurnitureNames = { + 100 : "ひじかけいす", + 105 : "ひじかけいす", + 110 : "いす", + 120 : "デスクチェア", + 130 : "ログチェア", + 140 : "ロブスターチェア", + 145 : "ライフジャケットチェア", + 150 : "サドルスツール", + 160 : "ネイティブチェアー", + 170 : "カップケーキチェアー", + 200 : "ベッド", + 205 : "ベッド", + 210 : "ベッド", + 220 : "バスタブベッド", + 230 : "はっぱのベッド", + 240 : "ボートのベッド", + 250 : "トゲトゲハンモック", + 260 : "アイスクリームベッド", + 270 : "エリンとネコのベッド", + 300 : "プレイヤーピアノ", + 310 : "パイプオルガン", + 400 : "だんろ", + 410 : "だんろ", + 420 : "まるいだんろ", + 430 : "だんろ", + 440 : "りんごのだんろ", + 450 : "エリンのだんろ", + 500 : "クローゼット", + 502 : "クローゼット(15)", + 504 : "クローゼット(20)", + 506 : "クローゼット(25)", + 510 : "クローゼット", + 512 : "クローゼット(15)", + 514 : "クローゼット(20)", + 516 : "クローゼット(25)", + 600 : "小さいスタンド", + 610 : "大きいスタンド", + 620 : "テーブルライト", + 625 : "テーブルライト", + 630 : "ひなぎくのランプ", + 640 : "ひなぎくのランプ", + 650 : "くらげのランプ", + 660 : "くらげのランプ", + 670 : "カウボーイランプ", + 700 : "ふかふかのいす", + 705 : "ふかふかのいす", + 710 : "ソファ", + 715 : "ソファ", + 720 : "わらのカウチ", + 730 : "ショートケーキカウチ", + 800 : "つくえ", + 810 : "ログデスク", + 900 : "かさたて", + 910 : "コートかけ", + 920 : "ごみばこ", + 930 : "赤いキノコ", + 940 : "黄色いキノコ", + 950 : "コート掛け", + 960 : "タルのスタンド", + 970 : "サボテン", + 980 : "テント小屋", + 990 : "ジュリエットのせんす", + 1000 : "大きなじゅうたん", + 1010 : "丸いじゅうたん", + 1015 : "丸いじゅうたん", + 1020 : "小さいじゅうたん", + 1030 : "葉っぱのマット", + 1100 : "かざりだな", + 1110 : "かざりだな", + 1120 : "背のたかい本だな", + 1130 : "背のひくい本だな", + 1140 : "サンデーチェスト", + 1200 : "サイドテーブル", + 1210 : "小さいテーブル", + 1215 : "小さいテーブル", + 1220 : "コーヒーテーブル", + 1230 : "コーヒーテーブル", + 1240 : "シュノーケルテーブル", + 1250 : "クッキーテーブル", + 1260 : "ベッドルームテーブル", + 1300 : "1000コ貯ビーン箱", + 1310 : "2500コ貯ビーン箱", + 1320 : "5000コ貯ビーン箱", + 1330 : "7500コ貯ビーン箱", + 1340 : "10000コ貯ビーン箱", + 1399 : "電話", + 1400 : "セザンヌ・トゥーンの絵", + 1410 : "お花", + 1420 : "モダン・ミッキー", + 1430 : "レンブラント・トゥーンの絵", + 1440 : "トゥーンスケープ", + 1441 : "ホイッスルホース", + 1442 : "トゥーンスター", + 1443 : "「パイじゃない」", + 1500 : "ラジオ", + 1510 : "ラジオ", + 1520 : "ラジオ", + 1530 : "テレビ", + 1600 : "背のひくい花びん", + 1610 : "背のたかい花びん", + 1620 : "背のひくい花びん", + 1630 : "背のたかい花びん", + 1640 : "花びん", + 1650 : "花びん", + 1660 : "サンゴの花びん", + 1661 : "貝がらの花びん", + 1700 : "ポップコーンカート", + 1710 : "てんとう虫", + 1720 : "ふんすい", + 1725 : "洗濯機", + 1800 : "キンギョばち", + 1810 : "キンギョばち", + 1900 : "メカジキ", + 1910 : "シュモクザメ", + 1920 : "ツノのかべかけ", + 1930 : "シンプル・ソンブレロ", + 1940 : "ファンシー・ソンブレロ", + 1950 : "ドリームキャッチャー", + 1960 : "ひづめ", + 1970 : "バイソンの絵", + 2000 : "キャンディースウィングセット", + 2010 : "ケーキスライド", + 3000 : "バナナスプリットタブ", + 10000 : "丸いかぼちゃ", + 10010 : "細長いかぼちゃ", + 10020 : "ウィンター・ツリー", + 10030 : "ウィンター・リース" + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "シャツ", + "シャツ", + "シャツ", + "短パン", + "短パン", + "スカート", + "短パン", + ) + +ClothingTypeNames = { + 1400 : "マシューのシャツ", + 1401 : "ジェシカのシャツ", + 1402 : "マリッサのシャツ", + 1600 : "ワナの洋服", + 1601 : "サウンドの洋服", + 1602 : "おとりの洋服", + 1603 : "ワナの洋服", + 1604 : "サウンドの洋服", + 1605 : "おとりの洋服", + 1606 : "ワナの洋服", + 1607 : "サウンドの洋服", + 1608 : "おとりの洋服", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "かべがみ", + "いがた", + "床", + "腰板", + "へり", + ) + +WallpaperNames = { + 1000 : "パーチメント", + 1100 : "ミラノ", + 1200 : "ドーバー", + 1300 : "ビクトリア", + 1400 : "ニューポート", + 1500 : "パストラル", + 1600 : "ハーレクイン", + 1700 : "ムーン", + 1800 : "スター", + 1900 : "フラワー", + 2000 : "スプリングガーデン", + 2100 : "フォーマルガーデン", + 2200 : "勝負の日", + 2300 : "タッチダウン!", + 2400 : "くも", + 2500 : "つた", + 2600 : "春", + 2700 : "こけし", + 2800 : "花束", + 2900 : "エンゼルフィッシュ", + 3000 : "バブル", + 3100 : "バブル", + 3200 : "ゴーフィッシュ", + 3300 : "ストップフィッシュ", + 3400 : "たつのおとしご", + 3500 : "貝がら", + 3600 : "深海", + 3700 : "ブーツ", + 3800 : "さぽてん", + 3900 : "カウボーイハット", + 10100 : "ねこ", + 10200 : "こうもり", + 11000 : "雪のけっしょう", + 11100 : "葉っぱ", + 11200 : "雪だるま", + 13000 : "クローバー", + 13100 : "クローバー", + 13200 : "レインボー", + 13300 : "クローバー", + } + +FlooringNames = { + 1000 : "ウッドフロア", + 1010 : "カーペット", + 1020 : "ダイヤモンドタイル", + 1030 : "ダイヤモンドタイル", + 1040 : "しばふ", + 1050 : "茶色いレンガ", + 1060 : "赤いレンガ", + 1070 : "四角いタイル", + 1080 : "石", + 1090 : "遊歩道", + 1100 : "つち", + 1110 : "木のタイル", + 1120 : "タイル", + 1130 : "ハチの巣", + 1140 : "みず", + 1150 : "ビーチタイル", + 1160 : "ビーチタイル", + 1170 : "ビーチタイル", + 1180 : "ビーチタイル", + 1190 : "すな", + 10000 : "アイスキューブ", + 10010 : "イグルー", + 11000 : "クローバー", + 11010 : "クローバー", + } + +MouldingNames = { + 1000 : "節目", + 1010 : "クラシック", + 1020 : "ぼこぼこ", + 1030 : "花がら", + 1040 : "花がら", + 1050 : "てんとうむし", + } + +WainscotingNames = { + 1000 : "ペンキ", + 1010 : "木のパネル", + 1020 : "木", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "おおきな庭", + 20 : "ワイルドな庭", + 30 : "ギリシャ風の庭", + 40 : "都市の風景", + 50 : "ウェスタン", + 60 : "水中", + 70 : "トロピカルアイランド", + 80 : "星空", + 90 : "チキプール", + 100 : "南極", + 110 : "農場", + 120 : "キャンプ", + 130 : "大通り", + } + + +# don't translate yet +NewCatalogNotify = "電話で注文できる新しい品物があるわよ!" +NewDeliveryNotify = "キミのメールボックスに荷物が届いているわよ。" +CatalogNotifyFirstCatalog = "最初のカタログが届いたわよ! 楽しいアイテムでおしゃれしたり、家の模様替えができるわよ。" +CatalogNotifyNewCatalog = "カタログVol%sが届いたわよ! 電話で商品を注文してね。" +CatalogNotifyNewCatalogNewDelivery = "注文した商品がメールボックスに 届いたわよ! カタログVOL%sも届いているわよ!" +CatalogNotifyNewDelivery = "注文した商品がメールボックスに届いたわよ!" +CatalogNotifyNewCatalogOldDelivery = "カタログVol%sが届いたわよ!注文した商品がまだメールボックスに残っているわよ!" +CatalogNotifyOldDelivery = "注文した商品がまだメールボックスに残っているわよ!" +CatalogNotifyInstructions = "トゥーンガイドの\"家に帰る\"ボタンを押して、おうちの電話まで行ってね!" +CatalogNewDeliveryButton = "商品が\n届いたよ" +CatalogNewCatalogButton = "新しい\nカタログ" +CatalogSaleItem = "" + +# don't translate yet +DistributedMailboxEmpty = "いまキミのメールボックスはからっぽだよ。電話注文したあとに、荷物をチェックしにここへ戻ってきて!" +DistributedMailboxWaiting = "いまキミのメールボックスはからっぽだけど、キミの注文した荷物はこちらに向かっているよ。あとでまたチェックしてみて!" +DistributedMailboxReady = "キミの注文したものがとどいたよ!" +DistributedMailboxNotOwner = "ごめん、これはキミのメールボックスじゃないね。" +DistributedPhoneEmpty = "キミとキミの家向けの特別品は、どの電話からでも注文できるよ。あたらしい品物は営業時間外に注文することができるんだ。\n\n今すぐ注文できるものはないので、あとでチェックしにきてね!" + +# don't translate yet +Clarabelle = "クララベル" +MailboxExitButton = "メールボックスを閉じる" +MailboxAcceptButton = "荷物をうけとる" +MailBoxDiscard = "このアイテムをすてる" #localize +MailboxAcceptInvite = "さんかする" +MailBoxRejectInvite = "さんかしない" +MailBoxDiscardVerify = "本当に %s をすててもいい?" +MailBoxRejectVerify = "Are you sure you want to Reject %s?" +MailboxOneItem = "品物が1つ届いています。" +MailboxNumberOfItems = "品物が%sつ届いています。" +MailboxGettingItem = "%s\nを取り出しました。" +MailboxGiftTag = "%s からのおくりもの" +MailboxGiftTagAnonymous = "とくめい" +MailboxItemNext = "次の\nアイテム" +MailboxItemPrev = "前の\nアイテム" +MailboxDiscard = "すてる" +MailboxReject = "Reject" +MailboxLeave = "とっておく" +CatalogCurrency = "ジェリービーン" +CatalogHangUp = "電話を切る" +CatalogNew = "しんせいひん" +CatalogBackorder = "バックオーダー" +CatalogLoyalty = "スペシャル" +CatalogPagePrefix = "ページ" +CatalogGreeting = "お電話ありがとうございます。\nクララベルのショッピングカタログです。ご注文は?" +CatalogGoodbyeList = ["それじゃ!", + "またお電話くださいね!", + "お電話ありがとう!", + "またどうぞ~!", + "ありがとうございました~!", + ] +CatalogHelpText1 = "ページをめくって買いたい商品を見てね。" +CatalogSeriesLabel = "シリーズ%s" +CatalogGiftFor = "ギフトを送る相手:" +CatalogGiftTo = "ギフトを送る相手: %s" +CatalogGiftToggleOn = "ギフトをやめる" +CatalogGiftToggleOff = "ギフトを買う" +CatalogGiftToggleWait = "配達中 ..." +CatalogGiftToggleNoAck = "配達できません" +CatalogPurchaseItemAvailable = "お買いあげありがとう! これはすぐに使うことができるわね。" +CatalogPurchaseGiftItemAvailable = "すばらしい! %sはすぐにこのギフトをつかえそうだね。" +CatalogPurchaseItemOnOrder = "お買いあげありがとうございます!ご注文の商品はキミのメールボックスに届きます!\n\nメールボックスを後でチェックしてみてね。" +CatalogPurchaseGiftItemOnOrder = "かしこまりました! %sへのギフトはうけとり人のメールボックスに配達されます。" +CatalogAnythingElse = "ほかの商品はよろしいですか?" +CatalogPurchaseClosetFull = "キミのクローゼットはいっぱいだね。 この品物を購入してもいいけど、もしかしたらこの品物が到着した時に、クローゼットのスペースにあきをもたせるため、なにかをすてる必要がでてくるよ。\n\nまだこの品物を購入したい?" +CatalogAcceptClosetFull = "キミのクローゼットはいっぱいだね。この品物をメールボックスからとってくる前に、それ用にスペースにあきをもたせるため、クローゼットにはいってなにかを削除しなきゃね。" +CatalogAcceptShirt = "あたらしいシャツを着るよ。今まで着ていたのはキミのクローゼットに入っているよ。" +CatalogAcceptShorts = "あたらしい短パンをはくよ。今まではいてたのはキミのクローゼットに入っているよ。" +CatalogAcceptSkirt = "あたらしいスカートをはくよ。今まではいてたのはキミのクローゼットに入っているよ。" +CatalogAcceptPole = "あたらしい釣ざおでもっと大きな魚を釣りに行こう!" +CatalogAcceptPoleUnneeded = "これよりも良い釣りざおを持っているよ!" +CatalogAcceptChat = "新しいスピードチャットのせりふを手に入れました!" +CatalogAcceptEmote = "新しい“きもち”を手に入れました!" +CatalogAcceptBeans = "ジェリービーンを受け取りました!" +CatalogAcceptRATBeans = "トゥーン・リクルートのごほうびが届きました!" +CatalogAcceptNametag = "新しいネーム・タグが届いたよ!" +CatalogAcceptGarden = "ガーデニングの道具が届いたよ!" +CatalogAcceptPet = "キミのペットの新しいトリックを受け取りました!" +CatalogPurchaseHouseFull = "おうちの中が荷物でいっぱいよ。この品物を購入してもいいけど、もしかしたらこの品物が到着した時に、おうちのスペースにあきをもたせるため、なにかをすてる必要がでてくるわよ。\n\nまだこの品物を購入したい? " +CatalogAcceptHouseFull = "おうちの中が荷物でいっぱいよ。この品物をメールボックスからとってくる前に、それ用にスペースにあきをもたせるため、おうちの中のなにかを捨てなきゃね。" +CatalogAcceptInAttic = "新しい品物は今キミの屋根裏にあるよ。 中にはいって、\"模様替え\"ボタンをクリックすると、キミの家の中におくことができるよ。" +CatalogAcceptInAtticP = "新しい品物は今キミの屋根裏にあるよ。 中にはいって、\"模様替え\"ボタンをクリックすると、キミの家の中におくことができるよ。" +CatalogPurchaseMailboxFull = "キミのメールボックスはいっぱいだね! 品物をいくつかとりだしてスペースにあきをもたせるまで、この品物を購入することはできないよ。" +CatalogPurchaseGiftMailboxFull = "%sのメールボックスはもういっぱいです!このアイテムは買えません。" +CatalogPurchaseOnOrderListFull = "いま注文している品物が多すぎるよ。すでに注文したものがいくつか届くまで、キミはこれ以上なにも注文することはできないよ。" +CatalogPurchaseGiftOnOrderListFull = "%sは、げんざい注文しすぎです。" +CatalogPurchaseGeneralError = "ゲーム内のエラーにより、この品物は購入できません:エラーコード %s" +CatalogPurchaseGiftGeneralError = "ゲームエラーのため、%(friend)sにこのアイテムをおくれませんでした: エラーコード %(error)s" +CatalogPurchaseGiftNotAGift = "このアイテムは%sにはもったいないのでおくれません。" +CatalogPurchaseGiftWillNotFit = "このアイテムは%sにはにあわないからおくれません。" +CatalogPurchaseGiftLimitReached = "このアイテムはもう持っているのでおくれません。" +CatalogPurchaseGiftNotEnoughMoney = "このアイテムはキミには高すぎて%sにはおくれないよ。" +CatalogAcceptGeneralError = "ゲーム内のエラーにより、この品物はメールボックスから削除することはできません:エラーコード %s" +CatalogAcceptRoomError = "置き場所がたりません。先になにかをすてなきゃネ!" +CatalogAcceptLimitError = "もう持ちきれないよ。先になにかをすてなきゃネ!" +CatalogAcceptFitError = "これはキミのサイズとちがうよ!他のトゥーンにあげよう。" +CatalogAcceptInvalidError = "このアイテムはなんだかイケてないね…。他のトゥーンにあげよう!" + +MailboxOverflowButtonDicard = "すてる" +MailboxOverflowButtonLeave = "そのまま" + +HDMoveFurnitureButton = "模様替え" +HDStopMoveFurnitureButton = "移動\n終了" +HDAtticPickerLabel = "屋根裏の中" +HDInRoomPickerLabel = "部屋の中" +HDInTrashPickerLabel = "ゴミ箱の中" +HDDeletePickerLabel = "削除する?" +HDInAtticLabel = "屋根裏" +HDInRoomLabel = "部屋" +HDInTrashLabel = "ゴミ箱" +HDToAtticLabel = "屋根裏に\n置く" +HDMoveLabel = "動かす" +HDRotateCWLabel = "右に回転" +HDRotateCCWLabel = "左に回転" +HDReturnVerify = "このアイテムを屋根裏に戻しますか?" +HDReturnFromTrashVerify = "このアイテムをゴミ箱から屋根裏に戻しますか?" +HDDeleteItem = "OKを押すとこのアイテムをゴミ箱に送るよ\nキャンセルを押すと取っておくよ。" +HDNonDeletableItem = "この種類の品物は削除できないよ!" +HDNonDeletableBank = "キミの銀行は削除できないよ!" +HDNonDeletableCloset = "キミのクローゼットは削除できないよ!" +HDNonDeletablePhone = "キミの電話は削除できないよ!" +HDNonDeletableNotOwner = "キミは%s'sのものを削除できないよ!" +HDHouseFull = "キミのうちが荷物でいっぱいだよ。部屋か屋根裏のアイテムを何か捨ててね。" + +HDHelpDict = { + "DoneMoving" : "部屋の模様替えをやめる", + "Attic" : "屋根裏にあるアイテムを\n表示します。\n部屋にまだ置いていない\nアイテムがあります。", + "Room" : "部屋の中にあるアイテムを表示します。\nさがしものをするのに便利です。", + "Trash" : "ゴミ箱が一杯になったり、\n捨ててから時間が経つと、\n古いアイテムから順番になくなります。", + "ZoomIn" : "部屋を拡大して見る", + "ZoomOut" : "部屋を縮小して見る", + "SendToAttic" : "アイテムを屋根裏にとって置きます。", + "RotateLeft" : "左に回転", + "RotateRight" : "右に回転", + "DeleteEnter" : "「すてるモード」に切り替えます。", + "DeleteExit" : "「すてるモード」を終わります。", + "FurnitureItemPanelDelete" : "%sをゴミ箱に入れる", + "FurnitureItemPanelAttic" : "%sを部屋に置く", + "FurnitureItemPanelRoom" : "%sを屋根裏に戻す。", + "FurnitureItemPanelTrash" : "%sを屋根裏に戻す。", + } + + +MessagePickerTitle = "フレーズがおおすぎるね。 \n\"%s\"\nを購入するには、なにか削除するものを選択しなきゃならないよ。" +MessagePickerCancel = "取り消す" +MessageConfirmDelete = "ほんとうに\"%s\"をキミのスピードチャットメニューから削除してもいいの?" + +CatalogBuyText = "買う" +CatalogRentText = "かりる" +CatalogGiftText = "ギフト" +CatalogOnOrderText = "注文済" +CatalogPurchasedText = "持ってるよ" +CatalogGiftedText = "ギフトが\n届いたよ" +CatalogPurchasedGiftText = "持ってるよ" +CatalogMailboxFull = "もういっぱい" +CatalogNotAGift = "ギフトじゃないよ" +CatalogNoFit = "コレはにあわないよ" +CatalogMembersOnly = "フルアクセス\nメンバー用" +CatalogSndOnText = "サウンド" +CatalogSndOffText = "ミュート" +CatalogPurchasedMaxText = "これ以上、買えないよ!" +CatalogVerifyPurchase = "ジェリービーン%(price)s個で%(item)sを買いますか?" +CatalogVerifyRent = "%(item)sをジェリービーン%(price)s個でかりますか?" +CatalogVerifyGift = "%(friend)sへのギフトとして%(item)sをジェリービーン%(price)s個で買いますか?" +CatalogOnlyOnePurchase = "この商品は一度にひとつしか持てないんだ。この品物を購入したら、%(old)sはなくなっちゃうんだ。\n\nほんとうにジェリービーン%(price)sの%(item)sを買う?" +CatalogExitButtonText = "電話を切る" +CatalogCurrentButtonText = "今もっている品物へ" +CatalogPastButtonText = "前にもっていた品物へ" + +TutorialHQOfficerName = "HQスタッフのハリー" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "チュートリアルのトム", + 999 : "トゥーン・テイラー", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "ぎんこういんのボブ", + 2003 : "ピートきょうじゅ", + 2004 : "したてやタミー", + 2005 : "としょかんいんのラリー", + 2006 : "てんいんの\nクラーク", + 2011 : "てんいんの\nクララ", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "りょうしのフレディー", + 2018 : "Duff..err..TIP Man", + # NPCPetClerks + 2013 : "てんいんの\nポッピー", + 2014 : "てんいんの\nペッピー", + 2015 : "てんいんの\nパッピー", + # NPCPartyPerson + 2016 : "パーティープランナーの\nパンプキン", + 2017 : "パーティープランナーの\nポリー", + + # Silly Street + 2101 : "はいしゃのダニエル", + 2102 : "けいさつかんのシェリー", + 2103 : "ヘクション・キティー", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "カナリア・コールマイン", + 2109 : "バブル・ブローハード", + 2110 : "ビル・ボード", + 2111 : "ダンシング・ディエゴ", + 2112 : "ドクター・トム", + 2113 : "スバラシイローロ", + 2114 : "ロズ・ベリー", + 2115 : "パティ・ペーパーカット", + 2116 : "ブル・マクドナル", + 2117 : "ママ・ズイーナ", + 2118 : "ピエロのドッケ", + 2119 : "ハニー・ハハ", + 2120 : "ビンキーきょうじゅ", + 2121 : "ホッホふじん", + 2122 : "おサルのハリー", + 2123 : "ナンデ・ボトルニスキー", + 2124 : "オヤジモ・コケール", + 2125 : "ナマケルスキイー", + 2126 : "ガハハきょうじゅ", + 2127 : "ウッディ・コゼーニ", + 2128 : "ピエロのピエール", + 2129 : "フランク・フルター", + 2130 : "ジョイ・バズ", + 2131 : "フェザー・コーチョチョ", + 2132 : "クレージー・ドン", + 2133 : "ドクター・ハッピー", + 2134 : "サイレント・シモーヌ", + 2135 : "メアリ", + 2136 : "サル・クスクス", + 2137 : "ハッピー・ヘイキュン", + 2138 : "マルドゥーン", + 2139 : "ポール・ポタポッター", + 2140 : "りょうしのビリー", + + # Loopy Lane + 2201 : "ゆうびんきょくちょうのユーゴ", + 2202 : "シャーリー・ジョーダン", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "ノイ・ワイズメーカー", + 2208 : "ステイッキー・ルー", + 2209 : "チャーリー・ケラリン", + 2210 : "ヒッヒ", + 2211 : "サリー・ピッチャー", + 2212 : "ワーレン・ボトラー", + 2213 : "ルーシー・タイヤ", + 2214 : "シュミット・シミー", + 2215 : "シド・ビンカーン", + 2216 : "ノナ・クリア", + 2217 : "シャーキー・ジョーンズ", + 2218 : "ファニー・ペイジ", + 2219 : "シェフ・ビーダマー", + 2220 : "リック・ガンセキー", + 2221 : "ペルニラ・ペッタラ", + 2222 : "ショーティ・フューズ", + 2223 : "サーシャ・ビリリン", + 2224 : "スモーキー・ジョー", + 2225 : "りょうしのドゥルーピー", + + # Punchline Place + 2301 : "ドクター・ポッキン", + 2302 : "ギグルきょうじゅ", + 2303 : "ナースのナンシー", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "ナンシー・ガス", + 2309 : "ブルース・ブー", + 2311 : "フランツ・クビマガラーン", + 2312 : "ドクター・センシティブ", + 2313 : "シーラ・シミアトン", + 2314 : "ネッド・ナゲット", + 2315 : "グニー・カメナイン", + 2316 : "シンディ・スプリンクル", + 2318 : "トニー・トニン", + 2319 : "ジッピー", + 2320 : "アル・カチーノ", + 2321 : "りょうしのパンチー", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "てんいんの\nウィリー", + 1002 : "てんいんの\nビリー", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "スタン・ステテコ", + # NPCFisherman + 1008 : "りょうしのファーボール", + # NPCPetClerks + 1009 : "てんいんの\nバーキー", + 1010 : "てんいんの\nパー", + 1011 : "てんいんの\nブループ", + # NPCPartyPerson + 1012 : "パーティープランナーの\nピクルス", + 1013 : "パーティープランナーの\nパッティー", + + # Barnacle Blvd. + 1101 : "ビリー・バッド", + 1102 : "キャプテン・カール", + 1103 : "レイ・シャケスーツ", + 1104 : "ドクター・メガリス", + 1105 : "フックていとく", + 1106 : "ノーリーふじん", + 1107 : "カル・プープデック", + 1108 : "HQスタッフ", + 1109 : "HQスタッフ", + 1110 : "HQスタッフ", + 1111 : "HQスタッフ", + 1112 : "ゲリー・ガボガボ", + 1113 : "エノラ・ゲラゲーラ", + 1114 : "チャーリー・チッチャ", + 1115 : "シーラ・イーカン", + 1116 : "ベッシー・シェル", + 1117 : "キャプテン・オット", + 1118 : "チョビー・ヒゲール", + 1121 : "リンダ・ツリーラバー", + 1122 : "スタン・ショッペー", + 1123 : "ビリット・クルール", + 1124 : "フリッピー・スピーディー", + 1125 : "アイリーン・ポロポロン", + 1126 : "りょうしのバーニー", + + # Seaweed Street + 1201 : "バーバラ・シェル", + 1202 : "アート", + 1203 : "エイハブ", + 1204 : "ロッキー・ムッキ", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "ブイきょうじゅ", + 1210 : "ギャング・アルネ", + 1211 : "ウィン・バッグ", + 1212 : "トビー・トングスティンガー", + 1213 : "ダンテ・ドルフィン", + 1214 : "ケイト・ヒュー", + 1215 : "ダイナ・ダウン", + 1216 : "ロッド・リール", + 1217 : "シーシー・ワカメイン", + 1218 : "ティム・カンダイスキー", + 1219 : "ブライアン・ビーチヘッド", + 1220 : "カーラ・スイモン", + 1221 : "ブッキー・マッキー", + 1222 : "ヨー・オーイ", + 1223 : "シド・イーカン", + 1224 : "エミリー・ヌルール", + 1225 : "ボンゾ・ボソール", + 1226 : "モッチ・ホー", + 1227 : "ミス・サンゴ", + 1228 : "りょうしのリード", + + # Lighthouse Lane + 1301 : "アリス", + 1302 : "メルヴィル", + 1303 : "クラッガー", + 1304 : "スヴェトラーナ", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "シーフォーム", + 1310 : "テッド・タックル", + 1311 : "サカ・サマール", + 1312 : "イーサン・コッツ", + 1313 : "アーリー・サンモン", + 1314 : "サビータ・ラルフ", + 1315 : "ドクター・ユラー", + 1316 : "ヒルマ・イルマー", + 1317 : "ポーラ・ストレス", + 1318 : "ダン・ドンダン", + 1319 : "オーライ・ドライ", + 1320 : "タイヘイ・ヨー", + 1321 : "ダイナ・ドッカー", + 1322 : "ブーブー・クッション", + 1323 : "スティンキー・ネッド", + 1324 : "パール・ダイバー", + 1325 : "ネッド・ビーグル", + 1326 : "フェリシア・チップス", + 1327 : "シンディ・チズル", + 1328 : "フレッド・フランダー", + 1329 : "シェリィ・シーウィード", + 1330 : "ポーター・ホール", + 1331 : "ルディ・ラダー", + 1332 : "りょうしのシェーン", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "ベティ・フリーズ", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "てんいんの\nレニー", + 3007 : "てんいんの\nペニー", + 3008 : "ウォーレン・ボタン", + # NPCFisherman + 3009 : "りょうしのフリジー", + # NPCPetClerks + 3010 : "てんいんの\nスキップ", + 3011 : "てんいんの\nディップ", + 3012 : "てんいんの\nキップ", + # NPCPartyPerson + 3013 : "パーティープランナーの\nピート", + 3014 : "パーティープランナーの\nペニー", + + # Walrus Way + 3101 : "ウシおじさん", + 3102 : "フリーズおばさん", + 3103 : "フレッド", + 3104 : "ハッティ", + 3105 : "フロスティ・フレディ", + 3106 : "マモル・トリハダ", + 3107 : "パティ・パスポート", + 3108 : "トボガン・テッド", + 3109 : "ケイト", + 3110 : "チキン・ボーイ", + 3111 : "シンジン", + 3112 : "ミニおじさん", + 3113 : "ヒステリー・ハリー", + 3114 : "ヘンリー・ハザード", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "クリーピー・カール", + 3120 : "マイク・テブクロン", + 3121 : "ジョー・ショック", + 3122 : "ルーシー・リュージュ", + 3123 : "フランク・ロイド・アイス", + 3124 : "ランス・アイスバーグ", + 3125 : "カーネル・クランチ", + 3126 : "コレスト・ローラー", + 3127 : "オチルーラ", + 3128 : "スティッキィ・ジョージ", + 3129 : "パンやのブリジット", + 3130 : "サンディ", + 3131 : "レイジー・ロレンゾ", + 3132 : "Mr.ハイ", + 3133 : "フリーズフレームはかせ", + 3134 : "ラウンジ・ラッサー", + 3135 : "メルティ・ニール", + 3136 : "ハッピー・スー", + 3137 : "Mr.フリーズ", + 3138 : "シェフ・バンブルスープ", + 3139 : "ツララばあちゃん", + 3140 : "りょうしのルシール", + + # Sleet Street + 3201 : "アークティックおばさん", + 3202 : "シェイキー", + 3203 : "ウォルト", + 3204 : "ドクター・アイシィ", + 3205 : "バンピー・ノギン", + 3206 : "セクシー・ヴィダリア", + 3207 : "ドクター・シンキクサーイ", + 3208 : "グランピー・フィル", + 3209 : "ギグル・マギー", + 3210 : "シミアン・サム", + 3211 : "ファニー・フリーズ", + 3212 : "フロスティ・フレッド", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "あせかきピート", + 3218 : "ブルー・ルー", + 3219 : "トム・フロスト", + 3220 : "Mr.ヘックショイ", + 3221 : "ネリー・スノウ", + 3222 : "ミンディ・トーショウ", + 3223 : "チャッピー", + 3224 : "フリーダ・フロストバイト", + 3225 : "ブレイク・アイス", + 3226 : "サンタ・ポーズ", + 3227 : "ソーラー・レイ", + 3228 : "ウィン・チル", + 3229 : "ヘルニア・ベルト", + 3230 : "ハゲのハーゲン", + 3231 : "チョッピ", + 3232 : "りょうしのアルバート", + + # Polar Place + 3301 : "ペイズリー・パッチ", + 3302 : "ビヨン・ボード", + 3303 : "ミエールきょうじゅ", + 3304 : "エディー・イエティ", + 3305 : "マック・ラメイ", + 3306 : "ポーラ・ベアー", + # NPC Fisherman + 3307 : "つりびとのフレドリカ", + 3308 : "ドナルド・フランプ", + 3309 : "ブーツィー", + 3310 : "フレークきょうじゅ", + 3311 : "コニー・フェリス", + 3312 : "マーチ・ハリー", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "キッシー・クリッシー", + 3318 : "ジョニー・カシミア", + 3319 : "サム・ステットソン", + 3320 : "フィジー・リジー", + 3321 : "ポール・ツルハシー", + 3322 : "フルー・ルー", + 3323 : "ダラス・ボレアリス", + 3324 : "じまん屋のストゥー", + 3325 : "グルービー・ガーランド", + 3326 : "ブリーチ", + 3327 : "チャック・ロースト", + 3328 : "シェイディー・サディー", + 3329 : "トレディング・エド", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "ミンディ・マンデー", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "てんいんのド", + 4007 : "てんいんのレ", + 4008 : "したてやハーモニー", + # NPCFisherman + 4009 : "りょうしのファニー", + # NPCPetClerks + 4010 : "てんいんの\nクリス", + 4011 : "てんいんの\nネール", + 4012 : "てんいんの\nウェスティンガール", + # NPCPartyPerson + 4013 : "パーティープランナーの\nプレストン", + 4014 : "パーティープランナーの\nペネロペ", + + # Alto Ave. + 4101 : "トム", + 4102 : "バイオレット", + 4103 : "ドクター・アイタタ", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "クレフ", + 4109 : "カルロス", + 4110 : "メトラ・ノーム", + 4111 : "トム・ハム", + 4112 : "ファー", + 4113 : "マダム・マナーズ", + 4114 : "オフキー・エリック", + 4115 : "バーバラ・セヴィル", + 4116 : "ピッコロ", + 4117 : "マンディ・リン", + 4118 : "アテンダントのエイブ", + 4119 : "モー・ツァールト", + 4120 : "ヴィオラ・フカフーカ", + 4121 : "ジー・マイナー", + 4122 : "ミッコ・ミント", + 4123 : "カミナリ・テッド", + 4124 : "リフラフ", + 4125 : "メロディ・ウェーバー", + 4126 : "メル・カント", + 4127 : "ハッピー・タップ", + 4128 : "スクープ・ルチアーノ", + 4129 : "トゥッツィー・ツーステップ", + 4130 : "メタルマイク", + 4131 : "アブラハム・アーマー", + 4132 : "ジャジー・サリー", + 4133 : "スコット・ポプリン", + 4134 : "ディスコ・デイビッド", + 4135 : "ルーニー・ルンルン", + 4136 : "パティ・ポーズ", + 4137 : "トニー・イヤプラグ", + 4138 : "シェフ・トレモロ", + 4139 : "ハーモニー・スウェル", + 4140 : "ネッド・ブキヨン", + 4141 : "りょうしのジェッド", + + # Baritone Blvd. + 4201 : "ティナ", + 4202 : "バリー", + 4203 : "モックン", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "ヘイディ", + 4209 : "シュマルツ・ワルツ", + 4211 : "カール・コンチェルト", + 4212 : "ハイドンたんてい", + 4213 : "フラン・フォリー", + 4214 : "ティナ・ビート", + 4215 : "ティム・タム", + 4216 : "ガミー・ホイッスル", + 4217 : "ハンサム・アントン", + 4218 : "ウィルマ・ウィンド", + 4219 : "シド・ソナタ", + 4220 : "ミミ・ショパン", + 4221 : "モー・マドリガル", + 4222 : "ソラ・シド", + 4223 : "ペニー・プロンプター", + 4224 : "ジャングル・ジム", + 4225 : "ホーリー・ヒス", + 4226 : "オクタヴィア・オクターブ", + 4227 : "フランチェスカ・ヒソヒッソ", + 4228 : "オーガスト・ウィンド", + 4229 : "ジューン・ルーン", + 4230 : "マシュー・エチュード", + 4231 : "ステフィ・チロル", + 4232 : "ヘドリー・キンコン", + 4233 : "チャーリー・カープ", + 4234 : "リード・ギター", + 4235 : "りょうしのラリー", + + # Tenor Terrace + 4301 : "ユキ", + 4302 : "アンナ", + 4303 : "レオ", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "タバサ", + 4309 : "マーシャル", + 4310 : "メロディ・メロン", + 4311 : "シャンティ・バッハッハ", + 4312 : "マーク・パッサージュ", + 4313 : "ホワイティ・ウィッグ", + 4314 : "ダナ・ダンダー", + 4315 : "カレン・グルック", + 4316 : "シェーン・シューマン", + 4317 : "スタッビィ・パッヘルベル", + 4318 : "ボブ・マーリン", + 4319 : "リンキー・ディンク", + 4320 : "キャミー・コーダ", + 4321 : "ルーク・リュート", + 4322 : "ランディ・リズム", + 4323 : "ハンナ・プチ", + 4324 : "エリィ", + 4325 : "ぎんこういんのブラン", + 4326 : "フラン・フレット", + 4327 : "ワンバ・サンバ", + 4328 : "ワグナー", + 4329 : "テリィ・プロンプター", + 4330 : "クエンティン", + 4331 : "メロウ・コステロ", + 4332 : "ジギー", + 4333 : "ハリー", + 4334 : "フレディ・マーズ", + 4335 : "りょうしのウォルデン", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "てんいんの\nピーチ", + 5006 : "てんいんの\nハーブ", + 5007 : "ボニー・ブロッソム", + # NPCFisherman + 5008 : "りょうしのフローラ", + # NPCPetClerks + 5009 : "てんいんの\nボー・タニー", + 5010 : "てんいんの\nトム・ドー", + 5011 : "てんいんの\nダグ・ウッド", + # NPCPartyPerson + 5012 : "パーティープランナーの\nピアス", + 5013 : "パーティープランナーの\nペギー", + + # Elm Street + 5101 : "クータ", + 5102 : "スージー", + 5103 : "サッサ", + 5104 : "ランラン", + 5105 : "ジャック", + 5106 : "さんぱつやビョーン", + 5107 : "ゆうびんきょくいんのフェリペ", + 5108 : "おかみのジャネット", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "ドングリン", + 5114 : "フニャリン", + 5115 : "ハニー・メロン", + 5116 : "ビッグ・グリーン", + 5117 : "ペタル", + 5118 : "ポップ・コーン", + 5119 : "バリー・メドレー", + 5120 : "ゴーファー", + 5121 : "ポーラ・ピース", + 5122 : "ジミー・モミジ", + 5123 : "ソー・セキ", + 5124 : "ビアンキ・コスモス", + 5125 : "サンジェイ・スプラッシュ", + 5126 : "マダム・コギク", + 5127 : "ポリーせんせい", + 5128 : "ケイト・ケイトウ", + 5129 : "りょうしのサリー", + + # Maple Street + 5201 : "ジェイク", + 5202 : "シンシア", + 5203 : "リサ", + 5204 : "バート", + 5205 : "ダンディ・タンポポ", + 5206 : "ブル・グリーン", + 5207 : "ソフィ・テッポー", + 5208 : "サマンサ・スペード", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "ビッグ・マヨ", + 5214 : "ガッチィ・リー", + 5215 : "イモーナ・コロリン", + 5216 : "スティンキー・ジム", + 5217 : "フランク・フラワー", + 5218 : "ロッキーじいさん", + 5219 : "ビッグ・タジャーン", + 5220 : "シルキー・レイス", + 5221 : "ピンク・フラミンゴ", + 5222 : "ハッピー・タヌキン", + 5223 : "ウェット・ウィリー", + 5224 : "つるまきおじさん", + 5225 : "ナナ・グリーン", + 5226 : "ピート・モス", + 5227 : "ピーチ・ホップ", + 5228 : "ダグラス・シルバー", + 5229 : "りょうしのリリー", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "クリスタル", + 5306 : "エス・カルゴ", + 5307 : "マッシュ・ルーム", + 5308 : "ブンブン", + 5309 : "ロー・メイン", + 5310 : "パット・パター", + 5311 : "ミスター・ジャッジ", + 5312 : "ビーン・ビーン", + 5313 : "コーチのヨーガ", + 5314 : "ミセス・ウエスタン", + 5315 : "マッドおじさん", + 5316 : "ソファおじさん", + 5317 : "タンテイのメイ", + 5318 : "シーザー", + 5319 : "ローズ", + 5320 : "フリージア", + 5321 : "パインきょうじゅ", + 5322 : "りょうしのローズ", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "グラハム・プーリー", # "Graham Pree" + 8002 : "イボナ・レース", # "Ivona Race" + 8003 : "アニータ・カッツ", # "Anita Winn" + 8004 : "フィル・ゴール", # "Phil Errup" + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "スーザン・ムーニャ", + 9002 : "スリーピー・トム", + 9003 : "トビー・アクビー", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "てんいんの\nジル", + 9009 : "てんいんの\nフィル", + 9010 : "よれよれジョニー", + # NPCFisherman + 9011 : "りょうしのフロイト", + # NPCPetClerks + 9012 : "てんいんの\nサラ・スヌーズ", + 9013 : "てんいんの\nキャット・ナップ", + 9014 : "てんいんの\nリンクル", + # NPCPartyPerson + 9015 : "パーティープランナーの\nペブルス", + 9016 : "パーティープランナーの\nパール", + + # Lullaby Lane + 9101 : "ふとんやトニー", + 9102 : "ビッグ・ママ", + 9103 : "P.J.", + 9104 : "スウィーティ", + 9105 : "アクビはかせ", + 9106 : "マックス・スマイル", + 9107 : "スピカ", + 9108 : "ウィルバー・ウィンク", + 9109 : "ドリーミー・ダフネ", + 9110 : "マティ・タビー", + 9111 : "テティ・テーデン", + 9112 : "ララバイ・ルー", + 9113 : "ジャック・クロック", + 9114 : "スウィート・リップス", + 9115 : "ベイビーフェイス・マクドゥーガル", + 9116 : "スティーブ・スリープ", + 9117 : "アフタ・アワーズ", + 9118 : "スター・ナイト", + 9119 : "ロッコ", + 9120 : "サラ・グースカ", + 9121 : "セレナ・ハッピー", + 9122 : "ハレー・ボッタイ", + 9123 : "テディ・ブレア", + 9124 : "ニーナ・ナイトライト", + 9125 : "ドクター・ウッツラ", + 9126 : "アイ・パチーリ", + 9127 : "タビー・タッカー", + 9128 : "ハーディ・トゥール", + 9129 : "ベルサねえさん", + 9130 : "チャーリー・シーツ", + 9131 : "スーザン・シエスタ", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "りょうしのテーラー", + + # Pajama Place + 9201 : "バーニー", + 9202 : "オービ", + 9203 : "ナット", + 9204 : "クレア", + 9205 : "ゼン・グレン", + 9206 : "スキニー・ジニー", + 9207 : "ジェーン・ドレイン", + 9208 : "ドロジー・デイブ", + 9209 : "ドクター・フロス", + 9210 : "マスター・マイク", + 9211 : "ドーン", + 9212 : "ムーン・ビーム", + 9213 : "ルースター・リック", + 9214 : "ドクター・ブリンキィ", + 9215 : "リップ", + 9216 : "キャット", + 9217 : "ロウフル・リンダ", + 9218 : "ワルツ・マチルダ", + 9219 : "カウンテス", + 9220 : "グランピィ・ゴードン", + 9221 : "ザリ", + 9222 : "カウボーイ・ジョージ", + 9223 : "マーク・ザ・ラーク", + 9224 : "サンディ・サンドマン", + 9225 : "フィジェティ・ブリジェッド", + 9226 : "ウィリアム・テラー", + 9227 : "ベッド・ヘッド・テッド", + 9228 : "ウィスパリング・ウィロー", + 9229 : "ローズ・ペタル", + 9230 : "テックス", + 9231 : "ハリー・ハンモック", + 9232 : "ハニー・ムーン", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "りょうしのジャン", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("トゥーンホール", ""), + 2514 : ("トゥーンタウン バンク", ""), + 2516 : ("トゥーンタウン スクールハウス", ""), + 2518 : ("トゥーンタウン ライブラリー", ""), + 2519 : (lGagShop, ""), + 2520 : (lToonHQ, ""), + 2521 : (lClothingShop, ""), + 2522 : (lPetShop, ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("ニコニコ はいしゃ", ""), + 2602 : ("", ""), + 2603 : ("カナリア さいくつ屋", ""), + 2604 : ("ブーブー ドライ クリーニング", ""), + 2605 : ("トゥーンタウン カンバン工房", ""), + 2606 : ("", ""), + 2607 : ("踊る大まめ屋", ""), + 2610 : ("ドクター トム フォーリー", ""), + 2611 : ("", ""), + 2616 : ("ヘンチク チクチク へんそう ショップ", ""), + 2617 : ("おまぬけ スタント", ""), + 2618 : ("笑われ ダンス スタジオ", ""), + 2621 : ("空とぶ 紙ひこうき屋", ""), + 2624 : ("ハッピー フーリガンの お店", ""), + 2625 : ("まさかの パイショップ", ""), + 2626 : ("ドッケの ジョーク リペア", ""), + 2629 : ("ワハハ プレイス", ""), + 2632 : ("ピエロ スクール", ""), + 2633 : ("ヒッヒの ティーショップ", ""), + 2638 : ("トゥーンタウン プレイハウス", ""), + 2639 : ("モンキー トリック ショップ", ""), + 2643 : ("ボトルニスキーの カンヅメ屋", ""), + 2644 : ("笑えない ジョーク ショップ", ""), + 2649 : ("スーパー ゲームショップ", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("ワハハ きょうしつ", ""), + 2655 : ("ファニー マニ バンク", ""), + 2656 : ("中古ピエロ車 はんばい", ""), + 2657 : ("フランクの ジョークショップ", ""), + 2659 : ("有名な ジョイ バズの店", ""), + 2660 : ("くすぐり マシーンズ", ""), + 2661 : ("クレージー ダフィー", ""), + 2662 : ("ドクター ハッピー しんりょうじょ", ""), + 2663 : ("トゥーンタウン シネラマ", ""), + 2664 : ("マイム マイム", ""), + 2665 : ("メアリーのワールド トラベル", ""), + 2666 : ("ケラケラ ガス ステーション", ""), + 2667 : ("ハッピー タイムズ", ""), + 2669 : ("マルドゥーン バルーン", ""), + 2670 : ("スープ・フォーク", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("ムービー マルチプレックス", ""), + 2705 : ("ワイズエイカーの ノイズメーカー", ""), + 2708 : ("ブルー グルー", ""), + 2711 : ("トゥーンタウン ゆうびんきょく", ""), + 2712 : ("ワハハハハ カフェ", ""), + 2713 : ("スマイルアワー カフェ", ""), + 2714 : ("クーキー シネプレックス", ""), + 2716 : ("スープ アンド クランクアップ", ""), + 2717 : ("ボトル & カンショップ", ""), + 2720 : ("クランクアップ じどうしゃ しゅうり", ""), + 2725 : ("", ""), + 2727 : ("セルツァー ヘブンの びんショップ", ""), + 2728 : ("とうめい クリームショップ", ""), + 2729 : ("14金魚 ショップ", ""), + 2730 : ("ワクワク ニュース", ""), + 2731 : ("", ""), + 2732 : ("グーフボール スパゲッティ", ""), + 2733 : ("ヘビー級 タコショップ", ""), + 2734 : ("キュウバン & ソーサー", ""), + 2735 : ("ザ うちあげ ショップ", ""), + 2739 : ("チョットビリット しゅうりてん", ""), + 2740 : ("中古 バクチク屋", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("ジャズ ドライ クリーニング", ""), + 2744 : ("", ""), + 2747 : ("シュミシミ インク", ""), + 2748 : ("シャーリーの ギャグショップ", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("ブーブー クッション ソファ", ""), + 2802 : ("プー パチン ボールショップ", ""), + 2803 : ("カーニバル キッド", ""), + 2804 : ("ポキポキ カイロ プラクティック", ""), + 2805 : ("", ""), + 2809 : ("ザ パンチライン ジム", ""), + 2814 : ("トゥーンタウン シアター", ""), + 2818 : ("ザ フライング パイ", ""), + 2821 : ("", ""), + 2822 : ("ゴム・チキン サンドイッチ", ""), + 2823 : ("ユースクリーム アイスクリーム", ""), + 2824 : ("パンチライン ムービー パレス", ""), + 2829 : ("いかさま屋", ""), + 2830 : ("ハッピー ジッピー ショップ", ""), + 2831 : ("ギグルの ヒヒヒハウス", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("ケラケラ きゅうきゅう びょういん", ""), + 2836 : ("", ""), + 2837 : ("ハディハッハ セミナー", ""), + 2839 : ("パスパスパスタ", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : (lGagShop, ""), + 1507 : (lToonHQ, ""), + 1508 : (lClothingShop, ""), + 1510 : (lPetShop, ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("中古 ライフセーバー ショップ", ""), + 1604 : ("ウェット スーツ ドライ クリーニング", ""), + 1606 : ("フックの 時計しゅうり店", ""), + 1608 : ("ゲラゲラ ボートグッズ", ""), + 1609 : ("マメつりえさ ショップ", ""), + 1612 : ("ダイム&デック バンク", ""), + 1613 : ("イーカン ほうりつ じむしょ", ""), + 1614 : ("マキガイ ネイルサロン", ""), + 1615 : ("オットの ヨットショップ", ""), + 1616 : ("チョビヒゲ ビューティー サロン", ""), + 1617 : ("メガリスの めがねショップ", ""), + 1619 : ("木の おいしゃさん", ""), + 1620 : ("あさから ばんまで", ""), + 1621 : ("ポート デッキ ジム", ""), + 1622 : ("アメとムチ でんき店", ""), + 1624 : ("クイックリペア サービス", ""), + 1626 : ("シャケ チャント れいふく屋", ""), + 1627 : ("バーゲン ビン バーン", ""), + 1628 : ("センリツ!? ピアノちょうきょうし", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("ブイと カモメの かんごスクール", ""), + 1703 : ("テツナベ クイジーン", ""), + 1705 : ("ヨットを 売るよっと ショップ", ""), + 1706 : ("クラクラ クラゲショップ", ""), + 1707 : ("ザブント ギフト ショップ", ""), + 1709 : ("ジェットカイト ショップ", ""), + 1710 : ("バーバラの バーゲン ショップ", ""), + 1711 : ("ディープ シー ダイナー", ""), + 1712 : ("ムキムキ ジム", ""), + 1713 : ("アートの スマート チャート マート", ""), + 1714 : ("くるくる ホテル", ""), + 1716 : ("マーメイド スイムウェア", ""), + 1717 : ("ヒロク カンガエヨウ 屋", ""), + 1718 : ("ガッチャン タクシー", ""), + 1719 : ("ダック バック ウォーター社", ""), + 1720 : ("リール ディール ショップ", ""), + 1721 : ("海のなんでも屋", ""), + 1723 : ("イーカンの イカリ屋", ""), + 1724 : ("にゅるにゅる ウナギ モーレイ", ""), + 1725 : ("プレハブ シークラブ センター", ""), + 1726 : ("ソーダ フロート ショップ", ""), + 1727 : ("オール オア ナッシング", ""), + 1728 : ("これでいい カニ屋", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("海の もくず屋", ""), + 1804 : ("ショーナン ビーチ ジム", ""), + 1805 : ("タックル 宅配ランチ", ""), + 1806 : ("ピグミー ハット ストア", ""), + 1807 : ("リュウ骨 コッツ", ""), + 1808 : ("スピード ノット", ""), + 1809 : ("サビータ バケツ店", ""), + 1810 : ("イカリ カンリじむしょ", ""), + 1811 : ("カヌー しようよ?", ""), + 1813 : ("サンバシ サンザン カウンセリング", ""), + 1814 : ("あっち 向いて ホイショップ", ""), + 1815 : ("どうした ドック?", ""), + 1818 : ("セブン シー カフェ", ""), + 1819 : ("ドッカーズ ダイナー", ""), + 1820 : ("イタズラ ギア ショップ", ""), + 1821 : ("ネプトゥーン カンヅメ工房", ""), + 1823 : ("ハマグリ ダイナー", ""), + 1824 : ("イヌカキ屋", ""), + 1825 : ("めでタイ さかな市場", ""), + 1826 : ("クラッガーの クレバー クロビス クローゼット", ""), + 1828 : ("アリスの 砂のお城", ""), + 1829 : ("かもめの ちょうこく屋", ""), + 1830 : ("おとしもの かいしゅう所", ""), + 1831 : ("海の メイドさん", ""), + 1832 : ("がっしり がんがん マート", ""), + 1833 : ("カッチリ テーラー", ""), + 1834 : ("ルディーの おもしろ ショップ", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : (lGagShop, ""), + 4504 : (lToonHQ, ""), + 4506 : (lClothingShop, ""), + 4508 : (lPetShop, ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("トムトムの ドラム", ""), + 4604 : ("カッチコッチ タイム", ""), + 4605 : ("バイオレットの バイオリン店", ""), + 4606 : ("カーサ デ カスタネット", ""), + 4607 : ("パラレル アパレル", ""), + 4609 : ("ドレミ ピアノ ギャラリー", ""), + 4610 : ("キチント教室", ""), + 4611 : ("ジャスト ちょうりつ師", ""), + 4612 : ("アイタタ 歯科", ""), + 4614 : ("ようきな ひげそり びようしつ", ""), + 4615 : ("ピッコロの ピザパーラー", ""), + 4617 : ("ハッピー マンドリン", ""), + 4618 : ("レストルーム", ""), + 4619 : ("スコア! スポーツ グッズ店", ""), + 4622 : ("すやすや マクラ", ""), + 4623 : ("フラット シャープ", ""), + 4625 : ("ミッコの はみがきこ", ""), + 4626 : ("スラスラ本舗", ""), + 4628 : ("うっかり 保険", ""), + 4629 : ("リフラフ 紙コップ屋", ""), + 4630 : ("フォルテ ミュージック", ""), + 4631 : ("ポエム ブックショップ", ""), + 4632 : ("チクタク クロック ショップ", ""), + 4635 : ("テノール しんぶんしゃ", ""), + 4637 : ("ピッタンコ あなたサイズ テーラー", ""), + 4638 : ("ハードロック ショップ", ""), + 4639 : ("ニュー アンティーク屋", ""), + 4641 : ("ブルース ニュース", ""), + 4642 : ("ラグタイム クリーニング ショップ", ""), + 4645 : ("クラブ88", ""), + 4646 : ("", ""), + 4648 : ("ルンタッタ ひっこし屋", ""), + 4649 : ("", ""), + 4652 : ("フルストップ ショップ", ""), + 4653 : ("", ""), + 4654 : ("われがね 屋根がわら店", ""), + 4655 : ("トレモロ クッキング スクール", ""), + 4656 : ("", ""), + 4657 : ("いっぱつ さんぱつ屋", ""), + 4658 : ("ガタゴト ピアノ店", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("シュワルツ ワルツの ダンススクール", ""), + 4702 : ("モックンの 木材店", ""), + 4703 : ("コンチェルト トラベルショップ", ""), + 4704 : ("コンチェルティーナ コンサート 紹介サービス", ""), + 4705 : ("おいどん ハイドン たんてい社", ""), + 4707 : ("ドップラー効果 スタジオ", ""), + 4709 : ("アップビート 登山グッズ", ""), + 4710 : ("スローテンポ ドライビング スクール", ""), + 4712 : ("パンクパンク タイヤしゅうり店", ""), + 4713 : ("シャープな メンズ ファッション", ""), + 4716 : ("吹いてみるか ハーモニカ店", ""), + 4717 : ("ソナタ そんがい保険", ""), + 4718 : ("ショパン 食パン 工房", ""), + 4719 : ("マドリガル トレーラー ディーラー", ""), + 4720 : ("ドレミファ がっき店", ""), + 4722 : ("ぜんそう曲 代行屋", ""), + 4723 : ("プレイグラウンド ようひん店", ""), + 4724 : ("よいこの 学生服", ""), + 4725 : ("バリトン ヘアサロン", ""), + 4727 : ("オクタヴィアの ミュージック ショップ", ""), + 4728 : ("ソロで歌って", ""), + 4729 : ("オカリナ 書店", ""), + 4730 : ("ブレーメンの おんがく屋", ""), + 4731 : ("トゥーン チューンズ", ""), + 4732 : ("劇団エチュード シェイクス ピアリアン シアター", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("チロリアン アコーディオン", ""), + 4736 : ("キンコンカン ウェディング", ""), + 4737 : ("ハープ タープ", ""), + 4738 : ("ロックンローリー ギフトショップ", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("マーシャルの パンケーキ ショップ", ""), + 4803 : ("メロディ メイドさん サービス", ""), + 4804 : ("バッハッハ バーテンダー スクール", ""), + 4807 : ("マッサージの パッサージュ", ""), + 4809 : ("バロック ほんやく サービス", ""), + 4812 : ("", ""), + 4817 : ("トランペット ペットショップ", ""), + 4819 : ("ユキの ウクレレ ショップ", ""), + 4820 : ("", ""), + 4821 : ("アンナの 旅行代理店", ""), + 4827 : ("グルック クロック", ""), + 4828 : ("シューマンの シューズ ショップ", ""), + 4829 : ("パッヘルベル キャノン ボール店", ""), + 4835 : ("タバサの マウスピース店", ""), + 4836 : ("レゲエ レガリア", ""), + 4838 : ("シャコンヌ 音楽学校", ""), + 4840 : ("コーダコーラ ショップ", ""), + 4841 : ("ライアー うそ発見 サービス", ""), + 4842 : ("シンコペー ション コーポレー ション", ""), + 4843 : ("", ""), + 4844 : ("バイクショップ ピアニッシモ", ""), + 4845 : ("エリーの エレガント エレジー", ""), + 4848 : ("シルバー ハーブ 銀行", ""), + 4849 : ("", ""), + 4850 : ("Gマイナー セブン質屋", ""), + 4852 : ("サンバ セーター ショップ", ""), + 4853 : ("レオの フェンダー ショップ", ""), + 4854 : ("ワーグナーの ビデオショップ", ""), + 4855 : ("エレジー TV ネットワーク", ""), + 4856 : ("", ""), + 4862 : ("クエンティンの クアドリーユ", ""), + 4867 : ("コステロの チェロ工房", ""), + 4868 : ("", ""), + 4870 : ("ジギーズ ジグショップ", ""), + 4871 : ("ハーモニー ハンバーガー", ""), + 4872 : ("フレディーの ギターショップ", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : (lGagShop, ""), + 5502 : (lToonHQ, ""), + 5503 : (lClothingShop, ""), + 5505 : (lPetShop, ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("どんぐりまなこ メガネ店", ""), + 5602 : ("クタクタ ネクタイ店", ""), + 5603 : ("もりもり サラダバー", ""), + 5604 : ("メロメロン ブライダル", ""), + 5605 : ("みどりの おやゆび 家具店", ""), + 5606 : ("フラワー ペダル", ""), + 5607 : ("チューリップ ポストオフィス", ""), + 5608 : ("ポップコーン ショップ", ""), + 5609 : ("落ち葉 アンティーク店", ""), + 5610 : ("黒目スージーのボクシングジム事務所", ""), + 5611 : ("ゴーファーギャグ", ""), + 5613 : ("高枝バサミ さんぱつ屋", ""), + 5615 : ("アラエッサッサ 鳥のエサ店", ""), + 5616 : ("つゆくさ ホテル", ""), + 5617 : ("ランランの ちょうちょう ショップ", ""), + 5618 : ("グリーンピース 専門店", ""), + 5619 : ("ジャックの豆の木店", ""), + 5620 : ("もみじ ホテル", ""), + 5621 : ("わが輩は エコショップ", ""), + 5622 : ("くるくる 自転車店", ""), + 5623 : ("ことりの おふろやさん", ""), + 5624 : ("マダムに おまかせ", ""), + 5625 : ("ハチノコ ほいくえん", ""), + 5626 : ("ケイトウ けいと店", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("グリーン サラダバー", ""), + 5702 : ("ジェイクの くまで店", ""), + 5703 : ("クスノキ カメラショップ", ""), + 5704 : ("リサ・レモンの中古車店", ""), + 5705 : ("だいこく 家具店", ""), + 5706 : ("ジュエリー コロリン", ""), + 5707 : ("ミュージカル フルーツ レストラン", ""), + 5708 : ("フラフラ フラワーショップ", ""), + 5709 : ("おじいさんの 芝刈り サービス", ""), + 5710 : ("ジャングル ジム", ""), + 5711 : ("シルキー ストッキング ショップ", ""), + 5712 : ("ストーン アニマル", ""), + 5713 : ("ブンブック ちゃがま ブックショップ", ""), + 5714 : ("スプリング レイン ペットボトル", ""), + 5715 : ("はなしのタネ新聞", ""), + 5716 : ("ななくさ 質屋", ""), + 5717 : ("テッポウギクの 水でっぽう", ""), + 5718 : ("森の ペット屋さん", ""), + 5719 : ("根ほり葉ほり タンテイ事務所", ""), + 5720 : ("メンズ ファッション あおむし", ""), + 5721 : ("みちくさ食堂", ""), + 5725 : ("ホップス カフェ", ""), + 5726 : ("バートの ダート", ""), + 5727 : ("シルバーグラス バンク", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("どびん 花びん ちょびんショップ", ""), + 5805 : ("でんでん はいたつ屋", ""), + 5809 : ("きのこ のこのこ スクール", ""), + 5810 : ("はちみつドロップ ショップ", ""), + 5811 : ("レタス ホテル", ""), + 5815 : ("グリーン パットゴルフ", ""), + 5817 : ("白黒 はっきり屋", ""), + 5819 : ("グリーン ビーン ジーンズ", ""), + 5821 : ("スカッシュ& ストレッチ ジム", ""), + 5826 : ("カントリー ファーム ショップ", ""), + 5827 : ("どろんこ ディスカウント", ""), + 5828 : ("カウチ・ポテト インテリア", ""), + 5830 : ("とりこぼし タンテイ ジムショ", ""), + 5833 : ("ザ サラダバー", ""), + 5835 : ("フラワーベッド ブレックファスト", ""), + 5836 : ("エイプリル シャワーショップ", ""), + 5837 : ("ぼんさい スクール", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("ララバイ ライブラリー", ""), + 9503 : ("バー こもりうた", ""), + 9504 : (lGagShop, ""), + 9505 : (lToonHQ, ""), + 9506 : (lClothingShop, ""), + 9508 : (lPetShop, ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("ほしぞらホテル", ""), + 9602 : ("ウィンク まばたき ショップ", ""), + 9604 : ("ふかふかふとん店", ""), + 9605 : ("ララバイ ストリート 323番地", ""), + 9607 : ("ビッグママの ジャマイカ パジャマ", ""), + 9608 : ("ペットショップ またたび", ""), + 9609 : ("正しいスイミン教室", ""), + 9613 : ("クロック クリーナー", ""), + 9616 : ("テーデン家電店", ""), + 9617 : ("ララバイ ストリート 212番地", ""), + 9619 : ("にこにこ リラクゼーション センター", ""), + 9620 : ("いねむり タクシー サービス", ""), + 9622 : ("チクタク トケイ店", ""), + 9625 : ("スイートドリーム ビューティー パーラー", ""), + 9626 : ("ララバイ ストリート 818番地", ""), + 9627 : ("スリーピー ハウス", ""), + 9628 : ("ダレンダー カレンダー ショップ", ""), + 9629 : ("ララバイ ストリート 310番地", ""), + 9630 : ("うたたね サイクツ屋", ""), + 9631 : ("グースカ トケイ しゅうり店", ""), + 9633 : ("ドリームランド 上映室", ""), + 9634 : ("ジュクスイ マットレス", ""), + 9636 : ("フミン症保険", ""), + 9639 : ("トウミンの ススメ", ""), + 9640 : ("ララバイ ストリート 805番地", ""), + 9642 : ("ぐっすり ベッドギャラリー", ""), + 9643 : ("うつらうつら メガネ店", ""), + 9644 : ("まくら投げ ショップ", ""), + 9645 : ("ぬくぬく ホテル", ""), + 9647 : ("ベッドポスト 工具店", ""), + 9649 : ("いびき ねびき ベッド", ""), + 9650 : ("ララバイ ストリート 714番地", ""), + 9651 : ("うたたね いびき 研究所", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("ユメの旅行代理店", ""), + 9704 : ("夜のふくろう ペットショップ", ""), + 9705 : ("いねむり 自動車修理工場", ""), + 9706 : ("歯の妖精 デンタルクリニック", ""), + 9707 : ("夜明けのあくび ガーデンセンター", ""), + 9708 : ("バラのベットの フラワーショップ", ""), + 9709 : ("夢見がちパイプ屋", ""), + 9710 : ("レムすいみん眼科", ""), + 9711 : ("モーニングコール カンパニー", ""), + 9712 : ("ヒツジ数えます屋", ""), + 9713 : ("うとうと弁護士事務所", ""), + 9714 : ("ドリームボート マリーンショップ", ""), + 9715 : ("第一ねんね タオル銀行", ""), + 9716 : ("どっちらけパーティー 企画会社", ""), + 9717 : ("いねむりパン屋のドーナツ", ""), + 9718 : ("ねむりの精の サンドイッチ屋", ""), + 9719 : ("アルマジロまくら店", ""), + 9720 : ("ねごと発声教室", ""), + 9721 : ("ぬくぬく じゅうたん販売店", ""), + 9722 : ("寝ぼけタレント事務所", ""), + 9725 : ("猫の高級 パジャマ屋", ""), + 9727 : ("いびき帽子屋", ""), + 9736 : ("ドリームエージェンシー", ""), + 9737 : ("ワルツ・マチルダ ダンススクール", ""), + 9738 : ("いびきの館", ""), + 9740 : ("おやすみ フェンシングスクール", ""), + 9741 : ("ベッドの虫 くじょサービス", ""), + 9744 : ("三年寝太郎の しわのばしクリーム", ""), + 9752 : ("夜通し ガス会社", ""), + 9753 : ("月明かり アイスクリーム", ""), + 9754 : ("眠らない乗馬場", ""), + 9755 : ("ベッドかざりと ほうきの映画館", ""), + 9756 : ("", ""), + 9759 : ("眠れる美女パーラー", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : (lGagShop, ""), + 3508 : (lToonHQ, ""), + 3509 : (lClothingShop, ""), + 3511 : (lPetShop, ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("ノーザン ライト エレクトリックス", ""), + 3602 : ("北のご婦人 ぼうし屋", ""), + 3605 : ("", ""), + 3607 : ("ブリザード ウィザード", ""), + 3608 : ("ルージュ リュージュ", ""), + 3610 : ("ぬくぬく ムクルク ショップ", ""), + 3611 : ("うしおじさんの 雪かき屋", ""), + 3612 : ("イグルー デザイン ファクトリー", ""), + 3613 : ("アイシクル バイシクル", ""), + 3614 : ("シャキシャキ スノウ フレーク シリアル社", ""), + 3615 : ("さくさく しゃけ焼き亭", ""), + 3617 : ("冷気球 カンパニー", ""), + 3618 : ("イカリも トウケツ! コンサルティング", ""), + 3620 : ("スキー クリニック", ""), + 3621 : ("どろどろ アイスクリーム バー", ""), + 3622 : ("", ""), + 3623 : ("カチコチ パン屋", ""), + 3624 : ("コールド サンドウィッチ ショップ", ""), + 3625 : ("フリーズ おばさんの ラジエータ屋", ""), + 3627 : ("セントバーナード ケンネル", ""), + 3629 : ("ピースープ カフェ", ""), + 3630 : ("オーロラ トラベル エージェンシー", ""), + 3634 : ("安楽リフト", ""), + 3635 : ("中古のマキ屋", ""), + 3636 : ("ゾクゾク トリハダ屋", ""), + 3637 : ("ケイトの スケート ショップ", ""), + 3638 : ("トボガン ソリ ショップ", ""), + 3641 : ("フレッドの スレッド ベッド", ""), + 3642 : ("タイフウの メガネ ショップ", ""), + 3643 : ("スノウボール ホール", ""), + 3644 : ("とろける アイスキューブ", ""), + 3647 : ("ザ ペンギン タキシード ショップ", ""), + 3648 : ("インスタント アイスキューブ", ""), + 3649 : ("ブルブル バーガー ショップ", ""), + 3650 : ("アンチフリーズ アンティーク", ""), + 3651 : ("フローズン ホットドッグ", ""), + 3653 : ("アイスハウス ジュエリー", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("ウィンター ストレージ", ""), + 3703 : ("", ""), + 3705 : ("ツララ スティック ショップ", ""), + 3706 : ("シェイク シェイク! ショップ", ""), + 3707 : ("あったか ホーム 家具店", ""), + 3708 : ("プルート プレイス", ""), + 3710 : ("ヒョウテンカ ダイナー", ""), + 3711 : ("", ""), + 3712 : ("トウケツも おまかせ 配管工", ""), + 3713 : ("ガタガタ 歯科", ""), + 3715 : ("アークティック おばさんの スープショップ", ""), + 3716 : ("ゆきどけ パウダー ショップ", ""), + 3717 : ("ジュノー セミナー", ""), + 3718 : ("インナーチューブ ギャラリー", ""), + 3719 : ("ナイス アイスキューブ", ""), + 3721 : ("トボガン バーゲン ショップ", ""), + 3722 : ("ユキうさぎ スキーショップ", ""), + 3723 : ("スノウ グローブ屋", ""), + 3724 : ("ガチガチ クロニクル", ""), + 3725 : ("それのれ ソリ屋", ""), + 3726 : ("ソーラー パワー ブランケット", ""), + 3728 : ("雪かき 工房", ""), + 3729 : ("", ""), + 3730 : ("雪だるま 質屋", ""), + 3731 : ("ポータブル だんろ工房", ""), + 3732 : ("ザ フローズン ノーズ", ""), + 3734 : ("冷たいシセン 眼科", ""), + 3735 : ("アイス&キャップ", ""), + 3736 : ("キュートな アイスキューブ ショップ", ""), + 3737 : ("ダウンヒル ダイナー", ""), + 3738 : ("イマノウチ だんぼう屋", ""), + 3739 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : ("トゥーンHQ", ""), + 3806 : ("てぐすねカフェテリア", ""), + 3807 : ("中古かげ絵店", ""), + 3808 : ("セーター・ロッジ", ""), + 3809 : ("みたみた!話し相手サービス", ""), + 3810 : ("ふわふわキルト", ""), + 3811 : ("雪の天使商会", ""), + 3812 : ("ネコ用てぶくろ屋", ""), + 3813 : ("なぜかはきたい雪ぐつ屋", ""), + 3814 : ("リジーのソーダ・ショップ", ""), + 3815 : ("シャレのかつら店", ""), + 3816 : ("キッスィーの雪かざり", ""), + 3817 : ("雪のワンダーランド旅行社", ""), + 3818 : ("物置きかいたい屋", ""), + 3819 : ("エントツそうじ屋さん", ""), + 3820 : ("『白雪』ひょうはく店", ""), + 3821 : ("とうみんツアー", ""), + 3823 : ("大急ぎファンデーション", ""), + 3824 : ("直火やきぐり屋", ""), + 3825 : ("イケてるぼうし店", ""), + 3826 : ("げきおもクツ店", ""), + 3827 : ("歌うはなわ商会", ""), + 3828 : ("雪おとこカフェ", ""), + 3829 : ("コニーのマツボックリ店", ""), + 3830 : ("じきに見えるくもりどめ店", ""), + } + +# DistributedCloset.py +ClosetTimeoutMessage = "ごめん、\n時間切れだ!" +ClosetNotOwnerMessage = "キミのクローゼットじゃないけど、 ようふくを試着できるよ。" +ClosetPopupOK = lOK +ClosetPopupCancel = "取り消し" +ClosetDiscardButton = "すてる" +ClosetAreYouSureMessage = "何枚かようふくをすてるよ。ほんとにすてていい?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "ほんとにほんとに%sをすてていいの?" +ClosetShirt = "このシャツ" +ClosetShorts = "この短パン" +ClosetSkirt = "このスカート" +ClosetDeleteShirt = "シャツを\nすてる" +ClosetDeleteShorts = "ズボンを\nすてる" +ClosetDeleteSkirt = "ボトムを\nすてる" + +# EstateLoader.py +EstateOwnerLeftMessage = "ごめん、おうちの持ち主がいなくなっちゃった。 キミは%s秒以内にプレイグランドにワープするよ。" +EstatePopupOK = lOK +EstateTeleportFailed = "家へ帰れない?\nもう一度やってみて!" +EstateTeleportFailedNotFriends = "%sはキミの知らないトゥーンの家にいるよ。" + +# DistributedTarget.py +EstateTargetGameStart = "トゥーンアップ ターゲットゲーム、スタート!" +EstateTargetGameInst = "赤いまとにたくさん当てるとトゥーンアップできるよ。" +EstateTargetGameEnd = "トゥーンアップ ターゲットゲーム、おしまい..." + +# DistributedCannon.py +EstateCannonGameEnd = "キャノンゲームのレンタルは終わったよ。" + +# DistributedHouse.py +AvatarsHouse = "%s\nおうち" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "ごめん、これはキミの貯ビーン箱じゃないんだ。" +DistributedBankNotOwner = "ごめん、これはキミの貯ビーン箱じゃないんだ。" + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = lOK +FishTankValue = "やぁ、%(name)s!\n%(num)s匹の魚がいるね。ジェリービーン%(value)s個分だけど。\nもしよかったら魚を全部、買い取ろうか?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "ぜんぶうる" +FlowerBasketValue = "やぁ%(name)s、キミのバスケットにはジェリービーン%(value)sコ分の花が%(num)s本入ってるね。全部売っててくれるかい?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name + "" + else: + possesive = name + "の" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('いつもくうふく', 'しばしばくうふく', + 'ときどきくうふく', 'まれにくうふく',), + 'boredomThreshold': ('いつもたいくつ', 'しばしばたいくつ', + 'ときどきたいくつ', 'まれにたいくつ',), + 'angerThreshold': ('いつもふきげん', 'しばしばふきげん', + 'ときどきふきげん', 'ときどきふきげん'), + 'forgetfulness': ('いつもわすれる', 'しばしばわすれる', + 'ときどきわすれる', 'まれにわすれる',), + 'excitementThreshold': ('とてもうきうき', 'まあまあれいせい', + 'ちょっとうきうき', 'とってもうきうき',), + 'sadnessThreshold': ('とってもかなしい', 'しばしばかなしい', + 'ときどきかなしい', 'まれにかなしい',), + 'restlessnessThreshold': ('いつもおちつかない', 'しばしばおちつかない', + 'ときどきおちつかない', 'おちついている',), + 'playfulnessThreshold': ('ほとんどはしゃがない', 'ときどきはしゃぐ', + 'しばしばはしゃぐ', 'いつもはしゃぐ',), + 'lonelinessThreshold': ('いつもさびしい', 'しばしばさびしい', + 'ときどきさびしい', 'さびしくない',), + 'fatigueThreshold': ('いつもつかれた', 'しばしばつかれた', + 'ときどきつかれた', 'まれにつかれた',), + 'confusionThreshold': ('いつもこんらん', 'しばしばこんらん', + 'ときどきこんらん', 'まれにこんらん',), + 'surpriseThreshold': ('いつもびっくり', 'しばしばびっくり', + 'ときどきびっくり', 'まれにびっくり',), + 'affectionThreshold': ('まれにラブラブ', 'ときどきラブラブ', + 'しばしばラブラブ', 'いつもラブラブ',), + } + + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+":\"PageUp\"キーを押すと、よく見えるよ。" + +FireworksValentinesBeginning = "" +FireworksValentinesEnding = "" +FireworksJuly4Beginning = "トゥーンHQ:夏の花火大会へようこそ!楽しんでいってね!" +FireworksJuly4Ending = "トゥーンHQ:花火楽しんでくれたかな?すてきな夏をすごしてね!" +FireworksJuly14Beginning = lToonHQ+"" +FireworksJuly14Ending = lToonHQ+"" +FireworksOctober31Beginning = lToonHQ+"" +FireworksOctober31Ending = lToonHQ+"" +FireworksNewYearsEveBeginning = lToonHQ+":冬の花火大会へようこそ!" +FireworksNewYearsEveEnding = lToonHQ+":明けましておめでとう!2010年もいっしょにサイコーの一年にしようね!" +FireworksBeginning = "トゥーンHQ:夏の花火へようこそ!楽しんでいってね!" +FireworksEnding = "トゥーンHQ:花火楽しんでくれたかな?すてきな夏をすごしてね!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "トゥーンアドバイス:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "\"End\"キーを押している間、 トゥーンタスクの進み具合をチェックできるよ。", + "ギャグページは、\"Home\"キーを押したままにするとすぐチェックできるよ。", + "\"PageUp\"キーを押すと、\n見上げることができ、\n\"PageDown\"キーを押すと、見おろすことができるよ。", + "ジャンプするには、 \"Control\"キーを押してね。", + "\"F8\"キーを押すと、 トゥーンガイドを開け閉めできるよ。", + "トゥーンタウンの外で誰か知ってる人と「ひみつのともだち」の暗号を交換すると、自由にチャットができるようになるよ。", + "\"F9\"キーを押すと、画面の写真がとれるよ。写真はトゥーンタウンのフォルダに保存されるよ。", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "You can exchange Secret Friend Codes with somebody you know outside Toontown to enable open chat with them in Toontown.", + "トゥーンガイドのオプションページで、画面の解像度を変えたり、音の調整などの設定ができるよ。", + "キミの友だちの家のクローゼットにあるようふくをためすことができるよ。", + "トゥーンガイドの地図にある「家に戻る」ボタンを押すと、おうちに帰れるよ。", + "トゥーンタスクをクリアすると、キミのゲラゲラメーターが自動的に補充されるよ。", + "ようふく券がなくても、ようふくを試着できるよ。", + "トゥーンタスクのごほうびとして、もっとたくさんのギャグやジェリービーンをもてるようになることがあるよ。", + "ともだちリストには、ともだちを50人まで書き込むことができるよ。", + "いくつかトゥーンタスクは、キミがトゥーンタウンのプレイグラウンドへワープできるようにするんだ。トゥーンガイドにある地図のページを使えばテレポートできるよ。", + "プレイグラウンドでは、その地域のまわりにあらわれるアイスクリームといったアイテムをとると、はやくパワーが戻るよ。", + "バトルの後に体力を早く回復させたいときには、おうちに戻ってアイスクリームを集めよう!", + "\"TAB\"キーを押すと、キミのトゥーンの視点をいろいろ変えることができるよ。", + "ときどき同じごほうびをくれるちがうトゥーンタスクをみつけることがあるよ。いろいろ行ってみて!", + "おなじようなトゥーンタスクのともだちをみつければ、一緒に楽しみながらゲームも上達できるよ!", + "トゥーンタウンでしたことはセーブしなくても大丈夫だよ。トゥーンタウンのサーバーがすべての状況をちゃんと保存してるからね。", + "トゥーンをクリックするか、ともだちリストから選ぶと、他のトゥーンとないしょ話ができるよ。", + "スピードチャットのセリフの中には、体の動きがついているものもあるよ。", + "もしキミのいる場所が混みすぎていたら、 トゥーンガイドを使って、ロビーを変えてみて。", + "たくさんの建物をコグ達から取り戻すと、 トゥーンの頭の上に 金、銀、銅の星が付くよ!", + "ある程度の建物を取り戻すと、トゥーンHQの黒板にキミの名前がのるかもね。", + "コグから取り戻したビルが、再びコグに奪われることがあるよ。トゥーンの頭の上の星を維持するには、どんどんビルを取り戻そう!", + "ひみつの友達の名前は青色の文字で表示されるよ。", + # Fishing + "キミはトゥーンタウンにいる全ての魚を集められるかな?", + "池が違うと魚も違うよ。全ての池で釣りをしてみよう!", + "魚のバケツが一杯になったらプレイグラウンドにいるペットショップのてんいんに売ろう!", + "釣った魚は釣り人かペットショップで売ろう!", + "強い釣りざおは、より重い魚を釣ることができるけど、買うのにより多くのジェリービーンが必要になるよ。", + "より強い釣りざおはクララベルからカタログで購入できるよ。", + "ペットショップにとって魚は、重ければ重いほど価値があるよ。", + "珍しい魚はペットショップがより多いジェリービーンと交換してくれるよ。", + "釣りをしているとたまにジェリービーンが入ったバッグを見つけることがあるよ。", + "トゥーンタスクの中には池の中からあるアイテムを釣りあげるものがあるよ。", + "プレイグラウンドの池とストリートの池では釣れる魚が違うよ。", + "魚の中には本当に珍しい種類がいるよ。全部集めるまで釣り続けよう!", + "キミのおうちの近くにある池では、そこでしか釣れない魚がいるよ。", + "10種類の魚を釣るごとに、トロフィーがもらえるよ!", + "トゥーンガイドを見れば、どんな魚を釣ったかを見ることができるよ。", + "トロフィーの中にはゲラゲラメーターをアップさせるものがあるよ。", + "たくさんのジェリービーンを稼ぎたいなら、釣りがお勧めだよ!", + # Doodles + "ペットショップでドゥードゥルを手に入れよう!", + "ペットショップでは毎日、新しいドゥードゥルが売ってるよ!", + "どんなドゥードゥルがいるか、毎日ペットショップをチェックしに行ってみよう!", + "ロビーが違うと、そこにいるドゥードゥルも違うよ。", + # Karting + "スピードウェイでキミのホットロッドを展示してレース相手を探そう!", + "トゥーンタウン・セントラルのタイヤがたのトンネルから、グーフィー・サーキットに行こう!", + "グーフィー・サーキットでゲラゲラポイントをゲットしよう!", + "グーフィー・サーキットには、6つのレーストラックがあるよ。 " + ), + + TIP_STREET : ( + "コグにはロウボット、マネーボット、セルボット、ボスボットの4種類があるんだ。", + "各ギャグトラックは、「めいちゅうりつ」と「ダメージ」がそれぞれ決まっているよ。", + "「サウンド」ギャグは、すべてのコグにダメージをあたえるけど、「おとり」にはまったコグを逃してしまうよ。", + "よく考えて、戦略的な順番でコグをたおすと、バトルで勝つチャンスがすごく増えるよ。", + "「トゥーンアップ」はバトルの間、他のトゥーンのゲラゲラメーターを回復することができるよ。", + "ギャグのスキルポイントは、「コグ侵略中」だと2倍になるよ。", + "たくさんのトゥーンがバトルで同じギャグトラックを使うと、与えられるダメージが大きくなるよ。", + "バトル中のギャグは、メニューに表示されている順番で上から下の順で使われるよ。", + "コグに乗っ取られたビルのエレベーターの上のランプは、そのビルが何階建てかを示しているんだ。", + "コグをクリックすると、もっと詳しい情報が見れるよ。", + "高いレベルのギャグを低いレベルのコグに使っても、スキルレベルは増えないよ。", + "バトル中のギャグのメニューで青い背景のギャグは、スキルレベルをかせぐことができるよ。", + "コグのビルの中にいると、スキルレベルが増えるんだ。高い階ほど、たくさん増えるよ。", + "コグが倒されると、バトルが終わった時まわりにいるトゥーンたちはみなそのコグに対するクレジットがもらえるんだ。", + "トゥーンタウンの通りはそれぞれ、いろいろな種類やレベルのコグがまじっているよ。", + "歩道にいれば、コグから攻撃はされないよ。", + "通りでは、カンバンのないドアに近づくと、そのドアがジョークを言うんだ。", + "トゥーンタスクを完了するうちに、キミは新しいギャグトラックで練習することになるんだ。7つのギャグトラックのうち6つを選ぶことになるから、気をつけて選んでね!", + "「トラップ」は、バトルでキミかキミのともだちが「おとり」を使うようにする時だけ効果があるよ。", + "高いレベルの「おとり」は、成功する確率が高いよ。", + "低いレベルの「おとり」は、高いレベルのコグに対してあんまり効果がないよ。", + "バトルで「おとり」にはまっていたら、コグは攻撃できないんだ。", + "コグに乗っ取られたビルを取り戻すと、ごほうびで、キミたちが救ったトゥーンのビルの中に肖像画がかざられるよ。", + "ゲラゲラメーターがすでにいっぱいのトゥーンをトゥーンアップしても、体力は回復しないよ。", + "コグはどんなギャグにでも決まると簡単に気絶しちゃうんだ、だから同じラウンドで他のギャグが当たるチャンスが増えるんだよ。", + "「ドロップ」ギャグは当たる確率は低いけど、同じラウンドで他のギャグが決まると、より当たりやすくなるんだ。", + "コグをたくさん倒すと、トゥーンガイドのコグページを使って \"コグレーダー\"ができるようになるよ。", + "バトル中に仲間のトゥーンがどのコグを攻撃しているかは\"-\"や\"X\"マークが教えてくれるよ。", + "バトル中のコグ達の胸の場所にあるライトは、彼らの体力を表しているよ:\n緑は健康で、赤はこわれる寸前。", + "バトルで同時に戦えるのは、最大4人のトゥーンまでだからね。", + "ストリートでは、コグは一人のトゥーンよりも大勢のトゥーンたちのバトルに参加したがるよ。", + "コグの種類の中でも強い2体は乗っ取られたビルの中にしか出てこないよ。", + "「ドロップ」ギャグは「おとり」にハマったコグには通用しないよ。", + "コグは最も大きなダメージを与えたトゥーンを攻撃する傾向があるよ。", + "「サウンド」ギャグは「おとり」にハマったコグに対してはボーナスダメージが与えられないんだ。", + "「おとり」にハマったコグを長い時間待たせると、目覚めてしまうよ。レベルの高い「おとり」だと効果がより長く持続するよ。", + "池はトゥーンタウンのどのストリートにもあるよ。中には変わった魚もいるから色々ためしてみてね。", + ), + + TIP_MINIGAME : ( + "ジェリービーンのびんがいっぱいになると、トロリーゲームで稼いだジェリービーンは全部自動的にキミの銀行からこぼれでちゃうよ。", + "\"マッチミニーゲーム\"では、マウスのかわりにやじるしキーを使えるよ。", + "\"たいほうゲーム\"では、やじるしキーを使って大砲を動かして、コントロールキーを押すと発砲できるよ。", + "\"リングゲーム\"では、チームの全員がうきわをくぐって泳ぐのに成功するとボーナスポイントがもらえるよ。", + "\"マッチミニーゲーム\"でかんぺきな踊りをすると、ジェリービーンを2倍もらえるよ。", + "\"つなひきゲーム\"ではより強いコグに勝つと、より多いジェリービーンがもらえるよ。", + "トロリーゲームの難しさは遊ぶ場所によって変わるよ:トゥーンタウンセントラルが一番簡単でドナルドのドリームランドが一番難しいよ。", + "トロリーゲームのいくつかは仲間で参加しないと遊べないものがあるよ。", + ), + + TIP_COGHQ : ( + "コグへの変装をコンプリートしないと、ボスのビルに入れないよ!", + "警備兵の上にジャンプすると、しばらくの間、動きが止まるよ!", + "コグをたくさん倒して、コグのメリットを集めよう!", + "レベルの高いコグからは、より多くのメリットを手に入れることが出来るよ!", + "コグのメリットを集めると「格上げ」されて、セルボットのコグゼキュティブに会いに行けるようになるよ!", + "コグに変装しているときには、コグのように話すことが出来るよ!", + "セルボットのコグゼキュティブとのバトルには最大8トゥーンまで参加できるよ!", + "セルボットのコグゼキュティブは、コグ本部の一番上にいるよ!", + "コグ工場の中では、階段に沿っていくことで工場長の所までたどり着くことができるよ!", + "工場でのバトルごとに、コグへの変装パーツを1つ手に入れることができるよ!", + "トゥーンガイドでコグへの変装の度合いをチェックすることができるよ!", + "トゥーンガイドの変装のページで「メリット」の進行度合いをチェックできるよ!", + "コグゼキュティブに会うときには、ギャグとゲラゲラメーターがまんたんかどうかをちゃんとチェックしてね。", + "格上げされると、コグ変装グッズがアップデートされるよ。", + "工場長を倒さないとコグに変装するパーツを手に入れることはできないよ。", + "ドナルドのドリームランドでトゥーンタスクをやると、マネーボットのへんそうスーツがゲットできるよ!", + "マネーボットほんぶには、コイン・ドル・ゴールドの3つの工場があるよ。", + "マネーマネーがフラフラの時にきんこを投げないと、ヘルメットがわりにとられちゃうよ!きんこを当てて、ヘルメットをはじき飛ばそう!", + "バトルでロウボットを倒してショウカンジョーを集めよう。", + "レベルの高いコグを倒すとより多くのメリットが得られるよ。", + "ショウカンジョーを集めてじゅうぶん格上げされたら、ロウボット本部のサイバンチョーにちょうせんだ!", + "サイバンチョーにちょうせんするには、ロウボットのへんそうパーツがひつようだよ。", + "サンバンチョーには同時に8人までいっしょにちょうせんできるよ。", + "パズルにちょうせん!しっぱいするとバーチャル・コグがキミのショウカンをじゃまするよ。 ", + "", + "", + "", + "", + "", + "", + ), + TIP_ESTATE : ( + # Doodles #★ + "ドゥードゥルはスピードチャットのいくつかのセリフがわかるから試してみよう!", + "スピードチャットの\"ペット\"の項目からドゥードゥルに「トリック」をさせよう!", + "クララベルのショッピングカタログのアイテムでドゥードゥルに「トリック」を教えることができるぞ!", + "ドゥードゥルが「トリック」をしたら、ごほうびをあげよう!", + "友達のおうちに遊びにいくと、キミのドゥードゥルもついてくるよ。", + "ドゥードゥルが「くうふく」のときにはジェリービーンをあげよう。", + "ドゥードゥルをクリックすればパネルが開くよ。そうしたら、エサをあげたり、なでたり、呼んでみよう。", + "ドゥードゥルは集まるのが好き。友達を呼んで、一緒に遊ぼう!", + "全てのドゥードゥルはそれぞれ違う個性をもっているよ。", + "キミのドゥードゥルをペットショップに返して、新しいドゥードゥルを手に入れることもできるぞ。", + "ドゥードゥルが「トリック」をつかうと、そのまわりにいるトゥーンがトゥーンアップするぞ。", + "練習すればするほどドゥードゥルの「トリック」がうまくなるから、がんばってみよう。", + "上達したドゥードゥルの「トリック」は、トゥーンアップの効果も大きいぞ。", + "「トリック」をするとドゥードゥルは疲れるけど、経験のあるドゥードゥルはより多くできるぞ!", + "トゥーンの近くにいるドゥードゥルのリストは、ともだちリストの場所でみることができるぞ。", + # Furniture / Cattlelog + "クララベルのカタログから家具を買って、おうちのインテリアをコーディネートしよう。", + "貯ビーン箱にはもっとジェリービーンをたくわえこむことができるぞ。", + "クローゼットにはキミの着ていないようふくを入れておけるよ。たまには着替えて出かけよう!", + "ともだちのおうちに遊びに行ったら、ようふくを試着させてもらおう!", + "カタログからより良いつりざおを買って、変わった魚を釣ろう!", + "大きな貯ビーン箱を買えばもっとジェリービーンをとっておけるぞ!", + "クララベルに連絡したいときには、おうちの電話をつかってね。", + "より大きなクローゼットもクララベルのカタログから買うことができるよ。", + "「ようふく券」を使うときには、クローゼットに空きを作っておこう。", + "キミのおうちをおしゃれにする色々なアイテムを扱ってるよ!", + "クララベルに注文をしたら、キミのおうちの前のポストをチェックしてみよう!", + "カタログでようふくを注文すると、1時間でポストに届くよ。", + "かべ紙や床のフローリングは注文してから1時間かかるよ。", + "買った家具がおうちに届くには丸1日かかるよ。", + "屋根裏に余った家具をとっておこう。", + "クララベルから連絡があると、それは新しい商品カタログがついたってこと。", + "ショッピングカタログが届くと、クララベルから連絡がくるよ!", + "新しいショッピングカタログは毎週届くよ!", + "カタログのある時期にしか売ってないアイテムの中でも、1年中使えるものがあるから探してみよう!", + "いらない家具はごみ箱に移そう。", + # Fish + "ホーレーマカレルのようなサカナは、キミのおうちの近くの池のほうが、よく見かけるらしい。", + # Misc + "スピードチャットを使って、ともだちをキミのおうちに呼ぼう!", + "キミのおうちの色は最初にトゥーンを作ったときのパネルの色と同じだって知ってた?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "グーフィーのオートショップで、ロードスターやトゥーンヴィークルや、クルーザーを買おう。", + "グーフィーのオートショップで、キミのカートの色やパーツをカスタムしよう。", + "グーフィー・サーキットでレースをして、チケットをゲットしよう。", + "グーフィーのオートショップで買い物をする時は、チケットを使うんだ。", + "レース参加に使うデポジットは、レースのあとに返ってくるよ。", + "トゥーンガイドのステッカーブックで、キミのカートをカスタムしよう。", + "トゥーンガイドのステッカーブックで、キミのカートでのベストラップがみれるよ。", + "トゥーンガイドのステッカーブックで、キミがレースでゲットしたトロフィーが見れるよ。", + "スクリュースタジアムがグーフィー・サーキットで一番かんたんなコースだよ。", + "エアボーン・エーカースはグーフィー・サーキットで一番ジャンプが多いコースだよ。", + "ブリザード・ブルバードはグーフィー・サーキットで一番むずかしいんだ。", + ), + TIP_GOLF: ( + # Golfing specific + "コースを上から見る時はTabキーを押してね。", + "パットの方向をカップにまっすぐにするには、上矢印(↑)キーを押してね。", + "クラブのスイングのコントロールはパイ投げと同じだよ。", + ), + } + +FishGenusNames = { + 0 : "バルーン フィッシュ", + 2 : "キャット フィッシュ", + 4 : "クラウン フィッシュ", + 6 : "フローズン フィッシュ", + 8 : "スター フィッシュ", + 10 : "ホーレー マカレル", + 12 : "ドッグ フィッシュ", + 14 : "アモーレ イール", + 16 : "ナース シャーク", + 18 : "キング クラブ", + 20 : "ムーン フィッシュ", + 22 : "シー ホース", + 24 : "プール シャーク", + 26 : "ベア アキューダ", + 28 : "カットスロート トラウト", + 30 : "ピアノ ツナ", + 32 : "PB&J フィッシュ", + 34 : "デビル レイ", + } + +FishSpeciesNames = { + 0 : ( "バルーン フィッシュ", + "ホットエアー バルーン フィッシュ", + "ウェザー バルーン フィッシュ", + "ウォーター バルーン フィッシュ", + "レッド バルーン フィッシュ", + ), + 2 : ( "キャット フィッシュ", + "シャム キャット フィッシュ", + "アレー キャット フィッシュ", + "タビー キャット フィッシュ", + "トム キャット フィッシュ", + ), + 4 : ( "クラウン フィッシュ", + "サッド クラウン フィッシュ", + "パーティー クラウン フィッシュ", + "サーカス クラウン フィッシュ", + ), + 6 : ( "フローズン フィッシュ", + ), + 8 : ( "スター フィッシュ", + "ファイブ スター フィッシュ", + "ロック スター フィッシュ", + "シャイニング スター フィッシュ", + "オール スター フィッシュ", + ), + 10 : ( "ホーレー マカレル", + ), + 12 : ( "ドッグ フィッシュ", + "ブル ドッグ フィッシュ", + "ホット ドッグ フィッシュ", + "ダルメシア ドッグ フィッシュ", + "パピー ドッグ フィッシュ", + ), + 14 : ( "アモーレ イール", + "エレクトリック アモーレ イール", + ), + 16 : ( "ナース シャーク", + "クララ ナース シャーク", + "フローレンス シャーク ", + ), + 18 : ( "キング クラブ", + "アラスカ キング クラブ", + "オールド キング クラブ", + ), + 20 : ( "ムーン フィッシュ", + "フルムーン フィッシュ", + "ハーフムーン フィッシュ", + "ニュームーン フィッシュ", + "クレセントムーン フィッシュ", + "ハーベストムーン フィッシュ", + ), + 22 : ( "シー ホース", + "ロッキング シー ホース", + "クライズデール シー ホース", + "アラビアン シー ホース", + ), + 24 : ( "プール シャーク", + "キディー プール シャーク", + "スイミング プール シャーク", + "オリンピック プール シャーク", + ), + 26 : ( "ブラウン ベアー アキューダ", + "ブラック ベアー アキューダ", + "コアラ ベアー アキューダ", + "ハニー ベアー アキューダ", + "ポーラー ベアー アキューダ", + "パンダ ベアー アキューダ", + "コディアック ベアー アキューダ", + "グリズリー ベアー アキューダ", + ), + 28 : ( "カットスロート トラウト", + "キャプテン カットスロート トラウト", + "スカービー カットスロート トラウト", + ), + 30 : ( "ピアノ ツナ", + "グランドピアノ ツナ", + "ベビー グランドピアノ ツナ", + "アップライト ピアノ ツナ", + "プレイヤー ピアノ ツナ", + ), + 32 : ( "PB&J フィッシュ", + "グレープ PB&J フィッシュ", + "クランチー PB&J フィッシュ", + "ストロベリー PB&J フィッシュ", + "コンコルド グレープ PB&J フィッシュ", + ), + 34 : ( "デビル・レイ", + ), + } + +CogPartNames = ( + "左レッグ①", "左レッグ②", "左フット", + "右レッグ①", "右レッグ②", "右フット", + "左ショルダー", "右ショルダー", "ボディ①", "コグメーター", "ボディ②", + "左アーム①", "左アーム②", "左ハンド", + "右アーム①", "右アーム②", "右ハンド", + ) + +CogPartNamesSimple = ( + "ボディ上", + ) + +FishFirstNames = ( + "", + "エンジェル", + "アーティック", + "ベイビー", + "バミューダ", + "ビッグ", + "ブルーク", + "バブルス", + "バスター", + "キャンディー", + "キャプテン", + "チップ", + "チャブ", + "コーラル", + "ドクター", + "ダスティー", + "エンペラー", + "ファングス", + "ファット", + "フィッシー", + "フリッパー", + "フラウンダー", + "フレックル", + "ハニー", + "ジャック", + "キング", + "リトル", + "マーリン", + "ミス", + "ミスター", + "ピーチ", + "ピンキー", + "プリンス", + "プリンセス", + "プロフェッサー", + "パフィー", + "クィーン", + "レインボー", + "レイ", + "ロージー", + "ラスティー", + "ソルティー", + "サム", + "サンディー", + "スケールス", + "シャーキー", + "サー", + "スキッピー", + "スキッパー", + "スナッパー", + "スペック", + "スパイク", + "スポッティー", + "スター", + "シュガー", + "スーパー", + "タイガー", + "タイニー", + "ウィスカーズ", + ) + +FishLastPrefixNames = ( + "", + "ビーチ", + "ブラック", + "ブルー", + "ボア", + "ブル", + "キャット", + "ディープ", + "ダブル", + "イースト", + "ファンシー", + "フレーキー", + "フラット", + "フレッシュ", + "ジャイアント", + "ゴールド", + "ゴールデン", + "グレイ", + "グリーン", + "ホグ", + "ジャバー", + "ジェリー", + "レディー", + "レザー", + "レモン", + "ロング", + "ノーザン", + "オーシャン", + "オクト", + "オイル", + "パール", + "パフ", + "レッド", + "リボン", + "リバー", + "ロック", + "ルビー", + "ラダー", + "ソルト", + "シー", + "シルバー", + "シュノーケル", + "ソール", + "サザン", + "スパイキー", + "サーフ", + "ソード", + "タイガー", + "トリプル", + "トロピカル", + "ツナ", + "ウェーブ", + "ウィーク", + "ウエスト", + "ホワイト", + "イエロー", + ) + +FishLastSuffixNames = ( + "", + "ボール", + "バス", + "ベリー", + "バグ", + "バーグラー", + "バター", + "クロー", + "コブラー", + "クラブ", + "クローカー", + "ドラム", + "フィン", + "フィッシュ", + "フラッパー", + "フリッパー", + "ゴースト", + "グラント", + "ヘッド", + "ジャケット", + "ジャンパー", + "マカレル", + "ムーン", + "マウス", + "ミュレー", + "ネック", + "ノーズ", + "パーチ", + "ラフィー", + "ランナー", + "セイル", + "シャーク", + "シェル", + "シルク", + "スライム", + "スナッパー", + "スティンク", + "テイル", + "トード", + "トラウト", + "ウォーター", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "メインゲート" +SellbotLegFactorySpecLobby = "ロビー" +SellbotLegFactorySpecLobbyHallway = "廊下" +SellbotLegFactorySpecGearRoom = "ギヤルーム" +SellbotLegFactorySpecBoilerRoom = "ボイラールーム" +SellbotLegFactorySpecEastCatwalk = "東の通路" +SellbotLegFactorySpecPaintMixer = "ペンキルーム" +SellbotLegFactorySpecPaintMixerStorageRoom = "ペンキルーム倉庫" +SellbotLegFactorySpecWestSiloCatwalk = "西タワー通路" +SellbotLegFactorySpecPipeRoom = "パイプルーム" +SellbotLegFactorySpecDuctRoom = "ダクトルーム" +SellbotLegFactorySpecSideEntrance = "サイドゲート" +SellbotLegFactorySpecStomperAlley = "プレスルーム" +SellbotLegFactorySpecLavaRoomFoyer = "ヨウガンルーム" +SellbotLegFactorySpecLavaRoom = "ヨウガンルーム" +SellbotLegFactorySpecLavaStorageRoom = "ヨウガンルーム倉庫" +SellbotLegFactorySpecWestCatwalk = "西の通路" +SellbotLegFactorySpecOilRoom = "オイルルーム" +SellbotLegFactorySpecLookout = "見張り台" +SellbotLegFactorySpecWarehouse = "倉庫" +SellbotLegFactorySpecOilRoomHallway = "オイルルーム入口" +SellbotLegFactorySpecEastSiloControlRoom = "東コントロールルーム" +SellbotLegFactorySpecWestSiloControlRoom = "西コントロールルーム" +SellbotLegFactorySpecCenterSiloControlRoom = "工場長の部屋" +SellbotLegFactorySpecEastSilo = "東タワー屋上" +SellbotLegFactorySpecWestSilo = "西タワー屋上" +SellbotLegFactorySpecCenterSilo = "メインタワー屋上" +SellbotLegFactorySpecEastSiloCatwalk = "東タワー通路" +SellbotLegFactorySpecWestElevatorShaft = "西エレベーター" +SellbotLegFactorySpecEastElevatorShaft = "東エレベーター" + +#FISH BINGO +FishBingoBingo = "ビンゴ!" +FishBingoVictory = "やったね!" +FishBingoJackpot = "ジャックポット!" +FishBingoGameOver = "ゲームオーバー" +FishBingoIntermission = "お休み終了まで\nあと" +FishBingoNextGame = "ゲーム開始まで\nあと" +FishBingoTypeNormal = "クラシック" +FishBingoTypeCorners = "4コーナー" +FishBingoTypeDiagonal = "ななめ" +FishBingoTypeThreeway = "3ウェイ!" +FishBingoTypeBlockout = "ブロックアウト!" +FishBingoStart = "「魚でビンゴ!」の時間です! 遊びたい人は好きな橋に立ってね。" +FishBingoOngoing = "フィッシュビンゴをかいさい中だよ!" +FishBingoEnd = "魚でビンゴ!、楽しかった?" +FishBingoHelpMain = "「魚でビンゴ!」へようこそ! 時間内にみんなで協力して、ビンゴカードをマークしよう!" +FishBingoHelpFlash = "魚を釣ったら、点滅している場所のひとつをクリックしてカードにマークしてね。" +FishBingoHelpNormal = "これは普通のビンゴカードだよ。たて、よこ、ななめ一列にマークがつけば勝ち!" +FishBingoHelpDiagonals = "ななめに2本、バッテンになるようにマークしたら勝ち!" +FishBingoHelpCorners = "簡単なコーナーカード。4つのコーナーをマークしたら勝ち!" +FishBingoHelpThreeway = "3ウェイ! ななめ2本と真ん中の横ラインをマークしたら勝ち!なかなか難しいぞ!" +FishBingoHelpBlockout = "ブロックアウト! 全ての場所をマークすれば勝ち。他の全ての池にいるトゥーンと競ってジャックポットを目指そう!" +FishBingoOfferToSellFish = "キミのバケツが一杯だよ。魚を売りますか?" +FishBingoJackpotWin = "%s ジェリービーン ゲット!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "トゥーンアップ" +ResistanceToonupItem = "%s トゥーンアップ" +ResistanceToonupItemMax = "さいだい" +ResistanceToonupChat = "トゥーン最高!トゥーンアップ!" #▲ +ResistanceRestockMenu = "ギャグアップ" +ResistanceRestockItem = "%s ギャグアップ" +ResistanceRestockItemAll = "すべて" +ResistanceRestockChat = "笑って!ギャグアップ!" #▲ +ResistanceMoneyMenu = "ジェリービーン" +ResistanceMoneyItem = "ジェリービーン %s個" +ResistanceMoneyChat = "かしこく使おう!トゥーン最高!" #▲ + +# Resistance Emote NPC chat phrases #localize +ResistanceEmote1 = NPCToonNames[9228] + ": レジスタンスへようこそ!" +ResistanceEmote2 = NPCToonNames[9228] + ": 新しい'きもち'を使ってじぶんをひょうげんしてみよう。" +ResistanceEmote3 = NPCToonNames[9228] + ": がんばってね!" + +# Kart racing +KartUIExit = "おりる" +KartShop_Cancel = lCancel +KartShop_BuyKart = "かう" +KartShop_BuyAccessories = "アクセサリーをかう" +KartShop_BuyAccessory = "アクセサリーをかう" +KartShop_Cost = "ねだん: %d チケット" +KartShop_ConfirmBuy = "%sを%dチケットでかう?" +KartShop_NoAvailableAcc = "この種類のアクセサリーはダメだよ。" +KartShop_FullTrunk = "キミのトランクがいっぱいだよ。" +KartShop_ConfirmReturnKart = "本当にキミのカートを返してもいい?" +KartShop_ConfirmBoughtTitle = "おめでとう!" +KartShop_NotEnoughTickets = "じゅうぶんなチケットがありません。" + +KartView_Rotate = "かいてん" +KartView_Right = "右" +KartView_Left = "左" + +# starting block +StartingBlock_NotEnoughTickets = "チケットが足りないよ。かわりにれんしゅうのレースに出よう!" +StartingBlock_NoBoard = "今回のレースは申し込みがおわったよ。次のレースまで待っててね。" +StartingBlock_NoKart = "まずはカートが必要だね!カートショップのてんいんに聞いてみよう。" +StartingBlock_Occupied = "この場所はすでにうまっています。ほかの場所をためしてね。" +StartingBlock_TrackClosed = "ごめんね、このレーストラックは工事中です。" +StartingBlock_EnterPractice = "れんしゅうのレースに出ますか?" +StartingBlock_EnterNonPractice = "%sのレースにチケット%s枚で参加しますか?" +StartingBlock_EnterShowPad = "ここにキミのカートを止めますか?" +StartingBlock_KickSoloRacer = "トゥーンバトルレースはひとりではできないよ。" +StartingBlock_Loading = "レースに行く!" + +#stuff for leader boards +LeaderBoard_Time = "タイム" +LeaderBoard_Name = "レーサー名" +LeaderBoard_Daily = "ほんじつのベストタイム" +LeaderBoard_Weekly = "しゅうかんベストタイム" +LeaderBoard_AllTime = "ベストタイムのでんどう" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "れんしゅう", + "トゥーンバトル", + "トーナメント", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "スタート!" +KartRace_Reverse = "リバース " + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "スクリュースタジアム", + RaceGlobals.RT_Speedway_1_rev : KartRace_Reverse + "スクリュースタジアム", + RaceGlobals.RT_Rural_1 : "さびさびレースウェイ", + RaceGlobals.RT_Rural_1_rev : KartRace_Reverse + "さびさびレースウェイ", + RaceGlobals.RT_Urban_1 : "シティーサーキット", + RaceGlobals.RT_Urban_1_rev : KartRace_Reverse + "シティーサーキット", + RaceGlobals.RT_Speedway_2 : "きりもみコロシアム", + RaceGlobals.RT_Speedway_2_rev : KartRace_Reverse + "きりもみコロシアム", + RaceGlobals.RT_Rural_2 : "エアボーンエーカース", + RaceGlobals.RT_Rural_2_rev : KartRace_Reverse + "エアボーンエーカース", + RaceGlobals.RT_Urban_2 : "ブリザードブルバード", + RaceGlobals.RT_Urban_2_rev : KartRace_Reverse + "ブリザードブルバード", + } + +KartRace_Unraced = "N/A" + +KartDNA_KartNames = { + 0:"クルーザー", + 1:"ロードスター", + 2:"トゥーンビークル" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "エアークリーナー", + 1001: "4バレル", + 1002: "フライングイーグル", + 1003: "ステア ホーン", + 1004: "ストレート6", + 1005: "スモール スクープ", + 1006: "シングル オーバーヘッド", + 1007: "ミディアム スクープ", + 1008: "シングル バレル", + 1009: "フラグル ホーン", + 1010: "ストライプ スクープ", + #spoiler accessory names + 2000: "スペース ウィング", + 2001: "ツギハギ スペア", + 2002: "ロール ケージ", + 2003: "シングル フィン", + 2004: "ダブルデッカー ウィング", + 2005: "シングル ウイング", + 2006: "スタンダード スペア", + 2007: "シングル フィン", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "ケットー ホーン", + 3001: "フレディー フェンダー", + 3002: "コバルト ステップ", + 3003: "コブラ サイドパイプ", + 3004: "ストレート サイドパイプ", + 3005: "ホタテ フェンダー", + 3006: "カーボン ステップ", + 3007: "ウッド ステップ", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "クルクル テールパイプ", + 4001: "スプラッシュ フェンダー", + 4002: "デュアル エグゾースト", + 4003: "プレーン デュアルフィン", + 4004: "プレーン ドロヨケ", + 4005: "クアッド エグゾースト", + 4006: "デュアル フレアー", + 4007: "メガ エグゾースト", + 4008: "ストライプ デュアルフィン", + 4009: "バブル デュアルフィン", + 4010: "ストライプ ドロヨケ", + 4011: "ミッキー ドロヨケ", + 4012: "ホタテ ドロヨケ", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "ターボ", + 5001: "ムーン", + 5002: "ツギハギ", + 5003: "3スポーク", + 5004: "ペイントリッド", + 5005: "ハート", + 5006: "ミッキー", + 5007: "5ボルト", + 5008: "デイジー", + 5009: "バスケットボール", + 5010: "ヒプノ", + 5011: "トライバル", + 5012: "ジェムストーン", + 5013: "5スポーク", + 5014: "ノックオフ", + #decal accessory names + 6000: "ナンバー5", + 6001: "スプラッター", + 6002: "チェッカーボード", + 6003: "フレイム", + 6004: "ハーツ", + 6005: "バブルス", + 6006: "タイガー", + 6007: "フラワー", + 6008: "ライトニング", + 6009: "エンジエル", + #paint accessory names + 7000: "シャルトルーズ", + 7001: "ピーチ", + 7002: "ブライトレッド", + 7003: "レッド", + 7004: "マルーン", + 7005: "シエナ", + 7006: "ブラウン", + 7007: "タン", + 7008: "コーラル", + 7009: "オレンジ", + 7010: "イエロー", + 7011: "クリーム", + 7012: "シトリーン", + 7013: "ライム", + 7014: "シーグリーン", + 7015: "グリーン", + 7016: "ライトブルー", + 7017: "アクア", + 7018: "ブルー", + 7019: "ペリウィンクル", + 7020: "ロイヤルブル-", + 7021: "スレートブルー", + 7022: "パープル", + 7023: "ラベンダー", + 7024: "ピンク", + 7025: "プラム", + 7026: "ブラック", + } + +RaceHoodSpeedway = "スピードウェイ" +RaceHoodRural = "なごやか" +RaceHoodUrban = "アーバン" +RaceTypeCircuit = "トーナメント" +RaceQualified = "よせんつうか" +RaceSwept = "ぜんしょう!" +RaceWon = "かち" +Race = "レース" +Races = "レース" +Total = "合計" +GrandTouring = "グランドツーリング" + +def getTrackGenreString(genreId): + genreStrings = [ "Speedway", + "Country", + "City" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + str(RaceGlobals.TrophiesPerCup * 2) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + str(RaceGlobals.TrophiesPerCup * 3) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + ] + +KartRace_TitleInfo = "レースの準備はいいかな?" +KartRace_SSInfo = "スクリュースタジアムへようこそ!\nエンジンをふかして、ハンドルをにぎりしめて!\n" +KartRace_CoCoInfo = "きりもみコロシアムにようこそ!\nスピードを落さないように、バンクをうまく使ってね。\n" +KartRace_RRInfo = "さびさびレースウェイへようこそ!\nコースをよーく見て!ライバルにおてやわらかに!\n" +KartRace_AAInfo = "エアボーン・エーカースにようこそ!\nアップ・ダウンのはげしいコースに注意してね!\n" +KartRace_CCInfo = "シティーサーキットへようこそ!\nダウンタウンを通りぬけるときには、ほこうしゃに気をつけて!\n" +KartRace_BBInfo = "ブリザード・ブルバードにようこそ!\nスピード出しすぎ注意!道路がこおってるかも!?\n" +KartRace_GeneralInfo = "方向キーでカートをコントロールしよう!コース上でひろったギャグはコントロールキーで投げられるよ!" + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'ほんじつの', + RaceGlobals.Weekly : 'こんしゅうの', + RaceGlobals.AllTime : 'れきだいの', + } + +KartRace_FirstSuffix = '位' +KartRace_SecondSuffix = '位' +KartRace_ThirdSuffix = '位' +KartRace_FourthSuffix = '位' +KartRace_WrongWay = '逆方向!' +KartRace_LapText = "ラップ %s" +KartRace_FinalLapText = "ファイナルラップ!" +KartRace_Exit = "レースしゅうりょう" +KartRace_NextRace = "次のレース" +KartRace_Leave = "レースをやめる" +KartRace_Qualified = "よせんつうか!" +KartRace_Record = "しんきろく!" +KartRace_RecordString = '%sしんきろく!\n%sから\nチケット%s枚のボーナス!' +KartRace_Tickets = "チケット" +KartRace_Exclamations = "!" +KartRace_Deposit = "デポジット" +KartRace_Winnings = "しょうり" +KartRace_Bonus = "ボーナス" +KartRace_RaceTotal = "レーストータル" +KartRace_CircuitTotal = "サーキットトータル" +KartRace_Trophies = "トロフィー" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s " + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "よせんつうか:\n" +KartRace_RaceTimeout = "タイムアップ!キミのチケットはもどったよ。がんばって!" +KartRace_RaceTimeoutNoRefund = "じかんぎれです。グランプリはもう始まってしまったからチケットはもどらないよ。次は頑張ってね!" +KartRace_RacerTooSlow = "ざんねん!じかんぎれです。デポジットはかえってこないけど、あきらめずにがんばってね!" +KartRace_PhotoFinish = "フォト・フィニッシュ!" +KartRace_CircuitPoints = 'サーキットポイント' + +CircuitRaceStart = "グーフィー・サーキットでトゥーンタウン・グランプリが始まるよ!3つのレースにさんかして、一番ポイントをゲットしたらチャンピオンに!!" +CircuitRaceOngoing = "トゥーンタウン・グランプリをかいさい中だよ!" +CircuitRaceEnd = "本日のトゥーンタウン・グランプリは終了しました。また来週月曜日に!" + +# Trick-or-Treat holiday +TrickOrTreatMsg = 'キミはすでにこのトリートを\nみつけているよ!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "ふーむ。今日のサイバンリストは…" +LawbotBossTempIntro1 = "ほー。トゥーンがサイバンに来ているのか!" +LawbotBossTempIntro2 = "ケンサツ側は強いぞ。" +LawbotBossTempIntro3 = "そしてこちらがベンゴニン。" +LawbotBossTempIntro4 = "ちょっと待てよ…おまえらもトゥーンじゃないか!" +LawbotBossTempJury1 = "バイシンインの選定を始めます。" +LawbotBossHowToGetEvidence = "ショウコを手に入れるにはショウゲンダイに触れてください。" +LawbotBossTrialChat1 = "これからサイバンを始めます。" +LawbotBossHowToThrowPies = "Insertキーを押すと、ベンゴシや「はかり」に向けてショウコを投げることができるよ!" +LawbotBossNeedMoreEvidence = "もっとショウコが必要だよ!" +LawbotBossDefenseWins1 = "ありえない!ヒコクニンが勝つなんて!" +LawbotBossDefenseWins2 = "いや。このハンケツは間違っている!もう一度、新しいサイバンを開こう。" +LawbotBossDefenseWins3 = "はぁ~っ。疲れたので部屋で休むとするか。" +LawbotBossProsecutionWins = "ケンサツ側の勝ちとします。" +LawbotBossReward = "格上げとコグをショウカンできるようになったぞ。" +LawbotBossLeaveCannon = "キャノンから去る" +LawbotBossPassExam = "ほう、シホウシケンを通ったのか。" +LawbotBossTaunts = [ + "%s、ホウテイブジョクザイにあたりますぞ!", + "イギをみとめます!", + "今の内容を記録から消すように。", + "訴えをキャッカします。あなたに悲しみをセンコクします!", + "ホウテイのルールを守るように!", + ] +LawbotBossAreaAttackTaunt = "ホウテイをブジョクするつもりか!" + +WitnessToonName = "バンピー バンブルベア" +WitnessToonPrepareBattleTwo = "な、なんと。コグのバイシンインしかいないじゃないか!\a急いで、キャノンを使ってトゥーンのバイシンインを送り込もう!\a「はかり」を合わせるのに%d人、必要だよ。" +WitnessToonNoJuror = "がーん。トゥーンのバイシンインが一人もいない!これではサイバンが大変になるぞ。" +WitnessToonOneJuror = "よかった!トゥーンのバイシンインが一人いるね。" +WitnessToonSomeJurors = "いーねー!トゥーンのバイシンインが%d人いるね。" +WitnessToonAllJurors = "すごいね!全員、トゥーンのバイシンインだね。" +WitnessToonPrepareBattleThree = "急いで、ショウゲンダイを触ってショウコをゲットしてね。\aベンゴシや「はかり」に向けてショウコを投げるにはInsertキーを押してね!" +WitnessToonCongratulations = "やったね! すばらしいベンゴだったね。\aサイバンチョーが残していった書類を受け取って!\aこれがあれば、トゥーンガイドのコグのページでコグをショウカンできるぞ。" + +WitnessToonLastPromotion = "\aワオ!コグスーツのレベルが%sになったよ。\aこれ以上は「格上げ」できないんだ。\aでも是非、レジスタンスのためにこれからもがんばってください!" +WitnessToonHPBoost = "\aキミはレジスタンスのために本当に一杯がんばってくれているね。\aトゥーン評議会がキミのゲラゲラポイントをアップさせるよ。おめでとう!" +WitnessToonMaxed = "\aキミはレベル%sのコグのスーツを着ているね。とてもいいよ!\aまた、トゥーンを助けるために戻ってきてくれたことに、トゥーン評議会にかわって、お礼します!" +WitnessToonBonus = "ワンダフル!全てのベンゴシがキゼツしたよ。キミのショウコの重さが%s倍になるぞ!(%s秒間)" + +WitnessToonJuryWeightBonusSingular = { + 6: 'これは大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 7: 'これはとっても大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 8: 'これは今までにない大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'これは大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 7: 'これはとっても大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 8: 'これは今までにない大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', +} + +# Cog Summons stuff +IssueSummons = "ショウカン" +SummonDlgTitle = "ショウカンジョーを出す" +SummonDlgButton1 = "コグをショウカン" +SummonDlgButton2 = "コグビルをショウカン" +SummonDlgButton3 = "コグの侵略をショウカン" +SummonDlgSingleConf = "%sをショウカンしますか?" +SummonDlgBuildingConf = "トゥーンビルの近くで%sをショウカンしますか?" +SummonDlgInvasionConf = "%sの侵略をショウカンしますか?" +SummonDlgNumLeft = "あと%s、残ってます" +SummonDlgDelivering = "ショウカン中…" +SummonDlgSingleSuccess = "コグのショウカンに成功しました。" +SummonDlgSingleBadLoc = "ごめんね。ここではコグは呼べません。別の場所を試してね。" +SummonDlgBldgSuccess = "コグのショウカンに成功しました。 %sが%sを乗っ取ります。" +SummonDlgBldgSuccess2 = "コグのショウカンに成功しました。店主もちょっとだけコグに乗っ取ることをOKしました!" +SummonDlgBldgBadLoc = "ごめんね!コグが乗っ取れるトゥーンビルが近くにありません。" +SummonDlgInvasionSuccess = "コグのショウカンに成功しました。コグの侵略がはじまった!" +SummonDlgInvasionBusy = "%sが見つかりません。コグの侵略が終わったら、もう一度試してね。" +SummonDlgInvasionFail = "ごめんね。コグの侵略が失敗しました。" +SummonDlgShopkeeper = "店主 " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": ポーラープレイスへようこそ!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": ちょっとこれを着てサイズを見てくれる?" +PolarPlaceEffect3 = NPCToonNames[3306] + ": このかっこうは " + lTheBrrrgh + "でしか、着れないけどねー" + +# LaserGrid game Labels +LaserGameMine = "ガイコツを探せ!" +LaserGameRoll = "マッチゲーム!" +LaserGameAvoid = "ガイコツを避けてボタンへ!" +LaserGameDrag = "同じ色を3つ並べよう!" +LaserGameDefault = "知らないゲーム" + +# Pinball text +#PinballHiScore = "ハイスコア: %d %s\n" +#PinballYourBestScore = "ベストスコア: %d\n" +#PinballScore = "スコア: %d x %d:%d" +PinballHiScore = "ハイスコア: %s\n" +PinballHiScoreAbbrev = "…" +PinballYourBestScore = "ベストスコア: \n" +PinballScore = "スコア: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "ちょいギャグの木" +GagTreeJugglingBalls = "ジャグリングボール" +StatuaryFountain = "噴水" +StatuaryToonStatue = "トゥーンの像" +StatuaryDonald = "ドナルドの像" +StatuaryMinnie = "ミニーの像" +StatuaryMickey1 = "ミッキーの像1" +StatuaryMickey2 = "ミッキーの像2" +StatuaryToon = "トゥーンの像" +StatuaryToonWave = "手をふる像" +StatuaryToonVictory = "勝利の像" +StatuaryToonCrossedArms = '権威の像' +StatuaryToonThinking = '喜びの像' +StatuaryMeltingSnowman = 'とける雪だるま像' +StatuaryGardenAccelerator = "ヨクソダーツ" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['レッド','オレンジ','バイオレット','ブルー','ピンク','イエロー','ホワイト','グリーン'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'デイジー', + 50: 'チューリップ', + 51: 'カーネーション', + 52: 'リリー', + 53: 'ダフォディル', + 54: 'パンジー', + 55: 'ペチュニア', + 56: 'ローズ', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('スクールデイジー', + 'レイジーデイジー', + 'サマーデイジー', + 'リフレッシュデイジー', + 'ウーピーデイジー', + 'アップジーデイジー', + 'クレイジーデイジー', + 'ヘイジーデイジー', + ), + 50: ('ワンリップ', + 'ツーリップ', + 'スリーリップ', + ), + 51: ('ワットインカーネーション', + 'インスタントカーネーション', + 'ハイブリッドカーネーション', + 'サイドカーネーション', + 'モデルカーネーション', + ), + 52: ('リリーオブジアリー', + 'リリーパッド', + 'タイガーリリー', + 'リバードリリー', + 'チリーリリー', + 'シリーリリー', + 'タブリリー', + 'ディリーリリー', + ), + 53: ('ラフォディル', + 'ダフィーディル', + 'ジラフォディル', + 'タイムアンドアハーフォディル', + ), + 54: ('ダンディーバンジー', + 'チンパンジー', + 'ポッツェンパンジー', + 'マルチパンジー', + 'スマーティーパンジー' + ), + 55: ('カーペチュニア', + 'プラトーニア', + ), + 56: ("サマーズラストローズ", + 'コーンローズ', + 'ティントローズ', + 'スティンキングローズ', + 'イスティラローズ', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "(スズ)", + 1 : "(銅)", + 2 : "(銀)", + 3 : "(金)", + } +WateringCanNameDict = { + 0 : "(小)", + 1 : "(中)", + 2 : "(大)", + 3 : "(特大)", + } +GardeningPlant = "植物" +GardeningWater = "水" +GardeningRemove = "やめる" +GardeningPick = "しゅう\nかく" +GardeningFull = "いっぱい" +GardeningSkill = "スキル" +GardeningWaterSkill = "水スキル" +GardeningShovelSkill = "ショベルスキル" +GardeningNoSkill = "スキルアップなし" +GardeningPlantFlower = "花を\nうえる" +GardeningPlantTree = "木を\nうえる" +GardeningPlantItem = "アイテムを\nうえる" +PlantingGuiOk = "うえる" +PlantingGuiCancel = "キャンセル" +PlantingGuiReset = "リセット" +GardeningChooseBeans = "うえたいジェリービーンを選んでね。" +GardeningChooseBeansItem = "うえたいジェリービーンか\nアイテムを選んでね。" +GardeningChooseToonStatue = "キミのトゥーンで像を作れるよ。どのトゥーンで作る?" +GardenShovelLevelUp = "おめでとう!%(shovel)sをゲット! %(oldbeans)dのビーンフラワーをマスターしました。次に進むには%(newbeans)dのビーンフラワーを取ろう。" +GardenShovelSkillLevelUp = "おめでとう!%(oldbeans)dのビーンフラワーをマスターしました。次に進むには%(newbeans)dのビーンフラワーを取ろう。" +GardenShovelSkillMaxed = "すごいねー!キミのショベルスキルが最大になったよ!" + +GardenWateringCanLevelUp = "おめでとう!あたらしいジョウロをゲットしたよ!" +GardenMiniGameWon = "やったね!ちゃんと水をあげられたね!" +ShovelTin = "ショベル(スズ)" +ShovelSteel = "ショベル(銅)" +ShovelSilver = "ショベル(銀)" +ShovelGold = "ショベル(金)" +WateringCanSmall = "ジョウロ(小)" +WateringCanMedium = "ジョウロ(中)" +WateringCanLarge = "ジョウロ(大)" +WateringCanHuge = "ジョウロ(特大)" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('レッド', 'グリーン', 'オレンジ','バイオレット','ブルー','ピンク','イエロー', + 'シアン','シルバー') +PlantItWith = "%sを与える" +MakeSureWatered = " まずきちんとすべての植木に水を与えましょう。" +UseFromSpecialsTab = "ガーデンページのスペシャルのタブから使いましょう。" +UseSpecial = "スペシャルを使う" +UseSpecialBadLocation = 'キミのガーデンでしか使えません。' +UseSpecialSuccess = 'うまくいったね!植木が育ちました。' +ConfirmWiltedFlower = "%(plant)sがしぼんでいます。バスケットにも残らないし、スキルも上がりません。" +ConfirmUnbloomingFlower = "まだ%(plant)sは咲いてませんが、つみとりますか?バスケットにも残らないしスキルも上がりません。" +ConfirmNoSkillupFlower = "%(plant)sをつみとって、キミの花のバスケットに入れますか?残念だけどスキルは上がらないよ。" +ConfirmSkillupFlower = "%(plant)sをしゅうかくして、キミの花のバスケットに入れますか?またスキルが上がるよ。" +ConfirmMaxedSkillFlower = "%(plant)sをしゅうかくして、キミの花のバスケットに入れますか?もうスキルが最大だからスキルは上がらないよ。" +ConfirmBasketFull = "キミのバスケットは一杯だよ。花を売るにはテオシグルマに行こう。" +ConfirmRemoveTree = "%(tree)sを抜いてもいいかな?" +ConfirmWontBeAbleToHarvest = "もしこの木を抜くと、高いレベルの木からギャグを育てることができなくなるよ。" +ConfirmRemoveStatuary = "本当に%(item)sがなくなるけど、いいかな??" +ResultPlantedSomething = "おめでとう!%sを植えました。" +ResultPlantedSomethingAn = "おめでとう!%sを植えました。" +ResultPlantedNothing = "うまくいかなかったね。違うジェリービーンの組み合わせを試してね。" + +GardenGagTree = "ギャグの木" +GardenUberGag = "レベル7ギャグ" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s ジェリービーン" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "a %s ジェリービーン" % BeanColorWords[beanTuple[0]] + else: + retval += 'a' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " そして %s ジェリービーン" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "マジックビーン" +GardenTextMagicBeansB = "ふつうのビーン" +GardenSpecialDiscription = "この文章はどのようにガーデンスペシャルを使うかを説明するものです。" +GardenSpecialDiscriptionB = "この文章はどのようにガーデンスペシャルを使うかを説明するものです。" +GardenTrophyAwarded = "ワオ!花、%s輪(%s輪のうち)をゲット!トロフィーとゲラゲラポイントアップ!" +GardenTrophyNameDict = { + 0 : "テオシグルマ", + 1 : "ショベル", + 2 : "フラワー", + 3 : "ジョウロ", + 4 : "サメ", + 5 : "メカジキ", + 6 : "シャチ", + } +SkillTooLow = "スキルが\n足りません" +NoGarden = "ガーデンが\nありません" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "『ターン・テーブル』" +TravelGameInstructions = "キミのヒミツのゴールにたどり着くようにとうひょう数を決めよう。決めたら“とうひょう”ボタンをクリック。うまくゆけばボーナスをゲット!他のゲームも頑張るととうひょうできる数が増えるよ。" +TravelGameRemainingVotes = "とうひょうできる数:" +TravelGameUse = "つかう" +TravelGameVotesWithPeriod = "とうひょう" +TravelGameVotesToGo = "のこり" +TravelGameVoteToGo = "のこり" +TravelGameUp = "もっと!" +TravelGameDown = "へらす。" +TravelGameVoteWithExclamation = "とうひょう!" +TravelGameWaitingChoices = "他のプレイヤーがとうひょうするのを待っています..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['上へ', '下へ'] +TravelGameTotals = 'ごうけい ' +TravelGameReasonVotesPlural = 'とうひょう数%(numVotes)dで、トロリーは%(dir)s!' +TravelGameReasonVotesSingular = 'とうひょう数%(numVotes)dで、トロリーは%(dir)s!' +TravelGameReasonPlace = '%(name)sとどうてん!トロリーは%(dir)sすすむよ!' +TravelGameReasonRandom = 'トロリーは%(dir)sかってにすすむよ。' +TravelGameOneToonVote = "%(name)sが%(dir)s\nすすめるように%(numVotes)sとうひょうしました。" +TravelGameBonusBeans = "%(numBeans)dボーナスジェリービーン!" +TravelGamePlaying = 'つぎのトロリーゲームは%(game)sだよ。' +TravelGameGotBonus = '%(name)sが%(numBeans)sボーナスジェリービーンをゲット!' +TravelGameNoOneGotBonus = "だれもヒミツのゴールにたどりつけなかったよ。みんなジェリービーン1つずつゲット。" +TravelGameConvertingVotesToBeans = "とうひょうをジェリービーンにこうかんする" +TravelGameGoingBackToShop ="プレイヤーが1人しかいないからグーフィーのギャグショップへ…。" + +PairingGameTitle = "トゥーンしんけいすいじゃく" +PairingGameInstructions = "Deleteキーでカードをオープン。2枚そろえば得点。ボーナスマークは追加ポイントに!なるべく少ない回数でクリアしよう!" +PairingGameInstructionsMulti = "Deleteキーでカードをオープン。Controlキーで他のプレイヤーにじゅんばんを知らせよう。2枚そろえば得点。ボーナスマークは追加ポイントに!なるべく少ない回数でクリアしよう" +PairingGamePerfect = 'パーフェクト!!' +PairingGameFlips = 'オープン回数:' +PairingGamePoints = 'ポイント:' + +TrolleyHolidayStart = "『ターン・テーブル』が始まるよ!2人いじょうでトロリーに乗ってね。" +TrolleyHolidayOngoing = "ようこそ!『ターン・テーブル』をかいさいちゅうだよ。" +TrolleyHolidayEnd = "『ターン・テーブル』をしゅうりょうします!また来週ね!!" + +TrolleyWeekendStart = "『ターン・テーブル』ウィークが始まるよ!2人いじょうでトロリーに乗ってね。" +TrolleyWeekendEnd = "『ターン・テーブル』ウィークをしゅうりょうします。" + +VineGameTitle = "『ジャングル・ジャンプ』" +VineGameInstructions = "せいげん時間までにゴールを目指そう!矢印キーの上(↑)と下(↓)で高さをちょうせつ。右(→)と左(←)で向きを変えてジャンプ!低いところからだとスピードアップ。バナナを集めながらコウモリとクモのこうげきをかわそう。" + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "ウォーク・イン・パー", + 1: "ホール・サム・ファン", + 2: "ザ・ホール・カブードル" + } + +GolfHoleNames = { + 0: 'ホール・イン・ウィン', + 1: 'ノー・パット・アバウト・イット', + 2: 'ダウン・ザ・ハッチ', + 3: 'シーイング・グリーン', + 4: 'ホット・リンクス', + 5: 'ピーナツ・パター', + 6: 'スィング・ア・ロング', + 7: 'アフタヌーン・ティー', + 8: 'ホール・イン・ファン', + 9: 'ロックンロール・イン', + 10: 'ボギー・ナイツ', + 11: 'ティー・オフ・タイム', + 12: 'ホーリー・マカレル!', + 13: 'ワン・リトル・バーディー', + 14: 'ザ・ドライブ・イン', + 15: 'スィング・タイム', + 16: 'ホール・オン・ザ・レンジ', + 17: 'セカンド・ウィンド', + 18: 'ホール・イン・ウィン-2', + 19: 'ノー・パット・アバウト・イット-2', + 20: 'ダウン・ザ・ハッチ-2', + 21: 'シーイング・グリーン-2', + 22: 'ホット・リンクス-2', + 23: 'ピーナツ・パター-2', + 24: 'スィング・ア・ロング-2', + 25: 'アフタヌーン・ティー-2', + 26: 'ホール・イン・ファン-2', + 27: 'ロックンロール・イン-2', + 28: 'ボギー・ナイツ-2', + 29: 'ティー・オフ・タイム-2', + 30: 'ホーリー・マカレル!-2', + 31: 'ワン・リトル・バーディー-2', + 32: 'ザ・ドライブ・イン-2', + 33: 'スィング・タイム-2', + 34: 'ホール・オン・ザ・レンジ-2', + 35: 'セカンド・ウィンド-2', + } + +GolfHoleInOne = "ホール・イン・ワン" +GolfCondor = "コンドル" # four Under Par +GolfAlbatross = "アルバトロス" # three under par +GolfEagle = "イーグル" # two under par +GolfBirdie = "バーディー" # one under par +GolfPar = "パー" +GolfBogey = "ボギー" # one over par +GolfDoubleBogey = "ダブル・ボギー" # two over par +GolfTripleBogey = "トリプル・ボギー" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "コース、1人で完了" +CoursesUnderPar = "コース、アンダー・パー" +HoleInOneShots = "ホール・イン・ワン" +EagleOrBetterShots = "イーグル以上" +BirdieOrBetterShots = "バーディー以上" +ParOrBetterShots = "パー以上" +MultiPlayerCoursesCompleted = "コース パーティーで完了" +TwoPlayerWins = "2プレイヤーで勝利" +ThreePlayerWins = "3プレイヤーで勝利" +FourPlayerWins = "4プレイヤーで勝利" +CourseZeroWins = GolfCourseNames[0] + " Wins" +CourseOneWins = GolfCourseNames[1] + " Wins" +CourseTwoWins = GolfCourseNames[2] + " Wins" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + "個のトロフィー", + str(GolfGlobals.TrophiesPerCup * 2) + "個のトロフィー", + str(GolfGlobals.TrophiesPerCup * 3) + "個のトロフィー", +] + +GolfAvReceivesHoleBest = "%(name)sが%(hole)sでホールレコードをこうしん!" +GolfAvReceivesCourseBest = "%(name)sが%(course)sのコースレコードをこうしん!" +GolfAvReceivesCup = "%(name)sが%(cup)s杯をかくとく!賞品はゲラゲラブーストだ!!" +GolfAvReceivesTrophy = "%(name)sが%(award)sのトロフィーをかくとく!" +GolfRanking = "ランキング: \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "左・右の矢印キーでティーの位置をへんこう。\nCtrlキーで決定。" +GolfWarningMustSwing = "注意: 次のスィングではちゃんとCtrlキーを使ってね。" +GolfAimInstructions = "左・右の矢印キーで方向を決めて、\nCtrlキーを長押ししてスィングの強さをコントロールしよう。" +GolferExited = "%sがゴルフコースから出ました。" +GolfPowerReminder = "ボールをもっと遠くに打つには、\nCtrlキーをもっと長く押し続けよう。" + + +# GolfScoreBoard.py +GolfPar = "パー" +GolfHole = "ホール" +GolfTotal = "トータル" +GolfExitCourse = "ホールアウト" +GolfUnknownPlayer = "" + +# GolfPage.py +GolfPageTitle = "ゴルフ" +GolfPageTitleCustomize = "ゴルフのカスタマイズ" +GolfPageTitleRecords = "自己ベスト" +GolfPageTitleTrophy = "ゴルフ・トロフィー" +GolfPageCustomizeTab = "カスタマイズ" +GolfPageRecordsTab = "レコード" +GolfPageTrophyTab = "トロフィー" +GolfPageTickets = "チケット: " +GolfPageConfirmDelete = "アクセサリーを削除する?" +GolfTrophyTextDisplay = "トロフィー%(number)s個 : %(desc)s" +GolfCupTextDisplay = "カップ%(number)s個 : %(desc)s" +GolfCurrentHistory = "現在%(historyDesc)s : %(num)s" +GolfTieBreakWinner = "%(name)sがランダムでタイブレークに勝利!" +GolfSeconds = " - %(time).2f秒" +GolfTimeTieBreakWinner = "%(name)sが最短時間でタイブレークに勝利!!" + + + +RoamingTrialerWeekendStart = "ツアー・トゥーンタウンが始まるよ!フリー・プランの入場制限が解除されるよ!" +RoamingTrialerWeekendOngoing = "ツアー・トゥーンタウンへようこそ!フリー・プランの入場制限が解除されるよ!" +RoamingTrialerWeekendEnd = "ツアー・トゥーンタウンは終了しました。" +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Good news! Exclusive Test Toon double gag experience time has started." +MoreXpHolidayOngoing = "Welcome! Exclusive Test Toon double gag experience time is currently ongoing." +MoreXpHolidayEnd = "Exclusive Test Toon double gag experience time has ended. Thanks for helping us Test things!" + +JellybeanDayHolidayStart = "今日はジェリービーン・デーだよ!パーティーでいつもの二倍のジェリービーンをもらおう!" +JellybeanDayHolidayEnd = "ジェリービーン・デーは終了しました。また次回に会おうね。" +PartyRewardDoubledJellybean = "ダブル・ジェリービーン!!" + +GrandPrixWeekendHolidayStart = "グーフィーサーキットでグランプリウィークエンド開催中! ダレでも3連戦参加で大量ポイントゲットのチャンス!" +GrandPrixWeekendHolidayEnd = "グランプリウィークエンドは終了しました。また次回に会おう!" + +LogoutForced = "You have done something wrong\n and are being logged out automatically,\n additionally your account may be frozen.\n Try going on a walk outside, it is fun." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \nがゴルフカートにのったよ" +CountryClubBossConfrontedMsg = "%sがクラブのオーナーとバトルちゅうだよ!" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "さきにたおすあいてがいるみたいだよ。" + +# DistributedMolefield.py +MolesLeft = "のこりのモグラ: %d" +MolesInstruction = "モール・ストンプ!\nあかいモグラのうえにとびのろう!" +MolesFinished = "モール・ストンプをクリア!" +MolesRestarted = "モール・ストンプにしっぱい!もういちど・・・" + +# DistributedGolfGreenGame.py +BustACogInstruction = "コグボールをねらってね!" +BustACogExit = "いまはやめとく" +BustACogHowto = "あそびかた" +BustACogFailure = "じかんぎれ!" +BustACogSuccess = "だいせいこう!" + +# bossbot golf green games +GolfGreenGameScoreString = "のこりのパズル: %s" +GolfGreenGamePlayerScore = "クリアのかず %s" +GolfGreenGameBonusGag = "ボーナス!%sかくとく" +GolfGreenGameGotHelp = "%s パズルをクリア!" + +GolfGreenGameDirections = "マウスをつかってボールをねらってね!\n三つ同じ色をそろえるとボールが消せるよ。\nボードから全てのコグボールを消そう!" + +# DistributedMaze.py +enterHedgeMaze = "さいしょにめいろをクリアして\nポイントをゲットしよう!" +toonFinishedHedgeMaze = "%s \n が %s でゴール!" +hedgeMazePlaces = ["1ばん","2ばん","3ばん","4ばん"] +mazeLabel = "めいろでレース!" + +# Boarding Group +BoardingPartyReadme = 'ボーディング・グループ?' +BoardingGroupHide = 'かくす' +BoardingGroupShow = 'ボーディング・グループを見る' +BoardingPartyInform = '他のトゥーンをクリックしてエレベータ・ボーディング・グループに招待しよう。\nここではボーディング・グループは%s以下で作れます。' +BoardingPartyTitle = 'ボーディング・グループ' +QuitBoardingPartyLeader = 'かいさんする' +QuitBoardingPartyNonLeader = 'やめる' +QuitBoardingPartyConfirm = '本当にこのボーディング・グループをやめる?' +BoardcodeMissing = 'あれれ、トラブルかな?後でもう一度ためしてね。' +BoardcodeMinLaffLeader = 'キミのゲラゲラメーターが%s以下なので、キミのグループは参加できません。' +BoardcodeMinLaffNonLeaderSingular = '%sのゲラゲラメーターが%s以下なので、このグループは参加できません。' +BoardcodeMinLaffNonLeaderPlural = '%sのゲラゲラメーターが%s以下なので、キミのグループは参加できません。' +BoardcodePromotionLeader = 'メリットが足りないからキミのグループは参加できません。' +BoardcodePromotionNonLeaderSingular = '%sはメリットが足りないのでキミのグループは参加できません。' +BoardcodePromotionNonLeaderPlural = '%sのメリットが足りないので、キミのグループは参加できません。' +BoardcodeSpace = 'キミのグループは大きすぎて参加できません。' +BoardcodeBattleLeader = 'バトル中はキミのグループは参加できません。' +BoardcodeBattleNonLeaderSingular = '%sがバトル中なので、キミのグループは参加できません。' +BoardcodeBattleNonLeaderPlural = '%sがバトル中なので、キミのグループは参加できません。' +BoardingInviteMinLaffInviter = 'このボーディング・グループに入るには、ゲラゲラメーターが%s必要です。' +BoardingInviteMinLaffInvitee = '%sがこのボーディング・グループに入るには、ゲラゲラメーターが%s必要です。' +BoardingInvitePromotionInviter = 'このボーディング・グループに入るには格上げが必要だよ。' +BoardingInvitePromotionInvitee = '%sがこのボーディング・グループに入るには格上げが必要だよ。' +BoardingInviteNotPaidInvitee = '%sはフルアクセス・メンバーではないのでキミのボーディング・グループには入れないよ。' +BoardingInviteeInDiffGroup = '%sはもう他のボーディング・グループのメンバーです。' +BoardingInviteeInKickOutList = '%sはキミのリーダーがさくじょしました。リーダーしか招待できません。' +BoardingInviteePendingIvite = '%sは別の招待を保留中です。後でまた試してね。' +BoardingInviteeInElevator = '%sは今忙しいみたい…。後でまた試してね。' +BoardingInviteGroupFull = 'キミのボーディング・グループはもういっぱいです。' +BoardingAlreadyInGroup = 'キミはもう他のボーディング・グループに入っているのでこの招待は受けられません。' +BoardingGroupAlreadyFull = 'このグループはもういっぱいなので、この招待は受けられません。' +BoardingKickOutConfirm = '本当に%sを削除してもいい?' +BoardingPendingInvite = '先に保留中の招待に\n返事をしようね。' +BoardingCannotLeaveZone = 'ボーディング・グループに参加しているから今はだめだよ。' +BoardingInviteeMessage = "%sがキミのボーディング・グループに入ってほしいって。" +BoardingInvitingMessage = "%sをキミのボーディング・グループにさそっています。" +BoardingInvitationRejected = "%sが今回はキミのグループへの参加をみおくりました。" +BoardingMessageKickedOut = "ボーディング・グループから削除されました。" +BoardingMessageInvited = "%sが%sをボーディング・グループに招待しました。" +BoardingMessageLeftGroup = "%sがボーディング・グループからはずれました。" +BoardingMessageGroupDissolved = "キミのボーディング・グループはリーダーによって解散しました。" +BoardingMessageGroupDisbandedGeneric = "キミのボーディング・グループは解散しました。" +BoardingMessageInvitationFailed = "%sがボーディング・グループに招待しようとしていました。" +BoardingMessageGroupFull = "%sが参加しようとしましたが、あなたのグループはもういっぱいでした。" +BoardingGo = '行く' +BoardingCancelGo = '中止は<行く>を\n再クリック' +And = '+' +BoardingGoingTo = '行き先' +BoardingTimeWarning = 'エレベーターにのるまで ' +BoardingMore = 'もっと' +BoardingGoShow = '%sに行くまで' +BoardingGoPreShow = '確認中...' + +# DistributedBossbotBoss.py +BossbotBossName = "チーフ・ボスゼキュティブ" +BossbotRTWelcome = "ここでは別のへんそうパーツがひつようなんだ。" +BossbotRTRemoveSuit = "まずはコグ・スーツをぬいで..." +BossbotRTFightWaiter = "ここのウェイターたちとたたかおう!" +BossbotRTWearWaiter = "やったね! さぁ、ウェイターのようふくをきてみよう。" +BossbotBossPreTwo1 = "おい、まだか?ぐずぐずするな" +BossbotBossPreTwo2 = "楽しいえんかいのスタートだ。テキパキとたのむぞ!" +BossbotRTServeFood1 = "さぁ、コンベアーにおいた料理をどんどんはこんでくれ。" +BossbotRTServeFood2 = "3回つづけて同じコグにはこぶと、ばくはつするしかけなんだ。" +BossbotResistanceToonName = "グッドール・ジル・ギグルス" +BossbotPhase3Speech1 = "なんだ、どうなってるんだ!?" +BossbotPhase3Speech2 = "お、おまえたちは…あぁっ、トゥーンじゃないか!" +BossbotPhase3Speech3 = "つかまえろ!!" +BossbotPhase4Speech1 = "まったくだらしないやつらだ。ならば..." +BossbotPhase4Speech2 = "わたしがあいてになってやる!" +BossbotRTPhase4Speech1 = "いいぞ!こんどはテーブルの上の水でっぽうでボスゼキュティブをこうげきしよう。" +BossbotRTPhase4Speech2 = "それから、ゴルフボールを当てるとボスゼキュティブの動きがおそくなるよ。" +BossbotPitcherLeave = "やめる" +BossbotPitcherLeaving = "いどう中" +BossbotPitcherAdvice = "左右のキーで向きをかえられるよ。\nCtrlキーを押してパワーをためて、はなすとはっしゃ" +BossbotGolfSpotLeave = "やめる" +BossbotGolfSpotLeaving = "いどう中" +BossbotGolfSpotAdvice = "左右のキーで向きをかえられるよ。\nCtrlキーではっしゃ" +BossbotRewardSpeech1 = "なんてことを!おまえたち、わたしのカオをまるつぶれにしてくれたな!" +BossbotRewardSpeech2 = "ガルルルッ!!" +BossbotRTCongratulations = "すごいすごい!あのボスゼキュティブをついにたおしたぞ!\aさぁ、ボスゼキュティブが忘れていったカイコツウチだよ。\aこれでバトル中のコグをクビにできるんだ。""" +BossbotRTLastPromotion = "\aおぉ!キミのコグスーツはレベル%sになったよ!\aほんもののコグたちもそれ以上シュッセできないんだ。\aスーツのアップグレードはここまでだけど、レジスタンスを続けるとボーナスがもらえるんだ☆" +BossbotRTHPBoost = "\aキミの日ごろのかつやくには目をみはるものがある!\aトゥーンひょうぎ会はそのえいよをたたえ、キミにゲラゲラポイントをあたえる事にした。おめでとう!!" +BossbotRTMaxed = "\aレベル%sのコグスーツを持っているんだね。キミに会えてこうえいだよ!\aトゥーンひょうぎ会にかわって、キミのトゥーン・レジスタンスへのこうけんにかんしゃするよ!" +GolfAreaAttackTaunt = "ファ~ッ!" +OvertimeAttackTaunts = [ "今のそしきではだめだ。", + "ダメなコグをリストラしたらまた相手をしてやる!"] + +#ElevatorDestination Names +ElevatorBossBotBoss = "ボスゼキュティブ戦" +ElevatorBossBotCourse = "" +ElevatorBossBotCourse0 = "フロント・スリー" +ElevatorBossBotCourse1 = "ミドル・シックス" +ElevatorBossBotCourse2 = "バック・ナイン" +ElevatorCashBotBoss = "マネーマネー戦" +ElevatorCashBotMint0 = "コイン工場" +ElevatorCashBotMint1 = "ドル工場" +ElevatorCashBotMint2 = "ゴールド工場" +ElevatorSellBotBoss = "コグゼキュティブ戦" +ElevatorSellBotFactory0 = "正面入り口" +ElevatorSellBotFactory1 = "裏口" +ElevatorLawBotBoss = "チーフ・ジャスティス戦" +ElevatorLawBotCourse0 = "オフィスA" +ElevatorLawBotCourse1 = "オフィスB" +ElevatorLawBotCourse2 = "オフィスC" +ElevatorLawBotCourse3 = "オフィスD" + +# CatalogNameTagItem.py +DaysToGo = "あと\n%s日" + +# DistributedIceGame.py +IceGameTitle = "アイス・スライド" +IceGameInstructions = "第2ラウンドが終わるまでになるべく中心にたどり着こう。矢印キーで方向と強さを変えてね。Ctrlキーでトゥーンを発射!ドラム缶に当たるとボーナスポイント。でもTNTには当てちゃだめだよ!" +IceGameInstructionsNoTnt = "第2ラウンドが終わるまでになるべく中心にたどり着こう。矢印キーで方向と強さを変えてね。Ctrlキーでトゥーンを発射!ドラム缶に当たるとボーナスポイント。" +IceGameWaitingForPlayersToFinishMove = "他のプレイヤーを待っています..." +IceGameWaitingForAISync = "他のプレイヤーを待っています..." +IceGameInfo= "マッチ %(curMatch)d/%(numMatch)d, ラウンド %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="Ctrlキーで発射だよ!" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "参加する" +PicnicTableObserveButton = "みるだけ" +PicnicTableCancelButton = "キャンセル" +PicnicTableTutorial = "遊び方" +PicnicTableMenuTutorial = "どのゲームの説明?" +PicnicTableMenuSelect = "どのゲームをプレイする?" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = "席を立つ" +ChineseCheckersStartButton = "ゲーム開始!" +ChineseCheckersQuitButton = "ゲーム終了!" +ChineseCheckersIts = "It's " + +ChineseCheckersYourTurn = "キミの番だよ" +ChineseCheckersGreenTurn = "緑の番だよ" +ChineseCheckersYellowTurn = "黄色の番だよ" +ChineseCheckersPurpleTurn = "紫の番だよ" +ChineseCheckersBlueTurn = "青の番だよ" +ChineseCheckersPinkTurn = "ピンクの番だよ" +ChineseCheckersRedTurn = "赤の番だよ" + +ChineseCheckersColorG = "キミは緑だよ" +ChineseCheckersColorY = "キミは黄色だよ" +ChineseCheckersColorP = "キミは紫だよ" +ChineseCheckersColorB = "キミは青だよ" +ChineseCheckersColorPink = "キミはピンクだよ" +ChineseCheckersColorR = "キミは赤だよ" +ChineseCheckersColorO = "キミはみているだけだよ" + +ChineseCheckersYouWon = "おめでとう!ダイヤモンドゲームに勝利!!" +ChineseCheckers = "ダイヤモンドゲーム" +ChineseCheckersGameOf = " が勝ったゲーム: " + +#GameTutorials.py +ChineseTutorialTitle1 = "ゲームについて" +ChineseTutorialTitle2 = "遊び方" +ChineseTutorialPrev = "前のページ" +ChineseTutorialNext = "次のページ" +ChineseTutorialDone = "閉じる" +ChinesePage1 = "『ダイヤモンドゲーム』は、下の三角形にあるコマを他のプレイヤーより先に上の三角形に移動させるゲームだよ。先に移動し終わったプレイヤーが勝ちだよ!" +ChinesePage2 = "順番に自分の色のコマを移動します。コマはとなりあった穴か、他のコマを一つだけとびこえて空いている穴に移動(ホップ)してもOK。コマの移動先でもホップの条件が続いてそろう時は、遠くまで移動できるチェーンホップが有効だよ!" + +CheckersPage1 = "『チェッカー』は、相手のコマの動きを止めると勝ちなんだ。そのためには、相手のコマを全部とってしまうか、相手のコマが進んでいいところを先にうばってしまう方法があるよ。" +CheckersPage2 = "順番に自分の色のコマを動かします。各コマは、マスが空いている限りななめに一コマ、または前に一コマずつ進めます。キングも同じ動きと、さらに後ろに戻る事もできるんだ。" +CheckersPage3 = "ななめに相手のコマをとびこえてその先の空いているマスに進むと、とびこえた相手のコマをとることができるよ(ジャンプ)。もしも自分の番の時に相手のコマがとれる(ジャンプできる)時は、必ず一つはとらなければならないから注意してね。連続のジャンプは同じコマでだけできるからがんばってみよう。" +CheckersPage4 = "全てのコマは、ボードのさいごの列についたらキングにかわるんだ!キングになったばかりのコマは、次の順番まではジャンプできないよ。それから、キングは全ての方向に動けるし、ジャンプの途中で方向をかえる事もできるからためしてね!" + + + +#DistributedCheckers.py +CheckersGetUpButton = "席を立つ" +CheckersStartButton = "ゲーム開始!" +CheckersQuitButton = "ゲーム終了!" +CheckersIts = "次は" +CheckersYourTurn = "キミの番だよ" +CheckersWhiteTurn = "シロの番だよ" +CheckersBlackTurn = "クロの番だよ" +CheckersColorWhite = "キミはシロだよ" +CheckersColorBlack = "キミはクロだよ" +CheckersObserver = "けんがくちゅう…" +RegularCheckers = "チェッカー" +RegularCheckersGameOf = " が勝ったゲーム: " +RegularCheckersYouWon = "おめでとう!チェッカーに勝利!" + +MailNotifyNewItems = "メールがとどいたよ!" +MailNewMailButton = "メール" +MailSimpleMail = "ノート" +MailFromTag = "ノート: %sから" + +# MailboxScreen.py +InviteInvitation = "しょうたい状" +InviteAcceptInvalidError = "このしょうたい状はもう使えません。" +InviteAcceptPartyInvalid = "このパーティーはキャンセルされました。" +InviteAcceptAllOk = "あなたの返事はしゅさい者にとどきました。" +InviteRejectAllOk = "しゅさい者にキミの欠席の知らせがとどきました。" + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "1月", + 2: "2月", + 3: "3月", + 4: "4月", + 5: "5月", + 6: "6月", + 7: "7月", + 8: "8月", + 9: "9月", +10: "10月", +11: "11月", +12: "12月" +} + +# Note 0 for Monday to match datetime +DayNames = ("月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", "日曜日") +DayNamesAbbrev = ("月", "火", "水", "木", "金", "土", "日") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("夏の花火大会", "プレイグラウンドで1時間おきに開かれる花火大会をお友達といっしょに楽しもう! "), + 2: ("新年の花火", "明けましておめでとう!プレイグラウンドで1時間おきにあがる花火でいっしょにお祝いしよう!"), + 3: ("ガッツキーのしんりゃく", "ハッピー・ハロウィーン! 吸血鬼顔のガッツキーのしんりゃくをくい止めろ! "), + 4: ("ウィンターデコレーション", "ムードたっぷりのストリートや木々のデコレーションをお楽しみください♪"), + 5: ("ガイコグのしんりゃく", "キミ達の力でガイコグのしんりゃくをくい止めよう! "), + 6: ("ビッグスマイルの侵略", "ビッグスマイルの侵略をくいとめろ!"), + 7: ("フィッシュビンゴ", "今日はフィッシュビンゴの日!なかまたちと“ビンゴ!”をめざそう。 "), + 8: ("新種トゥーン投票", "キミはどんな新種トゥーンがいいと思う?ヤギ?ライオン?好きな新種に投票しよう! "), + 9: ("くろねこトゥーン!", "ハッピー・ハロウィーン!キミもくろねこトゥーン!を作ってみよう。10/31限定だよ!"), + 13: ("トリック・オア・トリート", "ハッピー・ハロウィーン!ハロウィーンのパンプキンヘッドをもらおう! "), + 14: ("グランプリ", "グーフィーサーキットでグランプリ開催中!3連勝してチャンピオンを目指そう。"), + 16: ("グランプリ・ウィークエンド", "フリー・プラン会員もレースに参加できるよ! "), + 17: ("トロリー・トラック", "今日はトロリー・トラックの日。二人以上でトロリーに乗ってトロリー・トラックを楽しもう! "), + 19: ("満タン・サタデー", "土曜日は一日中フィッシュビンゴにグランプリ、それからトロリー・トラックで楽しもう! "), + 24: ("3月ツキナカ", "3月15日を警戒せよ!ウラギリンの侵略からトゥーンタウンを守れ!"), + 26: ("ハロウィーン デコレ", "おばけの木とデコレーションでさまがわりしたトゥーンタウンをお楽しみあれ!"), + 28: ("おし売りおことわり!", "セルボット達のしつこいセールス戦略には、バトルで“No”と言おう!"), + 33: ("セルボット・サプライズ1", "セルボット・サプライズ!ブアイソン達の侵略からトゥーンタウンを守れ! "), + 34: ("セルボット・サプライズ2", "セルボット・サプライズ!タッシャーナ達の侵略からトゥーンタウンを守れ! "), + 35: ("セルボット・サプライズ3", "セルボット・サプライズ!オオゲーサの侵略からトゥーンタウンを守れ!"), + 36: ("セルボット・サプライズ4", "セルボット・サプライズ!クロマクールの侵略からトゥーンタウンを守れ! "), + 37: ("マネーボット・スクランブル1", "マネーボット・スクランブル!チョロマカシー達の侵略からトゥーンタウンを守れ! "), + 38: ("マネーボット・スクランブル2", "マネーボット・スクランブル!セコビッチ達の侵略からトゥーンタウンを守れ!"), + 39: ("マネーボット・スクランブル3", "マネーボット・スクランブル!カッチリン達の侵略からトゥーンタウンを守れ! "), + 40: ("マネーボット・スクランブル4", "マネーボット・スクランブル!スウジスキー達の侵略からトゥーンタウンを守れ! "), + 41: ("ロウボット・チャージ1", "ロウボット・チャージ!タイコモチー達の侵略からトゥーンタウンを守れ!"), + 42: ("ロウボット・チャージ2", "ロウボット・チャージ!ニマイジタン達の侵略からトゥーンタウンを守れ!"), + 43: ("ロウボット・チャージ3", "ロウボット・チャージ!ツケコミン達の侵略からトゥーンタウンを守れ!"), + 44: ("ロウボット・チャージ4", "ロウボット・チャージ!ウラギリン達の侵略からトゥーンタウンを守れ! "), + 45: ("ボスボット・リベンジ1", "ボスボット・リベンジ!オベッカー達の侵略からトゥーンタウンを守れ!"), + 46: ("ボスボット・リベンジ2", "ボスボット・リベンジ!カリカリン達の侵略からトゥーンタウンを守れ!"), + 47: ("ボスボット・リベンジ3", "ボスボット・リベンジ!ガミガミーナ達の侵略からトゥーンタウンを守れ!"), + 48: ("ボスボット・リベンジ4", "ボスボット・リベンジリストラマン達の侵略からトゥーンタウンを守れ!"), + 49: ("ジェリービーン・デー", "今日はパーティーに参加するとごほうびのジェリービーンがいつもの二倍もらえるよ! "), + 53: ("ブアイソンの侵略", "ブアイソン達の侵略からトゥーンタウンを守れ!"), + 54: ("カッチリンの侵略", "カッチリン達の侵略からトゥーンタウンを守れ!"), + 55: ("ニマイジタンの侵略", "ニマイジタン達の侵略からトゥーンタウンを守れ!"), + 56: ("リストラマンの侵略", "リストラマンの侵略からトゥーンタウンを守れ!"), + + } + +UnknownHoliday = "未知の休日 %d" +HolidayFormat = "%m/%d " + +# parties/ToontownTimeManager.py +TimeZone = "Japan" diff --git a/toontown/src/toonbase/TTLocalizer_japanese_Property.py b/toontown/src/toonbase/TTLocalizer_japanese_Property.py new file mode 100644 index 0000000..0138508 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_japanese_Property.py @@ -0,0 +1,496 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (2.15,1,0.75) +RPtrackLabels = 0.043 +RPmeritBarLabels = 0.13 + +#battle/RewardPanel.py +RPmeritLabelXPosition = 0.55 +RPmeritBarsXPosition = 0.825 + +#battle/BattleBase.py +BBbattleInputTimeout = 50.0 + +#battle/FireCogPanel.py +FCPtextFrameScale = 0.05 + +#building/DistributedHQInterior.py +DHtoonName = 0.9 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.8 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.045 + +#catalog/CatalogItemPanel.py +CIPnameLabel = 1.0 +CIPwordwrapOffset = 0 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.13) +CSgiftToggle = 0.08 +CSbackCatalogButton = 0.065 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.045 +CISCtopLevelOverlap = 0. + +#chat/ToontownChatManager.py +CMnormalButton = 0.05 +CMscButtonPos = (-1.084, 0, 0.928) +CMscButton = 0.05 +CMwhisperFrame = 0.05 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 18 +CMunpaidChatWarning = 0.06 +CMunpaidChatWarning_text_z = 0.23 +CMpayButton = 0.06 +CMpayButton_pos_z = -0.08 +CMopenChatWarning = 0.06 +CMactivateChat = 0.06 +CMchatActivated = 0.06 +CMNoPasswordContinue_z = -0.23 + +#coghq/LawbotCogHQLoader.py +LCLdgSign = 0.1 # the scale of the gate name + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.075 +SCLdgSign = 0.1 # the scale of the gate name + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 1 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 2 + +#coghq/BossbotCogHQLoader.py +BCHQLmakeSign = 1.12 + +#coghq/DistributedGolfGreenGame.py +DGGGquitButton = 0.045 +DGGGhowToButton = 0.045 +DGGGscoreLabel = 0.075 + +#estate/houseDesign.py +HDhelpText = 0.8 +HDatticButton = 1.0 +HDroomButton = 1.0 +HDtrashButton = 1.0 +HDscrolledList = 0.1 + +#estate/PlantingGUI.py +GardeningInstructionScale = 0.07 + +#estate/FlowerPanel.py +FPBlankLabelPos = -0.25 +FPBlankLabelTextScale = 0.025 + +#estate/FlowerPicker.py +FPFlowerValueTotal = 0.045 + +#estate/FlowerSellGUI.py +FSGDFTextScale = 0.048 +FSGCancelBtnTextScale = 0.04 +FSGOkBtnTextScale = 0.04 + +#estate/GardenTutorial.py +GardenTutorialPage2Wordwrap = 14.5 +GardenTutorialPage4Wordwrap = 22.5 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.71 + +#fishing/FishSellGUI.py +FSGokButton = 0.06 +FSGcancelButton = 0.06 + +#fishing/FishPanel.py +FPnewEntry = 0.08 +FPnewRecord = 0.08 + +#fishing/GenusPanel.py +GPgenus = 0.045 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.035 +FLPsecrets = 0.035 +FLPsecretsPos = (0.125, 0.0, 0.14) +FLPtitleScale = 0.035 + +#friends/FriendInviter.py +FIstopButton = 0.05 +FIdialog = 0.06 +FIcancelButtonPositionX = 0.20 +FIstopTextPositionY = -0.1 +FIstopButtonPositionX = -0.20 +FIyesButtonPositionX = -0.20 +FIdirectFrameTextWorkWrap = 13.5 +FIdirectFrameTextPosZ = 0.13 + +#golf/DistributedGolfHole.py +DGHpowerReminder = 0.09 +DGHaimInstructions = 0.065 +DGHteeInstructions = 0.065 + +#golf/DistributedGolfHole.py +DGHAimInstructScale = 0.075 +DGHTeeInstructScale = 0.075 + +#golf/GolfScoreBoard.py +GSBExitCourseBTextPose = (0.20, -.01) +GSBtitleLabelScale = 0.07 + +#golf/GolfScoreBoard.py +GSBexitCourseBPos = (0.20, -.01) +GSBtitleLabel = 0.07 + +#hood/EstateHood.py +EHpopupInfo = .07 + +#hood/Hood.py +HDenterTitleTextScale = 0.13 + +#login/AvatarChoice.py +ACplayThisToon = 0.11 +ACmakeAToon = 0.12 +ACsubscribersOnly = 0.115 +ACdeleteWithPassword = 0.05 +ACstatusText = 0.6 + +#login/AvatarChooser.py +ACtitle = 0.125 +ACquitButton = 0.1 +AClogoutButton = 0.09 +ACquitButton_pos = -0.035 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.055 +MRPinstructionsText = 0.045 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.055 +MPMtooSlow = 0.055 +MPMtooFast = 0.055 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.07 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.08 + +#makeatoon/NameShop.py +NSmaxNameWidth = 7.5 +NSdirectScrolleList = None +NSmakeLabel = 0.1 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.06 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.02 +NSnameResult = 0.08 +NStypeName = 0.07 +NSnewName = 0.08 +NScolorPrecede = True + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.18 +MATenterBodyShop = 0.18 +MATenterColorShop = 0.18 +MATenterClothesShop = 0.16 +MATenterNameShop = 0.15 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.08 + +#makeatoon\ShuffleButton.py +SBshuffleBtn = 0.05 + +#minigame/DistributedPairingGame.py +DPGPointsFrameTextScale = 0.45 +DPGFlipsFrameTextScale = 0.45 + +#minigame/DistributedTravelGame.py +DTGVoteBtnTextScale = 0.05 +DTGUseLabelTextScale = 0.07 +DTGVotesPeriodLabelTextScale = 0.07 +DTGVotesToGoLabelTextScale = 0.07 +DTGUpLabelTextScale = 0.07 +DTGDownLabelTextScale = 0.07 +DTGRemainingVotesFrameTextScale = 0.6 + +#minigame/MinigameRulesPanel.py +MRPGameTitleTextScale = 0.10 +MRPGameTitleTextPos = (-0.12, 0.2, 0.092) +MRPInstructionsTextWordwrap = 32 +MRPInstructionsTextPos = (-0.12, 0.05, 0) + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.35 + +#parties/InviteVisual.py +IVwhenTextLabel = 0.05 +IVactivityTextLabel = 0.05 + +#parties/PartyPlanner.py +PPDescriptionScale = 0.05 +PPelementTitleLabelScale = 0.045 +PPelementBuyButtonTextScale = 0.055 +PPtitleScale = 0.08 +PPpbulicDescriptionLabel = 0.055 +PPprivateDescriptionLabel = 0.055 +PPpublicButton = 0.03 +PPprivateButton = 0.03 +PPcostLabel = 0.05 +PPpartyGroundsLabel = 0.7 +PPinstructionLabel = 0.055 +PPelementPrice = 0.055 + +#parties/DistributedParty.py +DPpartyCountdownClockTextScale = 1.1 +DPpartyCountdownClockMinutesScale = 1.1 +DPpartyCountdownClockColonScale = 1.1 +DPpartyCountdownClockSecondScale = 1.1 +DPpartyCountdownClockMinutesPosY = 0.0 +DPpartyCountdownClockColonPosY = 0.0 +DPpartyCountdownClockSecondPosY = 0.0 + +#parties/PublicPartyGui.py +PPGpartyStartButton = 0.04 +PPGinstructionsLabel = 0.045 +PPGcreatePartyListAndLabel = 0.05 + +#parties/JukeboxGui.py +JGcurrentlyPlayingLabel = 0.08 +JGsongNameLabel = 0.10 +JGaddSongButton = 0.08 +JGnumItemsVisible = 12 +JGlistItem = 0.75 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.3 +PAPcall = 0.3 +PAPowner = 0.25 +PAPscratch = 0.3 +PAPstateLabel = 0.25 +PAPstateLabelPos = (0, 0, 3.5) +PAPstateLabelwordwrap = 10 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.8 +PGUIchooserTitle = 0.09 +PGUIwordwrap = 16 +PGUIdescLabel = 0.8 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.6 +PGUIadoptSubmit = 0.8 +PGUIpetsopAdoptPos = (-0.21,1.05) +PGUIpetshopCancelPos = (-2.3, 2.95) +PGUIcharLength = 3 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.10 +PTpage1Pos = (-0.37, 0.18) +PTpage2Pos = (-0.67, 0.18) +PTpage3Pos = (-0.37, 0.18) + +#quest/QuestPoster.py +QPauxText = 0.04 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0725 + +#racing/DistributedLeaderBoard.py +DLBtitleRowScale = 0.4 + +#racing/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.5 + +#raceing/DistributedRacePad.py +DRPnodeScale = 0.65 + +#racing/KartShopGui.py +KSGtextSizeBig = 0.066 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 11 + +#racing/RaceEndPanels.py +REPraceEnd = 0.06 +REPraceExit = 0.02 +REPticket_text_x = -0.85 + +#racing/RaceGUI.py +RGphotoFinish = 0.15 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#racing/DistributedRaceAI.py +DRAwaitingForJoin = 90 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.05 + +#safezone/Playground.py +PimgLabel = 0.7 + +#safezone/GZSafeZoneLoader.py +GSZLbossbotSignScale = 1.2 + +#shtiker/EventsPage.py +EPtitleLabel = 0.08 +EPhostTab = 0.07 +EPinvitedTab = 0.07 +EPcalendarTab = 0.07 +EPnewsTab = 0.07 +EPhostingCancelButton = 0.018 +EPhostingDateLabel = 0.04 +EPpartyGoButton = 0.020 +EPpublicPrivateLabel = 0.035 +EPpublicButton= 0.35 +EPprivateButton = 0.35 +EPinvitePartyGoButton = 0.025 +EPdecorationItemLabel = 0.045 +EPactivityItemLabel = 0.045 +EPcreateListAndLabel = 0.045 + +#shtiker/FishPage.py +FPtankTab = 0.06 +FPcollectionTab = 0.06 +FPtrophyTab = 0.06 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.048 +DSDintroTextwordwrap = 30 +DSDwindowedButtonPos = (0.0850, 0, -0.218) +DSDfullscreenButtonPos = (0.0865, 0, -0.307) +DSDcancel = 0.04 +DSDcancelButtonPositionX = 0 + +#shtiker/DisguisePage.py +DPtab = 0.08 +DPdeptLabel = 0.17 +DPcogName = 0.093 + +#shtiker/TrackPage.py +TPstartFrame = 0.09 +TPendFrame = 0.09 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.045 +MPgoHome = 0.055 +MPhoodLabel = 0.06 +MPhoodWordwrap = 14 + +#shtiker/KartPage.py +KPkartTab = 0.05 +KPdeleteButton = 0.06 +KProtateButton = 0.035 + +#shtiker/GardenPage.py +GPBasketTabTextScale = 0.06 +GPCollectionTabTextScale = 0.06 +GPTrophyTabTextScale = 0.06 +GPSpecialsTabTextScale = 0.06 + +#shtiker/GolfPage.py +GFPRecordsTabTextScale = 0.06 +GFPRecordsTabPos = (0.82, 0, 0.1) +GFPTrophyTabTextScale = 0.06 +GFPRecordsTabTextPos = (0.03, 0.0, 0.0) +GFPRTrophyTabPos = (0.82, 0, -0.3) + +#toon/AvatarPanelBase.py +APBignorePanelAddIgnoreTextScale = 0.045 +APBignorePanelTitlePosY = 0.1 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.05 +TAPwhisperButton = 0.05 +TAPsecretsButton = 0.05 +TAPgoToButton = 0.05 +TAPignoreButton = 0.05 +TAPpetButton = 0.325 +TAPdetailButton = 0.05 +TAPgroupFrameScale = 0.025 +TAPgroupButtonScale = 0.04 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.038 +TADPcancelButton = 0.05 + +#toon/GroupPanel.py +GPdestFrameScale = 0.035 +GPdestScrollListScale = 0.035 +GPgoButtonScale = 0.06 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.05 +INrunButton = 0.05 +INdetailNameLabel = 0.8 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.7 + +#toon/PlayerInfoPanel.py +PIPsecretsButtonScale=0.06 +PIPwisperButton = 0.06 +PIPdetailButton = 0.05 + +#toon/ToonAvatarPanel.py +TAPsecretsButtonScale=0.06 +TAPwisperButtonScale=0.06 + +#toon/ToonAvatarDetailPanel.py +TADPcancelPos = (-0.865, 0.0, -0.765) +TADtrackLabelPosZ = 0.17 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.18 + +#toontowngui/TeaserPanel.py +TSRPdialogWordwrap = 20 +TSRPtop = 0.07 +TSRPpanelScale = 0.06 +TSRPpanelPos = (0., -0.7) +TSRPbrowserPosZ = -0.65 +TSRPbutton = 0.05 +TSRPteaserBottomScale = 0.06 +TSRPhaveFunText = 0.08 +TSRPjoinUsText = 0.08 + +#toontowngui/TeaserPanel.py (OLD) +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.07 + +#trolley/Trolley.py +TtrolleyHopOff = 0.8 diff --git a/toontown/src/toonbase/TTLocalizer_portuguese.py b/toontown/src/toonbase/TTLocalizer_portuguese.py new file mode 100644 index 0000000..6810362 --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_portuguese.py @@ -0,0 +1,10443 @@ +import string +import time +from toontown.toonbase.TTLocalizer_portuguese_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore-me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +FancyFont = 'phase_3/models/fonts/Comedy' +NametagFonts = ('phase_3/models/fonts/AnimGothic', #0 * + 'phase_3/models/fonts/Aftershock', #1 * + 'phase_3/models/fonts/JiggeryPokery', #2 * + 'phase_3/models/fonts/Ironwork', #3 * + 'phase_3/models/fonts/HastyPudding', #4 * + 'phase_3/models/fonts/Comedy', #5 * + 'phase_3/models/fonts/Humanist', #6 * + 'phase_3/models/fonts/Portago', #7 * + 'phase_3/models/fonts/Musicals', #8 * + 'phase_3/models/fonts/Scurlock', #9 * + 'phase_3/models/fonts/Danger', #10 * + 'phase_3/models/fonts/Alie', #11 * + 'phase_3/models/fonts/OysterBar', #12 * + 'phase_3/models/fonts/RedDogSaloon', #13 * + ) +NametagFontNames = ('Usuário', #0 * + 'Tremido', #1 * + 'Arrepiante', #2 * + 'Exorbitante', #3 * + 'Bobo', #4 * + 'Doido', #5 * + 'Pratico', #6 * + 'Nautico', #7 * + 'Caprichoso', #8 * + 'Estremecer', #9 * + 'Ação', #10 * + 'Poético', #11 * + 'Passeio', #12 * + 'Ocidental', #13 * + ) + +NametagLabel = "Nome" + +UnpaidNameTag = "Basico" + +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "Mickey" +VampireMickey = "VampireMickey" +Minnie = "Minnie" +WitchMinnie = "WitchMinnie" +Donald = "Donald" +DonaldDock = "DonaldDock" +Daisy = "Margarida" +Goofy = "Pateta" +SuperGoofy = "SuperGoofy" +Pluto = "Pluto" +WesternPluto = "WesternPluto" +Flippy = "Flippy" +Chip = "Tico" +Dale = "Teco" + +# common locations +lTheBrrrgh = 'O Brrrgh' +lDaisyGardens = 'Jardim da Margarida' +lDonaldsDock = "Porto do Donald" +lDonaldsDreamland = "Sonholândia do Donald" +lMinniesMelodyland = "Melodilândia da Minnie" +lToontownCentral = 'Centro de Toontown' +lToonHQ = 'Quartel dos Toons' +lSellbotHQ = 'Sellbot HQ' +lGoofySpeedway = "Autódromo do Pateta" +lOutdoorZone = "Bosque de Bolotas de Tico e Teco" +lGolfZone = "Minigolfe de Tico e Teco" +lPartyHood = "Party Grounds" + +lGagShop = 'Loja de Piadas' +lClothingShop = 'Loja de Roupas' +lPetShop = 'Loja de Animais' + +# common strings +lCancel = 'Cancelar' +lClose = 'Fechar' +lOK = 'OK' +lNext = 'Próximo' +lQuit = 'Sair' +lYes = 'Sim' +lNo = 'Não' +lBack = 'Voltar' + +sleep_auto_reply = "%s is sleeping right now" +lHQ = 'Oficial' + +lHQOfficerF = 'Oficial do Quartel' +lHQOfficerM = 'Oficial do Quartel' + +MickeyMouse = "Mickey Mouse" + +AIStartDefaultDistrict = "Vila dos Idiotas" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "um Cog" +TheCogs = "os Cogs" +ASkeleton = "um Esqueletocog" +Skeleton = "Esqueletocogs" +SkeletonP = "Esqueletocogs" +Av2Cog = "um Cog Versão 2.0" +v2Cog = "Cog Versão 2.0" +v2CogP = "Cogs Versão 2.0" +ASkeleton = "um Esqueletocog" +Foreman = "Supervisor da fábrica" +ForemanP = "Supervisores da fábrica" +AForeman = "um Supervisor da fábrica" +CogVP = Cog + " VP" +CogVPs = "Cogs VPs" +ACogVP = ACog + " VP" +Supervisor = "Supervisor da Casa da Moeda" +SupervisorP = "Supervisores da Casa da Moeda" +ASupervisor = "um Supervisor da Casa da Moeda" +CogCFO = Cog + "Diretor Financeiro" +CogCFOs = "Diretores Financeiros Cogs" +ACogCFO = ACog + "Diretor Financeiro" + +# AvatarDNA.py +Bossbot = "Robô-chefe" +Lawbot = "Robô da Lei" +Cashbot = "Robô Mercenário" +Sellbot = "Robô Vendedor" +BossbotS = "um Robô-chefe" +LawbotS = "um Robô da Lei" +CashbotS = "um Robô Mercenário" +SellbotS = "um Robô Vendedor" +BossbotP = "Robôs-chefe" +LawbotP = "Robôs da Lei" +CashbotP = "Robôs Mercenários" +SellbotP = "Robôs Vendedores" +BossbotSkelS = "um Esqueletocog %s" % (Bossbot) +LawbotSkelS = "um Esqueletocog %s" % (Lawbot) +CashbotSkelS = "um Esqueletocog %s" % (Cashbot) +SellbotSkelS = "um Esqueletocog %s" % (Sellbot) +BossbotSkelP = "Esqueletocogs %s" % (BossbotP) +LawbotSkelP = "Esqueletocogs %s" % (LawbotP) +CashbotSkelP = "Esqueletocogs %s" % (CashbotP) +SellbotSkelP = "Esqueletocogs %s" % (SellbotP) +SkeleRevivePostFix = " v2.0" + +lBossbotHQ = 'Quartel do Robô-chefe' +lLawbotHQ = 'Quartel do Robô da Lei' +lCashbotHQ = 'Quartel do Robô Mercenário' +lSellbotHQ = 'Quartel do Robô Vendedor' +lTutorial = 'Toon-torial' +lMyEstate = 'sua casa' +lWelcomeValley = 'Vale Boas-vindas' + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("para o", "no", "Terraço do Tutorial"), # Tutorial + 1000 : ("para o", "no", "Parque"), + 1100 : ("para o", "no", "Boulevard das Cracas"), + 1200 : ("para a", "na", "Rua da Alga Marinha"), + 1300 : ("para a", "na", "Travessa do Farol"), + 2000 : ("para o", "no", "Parque"), + 2100 : ("para a", "na", "Rua da Bobeira"), + 2200 : ("para a", "na", "Travessa dos Tontos"), + 2300 : ("para o", "no", "Largo do Auge da Graça"), + 3000 : ("para o", "no", "Parque"), + 3100 : ("para a", "na", "Via dos Leões Marinhos"), + 3200 : ("para a", "na", "Rua da Chuva de Neve"), + 3300 : ("para o", "no", "Lugar Polar"), + 4000 : ("para o", "no", "Parque"), + 4100 : ("para a", "na", "Avenida do Tom Alto"), + 4200 : ("para o", "no", "Boulevard do Barítono"), + 4300 : ("para o", "no", "Terraço do Tenor"), + 5000 : ("para o", "no", "Parque"), + 5100 : ("para a", "na", "Rua das Nogueiras"), + 5200 : ("para a", "na", "Rua das Amendoeiras"), + 5300 : ("para a", "na", "Rua dos Carvalhos"), + 9000 : ("para o", "no", "Parque"), + 9100 : ("para a", "na", "Travessa da Canção de Ninar"), + 9200 : ("para o", "no", "Pedaço do Pijama"), + 10000 : ("","", ""), + 10100 : ("para o", "no", "Salão do "+lBossbotHQ), + 10200 : ("para a", "na", "Sede do Clube"), + 10500 : ("para o", "no", "Três da Frente"), + 10600 : ("para o", "no", "Seis do Meio"), + 10700 : ("para o", "no", "Nove de Trás"), + 11000 : ("","", ""), + 11100 : ("para o", "no", "Salão do "+lSellbotHQ), + 11200 : ("para a", "na", "Fábrica do "+Sellbot), + 11500 : ("para a", "na", "Fábrica do "+Sellbot), + 12000 : ("","", ""), + 12100 : ("para o", "no", "Salão do "+lCashbotHQ), + 12500 : ("para a", "na", "Casa da Moeda"), + 12600 : ("para a", "na", "Casa da Moeda de Dólar"), + 12700 : ("para a", "na", "Casa da Moeda de Barras de Ouro"), + 13000 : ("","", ""), + 13100 : ("para o", "no", "Salão do "+lLawbotHQ), + 13200 : ("para o", "no", "Lobby do Escritório do Promotor"), + 13300 : ("para o", "no", "Escritório da Lei A"), + 13400 : ("para o", "no", "Escritório da Lei B"), + 13500 : ("para o", "no", "Escritório da Lei C"), + 13600 : ("para o", "no", "Escritório da Lei D"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("para o", "no", lDonaldsDock) +ToontownCentral = ("para o", "no", lToontownCentral) +TheBrrrgh = ("para", "em", lTheBrrrgh) +MinniesMelodyland = ("para a", "na", lMinniesMelodyland) +DaisyGardens = ("para os", "nos", lDaisyGardens) +OutdoorZone = ("para a", "na", lOutdoorZone) +FunnyFarm = ("para a", "na", "Fazenda Divertida") +GoofySpeedway = ("para o", "no", lGoofySpeedway) +DonaldsDreamland = ("para a", "na", lDonaldsDreamland) +BossbotHQ = ("para o", "no", lBossbotHQ) +SellbotHQ = ("para o", "no", lSellbotHQ) +CashbotHQ = ("para o", "no", lCashbotHQ) +LawbotHQ = ("para o", "no", lLawbotHQ) +Tutorial = ("para o", "no", lTutorial) +MyEstate = ("para a", "na", lMyEstate) +WelcomeValley = ("para o", "no", lWelcomeValley) +GolfZone = ("para a", "na", lGolfZone) +PartyHood = ("to the", "in the", lPartyHood) + +Factory = 'Fábrica' +Headquarters = 'Quartel' +SellbotFrontEntrance = 'Entrada principal' +SellbotSideEntrance = 'Entrada lateral' +Office = 'Escritório' + +FactoryNames = { + 0 : 'Molde da fábrica', + 11500 : 'Fábrica do Cog '+Sellbot, + 13300 : 'Escritório de Cogs Policiais', #remove me JML + } + +FactoryTypeLeg = 'Perna' +FactoryTypeArm = 'Braço' +FactoryTypeTorso = 'Busto' + +MintFloorTitle = 'Andar %s' + +# Quests.py +TheFish = "o Peixe" +AFish = "um peixe" +Level = "nível" +QuestsCompleteString = "Concluir" +QuestsNotChosenString = "Não escolhido" +Period = "." + +Laff = "Risada" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Olá, _avName_!", + "Oi, _avName_!", + "E aí, _avName_?", + "Diga aí, _avName_!", + "Bem-vindo, _avName_!", + "Tudo certo, _avName_?", + "Como vai você, _avName_?", + "Olá _avName_!", + ) +QuestsDefaultIncomplete = ("Como está indo aquela tarefa, _avName_?", + "Parece que você ainda tem mais trabalho a fazer naquela tarefa!", + "Continue com o bom trabalho, _avName_!", + "Continue tentando concluir aquela tarefa. Eu sei que você consegue!", + "Continue tentando concluir a tarefa. Contamos com você!", + "Continue trabalhando naquela Tarefa Toon!", + ) +QuestsDefaultIncompleteProgress = ("Você veio ao lugar certo, mas, primeiramente, precisa concluir sua Tarefa Toon.", + "Ao terminar a Tarefa Toon, volte aqui.", + "Volte quando tiver terminado sua Tarefa Toon.", + ) +QuestsDefaultIncompleteWrongNPC = ("Bom trabalho naquela Tarefa Toon. Você deveria visitar _toNpcName_._where_", + "Parece que você está pronto para concluir sua Tarefa Toon. Vá ver _toNpcName_._where_.", + "Vá ver _toNpcName_ para concluir sua Tarefa Toon._where_", + ) +QuestsDefaultComplete = ("Bom trabalho! Aqui está a sua recompensa...", + "Ótimo trabalho, _avName_! Tome esta recompensa...", + "Excelente trabalho, _avName_! Aqui está a sua recompensa...", + ) +QuestsDefaultLeaving = ("Tchau!", + "Até logo!", + "Até mais, _avName_.", + "Te vejo por aí, _avName_!", + "Boa sorte!", + "Divirta-se em Toontown!", + "Vejo você depois!", + ) +QuestsDefaultReject = ("Olá.", + "Posso ajudar?", + "Como vai você?", + "E aí, pessoal?", + "Estou um pouco ocupado agora, _avName_.", + "Sim?", + "Tudo certo, _avName_!", + "Bem-vindo, _avName_!", + "Ei, _avName_! Tudo bem?", + # Game Hints + "Você sabia que pode abrir seu Álbum Toon clicando em F8?", + "Você pode usar seu mapa para se teletransportar de volta ao pátio!", + "Você pode ficar amigo de outros jogadores clicando neles.", + "Você pode descobrir mais sobre um "+ Cog +" clicando nele.", + "Junte tesouros nos pátios para encher seu Risômetro.", + "Os edifícios " + Cog + " são lugares perigosos! Não entre neles sozinho!", + "Quando você perde uma batalha, os "+ Cogs +" tomam todas as suas piadas.", + "Para obter mais piadas, jogue no Bondinho!", + "Você pode obter mais Pontos de risadas completando as Tarefas Toon.", + "Toda Tarefa Toon dá uma recompensa a você.", + "Algumas recompensas permitem que você carregue consigo mais Piadas.", + "Se você vencer uma batalha, ganhará créditos de Tarefa Toon para cada "+ Cog +" derrotado.", + "Se você recuperar um edifício "+ Cog +", entre e verá um agradecimento especial do proprietário!", + "Se pressionar a tecla Page Up, poderá ver acima de você!", + "Se você pressionar a tecla Tab, poderá ver os arredores sob diversos ângulos!", + "Para mostrar aos amigos secretos o que está pensando, coloque '.' antes do pensamento.", + "Se um "+ Cog +" estiver atordoado, será mais difícil para ele desviar de objetos cadentes.", + "Cada tipo de edifício "+ Cog +" possui um visual diferente.", + "Derrotar os "+ Cogs +" nos andares mais altos de um edifício dará a você maiores recompensas de habilidade.", + ) +QuestsDefaultTierNotDone = ("Olá, _avName_! Você deve concluir sua Tarefa Toon atual antes de começar uma nova.", + "E aí? Você precisa concluir suas Tarefas Toon atuais antes de começar uma nova.", + "Oi, _avName_! Para que eu possa dar a você uma nova Tarefa Toon, você precisa terminar as que você tem.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("Ouvi falar que _toNpcName_ está procurando por você._where_", + "Passe por lá e visite _toNpcName_ quando tiver um tempinho._where_", + "Visite _toNpcName_ da próxima vez em que estiver passando por aquele caminho._where_", + "Se tiver um tempinho, pare e diga olá para _toNpcName_._where_", + "_toNpcName_ dará a você sua nova Tarefa Toon._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogQuestHeadline = "PROCURADO" +QuestsCogQuestSCStringS = "Eu preciso derrotar %(cogName)s%(cogLoc)s" +QuestsCogQuestSCStringP = "Eu preciso derrotar alguns %(cogName)s%(cogLoc)s." +QuestsCogQuestDefeat = "Derrotar %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Ajude um novo Toon a derrotar %s" +QuestsCogNewNewbieQuestCaption = "Ajude um novo Toon que tenha %d risadas ou menos que isso" +QuestsCogOldNewbieQuestObjective = "Ajude um Toon com %(laffPoints)d risadas, ou menos, a dominar %(objective)s" +QuestsCogOldNewbieQuestCaption = "Ajude um Toon com %d risadas, ou menos" +QuestsCogNewbieQuestAux = "Derrotar:" +QuestsNewbieQuestHeadline = "APRENDIZ" + +QuestsCogTrackQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogTrackQuestHeadline = "PROCURADO" +QuestsCogTrackQuestSCStringS = "Eu preciso derrotar %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestSCStringP = "Eu preciso derrotar alguns %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Derrotar %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogLevelQuestHeadline = "PROCURADO" +QuestsCogLevelQuestDefeat = "Derrotar %s" +QuestsCogLevelQuestDesc = "um Nível %(level)s+ Cog" +QuestsCogLevelQuestDescC = "%(count)s Nível %(level)s+ Cogs" +QuestsCogLevelQuestDescI = "algum Nível %(level)s+ Cogs" +QuestsCogLevelQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('','dois+','três+','quatro+','cinco+') +QuestsBuildingQuestBuilding = "Edifício" +QuestsBuildingQuestBuildings = "Edifícios" +QuestsBuildingQuestHeadline = "DERROTAR" +QuestsBuildingQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsBuildingQuestString = "Derrotar %s" +QuestsBuildingQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "um Edifício %(type)s" +QuestsBuildingQuestDescF = "um Edifício %(type)s de %(floors)s andares" +QuestsBuildingQuestDescC = "%(count)s Edifícios %(type)s" +QuestsBuildingQuestDescCF = "%(count)s Edifícios %(type)s de %(floors)s andares" +QuestsBuildingQuestDescI = "alguns Edifícios %(type)s" +QuestsBuildingQuestDescIF = "alguns Edifícios %(type)s de %(floors)s andares" + +QuestFactoryQuestFactory = "Fábrica" +QuestsFactoryQuestFactories = "Fábricas" +QuestsFactoryQuestHeadline = "DERROTAR" +QuestsFactoryQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsFactoryQuestString = "Derrotar %s" +QuestsFactoryQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "uma Fábrica %(type)s" +QuestsFactoryQuestDescC = "%(count)s Fábricas %(type)s" +QuestsFactoryQuestDescI = "algumas Fábricas %(type)s" + +QuestMintQuestMint = "Casa da Moeda" +QuestsMintQuestMints = "Casas da Moeda" +QuestsMintQuestHeadline = "DERROTAR" +QuestsMintQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsMintQuestString = "Derrotar %s" +QuestsMintQuestSCString = "Preciso derrotar %(objective)s%(location)s." + +QuestsMintQuestDesc = "uma Casa da Moeda dos Cogs" +QuestsMintQuestDescC = "%(count)s Casas da Moeda dos Cogs" +QuestsMintQuestDescI = "algumas Casas da Moeda dos Cogs" + +QuestsRescueQuestProgress = "%(progress)s de %(numToons)s salvos" +QuestsRescueQuestHeadline = "SALVAMENTO" +QuestsRescueQuestSCStringS = "Preciso salvar um Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "Preciso salvar alguns Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Salvar %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "um Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Salvar:" + +QuestsRescueNewNewbieQuestObjective = "Ajudar um novo Toon a salvar %s" +QuestsRescueOldNewbieQuestObjective = "Ajude um Toon com %(laffPoints)de risadas, ou menos, a resgatar %(objective)s" + +QuestCogPartQuestCogPart = "Parte do Processo Cog" +QuestsCogPartQuestFactories = "Fábricas" +QuestsCogPartQuestHeadline = "RECUPERAR" +QuestsCogPartQuestProgressString = "%(progress)s de %(num)s recuperados" +QuestsCogPartQuestString = "Recuperar %s" +QuestsCogPartQuestSCString = "Preciso recuperar %(objective)s%(location)s." +QuestsCogPartQuestAux = "Recuperar:" + +QuestsCogPartQuestDesc = "uma Parte do Processo Cog" +QuestsCogPartQuestDescC = "%(count)s Parte(s) do Processo Cog" +QuestsCogPartQuestDescI = "algumas Partes do Processo Cog" + +QuestsCogPartNewNewbieQuestObjective = "Ajude um novo Toon a recuperar %s" +QuestsCogPartOldNewbieQuestObjective = 'Ajude um Toon com %(laffPoints)de risadas, ou menos, a recuperar %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s de %(numGags)s entregues" +QuestsDeliverGagQuestHeadline = "ENTREGAR" +QuestsDeliverGagQuestToSCStringS = "Preciso entregar %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Preciso entregar algumas %(gagName)s." +QuestsDeliverGagQuestSCString = "Preciso fazer uma entrega." +QuestsDeliverGagQuestString = "Entregar %s" +QuestsDeliverGagQuestStringLong = "Entregar %s a _toNpcName_." +QuestsDeliverGagQuestInstructions = "Você pode comprar esta piada na Loja de Piadas quando tiver acesso a ela." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "ENTREGAR" +QuestsDeliverItemQuestSCString = "Preciso entregar %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Entregar %s" +QuestsDeliverItemQuestStringLong = "Entregar %s a _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITAR" +QuestsVisitQuestStringShort = "Visitar" +QuestsVisitQuestStringLong = "Visitar _toNpcName_" +QuestsVisitQuestSeeSCString = "Preciso ver %s." + +QuestsRecoverItemQuestProgress = "%(progress)s de %(numItems)s recuperados" +QuestsRecoverItemQuestHeadline = "RECUPERAR" +QuestsRecoverItemQuestSeeHQSCString = "Preciso ver um "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToHQSCString = "Preciso devolver %s para um "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToSCString = "Preciso devolver %(item)s para %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Preciso ir a um Quartel dos Toons." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Preciso ir ao Pátio %s." +QuestsRecoverItemQuestGoToStreetSCString = "Preciso ir %(to)s %(street)s em %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Preciso visitar %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Onde é %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Preciso recuperar %(item)s de %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Recuperar %(item)s de %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "ESCOLHER" +QuestsTrackChoiceQuestSCString = "Preciso escolher entre %(trackA)s e %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Talvez eu deva escolher %s." +QuestsTrackChoiceQuestString = "Escolha entre %(trackA)s e %(trackB)s" + +QuestsFriendQuestHeadline = "AMIGO" +QuestsFriendQuestSCString = "Preciso fazer um amigo." +QuestsFriendQuestString = "Fazer um amigo" + +QuestsMailboxQuestHeadline = "CORRESPONDÊNCIA" +QuestsMailboxQuestSCString = "Preciso verificar minha correspondência." +QuestsMailboxQuestString = "Verificar sua correspondência" + +QuestsPhoneQuestHeadline = "CLARABELA" +QuestsPhoneQuestSCString = "Preciso ligar para Clarabela." +QuestsPhoneQuestString = "Ligar para Clarabela" + +QuestsFriendNewbieQuestString = "Faça %d amigos %d risadas ou menos" +QuestsFriendNewbieQuestProgress = "%(progress)s de %(numFriends)s feitos" +QuestsFriendNewbieQuestObjective = "Faça amizade com %d novos Toons" + +QuestsTrolleyQuestHeadline = "BONDINHO" +QuestsTrolleyQuestSCString = "Preciso pegar o bondinho." +QuestsTrolleyQuestString = "Andar no bondinho" +QuestsTrolleyQuestStringShort = "Pegar o bondinho" + +QuestsMinigameNewbieQuestString = "%d Minijogos" +QuestsMinigameNewbieQuestProgress = "%(progress)s de %(numMinigames)s jogados" +QuestsMinigameNewbieQuestObjective = "Divirta-se com %d minijogos com a ajuda de novos Toons" +QuestsMinigameNewbieQuestSCString = "Preciso participar de minijogos com novos Toons." +QuestsMinigameNewbieQuestCaption = "Ajude um novo Toon %d risadas ou menos" +QuestsMinigameNewbieQuestAux = "Jogar:" + +QuestsMaxHpReward = "Seu Limite de risadas foi aumentado em %s." +QuestsMaxHpRewardPoster = "Recompensa: %s ponto de Acréscimo de risadas" + +QuestsMoneyRewardSingular = "Você ganha 1 balinha." +QuestsMoneyRewardPlural = "Você ganha %s balinhas." +QuestsMoneyRewardPosterSingular = "Recompensa: 1 balinha" +QuestsMoneyRewardPosterPlural = "Recompensa: %s balinhas" + +QuestsMaxMoneyRewardSingular = "Agora, você pode carregar 1 balinha." +QuestsMaxMoneyRewardPlural = "Agora, você pode carregar %s balinhas." +QuestsMaxMoneyRewardPosterSingular = "Recompensa: Carregue 1 balinha" +QuestsMaxMoneyRewardPosterPlural = "Recompensa: Carregue %s balinhas" + +QuestsMaxGagCarryReward = "Você ganha %(name)s. Agora, você pode carregar %(num)s piadas." +QuestsMaxGagCarryRewardPoster = "Recompensa: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "Agora, você pode ter %s Tarefas Toon." +QuestsMaxQuestCarryRewardPoster = "Recompensa: Carregue %s Tarefas Toon" + +QuestsTeleportReward = "Agora, você tem acesso por teletransporte a %s." +QuestsTeleportRewardPoster = "Recompensa: Acesso por teletransporte a %s" + +QuestsTrackTrainingReward = "Agora, você pode treinar para \"%s\" piadas." +QuestsTrackTrainingRewardPoster = "Recompensa: Treinamento de piadas" + +QuestsTrackProgressReward = "Agora, você tem o quadro %(frameNum)s da animação do tipo %(trackName)s." +QuestsTrackProgressRewardPoster = "Recompensa: \"Quadro %(frameNum)s da animação do tipo %(trackName)s\"" + +QuestsTrackCompleteReward = "Agora, você pode carregar e usar \"%s\" piadas." +QuestsTrackCompleteRewardPoster = "Recompensa: Treinamento final do tipo %s" + +QuestsClothingTicketReward = "Você pode trocar de roupa" +QuestsClothingTicketRewardPoster = "Recompensa: Bilhete de roupas" + +QuestsCheesyEffectRewardPoster = "Recompensa: %s" + +TIPQuestsClothingTicketReward = "Você pode trocar sua camisa por uma camisa DICA" +TIPQuestsClothingTicketRewardPoster = "Recompensa: Bilhete de Roupa DICA" + +QuestsCogSuitPartReward = "Agora, você tem uma %(cogTrack)s %(part)s peça de vestimenta de Cog." +QuestsCogSuitPartRewardPoster = "Recompensa: %(cogTrack)s %(part)s Peça" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "neste pátio" +QuestsStreetLocationThisStreet = "nesta rua" +QuestsStreetLocationNamedPlayground = "no pátio %s" +QuestsStreetLocationNamedStreet = "na %(toStreetName)s em %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "O edifício de %s's chama-se" +QuestsLocationBuildingVerb = "o qual é" +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Preciso terminar uma Tarefa Toon." + +# MaxGagCarryReward names +QuestsMediumPouch = "Sacola média" +QuestsLargePouch = "Sacola grande" +QuestsSmallBag = "Bolsa pequena" +QuestsMediumBag = "Bolsa média" +QuestsLargeBag = "Bolsa grande" +QuestsSmallBackpack = "Mochila pequena" +QuestsMediumBackpack = "Mochila média" +QuestsLargeBackpack = "Mochila grande" +QuestsItemDict = { + 1 : ["Par de óculos", "Pares de óculos", "um "], + 2 : ["Chave", "Chaves", "uma "], + 3 : ["Quadro-negro", "Quadros-negros", "um "], + 4 : ["Livro", "Livros", "um "], + 5 : ["Chocolate", "Chocolates", "um "], + 6 : ["Pedaço de giz", "Pedaços de giz", "um "], + 7 : ["Receita", "Receitas", "uma "], + 8 : ["Nota", "Notas", "uma "], + 9 : ["Calculadora", "Calculadoras", "uma "], + 10 : ["Pneu de carro de palhaço", "Pneus de carro de palhaço", "um "], + 11 : ["Bomba de ar", "Bombas de ar", "uma "], + 12 : ["Tinta de polvo", "Tintas de polvo", "uma "], + 13 : ["Pacotes", "Pacotes", "um "], + 14 : ["Recibo de peixe dourado", "Recibos de peixe dourado", "um "], + 15 : ["Peixe dourado", "Peixe dourado", "um "], + 16 : ["Óleo", "Óleos", "um pouco de "], + 17 : ["Graxa", "Graxas", "um pouco de "], + 18 : ["Água", "Águas", "uma "], + 19 : ["Relatório de engrenagens", "Relatórios de engrenagens", "um "], + 20 : ["Apagador de quadro-negro", "Apagadores de quadro-negro", "a "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 110 : ["Bilhete de Roupa DICA", "Bilhetes de Roupa", "um"], + 1000 : ["Bilhete de roupas", "Bilhetes de roupas", "um "], + + # Donald's Dock quest items + 2001 : ["Câmara de ar", "Câmaras de ar", "uma "], + 2002 : ["Receita de monóculo", "Receita de monóculo", "uma "], + 2003 : ["Armação de óculos", "Armações de óculos", "algumas "], + 2004 : ["Monóculo", "Monóculos", "um "], + 2005 : ["Grande peruca branca", "Grandes perucas brancas", "uma "], + 2006 : ["Alqueire de cascalho", "Alqueires de cascalho", "um "], + 2007 : ["Engrenagem Cog", "Engrenagens de Cog", "uma "], + 2008 : ["Carta marinha", "Cartas marinhas", "uma "], + 2009 : ["Braçadeira suja", "Braçadeiras sujas", "uma "], + 2010 : ["Braçadeira limpa", "Braçadeiras limpas", "uma "], + 2011 : ["Mola de relógio", "Molas de relógio", "uma "], + 2012 : ["Contrapeso", "Contrapesos", "um "], + + # Minnie's Melodyland quest items + 4001 : ["Estoque da Tina", "Estoques da Tina", ""], + 4002 : ["Estoque da Cavaca", "Estoques da Cavaca", ""], + 4003 : ["Formulário de estoque", "Formulários de estoque", "um "], + 4004 : ["Estoque da Fifi", "Estoques da Fifi", ""], + 4005 : ["Passagem do Alê Nhador", "Passagens do Alê Nhador", ""], + 4006 : ["Passagem da Tábata", "Passagens da Tábata", ""], + 4007 : ["Passagem do Barry", "Passagens do Barry", ""], + 4008 : ["Castanhola fosca", "Castanholas foscas", ""], + 4009 : ["Tinta de lula azul", "Tintas de lula azul", "obter "], + 4010 : ["Castanhola polida", "Castanholas polidas", "uma "], + 4011 : ["Letra de música do Léo", "Letras de músicas do Léo", ""], + + # Daisy's Gardens quest items + 5001 : ["Gravata de seda", "Gravatas de seda", "uma "], + 5002 : ["Terno listrado", "Ternos listrados", "um "], + 5003 : ["Tesoura", "Tesouras", "uma "], + 5004 : ["Cartão-postal", "Cartões-postais", "um "], + 5005 : ["Caneta", "Canetas", "uma "], + 5006 : ["Tinteiro", "Tinteiros", "um "], + 5007 : ["Bloco de notas", "Blocos de notas", "um "], + 5008 : ["Cofre de escritório", "Cofres de escritório", "um "], + 5009 : ["Saco de ração para pássaros", "Sacos de ração para pássaros", "um "], + 5010 : ["Roda dentada", "Rodas dentadas", "uma "], + 5011 : ["Salada", "Saladas", "uma "], + 5012 : ["Chave para os Jardins da Margarida", "Chaves para os Jardins da Margarida", "uma "], + 5013 : ["Mapa do "+lSellbotHQ, "Mapas do "+lSellbotHQ, "alguns "], + 5014 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5015 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5016 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5017 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + + # The Brrrgh quests + 3001 : ["Bola de futebol", "Bolas de futebol", "uma "], + 3002 : ["Tobogã", "Tobogãs", "um "], + 3003 : ["Cubo de gelo", "Cubos de gelo", "um "], + 3004 : ["Carta de amor", "Cartas de amor", "uma "], + 3005 : ["Cão-linguiça", "cães-linguiça", "um "], + 3006 : ["Anel de noivado", "Anéis de noivado", "um "], + 3007 : ["Bigode de sardinha", "Bigodes de sardinhas", "um pouco de "], + 3008 : ["Poção calmante", "Poções calmantes", "uma "], + 3009 : ["Dente quebrado", "Dentes quebrados", "um "], + 3010 : ["Dente de ouro", "Dentes de ouro", "um "], + 3011 : ["Pão de pinha", "Pães de pinha", "um "], + 3012 : ["Coco em pedaços", "Cocos em pedaços", "um pouco de "], + 3013 : ["Colher simples", "Colheres simples", "uma "], + 3014 : ["Sapo falante", "Sapos falantes", "um "], + 3015 : ["Casquinha de sorvete", "Casquinhas de sorvete", "uma "], + 3016 : ["Pó de peruca", "Pós de perucas", "um pouco de "], + 3017 : ["Patinho de borracha", "Patinhos de borracha", "um "], + 3018 : ["Dados de pelúcia", "Dados de pelúcia", "alguns "], + 3019 : ["Microfone", "Microfones", "um "], + 3020 : ["Teclado elétrico", "Teclados elétricos", "um "], + 3021 : ["Sapatos de plataforma", "Sapatos de plataforma", "alguns "], + 3022 : ["Caviar", "Caviar", "um pouco de "], + 3023 : ["Pó de arroz", "Pó de arroz", "um pouco de "], + 3024 : ["Fio", "Fios", "alguns " ], + 3025 : ["Agulha de Tricô", "Agulhas de Tricô", "uma "], + 3026 : ["Álibi", "Álibis", "um "], + 3027 : ["Termômetro Externo", "Termômetros Externos", "um "], + + #Dreamland Quests + 6001 : ["Plano do "+lCashbotHQ, "Planos do "+lCashbotHQ, "algum "], + 6002 : ["Vara de pescar", "Varas de pescar", "uma "], + 6003 : ["Cinto de segurança", "Cintos de segurança", "um "], + 6004 : ["Par de pinças", "Pares de pinças", "um "], + 6005 : ["Abajur de leitura", "Abajures de leitura", "um "], + 6006 : ["Cítara", "Cítaras", "uma "], + 6007 : ["Zamboni", "Zambonis", "uma "], + 6008 : ["Zabuton de zebra", "Zabutons de zebra", "uma "], + 6009 : ["Zinnias", "Zinnias", "alguns "], + 6010 : ["Discos de forró", "Discos de forró", "algum "], + 6011 : ["Abobrinha", "Abobrinhas", "uma "], + 6012 : ["Paletó zoot", "Paletós zoot", "um "], + + #Dreamland+1 quests + 7001 : ["Cama comum", "Camas comuns", "uma "], + 7002 : ["Cama elegante", "Camas elegantes", "uma "], + 7003 : ["Colcha azul", "Colchas azuis", "uma "], + 7004 : ["Colcha estampada", "Colchas estampadas ", "uma"], + 7005 : ["Travesseiros", "Travesseiros", "alguns "], + 7006 : ["Travesseiros duros", "Travesseiros duros ", "um"], + 7007 : ["Pijamas", "Pijamas", "um par de "], + 7008 : ["Pijamas com pés", "Pijamas com pés", "um par de "], + 7009 : ["Pijamas com pés marrons", "Pijamas com pés marrons", "um par de "], + 7010 : ["Pijamas com pés fúcsia", "Pijamas com pés fúcsia", "um par de "], + 7011 : ["Coral de couve-flor", "Coral de couve-flor", "algumas "], + 7012 : ["Alga-marinha viscosa", "Alga-marinha viscosa", "um "], + 7013 : ["Pilão", "Pilões", "um "], + 7014 : ["Pote de creme para rugas", "Potes de creme para rugas", "um "], + } + +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "em qualquer bairro" + +QuestsTailorFillin = "Costureiro" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Loja de Roupas" +QuestsTailorLocationNameFillin = "em qualquer bairro" +QuestsTailorQuestSCString = "Preciso ir ao Costureiro." + +QuestMovieQuestChoiceCancel = "Volte mais tarde se precisar de uma Tarefa Toon! Tchau!" +QuestMovieTrackChoiceCancel = "Volte quando já tiver decidido o que fazer! Tchau!" +QuestMovieQuestChoice = "Escolha uma Tarefa Toon." +QuestMovieTrackChoice = "Já decidiu o que escolher? Escolha um tipo ou volte mais tarde." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Agora, você está pronto.\aSaia e refresque a cabeça até descobrir que tipo você gostaria de escolher.\aEscolha bem, pois você não poderá mudar.\aQuando tiver certeza, volte aqui.", + INCOMPLETE_PROGRESS : "Escolha bem.", + INCOMPLETE_WRONG_NPC : "Escolha bem.", + COMPLETE : "Ótima escolha!", + LEAVING : "Boa sorte. Volte aqui quando tiver dominado sua nova habilidade.", + } + +QuestDialog_3225 = { + QUEST : "Puxa, obrigado por vir, _avName_!\aOs Cogs que estão no bairro assustaram o rapaz que faz as entregas.\aEu não tenho quem entregue esta salada para _toNpcName_!\aVocê poderia fazer isso por mim? Muitíssimo obrigado!_where_" + } + +QuestDialog_2910 = { + QUEST : "De volta tão rápido assim?\aÓtimo trabalho com aquela mola.\aO último item é um contrapeso.\aPasse lá, veja com _toNpcName_ e traga o que você conseguir._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs-chefe.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs-chefe. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs da Lei.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas rua e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs da Lei. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs Mercenários.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs Mercenários. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs Vendedores.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs Vendedores. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Parece que você precisa de novas piadas.\aVisite o Flippy, talvez ele possa ajudá-lo._where_" }, + 165 : {QUEST : "Olá.\aParece que você precisa praticar suas piadas.\aToda vez que você atinge um Cog com uma de suas piadas, sua experiência aumenta.\aQuando tiver experiência suficiente, você será capaz de usar uma piada ainda melhor.\aVá praticar suas piadas derrotando 4 Cogs."}, + 166 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs-chefe."}, + 167 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs da Lei."}, + 168 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs Vendedores."}, + 169 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs Mercenários."}, + 170 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ele pode dar alguns conselhos especiais para você._where_" }, + 171 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ele pode dar alguns conselhos especiais para você._where_" }, + 172 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ela pode dar alguns conselhos especiais para você._where_" }, + + 175 : {GREETING : "", + QUEST : "Você sabia que possui sua própria casa Toon?\aA vaca Clarabela administra um catálogo telefônico no qual você pode escolher e encomendar móveis para decorar sua casa.\aVocê também pode comprar frases do Chat Rápido, roupas e outras coisas muito legais!\aPedirei à Clarabela para enviar agora a você seu primeiro catálogo.\aVocê receberá um catálogo com novos itens toda semana!\aVá para sua casa e use o seu telefone para ligar para Clarabela.", + INCOMPLETE_PROGRESS : "Vá para casa e use o seu telefone para ligar para Clarabela.", + COMPLETE : "Espero que você se divirta fazendo encomendas com Clarabela!\a Acabei de redecorar minha casa. Está Toontástica!\aContinue com as Tarefas Toon para ganhar mais recompensas!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Lançamento e Esguicho são tipos ótimos, mas você vai precisar de mais piadas para lutar com Cogs de níveis mais altos.\aQuando você se juntar com outros Toons para enfrentar os Cogs, pode combinar ataques para conseguir danos maiores ao inimigo.\aTente diferentes combinações de Piadas para ver o que funciona melhor.\aPara o seu próximo tipo, escolha as Sonoras ou Toonar.\aAs Sonoras são especiais, pois quando atingem algum Cog, todos os outros também sofrem danos.\aAs Toonar permitem curar outros Toons durante a batalha.\aQuando estiver pronto para decidir, venha aqui e escolha uma.", + INCOMPLETE_PROGRESS : "De volta tão rápido? Ok, você está pronto para escolher?", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Boa decisão. Agora, antes de usar estas piadas, você deve treinar.\aVocê deve completar uma série de Tarefas Toon como treinamento.\aCada tarefa dará a você um único quadro da animação do seu ataque de piadas.\aQuando você coletar todas as 15, poderá obter a tarefa Treinamento final de piadas, que lhe permitirá usar suas novas piadas.\aVocê pode verificar seu progresso no Álbum Toon.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Visite _toNpcName_ se desejar transitar pela cidade com mais facilidade._where_" }, + 1040 : { QUEST : "Visite _toNpcName_ se desejar transitar pela cidade com mais facilidade._where_" }, + 1041 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1042 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1043 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1044 : { QUEST : "Puxa, obrigado por passar por aqui. Eu realmente preciso de ajuda.\aComo você pode ver, eu não tenho clientes.\aO meu livro de receitas secreto está perdido e ninguém mais vem ao meu restaurante.\aA última vez que eu o vi foi pouco antes de os Cogs tomarem meu edifício.\aVocê pode me ajudar recuperando quatro de minhas receitas favoritas?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu recuperar minhas receitas?" }, + 1045 : { QUEST : "Valeu mesmo!\aLogo terei de volta minha coleção completa e poderei reabrir meu restaurante.\aAh, há uma nota aqui para você - algo sobre acesso por teletransporte?\aDiz: \"obrigado por ajudar meu amigo e, por favor, entregue isto ao Quartel dos Toons\".\aBem, valeu mesmo - tchau!", + LEAVING : "", + COMPLETE : "Ah, sim, aqui diz que você foi de grande ajuda para alguns dos caras mais legais da Travessa dos Tontos.\aDiz também que você precisa de acesso por teletransporte para o Centro de Toontown.\aBem, considere concedido.\aAgora, você pode se teletransportar de volta para o pátio, de praticamente qualquer lugar de Toontown.\aBasta abrir o seu mapa e clicar em Centro de Toontown." }, + 1046 : { QUEST : "Os Robôs Mercenários têm importunado bastante a Financeira Dinheiro Feliz.\aPasse por lá e veja se há algo que você possa fazer._where_" }, + 1047 : { QUEST : "Os Robôs Mercenários têm se infiltrado no banco e roubado nossas calculadoras.\aRecupere 5 calculadoras dos Robôs Mercenários.\aPara evitar que você fique indo para lá e para cá, traga-as todas de uma vez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda procurando pelas calculadoras?" }, + 1048 : { QUEST : "Uau! Valeu mesmo por encontrar nossas calculadoras.\aHumm... Elas parecem danificadas.\aVocê poderia levá-las para a loja de _toNpcName_, \"Máquinas de Cosquinhas\", nesta rua?\aVeja se podem consertá-las.", + LEAVING : "", }, + 1049 : { QUEST : "O que é isto? Calculadoras quebradas?\aRobôs Mercenários?\aBem, vamos dar uma olhada...\aÉ, as engrenagens estão partidas mas eu estou sem essa peça...\aSabe o que poderia dar jeito? Algumas engrenagens de Cog, das grandes, dos Cogs maiores...\aEngrenagens de Cogs de nível 3 devem servir. Precisarei de 2 para cada máquina, 10 no total.\aTraga-as todas de uma vez e eu as consertarei!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Lembre-se, eu preciso de 10 engrenagens para consertar as máquinas." }, + 1053 : { QUEST : "Ah sim, isto deve servir.\aTudo consertado agora, grátis.\aLeve-as de volta para a Dinheiro Feliz e diga olá a ela por mim.", + LEAVING : "", + COMPLETE : "Calculadoras consertadas?\aBom trabalho. Tenho certeza de que tenho algo por aqui para recompensar você..." }, + 1054 : { QUEST : "_toNpcName_ precisa de alguma ajuda com seus carros de palhaço._where_" }, + 1055 : { QUEST : "Oláááá! Eu não consigo encontrar os pneus para este carro de palhaço em lugar nenhum!\aVocê acha que pode me ajudar?\aEu acho que o Tito Tonto pode ter jogado os pneus no lago do pátio do Centro de Toontown.\aSe você ficar em um dos cais de lá, poderá tentar pescar os pneus para mim.", + GREETING : "Iuhuu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar os 4 pneus?" }, + 1056 : { QUEST : "Demorôô! Agora, este velho carro de palhaço vai poder voltar às ruas!\aEi, eu pensei que tivesse uma bomba de ar aqui para inflar estes pneus...\aAcho que _toNpcName_ pegou emprestado.\aVocê poderia pedir de volta para mim?_where_", + LEAVING : "" }, + 1057 : { QUEST : "E aí?\aUma bomba de pneus?\aVamos fazer o seguinte: você me ajuda a retirar das ruas alguns desses Cogs de alto nível...\aE, então, darei a você a bomba de pneus.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Isso é o melhor que você pode fazer?" }, + 1058 : { QUEST : "Bom trabalho! Eu sabia que você conseguiria.\aAqui está a bomba. Estou certo de que _toNpcName_ ficará feliz em recebê-la de volta.", + LEAVING : "", + GREETING : "", + COMPLETE : "Dez! Agora está tudo certo!\aPor falar nisso, obrigado por me ajudar.\aAqui, tome isto." }, + 1059 : { QUEST : "_toNpcName_ está com poucos suprimentos. Quem sabe você pode ajudá-lo?_where_" }, + 1060 : { QUEST : "Valeu mesmo por passar aqui!\aOs Cogs roubam sempre a minha tinta e, por isso, ela está quase no fim.\aVocê poderia pescar um pouco de tinta de polvo para mim no lago?\aPara pescar, basta ficar parado em um cais perto do lago.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar?" }, + 1061 : { QUEST : "Ótimo, valeu pela tinta!\aSabe de uma coisa, se você eliminasse alguns daqueles Ratos de Escritório...\aAí minha tinta não acabaria tão rápido.\aDerrote 6 Ratos de Escritório no Centro de Toontown para receber sua recompensa.", + LEAVING : "", + COMPLETE : "Valeu! Vou recompensar você pela sua ajuda.", + INCOMPLETE_PROGRESS : "Eu acabei de ver mais alguns Ratos de Escritório." }, + 1062 : { QUEST : "Ótimo, valeu pela tinta!\aSabe de uma coisa? Se você eliminasse alguns daqueles Sanguessugas...\aAí minha tinta não acabaria tão rápido.\aDerrote 6 Sanguessugas no Centro de Toontown para receber sua recompensa.", + LEAVING : "", + COMPLETE : "Valeu! Vou recompensar você pela sua ajuda.", + INCOMPLETE_PROGRESS : "Eu acabei de ver mais alguns Sanguessugas." }, + 900 : { QUEST : "Fiquei sabendo que _toNpcName_ precisa de ajuda com um pacote._where_" }, + 1063 : { QUEST : "Olá! Legal você ter vindo.\aUm Cog roubou um pacote muito importante bem debaixo do meu nariz.\aVeja se você consegue recuperá-lo. Eu acho que ele era de nível 3...\aEntão, derrote Cogs de nível 3 até encontrar meu pacote.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1067 : { QUEST : "É ele mesmo, está tudo certo!\aEi, o endereço está borrado...\aTudo o que eu posso ler é que é para um Dr. - o resto está ilegível.\aTalvez seja para _toNpcName_? Você pode levar para ele?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Eu não estava esperando um pacote. Talvez seja para o Dr. E.U. Fórico.\aMeu assistente ia passar mesmo lá hoje, então pedirei a ele que verifique para você.\aNesse meio tempo, você se importaria de se livrar de alguns dos Cogs que estão na minha rua?\aDerrote 10 Cogs no Centro de Toontown.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Meu assistente ainda não voltou." }, + 1069 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô Mercenário roubou o pacote de meu assistente no caminho de volta.\aVocê poderia tentar pegá-lo de volta?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1070 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô Vendedor roubou o pacote de meu assistente no caminho de volta.\aSinto muito, mas você terá que encontrar esse Robô Vendedor para pegá-lo de volta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1071 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô-chefe roubou o pacote de meu assistente no caminho de volta.\aVocê poderia tentar pegá-lo de volta?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1072 : { QUEST : "Ótimo, você o pegou de volta!\aTalvez você deva tentar entregá-lo a _toNpcName_, pode ser para ele._where_", + LEAVING : "" }, + 1073 : { QUEST : "Puxa, obrigado por trazer meus pacotes para mim.\aEspere um segundo, eu estava esperando dois. Você poderia verificar com _toNpcName_ e ver se ele está com o outro?", + INCOMPLETE : "Conseguiu encontrar meu outro pacote?", + LEAVING : "" }, + 1074 : { QUEST : "Ele disse que havia outro pacote? Talvez os Cogs o tenham roubado também.\aDerrote Cogs até encontrar o segundo pacote.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o outro pacote, né?" }, + 1075 : { QUEST : "No final das contas, acho que não havia um segundo pacote!\aCorra e leve-o para _toNpcName_, com minhas desculpas.", + COMPLETE : "Ei, meu pacote está aqui!\aJá que você parece ser um Toon tão prestativo, isto vai ser fichinha.", + LEAVING : "" }, + 1076 : { QUEST : "Houve alguns problemas na Peixinhos Dourados Ki-late.\a_toNpcName_ provavelmente podem precisar de você._where_" }, + 1077 : { QUEST : "Legal você ter vindo. Os Cogs roubaram todos os meus peixes dourados.\aEu acho que os Cogs querem vendê-los para ganhar dinheiro fácil.\aHá muitos anos, aqueles 5 peixes têm sido minhas únicas companhias nesta pequena loja ...\aSe você pudesse recuperá-los, eu agradeceria muito.\aTenho certeza de que os Cogs estão com meus peixes.\aDerrote Cogs até encontrar meus peixes dourados.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Consiga meus peixes dourados de volta." }, + 1078 : { QUEST : "Puxa, você recuperou meus peixes!\aHã? O que é isto - um recibo?\aAi, ai... Acho que eles são Cogs mesmo.\aEu não consigo decifrar este recibo. Você poderia levá-lo para _toNpcName_ e ver se ele consegue lê-lo?_where_", + INCOMPLETE : "O que _toNpcName_ disse sobre o recibo?", + LEAVING : "" }, + 1079 : { QUEST : "Humm, deixe-me ver este recibo.\a...Ah, sim, diz que 1 peixe dourado foi vendido para um Puxa-saco.\aO recibo não menciona o que aconteceu com os outros 4 peixes.\aTalvez você deva tentar encontrar esse Puxa-saco.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que não há mais nada em que eu possa ajudar.\aPor que você não tenta encontrar aquele peixe dourado?" }, + 1092 : { QUEST : "Humm, deixe-me ver este recibo.\a...Ah, sim, diz que 1 peixe dourado foi vendido para um Farsante.\aO recibo não menciona o que aconteceu com os outros 4 peixes.\aTalvez você deva tentar encontrar esse Farsante.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que não há mais nada em que eu possa ajudar.\aPor que você não tenta encontrar aquele peixe dourado?" }, + 1080 : { QUEST : "Ah, graças aos céus! Você encontrou Oscar - ele é o meu favorito.\aO que foi, Oscar? Hã-hã... Verdade? ... Estão?\aOscar diz que os outros 4 escaparam para dentro do lago no pátio.\aVocê poderia reuni-los para mim?\aÉ só pescá-los no lago.", + LEAVING : "", + COMPLETE : "Nossa, estou tããão feliz! Estou junto com meus companheiros novamente!\aVocê merece uma bela recompensa por isso!", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar esses peixes?" }, + 1081 : { QUEST : "_toNpcName_ parece estar numa situação grudenta. Ela, com certeza, apreciaria alguma ajuda._where_" }, + 1082 : { QUEST : "Eu derramei supercola e estou presa - presa pra valer!\aSe houver uma maneira de sair, eu gostaria de saber.\aIsso me dá uma ideia; abra os olhos.\aDerrote alguns Robôs Vendedores e traga de volta um pouco de óleo.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1083 : { QUEST : "Bem, o óleo ajudou um pouco, mas eu ainda não consigo me mexer.\aO que mais poderia ajudar? É difícil dizer.\aIsso me dá uma ideia; vale a pena tentar.\aDerrote alguns Robôs da Lei e me traga graxa.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1084 : { QUEST : "Não, isso não ajudou. Isso realmente não é engraçado.\aEu coloquei a graxa bem ali,\aIsso me dá uma ideia, não me deixe esquecer.\aDerrote alguns Robôs Mercenários e traga água para umedecer.", + LEAVING : "", + GREETING : "", + COMPLETE : "Oba! Estou livre da supercola,\aComo recompensa, dou este presente a você.\aVocê pode rir um pouco mais enquanto luta e, então...\aAh, não! Já estou presa aqui novamente!", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1085 : { QUEST : "_toNpcName_ está fazendo uma pesquisa sobre os Cogs.\aVá falar com ele para ver se ele precisa da sua ajuda._where_" }, + 1086 : { QUEST : "É verdade, estou fazendo um estudo sobre os Cogs.\aEu quero aprender sobre o comportamento deles.\aCom certeza ajudaria se você pudesse reunir algumas engrenagens de Cogs.\aMas elas têm que ser de Cogs de nível 2, pelo menos, para serem grandes o suficiente para o exame visual.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu encontrar engrenagens suficientes?" }, + 1089 : { QUEST : "Certo, vamos dar uma olhada. Estas são amostras excelentes!\aHummm...\aCerto, aqui está meu relatório. Leve isto de volta imediatamente para o Quartel dos Toons.", + INCOMPLETE : "Você entregou meu relatório no Quartel?", + COMPLETE : "Bom trabalho _avName_, nós assumiremos a partir daqui.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ tem informações úteis para você._where_" }, + 1091 : { QUEST : "Fiquei sabendo que o Quartel dos Toons está trabalhando em uma espécie de Radar de Cogs.\aEle permite ver onde os Cogs estão, para que seja mais fácil encontrá-los.\aA Página de Cogs em seu Álbum Toon é a chave.\aAo derrotar Cogs suficientes, você pode sintonizar os sinais deles e rastrear onde estão.\aContinue derrotando Cogs para ficar pronto.", + COMPLETE : "Bom trabalho! Você provavelmente vai poder fazer uso disso...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Agora, você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Aqueles cogs traiçoeiros estão envolvidos nisto novamente.\a_toNpcName_ reportou outro item ausente. Pare um pouco aqui e veja se consegue acertar isso._where_" }, + 2202 : { QUEST : "Oi, _avName_. Ainda bem que você está aqui. Um Mão de vaca de má aparência acabou de passar por aqui e saiu com uma câmara de ar.\aTemo que ele possa usá-la para seus planos diabólicos.\aVeja se você consegue encontrá-la e trazê-la de volta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha câmara de ar?", + COMPLETE : "Você encontrou minha câmara de ar! Você é legal MESMO! Olha aqui, tome a sua recompensa...", + }, + 2203 : { QUEST : "Os cogs estão espalhando o caos no banco.\aVá até o Capitão Carlão e veja o que você pode fazer._where_" }, + 2204 : { QUEST : "Bem-vindo a bordo, colega.\aDroga! Aqueles cogs patifes quebraram meu monóculo e eu não vivo sem ele.\aSeja um bom marujo e leve esta receita para o Dr. Quiqueres para trazer um novo para mim._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "O que é isso?\aPuxa, eu adoraria poder trabalhar nesta receita, mas os cogs têm furtado meus suprimentos.\aSe você pegasse a armação dos óculos de um Puxa-saco eu provavelmente poderia ajudá-lo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sinto muito. Sem armações de Puxa-saco, não tem monóculo!", + }, + 2206: { QUEST : "Excelente!\aSó um segundo...\aSua receita está pronta. Leve este monóculo diretamente ao Capitão Carlão._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Alto!\aVocê vai ganhar sua condecoração, afinal de contas.\aAqui está.", + }, + 2207 : { QUEST : "Há um Cog na loja da Craca Bárbara!\aÉ melhor você ir para lá imediatamente._where_" }, + 2208 : { QUEST : "Droga! Você se desencontrou dele, gracinha.\aHavia um Golpe Sujo aqui. Ele levou a minha grande peruca branca.\aEle disse que era para o chefe dele e mencionou algo como \"precedente legal\".\aSe você puder pegá-la de volta, ficarei eternamente grata.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Ainda não o encontrou?\aEle é alto e tem uma cabeça pontuda.", + COMPLETE : "Você a encontrou!?!?\aVocê é uma gracinha!\aSua recompensa é mais do que merecida...", + }, + 2209 : { QUEST : "Moby está se preparando para uma viagem importante.\aVisite-o e veja o que pode fazer para ajudá-lo._where_"}, + 2210 : { QUEST : "Sua ajuda será bem-vinda.\aO Quartel dos Toons me pediu para fazer uma viagem e ver se consigo descobrir de onde os cogs estão vindo.\aPrecisarei de algumas coisas para o meu navio, mas não tenho muitas balinhas.\aPasse pela loja da Alice e pegue um pouco de cascalho para mim. Você terá que fazer um favor para ela para poder pegar o cascalho._where_", + GREETING : "E aí, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Então, o Moby quer cascalho, né?\aEle ainda está me devendo por aquele último alqueire.\aEu lhe darei se você conseguir eliminar cinco Microempresários na minha rua.", + INCOMPLETE_PROGRESS : "Não, seu bobinho! Eu disse CINCO microempresários...", + GREETING : "O que posso fazer por você?", + LEAVING : "", + }, + 2212 : { QUEST : "Trato é trato.\aAqui está o cascalho para aquele fominha do Moby._where_", + GREETING : "Ora, ora, o que temos aqui...", + LEAVING : "", + }, + 2213 : { QUEST : "Excelente trabalho. Eu sabia que ela encontraria uma saída.\aAgora, eu preciso pegar uma carta de navegação com o Mário.\aAcho que meu crédito lá também não é tão bom, portanto, você vai ter que negociar com ele._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Sim, eu tenho a carta de navegação que o Moby quer.\aE se você estiver disposto a trabalhar para consegui-la, eu a darei para você.\aEstou tentando construir um astrolábio para navegar pelas estrelas.\aPreciso de três engrenagens de Cog para construí-la.\aVolte aqui quando encontrá-las.", + INCOMPLETE_PROGRESS: "Como está indo com aquelas engrenagens de Cog?", + GREETING : "Bem-vindo!", + LEAVING : "Boa sorte!", + }, + 2215 : { QUEST : "Oh! Essas engrenagens vão ser úteis mesmo.\aAqui está a carta. Leve para o Moby, com meus cumprimentos._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bem, agora sim. Estou pronto para zarpar!\aEu o levaria comigo se você não fosse novato. Leve isto, então.", + }, + 901 : { QUEST : "Se estiver disposto, o Salgado está precisando de ajuda na loja dele..._where_", + }, + 2902 : { QUEST : "Você é o novo recruta?\aBom, bom. Talvez você possa me ajudar.\aEstou construindo um caranguejo pré-fabricado gigante para confundir os cogs.\aEu vou precisar de uma braçadeira. Visite o Mário e me traga uma._where_", + }, + 2903 : { QUEST : "Olá!\aSim, eu ouvi falar no caranguejo gigante que Salgado está construindo.\aA melhor braçadeira que tenho está meio suja.\aSeja gentil e passe pela lavanderia antes de levá-la para ele._where_", + LEAVING : "Valeu!" + }, + 2904 : { QUEST : "Você deve ser o amigo do Mário.\aAcho que posso limpar isso rapidinho.\aSó um minuto...\aAqui está. Nova em folha!\aDiga olá ao Salgado por mim._where_", + }, + 2905 : { QUEST : "Ah, era exatamente o que eu queria.\aJá que você está aqui, eu também vou precisar de uma mola de relógio de corda bem grande.\aVá até a loja do Gancho e veja se ele tem uma._where_", + }, + 2906 : { QUEST : "Uma mola bem grande?\aSinto muito, mas a maior que tenho ainda é pequena.\aTalvez eu consiga montar uma com as molas do gatilho de revólver de água.\aTraga-me três dessas piadas e eu vou ver o que posso fazer.", + }, + 2907 : { QUEST : "Vamos dar uma olhada...\aArrasou. Simplesmente arrasou.\aAlgumas vezes eu surpreendo até a mim mesmo.\aAqui está: uma mola grande para o Salgado!_where_", + LEAVING : "Bon Voyage!", + }, + 2911 : { QUEST : "Ficaria feliz em ajudar nisso, _avName_.\aMas temo que as ruas não estejam mais tão seguras.\aPor que você não vai derrotar alguns Robôs Mercenários? Depois a gente conversa.", + INCOMPLETE_PROGRESS : "Eu ainda acho que você precisa fazer que as ruas fiquem mais seguras.", + }, + 2916 : { QUEST : "Sim, eu tenho um peso para o Salgado.\aNo entanto, acho que seria mais seguro se você derrotasse alguns Robôs Vendedores primeiro.", + INCOMPLETE_PROGRESS : "Ainda não. Derrote mais alguns Robôs Vendedores.", + }, + 2921 : { QUEST : "Humm, acho que poderia ceder um peso.\aMas eu me sentiria melhor se não houvesse tantos Robôs-chefe por aí.\aDerrote seis deles e volte aqui.", + INCOMPLETE_PROGRESS : "Acho que ainda não está seguro...", + }, + 2925 : { QUEST : "Tudo pronto?\aBem, acho que agora está suficientemente seguro.\aAqui está o contrapeso para o Salgado._where_" + }, + 2926 : {QUEST : "Bem, isso é tudo.\aDeixe-me ver se funciona.\aHumm, um pequeno problema.\aNão estou conseguindo obter energia, pois aquele edifício Cog está bloqueando meu painel solar.\aVocê poderia dominá-lo para mim?", + INCOMPLETE_PROGRESS : "Ainda sem energia. E aquele edifício?", + COMPLETE : "Súper! Você é um destruidor de cogs e tanto! Tome isto aqui como recompensa...", + }, + 3200 : { QUEST : "Acabo de receber uma ligação do _toNpcName_.\aEle está tendo um dia difícil. Talvez você possa ajudá-lo!\aPasse por lá e veja do que ele precisa._where_" }, + 3201 : { QUEST : "Puxa, obrigado por vir!\aPreciso de alguém para levar esta nova gravata de seda para _toNpcName_.\aVocê poderia fazer isso para mim?_where_" }, + 3203 : { QUEST : "Ah, esta deve ser a gravata que eu pedi! Obrigado!\aEla combina com o terno listrado que acabei de terminar, logo ali.\aEi, o que aconteceu com o terno?\aOh, não! Os Cogs devem ter roubado meu terno novo!\aDerrote Cogs até encontrar meu terno e traga-o de volta para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já encontrou meu terno? Tenho certeza de que os Cogs o pegaram!", + COMPLETE : "Legal! Você encontrou meu terno novo!\aViu, eu disse que os Cogs estavam com ele! Aqui está a sua recompensa...", + }, + + 3204 : { QUEST : "_toNpcName_ acabou de ligar para informar um roubo.\aPor que você não passa por lá e vê se consegue resolver as coisas?_where_" }, + 3205 : { QUEST : "Olá, _avName_! Você veio me ajudar?\aAcabei de expulsar um Sanguessuga de minha loja. Puxa! Foi horrível.\aMas agora não encontro minha tesoura em lugar nenhum! Tenho certeza de que o Sanguessuga a levou.\aEncontre-o e recupere minha tesoura.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você ainda está procurando minha tesoura?", + COMPLETE : "Minha tesoura! Valeu mesmo, viu? Aqui está a sua recompensa...", + }, + + 3206 : { QUEST : "Parece que _toNpcName_ está tendo problemas com alguns Cogs.\aVá ver se você pode ajudá-lo._where_" }, + 3207 : { QUEST : "Oi, _avName_! Obrigado por vir!\aUm monte de Duplos Sentidos invadiu minha loja e roubou uma pilha de cartões-postais de meu balcão.\aVá e derrote todos os Duplos Sentidos e recupere meus cartões-postais!", + INCOMPLETE_PROGRESS : "Não há cartões-postais suficientes! Continue procurando!", + COMPLETE : "Ah, valeu! Agora eu posso entregar a correspondência na hora certa! Aqui está a sua recompensa...", + }, + + 3208 : { QUEST : "Ultimamente temos recebido reclamações dos moradores sobre os Reis da Incerta.\aVeja se consegue derrotar 10 Reis da Incerta para ajudar nossos colegas Toons nos Jardins da Margarida." }, + 3209 : { QUEST : "Valeu mesmo por derrotar os Reis da Incerta!\aMas agora os Operadores de Telemarketing ficaram fora de controle.\aDerrote 10 Operadores de Telemarketing nos Jardins da Margarida e volte aqui para pegar sua recompensa." }, + + 3247 : { QUEST : "Ultimamente, temos recebido reclamações dos moradores sobre os Sanguessugas.\aVeja se consegue derrotar 20 Sanguessugas para ajudar nossos colegas Toons nos Jardins da Margarida." }, + + + 3210 : { QUEST : "Oh, não, a Seivas Florais da Rua das Amendoeiras está sem flores!\aPara ajudar, leve dez de suas flores com esguicho.\aMas veja primeiramente se tem realmente 10 flores com esguicho em seu estoque.", + LEAVING: "", + INCOMPLETE_PROGRESS : "Preciso ter 10 flores com esguicho. Você não tem o suficiente!" }, + 3211 : { QUEST : "Puxa, valeu mesmo, viu? Estas flores com esguicho vão salvar a pátria.\aMas estou com medo daqueles Cogs lá fora.\aVocê pode me ajudar e derrotar alguns desses Cogs?\aVolte aqui depois de derrotar 20 Cogs nesta rua.", + INCOMPLETE_PROGRESS : "Ainda há Cogs lá fora para serem derrotados! Continue trabalhando!", + COMPLETE : "Ah, valeu! Isso ajudou muito. Sua recompensa é...", + }, + + 3212 : { QUEST : "_toNpcName_ precisa de ajuda para procurar por algo que ela perdeu.\aVá visitá-la e veja o que pode fazer._where_" }, + 3213 : { QUEST : "Oi, _avName_. Você pode me ajudar?\aNão sei onde coloquei minha caneta. Acho que alguns Cogs pegaram-na.\aDerrote Cogs para encontrar minha caneta roubada.", + INCOMPLETE_PROGRESS : "Você já encontrou minha caneta?" }, + 3214 : { QUEST : "Sim, é a minha caneta! Valeu!\aMas, enquanto você estava fora, eu percebi que meu tinteiro também desapareceu.\aDerrote Cogs para encontrar meu tinteiro.", + INCOMPLETE_PROGRESS : "Ainda estou procurando meu tinteiro!" }, + 3215 : { QUEST : "Demais! Agora tenho minha caneta e meu tinteiro de volta!\aMas você nem vai acreditar!\aMeu bloco de notas sumiu! Eles devem tê-lo roubado também!\aDerrote Cogs para encontrar meu bloco de notas roubado e, então, traga-o de volta para ter sua recompensa.", + INCOMPLETE_PROGRESS : "E meu bloco de notas?" }, + 3216 : { QUEST : "É o meu bloco de notas! Maneiro! Sua recompensa é...\aEi! Onde ela está?\aSua recompensa estava bem aqui no cofre de meu escritório. Mas o cofre inteiro sumiu!\aDá para acreditar? Aqueles cogs roubaram sua recompensa!\aDerrote Cogs para recuperar meu cofre.\aQuando você o trouxer de volta, eu lhe darei sua recompensa.", + INCOMPLETE_PROGRESS : "Continue procurando o cofre! Sua recompensa está lá dentro!", + COMPLETE : "Finalmente! Seu novo saco de piadas está dentro daquele cofre. Aqui está...", + }, + + 3217 : { QUEST : "Temos feito alguns estudos sobre a mecânica dos Robôs Vendedores.\aNós ainda precisamos estudar algumas peças de forma mais detalhada.\aTraga-nos uma roda dentada de algum Dr. Sabe-com-quem-está-falando.\aVocê poderá conseguir uma quando o Cog estiver explodindo." }, + 3218 : { QUEST : "Muito bom! Agora precisamos de uma roda dentada de um Amigo da Onça.\aEstas são mais difíceis de conseguir, portanto, continue tentando." }, + 3219 : { QUEST : "Demais! Agora precisamos de apenas mais uma roda dentada.\aDesta vez, precisamos de uma de um Agitador.\aTalvez você precise procurar esses Cogs nos edifícios dos Robôs Vendedores.\aQuando achar a roda, traga-a aqui para receber sua recompensa." }, + + 3244 : { QUEST : "Temos feito alguns estudos sobre a mecânica dos Robôs da Lei.\aNós ainda precisamos estudar algumas peças de forma mais detalhada.\aTraga-nos uma roda dentada de algum Perseguidor de Ambulâncias.\aVocê poderá conseguir uma quando o Cog estiver explodindo." }, + 3245 : { QUEST : "Muito bom! Agora precisamos de uma roda dentada de um Golpe Sujo.\aEstas são mais difíceis de conseguir, portanto, continue tentando." }, + 3246 : { QUEST : "Demais! Agora precisamos de apenas mais uma roda dentada.\aDesta vez, de um Relações Públicas.\aQuando pegá-la, traga-a aqui para conseguir sua recompensa." }, + + 3220 : { QUEST : "Acabei de saber que _toNpcName_ estava perguntando por você.\aPor que você não passa por lá e vê o que ela quer?_where_" }, + 3221 : { QUEST : "Oi, _avName_! Aí está você!\aOuvi dizer que você é especialista em ataques com esguicho.\aPreciso de alguém para dar um bom exemplo a todos os Toons nos Jardins da Margarida.\aUse seus ataques com esguicho para derrotar vários Cogs.\aIncentive seus amigos a usarem o esguicho também.\aQuando tiver derrotado 20 Cogs, volte aqui para pegar sua recompensa!" }, + + 3222 : { QUEST : "É hora de demonstrar sua Toonmizade.\aSe você recuperar, com sucesso, um número de edifícios de Cogs, ganhará o direito de fazer três buscas.\aPrimeiramente, derrote dois edifícios de Cogs.\aSinta-se à vontade para chamar seus amigos para ajudá-lo."}, + 3223 : { QUEST : "Bom trabalho naqueles edifícios!\aAgora, derrote mais dois.\aOs edifícios devem ter, pelo menos, dois andares." }, + 3224 : { QUEST : "Fantástico!\aAgora é só derrotar mais dois edifícios.\aEles devem ter, pelo menos, três andares.\aQuando terminar, volte para pegar sua recompensa!", + COMPLETE : "Você conseguiu, _avName_!\aVocê demonstrou uma elevada Toonmizade.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ diz que precisa de ajuda.\aPor que você não vai até lá e vê o que pode fazer para ajudá-la?_where_" }, + 3235 : { QUEST : "Ah, esta é a salada que pedi!\aObrigada por trazê-la para mim.\aTodos esses Cogs devem ter amedrontado novamente o entregador de _toNpcName_ .\aPor que você não nos faz um favor e derrota alguns desses Cogs lá fora?\aDerrote 10 Cogs nos Jardins da Margarida e, então, vá até _toNpcName_.", + INCOMPLETE_PROGRESS : "Você está trabalhando na eliminação de Cogs para mim?\aIsto é maravilhoso! Continue com o bom trabalho!", + COMPLETE : "Oh, muito obrigada por derrotar aqueles Cogs!\aAgora, acho que poderei manter minha escala normal de entregas.\aSua recompensa é...", + INCOMPLETE_WRONG_NPC : "Vá contar a _toNpcName_ sobre os Cogs que você derrotou._where_" }, + + 3236 : { QUEST : "Há muitos Robôs da Lei por aí.\aVocê pode fazer sua parte para ajudar!\aDerrote 3 edifícios de Robôs da Lei." }, + 3237 : { QUEST : "Bom trabalho naqueles edifícios de Robôs da Lei!\aMas agora há muitos Robôs Vendedores!\aDerrote 3 edifícios de Robôs Vendedores e volte para buscar sua recompensa." }, + + 3238 : { QUEST : "Ah não! Um Cog \"Amizade Fácil\" roubou a Chave para os Jardins da Margarida!\aVeja se você consegue recuperá-la.\aLembre-se, o Amizade Fácil só pode ser encontrado dentro dos edifícios de Robôs Vendedores." }, + 3239 : { QUEST : "Você achou uma chave, tudo bem, mas esta não é a correta!\aPrecisamos da chave dos Jardins da Margarida.\aContinue de olho! Ela ainda está com algum Cog \"Amizade Fácil\"!" }, + + 3242 : { QUEST : "Ah não! Um Cog Macaco velho roubou a Chave para os Jardins da Margarida!\aVeja se você consegue recuperá-la.\aLembre-se, os Macacos-velhos só podem ser encontrados dentro dos edifícios de Robôs da Lei." }, + 3243 : { QUEST : "Você achou uma chave, tudo bem, mas esta não é a correta!\aPrecisamos da chave dos Jardins da Margarida.\aContinue de olho! Ela ainda está com algum Cog Macaco velho!" }, + + 3240 : { QUEST : "Acabei de saber que um Macaco velho roubou um saco de ração para pássaros de _toNpcName_ .\aDerrote Macacos velhos até recuperar a ração para pássaros do Florêncio e levá-la de volta para ele.\aOs Macacos velhos só são encontrados dentro de edifícios de Robôs da Lei._where_", + COMPLETE : "Ah, muito obrigado por encontrar minha ração para pássaros!\aSua recompensa é...", + INCOMPLETE_WRONG_NPC : "Bom trabalho na recuperação da ração para pássaros!\aAgora, leve-a para _toNpcName_._where_", + }, + + 3241 : { QUEST : "Alguns dos edifícios de Cogs estão ficando altos demais e isso já está incomodando.\aVeja se você consegue derrubar alguns dos edifícios mais altos.\aRecupere 5 edifícios de 3 andares, ou mais altos, e volte para pegar sua recompensa.", + }, + + 3250 : { QUEST : "A Detetive Linda da Rua dos Carvalhos recebeu informações sobre um Quartel de Robôs Vendedores.\aVá até lá e ajude-a a investigar.", + }, + 3251 : { QUEST : "Há algo estranho acontecendo por aqui.\aHá tantos Robôs Vendedores!\aOuvi dizer que eles organizaram seu próprio quartel no final desta rua.\aVá até lá e veja o que consegue descobrir.\aEncontre Cogs Robôs Vendedores em seu quartel, derrote 5 deles e volte aqui.", + }, + 3252 : { QUEST : "Ok, desembucha.\aO que você disse?\aQuartel de Robôs Vendedores?? Ah não!!! Algo tem que ser feito.\aDevemos avisar a Juíza Gala. Ela saberá o que fazer.\aVá até lá e conte a ela o que descobrimos. É só descer a rua.", + }, + 3253 : { QUEST : "Sim, posso ajudá-lo? Estou muito ocupada.\aHã? Quartel de Cogs?\aHã? Besteira. Isto nunca poderia acontecer.\aVocê deve estar enganado. Absurdo.\aHã? Não discuta comigo.\aOk, então, traga alguma prova.\aSe os Robôs Vendedores realmente estão construindo este Quartel de Cogs, qualquer Cog de lá estará carregando mapas.\aCogs amam trabalhar com papelada, sabe?\aDerrote Robôs Vendedores até encontrar os mapas.\aTraga-os aqui, e eu talvez acredite em você.", + }, + 3254 : { QUEST : "Você de novo, hã? Mapas? Você está com eles?\aDeixe-me vê-los! Humm... Uma fábrica?\aDeve ser lá que eles estão construindo os Robôs Vendedores... E o que é isso?\aSim, exatamente como eu suspeitava. Eu sabia o tempo todo.\aEles estão construindo um Quartel de Robôs Vendedores.\aIsso não é bom. Preciso fazer algumas ligações. Estou muito ocupada. Adeus!\aHã? Ah sim, leve estes mapas de volta para a Detetive Linda.\aEla poderá decifrá-los melhor.", + COMPLETE : "O que a Juíza Gala disse?\aNós tínhamos razão? Ah, não. Vamos ver estes mapas.\aHumm... Parece que os Robôs Vendedores construíram uma fábrica com maquinário para fazer Cogs.\aParece muito perigoso. Fique de fora até que você tenha mais Pontos de risadas.\aQuando você tiver mais Pontos de risadas, teremos muito mais a aprender sobre o Quartel dos Robôs Vendedores.\aAqui está sua recompensa. Bom trabalho!", + }, + + + 3255 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3256 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3257 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3258 : { QUEST : "Há muita confusão sobre o que os Cogs pretendem com seu novo Quartel.\aPreciso que você traga algumas informações diretamente deles.\aSe nós conseguirmos quatro memorandos internos de Robôs Vendedores dentro de seu Quartel, isso ajudará a esclarecer as coisas.\aTraga o primeiro memorando para mim para que possamos nos informar melhor.", + }, + 3259 : { QUEST : "Demais! Vamos ver o que diz o memorando...\a\"A/C Robôs Vendedores:\"\a\"Estarei em meu escritório no topo das Torres Robôs Vendedores promovendo Cogs a níveis mais altos.\"\a\"Quando você tiver méritos suficientes, entre no elevador do saguão para falar comigo\".\a\"O intervalo chegou ao fim. De volta ao trabalho!\"\a\"Assinado, Robô Vendedor VP\"\aAhá.... Flippy vai querer ver isto. Enviarei a ele imediatamente.\aVá buscar o segundo memorando e traga aqui.", + }, + 3260 : { QUEST : "Que bom, você está de volta. Deixe-me ver o que você encontrou....\a\"A/C Robôs Vendedores:\"\a\"As Torres Robôs Vendedores instalaram um novo sistema de segurança para afastar todos os Toons.\"\a\"Os Toons que forem encontrados nas Torres Robôs Vendedores serão detidos para interrogatório\".\a\"Encontrem-se no saguão para um coquetel, no qual discutiremos o assunto.\"\a\"Assinado, Amizade Fácil\"\aMuito interessante... Passarei imediatamente esta informação adiante.\aTraga o terceiro memorando.", + }, + 3261 : { QUEST : "Excelente trabalho _avName_! O que diz o memorando?\a\"A/C Robôs Vendedores:\"\a\"De algum modo, os Toons encontraram um jeito de se infiltrarem nas Torres Robôs Vendedores.\"\a\"Ligarei para vocês esta noite na hora do jantar para fornecer os detalhes.\"\a\"Assinado, Operador de Telemarketing\"\aHumm... Queria saber como os Toons estão conseguindo se infiltrar....\aTraga mais um memorando e acho que assim teremos informações suficientes.", + COMPLETE : "Eu sabia que você conseguiria! Ok, o memorando diz...\a\"A/C Robôs Vendedores:\"\a\"Ontem, estava almoçando com Dr. Celebridade.\"\a\"Ele disse que o VP tem estado bastante ocupado nestes dias.\"\a\"Ele só receberá os Cogs que merecem promoção.\"\a\"Esqueci de dizer, o Amigo da Onça jogará golfe comigo no domingo.\"\a\"Assinado, Dr. Sabe-com-quem-está-falando\"\aBem... _avName_, isto foi muito útil.\aAqui está sua recompensa.", + }, + + 3262 : { QUEST : "_toNpcName_ tem novas informações sobre a Fábrica do "+lSellbotHQ+".\aVá ver o que ele tem a dizer._where_" }, + 3263 : { GREETING : "Olá, parceiro!", + QUEST : "Eu sou o Treinador Abobrinha, mas você pode me chamar de Treinador A.\aEu sou a favor de treinos com a raquete e alongamento, se é que você me entende.\aOuça, os Robôs Vendedores terminaram uma enorme fábrica para produzir Robôs Vendedores 24 horas por dia.\aReúna um grupo de parceiros Toon e raquetada na fábrica!\aDentro do Quartel do Robô Vendedor, procure pelo túnel que leva até a fábrica e, então, entre no elevador.\aVocê já tem que estar com as piadas e os pontos de risadas completos e ter Toons fortes como guias.\aPara retardar o progresso dos Robôs Vendedores, derrote o Supervisor dentro da fábrica.\aParece um grande exercício, se é que fui bem claro.", + LEAVING : "Te vejo por aí, parceiro!", + COMPLETE : "Ei, parceiro, bom trabalho naquela Fábrica!\aParece que você encontrou parte de um terno de Cog.\aDeve ser uma sobra do processo de fabricação de Cogs.\aIsto pode vir a calhar. Continue coletando estas partes quando tiver um tempo livre.\aQuem sabe, quando você coletar um terno de Cog completo, poderá vir a ser útil para alguma coisa....", + }, + + 4001 : {GREETING : "", + QUEST : "Agora, você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Agora você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Aposto que o Tom iria gostar de ter alguma ajuda na pesquisa que ele está fazendo._where_", + }, + 4201 : { GREETING: "Tudo certo?", + QUEST : "Estou bastante preocupado com a onda de roubos de instrumentos musicais.\aEstou conduzindo uma pesquisa com meus amigos comerciantes.\aTalvez seja possível encontrar um padrão para me ajudar a resolver este caso.\aPeça a Tina o controle de estoque de concertina._where_", + }, + 4202 : { QUEST : "Sim, eu falei com Tom nesta manhã.\aO estoque está bem aqui.\aLeve para ele imediatamente, ok?_where_" + }, + 4203 : { QUEST : "Demais! Um a menos...\aAgora peça o da Cavaca._where_", + }, + 4204 : { QUEST : "Ah! O estoque!\aEsqueci completamente.\aAposto que consigo fazer enquanto você derrota 10 cogs.\aPasse por aqui depois, e eu prometo que estará pronto.", + INCOMPLETE_PROGRESS : "31, 32... DROGA!\aVocê me fez perder a conta!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, aí está você.\aObrigada por me dar algum tempo.\aLeve isto para o Tom e diga olá por mim._where_", + }, + 4206 : { QUEST : "Humm, muito interessante.\aAgora estamos chegando a algum lugar.\aOk, o último estoque é o da Fifi._where_", + }, + 4207 : { QUEST : "Estoque?\aComo posso fazer o estoque se não tenho o formulário?\aVá até o Clave e veja se ele tem um para mim._where_", + INCOMPLETE_PROGRESS : "Algum sinal daquele formulário?", + }, + 4208 : { QUEST : "Claro que eu tenho um formulário de estoque, monsenhor!\aMas eles não são de graça, sabe?.\aFaçamos o seguinte. Eu troco por uma torta de creme inteira.", + GREETING : "Ei, monsenhor!", + LEAVING : "Boa sorte...", + INCOMPLETE_PROGRESS : "Um pedaço não adianta.\aEstou com fome, monsenhor. Eu preciso da torta INTEIRA.", + }, + 4209 : { GREETING : "", + QUEST : "Humm...\aMuito gostoso!\aAqui está o formulário para Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Valeu, foi uma grande ajuda.\aVamos ver...Violinos: 2\aTudo pronto! Aqui está!", + COMPLETE : "Bom trabalho, _avName_.\aTenho certeza de que solucionarei este caso agora.\aPor que você não o soluciona?", + }, + + 4211 : { QUEST : "Veja, o Dr. Triturador está ligando de cinco em cinco minutos. Você pode conversar com ele e ver qual o problema?_where_", + }, + 4212 : { QUEST : "Puxa! Estou feliz de ver que o Quartel dos Toons finalmente mandou alguém.\aNão tenho um cliente há dias.\aSão estes malditos Destruidores de Números que estão em todo lugar.\aAcho que eles estão ensinando maus hábitos de higiene oral a nossos moradores.\aDerrote dez deles e vamos ver se o negócio anda.", + INCOMPLETE_PROGRESS : "Ainda sem clientes. Mas continue assim!", + }, + 4213 : { QUEST : "Sabe, talvez não sejam os Destruidores de Números, no final das contas.\aTalvez sejam apenas os Robôs Mercenários em geral.\aDerrote vinte deles e, com alguma sorte, alguém virá, pelo menos, para um check-up.", + INCOMPLETE_PROGRESS : "Eu sei que vinte é muito. Mas tenho certeza de que vai valer a pena.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Eu não consigo entender!\aAinda não há UM BENDITO freguês.\aTalvez precisemos ir até a fonte.\aTente recuperar um edifício Cog de Robôs Mercenários.\aIsso deve funcionar...", + INCOMPLETE_PROGRESS : "Oh, por favor! Apenas um mísero prediozinho...", + COMPLETE : "Ainda não há uma alma sequer aqui.\aMas, pense bem.\aEu não tinha mesmo clientes antes da invasão dos cogs!\aRealmente agradeço toda a sua ajuda.\aIsto deve ajudar você a prosseguir." + }, + + 4215 : { QUEST : "A Ana precisa desesperadamente da ajuda de alguém.\aPor que você não passa lá e vê o que pode fazer?_where_", + }, + 4216 : { QUEST : "Obrigada por chegar tão rápido!\aParece que os cogs sumiram com várias passagens dos meus clientes.\aA Cavaca disse que viu um Amigo da Onça saindo daqui com as garras cheias de passagens.\aVeja se você consegue recuperar a passagem do Alê Nhador para o Alasca.", + INCOMPLETE_PROGRESS : "Aqueles Amigos da Onça podem estar em qualquer lugar agora...", + }, + 4217 : { QUEST : "Legal! Você encontrou!\aAgora seja um cavalheiro e entregue ao Alê Nhador para mim, está bem?_where_", + }, + 4218 : { QUEST : "Genial, estupendo, fabuloso!\aAlasca, aqui vou eu!\aNão aguento mais esses cogs infernais.\aOlha, acho que a Ana precisa de você de novo._where_", + }, + 4219 : { QUEST : "Exatamente, você adivinhou!\aPreciso de você para derrotar aquelas pestes dos Amigos da Onça para recuperar a passagem da Tábata para o Festival de Jazz.\aVocê sabe como fazer...", + INCOMPLETE_PROGRESS : "Há mais lá fora, em algum lugar...", + }, + 4220 : { QUEST : "Gracinha!\aVocê poderia entregar este também?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Fica frio...", + QUEST : "Legal, cara!\aAgora estou na cidade dos gordinhos, _avName_.\aAntes de sair fora, é melhor falar com a Ana Banana de novo..._where_", + }, + 4222 : { QUEST : "Este é o último, prometo!\aAgora procure pela passagem do Barry para o grande concurso de cantores.", + INCOMPLETE_PROGRESS : "Vamos lá, _avName_.\aO Barry está contando com você.", + }, + 4223 : { QUEST : "Isto deve alegrar o Barry._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Olá, Olá, OLÁ!\aMagnífico!\aSó conheço eu mesmo e os caras que vão fazer a faxina. \aA Ana disse para você passar lá e pegar a sua recompensa._where_\aTchau, Tchau, TCHAU!", + COMPLETE : "Obrigado por toda a sua ajuda, _avName_.\aVocê é realmente um tesouro aqui de Toontown.\aFalando em tesouros...", + }, + + 902 : { QUEST : "Vá ver o Léo.\aEle precisa de alguém para entregar uma mensagem para ele._where_", + }, + 4903 : { QUEST : "Cara!\aMinhas castanholas estão foscas e tenho um grande show hoje à noite.\aLeve-as para o Carlos e veja se ele pode dar um polimento nelas._where_", + }, + 4904 : { QUEST : "Sim, acho que posso polir esta peça. Mas preciso de alguma tinta azul de lula", + GREETING : "Olá!", + LEAVING : "Tchau!", + INCOMPLETE_PROGRESS : "Você pode achar uma lula perto de algum píer de pesca.", + }, + 4905 : { QUEST : "Claro! Isso mesmo!\aAgora, preciso de um minuto para polir isto. Por que você não trabalha na recuperação de um prédio de um andar enquanto trabalho por aqui?", + GREETING : "Ola!", + LEAVING : "Tchau!", + INCOMPLETE_PROGRESS : "Só mais um minutinho...", + }, + 4906 : { QUEST : "Muito bom!\aAqui estão as castanholas do Léo._onde_", + }, + 4907 : { GREETING : "", + QUEST : "Maneiro, cara!\aElas estão incríveis!\aAgora preciso que você consiga uma cópia da letra da “Música de Natal” da Heidi._where_", + }, + 4908 : { QUEST: "E aí pessoal!\aHumm, Eu não tenho uma cópia dessa música à mão.\aSe você me der um tempinho, eu posso transcrever de cabeça.\aPor que você não dá uma voltinha e aproveita para recuperar um edifício de dois andares enquanto escrevo?", + }, + 4909 : { QUEST : "Desculpe.\aMinha memória está ficando meio confusa.\aSe você recuperar um edifício de três andares, tenho certeza de que estarei pronta quando voltar...", + }, + 4910 : { QUEST : "Tudo pronto!\aDesculpe a demora.\aLeve isto para o Léo._where_", + GREETING : "", + COMPLETE : "Caramba, cara!\aMeu show vai detonar!\aFalando em detonar, você pode detonar alguns cogs com isto..." + }, + 5247 : { QUEST : "Este bairro está ficando perigoso...\aVocê deve estar querendo aprender alguns truques novos.\a_toNpcName_ me ensinou tudo que sei, então, talvez ele possa ajudar você também._where_" }, + 5248 : { GREETING : "Ah, sim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você parece estar empenhado na missão.", + QUEST : "Ah, bem-vindo, novo aprendiz.\aEu sei de tudo que há para saber sobre o jogo de tortas.\aPorém, antes de começarmos o seu treinamento, é necessário uma pequena demonstração.\aSaia e derrote dez dos maiores Cogs." }, + 5249 : { GREETING: "Humm.", + QUEST : "Excelente!\aAgora demonstre sua habilidade como pescador.\aColoquei ontem três dados de pelúcia no lago.\aPesque-os e traga-os para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que você não é tão hábil com a vara e o molinete." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs da Lei.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs-chefes.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs Mercenários.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs Vendedores.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5200 : { QUEST : "Aqueles cogs traiçoeiros estão envolvidos nisto novamente.\a_toNpcName_ percebeu que tem outro item ausente. Pare um pouco aqui e veja se consegue acertar isso._where_" }, + 5201 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Caça-talentos chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5261 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Duas Caras chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5262 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Sacos de Dinheiro chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5263 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Relações Públicas chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5202 : { QUEST : "O Brrrgh foi invadido por alguns dos mais temíveis Cogs já vistos.\aVocê provavelmente desejará carregar mais piadas consigo.\aOuvi falar que _toNpcName_ tem uma sacola grande que você pode usar para carregar mais piadas._where_" }, + 5203 : { GREETING: "Hã? Você está no meu time de trenó?", + QUEST : "O que é isto? Você quer uma bolsa?\aEu tinha uma aqui em algum lugar... Acho que está no meu tobogã?\aSó que... Eu não vejo o meu tobogã desde a grande corrida!\aTalvez um destes Cogs o tenha pego.", + LEAVING : "Você viu meu tobogã?", + INCOMPLETE_PROGRESS : "Quem é você novamente? Desculpe, estou meio confuso depois da batida." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Este é o meu tobogã? Não vejo nenhuma sacola aqui.\aAcho que o Cabeção Kika estava na equipe... Será que está com ele?_where_" }, + 5205 : { GREETING : "Ai, minha cabeça!", + LEAVING : "", + QUEST : "Hã? Tobi? Ah, a bolsa?\aBom, acho que ele estava na nossa equipe de tobogã?\aMinha cabeça dói tanto que não consigo pensar direito.\aVocê consegue para mim alguns cubos de gelo no lago congelado para eu pôr na minha cabeça?", + INCOMPLETE_PROGRESS : "Aaiii, minha cabeça está me matando! Tem gelo aí?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Ahhh, agora me sinto bem melhor!\aEntão você está procurando a bolsa do Tobi, né?\aAcho que ela foi parar na cabeça do Álvaro Asno depois da batida._where_" }, + 5207 : { GREETING : "Iiiiiiiiiip!", + LEAVING : "", + QUEST : "O que é bolsa? Quem é Cabeção?\aTenho medo de edifícios! Você detona edifício, eu dou bolsa!", + INCOMPLETE_PROGRESS : "Mais edifícios! Ainda com medo!", + COMPLETE : "Ooooh! Mim gosta você!" }, + 5208 : { GREETING : "", + LEAVING : "Iiiiiiiiiiik!", + QUEST : "Ooooh! Mim gosta você!\aVai pra Clínica do Esqui. Sacola lá." }, + 5209 : { GREETING : "Valeu, garoto!", + LEAVING : "Até mais!", + QUEST : "Cara, o Álvaro Asno é doido!\aSe você fosse maluco que nem o Álvaro, eu daria a bolsa para você, cara.\aVai ensacar uns Cogs para poder pegar a sua sacola, cara! Essa agora!", + INCOMPLETE_PROGRESS : "Tem certeza de que você é radical o bastante para isso? Vai ensacar mais Cogs.", + COMPLETE : "Caramba, você é irado! Aquilo foi um bando de Cogs que você ensacou!\aToma a sua bolsa!" }, + + 5210 : { QUEST : "_toNpcName_ está gamada em alguém do bairro, mas é segredo.\aSe você ajudá-la, ela pode lhe dar uma boa recompensa._where_" }, + 5211 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos com bico veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5264 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos de barbatana veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5265 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos de Amizade Fácil veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5266 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs Aventureiros Corporativos asquerosos veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5212 : { QUEST : "Oh, obrigada por encontrar a minha carta!\aPor favor, você poderia entregá-la ao cão mais lindo do bairro? Por favor! Por favor!", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você não entregou a minha carta, não é?", + }, + 5213 : { GREETING : "Enfeitiçado, com certeza.", + QUEST : "Não posso dar atenção à sua carta, sabe.\aTodos os meus cãezinhos foram levados!\aSe você os trouxer de volta, a gente volta a conversar.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tadinhos dos meus cãezinhos!" }, + 5214 : { GREETING : "", + LEAVING : "Tchauzinho!", + QUEST : "Graças a você minhas belezinhas voltaram.\aVamos ver a carta agora...\nMmmm, parece que tenho outra admiradora secreta.\aIsso exigirá uma visita ao meu querido amigo Carlo.\aAposto como você vai adorá-lo._where_" }, + 5215 : { GREETING : "He, he...", + LEAVING : "Volte aqui, sim, sim.", + INCOMPLETE_PROGRESS : "Ainda há alguns grandalhões na área. Volte aqui para falar conosco quando eles forem embora.", + QUEST : "Quem mandou você? Não gostamos muito de Snobs, não...\aMas gostamos menos ainda de Cogs...\aExpulse os grandalhões e ajudaremos vocês, ajudaremos." }, + 5216 : { QUEST : "Falamos que ajudaríamos você.\aEntão, pegue este anel e leve à garota.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você ainda está com o anel???", + COMPLETE : "Oh querrrrido!!! Obrigado!!!\aAh, também tenho algo especial para você.", + }, + 5217 : { QUEST : "Parece que _toNpcName_ pode dar uma ajuda._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tenho certeza de que há mais Amizades Fáceis por aqui em algum lugar.", + QUEST : "Socorro!!! Socorro!!! Assim não dá!\aEsses Amizades Fáceis estão me deixando maluco!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não são só estes. Só vi um!!!", + QUEST : "Ah, obrigado, mas agora são os Aventureiros Corporativos!!!\aVocê tem que me ajudar!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não, não, não, havia um aqui agora mesmo!", + QUEST : "Agora, eu percebo que são aqueles Agiotas!!!\aPensei que você ia me salvar!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Sabe de uma coisa, talvez não sejam os Cogs coisa nenhuma!\aVocê pode pedir à Hilária para fazer para mim uma poção calmante? Talvez isto ajude...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Esse Américo é mesmo uma figura!\aVou preparar algo que vai dar jeito nele rapidinho!\aPuxa, parece que estou sem bigodes de sardinha...\aSeja legal comigo e corra lá no lago para pegar alguns para mim.", + INCOMPLETE_PROGRESS : "Já pegou aqueles bigodes para mim?", }, + 5223 : { QUEST : "OK. Obrigada!\aTome, leve agora para o Américo. Isto deve acalmá-lo de uma vez por todas.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vá logo, leve a poção para o Américo.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vá pegar aqueles Macacos velhos para mim, ok?", + QUEST : "Puxa vida, graças a Deus você voltou!\aPasse logo para cá esta poção!!!\aGlub, glub, glub...\aQue gosto horrível!\aSabe de uma coisa? Sinto-me bem mais calmo. Agora que eu posso pensar com mais clareza, me toquei que...\aEram os Macacos-velhos que estavam me enlouquecendo todo este tempo!!!", + COMPLETE : "Nossa! Agora eu posso relaxar!\aTenho certeza de que há alguma coisa aqui que posso dar a você. Aqui, leve isto!" }, + 5225 : { QUEST : "Desde o acidente com o pão de nabo, Felipe Nervosinho ficou furioso com _toNpcName_.\aQuem sabe você não consegue ajudar o Pio a acertar os ponteiros entre eles?_where_" }, + 5226 : { QUEST : "Isso mesmo, você deve ter ouvido falar que o Felipe Nervosinho está furioso comigo...\aEu estava só tentando ser legal oferecendo o pão de nabo.\aQuem sabe você não consegue alegrá-lo.\aO Felipe detesta aqueles Cogs Robôs Mercenários, principalmente os edifícios deles.\aSe você recuperar alguns edifícios de Robôs Mercenários, talvez ajude.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Quem sabe alguns edifícios a mais?", }, + 5227 : { QUEST : "Demais! Vá dizer ao Felipe o que você fez._where_" }, + 5228 : { QUEST : "Puxa, ele fez isso mesmo?\aEsse Pio acha que pode se safar fácil, né?\aSó quebrou meu dente, só isso que ele fez, com aquele pão de nabo dele!\aSe você levar o meu dente para o Dr. Ban Guela para mim, quem sabe ele consegue dar jeito.", + GREETING : "Mmmmrrf.", + LEAVING : "Resmungo, resmungo.", + INCOMPLETE_PROGRESS : "Você de novo? Pensei que você estava indo levar meu dente para consertar.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs Mercenários das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs Vendedores das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs da Lei das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs-chefe das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5230 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Barão Ladrão entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5270 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Rei da Cocada Preta entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5271 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que o Dr. Celebridade entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5272 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Figurão entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5231 : { QUEST : "Legal, é este dente mesmo!\aPor que você não corre para levá-lo para o Felipe?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Aposto como o Felipe vai adorar ver o dente novo dele.", + }, + 5232 : { QUEST : "Puxa, obrigado.\aMmmrrrfffffff\aE aí, que tal, hein?\aOk, tudo bem, pode dizer ao Pio que eu o perdôo.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Legal, muito bom saber disso.\aAchei mesmo que meu velho amigo Felipe não podia ficar com raiva de mim.\aPara agradecer e ser gentil, preparei para ele este pão de pinha.\aSerá que você podia correr lá e entregar a ele para mim?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Melhor se apressar. O pão de pinha só é bom quando está quente.", + COMPLETE : "Puxa, o que é isto? Para mim?\aNham, nham...\aOhhhhhh! Meu dente! Aquele Pio Arrepio!\aTudo bem, não foi sua culpa. Tome aqui, leve isto por todo o trabalho que demos a você.", + }, + 903 : { QUEST : "Você deve se aprontar para ver _toNpcName_, o Mago do Lago Congelado, para o seu teste final._where_", }, + 5234 : { GREETING: "", + QUEST : "Ahá! Você voltou.\aAntes de você começar, precisamos comer.\aTraga para a gente alguns pedaços de coco para o nosso caldo.\aO coco em pedaços só pode ser conseguido nos Cogs Rei da Cocada Preta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda precisamos de coco em pedaços." }, + 5278 : { GREETING: "", + QUEST : "Ahá! Você voltou.\aAntes de você começar, precisamos comer.\aTraga para a gente caviar para o nosso caldo.\aO caviar só pode ser conseguido nos Cogs Dr. Celebridade.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda precisamos de caviar." }, + 5235 : { GREETING: "", + QUEST : "Homens simples comem com colheres simples.\aOs Cogs levaram minha colher simples, por isso, eu simplesmente não posso comer.\aPegue minha colher de volta. Acho que foi um Barão Ladrão.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eu simplesmente preciso da minha colher." }, + 5279 : { GREETING: "", + QUEST : "Homens simples comem com colheres simples.\aOs Cogs levaram minha colher simples, por isso, eu não posso comer.\aPegue minha colher de volta. Acho que foi um Figurão.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eu simplesmente preciso da minha colher." }, + 5236 : { GREETING: "", + QUEST : "Muito obrigado.\aSlurp, slurp...\aAhhh, agora, você precisa pegar um sapo falante. Tente pescá-lo no lago.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cadê o sapo falante?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você não conseguiu a sobremesa ainda.", + QUEST : "Ah, isto é, com certeza, um sapo falante. Passe para cá.\aO que você me diz, sapo?\aUh huh.\aUh huh...\aO sapo falou. Precisamos da sobremesa.\aTraga para a gente algumas casquinhas de sorvete da _toNpcName_.\aPor alguma razão, o sapo gosta de sorvete sabor feijão vermelho._where_", }, + 5238 : { GREETING: "", + QUEST : "Então, o mago mandou você aqui. Sinto dizer que acabamos de ficar sem as casquinhas sabor feijão vermelho.\aVocê nem imagina, mas um bando de Cogs entrou aqui e as levou.\aEles disseram que iam levá-las para o Dr. Celebridade, ou alguma baboseira parecida.\aCertamente, apreciaria se você pudesse recuperá-las para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Já achou todas as minhas casquinhas de sorvete?" }, + 5280 : { GREETING: "", + QUEST : "Então, o mago mandou você aqui. Sinto dizer que acabamos de ficar sem as casquinhas sabor feijão vermelho.\aVocê nem imagina, mas um bando de Cogs entrou aqui e as levou.\aEles disseram que iam levá-las para O Rei da Cocada Preta, ou alguma baboseira parecida.\aCertamente, apreciaria se você pudesse recuperá-las para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Já achou todas as minhas casquinhas de sorvete?" }, + 5239 : { QUEST : "Obrigado por trazer de volta as minhas casquinhas de sorvete!\aTome uma para o Pequeno Grande Ancião.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "É melhor você levar este sorvete para o Pequeno Grande Ancião antes que ele derreta.", }, + 5240 : { GREETING: "", + QUEST : "Muito bem. Aqui está, sapo...\aSlurp, slurp...\aOk, agora estamos quase prontos.\aSe você pudesse apenas trazer um pozinho para secar as minhas mãos...\aAcho que das perucas daqueles Cogs Figurões às vezes sai pó.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Achou algum pó?" }, + 5281 : { GREETING: "", + QUEST : "Muito bem. Aqui está, sapo...\aSlurp, slurp...\aOk, agora estamos quase prontos.\aSe você pudesse apenas trazer um pozinho para secar as minhas mãos...\aAcho que aqueles Cogs Drs. Celebridades às vezes têm pó para o nariz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Achou algum pó?" }, + 5241 : { QUEST : "Ok.\aComo já disse antes, para lançar uma torta pra valer, não basta jogá-la com a mão...\a...É preciso jogar com a alma.\aNão sei exatamente o que isto significa, portanto, sentarei e contemplarei você em seu trabalho de recuperar edifícios.\aVolte quando tiver concluído a sua tarefa.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sua tarefa ainda não está concluída.", }, + 5242 : { GREETING: "", + QUEST : "Embora eu ainda não saiba sobre o que estou falando, você realmente merece.\aDou a você, então, uma tarefa final...\aO sapo falante precisa de uma namorada.\aAche uma sapa falante. O sapo falou.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cadê a sapa falante?", + COMPLETE : "Puxa! Estou cansado com todo esse esforço. Preciso descansar agora.\aAgora, pegue a sua recompensa e saia." }, + + 5243 : { QUEST : "Soares Suado está começando a feder no início da rua.\aFala com ele para tomar um banho ou algo do gênero?_where_" }, + 5244 : { GREETING: "", + QUEST : "É, acho que suei demais aqui.\aMmmm, se eu pudesse consertar aquele vazamento no encanamento do meu chuveiro...\aAcho que a engrenagem de um daqueles Cogs pequenos bastaria para o conserto.\aVá achar uma engrenagem de um Microempresário para a gente tentar consertar.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Onde está aquela engrenagem que você ia conseguir?" }, + 5245 : { GREETING: "", + QUEST : "É, parece que funcionou.\aMas eu fico solitário quando tomo banho...\aSerá que você poderia pescar um patinho de borracha para me fazer companhia?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não acha o patinho de borracha?" }, + 5246 : { QUEST : "O patinho é ótimo, mas...\aTodos aqueles edifícios aqui em volta me deixam com os nervos em frangalhos.\aEu me sentiria bem melhor se houvesse menos edifícios por aqui.", + LEAVING : "", + COMPLETE : "Ok, agora eu vou tomar banho. Ah, aqui está uma coisinha para você.", + INCOMPLETE_PROGRESS : "Ainda estou preocupado com os edifícios.", }, + 5251 : { QUEST : "Vítor Vestíbulo devia estar fazendo um show nesta noite.\aOuvi falar que ele estava tendo problemas com o equipamento._where_" }, + 5252 : { GREETING: "", + QUEST : "É isso aí! Seria bom mesmo aceitar a sua ajuda.\aAqueles Cogs entraram aqui e levaram todas as engrenagens do meu equipamento enquanto eu estava descarregando a caminhonete.\aVocê pode me dar uma mãozinha e conseguir de volta o meu microfone?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cara, eu não consigo cantar sem o microfone." }, + 5253 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Aventureiros Corporativos o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5273 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Amizades Fáceis o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5274 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Agiotas o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5275 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Macacos velhos o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5254 : { GREETING: "", + QUEST : "Tudo em cima! Agora estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Dr. Celebridade, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5282 : { GREETING: "", + QUEST : "Tudo em cima! Agora, estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Rei da Cocada Preta, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5283 : { GREETING: "", + QUEST : "Tudo em cima! Agora estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Barão Ladrão, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5284 : { GREETING: "", + QUEST : "Tudo em cima! Agora, estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Figurão, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + + 5255 : { QUEST : "Parece que você pode usar mais pontos de risadas.\aTalvez _toNpcName_ entre em um acordo com você.\aNão deixe de firmar o acordo por escrito..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Trato é trato.", + QUEST : "Então, você está atrás de pontos de risadas, né?\aSe eu tenho uma proposta para você!?\aÉ só tomar conta de alguns Cogs Robôs-chefe para mim...\aAí eu dou uma injeção de ânimo nos seus pontos." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Trato é trato.", + QUEST : "Então, você está atrás de pontos de risadas, né?\aSe eu tenho uma proposta para você!?\aÉ só tomar conta de alguns Cogs Robôs da Lei para mim...\aAí eu dou uma injeção de ânimo nos seus pontos." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Ok, mas tenho certeza de que falei para você reunir alguns Cogs Robôs da Lei.\aBom, se você está falando, tudo bem, mas, então, fica me devendo uma.", + INCOMPLETE_PROGRESS : "Acho que você não terminou ainda.", + QUEST : "Você está dizendo que acabou? Derrotou todos os Cogs?\aVocê deve ter entendido errado, nosso trato era para os Cogs Robôs Vendedores.\aTenho certeza de que disse para você derrotar alguns Cogs Robôs Vendedores para mim." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Ok, mas tenho certeza de que falei para você reunir alguns Cogs Robôs da Lei.\aBom, se você está falando, tudo bem, mas, então, fica me devendo uma.", + INCOMPLETE_PROGRESS : "Acho que você não terminou ainda.", + QUEST : "Você está dizendo que acabou? Derrotou todos os Cogs?\aVocê deve ter entendido errado, nosso trato era para os Cogs Robôs Mercenários.\aTenho certeza de que disse para você derrotar alguns Cogs Robôs Mercenários para mim." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "Eu não posso ajudar com os pontos de Risada, mas talvez _toNpcName_ faça negócio com você.\aMas ele é um pouco temperamental..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "Eu te disse o quê?!?!\aValeu mesmo! Aqui está o seu ponto de Risada!", + INCOMPLETE_PROGRESS : "Oi!\aO que está fazendo aqui de novo!", + QUEST : "Um ponto de Risada? Acho que não!\aClaro, mas só se der um jeito em alguns desses Robôs da Lei antes." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" está repleto de Cogs perigosos.\aSe fosse você, carregaria mais piadas por aqui.\aOuvi dizer que _toNpcName_ pode fazer uma bolsa maior para você se estiver a fim de trabalhar._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Deve haver bastante Robôs da Lei lá fora.\aEntão mexa-se!" , + QUEST : "Uma bolsa maior?\aEu até poderia arranjar uma procê.\aMas vou precisar de fios.\aUns Robôs da Lei roubaram os meus fios ontem de manhã." }, + 5305 : { GREETING : "Olá!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vai atacar mais uns cogs.\aEssa cor ainda não pegou.", + QUEST : "Esse é um fio bom!\aMas não seria a minha primeira escolha de cor.\aVou te dizer...\aVai lá fora e derrote alguns dos cogs mais difíceis...\aE eu começo a a trabalhar em tingir este fio." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eles têm que estar lá em algum lugar...", + QUEST : "Bem, este fio está todo tingido. Mas tem um probleminha.\aNão consigo encontrar as minhas agulhas de tricô.\aO último lugar que estavam foi no lago." }, + 5307 : { GREETING : "", + LEAVING : "Muito obrigado!", + INCOMPLETE_PROGRESS : "Roma não foi tricotada em um dia!" , + QUEST : "Essas são as minhas agulhas.\aEnquanto eu tricoto, que tal fazer uma limpeza em alguns dos prédios grandes?", + COMPLETE : "Ótimo trabalho!\aE falando em trabalho ótimo...\aAqui está a sua nova bolsa!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "Ouvi dizer que _toNpcName_ tem problemas legais.\aVocê pode passar lá e dar uma olhada?_where_" }, + 5309 : { GREETING : "Que bom ver você...", + LEAVING : "", + INCOMPLETE_PROGRESS : "Rápido, por favor! A rua está transbordando com eles!", + QUEST : "Os Robôs da Lei tomaram conta daqui.\aTemo que eles vão me levar a julgamento.\aVocê poderia me ajudar a tirá-los desta rua?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que os ouço vindo por mim...", + QUEST : "Obrigado. Sinto-me um pouco melhor agora.\a Mas tem mais uma coisa...\aVocê poderia ir até a casa de _toNpcName_ e me conseguir um álibi?_where_" }, + 5311 : { GREETING : "O QUEEE!!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não posso ajudá-lo se não encontrar!", + QUEST : "Álibi?! Mas que ótima ideia!\aE traga duas!\aAposto que um Macaco velho deve ter alguns..." }, + 5312 : { GREETING : "Finalmente!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "Ufa! Que alívio é ter isso.\aAqui está a sua recompensa...", + QUEST : "Súper! É melhor você voltar até _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Elle Étrica precisa de ajuda. Você pode passar lá e dar uma mãozinha a ela?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "Um cliente! Beleza! Em que posso ajudar?\aComo assim, você me ajudar? AH! Você não é um cliente.\aAgora me lembrei. Você veio para me ajudar com aqueles Cogs horrorosos.\aNa verdade, eu aceitaria sua ajuda, você sendo um cliente ou não.\aSe você fizer uma pequena limpa nas ruas, dou uma coisa a você.", + INCOMPLETE_PROGRESS : "Se você não quiser eletricidade, não posso ajudar até que derrote aqueles Cogs.", + COMPLETE : "Bom trabalho com aqueles Cogs, _avName_.\aAgora, você tem certeza de que não quer um choquezinho? Pode ser útil....\aNão? OK, você que sabe.\aHã? Ah sim, lembro. Aqui está. Com certeza, vai ajudar você a deter aqueles Cogs nojentos.\aContinue assim!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Bem, _avName_, não tenho nada para você agora.\aEspera aí! Acho que a Célia Sesta estava procurando ajuda. Por que não vai encontrá-la?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "Nunca enriquecerei com aqueles malditos Cogs atrapalhando os meus negócios!\aVocê tem que me ajudar, _avName_.\aElimine alguns edifícios de Cogs para salvar a vizinhança e ajudarei você em sua poupança.", + INCOMPLETE_PROGRESS : "O que farei agora? Você não conseguiu se livrar dos edifícios?", + COMPLETE : "Agora, vou entrar na grana! Agora sim!\aVou passar todo o meu tempo livre pescando. Agora, deixe-me enriquecer sua vida um pouquinho.\aLá vai!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "Oi, _avName_! Ouvi dizer que a Linda Legal estava procurando você.\aPassa lá para fazer uma visitinha a ela._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "E aí! Nossa, como é bom ver você!\aFiquei trabalhando nesta secretária eletrônica nas horas vagas, mas faltam algumas peças.\aPreciso de mais três varas, e as do Conta-moedinha parecem perfeitas.\aVocê poderia tentar encontrar algumas varas de pescar para mim?", + INCOMPLETE_PROGRESS : "Ainda à procura daquelas varas de pescar?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, estas aqui já ajudam.\aEngraçado. Eu tinha certeza de que havia um cinto de segurança extra por aqui, mas não consigo encontrá-lo.\aVocê pode pegar um de uns Sacos de Dinheiro para mim? Valeu!", + INCOMPLETE : "Olha, eu só posso ajudar você depois que conseguir aquele cinto de segurança.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Agora sim. Vai funcionar que é uma beleza.\aOnde está meu alicate? Não vou poder ajustar isto aqui sem o alicate.\aTalvez as pinças da Mão de vaca ajudem.\aSe você conseguir encontrá-las, dou a você uma coisa que vai ajudar na batalha com os Cogs.", + INCOMPLETE_PROGRESS : "Nada das pinças ainda, né? Vai procurando.", + COMPLETE : "Beleza! Agora é só fazer o ajuste aqui.\aParece que agora está funcionando. Estou de novo na ativa!\aNa verdade, falta ainda o telefone. Mas, estou satisfeito com a sua ajuda.\aAcho que isso vai ajudar você com os Cogs. Boa sorte!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "Ouvi dizer que Pedro estava atrás da sua ajuda. Veja o que pode fazer por ele._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "Qualé? Chegou no point certo. Não estou legal.\aÉ isso aí, tava procurando ajuda pra me livrar daqueles Cogs. Eles chegam e ficam mandando em mim.\aBem que você podia mandar aqueles Robôs-chefe se aposentarem. Você não vai se arrepender.", + INCOMPLETE_PROGRESS : "E aí, _avName_, qual foi?\aVai lá atrás dos Robôs-chefe. A gente tem um trato, falou?\aO Pedro aqui tem palavra.", + COMPLETE : "Qualé, _avName_! Agora, você está bem na fita.\aQuero ver os Robôs-chefe chefiar agora, né não?\aVamo lá! Um tremendo acréscimo pra você. Agora, vê se não entra em nenhuma fria, falou?", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "O Zezé ouviu um boato na Alameda do Pijama sobre um Quartel do Robô Mercenário.\aVai lá e veja se consegue ajudá-lo._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "Soube de umas coisas estranhas que estão acontecendo.\aTalvez sejam as pulgas, mas deve ter alguma coisa rolando.\aTodos esses Robôs Mercenários!\aAcho que abriram outro quartel bem na Alameda do Pijama.\aO Py Jama sabe o caminho.\aVá ver _toNpcName_ _where_ Pergunte a ele se viu alguma coisa.", + INCOMPLETE_PROGRESS : "Ainda não viu o Py Jama? O que você está esperando?\aAi, essas malditas pulgas!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "E aí, _avName_, para onde você está indo?\aPara o Quartel dos Robôs Mercenários?? Eu não vi nada.\aVocê pode ir até o final da Alameda do Pijama e ver se é verdade?\aEncontre alguns Cogs do Robô Mercenário no quartel, derrote alguns deles e venha me contar.", + INCOMPLETE_PROGRESS : "Já encontrou o Quartel? Você precisará derrotar alguns Robôs Mercenários para localizá-lo.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "O quê?! Existe mesmo um Quartel de Robôs Mercenários?\aÉ melhor você ir e contar a Zezé agora mesmo!\aQuem poderia imaginar que existiria um Quartel de Cogs na rua bem em frente a ele?", + INCOMPLETE_PROGRESS : "O que Zezé disse? Você ainda não o encontrou?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "Estou tentado para ouvir o que o Py Jamas disse.\aHmm... Precisamos de mais informações sobre esse negócio de Cog, mas preciso me livrar dessas pulgas!\aEu sei! VOCÊ pode descobrir mais coisas!\aVá derrotar os Robôs Mercenários no Quartel até encontrar alguns planos, depois venha direto pra cá!", + INCOMPLETE_PROGRESS : "Nada ainda? Continue procurando esses Cogs!\aEles devem ter algum plano!", + COMPLETE : "Você conseguiu os planos?\aExcelente! Vejamos o que diz aqui.\aEntendi... os Robôs Mercenários construíram uma Casa da Moeda para fabricar grana Cog.\aDeve estar CHEIA de Robôs Mercenários. Precisamos averiguar.\aE se você se disfarçasse? Hmmm...Já sei! Acho que tenho uma peça de vestimenta de Cog aqui em algum lugar....\aAqui está! Isto aqui é para compensar o trabalho. Agradeço novamente pela ajuda!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "A Condessa está procurando você por toda parte! Visite-a logo para que pare de ligar _where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, conto com a sua ajuda!\aSabe, esses Cogs estão fazendo tanto barulho que eu simplesmente não consigo me concentrar.\aPerco a conta dos carneirinhos a todo instante!\aSe você acabar com esse barulho, te dou uma ajuda! Pode contar com isso!\aMas, onde eu parei mesmo? Ah sim: cento e trinta e seis, cento e trinta e sete....", + INCOMPLETE_PROGRESS : "Quatrocentos e quarenta e dois... Quatrocentos e quarenta e três...\aO quê? Você já voltou? Mas ainda tem tanto barulho!\aEssa não, perdi a conta novamente.\a Um...dois...três....", + COMPLETE : "Quinhentos e noventa e três... Quinhentos e noventa e quatro...\aOlá? Ah, eu sabia que poderia contar com a sua ajuda! Agora, o silêncio voltou.\aPegue aqui, por todos esses Destruidores de Números.\aContar? Agora preciso começar a contar tudo outra vez! Um...dois....", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "Pobre Zéfiro, o zíper dela quebrou e, agora, ela não consegue fazer as entregas de seus clientes. Ela certamente precisa de sua ajuda._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oi _avName_. Você está aqui para ajudar com minhas entregas?\aIsso é ótimo! Com esse zíper quebrado é muito difícil fazer as entregas sozinha.\aDeixe-me ver... Ok, vai ser fácil. O Vaqueiro George pediu uma cítara semana passada.\aVocê poderia levá-la para ele? _where_", + INCOMPLETE_PROGRESS : "Oi! Esqueceu alguma coisa? O Vaqueiro George está esperando pela cítara.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "Minha cítara! Finalmente! Caramba, mal posso esperar para tocá-la.\aPoderia agradecer à Zéfiro por mim?", + INCOMPLETE_PROGRESS : "Obrigado novamente pela cítara. A Zéfiro não tem mais entregas para você fazer?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "Essa foi rápida. Qual será o próximo item da minha lista?\aAh sim! Mestre Mário pediu um Zamboni. Aquele zombeteiro.\aPoderia levar para ele?_where_", + INCOMPLETE_PROGRESS : "Aquele Zamboni precisa ser levado para o Mestre Mário._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "Tudo certo! O Zamboni que eu pedi!\aAgora, se não houvesse tantos Cogs por aí, eu teria algum tempo para usá-lo.\aSeja gentil e cuide de alguns desses Robôs Mercenários para mim, tudo bem?", + INCOMPLETE_PROGRESS : "Esses Robôs Mercenários são durões, não são? Assim, eu não consigo testar o meu Zamboni.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "Excelente! Agora, eu posso testar o meu Zamboni.\aDiga à Zéfiro que eu estarei lá para fazer um outro pedido na próxima semana.", + INCOMPLETE_PROGRESS : "Por enquanto é só isso. A Zéfiro não está esperando por você?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "Então, o Mestre Mário ficou satisfeito com o Zamboni? Excelente.\aQuem é o próximo? Ah, o Bob Bocão pediu uma almofada zabuton com listras de zebra.\aAqui está! Poderia ir até a casa dele?_where_", + INCOMPLETE_PROGRESS : "Acho que o Bob Bocão precisa da almofada zabuton para meditar.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, minha almofada zabuton finalmente. Agora, eu posso meditar.\aQuem consegue se concentrar com aquela algazarra? Todos aqueles Cogs!\aJá que você está aqui, poderia cuidar de alguns desses Cogs?\aSó assim eu poderei usar minha almofada zabuton em paz.", + INCOMPLETE_PROGRESS : "Ainda há muito barulho com esses Cogs! Quem consegue se concentrar?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "Paz e silêncio afinal. Obrigado, _avName_.\aDiga à Zéfiro que estou muito satisfeito. OM....", + INCOMPLETE_PROGRESS : "A Zéfiro ligou procurando por você. É melhor você ir ver o que ela precisa.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "Estou feliz em saber que o Bob Bocão está satisfeito com sua almofada zabuton de zebra.\aAh, estas zínias acabaram de chegar para a Rosa Sonada.\aJá que você parece tão animado para fazer entregas, talvez possa levar essas zínias para ela, não é?_where_", + INCOMPLETE_PROGRESS : "Essas zínias vão murchar se você não fizer logo a entrega.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "Que lindas zínias! Certamente que é entrega da Zéfiro.\aQuer dizer, é SUA entrega, _avName_. Agradeça à Zéfiro por mim!", + INCOMPLETE_PROGRESS : "Não se esqueça de agradecer à Zéfiro pelas zínias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom que voltou, _avName_. Você é bastante veloz.\aVejamos... Qual é o próximo item da lista a ser entregue? Discos de forró para Jatha Cordada._where_", + INCOMPLETE_PROGRESS : "Tenho certeza de que Jatha Cordada está esperando por esses discos de forró.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "Discos de forró? Não me lembro de ter pedido discos de forró.\aAh, aposto que foi Denis Nar quem pediu._where_", + INCOMPLETE_PROGRESS : "Não, esses discos de forró são para Denis Nar._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "Finalmente, meus discos de forró! Pensei que a Zéfiro tivesse se esquecido.\aPoderia levar essa abobrinha para ela? Ela encontrará alguém que esteja querendo uma. Valeu!", + INCOMPLETE_PROGRESS : "Eu já tenho muitas abobrinhas. Leve esta para Zéfiro.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "Abobrinha? Hmm. Bem, alguém irá querer, tenho certeza.\aOk, estamos quase terminando com a minha lista. Mais uma entrega a fazer.\aNenê Crespo pediu um paletó zoot._where_", + INCOMPLETE_PROGRESS : "Se você não entregar esse paletó zoot ao Nenê Crespo,\a ele ficará todo amarrotado.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Era uma vez... Ah! Você não está aqui para ouvir uma história, não é?\aÉ a entrega do meu terno zoot? Beleza! Uau, isso aqui é demais.\aEi, poderia dar um recado meu para a Zéfiro? Precisarei de abotoaduras de zircônio para usar com o paletó. Valeu!", + INCOMPLETE_PROGRESS : "Você deu o meu recado à Zéfiro?", + COMPLETE : "Abotoaduras de zircônio, certo? Bem, verei o que posso fazer por ele.\aSeja como for, você tem sido muito útil e não posso deixar você ir sem nada.\aAqui está um GRANDE acréscimo para ajudar a derrotar esses Cogs!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "Solano Sonolento está tendo problemas e você talvez possa ajudá-lo. Por que você não dá uma passada na loja dele?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "O quê? Hã? Eu devo ter cochilado.\aSabe, esses edifícios de Cogs estão cheios de máquinas que realmente me dão um sono.\aEu ouço esse zumbido o dia inteiro e...\aHã? Ah, sim, está certo. Se você pudesse se livrar de alguns desses edifícios de Cogs, eu conseguiria ficar acordado.", + INCOMPLETE_PROGRESS : "Zzzzz...hã? Ah, é você, _avName_.\aJá está de volta? Eu só estava tirando uma sonequinha.\aVolte quando acabar com esses edifícios.", + COMPLETE : "O quê? Eu caí no sono um minutinho.\aAgora que aqueles edifícios de Cogs viraram pó, finalmente posso relaxar.\aValeu pela ajuda, _avName_.\aVejo você depois! Acho que vou tirar uma sonequinha.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Vá em frente e ligue para o Ursinho de P. Lúcia. Ele tem um trabalho para você._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "O que você disse? Não, eu não tenho um baralho pra você.\aAh, é um trabalho! Por que você não disse logo? Você precisa falar alto.\aEsses Cogs não me deixam hibernar. Se você ajudar a tornar a Sonholândia mais silenciosa,\aeu lhe darei uma coisinha.", + INCOMPLETE_PROGRESS: "Você derrotou os bogs? Que bogs?\aAh, os Cogs! Por que você não disse logo?\aHmm, ainda tem barulho. O que acha de derrotar mais alguns?", + COMPLETE : "Você se divertiu? Hã? Ah!\aVocê conseguiu! Beleza. Muito legal você ter me ajudado.\aEu achei isso nos fundos da loja, mas não tem utilidade para mim.\aTalvez você descubra o que fazer com isso. Até logo, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "Os Cogs arrombaram o Banco A Fraldinha de Dormir! Vá até o Guilherme Sonoleve e veja se você pode ajudá-lo.", + }, + 6292 : { QUEST : "Aqueles malditos Cogs do Robô Mercenário! Eles roubaram meus abajures de leitura!\aEu preciso deles de volta agora mesmo. Você pode procurar por eles?\aSe você encontrar meus abajures, talvez eu possa ajudar a encontrar o Diretor Financeiro.\aDepressa!", + INCOMPLETE_PROGRESS : "Eu preciso dos abajures de volta. Continue procurando!", + COMPLETE : "Você voltou! E trouxe meus abajures!\aNão tenho como agradecer o favor, mas posso dar isto a você.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Nana de Nina estava à sua procura, _avName_. Ela precisa de ajuda._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Estou tão feliz em ver você, _avName_. Espero que possa me ajudar!\aAqueles malditos Cogs assustaram o pessoal da entrega e não tenho mais camas no estoque.\aPoderia ir ao Pedro Fuso e trazer uma cama para mim?_where_", + INCOMPLETE_PROGRESS : "O Pedro tinha alguma cama? Tinha certeza de que ele teria uma.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "Uma cama? Isso mesmo, aqui está uma prontinha para viagem.\aEntregue a cama pra Nana por mim, OK? Cama, Nana...\a\"Rimou!\" Há-há!\aMuito engraçado. Não? Bem, mas leve para ela, por favor.", + INCOMPLETE_PROGRESS : "A Nana gostou da cama?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "Essa cama não está legal. Ela é muito simples.\aVocê poderia ir até lá e ver se ele tem alguma coisa mais sofisticada?\aTenho certeza de que não vai demorar nadinha.", + INCOMPLETE_PROGRESS : "Estou certa de que o Pedro tem uma cama mais sofisticada.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "Não acertei na mosca com essa cama, não é? Tenho uma aqui que servirá.\aSó tem um pequeno problema: é preciso montá-la primeiro.\aEnquanto eu resolvo esse problema, você pode se livrar de alguns Cogs que estão lá fora?\aAqueles terríveis Cogs jogaram uma chave inglesa nos móveis.\aVolte quando terminar e a cama estará pronta.", + INCOMPLETE_PROGRESS : "Ainda não terminei a montagem da cama.\aQuando você tiver acabado com os Cogs, ela estará pronta.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "E aí _avName_!\aVocê fez um excelente trabalho com aqueles Cogs.\aA cama já está prontinha. Você pode entregá-la para mim?\aAgora que aqueles Cogs se foram, as coisas estão rápidas por aqui!", + INCOMPLETE_PROGRESS : "Acho que a Nana está esperando pela entrega da cama.", + COMPLETE : "Que cama adorável!\aAgora, meus clientes ficarão satisfeitos. Obrigada, _avName_.\aOlha só, talvez você possa usar isto. Alguém deixou isso aqui.", + }, + 7209 : { QUEST : "Vá até a Lua de Mel. Ela precisa de ajuda._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Estou tão feliz em ver você, _avName_. Eu preciso muito de ajuda!\aNão consigo tirar o meu sono reparador há séculos. Veja você, aqueles Cogs roubaram a minha colcha.\aVocê pode correr e ver se o Marcelo tem alguma coisa em azul?_where_", + INCOMPLETE_PROGRESS : "O que o Marcelo falou sobre a colcha azul?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "Então, a Mel quer uma colcha, né?\aDe que cor? AZUL?!\aBem, eu terei de fazer uma especialmente para ela. Tudo o que eu tenho aqui é em vermelho.\aEscuta... Se você for derrotar alguns Cogs lá fora, farei uma colcha azul especialmente para ela.\aColchas azuis... O que será da próxima vez?", + INCOMPLETE_PROGRESS : "Ainda estou trabalhando na colcha azul, _avName_. Continue a derrotar esses Cogs!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom ver você novamente. Tenho algo pra você!\aAqui está a colcha, e é azul. Ela vai adorar.", + INCOMPLETE_PROGRESS : "A Mel gostou da colcha?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "Minha colcha? Não, não está legal.\aÉ XADREZ! Como alguém pode dormir com uma estampa tão CHAMATIVA?\aVocê terá que levá-la de volta e trazer uma outra colcha.\aTenho certeza de que ele tem outras.", + INCOMPLETE_PROGRESS : "Eu simplesmente não vou aceitar uma colcha xadrez. Veja o que Marcelo pode fazer.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "O quê? Ela não gosta de XADREZ?\aHmm... Deixe-me ver o que eu tenho aqui.\aIsso vai levar algum tempo. Por que você não cuida de alguns Cogs enquanto eu tento encontrar algo diferente?\aTerei alguma coisa quando você estiver de volta.", + INCOMPLETE_PROGRESS : "Ainda estou procurando uma colcha diferente. Como está indo com os Cogs?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "Ei, bom trabalho com os Cogs!\aAqui está, é azul e não é xadrez.\aEspero que ela goste de estampado.\aLeve a colcha para a Mel.", + INCOMPLETE_PROGRESS : "Isso é tudo o que eu tenho para você agora.\aPor favor, leve esta colcha para a Mel.", + COMPLETE : "Ah! Que linda! Estampado combina muito bem comigo.\aÉ hora do meu sono reparador! Até logo, _avName_.\aO quê? Você ainda está aqui? Não vê que estou tentando dormir?\aTome isto aqui e me deixe descansar. Devo estar medonha!", + }, + + 7218 : { QUEST : "Dafne Sonolinda precisa de ajuda._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, _avName_, estou tão feliz em ver você! Aqueles Cogs levaram meus travesseiros.\aVocê pode ver se o Lelê tem alguns travesseiros?_where_\aTenho certeza de que ele pode ajudar.", + INCOMPLETE_PROGRESS : "O Lelê tem algum travesseiro para mim?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "Como vai? A Dafne precisa de alguns travesseiros, né? Bem, você veio ao lugar certo, parceria!\aHá mais travesseiros aqui do que espinhos em um cacto.\aAqui está, _avName_. Leve estes para Dafne, com os meus cumprimentos.\aÉ sempre um prazer ajudar uma mocinha.", + INCOMPLETE_PROGRESS : "Os travesseiros eram macios o suficiente para uma pequena dama?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "Você trouxe os travesseiros! Valeu!\aEi, espere um segundo! Esses travesseiros são muito macios.\aMacios demais para mim. Preciso de travesseiros mais duros.\aLeve estes de volta para o Lelê e veja o que mais ele tem. Valeu.", + INCOMPLETE_PROGRESS : "Não! Muito macios. Peça ao Lelê outros travesseiros.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Muito macios, né? Bem, deixe-me ver o que tenho....\aHmm... Eu achava que tinha um montão de travesseiros duros. Onde eles estão?\aAh! Lembrei. Eu estava vendo se conseguia devolvê-los, então devem estar no estoque.\aQue tal eliminar alguns desses edifícios de Cogs lá fora enquanto eu pego os travesseiros no estoque, parceria?", + INCOMPLETE_PROGRESS : "Os edifícios de Cog são duros de roer. Mas esses travesseiros não são.\aContinuarei procurando.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "Já está de volta? Está tudo bem. Veja, encontrei os travesseiros que a Dafne queria.\aAgora é só levar para ela. Eles são duros o suficiente para quebrar um dente!", + INCOMPLETE_PROGRESS : "É, esses travesseiros são bastante duros. Espero que a Dafne goste deles.", + COMPLETE : "Eu sabia que o Lelê teria alguns travesseiros mais duros.\aAh sim, estes são perfeitos. Bons e duros.\aPor acaso esta peça de vestimenta de Cog seria útil para você? Pode levar.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Passe lá na Cuca P. Gol. Ela perdeu o pijama._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "Não tenho pijamas! Eles sumiram!\aO que vou fazer? Ah! Já sei!\aVá até a Mama. Ela terá pijamas para mim._where_", + INCOMPLETE_PROGRESS : "A Mama tem pijamas para mim?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "E aí, pequeno Toon? A Mama tem os melhores pijamas das Bahamas.\aAh, quer algo para a Cuca P. Gol, né? Bem, deixe-me ver o que tenho aqui.\aAqui está. Agora, ela pode dormir com estilo!\aVocê pode correr e levar isso para ela? Não posso deixar a loja sozinha agora.\aObrigada, _avName_. Vejo você por aí!", + INCOMPLETE_PROGRESS : "Você precisa levar esse pijama para a Cuca P. Gol._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "A Mama mandou esse para mim? Ah...\aEla não tem nenhum pijama com pés?\aEu sempre uso pijamas com pés. Todo mundo usa esse tipo de pijama...\aLeve este de volta e peça a ela que encontre um com pés.", + INCOMPLETE_PROGRESS : "Meu pijama precisa ter pés. Veja o que a Mama pode fazer.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "Pés? Deixe-me pensar....\aEspere aí! Eu tenho um perfeito!\aTchan! Pijama com pés. Um lindo pijama azul com pés. O melhor de toda a face da terra.\aVocê pode levar para ela? Valeu!", + INCOMPLETE_PROGRESS : "A Cuca P. Gol gostou do pijama azul com pés?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "Bem, este TEM pés, mas não posso usar pijama azul!\aPergunte à Mama se ela tem uma cor diferente.", + INCOMPLETE_PROGRESS : "Tenho certeza de que a Mama tem pijamas em uma cor diferente e com pés.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "Que pena. Estes são os únicos pijamas com pés que eu tenho.\aAh, tive uma ideia. Vá perguntar à outra Cuca. Ela talvez tenha algum pijama com pés._where_", + INCOMPLETE_PROGRESS : "Não, aqueles são os únicos que eu tenho. Vá até a outra Cuca para ver o que ela tem._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "Pijama com pés? Sem dúvida.\aComo assim, este é azul? Ela não quer azul?\aNossa, vai ser um pouco difícil. Veja, que tal este?\aEle não é azul e TEM pés.", + INCOMPLETE_PROGRESS : "Eu adoro marrom, você não?\aEspero que a Cuca P. Gol goste....", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "Não, este não é azul, mas ninguém com o meu tom de pele poderia usar marrom.\aNão e não. Ele vai fazer o caminho de volta, e você irá com ele! Veja o que mais a Cuca tem.", + INCOMPLETE_PROGRESS : "A Cuca deve ter mais pijamas. Nada de marrom!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Não pode ser marrom também. Hmm....\aEu sei que tenho outros.\aVai demorar um pouquinho para encontrá-los. Vamos fazer um trato.\aEu procuro outro pijama se você derrotar alguns desses edifícios de Cog. Eles perturbam demais.\aTerei o pijama quando você voltar, _avName_.", + INCOMPLETE_PROGRESS : "Você precisa eliminar mais alguns edifícios de Cog enquanto eu procuro outro pijama.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "Você fez um excelente trabalho com esses Cogs! Valeu!\aAchei este pijama para a Cuca P. Gol; espero que ela goste.\aLeve-o para ela. Obrigada.", + INCOMPLETE_PROGRESS : "A Cuca P. Gol está esperando pelo pijama, _avName_.", + COMPLETE : "Um pijama fúcsia com pés! Perr-feito!\aAh, agora estou pronta. Vejamos....\aAcho que devo lhe dar alguma coisa por ter me ajudado.\aTalvez você possa usar isto. Alguém deixou aqui.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Vá até a Máki Agem. Ela está procurando ajuda._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "Aqueles malditos Cogs levaram meu creme para rugas!\aMeus clientes PRECISAM do creme para rugas enquanto eu trabalho neles.\aVá até o Dedé Descanso e veja se ele tem a minha fórmula especial no estoque._where_", + INCOMPLETE_PROGRESS : "Eu me recuso a trabalhar em alguém sem o creme para rugas.\aVeja o que o Dedé Descanso tem para mim.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "A Máki Agem é uma figura exigente. Ela não vai se contentar com a minha fórmula comum.\aIsso significa que eu precisarei de alguns corais de couve-flor, meu ingrediente especial supersecreto. Mas eu não tenho nada no estoque.\aVocê poderia pescar alguns na lagoa? Assim que você conseguir os corais, eu farei um lote de creme para a Máki Agem.", + INCOMPLETE_PROGRESS : "Precisarei do coral de couve-flor para fazer o lote de creme para rugas.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "Uau, que belo coral de couve-flor!\aOk, vejamos... Um pouco disto e uma pitada daquilo... Agora, um bocado de alga-marinha.\aEssa não, onde está a alga-marinha? Parece que estou sem alga-marinha também.\aVocê pode voltar à lagoa e pescar uma boa alga-marinha viscosa?", + INCOMPLETE_PROGRESS : "Nem uma laminazinha de alga-marinha viscosa na loja.\aNão posso fazer o creme sem ela.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "Aaaah! Que ótima alga-marinha viscosa você trouxe, _avName_.\aAgora, é só espremer algumas pérolas no pilão.\aIh, onde está o meu pilão? Como vou fazer sem o pilão?\aAposto que aquele maldito Agiota o pegou quando esteve aqui!\aVocê precisa me ajudar a encontrá-lo! Ele estava indo ao Quartel do Robô Mercenário!", + INCOMPLETE_PROGRESS : "Eu simplesmente não consigo triturar as pérolas sem um pilão.\aMalditos Agiotas!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "Ótimo! Você trouxe o meu pilão!\aAgora voltemos ao trabalho. Triture aqui... Misture lá e...\aPronto! Diga à Máki Agem que é de boa qualidade e está fresquinho.", + INCOMPLETE_PROGRESS : "Você precisa entregar isso para a Máki Agem enquanto está fresco.\aEla é muito exigente.", + COMPLETE : "O Dedé Descanso não tinha frasco de creme maior que este? Não?\aBem, acho que vou pedir mais quando o meu acabar.\aAté logo, _avName_.\aO quê? Você ainda está aqui? Não vê que estou tentando trabalhar?\aTome isto aqui.", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "Se está interessado em peças de disfarce de Robôs da Lei, visite _toNpcName_.\aOuvi dizer que ele precisa de ajuda na sua pesquisa sobre o clima._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Sim, sim. Eu tenho peças de disfarce de Robôs da Lei.\aMas não tenho interesse nelas.\aO foco da minha pesquisa são as flutuações na temperatura ambiente de Toontown.\aEu troco com você as minhas peças de disfarce por termômetros de cogs.\aVocê pode começar em %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "Ah, ótimo!\aComo eu temia...\aAh, é! Aqui está a sua peça de disfarce.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "Para mais peças de disfarce, visite _toNpcName_ de novo.\aOuvi dizer que ele precisa de assistentes de pesquisa._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "Mais peças de disfarce de Robô da Lei?\aBem, se você insiste...\amas eu vou precisar de outro termômetro de Cog.\aDesta vez, procure em %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[2200][-1], + COMPLETE : "Obrigado!\aE aqui está a sua peça de disfarce.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "Se precisa de mais peças de disfarce de Robô da Lei, vá falar com o _toNpcName_.\aOuvi que ele ainda precisa de ajuda com a pesquisa sobre o clima._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "Você está me saindo bastante útil!\aVocê pode dar uma ollhada em %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[2300][-1], + COMPLETE : "Humm, não gostei muito da aparência disto...\amas aqui está a sua peça de disfarce...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "Você-sabe-quem precisa de mais medições de temperatura.\aDê uma passada se quiser mais uma peça de disfarce._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "Já de volta?\aQue dedicação...\aA próxima parada é %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "Você já tentou observar %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "Isso! Parece que você está pegando o jeito da coisa!\aA sua peça de disfarce...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "Se estiver a fim de mais uma peça de disfarce de Robô da Lei..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "Engraçado encontrar você aqui!\aAgora, eu preciso de medições em %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[1200][-1], + COMPLETE : "Muito obrigado.\aO seu disfarce deve estar quase pronto...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "Acredito que _toNpcName_ tem mais um trabalho para você._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom ver você de novo, _avName_!\aVocê pode fazer uma medição em %s, por favor?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "Ótimo trabalho!\aAqui está a sua merecida recompensa!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "Você sabe o que fazer._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, meu caro!\aVocê pode ir até %s e encontrar mais um termômetro para mim?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "Excelente!\aCom a sua ajuda, a minha pesquisa está caminhando!\aAqui está a sua recompensa.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ estava pedindo por você.\aParece que você causou uma boa impressão!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "Bem-vindo de volta!\aEstive esperando.\aA próxima medição tem que ser em %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[5200][-1], + COMPLETE : "Obrigado!\aAqui está sua recompensa.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "Se precisa completar o seu disfarce de Robô da Lei...\a_toNpcName_ pode ajudar você._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "Olá, Cientista de Pesquisas Iniciante!\aAinda precisamos de medições de %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "Ótimo trabalho!\aAqui está o seu negócio de Robô da Lei...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tem outro trabalho para você.\aSe ainda não tiver se cansado dele..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Então.\aPronto para outra recuperação?\aDesta vez, tente %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[4100][-1], + COMPLETE : "Mais um!\aNossa, você é a eficiência em pessoa!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "Ainda está atrás de peças de disfarce de Robô da Lei?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "Você já deve ter adivinhado...\amas eu preciso de medições de %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[4200][-1], + COMPLETE : "Quase lá!\aAqui está...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "Odeio dizer isto, mas..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "O que acha de %s? Poderia conseguir um termômetro de lá também?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Outro ótimo trabalho, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Vá visitar o Professor se ainda precisar de peças de disfarce._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "Acho que ainda precisamos de uma medição de %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[9100][-1], + COMPLETE : "Bom trabalho!\aAcho que estamos chegando perto...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tem uma última missão para você._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "Já de volta?\aA medição final é em %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[9200][-1], + COMPLETE : "Está pronto!\aAgora, você já pode se infiltrar no Escritório do Promotor Público e coletar Avisos de Júri.\aBoa sorte e obrigado pela sua ajuda!", + }, + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "Se quiser peças de disfarce de Robô Chefe, deve visitar _toNpcName_._where_", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "Sim, posso pegar as suas peças de Robô-Chefe.\aMas vou precisar de sua ajuda para completar a minha coleção de Robô-Chefe.\aVá lá fora e derrote um Puxa-saco. ", + INCOMPLETE_PROGRESS : "Não consegue encontrar um Puxa-saco? Que vergonha...", + COMPLETE : "Você não fracassou, não é?\ aAqui está a sua primeira peça de disfarce. ", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ precisa de mais ajuda se você puder._where_ ", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "Outra peça de disfarce?\aCertamente...\aMas só se você derrotar um Rato de Escritório. ", + INCOMPLETE_PROGRESS : "Os Rato de Escritório podem ser encontrados nas ruas.", + COMPLETE : "Ele realmente foi um fracote! \ aAqui está sua segunda peça de disfarce.", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "Só há mesmo um lugar onde encontrar peças de Robô-Chefe._where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "Agora, preciso de um “Vaquinha de Presépio”...", + INCOMPLETE_PROGRESS : "O “Vaquinha de Presépio” pode ser encontrado nas ruas.", + COMPLETE : "Isso! Cara, você é demais.\aAqui está sua terceira peça de disfarce.", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tem mais peças para você... ", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "Se você derrotar um Micro\4empresário, darei a você mais uma peça.", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[1100][-1], + COMPLETE : "Você se saiu muito bem!\aAqui está sua quarta peça de disfarce.", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "Direto para..._where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "Agora estou atrás de um Facão...", + INCOMPLETE_PROGRESS : "Algum problema? Tente procurar em %s" % GlobalStreetNames[3100][-1], + COMPLETE : "Não foi tão difícil!\aAqui está sua quinta peça de disfarce. ", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "Acho que você sabe aonde ir agora..._where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "Um Caça-\4talentos é o próximo da minha lista.", + INCOMPLETE_PROGRESS : "Você terá mais sorte procurando em edifícios.", + COMPLETE : "Vejo que não teve problemas para caçar um desses.\aAqui está sua sexta peça de disfarce. ", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ precisa de mais Robôs-Chefe. ", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "A seguir, preciso que você localize um Aventureiro Corporativo.", + INCOMPLETE_PROGRESS : "Você terá mais sorte procurando em edifícios.", + COMPLETE : "Você leva mesmo jeito para isso!\aAqui está sua sétima peça de disfarce.", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "Se quiser mais peças de disfarce, vá para..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "Agora, o mais precioso de todos: O um Rei da Cocada Preta!", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Sabia que podia contar com você para cortar...\aAh, não importa.\aAqui está sua próxima peça de disfarce. ", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ estava à sua procura...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "Agora, preciso que você derrote um dos novos e mais traiçoeiros Cogs de Robô-Chefe.", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Eles são mais fortes do que parecem, hein?\aAcho que lhe devo uma peça de disfarce. ", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "Pode dar um giro até..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "Esses Cogs versão 2.0 são muito interessantes.\aPor favor, derrote mais um. ", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Valeu!\aMais uma peça de disfarce chegando. ", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "Se tiver a oportunidade, dê uma parada e visite _toNpcName_.", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "Imagino se puderem se regenerar...", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Acho que não.\aAqui está sua peça... ", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "Você sabe..._where", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "Talvez não sejam Robôs-Chefe afinal...", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Hummm, acho que realmente são Robôs-Chefe.\aConsiga mais uma peça. ", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "Você provavelmente já sabe o que vou dizer...", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "Talvez, de alguma maneira, estejam relacionados aos Esqueletocogs... ", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Isso foi inconsequente...\aAqui está sua peça de disfarce. ", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "Por favor, visite _toNpcName_ novamente.", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "Ainda tenho dúvidas de que não sejam algum tipo de Esqueletocogs...", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Bem, talvez não.\aAqui está sua próxima peça. ", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "Talvez seja o último lugar a que gostaria de ir. Mas...", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "Esses novos Cogs ainda me deixam dúvidas.\aPoderia derrotar mais um, por favor?", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Fascinante. Simplesmente fascinante.\aUma peça de disfarce pelos inconvenientes. ", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ está começando a parecer um disco riscado...", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "Já havia quase descoberto o que são esses novos Cogs.\aSó mais um... ", + INCOMPLETE_PROGRESS : "Tente procurar em %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Sim, acho que encontrei algo importante.\aAh, sim.\aIsso é para você... ", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "Você precisa contar ao Flippy sobre isso...", + INCOMPLETE_PROGRESS : "Flippy está no Toon Hall", + COMPLETE : "Um novo tipo de Cog!\aBom trabalho!\aAqui está sua última peça de disfarce. ", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["au", "arf", "grrrr"] +ChatGarblerCat = ["miau", "miu"] +ChatGarblerMouse = ["quick", "quiiii", "quiiiiquiiii"] +ChatGarblerHorse = ["rííírrrr", "brrr"] +ChatGarblerRabbit = ["ick", "iipr", "iipi", "iicki"] +ChatGarblerDuck = ["quá", "quack", "quáááck"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerBear = ["grrrau", "grrr"] +ChatGarblerPig = ["oinc", "oic", "rrroinc"] +ChatGarblerDefault = ["blá"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Procurando detalhes de %s." +AvatarDetailPanelFailedLookup = "Não foi possível obter detalhes de %s." +#AvatarDetailPanelPlayer = "Jogador: %(player)s\nMundo: %(world)s\nLocal: %(location)s" +# sublocation is not working now +AvatarDetailPanelPlayer = "Jogador: %(player)s\nMundo: %(world)s" +AvatarDetailPanelPlayerShort = "%(player)s\nMundo: %(world)s\nLocal: %(location)s" +AvatarDetailPanelRealLife = "Off-line" +AvatarDetailPanelOnline = "Região: %(district)s\nLocal: %(location)s" +AvatarDetailPanelOnlinePlayer = "Região: %(district)s\nLocal: %(location)s\nJogador: %(player)s" +AvatarDetailPanelOffline = "Região: off-line\nLocal: off-line" +AvatarShowPlayer = "Exibir Jogador" +OfflineLocation = "Off-line" + +#PlayerDetailPanel +PlayerToonName = "Toon: %(toonname)s" +PlayerShowToon = "Mostrar Toon" +PlayerPanelDetail = "Detalhes do jogador" + +# AvatarPanel.py +AvatarPanelFriends = "Amigos" +AvatarPanelWhisper = "Cochichar" +AvatarPanelSecrets = "Segredos" +AvatarPanelGoTo = "Ir para" +AvatarPanelPet = "Mostrar Rabisco" +AvatarPanelIgnore = "Ignorar" +AvatarPanelIgnoreCant = "OK" +AvatarPanelStopIgnoring = "Parar de Ignorar" +AvatarPanelReport = "Relatar" +#AvatarPanelCogDetail = "Dept: %s\nNível: %s\n" +AvatarPanelCogLevel = "Nível: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Detalhes do Toon" +AvatarPanelGroupInvite = "Convidar para Grupo" +AvatarPanelGroupRetract = "Retirar Convite" +AvatarPanelGroupMember = "Já no Grupo" +AvatarPanelGroupMemberKick = "Remova" + +# grouping messages +groupInviteMessage = "%s quer que você entre em seu grupo" + + +# Report Panel +ReportPanelTitle = "Denunciar um Jogador" +ReportPanelBody = "Este recurso enviará uma denúncia completa a um Moderador. Em vez de denunciar, você pode optar pelo seguinte:\n\n - Teleportar-se para outra região\n - Usar \"Ignorar\" no painel do Toon\n\nQuer mesmo denunciar %s para um Moderador?" +ReportPanelBodyFriends = "Este recurso enviará uma denúncia completa a um Moderador. Em vez de denunciar, você pode optar pelo seguinte:\n\n - Teleportar-se para outra região\n - Romper sua amizade\n\nQuer mesmo denunciar %s para um Moderador?\n\n(Isso também vai romper sua amizade)" +ReportPanelCategoryBody = "Você está prestes a denunciar %s. Um Moderador será alertado sobre sua reclamação e tomará medidas apropriadas contra quem estiver quebrando as regras. Escolha o motivo pelo qual está denunciando %s:" +ReportPanelBodyPlayer = "Este recurso ainda está sendo desenvolvido e será disponibilizado em breve. Enquanto isso, você pode fazer o seguinte:\n\n - Vá até o DXD e termine a amizade por lá.\n – Conte aos pais ou responsáveis o que está acontecendo." + +ReportPanelCategoryLanguage = "Linguagem Rude" +ReportPanelCategoryPii = "Compartilhar/Solicitar Informações Pessoais" +ReportPanelCategoryRude = "Comportamento Rude ou Mau" +ReportPanelCategoryName = "Nome Ruim" + +ReportPanelConfirmations = ( + "Você está prestes a denunciar que %s usou linguagem obscena, intolerante, preconceituosa ou sexualmente explícita.", + "Você está prestes a denunciar %s está promovendo insegurança ao divulgar ou solicitar um número de telefone, sobrenome, endereço de e-mail, senha ou nome de conta.", + "Você está prestes a relatar que %s está importunando, atormentando ou usando de comportamento radical para atrapalhar o jogo.", + "Você está prestes a relatar que %s criou um nome que não segue as regras da Disney.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "Levamos as denúncias muito a sério. Sua denúncia será vista por um Moderador, que tomará medidas contra qualquer um que quebrar nossas regras. Se for descoberto que sua conta também quebrou as regras, ou se você fizer denúncias falsas ou abusar do sistema 'Denunciar um Jogador', um Moderador pode tomar medidas contra sua conta. Tem certeza absoluta de que quer denunciar este jogador?" + +ReportPanelThanks = "Obrigado! Sua denúncia foi enviada a um Moderador para análise. Não há necessidade de nos contatarmos novamente sobre o problema. A equipe de moderação tomará medidas adequadas contra um jogador que for descoberto quebrando as regras." + +ReportPanelRemovedFriend = "Removemos automaticamente %s da sua Lista de Amigos." +ReportPanelRemovedPlayerFriend = "Removemos automaticamente %s como amigo Jogador, e você não o verá mais como seu amigo em nenhum produto Disney." + +ReportPanelAlreadyReported = "Você já denunciou %s nesta sessão. Um Moderador vai analisar sua denúncia anterior." + +# Report Panel +IgnorePanelTitle = "Ignorar um Jogador" +IgnorePanelAddIgnore = "Quer ignorar %s pelo restante da sessão?" +IgnorePanelIgnore = "Você agora está ignorando %s." +IgnorePanelRemoveIgnore = "Deseja parar de ignorar %s?" +IgnorePanelEndIgnore = "Você não está mais ignorando %s." +IgnorePanelAddFriendAvatar = "%s está entre seus amigos, você não pode ignorá-lo(la)enquanto forem amigos(as)." +IgnorePanelAddFriendPlayer = "%s (%s)está entre seus amigos, você não pode ignorá-lo(la) enquanto forem amigos(as)." + +# PetAvatarPanel.py +PetPanelFeed = "Alimentar" +PetPanelCall = "Chamar" +PetPanelGoTo = "Ir para" +PetPanelOwner = "Mostrar dono" +PetPanelDetail = "Detalhes do bichinho" +PetPanelScratch = "Coçar" + +# PetDetailPanel.py +PetDetailPanelTitle = "Adestramento" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Pular', + 1: 'Dar a pata', + 2: 'Fingir de morto', + 3: 'Rolar', + 4: 'Dar cambalhota', + 5: 'Dançar', + 6: 'Falar', + } + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutro', + 'hunger': 'faminto', + 'boredom': 'chateado', + 'excitement': 'animado', + 'sadness': 'triste', + 'restlessness': 'inquieto', + 'playfulness': 'brincalhão', + 'loneliness': 'solitário', + 'fatigue': 'cansado', + 'confusion': 'confuso', + 'anger': 'zangado', + 'surprise': 'surpreso', + 'affection': 'carinhoso', + } + +SpokenMoods = { + 'neutral': 'neutro', + 'hunger': 'Eu\estou cansado de Balinhas! Que tal me dar uma fatia de torta?', + 'boredom': 'Você não\ achou que eu entenderia, hein?', + 'excitement': 'Toontástico!', + 'sadness': 'Eu quero ser rabisco de qualidade', + 'restlessness': 'Eu\estou tãooo inquieto', + 'playfulness': 'Brinque comigo ou eu\vou desenterrar algumas flores!', + 'loneliness': 'Quero lutar com os Cogs com você!', + 'fatigue': 'É muito cansativo fazer truques de rabisco! Que\tal dar um tempinho?', + 'confusion': 'Onde estou? Quem é mesmo você?', + 'anger': 'Você sempre me deixa para trás', + 'surprise': 'Opa, de onde você surgiu?', + 'affection': 'Você é um ótimo toon', + } + +# DistributedAvatar.py +DialogExclamation = "!" +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Amigos" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Tentando ir para %s." +TeleportPanelNotAvailable = "%s está ocupado(a) agora; tente novamente mais tarde." +TeleportPanelIgnored = "%s está ignorando você." +TeleportPanelNotOnline = "%s não está on-line neste momento." +TeleportPanelWentAway = "%s saiu." +TeleportPanelUnknownHood = "Você não sabe ir para %s!" +TeleportPanelUnavailableHood = "%s não está disponível agora; tente novamente mais tarde." +TeleportPanelDenySelf = "Você não pode ir lá por conta própria!" +TeleportPanelOtherShard = "%(avName)s está na região %(shardName)s, e você está na região %(myShardName)s. Deseja ir para %(shardName)s?" +TeleportPanelBusyShard = "%(avName)s está em uma Região lotada. Jogar em uma Região lotada pode afetar severamente o desempenho do jogo. Tem certeza de que quer mudar de região?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Sou o chefe." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Sou o Supervisor." +FactoryBossBattleTaunt = "Deixe-me te apresentar ao Supervisor." +MintBossTaunt = "Sou o Supervisor." +MintBossBattleTaunt = "Você precisa falar com o Supervisor." +StageBossTaunt = "A minha Justiça não é Cega" +StageBossBattleTaunt = "Eu estou acima da Lei" +CountryClubBossTaunt = "Sou o Presidente do Clube." +CountryClubBossBattleTaunt = "Você precisa falar com o Presidente do Clube." +ForcedLeaveCountryClubAckMsg = "O Presidente do Clube foi derrotado antes que você pudesse chegar a ele. Você não recuperou nenhuma Ação." + +# HealJokes.py +ToonHealJokes = [ + ["O que faz TIQUE-TIQUE-TIQUE-AU?", + "Um cãonômetro!"], + ["Por que o louco toma banho com o chuveiro desligado?", + "Porque ele comprou xampú para cabelos secos!"], + ["Por que é difícil para o fantasma contar mentiras?", + "Porque seus pensamentos são transparentes."], + ["Do que a bailarina é chamada quando machuca o pé e se recusa a dançar?", + "Pé-nóstica!"], + ["O que a vaca foi fazer no espaço?", + "Foi se encontrar com o vácuo!"], + ["Por que o gato mia para a Lua e a Lua não mia para o gato?", + "Porque astro-no-mia!"], + ["Por que as tartarugas não ficam bêbadas?", + "Porque elas só têm um casco!"], + ["Por que o elefante usa tênis vermelhos?", + "Porque os branquinhos sujam muito."], + ["Por que a galinha atravessa a rua?", + "Para chegar ao outro lado!"], + ["Qual é a maior injustiça do Natal?", + "O peru morre e a missa é do galo."], + ["Qual é o cúmulo dos trabalhos manuais?", + "Tricotar com a linha do trem."], + ["O que é um vulcão?", + "Uma montanha com soluço."], + ["O que é um pontinho vermelho, um azul e um rosa em cima de uma árvore?", + "Um morangotango com urublue num pinkenick."], + ["Por que o elefante não consegue tirar carteira de motorista?", + "Porque ele só dá trombada."], + ["O que um tijolo disse para o outro?", + "Existe um 'ciumento' entre nós."], + ["O que a porta disse para a chave?", + "Vamos dar uma voltinha."], + ["O que o elétron fala quando atende ao telefone?", + "Próton!"], + ["Quem é o rei da horta?", + "Rei Polho."], + ["Por que as pilhas são melhores que os políticos?", + "Porque elas têm, pelo menos, um lado positivo."], + ["O que Benjamin Franklin disse quando inventou a eletricidade?", + "Nada. Ele estava em estado de choque."], + ["Por que o cachorro balança o rabo?", + "Porque o rabo não tem força para balançar o cachorro."], + ["Qual é o cúmulo da força?", + "Dobrar a esquina."], + ["O que não é de comer, mas dá água na boca?", + "O copo."], + ["Quem é a mãe do mingau?", + "Mãe Zena."], + ["O que o Batman disse para o Robin na hora em que entraram no carro?", + "BAT a porta!"], + ["O que é um pontinho amarelo tomando sol?", + "É um fandango querendo virar baconzito."], + ["O que é um pontinho rosa no armário?", + "É um cupink."], + ["Quem é o tio da construção?", + "Tio Jolo."], + ["O que dá um cruzamento de um dálmata com um canário?", + "Uma onça pintada da Amazônia."], + ["O que é uma porção de letras voando?", + "Um bando de borboletras."], + ["O que é que viaja o mundo inteiro, mas fica o tempo todo em um canto só?", + "O selo."], + ["O que é um pontinho verde em cima de um amarelo no canto da parede?", + "Uma ervilha de castigo ajoelhada no milho."], + ["Por que o namoro da goiabada com o queijo não deu certo?", + "Porque o queijo era fresco."], + ["O que é um pontinho azul no guarda-roupas?", + "É uma bluesa."], + ["O que é um pontinho verde no fundo da piscina?", + "É uma ervilha... Segurando a respiração!"], + ["O que é um pontinho vermelho e azul voando de um lado para o outro?", + "Uma mosca fantasiada de Super-homem."], + ["Qual é o animal que tem mais de três olhos e menos de quatro?", + "O pi-olho, ou seja, 3,14."], + ["O que a aranha faz quando vai para a aula de dança?", + "Sapa-teia."], + ["Por que o pato tem ciúmes do cavalo?", + "Porque ele tem quatro patas."], + ["Quando você tem certeza de que um ovo não tem um pintinho dentro?", + "Quando o ovo é de pata."], + ["Por que ninguém apareceu no enterro do elefante?", + "Porque ninguém queria carregar o caixão."], + ["O que é que sempre aumenta, mas nunca diminui?", + "A idade."], + ["O que é que tem muitos pés, mas não fica de pé?", + "A centopéia."], + ["Em que espécie de mato se senta o elefante quando chove?", + "Mato molhado."], + ["Quem é que bate em você, mas você não revida?", + "O vento."], + ["O que é o cúmulo do contra-senso?", + "Na casa de saúde, só haver doentes."], + ["Quando um jogador de futebol é um literato?", + "Quando ele faz um gol de letra."], + ["Onde é que a sereia Ariel vê filmes?", + "No cinemaré."], + ["O que é que atravessa a porta, mas nunca entra nem sai?", + "A fechadura."], + ["Por que os rios são considerados preguiçosos?", + "Porque não saem dos seus leitos."], + ["Qual é a diferença entre a galinha e o tecido?", + "A galinha bota e o tecido desbota."], + ["Era uma vez uma orquestra que não tocava nada. Qual o nome do filme?", + "Os Intocáveis."], + ["Quando é que um gaúcho é chamado de mineiro?", + "Quando trabalha em uma mina."], + ["O que deveríamos colocar embaixo da forca para que o condenado não morra?", + "Cedilha!"], + ["O que é que todos nós temos, mas quando precisamos vamos ao mercado comprar?", + "Canela!"], + ["O que você faz quando está nadando em um oceano e um crocodilo ataca?", + "Você acorda."], + ["Quem é que nasce no rio, vive no rio e morre no rio, mas só se molha se quiser?", + "O carioca."], + ["O que é que está no fim de tudo?", + "A letra O."], + ["Qual é o único monstro que é bonzinho?", + "Good-zila."], + ["O que é a única coisa que o vencedor da maratona perde?", + "O fôlego."], + ["O que acontece se você alimentar uma vaca com flores?", + "Ela dará leite de rosas."], + ["O que é que tem seis olhos, mas não pode ver?", + "Três ratinhos cegos."], + ["Afinal, o que é que sempre encontramos no final do túnel?", + "A letra L."], + ["Qual a palavra que tem duas letras e três sílabas?", + "Arara!"], + ["Por que os elefantes são encontrados na África?", + "Porque eles são muito grandes para se esconderem."], + ["Onde estavam todos os moradores da cidade durante o último apagão?", + "No escuro."], + ["Quando é que o cliente fica preso no banco?", + "Quando fecha a conta-corrente."], + ["Quem é que vai a todos os casamentos sem ser convidado?", + "O padre."], + ["Por que os dinossauros têm pescoços longos?", + "Porque eles têm chulé."], + ["Qual é a mulher que sempre aparece antes do nascer do sol?", + "Aurora."], + ["Por que os elefantes nunca esquecem?", + "Porque ninguém nunca fala nada para eles."], + ["Qual é o país que o criminoso não gosta de visitar?", + "O Cana-dá."], + ["Por que o leão é considerado o rei das selvas?", + "Porque ele é macho; se fosse fêmea, seria rainha."], + ["O que é um 'fuio'?", + "É um 'buiaco na paiede'."], + ["Sou enrolado, tenho a cabeça rachada e vivo apertado?", + "Parafuso."], + ["Por que o cachorro rói o osso?", + "Porque ele não consegue engolir o osso inteiro."], + ["Como é que você impede um elefante de passar pelo buraco de uma agulha?", + "Dando um nó no rabo dele."], + ["Em que lugar do mundo, o sono é mais profundo?", + "No cemitério."], + ["O que é que é menor que a boca de uma formiga?", + "O que ela come."], + ["Um é pouco, dois é bom, três é demais. O que são quatro e cinco?", + "Nove."], + ["Qual é a corrente que, por mais forte que seja, não consegue segurar o navio?", + "A corrente marinha."], + ["O que é que tem boca e um só dente e chama a atenção de muita gente?", + "O sino."], + ["Qual deve ser o comprimento máximo de uma perna?", + "O suficiente para alcançar o chão."], + ["O que é uma molécula?", + "É uma 'Meninula Sapécula'."], + ["Como se pode escrever a maior palavra do mundo?", + "Com a caneta."], + ["Que refeição é colocada sobre a água e não afunda?", + "A bóia."], + ["Qual o melhor castigo para um time de futebol que joga sujo?", + "Levar um banho de gols."], + ["Por que os elefantes usam tênis de corrida?", + "Para fazer cooper, é claro."], + ["Por que os elefantes são grandes e cinza?", + "Porque, se eles fossem pequenos e amarelos, seriam canários."], + ["O que é que tem na árvore, no futebol, no chapéu e na casa?", + "Copa."], + ["O que é que deixa um cachorro desconfiado?", + "Uma pulga atrás da orelha."], + ["Por que o "+ Donald +" espalhou açúcar no travesseiro?", + "Porque ele queria ter doces sonhos."], + ["Por que o "+ Goofy +" levou o pente dele ao dentista?", + "Porque ele perdeu todos os dentes."], + ["Por que o "+ Goofy +" usa a camisa no banho?", + "Porque a etiqueta diz para lavar e usar."], + ["Qual o país está na granja e a capital está no pomar?", + "Peru, capital Lima."], + ["Qual é o prato preferido da maioria das pessoas?", + "O prato cheio."], + ["Como você chama uma pessoa que leva outra para almoçar?", + "Canibal."], + ["O que é um ponto amarelo no canto da sala?", + "É milho Santiago."], + ["O que é um ponto preto dentro do tubo de ensaio?", + "Uma blacktéria."], + ["Por que o "+ Pluto +" dorme com uma casca de banana?", + "Para pular da cama cedo."], + ["Por que o rato usa tênis marrom?", + "Porque o branco está lavando."], + ["O que é que a dentadura tem em comum com as estrelas?", + "Ela sai à noite."], + ["O que é um pontinho preto no meio da estrada?", + "É um calhamblack."], + ["Por que o arqueólogo foi à falência?", + "Porque sua carreira estava uma ruína."], + ["Como é que você ficaria se atravessasse o Atlântico no Titanic?", + "Ensopado."], + ["O que é um pontinho amarelo no alto de um prédio?", + "Um milho suicida."], + ["Por que é que o milho suicida quer se suicidar?", + "Porque o lugar onde ele mora é um bagaço."], + ["O que é um pontinho vermelho lá embaixo do prédio onde está o milho suicida?", + "Um milho bombeiro para salvar o milho suicida..."], + ["Qual a cor mais barulhenta?", + "A corneta."], + ["O que é que a banana suicida falou?", + "Macacos me mordam!!!"], + ["Qual o tipo de alimento de que o político mais gosta?", + "As massas."], + ["O que a chaminé grande falou para a chaminé pequena?", + "Você é muito jovem para fumar."], + ["O que é um pontinho vermelho no pântano?", + "É um jacared."], + ["O que é um pontinho azul no gramado?", + "Uma formiguinha de calça jeans."], + ["O que é um ponto brilhante no gramado?", + "Uma formiguinha de aparelho nos dentes."], + ["O que é um pontinho marrom na pré-história?", + "Um browntossauro."], + ["Como se chama um dinossauro que nunca se atrasa?", + "Prontossauro."], + ["O que é um pontinho vermelho num pedacinho de neve?", + "Uma miniatura da bandeira do Japão."], + ["O que é um pontinho dourado no gramado?", + "É uma formiguinha brincando de Jaspion."], + ["Qual e a comida que liga e desliga ?", + "O StrogON-OFF."], + ["Por que o livro de matemática ficou triste?", + "Porque ele tinha muitos problemas."], + ["O que o tomate foi fazer no banco?", + "Foi tirar extrato"], + ["Como se faz para transformar um giz numa cobra?", + "É só colocar o giz num copo de água. Aí o 'gizbóia'"], + ["Qual é o cúmulo da rapidez?", + "Fechar a gaveta, trancar e jogar a chave dentro."], + ["Qual é o cúmulo do egoísmo?", + "Não vou contar, só eu que sei."], + ["Qual é o cúmulo da revolta?", + "Morar sozinho, fugir de casa e deixar um bilhete dizendo que não volta mais."], + ["Qual é o cúmulo do exagero?", + "Passar manteiga no Pão de Açúcar."], + ["Qual é o cúmulo do arrependimento do carrasco?", + "Pois é, sempre que enforco alguém me dá um nó na garganta..."], + ["Qual é o cúmulo da visão?", + "Derrubar dez faixas-pretas com um golpe de vista."], + ["Qual é o cúmulo da sorte?", + "Ser atropelado por uma ambulância."], + ["Qual é o cúmulo da maldade?", + "Colocar tachinhas na cadeira elétrica."], + ["Qual é o cúmulo da burrice?", + "Ser reprovado no exame de fezes."], + ["Qual é o cúmulo da economia?", + "Usar o papel higiênico dos dois lados."], + ["Qual é o cúmulo do esquecimento?", + "Ih! Esqueci!"], + ["Qual é o cúmulo da sede?", + "Tomar um ônibus."], + ["O que que faz ABC...Slurp...DEF...Slurp?", + "Alguém tomando sopa de letrinhas."], + ["O que é que é verde e fica saltando sem parar em cima do sofá?", + "Uma ervilha que saiu do castigo."], + ["O que é que o tomate foi fazer no banco?", + "Tirar extrato."], + ["Por que o médico que trabalha à noite se veste de verde?", + "Porque ele está de plantão."], + ["O que é que é branco com pontinhos pretos e vermelhos?", + "Um dálmata com catapora."], + ["O que a galinha foi fazer na igreja?", + "Assistir à missa do galo."], + ["O que é o que é? Cai em pé e corre deitado?", + "Não é a chuva não! É uma minhoca de paraquedas."], + ["Por que é que não é bom guardar o quibe no freezer?", + "Porque lá dentro ele esfirra."], + ["O que o advogado do frango foi fazer na delegacia?", + "Foi soltar a franga"], + ["Por que o galo canta de olhos fechados?", + "Porque ele já sabe a música de cor."], + ["Um peixe foi jogado de cima de um prédio de vinte andares. Que peixe era esse?", + "Um atum, porque quando ele caiu fez: Aaaaaaaaaaaa Tum!"], + ["Como se faz omelete de chocolate?", + "Com ovos de Páscoa."], + ["Para que servem óculos verdes?", + "Para verde perto."], + ["Para que servem óculos vermelhos?", + "Para 'vermelhor'."], + ["O que é verde por fora e amarela por dentro?", + "Uma banana disfarçada de pepino."], + ["Qual é a parte do carro que se originou no Antigo Egito?", + "Os faraóis."], + ["Como é que a bruxa sai na chuva?", + "De rodo."], + ["Por que o cachorro entrou na igreja?", + "Porque ele é um cão pastor."], + ["Quem é o pai do volante?", + "O painel."], + ["Como chamamos uma mulher que visitou uma plantação de uva?", + "Viúva."], + ["O que o amendoim falou para o elefante?", + "Nada, o amendoim não fala."], + ["O que os elefantes falam quando se esbarram?", + "Mundo pequeno esse, né?"], + ["O que o caixa falou para a registradora?", + "Estou contando com você."], + ["Por que o caminhão de frigorífico não sobe a ladeira?", + "Porque 'elinguiça'."], + ["Qual é a comida que liga e desliga?", + "É o strogON-OFF."], + ["O que a vaca foi fazer na Argentina?", + "Foi ver o Boi nos Ares."], + ["Qual é o peixe mais salgado que existe?", + "O sal-mão."], + ["O que é um cão indeciso?", + "É um 'cão-fuso'."], + ["Sabe por que o italiano não come churrasco?", + "Porque o macarrão não cabe no espeto."], + ["Qual é o cúmulo da rapidez?", + "Ir ao enterro de um parente e ainda encontrá-lo vivo."], + ["Qual é o cúmulo do azar?", + "Ser atropelado por um carro funerário."], + ["Por que o jacaré tomou o cartão de crédito do jacarezinho?", + "Porque o jacarezinho gastou muito e mandou o jacarepaguá."], + ["Qual é o cúmulo da burrice?", + "Olhar pelo buraco da fechadura numa porta de vidro."], + ["Qual é o cúmulo da confiança?", + "Jogar par-ou-ímpar pelo telefone?"], + ["Qual é o cúmulo da paciência?", + "Esvaziar uma piscina com conta-gotas."], + ["Qual é o cúmulo da traição?", + "Suicidar-se com uma punhalada nas costas."], + ["O que uma nuvem disse pra outra?", + "'Nu-vem' não."], + ["Qual é o cúmulo da moleza?", + "Correr sozinho e chegar em segundo."], + ["Por que o jacaré tirou o jacarezinho da escola?", + "Porque ele 'reptil'."], + ["Qual é o fim da picada?", + "Quando o mosquito vai embora."], + ["O que o paraquedas disse para o paraquedista?", + "Tô contigo e não abro."], + ["Qual é a cor mais barulhenta?", + "A corneta."], + ["O que é um pontinho amarelo no céu?", + "Um yellowcóptero."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","hehe","ah","Rá rá") +MovieHealLaughterHits1= ("Ah ah ah","Ri, ri, ri","Ré, ré","Ah, ah") +MovieHealLaughterHits2= ("AH HAH HAH!","HO HO HO!","RÁ RÁ RÁ!") + +# MovieSOS.py +MovieSOSCallHelp = "%s SOCORRO!" +MovieSOSWhisperHelp = "%s precisa de ajuda na batalha!" +MovieSOSObserverHelp = "SOCORRO!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Oi %s! É uma satisfação ajudar você!" +MovieNPCSOSGoodbye = "Vejo você depois!" +MovieNPCSOSToonsHit = "Os Toons sempre acertam!" +MovieNPCSOSCogsMiss = "Os Cogs sempre erram!" +MovieNPCSOSRestockGags = "Reabastecendo com %s piadas!" +MovieNPCSOSHeal = "Curar" +MovieNPCSOSTrap = "Armadilha" +MovieNPCSOSLure = "Isca" +MovieNPCSOSSound = "Sonora" +MovieNPCSOSThrow = "Lançamento" +MovieNPCSOSSquirt = "Esguicho" +MovieNPCSOSDrop = "Cadente" +MovieNPCSOSAll = "Todos" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Suspiro" +MoviePetSOSTrickSucceedBoy = "Bom garoto!" +MoviePetSOSTrickSucceedGirl = "Boa menina!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "CANCELADO\nCANCELADO\nCANCELADO" + +# RewardPanel.py +RewardPanelToonTasks = "Tarefas Toon" +RewardPanelItems = "Itens recuperados" +RewardPanelMissedItems = "Itens não-recuperados" +RewardPanelQuestLabel = "Buscar %s" +RewardPanelCongratsStrings = ["É isso aí!", "Parabéns!", "Uau!", + "Legal!", "Caraca!", "Toontástico!"] +RewardPanelNewGag = "Nova piada %(gagName)s para %(avName)s!" +RewardPanelUberGag = "%(avName)s ganhou a piada %(gagName)s com %(exp)s pontos de experiência!" +RewardPanelEndTrack = "Oba! %(avName)s chegou ao fim da Trilha de Piadas da piada %(gagName)s!" +RewardPanelMeritsMaxed = "Maximizados" +RewardPanelMeritBarLabels = [ "Bilhetes azuis", "Intimações", "Granas Cog", "Méritos" ] +RewardPanelMeritAlert = "Pronto para a promoção!" + +RewardPanelCogPart = "Você ganhou uma parte de disfarce de Cog!" +RewardPanelPromotion = "%s prepare-se para a promoção!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Toon normal", "você ficará normal"), + ("Cabeção", "você ficará com uma cabeça grande"), + ("Cabecinha", "você ficará com uma cabeça pequena"), + ("Pernonas", "você ficará com pernas grandes"), + ("Perninhas", "você ficará com pernas pequenas"), + ("Toonzão", "você ficará um pouco maior"), + ("Toonzinho", "você ficará um pouco menor"), + ("Quadro reto", "você ficará em duas dimensões"), + ("Perfil reto", "você ficará em duas dimensões"), + ("Transparente", "você ficará transparente"), + ("Sem cor", "você ficará sem cor"), + ("Toon invisível", "você ficará invisível"), + ] +CheesyEffectIndefinite = "Até que escolha outro efeito, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Nos próximos %(time)s minutos, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Nas próximas %(time)s horas, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Nos próximos %(time)s dias, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " enquanto estiver %s" +CheesyEffectExceptIn = ", exceto em %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Puxa-saco" +SuitPencilPusher = "Rato de Escritório" +SuitYesman = "Vaquinha de Presépio" +SuitMicromanager = "Micro\4empresário" +SuitDownsizer = "Facão" +SuitHeadHunter = "Caça-\4talentos" +SuitCorporateRaider = "Aventureiro Corporativo" +SuitTheBigCheese = "O Rei da Cocada Preta" +SuitColdCaller = "Rei da Incerta" +SuitTelemarketer = "Operador de Tele\4marketing" +SuitNameDropper = "Dr. Sabe-com-\4quem-está-\4falando" +SuitGladHander = "Amigo da Onça" +SuitMoverShaker = "Agitador" +SuitTwoFace = "Duas Caras" +SuitTheMingler = "Amizade Fácil" +SuitMrHollywood = "Dr. Celebridade" +SuitShortChange = "Farsante" +SuitPennyPincher = "Mão de vaca" +SuitTightwad = "Pão-duro" +SuitBeanCounter = "Conta-\4moedinha" +SuitNumberCruncher = "Destruidor de Números" +SuitMoneyBags = "Sacos de Dinheiro" +SuitLoanShark = "Agiota" +SuitRobberBaron = "Barão Ladrão" +SuitBottomFeeder = "Comensal" +SuitBloodsucker = "Sanguessuga" +SuitDoubleTalker = "Duplo Sentido" +SuitAmbulanceChaser = "Perseguidor de Ambulâncias" +SuitBackStabber = "Golpe Sujo" +SuitSpinDoctor = "Relações Públicas" +SuitLegalEagle = "Macaco velho" +SuitBigWig = "Figurão" + +# Singular versions (indefinite article) +SuitFlunkyS = "um Puxa-saco" +SuitPencilPusherS = "um Rato de Escritório" +SuitYesmanS = "uma Vaquinha de Presépio" +SuitMicromanagerS = "um Micro\4empresário" +SuitDownsizerS = "um Facão" +SuitHeadHunterS = "um Caça-talentos" +SuitCorporateRaiderS = "um Aventureiro Corporativo" +SuitTheBigCheeseS = "um Rei da Cocada Preta" +SuitColdCallerS = "um Rei da Incerta" +SuitTelemarketerS = "um Operador de Telemarketing" +SuitNameDropperS = "um Dr. Sabe-com-\4quem-está-\4falando" +SuitGladHanderS = "um Amigo da Onça" +SuitMoverShakerS = "um Agitador" +SuitTwoFaceS = "um Duas Caras" +SuitTheMinglerS = "um Amizade Fácil" +SuitMrHollywoodS = "um Dr. Celebridade" +SuitShortChangeS = "um Farsante" +SuitPennyPincherS = "um Mão de vaca" +SuitTightwadS = "um Pão-duro" +SuitBeanCounterS = "um Conta-\4moedinha" +SuitNumberCruncherS = "um Destruidor de Números" +SuitMoneyBagsS = "um Sacos de Dinheiro" +SuitLoanSharkS = "um Agiota" +SuitRobberBaronS = "um Barão Ladrão" +SuitBottomFeederS = "um Comensal" +SuitBloodsuckerS = "um Sanguessuga" +SuitDoubleTalkerS = "um Duplo Sentido" +SuitAmbulanceChaserS = "um Perseguidor de Ambulâncias" +SuitBackStabberS = "um Golpe Sujo" +SuitSpinDoctorS = "um Relações Públicas" +SuitLegalEagleS = "um Macaco velho" +SuitBigWigS = "um Figurão" + +# Plural versions +SuitFlunkyP = "Puxa-sacos" +SuitPencilPusherP = "Ratos de Escritório" +SuitYesmanP = "Vaquinhas de Presépio" +SuitMicromanagerP = "Micro\4empresários" +SuitDownsizerP = "Facões" +SuitHeadHunterP = "Caça-\4talentos" +SuitCorporateRaiderP = "Aventureiros Corporativos" +SuitTheBigCheeseP = "Os Reis da Cocada Preta" +SuitColdCallerP = "Reis da Incerta" +SuitTelemarketerP = "Operadores de Tele\4marketing" +SuitNameDropperP = "Drs. Sabe-com-\4quem-está-\4falando" +SuitGladHanderP = "Amigos da Onça" +SuitMoverShakerP = "Agitadores" +SuitTwoFaceP = "Duas Caras" +SuitTheMinglerP = "Amizades Fáceis" +SuitMrHollywoodP = "Drs. Celebridade" +SuitShortChangeP = "Farsantes" +SuitPennyPincherP = "Mãos de vaca" +SuitTightwadP = "Pães-duros" +SuitBeanCounterP = "Conta-\4moedinhas" +SuitNumberCruncherP = "Destruidores de Números" +SuitMoneyBagsP = "Sacos de Dinheiro" +SuitLoanSharkP = "Agiotas" +SuitRobberBaronP = "Barões Ladrões" +SuitBottomFeederP = "Comensais" +SuitBloodsuckerP = "Sanguessugas" +SuitDoubleTalkerP = "Duplos Sentidos" +SuitAmbulanceChaserP = "Perseguidores de Ambulâncias" +SuitBackStabberP = "Golpes Sujos" +SuitSpinDoctorP = "Relações Públicas" +SuitLegalEagleP = "Macacos velhos" +SuitBigWigP = "Figurões" + +SuitFaceOffDefaultTaunts = ['Buuuuu!'] + +SuitAttackDefaultTaunts = ['Pega essa!', 'Pode escrever!'] + +SuitAttackNames = { + 'Audit' : 'Auditoria!', + 'Bite' : 'Mordida!', + 'BounceCheck' : 'Cheque sem fundos!', + 'BrainStorm' : 'Grande ideia!', + 'BuzzWord' : 'Palavra-chave!', + 'Calculate' : 'Calcular!', + 'Canned' : 'Enlatado!', + 'Chomp' : 'Nhac!', + 'CigarSmoke' : 'Fumaça de charuto!', + 'ClipOnTie' : 'Prendedor de gravata!', + 'Crunch' : 'Triturar!', + 'Demotion' : 'Rebaixar!', + 'Downsize' : 'Reduzir!', + 'DoubleTalk' : 'Duplo sentido!', + 'EvictionNotice' : 'Aviso de despejo!', + 'EvilEye' : 'Mau-olhado!', + 'Filibuster' : 'Enchedor de linguiça!', + 'FillWithLead' : 'Pelotão de frente!', + 'FiveOClockShadow' : "Barba por fazer!", + 'FingerWag' : 'Dedo na cara!', + 'Fired' : 'Fogo!', + 'FloodTheMarket' : 'Invadir o mercado!', + 'FountainPen' : 'Caneta-tinteiro!', + 'FreezeAssets' : 'Bens congelados!', + 'Gavel' : 'Martelo!', + 'GlowerPower' : 'Olhar raivoso!', + 'GuiltTrip' : 'Sentimento de culpa!', + 'HalfWindsor' : 'Nó francês!', + 'HangUp' : 'Desligar!', + 'HeadShrink' : 'Analista!', + 'HotAir' : 'Ar quente!', + 'Jargon' : 'Jargão!', + 'Legalese' : 'Legalês!', + 'Liquidate' : 'Liquidar!', + 'MarketCrash' : 'Queda da Bolsa!', + 'MumboJumbo' : 'Bobeira!', + 'ParadigmShift' : 'Desvio de paradigma!', + 'PeckingOrder' : 'Hierarquia!', + 'PickPocket' : 'Pivete!', + 'PinkSlip' : 'Bilhete azul!', + 'PlayHardball' : 'Jogo duro!', + 'PoundKey' : 'Tecla de Jogo da velha!', + 'PowerTie' : 'Gravata!', + 'PowerTrip' : 'Viajou na autoridade!', + 'Quake' : 'Terremoto!', + 'RazzleDazzle' : 'Agito!', + 'RedTape' : 'Burrocracia!', + 'ReOrg' : 'ReOrg!', + 'RestrainingOrder' : 'Repressão!', + 'Rolodex' : 'Agenda telefônica!', + 'RubberStamp' : 'Carimbo!', + 'RubOut' : 'Apagar!', + 'Sacked' : 'Ensacado!', + 'SandTrap' : 'Trincheira!', + 'Schmooze' : 'Bajula!', + 'Shake' : 'Tremor!', + 'Shred' : 'Retalho!', + 'SongAndDance' : 'Conta prosa!', + 'Spin' : 'Giro!', + 'Synergy' : 'Sinergia!', + 'Tabulate' : 'Tabular!', + 'TeeOff' : 'Tacada!', + 'ThrowBook' : 'Livro de lançamentos!', + 'Tremor' : 'Tremor!', + 'Watercooler' : 'Bebedouro!', + 'Withdrawal' : 'Retirada!', + 'WriteOff' : 'Baixa!', + } + +SuitAttackTaunts = { + 'Audit': ["Seus livros não têm balanço.", + "Parece que você está no vermelho.", + "Deixe-me ajudá-lo com esses livros.", + "Sua coluna de débitos é muito alta.", + "Vamos verificar os seus bens.", + "Assim, você vai ficar endividado.", + "Vamos conferir direitinho o que você deve.", + "Assim, a sua conta vai ficar zerada.", + "É hora de você se responsabilizar pelas suas despesas.", + "Encontrei um erro nos seus livros.", + ], + 'Bite': ["Quer uma mordida?", + "Dá uma mordida!", + "A sua mordida é maior do que você pode mastigar.", + "Minha mordida é maior do que o meu latido.", + "Morde logo!", + "Tome cuidado, eu mordo.", + "Eu não mordo só quando estou encurralado.", + "Só vou dar uma mordidinha.", + "Não dei uma mordida o dia todo.", + "Só quero uma mordida. É pedir muito?", + ], + 'BounceCheck': ["Ah, que pena, você não tem graça.", + "Você tem uma dívida.", + "Acho que este cheque é seu.", + "Você me devia isso.", + "Estou cobrando esta dívida.", + "Este cheque não vai ser mole.", + "Você será cobrado por isso.", + "Feche a conta.", + "Isso terá um custo para você.", + "Queria trocar por dinheiro.", + "Vou mandar isso de volta para você.", + "Esta conta está salgada.", + "Estou descontando o serviço.", + ], + 'BrainStorm':["Acho que vai chover.", + "Espero que você esteja com o guarda-chuva.", + "Quero orientar você.", + "Que tal uma saraivada básica?", + "Cadê o seu brilho agora, Toon?", + "Pronto para a chuvarada?", + "Vou atacar você como um furacão.", + "Chamo isso de ataque-relâmpago.", + "Adoro ser um desmancha-prazeres.", + ], + 'BuzzWord':["Desculpe-me se estou te aborrecendo.", + "Ouviu a última?", + "Veja se você pega esta.", + "Vamos cantarolar, Toon?", + "Deixe-me defender você.", + "Vou \"C\" perfeitamente claro.", + "Você devia \"C\" mais cuidadoso.", + "Veja se você consegue desviar desse enxame.", + "Cuidado, você está prestes a ser picado.", + "Parece que a sua urticária é séria.", + ], + 'Calculate': ["Estes números fazem mesmo uma diferença!", + "Você contou com isso?", + "Faça as contas, você está caindo.", + "Deixe-me ajudar você a somar isso.", + "Você registrou todas as suas despesas?", + "De acordo com os meus cálculos, você não ficará por muito tempo aqui.", + "Aqui está o total.", + "Uau, a sua conta está se multiplicando.", + "Tente brincar com esses números!", + Cogs + ": 1 Toons: 0", + ], + 'Canned': ["Gosta fora da lata?", + "\"Lata\" limpo?", + "Fresquinho, saído da lata!", + "Já foi atacado alguma vez por enlatados?", + "Gostaria de doar a você este enlatado!", + "Prepare-se para o \"Vira-lata\"!", + "Você acha que pode abrir a lata, na lata.", + "Vou te jogar na lata!", + "Vou transformar você em um a-Toon em lata!", + "Seu gosto não é tão bom fora da lata.", + ], + 'Chomp': ["Olha só esses comilões!", + "Nhac, nhac, nhac!", + "Aqui tem algo para mastigar.", + "Procurando alguma coisa para mastigar?", + "Por que você não mastiga um pouco disto?", + "Eu vou jantar você.", + "Adoro comer Toons no café da manhã!", + ], + 'ClipOnTie': ["Melhor se arrumar para a reunião.", + "Você não pode SAIR sem a gravata.", + "Os "+ Cogs +" mais bem vestidos usam isto." + "Experimente este tamanho.", + "Você devia se vestir para arrasar.", + "Sem gravata não tem serviço...", + "Precisa de ajuda para se vestir?", + "Nada é tão poderoso quanto uma boa gravata.", + "Vamos ver se serve.", + "Esta vai apertar você.", + "Você vai querer se vestir antes de SAIR.", + "Acho que vou dar uma gravata em você.", + ], + 'Crunch': ["Parece que você está espremido contra a parede.", + "Hora de mexer a mandíbula!", + "Vou dar alguma coisa para você mascar!", + "Triture isso!", + "Mordida para viagem.", + "Qual você prefere, molinho ou crocante?", + "Espero que esteja preparado para a hora da mandíbula.", + "Parece que você está ficando amassadinho!", + "Vou amassar você como uma latinha." + ], + 'Demotion': ["Você está descendo os degraus da empresa.", + "Vou mandar você de volta para a Expedição.", + "Está na hora de virar a sua placa de identificação.", + "Você está caidaço, palhaço.", + "Parece que você está ferrado.", + "Você não vai a lugar nenhum.", + "Você está em um beco sem saída.", + "Você não vai se mover tão cedo.", + "Você não vai a lugar nenhum.", + "Vai ficar registrado em seu arquivo permanente.", + ], + 'Downsize': ["Desce!", + "Sabe como descer?", + "Vamos entrar direto no assunto.", + "O que houve? Você parece deprimido.", + "Decaindo?", + "O que é que está caindo? Você!", + "Por que não tem alguém do meu tamanho?", + "Por que eu não meço você - ou será que é melhor dizer despeço?", + "Quer um tamanho menor por apenas mais uma moeda?", + "Experimente este tamanho!", + "Tem em um tamanho menor.", + "Este ataque é tamanho único!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["Mudança à vista.", + "Arrume as malas, Toon.", + "É hora de arrumar outro lugar para morar.", + "Considere-se servido.", + "O seu aluguel está atrasado.", + "Isto vai te torpedear.", + "Você está prestes a ser despejado.", + "Vou espirrar você daqui.", + "Você está deslocado.", + "Prepare-se para ser realocado.", + "Você está abrigado.", + ], + 'EvilEye': ["Estou botando um mau-olhado em você.", + "Você fica de olho vivo nisso para mim?", + "Espere. Tem alguma coisa no meu olho.", + "Estou de olho em você!", + "Você pode botar o olho nisso aqui para mim?", + "Tenho um olho gordo danado.", + "Você vai levar um soco no olho!", + "Minha crueldade não está de molho, abre o olho!", + "Vou colocar você no olho do furacão!", + "Estou dando com os olhos em você.", + ], + 'Filibuster':["Devo encher?", + "Isso vai demorar um pouco.", + "Poderia fazer isso o dia todo.", + "Não preciso nem respirar fundo.", + "Vou fazendo, fazendo, fazendo...", + "Nunca fica cansado de fazer isso.", + "Posso tagarelar sem parar.", + "Tem problema se eu puxar a sua orelha?", + "Acho que vou papear à vontade.", + "Sempre consigo dar o meu recado.", + ], + 'FingerWag': ["Já te disse milhares de vezes.", + "Olha aqui, Toon.", + "Não me faça rir.", + "Não me faça ir até aí.", + "Já cansei de repetir.", + "Fim de papo, eu já falei.", + "\Você não tem respeito por nós, "+ Cogs +"." + "Acho que está na hora de você prestar atenção.", + "Blá, Blá, Blá, Blá, Blá.", + "Não me obrigue a interromper a reunião.", + "Será que eu vou ter que separar vocês?", + "Já passamos por isto antes.", + ], + 'Fired': ["É fogo! O jeito é fazer um churrasquinho.", + "Vai esquentar por aqui.", + "Assim, o frio passa.", + "Espero que você tenha sangue frio.", + "Quente, quentão e pelando.", + "Melhor parar tudo, deitar no chão e rolar!", + "Você está fora daqui.", + "O que você acha de \"bem-feito\"?", + "Pode dizer aí?", + "Espero que tenha usado protetor solar.", + "Está se sentindo um pouco tostado?", + "Você vai arder em chamas.", + "Você vai ficar aceso que nem fogueira.", + "Você está frito.", + "Eu sou fogo na roupa.", + "Só aticei o fogo um pouquinho, né?", + "Olha, um churrasquinho crocante.", + "Você não devia sair por aí malpassado.", + ], + 'FountainPen': ["Vai deixar mancha.", + "Vamos assinar embaixo.", + "Esteja preparado para alguns danos irreparáveis.", + "Você vai precisar de um bom tintureiro.", + "Você devia mudar.", + "Esta caneta-tinteiro tem uma tinta legal.", + "Aqui, vou usar a minha caneta.", + "Você entende a minha letra?", + "Isso é que é carregar nas tintas.", + "Seu desempenho babou.", + "Não é chato quando isso acontece?", + ], + 'FreezeAssets': ["Seus bens são meus.", + "Está sentindo um vento? É o cheque voador.", + "Espero que não tenha planos.", + "Isso vai manter você na geladeira.", + "Tem uma brisa fria no ar.", + "O inverno está chegando mais cedo neste ano.", + "Você está sentido um calafrio?", + "Vou cristalizar o meu plano.", + "Você vai ver, no duro.", + "O gelo queima.", + "Espero que goste de frios.", + "Tenho muito sangue frio.", + ], + 'GlowerPower': ["Está olhando para mim?", + "Disseram que tenho olhos muito penetrantes.", + "Gosto de estar no fio da navalha.", + "Caçamba, caramba, meus quatro-olhos não são bambas?", + "Estou de olho em você, pirralho.", + "Que tal estes olhos expressivos?", + "Meus olhos são o meu forte.", + "Enche os olhos.", + "Estou de olho, piolho.", + "Olhe nos meus olhos...", + "Podemos dar uma espiada no seu futuro?", + ], + 'GuiltTrip': ["Você vai ficar com um baita sentimento de culpa!", + "Está se sentindo culpado?", + "É tudo culpa sua!", + "Sempre ponho a culpa de tudo em você.", + "Afogue-se na própria culpa!", + "Nunca mais falo contigo!", + "É melhor pedir desculpas.", + "Só vou perdoar você daqui a um milhão de anos!", + "Está preparado para viajar na maionese da culpa?", + "Ligue para mim quando voltar de viagem.", + "Quando você volta de viagem?", + ], + 'HalfWindsor': ["Esta é a gravata mais elegante que você já viu!", + "Procure não apertar tanto.", + "Você não viu nem metade do nó em que você se meteu.", + "Você tem sorte de eu não saber francês.", + "Esta gravata é demais para você.", + "Aposto como você nunca VIU um nó francês!", + "Esta gravata não é para o seu bico.", + "Eu não deveria ter gasto esta gravata com você.", + "Você não vale nem o nó desta gravata!", + ], + 'HangUp': ["Você foi desconectado.", + "Tchau!", + "Está na hora de terminar a sua conexão.", + "...e não ligue de novo!", + "Clique!", + "A conversa acabou.", + "Estou cortando este fio.", + "Acho que você está meio desligado.", + "Parece que você está com mau contato.", + "Seu tempo acabou.", + "Espero que tenha ouvido em claro e bom som.", + "Foi engano.", + ], + 'HeadShrink': ["Parece que você tem ido ao analista.", + "Querida, encolhi o analista.", + "Espero que não precise analisar o seu amor-próprio.", + "Você se abriu?", + "Analiso, logo existo.", + "Não é nada que faça você perder a cabeça.", + "Você vai abrir a cabeça?", + "Levanta essa cabeça! Ou será que é melhor abaixar?", + "Os objetos podem ser maiores do que parecem.", + "Os melhores Toons vêm nos menores frascos.", + ], + 'HotAir':["Estamos tendo uma discussão acalorada.", + "Está rolando uma onda de calor.", + "Atingi o meu ponto de ebulição.", + "Que vento cortante.", + "Odeio ter que te grelhar, mas...", + "Lembre-se sempre: onde há fumaça, há fogo.", + "Você parece meio queimadinho.", + "Outra reunião que virou fumaça.", + "Acho que está na hora de botar lenha na fogueira.", + "Deixe-me acender uma relação de trabalho.", + "Tenho umas observações inflamadas pra você.", + "Ataque aéreo!!!", + ], + 'Jargon':["Que besteira.", + "Veja se você consegue ver algum sentido nisso.", + "Espero que tenha sido claro como água.", + "Parece que vou ter que falar mais alto.", + "Insisto em ter a palavra.", + "Sou muito direto.", + "Devo sustentar a minha opinião neste assunto.", + "Olha, as palavras podem machucar você.", + "Entendeu o que eu quis dizer?", + "Palavras, palavras, palavras, palavras, palavras.", + ], + 'Legalese':["Você deve se conformar e desistir.", + "Você vai ser derrotado, legalmente falando.", + "Você está ciente das implicações legais?", + "Você não está acima da lei!", + "Devia haver uma lei contra você.", + "Não há lei marcial comigo!", + "As opiniões expressadas neste ataque não são compartilhadas pela Toontown On-line da Disney.", + "Não podemos ser responsabilizados por danos sofridos neste ataque.", + "Os resultados deste ataque podem variar.", + "Este ataque não tem validade legal quando proibido.", + "Você não se enquadra no meu sistema legal!", + "Você não sabe lidar com assuntos jurídicos.", + ], + 'Liquidate':["Gosto de manter as coisas fluindo.", + "Você está com algum problema de fluxo de caixa?", + "Vou ter que lavar os seus bens.", + "É hora de você ser levado pelo fluxo.", + "Não se esqueça de que fica escorregadio quando está molhado.", + "Os números estão correndo.", + "Você escorrega que nem sabão.", + "Está caindo tudo em cima de você.", + "Acho que você vai por ralo abaixo.", + "Você tomou uma lavada.", + ], + 'MarketCrash':["Vou acabar com a sua festa.", + "Você não vai sobreviver à queda.", + "Sou mais do que o mercado pode aguentar.", + "Tenho uma queda por você!", + "Agora eu vou entrar detonando.", + "Sou um verdadeiro dragão no mercado.", + "Parece que o mercado está em baixa.", + "É melhor você sair fora rapidamente!", + "Vender! Vender! Vender!", + "Devo liderar a recessão?", + "Todo mundo está saindo fora, você não vai?", + ], + 'MumboJumbo':["Deixe-me explicar melhor.", + "É muito simples.", + "Vamos fazer desta maneira.", + "Deixe-me ampliar para você.", + "Você pode chamar isso de baboseira tecnológica.", + "Aqui estão meus eufemismos.", + "Caramba, isso é que é encher a boca.", + "Algumas pessoas me chamam de exagerado.", + "Posso me meter?", + "Acho que estas são as palavras certas.", + ], + 'ParadigmShift':["Cuidado! Eu saio pela tangente.", + "Prepare-se para mudar radicalmente!" + "Não é uma mudança interessante?" + "Você vai ter que desviar de caminho.", + "Agora é sua vez de desviar.", + "Acabou o desvio!", + "Você nunca trabalhou tanto neste desvio.", + "Estou transviando você!", + "Olhe para o meu rabo de olho!", + ], + 'PeckingOrder':["Este aqui é para quem berra mais.", + "Prepare-se para o grito de guerra.", + "Por falta de um grito, morre um burro no atoleiro.", + "Vou ganhar no grito.", + "Você está no último grito da hierarquia.", + "Se gritos resolvessem, porcos não morreriam!", + "A ordem está valendo, no grito!", + "Por que não grito com alguém do meu tamanho? Ah!", + "Cão que ladra não morde.", + ], + 'PickPocket': ["Deixe-me verificar os seus pertences.", + "E aí, qual é o pó?", + "É mais fácil do que tirar doce de criança.", + "Golpe de mestre.", + "Deixa que eu seguro para você.", + "Não tire os olhos de minhas mãos.", + "As mãos são mais rápidas que os olhos.", + "Não tenho nada para tirar da manga.", + "A gerência não se responsabiliza por extravio de itens.", + "Achado não é roubado.", + "Você nem vai sentir.", + "Dois pra mim, um pra você.", + "Está bom assim.", + "Você não vai precisar mesmo...", + ], + 'PinkSlip': ["Tente imaginar que está tudo azul.", + "Tá com medo? Você está azul!", + "Com certeza, este bilhete vai fazer você ficar azul.", + "Êpa, acho que mudei de cor, né?", + "Olha lá, você não quer ficar azul, ou quer?", + "Este bilhete não é branco, é azul.", + "Estou azul de fome!", + "Você se importa que eu passe aí para ver se está tudo azul?", + "O azul não é exatamente a sua cor.", + "Toma seu bilhete azul e fora daqui!", + ], + 'PlayHardball': ["Então você quer jogar bola comigo?", + "Você não quer jogar bola comigo.", + "Chuta forte!", + "Passa, cara, passa!", + "Aí está o passe...", + "Você vai precisar de um refresco do goleiro.", + "Vou jogar você para fora do campo.", + "Depois que você se contundir, vai direto para casa.", + "São 45 minutos do segundo tempo!", + "Você não consegue jogar comigo!", + "Vou atingir você.", + "Vou dar um chute com efeito na bola!", + ], + 'PoundKey': ["É hora de retornar algumas ligações.", + "Gostaria de fazer uma ligação a cobrar.", + "Trrriiimmm - é para você!", + "Você quer brincar com o Jogo da Velha?", + "Tenho um método incrível para ganhar.", + "Está se sentindo nocauteado?", + "Vou dar um golpe neste número.", + "Deixe-me ligar para fazer uma surpresinha.", + "Vou ligar para você.", + "O.K. Toon, é o fim para você.", + ], + 'PowerTie': ["Eu ligo mais tarde, você parece enrolado na gravata.", + "Você está pronto para uma gravata?", + "Senhoras e senhores, esta é a gravata!", + "É melhor aprender a dar este nó.", + "Vou manter a sua língua dentro do nó!", + "É a gravata mais horrível que você já comprou!", + "Está sentindo o aperto?", + "Minha gravata é muito mais poderosa que a sua!", + "Eu tenho o poder do nó!", + "Pelos poderes do nó, vou engravatar você.", + ], + 'PowerTrip': ["Faça as malas, vamos fazer uma pequena viagem.", + "Você fez uma boa viagem?", + "Boa viagem, acho que nos veremos na próxima temporada.", + "Como foi a viagem?", + "Desculpe ter \"viajado\" dessa maneira!", + "Você parece viajandão.", + "Agora, você sabe quem é a autoridade!", + "Tenho muito mais autoridade do que você.", + "Quem manda agora?", + "Você não pode lutar contra o poder.", + "O poder corrompe, principalmente em minhas mãos!", + ], + 'Quake': ["Vamos balançar, agitar e rolar.", + "Tem muita vibração por aqui!", + "As suas canelas estão tremendo.", + "Aí vem ele, este é grande!", + "Este está fora da escala Richter.", + "Agora é que a terra vai tremer!", + "E aí, quem é que está agitando? Você!", + "Já esteve em um terremoto?", + "Agora, você está em território de tremores!", + ], + 'RazzleDazzle': ["Leia os meus lábios.", + "Que acha da minha dentadura?", + "Não acha que tenho charme?", + "Vou impressionar você.", + "Meu dentista faz um excelente trabalho.", + "Cegadores, não acha?", + "Não dá nem para acreditar que não é de verdade.", + "Chocante, né?", + "Vou dar um fim nisso.", + "Passo o fio dental após cada refeição.", + "Sorria!", + ], + 'RedTape': ["Isto deve acalmar o bicho.", + "Vou te amarrar por um tempo.", + "Você está acorrentado.", + "Veja se consegue cortar caminho por aqui.", + "O bicho vai pegar.", + "Tomara que você tenha claustrofobia.", + "Vou me certificar de que você não vai escapulir.", + "Vou ocupar você com alguma coisa.", + "Tente desatar o nó.", + "Espero que você concorde com os tópicos da reunião.", + ], +'ReOrg': ["Você não gostou da maneira como eu reorganizei as coisas!", + "Talvez um pouco de organização seja bom.", + "Você não é tão ruim assim, só precisa se organizar.", + "Você gosta do meu tino para organização?", + "Só pensei em dar um novo visual às coisas.", + "Você precisa se organizar!", + "Você parece um pouco desorganizado.", + "Espera um pouco enquanto eu reorganizo os seus pensamentos.", + "Só vou esperar até que você se organize um pouco mais.", + "Você se importa se eu só der uma reorganizadinha?", + ], + 'RestrainingOrder': ["Você precisa levar broncas de vez em quando.", + "Estou te jogando na cara uma ordem repressora!", + "Você não pode chegar nem um metro e meio perto de mim.", + "Talvez seja melhor você manter distância.", + "Entre na linha.", + Cogs + "! Reprimam este Toon!", + "Tente entrar na linha sozinho.", + "Espero que eu esteja sendo bem repressor com você.", + "Veja se você consegue acabar com essa repressão!", + "Estou ordenando que você se reprima!", + "Por que não começamos com uma repressão básica?" + ], + 'Rolodex': ["O seu cartão está aqui, em algum lugar.", + "Aqui está o número do dedetizador.", + "Quero dar o meu cartão a você.", + "Tenho o seu número bem aqui.", + "Tenho tudo aqui sobre você, de A a Z.", + "Você vai se virar com isso.", + "Dê um giro pelas páginas.", + "Cuidado com a papelada solta.", + "Vou apontar o dedo para a letra que desejo.", + "É assim que eu consigo entrar em contato com você?", + "Quero ter certeza de que manteremos o contato.", + ], + 'RubberStamp': ["Eu sempre causo uma boa impressão.", + "É importante aplicar uma pressão firme e bem distribuída.", + "Impressos perfeitos todas as vezes.", + "Quero carimbar você.", + "Você precisa ser DEVOLVIDO AO REMETENTE.", + "Você foi CANCELADO.", + "Você possui uma entrega de PRIORIDADE.", + "Vou me certificar de que a minha mensagem foi RECEBIDA.", + "Você não vai a lugar nenhum - você tem uma TARIFA POSTAL A PAGAR.", + "Preciso de uma resposta IMEDIATA.", + ], + 'RubOut': ["E agora, desapareceu!", + "Sinto que perdi você em algum lugar.", + "Decidi deixar você de fora.", + "Eu sempre apago todos os obstáculos.", + "Vou só apagar este erro.", + "Posso fazer qualquer perturbação desaparecer.", + "Gosto das coisas organizadas e limpas.", + "Tente manter a animação.", + "Estou vendo você... Agora, não vejo você.", + "Vai ficar meio esmaecido.", + "Vou eliminar o problema.", + "Deixe-me cuidar das suas áreas problemáticas.", + ], + 'Sacked':["Parece que você foi embrulhado.", + "Está no saco.", + "Você foi embolsado.", + "Papel ou plástico?", + "Meus inimigos serão ensacados!", + "Eu tenho o recorde de Toontown de sacos por jogo.", + "Você não é mais bem-vindo por aqui.", + "O seu tempo acabou aqui, você vai ser ensacado!", + "Deixe-me ensacar isto para você.", + "Nenhuma defesa se iguala ao meu ataque com sacos!", + ], + 'Schmooze':["Você nunca vai ver quando chega.", + "Vai ficar legal em você.", + "Você conseguiu.", + "Não quero despejar nada em você.", + "Como puxa-saco, eu vou longe.", + "Agora, eu vou florear bastante.", + "É hora de carregar nas tintas.", + "Vou ressaltar o seu lado bom.", + "Isso merece um bom tapinha nas costas.", + "Vou falar bem de você para todo mundo.", + "Detesto tirá-lo do seu pedestal, mas...", + ], + 'Shake': ["Você está bem no epicentro.", + "Você está em cima da falha.", + "Vai ser um sacolejo só.", + "Acho que isso é um desastre natural.", + "É um desastre de proporções sísmicas.", + "Este está fora da escala Richter.", + "É hora de entrar na toca.", + "Você parece perturbado.", + "Preparado para os solavancos?", + "Você vai sacolejar, e não centrifugar.", + "Isso vai agitar você.", + "Sugiro um bom plano de fuga.", + ], + 'Shred': ["Preciso me livrar de alguns fragmentos perigosos.", + "As porções produzidas estão aumentando de quantidade.", + "Acho que vou dispor de você agora mesmo.", + "Assim, a prova é eliminada.", + "Não há como provar isso agora.", + "Veja se você consegue juntar os pedaços novamente.", + "Assim, você vai cortar as sobras e ficar do tamanho certo.", + "Vou retalhar esta ideia todinha.", + "Não queremos que isto caia nas mãos erradas.", + "Fácil se tem, fácil se perde.", + "Não é o seu último fio de esperança?", + ], + 'Spin': ["O que me diz de sairmos para um giro?", + "Você usa a centrifugação?", + "Isto vai fazer a sua cabeça girar de verdade!", + "Este é o meu giro das coisas.", + "Vou levar você para uma volta.", + "Como é que você dá a \"volta\" no seu tempo?", + "Olha só: você não quer girar até ficar tonto?", + "Nossa, você está no meio de um furacão!", + "Meus ataques vão fazer sua cabeça rodar!", + ], + 'Synergy': ["Vou encaminhar ao comitê.", + "O seu projeto foi cancelado.", + "O seu centro de custos será cortado.", + "Estamos reestruturando o seu setor.", + "Colocamos em votação, e você perdeu.", + "Acabei de receber a aprovação final.", + "Uma boa equipe pode se livrar de qualquer problema.", + "Já dou um retorno a você sobre isso.", + "Vamos direto ao que interessa.", + "Vamos encarar isto como uma crise de sinergia.", + ], + 'Tabulate': ["Isto não soma em nada.", + "Pela minha conta, você perdeu.", + "Você está fazendo um bom cálculo.", + "Vou fazer o seu total em um minuto.", + "Está preparado para estes números?", + "Sua conta já está vencida e pode ser paga.", + "É hora de calcular.", + "Gosto de colocar as coisas em ordem.", + "E a contagem é...", + "Estes números devem ser muito poderosos.", + ], + 'TeeOff': ["Você não vai bem de condições.", + "Olha a frente!", + "Confio no meu taco.", + "Gandula, preciso do meu taco!", + "Tente evitar este risco.", + "Dê impulso!", + "É mesmo um furo dentro do outro.", + "Você está no meu campo.", + "Repara só a precisão.", + "Cuidado com o passarinho!", + "Fique de olho na bola!", + "Você se importa se eu continuar a jogar?", + ], + 'Tremor': ["Você sentiu?", + "Você não tem medo de um tremorzinho de nada, ou tem?", + "O tremor é apenas o começo.", + "Você parece tenso.", + "Vou agitar as coisas um pouco!", + "Tudo preparado para retumbar?", + "O que houve? Você parece balançado.", + "Tremedeira de medo!", + "Por que está tremendo de medo?", + ], + 'Watercooler': ["Certamente, isto vai refrescar você.", + "Não é refrescante?", + "Faço a entrega.", + "Direto da fonte - até a sua boca.", + "Qual é o problema, é só uma água de nascente.", + "Não se preocupe, é pura.", + "Ah, outro cliente satisfeito.", + "É hora da entrega diária.", + "Espero que as suas cores não desbotem.", + "Quer beber?", + "Sai tudo na lavagem.", + "A bebida é com você.", + ], + 'Withdrawal': ["Acho que você está no vermelho.", + "Espero que o seu saldo seja o suficiente para cobrir isto.", + "Olha que vou cobrar juros.", + "O seu saldo está diminuindo.", + "Em breve, você vai precisar fazer um depósito.", + "Você sofreu um colapso financeiro.", + "Acho que você está em baixa.", + "Suas finanças decaíram.", + "Prevejo um período de vacas magras.", + "É uma inversão de valores.", + ], + 'WriteOff': ["Deixe-me aumentar as suas perdas.", + "Vamos tirar o melhor proveito possível de um mau negócio.", + "É hora de fazer o balanço dos caixas.", + "Isso não vai ficar bom nos livros-caixa.", + "Procuro alguns dividendos.", + "Você deve se responsabilizar por suas perdas.", + "Pode esquecer o bônus.", + "Vou bagunçar todas as suas contas.", + "Você está prestes a sofrer algumas perdas.", + "Isto vai afetar os seus resultados finais.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Aguardando outros jogadores...", + +# Elevator.py +ElevatorHopOff = "Descer" +ElevatorStayOff = "Se descer, terá de esperar\naté que o elevador parta ou fique vazio" +ElevatorLeaderOff = "Somente seu líder pode decidir quando deve descer." +ElevatorHoppedOff = "Você precisa esperar o próximo elevador" +ElevatorMinLaff = "Você precisa de %s pontos de risada para poder subir neste elevador" +ElevatorHopOK = "OK" +ElevatorGroupMember = "Somente o líder deste grupo pode\ndecidir quando deve entrar" + +# DistributedCogKart.py +KartMinLaff = "Você precisa de %s pontos de risada para poder andar neste carte." + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Ltda." +CogsIncModifier = "%s" + CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "Quem é?" +DoorWhoAppendix = " Quem?" +DoorNametag = "Porta" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Você precisa de piadas! Vá falar com o Tutorial Tom!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Volte aqui quando tiver derrotado o Puxa-saco!" +FADoorCodes_TALK_TO_HQ = "Vá pegar a sua recompensa com o Haroldo do Quartel!" +FADoorCodes_WRONG_DOOR_HQ = "Porta errada! Vá pela outra porta para o pátio!" +FADoorCodes_GO_TO_PLAYGROUND = "Direção errada! Você precisa ir para o pátio!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Ande até o Puxa-saco para lutar com ele!" +FADoorCodes_TALK_TO_HQ_TOM = "Vá pegar a sua recompensa no Quartel dos Toons!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Cuidado! Tem um COG lá dentro!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Cog primeiro!\n\nMonte o seu Disfarce de Cog com pedaços da Fábrica." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Robô Mercenário primeiro!\n\nMonte o seu Disfarce de Robô Mercenário executando Tarefas Toon na Sonholândia." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Cog primeiro!\n\nMonte o seu Disfarce de Cog com pedaços da Fábrica." +FADoorCodes_BB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como Toon! Primeiramente, você precisa concluir seu Disfarce de Robô Chefe!\n\nConstrua seu Disfarce de Robô Chefe cumprindo as TarefasToon depois da Sonholândia do Donald." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Jaque", + "Jaque não está olhando, joga uma torta nele!"], + + # 2009 April fools contest Jokes. First few doors of Loopy lane + 2200 : {28:["Biscuit (Biscoito)", + "Biscuitos (Biscoitos) me mordam, os Cogs vêm aí!"], + 41:["Dewey", + "Dewemos ir detonar mais alguns Cogs?"], + 40:["Minnie", + "Minnie-pessoas falaram comigo, e isso está me enlouquecendo!"], +## 25:["Biscuit25 (Biscoito25)", +## "Biscuitos (Biscoitos) me mordam, os Cogs vêm aí!"], + 27:["Disguise", + "A Disguisetante perseguição aos Cogs!"]}, + + 2300: ["Justa", + "Justa gora peguei uns dois pedaços de Cogs, pronto!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdinheiro no chão."], + 6 : ["Adon", + "Adondé que esses Cogs tão saindo?"], + 30 : ["Bacon", + "Bacon uma torta ia bem."], + 28: ["Isaías", + "Isaías mas voltou-se."], + 12: ["Julieta", + "Julieta me chamando praquele prédio Cog com você pra eu te Toonar."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Quem", + "Aqui tem eco, não?"], + + ["Kika", + "Kikalor!"], + + ["Joe", + "Você é Joetromundo?"], + + ["Eudin", + "Eudinovo por aqui!"], + + ["Silêncio", + "Pssss!"], + + ["Simbó", + "Simbora pra praia."], + + ["Takent", + "Takent ou frio?"], + + ["Noá", + "Noá de quê."], + + ["Não sei", + "Nem eu, já te falei."], + + ["Otudo", + "Otudo ou nada?"], + + ["Totan", + "Totan feliz que você está aqui!"], + + ["Osmar", + "Osmartodontes não existem mais!"], + + ["Silem", + "Silembra de mim?"], + + ["Ostra", + "Ostra vez?"], + + ["Aimée", + "Aimée Tida?"], + + ["Zoom", + "Zooma imediatamente daqui!"], + + ["Aiki", + "Aiki medo!"], + + ["Quiba", + "Quibagunça é essa"], + + ["Tasó", + "Não, tacompanhado."], + + ["Iago", + "Iagora, José?"], + + ["'Tácom'", + "'Tácom' tudo, não é?"], + + ["Tádi", + "Tádi graça, é? Meu nome é "+Flippy+"."], + + ["Opato", + "Opato "+Donald+" Deduct."], + + ["Masqui", + "Masqui coisa, abre a porta logo."], + + ["Nénim.", + "Nénim guém que te interesse, deixa eu entrar."], + + ["Omos", + "Omosquito que te picou."], + + ["Colés", + "Colesterol faz mal, sai fora."], + + ["Breno", + "Breno que eu te falei que esse cara vinha."], + + ["Kiko", + "Kiko-losso!"], + + ["Vaivê", + "Vaivê que eu tô atrasado."], + + ["Quente", + "Quente viu e quem te vê!"], + + ["Vopri", + "Vopri-meiro, tô com medo."], + + ["Eunum", + "Eunum sei. Desculpe!"], + + ["Ubaldo", + "Ubaldo é o marido da balda?"], + + ["Alfa", + "Alface ou tomate?"], + + ["Ka", + "Ka, L, M, N, O, P."], + + ["Justa", + "Justagora que eu ia jantar."], + + ["Maki", + "Makiagem é coisa para adultos."], + + ["Loga", + "Logagora que eu entrei no banho."], + + ["Quessa", + "Quessa B? Vou me mandar."], + + ["Masqui", + "Masqui droga - abre a porta e pronto!"], + + ["'Jaques'", + "'Jaquesou' importante, você deveria falar comigo primeiro."], + + ["Midê", + "Midêxa em paz!"], + + ["Undi", + "Undia é da caça outro é do caçador."], + + ["Tudor", + "Tudor que sobe, desce."], + + ["Acara", + "Acara-puça serviu, hein?"], + + ["Aispe", + "Aispe-rança é a última que morre."], + + ["Kênia", + "Kênia sabe?"], + + ["Bemki", + "Bemki te vi lá fora."], + + ["Jaca", + "Jacaré no seco anda?"], + + ["Quenco", + "Quenco-chicha o rabo espicha."], + + ["Tádi", + "Tádi brincadeira, né? Deixa eu rir, então."], + + ["Ocessá", + "Ocessá-be um monte de coisa, né?"], + + ["Temki", + "Temki ter uma piada melhor que essa."], + + ["Cetá", + "Cetá pensando que eu sou besta?"], + + ["Vôti", + "Vôti botar pra correr."], + + ["Donalda", + "Donalda não... São cinquenta centavos."], + + ["Alface", + "Alface a face é mais emocionante."], + + ["Ivo", + "Ivo cê não sabia que não tem ninguém em casa?"], + + ["Quessa", + "Quessa Bê? Essa brincadeira está um saco."], + + ["Quenfo", + "Quenfo à roça perdeu a carroça."], + + ["Justa", + "Justagora que eu ia embora."], + + ["Taca", + "Taca mãe primeiro!"], + + ["Tanaka", + "'Tanaka-ra' que você não vai se dar bem!"], + + ["Quenfo", + "Quenfo ao ar perdeu o lugar!"], + + ["Soé", + "Soé jeito de falar com um amigo?"], + + ["Daum", + "'Daum' tempo, pode ser?"], + + ["Isadora", + "Isadora, o que é que eu faço?"], + + ["Vêssi", + "'Vêssi' da próxima vez toma mais cuidado!"], + + ["Teá", + "Teá-doro, mas isso também é demais."], + + ["Carlota", + "A Carlota está presa na roda?"], + + ["Bu", + "Eu nem me assustei."], + + ["Tu", + "Tu, cara de tatu!"], + + ["Pó", + "Pó-su entrar?"], + + ["Sará", + "Sará que que tem outro jeito de entrar neste prédio?"], + + ["Mico", + "Miconta que novidade é essa!"], + + ["Numi", + "Numi amola e me deixa entrar."], + + ["Miá", + "Miá-juda, a porta emperrou."], + + ["Nuncre", + "Nuncre dita em mim?"], + + ["Dianta", + "não Dianta falar, você não vai abrir a porta..."], + + ["Dorré", + "Mi, Fá, Sol, Lá, Si!"], + + ["Dexeu", + "Dexeu ver quem taí."], + + ["Tássia", + "Tássia-chando, hem? Abre logo."], + + ["Omeu", + "Omeu Deus do céu!"], + + ["Dizaí", + "Dizaí o quê?"], + + ["Inter", + "Interessante esta brincadeira."], + + ["Grato", + "Não há de quê."], + + ["Quicão", + "Quicão fusão é essa?"], + + ["Mamão", + "Mamão mandou bater nesta daqui!"], + + ["Nunci", + "Nunci deve comer de boca cheia."], + + ["Kiko", + "E o Kiko eu tenho que saber sobre isso?"], + + ["Silêncio", + "Pssss!"], + + ["Vossocá", + "Eu não, por favor."], + + ["Pu", + "Pu xavida, você me enganou."], + + ["Miá", + "Miá corda, não me deixa perder o bondinho."], + + ["Nívea", + "Feliz Niveassário!"], + + ["Sino", + "O sino faz \"blém\" não \"quem\"."], + + ["Diki", + "Diki lado você está?"], + + ["Querê", + "Querê não é podê."], + + ["Frankstein", + "Frankstein, mas você não tem."], + + ["Pra Z", + "Pra Z em conhecê-lo."], + + ["Ex-conde", + "Ex-conde-conde é legal."], + + ["Apri", + "Apri-ncesa despertou com o beijo do príncipe."], + + ["Quacker", + "Quacker uma, menos esta!"], + + ["Qualquerco", + "Qualquerco-isa parecida com isto já vai me ajudar."], + + ["Quiqui", + "'Quiqui' é isso?"], + + ["Abá", + "Abá-xaqui, a chave caiu!"], + + ["Póba", + "Póba-tê, esqueci a piada!"], + + ["Urralo", + "Urralo-ween já passou!"], + + ["Sará", + "Sará que tem um médico em casa?"], + + ["Aline", + "Aline é reta ou curva?"], + + ["Dôdis", + "Dôdis tômago."], + + ["Dôdi", + "Dôdi dente."], + + ["Toco", + "Toco dor de cabeça."], + + ["Atch", + "Saúde."], + + ["Aumen", + "Aumen-te o volume, por favor."], + + ["Zupra", + "Zupra-sumo."], + + ["Tupó", + "Tupó descer ali comigo?"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Oi, %!", + "Iuhuuu %, legal ver você.", + "Estou feliz que você esteja aqui hoje!", + "Bom, oi pessoal, %.", + ] + +SharedChatterComments = [ + "Que nome legal, %.", + "Gosto do seu nome.", + "Cuidado com os " + Cogs + "." + "Parece que o bondinho está chegando!", + "Preciso jogar um jogo no bondinho para ganhar algumas tortas!", + "Às vezes, eu me divirto com os jogos no bondinho só para comer a torta de frutas!", + "Puxa, acabei de deter um bando de " + Cogs + ". Preciso de descanso!", + "Puxa vida, alguns desses " + Cogs + " são grandalhões!", + "Você parece estar se divertindo.", + "Nossa, que dia legal!", + "Gostei da sua roupa.", + "Acho que vou pescar esta tarde.", + "Divirta-se no meu bairro.", + "Espero que você esteja aproveitando sua estada em Toontown!", + "Ouvi falar que está nevando no Brrrgh.", + "Você pegou o bondinho hoje?", + "Gosto de conhecer pessoas novas.", + "Uau, há vários "+ Cogs +" no Brrrgh." + "Eu adoro brincar de pique. E você?", + "Os jogos no bondinho são divertidos.", + "Adoro fazer as pessoas rirem.", + "É divertido ajudar meus amigos.", + "Hum-hum, você está perdido? Não se esqueça de que você tem um mapa no Álbum Toon.", + "Procure não ficar atolado na Burocracia dos " + Cogs + "'.", + "Ouvi falar que a " + Daisy + " plantou novas flores no jardim.", + "Se você pressionar a tecla Page Up, poderá ver acima!", + "Se você ajudar a tomar os edifícios dos Cogs, poderá ganhar uma estrela de bronze!", + "Se você pressionar a tecla Tab, poderá ver os arredores sob diversos ângulos!", + "Se você pressionar a tecla Ctrl, poderá descer!", + ] + +SharedChatterGoodbyes = [ + "Tenho que ir agora, tchau!", + "Acho que vou jogar no bondinho.", + "Bom, até mais. Vejo você por aí, %!", + "Melhor eu me apressar e voltar ao trabalho para deter esses "+ Cogs +".", + "Preciso ir andando.", + "Desculpe, mas tenho que ir.", + "Tchau.", + "Vejo você mais tarde, %!", + "Acho que vou praticar lançamento de bolinhos.", + "\Vou me juntar a um grupo para deter alguns "+Cogs+".", + "Foi legal ver você hoje, %.", + "Tenho muito a fazer hoje. É melhor começar logo.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bem-vindo ao "+lToontownCentral+".", + "Oi, meu nome é " + Mickey + ". Qual é o seu?", + ], + [ # Comments + "Ei, você viu o "+ Donald +"?", + "Vou ver o nevoeiro passar no "+lDonaldsDock+".", + "Se você vir o meu camarada "+Goofy+", dê um oi para ele por mim.", + "Ouvi falar que a "+Daisy+" plantou novas flores no jardim." + ], + [ # Goodbyes + "\Vou para a Melodilândia ver a "+Minnie+"!", + "Caramba, estou atrasado para meu encontro com a "+ Minnie +"!", + "Parece que é hora de "+ Pluto +" jantar.", + "Acho que vou nadar no "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +VampireMickeyChatter = ( + [ # Greetings specific to Vampire Mickey + "Bem-vindo ao "+lToontownCentral+".", + "Oi, meu nome é "+Mickey+". Qual é o seu?", + "Feliz Halloween!", + "Feliz Halloween, %!", + "Bem-vindo ao Centro da Cidade Assombrada... quero dizer ao "+lToontownCentral+"!", + ], + [ # Comments + "É divertido se vestir para o Halloween!", + "Gostou da minha fantasia?", + "%, cuidado com os Cogs Sanguessugas!", + "As decorações de Halloween não são fantásticas?", + "Cuidado com os gatos pretos", + "Você viu o Toon com a cabeça de abóbora?", + "Buu! Assustei você?", + "Não se esqueça de escovar suas presas", + "Não tenha medo, sou um vampiro amigável", + "Gostou da minha capa?", + "Assustei você? Foi a melhor brincadeira da minha vida!", + "Espero que esteja curtindo nossa festa de Halloween!", + "Assombroso, está escuro como a noite!", + ], + [ # Goodbyes + "Vou olhar as decorações curiosas de Halloween.", + "Vou a Melodilândia fazer uma surpresa à "+Minnie+"!", + "Vou assustar outro Toon! Shhh!", + "Vou brincar de doces ou travessuras!", + "Shhh, vem comigo.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bem-vindo à Melodilândia.", + "Oi, meu nome é "+ Minnie +". Qual é o seu?" + ], + [ # Comments + "As colinas ganham vida com o som da música!", + # the merry no longer goes round + #"Não deixe de tentar andar no carrossel gigante!", + "Sua roupa é legal, %.", + "Ei, você viu o "+ Mickey +"?", + "Se você vir meu amigo "+ Goofy +", dê um oi para ele por mim.", + "Uau, há milhares de "+ Cogs +" perto da "+lDonaldsDreamland+".", + "Ouvi falar que tem neblina no "+lDonaldsDock+".", + "Não deixe de experimentar o labirinto dos "+lDaisyGardens+".", + "Acho que vou catar algumas canções.", + "Ei, %, olha aquilo lá.", + "Adoro o som da música.", + "Aposto que você não sabia que a Melodilândia também é chamada de ToadaTown! Ah, ah, ah!", + "Adoro jogo da memória. E você?", + "Gosto de fazer as pessoas rirem.", + "Cara, andar sobre rodas o dia todo não é moleza para os pés!", + "Bonita camisa, %.", + "Aquilo no chão é uma balinha?", + ], + [ # Goodbyes + "Caramba, estou atrasada para o meu encontro com o "+ Mickey +"!", + "Parece que é hora de "+ Pluto +" jantar.", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +DaisyChatter = ( + [ # Greetings + "Bem-vindo(a) ao meu Jardim!", + "Olá, meu nome é "+Daisy+". Qual o seu nome?", + "É muito bom ver você, %!", + ], + [ # Comments + "Minha flor premiada está no centro do labirinto do jardim.", + "Eu adoro andar pelo labirinto.", + "Eu não ví o "+Goofy+" hoje.", + "Eu gostaria de saber onde o "+Goofy+" está.", + "Você viu o "+Donald+"? Eu não consigo encontrá-lo em lugar algum.", + "Se você vir minha amiga "+Minnie+", por favor diga \"Oi\" por mim.", + "Quanto melhor as ferramentas de jardinagem que você tem, melhor será seu jardim.", + "Existem muitos "+Cogs+" perto do "+lDonaldsDock+".", + "Regando seu jardim diariamente você deixa suas plantas felizes.", + "Para cultivar uma Margarida Rosa, plante uma balinha amarela e uma vermelha juntas.", + "É facil cultivar uma Margarida Amarela. Basta plantar uma balinha amarela.", + "Se você vir areia embaixo de uma planta, está na hora de regar ou ela morrerá.", + ], + [ # Goodbyes + "Estou indo para Melodilândia para ver %s!" % Minnie, + "Preciso correr para o meu picnic com %s!" % Donald, + "Acho que vou nadar no "+lDonaldsDock+".", + "Oh, estou com sono. Acho que vou para a Sonholândia.", + ] + ) + +ChipChatter = ( + [ # Greetings + "Boas-vindas a %s!" % lOutdoorZone, + "Olá, sou " + Chip + ". Qual é o seu nome?", + "Não, eu sou " + Chip + ".", + "É tão bom ver você, %!", + "Somos Tico e Teco!", + ], + [ # Comments + "Gosto de golfe.", + "Temos as melhores bolotas de Toontown.", + "Os buracos de golfe com vulcões são os mais desafiadores para mim.", + ], + [ # Goodbyes + "Vamos até " + lTheBrrrgh +" brincar com %s." % Pluto, + "Vamos visitar %s e dar um jeito nele." % Donald, + "Acho que vou nadar no " + lDonaldsDock + ".", + "Oh, estou com sono. Acho que vou até a Sonholândia.", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "É tão bom ver você, %!", + "Olá, sou " + Dale + ". Qual é o seu nome?", + "Olá, sou " + Chip + ".", + "Boas-vindas a %s!" % lOutdoorZone, + "Somos Tico e Teco!", + ], + [ # Comments + "Gosto de piqueniques.", + "As bolotas são gostosas, experimente.", + "Aqueles moinhos também são difíceis.", + ], + [ # Goodbyes + "Hihihi, é divertido brincar com " + Pluto + ".", + "Sim, vamos dar um jeito em %s." % Donald, + "Ah, seria refrescante dar uma nadada.", + "Estou ficando cansado, uma boa soneca cairia bem.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bem-vindo aos "+lDaisyGardens+".", + "Oi, meu nome é "+ Goofy +". Qual é o seu?", + "Puxa, muito legal ver você %!", + ], + [ # Comments + "Cara, com certeza é fácil se perder no labirinto do jardim!", + "Não deixe de tentar entrar no labirinto.", + "Não vi a "+ Daisy +" o dia todo.", + "Onde será que a "+ Daisy +" está?", + "Ei, você viu o "+ Donald +"?", + "Se você vir o meu amigo "+ Mickey +", dê um oi para ele por mim.", + "Ah, não! Esqueci de fazer o café da manhã do "+ Mickey +"!", + "Puxa, com certeza há muitos "+ Cogs +" perto do "+lDonaldsDock+".", + "Parece que a "+ Daisy +" plantou novas flores no jardim.", + "Na filial da minha Loja de Piadas no Brrrgh, há Óculos hipnóticos em promoção por apenas uma balinha!", + "As Lojas de piadas do Pateta oferecem as melhores gozações, truques e comédias de toda Toontown!", + "Nas Lojas de piadas do Pateta, todas as tortas na cara têm garantia de fazer rir, ou você tem as suas balinhas de volta!", + ], + [ # Goodbyes + "\Vou para Melodilândia para ver a "+ Minnie +"!", + "Caramba, estou atrasado para o meu jogo com o "+ Donald + "!", + "Acho que vou nadar no Porto do "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +GoofySpeedwayChatter = ( + [ # Greetings + "Bem-vindo a "+lGoofySpeedway+".", + "Oi, meu nome é "+Goofy+". Qual é o seu?", + "Puxa, muito legal ver você %!", + ], + [ # Comments + "Cara, assisti a uma corrida maneira hoje.", + "Cuidado com as cascas de banana na pista!", + "Você deu uma incrementada no seu kart?", + "A gente acabou de pegar uns aros novos na loja do kart.", + "Oi, você viu "+Donald+"?", + "Se você vir meu amigo "+Mickey+", diz que eu mandei um alô.", + "Ah, não! Esqueci de preparar para "+Mickey+" o café da manhã!", + "Puxa, com certeza há muitos "+Cogs+" perto de "+lDonaldsDock+".", + "Na filial da minha Loja de Piadas no Brrrgh, há Óculos hipnóticos em promoção por apenas uma balinha!", + "As Lojas de piadas do Pateta oferecem as melhores gozações, truques e comédias de toda Toontown!", + "Nas Lojas de piadas do Pateta, todas as tortas na cara têm garantia de fazer rir, ou você tem as suas balinhas de volta!" + ], + [ # Goodbyes + "Vou para Melodilândia para ver %s!" % Mickey, + "Caramba, estou atrasado para o meu jogo com %s!" % Donald, + "Acho que vou nadar no "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bem-vindo à Sonholândia.", + "Oi, meu nome é "+ Donald +". Qual é o seu?", + ], + [ # Comments + "Às vezes este lugar me dá arrepios.", + "Não deixe de experimentar o labirinto dos "+lDaisyGardens+".", + "Nossa, que dia legal!", + "Ei, você viu o "+ Mickey +"?", + "Se você vir meu parceiro "+ Goofy +", dê um oi para ele por mim.", + "Acho que vou pescar esta tarde.", + "Uau, há um monte de "+ Cogs +" no "+lDonaldsDock+".", + "Escuta, eu não levei você ainda para um passeio no "+lDonaldsDock+"?", + "Não vi a "+ Daisy +" o dia todo.", + "Ouvi falar que a "+ Daisy +" plantou novas flores no jardim.", + "Quack.", + ], + [ # Goodbyes + "Vou a Melodilândia para ver a "+ Minnie +"!", + "Ah não, estou atrasado para o meu encontro com a "+ Daisy +"!", + "Acho que vou nadar no meu cais.", + "Acho que vou levar meu barco para um giro no meu cais.", + ] + ) + +# April Fools Chatter's +AFMickeyChatter = ( + [ # Greetings specific to Mickey + "Feliz Semana dos April Toons (Toons de Abril)!", + "Feliz Semana dos April Toons (Toons de Abril), %!", + "Oi, meu nome é "+Mickey+". Qual é o seu?", + ], + [ # Comments + "Você viu a Margarida por aí?", + "Queria desejar uma feliz Semana dos April Toons (Toons de Abril) para a Margarida!", + "Você ouviu um Rabisco falar?", + "Oh, essas flores são lindas!", + "Aposto que a Margarida tem ótimas dicas de Jardinagem!", + ], + [ # Goodbyes + "Oi, estou procurado a Margarida. Você a viu?", + "É hora de dar uma cochilada. Vou para a Sonholândia.", + ] + ) + +AFMinnieChatter = ( + [ # Greetings + "Oi, meu nome é "+Minnie+". Qual é o seu?", + "Feliz Semana dos April Toons (Toons de Abril)!", + "Feliz Semana dos April Toons (Toons de Abril), %!", + ], + [ # Comments + "Oi, preciso dar de comer ao Pluto. Você o viu?", + "Queria desejar uma feliz Semana dos April Toons (Toons de Abril) para o Pluto com um biscoito canino!", + "Você ouviu um Rabisco falar?", + ], + [ # Goodbyes + "Oi, preciso dar de comer ao Pluto. Você o viu?", + "Nossa, estou atrasada para meu encontro com o %s!" % Mickey, + ] + ) + +AFDaisyChatter = ( + [ # Greetings + "Oi, sou a "+Daisy+". Qual é o seu nome?", + "Feliz Semana dos April Toons (Toons de Abril)!", + "Feliz Semana dos April Toons (Toons de Abril), %!", + ], + [ # Comments + "Queria saber se o Mickey foi combater alguns Cogs?", + "Você viu o Mickey por aí?", + "Queria desejar uma feliz Semana dos April Toons (Toons de Abril) para o Mickey!", + "Você ouviu um Rabisco falar ou estou ouvindo coisas?", + ], + [ # Goodbyes + "Oi, preciso falar com o Micky (Mickey). Você o viu?", + "Acho que vou nadar no "+lDonaldsDock+".", + "Oh, estou com soninho. Acho que vou para a Sonholândia", + ] + ) + +AFGoofySpeedwayChatter = ( + [ # Greetings + "Feliz Semana da Preguiça, hã, dos April Toons (Toons de Abril)!", + "Feliz Semana dos April Toons (Toons de Abril), %!", + "Oi, meu nome é "+Goofy+". Qual é o seu?", + ], + [ # Comments + "Ohoh, você viu o Donald? Acho que ele está sonâmbulo novamente", + "Queria desejar uma feliz Semana dos April Toons (Toons de Abril) para o Donald!", + "Você ouviu um Rabisco falar ou estou vendo coisas?", + "Espero que tudo esteja bem no Autódromo.", + ], + [ # Goodbyes + "Ohoh, estou atrasado para meu jogo com o %s!" % Donald, + ] + ) + +AFDonaldChatter = ( + [ # Greetings + "Feliz Semana da Preguiça, hã, dos April Toons (Toons de Abril)!", + "Feliz Semana dos April Toons (Toons de Abril), %!", + "Oi, meu nome é%s. Qual é o seu?" % Donald, + ], + [ # Comments + "Você viu o Pateta por aí?", + "Queria desejar uma feliz Semana dos April Toons (Toons de Abril) para o Pateta!", + "Você ouviu um Rabisco falar ou estou sonhando?", + "De onde surgiu esse kart?", + ], + [ # Goodbyes + "De onde surgiram repentinamente todos esses carros barulhentos?", + "Vou para Melodilândia ver a %s!" % Minnie, + ] + ) + +CLGoofySpeedwayChatter = ( + [ # Greetings + "Bem-vindo ao "+lGoofySpeedway+".", + "Oi, meu nome é "+Goofy+". Qual é o seu?", + "Ohoh, que bom ver você %!", + "Olá! Perdoe minhas roupas sujas, estava consertando aquele Quadro de Pontuação quebrado.", + ], + [ # Comments + "É bom que o Quadro de Pontuação esteja funcionando logo, pois o Fim de Semana do Grande Prêmio está chegando!", + "Alguém quer comprar um kart meio usado? Ele já apareceu no Quadro de Pontuação!", + "O Fim de Semana do Grande do Prêmio está chegando, é melhor começar a treinar.", + "O Fim de Semana do Grande Prêmio será de sexta-feira, 22, a segunda-feira, 25 de maio!", + "Preciso de uma escada para descer aquele kart.", + "Aquele Toon realmente queria aparecer no Quadro de Pontuação!", + "Cara, acabei de ver uma corrida terrível.", + "Cuidado com as cascas de banana na pista!", + "Você fez algumas melhorias em seu kart ultimamente?", + "Precisamos adquirir alguns aros novos na loja de kart.", + "Ei, você viu o "+Donald+"?", + "Se vir o meu amigo "+Mickey+", mande lembranças a ele por mim.", + "Puxa! Esqueci de preparar o café da manhã do "+Mickey+"!", + "Ohoh, tem um monte de "+Cogs+" perto do "+lDonaldsDock+".", + "Nos galhos do Brrrgh da minha Loja de Brincadeiras, os Óculos Hipnotizantes estão à venda por apenas 1 balinha!", + "A Loja de Piadas do Pateta tem as melhores piadas, truques e brincadeiras divertidas de toda Toontown!", + "Na Loja de Piadas do Pateta, até uma torta na cara é garantia de boas risadas ou você recebe suas balinhas de volta!" + ], + [ # Goodbyes + "É bom eu fazer uma nova pintura no meu kart antes do Fim de Semana do Grande Prêmio.", + "Caramba, é melhor eu dar um jeito nesse Quadro de Pontuação quebrado!", + "Espero ver todos vocês no Fim de Semana do Grande Prêmio! Adeus!", + "É hora de dar uma cochilada. Vou para a Sonholândia sonhar com a vitória no Grande Prêmio.", + ] + ) + + +GPGoofySpeedwayChatter = ( + [ # Greetings + "Bem-vindo ao "+lGoofySpeedway+".", + "Bem-vindo ao Fim de Semana do Grande Prêmio!", + "Oi, meu nome é "+Goofy+". Qual é o seu?", + "Ohoh, que bom ver você %!", + ], + [ # Comments + "Você está na expectativa do Fim de Semana do Grande Prêmio?", + "A boa notícia é que o Quadro de Pontuação está pronto.", + "Conseguimos consertar o Quadro de Pontuação bem na hora do Fim de Semana do Grande Prêmio!", + "Nunca encontramos aquele Toon!", + "Cara, acabei de ver uma corrida terrível.", + "Cuidado com as cascas de banana na pista!", + "Você fez algumas melhorias em seu kart ultimamente?", + "Precisamos comprar alguns aros novos na loja de kart.", + "Ohoh, você viu o "+Donald+"? Ele disse que estava vindo para o Grande Prêmio!", + "Se vir o meu amigo "+Mickey+", diga a ele que está perdendo uma corrida incrível!", + "Puxa! Esqueci de preparar o café da manhã do "+Mickey+"!", + "Ohoh, tem um monte de "+Cogs+" perto do "+lDonaldsDock+".", + "Nos galhos do Brrrgh da minha Loja de Brincadeiras, os Óculos Hipnotizantes estão à venda por apenas 1 balinha!", + "A Loja de Piadas do Pateta tem as melhores piadas, truques e brincadeiras divertidas de toda Toontown!", + "Na Loja de Piadas do Pateta, até uma torta na cara é garantia de boas risadas ou você recebe suas balinhas de volta!" + ], + [ # Goodbyes + "Boa sorte no Grande Prêmio!", + "Vou participar da próxima corrida do Grande Prêmio!", + "Ohoh, acho que a próxima corrida já vai começar!", + "Puxa, é melhor verificar o novo Quadro de Pontuação e garantir que esteja funcionando bem!", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Novo amigo" +FriendsListPanelSecrets = "Segredos" +FriendsListPanelOnlineFriends = "AMIGOS\nON-LINE" +FriendsListPanelAllFriends = "TODOS\nOS AMIGOS" +FriendsListPanelIgnoredFriends = "TOONS\nIGNORADOS" +FriendsListPanelPets = "BICHINHOS\nPRÓXIMOS" +FriendsListPanelPlayers = "TODOS OS AMIGOS\nDO JOGADOR" +FriendsListPanelOnlinePlayers = "AMIGOS ONLINE\nDO JOGADOR" + +FriendInviterClickToon = "Clique no Toon com o qual você quer fazer amizade.\n\n(Você tem %s amigos)" + +# Support DISL account friends +FriendInviterToon = "Toon" +FriendInviterThatToon = "Aquele Toon" +FriendInviterPlayer = "Jogador" +FriendInviterThatPlayer = "Aquele jogador" +FriendInviterBegin = "Que tipo de amigo você quer ter?" +FriendInviterToonFriendInfo = "Um amigo somente em Toontown" +FriendInviterPlayerFriendInfo = "Um amigo em toda a rede Disney.com" +FriendInviterToonTooMany = "Você tem amigos Toons demais para poder acrescentar um agora. Você terá de remover alguns amigos Toons se quiser fazer amizade com %s. Você também pode tentar fazer amizade com seu jogador." +FriendInviterPlayerTooMany = "Você tem amigos jogadores demais para poder acrescentar um agora. Você terá de remover alguns amigos jogadores se quiser fazer amizade com %s. Você também pode tentar fazer amizade com seu Toon." +FriendInviterToonAlready = "%s já é seu amigo Toon." +FriendInviterPlayerAlready = "%s já é seu amigo jogador." +FriendInviterStopBeingToonFriends = "Romper amizade Toon" +FriendInviterStopBeingPlayerFriends = "Romper amizade de jogador" +FriendInviterEndFriendshipToon = "Tem certeza de que quer romper a amizade de Toon com %s?" +FriendInviterEndFriendshipPlayer = "Tem certeza de que quer romper a amizade de jogador com %s?" +FriendInviterRemainToon = "\n(Você vai continuar sendo amigo Toon de %s)" +FriendInviterRemainPlayer = "\n(Você vai continuar sendo amigo jogador de %s)" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Sinto muito, você não pode avançar porque o download de %(phase)s está apenas %(percent)s%% concluído.\n\nTente novamente mais tarde." + +# TeaserPanel.py +TeaserTop = "" +TeaserBottom = "" +TeaserDefault = ",\nVocê precisa ser um associado.\nUna-se!" +TeaserOtherHoods = "Visite os 6 bairros exclusivos!" +TeaserTypeAName = "Digite o seu nome favorito para o seu Toon!" +TeaserSixToons = "Crie até 6 Toons em uma só conta!" +TeaserClothing = "Compre roupas exclusivas para personalizar o seu Toon!" +TeaserCogHQ = "Infiltre-se nas\nperigosas áreas avançadas dos Cogs!" +TeaserSecretChat = "Troque segredos\ncom seus amigos conversando on-line com eles!" +TeaserSpecies = "Crie e jogue com Toons Macacos, Cavalos e Ursos!" +TeaserFishing = "Colecione todas as espécies de peixes!" +TeaserGolf = "Jogue em campos de golfe malucos!" +TeaserParties = " Para planear Partes" +TeaserSubscribe = "Assinar" +TeaserContinue = "Continuar na versão gratuita" +TeaserEmotions = "Para fazer seu Toon mais expressivo" +TeaserKarting = "Aposte corridas com outros Toons em karts maneiros!" +TeaserKartingAccessories = "Personalize seu kart com acessórios incríveis." +TeaserGardening = "Plante flores, construa estátuas e cultive árvores em seu terreno." +TeaserHaveFun = "Encontre mais diversão!" +TeaserJoinUs = "Una-se!" + +#TeaserCardsAndPosters = "" +#TeaserFurniture = "" +TeaserMinigames = TeaserOtherHoods +#TeaserHolidays = "" +TeaserQuests = TeaserOtherHoods +TeaserOtherGags = TeaserOtherHoods +#TeaserRental = "" +#TeaserBigger = "" +TeaserTricks = TeaserOtherHoods + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Fazendo download %s" +DownloadWatcherInitializing = "Iniciando Download..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Inicialização", + 1 : "Panda", + 2 : "Motor", + 3 : "Fazer um Toon", + 3.5 : "Toontorial", + 4 : "Parque", + 5 : "Ruas", + 5.5 : "Estados", + 6 : "Bairros I", + 7 : Cog + " Edifícios dos", + 8 : "Bairros II", + 9 : Sellbot + " Quartel dos", + 10 : Cashbot + " Quartel dos", + 11 : Lawbot + " Quartel dos", + 12 : Bossbot + " HQ", + 13 : "Festas", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s de %(total)s)" +LauncherStartingMessage = "Iniciando Toontown On-line da Disney..." +LauncherDownloadFile = "Fazendo download da atualização de "+ LauncherProgress +"..." +LauncherDownloadFileBytes = "Fazendo download da atualização de "+ LauncherProgress +": %(bytes)s" +LauncherDownloadFilePercent = "Fazendo download da atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherDecompressingFile = "Descompactando atualização de "+ LauncherProgress +"..." +LauncherDecompressingPercent = "Descompactando atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherExtractingFile = "Extraindo atualização de "+ LauncherProgress +"..." +LauncherExtractingPercent = "Extraindo atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherPatchingFile = "Aplicando atualização de "+ LauncherProgress +"..." +LauncherPatchingPercent = "Aplicando atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherConnectProxyAttempt = "Conectando-se a Toontown: %s (proxy: %s) tentativa: %s" +LauncherConnectAttempt = "Conectando-se a Toontown: %s tentativa %s" +LauncherDownloadServerFileList = "Atualizando Toontown..." +LauncherCreatingDownloadDb = "Atualizando Toontown..." +LauncherDownloadClientFileList = "Atualizando Toontown..." +LauncherFinishedDownloadDb = "Atualizando Toontown..." +LauncherStartingToontown = "Iniciando Toontown..." +LauncherStartingGame = "Iniciando Toontown..." +LauncherRecoverFiles = "Atualizando Toontown. Recuperando arquivos..." +LauncherCheckUpdates = "Verificando atualizações de "+ LauncherProgress +LauncherVerifyPhase = "Atualizando Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Fazer um\nToon" +AvatarChoicePlayThisToon = "Jogar com\neste Toon" +AvatarChoiceSubscribersOnly = "Assinar\n\n\n\nAgora!" +AvatarChoiceDelete = "Excluir" +AvatarChoiceDeleteConfirm = "Isto fará que %s seja excluído para sempre." +AvatarChoiceNameRejected = "Nome\nrejeitado" +AvatarChoiceNameApproved = "Nome\naprovado!" +AvatarChoiceNameReview = "Em\nrevisão" +AvatarChoiceNameYourToon = "Dar um\nnome ao Toon!" +AvatarChoiceDeletePasswordText = "Cuidado! Isto fará que %s seja excluído para sempre. Para excluir este Toon, insira a sua senha." +AvatarChoiceDeleteConfirmText = "Cuidado! Isto excluirá %(name)s para sempre. Se você tiver certeza de que é isso mesmo que deseja, digite \"%(confirm)s\" e clique em OK." +AvatarChoiceDeleteConfirmUserTypes = "excluir" +AvatarChoiceDeletePasswordTitle = "Excluir Toon?" +AvatarChoicePassword = "Senha" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Esta senha não parece ser a correta. Para excluir este Toon, insira a sua senha." +AvatarChoiceDeleteWrongConfirm = "Você não digitou corretamente. Para excluir %(name)s, digite \"%(confirm)s\" e clique em OK. Não digite as aspas. Clique em Cancelar se desistir." + +# AvatarChooser.py +AvatarChooserPickAToon = "Escolha um Toon para jogar" +AvatarChooserQuit = lQuit + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Ligue para o Atendimento ao Cliente: %s." +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nSe precisar de ajuda, ligue para o Atendimento ao Cliente%s." +TTAccountIntractibleError = "Erro." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez',] +DateOfBirthEntryDefaultLabel = "Data de nascimento" + + +# AchievePage.py +AchievePageTitle = "Realizações\n(em breve)" + +# PhotoPage.py +PhotoPageTitle = "Foto\n(em breve)" + +# BuildingPage.py +BuildingPageTitle = "Edifícios\n(em breve)" + +# InventoryPage.py +InventoryPageTitle = "Piadas" +InventoryPageDeleteTitle = "EXCLUIR PIADAS" +InventoryPageTrackFull = "Você possui todas as piadas do tipo %s." +InventoryPagePluralPoints = "Você obterá uma nova piada de\n%(trackName)s quando\nganhar mais %(numPoints)s pontos de %(trackName)s." +InventoryPageSinglePoint = "Você obterá uma nova piada de\n%(trackName)s quando\nganhar mais %(numPoints)s pontos de %(trackName)s." +InventoryPageNoAccess = "Você ainda não tem acesso ao tipo %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS Toons" + +# EventsPage.py +PartyDateFormat = "%(mm)s %(dd)d, %(yyyy).4d" # Dec 8, 2008 +PartyTimeFormat = "%d:%.2d %s" # 1:45 pm +PartyTimeFormatMeridiemAM = "am" +PartyTimeFormatMeridiemPM = "pm" +PartyCanStart = "É Hora de Festejar! Clique em Start Party (Iniciar Festa) na sua página Shticker Book Hosting (Hospedando do Livro de Brincadeiras)!" +PartyHasStartedAcceptedInvite = '%s, a festa começou! Clique no anfitrião e em "Ir à Festa" na página Shticker Book Invites (Convites do Livro de Brincadeiras).' +PartyHasStartedNotAcceptedInvite = '%s, a festa começou! Você também pode ir, teletransportando-se para o anfitrião.' + +EventsPageName = "Eventos" +EventsPageCalendarTabName = "Calendário" +EventsPageCalendarTabParty = "Festa" +EventsPageToontownTimeIs = "A HORA DE TOONTOWN É" +EventsPageConfirmCancel = "Se cancelar, receberá uma devolução de %d%%. Tem certeza de que quer cancelar sua festa?" +EventsPageCancelPartyResultOk = "Sua festa foi cancelada e você recebeu %d balinhas de volta!" +EventsPageCancelPartyResultError = "Desculpe, sua festa não foi cancelada." +EventsPageTooLateToStart = "Desculpe, tarde demais para começar a sua festa. Você pode cancelá-la e planejar outra." +EventsPagePublicPrivateChange = "Alterando a sua configuração de privacidade de festa..." +EventsPagePublicPrivateNoGo = "Desculpe, você não pode alterar a sua configuração de privacidade de festa agora." +EventsPagePublicPrivateAlreadyStarted = "Desculpe, a sua festa já começou, e você não pode alterar sua configuração de privacidade de festa." +EventsPageHostTabName = "Hospedando" # displayed on the physical tab +EventsPageHostTabTitle = "Minha Próxima Festa" # banner text displayed across the top +EventsPageHostTabTitleNoParties = "Sem Festas" +EventsPageHostTabDateTimeLabel = "Você tem uma festa em %s às %s, Hora de Toontown." +EventsPageHostingTabNoParty = "Vá ao Portão de Festa\nde um pátio para planejar\nsua festa!" +EventsPageHostTabPublicPrivateLabel = "Esta festa é:" +EventsPageHostTabToggleToPrivate = "Particular" +EventsPageHostTabToggleToPublic = "Pública" +EventsPageHostingTabGuestListTitle = "Convidados" +EventsPageHostingTabActivityListTitle = "Atividades" +EventsPageHostingTabDecorationsListTitle = "Decorações" +EventsPageHostingTabPartiesListTitle = "Anfitriões" +EventsPageHostTabCancelButton = "Cancelar Festa" +EventsPageGoButton = "Iniciar\nFesta!" +EventsPageGoBackButton = "Festa\nJá!" +EventsPageInviteGoButton = "Ir para\Festa!" +EventsPageUnknownToon = "Toon Desconhecido" + +EventsPageInvitedTabName = "Convites" +EventsPageInvitedTabTitle = "Convites de Festa" +EventsPageInvitedTabInvitationListTitle = "Convites" +EventsPageInvitedTabActivityListTitle = "Atividades" +EventsPageInvitedTabTime = "%s %s Hora de Toontown" + +EventsPageNewsTabName = "Notícias" +EventsPageNewsTabTitle = "Notícias" +EventsPageNewsDownloading= "Recuperando Notícias..." +EventsPageNewsUnavailable = "Tico e Teco brincando com a impressora da gráfica. Notícias não disponíveis." +EventsPageNewsPaperTitle = "TOONTOWN TIMES (GAZETA DE TOONTOWN)" +EventsPageNewsLeftSubtitle = "Ainda só por 1 balinha" +EventsPageNewsRightSubtitle = "Tiragem de nove mil toonplares" + +# InvitationSelection.py +SelectedInvitationInformation = "%s tem uma festa em %s às %s, Hora de Toontown." + +# PartyPlanner.py +PartyPlannerNextButton = "Continuar" +PartyPlannerPreviousButton = "Voltar" +PartyPlannerWelcomeTitle = "Planejador de Festas de Toontown" +PartyPlannerInstructions = "Hospedar sua festa é muito divertido!\nComece a planejar com as setas na parte inferior!" +PartyPlannerDateTitle = "Escolha o Dia de Sua Festa" +PartyPlannerTimeTitle = "Escolha a Hora de Sua Festa" +PartyPlannerGuestTitle = "Selecione os Convidados" +PartyPlannerEditorTitle = "Crie Sua Festa\nInsira as Atividades e Decorações" +PartyPlannerConfirmTitle = "Escolha os Convites para Enviar" +PartyPlannerConfirmTitleNoFriends = "Reveja os Detalhes de Sua Festa" +PartyPlannerTimeToontown = "Toontown" +PartyPlannerTimeTime = "Hora" +PartyPlannerTimeRecap = "Dia e Hora da Festa" +PartyPlannerPartyNow = "O Mais Breve Possível" +PartyPlannerTimeToontownTime = "Hora de Toontown:" +PartyPlannerTimeLocalTime = "Hora Local da Festa: " +PartyPlannerPublicPrivateLabel = "A festa será:" +PartyPlannerPublicDescription = "Todos os Toons\npodem vir!" +PartyPlannerPrivateDescription = "Apenas\nToons Convidados\npodem vir!" +PartyPlannerPublic = "Pública" +PartyPlannerPrivate = "Particular" +PartyPlannerCheckAll = "Selecionar\nTudo" +PartyPlannerUncheckAll = "Desmarcar\nTudo" +PartyPlannerDateText = "Data" +PartyPlannerTimeText = "Hora" +PartyPlannerTTTimeText = "Hora de Toontown" +PartyPlannerEditorInstructionsIdle = "Clique na Atividade ou Decoração de Festa que deseja adquirir." +PartyPlannerEditorInstructionsClickedElementActivity = "Clique em Comprar ou Arraste o Ícone da Atividade para o Mapa da Festa" +PartyPlannerEditorInstructionsClickedElementDecoration = "Clique em Comprar ou Arraste o Ícone da Decoração para o Mapa da Festa" +PartyPlannerEditorInstructionsDraggingActivity = "Arraste a Atividade para o Mapa da Festa." +PartyPlannerEditorInstructionsDraggingDecoration = "Arraste a Decoração para o Mapa da Festa." +PartyPlannerEditorInstructionsPartyGrounds = "Clique e Arraste os itens para movê-los pelo Mapa da Festa" +PartyPlannerEditorInstructionsTrash = "Arraste uma Atividade ou Decoração até aqui para removê-la." +PartyPlannerEditorInstructionsNoRoom = "Não há lugar para colocar essa atividade." +PartyPlannerEditorInstructionsRemoved = "%(removed)s removidos(as) desde que %(added)s foram adicionados(as)." +PartyPlannerBeans = "feijões" +PartyPlannerTotalCost = "Custo Total:\n%d feijões" +PartyPlannerSoldOut = "ESGOTADO" +PartyPlannerBuy = "COMPRAR" +PartyPlannerPaidOnly = "SÓ ASSOCIADOS" +PartyPlannerPartyGrounds = "MAPA DA FESTA" +PartyPlannerOkWithGroundsLayout = "Você já terminou de mover suas Atividades e Decorações pelo Mapa da Festa?" +PartyPlannerChooseFutureTime = "Por favor, selecione uma hora futura." +PartyPlannerInviteButton = "Enviar Convites" +PartyPlannerInviteButtonNoFriends = "Planejar Festa" +PartyPlannerBirthdayTheme = "Aniversário" +PartyPlannerGenericMaleTheme = "Estrelas" +PartyPlannerGenericFemaleTheme = "Flores" +PartyPlannerRacingTheme = "Corrida" +PartyPlannerGuestName = "Nome do Convidado" +PartyPlannerClosePlanner = "Fechar Planejador" +PartyPlannerConfirmationAllOkTitle = "Parabéns!" +PartyPlannerConfirmationAllOkText = "Sua festa foi criada e os convites enviados.\nObrigado!" +PartyPlannerConfirmationAllOkTextNoFriends = "Sua festa foi criada!\nObrigado!" +PartyPlannerConfirmationErrorTitle = "Opa." +PartyPlannerConfirmationValidationErrorText = "Desculpe, ocorreu um problema\ncom essa festa.\nPor favor, volte e tente novamente." +PartyPlannerConfirmationDatabaseErrorText = "Desculpe, não foi possível registrar todas as informações.\nPor favor, tente novamente mais tarde.\nNão se preocupe, nenhum feijão foi perdido." +PartyPlannerConfirmationTooManyText = "Desculpe, você já está dando uma festa.\nSe quiser planejar outra, por favor,\ncancele a atual." +PartyPlannerInvitationThemeWhatSentence = "Você está convidado(a) para minha festa de %s! %s!" +PartyPlannerInvitationThemeWhatSentenceNoFriends = "Estou dando uma festa de %s! %s!" +PartyPlannerInvitationThemeWhatActivitiesBeginning = "Terá " +PartyPlannerInvitationWhoseSentence = "Festa de %s" +PartyPlannerInvitationTheme = "Tema" +PartyPlannerInvitationWhenSentence = "Será em %s,\nàs %s, Hora de Toontown.\nEspero que você apareça!" +PartyPlannerInvitationWhenSentenceNoFriends = "Será em %s,\nàs %s, Hora de Toontown.\nToontástico!" +PartyPlannerComingSoon = "Em Breve" +PartyPlannerCantBuy= "Não Pode Comprar" +PartyPlannerGenericName = "Planejador de Festa" + +# DistributedPartyJukeboxActivity.py +PartyJukeboxOccupied = "Alguém está usando a jukebox. Tente novamente mais tarde." +PartyJukeboxNowPlaying = "A música que você escolheu já está tocando na jukebox!" + +# Jukebox Music +MusicEncntrGeneralBg = "Encontro Com Cogs" +MusicTcSzActivity = "Mistureba de Toontorial" +MusicTcSz = "Passeando Juntos" +MusicCreateAToon = "Novo Toon na Cidade" +MusicTtTheme = "O Tema de Toontown" +MusicMinigameRace = "Devagar e Firme" +MusicMgPairing = "Você se lembra de Mim?" +MusicTcNbrhood = "Centro de Toontown" +MusicMgDiving = "Cantiga do Tesouro" +MusicMgCannonGame = "Disparar os Canhões!" +MusicMgTwodgame = "Toon Contínuo" +MusicMgCogthief = "Pegue Aquele Cog!" +MusicMgTravel = "Música para Viagem" +MusicMgTugOWar = "Cabo de Guerra" +MusicMgVine = "O Balanço da Selva" +MusicMgIcegame = "Situação Delicada" +MusicMgToontag = "Mistureba de Minijogo" +MusicMMatchBg2 = "Jazz da Minnie" +MusicMgTarget = "Sobrevoando Toontown" +MusicFfSafezone = "A Fazenda Divertida" +MusicDdSz = "O Caminho Tortuoso" +MusicMmNbrhood = "Melodilândia da Minnie" +MusicGzPlaygolf = "Vamos Jogar Golfe!" +MusicGsSz = "Autódromo do Pateta" +MusicOzSz = "Terras do Tico e Teco" +MusicGsRaceCc = "Dirigindo no Centro" +MusicGsRaceSs = "Preparar, Acionar, Vai!" +MusicGsRaceRr = "Rota 66" +MusicGzSz = "A Polca Puf-Puf" +MusicMmSz = "Dançando nas Ruas" +MusicMmSzActivity = "Aí Vem o Soprano" +MusicDdNbrhood = "O Porto do Donald" +MusicGsKartshop = "Sr. Pateta Mãos à Obra" +MusicDdSzActivity = "Cabana da Praia" +MusicEncntrGeneralBgIndoor = "Botando pra Quebrar" +MusicTtElevator = "Subindo?" +MusicEncntrToonWinningIndoor = "Toons Unidos!" +MusicEncntrGeneralSuitWinningIndoor = "Cogtástrofe!" +MusicTbNbrhood = "O Brrrgh" +MusicDlNbrhood = "Sonholândia do Donald" +MusicDlSzActivity = "Contando Ovelhas" +MusicDgSz = "Valsa das Flores" +MusicDlSz = "Sonâmbulo" +MusicTbSzActivity = "Nevasca" +MusicTbSz = "Tremendo e Vibrando" +MusicDgNbrhood = "Jardim da Margarida" +MusicEncntrHallOfFame = "A Galeria da Fama" +MusicEncntrSuitHqNbrhood = "Reais e Centavos" +MusicChqFactBg = "Fábrica de Cogs" +MusicCoghqFinale = "Triunfo dos Toons" +MusicEncntrToonWinning = "Pagando à Vista!" +MusicEncntrSuitWinning = "Vendendo Seu Short" +MusicEncntrHeadSuitTheme = "O Chefão" +MusicLbJurybg = "O Julgamento Começou" +MusicLbCourtyard = "Balançando" +MusicBossbotCeoV2 = "O Gerente" +MusicBossbotFactoryV1 = "Valsa do Cog" +MusicBossbotCeoV1 = "Rodeado de Chefes" +MusicPartyOriginalTheme = "Hora da Festa" +MusicPartyPolkaDance = "Polca de Festa" +MusicPartySwingDance = "Balanço de Festa" +MusicPartyWaltzDance = "Valsa de Festa" +MusicPartyGenericThemeJazzy = "Jazz de Festa" +MusicPartyGenericTheme = "Jingle de Festa" + + +# JukeBoxGui +JukeboxAddSong = "Adicionar\nMúsica" +JukeboxReplaceSong = "Trocar\nMúsica" +JukeboxQueueLabel = "Tocar a Seguir:" +JukeboxSongsLabel = "Selecionar Música:" +JukeboxClose = "Pronto" +JukeboxCurrentlyPlaying = "Tocando Agora" +JukeboxCurrentlyPlayingNothing = "Jukebox em pausa." +JukeboxCurrentSongNothing = "Adicionar uma música à lista!" + +PartyOverWarningNoName = "A festa acabou! Obrigado por ter vindo!" +PartyOverWarningWithName = "A festa %s de acabou! Obrigado por ter vindo!" +PartyCountdownClockText = "Tempo\n\nRestante" +PartyTitleText = "Festa de %s" # what you see when you enter a party + +PartyActivityConjunction = ", e " +# Note : This dictionary is used to show the names of the activities in various +# contexts. If PartyGlobals.ActivityIds is changed, this list must be +# updated with new indices. +PartyActivityNameDict = { + 0 : { + "generic" : "Jukebox\n20 músicas", + "invite" : "uma Jukebox de 20 músicas", + "editor" : "Jukebox de 20", + "description" : "Ouça música com sua própria jukebox de 20 músicas!" + }, + 1 : { + "generic" : "Canhões de Festa", + "invite" : "Canhões de Festa", + "editor" : "Canhões", + "description" : "Dispare você mesmo os canhões e divirta-se!" + }, + 2 : { + "generic" : "Trampolim", + "invite" : "Trampolim", + "editor" : "Trampolim", + "description" : "Pegue balinhas e salte o mais alto possível!" + }, + 3 : { + "generic" : "Pescaria de Festa", + "invite" : "Pescaria de Festa", + "editor" : "Pescaria de Festa", + "description" : "Pegue as frutas para ganhar feijões! Desvie-se das bigornas!" + }, + 4 : { + "generic" : "Pista de Dança\n10 passos", + "invite" : "uma Pista de Dança com 10 passos", + "editor" : "Pista de Dança de 10", + "description" : "Mostre seus 10 passos de dança ao estilo toon!" + }, + 5 : { + "generic" : "Cabo de Guerra de Festa", + "invite" : "Cabo de Guerra de Festa", + "editor" : "Cabo de Guerra", + "description" : "Até 4 toons de cada lado puxando loucamente!" + }, + 6 : { + "generic" : "Fogos de Artifício de Festa", + "invite" : "Fogos de Artifício de Festa", + "editor" : "Fogos de Artifício", + "description" : "Lance seu próprio show de fogos de artifício!" + }, + 7 : { + "generic" : "Relógio de Festa", + "invite" : "um Relógio de Festa", + "editor" : "Relógio de Festa", + "description" : "Faça a contagem regressiva do tempo que resta de sua festa." + }, + 8 : { + "generic" : "Jukebox\n40 músicas", + "invite" : "uma Jukebox de 40 músicas", + "editor" : "Jukebox de 40", + "description" : "Ouça música com sua própria jukebox de 40 músicas!" + }, + 9 : { + "generic" : "Pista de Dança\n20 passos", + "invite" : "uma Pista de Dança de 20 passos", + "editor" : "Pista de Dança de 20", + "description" : "Mostre seus 20 passos de dança ao estilo toon!" + }, +} + +# Note : This dictionary is used to show the names of the decorations in various +# contexts. If PartyGlobals.DecorationIds is changed, this list must be +# updated with new indices. +PartyDecorationNameDict = { + 0 : { + "editor" : "Bigorna de Balões", + "description" : "Tente evitar que a diversão acabe!", + }, + 1 : { + "editor" : "Palco de Festa", + "description" : "Balões, estrelas ou qualquer coisa que desejar", + }, + 2 : { + "editor" : "Arco de Festa", + "description" : "Torne a diversão envolvente!", + }, + 3 : { + "editor" : "Bolo", + "description" : "Delicioso.", + }, + 4 : { + "editor" : "Castelo de Festa", + "description" : "A casa de um Toon é seu castelo.", + }, + 5 : { + "editor" : "Pilha de Presentes", + "description" : "Presentes para todos os Toons!", + }, + 6 : { + "editor" : "Língua de Sogra", + "description" : "Esse apito é muito estridente! Serpenteante!", + }, + 7 : { + "editor" : "Portão de Festa", + "description" : "Multicolorido e doidinho!", + }, + 8 : { + "editor" : "Itens Barulhentos", + "description" : "Piiiiiiiiiiii!", + }, + 9 : { + "editor" : "Cata-vento", + "description" : "Giros coloridos para todos!", + }, + 10 : { + "editor" : "Globo Engraçado", + "description" : "Globo engraçado e de estrelas criado por Olivea", + }, + 11 : { + "editor" : "Faixa Feijão", + "description" : "Uma faixa em forma de balinha criada por Cassidy", + }, + 12 : { + "editor" : "Bolo Engraçado", + "description" : "Um bolo engraçado e Caótico criado por Felícia", + }, +} + +ActivityLabel = "Custo – Nome da Atividade" +PartyDoYouWantToPlan = "Deseja planejar uma nova festa agora?" +PartyPlannerOnYourWay = "Divirta-se planejando a sua festa!" +PartyPlannerMaybeNextTime = "Talvez da próxima vez. Tenha um bom-dia!" +PartyPlannerHostingTooMany = "Desculpe, você só pode dar uma festa de cada vez." +PartyPlannerOnlyPaid = "Desculpe, só toons assinantes podem dar uma festa." +PartyPlannerNpcComingSoon = "Em breve surgirão mais festas! Tente novamente mais tarde." +PartyPlannerNpcMinCost = "O custo mínimo para planejar uma festa é de %d balinhas." + +# Party Gates +PartyHatPublicPartyChoose = "deseja ir para a primeira festa pública disponível?" +PartyGateTitle = "Festas Públicas" +PartyGateGoToParty = "Ir para\nFesta!" +PartyGatePartiesListTitle = "Anfitriões" +PartyGatesPartiesListToons = "Toons" +PartyGatesPartiesListActivities = "Atividades" +PartyGatesPartiesListMinLeft = "Minutos Restantes" +PartyGateLeftSign = "Venha se Divertir!" +PartyGateRightSign = "Partes público aqui!" +PartyGateTitle = "Festas Públicas Aqui!" +PartyGatePartyUnavailable = "Desculpe. Essa festa não está mais disponível." +PartyGatePartyFull = "Desculpe. Essa festa está lotada." +PartyGateInstructions = 'Clique em um anfitrião e em "Ir para Festa"' + +# DistributedPartyActivity.py +PartyActivityWaitingForOtherPlayers = "Aguardando outros jogadores para se juntarem à festa..." +PartyActivityPleaseWait = "Por favor, aguarde..." +DefaultPartyActivityTitle = "Título do Jogo de Festa" +DefaultPartyActivityInstructions = "Instruções do Jogo de Festa" +PartyOnlyHostLeverPull = "Apenas o anfitrião pode iniciar essa atividade. Desculpe." +PartyActivityDefaultJoinDeny = "Você não pode participar dessa atividade no momento. Desculpe." +PartyActivityDefaultExitDeny = "Você não pode sair dessa atividade no momento. Desculpe." + +# JellybeanRewardGui.py +PartyJellybeanRewardOK = "OK" + +# DistributedPartyCatchActivity.py +PartyCatchActivityTitle = "Atividade Pescaria de Festa" +PartyCatchActivityInstructions = "Pegue o máximo de peças de frutas que puder. Tente não 'pescar' quaisquer %(badThing)s!" +PartyCatchActivityFinishPerfect = "JOGO PERFEITO!" +PartyCatchActivityFinish = "Bom Jogo!" +PartyCatchActivityExit = 'EXIT' +PartyCatchActivityApples = 'maçãs' +PartyCatchActivityOranges = 'laranjas' +PartyCatchActivityPears = 'peras' +PartyCatchActivityCoconuts = 'cocos' +PartyCatchActivityWatermelons = 'melancias' +PartyCatchActivityPineapples = 'abacaxis' +PartyCatchActivityAnvils = 'bigornas' +PartyCatchStarted = "O jogo começou. Divirta-se." +PartyCatchCannotStart = "O jogo não pode ser iniciado no momento." +PartyCatchRewardMessage = "Peças de frutas coletadas: %s\n\nBalinhas recebidas: %s" + +# DistributedPartyDanceActivity.py +PartyDanceActivityTitle = "Pista de Dança de Festa" +PartyDanceActivityInstructions = "Combine 3 ou mais padrões de SETAS para fazer os passos de dança! Há 10 passos de dança disponíveis. Você consegue obter todos?" +PartyDanceActivity20Title = "Pista de Dança de festa" +PartyDanceActivity20Instructions = "Combine 3 ou mais padrões de SETAS para fazer os passos de dança! Há 20 passos de dança disponíveis. Você consegue obter todos?" + +DanceAnimRight = "Direita" +DanceAnimReelNeutral = "O Toonpescador" +DanceAnimConked = "O Cabeçamole" +DanceAnimHappyDance = "A Dança Feliz" +DanceAnimConfused = "Vertigem Total" +DanceAnimWalk = "Andando na Lua" +DanceAnimJump = "O Salto!" +DanceAnimFirehose = "O Toonbombeiro" +DanceAnimShrug = "Quem Sabe?" +DanceAnimSlipForward = "A Queda" +DanceAnimSadWalk = "Exaustão" +DanceAnimWave = "Olá, Tchauzinho" +DanceAnimStruggle = "O Pulo Misto" +DanceAnimRunningJump = "O Toon Fugitivo" +DanceAnimSlipBackward = "A Queda de Costas" +DanceAnimDown = "A Descida" +DanceAnimUp = "A Subida" +DanceAnimGoodPutt = "A Tacada" +DanceAnimVictory = "A Dança da Vitória" +DanceAnimPush = "O Toonmímico" +DanceAnimAngry = "Rock'n'Roll" +DanceAnimLeft = "Esquerda" + +# DistributedPartyCannonActivity.py +PartyCannonActivityTitle = "Canhões de Festa" +PartyCannonActivityInstructions = "Acerte as nuvens para mudar sua cor e ricochetear no ar! NO AR, você pode USAR AS SETAS para DESLIZAR." +PartyCannonResults = "Você recebeu %d balinhas!\n\nNuvens Atingidas: %d" + +# DistributedPartyFireworksActivity.py +FireworksActivityInstructions = "Pressione a tecla \"Page Up\" para visualizar melhor." +FireworksActivityBeginning = "Os fogos de artifício de festa vão ser lançados! Curta o espetáculo!" +FireworksActivityEnding = "Espero que tenha gostado do espetáculo!" +PartyFireworksAlreadyActive = "O espetáculo de fogos de artifício já começou." +PartyFireworksAlreadyDone = "O espetáculo de fogos de artifício acabou." + +# DistributedPartyTrampolineActivity.py +PartyTrampolineJellyBeanTitle = "Trampolim de Balinhas" +PartyTrampolineTricksTitle = "Trampolim de Acrobacias" +PartyTrampolineActivityInstructions = "Use a tecla Control para saltar.\n\nSalte quando seu Toon estiver no ponto mais baixo do trampolim para ir bem alto." +PartyTrampolineActivityOccupied = "O trampolim está sendo usado." +PartyTrampolineQuitEarlyButton = "Saltar" +PartyTrampolineBeanResults = "Você recebeu %d balinhas." +PartyTrampolineBonusBeanResults = "Você recebeu %d balinhas, além de mais %d por conseguir o Big Bean (Grande Feijão)." +PartyTrampolineTopHeightResults = "Seu recorde de altura foi %d ft (mt)." +PartyTrampolineTimesUp = "Acabou o Tempo" +PartyTrampolineReady = "Preparar..." +PartyTrampolineGo = "Já!" +PartyTrampolineBestHeight = "Recorde de Altura Até Agora:\n%s\n%d ft (mt)" +PartyTrampolineNoHeightYet = "Quão alto\nvocê pode saltar?" + +# DistributedPartyTugOfWarActivity.py +PartyTugOfWarJoinDenied = "Desculpe. Você não pode participar do cabo de guerra no momento." +PartyTugOfWarTeamFull = "Desculpe. Essa equipe já está completa." +PartyTrampolineQuitEarlyButton = "Saltar" +PartyTugOfWarExitButton = "Sair" +PartyTugOfWarWaitingForMore = "Aguardando mais jogadores" # extra spaces on purpose given the blocky font +PartyTugOfWarWaitingToStart = "Aguardando para começar" +PartyTugOfWarWaitingForOtherPlayers = "Aguardando outros jogadores" +PartyTugOfWarReady = "Preparar..." +PartyTugOfWarGo = "JÁ!" +PartyTugOfWarGameEnd = "Bom jogo!" +PartyTugOfWarGameTie = "Você empatou!" +PartyTugOfWarRewardMessage = "você conseguiu %d balinhas. Bom trabalho!" +PartyTugOfWarTitle = "Cabo de Guerra de Festa" + +# CalendarGuiMonth.py +CalendarShowAll = "Exibir Tudo" +CalendarShowOnlyHolidays = "Exibir Apenas Feriados" +CalendarShowOnlyParties = "Exibir Apenas Feriados" + +# CalendarGuiDay.py +CalendarEndsAt = "Termina em " +CalendarStartedOn = "Iniciada em " +CalendarEndDash = "Final-" +CalendarEndOf = "Final de " +CalendarPartyGetReady = "Prepare-se!" +CalendarPartyGo = "Festejar!" +CalendarPartyFinished = "Acabou..." +CalendarPartyCancelled = "Cancelado." +CalendarPartyNeverStarted = "Nunca Aconteceu." + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Restantes %s" + +# PartiesPage.py +PartiesPageTitle = "" +PartiesPageHostTab = "" +PartiesPageInvitedTab = "" +PartiesPageTitleHost = "" +PartiesPageTitleInvited = "" + +# MapPage.py +MapPageTitle = "Mapa" +MapPageBackToPlayground = "Voltar para o pátio" +MapPageBackToCogHQ = "Voltar para o Quartel de Cogs" +MapPageGoHome = "Ir para casa" +# hood name, street name +MapPageYouAreHere = "Você está em: %s %s" +MapPageYouAreAtHome = "Você está em\nsua propriedade" +MapPageYouAreAtSomeonesHome = "Você está na propriedade de %s" +MapPageGoTo = "Ir para\n%s" + +# OptionsPage.py +OptionsPageTitle = "Opções" +OptionsPagePurchase = "Assine já!" +OptionsPageLogout = "Sair" +OptionsPageExitToontown = "Sair de Toontown" +OptionsPageMusicOnLabel = "A música está ligada." +OptionsPageMusicOffLabel = "A música está desligada." +OptionsPageSFXOnLabel = "Os efeitos sonoros estão ligados." +OptionsPageSFXOffLabel = "Os efeitos sonoros estão desligados." +OptionsPageToonChatSoundsOnLabel = " Type Chat Sounds are on." +OptionsPageToonChatSoundsOffLabel = " Type Chat Sounds are off." +OptionsPageFriendsEnabledLabel = "Aceito fazer novas amizades." +OptionsPageFriendsDisabledLabel = "Não aceito fazer amizades." +OptionsPageSpeedChatStyleLabel = "Cor do Chat rápido" +OptionsPageDisplayWindowed = "com janela" +OptionsPageSelect = "Selecionar" +OptionsPageToggleOn = "Ligar" +OptionsPageToggleOff = "Desligar" +OptionsPageChange = "Alterar" +OptionsPageDisplaySettings = "Vídeo: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Vídeo: %(screensize)s" +OptionsPageExitConfirm = "Sair de Toontown?" + +DisplaySettingsTitle = "Configurações de vídeo" +DisplaySettingsIntro = "As configurações a seguir são usadas para determinar a maneira como Toontown é exibida em seu computador. Provavelmente, não será necessário ajustá-las, a menos que você esteja tendo algum problema." +DisplaySettingsIntroSimple = "Você pode ajustar a resolução da tela com um valor maior para melhorar o contraste do texto e dos gráficos em Toontown, mas, dependendo da placa de vídeo do seu computador, alguns valores mais altos podem fazer que o jogo fique lento ou trave." + +DisplaySettingsApi = "API de gráfico:" +DisplaySettingsResolution = "Resolução:" +DisplaySettingsWindowed = "Em uma janela" +DisplaySettingsFullscreen = "Tela cheia" +DisplaySettingsApply = "Aplicar" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Quando você pressionar OK, as configurações de vídeo serão alteradas. Se a nova configuração não ficar adequada em seu computador, o vídeo retornará à configuração original após %s segundos." +DisplaySettingsAccept = "Pressione em OK para manter as novas configurações, ou em Cancelar para voltar às anteriores. Se você não pressionar nada, as configurações voltarão em %s segundos automaticamente aos valores anteriores." +DisplaySettingsRevertUser = "As configurações de vídeo anteriores foram restauradas." +DisplaySettingsRevertFailed = "As configurações de vídeo selecionadas não funcionam em seu computador. As configurações de vídeo anteriores foram restauradas." + +# TrackPage.py +TrackPageTitle = "Treinamento de tipos de piadas" +TrackPageShortTitle = "Treinamento de piadas" +TrackPageSubtitle = "Execute as Tarefas Toon para aprender a usar novas piadas!" +TrackPageTraining = "Você está treinando para usar as Piadas de %s.\nQuando executar todas as 16 tarefas,\nestará apto a usar as Piadas de %s nas batalhas." +TrackPageClear = "Você não está treinando nenhum tipo de piadas agora." +TrackPageFilmTitle = "Filme de\ntreinamento\nde %s" +TrackPageDone = "FIM" + +# QuestPage.py +QuestPageToonTasks = "Tarefas Toon" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nPara: %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nPara escolher, selecione:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Escolha" +QuestPageLocked = "Travado" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Qualquer rua" +QuestPosterHQLocationName = "Qualquer bairro" + +QuestPosterTailor = "Costureiro" +QuestPosterTailorBuildingName = "Loja de Roupas" +QuestPosterTailorStreetName = "Qualquer pátio" +QuestPosterTailorLocationName = "Qualquer bairro" +QuestPosterPlayground = "No pátio" +QuestPosterAtHome = "Na "+lMyEstate +QuestPosterInHome = "Em "+lMyEstate +QuestPosterOnPhone = "No seu telefone" +QuestPosterEstate = "Na sua propriedade" +QuestPosterAnywhere = "Qualquer lugar" +QuestPosterAuxTo = "para:" +QuestPosterAuxFrom = "de:" +QuestPosterAuxFor = "para:" +QuestPosterAuxOr = "ou:" +QuestPosterAuxReturnTo = "Retornar\npara:" +QuestPosterLocationIn = "" +QuestPosterLocationOn = "" +QuestPosterFun = "Só de brincadeira!" +QuestPosterFishing = "IR PESCAR" +QuestPosterComplete = "CONCLUIR" + +# ShardPage.py +ShardPageTitle = "Regiões" +ShardPageHelpIntro = "Cada Região é uma cópia do mundo de Toontown." +ShardPageHelpWhere = " Você está agora na Região \"%s\"." +ShardPageHelpWelcomeValley = " Você está agora na Região \"Vale Boas-vindas\", em \"%s\"." +ShardPageHelpMove = " Para ir até uma nova Região, clique no nome dela." + +ShardPagePopulationTotal = "População Total de Toontown:\n%d" +ShardPageScrollTitle = "Nome População" +ShardPageLow = "Tranquila" +ShardPageMed = "Inteligente" +ShardPageHigh = "Lotada" +ShardPageChoiceReject = "Desculpe, essa Região está lotada. Por favor, tente outra." + +# SuitPage.py +SuitPageTitle = "Galeria de Cogs" +SuitPageMystery = DialogQuestion + DialogQuestion + DialogQuestion +SuitPageQuota = "%s de %s" +SuitPageCogRadar = "%s presentes" +SuitPageBuildingRadarS = "%s edifício" +SuitPageBuildingRadarP = "%s edifícios" + +# DisguisePage.py +DisguisePageTitle = Cog + "Disfarce" +DisguisePageMeritAlert = "Pronto para a\npromoção!" +DisguisePageCogLevel = "Nível %s" +DisguisePageMeritFull = "Completo" + +# FishPage.py +FishPageTitle = "Pescaria" +FishPageTitleTank = "Balde de peixes" +FishPageTitleCollection = "Álbum de peixes" +FishPageTitleTrophy = "Troféus de pesca" +FishPageWeightStr = "Peso: " +FishPageWeightLargeS = "%d Kg " +FishPageWeightLargeP = "%d Kg " +FishPageWeightSmallS = "%d g" +FishPageWeightSmallP = "%d g" +FishPageWeightConversion = 16 +FishPageValueS = "Valor: %d balinha" +FishPageValueP = "Valor: %d balinhas" +FishPageCollectedTotal = "Espécies de peixes recolhidas: %d de %d" +FishPageRodInfo = "Vara %s\n%d - %d quilos" +FishPageTankTab = "Balde" +FishPageCollectionTab = "Álbum" +FishPageTrophyTab = "Troféus" + +FishPickerTotalValue = "Balde: %s / %s\nValor: %d balinhas" + +UnknownFish = DialogQuestion + DialogQuestion + DialogQuestion + +FishingRod = "Vara %s" +FishingRodNameDict = { + 0 : "Vareta", + 1 : "Bambu", + 2 : "Madeira de lei", + 3 : "Aço", + 4 : "Dourado", + } +FishTrophyNameDict = { + 0 : "Peixinhozinho", + 1 : "Peixinho", + 2 : "Peixe", + 3 : "Peixe-voador", + 4 : "Tubarão", + 5 : "Peixe-espada", + 6 : "Baleia assassina", + } + +# GardenPage.py +GardenPageTitle = "Jardinagem" +GardenPageTitleBasket = "Cesto de Flores" +GardenPageTitleCollection = "Álbum de Flores" +GardenPageTitleTrophy = "Troféus de Jardinagem" +GardenPageTitleSpecials = "Especiais de Jardinagem" +GardenPageBasketTab = "Cesto" +GardenPageCollectionTab = "Álbum" +GardenPageTrophyTab = "Troféus" +GardenPageSpecialsTab = "Especiais" +GardenPageCollectedTotal = "Variedades de Flores Colecionadas: %d de %d" +GardenPageValueS = "Valor: %d balinha" +GardenPageValueP = "Valor: %d balinhas" +FlowerPickerTotalValue = "Cesto: %s / %s\nValor: %d balinhas" +GardenPageShovelInfo = "%s Pá: %d / %d\n" +GardenPageWateringCanInfo = "%s Regador: %d / %d" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Personalizador de karts" +KartPageTitleRecords = "Melhores recordes pessoais" +KartPageTitleTrophy = "Troféus de corridas" +KartPageCustomizeTab = "Personalizar" +KartPageRecordsTab = "Recordes" +KartPageTrophyTab = "Troféus" +KartPageTrophyDetail = "Troféus %s : %s" +KartPageTickets = "Bilhetes :" +KartPageConfirmDelete = "Excluir acessório?" + +#plural +KartShtikerDelete = "Excluir" +KartShtikerSelect = "Selecionar uma categoria" +KartShtikerNoAccessories = "Não possui acessórios" +KartShtikerBodyColors = "Cores de karts" +KartShtikerAccColors = "Cores de acessórios" +KartShtikerEngineBlocks = "Acessórios de capô" +KartShtikerSpoilers = "Acessórios de mala" +KartShtikerFrontWheelWells = "Acessórios de roda dianteira" +KartShtikerBackWheelWells = "Acessórios de roda traseira" +KartShtikerRims = "Acessórios de aro" +KartShtikerDecals = "Acessórios de decalque" +#singluar +KartShtikerBodyColor = "Cor do kart" +KartShtikerAccColor = "Cor do acessório" +KartShtikerEngineBlock = "Capô" +KartShtikerSpoiler = "Mala" +KartShtikerFrontWheelWell = "Roda dianteira" +KartShtikerBackWheelWell = "Roda traseira" +KartShtikerRim = "Aro" +KartShtikerDecal = "Decalque" + +KartShtikerDefault = "Padrão %s" +KartShtikerNo = "Nenhum acessório %s" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Escolher" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = 'Toonar permite que você cure outros Toons que estão na batalha.' +TrackChoiceGuiTRAP = 'Armadilhas são piadas poderosas que devem ser usadas com Iscas.' +TrackChoiceGuiLURE = 'Use Iscas para abalar os Cogs ou faça-os cair em armadilhas.' +TrackChoiceGuiSOUND = 'As piadas Sonoras afetam todos os Cogs, mas não são muito poderosas.' +TrackChoiceGuiDROP = "As piadas Cadentes fazem muitos estragos, mas não são muito precisas." + +# EmotePage.py +EmotePageTitle = "Expressões / Emoções" +EmotePageDance = "Você montou a seguinte sequência de dança:" +EmoteJump = "Saltitante" +EmoteDance = "Dançante" +EmoteHappy = "Feliz" +EmoteSad = "Triste" +EmoteAnnoyed = "Aborrecido" +EmoteSleep = "Sonolento" + +# TIP Page +TIPPageTitle = "DICA" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNível %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Você não pode sair do parque até que o seu Risômetro esteja sorrindo!" + +# InventoryNew.py +InventoryTotalGags = "Total de piadas\n%d / %d" +InventroyPinkSlips = "%s Bilhetes Azuis" +InventroyPinkSlip = "1 Bilhete Azul" +InventoryDelete = "EXCLUIR" +InventoryDone = "OK" +InventoryDeleteHelp = "Clique em uma piada para EXCLUIR." +InventorySkillCredit = "Crédito de habilidades:\n%s" +InventorySkillCreditNone = "Crédito de habilidades:\nNenhum" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Precisão: %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "Faltam %(nextExp)s!" +InventoryGuestExp = "Limite de Visitantes" +GuestLostExp = " Acima do Limite de Visitantes" +InventoryAffectsOneCog = "Afeta: Um "+ Cog +InventoryAffectsOneToon = "Afeta: Um Toon" +InventoryAffectsAllToons = "Afeta: Todos os Toons" +InventoryAffectsAllCogs = "Afeta: Todos os "+ Cogs +InventoryHealString = "Toonar" +InventoryDamageString = "Dano" +InventoryBattleMenu = "MENU DE BATALHA" +InventoryRun = "CORRER" +InventorySOS = "SOS" +InventoryPass = "PASSAR" +InventoryFire = "Fogo" +InventoryClickToAttack = "Clique em uma\npiada para\natacar" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Você deve pegar o bondinho antes de sair.\n\n\n\n\nVocê poderá encontrar o bondinho ao lado da Loja de Piadas do Pateta." +NPCForceAcknowledgeMessage2 = "Muito bem! Você completou a busca pelo bondinho!\nVisite o Quartel dos Toons para solicitar a sua recompensa.\n\n\n\n\n\nO Quartel dos Toons localiza-se próximo ao centro do pátio." +NPCForceAcknowledgeMessage3 = "Lembre-se de pegar o bondinho.\n\n\n\nVocê pode encontrar o bondinho ao lado da Loja de Piadas do Pateta." +NPCForceAcknowledgeMessage4 = "Parabéns! Você concluiu a sua primeira Tarefa Toon!\n\n\n\n\n\nVisite o Quartel dos Toons para solicitar a sua recompensa." +NPCForceAcknowledgeMessage5 = "Não se esqueça de sua Tarefa Toon!\n\n\n\n\n\n\n\n\n\n\nVocê pode encontrar Cogs para serem derrotados do outro lado de túneis como este." +NPCForceAcknowledgeMessage6 = "Excelente trabalho derrotando esses Cogs!\n\n\n\n\n\n\n\n\nVolte para o Quartel dos Toons o mais rápido possível." +NPCForceAcknowledgeMessage7 = "Não se esqueça de fazer um amigo!\n\n\n\n\n\n\nClique em outro jogador e use o botão Novo amigo." +NPCForceAcknowledgeMessage8 = "Ótimo! Você fez um novo amigo!\n\n\n\n\n\n\n\n\nAgora, você deve voltar para o Quartel dos Toons." +NPCForceAcknowledgeMessage9 = "Bom trabalho usando o telefone!\n\n\n\n\n\n\n\n\nVolte para o Quartel dos Toons para pedir a sua recompensa." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Você recebeu 1 ponto de Lançamento! Quando você obtém 10, ganha uma nova piada!" +MovieTutorialReward2 = "Você recebeu 1 ponto de Esguicho! Quando você obtém 10, ganha uma nova piada!" +MovieTutorialReward3 = "Muito bom! Você concluiu a sua primeira Tarefa Toon!" +MovieTutorialReward4 = "Vá para o Quartel dos Toons para pegar a sua recompensa!" +MovieTutorialReward5 = "Divirta-se!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toonar', 'armadilha', 'isca', 'sonora', 'lançamento', 'esguicho', 'cadente'] +BattleGlobalNPCTracks = ['reabastecer', 'toons atingidos', 'cogs não-atingidos'] +BattleGlobalAvPropStrings = ( + ('Pena', 'Megafone', 'Batom', 'Bengala', 'Pó mágico', 'Bolinhas de malabarismo', 'Mergulho Elevado'), + ('Casca de banana', 'Ancinho', 'Bolas de gude', 'Areia movediça', 'Alçapão', 'TNT', 'Estrada De Ferro'), + ('Nota de $1', 'Ímã pequeno', 'Nota de $5', 'Ímã grande', 'Nota de $10', 'Óculos hipnóticos', 'Presentação'), + ('Buzina de bicicleta', 'Apito', 'Trombeta', 'Foooonnnn!', 'Tromba de elefante', 'Buzina', 'Cantor de Ópera'), + ('Bolinho', 'Fatia de torta de frutas', 'Fatia de torta de creme', 'Torta de frutas inteira', 'Torta de creme inteira', 'Bolo de aniversário', 'Bolo de Casamento'), + ('Flor com esguicho', 'Copo d\'água', 'Revólver de água', 'Garrafa de água com gás', 'Mangueira de incêndio', 'Nuvem de chuva', 'Gêiser'), + ('Vaso de flor', 'Saco de areia', 'Bigorna', 'Peso pesado', 'Cofre', 'Piano de cauda', 'Toontanic') + ) +BattleGlobalAvPropStringsSingular = ( + ('uma Pena', 'um Megafone', 'um Batom', 'uma Bengala', 'um Pó mágico', 'um conjunto de Bolinhas de malabarismo', 'um Mergulho Elevado'), + ('uma Casca de banana', 'um Ancinho', 'um conjunto de Bolas de gude', 'uma poça de Areia movediça', 'um Alçapão', 'um TNT', 'uma Estrada de Ferro'), + ('uma Nota de $1', 'um Ímã pequeno', 'uma Nota de $5', 'um Ímã grande', 'uma Nota de $10', 'um par de Óculos hipnóticos', 'uma Presentação'), + ('uma Buzina de bicicleta', 'um Apito', 'uma Trombeta', 'um Foooonnnn!', 'uma Tromba de elefante', 'uma Buzina', 'um Cantor de Ópera'), + ('um Bolinho', 'uma Fatia de torta de frutas', 'uma Fatia de torta de creme', 'uma Torta de frutas inteira', 'uma Torta de creme inteira', 'um Bolo de Casamento'), + ('uma Flor com esguicho', 'um Copo d\'água', 'um Revólver de água', 'uma Garrafa de água com gás', 'uma Mangueira de incêndio', 'uma Nuvem de chuva', 'um Gêiser'), + ('um Vaso de flor', 'um Saco de areia', 'uma Bigorna', 'um Peso pesado', 'um Cofre', 'um Piano de cauda', 'the Toontanic') + ) +BattleGlobalAvPropStringsPlural = ( + ('Penas', 'Megafones', 'Batons', 'Bengalas', 'Pós mágicos', 'conjuntos de Bolinhas de malabarismo', 'Mergulhos Elevados'), + ('Cascas de banana', 'Ancinhos', 'conjuntos de Bolas de gude', 'poças de Areia movediça', 'Alçapões','TNTs', 'Estradas de Ferro'), + ('Notas de $1', 'Ímãs pequenos', 'Contas de $5', 'Ímãs grandes','Contas de $10', 'par de Óculos hipnóticos', 'Presentação'), + ('Buzinas de bicicleta', 'Apitos', 'Trombetas', 'Foooonnnns!', 'Trombas de elefante', 'Buzinas', 'Cantor de Ópera'), + ('Bolinhos', 'Fatias de torta de frutas', 'Fatias de torta de creme','Tortas de frutas inteiras', 'Tortas de creme inteiras', 'Bolos de aniversário', 'Bolo de Casamento'), + ('Flores com esguicho', 'Copos d\'água', 'Revólveres de água','Garrafas de água com gás', 'Mangueiras de incêndio', 'Nuvens de chuva', 'Gêiser'), + ('Vasos de flor', 'Sacos de areia', 'Bigornas', 'Pesos pesados', 'Cofres','Pianos de cauda', 'Transatlânticos') + ) +BattleGlobalAvTrackAccStrings = ("Médio", "Perfeito", "Baixo", "Alto", "Médio", "Alto", "Baixo") +BattleGlobalLureAccLow = "Baixo" +BattleGlobalLureAccMedium = "Médio" + +AttackMissed = "PERDEU" + +NPCCallButtonLabel = 'CHAMAR' + +# ToontownLoader.py +LoaderLabel = "Carregando..." + +# PlayGame.py +HeadingToHood = "Indo %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "Indo para a sua propriedade..." +HeadingToEstate = "Indo para a propriedade de %s..." # avatar name +HeadingToFriend = "Indo para a propriedade do amigo de %s..." # avatar name + +# Hood.py +HeadingToPlayground = "Indo para o Pátio..." +HeadingToStreet = "Indo %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Voltar correndo para o pátio?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "QUAL TOON?" +TownBattleChooseAvatarCogTitle = "QUAL " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "VOLTAR" + +#firecogpanel +FireCogTitle = "BILHETES AZUIS RESTANTES:%s\nQUAL COG DEMITIR?" +FireCogLowTitle = "BILHETES AZUIS RESTANTES:%s\nSEM BILHETES SUFICIENTES!" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Não há amigos para chamar!" +TownBattleSOSWhichFriend = "Chamar qual amigo?" +TownBattleSOSNPCFriends = "Toons resgatados" +TownBattleSOSBack = "VOLTAR" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "Disparar" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Aguardando\noutros jogadores..." +TownSoloBattleWaitTitle = "Aguarde..." +TownBattleWaitBack = "VOLTAR" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Procurando rabisco\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s está %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "Você não pode embarcar no bondinho até que o seu Risômetro esteja sorrindo." +TrolleyTFAMessage = "\Você não pode embarcar no bondinho até que o " + Mickey +" permita." +TrolleyHopOff = "Descer" + +# DistributedFishingSpot.py +FishingExit = "Sair" +FishingCast = "Lançar" +FishingAutoReel = "Molinete automático" +FishingItemFound = "Você pegou:" +FishingCrankTooSlow = "Muito\ndevagar" +FishingCrankTooFast = "Muito\nrápido" +FishingFailure = "Você não pegou nada!" +FishingFailureTooSoon = "Não comece a rebobinar a linha até que você veja uma pequena mordida. Espere a bóia balançar para cima e para baixo rapidamente!" +FishingFailureTooLate = "Rebobine a linha enquanto o peixe ainda está mordendo a isca!" +FishingFailureAutoReel = "O molinete automático não funcionou desta vez. Gire a manivela manualmente, na velocidade certa, para ter mais chance de pegar alguma coisa!" +FishingFailureTooSlow = "Você girou a manivela muito devagar. Alguns peixes são mais rápidos do que outros. Tente manter a barra de velocidade centralizada!" +FishingFailureTooFast = "Você girou a manivela muito rápido. Alguns peixes são mais lentos do que outros. Tente manter a barra de velocidade centralizada!" +FishingOverTankLimit = "O seu balde de pesca está cheio. Vá vender os seus peixes para o Vendedor da Loja de Animais e volte." +FishingBroke = "Você não tem mais balinhas para as iscas! Para ganhar mais balinhas, pegue o bondinho ou venda os peixes para os Vendedores da Loja de Animais." +FishingHowToFirstTime = "Clique e arraste para baixo no botão Lançar. Quanto mais baixo você arrastar, mais forte será o lançamento. Ajuste o ângulo para acertar os alvos dos peixes.\n\nTente agora!" +FishingHowToFailed = "Clique e arraste para baixo no botão Lançar. Quanto mais baixo você arrastar, mais forte será o lançamento. Ajuste o ângulo para acertar os alvos dos peixes.\n\nTente agora de novo!" +FishingBootItem = "Bota velha" +FishingJellybeanItem = "%s balinhas" +FishingNewEntry = "Novas espécies!" +FishingNewRecord = "Novo recorde!" + +# FishPoker +FishPokerCashIn = "Morrer\n%s\n%s" +FishPokerLock = "Bloquear" +FishPokerUnlock = "Desbloquear" +FishPoker5OfKind = "5 de um naipe" +FishPoker4OfKind = "4 de um naipe" +FishPokerFullHouse = "Full House" +FishPoker3OfKind = "3 de um naipe" +FishPoker2Pair = "2 pares" +FishPokerPair = "Par" + +# DistributedTutorial.py +TutorialGreeting1 = "Oi %s!" +TutorialGreeting2 = "Oi %s!\nVem cá!" +TutorialGreeting3 = "Oi %s!\nVem cá!\nUse as teclas de seta!" +TutorialMickeyWelcome = "Bem-vindo a Toontown!" +TutorialFlippyIntro = "Deixe-me apresentar você ao meu amigo "+ Flippy +"..." +TutorialFlippyHi = "Oi, %s!" +TutorialQT1 = "Você pode conversar usando isto." +TutorialQT2 = "Você pode conversar usando isto.\nClique no item e escolha \"Oi\"." +TutorialChat1 = "Você pode conversar usando qualquer um destes botões." +TutorialChat2 = "O botão azul permite que você converse usando o teclado." +TutorialChat3 = "Cuidado! A maior parte dos outros jogadores não entenderá o que você está dizendo se usar o teclado." +TutorialChat4 = "O botão verde abre o %s." +TutorialChat5 = "Todos entenderão se você usar o %s." +TutorialChat6 = "Tente dizer \"Oi\"." +TutorialBodyClick1 = "Muito bem!" +TutorialBodyClick2 = "Muito prazer! Quer ser meu amigo?" +TutorialBodyClick3 = "Para fazer amizade com "+ Flippy +", clique nele..." +TutorialHandleBodyClickSuccess = "Muito bom!" +TutorialHandleBodyClickFail = "Não é assim. Tente clicar em cima do "+ Flippy +"..." +TutorialFriendsButton = "Agora, clique no botão 'Amigos' abaixo da figura do "+ Flippy +" no canto direito." +TutorialHandleFriendsButton = "Em seguida, clique no botão 'Sim'.." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Você quer fazer amizade com o "+ Flippy +"?" +TutorialFriendsPanelMickeyChat = Flippy + " aceitou ser seu amigo. Clique em 'Ok' para concluir." +TutorialFriendsPanelYes = Flippy + " disse sim!" +TutorialFriendsPanelNo = "Isso não foi muito simpático!" +TutorialFriendsPanelCongrats = "Parabéns! Você fez seu primeiro amigo." +TutorialFlippyChat1 = "Venha me ver quando estiver pronto para a sua primeira Tarefa Toon!" +TutorialFlippyChat2 = "Estarei na PrefeiToona!" +TutorialAllFriendsButton = "Você pode ver todos os seus amigos clicando no botão Amigos. Experimente..." +TutorialEmptyFriendsList = "No momento, a sua lista está vazia porque o "+ Flippy +" não é um jogador real." +TutorialCloseFriendsList = "Clique no botão 'Fechar'\npara fazer que a\nlista desapareça" +TutorialShtickerButton = "O botão do canto direito inferior abre o seu Álbum Toon. Experimente..." +TutorialBook1 = "O álbum contém várias informações úteis, como este mapa de Toontown." +TutorialBook2 = "Você também pode verificar o andamento de suas Tarefas Toon." +TutorialBook3 = "Quando você estiver pronto, clique no botão do álbum novamente, para fechá-lo" +TutorialLaffMeter1 = "Você também precisará disto..." +TutorialLaffMeter2 = "Você também precisará disto...\nÉ o seu Risômetro." +TutorialLaffMeter3 = "Quando os "+ Cogs +" atacarem você, ele diminui." +TutorialLaffMeter4 = "Quando você está em pátios como este, ele volta a subir." +TutorialLaffMeter5 = "Quando concluir as Tarefas Toon, você obterá recompensas, como o aumento do seu Limite de risadas." +TutorialLaffMeter6 = "Cuidado! Se os "+ Cogs +" derrotarem você, perderá todas as suas piadas." +TutorialLaffMeter7 = "Para obter mais piadas, divirta-se com os jogos no bondinho." +TutorialTrolley1 = "Siga-me até o bondinho!" +TutorialTrolley2 = "Pule nele!" +TutorialBye1 = "Brinque com alguns jogos!" +TutorialBye2 = "Divirta-se com alguns jogos!\nCompre algumas piadas!" +TutorialBye3 = "\Vá encontrar o "+ Flippy +" quando terminar!"# TutorialForceAcknowledge.py + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "\Você está indo na direção errada! \Vá encontrar o "+ Mickey +"!"# SpeedChat + +PetTutorialTitle1 = "O Painel dos Rabiscos" +PetTutorialTitle2 = "Chat rápido dos Rabiscos" +PetTutorialTitle3 = "Gadálogo dos Rabiscos" +PetTutorialNext = "Próxima Página" +PetTutorialPrev = "Página Anterior" +PetTutorialDone = lOK +PetTutorialPage1 = "Clique em um Rabisco para exibir o painel de Rabiscos. Daqui, você pode alimentar, coçar e chamar o Rabisco." +PetTutorialPage2 = "Use a nova área 'Bichinhos' no menu Chat rápido para fazer com que um Rabisco faça um truque. Se ele fizer, recompense-o para ele melhorar ainda mais!" +PetTutorialPage3 = "Compre novos truques de Rabiscos no Gadálogo da Clarabela. Truques melhores produzem Toonar melhores!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Jardinagem" +GardenTutorialTitle2 = "Flores" +GardenTutorialTitle3 = "Árvores" +GardenTutorialTitle4 = "Instruções" +GardenTutorialTitle5 = "Estátuas" +GardenTutorialNext = "Próxima Página" +GardenTutorialPrev = "Página Anterior" +GardenTutorialDone = lOK +GardenTutorialPage1 = "Crie o seu próprio jardim botânico! Você pode plantar flores e árvores, e até erguer estátuas." +GardenTutorialPage2 = "As flores são sensíveis, e você precisa descobrir as suas receitas de balinhas. Plante todos os tipos para melhorar as risadas, e venda as flores para ganhar balinhas." +GardenTutorialPage3 = "Use uma piada para plantar uma árvore. Alguns dias depois, essa piada vai melhorar!! Mas cuide bem da saúde dela, ou a melhoria se vai." +GardenTutorialPage4 = "Para plantar, regar, cavar ou fazer a colheita no seu jardim, ande até estes locais." +GardenTutorialPage5 = "Estátuas podem ser compradas no Catálogo da Clarabela. Aumenta suas habilidades para destravar as estátuas mais extravagantes." + +# Playground.py +PlaygroundDeathAckMessage = "Os" + Cogs + " levaram todas as suas piadas!\n\nVocê está triste. Você não pode sair do pátio até ficar feliz." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "O Supervisor da fábrica foi derrotado antes de você alcançá-lo. Você não recuperou nenhuma parte do Cog." + +# MintInterior +ForcedLeaveMintAckMsg = "O Supervisor do Andar da Casa da Moeda foi derrotado antes de você alcançá-lo. Você não recuperou nenhuma Grana Cog." + +# DistributedFactory.py +HeadingToFactoryTitle = "Dirigindo-se a %s..." +ForemanConfrontedMsg = "%s está lutando com o Supervisor da fábrica!" + +# DistributedMint.py +MintBossConfrontedMsg = "%s está lutando com o Supervisor!" + +# DistributedStage.py +StageBossConfrontedMsg = "%s está lutando com o Funcionário!" +stageToonEnterElevator = "%s \nentrou no elevador" +ForcedLeaveStageAckMsg = "O Funcionário da Lei foi derrotado antes de você alcançá-lo. Você não recuperou nenhum Aviso de Júri." + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Aguardando outros jogadores..." +MinigamePleaseWait = "Aguarde..." +DefaultMinigameTitle = "Título do minijogo" +DefaultMinigameInstructions = "Instruções do minijogo" +HeadingToMinigameTitle = "Dirigindo-se a %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Medidor de potência" +MinigamePowerMeterTooSlow = "Muito\ndevagar" +MinigamePowerMeterTooFast = "Muito\nrápido" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Modelo de minijogo" +MinigameTemplateInstructions = "Este é um modelo de minijogo. Use-o para criar novos minijogos." + +# DistributedCannonGame.py +CannonGameTitle = "Jogo do canhão" +CannonGameInstructions = "Atire o seu Toon na torre de água o mais rápido que puder. Use o mouse ou as teclas de seta para mirar o canhão. Seja rápido e ganhe uma grande recompensa para todos!" +CannonGameReward = "RECOMPENSA" + +# DistributedTwoDGame.py +TwoDGameTitle = "Fuga dos Cartoons" +TwoDGameInstructions = "Fuja dos " + Cog + " o mais rápido que você puder. Use as setas para correr/pular e Ctrl para esguichar " + Cog + ". Colete " + Cog + " tesouros para ganhar mais pontos." +TwoDGameElevatorExit = "SAÍDA" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Cabo de guerra" +TugOfWarInstructions = "Toque alternadamente nas teclas de seta para a esquerda e para a direita rápido o suficiente para alinhar a barra verde com a linha vermelha. Não toque nelas muito devagar, ou você acabará na água!" +TugOfWarGameGo = "COMEÇAR!" +TugOfWarGameReady = "Pronto..." +TugOfWarGameEnd = "Bom jogo!" +TugOfWarGameTie = "Você empatou!" +TugOfWarPowerMeter = "Medidor" + +# DistributedPatternGame.py +PatternGameTitle = "Acompanhe a "+ Minnie +PatternGameInstructions = "A " + Minnie + " mostrará uma sequência de dança." + \ + "Tente repetir a dança da "+ Minnie +" exatamente como você vê usando as teclas de seta!" +PatternGameWatch = "Observe estes passos de dança..." +PatternGameGo = "COMEÇAR!" +PatternGameRight = "Bom, %s!" +PatternGameWrong = "Ops!" +PatternGamePerfect = "Perfeito, %s!" +PatternGameBye = "Obrigado por jogar!" +PatternGameWaitingOtherPlayers = "Aguardando outros jogadores..." +PatternGamePleaseWait = "Aguarde..." +PatternGameFaster = "Você foi\nmais rápido!" +PatternGameFastest = "Você foi o\nmais rápido!" +PatternGameYouCanDoIt = "Deixa disso!\nVocê consegue!" +PatternGameOtherFaster = "\nfoi mais rápido!" +PatternGameOtherFastest = "\nfoi o mais rápido!" +PatternGameGreatJob = "Muito bom!" +PatternGameRound = "Rodada %s!" # Round 1! Round 2! .. +PatternGameImprov = "You did great! Now Improv!" + +# DistributedRaceGame.py +RaceGameTitle = "Jogo de corrida" +RaceGameInstructions = "Clique em um número. Escolha bem! Você só avançará se ninguém mais escolher o mesmo número." +RaceGameWaitingChoices = "Esperando os outros jogadores escolherem..." +RaceGameCardText = "%(name)s aposta: %(reward)s" +RaceGameCardTextBeans = "%(name)s recebe: %(reward)s" +RaceGameCardTextHi1 = "%(name)s é um Toon fabuloso!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " avança 1 espaço" +RaceGameForwardTwoSpaces = " avança 2 espaços" +RaceGameForwardThreeSpaces = " avança 3 espaços" +RaceGameBackOneSpace = " recua 1 espaço" +RaceGameBackTwoSpaces = " recua 2 espaços" +RaceGameBackThreeSpaces = " recua 3 espaços" +RaceGameOthersForwardThree = " todos os outros avançam \n3 espaços" +RaceGameOthersBackThree = "todos os outros recuam \n3 espaços" +RaceGameInstantWinner = "Vencedor imediato!" +RaceGameJellybeans2 = "2 balinhas" +RaceGameJellybeans4 = "4 balinhas" +RaceGameJellybeans10 = "10 balinhas!" + +# DistributedRingGame.py +RingGameTitle = "Jogo dos anéis" +# color +RingGameInstructionsSinglePlayer = "Tente nadar através do número máximo de anéis %s que conseguir. Para nadar, use as teclas de seta." +# color +RingGameInstructionsMultiPlayer = "Tente nadar através dos anéis %s. Os outros jogadores tentarão nadar através dos outros anéis coloridos. Para nadar, use as teclas de seta." +RingGameMissed = "PERDEU" +RingGameGroupPerfect = "GRUPO\nPERFEITO!!" +RingGamePerfect = "PERFEITO!" +RingGameGroupBonus = "BÔNUS DO GRUPO" + +# RingGameGlobals.py +ColorRed = "vermelhos" +ColorGreen = "verdes" +ColorOrange = "laranja" +ColorPurple = "lilases" +ColorWhite = "brancos" +ColorBlack = "pretos" +ColorYellow = "amarelos" + +# DistributedDivingGame.py #localize +DivingGameTitle = "Mergulho pro Tesouro" +# color +DivingInstructionsSinglePlayer = "Tesouros irão aparecer no fundo do lago. Use as setas para nadar. Evite os peixes e leve os tesouros para o barco!" +# color +DivingInstructionsMultiPlayer = " Tesouros irão aparecer no fundo do lago. Use as setas para nadar. Trabalhem juntos para levar os tesouros para o barco!" +DivingGameTreasuresRetrieved = "Tesouros Recuperados" + +#Distributed Target Game +TargetGameTitle = "Estilingue do Toon" +TargetGameInstructionsSinglePlayer = "Acerta na velocidade do alvo" +TargetGameInstructionsMultiPlayer = "Acerta quantos alvos conseguir" +TargetGameBoard = "Rodada %s - Mantendo o Melhor Placar" +TargetGameCountdown = "Lançamento forçado em %s segundos" +TargetGameCountHelp = "Bata nas setas esquerda e direita para conseguir potência, pare para lançar" +TargetGameFlyHelp = "Aperte para baixo para abrir o guarda-chuva" +TargetGameFallHelp = "Use as teclas de seta para aterrissar no alvo" +TargetGameBounceHelp = " Bater e quicar pode tirar você do alvo" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nVocê: %s" +PhotoGameScoreBlank = "Placar: %s" +PhotoGameScoreOther = "\n%s"#"Placar: %s\n%s" +PhotoGameScoreYou = "\nMelhor Bônus!"#"Placar: %s\nMelhor Bônus!" + + +# DistributedTagGame.py +TagGameTitle = "Jogo de pique" +TagGameInstructions = "Pegue os tesouros. Você não pode pegar os tesouros se o pique estiver com você!" +TagGameYouAreIt = "Está com você!" +TagGameSomeoneElseIsIt = "Está com %s!" + +# DistributedMazeGame.py +MazeGameTitle = "Jogo do labirinto" +MazeGameInstructions = "Pegue os tesouros. Tente pegar todos, mas cuidado com os "+ Cogs +"!"# DistributedCatchGame.py + +# DistributedCatchGame.py +CatchGameTitle = "Jogo de pegar" +CatchGameInstructions = "Pegue o máximo de %(fruit)s que conseguir. Cuidado com os "+ Cogs +" e tente não 'pegar' nenhuma %(badThing)s!" +CatchGamePerfect = "PERFEITO!" +CatchGameApples = 'maçãs' +CatchGameOranges = 'laranjas' +CatchGamePears = 'pêras' +CatchGameCoconuts = 'cocos' +CatchGameWatermelons = 'melancias' +CatchGamePineapples = 'abacaxis' +CatchGameAnvils = 'bigornas' + +# DistributedPieTossGame.py +PieTossGameTitle = "Jogo de lançamento de tortas" +PieTossGameInstructions = "Lance as tortas nos alvos." + +# DistributedPhotoGame.py +PhotoGameInstructions = "Tire fotos de acordo com os Toons mostrados na parte de baixo. Mire a câmera usando o mouse, e clique com o botão esquerdo para tirar uma foto. Aperte Ctrl para aumentar ou reduzir o zoom, e olhe em sua volta com as teclas de seta. Fotos com notas maiores ganham mais pontos!" +PhotoGameTitle = "Diversão Fotográfica" +PhotoGameFilm = "FILME" +PhotoGameScore = "Placar da Equipe: %s\n\nMelhores Fotos: %s\n\nPlacar Total: %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + " Ladrão" +CogThiefGameInstructions = "Impeça que os " + Cogs + " roubem nossos barris! Aperte a tecla Ctrl para atirar uma torta. Use as teclas de seta para se mover. Dica: você pode andar nas diagonais." +CogThiefBarrelsSaved = "%(num)d Barris\nSalvos!" +CogThiefBarrelSaved = "%(num)d Barril\nSalvo!" +CogThiefNoBarrelsSaved = "Nenhum Barril\nSalvo" +CogThiefPerfect = "PERFEITO!!" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JOGAR" + +# Purchase.py +GagShopName = "Loja de Piadas do Pateta" +GagShopPlayAgain = "JOGAR\nNOVAMENTE" +GagShopBackToPlayground = "SAIR DE NOVO \nPARA O PÁTIO" +GagShopYouHave = "Você tem %s balinhas para gastar" +GagShopYouHaveOne = "Você tem 1 balinha para gastar" +GagShopTooManyProps = "Sinto muito, você tem muitos acessórios" +GagShopDoneShopping = "FIM DAS\nCOMPRAS" +# name of a gag +GagShopTooManyOfThatGag = "Sinto muito, você já tem %s o suficiente" +GagShopInsufficientSkill = "Você não tem muita habilidade para isso ainda" +# name of a gag +GagShopYouPurchased = "Você comprou %s" +GagShopOutOfJellybeans = "Sinto muito, você não tem mais balinhas!" +GagShopWaitingOtherPlayers = "Aguardando outros jogadores..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s desconectou-se" +GagShopPlayerExited = "%s saiu" +GagShopPlayerPlayAgain = "Jogar novamente" +GagShopPlayerBuying = "Comprando" + +# MakeAToon.py +GenderShopQuestionMickey = "Para criar um Toon menino, clique em mim!" +GenderShopQuestionMinnie = "Para criar um Toon menina, clique em mim!" +GenderShopFollow = "Siga-me!" +GenderShopSeeYou = "Vejo você depois!" +GenderShopBoyButtonText = "Menino" +GenderShopGirlButtonText = "Menina" + +# BodyShop.py +BodyShopHead = "Cabeça" +BodyShopBody = "Corpo" +BodyShopLegs = "Pernas" + +# ColorShop.py +ColorShopHead = "Cabeça" +ColorShopBody = "Corpo" +ColorShopLegs = "Pernas" +ColorShopToon = "Toon" +ColorShopParts = "Partes" +ColorShopAll = "Tudo" + +# ClothesShop.py +ClothesShopShorts = "Short" +ClothesShopShirt = "Camisa" +ClothesShopBottoms = "Parte de baixo" + +# MakeAToon +PromptTutorial = "Parabéns!\nVocê é o(a) mais recente morador(a) de Toontown!\n\nDeseja continuar com o Toontorial ou teletransportar-se diretamente para o Centro de Toontown?" +MakeAToonSkipTutorial = "Pular Toontorial" +MakeAToonEnterTutorial = "Acessar Toontorial" +MakeAToonDone = lOK +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = lBack +CreateYourToon = "Clique nas setas para criar o seu Toon." +CreateYourToonTitle = "Crie o seu Toon" +ShapeYourToonTitle = "Selecione o Tipo" +PaintYourToonTitle = "Selecione a Cor" +PickClothesTitle = "Selecione as Roupas" +NameToonTitle = "Selecione o Nome" +CreateYourToonHead = "Clique nas setas da 'cabeça' para escolher animais diferentes." +MakeAToonClickForNextScreen = "Clique na seta abaixo para ir até a próxima tela." +PickClothes = "Clique nas setas para escolher roupas!" +PaintYourToon = "Clique nas setas para pintar o seu toon!" +MakeAToonYouCanGoBack = "Você pode voltar para alterar o corpo também!" +MakeAFunnyName = "Escolha um nome engraçado para o seu Toon com o jogo Escolha um nome!" +MustHaveAFirstOrLast1 = "O seu Toon deve ter um nome ou um sobrenome, não é?" +MustHaveAFirstOrLast2 = "Você não quer que o seu Toon tenha um nome ou um sobrenome?" +ApprovalForName1 = "É isso aí, o seu Toon merece um nome muito legal!" +ApprovalForName2 = "Os nomes de Toons são os nomes mais legais que existem!" +MakeAToonLastStep = "Última etapa antes de ir para Toontown!" +PickANameYouLike = "Escolha o nome que quiser!" +TitleCheckBox = "Título" +FirstCheckBox = "Primeiro" +LastCheckBox = "Último" +RandomButton = "Aleatório" +ShuffleButton = "Misturar" +NameShopSubmitButton = "Enviar" +TypeANameButton = "Digite um nome" +TypeAName = "Não gostou destes nomes?\nClique aqui -->" +PickAName = "Tente usar o jogo Escolha um nome!\nClique aqui -->" +PickANameButton = "Escolha um nome" +RejectNameText = "Este nome não é permitido. Tente novamente." +WaitingForNameSubmission = "Enviando o seu nome..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_portuguese.txt" +PetshopUnknownName = "Nome: ???" +PetshopDescGender = "Sexo:\t%s" +PetshopDescCost = "Custo:\t%s balinhas" +PetshopDescTrait = "Características:\t%s" +PetshopDescStandard = "Padrão" +PetshopCancel = lCancel +PetshopSell = "Vender peixes" +PetshopAdoptAPet = "Adotar um Rabisco" +PetshopReturnPet = "Devolver o Rabisco" +PetshopAdoptConfirm = "Adotar %s por %d balinhas?" +PetshopGoBack = "Voltar" +PetshopAdopt = "Adotar" +PetshopReturnConfirm = "Devolver %s?" +PetshopReturn = "Devolver" +PetshopChooserTitle = "RABISCOS DE HOJE" +PetshopGoHomeText = 'Deseja ir à sua propriedade para brincar com seu novo Rabisco?' + +# NameShop.py +NameShopNameMaster = "NameMaster_portuguese.txt" +NameShopPay = "Assine já!" +NameShopPlay = "Avaliação gratuita" +NameShopOnlyPaid = "Somente usuários pagantes\npodem dar nomes aos seus Toons.\nAté que você se inscreva,\nseu nome será\n" +NameShopContinueSubmission = "Continuar envio" +NameShopChooseAnother = "Escolha outro nome" +NameShopToonCouncil = "O Conselho de Toons\nanalisará o seu\nnome."+ \ + "A análise pode\nlevar alguns dias.\nEnquanto você espera,\nseu nome será\n" +PleaseTypeName = "Digite o seu nome:" +AllNewNames = "Todos os novos nomes\ndevem ser aprovados\npelo Conselho de Toons." +NameMessages = "Use sua criatividade e lembre-se:\nnada de nomes relacionados com a Disney, por favor." +NameShopNameRejected = "O nome\nenviado foi\nrejeitado." +NameShopNameAccepted = "Parabéns!\nO nome\nenviado foi\naceito!" +NoPunctuation = "Não é permitido usar caracteres de pontuação nos nomes!" +PeriodOnlyAfterLetter = "Você pode usar um ponto no nome, mas apenas depois de uma letra." +ApostropheOnlyAfterLetter = "Você pode usar um apóstrofo no nome, mas apenas depois de uma letra." +NoNumbersInTheMiddle = "Dígitos numéricos podem não aparecer no meio da palavra." +ThreeWordsOrLess = "Seu nome deve ter três palavras ou menos." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "donald duck", + "donaldduck", + "pato donald", + "patodonald", + "pluto", + "goofy", + "pateta", + ) +NumToColor = ['Branco', 'Pêssego', 'Vermelho vivo', 'Vermelho', 'Castanho', + 'Siena', 'Marrom', 'Canela', 'Coral', 'Laranja', + 'Amarelo', 'Creme', 'Cítrico', 'Limão', 'Verde-água', + 'Verde', 'Azul-claro', 'Verde-azul', 'Azul', + 'Verde-musgo', 'Azul-turquesa', 'Azul cinzento', 'Lilás', + 'Púrpura', 'Rosa'] +AnimalToSpecies = { + 'dog' : 'Cachorro', + 'cat' : 'Gato', + 'mouse' : 'Rato', + 'horse' : 'Cavalo', + 'rabbit' : 'Coelho', + 'duck' : 'Pato', + 'monkey' : 'Macaco', + 'bear' : 'Urso', + 'pig' : 'Porco' + } +NameTooLong = "Este nome é muito longo. Tente novamente." +ToonAlreadyExists = "Você já tem um Toon com o nome %s!" +NameAlreadyInUse = "Este nome já foi usado!" +EmptyNameError = "Você deve primeiramente inserir um nome." +NameError = "Sinto muito. Este nome não vai funcionar." + +# NameCheck.py +NCTooShort = 'Este nome é muito curto.' +NCNoDigits = 'O nome não pode conter números.' +NCNeedLetters = 'Cada palavra do nome deve conter algumas letras.' +NCNeedVowels = 'Cada palavra do nome deve conter algumas vogais.' +NCAllCaps = 'O seu nome não pode estar todo em maiúsculas.' +NCMixedCase = 'Este nome tem muitas letras em maiúsculas.' +NCBadCharacter = "O seu nome não pode conter o caractere '%s'" +NCGeneric = 'Sinto muito, este nome não vai funcionar.' +NCTooManyWords = 'O seu nome não pode ter mais de quatro palavras.' +NCDashUsage = ("Hífens podem ser usados apenas para ligar duas palavras" + "(como em 'Bu-Bu').") +NCCommaEdge = "O seu nome não pode começar ou terminar com vírgula." +NCCommaAfterWord = "Você não pode começar uma palavra com vírgula." +NCCommaUsage = ('Este nome não usa vírgulas corretamente. As vírgulas devem' + 'juntar duas palavras, como no nome "Dr. Quack, MD".' + 'As vírgulas devem também ser seguidas por um espaço.') +NCPeriodUsage = ('Este nome não usa pontos corretamente. Os pontos são' + 'permitidos somente em palavras como "Sr.", "Sra.", "J.P.", etc.') +NCApostrophes = 'Este nome tem excesso de apóstrofos.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = "Quartel dos Toons: Os "+ Cogs +" dominaram um dos edifícios que você salvou!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Precisa de mais tempo para pensar?' +STOREOWNER_GOODBYE = 'Vejo você depois!' +STOREOWNER_NEEDJELLYBEANS = 'Você precisa pegar o bondinho para conseguir algumas balinhas.' +STOREOWNER_GREETING = 'Escolha o que deseja comprar.' +STOREOWNER_BROWSING = 'Você pode olhar, mas precisará de um bilhete de roupas para comprar.' +STOREOWNER_NOCLOTHINGTICKET = 'Para comprar roupas, você precisa de um bilhete de roupas.' + +STOREOWNER_NOFISH = 'Volte aqui para vender peixes para a loja de animais e ganhar balinhas.' +STOREOWNER_THANKSFISH = 'Valeu! A loja de animais vai adorar estes aqui. Tchau!' +STOREOWNER_THANKSFISH_PETSHOP = "Estes tipos são raros! Valeu." +STOREOWNER_PETRETURNED = "Não se preocupe. Acharemos um bom lar para o seu Rabisco." +STOREOWNER_PETADOPTED = "Parabéns pelo novo Rabisco! Você pode brincar com ele em sua propriedade." +STOREOWNER_PETCANCELED = "Lembre-se, caso veja um Rabisco de seu agrado, adote-o antes que alguém o faça!" + +STOREOWNER_NOROOM = "Hmm... Você pode precisar arranjar espaço no seu armário antes de comprar roupas novas.\n" +STOREOWNER_CONFIRM_LOSS = "O seu armário está cheio. Você vai perder as roupas que estava vestindo." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Uau! Você pegou %s de %s peixe. Merece um troféu e um Acréscimo de risadas!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": Foi iniciada uma Invasão de Cogs!!!" +SuitInvasionBegin2 = lToonHQ+": %s dominaram Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": A Invasão de %s terminou!!!" +SuitInvasionEnd2 = lToonHQ+": Mais uma vez os Toons salvaram a pátria!!!" +SuitInvasionUpdate1 = lToonHQ+": A Invasão de Cogs está agora em %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": Precisamos derrotar esses %s!!!" +SuitInvasionBulletin1 = lToonHQ+": Há uma Invasão de Cogs em andamento!!!" +SuitInvasionBulletin2 = lToonHQ+": %s dominaram Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Pelotão Toon" + +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown ganhou um novo cidadão! Você tem piadas de reserva?" +QuestScriptTutorialMickey_2 = "Claro, %s!" +QuestScriptTutorialMickey_3 = "O Tutorial Tom vai contar para você tudo sobre os Cogs.\aTchauzinho!" +QuestScriptTutorialMickey_4 = "Vem cá! Use as teclas de seta para mover-se." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown ganhou um novo cidadão! Você tem piadas de reserva?" +QuestScriptTutorialMinnie_2 = "Claro, %s!" +QuestScriptTutorialMinnie_3 = "O Tutorial Tom vai contar para você tudo sobre os Cogs.\aTchauzinho!" + +QuestScript101_1 = "Estes são os COGS. Eles são robôs que estão tentando dominar Toontown." +QuestScript101_2 = "Há vários tipos diferentes de COGS e..." +QuestScript101_3 = "...eles transformam os alegres edifícios dos Toons..." +QuestScript101_4 = "...em horríveis edifícios de Cogs!" +QuestScript101_5 = "Mas os COGS não aguentam piadas!" +QuestScript101_6 = "Uma boa piada os deterá." +QuestScript101_7 = "Há milhares de piadas, mas, para começar, use estas aqui." +QuestScript101_8 = "Ah! Você também vai precisar de um Risômetro!" +QuestScript101_9 = "Se o seu Risômetro estiver baixo, é porque você está triste!" +QuestScript101_10 = "Um Toon feliz é um Toon saudável!" +QuestScript101_11 = "OH NÃO! Há um COG na porta da minha loja!" +QuestScript101_12 = "AJUDE-ME, POR FAVOR! Derrote este COG!" +QuestScript101_13 = "Esta é a sua primeira Tarefa Toon!" +QuestScript101_14 = "Vamos nessa! Vá derrotar aquele Puxa-saco!" + +QuestScript110_1 = "Bom trabalho; você derrotou aquele Puxa-saco. Deixe-me dar a você um Álbum Toon..." +QuestScript110_2 = "O livro é cheio de coisas legais." +QuestScript110_3 = "Abra-o para eu mostrar a você." +QuestScript110_4 = "O mapa mostra o local onde você esteve." +QuestScript110_5 = "Vire a página para ver as suas piadas..." +QuestScript110_6 = "Êpa! Você não tem nenhuma piada! Vou passar uma tarefa para você." +QuestScript110_7 = "Vire a página para ver as suas tarefas." +QuestScript110_8 = "Dê uma volta no bondinho para ganhar balinhas e poder comprar piadas!" +QuestScript110_9 = "Para ir até o bondinho, saia pela porta logo atrás de mim e siga até o pátio." +QuestScript110_10 = "Agora, feche o livro e encontre o bondinho!" +QuestScript110_11 = "Volte para o Quartel dos Toons quando já estiver pronto. Tchau!" + +QuestScriptTutorialBlocker_1 = "Oi, e aí, pessoal?" +QuestScriptTutorialBlocker_2 = "Alô?" +QuestScriptTutorialBlocker_3 = "Ah! Você não sabe usar o Chat rápido!" +QuestScriptTutorialBlocker_4 = "Clique no botão para dizer algo." +QuestScriptTutorialBlocker_5 = "Muito bom!\aO local para onde você está indo tem um monte de Toons para conversar." +QuestScriptTutorialBlocker_6 = "Se você quiser conversar com seus amigos usando o teclado, há um outro botão que pode ser usado." +QuestScriptTutorialBlocker_7 = "Ele se chama botão \"Conversar\". Você precisa ser um cidadão oficial de Toontown para usá-lo." +QuestScriptTutorialBlocker_8 = "Boa sorte! Vejo você depois!" + +""" +GagShopTut + +You will also earn the ability to use other types of gags. + +""" + +QuestScriptGagShop_1 = "Bem-vindo à Loja de Piadas!" +QuestScriptGagShop_1a = "Aqui é o lugar onde os Toons vêm comprar piadas para usar contra os Cogs." +#QuestScriptGagShop_2 = "Este pote mostra quantas balinhas você tem." +#QuestScriptGagShop_3 = "Para comprar piadas, clique em botões de piada. Tente pegar umas agora!" +QuestScriptGagShop_3 = "Para comprar piadas, clique em botões de piada. Tente pegar algumas agora!" +QuestScriptGagShop_4 = "Bom! Você pode usar estas piadas nas batalhas contra os Cogs." +QuestScriptGagShop_5 = "Dê uma olhada para ver como são as piadas avançadas de jogar e de esguichar..." +QuestScriptGagShop_6 = "Depois que terminar de comprar piadas, clique neste botão para retornar ao Pátio." +QuestScriptGagShop_7 = "Normalmente, você pode usar este botão para participar de outro Jogo no Bondinho..." +QuestScriptGagShop_8 = "...Mas não há tempo para outro jogo agora. Estão precisando de você no Quartel dos Toons!" + +QuestScript120_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, você encontrou o Banqueiro Beto?\aEle é bem guloso por doces.\aPor que você não se apresenta e leva para ele este chocolate de presente." +QuestScript120_2 = "O Banqueiro Beto está lá no Banco de Toontown." + +QuestScript121_1 = "Mmm, obrigado pelo chocolate.\aOlha só, se você me ajudar, eu dou a você uma recompensa.\aEsses Cogs roubaram as chaves do meu cofre. Derrote os Cogs para encontrar a chave roubada.\aQuando você encontra-la, traga-a para mim." + +QuestScript130_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, recebi hoje um pacote para o Professor Paulo.\aDeve ser o novo giz que ele encomendou.\aVocê pode, por favor, levar para ele?\aEle está lá na escola." + +QuestScript131_1 = "Ah, obrigado pelo giz.\aO quê?!?\aEsses Cogs roubaram meu quadro-negro. Derrote os Cogs para encontrar meu quadro-negro roubado.\aQuando encontrá-lo, traga de volta para mim." + +QuestScript140_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, tenho um amigo, o Bibliotecário Bino, que é uma verdadeira traça de livros.\aPeguei este livro para ele da última vez em que estive no Porto do Donald.\aVocê podia levar para ele? Em geral, ele fica na Biblioteca." + +QuestScript141_1 = "Ah, sim, este livro vai quase completar a minha coleção.\aDeixe-me ver...\aÊpa...\aOnde é que eu pus os meus óculos agora?\aEu estava com eles um pouco antes de aqueles Cogs invadirem o meu edifício.\aDerrote os Cogs para encontrar meus óculos roubados.\aaQuando encontrá-los, traga de volta para mim para ganhar uma recompensa." + +QuestScript145_1 = "Estou vendo que você não teve problemas com o bondinho!\a Olha só, os Cogs roubaram o apagador do nosso quadro-negro.\a Vá para as ruas e lute com os Cogs até recuperar o apagador.\a Para encontrar as ruas, passe por um dos túneis como este:" +QuestScript145_2 = "Quando encontrar nosso apagador, traga-o de volta para cá.\aNão se esqueça, se precisar de piadas, pegue o bondinho.\aE se você precisar recuperar Pontos de risadas, colete casquinhas de sorvete no Pátio." + +QuestScript150_1 = "Ah... Esta próxima tarefa talvez seja muito difícil para você executar sozinho!" +QuestScript150_2 = "Para fazer amigos, encontre outro jogador e use o botão Novo amigo." +QuestScript150_3 = "Depois que você tiver arrumado um amigo, volte aqui." +QuestScript150_4 = "Algumas tarefas são muito difíceis de serem executadas sem ajuda!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore-me" + +SellbotBossName = "V. P. Sênior" +CashbotBossName = "Diretor Financeiro" +LawbotBossName = "Juiz-chefe" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "Com isto, você está promovido a %s sênior. Parabéns!" +BossCogDoobersAway = { 's' : "Vai! E faça essa venda!" } +BossCogWelcomeToons = "Bem-vindos, novos Cogs!" +BossCogPromoteToons = "Com isto, você está promovido a %s sênior. Parab--" +CagedToonInterruptBoss = "Oi! Uhuu! E aí pessoal!" +CagedToonRescueQuery = "Então, galera de Toons, vocês vêm me salvar?" +BossCogDiscoverToons = "Hã? Toons! Disfarçar!" +BossCogAttackToons = "Atacar!!" +CagedToonDrop = [ + "Bom trabalho! Ele está ficando exausto!", + "Fique atrás dele! Ele está fugindo!", + "Pessoal, vocês estão se saindo muito bem!", + "Fantástico! Você quase o pegou agora!", + ] +CagedToonPrepareBattleTwo = "Cuidado, ele está tentando escapar!\aAjudem-me todos! Levantem-se aqui e detenham-no!" +CagedToonPrepareBattleThree = "Maneiro! Estou quase livre!\aAgora, você precisa atacar o Cog V. P. em pessoa.\aTenho um montão de tortas que você pode usar!\aPule e toque na parte inferior da minha cela para que eu lhe dê algumas tortas.\aPressione a tecla Insert para jogar as tortas quando você as pegar!" +BossBattleNeedMorePies = "Você precisa de mais tortas!" +BossBattleHowToGetPies = "Pule para tocar na cela e pegar mais tortas." +BossBattleHowToThrowPies = "Pressione a tecla Insert para jogar tortas!" +CagedToonYippee = "Iupii!!" +CagedToonThankYou = "É ótimo estar livre!\aObrigado por toda a sua ajuda!\aTe devo esta.\aSe, por acaso, você estiver em apuros em alguma batalha, é só me chamar!\aBasta clicar no botão SOS para me chamar." +CagedToonPromotion = "\aOlha só! Aquele Cog V.P. acabou deixando aqui os seus documentos de promoção.\aVou arquivá-los para você na saída, para que pegue a promoção!" +CagedToonLastPromotion = "\aUau, você atingiu o nível %s no processo Cog!\aOs Cogs não têm promoção maior do que esta.\aVocê não pode mais subir no processo Cog, mas certamente pode continuar salvando os Toons!" +CagedToonHPBoost = "\aVocê salvou um monte de Toons neste quartel.\aO Conselho de Toons decidiu dar a você outro Ponto de risadas. Parabéns!" +CagedToonMaxed = "\aVi que você tem o nível %s no processo Cog. Impressionante!\aEm nome do Conselho de Toons, agradeço por retornar para salvar mais Toons!" +CagedToonGoodbye = "Te vejo por aí!" + + +CagedToonBattleThree = { + 10: "Belo salto, %(toon)s. Tome aqui algumas tortas!", + 11: "Oi, %(toon)s! Pegue algumas tortas!", + 12: "E aí, %(toon)s? Agora, você tem algumas tortas!", + + 20: "Olá, %(toon)s! Pule até a minha cela e pegue algumas tortas para jogar!", + 21: "Oi, %(toon)s! Use a tecla Ctrl para pular e tocar a minha cela!", + + 100: "Pressione a tecla Insert para jogar uma torta.", + 101: "O medidor de potência azul mostra a altura que a sua torta atinge.", + 102: "Primeiramente, tente jogar uma torta dentro da lataria dele para melecar seus mecanismos.", + 103: "Espere até que a porta se abra para jogar uma torta bem lá dentro.", + 104: "Quando ele estiver tonto, bata na cara ou no peito dele para empurrá-lo para trás!", + 105: "Você saberá se o seu golpe foi bom quando vir o chão colorido.", + 106: "Se você atingir um Toon com uma torta, ele ganhará um Ponto de risadas!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "É isso aí! Chega desses Toons irritantes!" +CashbotBossOuttaHere = "Tenho que pegar o trem!" +ResistanceToonName = "Mata Rara" +ResistanceToonCongratulations = "Você conseguiu! Parabéns!\aVocê é um orgulho para a Resistência!\aEsta é uma frase especial que você pode usar quando estiver em apuros:\a%s\aQuando você a pronunciar, %s.\aMas só pode usar uma vez, portanto, escolha a hora certa!" +ResistanceToonToonupInstructions = "todos os Toons próximos a você ganham %s pontos de risadas" +ResistanceToonToonupAllInstructions = "todos os Toons próximos a você ganham pontos de risadas completos" +ResistanceToonMoneyInstructions = "todos os Toons próximos a você ganham %s balinhas" +ResistanceToonMoneyAllInstructions = "todos os Toons próximos a você encherão suas jarras de balinhas" +ResistanceToonRestockInstructions = "todos os Toons próximos a você vão reabastecer suas \"%s\" piadas" +ResistanceToonRestockAllInstructions = "todos os Toons próximos a você vão reabastecer todas as suas piadas" + +ResistanceToonLastPromotion = "\aUau, você atingiu o nível %s no processo Cog!\aOs Cogs não têm promoção maior do que esta.\aVocê não pode mais subir no processo Cog, mas, certamente, pode continuar trabalhando para a Resistência!" +ResistanceToonHPBoost = "\aVocê trabalhou muito para a Resistência.\aO Conselho de Toons decidiu dar a você outro Ponto de risadas. Parabéns!" +ResistanceToonMaxed = "\aVejo que você tem o nível %s no processo Cog. Impressionante!\aEm nome do Conselho de Toons, agradeço por retornar para salvar mais Toons!" + +CashbotBossCogAttack = "Peguem-nos!!!" +ResistanceToonWelcome = "Você conseguiu! Siga-me até o cofre-forte antes que o Diretor Financeiro nos ache!" +ResistanceToonTooLate = "Droga! Estamos atrasados demais!" +CashbotBossDiscoverToons1 = "Ah-HAH!" +CashbotBossDiscoverToons2 = "Pensei ter farejado um tooninho por aqui! Impostores!" +ResistanceToonKeepHimBusy = "Mantenha-o ocupado! Vou montar uma armadilha!" +ResistanceToonWatchThis = "Olha isso!" +CashbotBossGetAwayFromThat = "Ei! Afaste-se!" +ResistanceToonCraneInstructions1 = "Controle um ímã subindo no pódio." +ResistanceToonCraneInstructions2 = "Use as teclas de setas para mover o guindaste e pressione a tecla Ctrl para pegar um objeto." +ResistanceToonCraneInstructions3 = "Pegue um cofre com o ímã e arranque o capacete de segurança do Diretor Financeiro." +ResistanceToonCraneInstructions4 = "Depois de fazer zunir o capacete, pegue um brutamontes desativado e dê uma pancada na cabeça dele!" +ResistanceToonGetaway = "Ih! Tenho que correr!" +CashbotCraneLeave = "Deixar o guindaste" +CashbotCraneAdvice = "Use as teclas de setas para mover o guindaste de pórtico." +CashbotMagnetAdvice = "Mantenha pressionada a tecla Ctrl para pegar os objetos." +CashbotCraneLeaving = "Deixando o guindaste" + +MintElevatorRejectMessage = "Não será possível entrar na Casa da Moeda até que a vestimenta de Cog %s esteja completa." +BossElevatorRejectMessage = "Você não pode pegar este elevador até que tenha recebido uma promoção." +NotYetAvailable = "Este elevador ainda não está disponível." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Mobília" +PaintingTypeName = "Pintura" +ClothingTypeName = "Roupas" +ChatTypeName = "Frase do Chat rápido" +EmoteTypeName = "Aulas de representação" +BeanTypeName = "Balinhas" +PoleTypeName = "Vara de pescar" +WindowViewTypeName = "Vista da janela" +PetTrickTypeName = 'Treinamento de Rabiscos' +GardenTypeName = 'Materiais de Jardim' +RentalTypeName = 'Item de Aluguel' +GardenStarterTypeName = 'Kit de Jardinagem' +NametagTypeName = "Crachá" + + +# Make sure numbers match up to CatalogItemTypes.py +CatalogItemTypeNames = { + 0 : "INVALID_ITEM", + 1 : FurnitureTypeName, + 2 : ChatTypeName, + 3 : ClothingTypeName, + 4 : EmoteTypeName, + 5 : "WALLPAPER_ITEM", + 6 : "WindowViewTypeName", + 7 : "FLOORING_ITEM", + 8 : "MOULDING_ITEM", + 9 : "WAINSCOTING_ITEM", + 10: PoleTypeName, + 11: PetTrickTypeName, + 12: BeanTypeName, + 13: GardenTypeName, + 14: RentalTypeName, + 15: GardenStarterTypeName, + 16: NametagTypeName, + 17: "TOON_STATUE_ITEM", + 18: "ANIMATED_FURNITURE_ITEM", +} + + +# Make sure this is in sync with ToonDNA.ShirtStyles +ShirtStylesDescriptions = { + # ------------------------------------------------------------------------- + # Boy styles + # ------------------------------------------------------------------------- + 'bss1' : "básica", + 'bss2' : "uma listra", + 'bss3' : "colarinho", + 'bss4' : "duas listras", + 'bss5' : "listrada", + 'bss6' : "colarinho com bolso", + 'bss7' : "havaiana", + 'bss8' : "colarinho com 2 bolsos", + 'bss9' : "camisa de boliche", + 'bss10' : "colete (especial)", + 'bss11' : "colarinho com franzidos", + 'bss12' : "camiseta de futebol (especial)", + 'bss13' : "camiseta lightning bolt (especial)", + 'bss14' : "camiseta 19 (especial)", + 'bss15' : "camisa panamá", + + # ------------------------------------------------------------------------- + # Girl styles + # ------------------------------------------------------------------------- + 'gss1' : "básica", + 'gss2' : "uma listra", + 'gss3' : "colarinho", + 'gss4' : "duas listras", + 'gss5' : "colarinho com bolso", + 'gss6' : "estampa de flor", + 'gss7' : "bordado de flor (especial)", + 'gss8' : "colarinho feminino com 2 bolsos ", + 'gss9' : "colete de brim (especial)", + 'gss10' : "camponesa", + 'gss11' : "camponesa com meia listra", + 'gss12' : "camiseta de futebol (especial)", + 'gss13' : "com corações", + 'gss14' : "com estrelas (especial)", + 'gss15' : "com flores", + + # ------------------------------------------------------------------------- + # Special Catalog-only shirts. + # ------------------------------------------------------------------------- + # yellow hooded - Series 1 + 'c_ss1' : "amarela com capuz - Série 1", + 'c_ss2' : "amarela com palmeira - Série 1", + 'c_ss3' : "roxa com estrelas - Série 2", + 'c_bss1' : "listras azuis (masculina) - Série 1", + 'c_bss2' : "laranja (masculina) - Série 1", + 'c_bss3' : "verde-limão com listra (masculina) - Série 2", + 'c_bss4' : "quimono vermelho com xadrez (masculina) - Série 2", + 'c_gss1' : "azul com listras amarelas (feminina) - Série 1", + 'c_gss2' : "rosa e bege com flor (feminina) - Série 1", + 'c_gss3' : "azul e dourado com listras ondulantes (feminina) - Série 2", + 'c_gss4' : "azul e rosa com arco (feminina) - Série 2", + 'c_gss5' : "quimono azul-piscina com listra (feminina) – NÃO USADO", + 'c_ss4' : "Camiseta tingida (unissex) - Série 3", + 'c_ss5' : "azul-claro com azul e listra branca (masculina) - Série 3", + 'c_ss6' : "camisa de vaqueiro 1 : Série 4", + 'c_ss7' : "camisa de vaqueiro 2 : Série 4", + 'c_ss8' : "camisa de vaqueiro 3 : Série 4", + 'c_ss9' : "camisa de vaqueiro 4 : Série 4", + 'c_ss10' : "camisa de vaqueiro 5 : Série 4", + 'c_ss11' : "camisa de vaqueiro 6 : Série 4", + + # Special Holiday-themed shirts. + 'hw_ss1' : "Fantasma de Halloween", + 'hw_ss2' : "Abóbora de Halloween", + 'wh_ss1' : "Feriado de Inverno 1", + 'wh_ss2' : "Feriado de Inverno 2", + 'wh_ss3' : "Feriado de Inverno 3", + 'wh_ss4' : "Feriado de Inverno 4", + + 'vd_ss1' : "Dia dos namorados, rosa com corações vermelhos (feminina)", + 'vd_ss2' : "Dia dos namorados, vermelha com corações brancos", + 'vd_ss3' : "Dia dos namorados, branca com corações alados (masculina)", + 'vd_ss4' : "Dia dos namorados, rosa com corações flamejantes", + 'vd_ss5' : "Dia dos namorados 2009, branca com cupido vermelho", + 'vd_ss6' : "Dia dos namorados 2009, azul com verde e corações vermelhos", + 'sd_ss1' : "Dia de São Patrício, camisa com trevo-de-quatro-folhas", + 'sd_ss2' : "Dia de São Patrício, camisa com pote de ouro", + 'tc_ss1' : "Concurso de Camiseta, Colete de Pesca", + 'tc_ss2' : "Concurso de Camiseta, Aquário", + 'tc_ss3' : "Concurso de Camiseta, Pegada", + 'tc_ss4' : "Concurso de Camiseta, Pegada", + 'tc_ss5' : "Concurso de Camiseta, Shorts de Couro", + 'tc_ss6' : "Concurso de Camiseta, Melancia", + 'tc_ss7' : "Concurso de Camiseta, Camisa de Corrida", + 'j4_ss1' : "Bandeira de 4 de julho", + 'j4_ss2' : "Fogos de Artifício de 4 de julho", + 'c_ss12' : "Catálogo série 7, Verde com botões de amarelos", + 'c_ss13' : "Catálogo série 7, Roxo com flor grande", + + 'pj_ss1' : "Camisa de Pijama de banana azul", + 'pj_ss2' : "Camisa de Pijama de chifre vermelho", + 'pj_ss3' : "Camisa de Pijama de óculos roxos", + + # Special award clothes + 'sa_ss1' : "Camisa Listrada", + 'sa_ss2' : "Camisa de Pesca 1", + 'sa_ss3' : "Camisa de Pesca 2", + 'sa_ss4' : "Camisa de Jardinagem 1", + 'sa_ss5' : "Camisa de Jardinagem 2", + 'sa_ss6' : "Camisa de Festa 1", + 'sa_ss7' : "Camisa de Festa 2", + 'sa_ss8' : "Camisa de Corrida 1", + 'sa_ss9' : "Camisa de Corrida 2", + 'sa_ss10' : "Camisa de Verão 1", + 'sa_ss11' : "Camisa de Verão 2", + + # name : [ shirtIdx, sleeveIdx, [(ShirtColorIdx, sleeveColorIdx), ... ]] + } + +# Make sure this is in sync with ToonDNA.BottomStyles +BottomStylesDescriptions = { + # name : [ bottomIdx, [bottomColorIdx, ...]] + # ------------------------------------------------------------------------- + # Boy styles (shorts) + # ------------------------------------------------------------------------- + 'bbs1' : "básico com bolsos", + 'bbs2' : "cinto", + 'bbs3' : "cargo", + 'bbs4' : "havaiano", + 'bbs5' : "listras laterais (especial)", + 'bbs6' : "shorts de futebol", + 'bbs7' : "chamas laterais (especial)", + 'bbs8' : "brim", + 'vd_bs1' : "Shorts de dia dos namorados", + 'vd_bs2' : "Verde com coração vermelho", + 'vd_bs3' : "Brim azul com coração verde e vermelho", + + # Catalog only shorts + 'c_bs1' : "Laranja com listras laterais azuis", + 'c_bs2' : "Azul com listras e pregas douradas", + 'c_bs5' : 'Listras verdes - série 7', + 'sd_bs1' : 'Shorts de Duende de São Patrício', + 'pj_bs1' : 'Calça de Pijama de banana azul', + 'pj_bs2' : 'Calça de Pijama de chifre vermelho', + 'pj_bs3' : 'Calça de Pijama de óculos roxos', + 'wh_bs1' : 'Shorts de Feriado de Inverno Estilo 1', + 'wh_bs2' : 'Shorts de Feriado de Inverno Estilo 2', + 'wh_bs3' : 'Shorts de Feriado de Inverno Estilo 3', + 'wh_bs4' : 'Shorts de Feriado de Inverno Estilo 4', + + # ------------------------------------------------------------------------- + # Girl styles (shorts and skirts) + # ------------------------------------------------------------------------- + # skirts + # ------------------------------------------------------------------------- + 'gsk1' : 'básica', + 'gsk2' : 'bolinhas (especial)', + 'gsk3' : 'listras verticais', + 'gsk4' : 'listra horizontal', + 'gsk5' : 'estampa de flor', + 'gsk6' : '2 bolsos (especial)', + 'gsk7' : 'saia de brim', + + # shorts + # ------------------------------------------------------------------------- + 'gsh1' : 'básico com bolsos', + 'gsh2' : 'florido', + 'gsh3' : 'shorts de brim', + # Special catalog-only skirts and shorts. + 'c_gsk1' : 'saia azul com borda bege e botão ', + 'c_gsk2' : 'saia roxa com rosa e fita', + 'c_gsk3' : 'saia violeta com amarelo e estrela', + + # Valentines skirt + 'vd_gs1' : 'Saia vermelha com corações', + 'vd_gs2' : 'Saia rosa com corações', + 'vd_gs3' : 'Saia de brim azul com coração verde e vermelho', + 'c_gsk4' : 'Saia de arco-íris - Série 3', + 'sd_gs1' : 'Shorts de dia de São Patrício', + 'c_gsk5' : 'Saias de vaqueira 1', + 'c_gsk6' : 'Saias de vaqueira 2', + # Western shorts + 'c_bs3' : 'Shorts de caubói 1', + 'c_bs4' : 'Shorts de caubói 2', + 'j4_bs1' : 'Shorts de 4 de julho', + 'j4_gs1' : 'Saia de 4 de julho', + 'c_gsk7' : 'Azul com flor - série 7', + 'pj_gs1' : 'Calça de Pijama de banana azul', + 'pj_gs2' : 'Calça de Pijama de chifre vermelho', + 'pj_gs3' : 'Calça de Pijama de óculos roxos', + 'wh_gsk1' : 'Saia de Feriado de Inverno Estilo 1', + 'wh_gsk2' : 'Saia de Feriado de Inverno Estilo 2', + 'wh_gsk3' : 'Saia de Feriado de Inverno Estilo 3', + 'wh_gsk4' : 'Saia de Feriado de Inverno Estilo 4', + + 'sa_bs1' : "Shorts de Pesca", + 'sa_bs2' : "Shorts de Jardinagem", + 'sa_bs3' : "Shorts de Festa", + 'sa_bs4' : "Shorts de Corrida", + 'sa_bs5' : "Shorts de Verão", + 'sa_gs1' : "Saia de Pesca", + 'sa_gs2' : "Saia de Jardinagem", + 'sa_gs3' : "Saia de Festa", + 'sa_gs4' : "Saia de Corrida", + 'sa_gs5' : "Saia de Verão", + } + +AwardMgrBoy = "masculino" +AwardMgrGirl = "feminino" +AwardMgrUnisex = "unissex" +AwardMgrShorts = "shorts" +AwardMgrSkirt = "saia" +AwardMgrShirt = "camisa" + +# Special Event Strings to display in mailbox screen +SpecialEventMailboxStrings = { + 1 : "Um item especial do conselho Toon", + 2 : "Prêmio do Torneio de Pesca de Melville", + 3 : "Prêmio do Torneio de Pesca de Billy Bud", + } + +# Rental items" +RentalHours = "Horas de" +RentalOf = "De" +RentalCannon = "Canhões!" +RentalTime = "Horas de" +RentalGameTable = "Mesa de Jogo!" + +EstateCannonGameEnd = "O aluguel do Jogo de Canhão acabou." +GameTableRentalEnd = "O aluguel da Mesa de Jogo acabou." + +MessageConfirmRent = "Iniciar o aluguel? Cancele para guardar o aluguel para depois" +MessageConfirmGarden = "Você quer mesmo iniciar um jardim?" + +#nametag Names +NametagPaid = "Crachá de Cidadão" +NametagAction = "Crachá de Ação" +NametagFrilly = "Crachá Chique" + +FurnitureYourOldCloset = "seu armário velho" +FurnitureYourOldBank = "seu banco velho" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py +FurnitureNames = { + 100 : "Poltrona", + 105 : "Poltrona", + 110 : "Cadeira", + 120 : "Cadeira de escrivaninha", + 130 : "Cadeira de jardim", + 140 : "Cadeira lagosta", + 145 : "Cadeira salva-vidas", + 150 : "Banco de sela", + 160 : "Cadeira nativa", + 170 : "Cadeira-bolinho", + 200 : "Cama", + 205 : "Cama", + 210 : "Cama", + 220 : "Cama banheira", + 230 : "Cama de folhas", + 240 : "Cama-barco", + 250 : "Rede de cáctus", + 260 : "Cama de sorvete", + 270 : "Olivia Erin & Cat's Bed", + 300 : "Pianola", + 310 : "Órgão de tubo", + 400 : "Lareira", + 410 : "Lareira", + 420 : "Lareira redonda", + 430 : "Lareira", + 440 : "Lareira-maçã", + 450 : "Lareira Irlandesa", + 460 : "Lareira Irlandesa Acesa", + 470 : "Lareira Acesa", + 480 : "Lareira Circular Acesa", + 490 : "Lareira Acesa", + 491 : "Lareira Acesa", + 492 : "Lareira em Forma de Maçã Acesa", + 500 : "Armário", + 502 : "Armário com 15 itens", + 504 : "Armário com 20 itens", + 506 : "Armário com 25 itens", + 510 : "Armário", + 512 : "Armário com 15 itens", + 514 : "Armário com 20 itens", + 516 : "Armário com 25 itens", + 600 : "Abajur pequeno", + 610 : "Abajur grande", + 620 : "Abajur de mesa", + 625 : "Abajur de mesa", + 630 : "Abajur da Margarida", + 640 : "Abajur da Margarida", + 650 : "Abajur da Água-viva", + 660 : "Abajur da Água-viva", + 670 : "Abajur do vaqueiro", + 700 : "Cadeira estofada", + 705 : "Cadeira estofada", + 710 : "Sofá", + 715 : "Sofá", + 720 : "Sofá de feno", + 730 : "Sofá-torta", + 800 : "Escrivaninha", + 810 : "Mesinha", + 900 : "Porta-guarda-chuva", + 910 : "Cabideiro", + 920 : "Lata de lixo", + 930 : "Cogumelo vermelho", + 940 : "Cogumelo amarelo", + 950 : "Cabideiro", + 960 : "Mesinha-barril", + 970 : "Planta cáctus", + 980 : "Tenda", + 990 : "O Fan (Leque) de Julieta", + 1000 : "Tapete grande", + 1010 : "Tapete redondo", + 1015 : "Tapete redondo", + 1020 : "Tapete pequeno", + 1030 : "Capacho de folha", + 1100 : "Vitrina", + 1110 : "Vitrina", + 1120 : "Estante alta", + 1130 : "Estante baixa", + 1140 : "Arca-sundae", + 1200 : "Mesinha lateral", + 1210 : "Mesa pequena", + 1215 : "Mesa pequena", + 1220 : "Mesinha de centro", + 1230 : "Mesinha de centro", + 1240 : "Mesa Snorkel", + 1250 : "Mesa-biscoito", + 1260 : "Mesa do quarto", + 1300 : "Banco 1.000 Balas", + 1310 : "Banco 2.500 Balas", + 1320 : "Banco 5.000 Balas", + 1330 : "Banco 7.500 Balas", + 1340 : "Banco 10.000 Balas", + 1399 : "Telefone", + 1400 : "Toon Cezanne", + 1410 : "Flores", + 1420 : "Mickey Moderno", + 1430 : "Toon Rembrandt", + 1440 : "Toonescape", + 1441 : "Cavalo Assobiador", + 1442 : "Estrela Toon", + 1443 : "Não é Torta", + 1450 : "Mickey é Minnie", + 1500 : "Rádio", + 1510 : "Rádio", + 1520 : "Rádio", + 1530 : "Televisão", + 1600 : "Vasinho", + 1610 : "Vaso alto", + 1620 : "Vasinho", + 1630 : "Vaso alto", + 1640 : "Vasinho", + 1650 : "Vasinho", + 1660 : "Vaso Coral", + 1661 : "Vaso de concha", + 1670 : "Rose Vase", + 1680 : "Rose Watercan", + 1700 : "Carrocinha de pipoca", + 1710 : "Joaninha", + 1720 : "Chafariz", + 1725 : "Lavadora de roupa", + 1800 : "Aquário", + 1810 : "Aquário", + 1900 : "Peixe-espada", + 1910 : "Tubarão-martelo", + 1920 : "Chifres de pendurar", + 1930 : "Sombreiro simples", + 1940 : "Sombreiro elegante", + 1950 : "Apanhador de sonhos", + 1960 : "Ferradura", + 1970 : "Retrato de búfalo", + 2000 : "Balanço de doces", + 2010 : "Escorregada de torta", + 3000 : "Banheira banana split", + 10000 : "Moranga", + 10010 : "Abóbora", + 10020 : "Árvore de Natal", + 10030 : "Guirlanda de Natal" + } + +# CatalogClothingItem.py +ClothingArticleNames = ( + "Camisa", + "Camisa", + "Camisa", + "Bermuda", + "Bermuda", + "Saia", + "Bermuda", + ) + +ClothingTypeNames = { + 1400 : "Camisa do Mateus", + 1401 : "Camisa da Jéssica", + 1402 : "Camisa da Marisa", + 1600 : "Traje de Armadilha", + 1601 : "Traje de Som", + 1602 : "Traje de Isca", + 1603 : "Traje de Armadilha", + 1604 : "Traje de Som", + 1605 : "Traje de Isca", + 1606 : "Traje de Armadilha", + 1607 : "Traje de Som", + 1608 : "Traje de Isca", + } + +# CatalogSurfaceItem.py +SurfaceNames = ( + "Papel de parede", + "Moldura do teto", + "Piso", + "Lambri", + "Moldura", + ) + +WallpaperNames = { + 1000 : "Pergaminho", + 1100 : "Milão", + 1200 : "Dover", + 1300 : "Vitória", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Arlequim", + 1700 : "Lua", + 1800 : "Estrelas", + 1900 : "Flores", + 2000 : "Jardim de primavera", + 2100 : "Jardim formal", + 2200 : "Dia de corrida", + 2300 : "Gol!", + 2400 : "Nuvem 9", + 2500 : "Trepadeira", + 2600 : "Primavera", + 2700 : "Boneca japonesa", + 2800 : "Arranjo de flores", + 2900 : "Peixe-anjo", + 3000 : "Bolhas", + 3100 : "Bolhas", + 3200 : "Ir pescar", + 3300 : "Parar de pescar", + 3400 : "Cavalo-marinho", + 3500 : "Conchinhas do mar", + 3600 : "Debaixo d'água", + 3700 : "Botinas", + 3800 : "Cáctus", + 3900 : "Chapéu de vaqueiro", + 10100 : "Gatos", + 10200 : "Morcegos", + 11000 : "Flocos de neve", + 11100 : "Folhas de Natal", + 11200 : "Boneco de neve", + 13000 : "Trevo", + 13100 : "Trevo", + 13200 : "Arco-íris", + 13300 : "Trevo", + } + +FlooringNames = { + 1000 : "Tábua-corrida", + 1010 : "Carpete", + 1020 : "Piso em losangos", + 1030 : "Piso em losangos", + 1040 : "Grama", + 1050 : "Tijolinho bege", + 1060 : "Tijolinho vermelho", + 1070 : "Piso quadrado", + 1080 : "Pedra", + 1090 : "Calçada", + 1100 : "Terra", + 1110 : "Sinteco", + 1120 : "Lajota", + 1130 : "Favo", + 1140 : "Água", + 1150 : "Piso praiano", + 1160 : "Piso praiano", + 1170 : "Piso praiano", + 1180 : "Piso praiano", + 1190 : "Areia", + 10000 : "Cubo de gelo", + 10010 : "Iglu", + 11000 : "Trevo", + 11010 : "Trevo", + } + +MouldingNames = { + 1000 : "Nós", + 1010 : "Pintado", + 1020 : "Dental", + 1030 : "Flores", + 1040 : "Flores", + 1050 : "Joaninha", + } + +WainscotingNames = { + 1000 : "Pintado", + 1010 : "Painel de madeira", + 1020 : "Madeira", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Jardim amplo", + 20 : "Jardim selvagem", + 30 : "Jardim grego", + 40 : "Paisagem urbana", + 50 : "Velho Oeste", + 60 : "Fundo do mar", + 70 : "Ilha tropical", + 80 : "Noite estrelada", + 90 : "Piscina Tiki", + 100 : "Fronteira congelada", + 110 : "Fazenda", + 120 : "Campo Nativo", + 130 : "Rua Principal", + } + +# don't translate yet +NewCatalogNotify = "Há novos itens disponíveis para serem encomendados por telefone!" +NewDeliveryNotify = "Chegou correspondência nova em sua caixa de correio!" +CatalogNotifyFirstCatalog = "Seu primeiro catálogo chegou! Você pode usá-lo para encomendar novos itens para uso pessoal ou para casa." +CatalogNotifyNewCatalog = "O seu catálogo No. %s chegou! Você pode fazer os pedidos dos itens do catálogo pelo telefone." +CatalogNotifyNewCatalogNewDelivery = "Chegou correspondência nova em sua caixa de correio! Além disso, o seu catálogo No. %s chegou!" +CatalogNotifyNewDelivery = "Chegou correspondência nova em sua caixa de correio!" +CatalogNotifyNewCatalogOldDelivery = "O seu catálogo No. %s chegou, e ainda há itens aguardando por você em sua caixa de correio!" +CatalogNotifyOldDelivery = "Ainda há itens aguardando por você em sua caixa de correio!" +CatalogNotifyInstructions = "Clique no botão \"Ir para casa\" na Página do mapa em seu Álbum Toon e vá até o telefone que há dentro da sua casa." +CatalogNewDeliveryButton = "Nova\nentrega!" +CatalogNewCatalogButton = "Novo\ncatálogo" +CatalogSaleItem = "À venda!" + +# don't translate yet +DistributedMailboxEmpty = "A sua caixa de correio está vazia no momento. Volte aqui para procurar entregas depois que você fizer um pedido pelo telefone!" +DistributedMailboxWaiting = "A sua caixa de correio está vazia no momento, mas o pacote que você encomendou está a caminho. Verifique mais tarde!" +DistributedMailboxReady = "Sua encomenda chegou!" +DistributedMailboxNotOwner = "Sinto muito, esta não é a sua caixa de correio." +DistributedPhoneEmpty = "Você pode usar qualquer telefone para encomendar itens especiais para uso pessoal ou para sua casa. Em breve, haverá novos itens disponíveis para pedidos.\n\nNão há nenhum item disponível para pedidos no momento, mas verifique novamente mais tarde!" + +# don't translate yet +Clarabelle = "Clarabela" +MailboxExitButton = "Fechar caixa\nde correio" +MailboxAcceptButton = "Pegar este item" +MailBoxDiscard = "Remover este item" +MailboxAcceptInvite = "Aceitar convite" +MailBoxRejectInvite = "Recusar convite" +MailBoxDiscardVerify = "Você quer mesmo Remover %s?" +MailBoxRejectVerify = "Você quer mesmo Recusar %s?" +MailboxOneItem = "Sua caixa postal contém 1 item." +MailboxNumberOfItems = "Sua caixa postal contém %s itens." +MailboxGettingItem = "Pegando %s da caixa postal." +MailboxGiftTag = "Presente De: %s" +MailboxGiftTagAnonymous = "Anônimo" +MailboxItemNext = "Próximo\nitem" +MailboxItemPrev = "Item\nanterior" +MailboxDiscard = "Remover" +MailboxReject = "Recusar" +MailboxLeave = "Guardar" +CatalogCurrency = "balas" +CatalogHangUp = "Desligar" +CatalogNew = "NOVA" +CatalogBackorder = "ENCOMENDA" +CatalogLoyalty = "ESPECIAL" +CatalogPagePrefix = "Página" +CatalogGreeting = "Olá! Agradecemos sua ligação para o Catálogo da Clarabela. Posso ajudar?" +CatalogGoodbyeList = ["Agora tchau!", + "Ligue novamente em breve!", + "Agradecemos sua ligação!", + "Ok, agora tchau!", + "Tchau!", + ] +CatalogHelpText1 = "Vire a página para ver os itens à venda." +CatalogSeriesLabel = "Série %s" +CatalogGiftFor = "Comprar Presente para:" +CatalogGiftTo = "Para: %s" +CatalogGiftToggleOn = "Parar de\nPresentear" +CatalogGiftToggleOff = "Comprar\nPresentes" +CatalogGiftToggleWait = "Tentando!..." +CatalogGiftToggleNoAck = "Não Disponível" +CatalogPurchaseItemAvailable = "Parabéns pela nova compra! Você já pode usar o seu produto imediatamente." +CatalogPurchaseGiftItemAvailable = "Ótimo! %s pode começar a usar o seu presente agora mesmo." +CatalogPurchaseItemOnOrder = "Parabéns! O produto será entregue em sua caixa de correio em breve." +CatalogPurchaseGiftItemOnOrder = "Ótimo! O seu presente para %s será entregue na caixa de correio dele." +CatalogAnythingElse = "Deseja mais alguma coisa hoje?" +CatalogPurchaseClosetFull = "O seu armário está cheio. Apesar disso, você pode comprar este item, mas se comprar, terá que excluir alguma coisa do seu armário para liberar espaço para o novo item, quando ele chegar.\n\nQuer comprar este item mesmo assim?" +CatalogAcceptClosetFull = "O seu armário está cheio. Entre em casa e exclua alguma coisa do seu armário para liberar espaço para o item antes de retirá-lo da caixa de correio." +CatalogAcceptShirt = "Você está vestindo agora a sua nova camisa. O que você estava vestindo antes foi transferido para o seu armário." +CatalogAcceptShorts = "Você está vestindo agora o seu novo short. O que você estava vestindo antes foi transferido para o seu armário." +CatalogAcceptSkirt = "Você está vestindo agora a sua nova saia. A que você estava vestindo antes foi transferida para o seu armário." +CatalogAcceptPole = "Agora, você está pronto para pescar uns peixes maiores com sua nova vara!" +CatalogAcceptPoleUnneeded = "Você já tem uma vara de pescar melhor do que esta!" +CatalogAcceptChat = "Você ganhou uma nova frase de Chat rápido!" +CatalogAcceptEmote = "Você ganhou uma nova Emoção!" +CatalogAcceptBeans = "Você recebeu algumas balinhas!" +CatalogAcceptRATBeans = "A sua recompensa de recruta Toon chegou!" +CatalogAcceptNametag = "Seu novo crachá chegou!" +CatalogAcceptGarden = "Os seus materiais de jardim chegaram!" +CatalogAcceptPet = "Você ganhou um novo Truque de Rabisco!" +CatalogPurchaseHouseFull = "Sua casa está cheia. Apesar disso, você pode comprar este item, mas se comprar, terá que excluir alguma coisa da sua casa para liberar espaço para o novo item, quando ele chegar.\n\nQuer comprar este item mesmo assim?" +CatalogAcceptHouseFull = "Sua casa está cheia. Entre em casa e exclua alguma coisa de lá para liberar espaço para o item antes de retirá-lo da caixa de correio." +CatalogAcceptInAttic = "O seu novo item está agora no sótão. Você pode colocá-lo em casa entrando lá e clicando no botão \"Mover mobília\"." +CatalogAcceptInAtticP = "Os seus novos itens estão agora no sótão. Você pode colocá-los em casa entrando lá e clicando no botão \"Mover mobília\"." +CatalogPurchaseMailboxFull = "Sua caixa de correio está cheia! Você não poderá comprar este item até retirar alguns itens da caixa de correio para liberar espaço." +CatalogPurchaseGiftMailboxFull = "A caixa de correio de %s está cheia! Você não pode comprar este item." +CatalogPurchaseOnOrderListFull = "Você tem itens demais encomendados no momento. Você não poderá encomendar mais nenhum item até que cheguem alguns já encomendados." +CatalogPurchaseGiftOnOrderListFull = "%s tem ítens demais encomendados." +CatalogPurchaseGeneralError = "Não foi possível encomendar o item devido a um erro interno no jogo: código de erro %s." +CatalogPurchaseGiftGeneralError = "Não foi possível dar o item de presente a %(friend)s por causa de algum erro interno de jogo: código de erro %(error)s." +CatalogPurchaseGiftNotAGift = "Este item não pôde ser enviado para %s porque seria uma vantagem injusta." +CatalogPurchaseGiftWillNotFit = "Este item não pôde ser enviado para %s porque não vai servir." +CatalogPurchaseGiftLimitReached = "Este item não pôde ser enviado para %s porque ele já o possui." +CatalogPurchaseGiftNotEnoughMoney = "Este item não pôde ser enviado para %s porque ele não pode pagar." +CatalogAcceptGeneralError = "Este item não pôde ser excluído da sua caixa de correio por causa de um erro interno do jogo: código do erro %s." +CatalogAcceptRoomError = "Você não tem espaço para isto. Você vai ter que se livrar de alguma coisa." +CatalogAcceptLimitError = "Você já tem o número máximo possível disto. Você vai ter que se livrar de alguma coisa." +CatalogAcceptFitError = "Isto não serve em você! Você o doa para Toons que precisam." +CatalogAcceptInvalidError = "Este item saiu da moda! Você o doa para Toons que precisam." + +MailboxOverflowButtonDicard = "Remover" +MailboxOverflowButtonLeave = "Sair" + +HDMoveFurnitureButton = "Mover\nmobília" +HDStopMoveFurnitureButton = "Mudança\nconcluída" +HDAtticPickerLabel = "No sótão" +HDInRoomPickerLabel = "Na sala" +HDInTrashPickerLabel = "Na lixeira" +HDDeletePickerLabel = "Excluir?" +HDInAtticLabel = "Sótão" +HDInRoomLabel = "Sala" +HDInTrashLabel = "Lixo" +HDToAtticLabel = "Enviar\npara o sótão" +HDMoveLabel = "Mover" +HDRotateCWLabel = "Girar para a direita" +HDRotateCCWLabel = "Girar para a esquerda" +HDReturnVerify = "Retornar este item para o sótão?" +HDReturnFromTrashVerify = "Retornar este item para o sótão, da lixeira?" +HDDeleteItem = "Clique em OK para enviar este item para a lixeira ou em Cancelar para mantê-lo." +HDNonDeletableItem = "Você não pode excluir itens deste tipo!" +HDNonDeletableBank = "Você não pode excluir o seu banco!" +HDNonDeletableCloset = "Você não pode excluir o seu armário!" +HDNonDeletablePhone = "Você não pode excluir o seu telefone!" +HDNonDeletableNotOwner = "Você não pode excluir as coisas de %s's!" +HDHouseFull = "Sua casa está cheia. Você precisa excluir algo mais de sua casa ou do sótão antes de recuperar este item da lixeira." + +HDHelpDict = { + "DoneMoving" : "Decoração da sala concluída.", + "Attic" : "Mostrar lista de itens do sótão. O sótão armazena itens que não estão na sala.", + "Room" : "Mostrar lista de itens da sala. Útil para encontrar itens perdidos.", + "Trash" : "Mostrar itens da lixeira. Os itens mais antigos são excluídos após um tempo ou quando a lixeira fica cheia demais.", + "ZoomIn" : "Tenha uma visão ampliada da sala.", + "ZoomOut" : "Tenha uma visão reduzida da sala.", + "SendToAttic" : "Envie o item de mobília atual para o sótão, para armazená-lo.", + "RotateLeft" : "Vire para a esquerda.", + "RotateRight" : "Vire para a direita.", + "DeleteEnter" : "Alterne para o modo de exclusão.", + "DeleteExit" : "Saia do modo de exclusão.", + "FurnitureItemPanelDelete" : "Envie o item %s para a lixeira.", + "FurnitureItemPanelAttic" : "Coloque o item %s na sala.", + "FurnitureItemPanelRoom" : "Voltar o item %s para o sótão.", + "FurnitureItemPanelTrash" : "Voltar o item %s para o sótão.", + } + +MessagePickerTitle = "Você tem frases demais. Para comprar o item\n\"%s\"\n você precisa escolher um deles para ser removido:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Tem certeza de que quer remover \"%s\" do menu de Chat rápido?" + +CatalogBuyText = "Comprar" +CatalogRentText = "Alugar" +CatalogGiftText = "Presente" +CatalogOnOrderText = "Encomendado" +CatalogPurchasedText = "Já\ncomprado" +CatalogGiftedText = "Presenteado\nPara Você" +CatalogPurchasedGiftText = "Já\nRecebido" +CatalogMailboxFull = "Sem Espaço" +CatalogNotAGift = "Não é um Presente" +CatalogNoFit = "Não\nServe" +CatalogMembersOnly = "Somente para\nUsuários!" +CatalogSndOnText = "Som Ligado" +CatalogSndOffText = "Som Desligado" +CatalogPurchasedMaxText = "Já\ncomprado o máx." +CatalogVerifyRent = "Alugar %(item)s por %(price)s balinhas?" +CatalogVerifyGift = "Comprar %(item)s por %(price)s balinhas de presente para %(friend)s?" +CatalogVerifyPurchase = "Comprar o item %(item)s por %(price)s balinhas?" +CatalogOnlyOnePurchase = "Você só pode ter um destes itens de cada vez. Se comprar este aqui, ele substituirá os itens %(old)s.\n\nTem certeza de que quer comprar o item %(item)s por %(price)s balinhas?" +CatalogExitButtonText = "Desligar" +CatalogCurrentButtonText = "Para itens atuais" +CatalogPastButtonText = "Para itens antigos" + +TutorialHQOfficerName = "Haroldo do Quartel" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tom Tutorial", + 999 : "Costureiro Toon", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Banqueiro Beto", + 2003 : "Professor Paulo", + 2004 : "Cora, a Costureira", + 2005 : "Bibliotecário Bino", + 2006 : "Vendedor Alaor", + 2011 : "Vendedora Isadora", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Vendedor da Loja de Animais", + 2018 : "Estúpi...doo... Homem-DICA", + # NPCPetClerks + 2013 : "Vendedor Pop", + 2014 : "Vendedora Elétrica", + 2015 : "Vendedor Molenga", + # NPCPartyPerson + 2016 : "Planejador de Festa Abóbora", + 2017 : "Planejadora de Festa Polly", + + # Silly Street + 2101 : "Dentista Daniel", + 2102 : "Delegada Délis", + 2103 : "Gatinho Funga-funga", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canária Mina de carvão", + 2109 : "Gugu Bolha", + 2110 : "Otto D'or", + 2111 : "Diego Dançante", + 2112 : "Dr. Tom", + 2113 : "Rolo, o Incrível", + 2114 : "Rosabela", + 2115 : "Pati Papel", + 2116 : "Brutus Crespo", + 2117 : "Dona Putrefata", + 2118 : "Bob Bobo", + 2119 : "Renata Rá Rá", + 2120 : "Professor Pimpão", + 2121 : "Madame Risadinha", + 2122 : "Toni Macacada", + 2123 : "Latônia Lata", + 2124 : "Massinha Mode Lar", + 2125 : "Ralf Desocupado", + 2126 : "Professora Gargalhada", + 2127 : "Nico Níquel", + 2128 : "Duda Doidinho", + 2129 : "Franco Furtado", + 2130 : "Felícia Ding-dong", + 2131 : "Espanadora Penas", + 2132 : "Joe Tromundo", + 2133 : "Dr. Fórico", + 2134 : "Simone Silêncio", + 2135 : "Karla Rossel", + 2136 : "Saulo Risadinha", + 2137 : "Alegria Alegre", + 2138 : "João", + 2139 : "Bebê Babá", + 2140 : "Gui Pescador", + + # Loopy Lane + 2201 : "Gerente Gil", + 2202 : "Shirley Vocezomba", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Saulo Sabichão", + 2208 : "Lucas Grude", + 2209 : "Rico Risada", + 2210 : "Chazinha", + 2211 : "Cláudia Cuspinho", + 2212 : "Estêvão Estranho", + 2213 : "Luciana da Roda", + 2214 : "Mano da Mancha", + 2215 : "Bob Bujão", + 2216 : "Kênia Teviu", + 2217 : "João Tubarão", + 2218 : "Hilária Folha", + 2219 : "Chef Cabeça de Vento", + 2220 : "Carlos Cabeça de Ferro", + 2221 : "Flora Canudinho", + 2222 : "Fusível Mirim", + 2223 : "Gláucia Gargalhada", + 2224 : "Fábio Fumacinha", + 2225 : "Corcunda Pescador", + + # Punchline Place + 2301 : "Dr. Puxaperna", + 2302 : "Professor Balanço", + 2303 : "Enfermeira Enferma", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Veneno", + 2309 : "João Grandão", + 2311 : "Francisco da Graça", + 2312 : "Dra. Sensível", + 2313 : "Lucinda Pinta", + 2314 : "Lúcio Lançador", + 2315 : "Tatá Tasco", + 2316 : "Bárbara Bola", + 2318 : "Ronaldo Engraçaldo", + 2319 : "Tiraldo", + 2320 : "Alfredo Nham", + 2321 : "Bartô Pescador", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Vendedor Willy", + 1002 : "Vendedor Billy", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Betão Calçalongas", + # NPCFisherman + 1008 : "Vendedor da Loja de Animais", + # NPCPetClerks + 1009 : "Vendedor Durão", + 1010 : "Vendedora Ron-ron", + 1011 : "Vendedora Blup", + # NPCPartyPerson + 1012 : "Planejador de Festa Pickles", + 1013 : "Planejador de Festa Patty", + + # Barnacle Blvd. + 1101 : "Levi Legal", + 1102 : "Capitão Carlão", + 1103 : "Pedro Peixe", + 1104 : "Doutor Alá", + 1105 : "Almirante Gancho", + 1106 : "Dona Goma", + 1107 : "Carlo Caiado", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gláucio Glubglub", + 1113 : "Adele Adernada", + 1114 : "Carlos Camarada", + 1115 : "Lúcia Lula", + 1116 : "Carla Craca", + 1117 : "Capitão Blargh", + 1118 : "Marinho Crespo", + 1121 : "Linda Terra Firme", + 1122 : "Salgado Pescado", + 1123 : "Enguia Elétrica", + 1124 : "João Farpa do Cais", + 1125 : "Arlene Além-mar", + 1126 : "Zé Silva Pescador", + + # Seaweed Street + 1201 : "Craca Bárbara", + 1202 : "Mário", + 1203 : "Salgado", + 1204 : "Marco Quebra-mar", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professora Pranchinha", + 1210 : "Aiki Sopa", + 1211 : "Malo Mala", + 1212 : "Tomás Língua de Trapo", + 1213 : "Bob Botinho", + 1214 : "Kátia Furacão", + 1215 : "Paula Profundeza", + 1216 : "Otto Ostra", + 1217 : "Ciça Caniço", + 1218 : "Toni Pacífico", + 1219 : "Carlos Encalhado", + 1220 : "Carla Canal", + 1221 : "Alan Abrolhos", + 1222 : "Bob Abordo", + 1223 : "Lula Lulu", + 1224 : "Emília Enguia", + 1225 : "Estêvão Estivador", + 1226 : "Pedro Pé na Tábua", + 1227 : "Coral do Recife", + 1228 : "Junqueira Pescador", + + # Lighthouse Lane + 1301 : "Alice", + 1302 : "Moby", + 1303 : "Mário", + 1304 : "Martina", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Esponja do Mar", + 1310 : "Fernando Ferramenta", + 1311 : "Paulinha Ponta Cabeça", + 1312 : "Hélio Hélice", + 1313 : "Wilson Nó", + 1314 : "Fernando Enferrujado", + 1315 : "Doutora Correnteza", + 1316 : "Beth Rodopio", + 1317 : "Paula Poste", + 1318 : "Teófilo Bote", + 1319 : "Estácio Estaleiro", + 1320 : "Caio Calmaria", + 1321 : "Camila Cais", + 1322 : "Rachel Recheio", + 1323 : "Fred Fedido", + 1324 : "Pérola Profunda", + 1325 : "Sérgio Vira-latas", + 1326 : "Felícia Batatinha", + 1327 : "Cíntia Tábua", + 1328 : "Lucas Linguado", + 1329 : "Conchita Alga Marina", + 1330 : "Porta Dor", + 1331 : "Rudy Ridíquilhas", + 1332 : "Polar Pescador", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Frida Freezer", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Vendedor Breno", + 3007 : "Vendedora Brenda", + 3008 : "Paco Pacote", + # NPCFisherman + 3009 : "Vendedor da Loja de Animais", + # NPCPetClerks + 3010 : "Vendedor Saltitante", + 3011 : "Vendedora Glub", + 3012 : "Vendedor Kiko", + # NPCPartyPerson + 3013 : "Planejador de Festa Pedro", + 3014 : "Planejador de Festa Penny", + + # Walrus Way + 3101 : "Seu Leão", + 3102 : "Tia Freezer", + 3103 : "Chicó", + 3104 : "Gorrete", + 3105 : "Fred Cavanhaque", + 3106 : "Pio Arrepio", + 3107 : "Patty Passaporte", + 3108 : "Tobi Tobogã", + 3109 : "Kate", + 3110 : "Franguinho", + 3111 : "Cão de Bico", + 3112 : "Pequeno Grande Ancião", + 3113 : "Américo Histérico", + 3114 : "Rico Arriscado", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carlos K. B. Loempé", + 3120 : "Kiko Quiprocó", + 3121 : "João Eletrochoque", + 3122 : "Luci Lugar", + 3123 : "Francis Quebra Gelo", + 3124 : "Estileto Iceberg", + 3125 : "Coronel Mastiga", + 3126 : "João Jornada", + 3127 : "Aérea Inflada", + 3128 : "Jorge Palitinho", + 3129 : "Fátima Fôrma", + 3130 : "Sandy", + 3131 : "Patrício Preguiça", + 3132 : "Maria Cinza", + 3133 : "Dr. Congelado", + 3134 : "Vítor Vestíbulo", + 3135 : "Ênia Sopada", + 3136 : "Susana Nimada", + 3137 : "Sr. Freezer", + 3138 : "Chefe Sopa Rala", + 3139 : "Vovó Ceroulas", + 3140 : "Luci Pescadora", + + # Sleet Street + 3201 : "Tia Ártica", + 3202 : "Tremendão", + 3203 : "Walter", + 3204 : "Dra. Vai C. V.", + 3205 : "Cabeção Kika", + 3206 : "Vidália VaVum", + 3207 : "Dr. Ban Guela", + 3208 : "Felipe Nervosinho", + 3209 : "Marcos Cem Graça", + 3210 : "Álvaro Asno", + 3211 : "Hilária Freezer", + 3212 : "Rogério Gélido", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Consuelo Suada", + 3218 : "Lu Lazul", + 3219 : "Bob BikeDupla", + 3220 : "Sr. Espirro", + 3221 : "Neli Neve", + 3222 : "Vera Vento Cortante", + 3223 : "Chapa", + 3224 : "Rita Raspadinha", + 3225 : "Foca Fofoca", + 3226 : "Papai Nó El", + 3227 : "Raiomundo de Sol", + 3228 : "Frida Calafrio", + 3229 : "Hermínia Cinta", + 3230 : "Pedro Pedreira", + 3231 : "G. Lopicado", + 3232 : "Pescador Alberto", + + # Polar Place + 3301 : "Remendo Tecidos", + 3302 : "Pedro Urso", + 3303 : "Dr. Olhadelas", + 3304 : "Abrão o Abominável", + 3305 : "Mick Eimei", + 3306 : "Paula Úrsula", + # NPC Fisherman + 3307 : "Pescadora Frederica", + 3308 : "Roberto Injustus", + 3309 : "Botinha", + 3310 : "Professor Floco", + 3311 : "Connie Feras", + 3312 : "Haroldo Marcha", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Bete Beijoqueira", + 3318 : "João Caxemira", + 3319 : "Reinaldo Retifica", + 3320 : "Ester Espuma", + 3321 : "Paulo Picareta", + 3322 : "Luis Fluis", + 3323 : "Aurora Borealis", + 3324 : "Otto Dentetorto", + 3325 : "Dercy Balançalves", + 3326 : "Blanche", + 3327 : "Cacá Sado", + 3328 : "Sônia Sombria", + 3329 : "Edu Pisada", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Mel Odia", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Vendedora Do-ré-mi", + 4007 : "Vendedor Fá-sol-lá-si", + 4008 : "Costureira Harmonia", + # NPCFisherman + 4009 : "Vendedor da Loja de Animais", + # NPCPetClerks + 4010 : "Vendedor Caco", + 4011 : "Vendedor Nilton", + 4012 : "Vendedora Flor do Nordeste", + # NPCPartyPerson + 4013 : "Planejador de Festa Preston", + 4014 : "Planejadora de Festa Penélope", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr. Triturador", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Clave", + 4109 : "Carlos", + 4110 : "Métrica Anã", + 4111 : "Tom Tum", + 4112 : "Fá", + 4113 : "Madame Boa Maneira", + 4114 : "Bino Desafino", + 4115 : "Bárbara de Sevilha", + 4116 : "Flávio Flautim", + 4117 : "Banda Lyn", + 4118 : "Faxineiro Abel", + 4119 : "Moz Arte", + 4120 : "Violante Almofada", + 4121 : "Gegê Menor", + 4122 : "Mentolada do Baixo", + 4123 : "André Raio", + 4124 : "Renato Refrão", + 4125 : "Ondina Musical", + 4126 : "Melô Canto", + 4127 : "Felícia Podos", + 4128 : "Luciano Furo", + 4129 : "Carla Cadência", + 4130 : "Miguel Metal", + 4131 : "Abraão Armário", + 4132 : "Marta Marrom", + 4133 : "Paulo Popeline", + 4134 : "Davi Disco", + 4135 : "Carlo Canoro", + 4136 : "Patrícia Pausa", + 4137 : "Toni Surdo", + 4138 : "Agudo Clave", + 4139 : "Harmonia Decrescente", + 4140 : "Daniel Desajeitado", + 4141 : "Pet Pescador", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Alê Nhador", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Heidi", + 4209 : "Brega Galopante", + 4211 : "Carlo Concerto", + 4212 : "Detetive Marcha Fúnebre", + 4213 : "Franca Foley", + 4214 : "Paula Meia-ponta", + 4215 : "Mário Marcha a ré", + 4216 : "Bob Buzina", + 4217 : "Toni Bonitão", + 4218 : "Sônia Soprano", + 4219 : "Bruno Barítono", + 4220 : "Dênis Dedus", + 4221 : "Marcos Madrigal", + 4222 : "João da Silva", + 4223 : "Pâmela Ponto", + 4224 : "Jim das Selvas", + 4225 : "Vânia Vaia", + 4226 : "Samantha Garganta", + 4227 : "Cláudia Calada", + 4228 : "Augusto de Sopro", + 4229 : "Júnia Bombom", + 4230 : "Marcelo Martelo", + 4231 : "Stefanie Acordes", + 4232 : "Helder Hino", + 4233 : "Enzo Enjoado", + 4234 : "Mestre Guitarra", + 4235 : "Lauro Pescador", + + # Tenor Terrace + 4301 : "Cavaca", + 4302 : "Ana", + 4303 : "Léo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tábata", + 4309 : "Punk Ecas", + 4310 : "Mezza Soprana", + 4311 : "Chica Shake", + 4312 : "Paulo Palheta", + 4313 : "Mário Mudo", + 4314 : "Danuza Uza", + 4315 : "Maritza Tique Ataque", + 4316 : "Toni Tango", + 4317 : "Dedo Curto", + 4318 : "Bob Marlin", + 4319 : "Cátia Zuza", + 4320 : "Roberta P. Rock", + 4321 : "Edinho Verde", + 4322 : "Antoniota Musical", + 4323 : "Bárbara Balado", + 4324 : "Elen", + 4325 : "Ralf Rádio", + 4326 : "Kíria Irrita", + 4327 : "Armínia Arranca Pele", + 4328 : "Wagner", + 4329 : "Teles Prompter", + 4330 : "Quarentino", + 4331 : "Mello Costello", + 4332 : "Ziggy", + 4333 : "Ubaldo", + 4334 : "Estêvão Expresso", + 4335 : "Sílvia Pescadora", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Vendedora Moranguinho", + 5006 : "Vendedor Herbal", + 5007 : "Florinda Flores", + # NPCFisherman + 5008 : "Vendedor da Loja de Animais", + # NPCPetClerks + 5009 : "Vendedora Buba Tânica", + 5010 : "Vendedor Tony Grana", + 5011 : "Vendedor Duda Madeira", + # NPCPartyPerson + 5012 : "Planejador de Festa Pierce", + 5013 : "Planejadora de Festa Peggy", + + # Elm Street + 5101 : "Sérgio", + 5102 : "Susana", + 5103 : "Florêncio", + 5104 : "Borba Oleta", + 5105 : "João", + 5106 : "Barbeiro Tosque Ador", + 5107 : "Carteiro Felipe", + 5108 : "Funcionária Janete", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dra. Ália e Ólea", + 5114 : "Al Fácio Murcho", + 5115 : "Lua de Melão", + 5116 : "Vítor Vegetal", + 5117 : "Pétala", + 5118 : "Pipo K.", + 5119 : "João Medalhão", + 5120 : "Toupeira", + 5121 : "Emília Ervilha", + 5122 : "J. Jardim", + 5123 : "Diana Uva", + 5124 : "Olavo Orvalho", + 5125 : "Chu Chuá", + 5126 : "Madame Calado", + 5127 : "Poliana Pólen", + 5128 : "Suzana Seiva", + 5129 : "Salgueira Pescadora", + + # Maple Street + 5201 : "Joãozinho", + 5202 : "Cíntia", + 5203 : "Lisa", + 5204 : "Ubaldo", + 5205 : "Maurício Leão", + 5206 : "Branco Vinho", + 5207 : "Sofia Seiva", + 5208 : "Samanta Pá", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Nabo Bobo", + 5214 : "Empolada Alérgica", + 5215 : "Clara Caules", + 5216 : "Fernando Fedido", + 5217 : "Vítor do Dedo Verde", + 5218 : "Francisco Framboesa", + 5219 : "Bi Ceps", + 5220 : "Luci Calçola", + 5221 : "Rosinha Flamingo", + 5222 : "Sandra Samambaia", + 5223 : "Paulo Ensopado", + 5224 : "Tio Camponês", + 5225 : "Pâmela Pântana", + 5226 : "Mauro Musgo", + 5227 : "Begônia Malte", + 5228 : "Drago Di Lama", + 5229 : "Lili Pescadora", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Cristal", + 5306 : "C. Postal", + 5307 : "Mo Fus", + 5308 : "Nely Nervo", + 5309 : "Rô Mann", + 5310 : "Timóteo", + 5311 : "Juíza Gala", + 5312 : "Eugênio", + 5313 : "Treinador Abobrinha", + 5314 : "Tia Miga", + 5315 : "Tio Lama", + 5316 : "Tio Batatinha", + 5317 : "Detetive Linda", + 5318 : "César", + 5319 : "Rose", + 5320 : "Márcia", + 5321 : "Professora Uva", + 5322 : "Rose Pescadora", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "Grandep Rêmio", + 8002 : "Keruk Orrê", + 8003 : "Precisuv Encer", + 8004 : "En Chaela", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Susana Pestana", + 9002 : "Dor Minhoco", + 9003 : "Sono Lento", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Vendedora Resona", + 9009 : "Vendedor Kisono", + 9010 : "Ultraje Velho", + # NPCFisherman + 9011 : "Vendedor da Loja de Animais", + # NPCPetClerks + 9012 : "Vendedora Sara Soneca", + 9013 : "Vendedora Gata na Lata", + 9014 : "Vendedor Cara Mujo", + # NPCPartyPerson + 9015 : "Planejador de Festa Pebbles (Pedregulho)", + 9016 : "Planejadora de Festa Pérola", + + # Lullaby Lane + 9101 : "Marcelo", + 9102 : "Mama", + 9103 : "Py Jama", + 9104 : "Dulce Lombra", + 9105 : "Professor Bocejo", + 9106 : "Máximo", + 9107 : "Aurora Ninho", + 9108 : "Pedro Pestana", + 9109 : "Dafne Sonolinda", + 9110 : "Gatária Soneca", + 9111 : "Elle Étrica", + 9112 : "Denis Nar", + 9113 : "Tique Eustáquio", + 9114 : "Máki Agem", + 9115 : "Nenê Crespo", + 9116 : "Dança com Carneirinhos", + 9117 : "Aurora Extra", + 9118 : "Celeste Estrelada", + 9119 : "Pedro", + 9120 : "Lúcia Lenta", + 9121 : "Serena Lençol Curto", + 9122 : "Paulo Pregado", + 9123 : "Ursolino de P. Lúcia", + 9124 : "Nana de Nina", + 9125 : "Dr. Turvo", + 9126 : "Jatha Cordada", + 9127 : "Tati U. Nidos", + 9128 : "Pedro Fuso", + 9129 : "Cátia Colcha", + 9130 : "Nico Penico", + 9131 : "Célia Sesta", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Tainha Pescador", + + # Pajama Place + 9201 : "Bernardo", + 9202 : "Carneiro", + 9203 : "Zezé", + 9204 : "Clara da Lua", + 9205 : "Bob Bocão", + 9206 : "Petra Pétala", + 9207 : "Denise Dreno", + 9208 : "Solano Sonolento", + 9209 : "Dr. Sedoso", + 9210 : "Mestre Mário", + 9211 : "Aurora", + 9212 : "Raio de Lua", + 9213 : "Gustavo Galo", + 9214 : "Dr. Soneca", + 9215 : "Dedé Descanso", + 9216 : "Cuca", + 9217 : "Linda Legal", + 9218 : "Matilda Madruga", + 9219 : "Condessa", + 9220 : "Ney Nervoso", + 9221 : "Zéfiro", + 9222 : "Vaqueiro George", + 9223 : "Vado Levado", + 9224 : "Cuca P. Gol", + 9225 : "Henriqueta Inquieta", + 9226 : "Guilherme Sonoleve", + 9227 : "Carlos Cabeceira", + 9228 : "Samuel Suspiro", + 9229 : "Rosa Sonada", + 9230 : "Lelê", + 9231 : "Régis Rede", + 9232 : "Lua de Mel", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "Jung Pescador", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("PrefeiToona", ""), + 2514 : ("Banco de Toontown", ""), + 2516 : ("Escola de Toontown", ""), + 2518 : ("Biblioteca de Toontown", ""), + 2519 : (lGagShop, ""), + 2520 : (lToonHQ, ""), + 2521 : (lClothingShop, ""), + 2522 : (lPetShop, ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Restaurações Dentárias Todo Sorrisos", ""), + 2602 : ("", ""), + 2603 : ("Mineradores Espirituosos", ""), + 2604 : ("Lavanderia Lavou está Novo", ""), + 2605 : ("Fábrica de Sinalização de Toontown", ""), + 2606 : ("", ""), + 2607 : ("Feijões Saltadores", ""), + 2610 : ("Dr. Tom Besteira", ""), + 2611 : ("", ""), + 2616 : ("Loja de Disfarces Bigode Bizarro", ""), + 2617 : ("Feitos Idiotas", ""), + 2618 : ("A Encarnação Deve Continuar", ""), + 2621 : ("Aviões de Papel", ""), + 2624 : ("Brutamontes Felizes", ""), + 2625 : ("Casa da Torta Azeda", ""), + 2626 : ("Restauração de Piadas do Bob", ""), + 2629 : ("A Casa da Risada", ""), + 2632 : ("Curso de Palhaços", ""), + 2633 : ("Casa de Chá Chapinha", ""), + 2638 : ("Casa de Brinquedos de Toontown", ""), + 2639 : ("Truques e Macaquices", ""), + 2643 : ("Conservas Conservadas", ""), + 2644 : ("Pregadinhas de Peça", ""), + 2649 : ("Loja de Diversões e Jogos", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Curso de Risada", ""), + 2655 : ("Financeira Dinheiro Feliz", ""), + 2656 : ("Carros de Palhaço Usados", ""), + 2657 : ("Pegadinhas do Franco", ""), + 2659 : ("Campainhas Ding-dong para o Mundo", ""), + 2660 : ("Máquinas de Cosquinhas", ""), + 2661 : ("Doces Joe", ""), + 2662 : ("Dr. E. U. Fórico", ""), + 2663 : ("Cinerama de Toontown", ""), + 2664 : ("Mímicas Divertidas", ""), + 2665 : ("Agência de Viagens K. Rossel", ""), + 2666 : ("Posto de Gás Hilariante", ""), + 2667 : ("A Folha da Alegria", ""), + 2669 : ("Balões do João", ""), + 2670 : ("Sopa no Garfo", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Cinemas Multiplex", ""), + 2705 : ("Instrumentos Barulhentos do Sabichão", ""), + 2708 : ("Cola Azul", ""), + 2711 : ("Correio de Toontown", ""), + 2712 : ("Café do Risada", ""), + 2713 : ("Café da Madrugargalhada", ""), + 2714 : ("CinePlex Lesado", ""), + 2716 : ("Sopas e Surtos", ""), + 2717 : ("Latas engarrafadas", ""), + 2720 : ("Oficina do Chilique", ""), + 2725 : ("", ""), + 2727 : ("Garrafas e Conservas do Gasoso", ""), + 2728 : ("Sorvete Sumiço", ""), + 2729 : ("Peixinhos Dourados Ki-late", ""), + 2730 : ("Notícias Divertidas", ""), + 2731 : ("", ""), + 2732 : ("Espaguete Maluquete", ""), + 2733 : ("Pipas de Ferro", ""), + 2734 : ("Copos de Leite Chupa-chupa", ""), + 2735 : ("Casa do Cabum", ""), + 2739 : ("Restauração de Gargalhadas", ""), + 2740 : ("Rojões Usados", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("Lavagem a Seco Beca", ""), + 2744 : ("", ""), + 2747 : ("Tinta Visível", ""), + 2748 : ("Zombarias para Gargalhadas", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Estofados Iupii", ""), + 2802 : ("Bolas de Ferro Infláveis", ""), + 2803 : ("Karnaval Kid", ""), + 2804 : ("Dr. Puxaperna, Ortopedista", ""), + 2805 : ("", ""), + 2809 : ("Academia Graça da Piada", ""), + 2814 : ("Teatro de Toontown", ""), + 2818 : ("A Torta Voadora", ""), + 2821 : ("", ""), + 2822 : ("Sanduíches de Frango de Borracha", ""), + 2823 : ("Sorvetes e Sundaes Divertidos", ""), + 2824 : ("Cinema Palácio do Auge da Graça", ""), + 2829 : ("Truques e Trocadilhos", ""), + 2830 : ("Tiradas Rápidas", ""), + 2831 : ("Casa do Sorriso Amarelo do Professor Balanço", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("Sala de Emergência Osso Bom", ""), + 2836 : ("", ""), + 2837 : ("Centro de Estudos Rá Rá Rá", ""), + 2839 : ("Grude Massas", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : (lGagShop, ""), + 1507 : (lToonHQ, ""), + 1508 : (lClothingShop, ""), + 1510 : (lPetShop, ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Salva-vidas Usados", ""), + 1604 : ("Lavagem a Seco Roupa de Mergulho", ""), + 1606 : ("Conserto de Relógios do Gancho", ""), + 1608 : ("Bugigangas a Vela", ""), + 1609 : ("Iscas e Petiscos", ""), + 1612 : ("Banco Moedinha no Convés", ""), + 1613 : ("Lula Ki Pro Quo, Advogados", ""), + 1614 : ("Butique Unha Afiada", ""), + 1615 : ("E aí, Galera?", ""), + 1616 : ("Salão de Beleza Barba Azul", ""), + 1617 : ("Ótica Olha Lá", ""), + 1619 : ("Arboristas Desembarcar!", ""), + 1620 : ("Da Proa à Popa", ""), + 1621 : ("Academia Castelo de Popa", ""), + 1622 : ("Artigos Elétricos Isca Interruptora", ""), + 1624 : ("Reparos de Pescadas na Hora", ""), + 1626 : ("Roupas de Gala Salmão Encantado", ""), + 1627 : ("Atacado de Bússolas do Levi Legal", ""), + 1628 : ("Pianos Atum", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Creche Peixinho Feliz", ""), + 1703 : ("Restaurante China Prancha", ""), + 1705 : ("Velas à Venda", ""), + 1706 : ("Pasta de Amendoim e Água-viva", ""), + 1707 : ("Presentes Golfinho Fofinho", ""), + 1709 : ("Veleiros e Gelatinas", ""), + 1710 : ("Liquidação das Cracas", ""), + 1711 : ("Restaurante Fundo do Mar", ""), + 1712 : ("Academia da Geração Saúde", ""), + 1713 : ("Mercado Carta Marinha do Mário", ""), + 1714 : ("Hotel do Otto", ""), + 1716 : ("Roupas de Banho Sereias", ""), + 1717 : ("Curso de Navegação Águas do Pacífico", ""), + 1718 : ("Serviços de Táxi Banco de Areia", ""), + 1719 : ("Empresas Correntes do Sul", ""), + 1720 : ("A Loja do Molinete", ""), + 1721 : ("Armarinho Marinho", ""), + 1723 : ("Alga Marinha do Lula", ""), + 1724 : ("A Enguia Moderna", ""), + 1725 : ("Centro de Caranguejos Pré-fabricados do Salgado", ""), + 1726 : ("Cerveja Preta Flutuante", ""), + 1727 : ("Rema aqui, Rema lá", ""), + 1728 : ("Caranguejos-ferradura Boa Sorte", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Nada como Náutica", ""), + 1804 : ("Ginásio Mexilhão da Praia", ""), + 1805 : ("Caixa de Ferramentas Lanches", ""), + 1806 : ("Loja de Chapéus Emborcado", ""), + 1807 : ("Loja do Hélice", ""), + 1808 : ("Nós Samãe", ""), + 1809 : ("Balde Enferrujado", ""), + 1810 : ("Administração de Âncoras", ""), + 1811 : ("Canoa para Lá, Canoa para Cá?", ""), + 1813 : ("Pressão do Pier Consultoria", ""), + 1814 : ("Parada do Ó", ""), + 1815 : ("Qual é, galerinha?", ""), + 1818 : ("Café dos Sete Mares", ""), + 1819 : ("Restaurante Cais", ""), + 1820 : ("Loja de Pegadinhas Linha e Anzol", ""), + 1821 : ("Conservas Rei Netuno", ""), + 1823 : ("Assados Ostra", ""), + 1824 : ("Remo Cachorrinho", ""), + 1825 : ("Mercado de Peixes Cavala Trotante!", ""), + 1826 : ("Armários Embutidos do Mário Metido", ""), + 1828 : ("Mansão da Alice Cascalhão", ""), + 1829 : ("Loja de Esculturas Piscicultura", ""), + 1830 : ("Linguados e Perdidos", ""), + 1831 : ("Alga Mais em sua Casa", ""), + 1832 : ("Hipermercado Mastro do Moby", ""), + 1833 : ("Alfaiataria sob Medida Seu Mastro", ""), + 1834 : ("Ridíquilhas!", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : (lGagShop, ""), + 4504 : (lToonHQ, ""), + 4506 : (lClothingShop, ""), + 4508 : (lPetShop, ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Baterias do Tomtom", ""), + 4604 : ("A Quatro Mãos", ""), + 4605 : ("Violinos da Fifi", ""), + 4606 : ("Casa da Castanhola", ""), + 4607 : ("Trajes de Gala Toon", ""), + 4609 : ("Teclas de Piano Dó-ré-mi", ""), + 4610 : ("O Bom Refrão", ""), + 4611 : ("Faqueiros Diapasão", ""), + 4612 : ("Clínica Dentária Dr. Triturador", ""), + 4614 : ("Barbearia Musical", ""), + 4615 : ("Pizza do Flautim", ""), + 4617 : ("Bandolins Animados", ""), + 4618 : ("Banheiros Públicos", ""), + 4619 : ("Mar Cação", ""), + 4622 : ("Travesseiros Descanso de Queixo", ""), + 4623 : ("Afiação Bemol", ""), + 4625 : ("Pasta de Dente Tuba", ""), + 4626 : ("Notas Musicais", ""), + 4628 : ("Seguradora Acidental", ""), + 4629 : ("Pratos de Papel Refrão", ""), + 4630 : ("A Música é o nosso Forte", ""), + 4631 : ("Auxílio Canto Neiras", ""), + 4632 : ("Loja do Rock", ""), + 4635 : ("Notícias do Tenor", ""), + 4637 : ("A Boa Escala", ""), + 4638 : ("Loja do Heavy Metal", ""), + 4639 : ("Antiguidades Oitenta", ""), + 4641 : ("Jornal dos Blues", ""), + 4642 : ("Lavagem a Seco Beca", ""), + 4645 : ("Clube 88", ""), + 4646 : ("", ""), + 4648 : ("Mudanças Carregando Toons", ""), + 4649 : ("", ""), + 4652 : ("Loja de Conveniência Ponto Final", ""), + 4653 : ("", ""), + 4654 : ("Telhados Volume Perfeito", ""), + 4655 : ("Escola de Culinária do Terrível Chef Agudo", ""), + 4656 : ("", ""), + 4657 : ("Barbearia Quarteto", ""), + 4658 : ("Pianos Submersos", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("Escola de Dança Jumento Sentimento", ""), + 4702 : ("Timbre! Artigos para Lenhadores", ""), + 4703 : ("A Mala Madeus", ""), + 4704 : ("Concertos de Concertina da Tina", ""), + 4705 : ("Zarpou fora", ""), + 4707 : ("Estúdio de Efeitos Sonoros Doppler", ""), + 4709 : ("Artigos de Montanhismo Pliê", ""), + 4710 : ("Auto-escola Pouca Polca", ""), + 4712 : ("Borracharia Dó do Murcho", ""), + 4713 : ("Moda Fina Masculina Desafina", ""), + 4716 : ("Gaitas de Quatro Segmentos", ""), + 4717 : ("Seguradora de Automóveis Barateira Barítono", ""), + 4718 : ("Peças para Chopp-in e Outros Artigos para Cozinha", ""), + 4719 : ("Casas-móveis Madrigal", ""), + 4720 : ("Dê um Nome a este Toon", ""), + 4722 : ("Substitutos Abertura", ""), + 4723 : ("Artigos para Parquinhos Infantis Ex-condesconde", ""), + 4724 : ("Moda Infantil Inci Dental", ""), + 4725 : ("O Barbeiro Barítono", ""), + 4727 : ("Bordados Corda Vocal", ""), + 4728 : ("Solo Vocal Não dá pra Ouvir", ""), + 4729 : ("Livraria Oboé", ""), + 4730 : ("Sebo de Letras de Músicas", ""), + 4731 : ("Tons dos Toons", ""), + 4732 : ("Companhia Teatral Prega Peça", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Acorde Não!", ""), + 4736 : ("Planejamento Matrimonial Casal Hino Esperado", ""), + 4737 : ("Lonas Harpa", ""), + 4738 : ("Presentes Cantata do Tatá", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Ponto do Punk", ""), + 4803 : ("Serviços de Governança Que Mezza!", ""), + 4804 : ("Curso de Barman Shake Shake Shake", ""), + 4807 : ("Não Quebre o Braço", ""), + 4809 : ("Não Com Verso!", ""), + 4812 : ("", ""), + 4817 : ("Loja de Animais Trin Canário", ""), + 4819 : ("Cavaquinhos da Cavaca", ""), + 4820 : ("", ""), + 4821 : ("Cruzeiros da Ana", ""), + 4827 : ("Relógios Ritmo Cadente", ""), + 4828 : ("Sapatos Masculinos Rima", ""), + 4829 : ("Bolas de Canhão Vaga Ner", ""), + 4835 : ("Fundamentos Musicais para Felinos Felizes", ""), + 4836 : ("Regalias do Reggae", ""), + 4838 : ("Escola de Música K. Zuza", ""), + 4840 : ("Bebidas Musicais Pop Rock", ""), + 4841 : ("Bandoleiro Bandolins!", ""), + 4842 : ("Corporação Sincopação", ""), + 4843 : ("", ""), + 4844 : ("Motocicletas Com Notação", ""), + 4845 : ("Elegias Elegantes da Elen", ""), + 4848 : ("Financeira Cordas de Dinheiro", ""), + 4849 : ("", ""), + 4850 : ("Hipoteca Cordas Emprestadas", ""), + 4852 : ("Arranca-peles Flauta Florida", ""), + 4853 : ("Para-choques do Léo Guitarra", ""), + 4854 : ("Vídeos de Violinos Vocacionais Wagner", ""), + 4855 : ("Rede de Televisão Teleouvisa", ""), + 4856 : ("", ""), + 4862 : ("Quatrilhos Quintessenciais do Quarentino", ""), + 4867 : ("Celos Amarelos do Costello", ""), + 4868 : ("", ""), + 4870 : ("Zoológico de Ziriguidum do Ziggy", ""), + 4871 : ("Humbuckers Únicos do Ubaldo", ""), + 4872 : ("Braços sem Estresse do Estevão Expresso", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : (lGagShop, ""), + 5502 : (lToonHQ, ""), + 5503 : (lClothingShop, ""), + 5505 : (lPetShop, ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Exames de Vista Olho do Alho", ""), + 5602 : ("Gravatas do Sérgio Sufocado", ""), + 5603 : ("Verde que te Quero Verdura", ""), + 5604 : ("Loja de Noivas Mel e Lão", ""), + 5605 : ("Sobre Mesas e Cadeiras", ""), + 5606 : ("Pétalas", ""), + 5607 : ("Correios Adubo Expresso", ""), + 5608 : ("Toca da Pipoca", ""), + 5609 : ("Tesouro dos Dentes de Alho de Ouro", ""), + 5610 : ("Aulas de Boxe da Susana Olhos Negros", ""), + 5611 : ("Piadas do Toupeira", ""), + 5613 : ("Barbeiros Tosa Completa", ""), + 5615 : ("Ração para Pássaros do Florêncio", ""), + 5616 : ("Pousada Pouso da Coruja", ""), + 5617 : ("Borboletas do Borba Oleta", ""), + 5618 : ("Ervilhas e Milhas", ""), + 5619 : ("Pés de feijão do João", ""), + 5620 : ("Pousada Pá de Coisa", ""), + 5621 : ("Uvinhas da Ira", ""), + 5622 : ("Loja de Bicicletas Bem-me-quer", ""), + 5623 : ("Banheiras para Pássaros Bolhinhas Aladas", ""), + 5624 : ("Bico Calado", ""), + 5625 : ("Os Abelhudos", ""), + 5626 : ("Artesanato Pínus", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Do Início ao Figo", ""), + 5702 : ("Ancinho do Joãozinho", ""), + 5703 : ("Fotos Cíntia", ""), + 5704 : ("Carros Usados Lisa Lima", ""), + 5705 : ("Móveis Urtigas", ""), + 5706 : ("Joalheiros 14 Ki Latas", ""), + 5707 : ("Fruta Musical", ""), + 5708 : ("Agência de Viagens Erva da Ninha", ""), + 5709 : ("Cortadores de Grama Ré U. Vassintética", ""), + 5710 : ("Academia Durão", ""), + 5711 : ("Roupas Íntimas Jardim de Inverno", ""), + 5712 : ("Estátuas Idiotas", ""), + 5713 : ("Mãos à Obra", ""), + 5714 : ("Água Mineral Chuva de Verão", ""), + 5715 : ("Notícias do Campo", ""), + 5716 : ("Hipotecas Folhas Caídas", ""), + 5717 : ("Seivas Florais", ""), + 5718 : ("Animais Exóticos Mauricinho Leão", ""), + 5719 : ("Investigadores Particulares Cara que Manchão!", ""), + 5720 : ("Moda Masculina Bran Covinho", ""), + 5721 : ("Restaurante Rota 66", ""), + 5725 : ("Cervejaria da Cevada", ""), + 5726 : ("Terra Adubada do Ubaldo", ""), + 5727 : ("Financeira Toupeira Encurralada", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("Vazar ou não Vazar?", ""), + 5805 : ("Correio da Lesma", ""), + 5809 : ("Escola de Palhaços Fungos", ""), + 5810 : ("Mela o Melado", ""), + 5811 : ("Pousada Al Face a Face", ""), + 5815 : ("Rural", ""), + 5817 : ("Maçãs e Laranjas", ""), + 5819 : ("Jeans Vagem Verde", ""), + 5821 : ("Academia Amassado e Esticado", ""), + 5826 : ("Artigos para o Cultivo de Formigas", ""), + 5827 : ("Promoção de Aterrar", ""), + 5828 : ("Móveis Batatinha Quando Nasce", ""), + 5830 : ("Espalhado o Babado", ""), + 5833 : ("Restaurante Saladas", ""), + 5835 : ("Café Colonial Flores do Campo", ""), + 5836 : ("Tubulações e Águas de Márcia", ""), + 5837 : ("Curso de Enólogo", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Biblioteca da Canção de Ninar", ""), + 9503 : ("O Bar da Soneca", ""), + 9504 : (lGagShop, ""), + 9505 : (lToonHQ, ""), + 9506 : (lClothingShop, ""), + 9508 : (lPetShop, ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Pousada A. Ninho", ""), + 9602 : ("Dois Dedos de Prosa com Morfeu pelo Preço de Um", ""), + 9604 : ("Sofá-cama Amarelo do Marcelo", ""), + 9605 : ("Travessa da Canção de Ninar, 323", ""), + 9607 : ("Pijamas Bahamas da Mama", ""), + 9608 : ("Erva-de-gato para Tirar um Cochilo", ""), + 9609 : ("Sono de Pedra por uma Bagatela", ""), + 9613 : ("Relojoeiros das Alturas", ""), + 9616 : ("Companhia Elétrica Luzes Apagadas", ""), + 9617 : ("Travessa da Canção de Ninar, 212", ""), + 9619 : ("Relaxe ao Máximo", ""), + 9620 : ("Serviços de Táxi Py Jama", ""), + 9622 : ("Relógios Sono Atrasado", ""), + 9625 : ("Salão de Beleza Enrolado Crespo", ""), + 9626 : ("Travessa da Canção de Ninar, 818", ""), + 9627 : ("A Tenda dos Sonhos", ""), + 9628 : ("Calendários Já Chega por Hoje", ""), + 9629 : ("Travessa da Canção de Ninar, 310", ""), + 9630 : ("Pedreira Sono de Pedra", ""), + 9631 : ("Conserto de Relógios Inatividade", ""), + 9633 : ("Sala de Projeção da Sonholândia", ""), + 9634 : ("Colchões Descanso da Mente", ""), + 9636 : ("Seguradora Insônia", ""), + 9639 : ("Casa de Hibernação", ""), + 9640 : ("Travessa da Canção de Ninar, 805", ""), + 9642 : ("Serraria Lombeira da Madeira", ""), + 9643 : ("Exames de Vista Olho Fechado", ""), + 9644 : ("Guerras de Travesseiro Noturnas", ""), + 9645 : ("Pousada Unidos Venceremos", ""), + 9647 : ("Loja de Ferragens Faça a sua Cama", ""), + 9649 : ("Ranking do Ronco", ""), + 9650 : ("Travessa da Canção de Ninar, 714", ""), + 9651 : ("Com Muito ou com Ronco", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Agência de Viagens Vôo Noturno", ""), + 9704 : ("Loja de Animais Coruja Noturna", ""), + 9705 : ("Oficina Dormindo ao Volante", ""), + 9706 : ("Clínica Dentária Fada do Dente", ""), + 9707 : ("Centro de Jardinagem Bocejo Matutino", ""), + 9708 : ("Floricultura Leito de Rosas", ""), + 9709 : ("Encanamentos Sonho do Bombeiro", ""), + 9710 : ("Exames de Vista REM", ""), + 9711 : ("Companhia Telefônica Despertador", ""), + 9712 : ("Contagem de Ovelhas - Nós Contamos para Você!", ""), + 9713 : ("Pisca-duro, Pestana e Cabeçada, Advogados", ""), + 9714 : ("Artigos Marítimos Barco dos Sonhos", ""), + 9715 : ("Banco A Fraldinha de Dormir", ""), + 9716 : ("Pipi na Cama Festas", ""), + 9717 : ("Padaria Sonho à Dúzia", ""), + 9718 : ("Sanduíches A Cuca Vai Pegar", ""), + 9719 : ("Fábrica de Travesseiros Tatu", ""), + 9720 : ("Fala Dormindo Fonoaudiólogos", ""), + 9721 : ("Tapetes Aconchego", ""), + 9722 : ("Agência de Talentos Sonho de Menina", ""), + 9725 : ("Pijamas Saco de Gato", ""), + 9727 : ("Cochilou, Dançou", ""), + 9736 : ("Agência de Empregos Trabalho dos Sonhos", ""), + 9737 : ("Escola de Dança Matilda Madruga", ""), + 9738 : ("Casa de Zzzzzs", ""), + 9740 : ("Escola de Esgrima Naná", ""), + 9741 : ("Deu Pulga na Cama Extermínio de Insetos", ""), + 9744 : ("Creme para Rugas Chega de Insônia", ""), + 9752 : ("Petroleira Meia-Noite", ""), + 9753 : ("Sorveteria Luar Gelado", ""), + 9754 : ("Passeios de Pônei Cavalgada Noturna", ""), + 9755 : ("Cinemas Cama Voadora", ""), + 9756 : ("", ""), + 9759 : ("Salão de Beleza Bela Adormecida", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : (lGagShop, ""), + 3508 : (lToonHQ, ""), + 3509 : (lClothingShop, ""), + 3511 : (lPetShop, ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Companhia Elétrica Esplendor do Norte", ""), + 3602 : ("Gorros do Pólo Norte", ""), + 3605 : ("", ""), + 3607 : ("Mago do Lago Congelado", ""), + 3608 : ("Existe um Lugar", ""), + 3610 : ("Hipermercado de Sapatos de Esquimó Quiprocó", ""), + 3611 : ("Rodinho do Leão Marinho", ""), + 3612 : ("Design de Iglus", ""), + 3613 : ("Cicle Geloso", ""), + 3614 : ("Indústria de Cereais Flocos de Neve", ""), + 3615 : ("Pastéis de Forno Lindo Alasca", ""), + 3617 : ("Passeios de Balão Vento Frio", ""), + 3618 : ("Consultoria de Gestão de Crises Grande Coisa!", ""), + 3620 : ("Clínica do Esqui", ""), + 3621 : ("Sorveteria Gelo Derretido", ""), + 3622 : ("", ""), + 3623 : ("Indústria de Pães Torradinha", ""), + 3624 : ("Sanduicheria Abaixo de Zero", ""), + 3625 : ("Aquecedores Tia Freezer", ""), + 3627 : ("Canil São Bernardo", ""), + 3629 : ("Restaurante Sopa de Ervilhas", ""), + 3630 : ("Agência de Viagens Com Gelo em Londres, Com Gelo na França", ""), + 3634 : ("Teleférico Boa Vista", ""), + 3635 : ("Lenha Usada", ""), + 3636 : ("Promoção de Arrepios", ""), + 3637 : ("Skates da Kate", ""), + 3638 : ("Tobogã da Lã", ""), + 3641 : ("Trenó do Chicó", ""), + 3642 : ("Ótica Olho do Furacão", ""), + 3643 : ("Salão Bola de Neve", ""), + 3644 : ("Cubos de Gelo Derretidos", ""), + 3647 : ("Loja de Smokings Pinguim Animado", ""), + 3648 : ("Sorvete Instantâneo", ""), + 3649 : ("Hambrrrgers", ""), + 3650 : ("Antiguidades Antárctica", ""), + 3651 : ("Salsichas Congeladas do Fred Barbicha", ""), + 3653 : ("Joalheria Cristal do Gelo", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Armazém do Inverno", ""), + 3703 : ("", ""), + 3705 : ("Cicle Pingo Congelado para Dois", ""), + 3706 : ("Fábrica de Refrigerantes Treme-treme", ""), + 3707 : ("Neve Doce Neve", ""), + 3708 : ("Loja do Pluto", ""), + 3710 : ("Temperatura em Queda Refeições", ""), + 3711 : ("", ""), + 3712 : ("Vai por Gelo Abaixo", ""), + 3713 : ("Dentista Abaixo de Zero Tiritante", ""), + 3715 : ("Casa de Sopas Tia Ártica", ""), + 3716 : ("Estrada de Sal e Pimenta", ""), + 3717 : ("A Lasca Verbal", ""), + 3718 : ("Designer de Câmaras de Ar", ""), + 3719 : ("Cubo de Gelo no Palitinho", ""), + 3721 : ("Liquidação de Tobogãs Cabeção", ""), + 3722 : ("Loja de Esquis Coelhinho de Neve", ""), + 3723 : ("Bolas de Neve Tremendão", ""), + 3724 : ("Fatos e Fofocas", ""), + 3725 : ("O Nó do Trenó", ""), + 3726 : ("Cobertores com Energia Solar", ""), + 3728 : ("Tratores de Neve Anta Lenta", ""), + 3729 : ("", ""), + 3730 : ("Compra e Venda de Bonecos de Neve", ""), + 3731 : ("Lareiras Portáteis", ""), + 3732 : ("O Nariz Congelado", ""), + 3734 : ("Exames de Vista C. V. Gelo", ""), + 3735 : ("Capas de Gelo Polar", ""), + 3736 : ("Cubos de Gelo com Zelo", ""), + 3737 : ("Restaurante Montanha Abaixo", ""), + 3738 : ("Aquecimento - Aproveite Enquanto está Quente", ""), + 3739 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : (lToonHQ, ""), + 3806 : ("Linha de Comida Alpina", ""), + 3807 : ("Sombras de Marmota Usadas", ""), + 3808 : ("A Cabana Suadoura", ""), + 3809 : ("Elvira Tão Bem", ""), + 3810 : ("O Bom Edredom", ""), + 3811 : ("Seu Anjo de Neve", ""), + 3812 : ("Gatinhos de Luvas", ""), + 3813 : ("Botas de Neve Essenciais", ""), + 3814 : ("Banca de Refrigerantes Gás-na-Boca", ""), + 3815 : ("O Chalé da Peruca", ""), + 3816 : ("Viste o Visco", ""), + 3817 : ("Clube de Trilha de Inverno do País das Maravilhas", ""), + 3818 : ("A Barraca das Pás", ""), + 3819 : ("Serviço de Chaminés Sopro Limpo", ""), + 3820 : ("Brancura de Neve", ""), + 3821 : ("Férias Hibernantes", ""), + 3823 : ("Fundações e Precipitações", ""), + 3824 : ("Nozes Assadas na Fogueira", ""), + 3825 : ("Chapéus do Gato Legal", ""), + 3826 : ("Ai, Minhas Galochas!", ""), + 3827 : ("Grinaldas Corais", ""), + 3828 : ("Terra do Boneco de Neve", ""), + 3829 : ("Área dos Pinheiros", ""), + 3830 : ("Desembaçamento de Óculos Espere-e-Veja", ""), + } + +# DistributedCloset.py +ClosetTimeoutMessage = "Sinto muito, o tempo\n acabou." +ClosetNotOwnerMessage = "Este não é o seu armário, mas você pode experimentar as roupas." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Remover" +ClosetAreYouSureMessage = "Você excluiu algumas roupas. Deseja mesmo excluí-las?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Excluir mesmo %s?" +ClosetShirt = "esta camisa" +ClosetShorts = "este short" +ClosetSkirt = "esta saia" +ClosetDeleteShirt = "Excluir\ncamisa" +ClosetDeleteShorts = "Excluir\nshort" +ClosetDeleteSkirt = "Excluir\nsaia" + +# EstateLoader.py +EstateOwnerLeftMessage = "Sinto muito, o dono desta propriedade saiu. Você será enviado ao pátio em %s segundos" +EstatePopupOK = lOK +EstateTeleportFailed = "Não foi possível ir para casa. Tente novamente!" +EstateTeleportFailedNotFriends = "Sinto muito, %s fica na propriedade de um toon com o qual você não fez amizade." + +# DistributedTarget.py +EstateTargetGameStart = "O jogo do Alvo de Toonar começou!" +EstateTargetGameInst = "Quanto mais acertar no alvo vermelho, mais Toonar você vai receber." +EstateTargetGameEnd = "O jogo de Alvo de Toonar acabou..." + +# DistributedHouse.py +AvatarsHouse = "Casa de\n %s" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Sinto muito, este não é o seu banco." +DistributedBankNotOwner = "Sinto muito, este não é o seu banco." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Vender tudo" +FishTankValue = "Oi, %(name)s! Você tem %(num)s peixe(s) em seu balde, que vale(m) o total de %(value)s balinhas. Deseja vender todos eles?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Vender Tudo" +FlowerBasketValue = "%(name)s, você tem %(num)s flores no seu cesto que valem um total de %(value)s balinhas. Você quer vender todas?" + + +def GetPossesive(name): + if name[-1:] == 'de': + possesive = name + "'" + else: + possesive = name + "" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('Sempre faminto', 'Muito faminto', + 'Às vezes faminto', 'Raramente faminto',), + 'boredomThreshold': ('Sempre chateado', 'Muito chateado', + 'Às vezes chateado', 'Raramente chateado',), + 'angerThreshold': ('Sempre irritado', 'Muito irritado', + 'Às vezes irritado', 'Raramente irritado'), + 'forgetfulness': ('Sempre esquecido', 'Muito esquecido', + 'Às vezes esquecido', 'Raramente esquecido',), + 'excitementThreshold': ('Muito calmo', 'Bem calmo', + 'Bem animado', 'Muito animado',), + 'sadnessThreshold': ('Sempre triste', 'Muitas vezes triste', + 'Às vezes triste', 'Raramente triste',), + 'restlessnessThreshold': ('Sempre inquieto', 'Muito inquieto', + 'Às vezes inquieto', 'Raramente inquieto',), + 'playfulnessThreshold': ('Raramente brincalhão', 'Às vezes brincalhão', + 'Muito brincalhão', 'Sempre brincalhão',), + 'lonelinessThreshold': ('Sempre solitário', 'Muito solitário', + 'Às vezes solitário', 'Raramente solitário',), + 'fatigueThreshold': ('Sempre cansado', 'Muito cansado', + 'Às vezes cansado', 'Raramente cansado',), + 'confusionThreshold': ('Sempre confuso', 'Muito confuso', + 'Às vezes confuso', 'Raramente confuso',), + 'surpriseThreshold': ('Sempre surpreso', 'Muito surpreso', + 'Às vezes surpreso', 'Raramente surpreso',), + 'affectionThreshold': ('Raramente carinhoso', 'Às vezes carinhoso', + 'Muito carinhoso', 'Sempre carinhoso',), + } + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Pressione a tecla \"Page Up\" para ver melhor." + +FireworksValentinesBeginning = "" +FireworksValentinesEnding = "" +FireworksJuly4Beginning = lToonHQ+": Bem-vindo à queima de fogos de verão! Divirta-se com o show!" +FireworksJuly4Ending = lToonHQ+": Espero que tenha gostado do show! Um ótimo verão para você!" +FireworksJuly14Beginning = lToonHQ+"" +FireworksJuly14Ending = lToonHQ+"" +FireworksOctober31Beginning = "" +FireworksOctober31Ending = "" +FireworksNewYearsEveBeginning = lToonHQ+": Feliz Ano Novo!!!!" +FireworksNewYearsEveEnding = lToonHQ+": Gostou dos Fogos? Logo tem mais!" +FireworksBeginning = lToonHQ+": Bem-vindo à queima de fogos de verão! Divirta-se com o show!" +FireworksEnding = lToonHQ+": Espero que tenha gostado do show! Um ótimo verão para você!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "DICA TOON:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Verifique com rapidez o andamento da Tarefa Toon mantendo pressionada a tecla \"End\".", + "Verifique com rapidez a sua Página de piadas mantendo pressionada a tecla \"Home\".", + "Abra a sua Lista de amigos pressionando a tecla \"F7\".", + "Abra ou feche o seu Álbum Toon pressionando a tecla \"F8\".", + "Você pode procurar acima pressionando a tecla \"Page Up\" e abaixo pressionando a tecla \"Page Down\".", + "Pressione a tecla \"Control\" para pular.", + "Pressione a tecla \"F9\" para capturar a tela, que será salva na pasta Toontown do seu computador.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Você pode trocar Códigos de Amigo secreto com alguém conhecido que não seja de Toontown, para permitir um chat aberto com essa pessoa em Toontown.", + "Você pode alterar a resolução de seu vídeo, ajustar o áudio e controlar outras opções na Página de opções do Álbum Toon.", + "Experimente as roupas de seus amigos no armário da casa deles.", + "Você pode ir para casa usando o botão \"Ir para casa\" em seu mapa.", + "Toda vez que você conclui uma Tarefa Toon, seus Pontos de risadas são automaticamente recarregados.", + "Você pode procurar a seleção nas lojas de roupas mesmo sem ter um bilhete de roupas.", + "As recompensas para algumas Tarefas Toon permitem que você carregue mais piadas e balinhas.", + "Você pode ter até 50 amigos na sua Lista de amigos.", + "Algumas recompensas das Tarefas Toon permitem que você se teletransporte para os pátios de Toontown usando a Página do mapa do Álbum Toon.", + "Aumente os seus Pontos de risadas nos pátios, catando tesouros como estrelas e casquinhas de sorvete.", + "Se você precisar se recuperar rápido após uma batalha difícil, vá para a sua propriedade e recolha casquinhas de sorvete.", + "Alterne entre os diversos modos de exibição de seu Toon pressionando a tecla Tab.", + "Algumas vezes, você poderá encontrar várias Tarefas Toon diferentes com a mesma recompensa. Faça sua pesquisa de mercado!", + "Encontrar amigos com Tarefas Toon semelhantes é uma maneira divertida de progredir no jogo.", + "Você nunca precisa salvar o seu progresso em Toontown. Os servidores de Toontown salvam continuamente todas as informações necessárias.", + "Você pode cochichar com outros Toons clicando neles ou selecionando-os em sua Lista de amigos.", + "Algumas frases do Chat rápido têm animações para indicar o estado de espírito do seu Toon.", + "Se a área em que você está se encontra lotada, tente mudar de região. Vá para a Página de região do Álbum Toon e selecione uma diferente.", + "Se você estiver em plena atividade de salvamento de edifícios, ganhará uma estrela de bronze, prata ou ouro, que ficará acima de seu Toon.", + "Se você salvar um número suficiente de edifícios para obter uma estrela acima da cabeça, seu nome pode estar no quadro-negro de um Quartel Toon.", + "Os edifícios salvos, às vezes, são recuperados pelos Cogs. A única maneira de manter a sua estrela é sair em campo e salvar mais edifícios!", + "Os nomes dos seus Amigos secretos aparecerão na cor azul.", + # Fishing + "Veja se você consegue pegar todos os peixes de Toontown!", + "Há peixes diferentes nos diversos lagos. Tente todos!", + "Quando o seu balde de pesca estiver cheio, venda os peixes para os pescadores dos pátios.", + "Venda os peixes para o pescador ou dentro das Lojas de Animais.", + "As varas de pescar mais fortes conseguem pegar peixes mais pesados, mas custam mais balinhas.", + "Você pode comprar varas de pescar mais fortes no Gadálogo.", + "Os peixes mais pesados valem mais balinhas na Loja de animais.", + "Os peixes raros valem mais balinhas na Loja de animais.", + "Às vezes, você consegue encontrar bolsas de balinhas durante a pesca.", + "Algumas Tarefas Toon exigem que você pesque itens fora dos lagos.", + "Os lagos de pesca dos pátios possuem peixes diferentes dos lagos das ruas.", + "Alguns peixes são realmente raros. Continue pescando até pegar todos!", + "O lago da sua propriedade possui peixes que só podem ser encontrados lá.", + "Para cada dez espécies pescadas, você ganhará um troféu de pesca!", + "Você pode ver qual peixe pescou no Álbum Toon.", + "Alguns troféus de pesca o recompensam com um Acréscimo de risadas.", + "A pesca é uma boa maneira de ganhar mais balinhas.", + # Doodles + "Adote um Rabisco na Loja de Animais!", + "As lojas de animais têm Rabiscos novos para vender todos os dias.", + "Visite as lojas de animais todos os dias para ver que Rabiscos novos elas têm.", + "Há diferentes Rabiscos para adoção nos diferentes bairros.", + # Karting + "Mostre o seu carrão e dê uma turbinada no seu limite de Risadas no Autódromo do Pateta. ", + "Entre no Autódromo do Pateta pelo túnel em forma de pneu no pátio do Centro de Toontown.", + "Ganhe pontos de Risada no Autódromo do Pateta.", + "O Autódromo do Pateta tem seis pistas de corrida diferentes. " + ), + + TIP_STREET : ( + "Há quatro tipos de Cogs: Robôs da Lei, Robôs Mercenários, Robôs Vendedores e Robôs-chefe.", + "Cada Método de piadas possui diferentes intensidades de precisão e dano.", + "As piadas sonoras afetam todos os Cogs, mas acordam qualquer Cog iscado.", + "Derrotar os Cogs em ordem estratégica pode aumentar bastante as suas chances de vencer as batalhas.", + "O Método de piadas Toonar permite que você atinja outros Toons na batalha.", + "Os pontos de experiência das piadas são dobrados durante uma Invasão de Cogs!", + "Vários Toons podem se reunir em equipes e usar o mesmo Método de piadas na batalha para conseguir danos extras aos Cogs.", + "Na batalha, as piadas são usadas na ordem de cima para baixo, conforme exibido no Menu de piadas.", + "A fileira de luzes circulares sobre os elevadores do Edifício dos Cogs mostram quantos andares haverá lá dentro.", + "Clique em um Cog para ver mais detalhes.", + "Usar piadas de alto nível contra Cogs de baixo nível não lhe renderá nenhum ponto de experiência.", + "As piadas que rendem experiência possuem um fundo azul no Menu de piadas da batalha.", + "A experiência de piadas é multiplicada quando usada dentro dos Edifícios dos Cogs. Os andares mais altos têm multiplicadores maiores.", + "Quando um Cog é derrotado, cada Toon daquela rodada recebe créditos de Cogs depois que a batalha termina.", + "Cada rua de Toontown possui níveis e tipos diferentes de Cogs.", + "As calçadas são locais seguros, sem Cogs.", + "Nas ruas, as portas laterais contam piadas do tipo toc-toc quando você se aproxima delas.", + "Algumas Tarefas Toon treinam você em novos Métodos de piadas. Você só pode escolher seis dos sete métodos, portanto, escolha direito!", + "As armadilhas só terão utilidade se você ou seus amigos coordenarem o uso de iscas na batalha.", + "As iscas de alto nível têm menos probabilidade de falhar.", + "As piadas de nível baixo oferecem menor precisão contra os Cogs de alto nível.", + "Os Cogs não podem atacar depois que forem \"iscados\" para a batalha.", + "Quando você e seus amigos dominam um Edifício de Cogs, vocês são recompensados com retratos dentro do Edifício dos Toons recuperado.", + "Usar uma piada Toonar em um Toon que possua um Risômetro cheio não renderá nenhuma experiência de Toonar.", + "Os Cogs ficarão atordoados por uns momentos quando atingidos por alguma. Assim, aumentam as chances de outras piadas da mesma rodada os atingirem.", + "As Piadas cadentes têm menos chance de atingir alguém, mas sua precisão aumenta quando os Cogs já tiverem sido atingidos por outra piada na mesma rodada.", + "Quando você já tiver derrotado um número suficiente de Cogs, use o \"Radar de Cogs\" clicando nos ícones de Cogs da página Galeria de Cogs do seu Álbum Toon.", + "Durante uma batalha, você tem como saber qual Cog os seus companheiros de equipe estão atacando; basta olhar para os travessões (-) e para os X.", + "Durante uma batalha, os Cogs carregam uma luz que mostra sua saúde: o verde significa saudável e o vermelho, quase destruído.", + "No máximo, quatro Toons podem guerrear ao mesmo tempo.", + "Na rua, os Cogs têm mais probabilidade de entrar em uma briga contra vários Toons do que contra apenas um Toon.", + "Os dois tipos de Cogs mais difíceis de cada tipo só são encontrados nos edifícios.", + "As Piadas cadentes nunca funcionam contra Cogs iscados.", + "Os Cogs tendem a atacar o Toon que lhes causou danos maiores.", + "As piadas sonoras não rendem danos extras contra Cogs iscados.", + "Se você esperar muito para atacar um Cog iscado, ele acordará. As iscas de nível mais alto têm duração maior.", + "Há lagos de pesca em cada rua de Toontown. Algumas ruas possuem peixes exclusivos.", + ), + + TIP_MINIGAME : ( + "Depois que você preenche a sua jarra de balinhas, qualquer balinha que ganhar nos Jogos no bondinho cairão direto no seu banco.", + "Você pode usar as teclas de seta em vez de o mouse no Jogo no bondinho \"Acompanhe a Minnie\".", + "No Jogo do canhão, você pode usar as teclas de seta para mover o seu canhão e pressionar a tecla \"Control\" para atirar.", + "No Jogo dos anéis, você ganha pontos extras quando todo o grupo consegue nadar com sucesso através dos anéis.", + "Um jogo perfeito de Acompanhe a Minnie dobrará seus pontos.", + "No Cabo de guerra, você ganha mais balinhas se jogar contra um Cog forte.", + "A dificuldade dos Jogos no bondinho varia conforme o bairro; os do Centro de Toontown são os mais fáceis, e os da Sonholândia do Donald são os mais difíceis.", + "Certos Jogos no bondinho só podem ser em grupo.", + ), + + TIP_COGHQ : ( + "Você deve completar o seu Disfarce de Robô Vendedor antes de visitar o VP.", + "Você deve completar o seu Disfarce de Robô Mercenário antes de visitar o Diretor Financeiro.", + "Você deve completar o seu Disfarce de Robô da Lei antes de visitar o Juiz-chefe.", + "Você pode pular em cima de cogs Brutamontes para desativá-los por um tempo.", + "Ganhe Méritos de cogs ao derrotar Robôs Vendedores em batalha.", + "Ganhe Cograna ao derrotar Robôs Mercenários em batalha.", + "Ganhe Avisos de Júri ao derrotar Robôs da Lei em batalha.", + "Você ganha mais Méritos, Cogranas ou Avisos de Júri de Cogs de nível maior.", + "Quando conseguir juntar Méritos o suficiente para merecer uma promoção, vá ver o VP dos Robôs Vendedores!", + "Quando conseguir juntar Cogranas o suficiente para merecer uma promoção, vá ver o Diretor Financeiro dos Robôs Mercenários!", + "Quando conseguir juntar Avisos de Júri o suficiente para merecer uma promoção, vá ver o Juiz-chefe dos Robôs da Lei!", + "Você pode falar como um Cog quando estiver usando o seu Disfarce de Cog.", + "Até oito Toons podem lutar juntos contra o VP dos Robôs Vendedores", + "Até oito Toons podem lutar juntos contra o Diretor Financeiro dos Robôs Mercenários", + "Até oito Toons podem lutar juntos contra o Juiz-chefe dos Robôs da Lei", + "Dentro do Quartel dos Cogs, o caminho é subindo as escadas.", + "Cada vez que lutar numa fábrica do Quartel dos Robôs Vendedores, você vai ganhar uma peça do seu Disfarce de Robô Vendedor.", + "Você pode verificar o progresso do seu Disfarce no seu Álbum Toon.", + "Você pode verificar o progresso da sua promoção na Página de Disfarce do seu Álbum Toon.", + "Certifique-se de estar com as piadas cheias e com o Risômetro cheio antes de ir até um Quartel dos Cogs.", + "Quando for promovido, seu disfarce de Cog será atualizado.", + "Você terá que derrotar o "+Foreman+" para recuperar uma peça do Disfarce de Robô Vendedor.", + "Ganhe peças de disfarce de Robô Mercenário como recompensa de Tarefas Toon na Sonholândia do Donald.", + "Os Robôs Mercenários produzem e distribuem o seu próprio dinheiro, as Cogranas, de três maneiras - Moeda, Dólar e Barra.", + "Espere até que o Diretor Financeiro esteja tonto para lançar um cofre, senão ele vai usá-lo como capacete! Acerte o capacete com outro cofre para derrubá-lo.", + "Ganhe peças de disfarce de Robô da Lei como recompensa de Tarefas Toon pelo Professor Floco.", + "Vale a pena a confusão: os Cogs virtuais no Quartel dos Robôs da Lei não dão Avisos de Júri de recompensa.", + " Robô Mercenário produz e distribui a sua própia moeda, Cogbucks, em três formas diferentes: Moedas, Dólar, e lingotes.", + " Aguarde até que o Diretor Financeiro fique doido para lançar um seguro ou o utilize-o como um capacete! Acerte no capacete com outro seguro para pegá-lo.", + "O Robô da Lei obtém as partes do traje como recompensa ao concluir a TarefaToon para o Professor Floco.", + ), + TIP_ESTATE : ( + # Doodles + "Os Rabiscos entendem algumas frases do Chat rápido. Experimente!", + "Use o menu \"Bichinho\" do Chat rápido para pedir a seu Rabisco que faça truques.", + "Você pode ensinar aos Rabiscos truques com as lições de treinamento do Gadálogo da Clarabela.", + "Recompense o seu Rabisco pelos truques.", + "Se você visitar a propriedade de um amigo, o seu Rabisco lhe fará companhia.", + "Alimente o seu Rabisco com uma balinha quando ele estiver com fome.", + "Clique em um Rabisco para ver um menu no qual você poderá Alimentar, Coçar e Chamá-lo.", + "Os Rabiscos adoram companhia. Convide os amigos para brincar!", + "Todos os Rabiscos possuem personalidades próprias.", + "Você pode devolver o seu Rabisco e adotar outro nas Lojas de Animais.", + "Quando um Rabisco faz um truque, os Toons que o cercam se recuperam.", + "Os Rabiscos ficam ainda melhores nos truques com a prática. Continue assim!", + "Os truques mais avançados dos Rabiscos recuperam os Toons com mais rapidez.", + "Rabiscos com mais experiência podem fazer mais truques sem ficar tão cansados.", + "Veja uma lista de Rabiscos próximos em sua Lista de amigos.", + # Furniture / Cattlelog + "Compre móveis usando o Gadálogo da Clarabela e decore a sua casa.", + "O banco da casa tem mais balinhas.", + "O armário da casa tem mais roupas.", + "Vá até a casa do seu amigo e experimente as roupas dele.", + "Compre varas de pescar melhores no Gadálogo da Clarabela.", + "Compre bancos maiores no Gadálogo da Clarabela.", + "Ligue para a Clarabela usando o telefone da casa.", + "A Clarabela vende um armário maior em que cabem mais roupas.", + "Reserve espaço no seu armário antes de usar o bilhete de roupas.", + "A Clarabela vende tudo o que é preciso para decorar a sua casa.", + "Verifique a sua caixa de correio para ver se há entregas antes de fazer seus pedidos com a Clarabela.", + "As roupas do Gadálogo da Clarabela levam uma hora para serem entregues.", + "Os papéis de parede e pisos do Gadálogo da Clarabela levam uma hora para serem entregues.", + "Os móveis do Gadálogo da Clarabela levam um dia inteiro para serem entregues.", + "Armazene móveis de reserva no sótão.", + "Você será avisado pela Clarabela quando um novo Gadálogo estiver disponível.", + "Você será avisado pela Clarabela quando uma entrega do Gadálogo chegar.", + "Novos Gadálogos são entregues toda semana.", + "Procure os produtos promocionais de estoque limitado no Gadálogo.", + "Mova os móveis indesejados para a lata de lixo.", + # Fish + "Alguns peixes, como a Cavala Trotante, são mais comuns nas propriedades de Toons.", + # Misc + "Você pode convidar os seus amigos para a sua propriedade usando o Chat rápido.", + "Você sabia que a cor da sua casa combina com a cor do seu painel Pegar um Toon?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Compre um Conversível, Utilitário Toon ou Cruzeiro na Loja do Kart do Pateta.", + "Personalize o seu kart com decalques, calotas e muito mais na Loja do Kart do Pateta.", + "Ganhe bilhetes correndo de kart no Autódromo do Pateta.", + "Os bilhetes são a única moeda aceita na Loja do Kart do Pateta.", + "São necessários bilhetes como depósito antes das corridas.", + "Uma página especial do seu Álbum Toon permite que você personalize o seu kart.", + "Uma página especial do seu Álbum Toon permite que você veja os recordes de cada pista.", + "Uma página especial do seu Álbum Toon permite que você veja seus troféus.", + "O Estádio dos Nerds é a pista mais fácil do Autódromo do Pateta.", + "A Pista de Pulos tem o maior número de inclinações e rampas do Autódromo do Pateta.", + "A Avenida da Neve é a pista mais difícil do Autódromo do Pateta.", + ), + TIP_GOLF: ( + # Golfing specific + "Aperte a tecla Tab para ver de cima o percurso de golfe.", + "Aperte a tecla de Seta para Cima para se colocar na direção do buraco de golfe.", + "Balançar o taco é como atirar uma torta.", + ), + } + +FishGenusNames = { + 0 : "Baiacu", + 2 : "Peixe-gato", + 4 : "Peixe-palhaço", + 6 : "Peixe congelado", + 8 : "Estrela-do-mar", + 10 : "Cavala Trotante!", + 12 : "Cachorra", + 14 : "Enguia Amore", + 16 : "Tubarão-enfermeira", + 18 : "Caranguejo-rei", + 20 : "Peixe-lua", + 22 : "Cavalo-marinho", + 24 : "Tubarão Fera", + 26 : "Barra Cursa", + 28 : "Truta Cicuta", + 30 : "Piano Atum", + 32 : "Manteiga de Amendoim e Água-viva", + 34 : "Raia Jamanta", + } + +FishSpeciesNames = { + 0 : ( "Baiacu", + "Baiacu Balão de Ar", + "Baiacu Balão Meteorológico", + "Baiacu Balão de Água", + "Baiacu Balão Vermelho", + ), + 2 : ( "Peixe-gato", + "Peixe-gato Siamês", + "Peixe-gato de Rua", + "Peixe-gato Rajado", + "Peixe-gato Tonto", + ), + 4 : ( "Peixe-palhaço", + "Peixe-palhaço Triste", + "Peixe-palhaço de Festa", + "Peixe-palhaço de Circo", + ), + 6 : ( "Peixe congelado", + ), + 8 : ( "Estrela-do-mar", + "Cinco Estrelas-do-mar", + "Estrela-do-mar do Rock", + "Estrela-do-mar Cintilante", + "Estrela-do-mar All Star", + ), + 10 : ( "Cavala Trotante!", + ), + 12 : ( "Cachorra", + "Cachorra Buldogue", + "Cachorra-quente", + "Cachorra Dálmata", + "Cachorrinha", + ), + 14 : ( "Enguia Amore", + "Enguia Amore Elétrica", + ), + 16 : ( "Tubarão-enfermeira", + "Tubarão-enfermeira Clara", + "Tubarão-enfermeira Flora", + ), + 18 : ( "Caranguejo-rei", + "Caranguejo-rei do Alasca", + "Caranguejo-rei Velho", + ), + 20 : ( "Peixe-lua", + "Peixe-lua Cheia", + "Peixe Meia-lua", + "Peixe-lua Nova", + "Peixe-lua Crescente", + "Peixe-lua da Colheita", + ), + 22 : ( "Cavalo-marinho", + "Cavalo-marinho de Pau", + "Cavalo-marinho Clydesdale", + "Cavalo-marinho Árabe", + ), + 24 : ( "Tubarão-Fera", + "Tubarãozinho Fera", + "Tubarão-Fera da Piscina", + "Tubarão-Fera da Piscina Olímpica", + ), + 26 : ( "Barra Cursa Marrom", + "Barra Cursa Preto", + "Barra Cursa Coala", + "Barra Cursa de Mel", + "Barra Cursa Polar", + "Barra Cursa Panda", + "Barra Cursa Kodiac", + "Barra Cursa Grizzly", + ), + 28 : ( "Truta", + "Capitão Truta Cicuta", + "Truta Cicuta Escorbuta", + ), + 30 : ( "Piano Atum", + "Grande Piano Atum", + "Grande Piano Atum Baby", + "Piano Atum Ereto", + "Músico de Piano Atum", + ), + 32 : ( "Manteiga de Amendoim e Água-viva", + "MA & Água-viva de Uva", + "MA & Água-viva Crocante", + "MA & Água-viva de Morango", + "Concord Grape PB&J Fish", + ), + 34 : ( "Raia Jamanta", + ), + } + +CogPartNames = ( + "Perna superior esquerda", "Perna inferior esquerda", "Pé esquerdo", + "Perna superior direita", "Perna inferior direita", "Pé direito", + "Ombro esquerdo", "Ombro direito", "Peito", "Medidor de saúde", "Quadril", + "Braço superior esquerdo", "Braço inferior esquerdo", "Mão esquerda", + "Braço superior direito", "Braço inferior direito", "Mão direita", + ) + +CogPartNamesSimple = ( + "Busto superior", + ) + +FishFirstNames = ( + "", + "Anjo", + "Ártico", + "Bebê", + "Bermuda", + "Grande", + "Bruna", + "Bolhas", + "Detuna", + "Docinho", + "Capitão", + "Tico", + "Cacho", + "Coral", + "Doutor", + "Arenoso", + "Imperador", + "Canino", + "Gordo", + "Peixinho", + "Flipper", + "Linguado", + "Sardinha", + "Mel", + "João", + "Rei", + "Pequeno", + "Marlin", + "Senhorita", + "Senhor", + "Pêssego", + "Rosado", + "Príncipe", + "Princesa", + "Professor", + "Inchadinho", + "Rainha", + "Arco-íris", + "Raio", + "Rosinha", + "Ferrugem", + "Salgado", + "Samuca", + "Sandy", + "Caspa", + "Tutubarão", + "Cavalheiro", + "Saltador", + "Chinela", + "Guaiúba", + "Malhado", + "Espinho", + "Pintado", + "Estrela", + "Doce", + "Súper", + "Tigre", + "Miúdo", + "Bigode", + ) + +FishLastPrefixNames = ( + "", + "Praia", + "Preto", + "Azul", + "Porcão", + "Machão", + "Gato", + "Fundo", + "Duplo", + "Leste", + "Chique", + "Escamoso", + "Chato", + "Fresco", + "Gigante", + "Ouro", + "Dourado", + "Cinza", + "Verde", + "Presunto", + "Mané", + "Geléia", + "Dama", + "Couro", + "Limão", + "Comprido", + "Nordeste", + "Oceano", + "Octo", + "Óleo", + "Pérola", + "Cachimbo", + "Vermelho", + "Faixa", + "Rio", + "Pedra", + "Rubi", + "Leme", + "Sal", + "Mar", + "Prata", + "Snorkel", + "Só", + "Sudeste", + "Espinhoso", + "Surfe", + "Espada", + "Tigre", + "Triplo", + "Tropical", + "Atum", + "Onda", + "Fraco", + "Oeste", + "Branco", + "Amarelo", + ) + +FishLastSuffixNames = ( + "", + "bola", + "baixo", + "barriga", + "besouro", + "gatuno", + "manteiga", + "garra", + "sapateiro", + "caranguejo", + "rosnador", + "tambor", + "barbatana", + "peixe", + "batedor", + "flipper", + "fantasma", + "roncador", + "cabeça", + "coroa", + "saltador", + "cavala", + "lua", + "boca", + "tainha", + "pescoço", + "nariz", + "galho", + "bruto", + "corredor", + "vela", + "tubarão", + "concha", + "seda", + "limo", + "mordedora", + "fedido", + "rabo", + "sapo", + "truta", + "água", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrada principal" +SellbotLegFactorySpecLobby = "Salão" +SellbotLegFactorySpecLobbyHallway = "Corredor do salão" +SellbotLegFactorySpecGearRoom = "Sala de engrenagens" +SellbotLegFactorySpecBoilerRoom = "Sala da caldeira" +SellbotLegFactorySpecEastCatwalk = "Passarela leste" +SellbotLegFactorySpecPaintMixer = "Misturador de tinta" +SellbotLegFactorySpecPaintMixerStorageRoom = "Depósito do Misturador de tinta" +SellbotLegFactorySpecWestSiloCatwalk = "Passarela do Silo Oeste" +SellbotLegFactorySpecPipeRoom = "Sala de tubulações" +SellbotLegFactorySpecDuctRoom = "Sala de dutos" +SellbotLegFactorySpecSideEntrance = "Entrada lateral" +SellbotLegFactorySpecStomperAlley = "Beco sinistro" +SellbotLegFactorySpecLavaRoomFoyer = "Antecâmara do Salão de lava" +SellbotLegFactorySpecLavaRoom = "Salão de lava" +SellbotLegFactorySpecLavaStorageRoom = "Depósito de lava" +SellbotLegFactorySpecWestCatwalk = "Passarela oeste" +SellbotLegFactorySpecOilRoom = "Sala de óleo" +SellbotLegFactorySpecLookout = "Vigilância" +SellbotLegFactorySpecWarehouse = "Armazém" +SellbotLegFactorySpecOilRoomHallway = "Corredor da Sala de óleo" +SellbotLegFactorySpecEastSiloControlRoom = "Sala de controle do Silo Leste" +SellbotLegFactorySpecWestSiloControlRoom = "Sala de controle do Silo Oeste" +SellbotLegFactorySpecCenterSiloControlRoom = "Sala de controle do Silo Central" +SellbotLegFactorySpecEastSilo = "Silo Leste" +SellbotLegFactorySpecWestSilo = "Silo Oeste" +SellbotLegFactorySpecCenterSilo = "Silo Central" +SellbotLegFactorySpecEastSiloCatwalk = "Passarela do Silo Leste" +SellbotLegFactorySpecWestElevatorShaft = "Eixo do Elevador Oeste" +SellbotLegFactorySpecEastElevatorShaft = "Eixo do Elevador Leste" + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "VITÓRIA!!" +FishBingoJackpot = "GRANDE PRÊMIO!" +FishBingoGameOver = "FIM DO JOGO" +FishBingoIntermission = "Intervalo\nTermina em:" +FishBingoNextGame = "Próximo jogo\nComeça em:" +FishBingoTypeNormal = "Clássico" +FishBingoTypeCorners = "Quatro cantos" +FishBingoTypeDiagonal = "Diagonais" +FishBingoTypeThreeway = "Três vias" +FishBingoTypeBlockout = "BLOQUEADO!" +FishBingoStart = "Está na hora do Bingo dos Peixes! Vá para qualquer píer disponível para jogar!" +FishBingoOngoing = "Bem-vindo! O Bingo dos Peixes já está rolando." +FishBingoEnd = "Espero que tenha se divertido no jogo Bingo dos Peixes." +FishBingoHelpMain = "Bem-vindo ao Bingo dos Peixes de Toontown! Todo mundo trabalha em conjunto no lago para preencher a cartela antes de acabar o tempo." +FishBingoHelpFlash = "Quando você pegar um peixe, clique em um dos quadrados piscantes para marcar a cartela." +FishBingoHelpNormal = "É uma cartela de Bingo Clássico. Para ganhar, complete qualquer linha vertical, horizontal ou na diagonal." +FishBingoHelpDiagonals = "Complete as duas diagonais para ganhar." +FishBingoHelpCorners = "Uma cartela de Cantos fácil. Complete todos os quatro cantos para ganhar." +FishBingoHelpThreeway = "Três vias. Complete ambas as diagonais e a linha do meio para ganhar. Esta não é fácil não!" +FishBingoHelpBlockout = "Bloqueado! Complete a cartela inteira para ganhar. Você está competindo contra todos os outros lagos e a bolada é grande!" +FishBingoOfferToSellFish = "O seu balde de pesca está cheio. Quer vender os seus peixes?" +FishBingoJackpotWin = "Ganhe %s balinhas!" +FishBingoJackpot = "Ganhe %s balinhas!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Toonar" +ResistanceToonupItem = "%s toonar" +ResistanceToonupItemMax = "Máx." +ResistanceToonupChat = "Toons de todo o mundo: vamos toonar!" +ResistanceRestockMenu = "Doar Piadas" +ResistanceRestockItem = "Doar Piadas %s" +ResistanceRestockItemAll = "Tudo" +ResistanceRestockChat = "Toons de todo o mundo: vamos piadar!" +ResistanceMoneyMenu = "Balinhas" +ResistanceMoneyItem = "%s balinhas" +ResistanceMoneyChat = "Toons de todo o mundo: gastem com consciência!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": Bem-vindo à Resistência!" +ResistanceEmote2 = NPCToonNames[9228] + ": Use a sua nova expressão para se identificar com outros membros." +ResistanceEmote3 = NPCToonNames[9228] + ": Boa sorte!" + +# Kart racing +KartUIExit = "Deixar o kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Comprar kart" +KartShop_BuyAccessories = "Comprar acessórios" +KartShop_BuyAccessory = "Comprar acessório" +KartShop_Cost = "Custo: %d bilhetes" +KartShop_ConfirmBuy = "Comprar %s por %d bilhetes?" +KartShop_NoAvailableAcc = "Não há acessórios deste tipo" +KartShop_FullTrunk = "A mala está cheia." +KartShop_ConfirmReturnKart = "Tem certeza de que quer devolver o seu kart atual?" +KartShop_ConfirmBoughtTitle = "Parabéns!" +KartShop_NotEnoughTickets = "Não há bilhetes suficientes!" + +KartView_Rotate = "Girar" +KartView_Right = "Direita" +KartView_Left = "Esquerda" + +# starting block +StartingBlock_NotEnoughTickets = "Você não tem bilhetes suficientes! Experimente participar de um treino." +StartingBlock_NoBoard = "O embarque para esta corrida terminou. Espere o início da próxima corrida." +StartingBlock_NoKart = "Primeiramente, você precisa de um kart! Por que você não pergunta a um dos funcionários da Loja do kart?" +StartingBlock_Occupied = "Este bloco já está ocupado! Procure outro ponto." +StartingBlock_TrackClosed = "Desculpe, esta pista está fechada para reformas." +StartingBlock_EnterPractice = "Deseja participar do treino?" +StartingBlock_EnterNonPractice = "Deseja participar de uma corrida %s por %s bilhetes?" +StartingBlock_EnterShowPad = "Deseja estacionar o seu carro aqui?" +StartingBlock_KickSoloRacer = "As corridas Batalha dos Toons e Grande Prêmio requerem dois ou mais participantes." +StartingBlock_Loading = "Indo para a corrida!" + +#stuff for leader boards +LeaderBoard_Time = "Tempo" +LeaderBoard_Name = "Nome do piloto" +LeaderBoard_Daily = "Pontuação diária" +LeaderBoard_Weekly = "Pontuação semanal" +LeaderBoard_AllTime = "Melhor pontuação de todos os tempos" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Treino", + "Batalha dos Toons", + "Torneio", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "Largar!" +KartRace_Reverse = " Rev" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Estádio dos Nerds", + RaceGlobals.RT_Speedway_1_rev : "Estádio dos Nerds" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Autódromo Rústico", + RaceGlobals.RT_Rural_1_rev : "Autódromo Rústico" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "Circuito da Cidade", + RaceGlobals.RT_Urban_1_rev : "Circuito da Cidade" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Coliseu Saca-Rolhas", + RaceGlobals.RT_Speedway_2_rev : "Coliseu Saca-Rolhas" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Pista de Pulos", + RaceGlobals.RT_Rural_2_rev : "Pista de Pulos" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Avenida da Neve", + RaceGlobals.RT_Urban_2_rev : "Avenida da Neve" + KartRace_Reverse, + } + +KartRace_Unraced = "N/D" + +KartDNA_KartNames = { + 0:"Cruzeiro", + 1:"Conversível", + 2:"Utilitário Toon" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Filtro de ar", + 1001: "Carburador quádruplo", + 1002: "Águia", + 1003: "Chifres", + 1004: "Seis cilindros", + 1005: "Aerofólio pequeno", + 1006: "Válvulas simples", + 1007: "Aerofólio médio", + 1008: "Carburador simples", + 1009: "Corneta", + 1010: "Aerofólio simétrico", + #spoiler accessory names + 2000: "Asa", + 2001: "Peça recondicionada", + 2002: "Gaiola", + 2003: "Aleta", + 2004: "Asa dupla", + 2005: "Asa simples", + 2006: "Peça sobressalente padrão", + 2007: "Aleta", + 2008: "ps9", + 2009: "ps10", + #front wheel well accessory names + 3000: "Buzina dupla", + 3001: "Para-choques do Joe", + 3002: "Estribos de cobalto", + 3003: "Descarga lateral cobra", + 3004: "Descarga lateral reta", + 3005: "Para-choques vazados", + 3006: "Estribos de carbono", + 3007: "Estribos de madeira", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "Canos de descarga traseiros curvos", + 4001: "Para-lamas", + 4002: "Escapamento duplo", + 4003: "Aletas duplas lisas", + 4004: "Para-lamas lisos", + 4005: "Escapamento quadrado", + 4006: "Acabamento duplo", + 4007: "Megaescapamento", + 4008: "Aletas duplas simétricas", + 4009: "Aletas duplas redondas", + 4010: "Para-lamas simétricos", + 4011: "Para-lamas do Mickey", + 4012: "Para-lamas vazados", + #rim accessoKartRace_Exit = "Sair da Corrida"ry names + 5000: "Turbo", + 5001: "Lua", + 5002: "Emendado", + 5003: "Três raios", + 5004: "Pintura da tampa", + 5005: "Coração", + 5006: "Mickey", + 5007: "Cinco raios", + 5008: "Margarida", + 5009: "Basquete", + 5010: "Hipnótico", + 5011: "Tribal", + 5012: "Diamante", + 5013: "Cinco raios", + 5014: "Roda", + #decal accessory names + 6000: "Número cinco", + 6001: "Respingo", + 6002: "Quadriculado", + 6003: "Chamas", + 6004: "Corações", + 6005: "Bolhas", + 6006: "Tigre", + 6007: "Flores", + 6008: "Raio", + 6009: "Anjo", + #paint accessory names + 7000: "Verde-limão", + 7001: "Pêssego", + 7002: "Vermelho vivo", + 7003: "Vermelho", + 7004: "Castanho", + 7005: "Siena", + 7006: "Marrom", + 7007: "Canela", + 7008: "Coral", + 7009: "Laranja", + 7010: "Amarelo", + 7011: "Creme", + 7012: "Cítrico", + 7013: "Limão", + 7014: "Verde-água", + 7015: "Verde", + 7016: "Azul-claro", + 7017: "Verde-azulado", + 7018: "Azul", + 7019: "Verde-musgo", + 7020: "Azul-turquesa", + 7021: "Azul-cinzento", + 7022: "Lilás", + 7023: "Púrpura", + 7024: "Rosa", + 7025: "Ameixa", + 7026: "Preto", + } + +RaceHoodSpeedway = "Autódromo" +RaceHoodRural = "Rural" +RaceHoodUrban = "Urbano" +RaceTypeCircuit = "Torneio" +RaceQualified = "classificado" +RaceSwept = "varrido" +RaceWon = "venceu" +Race = "corrida" +Races = "corridas" +Total = "total" +GrandTouring = "Gran Turismo" + +def getTrackGenreString(genreId): + genreStrings = [ "Autódromo", + "País", + "Cidade" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodRural + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodRural + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + str(RaceGlobals.TrophiesPerCup * 2) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + str(RaceGlobals.TrophiesPerCup * 3) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + ] + +KartRace_TitleInfo = "Preparar para a corrida" +KartRace_SSInfo = "Bem-vindo ao Estádio dos Nerds!\nPé na tábua e segure firme!" +KartRace_CoCoInfo = "Bem-vindo ao Coliseu Saca-Rolhas!\nUse as curvas inclinadas para manter a velocidade!\n" +KartRace_RRInfo = "Bem-vindo ao Autódromo Rústico!\nPreserve os animais e permaneça na pista!\n" +KartRace_AAInfo = "Bem-vindo à Pista de Pulos!\nSegure firme! O caminho parece acidentado...\n" +KartRace_CCInfo = "Bem-vindo ao Circuito da Cidade!\nCuidado com os pedestres quando passar pelo centro da cidade!\n" +KartRace_BBInfo = "Bem-vindo à Avenida da Neve!\nCuidado com a velocidade. Pode ter gelo na pista.\n" +KartRace_GeneralInfo = "Use Ctrl para lançar as piadas que pegar na pista, e as teclas de setas, para controlar o kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'diariamente', + RaceGlobals.Weekly : 'semanalmente', + RaceGlobals.AllTime : 'o tempo todo', + } + +KartRace_FirstSuffix = 'o' +KartRace_SecondSuffix = ' o' +KartRace_ThirdSuffix = ' o' +KartRace_FourthSuffix = ' o' +KartRace_WrongWay = 'Direção\nerrada!' +KartRace_LapText = "Volta %s" +KartRace_FinalLapText = "Volta final!" +KartRace_Exit = "Sair da corrida" +KartRace_NextRace = "Próxima Corrida" +KartRace_Leave = "Deixar a corrida" +KartRace_Qualified = "Classificado!" +KartRace_Record = "Recorde!" +KartRace_RecordString = 'Você bateu um novo recorde %s para %s! Seu bônus é %s bilhetes.' +KartRace_Tickets = "Bilhetes" +KartRace_Exclamations = "!" +KartRace_Deposit = "Depósito" +KartRace_Winnings = "Vitórias" +KartRace_Bonus = "Bônus" +KartRace_RaceTotal = "Total da corrida" +KartRace_CircuitTotal = "Total do Circuito" +KartRace_Trophies = "Troféus" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s" + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" + KartRace_Tickets +KartRace_QualifyPhrase = "Classificar:\n" +KartRace_RaceTimeout = "Tempo esgotado nesta corrida. Seus bilhetes foram reembolsados. Continue tentando!" +KartRace_RaceTimeoutNoRefund = "O tempo da corrida esgotou. Seus bilhetes não foram reembolsados porque o Grande Prêmio já começou. Continue tentando!" +KartRace_RacerTooSlow = "Você demorou demais para terminar a corrida. Seus bilhetes não foram reembolsados. Continue tentando!" +KartRace_PhotoFinish = "Foto da chegada!" +KartRace_CircuitPoints = 'Pontos do Circuito' + +CircuitRaceStart = "O Grande Prêmio de Toontown está prestes a começar! Para vencer, ganhe o maior número de pontos em três corridas consecutivas!" +CircuitRaceOngoing = "Olá! O Grande Prêmio de Toontown está acontecendo agora." +CircuitRaceEnd = "E por hoje é só do Grande Prêmio de Toontown no Autódromo do Pateta. Vejo você na próxima segunda-feira!" + +# Trick-or-Treat holiday +TrickOrTreatMsg = 'Você já encontrou\nesta gostosura!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Humm, o que temos na pauta de casos hoje?" +LawbotBossTempIntro1 = "Arrá, temos o julgamento de um Toon!" +LawbotBossTempIntro2 = "O caso da promotoria é forte." +LawbotBossTempIntro3 = "E aqui estão os defensores públicos." +LawbotBossTempIntro4 = "Espere um pouco... Vocês são Toons!" +LawbotBossTempJury1 = "A seleção do júri vai começar agora." +LawbotBossHowToGetEvidence = "Toque na tribuna da testemunha para pegar a evidência." +LawbotBossTrialChat1 = "A sessão da Corte está aberta" +LawbotBossHowToThrowPies = "Aperte a tecla Insert para arremessar a evidência\n nos advogados ou na balança!" +LawbotBossNeedMoreEvidence = "Você precisa de mais evidências!" +LawbotBossDefenseWins1 = "Impossível! A defesa venceu?" +LawbotBossDefenseWins2 = "Não. Eu declaro este julgamento nulo! Um novo julgamento será agendado." +LawbotBossDefenseWins3 = "Humpf. Estarei na minha sala." +LawbotBossProsecutionWins = "Eu julgo em favor do querelante" +LawbotBossReward = "O prêmio é uma promoção e a habilidade de evocar Cogs" +LawbotBossLeaveCannon = "Deixar canhão" +LawbotBossPassExam = "Bah, e daí que você passou no exame da ordem dos advogados?" +LawbotBossTaunts = [ + "%s, eu julgo você em desacato desta corte!", + "Objeção aceita!", + "Apague isso dos registros.", + "Sua apelação foi rejeitada. A sua sentença é a tristeza!", + "Ordem na corte!", + ] +LawbotBossAreaAttackTaunt = "Vocês todos estão em desacato da corte!" + +WitnessToonName = "Abel Abelhudo" +WitnessToonPrepareBattleTwo = "Oh, não! Eles estão colocando apenas Cogs no júri!\aRápido, use os canhões e atire alguns jurados Toons nas cadeiras do júri.\aPrecisamos de %d para ter uma balança justa." +WitnessToonNoJuror = "Oh-oh, sem jurados Toons. Vai ser um julgamento difícil." +WitnessToonOneJuror = "Legal! Tem 1 Toon no júri!" +WitnessToonSomeJurors = "Legal! Tem %d Toons no júri!" +WitnessToonAllJurors = "Irado! Todos os jurados são Toons!" +WitnessToonPrepareBattleThree = "Rápido, toque na tribuna da testemunha para pegar evidências.\aAperte a tecla Insert para arremessar a evidência nos advogados, ou no prato da defesa." +WitnessToonCongratulations = "Você conseguiu! Obrigado por uma defesa espetacular!\aAqui ,fique com estes papéis deixados pelo Juiz-chefe.\aCom isto você será capaz de evocar Cogs da sua página Galeria de Cogs." + +WitnessToonLastPromotion = "\aUau, você atingiu o nível %s do seu Disfarce de Cog!\aOs Cogs não são promovidos mais que isso.\aVocê não pode mais atualizar o seu Disfarce de Cog, mas ainda pode continuar trabalhando pela Resistência!" +WitnessToonHPBoost = "\aVocê fez muito pela Resistência.\aO Conselho de Toons decidiu lhe dar mais um ponto de Risada. Parabéns!" +WitnessToonMaxed = "\aVejo que tem um Disfarce de Cog nível %s. Impressionante!\aEm nome de todo o Conselho de Toons, obrigado por voltar para defender mais Toons!" +WitnessToonBonus = "Maravilhoso! Todos os advogados estão atordoados. O peso da sua evidência foi %s vezes mais denso por %s segundos" + +WitnessToonJuryWeightBonusSingular = { + 6: 'Este caso é difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', + 7: 'Este caso é muito difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', + 8: 'Este caso é o mais difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'Este caso é difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', + 7: 'Este caso é muito difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', + 8: 'Este caso é o mais difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', +} + +# Cog Summons stuff +IssueSummons = "Evocar" +SummonDlgTitle = "Evocar um Cog" +SummonDlgButton1 = "Evocar um Cog" +SummonDlgButton2 = "Evocar um Prédio Cog" +SummonDlgButton3 = "Evocar uma Invasão Cog" +SummonDlgSingleConf = "Gostaria de evocar um %s?" +SummonDlgBuildingConf = "Gostaria de evocar um %s para um prédio Toon próximo?" +SummonDlgInvasionConf = "Gostaria de evocar uma invasão de %s?" +SummonDlgNumLeft = "Você tem %s sobrando." +SummonDlgDelivering = "Evocando..." +SummonDlgSingleSuccess = "Você evocou o Cog com sucesso." +SummonDlgSingleBadLoc = "Desculpe, mas cogs são proibidos aqui. Tente em outro lugar." +SummonDlgBldgSuccess = "Você evocou os Cogs com sucesso. %s concordou em deixá-los tomar %s por um tempo!" +SummonDlgBldgSuccess2 = "Você evocou os Cogs com sucesso. Um Dono de Loja concordou em deixá-los tomar o prédio dele por um tempo!" +SummonDlgBldgBadLoc = "Desculpe, não há prédios Toon por perto para os Cogs tomarem." +SummonDlgInvasionSuccess = "Você evocou os Cogs com sucesso. É uma invasão!" +SummonDlgInvasionBusy = "Um %s não pôde ser encontrado. Tente novamente quando a invasão dos Cogs terminar." +SummonDlgInvasionFail = "Desculpe, a invasão dos Cogs fracassou." +SummonDlgShopkeeper = "O Dono da Loja " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": Bem-vindo ao Lugar Polar!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Tente isto." +PolarPlaceEffect3 = NPCToonNames[3306] + ": A sua nova aparência só vai funcionar em " + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "Caça-Caveiras!" +LaserGameRoll = "Combinando" +LaserGameAvoid = "Evite as Caveiras" +LaserGameDrag = "Arraste três da mesma cor em uma fileira" +LaserGameDefault = "Jogo Desconhecido" + +# Pinball text +#PinballHiScore = "Maior Pontuação: %d %s\n" +#PinballYourBestScore = "Sua Melhor Pontuação: %d\n" +#PinballScore = "Pontuação: %d x %d : %d" +PinballHiScore = "Maior Pontuação: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Sua Melhor Pontuação:\n" +PinballScore = "Pontuação: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Árvore de Piada de Pena" +GagTreeJugglingBalls = "Árvore de Piada de Bolinhas de Malabarismo" +StatuaryFountain = "Fonte" +StatuaryToonStatue = "Estátua de Toon" +StatuaryDonald = "Estátua do Donald" +StatuaryMinnie = "Estátua da Minnie" +StatuaryMickey1 = "Estátua do Mickey" +StatuaryMickey2 = "Fonte do Mickey" +StatuaryToon = "Estátua de Toon" +StatuaryToonWave = "Estátua da Onda Toon" +StatuaryToonVictory = "Estátua da Vitória Toon" +StatuaryToonCrossedArms = 'Estátua da Autoridade Toon' +StatuaryToonThinking = 'Estátua do Abraço Toon' +StatuaryMeltingSnowman =' Boneco de neve Derretendo' +StatuaryGardenAccelerator = "Fertilizante Instantâneo" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Vermelha','Laranja','Violeta','Azul','Rosa','Amarela','Branca','Verde'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Margarida', + 50: 'Tulipa', + 51: 'Cravo', + 52: 'Lírio', + 53: 'Narciso', + 54: 'Tilápia', + 55: 'Petúnia', + 56: 'Rosa', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('Margarida Lida', + 'Margarida Sumida', + 'Margarida Querida', + 'Margarida Lambida', + 'Margarida Caída', + 'Margarida Subida', + 'Margarida Enlouquecida', + 'Margarida Esclarecida', + ), + 50: ('Eulipa', + 'Tulipas', + 'Elelipa', + ), + 51: ('Encravou', + 'Cravado', + 'Cravo Híbrido', + 'Cravo de Lado', + 'Cravo Modelo', + ), + 52: ('De Lírio', + 'Co Lírio', + 'Lírio Selvagem', + 'Lírio Figueiro', + 'Lírio Pimenta', + 'Lírio Bobo', + 'Eclírio', + 'Lírio Dílio', + ), + 53: ('Nar-sorriso', + 'Nariz Ciso', + 'Narcisudo', + 'Ante Narciso', + ), + 54: ('Tilápia Pudo', + 'Ene-A-O-Tilápia', + 'Tilapiano', + 'Tilapiada', + 'Tilápia Sábia', + ), + 55: ('Car Petúnia', + 'Platúnia', + ), + 56: ("Última Rosa do Verão", + 'Choque de Rosa', + 'Rosa Tinta', + 'Rosa Fedida', + 'Rosa Aindarrosa', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "Latão", + 1 : "Bronze", + 2 : "Prata", + 3 : "Ouro", + } +WateringCanNameDict = { + 0 : "Pequeno", + 1 : "Médio", + 2 : "Grande", + 3 : "Enorme", + } +GardeningPlant = "Plantar" +GardeningWater = "Regar" +GardeningRemove = "Remover" +GardeningPick = "Colher" +GardeningFull = "Encher" +GardeningSkill = "Habilidade" +GardeningWaterSkill = "Habilidade na Água" +GardeningShovelSkill = "Habilidade com a Pá" +GardeningNoSkill = "Nenhuma Habilidade" +GardeningPlantFlower = "Plantar\nFlor" +GardeningPlantTree = "Plantar\nÁrvore" +GardeningPlantItem = "Plantar\nItem" +PlantingGuiOk = "Plantar" +PlantingGuiCancel = lCancel +PlantingGuiReset = "Restaurar" +GardeningChooseBeans = "Escolha as balinhas que deseja plantar." +GardeningChooseBeansItem = "Escolha as balinhas / item que deseja plantar." +GardeningChooseToonStatue = "Escolha o Toon do qual deseja criar uma estátua." +GardenShovelLevelUp = "Parabéns, você ganhou uma nova pá!" +GardenShovelSkillLevelUp = "Parabéns! Você atingiu %(oldbeans)d violetas! Para progredir, você deve coletar %(newbeans)d violetas." +GardenShovelSkillMaxed = "Incrível! Você superou sua habilidade com a pá!" + +GardenWateringCanLevelUp = "Parabéns, você ganhou um novo regador!" +GardenMiniGameWon = "Parabéns, você regou a planta!" +ShovelTin = "Pá de Latão" +ShovelSteel = "Pá de Aço" +ShovelSilver = "Pá de Prata" +ShovelGold = "Pá de Ouro" +WateringCanSmall = "Regador Pequeno" +WateringCanMedium = "Regador Médio" +WateringCanLarge = "Regador Grande" +WateringCanHuge = "Regador Enorme" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('vermelha', 'verde', 'laranja','lilás','azul','rosa','amarela', + 'ciano','prata') +PlantItWith = " Plante com %s." +MakeSureWatered = " Primeiramente, certifique-se de que todas as plantas foram regadas." +UseFromSpecialsTab = "Use por meio da guia de especiais na página do jardim." +UseSpecial = "Usar Especial" +UseSpecialBadLocation = 'Você só pode usar isso no seu jardim.' +UseSpecialSuccess = 'Sucesso! Suas plantas regadas acabaram de crescer.' +ConfirmWiltedFlower = "%(plant)s murchou. Tem certeza de que quer removê-la? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmUnbloomingFlower = "%(plant)s não está desabrochando. Tem certeza de que quer removê-la? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmNoSkillupFlower = "Tem certeza de que quer remover %(plant)s? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmSkillupFlower = "Tem certeza de que quer colher %(plant)s? Ela irá para o seu cesto de flores. Você vai receber um aumento de habilidade." +ConfirmMaxedSkillFlower = "Tem certeza que quer colher as %(plant)s? Elas irão para sua cesta de flores. Suas habilidades NÃO aumentarão pois você já atingiu o máximo." +ConfirmBasketFull = "Seu cesto de flores está cheio. Venda algumas flores primeiro." +ConfirmRemoveTree = "Tem certeza de que quer remover %(tree)s?" +ConfirmWontBeAbleToHarvest = " Se você remover esta árvore, você não colherá piadas das árvores mais altas." +ConfirmRemoveStatuary = "Tem certeza de que quer apagar para sempre %(item)s?" +ResultPlantedSomething = "Parabéns! Você acaba de plantar %s." +ResultPlantedSomethingAn = "Parabéns! Você acaba de plantar %s." +ResultPlantedNothing = "Isso não funcionou. Por favor, tente uma combinação diferente de balinhas." + +GardenGagTree = "TODO??? " +GardenUberGag = "TODO??? " + +def getRecipeBeanText(beanTuple): + """ + dado um múltiplo de balinhas, ex. (0,6), retorna uma versão de texto para + ser exibida para o usuário. (ex: uma balinha vermelha e amarela) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s balinhas" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "uma balinha %s" % BeanColorWords[beanTuple[0]] + else: + retval += 'a' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " e balinha %s" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Balas Mágicas" +GardenTextMagicBeansB = "Outras Balas" +GardenSpecialDiscription = "Este texto deveria explicar como usar certo especial do jardim" +GardenSpecialDiscriptionB = "Este texto deveria explicar como usar certo especial do jardim, podicrê!" +GardenTrophyAwarded = "Uau! Você tem %s de %s flores. Isso merece um troféu e uma melhora na Risada!" +GardenTrophyNameDict = { + 0 : "Carrinho de Mão", + 1 : "Pás", + 2 : "Flor", + 3 : "Regador", + 4 : "Tubarão", + 5 : "Peixe-Espada", + 6 : "Baleia Assassina", + } +SkillTooLow = "Habilidade\nBaixa Demais" +NoGarden = "Nenhum \nJardim" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "Trilhos de Bonde" +TravelGameInstructions = "Clique para cima ou para baixo para definir seu número de votos. Clique no botão votar para lançar os votos. Chegue ao seu objetivo secreto para conseguir balinhas extras. Ganhe mais votos quando se der bem nos outros jogos." +TravelGameRemainingVotes = "Votos Restantes:" +TravelGameUse = "Usar" +TravelGameVotesWithPeriod = "votos." +TravelGameVotesToGo = "votos restantes" +TravelGameVoteToGo = "voto restante" +TravelGameUp = "PARA CIMA." +TravelGameDown = "PARA BAIXO." +TravelGameVoteWithExclamation = "Vote!" +TravelGameWaitingChoices = "Esperando que outros jogadores votem..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['PARA CIMA', 'PARA BAIXO'] +TravelGameTotals = 'Totais ' +TravelGameReasonVotesPlural = 'O bonde está indo para %(dir)s, vencendo por %(numVotes)de votos.' +TravelGameReasonVotesSingular = 'O bonde está indo para %(dir)s, vencendo por %(numVotes)de voto.' +TravelGameReasonPlace = '%(name)s desempatou. O bonde está indo para %(dir)s.' +TravelGameReasonRandom = 'O bonde está indo aleatoriamente para %(dir)s.' +TravelGameOneToonVote = "%(name)s usou %(numVotes)s votos para ir para %(dir)s\n" +TravelGameBonusBeans = "%(numBeans)de Balinhas" +TravelGamePlaying = 'A seguir, o jogo do bonde de %(game)s.' +TravelGameGotBonus = '%(name)s ganhou um bônus de %(numBeans)s balinhas!' +TravelGameNoOneGotBonus = "Ninguém chegou ao seu objetivo secreto. Todos ganham 1 balinha." +TravelGameConvertingVotesToBeans = "Convertendo alguns votos em balinhas..." +TravelGameGoingBackToShop ="Só resta 1 jogador. Indo para a Loja de Piadas do Pateta." + +PairingGameTitle = "Jogo de Memória Toon" +PairingGameInstructions = "Aperte Delete para virar uma carta. Combine 2 cartas iguais para marcar um ponto. Combine cartas com o brilho de bônus e ganhe um ponto extra. Ganhe mais pontos virando poucas vezes." +PairingGameInstructionsMulti = "Aperte Delete para virar uma carta. Aperte Ctrl para fazer o sinal para outro jogador virar uma carta. Combine 2 cartas iguais para marcar um ponto. Combine cartas com o brilho de bônus e ganhe um ponto extra. Ganhe mais pontos virando poucas vezes." +PairingGamePerfect = 'PERFEITO!!' +PairingGameFlips = 'Viradas:' +PairingGamePoints = 'Pontos:' + +TrolleyHolidayStart = "Vamos começar com os Trilhos de Bonde! Para jogar, embarque em qualquer bonde com 2 ou mais Toons." +TrolleyHolidayOngoing = "" +TrolleyHolidayEnd = "Isso é tudo nos Trilhos de Bonde por hoje. Até a próxima semana!" + +TrolleyWeekendStart = "O Fim de Semana dos Trilhos de Bonde vai começar! Para jogar, embarque em qualquer bonde com 2 ou mais Toons." +TrolleyWeekendEnd = "Terminamos com o Fim de Semana dos Trilhos de Bonde." + +VineGameTitle = "Cipós da Selva" +VineGameInstructions = "Chegue ao cipó mais à direita a tempo. Aperte para Cima ou para Baixo para escalar o cipó. Aperte para Esquerda ou Direita para mudar de direção e pular. Quanto mais baixo você estiver no cipó, mais rápido poderá saltar dele. Colete as bananas se puder, mas evite os morcegos e aranhas." + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "Tacada e Caminhada", + 1: "Tacadas Divertidas", + 2: "Todas as Tacadas" + } + +GolfHoleNames = { + 0: 'Vitória-em-Uma', + 1: 'Sem Dúvida até o Buraco', + 2: 'Só na Descida', + 3: 'Só Vejo Verde', + 4: 'Tacadas Quentes', + 5: 'É na Manteiga', + 6: 'Balanço do Taco', + 7: 'Na Tacada das Cinco Horas', + 8: 'Diversão no Gramadão', + 9: 'A Bola Cai e a Gente Vibra', + 10: 'Nada de Bogey', + 11: 'Hora do Taco', + 12: 'Santa Tacada!', + 13: 'Só um Birdie, Vai', + 14: 'Correndo para o Buraco', + 15: 'Hora da Tacada', + 16: 'Buraco ao Alcance', + 17: 'Mais um Vento e Chega', + 18: 'Vitória-em-Uma-2', + 19: 'Sem Dúvida, até o Buraco-2', + 20: 'Só na Descida-2', + 21: 'Só Vejo Verde-2', + 22: 'Tacadas Quentes-2', + 23: 'É na Manteiga-2', + 24: 'Balanço do Taco-2', + 25: 'Na Tacada das Cinco Horas-2', + 26: 'Diversão no Gramadão-2', + 27: 'A Bola Cai e a Gente Vibra-2', + 28: 'Nada de Bogey-2', + 29: 'Hora do Taco-2', + 30: 'Santa Tacada!-2', + 31: 'Só um Birdie, Vai-2', + 32: 'Correndo para o Buraco-2', + 33: 'Hora da Tacada-2', + 34: 'Buraco ao Alcance-2', + 35: 'Mais um Vento e Chega-2', + } + +GolfHoleInOne = "Buraco-em-Uma" +GolfCondor = "Condor" # four Under Par +GolfAlbatross = "Albatroz" # three under par +GolfEagle = "Águia" # two under par +GolfBirdie = "Passarinho" # one under par +GolfPar = "Par" +GolfBogey = "Bogey" # one over par +GolfDoubleBogey = "Bogey Duplo" # two over par +GolfTripleBogey = "Bogey Triplo" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "Percursos Concluídos" +CoursesUnderPar = "Percursos Abaixo do Par" +HoleInOneShots = "Jogadas de Buraco-em-Uma" +EagleOrBetterShots = "Jogadas de Eagle ou Melhor" +BirdieOrBetterShots = "Jogadas de Birdie ou Melhor" +ParOrBetterShots = "Jogadas de Par ou Melhor" +MultiPlayerCoursesCompleted = "Concursos Multiplayer Concluídos" +TwoPlayerWins = "Vitórias com Dois Jogadores" +ThreePlayerWins = "Jogadas com Três Jogadores" +FourPlayerWins = "Jogadas com Quatro Jogadores" +CourseZeroWins = GolfCourseNames[0] + " Vitórias" +CourseOneWins = GolfCourseNames[1] + " Vitórias" +CourseTwoWins = GolfCourseNames[2] + " Vitórias" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + " Troféus ganhos", + str(GolfGlobals.TrophiesPerCup * 2) + " Troféus ganhos", + str(GolfGlobals.TrophiesPerCup * 3) + " Troféus ganhos", +] + +GolfAvReceivesHoleBest = "%(name)s marcou um novo recorde de tacadas em %(hole)s!" +GolfAvReceivesCourseBest = "%(name)s marcou um novo recorde de percurso em %(course)s!!" +GolfAvReceivesCup = "%(name)s ganhou a taça %(cup)s!! Bônus em pontos de risada!" +GolfAvReceivesTrophy = "%(name)s ganhou o troféu %(award)s!!" +GolfRanking = "Posição: \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "Aperte para Esquerda ou Direita para mudar a posição do taco.\nAperte Ctrl para selecionar." +GolfWarningMustSwing = "Atenção: você precisa apertar Ctrl na sua próxima tacada." +GolfAimInstructions = "Aperte para a Esquerda ou Direita para mirar.\nAperte e segure Ctrl para balançar o taco." +GolferExited = "%s saiu do percurso de golfe." +GolfPowerReminder = "Segure Ctrl por Mais Tempo para\nMandar a Bola Mais Longe" + + +# GolfScoreBoard.py +GolfPar = "Par" +GolfHole = "Buraco" +GolfTotal = "Total" +GolfExitCourse = "Sair do Percurso" +GolfUnknownPlayer = "???" + +# GolfPage.py +GolfPageTitle = "Golfe" +GolfPageTitleCustomize = "Personalizador de Golfe" +GolfPageTitleRecords = "Recordes Pessoais" +GolfPageTitleTrophy = "Troféus de Golfe" +GolfPageCustomizeTab = "Personalizar" +GolfPageRecordsTab = "Recordes" +GolfPageTrophyTab = "Troféu" +GolfPageTickets = "Bilhetes: " +GolfPageConfirmDelete = "Apagar Acessório?" +GolfTrophyTextDisplay = "Troféu %(number)s : %(desc)s" +GolfCupTextDisplay = "Taça %(number)s : %(desc)s" +GolfCurrentHistory = "%(historyDesc)s Atual: %(num)s" +GolfTieBreakWinner = "%(name)s venceu o desempate aleatório!" +GolfSeconds = " - %(time).2f segundos" +GolfTimeTieBreakWinner = "%(name)s venceu o desempate por tempo total de mira!!!" + + + +RoamingTrialerWeekendStart = "Está começando a Tour por Toontown! Jogadores podem entrar em qualquer vizinhança de graça!" +RoamingTrialerWeekendOngoing = "Boas-vindas ao Tour por Toontown! Jogadores podem entrar gratuitamente em qualquer vizinhança!" +RoamingTrialerWeekendEnd = "Terminamos com o Tour por Toontown." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Boas novas! Começou o período de Teste Toon, com o dobro de experiência em piadas." +MoreXpHolidayOngoing = "Olá! Estamos no período de Teste Toon, com o dobro de experiência em piadas." +MoreXpHolidayEnd = "Terminou o período exclusivo de Teste Toon, com o dobro de experiência em piadas. Obrigado por nos ajudar a Testar!" + +JellybeanDayHolidayStart = "É Dia das Balinhas! Ganhe prêmios de Balinhas em dobro nas Festas!" +JellybeanDayHolidayEnd = "Acabou o Dia das Balinhas. Vejo você no ano que vem." +PartyRewardDoubledJellybean = "Balinhas em Dobro!" + +GrandPrixWeekendHolidayStart = "É o Fim de Semana do Grande Prêmio no Autódromo do Pateta! Quem jogar gratuitamente ou pagando pode obter a maioria dos pontos em três corridas consecutivas." +GrandPrixWeekendHolidayEnd = "O Fim de Semana do Grande Prêmio acabou. Vejo você no ano que vem." + +LogoutForced = "Você fez algo errado\n e estamos fazendo seu logout automaticamente,\n sua conta também pode estar congelada.\n Experimente dar uma volta lá fora, é divertido." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \nentrou no carrinho de golfe." +CountryClubBossConfrontedMsg = "%s está lutando com o Presidente do Clube!" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "Todos os desafios devem ser vencidos antes disso." + +# DistributedMolefield.py +MolesLeft = "Toupeiras Restantes: %d" +MolesInstruction = "Pisão nas Toupeiras!\nPule nas toupeiras vermelhas!" +MolesFinished = "Pisão nas Toupeiras vencido!" +MolesRestarted = "Perdeu no Pisão! Recomeçando..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "Remova a bola Cog!" +BustACogExit = "Sair por Enquanto" +BustACogHowto = "Como Jogar" +BustACogFailure = "Acabou o Tempo!" +BustACogSuccess = "Sucesso!" + +# bossbot golf green games +GolfGreenGameScoreString = "Quebra-Cabeças Restantes: %s" +GolfGreenGamePlayerScore = "Resolveu %s" +GolfGreenGameBonusGag = "Você ganhou %s!" +GolfGreenGameGotHelp = "%s resolveu um Quebra-Cabeça!" + +GolfGreenGameDirections = "Dê tacadas nas bolas usando o mouse\n\n\nCombinar três bolas de uma mesma cor as faz cair\n\n\nRemova todas as bolas Cog da tela" + +# DistributedMaze.py +enterHedgeMaze = "Corra pela Sebe-Labirinto\n para ganhar bônus de risadas!" +toonFinishedHedgeMaze = "%s \n terminou em %s lugar!" +hedgeMazePlaces = ["primeiro","segundo","terceiro","quarto"] +mazeLabel = "Corrida no Labirinto!" + +# Boarding Group +BoardingPartyReadme = 'Grupo de Abordagem?' +BoardingGroupHide = 'Ocultar' +BoardingGroupShow = 'Exibir Grupo de Abordagem' +BoardingPartyInform = 'Crie um Grupo de Abordagem para o elevador clicando em outro Toon e fazendo um convite.\nNessa área, os Grupos de Abordagem não podem ter mais de %s Toons.' +BoardingPartyTitle = 'Grupo de Abordagem' +QuitBoardingPartyLeader = 'Dispensar' +QuitBoardingPartyNonLeader = 'Deixar' +QuitBoardingPartyConfirm = 'Tem certeza de que quer sair desse Grupo de Abordagem?' +BoardcodeMissing = 'Aconteceu algum erro; tente mais tarde.' +BoardcodeMinLaffLeader = 'Não é possível fazer abordagem com seu grupo porque você tem menos de %s pontos de risada.' +BoardcodeMinLaffNonLeaderSingular = 'Seu grupo não pode fazer abordagem porque %s tem menos de %s pontos de risada.' +BoardcodeMinLaffNonLeaderPlural = 'Seu grupo não pode fazer abordagem porque %s tem menos de %s pontos de risada.' +BoardcodePromotionLeader = 'Seu grupo não pode fazer abordagem porque você não tem méritos de promoção suficientes.' +BoardcodePromotionNonLeaderSingular = 'Seu grupo não pode fazer abordagem porque %s não tem méritos de promoção suficientes.' +BoardcodePromotionNonLeaderPlural = 'Seu grupo não pode fazer abordagem porque %s não tem méritos de promoção suficientes.' +BoardcodeSpace = 'Seu grupo não pode fazer abordagem porque não tem espaço suficiente.' +BoardcodeBattleLeader = 'Seu grupo não pode fazer abordagem porque você está combatendo.' +BoardcodeBattleNonLeaderSingular = 'Seu grupo não pode fazer abordagem porque %s está combatendo.' +BoardcodeBattleNonLeaderPlural = 'Seu grupo não pode fazer abordagem porque %s está combatendo.' +BoardingInviteMinLaffInviter = 'Você precisa de %s Pontos de Risada antes de se tornar associado desse Grupo de Abordagem.' +BoardingInviteMinLaffInvitee = '%s precisa de %s Pontos de Risada antes de se tornar associado desse Grupo de Abordagem.' +BoardingInvitePromotionInviter = 'Você precisa receber uma promoção antes de se tornar associado desse Grupo de Abordagem.' +BoardingInvitePromotionInvitee = '%s precisa receber uma promoção antes de se tornar associado desse Grupo de Abordagem.' +BoardingInviteNotPaidInvitee = '%s precisa ser um Assinante para fazer parte do seu Grupo de Abordagem.' +BoardingInviteeInDiffGroup = '%s já está em outro Grupo de Abordagem.' +BoardingInviteeInKickOutList = '%s foi removido por seu líder. Apenas o líder pode reconvidar associados removidos.' +BoardingInviteePendingIvite = '%s tem um convite pendente; tente novamente mais tarde.' +BoardingInviteeInElevator = '%s está ocupado(a) no momento; tente novamente mais tarde.' +BoardingInviteGroupFull = 'Seu Grupo de Abordagem já está completo' +BoardingAlreadyInGroup = 'Você não pode aceitar esse convite porque já está em outro Grupo de Abordagem.' +BoardingGroupAlreadyFull = 'Você não pode aceitar esse convite porque o grupo já está completo.' +BoardingKickOutConfirm = 'Tem certeza de que quer remover %s?' +BoardingPendingInvite = 'Primeiro você tem de resolver\n o convite pendente.' +BoardingCannotLeaveZone = 'Você não pode deixar essa área porque você faz parte de um Grupo de Abordagem.' +BoardingInviteeMessage = "%s gostaria de se juntar ao seu Grupo de Abordagem." +BoardingInvitingMessage = "Convidando %s para seu Grupo de Abordagem." +BoardingInvitationRejected = "%s recusou se juntar ao seu Grupo de Abordagem." +BoardingMessageKickedOut = "Você foi removido do Grupo de Abordagem." +BoardingMessageInvited = "%s convidou %s para o Grupo de Abordagem." +BoardingMessageLeftGroup = "%s deixou o Grupo de Abordagem." +BoardingMessageGroupDissolved = "Seu Grupo de Abordagem foi dispensado pelo líder do grupo." +BoardingMessageGroupDisbandedGeneric = "Seu Grupo de Abordagem foi dispensado." +BoardingMessageInvitationFailed = "%s tentou convidar você para seu Grupo de Abordagem." +BoardingMessageGroupFull = "%s tentou aceitar seu convite, mas seu grupo estava completo." +BoardingGo = 'IR' +BoardingCancelGo = 'Clique Novamente para\nCancelar o comando Ir' +And = 'e' +BoardingGoingTo = 'Indo Para:' +BoardingTimeWarning = 'Abordando o elevador em ' +BoardingMore = 'mais' +BoardingGoShow = 'Indo para\n%s em ' +BoardingGoPreShow = 'Confirmando...' + +# DistributedBossbotBoss.py +BossbotBossName = "Presidente" +BossbotRTWelcome = "Seus Toons vão precisar de disfarces diferentes." +BossbotRTRemoveSuit = "Primeiramente, tire suas roupas de Cog..." +BossbotRTFightWaiter = "e, então, lute com estes garçons." +BossbotRTWearWaiter = "Bom Trabalho! Agora, coloque as roupas de garçom." +BossbotBossPreTwo1 = "Por que está demorando tanto? " +BossbotBossPreTwo2 = "Vamos, sirva meu banquete!" +BossbotRTServeFood1 = "Hehe, sirva a comida que eu coloco nestas esteiras." +BossbotRTServeFood2 = "Se você servir um Cog três vezes seguidas, ele vai explodir." +BossbotResistanceToonName = "A velha e boa Risada" +BossbotPhase3Speech1 = "O que está acontecendo aqui?!" +BossbotPhase3Speech2 = "Esses garçons são Toons!" +BossbotPhase3Speech3 = "Peguem-nos!!!" +BossbotPhase4Speech1 = "Humpf. Se quero um trabalho bem feito..." +BossbotPhase4Speech2 = "tenho de fazer eu mesmo." +BossbotRTPhase4Speech1 = "Bom Trabalho! Agora, esguiche água no Presidente nas mesas..." +BossbotRTPhase4Speech2 = "ou use bolas de golfe para atrasá-lo." +BossbotPitcherLeave = "Deixar Garrafa" +BossbotPitcherLeaving = "Deixando Garrafa" +BossbotPitcherAdvice = "Use as teclas para esquerda e direita se quiser girar.\nSegure Ctrl para aumentar a força.\nSolte Ctrl para disparar." +BossbotGolfSpotLeave = "Deixar Bola de Golfe" +BossbotGolfSpotLeaving = "Deixando Bola de Golfe" +BossbotGolfSpotAdvice = "Use as teclas para esquerda e direita se quiser girar.\nCtrl dispara." +BossbotRewardSpeech1 = "Não! O Presidente do Conselho não vai gostar disso." +BossbotRewardSpeech2 = "Arrrggghhh!!!!" +BossbotRTCongratulations = "Você conseguiu! Você rebaixou o Presidente!\aPegue estes bilhetes azuis que o Presidente deixou para trás.\aCom eles, você vai poder disparar contra Cogs em batalha.""" +BossbotRTLastPromotion = "\aUau, você chegou ao nível %s com sua Roupa de Cog!\aOs Cogs não conseguem promoções maiores do que essa.\aVocê não pode mais atualizar sua Roupa de Cog, mas, certamente, poderá continuar trabalhando para a Resistência!" +BossbotRTHPBoost = "\aVocê trabalhou bastante para a Resistência.\aO Conselho Toon decidiu lhe dar mais um ponto de Risada. Parabéns!" +BossbotRTMaxed = "\aVejo que você tem uma Roupa de Cog de nível %s. Impressionante!\aEm nome do Conselho Toon, agradeço por voltar para defender mais Toons!" +GolfAreaAttackTaunt = "Bola!" +OvertimeAttackTaunts = [ "É hora de reorganizar.", + "Temos gente para demitir."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "Batalha do C.E.O." +ElevatorBossBotCourse = "Campo de Golfe Cog" +ElevatorBossBotCourse0 = "O Front Three (Três da Frente)" +ElevatorBossBotCourse1 = "O Middle Six (Seis do Meio)" +ElevatorBossBotCourse2 = "O Back Nine (Nove dos Fundos)" +ElevatorCashBotBoss = "Batalha do C.F.O" +ElevatorCashBotMint0 = "Coin Mint (a Mina de Moedas)" +ElevatorCashBotMint1 = "Dollar Mint (a Mina de Dinheiro)" +ElevatorCashBotMint2 = "Bullion Mint (a Mina de Ouro)" +ElevatorSellBotBoss = "Batalha do Sellbot" +ElevatorSellBotFactory0 = "Entrada Principal" +ElevatorSellBotFactory1 = "Entrada dos Fundos" +ElevatorLawBotBoss = "Batalha do Juiz-Chefe" +ElevatorLawBotCourse0 = "Escritório A" +ElevatorLawBotCourse1 = "Escritório B" +ElevatorLawBotCourse2 = "Escritório C" +ElevatorLawBotCourse3 = "Escritório D" + +# CatalogNameTagItem.py +DaysToGo = "Espere\n%s Dias" + +# DistributedIceGame.py +IceGameTitle = "Escorregador de Gelo" +IceGameInstructions = "Chegue o mais perto do centro ao final da segunda rodada. Use as teclas de seta para mudar a direção e a força. Aperte Ctrl para lançar seu Toon. Acerte os barris para ganhar mais pontos, e evite a dinamite!" +IceGameInstructionsNoTnt = "Chegue o mais perto do centro ao final da segunda rodada. Use as teclas de seta para mudar a direção e a força. Aperte Ctrl para lançar seu Toon. Acerte os barris para ganhar mais pontos." +IceGameWaitingForPlayersToFinishMove = "Esperando outros jogadores..." +IceGameWaitingForAISync = "Esperando outros jogadores..." +IceGameInfo= "Partida %(curMatch)d/%(numMatch)d, Rodada %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="Lembre-se de apertar a tecla Ctrl!" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "Entrar" +PicnicTableObserveButton = "Observar" +PicnicTableCancelButton = "Cancelar" +PicnicTableTutorial = "Como Jogar" +PicnicTableMenuTutorial = "Qual jogo você quer aprender?" +PicnicTableMenuSelect = "Qual jogo você quer jogar?" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = "Levantar-se" +ChineseCheckersStartButton = "Iniciar Jogo" +ChineseCheckersQuitButton = "Sair do Jogo" +ChineseCheckersIts = "É a " + +ChineseCheckersYourTurn = "Sua Vez" +ChineseCheckersGreenTurn = "Vez do Verde" +ChineseCheckersYellowTurn = "Vez do Amarelo" +ChineseCheckersPurpleTurn = "Vez do Roxo" +ChineseCheckersBlueTurn = "Vez do Azul" +ChineseCheckersPinkTurn = "Vez do Rosa" +ChineseCheckersRedTurn = "Vez do Vermelho" + +ChineseCheckersColorG = "Você é o Verde" +ChineseCheckersColorY = "Você é o Amarelo" +ChineseCheckersColorP = "Você é o Roxo" +ChineseCheckersColorB = "Você é o Azul" +ChineseCheckersColorPink = "Você é o Rosa" +ChineseCheckersColorR = "Você é o Vermelho" +ChineseCheckersColorO = "Você está Observando" + +ChineseCheckersYouWon = "Você acaba de ganhar uma partida de Xadrez Chinês!" +ChineseCheckers = "Xadrez Chinês." +ChineseCheckersGameOf = " acaba de ganhar uma partida de " + +#GameTutorials.py +ChineseTutorialTitle1 = "Objetivo" +ChineseTutorialTitle2 = "Como Jogar" +ChineseTutorialPrev = "Página Anterior" +ChineseTutorialNext = "Próxima Página" +ChineseTutorialDone = "Pronto" +ChinesePage1 = "O objetivo do Xadrez Chinês é ser o primeiro jogador a mover todas as suas peças do triângulo de baixo do tabuleiro até o triângulo do outro lado. O primeiro jogador a conseguir isso vence!" +ChinesePage2 = "Os jogadores se alternam movendo qualquer pedra de sua própria cor. Uma pedra pode se mover para um buraco ao lado, ou pode saltar por outras pedras. Os saltos devem passar por um mármore e cair em um buraco livre. É possível combinar saltos para andar mais longe!" + +CheckersPage1 = "O objetivo das Damas é deixar o oponente sem poder fazer jogadas. Para isso, você pode capturar todas as suas peças, ou bloqueá-las para que não ele não possa movê-las." +CheckersPage2 = "Os jogadores se alternam movendo qualquer pedra de sua própria cor. Uma peça pode se mover para um quadrado diagonal à frente. Uma peça só pode se mover para um quadrado que não esteja ocupado por outra peça. As damas seguem as mesmas regras, mas podem se mover para trás." +CheckersPage3 = "Para capturar uma peça do oponente, você deve saltar sobre ela diagonalmente para o quadrado vazio depois dela. Se você puder fazer alguma captura em sua vez, terá de fazê-la. Você pode combinar capturas, desde que seja com a mesma peça." +CheckersPage4 = "Uma peça se torna dama quando chegar à última linha do tabuleiro. Uma peça que acaba de se tornar dama não pode saltar de novo até o próximo turno. Além disso, damas podem se mover para todas as direções e podem mudar de direção ao saltar." + + + +#DistributedCheckers.py +CheckersGetUpButton = "Levantar-se" +CheckersStartButton = "Iniciar Jogo" +CheckersQuitButton = "Sair do Jogo" +CheckersIts = "É a " +CheckersYourTurn = "Sua Vez" +CheckersWhiteTurn = "Vez do Branco" +CheckersBlackTurn = "Vez do Preto" +CheckersColorWhite = "Você é o Branco" +CheckersColorBlack = "Você é o Preto" +CheckersObserver = "Você está Observando" +RegularCheckers = "Damas." +RegularCheckersGameOf = " acaba de ganhar uma partida de " +RegularCheckersYouWon = "Você acaba de ganhar uma partida de Damas!" + +MailNotifyNewItems = "Chegou correio para você!" +MailNewMailButton = "Correio" +MailSimpleMail = "Bilhete" +MailFromTag = "Bilhete de: %s" + +# MailboxScreen.py +InviteInvitation = "o convite" +InviteAcceptInvalidError = "O convite não é mais válido." +InviteAcceptPartyInvalid = "Sua festa foi cancelada." +InviteAcceptAllOk = "O anfitrião recebeu sua resposta." +InviteRejectAllOk = "O anfitrião recebeu sua recusa do convite." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "JANEIRO", + 2: "FEVEREIRO", + 3: "MARÇO", + 4: "ABRIL", + 5: "MAIO", + 6: "JUNHO", + 7: "JULHO", + 8: "AGOSTO", + 9: "SETEMBRO", +10: "OUTUBRO", +11: "NOVEMBRO", +12: "DEZEMBRO" +} + +# Note 0 for Monday to match datetime +DayNames = ("Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado", "Domingo") +DayNamesAbbrev = ("SEG", "TER", "QUA", "QUI", "SEX", "SÁB", "DOM") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Fogos de Artifício de Verão", "Comemore o Verão com um espetáculo de fogos de artifício a cada hora em cada pátio!"), + 2: ("Fogos de Artifício de Ano Novo", "Feliz Ano Novo! Curta um espetáculo de fogos de artifício a cada hora em cada pátio!"), + 3: ("Invasão Sanguessuga", "Feliz Halloween! Impeça que os Cogs Sanguessugas invadam Toontown!"), + 4: ("Decoração de Feriados de Inverno", "Comemore os Feriados de Inverno com árvores e postes de iluminação Toontásticos!"), + 5: ("Invasão Skelecog", "Impeça que os Skelecogs invadam Toontown!"), + 6: ("Invasão Dr. Celebridade ", "Impeça que os Cogs do Dr. Celebridade invadam Toontown!"), + 7: ("Bingo de Peixe", "Quarta-feira do Bingo de Peixe! Todos no lago trabalhando juntos para completar a cartela antes de o tempo esgotar."), + 8: ("Eleição de Espécie de Toon", "Vote na nova espécie de Toon! Será uma Cabra? Será um Porco?"), + 9: ("Dia do Gato Preto", "Feliz Halloween! Crie um Toon Gato Preto Toontástico – Só Hoje!"), + 13: ("Doces ou Travessuras", "Feliz Halloween! Vá atrás das guloseimas por toda Toontown para ganhar uma linda cabeça de abóbora de prêmio!"), + 14: ("Grande Prêmio", "Segunda-feira do Grande Prêmio no autódromo do Pateta! Para vencer, conquiste o maior número de pontos em três corridas consecutivas!"), + 16: ("Fim de Semana do Grande Prêmio", "Quem jogar gratuitamente ou pagando compete nas corridas do Autódromo do Pateta!"), + 17: ("Trilhas do Bondinho", "Quinta-feira das Trilhas do Bondinho! Embarque em qualquer Bondinho para jogar com dois ou mais Toons."), + 19: ("Sábados Engraçados", "Os sábados são engraçados com o Bingo de Peixe, Grande Prêmio e Trilhas do Bondinho o dia todo!"), + 24: ("Idos de Março", "Cuidado com os Idos de Março! Impeça que os Cogs Golpe Sujo invadam Toontown!"), + 26: ("Decoração de Halloween", "Comemore o Halloween deixando as árvores e postes de iluminação de Toontown assustadores!"), + 28: ("Invasão de Inverno", "Os sellbots estão à solta espalhando suas táticas de vendas frias!"), + 33: ("Surpresa de Robô Vendedor 1", "Surpresa de Robô Vendedor! Impeça que os Cogs Reis da Incerta invadam Toontown!"), + 34: ("Surpresa de Robô Vendedor 2", "Surpresa de Robô Vendedor! Impeça que os Cogs Sabe-com-quem-está-falando invadam Toontown!"), + 35: ("Surpresa de Robô Vendedor 3", "Surpresa de Robô Vendedor! Impeça que os Cogs Amigos da Onça invadam Toontown!"), + 36: ("Surpresa de Robô Vendedor 4", "Surpresa de Robô Vendedor! Impeça que os Cogs Agitadores invadam Toontown!"), + 37: ("Enigma de Robô Mercenário 1", "Enigma de Robô Mercenário. Impeça que os Cogs Farsantes invadam Toontown!"), + 38: ("Enigma de Robô Mercenário 2", "Enigma de Robô Mercenário. Impeça que os Cogs Mão de Vaca invadam Toontown!"), + 39: ("Enigma de Robô Mercenário 3", "Enigma de Robô Mercenário. Impeça que os Cogs Conta-moedinhas invadam Toontown!"), + 40: ("Enigma de Robô Mercenário 4", "Enigma de Robô Mercenário. Impeça que os Cogs Destruidores de Números invadam Toontown!"), + 41: ("A Estratégia do Robô da Lei 1", "A Estratégia do Robô da Lei. Impeça que os Cogs Comensais invadam Toontown!"), + 42: ("A Estratégia do Robô da Lei 2", "A Estratégia do Robô da Lei. Impeça que os Cogs Duplo Sentido invadam Toontown!"), + 43: ("A Estratégia do Robô da Lei 3", "A Estratégia do Robô da Lei. Impeça que os Cogs Perseguidores de Ambulância invadam Toontown!"), + 44: ("A Estratégia do Robô da Lei 4", "A Estratégia do Robô da Lei. Impeça que os Cogs Golpe Sujo invadam Toontown!"), + 45: ("O Problema Com Robôs Chefes 1", "O Problema Com Robôs Chefes. Impeça que os Cogs Puxa-sacos invadam Toontown!"), + 46: ("O Problema Com Robôs Chefes 2", "O Problema Com Robôs Chefes. Impeça que os Cogs Ratos de Escritório invadam Toontown!"), + 47: ("O Problema Com Robôs Chefes 3", "O Problema Com Robôs Chefes. Impeça que os Cogs Microempresários invadam Toontown!"), + 48: ("O Problema Com Robôs Chefes 4", "O Problema Com Robôs Chefes. Impeça que os Cogs Facões invadam Toontown!"), + 49: ("Dia da Balinha", "Comemore o Dia da Balinha ganhando Balinhas em dobro nas festas!"), + 53: ("Invasão Reis da Incerta", "Impeça que os Cogs Reis da Incerta invadam Toontown!"), + 54: ("Invasão Conta-moedinha", "Impeça que os Cogs Conta-moedinhas invadam Toontown!"), + 55: ("Invasão Duplo Sentido", "Impeça que os Cogs Duplo Sentido invadam Toontown!"), + 56: ("Invasão de Facão", "Impeça que os Cogs Facões invadam Toontown!"), + + } + +UnknownHoliday = "Feriado Desconhecido %d" +HolidayFormat = "%m/%d " + +# parties/ToontownTimeManager.py +TimeZone = "Brazil/West" diff --git a/toontown/src/toonbase/TTLocalizer_portuguese_Property.py b/toontown/src/toonbase/TTLocalizer_portuguese_Property.py new file mode 100644 index 0000000..15531de --- /dev/null +++ b/toontown/src/toonbase/TTLocalizer_portuguese_Property.py @@ -0,0 +1,496 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.045 +RPmeritBarLabels = 0.15 + +#battle/RewardPanel.py +RPmeritLabelXPosition = 0.68 +RPmeritBarsXPosition = 0.955 + +#battle/BattleBase.py +BBbattleInputTimeout = 50.0 + +#battle/FireCogPanel.py +FCPtextFrameScale = 0.06 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogItemPanel.py +CIPnameLabel = 1.0 +CIPwordwrapOffset = 0 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.10) +CSgiftToggle = 0.07 +CSbackCatalogButton = 0.065 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 20 +CMunpaidChatWarning = 0.055 +CMunpaidChatWarning_text_z = 0.27 +CMpayButton = 0.06 +CMpayButton_pos_z = -0.10 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 +CMNoPasswordContinue_z = -0.25 + +#coghq/LawbotCogHQLoader.py +LCLdgSign = 0.075 # the scale of the gate name + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.075 # the scale of the gate name + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 0.8 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 1.5 + +#coghq/BossbotCogHQLoader.py +BCHQLmakeSign = 1.12 + +#coghq/DistributedGolfGreenGame.py +DGGGquitButton = 0.045 +DGGGhowToButton = 0.045 +DGGGscoreLabel = 0.075 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#estate/PlantingGUI.py +GardeningInstructionScale = 0.07 + +#estate/FlowerPanel.py +FPBlankLabelPos = -0.25 +FPBlankLabelTextScale = 0.025 + +#estate/FlowerPicker.py +FPFlowerValueTotal = 0.045 + +#estate/FlowerSellGUI.py +FSGDFTextScale = 0.048 +FSGCancelBtnTextScale = 0.04 +FSGOkBtnTextScale = 0.04 + +#estate/GardenTutorial.py +GardenTutorialPage2Wordwrap = 14.5 +GardenTutorialPage4Wordwrap = 22.5 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.45 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.06 +FPnewRecord = 0.06 + +#fishing/GenusPanel.py +GPgenus = 0.035 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) +FLPtitleScale = 0.035 + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 +FIcancelButtonPosition = (0.20, 0.2, -0.1) +FIstopTextPosition = (-0.20, -0.1) +FIstopButtonPosition = (-0.2, 0.0, -0.1) +FIyesButtonPositionX = -0.20 +FIdirectFrameTextWorkWrap = 13.5 +FIdirectFrameTextPosZ = 0.13 + +#golf/DistributedGolfHole.py +DGHpowerReminder = 0.09 +DGHaimInstructions = 0.065 +DGHteeInstructions = 0.065 + +#golf/DistributedGolfHole.py +DGHAimInstructScale = 0.075 +DGHTeeInstructScale = 0.075 + +#golf/GolfScoreBoard.py +GSBExitCourseBTextPose = (0.20, -.01) +GSBtitleLabelScale = 0.06 + +#golf/GolfScoreBoard.py +GSBexitCourseBPos = (0.20, -.01) +GSBtitleLabel = 0.07 + +#hood/EstateHood.py +EHpopupInfo = .08 + +#hood/Hood.py +HDenterTitleTextScale = 0.12 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.095 +ACdeleteWithPassword = 0.06 +ACstatusText = 1.0 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 +ACquitButton_pos = -0.024 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 +MRPinstructionsText = 0.05 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.05 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.05 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.01 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.1 +NScolorPrecede = False + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.07 + +#makeatoon\ShuffleButton.py +SBshuffleBtn = 0.08 + +#minigame/DistributedPairingGame.py +DPGPointsFrameTextScale = 0.45 +DPGFlipsFrameTextScale = 0.45 + +#minigame/DistributedTravelGame.py +DTGVoteBtnTextScale = 0.05 +DTGUseLabelTextScale = 0.07 +DTGVotesPeriodLabelTextScale = 0.07 +DTGVotesToGoLabelTextScale = 0.07 +DTGUpLabelTextScale = 0.07 +DTGDownLabelTextScale = 0.07 +DTGRemainingVotesFrameTextScale = 0.6 + +#minigame/MinigameRulesPanel.py +MRPGameTitleTextScale = 0.10 +MRPGameTitleTextPos = (-0.12, 0.2, 0.092) +MRPInstructionsTextWordwrap = 32 +MRPInstructionsTextPos = (-0.12, 0.05, 0) + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.65 + +#parties/InviteVisual.py +IVwhenTextLabel = 0.06 +IVactivityTextLabel = 0.06 + +#parties/PartyPlanner.py +PPDescriptionScale = 0.05 +PPelementTitleLabelScale = 0.06 +PPelementBuyButtonTextScale = 0.050 +PPtitleScale = 0.1 +PPpbulicDescriptionLabel = 0.065 +PPprivateDescriptionLabel = 0.065 +PPpublicButton = 0.05 +PPprivateButton = 0.05 +PPcostLabel = 0.065 +PPpartyGroundsLabel = 1.0 +PPinstructionLabel = 0.07 +PPelementPrice = 0.065 + +#parties/DistributedParty.py +DPpartyCountdownClockTextScale = 0.8 +DPpartyCountdownClockMinutesScale = 0.8 +DPpartyCountdownClockColonScale = 0.8 +DPpartyCountdownClockSecondScale = 0.8 +DPpartyCountdownClockMinutesPosY = -0.9 +DPpartyCountdownClockColonPosY = -0.9 +DPpartyCountdownClockSecondPosY = -0.9 + +#parties/PublicPartyGui.py +PPGpartyStartButton = 0.065 +PPGinstructionsLabel = 0.065 +PPGcreatePartyListAndLabel = 0.06 + +#parties/JukeboxGui.py +JGcurrentlyPlayingLabel = 0.07 +JGsongNameLabel = 0.13 +JGaddSongButton = 0.1 +JGnumItemsVisible = 9 +JGlistItem = 1.0 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.4 +PAPcall = 0.4 +PAPowner = 0.30 +PAPscratch = 0.4 +PAPstateLabel = 0.35 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.08 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.095 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0625 + +#racing/DistributedLeaderBoard.py +DLBtitleRowScale = 0.3 + +#racing/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.3 + +#raceing/DistributedRacePad.py +DRPnodeScale = 0.65 + +#racing/KartShopGui.py +KSGtextSizeBig = 0.06 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 22 + +#racing/RaceEndPanels.py +REPraceEnd = 0.065 +REPraceExit = 0.025 +REPticket_text_x = -0.7 + +#racing/RaceGUI.py +RGphotoFinish = 0.20 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#racing/DistributedRaceAI.py +DRAwaitingForJoin = 90 + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.06 + +#safezone/Playground.py +PimgLabel = 0.6 + +#safezone/GZSafeZoneLoader.py +GSZLbossbotSignScale = 0.8 + +#shtiker/EventsPage.py +EPtitleLabel = 0.09 +EPhostTab = 0.06 +EPinvitedTab = 0.06 +EPcalendarTab = 0.06 +EPnewsTab = 0.06 +EPhostingCancelButton = 0.035 +EPhostingDateLabel = 0.05 +EPpartyGoButton = 0.045 +EPpublicPrivateLabel = 0.05 +EPpublicButton= 0.5 +EPprivateButton = 0.5 +EPinvitePartyGoButton = 0.045 +EPdecorationItemLabel = 0.045 +EPactivityItemLabel = 0.055 +EPcreateListAndLabel = 0.055 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 +DSDcancelButtonPositionX = 0.05 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.08 +TPendFrame = 0.08 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.05 +MPhoodWordwrap = 20 + +#shtiker/KartPage.py +KPkartTab = 0.06 +KPdeleteButton = 0.035 +KProtateButton = 0.03 + +#shtiker/GardenPage.py +GPBasketTabTextScale = 0.06 +GPCollectionTabTextScale = 0.06 +GPTrophyTabTextScale = 0.06 +GPSpecialsTabTextScale = 0.06 + +#shtiker/GolfPage.py +GFPRecordsTabTextScale = 0.06 +GFPRecordsTabPos = (0.82, 0, 0.1) +GFPTrophyTabTextScale = 0.06 +GFPRecordsTabTextPos = (0.03, 0.0, 0.0) +GFPRTrophyTabPos = (0.82, 0, -0.3) + +#toon/AvatarPanelBase.py +APBignorePanelAddIgnoreTextScale = 0.045 +APBignorePanelTitlePosY = 0.1 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 +TAPgroupFrameScale = 0.035 +TAPgroupButtonScale = 0.035 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/GroupPanel.py +GPdestFrameScale = 0.035 +GPdestScrollListScale = 0.035 +GPgoButtonScale = 0.035 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.032 +INrunButton = 0.045 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.6 + +#toon/PlayerInfoPanel.py +PIPsecretsButtonScale=0.045 +PIPwisperButton = 0.05 +PIPdetailButton = 0.045 + +#toon/ToonAvatarPanel.py +TAPsecretsButtonScale=0.045 +TAPwisperButtonScale=0.05 + +#toon/ToonAvatarDetailPanel.py +TADPcancelPos = (-0.865, 0.0, -0.765) +TADtrackLabelPosZ = 0.17 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TSRPdialogWordwrap = 20 +TSRPtop = 0.07 +TSRPpanelScale = 0.05 +TSRPpanelPos = (0, -0.65) +TSRPbrowserPosZ = -0.65 +TSRPbutton = 0.04 +TSRPteaserBottomScale = 0.06 +TSRPhaveFunText = 0.08 +TSRPjoinUsText = 0.08 + +#toontowngui/TeaserPanel.py (OLD) +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.08 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/ToonBase.py b/toontown/src/toonbase/ToonBase.py new file mode 100644 index 0000000..e25eb91 --- /dev/null +++ b/toontown/src/toonbase/ToonBase.py @@ -0,0 +1,601 @@ +"""ToonBase module: contains the ToonBase class""" + +#from ShowBaseGlobal import * +from otp.otpbase import OTPBase +from otp.otpbase import OTPLauncherGlobals +from direct.showbase.PythonUtil import * +import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +import ToontownLoader +from direct.gui import DirectGuiGlobals +from direct.gui.DirectGui import * +from pandac.PandaModules import * +import sys +import os +from toontown.toonbase import TTLocalizer +from toontown.toonbase import ToontownBattleGlobals +from toontown.launcher import ToontownDownloadWatcher + + +class ToonBase(OTPBase.OTPBase): + """ToonBase class""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ToonBase") + + # special methods + + def __init__(self): + """__init__(self) + ToonBase constructor: create a toon and launch it into the world + """ + if not config.GetInt('ignore-user-options',0): + Settings.readSettings() + mode = not Settings.getWindowedMode() + music = Settings.getMusic() + sfx = Settings.getSfx() + toonChatSounds = Settings.getToonChatSounds() + musicVol = Settings.getMusicVolume() + sfxVol = Settings.getSfxVolume() + resList = [(640, 480),(800,600),(1024,768),(1280,1024),(1600,1200)] #copied from Resolution in settingsFile.h + res = resList[Settings.getResolution()] + + if mode == None: + mode = 1 + if res == None: + res = (800,600) + + loadPrcFileData("toonBase Settings Window Res", ("win-size %s %s" % (res[0], res[1]))) + loadPrcFileData("toonBase Settings Window FullScreen", ("fullscreen %s" % (mode))) + loadPrcFileData("toonBase Settings Music Active", ("audio-music-active %s" % (music))) + loadPrcFileData("toonBase Settings Sound Active", ("audio-sfx-active %s" % (sfx))) + loadPrcFileData("toonBase Settings Music Volume", ("audio-master-music-volume %s" % (musicVol))) + loadPrcFileData("toonBase Settings Sfx Volume", ("audio-master-sfx-volume %s" % (sfxVol))) + loadPrcFileData("toonBase Settings Toon Chat Sounds", ("toon-chat-sounds %s" % (toonChatSounds))) + + OTPBase.OTPBase.__init__(self) + + if not self.isMainWindowOpen(): + try: + # For toontown, it is possible that window open failed + # because of a graphics card issue. In that case, take + # user to the appropriate page. + launcher.setPandaErrorCode(7) + except: + pass + sys.exit(1) + + self.disableShowbaseMouse() + + self.toonChatSounds = self.config.GetBool('toon-chat-sounds', 1) + + # Toontown doesn't care about dynamic shadows for now. + self.wantDynamicShadows = 0 + # this is temporary until we pull in the new launcher code in production + self.exitErrorCode = 0 + + camera.setPosHpr(0, 0, 0, 0, 0, 0) + self.camLens.setFov(ToontownGlobals.DefaultCameraFov) + self.camLens.setNearFar(ToontownGlobals.DefaultCameraNear, + ToontownGlobals.DefaultCameraFar) + + # Music should be a bit quieter in toontown + self.musicManager.setVolume(0.65) + + # Set the default background color. + self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor) + + # Set up the default colors of the IME candidate strings. + tpm = TextPropertiesManager.getGlobalPtr() + candidateActive = TextProperties() + candidateActive.setTextColor(0, 0, 1, 1) + tpm.setProperties('candidate_active', candidateActive) + candidateInactive = TextProperties() + candidateInactive.setTextColor(0.3, 0.3, 0.7, 1) + tpm.setProperties('candidate_inactive', candidateInactive) + + # set the showbase iris and fade models to point to + # models in the ttmodels tree + self.transitions.IrisModelName = "phase_3/models/misc/iris" + self.transitions.FadeModelName = "phase_3/models/misc/fade" + + # When the user closes the main window, we want to get a + # chance to shut down cleanly. + self.exitFunc = self.userExit + + # Now that we've got the exit hook set, we can set error code + # 11, indicating an abnormal termination. If the user exits + # via alt-F4, that will reset the error code to 0 before + # exiting; if we get a Python exception, that will reset the + # error code to 12. But if we terminate for any other reason + # without giving Python a chance to catch the error, it will + # remain exit code 11: sudden death without Python cleanup. + if __builtins__.has_key("launcher") and launcher: + launcher.setPandaErrorCode(11) + + # Set our max dt so avatars with a low frame rate will + # not pop through collision walls + + # Actually, we don't need such a low value these days, now + # that collisions are better behaved; and setting this number + # too low is frustrating for users with a poor frame rate. + # 1.0 seems to be large enough to ease that frustration, while + # small enough to prevent us from careening through space + # during a particularly big chug. + + # On second thought, Dave Schuyler argues that we should have + # a lower value now to prevent the various physics integrators + # from blowing up. And besides, there are still some + # collision holes in the world. So we compromise with 0.2 for + # now. + globalClock.setMaxDt(0.2) + + # Enable particles if desired + if (self.config.GetBool('want-particles', 1) == 1): + self.notify.debug('Enabling particles') + self.enableParticles() + + # Turn off screen clear (enable this when there are not any + # cracks left in the world) + # self.win.getGsg().enableFrameClear(0, 1) + + # Accept the screenshot key + self.accept(ToontownGlobals.ScreenshotHotkey, self.takeScreenShot) + + # If panda throws a panic event, we know we're not rendering + # properly; send the user to the appropriate web page. + self.accept('panda3d-render-error', self.panda3dRenderError) + + # make a ToontownLoader. Overwrite the ShowBase one. + oldLoader = self.loader + self.loader = ToontownLoader.ToontownLoader(self) + __builtins__["loader"] = self.loader + oldLoader.destroy() + + # Handle Alt-tab notification from gsg + self.accept('PandaPaused', self.disableAllAudio) + self.accept('PandaRestarted', self.enableAllAudio) + + self.friendMode = self.config.GetBool("switchboard-friends", 0) + self.wantPets = self.config.GetBool('want-pets', 1) + self.wantBingo = self.config.GetBool('want-fish-bingo', 1) + self.wantKarts = self.config.GetBool('want-karts', 1) + self.wantNewSpecies = self.config.GetBool('want-new-species', 0) + + # tell panda to ignore repeated key presses + self.inactivityTimeout = self.config.GetFloat("inactivity-timeout", ToontownGlobals.KeyboardTimeout) + if self.inactivityTimeout: + self.notify.debug('Enabling Panda timeout: %s' % self.inactivityTimeout) + self.mouseWatcherNode.setInactivityTimeout(self.inactivityTimeout) + + # minigame debug flags + self.randomMinigameAbort = self.config.GetBool( + 'random-minigame-abort', 0) + self.randomMinigameDisconnect = self.config.GetBool( + 'random-minigame-disconnect', 0) + self.randomMinigameNetworkPlugPull = self.config.GetBool( + 'random-minigame-netplugpull', 0) + self.autoPlayAgain = self.config.GetBool( + 'auto-play-again', 0) + self.skipMinigameReward = self.config.GetBool( + 'skip-minigame-reward', 0) + self.wantMinigameDifficulty = self.config.GetBool( + 'want-minigame-difficulty', 0) + + self.minigameDifficulty = self.config.GetFloat( + 'minigame-difficulty', -1.) + if self.minigameDifficulty == -1.: + del self.minigameDifficulty + self.minigameSafezoneId = self.config.GetInt( + 'minigame-safezone-id', -1) + if self.minigameSafezoneId == -1: + del self.minigameSafezoneId + + ToontownBattleGlobals.SkipMovie = self.config.GetBool( + 'skip-battle-movies', 0) + + # this must be retrieved as an int (and not a bool) so + # that we can detect the absence of this config var; if + # we get it as a bool, our default value of -1 is + # converted to a 1. + self.creditCardUpFront = self.config.GetInt( + 'credit-card-up-front', -1) + if self.creditCardUpFront == -1: + del self.creditCardUpFront + else: + # convert to a bool + self.creditCardUpFront = (self.creditCardUpFront != 0) + + # housing + self.housingEnabled = self.config.GetBool( + 'want-housing', 1) + + # cannons + self.cannonsEnabled = self.config.GetBool( + 'estate-cannons', 0) + + # firework cannons + self.fireworksEnabled = self.config.GetBool( + 'estate-fireworks', 0) + + # day/night in estates + self.dayNightEnabled = self.config.GetBool( + 'estate-day-night', 0) + + # cloud platforms in estates + self.cloudPlatformsEnabled = self.config.GetBool( + 'estate-clouds', 0) + + # greySpacing Allowed? + self.greySpacing = self.config.GetBool( + 'allow-greyspacing', 0) + + self.goonsEnabled = self.config.GetBool( + 'estate-goon', 0) + + self.restrictTrialers = self.config.GetBool( + 'restrict-trialers', 1) + + self.roamingTrialers = self.config.GetBool( + 'roaming-trialers', 1) + + self.slowQuietZone = self.config.GetBool( + 'slow-quiet-zone', 0) + self.slowQuietZoneDelay = self.config.GetFloat( + 'slow-quiet-zone-delay', 5) + + self.killInterestResponse = self.config.GetBool( + 'kill-interest-response', 0) + + #whitelist text styles + tpMgr = TextPropertiesManager.getGlobalPtr() + + WLDisplay = TextProperties() + WLDisplay.setSlant(0.3) + + WLEnter = TextProperties() + WLEnter.setTextColor(1.0,0.0,0.0,1) + + tpMgr.setProperties('WLDisplay', WLDisplay) + tpMgr.setProperties('WLEnter', WLEnter) + + del tpMgr + + self.lastScreenShotTime = globalClock.getRealTime() + self.accept('InputState-forward', self.__walking) + self.canScreenShot = 1 + self.glitchCount = 0 + self.walking = 0 + + self.resetMusic = self.loadMusic("phase_3/audio/bgm/MIDI_Events_16channels.mid") + + def disableShowbaseMouse(self): + # Hack: + # Enable drive mode but turn it off, and reset the camera + # This is here because ShowBase sets up a drive interface, this + # can be removed if ShowBase is changed to not set that up. + self.useDrive() + self.disableMouse() + if self.mouseInterface: + self.mouseInterface.reparentTo(self.dataUnused) + if base.mouse2cam: + self.mouse2cam.reparentTo(self.dataUnused) + # end of hack. + + def __walking(self, pressed): + self.walking = pressed + + + def takeScreenShot(self): + namePrefix = 'screenshot' + namePrefix = launcher.logPrefix + namePrefix + + #requires that you have at least 1 second between shots + timedif = globalClock.getRealTime() - self.lastScreenShotTime + if self.glitchCount > 10 and self.walking: + return + if timedif < 1.0 and self.walking: + self.glitchCount += 1 + return + if not hasattr(self, "localAvatar"): + self.screenshot(namePrefix = namePrefix) + self.lastScreenShotTime = globalClock.getRealTime() + return + coordOnScreen = self.config.GetBool('screenshot-coords', 0) + + # avoid problems where the toon can run through collision barriers + # if they hold down arrow_up while saving screenshots because + # scrnshot saves take so long. + self.localAvatar.stopThisFrame=1 + + ctext = self.localAvatar.getAvPosStr() + + self.screenshotStr = '' + messenger.send('takingScreenshot') + + if coordOnScreen: + # add coord text to lower left corner so our user's bug report + # screenshots contain better info + coordTextLabel = DirectLabel( + pos = (-0.81,0.001,-0.87), + text = ctext, + text_scale = 0.05, + text_fg = VBase4(1.0,1.0,1.0,1.0), + text_bg = (0,0,0,0), + text_shadow = (0,0,0,1), + relief = None) + # make sure it appears in front of arrows and other gui items + coordTextLabel.setBin("gui-popup",0) + strTextLabel = None + if len(self.screenshotStr): + strTextLabel = DirectLabel( + pos = (0.,0.001,0.9), + text = self.screenshotStr, + text_scale = 0.05, + text_fg = VBase4(1.0,1.0,1.0,1.0), + text_bg = (0,0,0,0), + text_shadow = (0,0,0,1), + relief = None) + # make sure it appears in front of arrows and other gui items + strTextLabel.setBin("gui-popup",0) + + self.graphicsEngine.renderFrame() + self.screenshot(namePrefix = namePrefix, + imageComment = ctext+' '+self.screenshotStr) + self.lastScreenShotTime = globalClock.getRealTime() + + if coordOnScreen: + if strTextLabel is not None: + strTextLabel.destroy() + coordTextLabel.destroy() + + def addScreenshotString(self, str): + if len(self.screenshotStr): + self.screenshotStr += '\n' + self.screenshotStr += str + + def initNametagGlobals(self): + """ + Should be called once during startup to initialize a few + defaults for the Nametags. + """ + + arrow = loader.loadModel('phase_3/models/props/arrow') + card = loader.loadModel('phase_3/models/props/panel') + speech3d = ChatBalloon(loader.loadModelNode('phase_3/models/props/chatbox')) + thought3d = ChatBalloon(loader.loadModelNode('phase_3/models/props/chatbox_thought_cutout')) + speech2d = ChatBalloon(loader.loadModelNode('phase_3/models/props/chatbox_noarrow')) + + chatButtonGui = loader.loadModel("phase_3/models/gui/chat_button_gui") + + NametagGlobals.setCamera(self.cam) + NametagGlobals.setArrowModel(arrow) + NametagGlobals.setNametagCard(card, VBase4(-0.5, 0.5, -0.5, 0.5)) + if self.mouseWatcherNode: + NametagGlobals.setMouseWatcher(self.mouseWatcherNode) + NametagGlobals.setSpeechBalloon3d(speech3d) + NametagGlobals.setThoughtBalloon3d(thought3d) + NametagGlobals.setSpeechBalloon2d(speech2d) + NametagGlobals.setThoughtBalloon2d(thought3d) + NametagGlobals.setPageButton(PGButton.SReady, chatButtonGui.find("**/Horiz_Arrow_UP")) + NametagGlobals.setPageButton(PGButton.SDepressed, chatButtonGui.find("**/Horiz_Arrow_DN")) + NametagGlobals.setPageButton(PGButton.SRollover, chatButtonGui.find("**/Horiz_Arrow_Rllvr")) + NametagGlobals.setQuitButton(PGButton.SReady, chatButtonGui.find("**/CloseBtn_UP")) + NametagGlobals.setQuitButton(PGButton.SDepressed, chatButtonGui.find("**/CloseBtn_DN")) + NametagGlobals.setQuitButton(PGButton.SRollover, chatButtonGui.find("**/CloseBtn_Rllvr")) + + rolloverSound = DirectGuiGlobals.getDefaultRolloverSound() + if rolloverSound: + NametagGlobals.setRolloverSound(rolloverSound) + clickSound = DirectGuiGlobals.getDefaultClickSound() + if clickSound: + NametagGlobals.setClickSound(clickSound) + + # For now, we'll leave the Toon at the same point as the + # camera. When we have a real toon later, we'll change it. + NametagGlobals.setToon(self.cam) + + # We need a node to be the parent of all of the 2-d onscreen + # messages along the margins. This should be in front of many + # things, but not all things. + self.marginManager = MarginManager() + self.margins = \ + self.aspect2d.attachNewNode(self.marginManager, DirectGuiGlobals.MIDGROUND_SORT_INDEX + 1) + + # And define a bunch of cells along the margins. + mm = self.marginManager + self.leftCells = [ + mm.addGridCell(0, 1, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(0, 2, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(0, 3, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop) + ] + self.bottomCells = [ + mm.addGridCell(0.5, 0, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(1.5, 0, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(2.5, 0, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(3.5, 0, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(4.5, 0, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop) + ] + self.rightCells = [ + mm.addGridCell(5, 2, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop), + mm.addGridCell(5, 1, base.a2dLeft, base.a2dRight, base.a2dBottom, base.a2dTop) + ] + + def setCellsAvailable(self, cell_list, available): + """setCellsAvailable(self, cell_list, bool available) + + Activates (if available is true) or deactivates (if available + is false) a list of cells that are to be used for displaying + margin popups, like red arrows and offscreen chat messages. + + This can be called from time to time to free up real estate + along the edges of the screen when necessary for special + purposes. + + cell_list should be a list of cell index numbers. Suitable + values are base.leftCells, base.bottomCells, or + base.rightCells. + """ + for cell in cell_list: + self.marginManager.setCellAvailable(cell, available) + + def cleanupDownloadWatcher(self): + self.downloadWatcher.cleanup() + self.downloadWatcher = None + + def startShow(self, cr, launcherServer=None): + self.cr = cr + # force the screen to update + base.graphicsEngine.renderFrame() + + self.downloadWatcher = ToontownDownloadWatcher.ToontownDownloadWatcher(TTLocalizer.LauncherPhaseNames) + if launcher.isDownloadComplete(): + self.cleanupDownloadWatcher() + else: + self.acceptOnce("launcherAllPhasesComplete", self.cleanupDownloadWatcher) + # Find the right server + gameServer = base.config.GetString("game-server", "") + if gameServer: + self.notify.info("Using game-server from Configrc: %s " % (gameServer)) + elif launcherServer: + gameServer = launcherServer + self.notify.info("Using gameServer from launcher: %s " % (gameServer)) + else: + gameServer = 'localhost' + + serverPort = base.config.GetInt("server-port", 6667) + + # The gameServer string will be a semicolon-separated list of + # URL's. + serverList = [] + for name in gameServer.split(';'): + url = URLSpec(name, 1) + # Insist on a secure (SSL-wrapped) connection, regardless + # of what was requested. + url.setScheme('s') + if not url.hasPort(): + url.setPort(serverPort) + serverList.append(url) + + if len(serverList) == 1: + # If the gameServer list has only one element in it, check + # for a failover port. This is a holdover from the old + # way of doing it, from before we could support multiple + # gameservers on the launcher command line. Once this + # code has been published and the startup scripts modified + # appropriately, we can remove this code. + + failover = base.config.GetString("server-failover", "") + serverURL = serverList[0] + for arg in failover.split(): + try: + port = int(arg) + url = URLSpec(serverURL) + url.setPort(port) + except: + url = URLSpec(arg, 1) + + if url != serverURL: + serverList.append(url) + + # Connect to the server + cr.loginFSM.request("connect", [serverList]) + + def removeGlitchMessage(self): + self.ignore('InputState-forward') + print("ignoring InputState-forward") + + + def exitShow(self, errorCode = None): + self.notify.info("Exiting Toontown: errorCode = %s" % errorCode) + if errorCode: + # tell the activeX control we exited cleanly + launcher.setPandaErrorCode(errorCode) + else: + # tell the activeX control we exited cleanly + launcher.setPandaErrorCode(0) + sys.exit() + + # this is temporary until we pull in the new launcher code in production + def setExitErrorCode(self, code): + self.exitErrorCode = code + if os.name == 'nt': + exitCode2exitPage = { + OTPLauncherGlobals.ExitEnableChat: "chat", + OTPLauncherGlobals.ExitSetParentPassword: "setparentpassword", + OTPLauncherGlobals.ExitPurchase: "purchase", + } + if code in exitCode2exitPage: + launcher.setRegistry("EXIT_PAGE", exitCode2exitPage[code]) + def getExitErrorCode(self): + return self.exitErrorCode + + # this is called when the user presses Alt+F4 (closes the window) + def userExit(self): + # The user has just closed the main window; we should shut + # down as cleanly as possible, without taking overly long + # about it. + + # It's nice if the rest of the world sees our toon teleport + # out (if we have a toon). + try: + self.localAvatar.d_setAnimState('TeleportOut', 1) + except: + pass + + # Tell the AI (if we have one) why we're going down. + if self.cr.timeManager: + self.cr.timeManager.setDisconnectReason(ToontownGlobals.DisconnectCloseWindow) + + # in case the user has started logging out through the book, reset this flag + # to indicate that they have forcibly closed the window and will not be going + # back into the game. + base.cr._userLoggingOut = False + # make sure to get rid of all the DistributedObjects before tearing down + # the playgame/login FSMs + try: + localAvatar + except: + pass + else: + messenger.send('clientLogout') + self.cr.dumpAllSubShardObjects() + + self.cr.loginFSM.request("shutdown") + + # If that returned, it didn't stick. No problem. + self.notify.warning("Could not request shutdown; exiting anyway.") + self.exitShow() + + def panda3dRenderError(self): + # The low-level graphics API has thrown an event indicating + # that something's fubar down there, so we should just give up + # and boot the user to the appropriate web page. + + launcher.setPandaErrorCode(14) + + if self.cr.timeManager: + self.cr.timeManager.setDisconnectReason(ToontownGlobals.DisconnectGraphicsError) + + # Disconnect cleanly from the server + self.cr.sendDisconnect() + sys.exit() + + def getShardPopLimits(self): + # returns (low, mid, high) + if self.cr.productName == "JP": + return (config.GetInt("shard-low-pop", ToontownGlobals.LOW_POP_JP), + config.GetInt("shard-mid-pop", ToontownGlobals.MID_POP_JP), + config.GetInt("shard-high-pop", ToontownGlobals.HIGH_POP_JP),) + elif self.cr.productName in ["BR", "FR"]: + return (config.GetInt("shard-low-pop", ToontownGlobals.LOW_POP_INTL), + config.GetInt("shard-mid-pop", ToontownGlobals.MID_POP_INTL), + config.GetInt("shard-high-pop", ToontownGlobals.HIGH_POP_INTL),) + else: + return (config.GetInt("shard-low-pop", ToontownGlobals.LOW_POP), + config.GetInt("shard-mid-pop", ToontownGlobals.MID_POP), + config.GetInt("shard-high-pop", ToontownGlobals.HIGH_POP),) + + def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0): + # play the reset midi file to kill stuck notes + OTPBase.OTPBase.playMusic(self, self.resetMusic) + OTPBase.OTPBase.playMusic(self, music, looping, interrupt, volume, time) diff --git a/toontown/src/toonbase/ToonBaseGlobal.py b/toontown/src/toonbase/ToonBaseGlobal.py new file mode 100644 index 0000000..ea396ec --- /dev/null +++ b/toontown/src/toonbase/ToonBaseGlobal.py @@ -0,0 +1,5 @@ +"""ToonBaseGlobal module: contains the global ToonBase instance""" + +from ToonBase import * + +#__builtins__["base"] = ToonBase() diff --git a/toontown/src/toonbase/ToontownAccessAI.py b/toontown/src/toonbase/ToontownAccessAI.py new file mode 100644 index 0000000..b23df58 --- /dev/null +++ b/toontown/src/toonbase/ToontownAccessAI.py @@ -0,0 +1,33 @@ +from otp.otpbase import OTPGlobals +from otp.ai import BanManagerAI + +from toontown.toonbase import ToontownGlobals +from toontown.hood import ZoneUtil + + +def canAccess(avatarId, zoneId): + avatar = simbase.air.doId2do.get(avatarId) + if avatar and avatar.getGameAccess() != OTPGlobals.AccessFull and not openToAll(zoneId, avatar): + simbase.air.writeServerEvent('suspicious', avatarId, 'User requesting enter for paid access content without proper rights.') + commentStr = "Tried to gain access to an area they were not allowed to using TTInjector Hack" + dislId = avatar.DISLid + simbase.air.banManager.ban(avatarId, dislId, commentStr) + return False + else: + return True + +# Determine if we're in a zone that free players have access to +def openToAll(zoneId, avatar): + canonicalZoneId = ZoneUtil.getCanonicalHoodId(zoneId) + if canonicalZoneId in \ + (ToontownGlobals.ToontownCentral, + ToontownGlobals.MyEstate, + ToontownGlobals.GoofySpeedway, + ToontownGlobals.Tutorial, + ) or avatar.isInEstate(): + return True + elif(canonicalZoneId >= ToontownGlobals.DynamicZonesBegin and not avatar.getTutorialAck()): + # Check for being in the tutorial + return True + else: + return False \ No newline at end of file diff --git a/toontown/src/toonbase/ToontownBattleGlobals.py b/toontown/src/toonbase/ToontownBattleGlobals.py new file mode 100644 index 0000000..73ffca3 --- /dev/null +++ b/toontown/src/toonbase/ToontownBattleGlobals.py @@ -0,0 +1,537 @@ +from ToontownGlobals import * +import math +import TTLocalizer + +### ToontownBattle globals: central repository for all battle globals + +# defaults for camera + +BattleCamFaceOffFov = 30.0 +BattleCamFaceOffPos = Point3(0, -10, 4) + +# BattleCamDefaultPos = Point3(0, -10, 11) +# BattleCamDefaultHpr = Vec3(0, -45, 0) +BattleCamDefaultPos = Point3(0, -8.6, 16.5) +BattleCamDefaultHpr = Vec3(0, -61, 0) +BattleCamDefaultFov = 80.0 +BattleCamMenuFov = 65.0 +BattleCamJoinPos = Point3(0, -12, 13) +BattleCamJoinHpr = Vec3(0, -45, 0) + +# This might be set true by a magic word to skip over the playback movie. +SkipMovie = 0 + +# avatar start hp +BaseHp = 15 + +# avatar track names and numbers +Tracks = TTLocalizer.BattleGlobalTracks +NPCTracks = TTLocalizer.BattleGlobalNPCTracks + +TrackColors = ((211/255.0, 148/255.0, 255/255.0), + (249/255.0, 255/255.0, 93/255.0), + (79/255.0, 190/255.0, 76/255.0), + (93/255.0, 108/255.0, 239/255.0), + (255/255.0, 145/255.0, 66/255.0), + (255/255.0, 65/255.0, 199/255.0), + (67/255.0, 243/255.0, 255/255.0), + ) + +# TODO: put want-all-props back in with quest system +#try: +# wantAllProps = base.config.GetBool('want-all-props', 0) +#except: +# wantAllProps = simbase.config.GetBool('want-all-props', 0) +#if (wantAllProps == 1): +# for i in range(len(TrackZones)): +# TrackZones[i] = ToontownCentral + +HEAL_TRACK = 0 +TRAP_TRACK = 1 +LURE_TRACK = 2 +SOUND_TRACK = 3 +THROW_TRACK = 4 +SQUIRT_TRACK = 5 +DROP_TRACK = 6 +# Special NPC Toon actions +NPC_RESTOCK_GAGS = 7 +NPC_TOONS_HIT = 8 +NPC_COGS_MISS = 9 + +MIN_TRACK_INDEX = 0 +MAX_TRACK_INDEX = 6 + +MIN_LEVEL_INDEX = 0 +MAX_LEVEL_INDEX = 6 + +MAX_UNPAID_LEVEL_INDEX = 4 + +LAST_REGULAR_GAG_LEVEL = 5 + +UBER_GAG_LEVEL_INDEX = 6 + +NUM_GAG_TRACKS = 7 + +# which props buffs which track +PropTypeToTrackBonus = { + AnimPropTypes.Hydrant: SQUIRT_TRACK, + AnimPropTypes.Mailbox : THROW_TRACK, + AnimPropTypes.Trashcan : HEAL_TRACK, + } + + +# avatar skill levels (totalled) +#Levels = [0, 10, 50, 140, 300, 550] +#Levels = [0, 10, 50, 250, 750, 2000] +Levels = [[0, 20, 200, 800, 2000, 6000, 10000], # heal + [0, 20, 100, 800, 2000, 6000, 10000], # trap + [0, 20, 100, 800, 2000, 6000, 10000], # lure + [0, 40, 200, 1000, 2500, 7500, 10000], # sound + [0, 10, 50, 400, 2000, 6000, 10000], # throw + [0, 10, 50, 400, 2000, 6000, 10000], # squirt + [0, 20, 100, 500, 2000, 6000, 10000], # drop + ] +#MaxSkill = 5000 +regMaxSkill = 10000 +UberSkill = 500 +MaxSkill = UberSkill + regMaxSkill +UnpaidMaxSkill = 1999 +# This is the maximum amount of experience per track that may be +# earned in one battle (or in one building). +ExperienceCap = 200 + +# This accuracy (a percentage) is the highest that can ever be attained. +MaxToonAcc = 95 + +# avatar starting skill level +StartingLevel = 0 + +CarryLimits = ( + # Heal + ( ( 10, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1) ), # lvl 7 + # Trap + ( ( 5, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 7, 3, 0, 0, 0, 0, 0), # lvl 2 + ( 10, 7, 3, 0, 0, 0, 0), # lvl 3 + ( 15, 10, 7, 3, 0, 0, 0), # lvl 4 + ( 15, 15, 10, 5, 3, 0, 0), # lvl 5 + ( 20, 15, 15, 10, 5, 2, 0), # lvl 6 + ( 20, 15, 15, 10, 5, 2, 1) ), # lvl 7 + # Lure + ( ( 10, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1) ), # lvl 7 + # Sound + ( ( 10, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1) ), # lvl 7 + # Throw + ( ( 10, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1) ), # lvl 7 + # Squirt + ( ( 10, 0, 0, 0, 0, 0, 0 ), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0 ), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0 ), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0 ), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0 ), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0 ), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1 ) ), # lvl 7 + # Drop + ( ( 10, 0, 0, 0, 0, 0, 0), # lvl 1 + ( 10, 5, 0, 0, 0, 0, 0), # lvl 2 + ( 15, 10, 5, 0, 0, 0, 0), # lvl 3 + ( 20, 15, 10, 5, 0, 0, 0), # lvl 4 + ( 25, 20, 15, 10, 3, 0, 0), # lvl 5 + ( 30, 25, 20, 15, 7, 3, 0), # lvl 6 + ( 30, 25, 20, 15, 7, 3, 1) ), # lvl 7 + ) + +# avatar prop maxes +MaxProps = ( (15, 40), (30, 60), (75, 80) ) + +# death-list flag masks for BattleExperience +DLF_SKELECOG = 0x01 +DLF_FOREMAN = 0x02 +DLF_VP = 0x04 +DLF_CFO = 0x08 +DLF_SUPERVISOR = 0x10 +DLF_VIRTUAL = 0x20 +DLF_REVIVES = 0x40 + +# Pie names. These map to props in BattleProps, but it must be +# defined here beccause BattleProps cannot be included on the AI. +pieNames = ['tart', + 'fruitpie-slice', + 'creampie-slice', + 'fruitpie', + 'creampie', + 'birthday-cake', + 'wedding-cake', + 'lawbook', #used in battle three of lawbot boss + ] + +# avatar prop icon filenames +AvProps = ( ('feather', 'bullhorn', 'lipstick', 'bamboocane', 'pixiedust', + 'baton', 'baton'), + ('banana', 'rake', 'marbles', 'quicksand', 'trapdoor', 'tnt', 'traintrack'), + ('1dollar', 'smmagnet', '5dollar', 'bigmagnet', '10dollar', + 'hypnogogs', 'hypnogogs'), + ('bikehorn', 'whistle', 'bugle', 'aoogah', 'elephant', 'foghorn', 'singing'), + ('cupcake', 'fruitpieslice', 'creampieslice', 'fruitpie', 'creampie', + 'cake', 'cake'), + ('flower', 'waterglass', 'waterballoon', 'bottle', 'firehose', + 'stormcloud', 'stormcloud'), + ('flowerpot', 'sandbag', 'anvil', 'weight', 'safe', 'piano', 'piano') + ) + +AvPropsNew = ( ('inventory_feather', 'inventory_megaphone', 'inventory_lipstick', 'inventory_bamboo_cane', 'inventory_pixiedust', + 'inventory_juggling_cubes', 'inventory_ladder'), + ('inventory_bannana_peel', 'inventory_rake', 'inventory_marbles', 'inventory_quicksand_icon', 'inventory_trapdoor', + 'inventory_tnt', 'inventory_traintracks'), + ('inventory_1dollarbill', 'inventory_small_magnet', 'inventory_5dollarbill', 'inventory_big_magnet', + 'inventory_10dollarbill', 'inventory_hypno_goggles', 'inventory_screen'), + ('inventory_bikehorn', 'inventory_whistle', 'inventory_bugle', 'inventory_aoogah', 'inventory_elephant', 'inventory_fog_horn', 'inventory_opera_singer'), + ('inventory_tart', 'inventory_fruit_pie_slice', 'inventory_cream_pie_slice', 'inventory_fruitpie', + 'inventory_creampie', 'inventory_cake', 'inventory_wedding'), + ('inventory_squirt_flower', 'inventory_glass_of_water', 'inventory_water_gun', 'inventory_seltzer_bottle', + 'inventory_firehose', 'inventory_storm_cloud', 'inventory_geyser'), + ('inventory_flower_pot', 'inventory_sandbag', 'inventory_anvil', 'inventory_weight', 'inventory_safe_box', 'inventory_piano', 'inventory_ship') + ) + + +# prettier on-screen versions of the prop names +AvPropStrings = TTLocalizer.BattleGlobalAvPropStrings + +# prettier on-screen versions of the prop names for singular usage +AvPropStringsSingular = TTLocalizer.BattleGlobalAvPropStringsSingular + +# prettier on-screen versions of the prop names for plural usage +AvPropStringsPlural = TTLocalizer.BattleGlobalAvPropStringsPlural + +# avatar prop accuracies +AvPropAccuracy = ((70, 70, 70, 70, 70, 70, 100), # Heal + (0, 0, 0, 0, 0, 0, 0), # Trap (always hits) + (50, 50, 60, 60, 70, 70, 90), # Lure + (95, 95, 95, 95, 95, 95, 95), # Sound + (75, 75, 75, 75, 75, 75, 75), # Throw + (95, 95, 95, 95, 95, 95, 95), # Squirt + (50, 50, 50, 50, 50, 50, 50) # Drop + ) +AvLureBonusAccuracy = (60, 60, 70, 70, 80, 80, 100) + +AvTrackAccStrings = TTLocalizer.BattleGlobalAvTrackAccStrings + +# avatar prop damages +# each entry represents a toon prop track and is a list of pairs, +# the first of each pair represents the damage range (min to max) which +# maps to the second pair which represents the toon's track +# exp. So the higher the toon's exp in that prop's track, the more damage +# that particular prop can do +# +AvPropDamage = ( + # Heal + (((8,10),(Levels[0][0], Levels[0][1])),#tickle + ((15,18),(Levels[0][1], Levels[0][2])),#group Joke + ((25,30),(Levels[0][2], Levels[0][3])),#kiss + ((40,45),(Levels[0][3], Levels[0][4])),#group Dance + ((60,70),(Levels[0][4], Levels[0][5])),#dust + ((90,120),(Levels[0][5], Levels[0][6])),#group Juggle + ((210,210),(Levels[0][6], MaxSkill))),#group Dive + # Trap + (((10, 12),(Levels[1][0], Levels[1][1])), + ((18, 20),(Levels[1][1], Levels[1][2])), + ((30, 35),(Levels[1][2], Levels[1][3])), + ((45, 50),(Levels[1][3], Levels[1][4])), + ((60, 70),(Levels[1][4], Levels[1][5])), + ((90, 180),(Levels[1][5], Levels[1][6])), + ((195, 195),(Levels[1][6], MaxSkill))), + # Lure + (((0,0),(0,0)), + ((0,0),(0,0)), + ((0,0),(0,0)), + ((0,0),(0,0)), + ((0,0),(0,0)), + ((0,0),(0,0)), + ((0,0),(0,0))), + # Sound + (((3, 4), (Levels[3][0], Levels[3][1])), + ((5, 7), (Levels[3][1], Levels[3][2])), + ((9, 11), (Levels[3][2], Levels[3][3])), + ((14, 16), (Levels[3][3], Levels[3][4])), + ((19, 21), (Levels[3][4], Levels[3][5])), + ((25, 50), (Levels[3][5], Levels[3][6])), + ((90, 90), (Levels[3][6], MaxSkill))), + # Throw + (((4, 6), (Levels[4][0], Levels[4][1])), + ((8, 10), (Levels[4][1], Levels[4][2])), + ((14, 17), (Levels[4][2], Levels[4][3])), + ((24, 27), (Levels[4][3], Levels[4][4])), + ((36, 40), (Levels[4][4], Levels[4][5])), + ((48, 100), (Levels[4][5], Levels[4][6])), + ((120, 120), (Levels[4][6], MaxSkill))), + # Squirt + (((3, 4), (Levels[5][0], Levels[5][1])), + ((6, 8), (Levels[5][1], Levels[5][2])), + ((10, 12), (Levels[5][2], Levels[5][3])), + ((18, 21), (Levels[5][3], Levels[5][4])), + ((27, 30), (Levels[5][4], Levels[5][5])), + ((36, 80), (Levels[5][5], Levels[5][6])), + ((105, 105), (Levels[5][6], MaxSkill))), + # Drop + (((10, 10), (Levels[6][0], Levels[6][1])), + ((18, 18), (Levels[6][1], Levels[6][2])), + ((30, 30), (Levels[6][2], Levels[6][3])), + ((45, 45), (Levels[6][3], Levels[6][4])), + ((60, 60), (Levels[6][4], Levels[6][5])), + ((85, 170), (Levels[6][5], Levels[6][6])), + ((180, 180), (Levels[6][6], MaxSkill))), + ) + +# avatar prop target type (0 for single target, +# 1 for group target) +# AvPropTargetCat is a grouping of target types +# for a single track and AvPropTarget is which +# target type group from AvPropTargetCat each +# toon attack track uses +# +ATK_SINGLE_TARGET = 0 +ATK_GROUP_TARGET = 1 +AvPropTargetCat = ( ( ATK_SINGLE_TARGET, + ATK_GROUP_TARGET, + ATK_SINGLE_TARGET, + ATK_GROUP_TARGET, + ATK_SINGLE_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET ), + ( ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET ), + ( ATK_GROUP_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET, + ATK_GROUP_TARGET ), + ( ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_SINGLE_TARGET, + ATK_GROUP_TARGET ), + ) + +AvPropTarget = ( 0, 3, 0, 2, 3, 3, 3) + + +def getAvPropDamage( attackTrack, attackLevel, exp, organicBonus=False, propBonus = False, + propAndOrganicBonusStack = False): + """ + //////////////////////////////////////////////////////////////////// + // Function: get the appropriate prop damage based on various + // attributes of the prop and the toon + // Parameters: attackTrack, the track of the prop + // attackLevel, the level of the prop + // exp, the toon's exp in the specified track + // Changes: + //////////////////////////////////////////////////////////////////// + """ + # Map a damage value for the prop based on the track exp of the + # toon, for example, a throw might have 3-6 damage which maps + # to 0-30 exp. So at 0 to 7.75 exp, the throw will do 3 damage; + # at 7.75 to 15.5 exp, the throw will do 4 damage; at 15.5 to 23.25, + # the throw will do 5 damage; at 23.25 to 30 exp, the throw will + # do 6 damage; at more than 30 exp, the throw will max out at 6 + # damage + # + minD = AvPropDamage[ attackTrack ][ attackLevel ][0][0] + maxD = AvPropDamage[ attackTrack ][ attackLevel ][0][1] + minE = AvPropDamage[ attackTrack ][ attackLevel ][1][0] + maxE = AvPropDamage[ attackTrack ][ attackLevel ][1][1] + expVal = min( exp, maxE ) + expPerHp = float((maxE-minE)+1) / float((maxD-minD)+1) + damage = math.floor( ( expVal - minE ) / expPerHp ) + minD + # In the gag purchase tutorial the sneak peak gags show as negative + if damage <= 0: + damage = minD + if propAndOrganicBonusStack: + originalDamage = damage + if organicBonus: + damage += getDamageBonus(originalDamage) + if propBonus: + damage += getDamageBonus(originalDamage) + else: + if organicBonus or propBonus: + damage += getDamageBonus(damage) + return damage + +def getDamageBonus(normal): + bonus = int(normal * 0.1) + if (bonus < 1) and (normal > 0): + bonus = 1 + return bonus + +#def isGroup(track, level): +# if ((track == SOUND_TRACK) or +# (((track == HEAL_TRACK) or (track == LURE_TRACK)) and +# ((level == 1) or (level == 3) or (level == 5) or (level == 6)))): +# return 1 +# else: +# return 0 + +def isGroup(track, level): + return AvPropTargetCat[AvPropTarget[track]][level] + +def getCreditMultiplier(floorIndex): + """ + Returns the skill credit multiplier appropriate for a particular + floor in a building battle. The floorIndex is 0 for the first + floor, up through 4 for the top floor of a five-story building. + """ + # Currently, this is 1 for the first floor (floor 0), 1.5 for the + # second floor (floor 1), etc. + return 1 + floorIndex * 0.5 + +def getFactoryCreditMultiplier(factoryId): + """ + Returns the skill credit multiplier for a particular factory. + factoryId is the factory-interior zone defined in ToontownGlobals.py. + """ + # for now, there's only one factory + return 2. + +def getFactoryMeritMultiplier(factoryId): + """ + Returns the skill merit multiplier for a particular factory. + factoryId is the factory-interior zone defined in ToontownGlobals.py. + """ + # Many people complained about how many runs you must make now that + # we lowered the cog levels so I have upped this by a factor of two. + return 4. + +def getMintCreditMultiplier(mintId): + """ + Returns the skill credit multiplier for a particular mint. + mintId is the mint-interior zone defined in ToontownGlobals.py. + """ + return { + CashbotMintIntA : 2., + CashbotMintIntB : 2.5, + CashbotMintIntC : 3., + }.get(mintId, 1.) + +def getStageCreditMultiplier(floor): + """ + Returns the skill credit multiplier for a particular mint. + stageId is the stage-interior zone defined in ToontownGlobals.py. + """ + return getCreditMultiplier(floor) + +def getCountryClubCreditMultiplier(countryClubId): + """ + Returns the skill credit multiplier for a particular mint. + mintId is the mint-interior zone defined in ToontownGlobals.py. + """ + return { + BossbotCountryClubIntA : 2., + BossbotCountryClubIntB : 2.5, + BossbotCountryClubIntC : 3., + }.get(countryClubId, 1.) + + +def getBossBattleCreditMultiplier(battleNumber): + """ + Returns the skill credit multiplier for the two first battles of + the final battle sequence with the Senior V.P. battleNumber is 1 + for the first battle and 2 for the second battle. + """ + return 1 + battleNumber + +def getInvasionMultiplier(): + """ + Returns the skill credit multiplier during invasions. + This gets multiplied on every street battle and in every interior. + User must first check to see if there is an invasion. + """ + return 2.0 + +def getMoreXpHolidayMultiplier(): + """ + Returns the skill credit multiplier during the more xp holiday. + This gets multiplied on every street battle and in every interior. + User must first check to see if there is an invasion. + """ + return 2.0 + +def encodeUber(trackList): + bitField = 0 + for trackIndex in range(len(trackList)): + if trackList[trackIndex] > 0: + bitField += pow(2,trackIndex) + return bitField + +def decodeUber(flagMask): + if flagMask == 0: + return [] + maxPower = 16 + workNumber = flagMask + workPower = maxPower + trackList = [] + #print("build") + #while (workNumber > 0) and (workPower >= 0): + while (workPower >= 0): + if workNumber >= pow(2,workPower): + workNumber -= pow(2,workPower) + trackList.insert(0, 1) + else: + trackList.insert(0, 0) + #print("Number %s List %s" % (workNumber, trackList)) + workPower -= 1 + endList = len(trackList) + foundOne = 0 + #print("compress") + while not foundOne: + #print trackList + if trackList[endList - 1] == 0: + trackList.pop(endList - 1) + endList -= 1 + else: + foundOne = 1 + return trackList + +def getUberFlag(flagMask, index): + decode = decodeUber(flagMask) + if index >= len(decode): + return 0 + else: + return decode[index] + +def getUberFlagSafe(flagMask, index): + if (flagMask == "unknown") or (flagMask < 0): + return -1 + else: + return getUberFlag(flagMask, index) diff --git a/toontown/src/toonbase/ToontownGlobals.py b/toontown/src/toonbase/ToontownGlobals.py new file mode 100644 index 0000000..beda793 --- /dev/null +++ b/toontown/src/toonbase/ToontownGlobals.py @@ -0,0 +1,1578 @@ +""" +ToonBase module: defines constants that are global across Toontown, and +may have meaning to several classes. +""" + +import TTLocalizer +from otp.otpbase.OTPGlobals import * +from direct.showbase.PythonUtil import Enum, invertDict +from pandac.PandaModules import BitMask32, Vec4 + +AccountDatabaseChannelId = 4008 +ToonDatabaseChannelId = 4021 +DoodleDatabaseChannelId = 4023 +DefaultDatabaseChannelId = AccountDatabaseChannelId +DatabaseIdFromClassName = { + # AccountDatabaseChannelId + 'Account': AccountDatabaseChannelId, + + # ToonDatabaseChannelId + #'DistributedPlayerToon': ToonDatabaseChannelId, + + # DoodleDatabaseChannelId + #'DistributedDoodle': DoodleDatabaseChannelId, + } + +# Toontown specific camera FOVs +CogHQCameraFov = 60.0 +BossBattleCameraFov = 72.0 +MakeAToonCameraFov = 52.0 + +# Things we can throw a pie at. (Pies also react to CameraBitmask and +# FloorBitmask, but not WallBitmask.): +# Brought in from OTPGlobals +PieBitmask = BitMask32(0x100) + +# Pets avoid this +PetBitmask = BitMask32(0x08) + +# For the catching minigame: +CatchGameBitmask = BitMask32(0x10) + +# Things the magnet can pick up in the Cashbot CFO battle (same as +# CatchGameBitmask): +CashbotBossObjectBitmask = BitMask32(0x10) + +# These are used by the furniture mover: +FurnitureSideBitmask = BitMask32(0x20) +FurnitureTopBitmask = BitMask32(0x40) +FurnitureDragBitmask = BitMask32(0x80) + +# Used to detect who's looking at a pet and who pets are looking at +PetLookatPetBitmask = BitMask32(0x100) +PetLookatNonPetBitmask = BitMask32(0x200) + +# Bitmask for the DistributedBanquetTable +BanquetTableBitmask = BitMask32(0x400) + +# When we have this many pies, it means an infinite number. +FullPies = 65535 + +CogHQCameraFar = 900.0 +CogHQCameraNear = 1.0 + +CashbotHQCameraFar = 2000.0 +CashbotHQCameraNear = 1.0 + +LawbotHQCameraFar = 3000.0 +LawbotHQCameraNear = 1.0 + +BossbotHQCameraFar = 3000.0 +BossbotHQCameraNear = 1.0 + +SpeedwayCameraFar = 8000.0 +SpeedwayCameraNear = 1.0 + +# How many items can be in the mailbox (or on the way to the mailbox) +# at once? +MaxMailboxContents = 30 + +# The maximum number of items you can have anywhere in your house at +# all, including in the attic. This includes windows and wallpaper in +# the attic, but not those windows and wallpaper which are installed. +MaxHouseItems = 45 + +# How many extra items, over the above limit, can we keep on the +# deletedItems list? +ExtraDeletedItems = 5 + +# How many minutes to keep an item on the deletedItems list. +DeletedItemLifetime = 7 * 24 * 60 + +# How many weeks are in each catalog series? +CatalogNumWeeksPerSeries = 13 + +# How many total weeks are there in the catalog before we're done? +CatalogNumWeeks = 78 + +# task priorities +PetFloorCollPriority = 5 # must run after smoothing and before main + # collision loop +PetPanelProximityPriority = 6 # must run after pet floor collisions + # so that pet is at correct Z + +# purchase codes returned by DistributedMailbox.acceptItem(), -15 and up extended for award manager +# DistributedPhone.requestPurchase, and CatalogItem.recordPurchase(). +# In general, positive codes are success, negative codes are failure. +P_OnAwardOrderListFull = -25 # unlikely, but just in case, he won 30 awards and their still in his onAwardOrder list +P_AwardMailboxFull = -24 # the award mailbox is full and can't take more +P_ItemInPetTricks= -23 # trying to give a pet trick award but the toon has it in his pet tricks +P_ItemInMyPhrases= -22 # trying to give a speed chat award but the toon has it in his My Phrases +P_ItemOnAwardOrder = -21 # trying to give an award but the toon has it onAwardOrder, +P_ItemInAwardMailbox = -20 # trying to give an award but the toon has it in his award mailbox +P_ItemAlreadyWorn = -19 # trying to give an award but the toon is already wearing the clothing item +P_ItemInCloset = -18 # trying to give an award but the toon has it in his closet +P_ItemOnGiftOrder = -17 # trying to give an award but the toon has it in the gift order queue +P_ItemOnOrder = -16 # trying to give an award but the toon has it in his onOrder queue +P_ItemInMailbox = -15 # trying to give an award but the toon has it in his mailbox +P_PartyNotFound = 14 # suspicious doublecheck if it needs to be negative +P_WillNotFit = -13 +P_NotAGift = -12 +P_OnOrderListFull = -11 +P_MailboxFull = -10 +P_NoPurchaseMethod = -9 +P_ReachedPurchaseLimit = -8 +P_NoRoomForItem = -7 +P_NotShopping = -6 +P_NotAtMailbox = -5 +P_NotInCatalog = -4 +P_NotEnoughMoney = -3 +P_InvalidIndex = -2 +P_UserCancelled = -1 + +P_ItemAvailable = 1 +P_ItemOnOrder = 2 +P_ItemUnneeded = 3 + +#gift codes +GIFT_user = 0 +GIFT_admin = 1 +GIFT_RAT = 2 +GIFT_mobile = 3 +GIFT_cogs = 4 + +# codes returned by DistributedFurnitureManager.moveItemToAttic, +# moveItemFromAttic, deleteItemFromAttic, and all similar related +# functions for wallpaper and windows. +FM_InvalidItem = -7 +FM_NondeletableItem = -6 +FM_InvalidIndex = -5 +FM_NotOwner = -4 +FM_NotDirector = -3 +FM_RoomFull = -2 +FM_HouseFull = -1 +FM_MovedItem = 1 +FM_SwappedItem = 2 +FM_DeletedItem = 3 +FM_RecoveredItem = 4 + +# Bits for the friend flags. Presently, we only use bit 1. +#FriendChat = 1 # OTPGlobals + +# Bits for the DistributedAvatar.commonChat word. +#CommonChat = 1 # OTPGlobals +#SuperChat = 2 # OTPGlobals + +# Maximum number of custom chat phrases +#MaxCustomMessages = 15 # OTPGlobals + +# Tokens for the various nodes we can parent avatars to via +# distributed setParent(). +#SPHidden = 1 # OTPGlobals +#SPRender = 2 # OTPGlobals +SPDonaldsBoat = 3 +SPMinniesPiano = 4 +#SPDynamic = 5 # OTPGlobals + +# These are in OTPGlobals +""" +# Cheesy rendering effects. +CENormal = 0 +CEBigHead = 1 +CESmallHead = 2 +CEBigLegs = 3 +CESmallLegs = 4 +CEBigToon = 5 +CESmallToon = 6 +CEFlatPortrait = 7 +CEFlatProfile = 8 +CETransparent = 9 +CENoColor = 10 +CEInvisible = 11 +CEPumpkin = 12 +CEBigWhite = 13 + +# This one is not really a cheesy effect, but it is implemented by the +# cheesy effect system. It's a string rather than a number to ensure +# that no one cheats and asks for this via the cheesy effect +# distributed message. +CEGhost = 'g' + +# How big is big and how small is small? +BigToonScale = 1.5 +SmallToonScale = 0.5 +""" +#why are the above part of otp? JML +CEVirtual = 14 + +# Cap on the max hp +# Quests = 100 +# Fishing = 7 +# Racing = 3 +# Lawbot HQ = 5 +# Cashbot HQ = 5 +# Sellbot HQ = 5 +# Gardening = 4 +# Golf = 3 +# Bossbot HQ = 5 +MaxHpLimit = 137 +# Cap on the max carry +MaxCarryLimit = 80 +# Cap on the max carry +MaxQuestCarryLimit = 4 + +# the highest level you can attain for your Cog HQ cog suit +# levels are stored zero-based, so subtract one +MaxCogSuitLevel = 50-1 +# the cog suit levels (of the most advanced cog suit of track) at which +# you get a +1 HP boost +# levels are stored zero-based, so subtract one +# NOTE: add 5 to MaxHpLimit every time we add a cog HQ +CogSuitHPLevels = (15-1,20-1,30-1,40-1,50-1,) + +# How fast must we move before we start to walk? +#WalkCutOff = 0.5 # OTPGlobals + +# How fast must we move before we break into a run cycle? +#RunCutOff = 8.0 # OTPGlobals + +# Override some values in OTPGlobals +setInterfaceFont(TTLocalizer.InterfaceFont) +setSignFont(TTLocalizer.SignFont) +from toontown.toontowngui import TTDialog +setDialogClasses(TTDialog.TTDialog, TTDialog.TTGlobalDialog) + +ToonFont = None +BuildingNametagFont = None +MinnieFont = None +SuitFont = None + +# New Function to Choose Custom Fonts for Gateway +def getToonFont(): + global ToonFont + if (ToonFont == None): + ToonFont = loader.loadFont(TTLocalizer.ToonFont, lineHeight = 1.0) + return ToonFont + +def getBuildingNametagFont(): + global BuildingNametagFont + if BuildingNametagFont == None: + BuildingNametagFont = loader.loadFont(TTLocalizer.BuildingNametagFont) + return BuildingNametagFont + +def getMinnieFont(): + global MinnieFont + if MinnieFont == None: + MinnieFont = loader.loadFont(TTLocalizer.MinnieFont) + return MinnieFont + +def getSuitFont(): + global SuitFont + if SuitFont == None: + SuitFont = loader.loadFont(TTLocalizer.SuitFont, + pixelsPerUnit = 40, + spaceAdvance = 0.25, lineHeight = 1.0) + return SuitFont + + +#### ZONE IDs #### + +# Hood ids. These are also the zone ids of the corresponding +# safezone, and also represent the first of a range of 1000 zone ids +# allocated to each hood. +DonaldsDock = 1000 +ToontownCentral = 2000 +TheBrrrgh = 3000 +MinniesMelodyland = 4000 +DaisyGardens = 5000 +OutdoorZone = 6000 +FunnyFarm = 7000 +GoofySpeedway = 8000 +DonaldsDreamland = 9000 + +# Street Branch zones +# DonaldsDock +BarnacleBoulevard = 1100 +SeaweedStreet = 1200 +LighthouseLane = 1300 +# ToontownCentral +SillyStreet = 2100 +LoopyLane = 2200 +PunchlinePlace = 2300 +# TheBrrrgh +WalrusWay = 3100 +SleetStreet = 3200 +PolarPlace = 3300 +# MinniesMelodyland +AltoAvenue = 4100 +BaritoneBoulevard = 4200 +TenorTerrace = 4300 +# DaisyGardens +ElmStreet = 5100 +MapleStreet = 5200 +OakStreet = 5300 +# DonaldsDreamland +LullabyLane = 9100 +PajamaPlace = 9200 + +# Keep a static zoneId for toonhall +ToonHall = 2513 + +HoodHierarchy = { + ToontownCentral : (SillyStreet, LoopyLane, PunchlinePlace), + DonaldsDock : (BarnacleBoulevard, SeaweedStreet, LighthouseLane), + TheBrrrgh : (WalrusWay, SleetStreet, PolarPlace), + MinniesMelodyland : (AltoAvenue, BaritoneBoulevard, TenorTerrace), + DaisyGardens : (ElmStreet, MapleStreet, OakStreet), + DonaldsDreamland : (LullabyLane, PajamaPlace), + GoofySpeedway : (), + } + +# This is a special case. It's not a real zoneId, but is used to +# represent the entire collection of WelcomeValley zones, which is +# maintained by the AI. Requesting a transfer to this zone really +# means to go to a WelcomeValley zone of the AI's choosing. +WelcomeValleyToken = 0 + +# CogHQ hood/zone ids. (Some of these are not real zoneIds but are here +# so that quests can specify them as locations.) +BossbotHQ = 10000 +BossbotLobby = 10100 +BossbotCountryClubIntA = 10500 +BossbotCountryClubIntB = 10600 +BossbotCountryClubIntC = 10700 +SellbotHQ = 11000 +SellbotLobby = 11100 +SellbotFactoryExt = 11200 +SellbotFactoryInt = 11500 # for the sake of quests +CashbotHQ = 12000 +CashbotLobby = 12100 +CashbotMintIntA = 12500 # for the sake of quests +CashbotMintIntB = 12600 # for the sake of quests +CashbotMintIntC = 12700 # for the sake of quests +LawbotHQ = 13000 +LawbotLobby = 13100 +LawbotOfficeExt = 13200 +LawbotOfficeInt = 13300 #should be a dynamic instance +LawbotStageIntA = 13300 # for the sake of quests +LawbotStageIntB = 13400 # for the sake of quests +LawbotStageIntC = 13500 # for the sake of quests +LawbotStageIntD = 13600 # for the sake of quests + +# These are hood ids, but they are not zone ids. +Tutorial = 15000 +MyEstate = 16000 + +# Minigolf hood ids +GolfZone = 17000 + +# Party zone hood id +PartyHood = 18000 + +# These hoods are considered children of other hoods and don't need to be explicitly visited +# NOTE: add all new zones of of the OZ here! +HoodsAlwaysVisited = [17000,18000,] + +# This is the pool of zone ids reserved for the dynamically-allocated +# copies of ToontownCentral known as WelcomeValley. Each dynamic hood +# gets 1000 zone ids. +WelcomeValleyBegin = 22000 +WelcomeValleyEnd = 61000 + +# Everything from this zone up to the top of the available range is +# reserved for the dynamic zone pool. Note that our effective maximum +# zone may be less than DynamicZonesEnd, depending on the assignment +# of available doIds--we must be careful not to overlap. +DynamicZonesBegin = 61000 +DynamicZonesEnd = (1 << 20) + + +cogDept2index = { + 'c':0, + 'l':1, + 'm':2, + 's':3, + } +cogIndex2dept = invertDict(cogDept2index) + +HQToSafezone = { + SellbotHQ : DaisyGardens, + CashbotHQ : DonaldsDreamland, + LawbotHQ : TheBrrrgh, + BossbotHQ : DonaldsDock, # ? + } + +CogDeptNames = [ + TTLocalizer.Bossbot, + TTLocalizer.Lawbot, + TTLocalizer.Cashbot, + TTLocalizer.Sellbot, + ] + +# cogHQ hood id to dept index +def cogHQZoneId2deptIndex(zone): + assert (zone >= 10000) and (zone <= 13999) + if (zone >= 13000) and (zone <= 13999): + return 1 # lawbot + elif zone >= 12000: + return 2 # cashbot + elif zone >= 11000: + return 3 # sellbot + else: + return 0 # bossbot + +def cogHQZoneId2dept(zone): + return cogIndex2dept[cogHQZoneId2deptIndex(zone)] + +def dept2cogHQ(dept): + dept2hq = { + 'c' : BossbotHQ, 'l' : LawbotHQ, + 'm' : CashbotHQ, 's' : SellbotHQ, + } + return dept2hq[dept] + +# this is a phony zoneId for the 'mockup' testbed factory +MockupFactoryId = 0 + +# these do not include the first (entrance) room of the mint floor +# mintId->(min,max) +MintNumFloors = { + CashbotMintIntA : 20, + CashbotMintIntB : 20, + CashbotMintIntC : 20, + } + +CashbotMintCogLevel = 10 +CashbotMintSkelecogLevel = 11 +CashbotMintBossLevel = 12 + +# An average DL 5-story building has about 224 cog 'level' points total. +# Every Mint battle has 4 cogs, and cog levels vary from +# CashbotMintCogLevel to CashbotMintCogLevel+1. +# 224 levels / 10.5 levels/cog = 21.33 cogs +# 21.33 cogs / 4 cogs/battle = 5 +# +# As a point of reference, the factory has a minimum of 6 battles, with 3 +# cogs in most battles, for a total of 18 cogs. + +MintNumBattles = { + CashbotMintIntA : 4, # 16 cogs = 8 merits + CashbotMintIntB : 6, # 24 cogs = 12 merits + CashbotMintIntC : 8, # 32 cogs = 16 merits + } + +MintCogBuckRewards = { + CashbotMintIntA : 8, # 8 * 1.0 + CashbotMintIntB : 14, # 12 * 1.1 + CashbotMintIntC : 20, # 16 * 1.2 + } + +# these room counts do NOT include the entrance room +# table of mintId->list of num rooms for each floor +MintNumRooms = { + CashbotMintIntA : 2*( 6,) + 5*( 7,) + 5*( 8,) + 5*( 9,) + 3*(10,), + CashbotMintIntB : 3*( 8,) + 6*( 9,) + 6*(10,) + 5*(11,), + CashbotMintIntC : 4*(10,) +10*(11,) + 6*(12,), + } +if __debug__: + for mintId in MintNumRooms: + assert len(MintNumRooms[mintId]) == MintNumFloors[mintId] + +# Country Club +BossbotCountryClubCogLevel = 11 +BossbotCountryClubSkelecogLevel = 12 +BossbotCountryClubBossLevel = 12 + +CountryClubNumRooms = { + BossbotCountryClubIntA : (4,), #2*( 6,) + 5*( 7,) + 5*( 8,) + 5*( 9,) + 3*(10,), + BossbotCountryClubIntB : 3*( 8,) + 6*( 9,) + 6*(10,) + 5*(11,), + BossbotCountryClubIntC : 4*(10,) +10*(11,) + 6*(12,), + } + +CountryClubNumBattles = { + BossbotCountryClubIntA : 3, + BossbotCountryClubIntB : 2, + BossbotCountryClubIntC : 3, + } + +CountryClubCogBuckRewards = { + BossbotCountryClubIntA: 8, # 8 * 1.0 + BossbotCountryClubIntB : 14, # 12 * 1.1 + BossbotCountryClubIntC: 20, # 16 * 1.2 + } + +#Stage + +LawbotStageCogLevel = 10 +LawbotStageSkelecogLevel = 11 +LawbotStageBossLevel = 12 + +# An average DL 5-story building has about 224 cog 'level' points total. +# Every Stage battle has 4 cogs, and cog levels vary from +# LawbotStageCogLevel to LawbotStageCogLevel+1. +# 224 levels / 10.5 levels/cog = 21.33 cogs +# 21.33 cogs / 4 cogs/battle = 5 +# +# As a point of reference, the factory has a minimum of 6 battles, with 3 +# cogs in most battles, for a total of 18 cogs. + +StageNumBattles = { + LawbotStageIntA : 0, # 16 cogs = 8 merits + LawbotStageIntB : 0, # 24 cogs = 12 merits + LawbotStageIntC : 0, # 32 cogs = 16 merits + LawbotStageIntD : 0, # 40 cogs = 20 merits + } + +StageNoticeRewards = { + LawbotStageIntA : 75, + LawbotStageIntB : 150, + LawbotStageIntC : 225, + LawbotStageIntD : 300, + } + +# these room counts do NOT include the entrance room +# table of stageId->list of num rooms for each floor +StageNumRooms = { + LawbotStageIntA : 2*( 6,) + 5*( 7,) + 5*( 8,) + 5*( 9,) + 3*(10,), + LawbotStageIntB : 3*( 8,) + 6*( 9,) + 6*(10,) + 5*(11,), + LawbotStageIntC : 4*(10,) +10*(11,) + 6*(12,), + LawbotStageIntD : 4*(10,) +10*(11,) + 6*(12,), + } +""" +if __debug__: + for stageId in StageNumRooms: + assert len(StageNumRooms[stageId]) == StageNumFloors[stageId] + """ + +# CogHQ factory types +# these are internal, do not localize +FT_FullSuit = 'fullSuit' +FT_Leg = 'leg' +FT_Arm = 'arm' +FT_Torso = 'torso' + +# 'factory id' is actually the faux-zone set aside for the factory +factoryId2factoryType = { + MockupFactoryId: FT_FullSuit, + SellbotFactoryInt: FT_FullSuit, + LawbotOfficeInt: FT_FullSuit, + } + + +# Street names +StreetNames = TTLocalizer.GlobalStreetNames +StreetBranchZones = StreetNames.keys() + +# hood name list +Hoods = ( DonaldsDock, + ToontownCentral, + TheBrrrgh, + MinniesMelodyland, + DaisyGardens, + OutdoorZone, + FunnyFarm, + GoofySpeedway, + DonaldsDreamland, + BossbotHQ, + SellbotHQ, + CashbotHQ, + LawbotHQ, + GolfZone, + ) + +HoodsForTeleportAll = ( DonaldsDock, + ToontownCentral, + TheBrrrgh, + MinniesMelodyland, + DaisyGardens, + OutdoorZone, + GoofySpeedway, + DonaldsDreamland, + BossbotHQ, + SellbotHQ, + CashbotHQ, + LawbotHQ, + GolfZone, + ) + +# Minigame Ids +# This id indicates that this is the first game in a trolley +# sequence. We need to keep track because we don't want to +# ever play the same game twice in a row. +NoPreviousGameId = 0 +# Real game Ids +RaceGameId = 1 +CannonGameId = 2 +TagGameId = 3 +PatternGameId = 4 +RingGameId = 5 +MazeGameId = 6 +TugOfWarGameId = 7 +CatchGameId = 8 +DivingGameId = 9 +TargetGameId = 10 +PairingGameId = 11 +VineGameId = 12 +IceGameId = 13 +CogThiefGameId = 14 +TwoDGameId = 15 +PhotoGameId = 16 +TravelGameId = 100 # only used in trolley metagame + + +MinigameNames = { + "race" : RaceGameId, + "cannon" : CannonGameId, + "tag" : TagGameId, + "pattern" : PatternGameId, + "minnie" : PatternGameId, + "match" : PatternGameId, + "matching" : PatternGameId, + "ring" : RingGameId, + "maze" : MazeGameId, + "tug" : TugOfWarGameId, + "catch" : CatchGameId, + "diving" : DivingGameId, + "target" : TargetGameId, + "pairing" : PairingGameId, + "vine" : VineGameId, + "ice" : IceGameId, + "thief" : CogThiefGameId, + "2d" : TwoDGameId, + "photo" : PhotoGameId, + "travel" : TravelGameId, + } + +# the minigame template; not used in final game +MinigameTemplateId = -1 + + +MinigameIDs = ( RaceGameId, CannonGameId, TagGameId, PatternGameId, RingGameId, MazeGameId, TugOfWarGameId, CatchGameId, DivingGameId, TargetGameId, PairingGameId, VineGameId, IceGameId, CogThiefGameId, TwoDGameId, PhotoGameId, TravelGameId,) + + +# Minigame Id list +MinigamePlayerMatrix = { + # If you only have one player, choose from these games + # Technically pattern game can be single player, but it is not nearly as fun, especially for demos + 1 : (CannonGameId, RingGameId, MazeGameId, TugOfWarGameId, CatchGameId, + DivingGameId, TargetGameId, PairingGameId, VineGameId, + CogThiefGameId, PhotoGameId, TwoDGameId), + # If you have exactly two players, choose from these games + 2 : (CannonGameId, PatternGameId, RingGameId, TagGameId, MazeGameId, TugOfWarGameId, CatchGameId, + DivingGameId, TargetGameId, PairingGameId, VineGameId, + IceGameId, CogThiefGameId, PhotoGameId, TwoDGameId), + # If you have exactly three players, choose from these games + 3 : (CannonGameId, PatternGameId, RingGameId, TagGameId, RaceGameId, MazeGameId, TugOfWarGameId, CatchGameId, + DivingGameId, TargetGameId, PairingGameId, VineGameId, + IceGameId, CogThiefGameId, PhotoGameId, TwoDGameId), + # If you have exactly four players, choose from these games + 4 : (CannonGameId, PatternGameId, RingGameId, TagGameId, RaceGameId, MazeGameId, TugOfWarGameId, CatchGameId, + DivingGameId, TargetGameId, PairingGameId, VineGameId, + IceGameId, CogThiefGameId, PhotoGameId, TwoDGameId), + } + +# we are releasing one minigame a week for the new minigames +MinigameReleaseDates = { + IceGameId : (2008, 8, 05), + PhotoGameId : (2008,8,13), + TwoDGameId : (2008,8,20), + CogThiefGameId : (2008,8,27), + } + +# Moved to OTPGlobals +""" +# Hotkeys +ThinkPosHotkey = "f1-up" +PlaceMarkerHotkey = "f2-up" +FriendsListHotkey = "f7-up" +StickerBookHotkey = "f8-up" +OptionsPageHotkey = "escape-up" +ScreenshotHotkey = "f9-up" +SynchronizeHotkey = "f6-up" +QuestsHotkeyOn = "end" +QuestsHotkeyOff = "end-up" +InventoryHotkeyOn = "home" +InventoryHotkeyOff = "home-up" +PrintCamPosHotkey = "f12-up" # just for dbging +""" + +# Keyboard inactivity timeout +KeyboardTimeout = 300 + +# Maps hoods to download phases +phaseMap = { + Tutorial : 4, + ToontownCentral : 4, # TT streets are in 5 + MyEstate : 5.5, + DonaldsDock : 6, + MinniesMelodyland : 6, + GoofySpeedway : 6, + TheBrrrgh : 8, + DaisyGardens : 8, + FunnyFarm : 8, + DonaldsDreamland : 8, + OutdoorZone : 8, + BossbotHQ : 12, + SellbotHQ : 9, + CashbotHQ : 10, + LawbotHQ : 11, + GolfZone : 8, + PartyHood : 13, + } + + +# town streets to download phases +streetPhaseMap = { + ToontownCentral : 5, + DonaldsDock : 6, + MinniesMelodyland : 6, + GoofySpeedway : 6, + TheBrrrgh : 8, + DaisyGardens : 8, + FunnyFarm : 8, + DonaldsDreamland : 8, + OutdoorZone : 8, + BossbotHQ : 12, + SellbotHQ : 9, + CashbotHQ : 10, + LawbotHQ : 11, + PartyHood : 13, + } + + +# Maps hoods to download phases +dnaMap = { + Tutorial : "toontown_central", + ToontownCentral : "toontown_central", + DonaldsDock : "donalds_dock", + MinniesMelodyland : "minnies_melody_land", + GoofySpeedway : "goofy_speedway", + TheBrrrgh : "the_burrrgh", + DaisyGardens : "daisys_garden", + FunnyFarm : "not done yet", + DonaldsDreamland : "donalds_dreamland", + OutdoorZone : "outdoor_zone", + BossbotHQ : "cog_hq_bossbot", + SellbotHQ : "cog_hq_sellbot", + CashbotHQ : "cog_hq_cashbot", + LawbotHQ : "cog_hq_lawbot", + GolfZone : "golf_zone", + } + +# Maps hoods to names +hoodNameMap = { + DonaldsDock : TTLocalizer.DonaldsDock, + ToontownCentral : TTLocalizer.ToontownCentral, + TheBrrrgh : TTLocalizer.TheBrrrgh, + MinniesMelodyland : TTLocalizer.MinniesMelodyland, + DaisyGardens : TTLocalizer.DaisyGardens, + OutdoorZone : TTLocalizer.OutdoorZone, + FunnyFarm : TTLocalizer.FunnyFarm, + GoofySpeedway : TTLocalizer.GoofySpeedway, + DonaldsDreamland : TTLocalizer.DonaldsDreamland, + BossbotHQ : TTLocalizer.BossbotHQ, + SellbotHQ : TTLocalizer.SellbotHQ, + CashbotHQ : TTLocalizer.CashbotHQ, + LawbotHQ : TTLocalizer.LawbotHQ, + Tutorial : TTLocalizer.Tutorial, + MyEstate : TTLocalizer.MyEstate, + GolfZone : TTLocalizer.GolfZone, + PartyHood : TTLocalizer.PartyHood + } + +# map of number of things to load per zone +safeZoneCountMap = { + MyEstate : 8 , + Tutorial : 6, + ToontownCentral : 6, + DonaldsDock : 10, + MinniesMelodyland : 5, + GoofySpeedway : 500, + TheBrrrgh : 8, + DaisyGardens : 9, + FunnyFarm : 500, + DonaldsDreamland : 5, + OutdoorZone : 500, + GolfZone : 500, + PartyHood : 500, + } + +townCountMap = { + # HACK! JNS just guessed at a tutorial count. + # and SDN guessed at Estate count + MyEstate : 8 , + Tutorial : 40 , + ToontownCentral : 37, + DonaldsDock : 40, + MinniesMelodyland : 40, + GoofySpeedway : 40, + TheBrrrgh : 40, + DaisyGardens : 40, + FunnyFarm : 40, + DonaldsDreamland : 40, + OutdoorZone : 40, + PartyHood : 20, + } + +hoodCountMap = { + MyEstate : 2, + Tutorial : 2, + ToontownCentral : 2, + DonaldsDock : 2, + MinniesMelodyland : 2, + GoofySpeedway : 2, + TheBrrrgh : 2, + DaisyGardens : 2, + FunnyFarm : 2, + DonaldsDreamland : 2, + OutdoorZone : 2, + BossbotHQ : 2, + SellbotHQ : 43, + CashbotHQ : 2, + LawbotHQ : 2, + GolfZone : 2, + PartyHood : 2, + } + +# Number of buildings you must have in your name to earn stars. +# Note - these have gone up since the credit is based on the size of the building now +TrophyStarLevels = ( + 10, # A bronze star + 20, # A spinning bronze star + 30, # A silver star + 50, # A spinning silver star + 75, # A gold star + 100, # A spinning gold star + ) + +TrophyStarColors = ( + Vec4(0.9,0.6,0.2,1), # A bronze star + Vec4(0.9,0.6,0.2,1), # A bronze star + Vec4(0.8,0.8,0.8,1), # A silver star + Vec4(0.8,0.8,0.8,1), # A silver star + Vec4(1,1,0,1), # A gold star + Vec4(1,1,0,1), # A gold star + ) + +# OTPGlobals +""" +ToonStandableGround = 0.707 # if ToonStandableGround > angle: toon is on ground. + +ToonForwardSpeed = 16.0 # feet per second +ToonJumpForce = 24.0 # feet per second +ToonReverseSpeed = 8.0 # feet per second +ToonRotateSpeed = 80.0 + +# When you are "dead" +ToonForwardSlowSpeed = 6.0 +ToonJumpSlowForce = 4.0 # feet per second +ToonReverseSlowSpeed = 2.5 +ToonRotateSlowSpeed = 33.0 +""" + +MickeySpeed = 5.0 # feet per second +VampireMickeySpeed = 1.15 +#VampireMickeySpeed = 5.15 +MinnieSpeed = 3.2 # feet per second +WitchMinnieSpeed = 1.8 +#DonaldSpeed = 4.6 # feet per second +DonaldSpeed = 3.68 # feet per second +DaisySpeed = 2.3 # feet per second +GoofySpeed = 5.2 # feet per second +SuperGoofySpeed = 1.6 # fps +PlutoSpeed = 5.5 # feet per second per second +WesternPlutoSpeed = 3.2 +ChipSpeed = 3 # feet per second +DaleSpeed = 3.5 # feet per second +DaleOrbitDistance = 3# feet + + +SuitWalkSpeed = 4.8 + +# The various pre-defined pieCode values in the world. These are used +# in the throw-a-pie-wherever-you-want interface, particularly in the +# final boss battle. +PieCodeBossCog = 1 +PieCodeNotBossCog = 2 +PieCodeToon = 3 +PieCodeBossInsides = 4 +PieCodeDefensePan = 5 #defense pan for lawbot boss battle +PieCodeProsecutionPan =6 #prosecution pan for lawbot boss battle +PieCodeLawyer = 7 #prosecution lawyers for lawbot boss battle + + +# And the splat colors, if any, that correspond to a hit on any of the +# above. +PieCodeColors = { + PieCodeBossCog : None, # A successful hit on the boss cog is in color. + PieCodeNotBossCog : (0.8, 0.8, 0.8, 1), + PieCodeToon : None, # hitting a toon is also in color. + } + + + +BossCogRollSpeed = 7.5 +BossCogTurnSpeed = 20 +BossCogTreadSpeed = 3.5 + + +# Boss Cog attack codes: +BossCogDizzy = 0 +BossCogElectricFence = 1 +BossCogSwatLeft = 2 +BossCogSwatRight = 3 +BossCogAreaAttack = 4 +BossCogFrontAttack = 5 +BossCogRecoverDizzyAttack = 6 +BossCogDirectedAttack = 7 +BossCogStrafeAttack = 8 +BossCogNoAttack = 9 +BossCogGoonZap = 10 +BossCogSlowDirectedAttack = 11 +BossCogDizzyNow = 12 +BossCogGavelStomp = 13 +BossCogGavelHandle = 14 +BossCogLawyerAttack = 15 +BossCogMoveAttack = 16 +BossCogGolfAttack = 17 +BossCogGolfAreaAttack = 18 +BossCogGearDirectedAttack = 19 +BossCogOvertimeAttack = 20 + +# The amount of time it takes to play each attack. +BossCogAttackTimes = { + BossCogElectricFence : 0, + BossCogSwatLeft : 5.5, + BossCogSwatRight : 5.5, + BossCogAreaAttack : 4.21, + BossCogFrontAttack : 2.65, + BossCogRecoverDizzyAttack : 5.1, + BossCogDirectedAttack : 4.84, + BossCogNoAttack : 6, + BossCogSlowDirectedAttack : 7.84, + BossCogMoveAttack : 3, + BossCogGolfAttack: 6, + BossCogGolfAreaAttack: 7, + BossCogGearDirectedAttack : 4.84, + BossCogOvertimeAttack : 5, + } + +# The damage that each attack applies to a Toon. +BossCogDamageLevels = { + BossCogElectricFence : 1, + BossCogSwatLeft : 5, + BossCogSwatRight : 5, + BossCogAreaAttack: 10, + BossCogFrontAttack: 3, + BossCogRecoverDizzyAttack: 3, + BossCogDirectedAttack: 3, + BossCogStrafeAttack : 2, + BossCogGoonZap : 5, + BossCogSlowDirectedAttack : 10, + BossCogGavelStomp : 20, + BossCogGavelHandle : 2, + BossCogLawyerAttack : 5, + BossCogMoveAttack : 20, + BossCogGolfAttack: 15, + BossCogGolfAreaAttack: 15, + BossCogGearDirectedAttack: 15, + BossCogOvertimeAttack : 10, + } + + +# Where are the Boss Cog's battles relative to him? +BossCogBattleAPosHpr = (0, -25, 0, 0, 0, 0) +BossCogBattleBPosHpr = (0, 25, 0, 180, 0, 0) + +# How many pie hits does it take to kill the Sellbot VP? +SellbotBossMaxDamage = 100 + +# Where is the Sellbot Boss sitting in the three stages of the +# VP sequence? +SellbotBossBattleOnePosHpr = (0, -35, 0, -90, 0, 0) +SellbotBossBattleTwoPosHpr = (0, 60, 18, -90, 0, 0) +SellbotBossBattleThreeHpr = (180, 0, 0) +SellbotBossBottomPos = (0, -110, -6.5) +SellbotBossDeathPos = (0, -175, -6.5) + +# Where do the VP's doobers walk to? +SellbotBossDooberTurnPosA = (-20, -50, 0) +SellbotBossDooberTurnPosB = (20, -50, 0) +SellbotBossDooberTurnPosDown = (0, -50, 0) +SellbotBossDooberFlyPos = (0, -135, -6.5) + +# How does the VP roll up the ramp? +SellbotBossTopRampPosA = (-80, -35, 18) +SellbotBossTopRampTurnPosA = (-80, 10, 18) +SellbotBossP3PosA = (-50, 40, 18) +SellbotBossTopRampPosB = (80, -35, 18) +SellbotBossTopRampTurnPosB = (80, 10, 18) +SellbotBossP3PosB = (50, 60, 18) + + +# How many points does it take to kill the Cashbot CFO? +CashbotBossMaxDamage = 500 + +# Where is the Cashbot Boss sitting in the CFO sequence? +CashbotBossOffstagePosHpr = (120, -195, 0, 0, 0, 0) +CashbotBossBattleOnePosHpr = (120, -230, 0, 90, 0, 0) +CashbotRTBattleOneStartPosHpr = (94, -220, 0, 110, 0, 0) +CashbotBossBattleThreePosHpr = (120, -315, 0, 180, 0, 0) + +# Where are the starting points for the toons in battle 3? +CashbotToonsBattleThreeStartPosHpr = [ + (105, -285, 0, 208, 0, 0), + (136, -342, 0, 398, 0, 0), + (105, -342, 0, 333, 0, 0), + (135, -292, 0, 146, 0, 0), + (93, -303, 0, 242, 0, 0), + (144, -327, 0, 64, 0, 0), + (145, -302, 0, 117, 0, 0), + (93, -327, 0, -65, 0, 0), + ] +# How many safes in the final battle sequence, and where are they? +CashbotBossSafePosHprs = [ + (120, -315, 30, 0, 0, 0), # safe 0 is special; it drops on from above. + (77.2, -329.3, 0, -90, 0, 0), + (77.1, -302.7, 0, -90, 0, 0), + (165.7, -326.4, 0, 90, 0, 0), + (165.5, -302.4, 0, 90, 0, 0), + (107.8, -359.1, 0, 0, 0, 0), + (133.9, -359.1, 0, 0, 0, 0), + (107.0, -274.7, 0, 180, 0, 0), + (134.2, -274.7, 0, 180, 0, 0), + ] + +# How many cranes, and where are they? +CashbotBossCranePosHprs = [ + (97.4, -337.6, 0, -45, 0, 0), + (97.4, -292.4, 0, -135, 0, 0), + (142.6, -292.4, 0, 135, 0, 0), + (142.6, -337.6, 0, 45, 0, 0), + ] + +# How long does it take an object to fly from the ground to the magnet? +CashbotBossToMagnetTime = 0.2 + +# And how long to straighten out when dropped? +CashbotBossFromMagnetTime = 1 + +# How much impact does it take to hit the Cashbot boss with an object? +CashbotBossSafeKnockImpact = 0.5 +CashbotBossSafeNewImpact = 0.0 +CashbotBossGoonImpact = 0.1 +CashbotBossKnockoutDamage = 15 + + +# Values used in controlling toon ripples when walking through water +# Value in TT Central +TTWakeWaterHeight = -4.79 +# Value in Donald's Dock +DDWakeWaterHeight = 1.669 +# Value in Estate (type 1) +EstateWakeWaterHeight = -.3 +# Value in Outdoor zone +OZWakeWaterHeight = -0.5 +WakeRunDelta = 0.1 +WakeWalkDelta = 0.2 + +# codes sent to setCatalogNotify(). +NoItems = 0 +NewItems = 1 +OldItems = 2 + +# Values for Suit Invasion news +SuitInvasionBegin = 0 +SuitInvasionUpdate = 1 +SuitInvasionEnd = 2 +SuitInvasionBulletin = 3 + + +# Toon defines now in OTPGlobals + + +# Holiday name constants +NO_HOLIDAY = 0 +JULY4_FIREWORKS = 1 +NEWYEARS_FIREWORKS = 2 +HALLOWEEN = 3 +WINTER_DECORATIONS = 4 +SKELECOG_INVASION = 5 +MR_HOLLYWOOD_INVASION = 6 +FISH_BINGO_NIGHT = 7 +ELECTION_PROMOTION = 8 +BLACK_CAT_DAY = 9 +RESISTANCE_EVENT = 10 +KART_RECORD_DAILY_RESET = 11 +KART_RECORD_WEEKLY_RESET = 12 +TRICK_OR_TREAT = 13 +CIRCUIT_RACING = 14 +POLAR_PLACE_EVENT = 15 +CIRCUIT_RACING_EVENT = 16 +TROLLEY_HOLIDAY = 17 +TROLLEY_WEEKEND = 18 +SILLY_SATURDAY_BINGO = 19 +SILLY_SATURDAY_CIRCUIT = 20 +SILLY_SATURDAY_TROLLEY = 21 +ROAMING_TRIALER_WEEKEND = 22 +BOSSCOG_INVASION = 23 +MARCH_INVASION = 24 +MORE_XP_HOLIDAY = 25 +HALLOWEEN_PROPS = 26 +HALLOWEEN_COSTUMES = 27 +DECEMBER_INVASION = 28 +APRIL_FOOLS_COSTUMES = 29 +CRASHED_LEADERBOARD = 30 + + +# German Holiday name constants +OCTOBER31_FIREWORKS = 31 +NOVEMBER19_FIREWORKS = 32 + +# these are holiday invasions, different cog types per number +SELLBOT_SURPRISE_1 = 33 +SELLBOT_SURPRISE_2 = 34 +SELLBOT_SURPRISE_3 = 35 +SELLBOT_SURPRISE_4 = 36 +CASHBOT_CONUNDRUM_1 = 37 +CASHBOT_CONUNDRUM_2 = 38 +CASHBOT_CONUNDRUM_3 = 39 +CASHBOT_CONUNDRUM_4 = 40 +LAWBOT_GAMBIT_1 = 41 +LAWBOT_GAMBIT_2 = 42 +LAWBOT_GAMBIT_3 = 43 +LAWBOT_GAMBIT_4 = 44 +TROUBLE_BOSSBOTS_1 = 45 +TROUBLE_BOSSBOTS_2 = 46 +TROUBLE_BOSSBOTS_3 = 47 +TROUBLE_BOSSBOTS_4 = 48 + +JELLYBEAN_DAY = 49 + +# French Holiday name constants +FEBRUARY14_FIREWORKS = 51 +JULY14_FIREWORKS = 52 +JUNE22_FIREWORKS = 53 +BIGWIG_INVASION = 54 + +# August Weekend Invasion +COLD_CALLER_INVASION = 53 +BEAN_COUNTER_INVASION = 54 +DOUBLE_TALKER_INVASION = 55 +DOWNSIZER_INVASION = 56 +WINTER_CAROLING = 57 +# Animated Props Holidays +HYDRANT_ZERO_HOLIDAY = 58 +VALENTINES_DAY = 59 +SILLYMETER_HOLIDAY = 60 +# Continuation of animted Prop Holidays +MAILBOX_ZERO_HOLIDAY = 61 +TRASHCAN_ZERO_HOLIDAY = 62 +SILLY_SURGE_HOLIDAY = 63 +HYDRANTS_BUFF_BATTLES = 64 +MAILBOXES_BUFF_BATTLES = 65 +TRASHCANS_BUFF_BATTLES = 66 + +SILLY_CHATTER_ONE = 67 +SILLY_CHATTER_TWO = 68 +SILLY_CHATTER_THREE = 69 +SILLY_CHATTER_FOUR = 70 + +SILLY_TEST = 71 + +YES_MAN_INVASION = 72 +TIGHTWAD_INVASION = 73 +TELEMARKETER_INVASION = 74 +HEADHUNTER_INVASION = 75 +SPINDOCTOR_INVASION = 76 +MONEYBAGS_INVASION = 77 +TWOFACES_INVASION = 78 +MINGLER_INVASION = 79 +LOANSHARK_INVASION = 80 +CORPORATE_RAIDER_INVASION = 81 +ROBBER_BARON_INVASION = 82 +LEGAL_EAGLE_INVASION = 83 +BIG_WIG_INVASION = 84 +BIG_CHEESE_INVASION = 85 +DOWN_SIZER_INVASION = 86 +MOVER_AND_SHAKER_INVASION = 87 +DOUBLETALKER_INVASION = 88 +PENNY_PINCHER_INVASION = 89 +NAME_DROPPER_INVASION = 90 +AMBULANCE_CHASER_INVASION = 91 +MICROMANAGER_INVASION = 92 +NUMBER_CRUNCHER_INVASION = 93 + +SILLY_CHATTER_FIVE = 94 + +VICTORY_PARTY_HOLIDAY = 95 + +# Trick or Treat Holiday Values +TOT_REWARD_JELLYBEAN_AMOUNT = 100 +TOT_REWARD_END_OFFSET_AMOUNT = 0 # Pumpkins will disappear as soon as the holiday ends +#TOT_REWARD_END_OFFSET_AMOUNT = 60*60*24*7 # a week's worth of seconds to be added to the end time of the holiday + + +# How many points does it take to kill the Lawbot Boss? +# This will probably represent evidence or gavel points +LawbotBossMaxDamage = 2700 + +#final scale tilt to indicate a win, in degrees +LawbotBossWinningTilt = 40 + +# if it becomes 0, the toons have lost the case +LawbotBossInitialDamage = 1350 + +# Where is the Sellbot Boss sitting in the three stages of the +# VP sequence? +LawbotBossBattleOnePosHpr = (-2.798, -60, 0, 0, 0, 0) + +#LawbotBossBattleTwoPosHpr = (50.0,-9, 0, -90, 0, 0) +LawbotBossBattleTwoPosHpr = (-2.798, 89, 19.145,0, 0, 0) + + +#temp only +# How does the VP roll up the ramp? +LawbotBossTopRampPosA = (-80, -35, 18) +LawbotBossTopRampTurnPosA = (-80, 10, 18) +#LawbotBossP3PosA = (-50, 40, 18) +LawbotBossP3PosA = (55, -9, 0) +LawbotBossTopRampPosB = (80, -35, 18) +LawbotBossTopRampTurnPosB = (80, 10, 18) +#LawbotBossP3PosB = (50, 60, 18) +LawbotBossP3PosB = (55, -9, 0) + +#LawbotBossBattleThreeHpr = (180, 0, 0) +#LawbotBossBattleThreePosHpr = (20.5, 152.7, 20,0, 0, 0) +LawbotBossBattleThreePosHpr = LawbotBossBattleTwoPosHpr + + +LawbotBossBottomPos = (50, 39, 0) +LawbotBossDeathPos = (50, 40, 0) + +# How many gavels, and where are they? +LawbotBossGavelPosHprs = [ + (35, 78.328, 0, -135, 0, 0), #corner near judge far from witness stand + ( 68.5, 78.328, 0, 135, 0, 0), # corner near witness stand + ( 47, -33, 0, 45, 0, 0), # corner next nearest witness stand + ( -50, -39, 0, -45, 0, 0), # corner diagonally opposite witness stand + + (-9, -37, 0, 0, 0, 0), #along wall opposite judge + (-9, 49, 0, -180, 0, 0), #along wall near judge + (32, 0, 0, 45, 0, 0), #opposite witness stand + (33, 56, 0, 135, 0, 0), #between center of room and witness stand + ] + +#first number is time to go down, 2nd is time to go up, 3rd number is how long it stays down +LawbotBossGavelTimes = [ + (0.20,0.9,0.6), + (0.25,1, 0.5), + (1.0,6,0.5), + (0.3,3,1), + + (0.26,0.9, 0.45), + (0.24,1.1, 0.65), + (0.27,1.2, 0.45), + (0.25,0.95, 0.5), + ] + +#change relative heading where the gavel lands, but give it a pattern so the observant player can predict where it lands +LawbotBossGavelHeadings = [ + (0, -15, 4, -70 -45, 5, 45), + (0, -45, -4, -35, -45,-16, 32), + (0, -8, 19, -7, 5, 23), + (0, -4, 8, -16, 32, -45, 7, 7, -30, 19, -13, 25), + + (0, -45, -90, 45, 90), + (0, -45, -90, 45, 90), + (0, -45, 45), + (0, -45, 45), + ] + + +# Where are the Boss Cog's battles relative to him? +LawbotBossCogRelBattleAPosHpr = (-25, -10, 0, 0, 0, 0) +LawbotBossCogRelBattleBPosHpr = (-25, 10, 0, 0, 0, 0) + +LawbotBossCogAbsBattleAPosHpr = (-5, -2, 0, 0, 0, 0) +LawbotBossCogAbsBattleBPosHpr = (-5, 0, 0, 0, 0, 0) + +LawbotBossWitnessStandPosHpr = (54,100,0,-90,0,0) + +LawbotBossInjusticePosHpr = ( -3, 12, 0, 90, 0, 0) +LawbotBossInjusticeScale = (1.75,1.75,1.5) +#LawbotBossInjusticePosHpr = ( 0, 0, 0, 0, 0, 0) + +#how much damage does each evidence on the defense pan do +LawbotBossDefensePanDamage = 1 + +# How many lawyers, and where are they? +LawbotBossLawyerPosHprs = [ + (-57, -24, 0, -90, 0, 0), + (-57, -12, 0, -90, 0, 0), + (-57, 0, 0, -90, 0, 0), + (-57, 12, 0, -90, 0, 0), + (-57, 24, 0, -90, 0, 0), + (-57, 36, 0, -90, 0, 0), + (-57, 48, 0, -90, 0, 0), + (-57, 60, 0, -90, 0, 0), + (-3, -37.3, 0, 0, 0, 0), + (-3, 53, 0, -180, 0, 0), + ] + +#how many seconds to wait till we attack or prosecute again +LawbotBossLawyerCycleTime = 6 + +#how many seconds does it take for evidence to fly from lawyers to prosecution +LawbotBossLawyerToPanTime = 2.5 + +#chance for the lawyers to do an attack (throw evidence at the toons) +LawbotBossLawyerChanceToAttack = 50 + +#when the lawyers throw stuff on the prosecution pan, how much damage to heal the boss +LawbotBossLawyerHeal = 2 + +#how many seconds do lawyers get stunned when we hit them +LawbotBossLawyerStunTime =5 + +#ammo count, how many gavels, how many lawyers, how much toonup, do jurors influence evidence weight, cog bonus subtractor (e.g. if this value is 1 and you seated 2 jurors), 2-1 leads to 1 as your bonus weight +LawbotBossDifficultySettings = [ + (38, 4, 8, 1, 0, 0), + (36, 5, 8, 1, 0, 0), + (34, 5, 8, 1, 0, 0), + (32, 6, 8, 2, 0, 0), + (30, 6, 8, 2, 0, 0), + (28, 7, 8, 3, 0, 0), + (26, 7, 9, 3, 1, 1), + (24, 8, 9, 4, 1, 1), + (22, 8, 10, 4, 1, 0), + ] + +#where are the cannons and how many are they +LawbotBossCannonPosHprs = [ + (-40, -12, 0, -90, 0, 0), + (-40, 0, 0, -90, 0, 0), + (-40, 12, 0, -90, 0, 0), + (-40, 24, 0, -90, 0, 0), + (-40, 36, 0, -90, 0, 0), + (-40, 48, 0, -90, 0, 0), + (-40, 60, 0, -90, 0, 0), + (-40, 72, 0, -90, 0, 0), + ] + + +LawbotBossCannonPosA = (-80, -51.48, 0) +LawbotBossCannonPosB = (-80, 70.73, 0) + +#where are the juror chairs and how many are they +LawbotBossChairPosHprs = [ + (60, 72, 0, -90, 0, 0), + (60, 62, 0, -90, 0, 0), + (60, 52, 0, -90, 0, 0), + (60, 42, 0, -90, 0, 0), + (60, 32, 0, -90, 0, 0), + (60, 22, 0, -90, 0, 0), + + (70, 72, 5, -90, 0, 0), + (70, 62, 5, -90, 0, 0), + (70, 52, 5, -90, 0, 0), + (70, 42, 5, -90, 0, 0), + (70, 32, 5, -90, 0, 0), + (70, 22, 5, -90, 0, 0), + + ] + +LawbotBossChairRow1PosB = (59.3, 48, 14.05) +LawbotBossChairRow1PosA = (59.3, -18.2, 14.05) +LawbotBossChairRow2PosB = (75.1, 48, 28.2) +LawbotBossChairRow2PosA = (75.1, -18.2, 28.2) + +#how many cannon balls can each toon fire +LawbotBossCannonBallMax = 12 + +#jury box info +LawbotBossJuryBoxStartPos = (94, -8, 5) +LawbotBossJuryBoxRelativeEndPos = (30,0,12.645) +LawbotBossJuryBoxMoveTime = 70 + +LawbotBossJurorsForBalancedScale = 8 +LawbotBossDamagePerJuror = 68 + +LawbotBossCogJurorFlightTime = 10 +LawbotBossCogJurorDistance = 75 + +#starting toon in cannon is Flippy at 2001 +LawbotBossBaseJurorNpcId = 2001 + +LawbotBossWitnessEpiloguePosHpr = ( -3, 0, 0, 180, 0, 0) + +LawbotBossChanceForTaunt = 25 + +#how long to wait before you can get bonus state again +LawbotBossBonusWaitTime = 60 +#how long does the bonus state last +LawbotBossBonusDuration = 20 +#how much of a toonup to give when they reach the bonus state +LawbotBossBonusToonup = 10 +#multiplier on defense evidence +LawbotBossBonusWeightMultiplier = 2 + +#spice it up they say, give the CJ a chance to do the area attack +LawbotBossChanceToDoAreaAttack = 11 + +# shard population limits +LOW_POP_JP = 0 +MID_POP_JP = 100 +HIGH_POP_JP = 200 + +LOW_POP_INTL = 399 +MID_POP_INTL = 499 +HIGH_POP_INTL = -1 + +LOW_POP = 399 +MID_POP = 599 +HIGH_POP = -1 + +# Cannon pinball stuff + +PinballCannonBumper = 0 +PinballCloudBumperLow = 1 +PinballCloudBumperMed = 2 +PinballCloudBumperHigh = 3 +PinballTarget = 4 +PinballRoof = 5 +PinballHouse =6 +PinballFence = 7 +PinballBridge = 8 +PinballStatuary = 9 + +#first number adds to score, 2nd number adds to multiplier +PinballScoring = [ + (100,1), #Cannon Bumper + (150,1), #Low cloud bumpers + (200,1), #medium cloud bumpers + (250,1), #high cloud bumpers + (350,1), #target + (100,1), #roof + (50,1), #house + (25,1), #fence + (100,1), #bridge + (10,1) #planted statuary items + ] + +PinballCannonBumperInitialPos = (0,-20,40) + +#rental types +RentalCop = 0 +RentalCannon = 1 +RentalGameTable = 2 + +#glichkiller zone list +GlitchKillerZones = [13300, 13400, 13500, 13600] + +#colors for the friends list panel +#could be a ColorCode in Name Tag groups +#but maybe all those should really be in script anyway + +ColorPlayer = (0.3, 0.7, 0.3, 1) +ColorAvatar = (0.3, 0.3, 0.7, 1) +ColorPet = (0.6, 0.4, 0.2, 1) + +ColorFreeChat = (0.3, 0.3, 0.8, 1) +ColorSpeedChat = (0.2, 0.6, 0.4, 1) +ColorNoChat = (0.8, 0.5, 0.1, 1) + +# Factory elevator laff point minimums + +FactoryLaffMinimums = [ + # sellbot + (0, 31), + # cashbot + (0, 66, 71), + # lawbot + (0, 81, 86, 96), + # bossbot + (0, 101, 106), + ] + +# Picnic table sitting ime +PICNIC_COUNTDOWN_TIME = 60 + +# CEO Battle stuff +BossbotRTIntroStartPosHpr = (0, -64, 0, 180, 0, 0) +BossbotRTPreTwoPosHpr = (0, -20, 0, 180, 0, 0) +BossbotRTEpiloguePosHpr = ( 0, 90, 0, 180, 0, 0) +BossbotBossBattleOnePosHpr = (0, 355, 0, 0, 0, 0) +BossbotBossPreTwoPosHpr = (0, 20, 0, 0, 0, 0) +BossbotElevCamPosHpr = (0, -100.544, 7.18258, 0, 0, 0) +BossbotFoodModelScale = 0.75 # do we scale up or down from the cog food model +BossbotNumFoodToExplode = 3 +BossbotBossServingDuration = 300 +BossbotPrepareBattleThreeDuration = 20 +# relative to bosscog coordinates for waiter battles +WaiterBattleAPosHpr = (20, -400, 0, 0, 0, 0) +WaiterBattleBPosHpr = (-20, -400, 0, 0, 0, 0) +BossbotBossBattleThreePosHpr = (0, 355, 0, 0, 0, 0) +DinerBattleAPosHpr = (20, -240, 0, 0, 0, 0) +DinerBattleBPosHpr = (-20, -240, 0, 0, 0, 0) +BossbotBossMaxDamage = 500 +BossbotMaxSpeedDamage = 90 +BossbotSpeedRecoverRate = 20 # in speed damage recovered per MINUTE +# num tables, diners per table, level of diners, table unflatten time, hungry duration, eating duration +BossbotBossDifficultySettings = [ + (8, 4, 11, 3, 30, 25), + (9, 5, 12, 6, 28, 26), + (10, 6, 11, 7, 26, 27), + (8, 8, 12, 8, 24, 28), + (13, 5, 12, 9, 22, 29), + ] +#BossbotRollSpeedMax = 15 +BossbotRollSpeedMax = 22 +#BossbotRollSpeedMin = 3.75 +BossbotRollSpeedMin = 7.5 +#BossbotTurnSpeedMax = 40 +BossbotTurnSpeedMax = 60 +#BossbotTurnSpeedMin = 10 +BossbotTurnSpeedMin = 20 +#BossbotTreadSpeedMax = 7 +BossbotTreadSpeedMax = 10.5 +#BossbotTreadSpeedMin = 1.75 +BossbotTreadSpeedMin = 3.5 + + +# Calendar Stuff +CalendarFilterShowAll = 0 +CalendarFilterShowOnlyHolidays = 1 +CalendarFilterShowOnlyParties = 2 + +# Hood identifiers +TTC = 1 +DD = 2 +MM = 3 +GS = 4 +DG = 5 +BR = 6 +OZ = 7 +DL = 8 + +# NewsPage stuff +DefaultWantNewsPageSetting = 1 + +# GM magic words +gmMagicWordList = [ + "restock", "restockUber", "autoRestock", + "resistanceRestock", "restockSummons", + "uberDrop", "rich", "maxBankMoney", + "toonUp", "rod", "cogPageFull", "pinkSlips", + "Tickets", "newSummons", "who", "who all" + ] + +NewsPageScaleAdjust = 0.85 +# Prop types for the new animating props +AnimPropTypes = Enum(("Unknown", + "Hydrant", + "Mailbox", + "Trashcan", + ), + start = -1 + ) diff --git a/toontown/src/toonbase/ToontownIntervals.py b/toontown/src/toonbase/ToontownIntervals.py new file mode 100644 index 0000000..45c5ba4 --- /dev/null +++ b/toontown/src/toonbase/ToontownIntervals.py @@ -0,0 +1,64 @@ +"""Generic intervals that can be used throughout Toontown classes.""" + +from direct.interval.MetaInterval import Sequence +from direct.interval.FunctionInterval import Wait, Func + +PULSE_GUI_DURATION = 0.2 +PULSE_GUI_CHANGE = .333 + +def cleanup(name): + """Clean stop of an ival""" + taskMgr.remove(name) + +def start(ival): + """Clean start of an ival""" + cleanup(ival.getName()) + ival.start() + +def loop(ival): + """Clean loop of an ival""" + cleanup(ival.getName()) + ival.loop() + + +def getPulseLargerIval(np, name, duration=PULSE_GUI_DURATION, scale=1): + """Creates a boing larger effect""" + return getPulseIval(np, name, 1 + PULSE_GUI_CHANGE, duration=duration, scale=scale) + +def getPulseSmallerIval(np, name, duration=PULSE_GUI_DURATION, scale=1): + """Creates a boing smaller effect""" + return getPulseIval(np, name, 1 - PULSE_GUI_CHANGE, duration=duration, scale=scale) + +def getPulseIval(np, name, change, duration=PULSE_GUI_CHANGE, scale=1): + """Creates a boing interval builder""" + return Sequence( + np.scaleInterval(duration, scale * change, blendType='easeOut'), + np.scaleInterval(duration, scale, blendType='easeIn'), + name=name, + autoFinish=1 + ) + + +def getPresentGuiIval(np, name, waitDuration=0.5, moveDuration=1.0, parent=aspect2d): + """ + Presents a new GUI: + Shows/boings the gui right on the center of the screen, + then moves the gui to where it's supposed to go. + """ + endPos = np.getPos() + np.setPos(parent, 0, 0, 0) + + return Sequence( + Func(np.show), + getPulseLargerIval(np, "", scale=np.getScale()), + Wait(waitDuration), + np.posInterval( + moveDuration, + endPos, + blendType="easeInOut", + ), + name=name, + autoFinish=1 + ) + + diff --git a/toontown/src/toonbase/ToontownLoader.py b/toontown/src/toonbase/ToontownLoader.py new file mode 100644 index 0000000..512a370 --- /dev/null +++ b/toontown/src/toonbase/ToontownLoader.py @@ -0,0 +1,101 @@ +"""ToontownLoader module: contains the extended loader that does wait bars""" + +from pandac.PandaModules import * +from direct.directnotify.DirectNotifyGlobal import * +from direct.showbase import Loader +from toontown.toontowngui import ToontownLoadingScreen + +class ToontownLoader(Loader.Loader): + """ToontownLoader class""" + + # special methods + def __init__(self, base): + Loader.Loader.__init__(self, base) + self.inBulkBlock = None + self.blockName = None + self.loadingScreen = ToontownLoadingScreen.ToontownLoadingScreen() + + def destroy(self): + self.loadingScreen.destroy() + del self.loadingScreen + Loader.Loader.destroy(self) + + # our extentions + def beginBulkLoad(self, name, label, range, gui, tipCategory): + Loader.Loader.notify.info("starting bulk load of block '%s'" % (name)) + if self.inBulkBlock: + Loader.Loader.notify.warning("Tried to start a block ('%s'), but am already in a block ('%s')" % (name, self.blockName)) + return None + self.inBulkBlock = 1 + self.blockName = name + self.loadingScreen.begin(range, label, gui, tipCategory) + + def endBulkLoad(self, name): + if not self.inBulkBlock: + Loader.Loader.notify.warning("Tried to end a block ('%s'), but not in one" % (name)) + return None + if name != self.blockName: + Loader.Loader.notify.warning("Tried to end a block ('%s'), other then the current one ('%s')" % (name, self.blockName)) + return None + self.inBulkBlock = None + expectedCount, loadedCount = self.loadingScreen.end() + Loader.Loader.notify.info("At end of block '%s', expected %s, loaded %s" % + (self.blockName, expectedCount, loadedCount)) + + def abortBulkLoad(self): + """ + Aborts whatever bulk load is in process, and cleans up neatly. + """ + if self.inBulkBlock: + Loader.Loader.notify.info("Aborting block ('%s')" % (self.blockName)) + self.inBulkBlock = None + self.loadingScreen.abort() + + # service function(s) for overloaded behavior + def tick(self): + if self.inBulkBlock: + self.loadingScreen.tick() + # Keep those heartbeats coming! + try: + base.cr.considerHeartbeat() + except: + pass + + # overload Loader.py functions + + def loadModel(self, *args, **kw): + ret = Loader.Loader.loadModel(self, *args, **kw) + self.tick() + return ret + + def loadFont(self, *args, **kw): + ret = Loader.Loader.loadFont(self, *args, **kw) + self.tick() + return ret + + def loadTexture(self, texturePath, alphaPath = None): + ret = Loader.Loader.loadTexture(self, texturePath, alphaPath) + self.tick() + if alphaPath: + self.tick() + return ret + + def loadSfx(self, soundPath): + ret = Loader.Loader.loadSfx(self, soundPath) + self.tick() + return ret + + def loadMusic(self, soundPath): + ret = Loader.Loader.loadMusic(self, soundPath) + self.tick() + return ret + + def loadDNAFileAI(self, dnaStore, dnaFile): + ret = loadDNAFileAI(dnaStore, dnaFile, CSDefault) + self.tick() + return ret + + def loadDNAFile(self, dnaStore, dnaFile): + ret = loadDNAFile(dnaStore, dnaFile, CSDefault, 0) + self.tick() + return ret diff --git a/toontown/src/toonbase/ToontownStart.py b/toontown/src/toonbase/ToontownStart.py new file mode 100644 index 0000000..003a470 --- /dev/null +++ b/toontown/src/toonbase/ToontownStart.py @@ -0,0 +1,242 @@ +# This is the file that gets imported by the Launcher as soon as phase 3 is +# complete. This will happen once during the install/download process as soon +# as phase 3 is finished downloading. When the Launcher is run after the +# download/install is complete, it will import this file after realizing +# phase 3 is already complete. +# +# +# Note: you can run this standalone by letting the launcher default to None +# from toontown.toonbase.ToontownStart import * +# + + +# This module redefines the builtin import function with one +# that prints out every import it does in a hierarchical form +# Annoying and very noisy, but sometimes useful +# import VerboseImport + +import __builtin__ + +class game: + name = "toontown" + process = "client" +__builtin__.game = game() + +import time +import os +import sys +import random +# Need to import __builtin__ and use the __builtin__.foo = x +# technique here in case you start toontown from the command line +import __builtin__ + +# See if we have a launcher, if we do not, make an empty one +try: + launcher +except: + from toontown.launcher.ToontownDummyLauncher import ToontownDummyLauncher + launcher = ToontownDummyLauncher() + __builtin__.launcher = launcher + + +# Default to "normal" web exit page. This should be set early +# so that the right thing will get done on a crash. "normal" +# may be marketting info, thanks for playing, or the report +# bug page. The installer.php file will use this setting. +launcher.setRegistry("EXIT_PAGE", "normal") + +# The first thing we need to do is make sure the Flash intro is not playing +# If it is, we need to wait here until it is done. We check to see if it is +# done by asking the Launcher. + +pollingDelay = 0.5 + +print 'ToontownStart: Polling for game2 to finish...' +while (not launcher.getGame2Done()): + time.sleep(pollingDelay) +print 'ToontownStart: Game2 is finished.' + +# Ok, now we know we are clear from the flash into, fire it up +print 'ToontownStart: Starting the game.' + +from pandac.PandaModules import * + +if launcher.isDummy(): + # Create a dummy HTTPClient so we can get that stupid openSSL + # random seed computed before we attempt to open the window. (We + # only need do this if we don't have a launcher. If we do have a + # launcher, it's already been created.) + http = HTTPClient() +else: + http = launcher.http + +# Preload the background scene before the window is even created +tempLoader = PandaLoader() +backgroundNode = tempLoader.loadSync(Filename('phase_3/models/gui/loading-background')) + +from direct.gui import DirectGuiGlobals +print 'ToontownStart: setting default font' +import ToontownGlobals +DirectGuiGlobals.setDefaultFontFunc(ToontownGlobals.getInterfaceFont) + +# First open a window so we can show the loading screen + +# Set the error code indicating failure opening a window in case we +# crash while opening it (the GSG code will just exit if it fails to +# get the window open). +launcher.setPandaErrorCode(7) + +# Make sure we create a ToonBase first +import ToonBase +ToonBase.ToonBase() +from pandac.PandaModules import * +if (base.win == None): + print "Unable to open window; aborting." + sys.exit() + +# Ok, we got the window open. +launcher.setPandaErrorCode(0) +# Tell the launcher that our panda window is open now so +# it can tell the browser and flash to shutdown +launcher.setPandaWindowOpen() + +# Also, once we open the window, dramatically drop the timeslice +# for decompressing and extracting files, so we don't interfere +# too much with rendering. +ConfigVariableDouble('decompressor-step-time').setValue(0.01) +ConfigVariableDouble('extractor-step-time').setValue(0.01) + +# Now put the background node under render +backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0) +backgroundNodePath.setPos(0.0, 0.0, 0.0) +backgroundNodePath.setScale(render2d, VBase3(1)) +# Set the draw order explicitly +backgroundNodePath.find("**/fg").setBin('fixed', 20) +backgroundNodePath.find("**/bg").setBin('fixed', 10) +# Let a frame render so we can see the background +base.graphicsEngine.renderFrame() + +# do the quest sanity check +if __debug__: + if base.config.GetBool('quest-sanity-check',0): + from toontown.quest import Quests + Quests.assertAllQuestsValid() + +DirectGuiGlobals.setDefaultRolloverSound(base.loadSfx("phase_3/audio/sfx/GUI_rollover.mp3")) +DirectGuiGlobals.setDefaultClickSound(base.loadSfx("phase_3/audio/sfx/GUI_create_toon_fwd.mp3")) +DirectGuiGlobals.setDefaultDialogGeom(loader.loadModel('phase_3/models/gui/dialog_box_gui')) + +# Set default product prefix +import TTLocalizer +from otp.otpbase import OTPGlobals +OTPGlobals.setDefaultProductPrefix(TTLocalizer.ProductPrefix) + +# Play music at startup +# This is a bit strange because the music is created here, then +# handed off to the cr to control. This is done so keep the music +# from skipping (if we stopped it and restarted it). +if base.musicManagerIsValid: + music = base.musicManager.getSound("phase_3/audio/bgm/tt_theme.mid") + if music: + music.setLoop(1) + music.setVolume(0.9) + music.play() + # Update default sound + print 'ToontownStart: Loading default gui sounds' + DirectGuiGlobals.setDefaultRolloverSound( + base.loadSfx("phase_3/audio/sfx/GUI_rollover.mp3")) + DirectGuiGlobals.setDefaultClickSound( + base.loadSfx("phase_3/audio/sfx/GUI_create_toon_fwd.mp3")) +else: + music = None + + +import ToontownLoader + +# tempLoaderOther = ToontownLoader.ToontownLoader(base) +# base.loader = tempLoaderOther +# __builtin__.loader = tempLoaderOther + +from direct.gui.DirectGui import * + +serverVersion = base.config.GetString("server-version", "no_version_set") +print 'ToontownStart: serverVersion: ', serverVersion +version = OnscreenText(serverVersion, + pos = (-1.3, -0.975), + scale = 0.06, + fg = Vec4(0,0,1,0.6), + align = TextNode.ALeft + ) + +# Now fire up toon base +loader.beginBulkLoad("init", TTLocalizer.LoaderLabel, 138, 0, TTLocalizer.TIP_NONE) +from ToonBaseGlobal import * +from direct.showbase.MessengerGlobal import * + +from toontown.distributed import ToontownClientRepository + +# Start up the client repository +cr = ToontownClientRepository.ToontownClientRepository(serverVersion, launcher) + +# Hand off the music to the TCR +cr.music = music +del music + +base.initNametagGlobals() + +# Save cr for debugging +base.cr = cr + +loader.endBulkLoad("init") + + + +# +# special Setup for A Global Friend Manager +# +from otp.friends import FriendManager +from otp.distributed.OtpDoGlobals import * + +cr.generateGlobalObject(OTP_DO_ID_FRIEND_MANAGER, "FriendManager") + +# Start the show +if not launcher.isDummy(): + # If the launcher is starting us, it knows the game server + # because it is passed in on the url + base.startShow(cr, launcher.getGameServer()) +else: + base.startShow(cr) + + +# Now get rid of the background and the temp loader +backgroundNodePath.reparentTo(hidden) +backgroundNodePath.removeNode() +del backgroundNodePath +del backgroundNode +del tempLoader +# tempLoaderOther.destroy() +# del tempLoaderOther +version.cleanup() +del version + +# replace the direct loader with the toontown one +base.loader = base.loader +__builtin__.loader = base.loader + +autoRun = ConfigVariableBool('toontown-auto-run', 1) + +if autoRun and launcher.isDummy(): + # This try .. except block exists solely to test the logic of + # PythonUtil.describeException. It's not at all necessary, and is + # useful only to those debugging that function; remove it if it + # bugs you. + try: + run() + + except SystemExit: + raise + + except: + from direct.showbase import PythonUtil + print PythonUtil.describeException() + raise diff --git a/toontown/src/toonbase/ToontownTimer.py b/toontown/src/toonbase/ToontownTimer.py new file mode 100644 index 0000000..9ded12f --- /dev/null +++ b/toontown/src/toonbase/ToontownTimer.py @@ -0,0 +1,25 @@ +from otp.otpbase.OTPTimer import OTPTimer +from pandac.PandaModules import * + +class ToontownTimer(OTPTimer): + """ + Implements the generic onscreen Toontown timer. + """ + + def __init__(self, useImage=True, highlightNearEnd=True): + # Initialize the parental stuff + OTPTimer.__init__(self, useImage, highlightNearEnd) + self.initialiseoptions(ToontownTimer) + + def getImage(self): + """ + Returns the image suitable for rendering the clock face. This + is loaded once if it has not been loaded before. This + function is useful to prevent loading this image (and leaking + it) every time a ToontownTimer is created. + """ + if ToontownTimer.ClockImage == None: + model = loader.loadModel("phase_3.5/models/gui/clock_gui") + ToontownTimer.ClockImage = model.find("**/alarm_clock") + model.removeNode() + return ToontownTimer.ClockImage diff --git a/toontown/src/toonbase/UserFunnel.py b/toontown/src/toonbase/UserFunnel.py new file mode 100644 index 0000000..c9dfe82 --- /dev/null +++ b/toontown/src/toonbase/UserFunnel.py @@ -0,0 +1,1093 @@ +"""UserFunnel.py: Contains functions to report data back to hitbox and our own data collection servers""" + +import os, sys, socket, random +from urllib import quote_plus + +from pandac.PandaModules import HTTPClient +from pandac.PandaModules import HTTPCookie +from pandac.PandaModules import URLSpec +from pandac.PandaModules import Ramfile +from pandac.PandaModules import Ostream +from pandac.PandaModules import HTTPDate +from pandac.PandaModules import DocumentSpec +from direct.task.Task import Task + +from direct.directnotify.DirectNotifyGlobal import directNotify +notify = directNotify.newCategory("UserFunnel") + +# The easy way to use this with hitbox is to import the UserFunnel module. +# Then execute UserFunnel.logSubmit(1,'STRING') +# Where STRING is the logging string (page name) to send to hitbox. +# You can use it with the internal data collector too, just use 0 instead of 1 + +class UserFunnel: + + def __init__(self): + + # Variables required for acct service access + # HitBox Account Number. + # DOL test account = DM510925KJWE + # Pirates Account = DM560804E8WD + # ToonTown Account = DM53030620EW + + self.hitboxAcct = 'DM53030620EW' + # self.hitboxAcct = 'DM510925KJWE' + # Language Used; example: English (US) = en-us + + self.language = 'en-us' + + # Content Group + + self.cgRoot = 'ToonTown_Online' + + # Content Sub-Groups + # For now, I've hardcoded the cgLocation to US. In the future + # we'll need to change this by hand or via a function, to reflect + # the geolocation that this is being built for. + + self.cgBeta = 'Beta' + self.cgRelease = 'Release' + self.cgLocation = 'US' + + # Campaign ID for hitbox + # Again, this ID has yet to be provided + + self.campaignID = '' + + # Cookie CallBack for HBX, but can be used for others if needed + + # self.cfCookie = cookielib.MozillaCookieJar('cf.txt') + self.cfCookieFile = 'cf.txt' + + # Host Listing. Access hostnames and paths will be listed here + # Each item has an int that goes along with it. + + self.dynamicVRFunnel = 'http://download.toontown.com/' + # self.dynamicVRFunnel = 'http://build64:3120/logging/collector.php' + + self.hostDict = {0:'Internal Disney PHP Collector Site', + 1:'ehg-dig.hitbox.com/HG?', + 2:'ehg-dig.hitbox.com/HG?', + 3:'build64.online.disney.com:5020/index.php?'} + + # The current host variable will be an int value that points to an + # entry in the hostDict. It is used at URL generation time to insert + # the correct hostname and path into the URL + + self.CurrentHost = '' + + # URLtoSend is the actual URL that will be accessed when run() is called + self.URLtoSend = '' + + # System Variables to report on. Currently, they are not all being used. + # Some variables have been put in place for future use. + + # GameName is the name of the game being reported on + + self.gameName = 'ToonTown' + + # BrowserName for ID + + self.browserName = 'Panda3D%20(' + self.gameName + ';%20' + sys.platform + ')' + # HTTPUserHeader to be transmitted once the http connection is established. This is not part of the URL that is sent. It is part of the header. + + self.HTTPUserHeader = [('User-agent', 'Panda3D')] + + # OS Major Version: Example MS-WinXP = 5, MacOSX Tiger = 10 + + self.osMajorver = '' + + # OS Minor Version: Example MS-WinXP = 1, OSX Tiger = 4 + + self.osMinorver = '' + + # OS Rev Version: Example OSX Tiger = 1...9 + + self.osRevver = '' + + # OS Build Number: Example MS-WinXP = 2600 + + self.osBuild = '' + + # OS Type. Example: int value that goes along with the msWinTypeDict + + self.osType = '' + + # The getwindowsversion command returns comments. An example would the a comment about the currently installed service pack + + self.osComments = '' + + # Dict of int to string pairs for self.osType + + self.msWinTypeDict = {0:'Win32s on Windows 3.1', + 1:'Windows 95/98/ME', + 2:'Windows NT/2000/XP', + 3:'Windows CE'} + + self.milestoneDict = {0:'New User', + 1:'Create Account', + 2:'View EULA', + 3:'Accept EULA', + 4:'Download Start', + 5:'Download End', + 6:'Installer Run', + 7:'Launcher Start', + 8:'Launcher Login', + 9:'Client Opens', + 10:'Create Pirate Loads', + 11:'Create Pirate Exit', + 12:'Cutscene One Start', + 13:'Cutscene One Ends', + 14:'Cutscene Two Start', + 15:'Cutscene Thee Start', + 16:'Cutscene Three Ends', + 17:'Access Cannon', + 18:'Cutscene Four Starts', + 19:'Cutscene Four Ends', + 20:'Dock - Start Game'} + + self.macTypeDict = {2:'Jaguar', + 1:'Puma', + 3:'Panther', + 4:'Tiger', + 5:'Lepard'} + + # Milestone string var. Used to hold the funnel location string. This used to be an int (as per the dict above), but later is was decided that it would be a string value; ie. START_GAME or BUILD_PIRATE_START. I have left the milestoneDict in place for reference purposes. + + self.milestone = '' + + # The next three lists hold the cookie vars for the three hitbox based + # variable / value pairs requred for hitbox. + # [DOMAIN, /, VARIABLE, VALUE] + + self.pandaHTTPClientVarWSS = [] + self.pandaHTTPClientVarCTG = [] + self.pandaHTTPClientVarDM = [] + + # In an effort to determine if this is the first time the client has + # been executed on the system, we will check for the existance of the + # cf.txt file. If the file does not exist, we will set the firstRun() + # to True. + + self.checkForCFfile() + + # Instance an HTTPClient session + + self.httpSession = HTTPClient() + + # Run the whatOSver command at the end of the constructor. + self.whatOSver() + + + def checkForCFfile(self): + # Check for the existance of the cf.txt file. If it does not exist, + # then set the firstRun() to True. If it does exist, do nothing. + + if firstRun() == True: + pass + else: + if(os.path.isfile(self.cfCookieFile) == False): + firstRun('write', True) + + + # Populate the osMajor, osMinor, osBuild, osType, osComments, and osRevver vars + def whatOSver(self): + if (sys.platform == 'win32'): + self.osMajorver = str(sys.getwindowsversion()[0]) + self.osMinorver = str(sys.getwindowsversion()[1]) + self.osBuild = str(sys.getwindowsversion()[2]) + self.osType = str(sys.getwindowsversion()[3]) + self.osComments = str(sys.getwindowsversion()[4]) + return + + if (sys.platform == 'darwin'): + self.osMajorver = '10' + osxcmd = '/usr/sbin/system_profiler SPSoftwareDataType |/usr/bin/grep "System Version"' + infopipe = os.popen(osxcmd, 'r') + parseLine = infopipe.read() + infopipe.close() + del infopipe + notify.info("parseLine = %s" % str(parseLine)) + versionStringStart = parseLine.find('10.') + notify.info("versionStringStart = %s" % str(versionStringStart)) + testPlist = False + try: + # I placed this segment into the try/except pair due to an + # exception that pops up at most once a day, where these + # assignments return an out of range error + # RAU exception always happen in 10.6 + + self.osMinorver = parseLine[versionStringStart+3] + self.osRevver = parseLine[versionStringStart+5:versionStringStart+7].strip(' ') + self.osBuild = parseLine[int(parseLine.find('('))+1:parseLine.find(')')] + except: + # This should catch this rare case. It's probably happening + # due to a corrupt OS install on the client. + # In this case, we'll just manually assign values + # RAU so 10.6 will always report as 10.0 + notify.info("couldn't parse the system_profiler output, using zeros") + self.osMinorver = '0' + self.osRevver = '0' + self.osBuild = '0000' + testPlist = True + del versionStringStart + del parseLine + del osxcmd + + if testPlist: + try: + import plistlib + pl = plistlib.readPlist("/System/Library/CoreServices/SystemVersion.plist") + notify.info("pl=%s" % str(pl)) + parseLine = pl['ProductVersion'] + numbers = parseLine.split('.') + notify.info("parseline =%s numbers =%s" % (parseLine, numbers)) + self.osMinorver = numbers[1] + self.osRevver = numbers[2] + self.osBuild = pl["ProductBuildVersion"] + except: + notify.info("tried plist but still got exception") + self.osMinorver = '0' + self.osRevver = '0' + self.osBuild = '0000' + return + + def setmilestone(self,ms): + if firstRun() == False: + self.milestone = ms + else: + self.milestone = '%s_INITIAL' % (ms) + + def setgamename(self, gamename): + self.gameName = gamename + + def printosComments(self): + return self.osComments + + def setHost(self, hostID): + assert hostID < len(self.hostDict), "Error: hostID passed in UserTracker.setHost not valid, value to high" + assert hostID > -1, "Error: hostID must be 0 or positive int" + self.CurrentHost = hostID + + # This will go out to the download server and get the current Disney Funnel logging URL + + def getFunnelURL(self): + # print 'VRS URL: ' + self.dynamicVRFunnel + if (patcherVer() == ['OFFLINE']): + # print "Funnel System Offline" + return + if (patcherVer() == []): + # print "Funnel URL not set. Setting now" + patcherHTTP = HTTPClient() + if checkParamFile() == None: + patcherDoc = patcherHTTP.getDocument(URLSpec('http://download.toontown.com/english/currentVersion/content/patcher.ver')) + # Now set vcon (Content Group) to the Release string + vconGroup('w', self.cgRelease) + else: + patcherDoc = patcherHTTP.getDocument(URLSpec(checkParamFile())) + # Set vcon (Content Group) to the Beta string + vconGroup('w', self.cgBeta) + # patcherDoc = patcherHTTP.getDocument(URLSpec('http://build64:3120/english/currentVersion/dev/content/patcher.ver')) + rf = Ramfile() + patcherDoc.downloadToRam(rf) + self.patcherURL = rf.getData() + if self.patcherURL.find('FUNNEL_LOG') == -1: + # The file did not download, need to set + # the patcherVer to offline + patcherVer('w','OFFLINE') + # print 'Patcher system could not be reached' + return + self.patcherURL = self.patcherURL.split('\n') + del rf, patcherDoc, patcherHTTP + while self.patcherURL: + self.confLine = self.patcherURL.pop() + if (self.confLine.find('FUNNEL_LOG=') != -1 and self.confLine.find('#FUNNEL_LOG=') == -1 ): + self.dynamicVRFunnel = self.confLine[11:].strip('\n') + patcherVer('w',self.confLine[11:].strip('\n')) + else: + self.dynamicVRFunnel = patcherVer()[0] + + def isVarSet(self, varInQuestion): + try: + tempvar = type(varInQuestion) + return 1 + except NameError: + return 0 + + def buildURL(self): + + # A recent Hitbox Addition. We need to generate a variable/value pair. + # The variable name depends on the OS. If the OS is win32, then the + # variable name is c3. If the OS is darwin, then the variable name + # is c4. The value to be passed to c3 and c4 is the same: + # str(self.osMajorver) + '_' + str(self.osMinorver) + '_' + str(self.osRevver) + '_' + str(self.osBuild) + + if sys.platform == 'win32': + hitboxOSType = 'c3' + else: + hitboxOSType = 'c4' + + # This will take all of the required variables and generate + # A URL to be transmitted to the currently selected service + # Host 1 is the hitbox URL config + + if (self.CurrentHost == 1): + self.URLtoSend = 'http://' + self.hostDict[self.CurrentHost] + 'hb=' + str(self.hitboxAcct) + '&n=' + str(self.milestone) + '&ln=' + self.language + '&gp=STARTGAME&fnl=TOONTOWN_FUNNEL&vcon=/' + self.cgRoot + '/' + self.cgLocation + '/' + str(vconGroup()) + '&c1=' + str(sys.platform) + '&' + str(hitboxOSType) + '=' + str(self.osMajorver) + '_' + str(self.osMinorver) + '_' + str(self.osRevver) + '_' + str(self.osBuild) + # print self.URLtoSend + + # Host 2 is for the Hitbox, with no funnel + + if (self.CurrentHost == 2): + self.URLtoSend = 'http://' + self.hostDict[self.CurrentHost] + 'hb=' + str(self.hitboxAcct) + '&n=' + str(self.milestone) + '&ln=' + self.language + '&vcon=/' + self.cgRoot + '/' + self.cgLocation + '/' + str(vconGroup()) + '&c1=' + str(sys.platform) + '&' + str(hitboxOSType) + '=' + str(self.osMajorver) + '_' + str(self.osMinorver) + '_' + str(self.osRevver) + '_' + str(self.osBuild) + # Host 3 is the disney logging server config. + + # if (self.CurrentHost == 3): + # self.URLtoSend = 'http://' + self.hostDict[self.CurrentHost] + 'some_var_name=' + self.gameName + + #Need to add a bunch more. Just not sure of the variable names yet + # This host is for the internal server + + if (self.CurrentHost == 0): + localMAC = str(getMAC()) + self.URLtoSend = str(self.dynamicVRFunnel) + '?funnel=' + str(self.milestone) + '&platform=' + str(sys.platform) + '&sysver=' + str(self.osMajorver) + '_' + str(self.osMinorver) + '_' + str(self.osRevver) + '_' + str(self.osBuild) + '&mac=' + localMAC + '&username=' + str(loggingSubID()) + '&id=' + str(loggingAvID()) + + + def readInPandaCookie(self): + # This function is designed to read in the cookie format that + # the panda HTTPClient uses. + # The format is as follows: + # DOMAINAME/VARIABLEVALUE<\n> + # EXAMPLE: + # .hitbox.com / CTG 1181271609 + + thefile = open(self.cfCookieFile, 'r') + thedata = thefile.read().split('\n') + thefile.close() + del thefile + # Before we go any further, lets check to see if the file is using + # the old Netscape HTTP format the python's MozillaCookie Jar + # supports. If so, lets delete the file and re-populate with + # a new cookie from the server + if (thedata[0].find('Netscape HTTP Cookie File') != -1): + return + # Pop off last element; it's blank + thedata.pop() + try: + while thedata: + temp = thedata.pop() + # if temp.find('.hitbox.com') != -1 or temp.find('ehg-dig.hitbox.com') != -1: + temp = temp.split('\t') + domain = temp[0] + loc = temp[1] + variable = temp[2] + value = temp[3] + + if (variable == 'CTG'): + self.pandaHTTPClientVarCTG = [domain, loc, variable, value] + self.setTheHTTPCookie(self.pandaHTTPClientVarCTG) + if (variable == self.hitboxAcct + 'V6'): + self.pandaHTTPClientVarDM = [domain, loc, variable, value] + self.setTheHTTPCookie(self.pandaHTTPClientVarDM) + if (variable == 'WSS_GW'): + self.pandaHTTPClientVarWSS = [domain, loc, variable, value] + self.setTheHTTPCookie(self.pandaHTTPClientVarWSS) + except IndexError: + print "UserFunnel(Warning): Cookie Data file bad" + + del thedata + + def updateInstanceCookieValues(self): + a = self.httpSession.getCookie(HTTPCookie('WSS_GW', '/', '.hitbox.com')) + if a.getName(): + self.pandaHTTPClientVarWSS = ['.hitbox.com', '/', 'WSS_GW', a.getValue()] + else: + # print 'WSS_GW Cookie Value not set' + pass + + b = self.httpSession.getCookie(HTTPCookie('CTG', '/', '.hitbox.com')) + if b.getName(): + self.pandaHTTPClientVarCTG = ['.hitbox.com', '/', 'CTG', b.getValue()] + else: + # print 'CTG Cookie Value not set' + pass + + c = self.httpSession.getCookie(HTTPCookie(self.hitboxAcct + 'V6', '/', 'ehg-dig.hitbox.com')) + if c.getName(): + self.pandaHTTPClientVarDM = ['ehg-dig.hitbox.com', '/', self.hitboxAcct + 'V6', c.getValue()] + else: + #print self.hitboxAcct + 'V6 Cookie Value not set' + pass + + del a, b, c + + def setTheHTTPCookie(self, cookieParams): + c = HTTPCookie(cookieParams[2], cookieParams[1], cookieParams[0]) + c.setValue(cookieParams[3]) + self.httpSession.setCookie(c) + + def writeOutPandaCookie(self): + # This is designed to write out a cookie file in the format that + # the panda HTTPClient uses. + # Please see the readInPandaCookie comments for format. + + try: + thefile = open(self.cfCookieFile, 'w') + if len(self.pandaHTTPClientVarWSS) == 4: + thefile.write(self.pandaHTTPClientVarWSS[0] + '\t' + self.pandaHTTPClientVarWSS[1] + '\t' + self.pandaHTTPClientVarWSS[2] + '\t' + self.pandaHTTPClientVarWSS[3] + '\n') + if len(self.pandaHTTPClientVarCTG) == 4: + thefile.write(self.pandaHTTPClientVarCTG[0] + '\t' + self.pandaHTTPClientVarCTG[1] + '\t' + self.pandaHTTPClientVarCTG[2] + '\t' + self.pandaHTTPClientVarCTG[3] + '\n') + if len(self.pandaHTTPClientVarDM) == 4: + thefile.write(self.pandaHTTPClientVarDM[0] + '\t' + self.pandaHTTPClientVarDM[1] + '\t' + self.pandaHTTPClientVarDM[2] + '\t' + self.pandaHTTPClientVarDM[3] + '\n') + thefile.close() + except IOError: + return + + # The next function spawns another thread and executed the network + # transaction; i.e. host resolve, open connection, send, close, etc. + # Update, the threading has been disbaled for the time being. + + def prerun(self): + + # print "Begin Hitbox Thread" + # Use start() method to execute this run() function in second thread + + # Commented out the next line (if statement) on 9-10-07, + # and moved the indent on self.getFunnelURL to the left. + # It looks like due to changes in the function that gets the + # patcher.ver, it is no longer necessary to only check the FunnelURL + # status when the CurrentHost is 0. The getFunnelURL() should be + # called no mater what the CurrentHost is set to. I think that it was + # only checking when CurrentHost == 0, due to some URLs being + # hardcoded in previous versions of the logging module. But that + # is no longer the case for the VRS collector. + + # if (self.CurrentHost == 0): + self.getFunnelURL() + + # print "build url" + self.buildURL() + if(os.path.isfile(self.cfCookieFile) == True): + # print "load preexisting cookie" + # self.cfCookie.load() + if self.CurrentHost == 1 or self.CurrentHost == 2: + self.readInPandaCookie() + # print "Cookies before transaction" + # self.httpSession.writeCookies(ostream) + # print "Cookie Header Line" + # self.httpSession.sendCookies(ostream, URLSpec(self.URLtoSend)) + + def run(self): + + # Here is where the new Panda based HTTP code starts + + # But before we hit the URL, let make sure we need to. + # Lets check to see if the VRS Collector is OFFLINE and + # host type 0 was selected. If that is the case, we can just + # return here; nothing needs to be done. + + if self.CurrentHost == 0 and patcherVer() == ['OFFLINE']: + return + + # Hit the URL + # The next line uses the Panda HTTP lib (blocking) + # doc = self.httpSession.getDocument(URLSpec(self.URLtoSend)) + + # Next line uses non-blocking + + self.nonBlock = self.httpSession.makeChannel(False) + # nonBlock.setHttpTimeout(1) + # nonBlock.setConnectTimeout(.5) + # nonBlock.setBlockingConnect(False) + # doc = nonBlock.getDocument(DocumentSpec(self.URLtoSend)) + self.nonBlock.beginGetDocument(DocumentSpec(self.URLtoSend)) + + instanceMarker = str(random.randint(1,1000)) + + instanceMarker = 'FunnelLoggingRequest-%s' % instanceMarker + + self.startCheckingAsyncRequest(instanceMarker) + + # That's it. The server should have recorded the hit + # delete the object + # del doc + + # print "The Funnel URL could not be accessed" + # if (self.CurrentHost == 0): + # patcherVer('w','OFFLINE') + # For testing, write out all cookies in memory + # print "Cookies after Transaction" + # self.httpSession.writeCookies(ostream) + + # Commented out the following, moved it to the taskMgr call + # if self.CurrentHost == 1 or self.CurrentHost == 2: + # self.updateInstanceCookieValues() + # self.writeOutPandaCookie() + + # Now lets do a check to see if the string LEAK is in the milestone + # If LEAK is present, then we will also call the memory leak report + # function to submit a report. + + # if self.milestone.find('LEAK') != -1: + # reportMemoryLeaks() + + def startCheckingAsyncRequest(self, name): + taskMgr.remove(name) + # print "Starting Checking Async Request" + taskMgr.doMethodLater(0.5, self.pollFunnelTask, name) + + def stopCheckingFunnelTask(self, name): + taskMgr.remove('pollFunnelTask') + + def pollFunnelTask(self, task): + # print "Polling....." + result = self.nonBlock.run() + if result == 0: + # print "Result = 0, Done" + # Funnel request complete + self.stopCheckingFunnelTask(task) + if self.CurrentHost == 1 or self.CurrentHost == 2: + self.updateInstanceCookieValues() + self.writeOutPandaCookie() + else: + return Task.again + +def logSubmit(setHostID, setMileStone): + + # Autopilot. Just run logSubmit passing it the service ID and the milestone id. It will do the rest + if __dev__: + # print "UserFunnel: Game running in Dev Mode. Not logging to Hitbox or VRS Collector." + assert notify.debug('UserFunnel: Game running in Dev Mode. Not logging to Hitbox or VRS Collector.') + return + if __debug__: + # print "UserFunnel: Game running in Debug Mode. Not logging to Hitbox or VRS Collector" + assert notify.debug('UserFunnel: Game running in Debug Mode. Not logging to Hitbox or VRS Collector.') + return + + trackItem = UserFunnel() + trackItem.setmilestone(quote_plus(setMileStone)) + trackItem.setHost(setHostID) + trackItem.prerun() + # trackItem.start() + trackItem.run() + # del trackItem + # print 'Hitbox logging executed: ' + setMileStone + +def getVRSFunnelURL(): + # Autopilot to get the funnel URL + a = UserFunnel() + a.getFunnelURL() + +class HitBoxCookie: + def __init__(self): + # print 'UserFunnel: Created New HitBoxCookie Object' + # Cookie file path and cookie file names + self.ieCookieDir = os.getenv('USERPROFILE') + '\\Cookies' + self.pythonCookieFile = 'cf.txt' + self.hitboxCookieFile = None + self.ehgdigCookieFile = None + + # HitBox Account Numbers + # DOL test account = DM510925KJWE + # Pirates Account = DM560804E8WD + # ToonTown Account = DM53030620EW + + self.hitboxAcct = 'DM53030620EW' + + # Data for the hitbox cookies. + # Once extracted each item will be a list of three items. + + self.ctg = None + self.wss_gw = None + #self.dm560804E8WD = None + self.dmAcct = None + + # Header for Python Cookie files + self.pythonCookieHeader = ' # Netscape HTTP Cookie File\n # http://www.netscape.com/newsref/std/cookie_spec.html\n # This is a generated file! Do not edit.\n\n' + + def returnIECookieDir(self): + return self.ieCookieDir + + def findIECookieFiles(self): + try: + sdir = os.listdir(self.ieCookieDir) + except WindowsError: + print 'Dir does not exist, do nothing' + return + + while sdir: + temp = sdir.pop() + if (temp.find('@hitbox[') != -1): + self.hitboxCookieFile = temp + if (temp.find('@ehg-dig.hitbox[') != -1): + self.ehgdigCookieFile = temp + if (self.hitboxCookieFile != None and self.ehgdigCookieFile != None): + # print 'UserFunnel: Both Files Have been located' + return 1 + if (self.hitboxCookieFile == None and self.ehgdigCookieFile == None): + # print 'UserFunnel: Error, neither file was located' + return 0 + else: + # print 'UserFunnel: At least one cookie file was located' + return -1 + + def openHitboxFile(self, filename, type = 'python'): + # If the type passed is 'ie', then the opener assumes that it should + # prefix the filename with the cookie dir path + if (type == 'ie'): + fullfile = self.ieCookieDir + '\\' + filename + else: + fullfile = filename + # print 'Opening ' + fullfile + cf = open(fullfile, 'r') + data = cf.read() + cf.close() + return data + + def splitIECookie(self,filestream): + # Break up the cookie, by placing each domain entry into a different list item + return filestream.split('*\n') + + def sortIECookie(self,filestreamListElement): + return [filestreamListElement.split('\n')[2],filestreamListElement.split('\n')[0],filestreamListElement.split('\n')[1]] + + def sortPythonCookie(self,filestreamListElement): + return [filestreamListElement.split('\t')[0], filestreamListElement.split('\t')[5], filestreamListElement.split('\t')[6]] + + # Writing to the IE CookieJar will require the preservation of other hitbox related entries. + + def writeIEHitBoxCookies(self): + if ( self.ctg == None or self.wss_gw == None or self.dmAcct ==None): + # print 'UserFunnel: Error: CTG, WSS, or DM vars are not populated' + return + if (sys.platform != 'win32'): + # print 'Not Windows' + return + # First we need to get the path to the cookiejar + # In case it hasn't been executed already, we'll run it again to + # location the two hitbox related cookie files we need to work with. + self.findIECookieFiles() + # First, we'll start with the ehg-dig file. + # This file has the dm560804E8WD entry + iecData = self.openHitboxFile(self.ehgdigCookieFile, 'ie') + iecData = iecData.split('*\n') + x = 0 + while (x < len(iecData)): + if iecData[x].find(self.hitboxAcct) != -1: + # We've located the entry we need to modify + # print 'DM Found' + iecData.pop(x) + print 'Removed it from the list' + break + x += 1 + # Now we need to write the list back out to file. + iecWrite = open(self.ieCookieDir + '\\' + self.ehgdigCookieFile, 'w') + while iecData: + # iecWrite.write(iecData.pop() + '*\n') + iecBuffer = (iecData.pop() + '*\n') + iecBuffer = iecBuffer.strip('/') + if (iecBuffer[0] == '.'): + iecBuffer = iecBuffer[1:] + iecWrite.write(iecBuffer) + tempDMBUFFER = self.dmAcct[0] + if tempDMBUFFER[0].find('.') == 0: + tempDMBUFFER = tempDMBUFFER[1:] + iecWrite.write(self.dmAcct[1] + '\n' + self.dmAcct[2] + '\n' + tempDMBUFFER + '/\n*\n') + iecWrite.close() + + # Now on to the other file. From what I can tell self.hitboxCookieFile is safe to overwrite completly with the CTG and WSS_GW values. + + del iecData + del iecWrite + del iecBuffer + iecWrite = open(self.ieCookieDir + '\\' + self.hitboxCookieFile, 'w') + iecBuffer = self.ctg[0] + if (iecBuffer[0] == '.'): + iecBuffer = iecBuffer[1:] + if (iecBuffer.find('/') == -1): + iecBuffer = iecBuffer + '/' + iecWrite.write(self.ctg[1] + '\n' + self.ctg[2] + '\n' + iecBuffer + '\n*\n') + iecWrite.write(self.wss_gw[1] + '\n' + self.wss_gw[2] + '\n' + iecBuffer + '\n*\n') + iecWrite.close() + + + + # This will write a python cookie file. Unless specified when called, the file will be called cf.txt. This is the old function, based on the netscape HTTP cokie format. + + def OLDwritePythonHitBoxCookies(self, filename = 'cf.txt'): + if ( self.ctg == None or self.wss_gw == None or self.dmAcct ==None): + # print 'UserFunnel: Error: CTG, WSS, or DM vars are not populated' + return + outputfile = open(filename,'w') + # First we can write out the header + + outputfile.write(self.pythonCookieHeader) + + # Next the domain, for the first entry. Note: the IE cookie files have a '/' after the domain name, while the python (Netscape HTTP format) does not. We need to check for the slash and strip it out before writing to the file + + outputfile.write('.' + self.dmAcct[0].strip('/') + '\tTRUE\t/\tFALSE\t9999999999\t' + self.dmAcct[1] + '\t' + self.dmAcct[2] + '\n') + # Now the second + outputfile.write('.' + self.ctg[0].strip('/') + '\tTRUE\t/\tFALSE\t9999999999\t' + self.ctg[1] + '\t' + self.ctg[2] + '\n') + # And the third + outputfile.write('.' + self.wss_gw[0].strip('/') + '\tTRUE\t/\tFALSE\t9999999999\t' + self.wss_gw[1] + '\t' + self.wss_gw[2] + '\n') + outputfile.close() + + def writePythonHitBoxCookies(self, filename = 'cf.txt'): + if ( self.ctg == None or self.wss_gw == None or self.dmAcct ==None): + # print 'UserFunnel: Error: CTG, WSS, or DM vars are not populated' + return + outputfile = open(filename,'w') + # First we can write out the header + + # outputfile.write(self.pythonCookieHeader) + + # Next the domain, for the first entry. Note: the IE cookie files have a '/' after the domain name, while the python (Netscape HTTP format) does not. We need to check for the slash and strip it out before writing to the file + + outputfile.write('.' + self.dmAcct[0].strip('/') + '\t/\t' + self.dmAcct[1] + '\t' + self.dmAcct[2] + '\n') + # Now the second + outputfile.write('.' + self.ctg[0].strip('/') + '\t/\t' + self.ctg[1] + '\t' + self.ctg[2] + '\n') + # And the third + outputfile.write('.' + self.wss_gw[0].strip('/') + '\t/\t' + self.wss_gw[1] + '\t' + self.wss_gw[2] + '\n') + outputfile.close() + + + + + # This will load the python cookies + + def loadPythonHitBoxCookies(self): + if (os.path.isfile(self.pythonCookieFile) != 1): + # print 'The python cookie file does not exist.' + return + pythonStandard = self.openHitboxFile(self.pythonCookieFile, 'python') + # print pythonStandard + # Now split the file at the \n\n, and pop off the second element. This will remove the header + pythonStandard = pythonStandard.split('\n\n').pop(1) + # Now split the second element into lines; each line is a cookie entry + pythonStandard = pythonStandard.split('\n') + # Now, we will locate the line with the DM, CTG, and WSS var + for x in pythonStandard: + if (x.find('\t' + self.hitboxAcct) != -1): + # print self.hitboxAcct + self.dmAcct = self.sortPythonCookie(x) + if (x.find('\tCTG\t') != -1): + # print 'CTG Found' + self.ctg = self.sortPythonCookie(x) + if (x.find('\tWSS_GW\t') != -1): + # print 'WSS_GW Found' + self.wss_gw = self.sortPythonCookie(x) + + # This function will locate the IE hitbox cookies (relating to Pirates), and place them in the proper list variable + + def loadIEHitBoxCookies(self): + if (self.findIECookieFiles() != 1): + # print 'UserFunnel: Error! One or both of the IE cookie files could not be loaded.' + return + + if (sys.platform != 'win32'): + # print 'Not Windows' + return + + hitboxStandard = self.openHitboxFile(self.hitboxCookieFile, 'ie') + hitboxDIG = self.openHitboxFile(self.ehgdigCookieFile, 'ie') + + hitboxStandard = self.splitIECookie(hitboxStandard) + hitboxDIG = self.splitIECookie(hitboxDIG) + + # Now we need to locate the CTG and WSS_GW variable + ctg = None + wss = None + for x in hitboxStandard: + if (x.find('CTG\n') != -1): + # print 'CTG Found' + ctg = x + if (x.find('WSS_GW\n') != -1): + # print 'WSS_GW Found' + wss = x + if (ctg == None or wss == None): + # print 'Both Cookie Values in hitbox could not be found' + return + + # Now locate the pirates account number in ehg-dig.hitbox file + DM = None + for x in hitboxDIG: + if (x.find(self.hitboxAcct) != -1): + # print 'DM560804E8WD account found in cookie' + DM = x + if (DM == None): + # print 'DM Cookie Value in ehg-dig.hitbox could not be found' + return + + # Now split the streams into 3 elements of a list + + self.ctg = self.sortIECookie(ctg) + self.wss_gw = self.sortIECookie(wss) + self.dm560804E8WD = self.sortIECookie(DM) + + + + +# This should convert HitBox cookies generated by MS-IE (AKA the ActiveX and Launcher on Win32 systems), to the Netscape/Mozilla format that python needs + +def convertHitBoxIEtoPython(): + if (sys.platform != 'win32'): + print "Cookie Converter: Warning: System is not MS-Windows. I have not been setup to work with other systems yet. Sorry " + sys.platform + " user. The game client will create a cookie." + return + if __dev__: + return + if __debug__: + return + # There are two IE cookie files that we need to extract from + # username@ehg-dig.hitbox[n].txt and username@hitbox[n].txt + # Once we have the data from each, we need to merge them into cf.txt + + a = HitBoxCookie() + a.loadIEHitBoxCookies() + a.writePythonHitBoxCookies() + del a + +# This will convert back the other way. Python to IE + +def convertHitBoxPythontoIE(): + if (sys.platform != 'win32'): + print "System is not MS-Windows. I have not been setup to work with other systems yet. Sorry " + sys.platform + " user." + return + + # Next, if the cookiefile already exists, then we don't have to convert it from IE to python. + + if(os.path.isfile('cf.txt') == True): + return + + a = HitBoxCookie() + a.loadPythonHitBoxCookies() + a.writeIEHitBoxCookies() + del a + +def getreg(regVar): + if (sys.platform != 'win32'): + print "System is not MS-Windows. I haven't been setup yet to work with systems other than MS-Win using MS-Internet Explorer Cookies" + return '' + # Site to scan for cookie from + + siteName = 'toontown.online.disney' + + # Next, get the USERPROFILE dir + + cookiedir = os.getenv('USERPROFILE') + '\\Cookies' + + # make sdir a list of file in the cookiedir + + sdir = os.listdir(cookiedir) + wholeCookie = None + while sdir: + temp = sdir.pop() + if (temp.find(siteName) != -1): + wholeCookie = temp + break + if (wholeCookie == None): + print "Cookie not found for site name: " + siteName + return '' + CompleteCookiePath = cookiedir + '\\' + wholeCookie + cf = open(CompleteCookiePath, 'r') + + # data will contain the complete contents of the cookie file + + data = cf.read() + + # Close the file + + cf.close() + + # Delete reference + + del cf + + # Change a few chars + + data = data.replace('%3D','=') + data = data.replace('%26','&') + + # Attempt to locate the variable + + regNameStart = data.find(regVar) + if (regNameStart == -1): + return '' + regVarStart = data.find('=',regNameStart + 1) + regVarEnd = data.find('&',regNameStart + 1) + return data[regVarStart+1: regVarEnd] + +# getMAC() should allow for the MAC and IP address to be extracted from the NIC +# This should be a unique identifier. Function currently returns the MAC +# For MS-Windows, we attempt to locate the first Local Area Connection, then return that MAC. +# For OSX, we take the first MAC address listed in the system profiler. + +def getMAC(staticMAC = [None]): + + if staticMAC[0] == None: + if (sys.platform == 'win32'): + correctSection = 0 + # curr_ip = socket.gethostbyname(socket.gethostname()) + try: + ipconfdata = os.popen('/WINDOWS/SYSTEM32/ipconfig /all').readlines() + except: + # Could not get MAC address due to lack of execute permissions + # on the ipconfig program. (Most likely a Vista issue) + # print "MAC ADDRESS Recovery Problm" + staticMAC[0] = 'NO_MAC' + return staticMAC[0] + + for line in ipconfdata: + if line.find('Local Area Connection') >= 0: + correctSection = 1 + if line.find('Physical Address') >= 0 and correctSection == 1: + pa = line.split(':')[-1].strip() + correctSection = 0 + staticMAC[0] = pa + return pa + + + if (sys.platform == 'darwin'): + # curr_ip = socket.gethostbyname(socket.gethostname()) + macconfdata = os.popen('/usr/sbin/system_profiler SPNetworkDataType |/usr/bin/grep MAC').readlines() + result = '-1' + if macconfdata: + if (macconfdata[0].find('MAC Address') != -1): + pa = macconfdata[0][macconfdata[0].find(':') + 2 : macconfdata[0].find(':') + 22].strip('\n') + staticMAC[0] = pa.replace(':','-') + result = staticMAC[0] + return result + + + if (sys.platform != 'darwin' and sys.platform != 'win32'): + print "System is not running OSX or MS-Windows." + return '-2' + + else: + + return staticMAC[0] + +def firstRun(operation = 'read', newPlayer = None, newPlayerBool = [False]): + if (operation != 'read'): + if (len(newPlayerBool) != 0): + newPlayerBool.pop() + newPlayerBool.append(newPlayer) + return newPlayerBool[0] + +def patcherVer(operation = 'read', url = None, patchfile=[]): + if (operation != 'read'): + if (len(patchfile) != 0): + patchfile.pop() + patchfile.append(url) + return patchfile + +def loggingAvID(operation = 'read', newId = None, localAvId = [None]): + if operation == 'write': + localAvId[0] = newId + else: + return localAvId[0] + +def loggingSubID(operation = 'read', newId = None, localSubId = [None]): + if operation == 'write': + localSubId[0] = newId + else: + return localSubId[0] + +def vconGroup(operation = 'read', group = None, staticStore=[]): + if (operation != 'read'): + if (len(staticStore) != 0): + staticStore.pop() + staticStore.append(group) + try: + return staticStore[0] + except IndexError: + return None + +def printUnreachableLen(): + # check memory on memory leaks + import gc + gc.set_debug(gc.DEBUG_SAVEALL) + gc.collect() + unreachableL = [] + for it in gc.garbage: + unreachableL.append(it) + return len(str(unreachableL)) + +def printUnreachableNum(): + # Check memory and return number of unreachable objects + import gc + gc.set_debug(gc.DEBUG_SAVEALL) + gc.collect() + return len(gc.garbage) + + +def reportMemoryLeaks(): + # First check to make sure we are leaking, if number of leaks = 0, we can return + if printUnreachableNum() == 0: + return + + + # If we made it this far, then some sort of leaking has happened. + # For this, we will need the bz2(compression), gc(access to garbage list) modules + + import bz2, gc + # import httplib + + gc.set_debug(gc.DEBUG_SAVEALL) + gc.collect() + uncompressedReport = '' + for s in gc.garbage: + try: + uncompressedReport += str(s) + '&' + except TypeError: + # __repr__ is probably trying to return a non-string + pass + reportdata = bz2.compress(uncompressedReport, 9) + + headers = {"Content-type": "application/x-bzip2", "Accept": "text/plain"} + # Need to split patcherVer()[0] to just get the base url and port + try: + baseURL = patcherVer()[0].split('/lo')[0] + except IndexError: + print 'Base URL not available for leak submit' + return + basePort = 80 + if baseURL.count(':') == 2: + basePort = baseURL[-4:] + baseURL = baseURL[:-5] + baseURL = baseURL[7:] + + if basePort != 80: + finalURL = 'http://' + baseURL + ':' + str(basePort) + '/logging/memory_leak.php?leakcount=' + str(printUnreachableNum()) + else: + finalURL = 'http://' + baseURL + '/logging/memory_leak.php?leakcount=' + str(printUnreachableNum()) + reporthttp = HTTPClient() + reporthttp.postForm(URLSpec(finalURL), reportdata) + + return + +def checkParamFile(): +# checkParamFile will check to see if the parameters.txt file exists +# If it does, then the file is read in. If the 'PATCHER_BASE_URL' is found +# then the value after the =, is returned. If the file does not exist or the +# file does not contain the PATCHER_BASE_URL, then None is returned. + if os.path.exists('parameters.txt') == 1: + paramfile = open('parameters.txt', 'r') + contents = paramfile.read() + paramfile.close() + del paramfile + contents = contents.split('\n') + newURL = '' + while contents: + checkLine = contents.pop() + if checkLine.find('PATCHER_BASE_URL=') != -1 and checkLine[0] == 'P': + newURL = checkLine.split('=')[1] + # Just in case a space exists after the =, we should remove whitespace from the element + newURL = newURL.replace(' ','') + break + if newURL == '': + #print 'Parameters.txt did not contain a new PATCHER_BASE_URL' + return + else: + #print 'Parameters.txt does have a new base url' + return newURL + 'patcher.ver' + # parameters.txt file not found + return diff --git a/toontown/src/toonbase/__init__.py b/toontown/src/toonbase/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/toonbase/french/TTLocalizer.py b/toontown/src/toonbase/french/TTLocalizer.py new file mode 100644 index 0000000..beeace1 --- /dev/null +++ b/toontown/src/toonbase/french/TTLocalizer.py @@ -0,0 +1,9394 @@ +import string +import time +from toontown.toonbase.french.TTLocalizer_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "Mickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Daisy" +Goofy = "Dingo" +Pluto = "Pluto" +Flippy = "Flippy" +Chip = "Tic" +Dale = "Tac" + +# common locations +lTheBrrrgh = 'Le Glagla' +lDaisyGardens = 'Le Jardin de Daisy' +lDonaldsDock = "Quais Donald" +lDonaldsDreamland = "Le Pays des Rêves de Donald" +lMinniesMelodyland = "Le Pays Musical de Minnie" +lToontownCentral = 'Toontown Centre' +lToonHQ = 'QG des Toons' +lOutdoorZone = "Forêt de glands de Tic et Tac" +lGoofySpeedway = "Circuit Dingo" +lGolfZone = "Minigolf de Tic et Tac" + +lGagShop = 'Gag Shop' +lClothingShop = 'En tant que magasin de marchandises sèches' +lPetShop = 'Pet Shop' + +# common strings +lBack = 'Retour' +lCancel = 'Annuler' +lClose = 'Fermer' +lOK = 'OK' +lNext = 'Suivant' +lQuit = 'Quitter' +lYes = 'Oui' +lNo = 'Non' +lHQ = '' + +lHQOfficerF = 'Officier QG' +lHQOfficerM = 'Officier QG' + +MickeyMouse = "Mickey" + +AIStartDefaultDistrict = "Idioville" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "un Cog" +TheCogs = "les Cogs" +ASkeleton = "un Skelecog" +Skeleton = "Skelecog" +SkeletonP = "Skelecogs" +Av2Cog = "a Version 2.0 Cog" +v2Cog = "Version 2.0 Cog" +v2CogP = "Version 2.0 Cogs" +Foreman = "Contremaître de l'usine" +ForemanP = "Contremaîtres de l'usine" +AForeman = "un contremaître de l'usine" +CogVP = "Vice-\nPrésident " + Cog +CogVPs = "Vice-\nPrésidents Cogs" +ACogVP = "Un Vice-\nPrésident " + Cog +Supervisor = "Superviseur de la Fabrique à Sous" +SupervisorP = "Superviseurs de la Fabrique à Sous" +ASupervisor = "un Superviseur de la Fabrique à Sous" +CogCFO = Cog + " Vice-\nPrésident" +CogCFOs = "Vice-\nPrésidents Cog" +ACogCFO = ACog + " Vice-\nPrésident" + +# AvatarDNA.py +Bossbot = "Chefbot" +Lawbot = "Loibot" +Cashbot = "Caissbot" +Sellbot = "Vendibot" +BossbotS = "un Chefbot" +LawbotS = "un Loibot" +CashbotS = "un Caissbot" +SellbotS = "un Vendibot" +BossbotP = "des Chefbots" +LawbotP = "des Loibots" +CashbotP = "des Caissbots" +SellbotP = "des Vendibots" +BossbotSkelS = "un Chefbot Skelecog" +LawbotSkelS = "un Loibot Skelecog" +CashbotSkelS = "un Caissbot Skelecog" +SellbotSkelS = "un Vendibot Skelecog" +BossbotSkelP = "des Chefbots Skelecogs" +LawbotSkelP = "des Loibots Skelecogs" +CashbotSkelP = "des Caissbots Skelecogs" +SellbotSkelP = "des Vendibots Skelecogs" + +#lToonHQ = 'トゥーン'+lHQ #check +lBossbotHQ = Bossbot+lHQ +lLawbotHQ = Lawbot+lHQ +lCashbotHQ = Cashbot+lHQ +lSellbotHQ = Sellbot+lHQ + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("vers la", "sur la", "terrasse du Tourbillon"), # Tutorial + 1000 : ("vers le", "sur le", "Terrain de jeux"), + 1100 : ("vers le", "sur le", "Boulevard de la Bernache"), + 1200 : ("vers la", "sur la", "Rue des Récifs"), + 1300 : ("vers l'", "sur l'", "Allée des Marées"), + 2000 : ("vers le", "sur le", "Terrain de jeux"), + 2100 : ("vers la", "sur la", "Rue Béta"), + 2200 : ("vers l'", "sur l'", "Avenue des Fondus"), + 2300 : ("vers la", "sur la", "Place des Blagues"), + 3000 : ("vers le", "sur le", "Terrain de jeux"), + 3100 : ("vers le", "sur le", "Chemin du Marin"), + 3200 : ("vers la", "sur la", "Rue de la Neige fondue"), + 3300 : ("vers la", "sur la", "Place Polaire"), + 4000 : ("vers le", "sur le", "Terrain de jeux"), + 4100 : ("vers l'", "sur l'", "Avenue du Contralto"), + 4200 : ("vers le", "sur le", "Boulevard du Baryton"), + 4300 : ("vers la", "sur la", "Terrasse des Ténors"), + 5000 : ("vers le", "sur le", "Terrain de jeux"), + 5100 : ("vers la", "sur la", "Rue des Ormes"), + 5200 : ("vers la", "sur la", "Rue des Érables"), + 5300 : ("vers la", "sur la", "Rue du Chêne"), + 9000 : ("vers le", "sur le", "Terrain de jeux"), + 9100 : ("vers le", "sur le", "Boulevard de la Berceuse"), + 9200 : ("", "", "Place de la Couette"), + 10000 : ("vers le", "au", "QG Chefbot"), + 10100 : ("vers le", "dans le", "hall du QG des Chefbots"), + 10200 : ("à", "dans", "Le Clubhouse"), + 10500 : ("à", "dans", "Les trois premiers à l'avant"), + 10600 : ("à", "dans", "Les six du milieu"), + 10700 : ("à", "dans", "Les neuf du fond"), + 11000 : ("vers la", "sur la", "cour du QG Vendibot"), + 11100 : ("vers le", "dans le", "hall du QG Vendibot"), + 11200 : ("vers l'", "à l'", "usine Vendibot"), + 11500 : ("vers l'", "à l'", "usine Vendibot"), + 12000 : ("vers le", "au", "QG Caissbot"), + 12100 : ("vers le", "dans le", "hall du QG Caissbot"), + 12500 : ("", "", "Fabrique à Sous Caissbot"), + 12600 : ("", "", "Fabrique à Euros Caissbot"), + 12700 : ("", "", "Fabrique à Lingots Caissbot"), + 13000 : ("vers le", "au", "QG Loibot"), + 13100 : ("vers le", "dans le", "hall du QG Loibot"), + 13200 : ("vers le", "au", "hall du bureau du Procureur"), + 13300 : ("vers le", "au", "bureau Loibot A"), + 13400 : ("vers le", "au", "bureau Loibot B"), + 13500 : ("vers le", "au", "bureau Loibot C"), + 13600 : ("vers le", "au", "bureau Loibot D"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("vers les", "sur les", "Quais Donald") +ToontownCentral = ("vers", "à", "Toontown centre") +TheBrrrgh = ("vers", "dans", "le Glagla") +MinniesMelodyland = ("vers le", "au", "Pays musical de Minnie") +DaisyGardens = ("vers les", "au", "Jardins de Daisy") +ConstructionZone = ("vers la", "dans la", "Zone de construction") +OutdoorZone = ("", "", lOutdoorZone) +FunnyFarm = ("vers la", "dans la", "Ferme farfelue") +GoofySpeedway = ("vers le", "au", "Circuit Dingo") +DonaldsDreamland = ("vers le", "au", "Pays des rêves de Donald") +BossbotHQ = ("vers le", "dans le", "QG des Chefbots") +SellbotHQ = ("vers le", "dans le", "QG Vendibot") +CashbotHQ = ("vers le", "dans le", "QG Caissbot") +LawbotHQ = ("vers le", "dans le", "QG Loibot") +Tutorial = ("vers les", "aux", "Travaux pratiques") +MyEstate = ("vers", "dans", "ta maison") +WelcomeValley = ("vers la", "dans la", "Bienvenue") +GolfZone = ("à", "dans", lGolfZone) + +Factory = 'Usine' +Headquarters = 'Quartiers généraux' +SellbotFrontEntrance = 'Entrée principale' +SellbotSideEntrance = 'Entrée latérale' +Office = 'Officier' + +FactoryNames = { + 0 : "Maquette d'usine", + 11500 : 'Usine des Cogs Vendibots', + 13300 : 'Bureau des Cogs Loibot', #remove me JML + } + +FactoryTypeLeg = 'Jambe' +FactoryTypeArm = 'Bras' +FactoryTypeTorso = 'Torse' + +MintFloorTitle = 'Étage %s' + +# Quests.py +TheFish = "les poissons" +AFish = "un poisson" +Level = "niveau" +QuestsCompleteString = "Terminé" +QuestsNotChosenString = "Non choisi" +Period = "." + +Laff = "Rigolpoints" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Bonjour, _avName_!", + "Ohé, _avName_!", + "Coucou, _avName_!", + "Eh, _avName_!", + "Bienvenue, _avName_!", + "Salut, _avName_!", + "Comment ça va, _avName_?", + "Quoi de neuf, _avName_?", + ) +QuestsDefaultIncomplete = ("Comment est-ce que ce défi se présente, _avName_?", + "On dirait que tu as encore du travail à faire pour ce défi!", + "Continue à bien travailler, _avName_!", + "Essaie de finir ce défi. Je sais que tu peux le faire!", + "Essaie de terminer ce défi, nous comptons sur toi!", + "Continue à travailler sur ce défitoon!", + ) +QuestsDefaultIncompleteProgress = ("Tu es au bon endroit, mais tu dois d'abord finir ton défitoon.", + "Quand tu auras terminé ton défitoon, reviens ici.", + "Reviens quand tu auras terminé ton défitoon.", + ) +QuestsDefaultIncompleteWrongNPC = ("Joli travail pour ce défitoon. Tu devrais aller voir _toNpcName_._where_", + "On dirait que tu as presque fini ton défitoon. Va voir _toNpcName_._where_.", + "Va voir _toNpcName_ pour finir ton défitoon._where_", + ) +QuestsDefaultComplete = ("Bon travail! Voilà ta récompense...", + "Super boulot, _avName_! Prends cette récompense...", + "Excellent boulot, _avName_! Voilà ta récompense...", + ) +QuestsDefaultLeaving = ("Salut!", + "Au revoir!", + "Bon vent, _avName_!", + "À plus, _avName_!", + "Bonne chance!", + "Amuse-toi bien à Toontown!", + "À plus tard!", + ) +QuestsDefaultReject = ("Bonjour.", + "Puis-je t'aider ?", + "Comment ça va?", + "Bien le bonjour.", + "Je suis un peu occupé là, _avName_.", + "Oui?", + "Salut, _avName_!", + "Bienvenue, _avName_!", + "Hé, _avName_! Comment ça va?", + # Game Hints + "Sais-tu que tu peux ouvrir ton journal de bord en appuyant sur la touche F8?", + "Tu peux utiliser ta carte pour te téléporter jusqu'au terrain de jeux!", + "Tu peux devenir ami(e) avec les autres joueurs en cliquant sur eux.", + "Tu peux en savoir plus sur un " + Cog + " en cliquant sur lui.", + "Trouve des trésors sur les terrains de jeux pour remplir ton rigolmètre.", + "Les immeubles " + Cog + " sont dangereux! N'y va pas tout seul!", + "Lorsque tu perds un combat, les " + Cogs + " prennent tous tes gags.", + "Pour avoir plus de gags, joue aux jeux du tramway!", + "Tu peux accumuler des rigolpoints en effectuant des défitoons.", + "Chaque défitoon te vaudra une récompense.", + "Certaines récompenses te permettent d'avoir plus de gags.", + "Si tu gagnes un combat, ton défitoon est crédité pour chaque " + Cog + " vaincu.", + "Si tu regagnes un bâtiment " + Cog + ", retourne à l'intérieur pour recevoir un remerciement spécial de la part de son propriétaire!", + "Si tu appuies sur la touche \"page précédente\", tu peux regarder vers le haut!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Pour montrer à tes amis ce que tu penses, entre un '.' avant ta pensée.", + "Si un " + Cog + " est assommé, il lui est plus difficile d'éviter les objets qui tombent.", + "Chaque type de bâtiment " + Cog + " a un aspect différent.", + "Tu obtiens plus de récompenses d'habileté si tu vaincs des " + Cogs + " aux plus hauts étages des bâtiments." + ) +QuestsDefaultTierNotDone = ("Bonjour, _avName_! Tu dois terminer tes défitoons commencés avant d'en obtenir un autre.", + "Salut! Tu dois terminer les défitoons sur lesquels tu es en train de travailler pour en obtenir un nouveau.", + "Ohé, _avName_! Pour que je puisse te donner un autre défitoon, tu dois finir ceux que tu as déjà.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("J'ai entendu dire que _toNpcName_ te cherchait._where_", + "Arrête-toi voir _toNpcName_ quand tu pourras._where_", + "Va donc voir _toNpcName_ la prochaine fois que tu passes par là-bas._where_", + "Si tu peux, arrête-toi dire bonjour à _toNpcName_._where_", + "_toNpcName_ va te donner ton prochain défitoon._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogQuestHeadline = "RECHERCHE" +QuestsCogQuestSCStringS = "Je dois vaincre %(cogName)s%(cogLoc)s" +QuestsCogQuestSCStringP = "Je dois vaincre quelques %(cogName)s%(cogLoc)s" +QuestsCogQuestDefeat = "Tu dois vaincre %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Aide un nouveau Toon à vaincre %s" +QuestsCogNewNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins" +QuestsCogOldNewbieQuestObjective = "Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s" +QuestsCogOldNewbieQuestCaption = "Aide un Toon avec %d rigolpoints ou moins" +QuestsCogNewbieQuestAux = "Tu dois\nvaincre:" +QuestsNewbieQuestHeadline = "APPRENTI" + +QuestsCogTrackQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogTrackQuestHeadline = "RECHERCHE" +QuestsCogTrackQuestSCStringS = "Je dois vaincre %(cogText)s%(cogLoc)s" +QuestsCogTrackQuestSCStringP = "Je dois vaincre quelques %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Tu dois vaincre %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s sur %(numCogs)s sont vaincus" +QuestsCogLevelQuestHeadline = "RECHERCHE" +QuestsCogLevelQuestDefeat = "Tu dois vaincre %s" +QuestsCogLevelQuestDesc = "un Cog de niveau %(level)s+" +QuestsCogLevelQuestDescC = "%(count)s Cogs de niveau %(level)s+" +QuestsCogLevelQuestDescI = "des Cogs de niveau %(level)s+" +QuestsCogLevelQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('', 'deux+', 'trois+', 'quatre+', 'cinq+') +QuestsBuildingQuestBuilding = "Bâtiment" +QuestsBuildingQuestBuildings = "Bâtiments" +QuestsBuildingQuestHeadline = "VAINCRE" +QuestsBuildingQuestProgressString = "%(progress)s sur %(num)s sont vaincus" +QuestsBuildingQuestString = "Tu dois vaincre %s" +QuestsBuildingQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "un bâtiment %(type)s" +QuestsBuildingQuestDescF = "un bâtiment %(type)s de %(floors)s étages" +QuestsBuildingQuestDescC = "%(count)s bâtiments %(type)s " +QuestsBuildingQuestDescCF = "%(count)s bâtiments %(type)s de %(floors)s étages" +QuestsBuildingQuestDescI = "des bâtiments %(type)s" +QuestsBuildingQuestDescIF = "des bâtiments %(type)s de %(floors)s étages" + +QuestFactoryQuestFactory = "Usine" +QuestsFactoryQuestFactories = "Usines" +QuestsFactoryQuestHeadline = "VAINCRE" +QuestsFactoryQuestProgressString = "%(progress)s sur%(num)s sont vaincus" +QuestsFactoryQuestString = "Tu dois vaincre %s" +QuestsFactoryQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "une usine %(type)s" +QuestsFactoryQuestDescC = "%(count)s usines %(type)s" +QuestsFactoryQuestDescI = "des usines %(type)s" + +QuestMintQuestMint = "Fabrique à Sous" +QuestsMintQuestMints = "Fabriques à Sous" +QuestsMintQuestHeadline = "VAINCRE" +QuestsMintQuestProgressString = "%(progress)s de %(num)s vaincus" +QuestsMintQuestString = "Vaincre %s" +QuestsMintQuestSCString = "Je dois vaincre %(objective)s%(location)s." + +QuestsMintQuestDesc = "une Fabrique à Sous Cog" +QuestsMintQuestDescC = "%(count)s Fabriques à Sous Cog" +QuestsMintQuestDescI = "des Fabriques à Sous Cog" + +QuestsRescueQuestProgress = "%(progress)s sur %(numToons)s sont sauvés" +QuestsRescueQuestHeadline = "SAUVER" +QuestsRescueQuestSCStringS = "Je dois sauver un Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "Je dois sauver des Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Tu dois sauver %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "un Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Tu dois sauver:" + +QuestsRescueNewNewbieQuestObjective = "Aide un nouveau Toon à sauver %s" +QuestsRescueOldNewbieQuestObjective = "Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s" + +QuestCogPartQuestCogPart = "Pièce de costume de Cog" +QuestsCogPartQuestFactories = "Usines" +QuestsCogPartQuestHeadline = "RÉCUPÉRER" +QuestsCogPartQuestProgressString = "%(progress)s sur %(num)s sont récupérés" +QuestsCogPartQuestString = "Récupérer %s" +QuestsCogPartQuestSCString = "Je dois récupérer %(objective)s%(location)s." +QuestsCogPartQuestAux = "Tu dois récupérer:" + +QuestsCogPartQuestDesc = "une pièce de costume de Cog" +QuestsCogPartQuestDescC = "%(count)s pièces de costume de Cog" +QuestsCogPartQuestDescI = "des pièces de costume de Cog" + +QuestsCogPartNewNewbieQuestObjective = 'Aide un nouveau Toon à récupérer %s' +QuestsCogPartOldNewbieQuestObjective = 'Aide un Toon avec %(laffPoints)d rigolpoints ou moins à vaincre %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s sur %(numGags)s sont livrés" +QuestsDeliverGagQuestHeadline = "LIVRER" +QuestsDeliverGagQuestToSCStringS = "Je dois livrer %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Je dois livrer des %(gagName)s." +QuestsDeliverGagQuestSCString = "Je dois faire une livraison." +QuestsDeliverGagQuestString = "Tu dois livrer %s" +QuestsDeliverGagQuestStringLong = "Tu dois livrer %s à _toNpcName_." +QuestsDeliverGagQuestInstructions = "Tu pourras acheter ce gag à la Boutique à gags une fois que tu en auras gagné le droit." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "LIVRER" +QuestsDeliverItemQuestSCString = "Je dois livrer %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Tu dois livrer %s" +QuestsDeliverItemQuestStringLong = "Tu dois livrer %s à _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITER" +QuestsVisitQuestStringShort = "Tu dois rendre visite" +QuestsVisitQuestStringLong = "Rends visite à _toNpcName_" +QuestsVisitQuestSeeSCString = "Je dois voir %s." + +QuestsRecoverItemQuestProgress = "%(progress)s sur %(numItems)s sont repris" +QuestsRecoverItemQuestHeadline = "REPRENDRE" +QuestsRecoverItemQuestSeeHQSCString = "Je dois voir un officier du QG." +QuestsRecoverItemQuestReturnToHQSCString = "Je dois rendre %s à un officier du QG." +QuestsRecoverItemQuestReturnToSCString = "Je dois rendre %(item)s à %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Je dois aller à un QG des Toons." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Je dois aller au terrain de jeux de %s." +QuestsRecoverItemQuestGoToStreetSCString = "Je dois aller %(to)s %(street)s dans %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Je dois rendre visite à %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Où est %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Je dois reprendre %(item)s à %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Reprendre %(item)s à %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "CHOISIR" +QuestsTrackChoiceQuestSCString = "Je dois choisir entre %(trackA)s et %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Je devrais peut-être choisir %s." +QuestsTrackChoiceQuestString = "Choisis entre %(trackA)s et %(trackB)s." + +QuestsFriendQuestHeadline = "AMI" +QuestsFriendQuestSCString = "Je dois trouver un(e) ami(e)." +QuestsFriendQuestString = "Trouve un(e) ami(e)." + +QuestsMailboxQuestHeadline = "COURRIER" +QuestsMailboxQuestSCString = "Je dois vérifier mon courrier." +QuestsMailboxQuestString = "Vérifie ton courrier." + +QuestsPhoneQuestHeadline = "CLARABELLE" +QuestsPhoneQuestSCString = "Je dois appeler Clarabelle." +QuestsPhoneQuestString = "Appelle Clarabelle." + +QuestsFriendNewbieQuestString = " Trouve %d contacts de %d rigolpoints ou moins" +QuestsFriendNewbieQuestProgress = "%(progress)s sur %(numFriends)s sont trouvés." +QuestsFriendNewbieQuestObjective = "Deviens ami(e) avec %d nouveaux Toons." + +QuestsTrolleyQuestHeadline = "TRAMWAY" +QuestsTrolleyQuestSCString = "Je dois faire un tour de tramway." +QuestsTrolleyQuestString = "Fais un tour de tramway." +QuestsTrolleyQuestStringShort = "Prends le tramway." + +QuestsMinigameNewbieQuestString = "%d Mini jeux" +QuestsMinigameNewbieQuestProgress = "%(progress)s sur %(numMinigames)s ont été joués." +QuestsMinigameNewbieQuestObjective = "Jouer à %d mini jeux avec de nouveaux Toons" +QuestsMinigameNewbieQuestSCString = "Je dois jouer aux mini jeux avec de nouveaux Toons." +QuestsMinigameNewbieQuestCaption = "Aide un nouveau Toon qui a %d rigolpoints ou moins." +QuestsMinigameNewbieQuestAux = "Tu dois jouer:" + +QuestsMaxHpReward = "Ta rigo-limite a été augmentée de %s." +QuestsMaxHpRewardPoster = "Récompense: Rigol-augmentation de %s point(s)" + +QuestsMoneyRewardSingular = "Tu obtiens 1 bonbon." +QuestsMoneyRewardPlural = "Tu obtiens %s bonbons." +QuestsMoneyRewardPosterSingular = "Récompense: 1 bonbon" +QuestsMoneyRewardPosterPlural = "Récompense: %s bonbons" + +QuestsMaxMoneyRewardSingular = "Tu peux maintenant avoir 1 bonbon." +QuestsMaxMoneyRewardPlural = "Tu peux maintenant avoir %s bonbons." +QuestsMaxMoneyRewardPosterSingular = "Récompense: Tu as 1 bonbon." +QuestsMaxMoneyRewardPosterPlural = "Récompense: Tu as %s bonbons." + +QuestsMaxGagCarryReward = "Tu as un %(name)s. Tu peux maintenant avoir %(num)s gags." +QuestsMaxGagCarryRewardPoster = "Récompense: (%(num)s) %(name)s" + +QuestsMaxQuestCarryReward = " Tu peux maintenant avoir %s défitoons." +QuestsMaxQuestCarryRewardPoster = "Récompense: Tu as %s défitoons" + +QuestsTeleportReward = "Tu peux maintenant accéder par téléportation à %s." +QuestsTeleportRewardPoster = "Récompense: Accès par téléportation à %s" + +QuestsTrackTrainingReward = "Tu peux maintenant t'entraîner pour les gags \"%s\"." +QuestsTrackTrainingRewardPoster = "Récompense: Entraînement aux gags" + +QuestsTrackProgressReward = "Tu as maintenant l'image %(frameNum)s de l'animation de la série %(trackName)s." +QuestsTrackProgressRewardPoster = "Récompense: image %(frameNum)s de l'animation de la série \"%(trackName)s\"" + +QuestsTrackCompleteReward = "Tu peux maintenant avoir et utiliser des gags \"%s\"." +QuestsTrackCompleteRewardPoster = "Récompense: Entraînement final aux séries %s" + +QuestsClothingTicketReward = "Tu peux changer de vêtements." +QuestsClothingTicketRewardPoster = "Récompense: Ticket d'habillement" + +QuestsCheesyEffectRewardPoster = "Récompense: %s" + +QuestsCogSuitPartReward = "Tu as maintenant une %(cogTrack)s %(part)s pièce de costume de Cog." +QuestsCogSuitPartRewardPoster = "Récompense: %(cogTrack)s %(part)s pièce" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "sur ce terrain de jeux" +QuestsStreetLocationThisStreet = "sur cette rue" +QuestsStreetLocationNamedPlayground = "sur le terrain de jeux de %s" +QuestsStreetLocationNamedStreet = "sur %(toStreetName)s dans %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "Le bâtiment de %s est appelé" +QuestsLocationBuildingVerb = "qui est" +QuestsLocationParagraph = "\a %(building)s \"%(buildingName)s \"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Je dois terminer un défitoon." + +# MaxGagCarryReward names +QuestsMediumPouch = "Bourse moyenne" +QuestsLargePouch = "Grande bourse" +QuestsSmallBag = "Petit sac" +QuestsMediumBag = "Sac moyen" +QuestsLargeBag = "Grand sac" +QuestsSmallBackpack = "Petit sac à dos" +QuestsMediumBackpack = "Sac à dos moyen" +QuestsLargeBackpack = "Grand sac à dos" +QuestsItemDict = { + 1 : ["Paire de lunettes", "Paires de lunettes", "une"], + 2 : ["Clé", "Clés", "une"], + 3 : ["Tableau", "Tableaux", "un"], + 4 : ["Livre", "Livres", "un"], + 5 : ["Sucre d'orge", "Sucres d'orge", "un"], + 6 : ["Craie", "Craies", "une"], + 7 : ["Recette", "Recettes", "une"], + 8 : ["Note", "Notes", "une"], + 9 : ["Machine à calculer", "Machines à calculer", "une"], + 10 : ["Pneu de voiture de clown", "Pneus de voiture de clown", "un"], + 11 : ["Pompe à air", "Pompes à air", "une"], + 12 : ["Encre de seiche", "Encres de seiche", "de l'"], + 13 : ["Paquet", "Paquets", "un "], + 14 : ["Reçu de poisson doré", "Reçus de poissons dorés", "un "], + 15 : ["Poisson doré", "Poissons dorés", "un "], + 16 : ["Huile", "Huiles", "de l'"], + 17 : ["Graisse", "Graisses", "de la "], + 18 : ["Eau", "Eaux", "de l'"], + 19 : ["Rapport de pignons", "Rapports de pignons", "un "], + 20 : ["Brosse à Tableaux", "Brosses à Tableaux", "une "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Ticket d'habillement", "Tickets d'habillement", "un "], + + # Donald's Dock quest items + 2001 : ["Chambre à air", "Chambres à air", "une "], + 2002 : ["Ordonnance de monocle", "Ordonnances de monocles", "une "], + 2003 : ["Monture de monocle", "Montures de monocles", "une "], + 2004 : ["Monocle", "Monocles", "un "], + 2005 : ["Grande perruque blanche", "Grandes perruques blanches", "une "], + 2006 : ["Boisseau de lest", "Boisseaux de lest", "un "], + 2007 : ["Équipement de Cog", "Équipements de Cog", "un "], + 2008 : ["Carte marine", "Cartes marines", "une "], + 2009 : ["Manille crado", "Manilles crados", "un "], + 2010 : ["Manille propre", "Manilles propres", "un "], + 2011 : ["Ressort d'horloge", "Ressorts d'horloge", "un "], + 2012 : ["Contrepoids", "Contrepoids", "un "], + + # Minnie's Melodyland quest items + 4001 : ["Inventaire de Tina", "Inventaires de Tina", ""], + 4002 : ["Inventaire de Yuki", "Inventaires de Yuki", ""], + 4003 : ["Formulaire d'inventaire", "Formulaires d'inventaire", "un "], + 4004 : ["Inventaire de Fifi", "Inventaires de Fifi", ""], + 4005 : ["Ticket de Jack Bûcheron", "Tickets de Jack Bûcheron", ""], + 4006 : ["Ticket de Tabatha", "Tickets de Tabatha", ""], + 4007 : ["Ticket de Barry", "Tickets de Barry", ""], + 4008 : ["Castagnette ternie", "Castagnettes ternies", ""], + 4009 : ["Encre de seiche bleue", "Encre de seiche bleue", "de l'"], + 4010 : ["Castagnette brillante", "Castagnettes brillantes", "une "], + 4011 : ["Paroles de Léo", "Paroles de Léo", ""], + + # Daisy's Gardens quest items + 5001 : ["Cravate en soie", "Cravates en soie", "une "], + 5002 : ["Costume à rayures", "Costumes à rayures", "un "], + 5003 : ["Paire de ciseaux", "Paires de ciseaux", "une "], + 5004 : ["Carte postale", "Cartes postales", "une "], + 5005 : ["Crayon", "Crayons", "un "], + 5006 : ["Encrier", "Encriers", "un "], + 5007 : ["Bloc-notes", "Blocs-notes", "un "], + 5008 : ["Coffre de bureau", "Coffres de bureau", "un "], + 5009 : ["Sac de graines pour oiseaux", "Sacs de graines pour oiseaux", "un "], + 5010 : ["Pignon", "Pignons", "un "], + 5011 : ["Salade", "Salades", "une "], + 5012 : ["Clé du jardin de Daisy", "Clés du jardin de Daisy", "une "], + 5013 : ["Plans du QG Vendibot", "Plans du QG Vendibot", "des "], + 5014 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5015 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5016 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + 5017 : ["Note de service du QG Vendibot", "Notes de service du QG Vendibot", "une "], + + # The Brrrgh quests + 3001 : ["Ballon de foot", "Ballons de foot", "un "], + 3002 : ["Luge", "Luges", "une "], + 3003 : ["Glaçon", "Glaçons", "un "], + 3004 : ["Lettre d'amour", "Lettres d'amour", "une "], + 3005 : ["Teckel", "Teckels", "un "], + 3006 : ["Bague de fiançailles", "Bagues de fiançailles", "une "], + 3007 : ["Moustaches de sardine", "Moustaches de sardines", "des "], + 3008 : ["Potion calmante", "Potions calmantes", "une "], + 3009 : ["Dent cassée", "Dents cassées", "une "], + 3010 : ["Dent en or", "Dents en or", "une "], + 3011 : ["Pain aux pommes de pin", "Pains aux pommes de pin", "un "], + 3012 : ["Fromage grumeleux", "Fromages grumeleux", "du "], + 3013 : ["Cuillère ordinaire", "Cuillères ordinaires", "une "], + 3014 : ["Crapaud parlant", "Crapauds parlants", "un "], + 3015 : ["Cône de glace", "Cônes de glace", "un "], + 3016 : ["Poudre à perruque", "Poudres à perruques", "de la "], + 3017 : ["Canard en plastique", "Canards en plastique", "un "], + 3018 : ["Dés en peluche", "Dés en peluche", "des "], + 3019 : ["Micro", "Micros", "un "], + 3020 : ["Clavier électrique", "Claviers électriques", "un "], + 3021 : ["Chaussures à plate-forme", "Chaussures à plate-forme", "des "], + 3022 : ["Caviar", "Caviar", "du "], + 3023 : ["Poudre de maquillage", "Poudres de maquillage", "de la "], + 3024 : ["Fil", "Fil", "du " ], + 3025 : ["Aiguille à tricoter", "Aiguilles à tricoter", "une "], + 3026 : ["Alibi", "Alibis", "un "], + 3027 : ["Thermomètre extérieur", "Thermomètres extérieurs", "un "], + + #Dreamland Quests + 6001 : ["Plans du QG Caissbot ", "Plans du QG Caissbot ", "des "], + 6002 : ["Tige", "Tiges", "une "], + 6003 : ["Courroie", "Courroies", "une "], + 6004 : ["Tenaille", "Tenailles", "une "], + 6005 : ["Lampe de lecture", "Lampes de lecture", "une "], + 6006 : ["Cithare", "Cithares", "une "], + 6007 : ["Surfaceuse", "Surfaceuses", "une "], + 6008 : ["Coussin zèbre", "Coussins zèbre", "un "], + 6009 : ["Zinnia", "Zinnias", "quelques "], + 6010 : ["Disques de Zydeco", "Disques de Zydeco", "des "], + 6011 : ["Courgette", "Courgettes", "une "], + 6012 : ["Costume de zazou", "Costumes de zazou", "un "], + + #Dreamland+1 quests + 7001 : ["Lit ordinaire", "Lits ordinaires", "un "], + 7002 : ["Lit fantaisie", "Lits fantaisie", "un "], + 7003 : ["Dessus-de-lit bleu", "Dessus-de-lit bleus", "un "], + 7004 : ["Dessus-de-lit motif cachemire", "Dessus-de-lit motif cachemire", "un "], + 7005 : ["Oreillers", "Oreillers", "des "], + 7006 : ["Oreillers durs", "Oreillers durs", "des "], + 7007 : ["Pyjama", "Pyjamas", "un "], + 7008 : ["Grenouillère", "Grenouillères", "une "], + 7009 : ["Grenouillère puce", "Grenouillères puce", "une "], + 7010 : ["Grenouillère fuchsia", "Grenouillères fuchsia", "une "], + 7011 : ["Corail chou-fleur", "Coraux chou-fleur", "du "], + 7012 : ["Algue gluante", "Algues gluantes", "de l'"], + 7013 : ["Pilon", "Pilons", "un "], + 7014 : ["Pot de crème antirides", "Pots de crème antirides", "un "], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "dans n'importe quel quartier" + +QuestsTailorFillin = "Tailleur" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Boutique de prêt-à-porter" +QuestsTailorLocationNameFillin = "dans n'importe quel quartier" +QuestsTailorQuestSCString = "J'ai besoin de voir un tailleur." + +QuestMovieQuestChoiceCancel = "Reviens plus tard si tu as besoin d'un défitoon! Salut!" +QuestMovieTrackChoiceCancel = "Reviens quand tu es prêt à te décider!! Salut!" +QuestMovieQuestChoice = "Choisis un défitoon." +QuestMovieTrackChoice = "Prêt à te décider ? Choisis une série, ou reviens plus tard." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Maintenant tu es prêt(e).\aSors et fais un tour avant de décider quelle série tu voudras choisir.\aChoisis bien, parce que c'est ta dernière série.\aQuand tu auras décidé, reviens me voir.", + INCOMPLETE_PROGRESS : "Choisis bien.", + INCOMPLETE_WRONG_NPC : "Choisis bien.", + COMPLETE : "Choix très sage!", + LEAVING : "Bonne chance. Reviens me voir quand tu as maîtrisé ta nouvelle habileté.", + } + +QuestDialog_3225 = { + QUEST : "Oh, merci pour ta visite, _avName_!\aLes Cogs du quartier ont effrayé mon livreur.\aJe n'ai personne pour livrer cette salade à _toNpcName_!\aPeux-tu le faire pour moi? Merci beaucoup!_where_" + } + +QuestDialog_2910 = { + QUEST : "Déjà de retour ?\aSuper travail avec le ressort.\aLe dernier article est un contrepoids.\aVa donc voir _toNpcName_ et ramène tout ce que tu peux._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Chefbots.", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Chefbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Loibots.", + INCOMPLETE_PROGRESS : "Les "+ Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Loibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Caissbots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Caissbots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "OK, maintenant je crois que nous sommes prêts pour quelque chose de plus compliqué.\aTu dois vaincre 3 Vendibots.", + INCOMPLETE_PROGRESS : "Les " + Cogs + " sont dans les rues, dans les tunnels.", + INCOMPLETE_WRONG_NPC : "Bien, tu as battu ces Vendibots! Maintenant, va au quartier général des Toons pour recevoir ta récompense!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Il semble que tu as besoin de nouveaux gags.\aVa voir Flippy, peut-être pourra-t-il t'aider._where_" }, + 165 : {QUEST : "Salut!\aOn dirait que tu as besoin de t'entraîner à utiliser tes gags.\aChaque fois que tu atteins un Cog avec l'un de tes gags, ton expérience augmente.\aQuand tu auras assez d'expérience, tu pourras utiliser un gag encore meilleur.\aVa t'entraîner à utiliser tes gags en battant 4 Cogs."}, + 166 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Chefbots."}, + 167 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Loibots."}, + 168 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Vendibots."}, + 169 : {QUEST : "Bien joué pour avoir battu ces Cogs.\aTu sais, les Cogs sont de quatre sortes différentes.\aIl y a les Loibots, les Caissbots, les Vendibots et les Chefbots.\aTu peux les distinguer par leurs couleurs et leurs étiquettes.\aPour t'entraîner va battre 4 Caissbots."}, + 170 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 171 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - il peut te donner des conseils avisés._where_" }, + 172 : {QUEST : "Bon travail, maintenant tu connais la différence entre les 4 sortes de Cogs.\aJe crois que tu peux commencer à t'entraîner pour ta troisième série de gags.\aVa parler à_toNpcName_ pour choisir ta prochaine série de gags - elle peut te donner des conseils avisés._where_" }, + + 175 : {GREETING : "", + QUEST : "Est-ce que tu savais que tu as une maison Toon à toi?\aClarabelle la vache s'occupe d'un catalogue par téléphone ou tu peux commander des meubles pour décorer ta maison.\aTu peux aussi y acheter des mots de Chat rapide, des vêtements et d'autres choses amusantes!\aJe vais dire à Clarabelle de t'envoyer ton premier catalogue maintenant.\aTu recevras un catalogue avec les nouveaux articles chaque semaine!\aRentre a la maison et appelle Clarabelle avec ton téléphone.", + INCOMPLETE_PROGRESS : "Rentre à la maison et appelle Clarabelle avec ton téléphone.", + COMPLETE : "J'espère que tu t'amuses en commandant des choses chez Clarabelle!\aJe viens tout juste de redécorer ma maison. Elle est toontastique!\aContinue à relever les défitoons pour avoir plus de récompenses!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Le lancer et l'éclaboussure sont super, mais tu auras besoin de plus de gags pour battre les Cogs de plus haut niveau.\aLorsque tu fais équipe avec d'autres Toons contre les Cogs, vous pouvez combiner vos attaques pour faire encore plus de dégâts.\aEssayez différentes combinaisons de gags pour voir ce qui marche le mieux.\aPour ta prochaine série, choisis entre tapage et toonique.\aTapage est particulier parce que lorsqu'il frappe, il endommage tous les Cogs.\aToonique te permet de soigner les autres Toons lors d'un combat.\aLorsque tu es prêt(e) à te décider, reviens ici faire ton choix.", + INCOMPLETE_PROGRESS : "Déjà de retour ? OK, quel est ton choix?", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Bonne décision. Maintenant tu dois t'entraîner avant de pouvoir utiliser ces gags.\aTu dois effectuer une série de défitoons pour t'entraîner.\aChaque défi te donnera une seule image de ton animation d'attaque avec les gags.\aLorsque tu auras les 15 images, tu pourras faire le dernier défi d'entraînement qui te permettra d'utiliser tes nouveaux gags.\aTu peux suivre tes progrès dans ton journal de bord.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1040 : { QUEST : "Va voir _toNpcName_ si tu veux parcourir la ville plus facilement._where_" }, + 1041 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1042 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1043 : { QUEST : "Salut! Qu'est-ce qui t'amène ?\aTout le monde utilise son trou portable pour voyager dans Toontown.\aTu peux te téléporter vers tes contacts en utilisant la liste d'contacts, ou vers n'importe quel quartier en utilisant la carte du journal de bord.\aBien entendu, tu dois d'abord gagner le droit de le faire!\aDisons que je peux activer ton accès à Toontown centre par téléportation si tu aides un de mes contacts.\aOn dirait que les Cogs font du désordre sur l'avenue des Fondus. Va voir _toNpcName_._where_" }, + 1044 : { QUEST : "Oh, merci de passer par ici. J'ai vraiment besoin d'aide.\aComme tu peux voir, je n'ai pas de clients.\aMon livre de recettes secret est perdu et personne ne vient plus dans mon restaurant.\aLa dernière fois que je l'ai vu, c'était avant que ces Cogs ne prennent mon bâtiment.\aEst-ce que tu peux m'aider à retrouver quatre de mes célèbres recettes?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as pu retrouver mes recettes?" }, + 1045 : { QUEST : "Merci beaucoup!\aD'ici peu, j'aurai retrouvé toutes mes recettes et je pourrai rouvrir mon restaurant.\aOh, j'ai une petite note ici pour toi - quelque chose à propos de l'accès par téléportation ?\aC'est écrit, merci d'avoir aidé mon ami et d'avoir livré ceci au quartier général des Toons. \aEh bien, merci vraiment - au revoir!", + LEAVING : "", + COMPLETE : "Ah oui, c'est écrit que tu as été d'une grande aide à de braves gens de l'avenue des Fondus.\aEt que tu as besoin d'un accès par téléportation à Toontown centre.\aBon, c'est comme si c'était fait.\aMaintenant tu peux revenir au terrain de jeux par téléportation depuis presque partout dans Toontown.\aOuvre simplement ta carte et clique sur Toontown centre." }, + 1046 : { QUEST : "Les Caissbots ont vraiment ennuyé la Caisse d'épargne Drôle d'argent.\aVa donc y faire un tour et vois si tu peux faire quelque chose._where_" }, + 1047 : { QUEST : "Les Caissbots se sont introduits dans la banque et ont volé nos machines.\aS'il te plaît, reprends 5 machines à calculer aux Caissbots.\aPour t'éviter de faire des allers et retours, rapporte-les toutes en une seule fois.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore des machines à calculer ?" }, + 1048 : { QUEST : "Oh là là! >Merci d'avoir trouvé nos machines à calculer.\aHmm... Elles ont l'air un peu abîmées.\aDis donc, pourrais-tu les amener à _toNpcName_ à son magasin, \"Machines à chatouilles\", dans cette rue ?\aVoir si elle peut les réparer.", + LEAVING : "", }, + 1049 : { QUEST : "Qu'est-ce que c'est ? Des machines à calculer cassées?\aDes Caissbots dis-tu?\aBon, regardons ça...\aMouais, les pignons sont cassés, mais je n'en vends pas...\aTu sais ce qui pourrait marcher - des pignons de Cog, des gros, de gros Cogs...\aDes pignons de Cog de niveau 3 devraient faire l'affaire. J'en aurai besoin de 2 pour chaque machine, donc 10 au total.\aRapporte-les moi tous ensemble et je ferai la réparation!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Souviens-toi, j'ai besoin de 10 pignons pour réparer les machines." }, + 1053 : { QUEST : "Ah oui, ça devrait bien faire l'affaire.\aTout est réparé maintenant, gratuitement.\aRapporte-les à Drôle d'argent, et dis-leur bonjour de ma part.", + LEAVING : "", + COMPLETE : "Toutes les machines à calculer sont réparées?\aJoli travail. Je crois bien que j'ai quelque chose par là pour te récompenser..." }, + 1054 : { QUEST : "_toNpcName_ a besoin d'aide pour ses voitures de clown._where_" }, + 1055 : { QUEST : "Bon sang! Je n'arrive pas à trouver les pneus de cette voiture de clown!\aTu crois que tu pourrais m'aider ?\aJe crois que Bob Fondu les a lancés dans la mare du terrain de jeux de Toontown centre.\aSi tu vas sur les pontons, de là tu peux essayer de repêcher les pneus.", + GREETING : "Youhouu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as du mal à repêcher les 4 pneus?" }, + 1056 : { QUEST : "Fanta-super-tastique! Maintenant je vais pouvoir remettre en marche cette vieille voiture de clown!\aHé, je croyais que j'avais une pompe par ici pour gonfler ces pneus...\aC'est peut-être _toNpcName_ qui l'a empruntée ?\aTu peux aller lui demander de me la rendre ?_where_", + LEAVING : "" }, + 1057 : { QUEST : "Salut!\aUne pompe à pneus tu dis?\aJe vais te dire - tu me nettoies les rues de quelques-uns de ces Cogs de haut niveau...\aEt je te donne la pompe à pneus.", + LEAVING : "", + INCOMPLETE_PROGRESS : "C'est tout ce que tu peux faire ?" }, + 1058 : { QUEST : "Bon travail - je savais que tu pouvais le faire.\aVoilà la pompe. Je suis certain que_toNpcName_ sera content de la récupérer.", + LEAVING : "", + GREETING : "", + COMPLETE : "Youpiii! Maintenant ça va marcher!\aEt dis donc, merci de m'avoir aidé.\aTiens, prends ça." }, + 1059 : { QUEST : "_toNpcName_ est à court de fournitures. Tu peux peut-être lui donner un coup de main ?_where_" }, + 1060 : { QUEST : "Merci d'être passé par ici!\aCes Cogs ont volé mon encre je n'en ai presque plus.\a Pourrais-tu me pêcher de l'encre de seiche dans la mare ?\aTu n'as qu'à rester sur un ponton près de la mare pour pêcher.", + LEAVING : "", + INCOMPLETE_PROGRESS : "{Tu as des problèmes pour pêcher ?" }, + 1061 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Gratte-papiers...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Gratte-papiers dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Gratte-papiers." }, + 1062 : { QUEST : "Super - merci pour l'encre!\aTu sais quoi, et si tu nous débarrassais de quelques Pique-au-sang...\aJe ne serais plus en panne d'encre aussi rapidement.\aTu dois vaincre 6 Pique-au-sang dans Toontown centre pour avoir ta récompense.", + LEAVING : "", + COMPLETE : "Merci! Laisse-moi te récompenser pour ton aide.", + INCOMPLETE_PROGRESS : "Je viens de voir encore d'autres Pique-au-sang." }, + 900 : { QUEST : "Je crois comprendre que_toNpcName_ a besoin d'aide avec un paquet._where_" }, + 1063 : { QUEST : "Salut, merci d'être là.\aUn Cog a volé un paquet très important juste sous mon nez.\aPeux-tu le récupérer ? Je crois que c'était un Cog de niveau 3...\aDonc, tu dois vaincre des Cogs de niveau 3 jusqu'à ce que tu retrouves mon paquet.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1067 : { QUEST : "C'est ça, très bien!\aOh, l'adresse est toute tachée...\aTout ce que j'arrive à lire, c'est Docteur... - le reste est brouillé.\aC'est peut-être pour_toNpcName_? Peux-tu lui porter ?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Je n'attendais pas de paquet. C'est peut-être pour le Dr E. Phorique ?\aMon assistant doit aller le voir aujourd'hui, je me charge de lui remettre.\aEn attendant, est-ce que tu voudrais bien débarrasser ma rue de quelques Cogs?\aTu dois vaincre 10 Cogs dans Toontown centre.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mon assistant n'est pas encore revenu." }, + 1069 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Caissbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1070 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Vendibot l'a volé à mon assistant alors qu'il revenait.\aJe suis désolé, mais il va falloir que tu retrouves ce Vendibot pour le récupérer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1071 : { QUEST : "Le Dr. E. Phorique dit qu'il n'attendait pas de paquet non plus.\aMalheureusement, un Chefbot l'a volé à mon assistant alors qu'il revenait.\aPourrais-tu essayer de le récupérer ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1072 : { QUEST : "Super - tu l'as retrouvé!\aTu devrais peut-être essayer _toNpcName_, cela pourrait être pour lui._where_", + LEAVING : "" }, + 1073 : { QUEST : "Oh, merci de m'avoir apporté mes paquets.\aJuste une seconde, j'en attendais deux. Pourrais-tu vérifier avec _toNpcName_ voir s'il a l'autre ?", + INCOMPLETE : "Est-ce que tu as trouvé mon autre paquet ?", + LEAVING : "" }, + 1074 : { QUEST : "Il a dit qu'il y avait un autre paquet ? Les Cogs l'ont peut-être aussi volé.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves le second paquet.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas retrouvé le paquet, hein ?" }, + 1075 : { QUEST : "Finalement je crois qu'il y avait un second paquet!\aVa vite le porter à _toNpcName_ avec mes excuses.", + COMPLETE : "Eh, mon paquet est là!\aPuisque tu es un Toon aussi serviable, cela devrait aider.", + LEAVING : "" }, + 1076 : { QUEST : "Il y a un problème au Ornithorynques 14 carats.\a_toNpcName_ serait sans doute content d'avoir de l'aide._where_" }, + 1077 : { QUEST : "Merci d'être venu - les Cogs ont volé tous mes poissons dorés.\aJe crois que les Cogs veulent les vendre pour se faire de l'argent facilement.\aCes 5 poissons ont été mes seuls compagnons dans cette petite boutique depuis tant d'années...\aSi tu pouvais me les retrouver, je t'en serais vraiment reconnaissant.\aJe suis certain qu'un des Cogs a mes poissons.\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mes poissons dorés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "S'il te plaît, ramène-moi mes poissons." }, + 1078 : { QUEST : "Oh, tu as mes poissons!\aEh? Qu'est-ce que c'est que ça?\aAah, ce sont bien les Cogs, après tout.\aJe ne comprends rien à ce reçu. Peux-tu l'emmener à _toNpcName_ voir s'il peut le lire ?_where_", + INCOMPLETE : "Qu'est-ce que _toNpcName_ a dit à propos du reçu?", + LEAVING : "" }, + 1079 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Laquaistic.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Laquaistic.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1092 : { QUEST : "Mmm, laisse-moi voir ce reçu.\a...Ah oui, il dit qu'un poisson doré a été vendu à un Gardoseille.\aÇa n'a pas l'air de dire ce qui est arrivé aux 4 autres poissons.\aTu devrais peut-être essayer de trouver ce Gardoseille.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne crois pas que je puisse t'aider à grand-chose d'autre.\aPourquoi n'essaies-tu pas de trouver ce poisson doré?" }, + 1080 : { QUEST : "Oh, Dieu merci! Tu as trouvé Oscar - c'est mon préféré.\aQu'est-ce que c'est, Oscar ? Hein, hein...ils ont quoi? ... Ils sont ?\aOscar dit que les 4 autres se sont échappés dans la mare du terrain de jeux.\aPeux-tu aller me les chercher ?\aTu n'as qu'à les pêcher dans la mare.", + LEAVING : "", + COMPLETE : "Ahh, je suis si content! Avoir retrouvé mes petits camarades!\aTu mérites une belle récompense pour cela!", + INCOMPLETE_PROGRESS : "Tu as des problèmes pour trouver ces poissons?" }, + 1081 : { QUEST : "_toNpcName_ a l'air d'être dans une situation difficile. Elle serait sûrement contente d'avoir de l'aide._where_" }, + 1082 : { QUEST : "J'ai renversé de la colle à séchage rapide, et je suis collée - complètement collée!\aSi seuleument je trouvais une façon de m'en sortir...\aCela me donne une idée, si tu veux bien.\aVa vaincre quelques Vendibots et ramène-moi de l'huile.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1083 : { QUEST : "Bon, l'huile a fait un peu d'effet, mais je ne peux toujours pas bouger.\aQu'est-ce qui pourrait bien m'aider ? C'est difficile à dire.\aCela me donne une idée on peut au moins essayer.\aVa vaincre quelques Loibots et ramène-moi de la graisse.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1084 : { QUEST : "Non, ça n'a rien fait. Ce n'est vraiment pas drôle.\aJ'ai pourtant mis de la graisse partout.\aÇa me donne une idée, avant que j'oublie.\aVa vaincre quelques Caissbots et rapporte de l'eau pour l'humecter.", + LEAVING : "", + GREETING : "", + COMPLETE : "Hourrah, je suis libérée de cette colle rapide.\aComme récompense, je te donne ce cadeau.\aTu peux rire un peu plus longtemps lorsque tu es en train de te battre, et puis...\aOh, non! Je suis de nouveau collée là!", + INCOMPLETE_PROGRESS : "Peux-tu m'aider à me décoller ?" }, + 1085 : { QUEST : "_toNpcName_ est en train de faire des recherches sur les Cogs.\aVa lui parler si tu veux l'aider._where_" }, + 1086 : { QUEST : "C'est cela, je fais une étude sur les Cogs.\aJe veux savoir ce qui les fait tiquer.\aCela m'aiderait certainement si tu pouvais me trouver des pignons de Cogs.\aAssure-toi qu'il s'agit de Cogs de niveau 2 au minimum, qu'ils soient assez gros pour être examinés.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ne peux pas trouver assez de pignons?" }, + 1089 : { QUEST : "OK, regardons un peu ça. Ce sont d'excellents spécimens!\aMmmm...\aOK, voilà mon rapport. Emmène-le tout de suite au quartier général des Toons.", + INCOMPLETE : "As-tu porté mon rapport au quartier général?", + COMPLETE : "Bon travail _avName_, on va s'occuper de ça.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ a des informations importantes pour toi._where_" }, + 1091 : { QUEST : "J'ai entendu dire que le quartier général des Toons travaille sur une sorte de détecteur de Cogs.\aIl te permettra de voir où sont les Cogs afin de les repérer plus facilement.\aLa page des Cogs dans ton journal de bord en est la clé.\aEn battant assez de Cogs, tu pourras te régler sur leurs signaux et détecter leur emplacement.\aContinue à vaincre des Cogs, afin d'être prêt.", + COMPLETE : "Bon travail! Tu pourras probablement utiliser ceci...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 2202 : { QUEST : "Salut, _avName_. Dieu merci, tu es là. Un Radino à l'air méchant était là à l'instant et il est parti avec une chambre à air.\aJe crains qu'ils ne l'utilisent pour leurs sombres desseins.\aS'il te plaît, essaie de le retrouver et ramène-la moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé ma chambre à air ?", + COMPLETE : "Tu as trouvé ma chambre à air! Tu es vraiment très doué. Tiens, prends ta récompense...", + }, + 2203 : { QUEST : "Les Cogs sont en train de mettre la banque sens dessus dessous.\aVa voir le Capitaine Carl et vois ce que tu peux faire._where_" }, + 2204 : { QUEST : "Bienvenue à bord, moussaillon.\aGrrr! Ces fripons de Cogs ont cassé mon monocle et je n'arrive plus à compter la monnaie sans lui.\aGarde les pieds sur terre et porte cette ordonnance au Dr. Queequeg puis rapporte m'en un nouveau._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "Qu'est-ce que c'est ?\aOh, je voudrais bien préparer cette ordonnance mais les Cogs ont chapardé mes réserves.\aSi tu peux reprendre la monture à un Laquaistic je pourrai probablement t'aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Désolé. Pas de monture du Laquaistic, pas de monocle.", + }, + 2206: { QUEST : "Excellent!\aUne seconde...\aTon ordonnance est prête. Emmène tout de suite ce monocle au Capitaine Carl._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Hisse et ho!\aTu vas finir par gagner du galon après tout.\aEt voilà.", + }, + 2207 : { QUEST : "Barbara Bernache a un Cog dans son magasin!\aIl vaudrait mieux que tu y ailles tout de suite._where_" }, + 2208 : { QUEST : "Ça alors! Tu viens de le rater, mon chou.\aIl y avait un Frappedos ici. Il a pris ma grande perruque blanche.\aIl a dit que c'était pour son chef et quelque chose à propos de \"jurisprudence\".\aSi tu pouvais me la rapporter, je t'en serais toujours reconnaissante.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Tu ne l'as toujours pas trouvé?\aIl est grand avec une tête pointue.", + COMPLETE : "Tu l'as trouvé!?!?\aTu es vraiment un ange!\aTu as bien mérité ceci...", + }, + 2209 : { QUEST : "Ginette se prépare pour un voyage important.\aVa y faire un tour et vois ce que tu peux faire pour l'aider._where_"}, + 2210 : { QUEST : "Tu peux m'aider.\aLe quartier général des Toons m'a demandé de faire un voyage pour voir si je peux trouver d'où viennent les Cogs.\aJ'aurais besoin de quelques affaires pour mon bateau mais je n'ai pas beaucoup de bonbons.\aVa et ramène-moi du lest de chez Ernest. Il faudra que tu lui rendes un service pour l'avoir._where_", + GREETING : "Salut, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Comme ça, Ginette veut du lest, n'est-ce pas?\aElle me doit encore de l'argent pour le dernier boisseau.\aJe te le donnerai si tu peux faire partir cinq Microchefs de ma rue.", + INCOMPLETE_PROGRESS : "Non, idiot! J'ai dit CINQ Microchefs...", + GREETING : "Que puis-je faire pour toi?", + LEAVING : "", + }, + 2212 : { QUEST : "Un marché est un marché.\aVoilà ton lest pour cette pingre de Ginette._where_", + GREETING : "Eh bien, regarde ce qui arrive là...", + LEAVING : "", + }, + 2213 : { QUEST : "Excellent travail. Je savais qu'il serait raisonnable.\aEnsuite il me faudra une carte marine de chez Art.\aJe ne crois pas avoir beaucoup de crédit là-bas non plus il faudra que tu t'arranges avec lui._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Oui, j'ai la carte marine que veut Ginette.\aEt tu l'auras en échange d'un petit travail.aJ'essaie de construire un astrolabe pour naviguer dans les étoiles.aJ'aurais besoin de trois pignons de Cog pour le construire.\aReviens quand tu les auras trouvés.", + INCOMPLETE_PROGRESS: "Alors ils arrivent ces pignons de Cogs?", + GREETING : "Bienvenue!", + LEAVING : "Bonne chance!", + }, + 2215 : { QUEST : "Ooh! Ces pignons feront très bien l'affaire.\aVoilà la carte. Donne-la à Ginette avec mes compliments._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bon, on y est presque. Je suis prête à prendre la mer!\aJe t'emmènerais avec moi si tu n'avais pas un teint si vert. Prends plutôt ceci.", + }, + 901 : { QUEST : "Si tu es d'accord, Ahab a besoin d'aide, chez lui..._where_", + }, + 2902 : { QUEST : "Tu es la nouvelle recrue ?\aBien, bien. Tu peux peut-être m'aider.\aJe suis en train de construire un crabe géant préfabriqué pour dérouter les Cogs.\aJe pourrais quand même utiliser une manille. Va voir Gérard et rapportes-en une, s'il te plaît._where_", + }, + 2903 : { QUEST : "Salut!\aOui, j'ai entendu parler du crabe géant qu'Ahab est en train de fabriquer.\aLa meilleure manille que j'aie est un peu sale quand même.\aSois sympa, passe chez un blanchisseur avant de la déposer._where_", + LEAVING : "Merci!" + }, + 2904 : { QUEST : "Tu dois être la personne que Gérard a envoyée.\aJe crois que je peux faire ça assez vite.\aJuste une minute...\aEt voilà. Comme neuf!\aTu salueras Ahab de ma part._where_", + }, + 2905 : { QUEST : "Ah, c'est exactement ce que je cherchais.\aPendant que tu es là, je vais aussi avoir besoin d'un très gros ressort d'horloge.\aVa donc chez Crochet voir s'il en a un._where_", + }, + 2906 : { QUEST : "Un gros ressort, hein ?\aJe suis désolé mais le plus gros ressort que j'aie est quand même plutôt petit.\aJe pourrais peut-être en fabriquer un avec des ressorts de gâchette de pistolet éclabousseur.\aApporte-moi trois de ces gags et je vais voir ce que je peux faire.", + }, + 2907 : { QUEST : "Regardons ça...\aGénial. Vraiment génial.\aQuelquefois je me surprends moi-même.\aEt voilà : un gros ressort pour Ahab!_where_", + LEAVING : "Bonne route!", + }, + 2911 : { QUEST : "Je serais très content de pouvoir aider, _avName_.\aMalheureusement, les rues ne sont plus sûres.\aVa donc éliminer quelques Cogs Caissbots et on pourra parler.", + INCOMPLETE_PROGRESS : "Je crois que tu peux rendre les rues encore plus sûres.", + }, + 2916 : { QUEST : "Oui, j'ai un contrepoids que je pourrais donner à Ahab.\aJe crois que ce serait plus sûr si tu pouvais vaincre deux Vendibots d'abord.", + INCOMPLETE_PROGRESS : "Pas encore. Tu dois vaincre plus de Vendibots.", + }, + 2921 : { QUEST : "Hmmm, je pensais que je pourrais me débarrasser d'un poids.\aJe me sentirais bien mieux s'il n'y avait pas autant de Cogs Chefbots à rôder par ici.\aVa donc en vaincre six et reviens me voir.", + INCOMPLETE_PROGRESS : "Je crois qu'on n'est toujours pas en sécurité...", + }, + 2925 : { QUEST : "Ça y est ?\aBon, je suppose qu'on est suffisamment en sécurité maintenant.\aVoilà le contrepoids pour Ahab._where_" + }, + 2926 : {QUEST : "Bon, c'est tout.\aVoyons si ça marche.\aHmmm, il y a un petit problème.\aJe n'ai plus de courant parce que ce bâtiment Cog bloque mon capteur solaire.\aPeux-tu le reprendre pour moi?", + INCOMPLETE_PROGRESS : "Toujours pas de courant. Où en es-tu avec ce bâtiment ?", + COMPLETE : "Super! Tu es une sacrée terreur pour les Cogs! Tiens, prends ta récompense...", + }, + 3200 : { QUEST : "Je viens d'avoir un appel de _toNpcName_.\aCe n'est pas son jour. Tu pourrais peut-être l'aider.!\aVa y faire un tour et vois ce dont il a besoin._where_" }, + 3201 : { QUEST : "Oh, merci d'être là!\aJ'ai besoin de quelqu'un pour emporter cette nouvelle cravate en soie à _toNpcName_.\aEst-ce que tu peux faire ça pour moi?_where_" }, + 3203 : { QUEST : "Oh, ça doit être la cravate que j'ai commandée! Merci!\aElle va avec un costume à rayures que je viens de finir, juste là.\aHé, qu'est ce qui est arrivé à ce costume ?\aOh non! Les Cogs ont dû voler mon nouveau costume!\aTu dois vaincre des Cogs jusqu'à ce que tu trouves mon costume, et que tu me le rapportes.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon costume ? Je suis certain que les Cogs l'ont pris!", + COMPLETE : "Youpii! Tu as trouvé mon nouveau costume!\aTu vois, je t'avais dit que les Cogs l'avaient! Voilà ta récompense...", + }, + + 3204 : { QUEST : "_toNpcName_ vient d'appeler pour signaler un vol.\aPourquoi n'irais-tu pas voir si tu peux arranger l'affaire ?_where_" }, + 3205 : { QUEST : "Bonjour, _avName_! Tu es là pour m'aider ?\aJe viens de chasser un Pique-au-sang de mon magasin. Houlala! C'était effrayant.\aMais maintenant je ne trouve plus mes ciseaux! Je suis certain que ce Pique-au-sang les a pris.\aTrouve-le, et ramène-moi mes ciseaux.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu cherches encore mes ciseaux?", + COMPLETE : "Mes ciseaux! Merci beaucoup! Voilà ta récompense...", + }, + + 3206 : { QUEST : "On dirait que _toNpcName_ a des problèmes avec des Cogs.\aVa voir si tu peux l'aider._where_" }, + 3207 : { QUEST : "Ohé, _avName_! Merci d'être venu!\aUne bande de Charabieurs est arrivée et a volé une pile de cartes postales sur mon comptoir.\aS'il te plaît, sors vaincre tous ces Charabieurs et rapporte-moi mes cartes postales!", + INCOMPLETE_PROGRESS : "Il n'y a pas assez de cartes postales! Continue de chercher!", + COMPLETE : "Oh, merci! Maintenant je vais pouvoir livrer le courrier à temps! Voilà ta récompense...", + }, + + 3208 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Cassepieds.\aEssaie de vaincre 10 Cassepieds pour aider tes camarades Toons du Jardin de Daisy." }, + 3209 : { QUEST : "Merci d'avoir battu ces Cassepieds!\aMais maintenant ce sont les Télévendeurs qui sont incontrôlables.\aVa vaincre 10 Télévendeurs au Jardin de Daisy et reviens ici pour ta récompense." }, + + 3247 : { QUEST : "Nous avons eu des plaintes des résidents récemment à propos des Pique-au-sang.\aEssaie de vaincre 20 Pique-au-sang pour aider tes camarades Toons du Jardin de Daisy." }, + + + 3210 : { QUEST : "Oh non, la Fleur qui mouille, rue des Érables, n'a plus de fleurs!\aEmmène-leur dix de tes fleurs à éclabousser pour les aider.\aVérifie que tu as bien 10 fleurs à éclabousser dans ton inventaire d'abord.", + LEAVING: "", + INCOMPLETE_PROGRESS : "J'ai besoin de 10 fleurs à éclabousser. Tu n'en as pas assez!" }, + 3211 : { QUEST : "Oh, merci beaucoup! Ces fleurs à éclabousser vont nous tirer d'embarras.\aMais j'ai peur des Cogs qui sont dehors.\aPeux-tu m'aider et vaincre quelques-uns de ces Cogs?\aReviens me voir après avoir vaincu 20 Cogs dans cette rue.", + INCOMPLETE_PROGRESS : "Il reste encore des Cogs à vaincre par ici! Continue!", + COMPLETE : "Oh, merci! Cela m'aide beaucoup. Ta récompense est...", + }, + + 3212 : { QUEST : "_toNpcName_ a besoin d'aide pour chercher quelque chose qu'elle a perdu.\aVa la voir et vois ce que tu peux faire._where_" }, + 3213 : { QUEST : "Salut, _avName_. Peux-tu m'aider ?\aJe crois que j'ai égaré mon stylo. Je pense que les Cogs l'ont peut-être pris.\aVa vaincre des Cogs pour retrouver le stylo qu'ils m'ont volé.", + INCOMPLETE_PROGRESS : "Tu n'as pas encore trouvé mon stylo?" }, + 3214 : { QUEST : "Oui, c'est mon stylo! Merci beaucoup!\aMais après ton départ, j'ai réalisé que mon encrier manquait aussi.\aVa vaincre des Cogs pour retrouver mon encrier.", + INCOMPLETE_PROGRESS : "Je cherche encore mon encrier!" }, + 3215 : { QUEST : "Super! Maintenant j'ai retrouvé mon stylo et mon encrier!\aMais tu ne devineras jamais!\aMon bloc-notes a disparu! Ils ont dû le voler aussi!\aVa vaincre des Cogs pour retrouver mon bloc-notes volé, puis reviens pour ta récompense.", + INCOMPLETE_PROGRESS : "Tu as des nouvelles de mon bloc-notes?" }, + 3216 : { QUEST : "C'est mon bloc-notes! Youpii! Ta récompense est...\aHé! Mais où est-elle ?\aJ'avais ta récompense là, dans le coffre de mon bureau. Mais le coffre entier a disparu!\aIncroyable! Ces Cogs ont volé ta récompense!\aVa vaincre des Cogs pour retrouver mon coffre.\aQuand tu me le ramèneras, je te donnerai ta récompense.", + INCOMPLETE_PROGRESS : "Continue de chercher ce coffre! Ta récompense est dedans!", + COMPLETE : "Enfin! J'avais ton nouveau sac à gags dans ce coffre. Le voilà...", + }, + + 3217 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Vendibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Cafteur.\aTu peux en attraper un quand le Cog explose." }, + 3218 : { QUEST : "Bon travail! Maintenant, nous avons besoin d'un pignon de Passetout pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3219 : { QUEST : "Super! Maintenant on n'a plus besoin que d'un pignon en plus.\aCette fois, il nous faut un pignon de Secousse-cousse.\aTu devras peut-être chercher à l'intérieur des bâtiments Vendibots pour trouver cette sorte de Cogs.\aQuand tu en auras attrapé un, rapporte-le pour recevoir ta récompense." }, + + 3244 : { QUEST : "Nous avons fait quelques études sur les mécanismes des Loibots.\aNous devons encore étudier certaines pièces de plus près.\aApporte-nous un pignon de Charognard.\aTu peux en attraper un quand le Cog explose." }, + 3245 : { QUEST : "Bon travail! Maintenant nous avons besoin d'un pignon de Frappedos pour faire la comparaison.\aCes pignons sont plus difficiles à attraper, ne te décourage pas." }, + 3246 : { QUEST : "Super! Encore un pignon et c'est bon.\aCette fois, il nous faut un pignon de Tournegris.\aQuand tu en auras attrapé un, rapporte-le pour avoir ta récompense." }, + + 3220 : { QUEST : "Je viens d'apprendre que _toNpcName_ te cherchait.\aPourquoi ne vas-tu pas voir ce qu'elle veut ?_where_" }, + 3221 : { QUEST : "Ohé, _avName_! Et voilà!\aJ'ai entendu dire que tu étais expert(e) en éclaboussures.\aJ'ai besoin de quelqu'un pour montrer l'exemple à tous les Toons du Jardin de Daisy.\aUtilise tes attaques par éclaboussure pour vaincre un groupe de Cogs.\aEncourage tes contacts à utiliser aussi les éclaboussures.\aLorque tu auras vaincu 20 Cogs, reviens ici pour ta récompense!" }, + + 3222 : { QUEST : "C'est le moment de faire preuve de ta Toonmaîtrise.\aSi tu réussis à reprendre un certain nombre de bâtiments aux Cogs, tu gagneras le droit à trois quêtes.\aD'abord, tu dois prendre deux bâtiments aux Cogs.\aN'hésite pas à demander l'aide de tes contacts."}, + 3223 : { QUEST : "Super travail pour ces bâtiments!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins deux étages." }, + 3224 : { QUEST : "Fantastique!\aMaintenant tu dois prendre deux bâtiments de plus.\aCes immeubles doivent faire au moins trois étages.\aQuand tu auras fini, reviens chercher ta récompense!", + COMPLETE : "Tu as réussi, _avName_!\aTu as fait preuve d'une excellente Toonmaîtrise.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ dit qu'elle a besoin d'aide.\aVa voir si tu peux lui donner un coup de main ?_where_" }, + 3235 : { QUEST : "Oh, c'est la salade que j'ai commandée!\aMerci de me l'avoir apportée.\aTous ces Cogs ont dû effrayer le livreur habituel de _toNpcName_ encore une fois.\aTu pourrais nous rendre service et vaincre quelques-uns des Cogs qui traînent par ici?\aVa vaincre 10 Cogs dans le Jardin de Daisy et reviens voir _toNpcName_.", + INCOMPLETE_PROGRESS : "Tu es en train de vaincre des Cogs pour moi?\aC'est super!! Continue comme ça!", + COMPLETE : "Oh, merci beaucoup d'avoir vaincu ces Cogs!\aMaintenant je vais peut-être pouvoir reprendre mon programme habituel de livraisons.\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Va raconter à _toNpcName_ tous les Cogs que tu as vaincus._where_" }, + + 3236 : { QUEST : "Il y a beaucoup trop de Loibots par ici.\aTu peux faire ta part de travail!\aVa vaincre 3 bâtiments Loibot." }, + 3237 : { QUEST : "Super travail pour ces bâtiments Loibot!\aMais maintenant il y a beaucoup trop de Vendibots!\aVa vaincre 3 bâtiments Vendibot, puis reviens chercher ta récompense." }, + + 3238 : { QUEST : "Oh non! Un Cog Circulateur a volé la clé du Jardin de Daisy!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Circulateurs ne se trouvent que dans les bâtiments Vendibot." }, + 3239 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du Jardin de Daisy.\aContinue de chercher! Un Cog Circulateur l'a encore!" }, + + 3242 : { QUEST : "Oh non! Un Cog Avocageot a volé la clé du Jardin de Daisy!\aVa voir si tu peux la retrouver.\aSouviens-toi que les Avocageots ne se trouvent que dans les bâtiments Loibot." }, + 3243 : { QUEST : "Tu as bien trouvé une clé, mais ce n'est pas la bonne!\aNous avons besoin de la clé du Jardin de Daisy.\aContinue de chercher! Un Cog Avocageot l'a encore!" }, + + 3240 : { QUEST : "_toNpcName_ vient de me dire qu'un Avocageot lui a volé un sac de graines pour oiseaux.\aVa vaincre des Avocageots jusqu'à ce que tu retrouves les graines pour oiseaux de Piaf, et rapporte-les lui.\aLes Avocageots ne se trouvent que dans les bâtiments Loibot._where_", + COMPLETE : "Oh, merci beaucoup d'avoir retrouvé mes graines pour oiseaux!\aTa récompense est...", + INCOMPLETE_WRONG_NPC : "Bien, tu as retrouvé ces graines pour oiseaux!\aMaintenant apporte-les à _toNpcName_._where_", + }, + + 3241 : { QUEST : "Certains des bâtiments des Cogs deviennent beaucoup trop hauts.\aVa voir si tu peux réduire de hauteur certains des immeubles les plus hauts.\aReprends 5 immeubles de 3 étages ou plus et reviens ici pour ta récompense.", + }, + + 3250 : { QUEST : "Lima, la détective de la rue du Chêne, a entendu parler d'un quartier général Vendibot. \aVa donc la voir et aide-la à enquêter.", + }, + 3251 : { QUEST : "Il y a quelque chose de bizarre par ici.\aIl y a tant de Vendibots!\aJ'ai entendu dire qu'ils ont installé leur propre quartier général au bout de cette rue.\aVa au bout de la rue voir ce qu'il en est.\aTrouve des Cogs Vendibots dans leur quartier général, vaincs-en 5 et reviens me le dire.", + }, + 3252 : { QUEST : "OK, annonce la couleur\aQu'est-ce que tu dis?\aAh, le quartier général des Vendibots?? Oh non!!! Il faut faire quelque chose.\aNous devons le dire au Juge Ticot - il saura quoi faire.\aVa le voir tout de suite et dis-lui ce que tu as trouvé. Il est juste au bout de la rue.", + }, + 3253 : { QUEST : "Oui, puis-je t'aider ? Je suis très occupé.\aHein ? Un quartier général Cog?\aHein ? Sottises. Ça n'est pas possible.\aTu dois te tromper. C'est grotesque.\aHein ? Ne discute pas avec moi.\aOk, alors ramène des preuves.\aSi les Vendibots sont vraiment en train de construire ce quartier général Cog, les Cogs du quartier auront des plans sur eux.\aLes Cogs adorent la paperasserie, tu le savais?\aVa vaincre des Vendibots par là-bas jusqu'à ce que tu trouves des plans.\aRapporte-les moi, alors je te croirai peut-être.", + }, + 3254 : { QUEST : "Encore toi, hein ? Des plans? Tu les as?\aLaisse-moi regarder ça! Hmmm... Une usine ?\aCe doit être là qu'ils fabriquent les Vendibots... Et qu'est-ce que c'est que ça?\aOui, exactement ce que je pensais. Je le savais depuis le départ.\aIls sont en train de construire un quartier général des Cogs Vendibots.\aCe n'est pas bon signe. Je dois passer quelques appels. Très occupé. Au revoir!\aHein ? Oh oui, retourne ces plans à la détective Lima.\aElle saura les lire mieux que quiconque.", + COMPLETE : "Qu'a dit le Juge Ticot ?\aOn avait raison ? Oh non. Regardons ces plans.\aHmmm... On dirait que les Vendibots ont installé une usine avec l'outillage pour construire des Cogs.\aÇa a l'air très dangereux. N'y va pas tant que tu n'as pas plus de rigolpoints.\aQuand tu auras plus de rigolpoints, nous en aurons beaucoup à apprendre sur le quartier général des Vendibots.\aPour l'instant, bon travail, voilà ta récompense.", + }, + + + 3255 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3256 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux donner un coup de main._where_" }, + 3257 : { QUEST : "_toNpcName_ est en train d'enquêter sur le quartier général des Vendibots.\aVa voir si tu peux lui donner un coup de main._where_" }, + 3258 : { QUEST : "Personne ne sait au juste ce que les Cogs sont en train de faire dans leur nouveau quartier général.\aJ'ai besoin que tu nous ramènes des informations venant directement d'eux.\aSi nous pouvons trouver quatre notes de service internes des Vendibots à l'intérieur de leur quartier général, cela mettrait un peu les choses au clair.\aRamène-moi la première note de service que tu pourras afin qu'on en sache un peu plus.", + }, + 3259 : { QUEST : "Super! Voyons ce que dit cette note de service...\a\"À l'attention des Vendibots :\aJe serai dans mon bureau tout en haut des Tours Vendibot pour faire monter en grade les Cogs. \aLorsque vous aurez gagné suffisamment de mérites, montez me voir par l'ascenseur du hall.\aLa pause est terminée - tout le monde au travail!\"\aSigné, Vice-Président des Vendibots\"\aAah.... Flippy sera content de voir ça. Je lui envoie ça tout de suite.\aVa chercher une seconde note de service et rapporte-la moi.", + }, + 3260 : { QUEST : "Oh, bien, tu es de retour. Voyons ce que tu as trouvé....\a\"À l'attention des Vendibots :\aLes Tours Vendibot ont été équipées d'un nouveau système de sécurité pour empêcher les Toons de pénétrer à l'intérieur.\aLes Toons qui seront attrapés dans les Tours Vendibot seront retenus pour interrogatoire.\aVeuillez en discuter dans le hall autour d'un apéritif.\aSigné, Le Circulateur \"\aTrès intéressant... Je communique l'information immédiatement.\aS'il te plaît, rapporte-moi une troisième note de service.", + }, + 3261 : { QUEST : "Excellent travail, _avName_! Que dit cette note de service ?\a\"À l'attention des Vendibots :\aLes Toons sont parvenus à trouver une façon d'infiltrer les Tours Vendibot.\aJe vous appellerai ce soir pendant le dîner pour vous donner des détails.\aSigné, Télévendeur\"\aHmmm... Je me demande comment les Toons se sont infiltrés....\aRapporte-moi une note de service supplémentaire et je crois que nous aurons assez d'informations pour l'instant.", + COMPLETE : "Je savais que tu pouvais le faire! OK, voilà ce que dit la note de service....\a\"À l'attention des Vendibots :\aJ'ai déjeûné avec M. Hollywood hier.\aIl dit que le Vice-Président est très occupé en ce moment.\aIl ne prendra de rendez-vous qu'avec les Cogs qui méritent une promotion.\aJ'allais oublier, Passetout joue au golf avec moi dimanche.\aSigné, Cafteur\"\aBon... _avName_, voilà qui est bien utile.\aVoilà ta récompense.", + }, + + 3262 : { QUEST : "_toNpcName_ a de nouvelles informations à propos de l'usine du quartier général Vendibot.\aVa donc voir ce que c'est._where_" }, + 3263 : { GREETING : "Salut, mon pote!", + QUEST : "Je suis Zucchini l'entraîneur, mais tu peux simplement m'appeler Coach Z.\aJe mets la gomme sur le squash et les étirements, si tu vois ce que je veux dire.\aÉcoute, les Vendibots ont terminé une énorme usine qui sort des Vendibots 24 heures sur 24.\aPrends une équipe de potes Toons et va me réduire cette usine à néant!\aÀ l'intérieur du quartier général Vendibot, cherche le tunnel qui mène à l'usine puis monte par l'ascenseur de l'usine.\aVérifie que tu as fait le plein de gags, de rigolpoints et que tu as quelques Toons costauds comme guides.\aVa vaincre le contremaître dans l'usine pour ralentir la progression des Vendibots.\aCe sera une vraie séance d'entraînement, si tu vois ce que je veux dire.", + LEAVING : "À plus, mon pote!", + COMPLETE : "Hé mon pote, bon boulot pour cette usine!\aOn dirait que tu as trouvé un morceau de costume de Cog.\aIl doit venir de la chaîne de fabrication des Cogs.\aÇa peut être pratique. Continue à en ramasser quand tu as du temps de libre.\aPeut-être que si tu récupères un costume de Cog complet, ça pourrait être utile à quelque chose....", + }, + + 4001 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Tu peux maintenant choisir la prochaine série de gags que tu veux apprendre.\aPrends ton temps pour te décider, et reviens quand tu auras choisi.", + INCOMPLETE_PROGRESS : "Pense bien à ta décision avant de choisir.", + INCOMPLETE_WRONG_NPC : "Pense bien à ta décision avant de choisir.", + COMPLETE : "Une sage décision...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Je parie que Tom aimerait de l'aide pour ses recherches._where_", + }, + 4201 : { GREETING: "Salut!", + QUEST : "Je suis très embêté au sujet d'une vague de vols d'instruments.\aJe fais une enquête parmi mes confrères commerçants.\aJe vais peut-être pouvoir trouver une constante qui me permettra de résoudre ce cas.\aVa voir Tina et demande-lui un inventaire des concertinas._where_", + }, + 4202 : { QUEST : "Oui, j'ai parlé à Tom ce matin.\aJ'ai l'inventaire ici.\aTu vas lui apporter tout de suite, ok?_where_" + }, + 4203 : { QUEST : "Super! Et de un...\aMaintenant va chercher celui de Yuki._where_", + }, + 4204 : { QUEST : "Oh! L'inventaire!\aJ'avais complètement oublié.\aJe parie que je peux le faire le temps que tu aies vaincu 10 Cogs.\aRepasse après ça et je promets que ce sera prêt.", + INCOMPLETE_PROGRESS : "31, 32... OUPS!\aTu m'as fait perdre mon compte!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, et voilà.\aMerci de m'avoir laissé un peu de temps.\aEmmène ça à Tom et dis-lui bonjour de ma part._where_", + }, + 4206 : { QUEST : "Hmm, très intéressant.\aÇa commence à ressembler à quelque chose.\aOK, le dernier inventaire est celui de Fifi._where_", + }, + 4207 : { QUEST : "Inventaire ?\aComment est-ce que je pourrais faire un inventaire sans formulaire ?\aVa voir Clément de sol et demande-lui s'il en a un pour moi._where_", + INCOMPLETE_PROGRESS : "Alors, ce formulaire ?", + }, + 4208 : { QUEST : "Ah ça oui j'ai un formulaire d'inventaire!\aMais c'est pas gratuit, tu vois.\aJe vais te dire. Je te le vends pour une tarte à la crème entière.", + GREETING : "Allez, mon petit!", + LEAVING : "Chouette...", + INCOMPLETE_PROGRESS : "Une seule tranche c'est pas assez.\aJ'ai faim, mon petit! Je veux la tarte TOUTE ENTIÈRE.", + }, + 4209 : { GREETING : "", + QUEST : "Mmmm...\aSuper bon!\aVoilà ton formulaire pour Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Merci. Ça va bien m'aider.\aVoyons...violons: 2\aÇa y est! Et voilà!", + COMPLETE : "Bon travail, _avName_!\aJe suis sûr de pouvoir attraper ces voleurs maintenant.\aOn va pouvoir creuser cette affaire!", + }, + + 4211 : { QUEST : "Dis donc, le Dr Tefaispasdebile appelle toutes les cinq minutes. Tu pourrais aller voir quel est son problème ?_where_", + }, + 4212 : { QUEST : "Houlala! Je suis content que le quartier général des Toons ait fini par envoyer quelqu'un.\aÇa fait des jours que je n'ai pas vu un client.\aCe sont ces satanés Gobechiffres qui sont partout.\aJe crois qu'ils enseignent une mauvaise hygiène buccale à nos résidents.\aVa donc en vaincre dix et nous verrons si les affaires reprennent.", + INCOMPLETE_PROGRESS : "Toujours pas de patients. Mais continue!", + }, + 4213 : { QUEST : "Tu sais après tout peut-être que ce n'était pas les Gobechiffres.\aPeut-être que ce sont simplement les Caissbots en général.\aDébarrasse-nous de vingt d'entre eux et j'espère que quelqu'un viendra au moins pour un bilan de santé.", + INCOMPLETE_PROGRESS : "Je sais que vingt ça fait beaucoup. Mais je suis certain que ça va rapporter des tonnes.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne comprends rien du tout!\aToujours pas un SEUL client.\aPeut-être qu'on devrait remonter jusqu'à la source.\aEssaie de reprendre un bâtiment Cog Caissbot.\aÇa devrait faire l'affaire...", + INCOMPLETE_PROGRESS : "Oh, s'il te plaît! Juste un tout petit bâtiment...", + COMPLETE : "Toujours personne.\aMais tu vois, maintenant que j'y pense.\aJe n'avais pas non plus de clients avant l'invasion des Cogs!\aJe te remercie quand même beaucoup pour ton aide.\aCela devrait te rendre service." + }, + + 4215 : { QUEST : "Anna a désespérément besoin de quelqu'un pour l'aider.\aPourquoi ne vas-tu pas voir ce que tu peux faire ?_where_", + }, + 4216 : { QUEST : "Merci d'être là aussi vite!\aOn dirait que les Cogs sont partis avec les tickets de croisière de plusieurs de mes clients.\aYuki a dit qu'elle avait vu un Passetout sortir d'ici avec des tickets plein les mains.\aVa voir si tu peux retrouver le ticket pour l'Alaska de Jack Bûcheron.", + INCOMPLETE_PROGRESS : "Ces Passetouts pourraient être n'importe où maintenant...", + }, + 4217 : { QUEST : "Oh, super. Tu l'as trouvé!\aPuisque je peux compter sur toi, va le porter à Jack pour moi, tu veux bien ?_where_", + }, + 4218 : { QUEST : "Tralala!\aAlaska, me voilà!\aJe ne peux plus supporter ces Cogs infernaux.\aDis donc, je crois qu'Anna a encore besoin de toi._where_", + }, + 4219 : { QUEST : "Ouais, tu as deviné.\aJ'aurais besoin que tu secoues ces satanés Passetouts pour récupérer le ticket de Tabatha pour la fête du Jazz.\aTu sais comment ça marche...", + INCOMPLETE_PROGRESS : "Il y en a d'autres qui rôdent...", + }, + 4220 : { QUEST : "Adorable!\aTu peux lui emmener ça pour moi aussi?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Sois sympa...", + QUEST : "Super, mon petit!\aMaintenant je suis à la fête, _avName_.\aAvant de partir, tu ferais mieux d'aller voir Anna Banane encore une fois..._where_", + }, + 4222 : { QUEST : "C'est la dernière fois, je te promets!\aMaintenant tu vas chercher le ticket de Barry pour le concours de chant.", + INCOMPLETE_PROGRESS : "Allez, _avName_.\aBarry compte sur toi.", + }, + 4223 : { QUEST : "Ça devrait redonner le sourire à Barry._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Bonjour, Bonjour, BONJOUR!\aSuper!\aJe suis sûr que les gars et moi on a va ramasser le gros lot cette année.\aAnna demande que tu repasses la voir pour récupérer ta récompense._where_\aAu revoir, au revoir, AU REVOIR!", + COMPLETE : "Merci pour toute ton aide, _avName_.\aTu es vraiment un atout pour nous à Toontown.\aEn parlant d'atouts...", + }, + + 902 : { QUEST : "Va donc voir Léo.\aIl a besoin de quelqu'un pour porter un message._where_", + }, + 4903 : { QUEST : "Pote!\aMes castagnettes sont toutes ternies et j'ai un grand spectacle ce soir. \aEmporte-les donc à Carlos voir s'il peut me les faire reluire._where_", + }, + 4904 : { QUEST : "Voui, yé crois que yé peux réluire ça.\aMé yé bézoin d'encre de seiche bleue.", + GREETING : "¡Holà!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Tou pé trrouver la seiche partout sour lé pontons de pêche.", + }, + 4905 : { QUEST : "Voui! Souperr!\aAhóra yé bézoin d'un peu de temps pour réluire ça.\aTou pé aller récoupérer un bâtiment de oun étage pendant qué yé trravaille ?", + GREETING : "¡Holà!", + LEAVING : "¡Adiós!", + INCOMPLETE_PROGRESS : "Oun pitite minute...", + }, + 4906 : { QUEST : "Trrès bien!\aVoilà les castagnettes pour Léo._where_", + }, + 4907 : { GREETING : "", + QUEST : "Super, mon petit!\aElles sont superbes!\aMaintenant j'ai besoin que tu me rapportes une copie des paroles de \"Un Noël toon\" de chez Élise._where_", + }, + 4908 : { QUEST: "Holà par ici!\aHmmm, je n'ai pas de copie de cette chanson.\aSi tu me laisses un peu de temps je pourrai la retranscrire de mémoire.\aPourquoi tu n'irais pas faire un tour et reprendre un bâtiment de deux étages pendant que j'écris?", + }, + 4909 : { QUEST : "Je suis désolée.\aMa mémoire est un peu floue.\aSi tu vas reprendre un bâtiment de trois étages, je suis sûre que ce sera fait quand tu reviendras...", + }, + 4910 : { QUEST : "Ça y est!\aDésolée d'avoir mis si longtemps.\aRapporte-ça à Léo._where_", + GREETING : "", + COMPLETE : "Génial, mon petit!\aMon concert va casser la baraque!\aÀ propos de casser, tu pourras utiliser ça sur quelques Cogs..." + }, + 5247 : { QUEST : "Le quartier est assez chaud...\aTu pourrais avoir besoin d'apprendre quelques nouveaux trucs.\a_toNpcName_ m'a appris tout ce que je sais, il peut peut-être t'aider aussi._where_" }, + 5248 : { GREETING : "Ahh, oui.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as l'air d'avoir des difficultés avec cette mission.", + QUEST : "Aah, bienvenue, nouvel apprenti.\aJe sais tout ce qu'on peut savoir à propos du jeu de tartes.\aMais avant qu'on ne commence ton entraînement, une petite démonstration s'impose.\aVa donc faire un tour et vaincre dix des plus gros Cogs." }, + 5249 : { GREETING: "Mmmmm.", + QUEST : "Excellent!\aMaintenant tu vas nous montrer ce que tu sais faire à la pêche.\aJ'ai fait tomber trois dés en peluche dans la mare hier.\aVa-les pêcher et rapporte-les moi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "On dirait que tu n'es pas si habile avec la canne et le moulinet." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Loibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Chefbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Caissbot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Aah! Ces dés auront l'air super, accrochés au rétroviseur de ma bagnole!\aMaintenant, montre-moi que tu peux distinguer tes ennemis les uns des autres.\aReviens quand tu auras repris deux des plus grands bâtiments Vendibot.", + INCOMPLETE_PROGRESS : "Est-ce que tu as des difficultés avec ces bâtiments?", }, + 5200 : { QUEST : "Ces faux-jetons de Cogs font encore des leurs.\a_toNpcName_ vient de signaler un autre objet disparu. Va voir si tu peux régler cela._where_" }, + 5201 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être venu.\aUn groupe de ces Chassetêtes est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5261 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Bifaces est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5262 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Sacasous est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5263 : { GREETING: "", + QUEST : "Salut, _avName_. Je sais que je devrais te remercier d'être là.\aUn groupe de ces Tournegris est venu et a volé mon ballon de foot.\aLe chef m'a dit que je devais faire des économies et me l'a arraché!\aPeux-tu me rapporter mon ballon ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Est-ce que tu as retrouvé mon ballon de foot ?", + COMPLETE : "Youpiii! Tu l'as trouvé! Tiens, prends ta récompense...", + }, + 5202 : { QUEST : "Le Glagla a été envahi par des Cogs parmi les plus robustes qu'on ait vus.\aTu auras probablement besoin d'emporter plus de gags là-bas.\aJ'ai entendu dire que _toNpcName_ pourrait te prêter un grand sac pour emporter plus de gags._where_" }, + 5203 : { GREETING: "Eh? Tu es dans mon équipe de luge ?", + QUEST : "Qu'est-ce que c'est ? Tu veux un sac?\aJ'en avais un par là...peut-être qu'il est dans ma luge ?\aMais c'est que... Je n'ai pas vu ma luge depuis la grande course!\aPeut-être qu'un de ces Cogs l'a prise ?", + LEAVING : "As-tu vu ma luge ?", + INCOMPLETE_PROGRESS : "Rappelle-moi qui tu es? Désolé, je suis un peu étourdi depuis l'accident." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Est-ce que c'est ma luge ? Je ne vois pas de sac par ici.\aJe crois que Boris Tourne était dans l'équipe...c'est peut-être lui qui l'a?_where_" }, + 5205 : { GREETING : "Oooh, ma tête!", + LEAVING : "", + QUEST : "Hein ? Ted qui? Un sac?\aAh, peut-être qu'il était dans notre équipe ?\aJ'ai tellement mal à la tête que je n'arrive plus à réfléchir.\aPourrais-tu aller me pêcher des glaçons dans la mare gelée pour ma tête ?", + INCOMPLETE_PROGRESS : "Aïe, ma tête me fait mal! Tu as de la glace ?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Aah, ma tête va beaucoup mieux!\aAlors tu cherches le sac de Ted, hein ?\aJe crois qu'il a atterri sur la tête de Sam Simiesque après l'accident._where_" }, + 5207 : { GREETING : "Hé-ho!", + LEAVING : "", + QUEST : "Quoi c'est ça un sac? Qui c'est ça Bouris?\aMoi avoir peur bâtiments! Toi battre bâtiments, moi te donner sac!", + INCOMPLETE_PROGRESS : "Encore bâtiments! Moi encore peur!", + COMPLETE : "Ooooh! Moi t'aime!" }, + 5208 : { GREETING : "", + LEAVING : "Hein!", + QUEST : "Ooooh! Moi t'aime!\aVa Atelier de ski. Sac là-bas." }, + 5209 : { GREETING : "Pote!", + LEAVING : "'plus!", + QUEST : "Bon sang, ce Sam Simiesque est fou!\aSi tu es aussi malade que Sam, je te donne ton sac.\aVa démolir des Cogs pour ton sac, mon pote! Salut!", + INCOMPLETE_PROGRESS : "Es-tu certain(e) d'être au point ? Va donc démolir plus de Cogs.", + COMPLETE : "Ouah! T'es vachement chouette! C'est un sacré tas de Cogs que tu as bousillés!\aVoilà ton sac!" }, + + 5210 : { QUEST : "_toNpcName_ aime quelqu'un du quartier en secret.\aSi tu l'aides, elle pourrait te donner une belle récompense._where_" }, + 5211 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un bec me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5264 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs avec un aileron me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5265 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Circulateurs me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5266 : { GREETING: "Bouhouhou.", + QUEST : "J'ai passé toute la nuit dernière à écrire une lettre au chien que j'aime.\aMais avant que je puisse l'envoyer, un de ces méchants Cogs Attactics me l'a dérobée.\aPeux-tu me la rapporter ?", + LEAVING : "Bouhouhou.", + INCOMPLETE_PROGRESS : "S'il te plaît, retrouve ma lettre." }, + 5212 : { QUEST : "Oh, merci d'avoir retrouvé ma lettre!\aS'il te plaît, peux-tu la remettre au plus beau chien du quartier ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas remis ma lettre, n'est-ce pas?", + }, + 5213 : { GREETING : "Charmé, certainement.", + QUEST : "Je ne peux pas m'occuper de ta lettre, tu vois.\aTous mes chiots m'ont été pris!\aSi tu les ramènes, peut-être qu'on pourra parler.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Mes pauvres petits chiots!" }, + 5214 : { GREETING : "", + LEAVING : "Youhouuu!", + QUEST : "Merci de m'avoir rapporté mes petits choux.\aRegardons cette lettre maintenant...Mmmm, il semblerait que j'ai une autre admiratrice secrète.\aIl est temps de rendre visite à mon cher ami Carl.\aTu l'aimeras beaucoup, c'est certain._where_" }, + 5215 : { GREETING : "Hé, hé...", + LEAVING : "Reviens, oui, oui.", + INCOMPLETE_PROGRESS : "Il y en a encore des gros par ici. Reviens nous voir quand il n'y en aura plus.", + QUEST : "Qui est-ce qui t'envoie ? On aime pas trop les bêcheurs, non...\aMais on aime encore moins les Cogs...\aDébarrasse-nous donc des gros et on t'aidera, oui on t'aidera." }, + 5216 : { QUEST : "On t'avait bien dit qu'on t'aiderait.\aTu peux emmener cette bague à la fille.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu as encore la bague ???", + COMPLETE : "Oh, tu es un amour!!! Merci!!!\aOh, et j'ai quelque chose de spécial pour toi aussi.", + }, + 5217 : { QUEST : "On dirait que _toNpcName_ pourrait avoir besoin d'aide._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je crois bien qu'il y a d'autres Circulateurs par ici.", + QUEST : "À l'aide!!! À l'aide!!! Je n'en peux plus!\aCes Circulateurs me rendent dingue!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ce n'est pas possible qu'il n'y ait que ça. Je viens d'en voir un!!!", + QUEST : "Oh, merci, mais maintenant ce sont les Attactics!!!\aIl faut que tu m'aides!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Non, non, il y en avait un juste là!", + QUEST : "Je réalise maintenant que ce sont les Usuriers!!!\aJe croyais que tu allais me sauver!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais quoi, peut-être finalement que ce ne sont pas du tout les Cogs!!!\aPourrais-tu demander à Gaëlle de me préparer une potion calmante ? Ça m'aiderait peut-être...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Oh, ce Harry, c'est quelqu'un!\aJe vais concocter quelque chose qui le remettra sur pied!\aBon, on dirait que je n'ai plus de moustaches de sardine...\aSois un ange et cours à la mare m'en attraper.", + INCOMPLETE_PROGRESS : "Tu les as, ces moustaches de sardine ?", }, + 5223 : { QUEST : "OK. Merci, mon ange.\aVoilà, maintenant porte ça à Harry. Ça devrait le calmer tout de suite.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vas-y maintenant, emporte la potion à Harry.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu vas attraper ces Avocageots pour moi, n'est-ce pas?", + QUEST : "Oh merci mon Dieu tu es de retour!\aDonne-moi la potion, vite!!!\aGlou, glou, glou...\aBerk, c'est dégoûtant!!\aMais tu sais quoi? Je me sens bien plus calme. Maintenant que j'ai les idées claires, je réalise que...\aC'est les Avocageots qui me rendaient malade pendant tout ce temps!!!", + COMPLETE : "Bon sang! Maintenant je peux me détendre!\aJ'ai sûrement quelque chose à te donner. Oh, prends ça!" }, + 5225 : { QUEST : "Depuis l'incident avec le pain de navets, Phil Électrique est furieux après _toNpcName_.\aTu pourrais peut-être aider Paul à les réconcilier ?_where_" }, + 5226 : { QUEST : "Ouais, tu as sans doute entendu dire que Phil Électrique est furieux contre moi...\aJ'essayais juste d'être gentil avec ce pain de navets.\aPeut-être que tu pourrais le remettre de bonne humeur.\aPhil a horreur de ces Cogs Caissbots, surtout leurs bâtiments.\aSi tu reprends des bâtiments Caissbot, ça pourrait aider.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Peut-être quelques bâtiments de plus?", }, + 5227 : { QUEST : "C'est formidable! Va dire à Phil ce que tu as fait._where_" }, + 5228 : { QUEST : "Oh il a fait ça?\aCe Paul croit qu'il peut s'en tirer comme ça, hein ?\aIl m'a cassé ma dent, oui, avec son fichu pain de navets!\aPeut-être que si tu amenais ma dent au Dr Marmotter, il pourrait la réparer.", + GREETING : "Mmmmrrphh.", + LEAVING : "Grrr, grrr.", + INCOMPLETE_PROGRESS : "Encore toi? Je pensais que tu allais faire réparer ma dent.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Caissbots?\aIls effraient mes clients." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Vendibots?\aIls effraient mes clients." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Loibots?\aIls effraient mes clients." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je suis encore en train de travailler sur la dent. Ça va être un peu plus long.", + QUEST : "Ah oui, cette dent est en mauvais état, c'est sûr.\aJe peux peut-être faire quelque chose, mais ça va mettre un moment.\aTu pourrais peut-être profiter de ce temps-là pour débarrasser les rues de quelques Cogs Chefbots?\aIls effraient mes clients." }, + 5230 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Pillard me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5270 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Gros Blochon me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5271 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement M. Hollywood me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5272 : { GREETING: "", + QUEST : "Je suis content que tu sois revenu(e)!\aJ'ai arrêté d'essayer de réparer cette vieille dent, et j'ai fait une nouvelle dent en or pour Phil à la place.\aMalheureusement un Chouffleur me l'a dérobée.\aTu peux peut-être le rattraper si tu cours.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu l'as retrouvée, cette dent ?" }, + 5231 : { QUEST : "Super, voilà la dent!\aPourquoi ne filerais-tu pas chez Phil pour lui porter ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je parie que Phil serait content de voir sa nouvelle dent.", + }, + 5232 : { QUEST : "Oh, merci.\aMmmrrrphhhh\aÇa a l'air de quoi, hein ?\aOK, tu peux dire à Paul que je lui pardonne.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Oh, bonne nouvelle.\aJe savais bien que ce vieux Phil ne pourrait pas rester fâché contre moi.\aPour prouver ma bonne volonté, je lui ai fait cuire ce pain de pommes de pin.\aPourrais-tu lui porter, s'il te plaît ?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Presse-toi donc. Le pain de pommes de pin est meilleur chaud.", + COMPLETE : "Oh, qu'est-ce que c'est que ça? Pour moi?\aGromp, gromp...\aAïïïaïïe! Ma dent! Ce Paul Poulemouillée!\aOh, après tout ce n'est pas ta faute. Voilà, prends ça pour ta peine.", + }, + 903 : { QUEST : "Tu dois te préparer à voir _toNpcName_ le vieillard du blizzard pour ton test final._where_", }, + 5234 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du fromage grumeleux pour notre bouillon.\aLe fromage grumeleux ne se trouve que sur les Cogs Gros Blochons.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de fromage grumeleux." }, + 5278 : { GREETING: "", + QUEST : "Aha, te revoilà.\aAvant de commencer, nous devons manger.\aApporte-nous du caviar pour notre bouillon.\aLe caviar ne se trouve que dans les Cogs M. Hollywood.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Nous avons encore besoin de caviar." }, + 5235 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Pillard l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5279 : { GREETING: "", + QUEST : "Un homme ordinaire mange avec une cuillère ordinaire.\aUn Cog a pris ma cuillère ordinaire, donc je ne peux tout simplement pas manger.\aRamène-moi ma cuillère, je crois qu'un Chouffleur l'a prise.", + LEAVING : "", + INCOMPLETE_PROGRESS : "J'ai tout simplement besoin de ma cuillère." }, + 5236 : { GREETING: "", + QUEST : "Merci beaucoup.\aSlurp, slurp...\aAhhh, maintenant tu dois attraper un crapaud parlant. Essaie d'en pêcher dans la mare.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce crapaud parlant ?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu n'as pas encore gagné ton dessert.", + QUEST : "Oh, c'est vraiment un crapaud parlant. Donne-le moi.\aQu'est-ce que tu dis, crapaud?\aCouac.\aCouac.\aLe crapaud a parlé. Nous avons besoin de dessert.\aRapporte-nous des cônes de glace de chez _toNpcName_.\aLe crapaud aime la glace aux haricots rouges pour une raison inconnue._where_", }, + 5238 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour M. Hollywood ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu déjà trouvé tous mes cônes de glace ?" }, + 5280 : { GREETING: "", + QUEST : "Alors c'est le vieillard du blizzard qui t'envoie. Je dois dire qu'on vient de tomber en rupture de stock de cônes de glace aux haricots rouges.\aTu vois, un groupe de Cogs est venu et les a tous emportés.\aIls ont dit qu'ils étaient pour le Gros Blochon ou quelque chose comme ça.\aJe serais ravi si tu pouvais me les rapporter.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé tous mes cônes de glace ?" }, + 5239 : { QUEST : "Merci de m'avoir rapporté mes cônes de glace!\aEn voilà un pour Allan Bic.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tu ferais mieux de porter cette glace à Allan Bic avant qu'elle ne fonde.", }, + 5240 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe pense que ces Cogs Chouffleurs ont quelquefois de la poudre dans leurs perruques.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre ?" }, + 5281 : { GREETING: "", + QUEST : "Très bien. Et voilà mon petit crapaud...\aSlurp, slurp...\aOK, maintenant nous sommes presque prêts.\aSi tu pouvais juste m'apporter de la poudre pour sécher mes mains.\aJe crois que ces Cogs M. Hollywood ont quelquefois de la poudre pour se poudrer le nez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "As-tu trouvé de la poudre ?" }, + 5241 : { QUEST : "OK.\aComme je l'ai déjà dit, pour bien lancer une tarte, tu ne dois pas la lancer avec la main...\a...mais avec ton âme.\aJe ne sais pas ce que cela veut dire, alors je vais m'asseoir et réfléchir pendant que tu récupères des bâtiments.\aReviens quand tu as terminé ton défi.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ton défi n'est pas terminé.", }, + 5242 : { GREETING: "", + QUEST : "Bien que je ne sache toujours pas de quoi je suis en train de parler, tu es vraiment quelqu'un de valeur.\aJe te donne un dernier défi...\aLe crapaud parlant voudrait une petite amie.\aTrouve un autre crapaud parlant. Le crapaud a parlé.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est cet autre crapaud parlant ?", + COMPLETE : "Houlala! Je suis fatigué par tous ces efforts. Je dois me reposer maintenant.\aTiens, prends ta récompense et va t'en." }, + + 5243 : { QUEST : "Pierre Lasueur commence à empester dans la rue.\aPeux-tu essayer de le convaincre de prendre une douche par exemple ?_where_" }, + 5244 : { GREETING: "", + QUEST : "Oui, je crois que je dois commencer à transpirer pas mal.\aMmmm, peut-être que si je pouvais réparer ce tuyau qui fuit dans ma douche...\aJe crois qu'un pignon de l'un de ces tous petits Cogs ferait l'affaire.\aVa trouver un pignon de Microchef et on va essayer.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Où est ce pignon que tu étais parti chercher ?" }, + 5245 : { GREETING: "", + QUEST : "Ouaip, on dirait que ça va.\aMais je me sens seul quand je prends ma douche...\aPourrais-tu aller me pêcher un canard en plastique pour me tenir compagnie ?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors ce canard?" }, + 5246 : { QUEST : "Le canard en plastique est génial, mais...\aTous ces bâtiments tout autour me rendent nerveux.\aJe me sentirais beaucoup plus détendu s'il y avait moins de bâtiments.", + LEAVING : "", + COMPLETE : "Ok, je vais prendre ma douche maintenant. Et voilà aussi quelque chose pour toi.", + INCOMPLETE_PROGRESS : "Je suis toujours embêté au sujet des bâtiments.", }, + 5251 : { QUEST : "Sébastien Toutseul est censé faire un concert ce soir.\aJ'ai entendu dire qu'il pourrait avoir des problèmes avec son matériel._where_" }, + 5252 : { GREETING: "", + QUEST : "Oh ouais! Sûr que j'aurais besoin d'aide.\aCes Cogs sont arrivés et ont piqué tout mon matériel pendant que je déchargeais la camionnette.\aTu pourrais me donner un coup de main pour retrouver mon micro?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Hé mon pote, je ne peux pas chanter sans micro." }, + 5253 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Attactics a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5273 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Circulateurs a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5274 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Usuriers a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5275 : { GREETING: "", + QUEST : "Ouais, c'est bien mon micro.\aMerci de me l'avoir rapporté, mais...\aJ'ai vraiment besoin de mon clavier pour chatouiller les touches.\aJe crois qu'un de ces Avocageots a pris mon clavier.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Alors, mon clavier ?" }, + 5254 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont sûrement aux pieds d'un M Hollywood.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5282 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Gros Blochon.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5283 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Pillard.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + 5284 : { GREETING: "", + QUEST : "Tout va bien! Maintenant je peux travailler.\aSi seulement ils n'avaient pas pris mes chaussures à plate-forme...\aJe parie que mes chaussures sont aux pieds d'un Chouffleur.", + LEAVING : "", + COMPLETE : "Tout va bien! Je suis prêt maintenant.\aVous êtes tous prêts à mettre le feu dans le Glagla ce soir ?\aEh? Où sont-ils?\aOK, prends ça et ramène-moi des fans, d'accord?", + INCOMPLETE_PROGRESS : "Je ne peux pas faire mon spectacle pieds nus, si?" }, + + 5255 : { QUEST : "On dirait que tu as besoin de plus de rigolpoints.\aPeut-être que tu pourrais passer un marché avec _toNpcName_.\aVérifie que c'est fait par écrit..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein ?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Chefbots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Un marché est un marché.", + QUEST : "Alors comme ça tu cherches des rigolpoints, hein ?\aJ'ai un marché pour toi!\aOccupe-toi simplement de quelques Cogs Loibots pour moi...\aEt je te garantis que tu n'y perdras pas." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait ? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Vendibots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Vendibots." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "OK, mais je suis sûr de t'avoir dit de ramasser des Cogs Loibots.\aBon, si tu le dis, mais tu m'es redevable.", + INCOMPLETE_PROGRESS : "Je ne crois pas que tu aies fini.", + QUEST : "Tu dis que c'est fait ? Tu as vaincu tous les Cogs?\aTu as dû mal comprendre, notre marché portait sur des Cogs Caissbots.\aJe suis certain de t'avoir dit de me vaincre des Cogs Caissbots." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "Je ne peux pas t'aider pour les rigolpoints, mais peut-être que _toNpcName_ pourra t'arranger.\aAttention: il est un peu caractériel..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "Je t'ai dit quoi?!?!\aMerci bien! Voilà ton rigolpoint!", + INCOMPLETE_PROGRESS : "Salut!\aQu'est-ce que tu fais encore là?!", + QUEST : "Un rigolpoint? Je ne crois pas!\aSans problème, mais il va d'abord falloir que tu me débarrasses de quelques-uns de ces fichus Loibots." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" est envahi de Cogs très dangereux.\aSi j'étais toi, j'irais là-bas avec plus de gags.\aJ'ai entendu dire que _toNpcName_ peut te faire un grand sac si tu n'as pas peur de marcher._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Il devrait y avoir plein de Loibots par là-bas.\aAlors, vas-y!" , + QUEST : "Un sac plus grand?\aJe pourrais sûrement t'en coudre un en vitesse.\aMais je vais avoir besoin de fil.\aDes Loibots m'ont volé le mien hier matin." }, + 5305 : { GREETING : "Coucou!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Va donc chercher quelques Cogs de plus.\aLa couleur n'a pas encore pris.", + QUEST : "En voilà du beau fil!\aBon, ce n'est pas ma couleur préférée.\aÉcoute-moi bien...\aTu vas là-bas et tu bousilles quelques-uns des Cogs les plus costauds...\aEt pendant ce temps-là, je vais teindre ton fil." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ils doivent bien être quelque part par là...", + QUEST : "Voilà, le fil est teint. Mais nous avons un petit problème.\aJe n'arrive pas à trouver mes aiguilles à tricoter.\aLa dernière fois que je les ai vues, c'était près de la mare." }, + 5307 : { GREETING : "", + LEAVING : "Merci beaucoup!", + INCOMPLETE_PROGRESS : "Rome ne s'est pas tricoté en un jour!" , + QUEST : "Ce sont bien mes aiguilles.\aPendant que je tricote, va faire un peu de nettoyage de Cogs dans ces grands bâtiments.", + COMPLETE : "Excellent travail!\aEt en parlant de bon travail...\aVoilà ton nouveau sac!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai entendu dire que _toNpcName_ a des problèmes avec la justice.\aEst-ce que tu pourrais aller le voir et lui demander?_where_" }, + 5309 : { GREETING : "Je suis content de te voir...", + LEAVING : "", + INCOMPLETE_PROGRESS : "Dépêche-toi! La rue en est envahie!", + QUEST : "Les Loibots ont vraiment pris le pouvoir dans le quartier.\aJ'ai bien peur qu'ils ne me traînent en justice.\aTu pourrais pas les faire dégager de cette rue?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je crois que je les entends qui viennent me chercher...", + QUEST : "Merci. Je me sens un peu mieux.\a Mais il y a autre chose...\aEst-ce que tu pourrais passer chez _toNpcName_ pour me trouver un alibi?_where_" }, + 5311 : { GREETING : "HOUAAA!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Je ne peux pas l'aider si tu n'en trouves pas!", + QUEST : "Un alibi?! Génial!\aTu n'en as pas une autre comme ça?\aJe parie qu'un Avocageot aurait..." }, + 5312 : { GREETING : "Enfin!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "Houlala! Je suis vraiment soulagé d'avoir ça.\aVoilà ta récompense...", + QUEST : "Super! Tu ferais mieux de rapporter ça en vitesse à _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Ali Mentation a besoin d'aide. Peux-tu y faire un saut et lui donner un coup de main ?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, un client! Super! Que puis-je faire pour toi?\aComment ça, que peux-tu faire pour moi? OH! Tu n'es pas un client.\aJe m'en souviens maintenant. Tu es là pour m'aider avec ces affreux Cogs.\aEh bien, ton aide me sera certainement bien utile, même si tu n'es pas un client.\aSi tu nettoies un peu les rues, je te réserve un petit quelque chose.", + INCOMPLETE_PROGRESS : "Si tu ne veux pas d'électricité, je ne peux rien faire pour toi tant que tu n'as pas vaincu ces Cogs.", + COMPLETE : "Bon boulot avec ces Cogs, _avName_.\aTu es vraiment sûr(e) que tu n'as pas besoin d'électricité? Ça pourrait t'être utile...\aNon ? OK, comme tu voudras.\aQuoi? Ah oui, je me souviens. Et voilà. Ça te sera sûrement utile contre ces méchants Cogs.\aContinue à bien travailler!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Eh bien, _avName_, je n'ai rien pour toi pour le moment.\aAttends! Je crois que Susan Sieste cherchait de l'aide. Pourquoi n'irais-tu pas la voir ?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne serai jamais riche avec ces satanés Cogs qui ruinent mes affaires!\aIl faut que tu m'aides, _avName_.\aNettoie quelques bâtiments Cog pour le bien de tout le voisinage et je te rendrai plus riche.", + INCOMPLETE_PROGRESS : "Pauvre de moi! Tu ne peux pas te débarrasser de ces bâtiments?", + COMPLETE : "À moi la fortune! Je vois ça d'ici!\aJe passerai tout mon temps à la pêche. Maintenant, laisse-moi t'enrichir un peu.\aEt voilà!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "Hé _avName_! J'ai entendu dire que Linda Kapok te cherchait.\aTu devrais passer lui rendre visite._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "Bonjour! Waouh, ce que je suis contente de te voir!\aJ'ai passé mon temps à réparer ce répondeur pendant mon temps libre mais il me manque des pièces.\aJ'ai besoin de trois tiges supplémentaires et celles des Pince Menus ont l'air de bien marcher.\aPourrais-tu m'en trouver quelques-unes?", + INCOMPLETE_PROGRESS : "Toujours en train de chercher ces tiges?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, ces tiges iront très bien.\aC'est drôle. J'étais sûre d'avoir une courroie de rechange quelque part mais je n'arrive pas à la trouver.\aPourrais-tu m'en rapporter une de chez Sacasous, s'il te plaît ? Merci!", + INCOMPLETE : "Non, je ne peux pas t'aider avant d'avoir cette courroie.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Voilà, c'est ça. Maintenant ça devrait marcher comme sur des roulettes.\aOù sont passées mes pinces? Je ne peux pas fixer ça sans mes pinces.\aPeut-être qu'une tenaille de Radino ferait l'affaire ?\aSi tu vas m'en chercher une, je te donnerai un petit quelque chose qui t'aidera contre les Cogs.", + INCOMPLETE_PROGRESS : "Toujours pas de tenaille, hein ? Continue à chercher.", + COMPLETE : "Génial! Maintenant il ne me reste plus qu'à fixer tout ça.\aÇa a l'air de marcher maintenant. Me voilà de retour aux affaires!\aEuh, sauf que nous n'avons pas de téléphone. Mais merci quand même de ton aide.\aJe pense que ça t'aidera contre les Cogs. Bonne chance!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "J'ai entendu dire que Rocco avait besoin d'aide. Va voir ce que tu peux faire pour lui._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "Yo! Tu tombes à pic. Moi, ça va pas mieux.\aOuais, j'aurais besoin d'un coup de main avec ces Cogs. Ils sont tout le temps là, à essayer de me donner des leçons.\aSi tu pouvais mettre hors d'état de nuire certains de ces Chefbots, je f'rais en sorte que t'aies pas perdu ton temps.", + INCOMPLETE_PROGRESS : "Eh, _avName_, qu'est-ce que tu fiches?\aFaut qu'tu fasses la chasse à ces Chefbots. On a un accord, tu te rappelles?\aRocco tient toujours sa parole.", + COMPLETE : "Yo, _avName_! Toi, t'es OK pour moi.\aCes Chefbots ils font moins les malins maintenant, pas vrai?\aEh voilà! Un bon petit coup de boost. Maintenant, évite les ennuis, t'entends?", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "Place de la couette, Plume a entendu des rumeurs à propos du quartier général Caissbot.\aVa y faire un tour et vois si tu peux l'aider._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai entendu dire qu'il se passait de drôles de choses.\aBon, c'est peut-être un coup des puces mais il se passe quelque chose de toute façon.\aTous ces Caissbots!\aIJe pense qu'ils ont installé un nouveau quartier général tout près de la Place de la Couette.\aP.J. connaît bien le coin.\aVa voir _toNpcName_ _where_ Demande-lui s'il a entendu quelque chose.", + INCOMPLETE_PROGRESS : "Tu n'as pas encore vu P.J.? Qu'est-ce qui t'en empêche ?\aAh, ces satanées puces!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "Salut _avName_, où vas-tu?\aUn quartier général Caissbot ?? Je n'ai rien vu.\aTu pourrais aller au bout de la place de la Couette et voir si c'est vrai?\aTrouve quelques Caissbots dans leur quartier général, bats-en quelques-uns et reviens me le dire.", + INCOMPLETE_PROGRESS : "Pas encore trouvé le QG? Tu dois y aller, vaincre des Caissbots et voir ce qui s'y passe.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi?! Il y a DÉJÀ un QG Caissbot ?\aTu ferais mieux d'aller tout de suite le dire à Plume!\aQui aurait pu deviner qu'il y aurait un QG Cog à deux pas de sa rue ?", + INCOMPLETE_PROGRESS : "Qu'est-ce que Plume t'a dit ? Tu ne l'as pas encore vu?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis impatient de savoir ce que P.J. a dit.\aHmm... on a besoin de plus d'informations sur cette affaire de Cogs mais je dois me débarrasser de ces puces!\aJe sais! TOI, tu peux essayer d'en savoir plus!\aVa vaincre des Caissbots au QG jusqu'à ce que tu trouves des plans. Après, tu reviens me voir!", + INCOMPLETE_PROGRESS : "Toujours pas de plans? Continue à chercher les Cogs!\aIls doivent avoir des plans!", + COMPLETE : "Tu as les plans?\aGénial! Voyons voir ce qu'ils disent.\aJe vois... Les Caissbots ont construit une Fabrique à Sous pour fabriquer des euros Cog.\aÇa doit être PLEIN de Caissbots. On devrait essayer d'en savoir plus.\aPeut-être que si tu avais un déguisement... Hmmm... attends! Je crois que j'ai une pièce de costume de Cog quelque part par là....\aLa voilà! Prends-la en récompense de tes efforts! Merci encore de ton aide!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "La comtesse te cherchait partout! S'il te plaît, va lui rendre visite, comme ça elle arrêtera d'appeler._where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, je compte sur toi pour m'aider!\aTu vois, ces Cogs font tellement de bruit que je ne peux tout simplement pas me concentrer.\aJe n'arrête pas de perdre le compte de mes moutons!\aSi tu fais diminuer ce bruit, je t'aiderai aussi! Tu peux compter là-dessus!\aBon, où en étais-je ? C'est ça, cent trente-six, cent trente-sept...", + INCOMPLETE_PROGRESS : "Quatre cent quarante-deux... quatre cent quarante-trois...\aQuoi? Tu es déjà de retour ? Mais il y a toujours trop de bruit!\aAh non, j'ai encore perdu le compte.\a Un...deux...trois...", + COMPLETE : "Cinq cent quatre-vingt-treize... cinq cent quatre-vingt-quatorze..\aHello! Ah, je savais que je pouvais compter sur toi! C'est beaucoup plus calme maintenant.\aEt voilà, pour tous ces Gobechiffres.\aLe nombre ? Maintenant il faut que je recommence à compter depuis le début! Un...deux....", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "Ce pauvre père San a cassé son zipper et maintenant il ne peut plus livrer ses clients. Ton aide lui sera certainement utile._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, bonjour _avName_. Tu es là pour m'aider à faire mes livraisons?\aC'est génial! Avec ce zipper cassé, c'est difficile de se déplacer.\aVoyons voir... OK, ça devrait être facile. Ron Chonneau a commandé une cithare la semaine dernière.\aPourrais-tu la lui apporter ? _where_", + INCOMPLETE_PROGRESS : "Ah, salut! Tu as oublié quelque chose ? Ron Chonneau attend sa cithare.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "Ma cithare! Enfin! Bon sang, je suis impatient d'en jouer.\aVa dire au père San que je le remercie, tu veux?", + INCOMPLETE_PROGRESS : "Merci encore pour la cithare. Le père San n'a pas d'autres livraisons pour toi?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "Quelle rapidité! Quelle est la prochaine livraison sur ma liste ?\aBon. Mike Mac a commandé une surfaceuse. Quel drôle de type.\aTu peux la lui apporter, s'il te plaît ?_where_", + INCOMPLETE_PROGRESS : "Cette surfaceuse est pour Mike Mac._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "Super! La surfaceuse que j'avais commandée!\aMaintenant, si seulement il n'y avait pas autant de Cogs dans les environs, je pourrais avoir le temps de m'en servir.\aSois sympa et occupe-toi de certains de ces Caissbots pour moi, tu veux?", + INCOMPLETE_PROGRESS : "Ces Caissbots résistent, hein ? Avec eux, pas facile d'essayer ma surfaceuse.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "Excellent! Maintenant je peux essayer ma surfaceuse.\aS'il te plaît, dis au père San que je viendrai la semaine prochaine passer ma prochaine commande.", + INCOMPLETE_PROGRESS : "C'est tout ce dont j'ai besoin pour le moment. Est-ce que le père San n'est pas en train de t'attendre ?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "Alors, est-ce que Mike Mac a été content de sa surfaceuse ? Génial.\aÀ qui le tour ? Ah, Olivier Daure a commandé un coussin zèbre.\aLe voilà! Pourrais-tu faire un saut chez lui, s'il te plaît ?_where_", + INCOMPLETE_PROGRESS : "Je crois qu'Olivier Daure a besoin de ce coussin pour méditer.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, mon coussin, enfin. Maintenant je peux méditer.\aComment se concentrer avec un tel vacarme ? Tous ces Cogs!\aComme tu es là, peut-être que tu pourrais t'occuper de certains de ces Cogs?\aAprès ça je pourrai utiliser mon coussin en paix.", + INCOMPLETE_PROGRESS : "Il y a toujours tellement de bruit avec ces Cogs! Comment se concentrer ?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "La paix et le calme, enfin. Merci, _avName_.\aS'il te plaît, va dire au père San que je suis très content. OMMM....", + INCOMPLETE_PROGRESS : "Le père San t'as appelé. Tu devrais aller voir ce qu'il veut.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis heureux de voir qu'Olivier Daure est content de son coussin zèbre.\aOh, ces zinnias viennent juste d'arriver pour Eva Sandor-Mir.\aComme tu as l'air d'être un livreur zélé, peut-être que tu pourrais les lui apporter ?_where_", + INCOMPLETE_PROGRESS : "Ces zinnias vont faner si tu ne les livres pas rapidement.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "Quels jolis zinnias! Ca c'est sûr, le père San s'y connaît en livraison.\aOh, eh bien, je suppose que c'est TOI qui fais les livraisons, _avName_. Tu remercieras le père San pour moi!", + INCOMPLETE_PROGRESS : "N'oublie pas de remercier le père San pour les zinnias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Te voilà de retour, _avName_. Tu es sacrément rapide.\aVoyons... Quelle est la prochaine livraison sur ma liste ? Des disques de Zydeco pour Thérèse Eveillé._where_", + INCOMPLETE_PROGRESS : "Je suis sûr que Thérèse Eveillé attend ses disques de Zydeco.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "Des disques de Zydeco? Je ne me rappelle pas avoir commandé de disques de Zydeco.\aOh, je parie que c'est Lou Laberceuse qui les a commandés._where_", + INCOMPLETE_PROGRESS : "Non, ces disques de Zydeco sont pour Lou Laberceuse._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, enfin, mes disques de Zydeco! Je pensais que le père San avait oublié.\aPourrais-tu lui apporter cette courgette ? Il trouvera bien quelqu'un qui en veut une. Merci!", + INCOMPLETE_PROGRESS : "Oh, j'ai déjà plein de courgettes. Apporte-la au père San.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "Une courgette ? Hmm. Eh bien, je trouverai sûrement quelqu'un qui en voudra.\aOK, nous avons presque fini ma liste. Plus qu'une livraison à faire.\aBébé MacDougal a commandé un costume zazou._where_", + INCOMPLETE_PROGRESS : "Si tu ne livres pas ce costume zazou à Bébé MacDougal,\a il va être tout froissé.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Il était une fois... oh! Tu n'es pas là pour écouter une histoire, hein ?\aTu es là pour me livrer mon costume zazou? Super! Waouh, c'est quelque chose.\aEh, tu pourrais transmettre un message au père San pour moi? J'aurais besoin de boutons de manchette en zircon pour aller avec le costume. Merci!", + INCOMPLETE_PROGRESS : "Tu as transmis mon message au père San ?", + COMPLETE : "Des boutons de manchette en zircon, hein ? Eh bien, je vais voir ce que je peux faire pour lui.\aBon, tu m'as été d'une aide précieuse et je ne peux pas te laisser partir sans rien.\aVoici un GROS coup de boost pour t'aider à zapper ces Cogs!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "Dave Bigleau a des problèmes et tu peux peut-être l'aider. Pourquoi ne pas passer à sa boutique ?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi? Hein ? Oh, j'ai dû m'endormir.\aTu sais, ces bâtiments Cog sont remplis de machines qui me donnent vraiment sommeil.\aJe les entends ronronner toute la journée et...\aHein ? Ah, ouais, d'accord. Si tu pouvais te débarrasser de certains de ces bâtiments Cog, je pourrais rester éveillé.", + INCOMPLETE_PROGRESS : "Zzzzz...hein ? Oh, c'est toi, _avName_.\aDéjà de retour ? Je faisais juste une petite sieste.\aReviens quand tu en auras fini avec ces bâtiments.", + COMPLETE : "Quoi? Je me suis juste assoupi une minute.\aMaintenant que ces bâtiments Cog ont disparu, je peux enfin me détendre.\aMerci de ton aide, _avName_.\aA plus tard! Je crois que je vais faire un petit somme.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Va voir Teddy Blaireau. Il a un boulot pour toi._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "Qu'est-ce que tu dis? Non, je n'ai pas de goulot pour toi.\aOh, un boulot! Pourquoi ne pas l'avoir dit plus tôt ? Il faudrait que tu parles plus fort.\aAvec ces Cogs, ce n'est pas facile d'hiberner. Si tu ramènes un peu de calme au Pays des Rêves,\aje te donnerai un petit quelque chose.", + INCOMPLETE_PROGRESS: "Tu as vaincu les bogs? Quels bogs?\aOh, les Cogs! Pourquoi ne pas l'avoir dit plus tôt ?\aHmm, il y a encore pas mal de bruit. Pourquoi ne pas en vaincre quelques autres?", + COMPLETE : "Tu t'es bien amusé? Hein ? Oh!\aTu as fini! Super. C'est sympa de ta part de donner un coup de main comme ça.\aJ'ai trouvé ça dans la pièce du fond mais ça ne m'est d'aucune utilité.\aPeut-être que tu pourras en faire quelque chose. À plus, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece + 6291 : { QUEST : "Les Cogs ont pénétré dans la Banque du Doudou d'Or! Va voir Laurent Lauronpat et vois si tu peux l'aider.", + }, + 6292 : { QUEST : "Ah ces satanés Caissbots! Ils ont volé mes lampes de lecture!\aJ'en ai besoin tout de suite. Tu peux aller les chercher ?\aSi tu me rapportes mes lampes de lecture, je pourrai peut-être t'aider à rencontrer le Vice-Président.\aFais vite!", + INCOMPLETE_PROGRESS : "Il me faut ces lampes. Continue de les chercher!", + COMPLETE : "Te voilà revenu! Et tu as mes lampes!\aJe ne peux pas te remercier comme il le faudrait mais je peux te donner ça.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Nina Lamparo te cherchait, _avName_. Elle a besoin d'aide._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Je suis si contente de te voir, _avName_. J'aurais bien besoin d'aide!\aCes fichus Cogs ont chassé les livreurs et je n'ai plus aucun lit en stock.\aPeux-tu aller voir Amédé Brouilletoitoutseul et me rapporter un lit ?_where_", + INCOMPLETE_PROGRESS : "Amédé n'avait pas de lit ? J'étais sûre qu'il en avait un.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "Un lit ? Bien sûr, en voilà un de prêt.\aApporte-le-lui pour boi, tu veux? Tu as compris? Pour \a\" BOIS \"? Hi-hi!\aTrès drôle, non ? Eh bien, amène-le quand même là-bas s'il te plaît.", + INCOMPLETE_PROGRESS : "Est-ce que le lit a plu à Nina?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "Ce lit ne convient pas. Il est beaucoup trop ordinaire.\aVa voir s'il a quelque chose de plus fantaisie, tu veux?\aJe suis sûre que ça ne te prendra qu'une minute.", + INCOMPLETE_PROGRESS : "Je suis sûre qu'Amédé a un lit plus fantaisie.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "On n'est pas tombé pile avec ce lit, hein ? J'en ai un ici qui devrait faire l'affaire.\aMais il y a un petit problème - il faut d'abord l'assembler.\aPendant que je m'en charge avec mon marteau, pourrais-tu te débarrasser de certains des Cogs, là-dehors?\aCes affreux Cogs ruinent mon travail.\aReviens quand tu auras fini et le lit sera prêt.", + INCOMPLETE_PROGRESS : "Je n'ai pas tout à fait fini d'assembler le lit.\aQuand tu en auras fini avec les Cogs, il sera prêt.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "Salut _avName_!\aTu as fait du sacré bon boulot avec ces Cogs.\aLe lit est prêt. Pourrais-tu le livrer pour moi?\aMaintenant que tous ces Cogs sont partis, les affaires vont reprendre!", + INCOMPLETE_PROGRESS : "Je pense que Nina attend la livraison de ce lit.", + COMPLETE : "Quel joli lit!\aMaintenant mes clients vont être contents. Merci, _avName_.\aTiens, ceci pourra peut-être t'être utile. Quelqu'un l'a laissé ici.", + }, + 7209 : { QUEST : "Va voir Rosée de Lune. Elle a besoin d'aide._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "Oh! Comme je suis contente de te voir, _avName_. J'ai vraiment besoin d'aide!\aJe n'ai pas eu mon compte de sommeil depuis bien longtemps. Tu vois, les Cogs m'ont volé mon dessus-de-lit.\aTu pourrais faire un saut voir si Ed n'aurait rien dans les tons bleus?_where_", + INCOMPLETE_PROGRESS : "Qu'est-ce qu'Ed a dit à propos de ce dessus-de-lit bleu?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "Alors comme ça, Rosée veut un dessus-de-lit, hein ?\aDe quelle couleur ? BLEU?!\aEh bien, je vais devoir le fabriquer spécialement pour elle. Tout ce que j'ai, c'est du rouge.\aTu sais quoi? Si tu vas t'occuper de certains des Cogs là-dehors, je fabriquerai un dessus-de-lit bleu spécialement pour elle.\aDes dessus-de-lit bleus... et puis quoi encore ?", + INCOMPLETE_PROGRESS : "Je travaille toujours sur ce dessus-de-lit bleu, _avName_. Continue de t'occuper de ces Cogs!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te revoir. J'ai quelque chose pour toi!\aVoilà le dessus-de-lit et il est bleu. Elle va l'adorer.", + INCOMPLETE_PROGRESS : "Est-ce que Rosée a aimé le dessus-de-lit ?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "C'est mon dessus-de-lit ? Non, ça ne va pas.\aC'est un tissu ÉCOSSAIS! Qui pourrait dormir avec un motif aussi CRIARD?\aTu vas devoir le rapporter et m'en ramener un autre.\aJe suis sûre qu'il en a d'autres.", + INCOMPLETE_PROGRESS : "Il est hors de question que j'accepte un dessus-de-lit écossais. Va voir ce qu'Ed peut faire.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "Quoi? Elle n'aime pas l'ÉCOSSAIS?\aHmm... Voyons ce que nous avons par ici.\aÇa va prendre un certain temps. Pourquoi tu n'irais pas t'occuper de quelques Cogs pendant que j'essaie de trouver autre chose ?\aJ'aurai trouvé quand tu reviendras.", + INCOMPLETE_PROGRESS : "Je suis toujours en train de chercher un autre dessus-de-lit. Comment ça se passe avec les Cogs?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "Hé, bon travail avec ces Cogs!\aEt voilà, il est bleu et il n'est pas écossais.\aReste à espérer qu'elle aime le cachemire.\aApporte ce dessus-de-lit à Rosée.", + INCOMPLETE_PROGRESS : "C'est tout ce que j'ai pour toi pour l'instant.\aS'il te plaît, va apporter ce dessus-de-lit à Rosée.", + COMPLETE : "Oh! Que c'est joli! Le cachemire me va vraiment bien.\aIl est temps pour moi de prendre un peu de repos! À plus tard, _avName_.\aQuoi? Tu es encore là? Tu ne vois pas que j'essaie de dormir ?\aTiens, prends ça et laisse-moi me reposer. Je dois être à faire peur!", + }, + + 7218 : { QUEST : "Daphné Puisé aurait bien besoin d'un coup de main._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, _avName_, je suis contente de te voir! Les Cogs ont pris mes oreillers.\aPourrais-tu aller voir si Pierrot en a?_where_\aJe suis sûre qu'il peut m'aider.", + INCOMPLETE_PROGRESS : "Est-ce que Pierrot a des oreillers pour moi ?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "Salut! Daphné a besoin d'oreillers, hein ? Eh bien, tu as frappé à la bonne porte, partenaire!\aIl y a plus d'oreillers ici que d'épines sur un cactus.\aEt voilà, _avName_. Apporte-les à Daphné, avec mes compliments.\aToujours heureux de donner un coup de main à une demoiselle.", + INCOMPLETE_PROGRESS : "Ces oreillers sont-ils assez doux pour cette jeune dame ?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as les oreillers! Génial!\aEh, attends une seconde! Ces oreillers sont affreusement mous.\aBeaucoup trop mous pour moi. J'ai besoin d'oreillers plus durs.\aRamène-les à Pierrot et vois ce qu'il a d'autre. Merci.", + INCOMPLETE_PROGRESS : "Non! Trop mous. Demande d'autres oreillers à Pierrot.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Trop mous, hein ? Eh bien, laisse-moi voir ce que j'ai d'autre....\aHmm... Il me semblait que j'avais un bon paquet d'oreillers durs. Où sont-ils passés?\aOh! Je me rappelle. Je pensais les renvoyer, donc ils sont à l'entrepôt.\aPourquoi tu ne nettoierais pas quelques bâtiments Cog là-dehors pendant que je les sors de l'entrepôt, partenaire ?", + INCOMPLETE_PROGRESS : "Dur, dur les bâtiments Cog. C'est pas comme ces oreillers.\aContinue à chercher.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "Déjà de retour ? Eh bien, c'est parfait. Tu vois, j'ai trouvé les oreillers que Daphné voulait.\aMaintenant, va les lui apporter. Ils sont tellement durs qu'on s'y casserait les dents!", + INCOMPLETE_PROGRESS : "Ouais, ces oreillers sont bien durs. J'espère qu'ils plairont à Daphné.", + COMPLETE : "Je savais bien que Pierrot aurait des oreillers plus durs.\aAh oui, ils sont parfaits. Bien durs, juste comme je les aime.\aTu aurais besoin de cette pièce de costume de Cog? Tu n'as qu'à la prendre.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Passe voir Sandie Marchand. Elle a perdu son pyjama._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "Je n'ai plus de pyjama! Je ne le trouve plus!\aQu'est-ce que je vais faire ? Oh! Je sais!\aVa voir Big Mama. Elle aura sûrement un pyjama pour moi._where_", + INCOMPLETE_PROGRESS : "Est-ce que Big Mama a un pyjama pour moi?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "Te voilà, petit Toon! Big Mama a les plus beaux pyjamas des Bahamas.\aOh, quelque chose pour Sandie Marchand, hein ? Bon, voyons voir ce que j'ai.\aVoilà un petit quelque chose. Maintenant elle peut dormir en toute élégance!\aVoudrais-tu courir le lui apporter pour moi? Je ne peux pas quitter la boutique pour l'instant.\aMerci, _avName_. À plus tard!", + INCOMPLETE_PROGRESS : "Tu dois apporter ce pyjama à Sandie._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "C'est Big Mama qui me l'envoie ? Oh...\aEst-ce qu'elle n'a pas de pyjama avec des pieds?\aJe porte toujours des pyjamas avec des pieds. Comme tout le monde, non ?\aRamène celui-là et demande-lui de m'en trouver un avec des pieds.", + INCOMPLETE_PROGRESS : "Mon pyjama doit avoir des pieds. Va voir si Big Mama peut m'aider.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "Des pieds? Laisse-moi réfléchir....\aAttends un peu! J'ai ce qu'il te faut!\aTa-dam! Un pyjama avec des pieds. Une jolie grenouillère bleue avec des pieds. La meilleure de toutes les îles.\aS'il te plaît, va-la-lui porter, tu veux? Merci!", + INCOMPLETE_PROGRESS : "Est-ce que Sandie a aimé la grenouillère bleue ?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "OK, elle a EFFECTIVEMENT des pieds, mais je ne peux pas porter une grenouillère bleue!\aDemande à Big Mama si elle n'a pas une autre couleur.", + INCOMPLETE_PROGRESS : "Je suis sûre que Big Mama a une grenouillère d'une autre couleur.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "Quel dommage. C'est la seule grenouillère que j'aie.\aOh, j'ai une idée. Va demander à Tartine. Elle aura peut-être des pyjamas avec des pieds._where_", + INCOMPLETE_PROGRESS : "Non, ce sont les seuls pyjamas que j'aie. Va voir si Tartine en a._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "Des pyjamas avec des pieds? Bien sûr.\aQu'est-ce que tu veux dire, il est bleu? Elle n'aime pas le bleu?\aOh, alors là, c'est plus compliqué. Tiens, essaie ça.\aIl n'est pas bleu et il A des pieds.", + INCOMPLETE_PROGRESS : "Moi j'adore la couleur puce, pas toi?\aJ'espère que Sandie l'aimera....", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "Non, il n'est pas bleu mais personne avec mon teint ne peut porter de couleur puce.\aAbsolument impossible. Retourne là-bas et rapporte-le! Va voir ce que Tartine a d'autre.", + INCOMPLETE_PROGRESS : "Tartine doit avoir d'autres pyjamas. La couleur puce, hors de question pour moi!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Pas de puce non plus. Hmm....\aPar ma barbe, je sais que j'en ai d'autres.\aIl va me falloir un moment pour les trouver. Faisons un marché.\aJe cherche d'autres grenouillères si tu te débarrasses de quelques bâtiments Cog. Ils sont vraiment gênants.\aLa grenouillère sera prête quand tu reviendras, _avName_.", + INCOMPLETE_PROGRESS : "Tu dois éliminer d'autres bâtiments Cog pendant que je cherche d'autres grenouillères.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as fait de l'excellent travail avec ces Cogs! Merci!\aJ'ai trouvé cette grenouillère pour Sandie, j'espère que ça lui plaira.\aApporte-la-lui. Merci.", + INCOMPLETE_PROGRESS : "Sandie attend sa grenouillère, _avName_.", + COMPLETE : "Une grenouillère fuchsia! Parr-fait!\aAh, maintenant je suis parfaitement bien. Voyons voir....\aOh, je suppose que je devrais te donner quelque chose pour te remercier de ton aide.\aPeut-être que ceci te sera utile. Quelqu'un l'a laissé ici.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Va voir Emma Scara. Elle demande de l'aide._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "Ces satanés Cogs ont pris ma crème antirides!\aMes clients DOIVENT absolument avoir de la crème antirides quand je m'occupe d'eux.\aVa voir Honoré et demande-lui s'il a toujours ma recette spéciale en stock._where_", + INCOMPLETE_PROGRESS : "Je refuse de m'occuper de quelqu'un qui n'a pas de crème antirides.\aVa voir si Honoré en a.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "Oh, cette Emma n'est pas facile. Elle ne se contente pas de ma recette habituelle.\aCe qui veut dire que je vais avoir besoin de corail chou-fleur, mon ingrédient spécial ultrasecret. Mais je n'en ai pas en stock.\aPourrais-tu aller m'en pêcher dans l'étang? Dès que tu auras le corail, je préparerai un mélange de crème pour Emma.", + INCOMPLETE_PROGRESS : "J'ai besoin de corail chou-fleur pour préparer ma crème antirides.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "Waouh, quel beau corail chou-fleur!\aOk, voyons voir... Un peu de ceci et une petite giclée de cela... Et maintenant, une cuillerée d'algues.\aHé, où sont les algues? On dirait que je suis aussi à court d'algues.\aPeux-tu faire un saut à l'étang et me ramasser une belle algue gluante ?", + INCOMPLETE_PROGRESS : "Plus un brin d'algue gluante dans cette boutique.\aImpossible de préparer la crème sans algue.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "Oooh! Voilà une algue gluante à souhait, _avName_.\aMaintenant, je vais juste écraser quelques perles dans le mortier avec le pilon.\aHum, où est mon pilon ? À quoi sert un mortier sans un pilon ?\aJe parie que ce fichu Usurier l'a pris quand il est venu ici!\aIl faut que tu m'aides à le trouver! Il se dirigeait vers le QG Caissbot!", + INCOMPLETE_PROGRESS : "Je ne peux tout simplement pas écraser mes perles sans un pilon.\aFichus Usuriers!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "Parfait! Tu as mon pilon!\aMaintenant on va pouvoir travailler. Écraser ça... Touiller un peu et...\aÇa y est! Va dire à Emma que c'est de la bonne crème, fraîchement préparée.", + INCOMPLETE_PROGRESS : "Tu devrais apporter cette crème à Emma tant qu'elle est encore fraîche.\aC'est une cliente très difficile.", + COMPLETE : "Honoré n'avait pas un pot de crème antirides plus gros que ça? Non ?\aEh bien, je suppose qu'il faudra simplement que j'en recommande quand je n'en aurai plus.\aÀ un de ces quatre, _avName_.\aQuoi? Tu es toujours là? Tu ne vois pas que j'essaie de travailler ?\aTiens, prends ça.", + }, + +# Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu es intéressé par les pièces de déguisement de Loibot, tu devrais aller voir _toNpcName_.\aJ'ai entendu dire qu'il a grand besoin d'aide pour ses recherches météorologiques._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Oui, oui. J'ai des pièces de déguisement de Loibot.\aMais elles sont sans intérêt pour moi.\aMes recherches portent sur les fluctuations de la température ambiante de Toontown.\aJ'échangerais volontiers des pièces de déguisement contre des sondes de température Cog.\aTu peux commencer par aller voir %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "Ah, parfait!\aC'est ce que je craignais...\aOh, oui! Voilà ta pièce de déguisement.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "Pour obtenir d'autres pièces de déguisement de Loibot, tu devrais retourner voir _toNpcName_.\aJ'ai entendu dire qu'il avait besoin d'assistants de recherche._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "Plus de pièces de déguisement de Loibot?\aBon, si tu insistes...\amais j'ai besoin d'une autre sonde de température Cog.\aCette fois-ci, va sur %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[2200][-1], + COMPLETE : "Merci!\aVoilà ta pièce de déguisement.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as besoin de pièces de déguisement Loibot supplémentaires, tu devrais retourner voir _toNpcName_.\aApparemment, il a toujours besoin d'aide pour ses recherches météorologiques._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais te montrer utile!\aEst-ce que tu peux aller jeter un oeil sur %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : " Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[2300][-1], + COMPLETE : "Hmmm, je n'aime pas trop ça...\amais voici ta pièce de déguisement...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : " Qui-tu-sais a besoin de relevés de température supplémentaires.\aPasse le voir si tu veux une autre pièce de déguisement._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "Encore toi?\aTu as vraiment envie de travailler...\aLa prochaine destination, c'est %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "Bon! On dirait que tu t'en sors plutôt bien!\aTa pièce de déguisement...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as envie d'une autre pièce de déguisement de Loibot..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te trouver ici!\aMaintenant, j'ai besoin de relevés sur %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[1200][-1], + COMPLETE : "Merci beaucoup!\aTu n'es probablement pas loin d'avoir tout ton déguisement...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "Je crois que _toNpcName_ a encore du travail pour toi._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : " Content de te revoir, _avName_!\aEst-ce que tu peux faire un relevé sur %s?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "Super boulot!\aVoici ta récompense. Tu l'as bien méritée!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais ce qu'il faut faire._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, mon ami!\aEst-ce que tu pourrais aller à %s et me trouver une autre sonde de température?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu es vraiment en train de chercher sur %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "Excellent!\aGrâce à ton aide, mes recherches avancent très vite!\aVoici ta récompense.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a parlé de toi.aOn dirait que tu as fait une sacrée impression!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "Content de te revoir!\aJe t'attendais.\aLe prochain relevé dont j'ai besoin, c'est sur %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[5200][-1], + COMPLETE : "Merci!\aVoici ta récompense.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu as besoin de terminer ton déguisement de Loibot...\a_toNpcName_ peut t'aider._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "Salut, jeune chercheur!\aNous avons encore besoin de relevés de %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "Excellent travail!\aVoilà ton machin de Loibot...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a un autre travail pour toi.\aSi tu n'en as pas assez de le voir..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Bon, très bien.\aTu te sens d'attaque pour aller chercher autre chose?\aCette fois-ci, essaie %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[4100][-1], + COMPLETE : "Et un de plus!\aQuelle efficacité!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "Tu es toujours à la recherche de pièces de déguisement de Loibot?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "Tu as sans doute déjà deviné...\amais j'ai besoin de relevés de %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[4200][-1], + COMPLETE : "On y est presque!\aEt voilà...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai presque honte de le dire, mais..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "Qu'est-ce que tu penses de %s? Est-ce que tu crois que tu pourrais aller chercher une sonde là-bas aussi?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "Est-ce que tu as essayé de chercher sur %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Encore du bon travail, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Va voir le Professeur, si tu as encore besoin de pièces de déguisement._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "Je crois qu'on a encore besoin d'un relevé de %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "Tu es bien en train de chercher sur %s ?" % GlobalStreetNames[9100][-1], + COMPLETE : "Bon travail!\aJe crois qu'on se rapproche...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a une dernière mission pour toi._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "Déjà de retour?\aLe dernier relevé est sur %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "Tu cherches bien sur %s ?" % GlobalStreetNames[9200][-1], + COMPLETE : "Ça y est enfin!\aMaintenant, tu vas pouvoir t'introduire dans le bureau du Procureur et ramasser des convocations du jury.\aBonne chance et merci pour ton aide!", + }, + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "If you are interested in Bossbot disguise parts you should visit _toNpcName_._where_", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "Oui, je peux te trouver des pièces de Chefbot.\aMais tu devras m'aider à terminer ma collection de Chefbot.\aVa défier un Laquaistic.", + INCOMPLETE_PROGRESS : "Tu n'as pas trouvé de Laquaistic ? Quel dommage !", + COMPLETE : "Tu n'as pas été recalé j'espère ?\aVoici ta première pièce de déguisement.", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a encore besoin d'aide, si ça te dit._where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "Une autre pièce de déguisement ?\aAbsolument...\amais seulement si tu parviens à vaincre un Gratte-papier.", + INCOMPLETE_PROGRESS : "Les Gratte-papiers sont dans les rues.", + COMPLETE : "Un vrai jeu d'enfant !\aVoici ta seconde pièce de déguisement.", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "Il y a vraiment un seul endroit où trouver des pièces de Chefbot._where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "Maintenant j'ai besoin d'un Béniouioui...", + INCOMPLETE_PROGRESS : "Les Béniouiouis sont dans les rues.", + COMPLETE : "Super, l'ami, tu es bon.\aVoici ta troisième pièce de déguisement.", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a d'autres pièces pour toi...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu triomphes d'un Microchef, je te donnerai une autre pièce de déguisement.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[1100][-1], + COMPLETE : "Tu t'es bien défendu !\aVoici ta quatrième pièce de déguisement.", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "Rends-toi dans..._where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "Je suis à la recherche d'un Touptisseur maintenant...", + INCOMPLETE_PROGRESS : "Des problèmes ? Va voir dans %s" % GlobalStreetNames[3100][-1], + COMPLETE : "Quelle terrible défaite !\aVoici ta cinquième pièce de déguisement.", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "Je pense que tu sais où aller maintenant..._where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "Prochain sur ma liste : un Chassetête.", + INCOMPLETE_PROGRESS : "Tu auras peut-être plus de chance en allant faire un tour dans les bâtiments.", + COMPLETE : "Je vois que tu n'as eu aucun mal à en trouver un.\aVoici ta sixième pièce de déguisement.", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ a besoin de plus de Chefbots.", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "À présent, j'ai besoin que tu me déniches un Attactic.", + INCOMPLETE_PROGRESS : "Tu auras peut-être plus de chance en allant faire un tour dans les bâtiments.", + COMPLETE : "Tu fais un excellent chasseur !\aVoici ta septième pièce de déguisement.", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu souhaites obtenir plus de pièces de déguisement, va dans..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "Et maintenant, le coup de grâce : le Gros Blochon !", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Je savais que je pouvais compter sur toi pour...\apeu importe !\aVoici une autre pièce de déguisement.", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ était à ta recherche...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "À présent, j'aimerais que tu t'attaques à l'un des nouveaux Cogs Chefbot, qui sont plus dangereux.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Ils sont plus forts qu'ils en ont l'air, hein ?\aEnfin, je te dois quand même une pièce de déguisement.", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "Pourrais-tu aller faire un tour à..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "Ces Cogs version 2.0 sont très intéressants.\aS'il te plaît, va en attaquer un autre.", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Merci !\aUne autre pièce de déguisement pour toi.", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "Si tu peux, arrête-toi et va voir _toNpcName_.", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "Je me demande s'ils peuvent se régénérer...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "J'imagine que non.\aVoici ta pièce de déguisement...", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "Peut-être que ce ne sont pas des Chefbots du tout...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Finalement, je crois bien que ce sont des Chefbots.\aChoisis une autre pièce de déguisement.", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "Tu sais sans doute déjà ce que je vais te dire...", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "Peut-être que d'une certaine manière, ils sont parents avec les Skelecogs...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Ce fut peu concluant...\aVoici ta pièce de déguisement.", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "S'il te plaît, retourne voir _toNpcName_.", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "Je ne suis toujours pas convaincu qu'il ne s'agit pas d'une espèce de Skelecog...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Peut-être pas.\aVoici une autre pièce de déguisement.", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "C'est sans doute l'endroit où tu as le moins envie d'aller, mais...", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "Ces nouveaux Cogs me laissent vraiment perplexe.\aPourrais-tu aller en attaquer un autre, s'il te plaît ?", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Vraiment fascinant.\aUne pièce de déguisement pour te remercier de tes efforts.", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ commence à sonner comme un disque rayé ...", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "J'ai presque trouvé ce que sont ces Cogs.\aJuste encore un...", + INCOMPLETE_PROGRESS : "Va voir dans %s" % GlobalStreetNames[10000][-1], + COMPLETE : "Oui, je crois que je suis sur la bonne voie.\aAh oui.\aÇa, c'est pour toi...", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "Tu dois aller raconter tout ça à Flippy...", + INCOMPLETE_PROGRESS : "Flippy se trouve dans Toon Hall", + COMPLETE : "Une nouvelle espèce de Cog !\aBon travail !\aVoici ta dernière pièce de déguisement.", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["ouaf", "ouarf", "rrrgh"] +ChatGarblerCat = ["miaou", "maaou"] +ChatGarblerMouse = ["couic", "couiiic", "iiiiic"] +ChatGarblerHorse = ["hihiii", "brrr"] +ChatGarblerRabbit = ["ouic", "pouip", "plouik", "bouip"] +ChatGarblerDuck = ["coin", "couac", "coiinc"] +ChatGarblerMonkey = ["oh", "hou", "ah"] +ChatGarblerBear = ["grrr", "grrr"] +ChatGarblerPig = ["rrrrr", "ouing", "ouing"] +ChatGarblerDefault = ["blabla"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Recherche de coordonnées pour %s." +AvatarDetailPanelFailedLookup = "Impossible d'obtenir les coordonnées de %s." +AvatarDetailPanelPlayer = "Joueur : %(player)s\nMonde : %(world)s" +AvatarDetailPanelPlayerShort = "%(player)s\nMonde : %(world)s\nLieu : %(location)s" +AvatarDetailPanelRealLife = "Hors ligne" +AvatarDetailPanelOffline = "District: hors-ligne\nLieu : hors-ligne" +AvatarDetailPanelOnline = "District : %(district)s\nLieu : %(location)s" +AvatarDetailPanelOnlinePlayer = "District : %(district)s\nLieu : %(location)s\nJoueur : %(player)s" +AvatarDetailPanelOffline = "District: hors-ligne\nLieu : hors-ligne" +AvatarShowPlayer = "Montrer joueur" +OfflineLocation = "Hors ligne" + +#PlayerDetailPanel +PlayerToonName = "Toon : %(toonname)s" +PlayerShowToon = "Montrer Toon" +PlayerPanelDetail = "Informations joueur" + + +# AvatarPanel.py +AvatarPanelFriends = "Contacts" +AvatarPanelWhisper = "Chuchoter" +AvatarPanelSecrets = "Secrets" +AvatarPanelGoTo = "Aller à" +AvatarPanelPet = "Montrer le Doudou" +AvatarPanelIgnore = "Ignorer" +AvatarPanelIgnoreCant = "OK" +AvatarPanelStopIgnoring = "Arrête d'ignorer" +AvatarPanelReport = "Signaler" +#AvatarPanelCogDetail = "Dépt : %s\nNiveau: %s\n" +AvatarPanelCogLevel = "Niveau: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Détails du Toon" +AvatarPanelGroupInvite = "Inviter dans le groupe" +AvatarPanelGroupRetract = "Retirer invitation" +AvatarPanelGroupMember = "Déjà dans le groupe" +AvatarPanelGroupMemberKick = "Kick Out" + +# grouping messages +groupInviteMessage = "%s aimerait que tu rejoignes son groupe" + + +# Report Panel +ReportPanelTitle = "Signaler un joueur" +ReportPanelBody = "Cette fonction permet d'envoyer un rapport complet à un modérateur. Au lieu d'envoyer un rapport, tu peux opter pour l'une des options suivantes :\n\n - Te téléporter dans un autre district\n - Utiliser \" Ignorer \" sur le panneau de commande du Toon\n\nSouhaites-tu vraiment signaler %s à un modérateur ?" +ReportPanelBodyFriends = "Cette fonction permet d'envoyer un rapport complet à un modérateur. Au lieu d'envoyer un rapport, tu peux opter pour l'une des options suivantes :\n\n - Te téléporter dans un autre district \n - Rompre votre amitié\n\n Souhaites-tu vraiment signaler %s à un modérateur ?\n\n(Cela rompra également votre amitié)" +ReportPanelCategoryBody = "Tu es sur le point de signaler %s. Un modérateur sera alerté de ta plainte et prendra les mesures appropriées envers toute personne ayant enfreint nos règles. Sélectionne la raison pour laquelle tu signales %s:" +ReportPanelBodyPlayer = "Cette fonction est en cours de développement et sera bientôt disponible. En attendant, tu peux :\n\n - Te rendre dans DXD pour rompre votre amitié.\n - Raconter ce qui s'est passé à un parent." + +ReportPanelCategoryLanguage = "Propos grossiers" +ReportPanelCategoryPii = "Partage ou demande d'informations personnelles" +ReportPanelCategoryRude = "Impoli ou méchant" +ReportPanelCategoryName = "Mauvais nom" + +ReportPanelConfirmations = ( + "Tu es sur le point de signaler que %s a utilisé des propos obscènes, sectaires ou sexuellement explicites.", + "Tu es sur le point de signaler que %s n'est pas prudent, et donne ou demande un numéro de téléphone, une adresse, un nom de famille, une adresse e-mail, un mot de passe ou un nom de compte.", + "Tu es sur le point de signaler que %s tyrannise, harcèle ou manifeste un comportement extrême pour perturber le jeu.", + "Tu es sur le point de signaler que %s a créé un nom qui n'est pas conforme aux Règles du jeu de Disney.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "Nous prenons les rapports très au sérieux. Ton rapport sera examiné par un modérateur, qui prendra les mesures appropriées envers toute personne ayant enfreint nos règles. S'il s'avère que ton compte a lui-même enfreint les règles, ou si tu fais de faux signalements ou utilises la fonction « Signaler un joueur » de manière abusive, un modérateur pourrait prendre des mesures contre ton compte. Es-tu sûr de vouloir signaler ce joueur ?" + +ReportPanelThanks = "Merci. Ton rapport a été envoyé à un modérateur qui se chargera de son examen. Tu n'as pas besoin de nous contacter à nouveau concernant cet incident. L'équipe chargée de la modération prendra les mesures qui s'imposent à l'encontre de tout joueur ayant enfreint nos règles." + +ReportPanelRemovedFriend = "Nous avons automatiquement supprimé %s de ta Liste d'amis." + +ReportPanelAlreadyReported = "Tu as déjà signalé %s durant cette session. Un modérateur se chargera d'examiner ton précédent rapport." + +# Report Panel +IgnorePanelTitle = "Ignorer un joueur" +IgnorePanelAddIgnore = "Veux-tu ignorer %s pour le reste de cette session ?" +IgnorePanelIgnore = "À présent, tu ignores %s." +IgnorePanelRemoveIgnore = "Veux-tu arrêter d'ignorer %s ?" +IgnorePanelEndIgnore = "Tu n'ignores plus %s." +IgnorePanelAddFriendAvatar = "%s est ton ami(e). Tu ne peux pas l'ignorer tant que vous êtes amis. %s (%s) est ton ami(e). Tu ne peux pas l'ignorer tant que vous êtes amis." +IgnorePanelAddFriendPlayer = "" + +# PetAvatarPanel.py +PetPanelFeed = "Nourrir" +PetPanelCall = "Appeler" +PetPanelGoTo = "Aller à" +PetPanelOwner = "Montrer le propriétaire" +PetPanelDetail = "Détails de l'animalerie" +PetPanelScratch = "Cajoler" + +# PetDetailPanel.py +PetDetailPanelTitle = "Apprentissage des tours" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Saute', + 1: 'Fais le beau', + 2: 'Fais le mort', + 3: 'Fais une roulade', + 4: 'Saute en arrière', + 5: 'Danse', + 6: 'Parle', + } + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutre', + 'hunger': 'affamé', + 'boredom': "s'ennuie", + 'excitement': 'excité', + 'sadness': 'triste', + 'restlessness': 'agité', + 'playfulness': 'joueur', + 'loneliness': 'solitaire', + 'fatigue': 'fatigué', + 'confusion': 'perplexe', + 'anger': 'en colère', + 'surprise': 'surpris', + 'affection': 'affectueux', + } + +# DistributedAvatar.py +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Contacts" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Essaie d'aller à %s." +TeleportPanelNotAvailable = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelIgnored = "%s t'ignore" +TeleportPanelNotOnline = "%s n'est pas en ligne en ce moment." +TeleportPanelWentAway = "%s est parti(e)." +TeleportPanelUnknownHood = "Tu ne sais pas aller jusqu'à %s!" +TeleportPanelUnavailableHood = "%s est occupé(e) en ce moment, ressaie plus tard." +TeleportPanelDenySelf = "Tu ne peux pas aller te voir toi-même!" +TeleportPanelOtherShard = "%(avName)s est dans le district %(shardName)s, et tu es dans le district %(myShardName)s. Veux-tu aller à %(shardName)s?" +TeleportPanelBusyShard = "%(avName)s se trouve dans un district qui affiche complet. Jouer dans un district complet peut considérablement ralentir les performances de jeu. Es-tu sûr de vouloir changer de district ?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Je suis le chef." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Je suis le contremaître." +FactoryBossBattleTaunt = "Je te présente le contremaître." +MintBossTaunt = "Je suis le Superviseur." +MintBossBattleTaunt = "Vous devez parler au Superviseur." +StageBossTaunt = "Ma justice n'est pas aveugle" +StageBossBattleTaunt = "Je suis au-dessus des lois" +CountryClubBossTaunt = "Je suis le Président du Club." +CountryClubBossBattleTaunt = "Tu dois t'adresser au Président du Club." +ForcedLeaveCountryClubAckMsg = "Le Président du Club a été vaincu avant que tu n'arrives jusqu'à lui. Tu n'as pas récupéré d'actions." + +# HealJokes.py +ToonHealJokes = [ + ["Qu'est ce qui fait PIOU-PIOU?", + "Un poussin de 500 kilos!"], + ["Que dit un pneu qui va voir un médecin ?", + "Docteur, je me sens crevé."], + ["Pourquoi est-ce difficile pour un fantôme de mentir ?", + "Parce qu'il est cousu de fil blanc."], + ["Vous connaissez l'histoire de la chaise ?", + "Dommage, elle est pliante!"], + ["Qu'est-ce qui est vert et qui monte et qui descend?", + "Un petit pois dans un ascenseur!"], + ["Quel est le comble de l'électricien ?", + "De ne pas être au courant."], + ["Que font deux chiens qui se rencontrent à Tokyo?", + "Ils se jappent au nez."], + ["Quel est le futur de \"je baille\"?", + "Je dors."], + ["Quel est l'animal le plus rapide ?", + "Le pou car il est toujours en tête!"], + ["Quel animal n'a jamais soif?", + "Le zébu, parce que quand zébu zé plus soif!"], + ["Quel est le comble pour un myope ?", + "De manger des lentilles."], + ["Pourquoi as-tu mis le journal dans le réfrigérateur ?", + "Pour avoir des nouvelles fraîches!"], + ["Qu'est-ce qui est gris et qui t'éclabousse de confiture ?", + "Une souris qui mange un beignet."], + ["Que demande un douanier à un cochon qui passe la frontière ?", + "Son passe-porc."], + ["Que dit un bébé souris à sa maman quand il voit passer une chauve-souris?", + "Maman, un ange!"], + ["Comment appelle-t-on un ascenseur au Japon ?", + "En appuyant sur le bouton."], + ["Comment appelle-t-on un poisson pas encore né?", + "Un poisson pané."], + ["Si tu fais tomber un chapeau blanc dans la mer rouge, comment ressort-il?", + "Mouillé."], + ["Que demande un chat qui entre dans une pharmacie ?", + "Du sirop pour matou."], + ["Quel est le comble pour un jockey?", + "D'être à cheval sur les principes."], + ["Quelles sont les deux choses que tu ne peux pas prendre au petit-déjeuner ?", + "Le déjeuner et le dîner."], + ["Qu'est ce qu'on donne à un éléphant qui a de grands pieds?", + "De grandes chaussures."], + ["Comment sait-on qu'un éléphant est caché dans le réfrigérateur ?", + "Aux empreintes de pattes dans le beurre."], + ["Quelle est la différence entre un instituteur et un thermomètre ?", + "Aucune, on tremble toujours quand ils marquent zéro!"], + ["Qu'est-ce qui est petit, carré et vert ?", + "Un petit carré vert."], + ["Quel est le comble pour un éléphant ?", + "D'être sans défense."], + ["Que dit le 0 au 8?", + "Tiens, tu as mis ta ceinture!"], + ["Qu'est ce qu'il ne faut jamais faire devant un poisson-scie ?", + "La planche!"], + ["Pourquoi est-ce que certaines personnes travaillent la nuit ?", + "Pour mettre leur travail à jour."], + ["Quel est le comble de la patience ?", + "Trier des petits pois avec des gants de boxe."], + ["Qu'est ce qui voyage tout autour du monde en restant dans son coin ?", + "Un timbre."], + ["Quel est le comble pour une souris?", + "Avoir un chat dans la gorge."], + ["Quel est le comble pour un canard?", + "En avoir marre!"], + ["Quel est le comble pour un magicien ?", + "Se nourrir d'illusions."], + ["Quel est le comble de la clé?", + "Se faire mettre à la porte."], + ["Quel est le comble pour un cordonnier ?", + "Avoir les dents qui se déchaussent."], + ["De quelle couleur sont les petits pois?", + "Les petits poissons rouges."], + ["Qu'est-ce qui baille et qui ne dort jamais?", + "Une porte."], + ["Tu sais ce que c'est un canif?", + "C'est un p'tit fien!"], + ["Qu'est-ce qu'un chou au fond d'une baignoire ?", + "Un choumarin!"], + ["Quel est le comble pour le propriétaire d'un champ de pommiers?", + "Travailler pour des prunes!"], + ["Qu'est-ce qui est aussi grand que l'Arc de Triomphe mais ne pèse rien ?", + "Son ombre."], + ["Comment s'appelle un boomerang qui ne revient pas?", + "Un bout de bois."], + ["Pourquoi est-ce que les éléphants se déplacent en troupeau compact ?", + "Parce que c'est celui du milieu qui a la radio."], + ["De quelle couleur sont les parapluies quand il pleut ?", + "Ils sont tout verts."], + ["Quel est le comble du torero?", + "Que le taureau soit vache."], + ["Quel est l'animal le plus heureux?", + "Le hibou, parce que sa femme est chouette."], + ["Que dit un vitrier à son fils?", + "Tiens-toi à carreau si tu veux une glace."], + ["Comment appelle-t-on un chien sans pattes?", + "On ne l'appelle pas, on va le chercher."], + ["Qu'ont les girafes que n'ont pas les autres animaux?", + "Des bébés girafes."], + ["Un chameau peut-il avoir 3 bosses?", + "Oui, s'il se cogne la tête contre le mur."], + ["Pourquoi les musiciens aiment-ils prendre le train ?", + "Parce que la voie fait ré."], + ["Deux fourmis sont sur un âne, laquelle va doubler l'autre ?", + "Aucune, il est interdit de doubler sur un dos d'âne."], + ["Quelle est la note la plus basse ?", + "Le sol."], + ["Pourquoi les trains électriques vont-ils plus vite que les trains à vapeur ?", + "Parce qu'ils ont arrêté de fumer."], + ["Qu'est ce qu'un arbuste dit à un géranium?", + "Espèce d'empoté!"], + ["Que recommande la maman allumette à ses enfants?", + "Surtout, ne vous grattez pas la tête!"], + ["Qu'est-ce qu'il y a à la fin de tout ?", + "La lettre T."], + ["Pourquoi les poissons-chats s'ennuient-ils?", + "Parce qu'il n'y a pas de poissons-souris."], + ["Qu'est-ce que le vainqueur du marathon a perdu?", + "Son souffle."], + ["Comment appelle-t-on un spectacle qui rend propre ?", + "Un ballet."], + ["Qu'est-ce qui fait 999 fois \"Tic\" et une fois \"Toc\"?", + "Un mille-pattes avec une jambe de bois."], + ["Comment reconnaît-on un écureuil d'une fourchette ?", + "En les mettant au pied d'un arbre, celui qui monte est l'écureuil."], + ["Pourquoi les flamants roses lèvent-ils une patte en dormant ?", + "Parce qu'ils tomberaient s'ils levaient les deux."], + ["Qu'est-ce qui est noir quand il est propre et blanc quand il est sale ?", + "Un tableau noir!"], + ["Qu'est-ce qui fait Oh, Oh, Oh?", + "Le Père Noël qui marche en arrière."], + ["Qu'est-ce qui peut voyager jour et nuit sans quitter son lit ?", + "La rivière."], + ["Quel arbre n'aime pas la vitesse ?", + "Le frêne."], + ["Pourquoi est-ce que les dinosaures ont de longs cous?", + "Parce que leurs pieds sentent mauvais."], + ["Qu'est-ce qui est jaune et qui court très vite ?", + "Un citron pressé."], + ["Pourquoi est-ce que les éléphants n'oublient jamais?", + "Parce qu'on ne leur dit jamais rien."], + ["Quel animal peut changer de tête facilement ?", + "Un pou."], + ["Qu'est-ce qu'un steak caché derrière un arbre ?", + "Un steak caché."], + ["Pourquoi est-ce que les serpents ne sont pas susceptibles?", + "Parce qu'on ne peut pas leur casser les pieds."], + ["Pourquoi dit-on que les boulangers travaillent rapidement ?", + "Parce qu'ils travaillent en un éclair."], + ["Que dit un fantôme quand il est ennuyé?", + "Je suis dans de beaux draps!"], + ["Comment peut-on arrêter un éléphant qui veut passer dans le chas d'une aiguille ?", + "On fait un nœud à sa queue."], + ["Pourquoi est-ce que les pompiers ont des bretelles rouges?", + "Pour tenir leurs pantalons!"], + ["Que prend un éléphant lorsqu'il rentre dans un bar ?", + "De la place!"], + ["Savez-vous que votre chien aboie toute la nuit ?", + "Ça ne fait rien, il dort toute la journée!"], + ["Savez-vous que le vétérinaire a épousé la manucure ?", + "Au bout d'un mois ils se battaient becs et ongles."], + ["Tu sais que nous sommes sur terre pour travailler ?", + "Bon, alors plus tard je serai marin."], + ["Quand je dis \"il pleuvait\", de quel temps s'agit-il?", + "D'un sale temps."], + ["À quoi reconnaît-on un motard heureux?", + "Aux moustiques collés sur ses dents."], + ["Son succès lui est monté à la tête.", + "C'est normal, c'est là qu'il y avait le plus de place libre."], + ["Qu'est-ce qui est gris, pousse de petits cris et fait 5 kilos?", + "Une souris qui a besoin de se mettre au régime."], + ["Que dit-on à un croque-mort qui rentre dans un café?", + "\"Je vous sers une bière ?\""], + ["Connais-tu l'histoire du lit vertical?", + "C'est une histoire à dormir debout."], + ["Pourquoi est-ce que les éléphants sont gros et gris?", + "Parce que s'ils étaient petits et jaunes ce seraient des canaris."], + ["Combien coûte cet aspirateur ?", + "750 et des poussières."], + ["Quel est le comble pour un juge gourmand?", + "De manger des avocats."], + ["Pourquoi"+ Donald + " regarde-t-il à droite et à gauche lorsqu'il rentre dans une pièce ?", + "Parce qu'il ne peut pas regarder des deux côtés à la fois."], + ["Pourquoi est-ce que"+ Goofy + " emmène son peigne chez le dentiste ?", + "Parce qu'il a perdu toutes ses dents."], + ["Quel bruit fait la fourmi?", + "La fourmi cro-onde."], + ["Si les sorties étaient surveillées, comment le voleur a-t-il pu s'échapper ?", + "Par l'entrée!"], + ["Que dit un haut-parleur à un autre haut-parleur ?", + "Tu veux une baffle ?"], + ["Pourquoi les lézards aiment-ils les vieux murs?", + "Parce qu'ils ont des lézardes."], + ["Pourquoi est-ce que les moutons ont des pelages en laine ?", + "Parce qu'ils auraient l'air idiots avec des pelages en synthétique."], + ["Où trouve-t-on le dimanche avant le jeudi?", + "Dans le dictionnaire."], + ["Pourquoi est-ce que"+ Pluto + " a dormi avec une peau de banane ?", + "Pour pouvoir se glisser hors de son lit le lendemain matin."], + ["Pourquoi est-ce que la souris portait des chaussons noirs?", + "Parce que les blancs étaient à la lessive."], + ["Quel est le point commun entre les fausses dents et les étoiles?", + "Elles sortent la nuit."], + ["Pourquoi est-ce que les chats aiment se faire photographier ?", + "Parce qu'on leur dit \"souris!\"."], + ["Pourquoi est-ce que l'archéologue a fait faillite ?", + "Parce que sa carrière était en ruine."], + ["Qui boit l'eau sans jamais l'avaler ?", + "L'éponge."], + ["Quelle est la couleur du virus de la grippe ?", + "Gris pâle."], + ["Pourquoi faut-il craindre le soleil?", + "Parce que c'est le plus grand des astres."], + ["Quel est le comble d'un avion ?", + "C'est d'avoir un antivol."], + ["Que dit la nappe à la table ?", + "Ne crains rien, je te couvre."], + ["Que fait"+ Goofy + " quand il tombe dans l'eau?", + "PLOUF!"], + ["Quel est le comble pour un crayon ?", + "Se tailler pour avoir bonne mine."], + ["Que dit la grosse cheminée à la petite cheminée ?", + "Tu es trop jeune pour fumer."], + ["Que dit le tapis au carrelage ?", + "Ne t'inquiète pas, je te couvre."], + ["Quelle est la différence entre le cancre et le premier de la classe ?", + "Quand le cancre redouble, c'est rarement d'attention."], + ["Qu'est-ce qui fait zzzb zzzb?", + "Une guêpe qui vole à l'envers."], + ["Comment appelle-t-on quelqu'un qui tue son beau-frère ?", + "Un insecticide, car il tue l'époux de sa sœur."], + ["Comment appelle-t-on un dinosaure qui n'est jamais en retard?", + "Un promptosaure."], + ["On ne devrait pas dire \"un chapitre\".", + "On devrait dire \"un chat rigolo\"."], + ["On ne devrait pas dire \"un perroquet\".", + "On devrait dire \"mon papa est d'accord\"."], + ["On ne devrait pas dire \"bosser à la chaîne\".", + "On devrait dire \"travailler à la télé\"."], + ["Pourquoi est-ce que le livre de maths était malheureux?", + "Parce qu'il avait trop de problèmes."], + ["On ne devrait pas dire \"un match interminable\".", + "On devrait dire \"une rencontre de mauvais joueurs\"."], + ["On ne devrait pas dire \"la maîtresse d'école\".", + "On devrait dire \"l'institutrice prend l'avion\"."], + ["Que voit-on quand deux mille-pattes se serrent la main ?", + "Une fermeture-éclair."], + ["Comment appelle-t-on un journal publié au Sahara?", + "Un hebdromadaire."], + ["Que doit planter un agriculteur frileux?", + "Un champ d'ail."], + ["Quel est le comble du chauve ?", + "Avoir un cheveu sur la langue."], + ["Qu'est-ce que tu trouves si tu croises un éléphant avec un corbeau?", + "Des tas de poteaux téléphoniques cassés."], + ["Combien gagne un fakir ?", + "Des clous!"], + ["Quelle est la meilleure manière d'économiser l'eau?", + "La diluer."], + ["Quelle différence y a-t-il entre un horloger et une girouette ?", + "L'horloger vend des montres et la girouette montre le vent."], + ["Pourquoi est-ce que les ordinateurs se grattent ?", + "Parce qu'ils sont pleins de puces."], + ["Qu'est-ce qui a un chapeau et pas de tête, un pied mais pas de souliers?", + "Un champignon."], + ["Pourquoi est-ce que le ciel est haut ?", + "Pour éviter que les oiseaux ne se cognent la tête en volant."], + ["Qu'est ce qui est pire qu'une girafe qui a mal à la gorge ?", + "Un mille-pattes avec des cors aux pieds."], + ["Qu'est-ce qui fait ABC...gloups...DEF...gloups?", + "Quelqu'un qui mange de la soupe aux pâtes alphabet."], + ["Qu'est-ce qui est blanc et qui va vite ?", + "Un frigo de course."], + ["Quel est le fruit que les poissons n'aiment pas?", + "La pêche!"], + ["Comment font les éléphants pour traverser un étang?", + "Ils sautent de nénuphar en nénuphar."], + ["Qu'est-ce qui est noir et blanc à pois rouges?", + "Un Dalmatien qui a la rougeole."], + ["Qu'est-ce qu'un chalumeau?", + "Un drolumadaire à 2 bosses."], + ["Pourquoi les éléphants sont-ils gris?", + "Pour ne pas les confondre avec les fraises."], + ["Qu'est-ce qui est gris, fait 100 kilos et appelle \"Minou, Minou!\"?", + "Une souris de 100 kilos."], + ["Quel est le point commun entre un pâtissier et un ciel orageux?", + "Tous les deux font des éclairs."], + ["Quel bruit font les esquimaux lorsqu'ils boivent ?", + "Iglou, iglou, iglou"], + ["Comment appelle-t-on une chauve-souris avec une perruque ?", + "Une souris."], + ["Pourquoi les aiguilles sont-elles moins intelligentes que les épingles?", + "Parce qu'elles n'ont pas de tête."], + ["Qu'est-ce qui a de la fourrure, miaule et chasse les souris sous l'eau?", + "Un poisson-chat."], + ["Comment fait-on aboyer un chat ?", + "Si on lui donne une tasse de lait il la boit."], + ["Qu'est-ce qui est vert à l'extérieur et jaune à l'intérieur ?", + "Une banane déguisée en concombre."], + ["Qu'est-ce qu'un ingrat ?", + "Le contraire d'un géant maigre."], + ["Qu'est-ce qui pèse 4 tonnes, a une trompe et est rouge vif?", + "Un éléphant qui a honte."], + ["Dans un virage à 60 degrés à droite, quelle est la roue qui tourne le moins vite ?", + "La roue de secours."], + ["Comment reconnaît-on un idiot dans un magasin de chaussures?", + "C'est celui qui essaie les boîtes."], + ["Que dit-on d'un enfant qui ramène le pain à la maison ?", + "C'est le petit calepin."], + ["Que dit la cacahuète à l'éléphant ?", + "Rien, les cacahuètes ne parlent pas."], + ["Que dit un éléphant lorsqu'il se heurte à un autre éléphant ?", + "Le monde est petit, n'est-ce pas?"], + ["Que dit la comptable à la machine à calculer ?", + "Je compte sur toi."], + ["Que dit la puce à une autre puce ?", + "On y va à pied ou on prend le chat ?"], + ["Que dit la grande aiguille à la petite aiguille ?", + "Attends une minute."], + ["Que dit une poule quand elle rencontre une autre poule ?", + "Tu viens, on va prendre un ver ?"], + ["Que dit le collant à la chaussure ?", + "À plus tard, je dois filer."], + ["Papa kangourou demande à sa fille qui rentre de l'école: \"Alors, cet examen ?\"", + "\"C'est dans la poche, pas de problème!\""], + ["Quelle est la ville de France la plus féroce ?", + "Lyon."], + ["Quelle est la ville de France la moins légère ?", + "Lourdes."], + ["Pourquoi porte-t-on des vêtements?", + "Parce qu'ils ne peuvent pas marcher tout seuls."], + ["Que dit une pomme de terre quand elle en voit une autre se faire écraser dans la rue ?", + "\"Oh, purée!\""], + ["Que dit un petit fakir quand il arrive en retard à l'école ?", + "\"Pardon maîtresse, je me suis endormi sur le passage clouté!\""], + ["Que dit un marin-pêcheur s'il se dispute avec un autre marin-pêcheur ?", + "Je ne veux pas que tu me parles sur ce thon!"], + ["Pourquoi les cultivateurs disent-ils des gros mots à leurs tomates?", + "Pour les faire rougir."], + ["Que disent deux vers de terre s'ils se rencontrent au milieu d'une pomme ?", + "\"Vous habitez dans le quartier ?\""], + ["Qu'est-ce que se disent deux serpents qui se rencontrent ?", + "\"Quelle heure reptile ?\""], + ["Pourquoi les mille-pattes ne peuvent-ils pas jouer au hockey?", + "Le temps d'enfiler leurs patins, la partie est déjà terminée!"], + ["Comment fait-on cuire un poisson dans un piano?", + "On fait Do, Ré, La, Sol."], + ["Connaissez-vous l'histoire du chauffeur d'autobus?", + "Moi non plus, j'étais à l'arrière!"], + ["Crois-tu aux girafes?", + "Non, c'est un cou monté."], + ["Que dit un crocodile s'il rencontre un chien ?", + "Salut, sac à puces!"], + ["Que dit un chien quand il rencontre un crocodile ?", + "Salut, sac à main!"], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","hou","ha","rhaa") +MovieHealLaughterHits1= ("Ha ha ha","Hi hi","Hé hé","Ha ha") +MovieHealLaughterHits2= ("OUARF OUARF OUARF!","HO HO HO!","HA HA HA!") + +# MovieSOS.py +MovieSOSCallHelp = "%s À L'AIDE!" +MovieSOSWhisperHelp = "%s a besoin d'aide pour un combat!" +MovieSOSObserverHelp = "À L'AIDE!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Salut, %s! C'est un plaisir de pouvoir t'aider!" +MovieNPCSOSGoodbye = "À plus tard!" +MovieNPCSOSToonsHit = "Les Toons font toujours mouche!" +MovieNPCSOSCogsMiss = "Les Cogs ratent toujours leurs cibles!" +MovieNPCSOSRestockGags = "En train de faire le plein de gags %s!" +MovieNPCSOSHeal = "Guérison" +MovieNPCSOSTrap = "Piégeage" +MovieNPCSOSLure = "Leurre" +MovieNPCSOSSound = "Tapage" +MovieNPCSOSThrow = "Lancer" +MovieNPCSOSSquirt = "Éclaboussure" +MovieNPCSOSDrop = "Chute" +MovieNPCSOSAll = "Tout" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Soupir" +MoviePetSOSTrickSucceedBoy = "Bon garçon!" +MoviePetSOSTrickSucceedGirl = "Brave fifille!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "ANNULÉ\nANNULÉ\nANNULÉ" + +# RewardPanel.py +RewardPanelToonTasks = "Défitoons" +RewardPanelItems = "Objets récupérés" +RewardPanelMissedItems = "Objets non récupérés" +RewardPanelQuestLabel = "Quête %s" +RewardPanelCongratsStrings = ["Ouais!", "Bravo!", "Ouah!", + "Sympa!", "Atmosphérique!", "Toontastique!"] +RewardPanelNewGag = "Nouveau gag %(gagName)s pour %(avName)s!" +RewardPanelUberGag = "%(avName)s earned the %(gagName)s gag with %(exp)s experience points!" +RewardPanelEndTrack = "Haa! %(avName)s a atteint la fin de la série de gags %(gagName)s!" +RewardPanelMeritsMaxed = "Au maximum" +RewardPanelMeritBarLabels = [ "Avis de licenciement", "Citations à comparaître", "Euros Cog", "Mérites" ] +RewardPanelMeritAlert = "Prêt pour la promotion!" + +RewardPanelCogPart = "Tu as gagné un morceau de déguisement de Cog!" +RewardPanelPromotion = "Préparez pour la promotion %s voie!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Toon normal", "tu seras normal(e)"), + ("Grosse tête", "tu auras une grosse tête"), + ("Petite tête", "tu auras une petite tête"), + ("Grosses jambes", "tu auras de grosses jambes"), + ("Petites jambes", "tu auras de petites jambes"), + ("Gros Toon", "tu seras un peu plus gros(se)"), + ("Petit Toon", "tu seras un peu plus petit(e)"), + ("À plat", "tu seras en deux dimensions"), + ("Profil plat", "tu seras en deux dimensions"), + ("Transparent", "tu seras transparent(e)"), + ("Sans couleur", "tu seras incolore"), + ("Toon invisible", "tu seras invisible"), + ] +CheesyEffectIndefinite = "Jusqu'à ce que tu choisisses un autre effet, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Pendant les %(time)s prochaines minutes, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Pendant les %(time)s prochaines heures, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Pendant les %(time)s prochains jours, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " pendant que tu es dans %s" +CheesyEffectExceptIn = ", excepté dans %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Laquaistic" +SuitPencilPusher = "Gratte-\npapier" +SuitYesman = "Béniouioui" +SuitMicromanager = "Micro\3chef" +SuitDownsizer = "Touptisseur" +SuitHeadHunter = "Chassetête" +SuitCorporateRaider = "Attactic" +SuitTheBigCheese = "Gros Blochon" +SuitColdCaller = "Cassepied" +SuitTelemarketer = "Télé\3vendeur" +SuitNameDropper = "Cafteur" +SuitGladHander = "Passetout" +SuitMoverShaker = "Secousse-\ncousse" +SuitTwoFace = "Biface" +SuitTheMingler = "Le Circulateur" +SuitMrHollywood = "M. Hollywood" +SuitShortChange = "Gardoseille" +SuitPennyPincher = "Radino" +SuitTightwad = "Grippesou" +SuitBeanCounter = "Pince Menu" +SuitNumberCruncher = "Gobechiffre" +SuitMoneyBags = "Sacasous" +SuitLoanShark = "Usurier" +SuitRobberBaron = "Pillard" +SuitBottomFeeder = "Volebas" +SuitBloodsucker = "Pique-\nau-sang" +SuitDoubleTalker = "Charabieur" +SuitAmbulanceChaser = "Charognard" +SuitBackStabber = "Frappedos" +SuitSpinDoctor = "Tournegris" +SuitLegalEagle = "Avocageot" +SuitBigWig = "Chouffleur" + +# Singular versions (indefinite article) +SuitFlunkyS = "un Laquaistic" +SuitPencilPusherS = "un Gratte-Papier" +SuitYesmanS = "un Béniouioui" +SuitMicromanagerS = "un Microchef" +SuitDownsizerS = "un Touptisseur" +SuitHeadHunterS = "un Chassetête" +SuitCorporateRaiderS = "un Attactic" +SuitTheBigCheeseS = "un Gros Blochon" +SuitColdCallerS = "un Cassepied" +SuitTelemarketerS = "un Télévendeur" +SuitNameDropperS = "un Cafteur" +SuitGladHanderS = "un Passetout" +SuitMoverShakerS = "un Secousse-cousse" +SuitTwoFaceS = "un Biface" +SuitTheMinglerS = "un Circulateur" +SuitMrHollywoodS = "un M. Hollywood" +SuitShortChangeS = "un Gardoseille" +SuitPennyPincherS = "un Radino" +SuitTightwadS = "un Grippesou" +SuitBeanCounterS = "un Pince-Menu" +SuitNumberCruncherS = "un Gobechiffre" +SuitMoneyBagsS = "un Sacasous" +SuitLoanSharkS = "un Usurier" +SuitRobberBaronS = "un Pillard" +SuitBottomFeederS = "un Volebas" +SuitBloodsuckerS = "un Pique-au-sang" +SuitDoubleTalkerS = "un Charabieur" +SuitAmbulanceChaserS = "un Charognard" +SuitBackStabberS = "un Frappedos" +SuitSpinDoctorS = "un Tournegris" +SuitLegalEagleS = "un Avocageot" +SuitBigWigS = "un Chouffleur" + +# Plural versions +SuitFlunkyP = "Laquaistics" +SuitPencilPusherP = "Gratte-Papiers" +SuitYesmanP = "Béniouiouis" +SuitMicromanagerP = "Microchefs" +SuitDownsizerP = "Touptisseurs" +SuitHeadHunterP = "Chassetêtes" +SuitCorporateRaiderP = "Attactics" +SuitTheBigCheeseP = "Gros Blochons" +SuitColdCallerP = "Cassepieds" +SuitTelemarketerP = "Télévendeurs" +SuitNameDropperP = "Cafteurs" +SuitGladHanderP = "Passetouts" +SuitMoverShakerP = "Secousse-cousses" +SuitTwoFaceP = "Bifaces" +SuitTheMinglerP = "Les Circulateurs" +SuitMrHollywoodP = "MM. Hollywood" +SuitShortChangeP = "Gardoseilles" +SuitPennyPincherP = "Radinos" +SuitTightwadP = "Grippesous" +SuitBeanCounterP = "Pince-Menus" +SuitNumberCruncherP = "Gobechiffres" +SuitMoneyBagsP = "Sacasous" +SuitLoanSharkP = "Usuriers" +SuitRobberBaronP = "Pillards" +SuitBottomFeederP = "Volebas" +SuitBloodsuckerP = "Pique-au-sang" +SuitDoubleTalkerP = "Charabieurs" +SuitAmbulanceChaserP = "Charognards" +SuitBackStabberP = "Frappedos" +SuitSpinDoctorP = "Tournegris" +SuitLegalEagleP = "Avocageots" +SuitBigWigP = "Chouffleurs" + +SuitFaceOffDefaultTaunts = ['Bouh!'] + +SuitAttackDefaultTaunts = ['Prends ça!', 'Garde des notes là-dessus!'] + +SuitAttackNames = { + 'Audit' : 'Audit!', + 'Bite' : 'Morsure!', + 'BounceCheck' : 'Chèque refusé!', + 'BrainStorm' : 'Remue-méninges!', + 'BuzzWord' : 'Mot à la mode!', + 'Calculate' : 'Évaluation!', + 'Canned' : 'En conserve!', + 'Chomp' : 'Mastication!', + 'CigarSmoke' : 'Fumée de cigare!', + 'ClipOnTie' : 'Cravate toute faite!', + 'Crunch' : 'Écrasement!', + 'Demotion' : 'Rétrogradation!', + 'Downsize' : 'Rapetissement!', + 'DoubleTalk' : 'Charabia!', + 'EvictionNotice' : "Ordre d'expulsion!", + 'EvilEye' : 'Mauvais œil!', + 'Filibuster' : 'Obstruction!', + 'FillWithLead' : 'Plombage!', + 'FiveOClockShadow' : "Barbe naissante!", + 'FingerWag' : 'Montré du doigt!', + 'Fired' : 'Liquidé!', + 'FloodTheMarket' : 'Invasion du marché!', + 'FountainPen' : 'Stylo-plume!', + 'FreezeAssets' : 'Capital gelé!', + 'Gavel' : 'Adjugé!', + 'GlowerPower' : 'Regard furieux!', + 'GuiltTrip' : 'Culpabilisation!', + 'HalfWindsor' : 'Nœud de cravate!', + 'HangUp' : 'Interruption!', + 'HeadShrink' : 'Rétrécissement de la tête!', + 'HotAir' : 'Air chaud!', + 'Jargon' : 'Jargon!', + 'Legalese' : 'Expression juridique!', + 'Liquidate' : 'Liquidation!', + 'MarketCrash' : 'Krach boursier!', + 'MumboJumbo' : 'Baragouinage!', + 'ParadigmShift' : 'Changement radical!', + 'PeckingOrder' : 'Hiérarchie!', + 'PickPocket' : 'Vol à la tire!', + 'PinkSlip' : 'Avis de licenciement!', + 'PlayHardball' : 'Grands moyens!', + 'PoundKey' : 'Touche dièse!', + 'PowerTie' : 'Cravate rayée!', + 'PowerTrip' : 'Mégalomanie!', + 'Quake' : 'Tremblement!', + 'RazzleDazzle' : 'Bringue!', + 'RedTape' : 'Paperasserie!', + 'ReOrg' : 'Réorganisation!', + 'RestrainingOrder' : 'Injonction!', + 'Rolodex' : 'Fichier rotatif!', + 'RubberStamp' : 'Tampon!', + 'RubOut' : 'Effacement!', + 'Sacked' : 'Licenciement!', + 'SandTrap' : 'Ensablement!', + 'Schmooze' : 'Jacasserie!', + 'Shake' : 'Secousse!', + 'Shred' : 'Déchiquetage!', + 'SongAndDance' : 'Couplet habituel!', + 'Spin' : 'Tournoiement!', + 'Synergy' : 'Synergie!', + 'Tabulate' : 'Tabulation!', + 'TeeOff' : 'Fâcherie!', + 'ThrowBook' : 'Maximum!', + 'Tremor' : 'Frémissement!', + 'Watercooler' : 'Boissons fraîches!', + 'Withdrawal' : 'Retrait!', + 'WriteOff' : 'Pertes et profits!', + } + +SuitAttackTaunts = { + 'Audit': ["Je crois que ton bilan n'est pas équilibré.", + "On dirait que tu es dans le rouge.", + "Laisse-moi t'aider à faire ta comptabilité.", + "Tes débits sont beaucoup trop élevés.", + "Vérifions ton capital", + "Tu vas avoir des dettes.", + "Regardons de plus près ce que tu dois.", + "Cela devrait mettre ton compte à sec.", + "Il est temps que tu comptabilises tes dépenses.", + "J'ai trouvé une erreur dans ton bilan.", + ], + 'Bite': ["Tu en veux une bouchée ?", + "Essaye d'en mordre un morceau!", + "Tu as les yeux plus gros que le ventre.", + "Je mords plus que je n'aboie.", + "Avale donc ça!", + "Attention, je pourrais mordre.", + "Je ne fais pas que mordre quand je suis coincé.", + "J'en veux juste une petite bouchée.", + "Je n'ai rien avalé de la journée.", + "Je ne veux qu'un petit morceau. C'est trop demander ?", + ], + 'BounceCheck': ["Dommage, tu n'as pas d'humour.", + "Tu as une échéance de retard.", + "Je crois que ce chèque est à toi.", + "Tu m'es redevable.", + "Je recouvre cette créance.", + "Ce chèque ne va pas être un cadeau.", + "Tu vas être facturé pour ça.", + "Vérifie ce chèque.", + "Ça va te coûter cher.", + "J'aimerais bien encaisser ça.", + "Je vais simplement te renvoyer ton chèque.", + "Voilà une facture salée.", + "Je déduis des frais de service.", + ], + 'BrainStorm':["Je prévois des perturbations.", + "J'adore les casse-tête.", + "Je voudrais t'éclairer.", + "Qu'est-ce que tu penserais de la CHUTE de tes facultés?", + "Que de médiocrité!", + "Tu es prêt(e) pour le grand déménagement ?", + "J'ai les neurones en feu.", + "Ça casse des briques.", + "Rien de tel qu'un remue-méninges.", + ], + 'BuzzWord':["Excuse-moi si je radote.", + "Tu connais la dernière ?", + "Tu peux piger ça?", + "Toonicoton!", + "Laisse-moi en placer une.", + "Je serai incontournablement clair.", + "Tu as dit un mot de trop.", + "Voyons si tu te situes en transversalité.", + "Fais attention, ça va être ringard.", + "Je crois que tu vas faire de l'urticaire.", + ], + 'Calculate': ["Le compte est bon!", + "Tu comptais là-dessus?", + "Ajoutes-en un peu, tu es en train de diminuer.", + "Je peux t'aider à faire cette addition ?", + "Tu as bien enregistré toutes tes dépenses?", + "D'après mes calculs, tu n'en as plus pour longtemps.", + "Voilà le total général.", + "Houlà, ton addition est bien longue.", + "Essaie de trafiquer ces chiffres!", + Cogs + " : 1 Toons: 0", + ], + 'Canned': ["Tu aimes quand c'est en boîte ?", + "Tu peux t'occuper des boîtes?", + "Celui-là vient de sortir de sa boîte!", + "Tu as déjà été attaqué par des boîtes de conserve ?", + "J'aimerais te faire un cadeau qui se conserve!", + "Tu es prêt pour la mise en boîte ?", + "Tu crois que tu es bien conservé?", + "Tu vas être emballé!", + "Je me fais du Toon à l'huile pour dîner!", + "Tu n'es pas si mangeable que ça en conserve.", + ], + 'Chomp': ["Tu as une mine de papier mâché!", + "Croc, croc, croc!", + "On va pouvoir se mettre quelque chose sous la dent.", + "Tu as besoin de grignoter quelque chose ?", + "Tu pourrais grignoter ça!", + "Je vais te manger pour le dîner.", + "Je me nourrirais bien de Toons!", + ], + 'ClipOnTie': ["Il faut s'habiller pour la réunion.", + "Tu ne peux PAS sortir sans ta cravate.", + "C'est ce que portent les " + Cogs + " les plus élégants.", + "Essaie pour voir si la taille te va.", + "Tu devrais mieux t'habiller pour réussir.", + "On ne sert que les clients portant une cravate.", + "Tu as besoin d'aide pour enfiler ça?", + "Rien n'est plus flatteur qu'une belle cravate.", + "Voyons si ça te va.", + "Ça va te bouleverser.", + "Il va falloir que tu t'habilles avant de SORTIR.", + "Je crois que je vais te faire un nœud de cravate.", + ], + 'Crunch': ["On dirait que tu es écrasé(e) par les événements.", + "C'est l'heure d'en écraser!", + "Je vais te donner quelque chose à pulvériser.", + "Je vais broyer tout ça.", + "J'ai tout écrasé.", + "Tu préfères tendre ou croquant ?", + "J'espère que tu aimes les croque-monsieur.", + "On dirait que tu es en train de te faire écraser!", + "Je vais te réduire en miettes." + ], + 'Demotion': ["Tu descends sur l'échelle de la hiérarchie.", + "Je te renvoie à trier le courrier.", + "Il est temps de rendre tes galons.", + "Tu descends, petit clown!", + "On dirait qu'il y a un blocage.", + "Tu progresses lentement.", + "Tu es dans une voie sans issue.", + "Tu n'iras nulle part dans l'immédiat.", + "Tu ne vas nulle part.", + "Cela sera porté sur ta fiche d'assiduité.", + ], + 'Downsize': ["Redescends donc de là!", + "Tu sais comment redescendre ?", + "Revenons à nos affaires.", + "Qu'est-ce qui ne va pas? Tu as l'air d'avoir le moral dans les chaussettes.", + "Tu descends?", + "Qu'est-ce qui te chiffonne ? Toi!", + "Pourquoi est-ce que tu choisis des gens de ma taille ?", + "Pourquoi es-tu si terre-à-terre ?", + "Est-ce que tu voudrais un modèle plus petit pour seulement dix cents de plus?", + "Essaie pour voir si la taille te va!", + "Ce modèle est disponible dans une plus petite taille.", + "C'est une attaque à taille unique!", + ], + # Hmmm - where is double talker ? + 'EvictionNotice': ["C'est l'heure de partir!", + "Fais tes bagages, Toon.", + "C'est le moment d'aller habiter ailleurs.", + "Disons que ton bail est terminé.", + "Tu as un loyer de retard.", + "Cela va être très déstabilisant.", + "Tu vas être déraciné d'ici peu.", + "Je vais t'envoyer sous les ponts.", + "Tu n'es pas à ta place.", + "Prépare-toi à une délocalisation.", + "Tu vas subir un placement d'office.", + ], + 'EvilEye': ["Je te donne le mauvais œil.", + "Tu peux donner un coup d'œil à ça pour moi?", + "Attends. J'ai quelque chose dans l'œil.", + "J'ai l'œil sur toi!", + "Tu pourrais garder un œil sur ça?", + "J'ai vraiment l'œil pour voir ce qui cloche.", + "Je vais te taper dans l'œil.", + "J'ai le regard méchant!", + "Tu vas te retrouver dans l'œil du cyclone!", + "Je te regarde en roulant des yeux.", + ], + 'Filibuster':["Est-ce que je dois te barrer la route ?", + "Ça va nous bloquer pendant un moment.", + "Je pourrais rester coincé là toute la journée.", + "Je n'ai même pas besoin de respirer.", + "J'avance et j'avance et j'avance.", + "Je ne m'en fatigue jamais.", + "On ne peut pas m'arrêter de parler.", + "Tu peux te boucher les oreilles?", + "Je crois que je vais te tenir la jambe.", + "Je finis toujours par placer un mot.", + ], + 'FingerWag': ["Ça fait mille fois que je te le répète!", + "Regarde bien là, Toon.", + "Ne me fais pas rire.", + "Ne m'oblige pas à y aller.", + "J'en ai assez de répéter la même chose.", + "Je crois qu'on en a déjà parlé.", + "Tu n'as aucun respect pour nous les" + Cogs + ".", + "Il est grand temps de faire attention.", + "Blablablablabla.", + "Ne m'oblige pas à mettre fin à cette réunion.", + "Est-ce que je vais devoir te séparer ?", + "On est déjà passés par là.", + ], + 'Fired': ["J'espère que tu as apporté des rafraîchissements.", + "On s'embête solide.", + "Ça va nous rafraîchir.", + "J'espère que tu as le sang froid.", + "J'ai la gorge sèche.", + "Va donc nager un peu!", + "Tu es déjà sur le départ.", + "Encore un peu de sauce ?", + "Tu peux dire \"aïe\"?", + "J'espère que tu sais nager.", + "Tu es en phase de déshydratation ?", + "Je vais te liquider!", + "Tu vas finir en bouillie.", + "Tu n'es qu'un feu de paille.", + "Je me trouve fondant.", + "Je suis d'une limpidité!", + "Et on n'en parle plus !", + "Un Toon à la mer !", + ], + 'FountainPen': ["Ça va tacher.", + "Mettons ça par écrit.", + "Prépare-toi à des ennuis indélébiles.", + "Tu vas avoir besoin d'un bon nettoyage à sec.", + "Tu devrais corriger.", + "Ce stylo écrit si bien.", + "Voilà, je prends mon crayon.", + "Tu peux lire mon écriture ?", + "Et voilà la plume de l'apocalypse.", + "Ta performance est entachée.", + "Tu n'as pas envie de tout effacer ?", + ], + 'FreezeAssets': ["Ton capital est le mien.", + "Tu ne sens pas un appel de fonds?", + "J'espère que tu n'as pas de projets.", + "Cela devrait te mettre sur la paille.", + "Le fond de l'air est frais.", + "L'hiver va venir tôt cette année.", + "Tu as froid?", + "Je vais geler mes projets.", + "Tu vas trouver ça froid.", + "Tu vas avoir des engelures.", + "J'espère que tu aimes la viande froide.", + "Je garde mon sang-froid.", + ], + 'GlowerPower': ["Tu me regardes?", + "On me dit que j'ai une vue perçante.", + "J'aime bien que tu sois à portée de mon regard.", + "Tu n'aimes pas que je te regarde ?", + "Voilà, je te regarde.", + "Tu ne trouves pas que j'ai un regard expressif?", + "Mon regard est mon point fort.", + "C'est le regard qui compte.", + "Coucou, je te vois.", + "Regarde-moi dans les yeux...", + "Est-ce que tu voudrais voir ton avenir ?", + ], + 'GuiltTrip': ["Tu vas vraiment te sentir coupable!", + "Tu te sens coupable!", + "C'est entièrement de ta faute!", + "C'est toujours ta faute.", + "Tu te complais dans la culpabilité!", + "Je ne te reparlerai plus jamais!", + "Tu ferais mieux de t'excuser.", + "Jamais je ne te pardonnerai!", + "Tu veux bien te faire de la bile ?", + "Rappelle-moi quand tu ne te sentiras plus coupable.", + "Quand finiras-tu par te pardonner à toi-même ?", + ], + 'HalfWindsor': ["Tu ne t'es encore jamais fait cravater comme ça!", + "Essaye de ne pas trop faire de nœuds.", + "Tu es dans une situation inextricable.", + "Tu as de la chance, j'aurais pu faire un nœud plus serré.", + "Cette cravate est trop chère pour toi.", + "Je crois que tu n'as jamais même VU de nœud de cravate!", + "Cette cravate est trop chère pour toi.", + "Cette cravate serait gâchée, sur toi.", + "Tu ne vaux même pas la moitié du prix de cette cravate!", + ], + 'HangUp': ["Tu as été déconnecté(e).", + "Au revoir!", + "C'est l'heure de mettre fin à notre conversation.", + "...et ne me rappelle pas!", + "Clic!", + "La conversation est terminée.", + "Je vais couper cette ligne.", + "Je crois que nous allons être coupés.", + "On dirait que la ligne est défectueuse.", + "Ton forfait est terminé.", + "J'espère que tu m'entends clairement.", + "Tu as fait le mauvais numéro.", + ], + 'HeadShrink': ["On dirait que tu as besoin de te faire soigner la tête.", + "Chérie, j'ai rétréci le Toon.", + "J'espère que ça ne t'est pas monté à la tête.", + "Tu rétrécis au lavage ?", + "Je rétrécis donc je suis.", + "Il n'y a pas de quoi perdre la tête.", + "Où as-tu la tête ?", + "Relève la tête! Ou plutôt, mets-la par terre.", + "Les choses sont parfois plus grandes qu'elles ne paraissent.", + "Les bons Toons se vendent par petits paquets.", + ], + 'HotAir':["Nous avons de chaudes discussions.", + "Tu subis une vague de chaleur.", + "J'ai atteint mon point d'ébullition.", + "Cela pourrait te brûler.", + "Je détesterais te passer au gril, mais...", + "N'oublie pas qu'il n'y a pas de fumée sans feu.", + "Tu m'as l'air un peu grillé(e).", + "C'est encore un écran de fumée.", + "C'est le moment de mettre de l'huile sur le feu.", + "Allumons le feu de l'amitié.", + "J'ai des remarques brûlantes à te faire.", + "Air chaud!", + ], + 'Jargon':["Quel non-sens.", + "Regarde si tu peux trouver du sens à tout ça.", + "J'espère que tu m'entends clairement.", + "On dirait que je vais devoir élever la voix.", + "J'ai vraiment mon mot à dire.", + "J'ai mon franc-parler.", + "Je vais pontifier sur ce sujet.", + "Tu sais, les mots peuvent faire mal.", + "Tu as compris ce que je voulais dire ?", + "Des mots, rien que des mots.", + ], + 'Legalese':["Tu dois cesser d'être et renoncer.", + "Tu vas être débouté(e), légalement parlant.", + "Tu es au courant des implications légales?", + "Tu n'es pas au-dessus des lois!", + "Il devrait y avoir une loi contre toi.", + "Il n'y a rien de postérieur aux faits!", + "Toontown Online de Disney n'est pas légalement responsable des opinions exprimées dans cette attaque.", + "Nous ne serons pas tenus responsables des dommages subis suite à cette attaque.", + "Les résultats de cette attaque peuvent différer.", + "Cette attaque est nulle là où elle n'est pas autorisée.", + "Tu ne rentres pas dans mon système législatif.", + "Tu ne peux pas gérer les questions juridiques.", + ], + 'Liquidate':["J'aime bien que les choses restent fluides.", + "As-tu des problèmes de liquidités?", + "Je dois purger ton capital.", + "Il est temps pour toi de suivre le flux monétaire.", + "N'oublie pas que ça glisse quand c'est mouillé.", + "Il y a des fuites dans ta comptabilité.", + "Tu as l'air de perdre pied.", + "Tout te tombe dessus.", + "Je crois que tu vas subir une dilution.", + "Tu es lessivé(e).", + ], + 'MarketCrash':["Tu vas avoir un choc.", + "Tu ne survivras pas au choc.", + "C'est plus que la bourse ne peut en supporter.", + "J'ai un traitement de choc pour toi!", + "Maintenant je vais te faire un choc.", + "Je m'attends à un choc boursier.", + "On dirait que le marché est sur la pente descendante.", + "Il vaudrait mieux que tu te retires du jeu!", + "Vends! Vends! Vends!", + "Est-ce que je dois mener la récession ?", + "Tout le monde s'enfuit, tu devrais peut-être en faire autant ?", + ], + 'MumboJumbo':["Que ce soit parfaitement clair.", + "C'est aussi simple que ça.", + "C'est comme cela que nous allons procéder.", + "Laisse-moi te l'écrire en grosses lettres.", + "C'est du jargon technique.", + "Ma parole est d'argent.", + "J'en ai plein la bouche.", + "On dit que je suis grandiloquent.", + "Je vais interjeter ça.", + "Je crois que ce sont les mots adéquats.", + ], + 'ParadigmShift':["Fais attention! Je suis plutôt changeant.", + "Prépare-toi pour un changement radical!", + "Voilà donc des substitutions intéressantes.", + "Tu n'es pas à ta place.", + "C'est ton tour de changer de place.", + "Ton temps de présence est terminé.", + "Tu n'as encore jamais autant changé dans ta vie.", + "Voilà qui est radical!", + "La lumière est changeante!", + ], + 'PeckingOrder':["Pauvre sous-fifre!", + "Tu vas te retrouver le bec dans l'eau.", + "Tu vas te retrouver en bas de l'échelle.", + "Ce n'est pas une attaque de débutant.", + "Tu es tout en bas de la hiérarchie.", + "Je vaux bien plus cher que toi!", + "La hiérarchie, il n'y a que ça de vrai!", + "Pourquoi est-ce que je ne trouve pas d'adversaire à ma taille ? Bof.", + "À moi le pouvoir!", + ], + 'PickPocket': ["Laisse-moi vérifier tes valeurs.", + "Eh, c'est quoi par ici?", + "C'est comme faucher les jouets d'un enfant.", + "C'est du vol.", + "Je te garde ça.", + "Ne lâche pas mes mains des yeux.", + "Mes mains sont plus rapides que tes yeux.", + "Je n'ai rien dans la manche.", + "La direction n'est pas responsable des objets perdus.", + "Qui trouve garde.", + "Tu ne le verras jamais revenir.", + "Tout pour moi, rien pour toi.", + "Ça ne te gêne pas que ça me gêne ?", + "Tu n'en auras plus besoin...", + ], + 'PinkSlip': ["On n'a pas besoin de ton avis.", + "Tu as peur de cette vague de licenciements?.", + "Celui-là va sûrement être d'un avis contraire.", + "Oh, tu as une licence de quoi?", + "Fais attention, si tu veux mon avis!", + "N'oublie pas que ça glisse à mon avis.", + "Je vais juste te renvoyer celui-là.", + "Tu ne te fâcheras pas si je te donne mon avis?", + "Tu ne vois pas l'avis en rose.", + "Tu peux sortir, je te licencie.", + ], + 'PlayHardball': ["Tu veux employer les grands moyens?", + "N'essaie pas d'employer tous les moyens avec moi.", + "Ne prends pas tes grands airs!", + "Tu es vraiment moyen(ne).", + "Et voilà le bon moyen...", + "Tu vas avoir besoin d'un bon moyen pour t'en sortir.", + "Je vais te chasser d'ici à grande vitesse.", + "Une fois que je t'aurai touché(e), tu rentreras en courant chez toi.", + "C'est ton grand départ!", + "Ton jeu est très moyen.", + "Je vais tout faire pour que tu sortes.", + "Je t'envoie promener dans les grandes largeurs!", + ], + 'PoundKey': ["Il est temps que je réponde à quelques appels.", + "J'aimerais faire un appel en PCV.", + "Dring dring, c'est pour toi!", + "Je vais bien toucher quelque chose.", + "Je devais te rappeler.", + "Cela devrait provoquer une sonnerie.", + "Je vais juste faire ce numéro.", + "Je t'appelle pour te faire une surprise.", + "Je vais t'appeler.", + "Allô Toon, c'est pour toi.", + ], + 'PowerTie': ["Je t'appellerai plus tard, tu as l'air d'avoir un nœud à l'estomac.", + "Tu te prépares à faire un trait là-dessus?", + "Tu vas te faire cravater.", + "Tu ferais mieux d'apprendre à faire un nœud de cravate.", + "Je vais te nouer la langue!", + "Tu n'as encore jamais vu quelqu'un se faire cravater comme ça!", + "Tu fais attention aux rayures?", + "Je vais te rayer de la carte!", + "Je rayonne!", + "Par les pouvoirs qui me sont conférés, je te raie de la liste.", + ], + 'PowerTrip': ["Fais tes valises, on fait un méga voyage.", + "Tu n'as pas perdu tes manies?", + "C'est une manie que tu as de partir en vacances.", + "Comment se sont passées les vacances?", + "C'est une vraie manie!", + "Ça a l'air méga ennuyeux.", + "Maintenant tu vois qui est le plus puissant!", + "Je suis bien plus puissant que toi.", + "Qui a les méga pouvoirs maintenant ?", + "Tu ne peux pas te battre contre ma puissance.", + "La puissance est corrompue, en particulier dans mon cas.", + ], + 'Quake': ["Tremblons, mes frères.", + "J'ai la tremblote!", + "Je te vois trembler dans tes chaussures.", + "Voilà la terre qui tremble!", + "Celui-ci est en-dehors de l'échelle de Richter.", + "La terre va trembler!", + "Hé, qu'est-ce qui tremble comme ça? Toi!", + "Tu as déjà ressenti un tremblement de terre ?", + "Tu es sur un terrain instable!", + ], + 'RazzleDazzle': ["Chante avec moi.", + "Tu as peur de perdre ton dentier ?", + "Je ne suis pas charmant ?", + "Je vais t'impressionner.", + "Mon dentiste fait un excellent travail.", + "Ils ne sont pas épatants?", + "Difficile de croire qu'ils ne sont pas réels.", + "Ils ne sont pas choquants?", + "Ça va décoiffer.", + "Je me lave les dents après tous les repas.", + "Dis \"Cheese!\"", + ], + 'RedTape': ["Ça va être bien emballé.", + "Tu vas rester collé(e) là un bon moment.", + "J'en ai un plein rouleau.", + "On va voir si tu peux y couper.", + "Ça va devenir collant.", + "J'espère que tu es claustrophobe.", + "Tu es d'un tempérament collant!", + "Je vais t'occuper un peu.", + "Essaie donc de sortir de là.", + "On va voir si ça colle entre nous.", + ], + 'ReOrg': ["Tu n'aimes pas la manière dont j'ai réorganisé les choses?", + "Peut-être qu'un peu plus d'organisation serait de mise.", + "Tout n'est pas si mauvais, tu as juste un peu besoin de réorganisation.", + "Est-ce que tu apprécies mes capacités d'organisation ?", + "J'essaye juste de donner un nouvel aspect aux choses.", + "Tu dois t'organiser!", + "Tu m'as l'air de faire dans la désorganisation.", + "Reste là pendant que je te réorganise.", + "Je vais attendre que tu aies le temps de t'organiser.", + "Ça ne te dérange pas si je réorganise un peu?", + ], + 'RestrainingOrder': ["Tu devrais faire la jonction.", + "Je t'assène une injonction!", + "Tu n'as pas le droit de t'approcher à moins de deux mètres de moi.", + "Tu ferais peut-être mieux de garder tes distances.", + "Tu devrais avoir une injonction.", + Cogs + "! Maîtrisez ce Toon!" + "Essaie de te maîtriser.", + "J'espère que je ne suis pas trop une contrainte pour toi.", + "Voyons si tu peux te libérer de ces contraintes!", + "Je te donne l'injonction de te maîtriser!", + "Pourquoi ne commençons-nous pas par les contraintes de base ?" + ], + 'Rolodex': ["Ta fiche est quelque part là-dedans.", + "Voilà la fiche de la chasse aux nuisibles.", + "Je vais te donner une fiche.", + "Ton numéro est juste là.", + "Je te couvre de A à Z.", + "Tu vas avoir la tête qui tourne.", + "Va donc faire un tour.", + "Attention aux bouts de papier.", + "J'ai des doigts pour trier.", + "Est-ce que c'est comme ça que je peux te contacter ?", + "Je voudrais être certain que nous allons rester en contact.", + ], + 'RubberStamp': ["Je fais toujours bonne impression.", + "Il est important de bien appuyer.", + "Une impression parfaite à chaque fois.", + "Je voudrais que tu imprimes.", + "Tu dois être RETOURNÉ à L'ENVOYEUR.", + "Tu es dans la pile ANNULÉ.", + "Tu es en livraison PRIORITAIRE.", + "Je voudrais être certain que tu as REÇU mon message!", + "Tu ne vas nulle part - tu es en PORT PAYÉ par le DESTINATAIRE.", + "Je veux une réponse URGENTE.", + ], + 'RubOut': ["Et maintenant un acte de disparition.", + "J'ai l'impression de t'avoir perdu quelque part.", + "J'ai décidé de te gommer.", + "Je gomme toujours tous les obstacles.", + "Je vais simplement effacer cette erreur.", + "Je peux faire disparaître tous les ennuis.", + "J'aime les choses nettes et propres.", + "Essaie de mettre la gomme.", + "Je te vois...je ne te vois plus.", + "Cela va finir par pâlir.", + "Je vais éliminer le problème.", + "Laisse-moi m'occuper de tes zones à problèmes.", + ], + 'Sacked':["On dirait que tu vas te faire licencier.", + "L'affaire est dans le sac.", + "Tu as une licence de vol?", + "De chasse ou de pêche ?", + "Mes ennemis vont être à la porte!", + "J'ai le record de Toontown pour les licenciements.", + "On n'a plus besoin de toi ici.", + "Tu as passé assez de temps ici, tu es renvoyé(e)!", + "Laisse-moi te mettre en boîte.", + "Tu ne peux pas te défendre si je veux te mettre dehors!", + ], + 'Schmooze':["Tu ne verras jamais ça venir.", + "Ça fera bien sur toi.", + "Tu as gagné ça.", + "Je ne voulais pas baver.", + "La flatterie mène partout.", + "Je vais en rajouter une couche.", + "C'est le moment d'en rajouter.", + "Je vais me mettre de ton bon côté!", + "Ça mérite une bonne tape dans le dos.", + "Je vais chanter tes louanges.", + "Je suis navré de te faire tomber de ton piédestal, mais...", + ], + 'Shake': ["Tu es juste à l'épicentre.", + "Tu es juste sur une faille.", + "Ça va secouer.", + "Je crois que c'est une catastrophe naturelle.", + "C'est un désastre de proportions sismiques.", + "Celui-ci est en dehors de l'échelle de Richter.", + "C'est le moment de se mettre à l'abri.", + "Tu as un air troublé.", + "Attention la secousse!", + "Je vais te secouer, pas te faire tourner.", + "Ça devrait te secouer.", + "J'ai un bon plan pour s'échapper.", + ], + 'Shred': ["Je dois me débarrasser de quelques déchets.", + "J'augmente ma capacité de traitement.", + "Je crois que je vais me débarrasser de toi maintenant.", + "On va pouvoir détruire les preuves.", + "Il n'y a plus aucune façon de prouver ça maintenant.", + "Vois si tu peux assembler toutes les pièces.", + "Cela devrait te remettre à la bonne taille.", + "Je vais jeter cette idée.", + "Il ne faut pas que ça tombe entre de mauvaises mains.", + "Vite venu, vite parti.", + "Ce n'est pas ton dernier fragment d'espoir ?", + ], + 'Spin': ["Tu veux qu'on aille faire un tour ?", + "À quelle vitesse tournes-tu?", + "Ça va te faire tourner la tête!", + "C'est le tour que prennent les choses.", + "Je vais t'emmener faire un tour.", + "Que feras-tu quand ce sera ton tour ?", + "Surveille-moi ça. Je ne voudrais pas que ça tourne trop vite!", + "Tu vas tourner longtemps comme ça?", + "Mes attaques vont te donner le tournis!", + ], + 'Synergy': ["Je transmets cela au comité.", + "Ton projet a été annulé.", + "Ton budget a été réduit.", + "Nous allons restructurer ton service.", + "J'ai mis ça au vote et tu as perdu.", + "Je viens de recevoir l'accord final.", + "Il n'y a pas de problèmes, il n'y a que des solutions.", + "Je te recontacte à ce sujet.", + "Revenons à cette affaire.", + "Considère que c'est un manque de synergie.", + ], + 'Tabulate': ["Ça ne s'additionne pas!", + "Si je compte bien, tu as perdu.", + "Tu comptes bien toutes les colonnes.", + "Je te fais le total dans un instant.", + "Tu es prêt(e) à compter tout ça?", + "Ta facture est payable dès maintenant.", + "Il est temps de faire une estimation.", + "J'aime bien mettre les choses en ordre.", + "Et les résultats au pointage sont...", + "Ces chiffres devraient être très puissants.", + ], + 'TeeOff': ["Tu ne fais pas le poids.", + "Gare à toi!", + "Je suis vexé.", + "Pourquoi es-tu en colère ?", + "Essaye simplement d'éviter le danger.", + "Scrongneugneu!", + "Tu vas prendre la mouche à tous les coups.", + "Tu es sur mon chemin.", + "J'ai une bonne prise sur la situation.", + "Attention le petit oiseau va se fâcher!", + "Garde un œil sur moi!", + "Ça te dérange si je joue ?", + ], + 'Tremor': ["Tu as senti ça?", + "Tu n'as pas peur d'un petit frémissement n'est-ce pas?", + "Au commencement était le frémissement.", + "Tu as l'air de trembler.", + "Je vais un peu secouer les choses!", + "Tu te prépare à sursauter ?", + "Qu'est-ce qui ne va pas? Tu as l'air d'accuser la secousse.", + "Crainte et tremblements!", + "Pourquoi trembles-tu de peur ?", + ], + 'Watercooler': ["Ça devrait te rafraîchir.", + "Tu ne trouves pas ça rafraîchissant ?", + "Je livre les boissons.", + "Directement du robinet dans ton gosier.", + "C'est quoi le problème, c'est juste de l'eau de source.", + "Ne t'inquiète pas, c'est filtré.", + "Ah, un autre client satisfait.", + "C'est l'heure de ta livraison quotidienne.", + "J'espère que les couleurs ne vont pas déteindre.", + "Tu as envie de boire ?", + "Tout s'en va à la lessive.", + "C'est toi qui paies à boire.", + ], + 'Withdrawal': ["Je crois que tu es à découvert.", + "J'espère que ton compte est suffisamment approvisionné.", + "Prends ça, avec les intérêts.", + "Ton solde n'est pas en équilibre.", + "Tu vas bientôt devoir faire un dépôt.", + "Tu as souffert de la récession économique.", + "Je crois que tu as un passage à vide.", + "Tes finances sont sur le déclin.", + "Je prévois une baisse définitive.", + "C'est un revers de fortune.", + ], + 'WriteOff': ["Laisse-moi augmenter tes pertes.", + "Profitons d'une mauvaise affaire.", + "C'est l'heure d'équilibrer les comptes.", + "Ça ne va pas faire bien dans ton bilan.", + "Je suis à la recherche de quelques dividendes.", + "Tu dois tenir compte de tes pertes.", + "Tu peux oublier les bonus.", + "Je vais mélanger tes comptes.", + "Tu vas avoir quelques pertes.", + "Ça va te faire mal au solde.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "En attente des autres joueurs...", + +# Elevator.py +ElevatorHopOff = "Quitter" +ElevatorStayOff = "Si tu descends, tu devras attendre \nque l'ascenseur se vide ou parte" +ElevatorLeaderOff = "Seul ton chef peut décider du moment où descendre." +ElevatorHoppedOff = "Tu dois attendre le prochain ascenseur" +ElevatorMinLaff = "Il te faut %s rigolpoints pour prendre cet ascenseur" +ElevatorHopOK = "OK" +ElevatorGroupMember = "Seul le chef de ton groupe peut\n décider quand monter" + +# DistributedCogKart.py +KartMinLaff = "Il te faut %s rigolpoints pour monter dans ce kart" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = " SA" +CogsIncModifier = "%s"+ CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "Qui est là?" +DoorWhoAppendix = "qui?" +DoorNametag = "Porte" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Tu as besoin de gags! Va en parler à Tom Tuteur!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Reviens ici quand tu auras vaincu le Laquaistic!" +FADoorCodes_TALK_TO_HQ = "Va chercher ta récompense auprès d'Harry au QG!" +FADoorCodes_WRONG_DOOR_HQ = "Mauvaise porte! Prends l'autre porte pour aller au terrain de jeux!" +FADoorCodes_GO_TO_PLAYGROUND = "Mauvais chemin! Tu dois aller au terrain de jeux!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Marche jusqu'à ce Laquaistic pour te battre avec lui!" +FADoorCodes_TALK_TO_HQ_TOM = "Va chercher ta récompense au QG des Toons!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Fais attention! Il y a un COG là-dedans!" +FADoorCodes_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Cog!n\nConstruis ton déguisement de Cog avec des pièces de l'usine." +FADoorCodes_SB_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Cog!n\nConstruis ton déguisement de Cog avec des pièces de l'usine." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "Tu vas te faire prendre si tu entres ici en Toon! Tu dois d'abord terminer ton déguisement de Caissbot!\n\nTermine ton déguisement de Caissbot en réussissant des défitoons au Pays des Rêves." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "Tu vas te faire attraper si tu rentres là-dedans habillé en Toon! Tu dois d'abord terminer ton déguisement de Loibot !\n\nAssemble ton déguisement de Loibot en terminant les défitoons qui sont après le Pays des Rêves de Donald." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Tank", + "Tank il ne regarde pas, lance-lui un gâteau!"], + + 2200 : ["Audrey", + "Audrey mieux sortir d'ici, voilà les Cogs qui arrivent!"], + + 2300: ["Hadrien", + "Hadrien que quelques pièces Cog et on y va!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdin mauvais goût..."], + 6 : ["Bidule", + "Bidule sais pas, d'où ils viennent tous ces Cogs?"], + 30 : ["Jambon", + "Jambon, ils sont même très bons ces gâteaux pour les Cogs."], + 28: ["Isaïe", + "Isaïe à la gare pour aller faire un tour de tramway."], + 12: ["Jules", + "Jules aurait parié, tu vas me laisser entrer dans un bâtiment Cog et je te donnerai un toonique."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Qui", + "Il y a un mauvais écho par ici, n'est-ce pas?"], + + ["Douglas", + "Douglas à la vanille ça t'intéresse ?"], + + ["Geoffrey", + "Geoffrey bien une petite sieste, laisse-moi entrer."], + + ["Justin", + "Justin petit moment."], + + ["Adhémar", + "Adhémar pas ta voiture ?"], + + ["Annie", + "Annie rien comprendre, pourquoi tu n'ouvres pas?"], + + ["Omer", + "Omer veille, j'ai fini par te trouver."], + + ["Thérèse", + "Thérèse, t'es là sans bouger depuis tout ce temps?"], + + ["Sylvie", + "Sylvie c'est un miracle, laisse-le au moins entrer."], + + ["Aude", + "Aude toilette à la lavande ce matin ?"], + + ["Alex", + "Alex Térieur, j'ai froid dehors."], + + ["Alain", + "Alain Térieur, je voudrais entrer!"], + + ["Justine", + "Justine petite minute, je n'en ai pas pour longtemps."], + + ["Vincent", + "Vincent rien, et repart sans rien."], + + ["Jean", + "Jean ai marre que tu n'ouvres pas cette porte!"], + + ["Firmin", + "Firmin peu la radio tu m'entendrais mieux."], + + ["Geoffroy", + "Geoffroy dehors laisse-moi entrer."], + + ["Jessica", + "Jessica difficiles à traiter, dépêche-toi un peu."], + + ["Djamila", + "Djamila clé sous la porte."], + + ["Emma", + "Emma claqué la porte au nez!!"], + + ["Nicole", + "Nicole rien du tout ça doit rester propre."], + + ["Yann-Adam", + "Yann-Adam le frigo je peux entrer ?"], + + ["Louis", + "Louis pas trop fine, décidément."], + + ["Mélusine", + "Mélusine des Cogs en faillite, au lieu de dormir."], + + ["Kim", + "Kim énerve, à ne pas ouvrir."], + + ["Ella", + "Ella pas envie de descendre ouvrir ?"], + + ["Jean", + "Jean file un pull et j'arrive."], + + ["Roger", + "Roger plus rien dans le frigo, tu peux aller faire les courses?"], + + ["John", + "John Dœuf est déjà passé vendre de la mayonnaise ?"], + + ["Alain", + "Alain d'Issoire! C'est ça, bon dimanche."], + + ["Steve", + "Steve a, j'y vais aussi."], + + ["Elvire", + "Elvire pas sur ses gonds, ta porte."], + + ["Jean", + "Jean, bon, je peux entrer finalement ?"], + + ["Sarah", + "Sarah fraîchit dernièrement, j'ai froid dehors."], + + ["Aïcha", + "Aïcha fait mal aux mains de frapper à ta porte."], + + ["Sarah", + "Sarah croche toujours au téléphone, tu ne veux vraiment pas me parler ?"], + + ["Déborah", + "Déborah, dis, qu'il y a dans ton jardin, je peux les voir ?"], + + ["Eddy", + "Eddy donc toi là-bas, tu vas finir par venir ?"], + + ["Élie", + "Élie quoi? Le journal est déjà arrivé?"], + + ["Mandy", + "Mandy donc tu fais quoi là?"], + + ["Yvon", + "Yvon pas revenir plus tard si tu n'ouvres pas!"], + + ["Isabelle", + "Isabelle toujours à n'importe quelle heure."], + + ["Robin", + "Robin, dis donc, c'est maintenant que tu arrives?"], + + ["Oscar", + "Oscar, il n'est jamais à l'heure, je prendrai le train la prochaine fois."], + + ["Léonard", + "Léonard j'aime pas, j'aime mieux les langoustines - merci quand même pour ton invitation."], + + ["Gérard", + "Gérard, mais rarement vu ça."], + + ["Théa", + "Théa l'heure, pour une fois?"], + + ["Médor", + "Médor, Médor, mais comment veux-tu que je dorme si tu ne me laisses pas entrer ?"], + + ["Stella", + "Stella mais c'est plus là."], + + ["Isidore", + "Isidore que la nuit, il est parti à l'heure qu'il est."], + + ["Élodie", + "Élodie, donc? C'est pas fini?"], + + ["Julien", + "Julien du tout à te donner."], + + ["Yvan", + "Yvan quoi? J'ai besoin de rien."], + + ["Eugène", + "Eugène pas du tout, prend ton temps."], + + ["Sultan", + "Sultan de travail, je ne peux pas dormir."], + + ["André", + "Mais André donc."], + + ["Alphonse", + "Alphonse pas dans l'escalier en venant ouvrir."], + + ["Amélie", + "Amélie donc ce qui est écrit au lieu de redemander."], + + ["Angèle", + "Angèle pas du tout, il ne fait pas froid."], + + ["Aubin", + "Aubin dis donc, quand est-ce que tu arrives?"], + + ["Cécile", + "Cécile est de bonne humeur qu'il vient ouvrir la porte ?"], + + ["Djemila", + "Djemila clé dans la serrure mais ça ne marche pas."], + + ["Éléonore", + "Éléonore maintenant mais j'ai pas sa nouvelle adresse."], + + ["Huguette", + "Huguette si quelqu'un d'autre arrive ?"], + + ["Isolde", + "Isolde pas, tout est au prix fort."], + + ["Jenny", + "Jenny figues ni raisin, l'épicerie a déménagé."], + + ["Jérémie", + "Jérémie le courrier à la poste, maintenant je suis rentré."], + + ["Jimmy", + "Jimmy ton courrier dans la boîte"], + + ["Johnny", + "Johnny connais rien du tout, viens donc voir ça."], + + ["Julie", + "Julie pas très bien ce qui est écrit sur la porte."], + + ["Cathy", + "Cathy donc dit ?"], + + ["Léo", + "Léo lit encore à cette heure-là?"], + + ["Léon", + "Léon-dit, ça ne m'intéresse pas. Je préfère que tu me dises la vérité."], + + ["Maël", + "Maël dit toujours la même chose!"], + + ["Marin", + "Marin du tout, je veux juste te dire bonjour."], + + ["Quentin", + "Quentin est là, on ouvre."], + + ["Sacha", + "Sacha pas, demande-lui directement."], + + ["Stella", + "Stella tu ouvres. Réponds!"], + + ["Théophile", + "Théophile encore une fois, tu ne fais que téléphoner."], + + ["Tudor", + "Tudor tout le temps quand je passe te voir."], + + ["Véra", + "Véra bien qui c'est si tu descends ouvrir."], + + ["Xavier", + "Xavier pas une sonnette la dernière fois?"], + + ["Yann", + "Yann a plus, y'en aura la prochaine fois."], + + ["Yvon", + "Yvon bien, merci de prendre des nouvelles!"], + + ["Odyssée", + "Odyssée quoi toutes ces questions?"], + + ["Thor", + "Thor ait le temps de descendre ouvrir ?"], + + ["Édith", + "Édith a vu l'heure, il est bien temps d'arriver."], + + ["Jean-Aymar", + "Jean-Aymar d'attendre."], + + ["Aubin", + "Aubin dis donc, tu en mets un temps!"], + + ["Ahmed", + "Ahmed dépens, j'ai fini par comprendre."], + + ["Henri", + "Henri encore, de ta dernière blague."], + + ["Aude", + "Aude désespoir, ô rage."], + + ["Ali", + "Ali qu'a tort, comme d'habitude."], + + ["Gilles", + "Gilles est de sauvetage aujourd'hui."], + + ["Hans", + "Hans qui me concerne, j'aimerais bien que tu ouvres la porte."], + + ["Roméo", + "Roméo lendemain ce que tu ne peux pas faire aujourd'hui."], + + ["Hildéphonse", + "Hildéphonse la porte."], + + ["Helmut", + "Helmut le pain de la bouche!"], + + ["Hercule", + "Hercule la voiture au fond de la cour."], + + ["Mylène", + "Mylène, mi-coton."], + + ["Célestin", + "Célestin ? Non c'est l'ouest."], + + ["Ondine", + "Ondine où ce soir ?"], + + ["Laurent", + "Laurent-Outang, je cherche le zoo?"], + + ["Anne", + "Anne pas dire."], + + ["Edgar", + "Edgar pas là, tu gênes."], + + ["José", + "José pas le dire."], + + ["Samira", + "Samira pas c'est trop petit."], + + ["Humphrey", + "Humphrey peur celui-là!"], + + ["Saturnin", + "Saturnin peu trop vite."], + + ["Juste", + "Juste pour voir."], + + ["Aziza", + "Aziza pouvait durer!"], + + ["Jonathan", + "Jonathan que toi."], + + ["Aubin", + "Aubin, ça alors! Je ne comptais pas sur toi."], + + ["Yamamoto", + "Yamamoto qu'a dérapé, je cherche un garage."], + + ["Stanislav", + "Stanislav tous les matins sous sa douche."], + + ["Yvan-Dédé", + "Yvan-Dédé, voitures d'occasion."], + + ["Céline", + "Céline évitable."], + + ["Jean-Philémon", + "Jean-Philémon blouson et je viens."], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Salut, %!", + "Youhouu %,\nravi de te voir.", + "Je suis content que tu sois là aujourd'hui!", + "Bien le bonjour, %.", + ] + +SharedChatterComments = [ + "C'est un super nom, %.", + "J'aime bien ton nom.", + "Fais attention aux" + Cogs + "." + "On dirait que le tramway arrive!", + "Je dois jouer à un jeu du tramway pour avoir quelques morceaux de tarte!", + "Quelquefois, je joue aux jeux du tramway juste pour manger de la tarte aux fruits!", + "Ouf, je viens d'arrêter un groupe de" + Cogs + ". J'ai besoin de repos!", + "Aïe, certains de ces" + Cogs + " sont costauds!", + "On dirait que tu t'amuses.", + "Oh bon sang, quelle bonne journée.", + "J'aime bien ce que tu portes.", + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Amuse-toi bien dans mon quartier.", + "J'espère que tu profites bien de ton séjour à Toontown!", + "J'ai entendu dire qu'il neigeait dans le Glagla.", + "Est-ce que tu as fait un tour de tramway aujourd'hui?", + "J'aime bien rencontrer des nouveaux.", + "Aïe, il y a beaucoup de " + Cogs + " dans le Glagla.", + "J'aime bien jouer à chat. Et toi ?", + "Les jeux du tramway sont amusants.", + "J'aime bien faire rire les gens.", + "J'adore aider mes contacts.", + "Hum, serais-tu perdu(e)? N'oublie pas que ta carte est dans ton journal de bord.", + "Essaie de ne pas te noyer dans la paperasserie des " + Cogs + ".", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + "Si tu appuies sur la touche \"page précédente\", tu peux regarder vers le haut!", + "Si tu aides à reprendre des bâtiments aux Cogs, tu peux gagner une étoile de bronze!", + "Si tu appuies sur la touche de tabulation, tu peux voir différents points de vue de ce qui t'entoure!", + "Si tu appuies sur la touche Ctrl, tu peux sauter!", + ] + +SharedChatterGoodbyes = [ + "Je dois partir maintenant, au revoir!", + "Je crois que je vais aller faire un jeu du tramway.", + "Eh bien, au revoir. À bientôt, %!", + "Il vaudrait mieux que je me dépêche et que je m'occupe d'arrêter ces " + Cogs + ".", + "C'est l'heure d'y aller.", + "Désolé, je dois partir.", + "Au revoir.", + "À plus tard,%!", + "Je crois que je vais aller m'entraîner à lancer des petits gâteaux.", + "Je vais me joindre à un groupe et arrêter des " + Cogs + ".", + "Je suis content(e) de t'avoir vu(e) aujourd'hui, %.", + "J'ai beaucoup de choses à faire. Je ferais mieux de m'y mettre.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bienvenue à Toontown centre.", + "Salut, je m'appelle " + Mickey + ". Et toi ?", + ], + [ # Comments + "Dis donc, as-tu vu " + Donald + "?", + "Je vais aller regarder le brouillard se lever sur les quais " + Donald + ".", + "Si tu vois mon copain " + Goofy + ", dis-lui bonjour de ma part.", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + ], + [ # Goodbyes + "Je vais au pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Minnie +"!", + "On dirait que c'est l'heure du dîner pour " + Pluto + ".", + "Je crois que je vais aller nager aux quais " + Donald + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bienvenue au Pays musical.", + "Salut, je m'appelle " + Minnie + ". Et toi ?" + ], + [ # Comments + "Les collines sont animées par les notes de musique!", + # the merry no longer goes round + #"N'oublie pas d'essayer le grand manège tourne-disques!", + "Tu as une chouette tenue, %.", + "Dis donc, as-tu vu " + Mickey + "?", + "Si tu vois mon ami " + Goofy + ", dis-lui bonjour de ma part.", + "Aïe, il y a beaucoup de " + Cogs + " près du Pays des rêves de " + Donald + ".", + "J'ai entendu dire qu'il y a du brouillard sur les quais " + Donald + ".", + "N'oublie pas d'essayer le labyrinthe dans le jardin de " + Daisy + ".", + "Je crois bien que je vais aller chercher quelques airs de musique.", + "Hé %, regarde donc par là-bas.", + "J'aime bien entendre de la musique.", + "Je parie que tu ne savais pas que le Pays musical de Minnie est aussi appelé le Haut-Bois? Hi hi!", + "J'aime bien jouer aux imitations. Et toi ?", + "J'aime bien faire rire les gens.", + "Oh là là, ça fait mal aux pieds de trotter toute la journée avec des talons!", + "Belle chemise, %.", + "Est-ce que c'est un bonbon par terre ?", + ], + [ # Goodbyes + "Aïe, je suis en retard pour mon rendez-vous avec " + Mickey + "!", + "On dirait que c'est l'heure du dîner pour " + Pluto + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +DaisyChatter = ( + [ # Greetings + "Bienvenue dans mon jardin!", + "Bonjour, je m'appelle"+Daisy+". Comment t'appelles-tu?", + "Ravi de faire ta connaissance, %!", + ], + [ # Comments + "Ma fleur qui a gagné le prix est au milieu du labyrinthe.", + "J'adore me promener dans le labyrinthe.", + "Je n'ai pas vu"+Goofy+" de la journée.", + "Je me demande où"+Goofy+" se trouve.", + "As-tu vu"+Donald+"?Il est introuvable.", + "Si tu vois mon ami"+Minnie+", dis-lui \"Bonjour\" de ma part.", + "Meilleurs sont tes outils de jardinage, et plus belles seront tes plantes.", + "Il y a beaucoup trop de"+Cogs+" par ici"+lDonaldsDock+".", + "Tu feras le bonheur de tes plantes si tu les arroses tous les jours.", + "Pour faire pousser une pâquerette rose, plante un bonbon jaune et un bonbon rouge ensemble.", + "C'est facile de faire pousser une pâquerette jaune, tu n'as qu'à planter un bonbon jaune.", + "Si tu vois du sable sous une plante, c'est qu'elle a besoin d'eau - faute de quoi elle va se faner!" + ], + [ # Goodbyes + "Je vais au Pays musical pour voir %s!" % Minnie, + "Je suis en retard pour mon pique-nique avec %s!" % Donald, + "Je crois que je vais aller nager à"+lDonaldsDock+".", + "Oh, je commence à avoir sommeil. Je crois que je vais aller au Pays des Rêves", + ] + ) + +ChipChatter = ( + [ # Greetings + "Bienvenue dans %s !" % lOutdoorZone, + "Bonjour, je m'appelle" + Chip + ". Et toi, comment t'appelles-tu ?", + "Non, je suis" + Chip + ".", + "Je suis content de te voir % !", + "Nous sommes Tic et Tac !", + ], + [ # Comments + "J'aime le golf.", + "Nous avons les meilleurs glands de Toontown.", + "Les trous de golf dotés de volcans sont les plus difficiles pour moi.", + ], + [ # Goodbyes + "Nous allons dans le" + lTheBrrrgh +"pour jouer avec %s." % Pluto, + "Nous rendrons visite à %s et pourrons le réparer." % Donald, + "Je crois que je vais aller me baigner dans" + lDonaldsDock + ".", + "Oh, j'ai un peu sommeil. Je pense que je vais aller au Pays des Rêves.", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "Je suis content de te voir % !", + "Bonjour, je m'appelle" + Dale + ". Et toi, comment t'appelles-tu ?", + "Salut, je suis" + Chip + ".", + "Bienvenue dans %s !" % lOutdoorZone, + "Nous sommes Tic et Tac !", + ], + [ # Comments + "J'aime les pique-niques.", + "Les glands sont délicieux. Goûte.", + "Ces moulins à vent sont difficiles aussi.", + ], + [ # Goodbyes + "Hihihi" + Pluto + "est un compagnon de jeu amusant.", + "D'accord, réparons %s." % Donald, + "Une baignade. Quelle idée rafraîchissante !", + "Je suis de plus en plus fatigué et ferais bien une petite sieste.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bienvenue au jardin de " + Daisy + ".", + "Salut, je m'appelle " + Goofy + ". Et toi ?", + "Wof, je suis content de te voir, %!", + ], + [ # Comments + "Bon sang, c'est facile de se perdre dans le labyrinthe!", + "N'oublie pas d'essayer le labyrinthe tant que tu es ici.", + "Je n'ai pas vu " + Daisy + " de la journée.", + "Je me demande où se trouve " + Daisy + ".", + "Dis donc, as-tu vu " + Donald + "?", + "Si tu vois mon ami " + Mickey + ", dis-lui bonjour de ma part.", + "Oh! J'ai oublié le petit déjeuner de " + Mickey + "!", + "Wof, il y a beaucoup de " + Cogs + " près des quais " + Donald + ".", + "On dirait que " + Daisy + " a planté de nouvelles fleurs dans son jardin.", + "À la succursale du Glagla de ma boutique à gags, les lunettes hypnotiques sont en vente pour seulement 1 bonbon!", + "La boutique à gags de Dingo propose les meilleurs blagues, astuces et chatouilles de tout Toontown!", + "À la boutique à gags de Dingo, chaque tarte à la crème est garantie faire rire ou tes bonbons te seront remboursés!" + ], + [ # Goodbyes + "Je vais au Pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Donald + "!", + "Je crois que je vais aller nager aux quais " + Donald + ".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +GoofySpeedwayChatter = ( + [ # Greetings + "Bienvenue au "+lGoofySpeedway+".", + "Salut, je m'appelle "+Goofy+". Et toi ?", + "Ouah, sympa de te voir %!", + ], + [ # Comments + "Bon sang, j'ai vu une super course tout à l'heure.", + "Attention aux peaux de banane sur la piste!", + "Est-ce que tu as fait des améliorations sur ton kart récemment ?", + "Nous venons d'acheter de nouvelles jantes dans le magasin de karts.", + "Dis-donc, tu as vu "+Donald+"?", + "Si tu vois mon ami "+Mickey+", dis-lui bonjour de ma part.", + "Oh! J'ai oublié de préparer le petit déjeuner de "+Mickey+"!", + "Bon sang, c'est vrai qu'il y a un tas de "+Cogs+" sur les "+lDonaldsDock+".", + "À la succursale du Glagla de ma boutique à gags, les lunettes hypnotiques sont en vente pour seulement 1 bonbon!", + "La boutique à gags de Dingo propose les meilleurs blagues, astuces et chatouilles de tout Toontown!", + "À la boutique à gags de Dingo, chaque tarte à la crème est garantie de te faire rire ou tes bonbons te seront remboursés !" + ], + [ # Goodbyes + "Je vais au Pays Musical pour voir %s!" % Mickey, + "Aïe, je suis en retard pour mon rendez-vous avec %s!" % Donald, + "Je crois que je vais aller nager aux "+lDonaldsDock+".", + "C'est l'heure de faire la sieste. Je vais au Pays des rêves.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bienvenue au Pays des rêves.", + "Salut, je m'appelle " + Donald + ". Et toi ?" + ], + [ # Comments + "Cet endroit me donne quelquefois la chair de poule.", + "N'oublie pas d'essayer le labyrinthe dans le jardin de" + Daisy + ".", + "Oh, bon sang! Quelle bonne journée.", + "Dis donc, as-tu vu" + Mickey + "?", + "Si tu vois mon copain" + Goofy + ", dis-lui bonjour de ma part." + "Je crois bien que je vais aller à la pêche cet après-midi.", + "Aïe, il y a beaucoup de " + Cogs + " près des quais " + Donald + ".", + "Hé dis donc, tu n'as pas encore fait un tour de bateau avec moi aux quais " + Donald + "?" + "Je n'ai pas vu " + Daisy + " de la journée.", + "J'ai entendu dire que " + Daisy + " a planté de nouvelles fleurs dans son jardin." + "Coin coin.", + ], + [ # Goodbyes + "Je vais au Pays musical voir " + Minnie + "!", + "Aïe, je suis en retard pour mon rendez-vous avec " + Daisy + "!", + "Je crois que je vais aller nager près de mes quais.", + "Je crois que je vais aller faire un tour de bateau près de mes quais.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Nouvel(le) ami(e)" +FriendsListPanelSecrets = "Secrets" +FriendsListPanelOnlineFriends = "CONTACTS\nEN LIGNE" +FriendsListPanelAllFriends = "TOUS\nLES CONTACTS" +FriendsListPanelIgnoredFriends = "TOONS\nIGNORÉS" +FriendsListPanelPets = "ANIMAUX FAMILIERS\nA PROXIMITÉ" +FriendsListPanelPlayers = "TOUS LES AMIS\nDU JOUEUR" +FriendsListPanelOnlinePlayers = "AMIS DU JOUEUR\nEN LIGNE" + +FriendInviterClickToon = "Clique sur le Toon avec lequel tu souhaites devenir ami.\n\n(Tu as %s amis)" + +# Support DISL account friends +FriendInviterToon = "Toon" +FriendInviterThatToon = "Ce Toon" +FriendInviterPlayer = "Joueur" +FriendInviterThatPlayer = "Ce joueur" +FriendInviterBegin = "Quel genre d'ami aimerais-tu te faire ?" +FriendInviterToonFriendInfo = "Un ami seulement dans Toontown" +FriendInviterPlayerFriendInfo = "Un ami à travers le réseau Disney.com" +FriendInviterToonTooMany = "Tu as trop d'amis Toon pour pouvoir en ajouter un nouveau. Tu devras supprimer des amis Toon si tu veux devenir ami avec %s. Tu pourrais aussi essayer de t'en faire un ami de jeu." +FriendInviterPlayerTooMany = "Tu as trop d'amis de jeu pour pouvoir en ajouter un nouveau. Tu devras supprimer des amis de jeu si tu veux devenir ami avec %s. Tu pourrais aussi essayer de t'en faire un ami Toon." +FriendInviterToonAlready = "%s est déjà ton ami Toon." +FriendInviterPlayerAlready = "%s est déjà ton ami de jeu." +FriendInviterStopBeingToonFriends = "Cesser d'être ami Toon" +FriendInviterStopBeingPlayerFriends = "Cesser d'être ami de jeu" +FriendInviterEndFriendshipToon = "Es-tu sûr de vouloir cesser d'être ami Toon avec %s ?" +FriendInviterEndFriendshipPlayer = "Es-tu sûr de vouloir cesser d'être ami de jeu avec %s ?" +FriendInviterRemainToon = "\n(Tu resteras néanmoins ami Toon avec %s)" +FriendInviterRemainPlayer = "\n(Tu resteras néanmoins ami de jeu avec %s)" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Désolé, tu ne peux pas avancer parce que le téléchargement de %(phase)s n'en est qu'à %(percent)s% %.\n\nRéessaie plus tard." + +# TeaserPanel.py +TeaserTop = "Désolé! Tu n'as pas accès à ceci pendant l'essai gratuit.\n\nInscris-toi maintenant et profite de ces super fonctionnalités :" +TeaserBottom = "Subscribe now and enjoy these great features:" +TeaserOtherHoods = "Visite les 6 quartiers exceptionnels!" +TeaserTypeAName = "Inscris le nom que tu préfères pour ton Toon!" +TeaserSixToons = "Crée jusqu'à 6 Toons par compte!" +TeaserOtherGags = "Additionne 6 niveaux d'habileté\ndans 6 séries de gags différentes!" +TeaserClothing = "Achète des vêtements originaux\npour personnaliser ton Toon!" +TeaserFurniture = "Achète et dispose des meubles dans ta maison!" +TeaserCogHQ = "Infiltre des zones dangereuses sur\nle territoire des Cogs!" +TeaserSecretChat = "Échange des secrets avec tes contacts\npour pouvoir discuter en ligne avec eux!" +TeaserCardsAndPosters = "Participe aux concours et compétitions gagne des trophées et \naugmente ta reserve des rigolpoints! \nTon nom apparaîtra sur www.toontown.fr" +TeaserHolidays = "Participe à des événements spéciaux et\npassionnants et à des fêtes!" +TeaserQuests = "Relève des centaines de défitoons pour sauver Toontown!" +TeaserEmotions = "Achète des émotions pour rendre ton\nToon plus expressif!" +TeaserMinigames = "Joue aux 8 sortes de mini jeux!" +TeaserKarting = "Fais la course contre d'autres Toons dans de super karts!" +TeaserKartingAccessories = " Personnaliseton kart avec des accessoiressuper cool." +TeaserTricks = " Entraîne ton Doudouà faire des tourspour\nqu'il t'aide dans les combats !" +TeaserGardening = "Plante des fleurs, des statues et des arbres à gags pour embellir\n ta propriété." +TeaserRental = "Loue des articles de fête amusants pour ta propriété !" +TeaserBigger = "Achète des articles Toon meilleurs et plus gros !" +TeaserTricks = "Entraîne ton Doudou à faire des tours pour t'aider dans le combat !" +TeaserSpecies = "Crée des Toons singe, cheval et ours, et joue avec !" +TeaserFishing = "Collectionne toutes les espèces de poissons !" +TeaserGolf = "Joue sur des terrains de golf complètement dingues !" +TeaserSubscribe = "S'inscrire maintenant" +TeaserContinue = "Continuer l'essai" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Téléchargement de: %s" +DownloadWatcherInitializing = "Initialisation du téléchargement..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Initialisation", + 1 : "Panda", + 2 : "Moteur", + 3 : "Faire un Toon", + 3.5 : "Toontoriel", + 4 : "Terrain de jeux", + 5 : "Rues", + 5.5 : "Domaines", + 6 : "Quartiers I", + 7 : "Bâtiments" + Cog, + 8 : "Quartiers II", + 9 : "QG Vendibot", + 10 : "QG Caissbot", + 11 : lLawbotHQ, + 12 : Bossbot + " HQ", + 13 : "Parties", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s sur %(total)s)" +LauncherStartingMessage = "Lancement de Toontown en ligne de Disney..." +LauncherDownloadFile = "Téléchargement des mises à jour:" + LauncherProgress + "..." +LauncherDownloadFileBytes = "Téléchargement des mises à jour:" + LauncherProgress + " : %(bytes)s" +LauncherDownloadFilePercent = "Téléchargement des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherDecompressingFile = "Décompression des mises à jour:" + LauncherProgress + "..." +LauncherDecompressingPercent = "Décompression des mises à jour:" + LauncherProgress + ". : %(percent)s% %" +LauncherExtractingFile = "Extraction des mises à jour:" + LauncherProgress + "..." +LauncherExtractingPercent = "Extraction des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherPatchingFile = "Application des mises à jour:" + LauncherProgress + "..." +LauncherPatchingPercent = "Application des mises à jour:" + LauncherProgress + " : %(percent)s% %" +LauncherConnectProxyAttempt = "En cours de connexion à Toontown: %s (proxy : %s) essai : %s" +LauncherConnectAttempt = "En cours de connexion à Toontown: %s essai %s" +LauncherDownloadServerFileList = "Mise à jour de Toontown..." +LauncherCreatingDownloadDb = "Mise à jour de Toontown..." +LauncherDownloadClientFileList = "Mise à jour de Toontown..." +LauncherFinishedDownloadDb = "Mise à jour de Toontown..." +LauncherStartingToontown = "Lancement de Toontown..." +LauncherStartingGame = "Lancement de Toontown..." +LauncherRecoverFiles = "Mise à jour de Toontown. Récupération des fichiers..." +LauncherCheckUpdates = "Recherche de mises à jour pour "+ LauncherProgress +LauncherVerifyPhase = "Mise à jour de Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Faire un\nToon" +AvatarChoicePlayThisToon = "Jouer\navec ce Toon" +AvatarChoiceSubscribersOnly = "S'inscrire\n\n\n\nMaintenant!" +AvatarChoiceDelete = "Supprimer" +AvatarChoiceDeleteConfirm = "Cela va supprimer %s pour toujours." +AvatarChoiceNameRejected = "Nom\nrefusé" +AvatarChoiceNameApproved = "Nom\naccordé!" +AvatarChoiceNameReview = "En cours\nd'examen" +AvatarChoiceNameYourToon = "Donne un nom\nà ton Toon!" +AvatarChoiceDeletePasswordText = "Attention! Cela va supprimer %s pour toujours. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteConfirmText = "Attention! Cela va supprimer %(name)s pour toujours. Si tu es certain(e) de vouloir faire cela, entre \"%(confirm)s\" et clique sur OK." +AvatarChoiceDeleteConfirmUserTypes = "supprimer" +AvatarChoiceDeletePasswordTitle = "Supprimer le Toon ?" +AvatarChoicePassword = "Mot de passe" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Ce mot de passe ne semble pas correspondre. Pour supprimer ce Toon, entre ton mot de passe." +AvatarChoiceDeleteWrongConfirm = "Tu n'as pas entré le bon mot. Pour supprimer %(name)s, entre \"%(confirm)s\" et clique sur OK. N'entre pas les guillemets. Clique sur Annuler si tu as changé d'avis." + +# AvatarChooser.py +AvatarChooserPickAToon = "Choisis un Toon pour jouer" +AvatarChooserQuit = lQuit + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Appelez le Service clients au %s. " +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nSi vous avez besoin d'aide, vous pouvez appeler le service clients au %s." +TTAccountIntractibleError = "Une erreur s'est produite." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', + 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc',] +DateOfBirthEntryDefaultLabel = "Date de naissance" + + +# AchievePage.py +AchievePageTitle = "Réussites\n (Bientôt disponible)" + +# PhotoPage.py +PhotoPageTitle = "Photo\n (Bientôt disponible)" + +# BuildingPage.py +BuildingPageTitle = "Bâtiments\n (Bientôt disponible)" + +# InventoryPage.py +InventoryPageTitle = "Gags" +InventoryPageDeleteTitle = "SUPPRIMER LES GAGS" +InventoryPageTrackFull = "Tu as tous les gags de la série %s." +InventoryPagePluralPoints = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageSinglePoint = "Tu auras un nouveau gag de la série \n%(trackName)s lorsque tu\nauras %(numPoints)s points de %(trackName)s en plus." +InventoryPageNoAccess = "Tu n'as pas encore accès à la série %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "Toons SOS" + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Restant %s" +# PartiesPage.py +PartiesPageTitle = "Fêtes" +PartiesPageHostTab = "Organiser" +PartiesPageInvitedTab = "Invitations" +PartiesPageTitleHost = "Ma prochaine fête" +PartiesPageTitleInvited = "Invitations pour la fête" + +# MapPage.py +MapPageTitle = "Carte" +MapPageBackToPlayground = "au terrain de jeux" +MapPageBackToCogHQ = "Retour au QG des Cogs" +MapPageGoHome = "à la maison" +# hood name, street name +MapPageYouAreHere = "Tu es à: %s\n%s" +MapPageYouAreAtHome = "Tu es dans\nta propriété." +MapPageYouAreAtSomeonesHome = "Tu es chez %s." +MapPageGoTo = "Aller chez\n%s." + +# OptionsPage.py +OptionsPageTitle = "Options" +OptionsPagePurchase = "S'inscrire!" +OptionsPageLogout = "Se déconnecter" +OptionsPageExitToontown = "Quitter Toontown" +OptionsPageMusicOnLabel = "Musique activée." +OptionsPageMusicOffLabel = "Musique désactivée." +OptionsPageSFXOnLabel = "Effets sonores activés." +OptionsPageSFXOffLabel = "Effets sonores désactivés." +OptionsPageFriendsEnabledLabel = "Demandes de nouveaux contacts acceptées." +OptionsPageFriendsDisabledLabel = "Demandes de nouveaux contacts non acceptées." +OptionsPageSpeedChatStyleLabel = "Couleur du Chat rapide" +OptionsPageDisplayWindowed = "dans une fenêtre" +OptionsPageSelect = "Choisir" +OptionsPageToggleOn = "Activer" +OptionsPageToggleOff = "Désactiver" +OptionsPageChange = "Modifier" +OptionsPageDisplaySettings = "Affichage: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Affichage: %(screensize)s" +OptionsPageExitConfirm = "Quitter Toontown ?" + +DisplaySettingsTitle = "Réglages d'affichage" +DisplaySettingsIntro = "Les réglages suivants sont utilisés pour configurer l'affichage de Toontown sur votre ordinateur. Il n'est sans doute pas indispensable de les modifier sauf si vous avez un problème." +DisplaySettingsIntroSimple = "Vous pouvez accroître la résolution d'écran pour améliorer la lisibilité du texte et des graphiques de Toontown, mais en fonction de votre carte graphique, certaines valeurs plus élevées risquent d'affecter le bon fonctionnement du jeu, voire de l'empêcher complètement de fonctionner." + +DisplaySettingsApi = "Interface graphique:" +DisplaySettingsResolution = "Résolution:" +DisplaySettingsWindowed = "Dans une fenêtre" +DisplaySettingsFullscreen = "Plein écran" +DisplaySettingsApply = "Appliquer" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Lorsque vous cliquez sur OK, les réglages d'affichage sont modifiés. Si la nouvelle configuration ne s'affiche pas correctement sur votre ordinateur, l'affichage revient automatiquement à sa configuration d'origine après %s secondes." +DisplaySettingsAccept = "Cliquez sur OK pour conserver les nouveaux réglages ou sur Annuler pour revenir aux valeurs précédentes. Si vous ne cliquez sur rien, les réglages reviennent automatiquement aux valeurs précédentes après %s secondes." +DisplaySettingsRevertUser = "Vos précédents réglages d'affichage ont été restaurés." +DisplaySettingsRevertFailed = "Les réglages d'affichage sélectionnés ne peuvent pas fonctionner sur votre ordinateur. Vos précédents réglages d'affichage ont été restaurés." + +# TrackPage.py +TrackPageTitle = "Entraînement à une série de gags" +TrackPageShortTitle = "Entraînement\naux gags" +TrackPageSubtitle = "Termine des défitoons pour apprendre à utiliser de nouveaux gags!" +TrackPageTraining = "Tu t'entraînes pour utiliser les gags %s. \nLorsque tu auras terminé les 16 défis, tu\npourras utiliser les gags %s lors des combats." +TrackPageClear = "Tu ne t'entraînes pour aucune série de gags actuellement." +TrackPageFilmTitle = "Entraînement\naux gags %s\n." +TrackPageDone = "FIN" + +# QuestPage.py +QuestPageToonTasks = "Défitoons" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nTo: %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nFrom: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nTo choose, go see:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Choisis" +QuestPageLocked = "Locked" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Une rue" +QuestPosterHQLocationName = "Un quartier" + +QuestPosterTailor = "Tailleur" +QuestPosterTailorBuildingName = "Boutique de prêt-à-porter" +QuestPosterTailorStreetName = "Un terrain de jeux" +QuestPosterTailorLocationName = "Un quartier" +QuestPosterPlayground = "Sur le terrain de jeux" +QuestPosterAtHome = "Chez toi" +QuestPosterInHome = "Dans ta maison" +QuestPosterOnPhone = "Sur ton téléphone" +QuestPosterEstate = "Dans ta propriété" +QuestPosterAnywhere = "N'importe où" +QuestPosterAuxTo = "à:" +QuestPosterAuxFrom = "depuis:" +QuestPosterAuxFor = "pour:" +QuestPosterAuxOr = "ou:" +QuestPosterAuxReturnTo = "Retourner à:" +QuestPosterLocationIn = " à" +QuestPosterLocationOn = " à" +QuestPosterFun = "Juste pour s'amuser!" +QuestPosterFishing = "ALLER PÊCHER" +QuestPosterComplete = "TERMINÉ" + +# ShardPage.py +ShardPageTitle = "Districts" +ShardPageHelpIntro = "Chaque district est une copie du monde de Toontown." +ShardPageHelpWhere = " Tu es actuellement dans le district de \"%s\"." +ShardPageHelpWelcomeValley = " Tu es actuellement dans le district de la \"Vallée de la Bienvenue\", dans \"%s\"." +ShardPageHelpMove = " Pour aller dans un nouveau district, clique sur son nom." + +ShardPagePopulationTotal = "Population totale de Toontown:\n%d" +ShardPageScrollTitle = "Nom Population" +ShardPageLow = "Calme" +ShardPageMed = "Idéal" +ShardPageHigh = "Complet" +ShardPageChoiceReject = "Désolé, ce district est complet. Merci d'en essayer un autre." + +# SuitPage.py +SuitPageTitle = "Galerie des Cogs" +SuitPageMystery = "???" +SuitPageQuota = "%s sur %s" +SuitPageCogRadar = "%s présents" +SuitPageBuildingRadarS = "Bâtiment %s" +SuitPageBuildingRadarP = "Bâtiments %s" + +# DisguisePage.py +DisguisePageTitle = "Déguisement de\n" + Cog +DisguisePageMeritBar = "Avancement au mérite" +DisguisePageMeritAlert = "Prêt pour la\npromotion!" +DisguisePageCogLevel = "Niveau %s" +DisguisePageMeritFull = "Plein" +DisguisePageMeritBar = "Avancement au mérite" +DisguisePageCogPartRatio = "%d/%d" + +# FishPage.py +FishPageTitle = "Pêche" +FishPageTitleTank = "Seau de pêche" +FishPageTitleCollection = "Album de pêche" +FishPageTitleTrophy = "Trophées de pêche" +FishPageWeightStr = "Poids:" +FishPageWeightLargeS = "%dkg" +FishPageWeightLargeP = "%dkg" +FishPageWeightSmallS = " %dg" +FishPageWeightSmallP = " %dg" +FishPageWeightConversion = 16 +FishPageValueS = "Valeur: %d bonbon" +FishPageValueP = "Valeur: %d bonbons" +FishPageTotalValue = "" +FishPageCollectedTotal = "Espèces de poissons pêchées: %d sur %d" +FishPageRodInfo = "Canne %s \n%d - %d livres" +FishPageTankTab = "Seau" +FishPageCollectionTab = "Album" +FishPageTrophyTab = "Trophées" + +FishPickerTotalValue = "Seau: %s / %s\nValeur: %d bonbons" + +UnknownFish = "???" + +FishingRod = "Canne %s" +FishingRodNameDict = { + 0 : "Brindille", + 1 : "Bambou", + 2 : "Bois dur", + 3 : "Acier", + 4 : "Or", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Vairon", + 2 : "Poisson", + 3 : "Poisson volant", + 4 : "Requin", + 5 : "Espadons", + 6 : "Épaulard", + } + +# GardenPage.py +GardenPageTitle = "Gardening" +GardenPageTitleBasket = "Panier de fleurs" +GardenPageTitleCollection = "Album de fleurs" +GardenPageTitleTrophy = "Trophées de jardinage" +GardenPageTitleSpecials = "Offres spéciales jardinage" +GardenPageBasketTab = "Panier" +GardenPageCollectionTab = "Album" +GardenPageTrophyTab = "Trophées" +GardenPageSpecialsTab = "Offres spéciales" +GardenPageCollectedTotal = "Variétés de fleurs rassemblées: %d sur %d" +GardenPageValueS = "Valeur: %d bonbon" +GardenPageValueP = "Valeur: %d bonbons" +FlowerPickerTotalValue = "Panier: %s / %s\nValeur: %d bonbons" +GardenPageShovelInfo = "%s Pelle: %d / %d\n" +GardenPageWateringCanInfo = "%s Arrosoir: %d / %d" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Customiser mon kart" +KartPageTitleRecords = "Meilleurs records personnels" +KartPageTitleTrophy = "Trophées de course" +KartPageCustomizeTab = "Customiser" +KartPageRecordsTab = "Records" +KartPageTrophyTab = "Trophée" +KartPageTrophyDetail = "Trophée %s : %s" +KartPageTickets = "Tickets:" +KartPageConfirmDelete = "Supprimer l'accessoire ?" + +#plural +KartShtikerDelete = "Supprimer" +KartShtikerSelect = "Choisir une catégorie" +KartShtikerNoAccessories = "Aucun accessoire acheté" +KartShtikerBodyColors = "Couleurs du kart" +KartShtikerAccColors = "Couleurs des accessoires" +KartShtikerEngineBlocks = "Accessoires du capot" +KartShtikerSpoilers = "Accessoires du coffre" +KartShtikerFrontWheelWells = "Accessoires des roues avant" +KartShtikerBackWheelWells = "Accessoires des roues arrière" +KartShtikerRims = "Accessoires des jantes" +KartShtikerDecals = "Accessoires décalcomanie" +#singluar +KartShtikerBodyColor = "Couleur du kart" +KartShtikerAccColor = "Couleur de l'accessoire" +KartShtikerEngineBlock = "Capot" +KartShtikerSpoiler = "Coffre" +KartShtikerFrontWheelWell = "Roue avant" +KartShtikerBackWheelWell = "Roue arrière" +KartShtikerRim = "Jante" +KartShtikerDecal = "Décalcomanie" + +KartShtikerDefault = "%s par défaut" +KartShtikerNo = "Aucun accessoire de %s" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Choisir" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = "Toonique te permet de soigner les autres Toons lors d'une bataille." +TrackChoiceGuiTRAP = "Les pièges sont des gags puissants qui doivent être utilisés avec les leurres." +TrackChoiceGuiLURE = "Utilise les leurres pour assommer les Cogs ou les attirer dans des pièges." +TrackChoiceGuiSOUND = "Les gags de tapage affectent tous les Cogs mais ne sont pas très puissants." +TrackChoiceGuiDROP = "Les gags de chute font beaucoup de dégâts mais ne sont pas très précis." + +# EmotePage.py +EmotePageTitle = "Expressions / Émotions" +EmotePageDance = "Tu as construit la séquence de danse suivante:" +EmoteJump = "Saut" +EmoteDance = "Danse" +EmoteHappy = "Content(e)" +EmoteSad = "Triste" +EmoteAnnoyed = "Agacement" +EmoteSleep = "Sommeil" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNiveau %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Tu ne peux pas quitter le terrain de jeux tant que ton rigolmètre ne sourit pas!" + +# InventoryNew.py +InventoryTotalGags = "Total des gags\n%d / %d" +InventroyPinkSlips = "%s Avis de licenciement" +InventroyPinkSlip = "1 Avis de licenciement" +InventoryDelete = "SUPPRIMER" +InventoryDone = "TERMINÉ" +InventoryDeleteHelp = "Clique sur un gag pour le SUPPRIMER." +InventorySkillCredit = "Crédit d'habileté: %s" +InventorySkillCreditNone = "Crédit d'habileté: Aucun" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Précision : %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "%(nextExp)s à terminer !" +InventoryGuestExp = "Nombre maxi d'invités" +GuestLostExp = "Plus que le nombre maxi d'invités" +InventoryAffectsOneCog = "Affecte : Un"+ Cog +InventoryAffectsOneToon = "Affecte : Un Toon" +InventoryAffectsAllToons = "Affecte : tous les Toons" +InventoryAffectsAllCogs = "Affecte : tous les"+ Cogs +InventoryHealString = "Toonique" +InventoryDamageString = "Dommages" +InventoryBattleMenu = "MENU DU COMBAT" +InventoryRun = "COURIR" +InventorySOS = "SOS" +InventoryPass = "PASSER" +InventoryFire = "FIRE" +InventoryClickToAttack = "Clique sur\nun gag pour\nattaquer." +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Tu dois faire un tour de tramway avant de partir.\n\n\n\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage2 = "Bien, tu as terminé ta recherche dans le tramway!\nVa voir le quartier général des Toons pour recevoir ta récompense.\n\n\n\n\n\n\n\nLe quartier général des Toons est situé près du centre du terrain de jeux." +NPCForceAcknowledgeMessage3 = "N'oublie pas de faire un tour de tramway.\n\n\n\n\n\nTu trouveras le tramway près de la boutique à gags de Dingo." +NPCForceAcknowledgeMessage4 = "Bravo! Tu as terminé ton premier défitoon!\n\n\n\n\n\n\nVa voir le quartier général des Toons pour recevoir ta récompense." +NPCForceAcknowledgeMessage5 = "N'oublie pas ton défitoon!\n\n\n\n\n\n\n\n\n\n\nTu peux trouver des Cogs a vaincre de l'autre côté de tunnels comme celui-ci." +NPCForceAcknowledgeMessage6 = "Félicitations pour avoir vaincu ces Cogs!\n\n\n\n\n\n\n\n\n\nReviens au quartier général des Toons aussi vite que possible." +NPCForceAcknowledgeMessage7 = "N'oublie pas de te faire un(e) ami(e)!\n\n\n\n\n\n\n\nClique sur un autre joueur et utilise le bouton Nouvel(le) ami(e)." +NPCForceAcknowledgeMessage8 = "Super! Tu t'es fait un(e) nouvel(le) ami(e)!\n\n\n\n\n\n\n\n\nTu dois retourner au quartier général des Toons maintenant." +NPCForceAcknowledgeMessage9 = "Tu as bien utilisé le téléphone!\n\n\n\n\n\n\n\n\nRetourne au quartier général des Toons pour demander ta récompense." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Tu as reçu 1 point de lancer! Quand tu en auras 10, tu pourras recevoir un nouveau gag!" +MovieTutorialReward2 = "Tu as reçu 1 point d'éclaboussure! Quand tu en auras 10, tu pourras avoir un nouveau gag!" +MovieTutorialReward3 = "Bon travail! Tu as terminé ton premier défitoon!" +MovieTutorialReward4 = "Va chercher ta récompense au quartier général des Toons!" +MovieTutorialReward5 = "Amuse-toi!" + +# BattleBase.py +Battle_Input_Timeout = 50.0 + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toonique', 'piège', 'leurre', 'tapage', 'lancer', 'éclaboussure', 'chute'] +BattleGlobalNPCTracks = ['rechargement', 'Toons marquent', 'Cogs ratent'] +BattleGlobalAvPropStrings = ( + ('Plume', 'Mégaphone', 'Tube de rouge à lèvres', 'Canne en bambou', 'Poussière de fée', 'Balles de jonglage', 'Plongeon'), + ('Peau de banane', 'Râteau', 'Billes', 'Sable mouvant', 'Trappe', 'TNT', 'Chemin de fer'), + ('Billet de 1 euro', 'Petit aimant', 'Billet de 5 euros', 'Gros aimant', 'Billet de 10 euros', 'Lunettes hypnotiques', 'Présentation'), + ('Sonnette de vélo', 'Sifflet', 'Clairon', 'Klaxon', "Trompe d'éléphant", 'Corne de brume', 'Chanteuse d’opéra'), + ('Petit gâteau', 'Tranche de tarte aux fruits', 'Tranche de tarte à la crème', 'Tarte aux fruits entière', 'Tarte à la crème entière', "Gâteau d'anniversaire", 'Gâteau de mariage'), + ('Fleur à éclabousser', "Verre d'eau", 'Pistolet à eau', "Bouteille d'eau gazeuse", "Lance d'incendie", "Nuage d'orage", 'Geyser'), + ('Pot de fleurs', 'Sac de sable', 'Enclume', 'Gros poids', 'Coffre-fort', 'Piano à queue', 'Toontanic') + ) +BattleGlobalAvPropStringsSingular = ( + ('une plume', 'un mégaphone', 'un tube de rouge à lèvres', 'une canne en bambou', 'de la poussière de fée', 'un jeu de balles de jonglage', 'un Plongeon'), + ('une peau de banane', 'un râteau', 'un jeu de billes', 'un peu de sable mouvant', 'une trappe', 'du TNT', 'un Chemin de fer'), + ('un billet de 1 euro', 'un petit aimant', 'un billet de 5 euros', 'un gros aimant', 'un billet de 10 euros', 'une paire de lunettes hypnotiques', 'une Présentation'), + ('une sonnette de vélo', 'un sifflet', 'un clairon', 'un klaxon', "une trompe d'éléphant", 'une corne de brume', 'une Chanteuse d’opéra'), + ('un petit gâteau', 'une tranche de tarte aux fruits', 'une tranche de tarte à la crème', 'une tarte aux fruits entière', 'une tarte à la crème entière', "un gâteau d'anniversaire", 'un Gâteau de mariage'), + ('une fleur à éclabousser', "un verre d'eau", 'un pistolet à eau', "une bouteille d'eau gazeuse", "une lance d'incendie", "un nuage d'orage", 'un Geyser'), + ('un pot de fleurs', 'un sac de sable', 'une enclume', 'un gros poids', 'un coffre-fort', 'un piano à queue', 'le Toontanic') + ) +BattleGlobalAvPropStringsPlural = ( + ('Plumes', 'Mégaphones', 'Tubes de rouge à lèvres', 'Cannes en bambou', 'Poussières de fée', 'jeux de balles de jonglage', 'Plongeons'), + ('Peaux de bananes', 'Râteaux', 'jeux de billes', 'morceaux de sable mouvant', 'Trappes','bâtons de TNT', 'Chemins de fer'), + ('Billets de 1 euro', 'Petits aimants', 'Billets de 5 euros', 'Gros aimants','Billets de 10 euros', 'Paires de lunettes hypnotiques', 'Présentations'), + ('Sonnettes de vélo', 'Sifflets', 'Clairons', 'Klaxons', "Trompes d'éléphants", 'Cornes de brume', 'Chanteuses d’opéra'), + ('Petits gâteaux', 'Tranches de tarte aux fruits', 'Tranches de tarte à la crème','Tartes aux fruits entières', 'Tartes à la crème entières', "Gâteaux d'anniversaire", 'Gâteaux de mariage'), + ('Fleurs à éclabousser', "Verres d'eau", 'Pistolets à eau',"Bouteilles d'eau gazeuse", "Lances d'incendie", "Nuages d'orage", 'Geysers'), + ('Pots de fleurs', 'Sacs de sable', 'Enclumes', 'Gros poids', 'Coffres-forts','Pianos à queue', 'Paquebots') + ) +BattleGlobalAvTrackAccStrings = ("Moyenne", "Parfaite", "Faible", "Forte", "Moyenne", "Forte", "Faible") +BattleGlobalLureAccLow = "Faible" +BattleGlobalLureAccMedium = "Moyen" + +AttackMissed = "RATÉ" + +NPCCallButtonLabel = 'APPEL' + +# ToontownLoader.py +LoaderLabel = "Chargement..." + +# PlayGame.py +HeadingToHood = "En route %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "En direction de ta propriété..." +HeadingToEstate = "En direction de la propriété de %s..." # avatar name +HeadingToFriend = "En direction de la propriété de l'ami(e) de %s..." # avatar name + +# Hood.py +HeadingToPlayground = "En direction du terrain de jeux..." +HeadingToStreet = "En route %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Revenir en courant au terrain de jeux?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "QUEL TOON ?" +TownBattleChooseAvatarCogTitle = "QUEL "+ string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "RETOUR" + +#firecogpanel +FireCogTitle = "PINK SLIPS LEFT:%s\nFIRE WHICH COG?" +FireCogLowTitle = "PINK SLIPS LEFT:%s\nNOT ENOUGH SLIPS !" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Pas d'contacts à appeler!" +TownBattleSOSWhichFriend = "Appeler quel(le) ami(e)?" +TownBattleSOSNPCFriends = "Toons sauvés" +TownBattleSOSBack = "RETOUR" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "Fire" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "En attente des\nautres joueurs..." +TownSoloBattleWaitTitle = "Patiente..." +TownBattleWaitBack = "RETOUR" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Recherche du Doudou\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s est %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "Tu ne peux pas monter dans le tramway avant que ton rigolmètre ne sourie." +TrolleyTFAMessage = "Tu ne peux pas monter dans le tramway avant que " + Mickey + " ne te le dise." +TrolleyHopOff = lQuit + +# DistributedFishingSpot.py +FishingExit = "Sortie" +FishingCast = "Lancer" +FishingAutoReel = "Moulinet automatique" +FishingItemFound = "Tu as attrapé :" +FishingCrankTooSlow = "Trop\nlent" +FishingCrankTooFast = "Trop\nrapide" +FishingFailure = "Tu n'as rien attrapé!" +FishingFailureTooSoon = "Ne commence pas à faire remonter ta ligne avant de voir une touche. Attends que ton flotteur se mette à s'enfoncer et à remonter rapidement!" +FishingFailureTooLate = "Remonte bien ta ligne avant que le poisson ne se décroche!" +FishingFailureAutoReel = "Le moulinet automatique n'a pas fonctionné cette fois-ci. Tourne la manivelle à la main, juste à la bonne vitesse, pour avoir les meilleurs chances d'attraper quelque chose!" +FishingFailureTooSlow = "Tu as tourné la manivelle trop lentement. Certains poissons sont plus rapides que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingFailureTooFast = "Tu as tourné la manivelle trop rapidement. Certains poissons sont plus lents que d'autres. Essaie de conserver la ligne de vitesse au centre!" +FishingOverTankLimit = "Ton seau de pêche est plein. Va vendre tes poissons au vendeur de l'animalerie et reviens." +FishingBroke = "Tu n'as plus de bonbons pour appâter! Va faire un tour de tramway ou vends des poissons aux vendeurs de l'animalerie pour avoir d'autres bonbons." +FishingHowToFirstTime = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie maintenant!" +FishingHowToFailed = "Clique sur le bouton de lancer et déplace le curseur vers le bas. Plus tu glisses vers le bas, plus ton lancer sera fort. Ajuste ton angle pour atteindre les poissons.\n\n Essaie encore maintenant!" +FishingBootItem = "Une vieille chaussure" +FishingJellybeanItem = "%s bonbons" +FishingNewEntry = "Nouvelle espèce!" +FishingNewRecord = "Nouveau record!" + +# FishPoker +FishPokerCashIn = "Encaisser\n%s\n%s" +FishPokerLock = "Bloquer" +FishPokerUnlock = "Débloquer" +FishPoker5OfKind = "5 identiques" +FishPoker4OfKind = "Carré" +FishPokerFullHouse = "Plein" +FishPoker3OfKind = "Brelan" +FishPoker2Pair = "2 paires" +FishPokerPair = "Paire" + +# DistributedTutorial.py +TutorialGreeting1 = "Salut,%s!" +TutorialGreeting2 = "Salut,%s!\nViens par ici!" +TutorialGreeting3 = "Salut,%s!\nViens par ici!\nUtilise les flèches!" +TutorialMickeyWelcome = "Bienvenue à Toontown!" +TutorialFlippyIntro = "Je te présente mon ami " + Flippy + "..." +TutorialFlippyHi = "Salut,%s!" +TutorialQT1 = "Tu peux parler en utilisant ceci." +TutorialQT2 = "Tu peux parler en utilisant ceci.\nClique dessus, puis choisis \"Salut\"." +TutorialChat1 = "Tu peux parler en utilisant l'un de ces boutons." +TutorialChat2 = "Le bouton bleu te permet de chatter avec le clavier." +TutorialChat3 = "Fais attention! La plupart des autres joueurs ne comprendront pas ce que tu dis lorsque tu utilises le clavier." +TutorialChat4 = "Le bouton vert ouvre le %s." +TutorialChat5 = "Tout le monde peut te comprendre si tu utilises le %s." +TutorialChat6 = "Essaie de dire \"salut\"." +TutorialBodyClick1 = "Très bien!" +TutorialBodyClick2 = "Ravi de t'avoir rencontré! Tu veux que nous soyons contacts?" +TutorialBodyClick3 = "Pour devenir ami(e) avec " + Flippy + ", clique sur lui..." +TutorialHandleBodyClickSuccess = "Bon travail!" +TutorialHandleBodyClickFail = "Ce n'est pas ça. Essaie de cliquer juste sur " + Flippy + "..." +TutorialFriendsButton = "Maintenant, clique sur le bouton \"contacts\" sous l'image de " + Flippy + " dans l'angle droit." +TutorialHandleFriendsButton = "Ensuite, clique sur le bouton \"oui\"." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Veux-tu devenir ami(e) avec " + Flippy + "?" +TutorialFriendsPanelMickeyChat = Flippy + " veut bien être ton ami. Clique sur \"OK\" pour terminer." +TutorialFriendsPanelYes = Flippy + " a dit oui!" +TutorialFriendsPanelNo = "Ça n'est pas très gentil!" +TutorialFriendsPanelCongrats = "Bravo! Tu t'es fait ton premier ami." +TutorialFlippyChat1 = "Reviens me voir quand tu seras prêt pour ton premier défitoon!" +TutorialFlippyChat2 = "Je serai à la Mairie de Toontown!" +TutorialAllFriendsButton = "Tu peux voir tous tes contacts en cliquant sur le bouton \"contacts\". Essaye donc..." +TutorialEmptyFriendsList = "Pour l'instant, ta liste est vide parce que " + Flippy + " n'est pas véritablement un joueur." +TutorialCloseFriendsList = "Clique sur le bouton \" Fermer \"\npour faire disparaître la liste." +TutorialShtickerButton = "Le bouton dans l'angle inférieur droit ouvre ton journal de bord. Essaye-le..." +TutorialBook1 = "Le journal contient de nombreuses informations utiles comme cette carte de Toontown." +TutorialBook2 = "Tu peux aussi y voir les progrès de tes défitoons." +TutorialBook3 = "Lorsque tu as fini, clique de nouveau sur le bouton représentant un livre pour le fermer." +TutorialLaffMeter1 = "Tu as aussi besoin de ça..." +TutorialLaffMeter2 = "Tu as aussi besoin de ça...\nC'est ton rigolmètre." +TutorialLaffMeter3 = "Lorsque les " + Cogs + " t'attaquent, il baisse." +TutorialLaffMeter4 = "Lorsque tu es sur les terrains de jeux comme celui-ci, il remonte." +TutorialLaffMeter5 = "Lorsque tu finis des défitoons, tu reçois des récompenses, comme l'augmentation de ta rigo-limite." +TutorialLaffMeter6 = "Fais attention! Si les " + Cogs + " te battent, tu perds tous tes gags." +TutorialLaffMeter7 = "Pour avoir plus de gags, joue aux jeux du tramway." +TutorialTrolley1 = "Suis-moi jusqu'au tramway!" +TutorialTrolley2 = "Monte à bord!" +TutorialBye1 = "Joue à des jeux!" +TutorialBye2 = "Joue à des jeux!\nAchète des gags!" +TutorialBye3 = "Va voir " + Flippy + " quand tu auras fini!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "Tu vas dans le mauvais sens! Va trouver " + Mickey + "!" + +PetTutorialTitle1 = "Le panneau des Doudous" +PetTutorialTitle2 = "Chat rapide des Doudous" +PetTutorialTitle3 = "Catalogue des Doudous" +PetTutorialNext = "Page suivante" +PetTutorialPrev = "Page précédente" +PetTutorialDone = "Terminé" +PetTutorialPage1 = "Clique sur un Doudou pour afficher le panneau des Doudous. Là tu pourras nourrir, cajoler et appeler le Doudou." +PetTutorialPage2 = "Utilise la nouvelle zone 'Animaux familiers' dans le menu de Chat rapide pour que le Doudou fasse un tour. S'il le fait, récompense-le et il s'améliorera!" +PetTutorialPage3 = "Achète de nouveaux tours pour les Doudous dans le catalogue de Clarabelle. De meilleures tours donnent de meilleures tooniques!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Jardinage" +GardenTutorialTitle2 = "Fleurs" +GardenTutorialTitle3 = "Arbres" +GardenTutorialTitle4 = "Comment faire" +GardenTutorialTitle5 = "Statues" +GardenTutorialNext = "Page suivante" +GardenTutorialPrev = "Page précédente" +GardenTutorialDone = "Terminé" +GardenTutorialPage1 = "Embellis ta propriété avec un jardin! Tu peux y planter des fleurs, y faire pousser des arbres, y récolter des gags super puissants et le décorer avec des statues!" +GardenTutorialPage2 = "Les fleurs sont délicates et demandent des recettes très particulières à base de bonbons. Une fois qu'elles ont poussé, tu peux les mettre dans une brouette pour aller les vendre et faire gonfler ton rigolmètre." +GardenTutorialPage3 = "Utilise un gag que tu as dans ton inventaire pour planter un arbre. Après quelques jours, ce gag sera encore plus puissant! N'oublie pas d'en prendre soin, ou tu perdras l'augmentation de puissance." +GardenTutorialPage4 = "Marche jusqu'à ces endroits pour planter, arroser, bêcher ou faire la cueillette dans ton jardin." +GardenTutorialPage5 = "Les statues sont vendues dans le Catalogue vachement branché de Clarabelle. Améliore ton habileté pour avoir accès aux statues les plus extravagantes!" + +# Playground.py +PlaygroundDeathAckMessage = "Les " + Cogs + " ont pris tous tes gags!\n\nTu es triste. Tu ne peux pas quitter le terrain de jeux avant d'avoir retrouvé la joie de vivre." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "Le contremaître de l'usine a été vaincu avant que tu ne le trouves. Tu n'as pas récupéré de pièces de Cogs." + +# MintInterior +ForcedLeaveMintAckMsg = "Le Superviseur de cet étage de la Fabrique à Sous a été vaincu avant que tu ne puisses l'atteindre. Tu n'as pas récupéré de euros Cog." + +# DistributedFactory.py +HeadingToFactoryTitle = "En route %s..." +ForemanConfrontedMsg = "%s est en train de combattre le contremaître de l'usine!" + +# DistributedMint.py +MintBossConfrontedMsg = "%s est en train de combattre le Superviseur!" + +# DistributedStage.py +StageBossConfrontedMsg = "%s se bat contre le juriste!" +stageToonEnterElevator = "%s \nest maintenant dans l'ascenseur" +ForcedLeaveStageAckMsg = "Le juriste a été vaincu avant que tu ne le trouves. Tu n'as pas récupéré de convocations du jury." + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "En attente d'autres joueurs..." +MinigamePleaseWait = "Patiente un peu..." +DefaultMinigameTitle = "Nom du mini jeu" +DefaultMinigameInstructions = "Instructions du mini jeu" +HeadingToMinigameTitle = "En route vers: %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Témoin de puissance" +MinigamePowerMeterTooSlow = "Trop\nlent" +MinigamePowerMeterTooFast = "Trop\nrapide" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Modèle de mini jeu" +MinigameTemplateInstructions = "C'est un modèle de mini jeu. Utilise-le pour créer des mini jeux." + +# DistributedCannonGame.py +CannonGameTitle = "Jeu du canon" +CannonGameInstructions = "Envoie ton Toon dans le château d'eau aussi vite que tu peux. Utilise les flèches du clavier ou la souris pour diriger le canon. Sois rapide et gagne une belle récompense pour tout le monde!" +CannonGameReward = "RÉCOMPENSE" + +# DistributedTwoDGame.py +TwoDGameTitle = "Toon Escape" +TwoDGameInstructions = "Echappe-toi de cette" + Cog + "tannière dès que possible. Utilise les flèches du clavier pour courir/sauter et la touche Ctrl pour faire gicler un" + Cog + ". Ramasse" + Cog + "les trésors pour gagner encore plus de points." +TwoDGameElevatorExit = "SORTIE" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Tir à la corde" +TugOfWarInstructions = "Appuie alternativement sur les flèches gauche et droite à la vitesse qu'il faut pour aligner la barre verte avec la ligne rouge. N'appuie pas trop rapidement ou trop lentement, tu pourrais finir dans l'eau!" +TugOfWarGameGo = "PARTEZ!" +TugOfWarGameReady = "Prêt..." +TugOfWarGameEnd = "Bien joué!" +TugOfWarGameTie = "Égalité!" +TugOfWarPowerMeter = "Témoin de puissance" + +# DistributedPatternGame.py +PatternGameTitle = "Imite "+ Minnie +PatternGameInstructions = Minnie +" va te montrer une suite de pas de danse. "+ \ + "Essaie de reproduire la danse de "+ Minnie + " comme tu la vois en utilisant les flèches!" +PatternGameWatch = "Regarde ces pas de danse..." +PatternGameGo = "PARTEZ!" +PatternGameRight = "Bien, %s!" +PatternGameWrong = "Aïe!" +PatternGamePerfect = "C'était parfait, %s!" +PatternGameBye = "Merci d'avoir joué!" +PatternGameWaitingOtherPlayers = "En attente d'autres joueurs..." +PatternGamePleaseWait = "Patiente un peu..." +PatternGameFaster = "Tu as été\nplus rapide!" +PatternGameFastest = "Tu as été\nle(la) plus rapide!" +PatternGameYouCanDoIt = "Allez!\nTu peux y arriver!" +PatternGameOtherFaster = "\na été plus rapide!" +PatternGameOtherFastest = "\na été le(la) plus rapide!" +PatternGameGreatJob = "Bon travail!" +PatternGameRound = "Partie %s!" # Round 1! Round 2! .. +PatternGameImprov = "Bien joué ! Maintenant monte !" + +# DistributedRaceAI.py +WaitingForJoin = 90 + +# DistributedRaceGame.py +RaceGameTitle = "Jeu de l'oie" +RaceGameInstructions = "Clique sur un nombre. Choisis bien! Tu n'avances que si personne d'autre n'a choisi le même nombre." +RaceGameWaitingChoices = "Attente du choix des autres joueurs..." +RaceGameCardText = "%(name)s tire: %(reward)s" +RaceGameCardTextBeans = "%(name)s reçoit: %(reward)s" +RaceGameCardTextHi1 = "%(name)s est un Toon fabuleux!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " avance d'une case" +RaceGameForwardTwoSpaces = " avance de 2 cases" +RaceGameForwardThreeSpaces = " avance de 3 cases" +RaceGameBackOneSpace = " recule d'une case" +RaceGameBackTwoSpaces = " recule de 2 cases" +RaceGameBackThreeSpaces = " recule de 3 cases" +RaceGameOthersForwardThree = " tous les autres avancent\nde 3 cases" +RaceGameOthersBackThree = "tous les autres reculent\nde 3 cases" +RaceGameInstantWinner = "Gagnant!" +RaceGameJellybeans2 = "2 bonbons" +RaceGameJellybeans4 = "4 bonbons" +RaceGameJellybeans10 = "10 bonbons!" + +# DistributedRingGame.py +RingGameTitle = "Jeu des anneaux" +# color +RingGameInstructionsSinglePlayer = "Essaie de nager en passant dans autant d'anneaux %s que tu pourras. Utilise les flèches pour nager." +# color +RingGameInstructionsMultiPlayer = "Essaie de nager en passant dans les anneaux %s. Les autres joueurs essaieront de passer les anneaux des autres couleurs. Utilise les flèches pour nager." +RingGameMissed = "RATÉ" +RingGameGroupPerfect = "GROUPE\nPARFAIT!!" +RingGamePerfect = "PARFAIT!" +RingGameGroupBonus = "BONUS DE GROUPE" + +# RingGameGlobals.py +ColorRed = "rouges" +ColorGreen = "verts" +ColorOrange = "orange" +ColorPurple = "violets" +ColorWhite = "blancs" +ColorBlack = "noirs" +ColorYellow = "jaunes" + +# DistributedDivingGame.py +DivingGameTitle = "Chasse aux trésors aquatique" +# color +DivingInstructionsSinglePlayer = "Les trésors apparaissent au fond du lac. Utilise les flèches du clavier pour nager. Évite les poissons et rapporte les trésors dans le bateau." +# color +DivingInstructionsMultiPlayer = "Les trésors apparaissent au fond du lac. Utilise les flèches de ton clavier pour nager. Travailler ensemble pour rapporter les trésors dans le bateau." +DivingGameTreasuresRetrieved = "Recherché Trésors" + +#Distributed Target Game +TargetGameTitle = "Jeu du parapluie" +TargetGameInstructionsSinglePlayer = "Atterris sur les cibles pour marquer des points" +TargetGameInstructionsMultiPlayer = "Atterris sur les cibles pour marquer des points" +TargetGameBoard = "Manche %s - Garder le meilleur score" +TargetGameCountdown = "Lancement forcé dans %s secondes" +TargetGameCountHelp = "Touche dièse et flèches droite et gauche pour allumer, stop pour lancer" +TargetGameFlyHelp = "Appuyer pour ouvrir le parapluie" +TargetGameFallHelp = "Utilise les flèches du clavier pour atterrir sur les cibles" +TargetGameBounceHelp = "En rebondissant, tu peux t'écarter de la cible" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nToi : %s" +PhotoGameScoreBlank = "Score : %s" +PhotoGameScoreOther = ""#"Score: %s\n%s" +PhotoGameScoreYou = "\nMeilleur bonus !" + + +# DistributedTagGame.py +TagGameTitle = "Jeu du chat" +TagGameInstructions = "Récupère les trésors. Tu ne peux pas récupérer les trésors quand tu es chat!" +TagGameYouAreIt = "Tu es chat!" +TagGameSomeoneElseIsIt = "%s est chat!" + +# DistributedMazeGame.py +MazeGameTitle = "Jeu du labyrinthe" +MazeGameInstructions = "Récupère les trésors. Essaie de les avoir tous, mais fais attention aux " + Cogs + "!" + +# DistributedCatchGame.py +CatchGameTitle = "Jeu du verger" +CatchGameInstructions = "Attrape des %(fruit)s, autant que tu peux. Attention aux " + Cogs + ", et essaie de ne pas attraper des %(badThing)s!" +CatchGamePerfect = "PARFAIT!" +CatchGameApples = 'pommes' +CatchGameOranges = 'oranges' +CatchGamePears = 'poires' +CatchGameCoconuts = 'noix de coco' +CatchGameWatermelons = 'pastèques' +CatchGamePineapples = 'ananas' +CatchGameAnvils = 'enclumes' + +# DistributedPieTossGame.py +PieTossGameTitle = "Jeu du lancer de tartes" +PieTossGameInstructions = "Envoie des tartes dans les cibles." + +# DistributedPhotoGame.py +PhotoGameInstructions = "Prends des photos correspondant aux Toons apparaissant en bas. Oriente l'appareil photo avec la souris et fais un clic gauche pour prendre une photo. Appuie sur la touche Ctrl pour faire un zoom avant/arrière, et déplace-toi avec les flèches du clavier. Les photos les mieux classées rapportent le plus de points." +PhotoGameTitle = "Délire photo" +PhotoGameFilm = "FILM" +PhotoGameScore = "Score par équipe : %s\n\nMeilleures photos: %s\n\nScore total : %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + "Voleur" +CogThiefGameInstructions = "Empêche-le" + Cogs + "de voler nos tonneaux de gags ! Appuie sur la touche Ctrl pour lancer une tarte. Utilise les flèches du clavier pour te déplacer. Astuce : tu peux te déplacer en diagonale." +CogThiefBarrelsSaved = "%(num)d Tonneaux\nsauvés !" +CogThiefBarrelSaved = "%(num)d Tonneaux\nsauvés !" +CogThiefNoBarrelsSaved = "Aucun tonneau\nsauvé" +CogThiefPerfect = "PARFAIT !" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JOUER" + +# Purchase.py +GagShopName = "La boutique à gags de Dingo" +GagShopPlayAgain = "REJOUER\n" +GagShopBackToPlayground = "RETOUR AU\nTERRAIN DE JEUX" +GagShopYouHave = "Tu as %s à dépenser" +GagShopYouHaveOne = "Tu as 1 bonbon à dépenser" +GagShopTooManyProps = "Désolé, tu as trop d'accessoires" +GagShopDoneShopping = "ACHATS\n TERMINÉS" +# name of a gag +GagShopTooManyOfThatGag = "Désolé, tu as déjà assez de %s" +GagShopInsufficientSkill = "Tu n'es pas encore assez habile pour cela" +# name of a gag +GagShopYouPurchased = "Tu as acheté %s" +GagShopOutOfJellybeans = "Désolé, tu n'as plus de bonbons!" +GagShopWaitingOtherPlayers = "En attente des autres joueurs..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s est déconnecté(e)" +GagShopPlayerExited = "%s est parti(e)" +GagShopPlayerPlayAgain = "Jouer encore" +GagShopPlayerBuying = "Achat en cours" + +# MakeAToon.py +GenderShopQuestionMickey = "Pour faire un garçon Toon, clique ici!" +GenderShopQuestionMinnie = "Pour faire une fille Toon, clique ici!" +GenderShopFollow = "Suis-moi!" +GenderShopSeeYou = "À plus tard!" +GenderShopBoyButtonText = "Garçon" +GenderShopGirlButtonText = "Fille" + +# BodyShop.py +BodyShopHead = "Tête" +BodyShopBody = "Corps" +BodyShopLegs = "Jambes" + +# ColorShop.py +ColorShopHead = "Tête" +ColorShopBody = "Corps" +ColorShopLegs = "Jambes" +ColorShopToon = "Toon" +ColorShopParts = "Parties" +ColorShopAll = "Tout" + +# ClothesShop.py +ClothesShopShorts = "Short" +ClothesShopShirt = "Chemise" +ClothesShopBottoms = "Bas" + +# MakeAToon +MakeAToonDone = "Fini" +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = "Retour" +CreateYourToon = "Clique sur les flèches pour créer ton Toon." +CreateYourToonTitle = "Crée ton Toon" +CreateYourToonHead = "Clique sur les flèches \"tête\" pour choisir différents animaux." +MakeAToonClickForNextScreen = "Clique sur la flèche ci-dessous pour aller à l'écran suivant." +PickClothes = "Clique sur les flèches pour choisir des vêtements!" +PickClothesTitle = "Choisis tes vêtements" +PaintYourToon = "Clique sur les flèches pour peindre ton Toon!" +PaintYourToonTitle = "Peins ton Toon" +MakeAToonYouCanGoBack = "Tu peux aussi retourner en arrière pour changer ton corps!" +MakeAFunnyName = "Choisis un nom amusant pour ton Toon!" +MustHaveAFirstOrLast1 = "Ton Toon devrait avoir un prénom ou un nom de famille, tu ne penses pas?" +MustHaveAFirstOrLast2 = "Tu ne veux pas que ton Toon ait de prénom ou de nom de famille ?" +ApprovalForName1 = "C'est ça, ton Toon mérite un super nom!" +ApprovalForName2 = "Les noms Toon sont les meilleurs noms!" +MakeAToonLastStep = "Dernière étape avant d'aller à Toontown!" +PickANameYouLike = "Choisis un nom que tu aimes!" +NameToonTitle = "Donne un nom à ton Toon" +TitleCheckBox = "Titre" +FirstCheckBox = "Prénom" +LastCheckBox = "Nom" +RandomButton = "Aléatoire" +NameShopSubmitButton = "Envoyer" +TypeANameButton = "Entre un nom" +TypeAName = "Tu n'aimes pas ces noms?\nClique ici -->" +PickAName = "Essaie le jeu Choisis-un-nom!\nClique ici -->" +PickANameButton = "Choisis un nom" +RejectNameText = "Ce nom n'est pas autorisé. Essaie encore." +WaitingForNameSubmission = "Envoi de ton nom..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_french.txt" +PetshopUnknownName = "Nom:???" +PetshopDescGender = "Sexe:\t%s" +PetshopDescCost = "Coûte:\t%s bonbons" +PetshopDescTrait = "Caractère:\t%s" +PetshopDescStandard = "Standard" +PetshopCancel = lCancel +PetshopSell = "Vendre tes poissons" +PetshopAdoptAPet = "Adopter un Doudou" +PetshopReturnPet = "Rapporter ton Doudou" +PetshopAdoptConfirm = "Adopter %s pour %d bonbons?" +PetshopGoBack = "Retourner" +PetshopAdopt = "Adopter" +PetshopReturnConfirm = "Rapporter %s?" +PetshopReturn = "Rapporter" +PetshopChooserTitle = "LES DOUDOUS DU JOUR" +PetshopGoHomeText = 'Est-ce que tu veux aller dans ta propriété pour jouer avec ton nouveau Doudou?' + +# NameShop.py +NameShopNameMaster = "NameMaster_french.txt" +NameShopPay = "Inscris-toi!" +NameShopPlay = "Essai gratuit" +NameShopOnlyPaid = "Seuls les utilisateurs payants\npeuvent donner un nom à leurs Toons.\nJusqu'à ce que tu t'inscrives,\nton nom sera\n" +NameShopContinueSubmission = "Continuer l'envoi" +NameShopChooseAnother = "Choisir un autre nom" +NameShopToonCouncil = "Le Conseil de Toontown\nva examiner ton\nnom. "+ \ + "L'examen peut\nprendre quelques jours.\nPendant que tu attends,\nton nom sera\n " +PleaseTypeName = "Entre ton nom:" +AllNewNames = "Tous les noms\ndoivent être approuvés\npar le Conseil de Toontown." +NameMessages = "Be creative and remember:\nno Disney-related names, please." +NameShopNameRejected = "Le nom que tu as\nenvoyé a été refusé." +NameShopNameAccepted = "Félicitations!\nLe nom que tu as\nenvoyé a\nété accepté!" +NoPunctuation = "Tu ne peux pas utiliser de signes de ponctuation dans ton nom!" +PeriodOnlyAfterLetter = "Tu peux utiliser un point dans ton nom, mais seulement après une lettre." +ApostropheOnlyAfterLetter = "Tu peux utiliser une apostrophe dans ton nom, mais seulement après une lettre." +NoNumbersInTheMiddle = "Les caractères numériques ne peuvent pas apparaître au milieu d'un mot." +ThreeWordsOrLess = "Ton nom doit comporter trois mots maximum." +CopyrightedNames = ( + "Mickey", + "Mickey Mouse", + "Mickey Mouse", + "Minnie Mouse", + "Minnie", + "Minnie Mouse", + "Minnie Mouse", + "Donald", + "Donald Duck", + "Donald Duck", + "Pluto", + "Dingo", + ) +NumToColor = ['Blanc', 'Pêche', 'Rouge vif', 'Rouge', 'Bordeaux', + 'Terre de Sienne', 'Brun', 'Brun clair', 'Corail', 'Orange', + 'Jaune', 'Crème', 'Jaune-vert', 'Citron vert', 'Vert marin', + 'Vert', 'Bleu clair', 'Turquoise', 'Bleu', + 'Pervenche', 'Bleu roi', 'Bleu ardoise', 'Violet', + 'Lavande', 'Rose'] +AnimalToSpecies = { + 'dog' : 'Chien', + 'cat' : 'Chat', + 'mouse' : 'Souris', + 'horse' : 'Cheval', + 'rabbit' : 'Lapin', + 'duck' : 'Canard', + 'monkey' : 'Singe', + 'bear' : 'Ours', + 'pig' : 'Cochon' + } +NameTooLong = "Ce nom est trop long. Essaie encore." +ToonAlreadyExists = "Tu as déjà un Toon qui s'appelle %s!" +NameAlreadyInUse = "Ce nom est déjà utilisé!" +EmptyNameError = "Tu dois indiquer un nom d'abord." +NameError = "Désolé. Ce nom ne pourra pas convenir." + +# NameCheck.py +NCTooShort = 'Ce nom est trop court.' +NCNoDigits = 'Ton nom ne peut pas contenir de chiffres.' +NCNeedLetters = 'Chaque mot de ton nom doit contenir des lettres.' +NCNeedVowels = 'Chaque mot de ton nom doit contenir des voyelles.' +NCAllCaps = 'Ton nom ne peut pas être entièrement en majuscules.' +NCMixedCase = 'Ton nom a trop de majuscules.' +NCBadCharacter = "Ton nom ne peut pas contenir le caractère \"%s\"" +NCGeneric = 'Désolé, ce nom ne pourra pas convenir.' +NCTooManyWords = 'Ton nom ne peut pas comporter plus de quatre mots.' +NCDashUsage = ("Les tirets ne peuvent être utilisés que pour relier deux mots ensemble." + "(comme dans \"Bou-Bou\").") +NCCommaEdge = "Ton nom ne peut pas commencer ou se terminer par une virgule." +NCCommaAfterWord = "Tu ne peux pas commencer un mot par une virgule." +NCCommaUsage = ("Ce nom n'utilise pas les virgules correctement. Les virgules doivent" + "assembler deux mots, comme dans le nom \"Dr Couac, médecin\"." + "Les virgules doivent aussi être suivies d'un espace.") +NCPeriodUsage = ("Ce nom n'utilise pas les points correctement. Les points sont" + "seulement autorisés dans des mots tels que \"M.\",\"doct.\",\"prof.\", etc.") +NCApostrophes = "Ton nom a trop d'apostrophes." + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+" : Les " + Cogs + " ont repris un des bâtiments que tu avais sauvés!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Tu as besoin de plus de temps pour réfléchir ?' +STOREOWNER_GOODBYE = 'À plus tard!' +STOREOWNER_NEEDJELLYBEANS = 'Tu dois faire un tour de tramway pour avoir des bonbons.' +STOREOWNER_GREETING = 'Choisis ce que tu veux acheter.' +STOREOWNER_BROWSING = "Tu peux regarder, mais tu auras besoin d'un ticket d'habillement pour acheter." +STOREOWNER_NOCLOTHINGTICKET = "Tu as besoin d'un ticket d'habillement pour acheter des vêtements." +# translate +STOREOWNER_NOFISH = "Reviens ici pour vendre des poissons à l'animalerie en échange de bonbons." +STOREOWNER_THANKSFISH = "Merci! L'animalerie va les adorer. Au revoir!" +STOREOWNER_THANKSFISH_PETSHOP = "Ce sont de beaux spécimens! Merci." +STOREOWNER_PETRETURNED = "Ne t'inquiete pas. Nous trouverons une bonne maison pour ton Doudou." +STOREOWNER_PETADOPTED = "Félicitations pour ton nouveau Doudou! Tu peux jouer avec lui dans ta propriété." +STOREOWNER_PETCANCELED = "N'oublie pas: si tu vois un Doudou qui te plaît, adopte-le avant que quelqu'un d'autre ne le fasse!" + +STOREOWNER_NOROOM = "Hmm...tu devrais faire de la place dans ton placard avant d'acheter de nouveaux vêtements.\n" +STOREOWNER_CONFIRM_LOSS = "Ton placard est plein. Tu vas perdre les vêtements que tu portais." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Oh là là! Tu as trouvé %s sur %s poissons. Ça mérite un trophée et une rigol-augmentation!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": Une invasion de Cogs a commencé!!!" +SuitInvasionBegin2 = lToonHQ+": Les %s ont pris Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": L'invasion des %s est terminée!!!" +SuitInvasionEnd2 = lToonHQ+": Les Toons nous ont sauvés une fois de plus!!!" +SuitInvasionUpdate1 = lToonHQ+": L'invasion de Cogs en est à %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": Nous devons battre ces %s!!!" +SuitInvasionBulletin1 = lToonHQ+": Il y a une invasion de Cogs en cours!!!" +SuitInvasionBulletin2 = lToonHQ+": Les %s ont pris Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Armée de Toons" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown compte un nouveau citoyen! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMickey_2 = "Bien sûr, %s!" +QuestScriptTutorialMickey_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" +QuestScriptTutorialMickey_4 = "Viens ici! Utilise les flèches pour te déplacer." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown compte une nouvelle citoyenne! Est-ce que tu as des gags en plus?" +QuestScriptTutorialMinnie_2 = "Bien sûr, %s!" +QuestScriptTutorialMinnie_3 = "Tom Tuteur va te parler des Cogs.\aJe dois y aller!" + +QuestScript101_1 = "Ce sont les COGS. Ce sont des robots qui essaient de prendre Toontown." +QuestScript101_2 = "Il y a différentes sortes de COGS et..." +QuestScript101_3 = "...ils transforment de bons bâtiments Toon..." +QuestScript101_4 = "...en affreuses bâtisses Cog!" +QuestScript101_5 = "Mais les COGS ne comprennent pas les blagues!" +QuestScript101_6 = "Un bon gag les arrête." +QuestScript101_7 = "Il y a des quantités de gags; prends ceux-là pour commencer." +QuestScript101_8 = "Oh! Tu as aussi besoin d'un rigolmètre!" +QuestScript101_9 = "Si ton rigolmètre descend trop bas, tu seras triste!" +QuestScript101_10 = "Un Toon heureux est un Toon en bonne santé!" +QuestScript101_11 = "OH NON! Il y a un COG devant ma boutique!" +QuestScript101_12 = "AIDE-MOI, S'IL TE PLAÎT! Va vaincre ce COG!" +QuestScript101_13 = "Voilà ton premier défitoon!" +QuestScript101_14 = "Dépêche-toi! Va battre ce Laquaistic!" + +QuestScript110_1 = "Bon travail pour avoir vaincu ce Laquaistic. Je vais te donner un journal de bord..." +QuestScript110_2 = "Ce journal est plein de choses intéressantes." +QuestScript110_3 = "Ouvre-le, et je vais te montrer." +QuestScript110_4 = "La carte montre où tu as été." +QuestScript110_5 = "Tourne la page pour voir tes gags..." +QuestScript110_6 = "Oh oh! Tu n'as pas de gags! Je vais te donner un défi." +QuestScript110_7 = "Tourne la page pour voir tes défis." +QuestScript110_8 = "Fais un tour de tramway, et gagne des bonbons pour acheter des gags!" +QuestScript110_9 = "Pour aller jusqu'au tramway, sors par la porte qui est derrière moi et va jusqu'au terrain de jeux." +QuestScript110_10 = "Maintenant, ferme le livre et trouve le tramway!" +QuestScript110_11 = "Retourne au QG des Toons quand tu as fini. Au revoir!" + +QuestScriptTutorialBlocker_1 = "Bien le bonjour!" +QuestScriptTutorialBlocker_2 = "Bonjour ?" +QuestScriptTutorialBlocker_3 = "Oh! Tu ne sais pas utiliser le Chat rapide!" +QuestScriptTutorialBlocker_4 = "Clique sur le bouton pour dire quelque chose." +QuestScriptTutorialBlocker_5 = "Très bien!\aLà où tu vas, il y a plein de Toons à qui parler." +QuestScriptTutorialBlocker_6 = "Si tu veux chatter avec tes contacts à l'aide du clavier, tu peux utiliser un autre bouton." +QuestScriptTutorialBlocker_7 = "Ça s'appelle le bouton \"Chat\". Tu dois être officiellement citoyen de Toontown pour l'utiliser." +QuestScriptTutorialBlocker_8 = "Bonne chance! À plus tard!" + +""" +GagShopTut + +Tu gagneras aussi la possibilité d'utiliser d'autres types de gags. + +""" + +QuestScriptGagShop_1 = "Bienvenue à la Boutique à gags!" +QuestScriptGagShop_1a = "C'est là que viennent les Toons pour acheter des gags qu'ils utiliseront contre les Cogs." +#QuestScriptGagShop_2 = "Ce pot indique combien de bonbons tu as." +#QuestScriptGagShop_3 = "Pour acheter un gag, clique sur le bouton " Gag ". Essaie maintenant!" +QuestScriptGagShop_3 = "Pour acheter des gags, clique sur les boutons de gag. Essaie d'en avoir maintenant!" +QuestScriptGagShop_4 = "Super! Tu peux utiliser ces gags lors des combats contre les Cogs." +QuestScriptGagShop_5 = "Voila un aperçu des gags avancés de lancer et d'éclaboussure..." +QuestScriptGagShop_6 = "Quand tu as fini d'acheter des gags, clique sur ce bouton pour retourner au terrain de jeu." +QuestScriptGagShop_7 = "Normalement, tu peux utiliser ce bouton pour jouer à un autre jeu du tramway..." +QuestScriptGagShop_8 = "...mais tu n'as pas le temps de faire un autre jeu maintenant. On t'attend au quartier général des Toons!" + +QuestScript120_1 = "Bien, tu as trouvé le tramway!\aAu fait, as-tu rencontré Bob le Banquier ?\aIl aime bien les sucreries.\aPourquoi n'irais-tu pas te présenter en lui emportant ce sucre d'orge comme cadeau?" +QuestScript120_2 = "Bob le Banquier est dans la banque de Toontown." + +QuestScript121_1 = "Miam, merci pour ce sucre d'orge.\aDis donc, si tu peux m'aider, je te donnerai une récompense.\aCes Cogs ont volé les clés de mon coffre. Va battre des Cogs pour trouver une clé volée.\aQuand tu auras trouvé une clé, ramène-la moi." + +QuestScript130_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai reçu un paquet pour le Professeur Pete aujourd'hui.\aÇa doit être la nouvelle craie qu'il a commandée.\aPeux-tu lui apporter s'il te plaît ?\aIl est dans l'école." + +QuestScript131_1 = "Oh, merci pour la craie.\aQuoi?!?\aCes Cogs ont volé mon tableau. Va vaincre des Cogs pour retrouver le tableau qu'ils m'ont volé.\aQuand tu l'auras trouvé, ramène-le moi." + +QuestScript140_1 = "Bien, tu as trouvé le tramway!\aPendant qu'on y est, j'ai un ami, Larry le Libraire, qui est un rat de bibliothèque.\aJ'ai pris ce livre pour lui la dernière fois que j'ai été aux quais Donald.\aPourrais-tu lui apporter ? Il est à la bibliothèque, d'habitude." + +QuestScript141_1 = "Oh, oui, ce livre complète presque ma collection.\aVoyons ça...\aAh, oh...\aMais où est-ce que j'ai mis mes lunettes?\aJe les avais juste avant que ces Cogs ne prennent mon bâtiment.\aVa vaincre des Cogs pour retrouver les lunettes qu'ils m'ont volées.\aQuand tu les auras retrouvées, reviens me voir pour avoir une récompense." + +QuestScript145_1 = "Je vois que tu n'as pas eu de problèmes avec le tramway!\aÉcoute, les Cogs ont volé notre brosse à tableaux.\aVa dans les rues et combats les Cogs jusqu'a ce que tu retrouves la brosse.\aPour atteindre les rues, passe par un des tunnels comme celui-ci :" +QuestScript145_2 = "Quand tu auras retrouvé notre brosse, ramene-la ici.\aN'oublie pas : si tu as besoin de gags, va faire un tour de tramway.\aDe meme, si tu as besoin de récupérer des rigolpoints, ramasse des cônes de glace sur le terrain de jeu." + +QuestScript150_1 = "Oh... le prochain défi pourrait être trop difficile pour que tu le fasses tout(e) seul(e)!" +QuestScript150_2 = "Pour te faire des contacts, trouve un autre joueur et utilise le bouton Nouvel ami." +QuestScript150_3 = "Une fois que tu t'es fait un(e) ami(e), reviens ici." +QuestScript150_4 = "Certains défis sont trop difficiles pour un Toon seul!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignorer" + +SellbotBossName = "Premier Vice-\nPrésident" +CashbotBossName = "Vice-\nPrésident" +LawbotBossName = "Chief Justice" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade %s. Félicitations!" +BossCogDoobersAway = { 's' : "Va! Et réalise cette vente!" } +BossCogWelcomeToons = "Bienvenue aux nouveaux Cogs!" +BossCogPromoteToons = "En vertu des pouvoirs qui me sont conférés, tu es promu au grade %s. Félicitations!" +CagedToonInterruptBoss = "Hé! Hou! Hé là-bas!" +CagedToonRescueQuery = "Alors les Toons, vous êtes venus me sauver ?" +BossCogDiscoverToons = "Eh? Des Toons! Déguisés!" +BossCogAttackToons = "À l'attaque!!" +CagedToonDrop = [ + "Bon travail! Tu l'épuises!", + "Ne le lâchez pas! Il va s'enfuir!", + "Vous êtes super les copains!", + "Fantastique! Vous l'avez presque maintenant!", + ] +CagedToonPrepareBattleTwo = "Attention, il essaie de s'enfuir!\aAidez-moi, tout le monde - montez jusque là et arrêtez-le!" +CagedToonPrepareBattleThree = "Youpi, je suis presque libre!\aMaintenant vous devez attaquer le vice-président des Cogs directement.\aJ'ai tout un lot de tartes que vous pouvez utiliser!\aSautez en l'air et touchez le fond de ma cage, je vous donnerai des tartes.\aAppuyez sur la touche \"Inser\" pour lancer les tartes une fois que vous les avez!" +BossBattleNeedMorePies = "Vous avez besoin de plus de tartes!" +BossBattleHowToGetPies = "Sautez en l'air pour toucher la cage et avoir des tartes." +BossBattleHowToThrowPies = "Appuyez sur la touche \"Inser\" pour lancer les tartes!" +CagedToonYippee = "Génial!" +CagedToonThankYou = "C'est super d'être libre!\aMerci pour toute votre aide!\aJe suis à votre service.\aSi jamais vous avez besoin d'aide pour un combat, vous pouvez m'appeler!\aCliquez simplement sur le bouton SOS pour m'appeler." +CagedToonPromotion = "\aDis donc - ce vice-président Cog a laissé derrière lui les papiers de ta promotion.\aJe vais les envoyer pour toi en sortant, pour que tu aies ta promotion!" +CagedToonLastPromotion = "\aWaou, tu as atteint le niveau %s sur ton costume de Cog!\aLes Cogs ne montent pas en grade plus haut que ça.\aTu ne peux plus monter ton costume de Cog en grade, mais tu peux évidemment continuer à sauver des Toons!" +CagedToonHPBoost = "\aTu as sauvé beaucoup de Toons dans ce QG.\aLe Conseil de Toontown a décidé de te donner un autre rigolpoint. Félicitations!" +CagedToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aDe la part du Conseil de Toontown, merci d'être revenu(e) sauver encore plus de Toons!" +CagedToonGoodbye = "À la prochaine!" + + +CagedToonBattleThree = { + 10: "Joli saut, %(toon)s. Voilà quelques tartes!", + 11: "Salut, %(toon)s! Prenez des tartes!", + 12: "Hé là,%(toon)s! Vous avez des tartes maintenant!", + + 20: "Hé, %(toon)s! Sautez jusqu'à ma cage et prenez des tartes à lancer!", + 21: "Hé, %(toon)s! Utilisez la touche \"Ctrl\" pour sauter et toucher ma cage!", + + 100: "Appuyez sur la touche \"Inser\" pour lancer une tarte!", + 101: "Le compteur bleu montre à quelle hauteur ta tarte va monter.", + 102: "Essaie d'abord de lancer une tarte sous son châssis pour bousiller son mécanisme.", + 103: "Attends que la porte s'ouvre, et lance une tarte à l'intérieur.", + 104: "Lorsqu'il est étourdi, frappe-le au visage ou au torse pour le renverser!", + 105: "Tu sauras que tu l'as frappé comme il faut quand tu verras une tache de couleur.", + 106: "Si tu frappes un Toon avec une tarte, cela donne à ce Toon un rigolpoint!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "Ça suffit. J'en ai assez de ces Toons si énervants!" +CashbotBossOuttaHere = "J'ai un train à prendre!" +ResistanceToonName = "Inès Pionne" +ResistanceToonCongratulations = "Tu y es arrivé(e)! Félicitations!\aTu es un membre de valeur de la Résistance!\aVoici une phrase très spéciale que tu peux utiliser en cas de situation difficile :\a%s\aQuand tu la prononces, %s.\aMais tu ne peux l'utiliser qu'une seule fois, alors choisis bien ton moment!" +ResistanceToonToonupInstructions = "Tous les Toons qui sont près de toi vont gagner %s rigolpoints." +ResistanceToonToonupAllInstructions = "Tous les Toons qui sont près de toi vont gagner un renouvellement de tout leur stock de rigolpoints." +ResistanceToonMoneyInstructions = "Tous les Toons qui sont près de toi vont gagner %s bonbons." +ResistanceToonMoneyAllInstructions = "Tous les Toons qui sont près de toi vont remplir leurs pots de bonbons." +ResistanceToonRestockInstructions = "Tous les Toons qui sont près de toi vont compléter leur stock de \"%s\" gags." +ResistanceToonRestockAllInstructions = "Tous les Toons qui sont près de toi vont compléter entièrement leur stock de gags." + +ResistanceToonLastPromotion = "\aWaouh, tu as atteint le niveau %s de ton costume de Cog!\aLes Cogs ne vont jamais plus haut que ce niveau.\aTu ne peux plus rien ajouter à ton costume de Cog mais tu peux bien sûr continuer à travailler pour la Résistance!" +ResistanceToonHPBoost = "\aTu as beaucoup fait pour la Résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations!" +ResistanceToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aDe la part du Conseil des Toons, merci d'être revenu pour secourir encore plus de Toons!" + +CashbotBossCogAttack = "Attrapez-les!!!" +ResistanceToonWelcome = "Ça y est, tu y es arrivé! Suis-moi jusqu'au coffre-fort principal avant que le Vice-Président ne nous trouve!" +ResistanceToonTooLate = "Zut alors! Nous arrivons trop tard!" +CashbotBossDiscoverToons1 = "Ah-AH!" +CashbotBossDiscoverToons2 = "Il me semblait bien que ça sentait le Toon par ici! Imposteurs!" +ResistanceToonKeepHimBusy = "Occupe-le! Je vais préparer un piège!" +ResistanceToonWatchThis = "Regarde ça!" +CashbotBossGetAwayFromThat = "Eh! Ne touche pas à ça!" +ResistanceToonCraneInstructions1 = "Prends le contrôle d'un aimant en montant sur un podium." +ResistanceToonCraneInstructions2 = "Utilise les flèches de ton clavier pour déplacer la grue et appuie sur la touche Ctrl pour attraper un objet." +ResistanceToonCraneInstructions3 = "Attrape un coffre-fort avec un aimant et fais tomber le casque de sécurité du Vice-Président." +ResistanceToonCraneInstructions4 = "Une fois que le casque est tombé, prends un goon désactivé et frappe-le à la tête!" +ResistanceToonGetaway = "Eek! Courons!" +CashbotCraneLeave = "Quitter la grue" +CashbotCraneAdvice = "Utilise les flèches de ton clavier pour déplacer la grue au-dessus." +CashbotMagnetAdvice = "Maintiens la touche Ctrl enfoncée pour attraper des objets." +CashbotCraneLeaving = "En train de quitter la grue" + +MintElevatorRejectMessage = "Tu ne peux pas entrer dans les Fabriques à Sous avant d'avoir complété ton %s costume de Cog." +BossElevatorRejectMessage = "Tu ne peux pas monter dans cet ascenseur avant d'avoir gagné une promotion." +NotYetAvailable = "Cet ascenseur n'est pas encore disponible." + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "Meuble" +PaintingTypeName = "Tableau" +ClothingTypeName = "Vêtement" +ChatTypeName = "Phrase de Chat rapide" +EmoteTypeName = "Leçons de comédie" +BeanTypeName = "Bonbons" +PoleTypeName = "Canne à pêche" +WindowViewTypeName = "Vue de la fenêtre" +PetTrickTypeName = "Entraînement du Doudou" +GardenTypeName = "Matériaux de jardinage" +RentalTypeName = "Article à louer" +GardenStarterTypeName = "Kit de jardinage" +NametagTypeName = "Badge" + +#rental names +RentalHours = "Heures" +RentalOf = "De" +RentalCannon = "Canons !" +RentalGameTable = "Table de jeu !" +RentalTime = "Heures de" + +EstateCannonGameEnd = "La location du jeu du canon est terminée." +GameTableRentalEnd = "La location de la table de jeu est terminée." + +MessageConfirmRent = "Commencer à louer? Annule pour enregistrer la location pour plus tard" +MessageConfirmGarden = "Veux-tu vraiment commencer un jardin?" + +#nametag Names +NametagPaid = "Badge Citoyen" +NametagAction = "Badge Action" +NametagFrilly = "Badge à fanfreluches" + +FurnitureYourOldCloset = "ton ancienne armoire" +FurnitureYourOldBank = "ton ancienne tirelire" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +FurnitureNames = { + 100 : "Fauteuil", + 105 : "Fauteuil", + 110 : "Chaise", + 120 : "Chaise de bureau", + 130 : "Chaise en rondins", + 140 : "Chaise homard", + 145 : "Chaise de survie", + 150 : "Tabouret selle", + 160 : "Chaise locale", + 170 : "Chaise gâteau", + 200 : "Lit", + 205 : "Lit", + 210 : "Lit", + 220 : "Lit baignoire", + 230 : "Lit feuille", + 240 : "Lit bateau", + 250 : "Hamac cactus", + 260 : "Lit crème glacée", + 270 : "Olivia Erin & Cat's Bed", + 300 : "Piano mécanique", + 310 : "Orgue", + 400 : "Cheminée", + 410 : "Cheminée", + 420 : "Cheminée ronde", + 430 : "Cheminée", + 440 : "Cheminée pomme", + 450 : "Erin's Fireplace", + 500 : "Armoire", + 502 : "Armoire pour 15 vêtements", + 504 : "Armoire 20 articles", + 506 : "Armoire 25 articles", + 510 : "Armoire", + 512 : "Armoire pour 15 vêtements", + 514 : "Armoire 20 articles", + 516 : "Armoire 25 articles", + 600 : "Petite lampe", + 610 : "Lampe haute", + 620 : "Lampe de table", + 625 : "Lampe de table", + 630 : "Lampe Daisy", + 640 : "Lampe Daisy", + 650 : "Lampe méduse", + 660 : "Lampe méduse", + 670 : "Lampe cow-boy", + 700 : "Chaise capitonnée", + 705 : "Chaise capitonnée", + 710 : "Divan", + 715 : "Divan", + 720 : "Divan foin", + 730 : "Divan sablé", + 800 : "Bureau", + 810 : "Bureau en rondins", + 900 : "Porte-parapluie", + 910 : "Portemanteau", + 920 : "Poubelle", + 930 : "Champignon rouge", + 940 : "Champignon jaune", + 950 : "Portemanteau", + 960 : "Étal onneau", + 970 : "Cactus", + 980 : "Tipi", + 990 : "Juliette's Fan", + 1000 : "Grand tapis", + 1010 : "Tapis rond", + 1015 : "Tapis rond", + 1020 : "Petit tapis", + 1030 : "Paillasson", + 1100 : "Vitrine", + 1110 : "Vitrine", + 1120 : "Bibliothèque haute", + 1130 : "Bibliothèque basse", + 1140 : "Coffre Sundae", + 1200 : "Table d'appui", + 1210 : "Petite table", + 1215 : "Petite table", + 1220 : "Table de salon", + 1230 : "Table de salon", + 1240 : "Table de plongeur", + 1250 : "Table cookie", + 1260 : "Table de chevet", + 1300 : "Tirelire de 1000 bonbons", + 1310 : "Tirelire de 2500 bonbons", + 1320 : "Tirelire de 5000 bonbons", + 1330 : "Tirelire de 7500 bonbons", + 1340 : "Tirelire de 10000 bonbons", + 1399 : "Téléphone", + 1400 : "Toon Cézanne", + 1410 : "Fleurs", + 1420 : "Mickey contemporain", + 1430 : "Toon Rembrandt", + 1440 : "Paysage Toon", + 1441 : "Cheval de Whistler", + 1442 : "Étoile Toon", + 1443 : "Pas une tarte", + 1500 : "Radio", + 1510 : "Radio", + 1520 : "Radio", + 1530 : "Télévision", + 1600 : "Vase bas", + 1610 : "Vase haut", + 1620 : "Vase bas", + 1630 : "Vase haut", + 1640 : "Vase bas", + 1650 : "Vase bas", + 1660 : "Vase corail", + 1661 : "Vase coquillage", + 1700 : "Chariot de pop-corn", + 1710 : "Coccinelle", + 1720 : "Fontaine", + 1725 : "Machine à laver", + 1800 : "Aquarium", + 1810 : "Aquarium", + 1900 : "Poisson-scie", + 1910 : "Requin-marteau", + 1920 : "Cornes porte-manteau", + 1930 : "Sombrero classique", + 1940 : "Sombrero fantaisie", + 1950 : "Attrapeur de rêves", + 1960 : "Fer à cheval", + 1970 : "Portrait de bison", + 2000 : "Balançoire bonbon", + 2010 : "Toboggan gâteau", + 3000 : "Baignoire Banana Split", + 10000 : "Petite citrouille", + 10010 : "Grande citrouille", + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "Chemise", + "Chemise", + "Chemise", + "Short", + "Short", + "Jupe", + "Short", + ) + +ClothingTypeNames = { + 1400 : "Chemise de Mathieu", + 1401 : "Chemise de Jessica", + 1402 : "Chemise de Marissa", + 1600 : "Tenue Piège", + 1601 : "Tenue Tapage", + 1602 : "Tenue Leurre", + 1603 : "Tenue Piège", + 1604 : "Tenue Tapage", + 1605 : "Tenue Leurre", + 1606 : "Tenue Piège", + 1607 : "Tenue Tapage", + 1608 : "Tenue Leurre", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "Papier peint", + "Moulures", + "Revêtement de sol", + "Lambris", + "Bordure", + ) + +WallpaperNames = { + 1000 : "Parchemin", + 1100 : "Milan", + 1200 : "Douvres", + 1300 : "Victoria", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Arlequin", + 1700 : "Lune", + 1800 : "Étoiles", + 1900 : "Fleurs", + 2000 : "Jardin de printemps", + 2100 : "Jardin classique", + 2200 : "Jour de course", + 2300 : "Marqué!", + 2400 : "Nuage 9", + 2500 : "Vigne vierge", + 2600 : "Printemps", + 2700 : "Kokeshi", + 2800 : "Petits bouquets", + 2900 : "Poisson ange", + 3000 : "Bulles", + 3100 : "Bulles", + 3200 : "À la pêche", + 3300 : "Poisson stop", + 3400 : "Hippocampe", + 3500 : "Coquillages", + 3600 : "Sous l'eau", + 3700 : "Bottes", + 3800 : "Cactus", + 3900 : "Chapeau de cow-boy", + 10100 : "Chats", + 10200 : "Chauve-souris", + 11000 : "Flocons de neige", + 11100 : "Houx", + 11200 : "Bonhomme de neige", + 13000 : "Trèfle", + 13100 : "Trèfle", + 13200 : "Arc-en-ciel", + 13300 : "Trèfle", + } + +FlooringNames = { + 1000 : "Parquet", + 1010 : "Moquette", + 1020 : "Carrelage losange", + 1030 : "Carrelage losange", + 1040 : "Pelouse", + 1050 : "Briques beiges", + 1060 : "Briques rouges", + 1070 : "Carrelage carré", + 1080 : "Pierre", + 1090 : "Bois", + 1100 : "Terre", + 1110 : "Pavage de bois", + 1120 : "Carrelage", + 1130 : "Nid d'abeilles", + 1140 : "Eau", + 1150 : "Carrelage plage", + 1160 : "Carrelage plage", + 1170 : "Carrelage plage", + 1180 : "Carrelage plage", + 1190 : "Sable", + 10000 : "Glaçon", + 10010 : "Igloo", + 11000 : "Trèfle", + 11010 : "Trèfle", + } + +MouldingNames = { + 1000 : "Noueux", + 1010 : "Peint", + 1020 : "Denté", + 1030 : "Fleurs", + 1040 : "Fleurs", + 1050 : "Coccinelle", + } + +WainscotingNames = { + 1000 : "Peint", + 1010 : "Panneau de bois", + 1020 : "Bois", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Grand jardin", + 20 : "Jardin sauvage", + 30 : "Jardin grec", + 40 : "Paysage urbain", + 50 : "Far West", + 60 : "Sous l'océan", + 70 : "Île tropicale", + 80 : "Nuit étoilée", + 90 : "Lagon Tiki", + 100 : "Frontière gelée", + 110 : "Pays fermier", + 120 : "Camp local", + 130 : "Grand rue", + } + +# don't translate yet +NewCatalogNotify = "De nouveaux articles sont prêts à être commandés par téléphone!" +NewDeliveryNotify = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyFirstCatalog = "Ton premier catalogue est arrivé! Tu peux l'utiliser pour commander de nouveaux objets pour toi ou pour ta maison." +CatalogNotifyNewCatalog = "Ton catalogue N°%s est arrivé! Tu peux utiliser ton téléphone pour commander des articles dans le catalogue de Clarabelle." +CatalogNotifyNewCatalogNewDelivery = "Un colis t'attend dans ta boîte aux lettres! Ton catalogue N°%s est aussi arrivé!" +CatalogNotifyNewDelivery = "Un colis t'attend dans ta boîte aux lettres!" +CatalogNotifyNewCatalogOldDelivery = "Ton catalogue N°%s est arrivé, et des objets t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyOldDelivery = "Des articles t'attendent encore dans ta boîte aux lettres!" +CatalogNotifyInstructions = "Clique sur le bouton \"Retour à la maison\" sur la carte de ton journal de bord, puis va jusqu'au téléphone qui est dans ta maison." +CatalogNewDeliveryButton = "Nouvelle\nlivraison!" +CatalogNewCatalogButton = "Nouveau\ncatalogue" +CatalogSaleItem = "Vente!" + +# don't translate yet +DistributedMailboxEmpty = "Ta boîte aux lettres est vide pour l'instant. Reviens ici chercher les articles que tu as commandés par téléphone quand ils seront livrés!" +DistributedMailboxWaiting = "Ta boîte aux lettres est vide pour l'instant, mais le paquet que tu as commandé est en chemin. Reviens voir plus tard!" +DistributedMailboxReady = "Ta commande est arrivée!" +DistributedMailboxNotOwner = "Désolé, ce n'est pas ta boîte aux lettres." +DistributedPhoneEmpty = "Tu peux utiliser n'importe quel téléphone pour commander des articles pour toi et pour ta maison. De nouveaux articles seront proposés dans l'avenir.\n\nAucun article n'est disponible à la commande maintenant, mais reviens voir plus tard!" + +# don't translate yet +Clarabelle = "Clarabelle" +MailboxExitButton = "Fermer boîte\naux lettres" +MailboxAcceptButton = "Accepter" +MailBoxDiscard = "Refuser" +MailboxAcceptInvite = "Accept this invite" +MailBoxRejectInvite = "Reject this invite" +MailBoxDiscardVerify = "Es-tu sûr de vouloir rejeter %s ?" +MailboxOneItem = "Ta boîte aux lettres contient 1 objet." +MailboxNumberOfItems = "Ta boîte aux lettres contient %s objets." +MailboxGettingItem = "Récupération de %s dans la boîte aux lettres." +MailboxGiftTag = "Cadeau de : %s" +MailboxGiftTagAnonymous = "Anonyme" +MailboxItemNext = "Objet\nsuivant" +MailboxItemPrev = "Objet\nprécédent" +MailboxDiscard = "Rejeter" +MailboxLeave = "Accepter" +CatalogCurrency = "bonbons" +CatalogHangUp = "Raccrocher" +CatalogNew = "NOUVEAUTÉ" +CatalogBackorder = "PRÉ-COMMANDE" +CatalogLoyalty = "SPECIAL" +CatalogPagePrefix = "Page" +CatalogGreeting = "Bonjour! Merci d'avoir appelé le catalogue de Clarabelle. Que puis-je pour toi?" +CatalogGoodbyeList = ["Au revoir!", + "Rappelle bientôt!", + "Merci de ton appel!", + "OK, au revoir!", + "Au revoir!", + ] +CatalogHelpText1 = "Tourne la page pour voir les articles qui sont en vente." +CatalogSeriesLabel = "Série %s" +CatalogGiftFor = "Acheter un cadeau pour :" +CatalogGiftTo = "Pour : %s" +CatalogGiftToggleOn = "Arrêter d'acheter\ndes cadeaux" +CatalogGiftToggleOff = "Acheter des\ncadeaux" +CatalogGiftToggleWait = "En train d'essayer!..." +CatalogGiftToggleNoAck = "Indisponible" +CatalogPurchaseItemAvailable = "Parfait ! Peut commencer à utiliser ton cadeau dès maintenant." +CatalogPurchaseGiftItemAvailable = "Parfait ! Ton cadeau pour %s sera livré dans sa boîte aux lettres." +CatalogPurchaseItemOnOrder = "Félicitations! Ton achat sera bientôt livré dans ta boîte aux lettres." +CatalogPurchaseGiftItemOnOrder = " Parfait ! Ton cadeau pour %s sera livré dans sa boîte aux lettres." +CatalogAnythingElse = "Puis-je autre chose pour toi?" +CatalogPurchaseClosetFull = "Ton placard est plein. Tu peux acheter cet article, mais tu devras supprimer quelque chose de ton placard pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article ?" +CatalogAcceptClosetFull = "Ton placard est plein. Tu dois rentrer et supprimer quelque chose de ton placard pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptShirt = "Tu portes maintenant ta nouvelle chemise. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptShorts = "Tu portes maintenant ton nouveau short. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptSkirt = "Tu portes maintenant ta nouvelle jupe. Ce que tu portais avant a été mis dans ton placard." +CatalogAcceptPole = "Tu peux maintenant attraper des poissons plus gros avec ta nouvelle canne!" +CatalogAcceptPoleUnneeded = "Tu as déjà une canne meilleure que celle-ci!" +CatalogAcceptChat = "Tu possèdes maintenant une nouvelle phrase de Chat rapide." +CatalogAcceptEmote = "Tu possèdes maintenant une nouvelle émotion !" +CatalogAcceptBeans = "Tu as reçu des bonbons !" +CatalogAcceptRATBeans = "Ta récompense de recrue Toon est arrivée !" + +CatalogAcceptNametag = "Your new name tag has arrived !" +CatalogAcceptGarden = "Tes matériaux de jardinage sont arrivés !" +CatalogAcceptPet = "Tu possèdes maintenant un nouveau tour pour ton Doodle !" +CatalogPurchaseHouseFull = "Ta maison est pleine. Tu peux acheter cet article, mais tu devras supprimer quelque chose dans ta maison pour faire de la place quand il arrivera.\n\nTu veux quand même acheter cet article ?" +CatalogAcceptHouseFull = "Ta maison est pleine. Tu dois rentrer et supprimer quelque chose dans ta maison pour faire de la place pour cet objet avant de pouvoir le sortir de la boîte aux lettres." +CatalogAcceptInAttic = "Ton nouvel article est maintenant dans ton grenier. Pour le placer dans ta maison, va à l'intérieur et clique sur le bouton \"Déplacer les meubles\"." +CatalogAcceptInAtticP = "Tes nouveaux articles sont maintenant dans ton grenier. Pour les placer dans ta maison, va à l'intérieur et clique sur le bouton \"Déplacer les meubles\"." +CatalogPurchaseMailboxFull = "Ta boîte aux lettres est pleine! Tu ne peux pas acheter cet article avant d'avoir sorti des articles de ta boîte aux lettres pour y faire de la place." +CatalogPurchaseGiftMailboxFull = "La boîte aux lettres de %s est pleine ! Tu ne peux pas acheter cet article." +CatalogPurchaseOnOrderListFull = "Tu as trop d'articles en commande actuellement. Tu ne peux pas commander d'autres articles avant que ceux que tu as déjà commandés ne soient arrivés." +CatalogPurchaseGiftOnOrderListFull = "%s a actuellement trop d'articles en commande." +CatalogPurchaseGeneralError = "L'article n'a pas pu être acheté à cause d'une erreur interne au jeu: code d'erreur %s." +CatalogPurchaseGiftGeneralError = "Le cadeau n'a pas pu être offert à ton(tes) %(friend) en raison d'une erreur interne %(error) au jeu." +CatalogPurchaseGiftNotAGift = "Cet article n'a pas pu être envoyé à %s parce qu'il n'est pas assez avancé dans le jeu." +CatalogPurchaseGiftWillNotFit = "Cet article n'a pas pu être envoyé à %s parce qu'il ne lui correspond pas." +CatalogPurchaseGiftLimitReached = "Cet article n'a pas pu être envoyé à %s parce qu'il le possède déjà." +CatalogPurchaseGiftNotEnoughMoney = "Cet article n'a pas pu être envoyé à %s parce que tu n'as pas les moyens de l'acheter." +CatalogAcceptGeneralError = "L'article n'a pas pu être retiré de ta boîte aux lettres à cause d'une erreur interne au jeu: code d'erreur %s." +CatalogAcceptRoomError = "Tu n'as pas de place pour mettre cet article. Tu vas devoir te débarasser de quelquechose." +CatalogAcceptLimitError = "Tu possèdes déjà beaucoup d'exemplaires de cet article. Tu vas devoir te débarasser de quelquechose." +CatalogAcceptFitError = "Cela ne t'ira pas ! Tu dois en faire don à un toon qui en a besoin." +CatalogAcceptInvalidError = "Cet article n'est plus à la mode. Tu dois en faire don à un toon qui en a besoin." + +MailboxOverflowButtonDicard = "Supprimer" +MailboxOverflowButtonLeave = "Garder" + +# don't translate yet +HDMoveFurnitureButton = "Déplacer\nles meubles" +HDStopMoveFurnitureButton = "Meubles\nplacés" +HDAtticPickerLabel = "Dans le grenier" +HDInRoomPickerLabel = "Dans la pièce" +HDInTrashPickerLabel = "À la poubelle" +HDDeletePickerLabel = "Supprimer ?" +HDInAtticLabel = "Grenier" +HDInRoomLabel = "Pièce" +HDInTrashLabel = "Poubelle" +HDToAtticLabel = "Mettre\nau grenier" +HDMoveLabel = "Déplacer" +HDRotateCWLabel = "Tourner vers la droite" +HDRotateCCWLabel = "Tourner vers la gauche" +HDReturnVerify = "Remettre cet objet dans le grenier ?" +HDReturnFromTrashVerify = "Ressortir cet objet de la poubelle et le mettre dans le grenier ?" +HDDeleteItem = "Clique sur OK pour mettre cet objet à la poubelle ou sur Annuler pour le garder." +HDNonDeletableItem = "Tu ne peux pas supprimer les objets de ce type!" +HDNonDeletableBank = "Tu ne peux pas supprimer ta tirelire!" +HDNonDeletableCloset = "Tu ne peux pas supprimer ton armoire!" +HDNonDeletablePhone = "Tu ne peux pas supprimer ton téléphone!" +HDNonDeletableNotOwner = "Tu ne peux pas supprimer les affaires de %s!" +HDHouseFull = "Ta maison est pleine. Tu dois supprimer quelque chose d'autre dans ta maison ou ton grenier avant de pouvoir ressortir cet article de la poubelle." + +HDHelpDict = { + "DoneMoving" : "Terminer la décoration de la pièce.", + "Attic" : "Voir la liste des objets qui sont au grenier. Les objets qui ne sont pas dans ta pièce sont au grenier.", + "Room" : "Voir la liste des objets qui sont dans la pièce. Utile pour retrouver des objets perdus.", + "Trash" : "Voir les objets qui sont dans la poubelle. Les objets les plus anciens sont supprimés après un temps ou si la poubelle déborde.", + "ZoomIn" : "Agrandir la vue de la pièce.", + "ZoomOut" : "Éloigner la vue de la pièce.", + "SendToAttic" : "Stocker le meuble actuel dans le grenier.", + "RotateLeft" : "Tourner vers la gauche.", + "RotateRight" : "Tourner vers la droite.", + "DeleteEnter" : "Passer en mode suppression.", + "DeleteExit" : "Sortir du mode suppression.", + "FurnitureItemPanelDelete" : "Mettre %s à la poubelle.", + "FurnitureItemPanelAttic" : "Mettre %s dans la pièce.", + "FurnitureItemPanelRoom" : "Remettre %s au grenier.", + "FurnitureItemPanelTrash" : "Remettre %s au grenier.", + } + +# don't translate yet +MessagePickerTitle = "Tu as trop de phrases. Pour pouvoir acheter\n\"%s\"\n tu dois choisir une chose à retirer:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Es-tu certain de vouloir retirer \"%s\" de ton menu de Chat rapide ?" + +# don't translate yet +CatalogBuyText = "Acheter" +CatalogRentText = "Louer" +CatalogGiftText = "Cadeau" +CatalogOnOrderText = "En commande" +CatalogPurchasedText = "Déjà\nacheté" +CatalogGiftedText = "Offert\nà toi" +CatalogPurchasedGiftText = "Déjà\nPossédé" +CatalogMailboxFull = "Pas de place" +CatalogNotAGift = "N'est pas un cadeau" +CatalogNoFit = "ne va pas" +CatalogMembersOnly = "Réservé aux membres\n !" +CatalogSndOnText = "Connecté" +CatalogSndOffText = "Non connecté" + +CatalogPurchasedMaxText = "Maximum\ndéjà acheté" +CatalogVerifyPurchase = "Acheter %(item)s pour %(price)s bonbons?" +CatalogVerifyRent = "Louer %(item)s pour le prix de %(price)s bonbons?" +CatalogVerifyGift = "Acheter %(item)s pour %(price)s bonbons comme cadeau pour %(friend)s?" +CatalogOnlyOnePurchase = "Tu ne peux avoir qu'un de ces articles à la fois. Si tu achètes celui-là, il remplacera %(old)s.\n\nEs-tu certain(e) de vouloir acheter %(item)s pour %(price)s bonbons?" + +CatalogExitButtonText = "Raccrocher" +CatalogCurrentButtonText = "Articles actuels" +CatalogPastButtonText = "Articles précédents" + +TutorialHQOfficerName = "Harry du QG" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "Tom Tuteur", + 999 : "Toon Tailleur", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Bob le Banquier", + 2003 : "Professeur Pete", + 2004 : "Tammy le Tailleur", + 2005 : "Larry le Libraire", + 2006 : "Vincent - Vendeur", + 2011 : "Véronique - Vendeuse", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Vendeur de l'animalerie", + # NPCPetClerks + 2013 : "M. Vacarme", + 2014 : "Melle Vadrouille", + 2015 : "M. Vagabond", + # NPCPartyPerson + 2016 : "Party Planner Pete", + 2017 : "Party Planner Penny", + + # Silly Street + 2101 : "Daniel le Dentiste", + 2102 : "Sherry le Shérif", + 2103 : "Kitty Lerhume", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canary Minederien", + 2109 : "Souffle douleur", + 2110 : "A. Fiche", + 2111 : "Diego Ladanse", + 2112 : "Dr Tom", + 2113 : "Rollo le Magnifique", + 2114 : "Rose Dévent", + 2115 : "Dédé Coupage", + 2116 : "Costaud McDougal", + 2117 : "Madame Putride", + 2118 : "Jesse Jememoque", + 2119 : "Meryl Semarre", + 2120 : "Professeur Morderire", + 2121 : "Madame Marrante", + 2122 : "Harry Lesinge", + 2123 : "Emile Esime", + 2124 : "Gaëtan Pipourtoi", + 2125 : "Lazy Mut", + 2126 : "Professeur Lagaffe", + 2127 : "Woody Troissous", + 2128 : "Loulou Fifou", + 2129 : "Frank Fort", + 2130 : "Sylvie Brateur", + 2131 : "Jeanne Laplume", + 2132 : "Daffy Don", + 2133 : "Dr E. Phorique", + 2134 : "Simone Silence-on-tourne", + 2135 : "Marie Satourne", + 2136 : "Sal Amandre", + 2137 : "Heureux Kikomulisse", + 2138 : "Gaston", + 2139 : "Bernard Bavunpeu", + 2140 : "Billy le pêcheur", + + # Loopy Lane + 2201 : "Pierre le Postier", + 2202 : "Paul Ochon", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Tony Truant", + 2208 : "Nicole Lacolle", + 2209 : "Henri Gole", + 2210 : "Valérie Golotte", + 2211 : "Sally Salive", + 2212 : "Max Imum", + 2213 : "Lucy Rustine", + 2214 : "Dino Zore", + 2215 : "Jean Aimarre", + 2216 : "Lady Sparue", + 2217 : "Jones Requin", + 2218 : "Fanny Larant", + 2219 : "Lanouille", + 2220 : "Louis Leroc", + 2221 : "Tina Pachangé", + 2222 : "Electre O'Cardiogramme", + 2223 : "Sasha Touille", + 2224 : "Joe Lefumeux", + 2225 : "Toumou le pêcheur", + + # Punchline Place + 2301 : "Dr Faismarcher", + 2302 : "Professeur Tortillard", + 2303 : "Nancy Nanny", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Gaz", + 2309 : "Gros Bruce", + 2311 : "Frank O'Debord", + 2312 : "Dr Sensible", + 2313 : "Lucy Boulette", + 2314 : "Ned Lafronde", + 2315 : "Valérie Deveau", + 2316 : "Cindy Ka", + 2318 : "Mac Aroni", + 2319 : "Annick", + 2320 : "Alfonse Danslebrouillard", + 2321 : "Vif le pêcheur", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Willy - Vendeur", + 1002 : "Billy - Vendeur", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Alain Térieur", + # NPCFisherman + 1008 : "Vendeur de l'animalerie", + # NPCPetClerks + 1009 : "M. Ouahouah", + 1010 : "Melle Ronron", + 1011 : "Mme Glouglou", + # NPCPartyPerson + 1012 : "Party Planner Phil", + 1013 : "Party Planner Patty", + + # Barnacle Boulevard + 1101 : "Sam Suffit", + 1102 : "Capitaine Carl", + 1103 : "Frank L'écaille", + 1104 : "Docteur Squale", + 1105 : "Amiral Crochet", + 1106 : "Mme Amidon", + 1107 : "Jim Nastic", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gary Glouglou", + 1113 : "Anna-Lise Deussan", + 1114 : "Mick Robe", + 1115 : "Sheila Seiche, Avocate", + 1116 : "Bernard Bernache", + 1117 : "Capitaine Hautlecoeur", + 1118 : "Choppy McDougal", + 1121 : "Marthe Aupiqueur", + 1122 : "Petit Salé", + 1123 : "Electre O'Magnétique", + 1124 : "Simon Strueux", + 1125 : "Elvire Debord", + 1126 : "Barnabé le pêcheur", + + # Seaweed Street + 1201 : "Barbara Bernache", + 1202 : "Art", + 1203 : "Ahab", + 1204 : "Rocky Roc", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professeur Planche", + 1210 : "Yaka Sauté", + 1211 : "Sarah Lenti", + 1212 : "Loulou Languedebois", + 1213 : "Dante Dauphin", + 1214 : "Aimé Duse", + 1215 : "Jean Peuplu", + 1216 : "Seymour Linet", + 1217 : "Cécile Savet", + 1218 : "Tim Pacifique", + 1219 : "Yvon Alot", + 1220 : "Minnie Stair", + 1221 : "McKee Labulle", + 1222 : "A. Marre", + 1223 : "Sid Seiche", + 1224 : "Anna Conda", + 1225 : "Bonzo Boitrop", + 1226 : "Ho Hisse", + 1227 : "Coral", + 1228 : "Rozo le pêcheur", + + # Lighthouse Lane + 1301 : "Ernest", + 1302 : "Ginette", + 1303 : "Gérard", + 1304 : "Hillary Varien", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Ecume de mer", + 1310 : "Ted Tentacule", + 1311 : "Jean Reveux", + 1312 : "Gaëtan Coque", + 1313 : "Gérard Timon", + 1314 : "Ralph Rouillé", + 1315 : "Docteur Dérive", + 1316 : "Elodie Toire", + 1317 : "Paule Pylone", + 1318 : "Barnabé Bouée", + 1319 : "David Bienosec", + 1320 : "Aldo Plate", + 1321 : "Dinah Esservi", + 1322 : "Peter Coussin", + 1323 : "Ned Savon", + 1324 : "Perle Démer", + 1325 : "Ned Setter", + 1326 : "G. Lafritte", + 1327 : "Cindy Nosore", + 1328 : "Sam Ouraille", + 1329 : "Shelly Beaucoup", + 1330 : "Icare Bonize", + 1331 : "Guy Rlande", + 1332 : "Martin le pêcheur", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Angèle Ici", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Lenny - Vendeur", + 3007 : "Penny - Vendeuse", + 3008 : "Warren Fagoté", + # NPCPêcheur + 3009 : "Vendeur de l'animalerie", + # NPCPetClerks + 3010 : "M. Cabo", + 3011 : "Melle Cabriole", + 3012 : "M. Cadichon", + # NPCPartyPerson + 3013 : "Party Planner Paul", + 3014 : "Party Planner Polly", + + # Walrus Way + 3101 : "M. Lapin", + 3102 : "Tante Angèle", + 3103 : "Tanguy", + 3104 : "Bonnie", + 3105 : "Freddy Frigo", + 3106 : "Paul Poulemouillée", + 3107 : "Patty Touteseule", + 3108 : "Ted Tobogan", + 3109 : "Patricia", + 3110 : "Jack Pot", + 3111 : "O. Tain", + 3112 : "Allan Bic", + 3113 : "Harry Hystérique", + 3114 : "Nathan Pastrop", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carl Magne", + 3120 : "Mike Mouffles", + 3121 : "Joe Courant", + 3122 : "Lucy Luge", + 3123 : "Nicole Apon", + 3124 : "Lance Iceberg", + 3125 : "Colonel Mâchetout", + 3126 : "Colette Erol", + 3127 : "Alex Térieur", + 3128 : "George Lacolle", + 3129 : "Brigitte Boulanger", + 3130 : "Sandy", + 3131 : "Pablo Paresseux", + 3132 : "Braise Cendrar", + 3133 : "Dr Jevoismieux", + 3134 : "Sébastien Toutseul", + 3135 : "Nelly Quéfié", + 3136 : "Claude Iqué", + 3137 : "M. Gel", + 3138 : "M. Empoté", + 3139 : "Virginie Aimaitropaul", + 3140 : "Lucile la pêcheuse", + + # Sleet Street + 3201 : "Tante Artique", + 3202 : "Tremblotte", + 3203 : "Walt", + 3204 : "Dr Ivan Deslunettes", + 3205 : "Boris Tourne", + 3206 : "Victoire Alarraché", + 3207 : "Dr Marmotter", + 3208 : "Phil Electrique", + 3209 : "Geoffroy Auxmains", + 3210 : "Sam Simiesque", + 3211 : "Gaelle Segèle", + 3212 : "Freddy Frigo", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Pierre Lasueur", + 3218 : "Lou Minaire", + 3219 : "Tom Tandem", + 3220 : "G. Ternue", + 3221 : "Nelly Neige", + 3222 : "Moricette Decuisine", + 3223 : "Chappy", + 3224 : "Agnes Kimo", + 3225 : "Frimas Ladouce", + 3226 : "Prospère Noël", + 3227 : "Ray Ondesoleil", + 3228 : "Maurice Quetout", + 3229 : "Hernie Discale", + 3230 : "Benjy Boule-à-zéro", + 3231 : "Choppy", + 3232 : "Albert le pêcheur", + + #Polar Place + 3301 : "Cathou Coupet", + 3302 : "Bjorn Bord", + 3303 : "Dr Flic-Flac", + 3304 : "Eddie le Yéti", + 3305 : "Mac Ramée", + 3306 : "Paul Hère", + # NPC Fisherman + 3307 : "Pêcheuse Frédérique", + 3308 : "Marcel Glassault", + 3309 : "Théo Citron", + 3310 : "Professeur Flocon", + 3311 : "Cella Glasse", + 3312 : "J. Boulet de Mars", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Chris Crisse", + 3318 : "Alan Sthiver", + 3319 : "Bo Nedlaine", + 3320 : "Lisette Frisquette", + 3321 : "Cédric Piolet", + 3322 : "Corinne Za", + 3323 : "Aurore Beau-Réal", + 3324 : "Mandra Gore", + 3325 : "Alban Quise", + 3326 : "Blanche", + 3327 : "J. Gault", + 3328 : "Rémi Taine", + 3329 : "Isaure Betière", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Molly Masson", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Doe - Vendeur", + 4007 : "Ray - Vendeur", + 4008 : "Bernard Mony", + # NPCFisherman + 4009 : "Vendeur de l'animalerie", + # NPCPetClerks + 4010 : "M. Chris", + 4011 : "M. Neil", + 4012 : "Melle Western", + # NPCPartyPerson + 4013 : "Party Planner Preston", + 4014 : "Party Planner Penelope", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr Tefaispasdebile", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Clément de Sol", + 4109 : "Carlos", + 4110 : "Métro Gnome", + 4111 : "Adam Levent", + 4112 : "Fa", + 4113 : "Madame Manière", + 4114 : "Eric Ochet", + 4115 : "Labelle Decadix", + 4116 : "Piccolo", + 4117 : "Mandy Lynn", + 4118 : "André Sansfrapper", + 4119 : "Moe Zart", + 4120 : "Viola Coussin", + 4121 : "Ray Mineur", + 4122 : "Armanthe Réglisse", + 4123 : "Ted l'éclair", + 4124 : "Riff Iffifi", + 4125 : "Mélodie Dantan", + 4126 : "Bel Canto", + 4127 : "Amédé Chausson", + 4128 : "Luciano Lescoop", + 4129 : "Terry Golo", + 4130 : "Rémi Crophone", + 4131 : "Abraham Armoire", + 4132 : "Sally Tristounet", + 4133 : "D. Taché", + 4134 : "Dave Disco", + 4135 : "Séraphin Ducompte", + 4136 : "Patty Pause", + 4137 : "Tony Doiseau", + 4138 : "Rémi Depain", + 4139 : "Harmony Ka", + 4140 : "Ned Maladroit", + 4141 : "Jojo le pêcheur", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Jack Bûcheron", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Elise", + 4209 : "Mo Végou", + 4211 : "Carl Concerto", + 4212 : "Funeste Funèbre", + 4213 : "Fran Chement", + 4214 : "Tina Crampon", + 4215 : "Tim Rouletroprès", + 4216 : "K. Outchouc", + 4217 : "Anton Beaugarçon", + 4218 : "Vanessa Vapasdutout", + 4219 : "Sid Sonate", + 4220 : "Jean-Bière", + 4221 : "Moe Madrigal", + 4222 : "John Deuf", + 4223 : "Penny Souffleur", + 4224 : "Jim Jongle", + 4225 : "Holly Stérie", + 4226 : "Georgina Gorge", + 4227 : "Francesca Taphonique", + 4228 : "August Ave", + 4229 : "June Comprendsrien", + 4230 : "Julius Césure", + 4231 : "Steffi Nalise", + 4232 : "Marie Toivite", + 4233 : "Charlie Lacarpe", + 4234 : "Guy Tare", + 4235 : "Larry le pêcheur", + + # Tenor Terrace + 4301 : "Yuki", + 4302 : "Anna", + 4303 : "Léo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tabatha", + 4309 : "Mémé Chignon", + 4310 : "Marthe Ingale", + 4311 : "Charlie Mande", + 4312 : "Ma Sage", + 4313 : "Muget Muet", + 4314 : "Dino Dodo", + 4315 : "Karen Rouages", + 4316 : "Tim Tango", + 4317 : "Sue Bitto", + 4318 : "Bob Marlin", + 4319 : "K. Zou", + 4320 : "Camille Cloda", + 4321 : "Luky Luth", + 4322 : "Henry Thme", + 4323 : "Hanna Purna", + 4324 : "Ellie", + 4325 : "Braque Labanque", + 4326 : "Jonathan Plurien", + 4327 : "Flim Flam", + 4328 : "Wagner", + 4329 : "Tyler Prompteur", + 4330 : "Quentin", + 4331 : "M. Costello", + 4332 : "Ziggy", + 4333 : "Harry", + 4334 : "Freddie Fastoche", + 4335 : "Serge le pêcheur", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Prune - Vendeuse", + 5006 : "Rose - Vendeuse", + 5007 : "Bonnie Menteuse", + # NPCFisherman + 5008 : "Vendeur de l'animalerie", + # NPCPetClerks + 5009 : "Mme Flore Halie", + 5010 : "M. Tom Hatte", + 5011 : "M. Ray Glisse", + # NPCPartyPerson + 5012 : "Party Planner Pierce", + 5013 : "Party Planner Peggy", + + # Elm Street + 5101 : "Eugène", + 5102 : "Susan", + 5103 : "Piaf", + 5104 : "Parpaillon", + 5105 : "Jack", + 5106 : "Bjorn le Barbier", + 5107 : "Felipe le Postier", + 5108 : "Janette l'Aubergiste", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dr Lacouenne", + 5114 : "Affaiblissement", + 5115 : "Rosée Dumatin", + 5116 : "R. Noncule", + 5117 : "Pétale", + 5118 : "Victor Nemuse", + 5119 : "Barry Dicule", + 5120 : "La taupe", + 5121 : "Paula Roïd", + 5122 : "A. Masse", + 5123 : "Diane Avecnouscesoir", + 5124 : "Chen Avélo", + 5125 : "A. Sperge", + 5126 : "Madame Mère", + 5127 : "Polly Pollène", + 5128 : "Salma Range", + 5129 : "Sally la pêcheuse", + + # Maple Street + 5201 : "Jacquot", + 5202 : "Cynthia", + 5203 : "Citronelle", + 5204 : "Bert", + 5205 : "Omar Souin", + 5206 : "Ray Zainblanc", + 5207 : "Sophie Stiquée", + 5208 : "Samantha Pir", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Gros Balourd", + 5214 : "Sam Gratte", + 5215 : "Henry Chisson", + 5216 : "Jim Lassenteur", + 5217 : "Walter Ego", + 5218 : "Rocky Groseille", + 5219 : "Mo Viette", + 5220 : "Adam Telle", + 5221 : "Flamant rose", + 5222 : "Pétronille Hiliste", + 5223 : "Marc Assin", + 5224 : "Oncle Balourd", + 5225 : "Pamela Asaplace", + 5226 : "Pierre Mousse", + 5227 : "B. Gonia", + 5228 : "Avi Dité", + 5229 : "Lili la pêcheuse", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Crystelle", + 5306 : "S. Cargot", + 5307 : "Cyril Semarre", + 5308 : "Nell Ronchon", + 5309 : "Romaine", + 5310 : "Thimothé", + 5311 : "Jonas Ticot", + 5312 : "Eugène", + 5313 : "Zucchini l'entraîneur", + 5314 : "Merlin Sect", + 5315 : "Oncle Boueux", + 5316 : "Oncle Patapouf", + 5317 : "Lima, détective", + 5318 : "César", + 5319 : "Rose", + 5320 : "J. Boulée", + 5321 : "Professeur Chèvrefeuille", + 5322 : "Rose la pêcheuse", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "Benjamin Salor", + 8002 : "Yvon Affond-Lacaisse", + 8003 : "Emma Nicourt", + 8004 : "Phil Assent", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Mélusine Enfaillite", + 9002 : "Tom Pouce", + 9003 : "Denis Doiseau", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Jill - Vendeuse", + 9009 : "Phil - Vendeur", + 9010 : "U. Zure", + # NPCFisherman + 9011 : "Vendeur de l'animalerie", + # NPCPetClerks + 9012 : "Melle Isabelle Bulle", + 9013 : "Mme Dorothée Dor", + 9014 : "M. Pierre Pionce", + # NPCPartyPerson + 9015 : "Party Planner Patrick", + 9016 : "Party Planner Pearl", + + # Lullaby Lane + 9101 : "Ed", + 9102 : "Big Mama", + 9103 : "P. J.", + 9104 : "Fay Debeauxrêves", + 9105 : "Professeur Baillebeaucoup", + 9106 : "Max", + 9107 : "Câline", + 9108 : "Matt Heula", + 9109 : "Daphné Puisé", + 9110 : "Kathy Mini", + 9111 : "Ali Mentation", + 9112 : "Lou Laberceuse", + 9113 : "Jacques Horloge", + 9114 : "Emma Scara", + 9115 : "Bébé MacDougal", + 9116 : "Celui qui danse avec les moutons", + 9117 : "Sam Suffit", + 9118 : "Stella Lune", + 9119 : "Rocco", + 9120 : "Aron Flebeaucoup", + 9121 : "Serena Dàlanuitombée", + 9122 : "Serge Souslesyeux", + 9123 : "Teddy Blaireau", + 9124 : "Nina Lamparo", + 9125 : "Dr Chassieux", + 9126 : "Thérèse Eveillé", + 9127 : "Tabby Tude", + 9128 : "Amédé Brouilletoitoutseul", + 9129 : "Amélie Decamp", + 9130 : "Paul Potdechambre", + 9131 : "Susan Sieste", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Titine la pêcheuse", + + # Pajama Place + 9201 : "Nesdor", + 9202 : "Orville", + 9203 : "Plume", + 9204 : "Claire de Moune", + 9205 : "Olivier Daure", + 9206 : "Phèdre Don", + 9207 : "Sacha Lumea", + 9208 : "Dave Bigleau", + 9209 : "Dr Drin", + 9210 : "Mike Mac", + 9211 : "Aurore", + 9212 : "Phœbe Lancre", + 9213 : "Fortuné Dargent", + 9214 : "Dr Ouffe", + 9215 : "Honoré", + 9216 : "Tartine", + 9217 : "Linda Kapok", + 9218 : "Rita Thasse", + 9219 : "La comtesse", + 9220 : "Matt Thuvu", + 9221 : "Père San", + 9222 : "Ron Chonneau", + 9223 : "Fay Dodeau", + 9224 : "Sandie Marchand", + 9225 : "Élodie Dont", + 9226 : "Laurent Lauronpat", + 9227 : "Édouard Sagrate", + 9228 : "Michu Chotte", + 9229 : "Eva Sandor-Mir", + 9230 : "Pierrot", + 9231 : "Léo Galleau", + 9232 : "Rosée de Lune", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "S. André", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Mairie de Toontown", ""), + 2514 : ("Banque de Toontown", ""), + 2516 : ("Ecole de Toontown", ""), + 2518 : ("Bibliothèque de Toontown", ""), + 2519 : ("Boutique à gags", ""), + 2520 : ("Quartier Général des Toons", ""), + 2521 : ("Boutique de prêt-à-porter", ""), + 2522 : ("ANIMALERIE", ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Tout-sourire - Réparations dentaires", ""), + 2602 : ("", ""), + 2603 : ("Mineurs Pince-sans-rire", ""), + 2604 : ("Qui vivra, verrat", ""), + 2605 : ("Usine à pancartes de Toontown", ""), + 2606 : ("", ""), + 2607 : ("Haricots sauteurs", ""), + 2610 : ("Dr. Tom Lepitre", ""), + 2611 : ("", ""), + 2616 : ("Barbefolle - Déguisements", ""), + 2617 : ("Cascades Comiques", ""), + 2618 : ("Nouba & Co", ""), + 2621 : ("Avions en papier", ""), + 2624 : ("Aux joyeux hooligans", ""), + 2625 : ("La maison du pâté raté", ""), + 2626 : ("Chez Jesse - Réparation de blagues", ""), + 2629 : ("Le coin du rire", ""), + 2632 : ("L'école des clowns", ""), + 2633 : ("Thé-hier - Salon de thé", ""), + 2638 : ("Théâtre de Toontown", ""), + 2639 : ("Monnaie de singe", ""), + 2643 : ("Bouteilles en boîte", ""), + 2644 : ("Farces farcies", ""), + 2649 : ("Magasin de jeux", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Leçons de rire", ""), + 2655 : ("Drôle d'argent - Caisse d'épargne", ""), + 2656 : ("Voitures de clown d'occasion", ""), + 2657 : ("Pirouettes de Pierrette", ""), + 2659 : ("L'univers des vibrateurs", ""), + 2660 : ("Machines à chatouilles", ""), + 2661 : ("Daffy Taffy", ""), + 2662 : ("Dr E. Phorique", ""), + 2663 : ("Théâtre de Toontown", ""), + 2664 : ("Les mimes marrants", ""), + 2665 : ("Le Manège - Agence de voyages", ""), + 2666 : ("Bouteilles de gaz hilarant", ""), + 2667 : ("Au bon temps", ""), + 2669 : ("Chez Gaston - ballons pas folichons", ""), + 2670 : ("Fourchettes à soupe", ""), + 2671 : ("Quartier Général des Toons", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Théâtre de Toontown", ""), + 2705 : ("Tony Truant - Bruits en tout genre", ""), + 2708 : ("Colle bleue", ""), + 2711 : ("Bureau de poste de Toontown", ""), + 2712 : ("Café des gloussements", ""), + 2713 : ("Café du rire", ""), + 2714 : ("Théâtre de Toontown", ""), + 2716 : ("Dr Ãle de soupe", ""), + 2717 : ("Boîtes en bouteille", ""), + 2720 : ("Plaies et Bosses - Réparations de voitures", ""), + 2725 : ("", ""), + 2727 : ("Bouteilles et boîtes Selter", ""), + 2728 : ("Crème de jour évanescente", ""), + 2729 : ("Ornithorynques 14 carats", ""), + 2730 : ("La gazette du rire", ""), + 2731 : ("", ""), + 2732 : ("Spaghettis et barbituriques", ""), + 2733 : ("Cerf-volants en fonte", ""), + 2734 : ("Tasses et soucoupes volantes", ""), + 2735 : ("Le Pétard mouillé", ""), + 2739 : ("Réparation de fous rires", ""), + 2740 : ("Pétards d'occasion", ""), + 2741 : ("", ""), + 2742 : ("Quartier Général des Toons", ""), + 2743 : ("", ""), + 2744 : ("", ""), + 2747 : ("Encre visible", ""), + 2748 : ("Rions un peu", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Coussins sonores", ""), + 2802 : ("Boulets de démolition gonflables", ""), + 2803 : ("Théâtre de Toontown", ""), + 2804 : ("Dr. Faismarcher, chiropracteur", ""), + 2805 : ("", ""), + 2809 : ("Salle de gym Le Poids lent", ""), + 2814 : ("Théâtre de Toontown", ""), + 2818 : ("Au pâté volant", ""), + 2821 : ("", ""), + 2822 : ("Sandwichs au poulet synthétique", ""), + 2823 : ("Glaces hilarantes", ""), + 2824 : ("Cinéma des blagues", ""), + 2829 : ("Balivernes", ""), + 2830 : ("Les piques d'Annick", ""), + 2831 : ("La maison du rire du professeur Tortillard", ""), + 2832 : ("Quartier Général des Toons", ""), + 2833 : ("", ""), + 2834 : ("Salle des urgences des morts de rire", ""), + 2836 : ("", ""), + 2837 : ("Hardi - Séminaires", ""), + 2839 : ("A la nouille amère", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : ("Boutique à gags", ""), + 1507 : ("Quartier Général des Toons", ""), + 1508 : ("Boutique de prêt-à-porter", ""), + 1510 : ("ANIMALERIE", ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Gilets de sauvetage d'occasion", ""), + 1604 : ("Costumes de bain - Nettoyage à sec", ""), + 1606 : ("Crochet - Réparation d'horloges", ""), + 1608 : ("Le Lof", ""), + 1609 : ("A l'appât rance", ""), + 1612 : ("Banque Sixsous", ""), + 1613 : ("La Pieuvre, cabinet d'avocats", ""), + 1614 : ("Toutes voiles devant - Boutique", ""), + 1615 : ("Yatch qu'à demander!", ""), + 1616 : ("Barbe Noire - Salon de beauté", ""), + 1617 : ("La mer à voir - Opticien", ""), + 1619 : ("L'écorcaire - Chirurgie arboricole", ""), + 1620 : ("Babord-tribord", ""), + 1621 : ("Salle de gym La poupe", ""), + 1622 : ("Gymnote - Electricité générale", ""), + 1624 : ("Réparation de couteaux et de peignes", ""), + 1626 : ("La perche rare - Tenues de soirée", ""), + 1627 : ("La cabane de Sam Suffit", ""), + 1628 : ("Accordeur de thons", ""), + 1629 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Ecole maternelle des p'tits loups", ""), + 1703 : ("Bar Accuda - Restaurant chinois", ""), + 1705 : ("Voiles à vendre", ""), + 1706 : ("La méduse médusée", ""), + 1707 : ("C'est assez - Boutique de cadeaux", ""), + 1709 : ("Gélée de méduse", ""), + 1710 : ("La belle bernache", ""), + 1711 : ("Restaurant de la pleine mer", ""), + 1712 : ("Salle de gymnote", ""), + 1713 : ("Chez Art - Cartes en tous genres", ""), + 1714 : ("Auberge du moulinet", ""), + 1716 : ("Maillots de bains pour sirènes", ""), + 1717 : ("Mi pacifique, mi raisin", ""), + 1718 : ("Société de taxi le Naufrage", ""), + 1719 : ("Société Je m'cache à l'eau", ""), + 1720 : ("Au requin malin", ""), + 1721 : ("Tout pour la mer", ""), + 1723 : ("Au royaume des algues", ""), + 1724 : ("Au mérou amoureux", ""), + 1725 : ("J'en pince pour toi - Crabes frais", ""), + 1726 : ("Bière à flots", ""), + 1727 : ("Je rame pour vous", ""), + 1728 : ("Limules porte-bonheur", ""), + 1729 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Les petits péchés", ""), + 1804 : ("Salle de gym Les mollusques", ""), + 1805 : ("Un petit ver pour le déjeuner", ""), + 1806 : ("Toucoule - Chapelier", ""), + 1807 : ("Coûte que soute", ""), + 1808 : ("Appât si vite!", ""), + 1809 : ("Seaux rouillés", ""), + 1810 : ("L'ancre noire", ""), + 1811 : ("Mérou tu vas chercher tout ça?", ""), + 1813 : ("A mâts couverts, conseiller", ""), + 1814 : ("Le Ho Hisse", ""), + 1815 : ("Quoi de neuf dockteur ?", ""), + 1818 : ("Café des sept mers", ""), + 1819 : ("Au dîner des dockers", ""), + 1820 : ("L'hameçon gobé - Farces et attrapes", ""), + 1821 : ("Chez Neptoon", ""), + 1823 : ("A la pomme de mât", ""), + 1824 : ("Au chien pas gai", ""), + 1825 : ("Le hareng sort! Marché aux poissons", ""), + 1826 : ("Le placard de Gérard", ""), + 1828 : ("Palais du lest d'Ernest", ""), + 1829 : ("Merlan l'enchanteur", ""), + 1830 : ("O sole et mio - Objets trouvés", ""), + 1831 : ("Une perle à domicile", ""), + 1832 : ("Supérette La Goélette", ""), + 1833 : ("Costumes pour gaillards d'avant", ""), + 1834 : ("Tranchement ridicule!", ""), + 1835 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : ("Boutique à gags", ""), + 4504 : ("Quartier Général des Toons", ""), + 4506 : ("Boutique de prêt-à-porter", ""), + 4508 : ("ANIMALERIE", ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Tom-Tom - Tambours", ""), + 4604 : ("A quatre temps", ""), + 4605 : ("Fifi - Violons d'Ingres", ""), + 4606 : ("La case des castagnettes", ""), + 4607 : ("Vêtements Toon branchés", ""), + 4609 : ("Dot, Raie, Mie - Pianos", ""), + 4610 : ("Attention refrain!", ""), + 4611 : ("Diapasons à l'unisson", ""), + 4612 : ("Dr. Tefaispasdebile - Dentiste", ""), + 4614 : ("On rase gratis pour une chanson", ""), + 4615 : ("Pizzéria chez Piccolo", ""), + 4617 : ("La mandoline joyeuse", ""), + 4618 : ("Salles des césures", ""), + 4619 : ("En avant la musique!", ""), + 4622 : ("Oreillers à mentonnière", ""), + 4623 : ("Bémols à la dièse", ""), + 4625 : ("Tuba de dentifrice", ""), + 4626 : ("Notations", ""), + 4628 : ("Assurance accidentelle", ""), + 4629 : ("Riff - Assiettes en papier", ""), + 4630 : ("La musique est notre force", ""), + 4631 : ("Canto de vous connaître!", ""), + 4632 : ("Boutique de la danse des heures", ""), + 4635 : ("Le quotidien des cantatrices", ""), + 4637 : ("Pour la bonne mesure", ""), + 4638 : ("Boutique Hard Rock", ""), + 4639 : ("Les quatre saisons - Antiquités", ""), + 4641 : ("L'actualité du yéyé", ""), + 4642 : ("D. Taché - Nettoyage à sec", ""), + 4645 : ("Club 88", ""), + 4646 : ("", ""), + 4648 : ("Le Toon siffleur - Déménageurs", ""), + 4649 : ("Quartier Général des Toons", ""), + 4652 : ("Boutique des doubles-croches", ""), + 4653 : ("", ""), + 4654 : ("Haut perché - Toitures", ""), + 4655 : ("La clé de sol - Ecole de cuisine", ""), + 4656 : ("", ""), + 4657 : ("Quatuor du barbier", ""), + 4658 : ("Pianos en chute libre", ""), + 4659 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("L'eau de rose - Ecole de valse", ""), + 4702 : (" Timbre de bois - Fournitures pour bûcherons", ""), + 4703 : ("Gros Bizet à tous!", ""), + 4704 : ("Tina - Concerts de concertina", ""), + 4705 : ("Il est déjà cithare ?", ""), + 4707 : ("Studio d'effets sonores Doppler", ""), + 4709 : ("Pirouettes - Magasin d'alpinisme", ""), + 4710 : ("Polka tu routes si vite ? Auto-école", ""), + 4712 : ("Mets un bémol! Réparation de pneus", ""), + 4713 : ("Dos dièse - Vêtements de luxe pour hommes", ""), + 4716 : ("Harmonicas à quatre voix", ""), + 4717 : ("Sonate pas ta faute! Assurance automobile", ""), + 4718 : ("Chopins de bière et autres ustensiles de cuisine", ""), + 4719 : ("Camping-cars Madrigal", ""), + 4720 : ("Le bon Toon", ""), + 4722 : ("Doublures pour ouvertures", ""), + 4723 : ("Bach à toi! Jeux et balançoires", ""), + 4724 : ("(Cale)sons blancs pour filles et garçons", ""), + 4725 : ("Le barbier baryton", ""), + 4727 : ("Cordes vocales tressées", ""), + 4728 : ("Chante en sourdine!", ""), + 4729 : ("Librairie J'aime lyre", ""), + 4730 : ("Lettre à un pou", ""), + 4731 : ("Des Toons de bon ton", ""), + 4732 : ("Etude brute ? Troupe de théâtre", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Soufflet pour accordéons", ""), + 4736 : ("Hyminent - Préparatifs de mariage", ""), + 4737 : ("Harpe Hônneur", ""), + 4738 : ("Mécanique cantique - Cadeaux", ""), + 4739 : ("Quartier Général des Toons", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Crêp'chignon", ""), + 4803 : ("Quelle Mezzo! Service de domestiques", ""), + 4804 : ("Ecole myxolidienne pour serveurs de barres", ""), + 4807 : ("Massage des Brahms et des jambes", ""), + 4809 : ("C'est une cata-strophe!", ""), + 4812 : ("", ""), + 4817 : ("Magasin d'animaux ternaires", ""), + 4819 : ("Chez Yuki - Ukélélés", ""), + 4820 : ("", ""), + 4821 : ("Chez Anna - Croisières", ""), + 4827 : ("Montres Lamesure", ""), + 4828 : ("Ravel - Réveils et horloges", ""), + 4829 : ("Chez Pachelbel - Obus pour canons et fugues", ""), + 4835 : ("Ursatz pour Kool Katz", ""), + 4836 : ("Reggae royal", ""), + 4838 : ("Ecole de kazoologie", ""), + 4840 : ("Coda Pop - Boissons musicales", ""), + 4841 : ("Lyre et Lyre", ""), + 4842 : ("Société Lasyncope", ""), + 4843 : ("", ""), + 4844 : ("Moto - deux roues", ""), + 4845 : ("Les élégies élégantes d'Ellie", ""), + 4848 : ("De haute luth - Caisse d'épargne", ""), + 4849 : ("", ""), + 4850 : ("L'accord emprunté - Prêteur sur gages", ""), + 4852 : ("Flasques fleuries pour flûtes", ""), + 4853 : ("Chez Léo - Garde-feu", ""), + 4854 : ("Chez Wagner - Vidéos de violons voilés", ""), + 4855 : ("Réseau de radeau-diffusion", ""), + 4856 : ("", ""), + 4862 : ("Les quadrilles quintessencielles de Quentin", ""), + 4867 : ("M. Costello - Kazoos à gogo", ""), + 4868 : ("", ""), + 4870 : ("Chez Ziggy - Zoo et Zigeunermusik", ""), + 4871 : ("Chez Harry - Harmonies harmonieuses", ""), + 4872 : ("Freddie Fastoche - Touches de piano", ""), + 4873 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : ("Boutique à gags", ""), + 5502 : ("Quartier Général des Toons", ""), + 5503 : ("Boutique de prêt-à-porter", ""), + 5505 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("L'œil de bouillon - Optométrie", ""), + 5602 : ("Eugène Coulissant - Cravates", ""), + 5603 : ("Arrête tes salades!", ""), + 5604 : ("Gai, gai, marions-les!", ""), + 5605 : ("Sols et meubles", ""), + 5606 : ("Pétales", ""), + 5607 : ("Bureau de composte", ""), + 5608 : ("Pop corn yéyé", ""), + 5609 : ("La baie au trésor", ""), + 5610 : ("L'œil au beurre noir - Cours de boxe", ""), + 5611 : ("Les gags de la Taupe", ""), + 5613 : ("La meule à zéro - Barbier", ""), + 5615 : ("Chez Piaf - Graines pour oiseaux", ""), + 5616 : ("Auberge de la goutte", ""), + 5617 : ("Chez Parpaillon - Papillons", ""), + 5618 : ("Deux pois deux mesures", ""), + 5619 : ("Chez Jack - Haricots géants", ""), + 5620 : ("Auberge du rateau", ""), + 5621 : ("La critique du Raisin pur", ""), + 5622 : ("La petite reine - claude - Bicyclettes", ""), + 5623 : ("Bains moussants pour oiseaux", ""), + 5624 : ("Ecoute ta mère", ""), + 5625 : ("Dur de la feuille", ""), + 5626 : ("Travaux d'aiguille (de pin)", ""), + 5627 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Le bambou du tunnel", ""), + 5702 : ("Les rateaux de Jacquot", ""), + 5703 : ("Cynthia - Magasin de photosynthèses", ""), + 5704 : ("Citronelle Citron - Voitures d'occasion", ""), + 5705 : ("Meubles en herbe à puce", ""), + 5706 : (" 14 carottes - Bijoutiers", ""), + 5707 : ("Fruit musical", ""), + 5708 : ("Sans soucis - Agence de voyages", ""), + 5709 : ("Astroturf - Tondeuses", ""), + 5710 : ("Gym des narcisses", ""), + 5711 : ("Bonneterie de jardin", ""), + 5712 : ("Statues squottes", ""), + 5713 : ("Buis clos", ""), + 5714 : ("Bouteilles d'eau de roche", ""), + 5715 : ("La Meule nouvelle", ""), + 5716 : ("Qui s'y frotte s'y pique - Prêteur sur gages", ""), + 5717 : ("La fleur qui mouille", ""), + 5718 : ("Le chèvre-feuille - Animalerie", ""), + 5719 : ("Sauge d'une nuit d'été - Détective privé", ""), + 5720 : ("La feuille de vigne - Prêt-à-porter masculin", ""), + 5721 : ("Routabaga 66 - Restaurant", ""), + 5725 : ("Boutique du grain d'orge", ""), + 5726 : ("Bert", ""), + 5727 : ("Le trou sans fond - Caisse d'épargne", ""), + 5728 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : ("Quartier Général des Toons", ""), + 5804 : ("La vase de Soisson", ""), + 5805 : ("Le cerveau lent", ""), + 5809 : ("Drôle d'oiseau - Ecole de clowns", ""), + 5810 : ("Ca ne rom à rain!", ""), + 5811 : ("Auberge Inn", ""), + 5815 : ("Des racines & des herbes", ""), + 5817 : ("Pommes et oranges", ""), + 5819 : ("Pantalons vert citron", ""), + 5821 : ("Centre de squash", ""), + 5826 : ("Matériel d'élevage de fourmis", ""), + 5827 : ("Terre bon marché", ""), + 5828 : ("Meubles Molasson", ""), + 5830 : ("Vide ton sac (de patates)", ""), + 5833 : ("Bar à salades", ""), + 5835 : ("Séjour en pots chez l'habitant", ""), + 5836 : ("Salles de bain J. Boulée", ""), + 5837 : ("L'école de la vigne", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Bibliothèque des berceuses", ""), + 9503 : ("Bar du roupillon", ""), + 9504 : ("Boutique à gags", ""), + 9505 : ("Quartier Général des Toons", ""), + 9506 : ("Boutique de prêt-à-porter", ""), + 9508 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Auberge des câlins", ""), + 9602 : ("Sommes au rabais", ""), + 9604 : ("Chez Ed - Edredons redondants", ""), + 9605 : ("Confection de bonnets de nuit", ""), + 9607 : ("Big Mama - Pyjamas des Bahamas", ""), + 9608 : ("Quand le chat dort, les souris dansent", ""), + 9609 : ("Roupillon pour trois ronds", ""), + 9613 : ("Théâtre du pays des rêves", ""), + 9616 : ("La veilleuse - Electricité générale", ""), + 9617 : ("L'enfant do - Petites musiques de nuit", ""), + 9619 : ("Relax Max", ""), + 9620 : ("PJ - Service de taxi", ""), + 9622 : ("Horloges du sommeil", ""), + 9625 : ("Histoire en boucle - Salon de beauté", ""), + 9626 : ("Histoiries Dodo", ""), + 9627 : ("Le tipi endormi", ""), + 9628 : ("Sam Suffit - Calendriers", ""), + 9629 : ("À l'édredon d'argent", ""), + 9630 : ("Marchand de sable", ""), + 9631 : ("Temps d'arrêt - Horloger", ""), + 9633 : ("Théâtre du pays des réves", ""), + 9634 : ("Je ronfle, donc je suis", ""), + 9636 : ("Assurance pour insomniaques", ""), + 9639 : ("Maison de l'hibernation", ""), + 9640 : ("Nous meublons vos rêves", ""), + 9642 : ("A la sciure de mon front", ""), + 9643 : ("Les yeux clos - Optométrie", ""), + 9644 : ("Combats d'oreillers nocturnes", ""), + 9645 : ("Auberge Viensmeborder", ""), + 9647 : ("Fais ton lit! Magasin de bricolage", ""), + 9649 : ("Bonnet blanc et blanc bonnet", ""), + 9650 : ("Réparateur de soupirs", ""), + 9651 : ("La vie est un ronflement tranquille", ""), + 9652 : ("Quartier Général des Toons", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Agence de voyages Vol de Nuit", ""), + 9704 : ("Animalerie du Hibou", ""), + 9705 : ("Garage de la Panne d'Oreiller", ""), + 9706 : ("Cabinet dentaire La Petite Souris", ""), + 9707 : ("Jardinerie de La Bâillerie", ""), + 9708 : ("Le Lys Douillet - Fleuriste", ""), + 9709 : ("Au Sommeil de Plomb - Plombier", ""), + 9710 : ("Rev'optique", ""), + 9711 : ("Service de réveil par téléphone", ""), + 9712 : ("Nous comptons les moutons pour vous!", ""), + 9713 : ("Roupille & Pionce, Avocats", ""), + 9714 : ("Croisière de rêve - Accastillage", ""), + 9715 : ("Banque du Doudou d'Or", ""), + 9716 : ("Le Lit en Cathédrale, farces at attrapes", ""), + 9717 : ("Pâtisserie du Croissant de Lune", ""), + 9718 : ("Sandwiches du Marchand de Sable", ""), + 9719 : ("Tout pour l'Oreiller", ""), + 9720 : ("Cours d'élocution pour somnambules", ""), + 9721 : ("Tapis du Loir", ""), + 9722 : ("Les Yeux Fermés - Spectacles en tous genres", ""), + 9725 : ("Pyjamas du Chat", ""), + 9727 : ("Ronflé, perdu", ""), + 9736 : ("Agence pour l'emploi Métiers de Rêve", ""), + 9737 : ("Au Tutu qui dort - École de danse", ""), + 9738 : ("Maison Ronflon", ""), + 9740 : ("Le Sabre Nocturne - Salle d'armes", ""), + 9741 : ("À l'Acarien Vorace - Destructeur de nuisibles", ""), + 9744 : ("Crème antirides Hicule", ""), + 9752 : ("Carburants Soporifiques", ""), + 9753 : ("Crèmes de Luna Glacées", ""), + 9754 : ("Randonnées équestres - Poney de Nuit", ""), + 9755 : ("Cinéma La Ronflette", ""), + 9756 : ("", ""), + 9759 : ("Institut de beauté du Bois - Dormant", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : ("Boutique à gags", ""), + 3508 : ("Quartier général des Toons", ""), + 3509 : ("Boutique de prêt-à-porter", ""), + 3511 : ("ANIMALERIE", ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Aurore boréale - Electricité générale", ""), + 3602 : ("Bonnets de pâques", ""), + 3605 : ("", ""), + 3607 : ("Le vieillard du blizzard", ""), + 3608 : ("A en perdre la boule (de neige)!", ""), + 3610 : ("Supérette Les Mirettes", ""), + 3611 : ("M. Lapin - Chasse-neige", ""), + 3612 : ("Conception d'igloos", ""), + 3613 : ("Glaces et miroirs", ""), + 3614 : ("Fabriquant de flocons d'avoine", ""), + 3615 : ("Omelettes norvégiennes", ""), + 3617 : ("Voyages en ballon à air froid", ""), + 3618 : ("Boule de neige! Gestion de crise", ""), + 3620 : ("Atelier de ski", ""), + 3621 : ("Glacier La fonte des neiges", ""), + 3622 : ("", ""), + 3623 : ("Croque-monsieur", ""), + 3624 : ("Sandwichs froids", ""), + 3625 : ("Tante Angèle - Radiateurs", ""), + 3627 : ("Chenil St Bernard", ""), + 3629 : ("La soupe aux pois - Café", ""), + 3630 : ("Agence de voyage Laglisse", ""), + 3634 : ("Télésièges rembourrés", ""), + 3635 : ("Bois de chauffage d'occasion", ""), + 3636 : ("Chair de poule bon marché", ""), + 3637 : ("Les patins de Patricia", ""), + 3638 : ("Hêtre ou ne pas hêtre", ""), + 3641 : ("Chez Tanguy - Bâteaux à dormir debout", ""), + 3642 : ("L'œil du cyclone - Opticien", ""), + 3643 : ("Chambre (froide) de danse", ""), + 3644 : ("Glaçons fondus", ""), + 3647 : ("Au pingouin sanguin - Magasin de smokings", ""), + 3648 : ("Glace instantanée", ""), + 3649 : ("Hambrrghers", ""), + 3650 : ("Articlités", ""), + 3651 : ("Freddy Frigo - Saucisses congelées", ""), + 3653 : ("Bijoux glacés", ""), + 3654 : ("Quartier général des Toons", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Stockage hivernal", ""), + 3703 : ("", ""), + 3705 : ("Glaçons pour deux", ""), + 3706 : ("Babas au rhume", ""), + 3707 : ("Mon igloo est mon royaume", ""), + 3708 : ("Chez Pluto", ""), + 3710 : ("Restaurant en chute libre", ""), + 3711 : ("", ""), + 3712 : ("Au royaume du déluge", ""), + 3713 : ("Les dents qui claquent - Dentiste polaire", ""), + 3715 : ("Les bonnes soupes de Tante Artique", ""), + 3716 : ("Salage et poivrage des routes", ""), + 3717 : ("Juneau sais pas ce que vous voulez dire", ""), + 3718 : ("Inventeur de chambres à air", ""), + 3719 : ("Glaçon en cornet", ""), + 3721 : ("Aux bonnes affaires glissantes", ""), + 3722 : ("Boutique d'après-ski", ""), + 3723 : ("Chez Tremblotte - globes des neiges", ""), + 3724 : ("La chronique des rhumeurs", ""), + 3725 : ("Alluge-toi un instant", ""), + 3726 : ("Couvertures solaires", ""), + 3728 : ("Chasse-neige à la pelle", ""), + 3729 : ("", ""), + 3730 : ("Achat et vente de bonhommes de neige", ""), + 3731 : ("Cheminées portatives", ""), + 3732 : ("Au nez gelé", ""), + 3734 : ("Regards glacés - Optométrie", ""), + 3735 : ("Calottes glaciaires", ""), + 3736 : ("Cubes de glace bon marché", ""), + 3737 : ("Restaurant de la pente", ""), + 3738 : ("Chaud devant!", ""), + 3739 : ("Quartier général des Toons", ""), +# titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : (lToonHQ, ""), + 3806 : ("Croisières Tartiflette", ""), + 3807 : ("Nuages d'occasion", ""), + 3808 : ("Gîte Gilet", ""), + 3809 : ("Glaces de découverte", ""), + 3810 : ("Pelisses Municipales", ""), + 3811 : ("L'Ange Neige", ""), + 3812 : ("Chaussons pour chatons", ""), + 3813 : ("Après-skis biodégradables", ""), + 3814 : ("Pailles à glaçons", ""), + 3815 : ("Chalet Frisquet", ""), + 3816 : ("Au gui tout neuf", ""), + 3817 : ("Club Le Verglas", ""), + 3818 : ("La pelle des cîmes", ""), + 3819 : ("Ramonage et dégivrage", ""), + 3820 : ("Blanchisserie de neige", ""), + 3821 : ("Sports d'hibernation", ""), + 3823 : ("Fondation des Pluies", ""), + 3824 : ("Froids les marrons!", ""), + 3825 : ("Chapeaux tout frais", ""), + 3826 : ("Saperlichaussette!", ""), + 3827 : ("Couronnes de gui", ""), + 3828 : ("Le potager du bonhomme de neige", ""), + 3829 : ("Frigo Déco", ""), + 3830 : ("Voyons Voir, dégivrage de monocles", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "Désolé, tu n'as plus\n le temps." +ClosetNotOwnerMessage = "Ce n'est pas ton placard, mais tu peux essayer les vêtements." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Supprimer" +ClosetAreYouSureMessage = "Tu as supprimé des vêtements. Veux-tu vraiment les supprimer?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Vraiment supprimer %s?" +ClosetShirt = "cette chemise" +ClosetShorts = "ce short" +ClosetSkirt = "cette jupe" +ClosetDeleteShirt = "Supprimer\nchemise" +ClosetDeleteShorts = "Supprimer\nshort" +ClosetDeleteSkirt = "Supprimer\njupe" + +# EstateLoader.py +EstateOwnerLeftMessage = "Désolé, le(la) propriétaire de cette maison est parti(e). Retour au terrain de jeux dans %s secondes" +EstatePopupOK = lOK +EstateTeleportFailed = "Impossible de retourner à la maison. Essaie encore!" +EstateTeleportFailedNotFriends = "Désolé, %s est chez un Toon avec qui tu n'es pas ami(e)." + +# DistributedTarget.py +EstateTargetGameStart = "Le jeu des cibles tooniques a commencé!" +EstateTargetGameInst = "Plus tu tires dans la cible rouge, et plus tu remportes de tooniques." +EstateTargetGameEnd = "le jeu des cibles tooniques est maintenant terminé..." + +# DistributedCannon.py +EstateCannonGameEnd = "La location du jeu de canon est terminée." + +# DistributedHouse.py +AvatarsHouse = "Maison %s\n" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Désolé, ce n'est pas ta tirelire." +DistributedBankNotOwner = "Désolé, ce n'est pas ta tirelire." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Tout vendre" +FishTankValue = "Salut,%(name)s! Tu as %(num)s poisson(s) dans ton seau pour une valeur totale de %(value)s bonbon(s). Veux-tu vendre le tout ?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Tout vendre" +FlowerBasketValue = "%(name)s, tu as %(num)s fleurs dans ton panier d'une valeur totale de %(value)s bonbons. Veux-tu toutes les vendre?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = "de " + name + else: + possesive = "de " + name + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold' : ('A toujours faim', 'A souvent faim', + 'A quelquefois faim', 'A rarement faim',), + 'boredomThreshold' : ("S'ennuie toujours", "S'ennuie souvent", + "S'ennuie quelquefois", "S'ennuie rarement",), + 'angerThreshold' : ('Toujours ronchon', 'Souvent ronchon', + 'Quelquefois ronchon', 'Rarement ronchon',), + 'forgetfulness' : ('Oublie toujours', 'Oublie souvent', + 'Oublie quelquefois', 'Oublie rarement',), + 'excitementThreshold' : ('Très calme', 'Plutôt calme', + 'Plutôt excité', 'Très excité',), + 'sadnessThreshold' : ('Toujours triste', 'Souvent triste', + 'Quelquefois triste', 'Rarement triste',), + 'restlessnessThreshold' : ('Toujours agité', 'Souvent agité', + 'Quelquefois agité', 'Rarement agité',), + 'playfulnessThreshold' : ('Rarement joueur', 'Quelquefois joueur', + 'Souvent joueur', 'Toujours joueur',), + 'lonelinessThreshold' : ('Toujours solitaire', 'Souvent solitaire', + 'Quelquefois solitaire', 'Rarement solitaire',), + 'fatigueThreshold' : ('Toujours fatigué', 'Souvent fatigué', + 'Quelquefois fatigué', 'Rarement fatigué',), + 'confusionThreshold' : ('Toujours perplexe', 'souvent perplexe', + 'Quelquefois perplexe', 'Rarement perplexe',), + 'surpriseThreshold' : ('Toujours surpris', 'souvent surpris', + 'Quelquefois surpris', 'Rarement surpris',), + 'affectionThreshold' : ('Rarement affectueux', 'Quelquefois affectueux', + 'Souvent affectueux', 'Toujours affectueux',), + } + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Appuie sur la touche \"Page précédente\" pour mieux voir." + +FireworksJuly4Beginning = lToonHQ+": Welcome to summer fireworks! Enjoy the show!" +FireworksJuly4Ending = lToonHQ+": Hope you enjoyed the show! Have a great summer!" +FireworksFebruary14Beginning = lToonHQ+": Joyeuse Saint Valentin à tous les amoureux!" +FireworksFebruary14Ending = lToonHQ+": Joyeuse Saint Valentin à tous les amoureux!" +FireworksJuly14Beginning = lToonHQ+": Feux d'artifices du 14 Juillet: Profitez du spectacle!" +FireworksJuly14Ending = lToonHQ+": Nous espérons que vous avez profité du spectacle!" +FireworksOctober31Beginning = lToonHQ+": Bons feux d'artifice!" +FireworksOctober31Ending = lToonHQ+": Nous espérons que vous avez aimé les feux d'artifice!" +FireworksNewYearsEveBeginning = lToonHQ+": Bonne année! Profitez du feu d'artifice!" +FireworksNewYearsEveEnding = lToonHQ+": Nous espérons que vous avez profité du spectacle! Bonne année!" +FireworksBeginning = lToonHQ+": Bons feux d'artifice!" +FireworksEnding = lToonHQ+": Nous espérons que vous avez aimé les feux d'artifice!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "ASTUCE TOON:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Pour vérifier rapidement les progrès de ton défitoon, maintiens enfoncée la touche \"Fin\".", + "Pour vérifier rapidement ta page de gags, maintiens enfoncée la touche \"Première page\".", + "Pour ouvrir ta liste d'contacts, appuie sur la touche \"F7\".", + "Pour ouvrir ou fermer ton journal de bord, appuie sur la touche \"F8\".", + "Pour regarder vers le haut, appuie sur la touche \"Page précédente\"; pour regarder vers le bas, appuie sur la touche \"Page suivante\".", + "Appuie sur la touche \"Contrôle\" pour sauter.", + "Appuie sur la touche \"F9\" pour faire une capture d'écran qui sera enregistrée dans le dossier Toontown de ton ordinateur.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Tu peux échanger des codes d'ami secret avec des personnes que tu connais en dehors de Toontown pour pouvoir chatter avec eux dans Toontown.", + "Tu peux changer ta résolution d'écran, régler le son et d'autres options dans la page d'options du journal de bord.", + "Essaie les vêtements de tes contacts, qui sont dans les placards de leur maison.", + "Tu peux rentrer chez toi grâce au bouton \"Retour à la maison\" sur ta carte.", + "Chaque fois que tu termines un défitoon avec succès, tes rigolpoints sont automatiquement ajoutés.", + "Tu peux voir la collection dans les boutiques de prêt-à-porter même sans ticket d'habillement.", + "Les récompenses de certains défitoons te permettent d'avoir plus de gags et de bonbons.", + "Tu peux avoir jusqu'à 50 contacts sur ta liste d'contacts.", + "La récompense de certains défitoons te permet de te téléporter jusqu'aux terrains de jeux de Toontown par la carte du journal de bord.", + "Récupère tes rigolpoints sur les terrains de jeux en ramassant des trésors tels que des étoiles et des cornets de glace.", + "Si tu as besoin de te soigner rapidement après un combat difficile, va chez toi et ramasse des cornets de glace.", + "Pour changer la visualisation de ton Toon, appuie sur la touche de tabulation.", + "Quelquefois tu peux trouver plusieurs défitoons différents proposés pour la même récompense. Fais ton choix!", + "Trouver des contacts qui font le même défitoon que toi est une manière amusante de progresser dans le jeu.", + "Tu n'as jamais besoin d'enregistrer ta progression dans Toontown. Les serveurs de Toontown enregistrent toutes les informations nécessaires en continu.", + "Tu peux parler en chuchotant à d'autres Toons en cliquant sur eux ou en les sélectionnant dans ta liste d'contacts.", + "Certaines phrases du Chat rapide provoquent une émotion animée sur ton Toon.", + "Si tu te trouves dans une zone où il y a trop de monde, tu peux essayer de changer de district. Va à la page des districts dans le journal de bord et choisis-en un autre.", + "Si tu sauves activement des bâtiments, une étoile de bronze, d'argent ou d'or s'affichera au-dessus de ton Toon.", + "Si tu sauves assez de bâtiments pour avoir une étoile au-dessus de la tête, tu pourras trouver ton nom affiché sur le tableau d'un Quartier Général des Toons.", + "Les bâtiments sauvés sont quelquefois recapturés par les Cogs. La seule façon de conserver ton étoile est d'aller sauver plus de bâtiments.", + "Les noms de tes amis apparaîtront en bleu.", + # Fishing + "Essaie d'avoir toutes les espèces de poisson de Toontown!", + "Chaque mare recèle différentes sortes de poissons. Essaie-les toutes!", + "Lorsque ton seau de pêche est plein, tu peux vendre tes poissons aux vendeurs de l'animalerie sur les terrains de jeux.", + "Tu peux vendre tes poissons au vendeur de l'animalerie, près des mares ou dans les animaleries même.", + "Les cannes à pêche plus solides attrapent de plus gros poissons mais requièrent plus de bonbons.", + "Tu peux acheter des cannes à pêche plus solides dans le catalogue.", + "Les plus gros poissons valent plus de bonbons à l'animalerie.", + "Les poissons plus rares valent plus de bonbons à l'animalerie.", + "Tu peux quelquefois trouver des sacs de bonbons en pêchant.", + "Certains défitoons nécessitent de pêcher des objets dans les mares.", + "Les mares des terrains de jeux ont des poissons différents de ceux des mares des rues.", + "Certains poissons sont vraiment rares. Continue à pêcher jusqu'à ce que tu les aies tous!", + "La mare que tu as chez toi contient des poissons qui ne peuvent pas être trouvés ailleurs.", + "À chaque fois que tu as attrapé 10 espèces, tu gagnes un trophée de pêche!", + "Tu peux voir quels poissons tu as pêchés dans ton journal de bord.", + "Certains trophées de pêche te valent une rigol-augmentation.", + "La pêche est une bonne façon de gagner plus de bonbons.", + # Doudous + "Adopte un Doudou au magasin d'animaux!", + "Les magasins d'animaux ont de nouveaux Doudous à vendre tous les jours.", + "Rends-toi dans les magasins d'animaux tous les jours pour voir quels nouveaux Doudous ils ont.", + "Dans les différents quartiers, il y a des Doudous différents à adopter.", + # Karting + "Fais chauffer ton super moteur et mets un coup de turbo à ta rigo-limite.", + "Rends-toi dans le Circuit Dingo par le tunnel en forme de pneu qui se trouve dans Toontown Central.", + "Gagne des rigolpoints au Circuit Dingo.", + "Le Circuit Dingo a six pistes de course différentes." + ), + + TIP_STREET : ( + "Il existe quatre types de Cogs : les Loibots, les Caissbots, les Vendibots et les Chefbots.", + "Chaque série de gags est associée à différents niveaux de précision et de dégâts.", + "Les gags de tapage affectent tous les Cogs mais réveillent les Cogs leurrés.", + "Battre les Cogs en ordre stratégique peut grandement augmenter tes chances de gagner les batailles.", + "La série de gags \"toonique\" te permet de soigner les autres Toons lors d'une bataille.", + "Les points d'expérience des gags sont doublés pendant une invasion de Cogs!", + "Plusieurs Toons peuvent faire équipe et utiliser la même série de gags lors d'une bataille pour infliger plus de dégâts aux Cogs.", + "Lors des batailles, les gags sont utilisés dans l'ordre affiché sur le menu des gags, de haut en bas.", + "La rangée de points lumineux sur les ascenseurs des bâtiments des Cogs indiquent combien d'étages ils contiennent.", + "Clique sur un Cog pour avoir plus de détails.", + "L'utilisation de gags de haut niveau contre des Cogs de bas niveau ne donne pas de points d'expérience.", + "Un gag qui donnera de l'expérience s'affiche sur fond bleu sur le menu des gags lors de la bataille.", + "L'expérience des gags est multipliée lorsqu'ils sont utilisés à l'intérieur des bâtiments des Cogs. Les étages les plus hauts ont des coefficients de multiplication plus grands.", + "Lorsqu'un Cog est vaincu, chacun des Toons ayant participé est crédité de la victoire sur ce Cog lorsque la bataille est terminée.", + "Chaque rue de Toontown a différents types et niveaux de Cogs.", + "Il n'y a pas de Cogs sur les trottoirs.", + "Dans les rues, tu peux entendre des blagues en t'approchant des portes latérales.", + "Certains défitoons t'entraînent à de nouvelles séries de gags. Tu ne pourras choisir que six des sept séries de gags, alors choisis bien!", + "Les pièges ne sont utiles que si toi ou tes contacts vous mettez d'accord pour utiliser les leurres lors d'une bataille.", + "Les leurres de plus haut niveau sont moins susceptibles de manquer leur cible.", + "Les gags de plus bas niveau ont une précision moindre contre les Cogs de haut niveau.", + "Les Cogs ne peuvent plus attaquer une fois qu'ils ont été leurrés lors d'un combat.", + "Lorsque tes contacts et toi aurez repris un bâtiment aux Cogs, vos portraits seront affichés à l'intérieur du bâtiment en guise de récompense.", + "L'utilisation d'un gag toonique sur un Toon qui a un rigolmètre au maximum ne donne pas d'expérience toonique.", + "Les Cogs sont brièvement assommés lorsqu'ils sont frappés par un gag. Cela augmente la chance que les autres gags du même tour le frappent.", + "Les gags de chute ont de faibles chances d'atteindre leur but, mais la précision est accrue lorsque les Cogs ont auparavant été frappés par un autre gag lors du même tour.", + "Lorsque tu as vaincu suffisamment de Cogs, tu peux utiliser le détecteur de Cogs en cliquant sur les icônes du détecteur sur la page de la galerie des Cogs dans ton journal de bord.", + "Pendant une bataille, les tirets (-) et les X indiquent quel Cog tes équipiers sont en train d'attaquer.", + "Pendant une bataille, un voyant lumineux sur les Cogs indique leur état de santé : vert signifie en bonne santé, rouge au bord de la destruction.", + "Un maximum de quatre Toons peuvent combattre simultanément.", + "Dans la rue, les Cogs prendront plus facilement part à une bataille contre plusieurs Toons qu'à une bataille contre un seul Toon.", + "Les deux Cogs les plus difficiles de chaque type ne se trouvent que dans les bâtiments.", + "Les gags de chute ne fonctionnent jamais contre les Cogs leurrés.", + "Les Cogs ont tendance à attaquer le Toon qui leur a causé le plus de dégâts.", + "Les gags de tapage ne donnent pas de bonus contre les Cogs leurrés.", + "Si tu attends trop longtemps avant d'attaquer un Cog leurré, il se réveille. Les leurres de plus haut niveau durent plus longtemps.", + "Il y a des mares dans toutes les rues de Toontown. Certaines rues ont des espèces de poissons uniques.", + ), + + TIP_MINIGAME : ( + "Après avoir rempli ton pot de bonbons, tous les bonbons que tu gagnes aux jeux du tramway sont automatiquement versés dans ta tirelire.", + "Tu peux utiliser les flèches du clavier au lieu de la souris dans le jeu du tramway \"Imite Minnie\".", + "Dans le jeu du canon, tu peux utiliser les flèches du clavier pour déplacer ton canon et appuyer sur la touche \"Contrôle\" pour tirer.", + "Dans le jeu des anneaux, des points supplémentaires sont attribués quand le groupe entier réussit à nager dans les anneaux.", + "Un jeu parfait d'\"Imite Minnie\" double tes points.", + "Dans le tir à la corde, tu reçois plus de bonbons si tu joues contre un Cog plus fort.", + "La difficulté des jeux du tramway varie selon les quartiers, Toontown centre a les plus faciles et le Pays des rêves de Donald les plus difficiles.", + "Certains jeux du tramway ne peuvent être joués qu'en groupe.", + ), + + TIP_COGHQ : ( + "Tu dois terminer ton déguisement de Cog avant d'entrer dans le bâtiment du Chef.", + "Tu peux sauter sur les gardes du corps des Cogs pour les désactiver temporairement.", + "Tu dois faire entièrement ton déguisement Loibot avant d'aller voir le Juge.", + "Additionne les mérites Cogs par tes victoires sur les Cogs.", + "Tu obtiens plus de mérites avec des Cogs de plus haut niveau.", + "Lorsque tu as additionné assez de mérites Cogs pour gagner une promotion, va voir le vice-président des Vendibots !", + "Tu peux parler comme un Cog lorsque tu portes ton déguisement de Cog.", + "Jusqu'à huit Toons peuvent faire équipe pour combattre le vice-président des Vendibots.", + "Le vice-président des Vendibots est tout en haut du quartier général des Cogs.", + "À l'intérieur des usines des Cogs, monte les escaliers pour arriver jusqu'au contremaître.", + "Chaque fois que tu te bats dans l'usine, tu gagnes une pièce de ton déguisement de Cog.", + "Tu peux visualiser le progrès de ton déguisement de Cog dans ton journal de bord.", + "Tu peux visualiser le progrès de tes mérites sur ta page de déguisements dans ton journal de bord.", + "Assure-toi d'avoir suffisamment de gags et un rigolmètre au maximum avant d'aller voir le vice-président.", + "Si tu as une promotion, ton déguisement de Cog est mis à jour.", + "Tu dois vaincre le contremaître de l'usine pour récupérer une pièce du déguisement de Cog.", + "Récupère des Convocations du Jury en défiant des Loibots.", + "Tu reçois plus de Mérites, d'euros Cog ou de Convocations du Jury en combattant des Cogs de plus haut niveau.", + "Quand tu as récupéré assez de Convocations du Jury pour gagner une promotion, va voir le Juge !", + "Tu dois faire entièrement ton déguisement Loibot avant d'aller voir le Juge.", + "Jusqu'à huit Toons peuvent combattre ensemble le Juge Loibot.", + "Cela paie d'être perplexe : Les Cogs virtuels dans le QG Loibot ne t'accableront pas de Convocations du Jury.", + " Gagne des pièces de costume de Caissbot comme récompense en terminant les défitoons qui sont proposés dans le Pays des Rêves de Donald.", + " Les Caissbots fabriquent et font circuler leur argent, les euros Cogs, à partir de trois Fabriques à Sous - Pièce, Euro et Lingot.", + " Attends que le directeur financier soit étourdi avant de lui lancer un coffre dessus, ou il pourrait l'utiliser comme casque. Frapper le casque avec un autre coffre est la seule manière de le faire tomber.", + " Gagne des pièces de costume de Loibot comme récompense en terminant les défitoons pour le professeur Flocon.", + " Ca paie de résoudre les problèmes : les Cogs virtuels du QG Loibot ne vont pas te récompenser avec des notices du jury.", + ), + TIP_ESTATE : ( + # Doodles + "Les Doudous peuvent comprendre certaines expressions de Chat rapide. Essaie-les!", + "Utilise le menu \"Animaux familiers\" du Chat rapide pour demander à ton Doudou de faire des tours.", + "Tu peux apprendre des tours aux Doudous avec les leçons du catalogue vachement branché de Clarabelle.", + "Récompense ton Doudou quand il fait des tours.", + "Si tu te rends chez un ami, ton Doudou viendra aussi.", + "Donne un bonbon à ton Doudou quand il a faim.", + "Clique sur un Doudou pour afficher un menu grâce auquel tu pourras le nourrir, le cajoler et l'appeler.", + "Les Doudous aiment la compagnie. Invite tes contacts à venir jouer!", + "Chaque Doudou a une personnalité unique.", + "Tu peux rapporter ton Doudou et en adopter un nouveau à l'animalerie.", + "Quand un Doudou fait un tour, les Toons qui sont aux alentours sont soignés.", + "Les Doudous font leurs tours de mieux en mieux avec de l'entraînement. Un peu de persévérance!", + "Les tours plus avancés des Doudous soignent plus vite les Toons.", + "Les Doudous expérimentés peuvent faire plus de tours avant de se fatiguer.", + "Tu peux voir une liste des Doudous qui sont à proximité dans ta liste d'contacts.", + # Furniture / Cattlelog + "Achète des fournitures dans le catalogue de Clarabelle pour décorer ta maison.", + "La tirelire de ta maison contient des bonbons supplémentaires.", + "Le placard de ta maison contient des vêtements supplémentaires.", + "Rends-toi dans la maison de ton ami et essaie ses vêtements.", + "Achète de meilleures cannes à pêche dans le catalogue de Clarabelle.", + "Achète de plus grandes tirelires dans le catalogue de Clarabelle.", + "Appelle Clarabelle avec le téléphone qui est dans ta maison.", + "Clarabelle vend un placard plus grand qui contient plus de vêtements.", + "Fais de la place dans ton placard avant d'utiliser un ticket d'habillement.", + "Clarabelle vend tout ce dont tu as besoin pour décorer ta maison.", + "Vérifie ta boîte aux lettres pour trouver ta livraison après avoir commandé chez Clarabelle.", + "Les vêtements du catalogue de Clarabelle sont livrés dans l'heure.", + "Le papier peint et le revêtement de sol du catalogue de Clarabelle sont livrés dans l'heure.", + "Les meubles du catalogue de Clarabelle sont livrés un jour plus tard.", + "Stocke plus de meubles dans ton grenier.", + "Tu seras averti(e) par Clarabelle quand un nouveau catalogue sera prêt.", + "Tu seras averti(e) par Clarabelle quand un nouveau catalogue sera prêt.", + "Les nouveaux catalogues sont livrés chaque semaine.", + "Cherche les articles de vacances en édition limitée dans le catalogue.", + "Mets les meubles dont tu ne veux plus à la poubelle.", + # Fish + "Certains poissons, comme le hareng saur, sont plus communs dans les propriétés des Toons.", + # Misc + "Tu peux inviter tes contacts sur ta propriété en utilisant le Chat rapide.", + "Est-ce que tu savais que la couleur de ta maison est assortie à celle de ton panneau Choisis un Toon ?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Achète un Roadster, un Utilitoon ou une Berline au Centre Auto Dingo.", + "Customise ton kart avec des autocollants, des baguettes et plein d'autres déco au Centre Auto Dingo.", + "Gagne des tickets en faisant la course sur le Circuit Dingo.", + "Les tickets sont la seule monnaie acceptée par le Centre Auto Dingo.", + " Tu dois déposer des tickets pour pouvoir faire la course.", + "Une page spéciale de ton journal de bord te permet de customiser ton kart.", + "Une page spéciale de ton journal de bord te permet de consulter tes scores sur chaque piste.", + "Une page spéciale de ton journal de bord te permet d'afficher tes trophées.", + "Le Colisée Tortillé est la piste la plus facile du Circuit Dingo.", + " Les Landes Légères est la piste qui a le plus de collines et de bosses du Circuit Dingo.", + "Le Boulevard du Blizzard est la piste la plus excitante du Circuit Dingo.", + ), + TIP_GOLF: ( + # Golfing specific + "Appuie sur la touche de tabulation pour obtenir une vue de dessus du terrain de golf.", + "Appuie sur la flèche de déplacement vers le haut pour t'orienter vers le trou.", + "Faire un swing avec un club, c'est comme un peu comme lancer une tarte.", + ), + } + +FishGenusNames = { + 0 : "Baudruche", + 2 : "Poisson-chat", + 4 : "Poisson-clown", + 6 : "Poisson surgelé", + 8 : "Étoile de mer", + 10 : "Hareng saur", + 12 : "Poisson chien", + 14 : "Anguille douce", + 16 : "Requin nourrice", + 18 : "Crabe-roi", + 20 : "Poisson-lune", + 22 : "Hippocampe", + 24 : "Requin d'eau douce", + 26 : "Bar à coudas", + 28 : "Truite coupe-gorge", + 30 : "Thon tonléon", + 32 : "Méduse médusée", + 34 : "Raie tissante", + } + +FishSpeciesNames = { + 0 : ( "Poisson baudruche", + "Baudruche à air chaud", + "Baudruche météo", + "Baudruche à eau", + "Baudruche rouge", + ), + 2 : ( "Poisson-chat", + "Poisson-chat siamois", + "Poisson-chat piteau", + "Poisson-chat de gouttière", + "Poisson matou", + ), + 4 : ( "Poisson-clown", + "Poisson-clown triste", + "Poisson-pitre", + "Poisson-cirque", + ), + 6 : ( "Poisson surgelé", + ), + 8 : ( "Étoile de mer", + "Étoile de mer lu", + "Étoile de mer sédès", + "Étoile de mer credi", + "Étoile de mer ciatous", + ), + 10 : ( "Hareng saur", + ), + 12 : ( "Poisson chien", + "Poisson-chien de traîneau", + "Poisson-chien-chien", + "Poisson dalmatien", + "Poisson chiot", + ), + 14 : ( "Anguille douce", + "Anguille rette électrique", + ), + 16 : ( "Requin nourrice", + "Requin nourrice tique", + "Requin nourrice tourne", + ), + 18 : ( "Crabe-roi", + "Crabe roi d'Alaska", + "Vieux crabe roi", + ), + 20 : ( "Poisson-lune", + "Poisson pleine lune", + "Poisson demi-lune", + "Poisson nouvelle lune", + "Poisson croissant de lune", + "Poisson équinoxe", + ), + 22 : ( "Hippocampe", + "Hippocampe oscillant", + "Hippocampe percheron", + "Hippocampe oriental", + ), + 24 : ( "Requin d'eau douce", + "Requin de baignoire", + "Requin de piscine", + "Requin olympique", + ), + 26 : ( "Bar tabba", + "Bar amine", + "Bar ratin", + "Bar ricade", + "Bar sovie", + "Bar racé", + "Bar cadaire", + "Bar bouillé", + ), + 28 : ( "Truite coupe-gorge", + "Truite capitaine", + "Truite scorbut", + ), + 30 : ( "Thon tonléon", + "Thon-clave", + "Thon-suret", + "Thon bola", + "Thon durasé", + ), + 32 : ( "Méduse médusée", + "Poisson-cacahuète", + "Poisson pané", + "Poisson fraise", + "Poisson raisin", + ), + 34 : ( "Raie tissante", + ), + } + +CogPartNames = ( + "Cuisse gauche", "Tibia gauche", "Pied gauche", + "Cuisse droite", "Tibia droit", "Pied droit", + "Épaule gauche", "Épaule droite", "Poitrine", "Compteur de santé", "Bassin", + "Bras gauche", "Avant-bras gauche", "Main gauche", + "Bras droit", "Avant-bras droit", "Main droite", + ) + +CogPartNamesSimple = ( + "Haut du torse", + ) + +FishFirstNames = ( + "", + "Angéline", + "Arctique", + "Bébé", + "Bermuda", + "Grand", + "Fontaine", + "Bubule", + "Buster", + "Candy", + "Capitaine", + "Ciboulette", + "Choupette", + "Corail", + "Docteur", + "Toussale", + "Empereur", + "Mâchefer", + "Gros", + "Filou", + "Palmyre", + "Polochon", + "Totoche", + "Doudou", + "Jack", + "Roi", + "P'tit", + "Marin", + "Mamzelle", + "Monsieur", + "Pomme", + "Petit-Doigt", + "Prince", + "Princesse", + "Professeur", + "Bouboule", + "Reine", + "Mirage", + "Ray", + "Rosie", + "Robert", + "Poivre", + "Nicole", + "Sandy", + "Écaille", + "Dent d'or", + "Sire", + "Sacha", + "Pantoufle", + "Chipeur", + "Mini", + "Sébastien", + "P'tit-Pois", + "Étoile", + "Sucre d'orge", + "Super", + "Tigre", + "Microbe", + "Moustache", + ) + +FishLastPrefixNames = ( + "", + "Laplage", + "Noir", + "Bleu", + "Marcassin", + "Lavache", + "Minou", + "Aufond", + "Double", + "Est", + "Chichi", + "Écaille", + "Plat", + "Frais", + "Géant", + "Dorpur", + "Doré", + "Gris", + "Vert", + "Goinfre", + "Jacasse", + "Gelée", + "Dame", + "Cuir", + "Citron", + "Long", + "Nord", + "Océan", + "Octo", + "Huile", + "Perle", + "Mousse", + "Rouge", + "Ruban", + "Fleuve", + "Roc", + "Rubis", + "Barre", + "Sel", + "Mer", + "Argent", + "Tuba", + "Semelle", + "Sud", + "Hérisse", + "Surf", + "Sabre", + "Tigre", + "Triple", + "Tropical", + "Thon", + "Coucou", + "Faible", + "Ouest", + "Blanc", + "Jaune", + ) + +FishLastSuffixNames = ( + "", + "balle", + "basse", + "ventre", + "punaise", + "vole", + "beurre", + "dents", + "botte", + "crabe", + "ronchon", + "tambour", + "palme", + "poisson", + "nette", + "nageoire", + "flou", + "grogne", + "tête", + "veste", + "saut", + "sardine", + "lune", + "bouche", + "mulet", + "cou", + "nez", + "perche", + "rauque", + "coureur", + "voile", + "requin", + "coquille", + "soie", + "bave", + "vif", + "pue", + "queue", + "crapaud", + "truite", + "eau", + ) + + +CogPartNames = ( + "Cuisse gauche", "Tibia gauche", "Pied gauche", + "Cuisse droite", "Tibia droit", "Pied droit", + "Épaule gauche", "Épaule droite", "Poitrine", "Compteur de santé", "Bassin", + "Bras gauche", "Avant-bras gauche", "Main gauche", + "Bras droit", "Avant-bras droit", "Main droite", + ) + +CogPartNamesSimple = ( + "Haut du torse", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrée principale" +SellbotLegFactorySpecLobby = "Accueil" +SellbotLegFactorySpecLobbyHallway = "Couloir de l'accueil" +SellbotLegFactorySpecGearRoom = "Salle des pignons" +SellbotLegFactorySpecBoilerRoom = "Chaufferie" +SellbotLegFactorySpecEastCatwalk = "Passerelle est" +SellbotLegFactorySpecPaintMixer = "Mélangeur à peinture" +SellbotLegFactorySpecPaintMixerStorageRoom = "Réserve du mélangeur à peinture" +SellbotLegFactorySpecWestSiloCatwalk = "Passerelle du silo ouest" +SellbotLegFactorySpecPipeRoom = "Salle des tuyaux" +SellbotLegFactorySpecDuctRoom = "Salle des canalisations" +SellbotLegFactorySpecSideEntrance = "Entrée latérale" +SellbotLegFactorySpecStomperAlley = "Allée des pas perdus" +SellbotLegFactorySpecLavaRoomFoyer = "Accueil des sanitaires" +SellbotLegFactorySpecLavaRoom = "Sanitaires" +SellbotLegFactorySpecLavaStorageRoom = "Réserve des sanitaires" +SellbotLegFactorySpecWestCatwalk = "Passerelle ouest" +SellbotLegFactorySpecOilRoom = "Salle du pétrole" +SellbotLegFactorySpecLookout = "Poste d'observation" +SellbotLegFactorySpecWarehouse = "Réserve" +SellbotLegFactorySpecOilRoomHallway = "Entrée de la salle du pétrole" +SellbotLegFactorySpecEastSiloControlRoom = "Salle de contrôle du silo est" +SellbotLegFactorySpecWestSiloControlRoom = "Salle de contrôle du silo ouest" +SellbotLegFactorySpecCenterSiloControlRoom = "Salle de contrôle du silo central" +SellbotLegFactorySpecEastSilo = "Silo est" +SellbotLegFactorySpecWestSilo = "Silo ouest" +SellbotLegFactorySpecCenterSilo = "Silo central" +SellbotLegFactorySpecEastSiloCatwalk = "Passerelle du silo est" +SellbotLegFactorySpecWestElevatorShaft = "Puits de l'ascenseur ouest" +SellbotLegFactorySpecEastElevatorShaft = "Puits de l'ascenseur est" + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "VICTOIRE!!" +FishBingoJackpot = "JACKPOT!" +FishBingoGameOver = "JEU TERMINÉ" +FishBingoIntermission = "La pause\nse termine dans :" +FishBingoNextGame = "Le prochain jeu\ncommence dans :" +FishBingoTypeNormal = "Classique" +FishBingoTypeCorners = "Quatre coins" +FishBingoTypeDiagonal = "Diagonales" +FishBingoTypeThreeway = "Trois voies" +FishBingoTypeBlockout = "GRILLE ENTIERE!" +FishBingoStart = "C'est l'heure du loto des poissons! Rends-toi sur n'importe quel ponton libre pour jouer!" +FishBingoOngoing = "" +FishBingoEnd = "J'espère que le loto des poissons t'a plu." +FishBingoHelpMain = "Bienvenue au loto des poissons de Toontown! Tout le monde à la mare s'active pour remplir la grille avant la fin du temps imparti." +FishBingoHelpFlash = "Quand tu attrapes un poisson, clique sur un des carrés clignotants pour marquer la grille." +FishBingoHelpNormal = "C'est une grille de loto classique. Tu gagnes si tu remplis n'importe quel rangée verticalement, horizontalement ou diagonalement." +FishBingoHelpDiagonals = "Remplis les deux diagonales pour gagner." +FishBingoHelpCorners = "Une grille de coins facile. Remplis les quatre coins pour gagner." +FishBingoHelpThreeway = "Trois voies. Remplis les deux diagonales et la rangée du milieu pour gagner. Ça n'est pas facile!" +FishBingoHelpBlockout = "Grille entière! Remplis la grille entière pour gagner. Tu joues contre toutes les autres mares pour remporter un énorme jackpot!" +FishBingoOfferToSellFish = "Ton seau est plein de poissons. Est-ce que tu voudrais en vendre ?" +FishBingoJackpot = "Gain: %s bonbons!" +FishBingoJackpotWin = "Gain: %s bonbons!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Toonique" +ResistanceToonupItem = "%s Toonique" +ResistanceToonupItemMax = "Max" +ResistanceToonupChat = "Toons du Monde entier, Toonique!" +ResistanceRestockMenu = "À vos gags" +ResistanceRestockItem = "À vos gags %s" +ResistanceRestockItemAll = "Tous" +ResistanceRestockChat = "Toons du Monde entier, à vos gags!" +ResistanceMoneyMenu = "Bonbons" +ResistanceMoneyItem = "%s bonbons" +ResistanceMoneyChat = "Toons du Monde entier, dépensez avec sagesse!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": Bienvenue dans la résistance!" +ResistanceEmote2 = NPCToonNames[9228] + ": Utilise ton nouvel émoticone pour t'identifier auprès des autres membres." +ResistanceEmote3 = NPCToonNames[9228] + ": Bonne chance!" + +# Kart racing +KartUIExit = "Laisser le kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Acheter un kart" +KartShop_BuyAccessories = "Acheter des accessoires" +KartShop_BuyAccessory = "Acheter un accessoire" +KartShop_Cost = "Prix: %d tickets" +KartShop_ConfirmBuy = "Acheter cette %s pour %d tickets?" +KartShop_NoAvailableAcc = "Aucun accessoire de ce type n'est disponible." +KartShop_FullTrunk = "Ton coffre est plein." +KartShop_ConfirmReturnKart = "Tu veux vraiment rendre ton kart actuel?" +KartShop_ConfirmBoughtTitle = "Bravo!" +KartShop_NotEnoughTickets = "Pas assez de tickets!" + +KartView_Rotate = "Faire tourner" +KartView_Right = "Droite" +KartView_Left = "Gauche" + +# starting block +StartingBlock_NotEnoughTickets = "Tu n'as pas assez de tickets! Fais plutôt une course d'entraînement." +StartingBlock_NoBoard = "Les inscriptions sont terminées pour cette course. Tu dois attendre que la prochaine course commence." +StartingBlock_NoKart = "Il te faut d'abord un kart! Va donc voir un des vendeurs du magasin de kart." +StartingBlock_Occupied = "Ce plot de départ est actuellement occupé! Essaie un autre endroit." +StartingBlock_TrackClosed = "Nous sommes désolés, cette piste est fermée pour cause de réfection." +StartingBlock_EnterPractice = "Tu veux participer à une course d'entraînement ?" +StartingBlock_EnterNonPractice = "Veux-tu participer à une course %s pour %s tickets?" +StartingBlock_EnterShowPad = "Veux-tu garer ta voiture ici?" +StartingBlock_KickSoloRacer = "Les combats de Toons et les Grands Prix requièrent deux pilotes ou plus." +StartingBlock_Loading = "Allons à la course!" + +#stuff for leader boards +LeaderBoard_Time = "Temps" +LeaderBoard_Name = "Nom du pilote" +LeaderBoard_Daily = "Scores quotidiens" +LeaderBoard_Weekly = "Scores hebdomadaires" +LeaderBoard_AllTime = "Meilleurs scores de tous les temps" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Entraînement", + "Combat de Toons", + "Tournoi", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "Partez!" +KartRace_Reverse = " Inversé" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Stade Cinglette", + RaceGlobals.RT_Speedway_1_rev : "Stade Cinglette" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Piste Champêtre", + RaceGlobals.RT_Rural_1_rev : "Piste Champêtre" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "Circuit de la Ville", + RaceGlobals.RT_Urban_1_rev : "Circuit de la Ville" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Colisée Tortillé", + RaceGlobals.RT_Speedway_2_rev : "Colisée Tortillé" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Landes Légères", + RaceGlobals.RT_Rural_2_rev : "Landes Légères" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Bld du Blizzard", + RaceGlobals.RT_Urban_2_rev : "Bld du Blizzard" + KartRace_Reverse, + } + +KartRace_Unraced = "S/O" + +KartDNA_KartNames = { + 0:"Berline", + 1:"Roadster", + 2:"Utilitoon" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Filtre à air", + 1001: "Carburateur quadruple", + 1002: "Aigle en vol", + 1003: "Cornes de bœuf", + 1004: "Six cylindres en ligne", + 1005: "Petit déflecteur", + 1006: "Arbre à cames simple", + 1007: "Déflecteur moyen", + 1008: "Carburateur monocorps", + 1009: "Klaxon à soufflet", + 1010: "Déflecteur rayé", + #spoiler accessory names + 2000: "Aileron espace", + 2001: "Roue de secours avec rustines", + 2002: "Arceau de sécurité", + 2003: "Ailette simple", + 2004: "Double aileron", + 2005: "Aileron simple", + 2006: "Roue de secours standard", + 2007: "Ailette simple", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "Klaxon 2 tons", + 3001: "Pare-chocs de Freddie", + 3002: "Bas de caisse Cobalt", + 3003: "Pots latéraux Cobra", + 3004: "Pots latéraux droits", + 3005: "Pare-chocs dentelés", + 3006: "Bas de caisse carbone", + 3007: "Bas de caisse bois", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "Pots arrières courbés", + 4001: "Pare-chocs Splash", + 4002: "Double échappement", + 4003: "Doubles ailettes simples", + 4004: "Bavettes simples", + 4005: "Échappement de quad", + 4006: "Doubles élargisseurs de caisse", + 4007: "Méga échappement", + 4008: "Doubles ailettes rayées", + 4009: "Doubles ailettes bulle", + 4010: "Bavettes rayées", + 4011: "Bavettes Mickey", + 4012: "Bavettes dentelées", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "Turbo", + 5001: "Lune", + 5002: "Roue avec rustine", + 5003: "Trois rayons", + 5004: "Couvercle peinture", + 5005: "Cœur", + 5006: "Mickey", + 5007: "Cinq boulons", + 5008: "Daisy", + 5009: "Basket-ball", + 5010: "Hypno", + 5011: "Tribal", + 5012: "Pierre précieuse", + 5013: "Cinq rayons", + 5014: "Pacotille", + #decal accessory names + 6000: "Numéro cinq", + 6001: "Éclaboussure", + 6002: "Damiers", + 6003: "Flammes", + 6004: "Cœurs", + 6005: "Bulles", + 6006: "Tigre", + 6007: "Fleurs", + 6008: "Éclair", + 6009: "Ange", + #paint accessory names + 7000: "Vertanis", + 7001: "Pêche", + 7002: "Rouge vif", + 7003: "Rouge", + 7004: "Bordeaux", + 7005: "Sienne", + 7006: "Marron", + 7007: "Havane", + 7008: "Corail", + 7009: "Orange", + 7010: "Jaune", + 7011: "Crème", + 7012: "Citrine", + 7013: "Citron vert", + 7014: "Vert marin", + 7015: "Vert", + 7016: "Bleu clair", + 7017: "Bleuaqua", + 7018: "Bleu", + 7019: "Bleupervenche", + 7020: "Bleu roi", + 7021: "Bleu ardoise", + 7022: "Violet", + 7023: "Lavande", + 7024: "Rose", + 7025: "Gris", + 7026: "Noir", + } + +RaceHoodSpeedway = "Circuit" +RaceHoodRural = "Champêtre" +RaceHoodUrban = "Ville" +RaceTypeCircuit = "Tournoi" +RaceQualified = "Tu es qualifié(e)" +RaceSwept = "Tu les as balayés" +RaceWon = "Tu as gagné" +Race = "parcours" +Races = "parcours" +Total = "total" +GrandTouring = "Grand Tour" + +def getTrackGenreString(genreId): + genreStrings = [ "Circuit", + "Pays", + "Ville" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "panneau ville1_tunnel" + elif trackId == 1 and padId == 0: + return "panneau campagne_tunnel1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "panneau %s_%stunnel" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodSpeedway, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodRural, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceHoodUrban, + RaceQualified + " pour " + str(RaceGlobals.TotalQualifiedRaces) + " " + Races + " au " + Total, + # won race trophies + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodSpeedway, + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodRural, + RaceWon + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceHoodUrban, + RaceWon + " " + str(RaceGlobals.TotalWonRaces) + " " + Races + " au " + Total, + #qualified circuit races + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceQualified + " pour " + str(RaceGlobals.WonCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # won circuit race trophies + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceWon + " " + str(RaceGlobals.WonCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # swept circuit races + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[0]) + " " + Race + " " + RaceTypeCircuit, + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[1]) + " " + Races + " " + RaceTypeCircuit, + RaceSwept + " dans " + str(RaceGlobals.SweptCircuitRaces[2]) + " " + Races + " " + RaceTypeCircuit, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + str(RaceGlobals.TrophiesPerCup * 2) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + str(RaceGlobals.TrophiesPerCup * 3) + " Trophées gagnés aux courses de kart! Rigol-augmentation!", + ] + +KartRace_TitleInfo = "Prépare-toi pour la course" +KartRace_SSInfo = "Bienvenue au stade Cinglette!\nPied au plancher, et on s'accroche. Ça va secouer!\n" +KartRace_CoCoInfo = "Bienvenue au Colisée Tortillé ! Utilise l'inclinaison des virages pour maintenir ta vitesse !\n" +KartRace_RRInfo = "Bienvenue sur la piste Champêtre!\nAttention aux animaux, reste bien sur la piste!\n" +KartRace_AAInfo = "Bienvenue aux Landes légères ! Tiens bien ton chapeau ! Ça a l'air d'être plein de bosses par ici...\n" +KartRace_CCInfo = "Bienvenue sur le circuit de la Ville!\nAttention aux piétons quand tu fonces à travers la ville!\n" +KartRace_BBInfo = "Bienvenue au Boulevard du Blizzard ! Attention à ta vitesse. Il se peut qu'il y ait de la glace par là-bas.\n" +KartRace_GeneralInfo = "Utilise la touche Contrôle pour lancer les gags que tu ramasses sur la piste, et les flèches pour diriger ton kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'quotidien', + RaceGlobals.Weekly : 'hebdomadaire', + RaceGlobals.AllTime : 'de tous les temps', + } + +KartRace_FirstSuffix = 'er' +KartRace_SecondSuffix = 'ème' +KartRace_ThirdSuffix = ' rd' +KartRace_FourthSuffix = ' th' +KartRace_WrongWay = 'Sens\ninterdit!' +KartRace_LapText = "Tour %s" +KartRace_FinalLapText = "Dernier tour!" +KartRace_Exit = "Sortir de la course" +KartRace_NextRace = "Course suivante" +KartRace_Leave = "Quitter la course" +KartRace_Qualified = "Qualifié(e)!" +KartRace_Record = "Record!" +KartRace_RecordString = 'Tu as établi un nouveau %s record pour %s! Ton bonus est de %s tickets.' +KartRace_Tickets = " Tickets" +KartRace_Exclamations = "!" +KartRace_Deposit = "Dépôt" +KartRace_Winnings = "Gains" +KartRace_Bonus = "Bonus" +KartRace_RaceTotal = "Total course" +KartRace_CircuitTotal = "Circuit entier" +KartRace_Trophies = "Trophées" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s" + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "Qualifié:\n" +KartRace_RaceTimeout = "Tu as fini après la fin de la course. Tes tickets ont été remboursés. Essaie encore!" +KartRace_RaceTimeoutNoRefund = "Tu as mis trop de temps à finir la course. Tes tickets n'ont pas été remboursés parce que le Grand Prix a déjà commencé. Essaie à nouveau !" +KartRace_RacerTooSlow = "Tu as mis trop de temps à finir la course. Tes tickets ne te sont pas remboursés. Fais une autre course !" +KartRace_PhotoFinish = "Photo à l'arrivée" +KartRace_CircuitPoints = "Score" + +CircuitRaceStart = "Le Grand Prix Toontown au Circuit Dingo va commencer ! Pour gagner la compétition, remporte le maximum de points en trois courses consécutives !" +CircuitRaceOngoing = "Bienvenue ! Le Grand Prix de Toontown bat son plein." +CircuitRaceEnd = "Le Grand Prix Toontown est terminé pour aujourd'hui. Rendez-vous lundi prochain pour une nouvelle édition." + +# Trick-or-Treat holiday +TrickOrTreatMsg = "Tu as déjà\ntrouvé cette friandise." + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Bon, on a quoi au registre aujourd'hui ?" +LawbotBossTempIntro1 = "Ha, on a le procès d'un Toon !" +LawbotBossTempIntro2 = "L'accusation a de bonnes cartes." +LawbotBossTempIntro3 = "Et voilà les avocats commis d'office." +LawbotBossTempIntro4 = "Attendez une minute... Vous êtes des Toons !" +LawbotBossTempJury1 = "La sélection du jury va maintenant commencer." +LawbotBossHowToGetEvidence = "Touche la barre des témoins pour obtenir des preuves." +LawbotBossTrialChat1 = "La séance est ouverte." +LawbotBossHowToThrowPies = "Appuie sur la touche « Inser » pour envoyer les preuves\n sur les avocats ou dans la balance !" +LawbotBossNeedMoreEvidence = "Il te faut plus de preuves !" +LawbotBossDefenseWins1 = "Ce n'est pas possible ! La défense a gagné ?" +LawbotBossDefenseWins2 = "Non. Je déclare le procès nul ! Un nouveau procès va être programmé." +LawbotBossDefenseWins3 = "Hmmmpfff. Je serai dans mon cabinet !" +LawbotBossProsecutionWins = "Je suis en faveur du plaignant" +LawbotBossReward = "Je décerne une promotion et le pouvoir de convoquer des Cogs" +LawbotBossLeaveCannon = "Laisse le canon" +LawbotBossPassExam = "Alors comme ça, tu as réussi le concours du barreau." +LawbotBossTaunts = [ + "%s, je te trouve coupable d'outrage à la cour !", + "Objection accordée !", + "Rayez ça du procès-verbal.", + "Ton appel a été rejeté. Je te condamne à la tristesse !", + "Silence dans l'audience !", + ] +LawbotBossAreaAttackTaunt = "Vous êtes tous coupables d'outrage à la cour!" +WitnessToonName = "Bumpy Bourdonnette" +WitnessToonPrepareBattleTwo = "Oh non! Il n'y a que des Cogs dans le jury!\aVite, utilise les canons et tire sur des jurés Toons sur le banc des jurés.\aNous avons besoin de %d pour équilibrer la balance." +WitnessToonNoJuror = "Oh là là, aucun juré Toon. Ça va être un procès difficile." +WitnessToonOneJuror = "Super! Il y a 1Toon parmi les jurés!" +WitnessToonSomeJurors = "Super! Il y a %d Toons parmi les jurés!" +WitnessToonAllJurors = "Fantastique! Tous les jurés sont des Toons!" +WitnessToonPrepareBattleThree = "Dépêche-toi de toucher la barre des témoins pour obtenir des preuves.\aAppuie sur la touche «Inser» pour envoyer les preuves sur les avocats ou sur la défense." +WitnessToonCongratulations = "Tu as réussi! Merci pour cette défense spectaculaire!\aPrends ces papiers que le Juge a oubliés.\aAvec ça, tu pourras convoquer des Cogs à partir de ta page de Galerie de Cogs." +WitnessToonLastPromotion = "\aWow, tu as atteint le niveau %s sur ton costume de Cog!\aC'est la plus haute promotion que peuvent atteindre les Cogs.\aTu ne peux plus monter ton costume de Cog en grade, mais tu peux évidemment continuer à travailler pour la résistance!" +WitnessToonHPBoost = "\aTu as fait beaucoup de travail pour la résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations!" +WitnessToonMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant!\aLe Conseil des Toons te remercie d'être revenu défendre encore plus de Toons!" +WitnessToonBonus = "Merveilleux! Tous les avocats sont étourdis. Le poids de tes preuves est %s fois plus lourd pendant %s secondes." + +WitnessToonJuryWeightBonusSingular = { + 6: "C'est un cas difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 7: "C'est un cas très difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 8: "C'est le cas le plus difficile. Tu as %d juré Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", +} + +WitnessToonJuryWeightBonusPlural = { + 6: "C'est un cas difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 7: "C'est un cas très difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", + 8: "C'est le cas le plus difficile. Tu as %d jurés Toon. Par conséquent, tes preuves ont un bonus de poids de %d.", +} + +# Cog Summons stuff +IssueSummons = "Convocation" +SummonDlgTitle = "Convoquer un Cog" +SummonDlgButton1 = "Convoquer un Cog" +SummonDlgButton2 = "Assigner un bâtiment Cog" +SummonDlgButton3 = "Convoquer une invasion de Cogs" +SummonDlgSingleConf = "Veux-tu convoquer un %s?" +SummonDlgBuildingConf = "Veux-tu convoquer un %s à se rendre dans un bâtiment Toon à proximité?" +SummonDlgInvasionConf = "Veux-tu convoquer une invasion de %s?" +SummonDlgNumLeft = "Il t'en reste %s." +SummonDlgDelivering = "Envoi des convocations..." +SummonDlgSingleSuccess = "Tu as réussi à convoquer le Cog." +SummonDlgSingleBadLoc = "Malheureusement, les Cogs ne sont pas autorisés à entrer ici. Essaie un autre endroit." +SummonDlgBldgSuccess = "Tu as réussi à convoquer les Cogs. %s a accepté de les laisser prendre provisoirement le contrôle de %s!" +SummonDlgBldgSuccess2 = "Tu as réussi à convoquer les Cogs. Un commerçant a accepté de les laisser prendre provisoirement le contrôle de son magasin!" +SummonDlgBldgBadLoc = "Malheureusement, il n'y a aucun bâtiment Toon à proximité que les Cogs peuvent prendre." +SummonDlgInvasionSuccess = "Tu as réussi à convoquer les Cogs. C'est une invasion!" +SummonDlgInvasionBusy = "On ne trouve pas de %s pour l'instant. Essaie à nouveau quand l'invasion de Cogs sera terminée." +SummonDlgInvasionFail = "Désolé. L'invasion de Cogs a échoué." +SummonDlgShopkeeper = "Le commerçant" + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": Bienvenue à la Place Polaire!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Essaie pour voir si la taille te va." +PolarPlaceEffect3 = NPCToonNames[3306] + ": Ton nouveau look ne marchera que" + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "Recherche de crâne!" +LaserGameRoll = "Correspondance" +LaserGameAvoid = "Évite les crânes" +LaserGameDrag = "Mets en trois de la même\ncouleur sur une rangée" +LaserGameDefault = "Jeu inconnu" + +# Pinball text +#PinballHiScore = "High Score: %d %s\n" +#PinballYourBestScore = "Your Best Score: %d\n" +#PinballScore = "Score: %d x %d : %d" +PinballHiScore = "Score élevé: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Ton meilleur score:\n" +PinballScore = "Score: %d x %d =" +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Arbre à gags à plumes" +GagTreeJugglingBalls = "Arbre à gags à balles de jonglage" +StatuaryFountain = "Fontaine" +StatuaryToonStatue = "Statue de Toon" +StatuaryDonald = "Statue de Donald" +StatuaryMinnie = "Statue de Minnie" +StatuaryMickey1 = "Statue de Mickey" +StatuaryMickey2 = "Fontaine de Mickey" +StatuaryToonStatue = "Statue de Toon" +StatuaryToon = "Toon Statue" +StatuaryToonWave = "Statue Toon Geste" +StatuaryToonVictory = "Statue Toon Victoire" +StatuaryToonCrossedArms = 'Statue Toon Autorité' +StatuaryToonThinking = 'Statue Toon Étreinte' +StatuaryMeltingSnowman = 'Melting Snowman' +StatuaryGardenAccelerator = "Engrais Pousse-Instantanée" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Rouge','Orange','Violet','Bleu','Rose','Jaune','Blanc','Vert'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Pâquerette', + 50: 'Tulipe', + 51: 'Œillet', + 52: 'Lys', + 53: 'Jonquille', + 54: 'Pensée', + 55: 'Pétunia', + 56: 'Rose', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ("Pâquerette d'école", + 'Pâquerette paresseuse', + "Pâquerette d'été", + 'Pâquerette frisquette', + 'Pâquerette houplàlà', + 'Pâquerette guillerette', + 'Pâquerette follette', + 'Pâquerette brumette', + ), + 50: ('Unelipe', + 'Tulipe', + 'Trilipe', + ), + 51: ('Œillet myope', + 'Œillet rapide', + 'Œillet hybride', + 'Œillet louche', + 'Œillet modèle', + ), + 52: ('Mugatine', + 'Lys téria', + 'Lys tigri', + 'Lys poire', + 'Lys pique', + 'Pneu-lys', + 'Lys tère', + 'Lys bis', + ), + 53: ('Jonquirille', + 'Jonquifolle', + 'Jonquirafe', + 'Jonquipasse', + ), + 54: ('Pensée à rien', + 'Chim-pensée', + 'Pensée zy', + 'Pensargarine', + 'Pensée folle' + ), + 55: ('Pétugniagnian', + 'Régitunia', + ), + 56: ("Rose estivale", + 'Rose des blés', + 'Rose colorante', + 'Rose malodorante', + 'Rose distillée', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "Étain", + 1 : "Bronze", + 2 : "Argent", + 3 : "Or", + } +WateringCanNameDict = { + 0 : "Petit", + 1 : "Moyen", + 2 : "Grand", + 3 : "Énorme", + } +GardeningPlant = "Plante" +GardeningWater = "Eau" +GardeningRemove = "Retirer" +GardeningPick = "Cueillir" +GardeningFull = "Full" +GardeningSkill = "Habileté" +GardeningWaterSkill = "Habileté à arroser" +GardeningShovelSkill = "Habileté avec la pelle" +GardeningNoSkill = "Pas d'habileté améliorée" +GardeningPlantFlower = "Plante\nFleur" +GardeningPlantTree = "Plante\nArbre" +GardeningPlantItem = "Plante\nArticle" +PlantingGuiOk = "Plante" +PlantingGuiCancel = "Annuler" +PlantingGuiReset = "Tout effacer" +GardeningChooseBeans = "Choisis les bonbons que tu veux planter" +GardeningChooseBeansItem = "Choisis les bonbons que tu veux planter." +GardeningChooseToonStatue = "Choisis le Toon dont tu veux créer la statue." +GardenShovelLevelUp = "Félicitations, tu as gagné une pelle %(shovel)s ! Tu as maîtrisé les fleurs de %(oldbeans)d bonbons ! Pour avancer, tu dois cueillir des fleurs de %(newbeans)d bonbons." +GardenShovelSkillLevelUp = "Félicitations ! Tu as maîtrisé les fleurs de %(oldbeans)d bonbons ! Pour avancer, tu dois cueillir des fleurs de %(newbeans)d bonbons." +GardenShovelSkillMaxed = "Extraordinaire ! Tu as explosé ton habileté avec la pelle !" + +GardenWateringCanLevelUp = "Félicitations, tu as gagné un nouvel arrosoir!" +GardenMiniGameWon = "Félicitations, tu as arrosé la plante!" +ShovelTin = "Pelle d'étain" +ShovelSteel = "Pelle de bronze" +ShovelSilver = "Pelle d'argent" +ShovelGold = "Pelle d'or" +WateringCanSmall = "Petit arrosoir" +WateringCanMedium = "Arrosoir moyen" +WateringCanLarge = "Grand arrosoir" +WateringCanHuge = "Énorme arrosoir" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('rouge', 'vert', 'orange','violet','bleu','rose','jaune', + 'bleu de cyan','argenté') +PlantItWith = " Plante avec %s." +MakeSureWatered = " Prends d'abord soin d`arroser toutes tes plantes." +UseFromSpecialsTab = "Utilise les onglets spéciaux de ta page de jardinage." +UseSpecial = "Utilise l'outil spécial" +UseSpecialBadLocation = 'Tu ne peux utiliser cela que dans ton jardin.' +UseSpecialSuccess = 'Bravo! Les plantes que tu as arrosées viennent de pousser.' +ConfirmWiltedFlower = "Le plant de %(plant)s est fané. Veux-tu vraiment le retirer? Ce plant n'ira pas dans ton panier de fleurs, et ton habileté n'augmentera pas." +ConfirmUnbloomingFlower = "Le plant de %(plant)s ne fleurit pas. Veux-tu vraiment le retirer? Ce plant n'ira pas dans ton panier de fleurs, et ton habileté n'augmentera pas." +ConfirmNoSkillupFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Ce plant ira dans ton panier de fleurs, mais ton habileté n'augmentera PAS." +ConfirmSkillupFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Il ira dans ton panier de fleurs. Ton habileté augmentera aussi." +ConfirmMaxedSkillFlower = "Veux-tu vraiment cueillir le plant de %(plant)s? Il ira dans ton panier de fleurs. Ton habileté n'augmentera PAS car elle est déjà au maximum." +ConfirmBasketFull = "Ton panier de fleurs est plein. Tu dois d'abord vendre des fleurs." +ConfirmRemoveTree = "Veux-tu vraiment retirer le pied de %(tree)s?" +ConfirmWontBeAbleToHarvest = " Si tu retires cet arbre, tu ne pourras pas récolter de gags dans les arbres de plus haut niveau." +ConfirmRemoveStatuary = "Veux-tu vraiment supprimer définitivement le plant de %(plant)s?" +ResultPlantedSomething = "Félicitations ! Tu viens de planter un %s." +ResultPlantedSomethingAn = "Félicitations ! Tu viens de mettre en terre un plant de %s." +ResultPlantedNothing = "Ça n'a pas marché. Essaie une nouvelle combinaison de bonbons." + +GardenGagTree = "Arbre à gags" +GardenUberGag = "Über Gag" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d bonbons %s" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "un bonbon %s" % BeanColorWords[beanTuple[0]] + else: + retval += 'un' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " et un bonbon %s" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Bonbons magiques" +GardenTextMagicBeansB = "Quelques autres bonbons" +GardenSpecialDiscription = "Ce texte doit expliquer comment utiliser un certain outil spécial pour le jardin" +GardenSpecialDiscriptionB = "Ce texte doit expliquer comment utiliser un certain outil spécial pour le jardin, en pleine face !" +GardenTrophyAwarded = "Oh là là! Tu as cueilli %s sur %s fleurs. Ça mérite un trophée et une rigol-augmentation!" +GardenTrophyNameDict = { + 0 : "Brouette", + 1 : "Pelles", + 2 : "Fleur", + 3 : "Arrosoir", + 4 : "Requin", + 5 : "Poisson-scie", + 6 : "Orque", + } +SkillTooLow = "Habileté\ntrop faible" +NoGarden = "Pas de\njardi" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "Les Jeudis du Tramway" +TravelGameInstructions = "Clique vers le haut ou vers le bas pour définir ton nombre de votes. Clique sur le bouton pour voter. Atteins ton objectif secret pour remporter des bonus de bonbons. Gagne plus de votes en obtenant de bons résultats dans les autres jeux." +TravelGameRemainingVotes = "Votes restants :" +TravelGameUse = "Utiliser" +TravelGameVotesWithPeriod = "votes." +TravelGameVotesToGo = "votes restants" +TravelGameVoteToGo = "votes restants" +TravelGameUp = "" +TravelGameDown = "BAS." +TravelGameVoteWithExclamation = "Vote !" +TravelGameWaitingChoices = "Attendre que les autres joueurs votent..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['HAUT', 'BAS'] +TravelGameTotals = 'Totaux' +TravelGameReasonVotesPlural = 'Le tramway se dirige vers le %(dir)s, avec une avance de %(numVotes)d votes.' +TravelGameReasonVotesSingular = 'Le tramway se dirige vers le %(dir)s, avec une avance de %(numVotes)d vote.' +TravelGameReasonPlace = '%(name)s brise le lien. Le tramway se dirige vers le %(dir)s.' +TravelGameReasonRandom = 'Le tramway se dirige de manière aléatoire vers le %(dir)s.' +TravelGameOneToonVote = "%(name)s a utilisé %(numVotes)s votes restants %(dir)s\n" +TravelGameBonusBeans = "%(numBeans)d Bonbons" +TravelGamePlaying = 'Ensuite, le %(game)s Jeu du Tramway.' +TravelGameGotBonus = '%(name)s a obtenu un bonus de %(numBeans)s bonbons !' +TravelGameNoOneGotBonus = "Personne n'a atteint son objectif secret. Chacun remporte 1 bonbon." +TravelGameConvertingVotesToBeans = "" +TravelGameGoingBackToShop ="Il reste un seul joueur. En route pour la boutique à gags de Dingo." + +PairingGameTitle = "Jeu de mémoire Toon" +PairingGameInstructions = "Appuie sur Effacer pour ouvrir une carte. Pour remporter un point, il faut assortir deux cartes. Fais une combinaison avec l'éclat bonus et remporte un point en plus. Remporte des points supplémentaires en effectuant de petits lancers." +PairingGameInstructionsMulti = "Appuie sur Effacer pour ouvrir une carte. Appuie sur Ctrl pour demander à un autre joueur d'ouvrir une carte. Pour remporter un point, il faut assortir deux cartes. Fais une combinaison avec l'éclat bonus et remporte un point en plus. Remporte des points supplémentaires en effectuant de petits lancers." +PairingGamePerfect = 'PARFAIT !' +PairingGameFlips = 'Lancers :' +PairingGamePoints = 'Points :' + +TrolleyHolidayStart = "Les Jeudis du Tramway est sur le point de commencer. Pour jouer, monte à bord de n'importe quel tramway contenant au moins deux Toons." +TrolleyHolidayOngoing = "Bienvenue ! Les Jeudis du Tramway est en cours d'exécution." +TrolleyHolidayEnd = "Les Jeudis du Tramway est terminé pour aujourd'hui. À la semaine prochaine !" + +TrolleyWeekendStart = "Le Weekend du Tramway est sur le point de commencer ! Pour jouer, monte à bord de n'importe quel tramway contenant au moins deux Toons." +TrolleyWeekendEnd = "Le Weekend du Tramway est terminé pour aujourd'hui." + +VineGameTitle = "Jeu des Lianes" +VineGameInstructions = "Atteins la liane la plus à droite à temps. Appuie sur les flèches Haut ou Bas du clavier pour grimper le long de la liane. Appuie sur les flèches Droite ou Gauche pour changer de direction et sauter. Plus tu es en bas de la liane, plus il est facile de sauter. Ramasse les bananes si tu peux, mais évite les chauves-souris et les araignées." + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "Marche dans le Par", + 1: "Trou joyeux", + 2: "Trou ce qu'il faut et plus encore" + } + +GolfHoleNames = { + 0: 'Trou-en-Un', + 1: 'Putt à choux', + 2: 'Cul sec', + 3: 'Ver de green', + 4: 'Liens chauds', + 5: 'Putter Pan', + 6: 'Club de Swing', + 7: 'A P Tee', + 8: 'Planter de Tee', + 9: 'Rock And Roll', + 10: 'Trou de boguey', + 11: 'Amor Tee', + 12: 'Tee Sage', + 13: 'Par Deux Nez', + 14: 'Au Drive In', + 15: 'Cours de Swing', + 16: "Terrain d'entraînement", + 17: 'Second Souffle', + 18: 'Trou-en-Un-2', + 19: 'Putt à choux -2', + 20: 'Cul sec-2', + 21: 'Ver de green-2', + 22: 'Liens chauds-2', + 23: 'Putter Pan-2', + 24: 'Club de Swing-2', + 25: 'A P Tee-2', + 26: 'Planter de Tee-2', + 27: 'Rock And Roll -2', + 28: 'Trou de boguey-2', + 29: 'Amor Tee-2', + 30: 'Tee Sage-2', + 31: 'Par Deux Nez-2', + 32: 'Au Drive In-2', + 33: 'Cours de Swing-2', + 34: "Terrain d'entraînement-2", + 35: 'Second Souffle-2', + } + +GolfHoleInOne = "Trou-en-un" +GolfCondor = "Condor" # four Under Par +GolfAlbatross = "Albatros" # three under par +GolfEagle = "Aigle" # two under par +GolfBirdie = "Birdie" # one under par +GolfPar = "Par" +GolfBogey = "Boguey" # one over par +GolfDoubleBogey = "Double Bougey" # two over par +GolfTripleBogey = "Triple Boguey" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "Parcours terminés" +CoursesUnderPar = "Parcours sous par" +HoleInOneShots = "Trous-en-un" +EagleOrBetterShots = "Aigle ou meilleurs tirs" +BirdieOrBetterShots = "Birdie ou meilleurs tirs" +ParOrBetterShots = "Par ou meilleurs tirs" +MultiPlayerCoursesCompleted = "Parcours multijoueurs terminés" +TwoPlayerWins = "Victoires à deux joueurs" +ThreePlayerWins = "Victoires à trois joueurs" +FourPlayerWins = "Victoires à quatre joueurs" +CourseZeroWins = GolfCourseNames[0] + " Victoires" +CourseOneWins = GolfCourseNames[1] + " Victoires" +CourseTwoWins = GolfCourseNames[2] + " Victoires" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + " Trophées remportés", + str(GolfGlobals.TrophiesPerCup * 2) + " Trophées remportés", + str(GolfGlobals.TrophiesPerCup * 3) + " Trophées remportés", +] + +GolfAvReceivesHoleBest = "%(name)s a établi un nouveau record de parcours au %(hole)s !" +GolfAvReceivesCourseBest = "%(name)s a établi un nouveau record de parcours au %(course)s !" +GolfAvReceivesCup = "%(name)s remporte la %(cup)s coupe ! Rigol-augmentation !" +GolfAvReceivesTrophy = "%(name)s remporte le %(award)s trophée !" +GolfRanking = "Classement : \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "Appuie sur la flèche gauche ou droite pour changer la position du tee.\nAppuie sur Ctrl pour sélectionner." +GolfWarningMustSwing = "Avertissement : tu dois appuyer sur la touche Ctrl lors de ton prochain swing." +GolfAimInstructions = "Appuie sur la flèche gauche ou droite pour viser.\nAppuie sur la touche Ctrl et maintiens-la enfoncée pour faire ton swing." +GolferExited = "%s a quitté le terrain de golf." +GolfPowerReminder = "Maintiens la touche Ctrl enfoncée plus longtemps pour \nEnvoyer la balle plus loin" + + +# GolfScoreBoard.py +GolfPar = "Par" +GolfHole = "Trou" +GolfTotal = "Total" +GolfExitCourse = "Quitter parcours" +GolfUnknownPlayer = "???" + +# GolfPage.py +GolfPageTitle = "Golf" +GolfPageTitleCustomize = "Personnaliseur de golf" +GolfPageTitleRecords = "Meilleurs records personnels" +GolfPageTitleTrophy = "Trophées de golf" +GolfPageCustomizeTab = "Personnaliser" +GolfPageRecordsTab = "Records" +GolfPageTrophyTab = "Trophée" +GolfPageTickets = "Tickets :" +GolfPageConfirmDelete = "Effacer un accessoire ?" +GolfTrophyTextDisplay = "Trophée %(number)s : %(desc)s" +GolfCupTextDisplay = "Coupe %(number)s : %(desc)s" +GolfCurrentHistory = "Actuel %(historyDesc)s : %(num)s" +GolfTieBreakWinner = "%(name)s remporte le jeu décisif aléatoire !" +GolfSeconds = " - %(time))2f secondes" +GolfTimeTieBreakWinner = "%(name)s remporte le jeu décisif de temps de visée total !!!" + + + +RoamingTrialerWeekendStart = "Visiter Toontown va commencer ! Les joueurs libres peuvent à présent se rendre dans n'importe quel quartier !" +RoamingTrialerWeekendOngoing = "Bienvenue dans Visiter Toontown ! Les joueurs libres peuvent à présent se rendre dans n'importe quel quartier !" +RoamingTrialerWeekendEnd = "Visiter Toontown est maintenant terminé." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Bonne nouvelle ! L'expérience exclusive de double gag Test Toon a commencé." +MoreXpHolidayOngoing = "Bienvenue ! L'expérience exclusive de double gag Test Toon est en cours." +MoreXpHolidayEnd = "L'expérience exclusive de double gag Test Toon est terminée. Merci de nous avoir aidé à tester des trucs !" + + +LogoutForced = "Tu as fait une erreur\n et as été automatiquement déconnecté(e).\n Il se peut également que ton compte soit gelé.\n Sors et va faire une balade. C'est amusant." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \na sauté dans la voiturette de golf." +CountryClubBossConfrontedMsg = "%s se bat contre le Président du Club !" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "Tous les adversaires doivent être vaincus." + +# DistributedMolefield.py +MolesLeft = "Taupes restantes : %d" +MolesInstruction = "Écrasement de taupes !\nSaute sur les taupes rouges !" +MolesFinished = "Écrasement de taupe réussi !" +MolesRestarted = "Écrasement de taupe manqué ! Recommence..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "Retirer la balle de Cog !" +BustACogExit = "Quitter pour le moment" +BustACogHowto = "Comment jouer" +BustACogFailure = "Temps expiré !" +BustACogSuccess = "Bien joué !" + +# bossbot golf green games +GolfGreenGameScoreString = "Énigmes restantes : %s" +GolfGreenGamePlayerScore = "Résolu %s" +GolfGreenGameBonusGag = "Tu as gagné %s !" +GolfGreenGameGotHelp = "%s a résolu une énigme !" + +GolfGreenGameDirections = "Lance les balles à l'aide de la souris\n\n\nSi tu parviens à regrouper trois balles de la même couleur, les balles tombent\n\n\nFais disparaître toutes les balles de Cog du tableau" + +# DistributedMaze.py +enterHedgeMaze = "Retrouve vite la sortie du labyrinthe\n pour obtenir un bonus de rigolpoints !" +toonFinishedHedgeMaze = "%s \n a fini en %s position !" +hedgeMazePlaces = ["première","deuxième","troisième","quatrième"] +mazeLabel = "Le jeu du labyrinthe !" + +# Boarding Party +BoardingPartyReadme = "グループを設定する?" +BoardingGroupHide = 'Hide' +BoardingGroupShow = 'Show Boarding Group' +BoardingPartyInform = "他のトゥーンをクリックして、一緒にエレベーターに乗るグループのメンバーに招待しよう。\nメンバーは%s人までだよ。" +BoardingPartyTitle = 'Boarding Group' +QuitBoardingPartyLeader = 'Disband' +QuitBoardingPartyNonLeader = 'Leave' +QuitBoardingPartyConfirm = 'Are you sure you want to quit this Boarding Group?' +BoardcodeMissing = 'Your group cannot board because something was missing.' +BoardcodeMinLaffLeader = 'Your group cannot board because you have less than %s laff points.' +BoardcodeMinLaffNonLeaderSingular = 'Your group cannot board because %s has less than %s laff points.' +BoardcodeMinLaffNonLeaderPlural = 'Your group cannot board because %s have less than %s laff points.' +BoardcodePromotionLeader = 'Your group cannot board because you do not have enough promotion merits.' +BoardcodePromotionNonLeaderSingular = 'Your group cannot board because %s does not have enough promotion merits.' +BoardcodePromotionNonLeaderPlural = 'Your group cannot board because %s do not have enough promotion merits.' +BoardcodeSpace = 'Your group cannot board because there is not enough space.' +BoardcodeBattleLeader = 'Your group cannot board beacause you are in battle.' +BoardcodeBattleNonLeaderSingular = 'Your group cannot board beacause %s is in battle.' +BoardcodeBattleNonLeaderPlural = 'Your group cannot board beacause %s are in battle.' +BoardingInviteMinLaffInviter = 'You need %s Laff Points before being a member of this Boarding Group.' +BoardingInviteMinLaffInvitee = '%s needs %s Laff Points before being a member of this Boarding Group.' +BoardingInvitePromotionInviter = 'You need to earn a promotion before being a member of this Boarding Group.' +BoardingInvitePromotionInvitee = '%s needs to earn a promotion before being a member of this Boarding Group.' +BoardingGo = 'GO' +And = 'and' +BoardingGoingTo = 'Going To:' + +# DistributedBossbotBoss.py +BossbotBossName = "DirecteurDirecteur" +BossbotRTWelcome = "Tes Toons auront besoin de différents déguisements." +BossbotRTRemoveSuit = "Tout d'abord, enlève les costumes de Cog" +BossbotRTFightWaiter = "puis attaque les serveurs." +BossbotRTWearWaiter = "Bon travail ! À présent, enfile les vêtements du serveur." +BossbotBossPreTwo1 = "Pourquoi mets-tu autant de temps ?" +BossbotBossPreTwo2 = "Dépêche-toi et sers mon banquet !" +BossbotRTServeFood1 = "Hé, sers les plats que je pose sur ces tapis déroulants." +BossbotRTServeFood2 = "Si tu sers un Cog trois fois de suite, il explose." +BossbotResistanceToonName = "Ce bon vieux Gilles Giggles" +BossbotPhase3Speech1 = "Qu'est-ce qui se passe ici ?!" +BossbotPhase3Speech2 = "Ces serveurs sont des Toons !" +BossbotPhase3Speech3 = "Attrapez-les !!!" +BossbotPhase4Speech1 = "Si je veux que le travail soit bien fait…" +BossbotPhase4Speech2 = "je le fais moi-même." +BossbotRTPhase4Speech1 = "Bon travail ! À présent, éclabousse le Directeur avec l'eau placée sur les tables..." +BossbotRTPhase4Speech2 = "ou utilise des balles de golf pour le ralentir." +BossbotPitcherLeave = "Laisser bouteille" +BossbotPitcherLeaving = "Laisse bouteille" +BossbotPitcherAdvice = "Utilise les flèches droite et gauche pour pivoter.\nMaintiens la touche Ctrl pour augmenter la puissance.\nRelâche la touche Ctrl pour tirer." +BossbotGolfSpotLeave = "Laisser balle de golf" +BossbotGolfSpotLeaving = "Laisse balle de golf\nUtilise les flèches droite et gauche pour pivoter.\nCtrl pour tirer." +BossbotGolfSpotAdvice = " " +BossbotRewardSpeech1 = "Non ! Le Président ne va pas apprécier." +BossbotRewardSpeech2 = "Arrrggghhh !!!" +BossbotRTCongratulations = "Tu as réussi. Tu a rétrogradé le Directeur !\aTiens, prends ces Avis de licenciement oubliés par le Directeur.\aTu pourras les utiliser pour licencier les Cogs durant un combat.""" +BossbotRTLastPromotion = "\aOuah, tu as atteint le niveau %s de costume de Cog !\aLes Cogs ne peuvent pas monter plus en grade. \aTu ne peux plus mettre ton costume de Cog à niveau mais tu peux continuer de travailler pour la Résistance !""" +BossbotRTHPBoost = "\aTu as fait beaucoup pour la Résistance.\aLe Conseil des Toons a décidé de te donner un autre rigolpoint. Félicitations !""" +BossbotRTMaxed = "\aJe vois que tu as un costume de Cog de niveau %s. Très impressionnant !\aLe Conseil des Toons te remercie de revenir pour défendre d'autres Toons !""" +GolfAreaAttackTaunt = "Attention !" +OvertimeAttackTaunts = [ "Il est temps de nous réorganiser.", + "Réduisons les effectifs."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "Combat Directeur" +ElevatorBossBotCourse = "Parcour de Golf Cog" +ElevatorBossBotCourse0 = "The Front Three" +ElevatorBossBotCourse1 = "The Middle Six" +ElevatorBossBotCourse2 = "The Back Nine" +ElevatorCashBotBoss = "C.F.O Battle" +ElevatorCashBotMint0 = "Coin Mint" +ElevatorCashBotMint1 = "Dollar Mint" +ElevatorCashBotMint2 = "Bullion Mint" +ElevatorSellBotBoss = "Sellbot Battle" +ElevatorSellBotFactory0 = "Front Entrance" +ElevatorSellBotFactory1 = "Back Entrance" +ElevatorLawBotBoss = "Chief Justice Battle" +ElevatorLawBotCourse0 = "Office A" +ElevatorLawBotCourse1 = "Office B" +ElevatorLawBotCourse2 = "Office C" +ElevatorLawBotCourse3 = "Office D" + + +# CatalogNameTagItem.py +DaysToGo = "Attendre\n%s Jours" + +# DistributedIceGame.py +IceGameTitle = "Glissade sur glace" +IceGameInstructions = "Rapproche-toi le plus possible du centre vers la fin de la seconde manche. Utilise les flèches du clavier pour changer de direction et de puissance. Appuie sur la touche Ctrl pour propulser ton Toon. Touche les tonneaux pour remporter des points supplémentaires et évite le TNT !" +IceGameInstructionsNoTnt = "Rapproche-toi le plus possible du centre vers la fin de la seconde manche. Utilise les flèches du clavier pour changer de direction et de puissance. Appuie sur la touche Ctrl pour propulser ton Toon. Touche les tonneaux pour remporter des points supplémentaires." +IceGameWaitingForPlayersToFinishMove = "En attente des autres joueurs..." +IceGameWaitingForAISync = "En attente des autres joueurs..." +IceGameInfo= "Match %(curMatch)d/%(numMatch)d, Manche %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="N'oublie pas d'appuyer sur la touche Ctrl !" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "Rejoindre" +PicnicTableObserveButton = "Observer" +PicnicTableCancelButton = "Annnuler" +PicnicTableTutorial = "Comment jouer" +PicnicTableMenuTutorial = "À quel jeu veux-tu apprendre à jouer ?" +PicnicTableMenuSelect = "À quel jeu veux-tu jouer ?\nSe lever" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = " " +ChineseCheckersStartButton = "Commencer jeu" +ChineseCheckersQuitButton = "Quitter jeu" +ChineseCheckersIts = "C'est" + +ChineseCheckersYourTurn = "Ton tour" +ChineseCheckersGreenTurn = "Le tour des verts" +ChineseCheckersYellowTurn = "Le tour des jaunes" +ChineseCheckersPurpleTurn = "Le tour des violets" +ChineseCheckersBlueTurn = "Le tour des bleus" +ChineseCheckersPinkTurn = "Le tour des roses" +ChineseCheckersRedTurn = "Le tour des rouges" + +ChineseCheckersColorG = "Tu es vert" +ChineseCheckersColorY = "Tu es jaune" +ChineseCheckersColorP = "Tu es violet" +ChineseCheckersColorB = "Tu es bleu" +ChineseCheckersColorPink = "Tu es rose" +ChineseCheckersColorR = "Tu es rouge" +ChineseCheckersColorO = "Tu observes" + +ChineseCheckersYouWon = "Tu viens de remporter une partie de dames chinoises !" +ChineseCheckers = "Dames chinoises." +ChineseCheckersGameOf = "vient de remporter une partie de" + +#GameTutorials.py +ChineseTutorialTitle1 = "But" +ChineseTutorialTitle2 = "Comment jouer" +ChineseTutorialPrev = "Page précédente" +ChineseTutorialNext = "Page suivante" +ChineseTutorialDone = "Terminé" +ChinesePage1 = "Le but du jeu des dames chinoises est d'être le premier joueur à déplacer toutes ses billes du triangle en bas du tableau vers le triangle en haut du tableau. Le premier joueur qui réussit a gagné. \n" +ChinesePage2 = "Chacun à son tour, les joueurs déplacent une bille de leur couleur. Celle-ci peut être placée dans un trou adjacent ou sauter par-dessus d'autres billes. Les sauts doivent passer au-dessus d'une bille et atterrir dans un trou vide. Il est possible d'enchaîner des sauts pour des mouvements plus longs." + +CheckersPage1 = "Le but du jeu de dames est de coincer l'adversaire pour qu'il ne puisse plus bouger. Pour ce faire, tu peux capturer toutes ses pièces ou les bloquer de manière à ce qu'il soit coincé et ne puisse plus bouger." +CheckersPage2 = "Chacun leur tour, les joueurs déplacent une pièce de leur couleur. Celle-ci peut être déplacée en diagonale ou vers l'avant. Elle peut uniquement avancer sur un carré ne contenant pas de pièce. Les règles sont les mêmes pour les dames, mais elles peuvent aller en arrière." +CheckersPage3 = "Pour capturer la pièce d'un adversaire, tu dois sauter par-dessus en diagonale et te placer dans le carré vide de l'autre côté. Si tu as la possibilité de faire des sauts durant un tour, tu dois exécuter l'un d'entre eux. Tu peux enchaîner les sauts, dans la mesure où tu utilises la même pièce." +CheckersPage4 = "Une pièce devient dame lorsqu'elle atteint la dernière rangée du tableau. Une pièce qui vient de devenir dame doit attendre le prochain tour pour pouvoir sauter. En outre, les dames ont le droit de se déplacer dans toutes les directions et peuvent changer de direction durant un saut." + + + +#DistributedCheckers.py +CheckersGetUpButton = "Se lever" +CheckersStartButton = "Commencer jeu" +CheckersQuitButton = "Quitter jeu" +CheckersIts = "C'est" +CheckersYourTurn = "Ton tour" +CheckersWhiteTurn = "Le tour des blancs" +CheckersBlackTurn = "Le tour des noirs" + +CheckersColorWhite = "Tu es blanc" +CheckersColorBlack = "Tu es noir" +CheckersObserver = "Tu observes" +RegularCheckers = "Jeu de dames." +RegularCheckersGameOf = "vient de remporter une partie de" +RegularCheckersYouWon = "Tu viens de remporter une partie de dames !" + +MailNotifyNewItems = "Tu as reçu un e-mail !" +MailNewMailButton = "E-mail" +MailSimpleMail = "Note" +MailFromTag = "Note de : %s" + +# MailboxScreen.py +InviteInvitation = "the invitation" +InviteAcceptInvalidError = "L'invitation n'est plus valide." +InviteAcceptPartyInvalid = "La fête a été annulée." +InviteAcceptAllOk = "L'hôte a été informé de ta réponse" +InviteRejectAllOk = "The host has been informed that you declined the invitation." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "JANUARY", + 2: "FEBRUARY", + 3: "MARCH", + 4: "APRIL", + 5: "MAY", + 6: "JUNE", + 7: "JULY", + 8: "AUGUST", + 9: "SEPTEMBER", +10: "OCTOBER", +11: "NOVEMBER", +12: "DECEMBER" +} + +# Note 0 for Monday to match datetime +DayNames = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") +DayNamesAbbrev = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Summer Fireworks", "Celebrate Summer with a fireworks show every hour in each playground!"), + 2: ("New Year Fireworks", "Happy New Year! Enjoy a fireworks show every hour in each playground!"), + 3: ("Bloodsucker Invasion", "Happy Halloween! Stop the Bloodsucker Cogs from invading Toontown!"), + 4: ("Winter Holidays Decor", "Celebrate the Winter Holidays with Toontastic trees and streetlights!"), + 5: ("Skelecog Invasion", "Stop the Skelecogs from invading Toontown!"), + 6: ("Mr. Hollywood Invasion", "Stop the Mr. Hollywood Cogs from invading Toontown!"), + 7: ("Fish Bingo", "Fish Bingo Wednesday! Everyone at the pond works together to complete the card before time runs out."), + 8: ("Toon Species Election", "Vote on the new Toon species! Will it be Goat? Will it be Pig?"), + 9: ("Black Cat Day", "Happy Halloween! Create a Toontastic Black Cat Toon - Today Only!"), + 13: ("Trick or Treat", "Happy Halloween! Trick or treat throughout Toontown to get a nifty Halloween pumpkin head reward!"), + 14: ("Grand Prix", "Grand Prix Monday at Goofy Speedway! To win, collect the most points in three consecutive races!"), + 17: ("Trolley Tracks", "Trolley Tracks Thursday! Board any Trolley with two or more Toons to play."), + 19: ("Silly Saturdays", "Saturdays are silly with Fish Bingo, Grand Prix, and Trolley Tracks throughout the day!"), + 24: ("Ides of March", "Beware the Ides of March! Stop the Backstabber Cogs from invading Toontown!"), + 26: ("Halloween Decor", "Celebrate Halloween as spooky trees and streetlights transform Toontown!"), + } +UnknownHoliday = "Unknown Holiday %d" diff --git a/toontown/src/toonbase/french/TTLocalizer_Property.py b/toontown/src/toonbase/french/TTLocalizer_Property.py new file mode 100644 index 0000000..8b02d9b --- /dev/null +++ b/toontown/src/toonbase/french/TTLocalizer_Property.py @@ -0,0 +1,287 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.045 +RPmeritBarLabels = 0.15 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.1 + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 0.8 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 1.5 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.10) +CSgiftToggle = 0.07 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 20 +CMunpaidChatWarning = 0.055 +CMunpaidChatWarning_text_z = 0.27 +CMpayButton = 0.06 +CMpayButton_pos_z = -0.10 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 +CMNoPasswordContinue_z = -0.25 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.45 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.06 +FPnewRecord = 0.06 + +#fishing/GenusPanel.py +GPgenus = 0.035 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 + +#hood/EstateHood.py +EHpopupInfo = .08 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.095 +ACdeleteWithPassword = 0.06 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 +ACquitButton_pos = -0.024 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 +MRPinstructionsText = 0.05 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.05 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.05 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.01 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.1 +NScolorPrecede = False + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.65 + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.07 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.4 +PAPcall = 0.4 +PAPowner = 0.30 +PAPscratch = 0.4 +PAPstateLabel = 0.35 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.08 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-3.3, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.095 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0625 + +#race/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.3 + +#race/DistributedRacePad.py +DRPnodeScale = 0.65 + +#race/KartShopGui.py +KSGtextSizeBig = 0.06 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 22 + +#race/RaceEndPanels.py +REPraceEnd = 0.065 +REPraceExit = 0.025 +REPticket_text_x = -0.7 + +#race/RaceGUI.py +RGphotoFinish = 0.20 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.06 + +#safezone/Playground.py +PimgLabel = 0.6 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.08 +TPendFrame = 0.08 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.05 +MPhoodWordwrap = 20 + +#shtiker/KartPage.py +KPkartTab = 0.06 +KPdeleteButton = 0.035 +KProtateButton = 0.03 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.032 +INrunButton = 0.045 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.6 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TPtop = 0.065 +TPpanel = 0.055 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.08 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/french/__init__.py b/toontown/src/toonbase/french/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/toonbase/japanese/TTLocalizer.py b/toontown/src/toonbase/japanese/TTLocalizer.py new file mode 100644 index 0000000..228acac --- /dev/null +++ b/toontown/src/toonbase/japanese/TTLocalizer.py @@ -0,0 +1,9634 @@ +import string +import time +from toontown.toonbase.japanese.TTLocalizer_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/HGHeiseiMarugothictaiW8.ttc' +ToonFont = 'phase_3/models/fonts/HGHeiseiMarugothictaiW8.ttc' +SuitFont = 'phase_3/models/fonts/HGHanKointai.ttc' +SignFont = 'phase_3/models/fonts/MickeyFont.bam' +MinnieFont = 'phase_3/models/fonts/MickeyFont.bam' +BuildingNametagFont = 'phase_3/models/fonts/DFKyG7.ttc:1' +BuildingNametagShadow = (0.05, 0.05) + +# Product prefix +ProductPrefix = 'TT' + +# common names +Mickey = "ミッキー" +Minnie = "ミニー" +Donald = "ドナルド" +Daisy = "デイジー" +Goofy = "グーフィー" +Pluto = "プルート" +Flippy = "フリッピー" +Chip = "チップ" +Dale = "デール" + +# common locations +lTheBrrrgh = 'ブルブルランド' +lDaisyGardens = 'デイジーガーデン' +lDonaldsDock = "ドナルドのハトバ" +lDonaldsDreamland = "ドナルドのドリームランド" +lMinniesMelodyland = "ミニーのメロディーランド" +lToontownCentral = 'トゥーンタウンセントラル' +lToonHQ = 'トゥーンHQ' +lOutdoorZone = "チップとデールのどんぐり広場" +lGoofySpeedway = "グーフィー・サーキット" +lGolfZone = "チップとデールのミニ・ゴルフ" + +lGagShop = 'ギャグショップ' +lClothingShop = 'ようふくや' +lPetShop = 'ペットショップ' + +# common strings +lCancel = 'キャンセル' +lClose = 'とじる' +lOK = 'OK' +lNext = 'つぎへ' +lQuit = 'やめる' +lYes = 'はい' +lNo = 'いいえ' +lBack = '戻る' + +lHQ = '本部' + +lHQOfficerF = 'HQスタッフ' +lHQOfficerM = 'HQスタッフ' + +MickeyMouse = "ミッキーマウス" + +AIStartDefaultDistrict = "シリーヴィル" + +Cog = "コグ" +Cogs = "コグ" +ACog = "コグ" +TheCogs = "コグ" +ASkeleton = "ガイコグ" +Skeleton = "ガイコグ" +SkeletonP = "ガイコグ" +Av2Cog = "a Version 2.0 Cog" +v2Cog = "Version 2.0 Cog" +v2CogP = "Version 2.0 Cogs" +Foreman = "工場長" +ForemanP = "工場長" +AForeman = "工場長" +CogVP = "コグゼキュティブ" +CogVPs = "コグゼキュティブの" +ACogVP = "コグゼキュティブ" +Supervisor = "金庫番" +SupervisorP = "金庫番" +ASupervisor = "金庫番" +CogCFO = "マネーマネー" +CogCFOs = "マネーマネーの" +ACogCFO = "マネーマネー" + +# AvatarDNA.py +Bossbot = "ボスボット" +Lawbot = "ロウボット" +Cashbot = "マネーボット" +Sellbot = "セルボット" +BossbotS = "ボスボット" +LawbotS = "ロウボット" +CashbotS = "マネーボット" +SellbotS = "セルボット" +BossbotP = "ボスボット" +LawbotP = "ロウボット" +CashbotP = "マネーボット" +SellbotP = "セルボット" +BossbotSkelS = BossbotS+" "+Skeleton +LawbotSkelS = LawbotS+" "+Skeleton +CashbotSkelS = CashbotS+" "+Skeleton +SellbotSkelS = SellbotS+" "+Skeleton +BossbotSkelP = Bossbot+" "+Skeleton +LawbotSkelP = Lawbot+" "+Skeleton +CashbotSkelP = Cashbot+" "+Skeleton +SellbotSkelP = Sellbot+" "+Skeleton + +#lToonHQ = 'トゥーン'+lHQ #check +lBossbotHQ = Bossbot+lHQ +lLawbotHQ = Lawbot+lHQ +lCashbotHQ = Cashbot+lHQ +lSellbotHQ = Sellbot+lHQ + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("", "", "チュートリアル・テラス"),# Tutorial + 1000 : ("", "", "プレイグラウンド"), + 1100 : ("", "", "バーナクル・ストリート"), + 1200 : ("", "", "シーウィード・ストリート"), + 1300 : ("", "", "ライトハウス・レーン"), + 2000 : ("", "", "プレイグラウンド"), + 2100 : ("", "", "シリー・ストリート"), + 2200 : ("", "", "ルーピー・ストリート"), + 2300 : ("", "", "パンチライン・ストリート"), + 3000 : ("", "", "プレイグラウンド"), + 3100 : ("", "", "セイウチ・ストリート"), + 3200 : ("", "", "スリート・ストリート"), + 3300 : ("", "", "ポーラープレイス"), + 4000 : ("", "", "プレイグラウンド"), + 4100 : ("", "", "アルト・アベニュー"), + 4200 : ("", "", "バリトン・ストリート"), + 4300 : ("", "", "テナー・ストリート"), + 5000 : ("", "", "プレイグラウンド"), + 5100 : ("", "", "エルム・ストリート"), + 5200 : ("", "", "メイプル・ストリート"), + 5300 : ("", "", "オーク・ストリート"), + 9000 : ("", "", "プレイグラウンド"), + 9100 : ("", "", "ララバイ・ストリート"), + 9200 : ("", "", "パジャマ・プレイス"), + 10000 : ("","", lBossbotHQ), + 10100 : ("","", lBossbotHQ+'ロビー'), + 10200 : ("to the", "in the", "The Clubhouse"), + 10500 : ("to the", "in the", "The Front Three"), + 10600 : ("to the", "in the", "The Middle Six"), + 10700 : ("to the", "in the", "The Back Nine"), + 11000 : ("","", lSellbotHQ+'中庭'), + 11100 : ("","", lSellbotHQ+'ロビー'), + 11200 : ("","", Sellbot+'ファクトリー'), + 11500 : ("","", Sellbot+'ファクトリー'), + 12000 : ("","", lCashbotHQ), + 12100 : ("","", lCashbotHQ+'ロビー'), + 12500 : ("","", Cashbot+' コイン工場'), + 12600 : ("","", Cashbot+' ドル工場'), + 12700 : ("","", Cashbot+' ゴールド工場'), + 13000 : ("","", lLawbotHQ), + 13100 : ("","", lLawbotHQ+'ロビー'), + 13200 : ("", "", "裁判所ロビー"), + 13300 : ("", "", "ロウボットAオフィス"), + 13400 : ("", "", "ロウボットBオフィス"), + 13500 : ("", "", "ロウボットCオフィス"), + 13600 : ("", "", "ロウボットDオフィス"), + } + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("", "", lDonaldsDock) +ToontownCentral = ("", "", lToontownCentral) +TheBrrrgh = ("", "", lTheBrrrgh) +MinniesMelodyland = ("", "", lMinniesMelodyland) +DaisyGardens = ("", "", lDaisyGardens) +ConstructionZone = ("", "", "こうじちゅう") +OutdoorZone = ("", "", lOutdoorZone) +FunnyFarm = ("", "", "ファニー・ファーム") +GoofySpeedway = ("", "", lGoofySpeedway) +DonaldsDreamland = ("", "", lDonaldsDreamland) +BossbotHQ = ("", "", "ボスボットほんぶ") +SellbotHQ = ("", "", "セルボットほんぶ") +CashbotHQ = ("", "", "マネーボットほんぶ") +LawbotHQ = ("", "", "ロウボットほんぶ") +Tutorial = ("", "", "トゥーントリアル") +MyEstate = ("", "", "キミのおうち") +WelcomeValley = ("", "", "ウェルカムバレー") +GolfZone = ("", "", lGolfZone) + +Factory = 'コグファクトリー' +Headquarters = '本部' +SellbotFrontEntrance = 'ファクトリー入口' +SellbotSideEntrance = 'ファクトリー裏口' +Office = '事務所' #localize + +FactoryNames = { + 0 : '工場の模型', + 11500 : 'セルボット コグファクトリー', + 13300 : 'ロウボットコグオフィス', #remove me JML + } + +FactoryTypeLeg = 'レッグ' +FactoryTypeArm = 'アーム' +FactoryTypeTorso = 'ボディ' + +MintFloorTitle = '%s階' + +# Quests.py +TheFish = "魚" +AFish = "魚" +Level = "レベル" +QuestsCompleteString = "コンプリート" +QuestsNotChosenString = "選択されていません" +Period = "。" + +Laff = "ゲラゲラポイント" + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("こんにちは、 _avName_!", + "やあ、 _avName_!", + "_avName_!", + "_avName_ じゃないか!", + "いらっしゃい、 _avName_!", + "ハロー、 _avName_!", + "ごきげんいかがかな、 _avName_?", + "あっ、 _avName_!", + ) +QuestsDefaultIncomplete = ("タスクは進んでるかい、_avName_?", + "そのタスクが終わるまで、まだまだみたいだね。", + "がんばってね、_avName_!", + "最後までがんばってね、君ならきっと大丈夫!", + "タスクをコンプリートするまでがんばってね、応援してるよ!", + "トゥーンタスクが終わるまで、がんばれ!", + ) +QuestsDefaultIncompleteProgress = ("ここに来たのは正解だけど、まずはトゥーンタスクを終わらせなきゃね。", + "そのトゥーンタスクが終わってからここに戻っておいで。", + "トゥーンタスクが終わったらここに戻ってきてね。", + ) +QuestsDefaultIncompleteWrongNPC = ("よく出来たね。次は_toNpcName_のところに行ってね。_where_", + "そのトゥーンタスクもそろそろ終わりかな。_toNpcName_に会うといい。_where_", + "_toNpcName_に会って、タスクを終わらせておいで。_where_", + ) +QuestsDefaultComplete = ("よくやったね!\nこれは君へのごほうびだよ。", + "やったね、_avName_! はい、ごほうびだよ。", + "素晴らしい出来だね、_avName_! これをごほうびにあげるよ。", + ) +QuestsDefaultLeaving = ("バイバイ!", + "さよなら!", + "じゃあね、_avName_", + "またね、_avName_!", + "がんばってね!", + "トゥーンタウンで楽しんでいってね!", + "それじゃまたね!", + ) +QuestsDefaultReject = ("こんにちは。", + "いらっしゃいませ。", + "調子はどう?", + "今日もいい天気ですね。", + "今ちょっと忙しいんですよ、_avName_さん。", + "はい?", + "こんにちは、 _avName_!", + "_avName_、 いらっしゃい!", + "あ、_avName_! こんにちは!", + # Game Hints + "トゥーンガイドを開くにはF8ボタンを押せばいいって知ってた?", + "地図を使うと、遊び場にワープすることができるよ!", + "他のプレイヤーと友達になりたかったら、彼らをクリックしてみるといいよ。", + "" + Cog + "をクリックすると、そいつの情報を得られるよ。", + "ゲラゲラメーターをいっぱいにするには遊び場でトレジャーを集めよう。", + "" + Cog + "の建物は危険だから、一人で行かないようにね。", + "バトルで負けちゃうと、" + Cogs + "がキミのギャグをぜんぶ持っていっちゃうんだ。", + "トロリーゲームをプレイするとギャグが集まりやすいよ!", + "トゥーンタスクを完了するとゲラゲラポイントをもらえるよ。", + "トゥーンタスクを終わらせるごとにごほうびがもらえるよ。", + "ギャグをもっと運べるようになる「ごほうび」もあるんだって", + "バトルで勝ったら、倒した" + Cog + "の数に応じてトゥーンタスクのポイントがもらえるのさ。", + "" + Cog + " の建物を奪回したら、中に入ってみよう。建物の所有者が感謝のことばを伝えてくれるよ。", + "PageUpボタンを押していると、上を見上げることができるよ!", + "Tabボタンを押すと、周りを違った視点から見ることができるよ。", + "自分の考えを友達にだけ伝えたかったら、発言の前に'.'をつけてね。", + "" + Cog + "がスタン状態になっていると、落下物をよけられなくなるらしいよ。", + "" + Cog + "の建物はひとつとして同じ外観のものがないんだって", + "建物の上の階にいる" + Cogs + "を倒すと、バトル後のスキルレベルがもっと高くなるよ。", + ) +QuestsDefaultTierNotDone = ("こんにちは、_avName_!新しいトゥーンタスクを始める前に、今のタスクを終わらせてね。", + "やあ!今もってるタスクを終わらせてから新しいトゥーンタスクをスタートしてね。", + "やあ、_avName_! 今もってるタスクを終わらせたら、新しいタスクをあげるよ。", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("_toNpcName_がキミを探してるらしいよ。_where_", + "時間がある時にでも_toNpcName_に会ってみて。_where_", + "もし_toNpcName_のいる方に行くことがあったら、会ってあげて。_where_", + "_toNpcName_に会いに行くといいよ。_where_", + "_toNpcName_が新しいタスクをくれるよ。_where_", + ) +# Quest dialog +QuestsLocationArticle = "で" +def getLocalNum(num): + if (num <=9): + return str(num) + "つ" + else: + return str(num) +QuestsItemNameAndNum = "%(name)s %(num)s" #★1月24日新規修正 by Gregさん + +QuestsCogQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogQuestHeadline = "ウォンテッド" +QuestsCogQuestSCStringS = "%(cogLoc)s%(cogName)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogQuestSCStringP = "%(cogLoc)s%(cogName)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogQuestDefeat = "%sを倒す" +QuestsCogQuestDefeatDesc = "%(cogName)s%(numCogs)s体" + +QuestsCogNewNewbieQuestObjective = "新しいトゥーンを助けて、%sをやっつけよう!" +QuestsCogNewNewbieQuestCaption = "ゲラゲラポイントが%d以下の新しいトゥーンを助けよう!" +QuestsCogOldNewbieQuestObjective = "ゲラゲラポイントが%(laffPoints)d以下のトゥーンを助けて%(objective)sをやっつける" +QuestsCogOldNewbieQuestCaption = "ゲラゲラポイントが%d以下のトゥーンを助けよう!" +QuestsCogNewbieQuestAux = "倒す相手:" +QuestsNewbieQuestHeadline = "みならい" + +QuestsCogTrackQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogTrackQuestHeadline = "ウォンテッド" +QuestsCogTrackQuestSCStringS = "%(cogLoc)s%(cogText)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogTrackQuestSCStringP = "%(cogLoc)s%(cogText)sを倒さなくちゃ!" #★「の」がAnywhereのときに不要 +QuestsCogTrackQuestDefeat = "%sを倒す" +QuestsCogTrackDefeatDesc = "%(trackName)s%(numCogs)s体" #★新規修正(1月20日) + +QuestsCogLevelQuestProgress = "倒した数:%(progress)s / %(numCogs)s" +QuestsCogLevelQuestHeadline = "ウォンテッド" +QuestsCogLevelQuestDefeat = "%sを倒す" +QuestsCogLevelQuestDesc = "レベル%(level)s以上の%(name)s" +QuestsCogLevelQuestDescC = "レベル%(level)s以上の%(name)s%(count)s体" +QuestsCogLevelQuestDescI = "レベル%(level)s以上の%(name)s" +QuestsCogLevelQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" #★「で」がAnywhereのときに不要 + +QuestsBuildingQuestFloorNumbers = ('', '2階建以上の', '3階建て以上の', '4階建て以上の', '5階建て以上の') +QuestsBuildingQuestBuilding = "ビル" +QuestsBuildingQuestBuildings = "ビル" +QuestsBuildingQuestHeadline = "とりもどす" +QuestsBuildingQuestProgressString = "とりもどした数:%(progress)s / %(num)s" +QuestsBuildingQuestString = "%sを倒す" +QuestsBuildingQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" #★「で」がAnywhereのときに不要 + +QuestsBuildingQuestDesc = "%(type)sビル" +QuestsBuildingQuestDescF = "%(floors)s%(type)sビル" +QuestsBuildingQuestDescC = "%(type)sビル×%(count)s軒" +QuestsBuildingQuestDescCF = "%(floors)s%(type)sビル×%(count)s軒" +QuestsBuildingQuestDescI = "%(type)sビル数軒" +QuestsBuildingQuestDescIF = "%(floors)s%(type)sビル数軒" + +QuestFactoryQuestFactory = "コグファクトリー" +QuestsFactoryQuestFactories = "コグファクトリー" +QuestsFactoryQuestHeadline = "やっつける" +QuestsFactoryQuestProgressString = "やっつけた数:%(progress)s / %(num)s" +QuestsFactoryQuestString = "%sを倒す" +QuestsFactoryQuestSCString = "%(location)s%(objective)sを倒さなくちゃ!" + +QuestsFactoryQuestDesc = "%(type)s 工場" +QuestsFactoryQuestDescC = "%(type)s 工場×%(count)s" +QuestsFactoryQuestDescI = "%(type)s 工場を数軒" + +QuestMintQuestMint = "マネーファクトリー" +QuestsMintQuestMints = "マネーファクトリー" +QuestsMintQuestHeadline = "やっつける" +QuestsMintQuestProgressString = "やっつけた数:%(progress)s / %(num)s" +QuestsMintQuestString = "%sを倒す" +QuestsMintQuestSCString = "%(objective)s%(location)sを倒さなくちゃ!" + +QuestsMintQuestDesc = "マネーファクトリー" +QuestsMintQuestDescC = "マネーファクトリー×%(count)s" +QuestsMintQuestDescI = "マネーファクトリー" + +QuestsRescueQuestProgress = "助けた数:%(progress)s / %(numToons)s" +QuestsRescueQuestHeadline = "たすける" +QuestsRescueQuestSCStringS = "%(toonLoc)sのトゥーンを助けなくちゃ!" +QuestsRescueQuestSCStringP = "%(toonLoc)sのトゥーンを何人か助けなくちゃ!" +QuestsRescueQuestRescue = "%sを助ける" +QuestsRescueQuestRescueDesc = "トゥーン%(numToons)s人" +QuestsRescueQuestToonS = "トゥーン" +QuestsRescueQuestToonP = "トゥーン" +QuestsRescueQuestAux = "たすける:" + +QuestsRescueNewNewbieQuestObjective = "新しいトゥーンの仲間と一緒に%sを助けよう!" +QuestsRescueOldNewbieQuestObjective = "ゲラゲラポイント%(laffPoints)d以下のトゥーンと%(objective)sを助けよう!" + +QuestCogPartQuestCogPart = "コグスーツの部品" +QuestsCogPartQuestFactories = "コグファクトリー" +QuestsCogPartQuestHeadline = "取り戻す" +QuestsCogPartQuestProgressString = "取り戻した数:%(progress)s / %(num)s" +QuestsCogPartQuestString = "%sを取り戻す" +QuestsCogPartQuestSCString = "%(location)sの%(objective)sを取り戻さなくちゃ!" +QuestsCogPartQuestAux = "取り戻す:" + +QuestsCogPartQuestDesc = "コグスーツの部品" +QuestsCogPartQuestDescC = "コグスーツの部品×%(count)sつ" +QuestsCogPartQuestDescI = "コグスーツの部品をいくつか" + +QuestsCogPartNewNewbieQuestObjective = '新しいトゥーンの仲間と一緒に%sを取り戻そう!' +QuestsCogPartOldNewbieQuestObjective = 'ゲラゲラポイント%(laffPoints)d以下のトゥーンと%(objective)sを取り戻そう!' + +QuestsDeliverGagQuestProgress = "デリバリーされた数:%(progress)s / %(numGags)s" +QuestsDeliverGagQuestHeadline = "デリバリー" +QuestsDeliverGagQuestToSCStringS = "%(gagName)sをデリバリーしなくちゃ!" +QuestsDeliverGagQuestToSCStringP = "%(gagName)sをデリバリーしなくちゃ!" +QuestsDeliverGagQuestSCString = "デリバリーしなくちゃ!" +QuestsDeliverGagQuestString = "%sをデリバリーする" +QuestsDeliverGagQuestStringLong = "%sを_toNpcName_にデリバリーする" +QuestsDeliverGagQuestInstructions = "アクセスを許可されたら、このギャグをギャグショップで買うことができるようになるよ。" + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "デリバリー" +QuestsDeliverItemQuestSCString = "%(article)s%(itemName)sをデリバリーしなくちゃ!" +QuestsDeliverItemQuestString = "%sをデリバリーする" +QuestsDeliverItemQuestStringLong = "%sを_toNpcName_にデリバリーする" + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "あいにいく" +QuestsVisitQuestStringShort = "あいにいく" +QuestsVisitQuestStringLong = "_toNpcName_に会いに行く" +QuestsVisitQuestSeeSCString = "%sに会いに行かなくちゃ!" + +QuestsRecoverItemQuestProgress = "取り返した数:%(progress)s / %(numItems)s" +QuestsRecoverItemQuestHeadline = "とりかえす" +QuestsRecoverItemQuestSeeHQSCString = lHQOfficerM+"に会いに行かなくちゃ。" +QuestsRecoverItemQuestReturnToHQSCString = lHQOfficerM+"に%sを返しに行かなくちゃ。" +QuestsRecoverItemQuestReturnToSCString = "%(npcName)sに%(item)sを返しに行かなくちゃ。" +QuestsRecoverItemQuestGoToHQSCString = "%sに行かなくちゃ。" % lToonHQ +QuestsRecoverItemQuestGoToPlaygroundSCString = "%sのプレイグラウンドに行かなくちゃ。" +QuestsRecoverItemQuestGoToStreetSCString = "%(hood)sの%(street)s%(to)sに行かなくちゃ。" #★ +QuestsRecoverItemQuestVisitBuildingSCString = "%s%sに行かなくちゃ。" +QuestsRecoverItemQuestWhereIsBuildingSCString = "%s%sはどこですか?" +QuestsRecoverItemQuestRecoverFromSCString = "%(loc)s%(holder)sから%(item)sを取り返さなくちゃ。" +QuestsRecoverItemQuestString = "%(holder)sから%(item)sを取り返す。" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d以上 %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "えらぶ" +QuestsTrackChoiceQuestSCString = "%(trackA)sと%(trackB)sのどっちかを選ばなくちゃ" +QuestsTrackChoiceQuestMaybeSCString = "%sにしようかな" +QuestsTrackChoiceQuestString = "%(trackA)sと%(trackB)sのどちらかを選ぶ" + +QuestsFriendQuestHeadline = "ともだち" +QuestsFriendQuestSCString = "ともだちを作らなくちゃ" +QuestsFriendQuestString = "ともだちを作る" + +QuestsMailboxQuestHeadline = "メール" +QuestsMailboxQuestSCString = "メールをチェックしなくちゃ!" +QuestsMailboxQuestString = "メールをチェックする" + +QuestsPhoneQuestHeadline = "クララベル" +QuestsPhoneQuestSCString = "クララベルに電話しなくちゃ!" +QuestsPhoneQuestString = "クララベルに電話する" + +QuestsFriendNewbieQuestString = "ゲラゲラポイント%d以下のトゥーン%d人とともだちになる。" +QuestsFriendNewbieQuestProgress = "ともだちの数:%(progress)s / %(numFriends)s" +QuestsFriendNewbieQuestObjective = "ゲラゲラポイント%d以下のトゥーン%d人とともだちになる。" + +QuestsTrolleyQuestHeadline = "トロリー" +QuestsTrolleyQuestSCString = "トロリーに乗らなくちゃ" +QuestsTrolleyQuestString = "トロリーに乗る" +QuestsTrolleyQuestStringShort = "トロリーに乗る" + +QuestsMinigameNewbieQuestString = "%dミニゲーム" +QuestsMinigameNewbieQuestProgress = "あそんだ数:%(progress)s / %(numMinigames)s" +QuestsMinigameNewbieQuestObjective = "ゲラゲラポイント%d以下のトゥーンと%d回、ミニゲームをする。" +QuestsMinigameNewbieQuestSCString = "新しいトゥーンとミニゲームをしなくちゃ!" +QuestsMinigameNewbieQuestCaption = "ゲラゲラポイント%d以下の新しいトゥーンを助ける。" +QuestsMinigameNewbieQuestAux = "あそぶ:" + +QuestsMaxHpReward = "ゲラゲラリミットが%sポイントふえました。" +QuestsMaxHpRewardPoster = "ごほうび:%sポイントのゲラゲラブースト" + +QuestsMoneyRewardSingular = "ジェリービーンを\n1コゲット" +QuestsMoneyRewardPlural = "ジェリービーンを\n%sコゲット!" +QuestsMoneyRewardPosterSingular = "ごほうび:ジェリービーン1コ" +QuestsMoneyRewardPosterPlural = "ごほうび:ジェリービーン%sコ" + +QuestsMaxMoneyRewardSingular = "ジェリービーン1コを持てるようになったよ。" +QuestsMaxMoneyRewardPlural = "ジェリービーン%sコ持てるようになったよ。" +QuestsMaxMoneyRewardPosterSingular = "ごほうび:ジェリービーン1コを持てる" +QuestsMaxMoneyRewardPosterPlural = "ごほうび:ジェリービーン%sコを持てる" + +QuestsMaxGagCarryReward = "%(name)sをゲット。ギャグを%(num)sコ持てるようになったよ。" +QuestsMaxGagCarryRewardPoster = "ごほうび:%(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "トゥーンタスクを%sつ持てるようにしよう。" +QuestsMaxQuestCarryRewardPoster = "ごほうび:トゥーンタスクを%sつ持てる" + +QuestsTeleportReward = "%sへのワープアクセスをゲット" +QuestsTeleportRewardPoster = "ごほうび:%sへのワープアクセス" + +QuestsTrackTrainingReward = "\"%s\"のギャグの練習ができるようになったよ。" +QuestsTrackTrainingRewardPoster = "ごほうび:ギャグの練習" + +QuestsTrackProgressReward = "\"%(trackName)s\"のアニメーション %(frameNum)sコマ目をゲット" +QuestsTrackProgressRewardPoster = "ごほうび:\"%(trackName)s\"のアニメーション %(frameNum)sコマ目" + +QuestsTrackCompleteReward = "ギャグ\"%s\"を使えるようになったよ。" +QuestsTrackCompleteRewardPoster = "ごほうび:最終トラック%sの練習" + +QuestsClothingTicketReward = "服を着替えられるようになったよ。" +QuestsClothingTicketRewardPoster = "ごほうび:ようふく券" + +QuestsCheesyEffectRewardPoster = "ごほうび:%s" + +QuestsCogSuitPartReward = "コグスーツの部品:%(cogTrack)sの%(part)sをゲット! " +QuestsCogSuitPartRewardPoster = "ごほうび: %(cogTrack)sの%(part)sパーツ" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "このプレイグラウンドにあるよ。" +QuestsStreetLocationThisStreet = "このストリートにあるよ。" +QuestsStreetLocationNamedPlayground = "%sのプレイグラウンドにあるよ。" +QuestsStreetLocationNamedStreet = "%(toHoodName)sの%(toStreetName)sだよ。" +QuestsLocationString = "%(location)s%(string)s" +QuestsLocationBuilding = "%sの建物は" +QuestsLocationBuildingVerb = "それは" +QuestsLocationParagraph = "\a%(building)s%(buildingName)sだよ。\a%(buildingVerb)s%(street)s" +QuestsGenericFinishSCString = "トゥーンタスクを終わらせなくちゃ。" + +# MaxGagCarryReward names +QuestsMediumPouch = "中くらいの袋" +QuestsLargePouch = "大きい袋" +QuestsSmallBag = "小さい袋" +QuestsMediumBag = "中くらいのバッグ" +QuestsLargeBag = "大きいバッグ" +QuestsSmallBackpack = "小さいリュック" +QuestsMediumBackpack = "中くらいのリュック" +QuestsLargeBackpack = "大きいリュック" +QuestsItemDict = { + 1 : ["めがね", "めがね", ""], + 2 : ["かぎ", "かぎ", ""], + 3 : ["こくばん", "こくばん", ""], + 4 : ["ほん", "ほん", ""], + 5 : ["チョコレート", "チョコレート", ""], + 6 : ["チョーク", "チョーク", ""], + 7 : ["レシピ", "レシピ", ""], + 8 : ["ノート", "ノート", ""], + 9 : ["ATM", "ATM", ""], + 10 : ["ピエロ車のタイヤ", "ピエロ車のタイヤ", ""], + 11 : ["くうきいれ", "くうきいれ", ""], + 12 : ["タコのすみ", "タコのすみ", ""], + 13 : ["つつみ", "つつみ", ""], + 14 : ["きんぎょのレシート", "きんぎょのレシート", ""], + 15 : ["きんぎょ", "きんぎょ", ""], + 16 : ["オイル", "オイル", ""], + 17 : ["あぶら", "あぶら", ""], + 18 : ["みず", "みず", ""], + 19 : ["ギアレポート", "ギアレポート", ""], + 20 : ["黒板消し", "黒板消し", ""], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["ようふく券", "ようふく券", ""], + + # Donald's Dock quest items + 2001 : ["インナーチューブ", "インナーチューブ", ""], + 2002 : ["かためがねの処方せん", "かためがねの処方せん", ""], + 2003 : ["めがねのフレーム", "めがねのフレーム", ""], + 2004 : ["かためがね", "かためがね", ""], + 2005 : ["しろいカツラ", "しろいカツラ", ""], + 2006 : ["たくさんのじゃり", "たくさんのじゃり", ""], + 2007 : ["コグ・ギア", "コグ・ギア", ""], + 2008 : ["かいず", "かいず", ""], + 2009 : ["よごれたクロヴィス", "よごれたクロヴィス", ""], + 2010 : ["きれいなクロヴィス", "きれいなクロヴィス", ""], + 2011 : ["とけいのばね", "とけいのばね", ""], + 2012 : ["カウンターウェイト", "カウンターウェイト", ""], + + # Minnie's Melodyland quest items + 4001 : ["ティナのもくろく", "ティナのもくろく", ""], + 4002 : ["ユキのもくろく", "ユキのもくろく", ""], + 4003 : ["もくろくひょう", "もくろくひょう", ""], + 4004 : ["バイオレットのもくろく", "バイオレットのもくろく", ""], + 4005 : ["モックンのチケット", "モックンのチケット", ""], + 4006 : ["タバサのチケット", "タバサのチケット", ""], + 4007 : ["バリーのチケット", "バリーのチケット", ""], + 4008 : ["くもったカスタネット", "くもったカスタネット", ""], + 4009 : ["あおいイカのすみ", "あおいイカのすみ", ""], + 4010 : ["とうめいなカスタネット", "とうめいなカスタネット", ""], + 4011 : ["レオの歌詞カード", "レオの歌詞カード", ""], + + # Daisy's Gardens quest items + 5001 : ["シルクのネクタイ", "シルクのネクタイ", ""], + 5002 : ["しましまのスーツ", "しましまのスーツ", ""], + 5003 : ["はさみ", "はさみ", ""], + 5004 : ["はがき", "はがき", ""], + 5005 : ["ペン", "ペン", ""], + 5006 : ["インクつぼ", "インクつぼ", ""], + 5007 : ["メモちょう", "メモちょう", ""], + 5008 : ["かぎばこ", "かぎばこ", ""], + 5009 : ["とりのえさ", "とりのえさ", ""], + 5010 : ["スプロケット", "スプロケット", ""], + 5011 : ["サラダ", "サラダ", ""], + 5012 : [lDaisyGardens+"のかぎ", lDaisyGardens+"のかぎ", ""], + 5013 : [lSellbotHQ+' 設計図', lSellbotHQ+' 設計図', ''], + 5014 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5015 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5016 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + 5017 : [lSellbotHQ+'のメモ', lSellbotHQ+'のメモ', ''], + + # The Brrrgh quests + 3001 : ["サッカーボール", "サッカーボール", ""], + 3002 : ["そり", "そり", ""], + 3003 : ["こおり", "こおり", ""], + 3004 : ["ラブレター", "ラブレター", ""], + 3005 : ["ダックスフント", "ダックスフント", ""], + 3006 : ["こんやくゆびわ", "こんやくゆびわ", ""], + 3007 : ["いわしのひげ", "いわしのひげ", ""], + 3008 : ["ちんせいざい", "ちんせいざい", ""], + 3009 : ["ぬけおちた歯", "ぬけおちた歯", ""], + 3010 : ["きんの歯", "きんの歯", ""], + 3011 : ["まつぼっくりパン", "まつぼっくりパン", ""], + 3012 : ["でこぼこチーズ", "でこぼこチーズ", ""], + 3013 : ["ふつうのスプーン", "ふつうのスプーン", ""], + 3014 : ["しゃべるカエル", "しゃべるカエル", ""], + 3015 : ["アイスクリーム", "アイスクリーム", ""], + 3016 : ["カツラのこな", "カツラのこな", ""], + 3017 : ["アヒルのにんぎょう", "アヒルのにんぎょう", ""], + 3018 : ["もこもこサイコロ", "もこもこサイコロ", ""], + 3019 : ["マイクロフォン", "マイクロフォン", ""], + 3020 : ["キーボード", "キーボード", ""], + 3021 : ["あつぞこのくつ", "あつぞこのくつ", ""], + 3022 : ["キャビア", "キャビア", ""], + 3023 : ["メイクのこな", "メイクのこな", ""], + 3024 : ["毛糸", "毛糸", "" ], + 3025 : ["あみ針", "あみ針", ""], + 3026 : ["アリバイ", "アリバイ", ""], + 3027 : ["気温センサー", "気温センサー", ""], + + #Dreamland Quests + 6001 : ["マネーボット本部プラン", "マネーボット本部プラン", ""], + 6002 : ["ロッド", "ロッド", ""], + 6003 : ["自動車のベルト", "自動車のベルト", ""], + 6004 : ["ペンチ", "ペンチ", ""], + 6005 : ["読書ランプ", "読書ランプ", ""], + 6006 : ["シタール", "シタール", ""], + 6007 : ["せいひょうき", "せいひょうき", ""], + 6008 : ["しまうまのざぶとん", "しまうまのざぶとん", ""], + 6009 : ["ひゃくにちそう", "ひゃくにちそう", ""], + 6010 : ["ザイデコのレコード", "ザイデコのレコード", ""], + 6011 : ["ズッキーニ", "ズッキーニ", ""], + 6012 : ["ズート・スーツ", "ズート・スーツ", ""], + + #Dreamland+1 quests + 7001 : ["プレーンベッド", "プレーンベッド", ""], + 7002 : ["ファンシーベッド", "ファンシーベッド", ""], + 7003 : ["青いベッドカバー", "青いベッドカバー", ""], + 7004 : ["ペーズリーのベッドカバー", "ペーズリーのベッドカバー", ""], + 7005 : ["まくら", "まくら", ""], + 7006 : ["かたいまくら", "かたいまくら", ""], + 7007 : ["パジャマ", "パジャマ", ""], + 7008 : ["足つきパジャマ", "足つきパジャマ", ""], + 7009 : ["赤の足つきパジャマ", "赤の足つきパジャマ", ""], + 7010 : ["ピンクの足つきパジャマ", "ピンクの足つきパジャマ", ""], + 7011 : ["カリフラワーサンゴ", "カリフラワーサンゴ", ""], + 7012 : ["ねばねばコンブ", "ねばねばコンブ", ""], + 7013 : ["ごますり棒", "ごますり棒", ""], + 7014 : ["しわのばしクリーム", "しわのばしクリーム", ""], + } +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "どのエリアでも" + +QuestsTailorFillin = "ようふく屋" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "ようふく屋" +QuestsTailorLocationNameFillin = "どんなエリアでも" +QuestsTailorQuestSCString = "ようふく屋に行かなくちゃ" + +QuestMovieQuestChoiceCancel = "トゥーンタスクが必要ならまた後で来てね!じゃあね!" +QuestMovieTrackChoiceCancel = "決められるようになったらまた来てね!バイバイ!" +QuestMovieQuestChoice = "トゥーンタスクを選んでね。" +QuestMovieTrackChoice = "トラックを選んだかい?選べないならまた後でおいで!" + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "準備は整った。\aさあ、選びたいトラックがわかるまで世界中を旅してみよう。\aキミにとって最後のトラックになるから、良いものを選んでね。\aトラックを選んだらまたここに戻っておいで。!", + INCOMPLETE_PROGRESS : "良いものを選んだほうがいいよ。", + INCOMPLETE_WRONG_NPC : "良いものを選んだほうがいいよ。", + COMPLETE : "いい選択だ!", + LEAVING : "新しいスキルをマスターしたらまたおいで。幸運を祈ってるよ!", + } + +QuestDialog_3225 = { + QUEST : "ああ、_avName_!\aこのサラダを_toNpcName_にデリバリーしなくちゃいけないのに、配達人がコグ達のせいで逃げ帰ってしまったんだ。\aキミにデリバリーをお願いしてもいいかな?助かるよ!_where_" + } + +QuestDialog_2910 = { + QUEST : "ずいぶん早かったね。\aバネの件、本当によくやった。\a最後のアイテムはカウンターウェイトだ。\a_toNpcName_のところに寄って、持って帰れるものを持って帰ってきてくれ。_where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aボスボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくボスボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aロウボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくロウボットを退治できたね。さ、トゥーンHQに行っててごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aマネーボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくマネーボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "今のキミならもっと難しいものにチャレンジできるだろう。\aセルボットを3体退治してきてくれ。", + INCOMPLETE_PROGRESS : "" + Cogs + "達はトンネルの向こうのストリートあたりにいるよ。", + INCOMPLETE_WRONG_NPC : "うまくセルボットを退治できたね。さ、トゥーンHQに行ってごほうびをもらってこよう!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "そろそろ新しいギャグが必要ってとこかな?\aフリッピーなら手伝ってくれるかもしれないよ。_where_" }, + 165 : {QUEST : "持ってるギャグの練習が必要みたいだね。\aコグにギャグをくらわせるたびにキミの経験値はあがるんだ。\a十分に経験をつんだらもっといいギャグを使えるようになるよ。\aまずはコグを4体やっつけて、ギャグの練習をしよう。"}, + 166 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のボスボットを倒してみよう。"}, + 167 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のロウボットを倒してみよう。"}, + 168 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のセルボットを倒してみよう。"}, + 169 : {QUEST : "うまくコグを倒すことができたね。\aところで、コグにはロウボット・マネーボット・セルボット・ボスボットの4種類があるって知ってた?\aコグの種類は色と名前のラベルでわかるよ。\aまずは練習に4体のマネーボットを倒してみよう。"}, + 170 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + 171 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + 172 : {QUEST : "よくやったね、これでキミも4種類のコグとその違いがわかったはずだ。\aさて、キミもそろそろ3つめのギャグトラックの練習を始められそうだね。\a_toNpcName_なら次のギャグトラックを選ぶにあたって良いアドバイスをしてくれるよ。_where_" }, + + 175 : {GREETING : "", + QUEST : "キミのトゥーンの特別なおうちがあるの、知ってた?\a牛のクララベルは電話のショッピングカタログで、キミのおうちをかざる家具を売ってるよ。\aスピードチャットのセリフや服、他にもたくさんの楽しいアイテムを買えるよ。\aクララベルに最初のカタログを送るように言っておくね。\a毎週、新しいアイテムのカタログが届くよ。\aおうちへ帰って、クララベルに電話してみよう!", + INCOMPLETE_PROGRESS : "おうちへ帰って、クララベルに電話してね。", + COMPLETE : "お買い物、楽しかったでしょ!\aわたしもちょうど、自分のおうちの模様替えをしたところなんだ。\aトゥーンタスクをこなして、もっとごほうびをもらおう!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "「なげる」と「みずでっぽう」もおもしろいけど、レベルの高いコグと戦うにはギャグがもっと必要だね。\a他のトゥーンと組んでコンビアタックをすればダメージも増大するよ。\aいろんなギャグのコンビネーションを試してみて、何が一番効果的か見てみるといい。\a次のトラックでは「サウンド」と「トゥーンナップ」から選んでみたらどうかな。\a「サウンド」は1回でその場にいる全てのコグにダメージを与えられるし、「トゥーンアップ」はバトル中に他のトゥーンを回復してあげられるよ。\aキミの決心がついたら、またここに戻ってきてね。", + INCOMPLETE_PROGRESS : "ずいぶん早かったね!どれにするか決めたかい?", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。そのギャグを使えるようになるには練習が必要だ。\a練習が出来るようになるのはトゥーンタスクをいくつかこなしてから。\aタスクひとつでギャグアタックのアニメーション1コマが与えられる。\a15個すべてをそろえたら、新しいギャグが使えるようになる『最後のギャグ練習』のタスクがもらえるよ。\aトゥーンガイドで進行ぐあいをチェックしよう。", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "街でもっと動けるようになりたかったら、_toNpcName_に会うといい。_where_" }, + 1040 : { QUEST : "街でもっと動けるようになりたかったら、_toNpcName_に会うといい。_where_" }, + 1041 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだ友達やトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1042 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだともだちやトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1043 : { QUEST : "おや、お客かい?\aトゥーンタウンでの移動にはワープホールというものが便利だよ。\aそれを使えば、ともだちリストで選んだともだちやトゥーンガイドの地図で選んだエリアにワープできるんだ。\aそうだ、ぼくの友達を助けてくれたらキミのトゥーンタウンセントラルへのワープアクセスを可能にしてあげるよ。\aルーピー・ストリートでコグ達がトラブルを起こしてるみたい。_toNpcName_に会ってきてくれ。_where_" }, + 1044 : { QUEST : "来てくれてありがとな、実は今助けが必要なんだ。\a見ての通り、ウチの店には客がいない。\a実は秘伝のレシピが盗まれてしまって、おかげでそれからお客が来なくなちゃったんだ。\a最後に見たのは、ぼくのビルがコグ達に乗っ取られる直前。\aお願いだ、ぼくの秘伝のレシピを取り返してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "レシピは見つかったかい?" }, + 1045 : { QUEST : "ああ、ありがとう!\aきっと近いうちに全部取り返してレストランを再開するよ。\aそうそう、キミにメモがあるんだった…ワープアクセスだっけ。\a『友達を助けてくれてありがとう、これをトゥーンHQに持って行ってくれ』だって。\a本当に助かったよ、じゃあね!", + LEAVING : "", + COMPLETE : "ふむ、なるほど、キミはルーピー・ストリートの住民の手助けをしてくれたようだな。\aトゥーンタウンセントラルへのワープアクセスが欲しいとの事だが、宜しい。\aアクセスを与えよう。\aこれからはトゥーンタウンのほとんどどこからでもプレイグラウンドにワープできるようになる。\a地図をひらいてトゥーンタウンセントラルをクリックするだけで移動できるようになるぞ。" }, + 1046 : { QUEST : "ここのところ、ファニマニ銀行はマネーボット達に悩まされている。\aもし良かったらそこに行って助けてやってくれないか。_where_" }, + 1047 : { QUEST : "マネーボット達が、銀行にこっそり入ってうちの機械を盗んでいるんです。\aお願い、ATMを5台、マネーボット達から取り返してきて。\a何回も往復しなくて済むように、一度に全部持ってきてくれるといいわ", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだATMを探してるの?" }, + 1048 : { QUEST : "マシンを探してくれたのね、ありがとう!\aえーと…少しキズがついてるみたい。\aねえ、これを\"くすぐりマシーン\"にいる_toNpcName_のところに持っていってもらえるかな。彼女なら直せるかもしれない。", + LEAVING : "", }, + 1049 : { QUEST : "こわれたATM?\aマネーボットにやられた?\aま、とにかく見てみようか。\aなるほど、ギアが取れてる…しかもウチには在庫がないわ。\a大きなコグ達が持ってるコグ・ギアなら使えるかもしれない。\aそうね、レベル3のコグ・ギアをマシン1台につき2つ使うから、全部で10コ取ってきて。\a一度に全部持ってきてね、マシンを一気に直してあげるわ", + LEAVING : "", + INCOMPLETE_PROGRESS : "マシンを直すには10コのギアが必要よ、忘れないようにね!" }, + 1053 : { QUEST : "いっちょあがり!\aさ、マシンも全部直ったわよ。お代はけっこう。\aこいつらをファニマニバンクに持ってった時に私からもヨロシク言っといて", + LEAVING : "", + COMPLETE : "ATMが全部直ったって?\aありがとう。ええと、なにかお礼にあげるものがあるといいんだけど…" }, + 1054 : { QUEST : "_toNpcName_がピエロ車のヘルプを欲しがっているみたいだよ。_where_" }, + 1055 : { QUEST : "オウノウ!ボクの愛しいピエロ車のタイヤがみつからないよ!\aねえキミ、ちょっと助けてくれないかい?\aもしかしたらルーピー・ボブがトゥーンタウンセントラルの遊び場の池に投げちゃったかも…\aどこかのドックに行ってつり上げてみてくれないかな?", + GREETING : "イヤッホーイ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "タイヤを4つともつり上げるのはやっぱりむずかしい?" }, + 1056 : { QUEST : "ウワオ、ワンダフル!おかげでこのピエロ車もまた路上で走ることができるよ。\aん?この辺に空気ポンプがあると思ったんだけど…\a_toNpcName_が借りていったのかな?\a彼が持ってるかどうか、聞いてきてくれないかい?_where_", + LEAVING : "" }, + 1057 : { QUEST : "よう!\aえ、空気ポンプ?\aそうだな、じゃあキミがこのへんのストリートを荒らしてるハイレベルのコグを倒してくれたら渡すってのはどうだい?", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミの実力はそんなものなのか?" }, + 1058 : { QUEST : "やったな!キミなら出来ると思ったよ。\aほら、約束の空気ポンプだ。_toNpcName_も喜ぶだろう。", + LEAVING : "", + GREETING : "", + COMPLETE : "ヤッホウ!旅立ちの時は来た!\aああそうだ、助けてくれてほんとうにありがとう。\aこれをあげるよ。" }, + 1059 : { QUEST : "_toNpcName_の店だが、在庫が足りなくて困っているらしい。助けてやってみるのも悪くないんじゃないか?_where_" }, + 1060 : { QUEST : "来てくれてありがとう!\aあのコグのやつらめ、私のインクを盗んでいってるので店の在庫がすごく減ってきてしまっているんだ。\a池のタコからスミをとってきてくれないか?タコをつる時は池の近くのドックに立つといい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "釣りは苦手なのかい?" }, + 1061 : { QUEST : "おお、スミだ、助かった!\aそうだ、これからもインクに困ることがないようにするためにもカリカリン達を退治してくれないか?\aトゥーンタウンセントラルでカリカリンを6体退治してくれたらお礼をあげるよ。", + LEAVING : "", + COMPLETE : "ありがとう!これはお礼だよ。", + INCOMPLETE_PROGRESS : "カリカリンがまだいるのを見たんだが…" }, + 1062 : { QUEST : "おお、スミだ、助かった!\aそうだ、これからもインクに困ることがないようにするためにもガッツキー達を退治してくれないか?\aトゥーンタウンセントラルでガッツキーを6体退治してくれたらお礼をあげるよ。", + LEAVING : "", + COMPLETE : "ありがとう!これはお礼だよ。", + INCOMPLETE_PROGRESS : "ガッツキーがまだいるのを見たんだが…" }, + 900 : { QUEST : "_toNpcName_が配達物で困ってるって聞いたよ。_where_" }, + 1063 : { QUEST : "やあ、来てくれてありがとう。\a実は手元にあった包みがコグに盗まれてしまったんだ。\a取り返してくれると助かるよ。\aやつはレベル3みたいだったから、包みが見つかるまでレベル3のコグを退治するといいだろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1067 : { QUEST : "ああ、これだ!ありがとう!\aっと、送り先が汚れちゃったな…\aナントカ博士あてだって事しか読み取れない。\a_toNpcName_のことかな、配達してくれないか?_where_", + LEAVING : "" }, + 1068 : { QUEST : "私に配達が来るはずはないんだが…ドクター・ハッピーの間違いじゃないかい?\a私の助手があちらに行く予定だから、その時にでも聞いてもらおう。\aところで、このストリートにいるコグを少々退治してもらって良いかな?\aトゥーンタウンセントラルにいるコグ10体ほどでも助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "助手がまだ戻ってきていないんだ" }, + 1069 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、マネーボットが助手の手から包みを奪ってしまったらしい…\a取り戻してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1070 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、セルボットが助手の手から包みを奪ってしまったらしい…\a申し訳ないが、そのセルボットを見つけて包みを取り返してくれないか。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1071 : { QUEST : "ドクター・ハッピーにも配達がある予定ではなかったそうだ。\aしかも、ボスボットが助手の手から包みを奪ってしまったらしい…\a取り戻してくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか見つけられないみたいだね。" }, + 1072 : { QUEST : "取り戻せたんだね、すばらしい!\aその包みはもしかしたら_toNpcName_に持って行くべきものかもしれないね。_where_", + LEAVING : "" }, + 1073 : { QUEST : "ああ、包みを持ってきてくれてありがとう。\aん?包みの数は2つだったはずだが…_toNpcName_がもうひとつを持っているか聞いてきてくれると助かるよ。", + INCOMPLETE : "もうひとつの包み、見つかったかい?", + LEAVING : "" }, + 1074 : { QUEST : "もうひとつあるはずだって?それもコグに盗まれたのかなぁ。\aコグを退治しつづけたらそのうち見つかるだろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "もうひとつの包みもなかなか見つけられないみたいだね。" }, + 1075 : { QUEST : "やっぱりふたつ目もあったんだなぁ。\aさ、急いでこれを_toNpcName_に届けてくれ、私の謝罪の言葉もそえて", + COMPLETE : "やっと届いた!\aキミは心優しいトゥーンだね。これをあげよう。きっと役に立つよ。", + LEAVING : "" }, + 1076 : { QUEST : "14金魚の店で何かあったようだ。\a_toNpcName_も助けが必要だろう。_where_" }, + 1077 : { QUEST : "ああ、来てくれてありがとう。コグが私の金魚をみんな盗んで行ってしまったんだ!\aあいつら、盗んだ金魚を売りさばいて小銭稼ぎするつもりなんだ。\aあの5匹の金魚は、この小さな店で私と何年も共に過ごした相棒達なんだ。\a取り返してくれると本当に、本当に助かるよ。\a1体のコグが5匹とも運んでいるだろう。\a金魚を見つけるまでコグをやっつけてくれ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "金魚を取り返してくれ~" }, + 1078 : { QUEST : "金魚が見つかったのか!?\aえ?これは…レシート?\aはぁ…コグのやつらめ。\aでもこれじゃ何が書いてあるかわからんな。_toNpcName_のところにこれを持って行って、読めるかどうか聞いてきてくれ。_where_", + INCOMPLETE : "_toNpcName_はなんて言ってた?", + LEAVING : "" }, + 1079 : { QUEST : "まずそのレシートを見せてくれ。\aふむ…なるほど、ここには1匹の金魚がオベッカーに買い取られた、とあるな。\a他の4匹のことは書いてないから、このオベッカーを見つけて問いただした方がよかろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "私に出来ることはもうないよ。金魚を探しに行った方がいいんじゃないかね?" }, + 1092 : { QUEST : "まずそのレシートを見せてくれ。\aふむ…なるほど、ここには1匹の金魚がチョロマカシーに買い取られた、とあるな。\a他の4匹のことは書いてないから、このチョロマカシーを見つけて問いただした方がよかろう。", + LEAVING : "", + INCOMPLETE_PROGRESS : "私に出来ることはもうないよ。金魚を探しに行った方がいいんじゃないかね?" }, + 1080 : { QUEST : "ああ、神様!オスカーを見つけてくれたんだね、彼は私の一番のお気に入りなんだ。\aえ、なんだって、オスカー?…そうなのか?\aオスカーが言うには、他の4匹遊び場の池に逃げたらしい。\a池に行って彼らを集めてくれないか?釣り上げるだけでいいはずだ。", + LEAVING : "", + COMPLETE : "ああ、なんと幸せなことなんだ!大事な大事な、小さな友達が帰ってきた!\aキミにはきちんとお礼をするべきだな。", + INCOMPLETE_PROGRESS : "金魚が見つからないのかい?" }, + 1081 : { QUEST : "どうやら_toNpcName_が面倒なシチュエーションにあるようだ。ねえ、良かったら彼女に手を貸してやってくれないか。_where_" }, + 1082 : { QUEST : "何が起こったって、速乾性のノリをこぼしちゃってこんなことになっちゃったのよ。\aこのシチュエーションをなんとかする手があったら聞いてみたいわよ。\a…あ、そうだわ、手伝ってくれない?\aセルボットを何体か倒してオイルを持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1083 : { QUEST : "うーん、オイルもあんまり役にたたないみたい…\a一体どうすればいいのかしら。\aあとは…そうね、アレならなんとかなるかも。\aロウボットを何体か倒して、油を持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1084 : { QUEST : "ああ、これもだめだわ!んもう!\a油に賭けてたのに…\aん?賭け?ねえ、思いついたわ。\aマネーボットを何体か倒して、お水を持ってきてちょうだい。", + LEAVING : "", + GREETING : "", + COMPLETE : "やったわ、速乾ノリ地獄から脱出よ!\aありがとう、これはお礼よ。\nこれはバトル中にもうちょっと笑う時間が長く…\aあらやだ!またくっついちゃったわ!", + INCOMPLETE_PROGRESS : "このべたべた地獄から助けて~" }, + 1085 : { QUEST : "_toNpcName_がコグについての研究をしているわ。\a助けたいなら彼に話してみるべきね。_where_" }, + 1086 : { QUEST : "いかにも、私はコグについての研究をしている。\a彼らがどのような作りになっているかを知りたいんだ。\aキミにコグのギアをいくつか集めてもらえると助かるんだがね。\a研究には大きめのギアが必要だから、レベル2以上で頼むよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "なかなか数が集まらないみたいだね?" }, + 1089 : { QUEST : "よし、さっそく見てみよう。ふむ、これは素晴らしいサンプルだ!\aふむふむ…\aさ、レポートができたぞ。これをトゥーンHQに大至急持って行ってくれ。", + INCOMPLETE : "私のレポートをトゥーンHQに持って行ってくれたかね?", + COMPLETE : "ありがとう、_avName_。ここからは我々に任せてくれ。", + LEAVING : "" }, + 1090 : { QUEST : "キミにとって役立ちそうな情報を_toNpcName_が持っているよ。_where_" }, + 1091 : { QUEST : "トゥーンHQは、いわゆるコグ・レーダーのようなものを開発しているらしい。\aそれを使えばコグを見つけるのも簡単になるってわけだ。\aキミのトゥーンガイドの中のコグページが重要なんだ。\aコグ達をある程度やっつけたら、コグのシグナルが入って来るようになる。 どこにいるかわかるようになるんだ。\aがんばってコグ達の退治を続けていれば、出来るようになるぞ。", + COMPLETE : "よくやった!これはきっとキミの役に立つだろう。", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "次に覚えたいギャグを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "コグのやつらめ、またやってくれたようだ。\a_toNpcName_がアイテムの盗難を報告してきたんだ、解決してやってくれないか。_where_" }, + 2202 : { QUEST : "こんにちは、_avName_。キミが来てくれて本当によかったよ!\a怖いツラしたセコビッチがついさっき、ボクのインナーチューブを盗んで走り去っていってしまったんだ。\aもしかしたらあいつらの悪巧みに使われてしまうかもしれない。\aお願いだ、どうか探し出して持ってきてほしい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "ボクのインナーチューブ、見つかった?", + COMPLETE : "見つけてくれたんだね、ボクのインナーチューブ!ほんとに腕がいいんだねぇ。はい、これはボクのキモチだよ。", + }, + 2203 : { QUEST : "コグ達が銀行で大暴れしてるらしい。\aキャプテン・カールがそこにいるから、何か手助けできるか聞いてあげて。_where_" }, + 2204 : { QUEST : "おお、ちょうどいいところに来てくれたな!\aあんのにっくきクズ鉄のガラクタ達め、我輩の片めがねを壊しおった!おかげで小銭が数えられなくなってしまったわい。\aそこでだ。キミにはドクター・キーケグにこの処方箋を持っていって、新しい片めがねを持ってきてもらいたいのだ。_where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "おお、これはこれは。\aこの処方箋の対処をしたいのはやまやまなんだが、コグ達が私のモノを盗んでいっていてな…\aオベッカーからめがねのフレームを取ってこられるのであれば、なんとかしてやれるかもしれないが。", + LEAVING : "", + INCOMPLETE_PROGRESS : "オベッカーのフレームがなけりゃ片めがねも作れんよ。", + }, + 2206: { QUEST : "持ってきたか、すばらしい!\aちょっとそこで待っていなさい…\aさ、処方箋通りに作ったぞ。さっそくキャプテン・カールに持って行ってやりなさい。_where_", + GREETING : "", + LEAVING : "", + COMPLETE : "ほほう!\aこれはまことにありがたい。\aこれは我輩の気持ちだ、受け取ってくれい。", + }, + 2207 : { QUEST : "バーバラ・シェルの店にコグが出た!\aさくっと行ってさくっとやっつけてやってくれ。_where_" }, + 2208 : { QUEST : "あらン、今ちょうど逃げてっちゃったのよね。\aウラギリンが入ってきて、あたしの白いカツラを盗んで逃げていったの。\aボスのためだとか、法的な先例だとかなんとか言ってたわよ。\a取り返してきてくれたらすっごぉく嬉しい!", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "まだ見つかってないのぉ?\aええとね、あいつは背が高くて頭がとんがってたわよ。", + COMPLETE : "見つかったのぉ!?\aああん、バーバラうれしい!\aこんなものじゃお礼にならないかもしれないけど…", + }, + 2209 : { QUEST : "メルヴィルが大事な航海にむけて準備中だって。\a忙しいだろうから、行って手伝ってあげて。_where_"}, + 2210 : { QUEST : "今、まさにネコの手も借りたい状態だよ。\aわしはコグ達が一体どこから来ているのかを調べるようトゥーンHQに依頼されたんだ\a船に必要なものがまだまだあるんだが、何しろジェリービーンが足らなくてな。\aアリスのところから砂利を持ってきてくれんか?もちろんタダじゃもらえんだろうが…頼む。_where_", + GREETING : "やあ、_avName_", + LEAVING : "", + }, + 2211 : { QUEST : "メルヴィルが砂利を欲しがってるって?\aったく、前回の分もまだ支払ってないのにさ。\aこのストリートでガミガミーナを5体やっつけたら砂利をあげるよ。", + INCOMPLETE_PROGRESS : "5体よ、ご・た・い。", + GREETING : "いらっしゃい!", + LEAVING : "", + }, + 2212 : { QUEST : "よし、追い払ってくれたね。\aほれ、この砂利をあのケチくさメルヴィルに持っていってやんな。_where_", + GREETING : "あれまあ、誰かと思ったら…", + LEAVING : "", + }, + 2213 : { QUEST : "思ったとおりだ、すばらしい。彼女も聞く耳を持ってるじゃないか。\aさて、次は海図だ。これはアートのところだな。\aここでもわしの評判はよくないだろうから、またタダ働きするはめになると思うが…。_where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "海図?ああ、持ってるよ。\aメルヴィルなんかのために働いてやるってんなら、その報酬に海図を渡してあげる。\a僕はいま、星空をたよりに航海できるよう天体観測儀を作っているところなんだ。\aコグ・ギアが3つ必要だから、それを取ってきてくれ。", + INCOMPLETE_PROGRESS: "3つのコグ・ギア、集められそうかい?", + GREETING : "いらっしゃい!", + LEAVING : "幸運を祈ってるよ!", + }, + 2215 : { QUEST : "うん!これならきっといいものが出来るよ。\aはい、これが海図だよ。メルヴィルに渡してやってくれ。_where_", + GREETING : "", + LEAVING : "", + COMPLETE : "よーし、これで準備は整ったぞ。旅立ちの時は来た!\aキミが経験者だったらこの旅に連れていくところだがな。かわりにこれを受け取ってくれ。", + }, + 901 : { QUEST : "エイハブが何か手助けを必要としているらしいぞ、キミにガッツがあるなら行ってみるがいい。_where_", + }, + 2902 : { QUEST : "おまえが例の新入りか?\aよろしい、ちょうど人手が必要だったところだ。\a私は今、コグ達をかくらんするのに使う巨大なカニのはりぼてを造っているんだ。\aクロヴィスが必要なんだが、ひとつクラッガーから借りてきてくれんか。_where_", + }, + 2903 : { QUEST : "いらっしゃい!\aああ、ボクもエイハブのカニのはりぼての事は聞いてるよ。\aでも、うちにあクロヴィスで一番いいものはちょっと汚れちゃってるんだ。\aまずこれをクリーニングに出してくれないかな、頼むよ。_where_", + LEAVING : "ありがとうね!" + }, + 2904 : { QUEST : "キミがクラッガーのおつかいのコだね。\aそれならすぐにきれいにクリーニングできるよ。\aちょっと待ってて…\aさ、できたよ。ぴっかぴかだ!\aエイハブによろしくね。_where_", + }, + 2905 : { QUEST : "これこれ、私が欲しかったのはまさにこれだよ。\aせっかく手伝ってくれてるんだ、次は大きな時計のバネを取ってきてもらおうか。\aフックの元へ行って、バネを持っているかどうか聞いてきてくれ。_where_", + }, + 2906 : { QUEST : "大きなバネねぇ。\aすまないが、うちにあるのは小さいものばかりだよ。\aでも、もしかしたら水鉄砲の引き金のバネをいくつか使って作れるかもしれないな。\a水鉄砲を3つ持ってきてくれ、なんとかやってみよう。", + }, + 2907 : { QUEST : "どれどれ…\aうーん、最高だね!これはいい!\aもう自分でもびっくりのいい出来だよ。\aはい、エイハブさまご注文の大きなバネだ。_where_", + LEAVING : "行ってらっしゃい!", + }, + 2911 : { QUEST : "手伝いたいのはやまやまだけど…\aこの辺はもう安全じゃなくなってしまったんだよ、_avName_。\aとりあえずマネーボットを何体か退治してくれないかな、話はそのあとだ。", + INCOMPLETE_PROGRESS : "まだまだ通りは危険だよ…", + }, + 2916 : { QUEST : "ああ、エイハブにあげられるような錘があるよ。\aでも、まずはセルボットを何体かやっつけた方が帰り道も安全なんじゃないかな。", + INCOMPLETE_PROGRESS : "まだまだセルボットがいるよ、倒したほうがいいよ。", + }, + 2921 : { QUEST : "まぁ、ひとつぐらいなら錘をあげてもいいかな。\aまわりでうろうろしているボスボット共をなんとかしてくれたら助かるけど。\a6体やっつけたらまた来てくれないかな。", + INCOMPLETE_PROGRESS : "うーん、まだ身の危険を感じるよ…", + }, + 2925 : { QUEST : "終わったかい?\aそうだね、もう危険は感じないかもしれない。\aこの錘をエイハブに持っていってくれ。_where_" + }, + 2926 : {QUEST : "よーし、航海に必要なものはすべてそろったぞ。\aうまくいくかな?……\aうむむ、問題がひとつだけあるな…\a船にエネルギーがないらしい。コグビルのせいでソーラーパネルに日光が来ていないんだな。\aちょっと奪回してきてくれんか?", + INCOMPLETE_PROGRESS : "まだエネルギーが来ないな。あっちの建物はどうだろう?", + COMPLETE : "おお!たいしたコグ退治の腕前だ。これはほんのお礼だよ。", + }, + 3200 : { QUEST : "たった今、_toNpcName_から連絡があったんだ。\aなんだかえらく大変そうなんだ、手伝ってあげてくれないか?_where_" }, + 3201 : { QUEST : "来てくれてありがとう!\aこのシルクネクタイを_toNpcName_に届けてくれないかな。\aお願いしてもいいかい?_where_" }, + 3203 : { QUEST : "おや、これは私が以前オーダーしたネクタイだね。ありがとう。\a私が仕立てたばっかりのあのしましまスーツにぴったりなん…\aあれ、スーツがないぞ。\aまさか!コグに盗まれてしまったのか!?\aやられた!\aスーツが見つかるまでコグを退治してくれ、頼んだぞ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スーツは見つかったか?コグ達が盗んだに違いないんだ!", + COMPLETE : "ばんざい!大事な新品のスーツが戻ってきた!\aやっぱりあいつらの仕業だったんだな。これは取り返してきてくれたお礼だよ。", + }, + + 3204 : { QUEST : "_toNpcName_がついさっき電話してきて、盗難に遭ったそうだ。\aちょっと様子を見てきてやってくれ。" }, + 3205 : { QUEST : "こんにちは、_avName_。助けにきてくれたのですね。\a今ちょうどガッツキーを店から追い出したところなんです…ふぅ。怖かったですよ。\aでも、混乱の中ハサミを無くしてしまいました。さっきのガッツキーが持って行ったに違いありません!\aお願いです、どうかハサミを取り返してください!", + LEAVING : "", + INCOMPLETE_PROGRESS : "ハサミはまだ見つかっていないのですか?", + COMPLETE : "ああ、ハサミだ!見つかったんだね、ありがとう!どうかこのお礼を受け取ってください!", + }, + + 3206 : { QUEST : "_toNpcName_がコグに悩まされてるみたいです。\a良かったら手助けしてあげてください。_where_" }, + 3207 : { QUEST : "やあ_avName_!来てくれてありがとな!\aニマイジタンの一団がやってきて、オレの担当カウンターからはがきを一山持ってっちまったんだよ。\aあいつらを全部やっつけて、はがきを取り返してきてくれ!", + INCOMPLETE_PROGRESS : "まだまだ取り残しがあるよ!", + COMPLETE : "サンキュー!これで時間通りにはがきを配達できるよ。これはお礼だ、受け取ってくれ。", + }, + + 3208 : { QUEST : "最近、デイジー・ガーデンでブアイソンについての苦情が出ているんだ。\a住民の安全のためにも、10体のブアイソンを倒してきてくれ" }, + 3209 : { QUEST : "ブアイソン退治、感謝しているよ。\aだが、今度はツーハーン達が暴れ始めてしまった。\aデイジー・ガーデンで10体のツーハーンを倒してきてくれれば、報酬を出す。頼んだよ。" }, + + 3247 : { QUEST : "最近、デイジー・ガーデンでガッツキーについての苦情が出ているんだ。\a住民の安全のためにも、20体のガッツキーを倒してきてくれ" }, + + + 3210 : { QUEST : "ありゃりゃ、メイプルストリートにあるフラワー・スプラッシュが品切れになってしまったみたいだぞ。\a自分で集めたフラワー・スプラッシュを持っていってやってくれないか。\a自分のもくろくにフラワー・スプラッシュが10本入っていることを確認してから行くんだぞ。", + LEAVING: "", + INCOMPLETE_PROGRESS : "それじゃ足りないわ、必要なのは10本のフラワー・スプラッシュよ。" }, + 3211 : { QUEST : "ああよかった、10本あれば今日はなんとかなるわ!ありがとう!\aでも、コグが外をうろついているのはやっぱり怖いわ。\aコグを20体ばかりやっつけてくれないかしら?", + INCOMPLETE_PROGRESS : "まだまだコグ達がいるわよ、やっつけて!お願い!", + COMPLETE : "よかったわ、これで少し安心よ。あなたへのお礼は…", + }, + + 3212 : { QUEST : "_toNpcName_が何か大事なものを無くしちゃったらしいの。\a探すのを手伝ってあげてくれない?_where_" }, + 3213 : { QUEST : "いらっしゃい、_avName_。\aペンが1本見当たらないんだが、もしかしたらコグが持って行ってしまったのではと思ってるんだ。\a探すのを手伝ってくれるかい?\aコグ達を倒してペンを見つけ出しておくれ。", + INCOMPLETE_PROGRESS : "ペンは見つかったかね?" }, + 3214 : { QUEST : "そうそう、それが私のペンだよ!感謝する!\aただね、キミがいない間にインクつぼも無いことに気付いて…\aコグ達をやっつけていけばきっと見つかるだろう。", + INCOMPLETE_PROGRESS : "インクつぼ、見つからないね…" }, + 3215 : { QUEST : "おお、これでペンとインクつぼが揃った!\aだがねキミ、実は…\a今度はメモ帳がなくなってしまったんだ。まったく、油断も隙もないね。\aメモ帳をコグ達から取り返してきてくれたら、お礼を差し上げるよ。", + INCOMPLETE_PROGRESS : "メモ帳は見つかった?" }, + 3216 : { QUEST : "ああ、メモ帳が見つかったんだね、やった!さて、キミへのお礼は…\aあれ?見当たらないぞ?\aオフィスのロックボックスの中に入れておいたのに、ロックボックスそのものがない!\aまったく信じられないな…コグのやつら、キミへのお礼まで盗んでいってしまったようだ。\aすまないが、ロックボックスを取り返してきてくれ。\aお礼はその後差し上げるよ。", + INCOMPLETE_PROGRESS : "ロックボックスを探し出してくれよ、キミへのお礼の品も入ってるんだから!", + COMPLETE : "やっと見つかったね、お疲れさま!このロックボックスにはキミにあげる新しいギャグバッグが入っているんだよ。", + }, + + 3217 : { QUEST : "我々はセルボットの構造について研究している者である。\aだがまだまだ詳細を知らない部分もある。\aそこでキミにはタッシャーナのスプロケットを採取してきてもらいたい!\aコグが爆発している時に入手できるようだ、任せたぞ。" }, + 3218 : { QUEST : "よくやった。今度はオオゲーサのスプロケットを比較材料として取ってきてくれ。\a先ほどより入手が難しいだろうから、根性で入手してきてくれ" }, + 3219 : { QUEST : "すばらしい!よし、あともうひとつ集めるだけだ。\a今度はクロマクールのスプロケットである。\aこやつらを見つけるにはセルボットビルの中を捜すのが良かろう。\aひとつ持って帰ってきてくれればキミにお礼を差し上げよう。" }, + + 3244 : { QUEST : "我々はロウボットの構造について研究している者である。\aだがまだまだ詳細を知らない部分もある。\aそこでキミにはツケコミンのスプロケットを採取してきてもらいたい!\aコグが爆発している時に入手できるようだ、任せたぞ。" }, + 3245 : { QUEST : "よくやった。今度はオオゲーサのスプロケットを比較材料として取ってきてくれ。\a先ほどより入手が難しいだろうから、根性で入手してきてくれ" }, + 3246 : { QUEST : "すばらしい!よし、あともうひとつ集めるだけだ。\a今度はドクター・トラブルのスプロケットである。\aこやつらを見つけるにはセルボットビルの中を捜すのが良かろう。\aひとつ持って帰ってきてくれればキミにお礼を差し上げよう。" }, + + 3220 : { QUEST : "ついさっき耳に入ったんだが、_toNpcName_がキミを探しているらしい。\a何事か聞いてみるとよかろう。_where_" }, + 3221 : { QUEST : "あら、_avName_!やっといたわ!\aあたし、あなたがみずでっぽう攻撃 のプロだって聞いたわ。\aデイジー・ガーデンのトゥーン達に自己防衛のいい例を見せてあげたいの。\aみずでっぽう攻撃でコグをたくさんやっつけてくれないかしら。\aそうすれば住民達もきっとみずでっぽうで反撃し始めるわ!\a20体のコグをやっつけてきたらお礼をあげるわ、がんばってね。" }, + + 3222 : { QUEST : "キミのトゥーン魂を見せてほしい。\aコグに占領された建物を何棟か奪回してくれたら、クエストを3つ持てるようにしてやろう。\aまず、どれか2つのコグビルを奪い返してくれ。\a友達に協力してもらうのもよかろう。"}, + 3223 : { QUEST : "よくやった。\aさあ、あと2棟だ。\a2階以上あるものを奪回してきなさい" }, + 3224 : { QUEST : "すばらしい!\aさあ、あと2棟だ。\a3階以上あるものを奪回してきなさい。\a終わったら戻っておいで、報酬が待っているよ。", + COMPLETE : "最後までやりとげたな、_avName_!\aキミのトゥーン魂、しかと見せてもらったぞ。", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_がヘルプを探しているらしいよ。\aキミならきっと彼女を助けられるだろう。_where_" }, + 3235 : { QUEST : "そう、これが私のオーダーしたサラダよ。\a持ってきてくれてありがとう。\aきっとコグ達が_toNpcName_の配達人を怯えさせてしまったのね。\aねえ、私達のために外を徘徊してるコグを倒していただけない?\aデイジー・ガーデンにいるコグを10体倒したら、_toNpcName_に報告してくださいな。", + INCOMPLETE_PROGRESS : "ボクのためにコグを倒しているんだって?\aありがたいよ、どんどん退治してくれ!", + COMPLETE : "コグ達を倒してくれてありがとう!\aこれで以前のように配達することが出来るよ。\aこれはお礼の気持ちさ。", + INCOMPLETE_WRONG_NPC : "コグ退治のことは_toNpcName_に伝えてあげてね。_where_" }, + + 3236 : { QUEST : "ここらでロウボットを倒そうにも、数が多すぎて!\aキミも手伝ってくれない?\aロウボットビルを3棟奪回してくれればいい" }, + 3237 : { QUEST : "よくやった!\aだが今度はセルボットが増えてきてしまったようだ。\aセルボットビルを3棟奪回して戻ってきてくれれば、お礼ははずむよ。" }, + + 3238 : { QUEST : "なんてこった!デイジー・ガーデンのカギが\"オマカセンヌ\"にぬすまれたらしい。\a取り返してきてくれるかい?\aそうそう、オマカセンヌはセルボットビルの中にしかいないから、覚えておいてね。" }, + 3239 : { QUEST : "あれれ、間違ったカギを持ってきてしまったみたいだよ。\aひつようなのはデイジー・ガーデンのカギ。\aがんばって探してね!持っているのは\"オマカセンヌ\"だよ!" }, + + 3242 : { QUEST : "なんてこった!デイジー・ガーデンのカギがホウノトリにぬすまれたらしい。\a取り返してきてくれるかい?\aそうそう、ホウノトリはロウボットビルの中にしかいないから、覚えておいてね。" }, + 3243 : { QUEST : "あれれ、間違ったカギを持ってきてしまったみたいだよ。\aひつようなのはデイジー・ガーデンのカギ。\aがんばって探してね!持っているのはホウノトリだよ!" }, + + 3240 : { QUEST : "_toNpcName_が言ってたんだけど、ホウノトリが彼の店の鳥のエサを盗んでいってしまったんだって。\aサッサの商品を持ってるホウノトリが見つかるまで、ホウノトリ達をやっつけてくれないかな。\aホウノトリがいるのはロウボットビルの中だけだよ。_where_", + COMPLETE : "ああ、僕の商品を見つけてくれたんだね、ありがとう!\aお礼にこれをあげるね。", + INCOMPLETE_WRONG_NPC : "鳥のエサを取り返すことができたね、ごくろうさま!\aさあ、今度はそれを_toNpcName_に持っていってあげてね。_where_", + }, + + 3241 : { QUEST : "この辺のコグビルは背が高くなりすぎて、なんだかこわいよ。\aそこでキミには背の高いビルを何棟かくずすことをお願いしたいんだ。\a3階建てのビルを5棟とりもどしたらごほうびをあげるよ。", + }, + + 3250 : { QUEST : "オークストリートにいるたんていのメイが「セルボット本部」の報告書の話を聞いたみたいなんだ。\aちょっと彼女のところに行って、彼女を手伝ってもらえないかな?", + }, + 3251 : { QUEST : "最近、この近所の様子がなんだか変なのよ!\aセルボット達がやたらにうろついてると思わない?\aこのストリートの先にコグたちが本部を作ったって話も聞いたし…\a悪いんだけど、ストリートの先に行って、何かヒントをつかんでくれないかしら。\aコグ本部にいるセルボットを見つけて、5体やっつけたら報告してちょうだい。", + }, + 3252 : { QUEST : "それじゃあ、教えてもらおうかしら!\aえっ、何ですって!!\aセルボット本部??\nそんなぁ!\n何とかしなくちゃ!\aミスター・ジャッジに急いで知らせないと!彼ならどうすればよいかを教えてくれるはず!\aすぐに行って、伝えてあげて!彼はこの先をちょっと行ったところにいるわよ!", + }, + 3253 : { QUEST : "ん?何かお困りかな?\nごらんの通り、私は忙しい…\aは?\nコグ本部?\aお?\nそれはありえん。\aきっとキミが間違っているのだ。\nまったくもってばかげた話だ。\aほ?\n私と議論してもムダだ。\aそれならば、証拠を見せてもらわなくては。\aもしセルボット達が本当にコグ本部を作っているのならば、設計図をもっているはずだ!\aなにしろコグたちはペーパーワークが好きだからな!\aその本部とやらにいるコグ達をやっつけて、設計図を持ってきたら、議論してやってもいいぞ。", + }, + 3254 : { QUEST : "また、キミか!\n設計図? 持ってきたのか?\aどれどれ…\nん? 工場?\aセルボット達を作るところに違いない… む? これは何だ?\aほう、思ったとおりだ。\a彼らはセルボットのコグ本部を建設中なのだ。\aこれは良くないな! 電話をしないと! あー忙しい、忙しい。それじゃあな!\aお? そうそう、たんていのメイのところに設計図を持っていってくれ。\a彼女ならなんらかのヒントがわかるかもしれん。", + COMPLETE : "ミスター・ジャッジは何だって?\a思ったとおりだったわけね。 どうしましょう? 設計図を見せて。\aふーん…\nコグを大量生産する工場を作っているって訳ね。\aとても危険そうね。\nゲラゲラメーターが増えるまでは立ち寄らないほうがよさそうね。\aゲラゲラポイントがたくさん増えるまでは、いろいろとコグ本部についていろいろ調べたほうが良さそうね。\a本当にありがとう。これはごほうびよ!", + }, + + + 3255 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3256 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3257 : { QUEST : "_toNpcName_がセルボット本部の調査をしているよ!\a行って手伝ってあげたら?_where_" }, + 3258 : { QUEST : "コグたちが本部の中で何をたくらんでるかわからなくて、みんな混乱しているんだ。\a彼らのところに行って直接、何か情報を取ってきてほしいんだ。\aもし、コグ本部の中でセルボット達からメモを4つ取ってきてくれれば、少しは混乱がなくなるんじゃないかな?\a最初のメモを手に入れたら、ここに持ってきてくれないかな?そうしたら何かわかるかもしれないし。", + }, + 3259 : { QUEST : "すばらしい!\nさてさて、メモには何て書いてあるかな…\a「セルボット達へ:」a\「私はセルボットのタワーの一番上のオフィスにいて、キミたちコグのレベルを『格上げ』する業務をしている。」\a「キミたちが『メリット』をたくさん集めたら、ロビーにあるエレベーターに乗って私に会いに着なさい。」\a「休み時間は終わりだ。さっさと仕事に戻りなさい!\a「セルボット\nコグゼキュティブ\nより」\aははーん。フリッピーにこれを見せないと!\a今すぐ、僕から送っておくから、キミは2番目のメモを取りにいってね。", + }, + 3260 : { QUEST : "ああ、よかった。\n戻ってきてくれて!\aえーと、次のメモには…\a\「セルボット達へ:」\a「セルボットのタワーはこの度、トゥーンを寄せ付けないために新しいセキュリティーシステムを導入したわ。」\a「セルボットタワーで捕まったトゥーン達は尋問のためにとらわれるようになったのよ。」\a「続きはロビーで会って、話し合いましょう!」\a「オマカセンヌより」\a非常に興味深い…\nさっそく、フリッピーに送らないと…\aキミは3番目のメモをよろしく頼むよ!", + }, + 3261 : { QUEST : "_avName_!\nメモは、っと。\a\「セルボット達へ:」\a「どうやらトゥーン達がセルボットタワーに入る方法を見つけたようだ。」\a「今晩の打ち合わせのときに詳しく話すことにするよ。」\a「ツーハーンより」\aほほう、色々わかってきたぞ!\aもう1つメモがあれば、十分な情報が集められるからよろしく頼むよ!", + COMPLETE : "キミならやれると信じてたよ!\nふむふむ、\a「セルボット達へ:」\a「昨日、ビッグスマイルとランチオンミーティングをしたよ。」\a「彼は、最近とても忙しいコグゼキュティブについて話をしてくれたんだけど、」\a「どうやら一生懸命働いて『格上げ』されたコグとしか会ってくれないらしいよ。」\a「あ、そうそう。\nオオゲーサと日曜日にゴルフに行くんだ。」\a「タッシャーナより」\aうーん、_avName_。これは非常に助かる情報だったね。\aこれはキミへのごほうびだよ。", + }, + + 3262 : { QUEST : "_toNpcName_ がセルボット本部の工場について何か新しい情報をつかんだみたいだよ。\a彼に会って、確認するといいよ!_where_" }, + 3263 : { GREETING : "やあ!", + QUEST : "わたしがコーチのヨーガだ。\aいいか、よーく聞いてくれ!\nセルボット達がとてつもなく大きな工場を完成させたみたいだ。1日24時間、セルボットを作り続けるつもりらしい。\aキミのトゥーン仲間たちと一緒に工場をたたきつぶしにいってくれ!\aセルボット本部の中で、工場へのトンネルを探し出し、工場のエレベーターに乗ってくれ!\aギャグやゲラゲラメーターを一杯にしてから、強いトゥーンたちと一緒に立ち向かおう!\a中にいる工場長を倒せば、セルボットの生産がきっと遅れるはずだ!\aどうだ?キミにできるかな?", + LEAVING : "それじゃあな!", + COMPLETE : "おー!やるじゃないか!\aちゃんとコグのパーツの一部を見つけたようだな。\aコグを作る途中で出来たものに違いない!\a持ち運びも出来る大きさだから、時間があるときに集めてみると良いかもな。\aひょっとしたら、コグのスーツのパーツ全てが集まるかもしれないしな。何かに使えるかもしれんし…", + }, + + 4001 : {GREETING : "", + QUEST : "次に覚えたいギャグトラックを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "次に覚えたいギャグトラックを選ぼう。\aじっくり考えてから決めてね。\a決める準備ができたらここに戻っておいで。", + INCOMPLETE_PROGRESS : "選択する前に、もう一度よく考えてね。", + INCOMPLETE_WRONG_NPC : "選択する前に、もう一度よく考えてね。", + COMPLETE : "いい選択だ。", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "トムの研究の手助けをしてあげるのもいいんじゃないかな?_where_", + }, + 4201 : { GREETING: "やあっ!", + QUEST : "最近、楽器がよく盗まれるようになってしまっていてね。\a店オーナーの仲間とちょっとした調査をしているんだ。\aどういう手口で行われているか解明できるかもしれない。\aティナの店に寄って、コンチェルティナのもくろくのことを聞いてくれないかな。_where_", + }, + 4202 : { QUEST : "うん、トムとは今朝会ったわ。\aもくろくならここにあるわよ。\aすぐに彼に持っていってあげてね。_where_" + }, + 4203 : { QUEST : "おお、さすがだね!これでひとつそろった。\a次はユキのところへ行って、彼女のもくろくを持ってきてくれ。_where_", + }, + 4204 : { QUEST : "ああ、もくろくね!\aすっかり忘れちゃってたわ。\aキミが10体のコグを倒している間に作り終えることができると思うんだけど…\aそれをやったらまた来てみて", + INCOMPLETE_PROGRESS : "31、32…ああっ!\aわからなくなっちゃったじゃない!", + GREETING : "", + }, + 4205 : { QUEST : "ああ、いたいた。\a時間かかっちゃってごめんね。\aこれをトムに持っていってあげてね。私からもよろしくって。_where_", + }, + 4206 : { QUEST : "ふむ、これはおもしろい。\aこれでなんとかなるかも…\aよし、あとはバイオレットのもくろくだけだ。_where_", + }, + 4207 : { QUEST : "え、もくろく?\aもくろくなんて、もくろく帳がなくちゃ作れないわよ。\aクレフのところに行って、もくろく帳を持っているか聞いてみて。_where_", + INCOMPLETE_PROGRESS : "もくろく帳はあったの?", + }, + 4208 : { QUEST : "もくろく帳?おれ様の店ならあるに決まってるよ~。\aでもタダじゃやれねえな~。\aどうだ、クリームパイまるごと1個と交換ってのは。", + GREETING : "いよう!", + LEAVING : "去る者は追わずってやつさ。", + INCOMPLETE_PROGRESS : "ひときれじゃあダメだ。\aおれ様はハラがへってんのよ。まるごとじゃなきゃあダメだ。", + }, + 4209 : { GREETING : "", + QUEST : "うぅ~ん!\aやっぱりうまいね~!\aほれ、約束のもくろく帳だ、バイオレットに持ってってやんな。_where_", + }, + 4210 : { GREETING : "", + QUEST : "ありがとう、これで何とかなりそうよ。\aさて、と…「バイオリン: 2」\aはい、できたわよ!", + COMPLETE : "おお、ありがとう、_avName_。\aこれで盗人たちをなんとかふんじばれるかも知れない。\aどうだ、もっと協力しないか?", + }, + + 4211 : { QUEST : "そういえばドクター・アイタタがひっきりなしに電話をかけてきてるんだ。いったいどうしたのか、聞いてきてくれないか?_where_", + }, + 4212 : { QUEST : "おやおや、トゥーンHQもやっと誰かよこしてくれたんだね。\aうちにはもう何日も患者が来ていないんだ。\aスウジスキーのやつらがうようよしているせいさ。\aこの辺の住民にもカネ至上主義の影響を与えかねんしな。\aスウジスキーを10体やっつけくれないか、患者が戻ってくるか試してみたいんだよ。", + INCOMPLETE_PROGRESS : "まだまだ患者が来ないなぁ。がんばってくれ!", + }, + 4213 : { QUEST : "もしかしたら原因はスウジスキーじゃないのかもしれんな。\aマネーボット全体がいかんのだろう。\aマネーボットを20体おっぱらってくれたら誰かが診療に来るかも…", + INCOMPLETE_PROGRESS : "20体はたしかに多いが、きっとなにかの結果を出してくれるだろう。", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "まったくもって理解できん!やっぱり患者が1人も来ないんだよ。\aこうなったら大元をたたくしかないのか…\aためしにマネーボットビルを倒してみてくれ。\aそれでダメならあきらめるさ。", + INCOMPLETE_PROGRESS : "マネーボットビルひと棟でいいんだ!たのむ!", + COMPLETE : "やっぱりだめだ、1人も来ない…\aだがな、ひとつ気がついたことがある。\a私の診療所には、コグが来る前にも患者が来たことがなかったのさ。\aだがあいつらをおっぱらってくれて感謝しているよ。\aお礼にこれをあげよう、きっと役にたつよ。" + }, + + 4215 : { QUEST : "どうやらアンナが困っているらしい。\a行って助けてあげてくれないか。_where_", + }, + 4216 : { QUEST : "さっそく来てくれてうれしいわ!\aどうやらコグが私のお客のクルーズチケットを盗んでいってしまったみたいなの。\aオオゲーサがチケットをたんまり持ってここから出て行くのをユキが見たらしいわ。\aモックンのアラスカ旅行のチケットを取り戻してくれないかしら", + INCOMPLETE_PROGRESS : "そろそろオオゲーサが出てくるんじゃないかしら…", + }, + 4217 : { QUEST : "まあ、みつけたのね!\aじゃあ、ついでにこれをモックンのところに届けてくれないかしら?_where_", + }, + 4218 : { QUEST : "うわぁ、ぼくのアラスカ旅行のチケットじゃないか!\aやった~!\aもうコグのやつらにはうんざりだよ。\aああそうだ、またアンナがキミの手をかりたいみたいだけど。_where_", + }, + 4219 : { QUEST : "オオゲーサのやつ!\a今度はタバサのジャズフェスティバルチケットを取り返してほしいの。\aさっきと同じようにさくっと、ね!", + INCOMPLETE_PROGRESS : "オオゲーサならそこら辺にいるはずよ。", + }, + 4220 : { QUEST : "やったわね!\aこれタバサのところにもっていってくれない?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "気をつけてね~", + QUEST : "イエーイ!やった!\aさーてジャズフェスタで踊りまくってやるわよ。\aそうそう、キミもどこかに行っちゃう前にまたアンナのとこに行ったほうがいいと思うよ。_where_", + }, + 4222 : { QUEST : "お願い、これで最後だから!\aこんどはバリーの歌唱コンクールのチケットを取り返してきてくれないかしら", + INCOMPLETE_PROGRESS : "お願いよ、_avName_。\aバリーも困ってるのよ~", + }, + 4223 : { QUEST : "よかった、これでバリーも安心して行けるわ!_where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "いやぁ、助かったよ!\aありがとう!\a今年こそ私達が1位をとれる気がするよ。\aアンナからの伝言だ、お礼をしたいので来てくれ、と。_where_\aそれではね、ありがと~う!", + COMPLETE : "助けてくれて本当にありがとう、_avName_。\aあなたはトゥーンタウンの宝だわ。\aそうそう、お宝といえば…", + }, + + 902 : { QUEST : "レオに会いに行ってあげて。\aメッセージの配達をしてくれる人を探しているみたいなの。_where_", + }, + 4903 : { QUEST : "いよっす!\aおれっちのカスタネットがなんだかくもっちまって、今夜のショーがうまく行くか心配なんだ。\aカルロスのところに持っていって、みがいてくれるかどうか聞いてみてくんないかな。_where_", + }, + 4904 : { QUEST : "エエ、みがけると思いマスよ。\aでもソレにはイカからとった青いインクが必要なんデス", + GREETING : "コニチハー!", + LEAVING : "サヨナーラ!", + INCOMPLETE_PROGRESS : "イカは、つり場があるトコロならドコでも見つけられマスよ。", + }, + 4905 : { QUEST : "エエ、これデス!\aサテ、あとはみがくジカンが必要なダケ…\aアナタ、どうせ待つならコグビルをヒトツたおしてきてみてはどーデスカ?", + GREETING : "コニチハー!", + LEAVING : "サヨナーラ!", + INCOMPLETE_PROGRESS : "ウーン、まだマダ…", + }, + 4906 : { QUEST : "ヨーシ!\aレオのカスタネット、みがきオワリましたヨ。_where_", + }, + 4907 : { GREETING : "", + QUEST : "やったー!\aいいカンジじゃーん!\aあとは…'ビート・クリスマス' の歌詞カードをヘイディからもらってこなきゃいけないんだ。_where_", + }, + 4908 : { QUEST: "こんにちは!\aうーん、その歌詞カードはウチにはないなぁ。\aちょっと時間をくれたら暗記してある歌詞を書き出せるんだけど。\aぼくがそれをやっている間、コグビルを2棟取り返すっていうのはどうだい?", + }, + 4909 : { QUEST : "うぅ~ん…\a思ったよりハッキリ覚えてないみたいだ。\a3階建てのコグビルを倒している間になんとかなりそうだと思うんだけど…", + }, + 4910 : { QUEST : "できたよ!\a待たせてしまってごめんね。\aこれをレオに持っていってあげて。_where_", + GREETING : "", + COMPLETE : "やったー!いいカンジじゃーん!\aこれでおれっちのショーもいいカンジじゃーん!\aそうそう、アンタにはお礼をしなきゃな" + }, + 5247 : { QUEST : "ここらも最近荒れてきてしまって…\aキミもいくつか新しい「トリック」を覚えたほうがいいよ。\a_toNpcName_が私にいろいろ教えてくれたんだけど、キミも会ってみるといいよ。_where_" }, + 5248 : { GREETING : "ああ、こんにちは…", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミへの宿題は、ちょっと難しかったかな?", + QUEST : "ああ、いらっしゃい、みならいくん。\aパイゲームのことなら私にまかせたまえ。\aだが訓練を始める前に、ちょっとキミの力をみせてくれ。\a外にいる一番おおきいコグを10体やっつけてみなさい" }, + 5249 : { GREETING: "ふむ…", + QUEST : "すばらしい。\a今度は釣り人としてのスキルを見せてくれ。\aきのう、池に3つのもこもこサイコロを落としておいた。\aそれらを釣って私に持ってきなさい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "釣竿の扱いはまだまだらしいな" }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いロウボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いボスボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いマネーボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "おお。このダイス、私の水牛車のミラーにつけたらかっこいいのであろうな。\aさあ、今度はキミの敵の見分けがつくかを見てみよう。\a一番背の高いセルボットビルを2棟取り返してみせなさい。", + INCOMPLETE_PROGRESS : "建物は苦手か?", }, + 5200 : { QUEST : "コグめ、また悪さをしておる。\a_toNpcName_がコグに何かを盗まれたらしい。行って手助けしてやってくれ。_where_" }, + 5201 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきヘッドハンターの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5261 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきアイソマンの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5262 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきカネモッチンの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5263 : { GREETING: "", + QUEST : "やあ、_avName_。来てくれてありがとう。\aついさっきドクター・トラブルの一団が押し入って来て、僕のサッカーボールを持っていってしまったんだ。\aカットバックをしろだとか何とか言いながら。\aボールを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "サッカーボールは見つかったかい?", + COMPLETE : "やったあ、見つかったんだね!ありがとう、これはお礼だよ。", + }, + 5202 : { QUEST : "最近、ブルブルタウンが見たこともないような屈強なコグ達に襲われているんだ。\a危ないから、キミももっとギャグを持ち歩いていたほうがいいよ。\a_toNpcName_がもっとギャグを入れることができるふくろを持っているらしいから、相談してみたらどうかな。_where_" }, + 5203 : { GREETING: "ああん?キミ、私のソリチームにいたっけ?", + QUEST : "なんだって、ふくろが欲しい?\aうーん、この辺にあったような気がするんだが…トボガンに入ってるかもしれないなあ。\aだが、ずっと前のレース以降あのトボガンを見てないしなあ。\aもしかしたらコグにとられたのかもしれん。", + LEAVING : "私のトボガンを見かけたかい?", + INCOMPLETE_PROGRESS : "えーっと、キミは誰だったっけ?すまんね、事故にあって以来ちょっと記憶が怪しくてな~" }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、これが私のトボガンだよ。だがふくろが見当たらんなあ。\aバンピー・ノギンがチームメイトだったんだが、彼が持ってるのかもな。_where_" }, + 5205 : { GREETING : "あああ、頭が~!", + LEAVING : "", + QUEST : "ああ?テッドだって?ふくろ?\aあー、もしかしたらそんなのが私のトボガンチームにいたかも知れんな。\aうう、なんとも頭が痛くてろくに考え事もできんわい。\a凍った池から氷を少し釣りあげてくれんか?", + INCOMPLETE_PROGRESS : "あうう、頭が痛い~!氷をくれ~!", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、ずいぶん楽になったわい。\aえーと、それでキミはテッドのふくろを探しているんだったっけな。\aあれはレースでの事故の時にシミアン・サムの頭にかぶさってたぞ。_where_" }, + 5207 : { GREETING : "Eeeep!", + LEAVING : "", + QUEST : "ふくろ?バンピー?\aワタシはビルが怖いのヨ!ビルを崩したらふくろあげるヨ!", + INCOMPLETE_PROGRESS : "もっともっと!ビル倒して、怖いのヨ!", + COMPLETE : "オーウ!スバラシーイ!" }, + 5208 : { GREETING : "", + LEAVING : "キャー!!!", + QUEST : "オーウ!スバラシーイ!\aふくろはスキー・クリニックにあるヨ" }, + 5209 : { GREETING : "ちーす!", + LEAVING : "じゃあな!", + QUEST : "あのサム・シミアンもおもしろいキャラだよなあ。\aあんたもあいつぐらいワイルドだってんならふくろをあげてやってもいいぜ。\aコグを何体かやっつけてきな。じゃあな、行ってらっしゃい!", + INCOMPLETE_PROGRESS : "そんなんで満足しちゃいけねえよ!もっと倒してこなきゃダメだ。", + COMPLETE : "ふぅん、なかなか悪くねえな。かなりの数のコグをやっつけたじゃねえか。\aほい、やくそくのふくろだぜ" }, + + 5210 : { QUEST : "_toNpcName_がこの辺のだれかに恋してるらしいんだ。\a彼女の恋の手助けをしてやったら、お礼がもらえるかもな。_where_" }, + 5211 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にクチバシのあるコグが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5264 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にヒレのあるコグが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5265 : { GREETING: "しくしく…", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にオマカセンヌが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "しくしく…", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5266 : { GREETING: "Boo hoo.", + QUEST : "昨日の夜、愛するワンちゃんにラブレターを書いていたの。\aでも持っていこうとした時にデッパラーダが入ってきて持っていっちゃったのよ!\aお願い、取り返してきて!", + LEAVING : "Boo hoo.", + INCOMPLETE_PROGRESS : "手紙を取り返してきて、お願い" }, + 5212 : { QUEST : "手紙をみつけてくれたのね、ありがとう!\aそれでね、あのう、ここらで一番ハンサムなワンちゃんに届けてくれないかしら…お願い!", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "手紙を届けてくれなかったのね…", + }, + 5213 : { GREETING : "あら、こんにちは。", + QUEST : "今手紙を持ってこられても困るんだよね。\aボクのワンちゃん達はみんなどこかに連れ去られてしまったから。\a全員連れて帰ってきてくれたら受け取ってあげてもいいけど", + LEAVING : "", + INCOMPLETE_PROGRESS : "ああ、かわいそうなワンちゃんたち!" }, + 5214 : { GREETING : "", + LEAVING : "ごきげんよう!", + QUEST : "ボクの粒ぞろいのワンちゃん達を連れ戻してくれてありがとう。\aそれではその手紙を見てみようか。なになに…\nおやおや、ボクに思いを寄せる人がまたひとり…か。\aキミにはボクのともだち、カールの所へ行ってもらいたい。\aキミもきっと彼を気に入るさ。_where_" }, + 5215 : { GREETING : "エッヘッヘ…", + LEAVING : "また来なさい、ね。", + INCOMPLETE_PROGRESS : "デカいやつらがまだいるから、そいつらをおっぱらったらまた来なさい。", + QUEST : "いったい誰がキミをここに送り込んだのだね?タカビシャな人間はどうも気にくわんのだよ。\aだがコグのほうがよっぽど気にくわん。\a特にデカいコグたちを追っ払ってきたら手伝ってやろう。" }, + 5216 : { QUEST : "やくそく通りキミを手伝ってやろう。\aこのゆびわを彼女のもとに持っていってやれ。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まーだゆびわを持っていってないのかい?", + COMPLETE : "きゃああ!すごいわすごいわ!ありがとう!\aそうだわ、お礼にこれをあなたにあげたいの。もらってくれる?", + }, + 5217 : { QUEST : "_toNpcName_がなんだか困っているらしいわよ。_where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "この辺にもっとオマカセンヌがいると思うんだが…", + QUEST : "たすけてくれぇー!もう耐えられない!\aオマカセンヌのせいでもう気が狂いそうだ!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだまだだよ、さっきこのへんで1体見たんだ!", + QUEST : "ありがとう。でも、こんどはデッパラーダ達が問題なのよ。\aお願い、なんとかして!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだまだだよ、さっきこのへんで1体見たんだ!", + QUEST : "わかったぞ、問題はシャークロンなんだ!絶対そうだ!\a手伝ってくれるんだろう?" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "もしかするともしかするんだけど、問題はコグじゃないのかも…。\aファニーにすっきりするポーションを作ってきてもらえないかな、それで解決するかも。_where_" }, + 5222 : { LEAVING : "", + QUEST : "まーったくもう、ハリーもすぐああなるんだから。\aアレを直すやつを作ってあげるから、ちょっと待っててね。\aあら、イワシのヒゲがないわ…\a池に行って少し釣ってきてくれない?", + INCOMPLETE_PROGRESS : "イワシのヒゲ、見つかった?", }, + 5223 : { QUEST : "よーっし、これでOKよ。\aはい、これをハリーに持っていってあげて。彼の興奮状態をすぐに落ち着かせるから", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "ほらほら、早くそのポーションをハリーに届けてやんな。", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "頼むよ、早くホウノトリたちをおっぱらってくれー!", + QUEST : "ああ、やっと戻ってきた!\aそのポーションをおくれ、早く!\aゴクゴクゴク…\aうええっ、まずい!\aでも…不思議だなぁ、なんだか落ち着いた気がするよ。これでやっとちゃんと考えられる気がする。\aそう、問題のコグはホウノトリだったんだ!", + COMPLETE : "やったぁ!これでリラックスできるよ!\aえーと、たくさんがんばってくれたお礼に…これこれ、これをあげるよ。" }, + 5225 : { QUEST : "あのだいこんパン事件以来、グランピー・フィルは_toNpcName_に対して怒ってるんだ。\aマモルだったら仲直りさせてあげられるかも…_where_" }, + 5226 : { QUEST : "キミも、グランピー・フィルが私に対して怒ってるのを聞いたみたいだね。\a私はただ、あのだいこんパンの件で私は悪気はなかったんだけど…\aキミなら彼をはげましてあげられるかもしれない。\aフィルはマネーボットがきらいで、特にやつらのビルは耐えられないらしい。\aマネーボットビルをいくつか倒したら少しはよくなるかも", + LEAVING : "", + INCOMPLETE_PROGRESS : "あと何棟かやってみてくれないか?", }, + 5227 : { QUEST : "すごいぞ!さ、フィルに報告してやってくれ。_where_" }, + 5228 : { QUEST : "ああ、そうなのかい。\aマモルのやつ、そんな事で許されると思ってんのかい。\aあいつのだいこんパンで歯が欠けたんだぞ!\aドクター・シンキクサーイだったら治せるかもしれんから、持っていってみてくれ。", + GREETING : "むにゃむにゃむにゃ", + LEAVING : "もごもごもご", + INCOMPLETE_PROGRESS : "またあんたかい。わしの歯を治してくれるんじゃなかったのかい。", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのマネーボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのセルボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのロウボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯はまだ治している最中だ。もうちょっと時間がかかってしまうな。", + QUEST : "そうだね、その歯はちょっとひどい状態だね。\aなんとかしてみせたいが、時間が必要だ。\a待ってる間にそこらのボスボットをおいはらってくれないかい?\a患者がこわがって診療に来れなくなってしまったんだよ。" }, + 5230 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ドロビッグが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5270 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグチーズが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5271 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグスマイルが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい??" }, + 5272 : { GREETING: "", + QUEST : "やあ、戻って来たね!\aあの古い歯を治すのはあきらめて、代わりに新しい金の歯を作ってみたよ。\aだが、ビッグホワイトが来て盗んで行ってしまったんだ。\a急いで追いかければ捕まえられるかもしれない。", + LEAVING : "", + INCOMPLETE_PROGRESS : "歯は見つかったかい?" }, + 5231 : { QUEST : "そうそう、それだよ!\aさっそくフィルに持っていってあげてくれないか。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "早くフィルに歯を持っていってあげてくれ。", + }, + 5232 : { QUEST : "おう、ありがとな。\aんむぐぐ…\aよし、これでどうだ!\aそうだな、うん。マモルを許してやってもいいかな。", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "ああ、よかった。\aフィルがずっと怒りっぱなしでいるとは思わなかったけど…安心したよ。\a友情の証として、フィルにまつぼっくりパンを焼いたんだ。持っていってくれるかい?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "急いでおくれよ、まつぼっくりパンはあったかいほうがおいしいんだ。", + COMPLETE : "んん、これは何だね?わしにかい?\aむしゃむしゃ…\aあいたたた!は、歯が!まったく、あのマモルめが!\aいやしかし、キミのせいではないしな。ほれ、これはおだちんだよ。", + }, + 903 : { QUEST : "キミもそろそろブリザード・ウィザードの_toNpcName_の最後のテストに挑んでもいいかも知れないね。_where_", }, + 5234 : { GREETING: "", + QUEST : "おっ、戻ってきたね。\a始める前にまずは食事といこうか。\aスープに合うでこぼこのチーズを持ってきてくれ。\aでこぼこチーズはコグのビッグチーズからしか得られないよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "でこぼこチーズが必要だよ。" }, + 5278 : { GREETING: "", + QUEST : "おっ、戻ってきたね。\a始める前にまずは食事といこうか。\aスープに合うキャビアを持ってきてくれ。\aキャビアはコグのビッグスマイルからしか得られないよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "キャビアが必要だよ。" }, + 5235 : { GREETING: "", + QUEST : "ふつうの人間はふつうの食器を使うべきだ。\aコグが私の「ふつうのスプーン」を取っていってしまったからスープも食べられない。\aスプーンを取り返してきてくれ、持っているのはたぶんドロビッグだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スプーンがなきゃ食べられないよ。" }, + 5279 : { GREETING: "", + QUEST : "ふつうの人間はふつうの食器を使うべきだ。\aコグが私の「ふつうのスプーン」を取っていってしまったからスープも食べられない。\aスプーンを取り返してきてくれ、持っているのはたぶんビッグホワイトだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "スプーンがなきゃ食べられないよ。" }, + 5236 : { GREETING: "", + QUEST : "ありがとう、ありがとう。\aズズッ、ゴクン…\aああ、うまい。さて、こんどはしゃべるカエルを池から獲ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "しゃべるカエルは見つかったかい?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだデザートを持ってきていないね。", + QUEST : "ほほう、それはたしかにしゃべるカエルだね。こっちにおくれ。\aなんだって、カエルくん?\aうんうん。\aふむふむ。\aカエルは言う、「デザートが必要だ」と。\aというわけで_toNpcName_のところからアイスクリームをいくつか持ってきてくれ。\aカエルくんはアズキ味をご所望らしい…_where_", }, + 5238 : { GREETING: "", + QUEST : "なるほど、魔術師がキミをここに送ったのだな。申し訳ないが、アズキ味は売り切れてしまった。\aというより、コグの一団が持っていってしまったのだ。\aビッグスマイルのためだとか何とか言っていたな。\a取り返してきてくれると助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "アイスクリームは見つかったかい?" }, + 5280 : { GREETING: "", + QUEST : "なるほど、魔術師がキミをここに送ったのだな。申し訳ないが、アズキ味は売り切れてしまった。\aというより、コグの一団が持っていってしまったのだ。\aビッグチーズのためだとか何とか言っていたな。\a取り返してきてくれると助かるよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "アイスクリームは見つかったかい?" }, + 5239 : { QUEST : "取り返してきてくれてありがとう!\aはい、これがミニおじさんの分だよ。", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "とけてしまう前にそれをミニおじさんに持っていったほうがいいよ。", }, + 5240 : { GREETING: "", + QUEST : "よろしい、よろしい。ほれ、カエルくんも。\aペロリ、ペロリ。\aよし、準備もあともう少しで整いそうだ。\a手をかわかす粉を持ってきてくれ。\aビッグホワイト達がカツラのメンテに使っている粉が良いから、それを取ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "粉は見つかったかね?" }, + 5281 : { GREETING: "", + QUEST : "よろしい、よろしい。ほれ、カエルくんも。\aペロリ、ペロリ。\aよし、準備もあともう少しで整いそうだ。\a手をかわかす粉を持ってきてくれ。\aビッグスマイル達が鼻のテカリをかくすために使っている粉が良いから、それを取ってきてくれ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "粉は見つかったかね?" }, + 5241 : { QUEST : "よし。\a前も言ったように、パイを投げるには手を使ってはいけないのだ。\a魂をこめて、念で投げる。\aこれは実に深い。深すぎて考える時間が必要だから、キミはコグビルをいくつか倒してきなさい。\aタスクが終わったらここに戻ってきなさい。", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミのタスクはまだ終わっていないよ。", }, + 5242 : { GREETING: "", + QUEST : "まだ考えがうまくまとまらない状態だが、キミが最終テストにふさわしい事はわかった。\aしゃべるカエルは言う、「ガールフレンドが欲しい」と。\a行ってしゃべるカエルちゃんを探してきなさい。それが最終タスクだ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "しゃべるカエルちゃんはどうしたんだ?", + COMPLETE : "ふぅ!考えすぎてなんだか疲れてしまった。\aほれ、これをごほうびにあげるから私を休ませてくれ" }, + + 5243 : { QUEST : "あせかきピートのせいでここいらがくさくなってきちゃったんだよ。\aおふろに入るよう彼を説得してくれないか?_where_" }, + 5244 : { GREETING: "", + QUEST : "そうだねぇ、ボクは確かによく汗をかくかも。\aでもねぇ、うちのシャワーのこわれたパイプを直さなきゃ体も洗えないよ。\aちっちゃいコグならパイプを直せるギアを持っているかもね。\aガミガミーナからひとつ取ってきてくれない?", + LEAVING : "", + INCOMPLETE_PROGRESS : "ギアは見つかったの?" }, + 5245 : { GREETING: "", + QUEST : "うん、これなら何とかなるかも。\aでも、一人でお風呂に入るのはさみしいよ…\a池にいって、アヒルの人形をひとつ取ってきてくれない?", + LEAVING : "", + INCOMPLETE_PROGRESS : "アヒルは見つかったの?" }, + 5246 : { QUEST : "アヒルちゃんを取ってきてくれてうれしいけど…\aうちのまわりの建物がこわくて落ち着かないよ。\aもう少し建物の数が少なかったら安心できるんだけど…", + LEAVING : "", + COMPLETE : "うん、じゃあお風呂に入ることにするよ。これはキミにあげる。", + INCOMPLETE_PROGRESS : "まだ建物の存在がこわいよ…", }, + 5251 : { QUEST : "ラウンジ・ラッサーが今夜、ショーをやるんだって。\aでもなんだかトラブルがあってこまってるみたい。_where_" }, + 5252 : { GREETING: "", + QUEST : "おおっと、手伝いにきてくれたのかい?\aバンから機材を下ろしていたらコグどもがギアを持っていっちまったんだよ。\a盗まれたマイクを取り返してきてくれないか?", + LEAVING : "", + INCOMPLETE_PROGRESS : "おいおい、マイクなしじゃ歌えないよ。" }, + 5253 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aデッパラーダが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5273 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aオマカセンヌが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5274 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aシャークロンが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5275 : { GREETING: "", + QUEST : "これだよ、俺のマイクは。\a取り返してくれてサンキュー。でも…\aキーボードもとられちまったんだよ、あれも必要なんだ。\aホウノトリが持ってったと思うんだ、頼む!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キーボード、まだ見つかってないのか…" }, + 5254 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグスマイルだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5282 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグチーズだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5283 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはドロビッグだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + 5284 : { GREETING: "", + QUEST : "やったぜ、ありがとな!\aでもなぁ、俺の厚底シューズがなきゃイマイチ決まんないんだよなぁ。\a俺のカンでは、持ってったのはビッグホワイトだな。", + LEAVING : "", + COMPLETE : "イエー!これでステージに立てるぜ!\aブルブルタウンのみんなー、元気かーい!?\a…ん?客がいないぞ?\aしょうがないな、これで客を集めてくれないか?", + INCOMPLETE_PROGRESS : "はだしでステージになんか立てないよ。なぁ?" }, + + 5255 : { QUEST : "あんた、もうちょっとゲラゲラポイントがあってもいいみたいだな。\a_toNpcName_なら安くしてくれるかもしれないぜ。\aちゃんと書き出してもらえよ。_where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "約束は守ってくださいよ。", + QUEST : "ゲラゲラポイントがほしいって?\aウチに来て正解ですよ、お客さん!\aボスボットを何体かたおしてくれたら…\aサービスしますよ。" }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "約束は守ってくださいよ。", + QUEST : "ゲラゲラポイントがほしいって?\aウチに来て正解ですよ、お客さん!\aロウボットを何体かたおしてくれたら…\aサービスしますよ。" }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "え?ロウボットをやっつけるようにお願いしませんでしたか?\aまあいいでしょう、でもこれであなたには私に借りができましたからね。", + INCOMPLETE_PROGRESS : "まだ終わっていませんよ。", + QUEST : "え、終わった?すべてのコグをやっつけた?\a何かの聞き間違いでしょう、私はセルボットを倒してくれと言ったのです。" }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "え?ロウボットをやっつけるようにお願いしませんでしたか?\aまあいいでしょう、でもこれであなたには私に借りができましたからね。", + INCOMPLETE_PROGRESS : "まだ終わっていませんよ。", + QUEST : "え、終わった?すべてのコグをやっつけた?\a何かの聞き間違いでしょう、私はマネーボットを倒してくれと言ったのです。" }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "ゲラゲラポイントs, でもひょっとしたら_toNpcName_が手伝ってくれるかも。\a彼はちょっと気むずかしいところがあるんだけどね。_where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "思ったとおりだ!\aありがとな!ゲラゲラポイントだ!", + INCOMPLETE_PROGRESS : "やあ!\aここでまた何しているんだい。", + QUEST : "ゲラゲラポイントが欲しいって?\aまず最初に悪いロウボット達をやっつけてからにしてくれ。" }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+"が危険なコグ達であふれかえっているんだ。\aもし僕がキミなら、ここではギャグをもっと持ち歩くね。\aもしキミが足を棒にして働くんだったら、_toNpcName_が大きいバッグを作ることができるみたいだよ。_where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "まだロウボット達がそこら中にうようよしている。\aやっつけてくれ!" , + QUEST : "大きいバッグ?\aキミのために一つ作ってあげられるかも。\a毛糸が必要だけど、\a残念ながら、昨日の朝、ロウボット達に盗まれたんだ。" }, + 5305 : { GREETING : "やあ!", + LEAVING : "", + INCOMPLETE_PROGRESS : "コグ達をもっとやっつけて!\aまだ毛糸がたりないよ。", + QUEST : "上質の毛糸だね。\a色はちょっと好みじゃないけど。\aじゃあ、こうしよう。\aキミがもっと強いコグ達をやっつける間に、\a僕がこの毛糸を染めるね。" }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "もっとやっつけてくれないと!", + QUEST : "毛糸が全て染まったよ。でもちょとした問題があるんだ。\aあみ針がどこにも見つからないんだ。\a確か最後に見たのは池だったけなぁ。" }, + 5307 : { GREETING : "", + LEAVING : "本当に助かるよ!", + INCOMPLETE_PROGRESS : "ローマは一日にして編めず" , + QUEST : "確かに僕のあみ針だ。\aあみものをしている間に、コグビルをやっつけてくれないかい?", + COMPLETE : "本当にすごいね、キミは!\aそしてこれもすごいよ…\aキミの新しいバッグだよ!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_が何か問題をかかえてるみたいなんだ。\aちょっと立ち寄って聞いてきてくれない?_where_" }, + 5309 : { GREETING : "来てくれて本当にうれしいよ。", + LEAVING : "", + INCOMPLETE_PROGRESS : "急いで!ストリートにコグがあふれてるんだ!", + QUEST : "ロウボット達が乗っ取りを始めているんだ。\aひょっとしたら僕をサイバンショに連れて行くんじゃないかと心配なんだ。\aあいつらを街から追い出してくれないかい?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "彼らはきっと僕を連れてこうとしているんだ…", + QUEST : "ありがとう。ちょっと気が楽になったよ。\aでもまだ、もうひとつやることがあるんだ。\a_toNpcName_のところに行って、アリバイをゲットしてきてくれないかい?_where_" }, + 5311 : { GREETING : "ヤッター!", + LEAVING : "", + INCOMPLETE_PROGRESS : "キミが見つけないと彼を助けてあげられないよ。", + QUEST : "アリバイ?!それはいいアイデアだね!\aホウノトリがきっともっているはず。" }, + 5312 : { GREETING : "ついに!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "ほっ。気持ちがようやく落ち着いたよ。\aこれがキミへのごほうびだ。", + QUEST : "ほんとうにすばらしい!\a_toNpcName_のところへ急いでいってあげて!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "テティ・テーデンが助けを必要としてるみたい。彼女に手を貸してあげて?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "いらっしゃいませ!\aあら、お客様じゃないのね。\aわかった、恐ろしいコグたちから助けてくれるためにきてくれたのよね。\aもしコグたちからこの街を少し救ってくれたら、わたしが電気の力でキミのために何かしてあげられるんだけどな…。", + INCOMPLETE_PROGRESS : "コグたちをやっつけてくれないと、お手伝いできないのよね。よろしくね!", + COMPLETE : "_avName_、コグたちをやっつけてくれて、ありがとう!\aこれで電気が使えるわ。\aちょっと待ってね。この電気でキミを…。ビビビビビ!\aどう?ちょっとパワーアップした?これからもがんばってね!", + }, + + # Susan Siesta wants to get rich but the コグたち are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "やあ, _avName_, 今すぐには何もないよ。\aあ、ちょっと待って!スーザン・シエスタが助けが欲しいって言ってたよ。なんで、彼女に会いにいかないの?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "まったく!コグたちのせいで、商売があがったりなの。\a_avName_、助けてくれる? \aいくつかのコグビルを取り返してくれたら、ごほうびをあげるわ。", + INCOMPLETE_PROGRESS : "がっかり…。まだコグビルを取り返してないの?", + COMPLETE : "ありがとう!これで商売もうまくいくはず!そんな気がする。\aこれで楽しみな釣りの時間も取れるわ。じゃあ、キミの人生をちょっと豊かにしてあげるね。\aはい、これをどうぞ!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "こんにちは _avName_! ロウフル・リンダ があなたをさがしてたって聞いたよ。.\aちょっと立ち寄って、あいさつしてみなよ。_where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "こんにちは! ワオ、キミに会えてうれしいよ!\a今、るすばんでんわを直している最中なんだけど、部品が足りないんだ。\a3つの棒が必要なんだけど、1つはカッチリンが持っているみたいなんだ。「ロッド」を持ってきてくれる?", + INCOMPLETE_PROGRESS : "まだ、「ロッド」を探しているの?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "これでよし、っと!\a「自動車のベルト」のスペアのもっていたはずなんだけど見つからないなぁ。\aカネモッチンからベルトをひとつ、もってきてくれないかな?ありがとう!", + INCOMPLETE : "うーん。自動車のベルトを持ってきてくれないと、キミを助けられないよ。", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "これこれ!これでチチンプイプイ!ちゃんと動くはず。\aあれ、でも道具がないや。\aセコビッチを倒して、「ペンチ」を持ってきてくれないかな?\aそうしてくれれば、コグたちをやっつける手助けができるんだけどな。よろしくね。", + INCOMPLETE_PROGRESS : "ペンチはまだかい?探しつづけてね!", + COMPLETE : "すばらしい!ペンチでここをしめつけて、っと!\a直ったみたいだね。仕事に戻らないと!\aそういえば、電話がないけど、まあいいか。\aこれはお礼のしるし。グッド、ラック!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "ロッコが助けが必要だって聞いたよ。彼を手伝ってあげられる?_where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "よっ!ちょ~どい~とこにきたね~。ごきげんだよ!\aいぇ~、コグたちにこまってるんだよね~。ボスボット、やっつけてくれるとハッピ~。できそ~?", + INCOMPLETE_PROGRESS : "やぁ、_avName_。\aボスボット、ど~だい?やくそくしたろ~?\aロッコ、やくそくまもる。これ、きまり~。", + COMPLETE : "よぉ、_avName_!これでボスボットもえばらなくなったかもね~!\aほ~らよっと!どでかいごほうびだ!トラブルにはまきこまれるな~!", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "パジャマ・プレイスにいるナットがマネーボット本部のうわさを聞いたって。\aちょっと手伝えるかどうか彼のところに向かってくれる? where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "ちょっと変なうわさを小耳にはさんだんだよね。\aでもひょっとしたらデマかもしれないけど、きっと何かは起こっているんだよ。\aあのマネーボットたちのことさ!\aパジャマ・プレイスのすぐ近くに新しい本部を作ったみたいなんだよね。\aP.J.が知っているみたいなんだよね。\a_toNpcName_に会ってみて!_where_\aで、何か知らないか聞いてみて!", + INCOMPLETE_PROGRESS : "まだP.J.に会ってないの?\a何か別の用事でもあるの?", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、_avName_。どこにいたんだい?\aえっ、マネーボット本部?!何も見てないけどなぁ~。\aパジャマ・プレイスの奥まで行って本当かどうか見てきてくれない?\a本部があったら、中のマネーボットたちを倒して、戻ってきてよ。", + INCOMPLETE_PROGRESS : "マネーボット本部は見つけた?様子を知るために、中のコグたちをやっつけてよ。", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "えっ?!マネーボット本部が実在するって?\aナットに今すぐ伝えてくれない?\a彼のいるすぐ近くにコグ本部があっただなんて信じられる?", + INCOMPLETE_PROGRESS : "ナットはなんて言うかな?まだ彼に会ってないんでしょ?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "P.J.のいうことは聞きあきたんだよね。\aもっとちゃんとした情報がほしいなぁ。その前に、このノミたちをなんとかしないと。\aわかった!もう一回、やっつけに行って「マネーボット本部プラン」を取ってきてよ。\aそしたら信じるよ。", + INCOMPLETE_PROGRESS : "「プラン」はまだ?本部にいるコグたちならきっともっているはず!", + COMPLETE : "えっ、「プラン」を持ってきたって!\aすばらしい!ふむふむ…。\aマネーボットがお金工場で「コグドル」を作っているって!\aきっとマネーボットだらけなんだろうね!でもより深く調べないと!\aコグに変装できればなぁ…。ちょっと待てよ。どこかにコグスーツのパーツがあったはずなんだけど…。\aあった!これを持っていけば何か役に立つはず。手伝ってくれてありがとう!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "カウンテスがキミのことを探し回っていたよ。彼女のところにいって声をかけてあげて!_where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_ね。きっと私のことを助けてくれると信じているわ。\aコグたちがさわがしくて集中できないの。\aいつも寝る前にひつじを数えるんだけど、うるさくていつも数え間違えちゃうのよ。\aもしコグたちをやっつけてくれたら、キミを助けてあげられるんだけど。まかせて!\aで、何匹数えたっけ?あっ、そうそう。ひつじが136匹、ひつじが137匹、…。", + INCOMPLETE_PROGRESS : "ひつじが442匹、ひつじが443、…\aえっ?もう、戻ったの?でもまだ外はうるさいじゃない!\aあーっ、また忘れちゃった!\aひつじが1匹、ひつじが2匹、…", + COMPLETE : "ひつじが593匹、ひつじが594匹、…\aあら?どうやら私の目にくるいはなかったようね。このしずけさ…。\aはい、スウジスキーなわたしからどうぞ。\aスウジ?あらやだ、また忘れちゃった。ひつじが1匹、ひつじが2匹、…", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "かわいそうなザリがお客さんに配達をすることができないみたいなんだ。キミならきっと助けてあげられるはず。_where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "こんにちは _avName_! 配達を手伝ってくれるの?\aすごい!配達する機械がこわれて動けないの。\aちょっと待ってね。これならすぐできるはず!カウボーイ・ジョージが先週、「シタール」を注文したのよ。\a本当に悪いわね。彼に届けてくれる?_where_", + INCOMPLETE_PROGRESS : "うーん、何か忘れてな? カウボーイ・ジョージは「シタール」を待ってるよ。", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "ぼくのシタール! あー、はやく曲をひきたいなぁ。\aザリにありがとうって伝えてくれない?", + INCOMPLETE_PROGRESS : "シタールを見つけてくれてありがとう!ザリがほかに何か手伝ってもらいたいたいと思ってるはすだよ。", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "早かったわね。次は何をすれば良いかって?\aオッケー。マスター・マイクが「せいひょうき」を注文したのよ。\a今度はこれをよろしくね!_where_", + INCOMPLETE_PROGRESS : "「せいひょうき」をマスター・マイクへ持っていてくれないと。_where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "やったね!僕が注文した「せいひょうき」だ!\aそこらじゅうにコグたちがいなければ使えるんだけど…、マネーボットをちょっとやっつけてくれない?", + INCOMPLETE_PROGRESS : "マネーボットのやつらはてごわいでしょ。早く「せいひょうき」を使いたいなぁ。", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "すごいね!これで「せいひょうき」を動かせるよ。.\aザリに来週、また注文するって伝えてくれない?お願い!", + INCOMPLETE_PROGRESS : "今は特に用事はないけど、ザリがキミを待ってるんじゃない?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "で、マスター・マイクが、「せいひょうき」、よろこんでたんだ~。よかったわ!\a次は、えっと…。そうだ、ゼン・グレンが「しまうまのざぶとん」を注文したの。\aはい、どうぞ!彼のところにさっと届けにいってくれる?_where_", + INCOMPLETE_PROGRESS : "ゼン・グレンがきっと、その「しまうまのざぶとん」。待ちこがれていると思うよ。", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "ついに「しまうまのざぶとん」が手に入った! これでやっとメイソウができる。\a………………\aだけどコグたちがさわがしくて集中できやしない!\aコグたちをやっつけてくれるよね?\a幸せにメイソウしたいなぁ~。", + INCOMPLETE_PROGRESS : "まだコグたちがうるさいなぁ!キミはメイソウできる?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "んー。「平和」と「しずけさ」。キミにも幸せがおとずれるよ、_avName_。\aザリに僕がどれだけ喜んでたか伝えてね。ありがとう! ", + INCOMPLETE_PROGRESS : "ザリから電話があって、キミを探してるってさ。会いに行って何が欲しいか聞いてきなよ。", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "「しまうまのざぶとん」、ゼン・グレンがよろこんでくれたんだって!うれしいよ!\aおっと、ちょうど「ひゃくにちそう」が届いたよ。\aローズ・ペタルが待ちわびてるみたいだから、悪いんだけど彼女に届けてあげて!_where_", + INCOMPLETE_PROGRESS : "はやく届けないと「ひゃくにちそう」がかれちゃうよ。", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "何て素敵な「ひゃくにちそう」なの!ザリならきっと届けてくれると思った!\aいや、_avName_ならきっと!ってね。ザリにありがとうって伝えて!", + INCOMPLETE_PROGRESS : "ザリに「ひゃくにちそう」のお礼をちゃんと言ってね!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_、よく戻ってくれたわ。いつも本当にありがとう!キミは本当に元気よね!\さてリストにはなんて書いてあるかしら…。\aこれこれ!この「ザイデコのレコード」をアイ・パチーリに届けてくれる?_where_", + INCOMPLETE_PROGRESS : "きっとアイ・パチーリがザイデコのレコードを待ってるよ。", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "ザイデコのレコード? ザイデコのレコードを頼んだのを覚えてないよ。\aひょっとしたらララバイ・ルーが頼んだのかもしれないね。_where_", + INCOMPLETE_PROGRESS : "きっとザイデコのレコードはララバイ・ルーのものだよ。_where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "ザイデコのレコード! ザリが忘れてたかと思ってた。\a彼女にこの「ズッキーニ」を持って行ってくれる? 彼女なら誰がこれを必要としているか、わかるはず。ありがと!", + INCOMPLETE_PROGRESS : "「ズッキーニ」ならたくさん持ってるわ。ザリにひとつもっていってあげて!", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "ズッキーニ? きっと誰かがほしいはずね。.\a配達リストもあと少しね。\aベイビーフェイス・マクドゥーガルが「ズート・スーツ」を注文してたわ。よろしくお願いね。_where_", + INCOMPLETE_PROGRESS : "申し訳ないんだけど「ズート・スーツ」を早く届けないとしわくちゃになっちゃうわ。", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "むかしむかし、あるところに。おじいさんと…。おっと、むかし話を聞きにきたようではないね。\a「ズート・スーツ」を届けにきてくれたんだね。すばらしい!ワオ!\aザリに伝言をお願いできるかな?このスーツにあうジルコンのカフスボタンが欲しいって!", + INCOMPLETE_PROGRESS : "ザリにメッセージを伝えてくれた?", + COMPLETE : "ジルコンのカフスボタン? そうねぇ…。ちょっと探してみるわ。\aとにかく、とにかく!\aコグたちに立ち向かうのには、大きなゲラゲラポイントが必要ね!本当にいろいろ手伝ってくれてありがとう!はい、これが感謝の気持ち!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "ドロジー・デイブが困っているみたい。キミならきっと助けてあげられるはず。彼の店に立ち寄ってあげて!_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "Zzzzz...\aZz、何?えっ?寝てないよ!たぶん…\a知ってる?コグビルの中には僕を眠くさせる機械がたくさんあるって。\a耳をかたむけると、ほら。\a………………\aはっ!そうそう。眠くならないようにコグビルをやっつけてくれない?", + INCOMPLETE_PROGRESS : "Zzzzz...はっ!キミかぁ、_avName_。\a早かったね。ちょっと昼寝をしていたんだ。\aコグビルをやっつけてくれないと………。Zzzzz...", + COMPLETE : "あれっ!あれれれれっ!\aコグビルがなくなって、ようやくリラックスできるよ。\a_avName_、本当にありがとう。\aまたね。リラックスしたらおかげで昼寝したくなったよ。", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "テディ・ブレアのところに行って、キミの仕事をもらおう!_where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "何だって?キミの仕事なんてないよ。\a仕事!どうしてそう言ってくれなかったんだ!もっとちゃんと言ってれなきゃ。\aコグたちがじゃまして冬眠ができないんだ。もしキミがドリームランドを静かにしてくれたら、\aちょっとした何かをあげるよ。", + INCOMPLETE_PROGRESS: "「ゴグ」をやっつけた?「ゴグ」って何かって?\aああ、「コグ」ね! そうしてそう言ってくれなかったんだ!\aまだ静かになってないから、もう少しやっつけてくれないかい?", + COMPLETE : "たのしかった?えっ?「たおした」って?\aやったね。本当に助けてくれてありがとう!\a部屋の奥にあったんだけど、使わないからどうぞ!\aきっと何かのパーツだから、他のパーツと一緒に使うんじゃない?ありがとう、_avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "コグたちが第一ねんねタオル銀行に押し入ったんだ!ウィリアム・テラーの所に行って助けてあげて!", + }, + 6292 : { QUEST : "まったくマネーボットのやつらといったら!やつらは私の大切な読書ランプをぬすんでいった!\a今すぐ取り戻さないと。何とか取り戻してくれないか?\aもし読書ランプを取り戻してくれたら、私が「マネーマネー」に会えるようにしてあげよう。\a早く!", + INCOMPLETE_PROGRESS : "ランプが必要なんだ!お願いだから探し続けて!", + COMPLETE : "よく戻ったね!それに読書ランプも!\a感謝しつくしてもし尽くせないけど、お礼にこれをあげよう!", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "ニーナ・ナイトライトがキミを探してたよ、_avName_。彼女が助けが必要だって。_where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "わー! 会えてうれしいわ、_avName_。さっそくなんだけど、助けてくれない?\aコグたちがじゃまして、私の倉庫に商品のベッドのざいこがなくなっちゃってるの。\aハーディ・トゥールのところにいってベッドをもってきてくれる?_where_ ", + INCOMPLETE_PROGRESS : "ハーディからベッドを受け取った?彼女ならきっと持っているはず。", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "ベッド?もちろん、ここにあるわよ。\aニーナに届けてくれる?わかったかしら?\aニーナにな!ってね。\aジョーク、ジョーク!よろしく頼むわ。", + INCOMPLETE_PROGRESS : "ニーナはよろこんでくれた?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "残念ながらこのベッドじゃないの。こんなシンプルなベッドじゃなくて、もうすこしファンシーなベッドなのよ。\aそんなに時間はかからないと思うからよろしくね。", + INCOMPLETE_PROGRESS : "ハーディーならきっともっとファンシーなベッドを持っているはずよ。", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "どうやらお気にめさなかったようね。でもきっとこれなら大丈夫。\aでもちょっとした問題があるの。実はまだ出来上がってないのよ。\a外にいるコグたちをやっつけている間に完成させるから、よろしく頼むわ。", + INCOMPLETE_PROGRESS : "ベッドを作るにはまだ外がうるさいようね。\aやっつけたときが、ベッドの出来上がり!", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "おまたせ、_avName_!\aコグたちをやっつけてくれたおかげでベッドが出来たわ!\aニーナによろしくね。キミのおかげで仕事がはかどるわ!ありがとう!", + INCOMPLETE_PROGRESS : "ニーナに早くベッドを届けてあげて!", + COMPLETE : "なんて素敵なベッドなの!\aこれでお客さんも大満足間違いなし。_avName_、本当にありがとう!\aきっとキミならこれを使えるはず。ずっと前に誰かがわすれていったものみたい。", + }, + 7209 : { QUEST : "ハニー・ムーンに会いにいってごらん。手助けしてほしいみたいだよ。_where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "おー、_avName_!キミの助けが必要なのよ!\a長い間、ゆっくり眠れないの。それもコグたちが私の大切なベッドカバーをうばっていったからなの。\aねぇ、ふとんやトニーに会って、青いベッドカバーがないか聞いてきてくれない?_where_", + INCOMPLETE_PROGRESS : "トニーに青いベッドカバーのこと、聞いてくれた?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "ハニーがベッドカバーを欲しいって?\a何色?青だって?!\a今、手持ちなのは全部、「赤」だから特別に作らないと。\a外にいるコグたちをやっつけてくれたら、作ってあげるよ。", + INCOMPLETE_PROGRESS : "まだ青いベッドカバーを作っているんだ、_avName_。コグたちをやっつけて!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "また会えてうれしいよ!ちょっとまってね…。\aはい、ベッドカバー。青だよ。彼女も間違いなく気に入るよ!", + INCOMPLETE_PROGRESS : "ハニーはベッドカバーを気に入ってくれた?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "私のベッドカバー?違うわ、これじゃないの。\a「しましまもよう」のが欲しいの!こんなデザインじゃ、落ち着いて眠れないわ。\aこれを彼に返して、しましまのをお願い。\aきっとあるはずよ。", + INCOMPLETE_PROGRESS : "「しましまもよう」なの。トニーにお願いって伝えて!", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "なんだって!「しましまもよう」じゃ、だめだって?\aそっか…。何があるか調べてみるね。\aちょっと時間かかりそうだから、その間、コグたちの相手をしてもらっていいかな?\aキミが戻るころには、何か見つかるはずだから…。", + INCOMPLETE_PROGRESS : "まだ他のベッドカバーを探している最中さ。コグたちの様子はどう?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "よくコグたちの相手をしてくれたね!\aどうぞ!彼女もきっと気に入る青くてしましまのカバーさ!\a早くハニーにこれを届けてあげてよ。", + INCOMPLETE_PROGRESS : "これ以上のものはないよ。\aハニーがきっと待ってるよ。", + COMPLETE : "あら、まあ!なんてすてきなの!やっぱりこのデザインじゃないと!\aじゃあ、すてきな夢でもみるかしらね。じゃあね、_avName_。\aなあに?まだいるの?レディーが寝ようとしてるのがわからない?\aどうぞ、これを受け取って、私を休ませて。おやすみなさい!", + }, + + 7218 : { QUEST : "ドリーミー・ダフネが「誰かに手伝ってもらいたい」って言ってたよ。_where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、_avName_。会えてうれしいな! コグたちが「まくら」をうばっていったの。\aテックスのところに「まくら」がないか見てきてくれません?_where_\aきっとあるはずなんだけどな?お願いします。", + INCOMPLETE_PROGRESS : "テックスから「まくら」を受け取っていただけました?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "元気?ダフネが「まくら」を欲しいって?そりゃまさにここだよ、キミ。\aはいどうぞ、_avName_!ダフネのところに持っていってあげな!それからよろしく伝えてくれよ!\a女の子を助けるのが生きがいなんでね。", + INCOMPLETE_PROGRESS : "渡した「まくら」は女の子に合ってたのかな?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "「まくら」だわ!\aあれっ、ちょっと待ってくださる?このまくら、やわらかすぎるわ。\aもっとかたいまくらがいいのですが…。\a申し訳ありませんがテックスにこれを返して、違うまくらを持ってないかきいてくれません?おねがいします。", + INCOMPLETE_PROGRESS : "残念ながら違うの。やわらかすぎてしまって。テックスに違うまくらをたのんでください。", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "やわらかすぎるって?ちょっと考えさせて…\aそういや倉庫にかたいまくらがあったはずだったかな?\a後で取りに行くから、ここいらのコグたちをそうじしておいてくれるかな?", + INCOMPLETE_PROGRESS : "倉庫に行くためにもコグをやっつけて!", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "もう戻ったの?すんばらしい!ダフネが探している「まくら」を取ってきたよ。\a早くこれを届けてあげてね。まかせたよ!", + INCOMPLETE_PROGRESS : "このまくら、とってもかたいよ!ダフネもとっても気に入るはず。", + COMPLETE : "きっとテックスならかたいまくらを持っているって信じてたわ。\aさわりごこちといい、かたさといいパーフェクト!\aこのコグスーツのパーツを受け取ってくれるかしら。", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "サンディ・サンドマンのところによってあげて。彼女のパジャマがなくなって困っているみたいだよ。_where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "あらっ、パジャマがない!なくなっちゃった!\aどうしたらいいの?あっ、わかった!\aビッグ・ママに会いに行ってくれない?きっとパジャマがあるはずよ。_where_", + INCOMPLETE_PROGRESS : "ビッグ・ママからパジャマを受け取った?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "まぁ、可愛いトゥーンね!ビッグ・ママのパジャマはバハマから取り寄せた最高のものよ。\aサンディ・サンドマンのパジャマね?ちょっとまってね。これかな?あれかな?\aはい、どうぞ。これで彼女もおしゃれに安心して眠れるわね。\a私の代わりに走って彼女に渡してくれる?今は、お店から出ることができないのよ。\a本当にありがとうね、_avName_。また、会いましょう!", + INCOMPLETE_PROGRESS : "サンディーのためにパジャマを持っていかないと!_where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "ビッグ・ママがわたしにこれを?あらまぁ。\a足がついてないじゃない…\a私はいつも足つきパジャマを着て寝るのよ。みんなはどう?\a悪いけどこれを戻して足つきのやつをお願いできるかしら?", + INCOMPLETE_PROGRESS : "私のパジャマは足がついてないとだめなの。ビッグ・ママに相談して!", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "足つきだって?ちょっと考えさせて。\aこれじゃないし、あれじゃないし…\aジャーン!足つきパジャマ!きれいな青のパジャマパジャマよ。他でさがそうったって、なかなか見つからないんだから!\a彼女に届けてあげて!ありがとう!", + INCOMPLETE_PROGRESS : "サンディーは青い足つきパジャマを気に入ってくれた?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "たしかに足はついてるけど、青いパジャマは着れないわ!\aビッグ・ママに色違いがあるか聞いてくれる?", + INCOMPLETE_PROGRESS : "ビッグ・ママならきっと、色の違う足つきパジャマを持ってるはず。", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "それは残念ねぇ。足つきパジャマは青しか持ってないのよ。\aでもひょっとしてキャットならいくつか色のバリエーションを持ってるはずよ。_where_", + INCOMPLETE_PROGRESS : "手持ちのパジャマはこれらしかないのよ。キャットのところに行ってみなさいよ。_where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "足つきパジャマ?もちろん!\aサンディーは青じゃだめなの?\aちょっとやっかいね。これはどうかしら?\a青じゃないし、ちゃんと足もついてるし。", + INCOMPLETE_PROGRESS : "わたしは赤が好きよ。あなたは?\aサンディーも気に入ってくれるとうれしいんだけど。", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "いやよ。たしかに青じゃないけど、私の顔の色でこんな色のパジャマはきれると思う?\aそんなわけないわ。キャットに別の色をたのんで、お願い!", + INCOMPLETE_PROGRESS : "キャットならもっといろいろパジャマを持ってるはず。わたしには赤は似合わないの。", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "ふーん、赤でもないと…。\aふむふむ。僕のひげによると、他にもあるらしい。\aもうちょっと考えてみるけど、取引しよう。\a僕が別のパジャマをみつけるから、かわりにコグビルをやっつけてよ。不安なんだ。\aパジャマを探しておくからよろしくね、_avName_。", + INCOMPLETE_PROGRESS : "パジャマのためにも、ちゃんとコグビルをやっつけて。", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "コグたちをやっつけてくれてありがとう!\aサンディーのためのパジャマをみつけたよ。彼女が気に入ってくれるといいんだけど。\a急いで彼女に持っていってあげて。ありがとう!", + INCOMPLETE_PROGRESS : "ねぇ、_avName_。サンディーがきっと首を長くしてパジャマを待ってるよ、", + COMPLETE : "足つきのピンクのパジャマ! かーんぺき!\aそれにサイズもぴったし。\a手伝ってくれた御礼をしないとね!\aこれなんてどうかしら?道でひろったんだけどね。", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "スウィート・リップスのところに行こう!助けを必要としているよ。_where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "にっくきコグたちが大切な「しわのばしクリーム」をうばっていったのよ!\aお客様の大切なものなのに…。\aリップのところに行って、特別なクリームの予備がないか見てくれる? _where_", + INCOMPLETE_PROGRESS : "「しわのばしクリーム」がないと仕事ができないの。\aリップが何を持ってるか聞いてくれる。", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "ああ、クリームね。リップスからの特別なお願いだから、ふつうのの作り方ではできないんだ。\aトップシークレットの材料のカリフラワーサンゴが必要になるね。でもあいにくなくなっちゃったんだ。\a釣りに行って、池から見つけてくれないかな?サンゴを見つけたらすぐにおいでよ。", + INCOMPLETE_PROGRESS : "カリフラワーサンゴを特別なレシピで「しわのばしクリーム」にするのさ!", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "ワオ、これはいい「カリフラワーサンゴ」だね!\aオッケー、こうして、ああして。そしてコンブをひとさじっと。\aあれコンブがないぞ?コンブもいるなぁ。\a悪いけどもう一度池に行って、「ねばねばコンブ」を取ってきてよ。", + INCOMPLETE_PROGRESS : "「ねばねばコンブ」はお店じゃ売ってないんだ。\aクリームを作るのにかかせないのさ。", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "おおおおっと。これはとってもねばねばなコンブだね。ありがとう、_avName_!\aそれじゃあ、シンジュをすりばちと「ごますり棒」で…。\aおや、今度は「ごますり棒」が見つからないぞ?棒がなきゃ、すりばちの意味がない…。\aきっとこの間、シャークロンが押し入ったときに取っていったのかも。\aマネーボット本部に行って「ごますり棒」を取り返してきて!", + INCOMPLETE_PROGRESS : "だから「ごますり棒」がないと、シンジュをすりつぶせないよ。\aまったくシャークロンのやつらといったら!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "そうそう、これこれ!\aこれですりつぶして、かきまぜて…。\aはい、出来上がり!この出来立てフレッシュなクリームをスウィート・リップスに届けてあげて!", + INCOMPLETE_PROGRESS : "彼女はいろいろこだわりがあるから早く、早く!", + COMPLETE : "もっと大きい「しわのばしクリーム」はなかったの?\a今度はちゃんと多くたのまないとね。すぐなくなっちゃうから。\aまたね、_avName_!\aなあに?まだ何かようかしら?今から大忙し。\aはい、これを受け取って!", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "もしロウボット変装パーツに興味があるなら、_toNpcName_をたずねてみて。\a彼は天気の研究の助けが必要みたいなんだ。_where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "はい、はい。確かにロウボット変装パーツなら持ってますよ。\a僕にとっては興味がないものなんですがね。\a僕はトゥーンタウン全体の天気の変化について研究をしているんだ。\aコグの持っている気温センサーとだったら、喜んで変装パーツと交換するよ。\aまずは%sから始めたらどうかな?" % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたかい?" % GlobalStreetNames[2100][-1], + COMPLETE : "これはすばらしい!\a恐れていた通りだ…\aあ、そうそう。これが変装パーツだよ。", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "もっとロウボット変装パーツがいるなら、_toNpcName_にもう一度、たずねてごらん。\a彼の研究のアシスタントが必要みたいなんだ。_where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツがまだ必要だって?\aキミがそこまで言うのなら…\aでももう一つセンサーが必要なんだ。\a今度は%sのを探してみて。" % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[2200][-1], + COMPLETE : "ありがとう!\a変装パーツをどうぞ!", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツがさらに必要なら_toNpcName_のところに戻ってみたら?\a天気の研究、まだ手助けがいるみたいだよ。_where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "キミは本当に優秀だね!\a今度は%sを調べてみてくれないかい?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べているのかい?" % GlobalStreetNames[2300][-1], + COMPLETE : "ふーん、それにしてもこのセンサー見た目はあまりよくないが…\aありがとう。これがキミへのごほうびのパーツだ。", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "さらに変装パーツが必要なんだよね。\a気温のデータがYou-know-who needs more temperature readings._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "また戻ったのかい?\a非常に熱心だね。\a次の場所は%sだ。" % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね?" % GlobalStreetNames[1100][-1], + COMPLETE : "おみごと!じゅんびがととのったね。\aほら、へんそうパーツだよ!", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツをお探しなら…。_where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "キミに会えてうれしいよ。\a次は%sのデータが欲しいんだ。お願いできるかな?" % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたのかい" % GlobalStreetNames[1200][-1], + COMPLETE : "どうもありがとう!\a変装パーツももうすぐ完成だね。", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "きっと_toNpcName_がもっと仕事があるみたい。_where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "また会えてうれしいよ、_avName_!\a%sのデータを取ってきてもらえるかな?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べたのかい?" % GlobalStreetNames[1300][-1], + COMPLETE : "すばらしい仕事だったね。\aよくがんばったキミへのごほうびだよ。", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "もうわかっているよね。_where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_、僕の大切な友達!\a今度は%sへ行って別の温度センサーを見つけてくれないかい?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね??" % GlobalStreetNames[5100][-1], + COMPLETE : "すばらしい!\aキミのおかげで研究が本当にはかどるよ!\aはい、ごほうび!", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がキミを名差しでお願いしてきたよ。\aキミが頑張っているのが街のウワサになってるんだね。_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "よく戻ってきたね!\aキミのことを待っていたんだよ。\a次に必要なのが%sのデータさ。" % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[5200][-1], + COMPLETE : "ありがとう!\aはい、キミへのごほうび!", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "キミがロウボットにちゃんと変装したければ、\a_toNpcName_が助けになるはず。_where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "やあ、研究者みならい君!\aさらに%sのデータが必要なんだよね。" % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "%sだからね。" % GlobalStreetNames[5300][-1], + COMPLETE : "すばらしい仕事だったね!\aはい、ロウボットのパーツをどうぞ。", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がキミに別の仕事があるってさ。\a気に入ってくれるといいんだけど。_where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "そっか…。\aさらに必要なんだね。\aそれなら今度は%sを試してみて!" % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "%sを探しているんだよね?" % GlobalStreetNames[4100][-1], + COMPLETE : "もうひとつ!\aキミはとってもスマートだね。", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "ロウボット変装パーツを探しているのかい?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "大体、察しがついているとは思うけど、\a今度は%sのデータが必要なんだ。" % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "キミは%sを調べているんだよね?" % GlobalStreetNames[4200][-1], + COMPLETE : "あとちょっとだね!\aはいどうぞ。", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "ほんとうは言いたくないんだけど。。。_where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "キミは%sのことどう思う?センサーをゲットできると思う?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "ちゃんと%sを調べた?" % GlobalStreetNames[4300][-1], + COMPLETE : "またいい仕事をしたね、_avName_。", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "もしまだ変装パーツが必要なら、教授のところにいってみたら?_where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "残念ながら、%sのデータがまだ入手できてないんだ。" % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "キミは%sをちゃんと調べているんだよね?" % GlobalStreetNames[9100][-1], + COMPLETE : "いい仕事をしたね。\aあともうちょっとのところだね。", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "キミへの最後のミッションの内容は_toNpcName_が知ってるよ。_where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "すぐもどってきたね。\a最後のデータは%s。" % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "%sだからね。" % GlobalStreetNames[9200][-1], + COMPLETE : "全部終わったね!\aこれでケンサツキョクに行って、ショウカンジョーを集められることができるね!\a今まで本当にありがとう。そして気をつけて!", + }, + 12000 : { GREETING : "", + LEAVING : "", + QUEST : "もしもボスボットのパーツにきょうみがあるなら_toNpcName_._where_に聞くといいかも。", + }, + 12001 : { GREETING : "", + LEAVING : "", + QUEST : "ボスボットのパーツかい?\aじゃぁ、ボスボット・コレクションに協力してくれるかな?\aなずはオベッカーからたのむよ。", + INCOMPLETE_PROGRESS : "オベッカーが見つからない? そんなはずないだろ…?", + COMPLETE : "もうたおしたのかい?\aほら、さいしょのへんそうパーツだ。", + }, + 12002 : { GREETING : "", + LEAVING : "", + QUEST : "もっとパーツが必要ならやはり_toNpcName_に聞くべきだよ。_where_", + }, + 12003 : { GREETING : "", + LEAVING : "", + QUEST : "もう一つパーツが必要なのかい?\aもちろん...\aカリカリンを倒してきたらあげるよ。", + INCOMPLETE_PROGRESS : "カリカリンはそのへんのストリートにいるだろうね。", + COMPLETE : "朝メシ前だっただろ?\aさぁ、二つ目のパーツだよ。", + }, + 12004 : { GREETING : "", + LEAVING : "", + QUEST : "ボスボットのパーツなら…もうわかるよね?_where_", + }, + 12005 : { GREETING : "", + LEAVING : "", + QUEST : "次はイエスマンだ…。", + INCOMPLETE_PROGRESS : "イエスマンもそのへんのストリートにいるだろう。", + COMPLETE : "イエス!なかなかやるね。\aさぁ、三つ目のパーツだ。", + }, + 12006 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がまだまだパーツを持ってるよ...", + }, + 12007 : { GREETING : "", + LEAVING : "", + QUEST : "ガミガミーナを倒したら次のをあげるよ。", + INCOMPLETE_PROGRESS : "%sを探してみたかい?" % GlobalStreetNames[1100][-1], + COMPLETE : "おみごと!\a四つ目のへんそうパーツだよ。", + }, + 12008 : { GREETING : "", + LEAVING : "", + QUEST : "次も...だね。_where_", + }, + 12009 : { GREETING : "", + LEAVING : "", + QUEST : "次はリストラマンだ。", + INCOMPLETE_PROGRESS : "見つからないのかい?%sを探してごらん。" % GlobalStreetNames[3100][-1], + COMPLETE : "やつはめんどうだったかい?\a五つ目のへんそうパーツだよ。", + }, + 12010 : { GREETING : "", + LEAVING : "", + QUEST : "キミ、…もうわかってるでしょ?_where_", + }, + 12011 : { GREETING : "", + LEAVING : "", + QUEST : "え~と、私のリストでは次はヘッドハンターだ。", + INCOMPLETE_PROGRESS : "こいつの場合はビルの中を探すほうがいいかも。", + COMPLETE : "早かったね!\aほら、六つ目のパーツだよ。", + }, + 12012 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がもっとボスボットがひつようだって…。", + }, + 12013 : { GREETING : "", + LEAVING : "", + QUEST : "次はデッパラーダをつかまえてくれないか?", + INCOMPLETE_PROGRESS : "こいつもやはりビルの中だろうな。", + COMPLETE : "キミもなかなかやるねぇ。\a六つ目のパーツだ!", + }, + 12014 : { GREETING : "", + LEAVING : "", + QUEST : "もっとパーツが必要なんだろ?さ、行っておいで..._where_", + }, + 12015 : { GREETING : "", + LEAVING : "", + QUEST : "さぁ、とどめだ!ビッグチーズを!!", + INCOMPLETE_PROGRESS : "%sにいるはずだぞ。" % GlobalStreetNames[10000][-1], + COMPLETE : "キミはきたい通りのチーズ好き…\aあぁ、いや、なんでもない。\a次のへんそうパーツはこれだ。", + }, + 12016 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_がさがしてましたよ...", + }, + 12017 : { GREETING : "", + LEAVING : "", + QUEST : "実はキミに新しくてずるがしこいボスボットを倒してほしいんだ。", + INCOMPLETE_PROGRESS : "%sをさがしてみてくれ。" % GlobalStreetNames[10000][-1], + COMPLETE : "ヤツらは見た目よりつよいようだな。\aさぁ、へんそうパーツが必要なんだろ?", + }, + 12018 : { GREETING : "", + LEAVING : "", + QUEST : "さぁ、行っていって..._where_", + }, + 12019 : { GREETING : "", + LEAVING : "", + QUEST : "このバージョン2.0コグはとてもきょうみ深い。\aもっとさがしてきてくれるかい?", + INCOMPLETE_PROGRESS : "%sにならいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "ありがとう!\aジャジャーン、へんそうパーツをどうぞ!", + }, + 12020 : { GREETING : "", + LEAVING : "", + QUEST : "ついででいいんだけど、_toNpcName_のトコに行ってくれる?", + }, + 12021 : { GREETING : "", + LEAVING : "", + QUEST : "ヤツらはもしかしてどんどん強くなっているのか?", + INCOMPLETE_PROGRESS : "%sを探してみてくれ。" % GlobalStreetNames[10000][-1], + COMPLETE : "アレ?ヤツらそんなに強くはないの?\aそら、いつものだ。", + }, + 12022 : { GREETING : "", + LEAVING : "", + QUEST : "え~と..._where_", + }, + 12023 : { GREETING : "", + LEAVING : "", + QUEST : "た~ぶ~ん、ヤツらはボスボットじゃなくってなんか別のぉ...", + INCOMPLETE_PROGRESS : "こいつらは%sにいるぞ。" % GlobalStreetNames[10000][-1], + COMPLETE : "あ、これはやっぱボスボットだわ。\aふむ、へんそうパーツはそこにあるから…", + }, + 12024 : { GREETING : "", + LEAVING : "", + QUEST : "え~と、ちゃんと言わないとわかりませんか?", + }, + 12025 : { GREETING : "", + LEAVING : "", + QUEST : "いや、た~ぶ~んなんだけど、…ヤツらってガイコグ系じゃない?", + INCOMPLETE_PROGRESS : "%sにいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "う~む、どうもはっきりしないなあ。\aあ、へんそうパーツを持っていってね。", + }, + 12026 : { GREETING : "", + LEAVING : "", + QUEST : "フゥ~、_toNpcName_がよんでいますよ。", + }, + 12027 : { GREETING : "", + LEAVING : "", + QUEST : "どうもまだヤツらがガイコグのナカマなのかわからなくってねぇ。", + INCOMPLETE_PROGRESS : "%sをさがすといいよ。" % GlobalStreetNames[10000][-1], + COMPLETE : "あぁ~っと、ちがう…かな?\a…はい、次のパーツ!", + }, + 12028 : { GREETING : "", + LEAVING : "", + QUEST : "多分、もうここはうんざりだと思いますけど…。", + }, + 12029 : { GREETING : "", + LEAVING : "", + QUEST : "いやぁ、スマン!ヤツらの事でなやんでしまっていてね。\aもう一体だけたのめないかな?", + INCOMPLETE_PROGRESS : "やはり%sにいるだろう。" % GlobalStreetNames[10000][-1], + COMPLETE : "「すばらしい!」の一言につきるよ!\aへんそうパーツだ。受け取ってくれ。", + }, + 12030 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_はもうこわれたレコード…って、意味わかります?", + }, + 12031 : { GREETING : "", + LEAVING : "", + QUEST : "もうヤツらの事はなんでも私に聞いてくれ!\aところでそうだんなんだけど…", + INCOMPLETE_PROGRESS : "きっと%sにならいるんじゃないか?" % GlobalStreetNames[10000][-1], + COMPLETE : "よし、思った通りだ!\aあぁ、そうそう。\aこれはキミにだよ。", + }, + 12032 : { GREETING : "", + LEAVING : "", + QUEST : "フリッピーにこの事を伝えてくれる?", + INCOMPLETE_PROGRESS : "フリッピーはトゥーンホールにいるよ。", + COMPLETE : "新しいコグだって?!\a教えてくれてありがとう。\aお礼にさいごのへんそうパーツをあげるよ!", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["わんわん", "きゅう~ん", "わふわふ"] +ChatGarblerCat = ["にゃ~", "みゃ~"] +ChatGarblerMouse = ["チュ~チュ~", "チチッ", "チュ~"] +ChatGarblerHorse = ["ひひひん", "ぶるるん"] +ChatGarblerRabbit = ["チチッ", "ふんふんふん", "くんかくんか", "チッチッ"] +ChatGarblerDuck = ["グワッ", "グワワ~", "グワグワッ"] +ChatGarblerMonkey = ["ウキッ", "キー", "ウッキー"] +ChatGarblerBear = ["ガウ~", "ガルルル"] +ChatGarblerPig = ["ブヒブヒ!", "ブーッ!", "ブホブホッ!"] +ChatGarblerDefault = ["フガー"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = "" +AvatarDetailPanelClose = "閉じる" +AvatarDetailPanelLookup = "%s の状態を調べています…" +AvatarDetailPanelFailedLookup = "%s の状態を調べられませんでした。" +#AvatarDetailPanelPlayer = "Player: %(player)s\nWorld: %(world)s\nLocation: %(location)s" +# sublocation is not working now +AvatarDetailPanelPlayer = "プレイヤー: %(player)s\nワールド: %(world)s\nロケーション: %(location)s" +AvatarDetailPanelPlayerShort = "%(player)s\nワールド: %(world)s\nロケーション: %(location)s" +AvatarDetailPanelRealLife = "オフライン" +AvatarDetailPanelOnline = "ロビー: %(district)s\nエリア: %(location)s" +AvatarDetailPanelOnlinePlayer = "ディストリクト: %(district)s\nロケーション: %(location)s\nプレイヤー: %(player)s" +AvatarDetailPanelOffline = "ロビー: オフライン\nエリア: オフライン" +AvatarShowPlayer = "プレイヤーをみる" +OfflineLocation = "Offline" + +#PlayerDetailPanel +PlayerToonName = "トゥーン: %(toonname)s" +PlayerShowToon = "トゥーンをみる" +PlayerPanelDetail = "Player Details" + + +# AvatarPanel.py +AvatarPanelFriends = "ともだち" +AvatarPanelWhisper = "ささやく" +AvatarPanelSecrets = "ひみつ" +AvatarPanelGoTo = "ワープ" +AvatarPanelPet = "ドゥードゥルを見る"#▲ +AvatarPanelIgnore = "むしする" +AvatarPanelIgnoreCant = "Okay" +AvatarPanelStopIgnoring = "むししない" +AvatarPanelReport = "ほうこくする" +#AvatarPanelCogDetail = "部署: %s\nレベル: %s\n" +AvatarPanelCogLevel = "レベル:%s" +AvatarPanelCogDetailClose = "閉じる" +AvatarPanelDetail = "トゥーン情報"#▲ +AvatarPanelGroupInvite = "グループに招待する" +AvatarPanelGroupRetract = "招待をやめる" +AvatarPanelGroupMember = "メンバー" +AvatarPanelGroupMemberKick = "Kick Out" + +# grouping messages +groupInviteMessage = "%sがグループに招待したいって" + + +# Report Panel +ReportPanelTitle = "めいわくトゥーン" +ReportPanelBody = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Use \"Ignore\" on the toon's panel\n\nDo you really want to report %s to a Moderator?" +ReportPanelBodyFriends = "This feature will send a complete report to a Moderator. Instead of sending a report, you might choose to do one of the following:\n\n - Teleport to another district\n - Break your friendship\n\nDo you really want to report %s to a Moderator?\n\n(This will also break your friendship)" +ReportPanelCategoryBody = "You are about to report %s. A Moderator will be alerted to your complaint and will take appropriate action for anyone breaking our rules. Please choose the reason you are reporting %s:" +ReportPanelBodyPlayer = "This feature is stilling being worked on and will be coming soon. In the meantime you can do the following:\n\n - Go to DXD and break the friendship there.\n - Tell a parent about what happened." + +ReportPanelCategoryLanguage = "Foul Language" +ReportPanelCategoryPii = "Sharing/Requesting Personal Info" +ReportPanelCategoryRude = "Rude or Mean Behavior" +ReportPanelCategoryName = "Bad Name" + +ReportPanelConfirmations = ( + "You are about to report that %s has used obscene, bigoted or sexually explicit language.", + "You are about to report that %s is being unsafe by giving out or requesting a phone number, address, last name, email address, password or account name.", + "You are about to report that %s is bullying, harassing, or using extreme behavior to disrupt the game.", + "You are about to report that %s has created a name that does not follow Disney's House Rules.", + ) + +# Put on confirmation screen! +ReportPanelWarning = "We take reporting very seriously. Your report will be viewed by a Moderator who will take appropriate action for anyone breaking our rules. If your account is found to have participated in breaking the rules, or if you make false reports or abuse the 'Report a Player' system, a Moderator may take action against your account. Are you absolutely sure you want to report this player?" + +ReportPanelThanks = "Thank you! Your report has been sent to a Moderator for review. There is no need to contact us again about the issue. The moderation team will take appropriate action for a player found breaking our rules." + +ReportPanelRemovedFriend = "We have automatically removed %s from your Friends List." + +ReportPanelAlreadyReported = "You have already reported %s during this session. A Moderator will review your previous report." + +# Report Panel +IgnorePanelTitle = "このトゥーンをむしする" +IgnorePanelAddIgnore = "今回のログインセッションの間は%sをむししますか?" +IgnorePanelIgnore = "%sをむししています" +IgnorePanelRemoveIgnore = "%sをむしするのをやめますか?" +IgnorePanelEndIgnore = " %sをむしするのをやめました" +IgnorePanelAddFriendAvatar = "%sはあなたのともだちです。ともだちをむしする事はできません。" +IgnorePanelAddFriendPlayer = "%s (%s)はあなたのともだちです。ともだちをむしする事はできません。" + +# PetAvatarPanel.py +PetPanelFeed = "えさをあげる" +PetPanelCall = "よぶ" +PetPanelGoTo = "行く" +PetPanelOwner = "かいぬし" +PetPanelDetail = "ペットの状態" +PetPanelScratch = "スクラッチ" + +# PetDetailPanel.py +PetDetailPanelTitle = "トリックの練習" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'ジャンプ', + 1: 'おじぎ', + 2: 'しんだふり', + 3: 'ころがる', + 4: 'ちゅうがえり', + 5: 'ダンス', + 6: 'はなす', + } + + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'ふつう', + 'hunger': 'おなかがすいた', + 'boredom': 'たいくつしている', + 'excitement': 'こうふんしている', + 'sadness': 'かなしんでいる', + 'restlessness': 'おちつかない', + 'playfulness': 'ようきな', + 'loneliness': 'さびしがっている', + 'fatigue': 'つかれている', + 'confusion': 'こんらんしている', + 'anger': 'おこっている', + 'surprise': 'おどろいている', + 'affection': 'ラブラブ', + } + +# DistributedAvatar.py +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "ともだち" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "%s に行こうとしています。" +TeleportPanelNotAvailable = "%s はいそがしいようです。またあとでトライしてね。" +TeleportPanelIgnored = "%s があなたをむししています!" +TeleportPanelNotOnline = "%s はオンラインにいません。" +TeleportPanelWentAway = "%s は行ってしまいました。" +TeleportPanelUnknownHood = "%sへの行き方がわかりません!" +TeleportPanelUnavailableHood = "%s はいそがしいようです。またあとでトライしてね。" +TeleportPanelDenySelf = "自分をみつけられません!" +TeleportPanelOtherShard = "%(avName)s は%(shardName)sにいて、キミは%(myShardName)sにいるよ。%(shardName)sに移動する?" +TeleportPanelBusyShard = "%(avName)sは今こんざつしているロビーにいるよ。こんでいるロビーではゲームの反応がおそかったり安定しない場合があるけど、それでもワープする?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "私がボスだ!" + +# DistributedBattleFactory.py +FactoryBossTaunt = "私が工場長だ!" +FactoryBossBattleTaunt = "われらの工場長を紹介しよう!" +MintBossTaunt = "私が金庫番だ!" +MintBossBattleTaunt = "われらの金庫番に話をしてもらおう!" +StageBossTaunt = "私が本当のせいぎなのだ!" +StageBossBattleTaunt = "私がほうりつなのだ!" +CountryClubBossTaunt = "I'm the Club President." +CountryClubBossBattleTaunt = "You need to talk to the Club President." +ForcedLeaveCountryClubAckMsg = "The Club President was defeated before you could reach him. You did not recover any Stock Options." + +# HealJokes.py +ToonHealJokes = [ + ["となりの家に囲いができたんだってなぁ", + "かっこいいー!"], + ["田んぼに稲を植えました", + "い~ね~"], + ["屋根が落ちてきたんだって", + "や~ね~"], + ["その和紙は誰のだ?", + "わしのだ"], + ["この植物臭わない", + "草っ!"], + ["橋のどこを走るんだい?", + "はしに決まってるじゃん!"], + ["牛はどこだ?", + "ウシろ~!"], + ["牛が笑った", + "ウシシシシ!"], + ["パンダが好きな食べ物は?", + "パンだ!"], + ["アシカの世話は誰がするの?", + "あっしか!?"], + ["誰にやってもらおう?", + "サイにまかせなサイ!"], + ["テントウ虫のけがの原因は?", + "転倒です"], + ["白鳥のくしゃみは?", + "ハクチョーン!"], + ["鮭が叫んだ!", + "裂ける~!"], + ["サバをさばけないのです…", + "サバイバルには向いてないな"], + ["なんの魚を食べようかなぁ?", + "タラでも食べタラ?"], + ["イルカは逆立ちすると空を飛べるらしいよ!", + "カルイってか!"], + ["イカダに乗って、何を釣ってるの?", + "イカだ!"], + ["このイカ、くさってるよね?", + "うーん、いかがわしい"], + ["彼が作ったカレーの味はどう?", + "かれぇ~!"], + ["バナナが青いんだけど…", + "そんなバナナな!?"], + ["いちじくは何時に食べる?", + "1時(に)食う!."], + ["このワインは誰の?", + "ワイんのだぁ~"], + ["ソースの総数はたくさんあるらしいよ。", + "へぇ、そーすか."], + ["アメの味はどう?", + "あめ~."], + ["谷が深くて、", + "困っタニ~"], + ["虹を見たのは何時だろ?", + "2時だろ?"], + ["キミに借りた斧を折っちゃった…", + "オーノー!"], + ["石が落ちたんだってさ", + "ストーンと…"], + ["リスの話を聞こうよ!", + "Listen!"], + ["この蕎麦はおいしくないなぁ", + "So bad!"], + ["送った紙はいつ届くの?", + "カミング・スーン"], + ["卵が泣いてるようだ…", + "エッグ、エッグ"], + ["重量級の赤ちゃんだね。", + "まさしくヘビー(ベビー)!"], + ["鴨が言った", + "カモーン!"], + ["あの塔が倒れてしまったよ。", + "そりゃ困っタワー."], + ["シェフが時間ギリギリに間に合った", + "シェーフ!!"], + ["カッターを買ったら高かった", + "じゃあ店は儲かったね。"], + ["あのコック、居眠りしてるよ。", + "コックりコックり"], + ["このカレンダーは誰んだー", + "俺んだー"], + ["行き過ぎてしまった、どうしよう?", + "大丈夫、モノレールでもどれーるよ!"], + ["このお面誰の?", + "おめぇんのだよ!"], + ["涼しくなる楽器は?", + "鈴~!"], + ["もれる~!", + "じゃあトイレに行っといれ"], + ["スイマーも睡魔に負けた…", + "すいません"], + ["後ろに何か来てないか?", + "じゃあ、バックミラーで見てみら~!"], + ["床って、ゆーかー!", + "ゆかいだな、キミは"], + ["あなたは配送業ですか??", + "はい、そうです"], + ["馬が、", + "ウマれた!"], + ["馬は走るのが、", + "ウマい!"], + ["猫が風邪で、", + "ねこんだ"], + ["逃げた虎を", + "捕らえた"], + ["豚が", + "ぶたれた?"], + ["犬が", + "いぬる"], + ["ラクダに乗ると", + "楽だ"], + ["象だ", + "ゾウー!?"], + ["トドが動物園に", + "トドいた"], + ["熊を", + "かくまう!"], + ["カバを", + "かばった"], + ["あのカバ、", + "かばいい!"], + ["ロバは", + "こロバない"], + ["ワニが", + "わになって遊んでる~"], + ["ネズミが", + "寝ずに(ネズミ)番をした"], + ["サルも", + "リハーサルをしてます"], + ["サルが", + "去る"], + ["かめない", + "亀"], + ["カエルが", + "池にカエル"], + ["ハチと", + "ハチ合わせ!"], + ["アブは", + "あぶない!"], + ["コウモリも", + "子守でたいへん!"], + ["ハエが", + "はえ~"], + ["鳥がエサを", + "トリ返した"], + ["カモメがこっちに", + "来るかもめ!"], + ["コンドルが", + "飛んどる"], + ["ハトが豆鉄砲うたれて", + "ハッとした!"], + ["アジを刺身を", + "アジわおう!"], + ["ヒラメが", + "ヒラメいた!"], + ["かれいに泳ぐ", + "カレイ"], + ["冷めてる", + "サメ"], + ["こぶりな", + "ブリ"], + ["マグロを焦がして", + "マッグロにしちゃった"], + ["鯛は", + "めでタイ!"], + ["コイも", + "恋するらしいよ!"], + ["フナも", + "船酔いするんだって!"], + ["カニは", + "いるかに?"], + ["貝は", + "おいしいカイ?"], + ["いかした", + "イカ~"], + ["イカの料理は、", + "イカがですか?"], + ["イクラは", + "いくら?"], + ["うめぼしが", + "うめ~!"], + ["のりを食べて…", + "ノリノリだぜ~!"], + ["ワカメは", + "よくかめ!"], + ["肉が", + "ニクい~!"], + ["このみそを", + "食べてみそ!"], + ["コーラを", + "コオラした"], + ["抹茶がしぶくて、", + "こまっちゃう…"], + ["プリンを食べて", + "しらんプリン!"], + ["栗にさわって", + "びっクリ!"], + ["ネギを", + "ねぎった!"], + ["秋の味覚は、", + "いつ食べてもアキないね~"], + ["暑さでビールの売り上げも、", + "のビール!"], + ["チョコを", + "ちょこっと食べる"], + ["コショウで", + "故障"], + ["ダイコン売り場は", + "いつもだいこんざつ!"], + ["稲をかるのに", + "誰もイネー"], + ["サクラが", + "さくらしい!"], + ["球があたって", + "タマげた!"], + ["棚が落ちてきて", + "まいっタナ~"], + ["板がぶつかってきて", + "イタかった…"], + ["アメリカの雨は", + "アメージング!"], + ["綱引きの勝負は、どろですべって", + "ドロー!"], + ["そりがぶつかて、", + "アイムソーリー!"], + ["月を見てうなった、", + "「ム~~ン」"], + ["これじゃ火星を見るのは、", + "マ~ズむり!"], + ["太陽が", + "サンサンと輝いている"], + ["台所は", + "キッチンとせいりせいとん!"], + ["マスカットを、", + "まずカット!"], + ["油だって日が経つと", + "老いる(オイル)"], + ["夏じゃなきゃ、", + "サマー(様)になんないよ"], + ["和尚が2人揃って、", + "オショウガツー!!"], + ["アリが十匹揃って", + "「ありがとう~!」"], + ["盲腸は", + "もう超ツライ。"], + ["ハサミの", + "ギャグはさみ"], + ["No!と言える", + "ノート"], + ["自称", + "辞書マニア"], + ["カサが多すぎて", + "カサばっちゃう"], + ["箱を", + "ハコんだ!"], + ["電話に", + "誰も出んわ~"], + ["時計は!", + "ほっとけい"], + ["僕さぁ", + "ボクサーなんだ"], + ["バスが凄いスピードで", + "すっ飛ばす"], + ["ライター工場で", + "はたライター"], + ["布団が", + "ふっとんだっ!"], + ["靴にガムが", + "くっついた"], + ["靴を脱いで", + "くつろいだ"], + ["下駄で", + "笑い転げた"], + ["ボートに乗って", + "ボーッとする"], + ["タイヤが当たると", + "痛いやー。"], + ["コロンを付けてて", + "転んだ"], + ["帽子で!", + "日焼け防止"], + ["このイス、", + "いーっすねぇ!"], + ["漢文は", + "チンプンカンブンですよ"], + ["ボスが", + "水をこぼす"], + ["奇怪な", + "機械…"], + ["銅像を", + "どうぞ~"], + ["内臓が", + "無いぞう…"], + ["魚の気持ちを", + "サカナでしないようにね"], + ["小判を交番に届けるのを", + "拒んだ"], + ["映画を", + "観に行ってもええが?"], + ["スキーが", + "好き~!"], + ["太陽出ないと", + "冷たいよう"], + ["月で", + "もちつき!"], + ["火星の", + "家政婦!?"], + ["岩は…", + "なにもイワン"], + ["池に", + "行け!"], + ["バレーで", + "頑張れ~!"], + ["鏡を見て、", + "かがみこんだ"], + ["窓が多いと", + "とまどう…"], + ["左遷だけは、", + "させん!"], + ["イカイヨウが", + "いたいよう!"], + ["うちの家内は~", + "おっかないよ"], + ["王子が", + "相談に応じた"], + ["マイルが貯まって、", + "スマイル!"], + ["紅葉を見に", + "行こうよう"], + ["メールが", + "読めーる!"], + ["最近はなんでもメールでさぁ、", + "全く気が滅入るよね"], + ["ラブレターが", + "破られたー"], + ["メーカーに聞かないと", + "だめーかー"], + ["デールが", + "呼んでーる"], + ["ドナルドだって、", + "怒鳴るど~!!"], + ["ドナルドダックが", + "汗だっく"], + ["イーヨーは", + "かっこいーよー!"], + ["プーさんが、", + "ハチミツをミツけた"], + ["サリーはいつも、", + "さりー気ないよね"], + ["ニューヨークで", + "入浴だってさ"], + ["隣の客はよく", + "ギャグ言う客だ"], + ["畑でレタスが", + "取れたっす!"], + ["そのつまらないギャグに、", + "ギャグ切れだ~!?"], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("う~む…","…さむいかも。","あいたた…","イマイチ!") +MovieHealLaughterHits1= ("はははっ!","えへへっ!","ププッ…","あははっ!") +MovieHealLaughterHits2= ("わっはっは!","あっはっは!","がっはっは!") + +# MovieSOS.py +MovieSOSCallHelp = "%s たすけて!" +MovieSOSWhisperHelp = "%sがバトルで助けが必要だって!" +MovieSOSObserverHelp = "たすけて!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "おまたせ%s!\n手助けするよ!" +MovieNPCSOSGoodbye = "また後で!" +MovieNPCSOSToonsHit = "ギャグがきまるよ!" +MovieNPCSOSCogsMiss = "コグはミスするよ!" +MovieNPCSOSRestockGags = "%sのギャグをチャージするよ!" +MovieNPCSOSHeal = "トゥーンアップ" +MovieNPCSOSTrap = "トラップ" +MovieNPCSOSLure = "おとり" +MovieNPCSOSSound = "サウンド" +MovieNPCSOSThrow = "なげる" +MovieNPCSOSSquirt = "みずでっぽう" +MovieNPCSOSDrop = "ドロップ" +MovieNPCSOSAll = "すべて" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "あ~あ!" +MoviePetSOSTrickSucceedBoy = "よくやったね!" +MoviePetSOSTrickSucceedGirl = "いいこ、いいこ!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "中止\n中止\n中止" + +# RewardPanel.py +RewardPanelToonTasks = "トゥーン・タスク" +RewardPanelItems = "とりかえしたアイテム" +RewardPanelMissedItems = "まだとりかえしていないアイテム" +RewardPanelQuestLabel = "クエスト:%s" +RewardPanelCongratsStrings = ["やったね!", "おめでとう!", "いいかんじ!", + "よくやったね!", "サイコー!", "かっこいいよ!"] +RewardPanelNewGag = "%(avName)sに新しいギャグ、\n%(gagName)sのごほうび!" +RewardPanelUberGag = "%(avName)sは %(gagName)sのギャグを%(exp)sのけいけんちでゲット!" +RewardPanelEndTrack = "やったね! %(avName)sは%(gagName)sのギャグを全部ゲットしたよ!" +RewardPanelMeritsMaxed = "まんたん" +RewardPanelMeritBarLabels = [ "カイコツウチ", "ショーカンジョー", "コグドル", "メリット" ] #▲あとで要チェック★★★★★★★★★★★★★★★★★★★★★ +RewardPanelMeritAlert = "格上げの準備OK!" + +RewardPanelCogPart = "コグ変装グッズをゲット!" +RewardPanelPromotion = "%sトラックで\n格上げ準備オーケー!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("ノーマル・トゥーン", "ふつうのトゥーンです。"), + ("ビッグ・ヘッド", "頭が大きくなります。"), + ("スモール・ヘッド", "頭が小さくなります。"), + ("ビッグ・レッグ", "がっしりとした足になります。"), + ("スモール・レッグ", "ほそい足になります。"), + ("ビッグ・トゥーン", "からだが少しおおきくなります。"), + ("スモール・トゥーン", "からだが少しちいさくなります。"), + ("ぺらぺらポートレート", "からだが平べったくなります。"), + ("ぺらぺらプロフィール", "かおが平べったくになります。"), + ("クリア", "ガラスのようにとうめいになります"), + ("ノーカラー", "無色になります"), + ("とうめい", "他の人から見えなくなります"), + ] +CheesyEffectIndefinite = "他のエフェクトを選択するまで、%(whileIn)s%(effectName)s" +CheesyEffectMinutes = "あと%(time)s分間、%(whileIn)s%(effectName)s" +CheesyEffectHours = "あと%(time)s時間、%(whileIn)s%(effectName)s" +CheesyEffectDays = "あと%(time)s日間、%(whileIn)s%(effectName)s" +CheesyEffectWhileYouAreIn = "%sにいる間だけ" +CheesyEffectExceptIn = "%s以外で" + + +# SuitBattleGlobals.py +SuitFlunky = "オベッカー" +SuitPencilPusher = "カリカリン" +SuitYesman = "イエスマン" +SuitMicromanager = "ガミガミーナ" +SuitDownsizer = "リストラマン" +SuitHeadHunter = "ヘッドハンター" +SuitCorporateRaider = "デッパラーダ" +SuitTheBigCheese = "ビッグチーズ" +SuitColdCaller = "ブアイソン" +SuitTelemarketer = "ツーハーン" +SuitNameDropper = "タッシャーナ" +SuitGladHander = "オオゲーサ" +SuitMoverShaker = "クロマクール" +SuitTwoFace = "アイソマン" +SuitTheMingler = "オマカセンヌ" +SuitMrHollywood = "ビッグスマイル" +SuitShortChange = "チョロマカシー" +SuitPennyPincher = "セコビッチ" +SuitTightwad = "ドケッチオ" +SuitBeanCounter = "カッチリン" +SuitNumberCruncher = "スウジスキー" +SuitMoneyBags = "カネモッチン" +SuitLoanShark = "シャークロン" +SuitRobberBaron = "ドロビッグ" +SuitBottomFeeder = "タイコモチー" +SuitBloodsucker = "ガッツキー" +SuitDoubleTalker = "ニマイジタン" +SuitAmbulanceChaser = "ツケコミン" +SuitBackStabber = "ウラギリン" +SuitSpinDoctor = "ドクター・\nトラブル" +SuitLegalEagle = "ホウノトリ" +SuitBigWig = "ビッグホワイト" + +# Singular versions (indefinite article) +SuitFlunkyS = "オベッカー" +SuitPencilPusherS = "カリカリン" +SuitYesmanS = "イエスマン" +SuitMicromanagerS = "ガミガミーナ" +SuitDownsizerS = "リストラマン" +SuitHeadHunterS = "ヘッドハンター" +SuitCorporateRaiderS = "デッパラーダ" +SuitTheBigCheeseS = "ビッグチーズ" +SuitColdCallerS = "ブアイソン" +SuitTelemarketerS = "ツーハーン" +SuitNameDropperS = "タッシャーナ" +SuitGladHanderS = "オオゲーサ" +SuitMoverShakerS = "クロマクール" +SuitTwoFaceS = "アイソマン" +SuitTheMinglerS = "オマカセンヌ" +SuitMrHollywoodS = "ビッグスマイル" +SuitShortChangeS = "チョロマカシー" +SuitPennyPincherS = "セコビッチ" +SuitTightwadS = "ドケッチオ" +SuitBeanCounterS = "カッチリン" +SuitNumberCruncherS = "スウジスキー" +SuitMoneyBagsS = "カネモッチン" +SuitLoanSharkS = "シャークロン" +SuitRobberBaronS = "ドロビッグ" +SuitBottomFeederS = "タイコモチー" +SuitBloodsuckerS = "ガッツキー" +SuitDoubleTalkerS = "ニマイジタン" +SuitAmbulanceChaserS = "ツケコミン" +SuitBackStabberS = "ウラギリン" +SuitSpinDoctorS = "ドクター・トラブル" +SuitLegalEagleS = "ホウノトリ" +SuitBigWigS = "ビッグホワイト" + +# Plural versions +SuitFlunkyP = "オベッカー" +SuitPencilPusherP = "カリカリン" +SuitYesmanP = "イエスマン" +SuitMicromanagerP = "ガミガミーナ" +SuitDownsizerP = "リストラマン" +SuitHeadHunterP = "ヘッドハンター" +SuitCorporateRaiderP = "デッパラーダ" +SuitTheBigCheeseP = "ビッグチーズ" +SuitColdCallerP = "ブアイソン" +SuitTelemarketerP = "ツーハーン" +SuitNameDropperP = "タッシャーナ" +SuitGladHanderP = "オオゲーサ" +SuitMoverShakerP = "クロマクール" +SuitTwoFaceP = "アイソマン" +SuitTheMinglerP = "オマカセンヌ" +SuitMrHollywoodP = "ビッグスマイル" +SuitShortChangeP = "チョロマカシー" +SuitPennyPincherP = "セコビッチ" +SuitTightwadP = "ドケッチオ" +SuitBeanCounterP = "カッチリン" +SuitNumberCruncherP = "スウジスキー" +SuitMoneyBagsP = "カネモッチン" +SuitLoanSharkP = "シャークロン" +SuitRobberBaronP = "ドロビッグ" +SuitBottomFeederP = "タイコモチー" +SuitBloodsuckerP = "ガッツキー" +SuitDoubleTalkerP = "ニマイジタン" +SuitAmbulanceChaserP = "ツケコミン" +SuitBackStabberP = "ウラギリン" +SuitSpinDoctorP = "ドクター・トラブル" +SuitLegalEagleP = "ホウノトリ" +SuitBigWigP = "ビッグホワイト" + +SuitFaceOffDefaultTaunts = ['ワッ!'] #★ + +SuitAttackDefaultTaunts = ['これでもくらえ!', '私の前から消えろ!'] + +SuitAttackNames = { + 'Audit' : 'カンサ!', + 'Bite' : 'ガブット!', + 'BounceCheck' : 'フワタリコギッテ!', + 'BrainStorm' : 'ブレインストーム!', + 'BuzzWord' : 'リュウコウゴ!', + 'Calculate' : 'ケイサン!', + 'Canned' : 'カンヅメ!', + 'Chomp' : 'ムシャムシャ!', + 'CigarSmoke' : 'ハマキ!', + 'ClipOnTie' : 'クリップネクタイ!', + 'Crunch' : 'キンユウキキ!', + 'Demotion' : 'コウカク!', + 'Downsize' : 'シュクショウ!', + 'DoubleTalk' : 'オセジ!', + 'EvictionNotice' : 'オイタテツウチ!', + 'EvilEye' : 'コワイシセン!', + 'Filibuster' : 'ボウガイ!', + 'FillWithLead' : 'エンピツゼメ!', + 'FiveOClockShadow' : "ブショウヒゲ!", + 'FingerWag' : 'チッチッ!', + 'Fired' : 'ヒノクルマ!', + 'FloodTheMarket' : 'シジョウハンラン!', + 'FountainPen' : 'マンネンヒツ!', + 'FreezeAssets' : 'オサムイフトコロ!', + 'Gavel' : 'コヅチ!', + 'GlowerPower' : 'スルドイシセン!', + 'GuiltTrip' : 'ザイアクカン!', + 'HalfWindsor' : 'ハーフウィンザー!', + 'HangUp' : 'ガチャギリ!', + 'HeadShrink' : 'マメアタマ!', + 'HotAir' : 'ネップウ!', + 'Jargon' : 'センモンヨウゴ!', + 'Legalese' : 'ホウリツヨウゴ!', + 'Liquidate' : 'ミズノアワ', + 'MarketCrash' : 'ケイザイシンブン!', + 'MumboJumbo' : 'ミツダン!', + 'ParadigmShift' : 'パラダイムシフト!', + 'PeckingOrder' : 'ピーチクパーチク!', + 'PickPocket' : 'スティール!', + 'PinkSlip' : 'カイコツウチ!', + 'PlayHardball' : 'チョッキュウショウブ!', + 'PoundKey' : 'シャープキー!', + 'PowerTie' : 'チョウネクタイ!', + 'PowerTrip' : 'ショッケンランヨウ!', + 'Quake' : 'ジシン!', + 'RazzleDazzle' : 'ビジネススマイル!', + 'RedTape' : 'ガンジガラメ!', + 'ReOrg' : 'サイヘンセイ!', + 'RestrainingOrder' : 'キンシメイレイ!', + 'Rolodex' : 'ローロデックス!', + 'RubberStamp' : 'ハンコ!', + 'RubOut' : 'ナカッタコトニ!', + 'Sacked' : 'フクロヅメ!', + 'SandTrap' : 'ドロヌマ!', + 'Schmooze' : 'ホメゴロシ!', + 'Shake' : 'シェイク!', + 'Shred' : 'シュレッダー!', + 'SongAndDance' : 'ウタッテオドッテ!', + 'Spin' : 'スピン!', + 'Synergy' : 'シナジー!', + 'Tabulate' : 'データサクセイ!', + 'TeeOff' : 'セッタイゴルフ!', + 'ThrowBook' : 'ロッポウゼンショ!', + 'Tremor' : 'シンドウ', + 'Watercooler' : 'ウォータークーラー!', + 'Withdrawal' : 'ザンダカショウカイ!', + 'WriteOff' : 'チョウケシ!', + } + +SuitAttackTaunts = { + 'Audit': ["帳簿と数字が合っていませんよ~", + "おやおや、 赤字ですか?", + "帳簿なら私におまかせを。", + "負債が大きすぎるんじゃないですか~?", + "まずあなたの資産を見てみましょうか?", + "負債というものがなんだか知っていますか?", + "さーて、 あなたの負債は?", + "口座をスッカラカンにしてやる!", + "ムダ遣いは いけませんねぇ。", + "おや、帳簿にミスがありますよ。", + ], + 'Bite': ["かまれるのは好きかい?", + "こいつにかまれると痛いよ!", + "がっつくのは良くありませんよ。", + "行くよ、ガブガブアタック!", + "これでも食らいな!", + "気をつけな、ボットだってかむんだぜ。", + "ガブっといってやる!", + "腹へったな~!", + "今日はおながペコペコだ!", + "一口だけでも食わせてくれよ。", + ], + 'BounceCheck': ["こぎって小切手?", + "お支払いの徴収に参りました。", + "この小切手はあなたのですね?", + "あなた、私に借りがあるんですよ。", + "借金の徴収に来ましたよ。", + "この小切手、使えませんよ。", + "罰金です!", + "あなたに払いきれますかねぇ。", + "これは高くつきますよ。", + "これを現金化したいんです。", + "小切手を使うなんて、なまいきな!", + "手切れ金のつもりかな?", + "このサービス料は高くつくよ。", + ], + 'BrainStorm':["今日の予報:大雨!", + "カサは持ってきましたか?", + "ピカッとひとつ来そうだ!", + "ひと降りきたみたいだな。", + "落雷注意報だ!", + "雲行きが怪しくなってきたよ。", + "いいこと思いついたぞ!", + "電光石火とはこのことだ!", + "濡れるのはお好きかい?", + ], + 'BuzzWord':["この言葉を知ってるかな?", + "最新の流行語を聞いた?", + "さすがのキミもこれは知らんだろう。", + "キミ、遅れてるよ~。", + "耳をちょっと貸しなさい。", + "情報収集はちゃんとやらねば!", + "そんなんじゃ付いていけないよ?", + "遅れてるキミに情報のプレゼントだ!", + "これぐらい知ってなきゃダメだよ。", + "キミ、これぐらい知ってなきゃ~", + ], + 'Calculate': ["総額が大変なことになってますね。", + "ちゃんと計算しないとダメですよ。", + "数字をバカにしちゃいけませんね。", + "計算のひとつも出来ないようじゃダメだよ。", + "使ったお金はちゃんと記録したかい?", + "私の計算によれば…キミはもうおしまいだ!", + "これがあなたの負債総額です!", + "おやおや、ちょっと浪費しすぎなんじゃない?", + "数字遊びはお好きかい?", + Cogs + "=∞ トゥーン=0", + ], + 'Canned': ["カンヅメは好き?", + "どっ「カーン」!", + "私は生よりカンヅメが好きなのだよ。", + "カンヅメにされたことはあるかい?", + "このカンをキミにプレゼントだ!", + "カンケリゲームのスタートだ!", + "カンカンカン!", + "まるくてかたくてつめたいもの、なーんだ?", + "カンヅメ作業は得意だぜ。", + "キミはカンヅメにしてもまずそうだねぇ。", + ], + 'Chomp': ["どうだ、このりっぱな歯!", + "ムシャムシャムシャ!", + "食べるときはゴーカイに!", + "いただきまーす!", + "これでも食らえ!", + "キミが私のディナーさ!", + "トゥーンは私の大好物さ!", + ], + 'ClipOnTie': ["正装こそ、できる人間のたしなみさ。", + "勝負の場ではネクタイをつけなきゃね。", + "おしゃれな" + Cogs + "にネクタイは不可欠!", + "これでも試しに着けてみな。", + "サクセスストーリーは一本のネクタイから。", + "ネクタイなしじゃみっともないよ。", + "これをつけるのを手伝ってあげるよ。", + "ネクタイと書いて権力と読むのさ!", + "さて、キミにはこのサイズかな?", + "おやおや、これじゃ首がしまってしまうね。", + "その格好、ラフすぎるね。", + "ネクタイでクタクタにしてやる!", + ], + 'Crunch': ["キミ、危なっかしいなぁ。", + "金融危機の波が来る!", + "札束の味は苦い味!", + "危機がきた!", + "これを生き残れるかな?", + "これでキミも一文無しだ!", + "備えあれば憂いなし!", + "キミは危機に強いほうかい?", + "キミのお先は真っ暗さ!" + ], + 'Demotion': ["一直線に下っ端行きだ!", + "キミには日のよく当たる席を用意したよ。", + "威張れる肩書きもなくなったね。", + "キミを蹴落としてみせる!", + "おやおや、 行き詰まりかい?", + "あっという間に行き場なし!", + "キミの人生、 行き止まりさ!", + "しばらくそこであがいてなさい。", + "運も才能のうちってやつさ。", + "キミの履歴書、見ていて恥ずかしいよ。", + ], + 'Downsize': ["ちっちゃくなーれ。", + "くらえ、 縮小ショック!", + "これ、英語ではダウンサイズっていうんだよ。", + "見苦しいなぁ、ちょっと小さくなってくれ。", + "キミ、ちょっと態度が大きいんじゃないかい?", + "目障りだなぁ…", + "私はちっちゃい人間を見下すのがだーいすき!", + "おやゆび姫って聞いたことある?", + "お客様、50円増しでSサイズにできますが?", + "私はちいさい人が好きなんだよね。", + "もっと小さくすることだってできるんだよ?", + "このアタックの対象はフリーサイズだ!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["はいはい、どいたどいた!", + "あんたの肩書きにさようなら、だ!", + "そろそろ安アパートに引越しだ。", + "クビにならなかっただけマシだよ。", + "まったく…キミにはこれしかないみたいだね。", + "キミにこれをつきつけるのは心苦しいなぁ。", + "出直してきな!", + "そろそろキミにもさよならだ。", + "キミの居場所はもうないよ。", + "左遷だ!", + "今のキミはギリギリの状態にあるんだよ?", + ], + 'EvilEye': ["悪い子には大目玉くらわせるぞ!", + "私の目が黒いうちは好きにさせん!", + "ああ、何かが眼に入ってしまったようだ。", + "私はキミに眼を付けていたのさ!", + "眼力なら負けないぞ!", + "私はコワいものが大好き!", + "キミはどこに目がついてるんだ?", + "あなたは私がコワくなーる、コワくなーる。", + "「台風の目」に巻き込んでやる!", + "あー、眼がゴロゴロしてきた。", + ], + 'Filibuster':["ボーガイボーガイ!てっていそしだ!", + "こうなったら牛歩だ!", + "私は妨害がだーいすきなんだ。", + "キミの進路を妨害してやる!", + "どんな手を使ってでも妨害する!", + "しゃべってしゃべって邪魔してやる!", + "このヤジに耐えられるかな?", + "あげあし取りならまかせろ!", + "口げんかなら負けないぞ!", + "妨害なら私にまかせなさい。", + ], + 'FingerWag': ["ふん、まだまだだな。", + "ちょっとキミ、そこに座りなさい。", + "笑わせてくれるねえ、まったく。", + "この私を怒らせるなよ。", + "何度も同じ事を言わせるな!", + "人の話は聞くものだよ。", + "キミ、われわれ" + Cogs + "に対して失礼なんじゃないかい?", + "そろそろ私の言うことに耳を傾けなさい。", + "ペラペラペラペラ…よくしゃべるね、まったく。", + "そんな考えは通用しないよ。", + "だからキミは半人前なんだよ。", + "まったく、未熟者だねぇ。", + ], + 'Fired': ["バーベキューは好きかい?", + "私は熱いほうが好きなんだよ。", + "ちょっと寒くないかい?", + "ファイヤーーー!", + "ホット!ホット!", + "ホットでジューシーに仕上げてやるよ。", + "ジュッとコゲちゃえ!", + "焼きかげんはウェルダンで?", + "燃え尽きちゃった?", + "日焼け止めは塗ったかい?", + "コンガリ焼いてやる!", + "ハデに炎上と行こうぜ!", + "灰になれ!", + "キミ、けっこう熱いね。", + "頭から火が出そうだよ。", + "ここは強火で行ってみよう!", + "コンガリトゥーン、いっちょあがり!", + "ナマ焼けは身体によくないよ。", + ], + 'FountainPen': ["シミ抜きの準備はいいかい?", + "私のペンは書き味ばつぐん!", + "このシミは絶対抜けないよ~", + "失礼、インクの出が悪いようだ。", + "着替えは持ってきたかい?", + "えーと、どこにサインしたらいいのかな?", + "ほら、私のペンをお使いなさい。", + "私の字、読めるかな?", + "書き味はどうかな?", + "「万年」ヒラにはこれがお似合いさ!", + "おっと、インクを入れすぎちゃったようだ。", + ], + 'FreezeAssets': ["ふところが寒いようだねぇ。", + "なんだか寒くないかい?", + "私はぬくぬく、キミは寒々。", + "景気が冷え込んでますね。", + "お寒い世の中ですな。", + "世間の風は冷たいもんだよ。", + "景気も人も冷えきってるね。", + "こう見えて私はけっこうクールなのさ。", + "これで凍えてしまいなさい。", + "凍傷ってなんだか知ってるかい?", + "寒さで震えあがっちゃうね。", + "私はいわゆる「冷」血漢らしいんだよ。", + ], + 'GlowerPower': ["なにガンくれてやがんでぇ。", + "私の視線は鋭いらしい。", + "突き刺すような視線ってこういうことさ。", + "私のチャームポイントはこの目!", + "チラっと見じゃものたりないな。", + "ごらん、表情豊かな目だろ?", + "何見てんだよ!", + "見~~た~~な~~~", + "あっかんべー!", + "私の目をよーく見てみなさい。", + "あなたの未来が見える…それは絶望!", + ], + 'GuiltTrip': ["謝るまでせめ続けてやる!", + "ほんとに悪いトゥーンだね!", + "全部キミのせいだ!", + "まったく、キミときたらいつもいつも…", + "キミには罪の意識ってものがないのかい?", + "キミなんて嫌いだ、もう話さない!", + "「ごめん」と一言言ったらどうなんだ?", + "百年経ってもキミのことは許さないよ!", + "泣いて謝ってもダメだからね。", + "キミはひどいトゥーンだね。", + "キミはもっとできると思ってたよ。", + ], + 'HalfWindsor': ["こんなハデなネクタイ、見たことないだろう?", + "ハイハイ、巻きこまれすぎないようにね。", + "しめつけちゃうよ!", + "ネクタイのしめかたも知らないのか?", + "キミにこのネクタイを買う金はないだろうな。", + "トゥーンもたまにはネクタイをしてみなさい。", + "キミにはもったいない気もするが…", + "まったく、キミ相手に使うことになるとはね。", + "キミにはちょっと早すぎるかな。", + ], + 'HangUp': ["接続が切れました!", + "バイバイ!", + "そろそろキミとのつながりを切ろうかな。", + "もう電話してこないで!", + "ガチャン!", + "キミと話すことはもうない!", + "私の時間のムダだ!", + "はい、さよーなら!", + "ん?接続が弱いみたいだね?", + "ハイ、時間切れでーす。", + "ハッキリ聞こえるようにしてやろう。", + "間違い電話です!", + ], + 'HeadShrink': ["よくも私の顔をつぶしてくれたな!", + "ミクロキッズならぬミクロトゥーンだ!", + "プライドも一緒にちっちゃくなるか?", + "せんたくしたらいつもこうなるの?", + "「わがはいは縮小されている。名前はもうない」", + "大きいのは図体だけか!", + "キミ、頭がどうかしちゃってるんじゃないか?", + "考えが足りないのはいけないよ。", + "キミはスケールの小さいトゥーンだね。", + "トゥーンは小さければ小さいほどいいんだってよ?", + ], + 'HotAir':["だんだん議論が過熱してまいりました!", + "あー、あついあつい。", + "沸点を迎えそうだ!", + "くらえ、熱風!", + "熱く語り合おうじゃないか。", + "火がないところに煙はたたないってよ?", + "ヒートアップしてきたね。", + "思ったよりアツいヤツだな。", + "私の怒りに火を注いだね?", + "私はこの仕事に燃えてるんだ!", + "トゥーン相手にはつい熱くなってしまうな。", + "ゴミは燃やさなきゃね。", + ], + 'Jargon':["キミ、こんなことも知らないのかい。", + "私はその道のプロなんだよ。", + "プロがしゃべるとこうなる!", + "わからずやには言い聞かせてやる!", + "私は自己主張がはげしいらしいんだが…", + "私にヘタな言い訳は通用しないよ。", + "ま、キミにはわからないだろうけどね。", + "ほら、言葉で人は傷つくんだよ。", + "私の言いたいこと、理解できるかな?", + "言葉の戦いなら負けん!", + ], + 'Legalese':["無駄な抵抗はおやめなさい。", + "法律上、あなたは負けるのです。", + "規約をよくお読みになりましたか?", + "キミは法の下に管理されている!", + "キミを罰する法律があったはずだ。", + "私に過失はない!", + "この攻撃にて見られる表現はディズニー・トゥーンタウン・オンラインの意見ではありません。", + "この攻撃で生じたダメージに関して、当社は一切の責任を持たないものとします。", + "この攻撃による結果は抽選で決定させていただきます。", + "この攻撃は当社指定の場所以外では無効です。", + "キミの行いは認めるわけにはいかない!", + "法律ごとを知らんお前には負けない!", + ], + 'Liquidate':["今回の件はお流れですな。", + "時代の流れが読めないとおぼれますよ。", + "水に流してしまいますよ。", + "ムダな努力を…!", + "おおっと危ない、濡れちまうよ。", + "気をつけていないと呑まれるよ。", + "きれいさっぱり流れてしまえ。", + "沈んでしまえ!", + "ではスッキリ水に流しましょうかね。", + "ムダな努力はやめておきなさい。", + ], + 'MarketCrash':["情報収集はこまめにやらねば!", + "この重さに耐えられるかな?", + "新聞くらい毎日読まなきゃダメじゃないか。", + "これは大事件だ!", + "せめてこれぐらいは読んでおけ!", + "売りに出たほうがいいらしいぞ!", + "私のコラムも読みたまえ!", + "ほれ、一年分だ。", + "これはキミの専門外かな?", + "テレビ欄以外もちゃんと読みなさい。", + "今日の4コマ、おもしろいぞ!", + ], + 'MumboJumbo':["ごにょごにょごにょ…", + "ひそひそひそ…", + "もにょもにょもにょ…", + "もごもごもご…", + "こしょこしょこしょ…", + "むにゃむにゃむにゃ…", + "ぼそぼそぼそ…", + "もそもそもそ…", + "こそこそこそ…", + "…というわけでひとつよろしくお願いしますよ。", + ], + 'ParadigmShift':["時空のシフトを引き起こすぞ!", + "異世界への旅へご案内しますぞ!", + "ふむ、おもしろいパラダイムだ。", + "キミの知らない世界へつれていってあげよう。", + "行ってらっしゃーい!", + "こんな世界があるって知ってたかい?", + "これは生まれて始めてかな?", + "さあ、行った行った!", + "この世界にキミの居場所はないんだよ。", + ], + 'PeckingOrder':["小鳥たちよ、おいで!", + "鳥たちと和むのもたまには必要だよ。", + "みんな、かかれー!", + "幸せの青い鳥につつかれたい?", + "騒いでごまかすのもひとつの方法さ。", + "落し物に注意!", + "議会ではたまにこのくらいうるさくなるんだよ。", + "ピーピー!ピーチクパーチク!", + "一石二鳥を狙うならこいつらはどうだ?", + ], + 'PickPocket': ["いただき!", + "おっ、ちょっとアレ見てみなよ。", + "ちょろいもんだ。", + "重いだろ?持つのを手伝ってあげよう。", + "私は手が早いので有名なんだ。", + "私は手ぐせが悪くてね。", + "脇が甘い!", + "無用心だなぁ。", + "なくし物には気をつけな。", + "キミのモノは私のモノ!", + "おっ、いいもの持ってるじゃないか。", + "サンキュー!", + "ちょっと失礼。", + "これ、どうせいらないだろう?", + ], + 'PinkSlip': ["おおっと、ついに来たか!", + "だから言わんこっちゃない。", + "元気でな!", + "これで二度と会うことはないだろうな。", + "残念だったね。", + "気を落とすなよ。", + "これでキミも終わりだな。", + "人生どこで転ぶかわからないものだね。", + "さよ~なら~~", + "キミの運もつきたってことさ。", + ], + 'PlayHardball': ["ではストレートに言おう!", + "直球勝負なら負けん!", + "バッターアップ!", + "ねらうならど真ん中!", + "ピッチャー、投げました!", + "リリーフピッチャーが必要なんじゃないか?", + "打ち取ってやるさ。", + "真っ向勝負といこうじゃないか。", + "2アウト2ストライク!もう後はない!", + "キミなんかに変化球はもったいないね。", + "何事もストレートに!", + "私はまがったことが嫌いなんだ。", + ], + 'PoundKey': ["これがキミへの伝言だ!", + "コレクトコールをかけたいんだが~", + "おっ、キミにいたずら電話だぞ。", + "私はなんでもシャープなものが好きなのさ。", + "このシャープさは痛いかもな。", + "メッセージは一件あります。", + "キーはたたくものなんだよ!", + "こんな攻撃、見たことないだろ?", + "サイゴニ「#」ヲオシテクダサイ。", + "電話代はちゃんと払ってるかい?", + ], + 'PowerTie': ["蝶々はお好きかな?", + "チョーいいネクタイでしょう、これ。", + "おしゃれな" + Cogs + "に蝶ネクタイは不可欠!", + "これでも試しに着けてみな。", + "サクセスストーリーは一本のネクタイから…", + "ネクタイなしじゃみっともないよ。", + "これをつけるのを手伝ってあげるよ。", + "ネクタイと書いて権力と読む!", + "さて、キミにはこの色かな?", + "こいつみたいにねじまげてやる!", + ], + 'PowerTrip': ["とばされたいのか?", + "遠いところはお好きかな?", + "弱肉強食だよ。", + "トゥーンは私に勝てんよ。", + "権力の前にひれ伏せ!", + "それ、接待費で落とすから。", + "逆らったら左遷だぞ!", + "チカラを持っているのは私なんだよ。", + "逆らうのか?", + "私に逆らったらあとがコワいぞ~", + "身の程知らずなトゥーンだね。", + ], + 'Quake': ["グラグラっとくる時のこの興奮!たまらんね。", + "こんな地震は初めてかい?", + "キミを見てるとぐらっと来るよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "すべてを揺るがしてやる!", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + ], + 'RazzleDazzle': ["私の唇の動きをよーく読んで。", + "カチカチカチ!", + "やっぱり私の歯、最高にかっこいいよね。", + "これを見て驚くなよ~", + "やっぱり歯がきれいじゃないとね~", + "え、きれいすぎてまぶしい?", + "これが本物だなんて信じられないでしょう。", + "びっくりした?", + "どうだ、強くてたくましいこの歯!", + "毎食後の歯磨きがひけつさ!", + "はい、笑って笑って~!", + ], + 'RedTape': ["決まりは決まり。言うことを聞け!", + "規則でがんじがらめにしてやる。", + "しばらく静かになってもらおう。", + "これから抜け出せるかな?", + "べたべたなシチュエーションかも知れないね。", + "閉所恐怖症だって?そいつは好都合だ。", + "刃向かえるものなら刃向かってみろ。", + "しばりつけてやる。", + "ほどけるもんならほどいてみな。", + "手も足も出まい!", + ], + 'ReOrg': ["散らかっているのが嫌いでね。", + "このまちはトゥーンだらけで整理が必要だ。", + "ちょっと身の回りを整頓してみたらどうだ。", + "キミのようなゴミが嫌いなんだよ。", + "手始めにキミから整頓しよう。", + "身辺整理をしてみたら?", + "まったく、トゥーンばかりごちゃごちゃと…", + "年に一度の大掃除だ!", + "キミ、ジャマなんだよ。", + "まずはキミから掃除してやる!", + ], + 'RestrainingOrder': ["私を傷つける行為は禁止!", + "やっていい事とわるい事があるだろう!", + "私の3メートル以内に近づくことを禁止する!", + "距離を保ちなさい。", + "命令だ!", + Cogs + "!そのトゥーンを捕まえなさい!", + "あれもこれも、それもしちゃダメ!", + "禁止!", + "これには逆らえまい!", + "トゥーンはおとなしくしていなさい!", + "だめ!" + ], + 'Rolodex': ["キミの名刺、このへんに入ってたんだが…", + "よろしくお願いいたします。", + "ほれ、私の名刺だ。", + "私はこういうモノだが。", + "はじめまして!", + "今後もよろしくお見知りおきを。", + "どーも、どーも。", + "こいつらは切れ味するどいよ。", + "何かあったらいつでもこちらへどうぞ。", + "私の顔を知らないとは!", + "キミ、名刺すら持ってないのかい。", + ], + 'RubberStamp': ["どこに押せばいいのかな?", + "ハイハイ、どんどん書類持ってきて~", + "認め印でいい?", + "「たいへんよくできました」", + "「もっとがんばりましょう」", + "「あとすこしです」", + "「ふつうです」", + "「よくできました」", + "特別に押してやろう!", + "ポンポンっとな。", + ], + 'RubOut': ["おおっと、間違いまちがい。", + "んん?どこ行った?", + "間違いは誰にでもあるものだよ。", + "私の邪魔になるものは消す!", + "間違いはすぐに消す主義なんだ。", + "キミの存在を消しちゃうよ。", + "ゴミはいらないんだよ。", + "消えてなくなれ!", + "あれ、さっきは姿が見えたのに…まぁいいか。", + "フェードアウトってやつ?", + "問題解決にはこれがいちばん!", + "おっと、しまったしまった。", + ], + 'Sacked':["さーて、どこに送ってやろうか。", + "リボンもつけてやろうか。", + "袋だたきにしてやる。", + "ビニール袋と紙袋、どっちがいい?", + "私の前から消えなさい!", + "二度とその顔を見せるな!", + "キミのことはもう要らんのさ。", + "キミにもう用はないのさ、達者でな!", + "たしか明日がゴミの日だったな。", + "うーむ、これって生ゴミになるのかな。", + ], + 'Schmooze':["ああ、キミはトゥーンの鑑だ!", + "キミ、本当にサイコー!", + "あなたにお会いできて光栄です~!", + "まったくキミは素晴らしい!", + "すごいねキミ、いやホント!", + "いやはや、恐れいりました~!", + "いやいや、あなたにはかないませんよ!", + "いや、まったくもってうらやましいですな!", + "あなたのお噂はかねがね!", + "感激のあまり涙がとまりませ~ん!", + "ああ、神様トゥーン様!", + ], + 'Shake': ["シェイクする時のこの興奮!最高だね。", + "こんなシェイクは初めてかい?", + "キミを見てるとぐらっと来るよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "すべてをシェイクしてやる!", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + "シェイクシェイク!", + "私はシェイクが大好きでね。", + "ちょっと体重が増えたみたいだな。", + ], + 'Shred': ["こいつは極秘資料だからね。", + "キミに見られるわけにはいかないんだ。", + "処分しなくては。", + "見たな!?", + "証拠インメツだ!", + "もみ消しは手際が大事なんだよ。", + "キミのアイデア、使えないね~", + "コマギレにしてやる!", + "キミみたいなのが外に漏れるわけにはいかないんだ。", + "シュレッドしたらリサイクル。", + "「社外秘」", + ], + 'Spin': ["グルグル~~", + "まったく、目が回るようじゃないか!", + "回れ回れ~~", + "え、何?回りたい?", + "バレリーナも真っ青だな!", + "さあ、何回まわれるかな?", + "もう首がまわらないんじゃないか?", + "おお、コマのようだな。", + "いつもより多く回しております。", + ], + 'Synergy': ["役員会に連れて行く。", + "キミのプロジェクトは 中止だ。", + "キミの予算は カットされたよ。", + "キミのチームは 他チームに吸収だ。", + "賛成多数で キミの降格が決定した。", + "組織とは シビアなものなんだよ。", + "チームにとってのガンは早めに取り除かんと。", + "まあ、これについては また後日。", + "残念ながらキミの チカラにはなれないよ。", + "多少の犠牲は やむを得んだろうな。", + ], + 'Tabulate': ["おや、計算が合わないな。", + "計算上、キミは負ける。", + "こんなデータ、見たことない!", + "今キミのデータを計測中だ。", + "データを見てみるかい?", + "損得勘定は大事だよ。", + "データ命!", + "私は計算しなくては気が済まないんだ。", + "出たぞ、計測結果は…。", + "ふむ、素晴らしいデータだ。", + ], + 'TeeOff': ["いやいや、今日は天気もいいですなぁ。", + "ファ~~~~!", + "ナイスショット!", + "キャディーさん、1番アイアン!", + "ハザードは避けなきゃな…", + "よっと!", + "ホールインワン目指しますよ~~!", + "いやー、私なんぞはまだまだ。", + "ドラコン賞はいただき!", + "池ポチャかな~", + "風向きヨシ!", + "もう1ラウンド行きますか?", + ], + 'Tremor': ["まさかこれしきの震動がこわいとか?", + "今の感じたかい?", + "今のは余震にすぎないよ。", + "おや、これはキミが震えてるのかい?", + "こりゃいったい震度いくつだろ?", + "恐怖に震えてるのか?", + "立っていられるかな?", + "これでもまだ耐えるっていうのか?", + "足元からくずしてやる!", + ], + 'Watercooler': ["これで頭を冷やしな!", + "顔洗って出直してこい!", + "みっともない、これできれいにしな!", + "ばっしゃーん!", + "シャワーでも浴びな。", + "心配しないで大丈夫、 ろ過してあるから。", + "ちゃんとお風呂入ってるのかい?", + "ちゃんと洗濯してるかい?", + "水遊びは好き?", + "ノド乾いてるんだろ?", + "服が色落ちしちゃうね。", + "水分補給が必要だな。", + ], + 'Withdrawal': ["もう残金がありませんよ。", + "口座にお金は残っていますか~?", + "利子が倍返しになるよ。", + "借金地獄になっちゃうよ。", + "そろそろ振り込みをしないとあぶないよ。", + "家計がまずいことになってるんじゃないか?", + "もしかして、赤字?", + "お買い物は計画的に!", + "破産が近いんじゃない?", + "口座がなくなっちゃうよ。", + ], + 'WriteOff': ["損失をチェックしてみようか。", + "こりゃー、分が悪い取引をしちゃってるね。", + "給料が来月出るかもあやしいかもね。", + "これはひどいな。", + "キミの負債を算出してるんだが…", + "生じた損害には責任を持ってもらう。", + "ボーナスのことは忘れたほうがいいね。", + "キミのアカウントを見てみよう。", + "全てはやりくり次第だよ。", + "限界ギリギリまで行くね、これは。", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "他のプレイヤーを待っています…", + +# Elevator.py +ElevatorHopOff = "おりる" +ElevatorStayOff = "一度おりたら、みんなもおりるのを待つか\n次のエレベーターを待ってね。" +ElevatorLeaderOff = "おりるタイミングはリーダーにまかせてね。" +ElevatorHoppedOff = "次のエレベーターを待ってね。" +ElevatorMinLaff = "このエレベーターに乗るにはゲラゲラポイントが%s必要です。" +ElevatorHopOK = "OK" +ElevatorGroupMember = "のるタイミングは\nリーダーにまかせてね。" + +# DistributedCogKart.py +KartMinLaff = "このカートに乗るには\nゲラゲラメーターが%s必要です。" + +# DistributedBuilding.py ★ +# DistributedElevatorExt.py ★ +CogsIncExt = "・インク" +CogsIncModifier = "%s" + CogsIncExt +CogsInc = Cogs.upper() + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "コンコン!" +DoorWhosThere = "そこにいるのはだーれ?" +DoorWhoAppendix = "ってだーれ?" +DoorNametag = "ドア" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "ギャグが必要だよ!チュートリアル・トムに話しかけてみてね。" +FADoorCodes_DEFEAT_FLUNKY_HQ = "オベッカーを倒したらまた来てね!" +FADoorCodes_TALK_TO_HQ = "ごほうびがHQスタッフのハリーからもらえるよ!" +FADoorCodes_WRONG_DOOR_HQ = "間違い!プレイグラウンドに行くドアはもうひとつの方だよ。" +FADoorCodes_GO_TO_PLAYGROUND = "間違い! プレイグランドに行かなくちゃ!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "バトルをはじめるには、オベッカーに近づいてみて!" +FADoorCodes_TALK_TO_HQ_TOM = "トゥーンHQでごほうびがもらえるよ!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "気をつけて!そこには「コグ」がいるよ!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとコグに変装しよう!\n\nコグファクトリーからパーツを手に入れて変装しよう!" +FADoorCodes_CB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとマネーボットに変装しよう!\n\nドリームランド内のタスクをして変装パーツを手に入よう!" +FADoorCodes_CB_DISGUISE_INCOMPLETE = "トゥーンの姿のままで入るとつかまるから、ちゃんとロウボットに変装しよう!\n\nドリームランド内のタスクをして変装パーツを手に入よう!" +FADoorCodes_LB_DISGUISE_INCOMPLETE = "トゥーンのままだとつかまっちゃうよ!まずコグの変装パーツを集めよう。\n\nドナルドのドリームランドでもらえるトゥーンタスクをやるとロウボットの変装ができるよ。" + +# KnockKnock joke contest winners ▲ +KnockKnockContestJokes = { + 2100 : ["ハイドン", + "こんな名前だけど音楽は苦手なんだ~"], + + 2200 : ["マックス", + "そんなにマクスたてるなよぉ~"], + + 2300: ["ジャスティン", + "間にあったね。ジャスト・イン・タイム!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdin HQ wants a word with you."], + 6 : ["Weirdo", + "Weirdo all these Cogs come from?"], + 30 : ["Bacon", + "Bacon a cake to throw at the Cogs."], + 28: ["Isaiah", + "Isaiah we go ride the trolley."], + 12: ["Juliet", + "Juliet me in that Cog building with you and I'll give you a Toon-Up."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ +# ["ダレ", +# "なぁんだ、わかってるんじゃん!"], + +# ["ダズンDozen", +# ""], + +# ["フレディ", +# "Freddie or not, here I come."], + +# ["ディッシュDishes", +# "Dishes your friend, let me in."], + + ["ウッディーだよ", + "でも金物屋なんだ。"], + +# ["ベティBetty", +# "Betty doesn't know who I am."], + +# ["ケントKent", +# "Kent you tell?"], + +# ["ノアNoah", +# "Noah don't know who either."], + +# ["しらないI don't know", +# "Neither do I, I keep telling you that."], + +# ["ハワードHoward", +# "Howard I know?"], + +# ["エマEmma", +# "Emma so glad you asked me that."], + +# ["オートAuto", +# "Auto know, but I've forgotten."], + +# ["ジェスJess", +# "Jess me and my shadow."], + +# ["ワンOne", +# "One-der why you keep asking that?"], + +# ["アルマAlma", +# "Alma not going to tell you!"], + +# ["ズームZoom", +# "Zoom do you expect?"], + +# ["エイミーAmy", +# "Amy fraid I've forgotten."], + +# ["アーファーArfur", +# "Arfur got."], + +# ["ユアンEwan", +# "No, just me"], + +# ["コージーCozy", +# "Cozy who's knocking will you?"], + + ["サムだよ", + "でも本当はあつがりなんだ…"], + +# ["フォジーFozzie", +# "Fozzie hundredth time, my name is " + Flippy + "."], + +# ["ディダクトDeduct", +# Donald + " Deduct."], + +# ["マックスMax", +# "Max no difference, just open the door."], + +# ["N.E.N.E.", +# "N.E. body you like, let me in."], + +# ["アモス(エイモス)Amos", +# "Amos-quito bit me."], + +# ["アルマAlma", +# "Alma candy's gone."], + + ["ブルースだよ", + "ジャズが大好きなブルースだよ。"], + +# ["コリーンColleen", +# "Colleen up your room, it's filthy."], + +# ["エルシーElsie", +# "Elsie you later."], + +# ["ヒューHugh", +# "Hugh is going to let me in?"], + +# ["ヒューゴHugo", +# "Hugo first - I'm scared."], + +# ["アイダIda", +# "Ida know. Sorry!"], + +# ["イサベルIsabel", +# "Isabel on a bike really necessary?"], + +# ["ジョアンJoan", +# "Joan call us, we'll call you."], + + ["ケイだよ", + "ケイ、エル、エム、オー、ピー…"], + + ["ジャスティンだよ", + "間にあったね。ジャスト・イン・タイム!"], + +# ["ライザLiza", +# "Liza wrong to tell."], + +# ["ルークLuke", +# "Luke and see who it is."], + +# ["マンディMandy", +# "Mandy the lifeboats, we're sinking."], + + ["マックスだよ", + "そんなにマクスたてないで~"], + +# ["ネッティNettie", +# "Nettie as a fruitcake."], + +# ["オリビア", +# "へぇ~!"], + +# ["オスカーOscar", +# "Oscar stupid question, you get a stupid answer."], + +# ["パッツィPatsy", +# "Patsy dog on the head, he likes it."], + +# ["ポールPaul", +# "Paul hard, the door's stuck again."], + +# ["テアThea", +# "Thea later, alligator."], + +# ["タイローンTyrone", +# "Tyrone shoelaces, you're old enough."], + +# ["ステラStella", +# "Stella no answer at the door."], + +# ["ユリアUriah", +# "Keep Uriah on the ball."], + +# ["ドゥエインDwayne", +# "Dwayne the bathtub. I'm drowning."], + +# ["ディスメイDismay", +# "Dismay be a joke, but it didn't make me laugh."], + +# ["オセロットOcelot", +# "Ocelot of questions, don't you?"], + +# ["テルモスThermos", +# "Thermos be a better knock knock joke than this."], + +# ["スルタンSultan", +# "Sultan Pepper."], + +# ["ヴォーンVaughan", +# "Vaughan day my prince will come."], + +# ["ドナルドDonald", +# "Donald come baby, cradle and all."], + +# ["レタスLettuce", +# "Lettuce in, won't you?"], + +# ["イヴォールIvor", +# "Ivor sore hand from knocking on your door!"], + +# ["イサベルIsabel", +# "Isabel broken, because I had to knock."], + +# ["ヘイウッド、ヒュー、、ハリーHeywood, Hugh, Harry", +# "Heywood Hugh Harry up and open this door."], + +# ["フアンJuan", +# "Juan of this days you'll find out."], + + ["アールさ", + "なくてもア~ル!"], + + ["ジミーだよ。", + "でもハデ好きなんだ!"], + +# ["アボットAbbot", +# "Abbot time you opened this door!"], + +# ["ファーディーFerdie", +# "Ferdie last time, open the door!"], + +# ["ドンDon", +# "Don mess around, just open the door."], + +# ["シスSis", +# "Sis any way to treat a friend?"], + +# ["イサドアIsadore", +# "Isadore open or locked?"], + + ["ハリーです", + "でもよくのんきだねって言われるんだ…"], + +# ["テオドアTheodore", +# "Theodore wasn't open so I knocked-knocked."], + +# ["ケンKen", +# "Ken I come in?"], + +# ["ブーBoo", +# "There's no need to cry about it."], + +# ["ユーYou", +# "You who! Is there anybody there?"], + +# ["アイスクリームIce cream", +# "Ice cream if you don't let me in."], + +# ["サラSarah", +# "Sarah 'nother way into this building?"], + +# ["マイキーMikey", +# "Mikey dropped down the drain."], + +# ["ドリスDoris", +# "Doris jammed again."], + +# ["イエルプYelp", +# "Yelp me, the door is stuck."], + +# ["スコルドScold", +# "Scold outside."], + +# ["ダイアナDiana", +# "Diana third, can I have a drink please?"], + +# ["ドリスDoris", +# "Doris slammed on my finger, open it quick!"], + +# ["レタスLettuce", +# "Lettuce tell you some knock knock jokes."], + +# ["イージーだよ", +# "!"], + +# ["オマー", +# "Omar goodness gracious - wrong door!"], + + ["セズだよ", + "道草セズに来たよ~"], + +# ["ダックDuck", +# "Just duck, they're throwing things at us."], + +# ["タンクTank", +# "You're welcome."], + +# ["アイズEyes", +# "Eyes got loads more knock knock jokes for you."], + +# ["ピザ", +# "宅配じゃないよ、ぼくだよ~"], + +# ["クロージュアClosure", +# "Closure mouth when you eat."], + +# ["ハリエットHarriet", +# "Harriet all my lunch, I'm starving."], + +# ["ウッデンWooden", +# "Wooden you like to know?"], + +# ["パンチPunch", +# "Not me, please."], + + ["リッチで~す", + "お金持ちじゃないけど…"], + +# ["ジュピターJupiter", +# "Jupiter hurry, or you'll miss the trolley."], + +# ["ベルタBertha", +# "Happy Bertha to you!"], + +# ["カウCows", +# "Cows go \"moo\" not \"who.\""], + +# ["マグロ(ツナフィッシュ)Tuna fish", +# "You can tune a piano, but you can't tuna fish."], + +# ["コンサンプション(タベル)Consumption", +# "Consumption be done about all these knock knock jokes?"], + +# ["バナナBanana", +# "Banana spilt so ice creamed."], + +# ["エックスX", +# "X-tremely pleased to meet you."], + + ["ハイドンだよ", + "こんな名前だけど音楽は苦手なんだ~"], + +# ["ローダRhoda", +# "Rhoda boat as fast as you can."], + +# ["グワグワQuacker", +# "Quacker 'nother bad joke and I'm off!"], + + ["ナナよ", + "ラッキー!"], + +# ["エーテルEther", +# "Ether bunny."], + +# ["オバサンLittle old lady", +# "My, you're good at yodelling!"], + +# ["ビートBeets", +# "Beets me, I forgot the joke."], + +# ["ハルHal", +# "Halloo to you too!"], + +# ["サラSarah", +# "Sarah doctor in the house?"], + +# ["アイリーンAileen", +# "Aileen Dover and fell down."], + +# ["アトミックAtomic", +# "Atomic ache"], + +# ["アガサAgatha", +# "Agatha headache. Got an aspirin?"], + +# ["スタンStan", +# "Stan back, I'm going to sneeze."], + +# ["ハッチHatch", +# "Bless you."], + +# ["アイダIda", +# "It's not Ida who, it's Idaho."], + +# ["ジッピーZippy", +# "Mrs. Zippy."], + +# ["ユーコンYukon", +# "ユウコじゃなくてユーコン!"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "こんにちは、%!", + "%、会えてうれしいよ。", + "会いに来てくれてありがとう!", + "来てくれたんだね、%!", + ] + +SharedChatterComments = [ + "%って、ステキななまえだね!", + "ステキななまえだね。", + "" + Cogs + "に気をつけてね!", + "トロリーが来たみたいだよ。", + "トロリーゲームをプレイして、パイを集めたいんだ。", + "フルーツパイが食べたくてトロリーゲームをプレイするんだ~", + "" + Cogs + "の一団を追い払ってきたとこなんだ。ひとやすみしようっと!", + "" + Cogs + "の中にも大きいのがいるね、たいへんだ!", + "楽しんでるみたいだね。", + "ああ、今日はさいこうに楽しいなぁ!", + "すてきな服だね。", + "今日は釣りに行ってこようかな。", + "ウチの近所で楽しんでいってね!", + "トゥーンタウンでの生活、楽しんでる?", + "ブルブルランドで雪がふってるらしいよ。", + "今日、トロリーに乗った?", + "あたらしいともだちを作るのは楽しいよ。", + "ブルブルランドにたくさんの" + Cogs + "がいるんだって!", + "鬼ゴッコって楽しいよね!キミは鬼ゴッコ、好き?", + "トロリーゲームって楽しいな~!", + "他のひとを笑わせるのが好きなんだ。", + "ともだちを助けるのって楽しいよ。", + "ええっと、迷子になったの?トゥーンガイドに地図があるから見てみてね。", + "" + Cogs + "の「ガンジガラメ」こうげきはやっかいだよ~", + "" + Daisy + "がガーデンに新しい花を植えたんだって!", + "PageUpキーを押し続けると、上を向けるよ!", + "コグビルをたおすと、ブロンズの星がもらえるよ!", + "Tabキーを押し続けると、周りを自分の視点で見られるよ!", + "Ctrlキーを押すと、ジャンプできるよ!", + ] + +SharedChatterGoodbyes = [ + "そろそろ行かなきゃ。じゃあね!", + "トロリーゲームでもやってこようかな~", + "じゃあ、またね、%!", + "" + Cogs + "をやっつけてこなくちゃ…", + "そろそろ行かなくちゃ。またね。", + "じゃあね、また会おう!", + "バイバイ!", + "またね、%!", + "カップケーキ投げのれんしゅうをしてこようかな。", + "" + Cogs + "をそしするためにも、グループに入ってくるね。", + "会えて嬉しかったよ、%", + "今日はすっごくいそがしいんだ、行かなくちゃ!", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + lToontownCentral+"へようこそ!", #CC_mickey_chatter_greetings01.mp3 + "こんにちは!僕の名前は" + Mickey + "マウス。君の名前は?", #CC_mickey_chatter_greetings02.mp3 + ], + [ # Comments + "ねぇ、キミ!" + Donald + "を見かけなかった?", #CC_mickey_chatter_comments01.mp3 + "これから、うっすらと霧の立ち込めた、"+lDonaldsDock+"に行こうと思うんだ!", #CC_mickey_chatter_comments02.mp3 + "もし僕の友達の" + Goofy + "に会ったら、よろしく言っておいてね。", #CC_mickey_chatter_comments03.mp3 + "ははっ!どうやら" + Daisy + "がお庭に新しいお花を植えたらしいよ。", #CC_mickey_chatter_comments04.mp3 + ], + [ # Goodbyes + "これから、" + Minnie + "に会いにメロディーランドに行こうかなぁ。", #CC_mickey_chatter_goodbyes01.mp3 + "ああっ、" + Minnie + "とのデートにおくれちゃうよ!", #CC_mickey_chatter_goodbyes02.mp3 + "そろそろ" + Pluto + "に晩御飯の準備をしないと…", #CC_mickey_chatter_goodbyes03.mp3 + "キミは"+lDonaldsDock+"に泳ぎに行ったことある?", #CC_mickey_chatter_goodbyes04.mp3 + "ドリームランドにおひるねしに行こうかなぁ…", #CC_mickey_chatter_goodbyes05.mp3 + ] + ) + +MinnieChatter = ( + [ # Greetings + "メロディランドへようこそ!", #CC_minnie_chatter_greetings01.mp3 + "私は" + Minnie + "マウスよ。あなたのお名前は?", #CC_minnie_chatter_greetings02.mp3 + ], + [ # Comments + "ここはいろいろな楽器の音色であふれてるのよ!", #CC_minnie_chatter_comments01.mp3 + # the merry no longer goes round + #"大きなメリーゴーランドを是非試してみてね!", #CC_minnie_chatter_comments02.mp3 + "あら!おしゃれなお洋服ね!", #CC_minnie_chatter_comments03.mp3 + "ねぇ、" + Mickey + "を見かけなかった?", #CC_minnie_chatter_comments04.mp3 + "" + Goofy + "に会ったら、よろしくね。", #CC_minnie_chatter_comments05.mp3 + "たくさんの" + Cogs + "が" + Donald + "のドリームランドまわりにいるらしいわ。", #CC_minnie_chatter_comments06.mp3 + lDonaldsDock+"には霧が立ち込めているみたいよ。", #CC_minnie_chatter_comments07.mp3 + lDaisyGardens+"の迷路も試してみてね。", #CC_minnie_chatter_comments08.mp3 + "私も楽器を演奏してみようかしら。", #CC_minnie_chatter_comments091.mp3 + "ねぇ、あれを見て!", #CC_minnie_chatter_comments10.mp3 + "楽器の音色ってほんと素敵よね。", #CC_minnie_chatter_comments11.mp3 + "メロディーランドはトゥーンじゃなくてチューンタウンなの。うふっ。", #CC_minnie_chatter_comments12.mp3 + "マッチングゲームって面白いわよね。そう思わない?", #CC_minnie_chatter_comments13.mp3 + "みんなが笑ってくれるとわたし、とってもうれしいわ!", #CC_minnie_chatter_comments14.mp3 + "ねぇ、歩き回ってつかれたんじゃなぁーい?", #CC_minnie_chatter_comments15.mp3 + "まぁ。素敵なシャツね!", #CC_minnie_chatter_comments16.mp3 + "あらっ、そこにあるのはゼリービーンかしら?", #CC_minnie_chatter_comments17.mp3 + ], + [ # Goodbyes + "いっけなーい、" + Mickey + "と会う約束をしてたんだ。", #CC_minnie_chatter_goodbyes01.mp3 + "そろそろ" + Pluto + "の夕飯のしたくする時間だわっ。", #CC_minnie_chatter_goodbyes02.mp3 + "ふぁーっ、ドリームランドに行こうかしら?", #CC_minnie_chatter_goodbyes03.mp3 + ] + ) + +DaisyChatter = ( + [ # Greetings + "マイガーデンへようこそ!", + "こんにちは!わたしは"+Daisy+"。あなたの名前をおしえてちょうだい。", + "あなたにお会いできてうれしいわ!", + ], + [ # Comments + "賞品のお花は庭の迷路の中にあるわよ。", + "迷路をぶらぶら散歩するのが好きなの。", + "ずっと"+Goofy+"グーフィーを見てないのよね。", + "一体、"+Goofy+"はどこにいるのかしら?", + "ねぇ、"+Donald+"を見かけなかった?どこ探しても見つからないのよ。", + "私の友達の"+Minnie+"を見かけたら、よろしくって伝えてくださらない?", + "いいガーデニングツールがあれば、植物もよく育つのよ。", + "たくさんの"+Cogs+"が"+lDonaldsDock+"にいるらしいのよ。", + "毎日の水やりは植木をハッピーにするのよ!", + "ピンクデイジーを育てたければ、黄色と赤のジェリービーンを植えてね。", + "イエローデイジーを育てるには、黄色のジェリービーンを植えてね。", + "もし植木の下に砂が見えたら、水をあげてね。でないと、植木がかれちゃうよ!" + ], + [ # Goodbyes + "メロディーランドに%sに会いに行くところよ。" % Minnie, + "%sとのピクニックに遅れちゃうわ~!" % Donald, + "これから"+lDonaldsDock+"に泳ぎに行こうかしら。", + "ふぁ~っ。ちょっと眠くなったから、ドリームランドに行こうかしら。", + ] + ) + +ChipChatter = ( + [ # Greetings + "%sにようこそ!" % lOutdoorZone, + "やぁ、僕は" + Chip + "。キミの名前は?", + "僕が" + Chip + "だよ!", + "%、会えてほんとうにうれしいよ!", + "僕たちはチップとデールだよ!", + ], + [ # Comments + "ゴルフが大好きなんだ!", + "ここのドングリはトゥーンタウンで一番なのさ。", + "火山があるゴルフコースが一番むずかしいとおもうよ。", + ], + [ # Goodbyes + "これから" + lTheBrrrgh +"に行って%sとあそぶんだ!" % Pluto, + "これから%sに会いに行ってくるんだ。" % Donald, + "今日は" + lDonaldsDock + "までおよぎに行こうかなぁ♪", + "なんだかねむいなぁ…。ドリームランドでひとねむりしようかな。", + ] + ) + +# Warning Dale's chatter is dependent on on Chip's, they should match up +DaleChatter = ( + [ # Greetings + "%、よくきてくれたね!", + "こんにちは、僕" + Dale + "だよ。キミの名前は?", + "僕は" + Chip + "だよ。", + "%sへようこそ!" % lOutdoorZone, + "僕たちがチップとデールだよ。", + ], + [ # Comments + "ピクニックって楽しいよね。", + "ここのドングリはおいしいんだよ。", + "そこの風車はもうためした?", + ], + [ # Goodbyes + "ヒヒヒ、" + Pluto + "は遊びともだちなんだ!", + "よし、%sに行くじゅんびをしよう!" % Donald, + "のんびりと泳ぎにゆきたいなあ。", + "うん、そろそろつかれてきたからきゅうけいしようよ。", + ] + ) + +GoofyChatter = ( + [ # Greetings + "ようこそ、"+lDaisyGardens+"へ!", #CC_goofy_chatter_greetings01.mp3 + "僕の名前は" + Goofy + "。よろしくね。キミの名前は?", #CC_goofy_chatter_greetings02.mp3 + "おひょっ。キミにあえてうれしいよ。", #CC_goofy_chatter_greetings03.mp3 + ], + [ # Comments + "迷路に迷わないように気をつけてね。", #CC_goofy_chatter_comments01.mp3 + "キミもここの迷路を試してみない?楽しいよ!", #CC_goofy_chatter_comments02.mp3 + "ねぇ、キミ。" + Daisy + "を見かけなかった?", #CC_goofy_chatter_comments03.mp3 + "ありゃま、" + Daisy + "はどこに行ったのかなぁ。", #CC_goofy_chatter_comments04.mp3 + "ねぇ、" + Donald + "はどこにいるか知ってる?知ってたら教えてね。", #CC_goofy_chatter_comments05.mp3 + "もし僕の親友の" + Mickey + "にあったらよろしくね。", #CC_goofy_chatter_comments06.mp3 + "おなかすいたなぁ。", #CC_goofy_chatter_comments07.mp3 + "この街の外にはね。" + Cogs + "がたくさんいるんだよ。", #CC_goofy_chatter_comments08.mp3 + "ねぇ、見て見て。" + Daisy + "がお花を植えたのかなぁ。", #CC_goofy_chatter_comments09.mp3 + "おひょっ。いろんな種類のギャグがあるから集めてごらん。", #CC_goofy_chatter_comments10.mp3 + "街には必ずギャグショップがあるよ。楽しいよ。", #CC_goofy_chatter_comments11.mp3 + "ギャグショップにはたっくさんギャグがあるよ。笑い過ぎないように気をつけてね。" #CC_goofy_chatter_comments12.mp3 + ], + [ # Goodbyes + "これから、" + Minnie + "に会いに、メロディーランドに行くところなんだ。", #CC_goofy_chatter_goodbyes01.mp3 + "オヒョッ!急がないと遅れちゃう!" + Donald + "と約束してたんだ!", #CC_goofy_chatter_goodbyes02.mp3 + lDonaldsDock+"に泳ぎに行こうかなぁ。", #CC_goofy_chatter_goodbyes03.mp3 + "ふああ…お昼寝の時間みたい。ドリームランドに行かなくちゃ。", #CC_goofy_chatter_goodbyes04.mp3 + ] + ) + + + +GoofySpeedwayChatter = ( + [ # Greetings + "ようこそ!"+lGoofySpeedway+"へ!", + "やあ、僕の名前は"+Goofy+"だよ。キミの名前を教えてよ。", + "オヒョッ!キミに会えてうれしいよ%!", + ], + [ # Comments + "さっきすごいレースを見たんだよ!", + "コースにあるバナナの皮に気をつけてね!", + "最近、キミのカートをアップグレードしたかな?", + "カートショップに新しいパーツが入ったみたいだよ!", + "ねぇ、ちょっと!"+Donald+"を見なかった?", + "おっと!"+Mickey+"の朝ごはんの準備をするのをすっかり忘れてたよ!", + "もし僕のともだちの"+Mickey+"に会ったら、よろしく伝えてよ!", + "オヒョッ!"+lDonaldsDock+"に"+Cogs+"たちが、うようよしてるって!", + "ブルブルランドのギャグショップでは、ぐるぐるめがねがなんと1ジェリービーンで売ってるよ!", + "ボクのギャグショップではトゥーンタウン中で一番のジョークや笑いのたねを取りそろえてるんだよ!", + "ギャグショップのパイは笑いの保障つき!笑わなかったらジェリービーンをちゃんとキミに返すよ!" + ], + [ # Goodbyes + "ちょっと%sに会いにメロディーランドに行ってくるよ。" % Mickey, + "オヒョッ!%sとのゲームの約束におくれちゃう!" % Donald, + "ねえねえ、キミ!"+lDonaldsDock+"で泳ぎに行こうかな?", + "あっ、お昼寝の時間だ!ドリームランドに行こうかなー。", + ] + ) + +DonaldChatter = ( + [ # Greetings + "ドリームランドへようこそ!", #CC_donald_chatter_greeting01.mp3 + "僕は" + Donald + "!君の名前は?", #CC_donald_chatter_greeting02.mp3 + ], + [ # Comments + "ここではたまにこわいことがあるんだよ。", #CC_donald_chatter_comments01.mp3 + "ねぇ、"+lDaisyGardens+"に行った?", #CC_donald_chatter_comments02.mp3 + "今日もいい日だねっ!", #CC_donald_chatter_comments03.mp3 + "ねぇ、" + Mickey + "を見なかった?", #CC_donald_chatter_comments041.mp3 + "" + Goofy + "によろしくね。", #CC_donald_chatter_comments05.mp3 + "釣りに行こうかなぁ", #CC_donald_chatter_comments06.mp3 + "わぉ、町の外に、コグたちがたくさんいるよ。" #CC_donald_chatter_comments07.mp3 + "もうボートには乗った?", #CC_donald_chatter_comments08.mp3 + "" + Daisy + "を見なかった?", #CC_donald_chatter_comments09.mp3 + "" + Daisy + "がお庭にお花を植えたみたいだよ。", #CC_donald_chatter_comments10.mp3 + "クワッ!", #CC_donald_chatter_comments11.mp3 + ], + [ # Goodbyes + "" + Minnie + "に会いに行こうかな?", #CC_donald_chatter_goodbyes01.mp3 + "" + Daisy + "とのデートに遅れちゃう…", #CC_donald_chatter_goodbyes02.mp3 + "よおっし、ちょっと泳ごうかなぁ…", #CC_donald_chatter_goodbyes03.mp3 + "ボートは楽しいな!", #CC_donald_chatter_goodbyes04.mp3 + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "新しいともだち" +FriendsListPanelSecrets = "ひみつリスト" +FriendsListPanelOnlineFriends = "オンラインの\nともだち" +FriendsListPanelAllFriends = "すべての\nともだち" +FriendsListPanelIgnoredFriends = "むしする\nトゥーン" +FriendsListPanelPets = "となりの\nペット" +FriendsListPanelPlayers = "すべての\nともだち" +FriendsListPanelOnlinePlayers = "オンラインの\nともだち" + +FriendInviterClickToon = "ともだちになりたいトゥーンをクリックしてね。\n\n(げんざいの友だち%s人)" + + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "%(phase)sのダウンロードが%(percent)s%%しかされていないので、先に進めません。\n\n後で再試行してください" + +# TeaserPanel.py +TeaserTop = "今すぐ、オフィシャルメンバーになろう!\n\nメンバーになるとこんなことができるよ。" +TeaserBottom = "Subscribe now and enjoy these great features:" +TeaserOtherHoods = "6つの変わったエリアで楽しもう!" +TeaserTypeAName = "自分のトゥーンに好きな名前をつけよう!" +TeaserSixToons = "1つのアカウントでトゥーンを6つ作れるよ!" +TeaserOtherGags = "トゥーンタウンには7種類のギャグと、\nそれぞれに6つのレベルがあるよ!" +TeaserClothing = "ユニークなアイテムでキミのトゥーンを目立たせよう!" +TeaserFurniture = "家具を買って、キミのおうちをコーディネートしよう!" +TeaserCogHQ = "強いコグたちの危険なエリアに忍び込もう!" +TeaserSecretChat = "ともだちとパスワードを交換して、\nオンラインでチャットしよう!" +TeaserCardsAndPosters = "いろんなディズニーの仲間たちにも会えるよ!" #★In Japan, we don't have this. +TeaserHolidays = "クリスマスやバレンタインといった\n季節スペシャルイベントにどんどん参加しよう!" +TeaserQuests = "もりだくさんのトゥーンタスクをこなして、\nトゥーンタウンを救おう!" +TeaserEmotions = "カタログでは「手を振る」、「ほめる」といった\nトゥーンの表現も買うことができるよ。\n表現ゆたかなトゥーンにしよう!" +TeaserMinigames = "8つのミニゲームを遊びたおそう!" +TeaserKarting = "ともだちのカートと一緒に楽しくレースしよう!" +TeaserKartingAccessories = "かっこいいアクセサリーで、\nキミのカートをカスタマイズしよう!" +TeaserGardening = "キミのおうちの庭を花や像やギャグの木できれいにかざろう!" +TeaserRental = "キミのおうちの庭のために楽しいパーティーアイテムをかりよう!" +TeaserBigger = "もっと大きくて強力なアイテムを買おう!" +TeaserTricks = "キミのドゥードゥルにトリックをおしえて、\nバトルの時にたすけてもらおう!" +TeaserSpecies = "サルやウマ、クマのトゥーンを作って遊ぼう!" +TeaserFishing = "全種類のサカナを集めてみよう!" +TeaserGolf = "しかけが一杯のゴルフコースで楽しもう!" +TeaserSubscribe = "今すぐ申し込む" +TeaserContinue = "お試し体験を続ける" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "%sをダウンロード中…" +DownloadWatcherInitializing = "ダウンロードを始めます…" + +# Launcher.py +LauncherPhaseNames = { + 0 : "初期化中", + 1 : "パンダ", + 2 : "エンジン", + 3 : "トゥーン", + 3.5 : "トゥーントリアル", + 4 : "プレイグラウンド", + 5 : "ストリート", + 5.5 : "おうち", + 6 : "エリア①", + 7 : Cog, + 8 : "エリア②", + 9 : Cog + lHQ, + 10 : lCashbotHQ, + 11 : lLawbotHQ, + 12 : Bossbot + " HQ", + 13 : "Parties", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)sの (%(current)s/%(total)s)" +LauncherStartingMessage = "トゥーンタウンをスタートしています…" +LauncherDownloadFile = LauncherProgress + "のアップデート中…" +LauncherDownloadFileBytes = LauncherProgress + "のアップデートをダウンロード中: %(bytes)s" +LauncherDownloadFilePercent = LauncherProgress + "のアップデートをダウンロード中: %(percent)s%%" +LauncherDecompressingFile = LauncherProgress + "のアップデートを解凍中…" +LauncherDecompressingPercent = LauncherProgress + "のアップデートを解凍中: %(percent)s%%" +LauncherExtractingFile = LauncherProgress + "のアップデートを抽出中…" +LauncherExtractingPercent = LauncherProgress + "のアップデートを抽出中: %(percent)s%%" +LauncherPatchingFile = LauncherProgress + "のアップデートを適用中…" +LauncherPatchingPercent = LauncherProgress + "のアップデートを適用中: %(percent)s%%" +LauncherConnectProxyAttempt = "トゥーンタウンに接続中: %s (proxy: %s) 試行中: %s" +LauncherConnectAttempt = "トゥーンタウンに接続中: %s attempt %s" +LauncherDownloadServerFileList = "トゥーンタウンをアップデート中…" +LauncherCreatingDownloadDb = "トゥーンタウンをアップデート中…" +LauncherDownloadClientFileList = "トゥーンタウンをアップデート中…" +LauncherFinishedDownloadDb = "トゥーンタウンをアップデート中…" +LauncherStartingToontown = "トゥーンタウンをスタート中…" +LauncherStartingGame = "トゥーンタウンをスタート中…" +LauncherRecoverFiles = "トゥーンタウンをアップデートしています。ファイルをリカバリー中…" +LauncherCheckUpdates = LauncherProgress + "のアップデートを確認中…" +LauncherVerifyPhase = "トゥーンタウンをアップデート中…" + +# AvatarChoice.py +AvatarChoiceMakeAToon = "トゥーンを\nつくろう!" +AvatarChoicePlayThisToon = "このトゥーンを\nえらぶ" +AvatarChoiceSubscribersOnly = "\nいますぐ\nメンバーに\nなろう!" #★ +AvatarChoiceDelete = "消す" +AvatarChoiceDeleteConfirm = "%s が削除されるよ。いいのかな?" +AvatarChoiceNameRejected = "なまえが\n使えないよ!" +AvatarChoiceNameApproved = "なまえが\n使えるよ!" +AvatarChoiceNameReview = "なまえを\nチエック中" +AvatarChoiceNameYourToon = "なまえを\nつけよう!" +AvatarChoiceDeletePasswordText = "%s が完全に削除されてしまうよ。 このトゥーンを削除する場合は、 パスワードを入力してね。" +AvatarChoiceDeleteConfirmText = "%(name)s が完全に削除されてしまうよ。このトゥーンを削除したい場合は、\"%(confirm)s\"と入力してから「OK」をクリックしてね。" +AvatarChoiceDeleteConfirmUserTypes = "削除" +AvatarChoiceDeletePasswordTitle = "このトゥーンを削除しますか?" +AvatarChoicePassword = "パスワード" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "パスワードが合っていないようだよ。このトゥーンを削除したい場合は、キミのパスワードを入力してね。" +AvatarChoiceDeleteWrongConfirm = "入力されたものは間違っているよ。%(name)sを削除したい場合は\"%(confirm)s\"と入力してから「OK」をクリックしてね。引用符(" ")は入力しないでください。削除したくなくなった場合は「キャンセル」をクリックしてね。" + +# AvatarChooser.py +AvatarChooserPickAToon = "プレイするトゥーンをえらぶ" +AvatarChooserQuit = lQuit + +# TTAccount.py ★ +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "ディズニー・インターネット・グループ・カスタマーセンター(%s)にごれんらくください。" +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nお問い合わせ等は、ディズニー・インターネット・グループ・カスタマーセンター(%s)までお願いします。" +TTAccountIntractibleError = "エラーが発生しました。" + +# DateOfBirthEntry.py ★「月」だけでなく「年」や「日」にも単位をつけたい +DateOfBirthEntryMonths = ['1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月',] +DateOfBirthEntryDefaultLabel = "生年月日" + + +# AchievePage.py +AchievePageTitle = "アチーブメント\n(近日公開予定)" + +# PhotoPage.py +PhotoPageTitle = "写真\n(近日公開予定)" + +# BuildingPage.py +BuildingPageTitle = "ビル\n(近日公開予定)" + +# InventoryPage.py +InventoryPageTitle = "ギャグ" +InventoryPageDeleteTitle = "ギャグを削除する" +InventoryPageTrackFull = "%sのギャグトラックはすべてそろってます。" +InventoryPagePluralPoints = "%(trackName)sのポイントを%(numPoints)sかせげば、新しい\n%(trackName)sのギャグを得ることができます。" +InventoryPageSinglePoint = "%(trackName)sのポイントを%(numPoints)sかせげば、新しい\n%(trackName)sのギャグを得ることができます。" +InventoryPageNoAccess = "まだ%sのトラックにはアクセスできません。" + +# NPCFriendPage.py +NPCFriendPageTitle = "SOSトゥーン" + +# EventsPage.py +PartyDateFormat = "%s %d, %.4d" # Dec 8, 2008 +PartyTimeFormat = "%d:%.2d %s" # 1:45 pm +PartyTimeFormatMeridiemAM = "am" +PartyTimeFormatMeridiemPM = "pm" +PartyCanStart = "You can start your party now!" +EventsPageName = "Events" + +EventsPageCalendarTabName = "Calendar" +EventsPageCalendarTabParty = "Party" +EventsPageToontownTimeIs = "TOONTOWN TIME IS" + +EventsPageHostTabName = "Hosting" # displayed on the physical tab +EventsPageHostTabTitle = "My Next Party" # banner text displayed across the top +EventsPageHostTabDateTimeLabel = "You are having a party on %s at %s Toontown Time." +EventsPageHostingTabNoParty = "Go to a playground\nParty Gate to plan\nyour own party!" +EventsPageHostTabPublicPrivateLabel = "Party is" +EventsPageHostTabToggleToPrivate = "Private" +EventsPageHostTabToggleToPublic = "Public" +EventsPageHostingTabGuestListTitle = "My Guests" +EventsPageHostingTabActivityListTitle = "My Activites" +EventsPageHostingTabDecorationsListTitle = "My Decorations" +EventsPageHostTabCancelButton = "Cancel Party" +EventsPageGoButton = "Start\nParty!" +EventsPageGoBackButton = "Party\nNow!" + +EventsPageInvitedTabName = "Invitations" +EventsPageInvitedTabTitle = "Party Invitations" +EventsPageInvitedTabInvitationListTitle = "Invitations" +EventsPageInvitedTabActivityListTitle = "Activites" + +EventsPageNewsTabName = "News" +EventsPageNewsTabTitle = "News" + +# InvitationSelection.py +SelectedInvitationInformation = "%s is having a party on %s at %s Toontown Time." + +# PartyPlanner.py +PartyPlannerNextButton = "Continue" +PartyPlannerPreviousButton = "Back" +PartyPlannerWelcomeTitle = "Toontown Party Planner" +PartyPlannerInstructions = "Hosting your own party is a lot of fun!\nStart planning with the arrows at the bottom!" +PartyPlannerDateTitle = "Pick A Day For Your Party" +PartyPlannerTimeTitle = "Pick A Time For Your Party" +PartyPlannerGuestTitle = "Choose Your Guests" +PartyPlannerEditorTitle = "Design Your Party\nPlace Activities and Decorations" +PartyPlannerConfirmTitle = "Choose Invitations To Send" +PartyPlannerTimeToontown = "Toontown" +PartyPlannerTimeTime = "Time" +PartyPlannerTimeRecap = "Party Date and Time" +PartyPlannerTimeToontownTime = "Toontown Time:" +PartyPlannerTimeLocalTime = "Your Local Time : " +PartyPlannerPublicPrivateLabel = "This party will be:" +PartyPlannerPublic = "Public" +PartyPlannerPrivate = "Private" +PartyPlannerCheckAll = "Check\nAll" +PartyPlannerUncheckAll = "Uncheck\nAll" +PartyPlannerDateText = "Date" +PartyPlannerTimeText = "Time" +PartyPlannerTTTimeText = "Toontown Time" +PartyPlannerEditorInstructionsIdle = "Click on the Party Activity or Decoration you would like to purchase." +PartyPlannerEditorInstructionsClickedElementActivity = "Click Buy or Drag the Activity Icon onto the Party Grounds" +PartyPlannerEditorInstructionsClickedElementDecoration = "Click Buy or Drag the Decoration onto the Party Grounds" +PartyPlannerEditorInstructionsDraggingActivity = "Drag the Activity onto the Party Grounds." +PartyPlannerEditorInstructionsDraggingDecoration = "Drag the Activity onto the Party Grounds." +PartyPlannerEditorInstructionsPartyGrounds = "Click and Drag items to move them around the Party Grounds" +PartyPlannerEditorInstructionsTrash = "Drag an Activity or Decoration here to remove it." +PartyPlannerEditorInstructionsNoRoom = "There is no room to place that activity." +PartyPlannerBeans = "BEANS" +PartyPlannerTotalCost = "Total:\n%d BEANS" +PartyPlannerSoldOut = "SOLD OUT" +PartyPlannerBuy = "BUY" +PartyPlannerPartyGrounds = "PARTY GROUNDS" +PartyPlannerInviteButton= "Send Invites" +PartyPlannerBirthdayTheme = "Birthday" +PartyPlannerGenericMaleTheme = "Cool" +PartyPlannerGenericFemaleTheme = "Fun" +PartyPlannerGuestName = "Guest Name" +PartyPlannerClosePlanner = "Close Planner" +PartyPlannerConfirmationAllOkTitle = "Congratulations!" +PartyPlannerConfirmationAllOkText = "Your party has been created\nand your invitations sent out.\nThanks!" +PartyPlannerConfirmationErrorTitle = "Oops." +PartyPlannerConfirmationValidationErrorText = "Sorry, there seems to be a\nproblem with that party.\n\nPlease go back and try again." +PartyPlannerConfirmationDatabaseErrorText = "Sorry, I didn't get all your information.\n\nPlease go back and try again." +PartyPlannerConfirmationTooManyText = "Oh, I'm sorry, I didn't realize you\nwere already hosting a party.\n\nIf you want to plan another party,\nplease cancel your current party\non your Events Page." +PartyPlannerInvitationThemeWhatSentence = "You are invited to my %s %s party! %s!" +PartyPlannerInvitationThemeWhatActivitiesBeginning = "It will have " +PartyPlannerInvitationWhoseSentence = "%s's %s Party" +PartyPlannerInvitationTheme = "Theme" +PartyPlannerInvitationWhenSentence = "It will be %s,\nat %s Toontown Time.\nHope you can make it!" + +# JukeBoxGui +JukeboxAddSong = "Add\nSong" +JukeboxReplaceSong = "Replace\nSong" +JukeboxQueueLabel = "Party Playlist" +JukeboxSongsLabel = "Available Songs" +JukeboxCurrentlyPlaying = "Currently Playing:" +JukeboxCurrentlyPlayingNothing = "No Songs Playing!" +JukeboxCurrentSongNothing = "Add a song to the playlist." + +PartyOverWarning = "The party has ended, thanks for coming! Bye!" + +# Note : This dictionary is used to show the names of the activities in various +# contexts. If PartyGlobals.ActivityIds is changed, this list must be +# updated with new indices. +PartyActivityNameDict = { + 0 : { + "generic" : "Jukebox", + "invite" : "a Jukebox", + "editor" : "Jukebox", + "description" : "Listen to music with your very own jukebox!" + }, + 1 : { + "generic" : "Party Cannons", + "invite" : "Party Cannons", + "editor" : "Cannons", + "description" : "Fire yourself out of the cannons and into fun!" + }, + 2 : { + "generic" : "Trampoline", + "invite" : "Trampolines", + "editor" : "Trampolines", + "description" : "Collect jellybeans and bounce the highest!" + }, + 3 : { + "generic" : "Party Catch", + "invite" : "Party Catch", + "editor" : "Party Catch", + "description" : "Catch fruit to win beans! Dodge those anvils!" + }, + 4 : { + "generic" : "Dance Floor", + "invite" : "a Dance Floor", + "editor" : "Dance Floor", + "description" : "Show off your moves, toon style!" + }, + 5 : { + "generic" : "Party Tug Of War", + "invite" : "Party Tug Of War", + "editor" : "Tug-o-war", + "description" : "Up to 4 on 4 toon tugging crazyness!" + }, + 6 : { + "generic" : "Party Fireworks", + "invite" : "Party Fireworks", + "editor" : "Fireworks", + "description" : "Launch your very own fireworks show!" + }, + 7 : { + "generic" : "Party Clock", + "invite" : "a Party Clock", + "editor" : "Party Clock", + "description" : "Counts down the time left in your party." + }, +} + +# Note : This dictionary is used to show the names of the decorations in various +# contexts. If PartyGlobals.DecorationIds is changed, this list must be +# updated with new indices. +PartyDecorationNameDict = { + 0 : { + "editor" : "Balloon Anvil", + "description" : "A hilarious balloon anvil.", + }, + 1 : { + "editor" : "BalloonStage", + "description" : "A hilarious balloon stage.", + }, + 2 : { + "editor" : "Bow", + "description" : "A hilarious bow.", + }, + 3 : { + "editor" : "Cake", + "description" : "A hilarious cake.", + }, + 4 : { + "editor" : "Castle", + "description" : "A hilarious castle.", + }, + 5 : { + "editor" : "GiftPile", + "description" : "A hilarious gift pile.", + }, + 6 : { + "editor" : "Horn", + "description" : "A hilarious horn.", + }, + 7 : { + "editor" : "MardiGras", + "description" : "A hilarious mardi gras.", + }, + 8 : { + "editor" : "NoiseMakers", + "description" : "A hilarious noise maker.", + }, + 9 : { + "editor" : "Pinwheel", + "description" : "A hilarious Pinwheel.", + }, +} + +ActivityLabel = "Cost - Activity Name" +PartyDoYouWantToPlan = "Would you like to plan a new party right now?" +PartyPlannerOnYourWay = "Have fun planning your party!" +PartyPlannerMaybeNextTime = "Maybe next time. Have a good day!" +PartyPlannerHostingTooMany = "You can only host one party at a time, sorry." +PartyHatPublicPartyChoose = "Do you want to go to the 1st available public party?" + +# DistributedPartyActivity.py +PartyActivityWaitingForOtherPlayers = "Waiting for other players to join the party game..." +PartyActivityPleaseWait = "Please wait..." +DefaultPartyActivityTitle = "Party Game Title" +DefaultPartyActivityInstructions = "PartyGame Instructions" +PartyOnlyHostLeverPull = "Only the host can start this activity, sorry." + +# DistributedPartyCatchActivity.py +PartyCatchActivityTitle = "Party Catch Activity" +PartyCatchActivityInstructions = "Catch as many %(fruit)s as you can. Try not to 'catch' any %(badThing)s!" +PartyCatchActivityPerfect = "PERFECT!" +PartyCatchActivityApples = 'apples' +PartyCatchActivityOranges = 'oranges' +PartyCatchActivityPears = 'pears' +PartyCatchActivityCoconuts = 'coconuts' +PartyCatchActivityWatermelons = 'watermelons' +PartyCatchActivityPineapples = 'pineapples' +PartyCatchActivityAnvils = 'anvils' + +# DistributedPartyFireworksActivity.py +FireworksActivityInstructions = "Hit the \"Page Up\" key to see better." +FireworksActivityBeginning = "Party fireworks are about to start! Enjoy the show!" +FireworksActivityEnding = "Hope you enjoyed the show!" +PartyFireworksAlreadyActive = "The fireworks show has already started." +PartyFireworksAlreadyDone = "You've already had your fireworks show." + +# DistributedPartyTrampolineActivity.py +PartyTrampolineJellyBeanTitle = "Jelly Beans Trampoline" +PartyTrampolineTricksTitle = "Tricks Trampoline" +PartyTrampolineActivityInstructions = "Use the Control key to jump.\n\nJump when your Toon is at its lowest point on the trampoline to jump higher." +PartyTrampolineActivityOccupied = "Another Toon is already using this trampoline." +PartyTrampolineQuitEarlyButton = "Quit Early" + +# DistributedPartyTugOfWarActivity.py +PartyTugOfWarJoinDenied = "Sorry. You can't join Tug-O-War right now." +PartyTugOfWarExitButton = "Hop Off" +PartyTugOfWarWaitingForMore = "Waiting for more players" # extra spaces on purpose given the blocky font +PartyTugOfWarWaitingToStart = "Waiting to start" +PartyTugOfWarWaitingForOtherPlayers = "Waiting for other players" +PartyTugOfWarReady = "Ready..." +PartyTugOfWarGo = "GO!" +PartyTugOfWarGameEnd = "Good game!" +PartyTugOfWarGameTie = "You tied!" +PartyTugOfWarTitle = "Party Tug-O-War" + +# CalendarGuiDay.py +CalendarEndsAt = "Ends at " +CalendarStartedOn = "Started on " +CalendarEndDash = "End-" +CalendarEndOf = "End of " + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "のこり%s回" + +# PartiesPage.py +PartiesPageTitle = "パーティー" +PartiesPageHostTab = "しゅさい者" +PartiesPageInvitedTab = "しょうたい客" +PartiesPageTitleHost = "次のパーティー" +PartiesPageTitleInvited = "しょうたい状" + +# MapPage.py +MapPageTitle = "地図" +MapPageBackToPlayground = "プレイグラウンドに戻る" +MapPageBackToCogHQ = "コグ本部に戻る" +MapPageGoHome = "家に帰る" +# hood name, street name +MapPageYouAreHere = " %s\n%s" +MapPageYouAreAtHome = "\n自分のおうちにいます" +MapPageYouAreAtSomeonesHome = "%s のおうちにいます" +MapPageGoTo = "%s\nへ行く" + +# OptionsPage.py +OptionsPageTitle = "オプション" +OptionsPagePurchase = "今すぐ申し込む" +OptionsPageLogout = "ログアウト" +OptionsPageExitToontown = "ゲームを終了する" +OptionsPageMusicOnLabel = "おんがく: あり" +OptionsPageMusicOffLabel = "おんがく: なし" +OptionsPageSFXOnLabel = "こうかおん: あり" +OptionsPageSFXOffLabel = "こうかおん: なし" +OptionsPageFriendsEnabledLabel = "ともだち:うけつける" +OptionsPageFriendsDisabledLabel = "ともだち:うけつけない" +OptionsPageSpeedChatStyleLabel = "スピードチャットの色" +OptionsPageDisplayWindowed = "ウインドウ・モード" +OptionsPageSelect = "選択する" +OptionsPageToggleOn = "きりかえ" +OptionsPageToggleOff = "きりかえ" +OptionsPageChange = "へんこう" +OptionsPageDisplaySettings = "かいぞうど: %(screensize)s、 %(api)s" +OptionsPageDisplaySettingsNoApi = "かいぞうど: %(screensize)s" +OptionsPageExitConfirm = "トゥーンタウン・\nオンラインを\n終了しますか?" + +DisplaySettingsTitle = "がめんひょうじせってい" +DisplaySettingsIntro = "トゥーンタウン・オンラインのひょうじのせっていをします。(おうちのひとと見てね)\nトゥーンタウン・オンラインでのテキストやグラフィックレベルを向上するため、画面解像度を高めに設定してもかまいませんが、ご使用のグラフィックカードにより、いくつかの設定でゲームのスピードが遅くなったり、全く動かなくなったりする可能性がありますのであらかじめご了承ください。 " +DisplaySettingsIntroSimple = "トゥーンタウンでのテキストやグラフィックレベルを向上するため、画面解像度を高めに設定してもかまいませんが、ご使用のグラフィックカードにより、いくつかの設定は、ゲームのスピードが遅くなったり、全く動かなくなったりする可能性があります。" + +DisplaySettingsApi = "グラフィックス API:" +DisplaySettingsResolution = "かいぞうど:" +DisplaySettingsWindowed = "ウインドウ・モード" +DisplaySettingsFullscreen = "フルスクリーン・モード" +DisplaySettingsApply = "OK" +DisplaySettingsCancel = "キャンセル" +DisplaySettingsApplyWarning = "OKボタンを押すと、表示設定が変わります。 新しい設定がコンピュータ上で正常に表示されない場合、自動的に%s秒後、元の状態に戻ります。" +DisplaySettingsAccept = "これでよろしければOKボタンを押してください。何も押さないと、%s秒後に自動的に変更する前の設定に戻ります。" +DisplaySettingsRevertUser = "前の表示設定に戻しました。" +DisplaySettingsRevertFailed = "選択された表示設定はお客様のコンピュータでは作動しません。前の表示設定が復帰しました。" + +# TrackPage.py +TrackPageTitle = "ギャグ・トラック・トレーニング" +TrackPageShortTitle = "ギャグ\nトレーニング" +TrackPageSubtitle = "トゥーンタスクをこなして、新しい種類のギャグをおぼえよう!" +TrackPageTraining = "%s ギャグを使用するトレーニングをしています。\n16コマ分のタスクをすべて終了すると、\n バトルで%sギャグを使えるようになります。" +TrackPageClear = "現在、どのトラックのトレーニングも始めていません。" +TrackPageFilmTitle = "%s\nトレーニング\nフィルム" +TrackPageDone = "おわり" + +# QuestPage.py +QuestPageToonTasks = "トゥーンタスク" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nへ: %s\n %s\n %s\n %s\n\nから: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nから:%s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\n選びに、 \n %s\n %s\n %s\n %sn %sへ行ってね。" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "選んでね" +QuestPageLocked = "へいさ中" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "どの通りでも" +QuestPosterHQLocationName = "どのエリアでも" + +QuestPosterTailor = "したてやさん" +QuestPosterTailorBuildingName = "ようふくや" +QuestPosterTailorStreetName = "どのプレイグラウンドでも" +QuestPosterTailorLocationName = "どのエリアでも" +QuestPosterPlayground = "プレイグラウンドで" +QuestPosterAtHome = "おうちで" +QuestPosterInHome = "おうちの中で" +QuestPosterOnPhone = "電話で" +QuestPosterEstate = "おうちで" +QuestPosterAnywhere = "どこでも" #★ +QuestPosterAuxTo = "→" +QuestPosterAuxFrom = "←" +QuestPosterAuxFor = "" #★ +QuestPosterAuxOr = "または" +QuestPosterAuxReturnTo = "" #★ +QuestPosterLocationIn = " in " +QuestPosterLocationOn = " in " +QuestPosterFun = "楽しいよ!" +QuestPosterFishing = "つりにいく" +QuestPosterComplete = "完了" + +# ShardPage.py +ShardPageTitle = "ロビー" +ShardPageHelpIntro = "それぞれのロビーは、\nトゥーンタウンの世界の\nコピーなんだ。" +ShardPageHelpWhere = "いま、君は\"%s\" にいるよ。" +ShardPageHelpWelcomeValley = "いま、君は\"%s\"の\"ウェルカムバレー\"にいるよ。" +ShardPageHelpMove = "\n\n新しいロビーに行くには、ロビーの名前をクリックしてね。" + +ShardPagePopulationTotal = "全トゥーンタウンの人口:\n%d" +ShardPageScrollTitle = "名前 人口" +ShardPageLow = "すいてる" +ShardPageMed = "てきせつ" +ShardPageHigh = "こんざつ" +ShardPageChoiceReject = "このロビーはこんざつしています。他をえらんで下さい。" + +# SuitPage.py +SuitPageTitle = Cog + "ギャラリー" +SuitPageMystery = "???" +SuitPageQuota = "%s / %s" +SuitPageCogRadar = "%s体発見!" #★ +SuitPageBuildingRadarS = "%s 建物" +SuitPageBuildingRadarP = "%s 建物" + +# DisguisePage.py +DisguisePageTitle = Cog + "へんそう\nパーツ" +DisguisePageMeritAlert = "いよいよ昇格!" +DisguisePageCogLevel = "レベル%s" +DisguisePageMeritFull = "満タン" +DisguisePageMeritBar = "メリット" +DisguisePageMeritBar = "進行状況" +DisguisePageCogPartRatio = "%d / %d" + +# FishPage.py +FishPageTitle = "魚のタンク" +FishPageTitleTank = "魚のタンク" +FishPageTitleCollection = "魚のコレクション" +FishPageTitleTrophy = "トロフィー" +FishPageWeightStr = "重さ: " +FishPageWeightLargeS = "%dパウンド" +FishPageWeightLargeP = "%dパウンド" +FishPageWeightSmallS = "%dオンス" +FishPageWeightSmallP = "%dオンス" +FishPageWeightConversion = 16 +FishPageValueS = "ジェリービーン%d個分" +FishPageValueP = FishPageValueS +FishPageCollectedTotal = "集めた魚: %d / %d種類" +FishPageRodInfo = "%s釣りざお:\n%d~%dパウンドの\n重さまでOK" +FishPageTankTab = "タンク" +FishPageCollectionTab = "アルバム" +FishPageTrophyTab = "トロフィー" +FishPageTotalValue = "全部でジェリービーン%d個相当" + +FishPickerTotalValue = "バケツ:%s / %s匹\nジェリービーン%d個相当" + +UnknownFish = "???" + +FishingRod = "%s釣りざお" +FishingRodNameDict = { + 0 : "小枝の", + 1 : "竹の", + 2 : "木の", + 3 : "鉄の", + 4 : "金の", + } +FishTrophyNameDict = { + 0 : "クマノミ", + 1 : "キンギョ", + 2 : "コイ", + 3 : "トビウオ", + 4 : "サメ", + 5 : "カジキ", + 6 : "シャチ", + } + +# GardenPage.py #localize +GardenPageTitle = "ガーデニング" +GardenPageTitleBasket = "フラワー・バスケット" +GardenPageTitleCollection = "フラワー・アルバム" +GardenPageTitleTrophy = "ガーデニング・トロフィー" +GardenPageTitleSpecials = "スペシャル" +GardenPageBasketTab = "バスケット" +GardenPageCollectionTab = "アルバム" +GardenPageTrophyTab = "トロフィー" +GardenPageSpecialsTab = "スペシャル" +GardenPageCollectedTotal = "あつめた花の種類: %d / %d" +GardenPageValueS = "かち: ジェリービーン%dコ分" +GardenPageValueP = "かち: ジェリービーン%dコ分" +FlowerPickerTotalValue = "バスケット: %s / %s\nかち: ジェリービーン%dコ分" +GardenPageShovelInfo = "ショベル%s: %d / %d\n" +GardenPageWateringCanInfo = "ジョウロ%s: %d / %d" + +# KartPage.py +KartPageTitle = "カート" +KartPageTitleCustomize = "カートカスタマイズ" +KartPageTitleRecords = "個人ベスト" +KartPageTitleTrophy = "レーストロフィー" +KartPageCustomizeTab = "カスタマイズ" +KartPageRecordsTab = "レコード" +KartPageTrophyTab = "トロフィー" +KartPageTrophyDetail = "トロフィー %s : %s" +KartPageTickets = "チケット : " +KartPageConfirmDelete = "アクセサリーをけす?" + +#plural +KartShtikerDelete = "さくじょ" +KartShtikerSelect = "カテゴリーをえらぶ" +KartShtikerNoAccessories = "アクセサリーなし" +KartShtikerBodyColors = "カートの色" +KartShtikerAccColors = "アクセサリーの色" +KartShtikerEngineBlocks = "ボンネットアクセサリー" +KartShtikerSpoilers = "トランクアクセサリー" +KartShtikerFrontWheelWells = "ぜんりんアクセサリー" +KartShtikerBackWheelWells = "こうりんアクセサリー" +KartShtikerRims = "リムアクセサリー" +KartShtikerDecals = "デカールアクセサリー" +#singluar +KartShtikerBodyColor = "カートの色" +KartShtikerAccColor = "アクセサリーの色" +KartShtikerEngineBlock = "ボンネット" +KartShtikerSpoiler = "トランク" +KartShtikerFrontWheelWell = "ぜんりん" +KartShtikerBackWheelWell = "こうりん" +KartShtikerRim = "リム" +KartShtikerDecal = "デカール" + +KartShtikerDefault = "ひょうじゅん %s" +KartShtikerNo = "%s アクセサリーなし" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "選ぶ" +TrackChoiceGuiCancel = "やめる" +TrackChoiceGuiHEAL = 'トゥーンアップ はバトルで他のトゥーンを元気にすることができるよ。' +TrackChoiceGuiTRAP = 'トラップは、おとりと一緒に使われる強力なギャグだよ。' +TrackChoiceGuiLURE = 'コグを気絶かトラップに引き込むため、おとりを使って。' +TrackChoiceGuiSOUND = 'サウンドは、コグすべてに効くけど、そんなに強力じゃないんだ。' +TrackChoiceGuiDROP = "ドロップ・ギャグは、大きなダメージを与えるけど、そんなに正確じゃないんだ。" + +# EmotePage.py +EmotePageTitle = "表現・感情" +EmotePageDance = "次のダンスフォームをつくったよ:" +EmoteJump = "ジャンプする" +EmoteDance = "おどる" +EmoteHappy = "たのしい" +EmoteSad = "かなしい" +EmoteAnnoyed = "いらいらする" +EmoteSleep = "ねむい" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nレベル %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "ゲラゲラメーターがいっぱいになるまで、プレイグラウンドから出ることはできないよ!" + +# InventoryNew.py +InventoryTotalGags = "ギャグごうけい\n%d / %d" +InventroyPinkSlips = "%s Pink Slips" +InventroyPinkSlip = "1 Pink Slip" +InventoryDelete = "すてる" +InventoryDone = "もどる" +InventoryDeleteHelp = "すてるギャグをクリックしてね。" +InventorySkillCredit = "スキルポイント: %s" +InventorySkillCreditNone = "スキルポイント: なし" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "めいちゅうりつ: %(accuracy)s\n%(damageString)s: %(damage)s\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryUberTrackExp = "あと%(nextExp)s!" +InventoryGuestExp = "ゲスト・リミット" +GuestLostExp = "ゲスト・リミットです。" +InventoryAffectsOneCog = "たいしょう:" + Cog +"一体" +InventoryAffectsOneToon = "たいしょう: 仲間一人" +InventoryAffectsAllToons = "たいしょう: 仲間全員" +InventoryAffectsAllCogs = "たいしょう:" + Cogs +"全体" +InventoryHealString = "かいふく" +InventoryDamageString = "ダメージ" +InventoryBattleMenu = "バトルメニュー" +InventoryRun = "にげる" +InventorySOS = "SOS" +InventoryPass = "パス" +InventoryFire = "FIRE" +InventoryClickToAttack = "使いたい\nギャグを\nクリック\nしてね!" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "終了する前にトロリーに乗らなきゃ!\n\n\n\n\n\n\nトロリーは、グーフィーのギャグショップのとなりだよ。" +NPCForceAcknowledgeMessage2 = "トロリーをよく見つけられたね!\nトゥーンHQに行ってごほうびをもらってね。\n\n\n\n\n\n\n\nトゥーンHQは、プレイグラウンドのまんなか近くにあるよ。" +NPCForceAcknowledgeMessage3 = "トロリーに乗るのをわすれないでね!\n\n\n\n\nグーフィーのギャグショップのとなりにあるからね!" +NPCForceAcknowledgeMessage4 = "おめでとう!最初のトゥーンタスク完了だよ!\n\n\n\n\n\n\nトゥーンHQに行ってごほうびをもらってね。" +NPCForceAcknowledgeMessage5 = "キミのトゥーンタスクを忘れずに!\n\n\n\n\n\n\n\n\n\n\nコグたちは、トンネルのむこうにもいるよ。" +NPCForceAcknowledgeMessage6 = "よくコグをやっつけたね!\n\n\n\n\n\n\n\n\n早くトゥーン本部に戻ろう!" +NPCForceAcknowledgeMessage7 = "ともだちを作るのを忘れずに!\n\n\n\n\n\n\n他のトゥーンをクリックして、ともだちボタンを押そう!" +NPCForceAcknowledgeMessage8 = "そう、それでOK!新しいともだちを作ったね。\n\n\n\n\n\n\n\n\n今すぐトゥーン本部に戻ろう!" +NPCForceAcknowledgeMessage9 = "電話はそんな感じに使うんだよ。\n\n\n\n\n\n\n\n\nトゥーン本部に戻って、ごほうびをもらおう!" + +# Toon.py +ToonSleepString = "…ぐ~ぐ~…" + +# Movie.py +MovieTutorialReward1 = "「なげる」ポイントを1つゲットしたね!\n10ポイントためると、\n次のレベルのギャグが手に入るよ!" +MovieTutorialReward2 = "「みずでっぽう」ポイントも1つ、ゲットしたね!\nこうやってコグを倒してポイントを貯めてギャグを\nレベルアップしよう!" +MovieTutorialReward3 = "よくやったね!最初のトゥーンタスク完了だよ!" #CC_tom_movie_tutorial_reward01.mp3 +MovieTutorialReward4 = "「トゥーンHQ」に\n行って、ごほうびをもらってね!" #CC_tom_movie_tutorial_reward02.mp3 +MovieTutorialReward5 = "楽しんでね!" #CC_tom_movie_tutorial_reward03.mp3 + +# BattleBase.py +Battle_Input_Timeout = 50.0 + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['トゥーンアップ', 'トラップ', 'おとり', 'サウンド', 'なげる', 'みずでっぽう', 'ドロップ'] +BattleGlobalNPCTracks = ['かいふく', 'トゥーン→ヒット', 'コグ→ミス'] +BattleGlobalAvPropStrings = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボール', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア', 'ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット', '10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ', 'まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップのみず', 'みずでっぽう', 'ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ', 'グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvPropStringsSingular = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボールのセット', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア', 'ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット', '10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ', 'まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップのみず', 'みずでっぽう', 'ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ ', 'グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvPropStringsPlural = ( + ('まほうのはね', 'メガホン', 'まほうのリップ', 'まほうのステッキ', 'ピクシー・ダスト', 'ジャグリング・ボールのセット', 'けっしのダイブ'), + ('バナナのかわ', 'くまで', 'ビーだま', 'ありじごく', 'しかけドア','ダイナマイト', 'ぼうそう機関車'), + ('1ドルさつ', 'ちいさなマグネット', '5ドルさつ', 'おおきなマグネット','10ドルさつ', 'グルグルめがね', 'プレゼン'), + ('スモール・ホーン', 'ホイッスル', 'ラッパ', 'ミドル・ホーン', 'エレファント・ホーン', 'ビッグ・ホーン', 'オペラ歌手'), + ('カップケーキ', 'フルーツパイひときれ', 'クリームパイひときれ','まるごとフルーツパイ', 'まるごとクリームパイ', 'バースデー・ケーキ', 'ウェディングケーキ'), + ('フラワー・スプラッシュ', 'コップの水', 'みずでっぽう','ペットボトル', 'しょうかホース', 'カミナリぐも', 'かんけつせん'), + ('うえきばち', 'サンドバッグ', 'かなとこ', '100キロ', 'きんこ','グランドピアノ', 'トゥーンタニック') + ) +BattleGlobalAvTrackAccStrings = ("ふつう", "100%", "ひくい", "たかい", "ふつう", "たかい", "ひくい") +BattleGlobalLureAccLow = "ひくい" +BattleGlobalLureAccMedium = "ふつう" + +AttackMissed = "しっぱい!" + +NPCCallButtonLabel = "よぶ" + +# ToontownLoader.py +LoaderLabel = "読み込み中…" + +# PlayGame.py +HeadingToHood = "%(hood)s%(to)sへ向かっているよ…" # hood name +HeadingToYourEstate = "キミのおうちに向かっているよ…" +HeadingToEstate = "%sのおうちに向かっているよ…" # avatar name +HeadingToFriend = "%sの友だちの土地に向かっているよ…" # avatar name + +# Hood.py +HeadingToPlayground = "プレイグラウンドに向かっているよ…" +HeadingToStreet = " %(street)s%(to)sへ向かっているよ…" #Street name + +# TownBattle.py +TownBattleRun = "さっきいたプレイグラウンドへ戻る?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "どのトゥーン?" +TownBattleChooseAvatarCogTitle = "どの " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = lBack + +#firecogpanel +FireCogTitle = "PINK SLIPS LEFT:%s\nFIRE WHICH COG?" +FireCogLowTitle = "PINK SLIPS LEFT:%s\nNOT ENOUGH SLIPS!" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "電話する友だちがいないよ!" +TownBattleSOSWhichFriend = "どの友だちに電話する?" +TownBattleSOSNPCFriends = "助けたトゥーンたち" +TownBattleSOSBack = lBack + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleToonFire = "Fire" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "他のプレイヤー\nを待ってます…" +TownSoloBattleWaitTitle = "待っててね…" +TownBattleWaitBack = lBack + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "ドゥードゥルを探しています\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s is %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "ゲラゲラメーターが笑うまで、トロリーには乗れないんだ。" +TrolleyTFAMessage = Mickey + "がOKを出すまで、トロリーに乗っちゃだめだよ。" +TrolleyHopOff = "おりる" + +# DistributedFishingSpot.py +FishingExit = "終了" +FishingCast = "キャスト" +FishingAutoReel = "オートリール" +FishingItemFound = "釣ったのは…" +FishingCrankTooSlow = "おそ\すぎる!" +FishingCrankTooFast = "はや\nすぎる!" +FishingFailure = "何も釣れなかったよ!" +FishingFailureTooSoon = "食いつきがあるまで、釣り糸を巻いちゃだめだよ。 うきがぴくぴく上下にすばやく動くまで待って!" +FishingFailureTooLate = "魚が食いついている間に、釣り糸を巻くんだよ!" +FishingFailureAutoReel = "今回はオートリールが動かなかったね。釣り上げる一番のタイミングに、ちょうどいい速さで手でクランクを回して!" +FishingFailureTooSlow = "クランクを回すのがおそすぎるよ。他の魚よりもすばしっこい魚もいるからね。スピードバーを中心にしておいてみて!" +FishingFailureTooFast = "クランクを回すのがはやすぎるよ。他の魚よりものろい魚もいるからね。スピードバーを中心にしておいてみて!" +FishingOverTankLimit = "タンクが一杯だよ。\n魚を売ってから\nもう一度きてね!" +FishingBroke = "釣り針につけるものがなくなっちゃったよ! トロリーにのって、ジェリービーンをもっとあつめてきてね!" +FishingHowToFirstTime = "キャストボタンをクリックして、下の方向にドラッグしてね。ドラッグすればするほど、より遠くに投げることができるよ。ターゲットに向けて角度も調節しよう。\n\n今すぐ、試そう!" +FishingHowToFailed = "キャストボタンをクリックして、下の方向にドラッグしてね。ドラッグすればするほど、より遠くに投げることができるよ。ターゲットに向けて角度も調節しよう。\n\nもう一度、試してみよう!" +FishingBootItem = "ボロぐつ" +FishingJellybeanItem = "%s ジェリービーン" +FishingNewEntry = "新種発見!" +FishingNewRecord = "新記録!" + +# FishPoker +FishPokerCashIn = "かけビーン\n%s\n%s" +FishPokerLock = "選ぶ" +FishPokerUnlock = "戻す" +FishPoker5OfKind = "5カード" +FishPoker4OfKind = "4カード" +FishPokerFullHouse = "フルハウス" +FishPoker3OfKind = "3カード" +FishPoker2Pair = "2ペア" +FishPokerPair = "1ペア" + +# DistributedTutorial.py ★台本チェック★ +TutorialGreeting1 = "やあ %s!" +TutorialGreeting2 = "やあ %s!\nこっちにおいでよ!" +TutorialGreeting3 = "やあ %s!\nこっちにおいでよ!\nやじるしキーを使ってね!" +TutorialMickeyWelcome = "トゥーンタウンへようこそ!" +TutorialFlippyIntro = "友だちの" + Flippy + "を紹介するよ。" +TutorialFlippyHi = "やあ、 %s!" +TutorialQT1 = "これを使って話してね。" +TutorialQT2 = "これを使って話せるよ。\nクリックして、\"やあ!\"を選んでね。" +TutorialChat1 = "ボタンのどちらかを使って話してね。" +TutorialChat2 = "あおいボタンは、キーボードを使ったチャット用だよ。" +TutorialChat3 = "気をつけて! キーボードを使ってる時、他のほとんどのプレイヤーは、キミの言ってることがわからないよ。" +TutorialChat4 = "みどりのボタンは、%sをひらくよ。" +TutorialChat5 = "%sを使えば、みんながキミのことわかってくれるようになるよ。" +TutorialChat6 = "\"やあ!\"って言ってみてごらん!" +TutorialBodyClick1 = "じょうずにできたね!" +TutorialBodyClick2 = "よろしくね!ねえ、ともだちにならない?" +TutorialBodyClick3 = Flippy + "と友だちになるには、その子をクリックしてね。" +TutorialHandleBodyClickSuccess = "いいカンジだね!" +TutorialHandleBodyClickFail = "まだまだだね。" + Flippy + "の真上をクリックして…" +TutorialFriendsButton = "右かどの"+ Flippy + "の下の'ともだち' ボタンをクリックして…" +TutorialHandleFriendsButton = "そして'はい' のボタンを押してね。" +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = Flippy + "とともだちになりたい?" +TutorialFriendsPanelMickeyChat = Flippy + " は、キミのともだちになりたいって。'OK' をクリックして終了してね。" +TutorialFriendsPanelYes = Flippy + "は、いいよって言ってるよ!" +TutorialFriendsPanelNo = "あんまり感じよくないね!" +TutorialFriendsPanelCongrats = "おめでとう!最初のともだちができたよ!" +TutorialFlippyChat1 = "最初のトゥーンタスクの準備ができたら、会いにきてね!" +TutorialFlippyChat2 = "タウンホールにいるね!" +TutorialAllFriendsButton = "ともだちボタンをクリックすると、キミの友だち全員をみることができるよ。やってみて…" +TutorialEmptyFriendsList = Flippy + " は実際のプレイヤーじゃないから、キミのリストは今からっぽだよ。" +TutorialCloseFriendsList = "リストを消すには、\n'閉じる'\nボタン をクリックしてね。" +TutorialShtickerButton = "下の右のかどのボタンは、トゥーンガイドを開くよ。 やってみて…" +TutorialBook1 = "このトゥーンガイドには、トゥーンタウンの地図のようにとっても役立つ情報がたくさん入っているんだ" +TutorialBook2 = "キミのトゥーンタスクの進歩もチェックできるよ。" +TutorialBook3 = "使い終わったら、とらの巻ボタンをもう一度クリックして、閉じておいてね。" +TutorialLaffMeter1 = "これも必要だよ…" +TutorialLaffMeter2 = "これも必要だよ…\nキミのゲラゲラメーター!" +TutorialLaffMeter3 = Cogs + "がキミを攻撃すると、ポイントが低くなっちゃうんだ" +TutorialLaffMeter4 = "こんな具合にプレイグラウンドにいると、ポイントが回復するよ。" +TutorialLaffMeter5 = "トゥーンタスクが終わったら、キミのゲラゲラリミットが上がったりするごほうびがもらえるよ。" +TutorialLaffMeter6 = "気をつけて! もし" + Cogs + "にたおされたら、そいつにキミの持ってるギャグが全部持っていかれちゃうんだ!" +TutorialLaffMeter7 = "トロリーゲームをして、ギャグをもっとゲットしよう!" +TutorialTrolley1 = "トロリーに行くから、ついてきて!" +TutorialTrolley2 = "乗るよ!" +TutorialBye1 = "ゲームをしよう!" +TutorialBye2 = "ゲームをしよう!\nギャグを買おう!" +TutorialBye3 = "終わったら、" + Flippy + " に会いにいこう!" + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "行き先がちがうよ! " + Mickey + "をさがしにいって!" + +PetTutorialTitle1 = "ドゥードゥル パネル" +PetTutorialTitle2 = "ドゥードゥル スピードチャット" +PetTutorialTitle3 = "ドゥードゥル カタログ" +PetTutorialNext = "次のページ" +PetTutorialPrev = "前のページ" +PetTutorialDone = "OK" +PetTutorialPage1 = "ドゥードゥルをクリックすると、ドゥードゥルパネルが表示されるよ。 エサをあげたり、なでたり、呼び出すことができるんだ。" +PetTutorialPage2 = "ドゥードゥルに「トリック」をさせたければ、スピードチャットの「ペット」の項目を使ってね。 「トリック」をしたら、ちゃんとごほうびをあげればごきげんになるよ。" +PetTutorialPage3 = "クララベルのショッピングカタログからドゥードゥルの新しい「トリック」を買ってね。 より良い「トリック」はより多くのトゥーンアップができるよ。" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ALeft + +GardenTutorialTitle1 = "ガーデニング" #localize +GardenTutorialTitle2 = "花" +GardenTutorialTitle3 = "木" +GardenTutorialTitle4 = "育て方" +GardenTutorialTitle5 = "ステータス" +GardenTutorialNext = "次ページ" +GardenTutorialPrev = "前ページ" +GardenTutorialDone = "わかった" +GardenTutorialPage1 = "キミのおうちをガーデニングでトゥーンアップ!お花や木を育ててデコレーションして、強力なギャグをしゅうかくしよう!" +GardenTutorialPage2 = "花の育ち方はジェリービーンのびみょうなまぜ方で決まるよ。うまく育ったらキミの庭にある手押し車で売りに行こう。続けるといいことがあるよ!" +GardenTutorialPage3 = "キミのもっているギャグを使って木をうえよう。何日かたつと、そのギャグが強力になってるよ!でも、その木の世話をしないとギャグはまたもとにもどっちゃうよ。" +GardenTutorialPage4 = "キミのおうちのまわりで花や木を育ててしゅうかくしてね。" +GardenTutorialPage5 = "花のぞうは、クララベルのカタログで買えるよ。スキルをあげて、もっとステキな花のぞうを手に入れよう!" + +# Playground.py +PlaygroundDeathAckMessage = "あーあ、 " + Cogs + "がキミの持ってたギャグを取ってっちゃったよ!\nゲラゲラメーターが笑うまではプレイグラウンドから出られないよ。" + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "工場長が倒されたので、コグパーツを手に入れることができませんでした。" + +# MintInterior +ForcedLeaveMintAckMsg = "金庫番が倒されたので、コグドルを手に入れることができませんでした。" #▲要チェック▲ + +# DistributedFactory.py +HeadingToFactoryTitle = "%sへ向かっているよ…" +ForemanConfrontedMsg = "%sは今、工場長と戦っているよ!" + +# DistributedMint.py +MintBossConfrontedMsg = "%sは今、金庫番と戦っているよ!" + +# DistributedStage.py #localize +StageBossConfrontedMsg = "%sがクラークとバトル中!" +stageToonEnterElevator = "%s \nがエレベーターにのったよ。" +ForcedLeaveStageAckMsg = "ロウクラークはキミがたどりつく前にたおされました。ショーカンジョーを取りもどせませんでした。" + + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "他のプレイヤーを待っています…" +MinigamePleaseWait = "…" +DefaultMinigameTitle = "ミニゲームのタイトル" +DefaultMinigameInstructions = "ミニゲームの説明" +HeadingToMinigameTitle = "%sをしよう!" # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "パワーメーター" +MinigamePowerMeterTooSlow = "おそ\nすぎる" +MinigamePowerMeterTooFast = "はや\nすぎる" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "ミニゲームのテンプレート" +MinigameTemplateInstructions = "ミニゲームのテンプレートだよ。これを使って新しいミニゲームを作成してね。" + +# DistributedCannonGame.py +CannonGameTitle = "キャノンゲーム" +CannonGameInstructions = "キミのトゥーンを発射して、できるだけ早く給水塔に入れてあげてね。マウスかやじるしキーを使って目標をさだめられるよ。早く入れれば、みんなが大きなごほうびをもらえるからがんばろう!" +CannonGameReward = "ごほうび" + +# DistributedTwoDGame.py +TwoDGameTitle = "トゥーン・エスケープ" +TwoDGameInstructions = "いそいでコグの隠れ家から逃げるんだ!矢印キーで移動とジャンプ、Ctrlキーでみずでっぽうを発射できるよ。コグ・コインを集めるとボーナスポイントがつくよ。" +TwoDGameElevatorExit = "出口" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "つなひきゲーム" +TugOfWarInstructions = "右と左のやじるしキーをちょうどいい速さで交互にたたいて、あかい線といっしょにみどりのバーをおいてね。たたく速さがおそすぎたり、はやすぎたりすると水のなかに落っこちて終わっちゃうよ!" +TugOfWarGameGo = "スタート!" +TugOfWarGameReady = "よおい…" +TugOfWarGameEnd = "がんばったね!" +TugOfWarGameTie = "ひきわけ!" +TugOfWarPowerMeter = "パワーメーター" + +# DistributedPatternGame.py +PatternGameTitle = Minnie + "のダンスゲーム" +PatternGameInstructions = Minnie + " が、ダンスをみせてくれるよ。" + \ + "やじるしキーを使って、" + Minnie + "のダンスをいま見たようにやってみてね!" +PatternGameWatch = "ダンスステップをみて…" +PatternGameGo = "スタート!" +PatternGameRight = "じょうずね、%s!" +PatternGameWrong = "あ~あ!" +PatternGamePerfect = "かんぺきだったわ、%s!" +PatternGameBye = "ごくろうさま!" +PatternGameWaitingOtherPlayers = "他のプレイヤーを待っててね…" +PatternGamePleaseWait = "ちょっと待っててね…" +PatternGameFaster = "はやすぎちゃった!" +PatternGameFastest = "キミが\n一番はやかったわよ!" +PatternGameYouCanDoIt = "さぁ!\nキミならできるわよ!" +PatternGameOtherFaster = "\nがはやかったわね!" +PatternGameOtherFastest = "\nが一番はやかったよ!" +PatternGameGreatJob = "よくやったわ!" +PatternGameRound = "ラウンド%s!" +PatternGameImprov = "すばらしかったわ!次もがんばってね!" + +# DistributedRaceAI.py +WaitingForJoin = 90 + +# DistributedRaceGame.py +RaceGameTitle = "きょうそうゲーム" +RaceGameInstructions = "数字をじょうずにえらんでクリックしてね。他にだれもえらんでない数字を選ばないと、先にいけないよ!" +RaceGameWaitingChoices = "他のプレイヤーを待っててね…" +RaceGameCardText = "%(name)sがゲットしたのは: %(reward)s" +RaceGameCardTextBeans = "%(name)s がもらえるごほうび: %(reward)s" +RaceGameCardTextHi1 = "すっごいね、%(name)s!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = "1マスすすむ" +RaceGameForwardTwoSpaces = "2マスすすむ" +RaceGameForwardThreeSpaces = "3マスすすむ" +RaceGameBackOneSpace = "1マスもどる" +RaceGameBackTwoSpaces = "2マスもどる" +RaceGameBackThreeSpaces = "3マスもどる" +RaceGameOthersForwardThree = " 他のひとたちは全員\n3マスすすめているよ。" +RaceGameOthersBackThree = "他のひとたちは全員\n3マス戻っているよ。" +RaceGameInstantWinner = "一気に勝ちをきめたー!" +RaceGameJellybeans2 = "ジェリービーン2コ" +RaceGameJellybeans4 = "ジェリービーン4コ" +RaceGameJellybeans10 = "ジェリービーン10コ!" + +# DistributedRingGame.py +RingGameTitle = "リングゲーム" +# color +RingGameInstructionsSinglePlayer = "できるだけ多くの%sうきわをくぐって泳いでみて! やじるしキーを使って泳いでね。" +# color +RingGameInstructionsMultiPlayer = " %sうきわをくぐって泳いでみて。ほかのプレイヤーはちがう色のうきわに挑戦するよ。 やじるしキーを使って泳いでね。" +RingGameMissed = "失敗" +RingGameGroupPerfect = "パーフェクト\nチームワーク!" +RingGamePerfect = "パーフェクト!" +RingGameGroupBonus = "グループボーナス" + +# RingGameGlobals.py +ColorRed = "あかい" +ColorGreen = "みどり色の" +ColorOrange = "オレンジ色の" +ColorPurple = "むらさき色の" +ColorWhite = "しろい" +ColorBlack = "くろい" +ColorYellow = "きいろの" + +# DistributedDivingGame.py #localize +DivingGameTitle = "ダイビングゲーム" +# color +DivingInstructionsSinglePlayer = "たからものはみずうみのそこにあるよ。矢印キーを使っておよいでね。サカナをさけながら、ボートまでたからをはこぼう!" +# color +DivingInstructionsMultiPlayer = "たからものはみずうみのそこにあるよ。矢印キーを使っておよいでね。みんなで力をあわせてボートまでたからものをはこぼう!" +DivingGameTreasuresRetrieved = "たからの数" + +#Distributed Target Game +TargetGameTitle = "スリング・ショット" +TargetGameInstructionsSinglePlayer = "発射の方向とスピードがポイントだよ。" +TargetGameInstructionsMultiPlayer = "まとの中心を目指して着地しよう!" +TargetGameBoard = "ラウンド%s - がんばってね!" +TargetGameCountdown = "%s秒でじどう的にはっしゃ!" +TargetGameCountHelp = "左右の矢印キーをこうごに連打してパワーを調節。やめると発射だよ。" +TargetGameFlyHelp = "下向き矢印でカサを開いてね。" +TargetGameFallHelp = "ターゲットには矢印キーでコントロールしながら着地しよう!" +TargetGameBounceHelp = "地面ではねるとターゲットからはずれちゃうかも。" + +#Distributed Photo Game +PhotoGameScoreTaken = "%s: %s\nYou: %s" +PhotoGameScoreBlank = "Score: %s" +PhotoGameScoreOther = "\n%s"#"スコア: %s\n%s" +PhotoGameScoreYou = "\nベスト・ボーナス!"#"スコア: %s\nベスト・ボーナス!" + + +# DistributedTagGame.py +TagGameTitle = "おにごっこゲーム" +TagGameInstructions = "できるだけ多くのアイスクリームを取ろう! オニになるとアイスは取れなくなるからね!" +TagGameYouAreIt = "キミがオニだよ!" +TagGameSomeoneElseIsIt = "%s がオニだよ!" + +# DistributedMazeGame.py +MazeGameTitle = "メイズゲーム" +MazeGameInstructions = "ミッキーマークをできるだけあつめよう!\nでも、" + Cogs + "には気をつけてね!" + +# DistributedCatchGame.py +CatchGameTitle = "キャッチゲーム" +CatchGameInstructions = "できるだけ多くの%(fruit)sを" + Cogs + "に気をつけながらキャッチして。 %(badThing)sはキャッチしないようにね!" +CatchGamePerfect = "パーフェクト!" +CatchGameApples = 'りんご' +CatchGameOranges = 'オレンジ' +CatchGamePears = 'ようナシ' +CatchGameCoconuts = 'ココナッツ' +CatchGameWatermelons = 'すいか' +CatchGamePineapples = 'バイナップル' +CatchGameAnvils = 'かなとこ' + +# DistributedPieTossGame.py +PieTossGameTitle = "パイ投げゲーム" +PieTossGameInstructions = "ターゲットにパイを投げよう!" + +# DistributedPhotoGame.py +PhotoGameInstructions = "下のトゥーン達を撮ろう!マウスでフレームを動かして左クリックで撮影だよ。Ctrlキーでズーム調整して、矢印キーで周りをみわたせるよ。☆の数が多い程高いポイントをゲット!" +PhotoGameTitle = "パパラトゥーン!" +PhotoGameFilm = "フィルム" +PhotoGameScore = "チームスコア: %s\n\nベストフォト: %s\n\nトータルスコア: %s" + +# DistributedCogThiefGame.py +CogThiefGameTitle = Cog + "バレル・スティール" +CogThiefGameInstructions = "コグ達からギャグ・バレルを守れ!矢印キーでトゥーンを操作して、Ctrlキーでパイを投げつけよう。ななめにも進めるよ!" +CogThiefBarrelsSaved = "%(num)d個のバレルを\n守った!" +CogThiefBarrelSaved = "%(num)d個のバレルを\n守った!" +CogThiefNoBarrelsSaved = "ぜんぶ\n盗まれちゃった…" +CogThiefPerfect = "パーフェクト!" + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "プレイ" + +# Purchase.py +GagShopName = "グーフィーの ギャグショップ" +GagShopPlayAgain = "もういちど\nプレイする" +GagShopBackToPlayground = "プレイグラウ\nンドにもどる" +GagShopYouHave = "使えるジェリービーンは%sコだよ。" +GagShopYouHaveOne = "使えるジェリービーンは1コだよ。" +GagShopTooManyProps = "もうこれ以上もてないよ。" +GagShopDoneShopping = "ショッピング\n終了" +# name of a gag +GagShopTooManyOfThatGag = "すでに%sをたくさん持ってるよ。" +GagShopInsufficientSkill = "まだ使えないよ。" +# name of a gag +GagShopYouPurchased = "%sをゲット!" +GagShopOutOfJellybeans = "ジェリービーンがないよ!" +GagShopWaitingOtherPlayers = "他のプレイヤーを待っててね…" +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%sは、接続を切りました。" +GagShopPlayerExited = "%s は、店をでたよ。" +GagShopPlayerPlayAgain = "もう1回!" +GagShopPlayerBuying = "お買い物中" + +# MakeAToon.py +GenderShopQuestionMickey = "男の子のトゥーンをつくるには、\nぼくをおしてね!" #CC_mickey_create01.mp3 +GenderShopQuestionMinnie = "女の子のトゥーンをつくるには、わたしをおしてね!" #CC_minnie_create01.mp3 +GenderShopFollow = "ついてきて!" #CC_mickey_create02.mp3 (if Mickey) +GenderShopSeeYou = "またね!"#CC_mickey_create03.mp3 (if Mickey) +GenderShopBoyButtonText = "男の子" +GenderShopGirlButtonText = "女の子" + +# BodyShop.py +BodyShopHead = "あたま" +BodyShopBody = "おなか" +BodyShopLegs = "あし" + +# ColorShop.py +ColorShopHead = "あたま" +ColorShopBody = "おなか" +ColorShopLegs = "あし" +ColorShopToon = "いろ" +ColorShopParts = "パーツ" +ColorShopAll = "すべて" + +# ClothesShop.py +ClothesShopShorts = "ズボン" +ClothesShopShirt = "シャツ" +ClothesShopBottoms = "ボトム" + +# MakeAToon +MakeAToonDone = "けってい" +MakeAToonCancel = "とりけす" +MakeAToonNext = "つぎへ" +MakeAToonLast = "もどる" +CreateYourToon = "左右のやじるしをクリックしてパーツを選んでね。" +CreateYourToonTitle = "トゥーンをつくる" +CreateYourToonHead = "他のトゥーンを選ぶには、'あたま' のやじるしをクリックしてね。" +MakeAToonClickForNextScreen = "次のステップに進むには、右下のやじるしをクリックしてね。" +PickClothes = "やじるしをクリックして、ようふくを選んでね。" +PickClothesTitle = "ようふくをえらぶ" +PaintYourToon = "やじるしをクリックして、トゥーンに色をつけてね。" +PaintYourToonTitle = "いろをつける" +MakeAToonYouCanGoBack = "前の画面に戻ってパーツを選び直すこともできるよ!" +MakeAFunnyName = "キミのトゥーン\nにおもしろい名\n前をつけよう!" +MustHaveAFirstOrLast1 = "トゥーンにはなまえかみょうじが必要だとおもわない?" +MustHaveAFirstOrLast2 = "キミのトゥーンになまえかみょうじほしくないの?" +ApprovalForName1 = "そうだよ、キミのトゥーンはすごくいいなまえをもつ価値があるよ!" +ApprovalForName2 = "トゥーンのなまえは、いいものしかそろえてないよ!" +MakeAToonLastStep = "トゥーンタウンへ行く前の最後のステップだからね!" +PickANameYouLike = "すきななまえを選んでね!" +NameToonTitle = "なまえをつける" +TitleCheckBox = "かたがき" +FirstCheckBox = "なまえ" +LastCheckBox = "みょうじ" +RandomButton = "ランダム" +NameShopSubmitButton = "名前を申込む" +TypeANameButton = "なまえを入力" +TypeAName = "ここにあるなまえはすきじゃない?\nここをクリックして -->" +PickAName = "なまえをえらぼうゲームをやってみて!\nここをクリックして -->" +PickANameButton = "なまえをえらぶ" +RejectNameText = "このなまえは使えないよ。もういちどトライしてね。" +WaitingForNameSubmission = "名前を登録します..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_japanese.txt" +PetshopUnknownName = "名前: ???" +PetshopDescGender = "せいべつ:\t%s" +PetshopDescCost = "かかく:\t%sジェリービーン" +PetshopDescTrait = "とくせい:\t%s" +PetshopDescStandard = "スタンダード" +PetshopCancel = lCancel +PetshopSell = "魚を売る" +PetshopAdoptAPet = "ドゥードゥルを飼う" +PetshopReturnPet = "ドゥードゥルを返す" +PetshopAdoptConfirm = "%sを飼う(%dジェリービーン)" +PetshopGoBack = lBack +PetshopAdopt = "飼う" +PetshopReturnConfirm = "%sを返しますか?" +PetshopReturn = "返す" +PetshopChooserTitle = "今日のドゥードゥル" +PetshopGoHomeText = 'おうちに戻って、新しいドゥードゥルと遊びに行きたいですか?' + +# NameShop.py +NameShopNameMaster = "NameMaster_japanese.txt" +NameShopPay = "今すぐお申しこみを!" +NameShopPlay = "登録無料" +NameShopOnlyPaid = "フルアクセスメンバーだけが、\nトゥーンのなまえを変えることができるんだ。\nキミが申しこみするまでの\nトゥーンのなまえは\nだよ。" +NameShopContinueSubmission = "なまえをとどける" +NameShopChooseAnother = "なまえをつける" +NameShopToonCouncil = "キミのなまえが\n使えるかどうか調べるんだ\n" + \ + "調べるのには数日かかるよ。\nそれまでのなまえは:\n" +PleaseTypeName = "トゥーンになまえをつけてあげてね:" +AllNewNames = "全ての新しいなまえは、\nトゥーン評議会のOKが\n必要なんだよ。" +NameMessages = "Be creative and remember:\nno Disney-related names, please." +NameShopNameRejected = "申込んだ\nなまえは\nだめだって。" +NameShopNameAccepted = "おめでとう!\n申込んだ\nなまえが\n使えるよ。" +NoPunctuation = "なまえに句読点(。、)は使えないよ!" +PeriodOnlyAfterLetter = "なまえでは、文字のあと以外、ピリオド(.)は使えないよ。" +ApostropheOnlyAfterLetter = "なまえでは、文字のあと以外、アポストロフィー( ' )は使えないよ。" +NoNumbersInTheMiddle = "言葉の間に数字があるのはだめだよ。" +ThreeWordsOrLess = "キミのなまえは3つの言葉かそれ以下じゃないとだめだよ。" +CopyrightedNames = ( + "ミッキー", + "ミッキー・マウス", + "ミッキーマウス", + "ミニーマウス", + "ミニー", + "ミニー・マウス", + "ミニーマウス", + "ドナルド", + "ドナルド・ダッグ", + "ドナルドダッグ", + "プルート", + "グーフィー", + ) +NumToColor = ['ホワイト', 'ピーチ', 'ブライトレッド', 'レッド', 'マルーン', + 'シエンナ', 'ブラウン', 'タン', 'コーラル', 'オレンジ', + 'イエロー', 'クリーム', 'シトリーン', 'ライム', 'シーグリーン', + 'グリーン', 'ライトブルー', 'アクア', 'ブルー', + 'ペリウィンクル', 'ロイヤルブルー', 'スレートブルー', 'パープル', + 'ラベンダー', 'ピンク'] +AnimalToSpecies = { + 'dog': 'ドッグ', + 'cat' : 'キャット', + 'mouse' : 'マウス', + 'horse' : 'ホース', + 'rabbit' : 'ラビット', + 'duck' : 'ダック', + 'monkey' : 'モンキー', + 'bear' : 'ベア', + 'pig' : 'ピッグ' + } +NameTooLong = "なまえは全角8文字までだよ。もう一度入力してね。" +ToonAlreadyExists = "もうトゥーン名%sができてるよ!" +NameAlreadyInUse = "そのなまえはもう使われているよ!" +EmptyNameError = "なまえを先に入力してね。" +NameError = "ごめん、そのなまえじゃだめみたい" + +# NameCheck.py +NCTooShort = 'なまえは全角2文字以上にしてね' +NCNoDigits = 'なまえには数字をいれないでね。' +NCNeedLetters = 'なまえのそれぞれの言葉には、文字をいれてね。' +NCNeedVowels = 'なまえのそれぞれの言葉には、母音をいれてね。' +NCAllCaps = 'なまえは全部大文字にしないでね。' +NCMixedCase = 'なまえに大文字がおおすぎるね。' +NCBadCharacter = "なまえには'%s'をいれないでね。\n\n漢字・記号・ABC…は使わないでね。" +NCGeneric = 'ごめん、このなまえじゃだめみたい。\n\nなまえは全角8文字以下にしてね。' +NCTooManyWords = '半角スペースは三つまでにしてね。' +NCDashUsage = "なまえにハイフン(-)は使えないよ。" +NCCommaEdge = "なまえにコンマ(,)は使えないよ。" +NCCommaAfterWord = "なまえにコンマ(,)は使えないよ。" +NCCommaUsage = 'なまえにコンマ(,)は使えないよ。' +NCPeriodUsage = 'なまえにピリオド(.)は使えないよ。' +NCApostrophes = "なまえにアポストロフィー(')は使えないよ。" + +# DistributedTrophyMgrAI.py +RemoveTrophy = lToonHQ+":キミが救った建物のひとつを" + Cogs + " にのっとられた!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'もっと考える時間がほしい?' +STOREOWNER_GOODBYE = 'またね!' +STOREOWNER_NEEDJELLYBEANS = 'ジェリービーンをとりにいくには、トロリーに乗らなきゃね。' +STOREOWNER_GREETING = '買いたいものを選んでね。' +STOREOWNER_BROWSING = 'ウィンドウショッピングもできるけど、ようふくを買うにはようふく券が必要だよ。' +STOREOWNER_NOCLOTHINGTICKET = 'ようふくを買うにはようふく券が必要だよ。' +# translate +STOREOWNER_NOFISH = 'ここに戻って、釣った魚をジェリービーンと交換しよう!' +STOREOWNER_THANKSFISH = 'ありがとう!ペットショップがきっとよろこんでくれるよ。バイバイ!' +STOREOWNER_THANKSFISH_PETSHOP = "おっ、いい種類の魚がいるね。ありがとう!" +STOREOWNER_PETRETURNED = "安心して、僕たちがキミのドゥードゥルのためのおうちを探してあげるから!" +STOREOWNER_PETADOPTED = "新しいドゥードゥル、おめでとう! キミのおうちで一緒にあそべるよ。" +STOREOWNER_PETCANCELED = "もしお気に入りのドゥードゥルを見つけたら、ほかのトゥーンが飼う前にキミが飼おう!" + +STOREOWNER_NOROOM = "うーん…あたらしいようふくを買うまえに、キミはクローゼットの中を整理したいかもしれないね。" +STOREOWNER_CONFIRM_LOSS = "キミのクローゼットはいっぱいだ!前にキミが着ていたようふくはなくなるよ。" +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "ワオ!キミは%s匹の魚(%s匹中)を釣ったね。ごほうびにトロフィーとゲラゲラブーストをあげよう!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": コグの侵略がはじまった!!!" +SuitInvasionBegin2 = lToonHQ+": %s にトゥーンタウンをのっとられた!!!" +SuitInvasionEnd1 = lToonHQ+": %s の侵略はおわった!!!" +SuitInvasionEnd2 = lToonHQ+": トゥーンがまた今日という日を救った!!!" +SuitInvasionUpdate1 = lToonHQ+": コグの侵略はいま%sコグになっている!!!" +SuitInvasionUpdate2 = lToonHQ+": %sを倒さねば!!!" +SuitInvasionBulletin1 = lToonHQ+": コグの侵略が進行中!!!" +SuitInvasionBulletin2 = lToonHQ+": %s にトゥーンタウンをのっとられた!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "トゥーン・プラトゥーン" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "こんにちは、トム!\nトゥーンタウンの新しい住人になにか面白いギャグ、持ってない?" #CC_mickey_tutorial02.mp3 ***DELETED "CC_mickey_tutorial01.mp3"*** +QuestScriptTutorialMickey_2 = "もちろん、%s!" #CC_tom_tutorial_mickey01.mp3 +QuestScriptTutorialMickey_3 = "彼がコグについて\nいろいろ教えてくれるって!\aそれじゃあ、\nまた後でね~!" #CC_mickey_tutorial03.mp3 \a CC_mickey_tutorial05.mp3 ***DELETED "CC_mickey_tutorial04.mp3"*** +QuestScriptTutorialMickey_4 = "やじるしキーを使ってこっちにおいで!" #CC_tom_tutorial_mickey02.mp3 + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "こんにちは、トム!\nトゥーンタウンの新しい住人になにか面白いギャグ、持ってない?" #CC_minnie_tutorial02.mp3 ***DELETED "CC_minnie_tutorial01.mp3"*** +QuestScriptTutorialMinnie_2 = "もちろん、%s!" #CC_tom_tutorial_minnie01.mp3 +QuestScriptTutorialMinnie_3 = "彼がコグについて\nいろいろ教えてくれるのよ!\aそれじゃあ、\nまたね~!" #CC_minnie_tutorial03.mp3 \a CC_minnie_tutorial05.mp3 *** DELETED "CC_minnie_tutorial04.mp3"*** + +QuestScript101_1 = "これらが「コグ」って言うんだ!\nトゥーンタウンをのっとろうとしているロボットたちなんだ。" #Please play "CC_tom_tutorial_questscript01.mp3" only / "CC_tom_tutorial_questscript02.mp3" is included. +QuestScript101_2 = "たくさんの種類のコグがいるんだけど…" #CC_tom_tutorial_questscript03.mp3 +QuestScript101_3 = "…ハッピーな\nトゥーンビルをね…" #CC_tom_tutorial_questscript041.mp3 +QuestScript101_4 = "…みにくいコグのビルにしてしまうんだ!" #CC_tom_tutorial_questscript05.mp3 +QuestScript101_5 = "でも頭のかったーいコグはギャグをまったく理解することができないんだ!" #CC_tom_tutorial_questscript06.mp3 +QuestScript101_6 = "だからトゥーンのおもしろいギャグで、 コグの動きを止めることができるんだよ。" #CC_tom_tutorial_questscript07.mp3 +QuestScript101_7 = "たくさんのギャグがあるけど、まずはこれとこれかな…" #CC_tom_tutorial_questscript08.mp3 +QuestScript101_8 = "そうだ、ゲラゲラメーターも必要だね!" #CC_tom_tutorial_questscript09.mp3 +QuestScript101_9 = "ゲラゲラメーターが低すぎると、かなしくなって落ち込んじゃうんだよ。" #CC_tom_tutorial_questscript10.mp3 +QuestScript101_10 = "つまりハッピーだと、トゥーンは健康ってことなんだ!" #CC_tom_tutorial_questscript11.mp3 +QuestScript101_11 = "あーっ! ぼくの店の外にコグがいる!" #CC_tom_tutorial_questscript12.mp3 +QuestScript101_12 = "たすけて、おねがい! コグをやっつけて!" #CC_tom_tutorial_questscript13.mp3 +QuestScript101_13 = "キミに最初のトゥーンタスクをあげるね!\n " #CC_tom_tutorial_questscript14.mp3 ***DELETED "CC_tom_tutorial_questscript15.mp3"*** +QuestScript101_14 = "外にいるオベッカーを倒そう!いそいで!" #CC_tom_tutorial_questscript16.mp3 + +QuestScript110_1 = "よくひとりでオベッカーを倒したね。 それじゃあ、ごほうびにトゥーンガイドをあげよう…" #CC_harry_tutorial_questscript01.mp3 +QuestScript110_2 = "トゥーンガイドには、たくさんの情報がはいってるよ。" #CC_harry_tutorial_questscript02.mp3 +QuestScript110_3 = "それを開いてごらん!ぼくがいろいろ教えてあげよう。" #CC_harry_tutorial_questscript03.mp3 +QuestScript110_4 = "地図はキミが行ったところを示してるんだ。" #CC_harry_tutorial_questscript04.mp3 +QuestScript110_5 = "ページをめくるとキミのギャグが…" #CC_harry_tutorial_questscript05.mp3 +QuestScript110_6 = "ん~!ギャグが残ってないね! キミに新しいタスクをあげよう。" #CC_harry_tutorial_questscript06.mp3 +QuestScript110_7 = "キミのやらなきゃいけないタスクは次のページに書いてあるよ。" #CC_harry_tutorial_questscript07.mp3 +QuestScript110_8 = "それじゃあトロリーに乗って、ギャグを買うためのジェリービーンを稼ぎに行こう!" #CC_harry_tutorial_questscript08.mp3 +QuestScript110_9 = "まずはトロリー乗り場に行こう。ぼくの後ろのドアからプレイグラウンドへ出られるよ。" #CC_harry_tutorial_questscript09.mp3 +QuestScript110_10 = "さぁ、トゥーンガイドをとじてトロリーをみつけて!" #CC_harry_tutorial_questscript10.mp3 +QuestScript110_11 = "それがすんだら、トゥーンHQに戻るんだ。 じゃあね!" #CC_harry_tutorial_questscript11.mp3 + +QuestScriptTutorialBlocker_1 = "やぁ、こんにちは!" #CC_flippy_tutorial_blocker01.mp3 +QuestScriptTutorialBlocker_2 = "あのぅ… こんにちは??" #CC_flippy_tutorial_blocker02.mp3 +QuestScriptTutorialBlocker_3 = "あ~、そうか! スピードチャットの使い方がわからないんだね!" #CC_flippy_tutorial_blocker03.mp3 +QuestScriptTutorialBlocker_4 = "そのボタンをクリックして、なにか言ってみて。" #CC_flippy_tutorial_blocker04.mp3 +QuestScriptTutorialBlocker_5 = "その調子!!\aキミがこれから行くところには、話せるトゥーンがたくさんいるからね。" #CC_flippy_tutorial_blocker05.mp3 \a CC_flippy_tutorial_blocker06.mp3 +QuestScriptTutorialBlocker_6 = "キミの友だちとキーボードを使ってチャットしたい場合は、となりの青いボタンをつかうんだよ。" #CC_flippy_tutorial_blocker07.mp3 +QuestScriptTutorialBlocker_7 = "\"チャット\"ボタンっていうんだけどこれを使うには、トゥーンタウンのオフィシャルメンバーになる必要があるんだ。" #CC_flippy_tutorial_blocker08.mp3 +QuestScriptTutorialBlocker_8 = "がんばって! じゃあまたあとでね!" #CC_flippy_tutorial_blocker09.mp3 + +""" +GagShopTut + +違うタイプのギャグを使う能力をかせぐこともできるぞ。 + +""" + +QuestScriptGagShop_1 = "ギャグショップへようこそ!" +QuestScriptGagShop_1a = "ここはトゥーンがコグと戦うためのギャグを買いに来る場所だよ。" +#QuestScriptGagShop_2 = "これはキミが持っているジェリービーンの数。" +#QuestScriptGagShop_3 = "ギャグを買うには、ギャグのボタンをクリックしてね。さっそくやってみて!" +QuestScriptGagShop_3 = "ギャグのボタンをクリックすれば、そのギャグを買えるよ!できるかな?" +QuestScriptGagShop_4 = "いいね!買ったギャグはコグと戦っているときに使えるよ!" +QuestScriptGagShop_5 = "「なげる」と「みずでっぽう」のレベルの高いギャグだよ!" #★ +QuestScriptGagShop_6 = "ギャグの買い物が終わったら、このボタンを押してプレイグラウンドに戻ろう!" +QuestScriptGagShop_7 = "普段ならこのボタンを押すと、もう一度トロリーゲームで楽しめるんだけど…" +QuestScriptGagShop_8 = "…今回は時間がないから、今度ためしてみよう。トゥーンHQに向かってね!" + +QuestScript120_1 = "よくトロリーをみつけたね!\aところで、銀行員のボブにあった?\aきれいな歯をしてるんだよ。\aこのチョコレートをおみやげに、彼に話しかけて自己紹介してみたら?" +QuestScript120_2 = "銀行員のボブは、トゥーンタウンバンクにいるよ。" + +QuestScript121_1 = "おいしいチョコレートをありがとう!\aねぇ、ぼくを助けてくれたらごほうびをあげるよ。\aコグのやつら、ぼくのきんこのかぎを盗んだんだ。\aコグたちを倒して、盗まれたかぎをみつけておくれよ。\aかぎをみつけたら、ぼくに持ってきて!" + +QuestScript130_1 = "よくトロリーをみつけたね!\aところで、今日ピート教授への荷物を受けとったんだ。\a彼が注文した新しいチョークにちがいないとおもうよ。\a彼にそれをとどけてくれないかなぁ?\a彼はスクールハウスにいるよ。" + +QuestScript131_1 = "ああ、チョークありがとう。\aなんだ!?!\aああ、コグたちがわたしの黒板を盗んでいってしまったのか…。コグたちを倒して、盗まれた黒板をみつけてくれないか。\aみつけたら、わたしに持ってきてくれ。" + +QuestScript140_1 = "よくトロリーをみつけたね!\aところで、ぼくにはかなりの読書中毒のとしょかんいんのラリーという友だちがいるんだ。\aこの間、ドナルドのハトバにいるとき、この本をひろったんだ。\a彼にこれを渡してくれないかい?彼はだいたいトゥーンタウンライブラリーにいるよ。" + +QuestScript141_1 = "ありがとう、この本でぼくのコレクションはだいぶそろったな。\aどれどれ…\aんー、あれー…\aめがねどこにおいたかなぁ?\aあのコグたちがぼくんちの建物をのっとる前まではあったんだ。\aコグを倒して、ぼくの盗まれためがねをみつけてくれよ。\aみつけてもってきてくれたら、ごほうびをあげる!" + +QuestScript145_1 = "トロリーは大丈夫だったみたいだね。\aよく聞いて!コグたちに「黒板消し」を盗まれてしまったんだ!\aストリートに出て、コグと戦って黒板消しを取り戻してくれない?\aストリートに行くにはこんな感じのトンネルを通ってね。" +QuestScript145_2 = "もし黒板消しを見つけたら、ここにもってきてくれないかな?\aギャグが必要だったら、トロリーに乗るんだよ。忘れないでね。\aそれと、ゲラゲラメーターをまんたんにしたかったら、プレイグラウンドのアイテムをひろってね!" + +QuestScript150_1 = "あー… つぎのタスクはキミひとりではむずかしすぎるなぁ!" +QuestScript150_2 = "友だちをつくるには、他のプレイヤーをみつけて、あたらしい友だちボタンを使って" +QuestScript150_3 = "友だちをつくったらすぐ、ここに戻ってきてね。" +QuestScript150_4 = "いくつかのタスクは、ひとりでやるのにはむずかしすぎるよ!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +SellbotBossName = "シニア コグゼキュティブ" +CashbotBossName = "マネーマネー" +LawbotBossName = "チーフ ジャスティス" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "キミは「格上げ」した!%s おめでとう!" +BossCogDoobersAway = { 's' : "さあ、急いでウリアゲを上げて!" } +BossCogWelcomeToons = "ようこそ、新しいコグの仲間達!" +BossCogPromoteToons = "キミは「格上げ」した!%s おめでとう!" +CagedToonInterruptBoss = "ねぇねぇ!\nこっちだよ!" +CagedToonRescueQuery = "キミたちは助けに来てくれたの?" +BossCogDiscoverToons = "は? トゥーンめが! ヘンソウしたってお見通しさ!" +BossCogAttackToons = "いざ!" +CagedToonDrop = [ + "やったね!彼を追い詰めたね。", + "彼の後を追いかけて! 逃げようとしているよ!", + "キミたちは本当にすごいね!", + "ファンタスティック! 彼をやっつけたも\n同然だね!", + ] +CagedToonPrepareBattleTwo = "ねぇ見て! 彼が逃げようとしているぞ!\aみんな、助けて!彼を止めて!" +CagedToonPrepareBattleThree = "ふーっ、\nもうすぐ自由だ!\aコグゼキュティブを\n直接、攻撃しよう!\aキミが使えるパイをたくさん手に入れたよ!\aジャンプして、オリの底にさわればキミにパイを渡せるんだ!\aパイを手に入れたら\nInsertキーを押してみよう!\aパイを投げることが出来るよ!" +BossBattleNeedMorePies = "もっとパイが必要だよ!" +BossBattleHowToGetPies = "オリのところまでジャンプして、パイを手に入れよう!" +BossBattleHowToThrowPies = "Insertキーを押すとパイを投げるぞ!" +CagedToonYippee = "いやっほう!" +CagedToonThankYou = "やった~!自由になったぞ!\a本当にありがとう!\a大きな借りができたね。\aもしバトルで助けが必要になったら、電話して!\aSOSボタンを押せば、呼ぶことができるよ!" +CagedToonPromotion = "\aコグゼキュティブがキミの「格上げ」の紙を置き忘れたみたいだよ。\aキミのために取っておくから、後で「格上げ」されるはずだよ!" +CagedToonLastPromotion = "\aワオ!キミのコグスーツ、とうとうレベル%sまできたね。\aそれ以上は格上げされないよ。\aこれ以上はスーツをアップグレードできないけど、もちろんトゥーンを助けに行けるよ。" +CagedToonHPBoost = "\aキミはこの本部からたくさんのトゥーンの仲間達を助けたね!\aトゥーン本部はキミにさらなるゲラゲラブーストをあげることにしたよ!おめでとう!" +CagedToonMaxed = "\aコグのスーツがレベル%sに達したね。本当に素晴らしい!\aトゥーン本部に代わって、もっとトゥーンを助けに戻ってきてくれたことに感謝するよ!" +CagedToonGoodbye = "それでは!" + + +CagedToonBattleThree = { + 10: "いいジャンプだよ、%(toon)s。 パイをどうぞ!", + 11: "やあ、%(toon)s! パイをどうぞ!", + 12: "こんにちは、%(toon)s! パイを手に入れたよ!", + + 20: "ねぇ、%(toon)s! オリのところまでジャンプして、パイを投げて!", + 21: "おーい、%(toon)s! Ctrlキーを使ってジャンプして、オリをさわって!", + + 100: "Insertキーを押すとパイを投げるよ!", + 101: "パイがどのくらい飛ぶかは青いパワーメーターでわかるよ!", + 102: "まず彼の土台に向けてパイを投げて、彼の動きを狂わせよう!", + 103: "ドアが開くのを待って、中にパイを投げ込もう!", + 104: "彼がうろたえているときに、顔をねらうか、おなかをねらってあとずさりさせよう!", + 105: "当たったときの色をみればちゃんとギャグが決まったかがわかるよ!", + 106: "トゥーンにパイがあたると、トゥーンのゲラゲラメーターが回復するよ!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "これで終わりだ!やっかいなトゥーンはこりごりだ!" +CashbotBossOuttaHere = "電車の時間に間に合わない!" +ResistanceToonName = "マタ・ヘアリー" +ResistanceToonCongratulations = "やったね!おめでとう!\aレジスタンスの仲間入りだ!\aピンチの時のあいことばを教えてあげるね。\a%s\a「%s」\a一度しか使えないから、ちゃんとタイミング考えよう!" +ResistanceToonToonupInstructions = "キミの近くのトゥーンみんなが%sゲラゲラポイントをゲット!" +ResistanceToonToonupAllInstructions = "キミの近くのトゥーンみんなのゲラゲラポイントが回復!" +ResistanceToonMoneyInstructions = "キミの近くのトゥーンみんなが%sジェリービーンをゲット!" +ResistanceToonMoneyAllInstructions = "キミの近くのトゥーンみんなのジェリービーンをゲット!" +ResistanceToonRestockInstructions = "キミの近くのトゥーンみんなの\"%s\"ギャグが回復!" +ResistanceToonRestockAllInstructions = "キミの近くのトゥーンみんなのギャグが回復!" + +ResistanceToonLastPromotion = "\aワオ!コグスーツがレベル%sになったね!\aコグは今のレベル以上にはなれないんだ。\aだからコグスーツのレベルも上がらないんだけど、レジスタンスのためにこれからもがんばって!" +ResistanceToonHPBoost = "\aレジスタンスのために本当にがんばってくれているね!\aトゥーンひょうぎかいがキミにゲラゲラポイントをあげるって!おめでとう!" +ResistanceToonMaxed = "\aレベル%sのコグスーツを手に入れたんだね!すばらしいよ!\aトゥーンひょうぎかいにかわって、トゥーン達を助けにもどってくれたことに心からかんしゃします!" + +CashbotBossCogAttack = "つかまえろ!!" +ResistanceToonWelcome = "キミ、やったね!マネーマネーが僕らを見つける前に金庫室まで行こう!" +ResistanceToonTooLate = "ちぇっ!おそすぎたかな。" +CashbotBossDiscoverToons1 = "ハーッ、ハッハッハ!" +CashbotBossDiscoverToons2 = "くんくん。思ったとおりだ!トゥーンのにおいがしたんだよ。にせものめ!" +ResistanceToonKeepHimBusy = "マネーマネーの気をそらして!これからワナをしかけるから!" +ResistanceToonWatchThis = "よーく、見ててね!" +CashbotBossGetAwayFromThat = "ねぇ!それからはなれて!" +ResistanceToonCraneInstructions1 = "台にのぼって、じしゃくをコントロールしよう!" +ResistanceToonCraneInstructions2 = "やじるしキーを使ってクレーンを動かして、Ctrlキーでモノをつかめるよ。" +ResistanceToonCraneInstructions3 = "じしゃくで金庫をつかんで、マネーマネーの安全ヘルメットをはずそう!" +ResistanceToonCraneInstructions4 = "ヘルメットが取れたら、動かなくなったグーンをつかんで頭にあてよう!" +ResistanceToonGetaway = "おーっと!にげろ!" +CashbotCraneLeave = "クレーンからはなれる" +CashbotCraneAdvice = "やじるしキーを使うとクレーンのあたまが動くよ。" +CashbotMagnetAdvice = "CTRLキーを押すとモノをつかめるよ。" +CashbotCraneLeaving = "クレーンからはなれているところ" + +MintElevatorRejectMessage = "キミの%sコグスーツを完成させるまでは、中に入れないよ!" +BossElevatorRejectMessage = "キミのトゥーンが「格上げ」されるまでは、このエレベーターに乗ることはできません。" +NotYetAvailable = "このエレベーターにはまだ乗れないよ" + +# Types of catalog items--don't translate yet. +FurnitureTypeName = "家具" +PaintingTypeName = "絵" +ClothingTypeName = "洋服" +ChatTypeName = "新しいフレーズ" +EmoteTypeName = "演技のレッスン" +BeanTypeName = "ジェリービーン" +PoleTypeName = "釣りざお" +WindowViewTypeName = "窓の景色" +PetTrickTypeName = "ドゥードゥル\nトレーニング" +GardenTypeName = "ガーデンアイテム" +RentalTypeName = "レンタルアイテム" +GardenStarterTypeName = "ガーデニングキット" +NametagTypeName = "Name tag" + +#rental names +RentalHours = "時間" +RentalOf = "Of" +RentalCannon = "レンタルキャノン" +RentalGameTable = "Game Table!" +RentalTime = "時間" + +EstateCannonGameEnd = "キャノンゲームのレンタルは終わったよ。" +GameTableRentalEnd = "ゲームテーブルのレンタルは終わったよ。" + +MessageConfirmRent = "レンタルする?後でレンタルしたければキャンセルしてね。" +MessageConfirmGarden = "ガーデニングするかい?" + +#nametag Names +NametagPaid = "Citizen Name Tag" +NametagAction = "Action Name Tag" +NametagFrilly = "Frilly Name Tag" + +FurnitureYourOldCloset = "キミの古いクローゼット" +FurnitureYourOldBank = "キミの古い銀行" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py--don't translate yet. +FurnitureNames = { + 100 : "ひじかけいす", + 105 : "ひじかけいす", + 110 : "いす", + 120 : "デスクチェア", + 130 : "ログチェア", + 140 : "ロブスターチェア", + 145 : "ライフジャケットチェア", + 150 : "サドルスツール", + 160 : "ネイティブチェアー", + 170 : "カップケーキチェアー", + 200 : "ベッド", + 205 : "ベッド", + 210 : "ベッド", + 220 : "バスタブベッド", + 230 : "はっぱのベッド", + 240 : "ボートのベッド", + 250 : "トゲトゲハンモック", + 260 : "アイスクリームベッド", + 270 : "エリンとネコのベッド", + 300 : "プレイヤーピアノ", + 310 : "パイプオルガン", + 400 : "だんろ", + 410 : "だんろ", + 420 : "まるいだんろ", + 430 : "だんろ", + 440 : "りんごのだんろ", + 450 : "エリンのだんろ", + 500 : "クローゼット", + 502 : "クローゼット(15)", + 504 : "クローゼット(20)", + 506 : "クローゼット(25)", + 510 : "クローゼット", + 512 : "クローゼット(15)", + 514 : "クローゼット(20)", + 516 : "クローゼット(25)", + 600 : "小さいスタンド", + 610 : "大きいスタンド", + 620 : "テーブルライト", + 625 : "テーブルライト", + 630 : "ひなぎくのランプ", + 640 : "ひなぎくのランプ", + 650 : "くらげのランプ", + 660 : "くらげのランプ", + 670 : "カウボーイランプ", + 700 : "ふかふかのいす", + 705 : "ふかふかのいす", + 710 : "ソファ", + 715 : "ソファ", + 720 : "わらのカウチ", + 730 : "ショートケーキカウチ", + 800 : "つくえ", + 810 : "ログデスク", + 900 : "かさたて", + 910 : "コートかけ", + 920 : "ごみばこ", + 930 : "赤いキノコ", + 940 : "黄色いキノコ", + 950 : "コート掛け", + 960 : "タルのスタンド", + 970 : "サボテン", + 980 : "テント小屋", + 990 : "ジュリエットのせんす", + 1000 : "大きなじゅうたん", + 1010 : "丸いじゅうたん", + 1015 : "丸いじゅうたん", + 1020 : "小さいじゅうたん", + 1030 : "葉っぱのマット", + 1100 : "かざりだな", + 1110 : "かざりだな", + 1120 : "背のたかい本だな", + 1130 : "背のひくい本だな", + 1140 : "サンデーチェスト", + 1200 : "サイドテーブル", + 1210 : "小さいテーブル", + 1215 : "小さいテーブル", + 1220 : "コーヒーテーブル", + 1230 : "コーヒーテーブル", + 1240 : "シュノーケルテーブル", + 1250 : "クッキーテーブル", + 1260 : "ベッドルームテーブル", + 1300 : "1000コ貯ビーン箱", + 1310 : "2500コ貯ビーン箱", + 1320 : "5000コ貯ビーン箱", + 1330 : "7500コ貯ビーン箱", + 1340 : "10000コ貯ビーン箱", + 1399 : "電話", + 1400 : "セザンヌ・トゥーンの絵", + 1410 : "お花", + 1420 : "モダン・ミッキー", + 1430 : "レンブラント・トゥーンの絵", + 1440 : "トゥーンスケープ", + 1441 : "ホイッスルホース", + 1442 : "トゥーンスター", + 1443 : "「パイじゃない」", + 1500 : "ラジオ", + 1510 : "ラジオ", + 1520 : "ラジオ", + 1530 : "テレビ", + 1600 : "背のひくい花びん", + 1610 : "背のたかい花びん", + 1620 : "背のひくい花びん", + 1630 : "背のたかい花びん", + 1640 : "花びん", + 1650 : "花びん", + 1660 : "サンゴの花びん", + 1661 : "貝がらの花びん", + 1700 : "ポップコーンカート", + 1710 : "てんとう虫", + 1720 : "ふんすい", + 1725 : "洗濯機", + 1800 : "キンギョばち", + 1810 : "キンギョばち", + 1900 : "メカジキ", + 1910 : "シュモクザメ", + 1920 : "ツノのかべかけ", + 1930 : "シンプル・ソンブレロ", + 1940 : "ファンシー・ソンブレロ", + 1950 : "ドリームキャッチャー", + 1960 : "ひづめ", + 1970 : "バイソンの絵", + 2000 : "キャンディースウィングセット", + 2010 : "ケーキスライド", + 3000 : "バナナスプリットタブ", + 10000 : "丸いかぼちゃ", + 10010 : "細長いかぼちゃ", + } + +# CatalogClothingItem.py--don't translate yet. +ClothingArticleNames = ( + "シャツ", + "シャツ", + "シャツ", + "短パン", + "短パン", + "スカート", + "短パン", + ) + +ClothingTypeNames = { + 1400 : "マシューのシャツ", + 1401 : "ジェシカのシャツ", + 1402 : "マリッサのシャツ", + 1600 : "ワナの洋服", + 1601 : "サウンドの洋服", + 1602 : "おとりの洋服", + 1603 : "ワナの洋服", + 1604 : "サウンドの洋服", + 1605 : "おとりの洋服", + 1606 : "ワナの洋服", + 1607 : "サウンドの洋服", + 1608 : "おとりの洋服", + } + +# CatalogSurfaceItem.py--don't translate yet. +SurfaceNames = ( + "かべがみ", + "いがた", + "床", + "腰板", + "へり", + ) + +WallpaperNames = { + 1000 : "パーチメント", + 1100 : "ミラノ", + 1200 : "ドーバー", + 1300 : "ビクトリア", + 1400 : "ニューポート", + 1500 : "パストラル", + 1600 : "ハーレクイン", + 1700 : "ムーン", + 1800 : "スター", + 1900 : "フラワー", + 2000 : "スプリングガーデン", + 2100 : "フォーマルガーデン", + 2200 : "勝負の日", + 2300 : "タッチダウン!", + 2400 : "くも", + 2500 : "つた", + 2600 : "春", + 2700 : "こけし", + 2800 : "花束", + 2900 : "エンゼルフィッシュ", + 3000 : "バブル", + 3100 : "バブル", + 3200 : "ゴーフィッシュ", + 3300 : "ストップフィッシュ", + 3400 : "たつのおとしご", + 3500 : "貝がら", + 3600 : "深海", + 3700 : "ブーツ", + 3800 : "さぽてん", + 3900 : "カウボーイハット", + 10100 : "ねこ", + 10200 : "こうもり", + 11000 : "雪のけっしょう", + 11100 : "葉っぱ", + 11200 : "雪だるま", + 13000 : "クローバー", + 13100 : "クローバー", + 13200 : "レインボー", + 13300 : "クローバー", + } + +FlooringNames = { + 1000 : "ウッドフロア", + 1010 : "カーペット", + 1020 : "ダイヤモンドタイル", + 1030 : "ダイヤモンドタイル", + 1040 : "しばふ", + 1050 : "茶色いレンガ", + 1060 : "赤いレンガ", + 1070 : "四角いタイル", + 1080 : "石", + 1090 : "遊歩道", + 1100 : "つち", + 1110 : "木のタイル", + 1120 : "タイル", + 1130 : "ハチの巣", + 1140 : "みず", + 1150 : "ビーチタイル", + 1160 : "ビーチタイル", + 1170 : "ビーチタイル", + 1180 : "ビーチタイル", + 1190 : "すな", + 10000 : "アイスキューブ", + 10010 : "イグルー", + 11000 : "クローバー", + 11010 : "クローバー", + } + +MouldingNames = { + 1000 : "節目", + 1010 : "クラシック", + 1020 : "ぼこぼこ", + 1030 : "花がら", + 1040 : "花がら", + 1050 : "てんとうむし", + } + +WainscotingNames = { + 1000 : "ペンキ", + 1010 : "木のパネル", + 1020 : "木", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "おおきな庭", + 20 : "ワイルドな庭", + 30 : "ギリシャ風の庭", + 40 : "都市の風景", + 50 : "ウェスタン", + 60 : "水中", + 70 : "トロピカルアイランド", + 80 : "星空", + 90 : "チキプール", + 100 : "南極", + 110 : "農場", + 120 : "キャンプ", + 130 : "大通り", + } + +# don't translate yet +NewCatalogNotify = "電話で注文できる新しい品物があるわよ!" +NewDeliveryNotify = "キミのメールボックスに荷物が届いているわよ。" +CatalogNotifyFirstCatalog = "最初のカタログが届いたわよ! 楽しいアイテムでおしゃれしたり、家の模様替えができるわよ。" +CatalogNotifyNewCatalog = "カタログVol%sが届いたわよ! 電話で商品を注文してね。" +CatalogNotifyNewCatalogNewDelivery = "注文した商品がメールボックスに 届いたわよ! カタログVOL%sも届いているわよ!" +CatalogNotifyNewDelivery = "注文した商品がメールボックスに届いたわよ!" +CatalogNotifyNewCatalogOldDelivery = "カタログVol%sが届いたわよ!注文した商品がまだメールボックスに残っているわよ!" +CatalogNotifyOldDelivery = "注文した商品がまだメールボックスに残っているわよ!" +CatalogNotifyInstructions = "トゥーンガイドの\"家に帰る\"ボタンを押して、おうちの電話まで行ってね!" +CatalogNewDeliveryButton = "商品が\n届いたよ" +CatalogNewCatalogButton = "新しい\nカタログ" +CatalogSaleItem = "セール中! " + +# don't translate yet +DistributedMailboxEmpty = "いまキミのメールボックスはからっぽだよ。電話注文したあとに、荷物をチェックしにここへ戻ってきて!" +DistributedMailboxWaiting = "いまキミのメールボックスはからっぽだけど、キミの注文した荷物はこちらに向かっているよ。あとでまたチェックしてみて!" +DistributedMailboxReady = "キミの注文したものがとどいたよ!" +DistributedMailboxNotOwner = "ごめん、これはキミのメールボックスじゃないね。" +DistributedPhoneEmpty = "キミとキミの家向けの特別品は、どの電話からでも注文できるよ。あたらしい品物は営業時間外に注文することができるんだ。\n\n今すぐ注文できるものはないので、あとでチェックしにきてね!" + +# don't translate yet +Clarabelle = "クララベル" +MailboxExitButton = "メールボックスを閉じる" +MailboxAcceptButton = "荷物をうけとる" +MailBoxDiscard = "このアイテムをすてる" #localize +MailboxAcceptInvite = "さんかする" +MailBoxRejectInvite = "さんかしない" +MailBoxDiscardVerify = "本当に %s をすててもいい?" +MailboxOneItem = "品物が1つ届いています。" +MailboxNumberOfItems = "品物が%sつ届いています。" +MailboxGettingItem = "%s\nを取り出しました。" +MailboxGiftTag = "%s からのおくりもの" +MailboxGiftTagAnonymous = "とくめい" +MailboxItemNext = "次の\nアイテム" +MailboxItemPrev = "前の\nアイテム" +MailboxDiscard = "すてる" +MailboxLeave = "とっておく" +CatalogCurrency = "ジェリービーン" +CatalogHangUp = "電話を切る" +CatalogNew = "しんせいひん" +CatalogBackorder = "バックオーダー" +CatalogLoyalty = "スペシャル" +CatalogPagePrefix = "ページ" +CatalogGreeting = "お電話ありがとうございます。\nクララベルのショッピングカタログです。ご注文は?" +CatalogGoodbyeList = ["それじゃ!", + "またお電話くださいね!", + "お電話ありがとう!", + "またどうぞ~!", + "ありがとうございました~!", + ] +CatalogHelpText1 = "ページをめくって買いたい商品を見てね。" +CatalogSeriesLabel = "シリーズ%s" +CatalogGiftFor = "ギフトを送る相手:" +CatalogGiftTo = "ギフトを送る相手: %s" +CatalogGiftToggleOn = "ギフトをやめる" +CatalogGiftToggleOff = "ギフトを買う" +CatalogGiftToggleWait = "配達中 ..." +CatalogGiftToggleNoAck = "配達できません" +CatalogPurchaseItemAvailable = "お買いあげありがとう! これはすぐに使うことができるわね。" +CatalogPurchaseGiftItemAvailable = "すばらしい! %sはすぐにこのギフトをつかえそうだね。" +CatalogPurchaseItemOnOrder = "お買いあげありがとうございます!ご注文の商品はキミのメールボックスに届きます!\n\nメールボックスを後でチェックしてみてね。" +CatalogPurchaseGiftItemOnOrder = "かしこまりました! %sへのギフトはうけとり人のメールボックスに配達されます。" +CatalogAnythingElse = "ほかの商品はよろしいですか?" +CatalogPurchaseClosetFull = "キミのクローゼットはいっぱいだね。 この品物を購入してもいいけど、もしかしたらこの品物が到着した時に、クローゼットのスペースにあきをもたせるため、なにかをすてる必要がでてくるよ。\n\nまだこの品物を購入したい?" +CatalogAcceptClosetFull = "キミのクローゼットはいっぱいだね。この品物をメールボックスからとってくる前に、それ用にスペースにあきをもたせるため、クローゼットにはいってなにかを削除しなきゃね。" +CatalogAcceptShirt = "あたらしいシャツを着るよ。今まで着ていたのはキミのクローゼットに入っているよ。" +CatalogAcceptShorts = "あたらしい短パンをはくよ。今まではいてたのはキミのクローゼットに入っているよ。" +CatalogAcceptSkirt = "あたらしいスカートをはくよ。今まではいてたのはキミのクローゼットに入っているよ。" +CatalogAcceptPole = "あたらしい釣ざおでもっと大きな魚を釣りに行こう!" +CatalogAcceptPoleUnneeded = "これよりも良い釣りざおを持っているよ!" +CatalogAcceptChat = "新しいスピードチャットのせりふを手に入れました!" +CatalogAcceptEmote = "新しい“きもち”を手に入れました!" +CatalogAcceptBeans = "ジェリービーンを受け取りました!" +CatalogAcceptRATBeans = "トゥーン・リクルートのごほうびが届きました!" +CatalogAcceptNametag = "Your new name tag has arrived!" +CatalogAcceptGarden = "ガーデニングの道具が届いたよ!" +CatalogAcceptPet = "キミのペットの新しいトリックを受け取りました!" +CatalogPurchaseHouseFull = "おうちの中が荷物でいっぱいよ。この品物を購入してもいいけど、もしかしたらこの品物が到着した時に、おうちのスペースにあきをもたせるため、なにかをすてる必要がでてくるわよ。\n\nまだこの品物を購入したい? " +CatalogAcceptHouseFull = "おうちの中が荷物でいっぱいよ。この品物をメールボックスからとってくる前に、それ用にスペースにあきをもたせるため、おうちの中のなにかを捨てなきゃね。" +CatalogAcceptInAttic = "新しい品物は今キミの屋根裏にあるよ。 中にはいって、\"模様替え\"ボタンをクリックすると、キミの家の中におくことができるよ。" +CatalogAcceptInAtticP = "新しい品物は今キミの屋根裏にあるよ。 中にはいって、\"模様替え\"ボタンをクリックすると、キミの家の中におくことができるよ。" +CatalogPurchaseMailboxFull = "キミのメールボックスはいっぱいだね! 品物をいくつかとりだしてスペースにあきをもたせるまで、この品物を購入することはできないよ。" +CatalogPurchaseGiftMailboxFull = "%sのメールボックスはもういっぱいです!このアイテムは買えません。" +CatalogPurchaseOnOrderListFull = "いま注文している品物が多すぎるよ。すでに注文したものがいくつか届くまで、キミはこれ以上なにも注文することはできないよ。" +CatalogPurchaseGiftOnOrderListFull = "%sは、げんざい注文しすぎです。" +CatalogPurchaseGeneralError = "ゲーム内のエラーにより、この品物は購入できません:エラーコード %s" +CatalogPurchaseGiftGeneralError = "ゲームエラーのため、%(friend)sにこのアイテムをおくれませんでした: エラーコード %(error)s" +CatalogPurchaseGiftNotAGift = "このアイテムは%sにはもったいないのでおくれません。" +CatalogPurchaseGiftWillNotFit = "このアイテムは%sにはにあわないからおくれません。" +CatalogPurchaseGiftLimitReached = "このアイテムはもう持っているのでおくれません。" +CatalogPurchaseGiftNotEnoughMoney = "このアイテムはキミには高すぎて%sにはおくれないよ。" +CatalogAcceptGeneralError = "ゲーム内のエラーにより、この品物はメールボックスから削除することはできません:エラーコード %s" +CatalogAcceptRoomError = "置き場所がたりません。先になにかをすてなきゃネ!" +CatalogAcceptLimitError = "もう持ちきれないよ。先になにかをすてなきゃネ!" +CatalogAcceptFitError = "これはキミのサイズとちがうよ!他のトゥーンにあげよう。" +CatalogAcceptInvalidError = "このアイテムはなんだかイケてないね…。他のトゥーンにあげよう!" + +MailboxOverflowButtonDicard = "すてる" +MailboxOverflowButtonLeave = "そのまま" + +# don't translate yet +HDMoveFurnitureButton = "模様替え" +HDStopMoveFurnitureButton = "移動\n終了" +HDAtticPickerLabel = "屋根裏の中" +HDInRoomPickerLabel = "部屋の中" +HDInTrashPickerLabel = "ゴミ箱の中" +HDDeletePickerLabel = "削除する?" +HDInAtticLabel = "屋根裏" +HDInRoomLabel = "部屋" +HDInTrashLabel = "ゴミ箱" +HDToAtticLabel = "屋根裏に\n置く" +HDMoveLabel = "動かす" +HDRotateCWLabel = "右に回転" +HDRotateCCWLabel = "左に回転" +HDReturnVerify = "このアイテムを屋根裏に戻しますか?" +HDReturnFromTrashVerify = "このアイテムをゴミ箱から屋根裏に戻しますか?" +HDDeleteItem = "OKを押すとこのアイテムをゴミ箱に送るよ\nキャンセルを押すと取っておくよ。" +HDNonDeletableItem = "この種類の品物は削除できないよ!" +HDNonDeletableBank = "キミの銀行は削除できないよ!" +HDNonDeletableCloset = "キミのクローゼットは削除できないよ!" +HDNonDeletablePhone = "キミの電話は削除できないよ!" +HDNonDeletableNotOwner = "キミは%s'sのものを削除できないよ!" +HDHouseFull = "キミのうちが荷物でいっぱいだよ。部屋か屋根裏のアイテムを何か捨ててね。" + +HDHelpDict = { + "DoneMoving" : "部屋の模様替えをやめる", + "Attic" : "屋根裏にあるアイテムを\n表示します。\n部屋にまだ置いていない\nアイテムがあります。", + "Room" : "部屋の中にあるアイテムを表示します。\nさがしものをするのに便利です。", + "Trash" : "ゴミ箱が一杯になったり、\n捨ててから時間が経つと、\n古いアイテムから順番になくなります。", + "ZoomIn" : "部屋を拡大して見る", + "ZoomOut" : "部屋を縮小して見る", + "SendToAttic" : "アイテムを屋根裏にとって置きます。", + "RotateLeft" : "左に回転", + "RotateRight" : "右に回転", + "DeleteEnter" : "「すてるモード」に切り替えます。", + "DeleteExit" : "「すてるモード」を終わります。", + "FurnitureItemPanelDelete" : "%sをゴミ箱に入れる", + "FurnitureItemPanelAttic" : "%sを部屋に置く", + "FurnitureItemPanelRoom" : "%sを屋根裏に戻す。", + "FurnitureItemPanelTrash" : "%sを屋根裏に戻す。", + } + + + +# don't translate yet +MessagePickerTitle = "フレーズがおおすぎるね。 \n\"%s\"\nを購入するには、なにか削除するものを選択しなきゃならないよ。" +MessagePickerCancel = "取り消す" +MessageConfirmDelete = "ほんとうに\"%s\"をキミのスピードチャットメニューから削除してもいいの?" + + +# don't translate yet +CatalogBuyText = "買う" +CatalogRentText = "かりる" +CatalogGiftText = "ギフト" +CatalogOnOrderText = "注文済" +CatalogPurchasedText = "持ってるよ" +CatalogGiftedText = "ギフトが\n届いたよ" +CatalogPurchasedGiftText = "持ってるよ" +CatalogMailboxFull = "もういっぱい" +CatalogNotAGift = "ギフトじゃないよ" +CatalogNoFit = "入りきらないよ" +CatalogMembersOnly = "Members\nOnly!" +CatalogSndOnText = "Snd On" +CatalogSndOffText = "Snd Off" + +CatalogPurchasedMaxText = "これ以上、買えないよ!" +CatalogVerifyPurchase = "ジェリービーン%(price)s個で%(item)sを買いますか?" +CatalogVerifyRent = "%(item)sをジェリービーン%(price)s個でかりますか?" +CatalogVerifyGift = "%(friend)sへのギフトとして%(item)sをジェリービーン%(price)s個で買いますか?" +CatalogOnlyOnePurchase = "この商品は一度にひとつしか持てないんだ。この品物を購入したら、%(old)sはなくなっちゃうんだ。\n\nほんとうにジェリービーン%(price)sの%(item)sを買う?" + +# don't translate yet +CatalogExitButtonText = "電話を切る" +CatalogCurrentButtonText = "今もっている品物へ" +CatalogPastButtonText = "前にもっていた品物へ" + + + +TutorialHQOfficerName = "HQスタッフのハリー" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 20000 : "チュートリアルのトム", + 999 : "トゥーン・テイラー", + 1000 : lToonHQ, + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "ぎんこういんのボブ", + 2003 : "ピートきょうじゅ", + 2004 : "したてやタミー", + 2005 : "としょかんいんのラリー", + 2006 : "てんいんの\nクラーク", + 2011 : "てんいんの\nクララ", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "りょうしのフレディー", + # NPCPetClerks + 2013 : "てんいんの\nポッピー", + 2014 : "てんいんの\nペッピー", + 2015 : "てんいんの\nパッピー", + # NPCPartyPerson + 2016 : "Party Planner Pete", + 2017 : "Party Planner Penny", + + # Silly Street + 2101 : "はいしゃのダニエル", + 2102 : "けいさつかんのシェリー", + 2103 : "ヘクション・キティー", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "カナリア・コールマイン", + 2109 : "バブル・ブローハード", + 2110 : "ビル・ボード", + 2111 : "ダンシング・ディエゴ", + 2112 : "ドクター・トム", + 2113 : "スバラシイローロ", + 2114 : "ロズ・ベリー", + 2115 : "パティ・ペーパーカット", + 2116 : "ブル・マクドナル", + 2117 : "ママ・ズイーナ", + 2118 : "ピエロのドッケ", + 2119 : "ハニー・ハハ", + 2120 : "ビンキーきょうじゅ", + 2121 : "ホッホふじん", + 2122 : "おサルのハリー", + 2123 : "ナンデ・ボトルニスキー", + 2124 : "オヤジモ・コケール", + 2125 : "ナマケルスキイー", + 2126 : "ガハハきょうじゅ", + 2127 : "ウッディ・コゼーニ", + 2128 : "ピエロのピエール", + 2129 : "フランク・フルター", + 2130 : "ジョイ・バズ", + 2131 : "フェザー・コーチョチョ", + 2132 : "クレージー・ドン", + 2133 : "ドクター・ハッピー", + 2134 : "サイレント・シモーヌ", + 2135 : "メアリ", + 2136 : "サル・クスクス", + 2137 : "ハッピー・ヘイキュン", + 2138 : "マルドゥーン", + 2139 : "ポール・ポタポッター", + 2140 : "りょうしのビリー", + + # Loopy Lane + 2201 : "ゆうびんきょくちょうのユーゴ", + 2202 : "シャーリー・ジョーダン", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "ノイ・ワイズメーカー", + 2208 : "ステイッキー・ルー", + 2209 : "チャーリー・ケラリン", + 2210 : "ヒッヒ", + 2211 : "サリー・ピッチャー", + 2212 : "ワーレン・ボトラー", + 2213 : "ルーシー・タイヤ", + 2214 : "シュミット・シミー", + 2215 : "シド・ビンカーン", + 2216 : "ノナ・クリア", + 2217 : "シャーキー・ジョーンズ", + 2218 : "ファニー・ペイジ", + 2219 : "シェフ・ビーダマー", + 2220 : "リック・ガンセキー", + 2221 : "ペルニラ・ペッタラ", + 2222 : "ショーティ・フューズ", + 2223 : "サーシャ・ビリリン", + 2224 : "スモーキー・ジョー", + 2225 : "りょうしのドゥルーピー", + + # Punchline Place + 2301 : "ドクター・ポッキン", + 2302 : "ギグルきょうじゅ", + 2303 : "ナースのナンシー", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "ナンシー・ガス", + 2309 : "ブルース・ブー", + 2311 : "フランツ・クビマガラーン", + 2312 : "ドクター・センシティブ", + 2313 : "シーラ・シミアトン", + 2314 : "ネッド・ナゲット", + 2315 : "グニー・カメナイン", + 2316 : "シンディ・スプリンクル", + 2318 : "トニー・トニン", + 2319 : "ジッピー", + 2320 : "アル・カチーノ", + 2321 : "りょうしのパンチー", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "てんいんの\nウィリー", + 1002 : "てんいんの\nビリー", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "スタン・ステテコ", + # NPCFisherman + 1008 : "りょうしのファーボール", + # NPCPetClerks + 1009 : "てんいんの\nバーキー", + 1010 : "てんいんの\nパー", + 1011 : "てんいんの\nブループ", + # NPCPartyPerson + 1012 : "Party Planner Phil", + 1013 : "Party Planner Patty", + + # Barnacle Blvd. + 1101 : "ビリー・バッド", + 1102 : "キャプテン・カール", + 1103 : "レイ・シャケスーツ", + 1104 : "ドクター・メガリス", + 1105 : "フックていとく", + 1106 : "ノーリーふじん", + 1107 : "カル・プープデック", + 1108 : "HQスタッフ", + 1109 : "HQスタッフ", + 1110 : "HQスタッフ", + 1111 : "HQスタッフ", + 1112 : "ゲリー・ガボガボ", + 1113 : "エノラ・ゲラゲーラ", + 1114 : "チャーリー・チッチャ", + 1115 : "シーラ・イーカン", + 1116 : "ベッシー・シェル", + 1117 : "キャプテン・オット", + 1118 : "チョビー・ヒゲール", + 1121 : "リンダ・ツリーラバー", + 1122 : "スタン・ショッペー", + 1123 : "ビリット・クルール", + 1124 : "フリッピー・スピーディー", + 1125 : "アイリーン・ポロポロン", + 1126 : "りょうしのバーニー", + + # Seaweed Street + 1201 : "バーバラ・シェル", + 1202 : "アート", + 1203 : "エイハブ", + 1204 : "ロッキー・ムッキ", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "ブイきょうじゅ", + 1210 : "ギャング・アルネ", + 1211 : "ウィン・バッグ", + 1212 : "トビー・トングスティンガー", + 1213 : "ダンテ・ドルフィン", + 1214 : "ケイト・ヒュー", + 1215 : "ダイナ・ダウン", + 1216 : "ロッド・リール", + 1217 : "シーシー・ワカメイン", + 1218 : "ティム・カンダイスキー", + 1219 : "ブライアン・ビーチヘッド", + 1220 : "カーラ・スイモン", + 1221 : "ブッキー・マッキー", + 1222 : "ヨー・オーイ", + 1223 : "シド・イーカン", + 1224 : "エミリー・ヌルール", + 1225 : "ボンゾ・ボソール", + 1226 : "モッチ・ホー", + 1227 : "ミス・サンゴ", + 1228 : "りょうしのリード", + + # Lighthouse Lane + 1301 : "アリス", + 1302 : "メルヴィル", + 1303 : "クラッガー", + 1304 : "スヴェトラーナ", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "シーフォーム", + 1310 : "テッド・タックル", + 1311 : "サカ・サマール", + 1312 : "イーサン・コッツ", + 1313 : "アーリー・サンモン", + 1314 : "サビータ・ラルフ", + 1315 : "ドクター・ユラー", + 1316 : "ヒルマ・イルマー", + 1317 : "ポーラ・ストレス", + 1318 : "ダン・ドンダン", + 1319 : "オーライ・ドライ", + 1320 : "タイヘイ・ヨー", + 1321 : "ダイナ・ドッカー", + 1322 : "ブーブー・クッション", + 1323 : "スティンキー・ネッド", + 1324 : "パール・ダイバー", + 1325 : "ネッド・ビーグル", + 1326 : "フェリシア・チップス", + 1327 : "シンディ・チズル", + 1328 : "フレッド・フランダー", + 1329 : "シェリィ・シーウィード", + 1330 : "ポーター・ホール", + 1331 : "ルディ・ラダー", + 1332 : "りょうしのシェーン", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "ベティ・フリーズ", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "てんいんの\nレニー", + 3007 : "てんいんの\nペニー", + 3008 : "ウォーレン・ボタン", + # NPCFisherman + 3009 : "りょうしのフリジー", + # NPCPetClerks + 3010 : "てんいんの\nスキップ", + 3011 : "てんいんの\nディップ", + 3012 : "てんいんの\nキップ", + # NPCPartyPerson + 3013 : "Party Planner Paul", + 3014 : "Party Planner Polly", + + # Walrus Way + 3101 : "ウシおじさん", + 3102 : "フリーズおばさん", + 3103 : "フレッド", + 3104 : "ハッティ", + 3105 : "フロスティ・フレディ", + 3106 : "マモル・トリハダ", + 3107 : "パティ・パスポート", + 3108 : "トボガン・テッド", + 3109 : "ケイト", + 3110 : "チキン・ボーイ", + 3111 : "シンジン", + 3112 : "ミニおじさん", + 3113 : "ヒステリー・ハリー", + 3114 : "ヘンリー・ハザード", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "クリーピー・カール", + 3120 : "マイク・テブクロン", + 3121 : "ジョー・ショック", + 3122 : "ルーシー・リュージュ", + 3123 : "フランク・ロイド・アイス", + 3124 : "ランス・アイスバーグ", + 3125 : "カーネル・クランチ", + 3126 : "コレスト・ローラー", + 3127 : "オチルーラ", + 3128 : "スティッキィ・ジョージ", + 3129 : "パンやのブリジット", + 3130 : "サンディ", + 3131 : "レイジー・ロレンゾ", + 3132 : "Mr.ハイ", + 3133 : "フリーズフレームはかせ", + 3134 : "ラウンジ・ラッサー", + 3135 : "メルティ・ニール", + 3136 : "ハッピー・スー", + 3137 : "Mr.フリーズ", + 3138 : "シェフ・バンブルスープ", + 3139 : "ツララばあちゃん", + 3140 : "りょうしのルシール", + + # Sleet Street + 3201 : "アークティックおばさん", + 3202 : "シェイキー", + 3203 : "ウォルト", + 3204 : "ドクター・アイシィ", + 3205 : "バンピー・ノギン", + 3206 : "セクシー・ヴィダリア", + 3207 : "ドクター・シンキクサーイ", + 3208 : "グランピー・フィル", + 3209 : "ギグル・マギー", + 3210 : "シミアン・サム", + 3211 : "ファニー・フリーズ", + 3212 : "フロスティ・フレッド", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "あせかきピート", + 3218 : "ブルー・ルー", + 3219 : "トム・フロスト", + 3220 : "Mr.ヘックショイ", + 3221 : "ネリー・スノウ", + 3222 : "ミンディ・トーショウ", + 3223 : "チャッピー", + 3224 : "フリーダ・フロストバイト", + 3225 : "ブレイク・アイス", + 3226 : "サンタ・ポーズ", + 3227 : "ソーラー・レイ", + 3228 : "ウィン・チル", + 3229 : "ヘルニア・ベルト", + 3230 : "ハゲのハーゲン", + 3231 : "チョッピ", + 3232 : "りょうしのアルバート", + + # Polar Place + 3301 : "ペイズリー・パッチ", + 3302 : "ビヨン・ボード", + 3303 : "ミエールきょうじゅ", + 3304 : "エディー・イエティ", + 3305 : "マック・ラメイ", + 3306 : "ポーラ・ベアー", + # NPC Fisherman + 3307 : "つりびとのフレドリカ", + 3308 : "ドナルド・フランプ", + 3309 : "ブーツィー", + 3310 : "フレークきょうじゅ", + 3311 : "コニー・フェリス", + 3312 : "マーチ・ハリー", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "キッシー・クリッシー", + 3318 : "ジョニー・カシミア", + 3319 : "サム・ステットソン", + 3320 : "フィジー・リジー", + 3321 : "ポール・ツルハシー", + 3322 : "フルー・ルー", + 3323 : "ダラス・ボレアリス", + 3324 : "じまん屋のストゥー", + 3325 : "グルービー・ガーランド", + 3326 : "ブリーチ", + 3327 : "チャック・ロースト", + 3328 : "シェイディー・サディー", + 3329 : "トレディング・エド", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "ミンディ・マンデー", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "てんいんのド", + 4007 : "てんいんのレ", + 4008 : "したてやハーモニー", + # NPCFisherman + 4009 : "りょうしのファニー", + # NPCPetClerks + 4010 : "てんいんの\nクリス", + 4011 : "てんいんの\nネール", + 4012 : "てんいんの\nウェスティンガール", + # NPCPartyPerson + 4013 : "Party Planner Preston", + 4014 : "Party Planner Penelope", + + # Alto Ave. + 4101 : "トム", + 4102 : "バイオレット", + 4103 : "ドクター・アイタタ", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "クレフ", + 4109 : "カルロス", + 4110 : "メトラ・ノーム", + 4111 : "トム・ハム", + 4112 : "ファー", + 4113 : "マダム・マナーズ", + 4114 : "オフキー・エリック", + 4115 : "バーバラ・セヴィル", + 4116 : "ピッコロ", + 4117 : "マンディ・リン", + 4118 : "アテンダントのエイブ", + 4119 : "モー・ツァールト", + 4120 : "ヴィオラ・フカフーカ", + 4121 : "ジー・マイナー", + 4122 : "ミッコ・ミント", + 4123 : "カミナリ・テッド", + 4124 : "リフラフ", + 4125 : "メロディ・ウェーバー", + 4126 : "メル・カント", + 4127 : "ハッピー・タップ", + 4128 : "スクープ・ルチアーノ", + 4129 : "トゥッツィー・ツーステップ", + 4130 : "メタルマイク", + 4131 : "アブラハム・アーマー", + 4132 : "ジャジー・サリー", + 4133 : "スコット・ポプリン", + 4134 : "ディスコ・デイビッド", + 4135 : "ルーニー・ルンルン", + 4136 : "パティ・ポーズ", + 4137 : "トニー・イヤプラグ", + 4138 : "シェフ・トレモロ", + 4139 : "ハーモニー・スウェル", + 4140 : "ネッド・ブキヨン", + 4141 : "りょうしのジェッド", + + # Baritone Blvd. + 4201 : "ティナ", + 4202 : "バリー", + 4203 : "モックン", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "ヘイディ", + 4209 : "シュマルツ・ワルツ", + 4211 : "カール・コンチェルト", + 4212 : "ハイドンたんてい", + 4213 : "フラン・フォリー", + 4214 : "ティナ・ビート", + 4215 : "ティム・タム", + 4216 : "ガミー・ホイッスル", + 4217 : "ハンサム・アントン", + 4218 : "ウィルマ・ウィンド", + 4219 : "シド・ソナタ", + 4220 : "ミミ・ショパン", + 4221 : "モー・マドリガル", + 4222 : "ソラ・シド", + 4223 : "ペニー・プロンプター", + 4224 : "ジャングル・ジム", + 4225 : "ホーリー・ヒス", + 4226 : "オクタヴィア・オクターブ", + 4227 : "フランチェスカ・ヒソヒッソ", + 4228 : "オーガスト・ウィンド", + 4229 : "ジューン・ルーン", + 4230 : "マシュー・エチュード", + 4231 : "ステフィ・チロル", + 4232 : "ヘドリー・キンコン", + 4233 : "チャーリー・カープ", + 4234 : "リード・ギター", + 4235 : "りょうしのラリー", + + # Tenor Terrace + 4301 : "ユキ", + 4302 : "アンナ", + 4303 : "レオ", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "タバサ", + 4309 : "マーシャル", + 4310 : "メロディ・メロン", + 4311 : "シャンティ・バッハッハ", + 4312 : "マーク・パッサージュ", + 4313 : "ホワイティ・ウィッグ", + 4314 : "ダナ・ダンダー", + 4315 : "カレン・グルック", + 4316 : "シェーン・シューマン", + 4317 : "スタッビィ・パッヘルベル", + 4318 : "ボブ・マーリン", + 4319 : "リンキー・ディンク", + 4320 : "キャミー・コーダ", + 4321 : "ルーク・リュート", + 4322 : "ランディ・リズム", + 4323 : "ハンナ・プチ", + 4324 : "エリィ", + 4325 : "ぎんこういんのブラン", + 4326 : "フラン・フレット", + 4327 : "ワンバ・サンバ", + 4328 : "ワグナー", + 4329 : "テリィ・プロンプター", + 4330 : "クエンティン", + 4331 : "メロウ・コステロ", + 4332 : "ジギー", + 4333 : "ハリー", + 4334 : "フレディ・マーズ", + 4335 : "りょうしのウォルデン", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "てんいんの\nピーチ", + 5006 : "てんいんの\nハーブ", + 5007 : "ボニー・ブロッソム", + # NPCFisherman + 5008 : "りょうしのフローラ", + # NPCPetClerks + 5009 : "てんいんの\nボー・タニー", + 5010 : "てんいんの\nトム・ドー", + 5011 : "てんいんの\nダグ・ウッド", + # NPCPartyPerson + 5012 : "Party Planner Pierce", + 5013 : "Party Planner Peggy", + + # Elm Street + 5101 : "クータ", + 5102 : "スージー", + 5103 : "サッサ", + 5104 : "ランラン", + 5105 : "ジャック", + 5106 : "さんぱつやビョーン", + 5107 : "ゆうびんきょくいんのフェリペ", + 5108 : "おかみのジャネット", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "ドングリン", + 5114 : "フニャリン", + 5115 : "ハニー・メロン", + 5116 : "ビッグ・グリーン", + 5117 : "ペタル", + 5118 : "ポップ・コーン", + 5119 : "バリー・メドレー", + 5120 : "ゴーファー", + 5121 : "ポーラ・ピース", + 5122 : "ジミー・モミジ", + 5123 : "ソー・セキ", + 5124 : "ビアンキ・コスモス", + 5125 : "サンジェイ・スプラッシュ", + 5126 : "マダム・コギク", + 5127 : "ポリーせんせい", + 5128 : "ケイト・ケイトウ", + 5129 : "りょうしのサリー", + + # Maple Street + 5201 : "ジェイク", + 5202 : "シンシア", + 5203 : "リサ", + 5204 : "バート", + 5205 : "ダンディ・タンポポ", + 5206 : "ブル・グリーン", + 5207 : "ソフィ・テッポー", + 5208 : "サマンサ・スペード", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "ビッグ・マヨ", + 5214 : "ガッチィ・リー", + 5215 : "イモーナ・コロリン", + 5216 : "スティンキー・ジム", + 5217 : "フランク・フラワー", + 5218 : "ロッキーじいさん", + 5219 : "ビッグ・タジャーン", + 5220 : "シルキー・レイス", + 5221 : "ピンク・フラミンゴ", + 5222 : "ハッピー・タヌキン", + 5223 : "ウェット・ウィリー", + 5224 : "つるまきおじさん", + 5225 : "ナナ・グリーン", + 5226 : "ピート・モス", + 5227 : "ピーチ・ホップ", + 5228 : "ダグラス・シルバー", + 5229 : "りょうしのリリー", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "クリスタル", + 5306 : "エス・カルゴ", + 5307 : "マッシュ・ルーム", + 5308 : "ブンブン", + 5309 : "ロー・メイン", + 5310 : "パット・パター", + 5311 : "ミスター・ジャッジ", + 5312 : "ビーン・ビーン", + 5313 : "コーチのヨーガ", + 5314 : "ミセス・ウエスタン", + 5315 : "マッドおじさん", + 5316 : "ソファおじさん", + 5317 : "タンテイのメイ", + 5318 : "シーザー", + 5319 : "ローズ", + 5320 : "フリージア", + 5321 : "パインきょうじゅ", + 5322 : "りょうしのローズ", + + # + # Goofy's Speedway + # + + #default area + #kart clerk + 8001 : "グラハム・プリー", # "Graham Pree" + 8002 : "イボナ・レース", # "Ivona Race" + 8003 : "アニータ・カッツ", # "Anita Winn" + 8004 : "フィル・ゴール", # "Phil Errup" + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "スーザン・ムーニャ", + 9002 : "スリーピー・トム", + 9003 : "トビー・アクビー", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "てんいんの\nジル", + 9009 : "てんいんの\nフィル", + 9010 : "よれよれジョニー", + # NPCFisherman + 9011 : "りょうしのフロイト", + # NPCPetClerks + 9012 : "てんいんの\nサラ・スヌーズ", + 9013 : "てんいんの\nキャット・ナップ", + 9014 : "てんいんの\nリンクル", + # NPCPartyPerson + 9015 : "Party Planner Patrick", + 9016 : "Party Planner Pearl", + + # Lullaby Lane + 9101 : "ふとんやトニー", + 9102 : "ビッグ・ママ", + 9103 : "P.J.", + 9104 : "スウィーティ", + 9105 : "アクビはかせ", + 9106 : "マックス・スマイル", + 9107 : "スピカ", + 9108 : "ウィルバー・ウィンク", + 9109 : "ドリーミー・ダフネ", + 9110 : "マティ・タビー", + 9111 : "テティ・テーデン", + 9112 : "ララバイ・ルー", + 9113 : "ジャック・クロック", + 9114 : "スウィート・リップス", + 9115 : "ベイビーフェイス・マクドゥーガル", + 9116 : "スティーブ・スリープ", + 9117 : "アフタ・アワーズ", + 9118 : "スター・ナイト", + 9119 : "ロッコ", + 9120 : "サラ・グースカ", + 9121 : "セレナ・ハッピー", + 9122 : "ハレー・ボッタイ", + 9123 : "テディ・ブレア", + 9124 : "ニーナ・ナイトライト", + 9125 : "ドクター・ウッツラ", + 9126 : "アイ・パチーリ", + 9127 : "タビー・タッカー", + 9128 : "ハーディ・トゥール", + 9129 : "ベルサねえさん", + 9130 : "チャーリー・シーツ", + 9131 : "スーザン・シエスタ", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "りょうしのテーラー", + + # Pajama Place + 9201 : "バーニー", + 9202 : "オービ", + 9203 : "ナット", + 9204 : "クレア", + 9205 : "ゼン・グレン", + 9206 : "スキニー・ジニー", + 9207 : "ジェーン・ドレイン", + 9208 : "ドロジー・デイブ", + 9209 : "ドクター・フロス", + 9210 : "マスター・マイク", + 9211 : "ドーン", + 9212 : "ムーン・ビーム", + 9213 : "ルースター・リック", + 9214 : "ドクター・ブリンキィ", + 9215 : "リップ", + 9216 : "キャット", + 9217 : "ロウフル・リンダ", + 9218 : "ワルツ・マチルダ", + 9219 : "カウンテス", + 9220 : "グランピィ・ゴードン", + 9221 : "ザリ", + 9222 : "カウボーイ・ジョージ", + 9223 : "マーク・ザ・ラーク", + 9224 : "サンディ・サンドマン", + 9225 : "フィジェティ・ブリジェッド", + 9226 : "ウィリアム・テラー", + 9227 : "ベッド・ヘッド・テッド", + 9228 : "ウィスパリング・ウィロー", + 9229 : "ローズ・ペタル", + 9230 : "テックス", + 9231 : "ハリー・ハンモック", + 9232 : "ハニー・ムーン", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "りょうしのジャン", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("トゥーンホール", ""), + 2514 : ("トゥーンタウン バンク", ""), + 2516 : ("トゥーンタウン スクールハウス", ""), + 2518 : ("トゥーンタウン ライブラリー", ""), + 2519 : (lGagShop, ""), + 2520 : (lToonHQ, ""), + 2521 : (lClothingShop, ""), + 2522 : (lPetShop, ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("ニコニコ はいしゃ", ""), + 2602 : ("", ""), + 2603 : ("カナリア さいくつ屋", ""), + 2604 : ("ブーブー ドライ クリーニング", ""), + 2605 : ("トゥーンタウン カンバン工房", ""), + 2606 : ("", ""), + 2607 : ("踊る大まめ屋", ""), + 2610 : ("ドクター トム フォーリー", ""), + 2611 : ("", ""), + 2616 : ("ヘンチク チクチク へんそう ショップ", ""), + 2617 : ("おまぬけ スタント", ""), + 2618 : ("笑われ ダンス スタジオ", ""), + 2621 : ("空とぶ 紙ひこうき屋", ""), + 2624 : ("ハッピー フーリガンの お店", ""), + 2625 : ("まさかの パイショップ", ""), + 2626 : ("ドッケの ジョーク リペア", ""), + 2629 : ("ワハハ プレイス", ""), + 2632 : ("ピエロ スクール", ""), + 2633 : ("ヒッヒの ティーショップ", ""), + 2638 : ("トゥーンタウン プレイハウス", ""), + 2639 : ("モンキー トリック ショップ", ""), + 2643 : ("ボトルニスキーの カンヅメ屋", ""), + 2644 : ("笑えない ジョーク ショップ", ""), + 2649 : ("スーパー ゲームショップ", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("ワハハ きょうしつ", ""), + 2655 : ("ファニー マニ バンク", ""), + 2656 : ("中古ピエロ車 はんばい", ""), + 2657 : ("フランクの ジョークショップ", ""), + 2659 : ("有名な ジョイ バズの店", ""), + 2660 : ("くすぐり マシーンズ", ""), + 2661 : ("クレージー ダフィー", ""), + 2662 : ("ドクター ハッピー しんりょうじょ", ""), + 2663 : ("トゥーンタウン シネラマ", ""), + 2664 : ("マイム マイム", ""), + 2665 : ("メアリーのワールド トラベル", ""), + 2666 : ("ケラケラ ガス ステーション", ""), + 2667 : ("ハッピー タイムズ", ""), + 2669 : ("マルドゥーン バルーン", ""), + 2670 : ("スープ・フォーク", ""), + 2671 : ("", ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("ムービー マルチプレックス", ""), + 2705 : ("ワイズエイカーの ノイズメーカー", ""), + 2708 : ("ブルー グルー", ""), + 2711 : ("トゥーンタウン ゆうびんきょく", ""), + 2712 : ("ワハハハハ カフェ", ""), + 2713 : ("スマイルアワー カフェ", ""), + 2714 : ("クーキー シネプレックス", ""), + 2716 : ("スープ アンド クランクアップ", ""), + 2717 : ("ボトル & カンショップ", ""), + 2720 : ("クランクアップ じどうしゃ しゅうり", ""), + 2725 : ("", ""), + 2727 : ("セルツァー ヘブンの びんショップ", ""), + 2728 : ("とうめい クリームショップ", ""), + 2729 : ("14金魚 ショップ", ""), + 2730 : ("ワクワク ニュース", ""), + 2731 : ("", ""), + 2732 : ("グーフボール スパゲッティ", ""), + 2733 : ("ヘビー級 タコショップ", ""), + 2734 : ("キュウバン & ソーサー", ""), + 2735 : ("ザ うちあげ ショップ", ""), + 2739 : ("チョットビリット しゅうりてん", ""), + 2740 : ("中古 バクチク屋", ""), + 2741 : ("", ""), + 2742 : ("", ""), + 2743 : ("ジャズ ドライ クリーニング", ""), + 2744 : ("", ""), + 2747 : ("シュミシミ インク", ""), + 2748 : ("シャーリーの ギャグショップ", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("ブーブー クッション ソファ", ""), + 2802 : ("プー パチン ボールショップ", ""), + 2803 : ("カーニバル キッド", ""), + 2804 : ("ポキポキ カイロ プラクティック", ""), + 2805 : ("", ""), + 2809 : ("ザ パンチライン ジム", ""), + 2814 : ("トゥーンタウン シアター", ""), + 2818 : ("ザ フライング パイ", ""), + 2821 : ("", ""), + 2822 : ("ゴム・チキン サンドイッチ", ""), + 2823 : ("ユースクリーム アイスクリーム", ""), + 2824 : ("パンチライン ムービー パレス", ""), + 2829 : ("いかさま屋", ""), + 2830 : ("ハッピー ジッピー ショップ", ""), + 2831 : ("ギグルの ヒヒヒハウス", ""), + 2832 : ("", ""), + 2833 : ("", ""), + 2834 : ("ケラケラ きゅうきゅう びょういん", ""), + 2836 : ("", ""), + 2837 : ("ハディハッハ セミナー", ""), + 2839 : ("パスパスパスタ", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : (lGagShop, ""), + 1507 : (lToonHQ, ""), + 1508 : (lClothingShop, ""), + 1510 : (lPetShop, ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("中古 ライフセーバー ショップ", ""), + 1604 : ("ウェット スーツ ドライ クリーニング", ""), + 1606 : ("フックの 時計しゅうり店", ""), + 1608 : ("ゲラゲラ ボートグッズ", ""), + 1609 : ("マメつりえさ ショップ", ""), + 1612 : ("ダイム&デック バンク", ""), + 1613 : ("イーカン ほうりつ じむしょ", ""), + 1614 : ("マキガイ ネイルサロン", ""), + 1615 : ("オットの ヨットショップ", ""), + 1616 : ("チョビヒゲ ビューティー サロン", ""), + 1617 : ("メガリスの めがねショップ", ""), + 1619 : ("木の おいしゃさん", ""), + 1620 : ("あさから ばんまで", ""), + 1621 : ("ポート デッキ ジム", ""), + 1622 : ("アメとムチ でんき店", ""), + 1624 : ("クイックリペア サービス", ""), + 1626 : ("シャケ チャント れいふく屋", ""), + 1627 : ("バーゲン ビン バーン", ""), + 1628 : ("センリツ!? ピアノちょうきょうし", ""), + 1629 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("ブイと カモメの かんごスクール", ""), + 1703 : ("テツナベ クイジーン", ""), + 1705 : ("ヨットを 売るよっと ショップ", ""), + 1706 : ("クラクラ クラゲショップ", ""), + 1707 : ("ザブント ギフト ショップ", ""), + 1709 : ("ジェットカイト ショップ", ""), + 1710 : ("バーバラの バーゲン ショップ", ""), + 1711 : ("ディープ シー ダイナー", ""), + 1712 : ("ムキムキ ジム", ""), + 1713 : ("アートの スマート チャート マート", ""), + 1714 : ("くるくる ホテル", ""), + 1716 : ("マーメイド スイムウェア", ""), + 1717 : ("ヒロク カンガエヨウ 屋", ""), + 1718 : ("ガッチャン タクシー", ""), + 1719 : ("ダック バック ウォーター社", ""), + 1720 : ("リール ディール ショップ", ""), + 1721 : ("海のなんでも屋", ""), + 1723 : ("イーカンの イカリ屋", ""), + 1724 : ("にゅるにゅる ウナギ モーレイ", ""), + 1725 : ("プレハブ シークラブ センター", ""), + 1726 : ("ソーダ フロート ショップ", ""), + 1727 : ("オール オア ナッシング", ""), + 1728 : ("これでいい カニ屋", ""), + 1729 : ("", ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("海の もくず屋", ""), + 1804 : ("ショーナン ビーチ ジム", ""), + 1805 : ("タックル 宅配ランチ", ""), + 1806 : ("ピグミー ハット ストア", ""), + 1807 : ("リュウ骨 コッツ", ""), + 1808 : ("スピード ノット", ""), + 1809 : ("サビータ バケツ店", ""), + 1810 : ("イカリ カンリじむしょ", ""), + 1811 : ("カヌー しようよ?", ""), + 1813 : ("サンバシ サンザン カウンセリング", ""), + 1814 : ("あっち 向いて ホイショップ", ""), + 1815 : ("どうした ドック?", ""), + 1818 : ("セブン シー カフェ", ""), + 1819 : ("ドッカーズ ダイナー", ""), + 1820 : ("イタズラ ギア ショップ", ""), + 1821 : ("ネプトゥーン カンヅメ工房", ""), + 1823 : ("ハマグリ ダイナー", ""), + 1824 : ("イヌカキ屋", ""), + 1825 : ("めでタイ さかな市場", ""), + 1826 : ("クラッガーの クレバー クロビス クローゼット", ""), + 1828 : ("アリスの 砂のお城", ""), + 1829 : ("かもめの ちょうこく屋", ""), + 1830 : ("おとしもの かいしゅう所", ""), + 1831 : ("海の メイドさん", ""), + 1832 : ("がっしり がんがん マート", ""), + 1833 : ("カッチリ テーラー", ""), + 1834 : ("ルディーの おもしろ ショップ", ""), + 1835 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : (lGagShop, ""), + 4504 : (lToonHQ, ""), + 4506 : (lClothingShop, ""), + 4508 : (lPetShop, ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("トムトムの ドラム", ""), + 4604 : ("カッチコッチ タイム", ""), + 4605 : ("バイオレットの バイオリン店", ""), + 4606 : ("カーサ デ カスタネット", ""), + 4607 : ("パラレル アパレル", ""), + 4609 : ("ドレミ ピアノ ギャラリー", ""), + 4610 : ("キチント教室", ""), + 4611 : ("ジャスト ちょうりつ師", ""), + 4612 : ("アイタタ 歯科", ""), + 4614 : ("ようきな ひげそり びようしつ", ""), + 4615 : ("ピッコロの ピザパーラー", ""), + 4617 : ("ハッピー マンドリン", ""), + 4618 : ("レストルーム", ""), + 4619 : ("スコア! スポーツ グッズ店", ""), + 4622 : ("すやすや マクラ", ""), + 4623 : ("フラット シャープ", ""), + 4625 : ("ミッコの はみがきこ", ""), + 4626 : ("スラスラ本舗", ""), + 4628 : ("うっかり 保険", ""), + 4629 : ("リフラフ 紙コップ屋", ""), + 4630 : ("フォルテ ミュージック", ""), + 4631 : ("ポエム ブックショップ", ""), + 4632 : ("チクタク クロック ショップ", ""), + 4635 : ("テノール しんぶんしゃ", ""), + 4637 : ("ピッタンコ あなたサイズ テーラー", ""), + 4638 : ("ハードロック ショップ", ""), + 4639 : ("ニュー アンティーク屋", ""), + 4641 : ("ブルース ニュース", ""), + 4642 : ("ラグタイム クリーニング ショップ", ""), + 4645 : ("クラブ88", ""), + 4646 : ("", ""), + 4648 : ("ルンタッタ ひっこし屋", ""), + 4649 : ("", ""), + 4652 : ("フルストップ ショップ", ""), + 4653 : ("", ""), + 4654 : ("われがね 屋根がわら店", ""), + 4655 : ("トレモロ クッキング スクール", ""), + 4656 : ("", ""), + 4657 : ("いっぱつ さんぱつ屋", ""), + 4658 : ("ガタゴト ピアノ店", ""), + 4659 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("シュワルツ ワルツの ダンススクール", ""), + 4702 : ("モックンの 木材店", ""), + 4703 : ("コンチェルト トラベルショップ", ""), + 4704 : ("コンチェルティーナ コンサート 紹介サービス", ""), + 4705 : ("おいどん ハイドン たんてい社", ""), + 4707 : ("ドップラー効果 スタジオ", ""), + 4709 : ("アップビート 登山グッズ", ""), + 4710 : ("スローテンポ ドライビング スクール", ""), + 4712 : ("パンクパンク タイヤしゅうり店", ""), + 4713 : ("シャープな メンズ ファッション", ""), + 4716 : ("吹いてみるか ハーモニカ店", ""), + 4717 : ("ソナタ そんがい保険", ""), + 4718 : ("ショパン 食パン 工房", ""), + 4719 : ("マドリガル トレーラー ディーラー", ""), + 4720 : ("ドレミファ がっき店", ""), + 4722 : ("ぜんそう曲 代行屋", ""), + 4723 : ("プレイグラウンド ようひん店", ""), + 4724 : ("よいこの 学生服", ""), + 4725 : ("バリトン ヘアサロン", ""), + 4727 : ("オクタヴィアの ミュージック ショップ", ""), + 4728 : ("ソロで歌って", ""), + 4729 : ("オカリナ 書店", ""), + 4730 : ("ブレーメンの おんがく屋", ""), + 4731 : ("トゥーン チューンズ", ""), + 4732 : ("劇団エチュード シェイクス ピアリアン シアター", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("チロリアン アコーディオン", ""), + 4736 : ("キンコンカン ウェディング", ""), + 4737 : ("ハープ タープ", ""), + 4738 : ("ロックンローリー ギフトショップ", ""), + 4739 : ("", ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("マーシャルの パンケーキ ショップ", ""), + 4803 : ("メロディ メイドさん サービス", ""), + 4804 : ("バッハッハ バーテンダー スクール", ""), + 4807 : ("マッサージの パッサージュ", ""), + 4809 : ("バロック ほんやく サービス", ""), + 4812 : ("", ""), + 4817 : ("トランペット ペットショップ", ""), + 4819 : ("ユキの ウクレレ ショップ", ""), + 4820 : ("", ""), + 4821 : ("アンナの 旅行代理店", ""), + 4827 : ("グルック クロック", ""), + 4828 : ("シューマンの シューズ ショップ", ""), + 4829 : ("パッヘルベル キャノン ボール店", ""), + 4835 : ("タバサの マウスピース店", ""), + 4836 : ("レゲエ レガリア", ""), + 4838 : ("シャコンヌ 音楽学校", ""), + 4840 : ("コーダコーラ ショップ", ""), + 4841 : ("ライアー うそ発見 サービス", ""), + 4842 : ("シンコペー ション コーポレー ション", ""), + 4843 : ("", ""), + 4844 : ("バイクショップ ピアニッシモ", ""), + 4845 : ("エリーの エレガント エレジー", ""), + 4848 : ("シルバー ハーブ 銀行", ""), + 4849 : ("", ""), + 4850 : ("Gマイナー セブン質屋", ""), + 4852 : ("サンバ セーター ショップ", ""), + 4853 : ("レオの フェンダー ショップ", ""), + 4854 : ("ワーグナーの ビデオショップ", ""), + 4855 : ("エレジー TV ネットワーク", ""), + 4856 : ("", ""), + 4862 : ("クエンティンの クアドリーユ", ""), + 4867 : ("コステロの チェロ工房", ""), + 4868 : ("", ""), + 4870 : ("ジギーズ ジグショップ", ""), + 4871 : ("ハーモニー ハンバーガー", ""), + 4872 : ("フレディーの ギターショップ", ""), + 4873 : ("", ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : (lGagShop, ""), + 5502 : (lToonHQ, ""), + 5503 : (lClothingShop, ""), + 5505 : (lPetShop, ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("どんぐりまなこ メガネ店", ""), + 5602 : ("クタクタ ネクタイ店", ""), + 5603 : ("もりもり サラダバー", ""), + 5604 : ("メロメロン ブライダル", ""), + 5605 : ("みどりの おやゆび 家具店", ""), + 5606 : ("フラワー ペダル", ""), + 5607 : ("チューリップ ポストオフィス", ""), + 5608 : ("ポップコーン ショップ", ""), + 5609 : ("落ち葉 アンティーク店", ""), + 5610 : ("黒目スージーのボクシングジム事務所", ""), + 5611 : ("ゴーファーギャグ", ""), + 5613 : ("高枝バサミ さんぱつ屋", ""), + 5615 : ("アラエッサッサ 鳥のエサ店", ""), + 5616 : ("つゆくさ ホテル", ""), + 5617 : ("ランランの ちょうちょう ショップ", ""), + 5618 : ("グリーンピース 専門店", ""), + 5619 : ("ジャックの豆の木店", ""), + 5620 : ("もみじ ホテル", ""), + 5621 : ("わが輩は エコショップ", ""), + 5622 : ("くるくる 自転車店", ""), + 5623 : ("ことりの おふろやさん", ""), + 5624 : ("マダムに おまかせ", ""), + 5625 : ("ハチノコ ほいくえん", ""), + 5626 : ("ケイトウ けいと店", ""), + 5627 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("グリーン サラダバー", ""), + 5702 : ("ジェイクの くまで店", ""), + 5703 : ("クスノキ カメラショップ", ""), + 5704 : ("リサ・レモンの中古車店", ""), + 5705 : ("だいこく 家具店", ""), + 5706 : ("ジュエリー コロリン", ""), + 5707 : ("ミュージカル フルーツ レストラン", ""), + 5708 : ("フラフラ フラワーショップ", ""), + 5709 : ("おじいさんの 芝刈り サービス", ""), + 5710 : ("ジャングル ジム", ""), + 5711 : ("シルキー ストッキング ショップ", ""), + 5712 : ("ストーン アニマル", ""), + 5713 : ("ブンブック ちゃがま ブックショップ", ""), + 5714 : ("スプリング レイン ペットボトル", ""), + 5715 : ("はなしのタネ新聞", ""), + 5716 : ("ななくさ 質屋", ""), + 5717 : ("テッポウギクの 水でっぽう", ""), + 5718 : ("森の ペット屋さん", ""), + 5719 : ("根ほり葉ほり タンテイ事務所", ""), + 5720 : ("メンズ ファッション あおむし", ""), + 5721 : ("みちくさ食堂", ""), + 5725 : ("ホップス カフェ", ""), + 5726 : ("バートの ダート", ""), + 5727 : ("シルバーグラス バンク", ""), + 5728 : ("", ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("どびん 花びん ちょびんショップ", ""), + 5805 : ("でんでん はいたつ屋", ""), + 5809 : ("きのこ のこのこ スクール", ""), + 5810 : ("はちみつドロップ ショップ", ""), + 5811 : ("レタス ホテル", ""), + 5815 : ("グリーン パットゴルフ", ""), + 5817 : ("白黒 はっきり屋", ""), + 5819 : ("グリーン ビーン ジーンズ", ""), + 5821 : ("スカッシュ& ストレッチ ジム", ""), + 5826 : ("カントリー ファーム ショップ", ""), + 5827 : ("どろんこ ディスカウント", ""), + 5828 : ("カウチ・ポテト インテリア", ""), + 5830 : ("とりこぼし タンテイ ジムショ", ""), + 5833 : ("ザ サラダバー", ""), + 5835 : ("フラワーベッド ブレックファスト", ""), + 5836 : ("エイプリル シャワーショップ", ""), + 5837 : ("ぼんさい スクール", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("ララバイ ライブラリー", ""), + 9503 : ("バー こもりうた", ""), + 9504 : (lGagShop, ""), + 9505 : (lToonHQ, ""), + 9506 : (lClothingShop, ""), + 9508 : (lPetShop, ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("ほしぞらホテル", ""), + 9602 : ("ウィンク まばたき ショップ", ""), + 9604 : ("ふかふかふとん店", ""), + 9605 : ("ララバイ ストリート 323番地", ""), + 9607 : ("ビッグママの ジャマイカ パジャマ", ""), + 9608 : ("ペットショップ またたび", ""), + 9609 : ("正しいスイミン教室", ""), + 9613 : ("クロック クリーナー", ""), + 9616 : ("テーデン家電店", ""), + 9617 : ("ララバイ ストリート 212番地", ""), + 9619 : ("にこにこ リラクゼーション センター", ""), + 9620 : ("いねむり タクシー サービス", ""), + 9622 : ("チクタク トケイ店", ""), + 9625 : ("スイートドリーム ビューティー パーラー", ""), + 9626 : ("ララバイ ストリート 818番地", ""), + 9627 : ("スリーピー ハウス", ""), + 9628 : ("ダレンダー カレンダー ショップ", ""), + 9629 : ("ララバイ ストリート 310番地", ""), + 9630 : ("うたたね サイクツ屋", ""), + 9631 : ("グースカ トケイ しゅうり店", ""), + 9633 : ("ドリームランド 上映室", ""), + 9634 : ("ジュクスイ マットレス", ""), + 9636 : ("フミン症保険", ""), + 9639 : ("トウミンの ススメ", ""), + 9640 : ("ララバイ ストリート 805番地", ""), + 9642 : ("ぐっすり ベッドギャラリー", ""), + 9643 : ("うつらうつら メガネ店", ""), + 9644 : ("まくら投げ ショップ", ""), + 9645 : ("ぬくぬく ホテル", ""), + 9647 : ("ベッドポスト 工具店", ""), + 9649 : ("いびき ねびき ベッド", ""), + 9650 : ("ララバイ ストリート 714番地", ""), + 9651 : ("うたたね いびき 研究所", ""), + 9652 : ("", ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("ユメの旅行代理店", ""), + 9704 : ("夜のふくろう ペットショップ", ""), + 9705 : ("いねむり 自動車修理工場", ""), + 9706 : ("歯の妖精 デンタルクリニック", ""), + 9707 : ("夜明けのあくび ガーデンセンター", ""), + 9708 : ("バラのベットの フラワーショップ", ""), + 9709 : ("夢見がちパイプ屋", ""), + 9710 : ("レムすいみん眼科", ""), + 9711 : ("モーニングコール カンパニー", ""), + 9712 : ("ヒツジ数えます屋", ""), + 9713 : ("うとうと弁護士事務所", ""), + 9714 : ("ドリームボート マリーンショップ", ""), + 9715 : ("第一ねんね タオル銀行", ""), + 9716 : ("どっちらけパーティー 企画会社", ""), + 9717 : ("いねむりパン屋のドーナツ", ""), + 9718 : ("ねむりの精の サンドイッチ屋", ""), + 9719 : ("アルマジロまくら店", ""), + 9720 : ("ねごと発声教室", ""), + 9721 : ("ぬくぬく じゅうたん販売店", ""), + 9722 : ("寝ぼけタレント事務所", ""), + 9725 : ("猫の高級 パジャマ屋", ""), + 9727 : ("いびき帽子屋", ""), + 9736 : ("ドリームエージェンシー", ""), + 9737 : ("ワルツ・マチルダ ダンススクール", ""), + 9738 : ("いびきの館", ""), + 9740 : ("おやすみ フェンシングスクール", ""), + 9741 : ("ベッドの虫 くじょサービス", ""), + 9744 : ("三年寝太郎の しわのばしクリーム", ""), + 9752 : ("夜通し ガス会社", ""), + 9753 : ("月明かり アイスクリーム", ""), + 9754 : ("眠らない乗馬場", ""), + 9755 : ("ベッドかざりと ほうきの映画館", ""), + 9756 : ("", ""), + 9759 : ("眠れる美女パーラー", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : (lGagShop, ""), + 3508 : (lToonHQ, ""), + 3509 : (lClothingShop, ""), + 3511 : (lPetShop, ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("ノーザン ライト エレクトリックス", ""), + 3602 : ("北のご婦人 ぼうし屋", ""), + 3605 : ("", ""), + 3607 : ("ブリザード ウィザード", ""), + 3608 : ("ルージュ リュージュ", ""), + 3610 : ("ぬくぬく ムクルク ショップ", ""), + 3611 : ("うしおじさんの 雪かき屋", ""), + 3612 : ("イグルー デザイン ファクトリー", ""), + 3613 : ("アイシクル バイシクル", ""), + 3614 : ("シャキシャキ スノウ フレーク シリアル社", ""), + 3615 : ("さくさく しゃけ焼き亭", ""), + 3617 : ("冷気球 カンパニー", ""), + 3618 : ("イカリも トウケツ! コンサルティング", ""), + 3620 : ("スキー クリニック", ""), + 3621 : ("どろどろ アイスクリーム バー", ""), + 3622 : ("", ""), + 3623 : ("カチコチ パン屋", ""), + 3624 : ("コールド サンドウィッチ ショップ", ""), + 3625 : ("フリーズ おばさんの ラジエータ屋", ""), + 3627 : ("セントバーナード ケンネル", ""), + 3629 : ("ピースープ カフェ", ""), + 3630 : ("オーロラ トラベル エージェンシー", ""), + 3634 : ("安楽リフト", ""), + 3635 : ("中古のマキ屋", ""), + 3636 : ("ゾクゾク トリハダ屋", ""), + 3637 : ("ケイトの スケート ショップ", ""), + 3638 : ("トボガン ソリ ショップ", ""), + 3641 : ("フレッドの スレッド ベッド", ""), + 3642 : ("タイフウの メガネ ショップ", ""), + 3643 : ("スノウボール ホール", ""), + 3644 : ("とろける アイスキューブ", ""), + 3647 : ("ザ ペンギン タキシード ショップ", ""), + 3648 : ("インスタント アイスキューブ", ""), + 3649 : ("ブルブル バーガー ショップ", ""), + 3650 : ("アンチフリーズ アンティーク", ""), + 3651 : ("フローズン ホットドッグ", ""), + 3653 : ("アイスハウス ジュエリー", ""), + 3654 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("ウィンター ストレージ", ""), + 3703 : ("", ""), + 3705 : ("ツララ スティック ショップ", ""), + 3706 : ("シェイク シェイク! ショップ", ""), + 3707 : ("あったか ホーム 家具店", ""), + 3708 : ("プルート プレイス", ""), + 3710 : ("ヒョウテンカ ダイナー", ""), + 3711 : ("", ""), + 3712 : ("トウケツも おまかせ 配管工", ""), + 3713 : ("ガタガタ 歯科", ""), + 3715 : ("アークティック おばさんの スープショップ", ""), + 3716 : ("ゆきどけ パウダー ショップ", ""), + 3717 : ("ジュノー セミナー", ""), + 3718 : ("インナーチューブ ギャラリー", ""), + 3719 : ("ナイス アイスキューブ", ""), + 3721 : ("トボガン バーゲン ショップ", ""), + 3722 : ("ユキうさぎ スキーショップ", ""), + 3723 : ("スノウ グローブ屋", ""), + 3724 : ("ガチガチ クロニクル", ""), + 3725 : ("それのれ ソリ屋", ""), + 3726 : ("ソーラー パワー ブランケット", ""), + 3728 : ("雪かき 工房", ""), + 3729 : ("", ""), + 3730 : ("雪だるま 質屋", ""), + 3731 : ("ポータブル だんろ工房", ""), + 3732 : ("ザ フローズン ノーズ", ""), + 3734 : ("冷たいシセン 眼科", ""), + 3735 : ("アイス&キャップ", ""), + 3736 : ("キュートな アイスキューブ ショップ", ""), + 3737 : ("ダウンヒル ダイナー", ""), + 3738 : ("イマノウチ だんぼう屋", ""), + 3739 : ("", ""), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : ("トゥーンHQ", ""), + 3806 : ("てぐすねカフェテリア", ""), + 3807 : ("中古かげ絵店", ""), + 3808 : ("セーター・ロッジ", ""), + 3809 : ("みたみた!話し相手サービス", ""), + 3810 : ("ふわふわキルト", ""), + 3811 : ("雪の天使商会", ""), + 3812 : ("ネコ用てぶくろ屋", ""), + 3813 : ("なぜかはきたい雪ぐつ屋", ""), + 3814 : ("リジーのソーダ・ショップ", ""), + 3815 : ("シャレのかつら店", ""), + 3816 : ("キッスィーの雪かざり", ""), + 3817 : ("雪のワンダーランド旅行社", ""), + 3818 : ("物置きかいたい屋", ""), + 3819 : ("エントツそうじ屋さん", ""), + 3820 : ("『白雪』ひょうはく店", ""), + 3821 : ("とうみんツアー", ""), + 3823 : ("大急ぎファンデーション", ""), + 3824 : ("直火やきぐり屋", ""), + 3825 : ("イケてるぼうし店", ""), + 3826 : ("げきおもクツ店", ""), + 3827 : ("歌うはなわ商会", ""), + 3828 : ("雪おとこカフェ", ""), + 3829 : ("コニーのマツボックリ店", ""), + 3830 : ("じきに見えるくもりどめ店", ""), + } + +# translate +# DistributedCloset.py +ClosetTimeoutMessage = "ごめん、\n時間切れだ!" +ClosetNotOwnerMessage = "キミのクローゼットじゃないけど、 ようふくを試着できるよ。" +ClosetPopupOK = lOK +ClosetPopupCancel = "取り消し" +ClosetDiscardButton = "すてる" +ClosetAreYouSureMessage = "何枚かようふくをすてたね。ほんとにすてていいの?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "ほんとにほんとに%sをすてていいの?" +ClosetShirt = "このシャツ" +ClosetShorts = "この短パン" +ClosetSkirt = "このスカート" +ClosetDeleteShirt = "シャツを\nすてる" +ClosetDeleteShorts = "ズボンを\nすてる" +ClosetDeleteSkirt = "ボトムを\nすてる" + +# EstateLoader.py +EstateOwnerLeftMessage = "ごめん、おうちの持ち主がいなくなっちゃった。 キミは%s秒以内にプレイグランドにワープするよ。" +EstatePopupOK = lOK +EstateTeleportFailed = "家へ帰れない?\nもう一度やってみて!" +EstateTeleportFailedNotFriends = "%sはキミの知らないトゥーンの家にいるよ。" + +# DistributedTarget.py +EstateTargetGameStart = "トゥーンアップ ターゲットゲーム、スタート!" +EstateTargetGameInst = "赤いまとにたくさん当てるとトゥーンアップできるよ。" +EstateTargetGameEnd = "トゥーンアップ ターゲットゲーム、おしまい..." + +# DistributedHouse.py +AvatarsHouse = "%s\nおうち" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "ごめん、これはキミの貯ビーン箱じゃないんだ。" +DistributedBankNotOwner = "ごめん、これはキミの貯ビーン箱じゃないんだ。" + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = lOK +FishTankValue = "やぁ、%(name)s!\n%(num)s匹の魚がいるね。ジェリービーン%(value)s個分だけど。\nもしよかったら魚を全部、買い取ろうか?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "ぜんぶうる" +FlowerBasketValue = "やぁ%(name)s、キミのバスケットにはジェリービーン%(value)sコ分の花が%(num)s本入ってるね。全部売っててくれるかい?" + + +def GetPossesive(name): + if name[-1:] == 's': + possesive = name + "" + else: + possesive = name + "の" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('いつもくうふく', 'しばしばくうふく', + 'ときどきくうふく', 'まれにくうふく',), + 'boredomThreshold': ('いつもたいくつ', 'しばしばたいくつ', + 'ときどきたいくつ', 'まれにたいくつ',), + 'angerThreshold': ('いつもふきげん', 'しばしばふきげん', + 'ときどきふきげん', 'ときどきふきげん'), + 'forgetfulness': ('いつもわすれる', 'しばしばわすれる', + 'ときどきわすれる', 'まれにわすれる',), + 'excitementThreshold': ('とてもうきうき', 'まあまあれいせい', + 'ちょっとうきうき', 'とってもうきうき',), + 'sadnessThreshold': ('とってもかなしい', 'しばしばかなしい', + 'ときどきかなしい', 'まれにかなしい',), + 'restlessnessThreshold': ('いつもおちつかない', 'しばしばおちつかない', + 'ときどきおちつかない', 'おちついている',), + 'playfulnessThreshold': ('ほとんどはしゃがない', 'ときどきはしゃぐ', + 'しばしばはしゃぐ', 'いつもはしゃぐ',), + 'lonelinessThreshold': ('いつもさびしい', 'しばしばさびしい', + 'ときどきさびしい', 'さびしくない',), + 'fatigueThreshold': ('いつもつかれた', 'しばしばつかれた', + 'ときどきつかれた', 'まれにつかれた',), + 'confusionThreshold': ('いつもこんらん', 'しばしばこんらん', + 'ときどきこんらん', 'まれにこんらん',), + 'surpriseThreshold': ('いつもびっくり', 'しばしばびっくり', + 'ときどきびっくり', 'まれにびっくり',), + 'affectionThreshold': ('まれにラブラブ', 'ときどきラブラブ', + 'しばしばラブラブ', 'いつもラブラブ',), + } + + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+":\"PageUp\"キーを押すと、よく見えるよ。" + +FireworksJuly4Beginning = "トゥーンHQ:夏の花火大会へようこそ!楽しんでいってね!" +FireworksJuly4Ending = "トゥーンHQ:花火楽しんでくれたかな?すてきな夏をすごしてね!" +FireworksFebruary14Beginning = lToonHQ+"" +FireworksFebruary14Ending = lToonHQ+"" +FireworksJuly14Beginning = lToonHQ+"" +FireworksJuly14Ending = lToonHQ+"" +FireworksOctober31Beginning = lToonHQ+"" +FireworksOctober31Ending = lToonHQ+"" +FireworksNewYearsEveBeginning = lToonHQ+":冬の花火大会へようこそ!" +FireworksNewYearsEveEnding = lToonHQ+":花火楽しかった?2009年もハッピー・トゥーンでゆこう!" +FireworksBeginning = "トゥーンHQ:夏の花火へようこそ!楽しんでいってね!" +FireworksEnding = "トゥーンHQ:花火楽しんでくれたかな?すてきな夏をすごしてね!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 +TIP_GOLF = 7 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "トゥーンアドバイス:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "\"End\"キーを押している間、 トゥーンタスクの進み具合をチェックできるよ。", + "ギャグページは、\"Home\"キーを押したままにするとすぐチェックできるよ。", + "\"PageUp\"キーを押すと、\n見上げることができ、\n\"PageDown\"キーを押すと、見おろすことができるよ。", + "ジャンプするには、 \"Control\"キーを押してね。", + "\"F8\"キーを押すと、 トゥーンガイドを開け閉めできるよ。", + "トゥーンタウンの外で誰か知ってる人と「ひみつのともだち」の暗号を交換すると、自由にチャットができるようになるよ。", + "\"F9\"キーを押すと、画面の写真がとれるよ。写真はトゥーンタウンのフォルダに保存されるよ。", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "You can exchange Secret Friend Codes with somebody you know outside Toontown to enable open chat with them in Toontown.", + "トゥーンガイドのオプションページで、画面の解像度を変えたり、音の調整などの設定ができるよ。", + "キミの友だちの家のクローゼットにあるようふくをためすことができるよ。", + "トゥーンガイドの地図にある「家に戻る」ボタンを押すと、おうちに帰れるよ。", + "トゥーンタスクをクリアすると、キミのゲラゲラメーターが自動的に補充されるよ。", + "ようふく券がなくても、ようふくを試着できるよ。", + "トゥーンタスクのごほうびとして、もっとたくさんのギャグやジェリービーンをもてるようになることがあるよ。", + "ともだちリストには、ともだちを50人まで書き込むことができるよ。", + "いくつかトゥーンタスクは、キミがトゥーンタウンのプレイグラウンドへワープできるようにするんだ。トゥーンガイドにある地図のページを使えばテレポートできるよ。", + "プレイグラウンドでは、その地域のまわりにあらわれるアイスクリームといったアイテムをとると、はやくパワーが戻るよ。", + "バトルの後に体力を早く回復させたいときには、おうちに戻ってアイスクリームを集めよう!", + "\"TAB\"キーを押すと、キミのトゥーンの視点をいろいろ変えることができるよ。", + "ときどき同じごほうびをくれるちがうトゥーンタスクをみつけることがあるよ。いろいろ行ってみて!", + "おなじようなトゥーンタスクのともだちをみつければ、一緒に楽しみながらゲームも上達できるよ!", + "トゥーンタウンでしたことはセーブしなくても大丈夫だよ。トゥーンタウンのサーバーがすべての状況をちゃんと保存してるからね。", + "トゥーンをクリックするか、ともだちリストから選ぶと、他のトゥーンとないしょ話ができるよ。", + "スピードチャットのセリフの中には、体の動きがついているものもあるよ。", + "もしキミのいる場所が混みすぎていたら、 トゥーンガイドを使って、ロビーを変えてみて。", + "たくさんの建物をコグ達から取り戻すと、 トゥーンの頭の上に 金、銀、銅の星が付くよ!", + "ある程度の建物を取り戻すと、トゥーンHQの黒板にキミの名前がのるかもね。", + "コグから取り戻したビルが、再びコグに奪われることがあるよ。トゥーンの頭の上の星を維持するには、どんどんビルを取り戻そう!", + "ひみつの友達の名前は青色の文字で表示されるよ。", + # Fishing + "キミはトゥーンタウンにいる全ての魚を集められるかな?", + "池が違うと魚も違うよ。全ての池で釣りをしてみよう!", + "魚のバケツが一杯になったらプレイグラウンドにいるペットショップのてんいんに売ろう!", + "釣った魚は釣り人かペットショップで売ろう!", + "強い釣りざおは、より重い魚を釣ることができるけど、買うのにより多くのジェリービーンが必要になるよ。", + "より強い釣りざおはクララベルからカタログで購入できるよ。", + "ペットショップにとって魚は、重ければ重いほど価値があるよ。", + "珍しい魚はペットショップがより多いジェリービーンと交換してくれるよ。", + "釣りをしているとたまにジェリービーンが入ったバッグを見つけることがあるよ。", + "トゥーンタスクの中には池の中からあるアイテムを釣りあげるものがあるよ。", + "プレイグラウンドの池とストリートの池では釣れる魚が違うよ。", + "魚の中には本当に珍しい種類がいるよ。全部集めるまで釣り続けよう!", + "キミのおうちの近くにある池では、そこでしか釣れない魚がいるよ。", + "10種類の魚を釣るごとに、トロフィーがもらえるよ!", + "トゥーンガイドを見れば、どんな魚を釣ったかを見ることができるよ。", + "トロフィーの中にはゲラゲラメーターをアップさせるものがあるよ。", + "たくさんのジェリービーンを稼ぎたいなら、釣りがお勧めだよ!", + # Doodles + "ペットショップでドゥードゥルを手に入れよう!", + "ペットショップでは毎日、新しいドゥードゥルが売ってるよ!", + "どんなドゥードゥルがいるか、毎日ペットショップをチェックしに行ってみよう!", + "ロビーが違うと、そこにいるドゥードゥルも違うよ。", + # Karting + "スピードウェイでキミのホットロッドを展示してレース相手を探そう!", + "トゥーンタウン・セントラルのタイヤがたのトンネルから、グーフィー・サーキットに行こう!", + "グーフィー・サーキットでゲラゲラポイントをゲットしよう!", + "グーフィー・サーキットには、6つのレーストラックがあるよ。 " + ), + + TIP_STREET : ( + "コグにはロウボット、マネーボット、セルボット、ボスボットの4種類があるんだ。", + "各ギャグトラックは、「めいちゅうりつ」と「ダメージ」がそれぞれ決まっているよ。", + "「サウンド」ギャグは、すべてのコグにダメージをあたえるけど、「おとり」にはまったコグを逃してしまうよ。", + "よく考えて、戦略的な順番でコグをたおすと、バトルで勝つチャンスがすごく増えるよ。", + "「トゥーンアップ」はバトルの間、他のトゥーンのゲラゲラメーターを回復することができるよ。", + "ギャグのスキルポイントは、「コグ侵略中」だと2倍になるよ。", + "たくさんのトゥーンがバトルで同じギャグトラックを使うと、与えられるダメージが大きくなるよ。", + "バトル中のギャグは、メニューに表示されている順番で上から下の順で使われるよ。", + "コグに乗っ取られたビルのエレベーターの上のランプは、そのビルが何階建てかを示しているんだ。", + "コグをクリックすると、もっと詳しい情報が見れるよ。", + "高いレベルのギャグを低いレベルのコグに使っても、スキルレベルは増えないよ。", + "バトル中のギャグのメニューで青い背景のギャグは、スキルレベルをかせぐことができるよ。", + "コグのビルの中にいると、スキルレベルが増えるんだ。高い階ほど、たくさん増えるよ。", + "コグが倒されると、バトルが終わった時まわりにいるトゥーンたちはみなそのコグに対するクレジットがもらえるんだ。", + "トゥーンタウンの通りはそれぞれ、いろいろな種類やレベルのコグがまじっているよ。", + "歩道にいれば、コグから攻撃はされないよ。", + "通りでは、カンバンのないドアに近づくと、そのドアがジョークを言うんだ。", + "トゥーンタスクを完了するうちに、キミは新しいギャグトラックで練習することになるんだ。7つのギャグトラックのうち6つを選ぶことになるから、気をつけて選んでね!", + "「トラップ」は、バトルでキミかキミのともだちが「おとり」を使うようにする時だけ効果があるよ。", + "高いレベルの「おとり」は、成功する確率が高いよ。", + "低いレベルの「おとり」は、高いレベルのコグに対してあんまり効果がないよ。", + "バトルで「おとり」にはまっていたら、コグは攻撃できないんだ。", + "コグに乗っ取られたビルを取り戻すと、ごほうびで、キミたちが救ったトゥーンのビルの中に肖像画がかざられるよ。", + "ゲラゲラメーターがすでにいっぱいのトゥーンをトゥーンアップしても、体力は回復しないよ。", + "コグはどんなギャグにでも決まると簡単に気絶しちゃうんだ、だから同じラウンドで他のギャグが当たるチャンスが増えるんだよ。", + "「ドロップ」ギャグは当たる確率は低いけど、同じラウンドで他のギャグが決まると、より当たりやすくなるんだ。", + "コグをたくさん倒すと、トゥーンガイドのコグページを使って \"コグレーダー\"ができるようになるよ。", + "バトル中に仲間のトゥーンがどのコグを攻撃しているかは\"-\"や\"X\"マークが教えてくれるよ。", + "バトル中のコグ達の胸の場所にあるライトは、彼らの体力を表しているよ:\n緑は健康で、赤はこわれる寸前。", + "バトルで同時に戦えるのは、最大4人のトゥーンまでだからね。", + "ストリートでは、コグは一人のトゥーンよりも大勢のトゥーンたちのバトルに参加したがるよ。", + "コグの種類の中でも強い2体は乗っ取られたビルの中にしか出てこないよ。", + "「ドロップ」ギャグは「おとり」にハマったコグには通用しないよ。", + "コグは最も大きなダメージを与えたトゥーンを攻撃する傾向があるよ。", + "「サウンド」ギャグは「おとり」にハマったコグに対してはボーナスダメージが与えられないんだ。", + "「おとり」にハマったコグを長い時間待たせると、目覚めてしまうよ。レベルの高い「おとり」だと効果がより長く持続するよ。", + "池はトゥーンタウンのどのストリートにもあるよ。中には変わった魚もいるから色々ためしてみてね。", + ), + + TIP_MINIGAME : ( + "ジェリービーンのびんがいっぱいになると、トロリーゲームで稼いだジェリービーンは全部自動的にキミの銀行からこぼれでちゃうよ。", + "\"マッチミニーゲーム\"では、マウスのかわりにやじるしキーを使えるよ。", + "\"たいほうゲーム\"では、やじるしキーを使って大砲を動かして、コントロールキーを押すと発砲できるよ。", + "\"リングゲーム\"では、チームの全員がうきわをくぐって泳ぐのに成功するとボーナスポイントがもらえるよ。", + "\"マッチミニーゲーム\"でかんぺきな踊りをすると、ジェリービーンを2倍もらえるよ。", + "\"つなひきゲーム\"ではより強いコグに勝つと、より多いジェリービーンがもらえるよ。", + "トロリーゲームの難しさは遊ぶ場所によって変わるよ:トゥーンタウンセントラルが一番簡単でドナルドのドリームランドが一番難しいよ。", + "トロリーゲームのいくつかは仲間で参加しないと遊べないものがあるよ。", + ), + + TIP_COGHQ : ( + "コグへの変装をコンプリートしないと、ボスのビルに入れないよ!", + "警備兵の上にジャンプすると、しばらくの間、動きが止まるよ!", + "コグをたくさん倒して、コグのメリットを集めよう!", + "レベルの高いコグからは、より多くのメリットを手に入れることが出来るよ!", + "コグのメリットを集めると「格上げ」されて、セルボットのコグゼキュティブに会いに行けるようになるよ!", + "コグに変装しているときには、コグのように話すことが出来るよ!", + "セルボットのコグゼキュティブとのバトルには最大8トゥーンまで参加できるよ!", + "セルボットのコグゼキュティブは、コグ本部の一番上にいるよ!", + "コグ工場の中では、階段に沿っていくことで工場長の所までたどり着くことができるよ!", + "工場でのバトルごとに、コグへの変装パーツを1つ手に入れることができるよ!", + "トゥーンガイドでコグへの変装の度合いをチェックすることができるよ!", + "トゥーンガイドの変装のページで「メリット」の進行度合いをチェックできるよ!", + "コグゼキュティブに会うときには、ギャグとゲラゲラメーターがまんたんかどうかをちゃんとチェックしてね。", + "格上げされると、コグ変装グッズがアップデートされるよ。", + "工場長を倒さないとコグに変装するパーツを手に入れることはできないよ。", + "ドナルドのドリームランドでトゥーンタスクをやると、マネーボットのへんそうスーツがゲットできるよ!", + "マネーボットほんぶには、コイン・ドル・ゴールドの3つの工場があるよ。", + "マネーマネーがフラフラの時にきんこを投げないと、ヘルメットがわりにとられちゃうよ!きんこを当てて、ヘルメットをはじき飛ばそう!", + "バトルでロウボットを倒してショウカンジョーを集めよう。", + "レベルの高いコグを倒すとより多くのメリットが得られるよ。", + "ショウカンジョーを集めてじゅうぶん格上げされたら、ロウボット本部のサイバンチョーにちょうせんだ!", + "サイバンチョーにちょうせんするには、ロウボットのへんそうパーツがひつようだよ。", + "サンバンチョーには同時に8人までいっしょにちょうせんできるよ。", + "パズルにちょうせん!しっぱいするとバーチャル・コグがキミのショウカンをじゃまするよ。 ", + "", + "", + "", + ), + TIP_ESTATE : ( + # Doodles #★ + "ドゥードゥルはスピードチャットのいくつかのセリフがわかるから試してみよう!", + "スピードチャットの\"ペット\"の項目からドゥードゥルに「トリック」をさせよう!", + "クララベルのショッピングカタログのアイテムでドゥードゥルに「トリック」を教えることができるぞ!", + "ドゥードゥルが「トリック」をしたら、ごほうびをあげよう!", + "友達のおうちに遊びにいくと、キミのドゥードゥルもついてくるよ。", + "ドゥードゥルが「くうふく」のときにはジェリービーンをあげよう。", + "ドゥードゥルをクリックすればパネルが開くよ。そうしたら、エサをあげたり、なでたり、呼んでみよう。", + "ドゥードゥルは集まるのが好き。友達を呼んで、一緒に遊ぼう!", + "全てのドゥードゥルはそれぞれ違う個性をもっているよ。", + "キミのドゥードゥルをペットショップに返して、新しいドゥードゥルを手に入れることもできるぞ。", + "ドゥードゥルが「トリック」をつかうと、そのまわりにいるトゥーンがトゥーンアップするぞ。", + "練習すればするほどドゥードゥルの「トリック」がうまくなるから、がんばってみよう。", + "上達したドゥードゥルの「トリック」は、トゥーンアップの効果も大きいぞ。", + "「トリック」をするとドゥードゥルは疲れるけど、経験のあるドゥードゥルはより多くできるぞ!", + "トゥーンの近くにいるドゥードゥルのリストは、ともだちリストの場所でみることができるぞ。", + # Furniture / Cattlelog + "クララベルのカタログから家具を買って、おうちのインテリアをコーディネートしよう。", + "貯ビーン箱にはもっとジェリービーンをたくわえこむことができるぞ。", + "クローゼットにはキミの着ていないようふくを入れておけるよ。たまには着替えて出かけよう!", + "ともだちのおうちに遊びに行ったら、ようふくを試着させてもらおう!", + "カタログからより良いつりざおを買って、変わった魚を釣ろう!", + "大きな貯ビーン箱を買えばもっとジェリービーンをとっておけるぞ!", + "クララベルに連絡したいときには、おうちの電話をつかってね。", + "より大きなクローゼットもクララベルのカタログから買うことができるよ。", + "「ようふく券」を使うときには、クローゼットに空きを作っておこう。", + "キミのおうちをおしゃれにする色々なアイテムを扱ってるよ!", + "クララベルに注文をしたら、キミのおうちの前のポストをチェックしてみよう!", + "カタログでようふくを注文すると、1時間でポストに届くよ。", + "かべ紙や床のフローリングは注文してから1時間かかるよ。", + "買った家具がおうちに届くには丸1日かかるよ。", + "屋根裏に余った家具をとっておこう。", + "クララベルから連絡があると、それは新しい商品カタログがついたってこと。", + "ショッピングカタログが届くと、クララベルから連絡がくるよ!", + "新しいショッピングカタログは毎週届くよ!", + "カタログのある時期にしか売ってないアイテムの中でも、1年中使えるものがあるから探してみよう!", + "いらない家具はごみ箱に移そう。", + # Fish + "ホーレーマカレルのようなサカナは、キミのおうちの近くの池のほうが、よく見かけるらしい。", + # Misc + "スピードチャットを使って、ともだちをキミのおうちに呼ぼう!", + "キミのおうちの色は最初にトゥーンを作ったときのパネルの色と同じだって知ってた?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "グーフィーのオートショップで、ロードスターやトゥーンヴィークルや、クルーザーを買おう。", + "グーフィーのオートショップで、キミのカートの色やパーツをカスタムしよう。", + "グーフィー・サーキットでレースをして、チケットをゲットしよう。", + "グーフィーのオートショップで買い物をする時は、チケットを使うんだ。", + "レース参加に使うデポジットは、レースのあとに返ってくるよ。", + "トゥーンガイドのステッカーブックで、キミのカートをカスタムしよう。", + "トゥーンガイドのステッカーブックで、キミのカートでのベストラップがみれるよ。", + "トゥーンガイドのステッカーブックで、キミがレースでゲットしたトロフィーが見れるよ。", + "スクリュースタジアムがグーフィー・サーキットで一番かんたんなコースだよ。", + "エアボーン・エーカースはグーフィー・サーキットで一番ジャンプが多いコースだよ。", + "ブリザード・ブルバードはグーフィー・サーキットで一番むずかしいんだ。", + ), + TIP_GOLF: ( + # Golfing specific + "コースを上から見る時はTabキーを押してね。", + "パットの方向をカップにまっすぐにするには、上矢印(↑)キーを押してね。", + "クラブのスイングのコントロールはパイ投げと同じだよ。", + ), + } + +FishGenusNames = { + 0 : "バルーン フィッシュ", + 2 : "キャット フィッシュ", + 4 : "クラウン フィッシュ", + 6 : "フローズン フィッシュ", + 8 : "スター フィッシュ", + 10 : "ホーレー マカレル", + 12 : "ドッグ フィッシュ", + 14 : "アモーレ イール", + 16 : "ナース シャーク", + 18 : "キング クラブ", + 20 : "ムーン フィッシュ", + 22 : "シー ホース", + 24 : "プール シャーク", + 26 : "ベア アキューダ", + 28 : "カットスロート トラウト", + 30 : "ピアノ ツナ", + 32 : "PB&J フィッシュ", + 34 : "デビル レイ", + } + +FishSpeciesNames = { + 0 : ( "バルーン フィッシュ", + "ホットエアー バルーン フィッシュ", + "ウェザー バルーン フィッシュ", + "ウォーター バルーン フィッシュ", + "レッド バルーン フィッシュ", + ), + 2 : ( "キャット フィッシュ", + "シャム キャット フィッシュ", + "アレー キャット フィッシュ", + "タビー キャット フィッシュ", + "トム キャット フィッシュ", + ), + 4 : ( "クラウン フィッシュ", + "サッド クラウン フィッシュ", + "パーティー クラウン フィッシュ", + "サーカス クラウン フィッシュ", + ), + 6 : ( "フローズン フィッシュ", + ), + 8 : ( "スター フィッシュ", + "ファイブ スター フィッシュ", + "ロック スター フィッシュ", + "シャイニング スター フィッシュ", + "オール スター フィッシュ", + ), + 10 : ( "ホーレー マカレル", + ), + 12 : ( "ドッグ フィッシュ", + "ブル ドッグ フィッシュ", + "ホット ドッグ フィッシュ", + "ダルメシア ドッグ フィッシュ", + "パピー ドッグ フィッシュ", + ), + 14 : ( "アモーレ イール", + "エレクトリック アモーレ イール", + ), + 16 : ( "ナース シャーク", + "クララ ナース シャーク", + "フローレンス シャーク ", + ), + 18 : ( "キング クラブ", + "アラスカ キング クラブ", + "オールド キング クラブ", + ), + 20 : ( "ムーン フィッシュ", + "フルムーン フィッシュ", + "ハーフムーン フィッシュ", + "ニュームーン フィッシュ", + "クレセントムーン フィッシュ", + "ハーベストムーン フィッシュ", + ), + 22 : ( "シー ホース", + "ロッキング シー ホース", + "クライズデール シー ホース", + "アラビアン シー ホース", + ), + 24 : ( "プール シャーク", + "キディー プール シャーク", + "スイミング プール シャーク", + "オリンピック プール シャーク", + ), + 26 : ( "ブラウン ベアー アキューダ", + "ブラック ベアー アキューダ", + "コアラ ベアー アキューダ", + "ハニー ベアー アキューダ", + "ポーラー ベアー アキューダ", + "パンダ ベアー アキューダ", + "コディアック ベアー アキューダ", + "グリズリー ベアー アキューダ", + ), + 28 : ( "カットスロート トラウト", + "キャプテン カットスロート トラウト", + "スカービー カットスロート トラウト", + ), + 30 : ( "ピアノ ツナ", + "グランドピアノ ツナ", + "ベビー グランドピアノ ツナ", + "アップライト ピアノ ツナ", + "プレイヤー ピアノ ツナ", + ), + 32 : ( "PB&J フィッシュ", + "グレープ PB&J フィッシュ", + "クランチー PB&J フィッシュ", + "ストロベリー PB&J フィッシュ", + "コンコルド グレープ PB&J フィッシュ", + ), + 34 : ( "デビル・レイ", + ), + } + +CogPartNames = ( + "左レッグ①", "左レッグ②", "左フット", + "右レッグ①", "右レッグ②", "右フット", + "左ショルダー", "右ショルダー", "ボディ①", "コグメーター", "ボディ②", + "左アーム①", "左アーム②", "左ハンド", + "右アーム①", "右アーム②", "右ハンド", + ) + +CogPartNamesSimple = ( + "ボディ上", + ) + +FishFirstNames = ( + "", + "エンジェル", + "アーティック", + "ベイビー", + "バミューダ", + "ビッグ", + "ブルーク", + "バブルス", + "バスター", + "キャンディー", + "キャプテン", + "チップ", + "チャブ", + "コーラル", + "ドクター", + "ダスティー", + "エンペラー", + "ファングス", + "ファット", + "フィッシー", + "フリッパー", + "フラウンダー", + "フレックル", + "ハニー", + "ジャック", + "キング", + "リトル", + "マーリン", + "ミス", + "ミスター", + "ピーチ", + "ピンキー", + "プリンス", + "プリンセス", + "プロフェッサー", + "パフィー", + "クィーン", + "レインボー", + "レイ", + "ロージー", + "ラスティー", + "ソルティー", + "サム", + "サンディー", + "スケールス", + "シャーキー", + "サー", + "スキッピー", + "スキッパー", + "スナッパー", + "スペック", + "スパイク", + "スポッティー", + "スター", + "シュガー", + "スーパー", + "タイガー", + "タイニー", + "ウィスカーズ", + ) + +FishLastPrefixNames = ( + "", + "ビーチ", + "ブラック", + "ブルー", + "ボア", + "ブル", + "キャット", + "ディープ", + "ダブル", + "イースト", + "ファンシー", + "フレーキー", + "フラット", + "フレッシュ", + "ジャイアント", + "ゴールド", + "ゴールデン", + "グレイ", + "グリーン", + "ホグ", + "ジャバー", + "ジェリー", + "レディー", + "レザー", + "レモン", + "ロング", + "ノーザン", + "オーシャン", + "オクト", + "オイル", + "パール", + "パフ", + "レッド", + "リボン", + "リバー", + "ロック", + "ルビー", + "ラダー", + "ソルト", + "シー", + "シルバー", + "シュノーケル", + "ソール", + "サザン", + "スパイキー", + "サーフ", + "ソード", + "タイガー", + "トリプル", + "トロピカル", + "ツナ", + "ウェーブ", + "ウィーク", + "ウエスト", + "ホワイト", + "イエロー", + ) + +FishLastSuffixNames = ( + "", + "ボール", + "バス", + "ベリー", + "バグ", + "バーグラー", + "バター", + "クロー", + "コブラー", + "クラブ", + "クローカー", + "ドラム", + "フィン", + "フィッシュ", + "フラッパー", + "フリッパー", + "ゴースト", + "グラント", + "ヘッド", + "ジャケット", + "ジャンパー", + "マカレル", + "ムーン", + "マウス", + "ミュレー", + "ネック", + "ノーズ", + "パーチ", + "ラフィー", + "ランナー", + "セイル", + "シャーク", + "シェル", + "シルク", + "スライム", + "スナッパー", + "スティンク", + "テイル", + "トード", + "トラウト", + "ウォーター", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "メインゲート" +SellbotLegFactorySpecLobby = "ロビー" +SellbotLegFactorySpecLobbyHallway = "廊下" +SellbotLegFactorySpecGearRoom = "ギヤルーム" +SellbotLegFactorySpecBoilerRoom = "ボイラールーム" +SellbotLegFactorySpecEastCatwalk = "東の通路" +SellbotLegFactorySpecPaintMixer = "ペンキルーム" +SellbotLegFactorySpecPaintMixerStorageRoom = "ペンキルーム倉庫" +SellbotLegFactorySpecWestSiloCatwalk = "西タワー通路" +SellbotLegFactorySpecPipeRoom = "パイプルーム" +SellbotLegFactorySpecDuctRoom = "ダクトルーム" +SellbotLegFactorySpecSideEntrance = "サイドゲート" +SellbotLegFactorySpecStomperAlley = "プレスルーム" +SellbotLegFactorySpecLavaRoomFoyer = "ヨウガンルーム" +SellbotLegFactorySpecLavaRoom = "ヨウガンルーム" +SellbotLegFactorySpecLavaStorageRoom = "ヨウガンルーム倉庫" +SellbotLegFactorySpecWestCatwalk = "西の通路" +SellbotLegFactorySpecOilRoom = "オイルルーム" +SellbotLegFactorySpecLookout = "見張り台" +SellbotLegFactorySpecWarehouse = "倉庫" +SellbotLegFactorySpecOilRoomHallway = "オイルルーム入口" +SellbotLegFactorySpecEastSiloControlRoom = "東コントロールルーム" +SellbotLegFactorySpecWestSiloControlRoom = "西コントロールルーム" +SellbotLegFactorySpecCenterSiloControlRoom = "工場長の部屋" +SellbotLegFactorySpecEastSilo = "東タワー屋上" +SellbotLegFactorySpecWestSilo = "西タワー屋上" +SellbotLegFactorySpecCenterSilo = "メインタワー屋上" +SellbotLegFactorySpecEastSiloCatwalk = "東タワー通路" +SellbotLegFactorySpecWestElevatorShaft = "西エレベーター" +SellbotLegFactorySpecEastElevatorShaft = "東エレベーター" + +#FISH BINGO +FishBingoBingo = "ビンゴ!" +FishBingoVictory = "やったね!" +FishBingoJackpot = "ジャックポット!" +FishBingoGameOver = "ゲームオーバー" +FishBingoIntermission = "お休み終了まで\nあと" +FishBingoNextGame = "ゲーム開始まで\nあと" +FishBingoTypeNormal = "クラシック" +FishBingoTypeCorners = "4コーナー" +FishBingoTypeDiagonal = "ななめ" +FishBingoTypeThreeway = "3ウェイ!" +FishBingoTypeBlockout = "ブロックアウト!" +FishBingoStart = "「魚でビンゴ!」の時間です! 遊びたい人は好きな橋に立ってね。" +FishBingoOngoing = "フィッシュビンゴをかいさい中だよ!" +FishBingoEnd = "魚でビンゴ!、楽しかった?" +FishBingoHelpMain = "「魚でビンゴ!」へようこそ! 時間内にみんなで協力して、ビンゴカードをマークしよう!" +FishBingoHelpFlash = "魚を釣ったら、点滅している場所のひとつをクリックしてカードにマークしてね。" +FishBingoHelpNormal = "これは普通のビンゴカードだよ。たて、よこ、ななめ一列にマークがつけば勝ち!" +FishBingoHelpDiagonals = "ななめに2本、バッテンになるようにマークしたら勝ち!" +FishBingoHelpCorners = "簡単なコーナーカード。4つのコーナーをマークしたら勝ち!" +FishBingoHelpThreeway = "3ウェイ! ななめ2本と真ん中の横ラインをマークしたら勝ち!なかなか難しいぞ!" +FishBingoHelpBlockout = "ブロックアウト! 全ての場所をマークすれば勝ち。他の全ての池にいるトゥーンと競ってジャックポットを目指そう!" +FishBingoOfferToSellFish = "キミのバケツが一杯だよ。魚を売りますか?" +FishBingoJackpotWin = "%s ジェリービーン ゲット!" +FishBingoJackpot = "%s ジェリービーン ゲット!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "トゥーンアップ" +ResistanceToonupItem = "%s トゥーンアップ" +ResistanceToonupItemMax = "さいだい" +ResistanceToonupChat = "トゥーン最高!トゥーンアップ!" #▲ +ResistanceRestockMenu = "ギャグアップ" +ResistanceRestockItem = "%s ギャグアップ" +ResistanceRestockItemAll = "すべて" +ResistanceRestockChat = "笑って!ギャグアップ!" #▲ +ResistanceMoneyMenu = "ジェリービーン" +ResistanceMoneyItem = "ジェリービーン %s個" +ResistanceMoneyChat = "かしこく使おう!トゥーン最高!" #▲ + +# Resistance Emote NPC chat phrases #localize +ResistanceEmote1 = NPCToonNames[9228] + ": レジスタンスへようこそ!" +ResistanceEmote2 = NPCToonNames[9228] + ": 新しい'きもち'を使ってじぶんをひょうげんしてみよう。" +ResistanceEmote3 = NPCToonNames[9228] + ": がんばってね!" + +# Kart racing +KartUIExit = "おりる" +KartShop_Cancel = lCancel +KartShop_BuyKart = "かう" +KartShop_BuyAccessories = "アクセサリーをかう" +KartShop_BuyAccessory = "アクセサリーをかう" +KartShop_Cost = "ねだん: %d チケット" +KartShop_ConfirmBuy = "%sを%dチケットでかう?" +KartShop_NoAvailableAcc = "この種類のアクセサリーはダメだよ。" +KartShop_FullTrunk = "キミのトランクがいっぱいだよ。" +KartShop_ConfirmReturnKart = "本当にキミのカートを返してもいい?" +KartShop_ConfirmBoughtTitle = "おめでとう!" +KartShop_NotEnoughTickets = "じゅうぶんなチケットがありません。" + +KartView_Rotate = "かいてん" +KartView_Right = "右" +KartView_Left = "左" + +# starting block +StartingBlock_NotEnoughTickets = "チケットが足りないよ。かわりにれんしゅうのレースに出よう!" +StartingBlock_NoBoard = "今回のレースは申し込みがおわったよ。次のレースまで待っててね。" +StartingBlock_NoKart = "まずはカートが必要だね!カートショップのてんいんに聞いてみよう。" +StartingBlock_Occupied = "この場所はすでにうまっています。ほかの場所をためしてね。" +StartingBlock_TrackClosed = "ごめんね、このレーストラックは工事中です。" +StartingBlock_EnterPractice = "れんしゅうのレースに出ますか?" +StartingBlock_EnterNonPractice = "%sのレースにチケット%s枚で参加しますか?" +StartingBlock_EnterShowPad = "ここにキミのカートを止めますか?" +StartingBlock_KickSoloRacer = "トゥーンバトルレースはひとりではできないよ。" +StartingBlock_Loading = "レースに行く!" + +#stuff for leader boards +LeaderBoard_Time = "タイム" +LeaderBoard_Name = "レーサー名" +LeaderBoard_Daily = "ほんじつのベストタイム" +LeaderBoard_Weekly = "しゅうかんベストタイム" +LeaderBoard_AllTime = "ベストタイムのでんどう" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "れんしゅう", + "トゥーンバトル", + "トーナメント", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "スタート!" +KartRace_Reverse = "リバース " + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "スクリュースタジアム", + RaceGlobals.RT_Speedway_1_rev : KartRace_Reverse + "スクリュースタジアム", + RaceGlobals.RT_Rural_1 : "さびさびレースウェイ", + RaceGlobals.RT_Rural_1_rev : KartRace_Reverse + "さびさびレースウェイ", + RaceGlobals.RT_Urban_1 : "シティーサーキット", + RaceGlobals.RT_Urban_1_rev : KartRace_Reverse + "シティーサーキット", + RaceGlobals.RT_Speedway_2 : "きりもみコロシアム", + RaceGlobals.RT_Speedway_2_rev : KartRace_Reverse + "きりもみコロシアム", + RaceGlobals.RT_Rural_2 : "エアボーンエーカース", + RaceGlobals.RT_Rural_2_rev : KartRace_Reverse + "エアボーンエーカース", + RaceGlobals.RT_Urban_2 : "ブリザードブルバード", + RaceGlobals.RT_Urban_2_rev : KartRace_Reverse + "ブリザードブルバード", + } + +KartRace_Unraced = "N/A" + +KartDNA_KartNames = { + 0:"クルーザー", + 1:"ロードスター", + 2:"トゥーンビークル" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "エアークリーナー", + 1001: "4バレル", + 1002: "フライングイーグル", + 1003: "ステア ホーン", + 1004: "ストレート6", + 1005: "スモール スクープ", + 1006: "シングル オーバーヘッド", + 1007: "ミディアム スクープ", + 1008: "シングル バレル", + 1009: "フラグル ホーン", + 1010: "ストライプ スクープ", + #spoiler accessory names + 2000: "スペース ウィング", + 2001: "ツギハギ スペア", + 2002: "ロール ケージ", + 2003: "シングル フィン", + 2004: "ダブルデッカー ウィング", + 2005: "シングル ウイング", + 2006: "スタンダード スペア", + 2007: "シングル フィン", + 2008: "sp9", + 2009: "sp10", + #front wheel well accessory names + 3000: "ケットー ホーン", + 3001: "フレディー フェンダー", + 3002: "コバルト ステップ", + 3003: "コブラ サイドパイプ", + 3004: "ストレート サイドパイプ", + 3005: "ホタテ フェンダー", + 3006: "カーボン ステップ", + 3007: "ウッド ステップ", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "クルクル テールパイプ", + 4001: "スプラッシュ フェンダー", + 4002: "デュアル エグゾースト", + 4003: "プレーン デュアルフィン", + 4004: "プレーン ドロヨケ", + 4005: "クアッド エグゾースト", + 4006: "デュアル フレアー", + 4007: "メガ エグゾースト", + 4008: "ストライプ デュアルフィン", + 4009: "バブル デュアルフィン", + 4010: "ストライプ ドロヨケ", + 4011: "ミッキー ドロヨケ", + 4012: "ホタテ ドロヨケ", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "ターボ", + 5001: "ムーン", + 5002: "ツギハギ", + 5003: "3スポーク", + 5004: "ペイントリッド", + 5005: "ハート", + 5006: "ミッキー", + 5007: "5ボルト", + 5008: "デイジー", + 5009: "バスケットボール", + 5010: "ヒプノ", + 5011: "トライバル", + 5012: "ジェムストーン", + 5013: "5スポーク", + 5014: "ノックオフ", + #decal accessory names + 6000: "ナンバー5", + 6001: "スプラッター", + 6002: "チェッカーボード", + 6003: "フレイム", + 6004: "ハーツ", + 6005: "バブルス", + 6006: "タイガー", + 6007: "フラワー", + 6008: "ライトニング", + 6009: "エンジエル", + #paint accessory names + 7000: "シャルトルーズ", + 7001: "ピーチ", + 7002: "ブライトレッド", + 7003: "レッド", + 7004: "マルーン", + 7005: "シエナ", + 7006: "ブラウン", + 7007: "タン", + 7008: "コーラル", + 7009: "オレンジ", + 7010: "イエロー", + 7011: "クリーム", + 7012: "シトリーン", + 7013: "ライム", + 7014: "シーグリーン", + 7015: "グリーン", + 7016: "ライトブルー", + 7017: "アクア", + 7018: "ブルー", + 7019: "ペリウィンクル", + 7020: "ロイヤルブル-", + 7021: "スレートブルー", + 7022: "パープル", + 7023: "ラベンダー", + 7024: "ピンク", + 7025: "プラム", + 7026: "ブラック", + } + +RaceHoodSpeedway = "スピードウェイ" +RaceHoodRural = "なごやか" +RaceHoodUrban = "アーバン" +RaceTypeCircuit = "トーナメント" +RaceQualified = "よせんつうか" +RaceSwept = "ぜんしょう!" +RaceWon = "かち" +Race = "レース" +Races = "レース" +Total = "合計" +GrandTouring = "グランドツーリング" + +def getTrackGenreString(genreId): + genreStrings = [ "Speedway", + "Country", + "City" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.QualifiedRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodSpeedway + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodSpeedway + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodRural + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodRural + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[0]) + " " + RaceHoodUrban + " " + Race + " " + RaceWon, + str(RaceGlobals.WonRaces[1]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.WonRaces[2]) + " " + RaceHoodUrban + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + str(RaceGlobals.TrophiesPerCup * 2) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + str(RaceGlobals.TrophiesPerCup * 3) + " カートレースのトロフィーゲット!\nゲラゲラポイント、アップ!", + ] + +KartRace_TitleInfo = "レースの準備はいいかな?" +KartRace_SSInfo = "スクリュースタジアムへようこそ!\nエンジンをふかして、ハンドルをにぎりしめて!\n" +KartRace_CoCoInfo = "きりもみコロシアムにようこそ!\nスピードを落さないように、バンクをうまく使ってね。\n" +KartRace_RRInfo = "さびさびレースウェイへようこそ!\nコースをよーく見て!ライバルにおてやわらかに!\n" +KartRace_AAInfo = "エアボーン・エーカースにようこそ!\nアップ・ダウンのはげしいコースに注意してね!\n" +KartRace_CCInfo = "シティーサーキットへようこそ!\nダウンタウンを通りぬけるときには、ほこうしゃに気をつけて!\n" +KartRace_BBInfo = "ブリザード・ブルバードにようこそ!\nスピード出しすぎ注意!道路がこおってるかも!?\n" +KartRace_GeneralInfo = "方向キーでカートをコントロールしよう!コース上でひろったギャグはコントロールキーで投げられるよ!" + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'ほんじつの', + RaceGlobals.Weekly : 'こんしゅうの', + RaceGlobals.AllTime : 'れきだいの', + } + +KartRace_FirstSuffix = '位' +KartRace_SecondSuffix = '位' +KartRace_ThirdSuffix = '位' +KartRace_FourthSuffix = '位' +KartRace_WrongWay = '逆方向!' +KartRace_LapText = "ラップ %s" +KartRace_FinalLapText = "ファイナルラップ!" +KartRace_Exit = "レースしゅうりょう" +KartRace_NextRace = "次のレース" +KartRace_Leave = "レースをやめる" +KartRace_Qualified = "よせんつうか!" +KartRace_Record = "しんきろく!" +KartRace_RecordString = '%sしんきろく!\n%sから\nチケット%s枚のボーナス!' +KartRace_Tickets = "チケット" +KartRace_Exclamations = "!" +KartRace_Deposit = "デポジット" +KartRace_Winnings = "しょうり" +KartRace_Bonus = "ボーナス" +KartRace_RaceTotal = "レーストータル" +KartRace_CircuitTotal = "サーキットトータル" +KartRace_Trophies = "トロフィー" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s " + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" +KartRace_QualifyPhrase = "よせんつうか:\n" +KartRace_RaceTimeout = "タイムアップ!キミのチケットはもどったよ。がんばって!" +KartRace_RaceTimeoutNoRefund = "じかんぎれです。グランプリはもう始まってしまったからチケットはもどらないよ。次は頑張ってね!" +KartRace_RacerTooSlow = "ざんねん!じかんぎれです。デポジットはかえってこないけど、あきらめずにがんばってね!" +KartRace_PhotoFinish = "フォト・フィニッシュ!" +KartRace_CircuitPoints = 'サーキットポイント' + +CircuitRaceStart = "グーフィー・サーキットでトゥーンタウン・グランプリが始まるよ!3つのレースにさんかして、一番ポイントをゲットしたらチャンピオンに!!" +CircuitRaceOngoing = "トゥーンタウン・グランプリをかいさい中だよ!" +CircuitRaceEnd = "本日のトゥーンタウン・グランプリは終了しました。また来週月曜日に!" + +# Trick-or-Treat holiday +TrickOrTreatMsg = 'キミはすでにこのトリートを\nみつけているよ!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "ふーむ。今日のサイバンリストは…" +LawbotBossTempIntro1 = "ほー。トゥーンがサイバンに来ているのか!" +LawbotBossTempIntro2 = "ケンサツ側は強いぞ。" +LawbotBossTempIntro3 = "そしてこちらがベンゴニン。" +LawbotBossTempIntro4 = "ちょっと待てよ…おまえらもトゥーンじゃないか!" +LawbotBossTempJury1 = "バイシンインの選定を始めます。" +LawbotBossHowToGetEvidence = "ショウコを手に入れるにはショウゲンダイに触れてください。" +LawbotBossTrialChat1 = "これからサイバンを始めます。" +LawbotBossHowToThrowPies = "Insertキーを押すと、ベンゴシや「はかり」に向けてショウコを投げることができるよ!" +LawbotBossNeedMoreEvidence = "もっとショウコが必要だよ!" +LawbotBossDefenseWins1 = "ありえない!ヒコクニンが勝つなんて!" +LawbotBossDefenseWins2 = "いや。このハンケツは間違っている!もう一度、新しいサイバンを開こう。" +LawbotBossDefenseWins3 = "はぁ~っ。疲れたので部屋で休むとするか。" +LawbotBossProsecutionWins = "ケンサツ側の勝ちとします。" +LawbotBossReward = "格上げとコグをショウカンできるようになったぞ。" +LawbotBossLeaveCannon = "キャノンから去る" +LawbotBossPassExam = "ほう、シホウシケンを通ったのか。" +LawbotBossTaunts = [ + "%s、ホウテイブジョクザイにあたりますぞ!", + "イギをみとめます!", + "今の内容を記録から消すように。", + "訴えをキャッカします。あなたに悲しみをセンコクします!", + "ホウテイのルールを守るように!", + ] +LawbotBossAreaAttackTaunt = "ホウテイをブジョクするつもりか!" + + +WitnessToonName = "バンピー バンブルベア" +WitnessToonPrepareBattleTwo = "な、なんと。コグのバイシンインしかいないじゃないか!\a急いで、キャノンを使ってトゥーンのバイシンインを送り込もう!\a「はかり」を合わせるのに%d人、必要だよ。" +WitnessToonNoJuror = "がーん。トゥーンのバイシンインが一人もいない!これではサイバンが大変になるぞ。" +WitnessToonOneJuror = "よかった!トゥーンのバイシンインが一人いるね。" +WitnessToonSomeJurors = "いーねー!トゥーンのバイシンインが%d人いるね。" +WitnessToonAllJurors = "すごいね!全員、トゥーンのバイシンインだね。" +WitnessToonPrepareBattleThree = "急いで、ショウゲンダイを触ってショウコをゲットしてね。\aベンゴシや「はかり」に向けてショウコを投げるにはInsertキーを押してね!" +WitnessToonCongratulations = "やったね! すばらしいベンゴだったね。\aサイバンチョーが残していった書類を受け取って!\aこれがあれば、トゥーンガイドのコグのページでコグをショウカンできるぞ。" + +WitnessToonLastPromotion = "\aワオ!コグスーツのレベルが%sになったよ。\aこれ以上は「格上げ」できないんだ。\aでも是非、レジスタンスのためにこれからもがんばってください!" +WitnessToonHPBoost = "\aキミはレジスタンスのために本当に一杯がんばってくれているね。\aトゥーン評議会がキミのゲラゲラポイントをアップさせるよ。おめでとう!" +WitnessToonMaxed = "\aキミはレベル%sのコグのスーツを着ているね。とてもいいよ!\aまた、トゥーンを助けるために戻ってきてくれたことに、トゥーン評議会にかわって、お礼します!" +WitnessToonBonus = "ワンダフル!全てのベンゴシがキゼツしたよ。キミのショウコの重さが%s倍になるぞ!(%s秒間)" + +WitnessToonJuryWeightBonusSingular = { + 6: 'これは大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 7: 'これはとっても大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 8: 'これは今までにない大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'これは大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 7: 'これはとっても大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', + 8: 'これは今までにない大変な事件だ。%d人のバイシンインがいるから、キミのショウコは「%dボーナスウエイト」分の重みがあるね。', +} + +# Cog Summons stuff +IssueSummons = "ショウカン" +SummonDlgTitle = "ショウカンジョーを出す" +SummonDlgButton1 = "コグをショウカン" +SummonDlgButton2 = "コグビルをショウカン" +SummonDlgButton3 = "コグの侵略をショウカン" +SummonDlgSingleConf = "%sをショウカンしますか?" +SummonDlgBuildingConf = "トゥーンビルの近くで%sをショウカンしますか?" +SummonDlgInvasionConf = "%sの侵略をショウカンしますか?" +SummonDlgNumLeft = "あと%s、残ってます" +SummonDlgDelivering = "ショウカン中…" +SummonDlgSingleSuccess = "コグのショウカンに成功しました。" +SummonDlgSingleBadLoc = "ごめんね。ここではコグは呼べません。別の場所を試してね。" +SummonDlgBldgSuccess = "コグのショウカンに成功しました。 %sが%sを乗っ取ります。" +SummonDlgBldgSuccess2 = "コグのショウカンに成功しました。店主もちょっとだけコグに乗っ取ることをOKしました!" +SummonDlgBldgBadLoc = "ごめんね!コグが乗っ取れるトゥーンビルが近くにありません。" +SummonDlgInvasionSuccess = "コグのショウカンに成功しました。コグの侵略がはじまった!" +SummonDlgInvasionBusy = "%sが見つかりません。コグの侵略が終わったら、もう一度試してね。" +SummonDlgInvasionFail = "ごめんね。コグの侵略が失敗しました。" +SummonDlgShopkeeper = "店主 " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": ポーラープレイスへようこそ!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": ちょっとこれを着てサイズを見てくれる?" +PolarPlaceEffect3 = NPCToonNames[3306] + ": このかっこうは " + lTheBrrrgh + "でしか、着れないけどねー" + +# LaserGrid game Labels +LaserGameMine = "ガイコツを探せ!" +LaserGameRoll = "マッチゲーム!" +LaserGameAvoid = "ガイコツを避けてボタンへ!" +LaserGameDrag = "同じ色を3つ並べよう!" +LaserGameDefault = "知らないゲーム" + +# Pinball text +#PinballHiScore = "ハイスコア: %d %s\n" +#PinballYourBestScore = "ベストスコア: %d\n" +#PinballScore = "スコア: %d x %d:%d" +PinballHiScore = "ハイスコア: %s\n" +PinballHiScoreAbbrev = "…" +PinballYourBestScore = "ベストスコア: \n" +PinballScore = "スコア: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "ちょいギャグの木" +GagTreeJugglingBalls = "ジャグリングボール" +StatuaryFountain = "噴水" +StatuaryDonald = "ドナルドの像" +StatuaryMinnie = "ミニーの像" +StatuaryMickey1 = "ミッキーの像1" +StatuaryMickey2 = "ミッキーの像2" +StatuaryToonStatue = "トゥーンの像" +StatuaryToon = "Toon Statue" +StatuaryToonWave = "Toon Wave Statue" +StatuaryToonVictory = "Toon Victory Statue" +StatuaryToonCrossedArms = 'Toon Authority Statue' +StatuaryToonThinking = 'Toon Embrace Statue' +StatuaryMeltingSnowman = 'Melting Snowman' +StatuaryGardenAccelerator = "ヨクソダーツ" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['レッド','オレンジ','バイオレット','ブルー','ピンク','イエロー','ホワイト','グリーン'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'デイジー', + 50: 'チューリップ', + 51: 'カーネーション', + 52: 'リリー', + 53: 'ダフォディル', + 54: 'パンジー', + 55: 'ペチュニア', + 56: 'ローズ', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('スクールデイジー', + 'レイジーデイジー', + 'サマーデイジー', + 'リフレッシュデイジー', + 'ウーピーデイジー', + 'アップジーデイジー', + 'クレイジーデイジー', + 'ヘイジーデイジー', + ), + 50: ('ワンリップ', + 'ツーリップ', + 'スリーリップ', + ), + 51: ('ワットインカーネーション', + 'インスタントカーネーション', + 'ハイブリッドカーネーション', + 'サイドカーネーション', + 'モデルカーネーション', + ), + 52: ('リリーオブジアリー', + 'リリーパッド', + 'タイガーリリー', + 'リバードリリー', + 'チリーリリー', + 'シリーリリー', + 'タブリリー', + 'ディリーリリー', + ), + 53: ('ラフォディル', + 'ダフィーディル', + 'ジラフォディル', + 'タイムアンドアハーフォディル', + ), + 54: ('ダンディーバンジー', + 'チンパンジー', + 'ポッツェンパンジー', + 'マルチパンジー', + 'スマーティーパンジー' + ), + 55: ('カーペチュニア', + 'プラトーニア', + ), + 56: ("サマーズラストローズ", + 'コーンローズ', + 'ティントローズ', + 'スティンキングローズ', + 'イスティラローズ', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "(スズ)", + 1 : "(銅)", + 2 : "(銀)", + 3 : "(金)", + } +WateringCanNameDict = { + 0 : "(小)", + 1 : "(中)", + 2 : "(大)", + 3 : "(特大)", + } +GardeningPlant = "植物" +GardeningWater = "水" +GardeningRemove = "やめる" +GardeningPick = "しゅう\nかく" +GardeningFull = "いっぱい" +GardeningSkill = "スキル" +GardeningWaterSkill = "水スキル" +GardeningShovelSkill = "ショベルスキル" +GardeningNoSkill = "スキルアップなし" +GardeningPlantFlower = "花を\nうえる" +GardeningPlantTree = "木を\nうえる" +GardeningPlantItem = "アイテムを\nうえる" +PlantingGuiOk = "うえる" +PlantingGuiCancel = "キャンセル" +PlantingGuiReset = "リセット" +GardeningChooseBeans = "うえたいジェリービーンを選んでね。" +GardeningChooseBeansItem = "うえたいジェリービーンか\nアイテムを選んでね。" +GardeningChooseToonStatue = "Choose the toon you want to create a statue of." +GardenShovelLevelUp = "おめでとう!%(shovel)sをゲット! %(oldbeans)dのビーンフラワーをマスターしました。次に進むには%(newbeans)dのビーンフラワーを取ろう。" +GardenShovelSkillLevelUp = "おめでとう!%(oldbeans)dのビーンフラワーをマスターしました。次に進むには%(newbeans)dのビーンフラワーを取ろう。" +GardenShovelSkillMaxed = "すごいねー!キミのショベルスキルが最大になったよ!" + +GardenWateringCanLevelUp = "おめでとう!あたらしいジョウロをゲットしたよ!" +GardenMiniGameWon = "やったね!ちゃんと水をあげられたね!" +ShovelTin = "ショベル(スズ)" +ShovelSteel = "ショベル(銅)" +ShovelSilver = "ショベル(銀)" +ShovelGold = "ショベル(金)" +WateringCanSmall = "ジョウロ(小)" +WateringCanMedium = "ジョウロ(中)" +WateringCanLarge = "ジョウロ(大)" +WateringCanHuge = "ジョウロ(特大)" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('レッド', 'グリーン', 'オレンジ','バイオレット','ブルー','ピンク','イエロー', + 'シアン','シルバー') +PlantItWith = "%sを与える" +MakeSureWatered = " まずきちんとすべての植木に水を与えましょう。" +UseFromSpecialsTab = "ガーデンページのスペシャルのタブから使いましょう。" +UseSpecial = "スペシャルを使う" +UseSpecialBadLocation = 'キミのガーデンでしか使えません。' +UseSpecialSuccess = 'うまくいったね!植木が育ちました。' +ConfirmWiltedFlower = "%(plant)sがしぼんでいます。バスケットにも残らないし、スキルも上がりません。" +ConfirmUnbloomingFlower = "まだ%(plant)sは咲いてませんが、つみとりますか?バスケットにも残らないしスキルも上がりません。" +ConfirmNoSkillupFlower = "%(plant)sをつみとって、キミの花のバスケットに入れますか?残念だけどスキルは上がらないよ。" +ConfirmSkillupFlower = "%(plant)sをしゅうかくして、キミの花のバスケットに入れますか?またスキルが上がるよ。" +ConfirmMaxedSkillFlower = "%(plant)sをしゅうかくして、キミの花のバスケットに入れますか?もうスキルが最大だからスキルは上がらないよ。" +ConfirmBasketFull = "キミのバスケットは一杯だよ。花を売るにはテオシグルマに行こう。" +ConfirmRemoveTree = "%(tree)sを抜いてもいいかな?" +ConfirmWontBeAbleToHarvest = "もしこの木を抜くと、高いレベルの木からギャグを育てることができなくなるよ。" +ConfirmRemoveStatuary = "本当に%(item)sがなくなるけど、いいかな??" +ResultPlantedSomething = "おめでとう!%sを植えました。" +ResultPlantedSomethingAn = "おめでとう!%sを植えました。" +ResultPlantedNothing = "うまくいかなかったね。違うジェリービーンの組み合わせを試してね。" + +GardenGagTree = "ギャグの木" +GardenUberGag = "レベル7ギャグ" + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s ジェリービーン" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "a %s ジェリービーン" % BeanColorWords[beanTuple[0]] + else: + retval += 'a' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " そして %s ジェリービーン" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "マジックビーン" +GardenTextMagicBeansB = "ふつうのビーン" +GardenSpecialDiscription = "この文章はどのようにガーデンスペシャルを使うかを説明するものです。" +GardenSpecialDiscriptionB = "この文章はどのようにガーデンスペシャルを使うかを説明するものです。" +GardenTrophyAwarded = "ワオ!花、%s輪(%s輪のうち)をゲット!トロフィーとゲラゲラポイントアップ!" +GardenTrophyNameDict = { + 0 : "テオシグルマ", + 1 : "ショベル", + 2 : "フラワー", + 3 : "ジョウロ", + 4 : "サメ", + 5 : "メカジキ", + 6 : "シャチ", + } +SkillTooLow = "スキルが\n足りません" +NoGarden = "ガーデンが\nありません" + +def isVowelStart(str): + """ + A utility function to return true if the first letter in the str is a vowel + """ + retval = False + if str and len(str)>0: + vowels = ['A','E','I','O','U'] + firstLetter = str.upper()[0:1] + if firstLetter in vowels: + retval = True + return retval + +def getResultPlantedSomethingSentence( flowerName): + """ + Returns a gramatically correct sentence when you've successfully planted something + """ + if isVowelStart(flowerName): + retval = ResultPlantedSomethingAn % flowerName + else: + retval = ResultPlantedSomething % flowerName + + return retval + + +#Stuff for trolley metagame +TravelGameTitle = "『ターン・テーブル』" +TravelGameInstructions = "キミのヒミツのゴールにたどり着くようにとうひょう数を決めよう。決めたら“とうひょう”ボタンをクリック。うまくゆけばボーナスをゲット!他のゲームも頑張るととうひょうできる数が増えるよ。" +TravelGameRemainingVotes = "とうひょうできる数:" +TravelGameUse = "つかう" +TravelGameVotesWithPeriod = "とうひょう" +TravelGameVotesToGo = "のこり" +TravelGameVoteToGo = "のこり" +TravelGameUp = "もっと!" +TravelGameDown = "へらす。" +TravelGameVoteWithExclamation = "とうひょう!" +TravelGameWaitingChoices = "他のプレイヤーがとうひょうするのを待っています..." +# cross the bridge later when the first choice is different for each node, +# e.g. NorthWest, NorthEast, etc. +TravelGameDirections = ['上へ', '下へ'] +TravelGameTotals = 'ごうけい ' +TravelGameReasonVotesPlural = 'とうひょう数%(numVotes)dで、トロリーは%(dir)s!' +TravelGameReasonVotesSingular = 'とうひょう数%(numVotes)dで、トロリーは%(dir)s!' +TravelGameReasonPlace = '%(name)sとどうてん!トロリーは%(dir)sすすむよ!' +TravelGameReasonRandom = 'トロリーは%(dir)sかってにすすむよ。' +TravelGameOneToonVote = "%(name)sが%(dir)s\nすすめるように%(numVotes)sとうひょうしました。" +TravelGameBonusBeans = "%(numBeans)dボーナスジェリービーン!" +TravelGamePlaying = 'つぎのトロリーゲームは%(game)sだよ。' +TravelGameGotBonus = '%(name)sが%(numBeans)sボーナスジェリービーンをゲット!' +TravelGameNoOneGotBonus = "だれもヒミツのゴールにたどりつけなかったよ。みんなジェリービーン1つずつゲット。" +TravelGameConvertingVotesToBeans = "とうひょうをジェリービーンにこうかんする" +TravelGameGoingBackToShop ="プレイヤーが1人しかいないからグーフィーのギャグショップへ…。" + +PairingGameTitle = "トゥーンしんけいすいじゃく" +PairingGameInstructions = "Deleteキーでカードをオープン。2枚そろえば得点。ボーナスマークは追加ポイントに!なるべく少ない回数でクリアしよう!" +PairingGameInstructionsMulti = "Deleteキーでカードをオープン。Controlキーで他のプレイヤーにじゅんばんを知らせよう。2枚そろえば得点。ボーナスマークは追加ポイントに!なるべく少ない回数でクリアしよう" +PairingGamePerfect = 'パーフェクト!!' +PairingGameFlips = 'オープン回数:' +PairingGamePoints = 'ポイント:' + +TrolleyHolidayStart = "『ターン・テーブル』が始まるよ!2人いじょうでトロリーに乗ってね。" +TrolleyHolidayOngoing = "ようこそ!『ターン・テーブル』をかいさいちゅうだよ。" +TrolleyHolidayEnd = "『ターン・テーブル』をしゅうりょうします!また来週ね!!" + +TrolleyWeekendStart = "『ターン・テーブル』ウィークが始まるよ!2人いじょうでトロリーに乗ってね。" +TrolleyWeekendEnd = "『ターン・テーブル』ウィークをしゅうりょうします。" + +VineGameTitle = "『ジャングル・ジャンプ』" +VineGameInstructions = "せいげん時間までにゴールを目指そう!矢印キーの上(↑)と下(↓)で高さをちょうせつ。右(→)と左(←)で向きを変えてジャンプ!低いところからだとスピードアップ。バナナを集めながらコウモリとクモのこうげきをかわそう。" + +# Make sure the golf text matches up with GolfGlobals.py +GolfCourseNames = { + 0: "ウォーク・イン・パー", + 1: "ホール・サム・ファン", + 2: "ザ・ホール・カブードル" + } + +GolfHoleNames = { + 0: 'ホール・イン・ウィン', + 1: 'ノー・パット・アバウト・イット', + 2: 'ダウン・ザ・ハッチ', + 3: 'シーイング・グリーン', + 4: 'ホット・リンクス', + 5: 'ピーナツ・パター', + 6: 'スィング・ア・ロング', + 7: 'アフタヌーン・ティー', + 8: 'ホール・イン・ファン', + 9: 'ロックンロール・イン', + 10: 'ボギー・ナイツ', + 11: 'ティー・オフ・タイム', + 12: 'ホーリー・マカレル!', + 13: 'ワン・リトル・バーディー', + 14: 'ザ・ドライブ・イン', + 15: 'スィング・タイム', + 16: 'ホール・オン・ザ・レンジ', + 17: 'セカンド・ウィンド', + 18: 'ホール・イン・ウィン-2', + 19: 'ノー・パット・アバウト・イット-2', + 20: 'ダウン・ザ・ハッチ-2', + 21: 'シーイング・グリーン-2', + 22: 'ホット・リンクス-2', + 23: 'ピーナツ・パター-2', + 24: 'スィング・ア・ロング-2', + 25: 'アフタヌーン・ティー-2', + 26: 'ホール・イン・ファン-2', + 27: 'ロックンロール・イン-2', + 28: 'ボギー・ナイツ-2', + 29: 'ティー・オフ・タイム-2', + 30: 'ホーリー・マカレル!-2', + 31: 'ワン・リトル・バーディー-2', + 32: 'ザ・ドライブ・イン-2', + 33: 'スィング・タイム-2', + 34: 'ホール・オン・ザ・レンジ-2', + 35: 'セカンド・ウィンド-2', + } + +GolfHoleInOne = "ホール・イン・ワン" +GolfCondor = "コンドル" # four Under Par +GolfAlbatross = "アルバトロス" # three under par +GolfEagle = "イーグル" # two under par +GolfBirdie = "バーディー" # one under par +GolfPar = "パー" +GolfBogey = "ボギー" # one over par +GolfDoubleBogey = "ダブル・ボギー" # two over par +GolfTripleBogey = "トリプル・ボギー" # three over par + +GolfShotDesc = { + -4: GolfCondor, + -3: GolfAlbatross, + -2: GolfEagle, + -1: GolfBirdie, + 0: GolfPar, + 1: GolfBogey, + 2: GolfDoubleBogey, + 3: GolfTripleBogey, + } + + +from toontown.golf import GolfGlobals + +CoursesCompleted = "コース、1人で完了" +CoursesUnderPar = "コース、アンダー・パー" +HoleInOneShots = "ホール・イン・ワン" +EagleOrBetterShots = "イーグル以上" +BirdieOrBetterShots = "バーディー以上" +ParOrBetterShots = "パー以上" +MultiPlayerCoursesCompleted = "コース パーティーで完了" +TwoPlayerWins = "2プレイヤーで勝利" +ThreePlayerWins = "3プレイヤーで勝利" +FourPlayerWins = "4プレイヤーで勝利" +CourseZeroWins = GolfCourseNames[0] + " Wins" +CourseOneWins = GolfCourseNames[1] + " Wins" +CourseTwoWins = GolfCourseNames[2] + " Wins" + +GolfHistoryDescriptions = [ + CoursesCompleted, + CoursesUnderPar, + HoleInOneShots, + EagleOrBetterShots, + BirdieOrBetterShots, + ParOrBetterShots, + MultiPlayerCoursesCompleted, + CourseZeroWins, + CourseOneWins, + CourseTwoWins, + ] + +GolfTrophyDescriptions = [ + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][0]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1]) + ' ' + CoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][2]) + ' ' + CoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][1]) + ' ' + CoursesUnderPar, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][2]) + ' ' + CoursesUnderPar, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][1]) + ' ' + HoleInOneShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][2]) + ' ' + HoleInOneShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][1]) + ' ' + EagleOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][2]) + ' ' + EagleOrBetterShots, + + + + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][1]) + ' ' + BirdieOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][2]) + ' ' + BirdieOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][1]) + ' ' + ParOrBetterShots, + str(GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][2]) + ' ' + ParOrBetterShots, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][1]) + ' ' + MultiPlayerCoursesCompleted, + str(GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][2]) + ' ' + MultiPlayerCoursesCompleted, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][1]) + ' ' + CourseZeroWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][2]) + ' ' + CourseZeroWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][1]) + ' ' + CourseOneWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][2]) + ' ' + CourseOneWins, + + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][1]) + ' ' + CourseTwoWins, + str(GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][2]) + ' ' + CourseTwoWins, + +] + +GolfCupDescriptions = [ + str(GolfGlobals.TrophiesPerCup) + "個のトロフィー", + str(GolfGlobals.TrophiesPerCup * 2) + "個のトロフィー", + str(GolfGlobals.TrophiesPerCup * 3) + "個のトロフィー", +] + +GolfAvReceivesHoleBest = "%(name)sが%(hole)sでホールレコードをこうしん!" +GolfAvReceivesCourseBest = "%(name)sが%(course)sのコースレコードをこうしん!" +GolfAvReceivesCup = "%(name)sが%(cup)s杯をかくとく!賞品はゲラゲラブーストだ!!" +GolfAvReceivesTrophy = "%(name)sが%(award)sのトロフィーをかくとく!" +GolfRanking = "ランキング: \n" +GolfPowerBarText = "%(power)s%%" +GolfChooseTeeInstructions = "左・右の矢印キーでティーの位置をへんこう。\nCtrlキーで決定。" +GolfWarningMustSwing = "注意: 次のスィングではちゃんとCtrlキーを使ってね。" +GolfAimInstructions = "左・右の矢印キーで方向を決めて、\nCtrlキーを長押ししてスィングの強さをコントロールしよう。" +GolferExited = "%sがゴルフコースから出ました。" +GolfPowerReminder = "ボールをもっと遠くに打つには、\nCtrlキーをもっと長く押し続けよう。" + + +# GolfScoreBoard.py +GolfPar = "パー" +GolfHole = "ホール" +GolfTotal = "トータル" +GolfExitCourse = "コースから出る" +GolfUnknownPlayer = "" + +# GolfPage.py +GolfPageTitle = "ゴルフ" +GolfPageTitleCustomize = "ゴルフのカスタマイズ" +GolfPageTitleRecords = "自己ベスト" +GolfPageTitleTrophy = "ゴルフ・トロフィー" +GolfPageCustomizeTab = "カスタマイズ" +GolfPageRecordsTab = "レコード" +GolfPageTrophyTab = "トロフィー" +GolfPageTickets = "チケット: " +GolfPageConfirmDelete = "アクセサリーを削除する?" +GolfTrophyTextDisplay = "トロフィー%(number)s個 : %(desc)s" +GolfCupTextDisplay = "カップ%(number)s個 : %(desc)s" +GolfCurrentHistory = "現在%(historyDesc)s : %(num)s" +GolfTieBreakWinner = "%(name)sがランダムでタイブレークに勝利!" +GolfSeconds = " - %(time).2f秒" +GolfTimeTieBreakWinner = "%(name)sが最短時間でタイブレークに勝利!!" + + + +RoamingTrialerWeekendStart = "Tour Toontown is starting! Free players may now enter any neighborhood!" +RoamingTrialerWeekendOngoing = "Welcome to Tour Toontown! Free players may now enter any neighborhood!" +RoamingTrialerWeekendEnd = "That's all for Tour Toontown." + +# change double if ToontownBattleGlobals.getMoreXpHolidayMultiplier() changes +MoreXpHolidayStart = "Good news! Exclusive Test Toon double gag experience time has started." +MoreXpHolidayOngoing = "Welcome! Exclusive Test Toon double gag experience time is currently ongoing." +MoreXpHolidayEnd = "Exclusive Test Toon double gag experience time has ended. Thanks for helping us Test things!" + + +LogoutForced = "You have done something wrong\n and are being logged out automatically,\n additionally your account may be frozen.\n Try going on a walk outside, it is fun." + +# DistributedCountryClub.py +CountryClubToonEnterElevator = "%s \nhas jumped in the golf kart." +CountryClubBossConfrontedMsg = "%s is battling the Club President!" + +# DistributedElevatorFSM.py +ElevatorBlockedRoom = "All challenges must be defeated first." + +# DistributedMolefield.py +MolesLeft = "Moles Left: %d" +MolesInstruction = "Mole Stomp!\nJump on the red moles!" +MolesFinished = "Mole Stomp successful!" +MolesRestarted = "Stomp Failed! Restarting..." + +# DistributedGolfGreenGame.py +BustACogInstruction = "Remove the cog ball!" +BustACogExit = "Exit for Now" +BustACogHowto = "How to Play" +BustACogFailure = "Out of Time!" +BustACogSuccess = "Success!" + +# bossbot golf green games +GolfGreenGameScoreString = "Puzzles Left: %s" +GolfGreenGamePlayerScore = "Solved %s" +GolfGreenGameBonusGag = "You won %s!" +GolfGreenGameGotHelp = "%s solved a Puzzle!" + +GolfGreenGameDirections = "Shoot balls using the the mouse\n\n\nMatching three of a color causes the balls to fall\n\n\nRemove all Cog balls from the board" + +# DistributedMaze.py +enterHedgeMaze = "Race through the Hedge Maze\n for a laff bonus!" +toonFinishedHedgeMaze = "%s \n finished in %s place!" +hedgeMazePlaces = ["first","second","third","Fourth"] +mazeLabel = "Maze Race!" + +# Boarding Party +BoardingPartyReadme = "グループを設定する?" +BoardingGroupHide = 'Hide' +BoardingGroupShow = 'Show Boarding Group' +BoardingPartyInform = "他のトゥーンをクリックして、一緒にエレベーターに乗るグループのメンバーに招待しよう。\nメンバーは%s人までだよ。" +BoardingPartyTitle = 'Boarding Group' +QuitBoardingPartyLeader = 'Disband' +QuitBoardingPartyNonLeader = 'Leave' +QuitBoardingPartyConfirm = 'Are you sure you want to quit this Boarding Group?' +BoardcodeMissing = 'Your group cannot board because something was missing.' +BoardcodeMinLaffLeader = 'Your group cannot board because you have less than %s laff points.' +BoardcodeMinLaffNonLeaderSingular = 'Your group cannot board because %s has less than %s laff points.' +BoardcodeMinLaffNonLeaderPlural = 'Your group cannot board because %s have less than %s laff points.' +BoardcodePromotionLeader = 'Your group cannot board because you do not have enough promotion merits.' +BoardcodePromotionNonLeaderSingular = 'Your group cannot board because %s does not have enough promotion merits.' +BoardcodePromotionNonLeaderPlural = 'Your group cannot board because %s do not have enough promotion merits.' +BoardcodeSpace = 'Your group cannot board because there is not enough space.' +BoardcodeBattleLeader = 'Your group cannot board beacause you are in battle.' +BoardcodeBattleNonLeaderSingular = 'Your group cannot board beacause %s is in battle.' +BoardcodeBattleNonLeaderPlural = 'Your group cannot board beacause %s are in battle.' +BoardingInviteMinLaffInviter = 'You need %s Laff Points before being a member of this Boarding Group.' +BoardingInviteMinLaffInvitee = '%s needs %s Laff Points before being a member of this Boarding Group.' +BoardingInvitePromotionInviter = 'You need to earn a promotion before being a member of this Boarding Group.' +BoardingInvitePromotionInvitee = '%s needs to earn a promotion before being a member of this Boarding Group.' +BoardingGo = 'GO' +And = 'and' +BoardingGoingTo = 'Going To:' + +# DistributedBossbotBoss.py +BossbotBossName = "C.E.O." +BossbotRTWelcome = "You toons will need different disguises." +BossbotRTRemoveSuit = "First take off your cog suits..." +BossbotRTFightWaiter = "and then fight these waiters." +BossbotRTWearWaiter = "Good Job! Now put on the waiters' clothes." +BossbotBossPreTwo1 = "What's taking so long? " +BossbotBossPreTwo2 = "Get cracking and serve my banquet!" +BossbotRTServeFood1 = "Hehe, serve the food I place on these conveyor belts." +BossbotRTServeFood2 = "If you serve a cog three times in a row it will explode." +BossbotResistanceToonName = "Good ol' Gil Giggles" +BossbotPhase3Speech1 = "What's happening here?!" +BossbotPhase3Speech2 = "These waiters are toons!" +BossbotPhase3Speech3 = "Get them!!!" +BossbotPhase4Speech1 = "Hrrmmpph. When I need a job done right..." +BossbotPhase4Speech2 = "I'll do it myself." +BossbotRTPhase4Speech1 = "Good Job! Now squirt the C.E.O. with the water on the tables..." +BossbotRTPhase4Speech2 = "or use golf balls to slow him down." +BossbotPitcherLeave = "Leave Bottle" +BossbotPitcherLeaving = "Leaving Bottle" +BossbotPitcherAdvice = "Use the left and right keys to rotate.\nHold down Ctrl increase power.\nRelease Ctrl to fire." +BossbotGolfSpotLeave = "Leave Golf Ball" +BossbotGolfSpotLeaving = "Leaving Golf Ball" +BossbotGolfSpotAdvice = "Use the left and right keys to rotate.\nCtrl to fire." +BossbotRewardSpeech1 = "No! The Chairman won't like this." +BossbotRewardSpeech2 = "Arrrggghhh!!!!" +BossbotRTCongratulations = "You did it! You've demoted the C.E.O.!\aHere, take these pink slips the C.E.O. left behind.\aWith it you'll be able to fire Cogs in a battle.""" +BossbotRTLastPromotion = "\aWow, you've reached level %s on your Cog Suit!\aCogs don't get promoted higher than that.\aYou can't upgrade your Cog Suit anymore, but you can certainly keep working for the Resistance!" +BossbotRTHPBoost = "\aYou've done a lot of work for the Resistance.\aThe Toon Council has decided to give you another Laff point. Congratulations!" +BossbotRTMaxed = "\aI see that you have a level %s Cog Suit. Very impressive!\aOn behalf of the Toon Council, thank you for coming back to defend more Toons!" +GolfAreaAttackTaunt = "Fore!" +OvertimeAttackTaunts = [ "It's time to reorganize.", + "Now let's downsize."] + +#ElevatorDestination Names +ElevatorBossBotBoss = "C.E.O Battle" +ElevatorBossBotCourse0 = "The Front Three" +ElevatorBossBotCourse1 = "The Middle Six" +ElevatorBossBotCourse2 = "The Back Nine" +ElevatorCashBotBoss = "C.F.O Battle" +ElevatorCashBotMint0 = "Coin Mint" +ElevatorCashBotMint1 = "Dollar Mint" +ElevatorCashBotMint2 = "Bullion Mint" +ElevatorSellBotBoss = "Sellbot Battle" +ElevatorSellBotFactory0 = "Front Entrance" +ElevatorSellBotFactory1 = "Back Entrance" +ElevatorLawBotBoss = "Chief Justice Battle" +ElevatorLawBotCourse0 = "Office A" +ElevatorLawBotCourse1 = "Office B" +ElevatorLawBotCourse2 = "Office C" +ElevatorLawBotCourse3 = "Office D" + + + +# CatalogNameTagItem.py +DaysToGo = "Wait\n%s Days" + +# DistributedIceGame.py +IceGameTitle = "Ice Slide" +IceGameInstructions = "Get as close to the center by the end of the second round. Use arrow keys to change direction and force. Press Ctrl to launch your toon. Hit barrels for extra points and avoid the TNT!" +IceGameInstructionsNoTnt = "Get as close to the center by the end of the second round. Use arrow keys to change direction and force. Press Ctrl to launch your toon. Hit barrels for extra points." +IceGameWaitingForPlayersToFinishMove = "Waiting for other players..." +IceGameWaitingForAISync = "Waiting for other players..." +IceGameInfo= "Match %(curMatch)d/%(numMatch)d, Round %(curRound)d/%(numRound)d" +IceGameControlKeyWarning="Remember to press the Ctrl key!" + + +#DistributedPicnicTable.py +PicnicTableJoinButton = "参加する" +PicnicTableObserveButton = "みるだけ" +PicnicTableCancelButton = "キャンセル" +PicnicTableTutorial = "遊び方" +PicnicTableMenuTutorial = "どのゲームの説明?" +PicnicTableMenuSelect = "どのゲームをプレイする?" + +#DistributedChineseCheckers.py +ChineseCheckersGetUpButton = "席を立つ" +ChineseCheckersStartButton = "ゲーム開始!" +ChineseCheckersQuitButton = "ゲーム終了!" +ChineseCheckersIts = "It's " + +ChineseCheckersYourTurn = "キミの番だよ" +ChineseCheckersGreenTurn = "緑の番だよ" +ChineseCheckersYellowTurn = "黄色の番だよ" +ChineseCheckersPurpleTurn = "紫の番だよ" +ChineseCheckersBlueTurn = "青の番だよ" +ChineseCheckersPinkTurn = "ピンクの番だよ" +ChineseCheckersRedTurn = "赤の番だよ" + +ChineseCheckersColorG = "キミは緑だよ" +ChineseCheckersColorY = "キミは黄色だよ" +ChineseCheckersColorP = "キミは紫だよ" +ChineseCheckersColorB = "キミは青だよ" +ChineseCheckersColorPink = "キミはピンクだよ" +ChineseCheckersColorR = "キミは赤だよ" +ChineseCheckersColorO = "キミはみているだけだよ" + +ChineseCheckersYouWon = "おめでとう!ダイヤモンドゲームに勝利!!" +ChineseCheckers = "ダイヤモンドゲーム" +ChineseCheckersGameOf = " が勝ったゲーム: " + +#GameTutorials.py +ChineseTutorialTitle1 = "ゲームについて" +ChineseTutorialTitle2 = "遊び方" +ChineseTutorialPrev = "前のページ" +ChineseTutorialNext = "次のページ" +ChineseTutorialDone = "閉じる" +ChinesePage1 = "『ダイヤモンドゲーム』は、下の三角形にあるコマを他のプレイヤーより先に上の三角形に移動させるゲームだよ。先に移動し終わったプレイヤーが勝ちだよ!" +ChinesePage2 = "順番に自分の色のコマを移動します。コマはとなりあった穴か、他のコマを一つだけとびこえて空いている穴に移動(ホップ)してもOK。コマの移動先でもホップの条件が続いてそろう時は、遠くまで移動できるチェーンホップが有効だよ!" + +CheckersPage1 = "『チェッカー』は、相手のコマの動きを止めると勝ちなんだ。そのためには、相手のコマを全部とってしまうか、相手のコマが進んでいいところを先にうばってしまう方法があるよ。" +CheckersPage2 = "順番に自分の色のコマを動かします。各コマは、マスが空いている限りななめに一コマ、または前に一コマずつ進めます。キングも同じ動きと、さらに後ろに戻る事もできるんだ。" +CheckersPage3 = "ななめに相手のコマをとびこえてその先の空いているマスに進むと、とびこえた相手のコマをとることができるよ(ジャンプ)。もしも自分の番の時に相手のコマがとれる(ジャンプできる)時は、必ず一つはとらなければならないから注意してね。連続のジャンプは同じコマでだけできるからがんばってみよう。" +CheckersPage4 = "全てのコマは、ボードのさいごの列についたらキングにかわるんだ!キングになったばかりのコマは、次の順番まではジャンプできないよ。それから、キングは全ての方向に動けるし、ジャンプの途中で方向をかえる事もできるからためしてね!" + + + +#DistributedCheckers.py +CheckersGetUpButton = "席を立つ" +CheckersStartButton = "ゲーム開始!" +CheckersQuitButton = "ゲーム終了!" + +CheckersIts = "次は" +CheckersYourTurn = "キミの番だよ" +CheckersWhiteTurn = "シロの番だよ" +CheckersBlackTurn = "クロの番だよ" + +CheckersColorWhite = "キミはシロだよ" +CheckersColorBlack = "キミはクロだよ" +CheckersObserver = "けんがくちゅう…" +RegularCheckers = "チェッカー" +RegularCheckersGameOf = " が勝ったゲーム: " +RegularCheckersYouWon = "おめでとう!チェッカーに勝利!" + +MailNotifyNewItems = "メールがとどいたよ!" +MailNewMailButton = "メール" +MailSimpleMail = "おしらせ" +MailFromTag = "送り主: %s" + +# MailboxScreen.py +InviteInvitation = "the invitation" +InviteAcceptInvalidError = "この招待はもう無効です。" +InviteAcceptPartyInvalid = "このグループはもうかいさんしました。" +InviteAcceptAllOk = "キミの返信が相手にとどきました。" +InviteRejectAllOk = "The host has been informed that you declined the invitation." + + +# Note Months is 1 based, to correspond to datetime +Months = { + 1: "JANUARY", + 2: "FEBRUARY", + 3: "MARCH", + 4: "APRIL", + 5: "MAY", + 6: "JUNE", + 7: "JULY", + 8: "AUGUST", + 9: "SEPTEMBER", +10: "OCTOBER", +11: "NOVEMBER", +12: "DECEMBER" +} + +# Note 0 for Monday to match datetime +DayNames = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") +DayNamesAbbrev = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN") + +# numbers must match holiday ids in ToontownGlobals +HolidayNamesInCalendar = { + 1: ("Summer Fireworks", "Celebrate Summer with a fireworks show every hour in each playground!"), + 2: ("New Year Fireworks", "Happy New Year! Enjoy a fireworks show every hour in each playground!"), + 3: ("Bloodsucker Invasion", "Happy Halloween! Stop the Bloodsucker Cogs from invading Toontown!"), + 4: ("Winter Holidays Decor", "Celebrate the Winter Holidays with Toontastic trees and streetlights!"), + 5: ("Skelecog Invasion", "Stop the Skelecogs from invading Toontown!"), + 6: ("Mr. Hollywood Invasion", "Stop the Mr. Hollywood Cogs from invading Toontown!"), + 7: ("Fish Bingo", "Fish Bingo Wednesday! Everyone at the pond works together to complete the card before time runs out."), + 8: ("Toon Species Election", "Vote on the new Toon species! Will it be Goat? Will it be Pig?"), + 9: ("Black Cat Day", "Happy Halloween! Create a Toontastic Black Cat Toon - Today Only!"), + 13: ("Trick or Treat", "Happy Halloween! Trick or treat throughout Toontown to get a nifty Halloween pumpkin head reward!"), + 14: ("Grand Prix", "Grand Prix Monday at Goofy Speedway! To win, collect the most points in three consecutive races!"), + 17: ("Trolley Tracks", "Trolley Tracks Thursday! Board any Trolley with two or more Toons to play."), + 19: ("Silly Saturdays", "Saturdays are silly with Fish Bingo, Grand Prix, and Trolley Tracks throughout the day!"), + 24: ("Ides of March", "Beware the Ides of March! Stop the Backstabber Cogs from invading Toontown!"), + 26: ("Halloween Decor", "Celebrate Halloween as spooky trees and streetlights transform Toontown!"), + } +UnknownHoliday = "Unknown Holiday %d" diff --git a/toontown/src/toonbase/japanese/TTLocalizer_Property.py b/toontown/src/toonbase/japanese/TTLocalizer_Property.py new file mode 100644 index 0000000..1c91c5b --- /dev/null +++ b/toontown/src/toonbase/japanese/TTLocalizer_Property.py @@ -0,0 +1,287 @@ +#battle/PlayByPlayText.py +PBPTonscreenText = 0.15 + +#battle/RewardPanel.py +RPdirectFrame = (2.15,1,0.75) +RPtrackLabels = 0.043 +RPmeritBarLabels = 0.13 + +#building/DistributedHQInterior.py +DHtoonName = 0.9 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.8 + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.075 +SCLdgSign = 0.08 + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 1 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 2 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.045 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.13) +CSgiftToggle = 0.08 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.045 +CISCtopLevelOverlap = 0. + +#chat/ChatManager.py +CMnormalButton = 0.05 +CMscButtonPos = (-1.084, 0, 0.928) +CMscButton = 0.05 +CMwhisperFrame = 0.05 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 18 +CMunpaidChatWarning = 0.06 +CMunpaidChatWarning_text_z = 0.23 +CMpayButton = 0.06 +CMpayButton_pos_z = -0.08 +CMopenChatWarning = 0.06 +CMactivateChat = 0.06 +CMchatActivated = 0.06 +CMNoPasswordContinue_z = -0.23 + +#estate/houseDesign.py +HDhelpText = 0.8 +HDatticButton = 1.0 +HDroomButton = 1.0 +HDtrashButton = 1.0 +HDscrolledList = 0.1 + +#fishing/BingoCardGui.py +BCGjpText = (0.035) +BCGjpTextWordwrap = 15.5 +BCGnextGame = 1.71 + +#fishing/FishSellGUI.py +FSGokButton = 0.06 +FSGcancelButton = 0.06 + +#fishing/FishPanel.py +FPnewEntry = 0.08 +FPnewRecord = 0.08 + +#fishing/GenusPanel.py +GPgenus = 0.045 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.035 +FLPsecrets = 0.035 +FLPsecretsPos = (0.125, 0.0, 0.14) + +#friends/FriendInviter.py +FIstopButton = 0.05 +FIdialog = 0.06 + +#hood/EstateHood.py +EHpopupInfo = .07 + +#login/AvatarChoice.py +ACplayThisToon = 0.11 +ACmakeAToon = 0.12 +ACsubscribersOnly = 0.115 +ACdeleteWithPassword = 0.05 + +#login/AvatarChooser.py +ACtitle = 0.125 +ACquitButton = 0.1 +AClogoutButton = 0.09 +ACquitButton_pos = -0.035 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.055 +MRPinstructionsText = 0.045 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.055 +MPMtooSlow = 0.055 +MPMtooFast = 0.055 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.07 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.08 + +#makeatoon/NameShop.py +NSmaxNameWidth = 7.5 +NSdirectScrolleList = None +NSmakeLabel = 0.1 +NSmakeCheckBox = 0.5 +NSnameEntry = 0.06 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.02 +NSnameResult = 0.08 +NStypeName = 0.07 +NSnewName = 0.08 +NScolorPrecede = True + +#Stuff for trolley metagame +TravelGameBonusBeansSize = 0.35 + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.18 +MATenterBodyShop = 0.18 +MATenterColorShop = 0.18 +MATenterClothesShop = 0.16 +MATenterNameShop = 0.15 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.08 + +#pets/PetAvatarPanel.py +PAPfeed = 0.3 +PAPcall = 0.3 +PAPowner = 0.25 +PAPscratch = 0.3 +PAPstateLabel = 0.25 +PAPstateLabelPos = (0, 0, 3.5) +PAPstateLabelwordwrap = 10 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.8 +PGUIchooserTitle = 0.09 +PGUIwordwrap = 16 +PGUIdescLabel = 0.8 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.6 +PGUIadoptSubmit = 0.8 +PGUIpetsopAdoptPos = (-0.21,1.05) +PGUIpetshopCancelPos = (-2.3, 2.95) +PGUIcharLength = 3 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial.py +PTtitle = 0.10 +PTpage1Pos = (-0.37, 0.18) +PTpage2Pos = (-0.67, 0.18) +PTpage3Pos = (-0.37, 0.18) + +#quest/QuestPoster.py +QPauxText = 0.04 +QPtextScale = 0.045 +QPtextWordwrap = 15.6 +QPinfoZ = -0.0725 + +#race/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.5 + +#race/DistributedRacePad.py +DRPnodeScale = 0.65 + +#race/KartShopGui.py +KSGtextSizeBig = 0.066 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 11 + +#race/RaceEndPanels.py +REPraceEnd = 0.06 +REPraceExit = 0.02 +REPticket_text_x = -0.85 + +#race/RaceGUI.py +RGphotoFinish = 0.15 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.05 + +#safezone/Playground.py +PimgLabel = 0.7 + +#shtiker/FishPage.py +FPtankTab = 0.06 +FPcollectionTab = 0.06 +FPtrophyTab = 0.06 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.048 +DSDintroTextwordwrap = 30 +DSDwindowedButtonPos = (0.0850, 0, -0.218) +DSDfullscreenButtonPos = (0.0865, 0, -0.307) +DSDcancel = 0.04 + +#shtiker/DisguisePage.py +DPtab = 0.08 +DPdeptLabel = 0.17 +DPcogName = 0.093 + +#shtiker/TrackPage.py +TPstartFrame = 0.09 +TPendFrame = 0.09 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.045 +MPgoHome = 0.055 +MPhoodLabel = 0.06 +MPhoodWordwrap = 14 + +#shtiker/KartPage.py +KPkartTab = 0.05 +KPdeleteButton = 0.06 +KProtateButton = 0.035 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.05 +TAPwhisperButton = 0.05 +TAPsecretsButton = 0.05 +TAPgoToButton = 0.05 +TAPignoreButton = 0.05 +TAPpetButton = 0.325 +TAPdetailButton = 0.05 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.038 +TADPcancelButton = 0.05 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.05 +INrunButton = 0.05 +INdetailNameLabel = 0.8 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 0.7 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.18 + +#toontowngui/TeaserPanel.py +TPtop = 0.07 +TPpanel = 0.05 +TPbutton = 0.06 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.07 + +#trolley/Trolley.py +TtrolleyHopOff = 0.8 diff --git a/toontown/src/toonbase/netTest.py b/toontown/src/toonbase/netTest.py new file mode 100644 index 0000000..34cb591 --- /dev/null +++ b/toontown/src/toonbase/netTest.py @@ -0,0 +1,22 @@ +#from ShowBaseGlobal import * +#import ToontownClientRepository + +#cr = ToontownClientRepository.ToontownClientRepository("D:\\Cygwin\\home\\jnschell\\player\\toontown\\src\\configfiles\\toon.dc") + +#import FSMInspector +#ins = FSMInspector.FSMInspector(ClassicFSM=cr.fsm) + +#cr.fsm.request("connect", ["206.18.93.17", 6667]) + +from ToonBaseGlobal import * +from toontown.distributed import ToontownClientRepository +import os +from pandac.PandaModules import Filename + +# Start up the client repository +fname = Filename(os.getenv("TOONTOWN") + "/src/configfiles/toon.dc") +cr = ToontownClientRepository.ToontownClientRepository(fname.toOsSpecific()) + +# Start the show +base.startShow(cr) +run() diff --git a/toontown/src/toonbase/portuguese/TTLocalizer.py b/toontown/src/toonbase/portuguese/TTLocalizer.py new file mode 100644 index 0000000..c7aa18e --- /dev/null +++ b/toontown/src/toonbase/portuguese/TTLocalizer.py @@ -0,0 +1,8407 @@ +import string +from toontown.toonbase.portuguese.TTLocalizer_Property import * + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +ExtraKeySanityCheck = "Ignore me" + +InterfaceFont = 'phase_3/models/fonts/ImpressBT.ttf' +ToonFont = 'phase_3/models/fonts/ImpressBT.ttf' +SuitFont = 'phase_3/models/fonts/vtRemingtonPortable.ttf' +SignFont = 'phase_3/models/fonts/MickeyFont' +MinnieFont = 'phase_3/models/fonts/MinnieFont' +BuildingNametagFont = 'phase_3/models/fonts/MickeyFont' +BuildingNametagShadow = None + +# common names +Mickey = "Mickey" +Minnie = "Minnie" +Donald = "Donald" +Daisy = "Margarida" +Goofy = "Pateta" +Pluto = "Pluto" +Flippy = "Flippy" + +# common locations +lTheBrrrgh = 'O Brrrgh' +lDaisyGardens = 'Jardim da Margarida' +lDonaldsDock = "Porto do Donald" +lDonaldsDreamland = "Sonholândia do Donald" +lGoofySpeedway = "Autódromo do Pateta" +lMinniesMelodyland = "Melodilândia da Minnie" +lToontownCentral = 'Centro de Toontown' + +lClothingShop = 'Loja de Roupas' +lGagShop = 'Loja de Piadas' +lPetShop = 'Loja de Animais' +lToonHQ = 'Quartel dos Toons' + +lBossbotHQ = 'Quartel do Robô-chefe' +lCashbotHQ = 'Quartel do Robô Mercenário' +lLawbotHQ = 'Quartel do Robô da Lei' +lSellbotHQ = 'Quartel do Robô Vendedor' +lTutorial = 'Toon-torial' +lMyEstate = 'sua casa' +lWelcomeValley = 'Vale Boas-vindas' + +# common strings +lBack = 'Voltar' +lCancel = 'Cancelar' +lClose = 'Fechar' +lOK = 'OK' +lNext = 'Próximo' +lNo = 'Não' +lQuit = 'Sair' +lYes = 'Sim' + +lHQOfficerF = 'Oficial do Quartel' +lHQOfficerM = 'Oficial do Quartel' + +MickeyMouse = "Mickey Mouse" + +AIStartDefaultDistrict = "Vila dos Idiotas" + +Cog = "Cog" +Cogs = "Cogs" +ACog = "um Cog" +TheCogs = "os Cogs" +Skeleton = "Esqueletocogs" +SkeletonP = "Esqueletocogs" +ASkeleton = "um Esqueletocog" +Foreman = "Supervisor da fábrica" +ForemanP = "Supervisores da fábrica" +AForeman = "um Supervisor da fábrica" +CogVP = Cog + " VP" +CogVPs = "VPs Cogs" +ACogVP = ACog + " VP" +Supervisor = "Supervisor da Casa da Moeda" +SupervisorP = "Supervisores da Casa da Moeda" +ASupervisor = "um Supervisor da Casa da Moeda" +CogCFO = Cog + "Diretor Financeiro" +CogCFOs = "Diretores Financeiros Cogs" +ACogCFO = ACog + "Diretor Financeiro" + +# Quests.py +TheFish = "o Peixe" +AFish = "um peixe" +Level = "nível" +QuestsCompleteString = "Concluir" +QuestsNotChosenString = "Não escolhido" +Period = "." + +Laff = "Risada" + +# AvatarDNA.py +Bossbot = "Robô-chefe" +Lawbot = "Robô da Lei" +Cashbot = "Robô Mercenário" +Sellbot = "Robô Vendedor" +BossbotS = "um Robô-chefe" +LawbotS = "um Robô da Lei" +CashbotS = "um Robô Mercenário" +SellbotS = "um Robô Vendedor" +BossbotP = "Robôs-chefe" +LawbotP = "Robôs da Lei" +CashbotP = "Robôs Mercenários" +SellbotP = "Robôs Vendedores" +BossbotSkelS = "um Esqueletocog %s" % (Bossbot) +LawbotSkelS = "um Esqueletocog %s" % (Lawbot) +CashbotSkelS = "um Esqueletocog %s" % (Cashbot) +SellbotSkelS = "um Esqueletocog %s" % (Sellbot) +BossbotSkelP = "Esqueletocogs %s" % (BossbotP) +LawbotSkelP = "Esqueletocogs %s" % (LawbotP) +CashbotSkelP = "Esqueletocogs %s" % (CashbotP) +SellbotSkelP = "Esqueletocogs %s" % (SellbotP) + +# ToontownGlobals.py + +# (to, in, location) +# reference the location name as [-1]; it's guaranteed to be the last entry +# This table may contain names for hood zones (N*1000) that are not +# appropriate when referring to the hood as a whole. See the list of +# names below this table for hood names. +GlobalStreetNames = { + 20000 : ("para o", "no", "Terraço do Tutorial"), # Tutorial + 1000 : ("para o", "no", "Parque"), + 1100 : ("para o", "no", "Boulevard das Cracas"), + 1200 : ("para a", "na", "Rua da Alga Marinha"), + 1300 : ("para a", "na", "Travessa do Farol"), + 2000 : ("para o", "no", "Parque"), + 2100 : ("para a", "na", "Rua da Bobeira"), + 2200 : ("para a", "na", "Travessa dos Tontos"), + 2300 : ("para o", "no", "Largo do Auge da Graça"), + 3000 : ("para o", "no", "Parque"), + 3100 : ("para a", "na", "Via dos Leões Marinhos"), + 3200 : ("para a", "na", "Rua da Chuva de Neve"), + 3300 : ("para o", "no", "Lugar Polar"), + 4000 : ("para o", "no", "Parque"), + 4100 : ("para a", "na", "Avenida do Tom Alto"), + 4200 : ("para o", "no", "Boulevard do Barítono"), + 4300 : ("para o", "no", "Terraço do Tenor"), + 5000 : ("para o", "no", "Parque"), + 5100 : ("para a", "na", "Rua das Nogueiras"), + 5200 : ("para a", "na", "Rua das Amendoeiras"), + 5300 : ("para a", "na", "Rua dos Carvalhos"), + 9000 : ("para o", "no", "Parque"), + 9100 : ("para a", "na", "Travessa da Canção de Ninar"), + 9200 : ("para o", "no", "Pedaço do Pijama"), + 10000 : ("para o", "no", lBossbotHQ), + 10100 : ("para o", "no", "Salão do "+lBossbotHQ), + 11000 : ("para o", "no", "Pátio do "+lSellbotHQ), + 11100 : ("para o", "no", "Salão do "+lSellbotHQ), + 11200 : ("para a", "na", "Fábrica do "+Sellbot), + 11500 : ("para a", "na", "Fábrica do "+Sellbot), + 12000 : ("para o", "no", lCashbotHQ), + 12100 : ("para o", "no", "Salão do "+lCashbotHQ), + 12500 : ("para a", "na", "Casa da Moeda"), + 12600 : ("para a", "na", "Casa da Moeda de Dólar"), + 12700 : ("para a", "na", "Casa da Moeda de Barras de Ouro"), + 13000 : ("para o", "no", lLawbotHQ), + 13100 : ("para o", "no", "Salão do "+lLawbotHQ), + 13200 : ("para o", "no", "Lobby do Escritório do Promotor"), + 13300 : ("para o", "no", "Escritório da Lei A"), + 13400 : ("para o", "no", "Escritório da Lei B"), + 13500 : ("para o", "no", "Escritório da Lei C"), + 13600 : ("para o", "no", "Escritório da Lei D"), + } + +QuestInLocationString = " %(inPhrase)s %(location)s" + +# _avName_ gets replaced with the avatar (player's) name +# _toNpcName_ gets replaced with the npc's name we are being sent to +# _where_ gets replaced with a description of where to find the npc, with a leading \a +QuestsDefaultGreeting = ("Olá, _avName_!", + "Oi, _avName_!", + "E aí, _avName_?", + "Diga aí, _avName_!", + "Bem-vindo, _avName_!", + "Tudo certo, _avName_?", + "Como vai você, _avName_?", + "Olá _avName_!", + ) +QuestsDefaultIncomplete = ("Como está indo aquela tarefa, _avName_?", + "Parece que você ainda tem mais trabalho a fazer naquela tarefa!", + "Continue com o bom trabalho, _avName_!", + "Continue tentando concluir aquela tarefa. Eu sei que você consegue!", + "Continue tentando concluir a tarefa. Contamos com você!", + "Continue trabalhando naquela Tarefa Toon!", + ) +QuestsDefaultIncompleteProgress = ("Você veio ao lugar certo, mas, primeiramente, precisa concluir sua Tarefa Toon.", + "Ao terminar a Tarefa Toon, volte aqui.", + "Volte quando tiver terminado sua Tarefa Toon.", + ) +QuestsDefaultIncompleteWrongNPC = ("Bom trabalho naquela Tarefa Toon. Você deveria visitar _toNpcName_._where_", + "Parece que você está pronto para concluir sua Tarefa Toon. Vá ver _toNpcName_._where_.", + "Vá ver _toNpcName_ para concluir sua Tarefa Toon._where_", + ) +QuestsDefaultComplete = ("Bom trabalho! Aqui está a sua recompensa...", + "Ótimo trabalho, _avName_! Tome esta recompensa...", + "Excelente trabalho, _avName_! Aqui está a sua recompensa...", + ) +QuestsDefaultLeaving = ("Tchau!", + "Até logo!", + "Até mais, _avName_.", + "Te vejo por aí, _avName_!", + "Boa sorte!", + "Divirta-se em Toontown!", + "Vejo você depois!", + ) +QuestsDefaultReject = ("Olá.", + "Posso ajudar?", + "Como vai você?", + "E aí, pessoal?", + "Estou um pouco ocupado agora, _avName_.", + "Sim?", + "Tudo certo, _avName_!", + "Bem-vindo, _avName_!", + "Ei, _avName_! Tudo bem?", + # Game Hints + "Você sabia que pode abrir seu Álbum Toon clicando em F8?", + "Você pode usar seu mapa para se teletransportar de volta ao pátio!", + "Você pode ficar amigo de outros jogadores clicando neles.", + "Você pode descobrir mais sobre um "+ Cog +" clicando nele.", + "Junte tesouros nos pátios para encher seu Risômetro.", + "Os edifícios " + Cog + " são lugares perigosos! Não entre neles sozinho!", + "Quando você perde uma batalha, os "+ Cogs +" tomam todas as suas piadas.", + "Para obter mais piadas, jogue no Bondinho!", + "Você pode obter mais Pontos de risadas completando as Tarefas Toon.", + "Toda Tarefa Toon dá uma recompensa a você.", + "Algumas recompensas permitem que você carregue consigo mais Piadas.", + "Se você vencer uma batalha, ganhará créditos de Tarefa Toon para cada "+ Cog +" derrotado.", + "Se você recuperar um edifício "+ Cog +", entre e verá um agradecimento especial do proprietário!", + "Se pressionar a tecla Page Up, poderá ver acima de você!", + "Se você pressionar a tecla Tab, poderá ver os arredores sob diversos ângulos!", + "Para mostrar aos amigos secretos o que está pensando, coloque '.' antes do pensamento.", + "Se um "+ Cog +" estiver atordoado, será mais difícil para ele desviar de objetos cadentes.", + "Cada tipo de edifício "+ Cog +" possui um visual diferente.", + "Derrotar os "+ Cogs +" nos andares mais altos de um edifício dará a você maiores recompensas de habilidade.", + ) +QuestsDefaultTierNotDone = ("Olá, _avName_! Você deve concluir sua Tarefa Toon atual antes de começar uma nova.", + "E aí? Você precisa concluir suas Tarefas Toon atuais antes de começar uma nova.", + "Oi, _avName_! Para que eu possa dar a você uma nova Tarefa Toon, você precisa terminar as que você tem.", + ) +# The default string gets replaced with the quest getstring +QuestsDefaultQuest = None +QuestsDefaultVisitQuestDialog = ("Ouvi falar que _toNpcName_ está procurando por você._where_", + "Passe por lá e visite _toNpcName_ quando tiver um tempinho._where_", + "Visite _toNpcName_ da próxima vez em que estiver passando por aquele caminho._where_", + "Se tiver um tempinho, pare e diga olá para _toNpcName_._where_", + "_toNpcName_ dará a você sua nova Tarefa Toon._where_", + ) +# Quest dialog +QuestsLocationArticle = "" +def getLocalNum(num): + if (num <=9): + return str(num) + "" + else: + return str(num) +QuestsItemNameAndNum = "%(num)s %(name)s" + +QuestsCogQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogQuestHeadline = "PROCURADO" +QuestsCogQuestSCStringS = "Eu preciso derrotar %(cogName)s%(cogLoc)s" +QuestsCogQuestSCStringP = "Eu preciso derrotar alguns %(cogName)s%(cogLoc)s." +QuestsCogQuestDefeat = "Derrotar %s" +QuestsCogQuestDefeatDesc = "%(numCogs)s %(cogName)s" + +QuestsCogNewNewbieQuestObjective = "Ajude um novo Toon a derrotar %s" +QuestsCogNewNewbieQuestCaption = "Ajude um novo Toon que tenha %d risadas ou menos que isso" +QuestsCogOldNewbieQuestObjective = "Ajude um Toon com %(laffPoints)d risadas, ou menos, a dominar %(objective)s" +QuestsCogOldNewbieQuestCaption = "Ajude um Toon com %d risadas, ou menos" +QuestsCogNewbieQuestAux = "Derrotar:" +QuestsNewbieQuestHeadline = "APRENDIZ" + +QuestsCogTrackQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogTrackQuestHeadline = "PROCURADO" +QuestsCogTrackQuestSCStringS = "Eu preciso derrotar %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestSCStringP = "Eu preciso derrotar alguns %(cogText)s%(cogLoc)s." +QuestsCogTrackQuestDefeat = "Derrotar %s" +QuestsCogTrackDefeatDesc = "%(numCogs)s %(trackName)s" + +QuestsCogLevelQuestProgress = "%(progress)s de %(numCogs)s derrotados" +QuestsCogLevelQuestHeadline = "PROCURADO" +QuestsCogLevelQuestDefeat = "Derrotar %s" +QuestsCogLevelQuestDesc = "um Nível %(level)s+ Cog" +QuestsCogLevelQuestDescC = "%(count)s Nível %(level)s+ Cogs" +QuestsCogLevelQuestDescI = "algum Nível %(level)s+ Cogs" +QuestsCogLevelQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsBuildingQuestFloorNumbers = ('','dois+','três+','quatro+','cinco+') +QuestsBuildingQuestBuilding = "Edifício" +QuestsBuildingQuestBuildings = "Edifícios" +QuestsBuildingQuestHeadline = "DERROTAR" +QuestsBuildingQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsBuildingQuestString = "Derrotar %s" +QuestsBuildingQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsBuildingQuestDesc = "um Edifício %(type)s" +QuestsBuildingQuestDescF = "um Edifício %(type)s de %(floors)s andares" +QuestsBuildingQuestDescC = "%(count)s Edifícios %(type)s" +QuestsBuildingQuestDescCF = "%(count)s Edifícios %(type)s de %(floors)s andares" +QuestsBuildingQuestDescI = "alguns Edifícios %(type)s" +QuestsBuildingQuestDescIF = "alguns Edifícios %(type)s de %(floors)s andares" + +QuestFactoryQuestFactory = "Fábrica" +QuestsFactoryQuestFactories = "Fábricas" +QuestsFactoryQuestHeadline = "DERROTAR" +QuestsFactoryQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsFactoryQuestString = "Derrotar %s" +QuestsFactoryQuestSCString = "Eu preciso derrotar %(objective)s%(location)s." + +QuestsFactoryQuestDesc = "uma Fábrica %(type)s" +QuestsFactoryQuestDescC = "%(count)s Fábricas %(type)s" +QuestsFactoryQuestDescI = "algumas Fábricas %(type)s" + +QuestMintQuestMint = "Casa da Moeda" +QuestsMintQuestMints = "Casas da Moeda" +QuestsMintQuestHeadline = "DERROTAR" +QuestsMintQuestProgressString = "%(progress)s de %(num)s derrotados" +QuestsMintQuestString = "Derrotar %s" +QuestsMintQuestSCString = "Preciso derrotar %(objective)s%(location)s." + +QuestsMintQuestDesc = "uma Casa da Moeda dos Cogs" +QuestsMintQuestDescC = "%(count)s Casas da Moeda dos Cogs" +QuestsMintQuestDescI = "algumas Casas da Moeda dos Cogs" + +QuestsRescueQuestProgress = "%(progress)s de %(numToons)s salvos" +QuestsRescueQuestHeadline = "SALVAMENTO" +QuestsRescueQuestSCStringS = "Preciso salvar um Toon%(toonLoc)s." +QuestsRescueQuestSCStringP = "Preciso salvar alguns Toons%(toonLoc)s." +QuestsRescueQuestRescue = "Salvar %s" +QuestsRescueQuestRescueDesc = "%(numToons)s Toons" +QuestsRescueQuestToonS = "um Toon" +QuestsRescueQuestToonP = "Toons" +QuestsRescueQuestAux = "Salvar:" + +QuestsRescueNewNewbieQuestObjective = "Ajudar um novo Toon a salvar %s" +QuestsRescueOldNewbieQuestObjective = "Ajude um Toon com %(laffPoints)de risadas, ou menos, a resgatar %(objective)s" + +QuestCogPartQuestCogPart = "Parte do Processo Cog" +QuestsCogPartQuestFactories = "Fábricas" +QuestsCogPartQuestHeadline = "RECUPERAR" +QuestsCogPartQuestProgressString = "%(progress)s de %(num)s recuperados" +QuestsCogPartQuestString = "Recuperar %s" +QuestsCogPartQuestSCString = "Preciso recuperar %(objective)s%(location)s." +QuestsCogPartQuestAux = "Recuperar:" + +QuestsCogPartQuestDesc = "uma Parte do Processo Cog" +QuestsCogPartQuestDescC = "%(count)s Parte(s) do Processo Cog" +QuestsCogPartQuestDescI = "algumas Partes do Processo Cog" + +QuestsCogPartNewNewbieQuestObjective = "Ajude um novo Toon a recuperar %s" +QuestsCogPartOldNewbieQuestObjective = 'Ajude um Toon com %(laffPoints)de risadas, ou menos, a recuperar %(objective)s' + +QuestsDeliverGagQuestProgress = "%(progress)s de %(numGags)s entregues" +QuestsDeliverGagQuestHeadline = "ENTREGAR" +QuestsDeliverGagQuestToSCStringS = "Preciso entregar %(gagName)s." +QuestsDeliverGagQuestToSCStringP = "Preciso entregar algumas %(gagName)s." +QuestsDeliverGagQuestSCString = "Preciso fazer uma entrega." +QuestsDeliverGagQuestString = "Entregar %s" +QuestsDeliverGagQuestStringLong = "Entregar %s a _toNpcName_." +QuestsDeliverGagQuestInstructions = "Você pode comprar esta piada na Loja de Piadas quando tiver acesso a ela." + +QuestsDeliverItemQuestProgress = "" +QuestsDeliverItemQuestHeadline = "ENTREGAR" +QuestsDeliverItemQuestSCString = "Preciso entregar %(article)s%(itemName)s." +QuestsDeliverItemQuestString = "Entregar %s" +QuestsDeliverItemQuestStringLong = "Entregar %s a _toNpcName_." + +QuestsVisitQuestProgress = "" +QuestsVisitQuestHeadline = "VISITAR" +QuestsVisitQuestStringShort = "Visitar" +QuestsVisitQuestStringLong = "Visitar _toNpcName_" +QuestsVisitQuestSeeSCString = "Preciso ver %s." + +QuestsRecoverItemQuestProgress = "%(progress)s de %(numItems)s recuperados" +QuestsRecoverItemQuestHeadline = "RECUPERAR" +QuestsRecoverItemQuestSeeHQSCString = "Preciso ver um "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToHQSCString = "Preciso devolver %s para um "+lHQOfficerM+"." +QuestsRecoverItemQuestReturnToSCString = "Preciso devolver %(item)s para %(npcName)s." +QuestsRecoverItemQuestGoToHQSCString = "Preciso ir a um Quartel dos Toons." +QuestsRecoverItemQuestGoToPlaygroundSCString = "Preciso ir ao Pátio %s." +QuestsRecoverItemQuestGoToStreetSCString = "Preciso ir %(to)s %(street)s em %(hood)s." +QuestsRecoverItemQuestVisitBuildingSCString = "Preciso visitar %s%s." +QuestsRecoverItemQuestWhereIsBuildingSCString = "Onde é %s%s?" +QuestsRecoverItemQuestRecoverFromSCString = "Preciso recuperar %(item)s de %(holder)s%(loc)s." +QuestsRecoverItemQuestString = "Recuperar %(item)s de %(holder)s" +QuestsRecoverItemQuestHolderString = "%(level)s %(holder)d+ %(cogs)s" + +QuestsTrackChoiceQuestHeadline = "ESCOLHER" +QuestsTrackChoiceQuestSCString = "Preciso escolher entre %(trackA)s e %(trackB)s." +QuestsTrackChoiceQuestMaybeSCString = "Talvez eu deva escolher %s." +QuestsTrackChoiceQuestString = "Escolha entre %(trackA)s e %(trackB)s" + +QuestsFriendQuestHeadline = "AMIGO" +QuestsFriendQuestSCString = "Preciso fazer um amigo." +QuestsFriendQuestString = "Fazer um amigo" + +QuestsMailboxQuestHeadline = "CORRESPONDÊNCIA" +QuestsMailboxQuestSCString = "Preciso verificar minha correspondência." +QuestsMailboxQuestString = "Verificar sua correspondência" + +QuestsPhoneQuestHeadline = "CLARABELA" +QuestsPhoneQuestSCString = "Preciso ligar para Clarabela." +QuestsPhoneQuestString = "Ligar para Clarabela" + +QuestsFriendNewbieQuestString = "Faça %d amigos %d risadas ou menos" +QuestsFriendNewbieQuestProgress = "%(progress)s de %(numFriends)s feitos" +QuestsFriendNewbieQuestObjective = "Faça amizade com %d novos Toons" + +QuestsTrolleyQuestHeadline = "BONDINHO" +QuestsTrolleyQuestSCString = "Preciso pegar o bondinho." +QuestsTrolleyQuestString = "Andar no bondinho" +QuestsTrolleyQuestStringShort = "Pegar o bondinho" + +QuestsMinigameNewbieQuestString = "%d Minijogos" +QuestsMinigameNewbieQuestProgress = "%(progress)s de %(numMinigames)s jogados" +QuestsMinigameNewbieQuestObjective = "Divirta-se com %d minijogos com a ajuda de novos Toons" +QuestsMinigameNewbieQuestSCString = "Preciso participar de minijogos com novos Toons." +QuestsMinigameNewbieQuestCaption = "Ajude um novo Toon %d risadas ou menos" +QuestsMinigameNewbieQuestAux = "Jogar:" + +QuestsMaxHpReward = "Seu Limite de risadas foi aumentado em %s." +QuestsMaxHpRewardPoster = "Recompensa: %s ponto de Acréscimo de risadas" + +QuestsMoneyRewardSingular = "Você ganha 1 balinha." +QuestsMoneyRewardPlural = "Você ganha %s balinhas." +QuestsMoneyRewardPosterSingular = "Recompensa: 1 balinha" +QuestsMoneyRewardPosterPlural = "Recompensa: %s balinhas" + +QuestsMaxMoneyRewardSingular = "Agora, você pode carregar 1 balinha." +QuestsMaxMoneyRewardPlural = "Agora, você pode carregar %s balinhas." +QuestsMaxMoneyRewardPosterSingular = "Recompensa: Carregue 1 balinha" +QuestsMaxMoneyRewardPosterPlural = "Recompensa: Carregue %s balinhas" + +QuestsMaxGagCarryReward = "Você ganha %(name)s. Agora, você pode carregar %(num)s piadas." +QuestsMaxGagCarryRewardPoster = "Recompensa: %(name)s (%(num)s)" + +QuestsMaxQuestCarryReward = "Agora, você pode ter %s Tarefas Toon." +QuestsMaxQuestCarryRewardPoster = "Recompensa: Carregue %s Tarefas Toon" + +QuestsTeleportReward = "Agora, você tem acesso por teletransporte a %s." +QuestsTeleportRewardPoster = "Recompensa: Acesso por teletransporte a %s" + +QuestsTrackTrainingReward = "Agora, você pode treinar para \"%s\" piadas." +QuestsTrackTrainingRewardPoster = "Recompensa: Treinamento de piadas" + +QuestsTrackProgressReward = "Agora, você tem o quadro %(frameNum)s da animação do tipo %(trackName)s." +QuestsTrackProgressRewardPoster = "Recompensa: \"Quadro %(frameNum)s da animação do tipo %(trackName)s\"" + +QuestsTrackCompleteReward = "Agora, você pode carregar e usar \"%s\" piadas." +QuestsTrackCompleteRewardPoster = "Recompensa: Treinamento final do tipo %s" + +QuestsClothingTicketReward = "Você pode trocar de roupa" +QuestsClothingTicketRewardPoster = "Recompensa: Tíquete de roupas" + +QuestsCheesyEffectRewardPoster = "Recompensa: %s" + +QuestsCogSuitPartReward = "Agora, você tem uma %(cogTrack)s %(part)s peça de vestimenta de Cog." +QuestsCogSuitPartRewardPoster = "Recompensa: %(cogTrack)s %(part)s Peça" + +# Quest location dialog text +QuestsStreetLocationThisPlayground = "neste pátio" +QuestsStreetLocationThisStreet = "nesta rua" +QuestsStreetLocationNamedPlayground = "no pátio %s" +QuestsStreetLocationNamedStreet = "na %(toStreetName)s em %(toHoodName)s" +QuestsLocationString = "%(string)s%(location)s" +QuestsLocationBuilding = "O edifício de %s's chama-se" +QuestsLocationBuildingVerb = "o qual é" +QuestsLocationParagraph = "\a%(building)s \"%(buildingName)s\"...\a...%(buildingVerb)s %(street)s." +QuestsGenericFinishSCString = "Preciso terminar uma Tarefa Toon." + +# MaxGagCarryReward names +QuestsMediumPouch = "Sacola média" +QuestsLargePouch = "Sacola grande" +QuestsSmallBag = "Bolsa pequena" +QuestsMediumBag = "Bolsa média" +QuestsLargeBag = "Bolsa grande" +QuestsSmallBackpack = "Mochila pequena" +QuestsMediumBackpack = "Mochila média" +QuestsLargeBackpack = "Mochila grande" +QuestsItemDict = { + 1 : ["Par de óculos", "Pares de óculos", "um "], + 2 : ["Chave", "Chaves", "uma "], + 3 : ["Quadro-negro", "Quadros-negros", "um "], + 4 : ["Livro", "Livros", "um "], + 5 : ["Chocolate", "Chocolates", "um "], + 6 : ["Pedaço de giz", "Pedaços de giz", "um "], + 7 : ["Receita", "Receitas", "uma "], + 8 : ["Nota", "Notas", "uma "], + 9 : ["Calculadora", "Calculadoras", "uma "], + 10 : ["Pneu de carro de palhaço", "Pneus de carro de palhaço", "um "], + 11 : ["Bomba de ar", "Bombas de ar", "uma "], + 12 : ["Tinta de polvo", "Tintas de polvo", "uma "], + 13 : ["Pacotes", "Pacotes", "um "], + 14 : ["Recibo de peixe dourado", "Recibos de peixe dourado", "um "], + 15 : ["Peixe dourado", "Peixe dourado", "um "], + 16 : ["Óleo", "Óleos", "um pouco de "], + 17 : ["Graxa", "Graxas", "um pouco de "], + 18 : ["Água", "Águas", "uma "], + 19 : ["Relatório de engrenagens", "Relatórios de engrenagens", "um "], + 20 : ["Apagador de quadro-negro", "Apagadores de quadro-negro", "a "], + + # This is meant to be delivered to NPCTailors to complete + # ClothingReward quests + 1000 : ["Tíquete de roupas", "Tíquetes de roupas", "um "], + + # Donald's Dock quest items + 2001 : ["Câmara de ar", "Câmaras de ar", "uma "], + 2002 : ["Receita de monóculo", "Receita de monóculo", "uma "], + 2003 : ["Armação de óculos", "Armações de óculos", "algumas "], + 2004 : ["Monóculo", "Monóculos", "um "], + 2005 : ["Grande peruca branca", "Grandes perucas brancas", "uma "], + 2006 : ["Alqueire de cascalho", "Alqueires de cascalho", "um "], + 2007 : ["Engrenagem Cog", "Engrenagens de Cog", "uma "], + 2008 : ["Carta marinha", "Cartas marinhas", "uma "], + 2009 : ["Braçadeira suja", "Braçadeiras sujas", "uma "], + 2010 : ["Braçadeira limpa", "Braçadeiras limpas", "uma "], + 2011 : ["Mola de relógio", "Molas de relógio", "uma "], + 2012 : ["Contrapeso", "Contrapesos", "um "], + + # Minnie's Melodyland quest items + 4001 : ["Estoque da Tina", "Estoques da Tina", ""], + 4002 : ["Estoque da Cavaca", "Estoques da Cavaca", ""], + 4003 : ["Formulário de estoque", "Formulários de estoque", "um "], + 4004 : ["Estoque da Fifi", "Estoques da Fifi", ""], + 4005 : ["Passagem do Alê Nhador", "Passagens do Alê Nhador", ""], + 4006 : ["Passagem da Tábata", "Passagens da Tábata", ""], + 4007 : ["Passagem do Barry", "Passagens do Barry", ""], + 4008 : ["Castanhola fosca", "Castanholas foscas", ""], + 4009 : ["Tinta de lula azul", "Tintas de lula azul", "obter "], + 4010 : ["Castanhola polida", "Castanholas polidas", "uma "], + 4011 : ["Letra de música do Léo", "Letras de músicas do Léo", ""], + + # Daisy's Gardens quest items + 5001 : ["Gravata de seda", "Gravatas de seda", "uma "], + 5002 : ["Terno listrado", "Ternos listrados", "um "], + 5003 : ["Tesoura", "Tesouras", "uma "], + 5004 : ["Cartão-postal", "Cartões-postais", "um "], + 5005 : ["Caneta", "Canetas", "uma "], + 5006 : ["Tinteiro", "Tinteiros", "um "], + 5007 : ["Bloco de notas", "Blocos de notas", "um "], + 5008 : ["Cofre de escritório", "Cofres de escritório", "um "], + 5009 : ["Saco de ração para pássaros", "Sacos de ração para pássaros", "um "], + 5010 : ["Roda dentada", "Rodas dentadas", "uma "], + 5011 : ["Salada", "Saladas", "uma "], + 5012 : ["Chave para os Jardins da Margarida", "Chaves para os Jardins da Margarida", "uma "], + 5013 : ["Mapa do "+lSellbotHQ, "Mapas do "+lSellbotHQ, "alguns "], + 5014 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5015 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5016 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + 5017 : ["Memorando do "+lSellbotHQ, "Memorandos do "+lSellbotHQ, "um "], + + # The Brrrgh quests + 3001 : ["Bola de futebol", "Bolas de futebol", "uma "], + 3002 : ["Tobogã", "Tobogãs", "um "], + 3003 : ["Cubo de gelo", "Cubos de gelo", "um "], + 3004 : ["Carta de amor", "Cartas de amor", "uma "], + 3005 : ["Cão-lingüiça", "cães-lingüiça", "um "], + 3006 : ["Anel de noivado", "Anéis de noivado", "um "], + 3007 : ["Bigode de sardinha", "Bigodes de sardinhas", "um pouco de "], + 3008 : ["Poção calmante", "Poções calmantes", "uma "], + 3009 : ["Dente quebrado", "Dentes quebrados", "um "], + 3010 : ["Dente de ouro", "Dentes de ouro", "um "], + 3011 : ["Pão de pinha", "Pães de pinha", "um "], + 3012 : ["Coco em pedaços", "Cocos em pedaços", "um pouco de "], + 3013 : ["Colher simples", "Colheres simples", "uma "], + 3014 : ["Sapo falante", "Sapos falantes", "um "], + 3015 : ["Casquinha de sorvete", "Casquinhas de sorvete", "uma "], + 3016 : ["Pó de peruca", "Pós de perucas", "um pouco de "], + 3017 : ["Patinho de borracha", "Patinhos de borracha", "um "], + 3018 : ["Dados de pelúcia", "Dados de pelúcia", "alguns "], + 3019 : ["Microfone", "Microfones", "um "], + 3020 : ["Teclado elétrico", "Teclados elétricos", "um "], + 3021 : ["Sapatos de plataforma", "Sapatos de plataforma", "alguns "], + 3022 : ["Caviar", "Caviar", "um pouco de "], + 3023 : ["Pó-de-arroz", "Pó-de-arroz", "um pouco de "], + 3024 : ["Fio", "Fios", "alguns " ], + 3025 : ["Agulha de Tricô", "Agulhas de Tricô", "uma "], + 3026 : ["Álibi", "Álibis", "um "], + 3027 : ["Termômetro Externo", "Termômetros Externos", "um "], + + #Dreamland Quests + 6001 : ["Plano do "+lCashbotHQ, "Planos do "+lCashbotHQ, "algum "], + 6002 : ["Vara de pescar", "Varas de pescar", "uma "], + 6003 : ["Cinto de segurança", "Cintos de segurança", "um "], + 6004 : ["Par de pinças", "Pares de pinças", "um "], + 6005 : ["Abajur de leitura", "Abajures de leitura", "um "], + 6006 : ["Cítara", "Cítaras", "uma "], + 6007 : ["Zamboni", "Zambonis", "uma "], + 6008 : ["Zabuton de zebra", "Zabutons de zebra", "uma "], + 6009 : ["Zinnias", "Zinnias", "alguns "], + 6010 : ["Discos de forró", "Discos de forró", "algum "], + 6011 : ["Abobrinha", "Abobrinhas", "uma "], + 6012 : ["Paletó zoot", "Paletós zoot", "um "], + + #Dreamland+1 quests + 7001 : ["Cama comum", "Camas comuns", "uma "], + 7002 : ["Cama elegante", "Camas elegantes", "uma "], + 7003 : ["Colcha azul", "Colchas azuis", "uma "], + 7004 : ["Colcha estampada", "Colchas estampadas ", "uma"], + 7005 : ["Travesseiros", "Travesseiros", "alguns "], + 7006 : ["Travesseiros duros", "Travesseiros duros ", "um"], + 7007 : ["Pijamas", "Pijamas", "um par de "], + 7008 : ["Pijamas com pés", "Pijamas com pés", "um par de "], + 7009 : ["Pijamas com pés marrons", "Pijamas com pés marrons", "um par de "], + 7010 : ["Pijamas com pés fúcsia", "Pijamas com pés fúcsia", "um par de "], + 7011 : ["Coral de couve-flor", "Coral de couve-flor", "algumas "], + 7012 : ["Alga-marinha viscosa", "Alga-marinha viscosa", "um "], + 7013 : ["Pilão", "Pilões", "um "], + 7014 : ["Pote de creme para rugas", "Potes de creme para rugas", "um "], + } + +QuestsHQOfficerFillin = lHQOfficerM +QuestsHQWhereFillin = "" +QuestsHQBuildingNameFillin = lToonHQ +QuestsHQLocationNameFillin = "em qualquer bairro" + +QuestsTailorFillin = "Costureiro" +QuestsTailorWhereFillin = "" +QuestsTailorBuildingNameFillin = "Loja de Roupas" +QuestsTailorLocationNameFillin = "em qualquer bairro" +QuestsTailorQuestSCString = "Preciso ir ao Costureiro." + +QuestMovieQuestChoiceCancel = "Volte mais tarde se precisar de uma Tarefa Toon! Tchau!" +QuestMovieTrackChoiceCancel = "Volte quando já tiver decidido o que fazer! Tchau!" +QuestMovieQuestChoice = "Escolha uma Tarefa Toon." +QuestMovieTrackChoice = "Já decidiu o que escolher? Escolha um tipo ou volte mais tarde." + +# Constants used in Quests.py, globally defined here +GREETING = 0 +QUEST = 1 +INCOMPLETE = 2 +INCOMPLETE_PROGRESS = 3 +INCOMPLETE_WRONG_NPC = 4 +COMPLETE = 5 +LEAVING = 6 + +TheBrrrghTrackQuestDict = { + GREETING : "", + QUEST : "Agora, você está pronto.\aSaia e refresque a cabeça até descobrir que tipo você gostaria de escolher.\aEscolha bem, pois você não poderá mudar.\aQuando tiver certeza, volte aqui.", + INCOMPLETE_PROGRESS : "Escolha bem.", + INCOMPLETE_WRONG_NPC : "Escolha bem.", + COMPLETE : "Ótima escolha!", + LEAVING : "Boa sorte. Volte aqui quando tiver dominado sua nova habilidade.", + } + +QuestDialog_3225 = { + QUEST : "Puxa, obrigado por vir, _avName_!\aOs Cogs que estão no bairro assustaram o rapaz que faz as entregas.\aEu não tenho quem entregue esta salada para _toNpcName_!\aVocê poderia fazer isso por mim? Muitíssimo obrigado!_where_" + } + +QuestDialog_2910 = { + QUEST : "De volta tão rápido assim?\aÓtimo trabalho com aquela mola.\aO último item é um contrapeso.\aPasse lá, veja com _toNpcName_ e traga o que você conseguir._where_" + } + +QuestDialogDict = { + 160 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs-chefe.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs-chefe. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 161 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs da Lei.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas rua e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs da Lei. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 162 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs Mercenários.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs Mercenários. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 163 : {GREETING : "", + QUEST : "Ok, agora acho que você está pronto para um desafio maior.\aDerrote 3 Robôs Vendedores.", + INCOMPLETE_PROGRESS : "Os "+ Cogs +" estão soltos pelas ruas e pelos túneis.", + INCOMPLETE_WRONG_NPC : "Bom trabalho com os Robôs Vendedores. Vá agora para o Quartel dos Toons para receber sua recompensa!", + COMPLETE : QuestsDefaultComplete, + LEAVING : QuestsDefaultLeaving, + }, + 164 : {QUEST : "Parece que você precisa de novas piadas.\aVisite o Flippy, talvez ele possa ajudá-lo._where_" }, + 165 : {QUEST : "Olá.\aParece que você precisa praticar suas piadas.\aToda vez que você atinge um Cog com uma de suas piadas, sua experiência aumenta.\aQuando tiver experiência suficiente, você será capaz de usar uma piada ainda melhor.\aVá praticar suas piadas derrotando 4 Cogs."}, + 166 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs-chefe."}, + 167 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs da Lei."}, + 168 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs Vendedores."}, + 169 : {QUEST : "Bom trabalho com aqueles Cogs.\aSabia que existem quatro tipos diferentes de Cogs?\aEles são os Robôs da Lei, os Robôs Mercenários, os Robôs Vendedores e os Robôs-chefe.\aVocê pode diferenciá-los pela cor e pelas etiquetas com os nomes.\aPara praticar, derrote 4 Robôs Mercenários."}, + 170 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ele pode dar alguns conselhos especiais para você._where_" }, + 171 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ele pode dar alguns conselhos especiais para você._where_" }, + 172 : {QUEST : "Bom trabalho; agora você sabe a diferença entre os 4 tipos de Cogs.\aAcho que você está pronto para começar a treinar o seu terceiro tipo de piada.\aFale com _toNpcName_ para escolher o seu próximo tipo de piada - ela pode dar alguns conselhos especiais para você._where_" }, + + 175 : {GREETING : "", + QUEST : "Você sabia que possui sua própria casa Toon?\aA vaca Clarabela administra um catálogo telefônico no qual você pode escolher e encomendar móveis para decorar sua casa.\aVocê também pode comprar frases do Chat Rápido, roupas e outras coisas muito legais!\aPedirei à Clarabela para enviar agora a você seu primeiro catálogo.\aVocê receberá um catálogo com novos itens toda semana!\aVá para sua casa e use o seu telefone para ligar para Clarabela.", + INCOMPLETE_PROGRESS : "Vá para casa e use o seu telefone para ligar para Clarabela.", + COMPLETE : "Espero que você se divirta fazendo encomendas com Clarabela!\a Acabei de redecorar minha casa. Está Toontástica!\aContinue com as Tarefas Toon para ganhar mais recompensas!", + LEAVING : QuestsDefaultLeaving, + }, + + 400 : {GREETING : "", + QUEST : "Lançamento e Esguicho são tipos ótimos, mas você vai precisar de mais piadas para lutar com Cogs de níveis mais altos.\aQuando você se juntar com outros Toons para enfrentar os Cogs, pode combinar ataques para conseguir danos maiores ao inimigo.\aTente diferentes combinações de Piadas para ver o que funciona melhor.\aPara o seu próximo tipo, escolha as Sonoras ou Toonar.\aAs Sonoras são especiais, pois quando atingem algum Cog, todos os outros também sofrem danos.\aAs Toonar permitem curar outros Toons durante a batalha.\aQuando estiver pronto para decidir, venha aqui e escolha uma.", + INCOMPLETE_PROGRESS : "De volta tão rápido? Ok, você está pronto para escolher?", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Boa decisão. Agora, antes de usar estas piadas, você deve treinar.\aVocê deve completar uma série de Tarefas Toon como treinamento.\aCada tarefa dará a você um único quadro da animação do seu ataque de piadas.\aQuando você coletar todas as 15, poderá obter a tarefa Treinamento final de piadas, que lhe permitirá usar suas novas piadas.\aVocê pode verificar seu progresso no Álbum Toon.", + LEAVING : QuestsDefaultLeaving, + }, + 1039 : { QUEST : "Visite _toNpcName_ se desejar transitar pela cidade com mais facilidade._where_" }, + 1040 : { QUEST : "Visite _toNpcName_ se desejar transitar pela cidade com mais facilidade._where_" }, + 1041 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1042 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1043 : { QUEST : "Oi! O que o traz aqui?\aTodo mundo usa o buraco portátil para andar por Toontown.\aÉ, você pode se teletransportar até seus amigos, usando a Lista de amigos, ou até qualquer bairro, usando o mapa no Álbum Toon.\aÉ claro que você precisa consegui-lo!\aOlha, eu posso ativar seu acesso por teletransporte até o Centro de Toontown se você ajudar um amigo meu.\aParece que os Cogs estão dando problema na Travessa dos Tontos. Visite _toNpcName_._where_" }, + 1044 : { QUEST : "Puxa, obrigado por passar por aqui. Eu realmente preciso de ajuda.\aComo você pode ver, eu não tenho clientes.\aO meu livro de receitas secreto está perdido e ninguém mais vem ao meu restaurante.\aA última vez que eu o vi foi pouco antes de os Cogs tomarem meu edifício.\aVocê pode me ajudar recuperando quatro de minhas receitas favoritas?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu recuperar minhas receitas?" }, + 1045 : { QUEST : "Valeu mesmo!\aLogo terei de volta minha coleção completa e poderei reabrir meu restaurante.\aAh, há uma nota aqui para você - algo sobre acesso por teletransporte?\aDiz: \"obrigado por ajudar meu amigo e, por favor, entregue isto ao Quartel dos Toons\".\aBem, valeu mesmo - tchau!", + LEAVING : "", + COMPLETE : "Ah, sim, aqui diz que você foi de grande ajuda para alguns dos caras mais legais da Travessa dos Tontos.\aDiz também que você precisa de acesso por teletransporte para o Centro de Toontown.\aBem, considere concedido.\aAgora, você pode se teletransportar de volta para o pátio, de praticamente qualquer lugar de Toontown.\aBasta abrir o seu mapa e clicar em Centro de Toontown." }, + 1046 : { QUEST : "Os Robôs Mercenários têm importunado bastante a Financeira Dinheiro Feliz.\aPasse por lá e veja se há algo que você possa fazer._where_" }, + 1047 : { QUEST : "Os Robôs Mercenários têm se infiltrado no banco e roubado nossas calculadoras.\aRecupere 5 calculadoras dos Robôs Mercenários.\aPara evitar que você fique indo para lá e para cá, traga-as todas de uma vez.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda procurando pelas calculadoras?" }, + 1048 : { QUEST : "Uau! Valeu mesmo por encontrar nossas calculadoras.\aHumm... Elas parecem danificadas.\aVocê poderia levá-las para a loja de _toNpcName_, \"Máquinas de Cosquinhas\", nesta rua?\aVeja se podem consertá-las.", + LEAVING : "", }, + 1049 : { QUEST : "O que é isto? Calculadoras quebradas?\aRobôs Mercenários?\aBem, vamos dar uma olhada...\aÉ, as engrenagens estão partidas mas eu estou sem essa peça...\aSabe o que poderia dar jeito? Algumas engrenagens de Cog, das grandes, dos Cogs maiores...\aEngrenagens de Cogs de nível 3 devem servir. Precisarei de 2 para cada máquina, 10 no total.\aTraga-as todas de uma vez e eu as consertarei!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Lembre-se, eu preciso de 10 engrenagens para consertar as máquinas." }, + 1053 : { QUEST : "Ah sim, isto deve servir.\aTudo consertado agora, grátis.\aLeve-as de volta para a Dinheiro Feliz e diga olá a ela por mim.", + LEAVING : "", + COMPLETE : "Calculadoras consertadas?\aBom trabalho. Tenho certeza de que tenho algo por aqui para recompensar você..." }, + 1054 : { QUEST : "_toNpcName_ precisa de alguma ajuda com seus carros de palhaço._where_" }, + 1055 : { QUEST : "Oláááá! Eu não consigo encontrar os pneus para este carro de palhaço em lugar nenhum!\aVocê acha que pode me ajudar?\aEu acho que o Tito Tonto pode ter jogado os pneus no lago do pátio do Centro de Toontown.\aSe você ficar em um dos cais de lá, poderá tentar pescar os pneus para mim.", + GREETING : "Iuhuu!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar os 4 pneus?" }, + 1056 : { QUEST : "Demorôô! Agora, este velho carro de palhaço vai poder voltar às ruas!\aEi, eu pensei que tivesse uma bomba de ar aqui para inflar estes pneus...\aAcho que _toNpcName_ pegou emprestado.\aVocê poderia pedir de volta para mim?_where_", + LEAVING : "" }, + 1057 : { QUEST : "E aí?\aUma bomba de pneus?\aVamos fazer o seguinte: você me ajuda a retirar das ruas alguns desses Cogs de alto nível...\aE, então, darei a você a bomba de pneus.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Isso é o melhor que você pode fazer?" }, + 1058 : { QUEST : "Bom trabalho! Eu sabia que você conseguiria.\aAqui está a bomba. Estou certo de que _toNpcName_ ficará feliz em recebê-la de volta.", + LEAVING : "", + GREETING : "", + COMPLETE : "Dez! Agora está tudo certo!\aPor falar nisso, obrigado por me ajudar.\aAqui, tome isto." }, + 1059 : { QUEST : "_toNpcName_ está com poucos suprimentos. Quem sabe você pode ajudá-lo?_where_" }, + 1060 : { QUEST : "Valeu mesmo por passar aqui!\aOs Cogs roubam sempre a minha tinta e, por isso, ela está quase no fim.\aVocê poderia pescar um pouco de tinta de polvo para mim no lago?\aPara pescar, basta ficar parado em um cais perto do lago.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar?" }, + 1061 : { QUEST : "Ótimo, valeu pela tinta!\aSabe de uma coisa, se você eliminasse alguns daqueles Ratos de Escritório...\aAí minha tinta não acabaria tão rápido.\aDerrote 6 Ratos de Escritório no Centro de Toontown para receber sua recompensa.", + LEAVING : "", + COMPLETE : "Valeu! Vou recompensar você pela sua ajuda.", + INCOMPLETE_PROGRESS : "Eu acabei de ver mais alguns Ratos de Escritório." }, + 1062 : { QUEST : "Ótimo, valeu pela tinta!\aSabe de uma coisa? Se você eliminasse alguns daqueles Sanguessugas...\aAí minha tinta não acabaria tão rápido.\aDerrote 6 Sanguessugas no Centro de Toontown para receber sua recompensa.", + LEAVING : "", + COMPLETE : "Valeu! Vou recompensar você pela sua ajuda.", + INCOMPLETE_PROGRESS : "Eu acabei de ver mais alguns Sanguessugas." }, + 900 : { QUEST : "Fiquei sabendo que _toNpcName_ precisa de ajuda com um pacote._where_" }, + 1063 : { QUEST : "Olá! Legal você ter vindo.\aUm Cog roubou um pacote muito importante bem debaixo do meu nariz.\aVeja se você consegue recuperá-lo. Eu acho que ele era de nível 3...\aEntão, derrote Cogs de nível 3 até encontrar meu pacote.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1067 : { QUEST : "É ele mesmo, está tudo certo!\aEi, o endereço está borrado...\aTudo o que eu posso ler é que é para um Dr. - o resto está ilegível.\aTalvez seja para _toNpcName_? Você pode levar para ele?_where_", + LEAVING : "" }, + 1068 : { QUEST : "Eu não estava esperando um pacote. Talvez seja para o Dr. E.U. Fórico.\aMeu assistente ia passar mesmo lá hoje, então pedirei a ele que verifique para você.\aNesse meio tempo, você se importaria de se livrar de alguns dos Cogs que estão na minha rua?\aDerrote 10 Cogs no Centro de Toontown.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Meu assistente ainda não voltou." }, + 1069 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô Mercenário roubou o pacote de meu assistente no caminho de volta.\aVocê poderia tentar pegá-lo de volta?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1070 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô Vendedor roubou o pacote de meu assistente no caminho de volta.\aSinto muito, mas você terá que encontrar esse Robô Vendedor para pegá-lo de volta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1071 : { QUEST : "O Dr. Fórico disse que também não estava esperando nenhum pacote.\aInfelizmente um Robô-chefe roubou o pacote de meu assistente no caminho de volta.\aVocê poderia tentar pegá-lo de volta?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o pacote, né?" }, + 1072 : { QUEST : "Ótimo, você o pegou de volta!\aTalvez você deva tentar entregá-lo a _toNpcName_, pode ser para ele._where_", + LEAVING : "" }, + 1073 : { QUEST : "Puxa, obrigado por trazer meus pacotes para mim.\aEspere um segundo, eu estava esperando dois. Você poderia verificar com _toNpcName_ e ver se ele está com o outro?", + INCOMPLETE : "Conseguiu encontrar meu outro pacote?", + LEAVING : "" }, + 1074 : { QUEST : "Ele disse que havia outro pacote? Talvez os Cogs o tenham roubado também.\aDerrote Cogs até encontrar o segundo pacote.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não teve sorte de encontrar o outro pacote, né?" }, + 1075 : { QUEST : "No final das contas, acho que não havia um segundo pacote!\aCorra e leve-o para _toNpcName_, com minhas desculpas.", + COMPLETE : "Ei, meu pacote está aqui!\aJá que você parece ser um Toon tão prestativo, isto vai ser fichinha.", + LEAVING : "" }, + 1076 : { QUEST : "Houve alguns problemas na Peixinhos Dourados Ki-late.\a_toNpcName_ provavelmente podem precisar de você._where_" }, + 1077 : { QUEST : "Legal você ter vindo. Os Cogs roubaram todos os meus peixes dourados.\aEu acho que os Cogs querem vendê-los para ganhar dinheiro fácil.\aHá muitos anos, aqueles 5 peixes têm sido minhas únicas companhias nesta pequena loja ...\aSe você pudesse recuperá-los, eu agradeceria muito.\aTenho certeza de que os Cogs estão com meus peixes.\aDerrote Cogs até encontrar meus peixes dourados.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Consiga meus peixes dourados de volta." }, + 1078 : { QUEST : "Puxa, você recuperou meus peixes!\aHã? O que é isto - um recibo?\aAi, ai... Acho que eles são Cogs mesmo.\aEu não consigo decifrar este recibo. Você poderia levá-lo para _toNpcName_ e ver se ele consegue lê-lo?_where_", + INCOMPLETE : "O que _toNpcName_ disse sobre o recibo?", + LEAVING : "" }, + 1079 : { QUEST : "Humm, deixe-me ver este recibo.\a...Ah, sim, diz que 1 peixe dourado foi vendido para um Puxa-saco.\aO recibo não menciona o que aconteceu com os outros 4 peixes.\aTalvez você deva tentar encontrar esse Puxa-saco.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que não há mais nada em que eu possa ajudar.\aPor que você não tenta encontrar aquele peixe dourado?" }, + 1092 : { QUEST : "Humm, deixe-me ver este recibo.\a...Ah, sim, diz que 1 peixe dourado foi vendido para um Farsante.\aO recibo não menciona o que aconteceu com os outros 4 peixes.\aTalvez você deva tentar encontrar esse Farsante.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que não há mais nada em que eu possa ajudar.\aPor que você não tenta encontrar aquele peixe dourado?" }, + 1080 : { QUEST : "Ah, graças aos céus! Você encontrou Oscar - ele é o meu favorito.\aO que foi, Oscar? Hã-hã... Verdade? ... Estão?\aOscar diz que os outros 4 escaparam para dentro do lago no pátio.\aVocê poderia reuni-los para mim?\aÉ só pescá-los no lago.", + LEAVING : "", + COMPLETE : "Nossa, estou tããão feliz! Estou junto com meus companheiros novamente!\aVocê merece uma bela recompensa por isso!", + INCOMPLETE_PROGRESS : "Você está tendo problemas para pescar esses peixes?" }, + 1081 : { QUEST : "_toNpcName_ parece estar numa situação grudenta. Ela, com certeza, apreciaria alguma ajuda._where_" }, + 1082 : { QUEST : "Eu derramei supercola e estou presa - presa pra valer!\aSe houver uma maneira de sair, eu gostaria de saber.\aIsso me dá uma idéia; abra os olhos.\aDerrote alguns Robôs Vendedores e traga de volta um pouco de óleo.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1083 : { QUEST : "Bem, o óleo ajudou um pouco, mas eu ainda não consigo me mexer.\aO que mais poderia ajudar? É difícil dizer.\aIsso me dá uma idéia; vale a pena tentar.\aDerrote alguns Robôs da Lei e me traga graxa.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1084 : { QUEST : "Não, isso não ajudou. Isso realmente não é engraçado.\aEu coloquei a graxa bem ali,\aIsso me dá uma idéia, não me deixe esquecer.\aDerrote alguns Robôs Mercenários e traga água para umedecer.", + LEAVING : "", + GREETING : "", + COMPLETE : "Oba! Estou livre da supercola,\aComo recompensa, dou este presente a você.\aVocê pode rir um pouco mais enquanto luta e, então...\aAh, não! Já estou presa aqui novamente!", + INCOMPLETE_PROGRESS : "Você pode me ajudar a descolar daqui?" }, + 1085 : { QUEST : "_toNpcName_ está fazendo uma pesquisa sobre os Cogs.\aVá falar com ele para ver se ele precisa da sua ajuda._where_" }, + 1086 : { QUEST : "É verdade, estou fazendo um estudo sobre os Cogs.\aEu quero aprender sobre o comportamento deles.\aCom certeza ajudaria se você pudesse reunir algumas engrenagens de Cogs.\aMas elas têm que ser de Cogs de nível 2, pelo menos, para serem grandes o suficiente para o exame visual.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu encontrar engrenagens suficientes?" }, + 1089 : { QUEST : "Certo, vamos dar uma olhada. Estas são amostras excelentes!\aHummm...\aCerto, aqui está meu relatório. Leve isto de volta imediatamente para o Quartel dos Toons.", + INCOMPLETE : "Você entregou meu relatório no Quartel?", + COMPLETE : "Bom trabalho _avName_, nós assumiremos a partir daqui.", + LEAVING : "" }, + 1090 : { QUEST : "_toNpcName_ tem informações úteis para você._where_" }, + 1091 : { QUEST : "Fiquei sabendo que o Quartel dos Toons está trabalhando em uma espécie de Radar de Cogs.\aEle permite ver onde os Cogs estão, para que seja mais fácil encontrá-los.\aA Página de Cogs em seu Álbum Toon é a chave.\aAo derrotar Cogs suficientes, você pode sintonizar os sinais deles e rastrear onde estão.\aContinue derrotando Cogs para ficar pronto.", + COMPLETE : "Bom trabalho! Você provavelmente vai poder fazer uso disso...", + LEAVING : "" }, + 401 : {GREETING : "", + QUEST : "Agora, você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + 2201 : { QUEST : "Aqueles cogs traiçoeiros estão envolvidos nisto novamente.\a_toNpcName_ reportou outro item ausente. Pare um pouco aqui e veja se consegue acertar isso._where_" }, + 2202 : { QUEST : "Oi, _avName_. Ainda bem que você está aqui. Um Mão-de-vaca de má aparência acabou de passar por aqui e saiu com uma câmara de ar.\aTemo que ele possa usá-la para seus planos diabólicos.\aVeja se você consegue encontrá-la e trazê-la de volta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha câmara de ar?", + COMPLETE : "Você encontrou minha câmara de ar! Você é legal MESMO! Olha aqui, tome a sua recompensa...", + }, + 2203 : { QUEST : "Os cogs estão espalhando o caos no banco.\aVá até o Capitão Carlão e veja o que você pode fazer._where_" }, + 2204 : { QUEST : "Bem-vindo a bordo, colega.\aDroga! Aqueles cogs patifes quebraram meu monóculo e eu não vivo sem ele.\aSeja um bom marujo e leve esta receita para o Dr. Qüiqüeres para trazer um novo para mim._where_", + GREETING : "", + LEAVING : "", + }, + 2205 : { QUEST : "O que é isso?\aPuxa, eu adoraria poder trabalhar nesta receita, mas os cogs têm furtado meus suprimentos.\aSe você pegasse a armação dos óculos de um Puxa-saco eu provavelmente poderia ajudá-lo.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sinto muito. Sem armações de Puxa-saco, não tem monóculo!", + }, + 2206: { QUEST : "Excelente!\aSó um segundo...\aSua receita está pronta. Leve este monóculo diretamente ao Capitão Carlão._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Alto!\aVocê vai ganhar sua condecoração, afinal de contas.\aAqui está.", + }, + 2207 : { QUEST : "Há um Cog na loja da Craca Bárbara!\aÉ melhor você ir para lá imediatamente._where_" }, + 2208 : { QUEST : "Droga! Você se desencontrou dele, gracinha.\aHavia um Golpe Sujo aqui. Ele levou a minha grande peruca branca.\aEle disse que era para o chefe dele e mencionou algo como \"precedente legal\".\aSe você puder pegá-la de volta, ficarei eternamente grata.", + LEAVING : "", + GREETING : "", + INCOMPLETE_PROGRESS : "Ainda não o encontrou?\aEle é alto e tem uma cabeça pontuda.", + COMPLETE : "Você a encontrou!?!?\aVocê é uma gracinha!\aSua recompensa é mais do que merecida...", + }, + 2209 : { QUEST : "Moby está se preparando para uma viagem importante.\aVisite-o e veja o que pode fazer para ajudá-lo._where_"}, + 2210 : { QUEST : "Sua ajuda será bem-vinda.\aO Quartel dos Toons me pediu para fazer uma viagem e ver se consigo descobrir de onde os cogs estão vindo.\aPrecisarei de algumas coisas para o meu navio, mas não tenho muitas balinhas.\aPasse pela loja da Alice e pegue um pouco de cascalho para mim. Você terá que fazer um favor para ela para poder pegar o cascalho._where_", + GREETING : "E aí, _avName_", + LEAVING : "", + }, + 2211 : { QUEST : "Então, o Moby quer cascalho, né?\aEle ainda está me devendo por aquele último alqueire.\aEu lhe darei se você conseguir eliminar cinco Microempresários na minha rua.", + INCOMPLETE_PROGRESS : "Não, seu bobinho! Eu disse CINCO microempresários...", + GREETING : "O que posso fazer por você?", + LEAVING : "", + }, + 2212 : { QUEST : "Trato é trato.\aAqui está o cascalho para aquele fominha do Moby._where_", + GREETING : "Ora, ora, o que temos aqui...", + LEAVING : "", + }, + 2213 : { QUEST : "Excelente trabalho. Eu sabia que ela encontraria uma saída.\aAgora, eu preciso pegar uma carta de navegação com o Mário.\aAcho que meu crédito lá também não é tão bom, portanto, você vai ter que negociar com ele._where_", + GREETING : "", + LEAVING : "", + }, + 2214 : { QUEST : "Sim, eu tenho a carta de navegação que o Moby quer.\aE se você estiver disposto a trabalhar para consegui-la, eu a darei para você.\aEstou tentando construir um astrolábio para navegar pelas estrelas.\aPreciso de três engrenagens de Cog para construí-la.\aVolte aqui quando encontrá-las.", + INCOMPLETE_PROGRESS: "Como está indo com aquelas engrenagens de Cog?", + GREETING : "Bem-vindo!", + LEAVING : "Boa sorte!", + }, + 2215 : { QUEST : "Oh! Essas engrenagens vão ser úteis mesmo.\aAqui está a carta. Leve para o Moby, com meus cumprimentos._where_", + GREETING : "", + LEAVING : "", + COMPLETE : "Bem, agora sim. Estou pronto para zarpar!\aEu o levaria comigo se você não fosse novato. Leve isto, então.", + }, + 901 : { QUEST : "Se estiver disposto, o Salgado está precisando de ajuda na loja dele..._where_", + }, + 2902 : { QUEST : "Você é o novo recruta?\aBom, bom. Talvez você possa me ajudar.\aEstou construindo um caranguejo pré-fabricado gigante para confundir os cogs.\aEu vou precisar de uma braçadeira. Visite o Mário e me traga uma._where_", + }, + 2903 : { QUEST : "Olá!\aSim, eu ouvi falar no caranguejo gigante que Salgado está construindo.\aA melhor braçadeira que tenho está meio suja.\aSeja gentil e passe pela lavanderia antes de levá-la para ele._where_", + LEAVING : "Valeu!" + }, + 2904 : { QUEST : "Você deve ser o amigo do Mário.\aAcho que posso limpar isso rapidinho.\aSó um minuto...\aAqui está. Nova em folha!\aDiga olá ao Salgado por mim._where_", + }, + 2905 : { QUEST : "Ah, era exatamente o que eu queria.\aJá que você está aqui, eu também vou precisar de uma mola de relógio de corda bem grande.\aVá até a loja do Gancho e veja se ele tem uma._where_", + }, + 2906 : { QUEST : "Uma mola bem grande?\aSinto muito, mas a maior que tenho ainda é pequena.\aTalvez eu consiga montar uma com as molas do gatilho de revólver de água.\aTraga-me três dessas piadas e eu vou ver o que posso fazer.", + }, + 2907 : { QUEST : "Vamos dar uma olhada...\aArrasou. Simplesmente arrasou.\aAlgumas vezes eu surpreendo até a mim mesmo.\aAqui está: uma mola grande para o Salgado!_where_", + LEAVING : "Bon Voyage!", + }, + 2911 : { QUEST : "Ficaria feliz em ajudar nisso, _avName_.\aMas temo que as ruas não estejam mais tão seguras.\aPor que você não vai derrotar alguns Robôs Mercenários? Depois a gente conversa.", + INCOMPLETE_PROGRESS : "Eu ainda acho que você precisa fazer que as ruas fiquem mais seguras.", + }, + 2916 : { QUEST : "Sim, eu tenho um peso para o Salgado.\aNo entanto, acho que seria mais seguro se você derrotasse alguns Robôs Vendedores primeiro.", + INCOMPLETE_PROGRESS : "Ainda não. Derrote mais alguns Robôs Vendedores.", + }, + 2921 : { QUEST : "Humm, acho que poderia ceder um peso.\aMas eu me sentiria melhor se não houvesse tantos Robôs-chefe por aí.\aDerrote seis deles e volte aqui.", + INCOMPLETE_PROGRESS : "Acho que ainda não está seguro...", + }, + 2925 : { QUEST : "Tudo pronto?\aBem, acho que agora está suficientemente seguro.\aAqui está o contrapeso para o Salgado._where_" + }, + 2926 : {QUEST : "Bem, isso é tudo.\aDeixe-me ver se funciona.\aHumm, um pequeno problema.\aNão estou conseguindo obter energia, pois aquele edifício Cog está bloqueando meu painel solar.\aVocê poderia dominá-lo para mim?", + INCOMPLETE_PROGRESS : "Ainda sem energia. E aquele edifício?", + COMPLETE : "Súper! Você é um destruidor de cogs e tanto! Tome isto aqui como recompensa...", + }, + 3200 : { QUEST : "Acabo de receber uma ligação do _toNpcName_.\aEle está tendo um dia difícil. Talvez você possa ajudá-lo!\aPasse por lá e veja do que ele precisa._where_" }, + 3201 : { QUEST : "Puxa, obrigado por vir!\aPreciso de alguém para levar esta nova gravata de seda para _toNpcName_.\aVocê poderia fazer isso para mim?_where_" }, + 3203 : { QUEST : "Ah, esta deve ser a gravata que eu pedi! Obrigado!\aEla combina com o terno listrado que acabei de terminar, logo ali.\aEi, o que aconteceu com o terno?\aOh, não! Os Cogs devem ter roubado meu terno novo!\aDerrote Cogs até encontrar meu terno e traga-o de volta para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já encontrou meu terno? Tenho certeza de que os Cogs o pegaram!", + COMPLETE : "Legal! Você encontrou meu terno novo!\aViu, eu disse que os Cogs estavam com ele! Aqui está a sua recompensa...", + }, + + 3204 : { QUEST : "_toNpcName_ acabou de ligar para informar um roubo.\aPor que você não passa por lá e vê se consegue resolver as coisas?_where_" }, + 3205 : { QUEST : "Olá, _avName_! Você veio me ajudar?\aAcabei de expulsar um Sanguessuga de minha loja. Puxa! Foi horrível.\aMas agora não encontro minha tesoura em lugar nenhum! Tenho certeza de que o Sanguessuga a levou.\aEncontre-o e recupere minha tesoura.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você ainda está procurando minha tesoura?", + COMPLETE : "Minha tesoura! Valeu mesmo, viu? Aqui está a sua recompensa...", + }, + + 3206 : { QUEST : "Parece que _toNpcName_ está tendo problemas com alguns Cogs.\aVá ver se você pode ajudá-lo._where_" }, + 3207 : { QUEST : "Oi, _avName_! Obrigado por vir!\aUm monte de Duplos Sentidos invadiu minha loja e roubou uma pilha de cartões-postais de meu balcão.\aVá e derrote todos os Duplos Sentidos e recupere meus cartões-postais!", + INCOMPLETE_PROGRESS : "Não há cartões-postais suficientes! Continue procurando!", + COMPLETE : "Ah, valeu! Agora eu posso entregar a correspondência na hora certa! Aqui está a sua recompensa...", + }, + + 3208 : { QUEST : "Ultimamente temos recebido reclamações dos moradores sobre os Reis da Incerta.\aVeja se consegue derrotar 10 Reis da Incerta para ajudar nossos colegas Toons nos Jardins da Margarida." }, + 3209 : { QUEST : "Valeu mesmo por derrotar os Reis da Incerta!\aMas agora os Operadores de Telemarketing ficaram fora de controle.\aDerrote 10 Operadores de Telemarketing nos Jardins da Margarida e volte aqui para pegar sua recompensa." }, + + 3247 : { QUEST : "Ultimamente, temos recebido reclamações dos moradores sobre os Sanguessugas.\aVeja se consegue derrotar 20 Sanguessugas para ajudar nossos colegas Toons nos Jardins da Margarida." }, + + + 3210 : { QUEST : "Oh, não, a Seivas Florais da Rua das Amendoeiras está sem flores!\aPara ajudar, leve dez de suas flores com esguicho.\aMas veja primeiramente se tem realmente 10 flores com esguicho em seu estoque.", + LEAVING: "", + INCOMPLETE_PROGRESS : "Preciso ter 10 flores com esguicho. Você não tem o suficiente!" }, + 3211 : { QUEST : "Puxa, valeu mesmo, viu? Estas flores com esguicho vão salvar a pátria.\aMas estou com medo daqueles Cogs lá fora.\aVocê pode me ajudar e derrotar alguns desses Cogs?\aVolte aqui depois de derrotar 20 Cogs nesta rua.", + INCOMPLETE_PROGRESS : "Ainda há Cogs lá fora para serem derrotados! Continue trabalhando!", + COMPLETE : "Ah, valeu! Isso ajudou muito. Sua recompensa é...", + }, + + 3212 : { QUEST : "_toNpcName_ precisa de ajuda para procurar por algo que ela perdeu.\aVá visitá-la e veja o que pode fazer._where_" }, + 3213 : { QUEST : "Oi, _avName_. Você pode me ajudar?\aNão sei onde coloquei minha caneta. Acho que alguns Cogs pegaram-na.\aDerrote Cogs para encontrar minha caneta roubada.", + INCOMPLETE_PROGRESS : "Você já encontrou minha caneta?" }, + 3214 : { QUEST : "Sim, é a minha caneta! Valeu!\aMas, enquanto você estava fora, eu percebi que meu tinteiro também desapareceu.\aDerrote Cogs para encontrar meu tinteiro.", + INCOMPLETE_PROGRESS : "Ainda estou procurando meu tinteiro!" }, + 3215 : { QUEST : "Demais! Agora tenho minha caneta e meu tinteiro de volta!\aMas você nem vai acreditar!\aMeu bloco de notas sumiu! Eles devem tê-lo roubado também!\aDerrote Cogs para encontrar meu bloco de notas roubado e, então, traga-o de volta para ter sua recompensa.", + INCOMPLETE_PROGRESS : "E meu bloco de notas?" }, + 3216 : { QUEST : "É o meu bloco de notas! Maneiro! Sua recompensa é...\aEi! Onde ela está?\aSua recompensa estava bem aqui no cofre de meu escritório. Mas o cofre inteiro sumiu!\aDá para acreditar? Aqueles cogs roubaram sua recompensa!\aDerrote Cogs para recuperar meu cofre.\aQuando você o trouxer de volta, eu lhe darei sua recompensa.", + INCOMPLETE_PROGRESS : "Continue procurando o cofre! Sua recompensa está lá dentro!", + COMPLETE : "Finalmente! Seu novo saco de piadas está dentro daquele cofre. Aqui está...", + }, + + 3217 : { QUEST : "Temos feito alguns estudos sobre a mecânica dos Robôs Vendedores.\aNós ainda precisamos estudar algumas peças de forma mais detalhada.\aTraga-nos uma roda dentada de algum Dr. Sabe-com-quem-está-falando.\aVocê poderá conseguir uma quando o Cog estiver explodindo." }, + 3218 : { QUEST : "Muito bom! Agora precisamos de uma roda dentada de um Amigo-da-Onça.\aEstas são mais difíceis de conseguir, portanto, continue tentando." }, + 3219 : { QUEST : "Demais! Agora precisamos de apenas mais uma roda dentada.\aDesta vez, precisamos de uma de um Agitador.\aTalvez você precise procurar esses Cogs nos edifícios dos Robôs Vendedores.\aQuando achar a roda, traga-a aqui para receber sua recompensa." }, + + 3244 : { QUEST : "Temos feito alguns estudos sobre a mecânica dos Robôs da Lei.\aNós ainda precisamos estudar algumas peças de forma mais detalhada.\aTraga-nos uma roda dentada de algum Perseguidor de Ambulâncias.\aVocê poderá conseguir uma quando o Cog estiver explodindo." }, + 3245 : { QUEST : "Muito bom! Agora precisamos de uma roda dentada de um Golpe Sujo.\aEstas são mais difíceis de conseguir, portanto, continue tentando." }, + 3246 : { QUEST : "Demais! Agora precisamos de apenas mais uma roda dentada.\aDesta vez, de um Relações Públicas.\aQuando pegá-la, traga-a aqui para conseguir sua recompensa." }, + + 3220 : { QUEST : "Acabei de saber que _toNpcName_ estava perguntando por você.\aPor que você não passa por lá e vê o que ela quer?_where_" }, + 3221 : { QUEST : "Oi, _avName_! Aí está você!\aOuvi dizer que você é especialista em ataques com esguicho.\aPreciso de alguém para dar um bom exemplo a todos os Toons nos Jardins da Margarida.\aUse seus ataques com esguicho para derrotar vários Cogs.\aIncentive seus amigos a usarem o esguicho também.\aQuando tiver derrotado 20 Cogs, volte aqui para pegar sua recompensa!" }, + + 3222 : { QUEST : "É hora de demonstrar sua Toonmizade.\aSe você recuperar, com sucesso, um número de edifícios de Cogs, ganhará o direito de fazer três buscas.\aPrimeiramente, derrote dois edifícios de Cogs.\aSinta-se à vontade para chamar seus amigos para ajudá-lo."}, + 3223 : { QUEST : "Bom trabalho naqueles edifícios!\aAgora, derrote mais dois.\aOs edifícios devem ter, pelo menos, dois andares." }, + 3224 : { QUEST : "Fantástico!\aAgora é só derrotar mais dois edifícios.\aEles devem ter, pelo menos, três andares.\aQuando terminar, volte para pegar sua recompensa!", + COMPLETE : "Você conseguiu, _avName_!\aVocê demonstrou uma elevada Toonmizade.", + GREETING : "", + }, + + 3225 : { QUEST : "_toNpcName_ diz que precisa de ajuda.\aPor que você não vai até lá e vê o que pode fazer para ajudá-la?_where_" }, + 3235 : { QUEST : "Ah, esta é a salada que pedi!\aObrigada por trazê-la para mim.\aTodos esses Cogs devem ter amedrontado novamente o entregador de _toNpcName_ .\aPor que você não nos faz um favor e derrota alguns desses Cogs lá fora?\aDerrote 10 Cogs nos Jardins da Margarida e, então, vá até _toNpcName_.", + INCOMPLETE_PROGRESS : "Você está trabalhando na eliminação de Cogs para mim?\aIsto é maravilhoso! Continue com o bom trabalho!", + COMPLETE : "Oh, muito obrigada por derrotar aqueles Cogs!\aAgora, acho que poderei manter minha escala normal de entregas.\aSua recompensa é...", + INCOMPLETE_WRONG_NPC : "Vá contar a _toNpcName_ sobre os Cogs que você derrotou._where_" }, + + 3236 : { QUEST : "Há muitos Robôs da Lei por aí.\aVocê pode fazer sua parte para ajudar!\aDerrote 3 edifícios de Robôs da Lei." }, + 3237 : { QUEST : "Bom trabalho naqueles edifícios de Robôs da Lei!\aMas agora há muitos Robôs Vendedores!\aDerrote 3 edifícios de Robôs Vendedores e volte para buscar sua recompensa." }, + + 3238 : { QUEST : "Ah não! Um Cog \"Amizade Fácil\" roubou a Chave para os Jardins da Margarida!\aVeja se você consegue recuperá-la.\aLembre-se, o Amizade Fácil só pode ser encontrado dentro dos edifícios de Robôs Vendedores." }, + 3239 : { QUEST : "Você achou uma chave, tudo bem, mas esta não é a correta!\aPrecisamos da chave dos Jardins da Margarida.\aContinue de olho! Ela ainda está com algum Cog \"Amizade Fácil\"!" }, + + 3242 : { QUEST : "Ah não! Um Cog Macaco velho roubou a Chave para os Jardins da Margarida!\aVeja se você consegue recuperá-la.\aLembre-se, os Macacos-velhos só podem ser encontrados dentro dos edifícios de Robôs da Lei." }, + 3243 : { QUEST : "Você achou uma chave, tudo bem, mas esta não é a correta!\aPrecisamos da chave dos Jardins da Margarida.\aContinue de olho! Ela ainda está com algum Cog Macaco velho!" }, + + 3240 : { QUEST : "Acabei de saber que um Macaco velho roubou um saco de ração para pássaros de _toNpcName_ .\aDerrote Macacos velhos até recuperar a ração para pássaros do Florêncio e levá-la de volta para ele.\aOs Macacos velhos só são encontrados dentro de edifícios de Robôs da Lei._where_", + COMPLETE : "Ah, muito obrigado por encontrar minha ração para pássaros!\aSua recompensa é...", + INCOMPLETE_WRONG_NPC : "Bom trabalho na recuperação da ração para pássaros!\aAgora, leve-a para _toNpcName_._where_", + }, + + 3241 : { QUEST : "Alguns dos edifícios de Cogs estão ficando altos demais e isso já está incomodando.\aVeja se você consegue derrubar alguns dos edifícios mais altos.\aRecupere 5 edifícios de 3 andares, ou mais altos, e volte para pegar sua recompensa.", + }, + + 3250 : { QUEST : "A Detetive Linda da Rua dos Carvalhos recebeu informações sobre um Quartel de Robôs Vendedores.\aVá até lá e ajude-a a investigar.", + }, + 3251 : { QUEST : "Há algo estranho acontecendo por aqui.\aHá tantos Robôs Vendedores!\aOuvi dizer que eles organizaram seu próprio quartel no final desta rua.\aVá até lá e veja o que consegue descobrir.\aEncontre Cogs Robôs Vendedores em seu quartel, derrote 5 deles e volte aqui.", + }, + 3252 : { QUEST : "Ok, desembucha.\aO que você disse?\aQuartel de Robôs Vendedores?? Ah não!!! Algo tem que ser feito.\aDevemos avisar a Juíza Gala. Ela saberá o que fazer.\aVá até lá e conte a ela o que descobrimos. É só descer a rua.", + }, + 3253 : { QUEST : "Sim, posso ajudá-lo? Estou muito ocupada.\aHã? Quartel de Cogs?\aHã? Besteira. Isto nunca poderia acontecer.\aVocê deve estar enganado. Absurdo.\aHã? Não discuta comigo.\aOk, então, traga alguma prova.\aSe os Robôs Vendedores realmente estão construindo este Quartel de Cogs, qualquer Cog de lá estará carregando mapas.\aCogs amam trabalhar com papelada, sabe?\aDerrote Robôs Vendedores até encontrar os mapas.\aTraga-os aqui, e eu talvez acredite em você.", + }, + 3254 : { QUEST : "Você de novo, hã? Mapas? Você está com eles?\aDeixe-me vê-los! Humm... Uma fábrica?\aDeve ser lá que eles estão construindo os Robôs Vendedores... E o que é isso?\aSim, exatamente como eu suspeitava. Eu sabia o tempo todo.\aEles estão construindo um Quartel de Robôs Vendedores.\aIsso não é bom. Preciso fazer algumas ligações. Estou muito ocupada. Adeus!\aHã? Ah sim, leve estes mapas de volta para a Detetive Linda.\aEla poderá decifrá-los melhor.", + COMPLETE : "O que a Juíza Gala disse?\aNós tínhamos razão? Ah, não. Vamos ver estes mapas.\aHumm... Parece que os Robôs Vendedores construíram uma fábrica com maquinário para fazer Cogs.\aParece muito perigoso. Fique de fora até que você tenha mais Pontos de risadas.\aQuando você tiver mais Pontos de risadas, teremos muito mais a aprender sobre o Quartel dos Robôs Vendedores.\aAqui está sua recompensa. Bom trabalho!", + }, + + + 3255 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3256 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3257 : { QUEST : "_toNpcName_ está investigando o "+lSellbotHQ+".\aVeja se você consegue ajudar._where_" }, + 3258 : { QUEST : "Há muita confusão sobre o que os Cogs pretendem com seu novo Quartel.\aPreciso que você traga algumas informações diretamente deles.\aSe nós conseguirmos quatro memorandos internos de Robôs Vendedores dentro de seu Quartel, isso ajudará a esclarecer as coisas.\aTraga o primeiro memorando para mim para que possamos nos informar melhor.", + }, + 3259 : { QUEST : "Demais! Vamos ver o que diz o memorando...\a\"A/C Robôs Vendedores:\"\a\"Estarei em meu escritório no topo das Torres Robôs Vendedores promovendo Cogs a níveis mais altos.\"\a\"Quando você tiver méritos suficientes, entre no elevador do saguão para falar comigo\".\a\"O intervalo chegou ao fim. De volta ao trabalho!\"\a\"Assinado, Robô Vendedor VP\"\aAhá.... Flippy vai querer ver isto. Enviarei a ele imediatamente.\aVá buscar o segundo memorando e traga aqui.", + }, + 3260 : { QUEST : "Que bom, você está de volta. Deixe-me ver o que você encontrou....\a\"A/C Robôs Vendedores:\"\a\"As Torres Robôs Vendedores instalaram um novo sistema de segurança para afastar todos os Toons.\"\a\"Os Toons que forem encontrados nas Torres Robôs Vendedores serão detidos para interrogatório\".\a\"Encontrem-se no saguão para um coquetel, no qual discutiremos o assunto.\"\a\"Assinado, Amizade Fácil\"\aMuito interessante... Passarei imediatamente esta informação adiante.\aTraga o terceiro memorando.", + }, + 3261 : { QUEST : "Excelente trabalho _avName_! O que diz o memorando?\a\"A/C Robôs Vendedores:\"\a\"De algum modo, os Toons encontraram um jeito de se infiltrarem nas Torres Robôs Vendedores.\"\a\"Ligarei para vocês esta noite na hora do jantar para fornecer os detalhes.\"\a\"Assinado, Operador de Telemarketing\"\aHumm... Queria saber como os Toons estão conseguindo se infiltrar....\aTraga mais um memorando e acho que assim teremos informações suficientes.", + COMPLETE : "Eu sabia que você conseguiria! Ok, o memorando diz...\a\"A/C Robôs Vendedores:\"\a\"Ontem, estava almoçando com Dr. Celebridade.\"\a\"Ele disse que o VP tem estado bastante ocupado nestes dias.\"\a\"Ele só receberá os Cogs que merecem promoção.\"\a\"Esqueci de dizer, o Amigo-da-onça jogará golfe comigo no domingo.\"\a\"Assinado, Dr. Sabe-com-quem-está-falando\"\aBem... _avName_, isto foi muito útil.\aAqui está sua recompensa.", + }, + + 3262 : { QUEST : "_toNpcName_ tem novas informações sobre a Fábrica do "+lSellbotHQ+".\aVá ver o que ele tem a dizer._where_" }, + 3263 : { GREETING : "Olá, parceiro!", + QUEST : "Eu sou o Treinador Abobrinha, mas você pode me chamar de Treinador A.\aEu sou a favor de treinos com a raquete e alongamento, se é que você me entende.\aOuça, os Robôs Vendedores terminaram uma enorme fábrica para produzir Robôs Vendedores 24 horas por dia.\aReúna um grupo de parceiros Toon e raquetada na fábrica!\aDentro do Quartel do Robô Vendedor, procure pelo túnel que leva até a fábrica e, então, entre no elevador.\aVocê já tem que estar com as piadas e os pontos de risadas completos e ter Toons fortes como guias.\aPara retardar o progresso dos Robôs Vendedores, derrote o Supervisor dentro da fábrica.\aParece um grande exercício, se é que fui bem claro.", + LEAVING : "Te vejo por aí, parceiro!", + COMPLETE : "Ei, parceiro, bom trabalho naquela Fábrica!\aParece que você encontrou parte de um terno de Cog.\aDeve ser uma sobra do processo de fabricação de Cogs.\aIsto pode vir a calhar. Continue coletando estas partes quando tiver um tempo livre.\aQuem sabe, quando você coletar um terno de Cog completo, poderá vir a ser útil para alguma coisa....", + }, + + 4001 : {GREETING : "", + QUEST : "Agora, você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + + 4002 : {GREETING : "", + QUEST : "Agora você tem que escolher o próximo tipo de piada que deseja aprender.\aDecida e depois volte aqui quando estiver pronto para escolher.", + INCOMPLETE_PROGRESS : "Pense bem sobre sua decisão antes de escolher.", + INCOMPLETE_WRONG_NPC : "Pense bem sobre sua decisão antes de escolher.", + COMPLETE : "Uma boa decisão...", + LEAVING : QuestsDefaultLeaving, + }, + 4200 : { QUEST : "Aposto que o Tom iria gostar de ter alguma ajuda na pesquisa que ele está fazendo._where_", + }, + 4201 : { GREETING: "Tudo certo?", + QUEST : "Estou bastante preocupado com a onda de roubos de instrumentos musicais.\aEstou conduzindo uma pesquisa com meus amigos comerciantes.\aTalvez seja possível encontrar um padrão para me ajudar a resolver este caso.\aPeça a Tina o controle de estoque de concertina._where_", + }, + 4202 : { QUEST : "Sim, eu falei com Tom nesta manhã.\aO estoque está bem aqui.\aLeve para ele imediatamente, ok?_where_" + }, + 4203 : { QUEST : "Demais! Um a menos...\aAgora peça o da Cavaca._where_", + }, + 4204 : { QUEST : "Ah! O estoque!\aEsqueci completamente.\aAposto que consigo fazer enquanto você derrota 10 cogs.\aPasse por aqui depois, e eu prometo que estará pronto.", + INCOMPLETE_PROGRESS : "31, 32... DROGA!\aVocê me fez perder a conta!", + GREETING : "", + }, + 4205 : { QUEST : "Ah, aí está você.\aObrigada por me dar algum tempo.\aLeve isto para o Tom e diga olá por mim._where_", + }, + 4206 : { QUEST : "Humm, muito interessante.\aAgora estamos chegando a algum lugar.\aOk, o último estoque é o da Fifi._where_", + }, + 4207 : { QUEST : "Estoque?\aComo posso fazer o estoque se não tenho o formulário?\aVá até o Clave e veja se ele tem um para mim._where_", + INCOMPLETE_PROGRESS : "Algum sinal daquele formulário?", + }, + 4208 : { QUEST : "Claro que eu tenho um formulário de estoque, monsenhor!\aMas eles não são de graça, sabe?.\aFaçamos o seguinte. Eu troco por uma torta de creme inteira.", + GREETING : "Ei, monsenhor!", + LEAVING : "Boa sorte...", + INCOMPLETE_PROGRESS : "Um pedaço não adianta.\aEstou com fome, monsenhor. Eu preciso da torta INTEIRA.", + }, + 4209 : { GREETING : "", + QUEST : "Humm...\aMuito gostoso!\aAqui está o formulário para Fifi._where_", + }, + 4210 : { GREETING : "", + QUEST : "Valeu, foi uma grande ajuda.\aVamos ver...Violinos: 2\aTudo pronto! Aqui está!", + COMPLETE : "Bom trabalho, _avName_.\aTenho certeza de que solucionarei este caso agora.\aPor que você não o soluciona?", + }, + + 4211 : { QUEST : "Veja, o Dr. Triturador está ligando de cinco em cinco minutos. Você pode conversar com ele e ver qual o problema?_where_", + }, + 4212 : { QUEST : "Puxa! Estou feliz de ver que o Quartel dos Toons finalmente mandou alguém.\aNão tenho um cliente há dias.\aSão estes malditos Destruidores de Números que estão em todo lugar.\aAcho que eles estão ensinando maus hábitos de higiene oral a nossos moradores.\aDerrote dez deles e vamos ver se o negócio anda.", + INCOMPLETE_PROGRESS : "Ainda sem clientes. Mas continue assim!", + }, + 4213 : { QUEST : "Sabe, talvez não sejam os Destruidores de Números, no final das contas.\aTalvez sejam apenas os Robôs Mercenários em geral.\aDerrote vinte deles e, com alguma sorte, alguém virá, pelo menos, para um check-up.", + INCOMPLETE_PROGRESS : "Eu sei que vinte é muito. Mas tenho certeza de que vai valer a pena.", + }, + 4214 : { GREETING : "", + LEAVING : "", + QUEST : "Eu não consigo entender!\aAinda não há UM BENDITO freguês.\aTalvez precisemos ir até a fonte.\aTente recuperar um edifício Cog de Robôs Mercenários.\aIsso deve funcionar...", + INCOMPLETE_PROGRESS : "Oh, por favor! Apenas um mísero prediozinho...", + COMPLETE : "Ainda não há uma alma sequer aqui.\aMas, pense bem.\aEu não tinha mesmo clientes antes da invasão dos cogs!\aRealmente agradeço toda a sua ajuda.\aIsto deve ajudar você a prosseguir." + }, + + 4215 : { QUEST : "A Ana precisa desesperadamente da ajuda de alguém.\aPor que você não passa lá e vê o que pode fazer?_where_", + }, + 4216 : { QUEST : "Obrigada por chegar tão rápido!\aParece que os cogs sumiram com várias passagens dos meus clientes.\aA Cavaca disse que viu um Amigo-da-Onça saindo daqui com as garras cheias de passagens.\aVeja se você consegue recuperar a passagem do Alê Nhador para o Alasca.", + INCOMPLETE_PROGRESS : "Aqueles Amigos da Onça podem estar em qualquer lugar agora...", + }, + 4217 : { QUEST : "Legal! Você encontrou!\aAgora seja um cavalheiro e entregue ao Alê Nhador para mim, está bem?_where_", + }, + 4218 : { QUEST : "Genial, estupendo, fabuloso!\aAlasca, aqui vou eu!\aNão agüento mais esses cogs infernais.\aOlha, acho que a Ana precisa de você de novo._where_", + }, + 4219 : { QUEST : "Exatamente, você adivinhou!\aPreciso de você para derrotar aquelas pestes dos Amigos da Onça para recuperar a passagem da Tábata para o Festival de Jazz.\aVocê sabe como fazer...", + INCOMPLETE_PROGRESS : "Há mais lá fora, em algum lugar...", + }, + 4220 : { QUEST : "Gracinha!\aVocê poderia entregar este também?_where_", + }, + 4221 : { GREETING : "", + LEAVING : "Fica frio...", + QUEST : "Legal, cara!\aAgora estou na cidade dos gordinhos, _avName_.\aAntes de sair fora, é melhor falar com a Ana Banana de novo..._where_", + }, + 4222 : { QUEST : "Este é o último, prometo!\aAgora procure pela passagem do Barry para o grande concurso de cantores.", + INCOMPLETE_PROGRESS : "Vamos lá, _avName_.\aO Barry está contando com você.", + }, + 4223 : { QUEST : "Isto deve alegrar o Barry._where_", + }, + 4224 : { GREETING : "", + LEAVING : "", + QUEST : "Olá, Olá, OLÁ!\aMagnífico!\aSó conheço eu mesmo e os caras que vão fazer a faxina. \aA Ana disse para você passar lá e pegar a sua recompensa._where_\aTchau, Tchau, TCHAU!", + COMPLETE : "Obrigado por toda a sua ajuda, _avName_.\aVocê é realmente um tesouro aqui de Toontown.\aFalando em tesouros...", + }, + + 902 : { QUEST : "Vá ver o Léo.\aEle precisa de alguém para entregar uma mensagem para ele._where_", + }, + 4903 : { QUEST : "Cara!\aMinhas castanholas estão foscas e tenho um grande show hoje à noite.\aLeve-as para o Carlos e veja se ele pode dar um polimento nelas._where_", + }, + 4904 : { QUEST : "Sim, acho que posso polir esta peça. Mas preciso de alguma tinta azul de lula", + GREETING : "Olá!", + LEAVING : "Tchau!", + INCOMPLETE_PROGRESS : "Você pode achar uma lula perto de algum píer de pesca.", + }, + 4905 : { QUEST : "Claro! Isso mesmo!\aAgora, preciso de um minuto para polir isto. Por que você não trabalha na recuperação de um prédio de um andar enquanto trabalho por aqui?", + GREETING : "Ola!", + LEAVING : "Tchau!", + INCOMPLETE_PROGRESS : "Só mais um minutinho...", + }, + 4906 : { QUEST : "Muito bom!\aAqui estão as castanholas do Léo._onde_", + }, + 4907 : { GREETING : "", + QUEST : "Maneiro, cara!\aElas estão incríveis!\aAgora preciso que você consiga uma cópia da letra da “Música de Natal” da Heidi._where_", + }, + 4908 : { QUEST: "E aí pessoal!\aHumm, Eu não tenho uma cópia dessa música à mão.\aSe você me der um tempinho, eu posso transcrever de cabeça.\aPor que você não dá uma voltinha e aproveita para recuperar um edifício de dois andares enquanto escrevo?", + }, + 4909 : { QUEST : "Desculpe.\aMinha memória está ficando meio confusa.\aSe você recuperar um edifício de três andares, tenho certeza de que estarei pronta quando voltar...", + }, + 4910 : { QUEST : "Tudo pronto!\aDesculpe a demora.\aLeve isto para o Léo._where_", + GREETING : "", + COMPLETE : "Caramba, cara!\aMeu show vai detonar!\aFalando em detonar, você pode detonar alguns cogs com isto..." + }, + 5247 : { QUEST : "Este bairro está ficando perigoso...\aVocê deve estar querendo aprender alguns truques novos.\a_toNpcName_ me ensinou tudo que sei, então, talvez ele possa ajudar você também._where_" }, + 5248 : { GREETING : "Ah, sim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você parece estar empenhado na missão.", + QUEST : "Ah, bem-vindo, novo aprendiz.\aEu sei de tudo que há para saber sobre o jogo de tortas.\aPorém, antes de começarmos o seu treinamento, é necessário uma pequena demonstração.\aSaia e derrote dez dos maiores Cogs." }, + 5249 : { GREETING: "Humm.", + QUEST : "Excelente!\aAgora demonstre sua habilidade como pescador.\aColoquei ontem três dados de pelúcia no lago.\aPesque-os e traga-os para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Parece que você não é tão hábil com a vara e o molinete." }, + 5250 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs da Lei.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5258 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs-chefes.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5259 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs Mercenários.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5260 : { GREETING : "", + LEAVING : "", + QUEST : "Ahá! Estes dados ficarão ótimos pendurados no retrovisor do meu carro de bois!\aAgora, mostre para mim que você sabe distinguir seus inimigos.\aVolte quando tiver recuperado dois dos edifícios mais altos dos Robôs Vendedores.", + INCOMPLETE_PROGRESS : "Os edifícios deram problema para você?", }, + 5200 : { QUEST : "Aqueles cogs traiçoeiros estão envolvidos nisto novamente.\a_toNpcName_ percebeu que tem outro item ausente. Pare um pouco aqui e veja se consegue acertar isso._where_" }, + 5201 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Caça-talentos chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5261 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Duas Caras chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5262 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Sacos de Dinheiro chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5263 : { GREETING: "", + QUEST : "Oi, _avName_. Acho que eu devo agradecer a você por ter vindo.\aUm grupo desses Relações Públicas chegou e roubou minha bola de futebol.\aO líder disse que eu tinha que fazer alguns cortes e tomou a bola de mim!\aVocê pode trazer de volta a minha bola?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Conseguiu achar minha bola de futebol?", + COMPLETE : "Dez! Encontrei! Olha aqui, tome a sua recompensa...", + }, + 5202 : { QUEST : "O Brrrgh foi invadido por alguns dos mais temíveis Cogs já vistos.\aVocê provavelmente desejará carregar mais piadas consigo.\aOuvi falar que _toNpcName_ tem uma sacola grande que você pode usar para carregar mais piadas._where_" }, + 5203 : { GREETING: "Hã? Você está no meu time de trenó?", + QUEST : "O que é isto? Você quer uma bolsa?\aEu tinha uma aqui em algum lugar... Acho que está no meu tobogã?\aSó que... Eu não vejo o meu tobogã desde a grande corrida!\aTalvez um destes Cogs o tenha pego.", + LEAVING : "Você viu meu tobogã?", + INCOMPLETE_PROGRESS : "Quem é você novamente? Desculpe, estou meio confuso depois da batida." }, + 5204 : { GREETING : "", + LEAVING : "", + QUEST : "Este é o meu tobogã? Não vejo nenhuma sacola aqui.\aAcho que o Cabeção Kika estava na equipe... Será que está com ele?_where_" }, + 5205 : { GREETING : "Ai, minha cabeça!", + LEAVING : "", + QUEST : "Hã? Tobi? Ah, a bolsa?\aBom, acho que ele estava na nossa equipe de tobogã?\aMinha cabeça dói tanto que não consigo pensar direito.\aVocê consegue para mim alguns cubos de gelo no lago congelado para eu pôr na minha cabeça?", + INCOMPLETE_PROGRESS : "Aaiii, minha cabeça está me matando! Tem gelo aí?", }, + 5206 : { GREETING : "", + LEAVING : "", + QUEST : "Ahhh, agora me sinto bem melhor!\aEntão você está procurando a bolsa do Tobi, né?\aAcho que ela foi parar na cabeça do Álvaro Asno depois da batida._where_" }, + 5207 : { GREETING : "Iiiiiiiiiip!", + LEAVING : "", + QUEST : "O que é bolsa? Quem é Cabeção?\aTenho medo de edifícios! Você detona edifício, eu dou bolsa!", + INCOMPLETE_PROGRESS : "Mais edifícios! Ainda com medo!", + COMPLETE : "Ooooh! Mim gosta você!" }, + 5208 : { GREETING : "", + LEAVING : "Iiiiiiiiiiik!", + QUEST : "Ooooh! Mim gosta você!\aVai pra Clínica do Esqui. Sacola lá." }, + 5209 : { GREETING : "Valeu, garoto!", + LEAVING : "Até mais!", + QUEST : "Cara, o Álvaro Asno é doido!\aSe você fosse maluco que nem o Álvaro, eu daria a bolsa para você, cara.\aVai ensacar uns Cogs para poder pegar a sua sacola, cara! Essa agora!", + INCOMPLETE_PROGRESS : "Tem certeza de que você é radical o bastante para isso? Vai ensacar mais Cogs.", + COMPLETE : "Caramba, você é irado! Aquilo foi um bando de Cogs que você ensacou!\aToma a sua bolsa!" }, + + 5210 : { QUEST : "_toNpcName_ está gamada em alguém do bairro, mas é segredo.\aSe você ajudá-la, ela pode lhe dar uma boa recompensa._where_" }, + 5211 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos com bico veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5264 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos de barbatana veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5265 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs asquerosos de Amizade Fácil veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5266 : { GREETING: "Buá!", + QUEST : "Passei a noite passada inteira escrevendo uma carta para o cachorro que eu amo.\aMas, antes mesmo que eu pudesse entregar a ele, um daqueles Cogs Aventureiros Corporativos asquerosos veio e a tomou de mim.\aVocê consegue pegá-la de volta para mim?", + LEAVING : "Buá!", + INCOMPLETE_PROGRESS : "Por favor, encontre minha carta." }, + 5212 : { QUEST : "Oh, obrigada por encontrar a minha carta!\aPor favor, você poderia entregá-la ao cão mais lindo do bairro? Por favor! Por favor!", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você não entregou a minha carta, não é?", + }, + 5213 : { GREETING : "Enfeitiçado, com certeza.", + QUEST : "Não posso dar atenção à sua carta, sabe.\aTodos os meus cãezinhos foram levados!\aSe você os trouxer de volta, a gente volta a conversar.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tadinhos dos meus cãezinhos!" }, + 5214 : { GREETING : "", + LEAVING : "Tchauzinho!", + QUEST : "Graças a você minhas belezinhas voltaram.\aVamos ver a carta agora...\nMmmm, parece que tenho outra admiradora secreta.\aIsso exigirá uma visita ao meu querido amigo Carlo.\aAposto como você vai adorá-lo._where_" }, + 5215 : { GREETING : "He, he...", + LEAVING : "Volte aqui, sim, sim.", + INCOMPLETE_PROGRESS : "Ainda há alguns grandalhões na área. Volte aqui para falar conosco quando eles forem embora.", + QUEST : "Quem mandou você? Não gostamos muito de Snobs, não...\aMas gostamos menos ainda de Cogs...\aExpulse os grandalhões e ajudaremos vocês, ajudaremos." }, + 5216 : { QUEST : "Falamos que ajudaríamos você.\aEntão, pegue este anel e leve à garota.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você ainda está com o anel???", + COMPLETE : "Oh querrrrido!!! Obrigado!!!\aAh, também tenho algo especial para você.", + }, + 5217 : { QUEST : "Parece que _toNpcName_ pode dar uma ajuda._where_" }, + 5218 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Tenho certeza de que há mais Amizades Fáceis por aqui em algum lugar.", + QUEST : "Socorro!!! Socorro!!! Assim não dá!\aEsses Amizades Fáceis estão me deixando maluco!!!" }, + 5219 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não são só estes. Só vi um!!!", + QUEST : "Ah, obrigado, mas agora são os Aventureiros Corporativos!!!\aVocê tem que me ajudar!!!" }, + 5220 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não, não, não, havia um aqui agora mesmo!", + QUEST : "Agora, eu percebo que são aqueles Agiotas!!!\aPensei que você ia me salvar!!!" }, + 5221 : { GREETING : "", + LEAVING : "", + QUEST : "Sabe de uma coisa, talvez não sejam os Cogs coisa nenhuma!\aVocê pode pedir à Hilária para fazer para mim uma poção calmante? Talvez isto ajude...._where_" }, + 5222 : { LEAVING : "", + QUEST : "Esse Américo é mesmo uma figura!\aVou preparar algo que vai dar jeito nele rapidinho!\aPuxa, parece que estou sem bigodes de sardinha...\aSeja legal comigo e corra lá no lago para pegar alguns para mim.", + INCOMPLETE_PROGRESS : "Já pegou aqueles bigodes para mim?", }, + 5223 : { QUEST : "OK. Obrigada!\aTome, leve agora para o Américo. Isto deve acalmá-lo de uma vez por todas.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vá logo, leve a poção para o Américo.", + }, + 5224 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vá pegar aqueles Macacos velhos para mim, ok?", + QUEST : "Puxa vida, graças a Deus você voltou!\aPasse logo para cá esta poção!!!\aGlub, glub, glub...\aQue gosto horrível!\aSabe de uma coisa? Sinto-me bem mais calmo. Agora que eu posso pensar com mais clareza, me toquei que...\aEram os Macacos-velhos que estavam me enlouquecendo todo este tempo!!!", + COMPLETE : "Nossa! Agora eu posso relaxar!\aTenho certeza de que há alguma coisa aqui que posso dar a você. Aqui, leve isto!" }, + 5225 : { QUEST : "Desde o acidente com o pão de nabo, Felipe Nervosinho ficou furioso com _toNpcName_.\aQuem sabe você não consegue ajudar o Pio a acertar os ponteiros entre eles?_where_" }, + 5226 : { QUEST : "Isso mesmo, você deve ter ouvido falar que o Felipe Nervosinho está furioso comigo...\aEu estava só tentando ser legal oferecendo o pão de nabo.\aQuem sabe você não consegue alegrá-lo.\aO Felipe detesta aqueles Cogs Robôs Mercenários, principalmente os edifícios deles.\aSe você recuperar alguns edifícios de Robôs Mercenários, talvez ajude.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Quem sabe alguns edifícios a mais?", }, + 5227 : { QUEST : "Demais! Vá dizer ao Felipe o que você fez._where_" }, + 5228 : { QUEST : "Puxa, ele fez isso mesmo?\aEsse Pio acha que pode se safar fácil, né?\aSó quebrou meu dente, só isso que ele fez, com aquele pão de nabo dele!\aSe você levar o meu dente para o Dr. Ban Guela para mim, quem sabe ele consegue dar jeito.", + GREETING : "Mmmmrrf.", + LEAVING : "Resmungo, resmungo.", + INCOMPLETE_PROGRESS : "Você de novo? Pensei que você estava indo levar meu dente para consertar.", + }, + 5229 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs Mercenários das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5267 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs Vendedores das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5268 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs da Lei das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5269 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda estou ajeitando o dente. Vai demorar um pouco.", + QUEST : "É, este dente parece estar ruim mesmo, mas tudo bem.\aEu acho que posso fazer uma coisa aqui, mas ainda vai demorar um pouco.\aVocê não quer dar cabo de alguns daqueles Cogs Robôs-chefe das ruas enquanto espera?\aEles estão assustando os meus clientes." }, + 5230 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Barão Ladrão entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5270 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Rei da Cocada Preta entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5271 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que o Dr. Celebridade entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5272 : { GREETING: "", + QUEST : "Ainda bem que você voltou!\aDesisti de consertar aquele dente velho e, em vez de consertá-lo, fiz um novo dente de ouro para o Felipe.\aSó que um Figurão entrou aqui e o levou, infelizmente.\aSerá que você não consegue pegá-lo? Vamos, apresse-se!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você já achou aquele dente?" }, + 5231 : { QUEST : "Legal, é este dente mesmo!\aPor que você não corre para levá-lo para o Felipe?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Aposto como o Felipe vai adorar ver o dente novo dele.", + }, + 5232 : { QUEST : "Puxa, obrigado.\aMmmrrrfffffff\aE aí, que tal, hein?\aOk, tá legal, pode dizer ao Pio que eu o perdôo.", + LEAVING : "", + GREETING : "", }, + 5233 : { QUEST : "Legal, muito bom saber disso.\aAchei mesmo que meu velho amigo Felipe não podia ficar com raiva de mim.\aPara agradecer e ser gentil, preparei para ele este pão de pinha.\aSerá que você podia correr lá e entregar a ele para mim?", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Melhor se apressar. O pão de pinha só é bom quando está quente.", + COMPLETE : "Puxa, o que é isto? Para mim?\aNham, nham...\aOhhhhhh! Meu dente! Aquele Pio Arrepio!\aTá legal, não foi sua culpa. Tome aqui, leve isto por todo o trabalho que demos a você.", + }, + 903 : { QUEST : "Você deve se aprontar para ver _toNpcName_, o Mago do Lago Congelado, para o seu teste final._where_", }, + 5234 : { GREETING: "", + QUEST : "Ahá! Você voltou.\aAntes de você começar, precisamos comer.\aTraga para a gente alguns pedaços de coco para o nosso caldo.\aO coco em pedaços só pode ser conseguido nos Cogs Rei da Cocada Preta.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda precisamos de coco em pedaços." }, + 5278 : { GREETING: "", + QUEST : "Ahá! Você voltou.\aAntes de você começar, precisamos comer.\aTraga para a gente caviar para o nosso caldo.\aO caviar só pode ser conseguido nos Cogs Dr. Celebridade.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Ainda precisamos de caviar." }, + 5235 : { GREETING: "", + QUEST : "Homens simples comem com colheres simples.\aOs Cogs levaram minha colher simples, por isso, eu simplesmente não posso comer.\aPegue minha colher de volta. Acho que foi um Barão Ladrão.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eu simplesmente preciso da minha colher." }, + 5279 : { GREETING: "", + QUEST : "Homens simples comem com colheres simples.\aOs Cogs levaram minha colher simples, por isso, eu não posso comer.\aPegue minha colher de volta. Acho que foi um Figurão.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eu simplesmente preciso da minha colher." }, + 5236 : { GREETING: "", + QUEST : "Muito obrigado.\aSlurp, slurp...\aAhhh, agora, você precisa pegar um sapo falante. Tente pescá-lo no lago.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cadê o sapo falante?" }, + + 5237 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Você não conseguiu a sobremesa ainda.", + QUEST : "Ah, isto é, com certeza, um sapo falante. Passe para cá.\aO que você me diz, sapo?\aUh huh.\aUh huh...\aO sapo falou. Precisamos da sobremesa.\aTraga para a gente algumas casquinhas de sorvete da _toNpcName_.\aPor alguma razão, o sapo gosta de sorvete sabor feijão vermelho._where_", }, + 5238 : { GREETING: "", + QUEST : "Então, o mago mandou você aqui. Sinto dizer que acabamos de ficar sem as casquinhas sabor feijão vermelho.\aVocê nem imagina, mas um bando de Cogs entrou aqui e as levou.\aEles disseram que iam levá-las para o Dr. Celebridade, ou alguma baboseira parecida.\aCertamente, apreciaria se você pudesse recuperá-las para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Já achou todas as minhas casquinhas de sorvete?" }, + 5280 : { GREETING: "", + QUEST : "Então, o mago mandou você aqui. Sinto dizer que acabamos de ficar sem as casquinhas sabor feijão vermelho.\aVocê nem imagina, mas um bando de Cogs entrou aqui e as levou.\aEles disseram que iam levá-las para O Rei da Cocada Preta, ou alguma baboseira parecida.\aCertamente, apreciaria se você pudesse recuperá-las para mim.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Já achou todas as minhas casquinhas de sorvete?" }, + 5239 : { QUEST : "Obrigado por trazer de volta as minhas casquinhas de sorvete!\aTome uma para o Pequeno Grande Ancião.", + GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "É melhor você levar este sorvete para o Pequeno Grande Ancião antes que ele derreta.", }, + 5240 : { GREETING: "", + QUEST : "Muito bem. Aqui está, sapo...\aSlurp, slurp...\aOk, agora estamos quase prontos.\aSe você pudesse apenas trazer um pozinho para secar as minhas mãos...\aAcho que das perucas daqueles Cogs Figurões às vezes sai pó.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Achou algum pó?" }, + 5281 : { GREETING: "", + QUEST : "Muito bem. Aqui está, sapo...\aSlurp, slurp...\aOk, agora estamos quase prontos.\aSe você pudesse apenas trazer um pozinho para secar as minhas mãos...\aAcho que aqueles Cogs Drs. Celebridades às vezes têm pó para o nariz.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Achou algum pó?" }, + 5241 : { QUEST : "Ok.\aComo já disse antes, para lançar uma torta pra valer, não basta jogá-la com a mão...\a...É preciso jogar com a alma.\aNão sei exatamente o que isto significa, portanto, sentarei e contemplarei você em seu trabalho de recuperar edifícios.\aVolte quando tiver concluído a sua tarefa.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Sua tarefa ainda não está concluída.", }, + 5242 : { GREETING: "", + QUEST : "Embora eu ainda não saiba sobre o que estou falando, você realmente merece.\aDou a você, então, uma tarefa final...\aO sapo falante precisa de uma namorada.\aAche uma sapa falante. O sapo falou.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cadê a sapa falante?", + COMPLETE : "Puxa! Estou cansado com todo esse esforço. Preciso descansar agora.\aAgora, pegue a sua recompensa e saia." }, + + 5243 : { QUEST : "Soares Suado está começando a feder no início da rua.\aFala com ele para tomar um banho ou algo do gênero?_where_" }, + 5244 : { GREETING: "", + QUEST : "É, acho que suei demais aqui.\aMmmm, se eu pudesse consertar aquele vazamento no encanamento do meu chuveiro...\aAcho que a engrenagem de um daqueles Cogs pequenos bastaria para o conserto.\aVá achar uma engrenagem de um Microempresário para a gente tentar consertar.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Onde está aquela engrenagem que você ia conseguir?" }, + 5245 : { GREETING: "", + QUEST : "É, parece que funcionou.\aMas eu fico solitário quando tomo banho...\aSerá que você poderia pescar um patinho de borracha para me fazer companhia?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não acha o patinho de borracha?" }, + 5246 : { QUEST : "O patinho é ótimo, mas...\aTodos aqueles edifícios aqui em volta me deixam com os nervos em frangalhos.\aEu me sentiria bem melhor se houvesse menos edifícios por aqui.", + LEAVING : "", + COMPLETE : "Ok, agora eu vou tomar banho. Ah, aqui está uma coisinha para você.", + INCOMPLETE_PROGRESS : "Ainda estou preocupado com os edifícios.", }, + 5251 : { QUEST : "Vítor Vestíbulo devia estar fazendo um show nesta noite.\aOuvi falar que ele estava tendo problemas com o equipamento._where_" }, + 5252 : { GREETING: "", + QUEST : "É isso aí! Seria bom mesmo aceitar a sua ajuda.\aAqueles Cogs entraram aqui e levaram todas as engrenagens do meu equipamento enquanto eu estava descarregando a caminhonete.\aVocê pode me dar uma mãozinha e conseguir de volta o meu microfone?", + LEAVING : "", + INCOMPLETE_PROGRESS : "Cara, eu não consigo cantar sem o microfone." }, + 5253 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Aventureiros Corporativos o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5273 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Amizades Fáceis o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5274 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Agiotas o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5275 : { GREETING: "", + QUEST : "Legal, você conseguiu meu microfone de volta.\aValeu, mas...\aEu preciso mesmo do meu teclado para poder fazer um som.\aAcho que um daqueles Macacos velhos o levaram.", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não conseguiu pegar o meu teclado?" }, + 5254 : { GREETING: "", + QUEST : "Tudo em cima! Agora estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Dr. Celebridade, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5282 : { GREETING: "", + QUEST : "Tudo em cima! Agora, estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Rei da Cocada Preta, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5283 : { GREETING: "", + QUEST : "Tudo em cima! Agora estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Barão Ladrão, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + 5284 : { GREETING: "", + QUEST : "Tudo em cima! Agora, estou na parada.\aSe ao menos eles não tivessem levado meus sapatos de plataforma...\aAqueles sapatos provavelmente acabaram com algum Figurão, creio eu.", + LEAVING : "", + COMPLETE : "Tudo bem!! Estou pronto agora.\aOlá Brrrgh!!!\aHã? Onde está todo mundo?\aOk, pegue isto e reúna alguns fãs, está bem?", + INCOMPLETE_PROGRESS : "Não posso me apresentar sem sapatos, né?" }, + + 5255 : { QUEST : "Parece que você pode usar mais pontos de risadas.\aTalvez _toNpcName_ entre em um acordo com você.\aNão deixe de firmar o acordo por escrito..._where_" }, + 5256 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Trato é trato.", + QUEST : "Então, você está atrás de pontos de risadas, né?\aSe eu tenho uma proposta para você!?\aÉ só tomar conta de alguns Cogs Robôs-chefe para mim...\aAí eu dou uma injeção de ânimo nos seus pontos." }, + 5276 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Trato é trato.", + QUEST : "Então, você está atrás de pontos de risadas, né?\aSe eu tenho uma proposta para você!?\aÉ só tomar conta de alguns Cogs Robôs da Lei para mim...\aAí eu dou uma injeção de ânimo nos seus pontos." }, + 5257 : { GREETING : "", + LEAVING : "", + COMPLETE : "Ok, mas tenho certeza de que falei para você reunir alguns Cogs Robôs da Lei.\aBom, se você está falando, tudo bem, mas, então, fica me devendo uma.", + INCOMPLETE_PROGRESS : "Acho que você não terminou ainda.", + QUEST : "Você está dizendo que acabou? Derrotou todos os Cogs?\aVocê deve ter entendido errado, nosso trato era para os Cogs Robôs Vendedores.\aTenho certeza de que disse para você derrotar alguns Cogs Robôs Vendedores para mim." }, + 5277 : { GREETING : "", + LEAVING : "", + COMPLETE : "Ok, mas tenho certeza de que falei para você reunir alguns Cogs Robôs da Lei.\aBom, se você está falando, tudo bem, mas, então, fica me devendo uma.", + INCOMPLETE_PROGRESS : "Acho que você não terminou ainda.", + QUEST : "Você está dizendo que acabou? Derrotou todos os Cogs?\aVocê deve ter entendido errado, nosso trato era para os Cogs Robôs Mercenários.\aTenho certeza de que disse para você derrotar alguns Cogs Robôs Mercenários para mim." }, + + # Eddie the will give you laff point for helping him + 5301 : { QUEST : "Eu não posso ajudar com os pontos de Risada, mas talvez _toNpcName_ faça negócio com você.\aMas ele é um pouco temperamental..._where_" }, + 5302 : { GREETING : "", + LEAVING : "", + COMPLETE : "Eu te disse o quê?!?!\aValeu mesmo! Aqui está o seu ponto de Risada!", + INCOMPLETE_PROGRESS : "Oi!\aO que está fazendo aqui de novo!", + QUEST : "Um ponto de Risada? Acho que não!\aClaro, mas só se der um jeito em alguns desses Robôs da Lei antes." }, + + # Johnny Cashmere will knit you a large bag if... + 5303 : { QUEST : lTheBrrrgh+" está repleto de Cogs perigosos.\aSe fosse você, carregaria mais piadas por aqui.\aOuvi dizer que _toNpcName_ pode fazer uma bolsa maior para você se estiver a fim de trabalhar._where_" }, + 5304 : { GREETING: "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Deve haver bastante Robôs da Lei lá fora.\aEntão mexa-se!" , + QUEST : "Uma bolsa maior?\aEu até poderia arranjar uma procê.\aMas vou precisar de fios.\aUns Robôs da Lei roubaram os meus fios ontem de manhã." }, + 5305 : { GREETING : "Olá!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Vai atacar mais uns cogs.\aEssa cor ainda não pegou.", + QUEST : "Esse é um fio bom!\aMas não seria a minha primeira escolha de cor.\aVou te dizer...\aVai lá fora e derrote alguns dos cogs mais difíceis...\aE eu começo a a trabalhar em tingir este fio." }, + 5306 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Eles têm que estar lá em algum lugar...", + QUEST : "Bem, este fio está todo tingido. Mas tem um probleminha.\aNão consigo encontrar as minhas agulhas de tricô.\aO último lugar que estavam foi no lago." }, + 5307 : { GREETING : "", + LEAVING : "Muito obrigado!", + INCOMPLETE_PROGRESS : "Roma não foi tricotada em um dia!" , + QUEST : "Essas são as minhas agulhas.\aEnquanto eu tricoto, que tal fazer uma limpeza em alguns dos prédios grandes?", + COMPLETE : "Ótimo trabalho!\aE falando em trabalho ótimo...\aAqui está a sua nova bolsa!" }, + + # March Harry can also give you max quest = 4. + 5308 : { GREETING : "", + LEAVING : "", + QUEST : "Ouvi dizer que _toNpcName_ tem problemas legais.\aVocê pode passar lá e dar uma olhada?_where_" }, + 5309 : { GREETING : "Que bom ver você...", + LEAVING : "", + INCOMPLETE_PROGRESS : "Rápido, por favor! A rua está transbordando com eles!", + QUEST : "Os Robôs da Lei tomaram conta daqui.\aTemo que eles vão me levar a julgamento.\aVocê poderia me ajudar a tirá-los desta rua?" }, + 5310 : { GREETING : "", + LEAVING : "", + INCOMPLETE_PROGRESS : "Acho que os ouço vindo por mim...", + QUEST : "Obrigado. Sinto-me um pouco melhor agora.\a Mas tem mais uma coisa...\aVocê poderia ir até a casa de _toNpcName_ e me conseguir um álibi?_where_" }, + 5311 : { GREETING : "O QUEEE!!!!", + LEAVING : "", + INCOMPLETE_PROGRESS : "Não posso ajudá-lo se não encontrar!", + QUEST : "Álibi?! Mas que ótima idéia!\aE traga duas!\aAposto que um Macaco velho deve ter alguns..." }, + 5312 : { GREETING : "Finalmente!", + LEAVING : "", + INCOMPLETE_PROGRESS : "", + COMPLETE : "Ufa! Que alívio é ter isso.\aAqui está a sua recompensa...", + QUEST : "Súper! É melhor você voltar até _toNpcName_!" }, + + # Powers Erge, though forgetful, will give you an LP boost + # if you'll defeat some Cogs for him + 6201 : { QUEST : "Elle Étrica precisa de ajuda. Você pode passar lá e dar uma mãozinha a ela?_where_", + }, + 6202 : { GREETING : "", + LEAVING : "", + QUEST : "Um cliente! Beleza! Em que posso ajudar?\aComo assim, você me ajudar? AH! Você não é um cliente.\aAgora me lembrei. Você veio para me ajudar com aqueles Cogs horrorosos.\aNa verdade, eu aceitaria sua ajuda, você sendo um cliente ou não.\aSe você fizer uma pequena limpa nas ruas, dou uma coisa a você.", + INCOMPLETE_PROGRESS : "Se você não quiser eletricidade, não posso ajudar até que derrote aqueles Cogs.", + COMPLETE : "Bom trabalho com aqueles Cogs, _avName_.\aAgora, você tem certeza de que não quer um choquezinho? Pode ser útil....\aNão? OK, você que sabe.\aHã? Ah sim, lembro. Aqui está. Com certeza, vai ajudar você a deter aqueles Cogs nojentos.\aContinue assim!", + }, + + # Susan Siesta wants to get rich but the Cogs are interfering. + # Take out some Cog buildings and she'll give you the small backpack + 6206 : { QUEST : "Bem, _avName_, não tenho nada para você agora.\aEspera aí! Acho que a Célia Sesta estava procurando ajuda. Por que não vai encontrá-la?_where_", + }, + 6207 : { GREETING : "", + LEAVING : "", + QUEST : "Nunca enriquecerei com aqueles malditos Cogs atrapalhando os meus negócios!\aVocê tem que me ajudar, _avName_.\aElimine alguns edifícios de Cogs para salvar a vizinhança e ajudarei você em sua poupança.", + INCOMPLETE_PROGRESS : "O que farei agora? Você não conseguiu se livrar dos edifícios?", + COMPLETE : "Agora, vou entrar na grana! Agora sim!\aVou passar todo o meu tempo livre pescando. Agora, deixe-me enriquecer sua vida um pouquinho.\aLá vai!", + }, + + # Lawful Linda is fixing her answering machine. + # Help her & she'll give you a 2LP reward. + 6211 : { QUEST : "Oi, _avName_! Ouvi dizer que a Linda Legal estava procurando você.\aPassa lá para fazer uma visitinha a ela._where_", + }, + 6212 : { GREETING : "", + LEAVING : "", + QUEST : "E aí! Nossa, como é bom ver você!\aFiquei trabalhando nesta secretária eletrônica nas horas vagas, mas faltam algumas peças.\aPreciso de mais três varas, e as do Conta-moedinha parecem perfeitas.\aVocê poderia tentar encontrar algumas varas de pescar para mim?", + INCOMPLETE_PROGRESS : "Ainda à procura daquelas varas de pescar?", + }, + 6213 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, estas aqui já ajudam.\aEngraçado. Eu tinha certeza de que havia um cinto de segurança extra por aqui, mas não consigo encontrá-lo.\aVocê pode pegar um de uns Sacos de Dinheiro para mim? Valeu!", + INCOMPLETE : "Olha, eu só posso ajudar você depois que conseguir aquele cinto de segurança.", + }, + 6214 : { GREETING : "", + LEAVING : "", + QUEST : "Agora sim. Vai funcionar que é uma beleza.\aOnde está meu alicate? Não vou poder ajustar isto aqui sem o alicate.\aTalvez as pinças da Mão-de-vaca ajudem.\aSe você conseguir encontrá-las, dou a você uma coisa que vai ajudar na batalha com os Cogs.", + INCOMPLETE_PROGRESS : "Nada das pinças ainda, né? Vai procurando.", + COMPLETE : "Beleza! Agora é só fazer o ajuste aqui.\aParece que agora está funcionando. Estou de novo na ativa!\aNa verdade, falta ainda o telefone. Mas, estou satisfeito com a sua ajuda.\aAcho que isso vai ajudar você com os Cogs. Boa sorte!", + }, + + # Scratch Rocco's back and he'll scratch yours. + # In fact, he'll give you a 3 LP bonus. + 6221 : { QUEST : "Ouvi dizer que Pedro estava atrás da sua ajuda. Veja o que pode fazer por ele._where_", + }, + 6222 : { GREETING : "", + LEAVING : "", + QUEST : "Qualé? Chegou no point certo. Não estou legal.\aÉ isso aí, tava procurando ajuda pra me livrar daqueles Cogs. Eles chegam e ficam mandando em mim.\aBem que você podia mandar aqueles Robôs-chefe se aposentarem. Você não vai se arrepender.", + INCOMPLETE_PROGRESS : "E aí, _avName_, qual foi?\aVai lá atrás dos Robôs-chefe. A gente tem um trato, falou?\aO Pedro aqui tem palavra.", + COMPLETE : "Qualé, _avName_! Agora, você tá bem na fita.\aQuero ver os Robôs-chefe chefiar agora, né não?\aVamo lá! Um tremendo acréscimo pra você. Agora, vê se não entra em nenhuma fria, falou?", + }, + + # Nat & PJ will get you acquainted with the new + # HQ. And they'll give you your first suit part + 6231 : { QUEST : "O Zezé ouviu um boato na Alameda do Pijama sobre um Quartel do Robô Mercenário.\aVai lá e veja se consegue ajudá-lo._where_", + }, + 6232 : { GREETING : "", + LEAVING : "", + QUEST : "Soube de umas coisas estranhas que estão acontecendo.\aTalvez sejam as pulgas, mas deve ter alguma coisa rolando.\aTodos esses Robôs Mercenários!\aAcho que abriram outro quartel bem na Alameda do Pijama.\aO Py Jama sabe o caminho.\aVá ver _toNpcName_ _where_ Pergunte a ele se viu alguma coisa.", + INCOMPLETE_PROGRESS : "Ainda não viu o Py Jama? O que você está esperando?\aAi, essas malditas pulgas!", + }, + 6233 : { GREETING : "", + LEAVING : "", + QUEST : "E aí, _avName_, para onde você está indo?\aPara o Quartel dos Robôs Mercenários?? Eu não vi nada.\aVocê pode ir até o final da Alameda do Pijama e ver se é verdade?\aEncontre alguns Cogs do Robô Mercenário no quartel, derrote alguns deles e venha me contar.", + INCOMPLETE_PROGRESS : "Já encontrou o Quartel? Você precisará derrotar alguns Robôs Mercenários para localizá-lo.", + }, + 6234 : { GREETING : "", + LEAVING : "", + QUEST : "O quê?! Existe mesmo um Quartel de Robôs Mercenários?\aÉ melhor você ir e contar a Zezé agora mesmo!\aQuem poderia imaginar que existiria um Quartel de Cogs na rua bem em frente a ele?", + INCOMPLETE_PROGRESS : "O que Zezé disse? Você ainda não o encontrou?", + }, + 6235 : { GREETING : "", + LEAVING : "", + QUEST : "Estou tentado para ouvir o que o Py Jamas disse.\aHmm... Precisamos de mais informações sobre esse negócio de Cog, mas preciso me livrar dessas pulgas!\aEu sei! VOCÊ pode descobrir mais coisas!\aVá derrotar os Robôs Mercenários no Quartel até encontrar alguns planos, depois venha direto pra cá!", + INCOMPLETE_PROGRESS : "Nada ainda? Continue procurando esses Cogs!\aEles devem ter algum plano!", + COMPLETE : "Você conseguiu os planos?\aExcelente! Vejamos o que diz aqui.\aEntendi... os Robôs Mercenários construíram uma Casa da Moeda para fabricar grana Cog.\aDeve estar CHEIA de Robôs Mercenários. Precisamos averiguar.\aE se você se disfarçasse? Hmmm...Já sei! Acho que tenho uma peça de vestimenta de Cog aqui em algum lugar....\aAqui está! Isto aqui é para compensar o trabalho. Agradeço novamente pela ajuda!", + }, + + # The Countess can't concentrate on counting her sheep with all + # these Cogs around. Clean up a bit and she'll reward you handsomely. + # Reward: MaxMoneyReward 705 - 150 jellybeans + 6241 : { QUEST : "A Condessa está procurando você por toda parte! Visite-a logo para que pare de ligar _where_", + }, + 6242 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, conto com a sua ajuda!\aSabe, esses Cogs estão fazendo tanto barulho que eu simplesmente não consigo me concentrar.\aPerco a conta dos carneirinhos a todo instante!\aSe você acabar com esse barulho, te dou uma ajuda! Pode contar com isso!\aMas, onde eu parei mesmo? Ah sim: cento e trinta e seis, cento e trinta e sete....", + INCOMPLETE_PROGRESS : "Quatrocentos e quarenta e dois... Quatrocentos e quarenta e três...\aO quê? Você já voltou? Mas ainda tem tanto barulho!\aEssa não, perdi a conta novamente.\a Um...dois...três....", + COMPLETE : "Quinhentos e noventa e três... Quinhentos e noventa e quatro...\aOlá? Ah, eu sabia que poderia contar com a sua ajuda! Agora, o silêncio voltou.\aPegue aqui, por todos esses Destruidores de Números.\aContar? Agora preciso começar a contar tudo outra vez! Um...dois....", + }, + + # Zari needs you to run some errands for her and maybe + # wipe out some Cogs along the way. She'll make it worthwhile + # though, she'll give you 4 LP if you run the gauntlet. + 6251 : { QUEST : "Pobre Zéfiro, o zíper dela quebrou e, agora, ela não consegue fazer as entregas de seus clientes. Ela certamente precisa de sua ajuda._where_", + }, + 6252 : { GREETING : "", + LEAVING : "", + QUEST : "Oi _avName_. Você está aqui para ajudar com minhas entregas?\aIsso é ótimo! Com esse zíper quebrado é muito difícil fazer as entregas sozinha.\aDeixe-me ver... Ok, vai ser fácil. O Vaqueiro George pediu uma cítara semana passada.\aVocê poderia levá-la para ele? _where_", + INCOMPLETE_PROGRESS : "Oi! Esqueceu alguma coisa? O Vaqueiro George está esperando pela cítara.", + }, + 6253 : { GREETING : "", + LEAVING : "", + QUEST : "Minha cítara! Finalmente! Caramba, mal posso esperar para tocá-la.\aPoderia agradecer à Zéfiro por mim?", + INCOMPLETE_PROGRESS : "Obrigado novamente pela cítara. A Zéfiro não tem mais entregas para você fazer?", + }, + 6254 : { GREETING : "", + LEAVING : "", + QUEST : "Essa foi rápida. Qual será o próximo item da minha lista?\aAh sim! Mestre Mário pediu um Zamboni. Aquele zombeteiro.\aPoderia levar para ele?_where_", + INCOMPLETE_PROGRESS : "Aquele Zamboni precisa ser levado para o Mestre Mário._where_", + }, + 6255 : { GREETING : "", + LEAVING : "", + QUEST : "Tudo certo! O Zamboni que eu pedi!\aAgora, se não houvesse tantos Cogs por aí, eu teria algum tempo para usá-lo.\aSeja gentil e cuide de alguns desses Robôs Mercenários para mim, tá legal?", + INCOMPLETE_PROGRESS : "Esses Robôs Mercenários são durões, não são? Assim, eu não consigo testar o meu Zamboni.", + }, + 6256 : { GREETING : "", + LEAVING : "", + QUEST : "Excelente! Agora, eu posso testar o meu Zamboni.\aDiga à Zéfiro que eu estarei lá para fazer um outro pedido na próxima semana.", + INCOMPLETE_PROGRESS : "Por enquanto é só isso. A Zéfiro não está esperando por você?" + }, + 6257 : { GREETING : "", + LEAVING : "", + QUEST : "Então, o Mestre Mário ficou satisfeito com o Zamboni? Excelente.\aQuem é o próximo? Ah, o Bob Bocão pediu uma almofada zabuton com listras de zebra.\aAqui está! Poderia ir até a casa dele?_where_", + INCOMPLETE_PROGRESS : "Acho que o Bob Bocão precisa da almofada zabuton para meditar.", + }, + 6258 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, minha almofada zabuton finalmente. Agora, eu posso meditar.\aQuem consegue se concentrar com aquela algazarra? Todos aqueles Cogs!\aJá que você está aqui, poderia cuidar de alguns desses Cogs?\aSó assim eu poderei usar minha almofada zabuton em paz.", + INCOMPLETE_PROGRESS : "Ainda há muito barulho com esses Cogs! Quem consegue se concentrar?", + }, + 6259 : { GREETING : "", + LEAVING : "", + QUEST : "Paz e silêncio afinal. Obrigado, _avName_.\aDiga à Zéfiro que estou muito satisfeito. OM....", + INCOMPLETE_PROGRESS : "A Zéfiro ligou procurando por você. É melhor você ir ver o que ela precisa.", + }, + 6260 : { GREETING : "", + LEAVING : "", + QUEST : "Estou feliz em saber que o Bob Bocão está satisfeito com sua almofada zabuton de zebra.\aAh, estas zínias acabaram de chegar para a Rosa Sonada.\aJá que você parece tão animado para fazer entregas, talvez possa levar essas zínias para ela, não é?_where_", + INCOMPLETE_PROGRESS : "Essas zínias vão murchar se você não fizer logo a entrega.", + }, + 6261 : { GREETING : "", + LEAVING : "", + QUEST : "Que lindas zínias! Certamente que é entrega da Zéfiro.\aQuer dizer, é SUA entrega, _avName_. Agradeça à Zéfiro por mim!", + INCOMPLETE_PROGRESS : "Não se esqueça de agradecer à Zéfiro pelas zínias!", + }, + 6262 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom que voltou, _avName_. Você é bastante veloz.\aVejamos... Qual é o próximo item da lista a ser entregue? Discos de forró para Jatha Cordada._where_", + INCOMPLETE_PROGRESS : "Tenho certeza de que Jatha Cordada está esperando por esses discos de forró.", + }, + 6263 : { GREETING : "", + LEAVING : "", + QUEST : "Discos de forró? Não me lembro de ter pedido discos de forró.\aAh, aposto que foi Denis Nar quem pediu._where_", + INCOMPLETE_PROGRESS : "Não, esses discos de forró são para Denis Nar._where_", + }, + 6264 : { GREETING : "", + LEAVING : "", + QUEST : "Finalmente, meus discos de forró! Pensei que a Zéfiro tivesse se esquecido.\aPoderia levar essa abobrinha para ela? Ela encontrará alguém que esteja querendo uma. Valeu!", + INCOMPLETE_PROGRESS : "Eu já tenho muitas abobrinhas. Leve esta para Zéfiro.", + }, + 6265 : { GREETING : "", + LEAVING : "", + QUEST : "Abobrinha? Hmm. Bem, alguém irá querer, tenho certeza.\aOk, estamos quase terminando com a minha lista. Mais uma entrega a fazer.\aNenê Crespo pediu um paletó zoot._where_", + INCOMPLETE_PROGRESS : "Se você não entregar esse paletó zoot ao Nenê Crespo,\a ele ficará todo amarrotado.", + }, + 6266 : { GREETING : "", + LEAVING : "", + QUEST : "Era uma vez... Ah! Você não está aqui para ouvir uma história, não é?\aÉ a entrega do meu terno zoot? Beleza! Uau, isso aqui é demais.\aEi, poderia dar um recado meu para a Zéfiro? Precisarei de abotoaduras de zircônio para usar com o paletó. Valeu!", + INCOMPLETE_PROGRESS : "Você deu o meu recado à Zéfiro?", + COMPLETE : "Abotoaduras de zircônio, certo? Bem, verei o que posso fazer por ele.\aSeja como for, você tem sido muito útil e não posso deixar você ir sem nada.\aAqui está um GRANDE acréscimo para ajudar a derrotar esses Cogs!", + }, + + # Drowsy Dave will give you teleport access to DL + # if he can stay awake long enough for you to finish. + 6271 : { QUEST : "Solano Sonolento está tendo problemas e você talvez possa ajudá-lo. Por que você não dá uma passada na loja dele?_where_", + }, + 6272 : { GREETING : "", + LEAVING : "", + QUEST : "O quê? Hã? Eu devo ter cochilado.\aSabe, esses edifícios de Cogs estão cheios de máquinas que realmente me dão um sono.\aEu ouço esse zumbido o dia inteiro e...\aHã? Ah, sim, tá certo. Se você pudesse se livrar de alguns desses edifícios de Cogs, eu conseguiria ficar acordado.", + INCOMPLETE_PROGRESS : "Zzzzz...hã? Ah, é você, _avName_.\aJá está de volta? Eu só estava tirando uma sonequinha.\aVolte quando acabar com esses edifícios.", + COMPLETE : "O quê? Eu caí no sono um minutinho.\aAgora que aqueles edifícios de Cogs viraram pó, finalmente posso relaxar.\aValeu pela ajuda, _avName_.\aVejo você depois! Acho que vou tirar uma sonequinha.", + }, + + # Teddy Blair has a piece of a cog suit to give you if you will + # clear out some cogs. Of course, his ear plugs make it tough. + 6281 : { QUEST : "Vá em frente e ligue para o Ursinho de P. Lúcia. Ele tem um trabalho para você._where_", + }, + 6282 : { GREETING : "", + LEAVING : "", + QUEST : "O que você disse? Não, eu não tenho um baralho pra você.\aAh, é um trabalho! Por que você não disse logo? Você precisa falar alto.\aEsses Cogs não me deixam hibernar. Se você ajudar a tornar a Sonholândia mais silenciosa,\aeu lhe darei uma coisinha.", + INCOMPLETE_PROGRESS: "Você derrotou os bogs? Que bogs?\aAh, os Cogs! Por que você não disse logo?\aHmm, ainda tem barulho. O que acha de derrotar mais alguns?", + COMPLETE : "Você se divertiu? Hã? Ah!\aVocê conseguiu! Beleza. Muito legal você ter me ajudado.\aEu achei isso nos fundos da loja, mas não tem utilidade para mim.\aTalvez você descubra o que fazer com isso. Até logo, _avName_!", + }, + + # William Teller needs help! Those darn Cashbots swiped his 3 + # money bags to use in the Mint! Retrieve them and he'll give you + # another cog Suit piece. + 6291 : { QUEST : "Os Cogs arrombaram o Banco A Fraldinha de Dormir! Vá até o Guilherme Sonoleve e veja se você pode ajudá-lo.", + }, + 6292 : { QUEST : "Aqueles malditos Cogs do Robô Mercenário! Eles roubaram meus abajures de leitura!\aEu preciso deles de volta agora mesmo. Você pode procurar por eles?\aSe você encontrar meus abajures, talvez eu possa ajudar a encontrar o Diretor Financeiro.\aDepressa!", + INCOMPLETE_PROGRESS : "Eu preciso dos abajures de volta. Continue procurando!", + COMPLETE : "Você voltou! E trouxe meus abajures!\aNão tenho como agradecer o favor, mas posso dar isto a você.", + }, + + # Help Nina Nightlight get a bed in stock - + # she'll give you a suit part + 7201 : { QUEST : "Nana de Nina estava à sua procura, _avName_. Ela precisa de ajuda._where_", + }, + 7202 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Estou tão feliz em ver você, _avName_. Espero que possa me ajudar!\aAqueles malditos Cogs assustaram o pessoal da entrega e não tenho mais camas no estoque.\aPoderia ir ao Pedro Fuso e trazer uma cama para mim?_where_", + INCOMPLETE_PROGRESS : "O Pedro tinha alguma cama? Tinha certeza de que ele teria uma.", + COMPLETE : "", + }, + 7203 : { GREETING : "", + LEAVING : "", + QUEST : "Uma cama? Isso mesmo, aqui está uma prontinha para viagem.\aEntregue a cama pra Nana por mim, OK? Cama, Nana...\a\"Rimou!\" Há-há!\aMuito engraçado. Não? Bem, mas leve para ela, por favor.", + INCOMPLETE_PROGRESS : "A Nana gostou da cama?", + COMPLETE : "", + }, + 7204 : { GREETING : "", + LEAVING : "", + QUEST : "Essa cama não está legal. Ela é muito simples.\aVocê poderia ir até lá e ver se ele tem alguma coisa mais sofisticada?\aTenho certeza de que não vai demorar nadinha.", + INCOMPLETE_PROGRESS : "Estou certa de que o Pedro tem uma cama mais sofisticada.", + COMPLETE : "", + }, + 7205 : { GREETING : "", + LEAVING : "", + QUEST : "Não acertei na mosca com essa cama, não é? Tenho uma aqui que servirá.\aSó tem um pequeno problema: é preciso montá-la primeiro.\aEnquanto eu resolvo esse problema, você pode se livrar de alguns Cogs que estão lá fora?\aAqueles terríveis Cogs jogaram uma chave inglesa nos móveis.\aVolte quando terminar e a cama estará pronta.", + INCOMPLETE_PROGRESS : "Ainda não terminei a montagem da cama.\aQuando você tiver acabado com os Cogs, ela estará pronta.", + COMPLETE : "", + }, + 7206 : { GREETING : "", + LEAVING : "", + QUEST : "E aí _avName_!\aVocê fez um excelente trabalho com aqueles Cogs.\aA cama já está prontinha. Você pode entregá-la para mim?\aAgora que aqueles Cogs se foram, as coisas estão rápidas por aqui!", + INCOMPLETE_PROGRESS : "Acho que a Nana está esperando pela entrega da cama.", + COMPLETE : "Que cama adorável!\aAgora, meus clientes ficarão satisfeitos. Obrigada, _avName_.\aOlha só, talvez você possa usar isto. Alguém deixou isso aqui.", + }, + 7209 : { QUEST : "Vá até a Lua-de-Mel. Ela precisa de ajuda._where_", + }, + 7210 : { GREETING : "", + LEAVING : "", + QUEST : "Ah! Estou tão feliz em ver você, _avName_. Eu preciso muito de ajuda!\aNão consigo tirar o meu sono reparador há séculos. Veja você, aqueles Cogs roubaram a minha colcha.\aVocê pode correr e ver se o Marcelo tem alguma coisa em azul?_where_", + INCOMPLETE_PROGRESS : "O que o Marcelo falou sobre a colcha azul?", + COMPLETE : "", + }, + 7211 : { GREETING : "", + LEAVING : "", + QUEST : "Então, a Mel quer uma colcha, né?\aDe que cor? AZUL?!\aBem, eu terei de fazer uma especialmente para ela. Tudo o que eu tenho aqui é em vermelho.\aEscuta... Se você for derrotar alguns Cogs lá fora, farei uma colcha azul especialmente para ela.\aColchas azuis... O que será da próxima vez?", + INCOMPLETE_PROGRESS : "Ainda estou trabalhando na colcha azul, _avName_. Continue a derrotar esses Cogs!", + COMPLETE : "", + }, + 7212 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom ver você novamente. Tenho algo pra você!\aAqui está a colcha, e é azul. Ela vai adorar.", + INCOMPLETE_PROGRESS : "A Mel gostou da colcha?", + COMPLETE : "", + }, + 7213 : { GREETING : "", + LEAVING : "", + QUEST : "Minha colcha? Não, não está legal.\aÉ XADREZ! Como alguém pode dormir com uma estampa tão CHAMATIVA?\aVocê terá que levá-la de volta e trazer uma outra colcha.\aTenho certeza de que ele tem outras.", + INCOMPLETE_PROGRESS : "Eu simplesmente não vou aceitar uma colcha xadrez. Veja o que Marcelo pode fazer.", + COMPLETE : "", + }, + 7214 : { GREETING : "", + LEAVING : "", + QUEST : "O quê? Ela não gosta de XADREZ?\aHmm... Deixe-me ver o que eu tenho aqui.\aIsso vai levar algum tempo. Por que você não cuida de alguns Cogs enquanto eu tento encontrar algo diferente?\aTerei alguma coisa quando você estiver de volta.", + INCOMPLETE_PROGRESS : "Ainda estou procurando uma colcha diferente. Como está indo com os Cogs?", + COMPLETE : "", + }, + 7215 : { GREETING : "", + LEAVING : "", + QUEST : "Ei, bom trabalho com os Cogs!\aAqui está, é azul e não é xadrez.\aEspero que ela goste de estampado.\aLeve a colcha para a Mel.", + INCOMPLETE_PROGRESS : "Isso é tudo o que eu tenho para você agora.\aPor favor, leve esta colcha para a Mel.", + COMPLETE : "Ah! Que linda! Estampado combina muito bem comigo.\aÉ hora do meu sono reparador! Até logo, _avName_.\aO quê? Você ainda está aqui? Não vê que estou tentando dormir?\aTome isto aqui e me deixe descansar. Devo estar medonha!", + }, + + 7218 : { QUEST : "Dafne Sonolinda precisa de ajuda._where_", + }, + 7219 : { GREETING : "", + LEAVING : "", + QUEST : "Ah, _avName_, estou tão feliz em ver você! Aqueles Cogs levaram meus travesseiros.\aVocê pode ver se o Lelê tem alguns travesseiros?_where_\aTenho certeza de que ele pode ajudar.", + INCOMPLETE_PROGRESS : "O Lelê tem algum travesseiro para mim?", + COMPLETE : "", + }, + 7220 : { GREETING : "", + LEAVING : "", + QUEST : "Como vai? A Dafne precisa de alguns travesseiros, né? Bem, você veio ao lugar certo, parceria!\aHá mais travesseiros aqui do que espinhos em um cacto.\aAqui está, _avName_. Leve estes para Dafne, com os meus cumprimentos.\aÉ sempre um prazer ajudar uma mocinha.", + INCOMPLETE_PROGRESS : "Os travesseiros eram macios o suficiente para uma pequena dama?", + COMPLETE : "", + }, + 7221 : { GREETING : "", + LEAVING : "", + QUEST : "Você trouxe os travesseiros! Valeu!\aEi, espere um segundo! Esses travesseiros são muito macios.\aMacios demais para mim. Preciso de travesseiros mais duros.\aLeve estes de volta para o Lelê e veja o que mais ele tem. Valeu.", + INCOMPLETE_PROGRESS : "Não! Muito macios. Peça ao Lelê outros travesseiros.", + COMPLETE : "", + }, + 7222 : { GREETING : "", + LEAVING : "", + QUEST : "Muito macios, né? Bem, deixe-me ver o que tenho....\aHmm... Eu achava que tinha um montão de travesseiros duros. Onde eles estão?\aAh! Lembrei. Eu estava vendo se conseguia devolvê-los, então devem estar no estoque.\aQue tal eliminar alguns desses edifícios de Cogs lá fora enquanto eu pego os travesseiros no estoque, parceria?", + INCOMPLETE_PROGRESS : "Os edifícios de Cog são duros de roer. Mas esses travesseiros não são.\aContinuarei procurando.", + COMPLETE : "", + }, + 7223 : { GREETING : "", + LEAVING : "", + QUEST : "Já está de volta? Está tudo bem. Veja, encontrei os travesseiros que a Dafne queria.\aAgora é só levar para ela. Eles são duros o suficiente para quebrar um dente!", + INCOMPLETE_PROGRESS : "É, esses travesseiros são bastante duros. Espero que a Dafne goste deles.", + COMPLETE : "Eu sabia que o Lelê teria alguns travesseiros mais duros.\aAh sim, estes são perfeitos. Bons e duros.\aPor acaso esta peça de vestimenta de Cog seria útil para você? Pode levar.", + }, + + # Sandy Sandman lost her pajamas but Big Mama + # and Cat can help her out. If you hang in there, + # you'll get another Cog Suit part. + 7226 : { QUEST : "Passe lá na Cuca P. Gol. Ela perdeu o pijama._where_", + }, + 7227 : { GREETING : "", + LEAVING : "", + QUEST : "Não tenho pijamas! Eles sumiram!\aO que vou fazer? Ah! Já sei!\aVá até a Mama. Ela terá pijamas para mim._where_", + INCOMPLETE_PROGRESS : "A Mama tem pijamas para mim?", + COMPLETE : "", + }, + 7228 : { GREETING : "", + LEAVING : "", + QUEST : "E aí, pequeno Toon? A Mama tem os melhores pijamas das Bahamas.\aAh, quer algo para a Cuca P. Gol, né? Bem, deixe-me ver o que tenho aqui.\aAqui está. Agora, ela pode dormir com estilo!\aVocê pode correr e levar isso para ela? Não posso deixar a loja sozinha agora.\aObrigada, _avName_. Vejo você por aí!", + INCOMPLETE_PROGRESS : "Você precisa levar esse pijama para a Cuca P. Gol._where_", + COMPLETE : "", + }, + 7229 : { GREETING : "", + LEAVING : "", + QUEST : "A Mama mandou esse para mim? Ah...\aEla não tem nenhum pijama com pés?\aEu sempre uso pijamas com pés. Todo mundo usa esse tipo de pijama...\aLeve este de volta e peça a ela que encontre um com pés.", + INCOMPLETE_PROGRESS : "Meu pijama precisa ter pés. Veja o que a Mama pode fazer.", + COMPLETE : "", + }, + 7230 : { GREETING : "", + LEAVING : "", + QUEST : "Pés? Deixe-me pensar....\aEspere aí! Eu tenho um perfeito!\aTchan! Pijama com pés. Um lindo pijama azul com pés. O melhor de toda a face da terra.\aVocê pode levar para ela? Valeu!", + INCOMPLETE_PROGRESS : "A Cuca P. Gol gostou do pijama azul com pés?", + COMPLETE : "", + }, + 7231 : { GREETING : "", + LEAVING : "", + QUEST : "Bem, este TEM pés, mas não posso usar pijama azul!\aPergunte à Mama se ela tem uma cor diferente.", + INCOMPLETE_PROGRESS : "Tenho certeza de que a Mama tem pijamas em uma cor diferente e com pés.", + COMPLETE : "", + }, + 7232 : { GREETING : "", + LEAVING : "", + QUEST : "Que pena. Estes são os únicos pijamas com pés que eu tenho.\aAh, tive uma idéia. Vá perguntar à outra Cuca. Ela talvez tenha algum pijama com pés._where_", + INCOMPLETE_PROGRESS : "Não, aqueles são os únicos que eu tenho. Vá até a outra Cuca para ver o que ela tem._where_", + COMPLETE : "", + }, + 7233 : { GREETING : "", + LEAVING : "", + QUEST : "Pijama com pés? Sem dúvida.\aComo assim, este é azul? Ela não quer azul?\aNossa, vai ser um pouco difícil. Veja, que tal este?\aEle não é azul e TEM pés.", + INCOMPLETE_PROGRESS : "Eu adoro marrom, você não?\aEspero que a Cuca P. Gol goste....", + COMPLETE : "", + }, + 7234 : { GREETING : "", + LEAVING : "", + QUEST : "Não, este não é azul, mas ninguém com o meu tom de pele poderia usar marrom.\aNão e não. Ele vai fazer o caminho de volta, e você irá com ele! Veja o que mais a Cuca tem.", + INCOMPLETE_PROGRESS : "A Cuca deve ter mais pijamas. Nada de marrom!", + COMPLETE : "", + }, + 7235 : { GREETING : "", + LEAVING : "", + QUEST : "Não pode ser marrom também. Hmm....\aEu sei que tenho outros.\aVai demorar um pouquinho para encontrá-los. Vamos fazer um trato.\aEu procuro outro pijama se você derrotar alguns desses edifícios de Cog. Eles perturbam demais.\aTerei o pijama quando você voltar, _avName_.", + INCOMPLETE_PROGRESS : "Você precisa eliminar mais alguns edifícios de Cog enquanto eu procuro outro pijama.", + COMPLETE : "", + }, + 7236 : { GREETING : "", + LEAVING : "", + QUEST : "Você fez um excelente trabalho com esses Cogs! Valeu!\aAchei este pijama para a Cuca P. Gol; espero que ela goste.\aLeve-o para ela. Obrigada.", + INCOMPLETE_PROGRESS : "A Cuca P. Gol está esperando pelo pijama, _avName_.", + COMPLETE : "Um pijama fúcsia com pés! Perr-feito!\aAh, agora estou pronta. Vejamos....\aAcho que devo lhe dar alguma coisa por ter me ajudado.\aTalvez você possa usar isto. Alguém deixou aqui.", + }, + + # Smudgy Mascara needs Wrinkle Cream but + # 39's missing ingredients. Help them out + # and get a piece of Cog suit + 7239 : { QUEST : "Vá até a Máki Agem. Ela está procurando ajuda._where_", + }, + 7240 : { GREETING : "", + LEAVING : "", + QUEST : "Aqueles malditos Cogs levaram meu creme para rugas!\aMeus clientes PRECISAM do creme para rugas enquanto eu trabalho neles.\aVá até o Dedé Descanso e veja se ele tem a minha fórmula especial no estoque._where_", + INCOMPLETE_PROGRESS : "Eu me recuso a trabalhar em alguém sem o creme para rugas.\aVeja o que o Dedé Descanso tem para mim.", + }, + 7241 : { GREETING : "", + LEAVING : "", + QUEST : "A Máki Agem é uma figura exigente. Ela não vai se contentar com a minha fórmula comum.\aIsso significa que eu precisarei de alguns corais de couve-flor, meu ingrediente especial supersecreto. Mas eu não tenho nada no estoque.\aVocê poderia pescar alguns na lagoa? Assim que você conseguir os corais, eu farei um lote de creme para a Máki Agem.", + INCOMPLETE_PROGRESS : "Precisarei do coral de couve-flor para fazer o lote de creme para rugas.", + }, + 7242 : { GREETING : "", + LEAVING : "", + QUEST : "Uau, que belo coral de couve-flor!\aOk, vejamos... Um pouco disto e uma pitada daquilo... Agora, um bocado de alga-marinha.\aEssa não, onde está a alga-marinha? Parece que estou sem alga-marinha também.\aVocê pode voltar à lagoa e pescar uma boa alga-marinha viscosa?", + INCOMPLETE_PROGRESS : "Nem uma laminazinha de alga-marinha viscosa na loja.\aNão posso fazer o creme sem ela.", + }, + 7243 : { GREETING : "", + LEAVING : "", + QUEST : "Aaaah! Que ótima alga-marinha viscosa você trouxe, _avName_.\aAgora, é só espremer algumas pérolas no pilão.\aIh, onde está o meu pilão? Como vou fazer sem o pilão?\aAposto que aquele maldito Agiota o pegou quando esteve aqui!\aVocê precisa me ajudar a encontrá-lo! Ele estava indo ao Quartel do Robô Mercenário!", + INCOMPLETE_PROGRESS : "Eu simplesmente não consigo triturar as pérolas sem um pilão.\aMalditos Agiotas!", + }, + 7244 : { GREETING : "", + LEAVING : "", + QUEST : "Ótimo! Você trouxe o meu pilão!\aAgora voltemos ao trabalho. Triture aqui... Misture lá e...\aPronto! Diga à Máki Agem que é de boa qualidade e está fresquinho.", + INCOMPLETE_PROGRESS : "Você precisa entregar isso para a Máki Agem enquanto está fresco.\aEla é muito exigente.", + COMPLETE : "O Dedé Descanso não tinha frasco de creme maior que este? Não?\aBem, acho que vou pedir mais quando o meu acabar.\aAté logo, _avName_.\aO quê? Você ainda está aqui? Não vê que estou tentando trabalhar?\aTome isto aqui.", + }, + + # Lawbot HQ part quests + 11000 : { GREETING : "", + LEAVING : "", + QUEST : "Se está interessado em peças de disfarce de Robôs da Lei, visite _toNpcName_.\aOuvi dizer que ele precisa de ajuda na sua pesquisa sobre o clima._where_", + }, + 11001 : { GREETING : "", + LEAVING : "", + QUEST : "Sim, sim. Eu tenho peças de disfarce de Robôs da Lei.\aMas não tenho interesse nelas.\aO foco da minha pesquisa são as flutuações na temperatura ambiente de Toontown.\aEu troco com você as minhas peças de disfarce por termômetros de cogs.\aVocê pode começar em %s." % GlobalStreetNames[2100][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[2100][-1], + COMPLETE : "Ah, ótimo!\aComo eu temia...\aAh, é! Aqui está a sua peça de disfarce.", + }, + + 11002 : { GREETING : "", + LEAVING : "", + QUEST : "Para mais peças de disfarce, visite _toNpcName_ de novo.\aOuvi dizer que ele precisa de assistentes de pesquisa._where_", + }, + 11003 : { GREETING : "", + LEAVING : "", + QUEST : "Mais peças de disfarce de Robô da Lei?\aBem, se você insiste...\amas eu vou precisar de outro termômetro de Cog.\aDesta vez, procure em %s." % GlobalStreetNames[2200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[2200][-1], + COMPLETE : "Obrigado!\aE aqui está a sua peça de disfarce.", + }, + 11004 : { GREETING : "", + LEAVING : "", + QUEST : "Se precisa de mais peças de disfarce de Robô da Lei, vá falar com o _toNpcName_.\aOuvi que ele ainda precisa de ajuda com a pesquisa sobre o clima._where_", + }, + 11005 : { GREETING : "", + LEAVING : "", + QUEST : "Você está me saindo bastante útil!\aVocê pode dar uma ollhada em %s?" % GlobalStreetNames[2300][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[2300][-1], + COMPLETE : "Humm, não gostei muito da aparência disto...\amas aqui está a sua peça de disfarce...", + }, + 11006 : { GREETING : "", + LEAVING : "", + QUEST : "Você-sabe-quem precisa de mais medições de temperatura.\aDê uma passada se quiser mais uma peça de disfarce._where_", + }, + 11007 : { GREETING : "", + LEAVING : "", + QUEST : "Já de volta?\aQue dedicação...\aA próxima parada é %s." % GlobalStreetNames[1100][-1], + INCOMPLETE_PROGRESS : "Você já tentou observar %s?" % GlobalStreetNames[1100][-1], + COMPLETE : "Isso! Parece que você está pegando o jeito da coisa!\aA sua peça de disfarce...", + }, + 11008 : { GREETING : "", + LEAVING : "", + QUEST : "Se estiver a fim de mais uma peça de disfarce de Robô da Lei..._where_", + }, + 11009 : { GREETING : "", + LEAVING : "", + QUEST : "Engraçado encontrar você aqui!\aAgora, eu preciso de medições em %s." % GlobalStreetNames[1200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[1200][-1], + COMPLETE : "Muito obrigado.\aO seu disfarce deve estar quase pronto...", + }, + 11010 : { GREETING : "", + LEAVING : "", + QUEST : "Acredito que _toNpcName_ tem mais um trabalho para você._where_", + }, + 11011 : { GREETING : "", + LEAVING : "", + QUEST : "Que bom ver você de novo, _avName_!\aVocê pode fazer uma medição em %s, por favor?" % GlobalStreetNames[1300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[1300][-1], + COMPLETE : "Ótimo trabalho!\aAqui está a sua merecida recompensa!", + }, + 11012 : { GREETING : "", + LEAVING : "", + QUEST : "Você sabe o que fazer._where_", + }, + 11013 : { GREETING : "", + LEAVING : "", + QUEST : "_avName_, meu caro!\aVocê pode ir até %s e encontrar mais um termômetro para mim?" % GlobalStreetNames[5100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[5100][-1], + COMPLETE : "Excelente!\aCom a sua ajuda, a minha pesquisa está caminhando!\aAqui está a sua recompensa.", + }, + 11014 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ estava pedindo por você.\aParece que você causou uma boa impressão!_where_", + }, + 11015 : { GREETING : "", + LEAVING : "", + QUEST : "Bem-vindo de volta!\aEstive esperando.\aA próxima medição tem que ser em %s." % GlobalStreetNames[5200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[5200][-1], + COMPLETE : "Obrigado!\aAqui está sua recompensa.", + }, + 11016 : { GREETING : "", + LEAVING : "", + QUEST : "Se precisa completar o seu disfarce de Robô da Lei...\a_toNpcName_ pode ajudar você._where_", + }, + 11017 : { GREETING : "", + LEAVING : "", + QUEST : "Olá, Cientista de Pesquisas Iniciante!\aAinda precisamos de medições de %s." % GlobalStreetNames[5300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[5300][-1], + COMPLETE : "Ótimo trabalho!\aAqui está o seu negócio de Robô da Lei...", + }, + 11018 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tem outro trabalho para você.\aSe ainda não tiver se cansado dele..._where_", + }, + 11019 : { GREETING : "", + LEAVING : "", + QUEST : "Então.\aPronto para outra recuperação?\aDesta vez, tente %s." % GlobalStreetNames[4100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[4100][-1], + COMPLETE : "Mais um!\aNossa, você é a eficiência em pessoa!", + }, + 11020 : { GREETING : "", + LEAVING : "", + QUEST : "Ainda está atrás de peças de disfarce de Robô da Lei?_where_", + }, + 11021 : { GREETING : "", + LEAVING : "", + QUEST : "Você já deve ter adivinhado...\amas eu preciso de medições de %s." % GlobalStreetNames[4200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[4200][-1], + COMPLETE : "Quase lá!\aAqui está...", + }, + 11022 : { GREETING : "", + LEAVING : "", + QUEST : "Odeio dizer isto, mas..._where_", + }, + 11023 : { GREETING : "", + LEAVING : "", + QUEST : "O que acha de %s? Poderia conseguir um termômetro de lá também?" % GlobalStreetNames[4300][-1], + INCOMPLETE_PROGRESS : "Tentou procurar em %s?" % GlobalStreetNames[4300][-1], + COMPLETE : "Outro ótimo trabalho, _avName_", + }, + 11024 : { GREETING : "", + LEAVING : "", + QUEST : "Vá visitar o Professor se ainda precisar de peças de disfarce._where_", + }, + 11025 : { GREETING : "", + LEAVING : "", + QUEST : "Acho que ainda precisamos de uma medição de %s." % GlobalStreetNames[9100][-1], + INCOMPLETE_PROGRESS : "Tem certeza de que está procurando em %s?" % GlobalStreetNames[9100][-1], + COMPLETE : "Bom trabalho!\aAcho que estamos chegando perto...", + }, + 11026 : { GREETING : "", + LEAVING : "", + QUEST : "_toNpcName_ tem uma última missão para você._where_", + }, + 11027 : { GREETING : "", + LEAVING : "", + QUEST : "Já de volta?\aA medição final é em %s." % GlobalStreetNames[9200][-1], + INCOMPLETE_PROGRESS : "Você está procurando em %s, certo?" % GlobalStreetNames[9200][-1], + COMPLETE : "Está pronto!\aAgora, você já pode se infiltrar no Escritório do Promotor Público e coletar Avisos de Júri.\aBoa sorte e obrigado pela sua ajuda!", + }, + } + +# ChatGarbler.py +ChatGarblerDog = ["au", "arf", "grrrr"] +ChatGarblerCat = ["miau", "miu"] +ChatGarblerMouse = ["quick", "quiiii", "quiiiiquiiii"] +ChatGarblerHorse = ["rííírrrr", "brrr"] +ChatGarblerRabbit = ["ick", "iipr", "iipi", "iicki"] +ChatGarblerDuck = ["quá", "quack", "quáááck"] +ChatGarblerMonkey = ["ooh", "ooo", "ahh"] +ChatGarblerBear = ["growl", "grrr"] +ChatGarblerPig = ["oink", "oik", "snort"] +ChatGarblerDefault = ["blá"] + +# AvatarDetailPanel.py +AvatarDetailPanelOK = lOK +AvatarDetailPanelCancel = lCancel +AvatarDetailPanelClose = lClose +AvatarDetailPanelLookup = "Procurando detalhes de %s." +AvatarDetailPanelFailedLookup = "Não foi possível obter detalhes de %s." +AvatarDetailPanelOnline = "Região: %(district)s\nLocal: %(location)s" +AvatarDetailPanelOffline = "Região: off-line\nLocal: off-line" + +#PlayerDetailPanel +PlayerToonName = "Toon: %(toonname)s" +PlayerShowToon = "Mostrar Toon" +PlayerPanelDetail = "Detalhes do jogador" + +# AvatarPanel.py +AvatarPanelFriends = "Amigos" +AvatarPanelWhisper = "Cochichar" +AvatarPanelSecrets = "Segredos" +AvatarPanelGoTo = "Ir para" +AvatarPanelPet = "Mostrar Rabisco" +AvatarPanelIgnore = "Ignorar" +#AvatarPanelCogDetail = "Dept: %s\nNível: %s\n" +AvatarPanelCogLevel = "Nível: %s" +AvatarPanelCogDetailClose = lClose +AvatarPanelDetail = "Detalhes do Toon" + +# PetAvatarPanel.py +PetPanelFeed = "Alimentar" +PetPanelCall = "Chamar" +PetPanelGoTo = "Ir para" +PetPanelOwner = "Mostrar dono" +PetPanelDetail = "Detalhes do bichinho" +PetPanelScratch = "Coçar" + +# PetDetailPanel.py +PetDetailPanelTitle = "Adestramento" +# NOTE: these are replicated from OTPLocalizerEnglish sans "!" +PetTrickStrings = { + 0: 'Pular', + 1: 'Dar a pata', + 2: 'Fingir de morto', + 3: 'Rolar', + 4: 'Dar cambalhota', + 5: 'Dançar', + 6: 'Falar', + } + +# PetMood.py +PetMoodAdjectives = { + 'neutral': 'neutro', + 'hunger': 'faminto', + 'boredom': 'entediado', + 'excitement': 'animado', + 'sadness': 'triste', + 'restlessness': 'inquieto', + 'playfulness': 'brincalhão', + 'loneliness': 'solitário', + 'fatigue': 'cansado', + 'confusion': 'confuso', + 'anger': 'zangado', + 'surprise': 'surpreso', + 'affection': 'carinhoso', + } + +# DistributedAvatar.py +DialogExclamation = "!" +DialogQuestion = '?' + +# LocalAvatar.py +FriendsListLabel = "Amigos" + +# TeleportPanel.py +TeleportPanelOK = lOK +TeleportPanelCancel = lCancel +TeleportPanelYes = lYes +TeleportPanelNo = lNo +TeleportPanelCheckAvailability = "Tentando ir para %s." +TeleportPanelNotAvailable = "%s está ocupado(a) agora; tente novamente mais tarde." +TeleportPanelIgnored = "%s está ignorando você." +TeleportPanelNotOnline = "%s não está on-line neste momento." +TeleportPanelWentAway = "%s saiu." +TeleportPanelUnknownHood = "Você não sabe ir para %s!" +TeleportPanelUnavailableHood = "%s não está disponível agora; tente novamente mais tarde." +TeleportPanelDenySelf = "Você não pode ir lá por conta própria!" +TeleportPanelOtherShard = "%(avName)s está na região %(shardName)s, e você está na região %(myShardName)s. Deseja ir para %(shardName)s?" + +# DistributedBattleBldg.py +BattleBldgBossTaunt = "Sou o chefe." + +# DistributedBattleFactory.py +FactoryBossTaunt = "Sou o Supervisor." +FactoryBossBattleTaunt = "Deixe-me te apresentar ao Supervisor." +MintBossTaunt = "Sou o Supervisor." +MintBossBattleTaunt = "Você precisa falar com o Supervisor." +StageBossTaunt = "A minha Justiça não é Cega" +StageBossBattleTaunt = "Eu estou acima da Lei" + +# HealJokes.py +ToonHealJokes = [ + ["O que faz TIQUE-TIQUE-TIQUE-AU?", + "Um cãonômetro!"], + ["Por que o louco toma banho com o chuveiro desligado?", + "Porque ele comprou xampú para cabelos secos!"], + ["Por que é difícil para o fantasma contar mentiras?", + "Porque seus pensamentos são transparentes."], + ["Do que a bailarina é chamada quando machuca o pé e se recusa a dançar?", + "Pé-nóstica!"], + ["O que a vaca foi fazer no espaço?", + "Foi se encontrar com o vácuo!"], + ["Por que o gato mia para a Lua e a Lua não mia para o gato?", + "Porque astro-no-mia!"], + ["Por que as tartarugas não ficam bêbadas?", + "Porque elas só têm um casco!"], + ["Por que o elefante usa tênis vermelhos?", + "Porque os branquinhos sujam muito."], + ["Por que a galinha atravessa a rua?", + "Para chegar ao outro lado!"], + ["Qual é a maior injustiça do Natal?", + "O peru morre e a missa é do galo."], + ["Qual é o cúmulo dos trabalhos manuais?", + "Tricotar com a linha do trem."], + ["O que é um vulcão?", + "Uma montanha com soluço."], + ["O que é um pontinho vermelho, um azul e um rosa em cima de uma árvore?", + "Um morangotango com urublue num pinkenick."], + ["Por que o elefante não consegue tirar carteira de motorista?", + "Porque ele só dá trombada."], + ["O que um tijolo disse para o outro?", + "Existe um 'ciumento' entre nós."], + ["O que a porta disse para a chave?", + "Vamos dar uma voltinha."], + ["O que o elétron fala quando atende ao telefone?", + "Próton!"], + ["Quem é o rei da horta?", + "Rei Polho."], + ["Por que as pilhas são melhores que os políticos?", + "Porque elas têm, pelo menos, um lado positivo."], + ["O que Benjamin Franklin disse quando inventou a eletricidade?", + "Nada. Ele estava em estado de choque."], + ["Por que o cachorro balança o rabo?", + "Porque o rabo não tem força para balançar o cachorro."], + ["Qual é o cúmulo da força?", + "Dobrar a esquina."], + ["O que não é de comer, mas dá água na boca?", + "O copo."], + ["Quem é a mãe do mingau?", + "Mãe Zena."], + ["O que o Batman disse para o Robin na hora em que entraram no carro?", + "BAT a porta!"], + ["O que é um pontinho amarelo tomando sol?", + "É um fandango querendo virar baconzito."], + ["O que é um pontinho rosa no armário?", + "É um cupink."], + ["Quem é o tio da construção?", + "Tio Jolo."], + ["O que dá um cruzamento de um dálmata com um canário?", + "Uma onça pintada da Amazônia."], + ["O que é uma porção de letras voando?", + "Um bando de borboletras."], + ["O que é que viaja o mundo inteiro, mas fica o tempo todo em um canto só?", + "O selo."], + ["O que é um pontinho verde em cima de um amarelo no canto da parede?", + "Uma ervilha de castigo ajoelhada no milho."], + ["Por que o namoro da goiabada com o queijo não deu certo?", + "Porque o queijo era fresco."], + ["O que é um pontinho azul no guarda-roupas?", + "É uma bluesa."], + ["O que é um pontinho verde no fundo da piscina?", + "É uma ervilha... Segurando a respiração!"], + ["O que é um pontinho vermelho e azul voando de um lado para o outro?", + "Uma mosca fantasiada de Super-homem."], + ["Qual é o animal que tem mais de três olhos e menos de quatro?", + "O pi-olho, ou seja, 3,14."], + ["O que a aranha faz quando vai para a aula de dança?", + "Sapa-teia."], + ["Por que o pato tem ciúmes do cavalo?", + "Porque ele tem quatro patas."], + ["Quando você tem certeza de que um ovo não tem um pintinho dentro?", + "Quando o ovo é de pata."], + ["Por que ninguém apareceu no enterro do elefante?", + "Porque ninguém queria carregar o caixão."], + ["O que é que sempre aumenta, mas nunca diminui?", + "A idade."], + ["O que é que tem muitos pés, mas não fica de pé?", + "A centopéia."], + ["Em que espécie de mato se senta o elefante quando chove?", + "Mato molhado."], + ["Quem é que bate em você, mas você não revida?", + "O vento."], + ["O que é o cúmulo do contra-senso?", + "Na casa de saúde, só haver doentes."], + ["Quando um jogador de futebol é um literato?", + "Quando ele faz um gol de letra."], + ["Onde é que a sereia Ariel vê filmes?", + "No cinemaré."], + ["O que é que atravessa a porta, mas nunca entra nem sai?", + "A fechadura."], + ["Por que os rios são considerados preguiçosos?", + "Porque não saem dos seus leitos."], + ["Qual é a diferença entre a galinha e o tecido?", + "A galinha bota e o tecido desbota."], + ["Era uma vez uma orquestra que não tocava nada. Qual o nome do filme?", + "Os Intocáveis."], + ["Quando é que um gaúcho é chamado de mineiro?", + "Quando trabalha em uma mina."], + ["O que deveríamos colocar embaixo da forca para que o condenado não morra?", + "Cedilha!"], + ["O que é que todos nós temos, mas quando precisamos vamos ao mercado comprar?", + "Canela!"], + ["O que você faz quando está nadando em um oceano e um crocodilo ataca?", + "Você acorda."], + ["Quem é que nasce no rio, vive no rio e morre no rio, mas só se molha se quiser?", + "O carioca."], + ["O que é que está no fim de tudo?", + "A letra O."], + ["Qual é o único monstro que é bonzinho?", + "Good-zila."], + ["O que é a única coisa que o vencedor da maratona perde?", + "O fôlego."], + ["O que acontece se você alimentar uma vaca com flores?", + "Ela dará leite de rosas."], + ["O que é que tem seis olhos, mas não pode ver?", + "Três ratinhos cegos."], + ["Afinal, o que é que sempre encontramos no final do túnel?", + "A letra L."], + ["Qual a palavra que tem duas letras e três sílabas?", + "Arara!"], + ["Por que os elefantes são encontrados na África?", + "Porque eles são muito grandes para se esconderem."], + ["Onde estavam todos os moradores da cidade durante o último apagão?", + "No escuro."], + ["Quando é que o cliente fica preso no banco?", + "Quando fecha a conta-corrente."], + ["Quem é que vai a todos os casamentos sem ser convidado?", + "O padre."], + ["Por que os dinossauros têm pescoços longos?", + "Porque eles têm chulé."], + ["Qual é a mulher que sempre aparece antes do nascer do sol?", + "Aurora."], + ["Por que os elefantes nunca esquecem?", + "Porque ninguém nunca fala nada para eles."], + ["Qual é o país que o criminoso não gosta de visitar?", + "O Cana-dá."], + ["Por que o leão é considerado o rei das selvas?", + "Porque ele é macho; se fosse fêmea, seria rainha."], + ["O que é um 'fuio'?", + "É um 'buiaco na paiede'."], + ["Sou enrolado, tenho a cabeça rachada e vivo apertado?", + "Parafuso."], + ["Por que o cachorro rói o osso?", + "Porque ele não consegue engolir o osso inteiro."], + ["Como é que você impede um elefante de passar pelo buraco de uma agulha?", + "Dando um nó no rabo dele."], + ["Em que lugar do mundo, o sono é mais profundo?", + "No cemitério."], + ["O que é que é menor que a boca de uma formiga?", + "O que ela come."], + ["Um é pouco, dois é bom, três é demais. O que são quatro e cinco?", + "Nove."], + ["Qual é a corrente que, por mais forte que seja, não consegue segurar o navio?", + "A corrente marinha."], + ["O que é que tem boca e um só dente e chama a atenção de muita gente?", + "O sino."], + ["Qual deve ser o comprimento máximo de uma perna?", + "O suficiente para alcançar o chão."], + ["O que é uma molécula?", + "É uma 'Meninula Sapécula'."], + ["Como se pode escrever a maior palavra do mundo?", + "Com a caneta."], + ["Que refeição é colocada sobre a água e não afunda?", + "A bóia."], + ["Qual o melhor castigo para um time de futebol que joga sujo?", + "Levar um banho de gols."], + ["Por que os elefantes usam tênis de corrida?", + "Para fazer cooper, é claro."], + ["Por que os elefantes são grandes e cinza?", + "Porque, se eles fossem pequenos e amarelos, seriam canários."], + ["O que é que tem na árvore, no futebol, no chapéu e na casa?", + "Copa."], + ["O que é que deixa um cachorro desconfiado?", + "Uma pulga atrás da orelha."], + ["Por que o "+ Donald +" espalhou açúcar no travesseiro?", + "Porque ele queria ter doces sonhos."], + ["Por que o "+ Goofy +" levou o pente dele ao dentista?", + "Porque ele perdeu todos os dentes."], + ["Por que o "+ Goofy +" usa a camisa no banho?", + "Porque a etiqueta diz para lavar e usar."], + ["Qual o país está na granja e a capital está no pomar?", + "Peru, capital Lima."], + ["Qual é o prato preferido da maioria das pessoas?", + "O prato cheio."], + ["Como você chama uma pessoa que leva outra para almoçar?", + "Canibal."], + ["O que é um ponto amarelo no canto da sala?", + "É milho Santiago."], + ["O que é um ponto preto dentro do tubo de ensaio?", + "Uma blacktéria."], + ["Por que o "+ Pluto +" dorme com uma casca de banana?", + "Para pular da cama cedo."], + ["Por que o rato usa tênis marrom?", + "Porque o branco está lavando."], + ["O que é que a dentadura tem em comum com as estrelas?", + "Ela sai à noite."], + ["O que é um pontinho preto no meio da estrada?", + "É um calhamblack."], + ["Por que o arqueólogo foi à falência?", + "Porque sua carreira estava uma ruína."], + ["Como é que você ficaria se atravessasse o Atlântico no Titanic?", + "Ensopado."], + ["O que é um pontinho amarelo no alto de um prédio?", + "Um milho suicida."], + ["Por que é que o milho suicida quer se suicidar?", + "Porque o lugar onde ele mora é um bagaço."], + ["O que é um pontinho vermelho lá embaixo do prédio onde está o milho suicida?", + "Um milho bombeiro para salvar o milho suicida..."], + ["Qual a cor mais barulhenta?", + "A corneta."], + ["O que é que a banana suicida falou?", + "Macacos me mordam!!!"], + ["Qual o tipo de alimento de que o político mais gosta?", + "As massas."], + ["O que a chaminé grande falou para a chaminé pequena?", + "Você é muito jovem para fumar."], + ["O que é um pontinho vermelho no pântano?", + "É um jacared."], + ["O que é um pontinho azul no gramado?", + "Uma formiguinha de calça jeans."], + ["O que é um ponto brilhante no gramado?", + "Uma formiguinha de aparelho nos dentes."], + ["O que é um pontinho marrom na pré-história?", + "Um browntossauro."], + ["Como se chama um dinossauro que nunca se atrasa?", + "Prontossauro."], + ["O que é um pontinho vermelho num pedacinho de neve?", + "Uma miniatura da bandeira do Japão."], + ["O que é um pontinho dourado no gramado?", + "É uma formiguinha brincando de Jaspion."], + ["Qual e a comida que liga e desliga ?", + "O StrogON-OFF."], + ["Por que o livro de matemática ficou triste?", + "Porque ele tinha muitos problemas."], + ["O que o tomate foi fazer no banco?", + "Foi tirar extrato"], + ["Como se faz para transformar um giz numa cobra?", + "É só colocar o giz num copo de água. Aí o 'gizbóia'"], + ["Qual é o cúmulo da rapidez?", + "Fechar a gaveta, trancar e jogar a chave dentro."], + ["Qual é o cúmulo do egoísmo?", + "Não vou contar, só eu que sei."], + ["Qual é o cúmulo da revolta?", + "Morar sozinho, fugir de casa e deixar um bilhete dizendo que não volta mais."], + ["Qual é o cúmulo do exagero?", + "Passar manteiga no Pão de Açúcar."], + ["Qual é o cúmulo do arrependimento do carrasco?", + "Pois é, sempre que enforco alguém me dá um nó na garganta..."], + ["Qual é o cúmulo da visão?", + "Derrubar dez faixas-pretas com um golpe de vista."], + ["Qual é o cúmulo da sorte?", + "Ser atropelado por uma ambulância."], + ["Qual é o cúmulo da maldade?", + "Colocar tachinhas na cadeira elétrica."], + ["Qual é o cúmulo da burrice?", + "Ser reprovado no exame de fezes."], + ["Qual é o cúmulo da economia?", + "Usar o papel higiênico dos dois lados."], + ["Qual é o cúmulo do esquecimento?", + "Ih! Esqueci!"], + ["Qual é o cúmulo da sede?", + "Tomar um ônibus."], + ["O que que faz ABC...Slurp...DEF...Slurp?", + "Alguém tomando sopa de letrinhas."], + ["O que é que é verde e fica saltando sem parar em cima do sofá?", + "Uma ervilha que saiu do castigo."], + ["O que é que o tomate foi fazer no banco?", + "Tirar extrato."], + ["Por que o médico que trabalha à noite se veste de verde?", + "Porque ele está de plantão."], + ["O que é que é branco com pontinhos pretos e vermelhos?", + "Um dálmata com catapora."], + ["O que a galinha foi fazer na igreja?", + "Assistir à missa do galo."], + ["O que é o que é? Cai em pé e corre deitado?", + "Não é a chuva não! É uma minhoca de pára-quedas."], + ["Por que é que não é bom guardar o quibe no freezer?", + "Porque lá dentro ele esfirra."], + ["O que o advogado do frango foi fazer na delegacia?", + "Foi soltar a franga"], + ["Por que o galo canta de olhos fechados?", + "Porque ele já sabe a música de cor."], + ["Um peixe foi jogado de cima de um prédio de vinte andares. Que peixe era esse?", + "Um atum, porque quando ele caiu fez: Aaaaaaaaaaaa Tum!"], + ["Como se faz omelete de chocolate?", + "Com ovos de Páscoa."], + ["Para que servem óculos verdes?", + "Para verde perto."], + ["Para que servem óculos vermelhos?", + "Para 'vermelhor'."], + ["O que é verde por fora e amarela por dentro?", + "Uma banana disfarçada de pepino."], + ["Qual é a parte do carro que se originou no Antigo Egito?", + "Os faraóis."], + ["Como é que a bruxa sai na chuva?", + "De rodo."], + ["Por que o cachorro entrou na igreja?", + "Porque ele é um cão pastor."], + ["Quem é o pai do volante?", + "O painel."], + ["Como chamamos uma mulher que visitou uma plantação de uva?", + "Viúva."], + ["O que o amendoim falou para o elefante?", + "Nada, o amendoim não fala."], + ["O que os elefantes falam quando se esbarram?", + "Mundo pequeno esse, né?"], + ["O que o caixa falou para a registradora?", + "Estou contando com você."], + ["Por que o caminhão de frigorífico não sobe a ladeira?", + "Porque 'elingüiça'."], + ["Qual é a comida que liga e desliga?", + "É o strogON-OFF."], + ["O que a vaca foi fazer na Argentina?", + "Foi ver o Boi nos Ares."], + ["Qual é o peixe mais salgado que existe?", + "O sal-mão."], + ["O que é um cão indeciso?", + "É um 'cão-fuso'."], + ["Sabe por que o italiano não come churrasco?", + "Porque o macarrão não cabe no espeto."], + ["Qual é o cúmulo da rapidez?", + "Ir ao enterro de um parente e ainda encontrá-lo vivo."], + ["Qual é o cúmulo do azar?", + "Ser atropelado por um carro funerário."], + ["Por que o jacaré tomou o cartão de crédito do jacarezinho?", + "Porque o jacarezinho gastou muito e mandou o jacarepaguá."], + ["Qual é o cúmulo da burrice?", + "Olhar pelo buraco da fechadura numa porta de vidro."], + ["Qual é o cúmulo da confiança?", + "Jogar par-ou-ímpar pelo telefone?"], + ["Qual é o cúmulo da paciência?", + "Esvaziar uma piscina com conta-gotas."], + ["Qual é o cúmulo da traição?", + "Suicidar-se com uma punhalada nas costas."], + ["O que uma nuvem disse pra outra?", + "'Nu-vem' não."], + ["Qual é o cúmulo da moleza?", + "Correr sozinho e chegar em segundo."], + ["Por que o jacaré tirou o jacarezinho da escola?", + "Porque ele 'reptil'."], + ["Qual é o fim da picada?", + "Quando o mosquito vai embora."], + ["O que o pára-quedas disse para o pára-quedista?", + "Tô contigo e não abro."], + ["Qual é a cor mais barulhenta?", + "A corneta."], + ["O que é um pontinho amarelo no céu?", + "Um yellowcóptero."], + ] + +# MovieHeal.py +MovieHealLaughterMisses = ("hmm","hehe","ah","Rá rá") +MovieHealLaughterHits1= ("Ah ah ah","Ri, ri, ri","Ré, ré","Ah, ah") +MovieHealLaughterHits2= ("AH HAH HAH!","HO HO HO!","RÁ RÁ RÁ!") + +# MovieSOS.py +MovieSOSCallHelp = "%s SOCORRO!" +MovieSOSWhisperHelp = "%s precisa de ajuda na batalha!" +MovieSOSObserverHelp = "SOCORRO!" + +# MovieNPCSOS.py +MovieNPCSOSGreeting = "Oi %s! É uma satisfação ajudar você!" +MovieNPCSOSGoodbye = "Vejo você depois!" +MovieNPCSOSToonsHit = "Os Toons sempre acertam!" +MovieNPCSOSCogsMiss = "Os Cogs sempre erram!" +MovieNPCSOSRestockGags = "Reabastecendo com %s piadas!" +MovieNPCSOSHeal = "Curar" +MovieNPCSOSTrap = "Armadilha" +MovieNPCSOSLure = "Isca" +MovieNPCSOSSound = "Sonora" +MovieNPCSOSThrow = "Lançamento" +MovieNPCSOSSquirt = "Esguicho" +MovieNPCSOSDrop = "Cadente" +MovieNPCSOSAll = "Todos" + +# MoviePetSOS.py +MoviePetSOSTrickFail = "Suspiro" +MoviePetSOSTrickSucceedBoy = "Bom garoto!" +MoviePetSOSTrickSucceedGirl = "Boa menina!" + +# MovieSuitAttacks.py +MovieSuitCancelled = "CANCELADO\nCANCELADO\nCANCELADO" + +# RewardPanel.py +RewardPanelToonTasks = "Tarefas Toon" +RewardPanelItems = "Itens recuperados" +RewardPanelMissedItems = "Itens não-recuperados" +RewardPanelQuestLabel = "Buscar %s" +RewardPanelCongratsStrings = ["É isso aí!", "Parabéns!", "Uau!", + "Legal!", "Caraca!", "Toon-tástico!"] +RewardPanelNewGag = "Nova piada %(gagName)s para %(avName)s!" +RewardPanelUberGag = "%(avName)s ganhou a piada %(gagName)s com %(exp)s pontos de experiência!" +RewardPanelEndTrack = "Oba! %(avName)s chegou ao fim da Trilha de Piadas da piada %(gagName)s!" +RewardPanelMeritsMaxed = "Maximizados" +RewardPanelMeritBarLabel = "Méritos" +RewardPanelMeritBarLabels = [ "Bilhetes azuis", "Intimações", "Granas Cog", "Méritos" ] +RewardPanelMeritAlert = "Pronto para a promoção!" + +RewardPanelCogPart = "Você ganhou uma parte de disfarce de Cog!" +RewardPanelPromotion = "%s prepare-se para a promoção!" + +# Cheesy effect descriptions: (short desc, sentence desc) +CheesyEffectDescriptions = [ + ("Toon normal", "você ficará normal"), + ("Cabeção", "você ficará com uma cabeça grande"), + ("Cabecinha", "você ficará com uma cabeça pequena"), + ("Pernonas", "você ficará com pernas grandes"), + ("Perninhas", "você ficará com pernas pequenas"), + ("Toonzão", "você ficará um pouco maior"), + ("Toonzinho", "você ficará um pouco menor"), + ("Quadro reto", "você ficará em duas dimensões"), + ("Perfil reto", "você ficará em duas dimensões"), + ("Transparente", "você ficará transparente"), + ("Sem cor", "você ficará sem cor"), + ("Toon invisível", "você ficará invisível"), + ] +CheesyEffectIndefinite = "Até que escolha outro efeito, %(effectName)s%(whileIn)s." +CheesyEffectMinutes = "Nos próximos %(time)s minutos, %(effectName)s%(whileIn)s." +CheesyEffectHours = "Nas próximas %(time)s horas, %(effectName)s%(whileIn)s." +CheesyEffectDays = "Nos próximos %(time)s dias, %(effectName)s%(whileIn)s." +CheesyEffectWhileYouAreIn = " enquanto estiver %s" +CheesyEffectExceptIn = ", exceto em %s" + + +# SuitBattleGlobals.py +SuitFlunky = "Puxa-saco" +SuitPencilPusher = "Rato de Escritório" +SuitYesman = "Vaquinha de Presépio" +SuitMicromanager = "Micro\4empresário" +SuitDownsizer = "Facão" +SuitHeadHunter = "Caça-\4talentos" +SuitCorporateRaider = "Aventureiro Corporativo" +SuitTheBigCheese = "O Rei da Cocada Preta" +SuitColdCaller = "Rei da Incerta" +SuitTelemarketer = "Operador de Tele\4marketing" +SuitNameDropper = "Dr. Sabe-com-\4quem-está-\4falando" +SuitGladHander = "Amigo-da-Onça" +SuitMoverShaker = "Agitador" +SuitTwoFace = "Duas Caras" +SuitTheMingler = "Amizade Fácil" +SuitMrHollywood = "Dr. Celebridade" +SuitShortChange = "Farsante" +SuitPennyPincher = "Mão-de-vaca" +SuitTightwad = "Pão-duro" +SuitBeanCounter = "Conta-\4moedinha" +SuitNumberCruncher = "Destruidor de Números" +SuitMoneyBags = "Sacos de Dinheiro" +SuitLoanShark = "Agiota" +SuitRobberBaron = "Barão Ladrão" +SuitBottomFeeder = "Comensal" +SuitBloodsucker = "Sanguessuga" +SuitDoubleTalker = "Duplo Sentido" +SuitAmbulanceChaser = "Perseguidor de Ambulâncias" +SuitBackStabber = "Golpe Sujo" +SuitSpinDoctor = "Relações Públicas" +SuitLegalEagle = "Macaco velho" +SuitBigWig = "Figurão" + +# Singular versions (indefinite article) +SuitFlunkyS = "um Puxa-saco" +SuitPencilPusherS = "um Rato de Escritório" +SuitYesmanS = "uma Vaquinha de Presépio" +SuitMicromanagerS = "um Micro\4empresário" +SuitDownsizerS = "um Facão" +SuitHeadHunterS = "um Caça-talentos" +SuitCorporateRaiderS = "um Aventureiro Corporativo" +SuitTheBigCheeseS = "um Rei da Cocada Preta" +SuitColdCallerS = "um Rei da Incerta" +SuitTelemarketerS = "um Operador de Telemarketing" +SuitNameDropperS = "um Dr. Sabe-com-\4quem-está-\4falando" +SuitGladHanderS = "um Amigo-da-Onça" +SuitMoverShakerS = "um Agitador" +SuitTwoFaceS = "um Duas Caras" +SuitTheMinglerS = "um Amizade Fácil" +SuitMrHollywoodS = "um Dr. Celebridade" +SuitShortChangeS = "um Farsante" +SuitPennyPincherS = "um Mão-de-vaca" +SuitTightwadS = "um Pão-duro" +SuitBeanCounterS = "um Conta-\4moedinha" +SuitNumberCruncherS = "um Destruidor de Números" +SuitMoneyBagsS = "um Sacos de Dinheiro" +SuitLoanSharkS = "um Agiota" +SuitRobberBaronS = "um Barão Ladrão" +SuitBottomFeederS = "um Comensal" +SuitBloodsuckerS = "um Sanguessuga" +SuitDoubleTalkerS = "um Duplo Sentido" +SuitAmbulanceChaserS = "um Perseguidor de Ambulâncias" +SuitBackStabberS = "um Golpe Sujo" +SuitSpinDoctorS = "um Relações Públicas" +SuitLegalEagleS = "um Macaco velho" +SuitBigWigS = "um Figurão" + +# Plural versions +SuitFlunkyP = "Puxa-sacos" +SuitPencilPusherP = "Ratos de Escritório" +SuitYesmanP = "Vaquinhas de Presépio" +SuitMicromanagerP = "Micro\4empresários" +SuitDownsizerP = "Facões" +SuitHeadHunterP = "Caça-\4talentos" +SuitCorporateRaiderP = "Aventureiros Corporativos" +SuitTheBigCheeseP = "Os Reis da Cocada Preta" +SuitColdCallerP = "Reis da Incerta" +SuitTelemarketerP = "Operadores de Tele\4marketing" +SuitNameDropperP = "Drs. Sabe-com-\4quem-está-\4falando" +SuitGladHanderP = "Amigos-da-Onça" +SuitMoverShakerP = "Agitadores" +SuitTwoFaceP = "Duas Caras" +SuitTheMinglerP = "Amizades Fáceis" +SuitMrHollywoodP = "Drs. Celebridade" +SuitShortChangeP = "Farsantes" +SuitPennyPincherP = "Mãos-de-vaca" +SuitTightwadP = "Pães-duros" +SuitBeanCounterP = "Conta-\4moedinhas" +SuitNumberCruncherP = "Destruidores de Números" +SuitMoneyBagsP = "Sacos de Dinheiro" +SuitLoanSharkP = "Agiotas" +SuitRobberBaronP = "Barões Ladrões" +SuitBottomFeederP = "Comensais" +SuitBloodsuckerP = "Sanguessugas" +SuitDoubleTalkerP = "Duplos Sentidos" +SuitAmbulanceChaserP = "Perseguidores de Ambulâncias" +SuitBackStabberP = "Golpes Sujos" +SuitSpinDoctorP = "Relações Públicas" +SuitLegalEagleP = "Macacos velhos" +SuitBigWigP = "Figurões" + +SuitFaceOffDefaultTaunts = ['Buuuuu!'] + +SuitAttackDefaultTaunts = ['Pega essa!', 'Pode escrever!'] + +SuitAttackNames = { + 'Audit' : 'Auditoria!', + 'Bite' : 'Mordida!', + 'BounceCheck' : 'Cheque sem fundos!', + 'BrainStorm' : 'Grande idéia!', + 'BuzzWord' : 'Palavra-chave!', + 'Calculate' : 'Calcular!', + 'Canned' : 'Enlatado!', + 'Chomp' : 'Nhac!', + 'CigarSmoke' : 'Fumaça de charuto!', + 'ClipOnTie' : 'Prendedor de gravata!', + 'Crunch' : 'Triturar!', + 'Demotion' : 'Rebaixar!', + 'Downsize' : 'Reduzir!', + 'DoubleTalk' : 'Duplo sentido!', + 'EvictionNotice' : 'Aviso de despejo!', + 'EvilEye' : 'Mau-olhado!', + 'Filibuster' : 'Enchedor de lingüiça!', + 'FillWithLead' : 'Pelotão de frente!', + 'FiveOClockShadow' : "Barba por fazer!", + 'FingerWag' : 'Dedo na cara!', + 'Fired' : 'Fogo!', + 'FloodTheMarket' : 'Invadir o mercado!', + 'FountainPen' : 'Caneta-tinteiro!', + 'FreezeAssets' : 'Bens congelados!', + 'Gavel' : 'Martelo!', + 'GlowerPower' : 'Olhar raivoso!', + 'GuiltTrip' : 'Sentimento de culpa!', + 'HalfWindsor' : 'Nó francês!', + 'HangUp' : 'Desligar!', + 'HeadShrink' : 'Analista!', + 'HotAir' : 'Ar quente!', + 'Jargon' : 'Jargão!', + 'Legalese' : 'Legalês!', + 'Liquidate' : 'Liquidar!', + 'MarketCrash' : 'Queda da Bolsa!', + 'MumboJumbo' : 'Bobeira!', + 'ParadigmShift' : 'Desvio de paradigma!', + 'PeckingOrder' : 'Hierarquia!', + 'PickPocket' : 'Pivete!', + 'PinkSlip' : 'Bilhete azul!', + 'PlayHardball' : 'Jogo duro!', + 'PoundKey' : 'Tecla de Jogo-da-velha!', + 'PowerTie' : 'Gravata!', + 'PowerTrip' : 'Viajou na autoridade!', + 'Quake' : 'Terremoto!', + 'RazzleDazzle' : 'Agito!', + 'RedTape' : 'Burrocracia!', + 'ReOrg' : 'ReOrg!', + 'RestrainingOrder' : 'Repressão!', + 'Rolodex' : 'Agenda telefônica!', + 'RubberStamp' : 'Carimbo!', + 'RubOut' : 'Apagar!', + 'Sacked' : 'Ensacado!', + 'SandTrap' : 'Trincheira!', + 'Schmooze' : 'Bajula!', + 'Shake' : 'Tremor!', + 'Shred' : 'Retalho!', + 'SongAndDance' : 'Conta prosa!', + 'Spin' : 'Giro!', + 'Synergy' : 'Sinergia!', + 'Tabulate' : 'Tabular!', + 'TeeOff' : 'Tacada!', + 'ThrowBook' : 'Livro de lançamentos!', + 'Tremor' : 'Tremor!', + 'Watercooler' : 'Bebedouro!', + 'Withdrawal' : 'Retirada!', + 'WriteOff' : 'Baixa!', + } + +SuitAttackTaunts = { + 'Audit': ["Seus livros não têm balanço.", + "Parece que você está no vermelho.", + "Deixe-me ajudá-lo com esses livros.", + "Sua coluna de débitos é muito alta.", + "Vamos verificar os seus bens.", + "Assim, você vai ficar endividado.", + "Vamos conferir direitinho o que você deve.", + "Assim, a sua conta vai ficar zerada.", + "É hora de você se responsabilizar pelas suas despesas.", + "Encontrei um erro nos seus livros.", + ], + 'Bite': ["Quer uma mordida?", + "Dá uma mordida!", + "A sua mordida é maior do que você pode mastigar.", + "Minha mordida é maior do que o meu latido.", + "Morde logo!", + "Tome cuidado, eu mordo.", + "Eu não mordo só quando estou encurralado.", + "Só vou dar uma mordidinha.", + "Não dei uma mordida o dia todo.", + "Só quero uma mordida. É pedir muito?", + ], + 'BounceCheck': ["Ah, que pena, você não tem graça.", + "Você tem uma dívida.", + "Acho que este cheque é seu.", + "Você me devia isso.", + "Estou cobrando esta dívida.", + "Este cheque não vai ser mole.", + "Você será cobrado por isso.", + "Feche a conta.", + "Isso terá um custo para você.", + "Queria trocar por dinheiro.", + "Vou mandar isso de volta para você.", + "Esta conta está salgada.", + "Estou descontando o serviço.", + ], + 'BrainStorm':["Acho que vai chover.", + "Espero que você esteja com o guarda-chuva.", + "Quero orientar você.", + "Que tal uma saraivada básica?", + "Cadê o seu brilho agora, Toon?", + "Pronto para a chuvarada?", + "Vou atacar você como um furacão.", + "Chamo isso de ataque-relâmpago.", + "Adoro ser um desmancha-prazeres.", + ], + 'BuzzWord':["Desculpe-me se estou te aborrecendo.", + "Ouviu a última?", + "Veja se você pega esta.", + "Vamos cantarolar, Toon?", + "Deixe-me defender você.", + "Vou \"C\" perfeitamente claro.", + "Você devia \"C\" mais cuidadoso.", + "Veja se você consegue desviar desse enxame.", + "Cuidado, você está prestes a ser picado.", + "Parece que a sua urticária é séria.", + ], + 'Calculate': ["Estes números fazem mesmo uma diferença!", + "Você contou com isso?", + "Faça as contas, você está caindo.", + "Deixe-me ajudar você a somar isso.", + "Você registrou todas as suas despesas?", + "De acordo com os meus cálculos, você não ficará por muito tempo aqui.", + "Aqui está o total.", + "Uau, a sua conta está se multiplicando.", + "Tente brincar com esses números!", + Cogs + ": 1 Toons: 0", + ], + 'Canned': ["Gosta fora da lata?", + "\"Lata\" limpo?", + "Fresquinho, saído da lata!", + "Já foi atacado alguma vez por enlatados?", + "Gostaria de doar a você este enlatado!", + "Prepare-se para o \"Vira-lata\"!", + "Você acha que pode abrir a lata, na lata.", + "Vou te jogar na lata!", + "Vou transformar você em um a-Toon em lata!", + "Seu gosto não é tão bom fora da lata.", + ], + 'Chomp': ["Olha só esses comilões!", + "Nhac, nhac, nhac!", + "Aqui tem algo para mastigar.", + "Procurando alguma coisa para mastigar?", + "Por que você não mastiga um pouco disto?", + "Eu vou jantar você.", + "Adoro comer Toons no café-da-manhã!", + ], + 'ClipOnTie': ["Melhor se arrumar para a reunião.", + "Você não pode SAIR sem a gravata.", + "Os "+ Cogs +" mais bem vestidos usam isto." + "Experimente este tamanho.", + "Você devia se vestir para arrasar.", + "Sem gravata não tem serviço...", + "Precisa de ajuda para se vestir?", + "Nada é tão poderoso quanto uma boa gravata.", + "Vamos ver se serve.", + "Esta vai apertar você.", + "Você vai querer se vestir antes de SAIR.", + "Acho que vou dar uma gravata em você.", + ], + 'Crunch': ["Parece que você está espremido contra a parede.", + "Hora de mexer a mandíbula!", + "Vou dar alguma coisa para você mascar!", + "Triture isso!", + "Mordida para viagem.", + "Qual você prefere, molinho ou crocante?", + "Espero que esteja preparado para a hora da mandíbula.", + "Parece que você está ficando amassadinho!", + "Vou amassar você como uma latinha." + ], + 'Demotion': ["Você está descendo os degraus da empresa.", + "Vou mandar você de volta para a Expedição.", + "Está na hora de virar a sua placa de identificação.", + "Você está caidaço, palhaço.", + "Parece que você está ferrado.", + "Você não vai a lugar nenhum.", + "Você está em um beco sem saída.", + "Você não vai se mover tão cedo.", + "Você não vai a lugar nenhum.", + "Vai ficar registrado em seu arquivo permanente.", + ], + 'Downsize': ["Desce!", + "Sabe como descer?", + "Vamos entrar direto no assunto.", + "O que houve? Você parece deprimido.", + "Decaindo?", + "O que é que está caindo? Você!", + "Por que não tem alguém do meu tamanho?", + "Por que eu não meço você - ou será que é melhor dizer despeço?", + "Quer um tamanho menor por apenas mais uma moeda?", + "Experimente este tamanho!", + "Tem em um tamanho menor.", + "Este ataque é tamanho único!", + ], + # Hmmm - where is double talker? + 'EvictionNotice': ["Mudança à vista.", + "Arrume as malas, Toon.", + "É hora de arrumar outro lugar para morar.", + "Considere-se servido.", + "O seu aluguel está atrasado.", + "Isto vai te torpedear.", + "Você está prestes a ser despejado.", + "Vou espirrar você daqui.", + "Você está deslocado.", + "Prepare-se para ser realocado.", + "Você está abrigado.", + ], + 'EvilEye': ["Estou botando um mau-olhado em você.", + "Você fica de olho vivo nisso para mim?", + "Espere. Tem alguma coisa no meu olho.", + "Estou de olho em você!", + "Você pode botar o olho nisso aqui para mim?", + "Tenho um olho gordo danado.", + "Você vai levar um soco no olho!", + "Minha crueldade não está de molho, abre o olho!", + "Vou colocar você no olho do furacão!", + "Estou dando com os olhos em você.", + ], + 'Filibuster':["Devo encher?", + "Isso vai demorar um pouco.", + "Poderia fazer isso o dia todo.", + "Não preciso nem respirar fundo.", + "Vou fazendo, fazendo, fazendo...", + "Nunca fica cansado de fazer isso.", + "Posso tagarelar sem parar.", + "Tem problema se eu puxar a sua orelha?", + "Acho que vou papear à vontade.", + "Sempre consigo dar o meu recado.", + ], + 'FingerWag': ["Já te disse milhares de vezes.", + "Olha aqui, Toon.", + "Não me faça rir.", + "Não me faça ir até aí.", + "Já cansei de repetir.", + "Fim de papo, eu já falei.", + "\Você não tem respeito por nós, "+ Cogs +"." + "Acho que está na hora de você prestar atenção.", + "Blá, Blá, Blá, Blá, Blá.", + "Não me obrigue a interromper a reunião.", + "Será que eu vou ter que separar vocês?", + "Já passamos por isto antes.", + ], + 'Fired': ["É fogo! O jeito é fazer um churrasquinho.", + "Vai esquentar por aqui.", + "Assim, o frio passa.", + "Espero que você tenha sangue frio.", + "Quente, quentão e pelando.", + "Melhor parar tudo, deitar no chão e rolar!", + "Você está fora daqui.", + "O que você acha de \"bem-feito\"?", + "Pode dizer ai?", + "Espero que tenha usado protetor solar.", + "Está se sentindo um pouco tostado?", + "Você vai arder em chamas.", + "Você vai ficar aceso que nem fogueira.", + "Você está frito.", + "Eu sou fogo na roupa.", + "Só aticei o fogo um pouquinho, né?", + "Olha, um churrasquinho crocante.", + "Você não devia sair por aí malpassado.", + ], + 'FountainPen': ["Vai deixar mancha.", + "Vamos assinar embaixo.", + "Esteja preparado para alguns danos irreparáveis.", + "Você vai precisar de um bom tintureiro.", + "Você devia mudar.", + "Esta caneta-tinteiro tem uma tinta legal.", + "Aqui, vou usar a minha caneta.", + "Você entende a minha letra?", + "Isso é que é carregar nas tintas.", + "Seu desempenho babou.", + "Não é chato quando isso acontece?", + ], + 'FreezeAssets': ["Seus bens são meus.", + "Está sentindo um vento? É o cheque voador.", + "Espero que não tenha planos.", + "isso vai manter você na geladeira.", + "Tem uma brisa fria no ar.", + "O inverno está chegando mais cedo neste ano.", + "Você está sentido um calafrio?", + "Vou cristalizar o meu plano.", + "Você vai ver, no duro.", + "O gelo queima.", + "Espero que goste de frios.", + "Tenho muito sangue frio.", + ], + 'GlowerPower': ["Está olhando para mim?", + "Disseram que tenho olhos muito penetrantes.", + "Gosto de estar no fio da navalha.", + "Caçamba, caramba, meus quatro-olhos não são bambas?", + "Estou de olho em você, pirralho.", + "Que tal estes olhos expressivos?", + "Meus olhos são o meu forte.", + "Enche os olhos.", + "Estou de olho, piolho.", + "Olhe nos meus olhos...", + "Podemos dar uma espiada no seu futuro?", + ], + 'GuiltTrip': ["Você vai ficar com um baita sentimento de culpa!", + "Está se sentindo culpado?", + "É tudo culpa sua!", + "Sempre ponho a culpa de tudo em você.", + "Afogue-se na própria culpa!", + "Nunca mais falo contigo!", + "É melhor pedir desculpas.", + "Só vou perdoar você daqui a um milhão de anos!", + "Está preparado para viajar na maionese da culpa?", + "Ligue para mim quando voltar de viagem.", + "Quando você volta de viagem?", + ], + 'HalfWindsor': ["Esta é a gravata mais elegante que você já viu!", + "Procure não apertar tanto.", + "Você não viu nem metade do nó em que você se meteu.", + "Você tem sorte de eu não saber francês.", + "Esta gravata é demais para você.", + "Aposto como você nunca VIU um nó francês!", + "Esta gravata não é para o seu bico.", + "Eu não deveria ter gasto esta gravata com você.", + "Você não vale nem o nó desta gravata!", + ], + 'HangUp': ["Você foi desconectado.", + "Tchau!", + "Está na hora de terminar a sua conexão.", + "...e não ligue de novo!", + "Clique!", + "A conversa acabou.", + "Estou cortando este fio.", + "Acho que você está meio desligado.", + "Parece que você está com mau contato.", + "Seu tempo acabou.", + "Espero que tenha ouvido em claro e bom som.", + "Foi engano.", + ], + 'HeadShrink': ["Parece que você tem ido ao analista.", + "Querida, encolhi o analista.", + "Espero que não precise analisar o seu amor-próprio.", + "Você se abriu?", + "Analiso, logo existo.", + "Não é nada que faça você perder a cabeça.", + "Você vai abrir a cabeça?", + "Levanta essa cabeça! Ou será que é melhor abaixar?", + "Os objetos podem ser maiores do que parecem.", + "Os melhores Toons vêm nos menores frascos.", + ], + 'HotAir':["Estamos tendo uma discussão acalorada.", + "Está rolando uma onda de calor.", + "Atingi o meu ponto de ebulição.", + "Que vento cortante.", + "Odeio ter que te grelhar, mas...", + "Lembre-se sempre: onde há fumaça, há fogo.", + "Você parece meio queimadinho.", + "Outra reunião que virou fumaça.", + "Acho que está na hora de botar lenha na fogueira.", + "Deixe-me acender uma relação de trabalho.", + "Tenho umas observações inflamadas pra você.", + "Ataque aéreo!!!", + ], + 'Jargon':["Que besteira.", + "Veja se você consegue ver algum sentido nisso.", + "Espero que tenha sido claro como água.", + "Parece que vou ter que falar mais alto.", + "Insisto em ter a palavra.", + "Sou muito direto.", + "Devo sustentar a minha opinião neste assunto.", + "Olha, as palavras podem machucar você.", + "Entendeu o que eu quis dizer?", + "Palavras, palavras, palavras, palavras, palavras.", + ], + 'Legalese':["Você deve se conformar e desistir.", + "Você vai ser derrotado, legalmente falando.", + "Você está ciente das implicações legais?", + "Você não está acima da lei!", + "Devia haver uma lei contra você.", + "Não há lei marcial comigo!", + "As opiniões expressadas neste ataque não são compartilhadas pela Toontown On-line da Disney.", + "Não podemos ser responsabilizados por danos sofridos neste ataque.", + "Os resultados deste ataque podem variar.", + "Este ataque não tem validade legal quando proibido.", + "Você não se enquadra no meu sistema legal!", + "Você não sabe lidar com assuntos jurídicos.", + ], + 'Liquidate':["Gosto de manter as coisas fluindo.", + "Você está com algum problema de fluxo de caixa?", + "Vou ter que lavar os seus bens.", + "É hora de você ser levado pelo fluxo.", + "Não se esqueça de que fica escorregadio quando está molhado.", + "Os números estão correndo.", + "Você escorrega que nem sabão.", + "Está caindo tudo em cima de você.", + "Acho que você vai por ralo abaixo.", + "Você tomou uma lavada.", + ], + 'MarketCrash':["Vou acabar com a sua festa.", + "Você não vai sobreviver à queda.", + "Sou mais do que o mercado pode agüentar.", + "Tenho uma queda por você!", + "Agora eu vou entrar detonando.", + "Sou um verdadeiro dragão no mercado.", + "Parece que o mercado está em baixa.", + "É melhor você sair fora rapidamente!", + "Vender! Vender! Vender!", + "Devo liderar a recessão?", + "Todo mundo está saindo fora, você não vai?", + ], + 'MumboJumbo':["Deixe-me explicar melhor.", + "É muito simples.", + "Vamos fazer desta maneira.", + "Deixe-me ampliar para você.", + "Você pode chamar isso de baboseira tecnológica.", + "Aqui estão meus eufemismos.", + "Caramba, isso é que é encher a boca.", + "Algumas pessoas me chamam de exagerado.", + "Posso me meter?", + "Acho que estas são as palavras certas.", + ], + 'ParadigmShift':["Cuidado! Eu saio pela tangente.", + "Prepare-se para mudar radicalmente!" + "Não é uma mudança interessante?" + "Você vai ter que desviar de caminho.", + "Agora é sua vez de desviar.", + "Acabou o desvio!", + "Você nunca trabalhou tanto neste desvio.", + "Estou transviando você!", + "Olhe para o meu rabo de olho!", + ], + 'PeckingOrder':["Este aqui é para quem berra mais.", + "Prepare-se para o grito de guerra.", + "Por falta de um grito, morre um burro no atoleiro.", + "Vou ganhar no grito.", + "Você está no último grito da hierarquia.", + "Se gritos resolvessem, porcos não morreriam!", + "A ordem está valendo, no grito!", + "Por que não grito com alguém do meu tamanho? Ah!", + "Cão que ladra não morde.", + ], + 'PickPocket': ["Deixe-me verificar os seus pertences.", + "E aí, qual é o pó?", + "É mais fácil do que tirar doce de criança.", + "Golpe de mestre.", + "Deixa que eu seguro para você.", + "Não tire os olhos de minhas mãos.", + "As mãos são mais rápidas que os olhos.", + "Não tenho nada para tirar da manga.", + "A gerência não se responsabiliza por extravio de itens.", + "Achado não é roubado.", + "Você nem vai sentir.", + "Dois pra mim, um pra você.", + "Está bom assim.", + "Você não vai precisar mesmo...", + ], + 'PinkSlip': ["Tente imaginar que está tudo azul.", + "Tá com medo? Você está azul!", + "Com certeza, este bilhete vai fazer você ficar azul.", + "Êpa, acho que mudei de cor, né?", + "Olha lá, você não quer ficar azul, ou quer?", + "Este bilhete não é branco, é azul.", + "Estou azul de fome!", + "Você se importa que eu passe aí para ver se está tudo azul?", + "O azul não é exatamente a sua cor.", + "Toma seu bilhete azul e fora daqui!", + ], + 'PlayHardball': ["Então você quer jogar bola comigo?", + "Você não quer jogar bola comigo.", + "Chuta forte!", + "Passa, cara, passa!", + "Aí está o passe...", + "Você vai precisar de um refresco do goleiro.", + "Vou jogar você para fora do campo.", + "Depois que você se contundir, vai direto para casa.", + "São 45 minutos do segundo tempo!", + "Você não consegue jogar comigo!", + "Vou atingir você.", + "Vou dar um chute com efeito na bola!", + ], + 'PoundKey': ["É hora de retornar algumas ligações.", + "Gostaria de fazer uma ligação a cobrar.", + "Trrriiimmm - é para você!", + "Você quer brincar com o Jogo-da-Velha?", + "Tenho um método incrível para ganhar.", + "Está se sentindo nocauteado?", + "Vou dar um golpe neste número.", + "Deixe-me ligar para fazer uma surpresinha.", + "Vou ligar para você.", + "O.K. Toon, é o fim para você.", + ], + 'PowerTie': ["Eu ligo mais tarde, você parece enrolado na gravata.", + "Você está pronto para uma gravata?", + "Senhoras e senhores, esta é a gravata!", + "É melhor aprender a dar este nó.", + "Vou manter a sua língua dentro do nó!", + "É a gravata mais horrível que você já comprou!", + "Está sentindo o aperto?", + "Minha gravata é muito mais poderosa que a sua!", + "Eu tenho o poder do nó!", + "Pelos poderes do nó, vou engravatar você.", + ], + 'PowerTrip': ["Faça as malas, vamos fazer uma pequena viagem.", + "Você fez uma boa viagem?", + "Boa viagem, acho que nos veremos na próxima temporada.", + "Como foi a viagem?", + "Desculpe ter \"viajado\" dessa maneira!", + "Você parece viajandão.", + "Agora, você sabe quem é a autoridade!", + "Tenho muito mais autoridade do que você.", + "Quem manda agora?", + "Você não pode lutar contra o poder.", + "O poder corrompe, principalmente em minhas mãos!", + ], + 'Quake': ["Vamos balançar, agitar e rolar.", + "Tem muita vibração por aqui!", + "As suas canelas estão tremendo.", + "Aí vem ele, este é grande!", + "Este está fora da escala Richter.", + "Agora é que a terra vai tremer!", + "E aí, quem é que está agitando? Você!", + "Já esteve em um terremoto?", + "Agora, você está em território de tremores!", + ], + 'RazzleDazzle': ["Leia os meus lábios.", + "Que acha da minha dentadura?", + "Não acha que tenho charme?", + "Vou impressionar você.", + "Meu dentista faz um excelente trabalho.", + "Cegadores, não acha?", + "Não dá nem para acreditar que não é de verdade.", + "Chocante, né?", + "Vou dar um fim nisso.", + "Passo o fio dental após cada refeição.", + "Sorria!", + ], + 'RedTape': ["Isto deve acalmar o bicho.", + "Vou te amarrar por um tempo.", + "Você está acorrentado.", + "Veja se consegue cortar caminho por aqui.", + "O bicho vai pegar.", + "Tomara que você tenha claustrofobia.", + "Vou me certificar de que você não vai escapulir.", + "Vou ocupar você com alguma coisa.", + "Tente desatar o nó.", + "Espero que você concorde com os tópicos da reunião.", + ], + 'ReOrg': ["Você não gostou da maneira como eu reorganizei as coisas!", + "Talvez um pouco de organização seja bom.", + "Você não é tão ruim assim, só precisa se organizar.", + "Você gosta do meu tino para organização?", + "Só pensei em dar um novo visual às coisas.", + "Você precisa se organizar!", + "Você parece um pouco desorganizado.", + "Espera um pouco enquanto eu reorganizo os seus pensamentos.", + "Só vou esperar até que você se organize um pouco mais.", + "Você se importa se eu só der uma reorganizadinha?", + ], + 'RestrainingOrder': ["Você precisa levar broncas de vez em quando.", + "Estou te jogando na cara uma ordem repressora!", + "Você não pode chegar nem um metro e meio perto de mim.", + "Talvez seja melhor você manter distância.", + "Entre na linha.", + Cogs + "! Reprimam este Toon!", + "Tente entrar na linha sozinho.", + "Espero que eu esteja sendo bem repressor com você.", + "Veja se você consegue acabar com essa repressão!", + "Estou ordenando que você se reprima!", + "Por que não começamos com uma repressão básica?" + ], + 'Rolodex': ["O seu cartão está aqui, em algum lugar.", + "Aqui está o número do dedetizador.", + "Quero dar o meu cartão a você.", + "Tenho o seu número bem aqui.", + "Tenho tudo aqui sobre você, de A a Z.", + "Você vai se virar com isso.", + "Dê um giro pelas páginas.", + "Cuidado com a papelada solta.", + "Vou apontar o dedo para a letra que desejo.", + "É assim que eu consigo entrar em contato com você?", + "Quero ter certeza de que manteremos o contato.", + ], + 'RubberStamp': ["Eu sempre causo uma boa impressão.", + "É importante aplicar uma pressão firme e bem distribuída.", + "Impressos perfeitos todas as vezes.", + "Quero carimbar você.", + "Você precisa ser DEVOLVIDO AO REMETENTE.", + "Você foi CANCELADO.", + "Você possui uma entrega de PRIORIDADE.", + "Vou me certificar de que a minha mensagem foi RECEBIDA.", + "Você não vai a lugar nenhum - você tem uma TARIFA POSTAL A PAGAR.", + "Preciso de uma resposta IMEDIATA.", + ], + 'RubOut': ["E agora, desapareceu!", + "Sinto que perdi você em algum lugar.", + "Decidi deixar você de fora.", + "Eu sempre apago todos os obstáculos.", + "Vou só apagar este erro.", + "Posso fazer qualquer perturbação desaparecer.", + "Gosto das coisas organizadas e limpas.", + "Tente manter a animação.", + "Estou vendo você... Agora, não vejo você.", + "Vai ficar meio esmaecido.", + "Vou eliminar o problema.", + "Deixe-me cuidar das suas áreas problemáticas.", + ], + 'Sacked':["Parece que você foi embrulhado.", + "Está no saco.", + "Você foi embolsado.", + "Papel ou plástico?", + "Meus inimigos serão ensacados!", + "Eu tenho o recorde de Toontown de sacos por jogo.", + "Você não é mais bem-vindo por aqui.", + "O seu tempo acabou aqui, você vai ser ensacado!", + "Deixe-me ensacar isto para você.", + "Nenhuma defesa se iguala ao meu ataque com sacos!", + ], + 'Schmooze':["Você nunca vai ver quando chega.", + "Vai ficar legal em você.", + "Você conseguiu.", + "Não quero despejar nada em você.", + "Como puxa-saco, eu vou longe.", + "Agora, eu vou florear bastante.", + "É hora de carregar nas tintas.", + "Vou ressaltar o seu lado bom.", + "Isso merece um bom tapinha nas costas.", + "Vou falar bem de você para todo mundo.", + "Detesto tirá-lo do seu pedestal, mas...", + ], + 'Shake': ["Você está bem no epicentro.", + "Você está em cima da falha.", + "Vai ser um sacolejo só.", + "Acho que isso é um desastre natural.", + "É um desastre de proporções sísmicas.", + "Este está fora da escala Richter.", + "É hora de entrar na toca.", + "Você parece perturbado.", + "Preparado para os solavancos?", + "Você vai sacolejar, e não centrifugar.", + "Isso vai agitar você.", + "Sugiro um bom plano de fuga.", + ], + 'Shred': ["Preciso me livrar de alguns fragmentos perigosos.", + "As porções produzidas estão aumentando de quantidade.", + "Acho que vou dispor de você agora mesmo.", + "Assim, a prova é eliminada.", + "Não há como provar isso agora.", + "Veja se você consegue juntar os pedaços novamente.", + "Assim, você vai cortar as sobras e ficar do tamanho certo.", + "Vou retalhar esta idéia todinha.", + "Não queremos que isto caia nas mãos erradas.", + "Fácil se tem, fácil se perde.", + "Não é o seu último fio de esperança?", + ], + 'Spin': ["O que me diz de sairmos para um giro?", + "Você usa a centrifugação?", + "Isto vai fazer a sua cabeça girar de verdade!", + "Este é o meu giro das coisas.", + "Vou levar você para uma volta.", + "Como é que você dá a \"volta\" no seu tempo?", + "Olha só: você não quer girar até ficar tonto?", + "Nossa, você está no meio de um furacão!", + "Meus ataques vão fazer sua cabeça rodar!", + ], + 'Synergy': ["Vou encaminhar ao comitê.", + "O seu projeto foi cancelado.", + "O seu centro de custos será cortado.", + "Estamos reestruturando o seu setor.", + "Colocamos em votação, e você perdeu.", + "Acabei de receber a aprovação final.", + "Uma boa equipe pode se livrar de qualquer problema.", + "Já dou um retorno a você sobre isso.", + "Vamos direto ao que interessa.", + "Vamos encarar isto como uma crise de sinergia.", + ], + 'Tabulate': ["Isto não soma em nada.", + "Pela minha conta, você perdeu.", + "Você está fazendo um bom cálculo.", + "Vou fazer o seu total em um minuto.", + "Está preparado para estes números?", + "Sua conta já está vencida e pode ser paga.", + "É hora de calcular.", + "Gosto de colocar as coisas em ordem.", + "E a contagem é...", + "Estes números devem ser muito poderosos.", + ], + 'TeeOff': ["Você não vai bem de condições.", + "Olha a frente!", + "Confio no meu taco.", + "Gandula, preciso do meu taco!", + "Tente evitar este risco.", + "Dê impulso!", + "É mesmo um furo dentro do outro.", + "Você está no meu campo.", + "Repara só a precisão.", + "Cuidado com o passarinho!", + "Fique de olho na bola!", + "Você se importa se eu continuar a jogar?", + ], + 'Tremor': ["Você sentiu?", + "Você não tem medo de um tremorzinho de nada, ou tem?", + "O tremor é apenas o começo.", + "Você parece tenso.", + "Vou agitar as coisas um pouco!", + "Tudo preparado para retumbar?", + "O que houve? Você parece balançado.", + "Tremedeira de medo!", + "Por que está tremendo de medo?", + ], + 'Watercooler': ["Certamente, isto vai refrescar você.", + "Não é refrescante?", + "Faço a entrega.", + "Direto da fonte - até a sua boca.", + "Qual é o problema, é só uma água de nascente.", + "Não se preocupe, é pura.", + "Ah, outro cliente satisfeito.", + "É hora da entrega diária.", + "Espero que as suas cores não desbotem.", + "Quer beber?", + "Sai tudo na lavagem.", + "A bebida é com você.", + ], + 'Withdrawal': ["Acho que você está no vermelho.", + "Espero que o seu saldo seja o suficiente para cobrir isto.", + "Olha que vou cobrar juros.", + "O seu saldo está diminuindo.", + "Em breve, você vai precisar fazer um depósito.", + "Você sofreu um colapso financeiro.", + "Acho que você está em baixa.", + "Suas finanças decaíram.", + "Prevejo um período de vacas magras.", + "É uma inversão de valores.", + ], + 'WriteOff': ["Deixe-me aumentar as suas perdas.", + "Vamos tirar o melhor proveito possível de um mau negócio.", + "É hora de fazer o balanço dos caixas.", + "Isso não vai ficar bom nos livros-caixa.", + "Procuro alguns dividendos.", + "Você deve se responsabilizar por suas perdas.", + "Pode esquecer o bônus.", + "Vou bagunçar todas as suas contas.", + "Você está prestes a sofrer algumas perdas.", + "Isto vai afetar os seus resultados finais.", + ], + } + +# DistributedBuilding.py +BuildingWaitingForVictors = "Aguardando outros jogadores...", + +# Elevator.py +ElevatorHopOff = "Descer" + +# DistributedBuilding.py +# DistributedElevatorExt.py +CogsIncExt = ", Inc." +CogsIncModifier = "%s" + CogsIncExt +CogsInc = string.upper(Cogs) + CogsIncExt + +# DistributedKnockKnockDoor.py +DoorKnockKnock = "Toc, toc." +DoorWhosThere = "Quem é?" +DoorWhoAppendix = " quem?" +DoorNametag = "Porta" + +# FADoorCodes.py +# Strings associated with codes +FADoorCodes_UNLOCKED = None +FADoorCodes_TALK_TO_TOM = "Você precisa de piadas! Vá falar com o Tutorial Tom!" +FADoorCodes_DEFEAT_FLUNKY_HQ = "Volte aqui quando tiver derrotado o Puxa-saco!" +FADoorCodes_TALK_TO_HQ = "Vá pegar a sua recompensa com o Haroldo do Quartel!" +FADoorCodes_WRONG_DOOR_HQ = "Porta errada! Vá pela outra porta para o pátio!" +FADoorCodes_GO_TO_PLAYGROUND = "Direção errada! Você precisa ir para o pátio!" +FADoorCodes_DEFEAT_FLUNKY_TOM = "Ande até o Puxa-saco para lutar com ele!" +FADoorCodes_TALK_TO_HQ_TOM = "Vá pegar a sua recompensa no Quartel dos Toons!" +FADoorCodes_SUIT_APPROACHING = None # no message, just refuse entry. +FADoorCodes_BUILDING_TAKEOVER = "Cuidado! Tem um COG lá dentro!" +FADoorCodes_SB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Cog primeiro!\n\nMonte o seu Disfarce de Cog com pedaços da Fábrica." +FADoorCodes_CB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Robô Mercenário primeiro!\n\nMonte o seu Disfarce de Robô Mercenário executando Tarefas Toon na Sonholândia." +FADoorCodes_LB_DISGUISE_INCOMPLETE = "Você vai ser pego se entrar lá como um Toon! Você precisa completar o seu Disfarce de Cog primeiro!\n\nMonte o seu Disfarce de Cog com pedaços da Fábrica." + +# KnockKnock joke contest winners +KnockKnockContestJokes = { + 2100 : ["Jaque", + "Jaque não está olhando, joga uma torta nele!"], + + 2200 : ["Pirulita", + "Pirulita daqui, os Cogs estão chegando!"], + + 2300: ["Justa", + "Justa gora peguei uns dois pedaços de Cogs, pronto!"], + + # Polar Place has multiple jokes so they are in a dict keyed of the propId of the door + 3300: { 10: ["Aladdin", + "Aladdinheiro no chão."], + 6 : ["Adon", + "Adondé que esses Cogs tão saindo?"], + 30 : ["Bacon", + "Bacon uma torta ia bem."], + 28: ["Isaías", + "Isaías mas voltou-se."], + 12: ["Julieta", + "Julieta me chamando praquele prédio Cog com você pra eu te Toonar."], + }, + } + +# KnockKnockJokes.py +KnockKnockJokes = [ + ["Quem", + "Aqui tem eco, não?"], + + ["Kika", + "Kikalor!"], + + ["Joe", + "Você é Joetromundo?"], + + ["Eudin", + "Eudinovo por aqui!"], + + ["Silêncio", + "Pssss!"], + + ["Simbó", + "Simbora pra praia."], + + ["Takent", + "Takent ou tá frio?"], + + ["Noá", + "Noá de quê."], + + ["Não sei", + "Nem eu, já te falei."], + + ["Otudo", + "Otudo ou nada?"], + + ["Totan", + "Totan feliz que você está aqui!"], + + ["Osmar", + "Osmartodontes não existem mais!"], + + ["Silem", + "Silembra de mim?"], + + ["Ostra", + "Ostra vez?"], + + ["Aimée", + "Aimée Tida?"], + + ["Zoom", + "Zooma imediatamente daqui!"], + + ["Aiki", + "Aiki medo!"], + + ["Quiba", + "Quibagunça é essa"], + + ["Tasó", + "Não, tacompanhado."], + + ["Iago", + "Iagora, José?"], + + ["'Tácom'", + "'Tácom' tudo, não é?"], + + ["Tádi", + "Tádi graça, é? Meu nome é "+Flippy+"."], + + ["Opato", + "Opato "+Donald+" Deduct."], + + ["Masqui", + "Masqui coisa, abre a porta logo."], + + ["Nénim.", + "Nénim guém que te interesse, deixa eu entrar."], + + ["Omos", + "Omosquito que te picou."], + + ["Colés", + "Colesterol faz mal, sai fora."], + + ["Breno", + "Breno que eu te falei que esse cara vinha."], + + ["Kiko", + "Kiko-losso!"], + + ["Vaivê", + "Vaivê que eu tô atrasado."], + + ["Quente", + "Quente viu e quem te vê!"], + + ["Vopri", + "Vopri-meiro, tô com medo."], + + ["Eunum", + "Eunum sei. Desculpe!"], + + ["Ubaldo", + "Ubaldo é o marido da balda?"], + + ["Alfa", + "Alface ou tomate?"], + + ["Ka", + "Ka, L, M, N, O, P."], + + ["Justa", + "Justagora que eu ia jantar."], + + ["Maki", + "Makiagem é coisa para adultos."], + + ["Loga", + "Logagora que eu entrei no banho."], + + ["Quessa", + "Quessa B? Vou me mandar."], + + ["Masqui", + "Masqui droga - abre a porta e pronto!"], + + ["'Jaques'", + "'Jaquesou' importante, você deveria falar comigo primeiro."], + + ["Midê", + "Midêxa em paz!"], + + ["Undi", + "Undia é da caça outro é do caçador."], + + ["Tudor", + "Tudor que sobe, desce."], + + ["Acara", + "Acara-puça serviu, hein?"], + + ["Aispe", + "Aispe-rança é a última que morre."], + + ["Kênia", + "Kênia sabe?"], + + ["Bemki", + "Bemki te vi lá fora."], + + ["Jaca", + "Jacaré no seco anda?"], + + ["Quenco", + "Quenco-chicha o rabo espicha."], + + ["Tádi", + "Tádi brincadeira, né? Deixa eu rir, então."], + + ["Ocessá", + "Ocessá-be um monte de coisa, né?"], + + ["Temki", + "Temki ter uma piada melhor que essa."], + + ["Cetá", + "Cetá pensando que eu sou besta?"], + + ["Vôti", + "Vôti botar pra correr."], + + ["Donalda", + "Donalda não... São cinqüenta centavos."], + + ["Alface", + "Alface a face é mais emocionante."], + + ["Ivo", + "Ivo cê não sabia que não tem ninguém em casa?"], + + ["Quessa", + "Quessa Bê? Essa brincadeira tá um saco."], + + ["Quenfo", + "Quenfo à roça perdeu a carroça."], + + ["Justa", + "Justagora que eu ia embora."], + + ["Taca", + "Taca mãe primeiro!"], + + ["Tanaka", + "'Tanaka-ra' que você não vai se dar bem!"], + + ["Quenfo", + "Quenfo ao ar perdeu o lugar!"], + + ["Soé", + "Soé jeito de falar com um amigo?"], + + ["Daum", + "'Daum' tempo, pode ser?"], + + ["Isadora", + "Isadora, o que é que eu faço?"], + + ["Vêssi", + "'Vêssi' da próxima vez toma mais cuidado!"], + + ["Teá", + "Teá-doro, mas isso também é demais."], + + ["Carlota", + "A Carlota está presa na roda?"], + + ["Bu", + "Eu nem me assustei."], + + ["Tu", + "Tu, cara de tatu!"], + + ["Pó", + "Pó-su entrar?"], + + ["Sará", + "Sará que que tem outro jeito de entrar neste prédio?"], + + ["Mico", + "Miconta que novidade é essa!"], + + ["Numi", + "Numi amola e me deixa entrar."], + + ["Miá", + "Miá-juda, a porta emperrou."], + + ["Nuncre", + "Nuncre dita em mim?"], + + ["Dianta", + "não Dianta falar, você não vai abrir a porta..."], + + ["Dorré", + "Mi, Fá, Sol, Lá, Si!"], + + ["Dexeu", + "Dexeu ver quem taí."], + + ["Tássia", + "Tássia-chando, hem? Abre logo."], + + ["Omeu", + "Omeu Deus do céu!"], + + ["Dizaí", + "Dizaí o quê?"], + + ["Inter", + "Interessante esta brincadeira."], + + ["Grato", + "Não há de quê."], + + ["Quicão", + "Quicão fusão é essa?"], + + ["Mamão", + "Mamão mandou bater nesta daqui!"], + + ["Nunci", + "Nunci deve comer de boca cheia."], + + ["Kiko", + "E o Kiko eu tenho que saber sobre isso?"], + + ["Silêncio", + "Pssss!"], + + ["Vossocá", + "Eu não, por favor."], + + ["Pu", + "Pu xavida, você me enganou."], + + ["Miá", + "Miá corda, não me deixa perder o bondinho."], + + ["Nívea", + "Feliz Niveassário!"], + + ["Sino", + "O sino faz \"blém\" não \"quem\"."], + + ["Diki", + "Diki lado você está?"], + + ["Querê", + "Querê não é podê."], + + ["Frankstein", + "Frankstein, mas você não tem."], + + ["Pra Z", + "Pra Z em conhecê-lo."], + + ["Ex-conde", + "Ex-conde-conde é legal."], + + ["Apri", + "Apri-ncesa despertou com o beijo do príncipe."], + + ["Quacker", + "Quacker uma, menos esta!"], + + ["Qualquerco", + "Qualquerco-isa parecida com isto já vai me ajudar."], + + ["Quiqui", + "'Quiqui' é isso?"], + + ["Abá", + "Abá-xaqui, a chave caiu!"], + + ["Póba", + "Póba-tê, esqueci a piada!"], + + ["Urralo", + "Urralo-ween já passou!"], + + ["Sará", + "Sará que tem um médico em casa?"], + + ["Aline", + "Aline é reta ou curva?"], + + ["Dôdis", + "Dôdis tômago."], + + ["Dôdi", + "Dôdi dente."], + + ["Toco", + "Toco dor de cabeça."], + + ["Atch", + "Saúde."], + + ["Aumen", + "Aumen-te o volume, por favor."], + + ["Zupra", + "Zupra-sumo."], + + ["Tupó", + "Tupó descer ali comigo?"], +] + +# CChatChatter.py + +# Shared Chatter + +SharedChatterGreetings = [ + "Oi, %!", + "Iuhuuu %, legal ver você.", + "Estou feliz que você esteja aqui hoje!", + "Bom, oi pessoal, %.", + ] + +SharedChatterComments = [ + "Que nome legal, %.", + "Gosto do seu nome.", + "Cuidado com os " + Cogs + "." + "Parece que o bondinho está chegando!", + "Preciso jogar um jogo no bondinho para ganhar algumas tortas!", + "Às vezes, eu me divirto com os jogos no bondinho só para comer a torta de frutas!", + "Puxa, acabei de deter um bando de " + Cogs + ". Preciso de descanso!", + "Puxa vida, alguns desses " + Cogs + " são grandalhões!", + "Você parece estar se divertindo.", + "Nossa, que dia legal!", + "Gostei da sua roupa.", + "Acho que vou pescar esta tarde.", + "Divirta-se no meu bairro.", + "Espero que você esteja aproveitando sua estada em Toontown!", + "Ouvi falar que está nevando no Brrrgh.", + "Você pegou o bondinho hoje?", + "Gosto de conhecer pessoas novas.", + "Uau, há vários "+ Cogs +" no Brrrgh." + "Eu adoro brincar de pique. E você?", + "Os jogos no bondinho são divertidos.", + "Adoro fazer as pessoas rirem.", + "É divertido ajudar meus amigos.", + "Hum-hum, você está perdido? Não se esqueça de que você tem um mapa no Álbum Toon.", + "Procure não ficar atolado na Burocracia dos " + Cogs + "'.", + "Ouvi falar que a " + Daisy + " plantou novas flores no jardim.", + "Se você pressionar a tecla Page Up, poderá ver acima!", + "Se você ajudar a tomar os edifícios dos Cogs, poderá ganhar uma estrela de bronze!", + "Se você pressionar a tecla Tab, poderá ver os arredores sob diversos ângulos!", + "Se você pressionar a tecla Ctrl, poderá descer!", + ] + +SharedChatterGoodbyes = [ + "Tenho que ir agora, tchau!", + "Acho que vou jogar no bondinho.", + "Bom, até mais. Vejo você por aí, %!", + "Melhor eu me apressar e voltar ao trabalho para deter esses "+ Cogs +".", + "Preciso ir andando.", + "Desculpe, mas tenho que ir.", + "Tchau.", + "Vejo você mais tarde, %!", + "Acho que vou praticar lançamento de bolinhos.", + "\Vou me juntar a um grupo para deter alguns "+Cogs+".", + "Foi legal ver você hoje, %.", + "Tenho muito a fazer hoje. É melhor começar logo.", + ] + +# Lines specific to each character. +# If a talking char is mentioned, it cant be shared among them all + +MickeyChatter = ( + [ # Greetings specific to Mickey + "Bem-vindo ao "+lToontownCentral+".", + "Oi, meu nome é " + Mickey + ". Qual é o seu?", + ], + [ # Comments + "Ei, você viu o "+ Donald +"?", + "Vou ver o nevoeiro passar no "+lDonaldsDock+".", + "Se você vir o meu camarada "+Goofy+", dê um oi para ele por mim.", + "Ouvi falar que a "+Daisy+" plantou novas flores no jardim." + ], + [ # Goodbyes + "\Vou para a Melodilândia ver a "+Minnie+"!", + "Caramba, estou atrasado para meu encontro com a "+ Minnie +"!", + "Parece que é hora de "+ Pluto +" jantar.", + "Acho que vou nadar no "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +MinnieChatter = ( + [ # Greetings + "Bem-vindo à Melodilândia.", + "Oi, meu nome é "+ Minnie +". Qual é o seu?" + ], + [ # Comments + "As colinas ganham vida com o som da música!", + # the merry no longer goes round + #"Não deixe de tentar andar no carrossel gigante!", + "Sua roupa é legal, %.", + "Ei, você viu o "+ Mickey +"?", + "Se você vir meu amigo "+ Goofy +", dê um oi para ele por mim.", + "Uau, há milhares de "+ Cogs +" perto da "+lDonaldsDreamland+".", + "Ouvi falar que tem neblina no "+lDonaldsDock+".", + "Não deixe de experimentar o labirinto dos "+lDaisyGardens+".", + "Acho que vou catar algumas canções.", + "Ei, %, olha aquilo lá.", + "Adoro o som da música.", + "Aposto que você não sabia que a Melodilândia também é chamada de ToadaTown! Ah, ah, ah!", + "Adoro jogo da memória. E você?", + "Gosto de fazer as pessoas rirem.", + "Cara, andar sobre rodas o dia todo não é moleza para os pés!", + "Bonita camisa, %.", + "Aquilo no chão é uma balinha?", + ], + [ # Goodbyes + "Caramba, estou atrasada para o meu encontro com o "+ Mickey +"!", + "Parece que é hora de "+ Pluto +" jantar.", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) +# localize +DaisyChatter = ( + [ # Greetings + "Bem-vindo(a) ao meu Jardim!", + "Olá, meu nome é "+Daisy+". Qual o seu nome?", + "É muito bom ver você, %!", + ], + [ # Comments + "Minha flor premiada está no centro do labirinto do jardim.", + "Eu adoro andar pelo labirinto.", + "Eu não ví o "+Goofy+" hoje.", + "Eu gostaria de saber onde o "+Goofy+" está.", + "Você viu o "+Donald+"? Eu não consigo encontrá-lo em lugar algum.", + "Se você vir minha amiga "+Minnie+", por favor diga \"Oi\" por mim.", + "Quanto melhor as ferramentas de jardinagem que você tem, melhor será seu jardim.", + "Existem muitos "+Cogs+" perto do "+lDonaldsDock+".", + "Regando seu jardim diariamente você deixa suas plantas felizes.", + "Para cultivar uma Margarida Rosa, plante uma balinha amarela e uma vermelha juntas.", + "É facil cultivar uma Margarida Amarela. Basta plantar uma balinha amarela.", + "Se você vir areia embaixo de uma planta, está na hora de regar ou ela morrerá.", + ], + [ # Goodbyes + "Estou indo para Melodilândia para ver %s!" % Minnie, + "Preciso correr para o meu picnic com %s!" % Donald, + "Acho que vou nadar no "+lDonaldsDock+".", + "Oh, estou com sono. Acho que vou para Sonholândia.", + ] + ) + +GoofyChatter = ( + [ # Greetings + "Bem-vindo aos "+lDaisyGardens+".", + "Oi, meu nome é "+ Goofy +". Qual é o seu?", + "Puxa, muito legal ver você %!", + ], + [ # Comments + "Cara, com certeza é fácil se perder no labirinto do jardim!", + "Não deixe de tentar entrar no labirinto.", + "Não vi a "+ Daisy +" o dia todo.", + "Onde será que a "+ Daisy +" está?", + "Ei, você viu o "+ Donald +"?", + "Se você vir o meu amigo "+ Mickey +", dê um oi para ele por mim.", + "Ah, não! Esqueci de fazer o café-da-manhã do "+ Mickey +"!", + "Puxa, com certeza há muitos "+ Cogs +" perto do "+lDonaldsDock+".", + "Parece que a "+ Daisy +" plantou novas flores no jardim.", + "Na filial da minha Loja de Piadas no Brrrgh, há Óculos hipnóticos em promoção por apenas uma balinha!", + "As Lojas de piadas do Pateta oferecem as melhores gozações, truques e comédias de toda Toontown!", + "Nas Lojas de piadas do Pateta, todas as tortas na cara têm garantia de fazer rir, ou você tem as suas balinhas de volta!", + ], + [ # Goodbyes + "\Vou para Melodilândia para ver a "+ Minnie +"!", + "Caramba, estou atrasado para o meu jogo com o "+ Donald + "!", + "Acho que vou nadar no Porto do "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +GoofySpeedwayChatter = ( + [ # Greetings + "Bem-vindo a "+lGoofySpeedway+".", + "Oi, meu nome é "+Goofy+". Qual é o seu?", + "Puxa, muito legal ver você %!", + ], + [ # Comments + "Cara, assisti a uma corrida maneira hoje.", + "Cuidado com as cascas de banana na pista!", + "Você deu uma incrementada no seu kart?", + "A gente acabou de pegar uns aros novos na loja do kart.", + "Oi, você viu "+Donald+"?", + "Se você vir meu amigo "+Mickey+", diz que eu mandei um alô.", + "Ah, não! Esqueci de preparar para "+Mickey+" o café-da-manhã!", + "Puxa, com certeza há muitos "+Cogs+" perto de "+lDonaldsDock+".", + "Na filial da minha Loja de Piadas no Brrrgh, há Óculos hipnóticos em promoção por apenas uma balinha!", + "As Lojas de piadas do Pateta oferecem as melhores gozações, truques e comédias de toda Toontown!", + "Nas Lojas de piadas do Pateta, todas as tortas na cara têm garantia de fazer rir, ou você tem as suas balinhas de volta!" + ], + [ # Goodbyes + "Vou para Melodilândia para ver %s!" % Mickey, + "Caramba, estou atrasado para o meu jogo com %s!" % Donald, + "Acho que vou nadar no "+lDonaldsDock+".", + "É hora de tirar um cochilo. Vou para a Sonholândia.", + ] + ) + +DonaldChatter = ( + [ # Greetings + "Bem-vindo à Sonholândia.", + "Oi, meu nome é "+ Donald +". Qual é o seu?", + ], + [ # Comments + "Às vezes este lugar me dá arrepios.", + "Não deixe de experimentar o labirinto dos "+lDaisyGardens+".", + "Nossa, que dia legal!", + "Ei, você viu o "+ Mickey +"?", + "Se você vir meu parceiro "+ Goofy +", dê um oi para ele por mim.", + "Acho que vou pescar esta tarde.", + "Uau, há um monte de "+ Cogs +" no "+lDonaldsDock+".", + "Escuta, eu não levei você ainda para um passeio no "+lDonaldsDock+"?", + "Não vi a "+ Daisy +" o dia todo.", + "Ouvi falar que a "+ Daisy +" plantou novas flores no jardim.", + "Quack.", + ], + [ # Goodbyes + "Vou a Melodilândia para ver a "+ Minnie +"!", + "Ah não, estou atrasado para o meu encontro com a "+ Daisy +"!", + "Acho que vou nadar no meu cais.", + "Acho que vou levar meu barco para um giro no meu cais.", + ] + ) + +for chatter in [MickeyChatter,DonaldChatter,MinnieChatter,GoofyChatter]: + chatter[0].extend(SharedChatterGreetings) + chatter[1].extend(SharedChatterComments) + chatter[2].extend(SharedChatterGoodbyes) + +# FriendsListPanel.py +FriendsListPanelNewFriend = "Novo amigo" +FriendsListPanelSecrets = "Segredos" +FriendsListPanelOnlineFriends = "AMIGOS\nON-LINE" +FriendsListPanelAllFriends = "TODOS\nOS AMIGOS" +FriendsListPanelIgnoredFriends = "TOONS\nIGNORADOS" +FriendsListPanelPets = "BICHINHOS\nPRÓXIMOS" +FriendsListPanelPlayers = "TODOS OS AMIGOS\nDO JOGADOR" +FriendsListPanelOnlinePlayers = "AMIGOS ONLINE\nDO JOGADOR" + +# DownloadForceAcknowledge.py +# phase, percent +DownloadForceAcknowledgeMsg = "Sinto muito, você não pode avançar porque o download de %(phase)s está apenas %(percent)s%% concluído.\n\nTente novamente mais tarde." + +# TeaserPanel.py +TeaserTop = "Infelizmente não é possível fazer isto na versão de avaliação gratuita...\nAssine já e aproveite esses recursos incríveis:" +TeaserOtherHoods = "Visite os 6 bairros exclusivos!" +TeaserTypeAName = "Digite o seu nome favorito para o seu Toon!" +TeaserSixToons = "Crie até 6 Toons em uma só conta!" +TeaserOtherGags = "Passe por 6 níveis em 6 tipos de piadas diferentes!" +TeaserClothing = "Compre roupas exclusivas para personalizar o seu Toon!" +TeaserFurniture = "Compre e arrume os móveis da sua própria casa!" +TeaserCogHQ = "Infiltre-se nas perigosas áreas avançadas dos Cogs!" +TeaserSecretChat = "Troque segredos com seus amigos conversando on-line com eles!" +TeaserCardsAndPosters = "Receba um pacote de boas-vindas e boletins mensais\ncom pôsteres e outras coisas maneiras!" +TeaserHolidays = "Participe dos eventos especiais e de datas comemorativas incríveis!" +TeaserQuests = "Complete centenas de Tarefas Toon para salvar Toontown!" +TeaserEmotions = "Compre emoções para tornar o seu Toon ainda mais expressivo!" +TeaserMinigames = "Brinque com os 8 tipos de minijogos!" +TeaserKarting = "Aposte corridas com outros Toons em karts maneiros!" +TeaserKartingAccessories = "Personalize seu kart com acessórios incríveis." +#TeaserGardening = "Plante flores, construa estátuas e cultive árvores\n em seu terreno." +#TeaserRental = "Alugue ítens de festa para seu terreno!" +#TeaserBigger = "Compre ítens Toon maiores e melhores!" +TeaserTricks = "Treine seus Rabisocs para que eles façam truques e ajudem na batalha!" +TeaserSubscribe = "Assinar agora" +TeaserContinue = "Continuar na versão de avaliação gratuita" + +# DownloadWatcher.py +# phase, percent +DownloadWatcherUpdate = "Fazendo download %s" +DownloadWatcherInitializing = "Iniciando Download..." + +# Launcher.py +LauncherPhaseNames = { + 0 : "Inicialização", + 3 : "Fazer um Toon", + 3.5 : "Toontorial", + 4 : "Parque", + 5 : "Ruas", + 5.5 : "Estados", + 6 : "Bairros I", + 7 : Cog + " Edifícios dos", + 8 : "Bairros II", + 9 : Sellbot + " Quartel dos", + 10 : Cashbot + " Quartel dos", + 11 : Lawbot + " Quartel dos", + } + +# Lets make these messages a little more friendly +LauncherProgress = "%(name)s (%(current)s de %(total)s)" +LauncherStartingMessage = "Iniciando Toontown On-line da Disney..." +LauncherDownloadFile = "Fazendo download da atualização de "+ LauncherProgress +"..." +LauncherDownloadFileBytes = "Fazendo download da atualização de "+ LauncherProgress +": %(bytes)s" +LauncherDownloadFilePercent = "Fazendo download da atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherDecompressingFile = "Descompactando atualização de "+ LauncherProgress +"..." +LauncherDecompressingPercent = "Descompactando atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherExtractingFile = "Extraindo atualização de "+ LauncherProgress +"..." +LauncherExtractingPercent = "Extraindo atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherPatchingFile = "Aplicando atualização de "+ LauncherProgress +"..." +LauncherPatchingPercent = "Aplicando atualização de "+ LauncherProgress +": %(percent)s%%" +LauncherConnectProxyAttempt = "Conectando-se a Toontown: %s (proxy: %s) tentativa: %s" +LauncherConnectAttempt = "Conectando-se a Toontown: %s tentativa %s" +LauncherDownloadServerFileList = "Atualizando Toontown..." +LauncherCreatingDownloadDb = "Atualizando Toontown..." +LauncherDownloadClientFileList = "Atualizando Toontown..." +LauncherFinishedDownloadDb = "Atualizando Toontown..." +LauncherStartingToontown = "Iniciando Toontown..." +LauncherRecoverFiles = "Atualizando Toontown. Recuperando arquivos..." +LauncherCheckUpdates = "Verificando atualizações de "+ LauncherProgress +LauncherVerifyPhase = "Atualizando Toontown..." + +# AvatarChoice.py +AvatarChoiceMakeAToon = "Fazer um\nToon" +AvatarChoicePlayThisToon = "Jogar com\neste Toon" +AvatarChoiceSubscribersOnly = "Assinar\n\n\n\nAgora!" +AvatarChoiceDelete = "Excluir" +AvatarChoiceDeleteConfirm = "Isto fará que %s seja excluído para sempre." +AvatarChoiceNameRejected = "Nome\nrejeitado" +AvatarChoiceNameApproved = "Nome\naprovado!" +AvatarChoiceNameReview = "Em\nrevisão" +AvatarChoiceNameYourToon = "Dar um\nnome ao Toon!" +AvatarChoiceDeletePasswordText = "Cuidado! Isto fará que %s seja excluído para sempre. Para excluir este Toon, insira a sua senha." +AvatarChoiceDeleteConfirmText = "Cuidado! Isto excluirá %(name)s para sempre. Se você tiver certeza de que é isso mesmo que deseja, digite \"%(confirm)s\" e clique em OK." +AvatarChoiceDeleteConfirmUserTypes = "excluir" +AvatarChoiceDeletePasswordTitle = "Excluir Toon?" +AvatarChoicePassword = "Senha" +AvatarChoiceDeletePasswordOK = lOK +AvatarChoiceDeletePasswordCancel = lCancel +AvatarChoiceDeleteWrongPassword = "Esta senha não parece ser a correta. Para excluir este Toon, insira a sua senha." +AvatarChoiceDeleteWrongConfirm = "Você não digitou corretamente. Para excluir %(name)s, digite \"%(confirm)s\" e clique em OK. Não digite as aspas. Clique em Cancelar se desistir." + +# AvatarChooser.py +AvatarChooserPickAToon = "Escolha um Toon para jogar" +AvatarChooserQuit = lQuit + + +# TTAccount.py +# Fill in %s with phone number from account server +TTAccountCallCustomerService = "Ligue para o Atendimento ao Cliente: %s." +# Fill in %s with phone number from account server +TTAccountCustomerServiceHelp = "\nSe precisar de ajuda, ligue para o Atendimento ao Cliente%s." +TTAccountIntractibleError = "Erro." + +# DateOfBirthEntry.py +DateOfBirthEntryMonths = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez',] +DateOfBirthEntryDefaultLabel = "Data de nascimento" + + +# AchievePage.py +AchievePageTitle = "Realizações\n(em breve)" + +# PhotoPage.py +PhotoPageTitle = "Foto\n(em breve)" + +# BuildingPage.py +BuildingPageTitle = "Edifícios\n(em breve)" + +# InventoryPage.py +InventoryPageTitle = "Piadas" +InventoryPageDeleteTitle = "EXCLUIR PIADAS" +InventoryPageTrackFull = "Você possui todas as piadas do tipo %s." +InventoryPagePluralPoints = "Você obterá uma nova piada de\n%(trackName)s quando\nganhar mais %(numPoints)s pontos de %(trackName)s." +InventoryPageSinglePoint = "Você obterá uma nova piada de\n%(trackName)s quando\nganhar mais %(numPoints)s pontos de %(trackName)s." +InventoryPageNoAccess = "Você ainda não tem acesso ao tipo %s." + +# NPCFriendPage.py +NPCFriendPageTitle = "SOS Toons" + +# NPCFriendPanel.py +NPCFriendPanelRemaining = "Restantes %s" + +# MapPage.py +MapPageTitle = "Mapa" +MapPageBackToPlayground = "Voltar para o pátio" +MapPageBackToCogHQ = "Voltar para o Quartel de Cogs" +MapPageGoHome = "Ir para casa" +# hood name, street name +MapPageYouAreHere = "Você está em: %s %s" +MapPageYouAreAtHome = "Você está em\nsua propriedade" +MapPageYouAreAtSomeonesHome = "Você está na propriedade de %s" +MapPageGoTo = "Ir para\n%s" + +# OptionsPage.py +OptionsPageTitle = "Opções" +OptionsPagePurchase = "Assine já!" +OptionsPageLogout = "Sair" +OptionsPageExitToontown = "Sair de Toontown" +OptionsPageMusicOnLabel = "A música está ligada." +OptionsPageMusicOffLabel = "A música está desligada." +OptionsPageSFXOnLabel = "Os efeitos sonoros estão ligados." +OptionsPageSFXOffLabel = "Os efeitos sonoros estão desligados." +OptionsPageFriendsEnabledLabel = "Aceito fazer novas amizades." +OptionsPageFriendsDisabledLabel = "Não aceito fazer amizades." +OptionsPageSpeedChatStyleLabel = "Cor do Chat rápido" +OptionsPageDisplayWindowed = "com janela" +OptionsPageSelect = "Selecionar" +OptionsPageToggleOn = "Ligar" +OptionsPageToggleOff = "Desligar" +OptionsPageChange = "Alterar" +OptionsPageDisplaySettings = "Vídeo: %(screensize)s, %(api)s" +OptionsPageDisplaySettingsNoApi = "Vídeo: %(screensize)s" +OptionsPageExitConfirm = "Sair de Toontown?" + +DisplaySettingsTitle = "Configurações de vídeo" +DisplaySettingsIntro = "As configurações a seguir são usadas para determinar a maneira como Toontown é exibida em seu computador. Provavelmente, não será necessário ajustá-las, a menos que você esteja tendo algum problema." +DisplaySettingsIntroSimple = "Você pode ajustar a resolução da tela com um valor maior para melhorar o contraste do texto e dos gráficos em Toontown, mas, dependendo da placa de vídeo do seu computador, alguns valores mais altos podem fazer que o jogo fique lento ou trave." + +DisplaySettingsApi = "API de gráfico:" +DisplaySettingsResolution = "Resolução:" +DisplaySettingsWindowed = "Em uma janela" +DisplaySettingsFullscreen = "Tela cheia" +DisplaySettingsApply = "Aplicar" +DisplaySettingsCancel = lCancel +DisplaySettingsApplyWarning = "Quando você pressionar OK, as configurações de vídeo serão alteradas. Se a nova configuração não ficar adequada em seu computador, o vídeo retornará à configuração original após %s segundos." +DisplaySettingsAccept = "Pressione em OK para manter as novas configurações, ou em Cancelar para voltar às anteriores. Se você não pressionar nada, as configurações voltarão em %s segundos automaticamente aos valores anteriores." +DisplaySettingsRevertUser = "As configurações de vídeo anteriores foram restauradas." +DisplaySettingsRevertFailed = "As configurações de vídeo selecionadas não funcionam em seu computador. As configurações de vídeo anteriores foram restauradas." + + +# TrackPage.py +TrackPageTitle = "Treinamento de tipos de piadas" +TrackPageShortTitle = "Treinamento de piadas" +TrackPageSubtitle = "Execute as Tarefas Toon para aprender a usar novas piadas!" +TrackPageTraining = "Você está treinando para usar as Piadas de %s.\nQuando executar todas as 16 tarefas,\nestará apto a usar as Piadas de %s nas batalhas." +TrackPageClear = "Você não está treinando nenhum tipo de piadas agora." +TrackPageFilmTitle = "Filme de\ntreinamento\nde %s" +TrackPageDone = "FIM" + +# QuestPage.py +QuestPageToonTasks = "Tarefas Toon" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageDelivery = "%s\nPara: %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName, npcName +#QuestPageVisit = "%s %s\n %s\n %s\n %s\n\nDe: %s" +# questName, toNpcName, toNpcBuilding, toNpcStreetName, toNpcLocationName +# Choose between trackA and trackB. +# +# To choose, go see: +# Flippy +# Town Hall +# Playground +# Toontown Central +#QuestPageTrackChoice = "%s\n\nPara escolher, selecione:\n %s\n %s\n %s\n %s" +# questName, npcName, buildingName, streetName, locationName +QuestPageChoose = "Escolha" +# building name, street name, Npc location +QuestPageDestination = "%s\n%s\n%s" +# npc name, building name, street name, Npc location +QuestPageNameAndDestination = "%s\n%s\n%s\n%s" + +QuestPosterHQOfficer = lHQOfficerM +QuestPosterHQBuildingName = lToonHQ +QuestPosterHQStreetName = "Qualquer rua" +QuestPosterHQLocationName = "Qualquer bairro" + +QuestPosterTailor = "Costureiro" +QuestPosterTailorBuildingName = "Loja de Roupas" +QuestPosterTailorStreetName = "Qualquer pátio" +QuestPosterTailorLocationName = "Qualquer bairro" +QuestPosterPlayground = "No pátio" +QuestPosterAtHome = "Na "+lMyEstate +QuestPosterInHome = "Em "+lMyEstate +QuestPosterOnPhone = "No seu telefone" +QuestPosterEstate = "Na sua propriedade" +QuestPosterAnywhere = "Qualquer lugar" +QuestPosterAuxTo = "para:" +QuestPosterAuxFrom = "de:" +QuestPosterAuxFor = "para:" +QuestPosterAuxOr = "ou:" +QuestPosterAuxReturnTo = "Retornar\npara:" +QuestPosterLocationIn = "" +QuestPosterLocationOn = "" +QuestPosterFun = "Só de brincadeira!" +QuestPosterFishing = "IR PESCAR" +QuestPosterComplete = "CONCLUIR" + +# ShardPage.py +ShardPageTitle = "Regiões" +ShardPageHelpIntro = "Cada Região é uma cópia do mundo de Toontown." +ShardPageHelpWhere = " Você está agora na Região \"%s\"." +ShardPageHelpWelcomeValley = " Você está agora na Região \"Vale Boas-vindas\", em \"%s\"." +ShardPageHelpMove = " Para ir até uma nova Região, clique no nome dela." + +ShardPagePopulationTotal = "População Total de Toontown:\n%d" +ShardPageScrollTitle = "Nome População" +ShardPageLow = "Tranqüila" +ShardPageMed = "Inteligente" +ShardPageHigh = "Lotada" +ShardPageChoiceReject = "Desculpe, essa Região está lotada. Por favor, tente outra." + +# SuitPage.py +SuitPageTitle = "Galeria de Cogs" +SuitPageMystery = DialogQuestion + DialogQuestion + DialogQuestion +SuitPageQuota = "%s de %s" +SuitPageCogRadar = "%s presentes" +SuitPageBuildingRadarS = "%s edifício" +SuitPageBuildingRadarP = "%s edifícios" + +# DisguisePage.py +DisguisePageTitle = Cog + "Disfarce" +DisguisePageMeritBar = "Progresso de méritos" +DisguisePageMeritAlert = "Pronto para a\npromoção!" +DisguisePageCogLevel = "Nível %s" +DisguisePageMeritFull = "Completo" +DisguisePageCogPartRatio = "%d/%d" + +# FishPage.py +FishPageTitle = "Pescaria" +FishPageTitleTank = "Balde de peixes" +FishPageTitleCollection = "Álbum de peixes" +FishPageTitleTrophy = "Troféus de pesca" +FishPageWeightStr = "Peso: " +FishPageWeightLargeS = "%d Kg " +FishPageWeightLargeP = "%d Kg " +FishPageWeightSmallS = "%d g" +FishPageWeightSmallP = "%d g" +FishPageWeightConversion = 16 +FishPageValueS = "Valor: %d balinha" +FishPageValueP = "Valor: %d balinhas" +FishPageTotalValue = "" +FishPageCollectedTotal = "Espécies de peixes recolhidas: %d de %d" +FishPageRodInfo = "Vara %s\n%d - %d quilos" +FishPageTankTab = "Balde" +FishPageCollectionTab = "Álbum" +FishPageTrophyTab = "Troféus" + +FishPickerTotalValue = "Balde: %s / %s\nValor: %d balinhas" + +UnknownFish = DialogQuestion + DialogQuestion + DialogQuestion + +FishingRod = "Vara %s" +FishingRodNameDict = { + 0 : "Vareta", + 1 : "Bambu", + 2 : "Madeira de lei", + 3 : "Aço", + 4 : "Dourado", + } +FishTrophyNameDict = { + 0 : "Guppy", + 1 : "Peixinho", + 2 : "Peixe", + 3 : "Peixe-voador", + 4 : "Tubarão", + 5 : "Peixe-espada", + 6 : "Baleia assassina", + } + +# GardenPage.py +GardenPageTitle = "Jardinagem" +GardenPageTitleBasket = "Cesto de Flores" +GardenPageTitleCollection = "Álbum de Flores" +GardenPageTitleTrophy = "Troféus de Jardinagem" +GardenPageTitleSpecials = "Especiais de Jardinagem" +GardenPageBasketTab = "Cesto" +GardenPageCollectionTab = "Álbum" +GardenPageTrophyTab = "Troféus" +GardenPageSpecialsTab = "Especiais" +GardenPageCollectedTotal = "Variedades de Flores Colecionadas: %d de %d" +GardenPageValueS = "Valor: %d balinha" +GardenPageValueP = "Valor: %d balinhas" +FlowerPickerTotalValue = "Cesto: %s / %s\nValor: %d balinhas" +GardenPageShovelInfo = "%s Pá: %d / %d\n" +GardenPageWateringCanInfo = "%s Regador: %d / %d" + +# KartPage.py +KartPageTitle = "Karts" +KartPageTitleCustomize = "Personalizador de karts" +KartPageTitleRecords = "Melhores recordes pessoais" +KartPageTitleTrophy = "Troféus de corridas" +KartPageCustomizeTab = "Personalizar" +KartPageRecordsTab = "Recordes" +KartPageTrophyTab = "Troféus" +KartPageTrophyDetail = "Troféus %s : %s" +KartPageTickets = "Tíquetes :" +KartPageConfirmDelete = "Excluir acessório?" + +#plural +KartShtikerDelete = "Excluir" +KartShtikerSelect = "Selecionar uma categoria" +KartShtikerNoAccessories = "Não possui acessórios" +KartShtikerBodyColors = "Cores de karts" +KartShtikerAccColors = "Cores de acessórios" +KartShtikerEngineBlocks = "Acessórios de capô" +KartShtikerSpoilers = "Acessórios de mala" +KartShtikerFrontWheelWells = "Acessórios de roda dianteira" +KartShtikerBackWheelWells = "Acessórios de roda traseira" +KartShtikerRims = "Acessórios de aro" +KartShtikerDecals = "Acessórios de decalque" +#singluar +KartShtikerBodyColor = "Cor do kart" +KartShtikerAccColor = "Cor do acessório" +KartShtikerEngineBlock = "Capô" +KartShtikerSpoiler = "Mala" +KartShtikerFrontWheelWell = "Roda dianteira" +KartShtikerBackWheelWell = "Roda traseira" +KartShtikerRim = "Aro" +KartShtikerDecal = "Decalque" + +KartShtikerDefault = "Padrão %s" +KartShtikerNo = "Nenhum acessório %s" + +# QuestChoiceGui.py +QuestChoiceGuiCancel = lCancel + +# TrackChoiceGui.py +TrackChoiceGuiChoose = "Escolher" +TrackChoiceGuiCancel = lCancel +TrackChoiceGuiHEAL = 'Toonar permite que você cure outros Toons que estão na batalha.' +TrackChoiceGuiTRAP = 'Armadilhas são piadas poderosas que devem ser usadas com Iscas.' +TrackChoiceGuiLURE = 'Use Iscas para abalar os Cogs ou faça-os cair em armadilhas.' +TrackChoiceGuiSOUND = 'As piadas Sonoras afetam todos os Cogs, mas não são muito poderosas.' +TrackChoiceGuiDROP = "As piadas Cadentes fazem muitos estragos, mas não são muito precisas." + +# EmotePage.py +EmotePageTitle = "Expressões / Emoções" +EmotePageDance = "Você montou a seguinte seqüência de dança:" +EmoteJump = "Saltitante" +EmoteDance = "Dançante" +EmoteHappy = "Feliz" +EmoteSad = "Triste" +EmoteAnnoyed = "Aborrecido" +EmoteSleep = "Sonolento" + +# SuitBase.py +SuitBaseNameWithLevel = "%(name)s\n%(dept)s\nNível %(level)s" + +# HealthForceAcknowledge.py +HealthForceAcknowledgeMessage = "Você não pode sair do parque até que o seu Risômetro esteja sorrindo!" + +# InventoryNew.py +InventoryTotalGags = "Total de piadas\n%d / %d" +InventoryDelete = "EXCLUIR" +InventoryDone = "OK" +InventoryDeleteHelp = "Clique em uma piada para EXCLUIR." +InventorySkillCredit = "Crédito de habilidades:\n%s" +InventorySkillCreditNone = "Crédito de habilidades:\nNenhum" +InventoryDetailAmount = "%(numItems)s / %(maxItems)s" +# acc, damage_string, damage, single_or_group +InventoryDetailData = "Precisão: %(accuracy)s\n%(damageString)s: %(damage)d\n%(singleOrGroup)s" +InventoryTrackExp = "%(curExp)s / %(nextExp)s" +InventoryAffectsOneCog = "Afeta: Um "+ Cog +InventoryAffectsOneToon = "Afeta: Um Toon" +InventoryAffectsAllToons = "Afeta: Todos os Toons" +InventoryAffectsAllCogs = "Afeta: Todos os "+ Cogs +InventoryHealString = "Toonar" +InventoryDamageString = "Dano" +InventoryBattleMenu = "MENU DE BATALHA" +InventoryRun = "CORRER" +InventorySOS = "SOS" +InventoryPass = "PASSAR" +InventoryClickToAttack = "Clique em uma\npiada para\natacar" +InventoryDamageBonus = "(+%d)" + +# NPCForceAcknowledge.py +NPCForceAcknowledgeMessage = "Você deve pegar o bondinho antes de sair.\n\n\n\n\nVocê poderá encontrar o bondinho ao lado da Loja de Piadas do Pateta." +NPCForceAcknowledgeMessage2 = "Muito bem! Você completou a busca pelo bondinho!\nVisite o Quartel dos Toons para solicitar a sua recompensa.\n\n\n\n\n\nO Quartel dos Toons localiza-se próximo ao centro do pátio." +NPCForceAcknowledgeMessage3 = "Lembre-se de pegar o bondinho.\n\n\n\nVocê pode encontrar o bondinho ao lado da Loja de Piadas do Pateta." +NPCForceAcknowledgeMessage4 = "Parabéns! Você concluiu a sua primeira Tarefa Toon!\n\n\n\n\n\nVisite o Quartel dos Toons para solicitar a sua recompensa." +NPCForceAcknowledgeMessage5 = "Não se esqueça de sua Tarefa Toon!\n\n\n\n\n\n\n\n\n\n\nVocê pode encontrar Cogs para serem derrotados do outro lado de túneis como este." +NPCForceAcknowledgeMessage6 = "Excelente trabalho derrotando esses Cogs!\n\n\n\n\n\n\n\n\nVolte para o Quartel dos Toons o mais rápido possível." +NPCForceAcknowledgeMessage7 = "Não se esqueça de fazer um amigo!\n\n\n\n\n\n\nClique em outro jogador e use o botão Novo amigo." +NPCForceAcknowledgeMessage8 = "Ótimo! Você fez um novo amigo!\n\n\n\n\n\n\n\n\nAgora, você deve voltar para o Quartel dos Toons." +NPCForceAcknowledgeMessage9 = "Bom trabalho usando o telefone!\n\n\n\n\n\n\n\n\nVolte para o Quartel dos Toons para pedir a sua recompensa." + +# Toon.py +ToonSleepString = ". . . ZZZ . . ." + +# Movie.py +MovieTutorialReward1 = "Você recebeu 1 ponto de Lançamento! Quando você obtém 10, ganha uma nova piada!" +MovieTutorialReward2 = "Você recebeu 1 ponto de Esguicho! Quando você obtém 10, ganha uma nova piada!" +MovieTutorialReward3 = "Muito bom! Você concluiu a sua primeira Tarefa Toon!" +MovieTutorialReward4 = "Vá para o Quartel dos Toons para pegar a sua recompensa!" +MovieTutorialReward5 = "Divirta-se!" + +# ToontownBattleGlobals.py +BattleGlobalTracks = ['toonar', 'armadilha', 'isca', 'sonora', 'lançamento', 'esguicho', 'cadente'] +BattleGlobalNPCTracks = ['reabastecer', 'toons atingidos', 'cogs não-atingidos'] +BattleGlobalAvPropStrings = ( + ('Pena', 'Megafone', 'Batom', 'Bengala', 'Pó mágico', 'Bolinhas de malabarismo'), + ('Casca de banana', 'Ancinho', 'Bolas de gude', 'Areia movediça', 'Alçapão', 'TNT'), + ('Nota de $1', 'Ímã pequeno', 'Nota de $5', 'Ímã grande', 'Nota de $10', 'Óculos hipnóticos'), + ('Buzina de bicicleta', 'Apito', 'Trombeta', 'Foooonnnn!', 'Tromba de elefante', 'Buzina'), + ('Bolinho', 'Fatia de torta de frutas', 'Fatia de torta de creme', 'Torta de frutas inteira', 'Torta de creme inteira', 'Bolo de aniversário'), + ('Flor com esguicho', 'Copo d\'água', 'Revólver de água', 'Garrafa de água com gás', 'Mangueira de incêndio', 'Nuvem de chuva'), + ('Vaso de flor', 'Saco de areia', 'Bigorna', 'Peso pesado', 'Cofre', 'Piano de cauda') + ) +BattleGlobalAvPropStringsSingular = ( + ('uma Pena', 'um Megafone', 'um Batom', 'uma Bengala', 'um Pó mágico', 'um conjunto de Bolinhas de malabarismo'), + ('uma Casca de banana', 'um Ancinho', 'um conjunto de Bolas de gude', 'uma poça de Areia movediça', 'um Alçapão', 'um TNT'), + ('uma Nota de $1', 'um Ímã pequeno', 'uma Nota de $5', 'um Ímã grande', 'uma Nota de $10', 'um par de Óculos hipnóticos'), + ('uma Buzina de bicicleta', 'um Apito', 'uma Trombeta', 'um Foooonnnn!', 'uma Tromba de elefante', 'uma Buzina'), + ('um Bolinho', 'uma Fatia de torta de frutas', 'uma Fatia de torta de creme', 'uma Torta de frutas inteira', 'uma Torta de creme inteira', 'um Bolo de aniversário'), + ('uma Flor com esguicho', 'um Copo d\'água', 'um Revólver de água', 'uma Garrafa de água com gás', 'uma Mangueira de incêndio', 'uma Nuvem de chuva'), + ('um Vaso de flor', 'um Saco de areia', 'uma Bigorna', 'um Peso pesado', 'um Cofre', 'um Piano de cauda') + ) +BattleGlobalAvPropStringsPlural = ( + ('Penas', 'Megafones', 'Batons', 'Bengalas', 'Pós mágicos', 'conjuntos de Bolinhas de malabarismo'), + ('Cascas de banana', 'Ancinhos', 'conjuntos de Bolas de gude', 'poças de Areia movediça', 'Alçapões','TNTs'), + ('Notas de $1', 'Ímãs pequenos', 'Contas de $5', 'Ímãs grandes','Contas de $10', 'par de Óculos hipnóticos'), + ('Buzinas de bicicleta', 'Apitos', 'Trombetas', 'Foooonnnns!', 'Trombas de elefante', 'Buzinas'), + ('Bolinhos', 'Fatias de torta de frutas', 'Fatias de torta de creme','Tortas de frutas inteiras', 'Tortas de creme inteiras', 'Bolos de aniversário'), + ('Flores com esguicho', 'Copos d\'água', 'Revólveres de água','Garrafas de água com gás', 'Mangueiras de incêndio', 'Nuvens de chuva'), + ('Vasos de flor', 'Sacos de areia', 'Bigornas', 'Pesos pesados', 'Cofres','Pianos de cauda') + ) +BattleGlobalAvTrackAccStrings = ("Médio", "Perfeito", "Baixo", "Alto", "Médio", "Alto", "Baixo") +BattleGlobalLureAccLow = "Baixo" +BattleGlobalLureAccMedium = "Médio" + +AttackMissed = "PERDEU" + +NPCCallButtonLabel = 'CHAMAR' + + +# reference the location name as [-1]; it's guaranteed to be the last entry +DonaldsDock = ("para o", "no", lDonaldsDock) +ToontownCentral = ("para o", "no", lToontownCentral) +TheBrrrgh = ("para", "em", lTheBrrrgh) +MinniesMelodyland = ("para a", "na", lMinniesMelodyland) +DaisyGardens = ("para os", "nos", lDaisyGardens) +ConstructionZone = ("para a", "na", "Zona de Construção") +FunnyFarm = ("para a", "na", "Fazenda Divertida") +GoofySpeedway = ("para o", "no", lGoofySpeedway) +DonaldsDreamland = ("para a", "na", lDonaldsDreamland) +BossbotHQ = ("para o", "no", lBossbotHQ) +SellbotHQ = ("para o", "no", lSellbotHQ) +CashbotHQ = ("para o", "no", lCashbotHQ) +LawbotHQ = ("para o", "no", lLawbotHQ) +Tutorial = ("para o", "no", lTutorial) +MyEstate = ("para a", "na", lMyEstate) +WelcomeValley = ("para o", "no", lWelcomeValley) + +Factory = 'Fábrica' +Headquarters = 'Quartel' +SellbotFrontEntrance = 'Entrada principal' +SellbotSideEntrance = 'Entrada lateral' +Office = 'Escritório' + +FactoryNames = { + 0 : 'Molde da fábrica', + 11500 : 'Fábrica do Cog '+Sellbot, + 13300 : 'Lawbot Cog Office', #remove me JML + } + +FactoryTypeLeg = 'Perna' +FactoryTypeArm = 'Braço' +FactoryTypeTorso = 'Busto' + +MintFloorTitle = 'Andar %s' + +# ToontownLoader.py +LoaderLabel = "Carregando..." + +# PlayGame.py +HeadingToHood = "Indo %(to)s %(hood)s..." # hood name +HeadingToYourEstate = "Indo para a sua propriedade..." +HeadingToEstate = "Indo para a propriedade de %s..." # avatar name +HeadingToFriend = "Indo para a propriedade do amigo de %s..." # avatar name + +# Hood.py +HeadingToPlayground = "Indo para o Pátio..." +HeadingToStreet = "Indo %(to)s %(street)s..." # Street name + +# TownBattle.py +TownBattleRun = "Voltar correndo para o pátio?" + +# TownBattleChooseAvatarPanel.py +TownBattleChooseAvatarToonTitle = "QUAL TOON?" +TownBattleChooseAvatarCogTitle = "QUAL " + string.upper(Cog) + "?" +TownBattleChooseAvatarBack = "VOLTAR" + +# TownBattleSOSPanel.py +TownBattleSOSNoFriends = "Não há amigos para chamar!" +TownBattleSOSWhichFriend = "Chamar qual amigo?" +TownBattleSOSNPCFriends = "Toons resgatados" +TownBattleSOSBack = "VOLTAR" + +# TownBattleToonPanel.py +TownBattleToonSOS = "SOS" +TownBattleUndecided = "?" +TownBattleHealthText = "%(hitPoints)s/%(maxHit)s" + +# TownBattleWaitPanel.py +TownBattleWaitTitle = "Aguardando\noutros jogadores..." +TownSoloBattleWaitTitle = "Aguarde..." +TownBattleWaitBack = "VOLTAR" + +# TownBattleSOSPetSearchPanel.py +TownBattleSOSPetSearchTitle = "Procurando rabisco\n%s..." + +# TownBattleSOSPetInfoPanel.py +TownBattleSOSPetInfoTitle = "%s está %s" +TownBattleSOSPetInfoOK = lOK + +# Trolley.py +TrolleyHFAMessage = "Você não pode embarcar no bondinho até que o seu Risômetro esteja sorrindo." +TrolleyTFAMessage = "\Você não pode embarcar no bondinho até que o " + Mickey +" permita." +TrolleyHopOff = "Descer" + +# DistributedFishingSpot.py +FishingExit = "Sair" +FishingCast = "Lançar" +FishingAutoReel = "Molinete automático" +FishingItemFound = "Você pegou:" +FishingCrankTooSlow = "Muito\ndevagar" +FishingCrankTooFast = "Muito\nrápido" +FishingFailure = "Você não pegou nada!" +FishingFailureTooSoon = "Não comece a rebobinar a linha até que você veja uma pequena mordida. Espere a bóia balançar para cima e para baixo rapidamente!" +FishingFailureTooLate = "Rebobine a linha enquanto o peixe ainda está mordendo a isca!" +FishingFailureAutoReel = "O molinete automático não funcionou desta vez. Gire a manivela manualmente, na velocidade certa, para ter mais chance de pegar alguma coisa!" +FishingFailureTooSlow = "Você girou a manivela muito devagar. Alguns peixes são mais rápidos do que outros. Tente manter a barra de velocidade centralizada!" +FishingFailureTooFast = "Você girou a manivela muito rápido. Alguns peixes são mais lentos do que outros. Tente manter a barra de velocidade centralizada!" +FishingOverTankLimit = "O seu balde de pesca está cheio. Vá vender os seus peixes para o Vendedor da Loja de Animais e volte." +FishingBroke = "Você não tem mais balinhas para as iscas! Para ganhar mais balinhas, pegue o bondinho ou venda os peixes para os Vendedores da Loja de Animais." +FishingHowToFirstTime = "Clique e arraste para baixo no botão Lançar. Quanto mais baixo você arrastar, mais forte será o lançamento. Ajuste o ângulo para acertar os alvos dos peixes.\n\nTente agora!" +FishingHowToFailed = "Clique e arraste para baixo no botão Lançar. Quanto mais baixo você arrastar, mais forte será o lançamento. Ajuste o ângulo para acertar os alvos dos peixes.\n\nTente agora de novo!" +FishingBootItem = "Bota velha" +FishingJellybeanItem = "%s balinhas" +FishingNewEntry = "Novas espécies!" +FishingNewRecord = "Novo recorde!" + +# FishPoker +FishPokerCashIn = "Morrer\n%s\n%s" +FishPokerLock = "Bloquear" +FishPokerUnlock = "Desbloquear" +FishPoker5OfKind = "5 de um naipe" +FishPoker4OfKind = "4 de um naipe" +FishPokerFullHouse = "Full House" +FishPoker3OfKind = "3 de um naipe" +FishPoker2Pair = "2 pares" +FishPokerPair = "Par" + +# DistributedTutorial.py +TutorialGreeting1 = "Oi %s!" +TutorialGreeting2 = "Oi %s!\nVem cá!" +TutorialGreeting3 = "Oi %s!\nVem cá!\nUse as teclas de seta!" +TutorialMickeyWelcome = "Bem-vindo a Toontown!" +TutorialFlippyIntro = "Deixe-me apresentar você ao meu amigo "+ Flippy +"..." +TutorialFlippyHi = "Oi, %s!" +TutorialQT1 = "Você pode conversar usando isto." +TutorialQT2 = "Você pode conversar usando isto.\nClique no item e escolha \"Oi\"." +TutorialChat1 = "Você pode conversar usando qualquer um destes botões." +TutorialChat2 = "O botão azul permite que você converse usando o teclado." +TutorialChat3 = "Cuidado! A maior parte dos outros jogadores não entenderá o que você está dizendo se usar o teclado." +TutorialChat4 = "O botão verde abre o %s." +TutorialChat5 = "Todos entenderão se você usar o %s." +TutorialChat6 = "Tente dizer \"Oi\"." +TutorialBodyClick1 = "Muito bem!" +TutorialBodyClick2 = "Muito prazer! Quer ser meu amigo?" +TutorialBodyClick3 = "Para fazer amizade com "+ Flippy +", clique nele..." +TutorialHandleBodyClickSuccess = "Muito bom!" +TutorialHandleBodyClickFail = "Não é assim. Tente clicar em cima do "+ Flippy +"..." +TutorialFriendsButton = "Agora, clique no botão 'Amigos' abaixo da figura do "+ Flippy +" no canto direito." +TutorialHandleFriendsButton = "Em seguida, clique no botão 'Sim'.." +TutorialOK = lOK +TutorialYes = lYes +TutorialNo = lNo +TutorialFriendsPrompt = "Você quer fazer amizade com o "+ Flippy +"?" +TutorialFriendsPanelMickeyChat = Flippy + " aceitou ser seu amigo. Clique em 'Ok' para concluir." +TutorialFriendsPanelYes = Flippy + " disse sim!" +TutorialFriendsPanelNo = "Isso não foi muito simpático!" +TutorialFriendsPanelCongrats = "Parabéns! Você fez seu primeiro amigo." +TutorialFlippyChat1 = "Venha me ver quando estiver pronto para a sua primeira Tarefa Toon!" +TutorialFlippyChat2 = "Estarei na PrefeiToona!" +TutorialAllFriendsButton = "Você pode ver todos os seus amigos clicando no botão Amigos. Experimente..." +TutorialEmptyFriendsList = "No momento, a sua lista está vazia porque o "+ Flippy +" não é um jogador real." +TutorialCloseFriendsList = "Clique no botão 'Fechar'\npara fazer que a\nlista desapareça" +TutorialShtickerButton = "O botão do canto direito inferior abre o seu Álbum Toon. Experimente..." +TutorialBook1 = "O álbum contém várias informações úteis, como este mapa de Toontown." +TutorialBook2 = "Você também pode verificar o andamento de suas Tarefas Toon." +TutorialBook3 = "Quando você estiver pronto, clique no botão do álbum novamente, para fechá-lo" +TutorialLaffMeter1 = "Você também precisará disto..." +TutorialLaffMeter2 = "Você também precisará disto...\nÉ o seu Risômetro." +TutorialLaffMeter3 = "Quando os "+ Cogs +" atacarem você, ele diminui." +TutorialLaffMeter4 = "Quando você está em pátios como este, ele volta a subir." +TutorialLaffMeter5 = "Quando concluir as Tarefas Toon, você obterá recompensas, como o aumento do seu Limite de risadas." +TutorialLaffMeter6 = "Cuidado! Se os "+ Cogs +" derrotarem você, perderá todas as suas piadas." +TutorialLaffMeter7 = "Para obter mais piadas, divirta-se com os jogos no bondinho." +TutorialTrolley1 = "Siga-me até o bondinho!" +TutorialTrolley2 = "Pule nele!" +TutorialBye1 = "Brinque com alguns jogos!" +TutorialBye2 = "Divirta-se com alguns jogos!\nCompre algumas piadas!" +TutorialBye3 = "\Vá encontrar o "+ Flippy +" quando terminar!"# TutorialForceAcknowledge.py + +# TutorialForceAcknowledge.py +TutorialForceAcknowledgeMessage = "\Você está indo na direção errada! \Vá encontrar o "+ Mickey +"!"# SpeedChat + +PetTutorialTitle1 = "O Painel dos Rabiscos" +PetTutorialTitle2 = "Chat rápido dos Rabiscos" +PetTutorialTitle3 = "Gadálogo dos Rabiscos" +PetTutorialNext = "Próxima Página" +PetTutorialPrev = "Página Anterior" +PetTutorialDone = lOK +PetTutorialPage1 = "Clique em um Rabisco para exibir o painel de Rabiscos. Daqui, você pode alimentar, coçar e chamar o Rabisco." +PetTutorialPage2 = "Use a nova área 'Bichinhos' no menu Chat rápido para fazer com que um Rabisco faça um truque. Se ele fizer, recompense-o para ele melhorar ainda mais!" +PetTutorialPage3 = "Compre novos truques de Rabiscos no Gadálogo da Clarabela. Truques melhores produzem Toonar melhores!" +def getPetGuiAlign(): + from pandac.PandaModules import TextNode + return TextNode.ACenter + +GardenTutorialTitle1 = "Jardinagem" +GardenTutorialTitle2 = "Flores" +GardenTutorialTitle3 = "Árvores" +GardenTutorialTitle4 = "Instruções" +GardenTutorialTitle5 = "Estátuas" +GardenTutorialNext = "Próxima Página" +GardenTutorialPrev = "Página Anterior" +GardenTutorialDone = lOK +GardenTutorialPage1 = "Crie o seu próprio jardim botânico! Você pode plantar flores e árvores, e até erguer estátuas." +GardenTutorialPage2 = "As flores são sensíveis, e você precisa descobrir as suas receitas de balinhas. Plante todos os tipos para melhorar as risadas, e venda as flores para ganhar balinhas." +GardenTutorialPage3 = "Use uma piada para plantar uma árvore. Alguns dias depois, essa piada vai melhorar!! Mas cuide bem da saúde dela, ou a melhoria se vai." +GardenTutorialPage4 = "Para plantar, regar, cavar ou fazer a colheita no seu jardim, ande até estes locais." +GardenTutorialPage5 = "Estátuas podem ser compradas no Catálogo da Clarabela. Aumenta suas habilidades para destravar as estátuas mais extravagantes." + +# Playground.py +PlaygroundDeathAckMessage = "Os" + Cogs + " levaram todas as suas piadas!\n\nVocê está triste. Você não pode sair do pátio até ficar feliz." + +# FactoryInterior.py +ForcedLeaveFactoryAckMsg = "O Supervisor da fábrica foi derrotado antes de você alcançá-lo. Você não recuperou nenhuma parte do Cog." + +# MintInterior +ForcedLeaveMintAckMsg = "O Supervisor do Andar da Casa da Moeda foi derrotado antes de você alcançá-lo. Você não recuperou nenhuma Grana Cog." + +# DistributedFactory.py +HeadingToFactoryTitle = "Dirigindo-se a %s..." +ForemanConfrontedMsg = "%s está lutando com o Supervisor da fábrica!" + +# DistributedMint.py +MintBossConfrontedMsg = "%s está lutando com o Supervisor!" + +# DistributedStage.py +StageBossConfrontedMsg = "%s está lutando com o Funcionário!" +stageToonEnterElevator = "%s \nentrou no elevador" +ForcedLeaveStageAckMsg = "O Funcionário da Lei foi derrotado antes de você alcançá-lo. Você não recuperou nenhum Aviso de Júri." + +# DistributedMinigame.py +MinigameWaitingForOtherPlayers = "Aguardando outros jogadores..." +MinigamePleaseWait = "Aguarde..." +DefaultMinigameTitle = "Título do minijogo" +DefaultMinigameInstructions = "Instruções do minijogo" +HeadingToMinigameTitle = "Dirigindo-se a %s..." # minigame title + +# MinigamePowerMeter.py +MinigamePowerMeterLabel = "Medidor de potência" +MinigamePowerMeterTooSlow = "Muito\ndevagar" +MinigamePowerMeterTooFast = "Muito\nrápido" + +# DistributedMinigameTemplate.py +MinigameTemplateTitle = "Modelo de minijogo" +MinigameTemplateInstructions = "Este é um modelo de minijogo. Use-o para criar novos minijogos." + +# DistributedCannonGame.py +CannonGameTitle = "Jogo do canhão" +CannonGameInstructions = "Atire o seu Toon na torre de água o mais rápido que puder. Use o mouse ou as teclas de seta para mirar o canhão. Seja rápido e ganhe uma grande recompensa para todos!" +CannonGameReward = "RECOMPENSA" + +# DistributedTugOfWarGame.py +TugOfWarGameTitle = "Cabo de guerra" +TugOfWarInstructions = "Toque alternadamente nas teclas de seta para a esquerda e para a direita rápido o suficiente para alinhar a barra verde com a linha vermelha. Não toque nelas muito devagar, ou você acabará na água!" +TugOfWarGameGo = "COMEÇAR!" +TugOfWarGameReady = "Pronto..." +TugOfWarGameEnd = "Bom jogo!" +TugOfWarGameTie = "Você empatou!" +TugOfWarPowerMeter = "Medidor" + +# DistributedPatternGame.py +PatternGameTitle = "Acompanhe a "+ Minnie +PatternGameInstructions = "A " + Minnie + " mostrará uma seqüência de dança." + \ + "Tente repetir a dança da "+ Minnie +" exatamente como você vê usando as teclas de seta!" +PatternGameWatch = "Observe estes passos de dança..." +PatternGameGo = "COMEÇAR!" +PatternGameRight = "Bom, %s!" +PatternGameWrong = "Ops!" +PatternGamePerfect = "Perfeito, %s!" +PatternGameBye = "Obrigado por jogar!" +PatternGameWaitingOtherPlayers = "Aguardando outros jogadores..." +PatternGamePleaseWait = "Aguarde..." +PatternGameFaster = "Você foi\nmais rápido!" +PatternGameFastest = "Você foi o\nmais rápido!" +PatternGameYouCanDoIt = "Deixa disso!\nVocê consegue!" +PatternGameOtherFaster = "\nfoi mais rápido!" +PatternGameOtherFastest = "\nfoi o mais rápido!" +PatternGameGreatJob = "Muito bom!" +PatternGameRound = "Rodada %s!" # Round 1! Round 2! .. + +# DistributedRaceGame.py +RaceGameTitle = "Jogo de corrida" +RaceGameInstructions = "Clique em um número. Escolha bem! Você só avançará se ninguém mais escolher o mesmo número." +RaceGameWaitingChoices = "Esperando os outros jogadores escolherem..." +RaceGameCardText = "%(name)s aposta: %(reward)s" +RaceGameCardTextBeans = "%(name)s recebe: %(reward)s" +RaceGameCardTextHi1 = "%(name)s é um Toon fabuloso!" # this category might eventually have secret game hints, etc + +# RaceGameGlobals.py +RaceGameForwardOneSpace = " avança 1 espaço" +RaceGameForwardTwoSpaces = " avança 2 espaços" +RaceGameForwardThreeSpaces = " avança 3 espaços" +RaceGameBackOneSpace = " recua 1 espaço" +RaceGameBackTwoSpaces = " recua 2 espaços" +RaceGameBackThreeSpaces = " recua 3 espaços" +RaceGameOthersForwardThree = " todos os outros avançam \n3 espaços" +RaceGameOthersBackThree = "todos os outros recuam \n3 espaços" +RaceGameInstantWinner = "Vencedor imediato!" +RaceGameJellybeans2 = "2 balinhas" +RaceGameJellybeans4 = "4 balinhas" +RaceGameJellybeans10 = "10 balinhas!" + +# DistributedRingGame.py +RingGameTitle = "Jogo dos anéis" +# color +RingGameInstructionsSinglePlayer = "Tente nadar através do número máximo de anéis %s que conseguir. Para nadar, use as teclas de seta." +# color +RingGameInstructionsMultiPlayer = "Tente nadar através dos anéis %s. Os outros jogadores tentarão nadar através dos outros anéis coloridos. Para nadar, use as teclas de seta." +RingGameMissed = "PERDEU" +RingGameGroupPerfect = "GRUPO\nPERFEITO!!" +RingGamePerfect = "PERFEITO!" +RingGameGroupBonus = "BÔNUS DO GRUPO" + +# RingGameGlobals.py +ColorRed = "vermelhos" +ColorGreen = "verdes" +ColorOrange = "laranja" +ColorPurple = "lilases" +ColorWhite = "brancos" +ColorBlack = "pretos" +ColorYellow = "amarelos" + +# DistributedDivingGame.py #localize +DivingGameTitle = "Mergulho pro Tesouro" +# color +DivingInstructionsSinglePlayer = "Tesouros irão aparecer no fundo do lago. Use as setas para nadar. Evite os peixes and leve os tesouros para o barco!" +# color +DivingInstructionsMultiPlayer = " Tesouros irão aparecer no fundo do lago. Use as setas para nadar. Trabalhem juntos para levar os tesouros para o barco!" + +#Distributed Target Game +TargetGameTitle = "Estilingue do Toon" +TargetGameInstructionsSinglePlayer = "Acerta na velocidade do alvo" +TargetGameInstructionsMultiPlayer = "Acerta quantos alvos conseguir" + +# DistributedTagGame.py +TagGameTitle = "Jogo de pique" +TagGameInstructions = "Pegue os tesouros. Você não pode pegar os tesouros se o pique estiver com você!" +TagGameYouAreIt = "Está com você!" +TagGameSomeoneElseIsIt = "Está com %s!" + +# DistributedMazeGame.py +MazeGameTitle = "Jogo do labirinto" +MazeGameInstructions = "Pegue os tesouros. Tente pegar todos, mas cuidado com os "+ Cogs +"!"# DistributedCatchGame.py + +# DistributedCatchGame.py +CatchGameTitle = "Jogo de pegar" +CatchGameInstructions = "Pegue o máximo de %(fruit)s que conseguir. Cuidado com os "+ Cogs +" e tente não 'pegar' nenhuma %(badThing)s!" +CatchGamePerfect = "PERFEITO!" +CatchGameApples = 'maçãs' +CatchGameOranges = 'laranjas' +CatchGamePears = 'pêras' +CatchGameCoconuts = 'cocos' +CatchGameWatermelons = 'melancias' +CatchGamePineapples = 'abacaxis' +CatchGameAnvils = 'bigornas' + +# DistributedPieTossGame.py +PieTossGameTitle = "Jogo de lançamento de tortas" +PieTossGameInstructions = "Lance as tortas nos alvos." + +# MinigameRulesPanel.py +MinigameRulesPanelPlay = "JOGAR" + +# Purchase.py +GagShopName = "Loja de Piadas do Pateta" +GagShopPlayAgain = "JOGAR\nNOVAMENTE" +GagShopBackToPlayground = "SAIR DE NOVO \nPARA O PÁTIO" +GagShopYouHave = "Você tem %s balinhas para gastar" +GagShopYouHaveOne = "Você tem 1 balinha para gastar" +GagShopTooManyProps = "Sinto muito, você tem muitos acessórios" +GagShopDoneShopping = "FIM DAS\nCOMPRAS" +# name of a gag +GagShopTooManyOfThatGag = "Sinto muito, você já tem %s o suficiente" +GagShopInsufficientSkill = "Você não tem muita habilidade para isso ainda" +# name of a gag +GagShopYouPurchased = "Você comprou %s" +GagShopOutOfJellybeans = "Sinto muito, você não tem mais balinhas!" +GagShopWaitingOtherPlayers = "Aguardando outros jogadores..." +# these show up on the avatar panels in the purchase screen +GagShopPlayerDisconnected = "%s desconectou-se" +GagShopPlayerExited = "%s saiu" +GagShopPlayerPlayAgain = "Jogar novamente" +GagShopPlayerBuying = "Comprando" + +# MakeAToon.py +GenderShopQuestionMickey = "Para criar um Toon menino, clique em mim!" +GenderShopQuestionMinnie = "Para criar um Toon menina, clique em mim!" +GenderShopFollow = "Siga-me!" +GenderShopSeeYou = "Vejo você depois!" +GenderShopBoyButtonText = "Menino" +GenderShopGirlButtonText = "Menina" + +# BodyShop.py +BodyShopHead = "Cabeça" +BodyShopBody = "Corpo" +BodyShopLegs = "Pernas" + +# ColorShop.py +ColorShopHead = "Cabeça" +ColorShopBody = "Corpo" +ColorShopLegs = "Pernas" +ColorShopToon = "Toon" +ColorShopParts = "Partes" +ColorShopAll = "Tudo" + +# ClothesShop.py +ClothesShopShorts = "Short" +ClothesShopShirt = "Camisa" +ClothesShopBottoms = "Parte de baixo" + +# MakeAToon +MakeAToonDone = lOK +MakeAToonCancel = lCancel +MakeAToonNext = lNext +MakeAToonLast = lBack +CreateYourToon = "Clique nas setas para criar o seu Toon." +CreateYourToonTitle = "Crie o seu Toon" +CreateYourToonHead = "Clique nas setas da 'cabeça' para escolher animais diferentes." +MakeAToonClickForNextScreen = "Clique na seta abaixo para ir até a próxima tela." +PickClothes = "Clique nas setas para escolher roupas!" +PickClothesTitle = "Escolha as roupas" +PaintYourToon = "Clique nas setas para pintar o seu Toon!" +PaintYourToonTitle = "Pinte o seu Toon" +MakeAToonYouCanGoBack = "Você pode voltar para alterar o corpo também!" +MakeAFunnyName = "Escolha um nome engraçado para o seu Toon com o jogo Escolha um nome!" +MustHaveAFirstOrLast1 = "O seu Toon deve ter um nome ou um sobrenome, não é?" +MustHaveAFirstOrLast2 = "Você não quer que o seu Toon tenha um nome ou um sobrenome?" +ApprovalForName1 = "É isso aí, o seu Toon merece um nome muito legal!" +ApprovalForName2 = "Os nomes de Toons são os nomes mais legais que existem!" +MakeAToonLastStep = "Última etapa antes de ir para Toontown!" +PickANameYouLike = "Escolha o nome que quiser!" +NameToonTitle = "Dê um nome ao seu Toon" +TitleCheckBox = "Título" +FirstCheckBox = "Primeiro" +LastCheckBox = "Último" +RandomButton = "Aleatório" +NameShopSubmitButton = "Enviar" +TypeANameButton = "Digite um nome" +TypeAName = "Não gostou destes nomes?\nClique aqui -->" +PickAName = "Tente usar o jogo Escolha um nome!\nClique aqui -->" +PickANameButton = "Escolha um nome" +RejectNameText = "Este nome não é permitido. Tente novamente." +WaitingForNameSubmission = "Enviando o seu nome..." + +# PetshopGUI.py +PetNameMaster = "PetNameMaster_portuguese.txt" +PetshopUnknownName = "Nome: ???" +PetshopDescGender = "Sexo:\t%s" +PetshopDescCost = "Custo:\t%s balinhas" +PetshopDescTrait = "Características:\t%s" +PetshopDescStandard = "Padrão" +PetshopCancel = lCancel +PetshopSell = "Vender peixes" +PetshopAdoptAPet = "Adotar um Rabisco" +PetshopReturnPet = "Devolver o Rabisco" +PetshopAdoptConfirm = "Adotar %s por %d balinhas?" +PetshopGoBack = "Voltar" +PetshopAdopt = "Adotar" +PetshopReturnConfirm = "Devolver %s?" +PetshopReturn = "Devolver" +PetshopChooserTitle = "RABISCOS DE HOJE" +PetshopGoHomeText = 'Deseja ir à sua propriedade para brincar com seu novo Rabisco?' + +# NameShop.py +NameShopNameMaster = "NameMaster_portuguese.txt" +NameShopPay = "Assine já!" +NameShopPlay = "Avaliação gratuita" +NameShopOnlyPaid = "Somente usuários pagantes\npodem dar nomes aos seus toons.\nAté que você se inscreva,\nseu nome será\n" +NameShopContinueSubmission = "Continuar envio" +NameShopChooseAnother = "Escolha outro nome" +NameShopToonCouncil = "O Conselho de Toons\nanalisará o seu\nnome."+ \ + "A análise pode\nlevar alguns dias.\nEnquanto você espera,\nseu nome será\n" +PleaseTypeName = "Digite o seu nome:" +AllNewNames = "Todos os novos nomes\ndevem ser aprovados\npelo Conselho de Toons." +NameShopNameRejected = "O nome\nenviado foi\nrejeitado." +NameShopNameAccepted = "Parabéns!\nO nome\nenviado foi\naceito!" +NoPunctuation = "Não é permitido usar caracteres de pontuação nos nomes!" +PeriodOnlyAfterLetter = "Você pode usar um ponto no nome, mas apenas depois de uma letra." +ApostropheOnlyAfterLetter = "Você pode usar um apóstrofo no nome, mas apenas depois de uma letra." +NoNumbersInTheMiddle = "Dígitos numéricos podem não aparecer no meio da palavra." +ThreeWordsOrLess = "Seu nome deve ter três palavras ou menos." +CopyrightedNames = ( + "mickey", + "mickey mouse", + "mickeymouse", + "minnie", + "minnie mouse", + "minniemouse", + "donald", + "donald duck", + "donaldduck", + "pato donald", + "patodonald", + "pluto", + "goofy", + "pateta", + ) +NumToColor = ['Branco', 'Pêssego', 'Vermelho vivo', 'Vermelho', 'Castanho', + 'Siena', 'Marrom', 'Canela', 'Coral', 'Laranja', + 'Amarelo', 'Creme', 'Cítrico', 'Limão', 'Verde-água', + 'Verde', 'Azul-claro', 'Verde-azul', 'Azul', + 'Verde-musgo', 'Azul-turquesa', 'Azul cinzento', 'Lilás', + 'Púrpura', 'Rosa'] +AnimalToSpecies = { + 'dog' : 'Cachorro', + 'cat' : 'Gato', + 'mouse' : 'Rato', + 'horse' : 'Cavalo', + 'rabbit' : 'Coelho', + 'duck' : 'Pato', + 'monkey' : 'Macaco', + 'bear' : 'Urso', + 'pig' : 'Porco' + } +NameTooLong = "Este nome é muito longo. Tente novamente." +ToonAlreadyExists = "Você já tem um Toon com o nome %s!" +NameAlreadyInUse = "Este nome já foi usado!" +EmptyNameError = "Você deve primeiramente inserir um nome." +NameError = "Sinto muito. Este nome não vai funcionar." + +# NameCheck.py +NCTooShort = 'Este nome é muito curto.' +NCNoDigits = 'O nome não pode conter números.' +NCNeedLetters = 'Cada palavra do nome deve conter algumas letras.' +NCNeedVowels = 'Cada palavra do nome deve conter algumas vogais.' +NCAllCaps = 'O seu nome não pode estar todo em maiúsculas.' +NCMixedCase = 'Este nome tem muitas letras em maiúsculas.' +NCBadCharacter = "O seu nome não pode conter o caractere '%s'" +NCGeneric = 'Sinto muito, este nome não vai funcionar.' +NCTooManyWords = 'O seu nome não pode ter mais de quatro palavras.' +NCDashUsage = ("Hífens podem ser usados apenas para ligar duas palavras" + "(como em 'Bu-Bu').") +NCCommaEdge = "O seu nome não pode começar ou terminar com vírgula." +NCCommaAfterWord = "Você não pode começar uma palavra com vírgula." +NCCommaUsage = ('Este nome não usa vírgulas corretamente. As vírgulas devem' + 'juntar duas palavras, como no nome "Dr. Quack, MD".' + 'As vírgulas devem também ser seguidas por um espaço.') +NCPeriodUsage = ('Este nome não usa pontos corretamente. Os pontos são' + 'permitidos somente em palavras como "Sr.", "Sra.", "J.P.", etc.') +NCApostrophes = 'Este nome tem excesso de apóstrofos.' + +# DistributedTrophyMgrAI.py +RemoveTrophy = "Quartel dos Toons: Os "+ Cogs +" dominaram um dos edifícios que você salvou!" + +# toon\DistributedNPCTailor/Clerk/Fisherman.py +STOREOWNER_TOOKTOOLONG = 'Precisa de mais tempo para pensar?' +STOREOWNER_GOODBYE = 'Vejo você depois!' +STOREOWNER_NEEDJELLYBEANS = 'Você precisa pegar o bondinho para conseguir algumas balinhas.' +STOREOWNER_GREETING = 'Escolha o que deseja comprar.' +STOREOWNER_BROWSING = 'Você pode olhar, mas precisará de um tíquete de roupas para comprar.' +STOREOWNER_NOCLOTHINGTICKET = 'Para comprar roupas, você precisa de um tíquete de roupas.' + +STOREOWNER_NOFISH = 'Volte aqui para vender peixes para a loja de animais e ganhar balinhas.' +STOREOWNER_THANKSFISH = 'Valeu! A loja de animais vai adorar estes aqui. Tchau!' +STOREOWNER_THANKSFISH_PETSHOP = "Estes tipos são raros! Valeu." +STOREOWNER_PETRETURNED = "Não se preocupe. Acharemos um bom lar para o seu Rabisco." +STOREOWNER_PETADOPTED = "Parabéns pelo novo Rabisco! Você pode brincar com ele em sua propriedade." +STOREOWNER_PETCANCELED = "Lembre-se, caso veja um Rabisco de seu agrado, adote-o antes que alguém o faça!" + +STOREOWNER_NOROOM = "Hmm... Você pode precisar arranjar espaço no seu armário antes de comprar roupas novas.\n" +STOREOWNER_CONFIRM_LOSS = "O seu armário está cheio. Você vai perder as roupas que estava vestindo." +STOREOWNER_OK = lOK +STOREOWNER_CANCEL = lCancel +STOREOWNER_TROPHY = "Uau! Você pegou %s de %s peixe. Merece um troféu e um Acréscimo de risadas!" +# end translate + +# NewsManager.py +SuitInvasionBegin1 = lToonHQ+": Foi iniciada uma Invasão de Cogs!!!" +SuitInvasionBegin2 = lToonHQ+": %s dominaram Toontown!!!" +SuitInvasionEnd1 = lToonHQ+": A Invasão de %s terminou!!!" +SuitInvasionEnd2 = lToonHQ+": Mais uma vez os Toons salvaram a pátria!!!" +SuitInvasionUpdate1 = lToonHQ+": A Invasão de Cogs está agora em %s Cogs!!!" +SuitInvasionUpdate2 = lToonHQ+": Precisamos derrotar esses %s!!!" +SuitInvasionBulletin1 = lToonHQ+": Há uma Invasão de Cogs em andamento!!!" +SuitInvasionBulletin2 = lToonHQ+": %s dominaram Toontown!!!" + +# DistributedHQInterior.py +LeaderboardTitle = "Pelotão Toon" +# QuestScript.txt +QuestScriptTutorialMickey_1 = "Toontown ganhou um novo cidadão! Você tem piadas de reserva?" +QuestScriptTutorialMickey_2 = "Claro, %s!" +QuestScriptTutorialMickey_3 = "O Tutorial Tom vai contar para você tudo sobre os Cogs.\aTchauzinho!" +QuestScriptTutorialMickey_4 = "Vem cá! Use as teclas de seta para mover-se." + +# These are needed to correspond to the Japanese gender specific phrases +QuestScriptTutorialMinnie_1 = "Toontown ganhou um novo cidadão! Você tem piadas de reserva?" +QuestScriptTutorialMinnie_2 = "Claro, %s!" +QuestScriptTutorialMinnie_3 = "O Tutorial Tom vai contar para você tudo sobre os Cogs.\aTchauzinho!" + +QuestScript101_1 = "Estes são os COGS. Eles são robôs que estão tentando dominar Toontown." +QuestScript101_2 = "Há vários tipos diferentes de COGS e..." +QuestScript101_3 = "...eles transformam os alegres edifícios dos Toons..." +QuestScript101_4 = "...em horríveis edifícios de Cogs!" +QuestScript101_5 = "Mas os COGS não agüentam piadas!" +QuestScript101_6 = "Uma boa piada os deterá." +QuestScript101_7 = "Há milhares de piadas, mas, para começar, use estas aqui." +QuestScript101_8 = "Ah! Você também vai precisar de um Risômetro!" +QuestScript101_9 = "Se o seu Risômetro estiver baixo, é porque você está triste!" +QuestScript101_10 = "Um Toon feliz é um Toon saudável!" +QuestScript101_11 = "OH NÃO! Há um COG na porta da minha loja!" +QuestScript101_12 = "AJUDE-ME, POR FAVOR! Derrote este COG!" +QuestScript101_13 = "Esta é a sua primeira Tarefa Toon!" +QuestScript101_14 = "Vamos nessa! Vá derrotar aquele Puxa-saco!" + +QuestScript110_1 = "Bom trabalho; você derrotou aquele Puxa-saco. Deixe-me dar a você um Álbum Toon..." +QuestScript110_2 = "O livro é cheio de coisas legais." +QuestScript110_3 = "Abra-o para eu mostrar a você." +QuestScript110_4 = "O mapa mostra o local onde você esteve." +QuestScript110_5 = "Vire a página para ver as suas piadas..." +QuestScript110_6 = "Êpa! Você não tem nenhuma piada! Vou passar uma tarefa para você." +QuestScript110_7 = "Vire a página para ver as suas tarefas." +QuestScript110_8 = "Dê uma volta no bondinho para ganhar balinhas e poder comprar piadas!" +QuestScript110_9 = "Para ir até o bondinho, saia pela porta logo atrás de mim e siga até o pátio." +QuestScript110_10 = "Agora, feche o livro e encontre o bondinho!" +QuestScript110_11 = "Volte para o Quartel dos Toons quando já estiver pronto. Tchau!" + +QuestScriptTutorialBlocker_1 = "Oi, e aí, pessoal?" +QuestScriptTutorialBlocker_2 = "Alô?" +QuestScriptTutorialBlocker_3 = "Ah! Você não sabe usar o Chat rápido!" +QuestScriptTutorialBlocker_4 = "Clique no botão para dizer algo." +QuestScriptTutorialBlocker_5 = "Muito bom!\aO local para onde você está indo tem um monte de toons para conversar." +QuestScriptTutorialBlocker_6 = "Se você quiser conversar com seus amigos usando o teclado, há um outro botão que pode ser usado." +QuestScriptTutorialBlocker_7 = "Ele se chama botão \"Conversar\". Você precisa ser um cidadão oficial de Toontown para usá-lo." +QuestScriptTutorialBlocker_8 = "Boa sorte! Vejo você depois!" + +""" +GagShopTut + + Você também vai conseguir a capacidade de usar outros tipos de piadas. + +""" + +QuestScriptGagShop_1 = "Bem-vindo à Loja de Piadas!" +QuestScriptGagShop_1a = "Aqui é o lugar onde os Toons vêm comprar piadas para usar contra os Cogs." +#QuestScriptGagShop_2 = "Este pote mostra quantas balinhas você tem." +#QuestScriptGagShop_3 = "Para comprar piadas, clique em botões de piada. Tente pegar umas agora!" +QuestScriptGagShop_3 = "Para comprar piadas, clique em botões de piada. Tente pegar algumas agora!" +QuestScriptGagShop_4 = "Bom! Você pode usar estas piadas nas batalhas contra os Cogs." +QuestScriptGagShop_5 = "Dê uma olhada para ver como são as piadas avançadas de jogar e de esguichar..." +QuestScriptGagShop_6 = "Depois que terminar de comprar piadas, clique neste botão para retornar ao Pátio." +QuestScriptGagShop_7 = "Normalmente, você pode usar este botão para participar de outro Jogo no Bondinho..." +QuestScriptGagShop_8 = "...Mas não há tempo para outro jogo agora. Estão precisando de você no Quartel dos Toons!" + +QuestScript120_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, você encontrou o Banqueiro Beto?\aEle é bem guloso por doces.\aPor que você não se apresenta e leva para ele este chocolate de presente." +QuestScript120_2 = "O Banqueiro Beto está lá no Banco de Toontown." + +QuestScript121_1 = "Mmm, obrigado pelo chocolate.\aOlha só, se você me ajudar, eu dou a você uma recompensa.\aEsses Cogs roubaram as chaves do meu cofre. Derrote os Cogs para encontrar a chave roubada.\aQuando você encontra-la, traga-a para mim." + +QuestScript130_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, recebi hoje um pacote para o Professor Paulo.\aDeve ser o novo giz que ele encomendou.\aVocê pode, por favor, levar para ele?\aEle está lá na escola." + +QuestScript131_1 = "Ah, obrigado pelo giz.\aO quê?!?\aEsses Cogs roubaram meu quadro-negro. Derrote os Cogs para encontrar meu quadro-negro roubado.\aQuando encontrá-lo, traga de volta para mim." + +QuestScript140_1 = "Muito bem, você encontrou o bondinho!\aPor falar nisso, tenho um amigo, o Bibliotecário Bino, que é uma verdadeira traça de livros.\aPeguei este livro para ele da última vez em que estive no Porto do Donald.\aVocê podia levar para ele? Em geral, ele fica na Biblioteca." + +QuestScript141_1 = "Ah, sim, este livro vai quase completar a minha coleção.\aDeixe-me ver...\aÊpa...\aOnde é que eu pus os meus óculos agora?\aEu estava com eles um pouco antes de aqueles Cogs invadirem o meu edifício.\aDerrote os Cogs para encontrar meus óculos roubados.\aaQuando encontrá-los, traga de volta para mim para ganhar uma recompensa." + +QuestScript145_1 = "Estou vendo que você não teve problemas com o bondinho!\a Olha só, os Cogs roubaram o apagador do nosso quadro-negro.\a Vá para as ruas e lute com os Cogs até recuperar o apagador.\a Para encontrar as ruas, passe por um dos túneis como este:" +QuestScript145_2 = "Quando encontrar nosso apagador, traga-o de volta para cá.\aNão se esqueça, se precisar de piadas, pegue o bondinho.\aE se você precisar recuperar Pontos de risadas, colete casquinhas de sorvete no Pátio." + +QuestScript150_1 = "Ah... Esta próxima tarefa talvez seja muito difícil para você executar sozinho!" +QuestScript150_2 = "Para fazer amigos, encontre outro jogador e use o botão Novo amigo." +QuestScript150_3 = "Depois que você tiver arrumado um amigo, volte aqui." +QuestScript150_4 = "Algumas tarefas são muito difíceis de serem executadas sem ajuda!" + +# To make sure the language checker is working +# DO NOT TRANSLATE THIS +MissingKeySanityCheck = "Ignore me" + +SellbotBossName = "V. P. Sênior" +CashbotBossName = "Diretor Financeiro" +LawbotBossName = "Juiz-chefe" +BossCogNameWithDept = "%(name)s\n%(dept)s" +BossCogPromoteDoobers = "Com isto, você está promovido a %s sênior. Parabéns!" +BossCogDoobersAway = { 's' : "Vai! E faça essa venda!" } +BossCogWelcomeToons = "Bem-vindos, novos Cogs!" +BossCogPromoteToons = "Com isto, você está promovido a %s sênior. Parab--" +CagedToonInterruptBoss = "Oi! Uhuu! E aí pessoal!" +CagedToonRescueQuery = "Então, galera de Toons, vocês vêm me salvar?" +BossCogDiscoverToons = "Hã? Toons! Disfarçar!" +BossCogAttackToons = "Atacar!!" +CagedToonDrop = [ + "Bom trabalho! Ele está ficando exausto!", + "Fique atrás dele! Ele está fugindo!", + "Pessoal, vocês estão se saindo muito bem!", + "Fantástico! Você quase o pegou agora!", + ] +CagedToonPrepareBattleTwo = "Cuidado, ele está tentando escapar!\aAjudem-me todos! Levantem-se aqui e detenham-no!" +CagedToonPrepareBattleThree = "Maneiro! Estou quase livre!\aAgora, você precisa atacar o Cog V. P. em pessoa.\aTenho um montão de tortas que você pode usar!\aPule e toque na parte inferior da minha cela para que eu lhe dê algumas tortas.\aPressione a tecla Insert para jogar as tortas quando você as pegar!" +BossBattleNeedMorePies = "Você precisa de mais tortas!" +BossBattleHowToGetPies = "Pule para tocar na cela e pegar mais tortas." +BossBattleHowToThrowPies = "Pressione a tecla Insert para jogar tortas!" +CagedToonYippee = "Iupii!!" +CagedToonThankYou = "É ótimo estar livre!\aObrigado por toda a sua ajuda!\aTe devo esta.\aSe, por acaso, você estiver em apuros em alguma batalha, é só me chamar!\aBasta clicar no botão SOS para me chamar." +CagedToonPromotion = "\aOlha só! Aquele Cog V.P. acabou deixando aqui os seus documentos de promoção.\aVou arquivá-los para você na saída, para que pegue a promoção!" +CagedToonLastPromotion = "\aUau, você atingiu o nível %s no processo Cog!\aOs Cogs não têm promoção maior do que esta.\aVocê não pode mais subir no processo Cog, mas certamente pode continuar salvando os Toons!" +CagedToonHPBoost = "\aVocê salvou um monte de Toons neste quartel.\aO Conselho de Toons decidiu dar a você outro Ponto de risadas. Parabéns!" +CagedToonMaxed = "\aVi que você tem o nível %s no processo Cog. Impressionante!\aEm nome do Conselho de Toons, agradeço por retornar para salvar mais Toons!" +CagedToonGoodbye = "Te vejo por aí!" + +CagedToonBattleThree = { + 10: "Belo salto, %(toon)s. Tome aqui algumas tortas!", + 11: "Oi, %(toon)s! Pegue algumas tortas!", + 12: "E aí, %(toon)s? Agora, você tem algumas tortas!", + + 20: "Olá, %(toon)s! Pule até a minha cela e pegue algumas tortas para jogar!", + 21: "Oi, %(toon)s! Use a tecla Ctrl para pular e tocar a minha cela!", + + 100: "Pressione a tecla Insert para jogar uma torta.", + 101: "O medidor de potência azul mostra a altura que a sua torta atinge.", + 102: "Primeiramente, tente jogar uma torta dentro da lataria dele para melecar seus mecanismos.", + 103: "Espere até que a porta se abra para jogar uma torta bem lá dentro.", + 104: "Quando ele estiver tonto, bata na cara ou no peito dele para empurrá-lo para trás!", + 105: "Você saberá se o seu golpe foi bom quando vir o chão colorido.", + 106: "Se você atingir um Toon com uma torta, ele ganhará um Ponto de risadas!", + } +CagedToonBattleThreeMaxGivePies = 12 +CagedToonBattleThreeMaxTouchCage = 21 +CagedToonBattleThreeMaxAdvice = 106 + +CashbotBossHadEnough = "É isso aí! Chega desses Toons irritantes!" +CashbotBossOuttaHere = "Tenho que pegar o trem!" +ResistanceToonName = "Mata Rara" +ResistanceToonCongratulations = "Você conseguiu! Parabéns!\aVocê é um orgulho para a Resistência!\aEsta é uma frase especial que você pode usar quando estiver em apuros:\a%s\aQuando você a pronunciar, %s.\aMas só pode usar uma vez, portanto, escolha a hora certa!" +ResistanceToonToonupInstructions = "todos os Toons próximos a você ganham %s pontos de risadas" +ResistanceToonToonupAllInstructions = "todos os Toons próximos a você ganham pontos de risadas completos" +ResistanceToonMoneyInstructions = "todos os Toons próximos a você ganham %s balinhas" +ResistanceToonMoneyAllInstructions = "todos os Toons próximos a você encherão suas jarras de balinhas" +ResistanceToonRestockInstructions = "todos os Toons próximos a você vão reabastecer suas \"%s\" piadas" +ResistanceToonRestockAllInstructions = "todos os Toons próximos a você vão reabastecer todas as suas piadas" + +ResistanceToonLastPromotion = "\aUau, você atingiu o nível %s no processo Cog!\aOs Cogs não têm promoção maior do que esta.\aVocê não pode mais subir no processo Cog, mas, certamente, pode continuar trabalhando para a Resistência!" +ResistanceToonHPBoost = "\aVocê trabalhou muito para a Resistência.\aO Conselho de Toons decidiu dar a você outro Ponto de risadas. Parabéns!" +ResistanceToonMaxed = "\aVejo que você tem o nível %s no processo Cog. Impressionante!\aEm nome do Conselho de Toons, agradeço por retornar para salvar mais Toons!" + +CashbotBossCogAttack = "Peguem-nos!!!" +ResistanceToonWelcome = "Você conseguiu! Siga-me até o cofre-forte antes que o Diretor Financeiro nos ache!" +ResistanceToonTooLate = "Droga! Estamos atrasados demais!" +CashbotBossDiscoverToons1 = "Ah-HAH!" +CashbotBossDiscoverToons2 = "Pensei ter farejado um tooninho por aqui! Impostores!" +ResistanceToonKeepHimBusy = "Mantenha-o ocupado! Vou montar uma armadilha!" +ResistanceToonWatchThis = "Olha isso!" +CashbotBossGetAwayFromThat = "Ei! Afaste-se!" +ResistanceToonCraneInstructions1 = "Controle um ímã subindo no pódio." +ResistanceToonCraneInstructions2 = "Use as teclas de setas para mover o guindaste e pressione a tecla Ctrl para pegar um objeto." +ResistanceToonCraneInstructions3 = "Pegue um cofre com o ímã e arranque o capacete de segurança do Diretor Financeiro." +ResistanceToonCraneInstructions4 = "Depois de fazer zunir o capacete, pegue um brutamontes desativado e dê uma pancada na cabeça dele!" +ResistanceToonGetaway = "Ih! Tenho que correr!" +CashbotCraneLeave = "Deixar o guindaste" +CashbotCraneAdvice = "Use as teclas de setas para mover o guindaste de pórtico." +CashbotMagnetAdvice = "Mantenha pressionada a tecla Ctrl para pegar os objetos." +CashbotCraneLeaving = "Deixando o guindaste" + +BossElevatorRejectMessage = "Você não pode pegar este elevador até que tenha recebido uma promoção." +MintElevatorRejectMessage = "Não será possível entrar na Casa da Moeda até que a vestimenta de Cog %s esteja completa." + +FurnitureTypeName = "Mobília" +PaintingTypeName = "Pintura" +ClothingTypeName = "Roupas" +ChatTypeName = "Frase do Chat rápido" +EmoteTypeName = "Aulas de representação" +BeanTypeName = "Jellybeans" +PoleTypeName = "Vara de pescar" +WindowViewTypeName = "Vista da janela" +PetTrickTypeName = 'Treinamento de Rabiscos' +GardenTypeName = 'Materiais de Jardim' +RentalTypeName = 'Item de Aluguel' +GardenStarterTypeName = 'Kit de Jardinagem' + +#rental names +RentalTime = "Horas de" +RentalCannon = "Canhões!" + +MessageConfirmRent = "Iniciar o aluguel? Cancele para guardar o aluguel para depois" +MessageConfirmGarden = "Você quer mesmo iniciar um jardim?" + +FurnitureYourOldCloset = "seu armário velho" +FurnitureYourOldBank = "seu banco velho" + +# How to put quotation marks around chat items--don't translate yet. +ChatItemQuotes = '"%s"' + +# CatalogFurnitureItem.py +FurnitureNames = { + 100 : "Poltrona", + 105 : "Poltrona", + 110 : "Cadeira", + 120 : "Cadeira de escrivaninha", + 130 : "Cadeira de jardim", + 140 : "Cadeira lagosta", + 145 : "Cadeira salva-vidas", + 150 : "Banco de sela", + 160 : "Cadeira nativa", + 170 : "Cadeira-bolinho", + 200 : "Cama", + 205 : "Cama", + 210 : "Cama", + 220 : "Cama banheira", + 230 : "Cama de folhas", + 240 : "Cama-barco", + 250 : "Rede de cáctus", + 260 : "Cama de sorvete", + 300 : "Pianola", + 310 : "Órgão de tubo", + 400 : "Lareira", + 410 : "Lareira", + 420 : "Lareira redonda", + 430 : "Lareira", + 440 : "Lareira-maçã", + 500 : "Armário", + 502 : "Armário com 15 itens", + 510 : "Armário", + 512 : "Armário com 15 itens", + 600 : "Abajur pequeno", + 610 : "Abajur grande", + 620 : "Abajur de mesa", + 625 : "Abajur de mesa", + 630 : "Abajur da Margarida", + 640 : "Abajur da Margarida", + 650 : "Abajur da Água-viva", + 660 : "Abajur da Água-viva", + 670 : "Abajur do vaqueiro", + 700 : "Cadeira estofada", + 705 : "Cadeira estofada", + 710 : "Sofá", + 715 : "Sofá", + 720 : "Sofá de feno", + 730 : "Sofá-torta", + 800 : "Escrivaninha", + 810 : "Mesinha", + 900 : "Porta-guarda-chuva", + 910 : "Cabideiro", + 920 : "Lata de lixo", + 930 : "Cogumelo vermelho", + 940 : "Cogumelo amarelo", + 950 : "Cabideiro", + 960 : "Mesinha-barril", + 970 : "Planta cáctus", + 980 : "Tenda", + 1000 : "Tapete grande", + 1010 : "Tapete redondo", + 1015 : "Tapete redondo", + 1020 : "Tapete pequeno", + 1030 : "Capacho de folha", + 1100 : "Vitrina", + 1110 : "Vitrina", + 1120 : "Estante alta", + 1130 : "Estante baixa", + 1140 : "Arca-sundae", + 1200 : "Mesinha lateral", + 1210 : "Mesa pequena", + 1215 : "Mesa pequena", + 1220 : "Mesinha de centro", + 1230 : "Mesinha de centro", + 1240 : "Mesa Snorkel", + 1250 : "Mesa-biscoito", + 1260 : "Mesa do quarto", + 1300 : "Banco 1.000 Balas", + 1310 : "Banco 2.500 Balas", + 1320 : "Banco 5.000 Balas", + 1330 : "Banco 7.500 Balas", + 1340 : "Banco 10.000 Balas", + 1399 : "Telefone", + 1400 : "Toon Cezanne", + 1410 : "Flores", + 1420 : "Mickey Moderno", + 1430 : "Toon Rembrandt", + 1440 : "Toonescape", + 1441 : "Cavalo Assobiador", + 1442 : "Estrela Toon", + 1443 : "Não é Torta", + 1500 : "Rádio", + 1510 : "Rádio", + 1520 : "Rádio", + 1530 : "Televisão", + 1600 : "Vasinho", + 1610 : "Vaso alto", + 1620 : "Vasinho", + 1630 : "Vaso alto", + 1640 : "Vasinho", + 1650 : "Vasinho", + 1660 : "Vaso Coral", + 1661 : "Vaso de concha", + 1700 : "Carrocinha de pipoca", + 1710 : "Joaninha", + 1720 : "Chafariz", + 1725 : "Lavadora de roupa", + 1800 : "Aquário", + 1810 : "Aquário", + 1900 : "Peixe-espada", + 1910 : "Tubarão-martelo", + 1920 : "Chifres de pendurar", + 1930 : "Sombreiro simples", + 1940 : "Sombreiro elegante", + 1950 : "Apanhador de sonhos", + 1960 : "Ferradura", + 1970 : "Retrato de búfalo", + 2000 : "Balanço de doces", + 2010 : "Escorregada de torta", + 3000 : "Banheira banana split", + 10000 : "Moranga", + 10010 : "Abóbora", + } + +# CatalogClothingItem.py +ClothingArticleNames = ( + "Camisa", + "Camisa", + "Camisa", + "Short", + "Short", + "Saia", + "Short", + ) + +ClothingTypeNames = { + 1400 : "Camisa do Mateus", + 1401 : "Camisa da Jéssica", + 1402 : "Camisa da Marisa", + } + +# CatalogSurfaceItem.py +SurfaceNames = ( + "Papel de parede", + "Moldura do teto", + "Piso", + "Lambri", + "Moldura", + ) + +WallpaperNames = { + 1000 : "Pergaminho", + 1100 : "Milão", + 1200 : "Dover", + 1300 : "Vitória", + 1400 : "Newport", + 1500 : "Pastoral", + 1600 : "Arlequim", + 1700 : "Lua", + 1800 : "Estrelas", + 1900 : "Flores", + 2000 : "Jardim de primavera", + 2100 : "Jardim formal", + 2200 : "Dia de corrida", + 2300 : "Gol!", + 2400 : "Nuvem 9", + 2500 : "Trepadeira", + 2600 : "Primavera", + 2700 : "Kokeshi", + 2800 : "Arranjo de flores", + 2900 : "Peixe-anjo", + 3000 : "Bolhas", + 3100 : "Bolhas", + 3200 : "Ir pescar", + 3300 : "Parar de pescar", + 3400 : "Cavalo-marinho", + 3500 : "Conchinhas do mar", + 3600 : "Debaixo d'água", + 3700 : "Botinas", + 3800 : "Cáctus", + 3900 : "Chapéu de vaqueiro", + 10100 : "Gatos", + 10200 : "Morcegos", + 11000 : "Flocos de neve", + 11100 : "Folhas de Natal", + 11200 : "Boneco de neve", + 13000 : "Trevo", + 13100 : "Trevo", + 13200 : "Arco-íris", + 13300 : "Trevo", + } + +FlooringNames = { + 1000 : "Tábua-corrida", + 1010 : "Carpete", + 1020 : "Piso em losangos", + 1030 : "Piso em losangos", + 1040 : "Grama", + 1050 : "Tijolinho bege", + 1060 : "Tijolinho vermelho", + 1070 : "Piso quadrado", + 1080 : "Pedra", + 1090 : "Calçada", + 1100 : "Terra", + 1110 : "Sinteco", + 1120 : "Lajota", + 1130 : "Favo", + 1140 : "Água", + 1150 : "Piso praiano", + 1160 : "Piso praiano", + 1170 : "Piso praiano", + 1180 : "Piso praiano", + 1190 : "Areia", + 10000 : "Cubo de gelo", + 10010 : "Iglu", + 11000 : "Trevo", + 11010 : "Trevo", + } + +MouldingNames = { + 1000 : "Nós", + 1010 : "Pintado", + 1020 : "Dental", + 1030 : "Flores", + 1040 : "Flores", + 1050 : "Joaninha", + } + +WainscotingNames = { + 1000 : "Pintado", + 1010 : "Painel de madeira", + 1020 : "Madeira", + } + +# CatalogWindowItem.py--don't translate yet. +WindowViewNames = { + 10 : "Jardim amplo", + 20 : "Jardim selvagem", + 30 : "Jardim grego", + 40 : "Paisagem urbana", + 50 : "Velho Oeste", + 60 : "Fundo do mar", + 70 : "Ilha tropical", + 80 : "Noite estrelada", + 90 : "Piscina Tiki", + 100 : "Fronteira congelada", + 110 : "Fazenda", + 120 : "Campo Nativo", + 130 : "Rua Principal", + } + +# don't translate yet +NewCatalogNotify = "Há novos itens disponíveis para serem encomendados por telefone!" +NewDeliveryNotify = "Chegou correspondência nova em sua caixa de correio!" +CatalogNotifyFirstCatalog = "Seu primeiro catálogo chegou! Você pode usá-lo para encomendar novos itens para uso pessoal ou para casa." +CatalogNotifyNewCatalog = "O seu catálogo No. %s chegou! Você pode fazer os pedidos dos itens do catálogo pelo telefone." +CatalogNotifyNewCatalogNewDelivery = "Chegou correspondência nova em sua caixa de correio! Além disso, o seu catálogo No. %s chegou!" +CatalogNotifyNewDelivery = "Chegou correspondência nova em sua caixa de correio!" +CatalogNotifyNewCatalogOldDelivery = "O seu catálogo No. %s chegou, e ainda há itens aguardando por você em sua caixa de correio!" +CatalogNotifyOldDelivery = "Ainda há itens aguardando por você em sua caixa de correio!" +CatalogNotifyInstructions = "Clique no botão \"Ir para casa\" na Página do mapa em seu Álbum Toon e vá até o telefone que há dentro da sua casa." +CatalogNewDeliveryButton = "Nova\nentrega!" +CatalogNewCatalogButton = "Novo\ncatálogo" +CatalogSaleItem = "À venda!" + +# don't translate yet +DistributedMailboxEmpty = "A sua caixa de correio está vazia no momento. Volte aqui para procurar entregas depois que você fizer um pedido pelo telefone!" +DistributedMailboxWaiting = "A sua caixa de correio está vazia no momento, mas o pacote que você encomendou está a caminho. Verifique mais tarde!" +DistributedMailboxReady = "Sua encomenda chegou!" +DistributedMailboxNotOwner = "Sinto muito, esta não é a sua caixa de correio." +DistributedPhoneEmpty = "Você pode usar qualquer telefone para encomendar itens especiais para uso pessoal ou para sua casa. Em breve, haverá novos itens disponíveis para pedidos.\n\nNão há nenhum item disponível para pedidos no momento, mas verifique novamente mais tarde!" + +# don't translate yet +Clarabelle = "Clarabela" +MailboxExitButton = "Fechar caixa\nde correio" +MailboxAcceptButton = "Pegar este item" +MailBoxDiscard = "Remover este item" +MailBoxDiscardVerify = "Você quer mesmo Remover %s?" +MailboxOneItem = "Sua caixa postal contém 1 item." +MailboxNumberOfItems = "Sua caixa postal contém %s itens." +MailboxGettingItem = "Pegando %s da caixa postal." +MailboxGiftTag = "Presente De: %s" +MailboxGiftTagAnonymous = "Anônimo" +MailboxItemNext = "Próximo\nitem" +MailboxItemPrev = "Item\nanterior" +MailboxDiscard = "Remover" +MailboxLeave = "Guardar" +CatalogCurrency = "balas" +CatalogHangUp = "Desligar" +CatalogNew = "NOVA" +CatalogBackorder = "ENCOMENDA" +CatalogPagePrefix = "Página" +CatalogGreeting = "Olá! Agradecemos sua ligação para o Catálogo da Clarabela. Posso ajudar?" +CatalogGoodbyeList = ["Agora tchau!", + "Ligue novamente em breve!", + "Agradecemos sua ligação!", + "Ok, agora tchau!", + "Tchau!", + ] +CatalogHelpText1 = "Vire a página para ver os itens à venda." +CatalogSeriesLabel = "Série %s" +CatalogGiftFor = "Comprar Presente para:" +CatalogGiftTo = "Para: %s" +CatalogGiftToggleOn = "Parar de Presentear" +CatalogGiftToggleOff = "Comprar Presentes" +CatalogPurchaseItemAvailable = "Parabéns pela nova compra! Você já pode usar o seu produto imediatamente." +CatalogPurchaseGiftItemAvailable = "Ótimo! %s pode começar a usar o seu presente agora mesmo." +CatalogPurchaseItemOnOrder = "Parabéns! O produto será entregue em sua caixa de correio em breve." +CatalogPurchaseGiftItemOnOrder = "Ótimo! O seu presente para %s será entregue na caixa de correio dele." +CatalogAnythingElse = "Deseja mais alguma coisa hoje?" +CatalogPurchaseClosetFull = "O seu armário está cheio. Apesar disso, você pode comprar este item, mas se comprar, terá que excluir alguma coisa do seu armário para liberar espaço para o novo item, quando ele chegar.\n\nQuer comprar este item mesmo assim?" +CatalogAcceptClosetFull = "O seu armário está cheio. Entre em casa e exclua alguma coisa do seu armário para liberar espaço para o item antes de retirá-lo da caixa de correio." +CatalogAcceptShirt = "Você está vestindo agora a sua nova camisa. O que você estava vestindo antes foi transferido para o seu armário." +CatalogAcceptShorts = "Você está vestindo agora o seu novo short. O que você estava vestindo antes foi transferido para o seu armário." +CatalogAcceptSkirt = "Você está vestindo agora a sua nova saia. A que você estava vestindo antes foi transferida para o seu armário." +CatalogAcceptPole = "Agora, você está pronto para pescar uns peixes maiores com sua nova vara!" +CatalogAcceptPoleUnneeded = "Você já tem uma vara de pescar melhor do que esta!" +CatalogAcceptChat = "Você ganhou uma nova frase de Chat rápido!" +CatalogAcceptEmote = "Você ganhou uma nova Emoção!" +CatalogAcceptBeans = "Você recebeu algumas balinhas!" +CatalogAcceptRATBeans = "A sua recompensa de recruta Toon chegou!" +CatalogAcceptGarden = "Os seus materiais de jardim chegaram!" +CatalogAcceptPet = "Você ganhou um novo Truque de Rabisco!" +CatalogPurchaseHouseFull = "Sua casa está cheia. Apesar disso, você pode comprar este item, mas se comprar, terá que excluir alguma coisa da sua casa para liberar espaço para o novo item, quando ele chegar.\n\nQuer comprar este item mesmo assim?" +CatalogAcceptHouseFull = "Sua casa está cheia. Entre em casa e exclua alguma coisa de lá para liberar espaço para o item antes de retirá-lo da caixa de correio." +CatalogAcceptInAttic = "O seu novo item está agora no sótão. Você pode colocá-lo em casa entrando lá e clicando no botão \"Mover mobília\"." +CatalogAcceptInAtticP = "Os seus novos itens estão agora no sótão. Você pode colocá-los em casa entrando lá e clicando no botão \"Mover mobília\"." +CatalogPurchaseMailboxFull = "Sua caixa de correio está cheia! Você não poderá comprar este item até retirar alguns itens da caixa de correio para liberar espaço." +CatalogPurchaseGiftMailboxFull = "A caixa de correio de %s está cheia! Você não pode comprar este item." +CatalogPurchaseOnOrderListFull = "Você tem itens demais encomendados no momento. Você não poderá encomendar mais nenhum item até que cheguem alguns já encomendados." +CatalogPurchaseGiftOnOrderListFull = "%s tem ítens demais encomendados." +CatalogPurchaseGeneralError = "Não foi possível encomendar o item devido a um erro interno no jogo: código de erro %s." +CatalogAcceptGeneralError = "Não foi possível remover o item de sua caixa de correio devido a um erro interno no jogo: código de erro %s." +CatalogPurchaseGiftNotAGift = "Este item não pôde ser enviado para %s porque seria uma vantagem injusta." +CatalogPurchaseGiftWillNotFit = "Este item não pôde ser enviado para %s porque não vai servir." +CatalogPurchaseGiftLimitReached = "Este item não pôde ser enviado para %s porque ele já o possui." +CatalogPurchaseGiftNotEnoughMoney = "Este item não pôde ser enviado para %s porque ele não pode pagar." +CatalogAcceptGeneralError = "Este item não pôde ser excluído da sua caixa de correio por causa de um erro interno do jogo: código do erro %s." +CatalogAcceptRoomError = "Você não tem espaço para isto. Você vai ter que se livrar de alguma coisa." +CatalogAcceptLimitError = "Você já tem o número máximo possível disto. Você vai ter que se livrar de alguma coisa." +CatalogAcceptFitError = "Isto não serve em você! Você o doa para toons que precisam." +CatalogAcceptInvalidError = "Este item saiu da moda! Você o doa para toons que precisam." + +MailboxOverflowButtonDicard = "Remover" +MailboxOverflowButtonLeave = "Sair" + +HDMoveFurnitureButton = "Mover\nmobília" +HDStopMoveFurnitureButton = "Mudança\nconcluída" +HDAtticPickerLabel = "No sótão" +HDInRoomPickerLabel = "Na sala" +HDInTrashPickerLabel = "Na lixeira" +HDDeletePickerLabel = "Excluir?" +HDInAtticLabel = "Sótão" +HDInRoomLabel = "Sala" +HDInTrashLabel = "Lixo" +HDToAtticLabel = "Enviar\npara o sótão" +HDMoveLabel = "Mover" +HDRotateCWLabel = "Girar para a direita" +HDRotateCCWLabel = "Girar para a esquerda" +HDReturnVerify = "Retornar este item para o sótão?" +HDReturnFromTrashVerify = "Retornar este item para o sótão, da lixeira?" +HDDeleteItem = "Clique em OK para enviar este item para a lixeira ou em Cancelar para mantê-lo." +HDNonDeletableItem = "Você não pode excluir itens deste tipo!" +HDNonDeletableBank = "Você não pode excluir o seu banco!" +HDNonDeletableCloset = "Você não pode excluir o seu armário!" +HDNonDeletablePhone = "Você não pode excluir o seu telefone!" +HDNonDeletableNotOwner = "Você não pode excluir as coisas de %s's!" +HDHouseFull = "Sua casa está cheia. Você precisa excluir algo mais de sua casa ou do sótão antes de recuperar este item da lixeira." + +HDHelpDict = { + "DoneMoving" : "Decoração da sala concluída.", + "Attic" : "Mostrar lista de itens do sótão. O sótão armazena itens que não estão na sala.", + "Room" : "Mostrar lista de itens da sala. Útil para encontrar itens perdidos.", + "Trash" : "Mostrar itens da lixeira. Os itens mais antigos são excluídos após um tempo ou quando a lixeira fica cheia demais.", + "ZoomIn" : "Tenha uma visão ampliada da sala.", + "ZoomOut" : "Tenha uma visão reduzida da sala.", + "SendToAttic" : "Envie o item de mobília atual para o sótão, para armazená-lo.", + "RotateLeft" : "Vire para a esquerda.", + "RotateRight" : "Vire para a direita.", + "DeleteEnter" : "Alterne para o modo de exclusão.", + "DeleteExit" : "Saia do modo de exclusão.", + "FurnitureItemPanelDelete" : "Envie o item %s para a lixeira.", + "FurnitureItemPanelAttic" : "Coloque o item %s na sala.", + "FurnitureItemPanelRoom" : "Voltar o item %s para o sótão.", + "FurnitureItemPanelTrash" : "Voltar o item %s para o sótão.", + } + +MessagePickerTitle = "Você tem frases demais. Para comprar o item\n\"%s\"\n você precisa escolher um deles para ser removido:" +MessagePickerCancel = lCancel +MessageConfirmDelete = "Tem certeza de que deseja remover \"%s\" do menu de Chat rápido?" + +CatalogBuyText = "Comprar" +CatalogRentText = "Alugar" +CatalogGiftText = "Presente" +CatalogOnOrderText = "Encomendado" +CatalogPurchasedText = "Já\ncomprado" +CatalogGiftedText = "Presenteado\nPara Você" +CatalogPurchasedGiftText = "Já\nRecebido" +CatalogMailboxFull = "Sem Espaço" +CatalogNotAGift = "Não é um Presente" +CatalogNoFit = "Não\nServe" + +CatalogPurchasedMaxText = "Já\ncomprado o máx." + +CatalogVerifyRent = "Alugar %(item)s por %(price)s balinhas?" +CatalogVerifyGift = "Comprar %(item)s por %(price)s balinhas de presente para %(friend)s?" +CatalogVerifyPurchase = "Comprar o item %(item)s por %(price)s balinhas?" +CatalogOnlyOnePurchase = "Você só pode ter um destes itens de cada vez. Se comprar este aqui, ele substituirá os itens %(old)s.\n\nTem certeza de que deseja comprar o item %(item)s por %(price)s balinhas?" + +CatalogExitButtonText = "Desligar" +CatalogCurrentButtonText = "Para itens atuais" +CatalogPastButtonText = "Para itens antigos" + +TutorialHQOfficerName = "Haroldo do Quartel" + +# NPCToons.py +NPCToonNames = { + # These are for the tutorial. We do not actually use the zoneId here + # But the quest posters need to know his name + 999 : "Costureiro Toon", + 1000 : lToonHQ, + 20000 : "Tutorial Tom", + 20001 : Flippy, + + # + # Toontown Central + # + + # Toontown Central Playground + + # This Flippy DNA matches the tutorial Flippy + # He is in Toon Hall + 2001 : Flippy, + 2002 : "Banqueiro Beto", + 2003 : "Professor Paulo", + 2004 : "Cora, a Costureira", + 2005 : "Bibliotecário Bino", + 2006 : "Vendedor Alaor", + 2011 : "Vendedora Isadora", + 2007 : lHQOfficerM, + 2008 : lHQOfficerM, + 2009 : lHQOfficerF, + 2010 : lHQOfficerF, + # NPCFisherman + 2012 : "Vendedor da Loja de Animais", + # NPCPetClerks + 2013 : "Vendedor Pop", + 2014 : "Vendedora Elétrica", + 2015 : "Vendedor Molenga", + + # Silly Street + 2101 : "Dentista Daniel", + 2102 : "Delegada Délis", + 2103 : "Gatinho Funga-funga", + 2104 : lHQOfficerM, + 2105 : lHQOfficerM, + 2106 : lHQOfficerF, + 2107 : lHQOfficerF, + 2108 : "Canária Mina de carvão", + 2109 : "Gugu Bolha", + 2110 : "Otto D'or", + 2111 : "Diego Dançante", + 2112 : "Dr. Tom", + 2113 : "Rolo, o Incrível", + 2114 : "Rosabela", + 2115 : "Pati Papel", + 2116 : "Brutus Crespo", + 2117 : "Dona Putrefata", + 2118 : "Bob Bobo", + 2119 : "Renata Rá Rá", + 2120 : "Professor Pimpão", + 2121 : "Madame Risadinha", + 2122 : "Toni Macacada", + 2123 : "Latônia Lata", + 2124 : "Massinha Mode Lar", + 2125 : "Ralf Desocupado", + 2126 : "Professora Gargalhada", + 2127 : "Nico Níquel", + 2128 : "Duda Doidinho", + 2129 : "Franco Furtado", + 2130 : "Felícia Ding-dong", + 2131 : "Espanadora Penas", + 2132 : "Joe Tromundo", + 2133 : "Dr. Fórico", + 2134 : "Simone Silêncio", + 2135 : "Karla Rossel", + 2136 : "Saulo Risadinha", + 2137 : "Alegria Alegre", + 2138 : "João", + 2139 : "Baby Baba", + 2140 : "Fisherman Billy", + + # Loopy Lane + 2201 : "Gerente Gil", + 2202 : "Shirley Vocezomba", + 2203 : lHQOfficerM, + 2204 : lHQOfficerM, + 2205 : lHQOfficerF, + 2206 : lHQOfficerF, + 2207 : "Saulo Sabichão", + 2208 : "Lucas Grude", + 2209 : "Rico Risada", + 2210 : "Chazinha", + 2211 : "Cláudia Cuspinho", + 2212 : "Estêvão Estranho", + 2213 : "Luciana da Roda", + 2214 : "Mano da Mancha", + 2215 : "Bob Bujão", + 2216 : "Kênia Teviu", + 2217 : "João Tubarão", + 2218 : "Hilária Folha", + 2219 : "Chef Cabeça de Vento", + 2220 : "Carlos Cabeça de Ferro", + 2221 : "Flora Canudinho", + 2222 : "Fusível Mirim", + 2223 : "Gláucia Gargalhada", + 2224 : "Fábio Fumacinha", + 2225 : "Corcunda Pescador", + + # Punchline Place + 2301 : "Dr. Puxaperna", + 2302 : "Professor Balanço", + 2303 : "Enfermeira Enferma", + 2304 : lHQOfficerM, + 2305 : lHQOfficerM, + 2306 : lHQOfficerF, + 2307 : lHQOfficerF, + 2308 : "Nancy Veneno", + 2309 : "João Grandão", + 2311 : "Francisco da Graça", + 2312 : "Dra. Sensível", + 2313 : "Lucinda Pinta", + 2314 : "Lúcio Lançador", + 2315 : "Tatá Tasco", + 2316 : "Bárbara Bola", + 2318 : "Ronaldo Engraçaldo", + 2319 : "Tiraldo", + 2320 : "Alfredo Nham", + 2321 : "Fisherman Punchy", + + # + # Donald's Dock + # + + # Donald's Dock Playground + 1001 : "Vendedor Willy", + 1002 : "Vendedor Billy", + 1003 : lHQOfficerM, + 1004 : lHQOfficerF, + 1005 : lHQOfficerM, + 1006 : lHQOfficerF, + 1007 : "Betão Calçalongas", + # NPCFisherman + 1008 : "Vendedor da Loja de Animais", + # NPCPetClerks + 1009 : "Vendedor Durão", + 1010 : "Vendedora Ron-ron", + 1011 : "Vendedora Blup", + + # Barnacle Blvd. + 1101 : "Levi Legal", + 1102 : "Capitão Carlão", + 1103 : "Pedro Peixe", + 1104 : "Doutor Alá", + 1105 : "Almirante Gancho", + 1106 : "Dona Goma", + 1107 : "Carlo Caiado", + 1108 : lHQOfficerM, + 1109 : lHQOfficerF, + 1110 : lHQOfficerM, + 1111 : lHQOfficerF, + 1112 : "Gláucio Glubglub", + 1113 : "Adele Adernada", + 1114 : "Carlos Camarada", + 1115 : "Lúcia Lula", + 1116 : "Carla Craca", + 1117 : "Capitão Blargh", + 1118 : "Marinho Crespo", + 1121 : "Linda Terra Firme", + 1122 : "Salgado Pescado", + 1123 : "Enguia Elétrica", + 1124 : "João Farpa do Cais", + 1125 : "Arlene Além-mar", + 1126 : "Zé Silva Pescador", + + # Seaweed Street + 1201 : "Craca Bárbara", + 1202 : "Mário", + 1203 : "Salgado", + 1204 : "Marco Quebra-mar", + 1205 : lHQOfficerM, + 1206 : lHQOfficerF, + 1207 : lHQOfficerM, + 1208 : lHQOfficerF, + 1209 : "Professora Pranchinha", + 1210 : "Aiki Sopa", + 1211 : "Malo Mala", + 1212 : "Tomás Língua de Trapo", + 1213 : "Bob Botinho", + 1214 : "Kátia Furacão", + 1215 : "Paula Profundeza", + 1216 : "Otto Ostra", + 1217 : "Ciça Caniço", + 1218 : "Toni Pacífico", + 1219 : "Carlos Encalhado", + 1220 : "Carla Canal", + 1221 : "Alan Abrolhos", + 1222 : "Bob Abordo", + 1223 : "Lula Lulu", + 1224 : "Emília Enguia", + 1225 : "Estêvão Estivador", + 1226 : "Pedro Pé na Tábua", + 1227 : "Coral do Recife", + 1228 : "Fisherman Reed", + + # Lighthouse Lane + 1301 : "Alice", + 1302 : "Moby", + 1303 : "Mário", + 1304 : "Martina", + 1305 : lHQOfficerM, + 1306 : lHQOfficerF, + 1307 : lHQOfficerM, + 1308 : lHQOfficerF, + 1309 : "Esponja do Mar", + 1310 : "Fernando Ferramenta", + 1311 : "Paulinha Ponta Cabeça", + 1312 : "Hélio Hélice", + 1313 : "Wilson Nó", + 1314 : "Fernando Enferrujado", + 1315 : "Doutora Correnteza", + 1316 : "Beth Rodopio", + 1317 : "Paula Poste", + 1318 : "Teófilo Bote", + 1319 : "Estácio Estaleiro", + 1320 : "Caio Calmaria", + 1321 : "Camila Cais", + 1322 : "Rachel Recheio", + 1323 : "Fred Fedido", + 1324 : "Pérola Profunda", + 1325 : "Sérgio Vira-latas", + 1326 : "Felícia Batatinha", + 1327 : "Cíntia Tábua", + 1328 : "Lucas Linguado", + 1329 : "Conchita Alga Marina", + 1330 : "Porta Dor", + 1331 : "Rudy Ridíquilhas", + 1332 : "Polar Pescador", + + # + # The Brrrgh + # + + # The Brrrgh Playground + 3001 : "Frida Freezer", + 3002 : lHQOfficerM, + 3003 : lHQOfficerF, + 3004 : lHQOfficerM, + 3005 : lHQOfficerM, + 3006 : "Vendedor Breno", + 3007 : "Vendedora Brenda", + 3008 : "Paco Pacote", + # NPCFisherman + 3009 : "Vendedor da Loja de Animais", + # NPCPetClerks + 3010 : "Vendedor Saltitante", + 3011 : "Vendedora Glub", + 3012 : "Vendedor Kiko", + + # Walrus Way + 3101 : "Seu Leão", + 3102 : "Tia Freezer", + 3103 : "Chicó", + 3104 : "Gorrete", + 3105 : "Fred Cavanhaque", + 3106 : "Pio Arrepio", + 3107 : "Patty Passaporte", + 3108 : "Tobi Tobogã", + 3109 : "Kate", + 3110 : "Franguinho", + 3111 : "Cão de Bico", + 3112 : "Pequeno Grande Ancião", + 3113 : "Américo Histérico", + 3114 : "Rico Arriscado", + 3115 : lHQOfficerM, + 3116 : lHQOfficerF, + 3117 : lHQOfficerM, + 3118 : lHQOfficerM, + 3119 : "Carlos K. B. Loempé", + 3120 : "Kiko Quiprocó", + 3121 : "João Eletrochoque", + 3122 : "Luci Lugar", + 3123 : "Francis Quebra Gelo", + 3124 : "Estileto Iceberg", + 3125 : "Coronel Mastiga", + 3126 : "João Jornada", + 3127 : "Aérea Inflada", + 3128 : "Jorge Palitinho", + 3129 : "Fátima Fôrma", + 3130 : "Sandy", + 3131 : "Patrício Preguiça", + 3132 : "Maria Cinza", + 3133 : "Dr. Congelado", + 3134 : "Vítor Vestíbulo", + 3135 : "Ênia Sopada", + 3136 : "Susana Nimada", + 3137 : "Sr. Freezer", + 3138 : "Chefe Sopa Rala", + 3139 : "Vovó Ceroulas", + 3140 : "Luci Pescadora", + + # Sleet Street + 3201 : "Tia Ártica", + 3202 : "Tremendão", + 3203 : "Walter", + 3204 : "Dra. Vai C. V.", + 3205 : "Cabeção Kika", + 3206 : "Vidália VaVum", + 3207 : "Dr. Ban Guela", + 3208 : "Felipe Nervosinho", + 3209 : "Marcos Cem Graça", + 3210 : "Álvaro Asno", + 3211 : "Hilária Freezer", + 3212 : "Rogério Gélido", + 3213 : lHQOfficerM, + 3214 : lHQOfficerF, + 3215 : lHQOfficerM, + 3216 : lHQOfficerM, + 3217 : "Consuelo Suada", + 3218 : "Lu Lazul", + 3219 : "Bob BikeDupla", + 3220 : "Sr. Espirro", + 3221 : "Neli Neve", + 3222 : "Vera Vento Cortante", + 3223 : "Chapa", + 3224 : "Rita Raspadinha", + 3225 : "Foca Fofoca", + 3226 : "Papai Nó El", + 3227 : "Raiomundo de Sol", + 3228 : "Frida Calafrio", + 3229 : "Hermínia Cinta", + 3230 : "Pedro Pedreira", + 3231 : "G. Lopicado", + 3232 : "Pescador Alberto", + + # Polar Place + 3301 : "Remendo Tecidos", + 3302 : "Pedro Urso", + 3303 : "Dr. Olhadelas", + 3304 : "Abrão o Abominável", + 3305 : "Mick Eimei", + 3306 : "Paula Úrsula", + # NPC Fisherman + 3307 : "Pescadora Frederica", + 3308 : "Roberto Injustus", + 3309 : "Botinha", + 3310 : "Professor Floco", + 3311 : "Connie Feras", + 3312 : "Haroldo Marcha", + 3313 : lHQOfficerM, + 3314 : lHQOfficerF, + 3315 : lHQOfficerM, + 3316 : lHQOfficerF, + 3317 : "Bete Beijoqueira", + 3318 : "João Caxemira", + 3319 : "Reinaldo Retifica", + 3320 : "Ester Espuma", + 3321 : "Paulo Picareta", + 3322 : "Luis Fluis", + 3323 : "Aurora Borealis", + 3324 : "Otto Dentetorto", + 3325 : "Dercy Balançalves", + 3326 : "Blanche", + 3327 : "Cacá Sado", + 3328 : "Sônia Sombria", + 3329 : "Edu Pisada", + + # + # Minnie's Melody Land + # + + # Minnie's Melody Land Playground + 4001 : "Mel Odia", + 4002 : lHQOfficerM, + 4003 : lHQOfficerF, + 4004 : lHQOfficerF, + 4005 : lHQOfficerF, + 4006 : "Vendedora Do-ré-mi", + 4007 : "Vendedor Fá-sol-lá-si", + 4008 : "Costureira Harmonia", + # NPCFisherman + 4009 : "Vendedor da Loja de Animais", + # NPCPetClerks + 4010 : "Vendedor Caco", + 4011 : "Vendedor Nilton", + 4012 : "Vendedora Flor do Nordeste", + + # Alto Ave. + 4101 : "Tom", + 4102 : "Fifi", + 4103 : "Dr. Triturador", + 4104 : lHQOfficerM, + 4105 : lHQOfficerF, + 4106 : lHQOfficerF, + 4107 : lHQOfficerF, + 4108 : "Clave", + 4109 : "Carlos", + 4110 : "Métrica Anã", + 4111 : "Tom Tum", + 4112 : "Fá", + 4113 : "Madame Boa Maneira", + 4114 : "Bino Desafino", + 4115 : "Bárbara de Sevilha", + 4116 : "Flávio Flautim", + 4117 : "Banda Lyn", + 4118 : "Faxineiro Abel", + 4119 : "Moz Arte", + 4120 : "Violante Almofada", + 4121 : "Gegê Menor", + 4122 : "Mentolada do Baixo", + 4123 : "André Raio", + 4124 : "Renato Refrão", + 4125 : "Ondina Musical", + 4126 : "Melô Canto", + 4127 : "Felícia Podos", + 4128 : "Luciano Furo", + 4129 : "Carla Cadência", + 4130 : "Miguel Metal", + 4131 : "Abraão Armário", + 4132 : "Marta Marrom", + 4133 : "Paulo Popeline", + 4134 : "Davi Disco", + 4135 : "Carlo Canoro", + 4136 : "Patrícia Pausa", + 4137 : "Toni Surdo", + 4138 : "Agudo Clave", + 4139 : "Harmonia Decrescente", + 4140 : "Daniel Desajeitado", + 4141 : "Pet Pescador", + + # Baritone Blvd. + 4201 : "Tina", + 4202 : "Barry", + 4203 : "Alê Nhador", + 4204 : lHQOfficerM, + 4205 : lHQOfficerF, + 4206 : lHQOfficerF, + 4207 : lHQOfficerF, + 4208 : "Heidi", + 4209 : "Brega Galopante", + 4211 : "Carlo Concerto", + 4212 : "Detetive Marcha Fúnebre", + 4213 : "Franca Foley", + 4214 : "Paula Meia-ponta", + 4215 : "Mário Marcha a ré", + 4216 : "Bob Buzina", + 4217 : "Toni Bonitão", + 4218 : "Sônia Soprano", + 4219 : "Bruno Barítono", + 4220 : "Dênis Dedus", + 4221 : "Marcos Madrigal", + 4222 : "João da Silva", + 4223 : "Pâmela Ponto", + 4224 : "Jim das Selvas", + 4225 : "Vânia Vaia", + 4226 : "Samantha Garganta", + 4227 : "Cláudia Calada", + 4228 : "Augusto de Sopro", + 4229 : "Júnia Bombom", + 4230 : "Marcelo Martelo", + 4231 : "Stefanie Acordes", + 4232 : "Helder Hino", + 4233 : "Enzo Enjoado", + 4234 : "Mestre Guitarra", + 4235 : "Lauro Pescador", + + # Tenor Terrace + 4301 : "Cavaca", + 4302 : "Ana", + 4303 : "Léo", + 4304 : lHQOfficerM, + 4305 : lHQOfficerF, + 4306 : lHQOfficerF, + 4307 : lHQOfficerF, + 4308 : "Tábata", + 4309 : "Punk Ecas", + 4310 : "Mezza Soprana", + 4311 : "Chica Shake", + 4312 : "Paulo Palheta", + 4313 : "Mário Mudo", + 4314 : "Danuza Uza", + 4315 : "Maritza Tique Ataque", + 4316 : "Toni Tango", + 4317 : "Dedo Curto", + 4318 : "Bob Marlin", + 4319 : "Cátia Zuza", + 4320 : "Roberta P. Rock", + 4321 : "Edinho Verde", + 4322 : "Antoniota Musical", + 4323 : "Bárbara Balado", + 4324 : "Elen", + 4325 : "Ralf Rádio", + 4326 : "Kíria Irrita", + 4327 : "Armínia Arranca Pele", + 4328 : "Wagner", + 4329 : "Teles Prompter", + 4330 : "Quarentino", + 4331 : "Mello Costello", + 4332 : "Ziggy", + 4333 : "Ubaldo", + 4334 : "Estêvão Expresso", + 4335 : "Sílvia Pescadora", + + # + # Daisy Gardens + # + + # Daisy Gardens Playground + 5001 : lHQOfficerM, + 5002 : lHQOfficerM, + 5003 : lHQOfficerF, + 5004 : lHQOfficerF, + 5005 : "Vendedora Moranguinho", + 5006 : "Vendedor Herbal", + 5007 : "Florinda Flores", + # NPCFisherman + 5008 : "Vendedor da Loja de Animais", + # NPCPetClerks + 5009 : "Vendedora Buba Tânica", + 5010 : "Vendedor Tony Grana", + 5011 : "Vendedor Duda Madeira", + + # Elm Street + 5101 : "Sérgio", + 5102 : "Susana", + 5103 : "Florêncio", + 5104 : "Borba Oleta", + 5105 : "João", + 5106 : "Barbeiro Tosque Ador", + 5107 : "Carteiro Felipe", + 5108 : "Funcionária Janete", + 5109 : lHQOfficerM, + 5110 : lHQOfficerM, + 5111 : lHQOfficerF, + 5112 : lHQOfficerF, + 5113 : "Dra. Ália e Ólea", + 5114 : "Al Fácio Murcho", + 5115 : "Lua de Melão", + 5116 : "Vítor Vegetal", + 5117 : "Pétala", + 5118 : "Pipo K.", + 5119 : "João Medalhão", + 5120 : "Toupeira", + 5121 : "Emília Ervilha", + 5122 : "J. Jardim", + 5123 : "Diana Uva", + 5124 : "Olavo Orvalho", + 5125 : "Chu Chuá", + 5126 : "Madame Calado", + 5127 : "Poliana Pólen", + 5128 : "Suzana Seiva", + 5129 : "Salgueira Pescadora", + + # Maple Street + 5201 : "Joãozinho", + 5202 : "Cíntia", + 5203 : "Lisa", + 5204 : "Ubaldo", + 5205 : "Maurício Leão", + 5206 : "Branco Vinho", + 5207 : "Sofia Seiva", + 5208 : "Samanta Pá", + 5209 : lHQOfficerM, + 5210 : lHQOfficerM, + 5211 : lHQOfficerF, + 5212 : lHQOfficerF, + 5213 : "Nabo Bobo", + 5214 : "Empolada Alérgica", + 5215 : "Clara Caules", + 5216 : "Fernando Fedido", + 5217 : "Vítor do Dedo Verde", + 5218 : "Francisco Framboesa", + 5219 : "Bi Ceps", + 5220 : "Luci Calçola", + 5221 : "Rosinha Flamingo", + 5222 : "Sandra Samambaia", + 5223 : "Paulo Ensopado", + 5224 : "Tio Camponês", + 5225 : "Pâmela Pântana", + 5226 : "Mauro Musgo", + 5227 : "Begônia Malte", + 5228 : "Drago Di Lama", + 5229 : "Lili Pescadora", + + # Oak street + 5301 : lHQOfficerM, + 5302 : lHQOfficerM, + 5303 : lHQOfficerM, + 5304 : lHQOfficerM, + 5305 : "Cristal", + 5306 : "C. Postal", + 5307 : "Mo Fus", + 5308 : "Nely Nervo", + 5309 : "Rô Mann", + 5310 : "Timóteo", + 5311 : "Juíza Gala", + 5312 : "Eugênio", + 5313 : "Treinador Abobrinha", + 5314 : "Tia Miga", + 5315 : "Tio Lama", + 5316 : "Tio Batatinha", + 5317 : "Detetive Linda", + 5318 : "Caesar", + 5319 : "Rose", + 5320 : "Márcia", + 5321 : "Professora Uva", + 5322 : "Rose Pescadora", + + # + # Goofy's Speedway + # + #default area + #kart clerk + 8001 : "Grandep Rêmio", + 8002 : "Keruk Orrê", + 8003 : "Precisuv Encer", + 8004 : "En Chaela", + + # + # Dreamland + # + + # Dreamland Playground + 9001 : "Susana Pestana", + 9002 : "Dor Minhoco", + 9003 : "Sono Lento", + 9004 : lHQOfficerF, + 9005 : lHQOfficerF, + 9006 : lHQOfficerM, + 9007 : lHQOfficerM, + 9008 : "Vendedora Resona", + 9009 : "Vendedor Kisono", + 9010 : "Ultraje Velho", + # NPCFisherman + 9011 : "Vendedor da Loja de Animais", + # NPCPetClerks + 9012 : "Vendedora Sara Soneca", + 9013 : "Vendedora Gata na Lata", + 9014 : "Vendedor Cara Mujo", + + # Lullaby Lane + 9101 : "Marcelo", + 9102 : "Mama", + 9103 : "Py Jama", + 9104 : "Dulce Lombra", + 9105 : "Professor Bocejo", + 9106 : "Máximo", + 9107 : "Aurora Ninho", + 9108 : "Pedro Pestana", + 9109 : "Dafne Sonolinda", + 9110 : "Gatária Soneca", + 9111 : "Elle Étrica", + 9112 : "Denis Nar", + 9113 : "Tique Eustáquio", + 9114 : "Máki Agem", + 9115 : "Nenê Crespo", + 9116 : "Dança com Carneirinhos", + 9117 : "Aurora Extra", + 9118 : "Celeste Estrelada", + 9119 : "Pedro", + 9120 : "Lúcia Lenta", + 9121 : "Serena Lençol Curto", + 9122 : "Paulo Pregado", + 9123 : "Ursolino de P. Lúcia", + 9124 : "Nana de Nina", + 9125 : "Dr. Turvo", + 9126 : "Jatha Cordada", + 9127 : "Tati U. Nidos", + 9128 : "Pedro Fuso", + 9129 : "Cátia Colcha", + 9130 : "Nico Penico", + 9131 : "Célia Sesta", + 9132 : lHQOfficerF, + 9133 : lHQOfficerF, + 9134 : lHQOfficerF, + 9135 : lHQOfficerF, + 9136 : "Tainha Pescador", + + # Pajama Place + 9201 : "Bernardo", + 9202 : "Carneiro", + 9203 : "Zezé", + 9204 : "Clara da Lua", + 9205 : "Bob Bocão", + 9206 : "Petra Pétala", + 9207 : "Denise Dreno", + 9208 : "Solano Sonolento", + 9209 : "Dr. Sedoso", + 9210 : "Mestre Mário", + 9211 : "Aurora", + 9212 : "Raio de Lua", + 9213 : "Gustavo Galo", + 9214 : "Dr. Soneca", + 9215 : "Dedé Descanso", + 9216 : "Cuca", + 9217 : "Linda Legal", + 9218 : "Matilda Madruga", + 9219 : "Condessa", + 9220 : "Ney Nervoso", + 9221 : "Zéfiro", + 9222 : "Vaqueiro George", + 9223 : "Vado Levado", + 9224 : "Cuca P. Gol", + 9225 : "Henriqueta Inquieta", + 9226 : "Guilherme Sonoleve", + 9227 : "Carlos Cabeceira", + 9228 : "Samuel Suspiro", + 9229 : "Rosa Sonada", + 9230 : "Lelê", + 9231 : "Régis Rede", + 9232 : "Lua-de-Mel", + 9233 : lHQOfficerM, + 9234 : lHQOfficerM, + 9235 : lHQOfficerM, + 9236 : lHQOfficerM, + 9237 : "Jung Pescador", + + # Tutorial IDs start at 20000, and are not part of this table. + # Don't add any Toon id's at 20000 or above, for this reason! + # Look in TutorialBuildingAI.py for more details. + + } + +# These building titles are output from the DNA files +# Run ppython $TOONTOWN/src/dna/DNAPrintTitles.py to generate this list +# DO NOT EDIT THE ENTRIES HERE -- EDIT THE ORIGINAL DNA FILE +zone2TitleDict = { + # titles for: phase_4/dna/toontown_central_sz.dna + 2513 : ("Mickey PrefeiToona", ""), + 2514 : ("Banco de Toontown", ""), + 2516 : ("Escola de Toontown", ""), + 2518 : ("Biblioteca de Toontown", ""), + 2519 : (lGagShop, ""), + 2520 : (lToonHQ, ""), + 2521 : (lClothingShop, ""), + 2522 : (lPetShop, ""), + # titles for: phase_5/dna/toontown_central_2100.dna + 2601 : ("Restaurações Dentárias Todo Sorrisos", ""), + 2602 : ("", ""), + 2603 : ("Mineradores Espirituosos", ""), + 2604 : ("Lavanderia Lavou Tá Novo", ""), + 2605 : ("Fábrica de Sinalização de Toontown", ""), + 2606 : ("", ""), + 2607 : ("Feijões Saltadores", ""), + 2610 : ("Dr. Tom Besteira", ""), + 2611 : ("", ""), + 2616 : ("Loja de Disfarces Bigode Bizarro", ""), + 2617 : ("Feitos Idiotas", ""), + 2618 : ("A Encarnação Deve Continuar", ""), + 2621 : ("Aviões de Papel", ""), + 2624 : ("Brutamontes Felizes", ""), + 2625 : ("Casa da Torta Azeda", ""), + 2626 : ("Restauração de Piadas do Bob", ""), + 2629 : ("A Casa da Risada", ""), + 2632 : ("Curso de Palhaços", ""), + 2633 : ("Casa de Chá Chapinha", ""), + 2638 : ("Teatro de Toontown", ""), + 2639 : ("Truques e Macaquices", ""), + 2643 : ("Conservas Conservadas", ""), + 2644 : ("Pregadinhas de Peça", ""), + 2649 : ("Loja de Diversões e Jogos", ""), + 2652 : ("", ""), + 2653 : ("", ""), + 2654 : ("Curso de Risada", ""), + 2655 : ("Financeira Dinheiro Feliz", ""), + 2656 : ("Carros de Palhaço Usados", ""), + 2657 : ("Pegadinhas do Franco", ""), + 2659 : ("Campainhas Ding-dong para o Mundo", ""), + 2660 : ("Máquinas de Cosquinhas", ""), + 2661 : ("Doces Joe", ""), + 2662 : ("Dr. E. U. Fórico", ""), + 2663 : ("Teatro de Toontown", ""), + 2664 : ("Mímicas Divertidas", ""), + 2665 : ("Agência de Viagens K. Rossel", ""), + 2666 : ("Posto de Gás Hilariante", ""), + 2667 : ("A Folha da Alegria", ""), + 2669 : ("Balões do João", ""), + 2670 : ("Sopa no Garfo", ""), + 2671 : (lToonHQ, ""), + # titles for: phase_5/dna/toontown_central_2200.dna + 2701 : ("", ""), + 2704 : ("Teatro de Toontown", ""), + 2705 : ("Instrumentos Barulhentos do Sabichão", ""), + 2708 : ("Cola Azul", ""), + 2711 : ("Correio de Toontown", ""), + 2712 : ("Café do Risada", ""), + 2713 : ("Café da Madrugargalhada", ""), + 2714 : ("Teatro de Toontown", ""), + 2716 : ("Sopas e Surtos", ""), + 2717 : ("Latas engarrafadas", ""), + 2720 : ("Oficina do Chilique", ""), + 2725 : ("", ""), + 2727 : ("Garrafas e Conservas do Gasoso", ""), + 2728 : ("Sorvete Sumiço", ""), + 2729 : ("Peixinhos Dourados Ki-late", ""), + 2730 : ("Notícias Divertidas", ""), + 2731 : ("", ""), + 2732 : ("Espaguete Maluquete", ""), + 2733 : ("Pipas de Ferro", ""), + 2734 : ("Copos de Leite Chupa-chupa", ""), + 2735 : ("Casa do Cabum", ""), + 2739 : ("Restauração de Gargalhadas", ""), + 2740 : ("Rojões Usados", ""), + 2741 : ("", ""), + 2742 : (lToonHQ, ""), + 2743 : ("Lavagem a Seco Beca", ""), + 2744 : ("", ""), + 2747 : ("Tinta Visível", ""), + 2748 : ("Zombarias para Gargalhadas", ""), + # titles for: phase_5/dna/toontown_central_2300.dna + 2801 : ("Estofados Iupii", ""), + 2802 : ("Bolas de Ferro Infláveis", ""), + 2803 : ("Teatro de Toontown", ""), + 2804 : ("Dr. Puxaperna, Ortopedista", ""), + 2805 : ("", ""), + 2809 : ("Academia Graça da Piada", ""), + 2814 : ("Teatro de Toontown", ""), + 2818 : ("A Torta Voadora", ""), + 2821 : ("", ""), + 2822 : ("Sanduíches de Frango de Borracha", ""), + 2823 : ("Sorvetes e Sundaes Divertidos", ""), + 2824 : ("Teatro de Toontown", ""), + 2829 : ("Truques e Trocadilhos", ""), + 2830 : ("Tiradas Rápidas", ""), + 2831 : ("Casa do Sorriso Amarelo do Professor Balanço", ""), + 2832 : (lToonHQ, ""), + 2833 : ("", ""), + 2834 : ("Sala de Emergência Osso Bom", ""), + 2836 : ("", ""), + 2837 : ("Centro de Estudos Rá Rá Rá", ""), + 2839 : ("Grude Massas", ""), + 2841 : ("", ""), + # titles for: phase_6/dna/donalds_dock_sz.dna + 1506 : (lGagShop, ""), + 1507 : (lToonHQ, ""), + 1508 : (lClothingShop, ""), + 1510 : (lPetShop, ""), + # titles for: phase_6/dna/donalds_dock_1100.dna + 1602 : ("Salva-vidas Usados", ""), + 1604 : ("Lavagem a Seco Roupa de Mergulho", ""), + 1606 : ("Conserto de Relógios do Gancho", ""), + 1608 : ("Bugigangas a Vela", ""), + 1609 : ("Iscas e Petiscos", ""), + 1612 : ("Banco Moedinha no Convés", ""), + 1613 : ("Lula Ki Pro Quo, Advogados", ""), + 1614 : ("Butique Unha Afiada", ""), + 1615 : ("E aí Galera!", ""), + 1616 : ("Salão de Beleza Barba Azul", ""), + 1617 : ("Ótica Olha Lá", ""), + 1619 : ("Arboristas Desembarcar!", ""), + 1620 : ("Da Proa à Popa", ""), + 1621 : ("Academia Castelo de Popa", ""), + 1622 : ("Artigos Elétricos Isca Interruptora", ""), + 1624 : ("Reparos de Pescadas na Hora", ""), + 1626 : ("Roupas de Gala Salmão Encantado", ""), + 1627 : ("Atacado de Bússolas do Levi Legal", ""), + 1628 : ("Pianos Atum", ""), + 1629 : (lToonHQ, ""), + # titles for: phase_6/dna/donalds_dock_1200.dna + 1701 : ("Creche Peixinho Feliz", ""), + 1703 : ("Restaurante China Prancha", ""), + 1705 : ("Velas à Venda", ""), + 1706 : ("Pasta de Amendoim e Água-viva", ""), + 1707 : ("Presentes Golfinho Fofinho", ""), + 1709 : ("Veleiros e Gelatinas", ""), + 1710 : ("Liquidação das Cracas", ""), + 1711 : ("Restaurante Fundo do Mar", ""), + 1712 : ("Academia da Geração Saúde", ""), + 1713 : ("Mercado Carta Marinha do Mário", ""), + 1714 : ("Hotel do Otto", ""), + 1716 : ("Roupas de Banho Sereias", ""), + 1717 : ("Curso de Navegação Águas do Pacífico", ""), + 1718 : ("Serviços de Táxi Banco de Areia", ""), + 1719 : ("Empresas Correntes do Sul", ""), + 1720 : ("A Loja do Molinete", ""), + 1721 : ("Armarinho Marinho", ""), + 1723 : ("Alga Marinha do Lula", ""), + 1724 : ("A Enguia Moderna", ""), + 1725 : ("Centro de Caranguejos Pré-fabricados do Salgado", ""), + 1726 : ("Cerveja Preta Flutuante", ""), + 1727 : ("Rema aqui, Rema lá", ""), + 1728 : ("Caranguejos-ferradura Boa Sorte", ""), + 1729 : (lToonHQ, ""), + # titles for: phase_6/dna/donalds_dock_1300.dna + 1802 : ("Nada como Náutica", ""), + 1804 : ("Ginásio Mexilhão da Praia", ""), + 1805 : ("Caixa de Ferramentas Lanches", ""), + 1806 : ("Loja de Chapéus Emborcado", ""), + 1807 : ("Loja do Hélice", ""), + 1808 : ("Nós Samãe", ""), + 1809 : ("Balde Enferrujado", ""), + 1810 : ("Administração de Âncoras", ""), + 1811 : ("Canoa para Lá, Canoa para Cá", ""), + 1813 : ("Pressão do Pier Consultoria", ""), + 1814 : ("Parada do Ó", ""), + 1815 : ("Qual é, galerinha?", ""), + 1818 : ("Café dos Sete Mares", ""), + 1819 : ("Restaurante Cais", ""), + 1820 : ("Loja de Pegadinhas Linha e Anzol", ""), + 1821 : ("Conservas Rei Netuno", ""), + 1823 : ("Assados Ostra", ""), + 1824 : ("Remo Cachorrinho", ""), + 1825 : ("Mercado de Peixes Cavala Trotante!", ""), + 1826 : ("Armários Embutidos do Mário Metido", ""), + 1828 : ("Mansão da Alice Cascalhão", ""), + 1829 : ("Loja de Esculturas Piscicultura", ""), + 1830 : ("Linguados e Perdidos", ""), + 1831 : ("Alga Mais em sua Casa", ""), + 1832 : ("Hipermercado Mastro do Moby", ""), + 1833 : ("Alfaiataria sob Medida Seu Mastro", ""), + 1834 : ("Ridíquilhas!", ""), + 1835 : (lToonHQ, ""), + # titles for: phase_6/dna/minnies_melody_land_sz.dna + 4503 : (lGagShop, ""), + 4504 : (lToonHQ, ""), + 4506 : (lClothingShop, ""), + 4508 : (lPetShop, ""), + # titles for: phase_6/dna/minnies_melody_land_4100.dna + 4603 : ("Baterias do Tomtom", ""), + 4604 : ("A Quatro Mãos", ""), + 4605 : ("Violinos da Fifi", ""), + 4606 : ("Casa da Castanhola", ""), + 4607 : ("Trajes de Gala Toon", ""), + 4609 : ("Teclas de Piano Dó-ré-mi", ""), + 4610 : ("O Bom Refrão", ""), + 4611 : ("Faqueiros Diapasão", ""), + 4612 : ("Clínica Dentária Dr. Triturador", ""), + 4614 : ("Barbearia Musical", ""), + 4615 : ("Pizza do Flautim", ""), + 4617 : ("Bandolins Animados", ""), + 4618 : ("Banheiros Públicos", ""), + 4619 : ("Mar Cação", ""), + 4622 : ("Travesseiros Descanso de Queixo", ""), + 4623 : ("Afiação Bemol", ""), + 4625 : ("Pasta de Dente Tuba", ""), + 4626 : ("Notas Musicais", ""), + 4628 : ("Seguradora Acidental", ""), + 4629 : ("Pratos de Papel Refrão", ""), + 4630 : ("A Música é o nosso Forte", ""), + 4631 : ("Auxílio Canto Neiras", ""), + 4632 : ("Loja do Rock", ""), + 4635 : ("Notícias do Tenor", ""), + 4637 : ("A Boa Escala", ""), + 4638 : ("Loja do Heavy Metal", ""), + 4639 : ("Antiguidades Oitenta", ""), + 4641 : ("Jornal dos Blues", ""), + 4642 : ("Lavagem a Seco Puro Jazz", ""), + 4645 : ("Clube 88", ""), + 4646 : ("", ""), + 4648 : ("Mudanças Carregando Toons", ""), + 4649 : ("", ""), + 4652 : ("Loja de Conveniência Ponto Final", ""), + 4653 : ("", ""), + 4654 : ("Telhados Volume Perfeito", ""), + 4655 : ("Escola de Culinária do Terrível Chef Agudo", ""), + 4656 : ("", ""), + 4657 : ("Barbearia Quarteto", ""), + 4658 : ("Pianos Submersos", ""), + 4659 : (lToonHQ, ""), + # titles for: phase_6/dna/minnies_melody_land_4200.dna + 4701 : ("Escola de Dança Jumento Sentimento", ""), + 4702 : ("Timbre! Artigos para Lenhadores", ""), + 4703 : ("A Mala Madeus", ""), + 4704 : ("Concertos de Concertina da Tina", ""), + 4705 : ("Zarpou fora", ""), + 4707 : ("Estúdio de Efeitos Sonoros Doppler", ""), + 4709 : ("Artigos de Montanhismo Pliê", ""), + 4710 : ("Auto-escola Pouca Polca", ""), + 4712 : ("Borracharia Dó do Murcho", ""), + 4713 : ("Moda Fina Masculina Desafina", ""), + 4716 : ("Gaitas de Quatro Segmentos", ""), + 4717 : ("Seguradora de Automóveis Barateira Barítono", ""), + 4718 : ("Peças para Churrasco e Outros Artigos para Cozinha", ""), + 4719 : ("Casas-móveis Madrigal", ""), + 4720 : ("Dê um Nome a este Toon", ""), + 4722 : ("Substitutos Abertura", ""), + 4723 : ("Artigos para Parquinhos Infantis Ex-condesconde", ""), + 4724 : ("Moda Infantil Inci Dental", ""), + 4725 : ("O Barbeiro Barítono", ""), + 4727 : ("Bordados Corda Vocal", ""), + 4728 : ("Solo Vocal Não dá pra Ouvir", ""), + 4729 : ("Livraria Oboé", ""), + 4730 : ("Sebo de Letras de Músicas", ""), + 4731 : ("Tons dos Toons", ""), + 4732 : ("Companhia Teatral Prega Peça", ""), + 4733 : ("", ""), + 4734 : ("", ""), + 4735 : ("Acorde Não!", ""), + 4736 : ("Planejamento Matrimonial Casal Hino Esperado", ""), + 4737 : ("Lonas Harpa", ""), + 4738 : ("Presentes Cantata do Tatá", ""), + 4739 : (lToonHQ, ""), + # titles for: phase_6/dna/minnies_melody_land_4300.dna + 4801 : ("Ponto do Punk", ""), + 4803 : ("Serviços de Governança Que Mezza!", ""), + 4804 : ("Curso de Barman Shake Shake Shake", ""), + 4807 : ("Não Quebre o Braço", ""), + 4809 : ("Não Com Verso!", ""), + 4812 : ("", ""), + 4817 : ("Loja de Animais Trin Canário", ""), + 4819 : ("Cavaquinhos da Cavaca", ""), + 4820 : ("", ""), + 4821 : ("Cruzeiros da Ana", ""), + 4827 : ("Relógios Ritmo Cadente", ""), + 4828 : ("Sapatos Masculinos Rima", ""), + 4829 : ("Bolas de Canhão Vaga Ner", ""), + 4835 : ("Fundamentos Musicais para Felinos Felizes", ""), + 4836 : ("Regalias do Reggae", ""), + 4838 : ("Escola de Música K. Zuza", ""), + 4840 : ("Bebidas Musicais Pop Rock", ""), + 4841 : ("Bandoleiro Bandolins", ""), + 4842 : ("Corporação Sincopação", ""), + 4843 : ("", ""), + 4844 : ("Motocicletas Com Notação", ""), + 4845 : ("Elegias Elegantes da Elen", ""), + 4848 : ("Financeira Cordas de Dinheiro", ""), + 4849 : ("", ""), + 4850 : ("Hipoteca Cordas Emprestadas", ""), + 4852 : ("Arranca Peles Flauta Florida", ""), + 4853 : ("Pára-choques do Léo Guitarra", ""), + 4854 : ("Vídeos de Violinos Vocacionais Wagner", ""), + 4855 : ("Rede de Televisão Teleouvisa", ""), + 4856 : ("", ""), + 4862 : ("Quatrilhos Quintessenciais do Quarentino", ""), + 4867 : ("Celos Amarelos do Costello", ""), + 4868 : ("", ""), + 4870 : ("Zoológico de Ziriguidum do Ziggy", ""), + 4871 : ("Humbuckers Únicos do Ubaldo", ""), + 4872 : ("Braços sem Estresse do Estêvão Expresso", ""), + 4873 : (lToonHQ, ""), + # titles for: phase_8/dna/daisys_garden_sz.dna + 5501 : (lGagShop, ""), + 5502 : (lToonHQ, ""), + 5503 : (lClothingShop, ""), + 5505 : (lPetShop, ""), + # titles for: phase_8/dna/daisys_garden_5100.dna + 5601 : ("Exames de Vista Olho do Alho", ""), + 5602 : ("Gravatas do Sérgio Sufocado", ""), + 5603 : ("Verde que te Quero Verdura", ""), + 5604 : ("Loja de Noivas Mel e Lão", ""), + 5605 : ("Sobre Mesas e Cadeiras", ""), + 5606 : ("Pétalas", ""), + 5607 : ("Correios Adubo Expresso", ""), + 5608 : ("Toca da Pipoca", ""), + 5609 : ("Tesouro dos Dentes de Alho de Ouro", ""), + 5610 : ("Aulas de Boxe da Susana Olhos Negros", ""), + 5611 : ("Piadas do Toupeira", ""), + 5613 : ("Barbeiros Tosa Completa", ""), + 5615 : ("Ração para Pássaros do Florêncio", ""), + 5616 : ("Pousada Pouso da Coruja", ""), + 5617 : ("Borboletas do Borba Oleta", ""), + 5618 : ("Ervilhas e Milhas", ""), + 5619 : ("Pés-de-feijão do João", ""), + 5620 : ("Pousada Pá de Coisa", ""), + 5621 : ("Uvinhas da Ira", ""), + 5622 : ("Loja de Bicicletas Bem-me-quer", ""), + 5623 : ("Banheiras para Pássaros Bolhinhas Aladas", ""), + 5624 : ("Bico Calado", ""), + 5625 : ("Os Abelhudos", ""), + 5626 : ("Artesanato Pínus", ""), + 5627 : (lToonHQ, ""), + # titles for: phase_8/dna/daisys_garden_5200.dna + 5701 : ("Do Início ao Figo", ""), + 5702 : ("Ancinho do Joãozinho", ""), + 5703 : ("Fotos Cíntia", ""), + 5704 : ("Carros Usados Lisa Lima", ""), + 5705 : ("Móveis Urtigas", ""), + 5706 : ("Joalheiros 14 Ki Latas", ""), + 5707 : ("Fruta Musical", ""), + 5708 : ("Agência de Viagens Erva que Partiu", ""), + 5709 : ("Cortadores de Grama Ré U. Vassintética", ""), + 5710 : ("Academia Durão", ""), + 5711 : ("Roupas Íntimas Jardim de Inverno", ""), + 5712 : ("Estátuas Idiotas", ""), + 5713 : ("Mãos à Obra", ""), + 5714 : ("Água Mineral Chuva de Verão", ""), + 5715 : ("Notícias do Campo", ""), + 5716 : ("Hipotecas Folhas Caídas", ""), + 5717 : ("Seivas Florais", ""), + 5718 : ("Animais Exóticos Mauricinho Leão", ""), + 5719 : ("Investigadores Particulares Cara que Manchão!", ""), + 5720 : ("Moda Masculina Bran Covinho", ""), + 5721 : ("Restaurante Rota 66", ""), + 5725 : ("Cervejaria da Cevada", ""), + 5726 : ("Terra Adubada do Ubaldo", ""), + 5727 : ("Financeira Toupeira Encurralada", ""), + 5728 : (lToonHQ, ""), + # titles for: phase_8/dna/daisys_garden_5300.dna + 5802 : (lToonHQ, ""), + 5804 : ("Vazar ou não Vazar?", ""), + 5805 : ("Correio da Lesma", ""), + 5809 : ("Escola de Palhaços Fungos", ""), + 5810 : ("Mela o Melado", ""), + 5811 : ("Pousada Al Face a Face", ""), + 5815 : ("Rural", ""), + 5817 : ("Maçãs e Laranjas", ""), + 5819 : ("Jeans Vagem Verde", ""), + 5821 : ("Academia Amassado e Esticado", ""), + 5826 : ("Artigos para o Cultivo de Formigas", ""), + 5827 : ("Promoção de Aterrar", ""), + 5828 : ("Móveis Batatinha Quando Nasce", ""), + 5830 : ("Espalhado o Babado", ""), + 5833 : ("Restaurante Saladas", ""), + 5835 : ("Café Colonial Flores do Campo", ""), + 5836 : ("Tubulações e Águas de Márcia", ""), + 5837 : ("Curso de Enólogo", ""), + # titles for: phase_8/dna/donalds_dreamland_sz.dna + 9501 : ("Biblioteca da Canção de Ninar", ""), + 9503 : ("O Bar da Soneca", ""), + 9504 : (lGagShop, ""), + 9505 : (lToonHQ, ""), + 9506 : (lClothingShop, ""), + 9508 : (lPetShop, ""), + # titles for: phase_8/dna/donalds_dreamland_9100.dna + 9601 : ("Pousada A. Ninho", ""), + 9602 : ("Dois Dedos de Prosa com Morfeu pelo Preço de Um", ""), + 9604 : ("Sofá-cama Amarelo do Marcelo", ""), + 9605 : ("Travessa da Canção de Ninar, 323", ""), + 9607 : ("Pijamas Bahamas da Mama", ""), + 9608 : ("Erva-de-gato para Tirar um Cochilo", ""), + 9609 : ("Sono de Pedra por uma Bagatela", ""), + 9613 : ("Relojoeiros das Alturas", ""), + 9616 : ("Companhia Elétrica Luzes Apagadas", ""), + 9617 : ("Travessa da Canção de Ninar, 212", ""), + 9619 : ("Relaxe ao Máximo", ""), + 9620 : ("Serviços de Táxi Py Jama", ""), + 9622 : ("Relógios Sono Atrasado", ""), + 9625 : ("Salão de Beleza Enrolado Crespo", ""), + 9626 : ("Travessa da Canção de Ninar, 818", ""), + 9627 : ("A Tenda dos Sonhos", ""), + 9628 : ("Calendários Já Chega por Hoje", ""), + 9629 : ("Travessa da Canção de Ninar, 310", ""), + 9630 : ("Pedreira Sono de Pedra", ""), + 9631 : ("Conserto de Relógios Inatividade", ""), + 9633 : ("Sala de Projeção da Sonholândia", ""), + 9634 : ("Colchões Descanso da Mente", ""), + 9636 : ("Seguradora Insônia", ""), + 9639 : ("Casa de Hibernação", ""), + 9640 : ("Travessa da Canção de Ninar, 805", ""), + 9642 : ("Serraria Lombeira da Madeira", ""), + 9643 : ("Exames de Vista Olho Fechado", ""), + 9644 : ("Guerras de Travesseiro Noturnas", ""), + 9645 : ("Pousada Unidos Venceremos", ""), + 9647 : ("Loja de Ferragens Faça a sua Cama", ""), + 9649 : ("Ranking do Ronco", ""), + 9650 : ("Travessa da Canção de Ninar, 714", ""), + 9651 : ("Com Muito ou com Ronco", ""), + 9652 : (lToonHQ, ""), + # titles for: phase_8/dna/donalds_dreamland_9200.dna + 9703 : ("Agência Voe Dormindo", ""), + 9704 : ("Loja de Animais Coruja Noturna", ""), + 9705 : ("Mecânica Dormindo no Volante", ""), + 9706 : ("Dentista da Fada dos Sonhos", ""), + 9707 : ("Jardinagem do Anoitecer", ""), + 9708 : ("Floricultura Cama de Rosas", ""), + 9709 : ("Encanador dos Sonhos", ""), + 9710 : ("Oculista do Sono", ""), + 9711 : ("Cia. Telefônica Despertadora", ""), + 9712 : ("Você Não Precisa Contar Carneirinhos!", ""), + 9713 : ("Dorminhoco, Preguiça e Sonolento Advogados", ""), + 9714 : ("Loja de Equipamentos Barco dos Sonhos", ""), + 9715 : ("Banco Nacional Cobertor", ""), + 9716 : ("Festas do Pijama", ""), + 9717 : ("Padaria do Roncador", ""), + 9718 : ("Sanduíches Sonhando Acordado", ""), + 9719 : ("Empresa de Travesseiros do Jacaré", ""), + 9720 : ("Treinamento de Voz Fale Dormindo", ""), + 9721 : ("Tapetes Dormindo Feito Pedra", ""), + 9722 : ("Agência de Talentos Vai Sonhando", ""), + 9725 : ("Pijama de Gato", ""), + 9727 : ("Roncou, Perdeu", ""), + 9736 : ("Agência de Empregos dos Sonhos", ""), + 9737 : ("Escola de Dança Canção de Ninar", ""), + 9738 : ("Casa dos Zzzzzs", ""), + 9740 : ("Escola de Cercas Caindo na Cama", ""), + 9741 : ("Detetizadora Não Saia da Cama", ""), + 9744 : ("Creme dos Sonhos", ""), + 9752 : ("Cia. de Petróleo e Gás da Meia-Noite", ""), + 9753 : ("Sorvetes Lua de Mel", ""), + 9754 : ("Passeios a Cavalo da Insônia", ""), + 9755 : ("Locadora Criado-Mudo", ""), + 9756 : ("", ""), + 9759 : ("Estética Bela Adormecida", ""), + # titles for: phase_8/dna/the_burrrgh_sz.dna + 3507 : (lGagShop, ""), + 3508 : (lToonHQ, ""), + 3509 : (lClothingShop, ""), + 3511 : (lPetShop, ""), + # titles for: phase_8/dna/the_burrrgh_3100.dna + 3601 : ("Companhia Elétrica Esplendor do Norte", ""), + 3602 : ("Gorros do Pólo Norte", ""), + 3605 : ("", ""), + 3607 : ("Mago do Lago Congelado", ""), + 3608 : ("Existe um Lugar", ""), + 3610 : ("Hipermercado de Sapatos de Esquimó Quiprocó", ""), + 3611 : ("Rodinho do Leão Marinho", ""), + 3612 : ("Design de Iglus", ""), + 3613 : ("Cicle Geloso", ""), + 3614 : ("Indústria de Cereais Flocos de Neve", ""), + 3615 : ("Pastéis de Forno Lindo Alasca", ""), + 3617 : ("Passeios de Balão Vento Frio", ""), + 3618 : ("Consultoria de Gestão de Crises Grande Coisa!", ""), + 3620 : ("Clínica do Esqui", ""), + 3621 : ("Sorveteria Gelo Derretido", ""), + 3622 : ("", ""), + 3623 : ("Indústria de Pães Torradinha", ""), + 3624 : ("Sanduicheria Abaixo de Zero", ""), + 3625 : ("Aquecedores Tia Freezer", ""), + 3627 : ("Canil São Bernardo", ""), + 3629 : ("Restaurante Sopa de Ervilhas", ""), + 3630 : ("Agência de Viagens Com Gelo em Londres, Com Gelo na França", ""), + 3634 : ("Teleférico Boa Vista", ""), + 3635 : ("Lenha Usada", ""), + 3636 : ("Promoção de Arrepios", ""), + 3637 : ("Skates da Kate", ""), + 3638 : ("Tobogã da Lã", ""), + 3641 : ("Trenó do Chicó", ""), + 3642 : ("Ótica Olho do Furacão", ""), + 3643 : ("Salão Bola de Neve", ""), + 3644 : ("Cubos de Gelo Derretidos", ""), + 3647 : ("Loja de Smokings Pingüim Animado", ""), + 3648 : ("Sorvete Instantâneo", ""), + 3649 : ("Hambrrrgers", ""), + 3650 : ("Antiguidades Antárctica", ""), + 3651 : ("Salsichas Congeladas do Fred Barbicha", ""), + 3653 : ("Joalheria Cristal do Gelo", ""), + 3654 : (lToonHQ, ""), + # titles for: phase_8/dna/the_burrrgh_3200.dna + 3702 : ("Armazém do Inverno", ""), + 3703 : ("", ""), + 3705 : ("Cicle Pingo Congelado para Dois", ""), + 3706 : ("Cervejaria Treme-treme", ""), + 3707 : ("Neve Doce Neve", ""), + 3708 : ("Loja do Pluto", ""), + 3710 : ("Temperatura em Queda Refeições", ""), + 3711 : ("", ""), + 3712 : ("Vai por Gelo Abaixo", ""), + 3713 : ("Dentista Abaixo de Zero Tiritante", ""), + 3715 : ("Casa de Sopas Tia Ártica", ""), + 3716 : ("Estrada de Sal e Pimenta", ""), + 3717 : ("A Lasca Verbal", ""), + 3718 : ("Designer de Câmaras de Ar", ""), + 3719 : ("Cubo de Gelo no Palitinho", ""), + 3721 : ("Liquidação de Tobogãs Cabeção", ""), + 3722 : ("Loja de Esquis Coelhinho de Neve", ""), + 3723 : ("Bolas de Neve Tremendão", ""), + 3724 : ("Fatos e Fofocas", ""), + 3725 : ("O Nó do Trenó", ""), + 3726 : ("Cobertores com Energia Solar", ""), + 3728 : ("Tratores de Neve Anta Lesada", ""), + 3729 : ("", ""), + 3730 : ("Compra e Venda de Bonecos de Neve", ""), + 3731 : ("Lareiras Portáteis", ""), + 3732 : ("O Nariz Congelado", ""), + 3734 : ("Exames de Vista C. V. Gelo", ""), + 3735 : ("Capas de Gelo Polar", ""), + 3736 : ("Cubos de Gelo com Zelo", ""), + 3737 : ("Restaurante Montanha Abaixo", ""), + 3738 : ("Aquecimento - Aproveite Enquanto Tá Quente", ""), + 3739 : (lToonHQ, ""), + # titles for: phase_8/dna/the_burrrgh_3300.dna + 3801 : (lToonHQ, ""), + 3806 : ("Caminho Alpino", ""), + 3807 : ("Casacos de Marmota Usados", ""), + 3808 : ("Cabana, Doce Cabana", ""), + 3809 : ("Olhar Congelante", ""), + 3810 : ("Cabideiro Cabe de Tudo", ""), + 3811 : ("Seu Anjo de Neve", ""), + 3812 : ("Luvas Para Patinhas", ""), + 3813 : ("Pé de Sapatos de Neve", ""), + 3814 : ("Fonte do Refrigerante Borbulhante", ""), + 3815 : ("O Chalé do Chulé", ""), + 3816 : ("Enfeite Enfeitado", ""), + 3817 : ("Clube do Instigante Inverno", ""), + 3818 : ("Escavana", ""), + 3819 : ("Disk-Chaminé", ""), + 3820 : ("Branco Nevado", ""), + 3821 : ("Férias de Hibernar", ""), + 3823 : ("Fundação da Precipitação", ""), + 3824 : ("Labareda das Castanhas", ""), + 3825 : ("Chapéus Felinos", ""), + 3826 : ("Oh Minhas Galochas!", ""), + 3827 : ("Guirlanda Cantante", ""), + 3828 : ("Terra do Homem de Neve", ""), + 3829 : ("Zona Pinhada", ""), + 3830 : ("Espere e Resolveremos", ""), + } + +# DistributedCloset.py +ClosetTimeoutMessage = "Sinto muito, o tempo\n acabou." +ClosetNotOwnerMessage = "Este não é o seu armário, mas você pode experimentar as roupas." +ClosetPopupOK = lOK +ClosetPopupCancel = lCancel +ClosetDiscardButton = "Remover" +ClosetAreYouSureMessage = "Você excluiu algumas roupas. Deseja mesmo excluí-las?" +ClosetYes = lYes +ClosetNo = lNo +ClosetVerifyDelete = "Excluir mesmo %s?" +ClosetShirt = "esta camisa" +ClosetShorts = "este short" +ClosetSkirt = "esta saia" +ClosetDeleteShirt = "Excluir\ncamisa" +ClosetDeleteShorts = "Excluir\nshort" +ClosetDeleteSkirt = "Excluir\nsaia" + +# EstateLoader.py +EstateOwnerLeftMessage = "Sinto muito, o dono desta propriedade saiu. Você será enviado ao pátio em %s segundos" +EstatePopupOK = lOK +EstateTeleportFailed = "Não foi possível ir para casa. Tente novamente!" +EstateTeleportFailedNotFriends = "Sinto muito, %s fica na propriedade de um toon com o qual você não fez amizade." + +# DistributedTarget.py +EstateTargetGameStart = "O jogo do Alvo de Toonar começou!" +EstateTargetGameInst = "Quanto mais acertar no alvo vermelho, mais Toonar você vai receber." +EstateTargetGameEnd = "O jogo de Alvo de Toonar acabou..." + +# DistributedCannon.py +EstateCannonGameEnd = "O aluguel do Jogo do Canhão acabou." + +# DistributedHouse.py +AvatarsHouse = "Casa de\n %s" + +# BankGui.py +BankGuiCancel = lCancel +BankGuiOk = lOK + +# DistributedBank.py +DistributedBankNoOwner = "Sinto muito, este não é o seu banco." +DistributedBankNotOwner = "Sinto muito, este não é o seu banco." + +# FishSellGui.py +FishGuiCancel = lCancel +FishGuiOk = "Vender tudo" +FishTankValue = "Oi, %(name)s! Você tem %(num)s peixe(s) em seu balde, que vale(m) o total de %(value)s balinhas. Deseja vender todos eles?" + +#FlowerSellGui.py +FlowerGuiCancel = lCancel +FlowerGuiOk = "Vender Tudo" +FlowerBasketValue = "%(name)s, você tem %(num)s flores no seu cesto que valem um total de %(value)s balinhas. Você quer vender todas?" + + +def GetPossesive(name): + if name[-1:] == 'de': + possesive = name + "'" + else: + possesive = name + "" + return possesive + +# PetTraits +# VERY_BAD, BAD, GOOD, VERY_GOOD +PetTrait2descriptions = { + 'hungerThreshold': ('Sempre faminto', 'Muito faminto', + 'Às vezes faminto', 'Raramente faminto',), + 'boredomThreshold': ('Sempre entediado', 'Muito entediado', + 'Às vezes entediado', 'Raramente entediado',), + 'angerThreshold': ('Sempre irritado', 'Muito irritado', + 'Às vezes irritado', 'Raramente irritado'), + 'forgetfulness': ('Sempre esquecido', 'Muito esquecido', + 'Às vezes esquecido', 'Raramente esquecido',), + 'excitementThreshold': ('Muito calmo', 'Bem calmo', + 'Bem animado', 'Muito animado',), + 'sadnessThreshold': ('Sempre triste', 'Muitas vezes triste', + 'Às vezes triste', 'Raramente triste',), + 'restlessnessThreshold': ('Sempre inquieto', 'Muito inquieto', + 'Às vezes inquieto', 'Raramente inquieto',), + 'playfulnessThreshold': ('Raramente brincalhão', 'Às vezes brincalhão', + 'Muito brincalhão', 'Sempre brincalhão',), + 'lonelinessThreshold': ('Sempre solitário', 'Muito solitário', + 'Às vezes solitário', 'Raramente solitário',), + 'fatigueThreshold': ('Sempre cansado', 'Muito cansado', + 'Às vezes cansado', 'Raramente cansado',), + 'confusionThreshold': ('Sempre confuso', 'Muito confuso', + 'Às vezes confuso', 'Raramente confuso',), + 'surpriseThreshold': ('Sempre surpreso', 'Muito surpreso', + 'Às vezes surpreso', 'Raramente surpreso',), + 'affectionThreshold': ('Raramente carinhoso', 'Às vezes carinhoso', + 'Muito carinhoso', 'Sempre carinhoso',), + } + +# end translate + +# DistributedFireworkShow.py +FireworksInstructions = lToonHQ+": Pressione a tecla \"Page Up\" para ver melhor." + +FireworksValentinesBeginning = "" +FireworksValentinesEnding = "" +FireworksJuly4Beginning = lToonHQ+": Bem-vindo à queima de fogos de verão! Divirta-se com o show!" +FireworksJuly4Ending = lToonHQ+": Espero que tenha gostado do show! Um ótimo verão para você!" +FireworksOctober31Beginning = "" +FireworksOctober31Ending = "" +FireworksNewYearsEveBeginning = lToonHQ+": Feliz Ano Novo!!!!" +FireworksNewYearsEveEnding = lToonHQ+": Gostou dos Fogos? Logo tem mais!" +FireworksBeginning = lToonHQ+": Bem-vindo à queima de fogos de verão! Divirta-se com o show!" +FireworksEnding = lToonHQ+": Espero que tenha gostado do show! Um ótimo verão para você!" + +# ToontownLoadingScreen.py + +TIP_NONE = 0 +TIP_GENERAL = 1 +TIP_STREET = 2 +TIP_MINIGAME = 3 +TIP_COGHQ = 4 +TIP_ESTATE = 5 +TIP_KARTING = 6 + +# As of 8/5/03, ToonTips shouldn't exceed 130 characters in length +TipTitle = "DICA TOON:" +TipDict = { + TIP_NONE : ( + "", + ), + + TIP_GENERAL : ( + "Verifique com rapidez o andamento da Tarefa Toon mantendo pressionada a tecla \"End\".", + "Verifique com rapidez a sua Página de piadas mantendo pressionada a tecla \"Home\".", + "Abra a sua Lista de amigos pressionando a tecla \"F7\".", + "Abra ou feche o seu Álbum Toon pressionando a tecla \"F8\".", + "Você pode procurar acima pressionando a tecla \"Page Up\" e abaixo pressionando a tecla \"Page Down\".", + "Pressione a tecla \"Control\" para pular.", + "Pressione a tecla \"F9\" para capturar a tela, que será salva na pasta Toontown do seu computador.", + # This one makes me nervous without mentioning Parent Passwords - but that would be too long + # "Você pode trocar Códigos de Amigo secreto com alguém conhecido que não seja de Toontown, para permitir um chat aberto com essa pessoa em Toontown.", + "Você pode alterar a resolução de seu vídeo, ajustar o áudio e controlar outras opções na Página de opções do Álbum Toon.", + "Experimente as roupas de seus amigos no armário da casa deles.", + "Você pode ir para casa usando o botão \"Ir para casa\" em seu mapa.", + "Toda vez que você conclui uma Tarefa Toon, seus Pontos de risadas são automaticamente recarregados.", + "Você pode procurar a seleção nas lojas de roupas mesmo sem ter um tíquete de roupas.", + "As recompensas para algumas Tarefas Toon permitem que você carregue mais piadas e balinhas.", + "Você pode ter até 50 amigos na sua Lista de amigos.", + "Algumas recompensas das Tarefas Toon permitem que você se teletransporte para os pátios de Toontown usando a Página do mapa do Álbum Toon.", + "Aumente os seus Pontos de risadas nos pátios, catando tesouros como estrelas e casquinhas de sorvete.", + "Se você precisar se recuperar rápido após uma batalha difícil, vá para a sua propriedade e recolha casquinhas de sorvete.", + "Alterne entre os diversos modos de exibição de seu Toon pressionando a tecla Tab.", + "Algumas vezes, você poderá encontrar várias Tarefas Toon diferentes com a mesma recompensa. Faça sua pesquisa de mercado!", + "Encontrar amigos com Tarefas Toon semelhantes é uma maneira divertida de progredir no jogo.", + "Você nunca precisa salvar o seu progresso em Toontown. Os servidores de Toontown salvam continuamente todas as informações necessárias.", + "Você pode cochichar com outros Toons clicando neles ou selecionando-os em sua Lista de amigos.", + "Algumas frases do Chat rápido têm animações para indicar o estado de espírito do seu Toon.", + "Se a área em que você está se encontra lotada, tente mudar de região. Vá para a Página de região do Álbum Toon e selecione uma diferente.", + "Se você estiver em plena atividade de salvamento de edifícios, ganhará uma estrela de bronze, prata ou ouro, que ficará acima de seu Toon.", + "Se você salvar um número suficiente de edifícios para obter uma estrela acima da cabeça, seu nome pode estar no quadro-negro de um Quartel Toon.", + "Os edifícios salvos, às vezes, são recuperados pelos Cogs. A única maneira de manter a sua estrela é sair em campo e salvar mais edifícios!", + "Os nomes dos seus Amigos secretos aparecerão na cor azul.", + # Fishing + "Veja se você consegue pegar todos os peixes de Toontown!", + "Há peixes diferentes nos diversos lagos. Tente todos!", + "Quando o seu balde de pesca estiver cheio, venda os peixes para os pescadores dos pátios.", + "Venda os peixes para o pescador ou dentro das Lojas de Animais.", + "As varas de pescar mais fortes conseguem pegar peixes mais pesados, mas custam mais balinhas.", + "Você pode comprar varas de pescar mais fortes no Gadálogo.", + "Os peixes mais pesados valem mais balinhas na Loja de animais.", + "Os peixes raros valem mais balinhas na Loja de animais.", + "Às vezes, você consegue encontrar bolsas de balinhas durante a pesca.", + "Algumas Tarefas Toon exigem que você pesque itens fora dos lagos.", + "Os lagos de pesca dos pátios possuem peixes diferentes dos lagos das ruas.", + "Alguns peixes são realmente raros. Continue pescando até pegar todos!", + "O lago da sua propriedade possui peixes que só podem ser encontrados lá.", + "Para cada dez espécies pescadas, você ganhará um troféu de pesca!", + "Você pode ver qual peixe pescou no Álbum Toon.", + "Alguns troféus de pesca o recompensam com um Acréscimo de risadas.", + "A pesca é uma boa maneira de ganhar mais balinhas.", + # Doodles + "Adote um Rabisco na Loja de Animais!", + "As lojas de animais têm Rabiscos novos para vender todos os dias.", + "Visite as lojas de animais todos os dias para ver que Rabiscos novos elas têm.", + "Há diferentes Rabiscos para adoção nos diferentes bairros.", + # Karting + "Mostre o seu carrão e dê uma turbinada no seu limite de Risadas no Autódromo do Pateta. ", + "Entre no Autódromo do Pateta pelo túnel em forma de pneu no pátio do Centro de Toontown.", + "Ganhe pontos de Risada no Autódromo do Pateta.", + "O Autódromo do Pateta tem seis pistas de corrida diferentes. " + ), + + TIP_STREET : ( + "Há quatro tipos de Cogs: Robôs da Lei, Robôs Mercenários, Robôs Vendedores e Robôs-chefe.", + "Cada Método de piadas possui diferentes intensidades de precisão e dano.", + "As piadas sonoras afetam todos os Cogs, mas acordam qualquer Cog iscado.", + "Derrotar os Cogs em ordem estratégica pode aumentar bastante as suas chances de vencer as batalhas.", + "O Método de piadas Toonar permite que você atinja outros Toons na batalha.", + "Os pontos de experiência das piadas são dobrados durante uma Invasão de Cogs!", + "Vários Toons podem se reunir em equipes e usar o mesmo Método de piadas na batalha para conseguir danos extras aos Cogs.", + "Na batalha, as piadas são usadas na ordem de cima para baixo, conforme exibido no Menu de piadas.", + "A fileira de luzes circulares sobre os elevadores do Edifício dos Cogs mostram quantos andares haverá lá dentro.", + "Clique em um Cog para ver mais detalhes.", + "Usar piadas de alto nível contra Cogs de baixo nível não lhe renderá nenhum ponto de experiência.", + "As piadas que rendem experiência possuem um fundo azul no Menu de piadas da batalha.", + "A experiência de piadas é multiplicada quando usada dentro dos Edifícios dos Cogs. Os andares mais altos têm multiplicadores maiores.", + "Quando um Cog é derrotado, cada Toon daquela rodada recebe créditos de Cogs depois que a batalha termina.", + "Cada rua de Toontown possui níveis e tipos diferentes de Cogs.", + "As calçadas são locais seguros, sem Cogs.", + "Nas ruas, as portas laterais contam piadas do tipo toc-toc quando você se aproxima delas.", + "Algumas Tarefas Toon treinam você em novos Métodos de piadas. Você só pode escolher seis dos sete métodos, portanto, escolha direito!", + "As armadilhas só terão utilidade se você ou seus amigos coordenarem o uso de iscas na batalha.", + "As iscas de alto nível têm menos probabilidade de falhar.", + "As piadas de nível baixo oferecem menor precisão contra os Cogs de alto nível.", + "Os Cogs não podem atacar depois que forem \"iscados\" para a batalha.", + "Quando você e seus amigos dominam um Edifício de Cogs, vocês são recompensados com retratos dentro do Edifício dos Toons recuperado.", + "Usar uma piada Toonar em um Toon que possua um Risômetro cheio não renderá nenhuma experiência de Toonar.", + "Os Cogs ficarão atordoados por uns momentos quando atingidos por alguma. Assim, aumentam as chances de outras piadas da mesma rodada os atingirem.", + "As Piadas cadentes têm menos chance de atingir alguém, mas sua precisão aumenta quando os Cogs já tiverem sido atingidos por outra piada na mesma rodada.", + "Quando você já tiver derrotado um número suficiente de Cogs, use o \"Radar de Cogs\" clicando nos ícones de Cogs da página Galeria de Cogs do seu Álbum Toon.", + "Durante uma batalha, você tem como saber qual Cog os seus companheiros de equipe estão atacando; basta olhar para os travessões (-) e para os X.", + "Durante uma batalha, os Cogs carregam uma luz que mostra sua saúde: o verde significa saudável e o vermelho, quase destruído.", + "No máximo, quatro Toons podem guerrear ao mesmo tempo.", + "Na rua, os Cogs têm mais probabilidade de entrar em uma briga contra vários Toons do que contra apenas um Toon.", + "Os dois tipos de Cogs mais difíceis de cada tipo só são encontrados nos edifícios.", + "As Piadas cadentes nunca funcionam contra Cogs iscados.", + "Os Cogs tendem a atacar o Toon que lhes causou danos maiores.", + "As piadas sonoras não rendem danos extras contra Cogs iscados.", + "Se você esperar muito para atacar um Cog iscado, ele acordará. As iscas de nível mais alto têm duração maior.", + "Há lagos de pesca em cada rua de Toontown. Algumas ruas possuem peixes exclusivos.", + ), + + TIP_MINIGAME : ( + "Depois que você preenche a sua jarra de balinhas, qualquer balinha que ganhar nos Jogos no bondinho cairão direto no seu banco.", + "Você pode usar as teclas de seta em vez de o mouse no Jogo no bondinho \"Acompanhe a Minnie\".", + "No Jogo do canhão, você pode usar as teclas de seta para mover o seu canhão e pressionar a tecla \"Control\" para atirar.", + "No Jogo dos anéis, você ganha pontos extras quando todo o grupo consegue nadar com sucesso através dos anéis.", + "Um jogo perfeito de Acompanhe a Minnie dobrará seus pontos.", + "No Cabo-de-guerra, você ganha mais balinhas se jogar contra um Cog forte.", + "A dificuldade dos Jogos no bondinho varia conforme o bairro; os do Centro de Toontown são os mais fáceis, e os da Sonholândia do Donald são os mais difíceis.", + "Certos Jogos no bondinho só podem ser em grupo.", + ), + + TIP_COGHQ : ( + "Você deve completar o seu Disfarce de Robô Vendedor antes de visitar o VP.", + "Você deve completar o seu Disfarce de Robô Mercenário antes de visitar o Diretor Financeiro.", + "Você deve completar o seu Disfarce de Robô da Lei antes de visitar o Juiz-chefe.", + "Você pode pular em cima de cogs Brutamontes para desativá-los por um tempo.", + "Ganhe Méritos de cogs ao derrotar Robôs Vendedores em batalha.", + "Ganhe Cograna ao derrotar Robôs Mercenários em batalha.", + "Ganhe Avisos de Júri ao derrotar Robôs da Lei em batalha.", + "Você ganha mais Méritos, Cogranas ou Avisos de Júri de Cogs de nível maior.", + "Quando conseguir juntar Méritos o suficiente para merecer uma promoção, vá ver o VP dos Robôs Vendedores!", + "Quando conseguir juntar Cogranas o suficiente para merecer uma promoção, vá ver o Diretor Financeiro dos Robôs Mercenários!", + "Quando conseguir juntar Avisos de Júri o suficiente para merecer uma promoção, vá ver o Juiz-chefe dos Robôs da Lei!", + "Você pode falar como um Cog quando estiver usando o seu Disfarce de Cog.", + "Até oito Toons podem lutar juntos contra o VP dos Robôs Vendedores", + "Até oito Toons podem lutar juntos contra o Diretor Financeiro dos Robôs Mercenários", + "Até oito Toons podem lutar juntos contra o Juiz-chefe dos Robôs da Lei", + "Dentro do Quartel dos Cogs, o caminho é subindo as escadas.", + "Cada vez que lutar numa fábrica do Quartel dos Robôs Vendedores, você vai ganhar uma peça do seu Disfarce de Robô Vendedor.", + "Você pode verificar o progresso do seu Disfarce no seu Álbum Toon.", + "Você pode verificar o progresso da sua promoção na Página de Disfarce do seu Álbum Toon.", + "Certifique-se de estar com as piadas cheias e com o Risômetro cheio antes de ir até um Quartel dos Cogs.", + "Quando for promovido, seu disfarce de Cog será atualizado.", + "Você terá que derrotar o "+Foreman+" para recuperar uma peça do Disfarce de Robô Vendedor.", + "Ganhe peças de disfarce de Robô Mercenário como recompensa de Tarefas Toon na Sonholândia do Donald.", + "Os Robôs Mercenários produzem e distribuem o seu próprio dinheiro, as Cogranas, de três maneiras - Moeda, Dólar e Barra.", + "Espere até que o Diretor Financeiro esteja tonto para lançar um cofre, senão ele vai usá-lo como capacete! Acerte o capacete com outro cofre para derrubá-lo.", + "Ganhe peças de disfarce de Robô da Lei como recompensa de Tarefas Toon pelo Professor Floco.", + "Vale a pena a confusão: os Cogs virtuais no Quartel dos Robôs da Lei não dão Avisos de Júri de recompensa.", + ), + TIP_ESTATE : ( + # Doodles + "Os Rabiscos entendem algumas frases do Chat rápido. Experimente!", + "Use o menu \"Bichinho\" do Chat rápido para pedir a seu Rabisco que faça truques.", + "Você pode ensinar aos Rabiscos truques com as lições de treinamento do Gadálogo da Clarabela.", + "Recompense o seu Rabisco pelos truques.", + "Se você visitar a propriedade de um amigo, o seu Rabisco lhe fará companhia.", + "Alimente o seu Rabisco com uma balinha quando ele estiver com fome.", + "Clique em um Rabisco para ver um menu no qual você poderá Alimentar, Coçar e Chamá-lo.", + "Os Rabiscos adoram companhia. Convide os amigos para brincar!", + "Todos os Rabiscos possuem personalidades próprias.", + "Você pode devolver o seu Rabisco e adotar outro nas Lojas de Animais.", + "Quando um Rabisco faz um truque, os Toons que o cercam se recuperam.", + "Os Rabiscos ficam ainda melhores nos truques com a prática. Continue assim!", + "Os truques mais avançados dos Rabiscos recuperam os Toons com mais rapidez.", + "Rabiscos com mais experiência podem fazer mais truques sem ficar tão cansados.", + "Veja uma lista de Rabiscos próximos em sua Lista de amigos.", + # Furniture / Cattlelog + "Compre móveis usando o Gadálogo da Clarabela e decore a sua casa.", + "O banco da casa tem mais balinhas.", + "O armário da casa tem mais roupas.", + "Vá até a casa do seu amigo e experimente as roupas dele.", + "Compre varas de pescar melhores no Gadálogo da Clarabela.", + "Compre bancos maiores no Gadálogo da Clarabela.", + "Ligue para a Clarabela usando o telefone da casa.", + "A Clarabela vende um armário maior em que cabem mais roupas.", + "Reserve espaço no seu armário antes de usar o tíquete de roupas.", + "A Clarabela vende tudo o que é preciso para decorar a sua casa.", + "Verifique a sua caixa de correio para ver se há entregas antes de fazer seus pedidos com a Clarabela.", + "As roupas do Gadálogo da Clarabela levam uma hora para serem entregues.", + "Os papéis de parede e pisos do Gadálogo da Clarabela levam uma hora para serem entregues.", + "Os móveis do Gadálogo da Clarabela levam um dia inteiro para serem entregues.", + "Armazene móveis de reserva no sótão.", + "Você será avisado pela Clarabela quando um novo Gadálogo estiver disponível.", + "Você será avisado pela Clarabela quando uma entrega do Gadálogo chegar.", + "Novos Gadálogos são entregues toda semana.", + "Procure os produtos promocionais de estoque limitado no Gadálogo.", + "Mova os móveis indesejados para a lata de lixo.", + # Fish + "Alguns peixes, como a Cavala Trotante, são mais comuns nas propriedades de Toons.", + # Misc + "Você pode convidar os seus amigos para a sua propriedade usando o Chat rápido.", + "Você sabia que a cor da sua casa combina com a cor do seu painel Pegar um Toon?", + ), + TIP_KARTING : ( + # Goofy Speedway zone specific + "Compre um Conversível, Utilitário Toon ou Cruzeiro na Loja do Kart do Pateta.", + "Personalize o seu kart com decalques, calotas e muito mais na Loja do Kart do Pateta.", + "Ganhe tíquetes correndo de kart no Autódromo do Pateta.", + "Os tíquetes são a única moeda aceita na Loja do Kart do Pateta.", + "São necessários tíquetes como depósito antes das corridas.", + "Uma página especial do seu Álbum Toon permite que você personalize o seu kart.", + "Uma página especial do seu Álbum Toon permite que você veja os recordes de cada pista.", + "Uma página especial do seu Álbum Toon permite que você veja seus troféus.", + "O Estádio dos Nerds é a pista mais fácil do Autódromo do Pateta.", + "A Pista de Pulos tem o maior número de inclinações e rampas do Autódromo do Pateta.", + "A Avenida da Neve é a pista mais difícil do Autódromo do Pateta.", + ), + } + +FishGenusNames = { + 0 : "Baiacu", + 2 : "Peixe-gato", + 4 : "Peixe-palhaço", + 6 : "Peixe congelado", + 8 : "Estrela-do-mar", + 10 : "Cavala Trotante!", + 12 : "Cachorra", + 14 : "Enguia Amore", + 16 : "Tubarão-enfermeira", + 18 : "Caranguejo-rei", + 20 : "Peixe-lua", + 22 : "Cavalo-marinho", + 24 : "Tubarão Fera", + 26 : "Barra Cursa", + 28 : "Truta Cicuta", + 30 : "Piano Atum", + 32 : "Manteiga de Amendoim e Água-viva", + 34 : "Raia Jamanta", + } + +FishSpeciesNames = { + 0 : ( "Baiacu", + "Baiacu Balão de Ar", + "Baiacu Balão Meteorológico", + "Baiacu Balão de Água", + "Baiacu Balão Vermelho", + ), + 2 : ( "Peixe-gato", + "Peixe-gato Siamês", + "Peixe-gato de Rua", + "Peixe-gato Rajado", + "Peixe-gato Tonto", + ), + 4 : ( "Peixe-palhaço", + "Peixe-palhaço Triste", + "Peixe-palhaço de Festa", + "Peixe-palhaço de Circo", + ), + 6 : ( "Peixe congelado", + ), + 8 : ( "Estrela-do-mar", + "Cinco Estrelas-do-mar", + "Estrela-do-mar do Rock", + "Estrela-do-mar Cintilante", + "Estrela-do-mar All Star", + ), + 10 : ( "Cavala Trotante!", + ), + 12 : ( "Cachorra", + "Cachorra Buldogue", + "Cachorra-quente", + "Cachorra Dálmata", + "Cachorrinha", + ), + 14 : ( "Enguia Amore", + "Enguia Amore Elétrica", + ), + 16 : ( "Tubarão-enfermeira", + "Tubarão-enfermeira Clara", + "Tubarão-enfermeira Flora", + ), + 18 : ( "Caranguejo-rei", + "Caranguejo-rei do Alasca", + "Caranguejo-rei Velho", + ), + 20 : ( "Peixe-lua", + "Peixe-lua Cheia", + "Peixe Meia-lua", + "Peixe-lua Nova", + "Peixe-lua Crescente", + "Peixe-lua da Colheita", + ), + 22 : ( "Cavalo-marinho", + "Cavalo-marinho de Pau", + "Cavalo-marinho Clydesdale", + "Cavalo-marinho Árabe", + ), + 24 : ( "Tubarão-Fera", + "Tubarãozinho Fera", + "Tubarão-Fera da Piscina", + "Tubarão-Fera da Piscina Olímpica", + ), + 26 : ( "Barra Cursa Marrom", + "Barra Cursa Preto", + "Barra Cursa Coala", + "Barra Cursa de Mel", + "Barra Cursa Polar", + "Barra Cursa Panda", + "Barra Cursa Kodiac", + "Barra Cursa Grizzly", + ), + 28 : ( "Truta", + "Capitão Truta Cicuta", + "Truta Cicuta Escorbuta", + ), + 30 : ( "Piano Atum", + "Grande Piano Atum", + "Grande Piano Atum Baby", + "Piano Atum Ereto", + "Músico de Piano Atum", + ), + 32 : ( "Manteiga de Amendoim e Água-viva", + "MA & Água-viva de Uva", + "MA & Água-viva Crocante", + "MA & Água-viva de Morango", + "Concord Grape PB&J Fish", + ), + 34 : ( "Raia Jamanta", + ), + } + +FishFirstNames = ( + "", + "Anjo", + "Ártico", + "Baby", + "Bermuda", + "Big", + "Bruna", + "Bolhas", + "Detuna", + "Docinho", + "Capitão", + "Chip", + "Cacho", + "Coral", + "Doutor", + "Arenoso", + "Imperador", + "Canino", + "Gordo", + "Peixinho", + "Flipper", + "Linguado", + "Sardinha", + "Mel", + "João", + "Rei", + "Pequeno", + "Marlin", + "Senhorita", + "Senhor", + "Pêssego", + "Rosado", + "Príncipe", + "Princesa", + "Professor", + "Inchadinho", + "Rainha", + "Arco-íris", + "Raio", + "Rosinha", + "Ferrugem", + "Salgado", + "Sam", + "Sandy", + "Caspa", + "Tutubarão", + "Cavalheiro", + "Saltador", + "Chinela", + "Guaiúba", + "Malhado", + "Espinho", + "Pintado", + "Estrela", + "Doce", + "Súper", + "Tigre", + "Miúdo", + "Bigode", + ) + +FishLastPrefixNames = ( + "", + "Praia", + "Preto", + "Azul", + "Porcão", + "Machão", + "Gato", + "Fundo", + "Duplo", + "Leste", + "Chique", + "Escamoso", + "Chato", + "Fresco", + "Gigante", + "Ouro", + "Dourado", + "Cinza", + "Verde", + "Presunto", + "Mané", + "Geléia", + "Dama", + "Couro", + "Limão", + "Comprido", + "Nordeste", + "Oceano", + "Octo", + "Óleo", + "Pérola", + "Cachimbo", + "Vermelho", + "Faixa", + "Rio", + "Pedra", + "Rubi", + "Leme", + "Sal", + "Mar", + "Prata", + "Snorkel", + "Só", + "Sudeste", + "Espinhoso", + "Surfe", + "Espada", + "Tigre", + "Triplo", + "Tropical", + "Atum", + "Onda", + "Fraco", + "Oeste", + "Branco", + "Amarelo", + ) + +FishLastSuffixNames = ( + "", + "bola", + "baixo", + "barriga", + "besouro", + "gatuno", + "manteiga", + "garra", + "sapateiro", + "caranguejo", + "rosnador", + "tambor", + "barbatana", + "peixe", + "batedor", + "flipper", + "fantasma", + "roncador", + "cabeça", + "coroa", + "saltador", + "cavala", + "lua", + "boca", + "tainha", + "pescoço", + "nariz", + "galho", + "bruto", + "corredor", + "vela", + "tubarão", + "concha", + "seda", + "limo", + "mordedora", + "fedido", + "rabo", + "sapo", + "truta", + "água", + ) + + +CogPartNames = ( + "Perna superior esquerda", "Perna inferior esquerda", "Pé esquerdo", + "Perna superior direita", "Perna inferior direita", "Pé direito", + "Ombro esquerdo", "Ombro direito", "Peito", "Medidor de saúde", "Quadril", + "Braço superior esquerdo", "Braço inferior esquerdo", "Mão esquerda", + "Braço superior direito", "Braço inferior direito", "Mão direita", + ) + +CogPartNamesSimple = ( + "Busto superior", + ) + +# SellbotLegFactorySpec.py + +SellbotLegFactorySpecMainEntrance = "Entrada principal" +SellbotLegFactorySpecLobby = "Salão" +SellbotLegFactorySpecLobbyHallway = "Corredor do salão" +SellbotLegFactorySpecGearRoom = "Sala de engrenagens" +SellbotLegFactorySpecBoilerRoom = "Sala da caldeira" +SellbotLegFactorySpecEastCatwalk = "Passarela leste" +SellbotLegFactorySpecPaintMixer = "Misturador de tinta" +SellbotLegFactorySpecPaintMixerStorageRoom = "Depósito do Misturador de tinta" +SellbotLegFactorySpecWestSiloCatwalk = "Passarela do Silo Oeste" +SellbotLegFactorySpecPipeRoom = "Sala de tubulações" +SellbotLegFactorySpecDuctRoom = "Sala de dutos" +SellbotLegFactorySpecSideEntrance = "Entrada lateral" +SellbotLegFactorySpecStomperAlley = "Beco sinistro" +SellbotLegFactorySpecLavaRoomFoyer = "Antecâmara do Salão de lava" +SellbotLegFactorySpecLavaRoom = "Salão de lava" +SellbotLegFactorySpecLavaStorageRoom = "Depósito de lava" +SellbotLegFactorySpecWestCatwalk = "Passarela oeste" +SellbotLegFactorySpecOilRoom = "Sala de óleo" +SellbotLegFactorySpecLookout = "Vigilância" +SellbotLegFactorySpecWarehouse = "Armazém" +SellbotLegFactorySpecOilRoomHallway = "Corredor da Sala de óleo" +SellbotLegFactorySpecEastSiloControlRoom = "Sala de controle do Silo Leste" +SellbotLegFactorySpecWestSiloControlRoom = "Sala de controle do Silo Oeste" +SellbotLegFactorySpecCenterSiloControlRoom = "Sala de controle do Silo Central" +SellbotLegFactorySpecEastSilo = "Silo Leste" +SellbotLegFactorySpecWestSilo = "Silo Oeste" +SellbotLegFactorySpecCenterSilo = "Silo Central" +SellbotLegFactorySpecEastSiloCatwalk = "Passarela do Silo Leste" +SellbotLegFactorySpecWestElevatorShaft = "Eixo do Elevador Oeste" +SellbotLegFactorySpecEastElevatorShaft = "Eixo do Elevador Leste" + +#FISH BINGO +FishBingoBingo = "BINGO!" +FishBingoVictory = "VITÓRIA!!" +FishBingoJackpot = "GRANDE PRÊMIO!" +FishBingoGameOver = "FIM DO JOGO" +FishBingoIntermission = "Intervalo\nTermina em:" +FishBingoNextGame = "Próximo jogo\nComeça em:" +FishBingoTypeNormal = "Clássico" +FishBingoTypeCorners = "Quatro cantos" +FishBingoTypeDiagonal = "Diagonais" +FishBingoTypeThreeway = "Três vias" +FishBingoTypeBlockout = "BLOQUEADO!" +FishBingoStart = "Está na hora do Bingo dos Peixes! Vá para qualquer píer disponível para jogar!" +FishBingoEnd = "Espero que tenha se divertido no jogo Bingo dos Peixes." +FishBingoHelpMain = "Bem-vindo ao Bingo dos Peixes de Toontown! Todo mundo trabalha em conjunto no lago para preencher a cartela antes de acabar o tempo." +FishBingoHelpFlash = "Quando você pegar um peixe, clique em um dos quadrados piscantes para marcar a cartela." +FishBingoHelpNormal = "É uma cartela de Bingo Clássico. Para ganhar, complete qualquer linha vertical, horizontal ou na diagonal." +FishBingoHelpDiagonals = "Complete as duas diagonais para ganhar." +FishBingoHelpCorners = "Uma cartela de Cantos fácil. Complete todos os quatro cantos para ganhar." +FishBingoHelpThreeway = "Três vias. Complete ambas as diagonais e a linha do meio para ganhar. Esta não é fácil não!" +FishBingoHelpBlockout = "Bloqueado! Complete a cartela inteira para ganhar. Você está competindo contra todos os outros lagos e a bolada é grande!" +FishBingoOfferToSellFish = "O seu balde de pesca está cheio. Quer vender os seus peixes?" +FishBingoJackpotWin = "Ganhe %s balinhas!" + +# ResistanceSCStrings: SpeedChat phrases rewarded for defeating the CFO. +# It is safe to remove entries from this list, which will disable them +# for use from any toons who have already purchased them. Note that the +# index numbers are stored directly in the database, so once assigned +# to a particular phrase, a given index number should never be +# repurposed to any other phrase. +ResistanceToonupMenu = "Toonar" +ResistanceToonupItem = "%s toonar" +ResistanceToonupItemMax = "Máx." +ResistanceToonupChat = "Toons de todo o mundo: vamos toonar!" +ResistanceRestockMenu = "Doar Piadas" +ResistanceRestockItem = "Doar Piadas %s" +ResistanceRestockItemAll = "Tudo" +ResistanceRestockChat = "Toons de todo o mundo: vamos piadar!" +ResistanceMoneyMenu = "Balinhas" +ResistanceMoneyItem = "%s balinhas" +ResistanceMoneyChat = "Toons de todo o mundo: gastem com consciência!" + +# Resistance Emote NPC chat phrases +ResistanceEmote1 = NPCToonNames[9228] + ": Bem-vindo à Resistência!" +ResistanceEmote2 = NPCToonNames[9228] + ": Use a sua nova expressão para se identificar com outros membros." +ResistanceEmote3 = NPCToonNames[9228] + ": Boa sorte!" + +# Kart racing +KartUIExit = "Deixar o kart" +KartShop_Cancel = lCancel +KartShop_BuyKart = "Comprar kart" +KartShop_BuyAccessories = "Comprar acessórios" +KartShop_BuyAccessory = "Comprar acessório" +KartShop_Cost = "Custo: %d tíquetes" +KartShop_ConfirmBuy = "Comprar %s por %d tíquetes?" +KartShop_NoAvailableAcc = "Não há acessórios deste tipo" +KartShop_FullTrunk = "A mala está cheia." +KartShop_ConfirmReturnKart = "Tem certeza de que deseja devolver o seu kart atual?" +KartShop_ConfirmBoughtTitle = "Parabéns!" +KartShop_NotEnoughTickets = "Não há tíquetes suficientes!" + +KartView_Rotate = "Girar" +KartView_Right = "Direita" +KartView_Left = "Esquerda" + +# starting block +StartingBlock_NotEnoughTickets = "Você não tem tíquetes suficientes! Experimente participar de um treino." +StartingBlock_NoBoard = "O embarque para esta corrida terminou. Espere o início da próxima corrida." +StartingBlock_NoKart = "Primeiramente, você precisa de um kart! Por que você não pergunta a um dos funcionários da Loja do kart?" +StartingBlock_Occupied = "Este bloco já está ocupado! Procure outro ponto." +StartingBlock_TrackClosed = "Desculpe, esta pista está fechada para reformas." +StartingBlock_EnterPractice = "Deseja participar do treino?" +StartingBlock_EnterNonPractice = "Deseja participar de uma corrida %s por %s tíquetes?" +StartingBlock_EnterShowPad = "Deseja estacionar o seu carro aqui?" +StartingBlock_KickSoloRacer = "As corridas Batalha dos Toons e Grande Prêmio requerem dois ou mais participantes." +StartingBlock_Loading = "Indo para a corrida!" + +#stuff for leader boards +LeaderBoard_Time = "Tempo" +LeaderBoard_Name = "Nome do piloto" +LeaderBoard_Daily = "Pontuação diária" +LeaderBoard_Weekly = "Pontuação semanal" +LeaderBoard_AllTime = "Melhor pontuação de todos os tempos" + +RecordPeriodStrings = [ + LeaderBoard_Daily, + LeaderBoard_Weekly, + LeaderBoard_AllTime, + ] + +KartRace_RaceNames = [ + "Treino", + "Batalha dos Toons", + "Torneio", + ] + +from toontown.racing import RaceGlobals + +KartRace_Go = "Largar!" +KartRace_Reverse = " Rev" + +#needed for leader boards +KartRace_TrackNames = { + RaceGlobals.RT_Speedway_1 : "Estádio dos Nerds", + RaceGlobals.RT_Speedway_1_rev : "Estádio dos Nerds" + KartRace_Reverse, + RaceGlobals.RT_Rural_1 : "Autódromo Rústico", + RaceGlobals.RT_Rural_1_rev : "Autódromo Rústico" + KartRace_Reverse, + RaceGlobals.RT_Urban_1 : "Circuito da Cidade", + RaceGlobals.RT_Urban_1_rev : "Circuito da Cidade" + KartRace_Reverse, + RaceGlobals.RT_Speedway_2 : "Coliseu Saca-Rolhas", + RaceGlobals.RT_Speedway_2_rev : "Coliseu Saca-Rolhas" + KartRace_Reverse, + RaceGlobals.RT_Rural_2 : "Pista de Pulos", + RaceGlobals.RT_Rural_2_rev : "Pista de Pulos" + KartRace_Reverse, + RaceGlobals.RT_Urban_2 : "Avenida da Neve", + RaceGlobals.RT_Urban_2_rev : "Avenida da Neve" + KartRace_Reverse, + } + +KartRace_Unraced = "N/D" + +KartDNA_KartNames = { + 0:"Cruzeiro", + 1:"Conversível", + 2:"Utilitário Toon" + } + +KartDNA_AccNames = { + #engine block accessory names + 1000: "Filtro de ar", + 1001: "Carburador quádruplo", + 1002: "Águia", + 1003: "Chifres", + 1004: "Seis cilindros", + 1005: "Aerofólio pequeno", + 1006: "Válvulas simples", + 1007: "Aerofólio médio", + 1008: "Carburador simples", + 1009: "Corneta", + 1010: "Aerofólio simétrico", + #spoiler accessory names + 2000: "Asa", + 2001: "Peça recondicionada", + 2002: "Gaiola", + 2003: "Aleta", + 2004: "Asa dupla", + 2005: "Asa simples", + 2006: "Peça sobressalente padrão", + 2007: "Aleta", + 2008: "ps9", + 2009: "ps10", + #front wheel well accessory names + 3000: "Buzina dupla", + 3001: "Pára-choques do Joe", + 3002: "Estribos de cobalto", + 3003: "Descarga lateral cobra", + 3004: "Descarga lateral reta", + 3005: "Pára-choques vazados", + 3006: "Estribos de carbono", + 3007: "Estribos de madeira", + 3008: "fw9", + 3009: "fw10", + #rear wheel well accessory names (twisty twisty) + 4000: "Canos de descarga traseiros curvos", + 4001: "Pára-lamas", + 4002: "Escapamento duplo", + 4003: "Aletas duplas lisas", + 4004: "Pára-lamas lisos", + 4005: "Escapamento quadrado", + 4006: "Acabamento duplo", + 4007: "Megaescapamento", + 4008: "Aletas duplas simétricas", + 4009: "Aletas duplas redondas", + 4010: "Pára-lamas simétricos", + 4011: "Pára-lamas do Mickey", + 4012: "Pára-lamas vazados", + #rim accessoKartRace_Exit = "Leave Race"ry names + 5000: "Turbo", + 5001: "Lua", + 5002: "Emendado", + 5003: "Três raios", + 5004: "Pintura da tampa", + 5005: "Coração", + 5006: "Mickey", + 5007: "Cinco raios", + 5008: "Margarida", + 5009: "Basquete", + 5010: "Hipnótico", + 5011: "Tribal", + 5012: "Diamante", + 5013: "Cinco raios", + 5014: "Roda", + #decal accessory names + 6000: "Número cinco", + 6001: "Respingo", + 6002: "Quadriculado", + 6003: "Chamas", + 6004: "Corações", + 6005: "Bolhas", + 6006: "Tigre", + 6007: "Flores", + 6008: "Raio", + 6009: "Anjo", + #paint accessory names + 7000: "Verde-limão", + 7001: "Pêssego", + 7002: "Vermelho vivo", + 7003: "Vermelho", + 7004: "Castanho", + 7005: "Siena", + 7006: "Marrom", + 7007: "Canela", + 7008: "Coral", + 7009: "Laranja", + 7010: "Amarelo", + 7011: "Creme", + 7012: "Cítrico", + 7013: "Limão", + 7014: "Verde-água", + 7015: "Verde", + 7016: "Azul-claro", + 7017: "Verde-azulado", + 7018: "Azul", + 7019: "Verde-musgo", + 7020: "Azul-turquesa", + 7021: "Azul-cinzento", + 7022: "Lilás", + 7023: "Púrpura", + 7024: "Rosa", + 7025: "Ameixa", + 7026: "Preto", + } + +RaceHoodSpeedway = "Autódromo" +RaceHoodRural = "Rural" +RaceHoodUrban = "Urbano" +RaceTypeCircuit = "Torneio" +RaceQualified = "classificado" +RaceSwept = "swept" +RaceWon = "venceu" +Race = "corrida" +Races = "corridas" +Total = "total" +GrandTouring = "Gran Turismo" + +def getTrackGenreString(genreId): + genreStrings = [ "Autódromo", + "País", + "Cidade" ] + return genreStrings[genreId].lower() + +def getTunnelSignName(trackId, padId): + # hack for bad naming! + if trackId == 2 and padId == 0: + return "tunne1l_citysign" + elif trackId == 1 and padId == 0: + return "tunnel_countrysign1" + else: + genreId = RaceGlobals.getTrackGenre(trackId) + return "tunnel%s_%ssign" % (padId + 1, RaceGlobals.getTrackGenreString(genreId)) + +# Kart Trophy Descriptions +KartTrophyDescriptions = [ + # qualified race trophies + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodSpeedway + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodRural + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[0]) + " " + Race + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[1]) + " " + Races + " " + RaceQualified, + RaceHoodUrban + " " + str(RaceGlobals.QualifiedRaces[2]) + " " + Races + " " + RaceQualified, + str(RaceGlobals.TotalQualifiedRaces) + " " + Total + " " + Races + " " + RaceQualified, + # won race trophies + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + RaceHoodSpeedway + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodRural + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodRural + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[0]) + " " + Race + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[1]) + " " + Races + " " + RaceWon, + RaceHoodUrban + " " + str(RaceGlobals.WonRaces[2]) + " " + Races + " " + RaceWon, + str(RaceGlobals.TotalWonRaces) + " " + Total + " " + Races + " " + RaceWon, + #qualified circuit races + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceQualified, + # won circuit race trophies + str(RaceGlobals.WonCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + str(RaceGlobals.WonCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceWon, + # swept circuit races + str(RaceGlobals.SweptCircuitRaces[0]) + " " + RaceTypeCircuit + " " + Race + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[1]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + str(RaceGlobals.SweptCircuitRaces[2]) + " " + RaceTypeCircuit + " " + Races + " " + RaceSwept, + # NOTE: to be added + GrandTouring, + # cups (+1 laff each) + str(RaceGlobals.TrophiesPerCup) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + str(RaceGlobals.TrophiesPerCup * 2) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + str(RaceGlobals.TrophiesPerCup * 3) + " Troféus de corrida de kart recebidos! Mais acréscimo de pontos de risada!", + ] + +KartRace_TitleInfo = "Preparar para a corrida" +KartRace_SSInfo = "Bem-vindo ao Estádio dos Nerds!\nPé na tábua e segure firme!" +KartRace_CoCoInfo = "Bem-vindo ao Coliseu Saca-Rolhas!\nUse as curvas inclinadas para manter a velocidade!\n" +KartRace_RRInfo = "Bem-vindo ao Autódromo Rústico!\nPreserve os animais e permaneça na pista!\n" +KartRace_AAInfo = "Bem-vindo à Pista de Pulos!\nSegure firme! O caminho parece acidentado...\n" +KartRace_CCInfo = "Bem-vindo ao Circuito da Cidade!\nCuidado com os pedestres quando passar pelo centro da cidade!\n" +KartRace_BBInfo = "Bem-vindo à Avenida da Neve!\nCuidado com a velocidade. Pode ter gelo na pista.\n" +KartRace_GeneralInfo = "Use Ctrl para lançar as piadas que pegar na pista, e as teclas de setas, para controlar o kart." + +KartRace_TrackInfo = { + RaceGlobals.RT_Speedway_1 : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_1_rev : KartRace_SSInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2 : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Speedway_2_rev : KartRace_CoCoInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1 : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_1_rev : KartRace_RRInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2 : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Rural_2_rev : KartRace_AAInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1 : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_1_rev : KartRace_CCInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2 : KartRace_BBInfo + KartRace_GeneralInfo, + RaceGlobals.RT_Urban_2_rev : KartRace_BBInfo + KartRace_GeneralInfo, + } + +KartRecordStrings = { + RaceGlobals.Daily : 'diariamente', + RaceGlobals.Weekly : 'semanalmente', + RaceGlobals.AllTime : 'o tempo todo', + } + +KartRace_FirstSuffix = 'o' +KartRace_SecondSuffix = ' o' +KartRace_ThirdSuffix = ' o' +KartRace_FourthSuffix = ' o' +KartRace_WrongWay = 'Direção\nerrada!' +KartRace_LapText = "Volta %s" +KartRace_FinalLapText = "Volta final!" +KartRace_Exit = "Sair da corrida" +KartRace_NextRace = "Próxima Corrida" +KartRace_Leave = "Deixar a corrida" +KartRace_Qualified = "Classificado!" +KartRace_Record = "Recorde!" +KartRace_RecordString = 'Você bateu um novo recorde %s para %s! Seu bônus é %s tíquetes.' +KartRace_Tickets = "Tíquetes" +KartRace_Exclamations = "!" +KartRace_Deposit = "Depósito" +KartRace_Winnings = "Vitórias" +KartRace_Bonus = "Bônus" +KartRace_RaceTotal = "Total da corrida" +KartRace_CircuitTotal = "Total do Circuito" +KartRace_Trophies = "Troféus" +KartRace_Zero = "0" +KartRace_Colon = ":" +KartRace_TicketPhrase = "%s" + KartRace_Tickets +KartRace_DepositPhrase = KartRace_Deposit + KartRace_Colon + "\n" + KartRace_Tickets +KartRace_QualifyPhrase = "Classificar:\n" +KartRace_RaceTimeout = "Tempo esgotado nesta corrida. Seus tíquetes foram reembolsados. Continue tentando!" +KartRace_RaceTimeoutNoRefund = "O tempo da corrida esgotou. Seus tíquetes não foram reembolsados porque o Grande Prêmio já começou. Continue tentando!" +KartRace_RacerTooSlow = "Você demorou demais para terminar a corrida. Seus tíquetes não foram reembolsados. Continue tentando!" +KartRace_PhotoFinish = "Foto da chegada!" +KartRace_CircuitPoints = 'Pontos do Circuito' +CircuitRaceStart = "O Grande Prêmio de Toontown está prestes a começar! Para vencer, ganhe o maior número de pontos em três corridas consecutivas!" +CircuitRaceEnd = "E por hoje é só do Grande Prêmio de Toontown no Autódromo do Pateta. Vejo você na próxima segunda-feira!" + +# Trick-or-Treat holiday +TrickOrTreatMsg = 'Você já encontrou\nesta gostosura!' + +#temp lawbot boss dialog text +LawbotBossTempIntro0 = "Humm, o que temos na pauta de casos hoje?" +LawbotBossTempIntro1 = "Arrá, temos o julgamento de um Toon!" +LawbotBossTempIntro2 = "O caso da promotoria é forte." +LawbotBossTempIntro3 = "E aqui estão os defensores públicos." +LawbotBossTempIntro4 = "Espere um pouco... Vocês são Toons!" +LawbotBossTempJury1 = "A seleção do júri vai começar agora." +LawbotBossHowToGetEvidence = "Toque na tribuna da testemunha para pegar a evidência." +LawbotBossTrialChat1 = "A sessão da Corte está aberta" +LawbotBossHowToThrowPies = "Aperte a tecla Insert para arremessar a evidência\n nos advogados ou na balança!" +LawbotBossNeedMoreEvidence = "Você precisa de mais evidências!" +LawbotBossDefenseWins1 = "Impossível! A defesa venceu?" +LawbotBossDefenseWins2 = "Não. Eu declaro este julgamento nulo! Um novo julgamento será agendado." +LawbotBossDefenseWins3 = "Humpf. Estarei na minha sala." +LawbotBossProsecutionWins = "Eu julgo em favor do querelante" +LawbotBossReward = "O prêmio é uma promoção e a habilidade de evocar Cogs" +LawbotBossLeaveCannon = "Deixar canhão" +LawbotBossPassExam = "Bah, e daí que você passou no exame da ordem dos advogados?" +LawbotBossTaunts = [ + "%s, eu julgo você em desacato desta corte!", + "Objeção aceita!", + "Apague isso dos registros.", + "Sua apelação foi rejeitada. A sua sentença é a tristeza!", + "Ordem na corte!", + ] +LawbotBossAreaAttackTaunt = "Vocês todos estão em desacato da corte!" + + +WitnessToonName = "Abel Abelhudo" +WitnessToonPrepareBattleTwo = "Oh, não! Eles estão colocando apenas Cogs no júri!\aRápido, use os canhões e atire alguns jurados Toons nas cadeiras do júri.\aPrecisamos de %d para ter uma balança justa." +WitnessToonNoJuror = "Oh-oh, sem jurados Toons. Vai ser um julgamento difícil." +WitnessToonOneJuror = "Legal! Tem 1 Toon no júri!" +WitnessToonSomeJurors = "Legal! Tem %d Toons no júri!" +WitnessToonAllJurors = "Irado! Todos os jurados são Toons!" +WitnessToonPrepareBattleThree = "Rápido, toque na tribuna da testemunha para pegar evidências.\aAperte a tecla Insert para arremessar a evidência nos advogados, ou no prato da defesa." +WitnessToonCongratulations = "Você conseguiu! Obrigado por uma defesa espetacular!\aAqui ,fique com estes papéis deixados pelo Juiz-chefe.\aCom isto você será capaz de evocar Cogs da sua página Galeria de Cogs." + +WitnessToonLastPromotion = "\aUau, você atingiu o nível %s do seu Disfarce de Cog!\aOs Cogs não são promovidos mais que isso.\aVocê não pode mais atualizar o seu Disfarce de Cog, mas ainda pode continuar trabalhando pela Resistência!" +WitnessToonHPBoost = "\aVocê fez muito pela Resistência.\aO Conselho de Toons decidiu lhe dar mais um ponto de Risada. Parabéns!" +WitnessToonMaxed = "\aVejo que tem um Disfarce de Cog nível %s. Impressionante!\aEm nome de todo o Conselho de Toons, obrigado por voltar para defender mais Toons!" +WitnessToonBonus = "Maravilhoso! Todos os advogados estão atordoados. O peso da sua evidência foi %s vezes mais denso por %s segundos" + +WitnessToonJuryWeightBonusSingular = { + 6: 'Este caso é difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', + 7: 'Este caso é muito difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', + 8: 'Este caso é o mais difícil. Você sentou %d jurado Toon, então a sua evidência tem um peso-bônus de %d.', +} + +WitnessToonJuryWeightBonusPlural = { + 6: 'Este caso é difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', + 7: 'Este caso é muito difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', + 8: 'Este caso é o mais difícil. Você sentou %d jurados Toon, então a sua evidência tem um peso-bônus de %d.', +} + +# Cog Summons stuff +IssueSummons = "Evocar" +SummonDlgTitle = "Evocar um Cog" +SummonDlgButton1 = "Evocar um Cog" +SummonDlgButton2 = "Evocar um Prédio Cog" +SummonDlgButton3 = "Evocar uma Invasão Cog" +SummonDlgSingleConf = "Gostaria de evocar um %s?" +SummonDlgBuildingConf = "Gostaria de evocar um %s para um prédio Toon próximo?" +SummonDlgInvasionConf = "Gostaria de evocar uma invasão de %s?" +SummonDlgNumLeft = "Você tem %s sobrando." +SummonDlgDelivering = "Evocando..." +SummonDlgSingleSuccess = "Você evocou o Cog com sucesso." +SummonDlgSingleBadLoc = "Desculpe, mas cogs são proibidos aqui. Tente em outro lugar." +SummonDlgBldgSuccess = "Você evocou os Cogs com sucesso. %s concordou em deixá-los tomar %s por um tempo!" +SummonDlgBldgSuccess2 = "Você evocou os Cogs com sucesso. Um Dono de Loja concordou em deixá-los tomar o prédio dele por um tempo!" +SummonDlgBldgBadLoc = "Desculpe, não há prédios Toon por perto para os Cogs tomarem." +SummonDlgInvasionSuccess = "Você evocou os Cogs com sucesso. É uma invasão!" +SummonDlgInvasionBusy = "Um %s não pôde ser encontrado. Tente novamente quando a invasão dos Cogs terminar." +SummonDlgInvasionFail = "Desculpe, a invasão dos Cogs fracassou." +SummonDlgShopkeeper = "O Dono da Loja " + +# Polar Place cheesy effect chat phrases +PolarPlaceEffect1 = NPCToonNames[3306] + ": Bem-vindo ao Lugar Polar!" +PolarPlaceEffect2 = NPCToonNames[3306] + ": Tente isto." +PolarPlaceEffect3 = NPCToonNames[3306] + ": A sua nova aparência só vai funcionar em " + lTheBrrrgh + "." + +# LaserGrid game Labels +LaserGameMine = "Caça-Caveiras!" +LaserGameRoll = "Combinando" +LaserGameAvoid = "Evite as Caveiras" +LaserGameDrag = "Arraste três da mesma cor em uma fileira" +LaserGameDefault = "Jogo Desconhecido" + +# Pinball text +#PinballHiScore = "Maior Pontuação: %d %s\n" +#PinballYourBestScore = "Sua Melhor Pontuação: %d\n" +#PinballScore = "Pontuação: %d x %d : %d" +PinballHiScore = "Maior Pontuação: %s\n" +PinballHiScoreAbbrev = "..." +PinballYourBestScore = "Sua Melhor Pontuação:\n" +PinballScore = "Pontuação: %d x %d = " +PinballScoreHolder = "%s\n" + + +# Gardening text +GagTreeFeather = "Árvore de Piada de Pena" +GagTreeJugglingBalls = "Árvore de Piada de Bolinhas de Malabarismo" +StatuaryFountain = "Fonte" +StatuaryToonStatue = "Estátua de Toon" +StatuaryDonald = "Estátua do Donald" +StatuaryMinnie = "Estátua da Minnie" +StatuaryMickey1 = "Estátua do Mickey" +StatuaryMickey2 = "Fonte do Mickey" + +StatuaryGardenAccelerator = "Fertilizante Instantâneo" +#see GardenGlobals.py for corresponding FlowerColors +FlowerColorStrings = ['Vermelha','Laranja','Violeta','Azul','Rosa','Amarela','Branca','Verde'] +#see GardenGlobals.py for PlantAttributes, keys must match +FlowerSpeciesNames = { + 49: 'Margarida', + 50: 'Tulipa', + 51: 'Cravo', + 52: 'Lírio', + 53: 'Narciso', + 54: 'Tilápia', + 55: 'Petúnia', + 56: 'Rosa', + } +#see GardenGlobals.py for PlantAttributes, keys must match, varieties must match +FlowerFunnyNames = { + 49: ('Margarida Lida', + 'Margarida Sumida', + 'Margarida Querida', + 'Margarida Lambida', + 'Margarida Caída', + 'Margarida Subida', + 'Margarida Enlouquecida', + 'Margarida Esclarecida', + ), + 50: ('Eulipa', + 'Tulipas', + 'Elelipa', + ), + 51: ('Encravou', + 'Cravado', + 'Cravo Híbrido', + 'Cravo de Lado', + 'Cravo Modelo', + ), + 52: ('De Lírio', + 'Co Lírio', + 'Lírio Selvagem', + 'Lírio Figueiro', + 'Lírio Pimenta', + 'Lírio Bobo', + 'Eclírio', + 'Lírio Dílio', + ), + 53: ('Nar-sorriso', + 'Nariz Ciso', + 'Narcisudo', + 'Ante Narciso', + ), + 54: ('Tilápia Pudo', + 'Ene-A-O-Tilápia', + 'Tilapiano', + 'Tilapiada', + 'Tilápia Sábia', + ), + 55: ('Car Petúnia', + 'Platúnia', + ), + 56: ("Última Rosa do Verão", + 'Choque de Rosa', + 'Rosa Tinta', + 'Rosa Fedida', + 'Rosa Aindarrosa', + ), + } +FlowerVarietyNameFormat = "%s %s" +FlowerUnknown = "????" +ShovelNameDict = { + 0 : "Latão", + 1 : "Bronze", + 2 : "Prata", + 3 : "Ouro", + } +WateringCanNameDict = { + 0 : "Pequeno", + 1 : "Médio", + 2 : "Grande", + 3 : "Enorme", + } +GardeningPlant = "Plantar" +GardeningWater = "Regar" +GardeningRemove = "Remover" +GardeningPick = "Colher" +GardeningSkill = "Habilidade" +GardeningWaterSkill = "Habilidade na Água" +GardeningShovelSkill = "Habilidade com a Pá" +GardeningNoSkill = "Nenhuma Habilidade" +GardeningPlantFlower = "Plantar\nFlor" +GardeningPlantTree = "Plantar\nÁrvore" +GardeningPlantItem = "Plantar\nItem" +PlantingGuiOk = "Plantar" +PlantingGuiCancel = lCancel +PlantingGuiReset = "Restaurar" +GardeningChooseBeans = "Escolha as balinhas que deseja plantar." +GardeningChooseBeansItem = "Escolha as balinhas / item que deseja plantar." +GardenShovelLevelUp = "Parabéns, você ganhou uma nova pá!" +GardenShovelSkillLevelUp = "Parabéns! Você atingiu %(oldbeans)d violetas! Para progredir, você deve coletar %(newbeans)d violetas." +GardenShovelSkillMaxed = "Incrível! Você superou sua habilidade com a pá!" +GardenWateringCanLevelUp = "Parabéns, você ganhou um novo regador!" +GardenMiniGameWon = "Parabéns, você regou a planta!" +ShovelTin = "Pá de Latão" +ShovelSteel = "Pá de Aço" +ShovelSilver = "Pá de Prata" +ShovelGold = "Pá de Ouro" +WateringCanSmall = "Regador Pequeno" +WateringCanMedium = "Regador Médio" +WateringCanLarge = "Regador Grande" +WateringCanHuge = "Regador Enorme" +#make sure it matches GardenGlobals.BeanColorLetters +BeanColorWords = ('vermelha', 'verde', 'laranja','lilás','azul','rosa','amarela', + 'ciano','prata') +PlantItWith = " Plante com %s." +MakeSureWatered = " Primeiramente, certifique-se de que todas as plantas foram regadas." +UseFromSpecialsTab = "Use por meio da guia de especiais na página do jardim." +UseSpecial = "Usar Especial" +UseSpecialBadLocation = 'Você só pode usar isso no seu jardim.' +UseSpecialSuccess = 'Sucesso! Suas plantas regadas acabaram de crescer.' +ConfirmWiltedFlower = "%(plant)s murchou. Tem certeza de que deseja removê-la? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmUnbloomingFlower = "%(plant)s não está desabrochando. Tem certeza de que deseja removê-la? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmNoSkillupFlower = "Tem certeza de que deseja remover %(plant)s? Ela não irá para o seu cesto de flores, e você também não receberá aumento na sua habilidade." +ConfirmSkillupFlower = "Tem certeza de que deseja colher %(plant)s? Ela irá para o seu cesto de flores. Você vai receber um aumento de habilidade." +ConfirmMaxedSkillFlower = "Tem certeza que quer colher as %(plant)s? Elas irão para sua cesta de flores. Suas habilidades NÃO aumentarão pois você já atingiu o máximo." + +ConfirmBasketFull = "Seu cesto de flores está cheio. Venda algumas flores primeiro." +ConfirmRemoveTree = "Tem certeza de que deseja remover %(tree)s?" +ConfirmWontBeAbleToHarvest = " Se você remover esta árvore, você não colherá piadas das árvores mais altas." +ConfirmRemoveStatuary = "Tem certeza de que deseja apagar para sempre %(item)s?" +ResultPlantedSomething = "Parabéns! Você acaba de plantar %s." +ResultPlantedNothing = "Isso não funcionou. Por favor, tente uma combinação diferente de balinhas." + +GardenGagTree = "TODO??? " +GardenUberGag = "TODO??? " + +def getRecipeBeanText(beanTuple): + """ + given a bean tuple, e.g (0,6) return a text version of it to + be displayed to the user. e.g( a red and yellow jellybean) + """ + #first check if all the beans are the same, so we can say something + #like 7 red jellybeans + retval = "" + if not beanTuple: + return retval + allTheSame = True + for index in range(len( beanTuple)): + if index + 1 < len(beanTuple): + if not beanTuple[index] == beanTuple[index+1]: + allTheSame = False + break + + if allTheSame: + if len(beanTuple) > 1: + retval = "%d %s balinhas" % (len(beanTuple), + BeanColorWords[beanTuple[0]]) + else: + retval = "uma balinha %s" % BeanColorWords[beanTuple[0]] + else: + retval += 'a' + maxBeans = len(beanTuple) + for index in range(maxBeans): + if index == maxBeans - 1: + retval += " e balinha %s" % BeanColorWords[beanTuple[index]] + elif index == 0: + retval += " %s" % BeanColorWords[beanTuple[index]] + else: + retval += ", %s" % BeanColorWords[beanTuple[index]] + + return retval + +GardenTextMagicBeans = "Balas Mágicas" +GardenTextMagicBeansB = "Outras Balas" +GardenSpecialDiscription = "Este texto deveria explicar como usar certo especial do jardim" +GardenSpecialDiscriptionB = "Este texto deveria explicar como usar certo especial do jardim, podicrê!" +GardenTrophyAwarded = "Uau! Você tem %s de %s flores. Isso merece um troféu e uma melhora na Risada!" +GardenTrophyNameDict = { + 0 : "Carrinho de Mão", + 1 : "Pás", + 2 : "Flor", + 3 : "Regador", + 4 : "Tubarão", + 5 : "Peixe-Espada", + 6 : "Baleia Assassina", + } +SkillTooLow = "Habilidade\nBaixa Demais" +NoGarden = "Nenhum \nJardim" + diff --git a/toontown/src/toonbase/portuguese/TTLocalizer_Property.py b/toontown/src/toonbase/portuguese/TTLocalizer_Property.py new file mode 100644 index 0000000..27dee22 --- /dev/null +++ b/toontown/src/toonbase/portuguese/TTLocalizer_Property.py @@ -0,0 +1,305 @@ +#avatar/AvatarPanel.py +APfriendButton = 0.042 +APwhisperButton = 0.042 +APsecretsButton = 0.042 +APgoToButton = 0.042 +APignoreButton = 0.042 + +#battle/PlayByPlayText.py +PBPTonscreenText = 0.2 + +#battle/RewardPanel.py +RPdirectFrame = (1.95,1,0.75) +RPtrackLabels = 0.05 +RPmeritBarLabels = 0.15 + +#building/DistributedHQInterior.py +DHtoonName = 0.75 +DHtoonNamePos = (-6, 0, 0) +DHscorePos = (-6.6, 0, 0) +DHtrophyPos = (-8.6, 0, 0.3) + +#building/Elevator.py +EelevatorHopOff = 0.7 + +#catalog/CatalogChatItemPicker.py +CCIPmessagePickerCancel = 0.06 + +#catalog/CatalogGenerator.py +CGvalentine_month = 6 +CGvalentine_startday = 5 +CGvalentine_endday = 12 + +#catalog/CatalogScreen.py +CSgiftTogglePos = (00.855, -0.10) +CSgiftToggle = 0.07 + +#chat/TTChatInputSpeedChat.py +CISCspeedChat = 0.048 +CISCtopLevelOverlap = 0.08 + +#chat/ToontownChatManager.py +CMnormalButton = 0.06 +CMscButtonPos = (-1.129, 0, 0.928) +CMscButton = 0.06 +CMwhisperFrame = 0.06 +CMwhisperButton = 0.05 +CMunpaidChatWarningwordwrap = 18 +CMunpaidChatWarning = 0.06 +CMunpaidChatWarning_text_y = 0.27 +CMpayButton = 0.05 +CMpayButton_pos_y = -0.10 +CMopenChatWarning = 0.05 +CMactivateChat = 0.05 +CMchatActivated = 0.05 +CMNoPasswordContinue_y = -0.25 + +#coghq/SellbotCogHQLoader.py +SCLfdSign = 0.12 +SCLdgSign = 0.1 + +#coghq/DistributedFactory.py +DFfactoryRoomTitle = 1 + +#coghq/DistributedMintElevatorExt.py +DMEEsignText = 1.5 + +#estate/houseDesign.py +HDhelpText = 0.55 +HDatticButton = 0.6 +HDroomButton = 0.7 +HDtrashButton = 0.7 +HDscrolledList = 0.07 + +#fishing/BingoCardGui.py +BCGjpText = (0.04) +BCGjpTextWordwrap = 10.5 +BCGnextGame = 1.71 + +#fishing/FishSellGUI.py +FSGokButton = 0.05 +FSGcancelButton = 0.05 + +#fishing/FishPanel.py +FPnewEntry = 0.06 +FPnewRecord = 0.06 + +#fishing/GenusPanel.py +GPgenus = 0.045 + +#friends/FriendsListPanel.py +FLPnewFriend = 0.04 +FLPsecrets = 0.04 +FLPsecretsPos = (0.125, 0.0, 0.14) + +#friends/FriendInviter.py +FIstopButton = 0.042 +FIdialog = 0.05 + +#hood/EstateHood.py +EHpopupInfo = 0.08 +#hood/Hood.py +Hhoodtext_scale = 0.13 + +#login/AvatarChoice.py +ACplayThisToon = 0.09 +ACmakeAToon = 0.11 +ACsubscribersOnly = 0.115 +ACdeleteWithPassword = 0.06 + +#login/AvatarChooser.py +ACtitle = 0.105 +ACquitButton = 0.07 +AClogoutButton = 0.08 +ACquitButton_pos = -0.024 + +#minigame/MinigameAvatarScorePanel.py +MASPscoreText = 0.07 +MASPnameText = 0.04 + +#minigame/MinigameRulesPanel.py +MRPplayButton = 0.040 +MRPinstructionsText = 0.07 + +#minigame/MinigamePowerMeter.py +MPMpowerText = 0.05 +MPMtooSlow = 0.05 +MPMtooFast = 0.05 +MPMgaugeA = .3 +MPMgaugeTargetTop = .3 +MPMgaugeTargetBot = .3 + +#minigame/Purchase.py +PstatusLabel = 0.08 + +#minigame/PurchaseBase.py +PBstatusLabel = 0.07 + +#makeatoon/NameShop.py +NSmaxNameWidth = 10 +NSdirectScrolleList = 0.1 +NSmakeLabel = 0.07 +NSmakeCheckBox = 0.7 +NSnameEntry = 0.08 +NStypeANameButton = 0.05 +NStypeANameButton_pos = -0.01 +NSnameResult = 0.065 +NStypeName = 0.1 +NSnewName = 0.1 +NScolorPrecede = True + +#makeatoon/MakeAToon.py +MATenterGenderShop = 0.14 +MATenterBodyShop = 0.14 +MATenterColorShop = 0.14 +MATenterClothesShop = 0.12 +MATenterNameShop = 0.11 +MATclothesGUIshirt_scale = 0.06 +MATclothesGUIshirt_posL = 0.010 +MATclothesGUIshirt_posR = -0.014 +MATnextButtonScale = 0.07 + +#pets/PetAvatarPanel.py & town/TownBattleSOSPetInfoPanel.py +PAPfeed = 0.5 +PAPcall = 0.5 +PAPowner = 0.35 +PAPscratch = 0.4 +PAPstateLabel = 0.4 +PAPstateLabelPos = (0.7, 0, 3.5) +PAPstateLabelwordwrap = 7.5 + +#pets/PetDetailPanel.py +PDPtrickText = 0.13 +PDPlaff = 0.13 +PDPlaffPos = (-0.2,-0.05) + +#pets/PetshopGUI.py +PGUItextScale = 0.7 +PGUIchooserTitle = 0.09 +PGUIwordwrap = 14 +PGUIdescLabel = 0.9 +PGUIreturnConfirm = 0.05 +PGUIpetsopAdopt = 0.4 +PGUIadoptSubmit = 0.6 +PGUIpetsopAdoptPos = (-0.13,1.05) +PGUIpetshopCancelPos = (-2.6, 2.95) +PGUIcharLength = 1 # 1 for one byte code 3 for two byte code + +#pets/PetTutorial +PTtitle = 0.11 +PTtitleScale1 = 0.11 +PTtitleScale2 = 0.09 +PTtitleScale3 = 0.10 +PTpage1Pos = (0.15, 0.13) +PTpage2Pos = (-0.27, 0.20) +PTpage3Pos = (0.15, 0.13) + +#quest/QuestPoster.py +QPauxText = 0.035 +QPtextScale = 0.032 +QPtextWordwrap = 21.5 +QPinfoZ = -0.0825 + +#race/DistributedRace.py +DRenterWaiting = .15 +DRrollScale = 0.3 + +#race/DistributedRacePad.py +DRPnodeScale = 0.65 + +#race/KartShopGui.py +KSGtextSizeBig = 0.06 +KSGtextSizeSmall = 0.04 +KSGaccDescriptionWordwrap = 22 + +#race/RaceEndPanels.py +REPtLabel_x = 0.95 +REPwLabel_x = 1.34 +REPraceEnd = 0.07 +REPraceExit = 0.04 +REPticket_text_x = -0.7 + +#racing/RaceGUI.py +RGphotoFinish = 0.20 +RGplaceLabelNumPos = (-1.2,0,-0.97) +RGplaceLabelStrPos = (-1.05,0.0,-0.8) + +#safezone/DistributedFishingSpot.py +DFSfailureDialog = 0.05 +DFSfailureDialog_pos = (-.35,.10) + +#safezone/Playground.py +PimgLabel = 0.6 +PimgScale = 0.6 + +#shtiker/FishPage.py +FPtankTab = 0.07 +FPcollectionTab = 0.07 +FPtrophyTab = 0.07 + +#shtiker/DisplaySettingsDialog.py +DSDintroText = 0.06 +DSDintroTextwordwrap = 25 +DSDwindowedButtonPos = (0.0961, 0, -0.221) +DSDfullscreenButtonPos = (0.097, 0, -0.311) +DSDcancel = 0.06 + +#shtiker/DisguisePage.py +DPtab = 0.065 +DPdeptLabel = 0.13 +DPcogName = 0.083 + +#shtiker/TrackPage.py +TPstartFrame = 0.10 +TPendFrame = 0.10 + +#shtiker/ShtikerBook.py +SBpageTab = 0.55 + +#shtiker/MapPage.py +MPbackToPlayground = 0.050 +MPgoHome = 0.050 +MPhoodLabel = 0.05 +MPhoodWordwrap = 16 + +#shtiker/KartPage.py +KPkartTab = 0.06 +KPdeleteButton = 0.035 +KProtateButton = 0.03 + +#toon/ToonAvatarPanel.py +TAPfriendButton = 0.042 +TAPwhisperButton = 0.042 +TAPsecretsButton = 0.042 +TAPgoToButton = 0.042 +TAPignoreButton = 0.042 +TAPpetButton = 0.26 +TAPdetailButton = 0.04 + +#toon/ToonAvatarDetailPanel.py +TADPtrackLabel = 0.042 +TADPcancelButton = 0.035 + +#toon/InventoryNew.py +INtrackNameLabels = 0.043 +INclickToAttack = 0.75 +INpassButton = 0.032 +INrunButton = 0.045 +INdetailNameLabel = 1.0 + +#toon/NPCForceAcknowledge.py +NPCFimgLabel = 1.0 + +#toontowngui/ToontownLoadingScreen.py +TLStip = 0.15 + +#toontowngui/TeaserPanel.py +TPtop = 0.067 +TPpanel = 0.047 +TPbutton = 0.045 + +#town/TownBattleSOSPetSearchPanel.py +TBPSpanel = 0.08 + +#trolley/Trolley.py +TtrolleyHopOff = 0.7 diff --git a/toontown/src/toonbase/portuguese/__init__.py b/toontown/src/toonbase/portuguese/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/toonbase/test.py b/toontown/src/toonbase/test.py new file mode 100644 index 0000000..aa3a3ca --- /dev/null +++ b/toontown/src/toonbase/test.py @@ -0,0 +1,208 @@ +# +#imports +# +from pandac.PandaModules import * +from otp.avatar import Avatar +from otp.avatar import AvatarDNA +from direct.task import Task +from direct.showbase.MessengerGlobal import * +import sys +from pandac.PandaModules import ClockObject +from pandac.PandaModules import PStatClient +from ChatManagerGlobal import * +# +# globals +# +connected = 0 +pStats = PStatClient().getGlobalPstats() +startTime = 0.0 +startFrameCount = 0 +globalClock = ClockObject.getGlobalClock() + +# +# viewpoint list +# +vpl = ( + (Point3(-10.5, 20.3, 5.6), VBase3(-153., -14., 0.)), + (Point3(-45., -0.3, -0.8), VBase3(-90., -4., 0.)), + (Point3(-74.4, -46.4, 6.5), VBase3(-58., -5.8, 0.)), + (Point3(127.7, -74., 5.), VBase3(60.5, -4.6, 0.)), + (Point3(66.3, 62.7, 6.95), VBase3(83.0, 0., 0.)) + ) +# +# load the environment +# +tt = loader.loadModel("phase_4/models/neighborhoods/toontown_central") +tt.reparentTo(render) +# +# load the avatars +# +dna1 = AvatarDNA.AvatarDNA() +dna1.newToon(("dll", "md", "l", "m"), 0.3, 0.2, 0.4) +av1 = Avatar.Avatar() +av1.setDNA(dna1) +av1.setPos(-3.0, 0.0, 1.75) +av1.setH(-90.0) +av1.loop("neutral") +av1.reparentTo(render) +dna2 = AvatarDNA.AvatarDNA() +dna2.newToon(("css", "ss", "s", "f"), 0.4, 0.2, 0.2) +av2 = Avatar.Avatar() +av2.setDNA(dna2) +av2.setPos(3.0, 0.0, 1.75) +av2.setH(90.0) +av2.loop("neutral") +av2.reparentTo(render) +# +# keyboard handling routines +# +# +# "ESC" - exit +# +def handleEscKey(): + if (pStats.isConnected()): + print "disconnecting from PStatClient" + pStats.disconnect() + print "bye!" + sys.exit() +# +# "1" - lerp to viewpoint #1 +# +def handle1Key(): + base.disableMouse() + camera.setPosHpr(vpl[0][0], vpl[0][1]) + print "viewpoint 1" +# +# "2" - lerp to viewpoint #2 +# +def handle2Key(): + base.disableMouse() + camera.setPosHpr(vpl[1][0], vpl[1][1]) + print "viewpoint 2" +# +# "3" - lerp to viewpoint #3 +# +def handle3Key(): + base.disableMouse() + camera.setPosHpr(vpl[2][0], vpl[2][1]) + print "viewpoint 3" +# +# "4" - lerp to viewpoint #4 +# +def handle4Key(): + base.disableMouse() + camera.setPosHpr(vpl[3][0], vpl[3][1]) + print "viewpoint 4" +# +# "5" - lerp to viewpoint #4 +# +def handle5Key(): + base.disableMouse() + camera.setPosHpr(vpl[4][0], vpl[4][1]) + print "viewpoint 5" +# +# "t" - trackball mode +# +def handleTKey(): + print "using trackball mode..." + base.useTrackball() +# +# "m" - mouse mode +# +def handleDKey(): + print "using drive mode..." + base.useDrive() +# +# "p" - print the camera position +# +def handlePKey(): + print "camera pos:" + camera.printPos() + camera.printHpr() +# +# "s" - toggle connection to stats +# +def handleSKey(): + if (pStats.isConnected()): + print "disconnecting from PStatClient" + pStats.disconnect() + else: + print "connecting to PStatClient" + pStats.connect() +# +# "f" - print the frame rate +# +def handleFKey(): + global startTime + global frameCount + time = globalClock.getFrameTime() + dt = time - startTime + frameCount = globalClock.getFrameCount() + df = frameCount - startFrameCount + if (df > 0): + print df, " frames in ", dt, "seconds" + print df/dt, " fps avg. (", 1000.0/(df/dt), "ms)" +# +# "a" - automatically lerp through viewpoint list +# +def handleAKey(): + print "starting viewpoint lerps..." + base.disableMouse() + camera.setPosHpr(vpl[0][0], vpl[0][1]) + lerpTimeline = Task.timeline( + (2.0, + camera.lerpPosHpr(vpl[1][0], vpl[1][1], 1.0), + "lerp1"), + (5.0, + camera.lerpPosHpr(vpl[2][0],vpl[2][1], 2.0), + "lerp2"), + (9.0, + camera.lerpPosHpr(vpl[3][0], vpl[3][1], 3.0), + "lerp3"), + (14.0, + camera.lerpPosHpr(vpl[4][0], vpl[4][1], 4.0), + "lerp4")) + + taskMgr.add(lerpTimeline, "lerpTimeline") + + +# +# spawn events to look for keyStrokes +# +messenger.accept("a-up", 1, handleAKey, [], 1) +messenger.accept("f-up", 1, handleFKey, [], 1) +messenger.accept("d-up", 1, handleDKey, [], 1) +messenger.accept("t-up", 1, handleTKey, [], 1) +messenger.accept("s-up", 1, handleSKey, [], 1) +messenger.accept("d-up", 1, handleDKey, [], 1) +messenger.accept("p-up", 1, handlePKey, [], 1) +messenger.accept("1-up", 1, handle1Key, [], 1) +messenger.accept("2-up", 1, handle2Key, [], 1) +messenger.accept("3-up", 1, handle3Key, [], 1) +messenger.accept("4-up", 1, handle4Key, [], 1) +messenger.accept("5-up", 1, handle5Key, [], 1) +messenger.accept("escape-up", 1, handleEscKey, [], 1) + + +# start the igLoop +base.disableMouse() +camera.setPosHpr(vpl[0][0], vpl[0][1]) +chatMgr.stop() +globalClock.tick() +startTime = globalClock.getFrameTime() +startFrameCount = globalClock.getFrameCount() +run() + + + + + + + + + + + + + + diff --git a/toontown/src/toonbase/test2.py b/toontown/src/toonbase/test2.py new file mode 100644 index 0000000..4e9d862 --- /dev/null +++ b/toontown/src/toonbase/test2.py @@ -0,0 +1,222 @@ +# +#imports +# +from pandac.PandaModules import * +from otp.avatar import Avatar +from otp.avatar import AvatarDNA +from direct.task import Task +from pandac.PandaModules import ClockObject +from direct.showbase.MessengerGlobal import * +import sys +from pandac.PandaModules import PStatClient + +from direct.showbase import ShowBase +from ChatManagerGlobal import * + +# +# globals +# +connected = 0 +pStats = PStatClient.getGlobalPstats() +startTime = 0.0 +startFrameCount = 0 +globalClock = ClockObject.getGlobalClock() + +# +# viewpoint list +# +vpl = ( + (Point3(-10.5, 20.3, 5.6), VBase3(-153., -14., 0.)), + (Point3(-45., -0.3, -0.8), VBase3(-90., -4., 0.)), + (Point3(-74.4, -46.4, 6.5), VBase3(-58., -5.8, 0.)), + (Point3(127.7, -74., 5.), VBase3(60.5, -4.6, 0.)), + (Point3(66.3, 62.7, 6.95), VBase3(83.0, 0., 0.)) + ) +# +# load the environment +# +tt = loader.loadModel("phase_4/models/neighborhoods/toontown_central") +tt.reparentTo(render) +# +# load the avatars +# +dna1 = AvatarDNA.AvatarDNA() +dna1.newToon(("dll", "md", "l", "m"), 0.3, 0.2, 0.4 ) +av1 = Avatar.Avatar() +av1.setDNA(dna1) +av1.setPos(-3.0, 0.0, 1.75) +av1.setH(-90.0) +av1.loop("neutral") +av1.reparentTo(render) +dna2 = AvatarDNA.AvatarDNA() +dna2.newToon( ("css", "ss", "s", "f"), 0.4, 0.2, 0.2 ) +av2 = Avatar.Avatar() +av2.setDNA(dna2) +av2.setPos(3.0, 0.0, 1.75) +av2.setH(90.0) +av2.loop("neutral") +av2.reparentTo(render) +# +# keyboard handling routines +# +# +# "ESC" - exit +# +def handleEscKey(): + if (pStats.isConnected()): + print "disconnecting from PStatClient" + pStats.disconnect() + print "bye!" + sys.exit() +# +# "1" - lerp to viewpoint #1 +# +def handle1Key(): + base.disableMouse() + camera.setPosHpr(vpl[0][0], vpl[0][1]) + print "viewpoint 1" +# +# "2" - lerp to viewpoint #2 +# +def handle2Key(): + base.disableMouse() + camera.setPosHpr(vpl[1][0], vpl[1][1]) + print "viewpoint 2" +# +# "3" - lerp to viewpoint #3 +# +def handle3Key(): + base.disableMouse() + camera.setPosHpr(vpl[2][0], vpl[2][1]) + print "viewpoint 3" +# +# "4" - lerp to viewpoint #4 +# +def handle4Key(): + base.disableMouse() + camera.setPosHpr(vpl[3][0], vpl[3][1]) + print "viewpoint 4" +# +# "5" - lerp to viewpoint #4 +# +def handle5Key(): + base.disableMouse() + camera.setPosHpr(vpl[4][0], vpl[4][1]) + print "viewpoint 5" +# +# "t" - trackball mode +# +def handleRKey(): + print "using trackball mode..." + base.useTrackball() +# +# "m" - mouse mode +# +def handleDKey(): + print "using drive mode..." + base.useDrive() + +def handleCKey(): + print "printing camera data (Pos, then Hpr)..." + camera.printPos() + camera.printHpr() + +# +# "p" - print the camera position +# +def handlePKey(): + print "camera pos:" + camera.printPos() + camera.printHpr() +# +# "s" - toggle connection to stats +# +def handleSKey(): + if (pStats.isConnected()): + print "disconnecting from PStatClient" + pStats.disconnect() + else: + print "connecting to PStatClient" + pStats.connect() + +def handleWKey(): + base.toggleWireframe() + +def handleTKey(): + base.toggleTexture() + +def handleEKey(): + base.toggleBackface() + +# +# "f" - print the frame rate +# +def handleFKey(): + global startTime + global startFrameCount + time = globalClock.getFrameTime() + dt = time - startTime + frameCount = globalClock.getFrameCount() + df = frameCount - startFrameCount + if (df > 0): + print df, " frames in ", dt, "seconds" + print df/dt, " fps avg. (", 1000.0/(df/dt), "ms)" + startTime = time + startFrameCount = frameCount + +# +# "a" - automatically lerp through viewpoint list +# +def handleAKey(): + print "starting viewpoint lerps..." + base.disableMouse() + camera.setPosHpr(vpl[0][0], vpl[0][1]) + lerpTimeline = Task.timeline( + (2.0, + camera.lerpPosHpr(vpl[1][0], vpl[1][1], 1.0), + "lerp1"), + (5.0, + camera.lerpPosHpr(vpl[2][0],vpl[2][1], 2.0), + "lerp2"), + (9.0, + camera.lerpPosHpr(vpl[3][0], vpl[3][1], 3.0), + "lerp3"), + (14.0, + camera.lerpPosHpr(vpl[4][0], vpl[4][1], 4.0), + "lerp4")) + + taskMgr.add(lerpTimeline, "lerpTimeline") + +# +# spawn events to look for keyStrokes +# +messenger.accept("a-up", 1, handleAKey) +messenger.accept("f-up", 1, handleFKey) +messenger.accept("d-up", 1, handleDKey) +messenger.accept("t-up", 1, handleTKey) +messenger.accept("s-up", 1, handleSKey) +messenger.accept("d-up", 1, handleDKey) +messenger.accept("p-up", 1, handlePKey) +messenger.accept("c-up", 1, handleCKey) +messenger.accept("w-up", 1, handleWKey) +messenger.accept("e-up", 1, handleEKey) +messenger.accept("r-up", 1, handleRKey) +messenger.accept("1-up", 1, handle1Key) +messenger.accept("2-up", 1, handle2Key) +messenger.accept("3-up", 1, handle3Key) +messenger.accept("4-up", 1, handle4Key) +messenger.accept("5-up", 1, handle5Key) +messenger.accept("escape-up", 1, handleEscKey) + +# start the igLoop +# uncomment disable mouse to get good initial viewpoint +# base.disableMouse() +chatMgr.stop() +globalClock.tick() +camera.setPosHpr(vpl[0][0], vpl[0][1]) +run() + + + + + diff --git a/toontown/src/toontownbase/.cvsignore b/toontown/src/toontownbase/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/toontownbase/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/toontownbase/Sources.pp b/toontown/src/toontownbase/Sources.pp new file mode 100644 index 0000000..d3995ad --- /dev/null +++ b/toontown/src/toontownbase/Sources.pp @@ -0,0 +1,16 @@ +#define USE_PACKAGES cg // from gobj. + +#begin lib_target + #define TARGET toontownbase + + #define OTHER_LIBS \ + dtool:m dtoolconfig:m \ + prc:c dtoolutil:c dtoolbase:c + + #define SOURCES \ + toontownbase.cxx toontownbase.h toontownsymbols.h \ + + #define INSTALL_HEADERS \ + toontownbase.h toontownsymbols.h + +#end lib_target diff --git a/toontown/src/toontownbase/toontownbase.cxx b/toontown/src/toontownbase/toontownbase.cxx new file mode 100644 index 0000000..313ca77 --- /dev/null +++ b/toontown/src/toontownbase/toontownbase.cxx @@ -0,0 +1,6 @@ +// Filename: toontownbase.cxx +// Created by: drose (15Sep00) +// +//////////////////////////////////////////////////////////////////// + +#include "toontownbase.h" diff --git a/toontown/src/toontownbase/toontownbase.h b/toontown/src/toontownbase/toontownbase.h new file mode 100644 index 0000000..2c9ebfc --- /dev/null +++ b/toontown/src/toontownbase/toontownbase.h @@ -0,0 +1,17 @@ +// Filename: toontownbase.h +// Created by: drose (12Sep00) +/////////////////////////////////////// + + +/* This file is included at the beginning of every header file and/or + C or C++ file. It must be compilable for C as well as C++ files, + so no C++-specific code or syntax can be put here. */ + +#ifndef TOONTOWNBASE_H +#define TOONTOWNBASE_H + +#include "pandabase.h" +#include "toontownsymbols.h" + +#endif + diff --git a/toontown/src/toontownbase/toontownsymbols.h b/toontown/src/toontownbase/toontownsymbols.h new file mode 100644 index 0000000..bc64fe5 --- /dev/null +++ b/toontown/src/toontownbase/toontownsymbols.h @@ -0,0 +1,27 @@ +// Filename: toontownsymbols.h +// Created by: drose (18Feb00) +///////////////////////////////////////////// + +#ifndef TOONTOWNSYMBOLS_H +#define TOONTOWNSYMBOLS_H + +/* See dtoolsymbols.h for a rant on the purpose of this file. */ + +#if defined(WIN32_VC) && !defined(CPPPARSER) + +#ifdef BUILDING_TOONTOWN + #define EXPCL_TOONTOWN __declspec(dllexport) + #define EXPTP_TOONTOWN +#else + #define EXPCL_TOONTOWN __declspec(dllimport) + #define EXPTP_TOONTOWN extern +#endif + +#else /* !WIN32_VC */ + +#define EXPCL_TOONTOWN +#define EXPTP_TOONTOWN + +#endif /* WIN32_VC */ + +#endif diff --git a/toontown/src/toontowngui/.cvsignore b/toontown/src/toontowngui/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/toontowngui/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/toontowngui/NewsPageButtonManager.py b/toontown/src/toontowngui/NewsPageButtonManager.py new file mode 100644 index 0000000..74068cd --- /dev/null +++ b/toontown/src/toontowngui/NewsPageButtonManager.py @@ -0,0 +1,347 @@ +from pandac.PandaModules import VBase4, VBase3 +from direct.fsm import FSM +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectButton import DirectButton +from toontown.toonbase import ToontownGlobals +from direct.interval.IntervalGlobal import * +from toontown.toonbase import TTLocalizer + +class NewsPageButtonManager (FSM.FSM): + """This will control which button shows up in the HUD, the Goto News, Goto Prev Page, or Goto 3d World.""" + notify = DirectNotifyGlobal.directNotify.newCategory("NewsPageButtonManager") + + def __init__(self): + """Create the buttons.""" + FSM.FSM.__init__(self,"NewsPageButtonManager") + self.buttonsLoaded = False + self.goingToNewsPageFrom3dWorld = False + self.goingToNewsPageFromStickerBook = False + self.__blinkIval = None + + self.load() + +## if not launcher.getPhaseComplete(5.5): +## # We haven't downloaded phase 5.5 yet; set a callback hook +## # so the pages will load when we do get phase 5.5. +## self.acceptOnce('phaseComplete-5.5', self.delayedLoadPhase55Stuff) +## return +## else: +## self.loadPhase55Stuff() +## +## def delayedLoadPhase55Stuff(self): +## """Load the buttons, and then show the appropriate button.""" +## # we've just finished downloading phase 55 +## self.loadPhase55Stuff() +## self.showAppropriateButton() + + def load(self): + """ + We're now loading the assets from phase 3.5. + """ + btnGui = loader.loadModel('phase_3.5/models/gui/tt_m_gui_ign_newsBtnGui') + self.openNewNewsUp = btnGui.find('**/tt_t_gui_ign_new') + self.openNewNewsUpBlink = btnGui.find('**/tt_t_gui_ign_newBlink') + self.openNewNewsHover = btnGui.find('**/tt_t_gui_ign_newHover') + self.openOldNewsUp = btnGui.find('**/tt_t_gui_ign_oldNews') + self.openOldNewsHover = btnGui.find('**/tt_t_gui_ign_oldHover') + self.closeNewsUp = btnGui.find('**/tt_t_gui_ign_open') + self.closeNewsHover = btnGui.find('**/tt_t_gui_ign_closeHover') + btnGui.removeNode() + + oldScale = 0.5 + newScale = 0.9 + newPos = VBase3(0.914, 0, 0.862) + textScale = 0.06 + self.gotoNewsButton = DirectButton( + relief = None, + image = (self.openOldNewsUp, self.openOldNewsHover, self.openOldNewsHover), + text = ('', TTLocalizer.EventsPageNewsTabName, TTLocalizer.EventsPageNewsTabName), # TODO replace this with a symbol + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = textScale, + text_font = ToontownGlobals.getInterfaceFont(), + pos = newPos, + scale = newScale, + command = self.__handleGotoNewsButton, + ) + + self.newIssueButton = DirectButton( + relief = None, + image = (self.openNewNewsUp, self.openNewNewsHover, self.openNewNewsHover), + text = ('', TTLocalizer.EventsPageNewsTabName, TTLocalizer.EventsPageNewsTabName), # TODO replace this with a symbol + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = textScale, + text_font = ToontownGlobals.getInterfaceFont(), + pos = newPos, + scale = newScale, + command = self.__handleGotoNewsButton, + ) + + self.gotoPrevPageButton = DirectButton( + relief = None, + image = (self.closeNewsUp, self.closeNewsHover, self.closeNewsHover), + text = ('', TTLocalizer.lClose, TTLocalizer.lClose), #"goto prev page", # TODO replace this with a synmbol + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = textScale, + text_font = ToontownGlobals.getInterfaceFont(), + pos = newPos, + scale = newScale, + command = self.__handleGotoPrevPageButton, + ) + + self.goto3dWorldButton = DirectButton( + relief = None, + image = (self.closeNewsUp, self.closeNewsHover, self.closeNewsHover), + text = ('', TTLocalizer.lClose, TTLocalizer.lClose), # "goto 3d world", # TODO replace this with a symbol + text_fg = (1,1,1,1), + text_shadow = (0,0,0,1), + text_scale = textScale, + text_font = ToontownGlobals.getInterfaceFont(), + pos = newPos, + scale = newScale, + command = self.__handleGoto3dWorldButton, + ) + + self.newIssueButton.hide() + self.gotoNewsButton.hide() + self.gotoPrevPageButton.hide() + self.goto3dWorldButton.hide() + + + self.accept('newIssueOut', self.handleNewIssueOut) + + bounce1Pos = VBase3(newPos.getX(), newPos.getY(), newPos.getZ() + 0.022) # (0.914, 0, 0.902) + bounce2Pos = VBase3(newPos.getX(), newPos.getY(), newPos.getZ() + 0.015) # (0.914, 0, 0.895) + + bounceIval = Sequence( + LerpPosInterval(self.newIssueButton, 0.1, bounce1Pos, blendType = 'easeOut'), + LerpPosInterval(self.newIssueButton, 0.1, newPos, blendType = 'easeIn'), + LerpPosInterval(self.newIssueButton, 0.07, bounce2Pos, blendType = 'easeOut'), + LerpPosInterval(self.newIssueButton, 0.07, newPos, blendType = 'easeIn') + ) + + self.__blinkIval = Sequence( + Func(self.__showOpenEyes), Wait(2), + bounceIval, Wait (0.5), + Func(self.__showClosedEyes), Wait(0.1), + Func(self.__showOpenEyes), Wait(0.1), + Func(self.__showClosedEyes), Wait(0.1), + ) + + + + # Start it looping, but pause it, so we can resume/pause it to + # start/stop the flashing. + self.__blinkIval.loop() + self.__blinkIval.pause() + + self.buttonsLoaded = True + + def __showOpenEyes(self): + self.newIssueButton['image'] = (self.openNewNewsUp, self.openNewNewsHover, self.openNewNewsHover) + + def __showClosedEyes(self): + self.newIssueButton['image'] = (self.openNewNewsUpBlink, self.openNewNewsHover, self.openNewNewsHover) + + def clearGoingToNewsInfo(self): + """Clear our flags on how we got to the news page.""" + self.goingToNewsPageFrom3dWorld = False + self.goingToNewsPageFromStickerBook = False + + def __handleGotoNewsButton(self): + # Don't open news if we are jumping + currentState = base.localAvatar.animFSM.getCurrentState().getName() + if currentState == 'jumpAirborne': + return + assert self.notify.debugStateCall(self) + from toontown.toon import LocalToon # must do import here to stop cyclic reference + if not LocalToon.WantNewsPage: + return + if base.cr and base.cr.playGame and base.cr.playGame.getPlace() and base.cr.playGame.getPlace().fsm: + fsm = base.cr.playGame.getPlace().fsm + curState = fsm.getCurrentState().getName() + if curState == 'walk': + if hasattr(localAvatar, "newsPage"): + base.cr.centralLogger.writeClientEvent("news gotoNewsButton clicked") + localAvatar.book.setPage(localAvatar.newsPage) + fsm.request("stickerBook") + self.goingToNewsPageFrom3dWorld = True + elif curState == 'stickerBook': + if hasattr(localAvatar, "newsPage"): + base.cr.centralLogger.writeClientEvent("news gotoNewsButton clicked") + localAvatar.book.setPage(localAvatar.newsPage) + fsm.request("stickerBook") + self.goingToNewsPageFromStickerBook = True + self.showAppropriateButton() + + + + def __handleGotoPrevPageButton(self): + assert self.notify.debugStateCall(self) + localAvatar.book.setPageBeforeNews() + self.clearGoingToNewsInfo() + self.showAppropriateButton() + pass + + def __handleGoto3dWorldButton(self): + assert self.notify.debugStateCall(self) + localAvatar.book.closeBook() + pass + + + def hideAllButtons(self): + """Hide everything.""" + if not self.buttonsLoaded: + return + self.gotoNewsButton.hide() + self.gotoPrevPageButton.hide() + self.goto3dWorldButton.hide() + self.newIssueButton.hide() + self.__blinkIval.pause() + + def enterHidden(self): + """There are times when we don't want any of this buttons to show, like when the shtikerbook is hidden.""" + self.hideAllButtons() + + def exitHidden(self): + pass + + def enterNormalWalk(self): + """The usual state when the avatar is just walking around the world.""" + if not self.buttonsLoaded: + return + + if localAvatar.getLastTimeReadNews() < base.cr.inGameNewsMgr.getLatestIssue(): + self.gotoNewsButton.hide() + self.newIssueButton.show() + self.__blinkIval.resume() + else: + self.gotoNewsButton.show() + self.newIssueButton.hide() + self.gotoPrevPageButton.hide() + self.goto3dWorldButton.hide() + + def exitNormalWalk(self): + if not self.buttonsLoaded: + return + self.hideAllButtons() + + def enterGotoWorld(self): + """We got here by directly clicking on the goto news button from the 3d world.""" + if not self.buttonsLoaded: + return + self.hideAllButtons() + self.goto3dWorldButton.show() + + def exitGotoWorld(self): + """Fix our state properly.""" + if not self.buttonsLoaded: + return + self.hideAllButtons() + localAvatar.book.setPageBeforeNews(enterPage = False) + self.clearGoingToNewsInfo() + + def enterPrevPage(self): + """We got here by directly clicking on the goto news button from the sticker book.""" + if not self.buttonsLoaded: + return + self.hideAllButtons() + self.gotoPrevPageButton.show() + + def exitPrevPage(self): + """Fix our state properly.""" + if not self.buttonsLoaded: + return + self.hideAllButtons() +## localAvatar.book.setPageBeforeNews() + self.clearGoingToNewsInfo() + + def showAppropriateButton(self): + """We know we want to show one of the 3 buttons, figure out which one.""" + self.notify.debugStateCall(self) + from toontown.toon import LocalToon # must do import here to stop cyclic reference + if not LocalToon.WantNewsPage: + return + if not self.buttonsLoaded: + return + if base.cr and base.cr.playGame and base.cr.playGame.getPlace() and \ + hasattr(base.cr.playGame.getPlace(),'fsm') and base.cr.playGame.getPlace().fsm: + fsm = base.cr.playGame.getPlace().fsm + curState = fsm.getCurrentState().getName() + # do not show the news page button if we are in the tutorial + # or in cog hq lobbies + if curState == 'walk': + if localAvatar.tutorialAck and not localAvatar.isDisguised: + self.request("NormalWalk") + else: + self.request("Hidden") + elif curState == 'stickerBook': + if self.goingToNewsPageFrom3dWorld: + if localAvatar.tutorialAck: + self.request("GotoWorld") + else: + self.request("Hidden") + elif self.goingToNewsPageFromStickerBook: + if localAvatar.tutorialAck: + self.request("PrevPage") + else: + self.request("Hidden") + else: + # we get here when he just clicked on the sticker book button + if localAvatar.tutorialAck: + self.request("NormalWalk") + else: + self.request("Hidden") + + + def setGoingToNewsPageFromStickerBook(self, newVal): + """Called when the news page tab gets clicked in sticker book.""" + assert self.notify.debugStateCall(self) + self.goingToNewsPageFromStickerBook = newVal + + def enterOff(self): + """Clean up the buttons.""" + self.ignoreAll() + if not self.buttonsLoaded: + return + + if self.__blinkIval: + self.__blinkIval.finish() + self.__blinkIval = None + + self.gotoNewsButton.destroy() + self.newIssueButton.destroy() + self.gotoPrevPageButton.destroy() + self.goto3dWorldButton.destroy() + + del self.openNewNewsUp + del self.openNewNewsUpBlink + del self.openNewNewsHover + del self.openOldNewsUp + del self.openOldNewsHover + del self.closeNewsUp + del self.closeNewsHover + + def exitOff(self): + """Print a warning if we get here.""" + self.notify.warning('Should not get here. NewsPageButtonManager.exitOff') + + def simulateEscapeKeyPress(self): + # Go back to the 3D World if you have come from the 3D World. + if self.goingToNewsPageFrom3dWorld: + self.__handleGoto3dWorldButton() + # Else, go back to the previous page in the shticker book if you have come from there. + if self.goingToNewsPageFromStickerBook: + self.__handleGotoPrevPageButton() + + def handleNewIssueOut(self): + """Handle the message that a new issue has been released.""" + # Our code does not deal with the case when it gets the new issue out message + # while you are reading the news + if localAvatar.isReadingNews(): + # do nothing he'll get informed when he closes the news that there's a new issue + pass + else: + self.showAppropriateButton() diff --git a/toontown/src/toontowngui/Sources.pp b/toontown/src/toontowngui/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/toontowngui/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/toontowngui/TTDialog.py b/toontown/src/toontowngui/TTDialog.py new file mode 100644 index 0000000..595fbab --- /dev/null +++ b/toontown/src/toontowngui/TTDialog.py @@ -0,0 +1,25 @@ +from otp.otpgui.OTPDialog import * + +class TTDialog(OTPDialog): + + def __init__(self, parent = None, style = NoButtons, **kw): + + self.path = 'phase_3/models/gui/dialog_box_buttons_gui' + OTPDialog.__init__(self, parent, style, **kw) + + # Merge keyword options with default options + self.initialiseoptions(TTDialog) + +class TTGlobalDialog(GlobalDialog): + + def __init__(self, message = '', doneEvent = None, style = NoButtons, + okButtonText = OTPLocalizer.DialogOK, + cancelButtonText = OTPLocalizer.DialogCancel, + **kw): + + self.path = 'phase_3/models/gui/dialog_box_buttons_gui' + GlobalDialog.__init__(self, message, doneEvent, style, + okButtonText, cancelButtonText, **kw) + + # Merge keyword options with default options + self.initialiseoptions(TTGlobalDialog) diff --git a/toontown/src/toontowngui/TeaserPanel.py b/toontown/src/toontowngui/TeaserPanel.py new file mode 100644 index 0000000..4a0f228 --- /dev/null +++ b/toontown/src/toontowngui/TeaserPanel.py @@ -0,0 +1,315 @@ +from pandac.PandaModules import * +from direct.gui.DirectGui import * +from direct.gui import DirectGuiGlobals +from pandac.PandaModules import * +from direct.directnotify import DirectNotifyGlobal +import TTDialog +from toontown.toonbase import TTLocalizer +from direct.showbase import PythonUtil +from direct.showbase.DirectObject import DirectObject +from otp.login import LeaveToPayDialog +#from otp.otpbase import OTPLauncherGlobals + +""" +d.destroy() +from direct.gui import DirectDialog +buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') +guiButton = loader.loadModel("phase_3/models/gui/quit_button") +cancelImageList = (buttons.find('**/CloseBtn_UP'),buttons.find('**/CloseBtn_DN'),buttons.find('**/CloseBtn_Rllvr')) +subscribeImageList = (guiButton.find('**/QuitBtn_DN'),guiButton.find("**/QuitBtn_DN"),guiButton.find("**/QuitBtn_RLVR"),) +buttonImage = [subscribeImageList,cancelImageList] +buttonText = ('Subscribe Now!', 'Cancel') +d = DirectDialog.DirectDialog(buttonImageList=buttonImage,buttonTextList=buttonText) +""" + +Pages = { + + # "stringToken" : (localized text, + # image filename, + # image format flag [square = 2, portrait = 1, landscape = 0], + # members only flag, + # ) + + 'otherHoods' : (TTLocalizer.TeaserOtherHoods,), + 'typeAName' : (TTLocalizer.TeaserTypeAName,), + 'sixToons' : (TTLocalizer.TeaserSixToons,), + 'otherGags' : (TTLocalizer.TeaserOtherGags,), + 'clothing' : (TTLocalizer.TeaserClothing,), + #'furniture' : (TTLocalizer.TeaserFurniture,), + 'cogHQ' : (TTLocalizer.TeaserCogHQ,), + 'secretChat' : (TTLocalizer.TeaserSecretChat,), + #'mailers' : (TTLocalizer.TeaserCardsAndPosters,), + #'holidays' : (TTLocalizer.TeaserHolidays,), + 'quests' : (TTLocalizer.TeaserQuests,), + 'emotions' : (TTLocalizer.TeaserEmotions,), + 'minigames' : (TTLocalizer.TeaserMinigames,), + 'karting' : (TTLocalizer.TeaserKarting,), + 'kartingAccessories' : (TTLocalizer.TeaserKartingAccessories,), + 'gardening' : (TTLocalizer.TeaserGardening,), + #'bigger' : (TTLocalizer.TeaserBigger,), + #'rental' : (TTLocalizer.TeaserRental,), + 'tricks' : (TTLocalizer.TeaserTricks,), + 'species' : (TTLocalizer.TeaserSpecies,), + 'golf' : (TTLocalizer.TeaserGolf,), + 'fishing' : (TTLocalizer.TeaserFishing,), + 'parties' : (TTLocalizer.TeaserParties,), + } + +PageOrder = [ + 'sixToons', + 'typeAName', + 'species', + 'otherHoods', + 'otherGags', + 'clothing', + #'furniture', + #'bigger', + #'rental', + 'parties', + 'tricks', + 'cogHQ', + 'secretChat', + #'mailers', + #'holidays', + 'quests', + 'emotions', + 'minigames', + 'karting', + 'kartingAccessories', + 'gardening', + 'golf', + 'fishing', + ] + +class TeaserPanel(DirectObject): + """Tease trialers with descriptions of what they'll get if they subscribe + """ + notify = DirectNotifyGlobal.directNotify.newCategory("TeaserPanel") + + def __init__(self, pageName, doneFunc=None): + + self.doneFunc = doneFunc + + # if we don't have a feature browser, make one + if not hasattr(self, "browser"): + self.browser = FeatureBrowser() + self.browser.load() + self.browser.setPos(0, 0, TTLocalizer.TSRPbrowserPosZ) + # make room for the top five features + self.browser.setScale(0.75) + self.browser.reparentTo(hidden) + + self.upsellBackground = loader.loadModel("phase_3/models/gui/tt_m_gui_ups_panelBg") + + self.leaveDialog = None + self.showPage(pageName) + + # The player might be able to exit the stop state either through some other + # panel or if his boarding party leader boards the elevator. + # Close any Teaser panel if the toon moves out of the stopped state. + self.ignore("exitingStoppedState") + self.accept("exitingStoppedState", self.cleanup) + + + def __handleDone(self, choice = 0): + # clean up the teaser panel and take appropriate action + self.cleanup() + self.unload() + + if choice == 1: + self.__handlePay() + else: + self.__handleContinue() + + def __handleContinue(self): + # call the user done function + if self.doneFunc: + self.notify.debug("calling doneFunc") + self.doneFunc() + + def __handlePay(self): + if base.cr.isWebPlayToken() or __dev__: + if self.leaveDialog == None: + self.notify.debug("making LTP") + self.leaveDialog = LeaveToPayDialog.LeaveToPayDialog(0, doneFunc=self.doneFunc) + self.notify.debug("showing LTP") + self.leaveDialog.show() + else: + self.notify.error("You should not have a TeaserPanel without a PlayToken") + + + def destroy(self): + self.cleanup() + + # dialog callback code passes a value + def cleanup(self): + if hasattr(self, 'browser'): + self.browser.reparentTo(hidden) + self.browser.ignoreAll() + if hasattr(self, 'dialog'): + base.transitions.noTransitions() + self.dialog.cleanup() + del self.dialog + if self.leaveDialog: + self.leaveDialog.destroy() + self.leaveDialog = None + self.ignoreAll() + + def unload(self): + # there is a chance the gui code might have already deleted this + if hasattr(self, 'browser'): + self.browser.destroy() + del self.browser + + def showPage(self, pageName): + if not pageName in PageOrder: + self.notify.error("unknown page '%s'" % pageName) + + # log velvet rope hits + base.cr.centralLogger.writeClientEvent('velvetRope: %s' % pageName) + + # map page name to browser index + self.browser.scrollTo(PageOrder.index(pageName)) + + # remove current global dialog if present + self.cleanup() + + self.dialog = TTDialog.TTDialog( + parent = aspect2dp, + text = TTLocalizer.TeaserTop, + text_align = TextNode.ACenter, + text_wordwrap = TTLocalizer.TSRPdialogWordwrap, + text_scale = TTLocalizer.TSRPtop, + topPad =-0.15, + midPad = 1.25, + sidePad = 0.25, + pad = (0.25, 0.25), + command = self.__handleDone, + fadeScreen = .5, + style = TTDialog.TwoChoice, + buttonTextList = [TTLocalizer.TeaserSubscribe, + TTLocalizer.TeaserContinue, + ], + button_text_scale = TTLocalizer.TSRPbutton, + buttonPadSF = 5.5, + sortOrder = NO_FADE_SORT_INDEX, + image = self.upsellBackground, + ) + self.dialog.setPos(0, 0, 0.75) + self.browser.reparentTo(self.dialog) + base.transitions.fadeScreen(.5) + + if base.config.GetBool('want-teaser-scroll-keys',0): + self.accept('arrow_right', self.showNextPage) + self.accept('arrow_left', self.showPrevPage) + self.accept('stoppedAsleep', self.__handleDone) + + def showNextPage(self): + self.notify.debug("show next") + self.browser.scrollBy(1) + + def showPrevPage(self): + self.notify.debug("show prev") + self.browser.scrollBy(-1) + + def showPay(self): + # show the pay button + self.dialog.buttonList[0].show() + + def hidePay(self): + # hide the pay button + self.dialog.buttonList[0].hide() + + def removed(self): + # return the removed status of our nodepath object + if hasattr(self, 'dialog') and self.dialog: + return self.dialog.removed() + elif hasattr(self, 'leaveDialog') and self.leaveDialog: + return self.leaveDialog.removed() + else: + return 1 + + +class FeatureBrowser(DirectScrolledList): + + # special methods + def __init__(self, parent=aspect2dp, **kw): + """__init__(self) + FeatureBrowser constructor: create a scrolling list of features + """ + assert PythonUtil.sameElements(Pages.keys(), PageOrder) + + self.parent = parent + + optiondefs = ( + ('parent', self.parent, None), + ('relief', None, None), + ('numItemsVisible', 1, None), + ('items', [], None), + ) + + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + # Initialize superclasses + DirectScrolledList.__init__(self, parent) + # We'll scroll using the arrow keys on the keyboard + self.incButton.hide() + self.decButton.hide() + self.initialiseoptions(FeatureBrowser) + + def destroy(self): + DirectScrolledList.destroy(self) + + def load(self): + # load up the images + # upsellModel = loader.loadModel("phase_3/models/gui/tt_m_gui_ups_mainGui") + # guiModel = upsellModel.find("**/tt_t_gui_ups_logo_noBubbles") + guiModel = loader.loadModel("phase_3/models/gui/tt_m_gui_ups_logo_noText") + + + leftLocator = guiModel.find("**/bubbleLeft_locator") + rightLocator = guiModel.find("**/bubbleRight_locator") + + haveFunNode = TextNode("Have Fun") + haveFunNode.setText(TTLocalizer.TeaserHaveFun) + haveFunNode.setTextColor(0,0,0,1) + haveFunNode.setWordwrap(6) + haveFunNode.setAlign(TextNode.ACenter) + haveFunNode.setFont(DirectGuiGlobals.getDefaultFont()) + haveFun = NodePath(haveFunNode) + haveFun.reparentTo(rightLocator) + haveFun.setScale(TTLocalizer.TSRPhaveFunText) + + JoinUsNode = TextNode("Join Us") + JoinUsNode.setText(TTLocalizer.TeaserJoinUs) + JoinUsNode.setTextColor(0,0,0,1) + JoinUsNode.setWordwrap(6) + JoinUsNode.setAlign(TextNode.ACenter) + JoinUsNode.setFont(DirectGuiGlobals.getDefaultFont()) + JoinUs = NodePath(JoinUsNode) + JoinUs.reparentTo(leftLocator) + JoinUs.setPos(0,0,-0.025) + JoinUs.setScale(TTLocalizer.TSRPjoinUsText) + + # axis = loader.loadModel("models/misc/xyzAxis") + # axis.reparentTo(guiModel) + + # make a panel for each feature + for page in PageOrder: + textInfo = Pages.get(page) + textInfo = textInfo[0] +TTLocalizer.TeaserDefault + + panel = DirectFrame( + parent = self, + relief = None, + image = guiModel, + image_scale = (0.65,0.65,0.65), + image_pos = (0, 0, 0.0), + text_align = TextNode.ACenter, + text = textInfo, + text_scale = TTLocalizer.TSRPpanelScale, + text_pos = TTLocalizer.TSRPpanelPos, + ) + self.addItem(panel) + guiModel.removeNode() + diff --git a/toontown/src/toontowngui/ToonHeadDialog.py b/toontown/src/toontowngui/ToonHeadDialog.py new file mode 100644 index 0000000..2f488de --- /dev/null +++ b/toontown/src/toontowngui/ToonHeadDialog.py @@ -0,0 +1,65 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.directnotify import DirectNotifyGlobal +import TTDialog +from toontown.toon import ToonHead + + +class ToonHeadDialog(TTDialog.TTDialog): + """ + Create a TTDialog panel with an avatar head + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("ToonHeadDialog") + + def __init__(self, dna, **kw): + self.dna = dna + + # Create an avatar head for the panel + head = hidden.attachNewNode('head', 20) + self.headModel = ToonHead.ToonHead() + self.headModel.setupHead(self.dna, forGui = 1) + self.headModel.fitAndCenterHead(1.0, forGui = 1) + self.headModel.reparentTo(head) + self.headModel.setName('headModel') + + # Start blinking, but don't look around--the avatar's looking + # at you! + self.headModel.startBlink() + + optiondefs = ( + ('dialogName', 'ToonHeadDialog', None), + ('style', TTDialog.NoButtons,None), + ('geom', head, None), + ('geom_scale', 0.35, None), + ('geom_pos', (-0.25,0,0), None), + ('text_wordwrap', 9, None), + ('fadeScreen', 0, None), + ) + # Merge keyword options with default options + self.defineoptions(kw, optiondefs) + + # initialize our base class. Need to pass style + TTDialog.TTDialog.__init__(self, style = self['style']) + + self.initialiseoptions(ToonHeadDialog) + + # Replace copy of head in dialog with blinking version + self.postInitialiseFuncList.append(self.replaceHead) + + def replaceHead(self): + head = self.stateNodePath[0].find('**/head') + headModelCopy = self.stateNodePath[0].find('**/headModel') + headModelCopy.removeNode() + self.headModel.reparentTo(head) + + def cleanup(self): + """ + Stop head model tasks + """ + TTDialog.TTDialog.cleanup(self) + self.headModel.stopBlink() + self.headModel.stopLookAroundNow() + self.headModel.delete() + + diff --git a/toontown/src/toontowngui/ToontownLoadingBlocker.py b/toontown/src/toontowngui/ToontownLoadingBlocker.py new file mode 100644 index 0000000..dab4272 --- /dev/null +++ b/toontown/src/toontowngui/ToontownLoadingBlocker.py @@ -0,0 +1,323 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * +from pandac.PandaModules import TextNode +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.toontowngui import TTDialog +from otp.otpgui.OTPDialog import * +from direct.interval.LerpInterval import LerpPosInterval, LerpScaleInterval, LerpFunc +from direct.interval.IntervalGlobal import Sequence, Parallel, Func, Wait +from direct.task import Task +import random + +class ToontownLoadingBlocker(TTDialog.TTDialog): + notify = DirectNotifyGlobal.directNotify.newCategory("ToontownLoadingBlocker") + + def __init__(self, avList): + if not self.__shouldShowBlocker(avList): + return + + TTDialog.TTDialog.__init__(self) + + gui = loader.loadModel("phase_3/models/gui/tt_m_gui_pat_mainGui") + img = gui.find("**/tt_t_gui_pat_loadingPopup") + self['image'] = img + self['image_scale'] = (1, 0, 1) + self['image_pos'] = (0, 0, -0.4) + + gui.removeNode() + + self.loadingTextChangeTimer = 10.0 + self.loadingTextTimerVariant = 3.0 + self.loadingTextFreezeTime = 3.0 + self.hideBlockerIval = None + self.canChangeLoadingText = True + + self.__setupLoadingBar() + self.__createTitleText() + self.__createToonTip() + self.__createLoadingText() + self.__showBlocker() + + self.accept("phaseComplete-4", self.__shrinkLoadingBar) + self.accept("launcherPercentPhaseComplete", self.__update) + + def destroy(self): + taskMgr.remove("changeLoadingTextTask") + taskMgr.remove("canChangeLoadingTextTask") + + self.ignore("phaseComplete-4") + self.ignore("launcherPercentPhaseComplete") + + self.__cleanupHideBlockerIval() + + self.title.destroy() + self.title = None + + self.loadingText.destroy() + self.loadingText = None + self.loadingTextList = None + + self.toonTipText.destroy() + self.toonTipText = None + + self.bar.destroy() + self.bar = None + + TTDialog.TTDialog.destroy(self) + + def __hideBlocker(self): + """ + Hide the blocker panel. + """ + self.hide() + # Show the smaller loading text at the left bottom corner when the blocker panel is hidden. + if self.__isValidDownloadBar(): + base.downloadWatcher.text.show() + + def __showBlocker(self): + """ + Show the blocker panel. + """ + self.show() + # Hide the smaller loading text at the left bottom corner when the blocker panel is up. + if self.__isValidDownloadBar(): + base.downloadWatcher.text.hide() + + def __setupLoadingBar(self): + """ + Setup the loading bar for the ToontownLoadingBlocker. + Make sure it is in the right bin so that it is visible in front of the + ToontownLoadingBlocker. + """ + self.bar = DirectWaitBar( + parent = self, + guiId = 'DownloadBlockerBar', + pos = (0, 0, -0.3138), + relief = DGG.SUNKEN, + frameSize = (-0.6,0.6,-0.1,0.1), + borderWidth = (0.02,0.02), + scale = (0.8, 0.8, 0.5), + range = 100, + sortOrder = 5000, + frameColor = (0.5,0.5,0.5,0.5), + barColor = (0.2,0.7,0.2,0.5), + text = "0%", + text_scale = (0.08, 0.128), + text_fg = (1, 1, 1, 1), + text_align = TextNode.ACenter, + text_pos = (0, -0.035), + ) + self.bar.setBin('gui-popup', 1) + + if self.__isValidDownloadBar(): + base.downloadWatcher.bar.hide() + + def __resetLoadingBar(self): + """ + Reset the bin for the loading bar so that it appears unchanged + as part of the ToontownDownloadWatcher. + """ + self.bar.clearBin() + if self.__isValidDownloadBar(): + base.downloadWatcher.bar.show() + + def __isValidDownloadBar(self): + """ + Just a validity function to check if base.downloadWatcher.bar is valid. + """ + if hasattr(base, "downloadWatcher") and base.downloadWatcher: + if hasattr(base.downloadWatcher, "bar") and base.downloadWatcher.bar: + return True + return False + + def __createTitleText(self): + """ + Create the title text for the blocker panel. + """ + self.title = DirectLabel( + parent = self, + relief = None, + guiId = 'BlockerTitle', + pos = (0, 0, 0.38), + text = TTLocalizer.BlockerTitle, + text_font = ToontownGlobals.getSignFont(), + text_fg = (1,0.9,0.1,1), + text_align = TextNode.ACenter, + # text_shadow = (0,0,0,1), + text_scale = 0.1, + textMayChange = 1, + sortOrder = 50, + ) + + def __createLoadingText(self): + """ + Create a loading text. + This will have crazy toony loading texts, irrelevant to what is being loaded. + """ + self.loadingText = DirectLabel( + parent = self, + relief = None, + guiId = 'BlockerLoadingText', + pos = (0, 0, -0.2357), + text = "Loading...", + text_fg = (1, 1, 1, 1), + # text_shadow = (0,0,0,1), + text_scale = 0.055, + textMayChange = 1, + text_align = TextNode.ACenter, + sortOrder = 50, + ) + + self.loadingTextList = TTLocalizer.BlockerLoadingTexts + + # Set the first random loading text. + self.__changeLoadingText() + + # Start a task to change the loading text every loadingTextChangeTimer seconds. + taskMgr.doMethodLater(self.loadingTextChangeTimer, self.__changeLoadingTextTask, "changeLoadingTextTask") + + def __changeLoadingText(self): + """ + Change the loading text every time the loading bar goes to 0%, + either when it starts loading a new phase or a new patch. + """ + def getLoadingText(): + listLen = len(self.loadingTextList) + if (listLen > 0): + randomIndex = random.randrange(listLen) + randomLoadingText = self.loadingTextList.pop(randomIndex) + return randomLoadingText + else: + # Oops the loading took so long, that we exhausted + # all our loading texts. Start over again. + self.loadingTextList = TTLocalizer.BlockerLoadingTexts + if self.canChangeLoadingText: + self.loadingText['text'] = getLoadingText() + self.canChangeLoadingText = False + taskMgr.doMethodLater(self.loadingTextFreezeTime, self.__canChangeLoadingTextTask, "canChangeLoadingTextTask") + + def __changeLoadingTextTask(self, task): + """ + The task that requests a change of loading text. + This task is kept going so that the loading text changes every 5 secs. + Along with this the loading text also changes everytime the loading + bar goes to 0%. + """ + self.__changeLoadingText() + # Adding a random time variation in changing the loading text, + # so that the loading text change looks less fake. + randVariation = random.uniform(-self.loadingTextTimerVariant, self.loadingTextTimerVariant) + task.delayTime = self.loadingTextChangeTimer + randVariation + return task.again + + def __canChangeLoadingTextTask(self, task): + """ + We want the loading text to stay on screen for at least + 3 seconds. If we have just changed the loading text, and then the + loading bar goes to 0%, it will try to change the loading text again. + This task will make sure this doesn't happen. Without this it is highly + possible for the loading text to change immediately resulting in a bad show. + """ + self.canChangeLoadingText = True + return task.done + + def __createToonTip(self): + """ + Create the toon tip text. + """ + def getTip(tipCategory): + return TTLocalizer.TipTitle + "\n" + random.choice(TTLocalizer.TipDict.get(tipCategory)) + + self.toonTipText = DirectLabel( + parent = self, + relief = None, + guiId = 'BlockerToonTip', + pos = (0, 0, -0.4688), + text = getTip(TTLocalizer.TIP_GENERAL), + text_fg = (1, 1, 1, 1), + # text_shadow = (0,0,0,1), + text_scale = 0.05, + textMayChange = 1, + text_align = TextNode.ACenter, + text_wordwrap = 32, + sortOrder = 50, + ) + + def __shouldShowBlocker(self, avList): + """ + Determines if the ToontownLoadingBlocker should be visible. + Returns True if the ToontownLoadingBlocker should be visible, + Returens False if the ToontownLoadingBlocker should be hidden. + """ + def hasPlayableToon(avList): + # Check if there is a playable toon. + if (len(avList) > 0): + if base.cr.isPaid(): + return True + else: + for av in avList: + if (av.position == 1): + return True + return False + + if hasPlayableToon(avList): + # Return True if there is anything to load. + if not (base.launcher.getPhaseComplete(3.5) and base.launcher.getPhaseComplete(4)): + return True + return False + + def __shrinkLoadingBar(self): + if self.__isValidDownloadBar(): + ivalDuration = 0.5 + barPosIval = LerpPosInterval(self.bar, ivalDuration, (-0.81,0,-0.96)) + barScaleIval = LerpScaleInterval(self.bar, ivalDuration, (0.25, 0.25, 0.25)) + + def posText(pos): + self.bar['text_pos'] = (0, pos) + + def scaleText(scale): + self.bar['text_scale'] = (scale, scale) + + textScaleIval = LerpFunc(scaleText, + fromData = 0.08, + toData = 0.16, + duration = ivalDuration) + + textPosIval = LerpFunc(posText, + fromData = -0.035, + toData = -0.05, + duration = ivalDuration) + + shrinkIval = Parallel(barPosIval, barScaleIval, + textPosIval, textScaleIval, + Func(self.loadingText.hide)) + + self.hideBlockerIval = Sequence( + shrinkIval, Wait(0.5), + Func(self.__hideBlocker), + Func(self.__resetLoadingBar), + Func(self.destroy)) + self.hideBlockerIval.start() + + def __cleanupHideBlockerIval(self): + """ + Cleans up the hideBlockerIval. + """ + if self.hideBlockerIval: + self.hideBlockerIval.finish() + self.hideBlockerIval = None + + def __update(self, phase, percent, reqByteRate, actualByteRate): + """ + Track the percent loads of the various phases. + """ + # Track the DownloadWatcherBar and replicate it's percentage complete + if self.__isValidDownloadBar(): + percent = base.downloadWatcher.bar['value'] + self.bar['text'] = ("%s %%" % (percent)) + self.bar['value'] = percent + # Change the loading text everytime we start from 0%. + if (percent == 0): + self.__changeLoadingText() + \ No newline at end of file diff --git a/toontown/src/toontowngui/ToontownLoadingScreen.py b/toontown/src/toontowngui/ToontownLoadingScreen.py new file mode 100644 index 0000000..1233f5b --- /dev/null +++ b/toontown/src/toontowngui/ToontownLoadingScreen.py @@ -0,0 +1,101 @@ + +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +import random + +class ToontownLoadingScreen: + + def __init__(self): + + self.__expectedCount = 0 + self.__count = 0 + + self.gui = loader.loadModel("phase_3/models/gui/progress-background") + + self.banner = loader.loadModel("phase_3/models/gui/toon_council").find("**/scroll") + self.banner.reparentTo(self.gui) + self.banner.setScale(0.4,0.4,0.4) + + self.tip = DirectLabel( + guiId = "ToontownLoadingScreenTip", + parent = self.banner, + relief = None, + text = "", + text_scale = TTLocalizer.TLStip, + textMayChange = 1, + pos = (-1.2,0.0,0.1), + text_fg = (0.4,0.3,0.2,1), + text_wordwrap = 13, + text_align = TextNode.ALeft, + ) + + self.title = DirectLabel( + guiId = "ToontownLoadingScreenTitle", + parent = self.gui, + relief = None, + pos = (-1.06, 0, -0.77), + text = "", + textMayChange = 1, + text_scale = 0.08, + text_fg = (0,0,0.5,1), + text_align = TextNode.ALeft, + ) + + # Hide the running man until we animate him + self.waitBar = DirectWaitBar( + guiId = "ToontownLoadingScreenWaitBar", + parent = self.gui, + frameSize = (-1.06,1.06,-0.03,0.03), + pos = (0,0,-0.85), + text = '', + ) + + def destroy(self): + self.tip.destroy() + self.title.destroy() + self.waitBar.destroy() + self.banner.removeNode() + self.gui.removeNode() + + def getTip(self, tipCategory): + return TTLocalizer.TipTitle + "\n" + random.choice(TTLocalizer.TipDict.get(tipCategory)) + + def begin(self, range, label, gui, tipCategory): + # make the loader bar and draw it. + self.waitBar['range'] = range + self.title['text'] = label + self.tip['text'] = self.getTip(tipCategory) + + self.__count = 0 + self.__expectedCount = range + + if gui: + # Put the progress gui in front of all the fade action + self.waitBar.reparentTo(self.gui) + self.title.reparentTo(self.gui) + self.gui.reparentTo(aspect2dp, NO_FADE_SORT_INDEX) + else: + self.waitBar.reparentTo(aspect2dp, NO_FADE_SORT_INDEX) + self.title.reparentTo(aspect2dp, NO_FADE_SORT_INDEX) + self.gui.reparentTo(hidden) + self.waitBar.update(self.__count) + + def end(self): + # animate end of bar, if needed, then get rid of it + self.waitBar.finish() + self.waitBar.reparentTo(self.gui) + self.title.reparentTo(self.gui) + self.gui.reparentTo(hidden) + return (self.__expectedCount, self.__count) + + def abort(self): + self.gui.reparentTo(hidden) + + def tick(self): + self.__count = self.__count + 1 + # update progress bar + self.waitBar.update(self.__count) + + diff --git a/toontown/src/toontowngui/__init__.py b/toontown/src/toontowngui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/town/.cvsignore b/toontown/src/town/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/town/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/town/BRStreet.py b/toontown/src/town/BRStreet.py new file mode 100644 index 0000000..e2cc876 --- /dev/null +++ b/toontown/src/town/BRStreet.py @@ -0,0 +1,15 @@ + +import Street + +class BRStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + + + diff --git a/toontown/src/town/BRTownLoader.py b/toontown/src/town/BRTownLoader.py new file mode 100644 index 0000000..f5a527f --- /dev/null +++ b/toontown/src/town/BRTownLoader.py @@ -0,0 +1,25 @@ + +import TownLoader +import BRStreet +from toontown.suit import Suit + +class BRTownLoader(TownLoader.TownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = BRStreet.BRStreet + self.musicFile = "phase_8/audio/bgm/TB_SZ.mid" + self.activityMusicFile = "phase_8/audio/bgm/TB_SZ_activity.mid" + self.townStorageDNAFile = "phase_8/dna/storage_BR_town.dna" + + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(3) + dnaFile = ("phase_8/dna/the_burrrgh_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + Suit.unloadSuits(3) + TownLoader.TownLoader.unload(self) + + + diff --git a/toontown/src/town/DDStreet.py b/toontown/src/town/DDStreet.py new file mode 100644 index 0000000..aff3016 --- /dev/null +++ b/toontown/src/town/DDStreet.py @@ -0,0 +1,25 @@ + +import Street + +class DDStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + + def enter(self, requestStatus): + # We need to do our own code here first because the act + # of entering can actually cause a complete exit/unload + # sequence in the case that we are teleporting to a friend + # that is not here + self.loader.hood.setWhiteFog() + Street.Street.enter(self, requestStatus) + + def exit(self): + self.loader.hood.setNoFog() + Street.Street.exit(self) + diff --git a/toontown/src/town/DDTownLoader.py b/toontown/src/town/DDTownLoader.py new file mode 100644 index 0000000..abd3936 --- /dev/null +++ b/toontown/src/town/DDTownLoader.py @@ -0,0 +1,29 @@ + +import TownLoader +import DDStreet +from toontown.suit import Suit + +class DDTownLoader(TownLoader.TownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = DDStreet.DDStreet + self.musicFile = "phase_6/audio/bgm/DD_SZ.mid" + self.activityMusicFile = "phase_6/audio/bgm/DD_SZ_activity.mid" + self.townStorageDNAFile = "phase_6/dna/storage_DD_town.dna" + + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(2) + dnaFile = ("phase_6/dna/donalds_dock_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + Suit.unloadSuits(2) + TownLoader.TownLoader.unload(self) + + def enter(self, requestStatus): + TownLoader.TownLoader.enter(self, requestStatus) + + def exit(self): + TownLoader.TownLoader.exit(self) + diff --git a/toontown/src/town/DGStreet.py b/toontown/src/town/DGStreet.py new file mode 100644 index 0000000..81b9733 --- /dev/null +++ b/toontown/src/town/DGStreet.py @@ -0,0 +1,14 @@ + +import Street + +class DGStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + + diff --git a/toontown/src/town/DGTownLoader.py b/toontown/src/town/DGTownLoader.py new file mode 100644 index 0000000..c1685a1 --- /dev/null +++ b/toontown/src/town/DGTownLoader.py @@ -0,0 +1,26 @@ + +import TownLoader +import DGStreet +from toontown.suit import Suit + +class DGTownLoader(TownLoader.TownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = DGStreet.DGStreet + self.musicFile = "phase_8/audio/bgm/DG_SZ.mid" + # Hack: There is not currently a DG_SZ_activity.mid file. + # We'll use the SZ music: + self.activityMusicFile = "phase_8/audio/bgm/DG_SZ.mid" + self.townStorageDNAFile = "phase_8/dna/storage_DG_town.dna" + + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(3) + dnaFile = ("phase_8/dna/daisys_garden_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + Suit.unloadSuits(3) + TownLoader.TownLoader.unload(self) + + diff --git a/toontown/src/town/DLStreet.py b/toontown/src/town/DLStreet.py new file mode 100644 index 0000000..2f5054b --- /dev/null +++ b/toontown/src/town/DLStreet.py @@ -0,0 +1,13 @@ + +import Street + +class DLStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + diff --git a/toontown/src/town/DLTownLoader.py b/toontown/src/town/DLTownLoader.py new file mode 100644 index 0000000..0b529c2 --- /dev/null +++ b/toontown/src/town/DLTownLoader.py @@ -0,0 +1,23 @@ + +import TownLoader +import DLStreet +from toontown.suit import Suit + +class DLTownLoader(TownLoader.TownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = DLStreet.DLStreet + self.musicFile = "phase_8/audio/bgm/DL_SZ.mid" + self.activityMusicFile = "phase_8/audio/bgm/DL_SZ_activity.mid" + self.townStorageDNAFile = "phase_8/dna/storage_DL_town.dna" + + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(3) + dnaFile = ("phase_8/dna/donalds_dreamland_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + Suit.unloadSuits(3) + TownLoader.TownLoader.unload(self) + diff --git a/toontown/src/town/MMStreet.py b/toontown/src/town/MMStreet.py new file mode 100644 index 0000000..5c04299 --- /dev/null +++ b/toontown/src/town/MMStreet.py @@ -0,0 +1,13 @@ + +import Street + +class MMStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + diff --git a/toontown/src/town/MMTownLoader.py b/toontown/src/town/MMTownLoader.py new file mode 100644 index 0000000..7e7ea5d --- /dev/null +++ b/toontown/src/town/MMTownLoader.py @@ -0,0 +1,31 @@ + +import TownLoader +import MMStreet +from toontown.suit import Suit +if __debug__: + from direct.directnotify import DirectNotifyGlobal + +class MMTownLoader(TownLoader.TownLoader): + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory("MMTownLoader") + + def __init__(self, hood, parentFSM, doneEvent): + assert self.notify.debug("__init__()") + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = MMStreet.MMStreet + self.musicFile = "phase_6/audio/bgm/MM_SZ.mid" + self.activityMusicFile = "phase_6/audio/bgm/MM_SZ_activity.mid" + self.townStorageDNAFile = "phase_6/dna/storage_MM_town.dna" + + def load(self, zoneId): + assert self.notify.debug("__init__()") + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(2) + dnaFile = ("phase_6/dna/minnies_melody_land_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + assert self.notify.debug("__init__()") + Suit.unloadSuits(2) + TownLoader.TownLoader.unload(self) + diff --git a/toontown/src/town/Sources.pp b/toontown/src/town/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/town/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/town/Street.py b/toontown/src/town/Street.py new file mode 100644 index 0000000..de159ed --- /dev/null +++ b/toontown/src/town/Street.py @@ -0,0 +1,637 @@ +"""Street module: contains the Street class""" + +from pandac.PandaModules import * +from toontown.battle.BattleProps import * +from toontown.battle.BattleSounds import * +from toontown.distributed.ToontownMsgTypes import * +from direct.gui.DirectGui import cleanupDialog +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import Place +from toontown.battle import BattlePlace +from direct.showbase import DirectObject +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.task import Task +from toontown.battle import BattleParticles +from toontown.building import Elevator +from toontown.hood import ZoneUtil +from toontown.toonbase import ToontownGlobals +from toontown.estate import HouseGlobals +from toontown.toonbase import TTLocalizer +from direct.interval.IntervalGlobal import * + + +visualizeZones = base.config.GetBool("visualize-zones", 0) + +class Street(BattlePlace.BattlePlace): + """ + Street class + """ + + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("Street") + + # special methods + + def __init__(self, loader, parentFSM, doneEvent): + """ + Street constructor: create a play game ClassicFSM + """ + assert self.notify.debug("__init__()") + BattlePlace.BattlePlace.__init__(self, loader, doneEvent) + self.fsm = ClassicFSM.ClassicFSM('Street', + [State.State('start', + self.enterStart, + self.exitStart, + ['walk', 'tunnelIn', + 'doorIn', 'teleportIn', + 'elevatorIn']), + State.State('walk', + self.enterWalk, + self.exitWalk, + ['push', 'sit', 'stickerBook', + 'WaitForBattle', 'battle', + 'DFA', 'trialerFA', + 'doorOut', 'elevator', + 'tunnelIn', 'tunnelOut', + 'teleportOut', 'quest', + 'stopped', 'fishing', 'purchase', + 'died']), + State.State('sit', + self.enterSit, + self.exitSit, + ['walk',]), + State.State('push', + self.enterPush, + self.exitPush, + ['walk',]), + State.State('stickerBook', + self.enterStickerBook, + self.exitStickerBook, + ['walk', 'push', 'sit', 'battle', + 'DFA', 'trialerFA', + 'doorOut', 'elevator', + 'tunnelIn', 'tunnelOut', + 'WaitForBattle', 'teleportOut', 'quest', + 'stopped', 'fishing', 'purchase', + ]), + State.State('WaitForBattle', + self.enterWaitForBattle, + self.exitWaitForBattle, + ['battle', 'walk']), + State.State('battle', + self.enterBattle, + self.exitBattle, + ['walk', 'teleportOut', 'died']), + State.State('doorIn', + self.enterDoorIn, + self.exitDoorIn, + ['walk']), + State.State('doorOut', + self.enterDoorOut, + self.exitDoorOut, + ['walk']), + State.State('elevatorIn', + self.enterElevatorIn, + self.exitElevatorIn, + ['walk']), + State.State('elevator', + self.enterElevator, + self.exitElevator, + ['walk']), + # Trialer Force Acknowledge: + State.State('trialerFA', + self.enterTrialerFA, + self.exitTrialerFA, + ['trialerFAReject', 'DFA']), + State.State('trialerFAReject', + self.enterTrialerFAReject, + self.exitTrialerFAReject, + ['walk']), + # Download Force Acknowlege: + State.State('DFA', + self.enterDFA, + self.exitDFA, + ['DFAReject', 'teleportOut', 'tunnelOut']), + State.State('DFAReject', + self.enterDFAReject, + self.exitDFAReject, + ['walk']), + State.State('teleportIn', + self.enterTeleportIn, + self.exitTeleportIn, + ['walk', 'teleportOut', 'quietZone', + 'WaitForBattle', 'battle']), + State.State('teleportOut', + self.enterTeleportOut, + self.exitTeleportOut, + ['teleportIn', 'quietZone', + 'WaitForBattle']), + State.State('died', + self.enterDied, + self.exitDied, + ['quietZone']), + State.State('tunnelIn', + self.enterTunnelIn, + self.exitTunnelIn, + ['walk']), + State.State('tunnelOut', + self.enterTunnelOut, + self.exitTunnelOut, + ['final']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['teleportIn']), + State.State('quest', + self.enterQuest, + self.exitQuest, + ['walk','stopped']), + State.State('stopped', + self.enterStopped, + self.exitStopped, + ['walk']), + State.State('stopped', + self.enterStopped, + self.exitStopped, + ['walk']), + State.State('fishing', + self.enterFishing, + self.exitFishing, + ['walk']), + State.State('purchase', + self.enterPurchase, + self.exitPurchase, + ['walk']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + + # Initial State + 'start', + # Final State + 'final', + ) + self.parentFSM = parentFSM + self.tunnelOriginList = [] + self.elevatorDoneEvent = "elevatorDone" + + # For Halloween + self.halloweenLights = [] + + def enter(self, requestStatus, visibilityFlag=1, arrowsOn=1): + # Note: The visibilityFlag was added for the tutorial, which + # doesn't want visibility. TutorialStreet overrides this function, + # calling it with visibilityFlag=0. + assert self.notify.debug("enter(requestStatus="+str(requestStatus) + +")") + self.fsm.enterInitialState() + # Play music + base.playMusic(self.loader.music, looping = 1, volume = 0.8) + self.loader.geom.reparentTo(render) + if visibilityFlag: + self.visibilityOn() + + ## give the camera a nodepath to the geometry + #base.localAvatar.camera.setGeom(self.geom) + base.localAvatar.setGeom(self.loader.geom) + ## tell the camera that we're in a level area + #base.localAvatar.camera.onLevelGround(1) + base.localAvatar.setOnLevelGround(1) + + # Turn on the little red arrows. + NametagGlobals.setMasterArrowsOn(arrowsOn) + + # Turn the sky on + # For halloween + def __lightDecorationOn__(): + geom = base.cr.playGame.getPlace().loader.geom + self.halloweenLights = geom.findAllMatches("**/*light*") + self.halloweenLights += geom.findAllMatches("**/*lamp*") + self.halloweenLights += geom.findAllMatches("**/prop_snow_tree*") + + for light in self.halloweenLights: + #light.reparentTo(render) + light.setColorScaleOff(1) + + newsManager = base.cr.newsManager + + if newsManager: + holidayIds = base.cr.newsManager.getDecorationHolidayId() + if (ToontownGlobals.HALLOWEEN_COSTUMES in holidayIds) and self.loader.hood.spookySkyFile: + + lightsOff = Sequence(LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(0.55, 0.55, 0.65, 1)), + Func(self.loader.hood.startSpookySky), + # Func(__lightDecorationOn__), + ) + + lightsOff.start() + else: + # Turn the sky on + self.loader.hood.startSky() + lightsOn = LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(1, 1, 1, 1)) + lightsOn.start() + else: + # Turn the sky on + self.loader.hood.startSky() + lightsOn = LerpColorScaleInterval( + base.cr.playGame.hood.loader.geom, + 0.1, + Vec4(1, 1, 1, 1)) + lightsOn.start() + + self.accept("doorDoneEvent", self.handleDoorDoneEvent) + self.accept("DistributedDoor_doorTrigger", self.handleDoorTrigger) + + self.enterZone(requestStatus["zoneId"]) + + # Add hooks for the linktunnels + self.tunnelOriginList = base.cr.hoodMgr.addLinkTunnelHooks(self, + self.loader.nodeList, self.zoneId) + + # Note that the following fsm request *could* result in an + # immediate request to teleportOut, which will call exit() and + # unload() before it returns! This will happen in the case in + # which we are teleporting to visit a particular toon, who is + # no longer in this zone by the time we get there (which + # requires immediately teleporting back to the safezone). + self.fsm.request(requestStatus["how"], [requestStatus]) + + def exit(self, visibilityFlag=1): + # See note on "enter" function about visibilityFlag. + assert self.notify.debug("exit()") + + if visibilityFlag: + self.visibilityOff() + self.loader.geom.reparentTo(hidden) + + # For halloween + def __lightDecorationOff__(): + for light in self.halloweenLights: + light.reparentTo(hidden) + + newsManager = base.cr.newsManager + +## if newsManager: +## holidayIds = base.cr.newsManager.getDecorationHolidayId() +## if (ToontownGlobals.HALLOWEEN_COSTUMES in holidayIds) and self.loader.hood.spookySkyFile: +## __lightDecorationOff__() + +## for node in self.tunnelOriginList: +## node.removeNode() +## del self.tunnelOriginList + + # Turn off the little red arrows. + NametagGlobals.setMasterArrowsOn(0) + + # Turn the sky off + self.loader.hood.stopSky() + + # Stop music + self.loader.music.stop() + + ## reset the camera to collide against everything and anything + #base.localAvatar.camera.setGeom(render) + base.localAvatar.setGeom(render) + ## tell the camera we're leaving an area with level ground + #base.localAvatar.camera.onLevelGround(0) + base.localAvatar.setOnLevelGround(0) + + + def load(self): + assert self.notify.debug("load()") + # Call up the chain + BattlePlace.BattlePlace.load(self) + # Prepare the state machine + self.parentFSM.getStateNamed("street").addChild(self.fsm) + + def unload(self): + assert self.notify.debug("unload()") + self.parentFSM.getStateNamed("street").removeChild(self.fsm) + del self.parentFSM + del self.fsm + self.enterZone(None) + + # remove any dfa dialogs + cleanupDialog("globalDialog") + self.ignoreAll() + # Call up the chain + BattlePlace.BattlePlace.unload(self) + + # walk state inherited from BattlePlace.py + + # sticker book state inherited from Place.py + + # battle state inherited from BattlePlace.py + + # elevatorIn state + # (for coming off of an elevator for a victory dance) + def enterElevatorIn(self, requestStatus): + assert self.notify.debug("enterElevatorIn()") + # Whew! Where did bldgDoId get set? + # Look in DistributedSuitInterior.py, in the enterReward state. + # Hey! That's funny... We don't even seem to need this, unless + # we want to use it to place ourselves inside the elevator while + # we wait for the building to start the movie... + bldg = base.cr.doId2do.get(requestStatus['bldgDoId']) + # TODO: Place us in the elevator while we wait for the building + # to start the movie. + + # We throw this event to tell the building that we are ready + # to exit the building now. + messenger.send("insideVictorElevator") + assert bldg + + def exitElevatorIn(self): + assert self.notify.debug("exitElevatorIn()") + + + # elevator state + # (For boarding a building elevator) + def enterElevator(self, distElevator): + assert self.notify.debug("enterElevator()") + + # Disable leave to pay / set parent password + base.localAvatar.cantLeaveGame = 1 + + self.accept(self.elevatorDoneEvent, self.handleElevatorDone) + self.elevator = Elevator.Elevator(self.fsm.getStateNamed("elevator"), + self.elevatorDoneEvent, + distElevator) + self.elevator.load() + self.elevator.enter() + return + + def exitElevator(self): + assert self.notify.debug("exitElevator()") + base.localAvatar.cantLeaveGame = 0 + self.ignore(self.elevatorDoneEvent) + self.elevator.unload() + self.elevator.exit() + del self.elevator + return + + def detectedElevatorCollision(self, distElevator): + assert self.notify.debug("detectedElevatorCollision()") + self.fsm.request("elevator", [distElevator]) + return None + + def handleElevatorDone(self, doneStatus): + assert self.notify.debug("handleElevatorDone()") + self.notify.debug("handling elevator done event") + where = doneStatus['where'] + if (where == 'reject'): + # If there has been a reject the Elevator should show an + # elevatorNotifier message and put the toon in the stopped state. + # Don't request the walk state here. Let the the toon be stuck in the + # stopped state till the player removes that message from his screen. + # Removing the message will automatically put him in the walk state there. + # Put the player in the walk state only if there is no elevator message. + if hasattr(base.localAvatar, "elevatorNotifier") and base.localAvatar.elevatorNotifier.isNotifierOpen(): + pass + else: + self.fsm.request("walk") + elif (where == 'exit'): + self.fsm.request("walk") + elif (where in ('suitInterior', 'cogdoInterior')): + self.doneStatus = doneStatus + messenger.send(self.doneEvent) + else: + self.notify.error("Unknown mode: " + where + + " in handleElevatorDone") + + # tunnel in state partly inherited from Place.py + + def enterTunnelIn(self, requestStatus): + """ + If we are entering a street, we need to show the tunnel + because the collisions are not turned on until we start + walking which means we will not get the onfloor event until + we finish walking through the tunnel which means the tunnel + will be hidden because of visibility + + SO... we explicitly set you into the zone you're headed + towards, which both reveals the tunnel and indicates the + correct zone ID as your current zone, in case anyone needs + to know your zone ID in the next few milliseconds. + """ + assert self.notify.debug("enterTunnelIn(requestStatus="+str(requestStatus)+")") + self.enterZone(requestStatus["zoneId"]) + BattlePlace.BattlePlace.enterTunnelIn(self, requestStatus) + + # tunnel out state inherited from Place.py + + # teleportIn state partly inherited from Place.py + + def enterTeleportIn(self, requestStatus): + assert self.notify.debug("enterTeleportIn(requestStatus="+str(requestStatus)+")") + avId = requestStatus["avId"] + hoodId = requestStatus["hoodId"] + zoneId = requestStatus["zoneId"] + + if avId != -1: + if not base.cr.doId2do.has_key(avId): + # We're trying to teleport to a toon who isn't here + # any more. Forget it, and bail to the safezone. + handle = base.cr.identifyFriend(avId) + requestStatus = {"how" : "teleportIn", + "hoodId" : hoodId, + "zoneId" : hoodId, + "shardId" : None, + "loader" : "safeZoneLoader", + "where" : "playground", + "avId" : avId} + + # We don't actually want to switch to the teleportOut + # state, because that will play the jump-in-the-hole + # animation and everything. Instead, we'll switch to + # the final state, and just directly execute the code + # that takes us out of here. + + # Note that switching states like this in the middle + # of the enterTeleportIn call can potentially confuse + # our parent state objects that didn't expect to + # suddenly switch out of their own state just as they + # were finishing up teleporting in. We will have to + # be careful in coding the parent state objects so + # that they can handle this. + self.fsm.request('final') + self.__teleportOutDone(requestStatus) + return + + # In the normal case, if we're not going directly to an avatar + # (or the avatar we're going to is still in sight), then set + # our zone and go there. + self.enterZone(zoneId) + BattlePlace.BattlePlace.enterTeleportIn(self, requestStatus) + + # teleport out state + + def enterTeleportOut(self, requestStatus): + assert self.notify.debug("enterTeleportOut(requestStatus="+str(requestStatus)+")") + # If the request comes from a battle, let the battle handle + # the teleport animation sequence, otherwise use the distributed + # toon version + if (requestStatus.has_key('battle')): + self.__teleportOutDone(requestStatus) + else: + BattlePlace.BattlePlace.enterTeleportOut(self, requestStatus, + self.__teleportOutDone) + + def __teleportOutDone(self, requestStatus): + assert self.notify.debug("__teleportOutDone(requestStatus="+str(requestStatus)+")") + hoodId = requestStatus["hoodId"] + zoneId = requestStatus["zoneId"] + shardId = requestStatus["shardId"] + if (hoodId == self.loader.hood.id and shardId == None): + # We are in the same hood + if (zoneId == self.zoneId): + # We are teleporting to somebody in the same zone, + # just do it + self.fsm.request("teleportIn", [requestStatus]) + elif (requestStatus["where"]=="street" + and ZoneUtil.getBranchZone(zoneId) + ==self.loader.branchZone): + # If you are teleporting to somebody in this branch. + # In this case, we don't need to leave the town or + # anything, but we do need to be careful with the set + # zone. We can't just send the set-zone message and + # go, since we need to be sure we'll have been told + # about the toon by the time we get there, so we must + # wait for the set-zone message to be completed. + self.fsm.request("quietZone", [requestStatus]) + else: + # Somebody in the same town, different branch, or safe + # zone we have to leave the town mode and come back in + self.doneStatus = requestStatus + messenger.send(self.doneEvent) + else: + # Different hood, we have to leave the town + if (hoodId == ToontownGlobals.MyEstate): + self.getEstateZoneAndGoHome(requestStatus) + else: + self.doneStatus = requestStatus + messenger.send(self.doneEvent) + + def exitTeleportOut(self): + assert self.notify.debug("exitTeleportOut()") + BattlePlace.BattlePlace.exitTeleportOut(self) + + def goHomeFailed(self, task): + # it took too long to hear back from the server, + # or we tried going to a non-friends house + self.notifyUserGoHomeFailed() + # ignore the setLocalEstateZone message + self.ignore("setLocalEstateZone") + self.doneStatus["avId"] = -1 + self.doneStatus["zoneId"] = self.getZoneId() + self.fsm.request("teleportIn", [self.doneStatus]) + return Task.done + + # Quietzone is in Place.py + + def renameFloorPolys(self, nodeList): + assert self.notify.debug("renameFloorPolys()") + for i in nodeList: + # Get all the collision nodes in the vis group + collNodePaths = i.findAllMatches("**/+CollisionNode") + numCollNodePaths = collNodePaths.getNumPaths() + visGroupName = i.node().getName() + for j in range(numCollNodePaths): + collNodePath = collNodePaths.getPath(j) + bitMask = collNodePath.node().getIntoCollideMask() + if bitMask.getBit(1): + # Bit 1 is the floor collision bit. This renames + # all floor collision polys to the same name as their + # visgroup. + collNodePath.node().setName(visGroupName) + + def hideAllVisibles(self): + assert self.notify.debug("hideAllVisibles()") + for i in self.loader.nodeList: + i.stash() + + def showAllVisibles(self): + assert self.notify.debug("showAllVisibles()") + for i in self.loader.nodeList: + i.unstash() + + def visibilityOn(self): + assert self.notify.debug("visibilityOn()") + self.hideAllVisibles() + self.accept("on-floor", self.enterZone) + + def visibilityOff(self): + assert self.notify.debug("visibilityOff()") + self.ignore("on-floor") + self.showAllVisibles() + + def doEnterZone(self, newZoneId): + # Ensure we are in a hood + assert self.loader.nodeDict and self.loader.hood + + # Hide the old zone (if there is one) + if self.zoneId != None: + for i in self.loader.nodeDict[self.zoneId]: + if newZoneId: + if (i not in self.loader.nodeDict[newZoneId]): + self.loader.fadeOutDict[i].start() + self.loader.exitAnimatedProps(i) + else: + i.stash() + self.loader.exitAnimatedProps(i) + + # Show the new zone + if newZoneId != None: + + for i in self.loader.nodeDict[newZoneId]: + # self.notify.debug(str(newZoneId)+" Visgroup: "+ i.getIntoNode().getName()) + if self.zoneId: + if (i not in self.loader.nodeDict[self.zoneId]): + self.loader.fadeInDict[i].start() + self.loader.enterAnimatedProps(i) + else: + # Finish any fade out tracks if they are playing + # otherwise you could teleport to a zone that is fading out + if self.loader.fadeOutDict[i].isPlaying(): + self.loader.fadeOutDict[i].finish() + # Same with fade in + if self.loader.fadeInDict[i].isPlaying(): + self.loader.fadeInDict[i].finish() + self.loader.enterAnimatedProps(i) + i.unstash() + + # Make sure we changed zones + if newZoneId != self.zoneId: + if visualizeZones: + # Set a color override on our zone to make it obvious what + # zone we're in. + if self.zoneId != None: + self.loader.zoneDict[self.zoneId].clearColor() + if newZoneId != None: + self.loader.zoneDict[newZoneId].setColor(0, 0, 1, 1, 100) + + # Tell the server that we changed zones + if newZoneId != None: + base.cr.sendSetZoneMsg(newZoneId) + self.notify.debug("Entering Zone %d" % (newZoneId)) + + # The new zone is now old + self.zoneId = newZoneId + assert self.notify.debug(" newZoneId="+str(newZoneId)) + + geom = base.cr.playGame.getPlace().loader.geom + self.halloweenLights = geom.findAllMatches("**/*light*") + self.halloweenLights += geom.findAllMatches("**/*lamp*") + self.halloweenLights += geom.findAllMatches("**/prop_snow_tree*") + + for light in self.halloweenLights: + #light.reparentTo(render) + light.setColorScaleOff(1) diff --git a/toontown/src/town/TTStreet.py b/toontown/src/town/TTStreet.py new file mode 100644 index 0000000..c94037a --- /dev/null +++ b/toontown/src/town/TTStreet.py @@ -0,0 +1,16 @@ + +import Street + +class TTStreet(Street.Street): + def __init__(self, loader, parentFSM, doneEvent): + Street.Street.__init__(self, loader, parentFSM, doneEvent) + + def load(self): + Street.Street.load(self) + + def unload(self): + Street.Street.unload(self) + + def doRequestLeave(self, requestStatus): + # when it's time to leave, check their trialer status first + self.fsm.request('trialerFA', [requestStatus]) diff --git a/toontown/src/town/TTTownLoader.py b/toontown/src/town/TTTownLoader.py new file mode 100644 index 0000000..2b1c33d --- /dev/null +++ b/toontown/src/town/TTTownLoader.py @@ -0,0 +1,22 @@ + +import TownLoader +import TTStreet +from toontown.suit import Suit + +class TTTownLoader(TownLoader.TownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TownLoader.TownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = TTStreet.TTStreet + self.musicFile = "phase_3.5/audio/bgm/TC_SZ.mid" + self.activityMusicFile = "phase_3.5/audio/bgm/TC_SZ_activity.mid" + self.townStorageDNAFile = "phase_5/dna/storage_TT_town.dna" + + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadSuits(1) + dnaFile = ("phase_5/dna/toontown_central_" + str(self.canonicalBranchZone) + ".dna") + self.createHood(dnaFile) + + def unload(self): + Suit.unloadSuits(1) + TownLoader.TownLoader.unload(self) diff --git a/toontown/src/town/TownBattle.py b/toontown/src/town/TownBattle.py new file mode 100644 index 0000000..699f536 --- /dev/null +++ b/toontown/src/town/TownBattle.py @@ -0,0 +1,938 @@ +from toontown.toonbase.ToontownBattleGlobals import * + +import types +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.fsm import State +import TownBattleAttackPanel +import TownBattleWaitPanel +import TownBattleChooseAvatarPanel +import TownBattleSOSPanel +import TownBattleSOSPetSearchPanel +import TownBattleSOSPetInfoPanel +import TownBattleToonPanel +from toontown.toontowngui import TTDialog +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import BattleBase +from toontown.toonbase import ToontownTimer +from direct.showbase import PythonUtil +from toontown.toonbase import TTLocalizer +from toontown.pets import PetConstants +from direct.gui.DirectGui import DGG +from toontown.battle import FireCogPanel + +class TownBattle(StateData.StateData): + + notify = DirectNotifyGlobal.directNotify.newCategory('TownBattle') + + evenPos = (0.75, 0.25, -0.25, -0.75) + oddPos = (0.5, 0, -0.5) + + def __init__(self, doneEvent): + """ __init__(doneEvent) + """ + StateData.StateData.__init__(self, doneEvent) + + self.numCogs = 1 + self.creditLevel = None + self.luredIndices = [] + self.trappedIndices = [] + + self.numToons = 1 + self.toons = [] + self.localNum = 0 + self.time = 0 + + self.bldg = 0 + self.track = -1 + self.level = -1 + + self.target = 0 + + self.toonAttacks = [(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0)] + + self.fsm = ClassicFSM.ClassicFSM('TownBattle', + [State.State('Off', + self.enterOff, + self.exitOff, + ['Attack']), + State.State('Attack', + self.enterAttack, + self.exitAttack, + ['ChooseCog', + 'ChooseToon', + 'AttackWait', + 'Run', + 'Fire', + 'SOS',]), + State.State('ChooseCog', + self.enterChooseCog, + self.exitChooseCog, + ['AttackWait', + 'Attack']), + State.State('AttackWait', + self.enterAttackWait, + self.exitAttackWait, + ['ChooseCog', + 'ChooseToon', + 'Attack']), + State.State('ChooseToon', + self.enterChooseToon, + self.exitChooseToon, + ['AttackWait', + 'Attack']), + State.State('Run', + self.enterRun, + self.exitRun, + ['Attack']), + State.State('SOS', + self.enterSOS, + self.exitSOS, + ['Attack', + 'AttackWait', + 'SOSPetSearch', + 'SOSPetInfo']), + State.State('SOSPetSearch', + self.enterSOSPetSearch, + self.exitSOSPetSearch, + ['SOS', + 'SOSPetInfo']), + State.State('SOSPetInfo', + self.enterSOSPetInfo, + self.exitSOSPetInfo, + ['SOS', + 'AttackWait']), + State.State('Fire', + self.enterFire, + self.exitFire, + ['Attack', + 'AttackWait',]), + ], + # Initial state + 'Off', + # Final state + 'Off', + ) + + self.runPanel = TTDialog.TTDialog( + dialogName = "TownBattleRunPanel", + text = TTLocalizer.TownBattleRun, + style = TTDialog.TwoChoice, + command = self.__handleRunPanelDone, + ) + self.runPanel.hide() + + self.attackPanelDoneEvent = 'attack-panel-done' + self.attackPanel = TownBattleAttackPanel.TownBattleAttackPanel( + self.attackPanelDoneEvent) + + self.waitPanelDoneEvent = 'wait-panel-done' + self.waitPanel = TownBattleWaitPanel.TownBattleWaitPanel( + self.waitPanelDoneEvent) + + self.chooseCogPanelDoneEvent = 'choose-cog-panel-done' + self.chooseCogPanel = \ + TownBattleChooseAvatarPanel.TownBattleChooseAvatarPanel( + self.chooseCogPanelDoneEvent, 0) + + self.chooseToonPanelDoneEvent = 'choose-toon-panel-done' + self.chooseToonPanel = \ + TownBattleChooseAvatarPanel.TownBattleChooseAvatarPanel( + self.chooseToonPanelDoneEvent, 1) + + self.SOSPanelDoneEvent = 'SOS-panel-done' + self.SOSPanel = TownBattleSOSPanel.TownBattleSOSPanel( + self.SOSPanelDoneEvent) + + self.SOSPetSearchPanelDoneEvent = 'SOSPetSearch-panel-done' + self.SOSPetSearchPanel = TownBattleSOSPetSearchPanel.TownBattleSOSPetSearchPanel(self.SOSPetSearchPanelDoneEvent) + + self.SOSPetInfoPanelDoneEvent = 'SOSPetInfo-panel-done' + self.SOSPetInfoPanel = TownBattleSOSPetInfoPanel.TownBattleSOSPetInfoPanel(self.SOSPetInfoPanelDoneEvent) + + + + self.fireCogPanelDoneEvent = 'fire-cog-panel-done' + self.FireCogPanel = FireCogPanel.FireCogPanel( + self.fireCogPanelDoneEvent) + + self.cogFireCosts = [None, None, None, None] + + + + + # These are not StateDatas, so they have no doneEvents + self.toonPanels = (TownBattleToonPanel.TownBattleToonPanel(0), + TownBattleToonPanel.TownBattleToonPanel(1), + TownBattleToonPanel.TownBattleToonPanel(2), + TownBattleToonPanel.TownBattleToonPanel(3)) + + self.timer = ToontownTimer.ToontownTimer() + self.timer.setPos(1.182, 0, 0.842) + self.timer.setScale(0.4) + self.timer.hide() + return + + def cleanup(self): + """ cleanup() + """ + self.ignore(self.attackPanelDoneEvent) + self.unload() + del self.fsm + self.runPanel.cleanup() + del self.runPanel + del self.attackPanel + del self.waitPanel + del self.chooseCogPanel + del self.chooseToonPanel + del self.SOSPanel + del self.FireCogPanel + del self.SOSPetSearchPanel + del self.SOSPetInfoPanel + for toonPanel in self.toonPanels: + toonPanel.cleanup() + del self.toonPanels + self.timer.destroy() + del self.timer + del self.toons + return + + def enter(self, event, parentFSMState, bldg=0, creditMultiplier=1, + tutorialFlag=0): + """ enter(event) + """ + self.parentFSMState = parentFSMState + self.parentFSMState.addChild(self.fsm) + + if (not self.isLoaded): + self.load() + print("Battle Event %s" % (event)) + self.battleEvent = event + self.fsm.enterInitialState() + base.localAvatar.laffMeter.start() + self.numToons = 1 + self.numCogs = 1 + self.toons = [base.localAvatar.doId] + self.toonPanels[0].setLaffMeter(base.localAvatar) + self.bldg = bldg + self.creditLevel = None + self.creditMultiplier = creditMultiplier + self.tutorialFlag = tutorialFlag + base.localAvatar.inventory.setBattleCreditMultiplier(self.creditMultiplier) + base.localAvatar.inventory.setActivateMode( + 'battle', heal=0, bldg=bldg, tutorialFlag=tutorialFlag) + self.SOSPanel.bldg = bldg + + def exit(self): + """ exit() + """ + base.localAvatar.laffMeter.stop() + self.parentFSMState.removeChild(self.fsm) + del self.parentFSMState + base.localAvatar.inventory.setBattleCreditMultiplier(1) + + def load(self): + """ load() + """ + if (self.isLoaded): + return + self.attackPanel.load() + self.waitPanel.load() + self.chooseCogPanel.load() + self.chooseToonPanel.load() + self.SOSPanel.load() + self.SOSPetSearchPanel.load() + self.SOSPetInfoPanel.load() + self.isLoaded = 1 + + def unload(self): + """ unload() + """ + if (not self.isLoaded): + return + self.attackPanel.unload() + self.waitPanel.unload() + self.chooseCogPanel.unload() + self.chooseToonPanel.unload() + self.FireCogPanel.unload() + self.SOSPanel.unload() + self.SOSPetSearchPanel.unload() + self.SOSPetInfoPanel.unload() + self.isLoaded = 0 + + def setState(self, state): + """ setState(state) + """ + # The distributed battle does some set states after the + # localtoon has left the battle and this fsm has been deleted + if hasattr(self, 'fsm'): + self.fsm.request(state) + + def updateTimer(self, time): + """ updateTimer(time) + """ + self.time = time + self.timer.setTime(time) + return None + + def __enterPanels(self, num, localNum): + """ __enterPanels(num, localNum) + """ + self.notify.debug('enterPanels() num: %d localNum: %d' % \ + (num, localNum)) + # Hide all the toon panels, and position them low on the screen + for toonPanel in self.toonPanels: + toonPanel.hide() + # toonPanel.setPos(0, 0, -0.58) + toonPanel.setPos(0, 0, -0.9) + + if num == 1: + self.toonPanels[0].setX(self.oddPos[1]) + self.toonPanels[0].show() + elif num == 2: + self.toonPanels[0].setX(self.evenPos[1]) + self.toonPanels[0].show() + self.toonPanels[1].setX(self.evenPos[2]) + self.toonPanels[1].show() + elif num == 3: + self.toonPanels[0].setX(self.oddPos[0]) + self.toonPanels[0].show() + self.toonPanels[1].setX(self.oddPos[1]) + self.toonPanels[1].show() + self.toonPanels[2].setX(self.oddPos[2]) + self.toonPanels[2].show() + elif num == 4: + self.toonPanels[0].setX(self.evenPos[0]) + self.toonPanels[0].show() + self.toonPanels[1].setX(self.evenPos[1]) + self.toonPanels[1].show() + self.toonPanels[2].setX(self.evenPos[2]) + self.toonPanels[2].show() + self.toonPanels[3].setX(self.evenPos[3]) + self.toonPanels[3].show() + else: + self.notify.error("Bad number of toons: %s" % num) + return None + + def updateChosenAttacks(self, battleIndices, tracks, levels, targets): + """ updateChosenAttacks(tracks, levels, targets, toons) + toonIndices: The battle indices for these parallel arrays + tracks: An array of four tracks, one for each player + levels: An array of four levels, one for each player + targets: An array for four indices, either toon or suit, depending + """ + self.notify.debug("updateChosenAttacks bi=%s tracks=%s levels=%s targets=%s" + % (battleIndices, tracks, levels, targets)) + for i in range(4): + # Determine the number of possible targets for this attack, + # and whether it is a group attack + if battleIndices[i] == -1: + pass + else: + if tracks[i] == BattleBase.NO_ATTACK: + numTargets = 0 + target = -2 + elif tracks[i] == BattleBase.PASS_ATTACK: + numTargets = 0 + target = -2 + elif (tracks[i] == BattleBase.SOS or + tracks[i] == BattleBase.NPCSOS or + tracks[i] == BattleBase.PETSOS): + numTargets = 0 + target = -2 + elif tracks[i] == HEAL_TRACK: + # A heal + numTargets = self.numToons + if self.__isGroupHeal(levels[i]): + # -2 means group heal + target = -2 + else: + target = targets[i] + else: + # An attack + numTargets = self.numCogs + if self.__isGroupAttack(tracks[i], levels[i]): + # -1 means group attack + target = -1 + else: + target = targets[i] + if target == -1: + # We haven't chosen a target yet. + numTargets = None + + self.toonPanels[battleIndices[i]].setValues(battleIndices[i], + tracks[i], levels[i], numTargets, target, self.localNum) + + return None + + def chooseDefaultTarget(self): + """ chooseDefaultTarget() + """ + if (self.track > -1): + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + response['target'] = self.target + messenger.send(self.battleEvent, [response]) + return 1 + return 0 + + def updateLaffMeter(self, toonNum, hp): + """ updateLaffMeter(toonNum, hp) + """ + self.toonPanels[toonNum].updateLaffMeter(hp) + + # Each state will have an enter function, an exit function, + # and a datagram handler, which will be set during each enter function. + + # Specific State functions + + ##### Off state ##### + + def enterOff(self): + if (self.isLoaded): + # Hide the toon panels + for toonPanel in self.toonPanels: + toonPanel.hide() + self.toonAttacks = [(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0)] + self.target = 0 + # Hide the timer + if hasattr(self, "timer"): + self.timer.hide() + return None + + def exitOff(self): + if (self.isLoaded): + self.__enterPanels(self.numToons, self.localNum) + # Show the timer + self.timer.show() + # Clear the attack values + self.track = -1 + self.level = -1 + self.target = 0 + return None + + ##### Attack state ##### + + def enterAttack(self): + self.attackPanel.enter() + self.accept(self.attackPanelDoneEvent, self.__handleAttackPanelDone) + # Clear the attack choices for all four toon panels + # TODO: do not reset all the panels - some Toons may have chosen their attack + # and you are going back and choosing a new attack + for toonPanel in self.toonPanels: + toonPanel.setValues(0, BattleBase.NO_ATTACK) + return None + + def exitAttack(self): + self.ignore(self.attackPanelDoneEvent) + self.attackPanel.exit() + return None + + def __handleAttackPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + self.notify.debug('doneStatus: %s' % doneStatus) + mode = doneStatus['mode'] + if (mode == 'Inventory'): + self.track = doneStatus['track'] + self.level = doneStatus['level'] + + # Update your own toon panel + self.toonPanels[self.localNum].setValues(self.localNum, self.track, self.level) + + if (self.track == HEAL_TRACK): + if self.__isGroupHeal(self.level): + # For group heals, no choice needs to be made, and no + # target is required. + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + response['target'] = self.target + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + else: + # For single heals, we may ask the user for a choice. + if ((self.numToons == 3) or (self.numToons == 4)): + # If there are 3 or 4 toons, a choice must be made. + self.fsm.request('ChooseToon') + elif (self.numToons == 2): + # If there are only two toons, then the other toon + # is the default target + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + # Figure out the index of the other guy + if self.localNum == 0: + response['target'] = 1 + elif self.localNum == 1: + response['target'] = 0 + else: + self.notify.error("Bad localNum value: %s" % + self.localNum) + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + else: + # You can't heal yourself, so only 2, 3, and 4 are valid + # values. + self.notify.error( + "Heal was chosen when number of toons is %s" % + self.numToons) + else: + # If it isn't heal, then it is an attack. + if self.__isCogChoiceNecessary(): + self.notify.debug("choice needed") + self.fsm.request('ChooseCog') + + # Now we send an Attack message anyway, so other + # toons in the battle are notified of our choice + # of track, even before we choose a target. The + # AI will know this doesn't count as a full choice + # yet. + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + response['target'] = -1 + messenger.send(self.battleEvent, [response]) + + else: + self.notify.debug("no choice needed") + self.fsm.request('AttackWait') + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + # If there is only one cog, then the target is cog 0 + response['target'] = 0 + messenger.send(self.battleEvent, [response]) + + elif (mode == 'Run'): + self.fsm.request('Run') + elif (mode == 'SOS'): + self.fsm.request('SOS') + elif (mode == 'Fire'): + self.fsm.request('Fire') + elif (mode == 'Pass'): + response = {} + response['mode'] = 'Pass' + response['id'] = -1 + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + else: + self.notify.warning('unknown mode: %s' % mode) + + ##### ChooseCog state ##### + + def checkHealTrapLure(self): + # Assume you can do all of these, then check each case + + self.notify.debug('numToons: %s, numCogs: %s, lured: %s, trapped: %s' % + (self.numToons, self.numCogs, self.luredIndices, self.trappedIndices)) + + # If everybody is trapped or lured, you cannot trap + if (len(PythonUtil.union(self.trappedIndices, self.luredIndices)) == self.numCogs): + canTrap = 0 + else: + canTrap = 1 + + # If all cogs are lured, no trap or lure + if (len(self.luredIndices) == self.numCogs): + canLure = 0 + canTrap = 0 + else: + canLure = 1 + + # If there is only one toon in battle, he cannot heal + if self.numToons == 1: + canHeal = 0 + else: + canHeal = 1 + + return canHeal, canTrap, canLure + + def adjustCogsAndToons(self, cogs, luredIndices, trappedIndices, toons): + numCogs = len(cogs) + assert numCogs > 0 + + self.notify.debug('adjustCogsAndToons() numCogs: %s self.numCogs: %s' % + (numCogs, self.numCogs)) + self.notify.debug('adjustCogsAndToons() luredIndices: %s self.luredIndices: %s' % + (luredIndices, self.luredIndices)) + self.notify.debug('adjustCogsAndToons() trappedIndices: %s self.trappedIndices: %s' % + (trappedIndices, self.trappedIndices)) + toonIds = map(lambda toon: toon.doId, toons) + self.notify.debug('adjustCogsAndToons() toonIds: %s self.toons: %s' % + (toonIds, self.toons)) + + # Determine the maximum level attack item we'll get credit + # for, based on the suits in the battle. This is the same as + # the highest level suit we're currently facing. + maxSuitLevel = 0 + cogFireCostIndex = 0 + for cog in cogs: + maxSuitLevel = max(maxSuitLevel, cog.getActualLevel()) + self.cogFireCosts[cogFireCostIndex] = 1#cog.getActualLevel() + cogFireCostIndex += 1 + creditLevel = maxSuitLevel + + # If nothing changed, we do not need to reactivate the gui + if ((numCogs == self.numCogs) and + (creditLevel == self.creditLevel) and + (luredIndices == self.luredIndices) and + (trappedIndices == self.trappedIndices) and + (toonIds == self.toons)): + resetActivateMode = 0 + else: + resetActivateMode = 1 + + self.notify.debug('adjustCogsAndToons() resetActivateMode: %s' % + (resetActivateMode)) + + self.numCogs = numCogs + self.creditLevel = creditLevel + self.luredIndices = luredIndices + self.trappedIndices = trappedIndices + self.toons = toonIds + self.numToons = len(toons) + self.localNum = toons.index(base.localAvatar) + currStateName = self.fsm.getCurrentState().getName() + + + if resetActivateMode: + self.__enterPanels(self.numToons, self.localNum) + # New toons means an adjustment to the laff meters. + for i in range(len(toons)): + self.toonPanels[i].setLaffMeter(toons[i]) + + # If we are picking a cog or a toon, those panels need to be adjusted + if (currStateName == 'ChooseCog'): + self.chooseCogPanel.adjustCogs(self.numCogs, self.luredIndices, + self.trappedIndices, self.track) + elif (currStateName == 'ChooseToon'): + self.chooseToonPanel.adjustToons(self.numToons, self.localNum) + + canHeal, canTrap, canLure = self.checkHealTrapLure() + base.localAvatar.inventory.setBattleCreditMultiplier(self.creditMultiplier) + base.localAvatar.inventory.setActivateMode( + 'battle', heal=canHeal, trap=canTrap, lure=canLure, + bldg=self.bldg, creditLevel=self.creditLevel, + tutorialFlag=self.tutorialFlag) + return + + def enterChooseCog(self): + self.cog = 0 + self.chooseCogPanel.enter(self.numCogs, + luredIndices = self.luredIndices, + trappedIndices = self.trappedIndices, + track = self.track) + self.accept(self.chooseCogPanelDoneEvent, + self.__handleChooseCogPanelDone) + return None + + def exitChooseCog(self): + self.ignore(self.chooseCogPanelDoneEvent) + self.chooseCogPanel.exit() + return None + + def __handleChooseCogPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Back'): + self.fsm.request('Attack') + elif (mode == 'Avatar'): + self.cog = doneStatus['avatar'] + self.target = self.cog + self.fsm.request('AttackWait') + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + response['target'] = self.cog + messenger.send(self.battleEvent, [response]) + else: + self.notify.warning('unknown mode: %s' % mode) + + ##### AttackWait state ##### + + def enterAttackWait(self, chosenToon=-1): + self.accept(self.waitPanelDoneEvent, self.__handleAttackWaitBack) + self.waitPanel.enter(self.numToons) + + def exitAttackWait(self): + self.waitPanel.exit() + self.ignore(self.waitPanelDoneEvent) + + def __handleAttackWaitBack(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Back'): + if (self.track == HEAL_TRACK): + # If a heal was chosen, go back to choose toon if there are + # more than 2 toons, otherwise go to the attack panel. + # Nope! That doesn't happen anymore. Now, if you have chosen + # a heal, it always makes you go back to the attack panel. This + # is the cowardly way of handling the situation where the selected + # healee flees the battle. + #if self.numToons > 2: + # self.fsm.request('ChooseToon') + #else: + self.fsm.request('Attack') + elif (self.track == BattleBase.NO_ATTACK): + # Must have come back from passing, show them the attack panel again + self.fsm.request('Attack') + else: + # An attack was chosen. Go back to choose a cog or to choose + # an attack, appropriately. + if self.__isCogChoiceNecessary(): + self.fsm.request('ChooseCog') + else: + self.fsm.request('Attack') + # Clear out whatever attack choice was sent to the server + response = {} + response['mode'] = 'UnAttack' + messenger.send(self.battleEvent, [response]) + else: + self.notify.error('unknown mode: %s' % mode) + + ##### ChooseToon state ##### + + def enterChooseToon(self): + self.toon = 0 + self.chooseToonPanel.enter(self.numToons, + localNum = self.localNum) + self.accept(self.chooseToonPanelDoneEvent, + self.__handleChooseToonPanelDone) + return None + + def exitChooseToon(self): + self.ignore(self.chooseToonPanelDoneEvent) + self.chooseToonPanel.exit() + return None + + def __handleChooseToonPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Back'): + self.fsm.request('Attack') + elif (mode == 'Avatar'): + self.toon = doneStatus['avatar'] + self.target = self.toon + self.fsm.request('AttackWait', [self.toon]) + response = {} + response['mode'] = 'Attack' + response['track'] = self.track + response['level'] = self.level + response['target'] = self.toon + messenger.send(self.battleEvent, [response]) + else: + self.notify.warning('unknown mode: %s' % mode) + + ##### Run state ##### + + def enterRun(self): + self.runPanel.show() + + def exitRun(self): + self.runPanel.hide() + + def __handleRunPanelDone(self, doneStatus): + if (doneStatus == DGG.DIALOG_OK): + response = {} + response['mode'] = 'Run' + messenger.send(self.battleEvent, [response]) + else: + self.fsm.request('Attack') + + + ##### Fire state ##### + + def enterFire(self): + canHeal, canTrap, canLure = self.checkHealTrapLure() + #import pdb; pdb.set_trace() + self.FireCogPanel.enter(self.numCogs, + luredIndices = self.luredIndices, + trappedIndices = self.trappedIndices, + track = self.track, fireCosts = self.cogFireCosts) + self.accept(self.fireCogPanelDoneEvent, self.__handleCogFireDone) + return None + + def exitFire(self): + self.ignore(self.fireCogPanelDoneEvent) + self.FireCogPanel.exit() + return None + + + def __handleCogFireDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Back'): + self.fsm.request('Attack') + elif (mode == 'Avatar'): + self.cog = doneStatus['avatar'] + self.target = self.cog + self.fsm.request('AttackWait') + response = {} + response['mode'] = 'Fire' + response['target'] = self.cog + #import pdb; pdb.set_trace() + messenger.send(self.battleEvent, [response]) + else: + self.notify.warning('unknown mode: %s' % mode) + + + + ##### SOS state ##### + + def enterSOS(self): + canHeal, canTrap, canLure = self.checkHealTrapLure() + self.SOSPanel.enter(canLure, canTrap) + self.accept(self.SOSPanelDoneEvent, self.__handleSOSPanelDone) + return None + + def exitSOS(self): + self.ignore(self.SOSPanelDoneEvent) + self.SOSPanel.exit() + return None + + def __handleSOSPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Friend'): + doId = doneStatus['friend'] + # TODO: Something must be done with this handle... I think we are + # supposed to use it for something... + # self.fsm.request('SOSWait', [handle]) + response = {} + response['mode'] = 'SOS' + response['id'] = doId + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + elif (mode == 'Pet'): + self.petId = doneStatus['petId'] + self.petName = doneStatus['petName'] + self.fsm.request('SOSPetSearch') + elif (mode == 'NPCFriend'): + doId = doneStatus['friend'] + response = {} + response['mode'] = 'NPCSOS' + response['id'] = doId + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + elif (mode == 'Back'): + self.fsm.request('Attack') + + ##### SOSPetSearch state ##### + + def enterSOSPetSearch(self): + response = {} + response['mode'] = 'PETSOSINFO' + response['id'] = self.petId + self.SOSPetSearchPanel.enter(self.petId, self.petName) + self.proxyGenerateMessage = 'petProxy-%d-generated' % self.petId + self.accept(self.proxyGenerateMessage, self.__handleProxyGenerated) + self.accept(self.SOSPetSearchPanelDoneEvent, self.__handleSOSPetSearchPanelDone) + messenger.send(self.battleEvent, [response]) + return None + + def exitSOSPetSearch(self): + self.ignore(self.proxyGenerateMessage) + self.ignore(self.SOSPetSearchPanelDoneEvent) + self.SOSPetSearchPanel.exit() + return None + + def __handleSOSPetSearchPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'Back'): + self.fsm.request('SOS') + else: + self.notify.error("invalid mode in handleSOSPetSearchPanelDone") + + def __handleProxyGenerated(self): + self.fsm.request('SOSPetInfo') + + ##### SOSPetInfo state ##### + + def enterSOSPetInfo(self): + self.SOSPetInfoPanel.enter(self.petId) + self.accept(self.SOSPetInfoPanelDoneEvent, self.__handleSOSPetInfoPanelDone) + return None + + def exitSOSPetInfo(self): + self.ignore(self.SOSPetInfoPanelDoneEvent) + self.SOSPetInfoPanel.exit() + return None + + def __handleSOSPetInfoPanelDone(self, doneStatus): + assert doneStatus.has_key('mode') + mode = doneStatus['mode'] + if (mode == 'OK'): + response = {} + response['mode'] = 'PETSOS' + response['id'] = self.petId + response['trickId'] = doneStatus['trickId'] + messenger.send(self.battleEvent, [response]) + self.fsm.request('AttackWait') + # We've requested that our pet do a trick. This is going to + # change its mood in the DB, so flag our mood as 'dirty' so + # that the pet panel will query the game server and not use + # old cached mood data. + bboard.post(PetConstants.OurPetsMoodChangedKey, True) + elif (mode == 'Back'): + self.fsm.request('SOS') + + def __isCogChoiceNecessary(self): + # If there is more than one cog, and a non-group attack + # has been selected, a cog choice needs to be made. + if ((self.numCogs > 1) and (not self.__isGroupAttack(self.track, + self.level))): + return 1 + else: + return 0 + + def __isGroupAttack(self, trackNum, levelNum): + """ + Lets you know if the given attack is a group attack. + """ + # Sanity checks + assert isinstance(trackNum, types.IntType) + assert isinstance(levelNum, types.IntType) + assert((trackNum >= MIN_TRACK_INDEX) and (trackNum <= MAX_TRACK_INDEX) or + (trackNum == BattleBase.NO_ATTACK) or + (trackNum == BattleBase.SOS) or + (trackNum == BattleBase.NPCSOS) or + (trackNum == BattleBase.PETSOS) or + (trackNum == BattleBase.FIRE)) + assert ((levelNum >= MIN_LEVEL_INDEX) and (levelNum <= MAX_LEVEL_INDEX) or + (trackNum == BattleBase.FIRE) and (levelNum == -1)) + + + #RAU 2006/08/02 for uber gags, we consult BattleBase.AttackAffectsGroup + + # Sound attacks are group attacks + #if trackNum == SOUND_TRACK: + # return 1 + # Odd numbered lure attacks are group attacks + #elif ((trackNum == LURE_TRACK) and + # (levelNum % 2)): + # return 1 + #else: + # return 0 + + retval = BattleBase.attackAffectsGroup(trackNum, levelNum) + return retval + + + + def __isGroupHeal(self, levelNum): + # Odd numbered heals are group heals JML- not any more + #if levelNum % 2: + # return 1 + #else: + # return 0 + retval = BattleBase.attackAffectsGroup(HEAL_TRACK, levelNum) + return retval + diff --git a/toontown/src/town/TownBattleAttackPanel.py b/toontown/src/town/TownBattleAttackPanel.py new file mode 100644 index 0000000..ba67d2b --- /dev/null +++ b/toontown/src/town/TownBattleAttackPanel.py @@ -0,0 +1,109 @@ + +from pandac.PandaModules import * + +from direct.directnotify import DirectNotifyGlobal +import string +from direct.fsm import StateData + +AttackPanelHidden = 0 +def hideAttackPanel(flag): + """ + This function is just called by the ~hideAttack or ~showAttack + magic words. If flag is true, the attack panel is hidden (so you + can see what's going on). If flag is false, the attack panel is + revealed again. + """ + global AttackPanelHidden + AttackPanelHidden = flag + messenger.send('hide-attack-panel') + +class TownBattleAttackPanel(StateData.StateData): + """ + This is the main menu for choosing an attack, a toon up, run away, or SOS. + Actually it is just a thin wrapper around the Inventory class + """ + + def __init__(self, doneEvent): + StateData.StateData.__init__(self, doneEvent) + return + + def load(self): + StateData.StateData.load(self) + + def unload(self): + StateData.StateData.unload(self) + + def enter(self): + StateData.StateData.enter(self) + # Display the inventory + if not AttackPanelHidden: + base.localAvatar.inventory.show() + # If someone clicks an item, we want to know about it. + self.accept('inventory-selection', self.__handleInventory) + self.accept('inventory-run', self.__handleRun) + self.accept('inventory-sos', self.__handleSOS) + self.accept('inventory-pass', self.__handlePass) + self.accept('inventory-fire', self.__handleFire) + self.accept('hide-attack-panel', self.__handleHide) + # The battle panel uses up a lot of space onscreen; in + # particular, it will hide all the chat balloons. Force these + # to go to the margins. + # NametagGlobals.setOnscreenChatForced(1) + return + + def exit(self): + StateData.StateData.exit(self) + # Ignore the inventory selection + self.ignore('inventory-selection') + self.ignore('inventory-run') + self.ignore('inventory-sos') + self.ignore('inventory-pass') + self.ignore('inventory-fire') + self.ignore('hide-attack-panel') + # Put the inventory away + base.localAvatar.inventory.hide() + # Restore the normal chat behavior. + # NametagGlobals.setOnscreenChatForced(0) + return + + def __handleRun(self): + doneStatus = {'mode':'Run'} + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handleSOS(self): + doneStatus = {'mode':'SOS'} + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handlePass(self): + doneStatus = {'mode':'Pass'} + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handleFire(self): + doneStatus = {'mode':'Fire'} + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handleInventory(self, track, level): + if (base.localAvatar.inventory.numItem(track, level) > 0): + # Report the selection + doneStatus = {} + doneStatus['mode'] = 'Inventory' + doneStatus['track'] = track + doneStatus['level'] = level + messenger.send(self.doneEvent, [doneStatus]) + else: + self.notify.error( + "An item we don't have: track %s level %s was selected." % + [track, level]) + return + + def __handleHide(self): + # Just used for magic words. + if AttackPanelHidden: + base.localAvatar.inventory.hide() + else: + base.localAvatar.inventory.show() + return diff --git a/toontown/src/town/TownBattleChooseAvatarPanel.py b/toontown/src/town/TownBattleChooseAvatarPanel.py new file mode 100644 index 0000000..b7c9d9e --- /dev/null +++ b/toontown/src/town/TownBattleChooseAvatarPanel.py @@ -0,0 +1,197 @@ +from toontown.toonbase.ToontownBattleGlobals import * +from toontown.toonbase import ToontownGlobals +from direct.fsm import StateData +from direct.directnotify import DirectNotifyGlobal +from toontown.battle import BattleBase +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class TownBattleChooseAvatarPanel(StateData.StateData): + """TownBattleChooseAvatarPanel + This is the panel used for choosing a avatar to attack. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('ChooseAvatarPanel') + + def __init__(self, doneEvent, toon): + self.notify.debug("Init choose panel...") + StateData.StateData.__init__(self, doneEvent) + # How many avatars in the battle? + self.numAvatars = 0 + # Which was picked? + self.chosenAvatar = 0 + # Is this for toons? (or suits?) + self.toon = toon + return + + def load(self): + gui = loader.loadModel("phase_3.5/models/gui/battle_gui") + self.frame = DirectFrame( + relief = None, + image = gui.find("**/BtlPick_TAB"), + image_color = Vec4(1,0.2,0.2,1), + ) + self.frame.hide() + + self.statusFrame = DirectFrame( + parent = self.frame, + relief = None, + image = gui.find("**/ToonBtl_Status_BG"), + image_color = Vec4(0.5,0.9,0.5,1), + pos = (0.611, 0, 0), + ) + + self.textFrame = DirectFrame( + parent = self.frame, + relief = None, + image = gui.find("**/PckMn_Select_Tab"), + image_color = Vec4(1,1,0,1), + text = "", + text_fg = Vec4(0,0,0,1), + text_pos = (0,-0.025,0), + text_scale = 0.08, + pos = (-0.013, 0, 0.013), + ) + if self.toon: + self.textFrame['text'] = TTLocalizer.TownBattleChooseAvatarToonTitle + else: + self.textFrame['text'] = TTLocalizer.TownBattleChooseAvatarCogTitle + + self.avatarButtons = [] + for i in range(4): + button = DirectButton( + parent = self.frame, + relief = None, + image = (gui.find("**/PckMn_Arrow_Up"), + gui.find("**/PckMn_Arrow_Dn"), + gui.find("**/PckMn_Arrow_Rlvr")), + command = self.__handleAvatar, + extraArgs = [i], + ) + if self.toon: + button.setScale(1,1,-1) + button.setPos(0,0,-0.2) + else: + button.setScale(1,1,1) + button.setPos(0,0,0.2) + self.avatarButtons.append(button) + + self.backButton = DirectButton( + parent = self.frame, + relief = None, + image = (gui.find("**/PckMn_BackBtn"), + gui.find("**/PckMn_BackBtn_Dn"), + gui.find("**/PckMn_BackBtn_Rlvr")), + pos = (-0.647, 0, 0.006), + scale = 1.05, + text = TTLocalizer.TownBattleChooseAvatarBack, + text_scale = 0.05, + text_pos = (0.01,-0.012), + text_fg = Vec4(0,0,0.8,1), + command = self.__handleBack, + ) + + gui.removeNode() + + return + + def unload(self): + """unload(self) + """ + self.frame.destroy() + del self.frame + del self.statusFrame + del self.textFrame + del self.avatarButtons + del self.backButton + return + + def enter(self, numAvatars, localNum=None, luredIndices=None, trappedIndices=None, track=None): + # Show the panel + self.frame.show() + # Place the buttons + # Suits that are lured should not be available to select for + # certain attacks + invalidTargets = [] + if not self.toon: + if (len(luredIndices) > 0): + # You can't place a trap in front of a suit that is already lured + if (track == BattleBase.TRAP or track == BattleBase.LURE): + invalidTargets += luredIndices + if (len(trappedIndices) > 0): + # You can't place a trap in front of a suit that is already trapped + if (track == BattleBase.TRAP): + invalidTargets += trappedIndices + self.__placeButtons(numAvatars, invalidTargets, localNum) + # Force chat balloons to the margins while this is up. + # NametagGlobals.setOnscreenChatForced(1) + return + + def exit(self): + # Hide the panel + self.frame.hide() + # NametagGlobals.setOnscreenChatForced(0) + return + + def __handleBack(self): + doneStatus = {'mode' : 'Back'} + messenger.send(self.doneEvent, [doneStatus]) + return + + def __handleAvatar(self, avatar): + doneStatus = {'mode' : 'Avatar', + 'avatar' : avatar} + messenger.send(self.doneEvent, [doneStatus]) + return + + def adjustCogs(self, numAvatars, luredIndices, trappedIndices, track): + # Suits that are lured should not be available to select for + # certain attacks + invalidTargets = [] + if (len(luredIndices) > 0): + # You can't place a trap in front of a suit that is already lured + if (track == BattleBase.TRAP or track == BattleBase.LURE): + invalidTargets += luredIndices + if (len(trappedIndices) > 0): + # You can't place a trap in front of a suit that is already trapped + if (track == BattleBase.TRAP): + invalidTargets += trappedIndices + self.__placeButtons(numAvatars, invalidTargets, None) + return + + def adjustToons(self, numToons, localNum): + self.__placeButtons(numToons, [], localNum) + return + + def __placeButtons(self, numAvatars, invalidTargets, localNum): + # Place the buttons. NOTE: Remember, from the toons point of view + # the avatars are numbered from right to left. + for i in range(4): + # Only show the button if this avatar is in the battle + # and he is not in the invalidTargets list + if ((numAvatars > i) and (i not in invalidTargets) and (i != localNum)): + self.avatarButtons[i].show() + else: + self.avatarButtons[i].hide() + + # Evenly positions the buttons on the bar + if numAvatars == 1: + self.avatarButtons[0].setX(0) + elif numAvatars == 2: + self.avatarButtons[0].setX(0.2) + self.avatarButtons[1].setX(-0.2) + elif numAvatars == 3: + self.avatarButtons[0].setX(0.4) + self.avatarButtons[1].setX(0.0) + self.avatarButtons[2].setX(-0.4) + elif numAvatars == 4: + self.avatarButtons[0].setX(0.6) + self.avatarButtons[1].setX(0.2) + self.avatarButtons[2].setX(-0.2) + self.avatarButtons[3].setX(-0.6) + else: + self.notify.error("Invalid number of avatars: %s" % numAvatars) + + return None + + diff --git a/toontown/src/town/TownBattleSOSPanel.py b/toontown/src/town/TownBattleSOSPanel.py new file mode 100644 index 0000000..aa69f1e --- /dev/null +++ b/toontown/src/town/TownBattleSOSPanel.py @@ -0,0 +1,400 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToontownGlobals import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.showbase import DirectObject +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import StateData +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +import types +from toontown.toon import NPCToons +from toontown.toon import NPCFriendPanel +from toontown.toonbase import ToontownBattleGlobals + +class TownBattleSOSPanel(DirectFrame, StateData.StateData): + """TownBattleSOSPanel: + + This is the panel that lists all of our friends, or at least those + who are currently online. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('TownBattleSOSPanel') + + def __init__(self, doneEvent): + """__init__(self, doneEvent) + """ + + DirectFrame.__init__(self, relief = None) + self.initialiseoptions(TownBattleSOSPanel) + StateData.StateData.__init__(self, doneEvent) + # Friends maps (friendId, flags) pairs to the button in the scrollList + self.friends = {} + self.NPCFriends = {} + self.textRolloverColor = Vec4(1,1,0,1) + self.textDownColor = Vec4(0.5,0.9,1,1) + self.textDisabledColor = Vec4(0.4,0.8,0.4,1) + self.bldg = 0 + self.chosenNPCToons = [] + + def load(self): + if self.isLoaded == 1: + return None + self.isLoaded = 1 + + # This is the dialog background. + #bgd = loader.loadModel('phase_3/models/gui/dialog_box_gui') + bgd = loader.loadModel('phase_3.5/models/gui/frame') + + # This has the up-and-down scroll buttons in it. + gui = loader.loadModel("phase_3.5/models/gui/frame4names") + + # We need this for the scroll buttons + scrollGui = loader.loadModel( + "phase_3.5/models/gui/friendslist_gui") + + # We need this to get the "back" button. + backGui = loader.loadModel("phase_3.5/models/gui/battle_gui") + + self['image'] = bgd + self['image_pos'] = (0.0, 0.1, -0.08) + self.setScale(0.3) + + self.title = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.TownBattleSOSNoFriends, + text_scale = 0.4, + text_fg = (1, 1, 1, 1), + text_shadow = (0, 0, 0, 1), + pos = (0.0, 0.0, 1.45), + ) + + self.NPCFriendPanel = NPCFriendPanel.NPCFriendPanel( + parent = self, doneEvent = self.doneEvent) + self.NPCFriendPanel.setPos(-0.75,0,-0.15) + self.NPCFriendPanel.setScale(0.325) + + self.NPCFriendsLabel = DirectLabel( + parent = self, + relief = None, + text = TTLocalizer.TownBattleSOSNPCFriends, + text_scale = 0.3, + text_fg = (1, 1, 1, 1), + text_shadow = (0, 0, 0, 1), + pos = (-0.75, 0.0, -1.85), + ) + + self.scrollList = DirectScrolledList( + parent = self, + relief = None, + image = gui.find("**/frame4names"), + image_scale = (0.11,1,.1), + text = TTLocalizer.FriendsListPanelOnlineFriends, + text_scale = 0.04, + text_pos = (-0.02,0.275), + text_fg = (0,0,0,1), + # inc and dec are DirectButtons + incButton_image = (scrollGui.find("**/FndsLst_ScrollUp"), + scrollGui.find("**/FndsLst_ScrollDN"), + scrollGui.find("**/FndsLst_ScrollUp_Rllvr"), + scrollGui.find("**/FndsLst_ScrollUp"), + ), + incButton_relief = None, + incButton_pos = (0.0, 0.0, -0.3), + # Make the disabled button darker + incButton_image3_color = Vec4(0.6, 0.6, 0.6, 0.6), + incButton_scale = (1.0, 1.0, -1.0), + decButton_image = (scrollGui.find("**/FndsLst_ScrollUp"), + scrollGui.find("**/FndsLst_ScrollDN"), + scrollGui.find("**/FndsLst_ScrollUp_Rllvr"), + scrollGui.find("**/FndsLst_ScrollUp"), + ), + decButton_relief = None, + decButton_pos = (0.0, 0.0, 0.175), + # Make the disabled button darker + decButton_image3_color = Vec4(0.6, 0.6, 0.6, 0.6), + # itemFrame is a DirectFrame + itemFrame_pos = (-0.17, 0.0, 0.11), + itemFrame_relief = None, + # each item is a button with text on it + numItemsVisible = 9, + items = [], + pos = (2.3, 0.0, 0.025), + scale = 3.5, + ) + + # Set up a clipping plane to truncate names that would extend + # off the right end of the scrolled list. + clipper = PlaneNode('clipper') + clipper.setPlane(Plane(Vec3(-1, 0, 0), Point3(0.32, 0, 0))) + clipNP = self.scrollList.component('itemFrame').attachNewNode(clipper) + self.scrollList.component('itemFrame').setClipPlane(clipNP) + + self.close = DirectButton( + parent = self, + relief = None, + image = (backGui.find("**/PckMn_BackBtn"), + backGui.find("**/PckMn_BackBtn_Dn"), + backGui.find("**/PckMn_BackBtn_Rlvr")), + pos = (2.2, 0.0, -1.65), + scale = 3, + text = TTLocalizer.TownBattleSOSBack, + text_scale = 0.05, + text_pos = (0.01,-0.012), + text_fg = Vec4(0,0,0.8,1), + command = self.__close, + ) + + gui.removeNode() + scrollGui.removeNode() + backGui.removeNode() + bgd.removeNode() + + self.hide() + + def unload(self): + if self.isLoaded == 0: + return None + self.isLoaded = 0 + self.exit() + + del self.title + del self.scrollList + del self.close + del self.friends + del self.NPCFriends + DirectFrame.destroy(self) + + def makeFriendButton(self, friendPair): + friendId, flags = friendPair + + #handle = base.cr.identifyFriend(friendId) + handle = base.cr.playerFriendsManager.identifyFriend(friendId) + if handle == None: + # If we don't know who this friend is, we can't make a + # button. We'll find out later; for now, leave it out. + base.cr.fillUpFriendsMap() + return None + + friendName = handle.getName() + + # On this panel, we display all names in black, regardless of + # chat permission. + fg = Vec4(0.0, 0.0, 0.0, 1.0) + + if handle.isPet(): + com = self.__chosePet + else: + com = self.__choseFriend + + return DirectButton( + relief = None, + text = friendName, + text_scale = 0.04, + text_align = TextNode.ALeft, + text_fg = fg, + text1_bg = self.textDownColor, + text2_bg = self.textRolloverColor, + text3_fg = self.textDisabledColor, + command = com, + extraArgs = [friendId, friendName], + ) + + def makeNPCFriendButton(self, NPCFriendId, numCalls): + if (not TTLocalizer.NPCToonNames.has_key(NPCFriendId)): + return None + + friendName = TTLocalizer.NPCToonNames[NPCFriendId] + friendName += " %d" % numCalls + + # On this panel, we display all names in black, regardless of + # chat permission. + fg = Vec4(0.0, 0.0, 0.0, 1.0) + + return DirectButton( + relief = None, + text = friendName, + text_scale = 0.04, + text_align = TextNode.ALeft, + text_fg = fg, + text1_bg = self.textDownColor, + text2_bg = self.textRolloverColor, + text3_fg = self.textDisabledColor, + command = self.__choseNPCFriend, + extraArgs = [NPCFriendId], + ) + + def enter(self, canLure = 1, canTrap = 1): + """enter(self) + """ + if self.isEntered == 1: + return None + self.isEntered = 1 + # Use isLoaded to avoid redundant loading + if self.isLoaded == 0: + self.load() + self.canLure = canLure + self.canTrap = canTrap + + self.factoryToonIdList = None + + # allow other parts of the system to make modifications + messenger.send('SOSPanelEnter', [self]) + + self.__updateScrollList() + self.__updateNPCFriendsPanel() + self.__updateTitleText() + self.show() + + self.accept('friendOnline', self.__friendOnline) + self.accept('friendOffline', self.__friendOffline) + self.accept('friendsListChanged', self.__friendsListChanged) + self.accept('friendsMapComplete', self.__friendsListChanged) + + def exit(self): + """exit(self) + """ + if self.isEntered == 0: + return None + self.isEntered = 0 + + self.hide() + + self.ignore('friendOnline') + self.ignore('friendOffline') + self.ignore('friendsListChanged') + self.ignore('friendsMapComplete') + + messenger.send(self.doneEvent) + + def __close(self): + doneStatus = {} + doneStatus['mode'] = 'Back' + messenger.send(self.doneEvent, [doneStatus]) + + def __choseFriend(self, friendId, friendName): + doneStatus = {} + doneStatus['mode'] = 'Friend' + doneStatus['friend'] = friendId + messenger.send(self.doneEvent, [doneStatus]) + + def __chosePet(self, petId, petName): + doneStatus = {} + doneStatus['mode'] = 'Pet' + doneStatus['petId'] = petId + doneStatus['petName'] = petName + messenger.send(self.doneEvent, [doneStatus]) + + def __choseNPCFriend(self, friendId): + doneStatus = {} + doneStatus['mode'] = 'NPCFriend' + doneStatus['friend'] = friendId + self.chosenNPCToons.append(friendId) + messenger.send(self.doneEvent, [doneStatus]) + + def setFactoryToonIdList(self, toonIdList): + # this list acts as a sort of 'mask' for online friends; + # only friends in this list should show up in the SOS panel + self.factoryToonIdList = toonIdList[:] + + def __updateScrollList(self): + + # Make a list of just those who are actually online. + # newFriends is a list of 2-item tuples (friendId, flags) + newFriends = [] + + battlePets = base.config.GetBool('want-pets-in-battle', 1) + + if base.wantPets and battlePets == 1 and base.localAvatar.hasPet(): + newFriends.append((base.localAvatar.getPetId(), 0)) + + # Only NPC friends should be visible inside buildings + # in factories, we should show friends that are with us in the factory + if (not self.bldg) or (self.factoryToonIdList is not None): + for friendPair in base.localAvatar.friendsList: + if base.cr.isFriendOnline(friendPair[0]): + if ((self.factoryToonIdList is None) or + (friendPair[0] in self.factoryToonIdList)): + newFriends.append(friendPair) + + if hasattr(base.cr, "playerFriendsManager"): + for avatarId in base.cr.playerFriendsManager.getAllOnlinePlayerAvatars(): + if not base.cr.playerFriendsManager.askAvatarKnownElseWhere(avatarId): + newFriends.append((avatarId, 0)) + + + # Remove old buttons + for friendPair in self.friends.keys(): + if friendPair not in newFriends: + friendButton = self.friends[friendPair] + self.scrollList.removeItem(friendButton) + if (not friendButton.isEmpty()): + friendButton.destroy() + del self.friends[friendPair] + + # Add buttons for new friends + for friendPair in newFriends: + if not self.friends.has_key(friendPair): + friendButton = self.makeFriendButton(friendPair) + if friendButton: + self.scrollList.addItem(friendButton) + self.friends[friendPair] = friendButton + + + + + def __updateNPCFriendsPanel(self): + # Make a dict of the NPC Toons that are callable at this time + self.NPCFriends = {} + + # Determine if any should be disabled because their + # attacks won't work (e.g. all the cogs are already lured) + for friend, count in base.localAvatar.NPCFriendsDict.items(): + track = NPCToons.getNPCTrack(friend) + if ((track == ToontownBattleGlobals.LURE_TRACK and + self.canLure == 0) or + (track == ToontownBattleGlobals.TRAP_TRACK and + self.canTrap == 0)): + self.NPCFriends[friend] = 0 + else: + self.NPCFriends[friend] = count + + self.NPCFriendPanel.update(self.NPCFriends, fCallable = 1) + + def __updateTitleText(self): + # Change the top text according to whether anyone's + # available. + isEmpty = (len(self.friends) == 0) and (len(self.NPCFriends) == 0) + if isEmpty: + self.title['text'] = TTLocalizer.TownBattleSOSNoFriends + else: + self.title['text'] = TTLocalizer.TownBattleSOSWhichFriend + + + def __friendOnline(self, doId, commonChatFlags, whitelistChatFlags): + """__friendOnline(self, int doId): + + Called when a friend comes online, this should update the + friends list appropriately. + """ + self.__updateScrollList() + self.__updateTitleText() + + def __friendOffline(self, doId): + """__friendOffline(self, int doId): + + Called when a friend goes offline, this should update the + friends list appropriately. + """ + self.__updateScrollList() + self.__updateTitleText() + + def __friendsListChanged(self): + """__friendsListChanged(self): + + Called when the friends list changes (by adding or removing a + friend), this should update the friends list appropriately. + """ + self.__updateScrollList() + self.__updateTitleText() + diff --git a/toontown/src/town/TownBattleSOSPetInfoPanel.py b/toontown/src/town/TownBattleSOSPetInfoPanel.py new file mode 100644 index 0000000..327ad5d --- /dev/null +++ b/toontown/src/town/TownBattleSOSPetInfoPanel.py @@ -0,0 +1,309 @@ +from pandac.PandaModules import * +from direct.fsm import StateData +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer +from toontown.pets import Pet, PetTricks, PetDetailPanel +from toontown.speedchat import TTSCPetTrickMenu +from otp.speedchat import SpeedChatGlobals, SCSettings +from otp.otpbase import OTPLocalizer + +class TownBattleSOSPetInfoPanel(StateData.StateData): + """TownBattleSOSPetInfoPanel + This displays a 'Pet Info' message and has a Back button + """ + + def __init__(self, doneEvent): + StateData.StateData.__init__(self, doneEvent) + + def load(self): + gui = loader.loadModel("phase_3.5/models/gui/PetControlPannel") + guiScale = 0.116 + #guiPos = (1.12, 0, 0.30) + guiPos = (0, 0, 0) + self.frame = DirectFrame( + image = gui, + scale = guiScale, + pos = guiPos, + relief = None, + ) + self.frame.hide() + + disabledImageColor = Vec4(.6,.6,.6,1) + text0Color = Vec4(1,1,1,1) + text1Color = Vec4(0.5,1,0.5,1) + text2Color = Vec4(1,1,0.5,1) + text3Color = Vec4(.6,.6,.6,1) + + self.closeButton = DirectButton( + parent = self.frame, + image = (gui.find("**/CancelButtonUp"), + gui.find("**/CancelButtonDown"), + gui.find("**/CancelButtonRollover"), + ), + relief = None, + command = self.__handleClose, + ) + self.feedButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ButtonFeedUp"), + gui.find("**/ButtonFeedDown"), + gui.find("**/ButtonFeedRollover"), + gui.find("**/ButtonFeedUp"), + ), + geom = gui.find("**/PetControlFeedIcon"), + image3_color = disabledImageColor, + relief = None, + text = TTLocalizer.PetPanelFeed, + text_scale = 0.5, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_pos = (-0.5,2.8), + text_align = TextNode.ALeft, + #command = self.__handleFeed, + ) + self.feedButton['state'] = DGG.DISABLED + + self.callButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ButtonGoToUp"), + gui.find("**/ButtonGoToDown"), + gui.find("**/ButtonGoToRollover"), + gui.find("**/ButtonGoToUp"), + ), + geom = gui.find("**/PetControlGoToIcon"), + image3_color = disabledImageColor, + relief = None, + text = TTLocalizer.PetPanelCall, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = 0.5, + text_pos = (-0.5,1.3), + text_align = TextNode.ALeft, + #command = self.__handleCall, + ) + self.callButton['state'] = DGG.DISABLED + + self.scratchButton = DirectButton( + parent = self.frame, + image = (gui.find("**/ButtonScratchUp"), + gui.find("**/ButtonScratchDown"), + gui.find("**/ButtonScratchRollover"), + gui.find("**/ButtonScratchUp"), + ), + geom = gui.find("**/PetControlScratchIcon"), + image3_color = disabledImageColor, + relief = None, + text = TTLocalizer.PetPanelScratch, + text0_fg = text0Color, + text1_fg = text1Color, + text2_fg = text2Color, + text3_fg = text3Color, + text_scale = 0.5, + text_pos = (-0.5,2.05), + text_align = TextNode.ALeft, + #command = self.__handleScratch, + ) + self.scratchButton['state'] = DGG.DISABLED + + self.callOwnerButton = DirectButton( + parent = self.frame, + image = (gui.find("**/PetControlToonButtonUp"), + gui.find("**/PetControlToonButtonDown"), + gui.find("**/PetControlToonButtonRollover"), + ), + geom = gui.find("**/PetControlToonIcon"), + geom3_color = disabledImageColor, + relief = None, + image3_color = disabledImageColor, + text = ("", TTLocalizer.PetPanelOwner, + TTLocalizer.PetPanelOwner, ""), + text_fg = text2Color, + text_shadow = (0, 0, 0, 1), + text_scale = 0.35, + text_pos = (0.3,1.1), + text_align = TextNode.ACenter, + command = self.__handleDetail, + ) + self.callOwnerButton['state'] = DGG.DISABLED + + self.detailButton = DirectButton( + parent = self.frame, + image = (gui.find("**/PetControlToonButtonUp1"), + gui.find("**/PetControlToonButtonDown1"), + gui.find("**/PetControlToonButtonRollover1"), + ), + geom = gui.find("**/PetBattleIcon"), + geom3_color = disabledImageColor, + relief = None, + pos = (0, 0, 0), + image3_color = disabledImageColor, + text = ("", TTLocalizer.PetPanelDetail, + TTLocalizer.PetPanelDetail, ""), + text_fg = text2Color, + text_shadow = (0, 0, 0, 1), + text_scale = 0.35, + text_pos = (0.3,1.1), + text_align = TextNode.ACenter, + command = self.__handleDetail, + ) + self.detailButton['state'] = DGG.NORMAL + + gui.removeNode() + + self.nameLabel = None + + self.trickMenu = TTSCPetTrickMenu.TTSCPetTrickMenu() + self.settings = SCSettings.SCSettings(eventPrefix = '') + self.trickMenu.privSetSettingsRef(self.settings) + self.trickMenuEventName = self.trickMenu.getEventName(SpeedChatGlobals.SCStaticTextMsgEvent) + self.trickMenu.setScale(.055) + self.trickMenu.setBin('gui-popup',0) + self.trickMenu.finalizeAll() + + # Make sure the pet battle phrases don't get whispered to friends + localAvatar.chatMgr.chatInputSpeedChat.whisperAvatarId = None + + self.petDetailPanel = None + + def unload(self): + self.frame.destroy() + del self.frame + self.frame = None + + if hasattr(self, 'petView'): + self.petView.removeNode() + del self.petView + + if hasattr(self, 'petModel'): + self.petModel.delete() + del self.petModel + + del self.closeButton + del self.feedButton + del self.callButton + del self.scratchButton + del self.callOwnerButton + del self.detailButton + + self.trickMenu.destroy() + del self.trickMenu + + del self.petDetailPanel + + def enter(self, petProxyId): + self.petProxyId = petProxyId + if not base.cr.doId2do.has_key(petProxyId): + self.notify.warning("petProxyId %s not in doId2do!" % petProxyId) + return + self.petProxy = base.cr.doId2do[petProxyId] + self.__fillPetInfo(self.petProxy) + # Show the panel + self.frame.show() + + # Set up the pet trick menu + self.accept(self.trickMenuEventName, self.__handleTrickMenuEvent) + self.trickMenu.reparentTo(aspect2dp, DGG.FOREGROUND_SORT_INDEX) + + # Make sure the pet battle phrases don't get whispered to friends + localAvatar.chatMgr.chatInputSpeedChat.whisperAvatarId = None + + # Make sure the detail button is enabled + self.detailButton['state'] = DGG.NORMAL + + def exit(self): + self.ignore(self.trickMenuEventName) + self.trickMenu.reparentTo(hidden) + + self.petProxy = None + if self.petDetailPanel != None: + self.petDetailPanel.cleanup() + self.petDetailPanel = None + + # Hide the panel + self.frame.hide() + + def __handleTrickMenuEvent(self, textId): + if PetTricks.ScId2trickId.has_key(textId): + trickId = PetTricks.ScId2trickId[textId] + doneStatus = {'mode':'OK', 'trickId':trickId} + messenger.send(self.doneEvent, [doneStatus]) + self.detailButton['state'] = DGG.NORMAL + + def __handleClose(self): + doneStatus = {'mode':'Back'} + messenger.send(self.doneEvent, [doneStatus]) + + def __handleCall(self): + doneStatus = {'mode':'OK', 'trickId':0} + messenger.send(self.doneEvent, [doneStatus]) + + def __handleDetailDone(self): + if self.petDetailPanel != None: + self.petDetailPanel.cleanup() + self.petDetailPanel = None + self.detailButton['state'] = DGG.NORMAL + + def __handleDetail(self): + self.petDetailPanel = PetDetailPanel.PetDetailPanel( + pet = self.petProxy, + closeCallback = self.__handleDetailDone, + parent = self.frame, + ) + self.detailButton['state'] = DGG.DISABLED + + def __fillPetInfo(self, avatar): + self.notify.debug("__fillPetInfo(): doId=%s" % avatar.doId) + #add the view of the pet + if self.nameLabel == None: + self.petView = self.frame.attachNewNode('petView') + self.petView.setPos(0, 0, 5.4) + self.petModel = Pet.Pet(forGui = 1) + self.petModel.setDNA(avatar.getDNA()) + self.petModel.fitAndCenterHead(3.575, forGui = 1) + self.petModel.reparentTo(self.petView) + self.petModel.enterNeutralHappy() + self.petModel.startBlink() + + # Put the avatar's name across the top. + self.nameLabel = DirectLabel( + parent = self.frame, + pos = (0, 0, 5.2), + relief = None, + text = avatar.getName(), + text_font = avatar.getFont(), + text_fg = Vec4(0,0,0,1), + text_pos = (0, 0), + text_scale = 0.4, + text_wordwrap = 7.5, + text_shadow = (1, 1, 1, 1), + ) + + # Put the avatar's 'state' (biggest need) + self.stateLabel = DirectLabel( + parent = self.frame, + pos = (0.7, 0, 3.5), + relief = None, + text = "", + text_font = avatar.getFont(), + text_fg = Vec4(0,0,0,1), + text_scale = 0.4, + text_wordwrap = 7.5, + text_shadow = (1, 1, 1, 1), + ) + + self.__refreshPetInfo(avatar) + + def __refreshPetInfo(self, avatar): + self.notify.debug("__refreshPetInfo(): doId=%s" % avatar.doId) + + avatar.updateOfflineMood() + mood = avatar.getDominantMood() + self.stateLabel['text'] = TTLocalizer.PetMoodAdjectives[mood] + + #refresh the pets name + self.nameLabel['text'] = avatar.getName() diff --git a/toontown/src/town/TownBattleSOSPetSearchPanel.py b/toontown/src/town/TownBattleSOSPetSearchPanel.py new file mode 100644 index 0000000..742085b --- /dev/null +++ b/toontown/src/town/TownBattleSOSPetSearchPanel.py @@ -0,0 +1,62 @@ +from pandac.PandaModules import * +from direct.fsm import StateData +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class TownBattleSOSPetSearchPanel(StateData.StateData): + """TownBattleSOSPetSearchPanel + This displays a 'Searching for pet...' message and has a Back button + """ + + def __init__(self, doneEvent): + StateData.StateData.__init__(self, doneEvent) + + def load(self): + gui = loader.loadModel("phase_3.5/models/gui/battle_gui") + self.frame = DirectFrame( + relief = None, + image = gui.find("**/Waiting4Others"), + text_align = TextNode.ALeft, + pos = (0,0,0), + scale = 0.65, + ) + self.frame.hide() + self.backButton = DirectButton( + parent = self.frame, + relief = None, + image = (gui.find("**/PckMn_BackBtn"), + gui.find("**/PckMn_BackBtn_Dn"), + gui.find("**/PckMn_BackBtn_Rlvr"), + ), + pos = (-0.647, 0, -0.011), + scale = 1.05, + text = TTLocalizer.TownBattleWaitBack, + text_scale = 0.05, + text_pos = (0.01,-0.012), + text_fg = Vec4(0,0,0.8,1), + command = self.__handleBack, + ) + gui.removeNode() + + def unload(self): + self.frame.destroy() + del self.frame + del self.backButton + + def enter(self, petId, petName): + self.petId = petId + self.petName = petName + self.frame['text'] = TTLocalizer.TownBattleSOSPetSearchTitle % petName + self.frame['text_pos'] = (0,0.01,0) + self.frame['text_scale'] = TTLocalizer.TBPSpanel + # Show the panel + self.frame.show() + + def exit(self): + # Hide the panel + self.frame.hide() + + def __handleBack(self): + doneStatus = {'mode':'Back'} + messenger.send(self.doneEvent, [doneStatus]) diff --git a/toontown/src/town/TownBattleToonPanel.py b/toontown/src/town/TownBattleToonPanel.py new file mode 100644 index 0000000..8c1e0f7 --- /dev/null +++ b/toontown/src/town/TownBattleToonPanel.py @@ -0,0 +1,266 @@ +from pandac.PandaModules import * +from toontown.toonbase import ToontownGlobals +from toontown.toonbase.ToontownBattleGlobals import * +from direct.directnotify import DirectNotifyGlobal +import string +from toontown.toon import LaffMeter +from toontown.battle import BattleBase +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class TownBattleToonPanel(DirectFrame): + """ + This panel shows the laff meter, and attack choice of a toon. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('TownBattleToonPanel') + + def __init__(self, id): + + gui = loader.loadModel("phase_3.5/models/gui/battle_gui") + + DirectFrame.__init__(self, + relief = None, + image = gui.find("**/ToonBtl_Status_BG"), + # What color should these be? + image_color = Vec4(0.5,0.9,0.5,0.7), + ) + self.setScale(0.8) + self.initialiseoptions(TownBattleToonPanel) + + # One day, this panel will be associated with an avatar + self.avatar = None + # The card + # In case someone calls SOS + self.sosText = DirectLabel(parent = self, + relief = None, + pos = (0.1, 0, 0.015), + text = TTLocalizer.TownBattleToonSOS, + text_scale = 0.06, + ) + self.sosText.hide() + + self.fireText = DirectLabel(parent = self, + relief = None, + pos = (0.1, 0, 0.015), + text = TTLocalizer.TownBattleToonFire, + text_scale = 0.06, + ) + self.fireText.hide() + + # Before you have decided + self.undecidedText = DirectLabel(parent = self, + relief = None, + pos = (0.1, 0, 0.015), + text = TTLocalizer.TownBattleUndecided, + text_scale = 0.1, + ) + + # The text below the laff meter + self.healthText = DirectLabel(parent = self, + text = '', + pos = (-0.06, 0, -0.075), + text_scale = 0.055, + ) + + # The event for the health text + self.hpChangeEvent = None + + # Create a gag node. + self.gagNode = self.attachNewNode('gag') + # Set the position just so + self.gagNode.setPos(0.1, 0, 0.03) + # We don't have a gag yet. + self.hasGag = 0 + + # create the pass node + passGui = gui.find("**/tt_t_gui_bat_pass") + passGui.detachNode() + self.passNode = self.attachNewNode('pass') + self.passNode.setPos(0.1, 0, 0.05) + passGui.setScale(0.2) + passGui.reparentTo(self.passNode) + self.passNode.hide() + + self.laffMeter = None + + # The display below the gag, that tells which suit or toon + # has been selected. + self.whichText = DirectLabel(parent = self, + text = '', + pos = (0.1, 0, -0.08), + text_scale = 0.05, + ) + + self.hide() + gui.removeNode() + return + + def setLaffMeter(self, avatar): + self.notify.debug("setLaffMeter: new avatar %s" % avatar.doId) + + # Don't set the laff meter if it is already set for this toon. + if self.avatar == avatar: + # Send an HP update, just in case things changed. + messenger.send(self.avatar.uniqueName("hpChange"), + [avatar.hp, avatar.maxHp, 1]) + return None + else: + # Cleanup the previous laffMeter, if there was one + if self.avatar: + self.cleanupLaffMeter() + + self.avatar = avatar + self.laffMeter = LaffMeter.LaffMeter(avatar.style, avatar.hp, + avatar.maxHp) + # Connect the laff meter to the avatar + self.laffMeter.setAvatar(self.avatar) + self.laffMeter.reparentTo(self) + self.laffMeter.setPos(-0.06, 0, 0.05) + self.laffMeter.setScale(0.045) + self.laffMeter.start() + + # Set the healthText + self.setHealthText(avatar.hp, avatar.maxHp) + self.hpChangeEvent = self.avatar.uniqueName("hpChange") + self.accept(self.hpChangeEvent, self.setHealthText) + return None + + def setHealthText(self, hp, maxHp, quietly = 0): + self.healthText['text'] = (TTLocalizer.TownBattleHealthText % {"hitPoints": hp, "maxHit": maxHp}) + return + + def show(self): + DirectFrame.show(self) + if self.laffMeter: + self.laffMeter.start() + return + + def hide(self): + DirectFrame.hide(self) + if self.laffMeter: + self.laffMeter.stop() + return + + def updateLaffMeter(self, hp): + # Just in case this gets called before an avatar is assigned to it. + if self.laffMeter: + self.laffMeter.adjustFace(hp, self.avatar.maxHp) + self.setHealthText(hp, maxHp) + return + + def setValues(self, index, track, level=None, numTargets=None, + targetIndex=None, localNum=None): + self.notify.debug( + "Toon Panel setValues: index=%s track=%s level=%s numTargets=%s targetIndex=%s localNum=%s" % + (index, track, level, numTargets, targetIndex, localNum)) + # Turn off all optional display items + self.undecidedText.hide() + self.sosText.hide() + self.fireText.hide() + self.gagNode.hide() + self.whichText.hide() + self.passNode.hide() + if self.hasGag: + self.gag.removeNode() + self.hasGag = 0 + + # Turn on the proper display items + if (track == BattleBase.NO_ATTACK or + track == BattleBase.UN_ATTACK): + # Indicates pass or timeout + self.undecidedText.show() + elif track == BattleBase.PASS_ATTACK: + self.passNode.show() + elif (track == BattleBase.FIRE): + #import pdb; pdb.set_trace() + self.fireText.show() + self.whichText.show() + self.whichText['text'] = self.determineWhichText(numTargets, + targetIndex, + localNum, + index) + pass + elif (track == BattleBase.SOS or + track == BattleBase.NPCSOS or + track == BattleBase.PETSOS): + # Indicates a call for help + self.sosText.show() + elif (track >= MIN_TRACK_INDEX and track <= MAX_TRACK_INDEX): + self.undecidedText.hide() + self.passNode.hide() + # It must be an attack... Show a button + self.gagNode.show() + invButton = base.localAvatar.inventory.buttonLookup(track, level) + self.gag = invButton.instanceUnderNode(self.gagNode, "gag") + self.gag.setScale(0.8) + self.gag.setPos(0, 0, 0.02) + # We have a gag now + self.hasGag = 1 + if ((numTargets is not None) + and (targetIndex is not None) + and (localNum is not None)): + # Show who is being attacked + self.whichText.show() + self.whichText['text'] = self.determineWhichText(numTargets, + targetIndex, + localNum, + index) + else: + self.notify.error("Bad track value: %s" % track) + return None + + def determineWhichText(self, numTargets, targetIndex, localNum, index): + assert numTargets >= 1 and numTargets <= 4 + assert localNum >= 0 and localNum <= 3 + assert index >= 0 and index <= 3 + returnStr = "" + # We want to traverse backwards, since suits and toons are + # numbered from right to left. + targetList = range(numTargets) + targetList.reverse() + for i in targetList: + if targetIndex == -1: + # Indicates a group attack... All suits are targets of + # a group attack. + returnStr += "X" + elif targetIndex == -2: + # Indicates a group heal... All toons but localToon are targets + # of a group heal. + if i == index: + returnStr += "-" + else: + returnStr += "X" + elif (targetIndex >= 0) and (targetIndex <= 3): + # A targeted attack or heal + if i == targetIndex: + # Indicates a target + returnStr += "X" + else: + # Not a target + returnStr += "-" + else: + self.notify.error("Bad target index: %s" % targetIndex) + return returnStr + + def cleanup(self): + self.ignoreAll() + # Clean up the laff meter + self.cleanupLaffMeter() + # Clean up the gag (if there is one) + if self.hasGag: + self.gag.removeNode() + del self.gag + # Clean up the gag node + self.gagNode.removeNode() + del self.gagNode + # Cleanup the rest of the panel + DirectFrame.destroy(self) + + def cleanupLaffMeter(self): + self.notify.debug("Cleaning up laffmeter!") + self.ignore(self.hpChangeEvent) + if self.laffMeter: + self.laffMeter.destroy() + self.laffMeter = None + return None diff --git a/toontown/src/town/TownBattleWaitPanel.py b/toontown/src/town/TownBattleWaitPanel.py new file mode 100644 index 0000000..27ff983 --- /dev/null +++ b/toontown/src/town/TownBattleWaitPanel.py @@ -0,0 +1,71 @@ +from pandac.PandaModules import * +from direct.fsm import StateData +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from toontown.toonbase import TTLocalizer + +class TownBattleWaitPanel(StateData.StateData): + """TownBattleWaitPanel + This displays a 'waiting for other players...' message and has a Back button + """ + + def __init__(self, doneEvent): + StateData.StateData.__init__(self, doneEvent) + + def load(self): + gui = loader.loadModel("phase_3.5/models/gui/battle_gui") + self.frame = DirectFrame( + relief = None, + image = gui.find("**/Waiting4Others"), + text_align = TextNode.ALeft, + pos = (0,0,0), + scale = 0.65, + ) + self.frame.hide() + self.backButton = DirectButton( + parent = self.frame, + relief = None, + image = (gui.find("**/PckMn_BackBtn"), + gui.find("**/PckMn_BackBtn_Dn"), + gui.find("**/PckMn_BackBtn_Rlvr"), + ), + pos = (-0.647, 0, -0.011), + scale = 1.05, + text = TTLocalizer.TownBattleWaitBack, + text_scale = 0.05, + text_pos = (0.01,-0.012), + text_fg = Vec4(0,0,0.8,1), + command = self.__handleBack, + ) + gui.removeNode() + + def unload(self): + self.frame.destroy() + del self.frame + del self.backButton + + def enter(self, numParticipants): + if numParticipants > 1: + self.frame['text'] = TTLocalizer.TownBattleWaitTitle + self.frame['text_pos'] = (0,0.01,0) + self.frame['text_scale'] = 0.1 + else: + self.frame['text'] = TTLocalizer.TownSoloBattleWaitTitle + self.frame['text_pos'] = (0,-0.05,0) + self.frame['text_scale'] = 0.13 + # Show the panel + self.frame.show() + # Force chat balloons to the margins while this is up. + # NametagGlobals.setOnscreenChatForced(1) + + def exit(self): + # Hide the panel + self.frame.hide() + # NametagGlobals.setOnscreenChatForced(0) + + def __handleBack(self): + doneStatus = {'mode':'Back'} + messenger.send(self.doneEvent, [doneStatus]) + + + diff --git a/toontown/src/town/TownLoader.py b/toontown/src/town/TownLoader.py new file mode 100644 index 0000000..9291a49 --- /dev/null +++ b/toontown/src/town/TownLoader.py @@ -0,0 +1,554 @@ +"""TownLoader module: contains the TownLoader class""" + +from pandac.PandaModules import * +from toontown.battle.BattleProps import * +from toontown.battle.BattleSounds import * +from toontown.distributed.ToontownMsgTypes import * +from toontown.toonbase.ToontownGlobals import * +from direct.gui.DirectGui import cleanupDialog +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import Place +from direct.showbase import DirectObject +from direct.fsm import StateData +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.task import Task +import TownBattle +from toontown.toon import Toon +from toontown.battle import BattleParticles +from direct.fsm import StateData +from toontown.building import ToonInterior +from toontown.hood import QuietZoneState +from toontown.hood import ZoneUtil +from direct.interval.IntervalGlobal import * + +class TownLoader(StateData.StateData): + """ + TownLoader class + """ + + # create a notify category + notify = DirectNotifyGlobal.directNotify.newCategory("TownLoader") + + # special methods + + def __init__(self, hood, parentFSMState, doneEvent): + """ + TownLoader constructor: create a play game ClassicFSM + """ + assert self.notify.debug("__init__()") + StateData.StateData.__init__(self, doneEvent) + self.hood=hood + self.parentFSMState = parentFSMState + self.fsm = ClassicFSM.ClassicFSM('TownLoader', + [State.State('start', + self.enterStart, + self.exitStart, + ['quietZone', 'street', 'toonInterior']), + State.State('street', + self.enterStreet, + self.exitStreet, + ['quietZone']), + State.State('toonInterior', + self.enterToonInterior, + self.exitToonInterior, + ['quietZone']), + State.State('quietZone', + self.enterQuietZone, + self.exitQuietZone, + ['street', 'toonInterior']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', + ) + self.branchZone = None + self.canonicalBranchZone = None + self.placeDoneEvent = "placeDone" + self.townBattleDoneEvent = 'town-battle-done' + + def loadBattleAnims(self): + # These are here so the tutorial can overwrite them + Toon.loadBattleAnims() + + def unloadBattleAnims(self): + # These are here so the tutorial can overwrite them + Toon.unloadBattleAnims() + + def load(self, zoneId): + assert self.notify.debug("load()") + + # We'll need to know this to rename the visibility zones + # correctly. + self.zoneId = zoneId + + # Prepare the state machine + self.parentFSMState.addChild(self.fsm) + # load Toon battle anims and props + self.loadBattleAnims() + # props loaded on the fly now + #globalPropPool.loadProps() + # TODO: Based on the zone id, load that branch + self.branchZone = ZoneUtil.getBranchZone(zoneId) + self.canonicalBranchZone = ZoneUtil.getCanonicalBranchZone(zoneId) + # Load the music: + self.music = base.loadMusic(self.musicFile) + self.activityMusic = base.loadMusic(self.activityMusicFile) + self.battleMusic = base.loadMusic( + 'phase_3.5/audio/bgm/encntr_general_bg.mid') + # Load the battle UI: + self.townBattle = TownBattle.TownBattle(self.townBattleDoneEvent) + self.townBattle.load() + + def unload(self): + assert self.notify.debug("unload()") + # unload Toon battle anims and props + self.unloadBattleAnims() + globalPropPool.unloadProps() + globalBattleSoundCache.clear() + BattleParticles.unloadParticles() + self.parentFSMState.removeChild(self.fsm) + del self.parentFSMState + del self.fsm + del self.streetClass + self.landmarkBlocks.removeNode() + del self.landmarkBlocks + # Clear out the old neighborhoods suit points + self.hood.dnaStore.resetSuitPoints() + # Clear out the battle cells + self.hood.dnaStore.resetBattleCells() + del self.hood + del self.nodeDict + del self.zoneDict + del self.fadeInDict + del self.fadeOutDict + del self.nodeList + self.geom.removeNode() + del self.geom + self.townBattle.unload() + self.townBattle.cleanup() + del self.townBattle + del self.battleMusic + del self.music + del self.activityMusic + del self.holidayPropTransforms + self.deleteAnimatedProps() + # remove any dfa dialogs + cleanupDialog("globalDialog") + # Get rid of any references to models or textures from this town + ModelPool.garbageCollect() + TexturePool.garbageCollect() + + def enter(self, requestStatus): + assert self.notify.debug("enter(requestStatus="+str(requestStatus)+")") + self.fsm.enterInitialState() + self.setState(requestStatus["where"], requestStatus) + + def exit(self): + assert self.notify.debug("exit()") + self.ignoreAll() + + def setState(self, stateName, requestStatus): + assert(self.notify.debug("setState(stateName=" + +str(stateName)+", requestStatus="+str(requestStatus)+")")) + self.fsm.request(stateName, [requestStatus]) + + # start state + + def enterStart(self): + assert self.notify.debug("enterStart()") + + def exitStart(self): + assert self.notify.debug("exitStart()") + + # street state + + def enterStreet(self, requestStatus): + assert(self.notify.debug( + "enterStreet(requestStatus="+str(requestStatus)+")")) + self.acceptOnce(self.placeDoneEvent, self.streetDone) + self.place=self.streetClass(self, self.fsm, self.placeDoneEvent) + self.place.load() + #self.hood.place = self.place + base.cr.playGame.setPlace(self.place) + + # The following call could actually take us out of the town + # altogether, switching us out of street mode, unloading our + # hood, and all the bad consequences that could go along with + # that, if this is a teleport attempt to a toon who has moved + # on. It should therefore be the very last thing this + # function does, because anything done after this call might + # be invalid. + self.place.enter(requestStatus) + + def exitStreet(self): + assert self.notify.debug("exitStreet()") + self.place.exit() + self.place.unload() + self.place=None + #self.hood.place = self.place + base.cr.playGame.setPlace(self.place) + + + def streetDone(self): + self.requestStatus=self.place.doneStatus + assert(self.notify.debug( + "streetDone() doneStatus="+str(self.requestStatus))) + status=self.place.doneStatus + # Check the loader, incase this is a change to a SuitInterior: + if (status["loader"] == "townLoader" and + ZoneUtil.getBranchZone(status["zoneId"]) == self.branchZone and + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send(self.doneEvent) + + # toonInterior state + + def enterToonInterior(self, requestStatus): + assert self.notify.debug("enterToonInterior()") + self.acceptOnce(self.placeDoneEvent, self.handleToonInteriorDone) + self.place=ToonInterior.ToonInterior(self, + self.fsm.getStateNamed("toonInterior"), + self.placeDoneEvent) + #self.hood.place = self.place + base.cr.playGame.setPlace(self.place) + self.place.load() + self.place.enter(requestStatus) + + def exitToonInterior(self): + assert self.notify.debug("exitToonInterior()") + self.ignore(self.placeDoneEvent) + #self.hood.place = None + self.place.exit() + self.place.unload() + self.place=None + base.cr.playGame.setPlace(self.place) + + def handleToonInteriorDone(self): + assert self.notify.debug("handleToonInteriorDone()") + status=self.place.doneStatus + if (ZoneUtil.getBranchZone(status["zoneId"]) == self.branchZone and + status["shardId"] == None): + self.fsm.request("quietZone", [status]) + else: + self.doneStatus = status + messenger.send(self.doneEvent) + + # quietZone state + + def enterQuietZone(self, requestStatus): + assert self.notify.debug("enterQuietZone()") + self.quietZoneDoneEvent = "quietZoneDone" + self.acceptOnce(self.quietZoneDoneEvent, self.handleQuietZoneDone) + self.quietZoneStateData = QuietZoneState.QuietZoneState( + self.quietZoneDoneEvent) + self.quietZoneStateData.load() + self.quietZoneStateData.enter(requestStatus) + + def exitQuietZone(self): + assert self.notify.debug("exitQuietZone()") + self.ignore(self.quietZoneDoneEvent) + del self.quietZoneDoneEvent + self.quietZoneStateData.exit() + self.quietZoneStateData.unload() + self.quietZoneStateData=None + + def handleQuietZoneDone(self): + status=self.quietZoneStateData.getRequestStatus() + assert(self.notify.debug("handleQuietZoneDone()\n base.cr.handlerArgs=" + +str(status))) + # Change to the destination state: + self.fsm.request(status["where"], [status]) + + # final state + + def enterFinal(self): + assert self.notify.debug("enterFinal()") + + def exitFinal(self): + assert self.notify.debug("exitFinal()") + + def createHood(self, dnaFile, loadStorage=1): + assert self.notify.debug("createHood(dnaFile="+str(dnaFile)+")") + # The tutorial does not use this + if loadStorage: + # Load the generic town storage if asked for + loader.loadDNAFile(self.hood.dnaStore, "phase_5/dna/storage_town.dna") + self.notify.debug("done loading %s" % "phase_5/dna/storage_town.dna") + # Load the specific town storage + loader.loadDNAFile(self.hood.dnaStore, self.townStorageDNAFile) + self.notify.debug("done loading %s" % self.townStorageDNAFile) + node = loader.loadDNAFile(self.hood.dnaStore, dnaFile) + self.notify.debug("done loading %s" % dnaFile) + + if node.getNumParents() == 1: + # If the node already has a parent arc when it's loaded, we must + # be using the level editor and we want to preserve that arc. + self.geom = NodePath(node.getParent(0)) + self.geom.reparentTo(hidden) + else: + # Otherwise, we should create a new arc for the node. + self.geom = hidden.attachNewNode(node) + + # self.geom = hidden.attachNewNode(node) + # Make the vis dictionaries + self.makeDictionaries(self.hood.dnaStore) + # Reparent Landmark block nodes: + self.reparentLandmarkBlockNodes() + # Rename the floor polys to have the same name as the + # visgroup they are in... This makes visibility possible. + self.renameFloorPolys(self.nodeList) + self.createAnimatedProps(self.nodeList) + # Record position of all holiday props before everything is flattened + self.holidayPropTransforms = {} + npl = self.geom.findAllMatches('**/=DNARoot=holiday_prop') + for i in range(npl.getNumPaths()): + np = npl.getPath(i) + np.setTag('transformIndex', `i`) + self.holidayPropTransforms[i] = np.getNetTransform() + # Flatten the neighborhood + #self.geom.flattenMedium() + self.notify.info("skipping self.geom.flattenMedium") + # Preload all textures in neighborhood + gsg = base.win.getGsg() + if gsg: + self.geom.prepareScene(gsg) + # Make a convenient search name for the town root + self.geom.setName('town_top_level') + + def reparentLandmarkBlockNodes(self): + """ + reparent the landmark block stub nodes into a 'bucket' under + hidden. We just use these for pos, hpr, and scale + """ + assert self.notify.debug("reparentLandmarkBlockNodes()") + bucket=self.landmarkBlocks=hidden.attachNewNode("landmarkBlocks") + npc=self.geom.findAllMatches("**/sb*:*_landmark_*_DNARoot") + for i in range(npc.getNumPaths()): + nodePath=npc.getPath(i) + nodePath.wrtReparentTo(bucket) + npc=self.geom.findAllMatches("**/sb*:*animated_building*_DNARoot") + for i in range(npc.getNumPaths()): + nodePath=npc.getPath(i) + nodePath.wrtReparentTo(bucket) + + def makeDictionaries(self, dnaStore): + """ + Extract the juicy bits from the dna, then unload as much as possible + """ + assert self.notify.debug("makeDictionaries()") + # A map of zone ID's to a list of nodes that are visible from + # that zone. + self.nodeDict = {} + + # A map of zone ID's to the particular node that corresponds + # to that zone. + self.zoneDict = {} + + # A list of all visible nodes + self.nodeList = [] + + self.fadeInDict = {} + self.fadeOutDict = {} + + # NOTE: this should change to find the groupnodes in + # the dna storage instead of searching through the tree + + # Colors for fading zones + a1 = Vec4(1,1,1,1) + a0 = Vec4(1,1,1,0) + + numVisGroups = dnaStore.getNumDNAVisGroups() + for i in range(numVisGroups): + groupFullName = dnaStore.getDNAVisGroupName(i) + groupName = base.cr.hoodMgr.extractGroupName(groupFullName) + zoneId = int(groupName) + zoneId = ZoneUtil.getTrueZoneId(zoneId, self.zoneId) + + groupNode = self.geom.find("**/" + groupFullName) + if groupNode.isEmpty(): + self.notify.error("Could not find visgroup") + else: + # Temporary hack (to be replaced with tag interface): + # mangle the name to put the modified zoneId back in, + # but keep the extra flags following the zoneId. + if ":" in groupName: + groupName = "%s%s" % (zoneId, groupName[groupName.index(":"):]) + else: + groupName = "%s" % (zoneId) + groupNode.setName(groupName) + + self.nodeDict[zoneId] = [] + self.nodeList.append(groupNode) + self.zoneDict[zoneId] = groupNode + + fadeDuration = 0.5 + + self.fadeOutDict[groupNode] = Sequence( + Func(groupNode.setTransparency, 1), + LerpColorScaleInterval(groupNode, fadeDuration, + a0, startColorScale = a1), + Func(groupNode.clearColorScale), + Func(groupNode.clearTransparency), + Func(groupNode.stash), + name = "fadeZone-" + str(zoneId), + autoPause = 1) + + self.fadeInDict[groupNode] = Sequence( + Func(groupNode.unstash), + Func(groupNode.setTransparency, 1), + LerpColorScaleInterval(groupNode, fadeDuration, + a1, startColorScale = a0), + Func(groupNode.clearColorScale), + Func(groupNode.clearTransparency), + name = "fadeZone-" + str(zoneId), + autoPause = 1) + + # Now that all the zoneDict has been filled in, fill in the nodeDict + for i in range(numVisGroups): + groupFullName = dnaStore.getDNAVisGroupName(i) + zoneId = int(base.cr.hoodMgr.extractGroupName(groupFullName)) + zoneId = ZoneUtil.getTrueZoneId(zoneId, self.zoneId) + for j in range(dnaStore.getNumVisiblesInDNAVisGroup(i)): + visName = dnaStore.getVisibleName(i, j) + groupName = base.cr.hoodMgr.extractGroupName(visName) + # visNode = self.geom.find("**/" + visName) + nextZoneId = int(groupName) + nextZoneId = ZoneUtil.getTrueZoneId(nextZoneId, self.zoneId) + visNode = self.zoneDict[nextZoneId] + self.nodeDict[zoneId].append(visNode) + + # Now that we have extracted the vis groups we do not need + # the dnaStore to keep them around + # Remove all references to the town specific models and textures + self.hood.dnaStore.resetPlaceNodes() + self.hood.dnaStore.resetDNAGroups() + self.hood.dnaStore.resetDNAVisGroups() + self.hood.dnaStore.resetDNAVisGroupsAI() + + def renameFloorPolys(self, nodeList): + assert self.notify.debug("renameFloorPolys()") + for i in nodeList: + # Get all the collision nodes in the vis group + collNodePaths = i.findAllMatches("**/+CollisionNode") + numCollNodePaths = collNodePaths.getNumPaths() + visGroupName = i.node().getName() + for j in range(numCollNodePaths): + collNodePath = collNodePaths.getPath(j) + bitMask = collNodePath.node().getIntoCollideMask() + if bitMask.getBit(1): + # Bit 1 is the floor collision bit. This renames + # all floor collision polys to the same name as their + # visgroup. + collNodePath.node().setName(visGroupName) + + def createAnimatedProps(self, nodeList): + assert self.notify.debug("createAnimatedProps()") + self.animPropDict = {} + self.zoneIdToInteractivePropDict = {} + for i in nodeList: + # Get all the anim in the vis group + animPropNodes = i.findAllMatches("**/animated_prop_*") + numAnimPropNodes = animPropNodes.getNumPaths() + for j in range(numAnimPropNodes): + animPropNode = animPropNodes.getPath(j) + + if animPropNode.getName().startswith('animated_prop_generic'): + className = 'GenericAnimatedProp' + elif animPropNode.getName().startswith('animated_prop_'): + name = animPropNode.getName()[len('animated_prop_'):] + splits = name.split('_') + className = splits[0] + else: + # The node name should be "animated_prop_ClassName_DNARoot" + # So strip off the first and last junk to get the ClassName + className = animPropNode.getName()[14:-8] + + symbols = {} + base.cr.importModule(symbols, 'toontown.hood', [className]) + + classObj = getattr(symbols[className], className) + animPropObj = classObj(animPropNode) + animPropList = self.animPropDict.setdefault(i, []) + animPropList.append(animPropObj) + + + interactivePropNodes = i.findAllMatches("**/interactive_prop_*") + numInteractivePropNodes = interactivePropNodes.getNumPaths() + for j in range(numInteractivePropNodes): + interactivePropNode = interactivePropNodes.getPath(j) + className = 'InteractiveAnimatedProp' + if "hydrant" in interactivePropNode.getName(): + className = "HydrantInteractiveProp" + elif "trashcan" in interactivePropNode.getName(): + className = "TrashcanInteractiveProp" + elif "mailbox" in interactivePropNode.getName(): + className = "MailboxInteractiveProp" + + symbols = {} + base.cr.importModule(symbols, 'toontown.hood', [className]) + + classObj = getattr(symbols[className], className) + interactivePropObj = classObj(interactivePropNode) + # [gjeon] I think we can use animPropList to store interactive props + animPropList = self.animPropDict.get(i) + if animPropList is None: + animPropList = self.animPropDict.setdefault(i, []) + animPropList.append(interactivePropObj) + if interactivePropObj.getCellIndex() == 0: + zoneId = int(i.getName()) + if not zoneId in self.zoneIdToInteractivePropDict: + self.zoneIdToInteractivePropDict[zoneId] = interactivePropObj + else: + self.notify.error("already have interactive prop %s in zone %s" % + (self.zoneIdToInteractivePropDict, zoneId)) + + animatedBuildingNodes = i.findAllMatches("**/*:animated_building_*;-h") + for np in animatedBuildingNodes: + if np.getName().startswith('sb'): + animatedBuildingNodes.removePath(np) + + numAnimatedBuildingNodes = animatedBuildingNodes.getNumPaths() + for j in range(numAnimatedBuildingNodes): + animatedBuildingNode = animatedBuildingNodes.getPath(j) + className = 'GenericAnimatedBuilding' + + symbols = {} + base.cr.importModule(symbols, 'toontown.hood', [className]) + + classObj = getattr(symbols[className], className) + animatedBuildingObj = classObj(animatedBuildingNode) + # [gjeon] I think we can use animPropList to store interactive props + animPropList = self.animPropDict.get(i) + if animPropList is None: + animPropList = self.animPropDict.setdefault(i, []) + animPropList.append(animatedBuildingObj) + + def deleteAnimatedProps(self): + for zoneNode, animPropList in self.animPropDict.items(): + for animProp in animPropList: + animProp.delete() + del self.animPropDict + + def enterAnimatedProps(self, zoneNode): + for animProp in self.animPropDict.get(zoneNode, ()): + animProp.enter() + + def exitAnimatedProps(self, zoneNode): + for animProp in self.animPropDict.get(zoneNode, ()): + animProp.exit() + + def getInteractiveProp(self, zoneId): + """Return the interactive prop for the battle cell at zone id, may return None""" + result = None + if zoneId in self.zoneIdToInteractivePropDict: + result = self.zoneIdToInteractivePropDict[zoneId] + return result + + diff --git a/toontown/src/town/TutorialStreet.py b/toontown/src/town/TutorialStreet.py new file mode 100644 index 0000000..d0ca5ec --- /dev/null +++ b/toontown/src/town/TutorialStreet.py @@ -0,0 +1,50 @@ + +import TTStreet + +class TutorialStreet(TTStreet.TTStreet): + + # This is where the meat of the tutorial hookup code is going to go. + # In fact, the whole state machine should probably go here. + + def enter(self, requestStatus): + # No visibility in the tutorial, please. + # turn arrows off, the one to flippy on the other side of HQ just confuses the user. + # the position of the Flunky is fairly obvious + TTStreet.TTStreet.enter(self, requestStatus, visibilityFlag=0, arrowsOn=0) + + def exit(self): + # No visibility in the tutorial, please. + TTStreet.TTStreet.exit(self, visibilityFlag=0) + + def enterTeleportIn(self, requestStatus): + TTStreet.TTStreet.enterTeleportIn(self, requestStatus) + # This is how the tutorial knows that the toon has arrived. + #messenger.send("toonEntersTutorial") + return + + def enterTownBattle(self, event): + # Here, we explicitly do not pay attention to the invasion status + # Let's just keep it simple and have creditMultiplier be 1.0 + # so the tutorial panels do not have to change to explain this + self.loader.townBattle.enter(event, self.fsm.getStateNamed("battle"), + tutorialFlag=1) + + def handleEnterTunnel(self, requestStatus, collEntry): + """ + This is an override of the version in Place.py, because + this is how we announce we are leaving the tutorial. + """ + messenger.send("stopTutorial") + TTStreet.TTStreet.handleEnterTunnel(self, requestStatus, collEntry) + return + + def exitDoorIn(self): + """ + This is an override of the version in Place.py, because + we want to leave arrows off + """ + base.localAvatar.obscureMoveFurnitureButton(-1) + + + + diff --git a/toontown/src/town/TutorialTownLoader.py b/toontown/src/town/TutorialTownLoader.py new file mode 100644 index 0000000..58b6d54 --- /dev/null +++ b/toontown/src/town/TutorialTownLoader.py @@ -0,0 +1,38 @@ +import TownLoader +import TTTownLoader +import TutorialStreet +from toontown.suit import Suit +from toontown.toon import Toon +from toontown.hood import ZoneUtil + +class TutorialTownLoader(TTTownLoader.TTTownLoader): + def __init__(self, hood, parentFSM, doneEvent): + TTTownLoader.TTTownLoader.__init__(self, hood, parentFSM, doneEvent) + self.streetClass = TutorialStreet.TutorialStreet + + # Override of of TTTownLoader, since the dna file is different. + def load(self, zoneId): + TownLoader.TownLoader.load(self, zoneId) + Suit.loadTutorialSuit() + dnaFile = ("phase_3.5/dna/tutorial_street.dna") + self.createHood(dnaFile, loadStorage=0) + self.alterDictionaries() + + def loadBattleAnims(self): + Toon.loadTutorialBattleAnims() + + def unloadBattleAnims(self): + Toon.unloadTutorialBattleAnims() + + def alterDictionaries(self): + # HACK: 20001 is a special zone number reserved explicitly for + # the tutorial street. We need to change it to the real zone + # number that has been assigned by the AI. I know this is strange, + # but I don't know how else to go about this. + # We need to use the external street zone, which we hackfully + # extract from ZoneUtil. + zoneId = ZoneUtil.tutorialDict["exteriors"][0] + self.nodeDict[zoneId] = self.nodeDict[20001] + del self.nodeDict[20001] + + diff --git a/toontown/src/town/__init__.py b/toontown/src/town/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/town/__pycache__/Street.cpython-37.pyc b/toontown/src/town/__pycache__/Street.cpython-37.pyc new file mode 100644 index 0000000..aba7092 Binary files /dev/null and b/toontown/src/town/__pycache__/Street.cpython-37.pyc differ diff --git a/toontown/src/trolley/.cvsignore b/toontown/src/trolley/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/trolley/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/trolley/Sources.pp b/toontown/src/trolley/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/trolley/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/trolley/Trolley.py b/toontown/src/trolley/Trolley.py new file mode 100644 index 0000000..f62e48f --- /dev/null +++ b/toontown/src/trolley/Trolley.py @@ -0,0 +1,263 @@ +from pandac.PandaModules import * +from toontown.toonbase.ToonBaseGlobal import * +from direct.gui.DirectGui import * +from pandac.PandaModules import * +from direct.interval.IntervalGlobal import * +from direct.fsm import ClassicFSM, State +from direct.fsm import State +from direct.fsm import StateData +from toontown.toontowngui import TTDialog +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer + +class Trolley(StateData.StateData): + def __init__(self, safeZone, parentFSM, doneEvent): + + StateData.StateData.__init__(self, doneEvent) + + self.fsm = ClassicFSM.ClassicFSM('Trolley', + [State.State('start', + self.enterStart, + self.exitStart, + ['requestBoard', + 'trolleyHFA', + 'trolleyTFA']), + State.State('trolleyHFA', + self.enterTrolleyHFA, + self.exitTrolleyHFA, + ['final', + ]), + State.State('trolleyTFA', + self.enterTrolleyTFA, + self.exitTrolleyTFA, + ['final', + ]), + State.State('requestBoard', + self.enterRequestBoard, + self.exitRequestBoard, + ['boarding']), + State.State('boarding', + self.enterBoarding, + self.exitBoarding, + ['boarded']), + State.State('boarded', + self.enterBoarded, + self.exitBoarded, + ['requestExit', + 'trolleyLeaving', + 'final']), + State.State('requestExit', + self.enterRequestExit, + self.exitRequestExit, + ['exiting', 'trolleyLeaving']), + State.State('trolleyLeaving', + self.enterTrolleyLeaving, + self.exitTrolleyLeaving, + ['final']), + State.State('exiting', + self.enterExiting, + self.exitExiting, + ['final']), + State.State('final', + self.enterFinal, + self.exitFinal, + ['start'])], + # Initial State + 'start', + # Final State + 'final', + ) + + self.parentFSM = parentFSM + + return None + + def load(self): + self.parentFSM.getStateNamed("trolley").addChild(self.fsm) + self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui") + self.upButton = self.buttonModels.find("**//InventoryButtonUp") + self.downButton = self.buttonModels.find("**/InventoryButtonDown") + self.rolloverButton = self.buttonModels.find( + "**/InventoryButtonRollover") + return + + def unload(self): + self.parentFSM.getStateNamed("trolley").removeChild(self.fsm) + del self.fsm + del self.parentFSM + self.buttonModels.removeNode() + del self.buttonModels + del self.upButton + del self.downButton + del self.rolloverButton + return + + def enter(self): + """enter(self) + """ + self.fsm.enterInitialState() + if base.localAvatar.hp > 0: + # let the distributed trolley know it is + # ok for us to enter the trolley + messenger.send('enterTrolleyOK') + self.fsm.request("requestBoard") + else: + # can't board if we are 'sad' + self.fsm.request("trolleyHFA") + return None + + def exit(self): + self.ignoreAll() + return None + + def enterStart(self): + return None + + def exitStart(self): + return None + + def enterTrolleyHFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyHFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyHFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def enterTrolleyTFA(self): + self.noTrolleyBox = TTDialog.TTGlobalDialog( + message = TTLocalizer.TrolleyTFAMessage, + doneEvent = "noTrolleyAck", + style = TTDialog.Acknowledge) + self.noTrolleyBox.show() + base.localAvatar.b_setAnimState("neutral", 1) + self.accept("noTrolleyAck", self.__handleNoTrolleyAck) + return + + def exitTrolleyTFA(self): + self.ignore("noTrolleyAck") + self.noTrolleyBox.cleanup() + del self.noTrolleyBox + return + + def __handleNoTrolleyAck(self): + ntbDoneStatus = self.noTrolleyBox.doneStatus + if ntbDoneStatus == "ok": + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + else: + self.notify.error("Unrecognized doneStatus: " + str(ntbDoneStatus)) + return + + def enterRequestBoard(self): + return None + + def handleRejectBoard(self): + doneStatus = {} + doneStatus["mode"] = "reject" + messenger.send(self.doneEvent, [doneStatus]) + + def exitRequestBoard(self): + return None + + def enterBoarding(self, nodePath): + camera.wrtReparentTo(nodePath) + self.cameraBoardTrack = LerpPosHprInterval(camera, 1.5, + Point3(-35, 0, 8), + Point3(-90, 0, 0)) + + self.cameraBoardTrack.start() + return None + + def exitBoarding(self): + self.ignore("boardedTrolley") + return None + + def enterBoarded(self): + self.enableExitButton() + return None + + def exitBoarded(self): + # Remove the boarding task... You might think this should be + # removed in exitBoarding, but we want the camera move to continue + # into the boarded state. Since boarding only goes directly into + # boarded state, it's okay. Probably boarding and boarded should + # be the same state. + self.cameraBoardTrack.finish() + self.disableExitButton() + return None + + def enableExitButton(self): + self.exitButton = DirectButton( + relief = None, + text = TTLocalizer.TrolleyHopOff, + text_fg = (1, 1, 0.65, 1), + text_pos = (0, -0.23), + text_scale = TTLocalizer.TtrolleyHopOff, + image = (self.upButton, self.downButton, self.rolloverButton), + image_color = (1, 0, 0, 1), + image_scale = (20, 1, 11), + pos = (0, 0, 0.8), + scale = 0.15, + command = lambda self=self: self.fsm.request("requestExit"), + ) + return + + def disableExitButton(self): + self.exitButton.destroy() + return + + def enterRequestExit(self): + messenger.send("trolleyExitButton") + return None + + def exitRequestExit(self): + return None + + def enterTrolleyLeaving(self): + # A camera move + camera.lerpPosHprXYZHPR(0, 18.55, 3.75, -180, 0, 0, 3, + blendType = "easeInOut", task="leavingCamera") + self.acceptOnce("playMinigame", self.handlePlayMinigame) + return None + + def handlePlayMinigame(self, zoneId, minigameId): + base.localAvatar.b_setParent(ToontownGlobals.SPHidden) + doneStatus = {} + doneStatus["mode"] = "minigame" + doneStatus["zoneId"] = zoneId + doneStatus["minigameId"] = minigameId + messenger.send(self.doneEvent, [doneStatus]) + + def exitTrolleyLeaving(self): + self.ignore("playMinigame") + taskMgr.remove("leavingCamera") + return None + + def enterExiting(self): + return None + + def handleOffTrolley(self): + doneStatus = {} + doneStatus["mode"] = "exit" + messenger.send(self.doneEvent, [doneStatus]) + return None + + def exitExiting(self): + return None + + def enterFinal(self): + return None + + def exitFinal(self): + return None + diff --git a/toontown/src/trolley/__init__.py b/toontown/src/trolley/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/ttinst/.cvsignore b/toontown/src/ttinst/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/ttinst/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/ttinst/Sources.pp b/toontown/src/ttinst/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/ttinst/Toontown.cfg b/toontown/src/ttinst/Toontown.cfg new file mode 100644 index 0000000..9b54724 --- /dev/null +++ b/toontown/src/ttinst/Toontown.cfg @@ -0,0 +1,8 @@ +[MYSINGLEFILE] +name= Toontown.pyz +type= PYZ +userunw = 0 +misc = +script= ttstart.py +debug = 0 +directories = . diff --git a/toontown/src/ttinst/__init__.py b/toontown/src/ttinst/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/ttinst/make.py b/toontown/src/ttinst/make.py new file mode 100644 index 0000000..a2c1908 --- /dev/null +++ b/toontown/src/ttinst/make.py @@ -0,0 +1,2 @@ +import os +os.system('python -OO ' + os.path.expandvars('$DIRECT/src/pyinst/Builder.py') + ' Toontown.cfg') diff --git a/toontown/src/ttinst/run.py b/toontown/src/ttinst/run.py new file mode 100644 index 0000000..57c4b95 --- /dev/null +++ b/toontown/src/ttinst/run.py @@ -0,0 +1,7 @@ +from direct.pyinst import imputil +from direct.pyinst import archive_rt + +za = archive_rt.ZlibArchive("Toontown.pyz") +imputil.FuncImporter(za.get_code).install() + +from toontown.toonbase.ToontownStart import * diff --git a/toontown/src/ttinst/ttstart.py b/toontown/src/ttinst/ttstart.py new file mode 100644 index 0000000..808f1a5 --- /dev/null +++ b/toontown/src/ttinst/ttstart.py @@ -0,0 +1 @@ +from toontown.toonbase.ToontownStart import * diff --git a/toontown/src/tutorial/.cvsignore b/toontown/src/tutorial/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/tutorial/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/tutorial/DistributedBattleTutorial.py b/toontown/src/tutorial/DistributedBattleTutorial.py new file mode 100644 index 0000000..65ad673 --- /dev/null +++ b/toontown/src/tutorial/DistributedBattleTutorial.py @@ -0,0 +1,14 @@ +from toontown.battle import DistributedBattle +from direct.directnotify import DirectNotifyGlobal + +class DistributedBattleTutorial(DistributedBattle.DistributedBattle): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorial') + + def startTimer(self, ts=0): + # Instead of starting the countdown, + # hide the clock! + self.townBattle.timer.hide() + + def playReward(self, ts): + self.movie.playTutorialReward(ts, self.uniqueName('reward'), + self.handleRewardDone) diff --git a/toontown/src/tutorial/DistributedBattleTutorialAI.py b/toontown/src/tutorial/DistributedBattleTutorialAI.py new file mode 100644 index 0000000..7e7523f --- /dev/null +++ b/toontown/src/tutorial/DistributedBattleTutorialAI.py @@ -0,0 +1,23 @@ +from toontown.battle import DistributedBattleAI +from toontown.battle import DistributedBattleBaseAI +from direct.directnotify import DirectNotifyGlobal + +class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorialAI') + + def __init__(self, air, battleMgr, pos, suit, toonId, zoneId, + finishCallback=None, maxSuits=4, interactivePropTrackBonus = -1): + """__init__(air, battleMgr, pos, suit, toonId, zoneId, + finishCallback, maxSuits) + """ + DistributedBattleAI.DistributedBattleAI.__init__( + self, air, battleMgr, pos, suit, toonId, zoneId, + finishCallback, maxSuits, tutorialFlag=1) + + # There is no timer in the tutorial... The reward movie is random length. + def startRewardTimer(self): + pass + + #def handleRewardDone(self): + # DistributedBattleAI.DistributedBattleAI.handleRewardDone(self) + diff --git a/toontown/src/tutorial/Sources.pp b/toontown/src/tutorial/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/src/tutorial/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/src/tutorial/SuitPlannerTutorialAI.py b/toontown/src/tutorial/SuitPlannerTutorialAI.py new file mode 100644 index 0000000..e36e069 --- /dev/null +++ b/toontown/src/tutorial/SuitPlannerTutorialAI.py @@ -0,0 +1,76 @@ +""" SuitPlannerTutorial module: contains the SuitPlannerTutorial class + which handles management of the suit you will fight during the + tutorial.""" + +from otp.ai.AIBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from toontown.suit import DistributedTutorialSuitAI +import TutorialBattleManagerAI + +class SuitPlannerTutorialAI: + """ + SuitPlannerTutorialAI: manages the single suit that you fight during + the tutorial. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'SuitPlannerTutorialAI') + + def __init__(self, air, zoneId, battleOverCallback): + # Store these things + self.zoneId = zoneId + self.air = air + self.battle = None + # This callback will be used to open the HQ doors when the + # battle is over. + self.battleOverCallback = battleOverCallback + + # Create a battle manager + self.battleMgr = TutorialBattleManagerAI.TutorialBattleManagerAI( + self.air) + + # Create a flunky + newSuit = DistributedTutorialSuitAI.DistributedTutorialSuitAI(self.air, self) + newSuit.setupSuitDNA(1, 1, "c") + # This is a special tutorial path state + newSuit.generateWithRequired(self.zoneId) + self.suit = newSuit + + def cleanup(self): + self.zoneId = None + self.air = None + if self.suit: + self.suit.requestDelete() + self.suit = None + if self.battle: + #self.battle.requestDelete() + #RAU made to kill the mem leak when you close the window in the middle of the battle tutorial + cellId = self.battle.battleCellId + battleMgr = self.battle.battleMgr + if battleMgr.cellId2battle.has_key(cellId): + battleMgr.destroy(self.battle) + + self.battle = None + + def getDoId(self): + # This is here because the suit expects the suit planner to be + # a distributed object, if it has a suit planner. We want it to + # have a suit planner, but not a distributed one, so we return + # 0 when asked what our DoId is. Kind of hackful, I guess. + return 0 + + def requestBattle(self, zoneId, suit, toonId): + # 70, 20, 0 is a battle cell position that I just made up. + self.battle = self.battleMgr.newBattle( + zoneId, zoneId, Vec3(35, 20, 0), + suit, toonId, + finishCallback=self.battleOverCallback) + return 1 + + def removeSuit(self, suit): + # Get rid of the suit. + suit.requestDelete() + self.suit = None + + diff --git a/toontown/src/tutorial/TutorialBattleManagerAI.py b/toontown/src/tutorial/TutorialBattleManagerAI.py new file mode 100644 index 0000000..c8683ab --- /dev/null +++ b/toontown/src/tutorial/TutorialBattleManagerAI.py @@ -0,0 +1,13 @@ +from toontown.battle import BattleManagerAI +from direct.directnotify import DirectNotifyGlobal +import DistributedBattleTutorialAI + +class TutorialBattleManagerAI(BattleManagerAI.BattleManagerAI): + + notify = DirectNotifyGlobal.directNotify.newCategory('TutorialBattleManagerAI') + + def __init__(self, air): + BattleManagerAI.BattleManagerAI.__init__(self, air) + self.battleConstructor = DistributedBattleTutorialAI.DistributedBattleTutorialAI + + diff --git a/toontown/src/tutorial/TutorialForceAcknowledge.py b/toontown/src/tutorial/TutorialForceAcknowledge.py new file mode 100644 index 0000000..b23ac16 --- /dev/null +++ b/toontown/src/tutorial/TutorialForceAcknowledge.py @@ -0,0 +1,35 @@ +from pandac.PandaModules import * +from toontown.toontowngui import TTDialog +from toontown.toonbase import TTLocalizer + +class TutorialForceAcknowledge: + + def __init__(self, doneEvent): + """___init___(self, Event)""" + self.doneEvent = doneEvent + self.dialog = None + return + + def enter(self): + """enter(phase) + """ + # Make the toon stop running. + base.localAvatar.loop("neutral") + self.doneStatus = {'mode' : 'incomplete'} + msg = TTLocalizer.TutorialForceAcknowledgeMessage + self.dialog = TTDialog.TTDialog(text = msg, + command = self.handleOk, + style = TTDialog.Acknowledge) + return + + def exit(self): + """exit(self) + """ + if self.dialog: + self.dialog.cleanup() + self.dialog = None + return + + def handleOk(self, value): + messenger.send(self.doneEvent, [self.doneStatus]) + return diff --git a/toontown/src/tutorial/TutorialManager.py b/toontown/src/tutorial/TutorialManager.py new file mode 100644 index 0000000..27593d5 --- /dev/null +++ b/toontown/src/tutorial/TutorialManager.py @@ -0,0 +1,70 @@ + +from pandac.PandaModules import * + +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from toontown.hood import ZoneUtil + +class TutorialManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManager") + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + + def generate(self): + DistributedObject.DistributedObject.generate(self) + # Let the cr know we have arrived. + messenger.send("tmGenerate") + # Wait for a tutorial request or rejection. + self.accept("requestTutorial", self.d_requestTutorial) + self.accept("requestSkipTutorial", self.d_requestSkipTutorial) + self.accept("rejectTutorial", self.d_rejectTutorial) + + def disable(self): + self.ignoreAll() + # In case we fell asleep in the tutorial + ZoneUtil.overrideOff() + DistributedObject.DistributedObject.disable(self) + + def d_requestTutorial(self): + self.sendUpdate("requestTutorial", []) + + def d_rejectTutorial(self): + self.sendUpdate("rejectTutorial", []) + + def d_requestSkipTutorial(self): + self.sendUpdate("requestSkipTutorial", []) + + def skipTutorialResponse(self, allOk): + """Handle AI responding to our skip tutorial request.""" + messenger.send("skipTutorialAnswered", [allOk]) + + def enterTutorial(self, branchZone, streetZone, shopZone, hqZone): + base.localAvatar.cantLeaveGame = 1 + # Override the ZoneUtil + ZoneUtil.overrideOn(branch=branchZone, + exteriorList=[streetZone], + interiorList=[shopZone, hqZone]) + # We start the tutorial in the gag shop. + messenger.send("startTutorial", [shopZone]) + # Add a hook on the tutorialDone event, which will get + # thrown when we are leaving the tutorial (by the handleEnterTunnel + # function in TutorialStreet.py + self.acceptOnce("stopTutorial", self.__handleStopTutorial) + # Add a hook that the Tutorial hood will send when the toon + # is fully in a zone. This lets the AI know it is clear to + # reset the toon properties in preparation for the tutorial + # (in case they bailed halfway through before) + self.acceptOnce("toonArrivedTutorial", self.d_toonArrived) + + def __handleStopTutorial(self): + base.localAvatar.cantLeaveGame = 0 + self.d_allDone() + ZoneUtil.overrideOff() + + def d_allDone(self): + self.sendUpdate("allDone", []) + + def d_toonArrived(self): + self.sendUpdate("toonArrived", []) diff --git a/toontown/src/tutorial/TutorialManagerAI.py b/toontown/src/tutorial/TutorialManagerAI.py new file mode 100644 index 0000000..5fca2e2 --- /dev/null +++ b/toontown/src/tutorial/TutorialManagerAI.py @@ -0,0 +1,322 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from toontown.building import TutorialBuildingAI +from toontown.building import TutorialHQBuildingAI +import SuitPlannerTutorialAI +from toontown.toonbase import ToontownBattleGlobals +from toontown.toon import NPCToons +from toontown.toonbase import TTLocalizer +from toontown.ai import BlackCatHolidayMgrAI +from toontown.ai import DistributedBlackCatMgrAI + +class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManagerAI") + + # how many seconds do we wait for the toon to appear on AI before we + # nuke his skip tutorial request + WaitTimeForSkipTutorial = 5.0 + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + # This is a dictionary of all the players who are currently in + # tutorials. We need to create things when someone requests + # a tutorial, and destroy them when they leave. + self.playerDict = {} + + # There are only two blocks in the tutorial. One for the gag shop + # building, and one for the Toon HQ. If there aren't, something + # is wrong. + self.dnaStore = DNAStorage() + + dnaFile = simbase.air.lookupDNAFileName("tutorial_street.dna") + self.air.loadDNAFileAI(self.dnaStore, dnaFile) + numBlocks = self.dnaStore.getNumBlockNumbers() + assert numBlocks == 2 + # Assumption: the only block that isn't an HQ is the gag shop block. + self.hqBlock = None + self.gagBlock = None + for blockIndex in range (0, numBlocks): + blockNumber = self.dnaStore.getBlockNumberAt(blockIndex) + buildingType = self.dnaStore.getBlockBuildingType(blockNumber) + if (buildingType == 'hq'): + self.hqBlock = blockNumber + else: + self.gagBlock = blockNumber + + assert self.hqBlock and self.gagBlock + + # key is avId, value is real time when the request was made + self.avIdsRequestingSkip = {} + self.accept("avatarEntered", self.waitingToonEntered ) + + return None + + def requestTutorial(self): + # TODO: possible security breach: what if client is repeatedly + # requesting tutorial? can client request tutorial from playground? + # can client request tutorial if hp is at least 16? How do we + # handle these cases? + avId = self.air.getAvatarIdFromSender() + # Handle unexpected exits + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, + extraArgs=[avId]) + # allocate tutorial objects and zones + zoneDict = self.__createTutorial(avId) + # Tell the player to enter the zone + self.d_enterTutorial(avId, + zoneDict["branchZone"], + zoneDict["streetZone"], + zoneDict["shopZone"], + zoneDict["hqZone"] + ) + self.air.writeServerEvent('startedTutorial', avId, '') + + def toonArrived(self): + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists + av = self.air.doId2do.get(avId) + # Clear out the avatar's quests, hp, inventory, and everything else in case + # he made it half way through the tutorial last time. + if av: + # No quests + av.b_setQuests([]) + av.b_setQuestHistory([]) + av.b_setRewardHistory(0, []) + av.b_setQuestCarryLimit(1) + # Starting HP + av.b_setMaxHp(15) + av.b_setHp(15) + # No exp + av.experience.zeroOutExp() + av.d_setExperience(av.experience.makeNetString()) + # One cupcake and one squirting flower + av.inventory.zeroInv() + av.inventory.addItem(ToontownBattleGlobals.THROW_TRACK, 0) + av.inventory.addItem(ToontownBattleGlobals.SQUIRT_TRACK, 0) + av.d_setInventory(av.inventory.makeNetString()) + # No cogs defeated + av.b_setCogStatus([1] * 32) + av.b_setCogCount([0] * 32) + return + + def allDone(self): + avId = self.air.getAvatarIdFromSender() + # No need to worry further about unexpected exits + self.ignore(self.air.getAvatarExitEvent(avId)) + # Make sure the avatar exists + av = self.air.doId2do.get(avId) + if av: + self.air.writeServerEvent('finishedTutorial', avId, '') + av.b_setTutorialAck(1) + self.__destroyTutorial(avId) + else: + self.notify.warning( + "Toon " + + str(avId) + + " isn't here, but just finished a tutorial. " + + "I will ignore this." + ) + return + + def __createTutorial(self, avId): + if self.playerDict.get(avId): + self.notify.warning(str(avId) + " is already in the playerDict!") + + branchZone = self.air.allocateZone() + streetZone = self.air.allocateZone() + shopZone = self.air.allocateZone() + hqZone = self.air.allocateZone() + # Create a building object + building = TutorialBuildingAI.TutorialBuildingAI(self.air, + streetZone, + shopZone, + self.gagBlock) + # Create an HQ object + hqBuilding = TutorialHQBuildingAI.TutorialHQBuildingAI(self.air, + streetZone, + hqZone, + self.hqBlock) + + def battleOverCallback(zoneId): + hqBuilding.battleOverCallback() + building.battleOverCallback() + + # Create a suit planner + suitPlanner = SuitPlannerTutorialAI.SuitPlannerTutorialAI( + self.air, + streetZone, + battleOverCallback) + + # Create the NPC blocking the tunnel to the playground + blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone, + questCallback=self.__handleBlockDone) + blockerNPC.setTutorial(1) + + # is the black cat holiday enabled? + blackCatMgr = None + if bboard.has(BlackCatHolidayMgrAI.BlackCatHolidayMgrAI.PostName): + blackCatMgr = DistributedBlackCatMgrAI.DistributedBlackCatMgrAI( + self.air, avId) + blackCatMgr.generateWithRequired(streetZone) + + zoneDict={"branchZone" : branchZone, + "streetZone" : streetZone, + "shopZone" : shopZone, + "hqZone" : hqZone, + "building" : building, + "hqBuilding" : hqBuilding, + "suitPlanner" : suitPlanner, + "blockerNPC" : blockerNPC, + "blackCatMgr" : blackCatMgr, + } + self.playerDict[avId] = zoneDict + return zoneDict + + def __handleBlockDone(self): + return None + + def __destroyTutorial(self, avId): + zoneDict = self.playerDict.get(avId) + if zoneDict: + zoneDict["building"].cleanup() + zoneDict["hqBuilding"].cleanup() + zoneDict["blockerNPC"].requestDelete() + if zoneDict["blackCatMgr"]: + zoneDict["blackCatMgr"].requestDelete() + self.air.deallocateZone(zoneDict["branchZone"]) + self.air.deallocateZone(zoneDict["streetZone"]) + self.air.deallocateZone(zoneDict["shopZone"]) + self.air.deallocateZone(zoneDict["hqZone"]) + zoneDict["suitPlanner"].cleanup() + del self.playerDict[avId] + else: + self.notify.warning("Tried to deallocate zones for " + + str(avId) + + " but none were present in playerDict.") + + def rejectTutorial(self): + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists + av = self.air.doId2do.get(avId) + if av: + # Acknowlege that the player has seen a tutorial + self.air.writeServerEvent('finishedTutorial', avId, '') + av.b_setTutorialAck(1) + + self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [1]) + else: + self.notify.warning( + "Toon " + + str(avId) + + " isn't here, but just rejected a tutorial. " + + "I will ignore this." + ) + return + + def respondToSkipTutorial(self, avId, av): + """Reply to the client if we let him skip the tutorial.""" + self.notify.debugStateCall(self) + assert avId + assert av + response = 1 + if av: + if av.tutorialAck: + self.air.writeServerEvent('suspicious', avId, 'requesting skip tutorial, but tutorialAck is 1') + response = 0 + + if av and response: + # Acknowlege that the player has seen a tutorial + self.air.writeServerEvent('skippedTutorial', avId, '') + av.b_setTutorialAck(1) + # these values were taken by running a real tutorial + self.air.questManager.assignQuest(avId, + 20000, + 101, + 100, + 1000, + 1 + ) + + self.air.questManager.completeAllQuestsMagically(av) + av.removeQuest(101) + self.air.questManager.assignQuest(avId, + 1000, + 110, + 2, + 1000, + 0 + ) + self.air.questManager.completeAllQuestsMagically(av) + + # do whatever needs to be done to make his quest state good + elif av: + self.notify.debug("%s requestedSkipTutorial, but tutorialAck is 1") + else: + response = 0 + self.notify.warning( + "Toon " + + str(avId) + + " isn't here, but requested to skip tutorial. " + + "I will ignore this." + ) + self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [response]) + return + + def waitingToonEntered(self, av): + """Check if the avatar is someone who's requested to skip, then proceed accordingly.""" + avId = av.doId + if avId in self.avIdsRequestingSkip: + requestTime = self.avIdsRequestingSkip[avId] + + curTime = globalClock.getFrameTime() + if (curTime - requestTime) <= self.WaitTimeForSkipTutorial: + self.respondToSkipTutorial(avId, av) + else: + self.notify.warning("waited too long for toon %d responding no to skip tutorial request" % avId) + self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0]) + del self.avIdsRequestingSkip[avId] + self.removeTask("skipTutorialToon-%d" % avId) + + + def waitForToonToEnter(self,avId): + """Mark our toon as requesting to skip, and start a task to timeout for it.""" + self.notify.debugStateCall(self) + self.avIdsRequestingSkip[avId] = globalClock.getFrameTime() + self.doMethodLater(self.WaitTimeForSkipTutorial, self.didNotGetToon, "skipTutorialToon-%d" % avId, [avId]) + + def didNotGetToon(self, avId): + """Just say no since the AI didn't get it.""" + self.notify.debugStateCall(self) + if avId in self.avIdsRequestingSkip: + del self.avIdsRequestingSkip[avId] + self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0]) + return Task.done + + def requestSkipTutorial(self): + """We are requesting to skip tutorial, add other quest history to be consistent.""" + self.notify.debugStateCall(self) + avId = self.air.getAvatarIdFromSender() + # Make sure the avatar exists + av = self.air.doId2do.get(avId) + if av: + self.respondToSkipTutorial(avId,av) + else: + self.waitForToonToEnter(avId) + + def d_enterTutorial(self, avId, branchZone, streetZone, shopZone, hqZone): + self.sendUpdateToAvatarId(avId, "enterTutorial", [branchZone, + streetZone, + shopZone, + hqZone]) + return + + def __handleUnexpectedExit(self, avId): + self.notify.warning("Avatar: " + str(avId) + + " has exited unexpectedly") + self.__destroyTutorial(avId) + return + + diff --git a/toontown/src/tutorial/__init__.py b/toontown/src/tutorial/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/uberdog/.cvsignore b/toontown/src/uberdog/.cvsignore new file mode 100644 index 0000000..985f113 --- /dev/null +++ b/toontown/src/uberdog/.cvsignore @@ -0,0 +1,4 @@ +.cvsignore +Makefile +pp.dep +*.pyc diff --git a/toontown/src/uberdog/DataStore.py b/toontown/src/uberdog/DataStore.py new file mode 100644 index 0000000..f3bb7d6 --- /dev/null +++ b/toontown/src/uberdog/DataStore.py @@ -0,0 +1,287 @@ +from direct.directnotify import DirectNotifyGlobal +from pandac.PandaModules import ConfigVariableBool +from direct.task import Task + +from string import maketrans +import cPickle +import os +import sys +import anydbm +import time + +class DataStore: + """ + This is the base class for all temporary data storage containers on + the Uberdog. It handles all the disk access needs for us. In order + to create a usable store, subclass this class and override the + handleQuery() function. Also, instantiate a corresponding + DataStoreAIClient on the AI. + + See DataStoreGlobals.py, TrickOrTreatScavengerHuntDataStore.py, + and TrickOrTreatMgrAI.py for an example of how to use this class. + """ + + # Define the available query strings. + # The DataStoreAIClient will take one of these strings + # as a parameter to send a query. + # This is here in case we think of any universal queries. + QueryTypes = [] # ['Query_one','Query_two',...] + QueryTypes = dict(zip(QueryTypes,range(len(QueryTypes)))) + + @classmethod + def addQueryTypes(cls,typeStrings): + superTypes = zip(cls.QueryTypes.values(),cls.QueryTypes.keys()) + superTypes.sort() + newTypes = [item[1] for item in superTypes] + typeStrings + newTypes = dict(zip(newTypes,range(1+len(newTypes)))) + return newTypes + + notify = DirectNotifyGlobal.directNotify.newCategory('DataStore') + + wantAnyDbm = ConfigVariableBool('want-ds-anydbm',1).getValue() + + def __init__(self,filepath,writePeriod = 300, writeCountTrigger = 100): + """ + filepath is where the data for this store will be held on the disk. + writePeriod is a value, in seconds, for how often we should check + to see if the data needs to synchronized with the disk data. If + they match already, the timer resets to 0 without writing. + writeCountTrigger is maximum number of writes we can perform before + synchronizing with the disk data. This will also reset the + writePeriod timer to 0. + """ + self.filepath = filepath + self.writePeriod = writePeriod + self.writeCountTrigger = writeCountTrigger + self.writeCount = 0 + self.data = None + self.className = self.__class__.__name__ + + if self.wantAnyDbm: + self.filepath += '-anydbm' + self.notify.debug('anydbm default module used: %s ' % anydbm._defaultmod.__name__) + + self.open() + + def readDataFromFile(self): + """ + Looks for a backup data file, ie. A file that only exists + while the current data is being written to disk. If it is + present, we assume there was some failure during the last + data synchronization and we should use the backup data instead. + If it is not present we look for the normal data file. + + We then set self.data to the values loaded from the chosen + file and return True. + + If no file is present, self.data is set to None, and we + return False. + """ + #import pdb; pdb.set_trace() + if self.wantAnyDbm: + try: + if os.path.exists(self.filepath): + self.data = anydbm.open(self.filepath,'w') + self.notify.debug('Opening existing anydbm database at: %s.' % \ + (self.filepath,)) + else: + self.data = anydbm.open(self.filepath,'c') + self.notify.debug('Creating new anydbm database at: %s.' % \ + (self.filepath,)) + except anydbm.error: + self.notify.warning('Cannot open anydbm database at: %s.' % \ + (self.filepath,)) + + else: + try: + # Try to open the backup file: + file = open(self.filepath + '.bu', 'r') + self.notify.debug('Opening backup pickle data file at %s.' % \ + (self.filepath+'.bu',)) + # Remove the (assumed) broken file: + if os.path.exists(self.filepath): + os.remove(self.filepath) + except IOError: + # OK, there's no backup file, good. + try: + # Open the real file: + file = open(self.filepath, 'r') + self.notify.debug('Opening old pickle data file at %s..' % \ + (self.filepath,)) + except IOError: + # OK, there's no file. + file = None + self.notify.debug('New pickle data file will be written to %s.' % \ + (self.filepath,)) + if file: + data = cPickle.load(file) + file.close() + self.data = data + else: + self.data = {} + + def writeDataToFile(self): + """ + Attempt to store the contents of self.data to disk. + + If not using anydbm, a backup file is created for + the duration of this process and then deleted when + complete. + """ + if self.data is not None: + self.notify.debug('Data is now synced with disk at %s' % \ + self.filepath) + if self.wantAnyDbm: + self.data.sync() + else: + try: + backuppath = self.filepath+ '.bu' + if os.path.exists(self.filepath): + os.rename(self.filepath,backuppath) + + outfile = open(self.filepath, 'w') + cPickle.dump(self.data,outfile) + outfile.close() + + if os.path.exists(backuppath): + os.remove(backuppath) + except EnvironmentError: + self.notify.warning(str(sys.exc_info()[1])) + else: + self.notify.warning('No data to write. Aborting sync.') + + + def syncTask(self,task): + """ + This task is responsible for synchronizing the data in memory with + the data on the disk. + + It will write under two conditions: + 1 - The data is out of sync and a specified amount of time + has passed. + 2 - There have been a specified number of updates to the + data in memory. + """ + task.timeElapsed += globalClock.getDt() + + if task.timeElapsed > self.writePeriod: + if self.writeCount: + self.writeDataToFile() + self.resetWriteCount() + task.timeElapsed = 0.0 + + + if self.writeCount > self.writeCountTrigger: + self.writeDataToFile() + self.resetWriteCount() + task.timeElapsed = 0.0 + + return Task.cont + + def incrementWriteCount(self): + """ + Record that we have updated the data. + """ + self.writeCount += 1 + + def resetWriteCount(self): + """ + Clear the update status of the data. + """ + self.writeCount = 0 + + def close(self): + """ + Syncs the RAM data with the disk. + Removes the syncTask from the taskMgr. + sets self.data to None. + """ + if self.data is not None: + self.writeDataToFile() + if self.wantAnyDbm: + self.data.close() + taskMgr.remove('%s-syncTask'%(self.className,)) + self.data = None + + def open(self): + """ + Loads the data from disk into RAM. + Starts the periodic update task. + """ + self.close() + self.readDataFromFile() + self.resetWriteCount() + + taskMgr.remove('%s-syncTask'%(self.className,)) + t = taskMgr.add(self.syncTask,'%s-syncTask'%(self.className,)) + t.timeElapsed = 0.0 + + def reset(self): + """ + Destroys the store's data and opens a blank store. + """ + self.destroy() + self.open() + + def destroy(self): + """ + Closes the store. + Creates a backup (anydbm) or deletes the data from the disk. + """ + self.close() + if self.wantAnyDbm: + lt = time.asctime(time.localtime()) + trans = maketrans(': ','__') + t = lt.translate(trans) + head, tail = os.path.split(self.filepath) + newFileName = 'UDStoreBak'+t + if os.path.exists(self.filepath): + try: + os.rename(tail, newFileName) + uber.air.writeServerEvent('Uberdog data store Info', 0 \ + , 'Creating backup of file: %s saving as: %s' %(tail, newFileName)) + except: + uber.air.writeServerEvent('Uberdog data store Info', 0 \ + , 'Unable to create backup of file: %s ' %tail) + else: + # Remove the filename with all sufix's + # .bak, .dir, .dat + files = os.listdir(head) + for file in files: + if file.find(tail)>-1: + filename, ext = os.path.splitext(file) + try: + os.rename(file, newFileName+ext) + uber.air.writeServerEvent('Uberdog data store Info', 0 \ + , 'Creating backup of file: %s saving as: %s' %(file,newFileName+ext)) + except: + uber.air.writeServerEvent('Uberdog data store Info', 0 \ + , 'Unable to create backup of file: %s ' %newFileName+ext) + else: + if os.path.exists(self.filepath + '.bu'): + os.remove(self.filepath + '.bu') + if os.path.exists(self.filepath): + os.remove(self.filepath) + + def query(self,query): + """ + Unpacks a query and sends the unpacked data + to handleQuery(). It then packs the results from + the handleQuery() call and returns them. + """ + if self.data is not None: + qData = cPickle.loads(query) + results = self.handleQuery(qData) + qResults = cPickle.dumps(results) + else: + results = None + qResults = cPickle.dumps(results) + return qResults + + def handleQuery(self,query): + """ + This should be overridden by subclasses. + """ + results = None + return results + diff --git a/toontown/src/uberdog/DataStoreAIClient.py b/toontown/src/uberdog/DataStoreAIClient.py new file mode 100644 index 0000000..9797108 --- /dev/null +++ b/toontown/src/uberdog/DataStoreAIClient.py @@ -0,0 +1,140 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from toontown.uberdog import DataStoreGlobals +from direct.showbase.DirectObject import DirectObject +import cPickle + +class DataStoreAIClient(DirectObject): + """ + This class should be instantiated by any class that needs to + access an Uberdog data store. + + The client, as it is now, has the ability to create and destroy + DataStores on the Uberdog. This is mainly provided for backwards + compatibility with the Toontown architecture where the logic has + already been written for the AI side of things. + + For example, the HolidayManagerAI is something that could feasably + be run on the Uberdog, however it's already well established on + the AI. For this reason, we'll allow the HolidayManagerAI to + create and destroy data stores as it needs to. + + All it takes is one request to the Uberdog to carry out one of + these operations. Any further requests for data to an already + destroyed store will go unanswered. + + In the future, we should make attempts to keep the create/destroy + control on the Uberdog. That way, we have only one point of control + rather than several various AIs who may not be entirely in sync. + """ + + notify = directNotify.newCategory('DataStoreAIClient') + wantDsm = simbase.config.GetBool('want-ddsm', 1) + + def __init__(self,air,storeId,resultsCallback): + """ + storeId is a unique identifier to the type of store + the client wishes to connect to. There will only be + one store of this type on the Uberdog at any given time. + + resultsCallback is a function that accepts one argument, + the results returned from a query. The format of this + result argument is defined in the store's class definition. + """ + + if self.wantDsm: + self.__storeMgr = air.dataStoreManager + self.__storeId = storeId + self.__resultsCallback = resultsCallback + self.__storeClass = DataStoreGlobals.getStoreClass(storeId) + self.__queryTypesDict = self.__storeClass.QueryTypes + self.__queryStringDict = dict(zip(self.__queryTypesDict.values(), + self.__queryTypesDict.keys())) + self.__enabled = False + + def openStore(self): + """ + Attempt to connect to the store defined by the storeId in the + __init__() function. If no store of this type is present on + the Uberdog, the store is created at this time. Queries can now + be sent to the store and replies from the store will be processed + by the client. + """ + if self.wantDsm: + self.__storeMgr.startStore(self.__storeId) + self.__startClient() + + def closeStore(self): + """ + This client will no longer receive results from the store. Also, + the store, if present on the Uberdog, will now be shutdown and all + data destroyed. Do not use this method unless you are sure that + the data is no longer needed by this, or any other, client. + """ + if self.wantDsm: + self.__stopClient() + self.__storeMgr.stopStore(self.__storeId) + + def isOpen(self): + return self.__enabled + + def getQueryTypes(self): + return self.__queryTypesDict.keys() + + def getQueryTypeString(self,qId): + return self.__queryStringDict.get(qId,None) + + def sendQuery(self,queryTypeString,queryData): + """ + Sends a query to the data store. The format of the query is + defined in the store's class definition. + """ + if self.__enabled: + qId = self.__queryTypesDict.get(queryTypeString,None) + if qId is not None: + query = (qId,queryData) + # pack the data to be sent to the Uberdog store. + pQuery = cPickle.dumps(query) + self.__storeMgr.queryStore(self.__storeId,pQuery) + else: + self.notify.debug('Tried to send invalid query type: \'%s\'' % (queryTypeString,)) + else: + self.notify.warning('Client currently stopped. \'%s\' query will fail.' % (queryTypeString,)) + + def receiveResults(self,data): + """ + Upon receiving a query, the store will respond with a result. + This function will call the resultsCallback function with the + result data as its sole argument. Try to treat the + resultsCallback function as an event that is fired whenever + the client receives data from the store. + """ + # unpack the results from the Uberdog store. + + if data == 'Store not found': + self.notify.debug('%s not present on uberdog. Query dropped.' %(self.__storeClass.__name__,)) + else: + results = cPickle.loads(data) + self.__resultsCallback(results) + + def __startClient(self): + """ + Allow the client to send queries and receive results from its + associated data store. + """ + self.accept('TDS-results-%d'%self.__storeId,self.receiveResults) + self.__enabled = True + + def __stopClient(self): + """ + Disallow the client from sending queries and receiving results + from its associated data store. + """ + self.ignoreAll() + self.__enabled = False + + def deleteBackupStores(self): + """ + Delete any backed up stores from previous year's + """ + if self.wantDsm: + self.__storeMgr.deleteBackupStores() diff --git a/toontown/src/uberdog/DataStoreGlobals.py b/toontown/src/uberdog/DataStoreGlobals.py new file mode 100644 index 0000000..bdc01a4 --- /dev/null +++ b/toontown/src/uberdog/DataStoreGlobals.py @@ -0,0 +1,17 @@ +from toontown.uberdog.ScavengerHuntDataStore import * +from toontown.uberdog.DataStore import * + +SH = 1 +GEN = 2 + +TYPES = { + # id : { classType, } + # Trick-or-treat scavenger hunt + SH : ( ScavengerHuntDataStore, ), + GEN : ( DataStore , ), + } + +def getStoreClass(type): + storeClass = TYPES.get(type,None) + if storeClass: + return storeClass[0] diff --git a/toontown/src/uberdog/DistributedAvatarManager.py b/toontown/src/uberdog/DistributedAvatarManager.py new file mode 100644 index 0000000..edcfa94 --- /dev/null +++ b/toontown/src/uberdog/DistributedAvatarManager.py @@ -0,0 +1,5 @@ + +from otp.uberdog.OtpAvatarManager import OtpAvatarManager + +class DistributedAvatarManager(OtpAvatarManager): + pass diff --git a/toontown/src/uberdog/DistributedAvatarManagerAI.py b/toontown/src/uberdog/DistributedAvatarManagerAI.py new file mode 100644 index 0000000..6717028 --- /dev/null +++ b/toontown/src/uberdog/DistributedAvatarManagerAI.py @@ -0,0 +1,5 @@ + +from otp.uberdog.OtpAvatarManagerAI import OtpAvatarManagerAI + +class DistributedAvatarManagerAI(OtpAvatarManagerAI): + pass diff --git a/toontown/src/uberdog/DistributedAvatarManagerUD.py b/toontown/src/uberdog/DistributedAvatarManagerUD.py new file mode 100644 index 0000000..db67cd9 --- /dev/null +++ b/toontown/src/uberdog/DistributedAvatarManagerUD.py @@ -0,0 +1,162 @@ + +""" +The Toontown Avatar Manager UD handles all the avatar accross all +districts. +""" + +from cPickle import loads, dumps + +from otp.uberdog.UberDogUtil import ManagedAsyncRequest +from otp.distributed import OtpDoGlobals +from otp.uberdog.OtpAvatarManagerUD import OtpAvatarManagerUD +from otp.uberdog.RejectCode import RejectCode + +if __debug__: + from direct.directnotify.DirectNotifyGlobal import directNotify + notify = directNotify.newCategory('AvatarManagerUD') + +#----------------------------------------------------------------------------- + +class AsyncRequestAvatarList(ManagedAsyncRequest): + if __debug__: + notify = notify + + def __init__(self, distObj, accountId): + assert self.notify.debugCall() + self.rejectString="rejectAvatarList" + ManagedAsyncRequest.__init__(self, distObj, distObj.air, accountId) + self.gotAvatars=0 + self.accountId=accountId + self.askForObjectField("AccountUD", "ACCOUNT_AV_SET", self.accountId) + + def getAvatarData(self, avatars): + assert self.notify.debugCall() + avatarData=[] + for i, slot in zip(avatars, range(6)): + if i is None: + avatarData.append(None) + else: + ad={} + ad["name"]=i[0] # i.getName() + ad["dna"]=i[1] # i.dna + ad["slot"]=slot + ad["id"]=i[2] # i.getDoId() + avatarData.append(ad) + return avatarData + + def finish(self): + assert self.notify.debugCall() + assert self.air is not None + pirateAvatarsIds=self.neededObjects["pirateAvatars"] + + if self.gotAvatars: + avatars = [] + for doId in pirateAvatarsIds: + if doId: + name = self.neededObjects.get("setName-%s"%(doId,)) + dna = self.neededObjects.get("setDNAString-%s"%(doId,)) + avatars.append([name, dna, doId]) + else: + avatars.append(None) + self.sendAvatarList(avatars) + else: + for avatarId in pirateAvatarsIds: + if avatarId: + self.gotAvatars=1 + self.neededObjects["setName-%s"%(avatarId,)]=None + self.neededObjects["setDNAString-%s"%(avatarId,)]=None + if self.gotAvatars: + # You may want to combine this with the above loop, but that + # would be bad because we need all the neededObjects listed + # before we start asking for them (each ask checks for + # completion). + for avatarId in pirateAvatarsIds: + if avatarId and hasattr(self, "air"): + self.askForObjectField( + "DistributedPlayerPirateUD", + "setName", avatarId, "setName-%s"%(avatarId,)) + self.askForObjectField( + "DistributedPlayerPirateUD", + "setDNAString", avatarId, "setDNAString-%s"%(avatarId,)) + return + else: + avatars=[None for i in pirateAvatarsIds] + self.sendAvatarList(avatars) + ManagedAsyncRequest.finish(self) + + def sendAvatarList(self, avatars): + assert self.notify.debugCall() + avatarData=self.getAvatarData(avatars) + pickleData=dumps(avatarData) + self.distObj.sendUpdateToAvatarId( + self.accountId, "avatarListResponse", [pickleData]) + +#----------------------------------------------------------------------------- + +class AsyncRequestCreateAvatar(ManagedAsyncRequest): + if __debug__: + notify = notify + + def __init__(self, distObj, accountId, slot, avatarData): + assert self.notify.debugCall() + assert accountId + self.rejectString="rejectCreateAvatar" + ManagedAsyncRequest.__init__(self, distObj, distObj.air, accountId) + self.accountId=accountId + self.slot=slot + self.avatarData=avatarData + + self.neededObjects[accountId]=None + self.neededObjects["avatar"]=None + self.neededObjects["friendsList"]=None + + self.askForObject(accountId) + self.createObject("avatar", 'DistributedPlayerToon') + self.createObject("friendsList", 'OtpFriendsList') + + def setupAvatar(self, avatar, friendsList, avatarData): + assert self.notify.debugCall() + avatar.saveDNA(avatarData) + + def finish(self): + assert self.notify.debugCall() + account=self.neededObjects[self.accountId] + if not account.may('createAvatar'): + # This account does not have permission to create an avatar: + self.sendRejectCode(RejectCode.MAY_NOT_CREATE_AVATAR) + elif self.slot > account.getSlotLimit(): + # This account doesn't have that many slots. + self.sendRejectCode(RejectCode.SLOT_OUT_OF_RANGE) + elif account.getPirate(self.slot): + # They're trying to create an avatar in an already used slot. + self.sendRejectCode(RejectCode.SLOT_TAKEN) + else: + self.createAvatar() + ManagedAsyncRequest.finish(self) + + def createAvatar(self): + assert self.notify.debugCall() + account = self.neededObjects[self.accountId] + avatar = self.neededObjects["avatar"] + friendsList = self.neededObjects["friendsList"] + + avatarId = avatar.getDoId() + + assert avatarId + assert friendsList.getDoId() + + avatar.sendSetFriendsListId(friendsList.getDoId()) + friendsList.sendSetOwnerId(avatarId) + + self.setupAvatar(avatar, friendsList, self.avatarData) + + account.setPirate(self.slot, avatarId) + self.distObj.sendUpdateToAvatarId( + self.accountId, "createAvatarResponse", [avatarId]) + +class DistributedAvatarManagerUD(OtpAvatarManagerUD): + def __init__(self, air): + OtpAvatarManagerUD.__init__(self, air) + self.AsyncRequestAvatarList=AsyncRequestAvatarList + self.AsyncRequestCreateAvatar=AsyncRequestCreateAvatar + diff --git a/toontown/src/uberdog/DistributedCpuInfoMgr.py b/toontown/src/uberdog/DistributedCpuInfoMgr.py new file mode 100644 index 0000000..2043edf --- /dev/null +++ b/toontown/src/uberdog/DistributedCpuInfoMgr.py @@ -0,0 +1,4 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal + +class DistributedCpuInfoMgr(DistributedObjectGlobal): + pass diff --git a/toontown/src/uberdog/DistributedCpuInfoMgrAI.py b/toontown/src/uberdog/DistributedCpuInfoMgrAI.py new file mode 100644 index 0000000..6170995 --- /dev/null +++ b/toontown/src/uberdog/DistributedCpuInfoMgrAI.py @@ -0,0 +1,93 @@ +import socket +import datetime +import os +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.http.WebRequest import WebRequestDispatcher +from otp.distributed import OtpDoGlobals +from toontown.toonbase import ToontownGlobals +from toontown.uberdog import InGameNewsResponses + +ParentClass = DistributedObjectAI +#ParentClass = DistributedObjectGlobal +class DistributedCpuInfoMgrAI(ParentClass ): + """ + Uberdog object that keeps track of the last time in game news has been updated + """ + notify = directNotify.newCategory('DistributedInGameNewsMgrAI') + + + def __init__(self, cr): + """Construct ourselves, set up web dispatcher.""" + assert self.notify.debugCall() + ParentClass.__init__(self, cr) + self.latestIssueStr = "" + self.avId2Fingerprint = {} + self.accept("avatarEntered", self.handleAvatarEntered) + + def generate(self): + """We have zone info but not required fields, register for the special.""" + # IN_GAME_NEWS_MGR_UD_TO_ALL_AI will arrive on this channel + self.air.registerForChannel(OtpDoGlobals.OTP_DO_ID_TOONTOWN_CPU_INFO_MANAGER) + ParentClass.generate(self) + + def announceGenerate(self): + # tell uberdog we are starting up, so we can get info on the currently running public parties + # do whatever other sanity checks is necessary here + DistributedObjectAI.announceGenerate(self) + #self.air.sendUpdateToDoId("DistributedInGameNewsMgr", + # 'inGameNewsMgrAIStartingUp', + # OtpDoGlobals.OTP_DO_ID_TOONTOWN_CPU_INFO_MANAGER, + # [self.doId, self.air.districtId] + #) + def setLatestIssueStr(self, issueStr): + """We normally get this once, we could get this when a new issue is released while logged in.""" + # we receive this as a utc str + assert self.notify.debugStateCall(self) + self.latestIssueStr = issueStr + pass + + def b_setLatestIssueStr(self, latestIssue): + self.setLatestIssueStr(latestIssue) + self.d_setLatestIssueStr(latestIssue) + + def d_setLatestIssueStr(self, latestIssue): + self.sendUpdate("setLatestIssueStr",[self.getLatestIssueStr()]) + + + def getLatestIssueStr(self): + """We normally get this once, we could get this when a new issue is released while logged in.""" + assert self.notify.debugStateCall(self) + return self.latestIssueStr + pass + + + def newIssueUDtoAI(self, issueStr): + """Well the UD is telling us we have a new issue, spread it to the clients.""" + self.b_setLatestIssueStr(issueStr) + + def sendCpuInfoToUd(self, info, fingerprint): + """Prepare to send the info to the UD, we don't have the DISLid yet.""" + requesterId = self.air.getAvatarIdFromSender() + self.avId2Fingerprint[requesterId] = (info,fingerprint) + + def handleAvatarEntered(self, avatar): + """Send the cpu info to the UD once we get the avatar and the DISL id.""" + if avatar.doId in self.avId2Fingerprint: + info, fingerprint = self.avId2Fingerprint.get(avatar.doId) + dislId = 0 + if avatar: + try: + dislId = avatar.DISLid + except: + pass + if dislId: + self.air.sendUpdateToDoId("DistributedCpuInfoMgr", + 'setCpuInfoToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_CPU_INFO_MANAGER, + [avatar.doId, dislId, info[:255],fingerprint[:255]] + ) + else: + self.notify.warning("avId=%s has dislId=%s" % (avatar.doId, dislId)) + del self.avId2Fingerprint[avatar.doId] + diff --git a/toontown/src/uberdog/DistributedCpuInfoMgrUD.py b/toontown/src/uberdog/DistributedCpuInfoMgrUD.py new file mode 100644 index 0000000..41160e7 --- /dev/null +++ b/toontown/src/uberdog/DistributedCpuInfoMgrUD.py @@ -0,0 +1,294 @@ +import urllib +import socket +import datetime +import os +import pytz +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.http.WebRequest import WebRequestDispatcher +from otp.distributed import OtpDoGlobals +from otp.ai import BanManagerAI +from toontown.toonbase import ToontownGlobals +from toontown.uberdog import InGameNewsResponses +from toontown.ai.ToontownAIMsgTypes import IN_GAME_NEWS_MANAGER_UD_TO_ALL_AI + + +class DistributedCpuInfoMgrUD(DistributedObjectGlobalUD): + """ + Uberdog object is more properly called the Security / Ban Manager + + Called Cpu Info for obfuscation as it is in toon.dc + """ + notify = directNotify.newCategory('DistributedCpuInfoMgrUD') + serverDataFolder = simbase.config.GetString('server-data-folder', "") + + # WARNING this is a global OTP object + # InGameNewsMgrAI is NOT! + # Hence the use of sendUpdateToDoId when sending back to AI + + securityBanMgrFailureXML = """ + + false + %s + + \r\n""" + + securityBanMgrAddFingerprintXML = """ + + true + %s + + \r\n""" + + securityBanMgrRemoveFingerprintXML = """ + + true + %s + + \r\n""" + + + + def __init__(self, air): + """Construct ourselves, set up web dispatcher.""" + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + self.HTTPListenPort = uber.cpuInfoMgrHTTPListenPort + + self.webDispatcher = WebRequestDispatcher() + self.webDispatcher.landingPage.setTitle("SecurityBanMgr") + self.webDispatcher.landingPage.setDescription("SecurityBanMgr for now handles banning my mac address.") + self.webDispatcher.registerGETHandler('securityBanMgr', self.securityBanMgr) + self.webDispatcher.registerGETHandler('securityBanMgrAddFingerprint', self.addFingerprint) + self.webDispatcher.registerGETHandler('securityBanMgrRemoveFingerprint', self.removeFingerprint) + self.webDispatcher.registerGETHandler('securityBanMgrListFingerprints', self.listFingerprints) + self.webDispatcher.listenOnPort(self.HTTPListenPort) + self.webDispatcher.landingPage.addTab("SecurityBanMgr","/securityBanMgr") + + self.air.setConnectionName("SecurityBanMgr") + self.air.setConnectionURL("http://%s:%s/" % (socket.gethostbyname(socket.gethostname()),self.HTTPListenPort)) + + self.filename = self.getFilename() + + self.bannedFingerprints = set() + self.bannedFingerprints = self.loadRecords() + self.banMgr = BanManagerAI.BanManagerAI() + + + def setCpuInfoToUd(self, avId, dislId, cpuInfo, cacheStatus): + """AI telling us a client just logged in.""" + if cacheStatus in self.bannedFingerprints: + self.notify.info("got a banned fingerprint %s for avId=%s dislId=%s" % (cacheStatus, avId, dislId)) + self.banMgr.ban(avId, dislId, "banned macId, fingerprint is %s" % cacheStatus) + pass + + def announceGenerate(self): + """Start accepting http requests.""" + assert self.notify.debugCall() + DistributedObjectGlobalUD.announceGenerate(self) + self.webDispatcher.startCheckingIncomingHTTP() + + def securityBanMgr(self, replyTo, **kw): + """Handle all calls to web requests awardMgr.""" + assert self.notify.debugCall() + + # If no arguments are passed, assume that the main menu should + # be displayed + + if not kw: + function = None + id = None + else: + function = "doAward" + + header = body = help = footer = "" + if not function: + header,body,footer,help= self.getMainMenu() + else: + self.notify.debug("%s" % str(kw)) + header,body,footer,help= self.getMainMenu() + body = """

got these arguments """ + body += str(kw) + + #self.notify.info("%s" % header + body + help + footer) + replyTo.respond(header + body + help + footer) + + + def getMainMenu(self): + """Create the main menu with forms for input.""" + header = """Main Menu: In Game News Mgr + """ + + body = """

""" + + body += """ +
+

+ + +
+ """ + + body += """ +
+
+ + +
+ """ + + body += """ +
+
+ +
+ """ + + footer = """

""" + help = """

Note
- Use add to add ONE fingerpint that's autobanned. Use remove to take ONE fingerprint out. And use list to see them all.

""" + return (header,body,footer,help) + + + def updateRecordFile(self): + """Update current track record in this shard's record file""" + # notify the leader boards that there has been an update + try: + backup = self.filename + '.bu' + if os.path.exists(self.filename): + os.rename(self.filename, backup) + file = open(self.filename, 'w') + file.seek(0) + for fingerprint in self.bannedFingerprints: + file.write(fingerprint + '\n') + file.close() + if os.path.exists(backup): + os.remove(backup) + except EnvironmentError: + self.notify.warning(str(sys.exc_info()[1])) + + def getFilename(self): + """Compose the track record filename""" + result = "%s.bannedFingerprints" % (self.serverDataFolder) + return result + + def getDefaultLatestIssueTime(self): + """Hmmm what the heck do we give. Lets use the current time.""" + result = self.air.toontownTimeManager.getCurServerDateTime() + return result + + def loadRecords(self): + """Load track record data from default location""" + try: + # Try to open the backup file: + file = open(self.filename + '.bu', 'r') + # Remove the (assumed) broken file: + if os.path.exists(self.filename): + os.remove(self.filename) + except IOError: + # OK, there's no backup file, good. + try: + # Open the real file: + file = open(self.filename, 'r') + except IOError: + # OK, there's no file. Grab the default empty set. + return set() + file.seek(0) + result = self.loadFrom(file) + file.close() + + return result + + def loadFrom(self, file): + """Load banned fingerprint record data from specified file""" + result = set() + try: + for oneFingerprint in file: + oneFingerprint = oneFingerprint.strip() + if oneFingerprint: + result.add(oneFingerprint) + except EOFError: + pass + return result + + def setLatestIssueStr(self, issueStr): + self.notify.debugStateCall(self) + + + def setLatestIssue(self, latestIssue): + self.latestIssue = latestIssue + + def b_setLatestIssue(self, latestIssue): + self.setLatestIssue(latestIssue) + self.d_setLatestIssue(latestIssue) + + def d_setLatestIssue(self, latestIssue): + pass + #self.sendUpdateToAllAis('newIssueUDtoAI', [ self.getLatestIssueUtcStr()]) + + def sendUpdateToAllAis(self, message, args): + dg = self.dclass.aiFormatUpdateMsgType( + message, self.doId, self.doId, self.air.ourChannel, IN_GAME_NEWS_MANAGER_UD_TO_ALL_AI, args) + self.air.send(dg) + + def inGameNewsMgrAIStartingUp(self, doId, shardId): + """Tell the new AI that just started up what the latest issue is.""" + self.air.sendUpdateToDoId( + "DistributedInGameNewsMgr", + 'newIssueUDtoAI', + doId , + [self.getLatestIssueStr()] + ) + + + def addFingerprint(self, replyTo, **kw): + """Add a new fingerprint to auto ban.""" + try: + fingerprint = urllib.unquote(kw['fingerprintToAdd']) + self.bannedFingerprints.add(fingerprint) + self.updateRecordFile() + header,body,footer,help= self.getMainMenu() + replyTo.respondXML(self.securityBanMgrAddFingerprintXML % + ("%s" % fingerprint)) + + except Exception, e: + replyTo.respondXML(self.securityBanMgrFailureXML % + ("Catastrophic failure add fingerprint %s" % str(e))) + self.notify.warning("Got exception %s" % str(e)) + + + def removeFingerprint(self, replyTo, **kw): + """Remove a fingerprint to auto ban.""" + try: + fingerprint = urllib.unquote(kw['fingerprintToRemove']) + if fingerprint in self.bannedFingerprints: + self.bannedFingerprints.remove(fingerprint) + self.updateRecordFile() + header,body,footer,help= self.getMainMenu() + replyTo.respondXML(self.securityBanMgrRemoveFingerprintXML % + ("%s" % fingerprint)) + else: + replyTo.respondXML(self.securityBanMgrFailureXML % ("%s not a banned fingerprint" % fingerprint)) + except Exception, e: + replyTo.respondXML(self.securityBanMgrFailureXML % + ("Catastrophic failure add fingerprint %s" % str(e))) + self.notify.warning("Got exception %s" % str(e)) + + def listFingerprints(self, replyTo, **kw): + """List all banned fingerprints.""" + try: + header,body,footer,help= self.getMainMenu() + body = """

""" + body += """

Banned Fingerprints:

+ + """ + for fingerprint in self.bannedFingerprints: + body += ""+"\n" + body += """ +
" + str(fingerprint) + "
+ """ + replyTo.respond(header +body+ help+footer) + except Exception, e: + replyTo.respondXML(self.securityBanMgrFailureXML % ("Catastrophic failure listing fingerprints %s" % str(e))) + self.notify.warning("Got exception %s" % str(e)) + + + diff --git a/toontown/src/uberdog/DistributedDataStoreManager.py b/toontown/src/uberdog/DistributedDataStoreManager.py new file mode 100644 index 0000000..452c5e3 --- /dev/null +++ b/toontown/src/uberdog/DistributedDataStoreManager.py @@ -0,0 +1,10 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from toontown.uberdog import DataStoreGlobals + +if __debug__: + from direct.directnotify.DirectNotifyGlobal import directNotify + notify = directNotify.newCategory('DistributedDataStoreManager') + +class DistributedDataStoreManager(DistributedObjectGlobal): + def __init__(self): + assert False, 'DistributedDataStoreManager should not be used.' diff --git a/toontown/src/uberdog/DistributedDataStoreManagerAI.py b/toontown/src/uberdog/DistributedDataStoreManagerAI.py new file mode 100644 index 0000000..05a2b4b --- /dev/null +++ b/toontown/src/uberdog/DistributedDataStoreManagerAI.py @@ -0,0 +1,71 @@ +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed.AsyncRequest import AsyncRequest +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem + +class DistributedDataStoreManagerAI(DistributedObjectAI): + """ + This is the main gateway for any DataStoreAIClients to + communicate to the Uberdog. There should only be one of these + per AI. + + We should only use the queryStore functions from now on. The + start/stopStore functions are provided for backwards compatibility + for some Toontown uses. + """ + + def __init__(self, air): + DistributedObjectAI.__init__(self, air) + + def startStore(self,storeId): + """ + Causes the Uberdog to bring up the desired store. + If it's already up, this has no effect. + """ + self.ud_startStore(storeId) + + def stopStore(self,storeId): + """ + Causes the Uberdog to bring down the desired store. + If it's already down, this has no effect. + + CAUTION: this will cause the permanent destruction + of any data currently in the store. + """ + self.ud_stopStore(storeId) + + def queryStore(self,storeId,query): + """ + Send a formatted query to the store. The format + should be defined in a subclass of the + DataStore class. + """ + self.ud_queryStore(storeId,query) + + # To UD + def ud_startStore(self,storeId): + self.sendUpdate('startStore',[storeId]) + + def ud_stopStore(self,storeId): + self.sendUpdate('stopStore',[storeId]) + + def ud_queryStore(self,storeId,query): + self.sendUpdate('queryStore',[storeId,query]) + + # From UD + def receiveResults(self,storeId,data): + """ + Sends a message to be received by any interested + DataStoreAIClients on the AI. + + Since all requests to all data stores must go through + this class, this is where the data gets dispatched from + this Uberdog data trunk to the AI client branches + """ + messenger.send('TDS-results-%d'%storeId,sentArgs=[data]) + + def deleteBackupStores(self): + """ + Delete the backedup stores from previous years + """ + self.sendUpdate('deleteBackupStores') diff --git a/toontown/src/uberdog/DistributedDataStoreManagerUD.py b/toontown/src/uberdog/DistributedDataStoreManagerUD.py new file mode 100644 index 0000000..3b92060 --- /dev/null +++ b/toontown/src/uberdog/DistributedDataStoreManagerUD.py @@ -0,0 +1,148 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * + +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from toontown.uberdog import DataStoreGlobals + +import time +import os + + +class DistributedDataStoreManagerUD(DistributedObjectGlobalUD): + """ + This is UD gateway to all DataStores. It receives queries + from various AIs, locates the desired store, runs the query, and sends + back a response(if specified). + + It looks for two config variables: + + 'server-data-folder' is a string that specifies the directory in + which each of the Stores will write their data. It defaults to '.'. + + 'enable-destroy-data-stores' controls whether the DataStore can be + destroyed by calling destroy(). If not, destroy() has no effect. + It defaults to 'False'. + """ + + notify = directNotify.newCategory('DistributedDataStoreManagerUD') + + serverDataFolder = simbase.config.GetString('server-data-folder', '.') + enableDestroyStore = simbase.config.GetBool('enable-destroy-data-stores', True) + + def __init__(self,air): + DistributedObjectGlobalUD.__init__(self,air) + self.stores = {} + + def __getFilePath(self,storeId): + #import pdb; pdb.set_trace() + return '%s/TUDS-%s'%(self.serverDataFolder, + `storeId`) + + def __str__(self): + outStr = self.__class__.__name__ + ' - ' + `len(self.stores)` + ' Stores\n' + outStr += '-'*40 + '\n' + for id in self.stores.keys(): + outStr += `id` + '\t: ' + self.stores[id].className + '\n' + return outStr + + # From AI + def startStore(self,storeId): + """ + Starts a store corresponding to the id as specified + in DataStoreGlobals. + If the store is already present, it has no effect. + """ + storeClass = DataStoreGlobals.getStoreClass(storeId) + + if not self.stores.get(storeId,None): + if storeClass is not None: + store = storeClass(self.__getFilePath(storeId)) + if store: + self.stores[storeId] = store + else: + self.notify.debug('DataStore type \'%d\' not found DataStoreGlobals. Store not started.' % \ + (storeId,)) + else: + self.notify.debug('%s already present on uberdog.' % \ + (storeClass.__name__,)) + + def stopStore(self,storeId): + """ + Closes the store corresponding to the id as specified + in DataStoreGlobals if the config variable + 'enable-destroy-data-stores' is True. Otherwise + it just closes it, leaving the data on disk + intact. + + If the store is not present, it has no effect. + """ + # import pdb; pdb.set_trace() + store = self.stores.pop(storeId,None) + if store: + if self.enableDestroyStore: + self.notify.debug('Destroying %s.' % `DataStoreGlobals.getStoreClass(storeId)`) + store.destroy() + else: + self.notify.debug('Closing %s.' % `DataStoreGlobals.getStoreClass(storeId)`) + store.close() + else: + self.notify.debug('%s not present on uberdog.' % `DataStoreGlobals.getStoreClass(storeId)`) + + def queryStore(self,storeId,query,retry = False): + """ + Pass a query on to the specified store and + respond with any results. If the store is + not present, return an error message to let + the client know. + + 'retry' is to let us know that this call is + an attempt to re-send a query that failed + because the desired store was not yet active. + We will try to start the store here and send + the query again. We only do this once per + query, otherwise we could get into an + infinite loop if the store cannot be started. + """ + store = self.stores.get(storeId,None) + + if store: + result = store.query(query) + self.respondToQuery(storeId,result) + else: + if retry: + result = 'Store not found' + self.respondToQuery(storeId,result) + + storeClass = DataStoreGlobals.getStoreClass(storeId) + if storeClass: + self.notify.debug('%s not present on uberdog. Query dropped.' % \ + (storeClass.__name__,)) + else: + self.notify.debug('Store of typeId %s not defined in DataStoreGlobals. Query dropped.' % \ + (storeId,)) + else: + self.startStore(storeId) + self.queryStore(storeId,query,True) + + + # To AI + def respondToQuery(self,storeId,data): + """ + Sends a message back to the querying AI with + the results of its query. + """ + replyToChannel = self.air.getSenderReturnChannel() + self.sendUpdateToChannel( + replyToChannel, 'receiveResults', [storeId,data]) + + def deleteBackupStores(self): + """ + Delete any backed up stores from previous year's + """ + year = time.localtime()[0] + for file in os.listdir(self.serverDataFolder): + if file.find('UDStoreBak')>-1 and file.find(str(year))==-1: + os.remove(file) + uber.air.writeServerEvent('Uberdog data store Info', 0 \ + , 'Removing backup file: %s ' %file) \ No newline at end of file diff --git a/toontown/src/uberdog/DistributedDeliveryManager.py b/toontown/src/uberdog/DistributedDeliveryManager.py new file mode 100644 index 0000000..e5d6b23 --- /dev/null +++ b/toontown/src/uberdog/DistributedDeliveryManager.py @@ -0,0 +1,40 @@ +from pandac.PandaModules import * +from direct.distributed.DistributedObject import DistributedObject +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem + +class DistributedDeliveryManager(DistributedObject): + neverDisable = 1 + def sendHello(self, message): + self.sendUpdate("hello", [message]) + + def rejectHello(self, message): + print "rejected", message + + def helloResponse(self, message): + print "accepted", message + + def sendAck(self): + self.sendUpdate("requestAck", []) + + def returnAck(self): + messenger.send("DeliveryManagerAck") + + def test(self): + print "Distributed Delviery Manager Stub Test" + """ + def sendRequestPurchaseGift(self, item, receiverId, callback): + print "sent request for gift" + giftBlob = item.getBlob(store = CatalogItem.Customization) + context = self.getCallbackContext(callback, [item]) + #self.sendUpdate("receiveRequestPurchaseGift", [giftBlob, receiverId, context]) + self.sendUpdate("receiveRequestPayForGift", [giftBlob, receiverId, context]) + """ + """ + def receiveAcceptPurchaseGift(self, context, retcode): + print "received AcceptPurchaseGift" + self.doCallbackContext(context, [retcode]) + def receiveRejectPurchaseGift(self, context, retcode): + print "received RejectPurchaseGift" + self.doCallbackContext(context, [retcode]) + """ diff --git a/toontown/src/uberdog/DistributedDeliveryManagerAI.py b/toontown/src/uberdog/DistributedDeliveryManagerAI.py new file mode 100644 index 0000000..1f28f29 --- /dev/null +++ b/toontown/src/uberdog/DistributedDeliveryManagerAI.py @@ -0,0 +1,82 @@ + +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed.AsyncRequest import AsyncRequest +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem + +class DistributedDeliveryManagerAI(DistributedObjectAI): + + notify = directNotify.newCategory("DistributedDeliveryManagerAI") + + def __init__(self, air): + DistributedObjectAI.__init__(self, air) + self.nameObject = None; + self.senderIdContexttoPhone = {} + + def hello(self, message): + print message + replyToChannel = self.air.getSenderReturnChannel() + print "This is the AI getting the hello message" , message + + def sendHello(self, message): + self.sendUpdate("hello", [message]) + + def rejectHello(self, message): + print "rejected", message + + def helloResponse(self, message): + print "accepted", message + + #NON Test functions begin here + + def sendDeliverGifts(self, avId, time): + self.notify.debugStateCall(self) + self.sendUpdate("deliverGifts", [avId, time]) + + def receiveAcceptDeliverGifts(self, avId, message): + self.notify.debugStateCall(self) + receiver = self.air.doId2do.get(avId) + #if receiver: + #if the gift receiver happens to be logged in tell them about the gift update + #print "Deliver Gifts Accepted:", message + + def receiveRejectDeliverGifts(self, avId, message): + self.notify.debugStateCall(self) + #print "Deliver Gifts Rejected:", message + pass + + def sendRequestPurchaseGift(self, item, rAvId, sAvId, context, phone): + self.notify.debugStateCall(self) + #print "sent request for gift" + uniquekey = (sAvId, context) + self.senderIdContexttoPhone[uniquekey] = phone + item.giftTag = sAvId + + giftBlob = item.getBlob(store = CatalogItem.Customization) + self.sendUpdate("receiveRequestPurchaseGift", [giftBlob, rAvId, sAvId, context]) + + + def receiveRejectPurchaseGift(self, sAvId, context, retcode, cost): + self.notify.debugStateCall(self) + #print "rejected Purcahse Gift" + uniquekey = (sAvId, context) + phone = self.senderIdContexttoPhone[uniquekey] + phone.sendUpdateToAvatarId(sAvId, "requestGiftPurchaseResponse", [context, retcode]) + simbase.air.catalogManager.refundMoney(sAvId, cost) + del self.senderIdContexttoPhone[uniquekey] + + def receiveAcceptPurchaseGift(self, sAvId, context, retcode): + self.notify.debugStateCall(self) + #print "accepeted Purcahse Gift" + uniquekey = (sAvId, context) + phone = self.senderIdContexttoPhone.get(uniquekey) + if phone: + phone.sendUpdateToAvatarId(sAvId, "requestGiftPurchaseResponse", [context, retcode]) + if uniquekey in self.senderIdContexttoPhone: + del self.senderIdContexttoPhone[uniquekey] + + + + + + diff --git a/toontown/src/uberdog/DistributedDeliveryManagerUD.py b/toontown/src/uberdog/DistributedDeliveryManagerUD.py new file mode 100644 index 0000000..c3005ac --- /dev/null +++ b/toontown/src/uberdog/DistributedDeliveryManagerUD.py @@ -0,0 +1,511 @@ +""" +The Toontown Delivery Manager UD handles all the delivery accross all +districts. +""" + +from cPickle import loads, dumps + +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals + +from otp.uberdog.UberDogUtil import ManagedAsyncRequest +from otp.distributed import OtpDoGlobals +from toontown.toonbase import ToontownGlobals +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from otp.uberdog.RejectCode import RejectCode +from direct.distributed.AsyncRequest import AsyncRequest +from toontown.catalog import CatalogItemList +from toontown.catalog import CatalogItem +from toontown.catalog import CatalogPoleItem +from toontown.catalog import CatalogBeanItem +from LRUlist import LRUlist + + +if __debug__: + from direct.directnotify.DirectNotifyGlobal import directNotify + notify = directNotify.newCategory('DeliveryManagerUD') + +#----------------------------------------------------------------------------- + + +class AddGiftRequestFR(AsyncRequest): + def __init__(self, distObj, replyToChannelId,avatarId, newGift, senderId, context, retcode, timeout = 4.0): + #print "AddGiftRequestFR INIT" + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + self.avatarId=avatarId + self.newGift = newGift + self.senderId = senderId + self.context = context + self.retcode = retcode + self.askForObjectField( + "DistributedToonUD", "setGiftSchedule", avatarId) + def finish(self): + #print "AddGiftRequestFR FINISH" + """ + gift = self.distObj.avatarIdToGifts.get(self.avatarId) + if gift == None: + gift=self.neededObjects.get("setGiftSchedule")[0] + gift.append(self.newGift) + self.distObj.writeGift(self.avatarId, gift, self.replyToChannelId) + AsyncRequest.finish(self) + """ + giftBlob = self.distObj.avatarIdToGifts.getData(self.avatarId) + if giftBlob == None: + giftBlob=self.neededObjects.get("setGiftSchedule")[0] + giftItem = CatalogItemList.CatalogItemList(self.newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftList = CatalogItemList.CatalogItemList(giftBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftList.append(giftItem[0]) + giftBlob = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + self.distObj.writeGiftFR(self.avatarId, giftBlob, self.replyToChannelId, self.senderId, self.context, self.retcode) + AsyncRequest.finish(self) + + +class AddGift(AsyncRequest): + def __init__(self, distObj, replyToChannelId,avatarId, newGift, timeout = 4.0): + #print "AddGiftRequestFR INIT" + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + self.avatarId=avatarId + self.newGift = newGift + self.askForObjectField( + "DistributedToonUD", "setGiftSchedule", avatarId) + def finish(self): + #print "AddGiftRequestFR FINISH" + """ + gift = self.distObj.avatarIdToGifts.get(self.avatarId) + if gift == None: + gift=self.neededObjects.get("setGiftSchedule")[0] + gift.append(self.newGift) + self.distObj.writeGift(self.avatarId, gift, self.replyToChannelId) + AsyncRequest.finish(self) + """ + giftBlob = self.distObj.avatarIdToGifts.getData(self.avatarId) + if giftBlob == None: + giftBlob=self.neededObjects.get("setGiftSchedule")[0] + giftItem = CatalogItemList.CatalogItemList(self.newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftList = CatalogItemList.CatalogItemList(giftBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if giftItem[0].giftCode != ToontownGlobals.GIFT_RAT: + #print("Gift Item not RAT") + giftList.append(giftItem[0]) + giftBlob = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + else: + #print("Gift Item is RAT") + giftBlob = AccumRATBeans(giftItem[0], giftBlob) + self.distObj.writeGift(self.avatarId, giftBlob, self.replyToChannelId) + giftList.append(giftItem[0]) + giftBlob = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + self.distObj.writeGift(self.avatarId, giftBlob, self.replyToChannelId) + + AsyncRequest.finish(self) + +def AccumRATBeans(newGift, GiftListBlob): + giftList = CatalogItemList.CatalogItemList(GiftListBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + + if newGift.giftCode == ToontownGlobals.GIFT_RAT: + numBeans = newGift.beanAmount + found = 0 + for index in range(len(giftList)): + if(giftList[index].giftCode == ToontownGlobals.GIFT_RAT and found == 0): + found = 1 + giftList[index].beanAmount = numBeans + giftList[index].beanAmount + #print giftList[index].beanAmount + if found: + #print("RAT already on list") + giftList.markDirty() + else: + #print("RAT is not on the list") + giftList.append(newGift) + + newBlobList = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + + return newBlobList + + + +class DeliverGiftRequest(AsyncRequest): + def __init__(self, distObj, replyToChannelId, avatarId, time, timeout = 4.0): + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + self.avatarId=avatarId + self.time = time + self.askForObjectField( + "DistributedToonUD", "setGiftSchedule", avatarId) + + def finish(self): + + giftListBlob=self.neededObjects.get("setGiftSchedule")[0] + giftList = CatalogItemList.CatalogItemList(giftListBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + delivered, remaining = giftList.extractDeliveryItems(self.time) + giftBlob = remaining.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + self.distObj.writeGiftField(self.avatarId, giftBlob, self.replyToChannelId) + AsyncRequest.finish(self) + +class GetAvatarRequest(AsyncRequest): + def __init__(self, distObj, replyToChannelId, avatarId, timeout = 4.0): + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + self.avatarId=avatarId + self.askForObject(avatarId) + + def finish(self): + avatar = self.neededObjects[self.avatarId] + #put neat stuff here + AsyncRequest.finish(self) + +class PurchaseGiftRequest(AsyncRequest): + def __init__(self, distObj, replyToChannelId, senderId, receiverId, itemBlob, context, timeout = 4.0): + #print "PurchaseGiftRequest INIT" + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + self.senderId=senderId + self.receiverId=receiverId + self.itemBlob=itemBlob + self.context = context + self.retcode = None + self.item = None + self.catalogType = None + self.neededObjects[senderId] = None + self.neededObjects[receiverId] = None + self.askForObject(senderId) + self.askForObject(receiverId) + self.cost = 0 + #print "PurchaseGiftRequest INIT10" + + def checkCatalog(self, retcode): + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + if self.item in sAv.monthlyCatalog: + self.catalogType = CatalogItem.CatalogTypeMonthly + elif self.item in sAv.weeklyCatalog: + self.catalogType = CatalogItem.CatalogTypeWeekly + elif self.item in sAv.backCatalog: + self.catalogType = CatalogItem.CatalogTypeBackorder + else: + self.air.writeServerEvent('suspicious', sAv.doId, 'purchaseItem %s not in catalog' % (self.item)) + self.notify.warning("Avatar %s attempted to purchase %s, not on catalog." % (sAv.doId, self.item)) + self.notify.warning("Avatar %s weekly: %s" % (sAv.doId, sAv.weeklyCatalog)) + return ToontownGlobals.P_NotInCatalog + return retcode + + def checkMoney(self, retcode): + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + price = self.item.getPrice(self.catalogType) + self.cost = price + if price > sAv.getTotalMoney(): + self.air.writeServerEvent('suspicious', sAv.doId, 'purchaseItem %s not enough money' % (self.item)) + self.notify.warning("Avatar %s attempted to purchase %s, not enough money." % (sAv.doId, self.item)) + return ToontownGlobals.P_NotEnoughMoney + return retcode + + def checkGift(self, retcode): + if (self.item.isGift() <= 0): + return ToontownGlobals.P_NotAGift + return retcode + + def checkGender(self, retcode): + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + if ((self.item.forBoysOnly() and rAv.dna.getGender() == 'f') or (self.item.forGirlsOnly() and rAv.dna.getGender() == 'm')): + return ToontownGlobals.P_WillNotFit + return retcode + + def checkPurchaseLimit(self, retcode): + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + if self.item.reachedPurchaseLimit(rAv): + return ToontownGlobals.P_ReachedPurchaseLimit + return retcode + + def checkMailbox(self, retcode): + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + if len(rAv.mailboxContents) + len(rAv.onGiftOrder) >= ToontownGlobals.MaxMailboxContents: + if len(rAv.mailboxContents) == 0: + retcode = ToontownGlobals.P_OnOrderListFull + else: + retcode = ToontownGlobals.P_MailboxFull + return retcode + + + + def finish(self): + #print "PurchaseGiftRequest FINISH" + sAv = self.neededObjects[self.senderId] + rAv = self.neededObjects[self.receiverId] + self.item = CatalogItem.getItem(self.itemBlob, store = CatalogItem.Customization) + retcode = None + #put neat stuff here + #----------------------------------------------------------------------------- + retcode = self.checkGift(retcode) + retcode = self.checkCatalog(retcode) + #retcode = self.checkMoney(retcode) + retcode = self.checkGender(retcode) + retcode = self.checkPurchaseLimit(retcode) + retcode = self.checkMailbox(retcode) + if (retcode != None): + self.distObj.sendUpdateToChannel(self.replyToChannelId, "receiveRejectPurchaseGift", + [self.senderId, self.context, retcode, self.cost]) + else: + now = (int)(time.time() / 60 + 0.5) + deliveryTime = self.item.getDeliveryTime() / self.distObj.timeScale + if deliveryTime < 2: + deliveryTime = 2 + self.item.deliveryDate = int(now + deliveryTime) + #self.item.giftTag = self.senderId + itemList = CatalogItemList.CatalogItemList([self.item]) + itemBlob = itemList.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + retcode = ToontownGlobals.P_ItemOnOrder + self.distObj.addGiftFR(self.receiverId, itemBlob, self.senderId, self.context, retcode, self.replyToChannelId) + #----------------------------------------------------------------------------- + + AsyncRequest.finish(self) + + +class GiveItem(AsyncRequest): + def __init__(self, distObj, replyToChannelId, receiverId, itemBlob, timeout = 4.0): + #print "AddItem INIT" + AsyncRequest.__init__(self, distObj.air, replyToChannelId, timeout) + self.distObj=distObj + #self.senderId=senderId + self.receiverId=receiverId + self.itemBlob=itemBlob + self.item = None + self.catalogType = None + self.neededObjects[receiverId] = None + #self.askForObject(senderId) + self.askForObject(receiverId) + self.cost = 0 + #print "AddItem INIT10" + + + + def finish(self): + #print "AddItem FINISH" + rAv = self.neededObjects[self.receiverId] + self.item = CatalogItem.getItem(self.itemBlob, store = CatalogItem.Customization) + + now = (int)(time.time() / 60 + 0.5) + deliveryTime = self.item.getDeliveryTime() / self.distObj.timeScale + #if deliveryTime < 2: + # deliveryTime = 2 + deliveryTime = 2 + self.item.deliveryDate = int(now + deliveryTime) + itemList = CatalogItemList.CatalogItemList([self.item]) + itemBlob = itemList.getBlob(store = CatalogItem.Customization | CatalogItem.DeliveryDate) + retcode = ToontownGlobals.P_ItemOnOrder + self.distObj.addGift(self.receiverId, itemBlob, self.replyToChannelId) + #----------------------------------------------------------------------------- + + AsyncRequest.finish(self) + +class DistributedDeliveryManagerUD(DistributedObjectGlobalUD): + timeScale = simbase.config.GetFloat('catalog-time-scale', 1.0) + def __init__(self, air): + DistributedObjectGlobalUD.__init__(self, air) + self.avatarIdToName = LRUlist(8192)#{} #cache for names from the database + self.avatarIdToGifts = LRUlist(8192)#{} #cache for gifts from the database + self.giftPendingCounter = 0; + + def giveGiftItemToAvatar(self, itemBlob, receiverId): + print("Adding Unchecked Gift Item") + replyToChannel = self.air.getSenderReturnChannel() + myGiveItem = GiveItem(self, replyToChannel, receiverId, itemBlob) + + def giveTestItemToAvatar(self, receiverId): + print("Adding Test Item") + replyToChannel = self.air.getSenderReturnChannel() + testItem = CatalogBeanItem.CatalogBeanItem(500) + testItem.tes = 0 + testItem.giftCode = 1 + itemBlob = testItem.getBlob(store = CatalogItem.Customization) + myGiveItem = GiveItem(self, replyToChannel, receiverId, itemBlob) + + def giveBeanBonus(self, receiverId, amount = 1): + replyToChannel = self.air.getSenderReturnChannel() + testItem = CatalogBeanItem.CatalogBeanItem(amount) + testItem.giftTag = 0 + testItem.giftCode = 1 + itemBlob = testItem.getBlob(store = CatalogItem.Customization) + myGiveItem = GiveItem(self, replyToChannel, receiverId, itemBlob) + + + def giveRecruitAToonPayment(self, receiverId, amount = 1): + # print("Adding Bean Item") + replyToChannel = self.air.getSenderReturnChannel() + testItem = CatalogBeanItem.CatalogBeanItem(amount) + testItem.giftTag = 0 + testItem.giftCode = ToontownGlobals.GIFT_RAT + itemBlob = testItem.getBlob(store = CatalogItem.Customization) + testBlob = CatalogItemList.CatalogItemList(itemBlob, store = CatalogItem.Customization) + #import pdb; pdb.set_trace() + myGiveItem = GiveItem(self, replyToChannel, receiverId, itemBlob) + + + def receiveRequestPurchaseGift(self, giftBlob, receiverId, senderId, context): + #print "receiveRequestPurchaseGift" + # this is where the message gets sent back to, in this case it is the calling Client + replyToChannelAI = self.air.getSenderReturnChannel() + # senderId = self.air.getAvatarIdFromSender() + myPGR=PurchaseGiftRequest(self, replyToChannelAI, senderId, receiverId, giftBlob, context) + + def addGiftFR(self, doId, newGift, senderId, context, retcode, replyToChannelId): + #print "addGiftFR" + """ + Appends an existing gift list with the parameter newGift + doId is the DO that you want to append newGift onto + newGift the gift blob you want to add + """ + # this is where the message gets sent back to, in this case it is the calling AI + # replyToChannel = self.air.getSenderReturnChannel() + # check to see if the gift is in our cache dictionary + giftBlob = self.avatarIdToGifts.getData(doId) + giftItem = CatalogItemList.CatalogItemList(newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + self.air.writeServerEvent("Adding Gift", doId, "sender %s receiver %s gift %s" % (senderId, doId, giftItem[0].getName())) + if giftBlob == None: + # if not in our cache + myAddGiftRequestFR=AddGiftRequestFR(self, replyToChannelId, doId, newGift, senderId, context, retcode) + else: + # else it's in our cache + giftList = CatalogItemList.CatalogItemList(giftBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftItem = CatalogItemList.CatalogItemList(newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftList.append(giftItem[0]) + + giftBlob = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + self.writeGiftFR(doId, giftBlob, replyToChannelId, senderId, context, retcode) + + + def writeGiftFR(self, avatarId, newGift, replyToChannelId, senderId, context, retcode): + #print "writeGiftFR" + #print retcode + """ + Writes the newly appended gift to the database + doId is the DO that you want to append newGift onto + newGift final appended blob to write to the database + replyToChannel is where the message gets sent back to, in this case it is the calling AI + """ + if 1 == 1: #case where it works + #print "sending acceptFR" + self.avatarIdToGifts.putData(avatarId, newGift)#update the cache + self.air.sendUpdateToDoId( + "DistributedToon", + "setGiftSchedule", avatarId, [newGift]) + #update the database + self.sendUpdateToChannel(replyToChannelId, "receiveAcceptPurchaseGift", + [senderId, context, retcode]) + #return an Accept message to the AI caller + else: + #print "sending rejectFR" + self.sendUpdateToChannel(replyToChannelId, "receiveRejectPurchaseGift", + [senderid, context, retcode]) + #return a Reject message to the AI caller + + + def addGift(self, doId, newGift, replyToChannelId): + # print "addGift" + """ + Appends an existing gift list with the parameter newGift + doId is the DO that you want to append newGift onto + newGift the gift blob you want to add + """ + # this is where the message gets sent back to, in this case it is the calling AI + # replyToChannel = self.air.getSenderReturnChannel() + # check to see if the gift is in our cache dictionary + giftBlob = self.avatarIdToGifts.getData(doId) + giftItem = CatalogItemList.CatalogItemList(newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + self.air.writeServerEvent("Adding Server Gift", doId, "receiver %s gift %s" % (doId, giftItem[0].getName())) + if giftBlob == None: + # if not in our cache + myAddGift=AddGift(self, replyToChannelId, doId, newGift) + else: + # else it's in our cache + giftList = CatalogItemList.CatalogItemList(giftBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + giftItem = CatalogItemList.CatalogItemList(newGift, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + if giftItem[0].giftCode != ToontownGlobals.GIFT_RAT: + + giftList.append(giftItem[0]) + giftBlob = giftList.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + else: + giftBlob = AccumRATBeans(giftItem[0], giftBlob) + self.writeGift(doId, giftBlob, replyToChannelId) + + def writeGift(self, avatarId, newGift, replyToChannelId): + #print "writeGift" + #print retcode + """ + Writes the newly appended gift to the database + doId is the DO that you want to append newGift onto + newGift final appended blob to write to the database + """ + if 1 == 1: #case where it works + #print "sending acceptFR" + self.avatarIdToGifts.putData(avatarId, newGift)#update the cache + self.air.sendUpdateToDoId( + "DistributedToon", + "setGiftSchedule", avatarId, [newGift]) + #update the database + + def deliverGifts(self, avId, time): + """ + a request to have a toon's gifts delivered to their mailbox + this simply removes old gifts based on the time parameter + """ + #retreive the onGiftDelivery field + #remove the old gifts from the onGiftDelivery field + replyToChannelId = self.air.getSenderReturnChannel() + + giftListBlob = self.avatarIdToGifts.getData(avId) + if giftListBlob == None: + # if not in our cache + myAddGiftRequest=DeliverGiftRequest(self, replyToChannelId, avId, time) + else: + giftList = CatalogItemList.CatalogItemList(giftListBlob, store = CatalogItem.Customization | CatalogItem.DeliveryDate) + delivered, remaining = giftList.extractDeliveryItems(time) + giftBlob = remaining.getBlob(CatalogItem.Customization | CatalogItem.DeliveryDate) + self.writeGiftField(avId, giftBlob, replyToChannelId) + + self.sendUpdateToChannel(replyToChannelId, "receiveAcceptDeliverGifts", + [avId, "deliverGifts activated"]) + #print "Delivering Gifts" + + + def writeGiftField(self, avatarId, giftBlob, replyToChannelId): + """ + Rewrites the gift feild + doId is the DO that you want to append newGift onto + giftBlob final blob to write to the database + replyToChannel is where the message gets sent back to, in this case it is the calling AI + """ + if 1 == 1: #case where it works + self.avatarIdToGifts.putData(avatarId, giftBlob)#update the cache + self.air.sendUpdateToDoId( + "DistributedToon", + "setGiftSchedule", avatarId, [giftBlob]) + #update the database + self.sendUpdateToChannel(replyToChannelId, "receiveAcceptDeliverGifts", + [avatarId, "rADG"]) + #return an Accept message to the AI caller + else: + self.sendUpdateToChannel(replyToChannelId, "receiveRejectDeliverGifts", + [avatarId, "rRDG"]) + #return a Reject message to the AI caller + + def hello(self, message): + print message + replyToChannel = self.air.getSenderReturnChannel() + if message == "boo boo stinks": + print "no he doesn't you fool" + self.sendUpdateToChannel( + replyToChannel, "rejectHello", ["no he doesn't you fool"]) + else: + self.sendUpdateToChannel( + replyToChannel, "helloResponse", [message + "response"]) + + def requestAck(self): + replyToChannel = self.air.getSenderReturnChannel() + self.sendUpdateToChannel(replyToChannel, "returnAck", []) + + + diff --git a/toontown/src/uberdog/DistributedInGameNewsMgr.py b/toontown/src/uberdog/DistributedInGameNewsMgr.py new file mode 100644 index 0000000..a95ecbd --- /dev/null +++ b/toontown/src/uberdog/DistributedInGameNewsMgr.py @@ -0,0 +1,55 @@ +import socket +import datetime +import os +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.distributed.DistributedObject import DistributedObject +from toontown.toonbase import ToontownGlobals +from toontown.uberdog import InGameNewsResponses + +class DistributedInGameNewsMgr(DistributedObject ): + """ + Uberdog object that keeps track of the last time in game news has been updated + """ + notify = directNotify.newCategory('InGameNewsMgr') + neverDisable = 1 + + def __init__(self, cr): + """Construct ourselves, set up web dispatcher.""" + assert self.notify.debugCall() + DistributedObject.__init__(self, cr) + base.cr.inGameNewsMgr = self + + def delete(self): + """Delete ourself.""" + DistributedObject.delete(self) + self.cr.inGameNewsMgr = None + + def disable(self): + self.notify.debug( "i'm disabling InGameNewsMgr rightnow.") + DistributedObject.disable(self) + + def generate(self): + # Called when the client loads + self.notify.debug("BASE: generate") + DistributedObject.generate(self) + + + def setLatestIssueStr(self, issueStr): + """We normally get this once, we could get this when a new issue is released while logged in.""" + # the string we get is in utc + assert self.notify.debugStateCall(self) + self.latestIssueStr = issueStr + self.latestIssue = base.cr.toontownTimeManager.convertUtcStrToToontownTime(issueStr) + messenger.send('newIssueOut') + self.notify.info('latestIssue=%s' % self.latestIssue) + pass + + def getLatestIssueStr(self): + """We normally get this once, we could get this when a new issue is released while logged in.""" + assert self.notify.debugStateCall(self) + # why are we here + pass + + def getLatestIssue(self): + """Return the latest issue as coming from the uberdog server.""" + return self.latestIssue diff --git a/toontown/src/uberdog/DistributedInGameNewsMgrAI.py b/toontown/src/uberdog/DistributedInGameNewsMgrAI.py new file mode 100644 index 0000000..6d105e9 --- /dev/null +++ b/toontown/src/uberdog/DistributedInGameNewsMgrAI.py @@ -0,0 +1,66 @@ +import socket +import datetime +import os +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.http.WebRequest import WebRequestDispatcher +from otp.distributed import OtpDoGlobals +from toontown.toonbase import ToontownGlobals +from toontown.uberdog import InGameNewsResponses + +ParentClass = DistributedObjectAI +#ParentClass = DistributedObjectGlobal +class DistributedInGameNewsMgrAI(ParentClass ): + """ + Uberdog object that keeps track of the last time in game news has been updated + """ + notify = directNotify.newCategory('DistributedInGameNewsMgrAI') + + + def __init__(self, cr): + """Construct ourselves, set up web dispatcher.""" + assert self.notify.debugCall() + ParentClass.__init__(self, cr) + self.latestIssueStr = "" + + def generate(self): + """We have zone info but not required fields, register for the special.""" + # IN_GAME_NEWS_MGR_UD_TO_ALL_AI will arrive on this channel + self.air.registerForChannel(OtpDoGlobals.OTP_DO_ID_TOONTOWN_IN_GAME_NEWS_MANAGER) + ParentClass.generate(self) + + def announceGenerate(self): + # tell uberdog we are starting up, so we can get info on the currently running public parties + # do whatever other sanity checks is necessary here + DistributedObjectAI.announceGenerate(self) + self.air.sendUpdateToDoId("DistributedInGameNewsMgr", + 'inGameNewsMgrAIStartingUp', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_IN_GAME_NEWS_MANAGER, + [self.doId, self.air.districtId] + ) + def setLatestIssueStr(self, issueStr): + """We normally get this once, we could get this when a new issue is released while logged in.""" + # we receive this as a utc str + assert self.notify.debugStateCall(self) + self.latestIssueStr = issueStr + pass + + def b_setLatestIssueStr(self, latestIssue): + self.setLatestIssueStr(latestIssue) + self.d_setLatestIssueStr(latestIssue) + + def d_setLatestIssueStr(self, latestIssue): + self.sendUpdate("setLatestIssueStr",[self.getLatestIssueStr()]) + + + def getLatestIssueStr(self): + """We normally get this once, we could get this when a new issue is released while logged in.""" + assert self.notify.debugStateCall(self) + return self.latestIssueStr + pass + + + def newIssueUDtoAI(self, issueStr): + """Well the UD is telling us we have a new issue, spread it to the clients.""" + self.b_setLatestIssueStr(issueStr) + diff --git a/toontown/src/uberdog/DistributedInGameNewsMgrUD.py b/toontown/src/uberdog/DistributedInGameNewsMgrUD.py new file mode 100644 index 0000000..7a8333f --- /dev/null +++ b/toontown/src/uberdog/DistributedInGameNewsMgrUD.py @@ -0,0 +1,205 @@ +import socket +import datetime +import os +import pytz +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.http.WebRequest import WebRequestDispatcher +from otp.distributed import OtpDoGlobals +from toontown.toonbase import ToontownGlobals +from toontown.uberdog import InGameNewsResponses +from toontown.ai.ToontownAIMsgTypes import IN_GAME_NEWS_MANAGER_UD_TO_ALL_AI + +class DistributedInGameNewsMgrUD(DistributedObjectGlobalUD): + """ + Uberdog object that keeps track of the last time in game news has been updated + """ + notify = directNotify.newCategory('DistributedInGameNewsMgrUD') + serverDataFolder = simbase.config.GetString('server-data-folder', "") + + # WARNING this is a global OTP object + # InGameNewsMgrAI is NOT! + # Hence the use of sendUpdateToDoId when sending back to AI + + + def __init__(self, air): + """Construct ourselves, set up web dispatcher.""" + assert self.notify.debugCall() + DistributedObjectGlobalUD.__init__(self, air) + self.HTTPListenPort = uber.inGameNewsMgrHTTPListenPort + + self.webDispatcher = WebRequestDispatcher() + self.webDispatcher.landingPage.setTitle("InGameNewsMgr") + self.webDispatcher.landingPage.setDescription("InGameNews is update when a new issue of in-game-news is out.") + self.webDispatcher.registerGETHandler('inGameNewsMgr', self.inGameNewsMgr) + self.webDispatcher.registerGETHandler('inGameNewsNewIssue', self.inGameNewsNewIssue) + self.webDispatcher.listenOnPort(self.HTTPListenPort) + self.webDispatcher.landingPage.addTab("InGameNewsMgr","/inGameNewsMgr") + + self.air.setConnectionName("InGameNewsMgr") + self.air.setConnectionURL("http://%s:%s/" % (socket.gethostbyname(socket.gethostname()),self.HTTPListenPort)) + + self.filename = self.getFilename() + self.latestIssue = datetime.datetime.now() + self.latestIssue = self.loadRecords() + + def getLatestIssueStr(self): + self.notify.debugStateCall(self) + return self.latestIssue.strftime(self.air.toontownTimeManager.formatStr) + + def getLatestIssueUtcStr(self): + self.notify.debugStateCall(self) + datetimeInUtc = self.latestIssue.astimezone(pytz.utc) + result = datetimeInUtc.strftime(self.air.toontownTimeManager.formatStr) + return result + + def announceGenerate(self): + """Start accepting http requests.""" + assert self.notify.debugCall() + DistributedObjectGlobalUD.announceGenerate(self) + self.b_setLatestIssue(self.latestIssue) + self.webDispatcher.startCheckingIncomingHTTP() + + def inGameNewsMgr(self, replyTo, **kw): + """Handle all calls to web requests awardMgr.""" + assert self.notify.debugCall() + + # If no arguments are passed, assume that the main menu should + # be displayed + + if not kw: + function = None + id = None + else: + function = "doAward" + + header = body = help = footer = "" + if not function: + header,body,footer,help= self.getMainMenu() + else: + self.notify.debug("%s" % str(kw)) + header,body,footer,help= self.getMainMenu() + body = """

got these arguments """ + body += str(kw) + + #self.notify.info("%s" % header + body + help + footer) + replyTo.respond(header + body + help + footer) + + def inGameNewsNewIssue(self, replyTo, **kw): + try: + newIssue = self.air.toontownTimeManager.getCurServerDateTime() + self.b_setLatestIssue(newIssue) + self.updateRecordFile() + replyTo.respondXML(InGameNewsResponses.setLatestIssueSuccessXML % (self.getLatestIssueStr())) + + pass + except Exception,e: + replyTo.respondXML(InGameNewsResponses.setLatestIssueFailureXML % ("Catastrophic failure setting latest issue %s" % str(e))) + pass + + + def getMainMenu(self): + """Create the main menu with forms for input.""" + header = """Main Menu: In Game News Mgr + """ + + body = """

""" + body += """ + Latest Issue = """ + body += self.getLatestIssueStr() + body += """ +
+

+ +
+ """ + + footer = """

""" + help = """

Note
- Click on the button when a new issue of in game news has been released.

""" + return (header,body,footer,help) + + + def updateRecordFile(self): + """Update current track record in this shard's record file""" + # notify the leader boards that there has been an update + try: + backup = self.filename + '.bu' + if os.path.exists(self.filename): + os.rename(self.filename, backup) + file = open(self.filename, 'w') + file.seek(0) + file.write(self.getLatestIssueStr()) + file.close() + if os.path.exists(backup): + os.remove(backup) + except EnvironmentError: + self.notify.warning(str(sys.exc_info()[1])) + + def getFilename(self): + """Compose the track record filename""" + return "%s.latestissue" % (self.serverDataFolder) + + def getDefaultLatestIssueTime(self): + """Hmmm what the heck do we give. Lets use the current time.""" + result = self.air.toontownTimeManager.getCurServerDateTime() + return result + + def loadRecords(self): + """Load track record data from default location""" + try: + # Try to open the backup file: + file = open(self.filename + '.bu', 'r') + # Remove the (assumed) broken file: + if os.path.exists(self.filename): + os.remove(self.filename) + except IOError: + # OK, there's no backup file, good. + try: + # Open the real file: + file = open(self.filename, 'r') + except IOError: + # OK, there's no file. Grab the default times. + return self.getDefaultLatestIssueTime() + file.seek(0) + result = self.loadFrom(file) + file.close() + + return result + + def loadFrom(self, file): + """Load track record data from specified file""" + result = self.air.toontownTimeManager.getCurServerDateTime() + try: + latestIssueStr = file.readline() + result = self.air.toontownTimeManager.convertStrToToontownTime(latestIssueStr) + except EOFError: + pass + return result + + def setLatestIssueStr(self, issueStr): + self.notify.debugStateCall(self) + + + def setLatestIssue(self, latestIssue): + self.latestIssue = latestIssue + + def b_setLatestIssue(self, latestIssue): + self.setLatestIssue(latestIssue) + self.d_setLatestIssue(latestIssue) + + def d_setLatestIssue(self, latestIssue): + self.sendUpdateToAllAis('newIssueUDtoAI', [ self.getLatestIssueUtcStr()]) + + def sendUpdateToAllAis(self, message, args): + dg = self.dclass.aiFormatUpdateMsgType( + message, self.doId, self.doId, self.air.ourChannel, IN_GAME_NEWS_MANAGER_UD_TO_ALL_AI, args) + self.air.send(dg) + + def inGameNewsMgrAIStartingUp(self, doId, shardId): + """Tell the new AI that just started up what the latest issue is.""" + self.air.sendUpdateToDoId( + "DistributedInGameNewsMgr", + 'newIssueUDtoAI', + doId , + [self.getLatestIssueStr()] + ) + diff --git a/toontown/src/uberdog/DistributedMailManager.py b/toontown/src/uberdog/DistributedMailManager.py new file mode 100644 index 0000000..59cbfde --- /dev/null +++ b/toontown/src/uberdog/DistributedMailManager.py @@ -0,0 +1,4 @@ +from direct.distributed.DistributedObject import DistributedObject + +class DistributedMailManager(DistributedObject): + neverDisable = 1 diff --git a/toontown/src/uberdog/DistributedMailManagerAI.py b/toontown/src/uberdog/DistributedMailManagerAI.py new file mode 100644 index 0000000..e1b76bd --- /dev/null +++ b/toontown/src/uberdog/DistributedMailManagerAI.py @@ -0,0 +1,28 @@ +from direct.distributed.DistributedObjectAI import DistributedObjectAI + +class DistributedMailManagerAI(DistributedObjectAI): + """AI side class for the mail manager.""" + + notify = directNotify.newCategory("DistributedMailManagerAI") + + def __init__(self, air): + DistributedObjectAI.__init__(self, air) + self.accept("avatarEntered", self.__handleAvatarEntered) + + def sendSimpleMail(self, senderId, recipientId, simpleText): + """Testing to send a simple text message to another.""" + DistributedMailManagerAI.notify.debug("sendSimpleMail( senderId=%d, recipientId=%d, simpleText='%s' )" %(senderId, recipientId, simpleText) ) + self.sendUpdate('sendSimpleMail', [senderId, recipientId, simpleText]) + + def __handleAvatarEntered(self, avatar): + """A toon just logged in, check his mail.""" + DistributedMailManagerAI.notify.debug("__handleAvatarEntered( avatar=%s )" %avatar ) + #import pdb; pdb.set_trace() + #self.sendUpdate('avatarLoggedIn', [avatar.doId]) + + def setNumMailItems(self, avatarId, numMailItems): + DistributedMailManagerAI.notify.debug("setNumMailItems( avatarId=%d, numMailItems=%d )" %(avatarId, numMailItems) ) + toon = simbase.air.doId2do.get(avatarId) + if toon: + toon.setNumMailItems(numMailItems) + diff --git a/toontown/src/uberdog/DistributedMailManagerUD.py b/toontown/src/uberdog/DistributedMailManagerUD.py new file mode 100644 index 0000000..861869b --- /dev/null +++ b/toontown/src/uberdog/DistributedMailManagerUD.py @@ -0,0 +1,120 @@ +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.AsyncRequest import AsyncRequest +from toontown.uberdog import PartiesUdConfig +from toontown.uberdog.PartiesUdLog import partiesUdLog +from toontown.uberdog.ttMaildb import ttMaildb +from toontown.toonbase import ToontownGlobals +from toontown.uberdog.ttPartyDb import ttPartyDb +from toontown.uberdog.ttInviteDb import ttInviteDb + +class DistributedMailManagerUD(DistributedObjectGlobalUD): + """UD side class for the mail manager.""" + + notify = DirectNotifyGlobal.directNotify.newCategory("DistrubtedMailManagerUD") + + def __init__(self, air): + DistributedObjectGlobalUD.__init__(self, air) + user = uber.config.GetString("mysql-user", '') + passwd = uber.config.GetString("mysql-passwd",'') + if not user: + user = PartiesUdConfig.ttDbUser + if not passwd: + passwd = PartiesUdConfig.ttDbPasswd + self.mailDB = ttMaildb(host=PartiesUdConfig.ttDbHost, + port=PartiesUdConfig.ttDbPort, + user = user, + passwd = passwd, + db=PartiesUdConfig.ttDbName) + + def announceGenerate(self): + DistributedObjectGlobalUD.announceGenerate(self) + self.accept("avatarOnlinePlusAccountInfo", self.avatarOnlinePlusAccountInfo, []) + + def sendSimpleMail(self, senderId, recipientId, simpleText): + """Testing to send a simple text message to another.""" + DistributedMailManagerUD.notify.debug("sendSimpleMail( senderId=%d, recipientId=%d, simpleText='%s')" %(senderId, recipientId, simpleText)) + self.mailDB.putMail(recipientId, senderId, simpleText) + + def avatarLoggedIn(self, avatarId): + """Handle an avatar just logging in.""" + DistributedMailManagerUD.notify.debug("avatarLoggedIn( avatarId=%d )" %avatarId) + # for now we get all the mail, then send it across the wire to the client. + + result = self.mailDB.getMail(avatarId) + + DistributedMailManagerUD.notify.debug('mailDB.getMail returned %d items for avatarID %d' % (len(result), avatarId)) + self.numMailItems = len(result) + mailStr = str(result) + + replyToChannelAI = self.air.getSenderReturnChannel() + + #sefl.sendUpdateToChannel( replyToChannelAI, 'avatarLoggedInMailResponse', + # mailStr) + #if result: + # myGAR = GetAvatarForMailRequest(self, replyToChannelAI, + # avatarId, result, numMailItems) + self.mail = result + self.avatarId = avatarId + formattedMail = [] + numOld = 0 + numNew = 0 + for item in self.mail: + senderId = item['senderId'] + datetime = item['lastupdate'] + year = datetime.year + month = datetime.month + day = datetime.day + msgId = item['messageId'] + body = item['message'] + readFlag = item['readFlag'] + formattedMail.append( (msgId, senderId, year, month, day, body) ) + if readFlag: + numOld += 1 + else: + numNew += 1 + + DistributedMailManagerUD.notify.debug("Calling DistributedToon::setMail across the network with avatarId %d" %self.avatarId ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setMail", + self.avatarId, + [formattedMail], + ) + # for now, inform the AI that a toon has X number of mail + #self.distObj.sendUpdateToChannel(self.replyToChannelId, "setNumMailItems", + # [self.avatarId, self.numMailItems]) + # #return an Accept message to the AI caller + + DistributedMailManagerUD.notify.debug("Calling DistributedToon::setNumMailItems( %d ) across the network with avatarId %d" %(self.numMailItems, self.avatarId) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setNumMailItems", + self.avatarId, + [self.numMailItems], + ) + + simpleMailNotify = ToontownGlobals.NoItems + if numNew: + simpleMailNotify = ToontownGlobals.NewItems + elif numOld: + simpleMailNotify = ToontownGlobals.OldItems + + DistributedMailManagerUD.notify.debug("Calling DistributedToon::setSimpleMailNotify( %d ) across the network with avatarId %d" %(simpleMailNotify, self.avatarId) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setSimpleMailNotify", + self.avatarId, + [simpleMailNotify], + ) + + def avatarOnlinePlusAccountInfo(self,avatarId,accountId,playerName, + playerNameApproved,openChatEnabled, + createFriendsWithChat,chatCodeCreation): + # otp server is telling us an avatar just logged in + # this is far far better than having the AI be the one to tell us + assert self.notify.debugCall() + assert avatarId + + self.notify.debug("avatarOnlinePlusAccountInfo") + self.avatarLoggedIn(avatarId) diff --git a/toontown/src/uberdog/DistributedPartyManager.py b/toontown/src/uberdog/DistributedPartyManager.py new file mode 100644 index 0000000..9ddc503 --- /dev/null +++ b/toontown/src/uberdog/DistributedPartyManager.py @@ -0,0 +1,238 @@ +from direct.distributed.DistributedObject import DistributedObject +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal + +from pandac.PandaModules import CFSpeech, CFTimeout + +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.toon import ToonDNA + +from toontown.parties import PartyGlobals + +class DistributedPartyManager(DistributedObject): + neverDisable = 1 + + notify = directNotify.newCategory("DistributedPartyManager") + + def __init__(self,cr): + """Construct ourself.""" + DistributedObject.__init__(self, cr) + base.cr.partyManager = self + self.allowUnreleased= False + self.partyPlannerStyle = None + self.partyPlannerName = None + self.showDoid = False + + def delete(self): + """Delete ourself.""" + DistributedObject.delete(self) + self.cr.partyManager = None + + def disable(self): + self.notify.debug( "i'm disabling DistributedPartyManager rightnow.") + self.ignore("deallocateZoneIdFromPlannedParty") + self.ignoreAll() # catch requestPartyZoneComplete + DistributedObject.disable(self) + + def generate(self): + # Called when the client loads + self.notify.debug("BASE: generate") + DistributedObject.generate(self) + + # listen for requests + self.accept("deallocateZoneIdFromPlannedParty", self.deallocateZoneIdFromPlannedParty) + + # listen for the generate event, which will be thrown after the + # required fields are filled in + self.announceGenerateName = self.uniqueName("generate") + + def deallocateZoneIdFromPlannedParty(self, zoneId): + self.sendUpdate("freeZoneIdFromPlannedParty", [base.localAvatar.doId, zoneId]) + +#=============================================================================== +# Party Creation Methods +#=============================================================================== + + def allowUnreleasedClient(self): + """Return do we allow player to buy unreleased activities and decorations on the client.""" + return self.allowUnreleased + + def setAllowUnreleaseClient(self, newValue): + """Set if we allow player to buy unreleased activities and decorations on the client.""" + self.allowUnreleased = newValue + + def toggleAllowUnreleasedClient(self): + """Toggle allow unreleased on the client, then return the new value.""" + self.allowUnreleased = not self.allowUnreleased + return self.allowUnreleased + + def sendAddParty(self, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds): + """Add a new party.""" + #self.sendHello('resr') + self.sendUpdate('addPartyRequest', [hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds]) + + def addPartyResponse (self, hostId, errorCode): + """Handle being told by AI or uberdog the result of our add party request.""" + # Tell the party planning gui if the add party succeeded or not. + messenger.send("addPartyResponseReceived", [hostId, errorCode]) + if hasattr(base.localAvatar, "creatingNewPartyWithMagicWord"): + if base.localAvatar.creatingNewPartyWithMagicWord: + base.localAvatar.creatingNewPartyWithMagicWord = False + if errorCode == PartyGlobals.AddPartyErrorCode.AllOk: + base.localAvatar.setChatAbsolute("New party entered into database successfully.", CFSpeech | CFTimeout) + else: + base.localAvatar.setChatAbsolute("New party creation failed : %s" % PartyGlobals.AddPartyErrorCode.getString(errorCode), CFSpeech | CFTimeout) + + assert self.notify.debugStateCall() + +#=============================================================================== +# Active Party Management +#=============================================================================== + + # Fullfill client request for partyZone + def requestPartyZone(self, avId, zoneId, callback): + assert(self.notify.debug("requestPartyZone : avId=%s zoneId=%s" % (avId, zoneId))) + if zoneId < 0: + zoneId = 0 + self.acceptOnce("requestPartyZoneComplete", callback) + # If we're about to plan a party, we want to sell the AI about it + if hasattr(base.localAvatar, "aboutToPlanParty"): + if base.localAvatar.aboutToPlanParty: + self.sendUpdate("getPartyZone", [avId, zoneId, True]) + return + self.sendUpdate("getPartyZone", [avId, zoneId, False]) + + # The AI is telling us the zone for the party this avatar wants to go to + def receivePartyZone(self, hostId, partyId, zoneId): + assert(self.notify.debug("receivePartyZone(%d, %d, %d)" % (hostId, partyId, zoneId))) + if partyId != 0 and zoneId != 0: + if base.localAvatar.doId == hostId: + # We're really starting a party here, disable our go button + for partyInfo in base.localAvatar.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status == PartyGlobals.PartyStatus.Started + + messenger.send("requestPartyZoneComplete", [hostId, partyId, zoneId]) + + def sendChangePrivateRequest(self, partyId, newPrivateStatus): + """Request AI to change the party to either private or public.""" + self.sendUpdate('changePrivateRequest', [partyId, newPrivateStatus]) + + def changePrivateResponse(self, partyId, newPrivateStatus, errorCode): + """Handle the response to our request to change private status.""" + if errorCode == PartyGlobals.ChangePartyFieldErrorCode.AllOk: + # TODO Schell games give feed back to user it was a success + self.notify.info("succesfully changed private field for the party") + for partyInfo in localAvatar.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.isPrivate = newPrivateStatus + # Send this to other hooks on the client side (AFTER updating partyinfo) + messenger.send("changePartyPrivateResponseReceived", [partyId, newPrivateStatus, errorCode]) + else: + # Send this to other hooks on the client side (AFTER updating partyinfo) + messenger.send("changePartyPrivateResponseReceived", [partyId, newPrivateStatus, errorCode]) + self.notify.info("FAILED changing private field for the party") + + + def sendChangePartyStatusRequest(self, partyId, newPartyStatus): + """Request AI to change the party status.""" + self.sendUpdate('changePartyStatusRequest', [partyId, newPartyStatus]) + + def changePartyStatusResponse(self, partyId, newPartyStatus, errorCode, beansRefunded): + """ + Handle the response to our request to change the party status. + Only the host gets this. + """ + self.notify.debug( "changePartyStatusResponse : partyId=%s newPartyStatus=%s errorCode=%s"%(partyId, newPartyStatus, errorCode)) + for partyInfo in localAvatar.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = newPartyStatus + # Send this to other hooks on the client side (AFTER updating partyinfo) + messenger.send("changePartyStatusResponseReceived", [partyId, newPartyStatus, errorCode, beansRefunded]) + + def sendAvToPlayground(self, avId, retCode): + assert(self.notify.debug("sendAvToPlayground: %d" % avId)) + messenger.send(PartyGlobals.KICK_TO_PLAYGROUND_EVENT, [retCode]) + self.notify.debug("sendAvToPlayground: %d" % avId) + + def leaveParty(self): + if self.isDisabled(): + self.notify.warning("DistributedPartyManager disabled; unable to leave party.") + return + # Tell AI I want outta here + self.sendUpdate("exitParty",[localAvatar.zoneId]) + + def removeGuest(self, ownerId, avId): + self.notify.debug("removeGuest ownerId = %s, avId = %s" % (ownerId, avId)) + # The party owner is removing avId from his party. + # Notify the AI, and kick the ex-friend out of the party + self.sendUpdate("removeGuest", [ownerId, avId]) + + # TODO-parties: Do checks based on the rules about going to the party: + def isToonAllowedAtParty(self, avId, partyId): + return PartyGlobals.GoToPartyStatus.AllowedToGo + + # TODO-parties: Add TTLocalized reasons based on GoToPartyStatus Enum + def getGoToPartyFailedMessage(self, reason): + return "" + + def sendAvatarToParty(self, hostId): + """ + This is a guest of a party or a host of an already started party asking + to be sent to the party. We'll ask the server for the shardId and + zoneId and then send the avatar to the party. + """ + DistributedPartyManager.notify.debug("sendAvatarToParty hostId = %s" % hostId) + self.sendUpdate("requestShardIdZoneIdForHostId", [hostId]) + + def sendShardIdZoneIdToAvatar(self, shardId, zoneId): + """ + We've received the shardId and the zoneId of a party the local avatar + wants to go to, send them there. + """ + DistributedPartyManager.notify.debug("sendShardIdZoneIdToAvatar shardId = %s zoneId = %s" % (shardId, zoneId)) + if shardId == 0 or zoneId == 0: + base.cr.playGame.getPlace().handleBookClose() + return + hoodId = ToontownGlobals.PartyHood + if shardId == base.localAvatar.defaultShard: + shardId = None + base.cr.playGame.getPlace().requestLeave({ + "loader": "safeZoneLoader", + "where": "party", + "how" : "teleportIn", + "hoodId" : hoodId, + "zoneId" : zoneId, + "shardId" : shardId, + "avId" : -1, + }) + + def setPartyPlannerStyle(self, dna): + self.partyPlannerStyle = dna + + def getPartyPlannerStyle(self): + if self.partyPlannerStyle: + return self.partyPlannerStyle + else: + dna = ToonDNA.ToonDNA() + dna.newToonRandom() + return dna + + def setPartyPlannerName(self, name): + self.partyPlannerName = name + + def getPartyPlannerName(self): + if self.partyPlannerName: + return self.partyPlannerName + else: + return TTLocalizer.PartyPlannerGenericName + + def toggleShowDoid(self): + """Toggle allow unreleased on the client, then return the new value.""" + self.showDoid = not self.showDoid + return self.showDoid + + def getShowDoid(self): + """Return do we allow player to buy unreleased activities and decorations on the client.""" + return self.showDoid + diff --git a/toontown/src/uberdog/DistributedPartyManagerAI.py b/toontown/src/uberdog/DistributedPartyManagerAI.py new file mode 100644 index 0000000..ca54c29 --- /dev/null +++ b/toontown/src/uberdog/DistributedPartyManagerAI.py @@ -0,0 +1,1396 @@ +import random +import sys +import time +from sets import Set + +from direct.showbase.PythonUtil import Functor +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed.DistributedObjectGlobalAI import DistributedObjectGlobalAI +from otp.distributed import OtpDoGlobals +from toontown.parties import PartyGlobals +from toontown.parties.DistributedPartyAI import DistributedPartyAI +from toontown.parties.PartyInfo import PartyInfoAI +from toontown.ai import RepairAvatars +from toontown.toonbase import ToontownGlobals + +class DistributedPartyManagerAI(DistributedObjectAI): + """AI side class for the party manager.""" + + notify = directNotify.newCategory("DistributedPartyManagerAI") + + def __init__(self, air): + DistributedObjectAI.__init__(self, air) + self.accept("avatarEntered", self.handleAvatarEntered) + + self.allowUnreleased= False + self.canBuy = True # change this to True when boarding has had time on test + self.avIdToPartyZoneId = {} + self.hostAvIdToPartiesRunning = {} # hostAvId to DistributedPartyAIs + self.hostAvIdToAllPartiesInfo = {} # hostAvId to ( public party start time, shardId, zoneId, isPrivate, number of toons there, hostName, activityIds, partyId) + self.avIdEnteringPartyToHostIdZoneId = {} # avIds of toons entering a party to (hostId, zoneId) + self.zoneIdToGuestAvIds = {} # zoneId to list of guest avIds at that party + self.zoneIdToHostAvId = {} # Zone id's mapped to party host Id's + self.hostAvIdToClosingPartyZoneId = {} + self.hostIdToPlanningPartyZoneId = {} # Used for security checks when freeing zones that were used for planning + + # Number of seconds between spontaneous heals + self.healFrequency = 30 # seconds + + def generate(self): + """We have zone info but not required fields, register for the special.""" + # PARTY_MANAGER_UD_TO_ALL_AI will arrive on this channel + self.air.registerForChannel(OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER) + DistributedObjectAI.generate(self) + + def announceGenerate(self): + DistributedObjectAI.announceGenerate(self) + + # tell uberdog we are starting up, so we can get info on the currently running public parties + # do whatever other sanity checks is necessary here + self.air.sendUpdateToDoId("DistributedPartyManager", + 'partyManagerAIStartingUp', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, self.air.districtId] + ) + goingDownDg = self.air.createDgUpdateToDoId("DistributedPartyManager", + 'partyManagerAIGoingDown', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, self.air.districtId] + ) + if goingDownDg: + self.air.addPostSocketClose(goingDownDg) + + + + def handleAvatarEntered(self, avatar): + """A toon just logged in, check his party information.""" + DistributedPartyManagerAI.notify.debug( "handleAvatarEntered" ) + #self.air.sendUpdateToDoId("DistributedPartyManager", + # 'avatarLoggedIn', + # OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + # [avatar.doId]) + + def partyUpdate(self, avId): + """Force uberdog to resend all party related info from databases.""" + DistributedPartyManagerAI.notify.debug( "partyUpdate" ) + self.sendUpdate('avatarLoggedIn', [avId]) + + def sendAddParty(self, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds, costOfParty): + """Pass add party request up to uberdog.""" + DistributedPartyManagerAI.notify.debug( "sendAddParty" ) + self.air.sendUpdateToDoId("DistributedPartyManager", + 'addParty', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds, costOfParty]) + # Set up a failsafe incase uberdog has crashed... + taskMgr.doMethodLater( + 5.0, + self.addPartyResponseUdToAi, + "NoResponseFromUberdog_%d_%d"%(self.doId,hostId), + [hostId,PartyGlobals.AddPartyErrorCode.DatabaseError,0] + ) + + def addPartyRequest(self, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds): + """Add a new party.""" + DistributedPartyManagerAI.notify.debug( "addPartyRequest" ) + validPartyRequest = True + senderId = self.air.getAvatarIdFromSender() + toonSender = simbase.air.doId2do.get(senderId) + if not toonSender: + # the toon is not on our district, let the party manager on that district handle it + DistributedPartyManagerAI.notify.debug('addPartyRequest toon %d is not in our district' % senderId) + return + + if hostId != senderId: + # really bad, potential hacker + self.air.writeServerEvent('suspicious', senderId, + 'trying to create party but not host : hostId = %d' % hostId) + validPartyRequest = False + + if validPartyRequest: + validPartyRequest, costOfPartyOrError = self.validatePartyAndReturnCost(hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds) + + # assuming all is well, send this to uberdog, otherwise respond back immediately + if validPartyRequest: + actList = [] + for actTuple in activities: + actList.append(actTuple[0]) + decList = [] + for decTuple in decorations: + decList.append(decTuple[0]) + self.air.writeServerEvent("party_buy_attempt", hostId, "act=%s dec=%s" % ( str(actList), str(decList))) + self.sendAddParty(hostId, startTime,endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds, costOfPartyOrError) + else: + DistributedPartyManagerAI.notify.debug('inValid party because : %s' % costOfPartyOrError) + self.sendAddPartyResponse(hostId, PartyGlobals.AddPartyErrorCode.ValidationError) + + def validatePartyAndReturnCost(self, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds): + DistributedPartyManagerAI.notify.debug( "validatePartyAndReturnCost" ) + # First, check to see if this is his only party that isn't cancelled or + # finished. + host = simbase.air.doId2do[hostId] + if not host.canPlanParty(): + return (False,"Other Parties") + + # TODO-parties : We need to validate startTime and endTime to make sure + # they are valid strings, or will sql do that? + try: + startTm = time.strptime(startTime, "%Y-%m-%d %H:%M:%S") + if PartyGlobals.MaxPlannedYear < startTm.tm_year: + return (False,"Start time too far in the future") + elif startTm.tm_year < PartyGlobals.MinPlannedYear: + return (False,"Start time too far in the future") + except ValueError: + return (False, "Can't parse startTime") + + try: + endTm = time.strptime(endTime, "%Y-%m-%d %H:%M:%S") + if PartyGlobals.MaxPlannedYear < endTm.tm_year: + return (False,"End time too far in the future") + elif endTm.tm_year < PartyGlobals.MinPlannedYear: + return (False,"End time too far in the future") + except ValueError: + return (False, "Can't parse endTime") + + if isPrivate not in (0,1): + return (False,"Invalid isPrivate %s" % isPrivate) + + if inviteTheme not in PartyGlobals.InviteTheme: + return (False,"Invalid inviteTheme %s" % inviteTheme) + + if hasattr(simbase.air, "holidayManager"): + if ToontownGlobals.VALENTINES_DAY not in simbase.air.holidayManager.currentHolidays: + if inviteTheme == PartyGlobals.InviteTheme.Valentoons: + return (False,"Invalid inviteTheme %s" % inviteTheme) + if ToontownGlobals.VICTORY_PARTY_HOLIDAY not in simbase.air.holidayManager.currentHolidays: + if inviteTheme == PartyGlobals.InviteTheme.VictoryParty: + return (False,"Invalid inviteTheme %s" % inviteTheme) + + costOfParty = 0 + activitiesUsedDict = {} + usedGridSquares = {} # key is a tuple (x,y), value isn't that important + actSet = Set([]) + for activityTuple in activities: + if activityTuple[0] not in PartyGlobals.ActivityIds: + return (False,"Invalid activity id %s"%activityTuple[0]) + + activityId = activityTuple[0] + + # Check for holiday restrictions. + if activityId in PartyGlobals.VictoryPartyActivityIds: + if not simbase.air.holidayManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY): + return (False, "Can't add activity %s during Victory Party " %activityId) + if activityId in PartyGlobals.VictoryPartyReplacementActivityIds: + if simbase.air.holidayManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY): + return (False, "Can't add activity %s during Victory Party " %activityId) + + actSet.add(activityTuple[0]) + if activitiesUsedDict.has_key(activityTuple[0]): + activitiesUsedDict[activityTuple[0]] += 1 + else: + activitiesUsedDict[activityTuple[0]] = 1 + costOfParty += PartyGlobals.ActivityInformationDict[activityTuple[0]]["cost"] + if activityTuple[1] < 0 or activityTuple[1] >= PartyGlobals.PartyEditorGridSize[0]: + return (False,"Invalid activity x %s"%activityTuple[1]) + if activityTuple[2] < 0 or activityTuple[2] >= PartyGlobals.PartyEditorGridSize[1]: + return (False,"Invalid activity y %s"%activityTuple[2]) + if activityTuple[3] < 0 or activityTuple[3] > 255: + return (False,"Invalid activity h %s"%activityTuple[3]) + # check for unreleased activity + if activityTuple[0] in PartyGlobals.UnreleasedActivityIds: + self.air.writeServerEvent('suspicious', hostId, "trying to buy unreleased activity %s" % + PartyGlobals.ActivityIds.getString(activityTuple[0])) + self.notify.warning("%d trying to buy unreleased activity %s" % + (hostId, PartyGlobals.ActivityIds.getString(activityTuple[0]))) + if not self.allowUnreleasedServer(): + return (False, "Activity %s is not released" % + PartyGlobals.ActivityIds.getString(activityTuple[0])) + + # check if the grid squares are valid + gridSize = PartyGlobals.ActivityInformationDict[activityId]["gridsize"] + centerGridX = activityTuple[1] + centerGridY = activityTuple[2] + # y has 14 at the north side (top) of the party editor + yRange = self.computeGridYRange(centerGridY, gridSize[1]) + xRange = self.computeGridXRange(centerGridX, gridSize[0]) + for curGridY in yRange: + for curGridX in xRange: + squareToTest = (curGridX, curGridY) + if squareToTest in usedGridSquares: + self.notify.debug("activitities=%s decor=%s usedGridSquares=%s" % + (str(activities), + str(decorations), + str(usedGridSquares))) + return (False, "Grid Square %s is used twice by %s and %s" % + (str(squareToTest), + str(activityId), + str(usedGridSquares[squareToTest]))) + else: + usedGridSquares[squareToTest]="activity-%d"%activityId + + # Check to see if an activity is used too many times + for id in PartyGlobals.ActivityIds: + if activitiesUsedDict.has_key(id): + if activitiesUsedDict[id] > PartyGlobals.ActivityInformationDict[id]["limitPerParty"]: + return (False,"Too many of activity %s"%id) + + # Check for mutually exclusive activities + for mutuallyExclusiveTuples in PartyGlobals.MutuallyExclusiveActivities: + mutSet = Set(mutuallyExclusiveTuples) + inter = mutSet.intersection(actSet) + if len(inter) > 1: + return (False, "Mutuallly exclusive activites %s" % str(inter)) + + decorationsUsedDict = {} + for decorationTuple in decorations: + decorId = decorationTuple[0] + if decorId not in PartyGlobals.DecorationIds: + return (False,"%s is not a valid decoration" % decorId) + # Check if decorId is a holiday specific decoration. + decorName = PartyGlobals.DecorationIds.getString(decorId) + if (decorName == "HeartTarget") \ + or (decorName == "HeartBanner") \ + or (decorName == "FlyingHeart"): + if not simbase.air.holidayManager.isHolidayRunning(ToontownGlobals.VALENTINES_DAY): + return (False, "Can't add ValenToons decoration %s" % decorId) + if decorId in PartyGlobals.VictoryPartyDecorationIds: + if not simbase.air.holidayManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY): + return (False, "Can't add Victory Party decoration %s" % decorId) + elif decorId in PartyGlobals.VictoryPartyReplacementDecorationIds: + if simbase.air.holidayManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY): + return (False, "Can't add decoration during Victory Party %s" % decorId) + + if decorationsUsedDict.has_key(decorationTuple[0]): + decorationsUsedDict[decorationTuple[0]] += 1 + else: + decorationsUsedDict[decorationTuple[0]] = 1 + costOfParty += PartyGlobals.DecorationInformationDict[decorationTuple[0]]["cost"] + if decorationTuple[1] < 0 or decorationTuple[1] >= PartyGlobals.PartyEditorGridSize[0]: + return (False,"Invalid decoration X %s" % decorationTuple[1]) + if decorationTuple[2] < 0 or decorationTuple[2] >= PartyGlobals.PartyEditorGridSize[1]: + return (False,"Invalid decoration Y %s" % decorationTuple[2]) + if decorationTuple[3] < 0 or decorationTuple[3] > 255: + return (False,"Invalid decoration H %s" % decorationTuple[3]) + # check for unreleased decoration + if decorationTuple[0] in PartyGlobals.UnreleasedDecorationIds: + self.air.writeServerEvent('suspicious', hostId, "trying to buy unreleased decoration %s" % + PartyGlobals.DecorationIds.getString(decorationTuple[0])) + self.notify.warning("%d trying to buy unreleased decoration %s" % + (hostId, PartyGlobals.DecorationIds.getString(decorationTuple[0]))) + if not self.allowUnreleasedServer(): + return (False, "Decoration %s is not released" % + PartyGlobals.DecorationIds.getString(decorationTuple[0])) + # check if the grid squares are valid + gridSize = PartyGlobals.DecorationInformationDict[decorId]["gridsize"] + centerGridX = decorationTuple[1] + centerGridY = decorationTuple[2] + # y has 14 at the north side (top) of the party editor + yRange = self.computeGridYRange(centerGridY, gridSize[1]) + xRange = self.computeGridXRange(centerGridX, gridSize[0]) + for curGridY in yRange: + for curGridX in xRange: + squareToTest = (curGridX, curGridY) + if squareToTest in usedGridSquares: + self.notify.debug("activitities=%s decor=%s usedGridSquares=%s" % + (str(activities), + str(decorations), + str(usedGridSquares))) + return (False, "decor Grid Square %s is used twice" % str(squareToTest)) + else: + usedGridSquares[squareToTest]="decor-%d"%decorId + + # Check to see if a decoration is used too many times + for id in PartyGlobals.DecorationIds: + if decorationsUsedDict.has_key(id): + if decorationsUsedDict[id] > PartyGlobals.DecorationInformationDict[id]["limitPerParty"]: + return (False,"Decoration %s used too many times." % id) + + # Can I afford this party, really? + if costOfParty > host.getTotalMoney(): + return (False,"Party too expensive, cost = %d"%costOfParty) + + # Can't have parties that have 0 empty grid squares. + if len(usedGridSquares) >= PartyGlobals.AvailableGridSquares: + return (False,"Party uses %s grid squares." % len(usedGridSquares)) + + # Wow, you passed all the tests I can think of, ship it! + return (True, costOfParty) + + #### + ## Grid range computation note: + ## We must round with negative values otherwise for center=0, size=3, the + ## result will be [1, 0] when we expect [1, 0, -1]. + ## The range without rounding: range(int(1.5), int(-1.5), -1) + ## The range with rounding: range(int(1.5), int(-2), -1) + ## Not a problem with center>=2 in this example: + ## The range without rounding: range(int(3.5), int(0.5), -1) + ## The range with rounding: range(int(3.5), int(0), -1) + #### + + def computeGridYRange(self, centerGridY, size): + result = [] + if size == 1: + result = [centerGridY] + else: + result = range(int(centerGridY + size/2.0), + int(centerGridY - round(size/2.0)), + -1) + + # The result list should be the same size as given. + assert len(result) == size, "Bad result range: c=%s s=%s result=%s" % (centerGridY, size, result) + + return result + + def computeGridXRange(self, centerGridX, size): + result = [] + if size == 1: + result = [centerGridX] + else: + result = range(int(centerGridX + size/2.0), + int(centerGridX - round(size/2.0)), + -1 + ) + + # The result list should be the same size as given. + assert len(result) == size, "Bad result range: c=%s s=%s result=%s" % (centerGridX, size, result) + + return result + + + def sendAddPartyResponse(self, hostId, errorCode): + """Tell the client if he's add party request got accepted.""" + self.sendUpdateToAvatarId(hostId, "addPartyResponse", [hostId, errorCode]) + + def markInviteReadButNotReplied(self, inviteKey): + """Just flag the invite as read in the database.""" + self.air.sendUpdateToDoId("DistributedPartyManager", + 'markInviteAsReadButNotReplied', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, inviteKey] + ) + + def respondToInviteFromMailbox(self, context, inviteKey, newStatus, mailboxDoId): + """Send invite response to uberdog.""" + DistributedPartyManagerAI.notify.debug( "respondToInvite" ) + senderId = self.air.getAvatarIdFromSender() + toonSender = simbase.air.doId2do.get(senderId) + if not toonSender: + # the toon is not on our district, let the party manager on that district handle it + DistributedPartyManagerAI.notify.debug('respondToInviteFromMailbox toon %d is not in our district' % senderId) + return + self.air.sendUpdateToDoId("DistributedPartyManager", + 'respondToInvite', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, mailboxDoId, context, inviteKey, newStatus] + ) + + def respondToInviteResponse(self, mailboxDoId, context, inviteKey, retcode, newStatus): + """UD responding to our invite change.""" + DistributedPartyManagerAI.notify.debug( "respondToInviteResponse" ) + mailboxAI = simbase.air.doId2do.get(mailboxDoId) + if mailboxAI: + if newStatus == PartyGlobals.InviteStatus.Rejected: + mailboxAI.respondToRejectInviteCallback(context, inviteKey, retcode) + else: + mailboxAI.respondToAcceptInviteCallback(context, inviteKey, retcode) + + def addPartyResponseUdToAi(self, hostId, errorCode, costOfParty): + """Handle uberdog responding to our addParty message.""" + taskMgr.remove("NoResponseFromUberdog_%d_%d"%(self.doId,hostId)) + if errorCode == PartyGlobals.AddPartyErrorCode.AllOk: + host = simbase.air.doId2do.get(hostId) + self.air.writeServerEvent("party_buy", hostId,"%d" % costOfParty) + if host : + host.takeMoney(costOfParty, bUseBank = True) + else: + # Woah, did he just get a free party? Someone + # bought a party, and while the uberdog was putting it in the + # database, they logged out... how can we make sure the money + # gets taken out? + self.deductMoneyFromOfflineToon(hostId, costOfParty) + + self.sendAddPartyResponse(hostId, errorCode) + + def changePrivateRequest(self, partyId, newPrivateStatus): + """Handle the client requesting to make a party public/private.""" + senderId = self.air.getAvatarIdFromSender() + toonSender = simbase.air.doId2do.get(senderId) + if not toonSender: + # the toon is not on our district, let the party manager on that district handle it + DistributedPartyManagerAI.notify.debug('changePrivateRequest toon %d is not in our district' % senderId) + return + + errorCode = self.partyFieldChangeValidate(partyId) + if errorCode != PartyGlobals.ChangePartyFieldErrorCode.AllOk: + # immediately say we have an error then return + self.sendUpdateToAvatarId(senderId,'changePrivateResponse', + [partyId, newPrivateStatus, errorCode]) + return + + # do whatever other sanity checks is necessary here + self.air.sendUpdateToDoId("DistributedPartyManager", + 'changePrivateRequestAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, partyId, newPrivateStatus] + ) + + def partyFieldChangeValidate(self, partyId): + """Do common validation when changing private and status fields for a party.""" + senderId = self.air.getAvatarIdFromSender() + errorCode = PartyGlobals.ChangePartyFieldErrorCode.AllOk + toon = simbase.air.doId2do.get(senderId) + if not toon: + # we don't have the toon for some reason + errorCode = PartyGlobals.ChangePartyFieldErrorCode.ValidationError + return errorCode + + hostingThisParty = False + for party in toon.hostedParties: + if partyId == party.partyId: + hostingThisParty = True + break + + if not hostingThisParty: + # the toon is not hosting this partyId + # really bad, potential hacker + self.air.writeServerEvent('suspicious', senderId, + 'trying to change field of party %s but not the host' % partyId) + errorCode = PartyGlobals.ChangePartyFieldErrorCode.ValidationError + return errorCode + + if party.hostId != senderId: + # really bad, potential hacker + self.air.writeServerEvent('suspicious', senderId, + 'trying to change field of party %s but not the host' % partyId) + errorCode = PartyGlobals.ChangePartyFieldErrorCode.ValidationError + return errorCode + + return errorCode + + def changePrivateResponseUdToAi(self, hostId, partyId, newPrivateStatus, errorCode): + """Handle the Uberdog telling us if the change private succeeded or not.""" + if errorCode == PartyGlobals.ChangePartyFieldErrorCode.AllOk: + if self.air.doId2do.has_key(hostId): + av = self.air.doId2do[hostId] + for partyInfo in av.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.isPrivate = newPrivateStatus + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + self.hostAvIdToAllPartiesInfo[hostId][3] = newPrivateStatus + + self.sendUpdateToAvatarId(hostId, "changePrivateResponse", [partyId, newPrivateStatus, errorCode]) + + def changePartyStatusRequest(self, partyId, newPartyStatus): + """Handle the client requesting to change the party status.""" + senderId = self.air.getAvatarIdFromSender() + toonSender = simbase.air.doId2do.get(senderId) + if not toonSender: + # the toon is not on our district, let the party manager on that district handle it + DistributedPartyManagerAI.notify.debug('changePartyStatusRequest toon %d not in our district' % senderId) + return + errorCode = self.partyFieldChangeValidate(partyId) + if errorCode != PartyGlobals.ChangePartyFieldErrorCode.AllOk: + # immediately say we have an error then return + self.sendUpdateToAvatarId(senderId,'changePartyStatusResponse', + [partyId, newPartyStatus, errorCode, 0]) + return + + # do whatever other sanity checks is necessary here + self.air.sendUpdateToDoId("DistributedPartyManager", + 'changePartyStatusRequestAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, partyId, newPartyStatus] + ) + + def changePartyStatusResponseUdToAi(self, hostId, partyId, newPartyStatus, errorCode): + """Handle the Uberdog telling us if the change partyStatus succeeded or not.""" + beansRefunded = 0 + if self.air.doId2do.has_key(hostId): + av = self.air.doId2do[hostId] + for partyInfo in av.hostedParties: + if partyInfo.partyId == partyId: + partyInfo.status = newPartyStatus + if newPartyStatus == PartyGlobals.PartyStatus.Cancelled: + beansRefunded = self.getCostOfParty(partyInfo) + beansRefunded = int(PartyGlobals.PartyRefundPercentage * beansRefunded) + av.addMoney(beansRefunded) + self.air.writeServerEvent("party_cancel", hostId, "%d|%d|%d|%d" % (beansRefunded, partyId, newPartyStatus, errorCode)) + self.sendUpdateToAvatarId(hostId, "changePartyStatusResponse", [partyId, newPartyStatus, errorCode, beansRefunded]) + + def getCostOfParty(self, partyInfo): + newCost = 0 + for activityBase in partyInfo.activityList: + newCost += PartyGlobals.ActivityInformationDict[activityBase.activityId]["cost"] + for decorBase in partyInfo.decors: + newCost += PartyGlobals.DecorationInformationDict[decorBase.decorId]["cost"] + return newCost + + def getAllPublicParties(self): + allParties = self.hostAvIdToAllPartiesInfo.values() + allParties.sort() + returnParties = [] + curGmTime = time.time() + for partyInfo in allParties: + # If the party is private, just continue, don't append it. + if partyInfo[3]: + continue + # We want to return a list that has positive time and isPrivate + minLeft = int( (PartyGlobals.DefaultPartyDuration *60) - ( curGmTime - partyInfo[0]) / 60.0) + if minLeft <= 0: + continue + returnParties.append(partyInfo[1:3] + partyInfo[4:7] + [minLeft]) + DistributedPartyManagerAI.notify.debug("getAllPublicParties : %s" % returnParties) + return returnParties + + def updateToPublicPartyCountUdToAllAi(self, hostId, newCount): + """ + The count has changed on a public party. + """ + DistributedPartyManagerAI.notify.debug("updateToPublicPartyCountUdToAllAi : hostId=%s newCount=%s"%(hostId, newCount)) + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + self.hostAvIdToAllPartiesInfo[hostId][4] = newCount + + def partyHasFinishedUdToAllAi(self, hostId): + """ + This party has finished, it may not have been mine, so just update my + public party information. + """ + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + del self.hostAvIdToAllPartiesInfo[hostId] + + def updateToPublicPartyInfoUdToAllAi(self, hostId, time, shardId, zoneId, isPrivate, numberOfGuests, hostName, activityIds, partyId): + """ + There is an update to a public party, might not be on this AI so just + update the public party information. + """ + DistributedPartyManagerAI.notify.debug("updateToPublicPartyInfoUdToAllAi : hostId=%s time=%s shardId=%s zoneId=%s isPrivate=%s numberOfGuests=%s hostName=%s"%(hostId, time, shardId, zoneId, isPrivate, numberOfGuests, hostName)) + self.hostAvIdToAllPartiesInfo[hostId] = [time, shardId, zoneId, isPrivate, numberOfGuests, hostName, activityIds, partyId] + + def delete(self): + DistributedPartyManagerAI.notify.debug("BASE: delete: deleting DistributedPartyManagerAI object") + self.ignoreAll() + DistributedObjectGlobalAI.DistributedObjectGlobalAI.delete(self) + for party in self.hostAvIdToPartiesRunning.values(): + party.requestDelete() + del self.avIdToPartyZoneId + del self.hostAvIdToPartiesRunning + del self.hostAvIdToAllPartiesInfo + del self.avIdEnteringPartyToHostIdZoneId + del self.zoneIdToGuestAvIds + del self.zoneIdToHostAvId + del self.hostAvIdToClosingPartyZoneId + del self.hostIdToPlanningPartyZoneId + + ## ----------------------------------------------------------- + ## Zone allocation and enter code + ## ----------------------------------------------------------- + + # Gets the party zone based on the host's avatar ID + def getPartyZone(self, hostId, zoneId, planningParty): + DistributedPartyManagerAI.notify.debug("getPartyZone: hostId=%s zoneId=%s planningParty=%s" % (hostId, zoneId, planningParty)) + # Get the party the avatar is in. + # If the party is running in this ai, and sender is allowed to go + # (public or invited, and not full), then return the party zone. + # If no party is running right now, and the sender is the avatar + # Then look for the party info and check if he's the owner. + # If he's the owner, then create the party, and return the created party zone + # If he's not the owner, maybe he's got a valid zone already and he's + # looking to join a party coming from a public party gate + # Otherwise fail + + senderId = self.air.getAvatarIdFromSender() + + # If we're planning a party, we need to give them a zone to plan in, but + # we don't need to create a DistributedParty/AI and add them to all the + # dictionaries. + if planningParty: + if hostId != senderId: + self.air.writeServerEvent('suspicious', senderId, 'trying to plan party but not host : hostId = %d' % hostId) + self.__sendNoPartyZoneToClient(senderId) + return + # let's allocate a zone for the client to plan the party in + zoneId = self.air.allocateZone() + # We'll need to free it later, and we want to make sure we're freeing + # the right zone, so let's remember it. + self.hostIdToPlanningPartyZoneId[senderId] = zoneId + DistributedPartyManagerAI.notify.debug("getPartyZone : Avatar %s is planning party in zone %s" % (senderId, zoneId)) + self.sendUpdateToAvatarId(senderId, "receivePartyZone", [senderId, 0, zoneId]) + return + + # If they have a zoneId, that means they came from a public party gate + # or they are teleporting directly to a toon in a party or they are the + # host returning to their own party. + if zoneId > 0 : + if zoneId not in self.zoneIdToHostAvId: + # this party is gone, you'cant go to it + self.notify.warning("Trying to go to a party that is gone. zoneId=%s" % zoneId) + self.__sendNoPartyZoneToClient(senderId) + return + + partyHostId = self.zoneIdToHostAvId[zoneId] + if self.hostAvIdToClosingPartyZoneId.has_key(partyHostId): + # This party is closing, you can't go to it. + self.notify.warning("Trying to go to a party that is closing. hostId=%s" % partyHostId) + self.__sendNoPartyZoneToClient(senderId) + return + + if partyHostId != senderId: + # If I'm not the host, check to see if the party is private + if self.hostAvIdToPartiesRunning[partyHostId].partyInfo.isPrivate: + # This is a private party, check the invitee list + if senderId not in self.hostAvIdToPartiesRunning[partyHostId].inviteeIds: + # The senderId is not on the invitee list of a private party + # so they can't attend, sorry. + self.__sendNoPartyZoneToClient(senderId) + return + self.__addReferences(senderId, partyHostId) + self.__waitForToonToEnterParty(senderId, partyHostId, zoneId) + self.__sendPartyZoneToClient(senderId, partyHostId) + return + + self.__enterParty(senderId, hostId) + + avPartyZoneId = self.avIdToPartyZoneId.get(hostId) + senderPartyZoneId = self.avIdToPartyZoneId.get(senderId) + + isSenderHost = False + isHostStartingParty = False + + # Else if sender is host, then toon might be starting a party + if senderId == hostId: + isSenderHost = True + + # If the host got here and there's no party for him, then start a party. + # TODO-parties: Double check on the party shard dict to make sure that toon's party is not happening somewhere else. + if ( + (not self.hostAvIdToPartiesRunning.has_key(senderId)) and + (not self.hostAvIdToClosingPartyZoneId.has_key(senderId)) + ): + # The host is starting a party, let's clear him out of the avIdToPartyZoneId + #self.clearPartyZoneId(senderId) # this doesn't clear him out of guests + self.__exitParty(senderId) + isHostStartingParty = True + # Else sender is visiting or attending a party at this shard: + else: + # The party we are visiting is not in this shard + if avPartyZoneId is None: + self.notify.warning("Avatar is not at a party in this shard.") + # SDN: tell the client and do something more graceful + # make sure we don't give this guy toonups + try: + # stop tooning up this visitor, he hasn't reached the party yet + av = self.air.doId2do[senderId] + av.stopToonUp() + except: + DistributedPartyManagerAI.notify.debug("couldn't stop toonUpTask for av %s" % self.air.getAvatarIdFromSender()) + self.__sendNoPartyZoneToClient(senderId) + return + + # If sender was at a party: + if senderPartyZoneId is not None: + # Check if toon is teleporting somewhere else in the same party: + # No need to update the party zone information in this case. + try: + if senderPartyZoneId == avPartyZoneId and not isHostStartingParty: + DistributedPartyManagerAI.notify.debug("We are staying in the same zone %s, don't delete." % senderPartyZoneId) + self.__sendPartyZoneToClient(senderId, hostId) + return + else: + DistributedPartyManagerAI.notify.debug("Sender is goint to a different party. Party av zone = %s, sender zone = %s." % (avPartyZoneId, senderPartyZoneId)) + except: + DistributedPartyManagerAI.notify.debug("Sender is not teleporting to the same party.") + + # At this point, toon is at a party and is going/creating a different party + if senderPartyZoneId != avPartyZoneId: + if self.hostAvIdToClosingPartyZoneId.has_key(hostId): + # This party is closing, you can't go to it. + self.notify.warning("Trying to go to a party that is closing. hostId=%s" % hostId) + self.__sendNoPartyZoneToClient(senderId) + return + else: + self.notify.debug("644 calling exitParty for %s" % senderId) + self.__exitParty(senderId) + + # Sender is host and he's starting a party, then start the party: + if isSenderHost and isHostStartingParty: + if self.checkHostHasPartiesThatCanStart(hostId): + # We need to ask DistributedPartyManagerUD for the party information + # self.partyInfoOfHostResponseUdToAi will be called with the response + self.notify.debug("starting party Asking uberdog for party ifnromation hostId=%d" % hostId) + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'partyInfoOfHostRequestAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, hostId] + ) + taskMgr.doMethodLater( + 3.0, + self.partyInfoOfHostFailedResponseUdToAi, + "UberdogTimedOut_%d"%hostId, + [hostId] + ) + else: + # save a round trip asking the uberdog, fail immediately + self.air.writeServerEvent('suspicious', senderId, + 'trying to start a party when none can start %d' % senderId) + self.notify.warning('suspicious %d trying to start a party when none can start' % senderId) + self.__sendNoPartyZoneToClient(senderId) + return + + + # We have a toon visiting another toon's party or a host visiting an + # already started party, update dicts + else: + # Note: hostId is host of the party sender is trying to go to + if self.hostAvIdToClosingPartyZoneId.has_key(hostId): + # This party is closing, you can't go to it. + self.notify.warning("Trying to go to a party that is closing. hostId=%s" % hostId) + self.__sendNoPartyZoneToClient(senderId) + return + self.__addReferences(senderId, hostId) + zoneId = self.avIdToPartyZoneId[hostId] + self.__waitForToonToEnterParty(senderId, hostId, zoneId) + self.__sendPartyZoneToClient(senderId, hostId) + + def checkHostHasPartiesThatCanStart(self, hostId): + """Return True if the host has any party that can start.""" + result = False + toon = simbase.air.doId2do.get(hostId) + if toon: + hostedParties = toon.hostedParties + for partyInfo in hostedParties: + # it must not be cancelle or finished + if partyInfo.status in (PartyGlobals.PartyStatus.Cancelled, + PartyGlobals.PartyStatus.Finished): + continue + curServerTime = self.air.toontownTimeManager.getCurServerDateTimeForComparison() + if curServerTime < partyInfo.startTime: + # the party is still in the future + continue + if partyInfo.endTime < curServerTime: + # party end time has passed + continue + # if we get here we have at least 1 party that could start + result = True + break + else: + result = True + self.notify.warning("checkHostedParties could not find toon %d " % hostId) + return result + + + def getAvEnterEvent(self): + return 'avatarEnterParty' + + def getAvExitEvent(self, avId=None): + # listen for all exits or a particular exit + # event args: + # if avId given: none + # if avId not given: avId, hostId, zoneId + if avId is None: + return 'avatarExitParty' + else: + return 'avatarExitParty-%s' % avId + + def __enterParty(self, avId, hostId): + # Tasks that should always get called when entering a party + + # Handle unexpected exit + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.__handleUnexpectedExit, extraArgs=[avId]) + + def __waitForToonToEnterParty(self, avId, hostId, zoneId): + if avId in self.avIdEnteringPartyToHostIdZoneId: + self.notify.warning( + '__waitForToonToEnterParty(avId=%s, ownerId=%s, zoneId=%s): ' + '%s already in avIdToPendingEnter. overwriting' % ( + avId, hostId, zoneId, avId)) + self.avIdEnteringPartyToHostIdZoneId[avId] = (hostId, zoneId) + self.accept(DistributedObjectAI.staticGetLogicalZoneChangeEvent(avId), + Functor(self.__toonChangedZone, avId)) + + def __toonLeftBeforeArrival(self, avId): + if avId not in self.avIdEnteringPartyToHostIdZoneId: + self.notify.warning('__toonLeftBeforeArrival: av %s not in table' % + avId) + return + hostId, zoneId = self.avIdEnteringPartyToHostIdZoneId[avId] + self.notify.warning( + '__toonLeftBeforeArrival: av %s left server before arriving in ' + 'party (host=%s, zone=%s)' % (avId, hostId, zoneId)) + del self.avIdEnteringPartyToHostIdZoneId[avId] + + # When toon changes zone, check if toon has finally entered party + def __toonChangedZone(self, avId, newZoneId, oldZoneId): + #DistributedPartyManagerAI.notify.debug('_toonChangedZone(avId=%s, newZoneId=%s, oldZoneId=%s)' % (avId, newZoneId, oldZoneId)) + if avId not in self.avIdEnteringPartyToHostIdZoneId: + self.notify.warning('__toonChangedZone: av %s not in table' % + avId) + return + av = self.air.doId2do.get(avId) + if not av: + self.notify.warning('__toonChangedZone(%s): av not present' % avId) + return + hostId, zoneId = self.avIdEnteringPartyToHostIdZoneId[avId] + if newZoneId == zoneId: + del self.avIdEnteringPartyToHostIdZoneId[avId] + self.ignore(DistributedObjectAI.staticGetLogicalZoneChangeEvent(avId)) + self.announceToonEnterPartyZone(avId, hostId, zoneId) + + def announceToonEnterPartyZone(self, avId, hostId, zoneId): + """ + announce to the rest of the system that a toon is entering a party + """ + DistributedPartyManagerAI.notify.debug('announceToonEnterPartyZone: %s %s %s' % (avId, hostId, zoneId)) + + av = self.air.doId2do[avId] + + # Toonup + av.startToonUp(self.healFrequency) + + if avId == hostId: + # We have to tell the uberdog that we've started a new party so it can + # update all the other AIs with public party info (but only host does this) + if not self.hostAvIdToAllPartiesInfo.has_key(avId): + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'partyHasStartedAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, self.hostAvIdToPartiesRunning[hostId].partyInfo.partyId, self.air.districtId, zoneId, av.getName()] + ) + + # tell host to whisper all his guests that the party has started. + # We have the host do this as host already has access to guest list. + av.sendUpdate( "announcePartyStarted", [self.hostAvIdToPartiesRunning[hostId].partyInfo.partyId] ) + + messenger.send(self.getAvEnterEvent(), [avId, hostId, zoneId]) + # Tell the uberdog about the new count + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'toonHasEnteredPartyAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [hostId], + ) + + def announceToonExitPartyZone(self, avId, hostId, zoneId): + """ announce to the rest of the system that a toon is exiting + a party """ + EstateManagerAI.notify.debug('announceToonExitPartyZone: %s %s %s' % + (avId, hostId, zoneId)) + messenger.send(self.getAvExitEvent(avId)) + messenger.send(self.getAvExitEvent(), [avId, hostId, zoneId]) + + # Return a running distributed party based on the Zone id: + def getRunningPartyFromZoneId(self, zoneId): + hostId = self.zoneIdToHostAvId.get(zoneId) + if hostId: + return self.hostAvIdToPartiesRunning.get(hostId) + return None + + # Send Party Zone information back to the client + def __sendPartyZoneToClient(self, avId, hostId): + self.notify.warning("__sendPartyZoneToClient called with avId=%d, hostId=%d" % (avId, hostId)) + try: + zoneId = self.avIdToPartyZoneId[avId] + partyId = self.hostAvIdToPartiesRunning[hostId].partyInfo.partyId + self.sendUpdateToAvatarId(avId, "receivePartyZone", [hostId, partyId, zoneId]) + except: + self.notify.warning("__sendPartyZoneToClient : zone did not exist for party host %d, and visitor %d" % (hostId, avId)) + self.sendUpdateToAvatarId(avId, "receivePartyZone", [0, 0, 0]) + + # Send empty Party Zone information back to the client + # This is called, for example, in case a toon teleports to a party not + # running on this shard or if the uberdog doesn't think this party can be + # created. + def __sendNoPartyZoneToClient(self, avId): + self.notify.warning("__sendNoPartyZoneToClient : not sending avId %d to a party." % avId) + self.sendUpdateToAvatarId(avId, "receivePartyZone", [0, 0, 0]) + + def partyInfoOfHostFailedResponseUdToAi(self, hostId): + """ + A host tried to create a party that it wasn't time to create, ie, + something fishy went down. Reply that the party creation failed. + If you want to test parties using magic words, be sure to set + allow-random-party-creation 1 in your config overrides. + """ + self.notify.warning("Host with avId %d tried to create a party it wasn't time for." % hostId) + taskMgr.remove("UberdogTimedOut_%d"%hostId) + self.__sendNoPartyZoneToClient(hostId) + + def partyInfoOfHostResponseUdToAi(self, partyInfoTuple, inviteeIds): + """ + Called by UD after it has gathered the relevant information for this + party. + """ + assert self.notify.debugStateCall(self) + taskMgr.remove("UberdogTimedOut_%d"%partyInfoTuple[1]) + # Note this method will send the zone info back to the client + # after it creates the party + partyInfo = PartyInfoAI(*partyInfoTuple) + # request an available zone to have this party in + zoneId = self.air.allocateZone() + + # remove any references to host in parties he's currently attending + # in case he start a party within another party + self.__exitParty(partyInfo.hostId) + + # Host is attending the party, too + self.setPartyZoneId(partyInfo.hostId, zoneId) + + # start a ref count for this zone id + self.zoneIdToGuestAvIds[zoneId] = [] + self.zoneIdToHostAvId[zoneId] = partyInfo.hostId + self.handleGetPartyInfo(partyInfo, inviteeIds) + + def __addReferences(self, senderId, hostId): + DistributedPartyManagerAI.notify.debug("__addReferences : senderId = %s hostId = %s" % (senderId, hostId)) + party = self.hostAvIdToPartiesRunning.get(hostId) + if party is not None: + self.setPartyZoneId(senderId, party.zoneId) + ref = self.zoneIdToGuestAvIds.get(party.zoneId) + if ref is not None: + if not senderId in ref: + ref.append(senderId) + else: + self.zoneIdToGuestAvIds[party.zoneId] = [senderId] + + def __removeReferences(self, avId, zoneId): + try: + self.clearPartyZoneId(avId) + self.zoneIdToGuestAvIds[zoneId].remove(avId) + except: + DistributedPartyManagerAI.notify.debug("we weren't in the zoneIdToGuestAvIds for %s." % zoneId) + pass + + def setPartyZoneId(self, avId, zoneId): + self.avIdToPartyZoneId[avId] = zoneId + frame = sys._getframe(1) + lineno = frame.f_lineno + defName = frame.f_code.co_name + DistributedPartyManagerAI.notify.debug("%s(%s):Added %s:%s" % (defName, lineno, avId, zoneId)) + + def clearPartyZoneId(self, avId, zoneIdFromClient = None): + """Clear avId to partyzoneId dict, if zoneIdFromClient is not none, do it only if they match.""" + if not self.avIdToPartyZoneId.has_key(avId): + return + zoneId = self.avIdToPartyZoneId[avId] + frame = sys._getframe(1) + lineno = frame.f_lineno + defName = frame.f_code.co_name + removeFromDict = False + if zoneIdFromClient != None: + if zoneId == zoneIdFromClient: + removeFromDict =True + else: + self.notify.debug("zoneIdFromClient=%s AI thinks he's at zone %s, not removing" % + (zoneIdFromClient, zoneId)) + else: + removeFromDict = True + + if removeFromDict: + DistributedPartyManagerAI.notify.debug("%s(%s):Removed %s:%s" % (defName, lineno, avId, self.avIdToPartyZoneId[avId])) + del self.avIdToPartyZoneId[avId] + + def handleGetPartyInfo(self, partyInfo, inviteeIds): + DistributedPartyManagerAI.notify.debug("handleGetPartyInfo for host %s" % partyInfo.hostId) + # this function is called after the party data is pulled + # from the database. the DistributedPartyAI object is initialized + # here + + # Note: this function is only called by the host of the party. + + # there is a chance that the owner will already have left (by + # closing the window). We need to handle that gracefully. + if not self.avIdToPartyZoneId.has_key(partyInfo.hostId): + self.notify.warning("Party Zone info was requested, but the guest left before it could be recived: %d" % estateId) + return + + # create the DistributedPartyAI object for this hostId + if self.hostAvIdToPartiesRunning.has_key(partyInfo.hostId): + self.notify.warning("Already have distobj %s, not generating again" % (partyInfo.partyId)) + else: + self.notify.info('start party %s init, owner=%s, frame=%s' % + (partyInfo.partyId, partyInfo.hostId, globalClock.getFrameCount())) + + partyZoneId = self.avIdToPartyZoneId[partyInfo.hostId] + partyAI = DistributedPartyAI(self.air, partyInfo.hostId, partyZoneId, partyInfo, inviteeIds) + + partyAI.generateOtpObject( + parentId=self.air.districtId, + zoneId=partyZoneId, + ) + partyAI.initPartyData() + self.hostAvIdToPartiesRunning[partyInfo.hostId] = partyAI + + self.__addReferences(partyInfo.hostId, partyInfo.hostId) + + # We need to kick guests out when the party is closing and not allow + # anyone else in. Send a message to guests to leave + # Also, alert uberdog. + taskMgr.doMethodLater( + PartyGlobals.DefaultPartyDuration * 3600.0, + self.__setPartyEnded, + "DistributedPartyManagerAI_PartyEnding_%d" % partyZoneId, + [partyInfo.hostId,partyZoneId] + ) + + # Boot the guests + taskMgr.doMethodLater( + PartyGlobals.DefaultPartyDuration * 3600.0 + PartyGlobals.DelayBeforeAutoKick, + self.__bootGuests, + "DistributedPartyManagerAI_BootGuests_%d" % partyZoneId, + [partyInfo.hostId,partyZoneId] + ) + + # We need to clean this party up after everybody is gone + taskMgr.doMethodLater( + PartyGlobals.DefaultPartyDuration * 3600.0 + PartyGlobals.DelayBeforeAutoKick + 10.0, + self.__cleanupParty, + "DistributedPartyManagerAI_CleanUpPartyZone_%d" % partyZoneId, + [partyInfo.hostId,partyZoneId] + ) + + self.notify.info('Finished creating party : partyId %s init, host = %s' % (partyInfo.partyId, partyInfo.hostId)) + + # Now that the zone is set up, send the notification back to + # the client. + zoneId = self.avIdToPartyZoneId[partyInfo.hostId] + self.__sendPartyZoneToClient(partyInfo.hostId, partyInfo.hostId) + self.__waitForToonToEnterParty(partyInfo.hostId, partyInfo.hostId, zoneId) + + def requestShardIdZoneIdForHostId(self, hostId): + """ + Request from either a host of an already started party or a guest of + a party for the shardId and zoneId of that host's party. + """ + senderId = self.air.getAvatarIdFromSender() + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + shardId = self.hostAvIdToAllPartiesInfo[hostId][1] + zoneId = self.hostAvIdToAllPartiesInfo[hostId][2] + self.sendUpdateToAvatarId(senderId, 'sendShardIdZoneIdToAvatar', [shardId, zoneId]) + else: + # The host's id is not in our dictionary... that most likely means + # that the AI server has crashed, send back 0 + self.sendUpdateToAvatarId(senderId, 'sendShardIdZoneIdToAvatar', [0, 0]) + + ## ----------------------------------------------------------- + ## Cleanup and exit functions + ## ----------------------------------------------------------- + + def exitParty(self, zoneId): + senderId = self.air.getAvatarIdFromSender() + DistributedPartyManagerAI.notify.debug("exitParty(%s)" % senderId) + # This function is called from client in the normal case, + # such as teleporting out, door out, exiting the game, etc + self.__exitParty(senderId, zoneId) + + def __handleUnexpectedExit(self, avId): + DistributedPartyManagerAI.notify.debug("we got an unexpected exit on av: %s: deleting." % avId) + taskMgr.remove("estateToonUp-" + str(avId)) + if avId in self.avIdEnteringPartyToHostIdZoneId: + self.__toonLeftBeforeArrival(avId) + if self.avIdToPartyZoneId.has_key(avId): + self.__exitParty(avId) + else: + DistributedPartyManagerAI.notify.debug("unexpected exit and %s is not in avIdToPartyZoneId" % avId) + return None + + def __exitParty(self, avId, zoneIdFromClient = None): + DistributedPartyManagerAI.notify.debug("__exitParty(%d)" % avId) + DistributedPartyManagerAI.notify.info("__exitParty(%d)" % avId) + # This is called whenever avId leaves a party. + # Just remove references of avId from the party + avZoneId = self.avIdToPartyZoneId.get(avId) + if zoneIdFromClient != None: + # We get a very weird case when you're starting a party from another party + if avZoneId != zoneIdFromClient: + self.notify.debug("overriding avZoneId to %s" % zoneIdFromClient) + avZoneId = zoneIdFromClient + + partyId = -1 + isPlanning = True + if avZoneId is not None: + isPlanning = False + self.clearPartyZoneId(avId, zoneIdFromClient) + if self.zoneIdToGuestAvIds.has_key(avZoneId): + if avId in self.zoneIdToGuestAvIds[avZoneId]: + self.notify.debug("removing guest %d from zone %d" % (avId, avZoneId)) + self.zoneIdToGuestAvIds[avZoneId].remove(avId) + else: + DistributedPartyManagerAI.notify.debug("wasn't in zoneIdToGuestAvIds list: %s, %s" % (avZoneId, avId)) + else: + DistributedPartyManagerAI.notify.debug("wasn't in zoneIdToGuestAvIds: %s, %s" % (avZoneId, avId)) + # Tell the uberdog that this host's party has lost a guest + hostAvId = self.zoneIdToHostAvId.get(avZoneId) + if hostAvId: + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'toonHasExitedPartyAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.zoneIdToHostAvId[avZoneId]] + ) + info = self.hostAvIdToAllPartiesInfo.get(hostAvId) + if info: + partyId = info[7] + + else: + self.notify.warning("__exitParty() avZoneId=%d not in self.zoneIdToHostAvId" % avZoneId) + + else: + DistributedPartyManagerAI.notify.debug("__exitParty can't find zone for %d" % avId) + + totalMoney = -1 + # stop the healing + if self.air.doId2do.has_key(avId): + # Find the avatar + av = self.air.doId2do[avId] + # Stop healing them + av.stopToonUp() + totalMoney = av.getTotalMoney() + + if not isPlanning: + self.air.writeServerEvent("party_exit", partyId,"%d|%d" % (avId, totalMoney)) + + def freeZoneIdFromPlannedParty(self, hostId, zoneId): + """ Free a zone that was allocated for the planning of a party """ + senderId = self.air.getAvatarIdFromSender() + if senderId != hostId: + self.air.writeServerEvent('suspicious', senderId, 'someone else trying to free a zone for this avatar: hostId = %d' % hostId) + return + if self.hostIdToPlanningPartyZoneId.has_key(hostId): + DistributedPartyManagerAI.notify.debug("freeZoneIdFromPlannedParty : freeing zone : hostId = %d, zoneId = %d" % (hostId, zoneId)) + self.air.deallocateZone(self.hostIdToPlanningPartyZoneId[hostId]) + del self.hostIdToPlanningPartyZoneId[hostId] + return + else: + self.notify.warning('suspicious senderId=%d trying to free a zone that this avatar did not allocate: hostId = %d' % (senderId,hostId)) + self.air.writeServerEvent('suspicious', senderId, 'trying to free a zone that this avatar did not allocate: hostId = %d' % hostId) + return + + def __cleanupParty(self, hostId, zoneId): + DistributedPartyManagerAI.notify.debug("__cleanupParty hostId = %d, zoneId = %d" % (hostId, zoneId)) + + self.clearPartyZoneId(hostId) + if self.hostAvIdToClosingPartyZoneId.has_key(hostId): + del self.hostAvIdToClosingPartyZoneId[hostId] + if self.zoneIdToHostAvId.has_key(zoneId): + del self.zoneIdToHostAvId[zoneId] + + # give our zoneId back to the air + self.air.deallocateZone(zoneId) + + # delete party grounds from state server + self.__deleteParty(hostId) + + # stop listening for unexpectedExit + self.ignore(self.air.getAvatarExitEvent(hostId)) + + if self.zoneIdToGuestAvIds.has_key(zoneId): + del self.zoneIdToGuestAvIds[zoneId] + + def __deleteParty(self, hostId): + # remove all our objects from the stateserver + DistributedPartyManagerAI.notify.debug("__deleteParty(hostId=%s)" % hostId) + + # delete from state server + if self.hostAvIdToPartiesRunning.has_key(hostId): + if self.hostAvIdToPartiesRunning[hostId] != None: + self.hostAvIdToPartiesRunning[hostId].destroyPartyData() + DistributedPartyManagerAI.notify.debug('DistributedPartyAI requestDelete, doId=%d' % getattr(self.hostAvIdToPartiesRunning[hostId], 'doId')) + self.hostAvIdToPartiesRunning[hostId].requestDelete() + del self.hostAvIdToPartiesRunning[hostId] + + def __bootGuests(self, hostId, zoneId): + DistributedPartyManagerAI.notify.debug("__bootGuests (hostId=%s zoneId=%s)" % (hostId,zoneId)) + try: + # we need a copy of the list, otherwise we skip some people in booting out + visitors = self.zoneIdToGuestAvIds[zoneId][:] + for avId in visitors: + # people get left behind in the party if we boot the host first + if avId == hostId: + continue + self.notify.debug("booting %d from zone %d host=%d" %(avId, zoneId, hostId)) + self.__bootAv(avId, zoneId, hostId) + if hostId in visitors: + self.__bootAv(hostId, zoneId, hostId) + self.notify.debug("booting %d from zone %d host=%d" %(avId, zoneId, hostId)) + except: + # refCount might have already gotten deleted + pass + + def __bootAv(self, avId, zoneId, hostId): + # Let anyone who might be doing something with this avatar in the party + assert self.notify.debugStateCall(self) + messenger.send("bootAvFromParty-"+str(avId)) + self.__exitParty(avId) + # Pass the message to the client, who will pass it to the PartyHood + self.sendUpdateToAvatarId(avId, "sendAvToPlayground", [avId, 1]) # 0 is a warning, 1 is final + + def getPartyEndedEvent(self, hostId): + return 'partyEnded-%s' % hostId + + def __setPartyEnded(self, hostId, zoneId): + """ + This party has ended, so no one can go to it, and we'll + warn people there to get out! + """ + DistributedPartyManagerAI.notify.debug("__setPartyEnded (hostId=%s zoneId=%s)" % (hostId,zoneId)) + # Tell uberdog about it so it can update hostAvIdToAllPartiesInfo + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePartyStatusRequestAiToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [self.doId, self.hostAvIdToPartiesRunning[hostId].partyInfo.partyId, PartyGlobals.PartyStatus.Finished] + ) + + self.hostAvIdToClosingPartyZoneId[hostId] = zoneId + + messenger.send(self.getPartyEndedEvent(hostId)) + + # Warn guests they have to leave + guests = self.zoneIdToGuestAvIds.get(zoneId) + if guests: + for avId in guests: + # Pass the message to the client, who will pass it to the PartyHood + self.sendUpdateToAvatarId(avId, "sendAvToPlayground", [avId, 0]) # 0 is a warning, 1 is final + + self.hostAvIdToPartiesRunning[hostId].b_setPartyState(True) + + def testMsgUdToAllAi(self): + """Try receiving a UD to all AI msg.""" + self.notify.debugStateCall(self) + pass + + def forceCheckStart(self): + """Force the uberdog party manager to do an immediate check for which parties can start.""" + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'forceCheckStart', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [] + ) + + def allowUnreleasedServer(self): + """Return do we allow player to buy unreleased activities and decorations on the client.""" + return self.allowUnreleased + + def setAllowUnreleaseServer(self, newValue): + """Set if we allow player to buy unreleased activities and decorations on the client.""" + self.allowUnreleased = newValue + + def toggleAllowUnreleasedServer(self): + """Toggle allow unreleased on the client, then return the new value.""" + self.allowUnreleased = not self.allowUnreleased + return self.allowUnreleased + + def canBuyParties(self): + """Return do we allow player to buy parties.""" + return self.canBuy + + def setCanBuyParties(self, newValue): + """Set if we allow player to buy unreleased activities and decorations on the client.""" + self.canBuy= newValue + + def toggleCanBuyParties(self): + """Toggle allow unreleased on the client, then return the new value.""" + self.canBuy= not self.canBuy + return self.canBuy + + def partyManagerUdStartingUp(self): + """The uberdog is restarting, tell it about parties running on this district.""" + for hostId in self.hostAvIdToAllPartiesInfo: + if hostId not in self.hostAvIdToPartiesRunning: + self.notify.warning('hostId %d is in self.hostAvIdToAllPartiesInfo but not in self.hostAvIdToPartiesRunning' % hostId) + # really check we have a DistributedPartyAI for the host + continue + partyInfo = self.hostAvIdToAllPartiesInfo[hostId] + shardId = partyInfo[1] + if shardId == self.air.districtId: + startTime = partyInfo[0] + zoneId = partyInfo[2] + isPrivate = partyInfo[3] + numberOfGuests = partyInfo[4] + hostName = partyInfo[5] + activityIds = partyInfo[6] + partyId = partyInfo[7] + + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'updateAllPartyInfoToUd', + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + [hostId, startTime, shardId, zoneId, isPrivate, numberOfGuests, + hostName, activityIds, partyId] + ) + + + def magicWordEnd(self, senderId): + """End the party prematurely as the sender said a magic word.""" + # first test if we are hosting a party + partyZoneId = self.avIdToPartyZoneId.get(senderId) + if not partyZoneId: + return "%d not in self.avIdToPartyZoneId" % senderId + + hostId = self.zoneIdToHostAvId.get(partyZoneId) + if hostId != senderId: + return "sender %d is not host (%d is)" % (senderId, hostId) + + # nuke the old tasks + taskMgr.remove("DistributedPartyManagerAI_PartyEnding_%d"%partyZoneId) + taskMgr.remove("DistributedPartyManagerAI_BootGuests_%d"%partyZoneId) + taskMgr.remove("DistributedPartyManagerAI_CleanUpPartyZone_%d"%partyZoneId) + + # now start up new tasks to end the party right now + taskMgr.doMethodLater(0.1, self.__setPartyEnded, "DistributedPartyManagerAI_PartyEnding_%d"%partyZoneId, [hostId,partyZoneId]) + kickDelay = simbase.config.GetInt("party-kick-delay",PartyGlobals.DelayBeforeAutoKick) + taskMgr.doMethodLater(0.1 + kickDelay, self.__bootGuests, "DistributedPartyManagerAI_BootGuests_%d"%partyZoneId, [hostId,partyZoneId]) + taskMgr.doMethodLater(0.1 + kickDelay + 10.0, self.__cleanupParty, "DistributedPartyManagerAI_CleanUpPartyZone_%d"%partyZoneId, [hostId,partyZoneId]) + + return ("Party Zone %d Ending Soon" % partyZoneId) + + + def deductMoneyFromOfflineToon(self, toonId, cost): + """Deduct the cost of the party from an offline toon.""" + # it's possible for someone to alt f4 out in between the time it takes for + # the uberdog to respond to AI that buying the party was a success + ag = RepairAvatars.AvatarGetter(self.air) + event = 'gotOfflineToon-%s' % toonId + ag.getAvatar(toonId, fields=['setName', 'setMaxHp', + 'setMaxMoney', + 'setMaxBankMoney', + 'setMoney', + 'setBankMoney'], + event = event) + self.acceptOnce(event, Functor(self.gotOfflineToon, cost = cost, toonId = toonId)) + + def gotOfflineToon(self, toon, cost, toonId): + """Handle a response to our request to get an offline toon, deduct the money from him.""" + if toon is None: + # prevent mem leak + self.notify.warning("gotOfflineToon - toon %s not found. buying a party for free!cost=%s" + % (toonId, cost)) + self.air.writeServerEvent('suspicious', toonId, + "gotOfflineToon - toon %s not found. buying a party for free!cost=%s" + % (toonId, cost)) + return + + totalMoney = toon.getTotalMoney() + result = toon.takeMoney(cost, bUseBank = True) + if result: + newTotalMoney = toon.getTotalMoney() + self.notify.info("gotOfflineToon - deducting %s from offline toon %s newTotalMoney=%s" + % (cost, toonId,newTotalMoney)) + else: + self.notify.warning("gotOfflineToon - Host %s got away with buying a party he can't afford! totalMoney=%s cost=%s" + % (toonId,totalMoney, cost)) + self.air.writeServerEvent('suspicious', toonId, + "gotOfflineToon - Host %s got away with buying a party he can't afford! totalMoney=%s cost=%s" + % (toonId,totalMoney, cost)) + + + # takeMoney is doing a b_setMoney, so that gets written into the otp database + # db = DatabaseObject.DatabaseObject(self.air, toon.doId) + # db.storeObject(toon, ["setMoney", "setBankMoney"]) + + # prevent mem leak + # as far as I can tell we don't need this, ~aigarbage reports 0 cycles + # toon.patchDelete() diff --git a/toontown/src/uberdog/DistributedPartyManagerUD.py b/toontown/src/uberdog/DistributedPartyManagerUD.py new file mode 100644 index 0000000..5173180 --- /dev/null +++ b/toontown/src/uberdog/DistributedPartyManagerUD.py @@ -0,0 +1,1106 @@ +import time +import math + +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.AsyncRequest import AsyncRequest +from otp.distributed import OtpDoGlobals +from toontown.uberdog import PartiesUdConfig +from toontown.uberdog.PartiesUdLog import partiesUdLog +from toontown.uberdog.ttMaildb import ttMaildb +from toontown.toonbase import ToontownGlobals +from toontown.parties import PartyGlobals +from toontown.parties import PartyUtils +from toontown.uberdog.ttPartyDb import ttPartyDb +from toontown.uberdog.ttInviteDb import ttInviteDb +from toontown.ai.ToontownAIMsgTypes import PARTY_MANAGER_UD_TO_ALL_AI +from datetime import timedelta # Used for testing, to create random test party +from datetime import datetime # Used for testing, to create random test party + +class DistributedPartyManagerUD(DistributedObjectGlobalUD): + """UD side class for the party manager.""" + + # WARNING this is a global OTP object + # DistributedPartyManagerAI is NOT! + # Hence the use of sendUpdateToDoId when sending back to AI + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPartyManagerUD") + + def __init__(self, air): + DistributedObjectGlobalUD.__init__(self, air) + self.printlog = partiesUdLog("PartiesUdMonitor","localhost",12346) + user = uber.config.GetString("mysql-user", '') + passwd = uber.config.GetString("mysql-passwd",'') + + # avId is key, if present, avatar is online + self.isAvatarOnline = {} + + self.hostAvIdToAllPartiesInfo = {} + # 0 1 2 3 + # hostAvId to ( shardId, zoneId, isPrivate, number of toons there, + # 4 5 6 7 + # hostName,activityIds, actualStartTime, partyId) + + # The uberdog has the database, and knows when every party in every shard + # is allowed to start, or rather, when the 'go' button is activated. So, + # every 15 minutes (parties can only start on increments of 15 minutes) + # we'll check and see what parties are allowed to start and make the calls + # to enable their go buttons. We'll do the 1st check a minute in... + taskMgr.doMethodLater(60, self._checkForPartiesStarting, "DistributedPartyManagerUD_checkForPartiesStarting" ) + + if not user: + user = PartiesUdConfig.ttDbUser + if not passwd: + passwd = PartiesUdConfig.ttDbPasswd + + self.partyDb = ttPartyDb(host=uber.mysqlhost, + port=PartiesUdConfig.ttDbPort, + user = user, + passwd = passwd, + db=PartiesUdConfig.ttDbName) + + self.inviteDb = ttInviteDb(host=uber.mysqlhost, + port=PartiesUdConfig.ttDbPort, + user = user, + passwd = passwd, + db=PartiesUdConfig.ttDbName) + + # in minutes, how often do we check if a party can start + self.startPartyFrequency = uber.config.GetFloat('start-party-frequency', PartyGlobals.UberdogCheckPartyStartFrequency) + + # The uberdog has the database, we need to check if party has been started but never finished + # We'll do the 1st check a 1 second in... + self.partiesSanityCheckFrequency = uber.config.GetInt('parties-sanity-check-frequency', + PartyGlobals.UberdogPartiesSanityCheckFrequency) + taskMgr.doMethodLater(1, self._sanityCheckParties, "DistributedPartyManagerUD_sanityCheckParties") + + def announceGenerate(self): + DistributedObjectGlobalUD.announceGenerate(self) + self.accept("avatarOnlinePlusAccountInfo", self.avatarOnlinePlusAccountInfo, []) + self.accept("avatarOffline", self.avatarOffline, []) + # assuming we are restarting, tell all the AIs so they can reply back with their + # currently running parties + self.sendUpdateToAllAis("partyManagerUdStartingUp", []) + + + def avatarLoggedIn(self, avatarId): + """Handle an avatar just logging in.""" + # Note this is no longer sent by the AI but is instead in response to + # avatarOnlinePlusAccountInfo from otp_server + # for now we get all the invites, then send them across the wire to the client. + DistributedPartyManagerUD.notify.debug( "avatarLoggedIn( avaterId=%d )" % avatarId ) + # we are blasting everything for now + partyIds, partyInfo = self._updateInvites( avatarId ) + + # we've sent invites, send party details related to those invites + self._updateInvitedToParties( avatarId, partyIds, partyInfo ) + + # send out the details of the parties he's hosting + hostedPartyIds, hostedPartyInfo = self._updateHostedParties( avatarId ) + + # send out replies to his parties + self._updatePartyReplies(avatarId, hostedPartyIds, hostedPartyInfo) + + def addParty(self, pmDoId, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, inviteeIds, costOfParty): + """Add a party to the the invite and party dbs.""" + DistributedPartyManagerUD.notify.debug( "addParty( hostId=%d, startTime=%s, endTime=%s, isPrivate=%s, inviteTheme=%s, invitees=%s... )" %(hostId, startTime, endTime, isPrivate, PartyGlobals.InviteTheme.getString(inviteTheme),str(inviteeIds)) ) + putSucceeded = self.partyDb.putParty(hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, PartyGlobals.PartyStatus.Pending) + if not putSucceeded: + DistributedPartyManagerUD.notify.warning( "putParty call for party with hostID %s failed." % hostId ) + # TODO having too many parties is not the only reason putParty can fail + # add those cases too + # case 1: too many decors + self.sendAddPartyResponse(pmDoId, hostId, PartyGlobals.AddPartyErrorCode.TooManyHostedParties) + return + + errorCode = PartyGlobals.AddPartyErrorCode.AllOk + partiesTuple = self.partyDb.getPartiesOfHost(hostId) + if len(partiesTuple) > 0: + partyId = partiesTuple[-1]['partyId'] # TODO-parties: is getting the -1 index guranteed to get the party we just pushed to the database? + # send out the details of the parties he's hosting + hostedPartyIds, hostedPartyInfo = self._updateHostedParties(hostId) + + # Send out updates to invitees + for inviteeId in inviteeIds: + self.inviteDb.putInvite(partyId, inviteeId) + if self.isOnline(inviteeId): + # update invitee's invites + partyIds, partyInfo = self._updateInvites( inviteeId ) + # update invitee's InvitedTo parties + self._updateInvitedToParties( inviteeId, partyIds, partyInfo ) + + # send out replies to his parties + self._updatePartyReplies(hostId, [partyId], hostedPartyInfo) + else: + DistributedPartyManagerUD.notify.warning( "Unable to find a party for hostId %s in the party database." % hostId ) + errorCode = PartyGlobals.AddPartyErrorCode.DatabaseError + self.sendAddPartyResponse(pmDoId, hostId, errorCode, costOfParty) + + def markInviteAsReadButNotReplied( self, partyManagerDoId, inviteKey): + """Just mark the invite as read in the database.""" + invite = self.inviteDb.getOneInvite(inviteKey) + if not invite: + # how the heck did this happen, inviteKey isn't there + DistributedPartyManagerUD.notify.warning('markInviteAsReadButNotReplied inviteKey=%s not found in inviteDb' % inviteKey) + return + + # verify the party is still there + partyId = invite[0]['partyId'] + party = self.partyDb.getParty(partyId) + if not party: + return + + updateResult = self.inviteDb.updateInvite(inviteKey, PartyGlobals.InviteStatus.ReadButNotReplied) + self.updateHostAndInviteeStatus(inviteKey, partyId, invite, party, PartyGlobals.InviteStatus.ReadButNotReplied ) + + def updateHostAndInviteeStatus(self, inviteKey, partyId, invite, party, newStatus): + """Tell the invitee and host toons of the change in inviteStatus.""" + # tell the Invitee DistributedToon + inviteeId = invite[0]['guestId'] + + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::updateInvite( inviteKey=%s, newStatus=%s ) across the network with inviteeId %d." %(inviteKey, PartyGlobals.InviteStatus.getString(newStatus), inviteeId ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "updateInvite", + inviteeId, + [inviteKey, newStatus], + ) + + # tell the host, he might not be logged in + hostId = party[0]['hostId'] + + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::updateReply( partyId=%d, inviteeId=%d, newStatus=%s ) across the network with hostId %d." %(partyId, inviteeId, PartyGlobals.InviteStatus.getString(newStatus), hostId ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "updateReply", + hostId, + [partyId, inviteeId, newStatus], + ) + + def respondToInvite(self, partyManagerDoId, mailboxDoId, context, inviteKey, newStatus): + """Handle accepting/rejecting an invite.""" + DistributedPartyManagerUD.notify.debug( "respondToInvite( partyManagerDoId=%d, mailboxDoId=%d, ..., inviteKey=%d, newStatus=%s )" %(partyManagerDoId, mailboxDoId, inviteKey, PartyGlobals.InviteStatus.getString(newStatus) ) ) + replyToChannelAI = self.air.getSenderReturnChannel() + retcode = ToontownGlobals.P_InvalidIndex + invite = self.inviteDb.getOneInvite(inviteKey) + if not invite: + # how the heck did this happen, inviteKey isn't there + DistributedPartyManagerUD.notify.warning('inviteKey=%s not found in inviteDb' % inviteKey) + self.air.sendUpdateToDoId( + "DistributedPartyManager", + "respondToInviteResponse", + partyManagerDoId, + [mailboxDoId, context, inviteKey, retcode, newStatus], + ) + return + + # verify the party is still there + partyId = invite[0]['partyId'] + party = self.partyDb.getParty(partyId) + if not party: + self.air.sendUpdateToDoId( + "DistributedPartyManager", + "respondToInviteResponse", + partyManagerDoId, + [mailboxDoId, context, inviteKey, ToontownGlobals.P_PartyNotFound, newStatus], + ) + return + + # we have a valid party and invite, update the status + # TODO updateResult is always empty, do we need to verify the update took? + updateResult = self.inviteDb.updateInvite(inviteKey, newStatus) + + self.air.sendUpdateToDoId( + "DistributedPartyManager", + "respondToInviteResponse", + partyManagerDoId, + [mailboxDoId, context, inviteKey, ToontownGlobals.P_ItemAvailable, newStatus] + ) + + # tell the invitee and host he accepted/rejected + self.updateHostAndInviteeStatus(inviteKey, partyId, invite, party, newStatus) + + def sendAddPartyResponse(self, pmDoId, hostId, errorCode, costOfParty=0): + """Tell the AI if all went well or if there's a problem adding the party.""" + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'addPartyResponseUdToAi', + pmDoId, + [hostId, errorCode, costOfParty], + ) + + def _updateInvites(self, avatarId): + """ + Push invites and setInviteMailNotify across to the DistributedToon. + + Returns a list of prioritized partyIds and the partyInfo that avatarId is invited to. + """ + DistributedPartyManagerUD.notify.debug( "_updateInvites( avatarId=%d )" % avatarId ) + + invitesTuple = self.inviteDb.getInvites(avatarId) + DistributedPartyManagerUD.notify.debug( "Found %d invites for avatarId %d in the invite database." % (len(invitesTuple), avatarId) ) + + # with 8 bytes inviteKey, 8 bytes partyId, 1 byte status = 17 bytes for the 1 invite + # 64kb / 17 = 3855 + # the party info related to these invites is the limiting factor + # However cancelled parties will show up in this list + # if we really want to be 100% sure we can pull the parties from the database + # and examine them one by one. + + # But an extremely large number should cover it, say 1000 + invitesTuple = invitesTuple[-PartyGlobals.MaxSetInvites:] + + # ok we really need to examine the parties + # since we need to figure out the correct value for inviteMailNotify + # we can have an invite that's not read, so it will trigger as a new invite in the + # mailbox, but since it's so far in the future the partyInvitedTo is not sent + # we get the case of a mailbox being flagged but having nothing in it! + partyIds = [inviteInfo['partyId'] for inviteInfo in invitesTuple] + + prioritizedPartyIds, prioritizedPartyInfo = self.reprioritizeParties(partyIds, PartyGlobals.MaxSetPartiesInvitedTo) + + formattedInvites = [] + partyIds = [] + numOld = 0 + numNew = 0 + for item in invitesTuple: + partyId = item['partyId'] + if partyId not in prioritizedPartyIds: + # skip this invite, too far in the past or in the future + continue + inviteKey = item['inviteId'] + status = item['statusId'] + if status == PartyGlobals.InviteStatus.NotRead: + numNew += 1 + elif status == PartyGlobals.InviteStatus.ReadButNotReplied: + numOld += 1 + # send even the rejected invites, it will show up in invites tab + formattedInvites.append( (inviteKey, partyId, status) ) + partyIds.append(partyId) + + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::setInvites across the network with avatarId %d. Sending %d formatted invites." %(avatarId, len(formattedInvites) ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setInvites", + avatarId, + [formattedInvites], + ) + + # we let DistributedToon.updateInviteMailNotify() properly + # set the right value for inviteMailNotify now instead of uberdog doing it here + + return prioritizedPartyIds, prioritizedPartyInfo + + def reprioritizeParties(self, partyIds, limit): + """Return a prioritized list of partyIds and the associated partyInfo.""" + thresholdTime = self.getThresholdTime() + futurePendingParties = () + futureCancelledParties = () + pastFinishedParties =() + pastCancelledParties = () + prioritizedPartyIds = [] + prioritizedPartyInfo = () + + futurePendingParties = self.partyDb.getPrioritizedParties(\ + partyIds, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + limit, + future = True, + cancelled = False) + self.notify.debug('futurePendingParties = %s' % str(futurePendingParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in futurePendingParties] + prioritizedPartyInfo += futurePendingParties + slotsLeft = limit - len(futurePendingParties) + + if slotsLeft > 0: + futureCancelledParties = self.partyDb.getPrioritizedParties(\ + partyIds, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = True, + cancelled = True) + #self.notify.debug('futureCancelledParties = %s' % str(futureCancelledParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in futureCancelledParties] + prioritizedPartyInfo += futureCancelledParties + slotsLeft -= len(futureCancelledParties) + if slotsLeft > 0: + pastFinishedParties = self.partyDb.getPrioritizedParties(\ + partyIds, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = False, + cancelled = False) + #self.notify.debug('pastFinishedParties = %s' % str(pastFinishedParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in pastFinishedParties] + prioritizedPartyInfo += pastFinishedParties + slotsLeft -= len(pastFinishedParties) + if slotsLeft > 0: + pastCancelledParties = self.partyDb.getPrioritizedParties(\ + partyIds, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = False, + cancelled = True) + #self.notify.debug('pastCancelledParties = %s' % str(pastCancelledParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in pastCancelledParties] + prioritizedPartyInfo += pastCancelledParties + + # prioritizedPartyIds should have everything, prioritizing pending parties in the future + # then cancelled parties in the future, then started parties in the past + # then cancelled parties in the past + + return prioritizedPartyIds, prioritizedPartyInfo + + def _updateInvitedToParties(self, avatarId, passedPartyIds, passedPartyInfo): + """ + Push information about parties that avatarId is invited to across to the DistributedToon. + + partyIds: list of partyIds that avatarId is invited to + """ + partyIds = passedPartyIds + partyInfo = passedPartyInfo + DistributedPartyManagerUD.notify.debug( "_updateInvitedToParties( avatarId=%d, partyIds=%s )" %(avatarId, partyIds) ) + if partyInfo == None: + partyIds, partyInfo = self.reprioritizeParties(passedPartyIds, PartyGlobals.MaxSetPartiesInvitedTo) + + formattedPartiesInvitedTo = [] + formattedPartiesSize = 0 + for partyInfoDict in partyInfo: + formattedPartyInfo = self.getFormattedPartyInfo(partyInfoDict) + partyInfoSize = self._getPartyInfoSize(formattedPartyInfo) + formattedPartiesSize += partyInfoSize + # A full party info can be as big as 383 bytes, and we can only send 16KB over the wire. + # So we clip off any party after 15.8 KB (we leave some leeway for any extra info) + if (formattedPartiesSize < 15800): + formattedPartiesInvitedTo.append(formattedPartyInfo) + else: + break + + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::setPartiesInvitedTo across the network with avatarId %d. Sending %d formatted parties." %(avatarId, len(formattedPartiesInvitedTo) ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setPartiesInvitedTo", + avatarId, + [formattedPartiesInvitedTo], + ) + + def getThresholdTime(self): + """Return the server threshold time for high priority parties.""" + # Some parties could have started recently. + # threshold time, let's get the current server time, subtract default party time + # and then subtract it again to get the threshold + thresholdTime = self.air.toontownTimeManager.getCurServerDateTime() + thresholdTime += timedelta(hours = -(2*PartyGlobals.DefaultPartyDuration )) + + return thresholdTime + + def getFormattedPartyInfo(self, partyInfoDict): + startTime = partyInfoDict['startTime'] + endTime = partyInfoDict['endTime'] + activitiesStr = partyInfoDict['activities'] + formattedActivities = [] + for i in xrange (len(activitiesStr) /4): + oneActivity = (ord(activitiesStr[i*4]), + ord(activitiesStr[i*4 + 1]), + ord(activitiesStr[i*4 + 2]), + ord(activitiesStr[i*4 + 3]) + ) + formattedActivities.append(oneActivity) + decorStr = partyInfoDict['decorations'] + formattedDecors = [] + for i in xrange( len(decorStr) / 4): + oneDecor = (ord(decorStr[i*4]), + ord(decorStr[i*4 + 1]), + ord(decorStr[i*4 + 2]), + ord(decorStr[i*4 + 3]) + ) + formattedDecors.append(oneDecor) + isPrivate = partyInfoDict['isPrivate'] + inviteTheme = partyInfoDict['inviteTheme'] + + return( + partyInfoDict['partyId'], + partyInfoDict['hostId'], + startTime.year, + startTime.month, + startTime.day, + startTime.hour, + startTime.minute, + endTime.year, + endTime.month, + endTime.day, + endTime.hour, + endTime.minute, + isPrivate, + inviteTheme, + formattedActivities, + formattedDecors, + partyInfoDict['statusId'] + ) + + def reprioritizeHostedParties(self, hostId, limit): + """Return a prioritized list of partyIds and the associated partyInfo.""" + thresholdTime = self.getThresholdTime() + futurePendingParties = () + futureCancelledParties = () + pastFinishedParties =() + pastCancelledParties = () + prioritizedPartyIds = [] + prioritizedPartyInfo = () + + futurePendingParties = self.partyDb.getHostPrioritizedParties(\ + hostId, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + limit, + future = True, + cancelled = False) + self.notify.debug('futurePendingParties = %s' % str(futurePendingParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in futurePendingParties] + prioritizedPartyInfo += futurePendingParties + slotsLeft = limit - len(futurePendingParties) + + if slotsLeft > 0: + futureCancelledParties = self.partyDb.getHostPrioritizedParties(\ + hostId, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = True, + cancelled = True) + self.notify.debug('futureCancelledParties = %s' % str(futureCancelledParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in futureCancelledParties] + prioritizedPartyInfo += futureCancelledParties + slotsLeft -= len(futureCancelledParties) + if slotsLeft > 0: + pastFinishedParties = self.partyDb.getHostPrioritizedParties(\ + hostId, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = False, + cancelled = False) + self.notify.debug('pastFinishedParties = %s' % str(pastFinishedParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in pastFinishedParties] + prioritizedPartyInfo += pastFinishedParties + slotsLeft -= len(pastFinishedParties) + if slotsLeft > 0: + pastCancelledParties = self.partyDb.getHostPrioritizedParties(\ + hostId, + thresholdTime.strftime("%Y-%m-%d %H:%M:%S"), + slotsLeft, + future = False, + cancelled = True) + self.notify.debug('pastCancelledParties = %s' % str(pastCancelledParties)) + prioritizedPartyIds += [partyInfo['partyId'] for partyInfo in pastCancelledParties] + prioritizedPartyInfo += pastCancelledParties + + # prioritizedPartyIds should have everything, prioritizing pending parties in the future + # then cancelled parties in the future, then started parties in the past + # then cancelled parties in the past + + return prioritizedPartyIds, prioritizedPartyInfo + + + def _updateHostedParties(self, avatarId): + """ + Push information about parties that avatarId is hosting across to the DistributedToon. + + Returns a list of hostedPartyIds + """ + DistributedPartyManagerUD.notify.debug( "_updateHostedParties( avatarId=%d )" % avatarId ) + hostedPartyIds, hostedParties = self.reprioritizeHostedParties(avatarId, PartyGlobals.MaxSetHostedParties) + + formattedHostedParties = [] + formattedPartiesSize = 0 + for partyInfoDict in hostedParties: + if partyInfoDict['startTime'] and partyInfoDict['endTime']: + + formattedPartyInfo = self.getFormattedPartyInfo(partyInfoDict) + partyInfoSize = self._getPartyInfoSize(formattedPartyInfo) + formattedPartiesSize += partyInfoSize + # A full party info can be as big as 383 bytes, and we can only send 16KB over the wire. + # So we clip off any party after 15.8 KB (we leave some leeway for any extra info) + if (formattedPartiesSize < 15800): + formattedHostedParties.append(formattedPartyInfo) + else: + break + else: + self.notify.warning("partyId=%s has an invalid start or end time startTime=%s endTime=%s" % \ + ( str(partyInfoDict["partyId"]), + str(partyInfoDict['startTime']), + str(partyInfoDict['endTime']) + )) + + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::setHostedParties across the network with avatarId %d. Sending %d formatted parties." %(avatarId, len(formattedHostedParties) ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setHostedParties", + avatarId, + [formattedHostedParties], + ) + + return hostedPartyIds, hostedParties + + def _updatePartyReplies(self, avatarId, hostedPartyIds, hostedParties): + """ + Look up replies to all parties avatarId is hosting from the database + and push them across to DistributedToon. + """ + DistributedPartyManagerUD.notify.debug( "_updatePartyReplies( avatarId=%d, hostedPartyIds=%s )" %(avatarId, hostedPartyIds) ) + thresholdTime = self.getThresholdTime() + formattedRepliesForAllParties = [] + for index, partyId in enumerate( hostedPartyIds): + if index >= len(hostedParties): + self.notify.warning('skipping len(hostedPartyIds)=%d != len(hostedParties)=%d' % (len(hostedPartyIds, len(hostedParties)))) + continue + gotCorrectPartyInfo = True + partyInfoDict = hostedParties[index] + if partyInfoDict['partyId'] != partyId: + gotCorrectPartyInfo = False + for hostedInfo in hostedParties: + if hostedInfo['partyId'] == partyId: + gotCorrectPartyInfo = True + partyInfoDict = hostedInfo + break + + if not gotCorrectPartyInfo: + self.notify.warning('partyId =%d not in hostedPartyIds' % partyId) + continue + + getRepliesForThisParty = True + # we only need replies for parties in the future that are not cancelled + # temporarily turned off as shticker book is not happy + #if partyInfoDict['statusId'] != PartyGlobals.PartyStatus.Cancelled and \ + # thresholdTime < partyInfoDict['startTime']: + # getRepliesForThisParty = True + + if getRepliesForThisParty: + formattedReplies = [] + replies = self.inviteDb.getReplies(partyId) + for oneReply in replies: + formattedReplies.append(( + oneReply['guestId'], + oneReply['statusId'] + )) + formattedRepliesForAllParties.append( (partyId, formattedReplies) ) + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::setPartyReplies across the network with avatarId %d. Sending %d formatted replies." %(avatarId, len(formattedRepliesForAllParties) ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setPartyReplies", + avatarId, + [formattedRepliesForAllParties], + ) + + def changePrivateRequestAiToUd(self, pmDoId, partyId, newPrivateStatus): + """Handle AI requesting to change a party to public or private.""" + errorCode = PartyGlobals.ChangePartyFieldErrorCode.AllOk + + # verify the party is still there + party = self.partyDb.getParty(partyId) + if not party: + errorCode = PartyGlobals.ChangePartyFieldErrorCode.DatabaseError + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePrivateResponseUdToAi', + pmDoId, + [0, partyId, newPrivateStatus, errorCode ], + ) + return + + if party[0]['statusId'] == PartyGlobals.PartyStatus.Started: + errorCode = PartyGlobals.ChangePartyFieldErrorCode.AlreadyStarted + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePrivateResponseUdToAi', + pmDoId, + [party[0]['hostId'], partyId, newPrivateStatus, errorCode ], + ) + return + + + # TODO updateResult is always empty, do we need to verify the update took? + updateResult = self.partyDb.changePrivate(partyId, newPrivateStatus) + + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePrivateResponseUdToAi', + pmDoId, + [ party[0]['hostId'], partyId, newPrivateStatus, errorCode ], + ) + + # TODO do we need to send out partiesInvitedTo again? + + def changePartyStatusRequestAiToUd(self, pmDoId, partyId, newPartyStatus): + """Handle AI requesting to change the party status.""" + DistributedPartyManagerUD.notify.debug("changePartyStatusRequestAiToUd partyId = %s, newPartyStatus = %s" % (partyId, newPartyStatus)) + errorCode = PartyGlobals.ChangePartyFieldErrorCode.AllOk + + # verify the party is still there + party = self.partyDb.getParty(partyId) + if not party: + errorCode = PartyGlobals.ChangePartyFieldErrorCode.DatabaseError + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePartyStatusResponseUdToAi', + pmDoId, + [0, partyId, newPartyStatus, errorCode ], + ) + + return errorCode + partyDict = party[0] + # Check to see if this is a party that has finished + if partyDict["statusId"] == PartyGlobals.PartyStatus.Started and newPartyStatus == PartyGlobals.PartyStatus.Finished: + # It's over, send word to all the AIs so they can update for their public party gates + if self.hostAvIdToAllPartiesInfo.has_key(partyDict["hostId"]): + self.sendUpdateToAllAis("partyHasFinishedUdToAllAi", [partyDict["hostId"]]) + del self.hostAvIdToAllPartiesInfo[partyDict["hostId"]] + + # TODO updateResult is always empty, do we need to verify the update took? + updateResult = self.partyDb.changePartyStatus(partyId, newPartyStatus) + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'changePartyStatusResponseUdToAi', + pmDoId, + [ partyDict['hostId'], partyId, newPartyStatus, errorCode ], + ) + + return errorCode + # TODO do we need to send out partiesInvitedTo again? + + def partyInfoOfHostRequestAiToUd(self, pmDoId, hostId): + """ + A host is trying to create a party, check to see if this host has a + party available and, if so, return the party info and inviteeIds + """ + DistributedPartyManagerUD.notify.debug( "partyInfoOfHostRequestAiToUd( pmDoId=%d, hostId=%d )" %(pmDoId, hostId) ) + + # Query the database, get the info! + hostedParties = self.partyDb.getPartiesOfHostThatCanStart(hostId) + + partyFail = False + partyInfo = None + if len(hostedParties) == 0: + DistributedPartyManagerUD.notify.debug( "partyInfoOfHostRequestAiToUd : party failed because avatar is not hosting any parties." ) + partyFail = True + else: + curServerDateTime = self.air.toontownTimeManager.getCurServerDateTime() + partyInfoDict = hostedParties[0] + # Check to see if this party's startTime is before the current time + # Note: Must make partyInfoDict["startTime"]'s time aware of any + # time offsets by creating a new datetime based on it but + # using the ToontownTimeManager's serverTimeZone info + partyStartTime = partyInfoDict["startTime"] + partyStartTime = datetime( + partyStartTime.year, + partyStartTime.month, + partyStartTime.day, + partyStartTime.hour, + partyStartTime.minute, + tzinfo=self.air.toontownTimeManager.serverTimeZone, + ) + curServerDateTime = datetime( + curServerDateTime.year, + curServerDateTime.month, + curServerDateTime.day, + curServerDateTime.hour, + curServerDateTime.minute, + tzinfo=self.air.toontownTimeManager.serverTimeZone, + ) + if partyStartTime <= curServerDateTime: + pass + else: + DistributedPartyManagerUD.notify.debug("partyInfoOfHostRequestAiToUd : party failed because avatar's party's start time has not passed yet.") + DistributedPartyManagerUD.notify.debug(" startTime = %s, servertime = %s" % (partyStartTime, curServerDateTime)) + partyFail = True + + partyEndTime = partyInfoDict["endTime"] + partyEndTime = datetime( + partyEndTime.year, + partyEndTime.month, + partyEndTime.day, + partyEndTime.hour, + partyEndTime.minute, + tzinfo=self.air.toontownTimeManager.serverTimeZone, + ) + if partyEndTime < curServerDateTime: + DistributedPartyManagerUD.notify.debug("partyInfoOfHostRequestAiToUd : party failed because avatar's party's end time has already passed.") + DistributedPartyManagerUD.notify.debug(" endTime = %s, servertime = %s" % (partyEndTime, curServerDateTime)) + partyFail = True + + if partyFail: + # Something is fishy... this host is not allowed to start this party now or has no parties planned + randomPartyCreationAllowed = uber.config.GetBool('allow-random-party-creation', 0) + if not randomPartyCreationAllowed: + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'partyInfoOfHostFailedResponseUdToAi', + pmDoId, + [hostId], + ) + return + else: + # We're allowed to create random parties for testing purposes + # We'll base the partyId on the current time... + curServerDateTime = self.air.toontownTimeManager.getCurServerDateTime() + partyDuration = timedelta(hours=PartyGlobals.DefaultPartyDuration) + endTime = curServerDateTime + partyDuration + partyId = int(time.time()) + partyId = int(str(partyId)[1:]) + DistributedPartyManagerUD.notify.debug( "partyInfoOfHostRequestAiToUd : Creating random test party with partyId %d" % partyId) + activities = [] + # Let's make one of each activity, arranged in a circle/oval in the party grounds + numActivities = len(PartyGlobals.ActivityIds) + circleStep = (2*math.pi)/numActivities + xRadius = 60.0 + yRadius = 80.0 + for i in range(numActivities): + # these are unsigned 8 bit ints (0-255) + activities += "%s%s%s%s"%( + chr(i), + chr(PartyUtils.convertDistanceToPartyGrid(math.cos(i*circleStep)*xRadius, 0)), + chr(PartyUtils.convertDistanceToPartyGrid(math.sin(i*circleStep)*yRadius, 1)), + chr(PartyUtils.convertDegreesToPartyGrid((i*circleStep*180)/math.pi + 270.0)) + ) + partyInfoDict = { + "partyId" : partyId, + "hostId" : hostId, + "startTime" : curServerDateTime,#.strftime("%Y-%m-%d %H:%M:%S"), + "endTime" : endTime,#.strftime("%Y-%m-%d %H:%M:%S"), + "isPrivate" : False, + "inviteTheme" : 0, + "activities" : activities, + "decorations" : [], + "statusId" : 0, + } + + # Form the list of inviteeIds + inviteeIds = [] + inviteeDict = self.inviteDb.getInviteesOfParty(partyInfoDict["partyId"]) + if inviteeDict is not None: + for info in inviteeDict: + inviteeIds.append(info['guestId']) + + # Send the party info back to the AI who requested it + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'partyInfoOfHostResponseUdToAi', + pmDoId, + [self.getFormattedPartyInfo(partyInfoDict), inviteeIds], + ) + + def _checkForPartiesStarting(self, task): + """ Called every 15 minutes to alert hosts to parties that can start """ + DistributedPartyManagerUD.notify.debug( "_checkForPartiesStarting : Checking for parties starting..." ) + curServerDateTime = self.air.toontownTimeManager.getCurServerDateTime() + # force started parties to finished if they've gone for for too long + self.forceFinishedForStarted() + # first mark as never started parties who went past the end time + self.forceNeverStartedForCanStart() + + partiesStartingTuples = self.partyDb.getPartiesAvailableToStart(curServerDateTime.strftime("%Y-%m-%d %H:%M:%S")) + # Now we know the partyIds and hostIds of parties that can start, let's + # send those directly out to the DistributedToons who can use them! + for infoDict in partiesStartingTuples: + self.notify.debug('%d can start party %d' % (infoDict['hostId'], infoDict['partyId'])) + self.air.sendUpdateToDoId( + "DistributedToon", + "setPartyCanStart", + infoDict['hostId'], + [infoDict['partyId']], + ) + timeToNextCheck = ((self.startPartyFrequency - (curServerDateTime.minute % self.startPartyFrequency)) * 60) - curServerDateTime.second + 1 + self.notify.debug("timeToNextCheck=%s" % timeToNextCheck) + if task: + taskMgr.doMethodLater(timeToNextCheck, self._checkForPartiesStarting, "DistributedPartyManagerUD_checkForPartiesStarting" ) + else: + # if we got here through ~party checkStart, don't schedule another check + self.notify.debug("not rescheduling self._checkForPartiesStarting") + + def _sanityCheckParties(self, task): + """ Called every 60 minutes to check the database for started but never finished parties """ + self.notify.debug( "_sanityCheckParties :..." ) + self.forceFinishedForStarted() + # check is now done every 5 minutes as part of check parties starting + # taskMgr.doMethodLater(self.partiesSanityCheckFrequency * 60, self._sanityCheckParties, "DistributedPartyManagerUD_sanityCheckParties") + + def forceFinishedForStarted(self): + """check the database for started but never finished parties.""" + curServerDateTime = self.air.toontownTimeManager.getCurServerDateTime() + thresholdTime = curServerDateTime + timedelta(hours = -(PartyGlobals.DefaultPartyDuration )) + result = self.partyDb.forceFinishForStarted(thresholdTime.strftime("%Y-%m-%d %H:%M:%S")) + for info in result: + partyId = info['partyId'] + hostId = info['hostId'] + if self.isOnline(hostId): + status = PartyGlobals.PartyStatus.Finished + self.sendNewPartyStatus(hostId, partyId, status) + + def forceNeverStartedForCanStart(self): + curServerDateTime = self.air.toontownTimeManager.getCurServerDateTime() + result = self.partyDb.forceNeverStartedForCanStart(curServerDateTime.strftime("%Y-%m-%d %H:%M:%S")) + for info in result: + partyId = info['partyId'] + hostId = info['hostId'] + if self.isOnline(hostId): + status = PartyGlobals.PartyStatus.NeverStarted + self.sendNewPartyStatus(hostId, partyId, status) + + def sendNewPartyStatus(self, avatarId, partyId, newStatus): + """Tell a toon a party status has changed.""" + DistributedPartyManagerUD.notify.debug( "Calling DistributedToon::sendNewPartyStatus across the network with avatarId %d. partyId=%d newStatus=%d." %(avatarId, partyId, newStatus ) ) + self.air.sendUpdateToDoId( + "DistributedToon", + "setPartyStatus", + avatarId, + [partyId, newStatus], + ) + + def toonHasEnteredPartyAiToUd(self, hostId): + """ This gets called when a toon enters a party. """ + DistributedPartyManagerUD.notify.debug("toonHasEnteredPartyAiToUd : someone entered hostIds %s party"%hostId) + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + self.hostAvIdToAllPartiesInfo[hostId][3] += 1 + if self.hostAvIdToAllPartiesInfo[hostId][3] >= 0: + self.sendUpdateToAllAis("updateToPublicPartyCountUdToAllAi", [hostId, self.hostAvIdToAllPartiesInfo[hostId][3]]) + + def toonHasExitedPartyAiToUd(self, hostId): + """ This gets called when a toon exits a party. """ + DistributedPartyManagerUD.notify.debug("toonHasExitedPartyAiToUd : someone exited hostIds %s party"%hostId) + if self.hostAvIdToAllPartiesInfo.has_key(hostId): + self.hostAvIdToAllPartiesInfo[hostId][3] -= 1 + if self.hostAvIdToAllPartiesInfo[hostId][3] >= 0: + self.sendUpdateToAllAis("updateToPublicPartyCountUdToAllAi", [hostId, self.hostAvIdToAllPartiesInfo[hostId][3]]) + + def partyHasStartedAiToUd(self, pmDoId, partyId, shardId, zoneId, hostName): + """ + This gets called by an AI when a party is started, updates + hostAvIdToAllPartiesInfo for use by other AIs and their public party + gates. + """ + DistributedPartyManagerUD.notify.debug("partyHasStartedAiToUd : pmDoId=%s partyId=%s shardId=%s zoneId=%s hostName=%s " % (pmDoId, partyId, shardId, zoneId, hostName)) + errorCode = self.changePartyStatusRequestAiToUd(pmDoId, partyId, PartyGlobals.PartyStatus.Started) + if errorCode != PartyGlobals.ChangePartyFieldErrorCode.AllOk: + return + party = self.partyDb.getParty(partyId) + partyInfo = party[0] + activityIds = [] + for i in range(len(partyInfo["activities"])): + if i%4 == 0: + activityIds.append(partyInfo["activities"][i]) + # we can not rely on globalClock.getRealTime() as that depends on when the process is started + # and will definitely be different between the uberdog and AI + actualStartTime = long(time.time()) + self.hostAvIdToAllPartiesInfo[partyInfo["hostId"]] = [shardId, zoneId, partyInfo["isPrivate"], 0, hostName, activityIds,actualStartTime, partyId] + self.sendUpdateToAllAis("updateToPublicPartyInfoUdToAllAi", [partyInfo["hostId"], actualStartTime, shardId, zoneId, partyInfo["isPrivate"], 0, hostName, activityIds, partyId]) + self.informInviteesPartyHasStarted(partyId) + + def sendUpdateToAllAis(self, message, args): + dg = self.dclass.aiFormatUpdateMsgType( + message, self.doId, self.doId, self.air.ourChannel, PARTY_MANAGER_UD_TO_ALL_AI, args) + self.air.send(dg) + + def sendTestMsg(self): + """Send a test msg to all AIs to prove it can be done.""" + fieldName = 'testMsgUdToAllAi' + args = [] + dg = self.dclass.aiFormatUpdateMsgType( + fieldName, self.doId, self.doId, self.air.ourChannel, PARTY_MANAGER_UD_TO_ALL_AI, args) + self.air.send(dg) + + def forceCheckStart(self): + """Do an immediate check which parties can start.""" + self._checkForPartiesStarting(None) + + def avatarOnlinePlusAccountInfo(self,avatarId,accountId,playerName, + playerNameApproved,openChatEnabled, + createFriendsWithChat,chatCodeCreation): + # otp server is telling us an avatar just logged in + # this is far far better than having the AI be the one to tell us + assert self.notify.debugCall() + assert avatarId + + self.notify.debug("avatarOnlinePlusAccountInfo") + self.avatarLoggedIn(avatarId) + self.markAvatarOnline(avatarId) + + def avatarOffline(self, avatarId): + """Handle otp_server telling us an avatar is offline.""" + self.markAvatarOffline(avatarId) + + + def markAvatarOnline(self, avatarId): + """Mark an avatar as online.""" + + if self.isAvatarOnline.has_key(avatarId): + assert self.notify.debug( + "\n\nWe got a duplicate avatar online notice %s"%(avatarId,)) + if avatarId and not self.isAvatarOnline.has_key(avatarId): + self.isAvatarOnline[avatarId]=True + + def markAvatarOffline(self, avatarId): + """Mark an avatar as offline.""" + self.isAvatarOnline.pop(avatarId,None) + + def isOnline(self, avatarId): + """Return True if an avatar is online.""" + result = avatarId in self.isAvatarOnline + return result + + def handleInterruptedPartiesOnShard(self, shardId): + """Tell other shards the parties on this shard are gone, set party status back to CanStart.""" + # figure out which partyIds are running on that shard + assert self.notify.debugStateCall(self) + interruptedParties = [] + interruptedPartiesToCanStart = [] + interruptedPartiesToFinished = [] + interruptedHostIds = [] + for hostId in self.hostAvIdToAllPartiesInfo: + partyInfo = self.hostAvIdToAllPartiesInfo[hostId] + if partyInfo[0] == shardId: + interruptedParties.append(partyInfo[7]) + interruptedHostIds.append(hostId) + + # TODO is it possible for a toon to get back online before we hit this point? + # Currently if the current server time is past party end time, he is SOL and can't start a party + curServerTime = self.air.toontownTimeManager.getCurServerDateTime() + interruptedInfo = self.partyDb.getMultipleParties(interruptedParties) + for info in interruptedInfo: + endTime = info["endTime"] + endTime = datetime( + endTime.year, + endTime.month, + endTime.day, + endTime.hour, + endTime.minute, + tzinfo=self.air.toontownTimeManager.serverTimeZone, + ) + if endTime < curServerTime: + interruptedPartiesToFinished.append(info["partyId"]) + else: + interruptedPartiesToCanStart.append(info["partyId"]) + + if interruptedPartiesToCanStart: + self.notify.debug('setting these parties to CanStart %s' % interruptedPartiesToCanStart) + if interruptedPartiesToFinished: + self.notify.debug('setting these parties to Finished %s' % interruptedPartiesToFinished) + + # the toon just got kicked out, he will probably want to go back and restart + self.partyDb.changeMultiplePartiesStatus(interruptedPartiesToCanStart, + PartyGlobals.PartyStatus.CanStart) + self.partyDb.changeMultiplePartiesStatus(interruptedPartiesToFinished, + PartyGlobals.PartyStatus.Finished) + + for index,hostId in enumerate(interruptedHostIds): + if self.isOnline(hostId): + partyId = interruptedParties[index] + if partyId in interruptedPartiesToCanStart: + status = PartyGlobals.PartyStatus.CanStart + else: + status = PartyGlobals.PartyStatus.Finished + self.sendNewPartyStatus(hostId, partyId, status) + + # tell all AI servers the party has finished since it was interrupted + for hostId in interruptedHostIds: + self.sendUpdateToAllAis("partyHasFinishedUdToAllAi", [hostId]) + del self.hostAvIdToAllPartiesInfo[hostId] + + + def partyManagerAIStartingUp(self, pmDoId, shardId): + """An AI server is starting up (or restarting) , send him all public parties running.""" + # if this shardId is starting up, it implies that all parties running on this + # shard have been interrupted + assert self.notify.debugStateCall(self) + # we still need this check just in case uberdog was accidentally shut down + # before the AI servers + self.handleInterruptedPartiesOnShard(shardId) + + for hostId in self.hostAvIdToAllPartiesInfo: + publicInfo = self.hostAvIdToAllPartiesInfo[hostId] + numToons = publicInfo[3] + if numToons <0: + numToons = 0 + self.air.sendUpdateToDoId( + "DistributedPartyManager", + 'updateToPublicPartyInfoUdToAllAi', + pmDoId, + [hostId, publicInfo[6], publicInfo[0], publicInfo[1], publicInfo[2], numToons, + publicInfo[4], publicInfo[5], publicInfo[7]], + ) + + def partyManagerAIGoingDown(self, pmDoId, shardId): + """An AI server is going down, interupt parties appropriately""" + # if this shardId is going down, it implies that all parties running on this + # shard have been interrupted + assert self.notify.debugStateCall(self) + self.handleInterruptedPartiesOnShard(shardId) + + + def updateAllPartyInfoToUd(self, hostId, startTime, shardId, zoneId, isPrivate, numberOfGuests, \ + hostName, activityIds, partyId): + """Handle an AI server telling us all the information about a party running on him.""" + if hostId in self.hostAvIdToAllPartiesInfo: + self.notify.warning("hostId %s already in self.hostAvIdToAllPartiesInfo %s" % ( + hostId, self.hostAvIdToAllPartiesInfo[hostId])) + + self.hostAvIdToAllPartiesInfo[hostId] = [ + shardId, zoneId, isPrivate, numberOfGuests, + hostName, activityIds, startTime, partyId] + + def informInviteesPartyHasStarted(self, partyId): + """The host has started his party, tell the invitees.""" + # WARNING since this is not sent through a ram field, if the toon switches + # districts the AI on the other district could have the party status wrong. + # To do it 100% safe we'd need to do a _updateInvites and _updatePartiesInvitedTo + # but those are fairly expensive operations, let's just try this for now + inviteeDict = self.inviteDb.getInviteesOfParty(partyId) + for info in inviteeDict: + avId = info['guestId'] + if self.isOnline(avId): + self.sendNewPartyStatus( avId, partyId, PartyGlobals.PartyStatus.Started) + + def _getPartyInfoSize(self, partyInfo): + """ + Calculate the size of the party info and return the value in bytes. + This is the format of the party info from toon.dc: + struct party{ + uint64 partyId; - 8 bytes + uint32 hostId; - 4 bytes + uint16 startYear; - 2 bytes + uint8 startMonth; - 1 byte + uint8 startDay; - 1 byte + uint8 startHour; - 1 byte + uint8 startMinute; - 1 byte + uint16 endYear; - 2 bytes + uint8 endMonth; - 1 byte + uint8 endDay; - 1 byte + uint8 endHour; - 1 byte + uint8 endMinute; - 1 byte + uint8 isPrivate; - 1 byte + uint8 inviteTheme; - 1 byte + activity activities[]; - 4 bytes * numberOfActivities + decoration decors[]; - 4 bytes * numberOfDecors + uint8 status; - 1 byte + }; + So the basic party info size is: + partyInfoSize = (27 + 4*numberOfActivities + 4*numberOfDecors) bytes + + Note: We assume that the party info format in toon.dc won't change. + Please change this method and calculation if the format changes. + """ + activities = partyInfo[14] + decors = partyInfo[15] + basePartySize = 27 + numActivities = 0 + numDecors = 0 + + if (type(activities) == type([])): + numActivities = len(activities) + else: + self.notify.warning("partyId=%s has an incorrect partyInfo format for activities" %str(partyInfo[0])) + + if (type(decors) == type([])): + numDecors = len(decors) + else: + self.notify.warning("partyId=%s has an incorrect partyInfo format for decors" %str(partyInfo[0])) + + partyInfoSize = basePartySize + (4 * numActivities) + (4 * numDecors) + return partyInfoSize + \ No newline at end of file diff --git a/toontown/src/uberdog/InGameNewsResponses.py b/toontown/src/uberdog/InGameNewsResponses.py new file mode 100644 index 0000000..96ade05 --- /dev/null +++ b/toontown/src/uberdog/InGameNewsResponses.py @@ -0,0 +1,20 @@ +""" +Constants file that contains XML and misc. codes for award responses +""" + +# --- Begin XML message constants --- + + +setLatestIssueFailureXML = """ + + false + %s + +\r\n""" + +setLatestIssueSuccessXML = """ + + true + %s + +\r\n""" diff --git a/toontown/src/uberdog/LRUlist.py b/toontown/src/uberdog/LRUlist.py new file mode 100644 index 0000000..d9fd5b5 --- /dev/null +++ b/toontown/src/uberdog/LRUlist.py @@ -0,0 +1,56 @@ +class LRUlist: + """ + A list of data that only gets to size cacheSize before removing the + LRU (least recently used) element + """ + def __init__(self, cacheSize = 256): + self.cacheSize = cacheSize + self.dataDictionary = {} + self.keyList = [] + + def __pruneData__(self): + """ + removes the LRU elements beyond cacheSize + """ + while len(self.keyList) > self.cacheSize: + _removeKey = self.keyList[self.cacheSize] + del self.dataDictionary[_removeKey] #optimize + self.keyList = self.keyList[:-1] #optimize + #self.removeData(_removeKey) + + def setCacheSize(self, cacheSize): + """ + sets the cacheSize and prunes the data + """ + self.cacheSize = cacheSize + self.__pruneData__() + + def putData(self, indexKey, data): + #puts data to the list, then prunes the list + self.dataDictionary[indexKey] = data + count = self.keyList.count(indexKey) + for i in range(count): + self.keyList.remove(indexKey) + #if self.keyList.count(indexKey): + # self.keyList.remove(indexKey) + self.keyList.insert(0, indexKey) + self.__pruneData__() + + def removeData(self, indexKey): + #removes a datum + del self.dataDictionary[indexKey] + self.keyList.remove(indexKey) + + def getData(self, indexKey): + + if self.dataDictionary.has_key(indexKey): + #if a datum exists + _returnData = self.dataDictionary[indexKey] + self.removeData(indexKey) + self.putData(indexKey, _returnData) + return _returnData + #retreives, removes, adds, and then returns a datum + else: + # otherwise return "none" + return None + diff --git a/toontown/src/uberdog/PartiesUdConfig.py b/toontown/src/uberdog/PartiesUdConfig.py new file mode 100644 index 0000000..0e1491a --- /dev/null +++ b/toontown/src/uberdog/PartiesUdConfig.py @@ -0,0 +1,34 @@ +from toontown.toonbase import TTLocalizer + +language = TTLocalizer.getLanguage() + +# Log config + +logFatal = True +logError = True +logWarning = True +logLog = True +logInfo = True +logDebug = False +logChat = True +logSecurity = True +logMaxLinesInMemory = 100 + +# DB config +ttDbHost = "localhost" +ttDbPort = 3306 + +if language == 'castillian': + ttDbName = "es_toontownTopDb" +elif language == "japanese": + ttDbName = "jp_toontownTopDb" +elif language == "portuguese": + ttDbName = "br_toontownTopDb" +elif language == "french": + ttDbName = "french_toontownTopDb" +else: + ttDbName = "toontownTopDb" + +ttDbUser = "ttDb_user" +ttDbPasswd = "toontastic2008" + diff --git a/toontown/src/uberdog/PartiesUdLog.py b/toontown/src/uberdog/PartiesUdLog.py new file mode 100644 index 0000000..c49a51e --- /dev/null +++ b/toontown/src/uberdog/PartiesUdLog.py @@ -0,0 +1,116 @@ +import sys +import time +import socket +import Queue +from toontown.uberdog import PartiesUdConfig + +class partiesUdLog: + def __init__(self,name,clHost=None,clPort=6060): + self.name = name + self.clHost = clHost + self.clPort = clPort + + self.inMemLog = Queue.Queue() + + if clHost: + # init UDP stuff + self.sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) + + def timeString(self): + tup = time.localtime() + return "%d-%02d-%02d %02d:%02d:%02d" % (tup[0],tup[1],tup[2],tup[3],tup[4],tup[5]) + + def output(self,level,msg): + str = "%s %s(%s): %s"%(self.timeString(),self.name,level,msg) + print str + self.memLog(str) + sys.stdout.flush() + + def chatoutput(self,msg): + str = "%s %s(chat): %s"%(self.timeString(),self.name,msg) + print str + self.memLog(str) + sys.stdout.flush() + + def memLog(self,str): + self.inMemLog.put(str) + while self.inMemLog.qsize() > PartiesUdConfig.logMaxLinesInMemory: + self.inMemLog.get() + + def getMemLog(self): + res = "" + for i in xrange(self.inMemLog.qsize()): + s = self.inMemLog.get() + res += s + "\n" + self.inMemLog.put(s) + return res + + def remoteLog(self, + eventName, + sourceSys, + sourceAcctId, + sourceAvId, + destSys, + destAcctId, + destAvId, + chatType, + filtered, + chatText): + assert self.clHost is not None + outstr = "%s|%s|%d|%d|%s|%d|%d|%s|%s|%s" % (eventName,sourceSys,sourceAcctId,sourceAvId, + destSys,destAcctId,destAvId,chatType,filtered, + chatText) + self.debug("Remote log entry sent: %s"%outstr) + self.sock.sendto(outstr,(self.clHost,self.clPort)) + + def fatal(self,message): + if PartiesUdConfig.logFatal: + self.output("FATAL",message) + + def error(self,message): + if PartiesUdConfig.logError: + self.output("ERROR",message) + + def security(self,message): + if PartiesUdConfig.logSecurity: + self.output("SECURITY",message) + + def warning(self,message): + if PartiesUdConfig.logWarning: + self.output("warning",message) + + def log(self,message): + if PartiesUdConfig.logLog: + self.output("log",message) + + def info(self,message): + if PartiesUdConfig.logInfo: + self.output("info",message) + + def debug(self,message): + if PartiesUdConfig.logDebug: + self.output("debug",message) + + def chat(self,dest,sender,msg): + if PartiesUdConfig.logChat: + self.chatoutput("WHISPER %d->%d: %s" % (sender,dest,msg)) + if self.clHost: + self.remoteLog("Client Chat",self.name,sender,-1,"",dest,-1,"PEER","N",msg) + + def mail(self,dest,sender,msg): + if PartiesUdConfig.logChat: + self.chatoutput("MAIL %d->%d: %s" % (sender,dest,msg)) + if self.clHost: + self.remoteLog("Client Chat",self.name,sender,-1,"",dest,-1,"MAIL","N",msg) + + def badChat(self,dest,sender,msg): + if PartiesUdConfig.logChat: + self.chatoutput("DIRTYCHAT %d->%d: %s" % (sender,dest,msg)) + if self.clHost: + self.remoteLog("Client Chat",self.name,sender,-1,"",dest,-1,"PEER","Y",msg) + + def badMail(self,dest,sender,msg): + if PartiesUdConfig.logChat: + self.chatoutput("DIRTYMAIL %d->%d: %s" % (sender,dest,msg)) + if self.clHost: + self.remoteLog("Client Chat",self.name,sender,-1,"",dest,-1,"MAIL","Y",msg) diff --git a/toontown/src/uberdog/ScavengerHuntDataStore.py b/toontown/src/uberdog/ScavengerHuntDataStore.py new file mode 100644 index 0000000..338794e --- /dev/null +++ b/toontown/src/uberdog/ScavengerHuntDataStore.py @@ -0,0 +1,114 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.uberdog.DataStore import * + +class ScavengerHuntDataStore(DataStore): + """ + This is a specialized DataStore class designed to handle + the TrickOrTreat holiday event. It responds to two query + types: one to get a toon's current progress in the + scavenger hunt, and one to update it's progress with a + newly completed goal. + """ + + # Define the available query strings + # We'll only need two types of queries. + # If for some strange reason this class is subclassed, + # in the subclass use the following line: + # QueryTypes = ScavengerHuntDataStore.addQueryTypes(['Type_1',...]) + QueryTypes = DataStore.addQueryTypes(['GetGoals', 'AddGoal']) + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ScavengerHuntDataStore') + + def __init__(self, filepath): + """ + filepath is where this store's data will be held on the disk. + This path should be unique to this data store. + """ + DataStore.__init__(self,filepath) + + def handleQuery(self,query): + """ + This function parses the query and performs the + necessary operations on the data store. If a + response is necessary, it is created here. + + Queries are of the format: + (queryId, (avId, goal)) + + queryId is QueryTypes['GetGoals'] or + QueryTypes['AddGoal'] + avId is the toon's id. + goal is the goal they're attempting to complete. + + A queryId of 'GetGoals' will return a list of the toon's + completed goals: + (queryId, (avId, goal, (goal1,goal2,...))) + + A queryId of 'AddGoals' will return a confirmation message: + (qId, (avId,)) + """ + + # extract the queryId + qId, qData = query + + # they're requesting a list of the toon's goals + if qId == self.QueryTypes['GetGoals']: + avId,goal = qData + goals = self.__getGoalsForAvatarId(avId) + # build the return message + return (qId,(avId,goal,goals)) + # they're trying to add a goal to the toon's list + elif qId == self.QueryTypes['AddGoal']: + avId,goal = qData + self.__addGoalToAvatarId(avId,goal) + # confirm the update + return (qId,(avId,)) + + # if not a valid queryId, return an empty result + return None + + + def __addGoalToAvatarId(self, avId, goal): + """ + Add the goal to avId's list of completed goals. + If the goal is already present, this function + has no effect. + """ + + if self.wantAnyDbm: + pAvId = cPickle.dumps(avId) + pGoal = cPickle.dumps(goal) + + pData = self.data.get(pAvId,None) + if pData is not None: + data = cPickle.loads(pData) + else: + data = set() + + data.add(goal) + + pData = cPickle.dumps(data) + self.data[pAvId] = pData + else: + self.data.setdefault(avId,set()) + self.data[avId].add(goal) + self.incrementWriteCount() + + + def __getGoalsForAvatarId(self, avId): + """ + Return a [] of goals for avid. + """ + if self.wantAnyDbm: + pAvId = cPickle.dumps(avId) + pData = self.data.get(pAvId,None) + if pData is not None: + data = list(cPickle.loads(pData)) + else: + data = [] + return data + else: + return list(self.data.get(avId,[])) + + diff --git a/toontown/src/uberdog/ServiceStart.py b/toontown/src/uberdog/ServiceStart.py new file mode 100644 index 0000000..6731b80 --- /dev/null +++ b/toontown/src/uberdog/ServiceStart.py @@ -0,0 +1,218 @@ +""" +Start the Toontown UberDog (Uber Distributed Object Globals server). +""" +import __builtin__ +from direct.task.Task import Task + +class game: + name = "uberDog" + process = "server" +__builtin__.game = game() + +import time +import os +import sys +import getopt + +# Initialize ihooks importer On the production servers, we run genPyCode -n +# meaning no squeeze, so nobody else does this. When we squeeze, the +# unpacker does this for us and it does not hurt to do in either case. +import ihooks +ihooks.install() + +if os.getenv('TTMODELS'): + from pandac.PandaModules import getModelPath, Filename + # In the publish environment, TTMODELS won't be on the model + # path by default, so we always add it there. In the dev + # environment, it'll be on the model path already, but it + # doesn't hurt to add it again. + getModelPath().appendDirectory(Filename.expandFrom("$TTMODELS")) + +from direct.directnotify import RotatingLog +from otp.uberdog.UberDogGlobal import * + +# Get the options +try: + opts, pargs = getopt.getopt(sys.argv[1:], '', + ['mdip=', + 'mdport=', + 'esip=', + 'esport=', + 'logpath=', + 'ssid=', + 'minChan=', + 'maxChan=', + 'sbNSHost=', + 'sbNSPort=', + 'sbListenPort=', + 'sbCLHost=', + 'sbCLPort=', + 'bwDictPath=', + 'mysqlhost=', + 'crDbName=', + ]) +except Exception, e: + print e + print helpString + sys.exit(1) + +# Only four of the items are required +if len(opts) < 4: + print helpString + sys.exit(1) + +# Default values +uber.mdip = "localhost" +uber.mdport = 6666 +uber.esip = "localhost" +uber.esport = 4343 +logpath = "" +stateServerId = None +minChannel = None +maxChannel = None +sbNSHost = "" +sbNSPort = 6053 +sbListenPort = 6053 +sbCLHost = "" +sbCLPort = 6060 + +homedir = os.getenv("HOME", "") +language = os.getenv("LANGUAGE", "") +if language in ['castillian', 'japanese', 'german', 'portuguese', 'french'] : + bwDictPath = homedir + "/support/" +else : + bwDictPath = "/home/toonpub/support/" +uber.RATManagerHTTPListenPort = 8080 +uber.awardManagerHTTPListenPort = 8888 +uber.inGameNewsMgrHTTPListenPort = 8889 +mysqlhost = "localhost" + +# example values +#minChannel = 20400000 +#maxChannel = 20449999 +#stateServerId = 20100000 +dcFileNames = ['otp.dc', 'toon.dc'] +#uber.bwDictPath = "" + +for opt in opts: + flag, value = opt + if (flag == '--logpath'): + logpath = value + elif (flag == '--ssid'): + stateServerId = int(value) + elif (flag == '--minChan'): + minChannel = int(value) + elif (flag == '--maxChan'): + maxChannel = int(value) + elif (flag == '--mdip'): + uber.mdip = value + elif (flag == '--mdport'): + uber.mdport = int(value) + elif (flag == '--esip'): + uber.esip = value + elif (flag == '--esport'): + uber.esport = int(value) + elif (flag == '--sbNSHost'): + sbNSHost = value + elif (flag == '--sbNSPort'): + sbNSPort = int(value) + elif (flag == '--sbCLHost'): + sbCLHost = value + elif (flag == '--sbCLPort'): + sbCLPort = int(value) + elif (flag == '--sbListenPort'): + sbListenPort = int(value) + elif (flag == '--bwDictPath'): + bwDictPath = value + elif (flag == '--mysqlhost'): + mysqlhost = value + elif (flag == '--crDbName'): + crDbName = value + else: + print "Error: Illegal option: " + flag + print helpString + sys.exit(1) + +# date_hour_sequence.log will be added to the logfile name by RotatingLog(): +logfile = logpath + 'aidistrict_uberdog' + +# Redirect Python output and err to the same file +class LogAndOutput: + def __init__(self, orig, log): + self.orig = orig + self.log = log + def write(self, str): + self.log.write(str) + self.log.flush() + self.orig.write(str) + self.orig.flush() + def flush(self): + self.log.flush() + self.orig.flush() + +log = RotatingLog.RotatingLog(logfile, hourInterval=24, megabyteLimit=1024) +logOut = LogAndOutput(sys.__stdout__, log) +logErr = LogAndOutput(sys.__stderr__, log) +sys.stdout = logOut +sys.stderr = logErr + +from pandac.PandaModules import * + +# Give Panda the same log we use +nout = MultiplexStream() +Notify.ptr().setOstreamPtr(nout, 0) +nout.addFile(Filename(logfile)) +nout.addStandardOutput() +nout.addSystemDebug() + +# We prefer writing the date on the same line as the starting message, +# so we can more easily grep for a restart on a particular date in the +# log files. +print "\n\nStarting Uberdog on %s port %s. %s %s" % \ + (uber.mdip, uber.mdport, time.asctime(time.localtime(time.time())), time.tzname[0]) + +print "Initializing the Toontown UberDog (Uber Distributed Object Globals server)..." + +from toontown.uberdog.ToontownUberDog import ToontownUberDog +from direct.showbase.PythonUtil import * + +uber.objectNames = set(os.getenv("uberdog_objects", "").split()) + +uber.sbNSHost = sbNSHost +uber.sbNSPort = sbNSPort +uber.sbListenPort = sbListenPort +uber.clHost = sbCLHost +uber.clPort = sbCLPort +uber.allowUnfilteredChat = 0 +uber.bwDictPath = bwDictPath + +uber.RATManagerHTTPListenPort = int(os.getenv("RAT_PORT","8080")) +uber.awardManagerHTTPListenPort = int(os.getenv("AWARD_MANAGER_PORT","8888")) +uber.inGameNewsMgrHTTPListenPort = int(os.getenv("IN_GAME_NEWS_PORT","8889")) +uber.mysqlhost = mysqlhost + +uber.codeRedemptionMgrHTTPListenPort = int(os.getenv("CODE_REDEMPTION_PORT","8998")) +uber.crDbName = crDbName + +uber.cpuInfoMgrHTTPListenPort = int(os.getenv("SECURITY_BAN_MGR_PORT",8892)) + +uber.air = ToontownUberDog( + uber.mdip, uber.mdport, + uber.esip, uber.esport, + dcFileNames, + stateServerId, + minChannel, + maxChannel) + +# We let the world know that we are running as a service +uber.aiService = 1 + +uber.wantEmbeddedOtpServer = 0 + +try: + run() +except: + info = describeException() + #uber.air.writeServerEvent('uberdog-exception', districtNumber, info) + raise + diff --git a/toontown/src/uberdog/Sources.pp b/toontown/src/uberdog/Sources.pp new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/uberdog/Start.py b/toontown/src/uberdog/Start.py new file mode 100644 index 0000000..5141f84 --- /dev/null +++ b/toontown/src/uberdog/Start.py @@ -0,0 +1,107 @@ +""" +Start the Toontown UberDog (Uber Distributed Object Globals server). +""" + +import __builtin__ +from direct.task.Task import Task + +class game: + name = "uberDog" + process = "server" +__builtin__.game = game() + +import time +import os +import sys + +# Initialize ihooks importer On the production servers, we run genPyCode -n +# meaning no squeeze, so nobody else does this. When we squeeze, the +# unpacker does this for us and it does not hurt to do in either case. +import ihooks +ihooks.install() + +if os.getenv('TTMODELS'): + from pandac.PandaModules import getModelPath, Filename + # In the publish environment, TTMODELS won't be on the model + # path by default, so we always add it there. In the dev + # environment, it'll be on the model path already, but it + # doesn't hurt to add it again. + getModelPath().appendDirectory(Filename.expandFrom("$TTMODELS/built")) + +from direct.showbase.PythonUtil import * +from otp.uberdog.UberDogGlobal import * +from toontown.coderedemption import TTCodeRedemptionConsts +from toontown.uberdog.ToontownUberDog import ToontownUberDog +from toontown.uberdog import PartiesUdConfig + +print "Initializing the Toontown UberDog (Uber Distributed Object Globals server)..." + +uber.mdip = uber.config.GetString("msg-director-ip", "localhost") +uber.mdport = uber.config.GetInt("msg-director-port", 6666) + +uber.esip = uber.config.GetString("event-server-ip", "localhost") +uber.esport = uber.config.GetInt("event-server-port", 4343) + +stateServerId = uber.config.GetInt("state-server-id", 20100000) + +uber.objectNames = set(os.getenv("uberdog_objects", "").split()) + +minChannel = uber.config.GetInt("uberdog-min-channel", 200400000) +maxChannel = uber.config.GetInt("uberdog-max-channel", 200449999) + +uber.sbNSHost = uber.config.GetString("sb-host","") +uber.sbNSPort = uber.config.GetInt("sb-port",6053) +uber.sbListenPort = 6060 +uber.clHost = "localhost" +uber.clPort = 9090 +uber.allowUnfilteredChat = uber.config.GetInt("allow-unfiltered-chat",0) +uber.bwDictPath = "" + +uber.RATManagerHTTPListenPort = uber.config.GetInt("rat-port",8080) +uber.awardManagerHTTPListenPort = uber.config.GetInt("award-port",8888) +uber.inGameNewsMgrHTTPListenPort = uber.config.GetInt("in-game-news-port",8889) +uber.mysqlhost = uber.config.GetString("mysql-host", PartiesUdConfig.ttDbHost) + + +uber.codeRedemptionMgrHTTPListenPort = uber.config.GetInt('code-redemption-port', 8998) +uber.crDbName = uber.config.GetString("tt-code-db-name", TTCodeRedemptionConsts.DefaultDbName) + +uber.cpuInfoMgrHTTPListenPort = uber.config.GetInt("security_ban_mgr_port",8892) + +uber.air = ToontownUberDog( + uber.mdip, uber.mdport, + uber.esip, uber.esport, + None, + stateServerId, + minChannel, + maxChannel) + +# How we let the world know we are not running a service +uber.aiService = 0 + +uber.wantEmbeddedOtpServer = uber.config.GetInt( + "toontown-uberdog-want-embedded-otp-server", 0) +if uber.wantEmbeddedOtpServer: + otpServerPath = uber.config.GetString( + "toontown-uberdog-otp-server-path", "c:/toonsrv") + sys.path.append(otpServerPath) + + import otp_server_py + if not otp_server_py.serverInit(otpServerPath): + sys.exit(1) + + def ServerYield(task): + otp_server_py.serverLoop() + return Task.cont + + uber.taskMgr.add(ServerYield, 'serverYield') + __builtins__["otpServer"] = otp_server_py + + +try: + run() +except: + info = describeException() + #uber.air.writeServerEvent('uberdog-exception', districtNumber, info) + raise + diff --git a/toontown/src/uberdog/TTSpeedchatRelay.py b/toontown/src/uberdog/TTSpeedchatRelay.py new file mode 100644 index 0000000..d3a41e8 --- /dev/null +++ b/toontown/src/uberdog/TTSpeedchatRelay.py @@ -0,0 +1,20 @@ +from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.otpbase import OTPGlobals +from otp.uberdog.SpeedchatRelay import SpeedchatRelay +from otp.uberdog import SpeedchatRelayGlobals + + +class TTSpeedchatRelay(SpeedchatRelay): + + def __init__(self, cr): + SpeedchatRelay.__init__(self, cr) + + def sendSpeedchatToonTask(self, receiverId, taskId, toNpcId, toonProgress, msgIndex): + #self.sendUpdate("forwardSpeedchat", [receiverId, SpeedchatRelayGlobals.PIRATES_QUEST, [questInt, msgType, taskNum], base.cr.accountDetailRecord.playerAccountId, base.cr.accountDetailRecord.playerName + " PHD"]) + self.sendSpeedchatToRelay(receiverId, SpeedchatRelayGlobals.TOONTOWN_QUEST, [taskId, toNpcId, toonProgress, msgIndex]) + #import pdb; pdb.set_trace() + + + + \ No newline at end of file diff --git a/toontown/src/uberdog/TTSpeedchatRelayUD.py b/toontown/src/uberdog/TTSpeedchatRelayUD.py new file mode 100644 index 0000000..ca1c575 --- /dev/null +++ b/toontown/src/uberdog/TTSpeedchatRelayUD.py @@ -0,0 +1,29 @@ +from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD +from direct.task.Task import Task +from otp.otpbase import OTPGlobals +from otp.friends.FriendInfo import FriendInfo +from direct.directnotify.DirectNotifyGlobal import directNotify +from otp.uberdog.SpeedchatRelayUD import SpeedchatRelayUD +from otp.uberdog import SpeedchatRelayGlobals +from otp.speedchat import SCDecoders +from toontown.speedchat import TTSCDecoders + +class TTSpeedchatRelayUD(SpeedchatRelayUD): + + def __init__(self, air): + SpeedchatRelayUD.__init__(self, air) + + def setTest(self): + print ("TTSpeedchatRelayUD TEST!") + + def translateMessage(self, messageType, indexArray, senderDISLName): + message = SpeedchatRelayUD.translateMessage(self, messageType, indexArray, "Bob") + if message: + return message + elif messageType == SpeedchatRelayGlobals.TOONTOWN_QUEST: + #message = SCDecoders.decodeTTSCToontaskMsg(taskId, toNpcId, toonProgress, msgIndex) + message = TTSCDecoders.decodeTTSCToontaskMsg(indexArray[0], indexArray[1], indexArray[2], indexArray[3]) + return message + else: + return None + \ No newline at end of file diff --git a/toontown/src/uberdog/ToontownUberDog.py b/toontown/src/uberdog/ToontownUberDog.py new file mode 100644 index 0000000..e9507a0 --- /dev/null +++ b/toontown/src/uberdog/ToontownUberDog.py @@ -0,0 +1,136 @@ +""" +The Toontown Uber Distributed Object Globals server. +""" + +from pandac.PandaModules import * +import time +if __debug__: + from direct.showbase.PythonUtil import * + +from direct.directnotify.DirectNotifyGlobal import directNotify + +from otp.distributed import OtpDoGlobals +from otp.ai.AIMsgTypes import * +from otp.ai import TimeManagerAI +from otp.uberdog.UberDog import UberDog + +from otp.friends.AvatarFriendsManagerUD import AvatarFriendsManagerUD +from toontown.uberdog.DistributedDeliveryManagerUD import DistributedDeliveryManagerUD +from toontown.uberdog.DistributedMailManagerUD import DistributedMailManagerUD +from toontown.parties import ToontownTimeManager +from toontown.rpc.RATManagerUD import RATManagerUD +from toontown.rpc.AwardManagerUD import AwardManagerUD +from toontown.uberdog import TTSpeedchatRelayUD +from toontown.uberdog import DistributedInGameNewsMgrUD +from toontown.uberdog import DistributedCpuInfoMgrUD + +from otp.uberdog.RejectCode import RejectCode + +class ToontownUberDog(UberDog): + notify = directNotify.newCategory("UberDog") + + def __init__( + self, mdip, mdport, esip, esport, dcFilenames, + serverId, minChannel, maxChannel): + assert self.notify.debugStateCall(self) + # TODO: The UD needs to know server time, but perhaps this isn't + # the place to do this? -SG-SLWP + self.toontownTimeManager = ToontownTimeManager.ToontownTimeManager() + self.toontownTimeManager.updateLoginTimes(time.time(), time.time(), globalClock.getRealTime()) + + def isManagerFor(name): + return len(uber.objectNames) == 0 or name in uber.objectNames + self.isFriendsManager = False # latest from Ian this should not run anymore + #self.isFriendsManager = isManagerFor('friends') + self.isSpeedchatRelay = isManagerFor('speedchatRelay') + self.isGiftingManager = isManagerFor('gifting') + self.isMailManager = False # isManagerFor('mail') + self.isPartyManager = isManagerFor('party') + self.isRATManager = False # isManagerFor('RAT') + self.isAwardManager = isManagerFor('award') + self.isCodeRedemptionManager = isManagerFor('coderedemption') + self.isInGameNewsMgr = isManagerFor('ingamenews') + self.isCpuInfoMgr = isManagerFor('cpuinfo') + self.isRandomSourceManager = False # isManagerFor('randomsource') + + UberDog.__init__( + self, mdip, mdport, esip, esport, dcFilenames, + serverId, minChannel, maxChannel) + + def createObjects(self): + UberDog.createObjects(self) + # Ask for the ObjectServer so we can check the dc hash value + self.queryObjectAll(self.serverId) + + if self.isFriendsManager: + self.playerFriendsManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_PLAYER_FRIENDS_MANAGER, + "TTPlayerFriendsManager") + + if self.isSpeedchatRelay: + self.speedchatRelay = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_SPEEDCHAT_RELAY, + "TTSpeedchatRelay") + + if self.isGiftingManager: + self.deliveryManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, + "DistributedDeliveryManager") + + if self.isMailManager: + self.mailManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_MAIL_MANAGER, + "DistributedMailManager") + + if self.isPartyManager: + self.partyManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + "DistributedPartyManager") + + if simbase.config.GetBool('want-ddsm', 1): + self.dataStoreManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_TEMP_STORE_MANAGER, + "DistributedDataStoreManager") + + if self.isRATManager: + self.RATManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_RAT_MANAGER, + "RATManager") + + if self.isAwardManager: + self.awardManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_AWARD_MANAGER, + "AwardManager") + + if config.GetBool('want-code-redemption', 1): + if self.isCodeRedemptionManager: + self.codeRedemptionManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_CODE_REDEMPTION_MANAGER, + "TTCodeRedemptionMgr") + + if self.isInGameNewsMgr: + self.inGameNewsMgr = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_IN_GAME_NEWS_MANAGER, + "DistributedInGameNewsMgr") + + if self.isCpuInfoMgr: + self.cpuInfoMgr = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_CPU_INFO_MANAGER, + "DistributedCpuInfoMgr") + + if self.isRandomSourceManager: + self.randomSourceManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_NON_REPEATABLE_RANDOM_SOURCE, + "NonRepeatableRandomSource") + + def getDatabaseIdForClassName(self, className): + return DatabaseIdFromClassName.get( + className, DefaultDatabaseChannelId) + + if __debug__: + def status(self): + if self.isGiftingManager: + print "deliveryManager is", self.deliveryManager + if self.isFriendsManager: + print "playerFriendsManager is ",self.playerFriendsManager + diff --git a/toontown/src/uberdog/TrickOrTreatScavengerHuntDataStore.py b/toontown/src/uberdog/TrickOrTreatScavengerHuntDataStore.py new file mode 100644 index 0000000..15c3fa0 --- /dev/null +++ b/toontown/src/uberdog/TrickOrTreatScavengerHuntDataStore.py @@ -0,0 +1,114 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.uberdog.DataStore import * + +class TrickOrTreatScavengerHuntDataStore(DataStore): + """ + This is a specialized DataStore class designed to handle + the TrickOrTreat holiday event. It responds to two query + types: one to get a toon's current progress in the + scavenger hunt, and one to update it's progress with a + newly completed goal. + """ + + # Define the available query strings + # We'll only need two types of queries. + # If for some strange reason this class is subclassed, + # in the subclass use the following line: + # QueryTypes = TrickOrTreatScavengerHuntDataStore.addQueryTypes(['Type_1',...]) + QueryTypes = DataStore.addQueryTypes(['GetGoals', 'AddGoal']) + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'TrickOrTreatScavengerHuntDataStore') + + def __init__(self, filepath): + """ + filepath is where this store's data will be held on the disk. + This path should be unique to this data store. + """ + DataStore.__init__(self,filepath) + + def handleQuery(self,query): + """ + This function parses the query and performs the + necessary operations on the data store. If a + response is necessary, it is created here. + + Queries are of the format: + (queryId, (avId, goal)) + + queryId is QueryTypes['GetGoals'] or + QueryTypes['AddGoal'] + avId is the toon's id. + goal is the goal they're attempting to complete. + + A queryId of 'GetGoals' will return a list of the toon's + completed goals: + (queryId, (avId, goal, (goal1,goal2,...))) + + A queryId of 'AddGoals' will return a confirmation message: + (qId, (avId,)) + """ + + # extract the queryId + qId, qData = query + + # they're requesting a list of the toon's goals + if qId == self.QueryTypes['GetGoals']: + avId,goal = qData + goals = self.__getGoalsForAvatarId(avId) + # build the return message + return (qId,(avId,goal,goals)) + # they're trying to add a goal to the toon's list + elif qId == self.QueryTypes['AddGoal']: + avId,goal = qData + self.__addGoalToAvatarId(avId,goal) + # confirm the update + return (qId,(avId,)) + + # if not a valid queryId, return an empty result + return None + + + def __addGoalToAvatarId(self, avId, goal): + """ + Add the goal to avId's list of completed goals. + If the goal is already present, this function + has no effect. + """ + + if self.wantAnyDbm: + pAvId = cPickle.dumps(avId) + pGoal = cPickle.dumps(goal) + + pData = self.data.get(pAvId,None) + if pData is not None: + data = cPickle.loads(pData) + else: + data = set() + + data.add(goal) + + pData = cPickle.dumps(data) + self.data[pAvId] = pData + else: + self.data.setdefault(avId,set()) + self.data[avId].add(goal) + self.incrementWriteCount() + + + def __getGoalsForAvatarId(self, avId): + """ + Return a [] of goals for avid. + """ + if self.wantAnyDbm: + pAvId = cPickle.dumps(avId) + pData = self.data.get(pAvId,None) + if pData is not None: + data = list(cPickle.loads(pData)) + else: + data = [] + return data + else: + return list(self.data.get(avId,[])) + + diff --git a/toontown/src/uberdog/WipeTtDbs.py b/toontown/src/uberdog/WipeTtDbs.py new file mode 100644 index 0000000..87044cf --- /dev/null +++ b/toontown/src/uberdog/WipeTtDbs.py @@ -0,0 +1,54 @@ +import MySQLdb +import direct +from pandac.PandaModules import * +from direct.showbase.ShowBase import ShowBase +from toontown.toonbase import TTLocalizer + +language = TTLocalizer.getLanguage() + +showbase = ShowBase(fStartDirect=False, windowType='none') +config = getConfigShowbase() + +from otp.uberdog.DBInterface import DBInterface +from toontown.coderedemption import TTCodeRedemptionConsts + +username = config.GetString("mysql-user") +password = config.GetString("mysql-passwd") + +if username == "" or password == "": + print "Username or password not found, check your config.prc!" + sys.exit(2) + + +db = MySQLdb.connect(host="localhost", + port=3306, + user=username, + passwd=password) + +print "Connected to MySQL at localhost." + +cursor = db.cursor() + +def dropdb(dbname): + try: + print "Dropping database %s:" % dbname + cursor.execute("DROP DATABASE %s"%dbname) + print " Success!" + except Exception,e: + print " Failed: %s" % e + +if language == 'castillian': + ttDbName = "es_toontownTopDb" +elif language == "japanese": + ttDbName = "jp_toontownTopDb" +elif language == "french": + ttDbName = "french_toontownTopDb" +elif language == "portuguese": + ttDbName = "br_toontownTopDb" +else: + ttDbName = "toontownTopDb" + + +dropdb(ttDbName) +dropdb(DBInterface.processDBName(TTCodeRedemptionConsts.DefaultDbName)) +db.commit() diff --git a/toontown/src/uberdog/__init__.py b/toontown/src/uberdog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/src/uberdog/ttInviteDb.py b/toontown/src/uberdog/ttInviteDb.py new file mode 100644 index 0000000..297d68f --- /dev/null +++ b/toontown/src/uberdog/ttInviteDb.py @@ -0,0 +1,350 @@ +#import Pyro.core +#import Pyro.naming +#import Pyro.errors +import sys +import datetime +import MySQLdb +import MySQLdb.constants.CR +import _mysql_exceptions +from direct.directnotify import DirectNotifyGlobal +from toontown.uberdog import ttSQL +from toontown.parties import PartyGlobals + +SERVER_GONE_ERROR = MySQLdb.constants.CR.SERVER_GONE_ERROR +SERVER_LOST = MySQLdb.constants.CR.SERVER_LOST + +class ttInviteDb: + """Based on sbMaildb.py in $OTP/src/switchboard.""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ttInviteDb") + + def __init__(self,host,port,user,passwd,db): + self.sqlAvailable = True + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.dbname = db + + try: + self.db = MySQLdb.connect(host=host, + port=port, + user=user, + passwd=passwd, + ) + except _mysql_exceptions.OperationalError,e: + self.notify.warning("Failed to connect to MySQL db=%s at %s:%d. ttInvitedb DB is disabled."%(db,host,port)) + self.notify.warning("Error detail: %s"%str(e)) + self.sqlAvailable = False + return + + self.notify.info("Connected to invitedb=%s at %s:%d."%(db,host,port)) + + #temp hack for initial dev, create DB structure if it doesn't exist already + cursor = self.db.cursor() + try: + cursor.execute("CREATE DATABASE `%s`"%self.dbname) + if __debug__: + self.notify.info("Database '%s' did not exist, created a new one!"%self.dbname) + except _mysql_exceptions.ProgrammingError, e: + # self.notify.info('%s' % str(e)) + pass + except _mysql_exceptions.OperationalError, e: + self.notify.info('%s' % str(e)) + pass + + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + self.notify.debug("Using database '%s'"%self.dbname) + try: + # well if we're creating the party table again, + # might as well create the party status lookup table for the benefit of database reporting + cursor.execute("Show tables like 'ttInviteStatus';") + if not cursor.rowcount: + # we know the ttInviteStatus table doesn't exist, create it again + cursor.execute(""" + DROP TABLE IF EXISTS ttInviteStatus; + """) + + cursor.execute(""" + CREATE TABLE ttInviteStatus( + statusId TINYINT NOT NULL, + description VARCHAR(20) NOT NULL, + lastupdate TIMESTAMP NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (statusId), + UNIQUE INDEX uidx_desc(description) + ) + ENGINE=Innodb + DEFAULT CHARSET=utf8; + """) + # this ensure that the table values come directly from PartyGlobals.InviteStatus + for index in xrange(len(PartyGlobals.InviteStatus)): + cursor.execute(\ + "INSERT INTO ttInviteStatus(statusId, description) VALUES (%d, '%s')" % + (index, PartyGlobals.InviteStatus.getString(index))) + + # TODO is it better to do a show tables than to do a try create Table except block? + cursor.execute(""" + CREATE TABLE ttInvite ( + inviteId BIGINT NOT NULL AUTO_INCREMENT, + partyId BIGINT NOT NULL, + guestId BIGINT NOT NULL, + statusId TINYINT NOT NULL DEFAULT 0, + lastupdate TIMESTAMP NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY (inviteId), + INDEX idx_guestId (guestId), + INDEX idx_partyId(partyId), + FOREIGN KEY (partyId) REFERENCES ttParty(partyId) ON DELETE CASCADE + ) + ENGINE=InnoDB + DEFAULT CHARSET=utf8; + """) + if __debug__: + self.notify.info("Table ttInvite did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + try: + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Using database '%s'"%self.dbname) + except: + self.notify.debug("%s database not found, ttInvite not active."%self.dbname) + self.sqlAvailable = False + + + def reconnect(self): + self.notify.debug("MySQL server was missing, attempting to reconnect.") + try: self.db.close() + except: pass + self.db = MySQLdb.connect(host=self.host, + port=self.port, + user=self.user, + passwd=self.passwd) + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port)) + + def disconnect(self): + if not self.sqlAvailable: + return + self.db.close() + self.db = None + + def getInvites(self, avatarId,isRetry=False): + """ + Returns a tuple, which could be empty. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getInvites") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getInvitesSELECT,(avatarId,)) + res = cursor.fetchall() + self.notify.debug("Select was successful in ttInvitedb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on getInvites retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getInvites(avatarId,True) + else: + self.notify.warning("Unknown error in getInvites, retrying:\n%s" % str(e)) + self.reconnect() + return self.getInvites(avatarId,True) + except Exception,e: + self.notify.warning("Unknown error in getInvites, giving up:\n%s" % str(e)) + return () + + + def putInvite(self, partyId, inviteeId,isRetry=False): + if not self.sqlAvailable: + return + + countcursor = self.db.cursor() + + try: + cursor = MySQLdb.cursors.DictCursor(self.db) + + cursor.execute(ttSQL.putInviteINSERT, + (partyId, inviteeId)) + self.db.commit() + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on putInvite retry, giving up:\n%s" % str(e)) + return + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + self.putInvite(partyId, inviteeId,True) + else: + self.notify.warning("Unknown error in putInvite, retrying:\n%s" % str(e)) + self.reconnect() + self.putInvite(partyId, inviteeId,True) + except Exception,e: + self.notify.warning("Unknown error in putInvite, giving up:\n%s" % str(e)) + return + + + def deleteInviteByParty(self,partyId,isRetry=False): + if not self.sqlAvailable: + return + + cursor = MySQLdb.cursors.DictCursor(self.db) + + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.deleteInviteByPartyDELETE,( partyId)) + + if cursor.rowcount < 1: + self.notify.warning("%d tried to delete party %d which didn't exist or wasn't his!" % (accountId,messageId)) + + self.db.commit() + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error in deleteInviteByParty retry, giving up:\n%s" % str(e)) + return + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + self.deleteMail(accountId,messageId,True) + else: + self.notify.warning("Unnown error in deleteInviteByParty, retrying:\n%s" % str(e)) + self.reconnect() + self.deleteMail(accountId,messageId,True) + except Exception,e: + self.notify.warning("Unknown error in deleteInviteByParty, giving up:\n%s" % str(e)) + return + + def getReplies(self,partyId,isRetry=False): + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getParty") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getRepliesSELECT,(partyId,)) + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttInvitedb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on getReplies retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getReplies(partyId,True) + else: + self.notify.warning("Unknown error in getReplies, retrying:\n%s" % str(e)) + self.reconnect() + return self.getReplies(partyId,True) + except Exception,e: + self.notify.warning("Unknown error in getReplies, giving up:\n%s" % str(e)) + return () + + + def dumpInviteTable(self): + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("USE `%s`"%self.dbname) + cursor.execute("SELECT * FROM ttInviteDb") + return cursor.fetchall() + + + def getOneInvite(self, inviteKey, isRetry = False): + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getParty") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getOneInviteSELECT,(inviteKey,)) + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttInvitedb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on getOneInvite retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getOneInvite(partyId,True) + else: + self.notify.warning("Unknown error in getOneInvite, retrying:\n%s" % str(e)) + self.reconnect() + return self.getOneInvite(partyId,True) + except Exception,e: + self.notify.warning("Unknown error in getOneInvite, giving up:\n%s" % str(e)) + return () + + def updateInvite(self, inviteKey, newStatus, isRetry = False): + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getParty") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.inviteUPDATE,(newStatus, inviteKey)) + self.db.commit() + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttInvitedb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on updateInvite retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.updateInvite( newStatus, inviteKey, True) + else: + self.notify.warning("Unknown error in updateInvite, retrying:\n%s" % str(e)) + self.reconnect() + return self.updateInvite( newStatus, inviteKey, True) + except Exception,e: + self.notify.warning("Unknown error in updateInvite, giving up:\n%s" % str(e)) + return () + + + def getInviteesOfParty(self, inviteKey, isRetry = False): + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getParty") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getInviteesOfPartySELECT,(inviteKey,)) + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttInvitedb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on getInviteesOfParty retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getInviteesOfParty(partyId,True) + else: + self.notify.warning("Unknown error in getInviteesOfParty, retrying:\n%s" % str(e)) + self.reconnect() + return self.getInviteesOfParty(partyId,True) + except Exception,e: + self.notify.warning("Unknown error in getInviteesOfParty, giving up:\n%s" % str(e)) + return () diff --git a/toontown/src/uberdog/ttMaildb.py b/toontown/src/uberdog/ttMaildb.py new file mode 100644 index 0000000..9dabad6 --- /dev/null +++ b/toontown/src/uberdog/ttMaildb.py @@ -0,0 +1,213 @@ +#import Pyro.core +#import Pyro.naming +#import Pyro.errors +import sys +import datetime +import MySQLdb +import MySQLdb.constants.CR +import _mysql_exceptions +from direct.directnotify import DirectNotifyGlobal +from toontown.uberdog import ttSQL + +from otp.switchboard import sbConfig + +SERVER_GONE_ERROR = MySQLdb.constants.CR.SERVER_GONE_ERROR +SERVER_LOST = MySQLdb.constants.CR.SERVER_LOST + +class ttMaildb: + """Based on sbMaildb.py in $OTP/src/switchboard.""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ttMaildb") + + def __init__(self,host,port,user,passwd,db): + self.sqlAvailable = True + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.dbname = db + + try: + self.db = MySQLdb.connect(host=host, + port=port, + user=user, + passwd=passwd, + ) + except _mysql_exceptions.OperationalError,e: + self.notify.warning("Failed to connect to MySQL db=%s at %s:%d. ttMaildb DB is disabled."%(db,host,port)) + self.notify.warning("Error detail: %s"%str(e)) + self.sqlAvailable = False + return + + self.notify.info("Connected to maildb=%s at %s:%d."%(db,host,port)) + + #temp hack for initial dev, create DB structure if it doesn't exist already + cursor = self.db.cursor() + try: + cursor.execute("CREATE DATABASE `%s`"%self.dbname) + if __debug__: + self.notify.info("Database '%s' did not exist, created a new one!"%self.dbname) + except _mysql_exceptions.ProgrammingError, e: + # self.notify.info('%s' % str(e)) + pass + except _mysql_exceptions.OperationalError, e: + self.notify.info('%s' % str(e)) + pass + + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + self.notify.debug("Using database '%s'"%self.dbname) + try: + cursor.execute(""" + CREATE TABLE ttrecipientmail ( + messageId BIGINT NOT NULL AUTO_INCREMENT UNIQUE, + recipientId BIGINT NOT NULL, + senderId BIGINT NOT NULL, + message TEXT NOT NULL, + lastupdate TIMESTAMP NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + dateSent TIMESTAMP NOT NULL default '0000-00-00 00:00:00', + readFlag BOOLEAN DEFAULT FALSE, + PRIMARY KEY (messageId), + INDEX idx_recipientId (recipientId) + ) + ENGINE=InnoDB + DEFAULT CHARSET=utf8; + + """) + if __debug__: + self.notify.info("Table ttrecipientmail did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + try: + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Using database '%s'"%self.dbname) + except: + self.notify.debug("%s database not found, maildb not active."%self.dbname) + self.sqlAvailable = False + + + def reconnect(self): + self.notify.debug("MySQL server was missing, attempting to reconnect.") + try: self.db.close() + except: pass + self.db = MySQLdb.connect(host=self.host, + port=self.port, + user=self.user, + passwd=self.passwd) + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port)) + + def disconnect(self): + if not self.sqlAvailable: + return + self.db.close() + self.db = None + + def getMail(self,recipientId,isRetry=False): + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getMail") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getMailSELECT,(recipientId,)) + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on getMail retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getMail(recipientId,True) + else: + self.notify.warning("Unknown error in getMail, retrying:\n%s" % str(e)) + self.reconnect() + return self.getMail(recipientId,True) + except Exception,e: + self.notify.warning("Unknown error in getMail, giving up:\n%s" % str(e)) + return () + + + def putMail(self,recipientId,senderId,message,isRetry=False): + if not self.sqlAvailable: + return + + countcursor = self.db.cursor() + + try: + countcursor.execute("USE `%s`"%self.dbname) + countcursor.execute(ttSQL.getMailSELECT,(recipientId,)) + if countcursor.rowcount >= sbConfig.mailStoreMessageLimit: + self.notify.debug("%d's mailbox is full! Can't fit message from %d." %(recipientId,senderId)) + return + + cursor = MySQLdb.cursors.DictCursor(self.db) + + cursor.execute(ttSQL.putMailINSERT, + (recipientId,senderId,message)) + self.db.commit() + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error on putMail retry, giving up:\n%s" % str(e)) + return + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + self.putMail(recipientId,senderId,message,True) + else: + self.notify.warning("Unknown error in putMail, retrying:\n%s" % str(e)) + self.reconnect() + self.putMail(recipientId,senderId,message,True) + except Exception,e: + self.notify.warning("Unknown error in putMail, giving up:\n%s" % str(e)) + return + + + def deleteMail(self,accountId,messageId,isRetry=False): + if not self.sqlAvailable: + return + + cursor = MySQLdb.cursors.DictCursor(self.db) + + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.deleteMailDELETE,(messageId,accountId)) + + if cursor.rowcount < 1: + self.notify.warning("%d tried to delete message %d which didn't exist or wasn't his!" % (accountId,messageId)) + + self.db.commit() + + except _mysql_exceptions.OperationalError,e: + if isRetry == True: + self.notify.warning("Error in deleteMail retry, giving up:\n%s" % str(e)) + return + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + self.deleteMail(accountId,messageId,True) + else: + self.notify.warning("Unnown error in deleteMail, retrying:\n%s" % str(e)) + self.reconnect() + self.deleteMail(accountId,messageId,True) + except Exception,e: + self.notify.warning("Unknown error in deleteMail, giving up:\n%s" % str(e)) + return + + + def dumpMailTable(self): + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("USE `%s`"%self.dbname) + cursor.execute("SELECT * FROM recipientmail") + return cursor.fetchall() + + + diff --git a/toontown/src/uberdog/ttPartyDb.py b/toontown/src/uberdog/ttPartyDb.py new file mode 100644 index 0000000..da6e135 --- /dev/null +++ b/toontown/src/uberdog/ttPartyDb.py @@ -0,0 +1,717 @@ +#import Pyro.core +#import Pyro.naming +#import Pyro.errors +import sys +import datetime +import MySQLdb +import MySQLdb.constants.CR +import _mysql_exceptions +from direct.directnotify import DirectNotifyGlobal +from toontown.uberdog import ttSQL +from toontown.parties import PartyGlobals +from toontown.parties.PartyGlobals import PartyStatus,InviteTheme + +SERVER_GONE_ERROR = MySQLdb.constants.CR.SERVER_GONE_ERROR +SERVER_LOST = MySQLdb.constants.CR.SERVER_LOST + +class ttPartyDb: + """Based on sbMaildb.py in $OTP/src/switchboard.""" + + notify = DirectNotifyGlobal.directNotify.newCategory("ttPartyDb") + + def __init__(self,host,port,user,passwd,db): + self.sqlAvailable = True + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.dbname = db + + try: + self.db = MySQLdb.connect(host=host, + port=port, + user=user, + passwd=passwd, + ) + except _mysql_exceptions.OperationalError,e: + self.notify.warning("Failed to connect to MySQL db=%s at %s:%d. ttMaildb DB is disabled."%(db,host,port)) + self.notify.warning("Error detail: %s"%str(e)) + self.sqlAvailable = False + return + + self.notify.info("Connected to maildb=%s at %s:%d."%(db,host,port)) + + #temp hack for initial dev, create DB structure if it doesn't exist already + cursor = self.db.cursor() + try: + cursor.execute("CREATE DATABASE `%s`"%self.dbname) + if __debug__: + ttPartyDb.notify.info("Database '%s' did not exist, created a new one!"%self.dbname) + except _mysql_exceptions.ProgrammingError, e: + # ttPartyDb.notify.info('%s' % str(e)) + pass + except _mysql_exceptions.OperationalError, e: + ttPartyDb.notify.info('%s' % str(e)) + pass + + cursor.execute("USE `%s`"%self.dbname) + if __debug__: + ttPartyDb.notify.debug("Using database '%s'"%self.dbname) + try: + # well if we're creating the party table again, + # might as well create the party status lookup table for the benefit of database reporting + cursor.execute("Show tables like 'ttPartyStatus';") + if not cursor.rowcount: + # we know the ttPartyStatus table doesn't exist, create it again + cursor.execute(""" + DROP TABLE IF EXISTS ttPartyStatus; + """) + + cursor.execute(""" + CREATE TABLE ttPartyStatus( + statusId TINYINT NOT NULL, + description VARCHAR(20) NOT NULL, + lastupdate TIMESTAMP NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (statusId), + UNIQUE INDEX uidx_desc(description) + ) + ENGINE=Innodb + DEFAULT CHARSET=utf8; + """) + # this ensure that the table values come directly from PartyGlobals.PartyStatus + for index in xrange(len(PartyGlobals.PartyStatus)): + cursor.execute(\ + "INSERT INTO ttPartyStatus(statusId, description) VALUES (%d, '%s')" % + (index, PartyGlobals.PartyStatus.getString(index))) + + # TODO is it better to do a show tables than to do a try create Table except block? + cursor.execute(""" + CREATE TABLE ttParty ( + partyId BIGINT NOT NULL AUTO_INCREMENT, + hostId BIGINT NOT NULL, + startTime TIMESTAMP NOT NULL default '0000-00-00 00:00:00', + endTime TIMESTAMP NOT NULL default '0000-00-00 00:00:00', + isPrivate BOOL default False, + inviteTheme TINYINT, + activities VARBINARY(252), + decorations VARBINARY(252), + statusId TINYINT default 0, + creationTime TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + lastupdate TIMESTAMP NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY (partyId), + INDEX idx_hostId (hostId), + INDEX idx_statusId(statusId) + ) + ENGINE=InnoDB + DEFAULT CHARSET=utf8; + + """) + + # size calculations + # partyId 8 bytes + # hostId 8 bytes + # startTime 4 bytes + # endTime 4 bytes + # isPrivate 1 byte + # inviteTheme 1 byte + # activites 252 bytes + # decorations 252 bytes + # statys 1 byte + # creationTime 4 bytes + # lastupdate 4 bytes + # TOTAL = 539 bytes + if __debug__: + ttPartyDb.notify.info("Table ttParty did not exist, created a new one!") + except _mysql_exceptions.OperationalError,e: + pass + + try: + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Using database '%s'"%self.dbname) + except: + self.notify.debug("%s database not found, ttPartydb not active."%self.dbname) + self.sqlAvailable = False + + + def reconnect(self): + self.notify.debug("MySQL server was missing, attempting to reconnect.") + try: self.db.close() + except: pass + self.db = MySQLdb.connect(host=self.host, + port=self.port, + user=self.user, + passwd=self.passwd) + cursor = self.db.cursor() + cursor.execute("USE `%s`"%self.dbname) + self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port)) + + def disconnect(self): + if not self.sqlAvailable: + return + self.db.close() + self.db = None + + def getParty(self, partyId, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getParty") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getPartySELECT,(partyId,)) + res = cursor.fetchall() + self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getParty retry. Giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getParty(partyId,True) + else: + self.notify.warning("Unknown error in getParty, retrying:\n%s" % str(e)) + self.reconnect() + return self.getParty(partyId,True) + except Exception,e: + self.notify.warning("Unknown error in getParty, giving up:\n%s" % str(e)) + return () + + + def putParty(self, hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, status, isRetry=False): + """ + Returns False if the operation failed for any reason. + + isRetry indicates whether this attempt is a retry or not. + """ + self.notify.debug("putParty( hostId=%s, startTime=%s, endTime=%s, isPrivate=%s, inviteTheme=%s, ... status=%s, isRetry=%s )" %(hostId, startTime, endTime, isPrivate, InviteTheme.getString(inviteTheme), PartyStatus.getString(status), isRetry) ) + if not self.sqlAvailable: + self.notify.warning("sqlAvailable is False in putParty call.") + return False + + # we need to parse activites and decorations + activityStr = "" + for activity in activities: + for field in activity: + activityStr += chr(field) + + decorStr = "" + for decor in decorations: + for field in decor: + decorStr += chr(field) + + countcursor = self.db.cursor() + + try: + countcursor.execute("USE `%s`"%self.dbname) + countcursor.execute(ttSQL.getPartyOfHostMatchingStatusSELECT,(hostId,PartyStatus.Pending)) + if countcursor.rowcount >= PartyGlobals.MaxHostedPartiesPerToon: + self.notify.debug("%d can't host another party, over the limit " %(hostId)) + return False + + cursor = MySQLdb.cursors.DictCursor(self.db) + + cursor.execute(ttSQL.putPartyINSERT, + (hostId, startTime, endTime, isPrivate, inviteTheme, activityStr, decorStr, status)) + self.db.commit() + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("putParty failed with error '%s' on retry. Giving up." % str(e)) + return False + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.putParty(hostId, startTime, endTime, isPrivate, inviteTheme, activityStr, decorStr, status, True) + else: + self.notify.warning("putParty failed with error '%s'. Retrying." % str(e)) + self.reconnect() + return self.putParty(hostId, startTime, endTime, isPrivate, inviteTheme, activityStr, decorStr, status, True) + except Exception,e: + self.notify.warning("putParty failed with error '%s'. Giving up." % str(e)) + return False + else: + return True # if we got this far without an exception, we're good + + def deleteParty(self,partyId,isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + return + + cursor = MySQLdb.cursors.DictCursor(self.db) + + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.deletePartyDELETE,(messageId, partyId)) + + if cursor.rowcount < 1: + self.notify.warning("%d tried to delete party %d which didn't exist or wasn't his!" % (accountId,messageId)) + + self.db.commit() + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error in deleteParty retry, giving up:\n%s" % str(e)) + return + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + self.deleteParty(partyId,True) + else: + self.notify.warning("Unnown error in deleteParty, retrying:\n%s" % str(e)) + self.reconnect() + self.deleteParty(partyId,True) + except Exception,e: + self.notify.warning("Unknown error in deleteParty, giving up:\n%s" % str(e)) + return + + + def dumpPartyTable(self): + cursor = MySQLdb.cursors.DictCursor(self.db) + cursor.execute("USE `%s`"%self.dbname) + cursor.execute("SELECT * FROM ttPartyDb") + return cursor.fetchall() + + def getPartiesAvailableToStart(self, currentTime, isRetry=False): + """ + Returns a list of tuples of partyId and hostId of all parties allowed to + start. A party is allowed to start if its status is Pending and server + time is past it's start time. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getPartiesAvailableToStart") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getPartiesAvailableToStart,(currentTime, PartyGlobals.PartyStatus.Pending)) + res = cursor.fetchall() + # Ok, these parties can start, go ahead and set their status to CanStart + self._setPartyStatusToCanStart(res) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getPartiesAvailableToStart retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getPartiesAvailableToStart(currentTime, True) + else: + self.notify.warning("Unknown error in getPartiesAvailableToStart, retrying:\n%s" % str(e)) + self.reconnect() + return self.getPartiesAvailableToStart(currentTime,True) + except Exception,e: + self.notify.warning("Unknown error in getPartiesAvailableToStart, giving up:\n%s" % str(e)) + return () + + + def _setPartyStatusToCanStart(self, tupleOfResultDictionaries): + """ Set the status on the following parties to CanStart """ + for resDict in tupleOfResultDictionaries: + self.changePartyStatus(resDict['partyId'], PartyGlobals.PartyStatus.CanStart) + + def getPartiesOfHost(self, hostId, sortedByStartTime = False, isRetry=False): + """ + Returns a tuple, which could be empty. + + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getPartiesOfHost") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + if sortedByStartTime: + cursor.execute(ttSQL.getPartyOfHostSortedSELECT,(hostId,)) + else: + cursor.execute(ttSQL.getPartyOfHostSELECT,(hostId,)) + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getPartiesOfHost retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getPartiesOfHost(hostId, sortedByStartTime, True) + else: + self.notify.warning("Unknown error in getPartiesOfHost, retrying:\n%s" % str(e)) + self.reconnect() + return self.getPartiesOfHost(hostId, sortedByStartTime, True) + except Exception,e: + self.notify.warning("Unknown error in getPartiesOfHost, giving up:\n%s" % str(e)) + return () + + def getPartiesOfHostThatCanStart(self, hostId, isRetry=False): + """ + Returns a tuple, which could be empty. + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getPartiesOfHostThatCanStart") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.getPartyOfHostMatchingStatusSELECT,(hostId,PartyGlobals.PartyStatus.CanStart)) + res = cursor.fetchall() + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getPartiesOfHostThatCanStart retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getPartiesOfHostThatCanStart(hostId, True) + else: + self.notify.warning("Unknown error in getPartiesOfHostThatCanStart, retrying:\n%s" % str(e)) + self.reconnect() + return self.getPartiesOfHostThatCanStart(hostId, True) + except Exception,e: + self.notify.warning("Unknown error in getPartiesOfHostThatCanStart, giving up:\n%s" % str(e)) + return () + + def changePrivate(self, partyId, newPrivateStatus, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling changePrivate") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.partyPrivateUPDATE,( newPrivateStatus, partyId)) + self.db.commit() + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on changePrivate retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.changePrivate(newPrivateStatus, partyId, True) + else: + self.notify.warning("Unknown error in changePrivate, retrying:\n%s" % str(e)) + self.reconnect() + return self.changePrivate( newPrivateStatus, partyId, True) + except Exception,e: + self.notify.warning("Unknown error in changePrivate, giving up:\n%s" % str(e)) + return () + + + def changePartyStatus(self, partyId, newPartyStatus, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling changePartyStatus") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.partyStatusUPDATE,( newPartyStatus, partyId)) + self.db.commit() + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on changePartyStatus retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.changePartyStatus(newPartyStatus, partyId, True) + else: + self.notify.warning("Unknown error in changePartyStatus, retrying:\n%s" % str(e)) + self.reconnect() + return self.changePartyStatus( newPartyStatus, partyId, True) + except Exception,e: + self.notify.warning("Unknown error in changePartyStatus, giving up:\n%s" % str(e)) + return () + + def convertListToSQLString(self, partyIds): + """Convert a list of integers to a string sql recognizes.""" + # string version of partyIds is so close to what we need, but it adds the L + inClause = "(" + for index in xrange(len(partyIds)): + inClause += "%d" % partyIds[index] + if index < len(partyIds) - 1: + inClause += "," + inClause += ")" + return inClause + + def getMultipleParties(self, partyIds, sortByStartTime = False, isRetry=False): + """ + Return all the partyInfo matching the partyIds list, + It may return nothing if there are no matches. + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getMultipleParties") + return () + + if not partyIds: + self.notify.debug("empty list in partyIds for getMultipleParties") + return() + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + inClause = self.convertListToSQLString(partyIds) + + if sortByStartTime: + cursor.execute(ttSQL.getMultiplePartiesSortedSELECT % inClause) + else: + cursor.execute(ttSQL.getMultiplePartiesSELECT % inClause) + res = cursor.fetchall() + self.notify.debug("Select was successful in getMultipleParties, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getMultipleParties retry. Giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getMultipleParties(partyIds, sortByStartTime, True) + else: + self.notify.warning("Unknown error in getMultipleParties, retrying:\n%s" % str(e)) + self.reconnect() + return self.getMultipleParties(partyIds,sortByStartTime, True) + except Exception,e: + self.notify.warning("Unknown error in getMultipleParties, giving up:\n%s" % str(e)) + return () + + + + def getPrioritizedParties(self, partyIds, thresholdTime, limit, future, cancelled, isRetry=False): + """Return parties from the database using the criteria specified in future and cancelled.""" + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getCancelledFutureParties") + return () + + if not partyIds: + self.notify.debug("empty list in partyIds for getCancelledFutureParties") + return() + + sqlString = "" + if future and cancelled: + sqlString = ttSQL.getCancelledFuturePartiesSELECT + elif future and not cancelled: + sqlString = ttSQL.getNonCancelledFuturePartiesSELECT + elif not future and cancelled: + sqlString = ttSQL.getCancelledPastPartiesSELECT + else: + sqlString = ttSQL.getNonCancelledPastPartiesSELECT + + cursor = MySQLdb.cursors.DictCursor(self.db) + + try: + cursor.execute("USE `%s`"%self.dbname) + inClause = self.convertListToSQLString(partyIds) + + parameters = (inClause, thresholdTime, str(limit)) + execStr = sqlString % parameters + cursor.execute(execStr) + + res = cursor.fetchall() + self.notify.debug("Select was successful in getPrioritizedParties, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getPrioritizedParties retry. Giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getPrioritizedParties( partyIds, thresholdTime, limit, future, cancelled, isRetry=True) + else: + self.notify.warning("Unknown error in getPrioritizedParties getCancelledFutureParties, retrying:\n%s" % str(e)) + self.reconnect() + return self.getPrioritizedParties( partyIds, thresholdTime, limit, future, cancelled, isRetry=True) + except Exception,e: + self.notify.warning("Unknown error in getPrioritizedParties getCancelledFutureParties, giving up:\n%s" % str(e)) + return () + + + def getHostPrioritizedParties(self, hostId, thresholdTime, limit, future, cancelled, isRetry=False): + """Return parties from the database using the criteria specified in future and cancelled.""" + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling getCancelledFutureParties") + return () + + if not hostId: + self.notify.debug("empty list in hostId for getCancelledFutureParties") + return() + + sqlString = "" + if future and cancelled: + sqlString = ttSQL.getHostCancelledFuturePartiesSELECT + elif future and not cancelled: + sqlString = ttSQL.getHostNonCancelledFuturePartiesSELECT + elif not future and cancelled: + sqlString = ttSQL.getHostCancelledPastPartiesSELECT + else: + sqlString = ttSQL.getHostNonCancelledPastPartiesSELECT + + cursor = MySQLdb.cursors.DictCursor(self.db) + + try: + cursor.execute("USE `%s`"%self.dbname) + parameters = (hostId, thresholdTime, str(limit)) + execStr = sqlString % parameters + cursor.execute(execStr) + + res = cursor.fetchall() + self.notify.debug("Select was successful in getHostPrioritizedParties, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on getHostPrioritizedParties retry. Giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.getHostPrioritizedParties( hostId, thresholdTime, limit, future, cancelled, isRetry=True) + else: + self.notify.warning("Unknown error in getHostPrioritizedParties getCancelledFutureParties, retrying:\n%s" % str(e)) + self.reconnect() + return self.getHostPrioritizedParties( hostId, thresholdTime, limit, future, cancelled, isRetry=True) + except Exception,e: + self.notify.warning("Unknown error in getHostPrioritizedParties getCancelledFutureParties, giving up:\n%s" % str(e)) + return () + + def forceFinishForStarted(self, thresholdTime, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + Returns a list of (partyId,hostId) for the ones that were forced to finished + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling forceFinishForStarted") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.partyGetPartiesGoingToFinishedSELECT,(thresholdTime,)) + res = cursor.fetchall() + cursor.execute(ttSQL.partyForceFinishForStartedUPDATE,(thresholdTime,)) + self.db.commit() + + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on forceFinishForStarted retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.forceFinishForStarted(thresholdTime, True) + else: + self.notify.warning("Unknown error in forceFinishForStarted, retrying:\n%s" % str(e)) + self.reconnect() + return self.forceFinishForStarted( thresholdTime, True) + except Exception,e: + self.notify.warning("Unknown error in forceFinishForStarted, giving up:\n%s" % str(e)) + return () + + def forceNeverStartedForCanStart(self, thresholdTime, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling forceNeverStartedForCanStart") + return () + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + cursor.execute(ttSQL.partyGetPartiesGoingToNeverStartedSELECT,(thresholdTime,)) + res = cursor.fetchall() + cursor.execute(ttSQL.partyForceNeverStartedForCanStartUPDATE ,(thresholdTime,)) + self.db.commit() + + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on forceNeverStartedForCanStart retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.forceNeverStartedForCanStart(thresholdTime, True) + else: + self.notify.warning("Unknown error in forceNeverStartedForCanStart, retrying:\n%s" % str(e)) + self.reconnect() + return self.forceNeverStartedForCanStart( thresholdTime, True) + except Exception,e: + self.notify.warning("Unknown error in forceNeverStartedForCanStart, giving up:\n%s" % str(e)) + return () + + + def changeMultiplePartiesStatus(self, partyIds, newPartyStatus, isRetry=False): + """ + isRetry indicates whether this attempt is a retry or not. + """ + if not self.sqlAvailable: + self.notify.debug("sqlAvailable was false when calling changeMultiplePartiesStatus") + return () + + if not partyIds: + self.notify.debug("empty list in partyIds for changeMultiplePartiesStatus") + return() + + cursor = MySQLdb.cursors.DictCursor(self.db) + try: + cursor.execute("USE `%s`"%self.dbname) + inClause = self.convertListToSQLString(partyIds) + sqlString = ttSQL.partyMultipleStatusUPDATE + parameters = ( newPartyStatus, inClause) + execStr = sqlString % parameters + cursor.execute(execStr) + self.db.commit() + res = cursor.fetchall() + #self.notify.debug("Select was successful in ttMaildb, returning %s" % str(res)) + return res + + except _mysql_exceptions.OperationalError,e: + if isRetry: + self.notify.warning("Error on changeMultiplePartiesStatus retry, giving up:\n%s" % str(e)) + return () + elif e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: + self.reconnect() + return self.changeMultiplePartiesStatus(partyIds, newPartyStatus, True) + else: + self.notify.warning("Unknown error in changeMultiplePartiesStatus, retrying:\n%s" % str(e)) + self.reconnect() + return self.changeMultiplePartiesStatus( partyIds, newPartyStatus, True) + except Exception,e: + self.notify.warning("Unknown error in changeMultiplePartiesStatus, giving up:\n%s" % str(e)) + return () diff --git a/toontown/src/uberdog/ttSQL.py b/toontown/src/uberdog/ttSQL.py new file mode 100644 index 0000000..0448523 --- /dev/null +++ b/toontown/src/uberdog/ttSQL.py @@ -0,0 +1,74 @@ +from toontown.parties import PartyGlobals +#sbMaildb + +getMailSELECT = "SELECT * FROM ttrecipientmail WHERE recipientId=%s" + +putMailINSERT = "INSERT INTO ttrecipientmail (recipientId,senderId,message,dateSent) VALUES (%s,%s,%s,NULL)" + +deleteMailDELETE = "DELETE FROM ttrecipientmail WHERE messageId=%s AND recipientId=%s" + +getPartySELECT = "SELECT * FROM ttParty WHERE partyId=%s" + +getMultiplePartiesSELECT = "SELECT * FROM ttParty WHERE partyId IN %s" + +getMultiplePartiesSortedSELECT = "SELECT * FROM ttParty WHERE partyId IN %s ORDER BY startTime" + +getPartyOfHostSELECT = "SELECT * FROM ttParty WHERE hostId=%s" + +getPartyOfHostSortedSELECT = "SELECT * FROM ttParty WHERE hostId=%s ORDER BY startTime" + +getPartyOfHostMatchingStatusSELECT = "SELECT * FROM ttParty WHERE hostId=%s and statusId=%s" + +putPartyINSERT = "INSERT INTO ttParty (hostId, startTime, endTime, isPrivate, inviteTheme, activities, decorations, statusId, creationTime) VALUES (%s,%s,%s,%s,%s,%s,%s,%s, now())" + +partyPrivateUPDATE = "UPDATE ttParty SET isPrivate=%s where partyId=%s" + +partyStatusUPDATE = "UPDATE ttParty SET statusId=%s where partyId=%s" + +partyMultipleStatusUPDATE = "UPDATE ttParty SET statusId=%s where partyId IN %s" + +partyForceFinishForStartedUPDATE = "UPDATE ttParty SET statusId=" + str(PartyGlobals.PartyStatus.Finished) + " where statusId=" + str(PartyGlobals.PartyStatus.Started)+ " and endTime< %s" + +partyGetPartiesGoingToFinishedSELECT = "SELECT partyId, hostId FROM ttParty where statusId=" + str(PartyGlobals.PartyStatus.Started)+ " and endTime< %s" + +partyForceNeverStartedForCanStartUPDATE = "UPDATE ttParty SET statusId=" + str(PartyGlobals.PartyStatus.NeverStarted) + " where statusId=" + str(PartyGlobals.PartyStatus.CanStart)+ " and endTime< %s" + +partyGetPartiesGoingToNeverStartedSELECT = "SELECT partyId, hostId FROM ttParty where statusId=" + str(PartyGlobals.PartyStatus.CanStart)+ " and endTime< %s" + +deletePartyDELETE = "DELETE FROM ttParty WHERE hostId=%s" + +getInvitesSELECT = "SELECT * FROM ttInvite WHERE guestId=%s" + +putInviteINSERT = "INSERT INTO ttInvite (partyId, guestId) VALUES (%s,%s)" + +deleteInviteByPartyDELETE = "DELETE FROM ttInvite WHERE partyId=%s" + +getRepliesSELECT = "SELECT * FROM ttInvite where partyId=%s" + +getOneInviteSELECT = "SELECT * FROM ttInvite WHERE inviteId=%s" + +inviteUPDATE = "UPDATE ttInvite SET statusId=%s WHERE inviteId=%s" + +getInviteesOfPartySELECT = "SELECT guestId FROM ttInvite where partyId=%s" + +getPartiesAvailableToStart = "SELECT partyId,hostId FROM ttParty WHERE startTime <= %s AND statusId = %s" + +getLastHostParty = "SELECT MAX(partyId) FROM ttParty where hostId = %s" + +getMultiplePartiesSortedFutureNotCancelledSELECT = "SELECT * FROM ttParty WHERE partyId IN %s ORDER BY startTime" + +getNonCancelledFuturePartiesSELECT = "SELECT * FROM ttParty WHERE (partyId IN %s) and (startTime >= '%s') and statusId!=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime LIMIT %s" + +getCancelledFuturePartiesSELECT = "SELECT * FROM ttParty WHERE (partyId IN %s) and (startTime >= '%s') and statusId=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime LIMIT %s" + +getNonCancelledPastPartiesSELECT = "SELECT * FROM ttParty WHERE (partyId IN %s) and (startTime < '%s') and statusId!=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime DESC LIMIT %s" + +getCancelledPastPartiesSELECT = "SELECT * FROM ttParty WHERE (partyId IN %s) and (startTime < '%s') and statusId=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime DESC LIMIT %s" + +getHostNonCancelledFuturePartiesSELECT = "SELECT * FROM ttParty WHERE (hostId = %s) and (startTime >= '%s') and statusId!=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime LIMIT %s" + +getHostCancelledFuturePartiesSELECT = "SELECT * FROM ttParty WHERE (hostId = %s) and (startTime >= '%s') and statusId=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime LIMIT %s" + +getHostNonCancelledPastPartiesSELECT = "SELECT * FROM ttParty WHERE (hostId = %s) and (startTime < '%s') and statusId!=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime DESC LIMIT %s" + +getHostCancelledPastPartiesSELECT = "SELECT * FROM ttParty WHERE (hostId = %s) and (startTime < '%s') and statusId=" + str(PartyGlobals.PartyStatus.Cancelled) + " ORDER BY startTime DESC LIMIT %s"